From 1d1779ebdf2d25a178cdc356799b4897d9105368 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 30 Nov 2015 16:54:47 +0100 Subject: [PATCH 0001/2266] Add license file --- LICENSE | 1 - 1 file changed, 1 deletion(-) diff --git a/LICENSE b/LICENSE index dceee0974..f2b059cd8 100644 --- a/LICENSE +++ b/LICENSE @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - From eec20c0e2533a94091476762f24353652d105359 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 30 Nov 2015 16:55:01 +0100 Subject: [PATCH 0002/2266] Draft first version of dev plan --- DevelopmentPlan.md | 74 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 DevelopmentPlan.md diff --git a/DevelopmentPlan.md b/DevelopmentPlan.md new file mode 100644 index 000000000..5665f0ff8 --- /dev/null +++ b/DevelopmentPlan.md @@ -0,0 +1,74 @@ +Development Plan +================ + +## Milestone 1 +Have a fake gateway able to mock the behavior of an existing real gateway. This will be used +mainly for testing and ensuring the correctness of other components. + +- [ ] Fake gateway + - [ ] Types, packages and data structures in use + - [ ] Emit udp packets towards a server + - [ ] Handle reception acknowledgement from server + - [ ] Generate and serialize json rxpk object(s) + - [ ] Generate and serialize json stat object(s) + - [ ] Simulate fake end-devices activity + + +## Milestone 2 +Handle an uplink process that can forward packet coming from a gateway to a simple end-server +(fake handler). We handle no mac command and we does not care about registration yet. The +system will just forward messages using pre-configured end-device addresses. + + +- [ ] Basic Router + - [ ] Detail the list of features + + +- [ ] Basic Broker + - [ ] Detail the list of features + + +- [ ] Minimalist Dumb Network-Server + - [ ] Detail the list of features + +## Milestone 3 +Handle OTAA and downlink accept message. We still not allow mac commands from neither the +end-device nor a network server. Also, no messages can be sent by an application or whatever. +The only downlink message we accept is the join-accept / join-reject message sent during an +OTAA. + +- [ ] Extend Router + - [ ] Detail the list of features + + +- [ ] Extend Broker + - [ ] Detail the list of features + + +- [ ] Extend Network-server + - [ ] Detail the list of features + + +- [ ] Minimalist Handler + - [ ] Detail the list of features + +## Milestone 4 +Allow transmission of downlink messages from an application. Messages will be shipped as +response after an uplink transmission from a node. + +- [ ] Extend Broker + - [ ] Detail the list of features + + +- [ ] Extend Handler + - [ ] Detail the list of features + + +- [ ] Fake minismalist Application server + - [ ] Detail the list of features + +## Milestone 5 +Handle more complexe commands and their corresponding acknowledgement. + +- [ ] Extend Network server + - [ ] Detail the list of features From 9a21b11c9aef9042e2fa486038b94e87062fe01a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 14:28:09 +0100 Subject: [PATCH 0003/2266] Detail types and setup package organisation. --- DevelopmentPlan.md | 88 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 86 insertions(+), 2 deletions(-) diff --git a/DevelopmentPlan.md b/DevelopmentPlan.md index 5665f0ff8..3ad2821e9 100644 --- a/DevelopmentPlan.md +++ b/DevelopmentPlan.md @@ -6,13 +6,97 @@ Have a fake gateway able to mock the behavior of an existing real gateway. This mainly for testing and ensuring the correctness of other components. - [ ] Fake gateway - - [ ] Types, packages and data structures in use - - [ ] Emit udp packets towards a server + - [x] Types, packages and data structures in use + - [x] Emit udp packets towards a server - [ ] Handle reception acknowledgement from server - [ ] Generate and serialize json rxpk object(s) - [ ] Generate and serialize json stat object(s) - [ ] Simulate fake end-devices activity +```go +type gateway struct { + id []string + routers []string + faking boolean + started boolean + status Stat +} + +type RXPK struct { + Chan uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr string `json:"codr"` // LoRa ECC coding rate identifier + Data string `json:"data"` // Base64 encoded RF packet payload, padded + Datr string `json:"datr"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Rfch uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) + Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Stat int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time time.Time `json:"time"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +type TXPK struct { + Codr string `json:"codr"` // LoRa ECC coding rate identifier + Data string `json:"data"` // Base64 encoded RF packet payload, padding optional + Datr string `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) + Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) + Ipol bool `json:"ipol"` // Lora modulation polarization inversion + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Ncrc bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) + Powe uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) + Prea uint `json:"prea"` // RF preamble size (unsigned integer) + Rfch uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) + Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Time time.Time `json:"time"` // Send packet at a certain time (GPS synchronization required) + Tmst uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) +} + +type Stat struct { + Ackr float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged + Alti int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) + Dwnb uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) + Lati float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) + Long float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) + Rxnb uint `json:"rxnb"` // Number of radio packets received (unsigned integer) + Rxok uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC + Time time.Time `json:"time"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb uint `json:"txnb"` // Number of packets emitted (unsigned integer) +} + +type Option struct { + key string + value {}interface +} + +type Packet struct { + version byte + token [2]byte + identifier byte + payload []byte +} + +type Gateway interface { + Start () error + EmitData (data []byte, options ...Option) error + EmitStat (options ...Option) error + Mimic (errors <-chan error) + + generateRXPK(nb int, options ...Option) []RXPK + generateStat(options ...Option) Stat + createPushData(rxpk []RXPK, stat Stat) error, []byte + pull(routers ...string) + decodeResponse(response []byte) error, Packet +} + +Create (id string, routers ...string) error, *Gateway +``` + ## Milestone 2 Handle an uplink process that can forward packet coming from a gateway to a simple end-server From 0de941760127a944e4bee216027ee09760d9ec79 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 15:29:55 +0100 Subject: [PATCH 0004/2266] Define test for Parse util method in gateway protocol --- lorawan/gateway/protocol_test.go | 98 ++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 lorawan/gateway/protocol_test.go diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go new file mode 100644 index 000000000..a97ae4212 --- /dev/null +++ b/lorawan/gateway/protocol_test.go @@ -0,0 +1,98 @@ +package protocol + +import ( + "testing" + "bytes" +) + +// ------------------------------------------------------------ +// ------------------------- Parse (raw []byte) (error, Packet) +// ------------------------------------------------------------ + +// Parse() with valid raw data and no payload +func TestParse1(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x00} + err, packet := Parse(raw) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload != nil { + t.Errorf("Invalid parsed payload: %x", packet.Payload) + } +} + +// Parse() with valid raw data and payload +func TestParse2(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} + err, packet := Parse(raw) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if !bytes.Equal([]byte{0x42, 0x14, 0x42, 0x14}, packet.Payload) { + t.Errorf("Invalid parsed payload: %x", packet.Payload) + } +} + +// Parse() with an invalid version number +func TestParse3(t *testing.T) { + raw := []byte{0x00, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} + err, _ := Parse(raw) + + if err == nil { + t.Errorf("Successfully parsed an incorrect version number") + } +} + +// Parse() with an invalid raw message +func TestParse4(t *testing.T) { + raw1 := []byte{0x01} + var raw2 []byte + err1, _ := Parse(raw1) + err2, _ := Parse(raw2) + + if err1 == nil { + t.Errorf("Successfully parsed an raw message") + } + + if err2 == nil { + t.Errorf("Successfully parsed a nil raw message") + } +} + +// Parse() with an invalid identifier +func TestParse5(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0xFF, 0x42, 0x14, 0x42, 0x14} + err, _ := Parse(raw) + + if err == nil { + t.Errorf("Successfully parsed an incorrect identifier") + } +} From 66bcc6832b53cb00bb35f4359776abefa8292895 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 15:30:33 +0100 Subject: [PATCH 0005/2266] Define gateway protocol types and structures --- lorawan/gateway/protocol.go | 87 +++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 lorawan/gateway/protocol.go diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go new file mode 100644 index 000000000..2e7cfe912 --- /dev/null +++ b/lorawan/gateway/protocol.go @@ -0,0 +1,87 @@ +// Copyright © 2015 Matthias Benkort +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package protocol + +import ( + "errors" + "time" +) + +// Uplink json message format sent by the gateway +// More details can be found in the following Semtech protocol: +// https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +type RXPK struct { + Chan uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr string `json:"codr"` // LoRa ECC coding rate identifier + Data string `json:"data"` // Base64 encoded RF packet payload, padded + Datr string `json:"datr"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Rfch uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) + Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Stat int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time time.Time `json:"time"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +// Downlink json message format received by the gateway +// Most field are optional +// More details can be found in the following Semtech protocol: +// https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +type TXPK struct { + Codr string `json:"codr"` // LoRa ECC coding rate identifier + Data string `json:"data"` // Base64 encoded RF packet payload, padding optional + Datr string `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) + Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) + Ipol bool `json:"ipol"` // Lora modulation polarization inversion + Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Ncrc bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) + Powe uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) + Prea uint `json:"prea"` // RF preamble size (unsigned integer) + Rfch uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) + Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Time time.Time `json:"time"` // Send packet at a certain time (GPS synchronization required) + Tmst uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) +} + +// Status json message format sent by the gateway +// More details can be found in the following Semtech protocol: +// https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +type Stat struct { + Ackr float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged + Alti int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) + Dwnb uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) + Lati float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) + Long float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) + Rxnb uint `json:"rxnb"` // Number of radio packets received (unsigned integer) + Rxok uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC + Time time.Time `json:"time"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb uint `json:"txnb"` // Number of packets emitted (unsigned integer) +} + +// Packet as seen by the gateway. The payload is optional and could be nil, otherwise, it is a +// Base64 encoding of one of the related message format (RXPK, TXPK or Stat). +// - Version refers to the protocol version, always 1 here +// - Identifier refers to a packet command (PUSH_DATA, PUSH_ACK, PULL_DATA, PULL_RESP, PULL_ACK) +// - Token is a random number generated by the gateway on some request +type Packet struct { + Version byte + Token []byte + Identifier byte + Payload []byte +} + +// Available packet commands +const ( + PUSH_DATA byte = iota + PUSH_ACK + PULL_DATA + PULL_RESP + PULL_ACK +) From 17729be533b9e0c111ab760eb63a4d95338983e1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 15:31:01 +0100 Subject: [PATCH 0006/2266] Add basic Parse method to parse raw response from a server --- lorawan/gateway/protocol.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 2e7cfe912..2bada67f5 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -85,3 +85,34 @@ const ( PULL_RESP PULL_ACK ) + +// Parse a raw response from a server and turn in into a packet +// Will return an error if the response fields are incorrect +func Parse(raw []byte) (error, *Packet) { + size := len(raw) + + if size < 3 { + return errors.New("Invalid raw data format"), nil + } + + packet := &Packet{ + raw[0], + raw[1:3], + raw[3], + nil, + } + + if packet.Version != 0x1 { + return errors.New("Unreckognized protocol version"), nil + } + + if packet.Identifier > PULL_ACK { + return errors.New("Unreckognized protocol identifier"), nil + } + + if size > 4 { + packet.Payload = raw[4:] + } + + return nil, packet +} From 2ff3a4577ad5a65e57bfe7c9fc53ffec081d2718 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 17:58:36 +0100 Subject: [PATCH 0007/2266] Enhance parsing to also decode json payloads --- lorawan/gateway/decode_utils.go | 54 +++++++++++++++++++++++++++++++++ lorawan/gateway/protocol.go | 22 +++++++++----- 2 files changed, 69 insertions(+), 7 deletions(-) create mode 100644 lorawan/gateway/decode_utils.go diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go new file mode 100644 index 000000000..6ec93768f --- /dev/null +++ b/lorawan/gateway/decode_utils.go @@ -0,0 +1,54 @@ +package protocol + +import ( + "errors" + "time" + "strings" + "encoding/json" +) + +type timeParser struct { + Value time.Time +} + +func (t *timeParser) UnmarshalJSON (raw []byte) error { + var err error + value := strings.Trim(string(raw), `"`) + t.Value, err = time.Parse("2006-01-02 15:04:05 GMT", value) + if err != nil { t.Value, err = time.Parse(time.RFC3339, value) } + if err != nil { t.Value, err = time.Parse(time.RFC3339Nano, value) } + if err != nil { return errors.New("Unkown date format. Unable to parse time") } + return nil +} + +func decodePayload (raw []byte) (error, *Payload) { + payload := &Payload{raw, nil, nil, nil} + timeStruct := &struct{ + Stat *struct{ Time timeParser `json:"time"` } `json:"stat"` + RXPK *[]struct{ Time timeParser `json:"time"` } `json:"rxpk"` + TXPK *struct{ Time timeParser `json:"time"` } `json:"txpk"` + }{} + + err := json.Unmarshal(raw, payload) + err = json.Unmarshal(raw, timeStruct) + + if err != nil { + return err, nil + } + + if timeStruct.Stat != nil { + payload.Stat.Time = timeStruct.Stat.Time.Value + } + + if timeStruct.RXPK != nil { + for i, x := range(*timeStruct.RXPK) { + (*payload.RXPK)[i].Time = x.Time.Value + } + } + + if timeStruct.TXPK != nil { + payload.TXPK.Time = timeStruct.TXPK.Time.Value + } + + return nil, payload +} diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 2bada67f5..91c6853a1 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -23,7 +23,7 @@ type RXPK struct { Rssi int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) Stat int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time time.Time `json:"time"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Time time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format Tmst uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) } @@ -45,7 +45,7 @@ type TXPK struct { Prea uint `json:"prea"` // RF preamble size (unsigned integer) Rfch uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Time time.Time `json:"time"` // Send packet at a certain time (GPS synchronization required) + Time time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) Tmst uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) } @@ -61,7 +61,7 @@ type Stat struct { Rxfw uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) Rxnb uint `json:"rxnb"` // Number of radio packets received (unsigned integer) Rxok uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC - Time time.Time `json:"time"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Time time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format Txnb uint `json:"txnb"` // Number of packets emitted (unsigned integer) } @@ -74,7 +74,14 @@ type Packet struct { Version byte Token []byte Identifier byte - Payload []byte + Payload *Payload +} + +type Payload struct { + Raw []byte `json:"-"` + RXPK *[]RXPK `json:"rxpk"` + Stat *Stat `json:"stat"` + TXPK *TXPK `json:"txpk"` } // Available packet commands @@ -88,7 +95,7 @@ const ( // Parse a raw response from a server and turn in into a packet // Will return an error if the response fields are incorrect -func Parse(raw []byte) (error, *Packet) { +func Parse (raw []byte) (error, *Packet) { size := len(raw) if size < 3 { @@ -110,9 +117,10 @@ func Parse(raw []byte) (error, *Packet) { return errors.New("Unreckognized protocol identifier"), nil } + var err error if size > 4 { - packet.Payload = raw[4:] + err, packet.Payload = decodePayload(raw[4:]) } - return nil, packet + return err, packet } From 4660df0492cce32ac55913675d8e7b4d6bd8b2a6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 18:59:03 +0100 Subject: [PATCH 0008/2266] Handle datr parsing, being either number or string --- lorawan/gateway/decode_utils.go | 46 +++++++++++++++++++++++++-------- lorawan/gateway/protocol.go | 4 +-- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go index 6ec93768f..cdb3df5d3 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode_utils.go @@ -21,33 +21,57 @@ func (t *timeParser) UnmarshalJSON (raw []byte) error { return nil } +type datrParser struct { + Value string +} + +func (d *datrParser) UnmarshalJSON (raw []byte) error { + d.Value = strings.Trim(string(raw), `"`) + + if d.Value == "" { + return errors.New("Invalid datr format") + } + + return nil +} + func decodePayload (raw []byte) (error, *Payload) { payload := &Payload{raw, nil, nil, nil} - timeStruct := &struct{ - Stat *struct{ Time timeParser `json:"time"` } `json:"stat"` - RXPK *[]struct{ Time timeParser `json:"time"` } `json:"rxpk"` - TXPK *struct{ Time timeParser `json:"time"` } `json:"txpk"` + customStruct := &struct{ + Stat *struct{ + Time timeParser `json:"time"` + } `json:"stat"` + RXPK *[]struct{ + Time timeParser `json:"time"` + Datr datrParser `json:"datr"` + } `json:"rxpk"` + TXPK *struct{ + Time timeParser `json:"time"` + Datr datrParser `json:"datr"` + } `json:"txpk"` }{} err := json.Unmarshal(raw, payload) - err = json.Unmarshal(raw, timeStruct) + err = json.Unmarshal(raw, customStruct) if err != nil { return err, nil } - if timeStruct.Stat != nil { - payload.Stat.Time = timeStruct.Stat.Time.Value + if customStruct.Stat != nil { + payload.Stat.Time = customStruct.Stat.Time.Value } - if timeStruct.RXPK != nil { - for i, x := range(*timeStruct.RXPK) { + if customStruct.RXPK != nil { + for i, x := range(*customStruct.RXPK) { (*payload.RXPK)[i].Time = x.Time.Value + (*payload.RXPK)[i].Datr = x.Datr.Value } } - if timeStruct.TXPK != nil { - payload.TXPK.Time = timeStruct.TXPK.Time.Value + if customStruct.TXPK != nil { + payload.TXPK.Time = customStruct.TXPK.Time.Value + payload.TXPK.Datr = customStruct.TXPK.Datr.Value } return nil, payload diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 91c6853a1..a0e1c547c 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -15,7 +15,7 @@ type RXPK struct { Chan uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) Codr string `json:"codr"` // LoRa ECC coding rate identifier Data string `json:"data"` // Base64 encoded RF packet payload, padded - Datr string `json:"datr"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Datr string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier Freq float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) Lsnr float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" @@ -34,7 +34,7 @@ type RXPK struct { type TXPK struct { Codr string `json:"codr"` // LoRa ECC coding rate identifier Data string `json:"data"` // Base64 encoded RF packet payload, padding optional - Datr string `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Datr string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) Fdev uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) From dd09077e25f169c9cb58c474a86add1f17a5c377 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 19:32:09 +0100 Subject: [PATCH 0009/2266] Fix issue with missing optional params --- lorawan/gateway/decode_utils.go | 29 ++++++++++++++++++++++------- lorawan/gateway/protocol.go | 8 +++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go index cdb3df5d3..ff0be2f04 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode_utils.go @@ -8,7 +8,8 @@ import ( ) type timeParser struct { - Value time.Time + Value time.Time + Parsed bool } func (t *timeParser) UnmarshalJSON (raw []byte) error { @@ -18,11 +19,14 @@ func (t *timeParser) UnmarshalJSON (raw []byte) error { if err != nil { t.Value, err = time.Parse(time.RFC3339, value) } if err != nil { t.Value, err = time.Parse(time.RFC3339Nano, value) } if err != nil { return errors.New("Unkown date format. Unable to parse time") } + + t.Parsed = true return nil } type datrParser struct { - Value string + Value string + Parsed bool } func (d *datrParser) UnmarshalJSON (raw []byte) error { @@ -32,6 +36,7 @@ func (d *datrParser) UnmarshalJSON (raw []byte) error { return errors.New("Invalid datr format") } + d.Parsed = true return nil } @@ -58,20 +63,30 @@ func decodePayload (raw []byte) (error, *Payload) { return err, nil } - if customStruct.Stat != nil { + if customStruct.Stat != nil && customStruct.Stat.Time.Parsed { payload.Stat.Time = customStruct.Stat.Time.Value } if customStruct.RXPK != nil { for i, x := range(*customStruct.RXPK) { - (*payload.RXPK)[i].Time = x.Time.Value - (*payload.RXPK)[i].Datr = x.Datr.Value + if x.Time.Parsed { + (*payload.RXPK)[i].Time = x.Time.Value + } + + if x.Datr.Parsed { + (*payload.RXPK)[i].Datr = x.Datr.Value + } } } if customStruct.TXPK != nil { - payload.TXPK.Time = customStruct.TXPK.Time.Value - payload.TXPK.Datr = customStruct.TXPK.Datr.Value + if customStruct.TXPK.Time.Parsed { + payload.TXPK.Time = customStruct.TXPK.Time.Value + } + + if customStruct.TXPK.Datr.Parsed { + payload.TXPK.Datr = customStruct.TXPK.Datr.Value + } } return nil, payload diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index a0e1c547c..436410985 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -74,6 +74,7 @@ type Packet struct { Version byte Token []byte Identifier byte + GatewayId []byte Payload *Payload } @@ -107,6 +108,7 @@ func Parse (raw []byte) (error, *Packet) { raw[1:3], raw[3], nil, + nil, } if packet.Version != 0x1 { @@ -117,8 +119,12 @@ func Parse (raw []byte) (error, *Packet) { return errors.New("Unreckognized protocol identifier"), nil } + if size >= 12 && packet.Identifier == PULL_DATA { + packet.GatewayId = raw[4:12] + } + var err error - if size > 4 { + if size > 4 && packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { err, packet.Payload = decodePayload(raw[4:]) } From 68719947e2ecae97502297a398bf3a83d3b12cf9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 19:32:36 +0100 Subject: [PATCH 0010/2266] Update tests. Still missing verifications for failure cases --- lorawan/gateway/protocol_test.go | 402 ++++++++++++++++++++++++++++++- 1 file changed, 393 insertions(+), 9 deletions(-) diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index a97ae4212..aafb2669b 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -3,6 +3,8 @@ package protocol import ( "testing" "bytes" + "reflect" + "time" ) // ------------------------------------------------------------ @@ -31,14 +33,28 @@ func TestParse1(t *testing.T) { } if packet.Payload != nil { - t.Errorf("Invalid parsed payload: %x", packet.Payload) + t.Errorf("Invalid parsed payload: % x", packet.Payload) } } -// Parse() with valid raw data and payload +// Parse() with valid raw data and stat payload func TestParse2(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} - err, packet := Parse(raw) + raw := []byte{0x01, 0x14, 0x14, 0x00} + payload := []byte(`{ + "stat": { + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 + } + }`) + err, packet := Parse(append(raw, payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -56,13 +72,348 @@ func TestParse2(t *testing.T) { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } - if !bytes.Equal([]byte{0x42, 0x14, 0x42, 0x14}, packet.Payload) { - t.Errorf("Invalid parsed payload: %x", packet.Payload) + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.Stat == nil { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } + + statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") + + stat := Stat{ + Time: statTime, + Lati: 46.24000, + Long: 3.25230, + Alti: 145, + Rxnb: 2, + Rxok: 2, + Rxfw: 2, + Ackr: 100.0, + Dwnb: 2, + Txnb: 2, + } + + if !reflect.DeepEqual(stat, *packet.Payload.Stat) { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) } + } -// Parse() with an invalid version number +// Parse() with valid raw data and rxpk payloads func TestParse3(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x00} + + payload := []byte(`{ + "rxpk":[ + { + "chan":2, + "codr":"4/6", + "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + "datr":"SF7BW125", + "freq":866.349812, + "lsnr":5.1, + "modu":"LORA", + "rfch":0, + "rssi":-35, + "size":32, + "stat":1, + "time":"2013-03-31T16:21:17.528002Z", + "tmst":3512348611 + },{ + "chan":9, + "data":"VEVTVF9QQUNLRVRfMTIzNA==", + "datr":50000, + "freq":869.1, + "modu":"FSK", + "rfch":1, + "rssi":-75, + "size":16, + "stat":1, + "time":"2013-03-31T16:21:17.530974Z", + "tmst":3512348514 + },{ + "chan":0, + "codr":"4/7", + "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass", + "datr":"SF10BW125", + "freq":863.00981, + "lsnr":5.5, + "modu":"LORA", + "rfch":0, + "rssi":-38, + "size":32, + "stat":1, + "time":"2013-03-31T16:21:17.532038Z", + "tmst":3316387610 + } + ] + }`) + + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } + + rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") + + rxpk := RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: "50000", + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: rxpkTime, + Tmst: 3512348514, + } + + if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } +} + +// Parse() with valid raw data and rxpk payloads + stat +func TestParse4(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x00} + + payload := []byte(`{ + "rxpk":[ + { + "chan":2, + "codr":"4/6", + "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + "datr":"SF7BW125", + "freq":866.349812, + "lsnr":5.1, + "modu":"LORA", + "rfch":0, + "rssi":-35, + "size":32, + "stat":1, + "time":"2013-03-31T16:21:17.528002Z", + "tmst":3512348611 + },{ + "chan":9, + "data":"VEVTVF9QQUNLRVRfMTIzNA==", + "datr":50000, + "freq":869.1, + "modu":"FSK", + "rfch":1, + "rssi":-75, + "size":16, + "stat":1, + "time":"2013-03-31T16:21:17.530974Z", + "tmst":3512348514 + },{ + "chan":0, + "codr":"4/7", + "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass", + "datr":"SF10BW125", + "freq":863.00981, + "lsnr":5.5, + "modu":"LORA", + "rfch":0, + "rssi":-38, + "size":32, + "stat":1, + "time":"2013-03-31T16:21:17.532038Z", + "tmst":3316387610 + } + ], + "stat": { + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 + } + }`) + + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } + + if packet.Payload.Stat == nil { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } + + rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") + + rxpk := RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: "50000", + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: rxpkTime, + Tmst: 3512348514, + } + + if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } + + statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") + + stat := Stat{ + Time: statTime, + Lati: 46.24000, + Long: 3.25230, + Alti: 145, + Rxnb: 2, + Rxok: 2, + Rxfw: 2, + Ackr: 100.0, + Dwnb: 2, + Txnb: 2, + } + + if !reflect.DeepEqual(stat, *packet.Payload.Stat) { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } +} +// Parse() with valid raw data and txpk payload +func TestParse5(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x03} + + payload := []byte(`{ + "txpk":{ + "imme":true, + "freq":864.123456, + "rfch":0, + "powe":14, + "modu":"LORA", + "datr":"SF11BW125", + "codr":"4/6", + "ipol":false, + "size":32, + "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" + } + }`) + + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x03 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.TXPK == nil { + t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) + } + + txpk := TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: "SF11BW125", + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + } + + if !reflect.DeepEqual(txpk, *packet.Payload.TXPK) { + t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) + } +} + +// Parse() with an invalid version number +func TestParse6(t *testing.T) { raw := []byte{0x00, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} err, _ := Parse(raw) @@ -72,7 +423,7 @@ func TestParse3(t *testing.T) { } // Parse() with an invalid raw message -func TestParse4(t *testing.T) { +func TestParse7(t *testing.T) { raw1 := []byte{0x01} var raw2 []byte err1, _ := Parse(raw1) @@ -88,7 +439,7 @@ func TestParse4(t *testing.T) { } // Parse() with an invalid identifier -func TestParse5(t *testing.T) { +func TestParse8(t *testing.T) { raw := []byte{0x01, 0x14, 0x14, 0xFF, 0x42, 0x14, 0x42, 0x14} err, _ := Parse(raw) @@ -96,3 +447,36 @@ func TestParse5(t *testing.T) { t.Errorf("Successfully parsed an incorrect identifier") } } + +// Parse() with an invalid payload +func TestParse9(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x00} + payload := []byte(`wrong`) + err, _ := Parse(append(raw, payload...)) + if err == nil { + t.Errorf("Successfully parsed an incorrect payload") + } +} + +// Parse() with an invalid date +func TestParse10(t *testing.T) { + raw := []byte{0x01, 0x14, 0x14, 0x00} + payload := []byte(`{ + "stat": { + "time":"null", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 + } + }`) + err, _ := Parse(append(raw, payload...)) + if err == nil { + t.Errorf("Successfully parsed an incorrect payload time") + } +} From ad94d0f1d5517d0d3282407c82b73e6f631aa18f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 19:42:39 +0100 Subject: [PATCH 0011/2266] Add License headers --- lorawan/gateway/decode_utils.go | 3 +++ lorawan/gateway/protocol_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go index ff0be2f04..0bcad2b8f 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode_utils.go @@ -1,3 +1,6 @@ +// Copyright © 2015 Matthias Benkort +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package protocol import ( diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index aafb2669b..d00f05f10 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -1,3 +1,6 @@ +// Copyright © 2015 Matthias Benkort +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package protocol import ( From 8a9b63e7a35a3d149881eee7d62f1981386a784e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 19:43:54 +0100 Subject: [PATCH 0012/2266] Format go files --- lorawan/gateway/decode_utils.go | 156 +++++---- lorawan/gateway/protocol.go | 26 +- lorawan/gateway/protocol_test.go | 565 ++++++++++++++++--------------- 3 files changed, 377 insertions(+), 370 deletions(-) diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go index 0bcad2b8f..bcc740ab3 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode_utils.go @@ -4,93 +4,99 @@ package protocol import ( - "errors" - "time" - "strings" - "encoding/json" + "encoding/json" + "errors" + "strings" + "time" ) type timeParser struct { - Value time.Time - Parsed bool + Value time.Time + Parsed bool } -func (t *timeParser) UnmarshalJSON (raw []byte) error { - var err error - value := strings.Trim(string(raw), `"`) - t.Value, err = time.Parse("2006-01-02 15:04:05 GMT", value) - if err != nil { t.Value, err = time.Parse(time.RFC3339, value) } - if err != nil { t.Value, err = time.Parse(time.RFC3339Nano, value) } - if err != nil { return errors.New("Unkown date format. Unable to parse time") } - - t.Parsed = true - return nil +func (t *timeParser) UnmarshalJSON(raw []byte) error { + var err error + value := strings.Trim(string(raw), `"`) + t.Value, err = time.Parse("2006-01-02 15:04:05 GMT", value) + if err != nil { + t.Value, err = time.Parse(time.RFC3339, value) + } + if err != nil { + t.Value, err = time.Parse(time.RFC3339Nano, value) + } + if err != nil { + return errors.New("Unkown date format. Unable to parse time") + } + + t.Parsed = true + return nil } type datrParser struct { - Value string - Parsed bool + Value string + Parsed bool } -func (d *datrParser) UnmarshalJSON (raw []byte) error { - d.Value = strings.Trim(string(raw), `"`) +func (d *datrParser) UnmarshalJSON(raw []byte) error { + d.Value = strings.Trim(string(raw), `"`) - if d.Value == "" { - return errors.New("Invalid datr format") - } + if d.Value == "" { + return errors.New("Invalid datr format") + } - d.Parsed = true - return nil + d.Parsed = true + return nil } -func decodePayload (raw []byte) (error, *Payload) { - payload := &Payload{raw, nil, nil, nil} - customStruct := &struct{ - Stat *struct{ - Time timeParser `json:"time"` - } `json:"stat"` - RXPK *[]struct{ - Time timeParser `json:"time"` - Datr datrParser `json:"datr"` - } `json:"rxpk"` - TXPK *struct{ - Time timeParser `json:"time"` - Datr datrParser `json:"datr"` - } `json:"txpk"` - }{} - - err := json.Unmarshal(raw, payload) - err = json.Unmarshal(raw, customStruct) - - if err != nil { - return err, nil - } - - if customStruct.Stat != nil && customStruct.Stat.Time.Parsed { - payload.Stat.Time = customStruct.Stat.Time.Value - } - - if customStruct.RXPK != nil { - for i, x := range(*customStruct.RXPK) { - if x.Time.Parsed { - (*payload.RXPK)[i].Time = x.Time.Value - } - - if x.Datr.Parsed { - (*payload.RXPK)[i].Datr = x.Datr.Value - } - } - } - - if customStruct.TXPK != nil { - if customStruct.TXPK.Time.Parsed { - payload.TXPK.Time = customStruct.TXPK.Time.Value - } - - if customStruct.TXPK.Datr.Parsed { - payload.TXPK.Datr = customStruct.TXPK.Datr.Value - } - } - - return nil, payload +func decodePayload(raw []byte) (error, *Payload) { + payload := &Payload{raw, nil, nil, nil} + customStruct := &struct { + Stat *struct { + Time timeParser `json:"time"` + } `json:"stat"` + RXPK *[]struct { + Time timeParser `json:"time"` + Datr datrParser `json:"datr"` + } `json:"rxpk"` + TXPK *struct { + Time timeParser `json:"time"` + Datr datrParser `json:"datr"` + } `json:"txpk"` + }{} + + err := json.Unmarshal(raw, payload) + err = json.Unmarshal(raw, customStruct) + + if err != nil { + return err, nil + } + + if customStruct.Stat != nil && customStruct.Stat.Time.Parsed { + payload.Stat.Time = customStruct.Stat.Time.Value + } + + if customStruct.RXPK != nil { + for i, x := range *customStruct.RXPK { + if x.Time.Parsed { + (*payload.RXPK)[i].Time = x.Time.Value + } + + if x.Datr.Parsed { + (*payload.RXPK)[i].Datr = x.Datr.Value + } + } + } + + if customStruct.TXPK != nil { + if customStruct.TXPK.Time.Parsed { + payload.TXPK.Time = customStruct.TXPK.Time.Value + } + + if customStruct.TXPK.Datr.Parsed { + payload.TXPK.Datr = customStruct.TXPK.Datr.Value + } + } + + return nil, payload } diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 436410985..94f32c745 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -74,15 +74,15 @@ type Packet struct { Version byte Token []byte Identifier byte - GatewayId []byte + GatewayId []byte Payload *Payload } type Payload struct { - Raw []byte `json:"-"` - RXPK *[]RXPK `json:"rxpk"` - Stat *Stat `json:"stat"` - TXPK *TXPK `json:"txpk"` + Raw []byte `json:"-"` + RXPK *[]RXPK `json:"rxpk"` + Stat *Stat `json:"stat"` + TXPK *TXPK `json:"txpk"` } // Available packet commands @@ -96,7 +96,7 @@ const ( // Parse a raw response from a server and turn in into a packet // Will return an error if the response fields are incorrect -func Parse (raw []byte) (error, *Packet) { +func Parse(raw []byte) (error, *Packet) { size := len(raw) if size < 3 { @@ -107,8 +107,8 @@ func Parse (raw []byte) (error, *Packet) { raw[0], raw[1:3], raw[3], - nil, - nil, + nil, + nil, } if packet.Version != 0x1 { @@ -119,13 +119,13 @@ func Parse (raw []byte) (error, *Packet) { return errors.New("Unreckognized protocol identifier"), nil } - if size >= 12 && packet.Identifier == PULL_DATA { - packet.GatewayId = raw[4:12] - } + if size >= 12 && packet.Identifier == PULL_DATA { + packet.GatewayId = raw[4:12] + } - var err error + var err error if size > 4 && packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { - err, packet.Payload = decodePayload(raw[4:]) + err, packet.Payload = decodePayload(raw[4:]) } return err, packet diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index d00f05f10..0bb7b4484 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -4,10 +4,10 @@ package protocol import ( - "testing" - "bytes" - "reflect" - "time" + "bytes" + "reflect" + "testing" + "time" ) // ------------------------------------------------------------ @@ -16,34 +16,34 @@ import ( // Parse() with valid raw data and no payload func TestParse1(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - err, packet := Parse(raw) + raw := []byte{0x01, 0x14, 0x14, 0x00} + err, packet := Parse(raw) - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } - if packet.Version != 0x01 { - t.Errorf("Invalid parsed version: %x", packet.Version) - } + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } - if packet.Identifier != 0x00 { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } - if packet.Payload != nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } + if packet.Payload != nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } } // Parse() with valid raw data and stat payload func TestParse2(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - payload := []byte(`{ + raw := []byte{0x01, 0x14, 0x14, 0x00} + payload := []byte(`{ "stat": { "time":"2014-01-12 08:59:28 GMT", "lati":46.24000, @@ -57,63 +57,63 @@ func TestParse2(t *testing.T) { "txnb":2 } }`) - err, packet := Parse(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != 0x01 { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != 0x00 { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.Stat == nil { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } - - statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") - - stat := Stat{ - Time: statTime, - Lati: 46.24000, - Long: 3.25230, - Alti: 145, - Rxnb: 2, - Rxok: 2, - Rxfw: 2, - Ackr: 100.0, - Dwnb: 2, - Txnb: 2, - } - - if !reflect.DeepEqual(stat, *packet.Payload.Stat) { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.Stat == nil { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } + + statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") + + stat := Stat{ + Time: statTime, + Lati: 46.24000, + Long: 3.25230, + Alti: 145, + Rxnb: 2, + Rxok: 2, + Rxfw: 2, + Ackr: 100.0, + Dwnb: 2, + Txnb: 2, + } + + if !reflect.DeepEqual(stat, *packet.Payload.Stat) { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } } // Parse() with valid raw data and rxpk payloads func TestParse3(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} + raw := []byte{0x01, 0x14, 0x14, 0x00} - payload := []byte(`{ + payload := []byte(`{ "rxpk":[ { "chan":2, @@ -159,63 +159,63 @@ func TestParse3(t *testing.T) { ] }`) - err, packet := Parse(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != 0x01 { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != 0x00 { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } - - rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") - - rxpk := RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", - Datr: "50000", - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, - Time: rxpkTime, - Tmst: 3512348514, - } - - if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } + + rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") + + rxpk := RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: "50000", + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: rxpkTime, + Tmst: 3512348514, + } + + if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } } // Parse() with valid raw data and rxpk payloads + stat func TestParse4(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} + raw := []byte{0x01, 0x14, 0x14, 0x00} - payload := []byte(`{ + payload := []byte(`{ "rxpk":[ { "chan":2, @@ -273,85 +273,86 @@ func TestParse4(t *testing.T) { } }`) - err, packet := Parse(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != 0x01 { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != 0x00 { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } - - if packet.Payload.Stat == nil { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } - - rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") - - rxpk := RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", - Datr: "50000", - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, - Time: rxpkTime, - Tmst: 3512348514, - } - - if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } - - statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") - - stat := Stat{ - Time: statTime, - Lati: 46.24000, - Long: 3.25230, - Alti: 145, - Rxnb: 2, - Rxok: 2, - Rxfw: 2, - Ackr: 100.0, - Dwnb: 2, - Txnb: 2, - } - - if !reflect.DeepEqual(stat, *packet.Payload.Stat) { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x00 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } + + if packet.Payload.Stat == nil { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } + + rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") + + rxpk := RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: "50000", + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: rxpkTime, + Tmst: 3512348514, + } + + if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { + t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) + } + + statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") + + stat := Stat{ + Time: statTime, + Lati: 46.24000, + Long: 3.25230, + Alti: 145, + Rxnb: 2, + Rxok: 2, + Rxfw: 2, + Ackr: 100.0, + Dwnb: 2, + Txnb: 2, + } + + if !reflect.DeepEqual(stat, *packet.Payload.Stat) { + t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) + } } + // Parse() with valid raw data and txpk payload func TestParse5(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x03} + raw := []byte{0x01, 0x14, 0x14, 0x03} - payload := []byte(`{ + payload := []byte(`{ "txpk":{ "imme":true, "freq":864.123456, @@ -366,105 +367,105 @@ func TestParse5(t *testing.T) { } }`) - err, packet := Parse(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != 0x01 { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != 0x03 { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.TXPK == nil { - t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) - } - - txpk := TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", - Datr: "SF11BW125", - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", - } - - if !reflect.DeepEqual(txpk, *packet.Payload.TXPK) { - t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) - } + err, packet := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != 0x01 { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != 0x03 { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload == nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + return + } + + if !bytes.Equal(payload, packet.Payload.Raw) { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.Payload.TXPK == nil { + t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) + } + + txpk := TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: "SF11BW125", + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + } + + if !reflect.DeepEqual(txpk, *packet.Payload.TXPK) { + t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) + } } // Parse() with an invalid version number func TestParse6(t *testing.T) { - raw := []byte{0x00, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} - err, _ := Parse(raw) + raw := []byte{0x00, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} + err, _ := Parse(raw) - if err == nil { - t.Errorf("Successfully parsed an incorrect version number") - } + if err == nil { + t.Errorf("Successfully parsed an incorrect version number") + } } // Parse() with an invalid raw message func TestParse7(t *testing.T) { - raw1 := []byte{0x01} - var raw2 []byte - err1, _ := Parse(raw1) - err2, _ := Parse(raw2) - - if err1 == nil { - t.Errorf("Successfully parsed an raw message") - } - - if err2 == nil { - t.Errorf("Successfully parsed a nil raw message") - } + raw1 := []byte{0x01} + var raw2 []byte + err1, _ := Parse(raw1) + err2, _ := Parse(raw2) + + if err1 == nil { + t.Errorf("Successfully parsed an raw message") + } + + if err2 == nil { + t.Errorf("Successfully parsed a nil raw message") + } } // Parse() with an invalid identifier func TestParse8(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0xFF, 0x42, 0x14, 0x42, 0x14} - err, _ := Parse(raw) + raw := []byte{0x01, 0x14, 0x14, 0xFF, 0x42, 0x14, 0x42, 0x14} + err, _ := Parse(raw) - if err == nil { - t.Errorf("Successfully parsed an incorrect identifier") - } + if err == nil { + t.Errorf("Successfully parsed an incorrect identifier") + } } // Parse() with an invalid payload func TestParse9(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - payload := []byte(`wrong`) - err, _ := Parse(append(raw, payload...)) - if err == nil { - t.Errorf("Successfully parsed an incorrect payload") - } + raw := []byte{0x01, 0x14, 0x14, 0x00} + payload := []byte(`wrong`) + err, _ := Parse(append(raw, payload...)) + if err == nil { + t.Errorf("Successfully parsed an incorrect payload") + } } // Parse() with an invalid date func TestParse10(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - payload := []byte(`{ + raw := []byte{0x01, 0x14, 0x14, 0x00} + payload := []byte(`{ "stat": { "time":"null", "lati":46.24000, @@ -478,8 +479,8 @@ func TestParse10(t *testing.T) { "txnb":2 } }`) - err, _ := Parse(append(raw, payload...)) - if err == nil { - t.Errorf("Successfully parsed an incorrect payload time") - } + err, _ := Parse(append(raw, payload...)) + if err == nil { + t.Errorf("Successfully parsed an incorrect payload time") + } } From 927cfedccf750c16be1537479321aae6896c9c81 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Dec 2015 19:46:50 +0100 Subject: [PATCH 0013/2266] Fix missing parenthesis in conditional expression --- lorawan/gateway/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 94f32c745..32ea4edbe 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -124,7 +124,7 @@ func Parse(raw []byte) (error, *Packet) { } var err error - if size > 4 && packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { + if size > 4 && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { err, packet.Payload = decodePayload(raw[4:]) } From 1e58e26ddfd2d02ff4cb27f52645941960ea319e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 10:33:28 +0100 Subject: [PATCH 0014/2266] Update protocol documentation --- lorawan/gateway/protocol.go | 66 +++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 32ea4edbe..81faa3be2 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -1,6 +1,9 @@ // Copyright © 2015 Matthias Benkort // Use of this source code is governed by the MIT license that can be found in the LICENSE file. +// Package gateway/protocol provides useful methods and types to handle communications with a gateway. +// +// This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT package protocol import ( @@ -8,9 +11,7 @@ import ( "time" ) -// Uplink json message format sent by the gateway -// More details can be found in the following Semtech protocol: -// https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +// RXPK represents an uplink json message format sent by the gateway type RXPK struct { Chan uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) Codr string `json:"codr"` // LoRa ECC coding rate identifier @@ -27,10 +28,8 @@ type RXPK struct { Tmst uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) } -// Downlink json message format received by the gateway -// Most field are optional -// More details can be found in the following Semtech protocol: -// https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +// TXPK represents a downlink json message format received by the gateway. +// Most field are optional. type TXPK struct { Codr string `json:"codr"` // LoRa ECC coding rate identifier Data string `json:"data"` // Base64 encoded RF packet payload, padding optional @@ -49,9 +48,7 @@ type TXPK struct { Tmst uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) } -// Status json message format sent by the gateway -// More details can be found in the following Semtech protocol: -// https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +// Stat represents a status json message format sent by the gateway type Stat struct { Ackr float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged Alti int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) @@ -65,37 +62,34 @@ type Stat struct { Txnb uint `json:"txnb"` // Number of packets emitted (unsigned integer) } -// Packet as seen by the gateway. The payload is optional and could be nil, otherwise, it is a -// Base64 encoding of one of the related message format (RXPK, TXPK or Stat). -// - Version refers to the protocol version, always 1 here -// - Identifier refers to a packet command (PUSH_DATA, PUSH_ACK, PULL_DATA, PULL_RESP, PULL_ACK) -// - Token is a random number generated by the gateway on some request +// Packet as seen by the gateway. type Packet struct { - Version byte - Token []byte - Identifier byte - GatewayId []byte - Payload *Payload + Version byte // Protocol version, should always be 1 here + Token []byte // Random number generated by the gateway on some request. 2-bytes long. + Identifier byte // Packet's command identifier + GatewayId []byte // Source gateway's identifier (Only PULL_DATA) + Payload *Payload // JSON payload transmitted if any, nil otherwise } +// Payload refers to the JSON payload sent by a gateway or a server. type Payload struct { - Raw []byte `json:"-"` - RXPK *[]RXPK `json:"rxpk"` - Stat *Stat `json:"stat"` - TXPK *TXPK `json:"txpk"` + Raw []byte `json:"-"` // The raw unparsed response + RXPK *[]RXPK `json:"rxpk"` // A list of RXPK messages transmitted if any + Stat *Stat `json:"stat"` // A Stat message transmitted if any + TXPK *TXPK `json:"txpk"` // A TXPK message transmitted if any } // Available packet commands const ( - PUSH_DATA byte = iota - PUSH_ACK - PULL_DATA - PULL_RESP - PULL_ACK + PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data + PUSH_ACK // Sent by the gateway's recipient in response to a PUSH_DATA + PULL_DATA // Sent periodically by the gateway to keep a connection open + PULL_RESP // Sent by the gateway's recipient to transmit back data to the Gateway + PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA ) -// Parse a raw response from a server and turn in into a packet -// Will return an error if the response fields are incorrect +// Parse parse a raw response from a server and turn in into a packet. +// Will return an error if the response fields are incorrect. func Parse(raw []byte) (error, *Packet) { size := len(raw) @@ -104,11 +98,11 @@ func Parse(raw []byte) (error, *Packet) { } packet := &Packet{ - raw[0], - raw[1:3], - raw[3], - nil, - nil, + Version: raw[0], + Token: raw[1:3], + Identifier: raw[3], + GatewayId: nil, + Payload: nil, } if packet.Version != 0x1 { From af4f0a770d033f263c98583696b9583c18432d73 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 11:18:36 +0100 Subject: [PATCH 0015/2266] Refactor functions signatures + add VERSION constant + handle gatewayId --- lorawan/gateway/decode_utils.go | 6 +++--- lorawan/gateway/protocol.go | 27 +++++++++++++++++---------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go index bcc740ab3..9a14c5e80 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode_utils.go @@ -49,7 +49,7 @@ func (d *datrParser) UnmarshalJSON(raw []byte) error { return nil } -func decodePayload(raw []byte) (error, *Payload) { +func decodePayload(raw []byte) (*Payload, error) { payload := &Payload{raw, nil, nil, nil} customStruct := &struct { Stat *struct { @@ -69,7 +69,7 @@ func decodePayload(raw []byte) (error, *Payload) { err = json.Unmarshal(raw, customStruct) if err != nil { - return err, nil + return nil, err } if customStruct.Stat != nil && customStruct.Stat.Time.Parsed { @@ -98,5 +98,5 @@ func decodePayload(raw []byte) (error, *Payload) { } } - return nil, payload + return payload, nil } diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 81faa3be2..035558cb7 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -88,13 +88,15 @@ const ( PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA ) +const VERSION = 0x01 + // Parse parse a raw response from a server and turn in into a packet. // Will return an error if the response fields are incorrect. -func Parse(raw []byte) (error, *Packet) { +func Parse(raw []byte) (*Packet, error) { size := len(raw) if size < 3 { - return errors.New("Invalid raw data format"), nil + return nil, errors.New("Invalid raw data format") } packet := &Packet{ @@ -105,22 +107,27 @@ func Parse(raw []byte) (error, *Packet) { Payload: nil, } - if packet.Version != 0x1 { - return errors.New("Unreckognized protocol version"), nil + if packet.Version != VERSION { + return nil, errors.New("Unreckognized protocol version") } if packet.Identifier > PULL_ACK { - return errors.New("Unreckognized protocol identifier"), nil + return nil, errors.New("Unreckognized protocol identifier") } - if size >= 12 && packet.Identifier == PULL_DATA { - packet.GatewayId = raw[4:12] + cursor := 4 + if packet.Identifier == PULL_DATA || packet.Identifier == PUSH_DATA { + if size < 12 { + return nil, errors.New("Invalid gateway identifier") + } + packet.GatewayId = raw[cursor:12] + cursor = 12 } var err error - if size > 4 && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { - err, packet.Payload = decodePayload(raw[4:]) + if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { + packet.Payload, err = decodePayload(raw[cursor:]) } - return err, packet + return packet, err } From b6ffb10cccbfec0bfd20d9ff626bc7758f22feea Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 11:19:54 +0100 Subject: [PATCH 0016/2266] Rewrite test to be compliant with new signatures --- lorawan/gateway/protocol_test.go | 81 +++++++++++++++++++------------- 1 file changed, 48 insertions(+), 33 deletions(-) diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index 0bb7b4484..a77d56c36 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -16,14 +16,14 @@ import ( // Parse() with valid raw data and no payload func TestParse1(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - err, packet := Parse(raw) + raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} + packet, err := Parse(raw) if err != nil { t.Errorf("Failed to parse with error: %#v", err) } - if packet.Version != 0x01 { + if packet.Version != VERSION { t.Errorf("Invalid parsed version: %x", packet.Version) } @@ -31,18 +31,23 @@ func TestParse1(t *testing.T) { t.Errorf("Invalid parsed token: %x", packet.Token) } - if packet.Identifier != 0x00 { + if packet.Identifier != PUSH_ACK { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } if packet.Payload != nil { t.Errorf("Invalid parsed payload: % x", packet.Payload) } + + if packet.GatewayId != nil { + t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) + } } // Parse() with valid raw data and stat payload func TestParse2(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} + raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA } + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "stat": { "time":"2014-01-12 08:59:28 GMT", @@ -57,13 +62,13 @@ func TestParse2(t *testing.T) { "txnb":2 } }`) - err, packet := Parse(append(raw, payload...)) + packet, err := Parse(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) } - if packet.Version != 0x01 { + if packet.Version != VERSION { t.Errorf("Invalid parsed version: %x", packet.Version) } @@ -71,7 +76,11 @@ func TestParse2(t *testing.T) { t.Errorf("Invalid parsed token: %x", packet.Token) } - if packet.Identifier != 0x00 { + if !bytes.Equal(gatewayId, packet.GatewayId) { + t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) + } + + if packet.Identifier != PUSH_DATA { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } @@ -111,8 +120,8 @@ func TestParse2(t *testing.T) { // Parse() with valid raw data and rxpk payloads func TestParse3(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - + raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "rxpk":[ { @@ -159,13 +168,13 @@ func TestParse3(t *testing.T) { ] }`) - err, packet := Parse(append(raw, payload...)) + packet, err := Parse(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) } - if packet.Version != 0x01 { + if packet.Version != VERSION { t.Errorf("Invalid parsed version: %x", packet.Version) } @@ -173,10 +182,14 @@ func TestParse3(t *testing.T) { t.Errorf("Invalid parsed token: %x", packet.Token) } - if packet.Identifier != 0x00 { + if packet.Identifier != PUSH_DATA { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } + if !bytes.Equal(gatewayId, packet.GatewayId) { + t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) + } + if packet.Payload == nil { t.Errorf("Invalid parsed payload: % x", packet.Payload) return @@ -213,8 +226,8 @@ func TestParse3(t *testing.T) { // Parse() with valid raw data and rxpk payloads + stat func TestParse4(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} - + raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "rxpk":[ { @@ -273,13 +286,13 @@ func TestParse4(t *testing.T) { } }`) - err, packet := Parse(append(raw, payload...)) + packet, err := Parse(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) } - if packet.Version != 0x01 { + if packet.Version != VERSION { t.Errorf("Invalid parsed version: %x", packet.Version) } @@ -287,7 +300,7 @@ func TestParse4(t *testing.T) { t.Errorf("Invalid parsed token: %x", packet.Token) } - if packet.Identifier != 0x00 { + if packet.Identifier != PUSH_DATA { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } @@ -350,7 +363,7 @@ func TestParse4(t *testing.T) { // Parse() with valid raw data and txpk payload func TestParse5(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x03} + raw := []byte{VERSION, 0x14, 0x14, PULL_RESP} payload := []byte(`{ "txpk":{ @@ -367,13 +380,13 @@ func TestParse5(t *testing.T) { } }`) - err, packet := Parse(append(raw, payload...)) + packet, err := Parse(append(raw, payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) } - if packet.Version != 0x01 { + if packet.Version != VERSION { t.Errorf("Invalid parsed version: %x", packet.Version) } @@ -381,7 +394,7 @@ func TestParse5(t *testing.T) { t.Errorf("Invalid parsed token: %x", packet.Token) } - if packet.Identifier != 0x03 { + if packet.Identifier != PULL_RESP { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } @@ -418,8 +431,8 @@ func TestParse5(t *testing.T) { // Parse() with an invalid version number func TestParse6(t *testing.T) { - raw := []byte{0x00, 0x14, 0x14, 0x00, 0x42, 0x14, 0x42, 0x14} - err, _ := Parse(raw) + raw := []byte{0x00, 0x14, 0x14, PUSH_ACK} + _, err := Parse(raw) if err == nil { t.Errorf("Successfully parsed an incorrect version number") @@ -428,10 +441,10 @@ func TestParse6(t *testing.T) { // Parse() with an invalid raw message func TestParse7(t *testing.T) { - raw1 := []byte{0x01} + raw1 := []byte{VERSION} var raw2 []byte - err1, _ := Parse(raw1) - err2, _ := Parse(raw2) + _, err1 := Parse(raw1) + _, err2 := Parse(raw2) if err1 == nil { t.Errorf("Successfully parsed an raw message") @@ -444,8 +457,8 @@ func TestParse7(t *testing.T) { // Parse() with an invalid identifier func TestParse8(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0xFF, 0x42, 0x14, 0x42, 0x14} - err, _ := Parse(raw) + raw := []byte{VERSION, 0x14, 0x14, 0xFF} + _, err := Parse(raw) if err == nil { t.Errorf("Successfully parsed an incorrect identifier") @@ -454,9 +467,10 @@ func TestParse8(t *testing.T) { // Parse() with an invalid payload func TestParse9(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} + raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`wrong`) - err, _ := Parse(append(raw, payload...)) + _, err := Parse(append(append(raw, gatewayId...), payload...)) if err == nil { t.Errorf("Successfully parsed an incorrect payload") } @@ -464,7 +478,8 @@ func TestParse9(t *testing.T) { // Parse() with an invalid date func TestParse10(t *testing.T) { - raw := []byte{0x01, 0x14, 0x14, 0x00} + raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "stat": { "time":"null", @@ -479,7 +494,7 @@ func TestParse10(t *testing.T) { "txnb":2 } }`) - err, _ := Parse(append(raw, payload...)) + _, err := Parse(append(append(raw, gatewayId...), payload...)) if err == nil { t.Errorf("Successfully parsed an incorrect payload time") } From 43d5d57179bfddc970364cb2501be2d9a3eb868c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 11:32:50 +0100 Subject: [PATCH 0017/2266] Add some tests for Parse() functions --- lorawan/gateway/protocol_test.go | 148 ++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index a77d56c36..47f43e344 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -14,7 +14,7 @@ import ( // ------------------------- Parse (raw []byte) (error, Packet) // ------------------------------------------------------------ -// Parse() with valid raw data and no payload +// Parse() with valid raw data and no payload (PUSH_ACK) func TestParse1(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} packet, err := Parse(raw) @@ -499,3 +499,149 @@ func TestParse10(t *testing.T) { t.Errorf("Successfully parsed an incorrect payload time") } } + +// Parse() with valid raw data but a useless payload +func TestParse11(t *testing.T) { + raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK } + payload := []byte(`{ + "stat": { + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 + } + }`) + packet, err := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse a valid PUSH_ACK packet") + } + + if packet.Payload != nil { + t.Errorf("Parsed payload on a PUSH_ACK packet") + } +} + +// Parse() with valid raw data but a useless payload +func TestParse12(t *testing.T) { + raw := []byte{VERSION, 0x14, 0x14, PULL_ACK } + payload := []byte(`{ + "stat": { + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 + } + }`) + packet, err := Parse(append(raw, payload...)) + + if err != nil { + t.Errorf("Failed to parse a valid PULL_ACK packet") + } + + if packet.Payload != nil { + t.Errorf("Parsed payload on a PULL_ACK packet") + } +} + +// Parse() with valid raw data but a useless payload +func TestParse13(t *testing.T) { + raw := []byte{VERSION, 0x14, 0x14, PULL_DATA } + gatewayId := []byte("qwerty1234")[0:8] + payload := []byte(`{ + "stat": { + "time":"2014-01-12 08:59:28 GMT", + "lati":46.24000, + "long":3.25230, + "alti":145, + "rxnb":2, + "rxok":2, + "rxfw":2, + "ackr":100.0, + "dwnb":2, + "txnb":2 + } + }`) + packet, err := Parse(append(append(raw, gatewayId...), payload...)) + + if err != nil { + t.Errorf("Failed to parse a valid PULL_DATA packet") + } + + if packet.Payload != nil { + t.Errorf("Parsed payload on a PULL_DATA packet") + } +} + +// Parse() with valid raw data and no payload (PULL_ACK) +func TestParse14(t *testing.T) { + raw := []byte{VERSION, 0x14, 0x14, PULL_ACK} + packet, err := Parse(raw) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != VERSION { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != PULL_ACK { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload != nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if packet.GatewayId != nil { + t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) + } +} + +// Parse() with valid raw data and no payload (PULL_DATA) +func TestParse15(t *testing.T) { + raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} + gatewayId := []byte("qwerty1234")[0:8] + packet, err := Parse(append(raw, gatewayId...)) + + if err != nil { + t.Errorf("Failed to parse with error: %#v", err) + } + + if packet.Version != VERSION { + t.Errorf("Invalid parsed version: %x", packet.Version) + } + + if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { + t.Errorf("Invalid parsed token: %x", packet.Token) + } + + if packet.Identifier != PULL_DATA { + t.Errorf("Invalid parsed identifier: %x", packet.Identifier) + } + + if packet.Payload != nil { + t.Errorf("Invalid parsed payload: % x", packet.Payload) + } + + if !bytes.Equal(gatewayId, packet.GatewayId) { + t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) + } +} From 3e1f7f727947869fe9fd92288c00d5262e32aab7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 11:33:19 +0100 Subject: [PATCH 0018/2266] Format files --- lorawan/gateway/protocol.go | 42 +++++++-------- lorawan/gateway/protocol_test.go | 88 ++++++++++++++++---------------- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 035558cb7..7d65847b7 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -73,19 +73,19 @@ type Packet struct { // Payload refers to the JSON payload sent by a gateway or a server. type Payload struct { - Raw []byte `json:"-"` // The raw unparsed response - RXPK *[]RXPK `json:"rxpk"` // A list of RXPK messages transmitted if any - Stat *Stat `json:"stat"` // A Stat message transmitted if any - TXPK *TXPK `json:"txpk"` // A TXPK message transmitted if any + Raw []byte `json:"-"` // The raw unparsed response + RXPK *[]RXPK `json:"rxpk"` // A list of RXPK messages transmitted if any + Stat *Stat `json:"stat"` // A Stat message transmitted if any + TXPK *TXPK `json:"txpk"` // A TXPK message transmitted if any } // Available packet commands const ( - PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data - PUSH_ACK // Sent by the gateway's recipient in response to a PUSH_DATA - PULL_DATA // Sent periodically by the gateway to keep a connection open - PULL_RESP // Sent by the gateway's recipient to transmit back data to the Gateway - PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA + PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data + PUSH_ACK // Sent by the gateway's recipient in response to a PUSH_DATA + PULL_DATA // Sent periodically by the gateway to keep a connection open + PULL_RESP // Sent by the gateway's recipient to transmit back data to the Gateway + PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA ) const VERSION = 0x01 @@ -100,11 +100,11 @@ func Parse(raw []byte) (*Packet, error) { } packet := &Packet{ - Version: raw[0], - Token: raw[1:3], - Identifier: raw[3], - GatewayId: nil, - Payload: nil, + Version: raw[0], + Token: raw[1:3], + Identifier: raw[3], + GatewayId: nil, + Payload: nil, } if packet.Version != VERSION { @@ -115,18 +115,18 @@ func Parse(raw []byte) (*Packet, error) { return nil, errors.New("Unreckognized protocol identifier") } - cursor := 4 + cursor := 4 if packet.Identifier == PULL_DATA || packet.Identifier == PUSH_DATA { - if size < 12 { - return nil, errors.New("Invalid gateway identifier") - } - packet.GatewayId = raw[cursor:12] - cursor = 12 + if size < 12 { + return nil, errors.New("Invalid gateway identifier") + } + packet.GatewayId = raw[cursor:12] + cursor = 12 } var err error if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { - packet.Payload, err = decodePayload(raw[cursor:]) + packet.Payload, err = decodePayload(raw[cursor:]) } return packet, err diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index 47f43e344..9ce17a28b 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -39,15 +39,15 @@ func TestParse1(t *testing.T) { t.Errorf("Invalid parsed payload: % x", packet.Payload) } - if packet.GatewayId != nil { - t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) - } + if packet.GatewayId != nil { + t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) + } } // Parse() with valid raw data and stat payload func TestParse2(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA } - gatewayId := []byte("qwerty1234")[0:8] + raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "stat": { "time":"2014-01-12 08:59:28 GMT", @@ -76,9 +76,9 @@ func TestParse2(t *testing.T) { t.Errorf("Invalid parsed token: %x", packet.Token) } - if !bytes.Equal(gatewayId, packet.GatewayId) { - t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) - } + if !bytes.Equal(gatewayId, packet.GatewayId) { + t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) + } if packet.Identifier != PUSH_DATA { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) @@ -121,7 +121,7 @@ func TestParse2(t *testing.T) { // Parse() with valid raw data and rxpk payloads func TestParse3(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "rxpk":[ { @@ -186,9 +186,9 @@ func TestParse3(t *testing.T) { t.Errorf("Invalid parsed identifier: %x", packet.Identifier) } - if !bytes.Equal(gatewayId, packet.GatewayId) { - t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) - } + if !bytes.Equal(gatewayId, packet.GatewayId) { + t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) + } if packet.Payload == nil { t.Errorf("Invalid parsed payload: % x", packet.Payload) @@ -227,7 +227,7 @@ func TestParse3(t *testing.T) { // Parse() with valid raw data and rxpk payloads + stat func TestParse4(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "rxpk":[ { @@ -468,7 +468,7 @@ func TestParse8(t *testing.T) { // Parse() with an invalid payload func TestParse9(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`wrong`) _, err := Parse(append(append(raw, gatewayId...), payload...)) if err == nil { @@ -479,7 +479,7 @@ func TestParse9(t *testing.T) { // Parse() with an invalid date func TestParse10(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "stat": { "time":"null", @@ -502,7 +502,7 @@ func TestParse10(t *testing.T) { // Parse() with valid raw data but a useless payload func TestParse11(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK } + raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} payload := []byte(`{ "stat": { "time":"2014-01-12 08:59:28 GMT", @@ -519,18 +519,18 @@ func TestParse11(t *testing.T) { }`) packet, err := Parse(append(raw, payload...)) - if err != nil { - t.Errorf("Failed to parse a valid PUSH_ACK packet") - } + if err != nil { + t.Errorf("Failed to parse a valid PUSH_ACK packet") + } - if packet.Payload != nil { - t.Errorf("Parsed payload on a PUSH_ACK packet") - } + if packet.Payload != nil { + t.Errorf("Parsed payload on a PUSH_ACK packet") + } } // Parse() with valid raw data but a useless payload func TestParse12(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_ACK } + raw := []byte{VERSION, 0x14, 0x14, PULL_ACK} payload := []byte(`{ "stat": { "time":"2014-01-12 08:59:28 GMT", @@ -547,19 +547,19 @@ func TestParse12(t *testing.T) { }`) packet, err := Parse(append(raw, payload...)) - if err != nil { - t.Errorf("Failed to parse a valid PULL_ACK packet") - } + if err != nil { + t.Errorf("Failed to parse a valid PULL_ACK packet") + } - if packet.Payload != nil { - t.Errorf("Parsed payload on a PULL_ACK packet") - } + if packet.Payload != nil { + t.Errorf("Parsed payload on a PULL_ACK packet") + } } // Parse() with valid raw data but a useless payload func TestParse13(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_DATA } - gatewayId := []byte("qwerty1234")[0:8] + raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} + gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ "stat": { "time":"2014-01-12 08:59:28 GMT", @@ -576,13 +576,13 @@ func TestParse13(t *testing.T) { }`) packet, err := Parse(append(append(raw, gatewayId...), payload...)) - if err != nil { - t.Errorf("Failed to parse a valid PULL_DATA packet") - } + if err != nil { + t.Errorf("Failed to parse a valid PULL_DATA packet") + } - if packet.Payload != nil { - t.Errorf("Parsed payload on a PULL_DATA packet") - } + if packet.Payload != nil { + t.Errorf("Parsed payload on a PULL_DATA packet") + } } // Parse() with valid raw data and no payload (PULL_ACK) @@ -610,15 +610,15 @@ func TestParse14(t *testing.T) { t.Errorf("Invalid parsed payload: % x", packet.Payload) } - if packet.GatewayId != nil { - t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) - } + if packet.GatewayId != nil { + t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) + } } // Parse() with valid raw data and no payload (PULL_DATA) func TestParse15(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} - gatewayId := []byte("qwerty1234")[0:8] + gatewayId := []byte("qwerty1234")[0:8] packet, err := Parse(append(raw, gatewayId...)) if err != nil { @@ -641,7 +641,7 @@ func TestParse15(t *testing.T) { t.Errorf("Invalid parsed payload: % x", packet.Payload) } - if !bytes.Equal(gatewayId, packet.GatewayId) { - t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) - } + if !bytes.Equal(gatewayId, packet.GatewayId) { + t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) + } } From d2efbbdfc7220275cb215c5a3a114630e5e97f11 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 12:06:52 +0100 Subject: [PATCH 0019/2266] Rename Parse to Unmarshal --- lorawan/gateway/decode_utils.go | 2 +- lorawan/gateway/protocol.go | 11 +++- lorawan/gateway/protocol_test.go | 94 ++++++++++++++++---------------- 3 files changed, 56 insertions(+), 51 deletions(-) diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode_utils.go index 9a14c5e80..ff927d242 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode_utils.go @@ -49,7 +49,7 @@ func (d *datrParser) UnmarshalJSON(raw []byte) error { return nil } -func decodePayload(raw []byte) (*Payload, error) { +func unmarshalPayload(raw []byte) (*Payload, error) { payload := &Payload{raw, nil, nil, nil} customStruct := &struct { Stat *struct { diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 7d65847b7..f04059d2f 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -90,9 +90,9 @@ const ( const VERSION = 0x01 -// Parse parse a raw response from a server and turn in into a packet. +// Unmarshal parse a raw response from a server and turn in into a packet. // Will return an error if the response fields are incorrect. -func Parse(raw []byte) (*Packet, error) { +func Unmarshal(raw []byte) (*Packet, error) { size := len(raw) if size < 3 { @@ -126,8 +126,13 @@ func Parse(raw []byte) (*Packet, error) { var err error if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { - packet.Payload, err = decodePayload(raw[cursor:]) + packet.Payload, err = unmarshalPayload(raw[cursor:]) } return packet, err } + +// Marshal transform a packet to a sequence of bytes. +func Marshal(packet Packet) ([]byte, error) { + return []byte{}, nil +} diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/protocol_test.go index 9ce17a28b..a378ee258 100644 --- a/lorawan/gateway/protocol_test.go +++ b/lorawan/gateway/protocol_test.go @@ -11,13 +11,13 @@ import ( ) // ------------------------------------------------------------ -// ------------------------- Parse (raw []byte) (error, Packet) +// ------------------------- Unmarshal (raw []byte) (Packet, error) // ------------------------------------------------------------ -// Parse() with valid raw data and no payload (PUSH_ACK) -func TestParse1(t *testing.T) { +// Unmarshal() with valid raw data and no payload (PUSH_ACK) +func TestUnmarshal1(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} - packet, err := Parse(raw) + packet, err := Unmarshal(raw) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -44,8 +44,8 @@ func TestParse1(t *testing.T) { } } -// Parse() with valid raw data and stat payload -func TestParse2(t *testing.T) { +// Unmarshal() with valid raw data and stat payload +func TestUnmarshal2(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ @@ -62,7 +62,7 @@ func TestParse2(t *testing.T) { "txnb":2 } }`) - packet, err := Parse(append(append(raw, gatewayId...), payload...)) + packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -118,8 +118,8 @@ func TestParse2(t *testing.T) { } -// Parse() with valid raw data and rxpk payloads -func TestParse3(t *testing.T) { +// Unmarshal() with valid raw data and rxpk payloads +func TestUnmarshal3(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ @@ -168,7 +168,7 @@ func TestParse3(t *testing.T) { ] }`) - packet, err := Parse(append(append(raw, gatewayId...), payload...)) + packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -224,8 +224,8 @@ func TestParse3(t *testing.T) { } } -// Parse() with valid raw data and rxpk payloads + stat -func TestParse4(t *testing.T) { +// Unmarshal() with valid raw data and rxpk payloads + stat +func TestUnmarshal4(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ @@ -286,7 +286,7 @@ func TestParse4(t *testing.T) { } }`) - packet, err := Parse(append(append(raw, gatewayId...), payload...)) + packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -361,8 +361,8 @@ func TestParse4(t *testing.T) { } } -// Parse() with valid raw data and txpk payload -func TestParse5(t *testing.T) { +// Unmarshal() with valid raw data and txpk payload +func TestUnmarshal5(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PULL_RESP} payload := []byte(`{ @@ -380,7 +380,7 @@ func TestParse5(t *testing.T) { } }`) - packet, err := Parse(append(raw, payload...)) + packet, err := Unmarshal(append(raw, payload...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -429,22 +429,22 @@ func TestParse5(t *testing.T) { } } -// Parse() with an invalid version number -func TestParse6(t *testing.T) { +// Unmarshal() with an invalid version number +func TestUnmarshal6(t *testing.T) { raw := []byte{0x00, 0x14, 0x14, PUSH_ACK} - _, err := Parse(raw) + _, err := Unmarshal(raw) if err == nil { t.Errorf("Successfully parsed an incorrect version number") } } -// Parse() with an invalid raw message -func TestParse7(t *testing.T) { +// Unmarshal() with an invalid raw message +func TestUnmarshal7(t *testing.T) { raw1 := []byte{VERSION} var raw2 []byte - _, err1 := Parse(raw1) - _, err2 := Parse(raw2) + _, err1 := Unmarshal(raw1) + _, err2 := Unmarshal(raw2) if err1 == nil { t.Errorf("Successfully parsed an raw message") @@ -455,29 +455,29 @@ func TestParse7(t *testing.T) { } } -// Parse() with an invalid identifier -func TestParse8(t *testing.T) { +// Unmarshal() with an invalid identifier +func TestUnmarshal8(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, 0xFF} - _, err := Parse(raw) + _, err := Unmarshal(raw) if err == nil { t.Errorf("Successfully parsed an incorrect identifier") } } -// Parse() with an invalid payload -func TestParse9(t *testing.T) { +// Unmarshal() with an invalid payload +func TestUnmarshal9(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`wrong`) - _, err := Parse(append(append(raw, gatewayId...), payload...)) + _, err := Unmarshal(append(append(raw, gatewayId...), payload...)) if err == nil { t.Errorf("Successfully parsed an incorrect payload") } } -// Parse() with an invalid date -func TestParse10(t *testing.T) { +// Unmarshal() with an invalid date +func TestUnmarshal10(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ @@ -494,14 +494,14 @@ func TestParse10(t *testing.T) { "txnb":2 } }`) - _, err := Parse(append(append(raw, gatewayId...), payload...)) + _, err := Unmarshal(append(append(raw, gatewayId...), payload...)) if err == nil { t.Errorf("Successfully parsed an incorrect payload time") } } -// Parse() with valid raw data but a useless payload -func TestParse11(t *testing.T) { +// Unmarshal() with valid raw data but a useless payload +func TestUnmarshal11(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} payload := []byte(`{ "stat": { @@ -517,7 +517,7 @@ func TestParse11(t *testing.T) { "txnb":2 } }`) - packet, err := Parse(append(raw, payload...)) + packet, err := Unmarshal(append(raw, payload...)) if err != nil { t.Errorf("Failed to parse a valid PUSH_ACK packet") @@ -528,8 +528,8 @@ func TestParse11(t *testing.T) { } } -// Parse() with valid raw data but a useless payload -func TestParse12(t *testing.T) { +// Unmarshal() with valid raw data but a useless payload +func TestUnmarshal12(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PULL_ACK} payload := []byte(`{ "stat": { @@ -545,7 +545,7 @@ func TestParse12(t *testing.T) { "txnb":2 } }`) - packet, err := Parse(append(raw, payload...)) + packet, err := Unmarshal(append(raw, payload...)) if err != nil { t.Errorf("Failed to parse a valid PULL_ACK packet") @@ -556,8 +556,8 @@ func TestParse12(t *testing.T) { } } -// Parse() with valid raw data but a useless payload -func TestParse13(t *testing.T) { +// Unmarshal() with valid raw data but a useless payload +func TestUnmarshal13(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} gatewayId := []byte("qwerty1234")[0:8] payload := []byte(`{ @@ -574,7 +574,7 @@ func TestParse13(t *testing.T) { "txnb":2 } }`) - packet, err := Parse(append(append(raw, gatewayId...), payload...)) + packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) if err != nil { t.Errorf("Failed to parse a valid PULL_DATA packet") @@ -585,10 +585,10 @@ func TestParse13(t *testing.T) { } } -// Parse() with valid raw data and no payload (PULL_ACK) -func TestParse14(t *testing.T) { +// Unmarshal() with valid raw data and no payload (PULL_ACK) +func TestUnmarshal14(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PULL_ACK} - packet, err := Parse(raw) + packet, err := Unmarshal(raw) if err != nil { t.Errorf("Failed to parse with error: %#v", err) @@ -615,11 +615,11 @@ func TestParse14(t *testing.T) { } } -// Parse() with valid raw data and no payload (PULL_DATA) -func TestParse15(t *testing.T) { +// Unmarshal() with valid raw data and no payload (PULL_DATA) +func TestUnmarshal15(t *testing.T) { raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} gatewayId := []byte("qwerty1234")[0:8] - packet, err := Parse(append(raw, gatewayId...)) + packet, err := Unmarshal(append(raw, gatewayId...)) if err != nil { t.Errorf("Failed to parse with error: %#v", err) From e2b5e6b1be008756e830e4344115673e3fdd049b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 13:36:27 +0100 Subject: [PATCH 0020/2266] Change files structure --- .../gateway/{decode_utils.go => decode.go} | 58 +++++++++++++++++-- lorawan/gateway/encode.go | 6 ++ lorawan/gateway/protocol.go | 48 --------------- 3 files changed, 60 insertions(+), 52 deletions(-) rename lorawan/gateway/{decode_utils.go => decode.go} (53%) create mode 100644 lorawan/gateway/encode.go diff --git a/lorawan/gateway/decode_utils.go b/lorawan/gateway/decode.go similarity index 53% rename from lorawan/gateway/decode_utils.go rename to lorawan/gateway/decode.go index ff927d242..59ad11611 100644 --- a/lorawan/gateway/decode_utils.go +++ b/lorawan/gateway/decode.go @@ -10,11 +10,56 @@ import ( "time" ) +// Unmarshal parse a raw response from a server and turn in into a packet. +// Will return an error if the response fields are incorrect. +func Unmarshal(raw []byte) (*Packet, error) { + size := len(raw) + + if size < 3 { + return nil, errors.New("Invalid raw data format") + } + + packet := &Packet{ + Version: raw[0], + Token: raw[1:3], + Identifier: raw[3], + GatewayId: nil, + Payload: nil, + } + + if packet.Version != VERSION { + return nil, errors.New("Unreckognized protocol version") + } + + if packet.Identifier > PULL_ACK { + return nil, errors.New("Unreckognized protocol identifier") + } + + cursor := 4 + if packet.Identifier == PULL_DATA || packet.Identifier == PUSH_DATA { + if size < 12 { + return nil, errors.New("Invalid gateway identifier") + } + packet.GatewayId = raw[cursor:12] + cursor = 12 + } + + var err error + if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { + packet.Payload, err = unmarshalPayload(raw[cursor:]) + } + + return packet, err +} + +// timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time +// module parse RFC3339 by default type timeParser struct { - Value time.Time - Parsed bool + Value time.Time // The parsed time value + Parsed bool // Set to true if the value has been parsed } +// implement the Unmarshaller interface from encoding/json func (t *timeParser) UnmarshalJSON(raw []byte) error { var err error value := strings.Trim(string(raw), `"`) @@ -33,11 +78,15 @@ func (t *timeParser) UnmarshalJSON(raw []byte) error { return nil } +// datrParser is used as a proxy to Unmarshal datr field in json payloads. +// Depending on the modulation type, the datr type could be either a string or a number. +// We're gonna parse it as a string in any case. type datrParser struct { - Value string - Parsed bool + Value string // The parsed value + Parsed bool // Set to true if the value has been parsed } +// implement the Unmarshaller interface from encoding/json func (d *datrParser) UnmarshalJSON(raw []byte) error { d.Value = strings.Trim(string(raw), `"`) @@ -49,6 +98,7 @@ func (d *datrParser) UnmarshalJSON(raw []byte) error { return nil } +// unmarshalPayload is an until used by Unmarshal to parse a Payload from a sequence of bytes. func unmarshalPayload(raw []byte) (*Payload, error) { payload := &Payload{raw, nil, nil, nil} customStruct := &struct { diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go new file mode 100644 index 000000000..2c5019993 --- /dev/null +++ b/lorawan/gateway/encode.go @@ -0,0 +1,6 @@ +package protocol + +// Marshal transform a packet to a sequence of bytes. +func Marshal(packet Packet) ([]byte, error) { + return []byte{}, nil +} diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index f04059d2f..f02210576 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -7,7 +7,6 @@ package protocol import ( - "errors" "time" ) @@ -89,50 +88,3 @@ const ( ) const VERSION = 0x01 - -// Unmarshal parse a raw response from a server and turn in into a packet. -// Will return an error if the response fields are incorrect. -func Unmarshal(raw []byte) (*Packet, error) { - size := len(raw) - - if size < 3 { - return nil, errors.New("Invalid raw data format") - } - - packet := &Packet{ - Version: raw[0], - Token: raw[1:3], - Identifier: raw[3], - GatewayId: nil, - Payload: nil, - } - - if packet.Version != VERSION { - return nil, errors.New("Unreckognized protocol version") - } - - if packet.Identifier > PULL_ACK { - return nil, errors.New("Unreckognized protocol identifier") - } - - cursor := 4 - if packet.Identifier == PULL_DATA || packet.Identifier == PUSH_DATA { - if size < 12 { - return nil, errors.New("Invalid gateway identifier") - } - packet.GatewayId = raw[cursor:12] - cursor = 12 - } - - var err error - if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { - packet.Payload, err = unmarshalPayload(raw[cursor:]) - } - - return packet, err -} - -// Marshal transform a packet to a sequence of bytes. -func Marshal(packet Packet) ([]byte, error) { - return []byte{}, nil -} From c4f3f5a36e28eb8a5353032d02b1a351c002a19b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 14:38:07 +0100 Subject: [PATCH 0021/2266] Fix oversight in protocol types doc --- lorawan/gateway/protocol.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index f02210576..32dadac68 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -66,7 +66,7 @@ type Packet struct { Version byte // Protocol version, should always be 1 here Token []byte // Random number generated by the gateway on some request. 2-bytes long. Identifier byte // Packet's command identifier - GatewayId []byte // Source gateway's identifier (Only PULL_DATA) + GatewayId []byte // Source gateway's identifier (Only PULL_DATA and PUSH_DATA) Payload *Payload // JSON payload transmitted if any, nil otherwise } From cd82907eb3ffa76e0d11b44563a42ced3d44e6f5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 14:39:03 +0100 Subject: [PATCH 0022/2266] Rename protocol_test to decode_test --- lorawan/gateway/{protocol_test.go => decode_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lorawan/gateway/{protocol_test.go => decode_test.go} (100%) diff --git a/lorawan/gateway/protocol_test.go b/lorawan/gateway/decode_test.go similarity index 100% rename from lorawan/gateway/protocol_test.go rename to lorawan/gateway/decode_test.go From 2edf1cd4358cc2dd551d33f3bf330db808ec64fe Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 14:40:21 +0100 Subject: [PATCH 0023/2266] Add test about Marshal for PUSH_ACK packets --- lorawan/gateway/encode_test.go | 151 +++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 lorawan/gateway/encode_test.go diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go new file mode 100644 index 000000000..5e22ec63b --- /dev/null +++ b/lorawan/gateway/encode_test.go @@ -0,0 +1,151 @@ +// Copyright © 2015 Matthias Benkort +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package protocol + +import ( + "bytes" + "reflect" + "testing" + "time" +) + +// ----------------------------------------------------------------- +// ------------------------- Marshal (packet *Packet) ([]byte, error) +// ----------------------------------------------------------------- + +// ---------- PUSH_DATA + +// ---------- PUSH_ACK +func checkMarshalPUSH_ACK(packet *Packet) error { + raw, err := Marshal(packet) + + if err != nil { + return err + } + + if len(raw) != 4 { + return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + } + + if raw[0] != packet.Version { + return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + } + + if !bytes.Equal(raw[1:3], packet.Token) { + return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + } + + if raw[3] != packet.Identifier { + return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + } + + return err +} + +// Marshal a basic push_ack packet +func TestMarshalPUSH_ACK1(*t testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: nil, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a push_ack packet with extra useless gatewayId +func TestMarshalPUSH_ACK2(*t testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a push_ack packet with extra useless Payload +func TestMarshalPUSH_ACK3(*t testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: payload, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a push_ack with extra useless gatewayId and payload +func TestMarshalPUSH_ACK4(*t testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: payload, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a push_ack with an invalid token (too short) +func TestMarshalPUSH_ACK5(*t testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: nil, + } + _, err := Marshal(packet) + if err == nil { + t.Errorf("Successfully marshalled an invalid packet") + } +} + +// Marshal a push_ack with an invalid token (too long) +func TestMarshalPUSH_ACK6(*t testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0x9A, 0x7A, 0x7E}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: nil, + } + _, err := Marshal(packet) + if err == nil { + t.Errorf("Successfully marshalled an invalid packet") + } +} + +// ---------- PULL_DATA + +// ---------- PULL_ACK + +// ---------- PULL_RESP + From 1e028c929dfd28e63a58547b2c1c49f7f1474bc5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 17:31:41 +0100 Subject: [PATCH 0024/2266] Add test data generator for marshal function --- lorawan/gateway/createTestValue.go | 231 +++++++++++++++++++++++++++++ 1 file changed, 231 insertions(+) create mode 100644 lorawan/gateway/createTestValue.go diff --git a/lorawan/gateway/createTestValue.go b/lorawan/gateway/createTestValue.go new file mode 100644 index 000000000..9eab892b4 --- /dev/null +++ b/lorawan/gateway/createTestValue.go @@ -0,0 +1,231 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "time" +) + +type Stat struct { + Ackr float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged + Alti int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) + Dwnb uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) + Lati float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) + Rxnb uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) + Rxok uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC + Time *mytime `json:"time,omitempty"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) +} + +// RXPK represents an uplink json message format sent by the gateway +type RXPK struct { + Chan uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *datr `json:"datr,omitempty"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *mytime `json:"time,omitempty"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +// TXPK represents a downlink json message format received by the gateway. +// Most field are optional. +type TXPK struct { + Codr string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional + Datr *datr `json:"datr,omitempty"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *mytime `json:"time,omitempty"` // Send packet at a certain time (GPS synchronization required) + Tmst uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) +} + +type datr struct { + kind string + value string +} + +func (d *datr) MarshalJSON() ([]byte, error) { + if d.kind == "uint" { + return []byte(d.value), nil + } + return append(append([]byte(`"`), []byte(d.value)...), []byte(`"`)...), nil +} + +type mytime struct { + layout string + value time.Time +} + +func (m *mytime) MarshalJSON() ([]byte, error) { + return append(append([]byte(`"`), []byte(m.value.Format(m.layout))...), []byte(`"`)...), nil +} + +type Payload struct { + RXPK *[]RXPK `json:"rxpk,omitempty"` + Stat *Stat `json:"stat,omitempty"` + TXPK *TXPK `json:"txpk,omitempty"` +} + +func main() { + time1, _ := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") + time2, _ := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") + time3, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + + rawRXPKs, _ := json.Marshal(Payload{ + RXPK: &[]RXPK{ + RXPK{ + Time: &mytime{time.RFC3339Nano, time1}, + Tmst: 3512348611, + Chan: 2, + Rfch: 0, + Freq: 866.349812, + Stat: 1, + Modu: "LORA", + Datr: &datr{"string", "SF7BW125"}, + Codr: "4/6", + Rssi: -35, + Lsnr: 5.1, + Size: 32, + Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + }, + RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: &datr{"uint", "50000"}, + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: &mytime{time.RFC3339Nano, time2}, + Tmst: 3512348514, + }, + }, + }) + + rawStat, _ := json.Marshal(Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: &mytime{"2006-01-02 15:04:05 GMT", time3}, + }, + }) + + rawRXPKsStat, _ := json.Marshal(Payload{ + RXPK: &[]RXPK{ + RXPK{ + Time: &mytime{time.RFC3339Nano, time1}, + Tmst: 3512348611, + Chan: 2, + Rfch: 0, + Freq: 866.349812, + Stat: 1, + Modu: "LORA", + Datr: &datr{"string", "SF7BW125"}, + Codr: "4/6", + Rssi: -35, + Lsnr: 5.1, + Size: 32, + Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + }, + RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: &datr{"uint", "50000"}, + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: &mytime{time.RFC3339Nano, time2}, + Tmst: 3512348514, + }, + }, + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: &mytime{"2006-01-02 15:04:05 GMT", time3}, + }, + }) + + rawTXPK, _ := json.Marshal(Payload{ + TXPK: &TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: &datr{"string", "SF11BW125"}, + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + }, + }) + + fmt.Printf("Stat: %v\n", string(rawStat)) + fmt.Printf("Raw Stat: ") + for _, x := range rawStat { + fmt.Printf("0x%x,", x) + } + fmt.Printf("\n\n\n") + ioutil.WriteFile("./test_data/marshal_stat", rawStat, 0644) + + fmt.Printf("RXPKs: %v\n", string(rawRXPKs)) + fmt.Printf("Raw RXPKs: ") + for _, x := range rawRXPKs { + fmt.Printf("0x%x,", x) + } + fmt.Printf("\n\n\n") + ioutil.WriteFile("./test_data/marshal_rxpk", rawRXPKs, 0644) + + fmt.Printf("RXPKsStats: %v\n", string(rawRXPKsStat)) + fmt.Printf("Raw RXPKsStats: ") + for _, x := range rawRXPKsStat { + fmt.Printf("0x%x,", x) + } + fmt.Printf("\n\n\n") + ioutil.WriteFile("./test_data/marshal_rxpk_stat", rawRXPKsStat, 0644) + + fmt.Printf("TXPK: %v\n", string(rawTXPK)) + fmt.Printf("Raw TXPK: ") + for _, x := range rawTXPK { + fmt.Printf("0x%x,", x) + } + fmt.Printf("\n\n\n") + ioutil.WriteFile("./test_data/marshal_txpk", rawTXPK, 0644) +} From 718c31d9f650bab223d4a041ab84ef0ae3367a06 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 17:31:55 +0100 Subject: [PATCH 0025/2266] Create test data --- lorawan/gateway/test_data/marshal_rxpk | 1 + lorawan/gateway/test_data/marshal_rxpk_stat | 1 + lorawan/gateway/test_data/marshal_stat | 1 + lorawan/gateway/test_data/marshal_txpk | 1 + 4 files changed, 4 insertions(+) create mode 100644 lorawan/gateway/test_data/marshal_rxpk create mode 100644 lorawan/gateway/test_data/marshal_rxpk_stat create mode 100644 lorawan/gateway/test_data/marshal_stat create mode 100644 lorawan/gateway/test_data/marshal_txpk diff --git a/lorawan/gateway/test_data/marshal_rxpk b/lorawan/gateway/test_data/marshal_rxpk new file mode 100644 index 000000000..65a9e7e52 --- /dev/null +++ b/lorawan/gateway/test_data/marshal_rxpk @@ -0,0 +1 @@ +{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}]} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_rxpk_stat b/lorawan/gateway/test_data/marshal_rxpk_stat new file mode 100644 index 000000000..a78f34388 --- /dev/null +++ b/lorawan/gateway/test_data/marshal_rxpk_stat @@ -0,0 +1 @@ +{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_stat b/lorawan/gateway/test_data/marshal_stat new file mode 100644 index 000000000..2c5f67d67 --- /dev/null +++ b/lorawan/gateway/test_data/marshal_stat @@ -0,0 +1 @@ +{"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_txpk b/lorawan/gateway/test_data/marshal_txpk new file mode 100644 index 000000000..10134eb23 --- /dev/null +++ b/lorawan/gateway/test_data/marshal_txpk @@ -0,0 +1 @@ +{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} \ No newline at end of file From 436ae66025564fa9379a46d75456d8c6a0f9f48f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 17:34:41 +0100 Subject: [PATCH 0026/2266] Add test about Marshal() for PUSH_DATA packets --- lorawan/gateway/encode_test.go | 526 ++++++++++++++++++++++++++------- 1 file changed, 425 insertions(+), 101 deletions(-) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index 5e22ec63b..80e0387b2 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -5,6 +5,7 @@ package protocol import ( "bytes" + "io/ioutil" "reflect" "testing" "time" @@ -13,134 +14,458 @@ import ( // ----------------------------------------------------------------- // ------------------------- Marshal (packet *Packet) ([]byte, error) // ----------------------------------------------------------------- - // ---------- PUSH_DATA +func checkMarshalPUSH_DATA(packet *Packet, payload []byte) error { + raw, err := Marshal(packet) + + if err != nil { + return err + } + + if len(raw) < 12 { + return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + } + + if raw[0] != packet.Version { + return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + } + + if !bytes.Equal(raw[1:3], packet.Token) { + return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + } + + if raw[3] != packet.Identifier { + return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + } + + if !bytes.Equal(raw[4:12], packet.GatewayId) { + return errors.New(fmt.Printf("Invalid raw gatewayId: % x", raw[4:12])) + } + + if packet.Payload != nil && !bytes.Equal(raw[12:], payload) { + return errors.New(fmt.Printf("Invalid raw payload: % x", raw[12:])) + } + + return err +} + +// Marshal a basic push_data packet with Stat payload +func TestMarshalPUSH_DATA1(t *testing.T) { + time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + + // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + payload, err := ioutil.ReadFile("./test_data/marshal_stat") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: &Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: time, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Mashal a push_data packet with RXPK payload +func TestMarshalPUSH_DATA2(t *testing.T) { + time1, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") + time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") + + //{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}]} + payload, err := ioutil.ReadFile("./test_data/marshal_rxpk") + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: &Payload{ + RXPK: &[]RXPK{ + RXPK{ + Time: time1, + Tmst: 3512348611, + Chan: 2, + Rfch: 0, + Freq: 866.349812, + Stat: 1, + Modu: "LORA", + Datr: "SF7BW125", + Codr: "4/6", + Rssi: -35, + Lsnr: 5.1, + Size: 32, + Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + }, + RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: "50000", + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: time2, + Tmst: 3512348514, + }, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Mashal a push_data packet with RXPK payload and Stat +func TestMarshalPUSH_DATA2(t *testing.T) { + time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") + time3, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") + + //{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + payload, err := ioutil.ReadFile("./test_data/marshal_rxpk_stat") + + if err != nil { + t.Errorf("Unexpected error: %v", err) + return + } + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: &Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: time1, + }, + RXPK: &[]RXPK{ + RXPK{ + Time: time2, + Tmst: 3512348611, + Chan: 2, + Rfch: 0, + Freq: 866.349812, + Stat: 1, + Modu: "LORA", + Datr: "SF7BW125", + Codr: "4/6", + Rssi: -35, + Lsnr: 5.1, + Size: 32, + Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + }, + RXPK{ + Chan: 9, + Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Datr: "50000", + Freq: 869.1, + Modu: "FSK", + Rfch: 1, + Rssi: -75, + Size: 16, + Stat: 1, + Time: time3, + Tmst: 3512348514, + }, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal with an invalid GatewayId (too short) +func TestMarshalPUSH_DATA3(t *testing.T) { + time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + + // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + payload, err := ioutil.ReadFile("./test_data/marshal_stat") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid + Payload: &Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: time, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + t.Errorf("Successfully mashalled a invalid packet") + } +} + +// Marshal with an invalid GatewayId (too long) +func TestMarshalPUSH_DATA4(t *testing.T) { + time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + + // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + payload, err := ioutil.ReadFile("./test_data/marshal_stat") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid + Payload: &Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: time, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + t.Errorf("Successfully mashalled a invalid packet") + } +} + +// Marshal with an invalid TokenId (too short) +func TestMarshalPUSH_DATA5(t *testing.T) { + time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + + // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + payload, err := ioutil.ReadFile("./test_data/marshal_stat") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid + Payload: &Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: time, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + t.Errorf("Successfully mashalled a invalid packet") + } +} + +// Marshal with an invalid TokenId (too long) +func TestMarshalPUSH_DATA6(t *testing.T) { + time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + + // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + payload, err := ioutil.ReadFile("./test_data/marshal_stat") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14, 0x28}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid + Payload: &Payload{ + Stat: &Stat{ + Ackr: 100.0, + Alti: 145, + Long: 3.25230, + Rxok: 2, + Rxfw: 2, + Rxnb: 2, + Lati: 46.24, + Dwnb: 2, + Txnb: 2, + Time: time, + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + t.Errorf("Successfully mashalled a invalid packet") + } +} // ---------- PUSH_ACK func checkMarshalPUSH_ACK(packet *Packet) error { - raw, err := Marshal(packet) + raw, err := Marshal(packet) - if err != nil { - return err - } + if err != nil { + return err + } - if len(raw) != 4 { - return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) - } + if len(raw) != 4 { + return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + } - if raw[0] != packet.Version { - return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) - } + if raw[0] != packet.Version { + return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + } - if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) - } + if !bytes.Equal(raw[1:3], packet.Token) { + return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + } - if raw[3] != packet.Identifier { - return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) - } + if raw[3] != packet.Identifier { + return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + } - return err + return err } // Marshal a basic push_ack packet -func TestMarshalPUSH_ACK1(*t testing.T) { - packet := &Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: nil, - } - if err := checkMarshalPUSH_ACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } +func TestMarshalPUSH_ACK1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: nil, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } } // Marshal a push_ack packet with extra useless gatewayId -func TestMarshalPUSH_ACK2(*t testing.T) { - packet := &Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalPUSH_ACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } +func TestMarshalPUSH_ACK2(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } } // Marshal a push_ack packet with extra useless Payload -func TestMarshalPUSH_ACK3(*t testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, - }, - } - packet := &Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: payload, - } - if err := checkMarshalPUSH_ACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } +func TestMarshalPUSH_ACK3(t *testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: payload, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } } // Marshal a push_ack with extra useless gatewayId and payload -func TestMarshalPUSH_ACK4(*t testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, - }, - } - packet := &Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: payload, - } - if err := checkMarshalPUSH_ACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } +func TestMarshalPUSH_ACK4(t *testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: payload, + } + if err := checkMarshalPUSH_ACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } } // Marshal a push_ack with an invalid token (too short) -func TestMarshalPUSH_ACK5(*t testing.T) { - packet := &Packet{ - Version: VERSION, - Token: []byte{0xAA}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: nil, - } - _, err := Marshal(packet) - if err == nil { - t.Errorf("Successfully marshalled an invalid packet") - } +func TestMarshalPUSH_ACK5(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: nil, + } + _, err := Marshal(packet) + if err == nil { + t.Errorf("Successfully marshalled an invalid packet") + } } // Marshal a push_ack with an invalid token (too long) -func TestMarshalPUSH_ACK6(*t testing.T) { - packet := &Packet{ - Version: VERSION, - Token: []byte{0x9A, 0x7A, 0x7E}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: nil, - } - _, err := Marshal(packet) - if err == nil { - t.Errorf("Successfully marshalled an invalid packet") - } +func TestMarshalPUSH_ACK6(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0x9A, 0x7A, 0x7E}, + Identifier: PUSH_ACK, + GatewayId: nil, + Payload: nil, + } + _, err := Marshal(packet) + if err == nil { + t.Errorf("Successfully marshalled an invalid packet") + } } // ---------- PULL_DATA @@ -148,4 +473,3 @@ func TestMarshalPUSH_ACK6(*t testing.T) { // ---------- PULL_ACK // ---------- PULL_RESP - From e2fc9b1a0b193a85e1ee247194c7c8d4720eb00e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 17:49:32 +0100 Subject: [PATCH 0027/2266] Change license headers + add AUTHORS list --- AUTHORS | 1 + DevelopmentPlan.md => DEVELOPMENT_PLAN.md | 0 lorawan/gateway/decode.go | 2 +- lorawan/gateway/decode_test.go | 2 +- lorawan/gateway/encode.go | 3 +++ lorawan/gateway/encode_test.go | 2 +- lorawan/gateway/protocol.go | 2 +- 7 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 AUTHORS rename DevelopmentPlan.md => DEVELOPMENT_PLAN.md (100%) diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..3f2e71c87 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Matthias Benkort diff --git a/DevelopmentPlan.md b/DEVELOPMENT_PLAN.md similarity index 100% rename from DevelopmentPlan.md rename to DEVELOPMENT_PLAN.md diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index 59ad11611..14bf849e7 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Matthias Benkort +// Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package protocol diff --git a/lorawan/gateway/decode_test.go b/lorawan/gateway/decode_test.go index a378ee258..a7d996fbc 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/gateway/decode_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Matthias Benkort +// Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package protocol diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go index 2c5019993..3c1417853 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/gateway/encode.go @@ -1,3 +1,6 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package protocol // Marshal transform a packet to a sequence of bytes. diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index 80e0387b2..249023ed6 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Matthias Benkort +// Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package protocol diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/protocol.go index 32dadac68..6e6a60d0d 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/protocol.go @@ -1,4 +1,4 @@ -// Copyright © 2015 Matthias Benkort +// Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package gateway/protocol provides useful methods and types to handle communications with a gateway. From 74b82356f548757932a7c992eacdddc3228ebb23 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 17:50:12 +0100 Subject: [PATCH 0028/2266] Disable createTestValue (package main, just an handy tool) --- .../{createTestValue.go => createTestValue.go.nope} | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) rename lorawan/gateway/{createTestValue.go => createTestValue.go.nope} (92%) diff --git a/lorawan/gateway/createTestValue.go b/lorawan/gateway/createTestValue.go.nope similarity index 92% rename from lorawan/gateway/createTestValue.go rename to lorawan/gateway/createTestValue.go.nope index 9eab892b4..ba3ba54cc 100644 --- a/lorawan/gateway/createTestValue.go +++ b/lorawan/gateway/createTestValue.go.nope @@ -16,7 +16,7 @@ type Stat struct { Rxfw uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) Rxnb uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) Rxok uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *mytime `json:"time,omitempty"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Time *mytime `json:"time,omitempty"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format Txnb uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) } @@ -25,7 +25,7 @@ type RXPK struct { Chan uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr string `json:"codr,omitempty"` // LoRa ECC coding rate identifier Data string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *datr `json:"datr,omitempty"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Datr *datr `json:"datr,omitempty"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier Freq float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) Lsnr float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) Modu string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" @@ -33,7 +33,7 @@ type RXPK struct { Rssi int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) Size uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) Stat int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *mytime `json:"time,omitempty"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Time *mytime `json:"time,omitempty"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format Tmst uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } @@ -42,7 +42,7 @@ type RXPK struct { type TXPK struct { Codr string `json:"codr,omitempty"` // LoRa ECC coding rate identifier Data string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional - Datr *datr `json:"datr,omitempty"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Datr *datr `json:"datr,omitempty"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) Fdev uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) Freq float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) Imme bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) @@ -53,7 +53,7 @@ type TXPK struct { Prea uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) Rfch uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) Size uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *mytime `json:"time,omitempty"` // Send packet at a certain time (GPS synchronization required) + Time *mytime `json:"time,omitempty"` // Send packet at a certain time (GPS synchronization required) Tmst uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } From c51bab7e84f70d06317791aaad89f36614e7b86d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 18:08:49 +0100 Subject: [PATCH 0029/2266] Add test for Marshal() about PULL_DATA --- lorawan/gateway/encode_test.go | 120 +++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index 249023ed6..edfd8d19c 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -469,6 +469,126 @@ func TestMarshalPUSH_ACK6(t *testing.T) { } // ---------- PULL_DATA +func checkMarshalPULL_ACK(packet *Packet) error { + raw, err := Marshal(packet) + + if err != nil { + return err + } + + if len(raw) != 12 { + return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + } + + if raw[0] != packet.Version { + return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + } + + if !bytes.Equal(raw[1:3], packet.Token) { + return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + } + + if raw[3] != packet.Identifier { + return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + } + + if !bytes.Equal(raw[4:12], packet.GatewayId) { + return errors.New(fmt.Printf("Invalid raw gatewayId: % x", raw[4:12])) + } + + return err +} + +// Marshal a basic pull_data packet +func TestMarshalPULL_DATA1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPULL_DATA(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a pull_data packet with an extra useless Payload +func TestMarshalPULL_DATA2(t *testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: payload, + } + if err := checkMarshalPULL_DATA(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a pull_data packet with an invalid token (too short) +func TestMarshalPULL_DATA1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPULL_DATA(packet); err == nil { + t.Errorf("Successfully marshalled a packet with an invalid token") + } +} + +// Marshal a pull_data packet with an invalid token (too long) +func TestMarshalPULL_DATA1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14, 0x42}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPULL_DATA(packet); err == nil { + t.Errorf("Successfully marshalled a packet with an invalid token") + } +} + +// Marshal a pull_data packet with an invalid gatewayId (too short) +func TestMarshalPULL_DATA1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPULL_DATA(packet); err == nil { + t.Errorf("Successfully marshalled a packet with an invalid gatewayId") + } +} + +// Marshal a pull_data packet with an invalid gatewayId (too long) +func TestMarshalPULL_DATA1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalPULL_DATA(packet); err == nil { + t.Errorf("Successfully marshalled a packet with an invalid gatewayId") + } +} // ---------- PULL_ACK From 432e5d3880ca43632079358dfa13d166ecafce6d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 18:14:05 +0100 Subject: [PATCH 0030/2266] Add test for Marshal() about PULL_ACK --- lorawan/gateway/encode_test.go | 110 +++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index edfd8d19c..059096bdd 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -342,7 +342,7 @@ func TestMarshalPUSH_DATA6(t *testing.T) { } // ---------- PUSH_ACK -func checkMarshalPUSH_ACK(packet *Packet) error { +func checkMarshalACK(packet *Packet) error { raw, err := Marshal(packet) if err != nil { @@ -377,7 +377,7 @@ func TestMarshalPUSH_ACK1(t *testing.T) { GatewayId: nil, Payload: nil, } - if err := checkMarshalPUSH_ACK(packet); err != nil { + if err := checkMarshalACK(packet); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -391,7 +391,7 @@ func TestMarshalPUSH_ACK2(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } - if err := checkMarshalPUSH_ACK(packet); err != nil { + if err := checkMarshalACK(packet); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -412,7 +412,7 @@ func TestMarshalPUSH_ACK3(t *testing.T) { GatewayId: nil, Payload: payload, } - if err := checkMarshalPUSH_ACK(packet); err != nil { + if err := checkMarshalACK(packet); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -433,7 +433,7 @@ func TestMarshalPUSH_ACK4(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: payload, } - if err := checkMarshalPUSH_ACK(packet); err != nil { + if err := checkMarshalACK(packet); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -591,5 +591,105 @@ func TestMarshalPULL_DATA1(t *testing.T) { } // ---------- PULL_ACK +// Marshal a basic pull_ack packet +func TestMarshalPULL_ACK1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PULL_ACK, + GatewayId: nil, + Payload: nil, + } + if err := checkMarshalACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a pull_ack packet with extra useless gatewayId +func TestMarshalPULL_ACK2(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PUSH_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: nil, + } + if err := checkMarshalACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a pull_ack packet with extra useless Payload +func TestMarshalPULL_ACK3(t *testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PULL_ACK, + GatewayId: nil, + Payload: payload, + } + if err := checkMarshalACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a pull_ack with extra useless gatewayId and payload +func TestMarshalPULL_ACK4(t *testing.T) { + payload := &Payload{ + Stat: &Stat{ + Rxfw: 14, + Rxnb: 14, + Rxok: 14, + }, + } + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PULL_ACK, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: payload, + } + if err := checkMarshalACK(packet); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal a pull_ack with an invalid token (too short) +func TestMarshalPULL_ACK5(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA}, + Identifier: PULL_ACK, + GatewayId: nil, + Payload: nil, + } + _, err := Marshal(packet) + if err == nil { + t.Errorf("Successfully marshalled an invalid packet") + } +} + +// Marshal a pull_ack with an invalid token (too long) +func TestMarshalPULL_ACK6(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0x9A, 0x7A, 0x7E}, + Identifier: PULL_ACK, + GatewayId: nil, + Payload: nil, + } + _, err := Marshal(packet) + if err == nil { + t.Errorf("Successfully marshalled an invalid packet") + } +} // ---------- PULL_RESP +//TODO From cfec82589a49fa139bcaecbbd880690171258095 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 22:14:26 +0100 Subject: [PATCH 0031/2266] Add test for Marshal() about PULL_RESP --- lorawan/gateway/encode_test.go | 169 ++++++++++++++++++++++++++++++++- 1 file changed, 168 insertions(+), 1 deletion(-) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index 059096bdd..01d074c33 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -692,4 +692,171 @@ func TestMarshalPULL_ACK6(t *testing.T) { } // ---------- PULL_RESP -//TODO +func checkMarshalPULL_RESP(packet *Packet, payload []byte) error { + raw, err := Marshal(packet) + + if err != nil { + return err + } + + if len(raw) < 4 { + return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + } + + if raw[0] != packet.Version { + return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + } + + if !bytes.Equal(raw[1:3], packet.Token) { + return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + } + + if raw[3] != packet.Identifier { + return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + } + + if packet.Payload != nil && !bytes.Equal(raw[4:], payload) { + return errors.New(fmt.Printf("Invalid raw payload: % x", raw[4:])) + } + + return err +} + +// Marshal() for a basic PULL_RESP packet with no payload +func TestMarshallPULL_RESP1(t *testing.T) { + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PULL_RESP, + GatewayId: nil, + Payload: nil, + } + + if err = checkMarshalPUSH_DATA(packet, Make([]byte)); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal() for a basic PULL_RESP packet with RXPK payload +func TestMarshallPULL_RESP2(t *testing.T) { + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + payload, err := ioutil.ReadFile("./test_data/marshal_txpk") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PULL_RESP, + GatewayId: nil, + Payload: &Payload{ + TXPK: &TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: "SF11BW125", + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal() for a basic PULL_RESP packet with RXPK payload and useless gatewayId +func TestMarshallPULL_RESP3(t *testing.T) { + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + payload, err := ioutil.ReadFile("./test_data/marshal_txpk") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14}, + Identifier: PULL_RESP, + GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, + Payload: &Payload{ + TXPK: &TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: "SF11BW125", + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + t.Errorf("Failed to marshal packet: %v", err) + } +} + +// Marshal() for a PULL_RESP packet with an invalid token (too short) +func TestMarshallPULL_RESP4(t *testing.T) { + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + payload, err := ioutil.ReadFile("./test_data/marshal_txpk") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA}, + Identifier: PULL_RESP, + GatewayId: nil, + Payload: &Payload{ + TXPK: &TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: "SF11BW125", + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + t.Errorf("Successfully marshalled a packet with an invalid token") + } +} + +// Marshal() for a PULL_RESP packet with an invalid token (too long) +func TestMarshallPULL_RESP5(t *testing.T) { + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + payload, err := ioutil.ReadFile("./test_data/marshal_txpk") + + packet := &Packet{ + Version: VERSION, + Token: []byte{0xAA, 0x14, 0x42}, + Identifier: PULL_RESP, + GatewayId: nil, + Payload: &Payload{ + TXPK: &TXPK{ + Imme: true, + Freq: 864.123456, + Rfch: 0, + Powe: 14, + Modu: "LORA", + Datr: "SF11BW125", + Codr: "4/6", + Ipol: false, + Size: 32, + Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + }, + }, + } + + if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + t.Errorf("Successfully marshalled a packet with an invalid token") + } +} From aea29434e42ef440893e5ecfe21a97797eea51de Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Dec 2015 22:39:00 +0100 Subject: [PATCH 0032/2266] add README, time to adopt a proper workflow --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 000000000..6ebea7211 --- /dev/null +++ b/README.md @@ -0,0 +1,49 @@ +The Things Network Core Architecture +==================================== + +## How to Contribute + +- Open an issue and explain on what you're working +- Work in a separate branch forked from `develop` +- Bring your feature to life and make a PR +- Do not commit on `master` anymore + +## Folder structure + +So far: +``` +-| components +-----| broker +-----| handler +-----| networkcontroller +-----| router + +-| adapters +-----| router-broker-http/brokAdapter +-----| router-gateway-udp/gateAdapter +-----| broker-ns-local/nsAdapter +-----| broker-handler-http/handAdapter +-----| broker-router-http/routAdapter +-----| ns-broker-local/brokAdapter +-----| handler-broker-http/brokAdapter + +-| lorawan +-----| mac +-----| gateway +---------| protocol + +-| simulators +-----| gateway +``` + +## Development Plan + +See the [development plan](DEVELOPMENT_PLAN.md) + +## Authors + +See the [author's list](AUTHORS.md) + +## License + +See the [license file](LICENSE) From bc3cc31ec8a77eaeb6395ba5ae981d43a955f5fc Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 10:52:42 +0100 Subject: [PATCH 0033/2266] Create pointer helpers --- utils/pointer/main.go | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 utils/pointer/main.go diff --git a/utils/pointer/main.go b/utils/pointer/main.go new file mode 100644 index 000000000..44bf70df0 --- /dev/null +++ b/utils/pointer/main.go @@ -0,0 +1,40 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package pointer provides helper method to quickly define pointer from basic go types +package pointer + +// String creates a pointer to a string from a string value +func String(v string) *string { + p := new(string) + *p = v + return p +} + +// Int creates a pointer to an int from an int value +func Int(v int) *int { + p := new(int) + *p = v + return p +} + +// Uint creates a pointer to an unsigned int from an unsigned int value +func Uint(v uint) *uint { + p := new(uint) + *p = v + return p +} + +// Float64 creates a pointer to a float64 from a float64 value +func Float64(v float64) *float64 { + p := new(float64) + *p = v + return p +} + +// Bool creates a pointer to a boolean from a boolean value +func Bool(v bool) *bool { + p := new(bool) + *p = v + return p +} From ac06868a8b98d52610041806b4231141668b4562 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 10:53:13 +0100 Subject: [PATCH 0034/2266] Change package name --- lorawan/gateway/decode.go | 2 +- lorawan/gateway/decode_test.go | 2 +- lorawan/gateway/encode.go | 2 +- lorawan/gateway/encode_test.go | 2 +- lorawan/gateway/{protocol.go => gateway.go} | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) rename lorawan/gateway/{protocol.go => gateway.go} (99%) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index 14bf849e7..0d020d376 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package protocol +package gateway import ( "encoding/json" diff --git a/lorawan/gateway/decode_test.go b/lorawan/gateway/decode_test.go index a7d996fbc..ef8bd0fa3 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/gateway/decode_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package protocol +package gateway import ( "bytes" diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go index 3c1417853..3a0d135a2 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/gateway/encode.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package protocol +package gateway // Marshal transform a packet to a sequence of bytes. func Marshal(packet Packet) ([]byte, error) { diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index 01d074c33..a7056d66a 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package protocol +package gateway import ( "bytes" diff --git a/lorawan/gateway/protocol.go b/lorawan/gateway/gateway.go similarity index 99% rename from lorawan/gateway/protocol.go rename to lorawan/gateway/gateway.go index 6e6a60d0d..816dfc48d 100644 --- a/lorawan/gateway/protocol.go +++ b/lorawan/gateway/gateway.go @@ -4,7 +4,7 @@ // Package gateway/protocol provides useful methods and types to handle communications with a gateway. // // This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT -package protocol +package gateway import ( "time" From 001be4db2c288c0024f20b88c329987768f087fe Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 10:54:36 +0100 Subject: [PATCH 0035/2266] rename pointer/main.go -> pointer/pointer.go --- utils/pointer/{main.go => pointer.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename utils/pointer/{main.go => pointer.go} (100%) diff --git a/utils/pointer/main.go b/utils/pointer/pointer.go similarity index 100% rename from utils/pointer/main.go rename to utils/pointer/pointer.go From 33e71e6575f4a1c89fae320f3bc9056f79e095c0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 11:05:53 +0100 Subject: [PATCH 0036/2266] Update decode_test with pointers in structs --- lorawan/gateway/decode_test.go | 104 +++++++++++++++++---------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/lorawan/gateway/decode_test.go b/lorawan/gateway/decode_test.go index ef8bd0fa3..c1bc5d673 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/gateway/decode_test.go @@ -8,6 +8,7 @@ import ( "reflect" "testing" "time" + "utils/pointer" ) // ------------------------------------------------------------ @@ -100,16 +101,16 @@ func TestUnmarshal2(t *testing.T) { statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") stat := Stat{ - Time: statTime, - Lati: 46.24000, - Long: 3.25230, - Alti: 145, - Rxnb: 2, - Rxok: 2, - Rxfw: 2, - Ackr: 100.0, - Dwnb: 2, - Txnb: 2, + Time: &statTime, + Lati: pointer.Float64(46.24000), + Long: pointer.Float64(3.25230), + Alti: pointer.Int(145), + Rxnb: pointer.Uint(2), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Ackr: pointer.Float64(100.0), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), } if !reflect.DeepEqual(stat, *packet.Payload.Stat) { @@ -206,17 +207,17 @@ func TestUnmarshal3(t *testing.T) { rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") rxpk := RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", - Datr: "50000", - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, - Time: rxpkTime, - Tmst: 3512348514, + Chan: pointer.Uint(9), + Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), + Datr: pointer.String("50000"), + Freq: pointer.Float64(869.1), + Modu: pointer.String("FSK"), + Rfch: pointer.Uint(1), + Rssi: pointer.Int(-75), + Size: pointer.Uint(16), + Stat: pointer.Int(1), + Time: &rxpkTime, + Tmst: pointer.Uint(3512348514), } if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { @@ -324,17 +325,17 @@ func TestUnmarshal4(t *testing.T) { rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") rxpk := RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", - Datr: "50000", - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, - Time: rxpkTime, - Tmst: 3512348514, + Chan: pointer.Uint(9), + Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), + Datr: pointer.String("50000"), + Freq: pointer.Float64(869.1), + Modu: pointer.String("FSK"), + Rfch: pointer.Uint(1), + Rssi: pointer.Int(-75), + Size: pointer.Uint(16), + Stat: pointer.Int(1), + Time: &rxpkTime, + Tmst: pointer.Uint(3512348514), } if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { @@ -344,16 +345,17 @@ func TestUnmarshal4(t *testing.T) { statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") stat := Stat{ + Time: &statTime, + Lati: pointer.Float64(46.24000), + Long: pointer.Float64(3.25230), + Alti: pointer.Int(145), + Rxnb: pointer.Uint(2), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Ackr: pointer.Float64(100.0), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), Time: statTime, - Lati: 46.24000, - Long: 3.25230, - Alti: 145, - Rxnb: 2, - Rxok: 2, - Rxfw: 2, - Ackr: 100.0, - Dwnb: 2, - Txnb: 2, } if !reflect.DeepEqual(stat, *packet.Payload.Stat) { @@ -412,16 +414,16 @@ func TestUnmarshal5(t *testing.T) { } txpk := TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", - Datr: "SF11BW125", - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + Imme: pointer.Bool(true), + Freq: pointer.Float64(864.123456), + Rfch: pointer.Uint(0), + Powe: pointer.Uint(14), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF11BW125"), + Codr: pointer.String("4/6"), + Ipol: pointer.Bool(false), + Size: pointer.Uint(32), + Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), } if !reflect.DeepEqual(txpk, *packet.Payload.TXPK) { From 95835da1c100f1dd131c4c576d13b25e864d5658 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 11:07:55 +0100 Subject: [PATCH 0037/2266] Format decode_test --- I need to use a plugin with vim >.> --- lorawan/gateway/decode_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan/gateway/decode_test.go b/lorawan/gateway/decode_test.go index c1bc5d673..d1d163553 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/gateway/decode_test.go @@ -8,7 +8,7 @@ import ( "reflect" "testing" "time" - "utils/pointer" + "utils/pointer" ) // ------------------------------------------------------------ From 8e53a9e26ceb7ac8319fe19bcb269fbfefd14ca4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 11:08:30 +0100 Subject: [PATCH 0038/2266] Changes types in gateway to pointers --- lorawan/gateway/gateway.go | 77 +++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index 816dfc48d..d2edf888d 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -12,53 +12,53 @@ import ( // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr string `json:"codr"` // LoRa ECC coding rate identifier - Data string `json:"data"` // Base64 encoded RF packet payload, padded - Datr string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Rfch uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) - Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Stat int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr"` // LoRa ECC coding rate identifier + Data *string `json:"data"` // Base64 encoded RF packet payload, padded + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) } // TXPK represents a downlink json message format received by the gateway. // Most field are optional. type TXPK struct { - Codr string `json:"codr"` // LoRa ECC coding rate identifier - Data string `json:"data"` // Base64 encoded RF packet payload, padding optional - Datr string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) - Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) - Ipol bool `json:"ipol"` // Lora modulation polarization inversion - Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Ncrc bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) - Powe uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) - Prea uint `json:"prea"` // RF preamble size (unsigned integer) - Rfch uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) - Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Time time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `json:"codr"` // LoRa ECC coding rate identifier + Data *string `json:"data"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol"` // Lora modulation polarization inversion + Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway type Stat struct { - Ackr float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged - Alti int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) - Dwnb uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) - Lati float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) - Long float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) - Rxnb uint `json:"rxnb"` // Number of radio packets received (unsigned integer) - Rxok uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC - Time time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb uint `json:"txnb"` // Number of packets emitted (unsigned integer) + Ackr *float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged + Alti *int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) + Lati *float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint `json:"rxnb"` // Number of radio packets received (unsigned integer) + Rxok *uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC + Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint `json:"txnb"` // Number of packets emitted (unsigned integer) } // Packet as seen by the gateway. @@ -87,4 +87,5 @@ const ( PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA ) +// Protocol version in use const VERSION = 0x01 From 7c644434857e5392592ff0cfddb5bbd987d8f9f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 13:35:36 +0100 Subject: [PATCH 0039/2266] Change decode to use pointer instead of direct type --- lorawan/gateway/decode.go | 44 +++++++++++++-------------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index 0d020d376..735451720 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -55,26 +55,23 @@ func Unmarshal(raw []byte) (*Packet, error) { // timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time // module parse RFC3339 by default type timeParser struct { - Value time.Time // The parsed time value - Parsed bool // Set to true if the value has been parsed + Value *time.Time // The parsed time value } // implement the Unmarshaller interface from encoding/json func (t *timeParser) UnmarshalJSON(raw []byte) error { - var err error - value := strings.Trim(string(raw), `"`) - t.Value, err = time.Parse("2006-01-02 15:04:05 GMT", value) + str := strings.Trim(string(raw), `"`) + v, err := time.Parse("2006-01-02 15:04:05 GMT", str) if err != nil { - t.Value, err = time.Parse(time.RFC3339, value) + v, err = time.Parse(time.RFC3339, str) } if err != nil { - t.Value, err = time.Parse(time.RFC3339Nano, value) + v, err = time.Parse(time.RFC3339Nano, str) } if err != nil { return errors.New("Unkown date format. Unable to parse time") } - - t.Parsed = true + t.Value = &v return nil } @@ -82,19 +79,18 @@ func (t *timeParser) UnmarshalJSON(raw []byte) error { // Depending on the modulation type, the datr type could be either a string or a number. // We're gonna parse it as a string in any case. type datrParser struct { - Value string // The parsed value - Parsed bool // Set to true if the value has been parsed + Value *string // The parsed value } // implement the Unmarshaller interface from encoding/json func (d *datrParser) UnmarshalJSON(raw []byte) error { - d.Value = strings.Trim(string(raw), `"`) + v := strings.Trim(string(raw), `"`) - if d.Value == "" { + if v == "" { return errors.New("Invalid datr format") } - d.Parsed = true + d.Value = &v return nil } @@ -122,30 +118,20 @@ func unmarshalPayload(raw []byte) (*Payload, error) { return nil, err } - if customStruct.Stat != nil && customStruct.Stat.Time.Parsed { + if customStruct.Stat != nil { payload.Stat.Time = customStruct.Stat.Time.Value } if customStruct.RXPK != nil { for i, x := range *customStruct.RXPK { - if x.Time.Parsed { - (*payload.RXPK)[i].Time = x.Time.Value - } - - if x.Datr.Parsed { - (*payload.RXPK)[i].Datr = x.Datr.Value - } + (*payload.RXPK)[i].Time = x.Time.Value + (*payload.RXPK)[i].Datr = x.Datr.Value } } if customStruct.TXPK != nil { - if customStruct.TXPK.Time.Parsed { - payload.TXPK.Time = customStruct.TXPK.Time.Value - } - - if customStruct.TXPK.Datr.Parsed { - payload.TXPK.Datr = customStruct.TXPK.Datr.Value - } + payload.TXPK.Time = customStruct.TXPK.Time.Value + payload.TXPK.Datr = customStruct.TXPK.Datr.Value } return payload, nil From 9216ea75f46d04d3d21411842f804ce562505b80 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 13:36:22 +0100 Subject: [PATCH 0040/2266] Add a dump helper to utils pointer --- utils/pointer/pointer.go | 63 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 44bf70df0..0035e599c 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -4,6 +4,12 @@ // package pointer provides helper method to quickly define pointer from basic go types package pointer +import ( + "fmt" + "reflect" + "time" +) + // String creates a pointer to a string from a string value func String(v string) *string { p := new(string) @@ -38,3 +44,60 @@ func Bool(v bool) *bool { *p = v return p } + +// DumpStruct print the content of a struct of pointers +func DumpPStruct(s interface{}) { + v := reflect.ValueOf(s) + + if v.Kind() != reflect.Struct { + fmt.Printf("Unable to dump: Not a struct.") + return + } + + for k := 0; k < v.NumField(); k += 1 { + i := v.Field(k).Interface() + fmt.Printf("%v: ", v.Type().Field(k).Name) + + switch t := i.(type) { + case *bool: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *int: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *uint: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *string: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *float64: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *time.Time: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + default: + fmt.Printf("unknown\n") + } + } +} + From 3524e650379e30499c5efc4b501d889bfc63c4d4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 13:37:45 +0100 Subject: [PATCH 0041/2266] Fix typo in decode_test --- lorawan/gateway/decode_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lorawan/gateway/decode_test.go b/lorawan/gateway/decode_test.go index d1d163553..0493dee12 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/gateway/decode_test.go @@ -5,10 +5,10 @@ package gateway import ( "bytes" + "github.com/thethingsnetwork/core/utils/pointer" "reflect" "testing" "time" - "utils/pointer" ) // ------------------------------------------------------------ @@ -355,7 +355,6 @@ func TestUnmarshal4(t *testing.T) { Ackr: pointer.Float64(100.0), Dwnb: pointer.Uint(2), Txnb: pointer.Uint(2), - Time: statTime, } if !reflect.DeepEqual(stat, *packet.Payload.Stat) { From 6e4f8eeaa3c3edb44d974d7ecfc82289fd95d5f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 13:55:14 +0100 Subject: [PATCH 0042/2266] Swtich encode_test to pointer as well --- lorawan/gateway/encode_test.go | 338 ++++++++++++++++----------------- 1 file changed, 169 insertions(+), 169 deletions(-) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index a7056d66a..2eef90fb0 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -5,8 +5,8 @@ package gateway import ( "bytes" + "github.com/thethingsnetwork/core/utils/pointer" "io/ioutil" - "reflect" "testing" "time" ) @@ -51,7 +51,7 @@ func checkMarshalPUSH_DATA(packet *Packet, payload []byte) error { // Marshal a basic push_data packet with Stat payload func TestMarshalPUSH_DATA1(t *testing.T) { - time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") @@ -63,16 +63,16 @@ func TestMarshalPUSH_DATA1(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, - Time: time, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), + Time: &time1, }, }, } @@ -103,32 +103,32 @@ func TestMarshalPUSH_DATA2(t *testing.T) { Payload: &Payload{ RXPK: &[]RXPK{ RXPK{ - Time: time1, - Tmst: 3512348611, - Chan: 2, - Rfch: 0, - Freq: 866.349812, - Stat: 1, - Modu: "LORA", - Datr: "SF7BW125", - Codr: "4/6", - Rssi: -35, - Lsnr: 5.1, - Size: 32, - Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + Time: &time1, + Tmst: pointer.Uint(3512348611), + Chan: pointer.Uint(2), + Rfch: pointer.Uint(0), + Freq: pointer.Float64(866.349812), + Stat: pointer.Int(1), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF7BW125"), + Codr: pointer.String("4/6"), + Rssi: pointer.Int(-35), + Lsnr: pointer.Float64(5.1), + Size: pointer.Uint(32), + Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), }, RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", - Datr: "50000", - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, - Time: time2, - Tmst: 3512348514, + Chan: pointer.Uint(9), + Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), + Datr: pointer.String("50000"), + Freq: pointer.Float64(869.1), + Modu: pointer.String("FSK"), + Rfch: pointer.Uint(1), + Rssi: pointer.Int(-75), + Size: pointer.Uint(16), + Stat: pointer.Int(1), + Time: &time2, + Tmst: pointer.Uint(3512348514), }, }, }, @@ -160,45 +160,45 @@ func TestMarshalPUSH_DATA2(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, - Time: time1, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), + Time: &time1, }, RXPK: &[]RXPK{ RXPK{ - Time: time2, - Tmst: 3512348611, - Chan: 2, - Rfch: 0, - Freq: 866.349812, - Stat: 1, - Modu: "LORA", - Datr: "SF7BW125", - Codr: "4/6", - Rssi: -35, - Lsnr: 5.1, - Size: 32, - Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + Time: &time2, + Tmst: pointer.Uint(3512348611), + Chan: pointer.Uint(2), + Rfch: pointer.Uint(0), + Freq: pointer.Float64(866.349812), + Stat: pointer.Int(1), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF7BW125"), + Codr: pointer.String("4/6"), + Rssi: pointer.Int(-35), + Lsnr: pointer.Float64(5.1), + Size: pointer.Uint(32), + Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), }, RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", - Datr: "50000", - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, - Time: time3, - Tmst: 3512348514, + Chan: pointer.Uint(9), + Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), + Datr: pointer.String("50000"), + Freq: pointer.Float64(869.1), + Modu: pointer.String("FSK"), + Rfch: pointer.Uint(1), + Rssi: pointer.Int(-75), + Size: pointer.Uint(16), + Stat: pointer.Int(1), + Time: &time3, + Tmst: pointer.Uint(3512348514), }, }, }, @@ -211,7 +211,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { // Marshal with an invalid GatewayId (too short) func TestMarshalPUSH_DATA3(t *testing.T) { - time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") @@ -223,16 +223,16 @@ func TestMarshalPUSH_DATA3(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, - Time: time, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), + Time: &time1, }, }, } @@ -244,7 +244,7 @@ func TestMarshalPUSH_DATA3(t *testing.T) { // Marshal with an invalid GatewayId (too long) func TestMarshalPUSH_DATA4(t *testing.T) { - time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") @@ -256,16 +256,16 @@ func TestMarshalPUSH_DATA4(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, - Time: time, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), + Time: &time1, }, }, } @@ -277,7 +277,7 @@ func TestMarshalPUSH_DATA4(t *testing.T) { // Marshal with an invalid TokenId (too short) func TestMarshalPUSH_DATA5(t *testing.T) { - time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") @@ -289,16 +289,16 @@ func TestMarshalPUSH_DATA5(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, - Time: time, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), + Time: &time1, Ackr: 100.0, }, }, } @@ -310,7 +310,7 @@ func TestMarshalPUSH_DATA5(t *testing.T) { // Marshal with an invalid TokenId (too long) func TestMarshalPUSH_DATA6(t *testing.T) { - time, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") + time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") @@ -322,16 +322,16 @@ func TestMarshalPUSH_DATA6(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, - Time: time, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), + Time: &time1, Ackr: 100.0, Ackr: 100.0, }, }, } @@ -400,9 +400,9 @@ func TestMarshalPUSH_ACK2(t *testing.T) { func TestMarshalPUSH_ACK3(t *testing.T) { payload := &Payload{ Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, + Rxfw: pointer.Uint(14), + Rxnb: pointer.Uint(14), + Rxok: pointer.Uint(14), }, } packet := &Packet{ @@ -421,9 +421,9 @@ func TestMarshalPUSH_ACK3(t *testing.T) { func TestMarshalPUSH_ACK4(t *testing.T) { payload := &Payload{ Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, + Rxfw: pointer.Uint(14), + Rxnb: pointer.Uint(14), + Rxok: pointer.Uint(14), }, } packet := &Packet{ @@ -517,9 +517,9 @@ func TestMarshalPULL_DATA1(t *testing.T) { func TestMarshalPULL_DATA2(t *testing.T) { payload := &Payload{ Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, + Rxfw: pointer.Uint(14), + Rxnb: pointer.Uint(14), + Rxok: pointer.Uint(14), }, } packet := &Packet{ @@ -623,9 +623,9 @@ func TestMarshalPULL_ACK2(t *testing.T) { func TestMarshalPULL_ACK3(t *testing.T) { payload := &Payload{ Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, + Rxfw: pointer.Uint(14), + Rxnb: pointer.Uint(14), + Rxok: pointer.Uint(14), }, } packet := &Packet{ @@ -644,9 +644,9 @@ func TestMarshalPULL_ACK3(t *testing.T) { func TestMarshalPULL_ACK4(t *testing.T) { payload := &Payload{ Stat: &Stat{ - Rxfw: 14, - Rxnb: 14, - Rxok: 14, + Rxfw: pointer.Uint(14), + Rxnb: pointer.Uint(14), + Rxok: pointer.Uint(14), }, } packet := &Packet{ @@ -749,16 +749,16 @@ func TestMarshallPULL_RESP2(t *testing.T) { GatewayId: nil, Payload: &Payload{ TXPK: &TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", - Datr: "SF11BW125", - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + Imme: pointer.Bool(true), + Freq: pointer.Float64(864.123456), + Rfch: pointer.Uint(0), + Powe: pointer.Uint(14), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF11BW125"), + Codr: pointer.String("4/6"), + Ipol: pointer.Bool(false), + Size: pointer.Uint(32), + Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), }, }, } @@ -780,16 +780,16 @@ func TestMarshallPULL_RESP3(t *testing.T) { GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ TXPK: &TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", - Datr: "SF11BW125", - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + Imme: pointer.Bool(true), + Freq: pointer.Float64(864.123456), + Rfch: pointer.Uint(0), + Powe: pointer.Uint(14), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF11BW125"), + Codr: pointer.String("4/6"), + Ipol: pointer.Bool(false), + Size: pointer.Uint(32), + Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), }, }, } @@ -811,16 +811,16 @@ func TestMarshallPULL_RESP4(t *testing.T) { GatewayId: nil, Payload: &Payload{ TXPK: &TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", - Datr: "SF11BW125", - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + Imme: pointer.Bool(true), + Freq: pointer.Float64(864.123456), + Rfch: pointer.Uint(0), + Powe: pointer.Uint(14), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF11BW125"), + Codr: pointer.String("4/6"), + Ipol: pointer.Bool(false), + Size: pointer.Uint(32), + Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), }, }, } @@ -842,16 +842,16 @@ func TestMarshallPULL_RESP5(t *testing.T) { GatewayId: nil, Payload: &Payload{ TXPK: &TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", - Datr: "SF11BW125", - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + Imme: pointer.Bool(true), + Freq: pointer.Float64(864.123456), + Rfch: pointer.Uint(0), + Powe: pointer.Uint(14), + Modu: pointer.String("LORA"), + Datr: pointer.String("SF11BW125"), + Codr: pointer.String("4/6"), + Ipol: pointer.Bool(false), + Size: pointer.Uint(32), + Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), }, }, } From 7ba3859355fbf5d779bf47276d095d3b6abb2468 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 14:28:43 +0100 Subject: [PATCH 0043/2266] Regenerate test data correctly with now all json fields --- lorawan/gateway/createTestValue.go.nope | 211 ++++++++++---------- lorawan/gateway/encode_test.go | 131 ++++++++++-- lorawan/gateway/test_data/marshal_rxpk | 2 +- lorawan/gateway/test_data/marshal_rxpk_stat | 2 +- lorawan/gateway/test_data/marshal_txpk | 2 +- 5 files changed, 225 insertions(+), 123 deletions(-) diff --git a/lorawan/gateway/createTestValue.go.nope b/lorawan/gateway/createTestValue.go.nope index ba3ba54cc..0cfc915a2 100644 --- a/lorawan/gateway/createTestValue.go.nope +++ b/lorawan/gateway/createTestValue.go.nope @@ -5,56 +5,57 @@ import ( "fmt" "io/ioutil" "time" + "github.com/thethingsnetwork/core/utils/pointer" ) type Stat struct { - Ackr float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged - Alti int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Dwnb uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) - Lati float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) - Rxnb uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) - Rxok uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *mytime `json:"time,omitempty"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) + Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged + Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) + Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) + Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC + Time *mytime `json:"time,omitempty"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) } // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *datr `json:"datr,omitempty"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *mytime `json:"time,omitempty"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *datr `json:"datr,omitempty"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *mytime `json:"time,omitempty"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } // TXPK represents a downlink json message format received by the gateway. // Most field are optional. type TXPK struct { - Codr string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional - Datr *datr `json:"datr,omitempty"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *mytime `json:"time,omitempty"` // Send packet at a certain time (GPS synchronization required) - Tmst uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional + Datr *datr `json:"datr,omitempty"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *mytime `json:"time,omitempty"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } type datr struct { @@ -93,46 +94,46 @@ func main() { RXPK: &[]RXPK{ RXPK{ Time: &mytime{time.RFC3339Nano, time1}, - Tmst: 3512348611, - Chan: 2, - Rfch: 0, - Freq: 866.349812, - Stat: 1, - Modu: "LORA", + Tmst: pointer.Uint(3512348611), + Chan: pointer.Uint(2), + Rfch: pointer.Uint(0), + Freq: pointer.Float64(866.349812), + Stat: pointer.Int(1), + Modu: pointer.String("LORA"), Datr: &datr{"string", "SF7BW125"}, - Codr: "4/6", - Rssi: -35, - Lsnr: 5.1, - Size: 32, - Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + Codr: pointer.String("4/6"), + Rssi: pointer.Int(-35), + Lsnr: pointer.Float64(5.1), + Size: pointer.Uint(32), + Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), }, RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Chan: pointer.Uint(9), + Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), Datr: &datr{"uint", "50000"}, - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, + Freq: pointer.Float64(869.1), + Modu: pointer.String("FSK"), + Rfch: pointer.Uint(1), + Rssi: pointer.Int(-75), + Size: pointer.Uint(16), + Stat: pointer.Int(1), Time: &mytime{time.RFC3339Nano, time2}, - Tmst: 3512348514, + Tmst: pointer.Uint(3512348514), }, }, }) rawStat, _ := json.Marshal(Payload{ Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), Time: &mytime{"2006-01-02 15:04:05 GMT", time3}, }, }) @@ -141,59 +142,59 @@ func main() { RXPK: &[]RXPK{ RXPK{ Time: &mytime{time.RFC3339Nano, time1}, - Tmst: 3512348611, - Chan: 2, - Rfch: 0, - Freq: 866.349812, - Stat: 1, - Modu: "LORA", + Tmst: pointer.Uint(3512348611), + Chan: pointer.Uint(2), + Rfch: pointer.Uint(0), + Freq: pointer.Float64(866.349812), + Stat: pointer.Int(1), + Modu: pointer.String("LORA"), Datr: &datr{"string", "SF7BW125"}, - Codr: "4/6", - Rssi: -35, - Lsnr: 5.1, - Size: 32, - Data: "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + Codr: pointer.String("4/6"), + Rssi: pointer.Int(-35), + Lsnr: pointer.Float64(5.1), + Size: pointer.Uint(32), + Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), }, RXPK{ - Chan: 9, - Data: "VEVTVF9QQUNLRVRfMTIzNA==", + Chan: pointer.Uint(9), + Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), Datr: &datr{"uint", "50000"}, - Freq: 869.1, - Modu: "FSK", - Rfch: 1, - Rssi: -75, - Size: 16, - Stat: 1, + Freq: pointer.Float64(869.1), + Modu: pointer.String("FSK"), + Rfch: pointer.Uint(1), + Rssi: pointer.Int(-75), + Size: pointer.Uint(16), + Stat: pointer.Int(1), Time: &mytime{time.RFC3339Nano, time2}, - Tmst: 3512348514, + Tmst: pointer.Uint(3512348514), }, }, Stat: &Stat{ - Ackr: 100.0, - Alti: 145, - Long: 3.25230, - Rxok: 2, - Rxfw: 2, - Rxnb: 2, - Lati: 46.24, - Dwnb: 2, - Txnb: 2, + Ackr: pointer.Float64(100.0), + Alti: pointer.Int(145), + Long: pointer.Float64(3.25230), + Rxok: pointer.Uint(2), + Rxfw: pointer.Uint(2), + Rxnb: pointer.Uint(2), + Lati: pointer.Float64(46.24), + Dwnb: pointer.Uint(2), + Txnb: pointer.Uint(2), Time: &mytime{"2006-01-02 15:04:05 GMT", time3}, }, }) rawTXPK, _ := json.Marshal(Payload{ TXPK: &TXPK{ - Imme: true, - Freq: 864.123456, - Rfch: 0, - Powe: 14, - Modu: "LORA", + Imme: pointer.Bool(true), + Freq: pointer.Float64(864.123456), + Rfch: pointer.Uint(0), + Powe: pointer.Uint(14), + Modu: pointer.String("LORA"), Datr: &datr{"string", "SF11BW125"}, - Codr: "4/6", - Ipol: false, - Size: 32, - Data: "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + Codr: pointer.String("4/6"), + Ipol: pointer.Bool(false), + Size: pointer.Uint(32), + Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), }, }) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index 2eef90fb0..ca77647c9 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -53,7 +53,20 @@ func checkMarshalPUSH_DATA(packet *Packet, payload []byte) error { func TestMarshalPUSH_DATA1(t *testing.T) { time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + // { + // "stat": { + // "ackr": 100, + // "alti": 145, + // "dwnb": 2, + // "lati": 46.24, + // "long": 3.2523, + // "rxfw": 2, + // "rxnb": 2, + // "rxok": 2, + // "time": "2014-01-12 08:59:28 GMT", + // "txnb": 2 + // } + // } payload, err := ioutil.ReadFile("./test_data/marshal_stat") packet := &Packet{ @@ -87,7 +100,38 @@ func TestMarshalPUSH_DATA2(t *testing.T) { time1, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") - //{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}]} + //{ + // "rxpk": [ + // { + // "chan": 2, + // "codr": "4/6", + // "data": "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + // "datr": "SF7BW125", + // "freq": 866.349812, + // "lsnr": 5.1, + // "modu": "LORA", + // "rfch": 0, + // "rssi": -35, + // "size": 32, + // "stat": 1, + // "time": "2013-03-31T16:21:17.528002Z", + // "tmst": 3512348611 + // }, + // { + // "chan": 9, + // "data": "VEVTVF9QQUNLRVRfMTIzNA==", + // "datr": 50000, + // "freq": 869.1, + // "modu": "FSK", + // "rfch": 1, + // "rssi": -75, + // "size": 16, + // "stat": 1, + // "time": "2013-03-31T16:21:17.530974Z", + // "tmst": 3512348514 + // } + // ] + //} payload, err := ioutil.ReadFile("./test_data/marshal_rxpk") if err != nil { @@ -103,19 +147,19 @@ func TestMarshalPUSH_DATA2(t *testing.T) { Payload: &Payload{ RXPK: &[]RXPK{ RXPK{ - Time: &time1, - Tmst: pointer.Uint(3512348611), Chan: pointer.Uint(2), - Rfch: pointer.Uint(0), + Codr: pointer.String("4/6"), + Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), + Datr: pointer.String("SF7BW125"), Freq: pointer.Float64(866.349812), - Stat: pointer.Int(1), + Lsnr: pointer.Float64(5.1), Modu: pointer.String("LORA"), - Datr: pointer.String("SF7BW125"), - Codr: pointer.String("4/6"), + Rfch: pointer.Uint(0), Rssi: pointer.Int(-35), - Lsnr: pointer.Float64(5.1), Size: pointer.Uint(32), - Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), + Stat: pointer.Int(1), + Time: &time1, + Tmst: pointer.Uint(3512348611), }, RXPK{ Chan: pointer.Uint(9), @@ -145,7 +189,50 @@ func TestMarshalPUSH_DATA2(t *testing.T) { time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") time3, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") - //{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} + // { + // "rxpk": [ + // { + // "chan": 2, + // "codr": "4/6", + // "data": "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", + // "datr": "SF7BW125", + // "freq": 866.349812, + // "lsnr": 5.1, + // "modu": "LORA", + // "rfch": 0, + // "rssi": -35, + // "size": 32, + // "stat": 1, + // "time": "2013-03-31T16:21:17.528002Z", + // "tmst": 3512348611 + // }, + // { + // "chan": 9, + // "data": "VEVTVF9QQUNLRVRfMTIzNA==", + // "datr": 50000, + // "freq": 869.1, + // "modu": "FSK", + // "rfch": 1, + // "rssi": -75, + // "size": 16, + // "stat": 1, + // "time": "2013-03-31T16:21:17.530974Z", + // "tmst": 3512348514 + // } + // ], + // "stat": { + // "ackr": 100, + // "alti": 145, + // "dwnb": 2, + // "lati": 46.24, + // "long": 3.2523, + // "rxfw": 2, + // "rxnb": 2, + // "rxok": 2, + // "time": "2014-01-12 08:59:28 GMT", + // "txnb": 2 + // } + // } payload, err := ioutil.ReadFile("./test_data/marshal_rxpk_stat") if err != nil { @@ -739,7 +826,21 @@ func TestMarshallPULL_RESP1(t *testing.T) { // Marshal() for a basic PULL_RESP packet with RXPK payload func TestMarshallPULL_RESP2(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + + // { + // "txpk": { + // "codr": "4/6", + // "data": "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", + // "datr": "SF11BW125", + // "freq": 864.123456, + // "imme": true, + // "ipol": false, + // "modu": "LORA", + // "powe": 14, + // "rfch": 0, + // "size": 32 + // } + // } payload, err := ioutil.ReadFile("./test_data/marshal_txpk") packet := &Packet{ @@ -770,7 +871,7 @@ func TestMarshallPULL_RESP2(t *testing.T) { // Marshal() for a basic PULL_RESP packet with RXPK payload and useless gatewayId func TestMarshallPULL_RESP3(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} payload, err := ioutil.ReadFile("./test_data/marshal_txpk") packet := &Packet{ @@ -801,7 +902,7 @@ func TestMarshallPULL_RESP3(t *testing.T) { // Marshal() for a PULL_RESP packet with an invalid token (too short) func TestMarshallPULL_RESP4(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} payload, err := ioutil.ReadFile("./test_data/marshal_txpk") packet := &Packet{ @@ -832,7 +933,7 @@ func TestMarshallPULL_RESP4(t *testing.T) { // Marshal() for a PULL_RESP packet with an invalid token (too long) func TestMarshallPULL_RESP5(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} + //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} payload, err := ioutil.ReadFile("./test_data/marshal_txpk") packet := &Packet{ diff --git a/lorawan/gateway/test_data/marshal_rxpk b/lorawan/gateway/test_data/marshal_rxpk index 65a9e7e52..f300bc08d 100644 --- a/lorawan/gateway/test_data/marshal_rxpk +++ b/lorawan/gateway/test_data/marshal_rxpk @@ -1 +1 @@ -{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}]} \ No newline at end of file +{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rfch":0,"rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}]} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_rxpk_stat b/lorawan/gateway/test_data/marshal_rxpk_stat index a78f34388..9ce400fb4 100644 --- a/lorawan/gateway/test_data/marshal_rxpk_stat +++ b/lorawan/gateway/test_data/marshal_rxpk_stat @@ -1 +1 @@ -{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} \ No newline at end of file +{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rfch":0,"rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_txpk b/lorawan/gateway/test_data/marshal_txpk index 10134eb23..a95c51fba 100644 --- a/lorawan/gateway/test_data/marshal_txpk +++ b/lorawan/gateway/test_data/marshal_txpk @@ -1 +1 @@ -{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"modu":"LORA","powe":14,"size":32}} \ No newline at end of file +{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} \ No newline at end of file From ba39ae2511b1a3db5adb6f46badb5872d67d8134 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 16:36:55 +0100 Subject: [PATCH 0044/2266] Remove createTestValue --- lorawan/gateway/createTestValue.go.nope | 232 ------------------------ 1 file changed, 232 deletions(-) delete mode 100644 lorawan/gateway/createTestValue.go.nope diff --git a/lorawan/gateway/createTestValue.go.nope b/lorawan/gateway/createTestValue.go.nope deleted file mode 100644 index 0cfc915a2..000000000 --- a/lorawan/gateway/createTestValue.go.nope +++ /dev/null @@ -1,232 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "time" - "github.com/thethingsnetwork/core/utils/pointer" -) - -type Stat struct { - Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged - Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) - Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) - Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *mytime `json:"time,omitempty"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) -} - -// RXPK represents an uplink json message format sent by the gateway -type RXPK struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *datr `json:"datr,omitempty"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *mytime `json:"time,omitempty"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) -} - -// TXPK represents a downlink json message format received by the gateway. -// Most field are optional. -type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional - Datr *datr `json:"datr,omitempty"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *mytime `json:"time,omitempty"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) -} - -type datr struct { - kind string - value string -} - -func (d *datr) MarshalJSON() ([]byte, error) { - if d.kind == "uint" { - return []byte(d.value), nil - } - return append(append([]byte(`"`), []byte(d.value)...), []byte(`"`)...), nil -} - -type mytime struct { - layout string - value time.Time -} - -func (m *mytime) MarshalJSON() ([]byte, error) { - return append(append([]byte(`"`), []byte(m.value.Format(m.layout))...), []byte(`"`)...), nil -} - -type Payload struct { - RXPK *[]RXPK `json:"rxpk,omitempty"` - Stat *Stat `json:"stat,omitempty"` - TXPK *TXPK `json:"txpk,omitempty"` -} - -func main() { - time1, _ := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") - time2, _ := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") - time3, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - - rawRXPKs, _ := json.Marshal(Payload{ - RXPK: &[]RXPK{ - RXPK{ - Time: &mytime{time.RFC3339Nano, time1}, - Tmst: pointer.Uint(3512348611), - Chan: pointer.Uint(2), - Rfch: pointer.Uint(0), - Freq: pointer.Float64(866.349812), - Stat: pointer.Int(1), - Modu: pointer.String("LORA"), - Datr: &datr{"string", "SF7BW125"}, - Codr: pointer.String("4/6"), - Rssi: pointer.Int(-35), - Lsnr: pointer.Float64(5.1), - Size: pointer.Uint(32), - Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), - }, - RXPK{ - Chan: pointer.Uint(9), - Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), - Datr: &datr{"uint", "50000"}, - Freq: pointer.Float64(869.1), - Modu: pointer.String("FSK"), - Rfch: pointer.Uint(1), - Rssi: pointer.Int(-75), - Size: pointer.Uint(16), - Stat: pointer.Int(1), - Time: &mytime{time.RFC3339Nano, time2}, - Tmst: pointer.Uint(3512348514), - }, - }, - }) - - rawStat, _ := json.Marshal(Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &mytime{"2006-01-02 15:04:05 GMT", time3}, - }, - }) - - rawRXPKsStat, _ := json.Marshal(Payload{ - RXPK: &[]RXPK{ - RXPK{ - Time: &mytime{time.RFC3339Nano, time1}, - Tmst: pointer.Uint(3512348611), - Chan: pointer.Uint(2), - Rfch: pointer.Uint(0), - Freq: pointer.Float64(866.349812), - Stat: pointer.Int(1), - Modu: pointer.String("LORA"), - Datr: &datr{"string", "SF7BW125"}, - Codr: pointer.String("4/6"), - Rssi: pointer.Int(-35), - Lsnr: pointer.Float64(5.1), - Size: pointer.Uint(32), - Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), - }, - RXPK{ - Chan: pointer.Uint(9), - Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), - Datr: &datr{"uint", "50000"}, - Freq: pointer.Float64(869.1), - Modu: pointer.String("FSK"), - Rfch: pointer.Uint(1), - Rssi: pointer.Int(-75), - Size: pointer.Uint(16), - Stat: pointer.Int(1), - Time: &mytime{time.RFC3339Nano, time2}, - Tmst: pointer.Uint(3512348514), - }, - }, - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &mytime{"2006-01-02 15:04:05 GMT", time3}, - }, - }) - - rawTXPK, _ := json.Marshal(Payload{ - TXPK: &TXPK{ - Imme: pointer.Bool(true), - Freq: pointer.Float64(864.123456), - Rfch: pointer.Uint(0), - Powe: pointer.Uint(14), - Modu: pointer.String("LORA"), - Datr: &datr{"string", "SF11BW125"}, - Codr: pointer.String("4/6"), - Ipol: pointer.Bool(false), - Size: pointer.Uint(32), - Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), - }, - }) - - fmt.Printf("Stat: %v\n", string(rawStat)) - fmt.Printf("Raw Stat: ") - for _, x := range rawStat { - fmt.Printf("0x%x,", x) - } - fmt.Printf("\n\n\n") - ioutil.WriteFile("./test_data/marshal_stat", rawStat, 0644) - - fmt.Printf("RXPKs: %v\n", string(rawRXPKs)) - fmt.Printf("Raw RXPKs: ") - for _, x := range rawRXPKs { - fmt.Printf("0x%x,", x) - } - fmt.Printf("\n\n\n") - ioutil.WriteFile("./test_data/marshal_rxpk", rawRXPKs, 0644) - - fmt.Printf("RXPKsStats: %v\n", string(rawRXPKsStat)) - fmt.Printf("Raw RXPKsStats: ") - for _, x := range rawRXPKsStat { - fmt.Printf("0x%x,", x) - } - fmt.Printf("\n\n\n") - ioutil.WriteFile("./test_data/marshal_rxpk_stat", rawRXPKsStat, 0644) - - fmt.Printf("TXPK: %v\n", string(rawTXPK)) - fmt.Printf("Raw TXPK: ") - for _, x := range rawTXPK { - fmt.Printf("0x%x,", x) - } - fmt.Printf("\n\n\n") - ioutil.WriteFile("./test_data/marshal_txpk", rawTXPK, 0644) -} From b51bec749163b169be2c336a185f963b9c384b51 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 17:11:02 +0100 Subject: [PATCH 0045/2266] Give more detailed struct tag for json marshaling in Payload struct --- lorawan/gateway/gateway.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index d2edf888d..47d575e3e 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -72,10 +72,10 @@ type Packet struct { // Payload refers to the JSON payload sent by a gateway or a server. type Payload struct { - Raw []byte `json:"-"` // The raw unparsed response - RXPK *[]RXPK `json:"rxpk"` // A list of RXPK messages transmitted if any - Stat *Stat `json:"stat"` // A Stat message transmitted if any - TXPK *TXPK `json:"txpk"` // A TXPK message transmitted if any + Raw []byte `json:"-"` // The raw unparsed response + RXPK *[]RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any + Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any + TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any } // Available packet commands From 59134d4dc76b247ec1211fba12ae5b74e7f24d26 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 17:11:41 +0100 Subject: [PATCH 0046/2266] Fix typos and syntax errors in encode_test.go --- lorawan/gateway/encode_test.go | 104 +++++++++++++++++---------------- 1 file changed, 53 insertions(+), 51 deletions(-) diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index ca77647c9..cd11b4d3e 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -5,6 +5,8 @@ package gateway import ( "bytes" + "errors" + "fmt" "github.com/thethingsnetwork/core/utils/pointer" "io/ioutil" "testing" @@ -23,27 +25,27 @@ func checkMarshalPUSH_DATA(packet *Packet, payload []byte) error { } if len(raw) < 12 { - return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) } if raw[0] != packet.Version { - return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) } if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) } if raw[3] != packet.Identifier { - return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) } if !bytes.Equal(raw[4:12], packet.GatewayId) { - return errors.New(fmt.Printf("Invalid raw gatewayId: % x", raw[4:12])) + return errors.New(fmt.Sprintf("Invalid raw gatewayId: % x", raw[4:12])) } if packet.Payload != nil && !bytes.Equal(raw[12:], payload) { - return errors.New(fmt.Printf("Invalid raw payload: % x", raw[12:])) + return errors.New(fmt.Sprintf("Invalid raw payload: % x", raw[12:])) } return err @@ -72,7 +74,7 @@ func TestMarshalPUSH_DATA1(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ Stat: &Stat{ @@ -142,7 +144,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ RXPK: &[]RXPK{ @@ -184,7 +186,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { } // Mashal a push_data packet with RXPK payload and Stat -func TestMarshalPUSH_DATA2(t *testing.T) { +func TestMarshalPUSH_DATA3(t *testing.T) { time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") time3, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") @@ -243,7 +245,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ Stat: &Stat{ @@ -297,7 +299,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { } // Marshal with an invalid GatewayId (too short) -func TestMarshalPUSH_DATA3(t *testing.T) { +func TestMarshalPUSH_DATA4(t *testing.T) { time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} @@ -306,7 +308,7 @@ func TestMarshalPUSH_DATA3(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ @@ -330,7 +332,7 @@ func TestMarshalPUSH_DATA3(t *testing.T) { } // Marshal with an invalid GatewayId (too long) -func TestMarshalPUSH_DATA4(t *testing.T) { +func TestMarshalPUSH_DATA5(t *testing.T) { time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} @@ -339,7 +341,7 @@ func TestMarshalPUSH_DATA4(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ @@ -363,7 +365,7 @@ func TestMarshalPUSH_DATA4(t *testing.T) { } // Marshal with an invalid TokenId (too short) -func TestMarshalPUSH_DATA5(t *testing.T) { +func TestMarshalPUSH_DATA6(t *testing.T) { time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} @@ -372,7 +374,7 @@ func TestMarshalPUSH_DATA5(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ @@ -385,7 +387,7 @@ func TestMarshalPUSH_DATA5(t *testing.T) { Lati: pointer.Float64(46.24), Dwnb: pointer.Uint(2), Txnb: pointer.Uint(2), - Time: &time1, Ackr: 100.0, + Time: &time1, }, }, } @@ -396,7 +398,7 @@ func TestMarshalPUSH_DATA5(t *testing.T) { } // Marshal with an invalid TokenId (too long) -func TestMarshalPUSH_DATA6(t *testing.T) { +func TestMarshalPUSH_DATA7(t *testing.T) { time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} @@ -405,7 +407,7 @@ func TestMarshalPUSH_DATA6(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14, 0x28}, - Identifier: PUSH_ACK, + Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid Payload: &Payload{ Stat: &Stat{ @@ -418,7 +420,7 @@ func TestMarshalPUSH_DATA6(t *testing.T) { Lati: pointer.Float64(46.24), Dwnb: pointer.Uint(2), Txnb: pointer.Uint(2), - Time: &time1, Ackr: 100.0, Ackr: 100.0, + Time: &time1, }, }, } @@ -437,19 +439,19 @@ func checkMarshalACK(packet *Packet) error { } if len(raw) != 4 { - return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) } if raw[0] != packet.Version { - return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) } if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) } if raw[3] != packet.Identifier { - return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) } return err @@ -556,7 +558,7 @@ func TestMarshalPUSH_ACK6(t *testing.T) { } // ---------- PULL_DATA -func checkMarshalPULL_ACK(packet *Packet) error { +func checkMarshalPULL_DATA(packet *Packet) error { raw, err := Marshal(packet) if err != nil { @@ -564,23 +566,23 @@ func checkMarshalPULL_ACK(packet *Packet) error { } if len(raw) != 12 { - return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) } if raw[0] != packet.Version { - return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) } if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) } if raw[3] != packet.Identifier { - return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) } if !bytes.Equal(raw[4:12], packet.GatewayId) { - return errors.New(fmt.Printf("Invalid raw gatewayId: % x", raw[4:12])) + return errors.New(fmt.Sprintf("Invalid raw gatewayId: % x", raw[4:12])) } return err @@ -591,7 +593,7 @@ func TestMarshalPULL_DATA1(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PULL_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } @@ -612,7 +614,7 @@ func TestMarshalPULL_DATA2(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PULL_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: payload, } @@ -622,11 +624,11 @@ func TestMarshalPULL_DATA2(t *testing.T) { } // Marshal a pull_data packet with an invalid token (too short) -func TestMarshalPULL_DATA1(t *testing.T) { +func TestMarshalPULL_DATA3(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA}, - Identifier: PUSH_ACK, + Identifier: PULL_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } @@ -636,11 +638,11 @@ func TestMarshalPULL_DATA1(t *testing.T) { } // Marshal a pull_data packet with an invalid token (too long) -func TestMarshalPULL_DATA1(t *testing.T) { +func TestMarshalPULL_DATA4(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14, 0x42}, - Identifier: PUSH_ACK, + Identifier: PULL_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } @@ -650,11 +652,11 @@ func TestMarshalPULL_DATA1(t *testing.T) { } // Marshal a pull_data packet with an invalid gatewayId (too short) -func TestMarshalPULL_DATA1(t *testing.T) { +func TestMarshalPULL_DATA5(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PULL_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } @@ -664,11 +666,11 @@ func TestMarshalPULL_DATA1(t *testing.T) { } // Marshal a pull_data packet with an invalid gatewayId (too long) -func TestMarshalPULL_DATA1(t *testing.T) { +func TestMarshalPULL_DATA6(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PULL_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } @@ -697,7 +699,7 @@ func TestMarshalPULL_ACK2(t *testing.T) { packet := &Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, + Identifier: PULL_ACK, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: nil, } @@ -787,23 +789,23 @@ func checkMarshalPULL_RESP(packet *Packet, payload []byte) error { } if len(raw) < 4 { - return errors.New(fmt.Printf("Invalid raw sequence length: %d", len(raw))) + return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) } if raw[0] != packet.Version { - return errors.New(fmt.Printf("Invalid raw version: %x", raw[0])) + return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) } if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Printf("Invalid raw token: %x", raw[1:3])) + return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) } if raw[3] != packet.Identifier { - return errors.New(fmt.Printf("Invalid raw identifier: %x", raw[3])) + return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) } if packet.Payload != nil && !bytes.Equal(raw[4:], payload) { - return errors.New(fmt.Printf("Invalid raw payload: % x", raw[4:])) + return errors.New(fmt.Sprintf("Invalid raw payload: % x", raw[4:])) } return err @@ -819,7 +821,7 @@ func TestMarshallPULL_RESP1(t *testing.T) { Payload: nil, } - if err = checkMarshalPUSH_DATA(packet, Make([]byte)); err != nil { + if err := checkMarshalPULL_RESP(packet, make([]byte, 0)); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -864,7 +866,7 @@ func TestMarshallPULL_RESP2(t *testing.T) { }, } - if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + if err = checkMarshalPULL_RESP(packet, payload); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -895,7 +897,7 @@ func TestMarshallPULL_RESP3(t *testing.T) { }, } - if err = checkMarshalPUSH_DATA(packet, payload); err != nil { + if err = checkMarshalPULL_RESP(packet, payload); err != nil { t.Errorf("Failed to marshal packet: %v", err) } } @@ -926,7 +928,7 @@ func TestMarshallPULL_RESP4(t *testing.T) { }, } - if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + if err = checkMarshalPULL_RESP(packet, payload); err == nil { t.Errorf("Successfully marshalled a packet with an invalid token") } } @@ -957,7 +959,7 @@ func TestMarshallPULL_RESP5(t *testing.T) { }, } - if err = checkMarshalPUSH_DATA(packet, payload); err == nil { + if err = checkMarshalPULL_RESP(packet, payload); err == nil { t.Errorf("Successfully marshalled a packet with an invalid token") } } From 234aeacde0ee685cf0f145b8c952876ae9c1d4e8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 17:12:19 +0100 Subject: [PATCH 0047/2266] Update encode to run tests correctly --- lorawan/gateway/encode.go | 192 +++++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 3 deletions(-) diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go index 3a0d135a2..5c1cca54f 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/gateway/encode.go @@ -3,7 +3,193 @@ package gateway -// Marshal transform a packet to a sequence of bytes. -func Marshal(packet Packet) ([]byte, error) { - return []byte{}, nil +import ( + "encoding/json" + "errors" + "time" +) + +// Marshal transforms a packet to a sequence of bytes. +func Marshal(packet *Packet) ([]byte, error) { + raw := append(make([]byte, 0), packet.Version) + + if len(packet.Token) != 2 { + return nil, errors.New("Invalid packet token") + } + + raw = append(raw, packet.Token...) + raw = append(raw, packet.Identifier) + + if packet.Identifier == PUSH_DATA || packet.Identifier == PULL_DATA { + if len(packet.GatewayId) != 8 { + return nil, errors.New("Invalid packet gatewayId") + } + raw = append(raw, packet.GatewayId...) + } + + if packet.Payload != nil && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { + payload, err := json.Marshal(packet.Payload) + if err != nil { + return nil, err + } + raw = append(raw, payload...) + } + + return raw, nil +} + +type timemarshaler struct { + layout string + value time.Time +} + +func (t *timemarshaler) MarshalJSON() ([]byte, error) { + return append(append([]byte(`"`), []byte(t.value.Format(t.layout))...), []byte(`"`)...), nil +} + +type datrmarshaler struct { + kind string + value string +} + +func (d *datrmarshaler) MarshalJSON() ([]byte, error) { + if d.kind == "uint" { + return []byte(d.value), nil + } + return append(append([]byte(`"`), []byte(d.value)...), []byte(`"`)...), nil +} + +func (r *RXPK) MarshalJSON() ([]byte, error) { + var rfctime *timemarshaler = nil + var datr *datrmarshaler = nil + + if r.Time != nil { + rfctime = &timemarshaler{time.RFC3339Nano, *r.Time} + } + + if r.Modu != nil && r.Datr != nil { + switch *r.Modu { + case "FSK": + datr = &datrmarshaler{"uint", *r.Datr} + case "LORA": + fallthrough + default: + datr = &datrmarshaler{"string", *r.Datr} + } + } + + return json.Marshal(struct { + Chan *uint `json:"chan,omitempty"` + Codr *string `json:"codr,omitempty"` + Data *string `json:"data,omitempty"` + Datr *datrmarshaler `json:"datr,omitempty"` + Freq *float64 `json:"freq,omitempty"` + Lsnr *float64 `json:"lsnr,omitempty"` + Modu *string `json:"modu,omitempty"` + Rfch *uint `json:"rfch,omitempty"` + Rssi *int `json:"rssi,omitempty"` + Size *uint `json:"size,omitempty"` + Stat *int `json:"stat,omitempty"` + Time *timemarshaler `json:"time,omitempty"` + Tmst *uint `json:"tmst,omitempty"` + }{ + r.Chan, + r.Codr, + r.Data, + datr, + r.Freq, + r.Lsnr, + r.Modu, + r.Rfch, + r.Rssi, + r.Size, + r.Stat, + rfctime, + r.Tmst, + }) +} + +func (s *Stat) MarshalJSON() ([]byte, error) { + var rfctime *timemarshaler = nil + if s.Time != nil { + rfctime = &timemarshaler{"2006-01-02 15:04:05 GMT", *s.Time} + } + + return json.Marshal(struct { + Ackr *float64 `json:"ackr,omitempty"` + Alti *int `json:"alti,omitempty"` + Dwnb *uint `json:"dwnb,omitempty"` + Lati *float64 `json:"lati,omitempty"` + Long *float64 `json:"long,omitempty"` + Rxfw *uint `json:"rxfw,omitempty"` + Rxnb *uint `json:"rxnb,omitempty"` + Rxok *uint `json:"rxok,omitempty"` + Time *timemarshaler `json:"time,omitempty"` + Txnb *uint `json:"txnb,omitempty"` + }{ + s.Ackr, + s.Alti, + s.Dwnb, + s.Lati, + s.Long, + s.Rxfw, + s.Rxnb, + s.Rxok, + rfctime, + s.Txnb, + }) +} + +func (t *TXPK) MarshalJSON() ([]byte, error) { + var rfctime *timemarshaler = nil + var datr *datrmarshaler = nil + + if t.Time != nil { + rfctime = &timemarshaler{time.RFC3339Nano, *t.Time} + } + + if t.Modu != nil && t.Datr != nil { + switch *t.Modu { + case "FSK": + datr = &datrmarshaler{"uint", *t.Datr} + case "LORA": + fallthrough + default: + datr = &datrmarshaler{"string", *t.Datr} + } + } + + return json.Marshal(struct { + Codr *string `json:"codr,omitempty"` + Data *string `json:"data,omitempty"` + Datr *datrmarshaler `json:"datr,omitempty"` + Fdev *uint `json:"fdev,omitempty"` + Freq *float64 `json:"freq,omitempty"` + Imme *bool `json:"imme,omitempty"` + Ipol *bool `json:"ipol,omitempty"` + Modu *string `json:"modu,omitempty"` + Ncrc *bool `json:"ncrc,omitempty"` + Powe *uint `json:"powe,omitempty"` + Prea *uint `json:"prea,omitempty"` + Rfch *uint `json:"rfch,omitempty"` + Size *uint `json:"size,omitempty"` + Time *timemarshaler `json:"time,omitempty"` + Tmst *uint `json:"tmst,omitempty"` + }{ + t.Codr, + t.Data, + datr, + t.Fdev, + t.Freq, + t.Imme, + t.Ipol, + t.Modu, + t.Ncrc, + t.Powe, + t.Prea, + t.Rfch, + t.Size, + rfctime, + t.Tmst, + }) } From 01738b73e900d7c024e2e3fae8218c0b46fbbc17 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 17:16:19 +0100 Subject: [PATCH 0048/2266] Update documentation of lorawan/gateway --- lorawan/gateway/decode.go | 6 +++--- lorawan/gateway/encode.go | 8 ++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index 735451720..36f78471e 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -10,7 +10,7 @@ import ( "time" ) -// Unmarshal parse a raw response from a server and turn in into a packet. +// Unmarshal parses a raw response from a server and turn in into a packet. // Will return an error if the response fields are incorrect. func Unmarshal(raw []byte) (*Packet, error) { size := len(raw) @@ -58,7 +58,7 @@ type timeParser struct { Value *time.Time // The parsed time value } -// implement the Unmarshaller interface from encoding/json +// UnmarshalJSON implements the Unmarshaler interface from encoding/json func (t *timeParser) UnmarshalJSON(raw []byte) error { str := strings.Trim(string(raw), `"`) v, err := time.Parse("2006-01-02 15:04:05 GMT", str) @@ -82,7 +82,7 @@ type datrParser struct { Value *string // The parsed value } -// implement the Unmarshaller interface from encoding/json +// UnmarshalJSON implements the Unmarshaler interface from encoding/json func (d *datrParser) UnmarshalJSON(raw []byte) error { v := strings.Trim(string(raw), `"`) diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go index 5c1cca54f..b3b8c8133 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/gateway/encode.go @@ -38,20 +38,25 @@ func Marshal(packet *Packet) ([]byte, error) { return raw, nil } +// timeMarshaler is used as a proxy to marshal times. +// By default, time.Time is marshalling to RFC3339 format, but we need differents format. type timemarshaler struct { layout string value time.Time } +// MarshalJSON implements the Marshaler interface from encoding/json func (t *timemarshaler) MarshalJSON() ([]byte, error) { return append(append([]byte(`"`), []byte(t.value.Format(t.layout))...), []byte(`"`)...), nil } +// datrmarshaler is used as a proxy to marshal datr field which could be either number or string type datrmarshaler struct { kind string value string } +// MarshalJSON implements the Marshaler interface from encoding/json func (d *datrmarshaler) MarshalJSON() ([]byte, error) { if d.kind == "uint" { return []byte(d.value), nil @@ -59,6 +64,7 @@ func (d *datrmarshaler) MarshalJSON() ([]byte, error) { return append(append([]byte(`"`), []byte(d.value)...), []byte(`"`)...), nil } +// MarshalJSON implements the Marshaler interface from encoding/json func (r *RXPK) MarshalJSON() ([]byte, error) { var rfctime *timemarshaler = nil var datr *datrmarshaler = nil @@ -109,6 +115,7 @@ func (r *RXPK) MarshalJSON() ([]byte, error) { }) } +// MarshalJSON implements the Marshaler interface from encoding/json func (s *Stat) MarshalJSON() ([]byte, error) { var rfctime *timemarshaler = nil if s.Time != nil { @@ -140,6 +147,7 @@ func (s *Stat) MarshalJSON() ([]byte, error) { }) } +// MarshalJSON implements the Marshaler interface from encoding/json func (t *TXPK) MarshalJSON() ([]byte, error) { var rfctime *timemarshaler = nil var datr *datrmarshaler = nil From 62afc334bc2e138fa0633ed991e27aa08afb884e Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 17:16:41 +0100 Subject: [PATCH 0049/2266] Fix doc typo in utils/pointer --- utils/pointer/pointer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 0035e599c..de36dcfae 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -45,7 +45,7 @@ func Bool(v bool) *bool { return p } -// DumpStruct print the content of a struct of pointers +// DumpStruct prints the content of a struct of pointers func DumpPStruct(s interface{}) { v := reflect.ValueOf(s) From fd49c1124d390d7907b1dd00d0fa28b1a7dc759a Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 17:18:40 +0100 Subject: [PATCH 0050/2266] Update dev plan --- DEVELOPMENT_PLAN.md | 66 ++------------------------------------------- 1 file changed, 2 insertions(+), 64 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 3ad2821e9..7ede28445 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -9,78 +9,16 @@ mainly for testing and ensuring the correctness of other components. - [x] Types, packages and data structures in use - [x] Emit udp packets towards a server - [ ] Handle reception acknowledgement from server - - [ ] Generate and serialize json rxpk object(s) - - [ ] Generate and serialize json stat object(s) + - [x] serialize json rxpk/stat object(s) + - [ ] generate json rxpl/stat object(s) - [ ] Simulate fake end-devices activity ```go -type gateway struct { - id []string - routers []string - faking boolean - started boolean - status Stat -} - -type RXPK struct { - Chan uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr string `json:"codr"` // LoRa ECC coding rate identifier - Data string `json:"data"` // Base64 encoded RF packet payload, padded - Datr string `json:"datr"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Rfch uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) - Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Stat int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time time.Time `json:"time"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) -} - -type TXPK struct { - Codr string `json:"codr"` // LoRa ECC coding rate identifier - Data string `json:"data"` // Base64 encoded RF packet payload, padding optional - Datr string `json:"datr"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) - Freq float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme bool `json:"imme"` // Send packet immediately (will ignore tmst & time) - Ipol bool `json:"ipol"` // Lora modulation polarization inversion - Modu string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Ncrc bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) - Powe uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) - Prea uint `json:"prea"` // RF preamble size (unsigned integer) - Rfch uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) - Size uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Time time.Time `json:"time"` // Send packet at a certain time (GPS synchronization required) - Tmst uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) -} - -type Stat struct { - Ackr float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged - Alti int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) - Dwnb uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) - Lati float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) - Long float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) - Rxnb uint `json:"rxnb"` // Number of radio packets received (unsigned integer) - Rxok uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC - Time time.Time `json:"time"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb uint `json:"txnb"` // Number of packets emitted (unsigned integer) -} - type Option struct { key string value {}interface } -type Packet struct { - version byte - token [2]byte - identifier byte - payload []byte -} - type Gateway interface { Start () error EmitData (data []byte, options ...Option) error From 23a60eac5f3cf9755d5e91aebcc337801fcc4b67 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 18:55:25 +0100 Subject: [PATCH 0051/2266] Implement Unmarshaler interface for Payload's components instead of a custom one --- lorawan/gateway/decode.go | 159 +++++++++++++++++++++++++-------- lorawan/gateway/decode_test.go | 8 +- lorawan/gateway/encode_test.go | 4 +- lorawan/gateway/gateway.go | 78 ++++++++-------- 4 files changed, 167 insertions(+), 82 deletions(-) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index 36f78471e..9150c048f 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -46,7 +46,9 @@ func Unmarshal(raw []byte) (*Packet, error) { var err error if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { - packet.Payload, err = unmarshalPayload(raw[cursor:]) + packet.Payload = new(Payload) + packet.Payload.Raw = raw[cursor:] + err = json.Unmarshal(raw[cursor:], packet.Payload) } return packet, err @@ -54,12 +56,12 @@ func Unmarshal(raw []byte) (*Packet, error) { // timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time // module parse RFC3339 by default -type timeParser struct { +type timeparser struct { Value *time.Time // The parsed time value } // UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (t *timeParser) UnmarshalJSON(raw []byte) error { +func (t *timeparser) UnmarshalJSON(raw []byte) error { str := strings.Trim(string(raw), `"`) v, err := time.Parse("2006-01-02 15:04:05 GMT", str) if err != nil { @@ -78,12 +80,12 @@ func (t *timeParser) UnmarshalJSON(raw []byte) error { // datrParser is used as a proxy to Unmarshal datr field in json payloads. // Depending on the modulation type, the datr type could be either a string or a number. // We're gonna parse it as a string in any case. -type datrParser struct { +type datrparser struct { Value *string // The parsed value } // UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (d *datrParser) UnmarshalJSON(raw []byte) error { +func (d *datrparser) UnmarshalJSON(raw []byte) error { v := strings.Trim(string(raw), `"`) if v == "" { @@ -94,45 +96,128 @@ func (d *datrParser) UnmarshalJSON(raw []byte) error { return nil } -// unmarshalPayload is an until used by Unmarshal to parse a Payload from a sequence of bytes. -func unmarshalPayload(raw []byte) (*Payload, error) { - payload := &Payload{raw, nil, nil, nil} - customStruct := &struct { - Stat *struct { - Time timeParser `json:"time"` - } `json:"stat"` - RXPK *[]struct { - Time timeParser `json:"time"` - Datr datrParser `json:"datr"` - } `json:"rxpk"` - TXPK *struct { - Time timeParser `json:"time"` - Datr datrParser `json:"datr"` - } `json:"txpk"` - }{} - - err := json.Unmarshal(raw, payload) - err = json.Unmarshal(raw, customStruct) +// UnmarshalJSON implements the Unmarshaler interface from encoding/json +func (r *RXPK) UnmarshalJSON(raw []byte) error { + proxy := new(struct { + Chan *uint `json:"chan"` + Codr *string `json:"codr"` + Data *string `json:"data"` + Datr *datrparser `json:"datr"` + Freq *float64 `json:"freq"` + Lsnr *float64 `json:"lsnr"` + Modu *string `json:"modu"` + Rfch *uint `json:"rfch"` + Rssi *int `json:"rssi"` + Size *uint `json:"size"` + Stat *int `json:"stat"` + Time *timeparser `json:"time"` + Tmst *uint `json:"tmst"` + }) + if err := json.Unmarshal(raw, proxy); err != nil { + return err + } - if err != nil { - return nil, err + r.Chan = proxy.Chan + r.Codr = proxy.Codr + r.Data = proxy.Data + r.Freq = proxy.Freq + r.Lsnr = proxy.Lsnr + r.Modu = proxy.Modu + r.Rfch = proxy.Rfch + r.Rssi = proxy.Rssi + r.Size = proxy.Size + r.Stat = proxy.Stat + r.Tmst = proxy.Tmst + + if proxy.Datr != nil { + r.Datr = proxy.Datr.Value } - if customStruct.Stat != nil { - payload.Stat.Time = customStruct.Stat.Time.Value + if proxy.Time != nil { + r.Time = proxy.Time.Value } + return nil +} - if customStruct.RXPK != nil { - for i, x := range *customStruct.RXPK { - (*payload.RXPK)[i].Time = x.Time.Value - (*payload.RXPK)[i].Datr = x.Datr.Value - } +// UnmarshalJSON implements the Unmarshaler interface from encoding/json +func (s *Stat) UnmarshalJSON(raw []byte) error { + proxy := new(struct { + Ackr *float64 `json:"ackr"` + Alti *int `json:"alti"` + Dwnb *uint `json:"dwnb"` + Lati *float64 `json:"lati"` + Long *float64 `json:"long"` + Rxfw *uint `json:"rxfw"` + Rxnb *uint `json:"rxnb"` + Rxok *uint `json:"rxok"` + Time *timeparser `json:"time"` + Txnb *uint `json:"txnb"` + }) + + if err := json.Unmarshal(raw, proxy); err != nil { + return err + } + + s.Ackr = proxy.Ackr + s.Alti = proxy.Alti + s.Dwnb = proxy.Dwnb + s.Lati = proxy.Lati + s.Long = proxy.Long + s.Rxfw = proxy.Rxfw + s.Rxnb = proxy.Rxnb + s.Rxok = proxy.Rxok + s.Txnb = proxy.Txnb + + if proxy.Time != nil { + s.Time = proxy.Time.Value + } + + return nil +} + +func (t *TXPK) UnmarshalJSON(raw []byte) error { + proxy := new(struct { + Codr *string `json:"codr"` + Data *string `json:"data"` + Datr *datrparser `json:"datr"` + Fdev *uint `json:"fdev"` + Freq *float64 `json:"freq"` + Imme *bool `json:"imme"` + Ipol *bool `json:"ipol"` + Modu *string `json:"modu"` + Ncrc *bool `json:"ncrc"` + Powe *uint `json:"powe"` + Prea *uint `json:"prea"` + Rfch *uint `json:"rfch"` + Size *uint `json:"size"` + Time *timeparser `json:"time"` + Tmst *uint `json:"tmst"` + }) + + if err := json.Unmarshal(raw, proxy); err != nil { + return err } - if customStruct.TXPK != nil { - payload.TXPK.Time = customStruct.TXPK.Time.Value - payload.TXPK.Datr = customStruct.TXPK.Datr.Value + t.Codr = proxy.Codr + t.Data = proxy.Data + t.Fdev = proxy.Fdev + t.Freq = proxy.Freq + t.Imme = proxy.Imme + t.Ipol = proxy.Ipol + t.Modu = proxy.Modu + t.Ncrc = proxy.Ncrc + t.Powe = proxy.Powe + t.Prea = proxy.Prea + t.Rfch = proxy.Rfch + t.Size = proxy.Size + t.Tmst = proxy.Tmst + + if proxy.Datr != nil { + t.Datr = proxy.Datr.Value } - return payload, nil + if proxy.Time != nil { + t.Time = proxy.Time.Value + } + return nil } diff --git a/lorawan/gateway/decode_test.go b/lorawan/gateway/decode_test.go index 0493dee12..63b5738ef 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/gateway/decode_test.go @@ -200,7 +200,7 @@ func TestUnmarshal3(t *testing.T) { t.Errorf("Invalid parsed payload: % x", packet.Payload) } - if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { + if packet.Payload.RXPK == nil || len(packet.Payload.RXPK) != 3 { t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) } @@ -220,7 +220,7 @@ func TestUnmarshal3(t *testing.T) { Tmst: pointer.Uint(3512348514), } - if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { + if !reflect.DeepEqual(rxpk, (packet.Payload.RXPK)[1]) { t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) } } @@ -314,7 +314,7 @@ func TestUnmarshal4(t *testing.T) { t.Errorf("Invalid parsed payload: % x", packet.Payload) } - if packet.Payload.RXPK == nil || len(*packet.Payload.RXPK) != 3 { + if packet.Payload.RXPK == nil || len(packet.Payload.RXPK) != 3 { t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) } @@ -338,7 +338,7 @@ func TestUnmarshal4(t *testing.T) { Tmst: pointer.Uint(3512348514), } - if !reflect.DeepEqual(rxpk, (*packet.Payload.RXPK)[1]) { + if !reflect.DeepEqual(rxpk, (packet.Payload.RXPK)[1]) { t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) } diff --git a/lorawan/gateway/encode_test.go b/lorawan/gateway/encode_test.go index cd11b4d3e..eabaee85b 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/gateway/encode_test.go @@ -147,7 +147,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { Identifier: PUSH_DATA, GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, Payload: &Payload{ - RXPK: &[]RXPK{ + RXPK: []RXPK{ RXPK{ Chan: pointer.Uint(2), Codr: pointer.String("4/6"), @@ -260,7 +260,7 @@ func TestMarshalPUSH_DATA3(t *testing.T) { Txnb: pointer.Uint(2), Time: &time1, }, - RXPK: &[]RXPK{ + RXPK: []RXPK{ RXPK{ Time: &time2, Tmst: pointer.Uint(3512348611), diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index 47d575e3e..bdb57d304 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -12,53 +12,53 @@ import ( // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan *uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr"` // LoRa ECC coding rate identifier - Data *string `json:"data"` // Base64 encoded RF packet payload, padded - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string // LoRa ECC coding rate identifier + Data *string // Base64 encoded RF packet payload, padded + Datr *string // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string // Modulation identifier "LORA" or "FSK" + Rfch *uint // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int // RSSI in dBm (signed integer, 1 dB precision) + Size *uint // RF packet payload size in bytes (unsigned integer) + Stat *int // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint // Internal timestamp of "RX finished" event (32b unsigned) } // TXPK represents a downlink json message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr"` // LoRa ECC coding rate identifier - Data *string `json:"data"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol"` // Lora modulation polarization inversion - Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) + Codr *string // LoRa ECC coding rate identifier + Data *string // Base64 encoded RF packet payload, padding optional + Datr *string // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool // Send packet immediately (will ignore tmst & time) + Ipol *bool // Lora modulation polarization inversion + Modu *string // Modulation identifier "LORA" or "FSK" + Ncrc *bool // If true, disable the CRC of the physical layer (optional) + Powe *uint // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint // RF preamble size (unsigned integer) + Rfch *uint // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint // RF packet payload size in bytes (unsigned integer) + Time *time.Time // Send packet at a certain time (GPS synchronization required) + Tmst *uint // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway type Stat struct { - Ackr *float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged - Alti *int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) - Lati *float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint `json:"rxnb"` // Number of radio packets received (unsigned integer) - Rxok *uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC - Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint `json:"txnb"` // Number of packets emitted (unsigned integer) + Ackr *float64 // Percentage of upstream datagrams that were acknowledged + Alti *int // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint // Number of downlink datagrams received (unsigned integer) + Lati *float64 // GPS latitude of the gateway in degree (float, N is +) + Long *float64 // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint // Number of radio packets forwarded (unsigned integer) + Rxnb *uint // Number of radio packets received (unsigned integer) + Rxok *uint // Number of radio packets received with a valid PHY CRC + Time *time.Time // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint // Number of packets emitted (unsigned integer) } // Packet as seen by the gateway. @@ -73,7 +73,7 @@ type Packet struct { // Payload refers to the JSON payload sent by a gateway or a server. type Payload struct { Raw []byte `json:"-"` // The raw unparsed response - RXPK *[]RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any + RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any } From 3d687b7c8ae1174dc068b2ccae0ff53a8cee2876 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 20:08:03 +0100 Subject: [PATCH 0052/2266] Use of proxy and struct inheritance to unmarshal json --- lorawan/gateway/decode.go | 178 ++++++++++++------------------------- lorawan/gateway/gateway.go | 76 ++++++++-------- 2 files changed, 93 insertions(+), 161 deletions(-) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index 9150c048f..fb3410844 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -97,127 +97,59 @@ func (d *datrparser) UnmarshalJSON(raw []byte) error { } // UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (r *RXPK) UnmarshalJSON(raw []byte) error { - proxy := new(struct { - Chan *uint `json:"chan"` - Codr *string `json:"codr"` - Data *string `json:"data"` - Datr *datrparser `json:"datr"` - Freq *float64 `json:"freq"` - Lsnr *float64 `json:"lsnr"` - Modu *string `json:"modu"` - Rfch *uint `json:"rfch"` - Rssi *int `json:"rssi"` - Size *uint `json:"size"` - Stat *int `json:"stat"` - Time *timeparser `json:"time"` - Tmst *uint `json:"tmst"` - }) - if err := json.Unmarshal(raw, proxy); err != nil { - return err - } - - r.Chan = proxy.Chan - r.Codr = proxy.Codr - r.Data = proxy.Data - r.Freq = proxy.Freq - r.Lsnr = proxy.Lsnr - r.Modu = proxy.Modu - r.Rfch = proxy.Rfch - r.Rssi = proxy.Rssi - r.Size = proxy.Size - r.Stat = proxy.Stat - r.Tmst = proxy.Tmst - - if proxy.Datr != nil { - r.Datr = proxy.Datr.Value - } - - if proxy.Time != nil { - r.Time = proxy.Time.Value - } - return nil -} - -// UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (s *Stat) UnmarshalJSON(raw []byte) error { - proxy := new(struct { - Ackr *float64 `json:"ackr"` - Alti *int `json:"alti"` - Dwnb *uint `json:"dwnb"` - Lati *float64 `json:"lati"` - Long *float64 `json:"long"` - Rxfw *uint `json:"rxfw"` - Rxnb *uint `json:"rxnb"` - Rxok *uint `json:"rxok"` - Time *timeparser `json:"time"` - Txnb *uint `json:"txnb"` - }) - - if err := json.Unmarshal(raw, proxy); err != nil { - return err - } - - s.Ackr = proxy.Ackr - s.Alti = proxy.Alti - s.Dwnb = proxy.Dwnb - s.Lati = proxy.Lati - s.Long = proxy.Long - s.Rxfw = proxy.Rxfw - s.Rxnb = proxy.Rxnb - s.Rxok = proxy.Rxok - s.Txnb = proxy.Txnb - - if proxy.Time != nil { - s.Time = proxy.Time.Value - } - - return nil -} - -func (t *TXPK) UnmarshalJSON(raw []byte) error { - proxy := new(struct { - Codr *string `json:"codr"` - Data *string `json:"data"` - Datr *datrparser `json:"datr"` - Fdev *uint `json:"fdev"` - Freq *float64 `json:"freq"` - Imme *bool `json:"imme"` - Ipol *bool `json:"ipol"` - Modu *string `json:"modu"` - Ncrc *bool `json:"ncrc"` - Powe *uint `json:"powe"` - Prea *uint `json:"prea"` - Rfch *uint `json:"rfch"` - Size *uint `json:"size"` - Time *timeparser `json:"time"` - Tmst *uint `json:"tmst"` - }) - - if err := json.Unmarshal(raw, proxy); err != nil { - return err - } - - t.Codr = proxy.Codr - t.Data = proxy.Data - t.Fdev = proxy.Fdev - t.Freq = proxy.Freq - t.Imme = proxy.Imme - t.Ipol = proxy.Ipol - t.Modu = proxy.Modu - t.Ncrc = proxy.Ncrc - t.Powe = proxy.Powe - t.Prea = proxy.Prea - t.Rfch = proxy.Rfch - t.Size = proxy.Size - t.Tmst = proxy.Tmst - - if proxy.Datr != nil { - t.Datr = proxy.Datr.Value - } - - if proxy.Time != nil { - t.Time = proxy.Time.Value - } - return nil +func (p *Payload) UnmarshalJSON(raw []byte) error { + proxy := new(struct { + Stat struct{ + *Stat + Time *timeparser `json:"time"` + } + RXPK []struct{ + *RXPK + Time *timeparser `json:"time"` + Datr *datrparser `json:"datr"` + } + TXPK struct{ + *TXPK + Time *timeparser `json:"time"` + Datr *datrparser `json:"datr"` + } + }) + + stat := new(Stat) + txpk := new(TXPK) + proxy.Stat.Stat = stat + proxy.TXPK.TXPK = txpk + + if err := json.Unmarshal(raw, proxy); err != nil { + return err + } + + if proxy.Stat.Stat != nil { + if proxy.Stat.Time != nil { + stat.Time = proxy.Stat.Time.Value + } + p.Stat = stat + } + + if proxy.TXPK.TXPK != nil { + if proxy.TXPK.Time != nil { + txpk.Time = proxy.TXPK.Time.Value + } + if proxy.TXPK.Datr != nil { + txpk.Datr = proxy.TXPK.Datr.Value + } + p.TXPK = txpk + } + + for _, rxpk := range(proxy.RXPK) { + if rxpk.Time != nil { + rxpk.RXPK.Time = rxpk.Time.Value + } + if rxpk.Datr != nil { + rxpk.RXPK.Datr = rxpk.Datr.Value + } + p.RXPK = append(p.RXPK, *rxpk.RXPK) + } + + return nil } diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index bdb57d304..760a158de 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -12,53 +12,53 @@ import ( // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan *uint // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string // LoRa ECC coding rate identifier - Data *string // Base64 encoded RF packet payload, padded - Datr *string // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string // Modulation identifier "LORA" or "FSK" - Rfch *uint // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int // RSSI in dBm (signed integer, 1 dB precision) - Size *uint // RF packet payload size in bytes (unsigned integer) - Stat *int // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr"` // LoRa ECC coding rate identifier + Data *string `json:"data"` // Base64 encoded RF packet payload, padded + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) } // TXPK represents a downlink json message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string // LoRa ECC coding rate identifier - Data *string // Base64 encoded RF packet payload, padding optional - Datr *string // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool // Send packet immediately (will ignore tmst & time) - Ipol *bool // Lora modulation polarization inversion - Modu *string // Modulation identifier "LORA" or "FSK" - Ncrc *bool // If true, disable the CRC of the physical layer (optional) - Powe *uint // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint // RF preamble size (unsigned integer) - Rfch *uint // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint // RF packet payload size in bytes (unsigned integer) - Time *time.Time // Send packet at a certain time (GPS synchronization required) - Tmst *uint // Send packet on a certain timestamp value (will ignore time) + Codr *string `json:"codr"` // LoRa ECC coding rate identifier + Data *string `json:"data"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol"` // Lora modulation polarization inversion + Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway type Stat struct { - Ackr *float64 // Percentage of upstream datagrams that were acknowledged - Alti *int // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint // Number of downlink datagrams received (unsigned integer) - Lati *float64 // GPS latitude of the gateway in degree (float, N is +) - Long *float64 // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint // Number of radio packets forwarded (unsigned integer) - Rxnb *uint // Number of radio packets received (unsigned integer) - Rxok *uint // Number of radio packets received with a valid PHY CRC - Time *time.Time // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint // Number of packets emitted (unsigned integer) + Ackr *float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged + Alti *int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) + Lati *float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint `json:"rxnb"` // Number of radio packets received (unsigned integer) + Rxok *uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC + Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint `json:"txnb"` // Number of packets emitted (unsigned integer) } // Packet as seen by the gateway. From 34300ff95340ee8a88b26667dab99eab3b490fe6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 23:45:14 +0100 Subject: [PATCH 0053/2266] Setup proxies to avoir duplication and non-evolutive code in encode/decode --- lorawan/gateway/encode_proxies.go | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 lorawan/gateway/encode_proxies.go diff --git a/lorawan/gateway/encode_proxies.go b/lorawan/gateway/encode_proxies.go new file mode 100644 index 000000000..c60e51b02 --- /dev/null +++ b/lorawan/gateway/encode_proxies.go @@ -0,0 +1,45 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import "time" + +// Proxies handle annoying json fields like datr and time +type payloadProxy struct { + ProxRXPK []rxpkProxy `json:"rxpk,omitempty"` + ProxStat *statProxy `json:"stat,omitempty"` + ProxTXPK *txpkProxy `json:"txpk,omitempty"` +} + +type statProxy struct { + *Stat + Time *timeparser `json:"time,omitempty"` +} + +type rxpkProxy struct { + *RXPK + Datr *datrparser `json:"datr,omitempty"` + Time *timeparser `json:"time,omitempty"` +} + +type txpkProxy struct { + *TXPK + Datr *datrparser `json:"datr,omitempty"` + Time *timeparser `json:"time,omitempty"` +} + +// datrParser is used as a proxy to Unmarshal datr field in json payloads. +// Depending on the modulation type, the datr type could be either a string or a number. +// We're gonna parse it as a string in any case. +type datrparser struct { + kind string + value *string // The parsed value +} + +// timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time +// module parse RFC3339 by default +type timeparser struct { + layout string + value *time.Time // The parsed time value +} From 49fcd21349a1b6d67679cd2156f970fa335e6a96 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 23:45:37 +0100 Subject: [PATCH 0054/2266] Use of proxy and struct inheritance in decode/encode --- lorawan/gateway/decode.go | 74 ++++--------- lorawan/gateway/encode.go | 226 +++++++++++++------------------------- 2 files changed, 98 insertions(+), 202 deletions(-) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index fb3410844..b6a6f538b 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -54,12 +54,6 @@ func Unmarshal(raw []byte) (*Packet, error) { return packet, err } -// timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time -// module parse RFC3339 by default -type timeparser struct { - Value *time.Time // The parsed time value -} - // UnmarshalJSON implements the Unmarshaler interface from encoding/json func (t *timeparser) UnmarshalJSON(raw []byte) error { str := strings.Trim(string(raw), `"`) @@ -73,17 +67,10 @@ func (t *timeparser) UnmarshalJSON(raw []byte) error { if err != nil { return errors.New("Unkown date format. Unable to parse time") } - t.Value = &v + t.value = &v return nil } -// datrParser is used as a proxy to Unmarshal datr field in json payloads. -// Depending on the modulation type, the datr type could be either a string or a number. -// We're gonna parse it as a string in any case. -type datrparser struct { - Value *string // The parsed value -} - // UnmarshalJSON implements the Unmarshaler interface from encoding/json func (d *datrparser) UnmarshalJSON(raw []byte) error { v := strings.Trim(string(raw), `"`) @@ -92,61 +79,48 @@ func (d *datrparser) UnmarshalJSON(raw []byte) error { return errors.New("Invalid datr format") } - d.Value = &v + d.value = &v return nil } // UnmarshalJSON implements the Unmarshaler interface from encoding/json func (p *Payload) UnmarshalJSON(raw []byte) error { - proxy := new(struct { - Stat struct{ - *Stat - Time *timeparser `json:"time"` - } - RXPK []struct{ - *RXPK - Time *timeparser `json:"time"` - Datr *datrparser `json:"datr"` - } - TXPK struct{ - *TXPK - Time *timeparser `json:"time"` - Datr *datrparser `json:"datr"` - } - }) - - stat := new(Stat) - txpk := new(TXPK) - proxy.Stat.Stat = stat - proxy.TXPK.TXPK = txpk + proxy := payloadProxy{ + ProxStat: &statProxy{ + Stat: new(Stat), + }, + ProxTXPK: &txpkProxy{ + TXPK: new(TXPK), + }, + } - if err := json.Unmarshal(raw, proxy); err != nil { + if err := json.Unmarshal(raw, &proxy); err != nil { return err } - if proxy.Stat.Stat != nil { - if proxy.Stat.Time != nil { - stat.Time = proxy.Stat.Time.Value + if proxy.ProxStat.Stat != nil { + if proxy.ProxStat.Time != nil { + proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.value } - p.Stat = stat + p.Stat = proxy.ProxStat.Stat } - if proxy.TXPK.TXPK != nil { - if proxy.TXPK.Time != nil { - txpk.Time = proxy.TXPK.Time.Value + if proxy.ProxTXPK.TXPK != nil { + if proxy.ProxTXPK.Time != nil { + proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.value } - if proxy.TXPK.Datr != nil { - txpk.Datr = proxy.TXPK.Datr.Value + if proxy.ProxTXPK.Datr != nil { + proxy.ProxTXPK.TXPK.Datr = proxy.ProxTXPK.Datr.value } - p.TXPK = txpk + p.TXPK = proxy.ProxTXPK.TXPK } - for _, rxpk := range(proxy.RXPK) { + for _, rxpk := range(proxy.ProxRXPK) { if rxpk.Time != nil { - rxpk.RXPK.Time = rxpk.Time.Value + rxpk.RXPK.Time = rxpk.Time.value } if rxpk.Datr != nil { - rxpk.RXPK.Datr = rxpk.Datr.Value + rxpk.RXPK.Datr = rxpk.Datr.value } p.RXPK = append(p.RXPK, *rxpk.RXPK) } diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go index b3b8c8133..c494f8571 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/gateway/encode.go @@ -38,166 +38,88 @@ func Marshal(packet *Packet) ([]byte, error) { return raw, nil } -// timeMarshaler is used as a proxy to marshal times. -// By default, time.Time is marshalling to RFC3339 format, but we need differents format. -type timemarshaler struct { - layout string - value time.Time -} - // MarshalJSON implements the Marshaler interface from encoding/json -func (t *timemarshaler) MarshalJSON() ([]byte, error) { +func (t *timeparser) MarshalJSON() ([]byte, error) { + if t.value == nil { + return nil, errors.New("Cannot marshal a null time") + } return append(append([]byte(`"`), []byte(t.value.Format(t.layout))...), []byte(`"`)...), nil } -// datrmarshaler is used as a proxy to marshal datr field which could be either number or string -type datrmarshaler struct { - kind string - value string -} - // MarshalJSON implements the Marshaler interface from encoding/json -func (d *datrmarshaler) MarshalJSON() ([]byte, error) { - if d.kind == "uint" { - return []byte(d.value), nil - } - return append(append([]byte(`"`), []byte(d.value)...), []byte(`"`)...), nil -} +func (d *datrparser) MarshalJSON() ([]byte, error) { + if d.value == nil { + return nil, errors.New("Cannot marshal a null datr") + } -// MarshalJSON implements the Marshaler interface from encoding/json -func (r *RXPK) MarshalJSON() ([]byte, error) { - var rfctime *timemarshaler = nil - var datr *datrmarshaler = nil - - if r.Time != nil { - rfctime = &timemarshaler{time.RFC3339Nano, *r.Time} - } - - if r.Modu != nil && r.Datr != nil { - switch *r.Modu { - case "FSK": - datr = &datrmarshaler{"uint", *r.Datr} - case "LORA": - fallthrough - default: - datr = &datrmarshaler{"string", *r.Datr} - } - } - - return json.Marshal(struct { - Chan *uint `json:"chan,omitempty"` - Codr *string `json:"codr,omitempty"` - Data *string `json:"data,omitempty"` - Datr *datrmarshaler `json:"datr,omitempty"` - Freq *float64 `json:"freq,omitempty"` - Lsnr *float64 `json:"lsnr,omitempty"` - Modu *string `json:"modu,omitempty"` - Rfch *uint `json:"rfch,omitempty"` - Rssi *int `json:"rssi,omitempty"` - Size *uint `json:"size,omitempty"` - Stat *int `json:"stat,omitempty"` - Time *timemarshaler `json:"time,omitempty"` - Tmst *uint `json:"tmst,omitempty"` - }{ - r.Chan, - r.Codr, - r.Data, - datr, - r.Freq, - r.Lsnr, - r.Modu, - r.Rfch, - r.Rssi, - r.Size, - r.Stat, - rfctime, - r.Tmst, - }) -} - -// MarshalJSON implements the Marshaler interface from encoding/json -func (s *Stat) MarshalJSON() ([]byte, error) { - var rfctime *timemarshaler = nil - if s.Time != nil { - rfctime = &timemarshaler{"2006-01-02 15:04:05 GMT", *s.Time} + if d.kind == "uint" { + return []byte(*d.value), nil } - - return json.Marshal(struct { - Ackr *float64 `json:"ackr,omitempty"` - Alti *int `json:"alti,omitempty"` - Dwnb *uint `json:"dwnb,omitempty"` - Lati *float64 `json:"lati,omitempty"` - Long *float64 `json:"long,omitempty"` - Rxfw *uint `json:"rxfw,omitempty"` - Rxnb *uint `json:"rxnb,omitempty"` - Rxok *uint `json:"rxok,omitempty"` - Time *timemarshaler `json:"time,omitempty"` - Txnb *uint `json:"txnb,omitempty"` - }{ - s.Ackr, - s.Alti, - s.Dwnb, - s.Lati, - s.Long, - s.Rxfw, - s.Rxnb, - s.Rxok, - rfctime, - s.Txnb, - }) + return append(append([]byte(`"`), []byte(*d.value)...), []byte(`"`)...), nil } // MarshalJSON implements the Marshaler interface from encoding/json -func (t *TXPK) MarshalJSON() ([]byte, error) { - var rfctime *timemarshaler = nil - var datr *datrmarshaler = nil - - if t.Time != nil { - rfctime = &timemarshaler{time.RFC3339Nano, *t.Time} - } - - if t.Modu != nil && t.Datr != nil { - switch *t.Modu { - case "FSK": - datr = &datrmarshaler{"uint", *t.Datr} - case "LORA": - fallthrough - default: - datr = &datrmarshaler{"string", *t.Datr} - } - } - - return json.Marshal(struct { - Codr *string `json:"codr,omitempty"` - Data *string `json:"data,omitempty"` - Datr *datrmarshaler `json:"datr,omitempty"` - Fdev *uint `json:"fdev,omitempty"` - Freq *float64 `json:"freq,omitempty"` - Imme *bool `json:"imme,omitempty"` - Ipol *bool `json:"ipol,omitempty"` - Modu *string `json:"modu,omitempty"` - Ncrc *bool `json:"ncrc,omitempty"` - Powe *uint `json:"powe,omitempty"` - Prea *uint `json:"prea,omitempty"` - Rfch *uint `json:"rfch,omitempty"` - Size *uint `json:"size,omitempty"` - Time *timemarshaler `json:"time,omitempty"` - Tmst *uint `json:"tmst,omitempty"` - }{ - t.Codr, - t.Data, - datr, - t.Fdev, - t.Freq, - t.Imme, - t.Ipol, - t.Modu, - t.Ncrc, - t.Powe, - t.Prea, - t.Rfch, - t.Size, - rfctime, - t.Tmst, - }) +func (p *Payload) MarshalJSON() ([]byte, error) { + // Define Stat Proxy + var proxStat *statProxy + if p.Stat != nil { + proxStat = new(statProxy) + proxStat.Stat = p.Stat + if p.Stat.Time != nil { + proxStat.Time = &timeparser{layout: "2006-01-02 15:04:05 GMT", value: p.Stat.Time} + } + } + + // Define RXPK Proxy + proxRXPK := make([]rxpkProxy, 0) + for _, rxpk := range(p.RXPK) { + proxr := new(rxpkProxy) + proxr.RXPK = new(RXPK) + *proxr.RXPK = rxpk + if rxpk.Time != nil { + proxr.Time = &timeparser{time.RFC3339Nano, rxpk.Time} + } + + if rxpk.Modu != nil && rxpk.Datr != nil { + switch *rxpk.Modu { + case "FSK": + proxr.Datr = &datrparser{kind: "uint", value: rxpk.Datr} + case "LORA": + fallthrough + default: + proxr.Datr = &datrparser{kind: "string", value: rxpk.Datr} + } + } + proxRXPK = append(proxRXPK,*proxr) + } + + // Define TXPK Proxy + var proxTXPK *txpkProxy + if p.TXPK != nil { + proxTXPK = new(txpkProxy) + proxTXPK.TXPK = p.TXPK + if p.TXPK.Time != nil { + proxTXPK.Time = &timeparser{time.RFC3339Nano, p.TXPK.Time} + } + if p.TXPK.Modu != nil && p.TXPK.Datr != nil { + switch *p.TXPK.Modu { + case "FSK": + proxTXPK.Datr = &datrparser{kind: "uint", value: p.TXPK.Datr} + case "LORA": + fallthrough + default: + proxTXPK.Datr = &datrparser{kind: "string", value: p.TXPK.Datr} + } + } + } + + // Define the whole Proxy + proxy := payloadProxy{ + ProxStat: proxStat, + ProxRXPK: proxRXPK, + ProxTXPK: proxTXPK, + } + + raw, err := json.Marshal(proxy) + return raw, err } From 8d777adbad8450e3ce8e2e2d42e44661f5f22183 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 23:45:53 +0100 Subject: [PATCH 0055/2266] Update json struct tag in gateway.go --- lorawan/gateway/gateway.go | 68 +++++++++++++++++++------------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index 760a158de..deb6d5879 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -12,53 +12,53 @@ import ( // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan *uint `json:"chan"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr"` // LoRa ECC coding rate identifier - Data *string `json:"data"` // Base64 encoded RF packet payload, padded + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst"` // Internal timestamp of "RX finished" event (32b unsigned) + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } -// TXPK represents a downlink json message format received by the gateway. +// TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr"` // LoRa ECC coding rate identifier - Data *string `json:"data"` // Base64 encoded RF packet payload, padding optional + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol"` // Lora modulation polarization inversion - Modu *string `json:"modu"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size"` // RF packet payload size in bytes (unsigned integer) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst"` // Send packet on a certain timestamp value (will ignore time) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway type Stat struct { - Ackr *float64 `json:"ackr"` // Percentage of upstream datagrams that were acknowledged - Alti *int `json:"alti"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint `json:"dwnb"` // Number of downlink datagrams received (unsigned integer) - Lati *float64 `json:"lati"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint `json:"rxfw"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint `json:"rxnb"` // Number of radio packets received (unsigned integer) - Rxok *uint `json:"rxok"` // Number of radio packets received with a valid PHY CRC + Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged + Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) + Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) + Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint `json:"txnb"` // Number of packets emitted (unsigned integer) + Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) } // Packet as seen by the gateway. From a37f7f443818d9590eb4c1d5d286e7a6b03ced4f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 23:46:14 +0100 Subject: [PATCH 0056/2266] Review test_data (re-order json keys) --- lorawan/gateway/test_data/marshal_rxpk | 2 +- lorawan/gateway/test_data/marshal_rxpk_stat | 2 +- lorawan/gateway/test_data/marshal_stat | 2 +- lorawan/gateway/test_data/marshal_txpk | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lorawan/gateway/test_data/marshal_rxpk b/lorawan/gateway/test_data/marshal_rxpk index f300bc08d..06397ac0c 100644 --- a/lorawan/gateway/test_data/marshal_rxpk +++ b/lorawan/gateway/test_data/marshal_rxpk @@ -1 +1 @@ -{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rfch":0,"rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}]} \ No newline at end of file +{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","freq":866.349812,"lsnr":5.1,"modu":"LORA","rfch":0,"rssi":-35,"size":32,"stat":1,"tmst":3512348611,"datr":"SF7BW125","time":"2013-03-31T16:21:17.528002Z"},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"tmst":3512348514,"datr":50000,"time":"2013-03-31T16:21:17.530974Z"}]} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_rxpk_stat b/lorawan/gateway/test_data/marshal_rxpk_stat index 9ce400fb4..21a8a8288 100644 --- a/lorawan/gateway/test_data/marshal_rxpk_stat +++ b/lorawan/gateway/test_data/marshal_rxpk_stat @@ -1 +1 @@ -{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","datr":"SF7BW125","freq":866.349812,"lsnr":5.1,"modu":"LORA","rfch":0,"rssi":-35,"size":32,"stat":1,"time":"2013-03-31T16:21:17.528002Z","tmst":3512348611},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","datr":50000,"freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"time":"2013-03-31T16:21:17.530974Z","tmst":3512348514}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} \ No newline at end of file +{"rxpk":[{"chan":2,"codr":"4/6","data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84","freq":866.349812,"lsnr":5.1,"modu":"LORA","rfch":0,"rssi":-35,"size":32,"stat":1,"tmst":3512348611,"datr":"SF7BW125","time":"2013-03-31T16:21:17.528002Z"},{"chan":9,"data":"VEVTVF9QQUNLRVRfMTIzNA==","freq":869.1,"modu":"FSK","rfch":1,"rssi":-75,"size":16,"stat":1,"tmst":3512348514,"datr":50000,"time":"2013-03-31T16:21:17.530974Z"}],"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"txnb":2,"time":"2014-01-12 08:59:28 GMT"}} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_stat b/lorawan/gateway/test_data/marshal_stat index 2c5f67d67..ad6bb1e36 100644 --- a/lorawan/gateway/test_data/marshal_stat +++ b/lorawan/gateway/test_data/marshal_stat @@ -1 +1 @@ -{"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} \ No newline at end of file +{"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"txnb":2,"time":"2014-01-12 08:59:28 GMT"}} \ No newline at end of file diff --git a/lorawan/gateway/test_data/marshal_txpk b/lorawan/gateway/test_data/marshal_txpk index a95c51fba..9229a0931 100644 --- a/lorawan/gateway/test_data/marshal_txpk +++ b/lorawan/gateway/test_data/marshal_txpk @@ -1 +1 @@ -{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} \ No newline at end of file +{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32,"datr":"SF11BW125"}} \ No newline at end of file From bf0b087496c91ce9c5b932fdc0aa01e0e1519095 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Dec 2015 23:47:50 +0100 Subject: [PATCH 0057/2266] Format go code --- lorawan/gateway/decode.go | 82 +++++++++--------- lorawan/gateway/encode.go | 136 +++++++++++++++--------------- lorawan/gateway/encode_proxies.go | 28 +++--- lorawan/gateway/gateway.go | 60 ++++++------- 4 files changed, 153 insertions(+), 153 deletions(-) diff --git a/lorawan/gateway/decode.go b/lorawan/gateway/decode.go index b6a6f538b..d1ee10e19 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/gateway/decode.go @@ -85,45 +85,45 @@ func (d *datrparser) UnmarshalJSON(raw []byte) error { // UnmarshalJSON implements the Unmarshaler interface from encoding/json func (p *Payload) UnmarshalJSON(raw []byte) error { - proxy := payloadProxy{ - ProxStat: &statProxy{ - Stat: new(Stat), - }, - ProxTXPK: &txpkProxy{ - TXPK: new(TXPK), - }, - } - - if err := json.Unmarshal(raw, &proxy); err != nil { - return err - } - - if proxy.ProxStat.Stat != nil { - if proxy.ProxStat.Time != nil { - proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.value - } - p.Stat = proxy.ProxStat.Stat - } - - if proxy.ProxTXPK.TXPK != nil { - if proxy.ProxTXPK.Time != nil { - proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.value - } - if proxy.ProxTXPK.Datr != nil { - proxy.ProxTXPK.TXPK.Datr = proxy.ProxTXPK.Datr.value - } - p.TXPK = proxy.ProxTXPK.TXPK - } - - for _, rxpk := range(proxy.ProxRXPK) { - if rxpk.Time != nil { - rxpk.RXPK.Time = rxpk.Time.value - } - if rxpk.Datr != nil { - rxpk.RXPK.Datr = rxpk.Datr.value - } - p.RXPK = append(p.RXPK, *rxpk.RXPK) - } - - return nil + proxy := payloadProxy{ + ProxStat: &statProxy{ + Stat: new(Stat), + }, + ProxTXPK: &txpkProxy{ + TXPK: new(TXPK), + }, + } + + if err := json.Unmarshal(raw, &proxy); err != nil { + return err + } + + if proxy.ProxStat.Stat != nil { + if proxy.ProxStat.Time != nil { + proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.value + } + p.Stat = proxy.ProxStat.Stat + } + + if proxy.ProxTXPK.TXPK != nil { + if proxy.ProxTXPK.Time != nil { + proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.value + } + if proxy.ProxTXPK.Datr != nil { + proxy.ProxTXPK.TXPK.Datr = proxy.ProxTXPK.Datr.value + } + p.TXPK = proxy.ProxTXPK.TXPK + } + + for _, rxpk := range proxy.ProxRXPK { + if rxpk.Time != nil { + rxpk.RXPK.Time = rxpk.Time.value + } + if rxpk.Datr != nil { + rxpk.RXPK.Datr = rxpk.Datr.value + } + p.RXPK = append(p.RXPK, *rxpk.RXPK) + } + + return nil } diff --git a/lorawan/gateway/encode.go b/lorawan/gateway/encode.go index c494f8571..430f736a9 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/gateway/encode.go @@ -40,17 +40,17 @@ func Marshal(packet *Packet) ([]byte, error) { // MarshalJSON implements the Marshaler interface from encoding/json func (t *timeparser) MarshalJSON() ([]byte, error) { - if t.value == nil { - return nil, errors.New("Cannot marshal a null time") - } + if t.value == nil { + return nil, errors.New("Cannot marshal a null time") + } return append(append([]byte(`"`), []byte(t.value.Format(t.layout))...), []byte(`"`)...), nil } // MarshalJSON implements the Marshaler interface from encoding/json func (d *datrparser) MarshalJSON() ([]byte, error) { - if d.value == nil { - return nil, errors.New("Cannot marshal a null datr") - } + if d.value == nil { + return nil, errors.New("Cannot marshal a null datr") + } if d.kind == "uint" { return []byte(*d.value), nil @@ -60,66 +60,66 @@ func (d *datrparser) MarshalJSON() ([]byte, error) { // MarshalJSON implements the Marshaler interface from encoding/json func (p *Payload) MarshalJSON() ([]byte, error) { - // Define Stat Proxy - var proxStat *statProxy - if p.Stat != nil { - proxStat = new(statProxy) - proxStat.Stat = p.Stat - if p.Stat.Time != nil { - proxStat.Time = &timeparser{layout: "2006-01-02 15:04:05 GMT", value: p.Stat.Time} - } - } - - // Define RXPK Proxy - proxRXPK := make([]rxpkProxy, 0) - for _, rxpk := range(p.RXPK) { - proxr := new(rxpkProxy) - proxr.RXPK = new(RXPK) - *proxr.RXPK = rxpk - if rxpk.Time != nil { - proxr.Time = &timeparser{time.RFC3339Nano, rxpk.Time} - } - - if rxpk.Modu != nil && rxpk.Datr != nil { - switch *rxpk.Modu { - case "FSK": - proxr.Datr = &datrparser{kind: "uint", value: rxpk.Datr} - case "LORA": - fallthrough - default: - proxr.Datr = &datrparser{kind: "string", value: rxpk.Datr} - } - } - proxRXPK = append(proxRXPK,*proxr) - } - - // Define TXPK Proxy - var proxTXPK *txpkProxy - if p.TXPK != nil { - proxTXPK = new(txpkProxy) - proxTXPK.TXPK = p.TXPK - if p.TXPK.Time != nil { - proxTXPK.Time = &timeparser{time.RFC3339Nano, p.TXPK.Time} - } - if p.TXPK.Modu != nil && p.TXPK.Datr != nil { - switch *p.TXPK.Modu { - case "FSK": - proxTXPK.Datr = &datrparser{kind: "uint", value: p.TXPK.Datr} - case "LORA": - fallthrough - default: - proxTXPK.Datr = &datrparser{kind: "string", value: p.TXPK.Datr} - } - } - } - - // Define the whole Proxy - proxy := payloadProxy{ - ProxStat: proxStat, - ProxRXPK: proxRXPK, - ProxTXPK: proxTXPK, - } - - raw, err := json.Marshal(proxy) - return raw, err + // Define Stat Proxy + var proxStat *statProxy + if p.Stat != nil { + proxStat = new(statProxy) + proxStat.Stat = p.Stat + if p.Stat.Time != nil { + proxStat.Time = &timeparser{layout: "2006-01-02 15:04:05 GMT", value: p.Stat.Time} + } + } + + // Define RXPK Proxy + proxRXPK := make([]rxpkProxy, 0) + for _, rxpk := range p.RXPK { + proxr := new(rxpkProxy) + proxr.RXPK = new(RXPK) + *proxr.RXPK = rxpk + if rxpk.Time != nil { + proxr.Time = &timeparser{time.RFC3339Nano, rxpk.Time} + } + + if rxpk.Modu != nil && rxpk.Datr != nil { + switch *rxpk.Modu { + case "FSK": + proxr.Datr = &datrparser{kind: "uint", value: rxpk.Datr} + case "LORA": + fallthrough + default: + proxr.Datr = &datrparser{kind: "string", value: rxpk.Datr} + } + } + proxRXPK = append(proxRXPK, *proxr) + } + + // Define TXPK Proxy + var proxTXPK *txpkProxy + if p.TXPK != nil { + proxTXPK = new(txpkProxy) + proxTXPK.TXPK = p.TXPK + if p.TXPK.Time != nil { + proxTXPK.Time = &timeparser{time.RFC3339Nano, p.TXPK.Time} + } + if p.TXPK.Modu != nil && p.TXPK.Datr != nil { + switch *p.TXPK.Modu { + case "FSK": + proxTXPK.Datr = &datrparser{kind: "uint", value: p.TXPK.Datr} + case "LORA": + fallthrough + default: + proxTXPK.Datr = &datrparser{kind: "string", value: p.TXPK.Datr} + } + } + } + + // Define the whole Proxy + proxy := payloadProxy{ + ProxStat: proxStat, + ProxRXPK: proxRXPK, + ProxTXPK: proxTXPK, + } + + raw, err := json.Marshal(proxy) + return raw, err } diff --git a/lorawan/gateway/encode_proxies.go b/lorawan/gateway/encode_proxies.go index c60e51b02..bb10fe097 100644 --- a/lorawan/gateway/encode_proxies.go +++ b/lorawan/gateway/encode_proxies.go @@ -7,39 +7,39 @@ import "time" // Proxies handle annoying json fields like datr and time type payloadProxy struct { - ProxRXPK []rxpkProxy `json:"rxpk,omitempty"` - ProxStat *statProxy `json:"stat,omitempty"` - ProxTXPK *txpkProxy `json:"txpk,omitempty"` + ProxRXPK []rxpkProxy `json:"rxpk,omitempty"` + ProxStat *statProxy `json:"stat,omitempty"` + ProxTXPK *txpkProxy `json:"txpk,omitempty"` } type statProxy struct { - *Stat - Time *timeparser `json:"time,omitempty"` + *Stat + Time *timeparser `json:"time,omitempty"` } type rxpkProxy struct { - *RXPK - Datr *datrparser `json:"datr,omitempty"` - Time *timeparser `json:"time,omitempty"` + *RXPK + Datr *datrparser `json:"datr,omitempty"` + Time *timeparser `json:"time,omitempty"` } type txpkProxy struct { - *TXPK - Datr *datrparser `json:"datr,omitempty"` - Time *timeparser `json:"time,omitempty"` + *TXPK + Datr *datrparser `json:"datr,omitempty"` + Time *timeparser `json:"time,omitempty"` } // datrParser is used as a proxy to Unmarshal datr field in json payloads. // Depending on the modulation type, the datr type could be either a string or a number. // We're gonna parse it as a string in any case. type datrparser struct { - kind string + kind string value *string // The parsed value } // timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time // module parse RFC3339 by default type timeparser struct { - layout string - value *time.Time // The parsed time value + layout string + value *time.Time // The parsed time value } diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index deb6d5879..8861dd1e0 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -15,7 +15,7 @@ type RXPK struct { Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" @@ -23,42 +23,42 @@ type RXPK struct { Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } // TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway type Stat struct { - Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged - Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) - Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) - Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) + Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged + Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) + Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) + Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC + Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) } // Packet as seen by the gateway. @@ -72,10 +72,10 @@ type Packet struct { // Payload refers to the JSON payload sent by a gateway or a server. type Payload struct { - Raw []byte `json:"-"` // The raw unparsed response + Raw []byte `json:"-"` // The raw unparsed response RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any - Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any - TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any + Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any + TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any } // Available packet commands From 9891caf4e344283aaee32c9210624b59081a5819 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 10:49:21 +0100 Subject: [PATCH 0058/2266] Change package name in lorawan/gateway documentation --- lorawan/gateway/gateway.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan/gateway/gateway.go b/lorawan/gateway/gateway.go index 8861dd1e0..4ca89a6b8 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/gateway/gateway.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// Package gateway/protocol provides useful methods and types to handle communications with a gateway. +// Package gateway provides useful methods and types to handle communications with a gateway. // // This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT package gateway From db9e68501a64cc9eb65dfe972950a0add1c83cec Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 11:15:54 +0100 Subject: [PATCH 0059/2266] [simulators.gateway] rename gateway package to semtech --- lorawan/{gateway => semtech}/decode.go | 2 +- lorawan/{gateway => semtech}/decode_test.go | 2 +- lorawan/{gateway => semtech}/encode.go | 2 +- lorawan/{gateway => semtech}/encode_proxies.go | 2 +- lorawan/{gateway => semtech}/encode_test.go | 2 +- lorawan/{gateway/gateway.go => semtech/semtech.go} | 4 ++-- lorawan/{gateway => semtech}/test_data/marshal_rxpk | 0 lorawan/{gateway => semtech}/test_data/marshal_rxpk_stat | 0 lorawan/{gateway => semtech}/test_data/marshal_stat | 0 lorawan/{gateway => semtech}/test_data/marshal_txpk | 0 10 files changed, 7 insertions(+), 7 deletions(-) rename lorawan/{gateway => semtech}/decode.go (99%) rename lorawan/{gateway => semtech}/decode_test.go (99%) rename lorawan/{gateway => semtech}/encode.go (99%) rename lorawan/{gateway => semtech}/encode_proxies.go (98%) rename lorawan/{gateway => semtech}/encode_test.go (99%) rename lorawan/{gateway/gateway.go => semtech/semtech.go} (98%) rename lorawan/{gateway => semtech}/test_data/marshal_rxpk (100%) rename lorawan/{gateway => semtech}/test_data/marshal_rxpk_stat (100%) rename lorawan/{gateway => semtech}/test_data/marshal_stat (100%) rename lorawan/{gateway => semtech}/test_data/marshal_txpk (100%) diff --git a/lorawan/gateway/decode.go b/lorawan/semtech/decode.go similarity index 99% rename from lorawan/gateway/decode.go rename to lorawan/semtech/decode.go index d1ee10e19..f9d4b667c 100644 --- a/lorawan/gateway/decode.go +++ b/lorawan/semtech/decode.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package gateway +package semtech import ( "encoding/json" diff --git a/lorawan/gateway/decode_test.go b/lorawan/semtech/decode_test.go similarity index 99% rename from lorawan/gateway/decode_test.go rename to lorawan/semtech/decode_test.go index 63b5738ef..e7b2c7928 100644 --- a/lorawan/gateway/decode_test.go +++ b/lorawan/semtech/decode_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package gateway +package semtech import ( "bytes" diff --git a/lorawan/gateway/encode.go b/lorawan/semtech/encode.go similarity index 99% rename from lorawan/gateway/encode.go rename to lorawan/semtech/encode.go index 430f736a9..638ebcb75 100644 --- a/lorawan/gateway/encode.go +++ b/lorawan/semtech/encode.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package gateway +package semtech import ( "encoding/json" diff --git a/lorawan/gateway/encode_proxies.go b/lorawan/semtech/encode_proxies.go similarity index 98% rename from lorawan/gateway/encode_proxies.go rename to lorawan/semtech/encode_proxies.go index bb10fe097..e56103493 100644 --- a/lorawan/gateway/encode_proxies.go +++ b/lorawan/semtech/encode_proxies.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package gateway +package semtech import "time" diff --git a/lorawan/gateway/encode_test.go b/lorawan/semtech/encode_test.go similarity index 99% rename from lorawan/gateway/encode_test.go rename to lorawan/semtech/encode_test.go index eabaee85b..3d00a625d 100644 --- a/lorawan/gateway/encode_test.go +++ b/lorawan/semtech/encode_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package gateway +package semtech import ( "bytes" diff --git a/lorawan/gateway/gateway.go b/lorawan/semtech/semtech.go similarity index 98% rename from lorawan/gateway/gateway.go rename to lorawan/semtech/semtech.go index 4ca89a6b8..9d3b97efa 100644 --- a/lorawan/gateway/gateway.go +++ b/lorawan/semtech/semtech.go @@ -1,10 +1,10 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// Package gateway provides useful methods and types to handle communications with a gateway. +// Package semtech provides useful methods and types to handle communications with a gateway. // // This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT -package gateway +package semtech import ( "time" diff --git a/lorawan/gateway/test_data/marshal_rxpk b/lorawan/semtech/test_data/marshal_rxpk similarity index 100% rename from lorawan/gateway/test_data/marshal_rxpk rename to lorawan/semtech/test_data/marshal_rxpk diff --git a/lorawan/gateway/test_data/marshal_rxpk_stat b/lorawan/semtech/test_data/marshal_rxpk_stat similarity index 100% rename from lorawan/gateway/test_data/marshal_rxpk_stat rename to lorawan/semtech/test_data/marshal_rxpk_stat diff --git a/lorawan/gateway/test_data/marshal_stat b/lorawan/semtech/test_data/marshal_stat similarity index 100% rename from lorawan/gateway/test_data/marshal_stat rename to lorawan/semtech/test_data/marshal_stat diff --git a/lorawan/gateway/test_data/marshal_txpk b/lorawan/semtech/test_data/marshal_txpk similarity index 100% rename from lorawan/gateway/test_data/marshal_txpk rename to lorawan/semtech/test_data/marshal_txpk From b5af2e4aeeed6f671366a99ccf95bd170656a106 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 11:56:13 +0100 Subject: [PATCH 0060/2266] [simulators.gateway] Define former interfaces for gateway simulator --- simulators/gateway/gateway.go | 48 +++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 simulators/gateway/gateway.go diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go new file mode 100644 index 000000000..7136b69fb --- /dev/null +++ b/simulators/gateway/gateway.go @@ -0,0 +1,48 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package gateway offers a dummy representation of a gateway. +// +// The package can be used to create a dummy gateway. +// Its former use is to provide a handy simulator for further testing of the whole network chain. +package gateway + +import ( + "github.com/thethingsnetwork/core/lorawan/semtech" +) + +type Gateway struct { + Coord GPSCoord // Gateway's GPS coordinates + Routers []string // List of routers addresses + + ackr float64 // Percentage of upstream datagrams that were acknowledged + dwnb uint // Number of downlink datagrams received + rxfw uint // Number of radio packets forwarded + rxnb uint // Number of radio packets received + rxok uint // Number of radio packets received with a valid PHY CRC + txnb uint // Number of packets emitted + + stderr <-chan error // Output error channel + stdout <-chan semtech.Packet // Output communication channel +} + +type GPSCoord struct { + altitude int // GPS altitude in RX meters + latitude float64 // GPS latitude, North is + + longitude float64 // GPS longitude, East is + +} + +func Create (id string, routers ...string) Gateway, error { + return nil, nil +} + +func genToken () []byte { + return nil +} + +type Forwarder interface { + Forward(packet semtech.Packet) () + Mimic() + Start() (<-chan semtech.Packet, <-chan error) + Stat() semtech.Stat +} From 57e4c547758197bee8e93b4ab8590834979357fa Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 15:17:32 +0100 Subject: [PATCH 0061/2266] [simulators.gateway] Write test for genToken() method (give a try to Convey in the meantime) --- simulators/gateway/utils_test.go | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 simulators/gateway/utils_test.go diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go new file mode 100644 index 000000000..7bcd6fde4 --- /dev/null +++ b/simulators/gateway/utils_test.go @@ -0,0 +1,42 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "testing" + . "github.com/smartystreets/goconvey/convey" +) + + +func TestGenToken(t *testing.T) { + Convey("The genToken() method should return randommly generated 2-byte long tokens", t, func() { + Convey("Given 5 generated tokens", func () { + randTokens := [5][]byte{ + genToken(), + genToken(), + genToken(), + genToken(), + genToken(), + } + + Convey("They shouldn't be all identical", func() { + sameTokens := [5][]byte{ + randTokens[0], + randTokens[0], + randTokens[0], + randTokens[0], + randTokens[0], + } + + So(randTokens, ShouldNotResemble, sameTokens) + }) + + Convey("They should all be 2-byte long", func() { + for _, t := range(randTokens) { + So(len(t), ShouldEqual, 2) + } + }) + }) + }) +} From 72dec47b6d8f78774e73acfbb73787af6c3335a7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 16:02:21 +0100 Subject: [PATCH 0062/2266] [simulators.gateway] Add genToken method --- simulators/gateway/gateway.go | 57 ++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 7136b69fb..6d6c6e4c5 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -8,41 +8,50 @@ package gateway import ( - "github.com/thethingsnetwork/core/lorawan/semtech" + "encoding/binary" + "fmt" + "github.com/thethingsnetwork/core/lorawan/semtech" + "math/rand" ) type Gateway struct { - Coord GPSCoord // Gateway's GPS coordinates - Routers []string // List of routers addresses - - ackr float64 // Percentage of upstream datagrams that were acknowledged - dwnb uint // Number of downlink datagrams received - rxfw uint // Number of radio packets forwarded - rxnb uint // Number of radio packets received - rxok uint // Number of radio packets received with a valid PHY CRC - txnb uint // Number of packets emitted - - stderr <-chan error // Output error channel - stdout <-chan semtech.Packet // Output communication channel + Coord GPSCoord // Gateway's GPS coordinates + Routers []string // List of routers addresses + + ackr float64 // Percentage of upstream datagrams that were acknowledged + dwnb uint // Number of downlink datagrams received + rxfw uint // Number of radio packets forwarded + rxnb uint // Number of radio packets received + rxok uint // Number of radio packets received with a valid PHY CRC + txnb uint // Number of packets emitted + + stderr <-chan error // Output error channel + stdout <-chan semtech.Packet // Output communication channel } type GPSCoord struct { - altitude int // GPS altitude in RX meters - latitude float64 // GPS latitude, North is + - longitude float64 // GPS longitude, East is + + altitude int // GPS altitude in RX meters + latitude float64 // GPS latitude, North is + + longitude float64 // GPS longitude, East is + } -func Create (id string, routers ...string) Gateway, error { - return nil, nil +func Create(id string, routers ...string) (Gateway, error) { + fmt.Printf("") + return Gateway{}, nil } -func genToken () []byte { - return nil +func genToken() []byte { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, rand.Uint32()) + return b[0:2] } type Forwarder interface { - Forward(packet semtech.Packet) () - Mimic() - Start() (<-chan semtech.Packet, <-chan error) - Stat() semtech.Stat + Forward(packet semtech.Packet) + Start() (<-chan semtech.Packet, <-chan error) + Stat() semtech.Stat +} + +type Imitator interface { + Mimic() } From 9d8870f5f0aa926225eaba4bc056b6e068c332b8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 19:03:39 +0100 Subject: [PATCH 0063/2266] [simulators.gateway] Add gateway.New() tests --- simulators/gateway/utils_test.go | 123 +++++++++++++++++++++++-------- 1 file changed, 91 insertions(+), 32 deletions(-) diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 7bcd6fde4..413e0c73c 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -4,39 +4,98 @@ package gateway import ( - "testing" - . "github.com/smartystreets/goconvey/convey" + . "github.com/smartystreets/goconvey/convey" + "testing" ) - func TestGenToken(t *testing.T) { - Convey("The genToken() method should return randommly generated 2-byte long tokens", t, func() { - Convey("Given 5 generated tokens", func () { - randTokens := [5][]byte{ - genToken(), - genToken(), - genToken(), - genToken(), - genToken(), - } - - Convey("They shouldn't be all identical", func() { - sameTokens := [5][]byte{ - randTokens[0], - randTokens[0], - randTokens[0], - randTokens[0], - randTokens[0], - } - - So(randTokens, ShouldNotResemble, sameTokens) - }) - - Convey("They should all be 2-byte long", func() { - for _, t := range(randTokens) { - So(len(t), ShouldEqual, 2) - } - }) - }) - }) + Convey("The genToken() method should return randommly generated 2-byte long tokens", t, func() { + Convey("Given 5 generated tokens", func() { + randTokens := [5][]byte{ + genToken(), + genToken(), + genToken(), + genToken(), + genToken(), + } + + Convey("They shouldn't be all identical", func() { + sameTokens := [5][]byte{ + randTokens[0], + randTokens[0], + randTokens[0], + randTokens[0], + randTokens[0], + } + + So(randTokens, ShouldNotResemble, sameTokens) + }) + + Convey("They should all be 2-byte long", func() { + for _, t := range randTokens { + So(len(t), ShouldEqual, 2) + } + }) + }) + }) +} + +func TestCreate(t *testing.T) { + Convey("The New method should return a valid gateway struct ready to use", t, func() { + id := "qwerty" + router1 := "router1Addr" + router2 := "router2Addr" + + Convey("Given an identifier and a router address", func() { + gateway, err := New(id, router1) + + Convey("No error should have been trown", func() { + So(err, ShouldBeNil) + }) + + Convey("The identifier should have been set correctly", func() { + So(gateway.Id, ShouldEqual, id) + }) + + Convey("The list of configured routers should have been set correctly", func() { + So(gateway.Routers, ShouldResemble, []string{router1}) + }) + }) + + Convey("Given an identifier and several routers address", func() { + gateway, err := New(id, router1, router2) + + Convey("No error should have been trown", func() { + So(err, ShouldBeNil) + }) + + Convey("The identifier should have been set correctly", func() { + So(gateway.Id, ShouldEqual, id) + }) + + Convey("The list of configured routers should have been set correctly", func() { + So(gateway.Routers, ShouldResemble, []string{router1, router2}) + }) + }) + + Convey("Given a bad identifier and/or bad router addresses", func() { + Convey("It should return an error for an empty id", func() { + gateway, err := New("", router1) + So(gateway, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + + Convey("It should return an error for an empty routers list", func() { + gateway, err := New(id) + So(gateway, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + + Convey("It should return an error for an invalid router address", func() { + gateway, err := New(id, "") + So(gateway, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + }) + }) } From ea0f8b2861cce01eccb603403ed1456a4284d239 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Dec 2015 19:03:58 +0100 Subject: [PATCH 0064/2266] [simulators.gateway] Implement gateway.New() --- simulators/gateway/gateway.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 6d6c6e4c5..bf193350a 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -9,6 +9,7 @@ package gateway import ( "encoding/binary" + "errors" "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" "math/rand" @@ -16,6 +17,7 @@ import ( type Gateway struct { Coord GPSCoord // Gateway's GPS coordinates + Id string // Gateway's Identifier Routers []string // List of routers addresses ackr float64 // Percentage of upstream datagrams that were acknowledged @@ -35,9 +37,34 @@ type GPSCoord struct { longitude float64 // GPS longitude, East is + } -func Create(id string, routers ...string) (Gateway, error) { +func New(id string, routers ...string) (*Gateway, error) { fmt.Printf("") - return Gateway{}, nil + if id == "" { + return nil, errors.New("Invalid gateway id provided") + } + + if len(routers) == 0 { + return nil, errors.New("At least one router address should be provided") + } + + wrongAddress := false + for _, r := range routers { + wrongAddress = wrongAddress || (r == "") + } + + if wrongAddress { + return nil, errors.New("Invalid router address") + } + + return &Gateway{ + Id: id, + Coord: GPSCoord{ + altitude: 120, // TEMPORARY + latitude: 53.3702, + longitude: 4.8952, + }, + Routers: routers, + }, nil } func genToken() []byte { From aed28aaac87f8366b1408902d4dda46b8fc3e1ec Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 16:22:39 +0100 Subject: [PATCH 0065/2266] [simulators.gateway] Split gateway file --- simulators/gateway/gateway.go | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index bf193350a..8cf7eafe9 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -8,17 +8,13 @@ package gateway import ( - "encoding/binary" "errors" - "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" - "math/rand" ) type Gateway struct { - Coord GPSCoord // Gateway's GPS coordinates - Id string // Gateway's Identifier - Routers []string // List of routers addresses + Coord GPSCoord // Gateway's GPS coordinates + Id string // Gateway's Identifier ackr float64 // Percentage of upstream datagrams that were acknowledged dwnb uint // Number of downlink datagrams received @@ -27,8 +23,9 @@ type Gateway struct { rxok uint // Number of radio packets received with a valid PHY CRC txnb uint // Number of packets emitted - stderr <-chan error // Output error channel - stdout <-chan semtech.Packet // Output communication channel + routers []string // List of routers addresses + cherr chan error // Output error channel + chout chan semtech.Packet // Output communication channel } type GPSCoord struct { @@ -38,7 +35,6 @@ type GPSCoord struct { } func New(id string, routers ...string) (*Gateway, error) { - fmt.Printf("") if id == "" { return nil, errors.New("Invalid gateway id provided") } @@ -63,22 +59,10 @@ func New(id string, routers ...string) (*Gateway, error) { latitude: 53.3702, longitude: 4.8952, }, - Routers: routers, + routers: routers, }, nil } -func genToken() []byte { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, rand.Uint32()) - return b[0:2] -} - -type Forwarder interface { - Forward(packet semtech.Packet) - Start() (<-chan semtech.Packet, <-chan error) - Stat() semtech.Stat -} - type Imitator interface { Mimic() } From 1780f53bcdddbe17eb0544448a15b49bc0e55c80 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 16:24:35 +0100 Subject: [PATCH 0066/2266] [simulators.gateway] Rewrite and write gateway's tests --- simulators/gateway/forwarder_test.go | 59 ++++++++++++++++++++++++ simulators/gateway/gateway_test.go | 69 ++++++++++++++++++++++++++++ simulators/gateway/utils_test.go | 60 ------------------------ 3 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 simulators/gateway/forwarder_test.go create mode 100644 simulators/gateway/gateway_test.go diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go new file mode 100644 index 000000000..da2f27278 --- /dev/null +++ b/simulators/gateway/forwarder_test.go @@ -0,0 +1,59 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core/lorawan/semtech" + "net" + "testing" +) + +func TestStart(t *testing.T) { + gatewayId := "MyGateway" + routerAddr := "0.0.0.0:3000" + gateway, err := New(gatewayId, routerAddr) + chout, cherr := gateway.Start() + + udpAddr, err := net.ResolveUDPAddr("udp", routerAddr) + if err != nil { + t.Errorf("Unexpected error %+v\n", err) + return + } + + conn, err := net.DialUDP("udp", nil, udpAddr) + if err != nil { + t.Errorf("Unexpected error %+v\n", err) + return + } + + Convey("Given two valid router adresses", t, func() { + Convey("After having created a new gateway with one router", func() { + Convey("Both channels should exist", func() { + So(cherr, ShouldNotBeNil) + So(chout, ShouldNotBeNil) + }) + + Convey("A valid packet should be forwarded", func() { + packet := semtech.Packet{ + Version: semtech.VERSION, + Token: []byte{0x1, 0x2}, + Identifier: semtech.PUSH_ACK, + } + raw, err := semtech.Marshal(&packet) + if err != nil { + t.Errorf("Unexpected error %+v\n", err) + return + } + conn.Write(raw) + So(<-chout, ShouldResemble, packet) + }) + + Convey("An invalid packet should raise an error", func() { + conn.Write([]byte("Invalid")) + So(<-cherr, ShouldNotBeNil) + }) + }) + }) +} diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go new file mode 100644 index 000000000..517b36ff2 --- /dev/null +++ b/simulators/gateway/gateway_test.go @@ -0,0 +1,69 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + . "github.com/smartystreets/goconvey/convey" + "testing" +) + +func TestNew(t *testing.T) { + Convey("The New method should return a valid gateway struct ready to use", t, func() { + id := "qwerty" + router1 := "router1Addr" + router2 := "router2Addr" + + Convey("Given an identifier and a router address", func() { + gateway, err := New(id, router1) + + Convey("No error should have been trown", func() { + So(err, ShouldBeNil) + }) + + Convey("The identifier should have been set correctly", func() { + So(gateway.Id, ShouldEqual, id) + }) + + Convey("The list of configured routers should have been set correctly", func() { + So(gateway.routers, ShouldResemble, []string{router1}) + }) + }) + + Convey("Given an identifier and several routers address", func() { + gateway, err := New(id, router1, router2) + + Convey("No error should have been trown", func() { + So(err, ShouldBeNil) + }) + + Convey("The identifier should have been set correctly", func() { + So(gateway.Id, ShouldEqual, id) + }) + + Convey("The list of configured routers should have been set correctly", func() { + So(gateway.routers, ShouldResemble, []string{router1, router2}) + }) + }) + + Convey("Given a bad identifier and/or bad router addresses", func() { + Convey("It should return an error for an empty id", func() { + gateway, err := New("", router1) + So(gateway, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + + Convey("It should return an error for an empty routers list", func() { + gateway, err := New(id) + So(gateway, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + + Convey("It should return an error for an invalid router address", func() { + gateway, err := New(id, "") + So(gateway, ShouldBeNil) + So(err, ShouldNotBeNil) + }) + }) + }) +} diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 413e0c73c..5ee58cf33 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -39,63 +39,3 @@ func TestGenToken(t *testing.T) { }) }) } - -func TestCreate(t *testing.T) { - Convey("The New method should return a valid gateway struct ready to use", t, func() { - id := "qwerty" - router1 := "router1Addr" - router2 := "router2Addr" - - Convey("Given an identifier and a router address", func() { - gateway, err := New(id, router1) - - Convey("No error should have been trown", func() { - So(err, ShouldBeNil) - }) - - Convey("The identifier should have been set correctly", func() { - So(gateway.Id, ShouldEqual, id) - }) - - Convey("The list of configured routers should have been set correctly", func() { - So(gateway.Routers, ShouldResemble, []string{router1}) - }) - }) - - Convey("Given an identifier and several routers address", func() { - gateway, err := New(id, router1, router2) - - Convey("No error should have been trown", func() { - So(err, ShouldBeNil) - }) - - Convey("The identifier should have been set correctly", func() { - So(gateway.Id, ShouldEqual, id) - }) - - Convey("The list of configured routers should have been set correctly", func() { - So(gateway.Routers, ShouldResemble, []string{router1, router2}) - }) - }) - - Convey("Given a bad identifier and/or bad router addresses", func() { - Convey("It should return an error for an empty id", func() { - gateway, err := New("", router1) - So(gateway, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - - Convey("It should return an error for an empty routers list", func() { - gateway, err := New(id) - So(gateway, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - - Convey("It should return an error for an invalid router address", func() { - gateway, err := New(id, "") - So(gateway, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - }) - }) -} From 7e85c7cc8739e8bb370e8a202f9fedbe2ae676b8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 16:24:50 +0100 Subject: [PATCH 0067/2266] [simulators.gateway] Split gateway -> utils --- simulators/gateway/utils.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 simulators/gateway/utils.go diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go new file mode 100644 index 000000000..c1f278fe4 --- /dev/null +++ b/simulators/gateway/utils.go @@ -0,0 +1,15 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "encoding/binary" + "math/rand" +) + +func genToken() []byte { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, rand.Uint32()) + return b[0:2] +} From 52a135c88e8233ec42513826272e8e2e2368b01f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 16:25:01 +0100 Subject: [PATCH 0068/2266] [simulators.gateway] Write Start() function of the forwarder --- simulators/gateway/forwarder.go | 55 +++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 simulators/gateway/forwarder.go diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go new file mode 100644 index 000000000..3ac8a8a86 --- /dev/null +++ b/simulators/gateway/forwarder.go @@ -0,0 +1,55 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "github.com/thethingsnetwork/core/lorawan/semtech" + "net" +) + +type Forwarder interface { + Forward(packet semtech.Packet) + Start() (<-chan semtech.Packet, <-chan error) + Stat() semtech.Stat +} + +func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error) { + chout := make(chan semtech.Packet) + cherr := make(chan error) + + for _, router := range g.routers { + addr, err := net.ResolveUDPAddr("udp", router) + if err != nil { + cherr <- err + continue + } + + go func() { + conn, err := net.ListenUDP("udp", addr) + if err != nil { + cherr <- err + return + } + defer conn.Close() + buf := make([]byte, 1024) + for { + n, _, err := conn.ReadFromUDP(buf) + if err != nil { + cherr <- err + continue + } + packet, err := semtech.Unmarshal(buf[:n]) + if err != nil { + cherr <- err + continue + } + chout <- *packet + } + }() + } + + g.chout = chout + g.cherr = cherr + return chout, cherr +} From 8feb690eb5b9a8ac507e80309d41fe5da90a9176 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 17:29:58 +0100 Subject: [PATCH 0069/2266] [simulators.gateway] Add tests for gateway.Stop() --- simulators/gateway/forwarder_test.go | 84 +++++++++++++++++++--------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index da2f27278..a2646cb0f 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -13,7 +13,7 @@ import ( func TestStart(t *testing.T) { gatewayId := "MyGateway" routerAddr := "0.0.0.0:3000" - gateway, err := New(gatewayId, routerAddr) + gateway, _ := New(gatewayId, routerAddr) chout, cherr := gateway.Start() udpAddr, err := net.ResolveUDPAddr("udp", routerAddr) @@ -28,32 +28,62 @@ func TestStart(t *testing.T) { return } - Convey("Given two valid router adresses", t, func() { - Convey("After having created a new gateway with one router", func() { - Convey("Both channels should exist", func() { - So(cherr, ShouldNotBeNil) - So(chout, ShouldNotBeNil) - }) - - Convey("A valid packet should be forwarded", func() { - packet := semtech.Packet{ - Version: semtech.VERSION, - Token: []byte{0x1, 0x2}, - Identifier: semtech.PUSH_ACK, - } - raw, err := semtech.Marshal(&packet) - if err != nil { - t.Errorf("Unexpected error %+v\n", err) - return - } - conn.Write(raw) - So(<-chout, ShouldResemble, packet) - }) - - Convey("An invalid packet should raise an error", func() { - conn.Write([]byte("Invalid")) - So(<-cherr, ShouldNotBeNil) - }) + Convey("Given a valid started gateway instance bound to a router", t, func() { + Convey("Both channels should exist", func() { + So(cherr, ShouldNotBeNil) + So(chout, ShouldNotBeNil) + }) + + Convey("A connection should exist", func() { + So(gateway.routers[routerAddr], ShouldNotBeNil) + }) + + Convey("A valid packet should be forwarded", func() { + packet := semtech.Packet{ + Version: semtech.VERSION, + Token: []byte{0x1, 0x2}, + Identifier: semtech.PUSH_ACK, + } + raw, err := semtech.Marshal(&packet) + if err != nil { + t.Errorf("Unexpected error %+v\n", err) + return + } + conn.Write(raw) + So(<-chout, ShouldResemble, packet) + }) + + Convey("An invalid packet should raise an error", func() { + conn.Write([]byte("Invalid")) + So(<-cherr, ShouldNotBeNil) + }) + + Convey("It should panic if started one more time", func() { + So(func() { + gateway.Start() + }, ShouldPanic) + }) + }) + +} + +func TestStop(t *testing.T) { + gatewayId := "MyGateway" + routerAddr := "0.0.0.0:3000" + gateway, _ := New(gatewayId, routerAddr) + Convey("Given a gateway instance", t, func() { + Convey("It should panic if stopped while not started", func() { + So(func() { gateway.Stop() }, ShouldPanic) + }) + + Convey("It should stop correctly after having started", func() { + gateway.Start() + err := gateway.Stop() + + So(err, ShouldBeNil) + So(gateway.cherr, ShouldBeNil) + So(gateway.chout, ShouldBeNil) + So(gateway.routers[routerAddr], ShouldBeNil) }) }) } From 7e5adbfc6bf13c7c9a14f9974cfd378de809bc17 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 17:30:27 +0100 Subject: [PATCH 0070/2266] [simulators.gateway] Implement gateway.Stop() --- simulators/gateway/forwarder.go | 70 +++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 3ac8a8a86..009d77b2f 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -4,6 +4,8 @@ package gateway import ( + "errors" + "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" "net" ) @@ -11,45 +13,87 @@ import ( type Forwarder interface { Forward(packet semtech.Packet) Start() (<-chan semtech.Packet, <-chan error) + Stop() error Stat() semtech.Stat } +func handleError(cherr chan error, err error) { + fmt.Printf("\n[Gateway.Start()] %+v\n", err) + if cherr != nil { + cherr <- err + } +} + +// Start a Gateway. This will create several udp connections - one per associated router. +// Then, incoming streams of packets are merged into a single one. +// Same for errors. func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error) { - chout := make(chan semtech.Packet) - cherr := make(chan error) + if g.cherr != nil || g.chout != nil { + panic("Try to start a started gateway") + } - for _, router := range g.routers { + g.chout = make(chan semtech.Packet) + g.cherr = make(chan error) + + for router := range g.routers { addr, err := net.ResolveUDPAddr("udp", router) if err != nil { - cherr <- err + handleError(g.cherr, err) continue } go func() { conn, err := net.ListenUDP("udp", addr) if err != nil { - cherr <- err + handleError(g.cherr, err) return } + g.routers[router] = conn defer conn.Close() buf := make([]byte, 1024) - for { - n, _, err := conn.ReadFromUDP(buf) + for g.routers[router] != nil { + n, _, err := g.routers[router].ReadFromUDP(buf) if err != nil { - cherr <- err + handleError(g.cherr, err) continue } packet, err := semtech.Unmarshal(buf[:n]) if err != nil { - cherr <- err + handleError(g.cherr, err) continue } - chout <- *packet + if g.chout != nil { + g.chout <- *packet + } } }() } - g.chout = chout - g.cherr = cherr - return chout, cherr + return g.chout, g.cherr +} + +// Stop remove all previously created connection +func (g *Gateway) Stop() error { + if g.cherr == nil || g.chout == nil { + panic("Try to stop a non-started gateway") + } + + errs := make([]error, 0) + for router, conn := range g.routers { + if conn != nil { + if err := conn.Close(); err != nil { + errs = append(errs, err) + continue + } + g.routers[router] = nil + } + } + if len(errs) != 0 { + return errors.New("Unable to stop the gateway") + } + close(g.cherr) + close(g.chout) + g.cherr = nil + g.chout = nil + return nil } From 875ecd6ac9354ea8e90dc7354aa27e97a0035c72 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Dec 2015 17:30:41 +0100 Subject: [PATCH 0071/2266] [simulators.gateway] Adjust gateway struct and test --- simulators/gateway/gateway.go | 11 +++++++---- simulators/gateway/gateway_test.go | 10 ++++++++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 8cf7eafe9..7bdbedc1f 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -10,6 +10,7 @@ package gateway import ( "errors" "github.com/thethingsnetwork/core/lorawan/semtech" + "net" ) type Gateway struct { @@ -23,9 +24,9 @@ type Gateway struct { rxok uint // Number of radio packets received with a valid PHY CRC txnb uint // Number of packets emitted - routers []string // List of routers addresses - cherr chan error // Output error channel - chout chan semtech.Packet // Output communication channel + routers map[string]*net.UDPConn // List of routers addresses + cherr chan error // Output error channel + chout chan semtech.Packet // Output communication channel } type GPSCoord struct { @@ -44,8 +45,10 @@ func New(id string, routers ...string) (*Gateway, error) { } wrongAddress := false + connections := make(map[string]*net.UDPConn) for _, r := range routers { wrongAddress = wrongAddress || (r == "") + connections[r] = nil } if wrongAddress { @@ -59,7 +62,7 @@ func New(id string, routers ...string) (*Gateway, error) { latitude: 53.3702, longitude: 4.8952, }, - routers: routers, + routers: connections, }, nil } diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go index 517b36ff2..5dec646f4 100644 --- a/simulators/gateway/gateway_test.go +++ b/simulators/gateway/gateway_test.go @@ -5,6 +5,7 @@ package gateway import ( . "github.com/smartystreets/goconvey/convey" + "net" "testing" ) @@ -26,7 +27,9 @@ func TestNew(t *testing.T) { }) Convey("The list of configured routers should have been set correctly", func() { - So(gateway.routers, ShouldResemble, []string{router1}) + routers := make(map[string]*net.UDPConn) + routers[router1] = nil + So(gateway.routers, ShouldResemble, routers) }) }) @@ -42,7 +45,10 @@ func TestNew(t *testing.T) { }) Convey("The list of configured routers should have been set correctly", func() { - So(gateway.routers, ShouldResemble, []string{router1, router2}) + routers := make(map[string]*net.UDPConn) + routers[router1] = nil + routers[router2] = nil + So(gateway.routers, ShouldResemble, routers) }) }) From e8271d0fbbe726ddf236794fae1c993b8418427d Mon Sep 17 00:00:00 2001 From: Johan Stokking Date: Mon, 7 Dec 2015 23:44:18 +0100 Subject: [PATCH 0072/2266] Fixed AUTHORS link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ebea7211..01b978ee7 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ See the [development plan](DEVELOPMENT_PLAN.md) ## Authors -See the [author's list](AUTHORS.md) +See the [author's list](AUTHORS) ## License From 4151a8ae0b6778cd5d43d79ac03f9a9bf1397f4f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 16:33:57 +0100 Subject: [PATCH 0073/2266] [simulators.gateway] Refactor gateway and forwarder --- simulators/gateway/forwarder.go | 130 ++++++++++++++------------- simulators/gateway/forwarder_test.go | 44 ++++----- simulators/gateway/gateway.go | 27 +++--- simulators/gateway/gateway_test.go | 22 ++--- 4 files changed, 118 insertions(+), 105 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 009d77b2f..4a6f6cc7d 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -5,95 +5,101 @@ package gateway import ( "errors" - "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" "net" ) +const LISTENER_BUF_SIZE = 1024 + type Forwarder interface { Forward(packet semtech.Packet) - Start() (<-chan semtech.Packet, <-chan error) + Start() (<-chan semtech.Packet, <-chan error, error) Stop() error Stat() semtech.Stat } -func handleError(cherr chan error, err error) { - fmt.Printf("\n[Gateway.Start()] %+v\n", err) - if cherr != nil { - cherr <- err - } -} - // Start a Gateway. This will create several udp connections - one per associated router. // Then, incoming streams of packets are merged into a single one. // Same for errors. -func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error) { - if g.cherr != nil || g.chout != nil { - panic("Try to start a started gateway") +func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { + // Ensure not already started + if g.quit != nil { + return nil, nil, errors.New("Try to start a started gateway") } - g.chout = make(chan semtech.Packet) - g.cherr = make(chan error) - - for router := range g.routers { - addr, err := net.ResolveUDPAddr("udp", router) + // Open all UDP connections + connections := make([]*net.UDPConn, 0) + var err error + for _, addr := range g.routers { + conn, err := net.ListenUDP("udp", addr) if err != nil { - handleError(g.cherr, err) - continue + break } + connections = append(connections, conn) + } + + // On error, close all opened UDP connection and leave + if err != nil { + for _, conn := range connections { + conn.Close() + } + return nil, nil, err + } + + // Create communication channels and launch goroutines to handle connections + chout := make(chan semtech.Packet) + cherr := make(chan error) + quit := make(chan bool, len(g.routers)) + for _, conn := range connections { + go listen(conn, chout, cherr, quit) + } + + // Keep a reference to the quit channel, and return the others + g.quit = quit + return chout, cherr, nil +} - go func() { - conn, err := net.ListenUDP("udp", addr) +// listen materialize the goroutine handling incoming packet from routers +func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, quit chan bool) { + buf := make([]byte, LISTENER_BUF_SIZE) + for { + select { + case <-quit: + conn.Close() // Any chance this would return an error ? :/ + return + default: + n, _, err := conn.ReadFromUDP(buf) if err != nil { - handleError(g.cherr, err) - return + cherr <- err + continue } - g.routers[router] = conn - defer conn.Close() - buf := make([]byte, 1024) - for g.routers[router] != nil { - n, _, err := g.routers[router].ReadFromUDP(buf) - if err != nil { - handleError(g.cherr, err) - continue - } - packet, err := semtech.Unmarshal(buf[:n]) - if err != nil { - handleError(g.cherr, err) - continue - } - if g.chout != nil { - g.chout <- *packet - } + packet, err := semtech.Unmarshal(buf[:n]) + if err != nil { + cherr <- err + continue } - }() + chout <- *packet + } } - - return g.chout, g.cherr } -// Stop remove all previously created connection +// Stop remove all previously created connection. func (g *Gateway) Stop() error { - if g.cherr == nil || g.chout == nil { - panic("Try to stop a non-started gateway") + if g.quit == nil { + return errors.New("Try to stop a non-started gateway") } - errs := make([]error, 0) - for router, conn := range g.routers { - if conn != nil { - if err := conn.Close(); err != nil { - errs = append(errs, err) - continue - } - g.routers[router] = nil - } - } - if len(errs) != 0 { - return errors.New("Unable to stop the gateway") + for range g.routers { + g.quit <- true } - close(g.cherr) - close(g.chout) - g.cherr = nil - g.chout = nil + close(g.quit) + g.routers = make([]*net.UDPAddr, 0) + g.quit = nil return nil } + +// Forward transfer a packet to all known routers. +// It panics if the gateway hasn't been started beforehand. +func (g *Gateway) Forward(packet semtech.Packet) { + +} diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index a2646cb0f..1752d9144 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -8,34 +8,37 @@ import ( "github.com/thethingsnetwork/core/lorawan/semtech" "net" "testing" + "time" ) func TestStart(t *testing.T) { gatewayId := "MyGateway" routerAddr := "0.0.0.0:3000" gateway, _ := New(gatewayId, routerAddr) - chout, cherr := gateway.Start() + chout, cherr, err := gateway.Start() - udpAddr, err := net.ResolveUDPAddr("udp", routerAddr) - if err != nil { - t.Errorf("Unexpected error %+v\n", err) + udpAddr, e := net.ResolveUDPAddr("udp", routerAddr) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) return } - conn, err := net.DialUDP("udp", nil, udpAddr) - if err != nil { - t.Errorf("Unexpected error %+v\n", err) + conn, e := net.DialUDP("udp", nil, udpAddr) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) return } + defer conn.Close() Convey("Given a valid started gateway instance bound to a router", t, func() { Convey("Both channels should exist", func() { So(cherr, ShouldNotBeNil) So(chout, ShouldNotBeNil) + So(err, ShouldBeNil) }) Convey("A connection should exist", func() { - So(gateway.routers[routerAddr], ShouldNotBeNil) + So(len(gateway.routers), ShouldEqual, 1) }) Convey("A valid packet should be forwarded", func() { @@ -44,9 +47,9 @@ func TestStart(t *testing.T) { Token: []byte{0x1, 0x2}, Identifier: semtech.PUSH_ACK, } - raw, err := semtech.Marshal(&packet) - if err != nil { - t.Errorf("Unexpected error %+v\n", err) + raw, e := semtech.Marshal(&packet) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) return } conn.Write(raw) @@ -58,13 +61,13 @@ func TestStart(t *testing.T) { So(<-cherr, ShouldNotBeNil) }) - Convey("It should panic if started one more time", func() { - So(func() { - gateway.Start() - }, ShouldPanic) + Convey("It should fail if started one more time", func() { + _, _, err := gateway.Start() + So(err, ShouldNotBeNil) }) }) + gateway.Stop() } func TestStop(t *testing.T) { @@ -72,18 +75,19 @@ func TestStop(t *testing.T) { routerAddr := "0.0.0.0:3000" gateway, _ := New(gatewayId, routerAddr) Convey("Given a gateway instance", t, func() { - Convey("It should panic if stopped while not started", func() { - So(func() { gateway.Stop() }, ShouldPanic) + Convey("It should failed if stopped while not started", func() { + err := gateway.Stop() + So(err, ShouldNotBeNil) }) Convey("It should stop correctly after having started", func() { gateway.Start() + time.Sleep(time.Second) err := gateway.Stop() So(err, ShouldBeNil) - So(gateway.cherr, ShouldBeNil) - So(gateway.chout, ShouldBeNil) - So(gateway.routers[routerAddr], ShouldBeNil) + So(gateway.quit, ShouldBeNil) + So(len(gateway.routers), ShouldEqual, 0) }) }) } diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 7bdbedc1f..6256bddde 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -9,7 +9,7 @@ package gateway import ( "errors" - "github.com/thethingsnetwork/core/lorawan/semtech" + "fmt" "net" ) @@ -24,9 +24,8 @@ type Gateway struct { rxok uint // Number of radio packets received with a valid PHY CRC txnb uint // Number of packets emitted - routers map[string]*net.UDPConn // List of routers addresses - cherr chan error // Output error channel - chout chan semtech.Packet // Output communication channel + routers []*net.UDPAddr // List of routers addresses + quit chan bool // Communication channel to stop connections } type GPSCoord struct { @@ -44,15 +43,19 @@ func New(id string, routers ...string) (*Gateway, error) { return nil, errors.New("At least one router address should be provided") } - wrongAddress := false - connections := make(map[string]*net.UDPConn) - for _, r := range routers { - wrongAddress = wrongAddress || (r == "") - connections[r] = nil + addresses := make([]*net.UDPAddr, 0) + var err error + for _, router := range routers { + var addr *net.UDPAddr + addr, err = net.ResolveUDPAddr("udp", router) + if err != nil { + break + } + addresses = append(addresses, addr) } - if wrongAddress { - return nil, errors.New("Invalid router address") + if err != nil { + return nil, errors.New(fmt.Sprintf("Invalid router address. %v", err)) } return &Gateway{ @@ -62,7 +65,7 @@ func New(id string, routers ...string) (*Gateway, error) { latitude: 53.3702, longitude: 4.8952, }, - routers: connections, + routers: addresses, }, nil } diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go index 5dec646f4..1a1e56133 100644 --- a/simulators/gateway/gateway_test.go +++ b/simulators/gateway/gateway_test.go @@ -5,15 +5,14 @@ package gateway import ( . "github.com/smartystreets/goconvey/convey" - "net" "testing" ) func TestNew(t *testing.T) { Convey("The New method should return a valid gateway struct ready to use", t, func() { id := "qwerty" - router1 := "router1Addr" - router2 := "router2Addr" + router1 := "0.0.0.0:3000" + router2 := "0.0.0.0:1337" Convey("Given an identifier and a router address", func() { gateway, err := New(id, router1) @@ -21,15 +20,16 @@ func TestNew(t *testing.T) { Convey("No error should have been trown", func() { So(err, ShouldBeNil) }) + if err != nil { + return + } Convey("The identifier should have been set correctly", func() { So(gateway.Id, ShouldEqual, id) }) Convey("The list of configured routers should have been set correctly", func() { - routers := make(map[string]*net.UDPConn) - routers[router1] = nil - So(gateway.routers, ShouldResemble, routers) + So(len(gateway.routers), ShouldEqual, 1) }) }) @@ -39,16 +39,16 @@ func TestNew(t *testing.T) { Convey("No error should have been trown", func() { So(err, ShouldBeNil) }) + if err != nil { + return + } Convey("The identifier should have been set correctly", func() { So(gateway.Id, ShouldEqual, id) }) Convey("The list of configured routers should have been set correctly", func() { - routers := make(map[string]*net.UDPConn) - routers[router1] = nil - routers[router2] = nil - So(gateway.routers, ShouldResemble, routers) + So(len(gateway.routers), ShouldEqual, 2) }) }) @@ -66,7 +66,7 @@ func TestNew(t *testing.T) { }) Convey("It should return an error for an invalid router address", func() { - gateway, err := New(id, "") + gateway, err := New(id, "invalid") So(gateway, ShouldBeNil) So(err, ShouldNotBeNil) }) From 281f7a7857238f76f770dcd81d5e0ba1ecfb93ff Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 16:54:04 +0100 Subject: [PATCH 0074/2266] [simulators.gateway] Rewrite forward() tests --- simulators/gateway/forwarder_test.go | 55 ++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 1752d9144..76edfd21c 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -91,3 +91,58 @@ func TestStop(t *testing.T) { }) }) } + +func TestForward(t *testing.T) { + gatewayId := "MyGateway" + routerAddr1 := "0.0.0.0:3000" + routerAddr2 := "0.0.0.0:3001" + + gateway, _ := New(gatewayId, routerAddr1, routerAddr2) + chout, cherr, e := gateway.Start() + + if e != nil { + t.Errorf("Unexpected error %v", e) + return + } + + Convey("Given a started gateway bound to two routers", t, func() { + Convey("When forwarding a valid packet", func() { + packet := semtech.Packet{ + Version: semtech.VERSION, + Token: []byte{0x1, 0x2}, + Identifier: semtech.PUSH_ACK, + } + gateway.Forward(packet) + + Convey("It should be forwarded to both routers", func() { + var received semtech.Packet + select { + case received = <-chout: + case <-time.After(time.Second): + } + So(received, ShouldResemble, packet) + select { + case received = <-chout: + case <-time.After(time.Second): + } + So(received, ShouldResemble, packet) + }) + }) + + Convey("When forwarding an invalid packet", func() { + packet := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_ACK, + } + gateway.Forward(packet) + Convey("The gateway should trigger an error through the error chan", func() { + var err error + select { + case err = <-cherr: + case <-time.After(time.Second): + } + So(err, ShouldNotBeNil) + }) + }) + }) +} From 45888c1c180f00305f60a7542320cf9ee79a7c6f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 16:54:33 +0100 Subject: [PATCH 0075/2266] [simulators.gateway] Add timeout to channel calls --- simulators/gateway/forwarder_test.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 76edfd21c..fa1ef7dc7 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -53,12 +53,22 @@ func TestStart(t *testing.T) { return } conn.Write(raw) - So(<-chout, ShouldResemble, packet) + var received semtech.Packet + select { + case received = <-chout: + case <-time.After(time.Second): + } + So(received, ShouldResemble, packet) }) Convey("An invalid packet should raise an error", func() { conn.Write([]byte("Invalid")) - So(<-cherr, ShouldNotBeNil) + var err error + select { + case err = <-cherr: + case <-time.After(time.Second): + } + So(err, ShouldNotBeNil) }) Convey("It should fail if started one more time", func() { @@ -82,7 +92,7 @@ func TestStop(t *testing.T) { Convey("It should stop correctly after having started", func() { gateway.Start() - time.Sleep(time.Second) + time.Sleep(250 * time.Millisecond) err := gateway.Stop() So(err, ShouldBeNil) From 517b784fcf91fc572e32b4cf4d1cdfbd36c2ccc0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 18:16:26 +0100 Subject: [PATCH 0076/2266] [simulators.gateway] Fix forwarder tests --- simulators/gateway/forwarder_test.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index fa1ef7dc7..1aff7813d 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -78,6 +78,7 @@ func TestStart(t *testing.T) { }) gateway.Stop() + time.Sleep(250 * time.Millisecond) } func TestStop(t *testing.T) { @@ -91,15 +92,21 @@ func TestStop(t *testing.T) { }) Convey("It should stop correctly after having started", func() { - gateway.Start() - time.Sleep(250 * time.Millisecond) - err := gateway.Stop() + _, _, err := gateway.Start() + if err != nil { + t.Errorf("Unexpected error %v\n", err) + return + } + time.Sleep(200 * time.Millisecond) + err = gateway.Stop() So(err, ShouldBeNil) So(gateway.quit, ShouldBeNil) So(len(gateway.routers), ShouldEqual, 0) }) }) + + time.Sleep(250 * time.Millisecond) } func TestForward(t *testing.T) { @@ -108,7 +115,7 @@ func TestForward(t *testing.T) { routerAddr2 := "0.0.0.0:3001" gateway, _ := New(gatewayId, routerAddr1, routerAddr2) - chout, cherr, e := gateway.Start() + chout, _, e := gateway.Start() if e != nil { t.Errorf("Unexpected error %v", e) @@ -144,15 +151,11 @@ func TestForward(t *testing.T) { Version: semtech.VERSION, Identifier: semtech.PUSH_ACK, } - gateway.Forward(packet) - Convey("The gateway should trigger an error through the error chan", func() { - var err error - select { - case err = <-cherr: - case <-time.After(time.Second): - } + err := gateway.Forward(packet) + Convey("The gateway should return an error", func() { So(err, ShouldNotBeNil) }) }) }) + time.Sleep(250 * time.Millisecond) } From b6977e03f147481a89e57eb9df43974942549878 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 18:19:31 +0100 Subject: [PATCH 0077/2266] [simulators.gateway] Use of channel to handle udp connections (to avoid from blocking when listening) --- simulators/gateway/forwarder.go | 40 +++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 4a6f6cc7d..4cc8f3eb7 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -5,8 +5,10 @@ package gateway import ( "errors" + "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" "net" + "strings" ) const LISTENER_BUF_SIZE = 1024 @@ -31,7 +33,8 @@ func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { connections := make([]*net.UDPConn, 0) var err error for _, addr := range g.routers { - conn, err := net.ListenUDP("udp", addr) + var conn *net.UDPConn + conn, err = net.ListenUDP("udp", addr) if err != nil { break } @@ -61,26 +64,49 @@ func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { // listen materialize the goroutine handling incoming packet from routers func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, quit chan bool) { - buf := make([]byte, LISTENER_BUF_SIZE) + connIn, connErr := asChannel(conn) for { select { case <-quit: + close(connIn) + close(connErr) conn.Close() // Any chance this would return an error ? :/ return - default: - n, _, err := conn.ReadFromUDP(buf) + case buf := <-connIn: + packet, err := semtech.Unmarshal(buf) if err != nil { cherr <- err continue } - packet, err := semtech.Unmarshal(buf[:n]) + chout <- *packet + case err := <-connErr: + cherr <- err + } + } +} + +func asChannel(conn *net.UDPConn) (chan []byte, chan error) { + cherr := make(chan error) + chout := make(chan []byte) + go func() { + defer func() { + // The handling could be better here + recover() // In case we're writing a close channel. + }() + buf := make([]byte, LISTENER_BUF_SIZE) + for { + n, _, err := conn.ReadFromUDP(buf) if err != nil { + if strings.Contains(err.Error(), "use of closed network connection") { + return + } cherr <- err continue } - chout <- *packet + chout <- buf[:n] } - } + }() + return chout, cherr } // Stop remove all previously created connection. From d05d1ee956a1d6cdbb26150b480f01fdff6ca5cd Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 18:20:05 +0100 Subject: [PATCH 0078/2266] [simulators.gateway] Implement the forward() method --- simulators/gateway/forwarder.go | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 4cc8f3eb7..fc9de5302 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -14,7 +14,7 @@ import ( const LISTENER_BUF_SIZE = 1024 type Forwarder interface { - Forward(packet semtech.Packet) + Forward(packet semtech.Packet) error Start() (<-chan semtech.Packet, <-chan error, error) Stop() error Stat() semtech.Stat @@ -125,7 +125,32 @@ func (g *Gateway) Stop() error { } // Forward transfer a packet to all known routers. -// It panics if the gateway hasn't been started beforehand. -func (g *Gateway) Forward(packet semtech.Packet) { +// It fails if the gateway hasn't been started beforehand. +func (g *Gateway) Forward(packet semtech.Packet) error { + connections := make([]*net.UDPConn, 0) + var err error + for _, addr := range g.routers { + var conn *net.UDPConn + conn, err = net.DialUDP("udp", nil, addr) + if err != nil { + break + } + defer conn.Close() + connections = append(connections, conn) + } + raw, err := semtech.Marshal(&packet) + if err != nil { + return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) + } + + for _, conn := range connections { + _, err = conn.Write(raw) + } + + if err != nil { + return errors.New(fmt.Sprintf("Something went wrong during forwarding. %v\n", err)) + } + + return nil } From 859525fb792e897b5a63e02febfa96c301fc5f24 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 18:22:41 +0100 Subject: [PATCH 0079/2266] [simulators.gateway] Add justification comment for asChannel func --- simulators/gateway/forwarder.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index fc9de5302..8b61938d8 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -85,6 +85,11 @@ func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, } } +// as channel is actually a []byte generator that listen to a given udp connection. +// It is used to prevent the listen() function from blocking on ReadFromUDP() and then, +// still be available for a quit event that could come at any time. +// This function is thereby nothing more than a mapping of incoming connection to channels of +// communication. func asChannel(conn *net.UDPConn) (chan []byte, chan error) { cherr := make(chan error) chout := make(chan []byte) From b604e5342e76129716d0c67eebbed3c26391d938 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Dec 2015 23:40:44 +0100 Subject: [PATCH 0080/2266] [simulators.gateway] Remove unecessary sleep --- simulators/gateway/forwarder_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 1aff7813d..7fada4baa 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -78,7 +78,6 @@ func TestStart(t *testing.T) { }) gateway.Stop() - time.Sleep(250 * time.Millisecond) } func TestStop(t *testing.T) { @@ -105,8 +104,6 @@ func TestStop(t *testing.T) { So(len(gateway.routers), ShouldEqual, 0) }) }) - - time.Sleep(250 * time.Millisecond) } func TestForward(t *testing.T) { @@ -157,5 +154,4 @@ func TestForward(t *testing.T) { }) }) }) - time.Sleep(250 * time.Millisecond) } From 6f29eb224309f9802c1670fc1738e0c710d07c34 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Dec 2015 10:11:15 +0100 Subject: [PATCH 0081/2266] [simulators.gateway] Make things prettier in forwarder asChannel() --- simulators/gateway/forwarder.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 8b61938d8..46fa59c51 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -9,9 +9,13 @@ import ( "github.com/thethingsnetwork/core/lorawan/semtech" "net" "strings" + "time" ) -const LISTENER_BUF_SIZE = 1024 +const ( + LISTENER_BUF_SIZE = 1024 + LISTENER_TIMEOUT = 4 * time.Second +) type Forwarder interface { Forward(packet semtech.Packet) error @@ -68,8 +72,6 @@ func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, for { select { case <-quit: - close(connIn) - close(connErr) conn.Close() // Any chance this would return an error ? :/ return case buf := <-connIn: @@ -90,14 +92,10 @@ func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, // still be available for a quit event that could come at any time. // This function is thereby nothing more than a mapping of incoming connection to channels of // communication. -func asChannel(conn *net.UDPConn) (chan []byte, chan error) { +func asChannel(conn *net.UDPConn) (<-chan []byte, <-chan error) { cherr := make(chan error) chout := make(chan []byte) go func() { - defer func() { - // The handling could be better here - recover() // In case we're writing a close channel. - }() buf := make([]byte, LISTENER_BUF_SIZE) for { n, _, err := conn.ReadFromUDP(buf) @@ -105,10 +103,22 @@ func asChannel(conn *net.UDPConn) (chan []byte, chan error) { if strings.Contains(err.Error(), "use of closed network connection") { return } - cherr <- err - continue + select { + case cherr <- err: + continue + case <-time.After(LISTENER_TIMEOUT): + close(cherr) + close(chout) + return + } + } + select { + case chout <- buf[:n]: + case <-time.After(LISTENER_TIMEOUT): + close(cherr) + close(chout) + return } - chout <- buf[:n] } }() return chout, cherr From 84266f8f853b44d39af7c2c02e15b000e1c2944e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Dec 2015 12:05:35 +0100 Subject: [PATCH 0082/2266] [simulators.gateway] Change Marshal signature --- lorawan/semtech/encode.go | 2 +- lorawan/semtech/encode_test.go | 70 ++++++++++++++++----------------- simulators/gateway/forwarder.go | 2 +- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/lorawan/semtech/encode.go b/lorawan/semtech/encode.go index 638ebcb75..3135ac93b 100644 --- a/lorawan/semtech/encode.go +++ b/lorawan/semtech/encode.go @@ -10,7 +10,7 @@ import ( ) // Marshal transforms a packet to a sequence of bytes. -func Marshal(packet *Packet) ([]byte, error) { +func Marshal(packet Packet) ([]byte, error) { raw := append(make([]byte, 0), packet.Version) if len(packet.Token) != 2 { diff --git a/lorawan/semtech/encode_test.go b/lorawan/semtech/encode_test.go index 3d00a625d..d8d133e62 100644 --- a/lorawan/semtech/encode_test.go +++ b/lorawan/semtech/encode_test.go @@ -14,10 +14,10 @@ import ( ) // ----------------------------------------------------------------- -// ------------------------- Marshal (packet *Packet) ([]byte, error) +// ------------------------- Marshal (packet Packet) ([]byte, error) // ----------------------------------------------------------------- // ---------- PUSH_DATA -func checkMarshalPUSH_DATA(packet *Packet, payload []byte) error { +func checkMarshalPUSH_DATA(packet Packet, payload []byte) error { raw, err := Marshal(packet) if err != nil { @@ -71,7 +71,7 @@ func TestMarshalPUSH_DATA1(t *testing.T) { // } payload, err := ioutil.ReadFile("./test_data/marshal_stat") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_DATA, @@ -141,7 +141,7 @@ func TestMarshalPUSH_DATA2(t *testing.T) { return } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_DATA, @@ -242,7 +242,7 @@ func TestMarshalPUSH_DATA3(t *testing.T) { return } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_DATA, @@ -305,7 +305,7 @@ func TestMarshalPUSH_DATA4(t *testing.T) { // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_DATA, @@ -338,7 +338,7 @@ func TestMarshalPUSH_DATA5(t *testing.T) { // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_DATA, @@ -371,7 +371,7 @@ func TestMarshalPUSH_DATA6(t *testing.T) { // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA}, Identifier: PUSH_DATA, @@ -404,7 +404,7 @@ func TestMarshalPUSH_DATA7(t *testing.T) { // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} payload, err := ioutil.ReadFile("./test_data/marshal_stat") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14, 0x28}, Identifier: PUSH_DATA, @@ -431,7 +431,7 @@ func TestMarshalPUSH_DATA7(t *testing.T) { } // ---------- PUSH_ACK -func checkMarshalACK(packet *Packet) error { +func checkMarshalACK(packet Packet) error { raw, err := Marshal(packet) if err != nil { @@ -459,7 +459,7 @@ func checkMarshalACK(packet *Packet) error { // Marshal a basic push_ack packet func TestMarshalPUSH_ACK1(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_ACK, @@ -473,7 +473,7 @@ func TestMarshalPUSH_ACK1(t *testing.T) { // Marshal a push_ack packet with extra useless gatewayId func TestMarshalPUSH_ACK2(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_ACK, @@ -494,7 +494,7 @@ func TestMarshalPUSH_ACK3(t *testing.T) { Rxok: pointer.Uint(14), }, } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_ACK, @@ -515,7 +515,7 @@ func TestMarshalPUSH_ACK4(t *testing.T) { Rxok: pointer.Uint(14), }, } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PUSH_ACK, @@ -529,7 +529,7 @@ func TestMarshalPUSH_ACK4(t *testing.T) { // Marshal a push_ack with an invalid token (too short) func TestMarshalPUSH_ACK5(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA}, Identifier: PUSH_ACK, @@ -544,7 +544,7 @@ func TestMarshalPUSH_ACK5(t *testing.T) { // Marshal a push_ack with an invalid token (too long) func TestMarshalPUSH_ACK6(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0x9A, 0x7A, 0x7E}, Identifier: PUSH_ACK, @@ -558,7 +558,7 @@ func TestMarshalPUSH_ACK6(t *testing.T) { } // ---------- PULL_DATA -func checkMarshalPULL_DATA(packet *Packet) error { +func checkMarshalPULL_DATA(packet Packet) error { raw, err := Marshal(packet) if err != nil { @@ -590,7 +590,7 @@ func checkMarshalPULL_DATA(packet *Packet) error { // Marshal a basic pull_data packet func TestMarshalPULL_DATA1(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_DATA, @@ -611,7 +611,7 @@ func TestMarshalPULL_DATA2(t *testing.T) { Rxok: pointer.Uint(14), }, } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_DATA, @@ -625,7 +625,7 @@ func TestMarshalPULL_DATA2(t *testing.T) { // Marshal a pull_data packet with an invalid token (too short) func TestMarshalPULL_DATA3(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA}, Identifier: PULL_DATA, @@ -639,7 +639,7 @@ func TestMarshalPULL_DATA3(t *testing.T) { // Marshal a pull_data packet with an invalid token (too long) func TestMarshalPULL_DATA4(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14, 0x42}, Identifier: PULL_DATA, @@ -653,7 +653,7 @@ func TestMarshalPULL_DATA4(t *testing.T) { // Marshal a pull_data packet with an invalid gatewayId (too short) func TestMarshalPULL_DATA5(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_DATA, @@ -667,7 +667,7 @@ func TestMarshalPULL_DATA5(t *testing.T) { // Marshal a pull_data packet with an invalid gatewayId (too long) func TestMarshalPULL_DATA6(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_DATA, @@ -682,7 +682,7 @@ func TestMarshalPULL_DATA6(t *testing.T) { // ---------- PULL_ACK // Marshal a basic pull_ack packet func TestMarshalPULL_ACK1(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_ACK, @@ -696,7 +696,7 @@ func TestMarshalPULL_ACK1(t *testing.T) { // Marshal a pull_ack packet with extra useless gatewayId func TestMarshalPULL_ACK2(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_ACK, @@ -717,7 +717,7 @@ func TestMarshalPULL_ACK3(t *testing.T) { Rxok: pointer.Uint(14), }, } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_ACK, @@ -738,7 +738,7 @@ func TestMarshalPULL_ACK4(t *testing.T) { Rxok: pointer.Uint(14), }, } - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_ACK, @@ -752,7 +752,7 @@ func TestMarshalPULL_ACK4(t *testing.T) { // Marshal a pull_ack with an invalid token (too short) func TestMarshalPULL_ACK5(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA}, Identifier: PULL_ACK, @@ -767,7 +767,7 @@ func TestMarshalPULL_ACK5(t *testing.T) { // Marshal a pull_ack with an invalid token (too long) func TestMarshalPULL_ACK6(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0x9A, 0x7A, 0x7E}, Identifier: PULL_ACK, @@ -781,7 +781,7 @@ func TestMarshalPULL_ACK6(t *testing.T) { } // ---------- PULL_RESP -func checkMarshalPULL_RESP(packet *Packet, payload []byte) error { +func checkMarshalPULL_RESP(packet Packet, payload []byte) error { raw, err := Marshal(packet) if err != nil { @@ -813,7 +813,7 @@ func checkMarshalPULL_RESP(packet *Packet, payload []byte) error { // Marshal() for a basic PULL_RESP packet with no payload func TestMarshallPULL_RESP1(t *testing.T) { - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_RESP, @@ -845,7 +845,7 @@ func TestMarshallPULL_RESP2(t *testing.T) { // } payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_RESP, @@ -876,7 +876,7 @@ func TestMarshallPULL_RESP3(t *testing.T) { //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14}, Identifier: PULL_RESP, @@ -907,7 +907,7 @@ func TestMarshallPULL_RESP4(t *testing.T) { //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA}, Identifier: PULL_RESP, @@ -938,7 +938,7 @@ func TestMarshallPULL_RESP5(t *testing.T) { //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - packet := &Packet{ + packet := Packet{ Version: VERSION, Token: []byte{0xAA, 0x14, 0x42}, Identifier: PULL_RESP, diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 46fa59c51..77231cb77 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -153,7 +153,7 @@ func (g *Gateway) Forward(packet semtech.Packet) error { defer conn.Close() connections = append(connections, conn) } - raw, err := semtech.Marshal(&packet) + raw, err := semtech.Marshal(packet) if err != nil { return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) From 56b327ac33a666af99f84df20d44ede7dcffcea9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 11:25:46 +0100 Subject: [PATCH 0083/2266] [simulators.gateway] Change gateway signatures a bit --- simulators/gateway/forwarder_test.go | 9 ++++--- simulators/gateway/gateway.go | 40 +++++++++++----------------- simulators/gateway/gateway_test.go | 8 +++--- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 7fada4baa..66334386e 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -12,7 +12,7 @@ import ( ) func TestStart(t *testing.T) { - gatewayId := "MyGateway" + gatewayId := []byte("MyGateway")[:8] routerAddr := "0.0.0.0:3000" gateway, _ := New(gatewayId, routerAddr) chout, cherr, err := gateway.Start() @@ -47,7 +47,7 @@ func TestStart(t *testing.T) { Token: []byte{0x1, 0x2}, Identifier: semtech.PUSH_ACK, } - raw, e := semtech.Marshal(&packet) + raw, e := semtech.Marshal(packet) if e != nil { t.Errorf("Unexpected error %+v\n", e) return @@ -81,7 +81,7 @@ func TestStart(t *testing.T) { } func TestStop(t *testing.T) { - gatewayId := "MyGateway" + gatewayId := []byte("MyGateway")[:8] routerAddr := "0.0.0.0:3000" gateway, _ := New(gatewayId, routerAddr) Convey("Given a gateway instance", t, func() { @@ -107,7 +107,7 @@ func TestStop(t *testing.T) { } func TestForward(t *testing.T) { - gatewayId := "MyGateway" + gatewayId := []byte("MyGateway")[:8] routerAddr1 := "0.0.0.0:3000" routerAddr2 := "0.0.0.0:3001" @@ -118,6 +118,7 @@ func TestForward(t *testing.T) { t.Errorf("Unexpected error %v", e) return } + defer gateway.Stop() Convey("Given a started gateway bound to two routers", t, func() { Convey("When forwarding a valid packet", func() { diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 6256bddde..064020348 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -14,28 +14,22 @@ import ( ) type Gateway struct { - Coord GPSCoord // Gateway's GPS coordinates - Id string // Gateway's Identifier - - ackr float64 // Percentage of upstream datagrams that were acknowledged - dwnb uint // Number of downlink datagrams received - rxfw uint // Number of radio packets forwarded - rxnb uint // Number of radio packets received - rxok uint // Number of radio packets received with a valid PHY CRC - txnb uint // Number of packets emitted - + Id []byte // Gateway's Identifier + alti int // GPS altitude in RX meters + ackr uint // Number of upstream datagrams that were acknowledged + dwnb uint // Number of downlink datagrams received + lati float64 // GPS latitude, North is + + long float64 // GPS longitude, East is + + rxfw uint // Number of radio packets forwarded + rxnb uint // Number of radio packets received + txnb uint // Number of packets emitted routers []*net.UDPAddr // List of routers addresses quit chan bool // Communication channel to stop connections } -type GPSCoord struct { - altitude int // GPS altitude in RX meters - latitude float64 // GPS latitude, North is + - longitude float64 // GPS longitude, East is + -} - -func New(id string, routers ...string) (*Gateway, error) { - if id == "" { +// New create a new gateway from a given id and a list of router addresses +func New(id []byte, routers ...string) (*Gateway, error) { + if len(id) != 8 { return nil, errors.New("Invalid gateway id provided") } @@ -59,12 +53,10 @@ func New(id string, routers ...string) (*Gateway, error) { } return &Gateway{ - Id: id, - Coord: GPSCoord{ - altitude: 120, // TEMPORARY - latitude: 53.3702, - longitude: 4.8952, - }, + Id: id, + alti: 120, + lati: 53.3702, + long: 4.8952, routers: addresses, }, nil } diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go index 1a1e56133..11fe2fdca 100644 --- a/simulators/gateway/gateway_test.go +++ b/simulators/gateway/gateway_test.go @@ -10,7 +10,7 @@ import ( func TestNew(t *testing.T) { Convey("The New method should return a valid gateway struct ready to use", t, func() { - id := "qwerty" + id := []byte("qwerty")[:8] router1 := "0.0.0.0:3000" router2 := "0.0.0.0:1337" @@ -25,7 +25,7 @@ func TestNew(t *testing.T) { } Convey("The identifier should have been set correctly", func() { - So(gateway.Id, ShouldEqual, id) + So(gateway.Id, ShouldResemble, id) }) Convey("The list of configured routers should have been set correctly", func() { @@ -44,7 +44,7 @@ func TestNew(t *testing.T) { } Convey("The identifier should have been set correctly", func() { - So(gateway.Id, ShouldEqual, id) + So(gateway.Id, ShouldResemble, id) }) Convey("The list of configured routers should have been set correctly", func() { @@ -54,7 +54,7 @@ func TestNew(t *testing.T) { Convey("Given a bad identifier and/or bad router addresses", func() { Convey("It should return an error for an empty id", func() { - gateway, err := New("", router1) + gateway, err := New(make([]byte, 0), router1) So(gateway, ShouldBeNil) So(err, ShouldNotBeNil) }) From 07729db846b3d038608e6d91f9905c03c99021a5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 11:27:34 +0100 Subject: [PATCH 0084/2266] [simulators.gateway] Write gateway.Stats() tests --- simulators/gateway/gateway_test.go | 161 +++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go index 11fe2fdca..56d7c8d03 100644 --- a/simulators/gateway/gateway_test.go +++ b/simulators/gateway/gateway_test.go @@ -5,6 +5,8 @@ package gateway import ( . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core/lorawan/semtech" + "net" "testing" ) @@ -73,3 +75,162 @@ func TestNew(t *testing.T) { }) }) } + +func TestStat(t *testing.T) { + id := []byte("gatewayId")[:8] + routerAddr := "0.0.0.0:3000" + gateway, e := New(id, routerAddr) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) + return + } + + addr, e := net.ResolveUDPAddr("udp", routerAddr) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) + return + } + + conn, e := net.DialUDP("udp", nil, addr) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) + return + } + defer conn.Close() + + Convey("Given a non started gateway", t, func() { + stats := gateway.Stats() + Convey("The stats should be equal to default values", func() { + So(*stats.Ackr, ShouldEqual, 0.0) + So(*stats.Dwnb, ShouldEqual, 0) + So(*stats.Rxfw, ShouldEqual, 0) + So(*stats.Rxnb, ShouldEqual, 0) + So(*stats.Rxok, ShouldEqual, 0) + So(*stats.Txnb, ShouldEqual, 0) + }) + }) + + _, _, e = gateway.Start() + if e != nil { + t.Errorf("Unexpected error %+v\n", e) + return + } + defer gateway.Stop() + + Convey("Given a fresh gateway (just started)", t, func() { + stats := gateway.Stats() + Convey("The stats should be equal to default values", func() { + So(*stats.Ackr, ShouldEqual, 0.0) + So(*stats.Dwnb, ShouldEqual, 0) + So(*stats.Rxfw, ShouldEqual, 0) + So(*stats.Rxnb, ShouldEqual, 0) + So(*stats.Rxok, ShouldEqual, 0) + So(*stats.Txnb, ShouldEqual, 0) + So(stats.Time, ShouldNotBeNil) + So(stats.Lati, ShouldNotBeNil) + So(stats.Long, ShouldNotBeNil) + So(stats.Alti, ShouldNotBeNil) + }) + + Convey("After having received a valid downlink packet", func() { + raw, e := semtech.Marshal(semtech.Packet{ + Version: semtech.VERSION, + Token: genToken(), + Identifier: semtech.PUSH_ACK, + }) + if e != nil { + t.Errorf("Unexpected error %+v\n") + return + } + conn.Write(raw) + + Convey("The downlink packet number should have been incremented", func() { + dwnb := *gateway.Stats().Dwnb + So(dwnb, ShouldEqual, *stats.Dwnb+1) + }) + }) + + Convey("After having received an invalid downlink packet", func() { + raw, e := semtech.Marshal(semtech.Packet{ + Version: semtech.VERSION, + Token: genToken(), + Identifier: semtech.PUSH_ACK, + }) + if e != nil { + t.Errorf("Unexpected error %+v\n") + return + } + conn.Write(raw) + Convey("The downlink packet number should have been incremented", func() { + dwnb := *gateway.Stats().Dwnb + So(dwnb, ShouldEqual, *stats.Dwnb+1) + }) + }) + + Convey("After having forwarded a valid radio packet", func() { + packet := semtech.Packet{ + Version: semtech.VERSION, + Token: genToken(), + Identifier: semtech.PULL_DATA, + GatewayId: gateway.Id, + } + e := gateway.Forward(packet) + if e != nil { + t.Errorf("Unexpected error %+v\n", e) + return + } + Convey("The number of packets forwarded should have been incremented", func() { + rxfw := *gateway.Stats().Rxfw + So(rxfw, ShouldEqual, *stats.Rxfw+1) + }) + + Convey("The number of packets received should have been incremented", func() { + rxnb := *gateway.Stats().Rxnb + So(rxnb, ShouldEqual, *stats.Rxnb+1) + }) + + Convey("The number of packets received with a valid PHY CRC should have been incremented", func() { + rxok := *gateway.Stats().Rxok + So(rxok, ShouldEqual, *stats.Rxok+1) + }) + + Convey("The number of packets emitted should have been incremented", func() { + txnb := *gateway.Stats().Txnb + So(txnb, ShouldEqual, *stats.Txnb+uint(len(gateway.routers))) + }) + }) + + Convey("After having forwarded an invalid radio packet", func() { + packet := semtech.Packet{ + Version: semtech.VERSION, + Token: genToken(), + Identifier: semtech.PULL_DATA, + GatewayId: gateway.Id[:4], // Invalid Gateway id + } + e := gateway.Forward(packet) + if e == nil { + t.Errorf("An error was expected") + return + } + Convey("The number of packets forwarded shouldn't have moved", func() { + rxfw := *gateway.Stats().Rxfw + So(rxfw, ShouldEqual, *stats.Rxfw) + }) + + Convey("The number of packets received should have been incremented", func() { + rxnb := *gateway.Stats().Rxnb + So(rxnb, ShouldEqual, *stats.Rxnb+1) + }) + + Convey("The number of packets received with a valid PHY CRC should have been incremented", func() { + rxok := *gateway.Stats().Rxok + So(rxok, ShouldEqual, *stats.Rxok+1) + }) + + Convey("The number of packets emitted shouldn't have moved", func() { + txnb := *gateway.Stats().Txnb + So(txnb, ShouldEqual, *stats.Txnb) + }) + }) + }) +} From f5f5699280156737cc889157cd521b79c2aacbce Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 11:28:11 +0100 Subject: [PATCH 0085/2266] [simulators.gateway] Write fake temporary implementation of gateway.Stats. Tests don't pass, obviously --- simulators/gateway/forwarder.go | 1 - simulators/gateway/gateway.go | 19 ++++++ utils/pointer/pointer.go | 112 +++++++++++++++++--------------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 77231cb77..174925ca4 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -21,7 +21,6 @@ type Forwarder interface { Forward(packet semtech.Packet) error Start() (<-chan semtech.Packet, <-chan error, error) Stop() error - Stat() semtech.Stat } // Start a Gateway. This will create several udp connections - one per associated router. diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 064020348..5d09948b5 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -10,7 +10,10 @@ package gateway import ( "errors" "fmt" + "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/pointer" "net" + "time" ) type Gateway struct { @@ -61,6 +64,22 @@ func New(id []byte, routers ...string) (*Gateway, error) { }, nil } +// Stats return the gateway usage statistics computed along its lifecycle +func (g Gateway) Stats() semtech.Stat { + return semtech.Stat{ + Ackr: pointer.Float64(0.0), //pointer.Float64(float64(g.ackr) / float64(g.txnb)), + Alti: pointer.Int(g.alti), + Dwnb: pointer.Uint(g.dwnb), + Lati: pointer.Float64(g.lati), + Long: pointer.Float64(g.long), + Rxfw: pointer.Uint(g.rxfw), + Rxnb: pointer.Uint(g.rxnb), + Rxok: pointer.Uint(g.rxnb), + Time: pointer.Time(time.Now()), + Txnb: pointer.Uint(g.txnb), + } +} + type Imitator interface { Mimic() } diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index de36dcfae..79a4d1563 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -5,9 +5,9 @@ package pointer import ( - "fmt" - "reflect" - "time" + "fmt" + "reflect" + "time" ) // String creates a pointer to a string from a string value @@ -45,59 +45,65 @@ func Bool(v bool) *bool { return p } +// Time creates a pointer to a time.Time from a time.Time value +func Time(v time.Time) *time.Time { + p := new(time.Time) + *p = v + return p +} + // DumpStruct prints the content of a struct of pointers func DumpPStruct(s interface{}) { - v := reflect.ValueOf(s) + v := reflect.ValueOf(s) - if v.Kind() != reflect.Struct { - fmt.Printf("Unable to dump: Not a struct.") - return - } + if v.Kind() != reflect.Struct { + fmt.Printf("Unable to dump: Not a struct.") + return + } - for k := 0; k < v.NumField(); k += 1 { - i := v.Field(k).Interface() - fmt.Printf("%v: ", v.Type().Field(k).Name) + for k := 0; k < v.NumField(); k += 1 { + i := v.Field(k).Interface() + fmt.Printf("%v: ", v.Type().Field(k).Name) - switch t := i.(type) { - case *bool: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) - } - case *int: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) - } - case *uint: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) - } - case *string: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) - } - case *float64: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) - } - case *time.Time: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) - } - default: - fmt.Printf("unknown\n") - } - } + switch t := i.(type) { + case *bool: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *int: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *uint: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *string: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *float64: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + case *time.Time: + if t == nil { + fmt.Printf("nil\n") + } else { + fmt.Printf("%+v\n", *t) + } + default: + fmt.Printf("unknown\n") + } + } } - From c039987395046061430d9ae2a5f07af00255c477 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 16:29:53 +0100 Subject: [PATCH 0086/2266] [simulators.gateway] Add a second router to gateway.Stats() tests --- simulators/gateway/gateway_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go index 56d7c8d03..cae3c324d 100644 --- a/simulators/gateway/gateway_test.go +++ b/simulators/gateway/gateway_test.go @@ -79,7 +79,8 @@ func TestNew(t *testing.T) { func TestStat(t *testing.T) { id := []byte("gatewayId")[:8] routerAddr := "0.0.0.0:3000" - gateway, e := New(id, routerAddr) + routerAddr2 := "0.0.0.0:3001" + gateway, e := New(id, routerAddr, routerAddr2) if e != nil { t.Errorf("Unexpected error %+v\n", e) return From 2ec7503460130d00d38b14df459bd66826695c16 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 16:30:25 +0100 Subject: [PATCH 0087/2266] [simulators.gateway] Fix gateway.Stop() not being called in case of error in forwarder tests --- simulators/gateway/forwarder_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 66334386e..034f978f4 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -16,6 +16,7 @@ func TestStart(t *testing.T) { routerAddr := "0.0.0.0:3000" gateway, _ := New(gatewayId, routerAddr) chout, cherr, err := gateway.Start() + defer gateway.Stop() udpAddr, e := net.ResolveUDPAddr("udp", routerAddr) if e != nil { @@ -76,8 +77,6 @@ func TestStart(t *testing.T) { So(err, ShouldNotBeNil) }) }) - - gateway.Stop() } func TestStop(t *testing.T) { From 2403b2c189ea1be39922a709842d46fb33747571 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 16:32:46 +0100 Subject: [PATCH 0088/2266] [simulators.gateway] Fix deadlocks goroutines in forwarder. Take advantage of select + nil channels in Go. --- simulators/gateway/forwarder.go | 46 ++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 174925ca4..ba8c42b50 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -55,7 +55,7 @@ func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { // Create communication channels and launch goroutines to handle connections chout := make(chan semtech.Packet) cherr := make(chan error) - quit := make(chan bool, len(g.routers)) + quit := make(chan chan error) for _, conn := range connections { go listen(conn, chout, cherr, quit) } @@ -66,22 +66,43 @@ func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { } // listen materialize the goroutine handling incoming packet from routers -func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, quit chan bool) { +func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, quit <-chan chan error) { connIn, connErr := asChannel(conn) + errBuf := make([]error, 0) + outBuf := make([]semtech.Packet, 0) for { + var safeChout chan<- semtech.Packet + var packet semtech.Packet + if len(outBuf) > 0 { + safeChout = chout + packet = outBuf[0] + } + + var safeCherr chan<- error + var err error + if len(errBuf) > 0 { + safeCherr = cherr + err = errBuf[0] + } + select { - case <-quit: - conn.Close() // Any chance this would return an error ? :/ + case ack := <-quit: // quit event, the gateway is stoppped + e := conn.Close() + ack <- e return - case buf := <-connIn: + case buf := <-connIn: // connIn event, a packet has been received by the listener goroutine packet, err := semtech.Unmarshal(buf) if err != nil { - cherr <- err + errBuf = append(errBuf, err) continue } - chout <- *packet - case err := <-connErr: - cherr <- err + outBuf = append(outBuf, *packet) + case safeChout <- packet: // emit an available packet to chout + outBuf = outBuf[1:] + case err := <-connErr: // connErr event, an error has been triggered by the listener goroutine + errBuf = append(errBuf, err) + case safeCherr <- err: // emit an existing error to cherr + errBuf = errBuf[1:] } } } @@ -130,7 +151,12 @@ func (g *Gateway) Stop() error { } for range g.routers { - g.quit <- true + errc := make(chan error) + g.quit <- errc + err := <-errc + if err != nil { + fmt.Printf("%+v\n", err) + } } close(g.quit) g.routers = make([]*net.UDPAddr, 0) From 5ec03c4920bdbb04372ffde377870bff85fbec48 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 16:33:28 +0100 Subject: [PATCH 0089/2266] [simulators.gateway] Implement gateway.Stats() function --- simulators/gateway/forwarder.go | 38 ++++++++++++++++++++++++++++--- simulators/gateway/gateway.go | 40 +++++++++++++++++++++++---------- 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index ba8c42b50..05cb62da6 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -56,17 +56,20 @@ func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { chout := make(chan semtech.Packet) cherr := make(chan error) quit := make(chan chan error) + cmd := make(chan command) + go reduceCmd(g, cmd) for _, conn := range connections { - go listen(conn, chout, cherr, quit) + go listen(conn, chout, cherr, cmd, quit) } // Keep a reference to the quit channel, and return the others g.quit = quit + g.cmd = cmd return chout, cherr, nil } // listen materialize the goroutine handling incoming packet from routers -func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, quit <-chan chan error) { +func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, cmd chan<- command, quit <-chan chan error) { connIn, connErr := asChannel(conn) errBuf := make([]error, 0) outBuf := make([]semtech.Packet, 0) @@ -91,6 +94,7 @@ func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, ack <- e return case buf := <-connIn: // connIn event, a packet has been received by the listener goroutine + cmd <- cmd_RECD_PACKET packet, err := semtech.Unmarshal(buf) if err != nil { errBuf = append(errBuf, err) @@ -144,9 +148,27 @@ func asChannel(conn *net.UDPConn) (<-chan []byte, <-chan error) { return chout, cherr } +// reduceCmd handle all updates made on the gateway statistics +func reduceCmd(gateway *Gateway, commands <-chan command) { + for command := range commands { + switch command { + case cmd_ACKN_PACKET: + gateway.ackr += 1 + case cmd_EMIT_PACKET: + gateway.txnb += 1 + case cmd_FORW_PACKET: + gateway.rxfw += 1 + case cmd_RECU_PACKET: + gateway.rxnb += 1 + case cmd_RECD_PACKET: + gateway.dwnb += 1 + } + } +} + // Stop remove all previously created connection. func (g *Gateway) Stop() error { - if g.quit == nil { + if g.quit == nil || g.cmd == nil { return errors.New("Try to stop a non-started gateway") } @@ -159,6 +181,7 @@ func (g *Gateway) Stop() error { } } close(g.quit) + close(g.cmd) g.routers = make([]*net.UDPAddr, 0) g.quit = nil return nil @@ -167,6 +190,12 @@ func (g *Gateway) Stop() error { // Forward transfer a packet to all known routers. // It fails if the gateway hasn't been started beforehand. func (g *Gateway) Forward(packet semtech.Packet) error { + if g.quit == nil || g.cmd == nil { + return errors.New("Unable to forward on a non-started gateway") + } + + g.cmd <- cmd_RECU_PACKET + connections := make([]*net.UDPConn, 0) var err error for _, addr := range g.routers { @@ -184,8 +213,11 @@ func (g *Gateway) Forward(packet semtech.Packet) error { return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) } + g.cmd <- cmd_FORW_PACKET + for _, conn := range connections { _, err = conn.Write(raw) + g.cmd <- cmd_EMIT_PACKET } if err != nil { diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 5d09948b5..0da1ed63f 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -17,19 +17,30 @@ import ( ) type Gateway struct { - Id []byte // Gateway's Identifier - alti int // GPS altitude in RX meters - ackr uint // Number of upstream datagrams that were acknowledged - dwnb uint // Number of downlink datagrams received - lati float64 // GPS latitude, North is + - long float64 // GPS longitude, East is + - rxfw uint // Number of radio packets forwarded - rxnb uint // Number of radio packets received - txnb uint // Number of packets emitted - routers []*net.UDPAddr // List of routers addresses - quit chan bool // Communication channel to stop connections + Id []byte // Gateway's Identifier + alti int // GPS altitude in RX meters + ackr uint // Number of upstream datagrams that were acknowledged + dwnb uint // Number of downlink datagrams received + lati float64 // GPS latitude, North is + + long float64 // GPS longitude, East is + + rxfw uint // Number of radio packets forwarded + rxnb uint // Number of radio packets received + txnb uint // Number of packets emitted + routers []*net.UDPAddr // List of routers addresses + quit chan chan error // Communication channel to stop connections + cmd chan command // Internal channel use to update gateway statistics } +type command uint + +const ( + cmd_ACKN_PACKET = iota + cmd_EMIT_PACKET + cmd_FORW_PACKET + cmd_RECU_PACKET + cmd_RECD_PACKET +) + // New create a new gateway from a given id and a list of router addresses func New(id []byte, routers ...string) (*Gateway, error) { if len(id) != 8 { @@ -66,8 +77,13 @@ func New(id []byte, routers ...string) (*Gateway, error) { // Stats return the gateway usage statistics computed along its lifecycle func (g Gateway) Stats() semtech.Stat { + var ackr float64 + if g.txnb != 0 { + ackr = float64(g.ackr) / float64(g.txnb) + } + return semtech.Stat{ - Ackr: pointer.Float64(0.0), //pointer.Float64(float64(g.ackr) / float64(g.txnb)), + Ackr: &ackr, Alti: pointer.Int(g.alti), Dwnb: pointer.Uint(g.dwnb), Lati: pointer.Float64(g.lati), From 0cb56de19097edfca9c2ffcda2daf6a35e2dd26f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Dec 2015 17:17:50 +0100 Subject: [PATCH 0090/2266] Update dev plan. Close #2 --- DEVELOPMENT_PLAN.md | 70 ++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 36 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 7ede28445..ea1b70964 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -2,43 +2,21 @@ Development Plan ================ ## Milestone 1 -Have a fake gateway able to mock the behavior of an existing real gateway. This will be used +Have a fake gateway able to mock the behavior of a physical gateway. This will be used mainly for testing and ensuring the correctness of other components. - [ ] Fake gateway - [x] Types, packages and data structures in use - [x] Emit udp packets towards a server - - [ ] Handle reception acknowledgement from server - - [x] serialize json rxpk/stat object(s) - - [ ] generate json rxpl/stat object(s) + - [ ] Handle behavior described by the semtech protocol + - [x] Serialize json rxpk/stat object(s) + - [x] Generate json rxpl/stat object(s) - [ ] Simulate fake end-devices activity - -```go -type Option struct { - key string - value {}interface -} - -type Gateway interface { - Start () error - EmitData (data []byte, options ...Option) error - EmitStat (options ...Option) error - Mimic (errors <-chan error) - - generateRXPK(nb int, options ...Option) []RXPK - generateStat(options ...Option) Stat - createPushData(rxpk []RXPK, stat Stat) error, []byte - pull(routers ...string) - decodeResponse(response []byte) error, Packet -} - -Create (id string, routers ...string) error, *Gateway -``` - + - [ ] Update gateway statistics accordingly ## Milestone 2 Handle an uplink process that can forward packet coming from a gateway to a simple end-server -(fake handler). We handle no mac command and we does not care about registration yet. The +(fake handler). We handle no MAC commands and we does not care about registration yet. The system will just forward messages using pre-configured end-device addresses. @@ -50,14 +28,12 @@ system will just forward messages using pre-configured end-device addresses. - [ ] Detail the list of features -- [ ] Minimalist Dumb Network-Server +- [ ] Minimalist Dumb Network-Controller - [ ] Detail the list of features ## Milestone 3 -Handle OTAA and downlink accept message. We still not allow mac commands from neither the -end-device nor a network server. Also, no messages can be sent by an application or whatever. -The only downlink message we accept is the join-accept / join-reject message sent during an -OTAA. +Support application registration for personalization. Applications provide a list of +personalized device addresses along with the network session keys - [ ] Extend Router - [ ] Detail the list of features @@ -67,7 +43,7 @@ OTAA. - [ ] Detail the list of features -- [ ] Extend Network-server +- [ ] Extend Network-Controller - [ ] Detail the list of features @@ -86,11 +62,33 @@ response after an uplink transmission from a node. - [ ] Detail the list of features -- [ ] Fake minismalist Application server +- [ ] Fake minimalist Application server - [ ] Detail the list of features ## Milestone 5 +Handle OTAA and downlink accept message. We still not allow MAC commands from neither the +end-device nor a network controller. Also, no downlink payload can be sent by an application: +the only downlink message we accept is the join-accept / join-reject message sent during an + +- [ ] Extend Router + - [ ] Detail the list of features + + +- [ ] Extend Broker + - [ ] Detail the list of features + + +- [ ] Extend Network-Controller + - [ ] Detail the list of features + + +- [ ] Minimalist Handler + - [ ] Detail the list of features + + + +## Milestone 6 Handle more complexe commands and their corresponding acknowledgement. -- [ ] Extend Network server +- [ ] Extend Network Controller - [ ] Detail the list of features From f04d097b0eb23549517ef60253709ba116acfcf7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Dec 2015 15:02:23 +0100 Subject: [PATCH 0091/2266] [simulators.gateway] Add pauses between test steps --- simulators/gateway/gateway_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go index cae3c324d..160abeeb2 100644 --- a/simulators/gateway/gateway_test.go +++ b/simulators/gateway/gateway_test.go @@ -8,6 +8,7 @@ import ( "github.com/thethingsnetwork/core/lorawan/semtech" "net" "testing" + "time" ) func TestNew(t *testing.T) { @@ -144,6 +145,7 @@ func TestStat(t *testing.T) { return } conn.Write(raw) + time.Sleep(100 * time.Millisecond) Convey("The downlink packet number should have been incremented", func() { dwnb := *gateway.Stats().Dwnb @@ -162,6 +164,7 @@ func TestStat(t *testing.T) { return } conn.Write(raw) + time.Sleep(100 * time.Millisecond) Convey("The downlink packet number should have been incremented", func() { dwnb := *gateway.Stats().Dwnb So(dwnb, ShouldEqual, *stats.Dwnb+1) @@ -180,6 +183,7 @@ func TestStat(t *testing.T) { t.Errorf("Unexpected error %+v\n", e) return } + time.Sleep(100 * time.Millisecond) Convey("The number of packets forwarded should have been incremented", func() { rxfw := *gateway.Stats().Rxfw So(rxfw, ShouldEqual, *stats.Rxfw+1) @@ -213,6 +217,7 @@ func TestStat(t *testing.T) { t.Errorf("An error was expected") return } + time.Sleep(100 * time.Millisecond) Convey("The number of packets forwarded shouldn't have moved", func() { rxfw := *gateway.Stats().Rxfw So(rxfw, ShouldEqual, *stats.Rxfw) From 7e853c9be9fe2fc4f9258eb1e37646b2d3a234fd Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Dec 2015 15:21:40 +0100 Subject: [PATCH 0092/2266] [simulators.gateway] Add comment about concurrent accesses in forwarder --- simulators/gateway/forwarder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 05cb62da6..48c7fd463 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -150,6 +150,7 @@ func asChannel(conn *net.UDPConn) (<-chan []byte, <-chan error) { // reduceCmd handle all updates made on the gateway statistics func reduceCmd(gateway *Gateway, commands <-chan command) { + //TODO beware of concurrent access on gateway properties. for command := range commands { switch command { case cmd_ACKN_PACKET: From 1b162da9a353d8d7ff077af7a44dcb85b8cfba78 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Dec 2015 15:22:04 +0100 Subject: [PATCH 0093/2266] [simulators.gateway] Create Mimic() function signature and type in adequate file --- simulators/gateway/gateway.go | 4 ---- simulators/gateway/imitator.go | 14 ++++++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) create mode 100644 simulators/gateway/imitator.go diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 0da1ed63f..05101cb54 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -95,7 +95,3 @@ func (g Gateway) Stats() semtech.Stat { Txnb: pointer.Uint(g.txnb), } } - -type Imitator interface { - Mimic() -} diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go new file mode 100644 index 000000000..155391037 --- /dev/null +++ b/simulators/gateway/imitator.go @@ -0,0 +1,14 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import () + +type Imitator interface { + Mimic() error +} + +func (g *Gateway) Mimic() error { + return nil +} From 107ce9ef80e8664f5afe25a0b788d374cd9697f9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Dec 2015 17:28:07 +0100 Subject: [PATCH 0094/2266] [simulators.gateway] Write down first tests for mimic() --- simulators/gateway/imitator_test.go | 101 ++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 simulators/gateway/imitator_test.go diff --git a/simulators/gateway/imitator_test.go b/simulators/gateway/imitator_test.go new file mode 100644 index 000000000..405c4413f --- /dev/null +++ b/simulators/gateway/imitator_test.go @@ -0,0 +1,101 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "fmt" + . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core/lorawan/semtech" + "testing" + "time" +) + +func TestMimic(t *testing.T) { + gatewayId := []byte("MyGateway")[:8] + routerAddr := "0.0.0.0:3000" + gateway, e := New(gatewayId, routerAddr) + + if e != nil { + t.Errorf("Unexpected error %v", e) + return + } + + chout, cherr, e := gateway.Start() + + if e != nil { + t.Errorf("Unexpected error %v", e) + return + } + + defer gateway.Stop() + + Convey("Given a started gateway", t, func() { + err := gateway.Mimic() + Convey("The imitator mode can be started", func() { + So(err, ShouldNotBeNil) + }) + + Convey("After having started the imitation", func() { + Convey("It should periodically and randomly emit PUSH_DATA packet", func() { + nb := 0 + for nb < 3 { + select { + case packet := <-chout: + So(packet.Identifier, ShouldEqual, semtech.PUSH_DATA) + nb += 1 + case err := <-cherr: + t.Errorf("Unexpected error %v", err) + return + case <-time.After(time.Second * 5): + t.Errorf("Timeout") + return + } + } + }) + + Convey("It should periodically emit PULL_DATA after a PUSH_ACK has been received", func() { + var packet semtech.Packet + select { + case packet = <-chout: + case <-time.After(5 * time.Second): + t.Errorf("Timeout") + return + } + if packet.Identifier != semtech.PUSH_DATA { + t.Errorf("Unexpected packet identifier") + return + } + e := gateway.Forward(semtech.Packet{ + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }) + + fmt.Println(e) + + maxTries := 3 + for maxTries > 0 { + select { + case packet := <-chout: + if packet.Identifier != semtech.PULL_DATA { + maxTries -= 1 + continue + } + So(packet.Identifier, ShouldEqual, semtech.PULL_DATA) + case err := <-cherr: + t.Errorf("Unexpected error %v", err) + return + case <-time.After(time.Second * 5): + t.Errorf("Timeout") + return + } + } + + if maxTries <= 0 { + So("No PULL_DATA sent", ShouldBeNil) + } + }) + }) + }) +} From 72b9e3cb7eb76a7d1418fd668092c56cf6f4ef6e Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Dec 2015 19:26:22 +0100 Subject: [PATCH 0095/2266] [simulators.gateway] Fix typo in imitator tests --- simulators/gateway/imitator_test.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/simulators/gateway/imitator_test.go b/simulators/gateway/imitator_test.go index 405c4413f..548c66bc6 100644 --- a/simulators/gateway/imitator_test.go +++ b/simulators/gateway/imitator_test.go @@ -4,7 +4,6 @@ package gateway import ( - "fmt" . "github.com/smartystreets/goconvey/convey" "github.com/thethingsnetwork/core/lorawan/semtech" "testing" @@ -33,7 +32,7 @@ func TestMimic(t *testing.T) { Convey("Given a started gateway", t, func() { err := gateway.Mimic() Convey("The imitator mode can be started", func() { - So(err, ShouldNotBeNil) + So(err, ShouldBeNil) }) Convey("After having started the imitation", func() { @@ -66,14 +65,13 @@ func TestMimic(t *testing.T) { t.Errorf("Unexpected packet identifier") return } - e := gateway.Forward(semtech.Packet{ + + gateway.Forward(semtech.Packet{ Version: semtech.VERSION, Token: packet.Token, Identifier: semtech.PUSH_ACK, }) - fmt.Println(e) - maxTries := 3 for maxTries > 0 { select { From 10cc995080bd6692e93b31358cd9c46c89335e4c Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Dec 2015 19:31:21 +0100 Subject: [PATCH 0096/2266] [simulators.gateway] Implement first part of imitator (send random PUSH_DATA). Still no data in them --- simulators/gateway/imitator.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 155391037..6bb4f8e31 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -3,12 +3,38 @@ package gateway -import () +import ( + "errors" + "github.com/thethingsnetwork/core/lorawan/semtech" + "time" +) type Imitator interface { Mimic() error } func (g *Gateway) Mimic() error { + if g.cmd == nil || g.quit == nil { + return errors.New("Cannot mimic on a stopped gateway") + } + + go func(g *Gateway) { + ticker := time.Tick(time.Millisecond * 800) + for { + select { + case <-ticker: + g.Forward(semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_DATA, + GatewayId: g.Id, + Token: genToken(), + }) + case ack := <-g.quit: + ack <- nil + return + } + } + }(g) + return nil } From 0ce87b6b44f4dfbc63464d02ec5e1e1e34a8d0fa Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Dec 2015 17:00:12 +0100 Subject: [PATCH 0097/2266] [simulators.gateway] Sketch gateway behavior. Need to think about it more calmly --- simulators/gateway/forwarder.go | 39 +++++++++++------ simulators/gateway/gateway.go | 5 +++ simulators/gateway/imitator.go | 76 +++++++++++++++++++++++++++------ 3 files changed, 95 insertions(+), 25 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 48c7fd463..83a39f445 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -28,7 +28,7 @@ type Forwarder interface { // Same for errors. func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { // Ensure not already started - if g.quit != nil { + if g.IsRunning() { return nil, nil, errors.New("Try to start a started gateway") } @@ -122,7 +122,8 @@ func asChannel(conn *net.UDPConn) (<-chan []byte, <-chan error) { go func() { buf := make([]byte, LISTENER_BUF_SIZE) for { - n, _, err := conn.ReadFromUDP(buf) + n, a, err := conn.ReadFromUDP(buf) + fmt.Println("Address", a) if err != nil { if strings.Contains(err.Error(), "use of closed network connection") { return @@ -169,7 +170,7 @@ func reduceCmd(gateway *Gateway, commands <-chan command) { // Stop remove all previously created connection. func (g *Gateway) Stop() error { - if g.quit == nil || g.cmd == nil { + if !g.IsRunning() { return errors.New("Try to stop a non-started gateway") } @@ -190,31 +191,43 @@ func (g *Gateway) Stop() error { // Forward transfer a packet to all known routers. // It fails if the gateway hasn't been started beforehand. -func (g *Gateway) Forward(packet semtech.Packet) error { - if g.quit == nil || g.cmd == nil { +func (g *Gateway) Forward(packet semtech.Packet, only ...*net.UDPAddr) error { + if !g.IsRunning() { return errors.New("Unable to forward on a non-started gateway") } - g.cmd <- cmd_RECU_PACKET + raddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:33333") + if err != nil { + return err + } + + var recipients []*net.UDPAddr + if len(only) == 0 { // len > 0 => packet retransmission + g.cmd <- cmd_RECU_PACKET + recipients = g.routers + } else { + recipients = only + } connections := make([]*net.UDPConn, 0) - var err error - for _, addr := range g.routers { - var conn *net.UDPConn - conn, err = net.DialUDP("udp", nil, addr) + for _, addr := range recipients { + + conn, err := net.DialUDP("udp", addr, raddr) if err != nil { - break + return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) } defer conn.Close() connections = append(connections, conn) } - raw, err := semtech.Marshal(packet) + raw, err := semtech.Marshal(packet) if err != nil { return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) } - g.cmd <- cmd_FORW_PACKET + if len(only) == 0 { // len > 0 => packet retransmission + g.cmd <- cmd_FORW_PACKET + } for _, conn := range connections { _, err = conn.Write(raw) diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go index 05101cb54..58f0770b7 100644 --- a/simulators/gateway/gateway.go +++ b/simulators/gateway/gateway.go @@ -75,6 +75,11 @@ func New(id []byte, routers ...string) (*Gateway, error) { }, nil } +// IsRunning gives information about the gateway status +func (g Gateway) IsRunning() bool { + return g.quit != nil && g.cmd != nil +} + // Stats return the gateway usage statistics computed along its lifecycle func (g Gateway) Stats() semtech.Stat { var ackr float64 diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 6bb4f8e31..7cbb7893a 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -5,7 +5,10 @@ package gateway import ( "errors" + "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" + "net" + "sync" "time" ) @@ -14,25 +17,74 @@ type Imitator interface { } func (g *Gateway) Mimic() error { - if g.cmd == nil || g.quit == nil { - return errors.New("Cannot mimic on a stopped gateway") + if g.IsRunning() { + return errors.New("Cannot mimic on a started gateway") } + chout, cherr, err := g.Start() + + if err != nil { + return err + } + + mutex := &sync.Mutex{} + communications := make(map[*semtech.Packet][]*net.UDPAddr) + go func(g *Gateway) { ticker := time.Tick(time.Millisecond * 800) for { - select { - case <-ticker: - g.Forward(semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_DATA, - GatewayId: g.Id, - Token: genToken(), - }) - case ack := <-g.quit: - ack <- nil + <-ticker + if !g.IsRunning() { return } + token := genToken() + packet := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_DATA, + GatewayId: g.Id, + Token: token, + } + mutex.Lock() + err := g.Forward(packet) + if err != nil { + fmt.Println(err) + } + communications[&packet] = g.routers + mutex.Unlock() + } + }(g) + + go func(g *Gateway) { + ticker := time.Tick(time.Millisecond * 800) + for { + <-ticker + mutex.Lock() + if !g.IsRunning() { + mutex.Unlock() + return + } + for packet, routers := range communications { + err := g.Forward(*packet, routers...) + if err != nil { + fmt.Println(err) + } + } + mutex.Unlock() + } + }(g) + + go func(g *Gateway) { + for { + if !g.IsRunning() { + return + } + select { + case packet := <-chout: + fmt.Printf("%+v\n", packet) + case err := <-cherr: + fmt.Println(err) + case <-time.After(time.Second): + } } }(g) From dcd2f6a0be148004cd3e736cb410983dd7ad92cc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 26 Dec 2015 17:50:48 +0100 Subject: [PATCH 0098/2266] [simulators.gateway] Specify what is expected from a gateway --- simulators/gateway/GATEWAY.md | 84 +++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 simulators/gateway/GATEWAY.md diff --git a/simulators/gateway/GATEWAY.md b/simulators/gateway/GATEWAY.md new file mode 100644 index 000000000..2024c347d --- /dev/null +++ b/simulators/gateway/GATEWAY.md @@ -0,0 +1,84 @@ +Gateway +------- + + + +### Behavior + +From the [packet +forwarder](https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT) +defined by Semtech we can extract the following behavior from the gateway: + +- The gateway is able to send two types of packet: + - `PUSH_DATA` packets with a payload coming from a device + - `PULL_DATA` packets use to maintain an established connection + +- The gateway is able to receive three types of packet: + - `PUSH_ACK` packets which acknowledge a `PUSH_DATA` + - `PULL_ACK` packets which acknowledge a `PULL_DATA` + - `PULL_RESP` packets used to transfer a response down to the devices + +For the moment, we simply log down any missing acknowledgement. In a second time, we'll +consider re-emitting the corresponding packets. + +A gateway instance does not presume of any activity, it is sleeping by default and is rather +stimulated by an external agent. + +We want the gateway to log and store each downlink packet. However, any external agent can +still flush the gateway to retrieve all stored packet and clear the gateway internal buffer. +This way, the gateway is nothing more than a forwarder while the handling logic is under the +control of a separated entity. + +### Interfaces + +```go +// New create a gateway instance bound to a set of routers. +func New (id string, routers ...io.ReadWriteCloser) (&Gateway, error) + +// Forward dispatch a packet to all connected routers. +func (g *Gateway) Forward(p semtech.Packet) error + +// Flush spits out all downlink packet received by the gateway since the last flush. +func (g *Gateway) Flush() []semtech.Packet + +// Stop terminate the gateway activity. Closing all routers connections +func (g *Gateway) Stop() error +``` + +### Stats + + Name | Type | Function +--------|----------|-------------------------------------------------------------- + *time* | *string* | *UTC 'system' time of the gateway, ISO 8601 'expanded' format* + *lati* | *number* | *GPS latitude of the gateway in degree (float, N is +)* + *long* | *number* | *GPS latitude of the gateway in degree (float, E is +)* + *alti* | *number* | *GPS altitude of the gateway in meter RX (integer)* + rxnb | number | Number of radio packets received (unsigned integer) + rxok | number | Number of radio packets received with a valid PHY CRC + rxfw | number | Number of radio packets forwarded (unsigned integer) + ackr | number | Percentage of upstream datagrams that were acknowledged + dwnb | number | Number of downlink datagrams received (unsigned integer) + txnb | number | Number of packets emitted (unsigned integer) + +##### rxnb +Incremented each time a packet is received from a device. In other words, any call to Forward +with a non-nil packet should incremented that stat. + +##### rxok +Incremented each time a packet is received from a device. Because the gateway only simulate +what a real gateway would do, we do not consider a full device packet with a CRC and a PHY +payload. We only consider the payload and thus, rxok and rxnb should be seemingly the same. + +##### rxfw +This conveys a successful packet forwarding. It should be incremented once per packet received +from devices that has successfully been forwarded to routers (regardless of any ack from them). + +##### ackr +Computed using the number of forwarded packet that has been acknowledged and the total number +of forwarded packet. + +##### dwnb +Incremented each time a packet is received from a router. + +##### txnb +Incremented for each packet forwarded but also for each `PULL_DATA` packets sent. From 4634f947e78c9014294213147dd949a9be77a7ad Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 26 Dec 2015 17:54:46 +0100 Subject: [PATCH 0099/2266] [simulators.gateway] Rename Gateway -> Forwarder to make it consistent with the package --- simulators/gateway/FORWARDER.md | 82 +++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 simulators/gateway/FORWARDER.md diff --git a/simulators/gateway/FORWARDER.md b/simulators/gateway/FORWARDER.md new file mode 100644 index 000000000..7fc2ae095 --- /dev/null +++ b/simulators/gateway/FORWARDER.md @@ -0,0 +1,82 @@ +Forwarder +------- + +### Behavior + +From the [packet +forwarder](https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT) +defined by Semtech we can extract the following behavior for the forwarder: + +- The forwarder is able to send two types of packet: + - `PUSH_DATA` packets with a payload coming from a device + - `PULL_DATA` packets use to maintain an established connection + +- The forwarder is able to receive three types of packet: + - `PUSH_ACK` packets which acknowledge a `PUSH_DATA` + - `PULL_ACK` packets which acknowledge a `PULL_DATA` + - `PULL_RESP` packets used to transfer a response down to the devices + +For the moment, we simply log down any missing acknowledgement. In a second time, we'll +consider re-emitting the corresponding packets. + +A forwarder instance does not presume of any activity, it is sleeping by default and is rather +stimulated by an external agent. + +We want the forwarder to log and store each downlink packet. However, any external agent can +still flush the forwarder to retrieve all stored packet and clear the forwarder internal buffer. +This way, the forwarder is nothing more than a forwarder while the handling logic is under the +control of a separated entity. + +### Interfaces + +```go +// New create a forwarder instance bound to a set of routers. +func New (id string, routers ...io.ReadWriteCloser) (&Forwarder, error) + +// Forward dispatch a packet to all connected routers. +func (g *Forwarder) Forward(p semtech.Packet) error + +// Flush spits out all downlink packet received by the forwarder since the last flush. +func (g *Forwarder) Flush() []semtech.Packet + +// Stop terminate the forwarder activity. Closing all routers connections +func (g *Forwarder) Stop() error +``` + +### Stats + + Name | Type | Function +--------|----------|-------------------------------------------------------------- + *time* | *string* | *UTC 'system' time of the gateway, ISO 8601 'expanded' format* + *lati* | *number* | *GPS latitude of the gateway in degree (float, N is +)* + *long* | *number* | *GPS latitude of the gateway in degree (float, E is +)* + *alti* | *number* | *GPS altitude of the gateway in meter RX (integer)* + rxnb | number | Number of radio packets received (unsigned integer) + rxok | number | Number of radio packets received with a valid PHY CRC + rxfw | number | Number of radio packets forwarded (unsigned integer) + ackr | number | Percentage of upstream datagrams that were acknowledged + dwnb | number | Number of downlink datagrams received (unsigned integer) + txnb | number | Number of packets emitted (unsigned integer) + +##### rxnb +Incremented each time a packet is received from a device. In other words, any call to Forward +with a non-nil packet should incremented that stat. + +##### rxok +Incremented each time a packet is received from a device. Because the forwarder only simulate +what a real gateway would do, we do not consider a full device packet with a CRC and a PHY +payload. We only consider the payload and thus, rxok and rxnb should be seemingly the same. + +##### rxfw +This conveys a successful packet forwarding. It should be incremented once per packet received +from devices that has successfully been forwarded to routers (regardless of any ack from them). + +##### ackr +Computed using the number of forwarded packet that has been acknowledged and the total number +of forwarded packet. + +##### dwnb +Incremented each time a packet is received from a router. + +##### txnb +Incremented for each packet forwarded but also for each `PULL_DATA` packets sent. From d9dec454c0f4c728a9a02fb6923fb58d96a742fc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 26 Dec 2015 19:47:22 +0100 Subject: [PATCH 0100/2266] [simulators.gateway] Rewrite signatures --- simulators/gateway/FORWARDER.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/simulators/gateway/FORWARDER.md b/simulators/gateway/FORWARDER.md index 7fc2ae095..2d3a2bd68 100644 --- a/simulators/gateway/FORWARDER.md +++ b/simulators/gateway/FORWARDER.md @@ -31,16 +31,19 @@ control of a separated entity. ```go // New create a forwarder instance bound to a set of routers. -func New (id string, routers ...io.ReadWriteCloser) (&Forwarder, error) +func NewForwarder (id string, routers ...io.ReadWriteCloser) (*Forwarder, error) + +// Flush spits out all downlink packet received by the forwarder since the last flush. +func (fwd *Forwarder) Flush() []semtech.Packet // Forward dispatch a packet to all connected routers. -func (g *Forwarder) Forward(p semtech.Packet) error +func (fwd *Forwarder) Forward(packet semtech.Packet) error -// Flush spits out all downlink packet received by the forwarder since the last flush. -func (g *Forwarder) Flush() []semtech.Packet +// Stats computes and return the forwarder statistics since it was created +func (fwd Forwarder) Stats() semtech.Stat // Stop terminate the forwarder activity. Closing all routers connections -func (g *Forwarder) Stop() error +func (fwd *Forwarder) Stop() error ``` ### Stats From 1684633c064bd6ab2e8ab1ecef53a75a4302b64f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 26 Dec 2015 19:49:25 +0100 Subject: [PATCH 0101/2266] [simulators.gateway] Remove old implementations while upgrading --- simulators/gateway/forwarder.go | 242 --------------------------- simulators/gateway/forwarder_test.go | 157 ----------------- simulators/gateway/gateway.go | 102 ----------- simulators/gateway/gateway_test.go | 242 --------------------------- simulators/gateway/imitator.go | 92 ---------- simulators/gateway/imitator_test.go | 99 ----------- 6 files changed, 934 deletions(-) delete mode 100644 simulators/gateway/forwarder.go delete mode 100644 simulators/gateway/forwarder_test.go delete mode 100644 simulators/gateway/gateway.go delete mode 100644 simulators/gateway/gateway_test.go delete mode 100644 simulators/gateway/imitator.go delete mode 100644 simulators/gateway/imitator_test.go diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go deleted file mode 100644 index 83a39f445..000000000 --- a/simulators/gateway/forwarder.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "errors" - "fmt" - "github.com/thethingsnetwork/core/lorawan/semtech" - "net" - "strings" - "time" -) - -const ( - LISTENER_BUF_SIZE = 1024 - LISTENER_TIMEOUT = 4 * time.Second -) - -type Forwarder interface { - Forward(packet semtech.Packet) error - Start() (<-chan semtech.Packet, <-chan error, error) - Stop() error -} - -// Start a Gateway. This will create several udp connections - one per associated router. -// Then, incoming streams of packets are merged into a single one. -// Same for errors. -func (g *Gateway) Start() (<-chan semtech.Packet, <-chan error, error) { - // Ensure not already started - if g.IsRunning() { - return nil, nil, errors.New("Try to start a started gateway") - } - - // Open all UDP connections - connections := make([]*net.UDPConn, 0) - var err error - for _, addr := range g.routers { - var conn *net.UDPConn - conn, err = net.ListenUDP("udp", addr) - if err != nil { - break - } - connections = append(connections, conn) - } - - // On error, close all opened UDP connection and leave - if err != nil { - for _, conn := range connections { - conn.Close() - } - return nil, nil, err - } - - // Create communication channels and launch goroutines to handle connections - chout := make(chan semtech.Packet) - cherr := make(chan error) - quit := make(chan chan error) - cmd := make(chan command) - go reduceCmd(g, cmd) - for _, conn := range connections { - go listen(conn, chout, cherr, cmd, quit) - } - - // Keep a reference to the quit channel, and return the others - g.quit = quit - g.cmd = cmd - return chout, cherr, nil -} - -// listen materialize the goroutine handling incoming packet from routers -func listen(conn *net.UDPConn, chout chan<- semtech.Packet, cherr chan<- error, cmd chan<- command, quit <-chan chan error) { - connIn, connErr := asChannel(conn) - errBuf := make([]error, 0) - outBuf := make([]semtech.Packet, 0) - for { - var safeChout chan<- semtech.Packet - var packet semtech.Packet - if len(outBuf) > 0 { - safeChout = chout - packet = outBuf[0] - } - - var safeCherr chan<- error - var err error - if len(errBuf) > 0 { - safeCherr = cherr - err = errBuf[0] - } - - select { - case ack := <-quit: // quit event, the gateway is stoppped - e := conn.Close() - ack <- e - return - case buf := <-connIn: // connIn event, a packet has been received by the listener goroutine - cmd <- cmd_RECD_PACKET - packet, err := semtech.Unmarshal(buf) - if err != nil { - errBuf = append(errBuf, err) - continue - } - outBuf = append(outBuf, *packet) - case safeChout <- packet: // emit an available packet to chout - outBuf = outBuf[1:] - case err := <-connErr: // connErr event, an error has been triggered by the listener goroutine - errBuf = append(errBuf, err) - case safeCherr <- err: // emit an existing error to cherr - errBuf = errBuf[1:] - } - } -} - -// as channel is actually a []byte generator that listen to a given udp connection. -// It is used to prevent the listen() function from blocking on ReadFromUDP() and then, -// still be available for a quit event that could come at any time. -// This function is thereby nothing more than a mapping of incoming connection to channels of -// communication. -func asChannel(conn *net.UDPConn) (<-chan []byte, <-chan error) { - cherr := make(chan error) - chout := make(chan []byte) - go func() { - buf := make([]byte, LISTENER_BUF_SIZE) - for { - n, a, err := conn.ReadFromUDP(buf) - fmt.Println("Address", a) - if err != nil { - if strings.Contains(err.Error(), "use of closed network connection") { - return - } - select { - case cherr <- err: - continue - case <-time.After(LISTENER_TIMEOUT): - close(cherr) - close(chout) - return - } - } - select { - case chout <- buf[:n]: - case <-time.After(LISTENER_TIMEOUT): - close(cherr) - close(chout) - return - } - } - }() - return chout, cherr -} - -// reduceCmd handle all updates made on the gateway statistics -func reduceCmd(gateway *Gateway, commands <-chan command) { - //TODO beware of concurrent access on gateway properties. - for command := range commands { - switch command { - case cmd_ACKN_PACKET: - gateway.ackr += 1 - case cmd_EMIT_PACKET: - gateway.txnb += 1 - case cmd_FORW_PACKET: - gateway.rxfw += 1 - case cmd_RECU_PACKET: - gateway.rxnb += 1 - case cmd_RECD_PACKET: - gateway.dwnb += 1 - } - } -} - -// Stop remove all previously created connection. -func (g *Gateway) Stop() error { - if !g.IsRunning() { - return errors.New("Try to stop a non-started gateway") - } - - for range g.routers { - errc := make(chan error) - g.quit <- errc - err := <-errc - if err != nil { - fmt.Printf("%+v\n", err) - } - } - close(g.quit) - close(g.cmd) - g.routers = make([]*net.UDPAddr, 0) - g.quit = nil - return nil -} - -// Forward transfer a packet to all known routers. -// It fails if the gateway hasn't been started beforehand. -func (g *Gateway) Forward(packet semtech.Packet, only ...*net.UDPAddr) error { - if !g.IsRunning() { - return errors.New("Unable to forward on a non-started gateway") - } - - raddr, err := net.ResolveUDPAddr("udp", "0.0.0.0:33333") - if err != nil { - return err - } - - var recipients []*net.UDPAddr - if len(only) == 0 { // len > 0 => packet retransmission - g.cmd <- cmd_RECU_PACKET - recipients = g.routers - } else { - recipients = only - } - - connections := make([]*net.UDPConn, 0) - for _, addr := range recipients { - - conn, err := net.DialUDP("udp", addr, raddr) - if err != nil { - return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) - } - defer conn.Close() - connections = append(connections, conn) - } - - raw, err := semtech.Marshal(packet) - if err != nil { - return errors.New(fmt.Sprintf("Unable to forward the packet. %v\n", err)) - } - - if len(only) == 0 { // len > 0 => packet retransmission - g.cmd <- cmd_FORW_PACKET - } - - for _, conn := range connections { - _, err = conn.Write(raw) - g.cmd <- cmd_EMIT_PACKET - } - - if err != nil { - return errors.New(fmt.Sprintf("Something went wrong during forwarding. %v\n", err)) - } - - return nil -} diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go deleted file mode 100644 index 034f978f4..000000000 --- a/simulators/gateway/forwarder_test.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/lorawan/semtech" - "net" - "testing" - "time" -) - -func TestStart(t *testing.T) { - gatewayId := []byte("MyGateway")[:8] - routerAddr := "0.0.0.0:3000" - gateway, _ := New(gatewayId, routerAddr) - chout, cherr, err := gateway.Start() - defer gateway.Stop() - - udpAddr, e := net.ResolveUDPAddr("udp", routerAddr) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - - conn, e := net.DialUDP("udp", nil, udpAddr) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - defer conn.Close() - - Convey("Given a valid started gateway instance bound to a router", t, func() { - Convey("Both channels should exist", func() { - So(cherr, ShouldNotBeNil) - So(chout, ShouldNotBeNil) - So(err, ShouldBeNil) - }) - - Convey("A connection should exist", func() { - So(len(gateway.routers), ShouldEqual, 1) - }) - - Convey("A valid packet should be forwarded", func() { - packet := semtech.Packet{ - Version: semtech.VERSION, - Token: []byte{0x1, 0x2}, - Identifier: semtech.PUSH_ACK, - } - raw, e := semtech.Marshal(packet) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - conn.Write(raw) - var received semtech.Packet - select { - case received = <-chout: - case <-time.After(time.Second): - } - So(received, ShouldResemble, packet) - }) - - Convey("An invalid packet should raise an error", func() { - conn.Write([]byte("Invalid")) - var err error - select { - case err = <-cherr: - case <-time.After(time.Second): - } - So(err, ShouldNotBeNil) - }) - - Convey("It should fail if started one more time", func() { - _, _, err := gateway.Start() - So(err, ShouldNotBeNil) - }) - }) -} - -func TestStop(t *testing.T) { - gatewayId := []byte("MyGateway")[:8] - routerAddr := "0.0.0.0:3000" - gateway, _ := New(gatewayId, routerAddr) - Convey("Given a gateway instance", t, func() { - Convey("It should failed if stopped while not started", func() { - err := gateway.Stop() - So(err, ShouldNotBeNil) - }) - - Convey("It should stop correctly after having started", func() { - _, _, err := gateway.Start() - if err != nil { - t.Errorf("Unexpected error %v\n", err) - return - } - time.Sleep(200 * time.Millisecond) - err = gateway.Stop() - - So(err, ShouldBeNil) - So(gateway.quit, ShouldBeNil) - So(len(gateway.routers), ShouldEqual, 0) - }) - }) -} - -func TestForward(t *testing.T) { - gatewayId := []byte("MyGateway")[:8] - routerAddr1 := "0.0.0.0:3000" - routerAddr2 := "0.0.0.0:3001" - - gateway, _ := New(gatewayId, routerAddr1, routerAddr2) - chout, _, e := gateway.Start() - - if e != nil { - t.Errorf("Unexpected error %v", e) - return - } - defer gateway.Stop() - - Convey("Given a started gateway bound to two routers", t, func() { - Convey("When forwarding a valid packet", func() { - packet := semtech.Packet{ - Version: semtech.VERSION, - Token: []byte{0x1, 0x2}, - Identifier: semtech.PUSH_ACK, - } - gateway.Forward(packet) - - Convey("It should be forwarded to both routers", func() { - var received semtech.Packet - select { - case received = <-chout: - case <-time.After(time.Second): - } - So(received, ShouldResemble, packet) - select { - case received = <-chout: - case <-time.After(time.Second): - } - So(received, ShouldResemble, packet) - }) - }) - - Convey("When forwarding an invalid packet", func() { - packet := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_ACK, - } - err := gateway.Forward(packet) - Convey("The gateway should return an error", func() { - So(err, ShouldNotBeNil) - }) - }) - }) -} diff --git a/simulators/gateway/gateway.go b/simulators/gateway/gateway.go deleted file mode 100644 index 58f0770b7..000000000 --- a/simulators/gateway/gateway.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package gateway offers a dummy representation of a gateway. -// -// The package can be used to create a dummy gateway. -// Its former use is to provide a handy simulator for further testing of the whole network chain. -package gateway - -import ( - "errors" - "fmt" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/utils/pointer" - "net" - "time" -) - -type Gateway struct { - Id []byte // Gateway's Identifier - alti int // GPS altitude in RX meters - ackr uint // Number of upstream datagrams that were acknowledged - dwnb uint // Number of downlink datagrams received - lati float64 // GPS latitude, North is + - long float64 // GPS longitude, East is + - rxfw uint // Number of radio packets forwarded - rxnb uint // Number of radio packets received - txnb uint // Number of packets emitted - routers []*net.UDPAddr // List of routers addresses - quit chan chan error // Communication channel to stop connections - cmd chan command // Internal channel use to update gateway statistics -} - -type command uint - -const ( - cmd_ACKN_PACKET = iota - cmd_EMIT_PACKET - cmd_FORW_PACKET - cmd_RECU_PACKET - cmd_RECD_PACKET -) - -// New create a new gateway from a given id and a list of router addresses -func New(id []byte, routers ...string) (*Gateway, error) { - if len(id) != 8 { - return nil, errors.New("Invalid gateway id provided") - } - - if len(routers) == 0 { - return nil, errors.New("At least one router address should be provided") - } - - addresses := make([]*net.UDPAddr, 0) - var err error - for _, router := range routers { - var addr *net.UDPAddr - addr, err = net.ResolveUDPAddr("udp", router) - if err != nil { - break - } - addresses = append(addresses, addr) - } - - if err != nil { - return nil, errors.New(fmt.Sprintf("Invalid router address. %v", err)) - } - - return &Gateway{ - Id: id, - alti: 120, - lati: 53.3702, - long: 4.8952, - routers: addresses, - }, nil -} - -// IsRunning gives information about the gateway status -func (g Gateway) IsRunning() bool { - return g.quit != nil && g.cmd != nil -} - -// Stats return the gateway usage statistics computed along its lifecycle -func (g Gateway) Stats() semtech.Stat { - var ackr float64 - if g.txnb != 0 { - ackr = float64(g.ackr) / float64(g.txnb) - } - - return semtech.Stat{ - Ackr: &ackr, - Alti: pointer.Int(g.alti), - Dwnb: pointer.Uint(g.dwnb), - Lati: pointer.Float64(g.lati), - Long: pointer.Float64(g.long), - Rxfw: pointer.Uint(g.rxfw), - Rxnb: pointer.Uint(g.rxnb), - Rxok: pointer.Uint(g.rxnb), - Time: pointer.Time(time.Now()), - Txnb: pointer.Uint(g.txnb), - } -} diff --git a/simulators/gateway/gateway_test.go b/simulators/gateway/gateway_test.go deleted file mode 100644 index 160abeeb2..000000000 --- a/simulators/gateway/gateway_test.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/lorawan/semtech" - "net" - "testing" - "time" -) - -func TestNew(t *testing.T) { - Convey("The New method should return a valid gateway struct ready to use", t, func() { - id := []byte("qwerty")[:8] - router1 := "0.0.0.0:3000" - router2 := "0.0.0.0:1337" - - Convey("Given an identifier and a router address", func() { - gateway, err := New(id, router1) - - Convey("No error should have been trown", func() { - So(err, ShouldBeNil) - }) - if err != nil { - return - } - - Convey("The identifier should have been set correctly", func() { - So(gateway.Id, ShouldResemble, id) - }) - - Convey("The list of configured routers should have been set correctly", func() { - So(len(gateway.routers), ShouldEqual, 1) - }) - }) - - Convey("Given an identifier and several routers address", func() { - gateway, err := New(id, router1, router2) - - Convey("No error should have been trown", func() { - So(err, ShouldBeNil) - }) - if err != nil { - return - } - - Convey("The identifier should have been set correctly", func() { - So(gateway.Id, ShouldResemble, id) - }) - - Convey("The list of configured routers should have been set correctly", func() { - So(len(gateway.routers), ShouldEqual, 2) - }) - }) - - Convey("Given a bad identifier and/or bad router addresses", func() { - Convey("It should return an error for an empty id", func() { - gateway, err := New(make([]byte, 0), router1) - So(gateway, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - - Convey("It should return an error for an empty routers list", func() { - gateway, err := New(id) - So(gateway, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - - Convey("It should return an error for an invalid router address", func() { - gateway, err := New(id, "invalid") - So(gateway, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - }) - }) -} - -func TestStat(t *testing.T) { - id := []byte("gatewayId")[:8] - routerAddr := "0.0.0.0:3000" - routerAddr2 := "0.0.0.0:3001" - gateway, e := New(id, routerAddr, routerAddr2) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - - addr, e := net.ResolveUDPAddr("udp", routerAddr) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - - conn, e := net.DialUDP("udp", nil, addr) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - defer conn.Close() - - Convey("Given a non started gateway", t, func() { - stats := gateway.Stats() - Convey("The stats should be equal to default values", func() { - So(*stats.Ackr, ShouldEqual, 0.0) - So(*stats.Dwnb, ShouldEqual, 0) - So(*stats.Rxfw, ShouldEqual, 0) - So(*stats.Rxnb, ShouldEqual, 0) - So(*stats.Rxok, ShouldEqual, 0) - So(*stats.Txnb, ShouldEqual, 0) - }) - }) - - _, _, e = gateway.Start() - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - defer gateway.Stop() - - Convey("Given a fresh gateway (just started)", t, func() { - stats := gateway.Stats() - Convey("The stats should be equal to default values", func() { - So(*stats.Ackr, ShouldEqual, 0.0) - So(*stats.Dwnb, ShouldEqual, 0) - So(*stats.Rxfw, ShouldEqual, 0) - So(*stats.Rxnb, ShouldEqual, 0) - So(*stats.Rxok, ShouldEqual, 0) - So(*stats.Txnb, ShouldEqual, 0) - So(stats.Time, ShouldNotBeNil) - So(stats.Lati, ShouldNotBeNil) - So(stats.Long, ShouldNotBeNil) - So(stats.Alti, ShouldNotBeNil) - }) - - Convey("After having received a valid downlink packet", func() { - raw, e := semtech.Marshal(semtech.Packet{ - Version: semtech.VERSION, - Token: genToken(), - Identifier: semtech.PUSH_ACK, - }) - if e != nil { - t.Errorf("Unexpected error %+v\n") - return - } - conn.Write(raw) - time.Sleep(100 * time.Millisecond) - - Convey("The downlink packet number should have been incremented", func() { - dwnb := *gateway.Stats().Dwnb - So(dwnb, ShouldEqual, *stats.Dwnb+1) - }) - }) - - Convey("After having received an invalid downlink packet", func() { - raw, e := semtech.Marshal(semtech.Packet{ - Version: semtech.VERSION, - Token: genToken(), - Identifier: semtech.PUSH_ACK, - }) - if e != nil { - t.Errorf("Unexpected error %+v\n") - return - } - conn.Write(raw) - time.Sleep(100 * time.Millisecond) - Convey("The downlink packet number should have been incremented", func() { - dwnb := *gateway.Stats().Dwnb - So(dwnb, ShouldEqual, *stats.Dwnb+1) - }) - }) - - Convey("After having forwarded a valid radio packet", func() { - packet := semtech.Packet{ - Version: semtech.VERSION, - Token: genToken(), - Identifier: semtech.PULL_DATA, - GatewayId: gateway.Id, - } - e := gateway.Forward(packet) - if e != nil { - t.Errorf("Unexpected error %+v\n", e) - return - } - time.Sleep(100 * time.Millisecond) - Convey("The number of packets forwarded should have been incremented", func() { - rxfw := *gateway.Stats().Rxfw - So(rxfw, ShouldEqual, *stats.Rxfw+1) - }) - - Convey("The number of packets received should have been incremented", func() { - rxnb := *gateway.Stats().Rxnb - So(rxnb, ShouldEqual, *stats.Rxnb+1) - }) - - Convey("The number of packets received with a valid PHY CRC should have been incremented", func() { - rxok := *gateway.Stats().Rxok - So(rxok, ShouldEqual, *stats.Rxok+1) - }) - - Convey("The number of packets emitted should have been incremented", func() { - txnb := *gateway.Stats().Txnb - So(txnb, ShouldEqual, *stats.Txnb+uint(len(gateway.routers))) - }) - }) - - Convey("After having forwarded an invalid radio packet", func() { - packet := semtech.Packet{ - Version: semtech.VERSION, - Token: genToken(), - Identifier: semtech.PULL_DATA, - GatewayId: gateway.Id[:4], // Invalid Gateway id - } - e := gateway.Forward(packet) - if e == nil { - t.Errorf("An error was expected") - return - } - time.Sleep(100 * time.Millisecond) - Convey("The number of packets forwarded shouldn't have moved", func() { - rxfw := *gateway.Stats().Rxfw - So(rxfw, ShouldEqual, *stats.Rxfw) - }) - - Convey("The number of packets received should have been incremented", func() { - rxnb := *gateway.Stats().Rxnb - So(rxnb, ShouldEqual, *stats.Rxnb+1) - }) - - Convey("The number of packets received with a valid PHY CRC should have been incremented", func() { - rxok := *gateway.Stats().Rxok - So(rxok, ShouldEqual, *stats.Rxok+1) - }) - - Convey("The number of packets emitted shouldn't have moved", func() { - txnb := *gateway.Stats().Txnb - So(txnb, ShouldEqual, *stats.Txnb) - }) - }) - }) -} diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go deleted file mode 100644 index 7cbb7893a..000000000 --- a/simulators/gateway/imitator.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "errors" - "fmt" - "github.com/thethingsnetwork/core/lorawan/semtech" - "net" - "sync" - "time" -) - -type Imitator interface { - Mimic() error -} - -func (g *Gateway) Mimic() error { - if g.IsRunning() { - return errors.New("Cannot mimic on a started gateway") - } - - chout, cherr, err := g.Start() - - if err != nil { - return err - } - - mutex := &sync.Mutex{} - communications := make(map[*semtech.Packet][]*net.UDPAddr) - - go func(g *Gateway) { - ticker := time.Tick(time.Millisecond * 800) - for { - <-ticker - if !g.IsRunning() { - return - } - token := genToken() - packet := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_DATA, - GatewayId: g.Id, - Token: token, - } - mutex.Lock() - err := g.Forward(packet) - if err != nil { - fmt.Println(err) - } - communications[&packet] = g.routers - mutex.Unlock() - } - }(g) - - go func(g *Gateway) { - ticker := time.Tick(time.Millisecond * 800) - for { - <-ticker - mutex.Lock() - if !g.IsRunning() { - mutex.Unlock() - return - } - for packet, routers := range communications { - err := g.Forward(*packet, routers...) - if err != nil { - fmt.Println(err) - } - } - mutex.Unlock() - } - }(g) - - go func(g *Gateway) { - for { - if !g.IsRunning() { - return - } - select { - case packet := <-chout: - fmt.Printf("%+v\n", packet) - case err := <-cherr: - fmt.Println(err) - case <-time.After(time.Second): - } - } - }(g) - - return nil -} diff --git a/simulators/gateway/imitator_test.go b/simulators/gateway/imitator_test.go deleted file mode 100644 index 548c66bc6..000000000 --- a/simulators/gateway/imitator_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/lorawan/semtech" - "testing" - "time" -) - -func TestMimic(t *testing.T) { - gatewayId := []byte("MyGateway")[:8] - routerAddr := "0.0.0.0:3000" - gateway, e := New(gatewayId, routerAddr) - - if e != nil { - t.Errorf("Unexpected error %v", e) - return - } - - chout, cherr, e := gateway.Start() - - if e != nil { - t.Errorf("Unexpected error %v", e) - return - } - - defer gateway.Stop() - - Convey("Given a started gateway", t, func() { - err := gateway.Mimic() - Convey("The imitator mode can be started", func() { - So(err, ShouldBeNil) - }) - - Convey("After having started the imitation", func() { - Convey("It should periodically and randomly emit PUSH_DATA packet", func() { - nb := 0 - for nb < 3 { - select { - case packet := <-chout: - So(packet.Identifier, ShouldEqual, semtech.PUSH_DATA) - nb += 1 - case err := <-cherr: - t.Errorf("Unexpected error %v", err) - return - case <-time.After(time.Second * 5): - t.Errorf("Timeout") - return - } - } - }) - - Convey("It should periodically emit PULL_DATA after a PUSH_ACK has been received", func() { - var packet semtech.Packet - select { - case packet = <-chout: - case <-time.After(5 * time.Second): - t.Errorf("Timeout") - return - } - if packet.Identifier != semtech.PUSH_DATA { - t.Errorf("Unexpected packet identifier") - return - } - - gateway.Forward(semtech.Packet{ - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }) - - maxTries := 3 - for maxTries > 0 { - select { - case packet := <-chout: - if packet.Identifier != semtech.PULL_DATA { - maxTries -= 1 - continue - } - So(packet.Identifier, ShouldEqual, semtech.PULL_DATA) - case err := <-cherr: - t.Errorf("Unexpected error %v", err) - return - case <-time.After(time.Second * 5): - t.Errorf("Timeout") - return - } - } - - if maxTries <= 0 { - So("No PULL_DATA sent", ShouldBeNil) - } - }) - }) - }) -} From 602ad0f8eb61da7d6cd17f1b99a16b0fa87ca2f2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 26 Dec 2015 19:49:41 +0100 Subject: [PATCH 0102/2266] [simulators.gateway] Remove renamed file --- simulators/gateway/GATEWAY.md | 84 ----------------------------------- 1 file changed, 84 deletions(-) delete mode 100644 simulators/gateway/GATEWAY.md diff --git a/simulators/gateway/GATEWAY.md b/simulators/gateway/GATEWAY.md deleted file mode 100644 index 2024c347d..000000000 --- a/simulators/gateway/GATEWAY.md +++ /dev/null @@ -1,84 +0,0 @@ -Gateway -------- - - - -### Behavior - -From the [packet -forwarder](https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT) -defined by Semtech we can extract the following behavior from the gateway: - -- The gateway is able to send two types of packet: - - `PUSH_DATA` packets with a payload coming from a device - - `PULL_DATA` packets use to maintain an established connection - -- The gateway is able to receive three types of packet: - - `PUSH_ACK` packets which acknowledge a `PUSH_DATA` - - `PULL_ACK` packets which acknowledge a `PULL_DATA` - - `PULL_RESP` packets used to transfer a response down to the devices - -For the moment, we simply log down any missing acknowledgement. In a second time, we'll -consider re-emitting the corresponding packets. - -A gateway instance does not presume of any activity, it is sleeping by default and is rather -stimulated by an external agent. - -We want the gateway to log and store each downlink packet. However, any external agent can -still flush the gateway to retrieve all stored packet and clear the gateway internal buffer. -This way, the gateway is nothing more than a forwarder while the handling logic is under the -control of a separated entity. - -### Interfaces - -```go -// New create a gateway instance bound to a set of routers. -func New (id string, routers ...io.ReadWriteCloser) (&Gateway, error) - -// Forward dispatch a packet to all connected routers. -func (g *Gateway) Forward(p semtech.Packet) error - -// Flush spits out all downlink packet received by the gateway since the last flush. -func (g *Gateway) Flush() []semtech.Packet - -// Stop terminate the gateway activity. Closing all routers connections -func (g *Gateway) Stop() error -``` - -### Stats - - Name | Type | Function ---------|----------|-------------------------------------------------------------- - *time* | *string* | *UTC 'system' time of the gateway, ISO 8601 'expanded' format* - *lati* | *number* | *GPS latitude of the gateway in degree (float, N is +)* - *long* | *number* | *GPS latitude of the gateway in degree (float, E is +)* - *alti* | *number* | *GPS altitude of the gateway in meter RX (integer)* - rxnb | number | Number of radio packets received (unsigned integer) - rxok | number | Number of radio packets received with a valid PHY CRC - rxfw | number | Number of radio packets forwarded (unsigned integer) - ackr | number | Percentage of upstream datagrams that were acknowledged - dwnb | number | Number of downlink datagrams received (unsigned integer) - txnb | number | Number of packets emitted (unsigned integer) - -##### rxnb -Incremented each time a packet is received from a device. In other words, any call to Forward -with a non-nil packet should incremented that stat. - -##### rxok -Incremented each time a packet is received from a device. Because the gateway only simulate -what a real gateway would do, we do not consider a full device packet with a CRC and a PHY -payload. We only consider the payload and thus, rxok and rxnb should be seemingly the same. - -##### rxfw -This conveys a successful packet forwarding. It should be incremented once per packet received -from devices that has successfully been forwarded to routers (regardless of any ack from them). - -##### ackr -Computed using the number of forwarded packet that has been acknowledged and the total number -of forwarded packet. - -##### dwnb -Incremented each time a packet is received from a router. - -##### txnb -Incremented for each packet forwarded but also for each `PULL_DATA` packets sent. From 1a637a8b1c518c582b9a51fea47ea29b7561f56f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 26 Dec 2015 19:50:23 +0100 Subject: [PATCH 0103/2266] [simulators.gateway] Add forwarder backbone --- simulators/gateway/forwarder.go | 47 +++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 simulators/gateway/forwarder.go diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go new file mode 100644 index 000000000..11ae3c3ef --- /dev/null +++ b/simulators/gateway/forwarder.go @@ -0,0 +1,47 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "github.com/thethingsnetwork/core/lorawan/semtech" + "io" +) + +type Forwarder struct { + Id [8]byte // Gateway's Identifier + alti int // GPS altitude in RX meters + ackr uint // Number of upstream datagrams that were acknowledged + dwnb uint // Number of downlink datagrams received + lati float64 // GPS latitude, North is + + long float64 // GPS longitude, East is + + rxfw uint // Number of radio packets forwarded + rxnb uint // Number of radio packets received + txnb uint // Number of packets emitted + routers []io.ReadWriteCloser // List of routers addresses +} + +// NewForwarder create a forwarder instance bound to a set of routers. +func NewForwarder(id [8]byte, routers ...io.ReadWriteCloser) (*Forwarder, error) { + return nil, nil +} + +// Forward dispatch a packet to all connected routers. +func (fwd *Forwarder) Forward(packet semtech.Packet) error { + return nil +} + +// Flush spits out all downlink packet received by the forwarder since the last flush. +func (fwd *Forwarder) Flush() []semtech.Packet { + return nil +} + +// Stats computes and return the forwarder statistics since it was created +func (fwd Forwarder) Stats() semtech.Stat { + return semtech.Stat{} +} + +// Stop terminate the forwarder activity. Closing all routers connections +func (fwd *Forwarder) Stop() error { + return nil +} From 629e01121c380a1aa2fbe0519bbfea9f26be3443 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 27 Dec 2015 00:22:58 +0100 Subject: [PATCH 0104/2266] [simulators.gateway] Add precision about forwarder behavior --- simulators/gateway/FORWARDER.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/simulators/gateway/FORWARDER.md b/simulators/gateway/FORWARDER.md index 2d3a2bd68..24da6a71d 100644 --- a/simulators/gateway/FORWARDER.md +++ b/simulators/gateway/FORWARDER.md @@ -27,6 +27,10 @@ still flush the forwarder to retrieve all stored packet and clear the forwarder This way, the forwarder is nothing more than a forwarder while the handling logic is under the control of a separated entity. +When a downlink datagram is received it is stored unless it does not reflect a valid semtech +Packet (i.e., a `PUSH_ACK`, `PULL_ACK` or `PULL_RESP` with valid data). Any other data received +by the forwarder is ignored. + ### Interfaces ```go From 61b5d2c33c4290fbe80bf1b9ceafaa2581bc03e3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 27 Dec 2015 00:23:14 +0100 Subject: [PATCH 0105/2266] [simulators.gateway] Add first tests of forwarder --- simulators/gateway/forwarder_test.go | 237 +++++++++++++++++++++++++++ 1 file changed, 237 insertions(+) create mode 100644 simulators/gateway/forwarder_test.go diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go new file mode 100644 index 000000000..ff90256bf --- /dev/null +++ b/simulators/gateway/forwarder_test.go @@ -0,0 +1,237 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import ( + "fmt" + . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core/lorawan/semtech" + "testing" + "time" +) + +type fakeAdapter struct { + id string + wrote []byte + Downlink chan []byte + closed bool +} + +func newFakeAdapter(id string) *fakeAdapter { + return &fakeAdapter{ + id: id, + wrote: []byte{}, + Downlink: make(chan []byte), + closed: false, + } +} + +// Write implement io.Writer interface +func (a *fakeAdapter) Write(p []byte) (int, error) { + fmt.Printf("%v wrote %+x\n", a.id, p) + a.wrote = p + return len(p), nil +} + +// Read implement io.Reader interface +func (a *fakeAdapter) Read(buf []byte) (int, error) { + return copy(buf, <-a.Downlink), nil +} + +// Close implement io.Closer interface +func (a *fakeAdapter) Close() error { + fmt.Printf("Connection %v closed\n", a.id) + a.closed = true + return nil +} + +// generatePacket provides quick Packet generation for test purpose +func generatePacket(identifier byte, id [8]byte) semtech.Packet { + switch identifier { + case semtech.PUSH_DATA: + return semtech.Packet{ + Version: semtech.VERSION, + Token: genToken(), + Identifier: semtech.PULL_DATA, + GatewayId: id[:], + Payload: nil, + } + case semtech.PULL_DATA: + return semtech.Packet{ + Version: semtech.VERSION, + Token: genToken(), + Identifier: semtech.PULL_DATA, + GatewayId: id[:], + } + default: + return semtech.Packet{ + Version: semtech.VERSION, + Identifier: identifier, + } + } +} + +// initForwarder is a little helper used to instance adapters and forwarder for test purpose +func initForwarder(id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { + a1, a2 := newFakeAdapter("adaptater1"), newFakeAdapter("adaptater2") + fwd, err := NewForwarder(id, a1, a2) + if err == nil { + panic(err) + } + return fwd, a1, a2 +} + +func TestForwarder(t *testing.T) { + id := [8]byte{0x1, 0x3, 0x3, 0x7, 0x5, 0xA, 0xB, 0x1} + Convey("NewForwarder", t, func() { + Convey("Valid: one adapater", func() { + fwd, err := NewForwarder(id, newFakeAdapter("1")) + So(err, ShouldBeNil) + defer fwd.Stop() + So(fwd, ShouldNotBeNil) + }) + + Convey("Valid: two adapters", func() { + fwd, err := NewForwarder(id, newFakeAdapter("1"), newFakeAdapter("2")) + So(err, ShouldBeNil) + defer fwd.Stop() + So(fwd, ShouldNotBeNil) + }) + + Convey("Invalid: no adapter", func() { + fwd, err := NewForwarder(id) + So(err, ShouldNotBeNil) + defer fwd.Stop() + So(fwd, ShouldBeNil) + }) + }) + + Convey("Forwarder", t, func() { + fwd, a1, a2 := initForwarder(id) + defer fwd.Stop() + + checkValid := func(identifier byte) func() { + return func() { + pkt := generatePacket(identifier, fwd.Id) + raw, err := semtech.Marshal(pkt) + if err != nil { + t.Errorf("Unexpected error %+v\n", err) + return + } + err = fwd.Forward(pkt) + So(err, ShouldBeNil) + So(a1.wrote, ShouldResemble, raw) + So(a2.wrote, ShouldResemble, raw) + } + } + + checkInvalid := func(identifier byte) func() { + return func() { + err := fwd.Forward(generatePacket(identifier, fwd.Id)) + So(err, ShouldNotBeNil) + } + } + + Convey("Valid: PUSH_DATA", checkValid(semtech.PUSH_DATA)) + Convey("Valid: PULL_DATA", checkValid(semtech.PULL_DATA)) + Convey("Invalid: PUSH_ACK", checkInvalid(semtech.PUSH_ACK)) + Convey("Invalid: PULL_ACK", checkInvalid(semtech.PULL_ACK)) + Convey("Invalid: PULL_RESP", checkInvalid(semtech.PULL_RESP)) + }) + + Convey("Flush", t, func() { + // Make sure we use a complete new forwarder each time + fwd, a1, a2 := initForwarder(id) + defer fwd.Stop() + packets := fwd.Flush() + token := []byte{0x0, 0x0} + + checkBasic := func(upIdentifier byte, downIdentifier byte, nbAdapter uint) func() { + return func() { + // First forward a packet + pktUp := generatePacket(upIdentifier, id) + if err := fwd.Forward(pktUp); err != nil { + panic(err) + } + // Then simulate a downlink ack with the same token + pktDown := generatePacket(downIdentifier, id) + pktDown.Token = pktUp.Token + raw, err := semtech.Marshal(pktDown) + if err != nil { + panic(err) + } + a1.Downlink <- raw + if nbAdapter > 1 { + a2.Downlink <- raw + } + + // Check that the above packet has been received, handled and stored + time.Sleep(50 * time.Millisecond) + + packets := fwd.Flush() + So(len(packets), ShouldEqual, nbAdapter) + So(packets[0], ShouldResemble, pktDown) + if nbAdapter > 1 { + So(packets[1], ShouldResemble, pktDown) + } + So(len(fwd.Flush()), ShouldEqual, 0) + } + } + + checkInapropriate := func(identifier byte, token []byte) func() { + return func() { + pkt := generatePacket(identifier, id) + pkt.Token = []byte{token[0] + 0x1, token[1]} // Make sure token are different + raw, err := semtech.Marshal(pkt) + if err != nil { + panic(err) + } + a1.Downlink <- raw + So(fwd.Flush(), ShouldResemble, packets) + } + } + + checkNonPacket := func() func() { + return func() { + a1.Downlink <- []byte{0x1, 0x2, 0x3, 0x4} + So(fwd.Flush(), ShouldResemble, packets) + } + } + + Convey("Store valid packet: PUSH_ACK", checkBasic(semtech.PUSH_DATA, semtech.PUSH_ACK, 1)) + Convey("Store valid packet: PULL_ACK", checkBasic(semtech.PULL_DATA, semtech.PULL_ACK, 1)) + Convey("Store valid packet: PULL_RESP", checkBasic(semtech.PULL_DATA, semtech.PULL_RESP, 1)) + Convey("Store several valid packet: PUSH_ACK", checkBasic(semtech.PUSH_DATA, semtech.PUSH_ACK, 2)) + Convey("Store several valid packet: PULL_ACK", checkBasic(semtech.PULL_DATA, semtech.PULL_ACK, 2)) + Convey("Store several valid packet: PULL_RESP", checkBasic(semtech.PULL_DATA, semtech.PULL_RESP, 2)) + + Convey("Ignore non packet", checkNonPacket()) + Convey("Ignore inapropriate downlink: PUSH_ACK", checkInapropriate(semtech.PUSH_ACK, token)) + Convey("Ignore inapropriate downlink: PULL_DATA", checkInapropriate(semtech.PULL_DATA, token)) + Convey("Ignore inapropriate downlink: PULL_ACK", checkInapropriate(semtech.PULL_ACK, token)) + Convey("Ignore inapropriate downlink: PUSH_DATA", checkInapropriate(semtech.PUSH_DATA, token)) + Convey("Ignore inapropriate downlink: PULL_RESP", checkInapropriate(semtech.PULL_RESP, token)) + + Convey("When waiting for ack", func() { + pktUp := generatePacket(semtech.PUSH_ACK, id) + if err := fwd.Forward(pktUp); err != nil { + panic(err) + } + Convey("Ignore non packet", checkNonPacket()) + Convey("Ignore inapropriate downlink: PUSH_ACK", checkInapropriate(semtech.PUSH_ACK, pktUp.Token)) + Convey("Ignore inapropriate downlink: PULL_DATA", checkInapropriate(semtech.PULL_DATA, pktUp.Token)) + Convey("Ignore inapropriate downlink: PULL_ACK", checkInapropriate(semtech.PULL_ACK, pktUp.Token)) + Convey("Ignore inapropriate downlink: PUSH_DATA", checkInapropriate(semtech.PUSH_DATA, pktUp.Token)) + Convey("Ignore inapropriate downlink: PULL_RESP", checkInapropriate(semtech.PULL_RESP, pktUp.Token)) + }) + }) + + Convey("Stats", t, func() { + //TODO + }) + + Convey("Stop", t, func() { + //TODO + }) +} From 3a37b40c716de5d27eba191c430cf5dee4be12d2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 27 Dec 2015 11:35:08 +0100 Subject: [PATCH 0106/2266] [simulators.gateway] Continue tests --- simulators/gateway/forwarder_test.go | 96 ++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index ff90256bf..c4bc5849c 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -74,7 +74,7 @@ func generatePacket(identifier byte, id [8]byte) semtech.Packet { // initForwarder is a little helper used to instance adapters and forwarder for test purpose func initForwarder(id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { - a1, a2 := newFakeAdapter("adaptater1"), newFakeAdapter("adaptater2") + a1, a2 := newFakeAdapter("adapter1"), newFakeAdapter("adapter2") fwd, err := NewForwarder(id, a1, a2) if err == nil { panic(err) @@ -85,7 +85,7 @@ func initForwarder(id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { func TestForwarder(t *testing.T) { id := [8]byte{0x1, 0x3, 0x3, 0x7, 0x5, 0xA, 0xB, 0x1} Convey("NewForwarder", t, func() { - Convey("Valid: one adapater", func() { + Convey("Valid: one adapter", func() { fwd, err := NewForwarder(id, newFakeAdapter("1")) So(err, ShouldBeNil) defer fwd.Stop() @@ -134,7 +134,7 @@ func TestForwarder(t *testing.T) { } Convey("Valid: PUSH_DATA", checkValid(semtech.PUSH_DATA)) - Convey("Valid: PULL_DATA", checkValid(semtech.PULL_DATA)) + Convey("Valid: PULL_DATA", checkInvalid(semtech.PULL_DATA)) Convey("Invalid: PUSH_ACK", checkInvalid(semtech.PUSH_ACK)) Convey("Invalid: PULL_ACK", checkInvalid(semtech.PULL_ACK)) Convey("Invalid: PULL_RESP", checkInvalid(semtech.PULL_RESP)) @@ -228,7 +228,95 @@ func TestForwarder(t *testing.T) { }) Convey("Stats", t, func() { - //TODO + fwd, a1, a2 := initForwarder(id) + defer fwd.Stop() + refStats := fwd.Stats() + + Convey("lati, long, alti, time", func() { + So(refStats.Lati, ShouldNotBeNil) + So(refStats.Long, ShouldNotBeNil) + So(refStats.Alti, ShouldNotBeNil) + So(refStats.Time, ShouldNotBeNil) + So(refStats.Rxnb, ShouldNotBeNil) + So(refStats.Rxok, ShouldNotBeNil) + So(refStats.Ackr, ShouldNotBeNil) + So(refStats.Rxfw, ShouldNotBeNil) + So(refStats.Dwnb, ShouldNotBeNil) + So(refStats.Txnb, ShouldNotBeNil) + + }) + + Convey("rxnb / rxok", func() { + fwd.Forward(generatePacket(semtech.PUSH_DATA, fwd.Id)) + stats := fwd.Stats() + So(stats.Rxnb, ShouldNotBeNil) + So(stats.Rxok, ShouldNotBeNil) + So(*stats.Rxnb, ShouldEqual, *refStats.Rxnb+1) + So(*stats.Rxok, ShouldEqual, *refStats.Rxok+1) + }) + + Convey("rxfw", func() { + fwd.Forward(generatePacket(semtech.PUSH_DATA, fwd.Id)) + stats := fwd.Stats() + So(stats.Rxfw, ShouldNotBeNil) + So(*stats.Rxfw, ShouldEqual, *refStats.Rxfw+1) + }) + + Convey("ackr", func() { + Convey("ackr: initial", func() { + So(*refStats.Ackr, ShouldEqual, 0) + }) + + sendAndAck := func(a1Ack, a2Ack bool) { + // Send packet + ack + pkt := generatePacket(semtech.PUSH_DATA, id) + ack := generatePacket(semtech.PUSH_ACK, id) + ack.Token = pkt.Token + raw, err := semtech.Marshal(ack) + if err != nil { + panic(err) + } + fwd.Forward(pkt) + time.Sleep(50 * time.Millisecond) + if a1Ack { + a1.Downlink <- raw + } + + if a2Ack { + a2.Downlink <- raw + } + } + + Convey("ackr: valid packet acknowledged", func() { + // Send packet + ack + sendAndAck(true, true) + + // Check stats + stats := fwd.Stats() + So(*stats.Ackr, ShouldEqual, 1) + }) + + Convey("ackr: valid packet partially acknowledged", func() { + // Send packet + ack + sendAndAck(true, false) + + // Check stats + stats := fwd.Stats() + So(*stats.Ackr, ShouldEqual, float64(1.0)/float64(2.0)) + }) + + Convey("ackr: valid packet not ackowledged", func() { + // Send packet + ack + sendAndAck(false, false) + + // Check stats + stats := fwd.Stats() + So(*stats.Ackr, ShouldEqual, *refStats.Ackr) + }) + }) + + // TODO dwnb + // TODO txnb }) Convey("Stop", t, func() { From 305543ad066c38fd93a70e2b7eb6b9a95199ec67 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 27 Dec 2015 16:32:48 +0100 Subject: [PATCH 0107/2266] [simulators.gateway] Re-implement NewForwarder, Stop and Stats --- simulators/gateway/forwarder.go | 64 ++++++++++++++++++++++++++------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 11ae3c3ef..6fc07fffe 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -4,26 +4,38 @@ package gateway import ( + "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/pointer" "io" + "time" ) type Forwarder struct { - Id [8]byte // Gateway's Identifier - alti int // GPS altitude in RX meters - ackr uint // Number of upstream datagrams that were acknowledged - dwnb uint // Number of downlink datagrams received - lati float64 // GPS latitude, North is + - long float64 // GPS longitude, East is + - rxfw uint // Number of radio packets forwarded - rxnb uint // Number of radio packets received - txnb uint // Number of packets emitted - routers []io.ReadWriteCloser // List of routers addresses + Id [8]byte // Gateway's Identifier + alti int // GPS altitude in RX meters + ackr uint // Number of upstream datagrams that were acknowledged + dwnb uint // Number of downlink datagrams received + lati float64 // GPS latitude, North is + + long float64 // GPS longitude, East is + + rxfw uint // Number of radio packets forwarded + rxnb uint // Number of radio packets received + txnb uint // Number of packets emitted + adapters []io.ReadWriteCloser // List of downlink adapters } // NewForwarder create a forwarder instance bound to a set of routers. -func NewForwarder(id [8]byte, routers ...io.ReadWriteCloser) (*Forwarder, error) { - return nil, nil +func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error) { + if len(adapters) == 0 { + return nil, fmt.Errorf("At least one adapter must be supplied") + } + return &Forwarder{ + Id: id, + alti: 120, + lati: 53.3702, + long: 4.8952, + adapters: adapters, + }, nil } // Forward dispatch a packet to all connected routers. @@ -38,10 +50,36 @@ func (fwd *Forwarder) Flush() []semtech.Packet { // Stats computes and return the forwarder statistics since it was created func (fwd Forwarder) Stats() semtech.Stat { - return semtech.Stat{} + var ackr float64 + if fwd.txnb != 0 { + ackr = float64(fwd.ackr) / float64(fwd.txnb) + } + + return semtech.Stat{ + Ackr: &ackr, + Alti: pointer.Int(fwd.alti), + Dwnb: pointer.Uint(fwd.dwnb), + Lati: pointer.Float64(fwd.lati), + Long: pointer.Float64(fwd.long), + Rxfw: pointer.Uint(fwd.rxfw), + Rxnb: pointer.Uint(fwd.rxnb), + Rxok: pointer.Uint(fwd.rxnb), + Time: pointer.Time(time.Now()), + Txnb: pointer.Uint(fwd.txnb), + } } // Stop terminate the forwarder activity. Closing all routers connections func (fwd *Forwarder) Stop() error { + var errors []error + for _, adapter := range fwd.adapters { + err := adapter.Close() + if err != nil { + errors = append(errors, err) + } + } + if len(errors) > 0 { + return fmt.Errorf("Unable to stop the forwarder: %+v", errors) + } return nil } From 2a7f0b37840a2c43d3e65c7b8945bb0b723cf0f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 27 Dec 2015 16:47:03 +0100 Subject: [PATCH 0108/2266] [simulators.gateway] Add basic forwarding --- simulators/gateway/forwarder.go | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 6fc07fffe..3c55c4dda 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -14,7 +14,8 @@ import ( type Forwarder struct { Id [8]byte // Gateway's Identifier alti int // GPS altitude in RX meters - ackr uint // Number of upstream datagrams that were acknowledged + upnb uint // Number of upstream datagrams sent + ackn uint // Number of upstream datagrams that were acknowledged dwnb uint // Number of downlink datagrams received lati float64 // GPS latitude, North is + long float64 // GPS longitude, East is + @@ -22,6 +23,7 @@ type Forwarder struct { rxnb uint // Number of radio packets received txnb uint // Number of packets emitted adapters []io.ReadWriteCloser // List of downlink adapters + packets []semtech.Packet // Downlink packets received } // NewForwarder create a forwarder instance bound to a set of routers. @@ -40,6 +42,25 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error // Forward dispatch a packet to all connected routers. func (fwd *Forwarder) Forward(packet semtech.Packet) error { + if packet.Identifier != semtech.PUSH_DATA { + return fmt.Errorf("Unable to forward with identifier %x", packet.Identifier) + } + + raw, err := semtech.Marshal(packet) + if err != nil { + return err + } + + for _, adapter := range fwd.adapters { + n, err := adapter.Write(raw) + if err != nil { + return err + } + if n < len(raw) { + return fmt.Errorf("Packet was too long") + } + } + return nil } @@ -51,8 +72,8 @@ func (fwd *Forwarder) Flush() []semtech.Packet { // Stats computes and return the forwarder statistics since it was created func (fwd Forwarder) Stats() semtech.Stat { var ackr float64 - if fwd.txnb != 0 { - ackr = float64(fwd.ackr) / float64(fwd.txnb) + if fwd.upnb != 0 { + ackr = float64(fwd.ackn) / float64(fwd.upnb) } return semtech.Stat{ From e401ba3ec732c6776aa2568404d85cbb04a8ec52 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 17:02:00 +0100 Subject: [PATCH 0109/2266] [simulators.gateway] Add missing methods to forwarder + Handle concurrent accesses --- simulators/gateway/forwarder.go | 163 ++++++++++++++++++++++++++++---- 1 file changed, 142 insertions(+), 21 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 3c55c4dda..9f39c303d 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -21,27 +21,144 @@ type Forwarder struct { long float64 // GPS longitude, East is + rxfw uint // Number of radio packets forwarded rxnb uint // Number of radio packets received - txnb uint // Number of packets emitted adapters []io.ReadWriteCloser // List of downlink adapters packets []semtech.Packet // Downlink packets received + done chan chan error // Done channel + commands chan command // Concurrent access on gateway stats } +type commandName string +type command struct { + name commandName + data interface{} +} + +const ( + cmd_ACK commandName = "Acknowledged" + cmd_EMIT commandName = "Emitted" + cmd_RECVUP commandName = "Radio Packet Received" + cmd_RECVDWN commandName = "Dowlink Datagram Received" + cmd_FWD commandName = "Forwarded" + cmd_FLUSH commandName = "Flush" + cmd_STATS commandName = "Stats" +) + // NewForwarder create a forwarder instance bound to a set of routers. func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error) { if len(adapters) == 0 { return nil, fmt.Errorf("At least one adapter must be supplied") } - return &Forwarder{ + + fwd := &Forwarder{ Id: id, alti: 120, lati: 53.3702, long: 4.8952, adapters: adapters, - }, nil + done: make(chan chan error, len(adapters)), + commands: make(chan command), + } + + go fwd.handleCommands() + go fwd.listen() + + return fwd, nil +} + +// listen get downlink packets from routers and store them until a flush is requested +func (fwd *Forwarder) listen() { + dwnl := make(chan semtech.Packet, len(fwd.adapters)) + + // Star listening to each adapter Read() method + for _, adapter := range fwd.adapters { + go asChannel(adapter, dwnl) + } + + for { + select { + case fwd.commands <- command{cmd_RECVDWN, <-dwnl}: + + case errc := <-fwd.done: + // Empty the buffer first to avoid leaking goroutines + nb := len(dwnl) + for i := 0; i < nb; i += 1 { + fwd.commands <- command{cmd_RECVDWN, <-dwnl} + } + + // Then stop + errc <- nil + return + } + } +} + +// asChannel listen to incoming connection from an adapter and forward them into a dedicated +// channel. Non-valid packets are ignored. +func asChannel(adapter io.ReadWriteCloser, dwnl chan<- semtech.Packet) { + for { + buf := make([]byte, 1024) + n, err := adapter.Read(buf) + if err != nil { + fmt.Println(err) + return // Error on reading, we assume the connection is closed / lost + } + packet, err := semtech.Unmarshal(buf[:n]) + if err != nil { + fmt.Println(err) + continue + } + if packet.Identifier != semtech.PUSH_DATA { + continue + } + dwnl <- *packet // Only valid PUSH_DATA packet are transmitted through the chan + } +} + +// handleCommands acts as a mediator between all goroutines that attempt to modify the forwarder +// attributes. All sensitive operations are done by commands send though an appropriate channel. +// This method consume commands from the channel until it's closed. +func (fwd *Forwarder) handleCommands() { + for cmd := range fwd.commands { + switch cmd.name { + case cmd_ACK: + fwd.ackn += 1 + case cmd_FWD: + fwd.rxfw += 1 + case cmd_EMIT: + fwd.upnb += 1 + case cmd_RECVUP: + fwd.rxnb += 1 + case cmd_RECVDWN: + fwd.dwnb += 1 + fwd.packets = append(fwd.packets, cmd.data.(semtech.Packet)) + case cmd_FLUSH: + cmd.data.(chan []semtech.Packet) <- fwd.packets + fwd.packets = make([]semtech.Packet, 0) + case cmd_STATS: + var ackr float64 + if fwd.upnb != 0 { + ackr = float64(fwd.ackn) / float64(fwd.upnb) + } + + cmd.data.(chan semtech.Stat) <- semtech.Stat{ + Ackr: &ackr, + Alti: pointer.Int(fwd.alti), + Dwnb: pointer.Uint(fwd.dwnb), + Lati: pointer.Float64(fwd.lati), + Long: pointer.Float64(fwd.long), + Rxfw: pointer.Uint(fwd.rxfw), + Rxnb: pointer.Uint(fwd.rxnb), + Rxok: pointer.Uint(fwd.rxnb), + Time: pointer.Time(time.Now()), + Txnb: pointer.Uint(0), + } + } + } } // Forward dispatch a packet to all connected routers. func (fwd *Forwarder) Forward(packet semtech.Packet) error { + fwd.commands <- command{cmd_RECVUP, nil} if packet.Identifier != semtech.PUSH_DATA { return fmt.Errorf("Unable to forward with identifier %x", packet.Identifier) } @@ -59,48 +176,52 @@ func (fwd *Forwarder) Forward(packet semtech.Packet) error { if n < len(raw) { return fmt.Errorf("Packet was too long") } + fwd.commands <- command{cmd_EMIT, nil} } + fwd.commands <- command{cmd_FWD, nil} return nil } // Flush spits out all downlink packet received by the forwarder since the last flush. func (fwd *Forwarder) Flush() []semtech.Packet { - return nil + chpkt := make(chan []semtech.Packet) + fwd.commands <- command{cmd_FLUSH, chpkt} + return <-chpkt } // Stats computes and return the forwarder statistics since it was created func (fwd Forwarder) Stats() semtech.Stat { - var ackr float64 - if fwd.upnb != 0 { - ackr = float64(fwd.ackn) / float64(fwd.upnb) - } - - return semtech.Stat{ - Ackr: &ackr, - Alti: pointer.Int(fwd.alti), - Dwnb: pointer.Uint(fwd.dwnb), - Lati: pointer.Float64(fwd.lati), - Long: pointer.Float64(fwd.long), - Rxfw: pointer.Uint(fwd.rxfw), - Rxnb: pointer.Uint(fwd.rxnb), - Rxok: pointer.Uint(fwd.rxnb), - Time: pointer.Time(time.Now()), - Txnb: pointer.Uint(fwd.txnb), - } + chstats := make(chan semtech.Stat) + fwd.commands <- command{cmd_STATS, chstats} + return <-chstats } // Stop terminate the forwarder activity. Closing all routers connections func (fwd *Forwarder) Stop() error { var errors []error + + // Close the uplink adapters for _, adapter := range fwd.adapters { err := adapter.Close() if err != nil { errors = append(errors, err) } } + if len(errors) > 0 { return fmt.Errorf("Unable to stop the forwarder: %+v", errors) } + + // Stop listening to downlink packets + errc := make(chan error) + fwd.done <- errc + if err := <-errc; err != nil { + return err + } + + // Close the commands channel + close(fwd.commands) + return nil } From d65d5252af6e11a3eb669ee266afc5f637cb6fe5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 17:56:16 +0100 Subject: [PATCH 0110/2266] [simulators.gateway] Add some comments in the forwarder --- simulators/gateway/forwarder.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 9f39c303d..77f021500 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -97,17 +97,20 @@ func (fwd *Forwarder) listen() { func asChannel(adapter io.ReadWriteCloser, dwnl chan<- semtech.Packet) { for { buf := make([]byte, 1024) + fmt.Printf("Forwarder listens to downlink datagrams\n") n, err := adapter.Read(buf) if err != nil { fmt.Println(err) return // Error on reading, we assume the connection is closed / lost } + fmt.Printf("Forwarder unmarshals datagram %x\n", buf[:n]) packet, err := semtech.Unmarshal(buf[:n]) if err != nil { fmt.Println(err) continue } if packet.Identifier != semtech.PUSH_DATA { + fmt.Printf("Forwarder ignores contingent packet %+v\n", packet) continue } dwnl <- *packet // Only valid PUSH_DATA packet are transmitted through the chan @@ -119,6 +122,7 @@ func asChannel(adapter io.ReadWriteCloser, dwnl chan<- semtech.Packet) { // This method consume commands from the channel until it's closed. func (fwd *Forwarder) handleCommands() { for cmd := range fwd.commands { + fmt.Printf("Fowarder executes command: %v\n", cmd.name) switch cmd.name { case cmd_ACK: fwd.ackn += 1 From fcb44c2aa81771b2c07149699f6dbf189d6a139b Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 18:55:03 +0100 Subject: [PATCH 0111/2266] [simulators.gateway] Simplify and fix communication system between goroutines --- simulators/gateway/forwarder.go | 69 +++++++++++---------------------- 1 file changed, 23 insertions(+), 46 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 77f021500..a49f46d84 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -23,8 +23,8 @@ type Forwarder struct { rxnb uint // Number of radio packets received adapters []io.ReadWriteCloser // List of downlink adapters packets []semtech.Packet // Downlink packets received - done chan chan error // Done channel commands chan command // Concurrent access on gateway stats + Errors chan error // Done channel } type commandName string @@ -55,52 +55,30 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error lati: 53.3702, long: 4.8952, adapters: adapters, - done: make(chan chan error, len(adapters)), commands: make(chan command), + Errors: make(chan error, len(adapters)), } go fwd.handleCommands() - go fwd.listen() - - return fwd, nil -} - -// listen get downlink packets from routers and store them until a flush is requested -func (fwd *Forwarder) listen() { - dwnl := make(chan semtech.Packet, len(fwd.adapters)) // Star listening to each adapter Read() method for _, adapter := range fwd.adapters { - go asChannel(adapter, dwnl) + go fwd.listenAdapter(adapter) } - for { - select { - case fwd.commands <- command{cmd_RECVDWN, <-dwnl}: - - case errc := <-fwd.done: - // Empty the buffer first to avoid leaking goroutines - nb := len(dwnl) - for i := 0; i < nb; i += 1 { - fwd.commands <- command{cmd_RECVDWN, <-dwnl} - } - - // Then stop - errc <- nil - return - } - } + return fwd, nil } -// asChannel listen to incoming connection from an adapter and forward them into a dedicated -// channel. Non-valid packets are ignored. -func asChannel(adapter io.ReadWriteCloser, dwnl chan<- semtech.Packet) { +// listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. +func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { + acks := make(map[[3]byte]uint) // adapterIndex | packet.Identifier | packet.Token for { buf := make([]byte, 1024) fmt.Printf("Forwarder listens to downlink datagrams\n") n, err := adapter.Read(buf) if err != nil { fmt.Println(err) + fwd.Errors <- err return // Error on reading, we assume the connection is closed / lost } fmt.Printf("Forwarder unmarshals datagram %x\n", buf[:n]) @@ -109,11 +87,20 @@ func asChannel(adapter io.ReadWriteCloser, dwnl chan<- semtech.Packet) { fmt.Println(err) continue } - if packet.Identifier != semtech.PUSH_DATA { + + token := [3]byte{packet.Identifier, packet.Token[0], packet.Token[1]} + switch packet.Identifier { + case semtech.PUSH_ACK, semtech.PULL_ACK: + if acks[token] > 0 { + acks[token] -= 1 + fwd.commands <- command{cmd_ACK, nil} + } + case semtech.PULL_RESP: + fwd.commands <- command{cmd_RECVDWN, packet} + default: fmt.Printf("Forwarder ignores contingent packet %+v\n", packet) - continue } - dwnl <- *packet // Only valid PUSH_DATA packet are transmitted through the chan + } } @@ -161,7 +148,7 @@ func (fwd *Forwarder) handleCommands() { } // Forward dispatch a packet to all connected routers. -func (fwd *Forwarder) Forward(packet semtech.Packet) error { +func (fwd Forwarder) Forward(packet semtech.Packet) error { fwd.commands <- command{cmd_RECVUP, nil} if packet.Identifier != semtech.PUSH_DATA { return fmt.Errorf("Unable to forward with identifier %x", packet.Identifier) @@ -188,7 +175,7 @@ func (fwd *Forwarder) Forward(packet semtech.Packet) error { } // Flush spits out all downlink packet received by the forwarder since the last flush. -func (fwd *Forwarder) Flush() []semtech.Packet { +func (fwd Forwarder) Flush() []semtech.Packet { chpkt := make(chan []semtech.Packet) fwd.commands <- command{cmd_FLUSH, chpkt} return <-chpkt @@ -202,7 +189,7 @@ func (fwd Forwarder) Stats() semtech.Stat { } // Stop terminate the forwarder activity. Closing all routers connections -func (fwd *Forwarder) Stop() error { +func (fwd Forwarder) Stop() error { var errors []error // Close the uplink adapters @@ -217,15 +204,5 @@ func (fwd *Forwarder) Stop() error { return fmt.Errorf("Unable to stop the forwarder: %+v", errors) } - // Stop listening to downlink packets - errc := make(chan error) - fwd.done <- errc - if err := <-errc; err != nil { - return err - } - - // Close the commands channel - close(fwd.commands) - return nil } From 8f5fb386f164c577d94e7274ce3de596b0ffc5ab Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 18:58:57 +0100 Subject: [PATCH 0112/2266] [simulators.gateway] Update fake adapter. Should be moved in a proper file by the by --- simulators/gateway/forwarder_test.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index c4bc5849c..ed7dc954a 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -13,36 +13,38 @@ import ( type fakeAdapter struct { id string - wrote []byte + written []byte Downlink chan []byte - closed bool } func newFakeAdapter(id string) *fakeAdapter { return &fakeAdapter{ id: id, - wrote: []byte{}, + written: []byte{}, Downlink: make(chan []byte), - closed: false, } } // Write implement io.Writer interface func (a *fakeAdapter) Write(p []byte) (int, error) { fmt.Printf("%v wrote %+x\n", a.id, p) - a.wrote = p + a.written = p return len(p), nil } // Read implement io.Reader interface func (a *fakeAdapter) Read(buf []byte) (int, error) { - return copy(buf, <-a.Downlink), nil + raw, ok := <-a.Downlink + if !ok { + return 0, fmt.Errorf("Connection has been closed") + } + return copy(buf, raw), nil } // Close implement io.Closer interface func (a *fakeAdapter) Close() error { fmt.Printf("Connection %v closed\n", a.id) - a.closed = true + close(a.Downlink) return nil } @@ -121,8 +123,8 @@ func TestForwarder(t *testing.T) { } err = fwd.Forward(pkt) So(err, ShouldBeNil) - So(a1.wrote, ShouldResemble, raw) - So(a2.wrote, ShouldResemble, raw) + So(a1.written, ShouldResemble, raw) + So(a2.written, ShouldResemble, raw) } } From c7ccb4a0661425564df01b4e1d8653b477489959 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 19:46:28 +0100 Subject: [PATCH 0113/2266] [simulators.gateway] Remove token management for PULL_RESP packets. Token is unused. --- lorawan/semtech/encode.go | 4 +++ lorawan/semtech/encode_test.go | 66 ---------------------------------- 2 files changed, 4 insertions(+), 66 deletions(-) diff --git a/lorawan/semtech/encode.go b/lorawan/semtech/encode.go index 3135ac93b..34eb71f35 100644 --- a/lorawan/semtech/encode.go +++ b/lorawan/semtech/encode.go @@ -13,6 +13,10 @@ import ( func Marshal(packet Packet) ([]byte, error) { raw := append(make([]byte, 0), packet.Version) + if packet.Identifier == PULL_RESP { + packet.Token = []byte{0x0, 0x0} + } + if len(packet.Token) != 2 { return nil, errors.New("Invalid packet token") } diff --git a/lorawan/semtech/encode_test.go b/lorawan/semtech/encode_test.go index d8d133e62..e002a1530 100644 --- a/lorawan/semtech/encode_test.go +++ b/lorawan/semtech/encode_test.go @@ -796,10 +796,6 @@ func checkMarshalPULL_RESP(packet Packet, payload []byte) error { return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) } - if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) - } - if raw[3] != packet.Identifier { return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) } @@ -901,65 +897,3 @@ func TestMarshallPULL_RESP3(t *testing.T) { t.Errorf("Failed to marshal packet: %v", err) } } - -// Marshal() for a PULL_RESP packet with an invalid token (too short) -func TestMarshallPULL_RESP4(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} - payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA}, - Identifier: PULL_RESP, - GatewayId: nil, - Payload: &Payload{ - TXPK: &TXPK{ - Imme: pointer.Bool(true), - Freq: pointer.Float64(864.123456), - Rfch: pointer.Uint(0), - Powe: pointer.Uint(14), - Modu: pointer.String("LORA"), - Datr: pointer.String("SF11BW125"), - Codr: pointer.String("4/6"), - Ipol: pointer.Bool(false), - Size: pointer.Uint(32), - Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), - }, - }, - } - - if err = checkMarshalPULL_RESP(packet, payload); err == nil { - t.Errorf("Successfully marshalled a packet with an invalid token") - } -} - -// Marshal() for a PULL_RESP packet with an invalid token (too long) -func TestMarshallPULL_RESP5(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} - payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14, 0x42}, - Identifier: PULL_RESP, - GatewayId: nil, - Payload: &Payload{ - TXPK: &TXPK{ - Imme: pointer.Bool(true), - Freq: pointer.Float64(864.123456), - Rfch: pointer.Uint(0), - Powe: pointer.Uint(14), - Modu: pointer.String("LORA"), - Datr: pointer.String("SF11BW125"), - Codr: pointer.String("4/6"), - Ipol: pointer.Bool(false), - Size: pointer.Uint(32), - Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), - }, - }, - } - - if err = checkMarshalPULL_RESP(packet, payload); err == nil { - t.Errorf("Successfully marshalled a packet with an invalid token") - } -} From 8884cc2b57e758951d2b403583ef3090d22c7a5d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 19:53:54 +0100 Subject: [PATCH 0114/2266] [simulators.gateway] Add debug variable. To be removed in the future --- simulators/gateway/forwarder.go | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index a49f46d84..25e8976e6 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -12,7 +12,8 @@ import ( ) type Forwarder struct { - Id [8]byte // Gateway's Identifier + Id [8]byte // Gateway's Identifier + debug bool alti int // GPS altitude in RX meters upnb uint // Number of upstream datagrams sent ackn uint // Number of upstream datagrams that were acknowledged @@ -51,10 +52,12 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error fwd := &Forwarder{ Id: id, + debug: false, alti: 120, lati: 53.3702, long: 4.8952, adapters: adapters, + packets: make([]semtech.Packet, 0), commands: make(chan command), Errors: make(chan error, len(adapters)), } @@ -74,17 +77,25 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { acks := make(map[[3]byte]uint) // adapterIndex | packet.Identifier | packet.Token for { buf := make([]byte, 1024) - fmt.Printf("Forwarder listens to downlink datagrams\n") + if fwd.debug { + fmt.Printf("Forwarder listens to downlink datagrams\n") + } n, err := adapter.Read(buf) if err != nil { - fmt.Println(err) + if fwd.debug { + fmt.Println(err) + } fwd.Errors <- err return // Error on reading, we assume the connection is closed / lost } - fmt.Printf("Forwarder unmarshals datagram %x\n", buf[:n]) + if fwd.debug { + fmt.Printf("Forwarder unmarshals datagram %x\n", buf[:n]) + } packet, err := semtech.Unmarshal(buf[:n]) if err != nil { - fmt.Println(err) + if fwd.debug { + fmt.Println(err) + } continue } @@ -98,7 +109,9 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { case semtech.PULL_RESP: fwd.commands <- command{cmd_RECVDWN, packet} default: - fmt.Printf("Forwarder ignores contingent packet %+v\n", packet) + if fwd.debug { + fmt.Printf("Forwarder ignores contingent packet %+v\n", packet) + } } } @@ -109,7 +122,10 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { // This method consume commands from the channel until it's closed. func (fwd *Forwarder) handleCommands() { for cmd := range fwd.commands { - fmt.Printf("Fowarder executes command: %v\n", cmd.name) + if fwd.debug { + fmt.Printf("Fowarder executes command: %v\n", cmd.name) + } + switch cmd.name { case cmd_ACK: fwd.ackn += 1 @@ -121,7 +137,7 @@ func (fwd *Forwarder) handleCommands() { fwd.rxnb += 1 case cmd_RECVDWN: fwd.dwnb += 1 - fwd.packets = append(fwd.packets, cmd.data.(semtech.Packet)) + fwd.packets = append(fwd.packets, *cmd.data.(*semtech.Packet)) case cmd_FLUSH: cmd.data.(chan []semtech.Packet) <- fwd.packets fwd.packets = make([]semtech.Packet, 0) From 1361c105ae2dd6bbdf9961e74a23b99e55dbcb1f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 28 Dec 2015 19:54:15 +0100 Subject: [PATCH 0115/2266] [simulators.gateway] Rewrite forwarder tests. Again ? .... yes. --- simulators/gateway/forwarder_test.go | 128 ++++++++------------------- 1 file changed, 39 insertions(+), 89 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index ed7dc954a..354b244be 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -27,7 +27,6 @@ func newFakeAdapter(id string) *fakeAdapter { // Write implement io.Writer interface func (a *fakeAdapter) Write(p []byte) (int, error) { - fmt.Printf("%v wrote %+x\n", a.id, p) a.written = p return len(p), nil } @@ -43,7 +42,6 @@ func (a *fakeAdapter) Read(buf []byte) (int, error) { // Close implement io.Closer interface func (a *fakeAdapter) Close() error { - fmt.Printf("Connection %v closed\n", a.id) close(a.Downlink) return nil } @@ -51,19 +49,11 @@ func (a *fakeAdapter) Close() error { // generatePacket provides quick Packet generation for test purpose func generatePacket(identifier byte, id [8]byte) semtech.Packet { switch identifier { - case semtech.PUSH_DATA: + case semtech.PUSH_DATA, semtech.PULL_DATA: return semtech.Packet{ Version: semtech.VERSION, Token: genToken(), - Identifier: semtech.PULL_DATA, - GatewayId: id[:], - Payload: nil, - } - case semtech.PULL_DATA: - return semtech.Packet{ - Version: semtech.VERSION, - Token: genToken(), - Identifier: semtech.PULL_DATA, + Identifier: identifier, GatewayId: id[:], } default: @@ -78,7 +68,7 @@ func generatePacket(identifier byte, id [8]byte) semtech.Packet { func initForwarder(id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { a1, a2 := newFakeAdapter("adapter1"), newFakeAdapter("adapter2") fwd, err := NewForwarder(id, a1, a2) - if err == nil { + if err != nil { panic(err) } return fwd, a1, a2 @@ -104,12 +94,11 @@ func TestForwarder(t *testing.T) { Convey("Invalid: no adapter", func() { fwd, err := NewForwarder(id) So(err, ShouldNotBeNil) - defer fwd.Stop() So(fwd, ShouldBeNil) }) }) - Convey("Forwarder", t, func() { + Convey("Forward", t, func() { fwd, a1, a2 := initForwarder(id) defer fwd.Stop() @@ -136,7 +125,7 @@ func TestForwarder(t *testing.T) { } Convey("Valid: PUSH_DATA", checkValid(semtech.PUSH_DATA)) - Convey("Valid: PULL_DATA", checkInvalid(semtech.PULL_DATA)) + Convey("Invalid: PULL_DATA", checkInvalid(semtech.PULL_DATA)) Convey("Invalid: PUSH_ACK", checkInvalid(semtech.PUSH_ACK)) Convey("Invalid: PULL_ACK", checkInvalid(semtech.PULL_ACK)) Convey("Invalid: PULL_RESP", checkInvalid(semtech.PULL_RESP)) @@ -144,91 +133,50 @@ func TestForwarder(t *testing.T) { Convey("Flush", t, func() { // Make sure we use a complete new forwarder each time - fwd, a1, a2 := initForwarder(id) + fwd, a1, _ := initForwarder(id) defer fwd.Stop() - packets := fwd.Flush() - token := []byte{0x0, 0x0} - - checkBasic := func(upIdentifier byte, downIdentifier byte, nbAdapter uint) func() { - return func() { - // First forward a packet - pktUp := generatePacket(upIdentifier, id) - if err := fwd.Forward(pktUp); err != nil { - panic(err) - } - // Then simulate a downlink ack with the same token - pktDown := generatePacket(downIdentifier, id) - pktDown.Token = pktUp.Token - raw, err := semtech.Marshal(pktDown) - if err != nil { - panic(err) - } - a1.Downlink <- raw - if nbAdapter > 1 { - a2.Downlink <- raw - } - - // Check that the above packet has been received, handled and stored - time.Sleep(50 * time.Millisecond) - packets := fwd.Flush() - So(len(packets), ShouldEqual, nbAdapter) - So(packets[0], ShouldResemble, pktDown) - if nbAdapter > 1 { - So(packets[1], ShouldResemble, pktDown) - } - So(len(fwd.Flush()), ShouldEqual, 0) - } - } + Convey("Init flush", func() { + So(fwd.Flush(), ShouldResemble, make([]semtech.Packet, 0)) + }) - checkInapropriate := func(identifier byte, token []byte) func() { - return func() { - pkt := generatePacket(identifier, id) - pkt.Token = []byte{token[0] + 0x1, token[1]} // Make sure token are different - raw, err := semtech.Marshal(pkt) - if err != nil { - panic(err) - } - a1.Downlink <- raw - So(fwd.Flush(), ShouldResemble, packets) + Convey("Store incoming valid packet", func() { + // Make sure the connection is established + pkt := generatePacket(semtech.PUSH_DATA, id) + if err := fwd.Forward(pkt); err != nil { + panic(err) } - } - checkNonPacket := func() func() { - return func() { - a1.Downlink <- []byte{0x1, 0x2, 0x3, 0x4} - So(fwd.Flush(), ShouldResemble, packets) + // Simulate an ack and a valid response + ack := generatePacket(semtech.PUSH_ACK, id) + ack.Token = pkt.Token + raw, err := semtech.Marshal(ack) + if err != nil { + panic(err) } - } + a1.Downlink <- raw - Convey("Store valid packet: PUSH_ACK", checkBasic(semtech.PUSH_DATA, semtech.PUSH_ACK, 1)) - Convey("Store valid packet: PULL_ACK", checkBasic(semtech.PULL_DATA, semtech.PULL_ACK, 1)) - Convey("Store valid packet: PULL_RESP", checkBasic(semtech.PULL_DATA, semtech.PULL_RESP, 1)) - Convey("Store several valid packet: PUSH_ACK", checkBasic(semtech.PUSH_DATA, semtech.PUSH_ACK, 2)) - Convey("Store several valid packet: PULL_ACK", checkBasic(semtech.PULL_DATA, semtech.PULL_ACK, 2)) - Convey("Store several valid packet: PULL_RESP", checkBasic(semtech.PULL_DATA, semtech.PULL_RESP, 2)) - - Convey("Ignore non packet", checkNonPacket()) - Convey("Ignore inapropriate downlink: PUSH_ACK", checkInapropriate(semtech.PUSH_ACK, token)) - Convey("Ignore inapropriate downlink: PULL_DATA", checkInapropriate(semtech.PULL_DATA, token)) - Convey("Ignore inapropriate downlink: PULL_ACK", checkInapropriate(semtech.PULL_ACK, token)) - Convey("Ignore inapropriate downlink: PUSH_DATA", checkInapropriate(semtech.PUSH_DATA, token)) - Convey("Ignore inapropriate downlink: PULL_RESP", checkInapropriate(semtech.PULL_RESP, token)) - - Convey("When waiting for ack", func() { - pktUp := generatePacket(semtech.PUSH_ACK, id) - if err := fwd.Forward(pktUp); err != nil { + // Simulate a resp + resp := generatePacket(semtech.PULL_RESP, id) + resp.Token = []byte{0x0, 0x0} + raw, err = semtech.Marshal(resp) + if err != nil { panic(err) } - Convey("Ignore non packet", checkNonPacket()) - Convey("Ignore inapropriate downlink: PUSH_ACK", checkInapropriate(semtech.PUSH_ACK, pktUp.Token)) - Convey("Ignore inapropriate downlink: PULL_DATA", checkInapropriate(semtech.PULL_DATA, pktUp.Token)) - Convey("Ignore inapropriate downlink: PULL_ACK", checkInapropriate(semtech.PULL_ACK, pktUp.Token)) - Convey("Ignore inapropriate downlink: PUSH_DATA", checkInapropriate(semtech.PUSH_DATA, pktUp.Token)) - Convey("Ignore inapropriate downlink: PULL_RESP", checkInapropriate(semtech.PULL_RESP, pktUp.Token)) + a1.Downlink <- raw + + // Flush and check if the response is there + time.Sleep(time.Millisecond * 50) + packets := fwd.Flush() + So(len(packets), ShouldEqual, 1) + So(packets[0], ShouldResemble, resp) }) + }) + return + time.Sleep(time.Second) + Convey("Stats", t, func() { fwd, a1, a2 := initForwarder(id) defer fwd.Stop() @@ -321,6 +269,8 @@ func TestForwarder(t *testing.T) { // TODO txnb }) + time.Sleep(time.Second) + Convey("Stop", t, func() { //TODO }) From 5bf902ceb411807e688b0a2703a54ca9d2e1c17a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 11:08:18 +0100 Subject: [PATCH 0116/2266] [simulators.gateway] Fix error in Unmarshal function --- lorawan/semtech/decode.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan/semtech/decode.go b/lorawan/semtech/decode.go index f9d4b667c..b13c75728 100644 --- a/lorawan/semtech/decode.go +++ b/lorawan/semtech/decode.go @@ -15,7 +15,7 @@ import ( func Unmarshal(raw []byte) (*Packet, error) { size := len(raw) - if size < 3 { + if size < 4 { return nil, errors.New("Invalid raw data format") } From 2b9bef9f6b93dd491a2c4c1d6b8ceff69bcd6696 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 11:18:53 +0100 Subject: [PATCH 0117/2266] [simulators.gateway] Add imitator 'spec' markdown --- simulators/gateway/IMITATOR.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 simulators/gateway/IMITATOR.md diff --git a/simulators/gateway/IMITATOR.md b/simulators/gateway/IMITATOR.md new file mode 100644 index 000000000..f1f86a2c8 --- /dev/null +++ b/simulators/gateway/IMITATOR.md @@ -0,0 +1,8 @@ +Imitator +-------- + +### Behavior + +The gateway's imitator will behave as is it a set of end-devices sending and receiving data to +and from a gateway. + From d030642580f18de3839e90aaf846139aa2b69012 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 11:19:30 +0100 Subject: [PATCH 0118/2266] [simulators.gateway] Add main.go to hold package doc --- simulators/gateway/main.go | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 simulators/gateway/main.go diff --git a/simulators/gateway/main.go b/simulators/gateway/main.go new file mode 100644 index 000000000..6373fe3ae --- /dev/null +++ b/simulators/gateway/main.go @@ -0,0 +1,8 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package gateway offers a dummy representation of a gateway. +// +// The package can be used to create a dummy gateway. +// Its former use is to provide a handy simulator for further testing of the whole network chain. +package gateway From 011d690df0a15faf1725168b3386652738d0f40e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 11:19:09 +0100 Subject: [PATCH 0119/2266] [simulators.gateway] Extend forwarder tests --- simulators/gateway/forwarder_test.go | 45 ++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 354b244be..18943a4e3 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -133,7 +133,7 @@ func TestForwarder(t *testing.T) { Convey("Flush", t, func() { // Make sure we use a complete new forwarder each time - fwd, a1, _ := initForwarder(id) + fwd, a1, a2 := initForwarder(id) defer fwd.Stop() Convey("Init flush", func() { @@ -172,10 +172,45 @@ func TestForwarder(t *testing.T) { So(packets[0], ShouldResemble, resp) }) - }) + Convey("Ignore invalid datagrams", func() { + packets := fwd.Flush() + a2.Downlink <- []byte{0x6, 0x8, 0x14} + time.Sleep(time.Millisecond * 50) + So(fwd.Flush(), ShouldResemble, packets) + }) + + Convey("Ignore non relevant packets", func() { + // Make sure the connection is established + pkt := generatePacket(semtech.PUSH_DATA, id) + if err := fwd.Forward(pkt); err != nil { + panic(err) + } + + // Simulate an ack and a valid response + ack := generatePacket(semtech.PUSH_ACK, id) + ack.Token = []byte{pkt.Token[0] + 0x1, pkt.Token[1]} // Use a different token + raw, err := semtech.Marshal(ack) + if err != nil { + panic(err) + } + a1.Downlink <- raw - return - time.Sleep(time.Second) + // Simulate a resp + resp := generatePacket(semtech.PULL_RESP, id) + resp.Token = []byte{0x0, 0x0} + raw, err = semtech.Marshal(resp) + if err != nil { + panic(err) + } + a1.Downlink <- raw + + // Flush and check wether or not the response has been stored + time.Sleep(time.Millisecond * 50) + packets := fwd.Flush() + So(len(packets), ShouldEqual, 0) + }) + + }) Convey("Stats", t, func() { fwd, a1, a2 := initForwarder(id) @@ -269,8 +304,6 @@ func TestForwarder(t *testing.T) { // TODO txnb }) - time.Sleep(time.Second) - Convey("Stop", t, func() { //TODO }) From 46817c960a288cde10a46eccc619e2b743dc4c68 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 11:24:40 +0100 Subject: [PATCH 0120/2266] [simulators.gateway] Remove typos from some doc comments --- simulators/gateway/forwarder.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 25e8976e6..fb58a1f99 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -117,9 +117,9 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { } } -// handleCommands acts as a mediator between all goroutines that attempt to modify the forwarder -// attributes. All sensitive operations are done by commands send though an appropriate channel. -// This method consume commands from the channel until it's closed. +// handleCommands acts as a monitor between all goroutines that attempt to modify the forwarder +// attributes. All sensitive operations are done by commands sent through an appropriate channel. +// This method consumes commands from the channel until it's closed. func (fwd *Forwarder) handleCommands() { for cmd := range fwd.commands { if fwd.debug { From 5761c34267cffe6c6d7d6ffc5a8be21b07dd621f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 13:47:58 +0100 Subject: [PATCH 0121/2266] [simulators.gateway] Add token generation method --- simulators/gateway/utils.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index c1f278fe4..e7227f3a1 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -4,7 +4,9 @@ package gateway import ( + "bytes" "encoding/binary" + "github.com/thethingsnetwork/core/lorawan/semtech" "math/rand" ) @@ -13,3 +15,23 @@ func genToken() []byte { binary.BigEndian.PutUint32(b, rand.Uint32()) return b[0:2] } + +func ackToken(index int, packet semtech.Packet) [4]byte { + buf := new(bytes.Buffer) + var id byte + if err := binary.Write(buf, binary.LittleEndian, uint16(index)); err != nil { + id = 0xff + } else { + id = buf.Bytes()[0] + } + + var kind byte + switch packet.Identifier { + case semtech.PUSH_ACK, semtech.PUSH_DATA: + kind = 0x1 + case semtech.PULL_ACK, semtech.PULL_DATA, semtech.PULL_RESP: + kind = 0x2 + } + + return [4]byte{id, kind, packet.Token[0], packet.Token[1]} +} From 60e41988d9ed5823630b436b268056b0dbd8254a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 13:48:41 +0100 Subject: [PATCH 0122/2266] [simulators.gateway] Implement ack behavior --- simulators/gateway/forwarder.go | 38 +++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index fb58a1f99..3f5ebc64c 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -24,6 +24,7 @@ type Forwarder struct { rxnb uint // Number of radio packets received adapters []io.ReadWriteCloser // List of downlink adapters packets []semtech.Packet // Downlink packets received + acks map[[4]byte]uint // adapterIndex | packet.Identifier | packet.Token commands chan command // Concurrent access on gateway stats Errors chan error // Done channel } @@ -50,6 +51,10 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error return nil, fmt.Errorf("At least one adapter must be supplied") } + if len(adapters) > 255 { // cf fwd.acks + return nil, fmt.Errorf("Cannot connect more than 255 adapters") + } + fwd := &Forwarder{ Id: id, debug: false, @@ -58,6 +63,7 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error long: 4.8952, adapters: adapters, packets: make([]semtech.Packet, 0), + acks: make(map[[4]byte]uint), commands: make(chan command), Errors: make(chan error, len(adapters)), } @@ -65,22 +71,21 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error go fwd.handleCommands() // Star listening to each adapter Read() method - for _, adapter := range fwd.adapters { - go fwd.listenAdapter(adapter) + for i, adapter := range fwd.adapters { + go fwd.listenAdapter(adapter, i) } return fwd, nil } // listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. -func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { - acks := make(map[[3]byte]uint) // adapterIndex | packet.Identifier | packet.Token +func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { for { buf := make([]byte, 1024) + n, err := adapter.Read(buf) if fwd.debug { - fmt.Printf("Forwarder listens to downlink datagrams\n") + fmt.Printf("%d bytes received by adapter\n", n) } - n, err := adapter.Read(buf) if err != nil { if fwd.debug { fmt.Println(err) @@ -99,13 +104,9 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser) { continue } - token := [3]byte{packet.Identifier, packet.Token[0], packet.Token[1]} switch packet.Identifier { case semtech.PUSH_ACK, semtech.PULL_ACK: - if acks[token] > 0 { - acks[token] -= 1 - fwd.commands <- command{cmd_ACK, nil} - } + fwd.commands <- command{cmd_ACK, ackToken(index, *packet)} case semtech.PULL_RESP: fwd.commands <- command{cmd_RECVDWN, packet} default: @@ -128,10 +129,16 @@ func (fwd *Forwarder) handleCommands() { switch cmd.name { case cmd_ACK: - fwd.ackn += 1 + token := cmd.data.([4]byte) + if fwd.acks[token] > 0 { + fwd.acks[token] -= 1 + fwd.ackn += 1 + } case cmd_FWD: fwd.rxfw += 1 case cmd_EMIT: + token := cmd.data.([4]byte) + fwd.acks[token] += 1 fwd.upnb += 1 case cmd_RECVUP: fwd.rxnb += 1 @@ -143,10 +150,9 @@ func (fwd *Forwarder) handleCommands() { fwd.packets = make([]semtech.Packet, 0) case cmd_STATS: var ackr float64 - if fwd.upnb != 0 { + if fwd.upnb > 0 { ackr = float64(fwd.ackn) / float64(fwd.upnb) } - cmd.data.(chan semtech.Stat) <- semtech.Stat{ Ackr: &ackr, Alti: pointer.Int(fwd.alti), @@ -175,7 +181,7 @@ func (fwd Forwarder) Forward(packet semtech.Packet) error { return err } - for _, adapter := range fwd.adapters { + for i, adapter := range fwd.adapters { n, err := adapter.Write(raw) if err != nil { return err @@ -183,7 +189,7 @@ func (fwd Forwarder) Forward(packet semtech.Packet) error { if n < len(raw) { return fmt.Errorf("Packet was too long") } - fwd.commands <- command{cmd_EMIT, nil} + fwd.commands <- command{cmd_EMIT, ackToken(i, packet)} } fwd.commands <- command{cmd_FWD, nil} From c33437fab5dea08d5c0305741f1885ef57008957 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 13:50:27 +0100 Subject: [PATCH 0123/2266] [simulators.gateway] Fix tests with ack and add one about multiples acks --- simulators/gateway/forwarder_test.go | 41 ++++++++++++---------------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 18943a4e3..b1584f545 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -180,25 +180,10 @@ func TestForwarder(t *testing.T) { }) Convey("Ignore non relevant packets", func() { - // Make sure the connection is established - pkt := generatePacket(semtech.PUSH_DATA, id) - if err := fwd.Forward(pkt); err != nil { - panic(err) - } - - // Simulate an ack and a valid response - ack := generatePacket(semtech.PUSH_ACK, id) - ack.Token = []byte{pkt.Token[0] + 0x1, pkt.Token[1]} // Use a different token - raw, err := semtech.Marshal(ack) - if err != nil { - panic(err) - } - a1.Downlink <- raw - // Simulate a resp - resp := generatePacket(semtech.PULL_RESP, id) + resp := generatePacket(semtech.PULL_DATA, id) resp.Token = []byte{0x0, 0x0} - raw, err = semtech.Marshal(resp) + raw, err := semtech.Marshal(resp) if err != nil { panic(err) } @@ -252,7 +237,7 @@ func TestForwarder(t *testing.T) { So(*refStats.Ackr, ShouldEqual, 0) }) - sendAndAck := func(a1Ack, a2Ack bool) { + sendAndAck := func(a1Ack, a2Ack uint) { // Send packet + ack pkt := generatePacket(semtech.PUSH_DATA, id) ack := generatePacket(semtech.PUSH_ACK, id) @@ -263,18 +248,19 @@ func TestForwarder(t *testing.T) { } fwd.Forward(pkt) time.Sleep(50 * time.Millisecond) - if a1Ack { + for i := uint(0); i < a1Ack; i += 1 { a1.Downlink <- raw } - if a2Ack { + for i := uint(0); i < a2Ack; i += 1 { a2.Downlink <- raw } + time.Sleep(50 * time.Millisecond) } Convey("ackr: valid packet acknowledged", func() { // Send packet + ack - sendAndAck(true, true) + sendAndAck(1, 1) // Check stats stats := fwd.Stats() @@ -283,7 +269,16 @@ func TestForwarder(t *testing.T) { Convey("ackr: valid packet partially acknowledged", func() { // Send packet + ack - sendAndAck(true, false) + sendAndAck(1, 0) + + // Check stats + stats := fwd.Stats() + So(*stats.Ackr, ShouldEqual, float64(1.0)/float64(2.0)) + }) + + Convey("ackr: valid packet several acks from same", func() { + // Send packet + ack + sendAndAck(2, 0) // Check stats stats := fwd.Stats() @@ -292,7 +287,7 @@ func TestForwarder(t *testing.T) { Convey("ackr: valid packet not ackowledged", func() { // Send packet + ack - sendAndAck(false, false) + sendAndAck(0, 0) // Check stats stats := fwd.Stats() From f4ab8e38d187cfdf73093235abd734a618f9272c Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 14:35:38 +0100 Subject: [PATCH 0124/2266] [simulators.gateway] Add generate RSSI util --- simulators/gateway/utils.go | 5 +++++ simulators/gateway/utils_test.go | 17 +++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index e7227f3a1..355302799 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -35,3 +35,8 @@ func ackToken(index int, packet semtech.Packet) [4]byte { return [4]byte{id, kind, packet.Token[0], packet.Token[1]} } + +func generateRSSI() int { + x := float32(rand.Int31()) / float32(2e8) + return -int(x * x) +} diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 5ee58cf33..33603e51b 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -39,3 +39,20 @@ func TestGenToken(t *testing.T) { }) }) } + +func TestAckToken(t *testing.T) { + +} + +func TestGenerateRSSI(t *testing.T) { + Convey("The generateRSSI should generate random RSSI values -120 < val < 0", t, func() { + values := make(map[int]bool) + for i := 0; i < 10; i += 1 { + rssi := generateRSSI() + So(rssi, ShouldBeGreaterThanOrEqualTo, -120) + So(rssi, ShouldBeLessThanOrEqualTo, 0) + values[rssi] = true + } + So(len(values), ShouldBeGreaterThan, 5) + }) +} From d7282c861d3b86594dcc52c15735670cc2019624 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 15:13:58 +0100 Subject: [PATCH 0125/2266] [simulators.gateway] Add generateFreq() util --- simulators/gateway/utils.go | 7 ++++++- simulators/gateway/utils_test.go | 17 +++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 355302799..8555bd8db 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -36,7 +36,12 @@ func ackToken(index int, packet semtech.Packet) [4]byte { return [4]byte{id, kind, packet.Token[0], packet.Token[1]} } -func generateRSSI() int { +func generateRssi() int { x := float32(rand.Int31()) / float32(2e8) return -int(x * x) } + +func generateFreq() float64 { + // EU 863-870MHz + return rand.Float64()*7 + 863.0 +} diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 33603e51b..525e7c20a 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -44,11 +44,11 @@ func TestAckToken(t *testing.T) { } -func TestGenerateRSSI(t *testing.T) { +func TestGenerateRssi(t *testing.T) { Convey("The generateRSSI should generate random RSSI values -120 < val < 0", t, func() { values := make(map[int]bool) for i := 0; i < 10; i += 1 { - rssi := generateRSSI() + rssi := generateRssi() So(rssi, ShouldBeGreaterThanOrEqualTo, -120) So(rssi, ShouldBeLessThanOrEqualTo, 0) values[rssi] = true @@ -56,3 +56,16 @@ func TestGenerateRSSI(t *testing.T) { So(len(values), ShouldBeGreaterThan, 5) }) } + +func TestGenerateFreq(t *testing.T) { + Convey("The generateFreq() method should generate random frequence between 863-870MHz", t, func() { + values := make(map[float64]bool) + for i := 0; i < 10; i += 1 { + freq := generateFreq() + So(freq, ShouldBeGreaterThanOrEqualTo, 863.0) + So(freq, ShouldBeLessThanOrEqualTo, 870.0) + values[freq] = true + } + So(len(values), ShouldBeGreaterThan, 5) + }) +} From 007dc55f270944c5b0cc2c26c09fa1e6ad0c2144 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 15:33:23 +0100 Subject: [PATCH 0126/2266] [simulators.gateway] temp commit --- simulators/gateway/imitator.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 simulators/gateway/imitator.go diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go new file mode 100644 index 000000000..98759a170 --- /dev/null +++ b/simulators/gateway/imitator.go @@ -0,0 +1,6 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gateway + +import () From 7ca7ac58432b2fb7ae9d0fc7adb2da1aec74d967 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 15:43:15 +0100 Subject: [PATCH 0127/2266] [simulators.gateway] Rename file to proxies.go --- lorawan/semtech/{encode_proxies.go => proxies.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lorawan/semtech/{encode_proxies.go => proxies.go} (100%) diff --git a/lorawan/semtech/encode_proxies.go b/lorawan/semtech/proxies.go similarity index 100% rename from lorawan/semtech/encode_proxies.go rename to lorawan/semtech/proxies.go From 734cf9e58903153d5230ea3746fe7ddd70abd6dd Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 15:43:15 +0100 Subject: [PATCH 0128/2266] Rename file to proxies.go --- lorawan/semtech/{encode_proxies.go => proxies.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lorawan/semtech/{encode_proxies.go => proxies.go} (100%) diff --git a/lorawan/semtech/encode_proxies.go b/lorawan/semtech/proxies.go similarity index 100% rename from lorawan/semtech/encode_proxies.go rename to lorawan/semtech/proxies.go From f11a91ec2429c0e7741695ad771a7ef686bf9fb0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 15:47:31 +0100 Subject: [PATCH 0129/2266] [simulators.gateway] Update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 01b978ee7..7f9d89355 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,7 @@ So far: -| lorawan -----| mac ------| gateway ----------| protocol +-----| semtech -| simulators -----| gateway From 507bdb3bf491271545c138e1b2db8a8093c9d8b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 15:47:31 +0100 Subject: [PATCH 0130/2266] Update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 01b978ee7..7f9d89355 100644 --- a/README.md +++ b/README.md @@ -29,8 +29,7 @@ So far: -| lorawan -----| mac ------| gateway ----------| protocol +-----| semtech -| simulators -----| gateway From 148397b1450ad6dfbb84da501c512420cd98a9bd Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 19:08:04 +0100 Subject: [PATCH 0131/2266] [router] Write down first sketch of interfaces between router and side components --- core.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 core.go diff --git a/core.go b/core.go new file mode 100644 index 000000000..2cbce17dc --- /dev/null +++ b/core.go @@ -0,0 +1,36 @@ +package core + +import ( + . "github.com/thethingsnetwork/core/lorawan/semtech" +) + +type DeviceAddress string +type BrokerAddress string +type ConnectionId uint +type GatewayId [8]byte + +type Component interface { + HandleError(err error) +} + +type Router interface { + Component + HandleUplink(packet Packet, connId ConnectionId) + HandleDownlink(packet Packet, connId ConnectionId) + RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) +} + +type Adapter interface { + Connect(comp Component) +} + +type GatewayRouterAdapter interface { + Adapter + Ack(packet Packet, gid GatewayId) +} + +type RouterBrokerAdapter interface { + Adapter + Broadcast(packet Packet) + Forward(packet Packet, broAddrs ...BrokerAddress) +} From 6cd40c27dc05473bb64eec68c2f4db4b0050b0ba Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 19:08:24 +0100 Subject: [PATCH 0132/2266] [router] Write down backbone of router and adapters --- components/router/main.go | 7 +++ components/router/router.go | 85 +++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 components/router/main.go create mode 100644 components/router/router.go diff --git a/components/router/main.go b/components/router/main.go new file mode 100644 index 000000000..8a9735ab7 --- /dev/null +++ b/components/router/main.go @@ -0,0 +1,7 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package router materializes a router of the network +// +// TODO description +package router diff --git a/components/router/router.go b/components/router/router.go new file mode 100644 index 000000000..9b8f5e264 --- /dev/null +++ b/components/router/router.go @@ -0,0 +1,85 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" + "time" +) + +type Router struct{} + +func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { + /* PULL_DATA + * + * Send PULL_ACK + * Store the gateway in known gateway + */ + + /* PUSH_DATA + * + * Send PUSH_ACK + * Stores the gateway connection id for later response + * Lookup for an existing broker associated to the device address + * Forward data to that broker + */ + + /* Else + * + * Ignore / Raise an error + */ + +} + +func (r *Router) HandleDownlink(packet semtech.Packet) { + +} + +func (r *Router) RegisterDevice(devAddr core.DeviceAddress, broAddrs ...core.BrokerAddress) { + +} + +func (r *Router) HandleError(err error) { + +} + +// --------------- Address keeper +type addressKeeper interface { + lookup(devAddr core.DeviceAddress) (core.BrokerAddress, error) + invalidate(broAddrs ...core.BrokerAddress) error +} + +type reddisAddressKeeper struct{} // In a second time + +type localAddressKeeper struct { + addresses map[core.DeviceAddress]time.Time +} + +func (a *localAddressKeeper) lookup(devAddr core.DeviceAddress) (*core.BrokerAddress, error) { + return nil, nil +} + +func (a *localAddressKeeper) invalidate(broAddrs ...core.BrokerAddress) error { + return nil +} + +// --------------- Routers Adapters + +type UpAdapter struct { +} + +func (u *UpAdapter) Ack(packet semtech.Packet, gid core.GatewayId) { +} + +type DownAdapter struct { +} + +func (d *DownAdapter) Broadcast(packet semtech.Packet) { + +} + +func (d *DownAdapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) { + +} From 9d62cd3458f9518ac8a5361b9e93c863c1331c3d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 19:17:16 +0100 Subject: [PATCH 0133/2266] [simulators.gateway] Update devplan --- DEVELOPMENT_PLAN.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index ea1b70964..2fbfd603b 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -8,11 +8,11 @@ mainly for testing and ensuring the correctness of other components. - [ ] Fake gateway - [x] Types, packages and data structures in use - [x] Emit udp packets towards a server - - [ ] Handle behavior described by the semtech protocol + - [x] Handle behavior described by the semtech protocol - [x] Serialize json rxpk/stat object(s) - [x] Generate json rxpl/stat object(s) + - [x] Update gateway statistics accordingly - [ ] Simulate fake end-devices activity - - [ ] Update gateway statistics accordingly ## Milestone 2 Handle an uplink process that can forward packet coming from a gateway to a simple end-server From 824b45335b782ac547c0c9cca4387bcf41a1662a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 19:27:03 +0100 Subject: [PATCH 0134/2266] [router] Update development plan --- DEVELOPMENT_PLAN.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index ea1b70964..95dafa773 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -21,7 +21,20 @@ system will just forward messages using pre-configured end-device addresses. - [ ] Basic Router - - [ ] Detail the list of features + - [ ] Core + - [ ] Lookup for device address + - [ ] Invalidate broker periodically + - [ ] Acknowledge packet from gateway + - [ ] Forward packet to brokers + - [ ] Reemit errored packet + - [ ] UpAdapter + - [ ] Listen and forward incoming packets to Core router + - [ ] Keep track of existing UDP connections + - [ ] Send ack through existing UDP connection + - [ ] DownAdapter + - [ ] Listen and forward incoming packet to Core router + - [ ] Broadcast a packet to several brokers + - [ ] Send packet to given brokers (same as above ?) - [ ] Basic Broker From 9249c30820488de9daca4b45a51e23bed517170b Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 29 Dec 2015 19:29:03 +0100 Subject: [PATCH 0135/2266] [simulators.gateway] Add todo list as a reminder --- simulators/gateway/TODO.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 simulators/gateway/TODO.md diff --git a/simulators/gateway/TODO.md b/simulators/gateway/TODO.md new file mode 100644 index 000000000..f24af9b25 --- /dev/null +++ b/simulators/gateway/TODO.md @@ -0,0 +1,5 @@ +- Complete tests set for stats functions +- Generate valid fake end-device packet +- Simulate fake end-devices traffic +- Generate UDP ReadWriteCloser +- Move existing fake ReadWriteCloser to a proper file with handy constructor From cb9ce2f994d53f8ea2234e3bd55d53712482ef86 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 11:11:24 +0100 Subject: [PATCH 0136/2266] [simulators.gateway] Add a logger interface --- utils/log/log.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 utils/log/log.go diff --git a/utils/log/log.go b/utils/log/log.go new file mode 100644 index 000000000..e921b72c8 --- /dev/null +++ b/utils/log/log.go @@ -0,0 +1,32 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package log provides some handy types and method to activate and deactivate specific log +// behavior within files in a transparent way. +package log + +import ( + "fmt" +) + +// Logger is a minimalist interface to represent logger +type Logger interface { + Log(format string, a ...interface{}) +} + +// DebugLogger can be used in development to display loglines in the console +type DebugLogger struct { + Tag string +} + +// Log implements the Logger interface +func (l DebugLogger) Log(format string, a ...interface{}) { + fmt.Printf("[ %v ] ", l.Tag) + fmt.Printf(format, a...) +} + +// VoidLogger can be used to deactivate logs by displaying nothing +type VoidLogger struct{} + +// Log implements the Logger interface +func (l VoidLogger) Log(format string, a ...interface{}) {} From 8a89b851926990f4f2ad6e1793d46360b10eb95c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 11:12:01 +0100 Subject: [PATCH 0137/2266] [simulators.gateway] Update forwarder with new logger --- simulators/gateway/forwarder.go | 34 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 3f5ebc64c..b8d6017b0 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -6,6 +6,7 @@ package gateway import ( "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" "io" "time" @@ -13,7 +14,7 @@ import ( type Forwarder struct { Id [8]byte // Gateway's Identifier - debug bool + Logger log.Logger alti int // GPS altitude in RX meters upnb uint // Number of upstream datagrams sent ackn uint // Number of upstream datagrams that were acknowledged @@ -57,7 +58,7 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error fwd := &Forwarder{ Id: id, - debug: false, + Logger: log.VoidLogger{}, alti: 120, lati: 53.3702, long: 4.8952, @@ -78,29 +79,26 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error return fwd, nil } +// log wraps the Logger.log method, this is nothing more than a shortcut +func (fwd Forwarder) log(format string, a ...interface{}) { + fwd.Logger.Log(format, a...) +} + // listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { for { buf := make([]byte, 1024) n, err := adapter.Read(buf) - if fwd.debug { - fmt.Printf("%d bytes received by adapter\n", n) - } + fwd.log("%d bytes received by adapter\n", n) if err != nil { - if fwd.debug { - fmt.Println(err) - } + fwd.log("Error: %+v", err) fwd.Errors <- err return // Error on reading, we assume the connection is closed / lost } - if fwd.debug { - fmt.Printf("Forwarder unmarshals datagram %x\n", buf[:n]) - } + fwd.log("Forwarder unmarshals datagram %x\n", buf[:n]) packet, err := semtech.Unmarshal(buf[:n]) if err != nil { - if fwd.debug { - fmt.Println(err) - } + fwd.log("Error: %+v", err) continue } @@ -110,9 +108,7 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { case semtech.PULL_RESP: fwd.commands <- command{cmd_RECVDWN, packet} default: - if fwd.debug { - fmt.Printf("Forwarder ignores contingent packet %+v\n", packet) - } + fwd.log("Forwarder ignores contingent packet %+v\n", packet) } } @@ -123,9 +119,7 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { // This method consumes commands from the channel until it's closed. func (fwd *Forwarder) handleCommands() { for cmd := range fwd.commands { - if fwd.debug { - fmt.Printf("Fowarder executes command: %v\n", cmd.name) - } + fwd.log("Fowarder executes command: %v\n", cmd.name) switch cmd.name { case cmd_ACK: From 1f18f1b480540d7ded1a58eff745c43e47ec5b36 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 11:44:26 +0100 Subject: [PATCH 0138/2266] [simulators.gateway] Fix stop goroutines leaks. --- simulators/gateway/forwarder.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index b8d6017b0..b5cd0193c 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -27,7 +27,7 @@ type Forwarder struct { packets []semtech.Packet // Downlink packets received acks map[[4]byte]uint // adapterIndex | packet.Identifier | packet.Token commands chan command // Concurrent access on gateway stats - Errors chan error // Done channel + quit chan error // Adapter which loses connection spit here } type commandName string @@ -66,7 +66,7 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error packets: make([]semtech.Packet, 0), acks: make(map[[4]byte]uint), commands: make(chan command), - Errors: make(chan error, len(adapters)), + quit: make(chan error, len(adapters)), } go fwd.handleCommands() @@ -81,7 +81,7 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error // log wraps the Logger.log method, this is nothing more than a shortcut func (fwd Forwarder) log(format string, a ...interface{}) { - fwd.Logger.Log(format, a...) + fwd.Logger.Log(format, a...) // NOTE: concurrent-safe ? } // listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. @@ -92,8 +92,8 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { fwd.log("%d bytes received by adapter\n", n) if err != nil { fwd.log("Error: %+v", err) - fwd.Errors <- err - return // Error on reading, we assume the connection is closed / lost + fwd.quit <- err + return // Connection lost / closed } fwd.log("Forwarder unmarshals datagram %x\n", buf[:n]) packet, err := semtech.Unmarshal(buf[:n]) @@ -110,7 +110,6 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { default: fwd.log("Forwarder ignores contingent packet %+v\n", packet) } - } } @@ -216,9 +215,15 @@ func (fwd Forwarder) Stop() error { } } + // Wait for each adapter to terminate + for range fwd.adapters { + <-fwd.quit + } + + close(fwd.commands) + if len(errors) > 0 { return fmt.Errorf("Unable to stop the forwarder: %+v", errors) } - return nil } From 90966a6ee500a6a417208e470521d28e4394e065 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 12:15:34 +0100 Subject: [PATCH 0139/2266] [simulators.gateway] Enhance tests and update functions accordingly --- simulators/gateway/forwarder_test.go | 19 +++++++++++++++++ simulators/gateway/utils.go | 8 ++----- simulators/gateway/utils_test.go | 31 ++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index b1584f545..d84374b05 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -7,6 +7,7 @@ import ( "fmt" . "github.com/smartystreets/goconvey/convey" "github.com/thethingsnetwork/core/lorawan/semtech" + "io" "testing" "time" ) @@ -96,6 +97,16 @@ func TestForwarder(t *testing.T) { So(err, ShouldNotBeNil) So(fwd, ShouldBeNil) }) + + Convey("Invalid: too many adapters", func() { + var adapters []io.ReadWriteCloser + for i := 0; i < 300; i += 1 { + adapters = append(adapters, newFakeAdapter(fmt.Sprintf("%d", i))) + } + fwd, err := NewForwarder(id, adapters...) + So(fwd, ShouldBeNil) + So(err, ShouldNotBeNil) + }) }) Convey("Forward", t, func() { @@ -129,6 +140,14 @@ func TestForwarder(t *testing.T) { Convey("Invalid: PUSH_ACK", checkInvalid(semtech.PUSH_ACK)) Convey("Invalid: PULL_ACK", checkInvalid(semtech.PULL_ACK)) Convey("Invalid: PULL_RESP", checkInvalid(semtech.PULL_RESP)) + Convey("Invalid: wrong PUSH_DATA", func() { + pkt := generatePacket(semtech.PUSH_DATA, fwd.Id) + pkt.Token = []byte{0x14} + err := fwd.Forward(pkt) + So(err, ShouldNotBeNil) + So(len(a1.written), ShouldEqual, 0) + So(len(a2.written), ShouldEqual, 0) + }) }) Convey("Flush", t, func() { diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 8555bd8db..5cf4ae953 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -18,12 +18,8 @@ func genToken() []byte { func ackToken(index int, packet semtech.Packet) [4]byte { buf := new(bytes.Buffer) - var id byte - if err := binary.Write(buf, binary.LittleEndian, uint16(index)); err != nil { - id = 0xff - } else { - id = buf.Bytes()[0] - } + binary.Write(buf, binary.LittleEndian, uint16(index)) + id := buf.Bytes()[0] var kind byte switch packet.Identifier { diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 525e7c20a..adbcba04f 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -5,6 +5,7 @@ package gateway import ( . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core/lorawan/semtech" "testing" ) @@ -41,7 +42,37 @@ func TestGenToken(t *testing.T) { } func TestAckToken(t *testing.T) { + token := []byte{0x1, 0x4} + generatePacket := func(id byte) semtech.Packet { + return semtech.Packet{ + Token: token, + Identifier: id, + Version: semtech.VERSION, + } + } + + Convey("The ackToken() method should generate appropriate ACK token", t, func() { + Convey("Valid identifier, PULL", func() { + token_data := ackToken(14, generatePacket(semtech.PULL_DATA)) + token_ack := ackToken(14, generatePacket(semtech.PULL_ACK)) + token_resp := ackToken(14, generatePacket(semtech.PULL_RESP)) + So(token_data, ShouldResemble, token_ack) + So(token_ack, ShouldResemble, token_resp) + }) + + Convey("Valid identifier, PUSH", func() { + token_data := ackToken(14, generatePacket(semtech.PUSH_DATA)) + token_ack := ackToken(14, generatePacket(semtech.PUSH_ACK)) + So(token_data, ShouldResemble, token_ack) + }) + + Convey("Valid but different ids", func() { + token_data := ackToken(14, generatePacket(semtech.PULL_DATA)) + token_ack := ackToken(42, generatePacket(semtech.PULL_ACK)) + So(token_data, ShouldNotResemble, token_ack) + }) + }) } func TestGenerateRssi(t *testing.T) { From 0afd26104f5b3300aae8e4177d35463bbbe8fa39 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 15:18:52 +0100 Subject: [PATCH 0140/2266] [router] Write tests for local address keeper --- components/router/address_keeper_test.go | 105 +++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 components/router/address_keeper_test.go diff --git a/components/router/address_keeper_test.go b/components/router/address_keeper_test.go new file mode 100644 index 000000000..5f562d090 --- /dev/null +++ b/components/router/address_keeper_test.go @@ -0,0 +1,105 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core" + "testing" + "time" +) + +func genDevAddr() core.DeviceAddress { + return core.DeviceAddress(fmt.Sprintf("DeviceAddress%d", time.Now())) +} + +func genBroAddr() core.BrokerAddress { + return core.BrokerAddress(fmt.Sprintf("BrokerAddress%d", time.Now())) +} + +func TestAddressKeeper(t *testing.T) { + Convey("Local Address Keeper", t, func() { + Convey("NewLocalDB", func() { + Convey("NewLocalDB: valid", func() { + localDB, err := NewLocalDB(time.Hour * 100) + So(err, ShouldBeNil) + So(localDB, ShouldNotBeNil) + }) + + Convey("NewLocalDB: invalid", func() { + localDB, err := NewLocalDB(0) + So(err, ShouldNotBeNil) + So(localDB, ShouldBeNil) + }) + }) + + Convey("Store & Lookup", func() { + Convey("Store then Lookup same", func() { + localDB, err := NewLocalDB(time.Hour) + if err != nil { + panic(err) + } + + devAddr := genDevAddr() + broAddr := genBroAddr() + + err = localDB.store(devAddr, broAddr) + So(err, ShouldBeNil) + + broAddrs, err := localDB.lookup(devAddr) + So(err, ShouldBeNil) + So(broAddrs, ShouldResemble, []core.BrokerAddress{broAddr}) + + devAddr = genDevAddr() + broAddr2 := genBroAddr() + err = localDB.store(devAddr, broAddr, broAddr2) + So(err, ShouldBeNil) + + broAddrs, err = localDB.lookup(devAddr) + So(err, ShouldBeNil) + So(broAddrs, ShouldResemble, []core.BrokerAddress{broAddr, broAddr2}) + }) + + Convey("Invalid lookups", func() { + localDB, err := NewLocalDB(time.Millisecond) + if err != nil { + panic(err) + } + + devAddr := genDevAddr() + broAddr := genBroAddr() + + err = localDB.store(devAddr, broAddr) + So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + broAddrs, err := localDB.lookup(devAddr) + So(broAddrs, ShouldBeNil) + So(err, ShouldNotBeNil) + + broAddrs, err = localDB.lookup(genDevAddr()) + So(err, ShouldNotBeNil) + So(broAddrs, ShouldBeNil) + }) + + Convey("Store existing", func() { + localDB, err := NewLocalDB(time.Hour) + if err != nil { + panic(err) + } + + devAddr := genDevAddr() + broAddr := genBroAddr() + + err = localDB.store(devAddr, broAddr) + So(err, ShouldBeNil) + err = localDB.store(devAddr, broAddr) + So(err, ShouldNotBeNil) + }) + }) + + }) +} From 382e2e50d1983ebda91981e07bb0dc93c5ab5a52 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 15:19:09 +0100 Subject: [PATCH 0141/2266] [router] Split router and implements local address keeper --- components/router/address_keeper.go | 69 +++++++++++++++++++++++++++++ components/router/router.go | 25 ++--------- 2 files changed, 73 insertions(+), 21 deletions(-) create mode 100644 components/router/address_keeper.go diff --git a/components/router/address_keeper.go b/components/router/address_keeper.go new file mode 100644 index 000000000..4925ec9c0 --- /dev/null +++ b/components/router/address_keeper.go @@ -0,0 +1,69 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "time" +) + +type addressKeeper interface { + lookup(devAddr core.DeviceAddress) ([]core.BrokerAddress, error) + store(devAddr core.DeviceAddress, brosAddr ...core.BrokerAddress) error +} + +type reddisAddressKeeper struct{} // In a second time + +type localDB struct { + expiryDelay time.Duration + addresses map[core.DeviceAddress]localEntry +} + +type localEntry struct { + addr []core.BrokerAddress + until time.Time +} + +// NewLocalDB constructs a new local address keeper +func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { + if expiryDelay == 0 { + return nil, fmt.Errorf("Invalid expiration delay") + } + + return &localDB{ + expiryDelay: expiryDelay, + addresses: make(map[core.DeviceAddress]localEntry), + }, nil +} + +// lookup implements the addressKeeper interface +func (a *localDB) lookup(devAddr core.DeviceAddress) ([]core.BrokerAddress, error) { + entry, ok := a.addresses[devAddr] + if !ok { + return nil, fmt.Errorf("Device address not found") + } + + if entry.until.Before(time.Now()) { + delete(a.addresses, devAddr) + return nil, fmt.Errorf("Broker address(es) expired") + } + + return entry.addr, nil +} + +// store implements the addressKeeper interface +func (a *localDB) store(devAddr core.DeviceAddress, brosAddr ...core.BrokerAddress) error { + _, ok := a.addresses[devAddr] + if ok { + return fmt.Errorf("An entry already exists for that device") + } + + a.addresses[devAddr] = localEntry{ + addr: brosAddr, + until: time.Now().Add(a.expiryDelay), + } + + return nil +} diff --git a/components/router/router.go b/components/router/router.go index 9b8f5e264..39c107325 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -9,6 +9,10 @@ import ( "time" ) +const ( + EXPIRY_DELAY = time.Hour * 8 +) + type Router struct{} func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { @@ -45,28 +49,7 @@ func (r *Router) HandleError(err error) { } -// --------------- Address keeper -type addressKeeper interface { - lookup(devAddr core.DeviceAddress) (core.BrokerAddress, error) - invalidate(broAddrs ...core.BrokerAddress) error -} - -type reddisAddressKeeper struct{} // In a second time - -type localAddressKeeper struct { - addresses map[core.DeviceAddress]time.Time -} - -func (a *localAddressKeeper) lookup(devAddr core.DeviceAddress) (*core.BrokerAddress, error) { - return nil, nil -} - -func (a *localAddressKeeper) invalidate(broAddrs ...core.BrokerAddress) error { - return nil -} - // --------------- Routers Adapters - type UpAdapter struct { } From cc072338ff2b7a040051013c9b98b2e0503ab5b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 15:21:31 +0100 Subject: [PATCH 0142/2266] [router] Update Dev plan --- DEVELOPMENT_PLAN.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 95dafa773..9ec0d51ce 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -22,11 +22,12 @@ system will just forward messages using pre-configured end-device addresses. - [ ] Basic Router - [ ] Core - - [ ] Lookup for device address - - [ ] Invalidate broker periodically + - [x] Lookup for device address (only local) + - [x] Invalidate broker periodically (only local) - [ ] Acknowledge packet from gateway - [ ] Forward packet to brokers - [ ] Reemit errored packet + - [ ] Switch from local in-memory storage to Reddis - [ ] UpAdapter - [ ] Listen and forward incoming packets to Core router - [ ] Keep track of existing UDP connections From 73be95958b5f90d74d8213bb9c9d4ca8040da5eb Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 15:33:43 +0100 Subject: [PATCH 0143/2266] [router] Remove abstraction from interfaces. We'll see that later --- core.go | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/core.go b/core.go index 2cbce17dc..063dda99c 100644 --- a/core.go +++ b/core.go @@ -9,28 +9,20 @@ type BrokerAddress string type ConnectionId uint type GatewayId [8]byte -type Component interface { - HandleError(err error) -} - type Router interface { - Component + HandleError(err error) HandleUplink(packet Packet, connId ConnectionId) HandleDownlink(packet Packet, connId ConnectionId) RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) } -type Adapter interface { - Connect(comp Component) -} - type GatewayRouterAdapter interface { - Adapter + Connect(router Router) Ack(packet Packet, gid GatewayId) } type RouterBrokerAdapter interface { - Adapter + Connect(router Router) Broadcast(packet Packet) Forward(packet Packet, broAddrs ...BrokerAddress) } From 7c2302891fddc9d5aafaf834f19fa98ae1799242 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 15:33:58 +0100 Subject: [PATCH 0144/2266] [router] Start implementing UpAdapter. --- components/router/router.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/components/router/router.go b/components/router/router.go index 39c107325..5b53ad6a3 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -6,6 +6,8 @@ package router import ( "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/log" + "net" "time" ) @@ -51,9 +53,32 @@ func (r *Router) HandleError(err error) { // --------------- Routers Adapters type UpAdapter struct { + router Router + logger log.logger + gateways map[core.GatewayId]net.UDPConn +} + +func NewUpAdapter(router Router) *UpAdapter { + adapter := UpAdapter{ + gateways: make(map[core.GatewayId]net.UDPConn), + logger: log.VoidLogger{}, + } + adapter.Connect(router) + return &adapter +} + +func (u UpAdapter) log(format string, a ...interface{}) { + u.logger.Log(format, a...) } func (u *UpAdapter) Ack(packet semtech.Packet, gid core.GatewayId) { + if u.router == nil { + u.log("Failed to Ack, not connected to a router") + } +} + +func (u *UpAdapter) Connect(router Router) { + u.router = router } type DownAdapter struct { From 7b0be649b6bec54e4ed96ab1c1a1b8bad3fb87d2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 11:11:24 +0100 Subject: [PATCH 0145/2266] [router] Add a logger interface --- utils/log/log.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 utils/log/log.go diff --git a/utils/log/log.go b/utils/log/log.go new file mode 100644 index 000000000..e921b72c8 --- /dev/null +++ b/utils/log/log.go @@ -0,0 +1,32 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package log provides some handy types and method to activate and deactivate specific log +// behavior within files in a transparent way. +package log + +import ( + "fmt" +) + +// Logger is a minimalist interface to represent logger +type Logger interface { + Log(format string, a ...interface{}) +} + +// DebugLogger can be used in development to display loglines in the console +type DebugLogger struct { + Tag string +} + +// Log implements the Logger interface +func (l DebugLogger) Log(format string, a ...interface{}) { + fmt.Printf("[ %v ] ", l.Tag) + fmt.Printf(format, a...) +} + +// VoidLogger can be used to deactivate logs by displaying nothing +type VoidLogger struct{} + +// Log implements the Logger interface +func (l VoidLogger) Log(format string, a ...interface{}) {} From d4417c0bc57d5bc9a245a2c8a1e973b1b85b20da Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 16:05:42 +0100 Subject: [PATCH 0146/2266] [router] Add UpAdapter Ack method --- components/router/router.go | 40 ++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index 5b53ad6a3..a47c29d41 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -4,6 +4,7 @@ package router import ( + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/log" @@ -17,6 +18,8 @@ const ( type Router struct{} +type errAck error + func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { /* PULL_DATA * @@ -48,13 +51,12 @@ func (r *Router) RegisterDevice(devAddr core.DeviceAddress, broAddrs ...core.Bro } func (r *Router) HandleError(err error) { - } // --------------- Routers Adapters type UpAdapter struct { - router Router - logger log.logger + router *Router + logger log.Logger gateways map[core.GatewayId]net.UDPConn } @@ -73,12 +75,40 @@ func (u UpAdapter) log(format string, a ...interface{}) { func (u *UpAdapter) Ack(packet semtech.Packet, gid core.GatewayId) { if u.router == nil { - u.log("Failed to Ack, not connected to a router") + u.log("Fails to Ack, not connected to a router") + return + } + + u.log("Acks packet %+v", packet) + + conn, ok := u.gateways[gid] + + if !ok { + u.log("Gateway connection not found") + u.router.HandleError(errAck(fmt.Errorf("Gateway connection not found"))) + return + } + + raw, err := semtech.Marshal(packet) + + if err != nil { + u.log("Unable to marshal given packet") + u.router.HandleError(errAck(fmt.Errorf("Unable to marshal given packet %+v", err))) + return + } + + _, err = conn.Write(raw) + + if err != nil { + u.log("Unable to send udp message") + u.router.HandleError(errAck(fmt.Errorf("Unable to send udp message %+v", err))) + return } } func (u *UpAdapter) Connect(router Router) { - u.router = router + u.log("Connects to router %+v", router) + u.router = &router } type DownAdapter struct { From 296261dd06b2dc318527dc8ae696f26ecc421084 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 18:45:38 +0100 Subject: [PATCH 0147/2266] [router] Add license and rewrite signatures in Core.go --- core.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core.go b/core.go index 063dda99c..bf985d781 100644 --- a/core.go +++ b/core.go @@ -1,3 +1,6 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package core import ( @@ -6,8 +9,7 @@ import ( type DeviceAddress string type BrokerAddress string -type ConnectionId uint -type GatewayId [8]byte +type ConnectionId string type Router interface { HandleError(err error) @@ -16,9 +18,10 @@ type Router interface { RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) } +type ErrAck error type GatewayRouterAdapter interface { Connect(router Router) - Ack(packet Packet, gid GatewayId) + Ack(packet Packet, cid ConnectionId) } type RouterBrokerAdapter interface { From e82b55f81aa7e8a4314527fdf1c25070638a0359 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 18:46:08 +0100 Subject: [PATCH 0148/2266] [router] Add first draft version of the router uplink adapter (gateway <-> router) --- adapters/gateway-router-udp/adapter.go | 118 +++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 adapters/gateway-router-udp/adapter.go diff --git a/adapters/gateway-router-udp/adapter.go b/adapters/gateway-router-udp/adapter.go new file mode 100644 index 000000000..264fb2b31 --- /dev/null +++ b/adapters/gateway-router-udp/adapter.go @@ -0,0 +1,118 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gtw_rtr_udp + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/log" + "net" + "sync" +) + +type Adapter struct { + router core.Router + logger log.Logger + gateways map[core.ConnectionId]*net.UDPAddr + conn *net.UDPConn + lock sync.RWMutex +} + +// New constructs a new Gateway-Router-UDP adapter +func New(router core.Router, port uint) (*Adapter, error) { + adapter := Adapter{ + gateways: make(map[core.ConnectionId]*net.UDPAddr), + lock: sync.RWMutex{}, + logger: log.VoidLogger{}, + } + + // Connect to the router and start listening on the given port of the current machine + adapter.Connect(router) + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + return nil, err + } + udpConn, err := net.ListenUDP("udp", addr) + if err != nil { + return nil, err + } + adapter.conn = udpConn + go adapter.listen() // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. + + // Return the adapter for further use + return &adapter, nil +} + +// log is nothing more than a shortcut / helper to access the logger +func (a Adapter) log(format string, i ...interface{}) { + a.logger.Log(format, i...) +} + +// Ack implements the core.GatewayRouterAdapter interface +func (a *Adapter) Ack(packet semtech.Packet, cid core.ConnectionId) { + if a.router == nil { + a.log("Fails to Ack, not connected to a router") + return + } + + a.log("Acks packet %+v", packet) + + a.lock.RLock() + addr, ok := a.gateways[cid] + a.lock.Unlock() + + if !ok { + a.log("Gateway connection not found") + a.router.HandleError(core.ErrAck(fmt.Errorf("Gateway connection not found"))) + return + } + + raw, err := semtech.Marshal(packet) + + if err != nil { + a.log("Unable to marshal given packet") + a.router.HandleError(core.ErrAck(fmt.Errorf("Unable to marshal given packet %+v", err))) + return + } + + _, err = a.conn.WriteToUDP(raw, addr) + + if err != nil { + a.log("Unable to send udp message") + a.router.HandleError(core.ErrAck(fmt.Errorf("Unable to send udp message %+v", err))) + return + } +} + +// Ack implements the core.GatewayRouterAdapter interface +func (a *Adapter) Connect(router core.Router) { + a.log("Connects to router %+v", router) + a.router = router +} + +func (a *Adapter) listen() { + for { + buf := make([]byte, 1024) + n, addr, err := a.conn.ReadFromUDP(buf) + if err != nil { + a.log("Error: %v", err) // NOTE Errors are just ignored for now + continue + } + a.log("Incoming datagram %x", buf[:n]) + + pkt, err := semtech.Unmarshal(buf[:n]) + if err != nil { + a.log("Error: %v", err) // NOTE Errors are just ignored for now + continue + } + + // When a packet is received pass it to the router for processing + cid := core.ConnectionId(addr.String()) + a.lock.Lock() + a.gateways[cid] = addr + a.lock.Unlock() + a.router.HandleUplink(*pkt, cid) + } +} From 11fbef1585b8cfb5287aab77c6b436d7ceb77dca Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 18:50:18 +0100 Subject: [PATCH 0149/2266] [router] Rename folder to be compliant with go package system --- adapters/{gateway-router-udp => gtw_rtr_udp}/adapter.go | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename adapters/{gateway-router-udp => gtw_rtr_udp}/adapter.go (100%) diff --git a/adapters/gateway-router-udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go similarity index 100% rename from adapters/gateway-router-udp/adapter.go rename to adapters/gtw_rtr_udp/adapter.go From 7af4d5e58ef1c398daac0493f55d99ad5e709cec Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 18:55:26 +0100 Subject: [PATCH 0150/2266] [router] Add impl router backbone --- components/router/router.go | 94 ++++++++----------------------------- 1 file changed, 20 insertions(+), 74 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index a47c29d41..b812478c2 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -8,7 +8,6 @@ import ( "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/log" - "net" "time" ) @@ -16,11 +15,23 @@ const ( EXPIRY_DELAY = time.Hour * 8 ) -type Router struct{} +type Router struct { + Port uint + upAdapter core.GatewayRouterAdapter + logger log.Logger +} -type errAck error +func New(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, port uint) (*Router, error) { + return &Router{ + Port: port, + logger: log.VoidLogger{}, + upAdapter: upAdapter, + }, nil +} +// HandleUplink implements the core.Router interface func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { + /* PULL_DATA * * Send PULL_ACK @@ -42,82 +53,17 @@ func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { } +// HandleDownlink implements the core.Router interface func (r *Router) HandleDownlink(packet semtech.Packet) { - + // TODO } +// RegisterDevice implements the core.Router interface func (r *Router) RegisterDevice(devAddr core.DeviceAddress, broAddrs ...core.BrokerAddress) { - + // TODO } +// RegisterDevice implements the core.Router interface func (r *Router) HandleError(err error) { -} - -// --------------- Routers Adapters -type UpAdapter struct { - router *Router - logger log.Logger - gateways map[core.GatewayId]net.UDPConn -} - -func NewUpAdapter(router Router) *UpAdapter { - adapter := UpAdapter{ - gateways: make(map[core.GatewayId]net.UDPConn), - logger: log.VoidLogger{}, - } - adapter.Connect(router) - return &adapter -} - -func (u UpAdapter) log(format string, a ...interface{}) { - u.logger.Log(format, a...) -} - -func (u *UpAdapter) Ack(packet semtech.Packet, gid core.GatewayId) { - if u.router == nil { - u.log("Fails to Ack, not connected to a router") - return - } - - u.log("Acks packet %+v", packet) - - conn, ok := u.gateways[gid] - - if !ok { - u.log("Gateway connection not found") - u.router.HandleError(errAck(fmt.Errorf("Gateway connection not found"))) - return - } - - raw, err := semtech.Marshal(packet) - - if err != nil { - u.log("Unable to marshal given packet") - u.router.HandleError(errAck(fmt.Errorf("Unable to marshal given packet %+v", err))) - return - } - - _, err = conn.Write(raw) - - if err != nil { - u.log("Unable to send udp message") - u.router.HandleError(errAck(fmt.Errorf("Unable to send udp message %+v", err))) - return - } -} - -func (u *UpAdapter) Connect(router Router) { - u.log("Connects to router %+v", router) - u.router = &router -} - -type DownAdapter struct { -} - -func (d *DownAdapter) Broadcast(packet semtech.Packet) { - -} - -func (d *DownAdapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) { - + fmt.Println(err) } From 0b005d5cff62a19289acc30f361491e6af6cfc6f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 19:05:00 +0100 Subject: [PATCH 0151/2266] [router] Handle PULL_DATA packet from the router --- components/router/router.go | 49 ++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 22 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index b812478c2..f5b15c4b6 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -29,41 +29,46 @@ func New(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapt }, nil } +func (r *Router) log(format string, a ...interface{}) { + r.logger.Log(format, a...) +} + // HandleUplink implements the core.Router interface func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { + switch packet.Identifier { + case semtech.PULL_DATA: + r.log("PULL_DATA received, sending ack") + r.upAdapter.Ack(semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_ACK, + Token: packet.Token, + }, connId) + case semtech.PUSH_DATA: + r.log("TODO PUSH_DATA") + /* PUSH_DATA + * + * Send PUSH_ACK + * Stores the gateway connection id for later response + * Lookup for an existing broker associated to the device address + * Forward data to that broker + */ + default: + r.log("Unexpected packet receive from uplink %+v", packet) - /* PULL_DATA - * - * Send PULL_ACK - * Store the gateway in known gateway - */ - - /* PUSH_DATA - * - * Send PUSH_ACK - * Stores the gateway connection id for later response - * Lookup for an existing broker associated to the device address - * Forward data to that broker - */ - - /* Else - * - * Ignore / Raise an error - */ - + } } // HandleDownlink implements the core.Router interface func (r *Router) HandleDownlink(packet semtech.Packet) { - // TODO + // TODO MileStone 4 } // RegisterDevice implements the core.Router interface func (r *Router) RegisterDevice(devAddr core.DeviceAddress, broAddrs ...core.BrokerAddress) { - // TODO + // TODO MileStone 4 } // RegisterDevice implements the core.Router interface func (r *Router) HandleError(err error) { - fmt.Println(err) + fmt.Println(err) // Wow, much handling, very reliable } From ef8ffccc9e1649fcea23eab9b54e0891954c0475 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 19:05:28 +0100 Subject: [PATCH 0152/2266] [router] Update DEVPLAN --- DEVELOPMENT_PLAN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 9ec0d51ce..e7a1fceed 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -24,7 +24,7 @@ system will just forward messages using pre-configured end-device addresses. - [ ] Core - [x] Lookup for device address (only local) - [x] Invalidate broker periodically (only local) - - [ ] Acknowledge packet from gateway + - [x] Acknowledge packet from gateway - [ ] Forward packet to brokers - [ ] Reemit errored packet - [ ] Switch from local in-memory storage to Reddis From 65a33675ac4dfada3019c7e36caca6135f84466f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 19:51:21 +0100 Subject: [PATCH 0153/2266] [router] Setup backbone of router-broker-http adapter --- adapters/rtr_brk_http/adapter.go | 44 ++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 adapters/rtr_brk_http/adapter.go diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go new file mode 100644 index 000000000..0123c3f0e --- /dev/null +++ b/adapters/rtr_brk_http/adapter.go @@ -0,0 +1,44 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package rtr_brk_http +// +// Assume one endpoint url accessible through a POST http request +package rtr_brk_http + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/log" +) + +type Adapter struct { + router core.Router + logger log.Logger +} + +// New constructs a new Router-Broker-HTTP adapter +func New(router core.Router, broAddrs ...core.BrokerAddress) (*Adapter, error) { + return nil, nil +} + +// Connect implements the core.BrokerRouter interface +func (a *Adapter) Connect(router core.Router) { + a.log("Connects to router %+v", router) + a.router = router +} + +// Broadcast implements the core.BrokerRouter interface +func (a *Adapter) Broadcast(packet semtech.Packet) { + +} + +// Forward implements the core.BrokerRouter interface +func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) { + +} + +// log is nothing more than a shortcut / helper to access the logger +func (a Adapter) log(format string, i ...interface{}) { + a.logger.Log(format, i...) +} From ccd9d695cd99fe86d010cba697afc1aadaa1b563 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 19:51:48 +0100 Subject: [PATCH 0154/2266] [router] Implement Forward method --- adapters/rtr_brk_http/adapter.go | 47 ++++++++++++++++++++++++++++++++ core.go | 3 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 0123c3f0e..267768427 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -7,9 +7,12 @@ package rtr_brk_http import ( + "bytes" + "encoding/json" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/log" + "net/http" ) type Adapter struct { @@ -35,7 +38,51 @@ func (a *Adapter) Broadcast(packet semtech.Packet) { // Forward implements the core.BrokerRouter interface func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) { + if packet.Payload == nil || len(packet.Payload.RXPK) == 0 { + a.log("Ignores irrelevant packet %+v", packet) // NOTE Should we trigger an error here ? + return + } + client := http.Client{} + for _, addr := range broAddrs { + go func() { + data := new(bytes.Buffer) + rawJSON, err := json.Marshal(packet.Payload) + if err != nil { + a.log("Unable to marshal payload %+v", err) + a.router.HandleError(core.ErrForward(err)) + return + } + + _, err = data.Write(rawJSON) + + if err != nil { + a.log("Unable to write raw JSON in buffer %+v", err) + a.router.HandleError(core.ErrForward(err)) + return + } + + resp, err := client.Post(string(addr), "application/json", data) + + if err != nil { + a.log("Unable to send POST request %+v", err) + a.router.HandleError(core.ErrForward(err)) + return + } + + if resp.StatusCode != http.StatusOK { + a.log("Unexpected answer from the broker %+v", err) + a.router.HandleError(core.ErrForward(err)) + return + } + + // NOTE Do We Care about the response ? The router is supposed to handle HTTP request + // from the broker to handle packets or anything else ? Is it efficient ? Should + // downlinks packets be sent back with the HTTP body response ? Its a 2 seconds frame... + + resp.Body.Close() + }() + } } // log is nothing more than a shortcut / helper to access the logger diff --git a/core.go b/core.go index bf985d781..bb3de26e5 100644 --- a/core.go +++ b/core.go @@ -24,8 +24,9 @@ type GatewayRouterAdapter interface { Ack(packet Packet, cid ConnectionId) } +type ErrForward error type RouterBrokerAdapter interface { Connect(router Router) - Broadcast(packet Packet) + Broadcast(payload Packet) Forward(packet Packet, broAddrs ...BrokerAddress) } From f11861762032d3b6a3884374ee7972ed5f04ecd6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 20:52:52 +0100 Subject: [PATCH 0155/2266] [router] Reduce states and complexity in gateway-router adapter --- adapters/gtw_rtr_udp/adapter.go | 77 +++++++++++++++------------------ core.go | 19 ++++---- 2 files changed, 47 insertions(+), 49 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index 264fb2b31..48f8372e6 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -9,37 +9,23 @@ import ( "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/log" "net" - "sync" ) type Adapter struct { - router core.Router - logger log.Logger - gateways map[core.ConnectionId]*net.UDPAddr - conn *net.UDPConn - lock sync.RWMutex + Logger log.Logger + conn *net.UDPConn } // New constructs a new Gateway-Router-UDP adapter func New(router core.Router, port uint) (*Adapter, error) { adapter := Adapter{ - gateways: make(map[core.ConnectionId]*net.UDPAddr), - lock: sync.RWMutex{}, - logger: log.VoidLogger{}, + Logger: log.VoidLogger{}, } // Connect to the router and start listening on the given port of the current machine - adapter.Connect(router) - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { + if err := adapter.Connect(router, port); err != nil { return nil, err } - udpConn, err := net.ListenUDP("udp", addr) - if err != nil { - return nil, err - } - adapter.conn = udpConn - go adapter.listen() // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. // Return the adapter for further use return &adapter, nil @@ -47,25 +33,27 @@ func New(router core.Router, port uint) (*Adapter, error) { // log is nothing more than a shortcut / helper to access the logger func (a Adapter) log(format string, i ...interface{}) { - a.logger.Log(format, i...) + if a.Logger == nil { + return + } + a.Logger.Log(format, i...) } // Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Ack(packet semtech.Packet, cid core.ConnectionId) { - if a.router == nil { - a.log("Fails to Ack, not connected to a router") +func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) { + if a.conn == nil { + a.log("Connection not established. Connect the adaptor first.") + router.HandleError(core.ErrAck(fmt.Errorf("Connection not established. Connect the adaptor first."))) return } a.log("Acks packet %+v", packet) - a.lock.RLock() - addr, ok := a.gateways[cid] - a.lock.Unlock() + addr, err := net.ResolveUDPAddr("udp", string(gateway)) - if !ok { - a.log("Gateway connection not found") - a.router.HandleError(core.ErrAck(fmt.Errorf("Gateway connection not found"))) + if err != nil { + a.log("Unable to retrieve gateway address") + router.HandleError(core.ErrAck(err)) return } @@ -73,7 +61,7 @@ func (a *Adapter) Ack(packet semtech.Packet, cid core.ConnectionId) { if err != nil { a.log("Unable to marshal given packet") - a.router.HandleError(core.ErrAck(fmt.Errorf("Unable to marshal given packet %+v", err))) + router.HandleError(core.ErrAck(err)) return } @@ -81,38 +69,45 @@ func (a *Adapter) Ack(packet semtech.Packet, cid core.ConnectionId) { if err != nil { a.log("Unable to send udp message") - a.router.HandleError(core.ErrAck(fmt.Errorf("Unable to send udp message %+v", err))) + router.HandleError(core.ErrAck(err)) return } } // Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Connect(router core.Router) { - a.log("Connects to router %+v", router) - a.router = router +func (a *Adapter) Connect(router core.Router, port uint) error { + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + return err + } + a.conn, err = net.ListenUDP("udp", addr) + if err != nil { + return err + } + go a.listen(router) // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. + return nil } -func (a *Adapter) listen() { +// listen Handle incominng packets and forward them to the router +func (a *Adapter) listen(router core.Router) { for { buf := make([]byte, 1024) n, addr, err := a.conn.ReadFromUDP(buf) if err != nil { - a.log("Error: %v", err) // NOTE Errors are just ignored for now + a.log("Error: %v", err) + go router.HandleError(core.ErrUplink(err)) continue } a.log("Incoming datagram %x", buf[:n]) pkt, err := semtech.Unmarshal(buf[:n]) if err != nil { - a.log("Error: %v", err) // NOTE Errors are just ignored for now + a.log("Error: %v", err) + go router.HandleError(core.ErrUplink(err)) continue } // When a packet is received pass it to the router for processing - cid := core.ConnectionId(addr.String()) - a.lock.Lock() - a.gateways[cid] = addr - a.lock.Unlock() - a.router.HandleUplink(*pkt, cid) + router.HandleUplink(*pkt, core.GatewayAddress(addr.String())) } } diff --git a/core.go b/core.go index bb3de26e5..f03361b0e 100644 --- a/core.go +++ b/core.go @@ -9,24 +9,27 @@ import ( type DeviceAddress string type BrokerAddress string -type ConnectionId string +type GatewayAddress string type Router interface { HandleError(err error) - HandleUplink(packet Packet, connId ConnectionId) - HandleDownlink(packet Packet, connId ConnectionId) + HandleUplink(packet Packet, gateway GatewayAddress) + HandleDownlink(packet Packet, broker BrokerAddress) RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) } +type ErrUplink error type ErrAck error type GatewayRouterAdapter interface { - Connect(router Router) - Ack(packet Packet, cid ConnectionId) + Connect(router Router, port uint) error + Ack(router Router, packet Packet, gateway GatewayAddress) } +type ErrDownlink error type ErrForward error +type ErrBroadcast error type RouterBrokerAdapter interface { - Connect(router Router) - Broadcast(payload Packet) - Forward(packet Packet, broAddrs ...BrokerAddress) + Connect(router Router, broAddrs ...BrokerAddress) error + Broadcast(router Router, payload Packet) + Forward(router Router, packet Packet, broAddrs ...BrokerAddress) } From 0124703aee07b10c942a4bee6eed20136e8ff583 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 21:01:16 +0100 Subject: [PATCH 0156/2266] [router] Reduce states and complexity in router-broker adapter --- adapters/rtr_brk_http/adapter.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 267768427..0a50fa184 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -16,28 +16,25 @@ import ( ) type Adapter struct { - router core.Router - logger log.Logger + Logger log.Logger } // New constructs a new Router-Broker-HTTP adapter -func New(router core.Router, broAddrs ...core.BrokerAddress) (*Adapter, error) { +func New(router core.Router, port uint, broAddrs ...core.BrokerAddress) (*Adapter, error) { return nil, nil } // Connect implements the core.BrokerRouter interface -func (a *Adapter) Connect(router core.Router) { +func (a *Adapter) Connect(router core.Router, port uint, broAddrs ...core.BrokerAddress) { a.log("Connects to router %+v", router) - a.router = router } // Broadcast implements the core.BrokerRouter interface func (a *Adapter) Broadcast(packet semtech.Packet) { - } // Forward implements the core.BrokerRouter interface -func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) { +func (a *Adapter) Forward(router core.Router, packet semtech.Packet, broAddrs ...core.BrokerAddress) { if packet.Payload == nil || len(packet.Payload.RXPK) == 0 { a.log("Ignores irrelevant packet %+v", packet) // NOTE Should we trigger an error here ? return @@ -50,7 +47,7 @@ func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) rawJSON, err := json.Marshal(packet.Payload) if err != nil { a.log("Unable to marshal payload %+v", err) - a.router.HandleError(core.ErrForward(err)) + router.HandleError(core.ErrForward(err)) return } @@ -58,7 +55,7 @@ func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) if err != nil { a.log("Unable to write raw JSON in buffer %+v", err) - a.router.HandleError(core.ErrForward(err)) + router.HandleError(core.ErrForward(err)) return } @@ -66,13 +63,13 @@ func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) if err != nil { a.log("Unable to send POST request %+v", err) - a.router.HandleError(core.ErrForward(err)) + router.HandleError(core.ErrForward(err)) return } if resp.StatusCode != http.StatusOK { a.log("Unexpected answer from the broker %+v", err) - a.router.HandleError(core.ErrForward(err)) + router.HandleError(core.ErrForward(err)) return } @@ -87,5 +84,8 @@ func (a *Adapter) Forward(packet semtech.Packet, broAddrs ...core.BrokerAddress) // log is nothing more than a shortcut / helper to access the logger func (a Adapter) log(format string, i ...interface{}) { - a.logger.Log(format, i...) + if a.Logger == nil { + return + } + a.Logger.Log(format, i...) } From 9b3500d5d6750b2a8bcb11e286d2ffdbe540a367 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 21:18:21 +0100 Subject: [PATCH 0157/2266] [router] Reduce states and complexity from router --- adapters/gtw_rtr_udp/adapter.go | 2 +- components/router/router.go | 45 ++++++++++++++++++++------------- core.go | 6 ++--- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index 48f8372e6..c8f5fe57a 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -108,6 +108,6 @@ func (a *Adapter) listen(router core.Router) { } // When a packet is received pass it to the router for processing - router.HandleUplink(*pkt, core.GatewayAddress(addr.String())) + router.HandleUplink(a, *pkt, core.GatewayAddress(addr.String())) } } diff --git a/components/router/router.go b/components/router/router.go index f5b15c4b6..d6a62c33c 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -16,33 +16,29 @@ const ( ) type Router struct { - Port uint - upAdapter core.GatewayRouterAdapter - logger log.Logger + PortUDP uint + PortHTTP uint + Logger log.Logger } -func New(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, port uint) (*Router, error) { +func New(portUDP, portHTTP uint) (*Router, error) { return &Router{ - Port: port, - logger: log.VoidLogger{}, - upAdapter: upAdapter, + PortUDP: portUDP, + PortHTTP: portHTTP, + Logger: log.VoidLogger{}, }, nil } -func (r *Router) log(format string, a ...interface{}) { - r.logger.Log(format, a...) -} - // HandleUplink implements the core.Router interface -func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { +func (r *Router) HandleUplink(upAdapter core.GatewayRouterAdapter, packet semtech.Packet, gateway core.GatewayAddress) { switch packet.Identifier { case semtech.PULL_DATA: r.log("PULL_DATA received, sending ack") - r.upAdapter.Ack(semtech.Packet{ + upAdapter.Ack(r, semtech.Packet{ Version: semtech.VERSION, Identifier: semtech.PULL_ACK, Token: packet.Token, - }, connId) + }, gateway) case semtech.PUSH_DATA: r.log("TODO PUSH_DATA") /* PUSH_DATA @@ -59,7 +55,7 @@ func (r *Router) HandleUplink(packet semtech.Packet, connId core.ConnectionId) { } // HandleDownlink implements the core.Router interface -func (r *Router) HandleDownlink(packet semtech.Packet) { +func (r *Router) HandleDownlink(downAdapter core.RouterBrokerAdapter, packet semtech.Packet, broker core.BrokerAddress) { // TODO MileStone 4 } @@ -69,6 +65,21 @@ func (r *Router) RegisterDevice(devAddr core.DeviceAddress, broAddrs ...core.Bro } // RegisterDevice implements the core.Router interface -func (r *Router) HandleError(err error) { - fmt.Println(err) // Wow, much handling, very reliable +func (r *Router) HandleError(err interface{}) { + switch err.(type) { + case core.ErrAck: + case core.ErrDownlink: + case core.ErrForward: + case core.ErrBroadcast: + case core.ErrUplink: + default: + fmt.Println(err) // Wow, much handling, very reliable + } +} + +func (r *Router) log(format string, a ...interface{}) { + if r.Logger == nil { + return + } + r.Logger.Log(format, a...) } diff --git a/core.go b/core.go index f03361b0e..58c73f5b3 100644 --- a/core.go +++ b/core.go @@ -12,9 +12,9 @@ type BrokerAddress string type GatewayAddress string type Router interface { - HandleError(err error) - HandleUplink(packet Packet, gateway GatewayAddress) - HandleDownlink(packet Packet, broker BrokerAddress) + HandleError(err interface{}) + HandleUplink(upAdapter GatewayRouterAdapter, packet Packet, gateway GatewayAddress) + HandleDownlink(downAdapter RouterBrokerAdapter, packet Packet, broker BrokerAddress) RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) } From a994bee7158c752a0400968ae2ed41f4c015e61a Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 21:22:41 +0100 Subject: [PATCH 0158/2266] [router] Move method for consistency between files --- adapters/gtw_rtr_udp/adapter.go | 38 ++++++++++++++++----------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index c8f5fe57a..e6d10aaa7 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -31,12 +31,18 @@ func New(router core.Router, port uint) (*Adapter, error) { return &adapter, nil } -// log is nothing more than a shortcut / helper to access the logger -func (a Adapter) log(format string, i ...interface{}) { - if a.Logger == nil { - return +// Ack implements the core.GatewayRouterAdapter interface +func (a *Adapter) Connect(router core.Router, port uint) error { + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + return err } - a.Logger.Log(format, i...) + a.conn, err = net.ListenUDP("udp", addr) + if err != nil { + return err + } + go a.listen(router) // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. + return nil } // Ack implements the core.GatewayRouterAdapter interface @@ -74,20 +80,6 @@ func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.Ga } } -// Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Connect(router core.Router, port uint) error { - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { - return err - } - a.conn, err = net.ListenUDP("udp", addr) - if err != nil { - return err - } - go a.listen(router) // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. - return nil -} - // listen Handle incominng packets and forward them to the router func (a *Adapter) listen(router core.Router) { for { @@ -111,3 +103,11 @@ func (a *Adapter) listen(router core.Router) { router.HandleUplink(a, *pkt, core.GatewayAddress(addr.String())) } } + +// log is nothing more than a shortcut / helper to access the logger +func (a Adapter) log(format string, i ...interface{}) { + if a.Logger == nil { + return + } + a.Logger.Log(format, i...) +} From f19d35af5792a6b7005e937534cffa415c3b6649 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 21:35:03 +0100 Subject: [PATCH 0159/2266] [router] Add some doc in core.go --- core.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/core.go b/core.go index 58c73f5b3..32b2774c4 100644 --- a/core.go +++ b/core.go @@ -12,16 +12,30 @@ type BrokerAddress string type GatewayAddress string type Router interface { + // HandlerError manages all kind of error that occur during the router lifecycle HandleError(err interface{}) + + // HandleUplink manages uplink packets coming from a gateway HandleUplink(upAdapter GatewayRouterAdapter, packet Packet, gateway GatewayAddress) + + // HandleDownlink manages downlink packets coming from a broker HandleDownlink(downAdapter RouterBrokerAdapter, packet Packet, broker BrokerAddress) + + // RegisterDevice associates a device address to a set of brokers for a given period RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) } +// The error types belows are going to be more complex in order to handle custom behavior for +// each error type. type ErrUplink error type ErrAck error + type GatewayRouterAdapter interface { + // Establish the adapter connection, whatever protocol is being used. Connect(router Router, port uint) error + + // Ack allows the router to send back a response to a gateway. The name of the method is quite a + // bad call and will probably change soon. Ack(router Router, packet Packet, gateway GatewayAddress) } @@ -29,7 +43,17 @@ type ErrDownlink error type ErrForward error type ErrBroadcast error type RouterBrokerAdapter interface { + // Establish the adapter connection, whatever protocol is being used. Connect(router Router, broAddrs ...BrokerAddress) error + + // Broadcast makes the adapter discover all available brokers by sending them a the given packets. + // + // We assume that broadcast is also registering a device address towards the router depending + // on the brokers responses. Broadcast(router Router, payload Packet) + + // Forward is an explicit forwarding of a packet which is known being handled by a set of + // brokers. None of the contacted broker is supposed to reject the incoming payload; They all + // ave been queried before and are known as dedicated brokers for the related end-device. Forward(router Router, packet Packet, broAddrs ...BrokerAddress) } From 6efd3108c1e7eaa15c667f437c8ab4e53361bc8e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 21:49:21 +0100 Subject: [PATCH 0160/2266] [router] Complete backbone of router-broker adapter --- adapters/rtr_brk_http/adapter.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 0a50fa184..2a9e0f3ea 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -16,21 +16,32 @@ import ( ) type Adapter struct { - Logger log.Logger + Logger log.Logger + brokers []core.BrokerAddress } // New constructs a new Router-Broker-HTTP adapter func New(router core.Router, port uint, broAddrs ...core.BrokerAddress) (*Adapter, error) { - return nil, nil + adapter := Adapter{ + Logger: log.VoidLogger{}, + brokers: broAddrs, + } + + if err := adapter.Connect(router, port, broAddrs...); err != nil { + return nil, err + } + + return &adapter, nil } // Connect implements the core.BrokerRouter interface -func (a *Adapter) Connect(router core.Router, port uint, broAddrs ...core.BrokerAddress) { +func (a *Adapter) Connect(router core.Router, port uint, broAddrs ...core.BrokerAddress) error { a.log("Connects to router %+v", router) + return nil } // Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(packet semtech.Packet) { +func (a *Adapter) Broadcast(router core.Router, packet semtech.Packet) { } // Forward implements the core.BrokerRouter interface From 1720971ffe87cc6fe1e6c874afdfd1e26032bad2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 22:50:40 +0100 Subject: [PATCH 0161/2266] [router] Add DevAddr() to semtech RXPK and TXPK to determine the associated end-device address --- lorawan/semtech/semtech.go | 100 ++++++++++++++++++++++++++----------- 1 file changed, 72 insertions(+), 28 deletions(-) diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go index 9d3b97efa..86c6a2184 100644 --- a/lorawan/semtech/semtech.go +++ b/lorawan/semtech/semtech.go @@ -7,44 +7,88 @@ package semtech import ( + "encoding/base64" "time" ) // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + devAddr *[4]byte // End-Device address, according to the Data. Memoized here. +} + +// DevAddr returns the end-device address described in the payload +func (rxpk *RXPK) DevAddr() *[4]byte { + if rxpk.devAddr != nil { + return rxpk.devAddr + } + + if rxpk.Data == nil { + return nil + } + + buf, err := base64.StdEncoding.DecodeString(*rxpk.Data) + if err != nil || len(buf) < 5 { + return nil + } + + rxpk.devAddr = new([4]byte) + copy((*rxpk.devAddr)[:], buf[1:5]) // Device Address corresponds to the first 4 bytes of the Frame Header, after one byte of MAC_HEADER + return rxpk.devAddr + } // TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + devAddr *[4]byte // End-Device address, according to the Data. Memoized here. +} + +// DevAddr returns the end-device address described in the payload +func (txpk *TXPK) DevAddr() *[4]byte { + if txpk.devAddr != nil { + return txpk.devAddr + } + + if txpk.Data == nil { + return nil + } + + buf, err := base64.StdEncoding.DecodeString(*txpk.Data) + if err != nil || len(buf) < 5 { + return nil + } + + txpk.devAddr = new([4]byte) + copy((*txpk.devAddr)[:], buf[1:5]) // Device Address corresponds to the first 4 bytes of the Frame Header, after one byte of MAC_HEADER + return txpk.devAddr } // Stat represents a status json message format sent by the gateway From db60c31a8387ba366b583641479915802eb7a330 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 22:51:07 +0100 Subject: [PATCH 0162/2266] [router] Fix core interfaces signatures --- core.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core.go b/core.go index 32b2774c4..60797a285 100644 --- a/core.go +++ b/core.go @@ -7,7 +7,7 @@ import ( . "github.com/thethingsnetwork/core/lorawan/semtech" ) -type DeviceAddress string +type DeviceAddress [4]byte type BrokerAddress string type GatewayAddress string @@ -50,10 +50,10 @@ type RouterBrokerAdapter interface { // // We assume that broadcast is also registering a device address towards the router depending // on the brokers responses. - Broadcast(router Router, payload Packet) + Broadcast(router Router, payload Payload) // Forward is an explicit forwarding of a packet which is known being handled by a set of // brokers. None of the contacted broker is supposed to reject the incoming payload; They all // ave been queried before and are known as dedicated brokers for the related end-device. - Forward(router Router, packet Packet, broAddrs ...BrokerAddress) + Forward(router Router, packet Payload, broAddrs ...BrokerAddress) } From b5407f207df9298f0fbd1ea3edfec1e7aa20eb8a Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 22:51:48 +0100 Subject: [PATCH 0163/2266] [router] Rewrite Router-Broker Forward() and add Broadcast() methods --- adapters/rtr_brk_http/adapter.go | 93 ++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 21 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 2a9e0f3ea..f84719864 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -9,10 +9,12 @@ package rtr_brk_http import ( "bytes" "encoding/json" + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/log" "net/http" + "sync" ) type Adapter struct { @@ -41,36 +43,70 @@ func (a *Adapter) Connect(router core.Router, port uint, broAddrs ...core.Broker } // Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, packet semtech.Packet) { -} - -// Forward implements the core.BrokerRouter interface -func (a *Adapter) Forward(router core.Router, packet semtech.Packet, broAddrs ...core.BrokerAddress) { - if packet.Payload == nil || len(packet.Payload.RXPK) == 0 { - a.log("Ignores irrelevant packet %+v", packet) // NOTE Should we trigger an error here ? +func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { + // Determine the devAddress associated to that payload + if payload.RXPK == nil || payload.TXPK != nil { + router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) return } + var devAddr [4]byte + // We check them all to be sure, but all RXPK should refer to the same End-Device + for _, rxpk := range payload.RXPK { + addr := rxpk.DevAddr() + if addr == nil || (devAddr != [4]byte{} && devAddr != *addr) { + router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) + return + } + devAddr = *addr + } + + // Prepare ground to store brokers that are in charge + brokers := make([]core.BrokerAddress, 0) + wg := sync.WaitGroup{} + wg.Add(len(a.brokers)) client := http.Client{} - for _, addr := range broAddrs { - go func() { - data := new(bytes.Buffer) - rawJSON, err := json.Marshal(packet.Payload) + for _, addr := range a.brokers { + go func(addr core.BrokerAddress) { + defer wg.Done() + + resp, err := post(client, string(addr), payload) + if err != nil { - a.log("Unable to marshal payload %+v", err) - router.HandleError(core.ErrForward(err)) + a.log("Unable to send POST request %+v", err) + router.HandleError(core.ErrBroadcast(err)) return } - _, err = data.Write(rawJSON) + defer resp.Body.Close() - if err != nil { - a.log("Unable to write raw JSON in buffer %+v", err) - router.HandleError(core.ErrForward(err)) - return + switch resp.StatusCode { + case http.StatusOK: + a.log("Broker %+v handles packets coming from %+v", addr, devAddr) + brokers = append(brokers, addr) + case http.StatusNotFound: //NOTE Convention with the broker + a.log("Broker %+v does not handle packets coming from %+v", addr, devAddr) + default: + a.log("Unexpected answer from the broker %+v", err) + router.HandleError(core.ErrBroadcast(err)) } + }(addr) + } - resp, err := client.Post(string(addr), "application/json", data) + go func() { + wg.Wait() + if len(brokers) > 0 { + router.RegisterDevice(core.DeviceAddress(devAddr), brokers...) + } + }() +} + +// Forward implements the core.BrokerRouter interface +func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { + client := http.Client{} + for _, addr := range broAddrs { + go func(url string) { + resp, err := post(client, url, payload) if err != nil { a.log("Unable to send POST request %+v", err) @@ -78,6 +114,8 @@ func (a *Adapter) Forward(router core.Router, packet semtech.Packet, broAddrs .. return } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { a.log("Unexpected answer from the broker %+v", err) router.HandleError(core.ErrForward(err)) @@ -88,9 +126,22 @@ func (a *Adapter) Forward(router core.Router, packet semtech.Packet, broAddrs .. // from the broker to handle packets or anything else ? Is it efficient ? Should // downlinks packets be sent back with the HTTP body response ? Its a 2 seconds frame... - resp.Body.Close() - }() + }(string(addr)) + } +} + +func post(client http.Client, url string, payload semtech.Payload) (*http.Response, error) { + data := new(bytes.Buffer) + rawJSON, err := json.Marshal(payload) + if err != nil { + return nil, err } + + if _, err := data.Write(rawJSON); err != nil { + return nil, err + } + + return client.Post(url, "application/json", data) } // log is nothing more than a shortcut / helper to access the logger From b63e34b5ca2199f88f84731818b38841f132a646 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 22:58:03 +0100 Subject: [PATCH 0164/2266] [router] Fix unfortunate race-condition with broker registration --- adapters/rtr_brk_http/adapter.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index f84719864..fe36454bd 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -61,7 +61,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { } // Prepare ground to store brokers that are in charge - brokers := make([]core.BrokerAddress, 0) + register := make(chan core.BrokerAddress, len(a.brokers)) wg := sync.WaitGroup{} wg.Add(len(a.brokers)) @@ -83,7 +83,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { switch resp.StatusCode { case http.StatusOK: a.log("Broker %+v handles packets coming from %+v", addr, devAddr) - brokers = append(brokers, addr) + register <- addr case http.StatusNotFound: //NOTE Convention with the broker a.log("Broker %+v does not handle packets coming from %+v", addr, devAddr) default: @@ -95,6 +95,11 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { go func() { wg.Wait() + close(register) + brokers := make([]core.BrokerAddress, 0) + for addr := range register { + brokers = append(brokers, addr) + } if len(brokers) > 0 { router.RegisterDevice(core.DeviceAddress(devAddr), brokers...) } From d03f66f8b4753eb0ee035f1581ce181c66e886a9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 23:22:04 +0100 Subject: [PATCH 0165/2266] [router] Move DeviceAddress from core to semtech --- adapters/rtr_brk_http/adapter.go | 7 ++- components/router/address_keeper.go | 14 ++--- components/router/address_keeper_test.go | 5 +- core.go | 1 - lorawan/semtech/semtech.go | 71 ++++++++++++------------ 5 files changed, 50 insertions(+), 48 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index fe36454bd..365eac43f 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -49,11 +49,12 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) return } - var devAddr [4]byte + var devAddr semtech.DeviceAddress + var defaultDevAddr semtech.DeviceAddress // We check them all to be sure, but all RXPK should refer to the same End-Device for _, rxpk := range payload.RXPK { addr := rxpk.DevAddr() - if addr == nil || (devAddr != [4]byte{} && devAddr != *addr) { + if addr == nil || (devAddr != defaultDevAddr && devAddr != *addr) { router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) return } @@ -101,7 +102,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { brokers = append(brokers, addr) } if len(brokers) > 0 { - router.RegisterDevice(core.DeviceAddress(devAddr), brokers...) + router.RegisterDevice(devAddr, brokers...) } }() } diff --git a/components/router/address_keeper.go b/components/router/address_keeper.go index 4925ec9c0..1e490b08f 100644 --- a/components/router/address_keeper.go +++ b/components/router/address_keeper.go @@ -5,20 +5,20 @@ package router import ( "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" "time" ) type addressKeeper interface { - lookup(devAddr core.DeviceAddress) ([]core.BrokerAddress, error) - store(devAddr core.DeviceAddress, brosAddr ...core.BrokerAddress) error + lookup(devAddr semtech.DeviceAddress) ([]core.BrokerAddress, error) + store(devAddr semtech.DeviceAddress, brosAddr ...core.BrokerAddress) error } type reddisAddressKeeper struct{} // In a second time type localDB struct { expiryDelay time.Duration - addresses map[core.DeviceAddress]localEntry + addresses map[semtech.DeviceAddress]localEntry } type localEntry struct { @@ -34,12 +34,12 @@ func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { return &localDB{ expiryDelay: expiryDelay, - addresses: make(map[core.DeviceAddress]localEntry), + addresses: make(map[semtech.DeviceAddress]localEntry), }, nil } // lookup implements the addressKeeper interface -func (a *localDB) lookup(devAddr core.DeviceAddress) ([]core.BrokerAddress, error) { +func (a *localDB) lookup(devAddr semtech.DeviceAddress) ([]core.BrokerAddress, error) { entry, ok := a.addresses[devAddr] if !ok { return nil, fmt.Errorf("Device address not found") @@ -54,7 +54,7 @@ func (a *localDB) lookup(devAddr core.DeviceAddress) ([]core.BrokerAddress, erro } // store implements the addressKeeper interface -func (a *localDB) store(devAddr core.DeviceAddress, brosAddr ...core.BrokerAddress) error { +func (a *localDB) store(devAddr semtech.DeviceAddress, brosAddr ...core.BrokerAddress) error { _, ok := a.addresses[devAddr] if ok { return fmt.Errorf("An entry already exists for that device") diff --git a/components/router/address_keeper_test.go b/components/router/address_keeper_test.go index 5f562d090..de65446e4 100644 --- a/components/router/address_keeper_test.go +++ b/components/router/address_keeper_test.go @@ -7,12 +7,13 @@ import ( "fmt" . "github.com/smartystreets/goconvey/convey" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" "testing" "time" ) -func genDevAddr() core.DeviceAddress { - return core.DeviceAddress(fmt.Sprintf("DeviceAddress%d", time.Now())) +func genDevAddr() semtech.DeviceAddress { + return semtech.DeviceAddress(fmt.Sprintf("DeviceAddress%d", time.Now())) } func genBroAddr() core.BrokerAddress { diff --git a/core.go b/core.go index 60797a285..8f91f8167 100644 --- a/core.go +++ b/core.go @@ -7,7 +7,6 @@ import ( . "github.com/thethingsnetwork/core/lorawan/semtech" ) -type DeviceAddress [4]byte type BrokerAddress string type GatewayAddress string diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go index 86c6a2184..2e688f536 100644 --- a/lorawan/semtech/semtech.go +++ b/lorawan/semtech/semtech.go @@ -11,26 +11,28 @@ import ( "time" ) +type DeviceAddress [4]byte + // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) - devAddr *[4]byte // End-Device address, according to the Data. Memoized here. + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + devAddr *DeviceAddress // End-Device address, according to the Data. Memoized here. } // DevAddr returns the end-device address described in the payload -func (rxpk *RXPK) DevAddr() *[4]byte { +func (rxpk *RXPK) DevAddr() *DeviceAddress { if rxpk.devAddr != nil { return rxpk.devAddr } @@ -44,35 +46,34 @@ func (rxpk *RXPK) DevAddr() *[4]byte { return nil } - rxpk.devAddr = new([4]byte) + rxpk.devAddr = new(DeviceAddress) copy((*rxpk.devAddr)[:], buf[1:5]) // Device Address corresponds to the first 4 bytes of the Frame Header, after one byte of MAC_HEADER return rxpk.devAddr - } // TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) - devAddr *[4]byte // End-Device address, according to the Data. Memoized here. + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + devAddr *DeviceAddress // End-Device address, according to the Data. Memoized here. } // DevAddr returns the end-device address described in the payload -func (txpk *TXPK) DevAddr() *[4]byte { +func (txpk *TXPK) DevAddr() *DeviceAddress { if txpk.devAddr != nil { return txpk.devAddr } @@ -86,7 +87,7 @@ func (txpk *TXPK) DevAddr() *[4]byte { return nil } - txpk.devAddr = new([4]byte) + txpk.devAddr = new(DeviceAddress) copy((*txpk.devAddr)[:], buf[1:5]) // Device Address corresponds to the first 4 bytes of the Frame Header, after one byte of MAC_HEADER return txpk.devAddr } From 9010adbe02a7ad50601d5be0bfa338c106c47739 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 23:38:05 +0100 Subject: [PATCH 0166/2266] [router] Change deny condition in router-broker adapter broadcast --- adapters/rtr_brk_http/adapter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 365eac43f..e03b89f25 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -45,7 +45,7 @@ func (a *Adapter) Connect(router core.Router, port uint, broAddrs ...core.Broker // Broadcast implements the core.BrokerRouter interface func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { // Determine the devAddress associated to that payload - if payload.RXPK == nil || payload.TXPK != nil { + if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) return } From 584b9fedfe85db6b00475196335efef4b829df49 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 23:38:42 +0100 Subject: [PATCH 0167/2266] [router] Handle PUSH_DATA packets with router --- components/router/router.go | 76 +++++++++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 16 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index d6a62c33c..3d486721f 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -16,38 +16,82 @@ const ( ) type Router struct { - PortUDP uint - PortHTTP uint - Logger log.Logger + PortUDP uint + PortHTTP uint + Logger log.Logger + addressKeeper addressKeeper } func New(portUDP, portHTTP uint) (*Router, error) { + localDB, err := NewLocalDB(EXPIRY_DELAY) + + if err != nil { + return nil, error + } + return &Router{ - PortUDP: portUDP, - PortHTTP: portHTTP, - Logger: log.VoidLogger{}, + PortUDP: portUDP, + PortHTTP: portHTTP, + addressKeeper: localDB, + Logger: log.VoidLogger{}, }, nil } // HandleUplink implements the core.Router interface -func (r *Router) HandleUplink(upAdapter core.GatewayRouterAdapter, packet semtech.Packet, gateway core.GatewayAddress) { +func (r *Router) HandleUplink(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, packet semtech.Packet, gateway core.GatewayAddress) { switch packet.Identifier { case semtech.PULL_DATA: - r.log("PULL_DATA received, sending ack") + r.log("receives PULL_DATA, sending ack") upAdapter.Ack(r, semtech.Packet{ Version: semtech.VERSION, Identifier: semtech.PULL_ACK, Token: packet.Token, }, gateway) case semtech.PUSH_DATA: - r.log("TODO PUSH_DATA") - /* PUSH_DATA - * - * Send PUSH_ACK - * Stores the gateway connection id for later response - * Lookup for an existing broker associated to the device address - * Forward data to that broker - */ + // 1. Send an ack + r.log("receives PUSH_DATA, sending ack") + upAdapter.Ack(r, semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_ACK, + Token: packet.Token, + }, gateway) + + // 2. Determine payloads related to different end-devices present in the packet + // NOTE So far, Stats are ignored. + if packet.Payload == nil || len(packet.Payload.RXPK) == 0 { + r.log("Ignores inconsistent PUSH_DATA packet") + return + } + + payloads = make(map[semtech.DeviceAddress]semtech.Payload) + for _, rxpk := range packet.Payload.RXPK { + devAddr := rxpk.DevAddr() + if devAddr == nil { + r.log("Unable to determine end-device address for rxpk: %+v", rxpk) + continue + } + + if _, ok := payloads[*devAddr]; !ok { + payloads[*devAddr] = semtech.Payload{ + RXPK: make([]semtech.RXPK, 0), + } + } + + payloads[*devAddr].RXPK = append(payloads[*devAddr].RXPK, rxpk) + } + + // 3. Broadcast or Forward payloads depending wether or not the brokers are known + for payload, devAddr := range payloads { + brokers, err := r.addressKeeper.lookup(devAddr) + if err != nil { + r.log("Forward payload to known brokers %+v", payload) + downAdapter.Forward(router, payload, brokers...) + continue + } + + r.log("Broadcast payload to all brokers %+v", payload) + downAdapter.Broadcast(router, payload) + } default: r.log("Unexpected packet receive from uplink %+v", packet) From 900cbf16e745b0d00725f7b0929102cab15302f2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 23:40:30 +0100 Subject: [PATCH 0168/2266] [router] Rewrite core interfaces signatures. Both adapters are needed to handle downlink and uplink. --- components/router/router.go | 6 +++--- core.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index 3d486721f..1bd5413ed 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -99,13 +99,13 @@ func (r *Router) HandleUplink(upAdapter core.GatewayRouterAdapter, downAdapter c } // HandleDownlink implements the core.Router interface -func (r *Router) HandleDownlink(downAdapter core.RouterBrokerAdapter, packet semtech.Packet, broker core.BrokerAddress) { +func (r *Router) HandleDownlink(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, packet semtech.Packet, broker core.BrokerAddress) { // TODO MileStone 4 } // RegisterDevice implements the core.Router interface -func (r *Router) RegisterDevice(devAddr core.DeviceAddress, broAddrs ...core.BrokerAddress) { - // TODO MileStone 4 +func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { + r.addressKeeper.store(devAddr, broAddrs) // TODO handle the error } // RegisterDevice implements the core.Router interface diff --git a/core.go b/core.go index 8f91f8167..539fe8b9b 100644 --- a/core.go +++ b/core.go @@ -15,10 +15,10 @@ type Router interface { HandleError(err interface{}) // HandleUplink manages uplink packets coming from a gateway - HandleUplink(upAdapter GatewayRouterAdapter, packet Packet, gateway GatewayAddress) + HandleUplink(upAdapter GatewayRouterAdapter, downAdapter RouterBrokerAdapter, packet Packet, gateway GatewayAddress) // HandleDownlink manages downlink packets coming from a broker - HandleDownlink(downAdapter RouterBrokerAdapter, packet Packet, broker BrokerAddress) + HandleDownlink(upAdapter GatewayRouterAdapter, downAdapter RouterBrokerAdapter, packet Packet, broker BrokerAddress) // RegisterDevice associates a device address to a set of brokers for a given period RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) From aa25bf15990508039f5444b13f25fcded088f8f8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 30 Dec 2015 23:41:35 +0100 Subject: [PATCH 0169/2266] [router] Update DEV PLAN --- DEVELOPMENT_PLAN.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index e7a1fceed..60f833d3b 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -25,7 +25,11 @@ system will just forward messages using pre-configured end-device addresses. - [x] Lookup for device address (only local) - [x] Invalidate broker periodically (only local) - [x] Acknowledge packet from gateway - - [ ] Forward packet to brokers + - [x] Forward packet to brokers + - [ ] Create Mock router + - [ ] Create Mock DownAdapter + - [ ] Create Mock UpAdatper + - [ ] Test'em all - [ ] Reemit errored packet - [ ] Switch from local in-memory storage to Reddis - [ ] UpAdapter From a3a662fe331798747c96a3f3088b8ef7a3f21c98 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 10:06:14 +0100 Subject: [PATCH 0170/2266] [simulators.gateway] Update dev plan --- DEVELOPMENT_PLAN.md | 8 +++++++- simulators/gateway/TODO.md | 5 ----- 2 files changed, 7 insertions(+), 6 deletions(-) delete mode 100644 simulators/gateway/TODO.md diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 2fbfd603b..bc97414d2 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -12,7 +12,13 @@ mainly for testing and ensuring the correctness of other components. - [x] Serialize json rxpk/stat object(s) - [x] Generate json rxpl/stat object(s) - [x] Update gateway statistics accordingly - - [ ] Simulate fake end-devices activity + - [ ] Load config from file or ENV + - [ ] Generate valid fake end-device packet + - [ ] Simulate fake end-devices traffic + - [ ] Complete tests set for stats functions + - [ ] Implements UDP ReadWriteCloser + - [ ] Move existing fake ReadWriteCloser to a proper file with handy constructor + ## Milestone 2 Handle an uplink process that can forward packet coming from a gateway to a simple end-server diff --git a/simulators/gateway/TODO.md b/simulators/gateway/TODO.md deleted file mode 100644 index f24af9b25..000000000 --- a/simulators/gateway/TODO.md +++ /dev/null @@ -1,5 +0,0 @@ -- Complete tests set for stats functions -- Generate valid fake end-device packet -- Simulate fake end-devices traffic -- Generate UDP ReadWriteCloser -- Move existing fake ReadWriteCloser to a proper file with handy constructor From a92bbb0f9f1d76540f79fb34de38dcf7a8f8a68d Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 10:06:30 +0100 Subject: [PATCH 0171/2266] [simulators.gateway] Rename main.go -> doc.go in simulators/gateway --- simulators/gateway/{main.go => doc.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename simulators/gateway/{main.go => doc.go} (100%) diff --git a/simulators/gateway/main.go b/simulators/gateway/doc.go similarity index 100% rename from simulators/gateway/main.go rename to simulators/gateway/doc.go From 608112d5fa312c4aaac829da8461acf6dca3504a Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 13:18:43 +0100 Subject: [PATCH 0172/2266] [router] Generate backbone of gateway -> router mock adapter --- testing/adapters/gtw_rtr_moke/adapter.go | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 testing/adapters/gtw_rtr_moke/adapter.go diff --git a/testing/adapters/gtw_rtr_moke/adapter.go b/testing/adapters/gtw_rtr_moke/adapter.go new file mode 100644 index 000000000..717ab481d --- /dev/null +++ b/testing/adapters/gtw_rtr_moke/adapter.go @@ -0,0 +1,25 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gtw_rtr_moke + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" +) + +type Adapter struct{} + +// New constructs a new Gateway-Router-Moke adapter +func New(router core.Router, port uint) (*Adapter, error) { + return nil, nil +} + +// Ack implements the core.GatewayRouterAdapter interface +func (a *Adapter) Connect(router core.Router, port uint) error { + return nil +} + +// Ack implements the core.GatewayRouterAdapter interface +func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) { +} From 43b88a96ec5cc87f77ebf72e0362fe02b636cb71 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 14:53:39 +0100 Subject: [PATCH 0173/2266] [router] Update core interfaces again. --- core.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/core.go b/core.go index 539fe8b9b..48c81b0d8 100644 --- a/core.go +++ b/core.go @@ -15,13 +15,16 @@ type Router interface { HandleError(err interface{}) // HandleUplink manages uplink packets coming from a gateway - HandleUplink(upAdapter GatewayRouterAdapter, downAdapter RouterBrokerAdapter, packet Packet, gateway GatewayAddress) + HandleUplink(packet Packet, gateway GatewayAddress) // HandleDownlink manages downlink packets coming from a broker - HandleDownlink(upAdapter GatewayRouterAdapter, downAdapter RouterBrokerAdapter, packet Packet, broker BrokerAddress) + HandleDownlink(payload Payload, broker BrokerAddress) // RegisterDevice associates a device address to a set of brokers for a given period RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) + + // Connect the router to its adapters + Connect(upAdapter GatewayRouterAdapter, downAdapter RouterBrokerAdapter) } // The error types belows are going to be more complex in order to handle custom behavior for @@ -29,10 +32,13 @@ type Router interface { type ErrUplink error type ErrAck error -type GatewayRouterAdapter interface { +type Adapter interface { // Establish the adapter connection, whatever protocol is being used. - Connect(router Router, port uint) error + Listen(router Router, options interface{}) error +} +type GatewayRouterAdapter interface { + Adapter // Ack allows the router to send back a response to a gateway. The name of the method is quite a // bad call and will probably change soon. Ack(router Router, packet Packet, gateway GatewayAddress) @@ -42,8 +48,7 @@ type ErrDownlink error type ErrForward error type ErrBroadcast error type RouterBrokerAdapter interface { - // Establish the adapter connection, whatever protocol is being used. - Connect(router Router, broAddrs ...BrokerAddress) error + Adapter // Broadcast makes the adapter discover all available brokers by sending them a the given packets. // @@ -54,5 +59,5 @@ type RouterBrokerAdapter interface { // Forward is an explicit forwarding of a packet which is known being handled by a set of // brokers. None of the contacted broker is supposed to reject the incoming payload; They all // ave been queried before and are known as dedicated brokers for the related end-device. - Forward(router Router, packet Payload, broAddrs ...BrokerAddress) + Forward(router Router, payload Payload, broAddrs ...BrokerAddress) } From 6ad95439232729e4382fb2909dca1816ba2961af Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 14:54:54 +0100 Subject: [PATCH 0174/2266] [router] Updates adapters to be compliant with the new interface + make function calls more pure --- adapters/gtw_rtr_udp/adapter.go | 29 +++++++++++++---------------- adapters/rtr_brk_http/adapter.go | 30 ++++++++---------------------- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index e6d10aaa7..e37e1567f 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -16,23 +16,20 @@ type Adapter struct { conn *net.UDPConn } -// New constructs a new Gateway-Router-UDP adapter -func New(router core.Router, port uint) (*Adapter, error) { - adapter := Adapter{ - Logger: log.VoidLogger{}, - } +// Ack implements the core.Adapter interface. It expects only one param "port" as a +// uint +func (a *Adapter) Listen(router core.Router, options interface{}) error { + // Parse options + var port uint + switch options.(type) { + case uint: + port = options.(uint) + default: + return fmt.Errorf("Unreckognized options %+v\n", options) - // Connect to the router and start listening on the given port of the current machine - if err := adapter.Connect(router, port); err != nil { - return nil, err } - // Return the adapter for further use - return &adapter, nil -} - -// Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Connect(router core.Router, port uint) error { + // Create the udp connection and start listening with a goroutine addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) if err != nil { return err @@ -80,7 +77,7 @@ func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.Ga } } -// listen Handle incominng packets and forward them to the router +// listen Handle incoming packets and forward them to the router func (a *Adapter) listen(router core.Router) { for { buf := make([]byte, 1024) @@ -100,7 +97,7 @@ func (a *Adapter) listen(router core.Router) { } // When a packet is received pass it to the router for processing - router.HandleUplink(a, *pkt, core.GatewayAddress(addr.String())) + router.HandleUplink(*pkt, core.GatewayAddress(addr.String())) } } diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index e03b89f25..377e6fca1 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -18,32 +18,17 @@ import ( ) type Adapter struct { - Logger log.Logger - brokers []core.BrokerAddress + Logger log.Logger } -// New constructs a new Router-Broker-HTTP adapter -func New(router core.Router, port uint, broAddrs ...core.BrokerAddress) (*Adapter, error) { - adapter := Adapter{ - Logger: log.VoidLogger{}, - brokers: broAddrs, - } - - if err := adapter.Connect(router, port, broAddrs...); err != nil { - return nil, err - } - - return &adapter, nil -} - -// Connect implements the core.BrokerRouter interface -func (a *Adapter) Connect(router core.Router, port uint, broAddrs ...core.BrokerAddress) error { +// Connect implements the core.Adapter interface +func (a *Adapter) Connect(router core.Router, options interface{}) error { a.log("Connects to router %+v", router) return nil } // Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { +func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { // Determine the devAddress associated to that payload if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) @@ -62,12 +47,12 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) { } // Prepare ground to store brokers that are in charge - register := make(chan core.BrokerAddress, len(a.brokers)) + register := make(chan core.BrokerAddress, len(broAddrs)) wg := sync.WaitGroup{} - wg.Add(len(a.brokers)) + wg.Add(len(broAddrs)) client := http.Client{} - for _, addr := range a.brokers { + for _, addr := range broAddrs { go func(addr core.BrokerAddress) { defer wg.Done() @@ -136,6 +121,7 @@ func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs } } +// post regroups some logic used in both Forward and Broadcast methods func post(client http.Client, url string, payload semtech.Payload) (*http.Response, error) { data := new(bytes.Buffer) rawJSON, err := json.Marshal(payload) From 83732f24ded31bd453c4787c466e6fda35d2b5e8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 14:55:18 +0100 Subject: [PATCH 0175/2266] [router] Implements basic mock upadapter --- testing/adapters/gtw_rtr_moke/adapter.go | 36 ++++++++++++++++++++---- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/testing/adapters/gtw_rtr_moke/adapter.go b/testing/adapters/gtw_rtr_moke/adapter.go index 717ab481d..ee4c464f3 100644 --- a/testing/adapters/gtw_rtr_moke/adapter.go +++ b/testing/adapters/gtw_rtr_moke/adapter.go @@ -1,25 +1,51 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. +// package gtw_rtr_moke offers a gateway <-> router moke adapter that can be used to test a router +// implementation. package gtw_rtr_moke import ( + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" ) -type Adapter struct{} +type Adapter struct { + FailAck bool + FailConnect bool + connected bool + acks map[core.GatewayAddress][]semtech.Packet +} // New constructs a new Gateway-Router-Moke adapter -func New(router core.Router, port uint) (*Adapter, error) { - return nil, nil +func New() (*Adapter, error) { + return &Adapter{ + FailAck: false, + FailConnect: false, + connected: false, + acks: make(map[core.GatewayAddress][]semtech.Packet), + }, nil } -// Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Connect(router core.Router, port uint) error { +// Listen implements the core.Adapter interface +func (a *Adapter) Listen(router core.Router, options interface{}) error { + if a.FailConnect { + return fmt.Errorf("Unable to establish connection") + } + a.connected = true return nil } // Ack implements the core.GatewayRouterAdapter interface func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) { + if a.FailAck { + router.HandleError(core.ErrAck(fmt.Errorf("Unable to ack the given packet"))) + return + } + a.acks[gateway] = append(a.acks[gateway], packet) +} + +func (a *Adapter) GetAcks(gateway core.GatewayAddress) []semtech.Packet { + return a.acks[gateway] } From b35f3ab109d62f0b4ffe6501a2543a47420e9474 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 15:07:56 +0100 Subject: [PATCH 0176/2266] [router] Correct some type previously made --- adapters/rtr_brk_http/adapter.go | 4 ++-- testing/adapters/{gtw_rtr_moke => gtw_rtr_mock}/adapter.go | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) rename testing/adapters/{gtw_rtr_moke => gtw_rtr_mock}/adapter.go (89%) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 377e6fca1..5cfa6e0e8 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -21,8 +21,8 @@ type Adapter struct { Logger log.Logger } -// Connect implements the core.Adapter interface -func (a *Adapter) Connect(router core.Router, options interface{}) error { +// Listen implements the core.Adapter interface +func (a *Adapter) Listen(router core.Router, options interface{}) error { a.log("Connects to router %+v", router) return nil } diff --git a/testing/adapters/gtw_rtr_moke/adapter.go b/testing/adapters/gtw_rtr_mock/adapter.go similarity index 89% rename from testing/adapters/gtw_rtr_moke/adapter.go rename to testing/adapters/gtw_rtr_mock/adapter.go index ee4c464f3..07ab7c854 100644 --- a/testing/adapters/gtw_rtr_moke/adapter.go +++ b/testing/adapters/gtw_rtr_mock/adapter.go @@ -1,9 +1,9 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// package gtw_rtr_moke offers a gateway <-> router moke adapter that can be used to test a router +// package gtw_rtr_mock offers a gateway <-> router mock adapter that can be used to test a router // implementation. -package gtw_rtr_moke +package gtw_rtr_mock import ( "fmt" @@ -46,6 +46,7 @@ func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.Ga a.acks[gateway] = append(a.acks[gateway], packet) } +// GetAcks returns packets that has been pass through the Ack method() func (a *Adapter) GetAcks(gateway core.GatewayAddress) []semtech.Packet { return a.acks[gateway] } From 56f0a26740a692ac8ed5400bd0e04592ae576022 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 15:08:14 +0100 Subject: [PATCH 0177/2266] [router] Add backbone of mock router <-> broker adapter --- testing/adapters/rtr_brk_mock/adapter.go | 26 ++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 testing/adapters/rtr_brk_mock/adapter.go diff --git a/testing/adapters/rtr_brk_mock/adapter.go b/testing/adapters/rtr_brk_mock/adapter.go new file mode 100644 index 000000000..e600305e8 --- /dev/null +++ b/testing/adapters/rtr_brk_mock/adapter.go @@ -0,0 +1,26 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package rtr_brk_mock offers a router <-> broker mock adapter that can be used to test a router +// implementation. +package rtr_brk_mock + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" +) + +type Adapter struct{} + +// Connect implements the core.Adapter interface +func (a *Adapter) Listen(router core.Router, options interface{}) error { + return nil +} + +// Broadcast implements the core.BrokerRouter interface +func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { +} + +// Forward implements the core.BrokerRouter interface +func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { +} From 950198e7ab765651be1277fb25ade96a39d873a0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 15:45:57 +0100 Subject: [PATCH 0178/2266] [router] Move Device address extraction in lorawan semtech for test purpose and clarity --- adapters/rtr_brk_http/adapter.go | 17 ++++++----------- lorawan/semtech/semtech.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 5cfa6e0e8..ea8972152 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -34,16 +34,11 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) return } - var devAddr semtech.DeviceAddress - var defaultDevAddr semtech.DeviceAddress - // We check them all to be sure, but all RXPK should refer to the same End-Device - for _, rxpk := range payload.RXPK { - addr := rxpk.DevAddr() - if addr == nil || (devAddr != defaultDevAddr && devAddr != *addr) { - router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) - return - } - devAddr = *addr + + devAddr, err := payload.UniformDevAddr() + if err != nil { + router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v, %+v", payload, err))) + return } // Prepare ground to store brokers that are in charge @@ -87,7 +82,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr brokers = append(brokers, addr) } if len(brokers) > 0 { - router.RegisterDevice(devAddr, brokers...) + router.RegisterDevice(*devAddr, brokers...) } }() } diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go index 2e688f536..bf15e2fea 100644 --- a/lorawan/semtech/semtech.go +++ b/lorawan/semtech/semtech.go @@ -8,6 +8,7 @@ package semtech import ( "encoding/base64" + "fmt" "time" ) @@ -123,6 +124,33 @@ type Payload struct { TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any } +// UniformDevAddr tries to extract a device address from the different part of a payload. If the +// payload is composed of messages coming from several end-device, the method will fail. +func (p Payload) UniformDevAddr() (*DeviceAddress, error) { + var devAddr *DeviceAddress + + // Determine the devAddress associated to that payload + if p.RXPK == nil || len(p.RXPK) == 0 { // NOTE are those conditions significantly different ? + if p.TXPK == nil { + return nil, fmt.Errorf("Unable to determine device address. No RXPK neither TXPK messages") + } + if devAddr = p.TXPK.DevAddr(); devAddr == nil { + return nil, fmt.Errorf("Unable to determine device address from TXPK") + } + + } else { + // We check them all to be sure, but all RXPK should refer to the same End-Device + for _, rxpk := range p.RXPK { + addr := rxpk.DevAddr() + if addr == nil || (devAddr != nil && *devAddr != *addr) { + return nil, fmt.Errorf("Payload is composed of messages from several end-devices") + } + devAddr = addr + } + } + return devAddr, nil +} + // Available packet commands const ( PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data From 37aa82b71426b977c6731b0f9091fb94cdfb4012 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 15:58:38 +0100 Subject: [PATCH 0179/2266] [router] Remove NOTE. Conditions were actually different. --- lorawan/semtech/semtech.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go index bf15e2fea..1070a5931 100644 --- a/lorawan/semtech/semtech.go +++ b/lorawan/semtech/semtech.go @@ -130,7 +130,7 @@ func (p Payload) UniformDevAddr() (*DeviceAddress, error) { var devAddr *DeviceAddress // Determine the devAddress associated to that payload - if p.RXPK == nil || len(p.RXPK) == 0 { // NOTE are those conditions significantly different ? + if p.RXPK == nil || len(p.RXPK) == 0 { if p.TXPK == nil { return nil, fmt.Errorf("Unable to determine device address. No RXPK neither TXPK messages") } From cd10e7771506d1665534af9376440e0fe7ce48f3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 16:13:44 +0100 Subject: [PATCH 0180/2266] [router] Fix and terminate mock adapter implementations --- testing/adapters/gtw_rtr_mock/adapter.go | 37 +++++++------ testing/adapters/rtr_brk_mock/adapter.go | 66 +++++++++++++++++++++++- 2 files changed, 83 insertions(+), 20 deletions(-) diff --git a/testing/adapters/gtw_rtr_mock/adapter.go b/testing/adapters/gtw_rtr_mock/adapter.go index 07ab7c854..23fbb36e5 100644 --- a/testing/adapters/gtw_rtr_mock/adapter.go +++ b/testing/adapters/gtw_rtr_mock/adapter.go @@ -12,25 +12,25 @@ import ( ) type Adapter struct { - FailAck bool - FailConnect bool - connected bool - acks map[core.GatewayAddress][]semtech.Packet + FailAck bool // If true, each call to Ack will fail with a core.ErrAck + FailListen bool // If true, each call to Listen will return an error + connected bool // Indicate wether or not the Listen method has been called + Acks map[core.GatewayAddress][]semtech.Packet // Stores all packet send through Ack() } -// New constructs a new Gateway-Router-Moke adapter -func New() (*Adapter, error) { - return &Adapter{ - FailAck: false, - FailConnect: false, - connected: false, - acks: make(map[core.GatewayAddress][]semtech.Packet), - }, nil +// New constructs a new Gateway-Router-Mock adapter +func New() Adapter { + return Adapter{ + FailAck: false, + FailListen: false, + connected: false, + Acks: make(map[core.GatewayAddress][]semtech.Packet), + } } // Listen implements the core.Adapter interface func (a *Adapter) Listen(router core.Router, options interface{}) error { - if a.FailConnect { + if a.FailListen { return fmt.Errorf("Unable to establish connection") } a.connected = true @@ -39,14 +39,13 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { // Ack implements the core.GatewayRouterAdapter interface func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) { + if !a.connected { + router.HandleError(core.ErrAck(fmt.Errorf("Try to send ack through non connected adapter"))) + return + } if a.FailAck { router.HandleError(core.ErrAck(fmt.Errorf("Unable to ack the given packet"))) return } - a.acks[gateway] = append(a.acks[gateway], packet) -} - -// GetAcks returns packets that has been pass through the Ack method() -func (a *Adapter) GetAcks(gateway core.GatewayAddress) []semtech.Packet { - return a.acks[gateway] + a.Acks[gateway] = append(a.Acks[gateway], packet) } diff --git a/testing/adapters/rtr_brk_mock/adapter.go b/testing/adapters/rtr_brk_mock/adapter.go index e600305e8..78ed7b959 100644 --- a/testing/adapters/rtr_brk_mock/adapter.go +++ b/testing/adapters/rtr_brk_mock/adapter.go @@ -6,21 +6,85 @@ package rtr_brk_mock import ( + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" + "time" ) -type Adapter struct{} +type Adapter struct { + FailListen bool // If true, any call to Listen will fail with an error + FailBroadcast bool // If true, any call to Broadcast will trigger a core.ErrBroadcast + FailForward bool // If true, any call to Forward will trigger a core.ErrForward + Broadcasts map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through broadcasts + Forwards map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through forwards + connected bool // Indicates whether or not the Listen() method has been called +} + +// New constructs a new router <-> broker adapter interface +func New() Adapter { + return Adapter{ + FailListen: false, + FailBroadcast: false, + FailForward: false, + connected: false, + Broadcasts: make(map[semtech.DeviceAddress][]semtech.Payload), + Forwards: make(map[semtech.DeviceAddress][]semtech.Payload), + } +} // Connect implements the core.Adapter interface func (a *Adapter) Listen(router core.Router, options interface{}) error { + if a.FailListen { + return fmt.Errorf("Unable to establish the connection") + } + a.connected = true return nil } // Broadcast implements the core.BrokerRouter interface func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { + if !a.connected { + router.HandleError(core.ErrBroadcast(fmt.Errorf("Try to broadcast with non connected adapter"))) + return + } + + devAddr, err := payload.UniformDevAddr() + + if a.FailBroadcast || payload.RXPK == nil || err != nil { + router.HandleError(core.ErrBroadcast(fmt.Errorf("Unable to broadcast given payload %+v", payload))) + return + } + + <-time.After(time.Millisecond * 50) + a.Broadcasts[*devAddr] = append(a.Broadcasts[*devAddr], payload) + router.RegisterDevice(*devAddr, a.InChargeOf(payload, broAddrs...)...) +} + +// InChargeOf returns a set of brokers in charge of a payload (result of simulating a broadcast +// operation). +func (a *Adapter) InChargeOf(payload semtech.Payload, broAddrs ...core.BrokerAddress) []core.BrokerAddress { + var brokers []core.BrokerAddress + for i, addr := range broAddrs { + if i%2 == 1 { + brokers = append(brokers, addr) + } + } + return brokers } // Forward implements the core.BrokerRouter interface func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { + if !a.connected { + router.HandleError(core.ErrForward(fmt.Errorf("Try to forward with non connected adapter"))) + return + } + + devAddr, err := payload.UniformDevAddr() + + if a.FailForward || err != nil { + router.HandleError(core.ErrForward(fmt.Errorf("Unable to forward given payload %+v", payload))) + return + } + a.Forwards[*devAddr] = append(a.Forwards[*devAddr], payload) } From aacf379d9d16a342a492123b1243b279b36dbe6e Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 16:25:22 +0100 Subject: [PATCH 0181/2266] [router] Rename main.go -> doc.go in component/router --- components/router/{main.go => doc.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename components/router/{main.go => doc.go} (100%) diff --git a/components/router/main.go b/components/router/doc.go similarity index 100% rename from components/router/main.go rename to components/router/doc.go From 38ad335ad0945b625f19d8ca37df07439373d631 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 16:51:40 +0100 Subject: [PATCH 0182/2266] [router] Add safety creation check in components/router --- components/router/router.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index 1bd5413ed..928397da5 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -38,7 +38,9 @@ func New(portUDP, portHTTP uint) (*Router, error) { } // HandleUplink implements the core.Router interface -func (r *Router) HandleUplink(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, packet semtech.Packet, gateway core.GatewayAddress) { +func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress) { + r.ensure() + switch packet.Identifier { case semtech.PULL_DATA: r.log("receives PULL_DATA, sending ack") @@ -99,17 +101,20 @@ func (r *Router) HandleUplink(upAdapter core.GatewayRouterAdapter, downAdapter c } // HandleDownlink implements the core.Router interface -func (r *Router) HandleDownlink(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, packet semtech.Packet, broker core.BrokerAddress) { +func (r *Router) HandleDownlink(payload semtech.Payload, broker core.BrokerAddress) { // TODO MileStone 4 } // RegisterDevice implements the core.Router interface func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { + r.ensure() r.addressKeeper.store(devAddr, broAddrs) // TODO handle the error } // RegisterDevice implements the core.Router interface func (r *Router) HandleError(err interface{}) { + r.ensure() + switch err.(type) { case core.ErrAck: case core.ErrDownlink: @@ -121,6 +126,14 @@ func (r *Router) HandleError(err interface{}) { } } +// ensure checks whether or not the current Router has been created via New(). It panics if not. +func (r *Router) ensure() bool { + if r == nil || r.addressKeeper == nil { + panic("Call method on non-initialized Router") + } +} + +// log is a shortcut to access the router logger func (r *Router) log(format string, a ...interface{}) { if r.Logger == nil { return From 228eae5963e28286fa23d01786a862666ef3b2ff Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 17:28:38 +0100 Subject: [PATCH 0183/2266] [router] Make router compliant with core interfaces. Works now with several goroutine to communicate with adapters --- components/router/router.go | 105 +++++++++++++++++++++++++++++------- 1 file changed, 85 insertions(+), 20 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index 928397da5..46e03e34a 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -12,27 +12,50 @@ import ( ) const ( - EXPIRY_DELAY = time.Hour * 8 + EXPIRY_DELAY = time.Hour * 8 + UP_POOL_SIZE = 1 + DOWN_POOL_SIZE = 1 ) +// Router represents a concrete router of TTN architecture. Use the New() method to create a new +// one and then connect it to its adapters. type Router struct { - PortUDP uint - PortHTTP uint - Logger log.Logger - addressKeeper addressKeeper + brokers []core.BrokerAddress // Brokers known by the router + Logger log.Logger // Specify a logger for the router. NOTE Having this exported isn't thread-safe. + addressKeeper addressKeeper // Local storage that maps end-device addresses to broker addresses + up chan upMsg // Internal communication channel which sends data to the up adapter + down chan downMsg // Internal communication channel which sends data to the down adapter } -func New(portUDP, portHTTP uint) (*Router, error) { +// upMsg materializes messages that flow along the up channel +type upMsg struct { + packet semtech.Packet // The packet to transfer + gateway core.GatewayAddress // The recipient gateway to reach +} + +// downMsg materializes messages that flow along the down channel +type downMsg struct { + payload semtech.Payload // The payload to transfer + brokers []core.BrokerAddress // The recipient broker to reach. If nil or empty, assume that all broker should be reached +} + +// New constructs a Router and setup its internal structure +func New(brokers ...core.BrokerAddress) (*Router, error) { localDB, err := NewLocalDB(EXPIRY_DELAY) if err != nil { return nil, error } + if len(brokers) == 0 { + return nil, fmt.Errorf("The router should be connected to at least one broker") + } + return &Router{ - PortUDP: portUDP, - PortHTTP: portHTTP, + brokers: brokers, addressKeeper: localDB, + up: make(chan upMsg), + down: make(chan downMsg), Logger: log.VoidLogger{}, }, nil } @@ -44,19 +67,25 @@ func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress switch packet.Identifier { case semtech.PULL_DATA: r.log("receives PULL_DATA, sending ack") - upAdapter.Ack(r, semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_ACK, - Token: packet.Token, - }, gateway) + r.up <- upMsg{ + packet: semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_ACK, + Token: packet.Token, + }, + gateway: gateway, + } case semtech.PUSH_DATA: // 1. Send an ack r.log("receives PUSH_DATA, sending ack") - upAdapter.Ack(r, semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_ACK, - Token: packet.Token, - }, gateway) + r.up <- upMsg{ + packet: semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_ACK, + Token: packet.Token, + }, + gateway: gateway, + } // 2. Determine payloads related to different end-devices present in the packet // NOTE So far, Stats are ignored. @@ -87,12 +116,15 @@ func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress brokers, err := r.addressKeeper.lookup(devAddr) if err != nil { r.log("Forward payload to known brokers %+v", payload) - downAdapter.Forward(router, payload, brokers...) + r.down <- downMsg{ + payload: payload, + brokers: brokers, + } continue } r.log("Broadcast payload to all brokers %+v", payload) - downAdapter.Broadcast(router, payload) + r.down <- downMsg{payload: payload} } default: r.log("Unexpected packet receive from uplink %+v", packet) @@ -126,6 +158,39 @@ func (r *Router) HandleError(err interface{}) { } } +// Connect implements the core.Router interface +func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) error { + r.ensure() + + for i := 0; i < UP_POOL_SIZE; i += 1 { + go r.connectUpAdapter(upAdapter) + } + + for i := 0; i < DOWN_POOL_SIZE; i += 1 { + go r.connectDownAdapter(downAdapter) + } + + return nil +} + +// Consume messages sent to r.up channel +func (r *Router) connectUpAdapter(upAdapter core.GatewayRouterAdapter) { + for msg := range r.up { + upAdapter.Ack(r, msg.packet, msg.gateway) + } +} + +// Consume messages sent to r.down channel +func (r *Router) connectDownAdapter(downAdapter core.RouterBrokerAdapter) { + for msg := range r.down { + if len(msg.brokers) == 0 { + downAdapter.Broadcast(r, msg.payload, r.Brokers...) + continue + } + downAdapter.Forward(r, msg.payload, msg.Brokers...) + } +} + // ensure checks whether or not the current Router has been created via New(). It panics if not. func (r *Router) ensure() bool { if r == nil || r.addressKeeper == nil { From 727af04d79decf918cf623a0fa8ccff55fb83522 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 17:51:30 +0100 Subject: [PATCH 0184/2266] [router] Make local address keeper thread-safe --- components/router/address_keeper.go | 11 +++++++++++ components/router/address_keeper_test.go | 8 +++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/components/router/address_keeper.go b/components/router/address_keeper.go index 1e490b08f..a2578c10f 100644 --- a/components/router/address_keeper.go +++ b/components/router/address_keeper.go @@ -5,7 +5,9 @@ package router import ( "fmt" + "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" + "sync" "time" ) @@ -19,6 +21,7 @@ type reddisAddressKeeper struct{} // In a second time type localDB struct { expiryDelay time.Duration addresses map[semtech.DeviceAddress]localEntry + lock sync.RWMutex } type localEntry struct { @@ -35,18 +38,23 @@ func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { return &localDB{ expiryDelay: expiryDelay, addresses: make(map[semtech.DeviceAddress]localEntry), + lock: sync.RWMutex{}, }, nil } // lookup implements the addressKeeper interface func (a *localDB) lookup(devAddr semtech.DeviceAddress) ([]core.BrokerAddress, error) { + a.lock.RLock() entry, ok := a.addresses[devAddr] + a.lock.RUnlock() if !ok { return nil, fmt.Errorf("Device address not found") } if entry.until.Before(time.Now()) { + a.lock.Lock() delete(a.addresses, devAddr) + a.lock.Unlock() return nil, fmt.Errorf("Broker address(es) expired") } @@ -55,8 +63,10 @@ func (a *localDB) lookup(devAddr semtech.DeviceAddress) ([]core.BrokerAddress, e // store implements the addressKeeper interface func (a *localDB) store(devAddr semtech.DeviceAddress, brosAddr ...core.BrokerAddress) error { + a.lock.Lock() _, ok := a.addresses[devAddr] if ok { + a.lock.Unlock() return fmt.Errorf("An entry already exists for that device") } @@ -65,5 +75,6 @@ func (a *localDB) store(devAddr semtech.DeviceAddress, brosAddr ...core.BrokerAd until: time.Now().Add(a.expiryDelay), } + a.lock.Unlock() return nil } diff --git a/components/router/address_keeper_test.go b/components/router/address_keeper_test.go index de65446e4..c84534ef5 100644 --- a/components/router/address_keeper_test.go +++ b/components/router/address_keeper_test.go @@ -4,6 +4,8 @@ package router import ( + "bytes" + "encoding/binary" "fmt" . "github.com/smartystreets/goconvey/convey" "github.com/thethingsnetwork/core" @@ -13,7 +15,11 @@ import ( ) func genDevAddr() semtech.DeviceAddress { - return semtech.DeviceAddress(fmt.Sprintf("DeviceAddress%d", time.Now())) + devAddr := [4]byte{} + token := new(bytes.Buffer) + binary.Write(token, binary.LittleEndian, time.Now().UnixNano()) + copy(devAddr[:], token.Bytes()[:4]) + return devAddr } func genBroAddr() core.BrokerAddress { From 6a369cdec19432f8789ee121830cd6cfb1122c7b Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 17:51:54 +0100 Subject: [PATCH 0185/2266] [router] Fix oversight in Broadcast signature --- core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core.go b/core.go index 48c81b0d8..3cd9644d4 100644 --- a/core.go +++ b/core.go @@ -54,7 +54,7 @@ type RouterBrokerAdapter interface { // // We assume that broadcast is also registering a device address towards the router depending // on the brokers responses. - Broadcast(router Router, payload Payload) + Broadcast(router Router, payload Payload, broAddrs ...BrokerAddress) // Forward is an explicit forwarding of a packet which is known being handled by a set of // brokers. None of the contacted broker is supposed to reject the incoming payload; They all From d03d95fece5ab85fdfd46fb85dd37195f6a0cdc2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 17:52:12 +0100 Subject: [PATCH 0186/2266] [router] Fix errors and formats in components/router --- components/router/router.go | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/components/router/router.go b/components/router/router.go index 46e03e34a..c2f9f1116 100644 --- a/components/router/router.go +++ b/components/router/router.go @@ -44,7 +44,7 @@ func New(brokers ...core.BrokerAddress) (*Router, error) { localDB, err := NewLocalDB(EXPIRY_DELAY) if err != nil { - return nil, error + return nil, err } if len(brokers) == 0 { @@ -94,7 +94,7 @@ func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress return } - payloads = make(map[semtech.DeviceAddress]semtech.Payload) + payloads := make(map[semtech.DeviceAddress]*semtech.Payload) for _, rxpk := range packet.Payload.RXPK { devAddr := rxpk.DevAddr() if devAddr == nil { @@ -103,28 +103,29 @@ func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress } if _, ok := payloads[*devAddr]; !ok { - payloads[*devAddr] = semtech.Payload{ + payloads[*devAddr] = &semtech.Payload{ RXPK: make([]semtech.RXPK, 0), } } - payloads[*devAddr].RXPK = append(payloads[*devAddr].RXPK, rxpk) + payload := payloads[*devAddr] + (*payload).RXPK = append(payloads[*devAddr].RXPK, rxpk) } // 3. Broadcast or Forward payloads depending wether or not the brokers are known - for payload, devAddr := range payloads { + for devAddr, payload := range payloads { brokers, err := r.addressKeeper.lookup(devAddr) if err != nil { r.log("Forward payload to known brokers %+v", payload) r.down <- downMsg{ - payload: payload, + payload: *payload, brokers: brokers, } continue } r.log("Broadcast payload to all brokers %+v", payload) - r.down <- downMsg{payload: payload} + r.down <- downMsg{payload: *payload} } default: r.log("Unexpected packet receive from uplink %+v", packet) @@ -140,7 +141,7 @@ func (r *Router) HandleDownlink(payload semtech.Payload, broker core.BrokerAddre // RegisterDevice implements the core.Router interface func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { r.ensure() - r.addressKeeper.store(devAddr, broAddrs) // TODO handle the error + r.addressKeeper.store(devAddr, broAddrs...) // TODO handle the error } // RegisterDevice implements the core.Router interface @@ -159,7 +160,7 @@ func (r *Router) HandleError(err interface{}) { } // Connect implements the core.Router interface -func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) error { +func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { r.ensure() for i := 0; i < UP_POOL_SIZE; i += 1 { @@ -169,8 +170,6 @@ func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.R for i := 0; i < DOWN_POOL_SIZE; i += 1 { go r.connectDownAdapter(downAdapter) } - - return nil } // Consume messages sent to r.up channel @@ -184,15 +183,15 @@ func (r *Router) connectUpAdapter(upAdapter core.GatewayRouterAdapter) { func (r *Router) connectDownAdapter(downAdapter core.RouterBrokerAdapter) { for msg := range r.down { if len(msg.brokers) == 0 { - downAdapter.Broadcast(r, msg.payload, r.Brokers...) + downAdapter.Broadcast(r, msg.payload, r.brokers...) continue } - downAdapter.Forward(r, msg.payload, msg.Brokers...) + downAdapter.Forward(r, msg.payload, msg.brokers...) } } // ensure checks whether or not the current Router has been created via New(). It panics if not. -func (r *Router) ensure() bool { +func (r *Router) ensure() { if r == nil || r.addressKeeper == nil { panic("Call method on non-initialized Router") } From ca0c8f7403a639271555cb22bd71de59dd4c7125 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 17:53:29 +0100 Subject: [PATCH 0187/2266] [router] Update DEV_PLAN --- DEVELOPMENT_PLAN.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 60f833d3b..a90257803 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -27,19 +27,19 @@ system will just forward messages using pre-configured end-device addresses. - [x] Acknowledge packet from gateway - [x] Forward packet to brokers - [ ] Create Mock router - - [ ] Create Mock DownAdapter - - [ ] Create Mock UpAdatper + - [x] Create Mock DownAdapter + - [x] Create Mock UpAdatper - [ ] Test'em all - [ ] Reemit errored packet - [ ] Switch from local in-memory storage to Reddis - [ ] UpAdapter - - [ ] Listen and forward incoming packets to Core router - - [ ] Keep track of existing UDP connections - - [ ] Send ack through existing UDP connection + - [x] Listen and forward incoming packets to Core router + - [x] Keep track of existing UDP connections + - [x] Send ack through existing UDP connection - [ ] DownAdapter - - [ ] Listen and forward incoming packet to Core router - - [ ] Broadcast a packet to several brokers - - [ ] Send packet to given brokers (same as above ?) + - [x] Listen and forward incoming packet to Core router + - [x] Broadcast a packet to several brokers + - [x] Send packet to given brokers (same as above ?) - [ ] Basic Broker From 0eb60d81ca39c579015564ce71a89a13fed00ca4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 18:16:45 +0100 Subject: [PATCH 0188/2266] [router] Create mock router for testing purposes --- testing/components/router/router.go | 52 +++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 testing/components/router/router.go diff --git a/testing/components/router/router.go b/testing/components/router/router.go new file mode 100644 index 000000000..7d0b8f1f2 --- /dev/null +++ b/testing/components/router/router.go @@ -0,0 +1,52 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package mock offers a mock router that can be used to test adapter implementations. +package mock + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" +) + +// Router represents the mock Router +type Router struct { + Errors []interface{} + Packets map[core.GatewayAddress][]semtech.Packet + Payloads map[core.BrokerAddress][]semtech.Payload + Devices map[semtech.DeviceAddress][]core.BrokerAddress +} + +// New constructs a mock Router; This method is a shortcut that creates all the internal maps. +func New() Router { + return Router{ + Packets: make(map[core.GatewayAddress][]semtech.Packet), + Payloads: make(map[core.BrokerAddress][]semtech.Payload), + Devices: make(map[semtech.DeviceAddress][]core.BrokerAddress), + } +} + +// HandleUplink implements the core.Router interface +func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress) { + r.Packets[gateway] = append(r.Packets[gateway], packet) +} + +// HandleDownlink implements the core.Router interface +func (r *Router) HandleDownlink(payload semtech.Payload, broker core.BrokerAddress) { + r.Payloads[broker] = append(r.Payloads[broker], payload) +} + +// RegisterDevice implements the core.Router interface +func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { + r.Devices[devAddr] = append(r.Devices[devAddr], broAddrs...) +} + +// RegisterDevice implements the core.Router interface +func (r *Router) HandleError(err interface{}) { + r.Errors = append(r.Errors, err) +} + +// Connect implements the core.Router interface +func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { + // Chill +} From 09b7793c230d57739b757e90e7035d4fbcefc573 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 18:17:24 +0100 Subject: [PATCH 0189/2266] [router] Update DEV_PLAN --- DEVELOPMENT_PLAN.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index a90257803..bdb3fbbf9 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -26,17 +26,17 @@ system will just forward messages using pre-configured end-device addresses. - [x] Invalidate broker periodically (only local) - [x] Acknowledge packet from gateway - [x] Forward packet to brokers - - [ ] Create Mock router + - [x] Create Mock router - [x] Create Mock DownAdapter - [x] Create Mock UpAdatper - [ ] Test'em all - [ ] Reemit errored packet - [ ] Switch from local in-memory storage to Reddis - - [ ] UpAdapter + - [x] UpAdapter - [x] Listen and forward incoming packets to Core router - [x] Keep track of existing UDP connections - [x] Send ack through existing UDP connection - - [ ] DownAdapter + - [x] DownAdapter - [x] Listen and forward incoming packet to Core router - [x] Broadcast a packet to several brokers - [x] Send packet to given brokers (same as above ?) From 88cd2d7eeef1784b82511aad4a20ca2ba3a36427 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 31 Dec 2015 18:42:44 +0100 Subject: [PATCH 0190/2266] Rearrange folders, namings and README --- README.md | 79 ++++++++++++++++--- components/{router => }/address_keeper.go | 2 +- .../{router => }/address_keeper_test.go | 2 +- components/{router => }/doc.go | 4 +- components/{router => }/router.go | 6 +- .../gtw_rtr_mock/adapter.go | 0 .../rtr_brk_mock/adapter.go | 0 .../router => mock_components}/router.go | 4 +- 8 files changed, 77 insertions(+), 20 deletions(-) rename components/{router => }/address_keeper.go (99%) rename components/{router => }/address_keeper_test.go (99%) rename components/{router => }/doc.go (64%) rename components/{router => }/router.go (97%) rename testing/{adapters => mock_adapters}/gtw_rtr_mock/adapter.go (100%) rename testing/{adapters => mock_adapters}/rtr_brk_mock/adapter.go (100%) rename testing/{components/router => mock_components}/router.go (93%) diff --git a/README.md b/README.md index 7f9d89355..193bed0d6 100644 --- a/README.md +++ b/README.md @@ -11,28 +11,85 @@ The Things Network Core Architecture ## Folder structure So far: + ``` +-> Router +-> GatewayRouterAdapter +-> RouterBrokerAdapter +-> BrokerAddress +-> GatewayAddress + -| components ------| broker ------| handler ------| networkcontroller ------| router +-----> Router +-----> Broker +-----> Networkcontroller +-----> Handler -| adapters ------| router-broker-http/brokAdapter ------| router-gateway-udp/gateAdapter ------| broker-ns-local/nsAdapter ------| broker-handler-http/handAdapter ------| broker-router-http/routAdapter ------| ns-broker-local/brokAdapter ------| handler-broker-http/brokAdapter +-----| rtr-brk-http +----------> Adapter + +-----| gtw-rtr-udp +----------> Adapter + +-----| brk-nwc-local +----------> Adapter + +-----| brk-hdl-http +----------> Adapter + +-----| brk-rtr-http +----------> Adapter + +-----| ns-brk-local +----------> Adapter + +-----| hdl-brk-http +----------> Adapter -| lorawan -----| mac -----| semtech +----------> Payload +----------> Packet +----------> DeviceAddress +----------> RXPK +----------> TXPK +----------> Stat -| simulators -----| gateway +----------> Forwarder +----------> Imitator + +-| testing +-----| mock_components +----------> Router +----------> Broker +----------> Networkcontroller +----------> Handler + +-----| mock_adapters +----------| rtr-brk-mock +---------------> Adapter + +----------| gtw-rtr-mock +---------------> Adapter + +----------| brk-nwc-mock +---------------> Adapter + +----------| brk-hdl-mock +---------------> Adapter + +----------| brk-rtr-mock +---------------> Adapter + +----------| ns-brk-mock +---------------> Adapter + +----------| hdl-brk-mock +---------------> Adapter ``` ## Development Plan diff --git a/components/router/address_keeper.go b/components/address_keeper.go similarity index 99% rename from components/router/address_keeper.go rename to components/address_keeper.go index a2578c10f..06a79c44c 100644 --- a/components/router/address_keeper.go +++ b/components/address_keeper.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package router +package components import ( "fmt" diff --git a/components/router/address_keeper_test.go b/components/address_keeper_test.go similarity index 99% rename from components/router/address_keeper_test.go rename to components/address_keeper_test.go index c84534ef5..f1651f160 100644 --- a/components/router/address_keeper_test.go +++ b/components/address_keeper_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package router +package components import ( "bytes" diff --git a/components/router/doc.go b/components/doc.go similarity index 64% rename from components/router/doc.go rename to components/doc.go index 8a9735ab7..4dce199a7 100644 --- a/components/router/doc.go +++ b/components/doc.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// package router materializes a router of the network +// package components holds all core component of The Things Network // // TODO description -package router +package components diff --git a/components/router/router.go b/components/router.go similarity index 97% rename from components/router/router.go rename to components/router.go index c2f9f1116..f322fdc25 100644 --- a/components/router/router.go +++ b/components/router.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package router +package components import ( "fmt" @@ -39,8 +39,8 @@ type downMsg struct { brokers []core.BrokerAddress // The recipient broker to reach. If nil or empty, assume that all broker should be reached } -// New constructs a Router and setup its internal structure -func New(brokers ...core.BrokerAddress) (*Router, error) { +// NewRouter constructs a Router and setup its internal structure +func NewRouter(brokers ...core.BrokerAddress) (*Router, error) { localDB, err := NewLocalDB(EXPIRY_DELAY) if err != nil { diff --git a/testing/adapters/gtw_rtr_mock/adapter.go b/testing/mock_adapters/gtw_rtr_mock/adapter.go similarity index 100% rename from testing/adapters/gtw_rtr_mock/adapter.go rename to testing/mock_adapters/gtw_rtr_mock/adapter.go diff --git a/testing/adapters/rtr_brk_mock/adapter.go b/testing/mock_adapters/rtr_brk_mock/adapter.go similarity index 100% rename from testing/adapters/rtr_brk_mock/adapter.go rename to testing/mock_adapters/rtr_brk_mock/adapter.go diff --git a/testing/components/router/router.go b/testing/mock_components/router.go similarity index 93% rename from testing/components/router/router.go rename to testing/mock_components/router.go index 7d0b8f1f2..490971965 100644 --- a/testing/components/router/router.go +++ b/testing/mock_components/router.go @@ -1,8 +1,8 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// package mock offers a mock router that can be used to test adapter implementations. -package mock +// package mock_components offers a mock router that can be used to test adapter implementations. +package mock_components import ( "github.com/thethingsnetwork/core" From 5dc069abe906e91c7c3791ac41b5375d67faf880 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 11:34:08 +0100 Subject: [PATCH 0191/2266] [router] Create log utils to display testing results. --- utils/testing/testing.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 utils/testing/testing.go diff --git a/utils/testing/testing.go b/utils/testing/testing.go new file mode 100644 index 000000000..54a6ce4e9 --- /dev/null +++ b/utils/testing/testing.go @@ -0,0 +1,27 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package testing offers some handy methods to display check and cross symbols with colors in test +// logs. +package testing + +import ( + "fmt" + "testing" +) + +// Ok displays a green check symbol +func Ok(t *testing.T) { + t.Log("\033[32;1m\u2714 ok\033[0m") +} + +// Ko fails the test and display a red cross symbol +func Ko(t *testing.T) { + t.Error("\033[31;1m\u2718 ko\033[0m") + t.Fail() +} + +// Desc displays the provided description in cyan +func Desc(t *testing.T, format string, a ...interface{}) { + t.Logf("\033[36m%s\033[0m", fmt.Sprintf(format, a...)) +} From 3b27d8ab0a04150427faacf7507e1536c7f7ee6d Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 11:57:43 +0100 Subject: [PATCH 0192/2266] [router] Create a new Logger that goes well with the test environment --- utils/log/log.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/utils/log/log.go b/utils/log/log.go index e921b72c8..734f13ef6 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -7,6 +7,7 @@ package log import ( "fmt" + "testing" ) // Logger is a minimalist interface to represent logger @@ -21,8 +22,20 @@ type DebugLogger struct { // Log implements the Logger interface func (l DebugLogger) Log(format string, a ...interface{}) { - fmt.Printf("[ %v ] ", l.Tag) + fmt.Printf("\033[33m[ %s ]\033[0m ", l.Tag) // Tag printed in yellow fmt.Printf(format, a...) + fmt.Print("\n") +} + +// TestLogger can be used in a test environnement to display log only on failure +type TestLogger struct { + Tag string + T *testing.T +} + +// Log implements the Logger interface +func (l TestLogger) Log(format string, a ...interface{}) { + l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, fmt.Sprintf(format, a...)) // Tag printed in yellow } // VoidLogger can be used to deactivate logs by displaying nothing From f89ea750a2105465a85bf7b2b5867a4f53462dbc Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 12:36:38 +0100 Subject: [PATCH 0193/2266] [router] Start refactor general error handling. We'll refer to errors as variable of the core package --- core.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core.go b/core.go index 3cd9644d4..b82f002e3 100644 --- a/core.go +++ b/core.go @@ -4,6 +4,7 @@ package core import ( + "fmt" . "github.com/thethingsnetwork/core/lorawan/semtech" ) @@ -32,6 +33,8 @@ type Router interface { type ErrUplink error type ErrAck error +var ErrBadOptions error = fmt.Errorf("Unreckonized or invalid options") + type Adapter interface { // Establish the adapter connection, whatever protocol is being used. Listen(router Router, options interface{}) error From 560b88328b186089b9ce5b52debeeb8f72ec2372 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 12:37:13 +0100 Subject: [PATCH 0194/2266] [router] Write test suite for Listen() method of gateway <-> router adapter --- adapters/gtw_rtr_udp/adapter_test.go | 62 ++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 adapters/gtw_rtr_udp/adapter_test.go diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go new file mode 100644 index 000000000..001eb08d2 --- /dev/null +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -0,0 +1,62 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gtw_rtr_udp + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/testing/mock_components" + "github.com/thethingsnetwork/core/utils/log" + . "github.com/thethingsnetwork/core/utils/testing" + "testing" +) + +// ----- func (a *Adapter) Listen(router core.Router, options interface{}) error +func TestListenOptions(t *testing.T) { + tests := []listenOptionsTest{ + {uint(3000), nil}, + {int(14), core.ErrBadOptions}, + {"somethingElse", core.ErrBadOptions}, + } + + for _, test := range tests { + test.run(t) + } +} + +type listenOptionsTest struct { + options interface{} + want error +} + +func (test listenOptionsTest) run(t *testing.T) { + Desc(t, "Run Listen(router, %T %v)", test.options, test.options) + adapter, router := generateAdapterAndRouter(t) + got := adapter.Listen(router, test.options) + test.check(t, got) +} + +func (test listenOptionsTest) check(t *testing.T, got error) { + if got != test.want { + t.Errorf("expected {%v} to be {%v}\n", got, test.want) + KO(t) + return + } + OK(t) +} + +// ----- Build Utilities +func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { + return Adapter{ + Logger: log.TestLogger{ + Tag: "Adapter", + T: t, + }, + }, mock_components.NewRouter() +} + +// ----- Operate Utilities + +// ----- Check Utilities +func checkListenResult(t *testing.T, got error, wanted error, options interface{}) { +} From d7fcb0636a39be7e24fbf1735bc119d22c69307c Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 12:37:34 +0100 Subject: [PATCH 0195/2266] [router] Change mock router constructor signature --- testing/mock_components/router.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/mock_components/router.go b/testing/mock_components/router.go index 490971965..a70de9d76 100644 --- a/testing/mock_components/router.go +++ b/testing/mock_components/router.go @@ -17,9 +17,9 @@ type Router struct { Devices map[semtech.DeviceAddress][]core.BrokerAddress } -// New constructs a mock Router; This method is a shortcut that creates all the internal maps. -func New() Router { - return Router{ +// NewRouter constructs a mock Router; This method is a shortcut that creates all the internal maps. +func NewRouter() *Router { + return &Router{ Packets: make(map[core.GatewayAddress][]semtech.Packet), Payloads: make(map[core.BrokerAddress][]semtech.Payload), Devices: make(map[semtech.DeviceAddress][]core.BrokerAddress), From 1cfe16bc24821299d9f504d8ce807461141af293 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 12:38:12 +0100 Subject: [PATCH 0196/2266] [router] Impact changes caused by error handling refactor + test suite on gateway <-> router adapter --- adapters/gtw_rtr_udp/adapter.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index e37e1567f..52bffd7ba 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -25,18 +25,15 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { case uint: port = options.(uint) default: - return fmt.Errorf("Unreckognized options %+v\n", options) - + a.log("Invalid option provided: %+v", options) + return core.ErrBadOptions } // Create the udp connection and start listening with a goroutine addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { - return err - } - a.conn, err = net.ListenUDP("udp", addr) - if err != nil { - return err + if a.conn, err = net.ListenUDP("udp", addr); err != nil { + a.log("Unable to establish the connection: %v", err) + return core.ErrBadOptions } go a.listen(router) // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. return nil @@ -79,6 +76,7 @@ func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.Ga // listen Handle incoming packets and forward them to the router func (a *Adapter) listen(router core.Router) { + defer a.conn.Close() for { buf := make([]byte, 1024) n, addr, err := a.conn.ReadFromUDP(buf) From c687d3067d0b66d6f5220ab4e107adbd9fbf577c Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 14:30:29 +0100 Subject: [PATCH 0197/2266] [router] Fix typos in adapter's tests --- adapters/gtw_rtr_udp/adapter_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index 001eb08d2..5bb86c577 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -11,10 +11,11 @@ import ( "testing" ) -// ----- func (a *Adapter) Listen(router core.Router, options interface{}) error +// ----- The adapter should be able to create a udp connection given a valid udp port func TestListenOptions(t *testing.T) { tests := []listenOptionsTest{ {uint(3000), nil}, + {uint(3000), core.ErrBadOptions}, // Already used now {int(14), core.ErrBadOptions}, {"somethingElse", core.ErrBadOptions}, } @@ -33,16 +34,16 @@ func (test listenOptionsTest) run(t *testing.T) { Desc(t, "Run Listen(router, %T %v)", test.options, test.options) adapter, router := generateAdapterAndRouter(t) got := adapter.Listen(router, test.options) - test.check(t, got) + test.check(t, got) // Check if errors match } func (test listenOptionsTest) check(t *testing.T, got error) { if got != test.want { t.Errorf("expected {%v} to be {%v}\n", got, test.want) - KO(t) + Ko(t) return } - OK(t) + Ok(t) } // ----- Build Utilities From ea55909d699a0a67a562fcb110f3ba5497c9c695 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 14:30:58 +0100 Subject: [PATCH 0198/2266] [router] Add tests about gateway <-> router adapter listening goroutine --- adapters/gtw_rtr_udp/adapter_test.go | 100 ++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index 5bb86c577..33ef6e459 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -4,11 +4,16 @@ package gtw_rtr_udp import ( + "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/testing/mock_components" "github.com/thethingsnetwork/core/utils/log" . "github.com/thethingsnetwork/core/utils/testing" + "net" + "reflect" "testing" + "time" ) // ----- The adapter should be able to create a udp connection given a valid udp port @@ -46,6 +51,55 @@ func (test listenOptionsTest) check(t *testing.T, got error) { Ok(t) } +// ----- The adapter should catch from the connection and forward valid semtech.Packet to the router +func TestPacketProcessing(t *testing.T) { + tests := []packetProcessingTest{ + {generatePUSH_DATA(), 1, 3001}, + {[]byte{0x14, 0xff}, 0, 3003}, + } + + for _, test := range tests { + test.run(t) + } +} + +type packetProcessingTest struct { + in interface{} // Could be raw []byte or plain semtech.Packet + want uint // 0 or 1 depending whether or not we expect a packet to has been transmitted + port uint // Probably temporary, just because goroutine and connection are still living between tests +} + +func (test packetProcessingTest) run(t *testing.T) { + Desc(t, "Simulate incoming datagram: %+v", test.in) + adapter, router := generateAdapterAndRouter(t) + gateway := listenAndSend(adapter, router, test.port, test.in) + test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router +} + +func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway core.GatewayAddress) { + <-time.After(time.Millisecond * 50) + mockRouter := router.(*mock_components.Router) + + // 1. Check if we expect a packet + packets := mockRouter.Packets[gateway] + if nb := len(packets); uint(nb) != test.want { + t.Errorf("Received %d packets whereas expected %d", nb, test.want) + Ko(t) + return + } + + // 2. If a packet was expected, check that it has been forwarded to the router + if test.want > 0 { + if !reflect.DeepEqual(packets[0], test.in) { + t.Errorf("Expected %+v to match %+v", packets[0], test.in) + Ko(t) + return + } + } + + Ok(t) +} + // ----- Build Utilities func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { return Adapter{ @@ -56,8 +110,50 @@ func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { }, mock_components.NewRouter() } +func generatePUSH_DATA() semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Token: []byte{0x14, 0x42}, + Identifier: semtech.PUSH_DATA, + } +} + // ----- Operate Utilities +func listenAndSend(adapter Adapter, router core.Router, port uint, data interface{}) core.GatewayAddress { + var err error + + // 1. Start the adapter watching procedure + if err = adapter.Listen(router, port); err != nil { + panic(err) + } + + // 2. Create a UDP connection on the same port the adapter is listening + var addr *net.UDPAddr + var conn *net.UDPConn + if addr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)); err != nil { + panic(err) + } + if conn, err = net.DialUDP("udp", nil, addr); err != nil { + panic(err) + } + + // 3. Send the packet or the raw sequence of bytes passed as argument + var raw []byte + switch data.(type) { + case []byte: + raw = data.([]byte) + case semtech.Packet: + if raw, err = semtech.Marshal(data.(semtech.Packet)); err != nil { + panic(err) + } + default: + panic(fmt.Errorf("Unexpected data type to be send : %T", data)) + } + if _, err = conn.Write(raw); err != nil { + panic(err) + } -// ----- Check Utilities -func checkListenResult(t *testing.T, got error, wanted error, options interface{}) { + // 4. Return the connection address which simulates a gateway + return core.GatewayAddress(conn.LocalAddr().String()) } From 268227aef23f8f2e3d893c7546a4d49e5b3a78b0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 14:33:10 +0100 Subject: [PATCH 0199/2266] [router] Move comment in adapter_test --- adapters/gtw_rtr_udp/adapter_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index 33ef6e459..b4d54140a 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -39,10 +39,11 @@ func (test listenOptionsTest) run(t *testing.T) { Desc(t, "Run Listen(router, %T %v)", test.options, test.options) adapter, router := generateAdapterAndRouter(t) got := adapter.Listen(router, test.options) - test.check(t, got) // Check if errors match + test.check(t, got) } func (test listenOptionsTest) check(t *testing.T, got error) { + // 1. Check if errors match if got != test.want { t.Errorf("expected {%v} to be {%v}\n", got, test.want) Ko(t) From 10747b246534449f28f337dc71facda837b8d5eb Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 15:15:06 +0100 Subject: [PATCH 0200/2266] [router] Split listenAndSend in two for farther needs --- adapters/gtw_rtr_udp/adapter_test.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index b4d54140a..be3f89573 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -73,7 +73,8 @@ type packetProcessingTest struct { func (test packetProcessingTest) run(t *testing.T) { Desc(t, "Simulate incoming datagram: %+v", test.in) adapter, router := generateAdapterAndRouter(t) - gateway := listenAndSend(adapter, router, test.port, test.in) + conn, gateway := listen(adapter, router, test.port) + send(conn, test.in) test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router } @@ -121,7 +122,7 @@ func generatePUSH_DATA() semtech.Packet { } // ----- Operate Utilities -func listenAndSend(adapter Adapter, router core.Router, port uint, data interface{}) core.GatewayAddress { +func listen(adapter Adapter, router core.Router, port uint) (*net.UDPConn, core.GatewayAddress) { var err error // 1. Start the adapter watching procedure @@ -139,8 +140,14 @@ func listenAndSend(adapter Adapter, router core.Router, port uint, data interfac panic(err) } - // 3. Send the packet or the raw sequence of bytes passed as argument + // 3. Return the UDP connection and the corresponding simulated gateway address + return conn, core.GatewayAddress(conn.LocalAddr().String()) +} + +func send(conn *net.UDPConn, data interface{}) { + // 1. Send the packet or the raw sequence of bytes passed as argument var raw []byte + var err error switch data.(type) { case []byte: raw = data.([]byte) @@ -154,7 +161,4 @@ func listenAndSend(adapter Adapter, router core.Router, port uint, data interfac if _, err = conn.Write(raw); err != nil { panic(err) } - - // 4. Return the connection address which simulates a gateway - return core.GatewayAddress(conn.LocalAddr().String()) } From 7de9b235f3ffe0035362aca270c8f96a28fde5ea Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 16:05:56 +0100 Subject: [PATCH 0201/2266] [router] Enhance error handling in gateway <-> router adapter --- adapters/gtw_rtr_udp/adapter.go | 86 ++++++++++++++++++---------- adapters/gtw_rtr_udp/adapter_test.go | 2 +- core.go | 6 +- 3 files changed, 60 insertions(+), 34 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index 52bffd7ba..f818349b9 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -13,11 +13,17 @@ import ( type Adapter struct { Logger log.Logger - conn *net.UDPConn + conn chan udpMsg } -// Ack implements the core.Adapter interface. It expects only one param "port" as a -// uint +type udpMsg struct { + addr *net.UDPAddr + raw []byte + conn *net.UDPConn +} + +// Listen implements the core.Adapter interface. It expects only one param "port" as a +// uint. Listen can be called several times to re-establish a lost connection. func (a *Adapter) Listen(router core.Router, options interface{}) error { // Parse options var port uint @@ -30,21 +36,30 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { } // Create the udp connection and start listening with a goroutine + var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if a.conn, err = net.ListenUDP("udp", addr); err != nil { + if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.log("Unable to establish the connection: %v", err) - return core.ErrBadOptions + return core.ErrBadGatewayAddress + } + go a.listen(router, udpConn) // Terminates when the connection is closed + + // Create the connection channel + if a.conn == nil { + a.conn = make(chan udpMsg) + go a.monitorConnection(udpConn) // Terminates that goroutine by closing the channel + } else { + a.conn <- udpMsg{conn: udpConn} } - go a.listen(router) // NOTE: There is no way to stop properly the adapter and thus this goroutine for now. + return nil } // Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) { +func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) error { if a.conn == nil { - a.log("Connection not established. Connect the adaptor first.") - router.HandleError(core.ErrAck(fmt.Errorf("Connection not established. Connect the adaptor first."))) - return + a.log("Trying to Ack on non-established connection") + return core.ErrMissingConnection } a.log("Acks packet %+v", packet) @@ -52,45 +67,38 @@ func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.Ga addr, err := net.ResolveUDPAddr("udp", string(gateway)) if err != nil { - a.log("Unable to retrieve gateway address") - router.HandleError(core.ErrAck(err)) - return + a.log("Unable to retrieve gateway address: %+v", err) + return core.ErrBadGatewayAddress } raw, err := semtech.Marshal(packet) if err != nil { - a.log("Unable to marshal given packet") - router.HandleError(core.ErrAck(err)) - return + a.log("Unable to marshal given packet: %+v", err) + return core.ErrInvalidPacket } - _, err = a.conn.WriteToUDP(raw, addr) - - if err != nil { - a.log("Unable to send udp message") - router.HandleError(core.ErrAck(err)) - return - } + a.conn <- udpMsg{raw: raw, addr: addr} + return nil } // listen Handle incoming packets and forward them to the router -func (a *Adapter) listen(router core.Router) { - defer a.conn.Close() +func (a *Adapter) listen(router core.Router, conn *net.UDPConn) { + defer conn.Close() for { buf := make([]byte, 1024) - n, addr, err := a.conn.ReadFromUDP(buf) - if err != nil { + n, addr, err := conn.ReadFromUDP(buf) + if err != nil { // Problem with the connection a.log("Error: %v", err) - go router.HandleError(core.ErrUplink(err)) - continue + go router.HandleError(core.ErrMissingConnection) + return } a.log("Incoming datagram %x", buf[:n]) pkt, err := semtech.Unmarshal(buf[:n]) if err != nil { a.log("Error: %v", err) - go router.HandleError(core.ErrUplink(err)) + go router.HandleError(core.ErrInvalidPacket) continue } @@ -99,6 +107,24 @@ func (a *Adapter) listen(router core.Router) { } } +// monitorConnection manages udpConnection of the adapter and send message through that connection +func (a *Adapter) monitorConnection(initConn *net.UDPConn) { + udpConn := initConn + for msg := range a.conn { + if msg.conn != nil { // Change the connection + udpConn.Close() + udpConn = msg.conn + } + + if msg.raw != nil { // Send the given udp message + if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { + a.log("Unable to send udp message: %+v", err) + } + } + } + udpConn.Close() // Make sure we close the connection before leaving +} + // log is nothing more than a shortcut / helper to access the logger func (a Adapter) log(format string, i ...interface{}) { if a.Logger == nil { diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index be3f89573..2bc5f9dd7 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -20,7 +20,7 @@ import ( func TestListenOptions(t *testing.T) { tests := []listenOptionsTest{ {uint(3000), nil}, - {uint(3000), core.ErrBadOptions}, // Already used now + {uint(3000), core.ErrBadGatewayAddress}, // Already used now {int(14), core.ErrBadOptions}, {"somethingElse", core.ErrBadOptions}, } diff --git a/core.go b/core.go index b82f002e3..f365ede16 100644 --- a/core.go +++ b/core.go @@ -30,10 +30,10 @@ type Router interface { // The error types belows are going to be more complex in order to handle custom behavior for // each error type. -type ErrUplink error -type ErrAck error - var ErrBadOptions error = fmt.Errorf("Unreckonized or invalid options") +var ErrBadGatewayAddress error = fmt.Errorf("Invalid gateway address") +var ErrMissingConnection error = fmt.Errorf("Can't proceed without establishing connection") +var ErrInvalidPacket error = fmt.Errorf("Invalid semtech packet") type Adapter interface { // Establish the adapter connection, whatever protocol is being used. From a2d5e61741cdc7da8c87289ac3102177a7647938 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 16:10:36 +0100 Subject: [PATCH 0202/2266] [router] Add some comments in adapter --- adapters/gtw_rtr_udp/adapter.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index f818349b9..185dd811f 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -12,14 +12,14 @@ import ( ) type Adapter struct { - Logger log.Logger - conn chan udpMsg + Logger log.Logger // A custom logger used to report errors + conn chan udpMsg // An internal communication channel use to send udp datagram through a valid connection } type udpMsg struct { - addr *net.UDPAddr - raw []byte - conn *net.UDPConn + addr *net.UDPAddr // The target gateway address targetted + raw []byte // The raw byte sequence that has to be sent + conn *net.UDPConn // Provide if you intent to change the current adapter connection } // Listen implements the core.Adapter interface. It expects only one param "port" as a From 38fc57dbd917c445f820df77bb337a4e6b961efc Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 17:15:01 +0100 Subject: [PATCH 0203/2266] [router] Add some logline in gateway <-> router adapter --- adapters/gtw_rtr_udp/adapter.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index 185dd811f..2518f3c0f 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -85,6 +85,7 @@ func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.Ga // listen Handle incoming packets and forward them to the router func (a *Adapter) listen(router core.Router, conn *net.UDPConn) { defer conn.Close() + a.log("Start listening on %s", conn.LocalAddr()) for { buf := make([]byte, 1024) n, addr, err := conn.ReadFromUDP(buf) @@ -112,6 +113,7 @@ func (a *Adapter) monitorConnection(initConn *net.UDPConn) { udpConn := initConn for msg := range a.conn { if msg.conn != nil { // Change the connection + a.log("Switch UDP connection") udpConn.Close() udpConn = msg.conn } From 078f4e3f05d129f7548d55adc12253a46a1cd20e Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 17:15:35 +0100 Subject: [PATCH 0204/2266] [router] Write test for Ack method --- adapters/gtw_rtr_udp/adapter_test.go | 105 +++++++++++++++++++++++++-- 1 file changed, 100 insertions(+), 5 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index 2bc5f9dd7..1818c92da 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -56,7 +56,7 @@ func (test listenOptionsTest) check(t *testing.T, got error) { func TestPacketProcessing(t *testing.T) { tests := []packetProcessingTest{ {generatePUSH_DATA(), 1, 3001}, - {[]byte{0x14, 0xff}, 0, 3003}, + {[]byte{0x14, 0xff}, 0, 3002}, } for _, test := range tests { @@ -73,8 +73,8 @@ type packetProcessingTest struct { func (test packetProcessingTest) run(t *testing.T) { Desc(t, "Simulate incoming datagram: %+v", test.in) adapter, router := generateAdapterAndRouter(t) - conn, gateway := listen(adapter, router, test.port) - send(conn, test.in) + conn, gateway := createConnection(&adapter, router, test.port) + sendDatagram(conn, test.in) test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router } @@ -102,6 +102,67 @@ func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway Ok(t) } +// ----- The adapter should send packet via back to an existing address through an opened connection +func TestSendAck(t *testing.T) { + // 1. Initialize test data + adapter, router := generateAdapterAndRouter(t) + adapter2, router2 := generateAdapterAndRouter(t) + conn, gateway := createConnection(&adapter, router, 3003) + defer conn.Close() + + tests := []sendAckTest{ + {adapter, router, conn, gateway, generatePUSH_ACK(), nil}, + {adapter, router, conn, core.GatewayAddress("patate"), generatePUSH_ACK(), core.ErrBadGatewayAddress}, + {adapter, router, conn, gateway, semtech.Packet{}, core.ErrInvalidPacket}, + {adapter2, router2, nil, gateway, generatePUSH_ACK(), core.ErrMissingConnection}, + } + + // 2. Run tests + for _, test := range tests { + test.run(t) + } +} + +type sendAckTest struct { + adapter Adapter + router core.Router + conn *net.UDPConn + gateway core.GatewayAddress + packet semtech.Packet + want error +} + +func (test sendAckTest) run(t *testing.T) { + Desc(t, "Send ack packet %v to %v via %v", test.packet, test.conn, test.gateway) + // Starts a goroutine that will redirect udp message to a dedicated channel + cmsg := listenFromConnection(test.conn) + defer close(cmsg) + got := test.adapter.Ack(test.router, test.packet, test.gateway) + test.check(t, cmsg, got) // Check the error or the packet if no error +} + +func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) { + // 1. Check if an error was expected + if test.want != nil { + if got != test.want { + t.Errorf("Expected %+v error but got %+v", test.want, got) + Ko(t) + return + } + Ok(t) + return + } + + // 2. Ensure the ack packet has been sent correctly + packet := <-cmsg + if !reflect.DeepEqual(test.packet, packet) { + t.Errorf("Expected %+v to equal %+v", test.packet, packet) + Ko(t) + return + } + Ok(t) +} + // ----- Build Utilities func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { return Adapter{ @@ -121,8 +182,16 @@ func generatePUSH_DATA() semtech.Packet { } } +func generatePUSH_ACK() semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: []byte{0x14, 0x42}, + Identifier: semtech.PUSH_ACK, + } +} + // ----- Operate Utilities -func listen(adapter Adapter, router core.Router, port uint) (*net.UDPConn, core.GatewayAddress) { +func createConnection(adapter *Adapter, router core.Router, port uint) (*net.UDPConn, core.GatewayAddress) { var err error // 1. Start the adapter watching procedure @@ -144,7 +213,7 @@ func listen(adapter Adapter, router core.Router, port uint) (*net.UDPConn, core. return conn, core.GatewayAddress(conn.LocalAddr().String()) } -func send(conn *net.UDPConn, data interface{}) { +func sendDatagram(conn *net.UDPConn, data interface{}) { // 1. Send the packet or the raw sequence of bytes passed as argument var raw []byte var err error @@ -162,3 +231,29 @@ func send(conn *net.UDPConn, data interface{}) { panic(err) } } + +func listenFromConnection(conn *net.UDPConn) (cmsg chan semtech.Packet) { + cmsg = make(chan semtech.Packet) + + // We won't listen on a nil connection + if conn == nil { + return + } + + // Otherwise, wait for a packet + go func() { + for { + buf := make([]byte, 128) + n, err := conn.Read(buf) + if err != nil { + return + } + packet, err := semtech.Unmarshal(buf[:n]) + if err == nil { + cmsg <- *packet + } + } + }() + + return +} From a9071766cc76ec703f02e8a83a7fc1857bee4669 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 17:17:26 +0100 Subject: [PATCH 0205/2266] [router] Close unclosed connection, little oversight --- adapters/gtw_rtr_udp/adapter_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index 1818c92da..d852a96ec 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -74,6 +74,7 @@ func (test packetProcessingTest) run(t *testing.T) { Desc(t, "Simulate incoming datagram: %+v", test.in) adapter, router := generateAdapterAndRouter(t) conn, gateway := createConnection(&adapter, router, test.port) + defer conn.Close() sendDatagram(conn, test.in) test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router } From 76375e1e9dd24c5007d4a95d05ed94831ad3e4d3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 17:27:43 +0100 Subject: [PATCH 0206/2266] [router] Test connection re-establishment for gateway <-> router adapter --- adapters/gtw_rtr_udp/adapter_test.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index d852a96ec..dcf1fd6c5 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -164,6 +164,31 @@ func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) Ok(t) } +// ----- In case of issue, the connection can be re-established +func TestConnectionRecovering(t *testing.T) { + adapter, router := generateAdapterAndRouter(t) + if err := adapter.Listen(router, uint(3004)); err != nil { + panic(err) + } + err := adapter.Listen(router, uint(3005)) + + if err != nil { + t.Errorf("No error was expected but got: %+v", err) + Ko(t) + return + } + + // Now try to send a packet on a switched connection + err = adapter.Ack(router, generatePUSH_ACK(), core.GatewayAddress("0.0.0.0:3005")) + if err != nil { + t.Errorf("No error was expected but got: %+v", err) + Ko(t) + return + } + + Ok(t) +} + // ----- Build Utilities func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { return Adapter{ From 5e38eb515c7ddaeaa54bf409190a4b1a0988ad64 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 17:33:11 +0100 Subject: [PATCH 0207/2266] [router] Change core signatures -> prefer early and direct error handling --- core.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core.go b/core.go index f365ede16..934e40cae 100644 --- a/core.go +++ b/core.go @@ -44,7 +44,7 @@ type GatewayRouterAdapter interface { Adapter // Ack allows the router to send back a response to a gateway. The name of the method is quite a // bad call and will probably change soon. - Ack(router Router, packet Packet, gateway GatewayAddress) + Ack(router Router, packet Packet, gateway GatewayAddress) error } type ErrDownlink error @@ -57,10 +57,10 @@ type RouterBrokerAdapter interface { // // We assume that broadcast is also registering a device address towards the router depending // on the brokers responses. - Broadcast(router Router, payload Payload, broAddrs ...BrokerAddress) + Broadcast(router Router, payload Payload, broAddrs ...BrokerAddress) error // Forward is an explicit forwarding of a packet which is known being handled by a set of // brokers. None of the contacted broker is supposed to reject the incoming payload; They all // ave been queried before and are known as dedicated brokers for the related end-device. - Forward(router Router, payload Payload, broAddrs ...BrokerAddress) + Forward(router Router, payload Payload, broAddrs ...BrokerAddress) error } From 02e5580d74118d5fd627ca70a22149c0b4ec2591 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 18:36:29 +0100 Subject: [PATCH 0208/2266] [router] Regroup call to t.Errorf in Ko() util method --- adapters/gtw_rtr_udp/adapter_test.go | 21 +++++++-------------- utils/testing/testing.go | 4 ++-- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index dcf1fd6c5..9c89dacf5 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -45,8 +45,7 @@ func (test listenOptionsTest) run(t *testing.T) { func (test listenOptionsTest) check(t *testing.T, got error) { // 1. Check if errors match if got != test.want { - t.Errorf("expected {%v} to be {%v}\n", got, test.want) - Ko(t) + Ko(t, "expected {%v} to be {%v}", got, test.want) return } Ok(t) @@ -86,16 +85,14 @@ func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway // 1. Check if we expect a packet packets := mockRouter.Packets[gateway] if nb := len(packets); uint(nb) != test.want { - t.Errorf("Received %d packets whereas expected %d", nb, test.want) - Ko(t) + Ko(t, "Received %d packets whereas expected %d", nb, test.want) return } // 2. If a packet was expected, check that it has been forwarded to the router if test.want > 0 { if !reflect.DeepEqual(packets[0], test.in) { - t.Errorf("Expected %+v to match %+v", packets[0], test.in) - Ko(t) + Ko(t, "Expected %+v to match %+v", packets[0], test.in) return } } @@ -146,8 +143,7 @@ func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) // 1. Check if an error was expected if test.want != nil { if got != test.want { - t.Errorf("Expected %+v error but got %+v", test.want, got) - Ko(t) + Ko(t, "Expected %+v error but got %+v", test.want, got) return } Ok(t) @@ -157,8 +153,7 @@ func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) // 2. Ensure the ack packet has been sent correctly packet := <-cmsg if !reflect.DeepEqual(test.packet, packet) { - t.Errorf("Expected %+v to equal %+v", test.packet, packet) - Ko(t) + Ko(t, "Expected %+v to equal %+v", test.packet, packet) return } Ok(t) @@ -173,16 +168,14 @@ func TestConnectionRecovering(t *testing.T) { err := adapter.Listen(router, uint(3005)) if err != nil { - t.Errorf("No error was expected but got: %+v", err) - Ko(t) + Ko(t, "No error was expected but got: %+v", err) return } // Now try to send a packet on a switched connection err = adapter.Ack(router, generatePUSH_ACK(), core.GatewayAddress("0.0.0.0:3005")) if err != nil { - t.Errorf("No error was expected but got: %+v", err) - Ko(t) + Ko(t, "No error was expected but got: %+v", err) return } diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 54a6ce4e9..8410ec890 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -16,8 +16,8 @@ func Ok(t *testing.T) { } // Ko fails the test and display a red cross symbol -func Ko(t *testing.T) { - t.Error("\033[31;1m\u2718 ko\033[0m") +func Ko(t *testing.T, format string, a ...interface{}) { + t.Errorf("\033[31;1m\u2718 ko | \033[0m\033[31m%s\033[0m", fmt.Sprintf(format, a...)) t.Fail() } From 5cea7bef278b17ca2005e20832d95affba9128cd Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 18:46:58 +0100 Subject: [PATCH 0209/2266] [router] Remove useless logline --- adapters/rtr_brk_http/adapter.go | 1 - 1 file changed, 1 deletion(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index ea8972152..7368cd5e6 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -23,7 +23,6 @@ type Adapter struct { // Listen implements the core.Adapter interface func (a *Adapter) Listen(router core.Router, options interface{}) error { - a.log("Connects to router %+v", router) return nil } From a8b0fd4706ce75411c0ea6dd986697f2e43288fb Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 18:47:33 +0100 Subject: [PATCH 0210/2266] [router] Prepare ground for router <-> broker adapter + write test for Listen & creation --- adapters/rtr_brk_http/adapter_test.go | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 adapters/rtr_brk_http/adapter_test.go diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go new file mode 100644 index 000000000..fadb6d781 --- /dev/null +++ b/adapters/rtr_brk_http/adapter_test.go @@ -0,0 +1,39 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package rtr_brk_http + +import ( + // "fmt" + "github.com/thethingsnetwork/core" + // "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/testing/mock_components" + "github.com/thethingsnetwork/core/utils/log" + . "github.com/thethingsnetwork/core/utils/testing" + // "net/http" + // "reflect" + "testing" + // "time" +) + +// ----- The adapter can be created and listen straigthforwardly +func TestListenOptionsTest(t *testing.T) { + adapter, router := generateAdapterAndRouter(t) + + Desc(t, "Listen to adapter") + if err := adapter.Listen(router, nil); err != nil { + Ko(t, "No error was expected but got: %+v", err) + return + } + Ok(t) +} + +// ----- Build Utilities +func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { + return Adapter{ + Logger: log.TestLogger{ + Tag: "Adapter", + T: t, + }, + }, mock_components.NewRouter() +} From ab114cd026dabb342e616fee035c38813d731051 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:09:14 +0100 Subject: [PATCH 0211/2266] [router] Update core errors to be consistent with previous work --- core.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core.go b/core.go index 934e40cae..396df1d3d 100644 --- a/core.go +++ b/core.go @@ -34,6 +34,9 @@ var ErrBadOptions error = fmt.Errorf("Unreckonized or invalid options") var ErrBadGatewayAddress error = fmt.Errorf("Invalid gateway address") var ErrMissingConnection error = fmt.Errorf("Can't proceed without establishing connection") var ErrInvalidPacket error = fmt.Errorf("Invalid semtech packet") +var ErrInvalidPayload error = fmt.Errorf("Invalid semtech payload") +var ErrBroadcast error = fmt.Errorf("Unable to broadcast the given payload") +var ErrForward error = fmt.Errorf("Unable to forward the given payload") type Adapter interface { // Establish the adapter connection, whatever protocol is being used. @@ -47,9 +50,6 @@ type GatewayRouterAdapter interface { Ack(router Router, packet Packet, gateway GatewayAddress) error } -type ErrDownlink error -type ErrForward error -type ErrBroadcast error type RouterBrokerAdapter interface { Adapter From 5e1f5d7caac4c70442d7874e5c1aaa20221b805c Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:10:38 +0100 Subject: [PATCH 0212/2266] [router] Update router <-> broker adapter to match new error report system --- adapters/rtr_brk_http/adapter.go | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 7368cd5e6..66359f276 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -27,17 +27,17 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { } // Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { +func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { // Determine the devAddress associated to that payload if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? - router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v", payload))) - return + a.log("Cannot broadcast given payload: %+v", payload) + return core.ErrInvalidPayload } devAddr, err := payload.UniformDevAddr() if err != nil { - router.HandleError(core.ErrBroadcast(fmt.Errorf("Cannot broadcast given payload: %+v, %+v", payload, err))) - return + a.log("Cannot broadcast given payload: %+v", payload) + return core.ErrInvalidPayload } // Prepare ground to store brokers that are in charge @@ -54,7 +54,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr if err != nil { a.log("Unable to send POST request %+v", err) - router.HandleError(core.ErrBroadcast(err)) + router.HandleError(core.ErrBroadcast) // NOTE Mote information should be sent return } @@ -68,7 +68,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr a.log("Broker %+v does not handle packets coming from %+v", addr, devAddr) default: a.log("Unexpected answer from the broker %+v", err) - router.HandleError(core.ErrBroadcast(err)) + router.HandleError(core.ErrBroadcast) // NOTE More information should be sent } }(addr) } @@ -84,18 +84,25 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr router.RegisterDevice(*devAddr, brokers...) } }() + + return nil } // Forward implements the core.BrokerRouter interface -func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { +func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { + if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? + a.log("Cannot broadcast given payload: %+v", payload) + return core.ErrInvalidPayload + } client := http.Client{} for _, addr := range broAddrs { go func(url string) { + a.log("Send payload to %s", url) resp, err := post(client, url, payload) if err != nil { a.log("Unable to send POST request %+v", err) - router.HandleError(core.ErrForward(err)) + router.HandleError(core.ErrForward) // NOTE More information should be sent return } @@ -103,7 +110,7 @@ func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs if resp.StatusCode != http.StatusOK { a.log("Unexpected answer from the broker %+v", err) - router.HandleError(core.ErrForward(err)) + router.HandleError(core.ErrForward) // NOTE More information should be sent return } @@ -113,6 +120,8 @@ func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs }(string(addr)) } + + return nil } // post regroups some logic used in both Forward and Broadcast methods From eddcb1181fb4366189d62c482b42657919d27cb0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:11:13 +0100 Subject: [PATCH 0213/2266] [router] Update DumpPStruct in util to avoid unexported attributes to cause panic :| --- utils/pointer/pointer.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 79a4d1563..14fb7e977 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -7,6 +7,7 @@ package pointer import ( "fmt" "reflect" + "strings" "time" ) @@ -62,6 +63,10 @@ func DumpPStruct(s interface{}) { } for k := 0; k < v.NumField(); k += 1 { + name := v.Type().Field(k).Name + if name[0] == strings.ToLower(name)[0] { // Unexported field + continue + } i := v.Field(k).Interface() fmt.Printf("%v: ", v.Type().Field(k).Name) From 91264dba55d4ab3f66232b89b51d08b3956b1872 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:11:55 +0100 Subject: [PATCH 0214/2266] [router] Write forward tests for router <-> broker adapter --- adapters/rtr_brk_http/adapter_test.go | 155 +++++++++++++++++++++++++- 1 file changed, 150 insertions(+), 5 deletions(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index fadb6d781..89f8b5cb6 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -4,16 +4,18 @@ package rtr_brk_http import ( - // "fmt" + "encoding/json" "github.com/thethingsnetwork/core" - // "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/testing/mock_components" "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" - // "net/http" - // "reflect" + "io" + "net/http" + "reflect" "testing" - // "time" + "time" ) // ----- The adapter can be created and listen straigthforwardly @@ -28,6 +30,73 @@ func TestListenOptionsTest(t *testing.T) { Ok(t) } +// ----- The adapter should forward a payload to a set of brokers +func TestForwardPayload(t *testing.T) { + tests := []forwardPayloadTest{ + {generateValidPayload(), []string{"0.0.0.0:3000", "0.0.0.0:3001"}, nil}, + {generateInvalidPayload(), []string{"0.0.0.0:3002"}, core.ErrInvalidPayload}, + } + + for _, test := range tests { + test.run(t) + } +} + +type forwardPayloadTest struct { + payload semtech.Payload + brokers []string + want error +} + +func (test forwardPayloadTest) run(t *testing.T) { + Desc(t, "Forward %v to %v", test.payload, test.brokers) + adapter, router := generateAdapterAndRouter(t) + cmsg := listenHTTP(t, test.brokers) + <-time.After(time.Millisecond * 250) + got := adapter.Forward(router, test.payload, toBrokerAddrs(test.brokers)...) + test.check(t, cmsg, got) +} + +func (test forwardPayloadTest) check(t *testing.T, cmsg chan semtech.Payload, got error) { + <-time.After(time.Millisecond * 500) + + // Check for the error + if test.want != nil { + if test.want != got { + Ko(t, "Expected error %v but got %v", test.want, got) + return + } + Ok(t) + return + } + + // Check if payload should have been sent + if len(test.brokers) == 0 { + Ok(t) + return + } + + // Gather payloads and check one of them + var payloads []semtech.Payload + select { + case payload := <-cmsg: + payloads = append(payloads, payload) + if len(payloads) == len(test.brokers) { + break + } + case <-time.After(time.Millisecond * 500): + Ko(t, "%d payload(s) send to server(s) whereas %d was/were expected", len(payloads), len(test.brokers)) + return + } + + if !reflect.DeepEqual(test.payload, payloads[0]) { + Ko(t, "Expected %+v to be sent but server received: %+v", test.payload, payloads[0]) + return + } + + Ok(t) +} + // ----- Build Utilities func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { return Adapter{ @@ -37,3 +106,79 @@ func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { }, }, mock_components.NewRouter() } + +func generateValidPayload() semtech.Payload { + return semtech.Payload{ + RXPK: []semtech.RXPK{{ + Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), + Freq: pointer.Float64(866.349812), + Rssi: pointer.Int(-35), + }, + }, + } +} + +func generateInvalidPayload() semtech.Payload { + return semtech.Payload{} +} + +func toBrokerAddrs(addrs []string) []core.BrokerAddress { + brokers := make([]core.BrokerAddress, 0) + for _, addr := range addrs { + brokers = append(brokers, core.BrokerAddress(addr)) + } + return brokers +} + +// ----- Operate Utilities +func listenHTTP(t *testing.T, addrs []string) chan semtech.Payload { + cmsg := make(chan semtech.Payload) + + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { + defer req.Body.Close() + res.Header().Set("Content-Type", "application/json") + + // Check the header type + if req.Header.Get("Content-Type") != "application/json" { + t.Log("Unexpected content-type ignore") + res.WriteHeader(http.StatusBadRequest) + res.Write(nil) + return + } + + // Check the body as well + var payload semtech.Payload + raw := make([]byte, 512) + n, err := req.Body.Read(raw) + if err != nil && err != io.EOF { + t.Logf("Error reading request body: %v", err) + res.WriteHeader(http.StatusBadRequest) + res.Write(nil) + return + } + + if err := json.Unmarshal(raw[:n], &payload); err != nil { + t.Logf("Error while unmarshaling: %v", err) + res.WriteHeader(http.StatusBadRequest) + res.Write(nil) + return + } + + // Send a fake response + res.WriteHeader(http.StatusOK) + res.Write(nil) + cmsg <- payload + }) + + for _, addr := range addrs { + go func(addr string) { + s := &http.Server{Addr: addr, Handler: serveMux} + if err := s.ListenAndServe(); err != nil { + panic(err) + } + }(addr) + } + + return cmsg +} From 55b47c28c2a51668ab51549fe3e89d57ab50d42f Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:12:09 +0100 Subject: [PATCH 0215/2266] [router] Update adapter to pass tests --- adapters/rtr_brk_http/adapter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 66359f276..43730cf06 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -118,7 +118,7 @@ func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs // from the broker to handle packets or anything else ? Is it efficient ? Should // downlinks packets be sent back with the HTTP body response ? Its a 2 seconds frame... - }(string(addr)) + }(fmt.Sprintf("http://%s", string(addr))) } return nil From 605718c3d956bf355b2ccde6358c7a1cde97df2e Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:12:34 +0100 Subject: [PATCH 0216/2266] [router] Fix error in semtech decode which created empty structure when not needed --- lorawan/semtech/decode.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/lorawan/semtech/decode.go b/lorawan/semtech/decode.go index b13c75728..ced8566a5 100644 --- a/lorawan/semtech/decode.go +++ b/lorawan/semtech/decode.go @@ -86,12 +86,8 @@ func (d *datrparser) UnmarshalJSON(raw []byte) error { // UnmarshalJSON implements the Unmarshaler interface from encoding/json func (p *Payload) UnmarshalJSON(raw []byte) error { proxy := payloadProxy{ - ProxStat: &statProxy{ - Stat: new(Stat), - }, - ProxTXPK: &txpkProxy{ - TXPK: new(TXPK), - }, + ProxStat: &statProxy{}, + ProxTXPK: &txpkProxy{}, } if err := json.Unmarshal(raw, &proxy); err != nil { From 1533fb41a6278cf5d7ae04abdd110f92a722df2e Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 1 Jan 2016 21:38:17 +0100 Subject: [PATCH 0217/2266] [router] Remove old references to errors in router --- components/router.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/components/router.go b/components/router.go index f322fdc25..8dba92799 100644 --- a/components/router.go +++ b/components/router.go @@ -149,11 +149,6 @@ func (r *Router) HandleError(err interface{}) { r.ensure() switch err.(type) { - case core.ErrAck: - case core.ErrDownlink: - case core.ErrForward: - case core.ErrBroadcast: - case core.ErrUplink: default: fmt.Println(err) // Wow, much handling, very reliable } From fbd9f6985408a36bb1d185cf535b3bd4d67382fd Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 16:38:11 +0100 Subject: [PATCH 0218/2266] [router] Make channel creation in gateway <-> router adapter more reliable --- adapters/gtw_rtr_udp/adapter.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index 2518f3c0f..e3d10cde6 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -42,16 +42,19 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { a.log("Unable to establish the connection: %v", err) return core.ErrBadGatewayAddress } - go a.listen(router, udpConn) // Terminates when the connection is closed - // Create the connection channel + // The following statements aren't thread-safe. It assumes that only one goroutine will attempt + // to Listen() in a first place, then it does not matter because all access will be read-only + // access and we won't have any data race here. However, for the very first call, this has to be + // done in a non-concurrent context. if a.conn == nil { a.conn = make(chan udpMsg) - go a.monitorConnection(udpConn) // Terminates that goroutine by closing the channel - } else { - a.conn <- udpMsg{conn: udpConn} + go a.monitorConnection() // Terminates that goroutine by closing the channel } + a.conn <- udpMsg{conn: udpConn} + go a.listen(router, udpConn) // Terminates when the connection is closed + return nil } @@ -109,22 +112,26 @@ func (a *Adapter) listen(router core.Router, conn *net.UDPConn) { } // monitorConnection manages udpConnection of the adapter and send message through that connection -func (a *Adapter) monitorConnection(initConn *net.UDPConn) { - udpConn := initConn +func (a *Adapter) monitorConnection() { + var udpConn *net.UDPConn for msg := range a.conn { if msg.conn != nil { // Change the connection - a.log("Switch UDP connection") - udpConn.Close() + if udpConn != nil { + a.log("Switch UDP connection") + udpConn.Close() + } udpConn = msg.conn } - if msg.raw != nil { // Send the given udp message + if udpConn != nil && msg.raw != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { a.log("Unable to send udp message: %+v", err) } } } - udpConn.Close() // Make sure we close the connection before leaving + if udpConn != nil { + udpConn.Close() // Make sure we close the connection before leaving + } } // log is nothing more than a shortcut / helper to access the logger From e3f3a84fa82645bff0d161fd45b07e1994e36198 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 16:52:13 +0100 Subject: [PATCH 0219/2266] [router] Split adapter test to enable broker to send different http response --- adapters/rtr_brk_http/adapter_test.go | 43 ++++++++++++++++++--------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index 89f8b5cb6..6249f83fc 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -5,6 +5,7 @@ package rtr_brk_http import ( "encoding/json" + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/testing/mock_components" @@ -33,8 +34,8 @@ func TestListenOptionsTest(t *testing.T) { // ----- The adapter should forward a payload to a set of brokers func TestForwardPayload(t *testing.T) { tests := []forwardPayloadTest{ - {generateValidPayload(), []string{"0.0.0.0:3000", "0.0.0.0:3001"}, nil}, - {generateInvalidPayload(), []string{"0.0.0.0:3002"}, core.ErrInvalidPayload}, + {generateValidPayload(), generateBrokers([]int{200, 200}), nil}, + {generateInvalidPayload(), generateBrokers([]int{200}), core.ErrInvalidPayload}, } for _, test := range tests { @@ -44,7 +45,7 @@ func TestForwardPayload(t *testing.T) { type forwardPayloadTest struct { payload semtech.Payload - brokers []string + brokers map[string]int want error } @@ -118,22 +119,30 @@ func generateValidPayload() semtech.Payload { } } +var port int = 3000 + +func generateBrokers(status []int) map[string]int { + brokers := make(map[string]int) + for _, s := range status { + brokers[fmt.Sprintf("0.0.0.0:%d", port)] = s + port += 1 + } + return brokers +} + func generateInvalidPayload() semtech.Payload { return semtech.Payload{} } -func toBrokerAddrs(addrs []string) []core.BrokerAddress { +func toBrokerAddrs(addrs map[string]int) []core.BrokerAddress { brokers := make([]core.BrokerAddress, 0) - for _, addr := range addrs { + for addr := range addrs { brokers = append(brokers, core.BrokerAddress(addr)) } return brokers } -// ----- Operate Utilities -func listenHTTP(t *testing.T, addrs []string) chan semtech.Payload { - cmsg := make(chan semtech.Payload) - +func createServeMux(t *testing.T, addr string, status int, cmsg chan semtech.Payload) *http.ServeMux { serveMux := http.NewServeMux() serveMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() @@ -166,18 +175,24 @@ func listenHTTP(t *testing.T, addrs []string) chan semtech.Payload { } // Send a fake response - res.WriteHeader(http.StatusOK) + res.WriteHeader(status) res.Write(nil) cmsg <- payload }) + return serveMux +} + +// ----- Operate Utilities +func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload { + cmsg := make(chan semtech.Payload) - for _, addr := range addrs { - go func(addr string) { - s := &http.Server{Addr: addr, Handler: serveMux} + for addr, status := range addrs { + go func(addr string, status int) { + s := &http.Server{Addr: addr, Handler: createServeMux(t, addr, status, cmsg)} if err := s.ListenAndServe(); err != nil { panic(err) } - }(addr) + }(addr, status) } return cmsg From 8bcc2b51cdd662d86c431f8cf76048655f8c0320 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 16:56:37 +0100 Subject: [PATCH 0220/2266] [router] Add some comments about utility functions --- adapters/rtr_brk_http/adapter_test.go | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index 6249f83fc..3ad29b1b5 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -99,6 +99,8 @@ func (test forwardPayloadTest) check(t *testing.T, cmsg chan semtech.Payload, go } // ----- Build Utilities + +// Create an instance of an Adapter with a predefined logger + a mock router func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { return Adapter{ Logger: log.TestLogger{ @@ -108,6 +110,7 @@ func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { }, mock_components.NewRouter() } +// Generate a very basic payload holding an RXPK packet func generateValidPayload() semtech.Payload { return semtech.Payload{ RXPK: []semtech.RXPK{{ @@ -119,8 +122,15 @@ func generateValidPayload() semtech.Payload { } } +// Generate a payload with no RXPK nor STAT packet +func generateInvalidPayload() semtech.Payload { + return semtech.Payload{} +} + +// Keep track of open TCP ports var port int = 3000 +// Generate a list of brokers given a list of http response status in the form address -> status func generateBrokers(status []int) map[string]int { brokers := make(map[string]int) for _, s := range status { @@ -130,10 +140,7 @@ func generateBrokers(status []int) map[string]int { return brokers } -func generateInvalidPayload() semtech.Payload { - return semtech.Payload{} -} - +// Transform the broker map address -> status to a list a BrokerAddress func toBrokerAddrs(addrs map[string]int) []core.BrokerAddress { brokers := make([]core.BrokerAddress, 0) for addr := range addrs { @@ -142,7 +149,9 @@ func toBrokerAddrs(addrs map[string]int) []core.BrokerAddress { return brokers } -func createServeMux(t *testing.T, addr string, status int, cmsg chan semtech.Payload) *http.ServeMux { +// Create an http handler that will listen to json request on "/" and forward payload into a +// dedicated channel. A custom response status can be given in param. +func createServeMux(t *testing.T, status int, cmsg chan semtech.Payload) *http.ServeMux { serveMux := http.NewServeMux() serveMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { defer req.Body.Close() @@ -183,12 +192,14 @@ func createServeMux(t *testing.T, addr string, status int, cmsg chan semtech.Pay } // ----- Operate Utilities + +// Start one http server per address which will forward request to the returned channel of payloads. func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload { cmsg := make(chan semtech.Payload) for addr, status := range addrs { go func(addr string, status int) { - s := &http.Server{Addr: addr, Handler: createServeMux(t, addr, status, cmsg)} + s := &http.Server{Addr: addr, Handler: createServeMux(t, status, cmsg)} if err := s.ListenAndServe(); err != nil { panic(err) } From ca564a561d07bf597bcfc22176ca2f245b06e2f7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 17:34:42 +0100 Subject: [PATCH 0221/2266] [router] Give more precise message with payload.UniformDevAddr() --- lorawan/semtech/semtech.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go index 1070a5931..cd37802c5 100644 --- a/lorawan/semtech/semtech.go +++ b/lorawan/semtech/semtech.go @@ -142,7 +142,11 @@ func (p Payload) UniformDevAddr() (*DeviceAddress, error) { // We check them all to be sure, but all RXPK should refer to the same End-Device for _, rxpk := range p.RXPK { addr := rxpk.DevAddr() - if addr == nil || (devAddr != nil && *devAddr != *addr) { + if addr == nil { + return nil, fmt.Errorf("Unable to determine uniform address of given payload") + } + + if devAddr != nil && *devAddr != *addr { return nil, fmt.Errorf("Payload is composed of messages from several end-devices") } devAddr = addr From 1f24db2401f995494afb433a0cc7538de25b0c34 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 17:39:47 +0100 Subject: [PATCH 0222/2266] [router] Rewrite adapter tests. Split check functions + write Broadcast tests --- adapters/rtr_brk_http/adapter_test.go | 193 ++++++++++++++++++++------ 1 file changed, 149 insertions(+), 44 deletions(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index 3ad29b1b5..0b65c0120 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -21,7 +21,7 @@ import ( // ----- The adapter can be created and listen straigthforwardly func TestListenOptionsTest(t *testing.T) { - adapter, router := generateAdapterAndRouter(t) + adapter, router := genAdapterAndRouter(t) Desc(t, "Listen to adapter") if err := adapter.Listen(router, nil); err != nil { @@ -34,8 +34,9 @@ func TestListenOptionsTest(t *testing.T) { // ----- The adapter should forward a payload to a set of brokers func TestForwardPayload(t *testing.T) { tests := []forwardPayloadTest{ - {generateValidPayload(), generateBrokers([]int{200, 200}), nil}, - {generateInvalidPayload(), generateBrokers([]int{200}), core.ErrInvalidPayload}, + {genValidPayload(), genBrokers([]int{200}), nil}, + {genValidPayload(), genBrokers([]int{200, 200}), nil}, + {genInvalidPayload(), nil, core.ErrInvalidPayload}, } for _, test := range tests { @@ -50,58 +51,68 @@ type forwardPayloadTest struct { } func (test forwardPayloadTest) run(t *testing.T) { + //Describe Desc(t, "Forward %v to %v", test.payload, test.brokers) - adapter, router := generateAdapterAndRouter(t) + + // Build + adapter, router := genAdapterAndRouter(t) + adapter.Listen(router, toBrokerAddrs(test.brokers)) cmsg := listenHTTP(t, test.brokers) + + // Operate <-time.After(time.Millisecond * 250) got := adapter.Forward(router, test.payload, toBrokerAddrs(test.brokers)...) - test.check(t, cmsg, got) -} -func (test forwardPayloadTest) check(t *testing.T, cmsg chan semtech.Payload, got error) { - <-time.After(time.Millisecond * 500) + // Check + <-time.After(time.Millisecond * 250) + checkErrors(t, test.want, got) + checkReception(t, len(test.brokers), test.payload, cmsg) +} - // Check for the error - if test.want != nil { - if test.want != got { - Ko(t, "Expected error %v but got %v", test.want, got) - return - } - Ok(t) - return +// ----- The adapter should broadcast a payload to a set of broker +func TestBroadcastPayload(t *testing.T) { + tests := []broadcastPayloadTest{ + {genValidPayload(), genBrokers([]int{200, 200}), nil}, + {genValidPayload(), genBrokers([]int{200, 404}), nil}, + {genValidPayloadInvalidDevAddr(), nil, core.ErrInvalidPayload}, + {genInvalidPayload(), nil, core.ErrInvalidPayload}, } - // Check if payload should have been sent - if len(test.brokers) == 0 { - Ok(t) - return + for _, test := range tests { + test.run(t) } +} - // Gather payloads and check one of them - var payloads []semtech.Payload - select { - case payload := <-cmsg: - payloads = append(payloads, payload) - if len(payloads) == len(test.brokers) { - break - } - case <-time.After(time.Millisecond * 500): - Ko(t, "%d payload(s) send to server(s) whereas %d was/were expected", len(payloads), len(test.brokers)) - return - } +type broadcastPayloadTest struct { + payload semtech.Payload + brokers map[string]int + want error +} - if !reflect.DeepEqual(test.payload, payloads[0]) { - Ko(t, "Expected %+v to be sent but server received: %+v", test.payload, payloads[0]) - return - } +func (test broadcastPayloadTest) run(t *testing.T) { + // Describe + Desc(t, "Forward %v to %v", test.payload, test.brokers) - Ok(t) + // Build + adapter, router := genAdapterAndRouter(t) + adapter.Listen(router, toBrokerAddrs(test.brokers)) + cmsg := listenHTTP(t, test.brokers) + + // Operate + <-time.After(time.Millisecond * 250) + got := adapter.Broadcast(router, test.payload) + + // Check + <-time.After(time.Millisecond * 250) + checkErrors(t, test.want, got) + checkReception(t, len(test.brokers), test.payload, cmsg) + checkRegistration(t, router, test.payload, test.brokers) } // ----- Build Utilities // Create an instance of an Adapter with a predefined logger + a mock router -func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { +func genAdapterAndRouter(t *testing.T) (Adapter, core.Router) { return Adapter{ Logger: log.TestLogger{ Tag: "Adapter", @@ -110,8 +121,20 @@ func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { }, mock_components.NewRouter() } -// Generate a very basic payload holding an RXPK packet -func generateValidPayload() semtech.Payload { +// gen a very basic payload holding an RXPK packet and identifying a valid device address +func genValidPayload() semtech.Payload { + return semtech.Payload{ + RXPK: []semtech.RXPK{{ + Data: pointer.String(""), + Freq: pointer.Float64(866.349812), + Rssi: pointer.Int(-35), + }, + }, + } +} + +// gen a very basic payload holding an RXPK packet but with scrap data +func genValidPayloadInvalidDevAddr() semtech.Payload { return semtech.Payload{ RXPK: []semtech.RXPK{{ Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), @@ -122,16 +145,16 @@ func generateValidPayload() semtech.Payload { } } -// Generate a payload with no RXPK nor STAT packet -func generateInvalidPayload() semtech.Payload { +// gen a payload with no RXPK nor STAT packet +func genInvalidPayload() semtech.Payload { return semtech.Payload{} } // Keep track of open TCP ports var port int = 3000 -// Generate a list of brokers given a list of http response status in the form address -> status -func generateBrokers(status []int) map[string]int { +// gen a list of brokers given a list of http response status in the form address -> status +func genBrokers(status []int) map[string]int { brokers := make(map[string]int) for _, s := range status { brokers[fmt.Sprintf("0.0.0.0:%d", port)] = s @@ -208,3 +231,85 @@ func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload { return cmsg } + +// ----- Check Utilities +func checkErrors(t *testing.T, want error, got error) bool { + // Check for the error + if want != nil { + if want != got { + Ko(t, "Expected error %v but got %v", want, got) + return false + } + Ok(t) + return true + } + return true +} + +func checkReception(t *testing.T, nbExpected int, want semtech.Payload, cmsg chan semtech.Payload) bool { + // Check if payload should have been sent + if nbExpected <= 0 { + Ok(t) + return true + } + + // Gather payloads and check one of them + var payloads []semtech.Payload + select { + case payload := <-cmsg: + payloads = append(payloads, payload) + if len(payloads) == nbExpected { + break + } + case <-time.After(time.Millisecond * 500): + Ko(t, "%d payload(s) send to server(s) whereas %d was/were expected", len(payloads), nbExpected) + return false + } + + if !reflect.DeepEqual(want, payloads[0]) { + Ko(t, "Expected %+v to be sent but server received: %+v", want, payloads[0]) + return false + } + + Ok(t) + return true +} + +func checkRegistration(t *testing.T, router core.Router, payload semtech.Payload, brokers map[string]int) bool { + if len(brokers) == 0 { + Ok(t) + return true + } + + devAddr, err := payload.UniformDevAddr() + if err != nil { + panic(err) + } + + mockRouter := router.(*mock_components.Router) // Need to access to registered devices of mock router + +outer: + for addr, status := range brokers { + if status != 200 { // Not a HTTP 200 OK, broker probably does not handle that device + continue + } + + addrs, ok := mockRouter.Devices[*devAddr] // Get all registered brokers for that device + if !ok { + Ko(t, "Broker %s wasn't registered for payload %v", addr, payload) + return false + } + + for _, broker := range addrs { + if string(broker) == addr { + continue outer // We are registered, everything's fine for that broker + } + } + + Ko(t, "Broker %s wasn't registered for payload %v", addr, payload) + return false + } + + Ok(t) + return true +} From ec13571a745bf7fe2a73bc9ccbc2bf96216f3793 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 19:34:08 +0100 Subject: [PATCH 0223/2266] [router] Rewrite broadcast method for upadapter. Add internal state of brokers --- adapters/rtr_brk_http/adapter.go | 29 ++++++++++++++++++++++++----- core.go | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 43730cf06..4a8b01f6c 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -18,16 +18,30 @@ import ( ) type Adapter struct { - Logger log.Logger + Logger log.Logger + brokers []core.BrokerAddress } // Listen implements the core.Adapter interface func (a *Adapter) Listen(router core.Router, options interface{}) error { + switch options.(type) { + case []core.BrokerAddress: + a.brokers = options.([]core.BrokerAddress) + default: + a.log("Invalid options provided: %v", options) + return core.ErrBadOptions + } + return nil } // Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { +func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { + if a.brokers == nil { + a.log("Cannot broadcast to 0 broker") + return core.ErrMissingConnection + } + // Determine the devAddress associated to that payload if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? a.log("Cannot broadcast given payload: %+v", payload) @@ -41,12 +55,12 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr } // Prepare ground to store brokers that are in charge - register := make(chan core.BrokerAddress, len(broAddrs)) + register := make(chan core.BrokerAddress, len(a.brokers)) wg := sync.WaitGroup{} - wg.Add(len(broAddrs)) + wg.Add(len(a.brokers)) client := http.Client{} - for _, addr := range broAddrs { + for _, addr := range a.brokers { go func(addr core.BrokerAddress) { defer wg.Done() @@ -90,6 +104,11 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddr // Forward implements the core.BrokerRouter interface func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { + if a.brokers == nil { + a.log("Cannot broadcast to 0 broker") + return core.ErrMissingConnection + } + if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? a.log("Cannot broadcast given payload: %+v", payload) return core.ErrInvalidPayload diff --git a/core.go b/core.go index 396df1d3d..02ec7b282 100644 --- a/core.go +++ b/core.go @@ -57,7 +57,7 @@ type RouterBrokerAdapter interface { // // We assume that broadcast is also registering a device address towards the router depending // on the brokers responses. - Broadcast(router Router, payload Payload, broAddrs ...BrokerAddress) error + Broadcast(router Router, payload Payload) error // Forward is an explicit forwarding of a packet which is known being handled by a set of // brokers. None of the contacted broker is supposed to reject the incoming payload; They all From f5e0dee2585e219ccfe706e32a0081a0c43b076d Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 19:43:42 +0100 Subject: [PATCH 0224/2266] [router] Add correct base64 data encoded with dev addr to test --- adapters/rtr_brk_http/adapter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index 0b65c0120..c3a853c7b 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -125,7 +125,7 @@ func genAdapterAndRouter(t *testing.T) (Adapter, core.Router) { func genValidPayload() semtech.Payload { return semtech.Payload{ RXPK: []semtech.RXPK{{ - Data: pointer.String(""), + Data: pointer.String("/xRC/zcBAAABqqq7uw=="), Freq: pointer.Float64(866.349812), Rssi: pointer.Int(-35), }, From 13717b8252e3268a20ecc50ab417e3131a1f661c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 19:50:49 +0100 Subject: [PATCH 0225/2266] [router] Fix url issue missing http protocol for both forward and broadcast --- adapters/rtr_brk_http/adapter.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 4a8b01f6c..2ba5d1381 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -137,14 +137,14 @@ func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs // from the broker to handle packets or anything else ? Is it efficient ? Should // downlinks packets be sent back with the HTTP body response ? Its a 2 seconds frame... - }(fmt.Sprintf("http://%s", string(addr))) + }(string(addr)) } return nil } // post regroups some logic used in both Forward and Broadcast methods -func post(client http.Client, url string, payload semtech.Payload) (*http.Response, error) { +func post(client http.Client, host string, payload semtech.Payload) (*http.Response, error) { data := new(bytes.Buffer) rawJSON, err := json.Marshal(payload) if err != nil { @@ -155,7 +155,7 @@ func post(client http.Client, url string, payload semtech.Payload) (*http.Respon return nil, err } - return client.Post(url, "application/json", data) + return client.Post(fmt.Sprintf("http://%s", host), "application/json", data) } // log is nothing more than a shortcut / helper to access the logger From 32c2b35e9561699a123020be78eb78032e63c34b Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 19:51:06 +0100 Subject: [PATCH 0226/2266] [router] Fix test issue with blocking channel --- adapters/rtr_brk_http/adapter_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index c3a853c7b..72b374268 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -64,7 +64,7 @@ func (test forwardPayloadTest) run(t *testing.T) { got := adapter.Forward(router, test.payload, toBrokerAddrs(test.brokers)...) // Check - <-time.After(time.Millisecond * 250) + <-time.After(time.Millisecond * 100) checkErrors(t, test.want, got) checkReception(t, len(test.brokers), test.payload, cmsg) } @@ -91,7 +91,7 @@ type broadcastPayloadTest struct { func (test broadcastPayloadTest) run(t *testing.T) { // Describe - Desc(t, "Forward %v to %v", test.payload, test.brokers) + Desc(t, "Broadcast %v to %v", test.payload, test.brokers) // Build adapter, router := genAdapterAndRouter(t) @@ -103,7 +103,7 @@ func (test broadcastPayloadTest) run(t *testing.T) { got := adapter.Broadcast(router, test.payload) // Check - <-time.After(time.Millisecond * 250) + <-time.After(time.Millisecond * 100) checkErrors(t, test.want, got) checkReception(t, len(test.brokers), test.payload, cmsg) checkRegistration(t, router, test.payload, test.brokers) @@ -218,7 +218,7 @@ func createServeMux(t *testing.T, status int, cmsg chan semtech.Payload) *http.S // Start one http server per address which will forward request to the returned channel of payloads. func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload { - cmsg := make(chan semtech.Payload) + cmsg := make(chan semtech.Payload, len(addrs)) for addr, status := range addrs { go func(addr string, status int) { From 4d384a60fc90a5eb74ce957abccbca24df19582c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 19:56:47 +0100 Subject: [PATCH 0227/2266] [router] Rewrite listen tests to reflect new Adapter behavior --- adapters/rtr_brk_http/adapter_test.go | 36 ++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index 72b374268..0d243541c 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -21,17 +21,40 @@ import ( // ----- The adapter can be created and listen straigthforwardly func TestListenOptionsTest(t *testing.T) { - adapter, router := genAdapterAndRouter(t) + tests := []listenOptionsTest{ + {[]core.BrokerAddress{core.BrokerAddress("0.0.0.0:3000"), core.BrokerAddress("0.0.0.0:3001")}, nil}, + {"Patate", core.ErrBadOptions}, + {nil, core.ErrBadOptions}, + {[]core.BrokerAddress{}, core.ErrBadOptions}, + } - Desc(t, "Listen to adapter") - if err := adapter.Listen(router, nil); err != nil { - Ko(t, "No error was expected but got: %+v", err) - return + for _, test := range tests { + test.run(t) } - Ok(t) } +type listenOptionsTest struct { + options interface{} + want error +} + +func (test listenOptionsTest) run(t *testing.T) { + // Describe + Desc(t, "Listen to adapter with options: %v", test.options) + + // Build + adapter, router := genAdapterAndRouter(t) + + // Operate + got := adapter.Listen(router, test.options) + + // Check + checkErrors(t, test.want, got) +} + +// -------------------------------------------------------------- // ----- The adapter should forward a payload to a set of brokers +// -------------------------------------------------------------- func TestForwardPayload(t *testing.T) { tests := []forwardPayloadTest{ {genValidPayload(), genBrokers([]int{200}), nil}, @@ -243,6 +266,7 @@ func checkErrors(t *testing.T, want error, got error) bool { Ok(t) return true } + Ok(t) return true } From e2941e49517558915c37e3399d32a6629a4aa799 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 19:57:48 +0100 Subject: [PATCH 0228/2266] [router] Fix failing test with Listen() --- adapters/rtr_brk_http/adapter.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 2ba5d1381..a019d4749 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -27,6 +27,9 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { switch options.(type) { case []core.BrokerAddress: a.brokers = options.([]core.BrokerAddress) + if len(a.brokers) == 0 { + return core.ErrBadOptions + } default: a.log("Invalid options provided: %v", options) return core.ErrBadOptions From 98f7df7a0cd856b6b0ba668347541787e9281edd Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 20:19:57 +0100 Subject: [PATCH 0229/2266] [router] Make router <-> broker adapter concurrent-safe --- adapters/rtr_brk_http/adapter.go | 30 +++++++++++++++++++++------ adapters/rtr_brk_http/adapter_test.go | 19 ++++++----------- core.go | 1 + 3 files changed, 31 insertions(+), 19 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index a019d4749..4c9c51e82 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -20,12 +20,27 @@ import ( type Adapter struct { Logger log.Logger brokers []core.BrokerAddress + mu *sync.RWMutex // Guard brokers +} + +func NewAdapter() Adapter { + return Adapter{mu: &sync.RWMutex{}} +} + +func (a *Adapter) ok() bool { + return a != nil && a.mu != nil } // Listen implements the core.Adapter interface func (a *Adapter) Listen(router core.Router, options interface{}) error { + if !a.ok() { + return core.ErrNotInitialized + } + switch options.(type) { case []core.BrokerAddress: + a.mu.Lock() + defer a.mu.Unlock() a.brokers = options.([]core.BrokerAddress) if len(a.brokers) == 0 { return core.ErrBadOptions @@ -40,9 +55,8 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { // Broadcast implements the core.BrokerRouter interface func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { - if a.brokers == nil { - a.log("Cannot broadcast to 0 broker") - return core.ErrMissingConnection + if !a.ok() { + return core.ErrNotInitialized } // Determine the devAddress associated to that payload @@ -63,6 +77,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { wg.Add(len(a.brokers)) client := http.Client{} + a.mu.RLock() for _, addr := range a.brokers { go func(addr core.BrokerAddress) { defer wg.Done() @@ -89,6 +104,7 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { } }(addr) } + a.mu.RUnlock() go func() { wg.Wait() @@ -107,16 +123,17 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { // Forward implements the core.BrokerRouter interface func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { - if a.brokers == nil { - a.log("Cannot broadcast to 0 broker") - return core.ErrMissingConnection + if !a.ok() { + return core.ErrNotInitialized } if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? a.log("Cannot broadcast given payload: %+v", payload) return core.ErrInvalidPayload } + client := http.Client{} + a.mu.RLock() for _, addr := range broAddrs { go func(url string) { a.log("Send payload to %s", url) @@ -142,6 +159,7 @@ func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs }(string(addr)) } + a.mu.RUnlock() return nil } diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index 0d243541c..c3d9fbe9c 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -136,12 +136,9 @@ func (test broadcastPayloadTest) run(t *testing.T) { // Create an instance of an Adapter with a predefined logger + a mock router func genAdapterAndRouter(t *testing.T) (Adapter, core.Router) { - return Adapter{ - Logger: log.TestLogger{ - Tag: "Adapter", - T: t, - }, - }, mock_components.NewRouter() + a := NewAdapter() + a.Logger = log.TestLogger{Tag: "Adapter", T: t} + return a, mock_components.NewRouter() } // gen a very basic payload holding an RXPK packet and identifying a valid device address @@ -258,13 +255,9 @@ func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload { // ----- Check Utilities func checkErrors(t *testing.T, want error, got error) bool { // Check for the error - if want != nil { - if want != got { - Ko(t, "Expected error %v but got %v", want, got) - return false - } - Ok(t) - return true + if want != got { + Ko(t, "Expected error %v but got %v", want, got) + return false } Ok(t) return true diff --git a/core.go b/core.go index 02ec7b282..1426a1278 100644 --- a/core.go +++ b/core.go @@ -31,6 +31,7 @@ type Router interface { // The error types belows are going to be more complex in order to handle custom behavior for // each error type. var ErrBadOptions error = fmt.Errorf("Unreckonized or invalid options") +var ErrNotInitialized error = fmt.Errorf("Structure not initialized") var ErrBadGatewayAddress error = fmt.Errorf("Invalid gateway address") var ErrMissingConnection error = fmt.Errorf("Can't proceed without establishing connection") var ErrInvalidPacket error = fmt.Errorf("Invalid semtech packet") From ea8350b728e44bd5db925a88ceab05a96f8cb736 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 20:21:52 +0100 Subject: [PATCH 0230/2266] [router] Add some comments to downlink adapter --- adapters/rtr_brk_http/adapter.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 4c9c51e82..bc762c77b 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -19,14 +19,16 @@ import ( type Adapter struct { Logger log.Logger - brokers []core.BrokerAddress - mu *sync.RWMutex // Guard brokers + brokers []core.BrokerAddress // List of brokers to which broadcast + mu *sync.RWMutex // Guard brokers } +// NewAdapter constructs a new Router <-> Broker adapter func NewAdapter() Adapter { return Adapter{mu: &sync.RWMutex{}} } +// Check whether or not the adapter has been initialized via NewAdapter() func (a *Adapter) ok() bool { return a != nil && a.mu != nil } From 805831b75c1a3cae6a5bad2c28c38ade3a2b6c87 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 20:29:15 +0100 Subject: [PATCH 0231/2266] [router] Make downlink adapter concurrent-safe --- adapters/gtw_rtr_udp/adapter.go | 28 ++++++++++++++++------------ adapters/gtw_rtr_udp/adapter_test.go | 11 +++-------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index e3d10cde6..f95afa585 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -22,9 +22,23 @@ type udpMsg struct { conn *net.UDPConn // Provide if you intent to change the current adapter connection } +func NewAdapter() Adapter { + a := Adapter{conn: make(chan udpMsg)} + go a.monitorConnection() // Terminates that goroutine by closing the channel + return a +} + +func (a *Adapter) ok() bool { + return a != nil && a.conn != nil +} + // Listen implements the core.Adapter interface. It expects only one param "port" as a // uint. Listen can be called several times to re-establish a lost connection. func (a *Adapter) Listen(router core.Router, options interface{}) error { + if !a.ok() { + return core.ErrNotInitialized + } + // Parse options var port uint switch options.(type) { @@ -43,15 +57,6 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { return core.ErrBadGatewayAddress } - // The following statements aren't thread-safe. It assumes that only one goroutine will attempt - // to Listen() in a first place, then it does not matter because all access will be read-only - // access and we won't have any data race here. However, for the very first call, this has to be - // done in a non-concurrent context. - if a.conn == nil { - a.conn = make(chan udpMsg) - go a.monitorConnection() // Terminates that goroutine by closing the channel - } - a.conn <- udpMsg{conn: udpConn} go a.listen(router, udpConn) // Terminates when the connection is closed @@ -60,9 +65,8 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { // Ack implements the core.GatewayRouterAdapter interface func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) error { - if a.conn == nil { - a.log("Trying to Ack on non-established connection") - return core.ErrMissingConnection + if !a.ok() { + return core.ErrNotInitialized } a.log("Acks packet %+v", packet) diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index 9c89dacf5..ce0604eb1 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -104,7 +104,6 @@ func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway func TestSendAck(t *testing.T) { // 1. Initialize test data adapter, router := generateAdapterAndRouter(t) - adapter2, router2 := generateAdapterAndRouter(t) conn, gateway := createConnection(&adapter, router, 3003) defer conn.Close() @@ -112,7 +111,6 @@ func TestSendAck(t *testing.T) { {adapter, router, conn, gateway, generatePUSH_ACK(), nil}, {adapter, router, conn, core.GatewayAddress("patate"), generatePUSH_ACK(), core.ErrBadGatewayAddress}, {adapter, router, conn, gateway, semtech.Packet{}, core.ErrInvalidPacket}, - {adapter2, router2, nil, gateway, generatePUSH_ACK(), core.ErrMissingConnection}, } // 2. Run tests @@ -184,12 +182,9 @@ func TestConnectionRecovering(t *testing.T) { // ----- Build Utilities func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { - return Adapter{ - Logger: log.TestLogger{ - Tag: "Adapter", - T: t, - }, - }, mock_components.NewRouter() + a := NewAdapter() + a.Logger = log.TestLogger{Tag: "Adapter", T: t} + return a, mock_components.NewRouter() } func generatePUSH_DATA() semtech.Packet { From 54f2fbaf8387bed6d35013c698c89cd4c24ad6f7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 20:30:25 +0100 Subject: [PATCH 0232/2266] [router] Add some comments to uplink adapter --- adapters/gtw_rtr_udp/adapter.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index f95afa585..cfffed2be 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -22,12 +22,14 @@ type udpMsg struct { conn *net.UDPConn // Provide if you intent to change the current adapter connection } +// NewAdapter constructs a gateway <-> router udp adapter func NewAdapter() Adapter { a := Adapter{conn: make(chan udpMsg)} go a.monitorConnection() // Terminates that goroutine by closing the channel return a } +// ok controls whether or not the adapter has been initialized via NewAdapter() func (a *Adapter) ok() bool { return a != nil && a.conn != nil } From be2e4a294ae1e6f99de0ae4663e2a8fcd2f4174a Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Jan 2016 20:55:39 +0100 Subject: [PATCH 0233/2266] Remove pointer to mutex in adapter. No need. --- adapters/rtr_brk_http/adapter.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index bc762c77b..5e11fb1cf 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -20,17 +20,17 @@ import ( type Adapter struct { Logger log.Logger brokers []core.BrokerAddress // List of brokers to which broadcast - mu *sync.RWMutex // Guard brokers + mu sync.RWMutex // Guard brokers } -// NewAdapter constructs a new Router <-> Broker adapter +// NewAdapter() constructs a new router <-> broker adapter func NewAdapter() Adapter { - return Adapter{mu: &sync.RWMutex{}} + return Adapter{} } -// Check whether or not the adapter has been initialized via NewAdapter() +// Check whether or not the adapter has been initialized func (a *Adapter) ok() bool { - return a != nil && a.mu != nil + return a != nil } // Listen implements the core.Adapter interface @@ -74,12 +74,18 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { } // Prepare ground to store brokers that are in charge - register := make(chan core.BrokerAddress, len(a.brokers)) + a.mu.RLock() + l := len(a.brokers) + if l == 0 { + a.mu.Unlock() + return core.ErrNotInitialized + } + + register := make(chan core.BrokerAddress, l) wg := sync.WaitGroup{} - wg.Add(len(a.brokers)) + wg.Add(l) client := http.Client{} - a.mu.RLock() for _, addr := range a.brokers { go func(addr core.BrokerAddress) { defer wg.Done() From d40201f077f2e866eeb59fa30e79eaacb88d2ccc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 14:30:10 +0100 Subject: [PATCH 0234/2266] [router] Write first tests for router constructor --- components/router_test.go | 56 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 components/router_test.go diff --git a/components/router_test.go b/components/router_test.go new file mode 100644 index 000000000..f39822aed --- /dev/null +++ b/components/router_test.go @@ -0,0 +1,56 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/thethingsnetwork/core" + . "github.com/thethingsnetwork/core/utils/testing" + "testing" +) + +// ----- A new router instance can be created an obtained from a constuctor +func TestNewRouter(t *testing.T) { + tests := []newRouterTest{ + {genBrokers(), nil}, + {[]core.BrokerAddress{}, core.ErrBadOptions}, + } + + for _, test := range tests { + test.run(t) + } +} + +type newRouterTest struct { + in []core.BrokerAddress + want error +} + +func (test newRouterTest) run(t *testing.T) { + Desc(t, "Create new router with params: %v", test.in) + router, err := NewRouter(test.in...) + checkErrors(t, test.want, err, router) +} + +// ----- Build Utilities +func genBrokers() []core.BrokerAddress { + return []core.BrokerAddress{ + core.BrokerAddress("0.0.0.0:3000"), + core.BrokerAddress("0.0.0.0:3001"), + } +} + +// ----- Check Utilities +func checkErrors(t *testing.T, want error, got error, router core.Router) { + if want != got { + Ko(t, "Expected error {%v} but got {%v}", want, got) + return + } + + if want == nil && router == nil { + Ko(t, "Expected no error but got a nil router") + return + } + + Ok(t) +} From 4394c8c64d230623a3e12a70250fbfcab2d4685c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 14:30:51 +0100 Subject: [PATCH 0235/2266] [router] Make tests pass --- components/router.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/router.go b/components/router.go index 8dba92799..8fcc57132 100644 --- a/components/router.go +++ b/components/router.go @@ -48,7 +48,7 @@ func NewRouter(brokers ...core.BrokerAddress) (*Router, error) { } if len(brokers) == 0 { - return nil, fmt.Errorf("The router should be connected to at least one broker") + return nil, core.ErrBadOptions } return &Router{ @@ -178,7 +178,7 @@ func (r *Router) connectUpAdapter(upAdapter core.GatewayRouterAdapter) { func (r *Router) connectDownAdapter(downAdapter core.RouterBrokerAdapter) { for msg := range r.down { if len(msg.brokers) == 0 { - downAdapter.Broadcast(r, msg.payload, r.brokers...) + downAdapter.Broadcast(r, msg.payload) continue } downAdapter.Forward(r, msg.payload, msg.brokers...) From 6cf8ef0105f8d80ab9f946256ecc003cd776affd Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 14:39:01 +0100 Subject: [PATCH 0236/2266] [router] Rewrite mock gateway <-> router adapter. Add a method to simulate incoming packets --- testing/mock_adapters/gtw_rtr_mock/adapter.go | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/testing/mock_adapters/gtw_rtr_mock/adapter.go b/testing/mock_adapters/gtw_rtr_mock/adapter.go index 23fbb36e5..543367bb9 100644 --- a/testing/mock_adapters/gtw_rtr_mock/adapter.go +++ b/testing/mock_adapters/gtw_rtr_mock/adapter.go @@ -15,6 +15,7 @@ type Adapter struct { FailAck bool // If true, each call to Ack will fail with a core.ErrAck FailListen bool // If true, each call to Listen will return an error connected bool // Indicate wether or not the Listen method has been called + router core.Router // The router to which the adapter is connected Acks map[core.GatewayAddress][]semtech.Packet // Stores all packet send through Ack() } @@ -23,7 +24,6 @@ func New() Adapter { return Adapter{ FailAck: false, FailListen: false, - connected: false, Acks: make(map[core.GatewayAddress][]semtech.Packet), } } @@ -33,19 +33,27 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { if a.FailListen { return fmt.Errorf("Unable to establish connection") } - a.connected = true + a.router = router return nil } // Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) { - if !a.connected { - router.HandleError(core.ErrAck(fmt.Errorf("Try to send ack through non connected adapter"))) - return +func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) error { + if a.router == nil { + return core.ErrNotInitialized } if a.FailAck { - router.HandleError(core.ErrAck(fmt.Errorf("Unable to ack the given packet"))) - return + return core.ErrInvalidPacket } a.Acks[gateway] = append(a.Acks[gateway], packet) + return nil +} + +// Simulate sends the given fake packet to the router as it was received by the adapter +func (a *Adapter) Simulate(packet semtech.Packet, gateway core.GatewayAddress) error { + if a.router == nil { + return core.ErrNotInitialized + } + a.router.HandleUplink(packet, gateway) + return nil } From 679a6e0a88cedafad38580f2cae0a15f5d8ffbac Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 14:46:47 +0100 Subject: [PATCH 0237/2266] [router] Make mock adapter simplier. Remove non necessary internal states and methods --- testing/mock_adapters/gtw_rtr_mock/adapter.go | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/testing/mock_adapters/gtw_rtr_mock/adapter.go b/testing/mock_adapters/gtw_rtr_mock/adapter.go index 543367bb9..e6d55ec42 100644 --- a/testing/mock_adapters/gtw_rtr_mock/adapter.go +++ b/testing/mock_adapters/gtw_rtr_mock/adapter.go @@ -6,7 +6,6 @@ package gtw_rtr_mock import ( - "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" ) @@ -14,8 +13,6 @@ import ( type Adapter struct { FailAck bool // If true, each call to Ack will fail with a core.ErrAck FailListen bool // If true, each call to Listen will return an error - connected bool // Indicate wether or not the Listen method has been called - router core.Router // The router to which the adapter is connected Acks map[core.GatewayAddress][]semtech.Packet // Stores all packet send through Ack() } @@ -31,29 +28,16 @@ func New() Adapter { // Listen implements the core.Adapter interface func (a *Adapter) Listen(router core.Router, options interface{}) error { if a.FailListen { - return fmt.Errorf("Unable to establish connection") + return core.ErrBadOptions } - a.router = router return nil } // Ack implements the core.GatewayRouterAdapter interface func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) error { - if a.router == nil { - return core.ErrNotInitialized - } if a.FailAck { return core.ErrInvalidPacket } a.Acks[gateway] = append(a.Acks[gateway], packet) return nil } - -// Simulate sends the given fake packet to the router as it was received by the adapter -func (a *Adapter) Simulate(packet semtech.Packet, gateway core.GatewayAddress) error { - if a.router == nil { - return core.ErrNotInitialized - } - a.router.HandleUplink(packet, gateway) - return nil -} From 1cd4e57cb7161a5a61438c53dad41cc8fceaf265 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 14:51:20 +0100 Subject: [PATCH 0238/2266] [router] Remove old stuff in mock router <-> broker adapter. Now compliant with current interfaces --- testing/mock_adapters/rtr_brk_mock/adapter.go | 34 ++++++------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/testing/mock_adapters/rtr_brk_mock/adapter.go b/testing/mock_adapters/rtr_brk_mock/adapter.go index 78ed7b959..f641fcb3e 100644 --- a/testing/mock_adapters/rtr_brk_mock/adapter.go +++ b/testing/mock_adapters/rtr_brk_mock/adapter.go @@ -6,19 +6,18 @@ package rtr_brk_mock import ( - "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "time" ) type Adapter struct { + Brokers []core.BrokerAddress // All known brokers with which the router is communicating FailListen bool // If true, any call to Listen will fail with an error FailBroadcast bool // If true, any call to Broadcast will trigger a core.ErrBroadcast FailForward bool // If true, any call to Forward will trigger a core.ErrForward Broadcasts map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through broadcasts Forwards map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through forwards - connected bool // Indicates whether or not the Listen() method has been called } // New constructs a new router <-> broker adapter interface @@ -27,38 +26,32 @@ func New() Adapter { FailListen: false, FailBroadcast: false, FailForward: false, - connected: false, Broadcasts: make(map[semtech.DeviceAddress][]semtech.Payload), Forwards: make(map[semtech.DeviceAddress][]semtech.Payload), } } -// Connect implements the core.Adapter interface +// Connect implements the core.Adapter interface. Expect a slice of broker address as options func (a *Adapter) Listen(router core.Router, options interface{}) error { if a.FailListen { - return fmt.Errorf("Unable to establish the connection") + return core.ErrBadOptions } - a.connected = true + a.Brokers = options.([]core.BrokerAddress) return nil } // Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { - if !a.connected { - router.HandleError(core.ErrBroadcast(fmt.Errorf("Try to broadcast with non connected adapter"))) - return - } - +func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { devAddr, err := payload.UniformDevAddr() if a.FailBroadcast || payload.RXPK == nil || err != nil { - router.HandleError(core.ErrBroadcast(fmt.Errorf("Unable to broadcast given payload %+v", payload))) - return + return core.ErrBroadcast } <-time.After(time.Millisecond * 50) a.Broadcasts[*devAddr] = append(a.Broadcasts[*devAddr], payload) - router.RegisterDevice(*devAddr, a.InChargeOf(payload, broAddrs...)...) + router.RegisterDevice(*devAddr, a.InChargeOf(payload, a.Brokers...)...) + return nil } // InChargeOf returns a set of brokers in charge of a payload (result of simulating a broadcast @@ -74,17 +67,12 @@ func (a *Adapter) InChargeOf(payload semtech.Payload, broAddrs ...core.BrokerAdd } // Forward implements the core.BrokerRouter interface -func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) { - if !a.connected { - router.HandleError(core.ErrForward(fmt.Errorf("Try to forward with non connected adapter"))) - return - } - +func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { devAddr, err := payload.UniformDevAddr() if a.FailForward || err != nil { - router.HandleError(core.ErrForward(fmt.Errorf("Unable to forward given payload %+v", payload))) - return + return core.ErrForward } a.Forwards[*devAddr] = append(a.Forwards[*devAddr], payload) + return nil } From f60adf721b092f5d3e224f3530f3353ee15ba2e3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 15:26:24 +0100 Subject: [PATCH 0239/2266] [router] Return *Adapter (implement core.xxxAdapter interfaces) instead of struct itself for mock adapters --- testing/mock_adapters/gtw_rtr_mock/adapter.go | 4 ++-- testing/mock_adapters/rtr_brk_mock/adapter.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/testing/mock_adapters/gtw_rtr_mock/adapter.go b/testing/mock_adapters/gtw_rtr_mock/adapter.go index e6d55ec42..4ad6c77fa 100644 --- a/testing/mock_adapters/gtw_rtr_mock/adapter.go +++ b/testing/mock_adapters/gtw_rtr_mock/adapter.go @@ -17,8 +17,8 @@ type Adapter struct { } // New constructs a new Gateway-Router-Mock adapter -func New() Adapter { - return Adapter{ +func New() *Adapter { + return &Adapter{ FailAck: false, FailListen: false, Acks: make(map[core.GatewayAddress][]semtech.Packet), diff --git a/testing/mock_adapters/rtr_brk_mock/adapter.go b/testing/mock_adapters/rtr_brk_mock/adapter.go index f641fcb3e..b0bf1e616 100644 --- a/testing/mock_adapters/rtr_brk_mock/adapter.go +++ b/testing/mock_adapters/rtr_brk_mock/adapter.go @@ -21,8 +21,8 @@ type Adapter struct { } // New constructs a new router <-> broker adapter interface -func New() Adapter { - return Adapter{ +func New() *Adapter { + return &Adapter{ FailListen: false, FailBroadcast: false, FailForward: false, From b0b39c94e0fcdd1d6dfdc7d03c5378fd0011af1f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 15:27:12 +0100 Subject: [PATCH 0240/2266] [router] Remove blank line in router.go --- components/router.go | 1 - 1 file changed, 1 deletion(-) diff --git a/components/router.go b/components/router.go index 8fcc57132..e5531aa35 100644 --- a/components/router.go +++ b/components/router.go @@ -129,7 +129,6 @@ func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress } default: r.log("Unexpected packet receive from uplink %+v", packet) - } } From 0a2219904e46f33e57510ec2e263d7a799657d7f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 15:27:36 +0100 Subject: [PATCH 0241/2266] [router] Add backbone of HandleUplink() tests --- components/router_test.go | 83 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/components/router_test.go b/components/router_test.go index f39822aed..e7b060adc 100644 --- a/components/router_test.go +++ b/components/router_test.go @@ -5,6 +5,9 @@ package components import ( "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/testing/mock_adapters/gtw_rtr_mock" + "github.com/thethingsnetwork/core/testing/mock_adapters/rtr_brk_mock" . "github.com/thethingsnetwork/core/utils/testing" "testing" ) @@ -32,6 +35,39 @@ func (test newRouterTest) run(t *testing.T) { checkErrors(t, test.want, err, router) } +// ----- A router can handle uplink packets +func TestHandleUplink(t *testing.T) { + tests := []handleUplinkTest{ + {genPULL_DATA(), core.GatewayAddress("a1"), 1, 0, 0}, + } + + for _, test := range tests { + test.run(t) + } +} + +type handleUplinkTest struct { + packet semtech.Packet + gateway core.GatewayAddress + wantAck int + wantForward int + wantBroadcast int +} + +func (test handleUplinkTest) run(t *testing.T) { + // Describe + Desc(t, "Handle uplink packet %v from gateway %v", test.packet, test.gateway) + + // Build + router, upAdapter, downAdapter := genAdaptersAndRouter(t) + + // Operate + router.HandleUplink(test.packet, test.gateway) + + // Check + checkUplink(t, upAdapter, downAdapter, test.wantAck, test.wantForward, test.wantBroadcast) +} + // ----- Build Utilities func genBrokers() []core.BrokerAddress { return []core.BrokerAddress{ @@ -40,6 +76,32 @@ func genBrokers() []core.BrokerAddress { } } +func genAdaptersAndRouter(t *testing.T) (core.Router, core.GatewayRouterAdapter, core.RouterBrokerAdapter) { + brokers := genBrokers() + router, err := NewRouter(brokers...) + if err != nil { + panic(err) + } + + upAdapter := gtw_rtr_mock.New() + downAdapter := rtr_brk_mock.New() + + upAdapter.Listen(router, nil) + downAdapter.Listen(router, brokers) + router.Connect(upAdapter, downAdapter) + + return router, upAdapter, downAdapter +} + +func genPULL_DATA() semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_DATA, + Token: []byte{0x14, 0xba}, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + } +} + // ----- Check Utilities func checkErrors(t *testing.T, want error, got error, router core.Router) { if want != got { @@ -54,3 +116,24 @@ func checkErrors(t *testing.T, want error, got error, router core.Router) { Ok(t) } + +func checkUplink(t *testing.T, upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, wantAck int, wantForward int, wantBroadcast int) { + mockUp := upAdapter.(*gtw_rtr_mock.Adapter) + mockDown := downAdapter.(*rtr_brk_mock.Adapter) + + if len(mockDown.Broadcasts) != wantBroadcast { + Ko(t, "Expected %d broadcast(s) but %d has/have been done", wantBroadcast, len(mockDown.Broadcasts)) + return + } + + if len(mockDown.Forwards) != wantForward { + Ko(t, "Expected %d forward(s) but %d has/have been done", wantForward, len(mockDown.Forwards)) + } + + if len(mockUp.Acks) != wantAck { + Ko(t, "Expected %d ack(s) but got %d", wantAck, len(mockUp.Acks)) + return + } + + Ok(t) +} From 2ff2800dfec25651d472f4a9179f92c7b5152d78 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 16:16:03 +0100 Subject: [PATCH 0242/2266] [router] Add log line in router.go --- components/router.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/router.go b/components/router.go index e5531aa35..83f788a41 100644 --- a/components/router.go +++ b/components/router.go @@ -115,7 +115,7 @@ func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress // 3. Broadcast or Forward payloads depending wether or not the brokers are known for devAddr, payload := range payloads { brokers, err := r.addressKeeper.lookup(devAddr) - if err != nil { + if err == nil { r.log("Forward payload to known brokers %+v", payload) r.down <- downMsg{ payload: *payload, @@ -140,6 +140,7 @@ func (r *Router) HandleDownlink(payload semtech.Payload, broker core.BrokerAddre // RegisterDevice implements the core.Router interface func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { r.ensure() + r.log("Register device %+x to brokers: %v", devAddr, broAddrs) r.addressKeeper.store(devAddr, broAddrs...) // TODO handle the error } From 368d0ccf7655418ea2244657513d06ebe5c7d5d1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 16:16:22 +0100 Subject: [PATCH 0243/2266] [router] Add tests for PUSH_DATA packets in router --- components/router_test.go | 64 +++++++++++++++---- testing/mock_adapters/rtr_brk_mock/adapter.go | 31 ++++----- 2 files changed, 62 insertions(+), 33 deletions(-) diff --git a/components/router_test.go b/components/router_test.go index e7b060adc..1e63da0d5 100644 --- a/components/router_test.go +++ b/components/router_test.go @@ -4,18 +4,22 @@ package components import ( + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/testing/mock_adapters/gtw_rtr_mock" "github.com/thethingsnetwork/core/testing/mock_adapters/rtr_brk_mock" + "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" "testing" + "time" ) // ----- A new router instance can be created an obtained from a constuctor func TestNewRouter(t *testing.T) { tests := []newRouterTest{ - {genBrokers(), nil}, + {genBrokers(2), nil}, {[]core.BrokerAddress{}, core.ErrBadOptions}, } @@ -37,52 +41,65 @@ func (test newRouterTest) run(t *testing.T) { // ----- A router can handle uplink packets func TestHandleUplink(t *testing.T) { + // Build tests + nbBrokers := 2 + brokers := genBrokers(nbBrokers) + router, upAdapter, downAdapter := genAdaptersAndRouter(t, brokers) + down := downAdapter.(*rtr_brk_mock.Adapter) + pushData := genPUSH_DATA() + down.Relations[*pushData.Payload.RXPK[0].DevAddr()] = brokers[1:] // Register the second broker as being in charge of device #1 + tests := []handleUplinkTest{ - {genPULL_DATA(), core.GatewayAddress("a1"), 1, 0, 0}, + {genPULL_DATA(), 1, 0, 0}, + {pushData, 1, 0, 3}, // PUSH_DATA generate a packet with 4 RXPK from 3 different devices + {pushData, 1, 1, 2}, // Now device #1 should be handled by broker #2 } for _, test := range tests { - test.run(t) + test.run(t, router, upAdapter, downAdapter) } } type handleUplinkTest struct { packet semtech.Packet - gateway core.GatewayAddress wantAck int wantForward int wantBroadcast int } -func (test handleUplinkTest) run(t *testing.T) { +func (test handleUplinkTest) run(t *testing.T, router core.Router, upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { // Describe - Desc(t, "Handle uplink packet %v from gateway %v", test.packet, test.gateway) + Desc(t, "Handle uplink packet %v", test.packet) // Build - router, upAdapter, downAdapter := genAdaptersAndRouter(t) + mockDown := downAdapter.(*rtr_brk_mock.Adapter) + mockDown.Forwards = make(map[semtech.DeviceAddress][]semtech.Payload) + mockDown.Broadcasts = make(map[semtech.DeviceAddress][]semtech.Payload) // Operate - router.HandleUplink(test.packet, test.gateway) + router.HandleUplink(test.packet, core.GatewayAddress("Gateway")) + <-time.After(time.Millisecond * 100) // Check checkUplink(t, upAdapter, downAdapter, test.wantAck, test.wantForward, test.wantBroadcast) } // ----- Build Utilities -func genBrokers() []core.BrokerAddress { - return []core.BrokerAddress{ - core.BrokerAddress("0.0.0.0:3000"), - core.BrokerAddress("0.0.0.0:3001"), +func genBrokers(n int) []core.BrokerAddress { + var brokers []core.BrokerAddress + for i := 0; i < n; i += 1 { + brokers = append(brokers, core.BrokerAddress(fmt.Sprintf("0.0.0.0:%d", 3000+i))) } + return brokers } -func genAdaptersAndRouter(t *testing.T) (core.Router, core.GatewayRouterAdapter, core.RouterBrokerAdapter) { - brokers := genBrokers() +func genAdaptersAndRouter(t *testing.T, brokers []core.BrokerAddress) (core.Router, core.GatewayRouterAdapter, core.RouterBrokerAdapter) { router, err := NewRouter(brokers...) if err != nil { panic(err) } + router.Logger = log.TestLogger{Tag: "Router", T: t} upAdapter := gtw_rtr_mock.New() downAdapter := rtr_brk_mock.New() @@ -102,6 +119,25 @@ func genPULL_DATA() semtech.Packet { } } +// genPUSH_DATA generate a a PUSH_DATA packet with 4 RXPK in payload coming from 3 different +// devices. +func genPUSH_DATA() semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_DATA, + Token: []byte{0x14, 0xba}, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqqq7uw==")}, // Device #1 + semtech.RXPK{Data: pointer.String("/7zN3u8BAAABqqv3RA==")}, // Device #2 + semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqmcSRQ==")}, // Device #1 + semtech.RXPK{Data: pointer.String("/wASo3YBAAAB+qpFeQ==")}, // Device #3 + }, + }, + } +} + // ----- Check Utilities func checkErrors(t *testing.T, want error, got error, router core.Router) { if want != got { diff --git a/testing/mock_adapters/rtr_brk_mock/adapter.go b/testing/mock_adapters/rtr_brk_mock/adapter.go index b0bf1e616..ad5a64829 100644 --- a/testing/mock_adapters/rtr_brk_mock/adapter.go +++ b/testing/mock_adapters/rtr_brk_mock/adapter.go @@ -12,12 +12,13 @@ import ( ) type Adapter struct { - Brokers []core.BrokerAddress // All known brokers with which the router is communicating - FailListen bool // If true, any call to Listen will fail with an error - FailBroadcast bool // If true, any call to Broadcast will trigger a core.ErrBroadcast - FailForward bool // If true, any call to Forward will trigger a core.ErrForward - Broadcasts map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through broadcasts - Forwards map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through forwards + Brokers []core.BrokerAddress // All known brokers with which the router is communicating + FailListen bool // If true, any call to Listen will fail with an error + FailBroadcast bool // If true, any call to Broadcast will trigger a core.ErrBroadcast + FailForward bool // If true, any call to Forward will trigger a core.ErrForward + Broadcasts map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through broadcasts + Forwards map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through forwards + Relations map[semtech.DeviceAddress][]core.BrokerAddress // Known association between brokers and device } // New constructs a new router <-> broker adapter interface @@ -28,6 +29,7 @@ func New() *Adapter { FailForward: false, Broadcasts: make(map[semtech.DeviceAddress][]semtech.Payload), Forwards: make(map[semtech.DeviceAddress][]semtech.Payload), + Relations: make(map[semtech.DeviceAddress][]core.BrokerAddress), } } @@ -50,20 +52,11 @@ func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { <-time.After(time.Millisecond * 50) a.Broadcasts[*devAddr] = append(a.Broadcasts[*devAddr], payload) - router.RegisterDevice(*devAddr, a.InChargeOf(payload, a.Brokers...)...) - return nil -} - -// InChargeOf returns a set of brokers in charge of a payload (result of simulating a broadcast -// operation). -func (a *Adapter) InChargeOf(payload semtech.Payload, broAddrs ...core.BrokerAddress) []core.BrokerAddress { - var brokers []core.BrokerAddress - for i, addr := range broAddrs { - if i%2 == 1 { - brokers = append(brokers, addr) - } + brokers, ok := a.Relations[*devAddr] + if ok { + router.RegisterDevice(*devAddr, brokers...) } - return brokers + return nil } // Forward implements the core.BrokerRouter interface From dbbcbc619fd6106378740cbee5fd54713f8fe724 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 3 Jan 2016 16:17:58 +0100 Subject: [PATCH 0244/2266] [router] Update Dev plan --- DEVELOPMENT_PLAN.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 949a7740b..2a58ae74b 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -35,8 +35,7 @@ system will just forward messages using pre-configured end-device addresses. - [x] Create Mock router - [x] Create Mock DownAdapter - [x] Create Mock UpAdatper - - [ ] Test'em all - - [ ] Reemit errored packet + - [x] Test'em all - [ ] Switch from local in-memory storage to Reddis - [x] UpAdapter - [x] Listen and forward incoming packets to Core router From 357ddd9f688e82e55c621587f0ae5ce236997140 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 4 Jan 2016 12:24:57 +0100 Subject: [PATCH 0245/2266] Add Slack badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 193bed0d6..61fef1fcc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ The Things Network Core Architecture ==================================== +[![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) + ## How to Contribute - Open an issue and explain on what you're working From 709501354425a7decd220eaf2c4ec46c63ff400a Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 11:53:29 +0100 Subject: [PATCH 0246/2266] Add some logs in adapters + change signature to return an instance that implements core interfaces --- adapters/gtw_rtr_udp/adapter.go | 4 ++-- adapters/gtw_rtr_udp/adapter_test.go | 8 ++++---- adapters/rtr_brk_http/adapter.go | 5 +++-- adapters/rtr_brk_http/adapter_test.go | 2 +- components/router.go | 2 ++ 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go index cfffed2be..c9cd1105f 100644 --- a/adapters/gtw_rtr_udp/adapter.go +++ b/adapters/gtw_rtr_udp/adapter.go @@ -23,10 +23,10 @@ type udpMsg struct { } // NewAdapter constructs a gateway <-> router udp adapter -func NewAdapter() Adapter { +func NewAdapter() *Adapter { a := Adapter{conn: make(chan udpMsg)} go a.monitorConnection() // Terminates that goroutine by closing the channel - return a + return &a } // ok controls whether or not the adapter has been initialized via NewAdapter() diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go index ce0604eb1..f776a3495 100644 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ b/adapters/gtw_rtr_udp/adapter_test.go @@ -72,7 +72,7 @@ type packetProcessingTest struct { func (test packetProcessingTest) run(t *testing.T) { Desc(t, "Simulate incoming datagram: %+v", test.in) adapter, router := generateAdapterAndRouter(t) - conn, gateway := createConnection(&adapter, router, test.port) + conn, gateway := createConnection(adapter, router, test.port) defer conn.Close() sendDatagram(conn, test.in) test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router @@ -104,7 +104,7 @@ func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway func TestSendAck(t *testing.T) { // 1. Initialize test data adapter, router := generateAdapterAndRouter(t) - conn, gateway := createConnection(&adapter, router, 3003) + conn, gateway := createConnection(adapter, router, 3003) defer conn.Close() tests := []sendAckTest{ @@ -120,7 +120,7 @@ func TestSendAck(t *testing.T) { } type sendAckTest struct { - adapter Adapter + adapter *Adapter router core.Router conn *net.UDPConn gateway core.GatewayAddress @@ -181,7 +181,7 @@ func TestConnectionRecovering(t *testing.T) { } // ----- Build Utilities -func generateAdapterAndRouter(t *testing.T) (Adapter, core.Router) { +func generateAdapterAndRouter(t *testing.T) (*Adapter, core.Router) { a := NewAdapter() a.Logger = log.TestLogger{Tag: "Adapter", T: t} return a, mock_components.NewRouter() diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go index 5e11fb1cf..9e51bb695 100644 --- a/adapters/rtr_brk_http/adapter.go +++ b/adapters/rtr_brk_http/adapter.go @@ -24,8 +24,8 @@ type Adapter struct { } // NewAdapter() constructs a new router <-> broker adapter -func NewAdapter() Adapter { - return Adapter{} +func NewAdapter() *Adapter { + return &Adapter{} } // Check whether or not the adapter has been initialized @@ -52,6 +52,7 @@ func (a *Adapter) Listen(router core.Router, options interface{}) error { return core.ErrBadOptions } + a.log("Start listening to brokers %v", a.brokers) return nil } diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go index c3d9fbe9c..05202716e 100644 --- a/adapters/rtr_brk_http/adapter_test.go +++ b/adapters/rtr_brk_http/adapter_test.go @@ -135,7 +135,7 @@ func (test broadcastPayloadTest) run(t *testing.T) { // ----- Build Utilities // Create an instance of an Adapter with a predefined logger + a mock router -func genAdapterAndRouter(t *testing.T) (Adapter, core.Router) { +func genAdapterAndRouter(t *testing.T) (*Adapter, core.Router) { a := NewAdapter() a.Logger = log.TestLogger{Tag: "Adapter", T: t} return a, mock_components.NewRouter() diff --git a/components/router.go b/components/router.go index 83f788a41..671ac5983 100644 --- a/components/router.go +++ b/components/router.go @@ -158,10 +158,12 @@ func (r *Router) HandleError(err interface{}) { func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { r.ensure() + r.log("Connect router to UpAdapter") for i := 0; i < UP_POOL_SIZE; i += 1 { go r.connectUpAdapter(upAdapter) } + r.log("Connect router to DownAdapter") for i := 0; i < DOWN_POOL_SIZE; i += 1 { go r.connectDownAdapter(downAdapter) } From c60f65c597fa46401888680a0a102176de83d554 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 11:53:51 +0100 Subject: [PATCH 0247/2266] Create basic binary example of router to be ship with Docker --- bin/router/main.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 bin/router/main.go diff --git a/bin/router/main.go b/bin/router/main.go new file mode 100644 index 000000000..b4c71fca8 --- /dev/null +++ b/bin/router/main.go @@ -0,0 +1,66 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// package router/main creates and runs an working instance of a router on the host machine +package main + +import ( + "flag" + . "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/adapters/gtw_rtr_udp" + "github.com/thethingsnetwork/core/adapters/rtr_brk_http" + "github.com/thethingsnetwork/core/components" + "github.com/thethingsnetwork/core/utils/log" + "strconv" + "strings" +) + +func main() { + brokers, udpPort := parseOptions() + router, err := components.NewRouter(brokers...) + if err != nil { + panic(err) + } + router.Logger = log.DebugLogger{Tag: "router"} + + upAdapter := gtw_rtr_udp.NewAdapter() + upAdapter.Logger = log.DebugLogger{Tag: "upAdapter"} + + downAdapter := rtr_brk_http.NewAdapter() + downAdapter.Logger = log.DebugLogger{Tag: "downAdapter"} + + router.Connect(upAdapter, downAdapter) + if err := upAdapter.Listen(router, uint(udpPort)); err != nil { + panic(err) + } + if err := downAdapter.Listen(router, brokers); err != nil { + panic(err) + } + + <-make(chan bool) +} + +func parseOptions() (brokers []BrokerAddress, udpPort uint64) { + var brokersFlag string + var udpPortFlag string + flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. + For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000 + `) + flag.StringVar(&udpPortFlag, "udpPort", "", "Udp port on which the router should listen to.") + flag.Parse() + + var err error + udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) + if err != nil { + panic(err) + } + if brokersFlag == "" { + panic("Need to provide at least one broker address") + } + + brokersStr := strings.Split(brokersFlag, ",") + for i := range brokersStr { + brokers = append(brokers, BrokerAddress(strings.Trim(brokersStr[i], " "))) + } + return +} From e559d6c079615aeb6e64aa4654927fe7a4eb7b39 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 15:43:39 +0100 Subject: [PATCH 0248/2266] [simulators.gateway] Add next random generators utils for RXPK & TXPK --- simulators/gateway/utils.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 5cf4ae953..4867fd2b9 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -6,8 +6,11 @@ package gateway import ( "bytes" "encoding/binary" + "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/utils/pointer" "math/rand" + "time" ) func genToken() []byte { @@ -41,3 +44,30 @@ func generateFreq() float64 { // EU 863-870MHz return rand.Float64()*7 + 863.0 } + +func generateDatr() string { + // Spread Factor from 12 to 7 + sf := 12 - rand.Intn(7) + var bw int + if sf == 6 { + // DR6 -> SF7@250Khz + sf = 7 + bw = 250 + } else { + bw = 125 + } + return fmt.Sprintf("SF%dBW%d", sf, bw) +} + +func generateCodr() string { + d := rand.Intn(4) + 5 + return fmt.Sprintf("4/%d", d) +} + +func generateLsnr() float64 { + return 0.0 +} + +func generateData() string { + return "" +} From 6396c095daf9ad66fc7ae67f475c3de0257a9daf Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 15:44:02 +0100 Subject: [PATCH 0249/2266] [simulators.gateway] Add generateRXPK() to generate fake RXPK packet --- simulators/gateway/utils.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 4867fd2b9..4c0de50d5 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -71,3 +71,21 @@ func generateLsnr() float64 { func generateData() string { return "" } + +func generateRXPK() semtech.RXPK { + now := time.Now() + return semtech.RXPK{ + Time: &now, + Tmst: pointer.Uint(uint(now.UnixNano())), + Freq: pointer.Float64(generateFreq()), + Chan: pointer.Uint(0), // Irrelevant + Rfch: pointer.Uint(0), // Irrelevant + Stat: pointer.Int(1), // Assuming CRC was ok + Modu: pointer.String("LORA"), // For now, only consider LORA modulation + Datr: pointer.String(generateDatr()), // Arbitrary + Codr: pointer.String("4/5"), // Arbitrary + Rssi: pointer.Int(generateRssi()), // Arbitrary + Lsnr: pointer.Float64(generateLsnr()), // Arbitrary + Data: pointer.String(generateData()), // Arbitrary + } +} From 8c0cf21459c7eebd6fde676bd736f2e882fc5878 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 16:10:03 +0100 Subject: [PATCH 0250/2266] [simulators.gateway] Implement generateLsnr and test it. Also adjust generation of Rssi --- simulators/gateway/utils.go | 15 ++++++++++++--- simulators/gateway/utils_test.go | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 4c0de50d5..ed531e830 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -9,6 +9,7 @@ import ( "fmt" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/pointer" + "math" "math/rand" "time" ) @@ -35,16 +36,20 @@ func ackToken(index int, packet semtech.Packet) [4]byte { return [4]byte{id, kind, packet.Token[0], packet.Token[1]} } +// Generates RSSI signal between -120 < rssi < 0 func generateRssi() int { - x := float32(rand.Int31()) / float32(2e8) - return -int(x * x) + // Generate RSSI. Tend towards generating great signal strength. + x := float64(rand.Int31()) * float64(2e-9) + return int(-1.6 * math.Exp(x)) } +// Generates a frequency between 863.0 and 870.0 Mhz func generateFreq() float64 { // EU 863-870MHz return rand.Float64()*7 + 863.0 } +// Generates Datr for instance: SF4BW125 func generateDatr() string { // Spread Factor from 12 to 7 sf := 12 - rand.Intn(7) @@ -59,15 +64,19 @@ func generateDatr() string { return fmt.Sprintf("SF%dBW%d", sf, bw) } +// Generates Codr for instance: 4/6 func generateCodr() string { d := rand.Intn(4) + 5 return fmt.Sprintf("4/%d", d) } +// Generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise func generateLsnr() float64 { - return 0.0 + x := float64(rand.Int31()) * float64(2e-9) + return math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10 } +// Generates fake data from a device func generateData() string { return "" } diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index adbcba04f..8eae6af71 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -83,6 +83,7 @@ func TestGenerateRssi(t *testing.T) { So(rssi, ShouldBeGreaterThanOrEqualTo, -120) So(rssi, ShouldBeLessThanOrEqualTo, 0) values[rssi] = true + t.Log(rssi) } So(len(values), ShouldBeGreaterThan, 5) }) @@ -96,6 +97,21 @@ func TestGenerateFreq(t *testing.T) { So(freq, ShouldBeGreaterThanOrEqualTo, 863.0) So(freq, ShouldBeLessThanOrEqualTo, 870.0) values[freq] = true + t.Log(freq) + } + So(len(values), ShouldBeGreaterThan, 5) + }) +} + +func TestGenerateLsnr(t *testing.T) { + Convey("The generateLsnr() function should generate random snr ratio between 5.5 and -2", t, func() { + values := make(map[float64]bool) + for i := 0; i < 10; i += 1 { + lsnr := generateLsnr() + So(lsnr, ShouldBeGreaterThanOrEqualTo, -2) + So(lsnr, ShouldBeLessThanOrEqualTo, 5.5) + values[lsnr] = true + t.Log(lsnr) } So(len(values), ShouldBeGreaterThan, 5) }) From 3ee33825e66d290cc809f38ccc4d1d3d4553eb9c Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 18:45:50 +0100 Subject: [PATCH 0251/2266] [simulators.gateway] Change computation of DevAddr: Data base64 encoding does not contain padding --- components/router_test.go | 8 ++++---- lorawan/semtech/semtech.go | 24 ++++++++++++++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/components/router_test.go b/components/router_test.go index 1e63da0d5..8699ba412 100644 --- a/components/router_test.go +++ b/components/router_test.go @@ -129,10 +129,10 @@ func genPUSH_DATA() semtech.Packet { GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, Payload: &semtech.Payload{ RXPK: []semtech.RXPK{ - semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqqq7uw==")}, // Device #1 - semtech.RXPK{Data: pointer.String("/7zN3u8BAAABqqv3RA==")}, // Device #2 - semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqmcSRQ==")}, // Device #1 - semtech.RXPK{Data: pointer.String("/wASo3YBAAAB+qpFeQ==")}, // Device #3 + semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqqq7uw")}, // Device #1 + semtech.RXPK{Data: pointer.String("/7zN3u8BAAABqqv3RA")}, // Device #2 + semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqmcSRQ")}, // Device #1 + semtech.RXPK{Data: pointer.String("/wASo3YBAAAB+qpFeQ")}, // Device #3 }, }, } diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go index cd37802c5..5beddbc55 100644 --- a/lorawan/semtech/semtech.go +++ b/lorawan/semtech/semtech.go @@ -42,7 +42,17 @@ func (rxpk *RXPK) DevAddr() *DeviceAddress { return nil } - buf, err := base64.StdEncoding.DecodeString(*rxpk.Data) + encoded := *rxpk.Data + switch len(encoded) % 4 { // Data does not contain encoding padding + case 1: + return nil + case 2: + encoded += "==" + case 3: + encoded += "=" + } + + buf, err := base64.StdEncoding.DecodeString(encoded) if err != nil || len(buf) < 5 { return nil } @@ -83,7 +93,17 @@ func (txpk *TXPK) DevAddr() *DeviceAddress { return nil } - buf, err := base64.StdEncoding.DecodeString(*txpk.Data) + encoded := *txpk.Data + switch len(encoded) % 4 { // Data does not contain encoding padding + case 1: + return nil + case 2: + encoded += "==" + case 3: + encoded += "=" + } + + buf, err := base64.StdEncoding.DecodeString(encoded) if err != nil || len(buf) < 5 { return nil } From 718f336190d751198a3d7a45daee39fe3ccf3b37 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 19:13:17 +0100 Subject: [PATCH 0252/2266] [simulators.gateway] Add fake accessor for Network Session Key. Ideally, should be stored in ENV --- core.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core.go b/core.go index 1426a1278..7e3245d87 100644 --- a/core.go +++ b/core.go @@ -65,3 +65,7 @@ type RouterBrokerAdapter interface { // ave been queried before and are known as dedicated brokers for the related end-device. Forward(router Router, payload Payload, broAddrs ...BrokerAddress) error } + +func GetNwSKey() [16]byte { + return [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} +} From 1267c156900dc5273e40002cfdacf48cdb4ab272 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 19:13:36 +0100 Subject: [PATCH 0253/2266] [simulators.gateway] Add data and RXPK generation to forwarder utils --- simulators/gateway/utils.go | 58 ++++++++++++++++++++++++++------ simulators/gateway/utils_test.go | 8 +++++ 2 files changed, 55 insertions(+), 11 deletions(-) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index ed531e830..d2ba99f1e 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -5,12 +5,16 @@ package gateway import ( "bytes" + "encoding/base64" "encoding/binary" "fmt" + "github.com/brocaar/lorawan" + "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan/semtech" "github.com/thethingsnetwork/core/utils/pointer" "math" "math/rand" + "strings" "time" ) @@ -77,8 +81,40 @@ func generateLsnr() float64 { } // Generates fake data from a device -func generateData() string { - return "" +func generateData(frmData string) string { + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: generateDevAddr(), + FCtrl: lorawan.FCtrl{}, + FCnt: 0, + } + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: []byte(frmData), + }} + macPayload.FPort = 14 + + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + phyPayload.SetMIC(core.GetNwSKey()) + + raw, err := phyPayload.MarshalBinary() + if err != nil { // Shouldn't be + panic(err) + } + return strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") +} + +// Generate a random device address +func generateDevAddr() lorawan.DevAddr { + devAddr := [4]byte{} + token := new(bytes.Buffer) + binary.Write(token, binary.LittleEndian, time.Now().UnixNano()) + copy(devAddr[:], token.Bytes()[:4]) + return lorawan.DevAddr(devAddr) } func generateRXPK() semtech.RXPK { @@ -87,14 +123,14 @@ func generateRXPK() semtech.RXPK { Time: &now, Tmst: pointer.Uint(uint(now.UnixNano())), Freq: pointer.Float64(generateFreq()), - Chan: pointer.Uint(0), // Irrelevant - Rfch: pointer.Uint(0), // Irrelevant - Stat: pointer.Int(1), // Assuming CRC was ok - Modu: pointer.String("LORA"), // For now, only consider LORA modulation - Datr: pointer.String(generateDatr()), // Arbitrary - Codr: pointer.String("4/5"), // Arbitrary - Rssi: pointer.Int(generateRssi()), // Arbitrary - Lsnr: pointer.Float64(generateLsnr()), // Arbitrary - Data: pointer.String(generateData()), // Arbitrary + Chan: pointer.Uint(0), // Irrelevant + Rfch: pointer.Uint(0), // Irrelevant + Stat: pointer.Int(1), // Assuming CRC was ok + Modu: pointer.String("LORA"), // For now, only consider LORA modulation + Datr: pointer.String(generateDatr()), // Arbitrary + Codr: pointer.String("4/5"), // Arbitrary + Rssi: pointer.Int(generateRssi()), // Arbitrary + Lsnr: pointer.Float64(generateLsnr()), // Arbitrary + Data: pointer.String(generateData("RXPKData")), // Arbitrary } } diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 8eae6af71..f53396b1d 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -116,3 +116,11 @@ func TestGenerateLsnr(t *testing.T) { So(len(values), ShouldBeGreaterThan, 5) }) } + +func TestGenerateRXPK(t *testing.T) { + Convey("The generateRXPK() function should generate a valid RXPK holding data from a device", t, func() { + rxpk := generateRXPK() + devAddr := rxpk.DevAddr() + So(devAddr, ShouldNotBeNil) + }) +} From a22ec4aef20a4d2cb374ebfbcc61bee45ec987ae Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 4 Jan 2016 19:15:32 +0100 Subject: [PATCH 0254/2266] [simulators.gateway] Update development plan --- DEVELOPMENT_PLAN.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 2a58ae74b..3b0014d39 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -12,8 +12,7 @@ mainly for testing and ensuring the correctness of other components. - [x] Serialize json rxpk/stat object(s) - [x] Generate json rxpl/stat object(s) - [x] Update gateway statistics accordingly - - [ ] Load config from file or ENV - - [ ] Generate valid fake end-device packet + - [x] Generate valid fake end-device packet - [ ] Simulate fake end-devices traffic - [ ] Complete tests set for stats functions - [ ] Implements UDP ReadWriteCloser From 1ef5ba4a045ed1ba5e75fe3fafccc2bd5cea5b7a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 5 Jan 2016 11:47:16 +0100 Subject: [PATCH 0255/2266] [broker] Draft dev plan for next parts --- DEVELOPMENT_PLAN.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 3b0014d39..adf7b1247 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -25,8 +25,8 @@ Handle an uplink process that can forward packet coming from a gateway to a simp system will just forward messages using pre-configured end-device addresses. -- [ ] Basic Router - - [ ] Core +- [x] Basic Router + - [x] Core - [x] Lookup for device address (only local) - [x] Invalidate broker periodically (only local) - [x] Acknowledge packet from gateway @@ -35,7 +35,6 @@ system will just forward messages using pre-configured end-device addresses. - [x] Create Mock DownAdapter - [x] Create Mock UpAdatper - [x] Test'em all - - [ ] Switch from local in-memory storage to Reddis - [x] UpAdapter - [x] Listen and forward incoming packets to Core router - [x] Keep track of existing UDP connections @@ -44,14 +43,32 @@ system will just forward messages using pre-configured end-device addresses. - [x] Listen and forward incoming packet to Core router - [x] Broadcast a packet to several brokers - [x] Send packet to given brokers (same as above ?) + - [ ] Bonus + - [ ] Switch from local in-memory storage to Reddis - [ ] Basic Broker - - [ ] Detail the list of features + - [ ] Core + - [ ] Lookup for associated device from Network Controller + - [ ] Send acknowledgement or rejection for given packet + - [ ] If necessary, forward packet to right handler + - [ ] Associate handlers to static device addresses + - [ ] Router adapter + - [ ] Listen to http request and forward valid request to core + - [ ] Respond with 200 OK (ack) or 404 Not Found (nack) + - [ ] Network Controller adapter + - [ ] Lookup for device from a packet + - [ ] Send back packet that are indeed handled by a handler + - [ ] Handler adapter + - [ ] Send packet to handler + - [ ] Accept registrations of static devAddr from handlers + - [ ] Minimalist Dumb Network-Controller - - [ ] Detail the list of features + - [ ] Retrieve corresponding key (appSKey or nwskey) + - [ ] Compute MIC check of received packet + ## Milestone 3 Support application registration for personalization. Applications provide a list of From f94d8f92ced60eb13ab2b4117cd574e6924fa456 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 10:56:10 +0100 Subject: [PATCH 0256/2266] [broker] Give a try to refactored archi + start broker backbone --- core.go | 95 ++++++++++++++------------------- refactored_components/broker.go | 22 ++++++++ 2 files changed, 63 insertions(+), 54 deletions(-) create mode 100644 refactored_components/broker.go diff --git a/core.go b/core.go index 7e3245d87..724964abb 100644 --- a/core.go +++ b/core.go @@ -1,71 +1,58 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - package core import ( - "fmt" - . "github.com/thethingsnetwork/core/lorawan/semtech" + "encoding" + "encoding/json" ) -type BrokerAddress string -type GatewayAddress string - -type Router interface { - // HandlerError manages all kind of error that occur during the router lifecycle - HandleError(err interface{}) - - // HandleUplink manages uplink packets coming from a gateway - HandleUplink(packet Packet, gateway GatewayAddress) - - // HandleDownlink manages downlink packets coming from a broker - HandleDownlink(payload Payload, broker BrokerAddress) - - // RegisterDevice associates a device address to a set of brokers for a given period - RegisterDevice(devAddr DeviceAddress, broAddrs ...BrokerAddress) - - // Connect the router to its adapters - Connect(upAdapter GatewayRouterAdapter, downAdapter RouterBrokerAdapter) +type Packet struct { + Addressable + Metadata Metadata + Payload PHYPayload } -// The error types belows are going to be more complex in order to handle custom behavior for -// each error type. -var ErrBadOptions error = fmt.Errorf("Unreckonized or invalid options") -var ErrNotInitialized error = fmt.Errorf("Structure not initialized") -var ErrBadGatewayAddress error = fmt.Errorf("Invalid gateway address") -var ErrMissingConnection error = fmt.Errorf("Can't proceed without establishing connection") -var ErrInvalidPacket error = fmt.Errorf("Invalid semtech packet") -var ErrInvalidPayload error = fmt.Errorf("Invalid semtech payload") -var ErrBroadcast error = fmt.Errorf("Unable to broadcast the given payload") -var ErrForward error = fmt.Errorf("Unable to forward the given payload") +type Addressable interface { + DevAddr() [4]byte +} -type Adapter interface { - // Establish the adapter connection, whatever protocol is being used. - Listen(router Router, options interface{}) error +type Recipient struct { + Address interface{} + Id interface{} } -type GatewayRouterAdapter interface { - Adapter - // Ack allows the router to send back a response to a gateway. The name of the method is quite a - // bad call and will probably change soon. - Ack(router Router, packet Packet, gateway GatewayAddress) error +type Metadata interface { + json.Marshaler + json.Unmarshaler + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler } -type RouterBrokerAdapter interface { - Adapter +type PHYPayload interface { + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler + DecryptMACPayload(key [16]byte) error + EncryptMACPayload(key [16]byte) error + SetMIC(key [16]byte) error + ValidateMic(key [16]byte) (bool, error) +} - // Broadcast makes the adapter discover all available brokers by sending them a the given packets. - // - // We assume that broadcast is also registering a device address towards the router depending - // on the brokers responses. - Broadcast(router Router, payload Payload) error +type AckNacker interface { + Ack(p Packet) error + Nack(p Packet) error +} - // Forward is an explicit forwarding of a packet which is known being handled by a set of - // brokers. None of the contacted broker is supposed to reject the incoming payload; They all - // ave been queried before and are known as dedicated brokers for the related end-device. - Forward(router Router, payload Payload, broAddrs ...BrokerAddress) error +type Component interface { + Handle(p Packet, an AckNacker) error + NextUp() (*Packet, error) + NextDown() (*Packet, error) } -func GetNwSKey() [16]byte { - return [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} +type Adapter interface { + Send(p Packet, r ...Recipient) error + Next() (*Packet, *AckNacker, error) } + +type Router Component +type Broker Component +type Handler Component +type NetworkController Component diff --git a/refactored_components/broker.go b/refactored_components/broker.go new file mode 100644 index 000000000..404a10f1c --- /dev/null +++ b/refactored_components/broker.go @@ -0,0 +1,22 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/thethingsnetwork/core" +) + +type Broker struct{} + +func (b *Broker) NextUp() (*core.Packet, error) { + return nil, nil +} + +func (b *Broker) NextDown() (*core.Packet, error) { + return nil, nil +} + +func (b *Broker) Handle(p core.Packet, an core.AckNacker) error { + return nil +} From 69af6965100ad6a96cc4573f591d53cef253de0a Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 11:14:20 +0100 Subject: [PATCH 0257/2266] [broker] Rename lorawan/semtech in semtech. --- lorawan/semtech/semtech.go | 188 ------------------ {lorawan/semtech => semtech}/decode.go | 0 {lorawan/semtech => semtech}/decode_test.go | 0 {lorawan/semtech => semtech}/encode.go | 0 {lorawan/semtech => semtech}/encode_test.go | 0 {lorawan/semtech => semtech}/proxies.go | 0 semtech/semtech.go | 93 +++++++++ .../test_data/marshal_rxpk | 0 .../test_data/marshal_rxpk_stat | 0 .../test_data/marshal_stat | 0 .../test_data/marshal_txpk | 0 11 files changed, 93 insertions(+), 188 deletions(-) delete mode 100644 lorawan/semtech/semtech.go rename {lorawan/semtech => semtech}/decode.go (100%) rename {lorawan/semtech => semtech}/decode_test.go (100%) rename {lorawan/semtech => semtech}/encode.go (100%) rename {lorawan/semtech => semtech}/encode_test.go (100%) rename {lorawan/semtech => semtech}/proxies.go (100%) create mode 100644 semtech/semtech.go rename {lorawan/semtech => semtech}/test_data/marshal_rxpk (100%) rename {lorawan/semtech => semtech}/test_data/marshal_rxpk_stat (100%) rename {lorawan/semtech => semtech}/test_data/marshal_stat (100%) rename {lorawan/semtech => semtech}/test_data/marshal_txpk (100%) diff --git a/lorawan/semtech/semtech.go b/lorawan/semtech/semtech.go deleted file mode 100644 index 5beddbc55..000000000 --- a/lorawan/semtech/semtech.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package semtech provides useful methods and types to handle communications with a gateway. -// -// This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT -package semtech - -import ( - "encoding/base64" - "fmt" - "time" -) - -type DeviceAddress [4]byte - -// RXPK represents an uplink json message format sent by the gateway -type RXPK struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) - devAddr *DeviceAddress // End-Device address, according to the Data. Memoized here. -} - -// DevAddr returns the end-device address described in the payload -func (rxpk *RXPK) DevAddr() *DeviceAddress { - if rxpk.devAddr != nil { - return rxpk.devAddr - } - - if rxpk.Data == nil { - return nil - } - - encoded := *rxpk.Data - switch len(encoded) % 4 { // Data does not contain encoding padding - case 1: - return nil - case 2: - encoded += "==" - case 3: - encoded += "=" - } - - buf, err := base64.StdEncoding.DecodeString(encoded) - if err != nil || len(buf) < 5 { - return nil - } - - rxpk.devAddr = new(DeviceAddress) - copy((*rxpk.devAddr)[:], buf[1:5]) // Device Address corresponds to the first 4 bytes of the Frame Header, after one byte of MAC_HEADER - return rxpk.devAddr -} - -// TXPK represents a downlink j,omitemptyson message format received by the gateway. -// Most field are optional. -type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) - devAddr *DeviceAddress // End-Device address, according to the Data. Memoized here. -} - -// DevAddr returns the end-device address described in the payload -func (txpk *TXPK) DevAddr() *DeviceAddress { - if txpk.devAddr != nil { - return txpk.devAddr - } - - if txpk.Data == nil { - return nil - } - - encoded := *txpk.Data - switch len(encoded) % 4 { // Data does not contain encoding padding - case 1: - return nil - case 2: - encoded += "==" - case 3: - encoded += "=" - } - - buf, err := base64.StdEncoding.DecodeString(encoded) - if err != nil || len(buf) < 5 { - return nil - } - - txpk.devAddr = new(DeviceAddress) - copy((*txpk.devAddr)[:], buf[1:5]) // Device Address corresponds to the first 4 bytes of the Frame Header, after one byte of MAC_HEADER - return txpk.devAddr -} - -// Stat represents a status json message format sent by the gateway -type Stat struct { - Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged - Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) - Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) - Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) -} - -// Packet as seen by the gateway. -type Packet struct { - Version byte // Protocol version, should always be 1 here - Token []byte // Random number generated by the gateway on some request. 2-bytes long. - Identifier byte // Packet's command identifier - GatewayId []byte // Source gateway's identifier (Only PULL_DATA and PUSH_DATA) - Payload *Payload // JSON payload transmitted if any, nil otherwise -} - -// Payload refers to the JSON payload sent by a gateway or a server. -type Payload struct { - Raw []byte `json:"-"` // The raw unparsed response - RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any - Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any - TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any -} - -// UniformDevAddr tries to extract a device address from the different part of a payload. If the -// payload is composed of messages coming from several end-device, the method will fail. -func (p Payload) UniformDevAddr() (*DeviceAddress, error) { - var devAddr *DeviceAddress - - // Determine the devAddress associated to that payload - if p.RXPK == nil || len(p.RXPK) == 0 { - if p.TXPK == nil { - return nil, fmt.Errorf("Unable to determine device address. No RXPK neither TXPK messages") - } - if devAddr = p.TXPK.DevAddr(); devAddr == nil { - return nil, fmt.Errorf("Unable to determine device address from TXPK") - } - - } else { - // We check them all to be sure, but all RXPK should refer to the same End-Device - for _, rxpk := range p.RXPK { - addr := rxpk.DevAddr() - if addr == nil { - return nil, fmt.Errorf("Unable to determine uniform address of given payload") - } - - if devAddr != nil && *devAddr != *addr { - return nil, fmt.Errorf("Payload is composed of messages from several end-devices") - } - devAddr = addr - } - } - return devAddr, nil -} - -// Available packet commands -const ( - PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data - PUSH_ACK // Sent by the gateway's recipient in response to a PUSH_DATA - PULL_DATA // Sent periodically by the gateway to keep a connection open - PULL_RESP // Sent by the gateway's recipient to transmit back data to the Gateway - PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA -) - -// Protocol version in use -const VERSION = 0x01 diff --git a/lorawan/semtech/decode.go b/semtech/decode.go similarity index 100% rename from lorawan/semtech/decode.go rename to semtech/decode.go diff --git a/lorawan/semtech/decode_test.go b/semtech/decode_test.go similarity index 100% rename from lorawan/semtech/decode_test.go rename to semtech/decode_test.go diff --git a/lorawan/semtech/encode.go b/semtech/encode.go similarity index 100% rename from lorawan/semtech/encode.go rename to semtech/encode.go diff --git a/lorawan/semtech/encode_test.go b/semtech/encode_test.go similarity index 100% rename from lorawan/semtech/encode_test.go rename to semtech/encode_test.go diff --git a/lorawan/semtech/proxies.go b/semtech/proxies.go similarity index 100% rename from lorawan/semtech/proxies.go rename to semtech/proxies.go diff --git a/semtech/semtech.go b/semtech/semtech.go new file mode 100644 index 000000000..33c5775c5 --- /dev/null +++ b/semtech/semtech.go @@ -0,0 +1,93 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package semtech provides useful methods and types to handle communications with a gateway. +// +// This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT +package semtech + +import ( + "time" +) + +type DeviceAddress [4]byte + +// RXPK represents an uplink json message format sent by the gateway +type RXPK struct { + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +// TXPK represents a downlink j,omitemptyson message format received by the gateway. +// Most field are optional. +type TXPK struct { + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) +} + +// Stat represents a status json message format sent by the gateway +type Stat struct { + Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged + Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) + Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) + Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC + Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) +} + +// Packet as seen by the gateway. +type Packet struct { + Version byte // Protocol version, should always be 1 here + Token []byte // Random number generated by the gateway on some request. 2-bytes long. + Identifier byte // Packet's command identifier + GatewayId []byte // Source gateway's identifier (Only PULL_DATA and PUSH_DATA) + Payload *Payload // JSON payload transmitted if any, nil otherwise +} + +// Payload refers to the JSON payload sent by a gateway or a server. +type Payload struct { + Raw []byte `json:"-"` // The raw unparsed response + RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any + Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any + TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any +} + +// Available packet commands +const ( + PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data + PUSH_ACK // Sent by the gateway's recipient in response to a PUSH_DATA + PULL_DATA // Sent periodically by the gateway to keep a connection open + PULL_RESP // Sent by the gateway's recipient to transmit back data to the Gateway + PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA +) + +// Protocol version in use +const VERSION = 0x01 diff --git a/lorawan/semtech/test_data/marshal_rxpk b/semtech/test_data/marshal_rxpk similarity index 100% rename from lorawan/semtech/test_data/marshal_rxpk rename to semtech/test_data/marshal_rxpk diff --git a/lorawan/semtech/test_data/marshal_rxpk_stat b/semtech/test_data/marshal_rxpk_stat similarity index 100% rename from lorawan/semtech/test_data/marshal_rxpk_stat rename to semtech/test_data/marshal_rxpk_stat diff --git a/lorawan/semtech/test_data/marshal_stat b/semtech/test_data/marshal_stat similarity index 100% rename from lorawan/semtech/test_data/marshal_stat rename to semtech/test_data/marshal_stat diff --git a/lorawan/semtech/test_data/marshal_txpk b/semtech/test_data/marshal_txpk similarity index 100% rename from lorawan/semtech/test_data/marshal_txpk rename to semtech/test_data/marshal_txpk From 40b30191802bf8d95e24124165d373278eadcb10 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 11:16:50 +0100 Subject: [PATCH 0258/2266] [broker] Add lorawan package from Orne Brocaar --- .gitmodules | 3 +++ lorawan | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 lorawan diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..c411a134d --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lorawan"] + path = lorawan + url = git@github.com:TheThingsNetwork/lorawan diff --git a/lorawan b/lorawan new file mode 160000 index 000000000..8089543fb --- /dev/null +++ b/lorawan @@ -0,0 +1 @@ +Subproject commit 8089543fb4bf52ed97539621ca43ea2ce33f907f From e603bcbe17f6091b22766124c9f793f7eabc0ccd Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 14:02:04 +0100 Subject: [PATCH 0259/2266] [broker] Expose Datrparser and Timeparser --- semtech/decode.go | 18 +++++++++--------- semtech/encode.go | 30 +++++++++++++++--------------- semtech/proxies.go | 22 +++++++++++----------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/semtech/decode.go b/semtech/decode.go index ced8566a5..e4558a5ac 100644 --- a/semtech/decode.go +++ b/semtech/decode.go @@ -55,7 +55,7 @@ func Unmarshal(raw []byte) (*Packet, error) { } // UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (t *timeparser) UnmarshalJSON(raw []byte) error { +func (t *Timeparser) UnmarshalJSON(raw []byte) error { str := strings.Trim(string(raw), `"`) v, err := time.Parse("2006-01-02 15:04:05 GMT", str) if err != nil { @@ -67,19 +67,19 @@ func (t *timeparser) UnmarshalJSON(raw []byte) error { if err != nil { return errors.New("Unkown date format. Unable to parse time") } - t.value = &v + t.Value = &v return nil } // UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (d *datrparser) UnmarshalJSON(raw []byte) error { +func (d *Datrparser) UnmarshalJSON(raw []byte) error { v := strings.Trim(string(raw), `"`) if v == "" { return errors.New("Invalid datr format") } - d.value = &v + d.Value = &v return nil } @@ -96,27 +96,27 @@ func (p *Payload) UnmarshalJSON(raw []byte) error { if proxy.ProxStat.Stat != nil { if proxy.ProxStat.Time != nil { - proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.value + proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.Value } p.Stat = proxy.ProxStat.Stat } if proxy.ProxTXPK.TXPK != nil { if proxy.ProxTXPK.Time != nil { - proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.value + proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.Value } if proxy.ProxTXPK.Datr != nil { - proxy.ProxTXPK.TXPK.Datr = proxy.ProxTXPK.Datr.value + proxy.ProxTXPK.TXPK.Datr = proxy.ProxTXPK.Datr.Value } p.TXPK = proxy.ProxTXPK.TXPK } for _, rxpk := range proxy.ProxRXPK { if rxpk.Time != nil { - rxpk.RXPK.Time = rxpk.Time.value + rxpk.RXPK.Time = rxpk.Time.Value } if rxpk.Datr != nil { - rxpk.RXPK.Datr = rxpk.Datr.value + rxpk.RXPK.Datr = rxpk.Datr.Value } p.RXPK = append(p.RXPK, *rxpk.RXPK) } diff --git a/semtech/encode.go b/semtech/encode.go index 34eb71f35..3e81e96df 100644 --- a/semtech/encode.go +++ b/semtech/encode.go @@ -43,23 +43,23 @@ func Marshal(packet Packet) ([]byte, error) { } // MarshalJSON implements the Marshaler interface from encoding/json -func (t *timeparser) MarshalJSON() ([]byte, error) { - if t.value == nil { +func (t *Timeparser) MarshalJSON() ([]byte, error) { + if t.Value == nil { return nil, errors.New("Cannot marshal a null time") } - return append(append([]byte(`"`), []byte(t.value.Format(t.layout))...), []byte(`"`)...), nil + return append(append([]byte(`"`), []byte(t.Value.Format(t.Layout))...), []byte(`"`)...), nil } // MarshalJSON implements the Marshaler interface from encoding/json -func (d *datrparser) MarshalJSON() ([]byte, error) { - if d.value == nil { +func (d *Datrparser) MarshalJSON() ([]byte, error) { + if d.Value == nil { return nil, errors.New("Cannot marshal a null datr") } - if d.kind == "uint" { - return []byte(*d.value), nil + if d.Kind == "uint" { + return []byte(*d.Value), nil } - return append(append([]byte(`"`), []byte(*d.value)...), []byte(`"`)...), nil + return append(append([]byte(`"`), []byte(*d.Value)...), []byte(`"`)...), nil } // MarshalJSON implements the Marshaler interface from encoding/json @@ -70,7 +70,7 @@ func (p *Payload) MarshalJSON() ([]byte, error) { proxStat = new(statProxy) proxStat.Stat = p.Stat if p.Stat.Time != nil { - proxStat.Time = &timeparser{layout: "2006-01-02 15:04:05 GMT", value: p.Stat.Time} + proxStat.Time = &Timeparser{Layout: "2006-01-02 15:04:05 GMT", Value: p.Stat.Time} } } @@ -81,17 +81,17 @@ func (p *Payload) MarshalJSON() ([]byte, error) { proxr.RXPK = new(RXPK) *proxr.RXPK = rxpk if rxpk.Time != nil { - proxr.Time = &timeparser{time.RFC3339Nano, rxpk.Time} + proxr.Time = &Timeparser{time.RFC3339Nano, rxpk.Time} } if rxpk.Modu != nil && rxpk.Datr != nil { switch *rxpk.Modu { case "FSK": - proxr.Datr = &datrparser{kind: "uint", value: rxpk.Datr} + proxr.Datr = &Datrparser{Kind: "uint", Value: rxpk.Datr} case "LORA": fallthrough default: - proxr.Datr = &datrparser{kind: "string", value: rxpk.Datr} + proxr.Datr = &Datrparser{Kind: "string", Value: rxpk.Datr} } } proxRXPK = append(proxRXPK, *proxr) @@ -103,16 +103,16 @@ func (p *Payload) MarshalJSON() ([]byte, error) { proxTXPK = new(txpkProxy) proxTXPK.TXPK = p.TXPK if p.TXPK.Time != nil { - proxTXPK.Time = &timeparser{time.RFC3339Nano, p.TXPK.Time} + proxTXPK.Time = &Timeparser{time.RFC3339Nano, p.TXPK.Time} } if p.TXPK.Modu != nil && p.TXPK.Datr != nil { switch *p.TXPK.Modu { case "FSK": - proxTXPK.Datr = &datrparser{kind: "uint", value: p.TXPK.Datr} + proxTXPK.Datr = &Datrparser{Kind: "uint", Value: p.TXPK.Datr} case "LORA": fallthrough default: - proxTXPK.Datr = &datrparser{kind: "string", value: p.TXPK.Datr} + proxTXPK.Datr = &Datrparser{Kind: "string", Value: p.TXPK.Datr} } } } diff --git a/semtech/proxies.go b/semtech/proxies.go index e56103493..f77bf8ad7 100644 --- a/semtech/proxies.go +++ b/semtech/proxies.go @@ -14,32 +14,32 @@ type payloadProxy struct { type statProxy struct { *Stat - Time *timeparser `json:"time,omitempty"` + Time *Timeparser `json:"time,omitempty"` } type rxpkProxy struct { *RXPK - Datr *datrparser `json:"datr,omitempty"` - Time *timeparser `json:"time,omitempty"` + Datr *Datrparser `json:"datr,omitempty"` + Time *Timeparser `json:"time,omitempty"` } type txpkProxy struct { *TXPK - Datr *datrparser `json:"datr,omitempty"` - Time *timeparser `json:"time,omitempty"` + Datr *Datrparser `json:"datr,omitempty"` + Time *Timeparser `json:"time,omitempty"` } // datrParser is used as a proxy to Unmarshal datr field in json payloads. // Depending on the modulation type, the datr type could be either a string or a number. // We're gonna parse it as a string in any case. -type datrparser struct { - kind string - value *string // The parsed value +type Datrparser struct { + Kind string + Value *string // The parsed value } // timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time // module parse RFC3339 by default -type timeparser struct { - layout string - value *time.Time // The parsed time value +type Timeparser struct { + Layout string + Value *time.Time // The parsed time value } From 312247009f88ebed512e7224a03c33d1a24cdb01 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 14:12:54 +0100 Subject: [PATCH 0260/2266] [broker] Merge PR in lorawan in advance. --- lorawan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan b/lorawan index 8089543fb..9b752c4d9 160000 --- a/lorawan +++ b/lorawan @@ -1 +1 @@ -Subproject commit 8089543fb4bf52ed97539621ca43ea2ce33f907f +Subproject commit 9b752c4d969941a1a312ff1d86d5b1c6b5fb3519 From af1a25fd45fcfd8ec24c8ba5bad95ae5f4af9459 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 14:13:44 +0100 Subject: [PATCH 0261/2266] [broker] Add packet address computation in core package itself --- core.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/core.go b/core.go index 724964abb..e221042d5 100644 --- a/core.go +++ b/core.go @@ -3,14 +3,24 @@ package core import ( "encoding" "encoding/json" + "fmt" ) +var ErrInvalidPacket error = fmt.Errorf("Invalid Packet") + type Packet struct { - Addressable Metadata Metadata Payload PHYPayload } +func (p Packet) DevAddr() *[4]byte { + addr, err := p.Payload.DevAddr() + if err != nil { + return nil + } + return &addr +} + type Addressable interface { DevAddr() [4]byte } @@ -34,6 +44,7 @@ type PHYPayload interface { EncryptMACPayload(key [16]byte) error SetMIC(key [16]byte) error ValidateMic(key [16]byte) (bool, error) + DevAddr() ([4]byte, error) } type AckNacker interface { From bd02136e954e35a36e4c8b6c2e94d7c3ac8cf9e6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 14:04:48 +0100 Subject: [PATCH 0262/2266] [broker] Use concrete type instead of pointer to string in Datrparser --- semtech/decode.go | 6 +++--- semtech/encode.go | 16 ++++++---------- semtech/proxies.go | 2 +- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/semtech/decode.go b/semtech/decode.go index e4558a5ac..8902857eb 100644 --- a/semtech/decode.go +++ b/semtech/decode.go @@ -79,7 +79,7 @@ func (d *Datrparser) UnmarshalJSON(raw []byte) error { return errors.New("Invalid datr format") } - d.Value = &v + d.Value = v return nil } @@ -106,7 +106,7 @@ func (p *Payload) UnmarshalJSON(raw []byte) error { proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.Value } if proxy.ProxTXPK.Datr != nil { - proxy.ProxTXPK.TXPK.Datr = proxy.ProxTXPK.Datr.Value + proxy.ProxTXPK.TXPK.Datr = &proxy.ProxTXPK.Datr.Value } p.TXPK = proxy.ProxTXPK.TXPK } @@ -116,7 +116,7 @@ func (p *Payload) UnmarshalJSON(raw []byte) error { rxpk.RXPK.Time = rxpk.Time.Value } if rxpk.Datr != nil { - rxpk.RXPK.Datr = rxpk.Datr.Value + rxpk.RXPK.Datr = &rxpk.Datr.Value } p.RXPK = append(p.RXPK, *rxpk.RXPK) } diff --git a/semtech/encode.go b/semtech/encode.go index 3e81e96df..561368f4b 100644 --- a/semtech/encode.go +++ b/semtech/encode.go @@ -52,14 +52,10 @@ func (t *Timeparser) MarshalJSON() ([]byte, error) { // MarshalJSON implements the Marshaler interface from encoding/json func (d *Datrparser) MarshalJSON() ([]byte, error) { - if d.Value == nil { - return nil, errors.New("Cannot marshal a null datr") - } - if d.Kind == "uint" { - return []byte(*d.Value), nil + return []byte(d.Value), nil } - return append(append([]byte(`"`), []byte(*d.Value)...), []byte(`"`)...), nil + return append(append([]byte(`"`), []byte(d.Value)...), []byte(`"`)...), nil } // MarshalJSON implements the Marshaler interface from encoding/json @@ -87,11 +83,11 @@ func (p *Payload) MarshalJSON() ([]byte, error) { if rxpk.Modu != nil && rxpk.Datr != nil { switch *rxpk.Modu { case "FSK": - proxr.Datr = &Datrparser{Kind: "uint", Value: rxpk.Datr} + proxr.Datr = &Datrparser{Kind: "uint", Value: *rxpk.Datr} case "LORA": fallthrough default: - proxr.Datr = &Datrparser{Kind: "string", Value: rxpk.Datr} + proxr.Datr = &Datrparser{Kind: "string", Value: *rxpk.Datr} } } proxRXPK = append(proxRXPK, *proxr) @@ -108,11 +104,11 @@ func (p *Payload) MarshalJSON() ([]byte, error) { if p.TXPK.Modu != nil && p.TXPK.Datr != nil { switch *p.TXPK.Modu { case "FSK": - proxTXPK.Datr = &Datrparser{Kind: "uint", Value: p.TXPK.Datr} + proxTXPK.Datr = &Datrparser{Kind: "uint", Value: *p.TXPK.Datr} case "LORA": fallthrough default: - proxTXPK.Datr = &Datrparser{Kind: "string", Value: p.TXPK.Datr} + proxTXPK.Datr = &Datrparser{Kind: "string", Value: *p.TXPK.Datr} } } } diff --git a/semtech/proxies.go b/semtech/proxies.go index f77bf8ad7..934ce72d5 100644 --- a/semtech/proxies.go +++ b/semtech/proxies.go @@ -34,7 +34,7 @@ type txpkProxy struct { // We're gonna parse it as a string in any case. type Datrparser struct { Kind string - Value *string // The parsed value + Value string // The parsed value } // timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time From 3da7fd23d0254da45be21a911eed0db81c610b23 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:04:14 +0100 Subject: [PATCH 0263/2266] [broker] Make pointer.DumpPStruct() more flexible. Now, can print on several line, and return the output instead of directely print it --- utils/pointer/pointer.go | 60 +++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 14fb7e977..9e1879059 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -54,12 +54,18 @@ func Time(v time.Time) *time.Time { } // DumpStruct prints the content of a struct of pointers -func DumpPStruct(s interface{}) { +func DumpPStruct(s interface{}, multiline bool) string { v := reflect.ValueOf(s) if v.Kind() != reflect.Struct { - fmt.Printf("Unable to dump: Not a struct.") - return + return "Not a struct" + } + + nl := ", " + str := "{ " + if multiline { + nl = "\n\t" + str += nl } for k := 0; k < v.NumField(); k += 1 { @@ -68,47 +74,43 @@ func DumpPStruct(s interface{}) { continue } i := v.Field(k).Interface() - fmt.Printf("%v: ", v.Type().Field(k).Name) + key := fmt.Sprintf("%v", v.Type().Field(k).Name) switch t := i.(type) { case *bool: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) + if t != nil { + str += fmt.Sprintf("%s: %+v%s", key, *t, nl) } case *int: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) + if t != nil { + str += fmt.Sprintf("%s: %+v%s", key, *t, nl) } case *uint: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) + if t != nil { + str += fmt.Sprintf("%s: %+v%s", key, *t, nl) } case *string: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) + if t != nil { + str += fmt.Sprintf("%s: %+v%s", key, *t, nl) } case *float64: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) + if t != nil { + str += fmt.Sprintf("%s: %+v%s", key, *t, nl) } case *time.Time: - if t == nil { - fmt.Printf("nil\n") - } else { - fmt.Printf("%+v\n", *t) + if t != nil { + str += fmt.Sprintf("%s: %+v%s", key, *t, nl) } default: - fmt.Printf("unknown\n") + str += fmt.Sprintf("%s: unknown%s", key, nl) } } + + str = str[:len(str)-2] + if multiline { + str += "\n}" + } else { + str += " }" + } + return str } From ed6fc241d826e16c48908951edececbb29fb7db2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:20:48 +0100 Subject: [PATCH 0264/2266] [broker] Add Metadata type + method signature --- refactored_components/metadata.go | 40 +++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 refactored_components/metadata.go diff --git a/refactored_components/metadata.go b/refactored_components/metadata.go new file mode 100644 index 000000000..d93882bca --- /dev/null +++ b/refactored_components/metadata.go @@ -0,0 +1,40 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "time" +) + +type Metadata struct { + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +// MarshalJSON implements the json.Marshal interface +func (m Metadata) MarshalJSON() ([]byte, error) { + return nil, nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (m *Metadata) UnmarshalJSON(raw []byte) error { + return nil +} From 7c1d57a8920ac34fda6c1651a48c8dae8e750c40 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:21:09 +0100 Subject: [PATCH 0265/2266] [broker] Write down test for metadata MarshalJSON() method --- refactored_components/metadata_test.go | 80 ++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 refactored_components/metadata_test.go diff --git a/refactored_components/metadata_test.go b/refactored_components/metadata_test.go new file mode 100644 index 000000000..9474bf6e6 --- /dev/null +++ b/refactored_components/metadata_test.go @@ -0,0 +1,80 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "encoding/json" + "github.com/thethingsnetwork/core/utils/pointer" + . "github.com/thethingsnetwork/core/utils/testing" + "testing" + "time" +) + +// The broker can handle an uplink packet +func TestMarshaljson(t *testing.T) { + tests := []struct { + Metadata Metadata + WantError error + WantJSON string + }{ + { // Basic attributes, uint, string and float64 + Metadata{Chan: pointer.Uint(2), Codr: pointer.String("4/6"), Freq: pointer.Float64(864.125)}, + nil, + `{"chan":2,"codr":"4/6","freq":864.125}`, + }, + + { // Basic attributes #2, int and bool + Metadata{Imme: pointer.Bool(true), Rssi: pointer.Int(-54)}, + nil, + `{"imme":true,"rssi":-54}`, + }, + + { // Datr attr, FSK type + Metadata{Datr: pointer.String("50000"), Modu: pointer.String("FSK")}, + nil, + `{"modu":"FSK","datr":50000}`, + }, + + { // Datr attr, lora modulation + Metadata{Datr: pointer.String("SF7BW125"), Modu: pointer.String("LORA")}, + nil, + `{"modu":"LORA","datr":"SF7BW125"}`, + }, + + { // Time attr + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC))}, + nil, + `{"time":"2016-01-06T15:11:12.000000142Z"}`, + }, + + { // Mixed + Metadata{ + Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC)), + Modu: pointer.String("FSK"), + Datr: pointer.String("50000"), + Size: pointer.Uint(14), + Lsnr: pointer.Float64(5.7), + }, + nil, + `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, + }, + } + + for _, test := range tests { + Desc(t, "Marshal medatadata: %v", pointer.DumpPStruct(test.Metadata, false)) + raw, err := json.Marshal(test.Metadata) + + if err != test.WantError { + Ko(t, "Expected error to be %v but got %v", test.WantError, err) + continue + } + + str := string(raw) + if str != test.WantJSON { + Ko(t, "Marshaled data don't match expectation.\nWant: %s\nGot: %s", test.WantJSON, str) + continue + } + Ok(t) + } +} From 1f7903986688244e2f5f6d401ab9db7edddd0354 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:21:57 +0100 Subject: [PATCH 0266/2266] [broker] Implement MarshalJSON() and make tests pass --- refactored_components/metadata.go | 39 ++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/refactored_components/metadata.go b/refactored_components/metadata.go index d93882bca..5ff937557 100644 --- a/refactored_components/metadata.go +++ b/refactored_components/metadata.go @@ -4,6 +4,8 @@ package components import ( + "encoding/json" + "github.com/thethingsnetwork/core/semtech" "time" ) @@ -29,12 +31,47 @@ type Metadata struct { Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } +type metadata Metadata + // MarshalJSON implements the json.Marshal interface func (m Metadata) MarshalJSON() ([]byte, error) { - return nil, nil + var d *semtech.Datrparser + var t *semtech.Timeparser + + if m.Datr != nil { + d = new(semtech.Datrparser) + if m.Modu != nil && *m.Modu == "FSK" { + *d = semtech.Datrparser{Kind: "uint", Value: *m.Datr} + } else { + *d = semtech.Datrparser{Kind: "string", Value: *m.Datr} + } + } + + if m.Time != nil { + t = new(semtech.Timeparser) + *t = semtech.Timeparser{Layout: time.RFC3339Nano, Value: m.Time} + } + + return json.Marshal(metadataProxy{ + metadata: metadata(m), + Datr: d, + Time: t, + }) } // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { return nil } + +// type metadataProxy is used to conveniently marshal and unmarshal Metadata structure. +// +// Datr field could be either string or uint depending on the Modu field. +// Time field could be parsed in a lot of different way depending of the time format. +// This proxy make sure that everything is marshaled and unmarshaled to the right thing and allow +// the Metadata struct to be user-friendly. +type metadataProxy struct { + metadata + Datr *semtech.Datrparser `json:"datr,omitempty"` + Time *semtech.Timeparser `json:"time,omitempty"` +} From 50abcd7072135730928f24d2c1be5ee48e90490e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:40:41 +0100 Subject: [PATCH 0267/2266] [broker] Write test for metadata.UnmarshalJSON() --- refactored_components/metadata_test.go | 156 ++++++++++++++++--------- 1 file changed, 103 insertions(+), 53 deletions(-) diff --git a/refactored_components/metadata_test.go b/refactored_components/metadata_test.go index 9474bf6e6..9b2572e3b 100644 --- a/refactored_components/metadata_test.go +++ b/refactored_components/metadata_test.go @@ -7,74 +7,124 @@ import ( "encoding/json" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" + "reflect" "testing" "time" ) -// The broker can handle an uplink packet -func TestMarshaljson(t *testing.T) { - tests := []struct { - Metadata Metadata - WantError error - WantJSON string - }{ - { // Basic attributes, uint, string and float64 - Metadata{Chan: pointer.Uint(2), Codr: pointer.String("4/6"), Freq: pointer.Float64(864.125)}, - nil, - `{"chan":2,"codr":"4/6","freq":864.125}`, - }, +var commonTests = []struct { + Metadata Metadata + WantError error + JSON string +}{ + { // Basic attributes, uint, string and float64 + Metadata{Chan: pointer.Uint(2), Codr: pointer.String("4/6"), Freq: pointer.Float64(864.125)}, + nil, + `{"chan":2,"codr":"4/6","freq":864.125}`, + }, - { // Basic attributes #2, int and bool - Metadata{Imme: pointer.Bool(true), Rssi: pointer.Int(-54)}, - nil, - `{"imme":true,"rssi":-54}`, - }, + { // Basic attributes #2, int and bool + Metadata{Imme: pointer.Bool(true), Rssi: pointer.Int(-54)}, + nil, + `{"imme":true,"rssi":-54}`, + }, - { // Datr attr, FSK type - Metadata{Datr: pointer.String("50000"), Modu: pointer.String("FSK")}, - nil, - `{"modu":"FSK","datr":50000}`, - }, + { // Datr attr, FSK type + Metadata{Datr: pointer.String("50000"), Modu: pointer.String("FSK")}, + nil, + `{"modu":"FSK","datr":50000}`, + }, - { // Datr attr, lora modulation - Metadata{Datr: pointer.String("SF7BW125"), Modu: pointer.String("LORA")}, - nil, - `{"modu":"LORA","datr":"SF7BW125"}`, - }, + { // Datr attr, lora modulation + Metadata{Datr: pointer.String("SF7BW125"), Modu: pointer.String("LORA")}, + nil, + `{"modu":"LORA","datr":"SF7BW125"}`, + }, - { // Time attr - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC))}, - nil, - `{"time":"2016-01-06T15:11:12.000000142Z"}`, - }, + { // Time attr + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC))}, + nil, + `{"time":"2016-01-06T15:11:12.000000142Z"}`, + }, - { // Mixed - Metadata{ - Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC)), - Modu: pointer.String("FSK"), - Datr: pointer.String("50000"), - Size: pointer.Uint(14), - Lsnr: pointer.Float64(5.7), - }, - nil, - `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, + { // Mixed + Metadata{ + Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC)), + Modu: pointer.String("FSK"), + Datr: pointer.String("50000"), + Size: pointer.Uint(14), + Lsnr: pointer.Float64(5.7), }, - } + nil, + `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, + }, +} - for _, test := range tests { +var unmarshalTests = []struct { + Metadata Metadata + WantError error + JSON string +}{ + { // Local time + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 0, time.UTC))}, + nil, + `{"time":"2016-01-06 15:11:12 GMT"}`, + }, + + { // RFC3339 time + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142000000, time.UTC))}, + nil, + `{"time":"2016-01-06T15:11:12.142000Z"}`, + }, +} + +// The broker can handle an uplink packet +func TestMarshaljson(t *testing.T) { + for _, test := range commonTests { Desc(t, "Marshal medatadata: %v", pointer.DumpPStruct(test.Metadata, false)) raw, err := json.Marshal(test.Metadata) + checkErrors(t, test.WantError, err) + checkJSON(t, test.JSON, raw) + } +} - if err != test.WantError { - Ko(t, "Expected error to be %v but got %v", test.WantError, err) - continue - } +func TestUnmarshalJSON(t *testing.T) { + for _, test := range append(commonTests, unmarshalTests...) { + Desc(t, "Unmarshal json: %s", test.JSON) + metadata := Metadata{} + err := json.Unmarshal([]byte(test.JSON), &metadata) + checkErrors(t, test.WantError, err) + checkMetadata(t, test.Metadata, metadata) + } +} + +// ----- Check utilities + +// Check that errors match +func checkErrors(t *testing.T, want error, got error) { + if got == want { + Ok(t) + return + } + Ko(t, "Expected error to be %v but got %v", want, got) +} + +// Check that obtained json matches expected one +func checkJSON(t *testing.T, want string, got []byte) { + str := string(got) + if str == want { + Ok(t) + return + } + Ko(t, "Marshaled data don't match expectations.\nWant: %s\nGot: %s", want, str) + return +} - str := string(raw) - if str != test.WantJSON { - Ko(t, "Marshaled data don't match expectation.\nWant: %s\nGot: %s", test.WantJSON, str) - continue - } +// Check that obtained metadata matches expected one +func checkMetadata(t *testing.T, want Metadata, got Metadata) { + if reflect.DeepEqual(want, got) { Ok(t) + return } + Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) } From 5d571bc25a799e8a80d062726e9927492272d304 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:40:55 +0100 Subject: [PATCH 0268/2266] [broker] Implement Metadata.UnmarshalJSON() --- refactored_components/metadata.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/refactored_components/metadata.go b/refactored_components/metadata.go index 5ff937557..585d451b0 100644 --- a/refactored_components/metadata.go +++ b/refactored_components/metadata.go @@ -5,6 +5,7 @@ package components import ( "encoding/json" + "fmt" "github.com/thethingsnetwork/core/semtech" "time" ) @@ -61,6 +62,23 @@ func (m Metadata) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { + if m == nil { + return fmt.Errorf("Cannot unmarshal nil Metadata") + } + + proxy := metadataProxy{} + if err := json.Unmarshal(raw, &proxy); err != nil { + return err + } + *m = Metadata(proxy.metadata) + if proxy.Time != nil { + m.Time = proxy.Time.Value + } + + if proxy.Datr != nil { + m.Datr = &proxy.Datr.Value + } + return nil } From 035e8ebeb6aa081934c8dba11329562cf9aaa57f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:46:05 +0100 Subject: [PATCH 0269/2266] [broker] Add tag feedback to Ok() method of utils/testing --- refactored_components/metadata_test.go | 6 +++--- utils/testing/testing.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/refactored_components/metadata_test.go b/refactored_components/metadata_test.go index 9b2572e3b..1a67648c8 100644 --- a/refactored_components/metadata_test.go +++ b/refactored_components/metadata_test.go @@ -103,7 +103,7 @@ func TestUnmarshalJSON(t *testing.T) { // Check that errors match func checkErrors(t *testing.T, want error, got error) { if got == want { - Ok(t) + Ok(t, "check Errors") return } Ko(t, "Expected error to be %v but got %v", want, got) @@ -113,7 +113,7 @@ func checkErrors(t *testing.T, want error, got error) { func checkJSON(t *testing.T, want string, got []byte) { str := string(got) if str == want { - Ok(t) + Ok(t, "check JSON") return } Ko(t, "Marshaled data don't match expectations.\nWant: %s\nGot: %s", want, str) @@ -123,7 +123,7 @@ func checkJSON(t *testing.T, want string, got []byte) { // Check that obtained metadata matches expected one func checkMetadata(t *testing.T, want Metadata, got Metadata) { if reflect.DeepEqual(want, got) { - Ok(t) + Ok(t, "check Metadata") return } Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 8410ec890..f9b641eb0 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -11,8 +11,8 @@ import ( ) // Ok displays a green check symbol -func Ok(t *testing.T) { - t.Log("\033[32;1m\u2714 ok\033[0m") +func Ok(t *testing.T, tag string) { + t.Log(fmt.Sprintf("\033[32;1m\u2714 ok | %s\033[0m", tag)) } // Ko fails the test and display a red cross symbol From c890a3cd03aac100a74a2b1e54e3d4f91e00919f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 15:50:50 +0100 Subject: [PATCH 0270/2266] [broker] Start methods related to the packet structure --- refactored_components/packet.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 refactored_components/packet.go diff --git a/refactored_components/packet.go b/refactored_components/packet.go new file mode 100644 index 000000000..da042eb54 --- /dev/null +++ b/refactored_components/packet.go @@ -0,0 +1,17 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/thethingsnetwork/core" + //"github.com/thethingsnetwork/core/lorawan" + "fmt" + "github.com/thethingsnetwork/core/semtech" +) + +var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") + +func ConvertSemtechPacket(p semtech.Packet) (core.Packet, error) { + return core.Packet{}, nil +} From 734a90de857e686bf0036d00465803d40ef751e1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 17:55:27 +0100 Subject: [PATCH 0271/2266] [broker] Implement io.Stringer interface for Metadata --- core.go | 28 +++++++++++++--------------- refactored_components/metadata.go | 6 ++++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/core.go b/core.go index e221042d5..b98e10589 100644 --- a/core.go +++ b/core.go @@ -1,19 +1,19 @@ package core import ( - "encoding" "encoding/json" "fmt" + "github.com/thethingsnetwork/core/lorawan" ) var ErrInvalidPacket error = fmt.Errorf("Invalid Packet") type Packet struct { Metadata Metadata - Payload PHYPayload + Payload lorawan.PHYPayload } -func (p Packet) DevAddr() *[4]byte { +func (p Packet) DevAddr() *lorawan.DevAddr { addr, err := p.Payload.DevAddr() if err != nil { return nil @@ -21,6 +21,15 @@ func (p Packet) DevAddr() *[4]byte { return &addr } +func (p Packet) String() string { + str := "Packet {" + if p.Metadata != nil { + str += fmt.Sprintf("\n\tMetadata %s}", p.Metadata.String()) + } + str += fmt.Sprintf("\n\tPayload%+v\n}", p.Payload) + return str +} + type Addressable interface { DevAddr() [4]byte } @@ -33,18 +42,7 @@ type Recipient struct { type Metadata interface { json.Marshaler json.Unmarshaler - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler -} - -type PHYPayload interface { - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler - DecryptMACPayload(key [16]byte) error - EncryptMACPayload(key [16]byte) error - SetMIC(key [16]byte) error - ValidateMic(key [16]byte) (bool, error) - DevAddr() ([4]byte, error) + String() string } type AckNacker interface { diff --git a/refactored_components/metadata.go b/refactored_components/metadata.go index 585d451b0..3c4bf4173 100644 --- a/refactored_components/metadata.go +++ b/refactored_components/metadata.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/pointer" "time" ) @@ -82,6 +83,11 @@ func (m *Metadata) UnmarshalJSON(raw []byte) error { return nil } +// String implement the io.Stringer interface +func (m Metadata) String() string { + return pointer.DumpPStruct(m, false) +} + // type metadataProxy is used to conveniently marshal and unmarshal Metadata structure. // // Datr field could be either string or uint depending on the Modu field. From 5ce45df86e26f20429be0a69153c8d7df9c01943 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 18:00:19 +0100 Subject: [PATCH 0272/2266] [broker] Impact changes related to Stringer interface --- core.go | 2 +- refactored_components/metadata_test.go | 4 ++-- utils/pointer/pointer.go | 20 ++++++++++---------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/core.go b/core.go index b98e10589..f28137ca0 100644 --- a/core.go +++ b/core.go @@ -24,7 +24,7 @@ func (p Packet) DevAddr() *lorawan.DevAddr { func (p Packet) String() string { str := "Packet {" if p.Metadata != nil { - str += fmt.Sprintf("\n\tMetadata %s}", p.Metadata.String()) + str += fmt.Sprintf("\n\t%s}", p.Metadata.String()) } str += fmt.Sprintf("\n\tPayload%+v\n}", p.Payload) return str diff --git a/refactored_components/metadata_test.go b/refactored_components/metadata_test.go index 1a67648c8..8bcd8e171 100644 --- a/refactored_components/metadata_test.go +++ b/refactored_components/metadata_test.go @@ -81,7 +81,7 @@ var unmarshalTests = []struct { // The broker can handle an uplink packet func TestMarshaljson(t *testing.T) { for _, test := range commonTests { - Desc(t, "Marshal medatadata: %v", pointer.DumpPStruct(test.Metadata, false)) + Desc(t, "Marshal medatadata: %s", test.Metadata.String()) raw, err := json.Marshal(test.Metadata) checkErrors(t, test.WantError, err) checkJSON(t, test.JSON, raw) @@ -126,5 +126,5 @@ func checkMetadata(t *testing.T, want Metadata, got Metadata) { Ok(t, "check Metadata") return } - Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) + Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) } diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 9e1879059..fbcf79e63 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -61,8 +61,8 @@ func DumpPStruct(s interface{}, multiline bool) string { return "Not a struct" } - nl := ", " - str := "{ " + nl := "," + str := fmt.Sprintf("%s{", v.Type().Name()) if multiline { nl = "\n\t" str += nl @@ -79,30 +79,30 @@ func DumpPStruct(s interface{}, multiline bool) string { switch t := i.(type) { case *bool: if t != nil { - str += fmt.Sprintf("%s: %+v%s", key, *t, nl) + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } case *int: if t != nil { - str += fmt.Sprintf("%s: %+v%s", key, *t, nl) + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } case *uint: if t != nil { - str += fmt.Sprintf("%s: %+v%s", key, *t, nl) + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } case *string: if t != nil { - str += fmt.Sprintf("%s: %+v%s", key, *t, nl) + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } case *float64: if t != nil { - str += fmt.Sprintf("%s: %+v%s", key, *t, nl) + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } case *time.Time: if t != nil { - str += fmt.Sprintf("%s: %+v%s", key, *t, nl) + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } default: - str += fmt.Sprintf("%s: unknown%s", key, nl) + str += fmt.Sprintf("%s:unknown%s", key, nl) } } @@ -110,7 +110,7 @@ func DumpPStruct(s interface{}, multiline bool) string { if multiline { str += "\n}" } else { - str += " }" + str += "}" } return str } From 1301e1f57c0fc3935cdf707112eb75a77025f8d3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 18:14:35 +0100 Subject: [PATCH 0273/2266] [broker] Change signature of convert method --- refactored_components/packet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refactored_components/packet.go b/refactored_components/packet.go index da042eb54..511781f0f 100644 --- a/refactored_components/packet.go +++ b/refactored_components/packet.go @@ -12,6 +12,6 @@ import ( var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") -func ConvertSemtechPacket(p semtech.Packet) (core.Packet, error) { +func ConvertRXPK(p semtech.RXPK) (core.Packet, error) { return core.Packet{}, nil } From d4c2806640e2eb51bd5c819f89c4544879b81d97 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 18:14:55 +0100 Subject: [PATCH 0274/2266] [broker] Write down tests for ConvertRXPK method --- refactored_components/packet_test.go | 164 +++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 refactored_components/packet_test.go diff --git a/refactored_components/packet_test.go b/refactored_components/packet_test.go new file mode 100644 index 000000000..bc58bed9b --- /dev/null +++ b/refactored_components/packet_test.go @@ -0,0 +1,164 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "encoding/base64" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/pointer" + . "github.com/thethingsnetwork/core/utils/testing" + "reflect" + "strings" + "testing" + "time" +) + +type convertSemtechPacketTest struct { + CorePacket core.Packet + RXPK semtech.RXPK + WantError error +} + +func TestComvertSemtechPacket(t *testing.T) { + tests := []convertSemtechPacketTest{ + genRXPKWithFullMetadata(), + genRXPKWithPartialMetadata(), + genRXPKWithNoData(), + } + + for _, test := range tests { + Desc(t, "Convert RXPK: %s", pointer.DumpPStruct(test.RXPK, false)) + packet, err := ConvertRXPK(test.RXPK) + checkErrors(t, test.WantError, err) + checkPackets(t, test.CorePacket, packet) + } +} + +func checkPackets(t *testing.T, want core.Packet, got core.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + Ko(t, "Converted packet don't match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) +} + +func genRXPKWithFullMetadata() convertSemtechPacketTest { + phyPayload := genPHYPayload(true) + rxpk := genRXPK(phyPayload) + metadata := genMetadata(rxpk) + return convertSemtechPacketTest{ + CorePacket: core.Packet{Metadata: &metadata, Payload: phyPayload}, + RXPK: rxpk, + WantError: nil, + } +} + +func genRXPKWithPartialMetadata() convertSemtechPacketTest { + phyPayload := genPHYPayload(true) + rxpk := genRXPK(phyPayload) + rxpk.Codr = nil + rxpk.Rfch = nil + rxpk.Rssi = nil + rxpk.Time = nil + rxpk.Size = nil + metadata := genMetadata(rxpk) + return convertSemtechPacketTest{ + CorePacket: core.Packet{Metadata: &metadata, Payload: phyPayload}, + RXPK: rxpk, + WantError: nil, + } +} + +func genRXPKWithNoData() convertSemtechPacketTest { + rxpk := genRXPK(genPHYPayload(true)) + rxpk.Data = nil + return convertSemtechPacketTest{ + CorePacket: core.Packet{}, + RXPK: rxpk, + WantError: ErrImpossibleConversion, + } +} + +func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + dst := make([]byte, 256) + base64.StdEncoding.Encode(dst, raw) + if err != nil { + panic(err) + } + data := strings.Trim(string(dst), "=") + + return semtech.RXPK{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Data: pointer.String(data), + Freq: pointer.Float64(863.125), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(uint(len([]byte(data)))), + Stat: pointer.Int(0), + Time: pointer.Time(time.Now()), + Tmst: pointer.Uint(uint(time.Now().UnixNano())), + } +} + +func genMetadata(RXPK semtech.RXPK) Metadata { + return Metadata{ + Chan: RXPK.Chan, + Codr: RXPK.Codr, + Data: RXPK.Data, + Freq: RXPK.Freq, + Lsnr: RXPK.Lsnr, + Modu: RXPK.Modu, + Rfch: RXPK.Rfch, + Rssi: RXPK.Rssi, + Size: RXPK.Size, + Stat: RXPK.Stat, + Time: RXPK.Time, + Tmst: RXPK.Tmst, + } +} + +func genPHYPayload(uplink bool) lorawan.PHYPayload { + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(uplink) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + FOpts: []lorawan.MACCommand{}, // you can leave this out when there is no MAC command to send + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(uplink) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + return payload +} From a9cb05a8ffaa02f5727abc9f8e37956a652b3750 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 18:20:24 +0100 Subject: [PATCH 0275/2266] [broker] Rename test functions + add comments to packet_test --- refactored_components/packet_test.go | 38 ++++++++++++++++++---------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/refactored_components/packet_test.go b/refactored_components/packet_test.go index bc58bed9b..a856f36db 100644 --- a/refactored_components/packet_test.go +++ b/refactored_components/packet_test.go @@ -16,14 +16,8 @@ import ( "time" ) -type convertSemtechPacketTest struct { - CorePacket core.Packet - RXPK semtech.RXPK - WantError error -} - -func TestComvertSemtechPacket(t *testing.T) { - tests := []convertSemtechPacketTest{ +func TestConvertRXPK(t *testing.T) { + tests := []convertRXPKTest{ genRXPKWithFullMetadata(), genRXPKWithPartialMetadata(), genRXPKWithNoData(), @@ -37,6 +31,8 @@ func TestComvertSemtechPacket(t *testing.T) { } } +// ----- Check Utilities + func checkPackets(t *testing.T, want core.Packet, got core.Packet) { if reflect.DeepEqual(want, got) { Ok(t, "Check packets") @@ -45,18 +41,28 @@ func checkPackets(t *testing.T, want core.Packet, got core.Packet) { Ko(t, "Converted packet don't match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) } -func genRXPKWithFullMetadata() convertSemtechPacketTest { +// ---- Build utilities + +type convertRXPKTest struct { + CorePacket core.Packet + RXPK semtech.RXPK + WantError error +} + +// Generates a test suite where the RXPK is fully complete +func genRXPKWithFullMetadata() convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) metadata := genMetadata(rxpk) - return convertSemtechPacketTest{ + return convertRXPKTest{ CorePacket: core.Packet{Metadata: &metadata, Payload: phyPayload}, RXPK: rxpk, WantError: nil, } } -func genRXPKWithPartialMetadata() convertSemtechPacketTest { +// Generates a test suite where the RXPK contains partial metadata +func genRXPKWithPartialMetadata() convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) rxpk.Codr = nil @@ -65,23 +71,25 @@ func genRXPKWithPartialMetadata() convertSemtechPacketTest { rxpk.Time = nil rxpk.Size = nil metadata := genMetadata(rxpk) - return convertSemtechPacketTest{ + return convertRXPKTest{ CorePacket: core.Packet{Metadata: &metadata, Payload: phyPayload}, RXPK: rxpk, WantError: nil, } } -func genRXPKWithNoData() convertSemtechPacketTest { +// Generates a test suite where the RXPK contains no data +func genRXPKWithNoData() convertRXPKTest { rxpk := genRXPK(genPHYPayload(true)) rxpk.Data = nil - return convertSemtechPacketTest{ + return convertRXPKTest{ CorePacket: core.Packet{}, RXPK: rxpk, WantError: ErrImpossibleConversion, } } +// Generate an RXPK packet using the given payload as Data func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { raw, err := phyPayload.MarshalBinary() if err != nil { @@ -110,6 +118,7 @@ func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { } } +// Generate a Metadata object that matches RXPK metadata func genMetadata(RXPK semtech.RXPK) Metadata { return Metadata{ Chan: RXPK.Chan, @@ -127,6 +136,7 @@ func genMetadata(RXPK semtech.RXPK) Metadata { } } +// Generate a Physical payload represting an uplink or downlink message func genPHYPayload(uplink bool) lorawan.PHYPayload { nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} From f9e55b6eccbb40097fd8ddb6b9bc8f34031fe6a5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 19:35:45 +0100 Subject: [PATCH 0276/2266] [broker] Fix packet_tests using a too large buffer for encoding + FOpts wrongly instantiated --- refactored_components/packet_test.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/refactored_components/packet_test.go b/refactored_components/packet_test.go index a856f36db..a0b613b1f 100644 --- a/refactored_components/packet_test.go +++ b/refactored_components/packet_test.go @@ -95,12 +95,7 @@ func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { if err != nil { panic(err) } - dst := make([]byte, 256) - base64.StdEncoding.Encode(dst, raw) - if err != nil { - panic(err) - } - data := strings.Trim(string(dst), "=") + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") return semtech.RXPK{ Chan: pointer.Uint(2), @@ -149,8 +144,7 @@ func genPHYPayload(uplink bool) lorawan.PHYPayload { ADRACKReq: false, ACK: false, }, - FCnt: 0, - FOpts: []lorawan.MACCommand{}, // you can leave this out when there is no MAC command to send + FCnt: 0, } macPayload.FPort = 10 macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} From ccc6b6ed36c8d0e8f8c1499bbfae1425613f3da7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 19:36:10 +0100 Subject: [PATCH 0277/2266] [broker] Implement convertRXPK for packet --- refactored_components/packet.go | 42 ++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/refactored_components/packet.go b/refactored_components/packet.go index 511781f0f..7541a9332 100644 --- a/refactored_components/packet.go +++ b/refactored_components/packet.go @@ -4,14 +4,50 @@ package components import ( - "github.com/thethingsnetwork/core" - //"github.com/thethingsnetwork/core/lorawan" + "encoding/base64" "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" + "reflect" ) var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") func ConvertRXPK(p semtech.RXPK) (core.Packet, error) { - return core.Packet{}, nil + packet := core.Packet{} + if p.Data == nil { + return packet, ErrImpossibleConversion + } + + encoded := *p.Data + switch len(encoded) % 4 { + case 2: + encoded += "==" + case 3: + encoded += "=" + } + + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return packet, err + } + + payload := lorawan.NewPHYPayload(true) + if err = payload.UnmarshalBinary(raw); err != nil { + return packet, err + } + + metadata := Metadata{} + rxpkValue := reflect.ValueOf(p) + rxpkStruct := rxpkValue.Type() + metas := reflect.ValueOf(&metadata).Elem() + for i := 0; i < rxpkStruct.NumField(); i += 1 { + field := rxpkStruct.Field(i).Name + if metas.FieldByName(field).CanSet() { + metas.FieldByName(field).Set(rxpkValue.Field(i)) + } + } + + return core.Packet{Metadata: &metadata, Payload: payload}, nil } From 1115da11d10888cb53cb7c9350fad923afbb3b8d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 19:38:49 +0100 Subject: [PATCH 0278/2266] [broker] Add some comments in packet.go --- refactored_components/packet.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/refactored_components/packet.go b/refactored_components/packet.go index 7541a9332..625bcf806 100644 --- a/refactored_components/packet.go +++ b/refactored_components/packet.go @@ -12,8 +12,11 @@ import ( "reflect" ) +// return by ConvertRXPK or ConvertTXPK if there's no data in the given packet var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") +// ConvertRXPK create a core.Packet from a semtech.RXPK. It's an handy way to both decode the +// frame payload and retrieve associated metadata from that packet func ConvertRXPK(p semtech.RXPK) (core.Packet, error) { packet := core.Packet{} if p.Data == nil { From 85ff834a59e10f2852b8438a969e73def6c6efa6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 19:50:07 +0100 Subject: [PATCH 0279/2266] [broker] Reorganise test files and split them for better readibility --- refactored_components/build_utilities_test.go | 98 +++++++++++++++++ refactored_components/check_utilities_test.go | 49 +++++++++ refactored_components/metadata_test.go | 32 ------ refactored_components/packet_test.go | 100 ------------------ 4 files changed, 147 insertions(+), 132 deletions(-) create mode 100644 refactored_components/build_utilities_test.go create mode 100644 refactored_components/check_utilities_test.go diff --git a/refactored_components/build_utilities_test.go b/refactored_components/build_utilities_test.go new file mode 100644 index 000000000..468c39250 --- /dev/null +++ b/refactored_components/build_utilities_test.go @@ -0,0 +1,98 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "encoding/base64" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/pointer" + "strings" + "time" +) + +type convertRXPKTest struct { + CorePacket core.Packet + RXPK semtech.RXPK + WantError error +} + +// Generate an RXPK packet using the given payload as Data +func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + + return semtech.RXPK{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Data: pointer.String(data), + Freq: pointer.Float64(863.125), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(uint(len([]byte(data)))), + Stat: pointer.Int(0), + Time: pointer.Time(time.Now()), + Tmst: pointer.Uint(uint(time.Now().UnixNano())), + } +} + +// Generate a Metadata object that matches RXPK metadata +func genMetadata(RXPK semtech.RXPK) Metadata { + return Metadata{ + Chan: RXPK.Chan, + Codr: RXPK.Codr, + Data: RXPK.Data, + Freq: RXPK.Freq, + Lsnr: RXPK.Lsnr, + Modu: RXPK.Modu, + Rfch: RXPK.Rfch, + Rssi: RXPK.Rssi, + Size: RXPK.Size, + Stat: RXPK.Stat, + Time: RXPK.Time, + Tmst: RXPK.Tmst, + } +} + +// Generate a Physical payload represting an uplink or downlink message +func genPHYPayload(uplink bool) lorawan.PHYPayload { + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(uplink) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(uplink) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + return payload +} diff --git a/refactored_components/check_utilities_test.go b/refactored_components/check_utilities_test.go new file mode 100644 index 000000000..31d8a4c5e --- /dev/null +++ b/refactored_components/check_utilities_test.go @@ -0,0 +1,49 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/thethingsnetwork/core" + . "github.com/thethingsnetwork/core/utils/testing" + "reflect" + "testing" +) + +// Checks that two core packets match +func checkPackets(t *testing.T, want core.Packet, got core.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + Ko(t, "Converted packet don't match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) +} + +// Checks that errors match +func checkErrors(t *testing.T, want error, got error) { + if got == want { + Ok(t, "check Errors") + return + } + Ko(t, "Expected error to be %v but got %v", want, got) +} + +// Checks that obtained json matches expected one +func checkJSON(t *testing.T, want string, got []byte) { + str := string(got) + if str == want { + Ok(t, "check JSON") + return + } + Ko(t, "Marshaled data don't match expectations.\nWant: %s\nGot: %s", want, str) + return +} + +// Checks that obtained metadata matches expected one +func checkMetadata(t *testing.T, want Metadata, got Metadata) { + if reflect.DeepEqual(want, got) { + Ok(t, "check Metadata") + return + } + Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) +} diff --git a/refactored_components/metadata_test.go b/refactored_components/metadata_test.go index 8bcd8e171..4b83937b5 100644 --- a/refactored_components/metadata_test.go +++ b/refactored_components/metadata_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" - "reflect" "testing" "time" ) @@ -97,34 +96,3 @@ func TestUnmarshalJSON(t *testing.T) { checkMetadata(t, test.Metadata, metadata) } } - -// ----- Check utilities - -// Check that errors match -func checkErrors(t *testing.T, want error, got error) { - if got == want { - Ok(t, "check Errors") - return - } - Ko(t, "Expected error to be %v but got %v", want, got) -} - -// Check that obtained json matches expected one -func checkJSON(t *testing.T, want string, got []byte) { - str := string(got) - if str == want { - Ok(t, "check JSON") - return - } - Ko(t, "Marshaled data don't match expectations.\nWant: %s\nGot: %s", want, str) - return -} - -// Check that obtained metadata matches expected one -func checkMetadata(t *testing.T, want Metadata, got Metadata) { - if reflect.DeepEqual(want, got) { - Ok(t, "check Metadata") - return - } - Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) -} diff --git a/refactored_components/packet_test.go b/refactored_components/packet_test.go index a0b613b1f..49a9e1e2d 100644 --- a/refactored_components/packet_test.go +++ b/refactored_components/packet_test.go @@ -4,16 +4,10 @@ package components import ( - "encoding/base64" "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" - "reflect" - "strings" "testing" - "time" ) func TestConvertRXPK(t *testing.T) { @@ -31,24 +25,8 @@ func TestConvertRXPK(t *testing.T) { } } -// ----- Check Utilities - -func checkPackets(t *testing.T, want core.Packet, got core.Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") - return - } - Ko(t, "Converted packet don't match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) -} - // ---- Build utilities -type convertRXPKTest struct { - CorePacket core.Packet - RXPK semtech.RXPK - WantError error -} - // Generates a test suite where the RXPK is fully complete func genRXPKWithFullMetadata() convertRXPKTest { phyPayload := genPHYPayload(true) @@ -88,81 +66,3 @@ func genRXPKWithNoData() convertRXPKTest { WantError: ErrImpossibleConversion, } } - -// Generate an RXPK packet using the given payload as Data -func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - - return semtech.RXPK{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Data: pointer.String(data), - Freq: pointer.Float64(863.125), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(uint(len([]byte(data)))), - Stat: pointer.Int(0), - Time: pointer.Time(time.Now()), - Tmst: pointer.Uint(uint(time.Now().UnixNano())), - } -} - -// Generate a Metadata object that matches RXPK metadata -func genMetadata(RXPK semtech.RXPK) Metadata { - return Metadata{ - Chan: RXPK.Chan, - Codr: RXPK.Codr, - Data: RXPK.Data, - Freq: RXPK.Freq, - Lsnr: RXPK.Lsnr, - Modu: RXPK.Modu, - Rfch: RXPK.Rfch, - Rssi: RXPK.Rssi, - Size: RXPK.Size, - Stat: RXPK.Stat, - Time: RXPK.Time, - Tmst: RXPK.Tmst, - } -} - -// Generate a Physical payload represting an uplink or downlink message -func genPHYPayload(uplink bool) lorawan.PHYPayload { - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(uplink) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(uplink) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} From c0c19af007621846547bf2d48645bda6a4bb1484 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 20:44:02 +0100 Subject: [PATCH 0280/2266] [broker] Update interfaces to add broker specific processing + create a small scenario in broker.go --- core.go | 14 ++++++--- refactored_components/broker.go | 55 +++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 5 deletions(-) diff --git a/core.go b/core.go index f28137ca0..fe118ea63 100644 --- a/core.go +++ b/core.go @@ -30,10 +30,6 @@ func (p Packet) String() string { return str } -type Addressable interface { - DevAddr() [4]byte -} - type Recipient struct { Address interface{} Id interface{} @@ -61,7 +57,15 @@ type Adapter interface { Next() (*Packet, *AckNacker, error) } +type BrkHdlAdapter interface { + Adapter + NextRegistration() (Recipient, lorawan.DevAddr, lorawan.AES128Key, error) +} + type Router Component -type Broker Component +type Broker interface { + Component + Register(handler Recipient, devAddr lorawan.DevAddr, nwskey lorawan.AES128Key) error +} type Handler Component type NetworkController Component diff --git a/refactored_components/broker.go b/refactored_components/broker.go index 404a10f1c..b5e9787f6 100644 --- a/refactored_components/broker.go +++ b/refactored_components/broker.go @@ -9,6 +9,61 @@ import ( type Broker struct{} +/* Scenario +broker := components.NewBroker() +upAdapter := adapters.brk_hdl_http +downAdapter := adapters.brk_rtr_http + +downAdapter.Start("3000") +upAdapter.Start("8080") + +// Handle registration coming from Handler +go func() { + for { + handler, devAddr, nwskey, err := upAdapter.NextRegistration() + if err != nil { + // Do some error handling + } + err = broker.Register(handler, devAddr, nwskey) + if err != nil { + // Do some error handling + } + } +}() + +// Handle response packet coming from Handler +go func() { + for { + packet, an, error := upAdapter.Next() + if err != nil { + // Do some error handling + } + err = broker.Handle(packet, an) + if err != nil { + // Do some error handling + } + } +} + +// Handle packet from router +go func() { + for { + packet, an, error := downAdapter.Next() + if err != nil { + // Do some error handling + } + err = broker.Handle(packet, an) + if err != nil { + // Do some error handling + } + } +} +*/ + +func NewBroker() { + +} + func (b *Broker) NextUp() (*core.Packet, error) { return nil, nil } From 7a250ed5a8bc7f296e3a5d22f957141c6141ac89 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 21:10:00 +0100 Subject: [PATCH 0281/2266] [broker] Write broker_test backbone and basic implement of fake ackNacker --- refactored_components/broker_test.go | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 refactored_components/broker_test.go diff --git a/refactored_components/broker_test.go b/refactored_components/broker_test.go new file mode 100644 index 000000000..539df4914 --- /dev/null +++ b/refactored_components/broker_test.go @@ -0,0 +1,38 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/thethingsnetwork/core" + . "github.com/thethingsnetwork/core/utils/testing" + "testing" +) + +type FakeAckNacker struct { + ackGot core.Packet + nackGot core.Packet +} + +func (f *FakeAckNacker) Ack(p core.Packet) error { + f.ackGot = p + return nil +} + +func (f *FakeAckNacker) Nack(p core.Packet) error { + f.nackGot = p + return nil +} + +// The broker can handle an uplink packet +func TestBrokerUplink(t *testing.T) { + // p = validFullMetaPacket() + // p = validPartialMetaPacket() + // p = packetWithoutPayload + // fake AckNacker -> + // broker.HandleUplink(p, an) + // + // checkAckNacker(p.Packet, An.got) + + Ok(t, "Pending") +} From 8ea00f901b2272b4e501f9d67f6b1deb043864dc Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 21:11:00 +0100 Subject: [PATCH 0282/2266] [broker] Write signature and empty methods of broker <-> handler http adapter --- adapters/brk_hdl_http/adapter.go | 31 +++++++++++++++++++++++++++ adapters/brk_hdl_http/adapter_test.go | 6 ++++++ 2 files changed, 37 insertions(+) create mode 100644 adapters/brk_hdl_http/adapter.go create mode 100644 adapters/brk_hdl_http/adapter_test.go diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go new file mode 100644 index 000000000..edcaac891 --- /dev/null +++ b/adapters/brk_hdl_http/adapter.go @@ -0,0 +1,31 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package brk_hdl_http + +import ( + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" +) + +type Adapter struct { +} + +func NewAdapter(port string) (*Adapter, error) { + return nil, nil +} + +// Send implements the core.Adapter interface +func (a *Adapter) Send(p core.Packet, an core.AckNacker) error { + return nil +} + +// Next implements the core.Adapter inerface +func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { + return core.Packet{}, nil, nil +} + +// NextRegistration implements the core.BrkHdlAdapter interface +func (a *Adapter) NextRegistration() (core.Recipient, lorawan.DevAddr, lorawan.AES128Key, error) { + return core.Recipient{}, lorawan.DevAddr{}, lorawan.AES128Key{}, nil +} diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go new file mode 100644 index 000000000..050ff6d0c --- /dev/null +++ b/adapters/brk_hdl_http/adapter_test.go @@ -0,0 +1,6 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package brk_hdl_http + +import () From 7280218912d6d38845cd6c33083f5e73f6349bc5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 21:20:50 +0100 Subject: [PATCH 0283/2266] [broker] Write down tests for NewAdapter() method and adjust wrong signature --- adapters/brk_hdl_http/adapter.go | 5 ++++- adapters/brk_hdl_http/adapter_test.go | 28 ++++++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index edcaac891..0c7afc310 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -4,14 +4,17 @@ package brk_hdl_http import ( + "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" ) +var ErrInvalidPort = fmt.Errorf("The given port is invalid") + type Adapter struct { } -func NewAdapter(port string) (*Adapter, error) { +func NewAdapter(port uint) (*Adapter, error) { return nil, nil } diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index 050ff6d0c..d976b4cb8 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -3,4 +3,30 @@ package brk_hdl_http -import () +import ( + . "github.com/thethingsnetwork/core/utils/testing" + "testing" +) + +func TestNewAdapter(t *testing.T) { + tests := []struct { + Port uint + WantError error + }{ + {3000, nil}, + {0, ErrInvalidPort}, + } + + for _, test := range tests { + _, err := NewAdapter(test.Port) + checkErrors(t, test.WantError, err) + } +} + +func checkErrors(t *testing.T, want error, got error) { + if want == got { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%v} but got {%v}", want, got) +} From dccb7c39974a10487fcf2cef79fdc9ab73b68ec9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 6 Jan 2016 21:33:58 +0100 Subject: [PATCH 0284/2266] [broker] Implement basic NewAdapter --- adapters/brk_hdl_http/adapter.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 0c7afc310..8536b24ea 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -15,7 +15,12 @@ type Adapter struct { } func NewAdapter(port uint) (*Adapter, error) { - return nil, nil + if port == 0 { + return nil, ErrInvalidPort + } + + a := Adapter{} + return &a, nil } // Send implements the core.Adapter interface From 44c1b39bb96e9ac40719e4818fe4ba29cf05f750 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 13:51:48 +0100 Subject: [PATCH 0285/2266] [broker] Update adapter test to NextRegistration() --- adapters/brk_hdl_http/adapter.go | 9 +- adapters/brk_hdl_http/adapter_test.go | 143 +++++++++++++++++++++++++- core.go | 2 +- 3 files changed, 149 insertions(+), 5 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 8536b24ea..40fbfabce 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -14,6 +14,11 @@ var ErrInvalidPort = fmt.Errorf("The given port is invalid") type Adapter struct { } +type Config struct { + Handler core.Recipient + NwsKey lorawan.AES128Key +} + func NewAdapter(port uint) (*Adapter, error) { if port == 0 { return nil, ErrInvalidPort @@ -34,6 +39,6 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { } // NextRegistration implements the core.BrkHdlAdapter interface -func (a *Adapter) NextRegistration() (core.Recipient, lorawan.DevAddr, lorawan.AES128Key, error) { - return core.Recipient{}, lorawan.DevAddr{}, lorawan.AES128Key{}, nil +func (a *Adapter) NextRegistration() (lorawan.DevAddr, interface{}, error) { + return lorawan.DevAddr{}, Config{}, nil } diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index d976b4cb8..a2e0c66aa 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -4,8 +4,16 @@ package brk_hdl_http import ( + "bytes" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/core/utils/log" . "github.com/thethingsnetwork/core/utils/testing" + "net/http" + "reflect" "testing" + "time" ) func TestNewAdapter(t *testing.T) { @@ -23,10 +31,141 @@ func TestNewAdapter(t *testing.T) { } } -func checkErrors(t *testing.T, want error, got error) { +type nextRegistrationTest struct { + AppId string + Handler string + DevAddr string + NwsKey string + WantResult nextRegistrationResult +} + +type nextRegistrationResult struct { + DevAddr lorawan.DevAddr + Config *Config + Error error +} + +func TestNextRegistration(t *testing.T) { + tests := []nextRegistrationTest{ + // Valid device address + { + AppId: "appid", + Handler: "myhandler.com:3000", + NwsKey: "00112233445566778899aabbccddeeff", + DevAddr: "14aab0a4", + WantResult: nextRegistrationResult{ + DevAddr: lorawan.DevAddr([4]byte{14, 0xaa, 0xb0, 0xa4}), + Config: &Config{ + Handler: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, + NwsKey: lorawan.AES128Key([16]byte{00, 11, 22, 33, 44, 55, 66, 77, 88, 99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}), + }, + Error: nil, + }, + }, + // Invalid device address + { + AppId: "appid", + Handler: "myhandler.com:3000", + NwsKey: "00112233445566778899aabbccddeeff", + DevAddr: "INVALID", + WantResult: nextRegistrationResult{ + Config: nil, + Error: nil, + }, + }, + // Invalid nwskey address + { + AppId: "appid", + Handler: "myhandler.com:3000", + NwsKey: "00112233445566778899af", + DevAddr: "14aaab0a4", + WantResult: nextRegistrationResult{ + Config: nil, + Error: nil, + }, + }, + } + + adapter, err := NewAdapter(3001) + client := &client{ + adapter: "0.0.0.0:3001", + c: http.Client{}, + logger: log.TestLogger{Tag: "http client", T: t}, + } + if err != nil { + panic(err) + } + + for _, test := range tests { + client.send(test.Handler, test.DevAddr, test.NwsKey) + res := make(chan nextRegistrationResult) + go func() { + devAddr, itf, err := adapter.NextRegistration() + config := itf.(Config) + res <- nextRegistrationResult{devAddr, &config, err} + }() + + select { + case result := <-res: + checkRegistrationResult(t, test.WantResult, result) + case <-time.After(time.Millisecond * 250): + checkRegistrationResult(t, test.WantResult, nextRegistrationResult{}) + } + } +} + +func checkErrors(t *testing.T, want error, got error) bool { if want == got { Ok(t, "Check errors") - return + return true } Ko(t, "Expected error to be {%v} but got {%v}", want, got) + return false +} + +func checkRegistrationResult(t *testing.T, want nextRegistrationResult, got nextRegistrationResult) bool { + if !checkErrors(t, want.Error, got.Error) { + return false + } + + if want.Config == nil { + if got.Error == nil { + Ko(t, "Was expecting no result but got %v", got.Config) + return false + } + Ok(t, "Check registration result") + return true + } + + if !reflect.DeepEqual(*want.Config, *got.Config) { + Ko(t, "Received configuration doesn't match expectations\nWant: %v\nGot: %v", *want.Config, *got.Config) + return false + } + + if !reflect.DeepEqual(want.DevAddr, got.DevAddr) { + Ko(t, "Expected devAddr to be %+x but got %+x", want.DevAddr, got.DevAddr) + return false + } + + Ok(t, "Check registration result") + return true +} + +type client struct { + c http.Client + logger log.Logger + adapter string +} + +func (c *client) send(handler, devAddr, nwsKey string) { + c.logger.Log("send request to %s", c.adapter) + buf := new(bytes.Buffer) + if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":%s,"url":%s,"nws_key":%s}`, "TestApp", handler, nwsKey)); err != nil { + panic(err) + } + resp, err := c.c.Post(fmt.Sprintf("http://%s", c.adapter), "application/json", buf) + if err != nil { + panic(err) + } + c.logger.Log("response code: %d", resp.StatusCode) } diff --git a/core.go b/core.go index fe118ea63..4a1c221d3 100644 --- a/core.go +++ b/core.go @@ -59,7 +59,7 @@ type Adapter interface { type BrkHdlAdapter interface { Adapter - NextRegistration() (Recipient, lorawan.DevAddr, lorawan.AES128Key, error) + NextRegistration() (lorawan.DevAddr, interface{}, error) } type Router Component From 6e868e90c7eacbc5a974a60f8d37b2fda77e387d Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 15:32:33 +0100 Subject: [PATCH 0286/2266] [broker] Adjust core signature --- core.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core.go b/core.go index 4a1c221d3..3cefcb9e8 100644 --- a/core.go +++ b/core.go @@ -48,24 +48,30 @@ type AckNacker interface { type Component interface { Handle(p Packet, an AckNacker) error - NextUp() (*Packet, error) - NextDown() (*Packet, error) + NextUp() (Packet, error) + NextDown() (Packet, error) } type Adapter interface { Send(p Packet, r ...Recipient) error - Next() (*Packet, *AckNacker, error) + Next() (Packet, AckNacker, error) +} + +type Registration struct { + DevAddr lorawan.DevAddr + Handler Recipient + NwsKey lorawan.AES128Key } type BrkHdlAdapter interface { Adapter - NextRegistration() (lorawan.DevAddr, interface{}, error) + NextRegistration() (Registration, AckNacker, error) } type Router Component type Broker interface { Component - Register(handler Recipient, devAddr lorawan.DevAddr, nwskey lorawan.AES128Key) error + Register(reg Registration) error } type Handler Component type NetworkController Component From d8409bebdb0fe79debae73007e84ae3c4f2c5805 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 15:33:02 +0100 Subject: [PATCH 0287/2266] [broker] Implement first base of http listener --- adapters/brk_hdl_http/adapter.go | 117 +++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 7 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 40fbfabce..7e0e7f7a3 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -4,19 +4,30 @@ package brk_hdl_http import ( + "encoding/hex" + "encoding/json" "fmt" "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan" + "io" + "net/http" + "regexp" + "strings" ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") type Adapter struct { + regs chan regMsg } -type Config struct { - Handler core.Recipient - NwsKey lorawan.AES128Key +type regMsg struct { + config core.Registration + resp chan httpResponse +} + +type httpResponse struct { + statusCode int + content []byte } func NewAdapter(port uint) (*Adapter, error) { @@ -24,7 +35,9 @@ func NewAdapter(port uint) (*Adapter, error) { return nil, ErrInvalidPort } - a := Adapter{} + a := Adapter{ + regs: make(chan regMsg), + } return &a, nil } @@ -39,6 +52,96 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { } // NextRegistration implements the core.BrkHdlAdapter interface -func (a *Adapter) NextRegistration() (lorawan.DevAddr, interface{}, error) { - return lorawan.DevAddr{}, Config{}, nil +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + return core.Registration{}, nil, nil +} + +func (a *Adapter) listen(port uint) { + serveMux := http.NewServeMux() + serveMux.HandleFunc("/end-device/", func(w http.ResponseWriter, req *http.Request) { + // Check Content-type + contentType := req.Header.Get("Content-Type") + if contentType != "application/json" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Bad content type")) + return + } + + // Check the query parameter + reg := regexp.MustCompile("end-device/([a-fA-F0-9]{10})$") + query := reg.FindStringSubmatch(req.RequestURI) + if len(query) < 2 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect end-device address format")) + return + } + devAddr := query[1] + + // Check configuration in body + body := make([]byte, 256) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect request body")) + return + } + body = body[:n] + params := &struct { + Id string `json:"app_id"` + Url string `json:"app_url"` + NwsKey string `json:"nws_key"` + }{} + if err := json.Unmarshal(body, params); err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect body payload")) + return + } + + nwsKey, err := hex.DecodeString(params.NwsKey) + if err != nil || len(nwsKey) != 16 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect network session nws_key")) + return + } + + params.Id = strings.Trim(params.Id, " ") + params.Url = strings.Trim(params.Url, " ") + if len(params.Id) <= 0 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect config app_id")) + return + } + if len(params.Url) <= 0 { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect config app_url")) + return + } + + // Create registration + config := core.Registration{ + Handler: core.Recipient{Id: params.Id, Address: params.Url}, + } + copy(config.NwsKey[:], nwsKey) + copy(config.DevAddr[:], devAddr) + + // Send the registration and wait for ack / nack + resp := make(chan httpResponse) + a.regs <- regMsg{config: config, resp: resp} + r, ok := <-resp + if !ok { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte("Core server not responding")) + return + } + w.WriteHeader(r.statusCode) + w.Write(r.content) + }) + + go func() { + server := http.Server{ + Addr: fmt.Sprintf("localhost:%d", port), + Handler: serveMux, + } + server.ListenAndServe() + }() } From 7eee4faf76f2d60f79f4e0138561ce73e270ac91 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 15:39:56 +0100 Subject: [PATCH 0288/2266] [broker] Add logger to adapter and start listening --- adapters/brk_hdl_http/adapter.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 7e0e7f7a3..5b24932bd 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -8,6 +8,7 @@ import ( "encoding/json" "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/utils/log" "io" "net/http" "regexp" @@ -17,7 +18,8 @@ import ( var ErrInvalidPort = fmt.Errorf("The given port is invalid") type Adapter struct { - regs chan regMsg + logger log.Logger + regs chan regMsg } type regMsg struct { @@ -36,8 +38,12 @@ func NewAdapter(port uint) (*Adapter, error) { } a := Adapter{ - regs: make(chan regMsg), + regs: make(chan regMsg), + logger: log.DebugLogger{Tag: "BRK_HDL_ADAPTER"}, } + + a.listen(port) + return &a, nil } @@ -59,9 +65,11 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) func (a *Adapter) listen(port uint) { serveMux := http.NewServeMux() serveMux.HandleFunc("/end-device/", func(w http.ResponseWriter, req *http.Request) { + a.log("Receive new registration request") // Check Content-type contentType := req.Header.Get("Content-Type") if contentType != "application/json" { + a.log("registration request rejected: not json") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Bad content type")) return @@ -71,6 +79,7 @@ func (a *Adapter) listen(port uint) { reg := regexp.MustCompile("end-device/([a-fA-F0-9]{10})$") query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { + a.log("registration request rejected: devAddr invalid") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Incorrect end-device address format")) return @@ -81,6 +90,7 @@ func (a *Adapter) listen(port uint) { body := make([]byte, 256) n, err := req.Body.Read(body) if err != nil && err != io.EOF { + a.log("registration request rejected: body unreadable") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Incorrect request body")) return @@ -92,6 +102,7 @@ func (a *Adapter) listen(port uint) { NwsKey string `json:"nws_key"` }{} if err := json.Unmarshal(body, params); err != nil { + a.log("registration request rejected: payload invalid or incomplete") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Incorrect body payload")) return @@ -99,6 +110,7 @@ func (a *Adapter) listen(port uint) { nwsKey, err := hex.DecodeString(params.NwsKey) if err != nil || len(nwsKey) != 16 { + a.log("registration request rejected: nwsKey invalid") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Incorrect network session nws_key")) return @@ -107,11 +119,13 @@ func (a *Adapter) listen(port uint) { params.Id = strings.Trim(params.Id, " ") params.Url = strings.Trim(params.Url, " ") if len(params.Id) <= 0 { + a.log("registration request rejected: appId invalid") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Incorrect config app_id")) return } if len(params.Url) <= 0 { + a.log("registration request rejected: appUrl invalid") w.WriteHeader(http.StatusBadRequest) w.Write([]byte("Incorrect config app_url")) return @@ -129,10 +143,12 @@ func (a *Adapter) listen(port uint) { a.regs <- regMsg{config: config, resp: resp} r, ok := <-resp if !ok { + a.log("Unexpected channel closure") w.WriteHeader(http.StatusInternalServerError) w.Write([]byte("Core server not responding")) return } + a.log("sending normal response to registration request") w.WriteHeader(r.statusCode) w.Write(r.content) }) @@ -142,6 +158,14 @@ func (a *Adapter) listen(port uint) { Addr: fmt.Sprintf("localhost:%d", port), Handler: serveMux, } - server.ListenAndServe() + err := server.ListenAndServe() + a.log("HTTP connection lost: %v", err) }() } + +func (a *Adapter) log(format string, i ...interface{}) { + if a == nil || a.logger == nil { + return + } + a.logger.Log(format, i...) +} From 6f4e38281d8cd6a28918c6b3dac77fe54010c6cc Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 15:43:57 +0100 Subject: [PATCH 0289/2266] [broker] Rewrite adapter tests to match new interface --- adapters/brk_hdl_http/adapter_test.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index a2e0c66aa..cffcd3dca 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -40,8 +40,8 @@ type nextRegistrationTest struct { } type nextRegistrationResult struct { - DevAddr lorawan.DevAddr - Config *Config + Config *core.Registration + AckNack core.AckNacker Error error } @@ -54,8 +54,8 @@ func TestNextRegistration(t *testing.T) { NwsKey: "00112233445566778899aabbccddeeff", DevAddr: "14aab0a4", WantResult: nextRegistrationResult{ - DevAddr: lorawan.DevAddr([4]byte{14, 0xaa, 0xb0, 0xa4}), - Config: &Config{ + Config: &core.Registration{ + DevAddr: lorawan.DevAddr([4]byte{14, 0xaa, 0xb0, 0xa4}), Handler: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, NwsKey: lorawan.AES128Key([16]byte{00, 11, 22, 33, 44, 55, 66, 77, 88, 99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}), }, @@ -100,9 +100,8 @@ func TestNextRegistration(t *testing.T) { client.send(test.Handler, test.DevAddr, test.NwsKey) res := make(chan nextRegistrationResult) go func() { - devAddr, itf, err := adapter.NextRegistration() - config := itf.(Config) - res <- nextRegistrationResult{devAddr, &config, err} + config, an, err := adapter.NextRegistration() + res <- nextRegistrationResult{&config, an, err} }() select { @@ -129,7 +128,7 @@ func checkRegistrationResult(t *testing.T, want nextRegistrationResult, got next } if want.Config == nil { - if got.Error == nil { + if got.Error == nil || got.AckNack != nil { Ko(t, "Was expecting no result but got %v", got.Config) return false } @@ -142,8 +141,8 @@ func checkRegistrationResult(t *testing.T, want nextRegistrationResult, got next return false } - if !reflect.DeepEqual(want.DevAddr, got.DevAddr) { - Ko(t, "Expected devAddr to be %+x but got %+x", want.DevAddr, got.DevAddr) + if want.AckNack == nil { + Ko(t, "Received configuration with a nil AckNacker") return false } @@ -160,10 +159,10 @@ type client struct { func (c *client) send(handler, devAddr, nwsKey string) { c.logger.Log("send request to %s", c.adapter) buf := new(bytes.Buffer) - if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":%s,"url":%s,"nws_key":%s}`, "TestApp", handler, nwsKey)); err != nil { + if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":%s,"app_url":%s,"nws_key":%s}`, "TestApp", handler, nwsKey)); err != nil { panic(err) } - resp, err := c.c.Post(fmt.Sprintf("http://%s", c.adapter), "application/json", buf) + resp, err := c.c.Post(fmt.Sprintf("http://%s/end-device", c.adapter), "application/json", buf) if err != nil { panic(err) } From 251abfa2ceb092c8f12569c658bd82bd1c79df72 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 16:00:51 +0100 Subject: [PATCH 0290/2266] [broker] Add delay after adapter creation (wait for connection establishment) + some renaming --- adapters/brk_hdl_http/adapter_test.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index cffcd3dca..a8753ac32 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -33,7 +33,7 @@ func TestNewAdapter(t *testing.T) { type nextRegistrationTest struct { AppId string - Handler string + AppUrl string DevAddr string NwsKey string WantResult nextRegistrationResult @@ -50,7 +50,7 @@ func TestNextRegistration(t *testing.T) { // Valid device address { AppId: "appid", - Handler: "myhandler.com:3000", + AppUrl: "myhandler.com:3000", NwsKey: "00112233445566778899aabbccddeeff", DevAddr: "14aab0a4", WantResult: nextRegistrationResult{ @@ -65,7 +65,7 @@ func TestNextRegistration(t *testing.T) { // Invalid device address { AppId: "appid", - Handler: "myhandler.com:3000", + AppUrl: "myhandler.com:3000", NwsKey: "00112233445566778899aabbccddeeff", DevAddr: "INVALID", WantResult: nextRegistrationResult{ @@ -76,7 +76,7 @@ func TestNextRegistration(t *testing.T) { // Invalid nwskey address { AppId: "appid", - Handler: "myhandler.com:3000", + AppUrl: "myhandler.com:3000", NwsKey: "00112233445566778899af", DevAddr: "14aaab0a4", WantResult: nextRegistrationResult{ @@ -87,17 +87,19 @@ func TestNextRegistration(t *testing.T) { } adapter, err := NewAdapter(3001) + adapter.logger = log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t} client := &client{ adapter: "0.0.0.0:3001", c: http.Client{}, logger: log.TestLogger{Tag: "http client", T: t}, } + <-time.After(time.Millisecond * 200) if err != nil { panic(err) } for _, test := range tests { - client.send(test.Handler, test.DevAddr, test.NwsKey) + client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) res := make(chan nextRegistrationResult) go func() { config, an, err := adapter.NextRegistration() @@ -156,13 +158,13 @@ type client struct { adapter string } -func (c *client) send(handler, devAddr, nwsKey string) { +func (c *client) send(appId, appUrl, devAddr, nwsKey string) { c.logger.Log("send request to %s", c.adapter) buf := new(bytes.Buffer) - if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":%s,"app_url":%s,"nws_key":%s}`, "TestApp", handler, nwsKey)); err != nil { + if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { panic(err) } - resp, err := c.c.Post(fmt.Sprintf("http://%s/end-device", c.adapter), "application/json", buf) + resp, err := c.c.Post(fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), "application/json", buf) if err != nil { panic(err) } From d6d3c367ffffc76b8e2186929d4e676a1130dd6b Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 16:01:20 +0100 Subject: [PATCH 0291/2266] [broker] Add some logs to adapter + fix regex parsing end-device query param --- adapters/brk_hdl_http/adapter.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 5b24932bd..4d556b6eb 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -76,7 +76,7 @@ func (a *Adapter) listen(port uint) { } // Check the query parameter - reg := regexp.MustCompile("end-device/([a-fA-F0-9]{10})$") + reg := regexp.MustCompile("end-device/([a-fA-F0-9]{8})$") query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { a.log("registration request rejected: devAddr invalid") @@ -96,6 +96,7 @@ func (a *Adapter) listen(port uint) { return } body = body[:n] + a.log("registration payload: %s", string(body)) params := &struct { Id string `json:"app_id"` Url string `json:"app_url"` @@ -155,9 +156,10 @@ func (a *Adapter) listen(port uint) { go func() { server := http.Server{ - Addr: fmt.Sprintf("localhost:%d", port), + Addr: fmt.Sprintf("0.0.0.0:%d", port), Handler: serveMux, } + a.log("Start listening on %d", port) err := server.ListenAndServe() a.log("HTTP connection lost: %v", err) }() From 70b63df689a0b07e91e2406b81b6b3b6833ce6b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 18:23:48 +0100 Subject: [PATCH 0292/2266] [broker] Refactor adapter tests. Make them more simple and readable --- adapters/brk_hdl_http/adapter_test.go | 147 +++++++++++--------------- 1 file changed, 62 insertions(+), 85 deletions(-) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index a8753ac32..26bb496a0 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -26,147 +26,124 @@ func TestNewAdapter(t *testing.T) { } for _, test := range tests { + Desc(t, "Create new adapter bound to %d", test.Port) _, err := NewAdapter(test.Port) checkErrors(t, test.WantError, err) } } -type nextRegistrationTest struct { - AppId string - AppUrl string - DevAddr string - NwsKey string - WantResult nextRegistrationResult -} - -type nextRegistrationResult struct { - Config *core.Registration - AckNack core.AckNacker - Error error -} - func TestNextRegistration(t *testing.T) { - tests := []nextRegistrationTest{ + tests := []struct { + AppId string + AppUrl string + DevAddr string + NwsKey string + WantResult *core.Registration + WantError error + }{ // Valid device address { AppId: "appid", AppUrl: "myhandler.com:3000", - NwsKey: "00112233445566778899aabbccddeeff", + NwsKey: "000102030405060708090a0b0c0d0e0f", DevAddr: "14aab0a4", - WantResult: nextRegistrationResult{ - Config: &core.Registration{ - DevAddr: lorawan.DevAddr([4]byte{14, 0xaa, 0xb0, 0xa4}), - Handler: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, - NwsKey: lorawan.AES128Key([16]byte{00, 11, 22, 33, 44, 55, 66, 77, 88, 99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}), - }, - Error: nil, + WantResult: &core.Registration{ + DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), + Handler: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, + NwsKey: lorawan.AES128Key([16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), }, + WantError: nil, }, // Invalid device address { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "00112233445566778899aabbccddeeff", - DevAddr: "INVALID", - WantResult: nextRegistrationResult{ - Config: nil, - Error: nil, - }, + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "000102030405060708090a0b0c0d0e0f", + DevAddr: "INVALID", + WantResult: nil, + WantError: nil, }, // Invalid nwskey address { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "00112233445566778899af", - DevAddr: "14aaab0a4", - WantResult: nextRegistrationResult{ - Config: nil, - Error: nil, - }, + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "00112233445566778899af", + DevAddr: "14aab0a4", + WantResult: nil, + WantError: nil, }, } - adapter, err := NewAdapter(3001) - adapter.logger = log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t} - client := &client{ - adapter: "0.0.0.0:3001", - c: http.Client{}, - logger: log.TestLogger{Tag: "http client", T: t}, - } + adapter, err := NewAdapter(3001, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + client := &client{adapter: "0.0.0.0:3001"} <-time.After(time.Millisecond * 200) if err != nil { panic(err) } for _, test := range tests { - client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) - res := make(chan nextRegistrationResult) + // Describe + Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) + + // Build + gotErr := make(chan error) + gotConf := make(chan core.Registration) + go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) + + // Operate go func() { - config, an, err := adapter.NextRegistration() - res <- nextRegistrationResult{&config, an, err} + config, _, err := adapter.NextRegistration() + gotErr <- err + gotConf <- config }() + // Check select { - case result := <-res: - checkRegistrationResult(t, test.WantResult, result) + case err := <-gotErr: + checkErrors(t, test.WantError, err) case <-time.After(time.Millisecond * 250): - checkRegistrationResult(t, test.WantResult, nextRegistrationResult{}) + checkErrors(t, test.WantError, nil) + } + + select { + case conf := <-gotConf: + checkRegistrationResult(t, test.WantResult, &conf) + case <-time.After(time.Millisecond * 250): + checkRegistrationResult(t, test.WantResult, nil) } } } -func checkErrors(t *testing.T, want error, got error) bool { +func checkErrors(t *testing.T, want error, got error) { if want == got { Ok(t, "Check errors") - return true + return } Ko(t, "Expected error to be {%v} but got {%v}", want, got) - return false } -func checkRegistrationResult(t *testing.T, want nextRegistrationResult, got nextRegistrationResult) bool { - if !checkErrors(t, want.Error, got.Error) { - return false - } - - if want.Config == nil { - if got.Error == nil || got.AckNack != nil { - Ko(t, "Was expecting no result but got %v", got.Config) - return false - } - Ok(t, "Check registration result") - return true - } - - if !reflect.DeepEqual(*want.Config, *got.Config) { - Ko(t, "Received configuration doesn't match expectations\nWant: %v\nGot: %v", *want.Config, *got.Config) - return false - } - - if want.AckNack == nil { - Ko(t, "Received configuration with a nil AckNacker") - return false +func checkRegistrationResult(t *testing.T, want, got *core.Registration) { + if !reflect.DeepEqual(want, got) { + Ko(t, "Received configuration doesn't match expectations") + return } Ok(t, "Check registration result") - return true } type client struct { - c http.Client - logger log.Logger + http.Client adapter string } -func (c *client) send(appId, appUrl, devAddr, nwsKey string) { - c.logger.Log("send request to %s", c.adapter) +func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { buf := new(bytes.Buffer) if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { panic(err) } - resp, err := c.c.Post(fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), "application/json", buf) + resp, err := c.Post(fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), "application/json", buf) if err != nil { panic(err) } - c.logger.Log("response code: %d", resp.StatusCode) + return *resp } From dc53197f4b7d47928fb2d7a14f2a93781fbdd2ee Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 18:26:38 +0100 Subject: [PATCH 0293/2266] [broker] Implement Registration AckNacker + fix issue with DevAddr hex decoding --- adapters/brk_hdl_http/adapter.go | 47 +++++++++++++++++++++++--------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 4d556b6eb..a411a6dbf 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -32,6 +32,23 @@ type httpResponse struct { content []byte } +type regAckNacker struct { + resp chan httpResponse +} + +func (r regAckNacker) Ack(p core.Packet) error { + r.resp <- httpResponse{statusCode: http.StatusOK} + return nil +} + +func (r regAckNacker) Nack(p core.Packet) error { + r.resp <- httpResponse{ + statusCode: http.StatusNotAcceptable, + content: []byte("Unable to register the given device"), + } + return nil +} + func NewAdapter(port uint) (*Adapter, error) { if port == 0 { return nil, ErrInvalidPort @@ -42,7 +59,7 @@ func NewAdapter(port uint) (*Adapter, error) { logger: log.DebugLogger{Tag: "BRK_HDL_ADAPTER"}, } - a.listen(port) + go a.listen(port) return &a, nil } @@ -59,7 +76,8 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { // NextRegistration implements the core.BrkHdlAdapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return core.Registration{}, nil, nil + reg := <-a.regs + return reg.config, regAckNacker{resp: reg.resp}, nil } func (a *Adapter) listen(port uint) { @@ -84,7 +102,13 @@ func (a *Adapter) listen(port uint) { w.Write([]byte("Incorrect end-device address format")) return } - devAddr := query[1] + devAddr, err := hex.DecodeString(query[1]) + if err != nil { + a.log("registration request rejected: devAddr invalid") + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("Incorrect end-device address format")) + return + } // Check configuration in body body := make([]byte, 256) @@ -96,7 +120,6 @@ func (a *Adapter) listen(port uint) { return } body = body[:n] - a.log("registration payload: %s", string(body)) params := &struct { Id string `json:"app_id"` Url string `json:"app_url"` @@ -154,15 +177,13 @@ func (a *Adapter) listen(port uint) { w.Write(r.content) }) - go func() { - server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), - Handler: serveMux, - } - a.log("Start listening on %d", port) - err := server.ListenAndServe() - a.log("HTTP connection lost: %v", err) - }() + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: serveMux, + } + a.log("Start listening on %d", port) + err := server.ListenAndServe() + a.log("HTTP connection lost: %v", err) } func (a *Adapter) log(format string, i ...interface{}) { From de8ba2dd5f0e6dc08b1d977098e84c010c6ce9fc Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 7 Jan 2016 18:27:03 +0100 Subject: [PATCH 0294/2266] [broker] Change the way logger is created, make is concurrent-safe --- adapters/brk_hdl_http/adapter.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index a411a6dbf..c7a8a3c63 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -16,10 +16,11 @@ import ( ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") +var ErrInvalidPacket = fmt.Errorf("The given core packet is invalid") type Adapter struct { - logger log.Logger - regs chan regMsg + loggers []log.Logger + regs chan regMsg } type regMsg struct { @@ -49,14 +50,14 @@ func (r regAckNacker) Nack(p core.Packet) error { return nil } -func NewAdapter(port uint) (*Adapter, error) { +func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { if port == 0 { return nil, ErrInvalidPort } a := Adapter{ - regs: make(chan regMsg), - logger: log.DebugLogger{Tag: "BRK_HDL_ADAPTER"}, + regs: make(chan regMsg), + loggers: loggers, } go a.listen(port) @@ -187,8 +188,7 @@ func (a *Adapter) listen(port uint) { } func (a *Adapter) log(format string, i ...interface{}) { - if a == nil || a.logger == nil { - return + for _, logger := range a.loggers { + logger.Log(format, i...) } - a.logger.Log(format, i...) } From 22abb4d52e361834123e59045d4acbff170cdf5b Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 11:06:16 +0100 Subject: [PATCH 0295/2266] [broker] Refactor broker <-> handler adapter, split logic in files and add helpers --- adapters/brk_hdl_http/adapter.go | 156 ++---------------------- adapters/brk_hdl_http/registrations.go | 159 +++++++++++++++++++++++++ 2 files changed, 168 insertions(+), 147 deletions(-) create mode 100644 adapters/brk_hdl_http/registrations.go diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index c7a8a3c63..8ea414960 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -4,63 +4,31 @@ package brk_hdl_http import ( - "encoding/hex" - "encoding/json" "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/utils/log" - "io" - "net/http" - "regexp" - "strings" ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") -var ErrInvalidPacket = fmt.Errorf("The given core packet is invalid") +var ErrConnectionLost = fmt.Errorf("The connection has been lost") type Adapter struct { - loggers []log.Logger - regs chan regMsg -} - -type regMsg struct { - config core.Registration - resp chan httpResponse -} - -type httpResponse struct { - statusCode int - content []byte -} - -type regAckNacker struct { - resp chan httpResponse -} - -func (r regAckNacker) Ack(p core.Packet) error { - r.resp <- httpResponse{statusCode: http.StatusOK} - return nil -} - -func (r regAckNacker) Nack(p core.Packet) error { - r.resp <- httpResponse{ - statusCode: http.StatusNotAcceptable, - content: []byte("Unable to register the given device"), - } - return nil + loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. + registrations chan regReq // Communication dedicated to incoming registration from handlers } +// NewAdapter constructs and allocate a new Broker <-> Handler http adapter func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { if port == 0 { return nil, ErrInvalidPort } a := Adapter{ - regs: make(chan regMsg), - loggers: loggers, + registrations: make(chan regReq), + loggers: loggers, } - go a.listen(port) + go a.listenRegistration(port) return &a, nil } @@ -77,114 +45,8 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { // NextRegistration implements the core.BrkHdlAdapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - reg := <-a.regs - return reg.config, regAckNacker{resp: reg.resp}, nil -} - -func (a *Adapter) listen(port uint) { - serveMux := http.NewServeMux() - serveMux.HandleFunc("/end-device/", func(w http.ResponseWriter, req *http.Request) { - a.log("Receive new registration request") - // Check Content-type - contentType := req.Header.Get("Content-Type") - if contentType != "application/json" { - a.log("registration request rejected: not json") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Bad content type")) - return - } - - // Check the query parameter - reg := regexp.MustCompile("end-device/([a-fA-F0-9]{8})$") - query := reg.FindStringSubmatch(req.RequestURI) - if len(query) < 2 { - a.log("registration request rejected: devAddr invalid") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect end-device address format")) - return - } - devAddr, err := hex.DecodeString(query[1]) - if err != nil { - a.log("registration request rejected: devAddr invalid") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect end-device address format")) - return - } - - // Check configuration in body - body := make([]byte, 256) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - a.log("registration request rejected: body unreadable") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect request body")) - return - } - body = body[:n] - params := &struct { - Id string `json:"app_id"` - Url string `json:"app_url"` - NwsKey string `json:"nws_key"` - }{} - if err := json.Unmarshal(body, params); err != nil { - a.log("registration request rejected: payload invalid or incomplete") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect body payload")) - return - } - - nwsKey, err := hex.DecodeString(params.NwsKey) - if err != nil || len(nwsKey) != 16 { - a.log("registration request rejected: nwsKey invalid") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect network session nws_key")) - return - } - - params.Id = strings.Trim(params.Id, " ") - params.Url = strings.Trim(params.Url, " ") - if len(params.Id) <= 0 { - a.log("registration request rejected: appId invalid") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect config app_id")) - return - } - if len(params.Url) <= 0 { - a.log("registration request rejected: appUrl invalid") - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte("Incorrect config app_url")) - return - } - - // Create registration - config := core.Registration{ - Handler: core.Recipient{Id: params.Id, Address: params.Url}, - } - copy(config.NwsKey[:], nwsKey) - copy(config.DevAddr[:], devAddr) - - // Send the registration and wait for ack / nack - resp := make(chan httpResponse) - a.regs <- regMsg{config: config, resp: resp} - r, ok := <-resp - if !ok { - a.log("Unexpected channel closure") - w.WriteHeader(http.StatusInternalServerError) - w.Write([]byte("Core server not responding")) - return - } - a.log("sending normal response to registration request") - w.WriteHeader(r.statusCode) - w.Write(r.content) - }) - - server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), - Handler: serveMux, - } - a.log("Start listening on %d", port) - err := server.ListenAndServe() - a.log("HTTP connection lost: %v", err) + request := <-a.registrations + return request.Registration, regAckNacker{response: request.response}, nil } func (a *Adapter) log(format string, i ...interface{}) { diff --git a/adapters/brk_hdl_http/registrations.go b/adapters/brk_hdl_http/registrations.go new file mode 100644 index 000000000..cc67483a8 --- /dev/null +++ b/adapters/brk_hdl_http/registrations.go @@ -0,0 +1,159 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package brk_hdl_http + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "github.com/thethingsnetwork/core" + "io" + "net/http" + "regexp" + "strings" + "time" +) + +type regReq struct { + core.Registration // The actual registration request + response chan regRes // A dedicated channel to send back a response (ack or nack) +} + +type regRes struct { + statusCode int // The response status, 200 for ack 4xx for nack + content []byte // The response content +} + +type regAckNacker struct { + response chan regRes // A channel dedicated to send back a response +} + +// Ack implements the core.Acker interface +func (r regAckNacker) Ack(p core.Packet) error { + select { + case r.response <- regRes{statusCode: http.StatusOK}: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} + +// Nack implements the core.Nacker interface +func (r regAckNacker) Nack(p core.Packet) error { + select { + case r.response <- regRes{ + statusCode: http.StatusConflict, + content: []byte("Unable to register the given device"), + }: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} + +// listenRegistration handles incoming registration request send through http to the broker +func (a *Adapter) listenRegistration(port uint) { + // Create a server multiplexer to handle request + serveMux := http.NewServeMux() + + // So far we only supports one endpoint [PUT] /end-device/:devAddr + serveMux.HandleFunc("/end-device/", a.handlePostEndDevice) + + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: serveMux, + } + a.log("Start listening on %d", port) + err := server.ListenAndServe() + a.log("HTTP connection lost: %v", err) +} + +// fail logs the given failure and sends an appropriate response to the client +func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { + a.log("registration request rejected: %s", msg) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(msg)) +} + +// handle request [PUT] on /end-device/:devAddr +func (a *Adapter) handlePostEndDevice(w http.ResponseWriter, req *http.Request) { + a.log("Receive new registration request") + // Check the http method + if req.Method != "PUT" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [PUT] to register a device")) + return + } + + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + a.badRequest(w, "Incorrect content type") + return + } + + // Check the query parameter + reg := regexp.MustCompile("end-device/([a-fA-F0-9]{8})$") + query := reg.FindStringSubmatch(req.RequestURI) + if len(query) < 2 { + a.badRequest(w, "Incorrect end-device address format") + return + } + devAddr, err := hex.DecodeString(query[1]) + if err != nil { + a.badRequest(w, "Incorrect end-device address format") + return + } + + // Check configuration in body + body := make([]byte, 256) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + a.badRequest(w, "Incorrect request body") + return + } + params := &struct { + Id string `json:"app_id"` + Url string `json:"app_url"` + NwsKey string `json:"nws_key"` + }{} + if err := json.Unmarshal(body[:n], params); err != nil { + a.badRequest(w, "Incorrect body payload") + return + } + + nwsKey, err := hex.DecodeString(params.NwsKey) + if err != nil || len(nwsKey) != 16 { + a.badRequest(w, "Incorrect network sesssion key") + return + } + + params.Id = strings.Trim(params.Id, " ") + params.Url = strings.Trim(params.Url, " ") + if len(params.Id) <= 0 { + a.badRequest(w, "Incorrect application id") + return + } + if len(params.Url) <= 0 { + a.badRequest(w, "Incorrect application url") + return + } + + // Create registration + config := core.Registration{ + Handler: core.Recipient{Id: params.Id, Address: params.Url}, + } + copy(config.NwsKey[:], nwsKey) + copy(config.DevAddr[:], devAddr) + + // Send the registration and wait for ack / nack + response := make(chan regRes) + a.registrations <- regReq{Registration: config, response: response} + r, ok := <-response + if !ok { + a.badRequest(w, "Core server not responding") + return + } + w.WriteHeader(r.statusCode) + w.Write(r.content) +} From 8d2b40e1fb491a11b0ed70ece38cd4f2203826bf Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 11:06:34 +0100 Subject: [PATCH 0296/2266] [broker] Adjust adapter test as the API method has changed to PUT --- adapters/brk_hdl_http/adapter_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index 26bb496a0..7f4b40bec 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -141,7 +141,12 @@ func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { panic(err) } - resp, err := c.Post(fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), "application/json", buf) + request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), buf) + if err != nil { + panic(err) + } + request.Header.Set("Content-Type", "application/json") + resp, err := c.Do(request) if err != nil { panic(err) } From d5be1c8736b36c716b73b22e9e349bdf2171cf4d Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 11:12:56 +0100 Subject: [PATCH 0297/2266] [broker] Change wrong signature for Ack method --- adapters/brk_hdl_http/adapter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index 8ea414960..ba0e8a5a2 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -34,7 +34,7 @@ func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { } // Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, an core.AckNacker) error { +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { return nil } From 90742c1f77276545b02101412f08c2b1f0681598 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 11:16:51 +0100 Subject: [PATCH 0298/2266] [broker] Add method signatures as comments in adapter_test + write skeleton of TestSend() --- adapters/brk_hdl_http/adapter_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index 7f4b40bec..de0620a98 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -16,6 +16,7 @@ import ( "time" ) +// NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) func TestNewAdapter(t *testing.T) { tests := []struct { Port uint @@ -32,6 +33,7 @@ func TestNewAdapter(t *testing.T) { } } +// NextRegistration() (core.Registration, core.AckNacker, error) func TestNextRegistration(t *testing.T) { tests := []struct { AppId string @@ -114,6 +116,10 @@ func TestNextRegistration(t *testing.T) { } } +// Send(p core.Packet, r ...core.Recipient) error +func TestSend(t *testing.T) { +} + func checkErrors(t *testing.T, want error, got error) { if want == got { Ok(t, "Check errors") From 5eabcedfa3171eaef0257da2c8266b3a74b6b34b Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 14:01:54 +0100 Subject: [PATCH 0299/2266] [broker] Remove field from metadata --- refactored_components/build_utilities_test.go | 1 - refactored_components/metadata.go | 1 - 2 files changed, 2 deletions(-) diff --git a/refactored_components/build_utilities_test.go b/refactored_components/build_utilities_test.go index 468c39250..145cf1cf6 100644 --- a/refactored_components/build_utilities_test.go +++ b/refactored_components/build_utilities_test.go @@ -48,7 +48,6 @@ func genMetadata(RXPK semtech.RXPK) Metadata { return Metadata{ Chan: RXPK.Chan, Codr: RXPK.Codr, - Data: RXPK.Data, Freq: RXPK.Freq, Lsnr: RXPK.Lsnr, Modu: RXPK.Modu, diff --git a/refactored_components/metadata.go b/refactored_components/metadata.go index 3c4bf4173..cd121504a 100644 --- a/refactored_components/metadata.go +++ b/refactored_components/metadata.go @@ -14,7 +14,6 @@ import ( type Metadata struct { Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) From 92b6f2f20e28ef80ffe7e7d722470cbc47949c9e Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 14:02:47 +0100 Subject: [PATCH 0300/2266] [broker] Write test for upadater Send() method --- adapters/brk_hdl_http/adapter_test.go | 133 ++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index de0620a98..4c6e8c3a8 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -8,7 +8,9 @@ import ( "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" + components "github.com/thethingsnetwork/core/refactored_components" "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" "net/http" "reflect" @@ -118,6 +120,41 @@ func TestNextRegistration(t *testing.T) { // Send(p core.Packet, r ...core.Recipient) error func TestSend(t *testing.T) { + tests := []struct { + Packet core.Packet + WantPayload string + WantError error + }{ + { + core.Packet{ + Payload: genPHYPayload("myData"), + Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, + }, + `{"metadata":{"rssi":-20,"modu":"LORA"},"payload":"myData"}`, + nil, + }, + { + core.Packet{ + Payload: lorawan.PHYPayload{}, + Metadata: &components.Metadata{}, + }, + "", + ErrInvalidPacket, + }, + } + + s := genMockServer(3100) + adapter, err := NewAdapter(3101, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + if err != nil { + panic(err) + } + + for _, test := range tests { + Desc(t, "Sending packet: %v", test.Packet) + err := adapter.Send(test.Packet, s.Recipient) + checkErrors(t, test.WantError, err) + checkSend(t, test.WantPayload, s) + } } func checkErrors(t *testing.T, want error, got error) { @@ -137,11 +174,31 @@ func checkRegistrationResult(t *testing.T, want, got *core.Registration) { Ok(t, "Check registration result") } +func checkSend(t *testing.T, want string, s MockServer) { + select { + case got := <-s.Payloads: + if want != got { + Ko(t, "Expected payload %s to be sent but got %s", want, got) + return + } + case <-time.After(time.Millisecond * 100): + if want != "" { + Ko(t, "Expected payload %s to be sent but got nothing", want) + return + } + } + Ok(t, "Check send result") +} + +// Operate utilities + +// Wrapper around the http client type client struct { http.Client adapter string } +// send is a convinient helper to send HTTP from a handler to the adapter func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { buf := new(bytes.Buffer) if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { @@ -158,3 +215,79 @@ func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { } return *resp } + +// Build utilities + +type MockServer struct { + Recipient core.Recipient + Payloads chan string +} + +func genMockServer(port uint) MockServer { + addr := fmt.Sprintf("0.0.0.0:%s", port) + payloads := make(chan string) + + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + body := make([]byte, 256) + n, err := req.Body.Read(body) + if err != nil { + panic(err) + } + payloads <- string(body[:n]) + }) + + go func() { + server := http.Server{ + Handler: serveMux, + Addr: addr, + } + server.ListenAndServe() + }() + + <-time.After(time.Millisecond * 50) + + return MockServer{ + Recipient: core.Recipient{ + Address: addr, + Id: "Mock server", + }, + Payloads: payloads, + } +} + +// Generate a Physical payload representing an uplink message +func genPHYPayload(msg string) lorawan.PHYPayload { + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(msg)}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + return payload +} From 741d9563644cc2013706852deeed24d31c9d6fb1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 15:49:05 +0100 Subject: [PATCH 0301/2266] [broker] Change core signature of DevAddr(). The method should also change since PR has been refused --- core.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/core.go b/core.go index 3cefcb9e8..1ff600d72 100644 --- a/core.go +++ b/core.go @@ -13,12 +13,8 @@ type Packet struct { Payload lorawan.PHYPayload } -func (p Packet) DevAddr() *lorawan.DevAddr { - addr, err := p.Payload.DevAddr() - if err != nil { - return nil - } - return &addr +func (p Packet) DevAddr() (lorawan.DevAddr, error) { + return p.Payload.DevAddr() } func (p Packet) String() string { From 47b9c54aa45836dc176ab97963f64645afbe05bf Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 15:49:29 +0100 Subject: [PATCH 0302/2266] [broker] enhance and remove typos from adapter_Test --- adapters/brk_hdl_http/adapter_test.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go index 4c6e8c3a8..368ecef61 100644 --- a/adapters/brk_hdl_http/adapter_test.go +++ b/adapters/brk_hdl_http/adapter_test.go @@ -5,6 +5,7 @@ package brk_hdl_http import ( "bytes" + "encoding/base64" "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" @@ -12,6 +13,7 @@ import ( "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" + "io" "net/http" "reflect" "testing" @@ -120,6 +122,13 @@ func TestNextRegistration(t *testing.T) { // Send(p core.Packet, r ...core.Recipient) error func TestSend(t *testing.T) { + payload := genPHYPayload("mData", [4]byte{0x1, 0x2, 0x3, 0x4}) + raw, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + encoded := base64.StdEncoding.EncodeToString(raw) + tests := []struct { Packet core.Packet WantPayload string @@ -127,10 +136,10 @@ func TestSend(t *testing.T) { }{ { core.Packet{ - Payload: genPHYPayload("myData"), + Payload: payload, Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, }, - `{"metadata":{"rssi":-20,"modu":"LORA"},"payload":"myData"}`, + fmt.Sprintf(`{"payload":"%s","metadata":{"modu":"LORA","rssi":-20}}`, encoded), nil, }, { @@ -178,7 +187,7 @@ func checkSend(t *testing.T, want string, s MockServer) { select { case got := <-s.Payloads: if want != got { - Ko(t, "Expected payload %s to be sent but got %s", want, got) + Ko(t, "Received payload does not match expectations.\nWant: %s\nGot: %s", want, got) return } case <-time.After(time.Millisecond * 100): @@ -224,17 +233,18 @@ type MockServer struct { } func genMockServer(port uint) MockServer { - addr := fmt.Sprintf("0.0.0.0:%s", port) + addr := fmt.Sprintf("0.0.0.0:%d", port) payloads := make(chan string) serveMux := http.NewServeMux() serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { body := make([]byte, 256) n, err := req.Body.Read(body) - if err != nil { + if err != nil && err != io.EOF { panic(err) } - payloads <- string(body[:n]) + w.Write(nil) + go func() { payloads <- string(body[:n]) }() }) go func() { @@ -257,13 +267,13 @@ func genMockServer(port uint) MockServer { } // Generate a Physical payload representing an uplink message -func genPHYPayload(msg string) lorawan.PHYPayload { +func genPHYPayload(msg string, devAddr [4]byte) lorawan.PHYPayload { nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} macPayload := lorawan.NewMACPayload(true) macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + DevAddr: lorawan.DevAddr(devAddr), FCtrl: lorawan.FCtrl{ ADR: false, ADRACKReq: false, From ccbee43653165ab5ca717ba33ab9a4ee04d04bb7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Jan 2016 15:49:50 +0100 Subject: [PATCH 0303/2266] [broker] Implement send method --- adapters/brk_hdl_http/adapter.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go index ba0e8a5a2..fe0cecfa1 100644 --- a/adapters/brk_hdl_http/adapter.go +++ b/adapters/brk_hdl_http/adapter.go @@ -4,15 +4,21 @@ package brk_hdl_http import ( + "bytes" + "encoding/base64" + "encoding/json" "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/utils/log" + "net/http" ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") +var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrConnectionLost = fmt.Errorf("The connection has been lost") type Adapter struct { + client http.Client loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. registrations chan regReq // Communication dedicated to incoming registration from handlers } @@ -35,6 +41,31 @@ func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { + metadata, err := json.Marshal(p.Metadata) + if err != nil { + return ErrInvalidPacket + } + + payload, err := p.Payload.MarshalBinary() + if err != nil { + return ErrInvalidPacket + } + base64Payload := base64.StdEncoding.EncodeToString(payload) + + var errors []error + for _, recipient := range r { + buf := new(bytes.Buffer) + buf.Write([]byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64Payload, metadata))) + a.log("Post to %v", recipient) + resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + if err != nil || resp.StatusCode != http.StatusOK { + errors = append(errors, err) + } + } + + if errors != nil { + return fmt.Errorf("Errors: %v", errors) + } return nil } From 883c789ba76d316cb72c0e978d79da5fba6cc0a1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 17:16:36 +0100 Subject: [PATCH 0304/2266] [broker] Split and untight brk_hdl_http adapter into an less specific http adapter --- adapters/http/adapter.go | 93 ++++++++++ adapters/http/adapter_test.go | 303 ++++++++++++++++++++++++++++++++ adapters/http/handler_parser.go | 71 ++++++++ adapters/http/registrations.go | 107 +++++++++++ 4 files changed, 574 insertions(+) create mode 100644 adapters/http/adapter.go create mode 100644 adapters/http/adapter_test.go create mode 100644 adapters/http/handler_parser.go create mode 100644 adapters/http/registrations.go diff --git a/adapters/http/adapter.go b/adapters/http/adapter.go new file mode 100644 index 000000000..80a786460 --- /dev/null +++ b/adapters/http/adapter.go @@ -0,0 +1,93 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/utils/log" + "net/http" +) + +var ErrInvalidPort = fmt.Errorf("The given port is invalid") +var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") +var ErrConnectionLost = fmt.Errorf("The connection has been lost") + +type Adapter struct { + Parser + client http.Client + loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. + registrations chan regReq // Communication dedicated to incoming registration +} + +type Parser interface { + Parse(req *http.Request) (core.Registration, error) +} + +// NewAdapter constructs and allocate a new Broker <-> Handler http adapter +func NewAdapter(port uint, parser Parser, loggers ...log.Logger) (*Adapter, error) { + if port == 0 { + return nil, ErrInvalidPort + } + + a := Adapter{ + Parser: parser, + registrations: make(chan regReq), + loggers: loggers, + } + + go a.listenRegistration(port) + + return &a, nil +} + +// Send implements the core.Adapter interface +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { + metadata, err := json.Marshal(p.Metadata) + if err != nil { + return ErrInvalidPacket + } + + payload, err := p.Payload.MarshalBinary() + if err != nil { + return ErrInvalidPacket + } + base64Payload := base64.StdEncoding.EncodeToString(payload) + + var errors []error + for _, recipient := range r { + buf := new(bytes.Buffer) + buf.Write([]byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64Payload, metadata))) + a.log("Post to %v", recipient) + resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + if err != nil || resp.StatusCode != http.StatusOK { + errors = append(errors, err) + } + } + + if errors != nil { + return fmt.Errorf("Errors: %v", errors) + } + return nil +} + +// Next implements the core.Adapter interface +func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { + return core.Packet{}, nil, nil +} + +// NextRegistration implements the core.Adapter interface +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + request := <-a.registrations + return request.Registration, regAckNacker{response: request.response}, nil +} + +func (a *Adapter) log(format string, i ...interface{}) { + for _, logger := range a.loggers { + logger.Log(format, i...) + } +} diff --git a/adapters/http/adapter_test.go b/adapters/http/adapter_test.go new file mode 100644 index 000000000..6bcd79f5e --- /dev/null +++ b/adapters/http/adapter_test.go @@ -0,0 +1,303 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "bytes" + "encoding/base64" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" + . "github.com/thethingsnetwork/core/utils/testing" + "io" + "net/http" + "reflect" + "testing" + "time" +) + +// NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) +func TestNewAdapter(t *testing.T) { + tests := []struct { + Port uint + WantError error + }{ + {3000, nil}, + {0, ErrInvalidPort}, + } + + for _, test := range tests { + Desc(t, "Create new adapter bound to %d", test.Port) + _, err := NewAdapter(test.Port, HandlerParser{}) + checkErrors(t, test.WantError, err) + } +} + +// NextRegistration() (core.Registration, core.AckNacker, error) +func TestNextRegistration(t *testing.T) { + tests := []struct { + AppId string + AppUrl string + DevAddr string + NwsKey string + WantResult *core.Registration + WantError error + }{ + // Valid device address + { + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "000102030405060708090a0b0c0d0e0f", + DevAddr: "14aab0a4", + WantResult: &core.Registration{ + DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), + Recipient: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, + Options: lorawan.AES128Key([16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), + }, + WantError: nil, + }, + // Invalid device address + { + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "000102030405060708090a0b0c0d0e0f", + DevAddr: "INVALID", + WantResult: nil, + WantError: nil, + }, + // Invalid nwskey address + { + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "00112233445566778899af", + DevAddr: "14aab0a4", + WantResult: nil, + WantError: nil, + }, + } + + adapter, err := NewAdapter(3001, HandlerParser{}, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + client := &client{adapter: "0.0.0.0:3001"} + <-time.After(time.Millisecond * 200) + if err != nil { + panic(err) + } + + for _, test := range tests { + // Describe + Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) + + // Build + gotErr := make(chan error) + gotConf := make(chan core.Registration) + go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) + + // Operate + go func() { + config, _, err := adapter.NextRegistration() + gotErr <- err + gotConf <- config + }() + + // Check + select { + case err := <-gotErr: + checkErrors(t, test.WantError, err) + case <-time.After(time.Millisecond * 250): + checkErrors(t, test.WantError, nil) + } + + select { + case conf := <-gotConf: + checkRegistrationResult(t, test.WantResult, &conf) + case <-time.After(time.Millisecond * 250): + checkRegistrationResult(t, test.WantResult, nil) + } + } +} + +// Send(p core.Packet, r ...core.Recipient) error +func TestSend(t *testing.T) { + payload := genPHYPayload("mData", [4]byte{0x1, 0x2, 0x3, 0x4}) + raw, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + encoded := base64.StdEncoding.EncodeToString(raw) + + tests := []struct { + Packet core.Packet + WantPayload string + WantError error + }{ + { + core.Packet{ + Payload: payload, + Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, + }, + fmt.Sprintf(`{"payload":"%s","metadata":{"modu":"LORA","rssi":-20}}`, encoded), + nil, + }, + { + core.Packet{ + Payload: lorawan.PHYPayload{}, + Metadata: &components.Metadata{}, + }, + "", + ErrInvalidPacket, + }, + } + + s := genMockServer(3100) + adapter, err := NewAdapter(3101, HandlerParser{}, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + if err != nil { + panic(err) + } + + for _, test := range tests { + Desc(t, "Sending packet: %v", test.Packet) + err := adapter.Send(test.Packet, s.Recipient) + checkErrors(t, test.WantError, err) + checkSend(t, test.WantPayload, s) + } +} + +func checkErrors(t *testing.T, want error, got error) { + if want == got { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%v} but got {%v}", want, got) +} + +func checkRegistrationResult(t *testing.T, want, got *core.Registration) { + if !reflect.DeepEqual(want, got) { + Ko(t, "Received configuration doesn't match expectations") + return + } + + Ok(t, "Check registration result") +} + +func checkSend(t *testing.T, want string, s MockServer) { + select { + case got := <-s.Payloads: + if want != got { + Ko(t, "Received payload does not match expectations.\nWant: %s\nGot: %s", want, got) + return + } + case <-time.After(time.Millisecond * 100): + if want != "" { + Ko(t, "Expected payload %s to be sent but got nothing", want) + return + } + } + Ok(t, "Check send result") +} + +// Operate utilities + +// Wrapper around the http client +type client struct { + http.Client + adapter string +} + +// send is a convinient helper to send HTTP from a handler to the adapter +func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { + buf := new(bytes.Buffer) + if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { + panic(err) + } + request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), buf) + if err != nil { + panic(err) + } + request.Header.Set("Content-Type", "application/json") + resp, err := c.Do(request) + if err != nil { + panic(err) + } + return *resp +} + +// Build utilities + +type MockServer struct { + Recipient core.Recipient + Payloads chan string +} + +func genMockServer(port uint) MockServer { + addr := fmt.Sprintf("0.0.0.0:%d", port) + payloads := make(chan string) + + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + body := make([]byte, 256) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + panic(err) + } + w.Write(nil) + go func() { payloads <- string(body[:n]) }() + }) + + go func() { + server := http.Server{ + Handler: serveMux, + Addr: addr, + } + server.ListenAndServe() + }() + + <-time.After(time.Millisecond * 50) + + return MockServer{ + Recipient: core.Recipient{ + Address: addr, + Id: "Mock server", + }, + Payloads: payloads, + } +} + +// Generate a Physical payload representing an uplink message +func genPHYPayload(msg string, devAddr [4]byte) lorawan.PHYPayload { + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr(devAddr), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(msg)}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + return payload +} diff --git a/adapters/http/handler_parser.go b/adapters/http/handler_parser.go new file mode 100644 index 000000000..eb4a650ce --- /dev/null +++ b/adapters/http/handler_parser.go @@ -0,0 +1,71 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "io" + "net/http" + "regexp" + "strings" +) + +type HandlerParser struct{} + +func (p HandlerParser) Parse(req *http.Request) (core.Registration, error) { + // Check the query parameter + reg := regexp.MustCompile("end-device/([a-fA-F0-9]{8})$") + query := reg.FindStringSubmatch(req.RequestURI) + if len(query) < 2 { + return core.Registration{}, fmt.Errorf("Incorrect end-device address format") + } + devAddr, err := hex.DecodeString(query[1]) + if err != nil { + return core.Registration{}, err + } + + // Check configuration in body + body := make([]byte, req.ContentLength) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + return core.Registration{}, err + } + params := &struct { + Id string `json:"app_id"` + Url string `json:"app_url"` + NwsKey string `json:"nws_key"` + }{} + if err := json.Unmarshal(body[:n], params); err != nil { + return core.Registration{}, err + } + + nwsKey, err := hex.DecodeString(params.NwsKey) + if err != nil || len(nwsKey) != 16 { + return core.Registration{}, fmt.Errorf("Incorrect network session key") + } + + params.Id = strings.Trim(params.Id, " ") + params.Url = strings.Trim(params.Url, " ") + if len(params.Id) <= 0 { + return core.Registration{}, fmt.Errorf("Incorrect application id") + } + if len(params.Url) <= 0 { + return core.Registration{}, fmt.Errorf("Incorrect application url") + } + + // Create registration + config := core.Registration{ + Recipient: core.Recipient{Id: params.Id, Address: params.Url}, + } + options := lorawan.AES128Key{} + copy(options[:], nwsKey) + config.Options = options + copy(config.DevAddr[:], devAddr) + + return config, nil +} diff --git a/adapters/http/registrations.go b/adapters/http/registrations.go new file mode 100644 index 000000000..a7f0fda4c --- /dev/null +++ b/adapters/http/registrations.go @@ -0,0 +1,107 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "net/http" + "time" +) + +type regReq struct { + core.Registration // The actual registration request + response chan regRes // A dedicated channel to send back a response (ack or nack) +} + +type regRes struct { + statusCode int // The response status, 200 for ack 4xx for nack + content []byte // The response content +} + +type regAckNacker struct { + response chan regRes // A channel dedicated to send back a response +} + +// Ack implements the core.Acker interface +func (r regAckNacker) Ack(p core.Packet) error { + select { + case r.response <- regRes{statusCode: http.StatusOK}: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} + +// Nack implements the core.Nacker interface +func (r regAckNacker) Nack(p core.Packet) error { + select { + case r.response <- regRes{ + statusCode: http.StatusConflict, + content: []byte("Unable to register the given device"), + }: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} + +// listenRegistration handles incoming registration request sent through http to the adapter +func (a *Adapter) listenRegistration(port uint) { + // Create a server multiplexer to handle request + serveMux := http.NewServeMux() + + // So far we only supports one endpoint [PUT] /end-device/:devAddr + serveMux.HandleFunc("/end-device/", a.handlePutEndDevice) + + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: serveMux, + } + a.log("Start listening on %d", port) + err := server.ListenAndServe() + a.log("HTTP connection lost: %v", err) +} + +// fail logs the given failure and sends an appropriate response to the client +func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { + a.log("registration request rejected: %s", msg) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(msg)) +} + +// handle request [PUT] on /end-device/:devAddr +func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { + a.log("Receive new registration request") + // Check the http method + if req.Method != "PUT" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [PUT] to register a device")) + return + } + + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + a.badRequest(w, "Incorrect content type") + return + } + + // Parse body and query params + config, err := a.Parse(req) + if err != nil { + a.badRequest(w, err.Error()) + return + } + + // Send the registration and wait for ack / nack + response := make(chan regRes) + a.registrations <- regReq{Registration: config, response: response} + r, ok := <-response + if !ok { + a.badRequest(w, "Core server not responding") + return + } + w.WriteHeader(r.statusCode) + w.Write(r.content) +} From b6582ac306dd771f6dce8e8f501e30d283ce905a Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 18:06:56 +0100 Subject: [PATCH 0305/2266] [broker] rename package in httpregister --- adapters/{http => httpregister}/adapter.go | 50 ++++++++++++++----- .../{http => httpregister}/adapter_test.go | 2 +- .../{http => httpregister}/handler_parser.go | 2 +- .../{http => httpregister}/registrations.go | 2 +- 4 files changed, 40 insertions(+), 16 deletions(-) rename adapters/{http => httpregister}/adapter.go (65%) rename adapters/{http => httpregister}/adapter_test.go (99%) rename adapters/{http => httpregister}/handler_parser.go (98%) rename adapters/{http => httpregister}/registrations.go (99%) diff --git a/adapters/http/adapter.go b/adapters/httpregister/adapter.go similarity index 65% rename from adapters/http/adapter.go rename to adapters/httpregister/adapter.go index 80a786460..b073eb5f2 100644 --- a/adapters/http/adapter.go +++ b/adapters/httpregister/adapter.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package http +package httpregister import ( "bytes" @@ -11,6 +11,7 @@ import ( "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/utils/log" "net/http" + "sync" ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") @@ -47,28 +48,50 @@ func NewAdapter(port uint, parser Parser, loggers ...log.Logger) (*Adapter, erro // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { - metadata, err := json.Marshal(p.Metadata) + // Generate payload from core packet + m, err := json.Marshal(p.Metadata) if err != nil { return ErrInvalidPacket } - - payload, err := p.Payload.MarshalBinary() + pl, err := p.Payload.MarshalBinary() if err != nil { return ErrInvalidPacket } - base64Payload := base64.StdEncoding.EncodeToString(payload) + payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) - var errors []error + // Prepare ground for parrallel http request + nb := len(r) + cherr := make(chan error, nb) + wg := sync.WaitGroup{} + wg.Add(nb) + + // Run each request for _, recipient := range r { - buf := new(bytes.Buffer) - buf.Write([]byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64Payload, metadata))) - a.log("Post to %v", recipient) - resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) - if err != nil || resp.StatusCode != http.StatusOK { - errors = append(errors, err) - } + go func(recipient core.Recipient) { + defer wg.Done() + a.log("Post to %v", recipient) + buf := new(bytes.Buffer) + buf.Write([]byte(payload)) + resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + if err != nil { + // Non-blocking, buffered + cherr <- err + return + } + if resp.StatusCode != http.StatusOK { + // Non-blocking, buffered + cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) + return + } + }(recipient) } + // Wait for each request to be done, and return + wg.Wait() + var errors []error + for i := 0; i < len(cherr); i += 1 { + errors = append(errors, <-cherr) + } if errors != nil { return fmt.Errorf("Errors: %v", errors) } @@ -77,6 +100,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { // Next implements the core.Adapter interface func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { + // NOTE not implemented return core.Packet{}, nil, nil } diff --git a/adapters/http/adapter_test.go b/adapters/httpregister/adapter_test.go similarity index 99% rename from adapters/http/adapter_test.go rename to adapters/httpregister/adapter_test.go index 6bcd79f5e..27d7c91ef 100644 --- a/adapters/http/adapter_test.go +++ b/adapters/httpregister/adapter_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package http +package httpregister import ( "bytes" diff --git a/adapters/http/handler_parser.go b/adapters/httpregister/handler_parser.go similarity index 98% rename from adapters/http/handler_parser.go rename to adapters/httpregister/handler_parser.go index eb4a650ce..6ccd2803f 100644 --- a/adapters/http/handler_parser.go +++ b/adapters/httpregister/handler_parser.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package http +package httpregister import ( "encoding/hex" diff --git a/adapters/http/registrations.go b/adapters/httpregister/registrations.go similarity index 99% rename from adapters/http/registrations.go rename to adapters/httpregister/registrations.go index a7f0fda4c..723dea7b3 100644 --- a/adapters/http/registrations.go +++ b/adapters/httpregister/registrations.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package http +package httpregister import ( "fmt" From b0857c4bfc4a6e26a57190604798e7112c37171b Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 18:48:49 +0100 Subject: [PATCH 0306/2266] [broker] Change again folder structure and file splitting for adapters. --- adapters/{httpregister => http}/adapter.go | 39 +---- .../{httpregister => http}/adapter_test.go | 143 +----------------- .../registrations}/handler_parser.go | 2 +- adapters/http/registrations/reg_acknacker.go | 40 +++++ .../registrations}/registrations.go | 62 ++++---- .../http/registrations/registrations_test.go | 143 ++++++++++++++++++ 6 files changed, 229 insertions(+), 200 deletions(-) rename adapters/{httpregister => http}/adapter.go (67%) rename adapters/{httpregister => http}/adapter_test.go (51%) rename adapters/{httpregister => http/registrations}/handler_parser.go (98%) create mode 100644 adapters/http/registrations/reg_acknacker.go rename adapters/{httpregister => http/registrations}/registrations.go (65%) create mode 100644 adapters/http/registrations/registrations_test.go diff --git a/adapters/httpregister/adapter.go b/adapters/http/adapter.go similarity index 67% rename from adapters/httpregister/adapter.go rename to adapters/http/adapter.go index b073eb5f2..8b2ab5556 100644 --- a/adapters/httpregister/adapter.go +++ b/adapters/http/adapter.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package httpregister +package http import ( "bytes" @@ -16,34 +16,15 @@ import ( var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") -var ErrConnectionLost = fmt.Errorf("The connection has been lost") type Adapter struct { - Parser - client http.Client - loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. - registrations chan regReq // Communication dedicated to incoming registration -} - -type Parser interface { - Parse(req *http.Request) (core.Registration, error) + client http.Client + loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. } // NewAdapter constructs and allocate a new Broker <-> Handler http adapter -func NewAdapter(port uint, parser Parser, loggers ...log.Logger) (*Adapter, error) { - if port == 0 { - return nil, ErrInvalidPort - } - - a := Adapter{ - Parser: parser, - registrations: make(chan regReq), - loggers: loggers, - } - - go a.listenRegistration(port) - - return &a, nil +func NewAdapter(loggers ...log.Logger) (*Adapter, error) { + return &Adapter{loggers: loggers}, nil } // Send implements the core.Adapter interface @@ -69,7 +50,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { for _, recipient := range r { go func(recipient core.Recipient) { defer wg.Done() - a.log("Post to %v", recipient) + a.Log("Post to %v", recipient) buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) @@ -104,13 +85,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { return core.Packet{}, nil, nil } -// NextRegistration implements the core.Adapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - request := <-a.registrations - return request.Registration, regAckNacker{response: request.response}, nil -} - -func (a *Adapter) log(format string, i ...interface{}) { +func (a *Adapter) Log(format string, i ...interface{}) { for _, logger := range a.loggers { logger.Log(format, i...) } diff --git a/adapters/httpregister/adapter_test.go b/adapters/http/adapter_test.go similarity index 51% rename from adapters/httpregister/adapter_test.go rename to adapters/http/adapter_test.go index 27d7c91ef..7070fb2d1 100644 --- a/adapters/httpregister/adapter_test.go +++ b/adapters/http/adapter_test.go @@ -1,10 +1,9 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package httpregister +package http import ( - "bytes" "encoding/base64" "fmt" "github.com/thethingsnetwork/core" @@ -15,111 +14,10 @@ import ( . "github.com/thethingsnetwork/core/utils/testing" "io" "net/http" - "reflect" "testing" "time" ) -// NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) -func TestNewAdapter(t *testing.T) { - tests := []struct { - Port uint - WantError error - }{ - {3000, nil}, - {0, ErrInvalidPort}, - } - - for _, test := range tests { - Desc(t, "Create new adapter bound to %d", test.Port) - _, err := NewAdapter(test.Port, HandlerParser{}) - checkErrors(t, test.WantError, err) - } -} - -// NextRegistration() (core.Registration, core.AckNacker, error) -func TestNextRegistration(t *testing.T) { - tests := []struct { - AppId string - AppUrl string - DevAddr string - NwsKey string - WantResult *core.Registration - WantError error - }{ - // Valid device address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "000102030405060708090a0b0c0d0e0f", - DevAddr: "14aab0a4", - WantResult: &core.Registration{ - DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), - Recipient: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, - Options: lorawan.AES128Key([16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), - }, - WantError: nil, - }, - // Invalid device address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "000102030405060708090a0b0c0d0e0f", - DevAddr: "INVALID", - WantResult: nil, - WantError: nil, - }, - // Invalid nwskey address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "00112233445566778899af", - DevAddr: "14aab0a4", - WantResult: nil, - WantError: nil, - }, - } - - adapter, err := NewAdapter(3001, HandlerParser{}, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) - client := &client{adapter: "0.0.0.0:3001"} - <-time.After(time.Millisecond * 200) - if err != nil { - panic(err) - } - - for _, test := range tests { - // Describe - Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) - - // Build - gotErr := make(chan error) - gotConf := make(chan core.Registration) - go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) - - // Operate - go func() { - config, _, err := adapter.NextRegistration() - gotErr <- err - gotConf <- config - }() - - // Check - select { - case err := <-gotErr: - checkErrors(t, test.WantError, err) - case <-time.After(time.Millisecond * 250): - checkErrors(t, test.WantError, nil) - } - - select { - case conf := <-gotConf: - checkRegistrationResult(t, test.WantResult, &conf) - case <-time.After(time.Millisecond * 250): - checkRegistrationResult(t, test.WantResult, nil) - } - } -} - // Send(p core.Packet, r ...core.Recipient) error func TestSend(t *testing.T) { payload := genPHYPayload("mData", [4]byte{0x1, 0x2, 0x3, 0x4}) @@ -153,7 +51,7 @@ func TestSend(t *testing.T) { } s := genMockServer(3100) - adapter, err := NewAdapter(3101, HandlerParser{}, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + adapter, err := NewAdapter(log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) if err != nil { panic(err) } @@ -166,6 +64,7 @@ func TestSend(t *testing.T) { } } +// Check utilities func checkErrors(t *testing.T, want error, got error) { if want == got { Ok(t, "Check errors") @@ -174,15 +73,6 @@ func checkErrors(t *testing.T, want error, got error) { Ko(t, "Expected error to be {%v} but got {%v}", want, got) } -func checkRegistrationResult(t *testing.T, want, got *core.Registration) { - if !reflect.DeepEqual(want, got) { - Ko(t, "Received configuration doesn't match expectations") - return - } - - Ok(t, "Check registration result") -} - func checkSend(t *testing.T, want string, s MockServer) { select { case got := <-s.Payloads: @@ -199,34 +89,7 @@ func checkSend(t *testing.T, want string, s MockServer) { Ok(t, "Check send result") } -// Operate utilities - -// Wrapper around the http client -type client struct { - http.Client - adapter string -} - -// send is a convinient helper to send HTTP from a handler to the adapter -func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { - buf := new(bytes.Buffer) - if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { - panic(err) - } - request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), buf) - if err != nil { - panic(err) - } - request.Header.Set("Content-Type", "application/json") - resp, err := c.Do(request) - if err != nil { - panic(err) - } - return *resp -} - // Build utilities - type MockServer struct { Recipient core.Recipient Payloads chan string diff --git a/adapters/httpregister/handler_parser.go b/adapters/http/registrations/handler_parser.go similarity index 98% rename from adapters/httpregister/handler_parser.go rename to adapters/http/registrations/handler_parser.go index 6ccd2803f..17537b78c 100644 --- a/adapters/httpregister/handler_parser.go +++ b/adapters/http/registrations/handler_parser.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package httpregister +package registrations import ( "encoding/hex" diff --git a/adapters/http/registrations/reg_acknacker.go b/adapters/http/registrations/reg_acknacker.go new file mode 100644 index 000000000..003cbd272 --- /dev/null +++ b/adapters/http/registrations/reg_acknacker.go @@ -0,0 +1,40 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package registrations + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "net/http" + "time" +) + +var ErrConnectionLost = fmt.Errorf("Connection has been lost") + +type regAckNacker struct { + response chan regRes // A channel dedicated to send back a response +} + +// Ack implements the core.Acker interface +func (r regAckNacker) Ack(p core.Packet) error { + select { + case r.response <- regRes{statusCode: http.StatusOK}: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} + +// Nack implements the core.Nacker interface +func (r regAckNacker) Nack(p core.Packet) error { + select { + case r.response <- regRes{ + statusCode: http.StatusConflict, + content: []byte("Unable to register the given device"), + }: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} diff --git a/adapters/httpregister/registrations.go b/adapters/http/registrations/registrations.go similarity index 65% rename from adapters/httpregister/registrations.go rename to adapters/http/registrations/registrations.go index 723dea7b3..ffb6ffce5 100644 --- a/adapters/httpregister/registrations.go +++ b/adapters/http/registrations/registrations.go @@ -1,15 +1,26 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package httpregister +package registrations import ( "fmt" "github.com/thethingsnetwork/core" + httpadapter "github.com/thethingsnetwork/core/adapters/http" + "github.com/thethingsnetwork/core/utils/log" "net/http" - "time" ) +type Adapter struct { + *httpadapter.Adapter + Parser + registrations chan regReq +} + +type Parser interface { + Parse(req *http.Request) (core.Registration, error) +} + type regReq struct { core.Registration // The actual registration request response chan regRes // A dedicated channel to send back a response (ack or nack) @@ -20,31 +31,28 @@ type regRes struct { content []byte // The response content } -type regAckNacker struct { - response chan regRes // A channel dedicated to send back a response -} +// NewAdapter constructs a new http adapter that also handle registrations via http requests +func NewAdapter(port uint, parser Parser, loggers ...log.Logger) (*Adapter, error) { + adapter, err := httpadapter.NewAdapter(loggers...) + if err != nil { + return nil, err + } -// Ack implements the core.Acker interface -func (r regAckNacker) Ack(p core.Packet) error { - select { - case r.response <- regRes{statusCode: http.StatusOK}: - return nil - case <-time.After(time.Millisecond * 50): - return ErrConnectionLost + a := &Adapter{ + Adapter: adapter, + Parser: parser, + registrations: make(chan regReq), } + + go a.listenRegistration(port) + + return a, nil } -// Nack implements the core.Nacker interface -func (r regAckNacker) Nack(p core.Packet) error { - select { - case r.response <- regRes{ - statusCode: http.StatusConflict, - content: []byte("Unable to register the given device"), - }: - return nil - case <-time.After(time.Millisecond * 50): - return ErrConnectionLost - } +// NextRegistration implements the core.Adapter interface +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + request := <-a.registrations + return request.Registration, regAckNacker{response: request.response}, nil } // listenRegistration handles incoming registration request sent through http to the adapter @@ -59,21 +67,21 @@ func (a *Adapter) listenRegistration(port uint) { Addr: fmt.Sprintf("0.0.0.0:%d", port), Handler: serveMux, } - a.log("Start listening on %d", port) + a.Log("Start listening on %d", port) err := server.ListenAndServe() - a.log("HTTP connection lost: %v", err) + a.Log("HTTP connection lost: %v", err) } // fail logs the given failure and sends an appropriate response to the client func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { - a.log("registration request rejected: %s", msg) + a.Log("registration request rejected: %s", msg) w.WriteHeader(http.StatusBadRequest) w.Write([]byte(msg)) } // handle request [PUT] on /end-device/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { - a.log("Receive new registration request") + a.Log("Receive new registration request") // Check the http method if req.Method != "PUT" { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/adapters/http/registrations/registrations_test.go b/adapters/http/registrations/registrations_test.go new file mode 100644 index 000000000..802dd17a3 --- /dev/null +++ b/adapters/http/registrations/registrations_test.go @@ -0,0 +1,143 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package registrations + +import ( + "bytes" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/core/utils/log" + . "github.com/thethingsnetwork/core/utils/testing" + "net/http" + "reflect" + "testing" + "time" +) + +// NextRegistration() (core.Registration, core.AckNacker, error) +func TestNextRegistration(t *testing.T) { + tests := []struct { + AppId string + AppUrl string + DevAddr string + NwsKey string + WantResult *core.Registration + WantError error + }{ + // Valid device address + { + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "000102030405060708090a0b0c0d0e0f", + DevAddr: "14aab0a4", + WantResult: &core.Registration{ + DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), + Recipient: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, + Options: lorawan.AES128Key([16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), + }, + WantError: nil, + }, + // Invalid device address + { + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "000102030405060708090a0b0c0d0e0f", + DevAddr: "INVALID", + WantResult: nil, + WantError: nil, + }, + // Invalid nwskey address + { + AppId: "appid", + AppUrl: "myhandler.com:3000", + NwsKey: "00112233445566778899af", + DevAddr: "14aab0a4", + WantResult: nil, + WantError: nil, + }, + } + + adapter, err := NewAdapter(3001, HandlerParser{}, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + client := &client{adapter: "0.0.0.0:3001"} + <-time.After(time.Millisecond * 200) + if err != nil { + panic(err) + } + + for _, test := range tests { + // Describe + Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) + + // Build + gotErr := make(chan error) + gotConf := make(chan core.Registration) + go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) + + // Operate + go func() { + config, _, err := adapter.NextRegistration() + gotErr <- err + gotConf <- config + }() + + // Check + select { + case err := <-gotErr: + checkErrors(t, test.WantError, err) + case <-time.After(time.Millisecond * 250): + checkErrors(t, test.WantError, nil) + } + + select { + case conf := <-gotConf: + checkRegistrationResult(t, test.WantResult, &conf) + case <-time.After(time.Millisecond * 250): + checkRegistrationResult(t, test.WantResult, nil) + } + } +} + +func checkErrors(t *testing.T, want error, got error) { + if want == got { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%v} but got {%v}", want, got) +} + +func checkRegistrationResult(t *testing.T, want, got *core.Registration) { + if !reflect.DeepEqual(want, got) { + Ko(t, "Received configuration doesn't match expectations") + return + } + + Ok(t, "Check registration result") +} + +// Operate utilities + +// Wrapper around the http client +type client struct { + http.Client + adapter string +} + +// send is a convinient helper to send HTTP from a handler to the adapter +func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { + buf := new(bytes.Buffer) + if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { + panic(err) + } + request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), buf) + if err != nil { + panic(err) + } + request.Header.Set("Content-Type", "application/json") + resp, err := c.Do(request) + if err != nil { + panic(err) + } + return *resp +} From b880a05131a73e639f01ee7603b6798a90610f16 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 18:54:53 +0100 Subject: [PATCH 0307/2266] [broker] Start refactor of rtr_brk_http with the broadcast component --- adapters/http/broadcast/broadcast.go | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 adapters/http/broadcast/broadcast.go diff --git a/adapters/http/broadcast/broadcast.go b/adapters/http/broadcast/broadcast.go new file mode 100644 index 000000000..e55425df3 --- /dev/null +++ b/adapters/http/broadcast/broadcast.go @@ -0,0 +1,38 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broadcast + +import ( + "fmt" + "github.com/thethingsnetwork/core" + httpadapter "github.com/thethingsnetwork/core/adapters/http" + "github.com/thethingsnetwork/core/utils/log" +) + +type Adapter struct { + *httpadapter.Adapter + recipients []core.Recipient +} + +var ErrBadOptions = fmt.Errorf("Bad options provided") + +func NewAdapter(recipients []core.Recipient, loggers ...log.Logger) (*Adapter, error) { + if len(recipients) == 0 { + return nil, ErrBadOptions + } + + adapter, err := httpadapter.NewAdapter(loggers...) + if err != nil { + return nil, err + } + + return &Adapter{ + Adapter: adapter, + recipients: recipients, + }, nil +} + +func (a *Adapter) broadcast(p core.Packet) error { + return nil +} From f8e85cde84801c0d2c85d37709babb9a6846bdaa Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 19:10:36 +0100 Subject: [PATCH 0308/2266] [broker] Translate router http adapter to broadcast adapter sub-component --- adapters/http/adapter.go | 1 + adapters/http/broadcast/broadcast.go | 88 +++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/adapters/http/adapter.go b/adapters/http/adapter.go index 8b2ab5556..8127bd015 100644 --- a/adapters/http/adapter.go +++ b/adapters/http/adapter.go @@ -54,6 +54,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + defer resp.Body.Close() if err != nil { // Non-blocking, buffered cherr <- err diff --git a/adapters/http/broadcast/broadcast.go b/adapters/http/broadcast/broadcast.go index e55425df3..36fe9d0f1 100644 --- a/adapters/http/broadcast/broadcast.go +++ b/adapters/http/broadcast/broadcast.go @@ -4,18 +4,25 @@ package broadcast import ( + "bytes" + "encoding/base64" + "encoding/json" "fmt" "github.com/thethingsnetwork/core" httpadapter "github.com/thethingsnetwork/core/adapters/http" "github.com/thethingsnetwork/core/utils/log" + "net/http" + "sync" ) type Adapter struct { *httpadapter.Adapter - recipients []core.Recipient + recipients []core.Recipient + registrations chan core.Registration } var ErrBadOptions = fmt.Errorf("Bad options provided") +var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") func NewAdapter(recipients []core.Recipient, loggers ...log.Logger) (*Adapter, error) { if len(recipients) == 0 { @@ -28,11 +35,86 @@ func NewAdapter(recipients []core.Recipient, loggers ...log.Logger) (*Adapter, e } return &Adapter{ - Adapter: adapter, - recipients: recipients, + Adapter: adapter, + recipients: recipients, + registrations: make(chan core.Registration, len(recipients)), }, nil } +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { + if len(r) == 0 { + return a.broadcast(p) + } + return a.Adapter.Send(p, r...) +} + func (a *Adapter) broadcast(p core.Packet) error { + // Generate payload from core packet + m, err := json.Marshal(p.Metadata) + if err != nil { + return ErrInvalidPacket + } + pl, err := p.Payload.MarshalBinary() + if err != nil { + return ErrInvalidPacket + } + payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) + + devAddr, err := p.DevAddr() + if err != nil { + return ErrInvalidPacket + } + + // Prepare ground for parrallel http request + nb := len(a.recipients) + cherr := make(chan error, nb) + register := make(chan core.Recipient, nb) + wg := sync.WaitGroup{} + wg.Add(nb) + + // Run each request + for _, recipient := range a.recipients { + go func(recipient core.Recipient) { + defer wg.Done() + + a.Log("Post to %v", recipient) + buf := new(bytes.Buffer) + buf.Write([]byte(payload)) + resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + defer resp.Body.Close() + if err != nil { + // Non-blocking, buffered + cherr <- err + return + } + + switch resp.StatusCode { + case http.StatusOK: + a.Log("Recipient %v registered for given packet", recipient) + register <- recipient + case http.StatusNotFound: + a.Log("Recipient %v don't care much about packet", recipient) + default: + // Non-blocking, buffered + cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) + } + }(recipient) + } + + // Wait for each request to be done, and return + wg.Wait() + close(cherr) + close(register) + var errors []error + for err := range cherr { + errors = append(errors, err) + } + if errors != nil { + return fmt.Errorf("Errors: %v", errors) + } + + for recipient := range register { + a.registrations <- core.Registration{DevAddr: devAddr, Recipient: recipient} + } return nil } From 0f7b4d20d88f14d48062ea15ea0b22bffd2f2bd1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 19:12:00 +0100 Subject: [PATCH 0309/2266] [broker] Clean adapter folder a bit --- adapters/brk_hdl_http/adapter.go | 87 ------- adapters/brk_hdl_http/adapter_test.go | 303 ------------------------- adapters/brk_hdl_http/registrations.go | 159 ------------- 3 files changed, 549 deletions(-) delete mode 100644 adapters/brk_hdl_http/adapter.go delete mode 100644 adapters/brk_hdl_http/adapter_test.go delete mode 100644 adapters/brk_hdl_http/registrations.go diff --git a/adapters/brk_hdl_http/adapter.go b/adapters/brk_hdl_http/adapter.go deleted file mode 100644 index fe0cecfa1..000000000 --- a/adapters/brk_hdl_http/adapter.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package brk_hdl_http - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/utils/log" - "net/http" -) - -var ErrInvalidPort = fmt.Errorf("The given port is invalid") -var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") -var ErrConnectionLost = fmt.Errorf("The connection has been lost") - -type Adapter struct { - client http.Client - loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. - registrations chan regReq // Communication dedicated to incoming registration from handlers -} - -// NewAdapter constructs and allocate a new Broker <-> Handler http adapter -func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { - if port == 0 { - return nil, ErrInvalidPort - } - - a := Adapter{ - registrations: make(chan regReq), - loggers: loggers, - } - - go a.listenRegistration(port) - - return &a, nil -} - -// Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { - metadata, err := json.Marshal(p.Metadata) - if err != nil { - return ErrInvalidPacket - } - - payload, err := p.Payload.MarshalBinary() - if err != nil { - return ErrInvalidPacket - } - base64Payload := base64.StdEncoding.EncodeToString(payload) - - var errors []error - for _, recipient := range r { - buf := new(bytes.Buffer) - buf.Write([]byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64Payload, metadata))) - a.log("Post to %v", recipient) - resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) - if err != nil || resp.StatusCode != http.StatusOK { - errors = append(errors, err) - } - } - - if errors != nil { - return fmt.Errorf("Errors: %v", errors) - } - return nil -} - -// Next implements the core.Adapter inerface -func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, nil -} - -// NextRegistration implements the core.BrkHdlAdapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - request := <-a.registrations - return request.Registration, regAckNacker{response: request.response}, nil -} - -func (a *Adapter) log(format string, i ...interface{}) { - for _, logger := range a.loggers { - logger.Log(format, i...) - } -} diff --git a/adapters/brk_hdl_http/adapter_test.go b/adapters/brk_hdl_http/adapter_test.go deleted file mode 100644 index 368ecef61..000000000 --- a/adapters/brk_hdl_http/adapter_test.go +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package brk_hdl_http - -import ( - "bytes" - "encoding/base64" - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan" - components "github.com/thethingsnetwork/core/refactored_components" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" - "io" - "net/http" - "reflect" - "testing" - "time" -) - -// NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) -func TestNewAdapter(t *testing.T) { - tests := []struct { - Port uint - WantError error - }{ - {3000, nil}, - {0, ErrInvalidPort}, - } - - for _, test := range tests { - Desc(t, "Create new adapter bound to %d", test.Port) - _, err := NewAdapter(test.Port) - checkErrors(t, test.WantError, err) - } -} - -// NextRegistration() (core.Registration, core.AckNacker, error) -func TestNextRegistration(t *testing.T) { - tests := []struct { - AppId string - AppUrl string - DevAddr string - NwsKey string - WantResult *core.Registration - WantError error - }{ - // Valid device address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "000102030405060708090a0b0c0d0e0f", - DevAddr: "14aab0a4", - WantResult: &core.Registration{ - DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), - Handler: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, - NwsKey: lorawan.AES128Key([16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), - }, - WantError: nil, - }, - // Invalid device address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "000102030405060708090a0b0c0d0e0f", - DevAddr: "INVALID", - WantResult: nil, - WantError: nil, - }, - // Invalid nwskey address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwsKey: "00112233445566778899af", - DevAddr: "14aab0a4", - WantResult: nil, - WantError: nil, - }, - } - - adapter, err := NewAdapter(3001, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) - client := &client{adapter: "0.0.0.0:3001"} - <-time.After(time.Millisecond * 200) - if err != nil { - panic(err) - } - - for _, test := range tests { - // Describe - Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) - - // Build - gotErr := make(chan error) - gotConf := make(chan core.Registration) - go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) - - // Operate - go func() { - config, _, err := adapter.NextRegistration() - gotErr <- err - gotConf <- config - }() - - // Check - select { - case err := <-gotErr: - checkErrors(t, test.WantError, err) - case <-time.After(time.Millisecond * 250): - checkErrors(t, test.WantError, nil) - } - - select { - case conf := <-gotConf: - checkRegistrationResult(t, test.WantResult, &conf) - case <-time.After(time.Millisecond * 250): - checkRegistrationResult(t, test.WantResult, nil) - } - } -} - -// Send(p core.Packet, r ...core.Recipient) error -func TestSend(t *testing.T) { - payload := genPHYPayload("mData", [4]byte{0x1, 0x2, 0x3, 0x4}) - raw, err := payload.MarshalBinary() - if err != nil { - panic(err) - } - encoded := base64.StdEncoding.EncodeToString(raw) - - tests := []struct { - Packet core.Packet - WantPayload string - WantError error - }{ - { - core.Packet{ - Payload: payload, - Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, - }, - fmt.Sprintf(`{"payload":"%s","metadata":{"modu":"LORA","rssi":-20}}`, encoded), - nil, - }, - { - core.Packet{ - Payload: lorawan.PHYPayload{}, - Metadata: &components.Metadata{}, - }, - "", - ErrInvalidPacket, - }, - } - - s := genMockServer(3100) - adapter, err := NewAdapter(3101, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) - if err != nil { - panic(err) - } - - for _, test := range tests { - Desc(t, "Sending packet: %v", test.Packet) - err := adapter.Send(test.Packet, s.Recipient) - checkErrors(t, test.WantError, err) - checkSend(t, test.WantPayload, s) - } -} - -func checkErrors(t *testing.T, want error, got error) { - if want == got { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%v} but got {%v}", want, got) -} - -func checkRegistrationResult(t *testing.T, want, got *core.Registration) { - if !reflect.DeepEqual(want, got) { - Ko(t, "Received configuration doesn't match expectations") - return - } - - Ok(t, "Check registration result") -} - -func checkSend(t *testing.T, want string, s MockServer) { - select { - case got := <-s.Payloads: - if want != got { - Ko(t, "Received payload does not match expectations.\nWant: %s\nGot: %s", want, got) - return - } - case <-time.After(time.Millisecond * 100): - if want != "" { - Ko(t, "Expected payload %s to be sent but got nothing", want) - return - } - } - Ok(t, "Check send result") -} - -// Operate utilities - -// Wrapper around the http client -type client struct { - http.Client - adapter string -} - -// send is a convinient helper to send HTTP from a handler to the adapter -func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { - buf := new(bytes.Buffer) - if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { - panic(err) - } - request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), buf) - if err != nil { - panic(err) - } - request.Header.Set("Content-Type", "application/json") - resp, err := c.Do(request) - if err != nil { - panic(err) - } - return *resp -} - -// Build utilities - -type MockServer struct { - Recipient core.Recipient - Payloads chan string -} - -func genMockServer(port uint) MockServer { - addr := fmt.Sprintf("0.0.0.0:%d", port) - payloads := make(chan string) - - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - body := make([]byte, 256) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - panic(err) - } - w.Write(nil) - go func() { payloads <- string(body[:n]) }() - }) - - go func() { - server := http.Server{ - Handler: serveMux, - Addr: addr, - } - server.ListenAndServe() - }() - - <-time.After(time.Millisecond * 50) - - return MockServer{ - Recipient: core.Recipient{ - Address: addr, - Id: "Mock server", - }, - Payloads: payloads, - } -} - -// Generate a Physical payload representing an uplink message -func genPHYPayload(msg string, devAddr [4]byte) lorawan.PHYPayload { - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr(devAddr), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(msg)}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(true) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} diff --git a/adapters/brk_hdl_http/registrations.go b/adapters/brk_hdl_http/registrations.go deleted file mode 100644 index cc67483a8..000000000 --- a/adapters/brk_hdl_http/registrations.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package brk_hdl_http - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "github.com/thethingsnetwork/core" - "io" - "net/http" - "regexp" - "strings" - "time" -) - -type regReq struct { - core.Registration // The actual registration request - response chan regRes // A dedicated channel to send back a response (ack or nack) -} - -type regRes struct { - statusCode int // The response status, 200 for ack 4xx for nack - content []byte // The response content -} - -type regAckNacker struct { - response chan regRes // A channel dedicated to send back a response -} - -// Ack implements the core.Acker interface -func (r regAckNacker) Ack(p core.Packet) error { - select { - case r.response <- regRes{statusCode: http.StatusOK}: - return nil - case <-time.After(time.Millisecond * 50): - return ErrConnectionLost - } -} - -// Nack implements the core.Nacker interface -func (r regAckNacker) Nack(p core.Packet) error { - select { - case r.response <- regRes{ - statusCode: http.StatusConflict, - content: []byte("Unable to register the given device"), - }: - return nil - case <-time.After(time.Millisecond * 50): - return ErrConnectionLost - } -} - -// listenRegistration handles incoming registration request send through http to the broker -func (a *Adapter) listenRegistration(port uint) { - // Create a server multiplexer to handle request - serveMux := http.NewServeMux() - - // So far we only supports one endpoint [PUT] /end-device/:devAddr - serveMux.HandleFunc("/end-device/", a.handlePostEndDevice) - - server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), - Handler: serveMux, - } - a.log("Start listening on %d", port) - err := server.ListenAndServe() - a.log("HTTP connection lost: %v", err) -} - -// fail logs the given failure and sends an appropriate response to the client -func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { - a.log("registration request rejected: %s", msg) - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(msg)) -} - -// handle request [PUT] on /end-device/:devAddr -func (a *Adapter) handlePostEndDevice(w http.ResponseWriter, req *http.Request) { - a.log("Receive new registration request") - // Check the http method - if req.Method != "PUT" { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [PUT] to register a device")) - return - } - - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - a.badRequest(w, "Incorrect content type") - return - } - - // Check the query parameter - reg := regexp.MustCompile("end-device/([a-fA-F0-9]{8})$") - query := reg.FindStringSubmatch(req.RequestURI) - if len(query) < 2 { - a.badRequest(w, "Incorrect end-device address format") - return - } - devAddr, err := hex.DecodeString(query[1]) - if err != nil { - a.badRequest(w, "Incorrect end-device address format") - return - } - - // Check configuration in body - body := make([]byte, 256) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - a.badRequest(w, "Incorrect request body") - return - } - params := &struct { - Id string `json:"app_id"` - Url string `json:"app_url"` - NwsKey string `json:"nws_key"` - }{} - if err := json.Unmarshal(body[:n], params); err != nil { - a.badRequest(w, "Incorrect body payload") - return - } - - nwsKey, err := hex.DecodeString(params.NwsKey) - if err != nil || len(nwsKey) != 16 { - a.badRequest(w, "Incorrect network sesssion key") - return - } - - params.Id = strings.Trim(params.Id, " ") - params.Url = strings.Trim(params.Url, " ") - if len(params.Id) <= 0 { - a.badRequest(w, "Incorrect application id") - return - } - if len(params.Url) <= 0 { - a.badRequest(w, "Incorrect application url") - return - } - - // Create registration - config := core.Registration{ - Handler: core.Recipient{Id: params.Id, Address: params.Url}, - } - copy(config.NwsKey[:], nwsKey) - copy(config.DevAddr[:], devAddr) - - // Send the registration and wait for ack / nack - response := make(chan regRes) - a.registrations <- regReq{Registration: config, response: response} - r, ok := <-response - if !ok { - a.badRequest(w, "Core server not responding") - return - } - w.WriteHeader(r.statusCode) - w.Write(r.content) -} From 227e4588b76868aa730325c3b43ce4935ae776f0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 19:13:20 +0100 Subject: [PATCH 0310/2266] [broker] Update core.go to reflect last changes. Abstract a bit Registration type (now with interface{} as Options) Make a distinction between handleUp and handleDown for components --- core.go | 23 ++++++++--------------- 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/core.go b/core.go index 1ff600d72..f3baf2c08 100644 --- a/core.go +++ b/core.go @@ -43,31 +43,24 @@ type AckNacker interface { } type Component interface { - Handle(p Packet, an AckNacker) error - NextUp() (Packet, error) - NextDown() (Packet, error) + Register(reg Registration) error + HandleUp(p Packet, an AckNacker) error + HandleDown(p Packet, an AckNacker) error } type Adapter interface { Send(p Packet, r ...Recipient) error Next() (Packet, AckNacker, error) + NextRegistration() (Registration, AckNacker, error) } type Registration struct { - DevAddr lorawan.DevAddr - Handler Recipient - NwsKey lorawan.AES128Key -} - -type BrkHdlAdapter interface { - Adapter - NextRegistration() (Registration, AckNacker, error) + DevAddr lorawan.DevAddr + Recipient Recipient + Options interface{} } type Router Component -type Broker interface { - Component - Register(reg Registration) error -} +type Broker Component type Handler Component type NetworkController Component From 46eb9c1fdd3c03106c6168907ccd9d99a3e320b2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 19:22:14 +0100 Subject: [PATCH 0311/2266] [broker] Implement Adapter interface with broadcast --- adapters/http/broadcast/broadcast.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/adapters/http/broadcast/broadcast.go b/adapters/http/broadcast/broadcast.go index 36fe9d0f1..40bdffeea 100644 --- a/adapters/http/broadcast/broadcast.go +++ b/adapters/http/broadcast/broadcast.go @@ -118,3 +118,20 @@ func (a *Adapter) broadcast(p core.Packet) error { } return nil } + +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + registration := <-a.registrations + return registration, voidAckNacker{}, nil +} + +type voidAckNacker struct{} + +// Ack implements the core.AckNacker interface +func (v voidAckNacker) Ack(p core.Packet) error { + return nil +} + +// Nack implements the core.AckNacker interface +func (v voidAckNacker) Nack(p core.Packet) error { + return nil +} From 64766d110046a7f2eead64cdf0d37148eb2dca5e Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 22:40:17 +0100 Subject: [PATCH 0312/2266] [broker] Implement Adapter interface for the semtech adapter --- adapters/semtech/adapter.go | 158 +++++++++++++++ adapters/semtech/adapter_test.go | 273 ++++++++++++++++++++++++++ adapters/semtech/semtech_acknacker.go | 40 ++++ 3 files changed, 471 insertions(+) create mode 100644 adapters/semtech/adapter.go create mode 100644 adapters/semtech/adapter_test.go create mode 100644 adapters/semtech/semtech_acknacker.go diff --git a/adapters/semtech/adapter.go b/adapters/semtech/adapter.go new file mode 100644 index 000000000..1ccbed39e --- /dev/null +++ b/adapters/semtech/adapter.go @@ -0,0 +1,158 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package semtech + +import ( + "fmt" + "github.com/thethingsnetwork/core" + components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/log" + "net" +) + +type Adapter struct { + loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. + conn chan udpMsg + next chan rxpkMsg +} + +type udpMsg struct { + conn *net.UDPConn // Provide if you intent to change the current adapter connection + addr *net.UDPAddr // The target recipient address + raw []byte // The raw byte sequence that has to be sent +} + +type rxpkMsg struct { + rxpk semtech.RXPK + recipient core.Recipient +} + +var ErrInvalidPort error = fmt.Errorf("Invalid port supplied. The connection might be already taken") +var ErrNotInitialized error = fmt.Errorf("Illegal call on non-initialized adapter") + +// New constructs and allocates a new udp_sender adapter +func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { + a := Adapter{ + loggers: loggers, + conn: make(chan udpMsg), + next: make(chan rxpkMsg), + } + + // Create the udp connection and start listening with a goroutine + var udpConn *net.UDPConn + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if udpConn, err = net.ListenUDP("udp", addr); err != nil { + a.log("Unable to establish the connection: %v", err) + return nil, ErrInvalidPort + } + + go a.monitorConnection() + a.conn <- udpMsg{conn: udpConn} + go a.listen(udpConn) // Terminates when the connection is closed + + return &a, nil +} + +// ok controls whether or not the adapter has been initialized via NewAdapter() +func (a *Adapter) ok() bool { + return a != nil && a.conn != nil && a.next != nil +} + +// Send implements the core.Adapter interface +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { + return fmt.Errorf("Unsupported method") +} + +// Next implements the core.Adapter interface +func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { + if !a.ok() { + return core.Packet{}, nil, ErrNotInitialized + } + msg := <-a.next + packet, err := components.ConvertRXPK(msg.rxpk) + if err != nil { + return core.Packet{}, nil, err + } + return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil +} + +// listen Handle incoming packets and forward them +func (a *Adapter) listen(conn *net.UDPConn) { + defer conn.Close() + a.log("Start listening on %s", conn.LocalAddr()) + for { + buf := make([]byte, 128) + n, addr, err := conn.ReadFromUDP(buf) + if err != nil { // Problem with the connection + a.log("Error: %v", err) + continue + } + a.log("Incoming datagram %x", buf[:n]) + + pkt, err := semtech.Unmarshal(buf[:n]) + if err != nil { + a.log("Error: %v", err) + continue + } + + switch pkt.Identifier { + case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK + pullAck, err := semtech.Marshal(semtech.Packet{ + Version: semtech.VERSION, + Token: pkt.Token, + Identifier: semtech.PUSH_ACK, + }) + if err != nil { + a.log("Unexpected error while marshaling PULL_ACK: %v", err) + continue + } + a.conn <- udpMsg{addr: addr, raw: pullAck} + case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component + if pkt.Payload == nil { + a.log("Inconsistent PUSH_DATA packet %v", pkt) + continue + } + + for _, rxpk := range pkt.Payload.RXPK { + a.next <- rxpkMsg{ + rxpk: rxpk, + recipient: core.Recipient{Address: addr, Id: pkt.GatewayId}, + } + } + default: + a.log("Unexpected packet received. Ignored: %v", pkt) + continue + } + } +} + +// monitorConnection manages udpConnection of the adapter and send message through that connection +func (a *Adapter) monitorConnection() { + var udpConn *net.UDPConn + for msg := range a.conn { + if msg.conn != nil { // Change the connection + if udpConn != nil { + a.log("Define new UDP connection") + udpConn.Close() + } + udpConn = msg.conn + } + + if udpConn != nil && msg.raw != nil { // Send the given udp message + if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { + a.log("Unable to send udp message: %+v", err) + } + } + } + if udpConn != nil { + udpConn.Close() // Make sure we close the connection before leaving if we dare ever leave. + } +} + +func (a *Adapter) log(format string, i ...interface{}) { + for _, logger := range a.loggers { + logger.Log(format, i...) + } +} diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go new file mode 100644 index 000000000..f776a3495 --- /dev/null +++ b/adapters/semtech/adapter_test.go @@ -0,0 +1,273 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package gtw_rtr_udp + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/testing/mock_components" + "github.com/thethingsnetwork/core/utils/log" + . "github.com/thethingsnetwork/core/utils/testing" + "net" + "reflect" + "testing" + "time" +) + +// ----- The adapter should be able to create a udp connection given a valid udp port +func TestListenOptions(t *testing.T) { + tests := []listenOptionsTest{ + {uint(3000), nil}, + {uint(3000), core.ErrBadGatewayAddress}, // Already used now + {int(14), core.ErrBadOptions}, + {"somethingElse", core.ErrBadOptions}, + } + + for _, test := range tests { + test.run(t) + } +} + +type listenOptionsTest struct { + options interface{} + want error +} + +func (test listenOptionsTest) run(t *testing.T) { + Desc(t, "Run Listen(router, %T %v)", test.options, test.options) + adapter, router := generateAdapterAndRouter(t) + got := adapter.Listen(router, test.options) + test.check(t, got) +} + +func (test listenOptionsTest) check(t *testing.T, got error) { + // 1. Check if errors match + if got != test.want { + Ko(t, "expected {%v} to be {%v}", got, test.want) + return + } + Ok(t) +} + +// ----- The adapter should catch from the connection and forward valid semtech.Packet to the router +func TestPacketProcessing(t *testing.T) { + tests := []packetProcessingTest{ + {generatePUSH_DATA(), 1, 3001}, + {[]byte{0x14, 0xff}, 0, 3002}, + } + + for _, test := range tests { + test.run(t) + } +} + +type packetProcessingTest struct { + in interface{} // Could be raw []byte or plain semtech.Packet + want uint // 0 or 1 depending whether or not we expect a packet to has been transmitted + port uint // Probably temporary, just because goroutine and connection are still living between tests +} + +func (test packetProcessingTest) run(t *testing.T) { + Desc(t, "Simulate incoming datagram: %+v", test.in) + adapter, router := generateAdapterAndRouter(t) + conn, gateway := createConnection(adapter, router, test.port) + defer conn.Close() + sendDatagram(conn, test.in) + test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router +} + +func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway core.GatewayAddress) { + <-time.After(time.Millisecond * 50) + mockRouter := router.(*mock_components.Router) + + // 1. Check if we expect a packet + packets := mockRouter.Packets[gateway] + if nb := len(packets); uint(nb) != test.want { + Ko(t, "Received %d packets whereas expected %d", nb, test.want) + return + } + + // 2. If a packet was expected, check that it has been forwarded to the router + if test.want > 0 { + if !reflect.DeepEqual(packets[0], test.in) { + Ko(t, "Expected %+v to match %+v", packets[0], test.in) + return + } + } + + Ok(t) +} + +// ----- The adapter should send packet via back to an existing address through an opened connection +func TestSendAck(t *testing.T) { + // 1. Initialize test data + adapter, router := generateAdapterAndRouter(t) + conn, gateway := createConnection(adapter, router, 3003) + defer conn.Close() + + tests := []sendAckTest{ + {adapter, router, conn, gateway, generatePUSH_ACK(), nil}, + {adapter, router, conn, core.GatewayAddress("patate"), generatePUSH_ACK(), core.ErrBadGatewayAddress}, + {adapter, router, conn, gateway, semtech.Packet{}, core.ErrInvalidPacket}, + } + + // 2. Run tests + for _, test := range tests { + test.run(t) + } +} + +type sendAckTest struct { + adapter *Adapter + router core.Router + conn *net.UDPConn + gateway core.GatewayAddress + packet semtech.Packet + want error +} + +func (test sendAckTest) run(t *testing.T) { + Desc(t, "Send ack packet %v to %v via %v", test.packet, test.conn, test.gateway) + // Starts a goroutine that will redirect udp message to a dedicated channel + cmsg := listenFromConnection(test.conn) + defer close(cmsg) + got := test.adapter.Ack(test.router, test.packet, test.gateway) + test.check(t, cmsg, got) // Check the error or the packet if no error +} + +func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) { + // 1. Check if an error was expected + if test.want != nil { + if got != test.want { + Ko(t, "Expected %+v error but got %+v", test.want, got) + return + } + Ok(t) + return + } + + // 2. Ensure the ack packet has been sent correctly + packet := <-cmsg + if !reflect.DeepEqual(test.packet, packet) { + Ko(t, "Expected %+v to equal %+v", test.packet, packet) + return + } + Ok(t) +} + +// ----- In case of issue, the connection can be re-established +func TestConnectionRecovering(t *testing.T) { + adapter, router := generateAdapterAndRouter(t) + if err := adapter.Listen(router, uint(3004)); err != nil { + panic(err) + } + err := adapter.Listen(router, uint(3005)) + + if err != nil { + Ko(t, "No error was expected but got: %+v", err) + return + } + + // Now try to send a packet on a switched connection + err = adapter.Ack(router, generatePUSH_ACK(), core.GatewayAddress("0.0.0.0:3005")) + if err != nil { + Ko(t, "No error was expected but got: %+v", err) + return + } + + Ok(t) +} + +// ----- Build Utilities +func generateAdapterAndRouter(t *testing.T) (*Adapter, core.Router) { + a := NewAdapter() + a.Logger = log.TestLogger{Tag: "Adapter", T: t} + return a, mock_components.NewRouter() +} + +func generatePUSH_DATA() semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Token: []byte{0x14, 0x42}, + Identifier: semtech.PUSH_DATA, + } +} + +func generatePUSH_ACK() semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: []byte{0x14, 0x42}, + Identifier: semtech.PUSH_ACK, + } +} + +// ----- Operate Utilities +func createConnection(adapter *Adapter, router core.Router, port uint) (*net.UDPConn, core.GatewayAddress) { + var err error + + // 1. Start the adapter watching procedure + if err = adapter.Listen(router, port); err != nil { + panic(err) + } + + // 2. Create a UDP connection on the same port the adapter is listening + var addr *net.UDPAddr + var conn *net.UDPConn + if addr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)); err != nil { + panic(err) + } + if conn, err = net.DialUDP("udp", nil, addr); err != nil { + panic(err) + } + + // 3. Return the UDP connection and the corresponding simulated gateway address + return conn, core.GatewayAddress(conn.LocalAddr().String()) +} + +func sendDatagram(conn *net.UDPConn, data interface{}) { + // 1. Send the packet or the raw sequence of bytes passed as argument + var raw []byte + var err error + switch data.(type) { + case []byte: + raw = data.([]byte) + case semtech.Packet: + if raw, err = semtech.Marshal(data.(semtech.Packet)); err != nil { + panic(err) + } + default: + panic(fmt.Errorf("Unexpected data type to be send : %T", data)) + } + if _, err = conn.Write(raw); err != nil { + panic(err) + } +} + +func listenFromConnection(conn *net.UDPConn) (cmsg chan semtech.Packet) { + cmsg = make(chan semtech.Packet) + + // We won't listen on a nil connection + if conn == nil { + return + } + + // Otherwise, wait for a packet + go func() { + for { + buf := make([]byte, 128) + n, err := conn.Read(buf) + if err != nil { + return + } + packet, err := semtech.Unmarshal(buf[:n]) + if err == nil { + cmsg <- *packet + } + } + }() + + return +} diff --git a/adapters/semtech/semtech_acknacker.go b/adapters/semtech/semtech_acknacker.go new file mode 100644 index 000000000..d03b86c6d --- /dev/null +++ b/adapters/semtech/semtech_acknacker.go @@ -0,0 +1,40 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package semtech + +import ( + "fmt" + "github.com/thethingsnetwork/core" + components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/semtech" + "net" +) + +type semtechAckNacker struct { + conn chan udpMsg + recipient core.Recipient +} + +func (an semtechAckNacker) Ack(p core.Packet) error { + txpk, err := components.ConvertToTXPK(p) + if err != nil { + return err + } + raw, err := semtech.Marshal(semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{TXPK: &txpk}, + }) + + addr, ok := an.recipient.Address.(*net.UDPAddr) + if !ok { + return fmt.Errorf("Recipient address was invalid. Expected UDPAddr but got: %v", an.recipient.Address) + } + an.conn <- udpMsg{raw: raw, addr: addr} + return nil +} + +func (an semtechAckNacker) Nack(p core.Packet) error { + return nil +} From 2511b0a1b4a2ab95857c35ff38c131a609b7dc20 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 22:41:32 +0100 Subject: [PATCH 0313/2266] [broker] Make abstract http adapter implements the core.Adapter interface --- adapters/http/adapter.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/adapters/http/adapter.go b/adapters/http/adapter.go index 8127bd015..86be17db0 100644 --- a/adapters/http/adapter.go +++ b/adapters/http/adapter.go @@ -86,6 +86,11 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { return core.Packet{}, nil, nil } +// NextRegistration implements the core.Adapter interface +func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { + return core.Packet{}, nil, nil +} + func (a *Adapter) Log(format string, i ...interface{}) { for _, logger := range a.loggers { logger.Log(format, i...) From da1f1885a169a380a952c59528c4f4def95d0922 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 22:41:49 +0100 Subject: [PATCH 0314/2266] [broker] Clean up adapters folder --- adapters/gtw_rtr_udp/adapter.go | 149 ------------ adapters/gtw_rtr_udp/adapter_test.go | 273 --------------------- adapters/rtr_brk_http/adapter.go | 197 --------------- adapters/rtr_brk_http/adapter_test.go | 332 -------------------------- 4 files changed, 951 deletions(-) delete mode 100644 adapters/gtw_rtr_udp/adapter.go delete mode 100644 adapters/gtw_rtr_udp/adapter_test.go delete mode 100644 adapters/rtr_brk_http/adapter.go delete mode 100644 adapters/rtr_brk_http/adapter_test.go diff --git a/adapters/gtw_rtr_udp/adapter.go b/adapters/gtw_rtr_udp/adapter.go deleted file mode 100644 index c9cd1105f..000000000 --- a/adapters/gtw_rtr_udp/adapter.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gtw_rtr_udp - -import ( - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/utils/log" - "net" -) - -type Adapter struct { - Logger log.Logger // A custom logger used to report errors - conn chan udpMsg // An internal communication channel use to send udp datagram through a valid connection -} - -type udpMsg struct { - addr *net.UDPAddr // The target gateway address targetted - raw []byte // The raw byte sequence that has to be sent - conn *net.UDPConn // Provide if you intent to change the current adapter connection -} - -// NewAdapter constructs a gateway <-> router udp adapter -func NewAdapter() *Adapter { - a := Adapter{conn: make(chan udpMsg)} - go a.monitorConnection() // Terminates that goroutine by closing the channel - return &a -} - -// ok controls whether or not the adapter has been initialized via NewAdapter() -func (a *Adapter) ok() bool { - return a != nil && a.conn != nil -} - -// Listen implements the core.Adapter interface. It expects only one param "port" as a -// uint. Listen can be called several times to re-establish a lost connection. -func (a *Adapter) Listen(router core.Router, options interface{}) error { - if !a.ok() { - return core.ErrNotInitialized - } - - // Parse options - var port uint - switch options.(type) { - case uint: - port = options.(uint) - default: - a.log("Invalid option provided: %+v", options) - return core.ErrBadOptions - } - - // Create the udp connection and start listening with a goroutine - var udpConn *net.UDPConn - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.log("Unable to establish the connection: %v", err) - return core.ErrBadGatewayAddress - } - - a.conn <- udpMsg{conn: udpConn} - go a.listen(router, udpConn) // Terminates when the connection is closed - - return nil -} - -// Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) error { - if !a.ok() { - return core.ErrNotInitialized - } - - a.log("Acks packet %+v", packet) - - addr, err := net.ResolveUDPAddr("udp", string(gateway)) - - if err != nil { - a.log("Unable to retrieve gateway address: %+v", err) - return core.ErrBadGatewayAddress - } - - raw, err := semtech.Marshal(packet) - - if err != nil { - a.log("Unable to marshal given packet: %+v", err) - return core.ErrInvalidPacket - } - - a.conn <- udpMsg{raw: raw, addr: addr} - return nil -} - -// listen Handle incoming packets and forward them to the router -func (a *Adapter) listen(router core.Router, conn *net.UDPConn) { - defer conn.Close() - a.log("Start listening on %s", conn.LocalAddr()) - for { - buf := make([]byte, 1024) - n, addr, err := conn.ReadFromUDP(buf) - if err != nil { // Problem with the connection - a.log("Error: %v", err) - go router.HandleError(core.ErrMissingConnection) - return - } - a.log("Incoming datagram %x", buf[:n]) - - pkt, err := semtech.Unmarshal(buf[:n]) - if err != nil { - a.log("Error: %v", err) - go router.HandleError(core.ErrInvalidPacket) - continue - } - - // When a packet is received pass it to the router for processing - router.HandleUplink(*pkt, core.GatewayAddress(addr.String())) - } -} - -// monitorConnection manages udpConnection of the adapter and send message through that connection -func (a *Adapter) monitorConnection() { - var udpConn *net.UDPConn - for msg := range a.conn { - if msg.conn != nil { // Change the connection - if udpConn != nil { - a.log("Switch UDP connection") - udpConn.Close() - } - udpConn = msg.conn - } - - if udpConn != nil && msg.raw != nil { // Send the given udp message - if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.log("Unable to send udp message: %+v", err) - } - } - } - if udpConn != nil { - udpConn.Close() // Make sure we close the connection before leaving - } -} - -// log is nothing more than a shortcut / helper to access the logger -func (a Adapter) log(format string, i ...interface{}) { - if a.Logger == nil { - return - } - a.Logger.Log(format, i...) -} diff --git a/adapters/gtw_rtr_udp/adapter_test.go b/adapters/gtw_rtr_udp/adapter_test.go deleted file mode 100644 index f776a3495..000000000 --- a/adapters/gtw_rtr_udp/adapter_test.go +++ /dev/null @@ -1,273 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gtw_rtr_udp - -import ( - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/testing/mock_components" - "github.com/thethingsnetwork/core/utils/log" - . "github.com/thethingsnetwork/core/utils/testing" - "net" - "reflect" - "testing" - "time" -) - -// ----- The adapter should be able to create a udp connection given a valid udp port -func TestListenOptions(t *testing.T) { - tests := []listenOptionsTest{ - {uint(3000), nil}, - {uint(3000), core.ErrBadGatewayAddress}, // Already used now - {int(14), core.ErrBadOptions}, - {"somethingElse", core.ErrBadOptions}, - } - - for _, test := range tests { - test.run(t) - } -} - -type listenOptionsTest struct { - options interface{} - want error -} - -func (test listenOptionsTest) run(t *testing.T) { - Desc(t, "Run Listen(router, %T %v)", test.options, test.options) - adapter, router := generateAdapterAndRouter(t) - got := adapter.Listen(router, test.options) - test.check(t, got) -} - -func (test listenOptionsTest) check(t *testing.T, got error) { - // 1. Check if errors match - if got != test.want { - Ko(t, "expected {%v} to be {%v}", got, test.want) - return - } - Ok(t) -} - -// ----- The adapter should catch from the connection and forward valid semtech.Packet to the router -func TestPacketProcessing(t *testing.T) { - tests := []packetProcessingTest{ - {generatePUSH_DATA(), 1, 3001}, - {[]byte{0x14, 0xff}, 0, 3002}, - } - - for _, test := range tests { - test.run(t) - } -} - -type packetProcessingTest struct { - in interface{} // Could be raw []byte or plain semtech.Packet - want uint // 0 or 1 depending whether or not we expect a packet to has been transmitted - port uint // Probably temporary, just because goroutine and connection are still living between tests -} - -func (test packetProcessingTest) run(t *testing.T) { - Desc(t, "Simulate incoming datagram: %+v", test.in) - adapter, router := generateAdapterAndRouter(t) - conn, gateway := createConnection(adapter, router, test.port) - defer conn.Close() - sendDatagram(conn, test.in) - test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router -} - -func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway core.GatewayAddress) { - <-time.After(time.Millisecond * 50) - mockRouter := router.(*mock_components.Router) - - // 1. Check if we expect a packet - packets := mockRouter.Packets[gateway] - if nb := len(packets); uint(nb) != test.want { - Ko(t, "Received %d packets whereas expected %d", nb, test.want) - return - } - - // 2. If a packet was expected, check that it has been forwarded to the router - if test.want > 0 { - if !reflect.DeepEqual(packets[0], test.in) { - Ko(t, "Expected %+v to match %+v", packets[0], test.in) - return - } - } - - Ok(t) -} - -// ----- The adapter should send packet via back to an existing address through an opened connection -func TestSendAck(t *testing.T) { - // 1. Initialize test data - adapter, router := generateAdapterAndRouter(t) - conn, gateway := createConnection(adapter, router, 3003) - defer conn.Close() - - tests := []sendAckTest{ - {adapter, router, conn, gateway, generatePUSH_ACK(), nil}, - {adapter, router, conn, core.GatewayAddress("patate"), generatePUSH_ACK(), core.ErrBadGatewayAddress}, - {adapter, router, conn, gateway, semtech.Packet{}, core.ErrInvalidPacket}, - } - - // 2. Run tests - for _, test := range tests { - test.run(t) - } -} - -type sendAckTest struct { - adapter *Adapter - router core.Router - conn *net.UDPConn - gateway core.GatewayAddress - packet semtech.Packet - want error -} - -func (test sendAckTest) run(t *testing.T) { - Desc(t, "Send ack packet %v to %v via %v", test.packet, test.conn, test.gateway) - // Starts a goroutine that will redirect udp message to a dedicated channel - cmsg := listenFromConnection(test.conn) - defer close(cmsg) - got := test.adapter.Ack(test.router, test.packet, test.gateway) - test.check(t, cmsg, got) // Check the error or the packet if no error -} - -func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) { - // 1. Check if an error was expected - if test.want != nil { - if got != test.want { - Ko(t, "Expected %+v error but got %+v", test.want, got) - return - } - Ok(t) - return - } - - // 2. Ensure the ack packet has been sent correctly - packet := <-cmsg - if !reflect.DeepEqual(test.packet, packet) { - Ko(t, "Expected %+v to equal %+v", test.packet, packet) - return - } - Ok(t) -} - -// ----- In case of issue, the connection can be re-established -func TestConnectionRecovering(t *testing.T) { - adapter, router := generateAdapterAndRouter(t) - if err := adapter.Listen(router, uint(3004)); err != nil { - panic(err) - } - err := adapter.Listen(router, uint(3005)) - - if err != nil { - Ko(t, "No error was expected but got: %+v", err) - return - } - - // Now try to send a packet on a switched connection - err = adapter.Ack(router, generatePUSH_ACK(), core.GatewayAddress("0.0.0.0:3005")) - if err != nil { - Ko(t, "No error was expected but got: %+v", err) - return - } - - Ok(t) -} - -// ----- Build Utilities -func generateAdapterAndRouter(t *testing.T) (*Adapter, core.Router) { - a := NewAdapter() - a.Logger = log.TestLogger{Tag: "Adapter", T: t} - return a, mock_components.NewRouter() -} - -func generatePUSH_DATA() semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Token: []byte{0x14, 0x42}, - Identifier: semtech.PUSH_DATA, - } -} - -func generatePUSH_ACK() semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: []byte{0x14, 0x42}, - Identifier: semtech.PUSH_ACK, - } -} - -// ----- Operate Utilities -func createConnection(adapter *Adapter, router core.Router, port uint) (*net.UDPConn, core.GatewayAddress) { - var err error - - // 1. Start the adapter watching procedure - if err = adapter.Listen(router, port); err != nil { - panic(err) - } - - // 2. Create a UDP connection on the same port the adapter is listening - var addr *net.UDPAddr - var conn *net.UDPConn - if addr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)); err != nil { - panic(err) - } - if conn, err = net.DialUDP("udp", nil, addr); err != nil { - panic(err) - } - - // 3. Return the UDP connection and the corresponding simulated gateway address - return conn, core.GatewayAddress(conn.LocalAddr().String()) -} - -func sendDatagram(conn *net.UDPConn, data interface{}) { - // 1. Send the packet or the raw sequence of bytes passed as argument - var raw []byte - var err error - switch data.(type) { - case []byte: - raw = data.([]byte) - case semtech.Packet: - if raw, err = semtech.Marshal(data.(semtech.Packet)); err != nil { - panic(err) - } - default: - panic(fmt.Errorf("Unexpected data type to be send : %T", data)) - } - if _, err = conn.Write(raw); err != nil { - panic(err) - } -} - -func listenFromConnection(conn *net.UDPConn) (cmsg chan semtech.Packet) { - cmsg = make(chan semtech.Packet) - - // We won't listen on a nil connection - if conn == nil { - return - } - - // Otherwise, wait for a packet - go func() { - for { - buf := make([]byte, 128) - n, err := conn.Read(buf) - if err != nil { - return - } - packet, err := semtech.Unmarshal(buf[:n]) - if err == nil { - cmsg <- *packet - } - } - }() - - return -} diff --git a/adapters/rtr_brk_http/adapter.go b/adapters/rtr_brk_http/adapter.go deleted file mode 100644 index 9e51bb695..000000000 --- a/adapters/rtr_brk_http/adapter.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package rtr_brk_http -// -// Assume one endpoint url accessible through a POST http request -package rtr_brk_http - -import ( - "bytes" - "encoding/json" - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/utils/log" - "net/http" - "sync" -) - -type Adapter struct { - Logger log.Logger - brokers []core.BrokerAddress // List of brokers to which broadcast - mu sync.RWMutex // Guard brokers -} - -// NewAdapter() constructs a new router <-> broker adapter -func NewAdapter() *Adapter { - return &Adapter{} -} - -// Check whether or not the adapter has been initialized -func (a *Adapter) ok() bool { - return a != nil -} - -// Listen implements the core.Adapter interface -func (a *Adapter) Listen(router core.Router, options interface{}) error { - if !a.ok() { - return core.ErrNotInitialized - } - - switch options.(type) { - case []core.BrokerAddress: - a.mu.Lock() - defer a.mu.Unlock() - a.brokers = options.([]core.BrokerAddress) - if len(a.brokers) == 0 { - return core.ErrBadOptions - } - default: - a.log("Invalid options provided: %v", options) - return core.ErrBadOptions - } - - a.log("Start listening to brokers %v", a.brokers) - return nil -} - -// Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { - if !a.ok() { - return core.ErrNotInitialized - } - - // Determine the devAddress associated to that payload - if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? - a.log("Cannot broadcast given payload: %+v", payload) - return core.ErrInvalidPayload - } - - devAddr, err := payload.UniformDevAddr() - if err != nil { - a.log("Cannot broadcast given payload: %+v", payload) - return core.ErrInvalidPayload - } - - // Prepare ground to store brokers that are in charge - a.mu.RLock() - l := len(a.brokers) - if l == 0 { - a.mu.Unlock() - return core.ErrNotInitialized - } - - register := make(chan core.BrokerAddress, l) - wg := sync.WaitGroup{} - wg.Add(l) - - client := http.Client{} - for _, addr := range a.brokers { - go func(addr core.BrokerAddress) { - defer wg.Done() - - resp, err := post(client, string(addr), payload) - - if err != nil { - a.log("Unable to send POST request %+v", err) - router.HandleError(core.ErrBroadcast) // NOTE Mote information should be sent - return - } - - defer resp.Body.Close() - - switch resp.StatusCode { - case http.StatusOK: - a.log("Broker %+v handles packets coming from %+v", addr, devAddr) - register <- addr - case http.StatusNotFound: //NOTE Convention with the broker - a.log("Broker %+v does not handle packets coming from %+v", addr, devAddr) - default: - a.log("Unexpected answer from the broker %+v", err) - router.HandleError(core.ErrBroadcast) // NOTE More information should be sent - } - }(addr) - } - a.mu.RUnlock() - - go func() { - wg.Wait() - close(register) - brokers := make([]core.BrokerAddress, 0) - for addr := range register { - brokers = append(brokers, addr) - } - if len(brokers) > 0 { - router.RegisterDevice(*devAddr, brokers...) - } - }() - - return nil -} - -// Forward implements the core.BrokerRouter interface -func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { - if !a.ok() { - return core.ErrNotInitialized - } - - if payload.RXPK == nil || len(payload.RXPK) == 0 { // NOTE are those conditions significantly different ? - a.log("Cannot broadcast given payload: %+v", payload) - return core.ErrInvalidPayload - } - - client := http.Client{} - a.mu.RLock() - for _, addr := range broAddrs { - go func(url string) { - a.log("Send payload to %s", url) - resp, err := post(client, url, payload) - - if err != nil { - a.log("Unable to send POST request %+v", err) - router.HandleError(core.ErrForward) // NOTE More information should be sent - return - } - - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - a.log("Unexpected answer from the broker %+v", err) - router.HandleError(core.ErrForward) // NOTE More information should be sent - return - } - - // NOTE Do We Care about the response ? The router is supposed to handle HTTP request - // from the broker to handle packets or anything else ? Is it efficient ? Should - // downlinks packets be sent back with the HTTP body response ? Its a 2 seconds frame... - - }(string(addr)) - } - a.mu.RUnlock() - - return nil -} - -// post regroups some logic used in both Forward and Broadcast methods -func post(client http.Client, host string, payload semtech.Payload) (*http.Response, error) { - data := new(bytes.Buffer) - rawJSON, err := json.Marshal(payload) - if err != nil { - return nil, err - } - - if _, err := data.Write(rawJSON); err != nil { - return nil, err - } - - return client.Post(fmt.Sprintf("http://%s", host), "application/json", data) -} - -// log is nothing more than a shortcut / helper to access the logger -func (a Adapter) log(format string, i ...interface{}) { - if a.Logger == nil { - return - } - a.Logger.Log(format, i...) -} diff --git a/adapters/rtr_brk_http/adapter_test.go b/adapters/rtr_brk_http/adapter_test.go deleted file mode 100644 index 05202716e..000000000 --- a/adapters/rtr_brk_http/adapter_test.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package rtr_brk_http - -import ( - "encoding/json" - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/testing/mock_components" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" - "io" - "net/http" - "reflect" - "testing" - "time" -) - -// ----- The adapter can be created and listen straigthforwardly -func TestListenOptionsTest(t *testing.T) { - tests := []listenOptionsTest{ - {[]core.BrokerAddress{core.BrokerAddress("0.0.0.0:3000"), core.BrokerAddress("0.0.0.0:3001")}, nil}, - {"Patate", core.ErrBadOptions}, - {nil, core.ErrBadOptions}, - {[]core.BrokerAddress{}, core.ErrBadOptions}, - } - - for _, test := range tests { - test.run(t) - } -} - -type listenOptionsTest struct { - options interface{} - want error -} - -func (test listenOptionsTest) run(t *testing.T) { - // Describe - Desc(t, "Listen to adapter with options: %v", test.options) - - // Build - adapter, router := genAdapterAndRouter(t) - - // Operate - got := adapter.Listen(router, test.options) - - // Check - checkErrors(t, test.want, got) -} - -// -------------------------------------------------------------- -// ----- The adapter should forward a payload to a set of brokers -// -------------------------------------------------------------- -func TestForwardPayload(t *testing.T) { - tests := []forwardPayloadTest{ - {genValidPayload(), genBrokers([]int{200}), nil}, - {genValidPayload(), genBrokers([]int{200, 200}), nil}, - {genInvalidPayload(), nil, core.ErrInvalidPayload}, - } - - for _, test := range tests { - test.run(t) - } -} - -type forwardPayloadTest struct { - payload semtech.Payload - brokers map[string]int - want error -} - -func (test forwardPayloadTest) run(t *testing.T) { - //Describe - Desc(t, "Forward %v to %v", test.payload, test.brokers) - - // Build - adapter, router := genAdapterAndRouter(t) - adapter.Listen(router, toBrokerAddrs(test.brokers)) - cmsg := listenHTTP(t, test.brokers) - - // Operate - <-time.After(time.Millisecond * 250) - got := adapter.Forward(router, test.payload, toBrokerAddrs(test.brokers)...) - - // Check - <-time.After(time.Millisecond * 100) - checkErrors(t, test.want, got) - checkReception(t, len(test.brokers), test.payload, cmsg) -} - -// ----- The adapter should broadcast a payload to a set of broker -func TestBroadcastPayload(t *testing.T) { - tests := []broadcastPayloadTest{ - {genValidPayload(), genBrokers([]int{200, 200}), nil}, - {genValidPayload(), genBrokers([]int{200, 404}), nil}, - {genValidPayloadInvalidDevAddr(), nil, core.ErrInvalidPayload}, - {genInvalidPayload(), nil, core.ErrInvalidPayload}, - } - - for _, test := range tests { - test.run(t) - } -} - -type broadcastPayloadTest struct { - payload semtech.Payload - brokers map[string]int - want error -} - -func (test broadcastPayloadTest) run(t *testing.T) { - // Describe - Desc(t, "Broadcast %v to %v", test.payload, test.brokers) - - // Build - adapter, router := genAdapterAndRouter(t) - adapter.Listen(router, toBrokerAddrs(test.brokers)) - cmsg := listenHTTP(t, test.brokers) - - // Operate - <-time.After(time.Millisecond * 250) - got := adapter.Broadcast(router, test.payload) - - // Check - <-time.After(time.Millisecond * 100) - checkErrors(t, test.want, got) - checkReception(t, len(test.brokers), test.payload, cmsg) - checkRegistration(t, router, test.payload, test.brokers) -} - -// ----- Build Utilities - -// Create an instance of an Adapter with a predefined logger + a mock router -func genAdapterAndRouter(t *testing.T) (*Adapter, core.Router) { - a := NewAdapter() - a.Logger = log.TestLogger{Tag: "Adapter", T: t} - return a, mock_components.NewRouter() -} - -// gen a very basic payload holding an RXPK packet and identifying a valid device address -func genValidPayload() semtech.Payload { - return semtech.Payload{ - RXPK: []semtech.RXPK{{ - Data: pointer.String("/xRC/zcBAAABqqq7uw=="), - Freq: pointer.Float64(866.349812), - Rssi: pointer.Int(-35), - }, - }, - } -} - -// gen a very basic payload holding an RXPK packet but with scrap data -func genValidPayloadInvalidDevAddr() semtech.Payload { - return semtech.Payload{ - RXPK: []semtech.RXPK{{ - Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), - Freq: pointer.Float64(866.349812), - Rssi: pointer.Int(-35), - }, - }, - } -} - -// gen a payload with no RXPK nor STAT packet -func genInvalidPayload() semtech.Payload { - return semtech.Payload{} -} - -// Keep track of open TCP ports -var port int = 3000 - -// gen a list of brokers given a list of http response status in the form address -> status -func genBrokers(status []int) map[string]int { - brokers := make(map[string]int) - for _, s := range status { - brokers[fmt.Sprintf("0.0.0.0:%d", port)] = s - port += 1 - } - return brokers -} - -// Transform the broker map address -> status to a list a BrokerAddress -func toBrokerAddrs(addrs map[string]int) []core.BrokerAddress { - brokers := make([]core.BrokerAddress, 0) - for addr := range addrs { - brokers = append(brokers, core.BrokerAddress(addr)) - } - return brokers -} - -// Create an http handler that will listen to json request on "/" and forward payload into a -// dedicated channel. A custom response status can be given in param. -func createServeMux(t *testing.T, status int, cmsg chan semtech.Payload) *http.ServeMux { - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(res http.ResponseWriter, req *http.Request) { - defer req.Body.Close() - res.Header().Set("Content-Type", "application/json") - - // Check the header type - if req.Header.Get("Content-Type") != "application/json" { - t.Log("Unexpected content-type ignore") - res.WriteHeader(http.StatusBadRequest) - res.Write(nil) - return - } - - // Check the body as well - var payload semtech.Payload - raw := make([]byte, 512) - n, err := req.Body.Read(raw) - if err != nil && err != io.EOF { - t.Logf("Error reading request body: %v", err) - res.WriteHeader(http.StatusBadRequest) - res.Write(nil) - return - } - - if err := json.Unmarshal(raw[:n], &payload); err != nil { - t.Logf("Error while unmarshaling: %v", err) - res.WriteHeader(http.StatusBadRequest) - res.Write(nil) - return - } - - // Send a fake response - res.WriteHeader(status) - res.Write(nil) - cmsg <- payload - }) - return serveMux -} - -// ----- Operate Utilities - -// Start one http server per address which will forward request to the returned channel of payloads. -func listenHTTP(t *testing.T, addrs map[string]int) chan semtech.Payload { - cmsg := make(chan semtech.Payload, len(addrs)) - - for addr, status := range addrs { - go func(addr string, status int) { - s := &http.Server{Addr: addr, Handler: createServeMux(t, status, cmsg)} - if err := s.ListenAndServe(); err != nil { - panic(err) - } - }(addr, status) - } - - return cmsg -} - -// ----- Check Utilities -func checkErrors(t *testing.T, want error, got error) bool { - // Check for the error - if want != got { - Ko(t, "Expected error %v but got %v", want, got) - return false - } - Ok(t) - return true -} - -func checkReception(t *testing.T, nbExpected int, want semtech.Payload, cmsg chan semtech.Payload) bool { - // Check if payload should have been sent - if nbExpected <= 0 { - Ok(t) - return true - } - - // Gather payloads and check one of them - var payloads []semtech.Payload - select { - case payload := <-cmsg: - payloads = append(payloads, payload) - if len(payloads) == nbExpected { - break - } - case <-time.After(time.Millisecond * 500): - Ko(t, "%d payload(s) send to server(s) whereas %d was/were expected", len(payloads), nbExpected) - return false - } - - if !reflect.DeepEqual(want, payloads[0]) { - Ko(t, "Expected %+v to be sent but server received: %+v", want, payloads[0]) - return false - } - - Ok(t) - return true -} - -func checkRegistration(t *testing.T, router core.Router, payload semtech.Payload, brokers map[string]int) bool { - if len(brokers) == 0 { - Ok(t) - return true - } - - devAddr, err := payload.UniformDevAddr() - if err != nil { - panic(err) - } - - mockRouter := router.(*mock_components.Router) // Need to access to registered devices of mock router - -outer: - for addr, status := range brokers { - if status != 200 { // Not a HTTP 200 OK, broker probably does not handle that device - continue - } - - addrs, ok := mockRouter.Devices[*devAddr] // Get all registered brokers for that device - if !ok { - Ko(t, "Broker %s wasn't registered for payload %v", addr, payload) - return false - } - - for _, broker := range addrs { - if string(broker) == addr { - continue outer // We are registered, everything's fine for that broker - } - } - - Ko(t, "Broker %s wasn't registered for payload %v", addr, payload) - return false - } - - Ok(t) - return true -} From e14e75a9a39f0df10b946816a67fbd8f9486b84d Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 22:54:29 +0100 Subject: [PATCH 0315/2266] [broker] Improve components/packet_tests readibility --- refactored_components/build_utilities_test.go | 7 --- refactored_components/packet_test.go | 43 ++++++++++--------- 2 files changed, 22 insertions(+), 28 deletions(-) diff --git a/refactored_components/build_utilities_test.go b/refactored_components/build_utilities_test.go index 145cf1cf6..c1477fc23 100644 --- a/refactored_components/build_utilities_test.go +++ b/refactored_components/build_utilities_test.go @@ -5,7 +5,6 @@ package components import ( "encoding/base64" - "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" @@ -13,12 +12,6 @@ import ( "time" ) -type convertRXPKTest struct { - CorePacket core.Packet - RXPK semtech.RXPK - WantError error -} - // Generate an RXPK packet using the given payload as Data func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { raw, err := phyPayload.MarshalBinary() diff --git a/refactored_components/packet_test.go b/refactored_components/packet_test.go index 49a9e1e2d..bb428c07d 100644 --- a/refactored_components/packet_test.go +++ b/refactored_components/packet_test.go @@ -5,6 +5,7 @@ package components import ( "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" "testing" @@ -12,9 +13,9 @@ import ( func TestConvertRXPK(t *testing.T) { tests := []convertRXPKTest{ - genRXPKWithFullMetadata(), - genRXPKWithPartialMetadata(), - genRXPKWithNoData(), + genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), + genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), + genRXPKWithNoData(&convertRXPKTest{WantError: ErrImpossibleConversion}), } for _, test := range tests { @@ -25,22 +26,27 @@ func TestConvertRXPK(t *testing.T) { } } +// ---- Declaration +type convertRXPKTest struct { + CorePacket core.Packet + RXPK semtech.RXPK + WantError error +} + // ---- Build utilities // Generates a test suite where the RXPK is fully complete -func genRXPKWithFullMetadata() convertRXPKTest { +func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) metadata := genMetadata(rxpk) - return convertRXPKTest{ - CorePacket: core.Packet{Metadata: &metadata, Payload: phyPayload}, - RXPK: rxpk, - WantError: nil, - } + test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.RXPK = rxpk + return *test } // Generates a test suite where the RXPK contains partial metadata -func genRXPKWithPartialMetadata() convertRXPKTest { +func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) rxpk.Codr = nil @@ -49,20 +55,15 @@ func genRXPKWithPartialMetadata() convertRXPKTest { rxpk.Time = nil rxpk.Size = nil metadata := genMetadata(rxpk) - return convertRXPKTest{ - CorePacket: core.Packet{Metadata: &metadata, Payload: phyPayload}, - RXPK: rxpk, - WantError: nil, - } + test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.RXPK = rxpk + return *test } // Generates a test suite where the RXPK contains no data -func genRXPKWithNoData() convertRXPKTest { +func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { rxpk := genRXPK(genPHYPayload(true)) rxpk.Data = nil - return convertRXPKTest{ - CorePacket: core.Packet{}, - RXPK: rxpk, - WantError: ErrImpossibleConversion, - } + test.RXPK = rxpk + return *test } From ea8377ad2c1cdd3a50d799758cd71608b8970481 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 23:00:23 +0100 Subject: [PATCH 0316/2266] [broker] Remove typos in check_utilities --- refactored_components/check_utilities_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/refactored_components/check_utilities_test.go b/refactored_components/check_utilities_test.go index 31d8a4c5e..c9ed0455f 100644 --- a/refactored_components/check_utilities_test.go +++ b/refactored_components/check_utilities_test.go @@ -16,7 +16,7 @@ func checkPackets(t *testing.T, want core.Packet, got core.Packet) { Ok(t, "Check packets") return } - Ko(t, "Converted packet don't match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) + Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) } // Checks that errors match @@ -35,7 +35,7 @@ func checkJSON(t *testing.T, want string, got []byte) { Ok(t, "check JSON") return } - Ko(t, "Marshaled data don't match expectations.\nWant: %s\nGot: %s", want, str) + Ko(t, "Marshaled data does not match expectations.\nWant: %s\nGot: %s", want, str) return } @@ -45,5 +45,5 @@ func checkMetadata(t *testing.T, want Metadata, got Metadata) { Ok(t, "check Metadata") return } - Ko(t, "Unmarshaled json don't match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) + Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) } From 15e41f28148d8fffbb55e5073153743ca7031b94 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 23:25:14 +0100 Subject: [PATCH 0317/2266] [broker] Write tests for convertToTXPK method --- refactored_components/build_utilities_test.go | 52 +++++++++++- refactored_components/check_utilities_test.go | 11 +++ refactored_components/packet_test.go | 79 +++++++++++++++++++ 3 files changed, 141 insertions(+), 1 deletion(-) diff --git a/refactored_components/build_utilities_test.go b/refactored_components/build_utilities_test.go index c1477fc23..5c5fb9496 100644 --- a/refactored_components/build_utilities_test.go +++ b/refactored_components/build_utilities_test.go @@ -36,6 +36,32 @@ func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { } } +// Generates a TXPK packet using the given payload and the given metadata +func genTXPK(phyPayload lorawan.PHYPayload, metadata Metadata) semtech.TXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + return semtech.TXPK{ + Codr: metadata.Codr, + Data: pointer.String(data), + Datr: metadata.Datr, + Fdev: metadata.Fdev, + Freq: metadata.Freq, + Imme: metadata.Imme, + Ipol: metadata.Ipol, + Modu: metadata.Modu, + Ncrc: metadata.Ncrc, + Powe: metadata.Powe, + Prea: metadata.Prea, + Rfch: metadata.Rfch, + Size: metadata.Size, + Time: metadata.Time, + Tmst: metadata.Tmst, + } +} + // Generate a Metadata object that matches RXPK metadata func genMetadata(RXPK semtech.RXPK) Metadata { return Metadata{ @@ -53,7 +79,31 @@ func genMetadata(RXPK semtech.RXPK) Metadata { } } -// Generate a Physical payload represting an uplink or downlink message +// Generates a Metadata object with all field completed with relevant values +func genFullMetadata() Metadata { + return Metadata{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Datr: pointer.String("LORA"), + Fdev: pointer.Uint(3), + Freq: pointer.Float64(863.125), + Imme: pointer.Bool(false), + Ipol: pointer.Bool(false), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Ncrc: pointer.Bool(true), + Powe: pointer.Uint(3), + Prea: pointer.Uint(8), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(14), + Stat: pointer.Int(0), + Time: pointer.Time(time.Now()), + Tmst: pointer.Uint(uint(time.Now().UnixNano())), + } +} + +// Generate a Physical payload representing an uplink or downlink message func genPHYPayload(uplink bool) lorawan.PHYPayload { nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} diff --git a/refactored_components/check_utilities_test.go b/refactored_components/check_utilities_test.go index c9ed0455f..2a7c12a77 100644 --- a/refactored_components/check_utilities_test.go +++ b/refactored_components/check_utilities_test.go @@ -5,6 +5,8 @@ package components import ( "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" "reflect" "testing" @@ -47,3 +49,12 @@ func checkMetadata(t *testing.T, want Metadata, got Metadata) { } Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) } + +// Checks that obtained TXPK matches expeceted one +func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { + if reflect.DeepEqual(want, got) { + Ok(t, "check TXPKs") + return + } + Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) +} diff --git a/refactored_components/packet_test.go b/refactored_components/packet_test.go index bb428c07d..c1529710e 100644 --- a/refactored_components/packet_test.go +++ b/refactored_components/packet_test.go @@ -5,6 +5,7 @@ package components import ( "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" @@ -26,6 +27,23 @@ func TestConvertRXPK(t *testing.T) { } } +func TestConvertTXPK(t *testing.T) { + tests := []convertToTXPKTest{ + genCoreFullMetadata(&convertToTXPKTest{WantError: nil}), + genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), + genCoreExtraMetadata(&convertToTXPKTest{WantError: nil}), + genCoreNoMetadata(&convertToTXPKTest{WantError: nil}), + genCoreNoPayload(&convertToTXPKTest{WantError: ErrImpossibleConversion}), + } + + for _, test := range tests { + Desc(t, "Convert to TXPK: %s", test.CorePacket.String()) + txpk, err := ConvertToTXPK(test.CorePacket) + checkErrors(t, test.WantError, err) + checkTXPKs(t, test.TXPK, txpk) + } +} + // ---- Declaration type convertRXPKTest struct { CorePacket core.Packet @@ -33,6 +51,12 @@ type convertRXPKTest struct { WantError error } +type convertToTXPKTest struct { + TXPK semtech.TXPK + CorePacket core.Packet + WantError error +} + // ---- Build utilities // Generates a test suite where the RXPK is fully complete @@ -67,3 +91,58 @@ func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { test.RXPK = rxpk return *test } + +// Generates a test suite where the core packet has all txpk metadata +func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + metadata.Chan = nil + metadata.Lsnr = nil + metadata.Rssi = nil + metadata.Stat = nil + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + return *test +} + +// Generates a test suite where the core packet has no metadata +func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := Metadata{} + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + return *test +} + +// Generates a test suite where the core packet has partial metadata but all supported +func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + metadata.Chan = nil + metadata.Lsnr = nil + metadata.Rssi = nil + metadata.Stat = nil + metadata.Modu = nil + metadata.Fdev = nil + metadata.Time = nil + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + return *test +} + +// Generates a test suite where the core packet has extra metadata not supported by txpk +func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + return *test +} + +// Generates a test suite where the core packet has no payload +func genCoreNoPayload(test *convertToTXPKTest) convertToTXPKTest { + metadata := genFullMetadata() + test.TXPK = semtech.TXPK{} + test.CorePacket = core.Packet{Metadata: &metadata, Payload: lorawan.PHYPayload{}} + return *test +} From f011fee08b830841ea9abdf02eaebd7c704e7eda Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 23:41:13 +0100 Subject: [PATCH 0318/2266] [broker] Implements ConvertToTXPK method --- refactored_components/packet.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/refactored_components/packet.go b/refactored_components/packet.go index 625bcf806..39ab34ac5 100644 --- a/refactored_components/packet.go +++ b/refactored_components/packet.go @@ -9,7 +9,9 @@ import ( "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/pointer" "reflect" + "strings" ) // return by ConvertRXPK or ConvertTXPK if there's no data in the given packet @@ -54,3 +56,29 @@ func ConvertRXPK(p semtech.RXPK) (core.Packet, error) { return core.Packet{Metadata: &metadata, Payload: payload}, nil } + +// ConvertToTXPK converts a core Packet to a semtech TXPK packet using compatible metadata. +func ConvertToTXPK(p core.Packet) (semtech.TXPK, error) { + raw, err := p.Payload.MarshalBinary() + if err != nil { + return semtech.TXPK{}, ErrImpossibleConversion + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + + txpk := semtech.TXPK{Data: pointer.String(data)} + if p.Metadata == nil { + return txpk, nil + } + + metadataValue := reflect.ValueOf(p.Metadata).Elem() + metadataStruct := metadataValue.Type() + txpkStruct := reflect.ValueOf(&txpk).Elem() + for i := 0; i < metadataStruct.NumField(); i += 1 { + field := metadataStruct.Field(i).Name + if txpkStruct.FieldByName(field).CanSet() { + txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) + } + } + + return txpk, nil +} From 6b8932f75e84be26829d31b7247d14a8e77eb989 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 23:46:14 +0100 Subject: [PATCH 0319/2266] [broker] Remove mock entities. No more need for that now --- testing/mock_adapters/gtw_rtr_mock/adapter.go | 43 ----------- testing/mock_adapters/rtr_brk_mock/adapter.go | 71 ------------------- testing/mock_components/router.go | 52 -------------- 3 files changed, 166 deletions(-) delete mode 100644 testing/mock_adapters/gtw_rtr_mock/adapter.go delete mode 100644 testing/mock_adapters/rtr_brk_mock/adapter.go delete mode 100644 testing/mock_components/router.go diff --git a/testing/mock_adapters/gtw_rtr_mock/adapter.go b/testing/mock_adapters/gtw_rtr_mock/adapter.go deleted file mode 100644 index 4ad6c77fa..000000000 --- a/testing/mock_adapters/gtw_rtr_mock/adapter.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package gtw_rtr_mock offers a gateway <-> router mock adapter that can be used to test a router -// implementation. -package gtw_rtr_mock - -import ( - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" -) - -type Adapter struct { - FailAck bool // If true, each call to Ack will fail with a core.ErrAck - FailListen bool // If true, each call to Listen will return an error - Acks map[core.GatewayAddress][]semtech.Packet // Stores all packet send through Ack() -} - -// New constructs a new Gateway-Router-Mock adapter -func New() *Adapter { - return &Adapter{ - FailAck: false, - FailListen: false, - Acks: make(map[core.GatewayAddress][]semtech.Packet), - } -} - -// Listen implements the core.Adapter interface -func (a *Adapter) Listen(router core.Router, options interface{}) error { - if a.FailListen { - return core.ErrBadOptions - } - return nil -} - -// Ack implements the core.GatewayRouterAdapter interface -func (a *Adapter) Ack(router core.Router, packet semtech.Packet, gateway core.GatewayAddress) error { - if a.FailAck { - return core.ErrInvalidPacket - } - a.Acks[gateway] = append(a.Acks[gateway], packet) - return nil -} diff --git a/testing/mock_adapters/rtr_brk_mock/adapter.go b/testing/mock_adapters/rtr_brk_mock/adapter.go deleted file mode 100644 index ad5a64829..000000000 --- a/testing/mock_adapters/rtr_brk_mock/adapter.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package rtr_brk_mock offers a router <-> broker mock adapter that can be used to test a router -// implementation. -package rtr_brk_mock - -import ( - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "time" -) - -type Adapter struct { - Brokers []core.BrokerAddress // All known brokers with which the router is communicating - FailListen bool // If true, any call to Listen will fail with an error - FailBroadcast bool // If true, any call to Broadcast will trigger a core.ErrBroadcast - FailForward bool // If true, any call to Forward will trigger a core.ErrForward - Broadcasts map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through broadcasts - Forwards map[semtech.DeviceAddress][]semtech.Payload // Stores all payload send through forwards - Relations map[semtech.DeviceAddress][]core.BrokerAddress // Known association between brokers and device -} - -// New constructs a new router <-> broker adapter interface -func New() *Adapter { - return &Adapter{ - FailListen: false, - FailBroadcast: false, - FailForward: false, - Broadcasts: make(map[semtech.DeviceAddress][]semtech.Payload), - Forwards: make(map[semtech.DeviceAddress][]semtech.Payload), - Relations: make(map[semtech.DeviceAddress][]core.BrokerAddress), - } -} - -// Connect implements the core.Adapter interface. Expect a slice of broker address as options -func (a *Adapter) Listen(router core.Router, options interface{}) error { - if a.FailListen { - return core.ErrBadOptions - } - a.Brokers = options.([]core.BrokerAddress) - return nil -} - -// Broadcast implements the core.BrokerRouter interface -func (a *Adapter) Broadcast(router core.Router, payload semtech.Payload) error { - devAddr, err := payload.UniformDevAddr() - - if a.FailBroadcast || payload.RXPK == nil || err != nil { - return core.ErrBroadcast - } - - <-time.After(time.Millisecond * 50) - a.Broadcasts[*devAddr] = append(a.Broadcasts[*devAddr], payload) - brokers, ok := a.Relations[*devAddr] - if ok { - router.RegisterDevice(*devAddr, brokers...) - } - return nil -} - -// Forward implements the core.BrokerRouter interface -func (a *Adapter) Forward(router core.Router, payload semtech.Payload, broAddrs ...core.BrokerAddress) error { - devAddr, err := payload.UniformDevAddr() - - if a.FailForward || err != nil { - return core.ErrForward - } - a.Forwards[*devAddr] = append(a.Forwards[*devAddr], payload) - return nil -} diff --git a/testing/mock_components/router.go b/testing/mock_components/router.go deleted file mode 100644 index a70de9d76..000000000 --- a/testing/mock_components/router.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package mock_components offers a mock router that can be used to test adapter implementations. -package mock_components - -import ( - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" -) - -// Router represents the mock Router -type Router struct { - Errors []interface{} - Packets map[core.GatewayAddress][]semtech.Packet - Payloads map[core.BrokerAddress][]semtech.Payload - Devices map[semtech.DeviceAddress][]core.BrokerAddress -} - -// NewRouter constructs a mock Router; This method is a shortcut that creates all the internal maps. -func NewRouter() *Router { - return &Router{ - Packets: make(map[core.GatewayAddress][]semtech.Packet), - Payloads: make(map[core.BrokerAddress][]semtech.Payload), - Devices: make(map[semtech.DeviceAddress][]core.BrokerAddress), - } -} - -// HandleUplink implements the core.Router interface -func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress) { - r.Packets[gateway] = append(r.Packets[gateway], packet) -} - -// HandleDownlink implements the core.Router interface -func (r *Router) HandleDownlink(payload semtech.Payload, broker core.BrokerAddress) { - r.Payloads[broker] = append(r.Payloads[broker], payload) -} - -// RegisterDevice implements the core.Router interface -func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { - r.Devices[devAddr] = append(r.Devices[devAddr], broAddrs...) -} - -// RegisterDevice implements the core.Router interface -func (r *Router) HandleError(err interface{}) { - r.Errors = append(r.Errors, err) -} - -// Connect implements the core.Router interface -func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { - // Chill -} From 7f96252ca09912a0a3e32299114477f80b1fc858 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 9 Jan 2016 23:47:28 +0100 Subject: [PATCH 0320/2266] [broker] Remove bin/ folder no more accurate --- bin/router/main.go | 66 ---------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 bin/router/main.go diff --git a/bin/router/main.go b/bin/router/main.go deleted file mode 100644 index b4c71fca8..000000000 --- a/bin/router/main.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package router/main creates and runs an working instance of a router on the host machine -package main - -import ( - "flag" - . "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/adapters/gtw_rtr_udp" - "github.com/thethingsnetwork/core/adapters/rtr_brk_http" - "github.com/thethingsnetwork/core/components" - "github.com/thethingsnetwork/core/utils/log" - "strconv" - "strings" -) - -func main() { - brokers, udpPort := parseOptions() - router, err := components.NewRouter(brokers...) - if err != nil { - panic(err) - } - router.Logger = log.DebugLogger{Tag: "router"} - - upAdapter := gtw_rtr_udp.NewAdapter() - upAdapter.Logger = log.DebugLogger{Tag: "upAdapter"} - - downAdapter := rtr_brk_http.NewAdapter() - downAdapter.Logger = log.DebugLogger{Tag: "downAdapter"} - - router.Connect(upAdapter, downAdapter) - if err := upAdapter.Listen(router, uint(udpPort)); err != nil { - panic(err) - } - if err := downAdapter.Listen(router, brokers); err != nil { - panic(err) - } - - <-make(chan bool) -} - -func parseOptions() (brokers []BrokerAddress, udpPort uint64) { - var brokersFlag string - var udpPortFlag string - flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. - For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000 - `) - flag.StringVar(&udpPortFlag, "udpPort", "", "Udp port on which the router should listen to.") - flag.Parse() - - var err error - udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) - if err != nil { - panic(err) - } - if brokersFlag == "" { - panic("Need to provide at least one broker address") - } - - brokersStr := strings.Split(brokersFlag, ",") - for i := range brokersStr { - brokers = append(brokers, BrokerAddress(strings.Trim(brokersStr[i], " "))) - } - return -} From d38386407c4d42eeef235d30d6e03465cdc9d617 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 00:08:55 +0100 Subject: [PATCH 0321/2266] [broker] Rename adapter and move a bit of logic for voidAckNacker --- adapters/http/broadcast/broadcast.go | 12 ------------ adapters/http/broadcast/void_acknacker.go | 20 ++++++++++++++++++++ adapters/protocols.md | 11 +++++++++++ adapters/semtech/adapter_test.go | 2 +- 4 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 adapters/http/broadcast/void_acknacker.go create mode 100644 adapters/protocols.md diff --git a/adapters/http/broadcast/broadcast.go b/adapters/http/broadcast/broadcast.go index 40bdffeea..2660737ba 100644 --- a/adapters/http/broadcast/broadcast.go +++ b/adapters/http/broadcast/broadcast.go @@ -123,15 +123,3 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) registration := <-a.registrations return registration, voidAckNacker{}, nil } - -type voidAckNacker struct{} - -// Ack implements the core.AckNacker interface -func (v voidAckNacker) Ack(p core.Packet) error { - return nil -} - -// Nack implements the core.AckNacker interface -func (v voidAckNacker) Nack(p core.Packet) error { - return nil -} diff --git a/adapters/http/broadcast/void_acknacker.go b/adapters/http/broadcast/void_acknacker.go new file mode 100644 index 000000000..d19ec8c8d --- /dev/null +++ b/adapters/http/broadcast/void_acknacker.go @@ -0,0 +1,20 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broadcast + +import ( + "github.com/thethingsnetwork/core" +) + +type voidAckNacker struct{} + +// Ack implements the core.AckNacker interface +func (v voidAckNacker) Ack(p core.Packet) error { + return nil +} + +// Nack implements the core.AckNacker interface +func (v voidAckNacker) Nack(p core.Packet) error { + return nil +} diff --git a/adapters/protocols.md b/adapters/protocols.md new file mode 100644 index 000000000..ba49753fe --- /dev/null +++ b/adapters/protocols.md @@ -0,0 +1,11 @@ +semtech ~ udp +============= + +basic ~ http +============ + +basic+registration ~ http +========================= + +basic+broadcast ~ http +====================== diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index f776a3495..caa7c911b 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package gtw_rtr_udp +package semtech import ( "fmt" From 39f5571ca167e9f6ace450091d9954756017791a Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 00:36:41 +0100 Subject: [PATCH 0322/2266] [broker] Rewrite http adapter tests a bit. --- adapters/http/adapter_test.go | 40 +++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/adapters/http/adapter_test.go b/adapters/http/adapter_test.go index 7070fb2d1..c220580d9 100644 --- a/adapters/http/adapter_test.go +++ b/adapters/http/adapter_test.go @@ -5,6 +5,7 @@ package http import ( "encoding/base64" + "encoding/json" "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" @@ -20,31 +21,18 @@ import ( // Send(p core.Packet, r ...core.Recipient) error func TestSend(t *testing.T) { - payload := genPHYPayload("mData", [4]byte{0x1, 0x2, 0x3, 0x4}) - raw, err := payload.MarshalBinary() - if err != nil { - panic(err) - } - encoded := base64.StdEncoding.EncodeToString(raw) - tests := []struct { Packet core.Packet WantPayload string WantError error }{ { - core.Packet{ - Payload: payload, - Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, - }, - fmt.Sprintf(`{"payload":"%s","metadata":{"modu":"LORA","rssi":-20}}`, encoded), + genCorePacket(), + genJSONPayload(genCorePacket()), nil, }, { - core.Packet{ - Payload: lorawan.PHYPayload{}, - Metadata: &components.Metadata{}, - }, + core.Packet{}, "", ErrInvalidPacket, }, @@ -164,3 +152,23 @@ func genPHYPayload(msg string, devAddr [4]byte) lorawan.PHYPayload { return payload } + +func genCorePacket() core.Packet { + return core.Packet{ + Payload: genPHYPayload("myData", [4]byte{0x1, 0x2, 0x3, 0x4}), + Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, + } +} + +func genJSONPayload(p core.Packet) string { + raw, err := p.Payload.MarshalBinary() + if err != nil { + panic(err) + } + payload := base64.StdEncoding.EncodeToString(raw) + metadatas, err := json.Marshal(p.Metadata) + if err != nil { + panic(err) + } + return fmt.Sprintf(`{"payload":"%s","metadata":%s}`, payload, string(metadatas)) +} From 9e698f9e67e3f94fc8a430d1041783d00a20a4d0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 01:22:58 +0100 Subject: [PATCH 0323/2266] [broker] Change logger tag in adapters test --- adapters/http/adapter_test.go | 2 +- adapters/http/registrations/registrations_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/http/adapter_test.go b/adapters/http/adapter_test.go index c220580d9..8875072eb 100644 --- a/adapters/http/adapter_test.go +++ b/adapters/http/adapter_test.go @@ -39,7 +39,7 @@ func TestSend(t *testing.T) { } s := genMockServer(3100) - adapter, err := NewAdapter(log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + adapter, err := NewAdapter(log.TestLogger{Tag: "Adapter", T: t}) if err != nil { panic(err) } diff --git a/adapters/http/registrations/registrations_test.go b/adapters/http/registrations/registrations_test.go index 802dd17a3..c8c32eead 100644 --- a/adapters/http/registrations/registrations_test.go +++ b/adapters/http/registrations/registrations_test.go @@ -59,7 +59,7 @@ func TestNextRegistration(t *testing.T) { }, } - adapter, err := NewAdapter(3001, HandlerParser{}, log.TestLogger{Tag: "BRK_HDL_ADAPTER", T: t}) + adapter, err := NewAdapter(3001, HandlerParser{}, log.TestLogger{Tag: "Adapter", T: t}) client := &client{adapter: "0.0.0.0:3001"} <-time.After(time.Millisecond * 200) if err != nil { From 7846b68661b496155d22c20fb261db95ea760a28 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 02:10:22 +0100 Subject: [PATCH 0324/2266] [broker] Write broadcast tests. Still some fixes to do --- adapters/http/broadcast/broadcast_test.go | 258 ++++++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 adapters/http/broadcast/broadcast_test.go diff --git a/adapters/http/broadcast/broadcast_test.go b/adapters/http/broadcast/broadcast_test.go new file mode 100644 index 000000000..37afae067 --- /dev/null +++ b/adapters/http/broadcast/broadcast_test.go @@ -0,0 +1,258 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broadcast + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" + . "github.com/thethingsnetwork/core/utils/testing" + "net/http" + "reflect" + "testing" + "time" +) + +func TestSend(t *testing.T) { + packet, devAddr, payload := genSample() + recipients := []core.Recipient{ + core.Recipient{Address: "0.0.0.0:3000", Id: "AlwaysReject"}, + core.Recipient{Address: "0.0.0.0:3001", Id: "AlwaysAccept"}, + core.Recipient{Address: "0.0.0.0:3002", Id: "AlwaysAccept"}, + core.Recipient{Address: "0.0.0.0:3003", Id: "AlwaysReject"}, + } + registrations := []core.Registration{ + core.Registration{DevAddr: devAddr, Recipient: recipients[0]}, + core.Registration{DevAddr: devAddr, Recipient: recipients[1]}, + core.Registration{DevAddr: devAddr, Recipient: recipients[2]}, + core.Registration{DevAddr: devAddr, Recipient: recipients[3]}, + } + + tests := []struct { + Recipients []core.Recipient + Packet core.Packet + WantRegistrations []core.Registration + WantPayload string + WantError error + }{ + { // Send to two recipients a valid packet + Recipients: recipients[:2], + Packet: packet, + WantRegistrations: []core.Registration{}, + WantPayload: payload, + WantError: nil, + }, + { // Broadcast a valid packet + Recipients: []core.Recipient{}, + Packet: packet, + WantRegistrations: registrations[2:4], + WantPayload: payload, + WantError: nil, + }, + { // Send to two recipients an invalid packet + Recipients: recipients[:2], + Packet: core.Packet{}, + WantRegistrations: []core.Registration{}, + WantPayload: "", + WantError: ErrInvalidPacket, + }, + { // Broadcast an invalid packet + Recipients: []core.Recipient{}, + Packet: core.Packet{}, + WantRegistrations: []core.Registration{}, + WantPayload: "", + WantError: ErrInvalidPacket, + }, + } + + // Build + adapter, err := NewAdapter(recipients, log.TestLogger{Tag: "Adapter", T: t}) + if err != nil { + panic(err) + } + var servers []chan string + for _, r := range recipients { + servers = append(servers, genMockServer(r)) + } + + for _, test := range tests { + // Describe + Desc(t, "Sending packet %v to %v", test.Packet, test.Recipients) + + // Operate + err := adapter.Send(test.Packet, test.Recipients...) + registrations := getRegistrations(adapter, test.WantRegistrations) + payloads := getPayloads(servers) + + // Check + checkErrors(t, test.WantError, err) + checkPayloads(t, test.WantPayload, payloads) + checkRegistrations(t, test.WantRegistrations, registrations) + } +} + +// Operate utilities +func getPayloads(chpayloads []chan string) []string { + var got []string + for _, ch := range chpayloads { + select { + case payload := <-ch: + got = append(got, payload) + case <-time.After(50 * time.Millisecond): + } + } + return got +} + +func getRegistrations(adapter *Adapter, want []core.Registration) []core.Registration { + var got []core.Registration + for range want { + ch := make(chan core.Registration) + go func() { + r, an, err := adapter.NextRegistration() + if err != nil { + return + } + an.Ack(core.Packet{}) + ch <- r + }() + select { + case r := <-ch: + got = append(got, r) + case <-time.After(50 * time.Millisecond): + } + } + return got +} + +// Build utilities + +func genMockServer(recipient core.Recipient) chan string { + chresp := make(chan string) + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + if req.Header.Get("Content-Type") != "application/json" { + w.WriteHeader(http.StatusBadRequest) + w.Write(nil) + return + } + + buf := make([]byte, req.ContentLength) + n, err := req.Body.Read(buf) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(nil) + return + } + + switch recipient.Id { + case "AlwaysReject": + w.WriteHeader(http.StatusNotFound) + w.Write(nil) + case "AlwaysAccept": + w.WriteHeader(http.StatusNotFound) + w.Write(nil) + } + chresp <- string(buf[:n]) + }) + + server := http.Server{ + Addr: recipient.Address.(string), + Handler: serveMux, + } + go server.ListenAndServe() + return chresp +} + +// Generate a Physical payload representing an uplink message +func genSample() (core.Packet, lorawan.DevAddr, string) { + + // 1. Generate a PHYPayload + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + devAddr := lorawan.DevAddr([4]byte{0x1, 0x14, 0x2, 0x42}) + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: devAddr, + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + // 2. Generate a JSON payload received by the server + raw, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + encoded := base64.StdEncoding.EncodeToString(raw) + metadata := components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")} + rawMeta, err := json.Marshal(metadata) + if err != nil { + panic(err) + } + jsonPayload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, encoded, string(rawMeta)) + + // 3. Return valuable info for the test + return core.Packet{Payload: payload, Metadata: &metadata}, devAddr, jsonPayload +} + +// Check utilities + +func checkErrors(t *testing.T, want error, got error) { + if want == got { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error %v but got %v", want, got) +} + +func checkRegistrations(t *testing.T, want []core.Registration, got []core.Registration) { +outer: + for _, rw := range want { + for _, rg := range got { + if reflect.DeepEqual(rg, rw) { + continue outer + } + } + Ko(t, "Registrations don't match expectation.\nWant: %v\nGot: %v", want, got) + return + } + Ok(t, "Check registrations") +} + +func checkPayloads(t *testing.T, want string, got []string) { + for _, payload := range got { + if want != payload { + Ko(t, "Paylaod don't match expectation.\nWant: %s\nGot: %s", want, payload) + return + } + } + Ok(t, "Check payloads") +} From 67e32274acc67f5ecc55d9780c68d4dc12c1742a Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 02:23:38 +0100 Subject: [PATCH 0325/2266] [broker] Fix tests and make the function passes them --- adapters/http/broadcast/broadcast.go | 6 +++++- adapters/http/broadcast/broadcast_test.go | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/adapters/http/broadcast/broadcast.go b/adapters/http/broadcast/broadcast.go index 2660737ba..75fe72eea 100644 --- a/adapters/http/broadcast/broadcast.go +++ b/adapters/http/broadcast/broadcast.go @@ -45,7 +45,11 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { if len(r) == 0 { return a.broadcast(p) } - return a.Adapter.Send(p, r...) + err := a.Adapter.Send(p, r...) + if err == httpadapter.ErrInvalidPacket { + return ErrInvalidPacket + } + return err } func (a *Adapter) broadcast(p core.Packet) error { diff --git a/adapters/http/broadcast/broadcast_test.go b/adapters/http/broadcast/broadcast_test.go index 37afae067..deb5800bc 100644 --- a/adapters/http/broadcast/broadcast_test.go +++ b/adapters/http/broadcast/broadcast_test.go @@ -13,6 +13,7 @@ import ( "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" + "io" "net/http" "reflect" "testing" @@ -42,7 +43,7 @@ func TestSend(t *testing.T) { WantError error }{ { // Send to two recipients a valid packet - Recipients: recipients[:2], + Recipients: recipients[1:3], // TODO test with a rejection. Need better error handling Packet: packet, WantRegistrations: []core.Registration{}, WantPayload: payload, @@ -51,7 +52,7 @@ func TestSend(t *testing.T) { { // Broadcast a valid packet Recipients: []core.Recipient{}, Packet: packet, - WantRegistrations: registrations[2:4], + WantRegistrations: registrations[1:3], WantPayload: payload, WantError: nil, }, @@ -145,7 +146,7 @@ func genMockServer(recipient core.Recipient) chan string { buf := make([]byte, req.ContentLength) n, err := req.Body.Read(buf) - if err != nil { + if err != nil && err != io.EOF { w.WriteHeader(http.StatusBadRequest) w.Write(nil) return @@ -156,10 +157,10 @@ func genMockServer(recipient core.Recipient) chan string { w.WriteHeader(http.StatusNotFound) w.Write(nil) case "AlwaysAccept": - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusOK) w.Write(nil) } - chresp <- string(buf[:n]) + go func() { chresp <- string(buf[:n]) }() }) server := http.Server{ From 59c4bf8ac61d7cf61a2aad7b1959ca39167dd743 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 14:38:06 +0100 Subject: [PATCH 0326/2266] [broker] Make semtech adapter implments the core.Adapter interface --- adapters/semtech/adapter.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/adapters/semtech/adapter.go b/adapters/semtech/adapter.go index 1ccbed39e..3afe65881 100644 --- a/adapters/semtech/adapter.go +++ b/adapters/semtech/adapter.go @@ -31,6 +31,7 @@ type rxpkMsg struct { var ErrInvalidPort error = fmt.Errorf("Invalid port supplied. The connection might be already taken") var ErrNotInitialized error = fmt.Errorf("Illegal call on non-initialized adapter") +var ErrNotSupported error = fmt.Errorf("Unsupported operation") // New constructs and allocates a new udp_sender adapter func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { @@ -62,7 +63,7 @@ func (a *Adapter) ok() bool { // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { - return fmt.Errorf("Unsupported method") + return ErrNotSupported } // Next implements the core.Adapter interface @@ -78,6 +79,11 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil } +// NextRegistration implements the core.Adapter interface +func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { + return core.Packet{}, nil, ErrNotSupported +} + // listen Handle incoming packets and forward them func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() From ddadb0e2ae34dc83eec20dab98833808a6bae01f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 14:40:19 +0100 Subject: [PATCH 0327/2266] [broker] Regenerate semtech adapter tests --- adapters/semtech/adapter_test.go | 264 +------------------------------ 1 file changed, 8 insertions(+), 256 deletions(-) diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index caa7c911b..91cb49db7 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -4,270 +4,22 @@ package semtech import ( - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/testing/mock_components" - "github.com/thethingsnetwork/core/utils/log" . "github.com/thethingsnetwork/core/utils/testing" - "net" - "reflect" "testing" - "time" ) -// ----- The adapter should be able to create a udp connection given a valid udp port -func TestListenOptions(t *testing.T) { - tests := []listenOptionsTest{ - {uint(3000), nil}, - {uint(3000), core.ErrBadGatewayAddress}, // Already used now - {int(14), core.ErrBadOptions}, - {"somethingElse", core.ErrBadOptions}, - } - - for _, test := range tests { - test.run(t) - } -} - -type listenOptionsTest struct { - options interface{} - want error -} - -func (test listenOptionsTest) run(t *testing.T) { - Desc(t, "Run Listen(router, %T %v)", test.options, test.options) - adapter, router := generateAdapterAndRouter(t) - got := adapter.Listen(router, test.options) - test.check(t, got) -} - -func (test listenOptionsTest) check(t *testing.T, got error) { - // 1. Check if errors match - if got != test.want { - Ko(t, "expected {%v} to be {%v}", got, test.want) - return - } - Ok(t) -} - -// ----- The adapter should catch from the connection and forward valid semtech.Packet to the router -func TestPacketProcessing(t *testing.T) { - tests := []packetProcessingTest{ - {generatePUSH_DATA(), 1, 3001}, - {[]byte{0x14, 0xff}, 0, 3002}, - } - - for _, test := range tests { - test.run(t) - } -} - -type packetProcessingTest struct { - in interface{} // Could be raw []byte or plain semtech.Packet - want uint // 0 or 1 depending whether or not we expect a packet to has been transmitted - port uint // Probably temporary, just because goroutine and connection are still living between tests +func TestNewAdapter(t *testing.T) { + Ok(t, "pending") } -func (test packetProcessingTest) run(t *testing.T) { - Desc(t, "Simulate incoming datagram: %+v", test.in) - adapter, router := generateAdapterAndRouter(t) - conn, gateway := createConnection(adapter, router, test.port) - defer conn.Close() - sendDatagram(conn, test.in) - test.check(t, router, gateway) // Check whether or not packet has been forwarded to core router +func TestSend(t *testing.T) { + Ok(t, "pending") } -func (test packetProcessingTest) check(t *testing.T, router core.Router, gateway core.GatewayAddress) { - <-time.After(time.Millisecond * 50) - mockRouter := router.(*mock_components.Router) - - // 1. Check if we expect a packet - packets := mockRouter.Packets[gateway] - if nb := len(packets); uint(nb) != test.want { - Ko(t, "Received %d packets whereas expected %d", nb, test.want) - return - } - - // 2. If a packet was expected, check that it has been forwarded to the router - if test.want > 0 { - if !reflect.DeepEqual(packets[0], test.in) { - Ko(t, "Expected %+v to match %+v", packets[0], test.in) - return - } - } - - Ok(t) -} - -// ----- The adapter should send packet via back to an existing address through an opened connection -func TestSendAck(t *testing.T) { - // 1. Initialize test data - adapter, router := generateAdapterAndRouter(t) - conn, gateway := createConnection(adapter, router, 3003) - defer conn.Close() - - tests := []sendAckTest{ - {adapter, router, conn, gateway, generatePUSH_ACK(), nil}, - {adapter, router, conn, core.GatewayAddress("patate"), generatePUSH_ACK(), core.ErrBadGatewayAddress}, - {adapter, router, conn, gateway, semtech.Packet{}, core.ErrInvalidPacket}, - } - - // 2. Run tests - for _, test := range tests { - test.run(t) - } -} - -type sendAckTest struct { - adapter *Adapter - router core.Router - conn *net.UDPConn - gateway core.GatewayAddress - packet semtech.Packet - want error +func TestNextRegistration(t *testing.T) { + Ok(t, "pending") } -func (test sendAckTest) run(t *testing.T) { - Desc(t, "Send ack packet %v to %v via %v", test.packet, test.conn, test.gateway) - // Starts a goroutine that will redirect udp message to a dedicated channel - cmsg := listenFromConnection(test.conn) - defer close(cmsg) - got := test.adapter.Ack(test.router, test.packet, test.gateway) - test.check(t, cmsg, got) // Check the error or the packet if no error -} - -func (test sendAckTest) check(t *testing.T, cmsg chan semtech.Packet, got error) { - // 1. Check if an error was expected - if test.want != nil { - if got != test.want { - Ko(t, "Expected %+v error but got %+v", test.want, got) - return - } - Ok(t) - return - } - - // 2. Ensure the ack packet has been sent correctly - packet := <-cmsg - if !reflect.DeepEqual(test.packet, packet) { - Ko(t, "Expected %+v to equal %+v", test.packet, packet) - return - } - Ok(t) -} - -// ----- In case of issue, the connection can be re-established -func TestConnectionRecovering(t *testing.T) { - adapter, router := generateAdapterAndRouter(t) - if err := adapter.Listen(router, uint(3004)); err != nil { - panic(err) - } - err := adapter.Listen(router, uint(3005)) - - if err != nil { - Ko(t, "No error was expected but got: %+v", err) - return - } - - // Now try to send a packet on a switched connection - err = adapter.Ack(router, generatePUSH_ACK(), core.GatewayAddress("0.0.0.0:3005")) - if err != nil { - Ko(t, "No error was expected but got: %+v", err) - return - } - - Ok(t) -} - -// ----- Build Utilities -func generateAdapterAndRouter(t *testing.T) (*Adapter, core.Router) { - a := NewAdapter() - a.Logger = log.TestLogger{Tag: "Adapter", T: t} - return a, mock_components.NewRouter() -} - -func generatePUSH_DATA() semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Token: []byte{0x14, 0x42}, - Identifier: semtech.PUSH_DATA, - } -} - -func generatePUSH_ACK() semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: []byte{0x14, 0x42}, - Identifier: semtech.PUSH_ACK, - } -} - -// ----- Operate Utilities -func createConnection(adapter *Adapter, router core.Router, port uint) (*net.UDPConn, core.GatewayAddress) { - var err error - - // 1. Start the adapter watching procedure - if err = adapter.Listen(router, port); err != nil { - panic(err) - } - - // 2. Create a UDP connection on the same port the adapter is listening - var addr *net.UDPAddr - var conn *net.UDPConn - if addr, err = net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)); err != nil { - panic(err) - } - if conn, err = net.DialUDP("udp", nil, addr); err != nil { - panic(err) - } - - // 3. Return the UDP connection and the corresponding simulated gateway address - return conn, core.GatewayAddress(conn.LocalAddr().String()) -} - -func sendDatagram(conn *net.UDPConn, data interface{}) { - // 1. Send the packet or the raw sequence of bytes passed as argument - var raw []byte - var err error - switch data.(type) { - case []byte: - raw = data.([]byte) - case semtech.Packet: - if raw, err = semtech.Marshal(data.(semtech.Packet)); err != nil { - panic(err) - } - default: - panic(fmt.Errorf("Unexpected data type to be send : %T", data)) - } - if _, err = conn.Write(raw); err != nil { - panic(err) - } -} - -func listenFromConnection(conn *net.UDPConn) (cmsg chan semtech.Packet) { - cmsg = make(chan semtech.Packet) - - // We won't listen on a nil connection - if conn == nil { - return - } - - // Otherwise, wait for a packet - go func() { - for { - buf := make([]byte, 128) - n, err := conn.Read(buf) - if err != nil { - return - } - packet, err := semtech.Unmarshal(buf[:n]) - if err == nil { - cmsg <- *packet - } - } - }() - - return +func TestNext(t *testing.T) { + Ok(t, "pending") } From 3a114e9550073cf66c07f9656d13277607c7027e Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 14:47:55 +0100 Subject: [PATCH 0328/2266] [broker] Write tests for unsupported methods --- adapters/semtech/adapter_test.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index 91cb49db7..3a5805d4b 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -4,6 +4,7 @@ package semtech import ( + "github.com/thethingsnetwork/core" . "github.com/thethingsnetwork/core/utils/testing" "testing" ) @@ -13,13 +14,33 @@ func TestNewAdapter(t *testing.T) { } func TestSend(t *testing.T) { - Ok(t, "pending") + Desc(t, "Send is not supported") + adapter, err := NewAdapter(33000) + if err != nil { + panic(err) + } + err = adapter.Send(core.Packet{}) + checkErrors(t, ErrNotSupported, err) } func TestNextRegistration(t *testing.T) { - Ok(t, "pending") + Desc(t, "Next registration is not supported") + adapter, err := NewAdapter(33001) + if err != nil { + panic(err) + } + _, _, err = adapter.NextRegistration() + checkErrors(t, ErrNotSupported, err) } func TestNext(t *testing.T) { Ok(t, "pending") } + +func checkErrors(t *testing.T, want error, got error) { + if want == got { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be %v but got %v", want, got) +} From cf75239a839994188c691ed12ccb26cace4b0f95 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 21:02:14 +0100 Subject: [PATCH 0329/2266] [broker] Write down test for semtech send() method --- adapters/semtech/adapter_test.go | 264 ++++++++++++++++++++++++++++++- 1 file changed, 263 insertions(+), 1 deletion(-) diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index 3a5805d4b..1af34215e 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -4,9 +4,19 @@ package semtech import ( + "encoding/base64" + "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" + "net" + "reflect" "testing" + "time" ) func TestNewAdapter(t *testing.T) { @@ -34,9 +44,245 @@ func TestNextRegistration(t *testing.T) { } func TestNext(t *testing.T) { - Ok(t, "pending") + adapter, err := NewAdapter(33002, log.TestLogger{Tag: "Adapter", T: t}) + if err != nil { + panic(err) + } + server := genMockServer(33002) + + tests := []struct { + Adapter *Adapter + Packet semtech.Packet + WantAck semtech.Packet + WantNext core.Packet + WantError error + }{ + { // Valid uplink PUSH_DATA + Adapter: adapter, + Packet: genPUSH_DATAWithRXPK([]byte{0x14, 0x42}), + WantAck: genPUSH_ACK([]byte{0x14, 0x42}), + WantNext: genCorePacket(genPUSH_DATAWithRXPK([]byte{0x14, 0x42})), + WantError: nil, + }, + { // Invalid uplink packet + Adapter: adapter, + Packet: genPUSH_ACK([]byte{0x22, 0x35}), + WantAck: semtech.Packet{}, + WantNext: core.Packet{}, + WantError: nil, + }, + { // Uplink PUSH_DATA with no RXPK + Adapter: adapter, + Packet: genPUSH_DATANoRXPK([]byte{0x22, 0x35}), + WantAck: genPUSH_ACK([]byte{0x22, 0x35}), + WantNext: core.Packet{}, + WantError: nil, + }, + { // Uplink PULL_DATA + Adapter: adapter, + Packet: genPULL_DATA([]byte{0x62, 0xfa}), + WantAck: genPULL_ACK([]byte{0x62, 0xfa}), + WantNext: core.Packet{}, + WantError: nil, + }, + { // Uplink PUSH_DATA with no encoded payload + Adapter: adapter, + Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), + WantAck: semtech.Packet{}, + WantNext: core.Packet{}, + WantError: ErrInvalidPacket, + }, + } + + for _, test := range tests { + // Describe + Desc(t, "Sending packet through adapter: %v", test.Packet) + + // Operate + ack := server.send(test.Packet) + packet, err := getNextPacket(adapter) + + // Check + checkErrors(t, test.WantError, err) + checkCorePackets(t, test.WantNext, packet) + checkResponses(t, test.WantAck, ack) + } +} + +// ----- build utilities +type mockServer struct { + conn *net.UDPConn +} + +func genMockServer(port uint) mockServer { + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + panic(err) + } + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + panic(err) + } + + return mockServer{conn: conn} +} + +func (s mockServer) send(p semtech.Packet) semtech.Packet { + raw, err := semtech.Marshal(p) + if err != nil { + panic(err) + } + response := make(chan semtech.Packet) + go func() { + buf := make([]byte, 256) + n, _, err := s.conn.ReadFromUDP(buf) + if err != nil { + panic(err) + } + packet, err := semtech.Unmarshal(buf[:n]) + if err != nil { + panic(err) + } + response <- *packet + }() + s.conn.Write(raw) + select { + case packet := <-response: + return packet + case <-time.After(100 * time.Millisecond): + return semtech.Packet{} + } +} + +func genCorePacket(p semtech.Packet) core.Packet { + if p.Payload == nil || len(p.Payload.RXPK) != 1 { + panic("Expected a payload with one rxpk") + } + packet, err := components.ConvertRXPK(p.Payload.RXPK[0]) + if err != nil { + panic(err) + } + return packet +} + +func genPUSH_DATANoRXPK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Token: token, + Identifier: semtech.PUSH_DATA, + } +} + +func genPUSH_DATANoPayload(token []byte) semtech.Packet { + packet := genPUSH_DATAWithRXPK(token) + packet.Payload.RXPK[0].Data = nil + return packet +} + +func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { + packet := genPUSH_DATANoRXPK(token) + packet.Payload = &semtech.Payload{ + RXPK: []semtech.RXPK{ + semtech.RXPK{ + Rssi: pointer.Int(-60), + Codr: pointer.String("4/7"), + Data: pointer.String(genRXPKData()), + }, + }, + } + return packet +} + +func genPULL_ACK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + Identifier: semtech.PULL_ACK, + } +} + +func genPUSH_ACK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + Identifier: semtech.PUSH_ACK, + } +} + +func genPULL_DATA(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Identifier: semtech.PULL_DATA, + } } +func genRXPKData() string { + // 1. Generate a PHYPayload + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + // 2. Generate a JSON payload received by the server + raw, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(raw) +} + +// ----- operate utilities +func getNextPacket(a *Adapter) (core.Packet, error) { + next := make(chan struct { + err error + packet core.Packet + }) + go func() { + packet, _, err := a.Next() + next <- struct { + err error + packet core.Packet + }{err: err, packet: packet} + }() + + select { + case res := <-next: + return res.packet, res.err + case <-time.After(100 * time.Millisecond): + return core.Packet{}, nil + } +} + +// ----- check utilities func checkErrors(t *testing.T, want error, got error) { if want == got { Ok(t, "Check errors") @@ -44,3 +290,19 @@ func checkErrors(t *testing.T, want error, got error) { } Ko(t, "Expected error to be %v but got %v", want, got) } + +func checkCorePackets(t *testing.T, want core.Packet, got core.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check core packets") + return + } + Ko(t, "Received core packet does not match expecatations.\nWant: %v\nGot: %v", want, got) +} + +func checkResponses(t *testing.T, want semtech.Packet, got semtech.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check responses") + return + } + Ko(t, "Received response does not match expecatations.\nWant: %v\nGot: %v", want, got) +} From d4b75d50938eea543d5237e31e8ca3f2d0c3f85c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 21:53:30 +0100 Subject: [PATCH 0330/2266] [broker] Fix tests and split it to enhance readiblity --- adapters/semtech/adapter_test.go | 185 +++------------------- adapters/semtech/build_utilities_tes.go | 197 ++++++++++++++++++++++++ 2 files changed, 215 insertions(+), 167 deletions(-) create mode 100644 adapters/semtech/build_utilities_tes.go diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index 1af34215e..cfcf84207 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -4,16 +4,10 @@ package semtech import ( - "encoding/base64" - "fmt" "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan" - components "github.com/thethingsnetwork/core/refactored_components" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" - "net" "reflect" "testing" "time" @@ -44,10 +38,7 @@ func TestNextRegistration(t *testing.T) { } func TestNext(t *testing.T) { - adapter, err := NewAdapter(33002, log.TestLogger{Tag: "Adapter", T: t}) - if err != nil { - panic(err) - } + adapter, next := genAdapter(t, 33002) server := genMockServer(33002) tests := []struct { @@ -88,7 +79,7 @@ func TestNext(t *testing.T) { { // Uplink PUSH_DATA with no encoded payload Adapter: adapter, Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), - WantAck: semtech.Packet{}, + WantAck: genPUSH_ACK([]byte{0x22, 0x35}), WantNext: core.Packet{}, WantError: ErrInvalidPacket, }, @@ -100,7 +91,7 @@ func TestNext(t *testing.T) { // Operate ack := server.send(test.Packet) - packet, err := getNextPacket(adapter) + packet, err := getNextPacket(next) // Check checkErrors(t, test.WantError, err) @@ -110,172 +101,32 @@ func TestNext(t *testing.T) { } // ----- build utilities -type mockServer struct { - conn *net.UDPConn -} - -func genMockServer(port uint) mockServer { - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) +func genAdapter(t *testing.T, port uint) (*Adapter, chan interface{}) { + adapter, err := NewAdapter(port, log.TestLogger{Tag: "Adapter", T: t}) if err != nil { panic(err) } - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - panic(err) - } - - return mockServer{conn: conn} -} - -func (s mockServer) send(p semtech.Packet) semtech.Packet { - raw, err := semtech.Marshal(p) - if err != nil { - panic(err) - } - response := make(chan semtech.Packet) + next := make(chan interface{}) go func() { - buf := make([]byte, 256) - n, _, err := s.conn.ReadFromUDP(buf) - if err != nil { - panic(err) - } - packet, err := semtech.Unmarshal(buf[:n]) - if err != nil { - panic(err) + for { + packet, _, err := adapter.Next() + next <- struct { + err error + packet core.Packet + }{err: err, packet: packet} } - response <- *packet }() - s.conn.Write(raw) - select { - case packet := <-response: - return packet - case <-time.After(100 * time.Millisecond): - return semtech.Packet{} - } -} - -func genCorePacket(p semtech.Packet) core.Packet { - if p.Payload == nil || len(p.Payload.RXPK) != 1 { - panic("Expected a payload with one rxpk") - } - packet, err := components.ConvertRXPK(p.Payload.RXPK[0]) - if err != nil { - panic(err) - } - return packet -} - -func genPUSH_DATANoRXPK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Token: token, - Identifier: semtech.PUSH_DATA, - } -} - -func genPUSH_DATANoPayload(token []byte) semtech.Packet { - packet := genPUSH_DATAWithRXPK(token) - packet.Payload.RXPK[0].Data = nil - return packet -} - -func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { - packet := genPUSH_DATANoRXPK(token) - packet.Payload = &semtech.Payload{ - RXPK: []semtech.RXPK{ - semtech.RXPK{ - Rssi: pointer.Int(-60), - Codr: pointer.String("4/7"), - Data: pointer.String(genRXPKData()), - }, - }, - } - return packet -} - -func genPULL_ACK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - Identifier: semtech.PULL_ACK, - } -} - -func genPUSH_ACK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - Identifier: semtech.PUSH_ACK, - } -} - -func genPULL_DATA(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Identifier: semtech.PULL_DATA, - } -} - -func genRXPKData() string { - // 1. Generate a PHYPayload - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(true) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - // 2. Generate a JSON payload received by the server - raw, err := payload.MarshalBinary() - if err != nil { - panic(err) - } - return base64.StdEncoding.EncodeToString(raw) + return adapter, next } // ----- operate utilities -func getNextPacket(a *Adapter) (core.Packet, error) { - next := make(chan struct { - err error - packet core.Packet - }) - go func() { - packet, _, err := a.Next() - next <- struct { +func getNextPacket(next chan interface{}) (core.Packet, error) { + select { + case i := <-next: + res := i.(struct { err error packet core.Packet - }{err: err, packet: packet} - }() - - select { - case res := <-next: + }) return res.packet, res.err case <-time.After(100 * time.Millisecond): return core.Packet{}, nil diff --git a/adapters/semtech/build_utilities_tes.go b/adapters/semtech/build_utilities_tes.go new file mode 100644 index 000000000..5c9bd9bd0 --- /dev/null +++ b/adapters/semtech/build_utilities_tes.go @@ -0,0 +1,197 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package semtech + +import ( + "encoding/base64" + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/core/utils/pointer" + "net" + "testing" + "time" +) + +// ----- build utilities +type mockServer struct { + conn *net.UDPConn + response chan semtech.Packet +} + +// Generate a mock server that will send packet through a udp connection and communicate back +// received packet. +func genMockServer(port uint) mockServer { + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + panic(err) + } + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + panic(err) + } + + response := make(chan semtech.Packet) + go func() { + for { + buf := make([]byte, 256) + n, _, err := conn.ReadFromUDP(buf) + if err != nil { + panic(err) + } + packet, err := semtech.Unmarshal(buf[:n]) + if err != nil { + panic(err) + } + response <- *packet + } + }() + + return mockServer{conn: conn, response: response} +} + +// Send a packet through the udp mock server toward the adapter +func (s mockServer) send(p semtech.Packet) semtech.Packet { + raw, err := semtech.Marshal(p) + if err != nil { + panic(err) + } + s.conn.Write(raw) + select { + case packet := <-s.response: + return packet + case <-time.After(100 * time.Millisecond): + return semtech.Packet{} + } +} + +// Generates an adapter as well as a channel that behaves like the Next() methods (but can be used +// in a select for timeout) +func genAdapter(t *testing.T, port uint) (*Adapter, chan interface{}) { + adapter, err := NewAdapter(port, log.TestLogger{Tag: "Adapter", T: t}) + if err != nil { + panic(err) + } + next := make(chan interface{}) + go func() { + for { + packet, _, err := adapter.Next() + next <- struct { + err error + packet core.Packet + }{err: err, packet: packet} + } + }() + return adapter, next +} + +// Generate a core packet from a semtech packet that has one RXPK +func genCorePacket(p semtech.Packet) core.Packet { + if p.Payload == nil || len(p.Payload.RXPK) != 1 { + panic("Expected a payload with one rxpk") + } + packet, err := components.ConvertRXPK(p.Payload.RXPK[0]) + if err != nil { + panic(err) + } + return packet +} + +func genPUSH_DATANoRXPK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Token: token, + Identifier: semtech.PUSH_DATA, + } +} + +func genPUSH_DATANoPayload(token []byte) semtech.Packet { + packet := genPUSH_DATAWithRXPK(token) + packet.Payload.RXPK[0].Data = nil + return packet +} + +func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { + packet := genPUSH_DATANoRXPK(token) + packet.Payload = &semtech.Payload{ + RXPK: []semtech.RXPK{ + semtech.RXPK{ + Rssi: pointer.Int(-60), + Codr: pointer.String("4/7"), + Data: pointer.String(genRXPKData()), + }, + }, + } + return packet +} + +func genPULL_ACK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + Identifier: semtech.PULL_ACK, + } +} + +func genPUSH_ACK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + Identifier: semtech.PUSH_ACK, + } +} + +func genPULL_DATA(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Identifier: semtech.PULL_DATA, + } +} + +func genRXPKData() string { + // 1. Generate a PHYPayload + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + // 2. Generate a JSON payload received by the server + raw, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(raw) +} From 2518d212113cc50a5449f58eb92e8254c95257c6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 21:54:09 +0100 Subject: [PATCH 0331/2266] [broker] Fix issues on semtech adapter and make tests pass --- adapters/semtech/adapter.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/adapters/semtech/adapter.go b/adapters/semtech/adapter.go index 3afe65881..cf2bea540 100644 --- a/adapters/semtech/adapter.go +++ b/adapters/semtech/adapter.go @@ -32,6 +32,7 @@ type rxpkMsg struct { var ErrInvalidPort error = fmt.Errorf("Invalid port supplied. The connection might be already taken") var ErrNotInitialized error = fmt.Errorf("Illegal call on non-initialized adapter") var ErrNotSupported error = fmt.Errorf("Unsupported operation") +var ErrInvalidPacket error = fmt.Errorf("Invalid packet supplied") // New constructs and allocates a new udp_sender adapter func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { @@ -74,7 +75,8 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { msg := <-a.next packet, err := components.ConvertRXPK(msg.rxpk) if err != nil { - return core.Packet{}, nil, err + a.log("Invalid Packet") + return core.Packet{}, nil, ErrInvalidPacket } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil } @@ -108,19 +110,31 @@ func (a *Adapter) listen(conn *net.UDPConn) { pullAck, err := semtech.Marshal(semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, - Identifier: semtech.PUSH_ACK, + Identifier: semtech.PULL_ACK, }) if err != nil { a.log("Unexpected error while marshaling PULL_ACK: %v", err) continue } + a.log("Sending PULL_ACK to %v", addr) a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component + pushAck, err := semtech.Marshal(semtech.Packet{ + Version: semtech.VERSION, + Token: pkt.Token, + Identifier: semtech.PUSH_ACK, + }) + if err != nil { + a.log("Unexpected error while marshaling PUSH_ACK: %v", err) + continue + } + a.log("Sending PUSH_ACK to %v", addr) + a.conn <- udpMsg{addr: addr, raw: pushAck} + if pkt.Payload == nil { a.log("Inconsistent PUSH_DATA packet %v", pkt) continue } - for _, rxpk := range pkt.Payload.RXPK { a.next <- rxpkMsg{ rxpk: rxpk, From c350dc100ff9349cd7b4bc6efb14d59af764ea66 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 21:55:11 +0100 Subject: [PATCH 0332/2266] [broker] Remove duplicate method in adapter_Test --- adapters/semtech/adapter_test.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index cfcf84207..8a576b306 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -6,7 +6,6 @@ package semtech import ( "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/log" . "github.com/thethingsnetwork/core/utils/testing" "reflect" "testing" @@ -100,25 +99,6 @@ func TestNext(t *testing.T) { } } -// ----- build utilities -func genAdapter(t *testing.T, port uint) (*Adapter, chan interface{}) { - adapter, err := NewAdapter(port, log.TestLogger{Tag: "Adapter", T: t}) - if err != nil { - panic(err) - } - next := make(chan interface{}) - go func() { - for { - packet, _, err := adapter.Next() - next <- struct { - err error - packet core.Packet - }{err: err, packet: packet} - } - }() - return adapter, next -} - // ----- operate utilities func getNextPacket(next chan interface{}) (core.Packet, error) { select { From ac03d7ccfa9ef7a26659e54ab0139c11b68d9fdc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 21:56:57 +0100 Subject: [PATCH 0333/2266] [broker] rename build_utilities (typo) --- .../semtech/{build_utilities_tes.go => build_utilities_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename adapters/semtech/{build_utilities_tes.go => build_utilities_test.go} (100%) diff --git a/adapters/semtech/build_utilities_tes.go b/adapters/semtech/build_utilities_test.go similarity index 100% rename from adapters/semtech/build_utilities_tes.go rename to adapters/semtech/build_utilities_test.go From 816dfcdf600b5e34cb0da060df1cfb3c03564e52 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 21:58:51 +0100 Subject: [PATCH 0334/2266] [broker] Remove router component which needs to be refactored. --- components/address_keeper.go | 80 ------- components/address_keeper_test.go | 112 ---------- .../broker.go | 0 .../broker_test.go | 0 .../build_utilities_test.go | 0 .../check_utilities_test.go | 0 components/doc.go | 7 - .../metadata.go | 0 .../metadata_test.go | 0 .../packet.go | 0 .../packet_test.go | 0 components/router.go | 203 ------------------ components/router_test.go | 175 --------------- 13 files changed, 577 deletions(-) delete mode 100644 components/address_keeper.go delete mode 100644 components/address_keeper_test.go rename {refactored_components => components}/broker.go (100%) rename {refactored_components => components}/broker_test.go (100%) rename {refactored_components => components}/build_utilities_test.go (100%) rename {refactored_components => components}/check_utilities_test.go (100%) delete mode 100644 components/doc.go rename {refactored_components => components}/metadata.go (100%) rename {refactored_components => components}/metadata_test.go (100%) rename {refactored_components => components}/packet.go (100%) rename {refactored_components => components}/packet_test.go (100%) delete mode 100644 components/router.go delete mode 100644 components/router_test.go diff --git a/components/address_keeper.go b/components/address_keeper.go deleted file mode 100644 index 06a79c44c..000000000 --- a/components/address_keeper.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "sync" - "time" -) - -type addressKeeper interface { - lookup(devAddr semtech.DeviceAddress) ([]core.BrokerAddress, error) - store(devAddr semtech.DeviceAddress, brosAddr ...core.BrokerAddress) error -} - -type reddisAddressKeeper struct{} // In a second time - -type localDB struct { - expiryDelay time.Duration - addresses map[semtech.DeviceAddress]localEntry - lock sync.RWMutex -} - -type localEntry struct { - addr []core.BrokerAddress - until time.Time -} - -// NewLocalDB constructs a new local address keeper -func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { - if expiryDelay == 0 { - return nil, fmt.Errorf("Invalid expiration delay") - } - - return &localDB{ - expiryDelay: expiryDelay, - addresses: make(map[semtech.DeviceAddress]localEntry), - lock: sync.RWMutex{}, - }, nil -} - -// lookup implements the addressKeeper interface -func (a *localDB) lookup(devAddr semtech.DeviceAddress) ([]core.BrokerAddress, error) { - a.lock.RLock() - entry, ok := a.addresses[devAddr] - a.lock.RUnlock() - if !ok { - return nil, fmt.Errorf("Device address not found") - } - - if entry.until.Before(time.Now()) { - a.lock.Lock() - delete(a.addresses, devAddr) - a.lock.Unlock() - return nil, fmt.Errorf("Broker address(es) expired") - } - - return entry.addr, nil -} - -// store implements the addressKeeper interface -func (a *localDB) store(devAddr semtech.DeviceAddress, brosAddr ...core.BrokerAddress) error { - a.lock.Lock() - _, ok := a.addresses[devAddr] - if ok { - a.lock.Unlock() - return fmt.Errorf("An entry already exists for that device") - } - - a.addresses[devAddr] = localEntry{ - addr: brosAddr, - until: time.Now().Add(a.expiryDelay), - } - - a.lock.Unlock() - return nil -} diff --git a/components/address_keeper_test.go b/components/address_keeper_test.go deleted file mode 100644 index f1651f160..000000000 --- a/components/address_keeper_test.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "bytes" - "encoding/binary" - "fmt" - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "testing" - "time" -) - -func genDevAddr() semtech.DeviceAddress { - devAddr := [4]byte{} - token := new(bytes.Buffer) - binary.Write(token, binary.LittleEndian, time.Now().UnixNano()) - copy(devAddr[:], token.Bytes()[:4]) - return devAddr -} - -func genBroAddr() core.BrokerAddress { - return core.BrokerAddress(fmt.Sprintf("BrokerAddress%d", time.Now())) -} - -func TestAddressKeeper(t *testing.T) { - Convey("Local Address Keeper", t, func() { - Convey("NewLocalDB", func() { - Convey("NewLocalDB: valid", func() { - localDB, err := NewLocalDB(time.Hour * 100) - So(err, ShouldBeNil) - So(localDB, ShouldNotBeNil) - }) - - Convey("NewLocalDB: invalid", func() { - localDB, err := NewLocalDB(0) - So(err, ShouldNotBeNil) - So(localDB, ShouldBeNil) - }) - }) - - Convey("Store & Lookup", func() { - Convey("Store then Lookup same", func() { - localDB, err := NewLocalDB(time.Hour) - if err != nil { - panic(err) - } - - devAddr := genDevAddr() - broAddr := genBroAddr() - - err = localDB.store(devAddr, broAddr) - So(err, ShouldBeNil) - - broAddrs, err := localDB.lookup(devAddr) - So(err, ShouldBeNil) - So(broAddrs, ShouldResemble, []core.BrokerAddress{broAddr}) - - devAddr = genDevAddr() - broAddr2 := genBroAddr() - err = localDB.store(devAddr, broAddr, broAddr2) - So(err, ShouldBeNil) - - broAddrs, err = localDB.lookup(devAddr) - So(err, ShouldBeNil) - So(broAddrs, ShouldResemble, []core.BrokerAddress{broAddr, broAddr2}) - }) - - Convey("Invalid lookups", func() { - localDB, err := NewLocalDB(time.Millisecond) - if err != nil { - panic(err) - } - - devAddr := genDevAddr() - broAddr := genBroAddr() - - err = localDB.store(devAddr, broAddr) - So(err, ShouldBeNil) - - time.Sleep(10 * time.Millisecond) - - broAddrs, err := localDB.lookup(devAddr) - So(broAddrs, ShouldBeNil) - So(err, ShouldNotBeNil) - - broAddrs, err = localDB.lookup(genDevAddr()) - So(err, ShouldNotBeNil) - So(broAddrs, ShouldBeNil) - }) - - Convey("Store existing", func() { - localDB, err := NewLocalDB(time.Hour) - if err != nil { - panic(err) - } - - devAddr := genDevAddr() - broAddr := genBroAddr() - - err = localDB.store(devAddr, broAddr) - So(err, ShouldBeNil) - err = localDB.store(devAddr, broAddr) - So(err, ShouldNotBeNil) - }) - }) - - }) -} diff --git a/refactored_components/broker.go b/components/broker.go similarity index 100% rename from refactored_components/broker.go rename to components/broker.go diff --git a/refactored_components/broker_test.go b/components/broker_test.go similarity index 100% rename from refactored_components/broker_test.go rename to components/broker_test.go diff --git a/refactored_components/build_utilities_test.go b/components/build_utilities_test.go similarity index 100% rename from refactored_components/build_utilities_test.go rename to components/build_utilities_test.go diff --git a/refactored_components/check_utilities_test.go b/components/check_utilities_test.go similarity index 100% rename from refactored_components/check_utilities_test.go rename to components/check_utilities_test.go diff --git a/components/doc.go b/components/doc.go deleted file mode 100644 index 4dce199a7..000000000 --- a/components/doc.go +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// package components holds all core component of The Things Network -// -// TODO description -package components diff --git a/refactored_components/metadata.go b/components/metadata.go similarity index 100% rename from refactored_components/metadata.go rename to components/metadata.go diff --git a/refactored_components/metadata_test.go b/components/metadata_test.go similarity index 100% rename from refactored_components/metadata_test.go rename to components/metadata_test.go diff --git a/refactored_components/packet.go b/components/packet.go similarity index 100% rename from refactored_components/packet.go rename to components/packet.go diff --git a/refactored_components/packet_test.go b/components/packet_test.go similarity index 100% rename from refactored_components/packet_test.go rename to components/packet_test.go diff --git a/components/router.go b/components/router.go deleted file mode 100644 index 671ac5983..000000000 --- a/components/router.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/utils/log" - "time" -) - -const ( - EXPIRY_DELAY = time.Hour * 8 - UP_POOL_SIZE = 1 - DOWN_POOL_SIZE = 1 -) - -// Router represents a concrete router of TTN architecture. Use the New() method to create a new -// one and then connect it to its adapters. -type Router struct { - brokers []core.BrokerAddress // Brokers known by the router - Logger log.Logger // Specify a logger for the router. NOTE Having this exported isn't thread-safe. - addressKeeper addressKeeper // Local storage that maps end-device addresses to broker addresses - up chan upMsg // Internal communication channel which sends data to the up adapter - down chan downMsg // Internal communication channel which sends data to the down adapter -} - -// upMsg materializes messages that flow along the up channel -type upMsg struct { - packet semtech.Packet // The packet to transfer - gateway core.GatewayAddress // The recipient gateway to reach -} - -// downMsg materializes messages that flow along the down channel -type downMsg struct { - payload semtech.Payload // The payload to transfer - brokers []core.BrokerAddress // The recipient broker to reach. If nil or empty, assume that all broker should be reached -} - -// NewRouter constructs a Router and setup its internal structure -func NewRouter(brokers ...core.BrokerAddress) (*Router, error) { - localDB, err := NewLocalDB(EXPIRY_DELAY) - - if err != nil { - return nil, err - } - - if len(brokers) == 0 { - return nil, core.ErrBadOptions - } - - return &Router{ - brokers: brokers, - addressKeeper: localDB, - up: make(chan upMsg), - down: make(chan downMsg), - Logger: log.VoidLogger{}, - }, nil -} - -// HandleUplink implements the core.Router interface -func (r *Router) HandleUplink(packet semtech.Packet, gateway core.GatewayAddress) { - r.ensure() - - switch packet.Identifier { - case semtech.PULL_DATA: - r.log("receives PULL_DATA, sending ack") - r.up <- upMsg{ - packet: semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_ACK, - Token: packet.Token, - }, - gateway: gateway, - } - case semtech.PUSH_DATA: - // 1. Send an ack - r.log("receives PUSH_DATA, sending ack") - r.up <- upMsg{ - packet: semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_ACK, - Token: packet.Token, - }, - gateway: gateway, - } - - // 2. Determine payloads related to different end-devices present in the packet - // NOTE So far, Stats are ignored. - if packet.Payload == nil || len(packet.Payload.RXPK) == 0 { - r.log("Ignores inconsistent PUSH_DATA packet") - return - } - - payloads := make(map[semtech.DeviceAddress]*semtech.Payload) - for _, rxpk := range packet.Payload.RXPK { - devAddr := rxpk.DevAddr() - if devAddr == nil { - r.log("Unable to determine end-device address for rxpk: %+v", rxpk) - continue - } - - if _, ok := payloads[*devAddr]; !ok { - payloads[*devAddr] = &semtech.Payload{ - RXPK: make([]semtech.RXPK, 0), - } - } - - payload := payloads[*devAddr] - (*payload).RXPK = append(payloads[*devAddr].RXPK, rxpk) - } - - // 3. Broadcast or Forward payloads depending wether or not the brokers are known - for devAddr, payload := range payloads { - brokers, err := r.addressKeeper.lookup(devAddr) - if err == nil { - r.log("Forward payload to known brokers %+v", payload) - r.down <- downMsg{ - payload: *payload, - brokers: brokers, - } - continue - } - - r.log("Broadcast payload to all brokers %+v", payload) - r.down <- downMsg{payload: *payload} - } - default: - r.log("Unexpected packet receive from uplink %+v", packet) - } -} - -// HandleDownlink implements the core.Router interface -func (r *Router) HandleDownlink(payload semtech.Payload, broker core.BrokerAddress) { - // TODO MileStone 4 -} - -// RegisterDevice implements the core.Router interface -func (r *Router) RegisterDevice(devAddr semtech.DeviceAddress, broAddrs ...core.BrokerAddress) { - r.ensure() - r.log("Register device %+x to brokers: %v", devAddr, broAddrs) - r.addressKeeper.store(devAddr, broAddrs...) // TODO handle the error -} - -// RegisterDevice implements the core.Router interface -func (r *Router) HandleError(err interface{}) { - r.ensure() - - switch err.(type) { - default: - fmt.Println(err) // Wow, much handling, very reliable - } -} - -// Connect implements the core.Router interface -func (r *Router) Connect(upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { - r.ensure() - - r.log("Connect router to UpAdapter") - for i := 0; i < UP_POOL_SIZE; i += 1 { - go r.connectUpAdapter(upAdapter) - } - - r.log("Connect router to DownAdapter") - for i := 0; i < DOWN_POOL_SIZE; i += 1 { - go r.connectDownAdapter(downAdapter) - } -} - -// Consume messages sent to r.up channel -func (r *Router) connectUpAdapter(upAdapter core.GatewayRouterAdapter) { - for msg := range r.up { - upAdapter.Ack(r, msg.packet, msg.gateway) - } -} - -// Consume messages sent to r.down channel -func (r *Router) connectDownAdapter(downAdapter core.RouterBrokerAdapter) { - for msg := range r.down { - if len(msg.brokers) == 0 { - downAdapter.Broadcast(r, msg.payload) - continue - } - downAdapter.Forward(r, msg.payload, msg.brokers...) - } -} - -// ensure checks whether or not the current Router has been created via New(). It panics if not. -func (r *Router) ensure() { - if r == nil || r.addressKeeper == nil { - panic("Call method on non-initialized Router") - } -} - -// log is a shortcut to access the router logger -func (r *Router) log(format string, a ...interface{}) { - if r.Logger == nil { - return - } - r.Logger.Log(format, a...) -} diff --git a/components/router_test.go b/components/router_test.go deleted file mode 100644 index 8699ba412..000000000 --- a/components/router_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" - "github.com/thethingsnetwork/core/testing/mock_adapters/gtw_rtr_mock" - "github.com/thethingsnetwork/core/testing/mock_adapters/rtr_brk_mock" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" - "testing" - "time" -) - -// ----- A new router instance can be created an obtained from a constuctor -func TestNewRouter(t *testing.T) { - tests := []newRouterTest{ - {genBrokers(2), nil}, - {[]core.BrokerAddress{}, core.ErrBadOptions}, - } - - for _, test := range tests { - test.run(t) - } -} - -type newRouterTest struct { - in []core.BrokerAddress - want error -} - -func (test newRouterTest) run(t *testing.T) { - Desc(t, "Create new router with params: %v", test.in) - router, err := NewRouter(test.in...) - checkErrors(t, test.want, err, router) -} - -// ----- A router can handle uplink packets -func TestHandleUplink(t *testing.T) { - // Build tests - nbBrokers := 2 - brokers := genBrokers(nbBrokers) - router, upAdapter, downAdapter := genAdaptersAndRouter(t, brokers) - down := downAdapter.(*rtr_brk_mock.Adapter) - pushData := genPUSH_DATA() - down.Relations[*pushData.Payload.RXPK[0].DevAddr()] = brokers[1:] // Register the second broker as being in charge of device #1 - - tests := []handleUplinkTest{ - {genPULL_DATA(), 1, 0, 0}, - {pushData, 1, 0, 3}, // PUSH_DATA generate a packet with 4 RXPK from 3 different devices - {pushData, 1, 1, 2}, // Now device #1 should be handled by broker #2 - } - - for _, test := range tests { - test.run(t, router, upAdapter, downAdapter) - } -} - -type handleUplinkTest struct { - packet semtech.Packet - wantAck int - wantForward int - wantBroadcast int -} - -func (test handleUplinkTest) run(t *testing.T, router core.Router, upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter) { - // Describe - Desc(t, "Handle uplink packet %v", test.packet) - - // Build - mockDown := downAdapter.(*rtr_brk_mock.Adapter) - mockDown.Forwards = make(map[semtech.DeviceAddress][]semtech.Payload) - mockDown.Broadcasts = make(map[semtech.DeviceAddress][]semtech.Payload) - - // Operate - router.HandleUplink(test.packet, core.GatewayAddress("Gateway")) - <-time.After(time.Millisecond * 100) - - // Check - checkUplink(t, upAdapter, downAdapter, test.wantAck, test.wantForward, test.wantBroadcast) -} - -// ----- Build Utilities -func genBrokers(n int) []core.BrokerAddress { - var brokers []core.BrokerAddress - for i := 0; i < n; i += 1 { - brokers = append(brokers, core.BrokerAddress(fmt.Sprintf("0.0.0.0:%d", 3000+i))) - } - return brokers -} - -func genAdaptersAndRouter(t *testing.T, brokers []core.BrokerAddress) (core.Router, core.GatewayRouterAdapter, core.RouterBrokerAdapter) { - router, err := NewRouter(brokers...) - if err != nil { - panic(err) - } - - router.Logger = log.TestLogger{Tag: "Router", T: t} - upAdapter := gtw_rtr_mock.New() - downAdapter := rtr_brk_mock.New() - - upAdapter.Listen(router, nil) - downAdapter.Listen(router, brokers) - router.Connect(upAdapter, downAdapter) - - return router, upAdapter, downAdapter -} - -func genPULL_DATA() semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_DATA, - Token: []byte{0x14, 0xba}, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - } -} - -// genPUSH_DATA generate a a PUSH_DATA packet with 4 RXPK in payload coming from 3 different -// devices. -func genPUSH_DATA() semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_DATA, - Token: []byte{0x14, 0xba}, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqqq7uw")}, // Device #1 - semtech.RXPK{Data: pointer.String("/7zN3u8BAAABqqv3RA")}, // Device #2 - semtech.RXPK{Data: pointer.String("/xRC/zcBAAABqmcSRQ")}, // Device #1 - semtech.RXPK{Data: pointer.String("/wASo3YBAAAB+qpFeQ")}, // Device #3 - }, - }, - } -} - -// ----- Check Utilities -func checkErrors(t *testing.T, want error, got error, router core.Router) { - if want != got { - Ko(t, "Expected error {%v} but got {%v}", want, got) - return - } - - if want == nil && router == nil { - Ko(t, "Expected no error but got a nil router") - return - } - - Ok(t) -} - -func checkUplink(t *testing.T, upAdapter core.GatewayRouterAdapter, downAdapter core.RouterBrokerAdapter, wantAck int, wantForward int, wantBroadcast int) { - mockUp := upAdapter.(*gtw_rtr_mock.Adapter) - mockDown := downAdapter.(*rtr_brk_mock.Adapter) - - if len(mockDown.Broadcasts) != wantBroadcast { - Ko(t, "Expected %d broadcast(s) but %d has/have been done", wantBroadcast, len(mockDown.Broadcasts)) - return - } - - if len(mockDown.Forwards) != wantForward { - Ko(t, "Expected %d forward(s) but %d has/have been done", wantForward, len(mockDown.Forwards)) - } - - if len(mockUp.Acks) != wantAck { - Ko(t, "Expected %d ack(s) but got %d", wantAck, len(mockUp.Acks)) - return - } - - Ok(t) -} From d915614f1a4bbf97db60947e8733e0ae7539d904 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 22:58:01 +0100 Subject: [PATCH 0335/2266] [broker] Re-introduce and update address_keeper --- components/address_keeper.go | 79 +++++++++++++++++++++++++ components/address_keeper_test.go | 97 +++++++++++++++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 components/address_keeper.go create mode 100644 components/address_keeper_test.go diff --git a/components/address_keeper.go b/components/address_keeper.go new file mode 100644 index 000000000..c29e23c21 --- /dev/null +++ b/components/address_keeper.go @@ -0,0 +1,79 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "sync" + "time" +) + +type addressKeeper interface { + lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) + store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error +} + +type reddisAddressKeeper struct{} // In a second time + +type localDB struct { + sync.RWMutex + expiryDelay time.Duration + addresses map[lorawan.DevAddr]localEntry +} + +type localEntry struct { + recipients []core.Recipient + until time.Time +} + +// NewLocalDB constructs a new local address keeper +func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { + if expiryDelay == 0 { + return nil, fmt.Errorf("Invalid expiration delay") + } + + return &localDB{ + expiryDelay: expiryDelay, + addresses: make(map[lorawan.DevAddr]localEntry), + }, nil +} + +// lookup implements the addressKeeper interface +func (a *localDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { + a.RLock() + entry, ok := a.addresses[devAddr] + a.RUnlock() + if !ok { + return nil, fmt.Errorf("Device address not found") + } + + if entry.until.Before(time.Now()) { + a.Lock() + delete(a.addresses, devAddr) + a.Unlock() + return nil, fmt.Errorf("Broker address(es) expired") + } + + return entry.recipients, nil +} + +// store implements the addressKeeper interface +func (a *localDB) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error { + a.Lock() + _, ok := a.addresses[devAddr] + if ok { + a.Unlock() + return fmt.Errorf("An entry already exists for that device") + } + + a.addresses[devAddr] = localEntry{ + recipients: recipients, + until: time.Now().Add(a.expiryDelay), + } + + a.Unlock() + return nil +} diff --git a/components/address_keeper_test.go b/components/address_keeper_test.go new file mode 100644 index 000000000..69ffa5bcb --- /dev/null +++ b/components/address_keeper_test.go @@ -0,0 +1,97 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + . "github.com/smartystreets/goconvey/convey" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" + "testing" + "time" +) + +func TestAddressKeeper(t *testing.T) { + Convey("Local Address Keeper", t, func() { + Convey("NewLocalDB", func() { + Convey("NewLocalDB: valid", func() { + localDB, err := NewLocalDB(time.Hour * 100) + So(err, ShouldBeNil) + So(localDB, ShouldNotBeNil) + }) + + Convey("NewLocalDB: invalid", func() { + localDB, err := NewLocalDB(0) + So(err, ShouldNotBeNil) + So(localDB, ShouldBeNil) + }) + }) + + Convey("Store & Lookup", func() { + Convey("Store then Lookup same", func() { + localDB, err := NewLocalDB(time.Hour) + if err != nil { + panic(err) + } + + devAddr := lorawan.DevAddr([4]byte{1, 2, 3, 4}) + recipient := core.Recipient{Address: "MyAddress", Id: "MyId"} + + err = localDB.store(devAddr, recipient) + So(err, ShouldBeNil) + + recipients, err := localDB.lookup(devAddr) + So(err, ShouldBeNil) + So(recipients, ShouldResemble, []core.Recipient{recipient}) + + devAddr = lorawan.DevAddr([4]byte{3, 4, 5, 2}) + recipient2 := core.Recipient{Address: "MyAddress2", Id: "MyId2"} + err = localDB.store(devAddr, recipient, recipient2) + So(err, ShouldBeNil) + + recipients, err = localDB.lookup(devAddr) + So(err, ShouldBeNil) + So(recipients, ShouldResemble, []core.Recipient{recipient, recipient2}) + }) + + Convey("Invalid lookups", func() { + localDB, err := NewLocalDB(time.Millisecond) + if err != nil { + panic(err) + } + + devAddr := lorawan.DevAddr([4]byte{1, 2, 3, 4}) + recipient := core.Recipient{Address: "MyAddress", Id: "MyId"} + + err = localDB.store(devAddr, recipient) + So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + recipients, err := localDB.lookup(devAddr) + So(recipients, ShouldBeNil) + So(err, ShouldNotBeNil) + + recipients, err = localDB.lookup([4]byte{4, 5, 6, 7}) + So(err, ShouldNotBeNil) + So(recipients, ShouldBeNil) + }) + + Convey("Store existing", func() { + localDB, err := NewLocalDB(time.Hour) + if err != nil { + panic(err) + } + + devAddr := lorawan.DevAddr([4]byte{1, 2, 3, 4}) + recipient := core.Recipient{Address: "MyAddress", Id: "MyId"} + + err = localDB.store(devAddr, recipient) + So(err, ShouldBeNil) + err = localDB.store(devAddr, recipient) + So(err, ShouldNotBeNil) + }) + }) + + }) +} From 8d276212b3e0be546e5b501863bc9d64c0d809d4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 23:04:17 +0100 Subject: [PATCH 0336/2266] [broker] Re-introduce router. So far, only constructor --- components/router.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 components/router.go diff --git a/components/router.go b/components/router.go new file mode 100644 index 000000000..d2080e3cc --- /dev/null +++ b/components/router.go @@ -0,0 +1,42 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "fmt" + "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/utils/log" + "time" +) + +const ( + EXPIRY_DELAY = time.Hour * 8 +) + +type Router struct { + loggers []log.Logger + brokers []core.Recipient + db addressKeeper // Local storage that maps end-device addresses to broker addresses +} + +var ErrBadOptions = fmt.Errorf("Invalid supplied options") + +// NewRouter constructs a Router and setup its internal structure +func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) { + localDB, err := NewLocalDB(EXPIRY_DELAY) + + if err != nil { + return nil, err + } + + if len(brokers) == 0 { + return nil, ErrBadOptions + } + + return &Router{ + loggers: loggers, + brokers: brokers, + db: localDB, + }, nil +} From 325e3aba2837c170043daa2a3f0fa2548f8d06d2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 23:06:10 +0100 Subject: [PATCH 0337/2266] [broker] Add blank failing router tests as reminder --- components/router_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 components/router_test.go diff --git a/components/router_test.go b/components/router_test.go new file mode 100644 index 000000000..36f8cbdf4 --- /dev/null +++ b/components/router_test.go @@ -0,0 +1,21 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + . "github.com/thethingsnetwork/core/utils/testing" + "testing" +) + +func TestNewRouter(t *testing.T) { + Ko(t, "TODO") +} + +func TestSendRouter(t *testing.T) { + Ko(t, "TODO") +} + +func TestHandleUpRouter(t *testing.T) { + Ko(t, "TODO") +} From 6829654613a3599c55f9ce02047f3fa5c6bf4169 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 23:15:29 +0100 Subject: [PATCH 0338/2266] [broker] Re-implement router.Register() --- components/router.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/components/router.go b/components/router.go index d2080e3cc..23e08b505 100644 --- a/components/router.go +++ b/components/router.go @@ -21,6 +21,7 @@ type Router struct { } var ErrBadOptions = fmt.Errorf("Invalid supplied options") +var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") // NewRouter constructs a Router and setup its internal structure func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) { @@ -40,3 +41,23 @@ func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) db: localDB, }, nil } + +// Register implements the core.Component interface +func (r *Router) Register(reg core.Registration) error { + if !r.ok() { + return ErrNotInitialized + } + return r.db.store(reg.DevAddr, reg.Recipient) +} + +// ok ensure the router has been initialized by NewRouter() +func (r *Router) ok() bool { + return r == nil && r.db != nil +} + +// log broadcast the log message to all registered logger +func (r *Router) log(format string, i ...interface{}) { + for _, logger := range r.loggers { + logger.Log(format, i...) + } +} From 11896e326e59de297790c1e4c489e1540676b94c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 23:16:34 +0100 Subject: [PATCH 0339/2266] [broker] Add dump-implementation of Router.HandleDown() --- components/router.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/components/router.go b/components/router.go index 23e08b505..30c386793 100644 --- a/components/router.go +++ b/components/router.go @@ -50,6 +50,11 @@ func (r *Router) Register(reg core.Registration) error { return r.db.store(reg.DevAddr, reg.Recipient) } +// HandleDown implements the core.Component interface +func (r *Router) HandleDown(p core.Packet, an core.AckNacker) error { + return fmt.Errorf("TODO. Not Implemented") +} + // ok ensure the router has been initialized by NewRouter() func (r *Router) ok() bool { return r == nil && r.db != nil From 14e1ff60071f51c4463118a9e64fbf89b5af9829 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 10 Jan 2016 23:31:04 +0100 Subject: [PATCH 0340/2266] [broker] Implements basic version of router.HandleUp for Uplink only --- components/address_keeper.go | 7 +++++-- components/router.go | 24 +++++++++++++++++++++++- core.go | 4 ++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/components/address_keeper.go b/components/address_keeper.go index c29e23c21..0c1dc9e39 100644 --- a/components/address_keeper.go +++ b/components/address_keeper.go @@ -29,6 +29,9 @@ type localEntry struct { until time.Time } +var ErrDeviceNotFound = fmt.Errorf("Device not found") +var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") + // NewLocalDB constructs a new local address keeper func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { if expiryDelay == 0 { @@ -47,14 +50,14 @@ func (a *localDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { entry, ok := a.addresses[devAddr] a.RUnlock() if !ok { - return nil, fmt.Errorf("Device address not found") + return nil, ErrDeviceNotFound } if entry.until.Before(time.Now()) { a.Lock() delete(a.addresses, devAddr) a.Unlock() - return nil, fmt.Errorf("Broker address(es) expired") + return nil, ErrEntryExpired } return entry.recipients, nil diff --git a/components/router.go b/components/router.go index 30c386793..d571416c5 100644 --- a/components/router.go +++ b/components/router.go @@ -51,10 +51,32 @@ func (r *Router) Register(reg core.Registration) error { } // HandleDown implements the core.Component interface -func (r *Router) HandleDown(p core.Packet, an core.AckNacker) error { +func (r *Router) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) error { return fmt.Errorf("TODO. Not Implemented") } +// HandleUp implements the core.Component interface +func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { + if !r.ok() { + return ErrNotInitialized + } + + // Lookup for an existing broker + devAddr, err := p.DevAddr() + if err != nil { + return err + } + + brokers, err := r.db.lookup(devAddr) + if err != ErrDeviceNotFound && err != ErrEntryExpired { + return err + } + + // We don't handle downlink for now, so an is quite useless. + + return upAdapter.Send(p, brokers...) +} + // ok ensure the router has been initialized by NewRouter() func (r *Router) ok() bool { return r == nil && r.db != nil diff --git a/core.go b/core.go index f3baf2c08..dc3689cac 100644 --- a/core.go +++ b/core.go @@ -44,8 +44,8 @@ type AckNacker interface { type Component interface { Register(reg Registration) error - HandleUp(p Packet, an AckNacker) error - HandleDown(p Packet, an AckNacker) error + HandleUp(p Packet, an AckNacker, upAdapter Adapter) error + HandleDown(p Packet, an AckNacker, downAdapter Adapter) error } type Adapter interface { From 71d96ce7dc2218f93d4de03f98518bdc9a579898 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 16:23:33 +0100 Subject: [PATCH 0341/2266] [broker] Prepare broker backbone --- components/broker.go | 67 +++++---------------------------------- components/broker_test.go | 29 ++++------------- 2 files changed, 14 insertions(+), 82 deletions(-) diff --git a/components/broker.go b/components/broker.go index b5e9787f6..931698649 100644 --- a/components/broker.go +++ b/components/broker.go @@ -4,74 +4,23 @@ package components import ( + "fmt" "github.com/thethingsnetwork/core" ) type Broker struct{} -/* Scenario -broker := components.NewBroker() -upAdapter := adapters.brk_hdl_http -downAdapter := adapters.brk_rtr_http - -downAdapter.Start("3000") -upAdapter.Start("8080") - -// Handle registration coming from Handler -go func() { - for { - handler, devAddr, nwskey, err := upAdapter.NextRegistration() - if err != nil { - // Do some error handling - } - err = broker.Register(handler, devAddr, nwskey) - if err != nil { - // Do some error handling - } - } -}() - -// Handle response packet coming from Handler -go func() { - for { - packet, an, error := upAdapter.Next() - if err != nil { - // Do some error handling - } - err = broker.Handle(packet, an) - if err != nil { - // Do some error handling - } - } -} - -// Handle packet from router -go func() { - for { - packet, an, error := downAdapter.Next() - if err != nil { - // Do some error handling - } - err = broker.Handle(packet, an) - if err != nil { - // Do some error handling - } - } -} -*/ - -func NewBroker() { - -} - -func (b *Broker) NextUp() (*core.Packet, error) { +func NewBroker() (*Broker, error) { return nil, nil } +func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, a core.Adapter) error { + return nil +} -func (b *Broker) NextDown() (*core.Packet, error) { - return nil, nil +func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) error { + return fmt.Errorf("Not Implemented") } -func (b *Broker) Handle(p core.Packet, an core.AckNacker) error { +func (b *Broker) Register(r core.Registration) error { return nil } diff --git a/components/broker_test.go b/components/broker_test.go index 539df4914..bb8aa6b40 100644 --- a/components/broker_test.go +++ b/components/broker_test.go @@ -4,35 +4,18 @@ package components import ( - "github.com/thethingsnetwork/core" . "github.com/thethingsnetwork/core/utils/testing" "testing" ) -type FakeAckNacker struct { - ackGot core.Packet - nackGot core.Packet +func TestNewBroker(t *testing.T) { + Ko(t, "TODO") } -func (f *FakeAckNacker) Ack(p core.Packet) error { - f.ackGot = p - return nil +func TestHandleUpBroker(t *testing.T) { + Ko(t, "TODO") } -func (f *FakeAckNacker) Nack(p core.Packet) error { - f.nackGot = p - return nil -} - -// The broker can handle an uplink packet -func TestBrokerUplink(t *testing.T) { - // p = validFullMetaPacket() - // p = validPartialMetaPacket() - // p = packetWithoutPayload - // fake AckNacker -> - // broker.HandleUplink(p, an) - // - // checkAckNacker(p.Packet, An.got) - - Ok(t, "Pending") +func TestRegisterBroker(t *testing.T) { + Ko(t, "TODO") } From b44e6d80c8b474d65cb1ac5ffe795a8f5004ac19 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 16:25:09 +0100 Subject: [PATCH 0342/2266] [broker] Rename address_keeper file --- components/{address_keeper.go => local_storage.go} | 2 +- components/{address_keeper_test.go => local_storage_test.go} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename components/{address_keeper.go => local_storage.go} (96%) rename components/{address_keeper_test.go => local_storage_test.go} (100%) diff --git a/components/address_keeper.go b/components/local_storage.go similarity index 96% rename from components/address_keeper.go rename to components/local_storage.go index 0c1dc9e39..efdce9b87 100644 --- a/components/address_keeper.go +++ b/components/local_storage.go @@ -16,7 +16,7 @@ type addressKeeper interface { store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error } -type reddisAddressKeeper struct{} // In a second time +type reddisAddressKeeper struct{} // NOTE In a second time type localDB struct { sync.RWMutex diff --git a/components/address_keeper_test.go b/components/local_storage_test.go similarity index 100% rename from components/address_keeper_test.go rename to components/local_storage_test.go From afd6893c0240e33487292e5e0ce41fb5cea433b5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 16:28:38 +0100 Subject: [PATCH 0343/2266] [broker] Allow null expiry delay for local storage --- components/local_storage.go | 6 +----- components/local_storage_test.go | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/components/local_storage.go b/components/local_storage.go index efdce9b87..d57f97314 100644 --- a/components/local_storage.go +++ b/components/local_storage.go @@ -34,10 +34,6 @@ var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") // NewLocalDB constructs a new local address keeper func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { - if expiryDelay == 0 { - return nil, fmt.Errorf("Invalid expiration delay") - } - return &localDB{ expiryDelay: expiryDelay, addresses: make(map[lorawan.DevAddr]localEntry), @@ -53,7 +49,7 @@ func (a *localDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { return nil, ErrDeviceNotFound } - if entry.until.Before(time.Now()) { + if a.expiryDelay != 0 && entry.until.Before(time.Now()) { a.Lock() delete(a.addresses, devAddr) a.Unlock() diff --git a/components/local_storage_test.go b/components/local_storage_test.go index 69ffa5bcb..e9e4db3ad 100644 --- a/components/local_storage_test.go +++ b/components/local_storage_test.go @@ -22,7 +22,7 @@ func TestAddressKeeper(t *testing.T) { Convey("NewLocalDB: invalid", func() { localDB, err := NewLocalDB(0) - So(err, ShouldNotBeNil) + So(err, ShouldBeNil) So(localDB, ShouldBeNil) }) }) From ee732225672f035f3fb70207207937358812f144 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 16:31:59 +0100 Subject: [PATCH 0344/2266] [broker] Add New and log method to broker --- components/broker.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/components/broker.go b/components/broker.go index 931698649..e0d1b41c0 100644 --- a/components/broker.go +++ b/components/broker.go @@ -6,13 +6,27 @@ package components import ( "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/utils/log" ) -type Broker struct{} +type Broker struct { + loggers []log.Logger + db addressKeeper +} + +func NewBroker(loggers ...log.Logger) (*Broker, error) { + localDB, err := NewLocalDB(EXPIRY_DELAY) + + if err != nil { + return nil, err + } -func NewBroker() (*Broker, error) { - return nil, nil + return &Broker{ + loggers: loggers, + db: localDB, + }, nil } + func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, a core.Adapter) error { return nil } @@ -24,3 +38,9 @@ func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) er func (b *Broker) Register(r core.Registration) error { return nil } + +func (b *Broker) log(format string, i ...interface{}) { + for _, logger := range b.loggers { + logger.Log(format, i...) + } +} From 6f581132d335106feb10c05a6d717a74fc1c31f0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 16:32:48 +0100 Subject: [PATCH 0345/2266] [broker] Rename http/registrations -> http/pubsub --- adapters/http/{registrations => pubsub}/handler_parser.go | 2 +- .../http/{registrations/registrations.go => pubsub/pubsub.go} | 2 +- .../registrations_test.go => pubsub/pubsub_test.go} | 2 +- adapters/http/{registrations => pubsub}/reg_acknacker.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename adapters/http/{registrations => pubsub}/handler_parser.go (98%) rename adapters/http/{registrations/registrations.go => pubsub/pubsub.go} (99%) rename adapters/http/{registrations/registrations_test.go => pubsub/pubsub_test.go} (99%) rename adapters/http/{registrations => pubsub}/reg_acknacker.go (97%) diff --git a/adapters/http/registrations/handler_parser.go b/adapters/http/pubsub/handler_parser.go similarity index 98% rename from adapters/http/registrations/handler_parser.go rename to adapters/http/pubsub/handler_parser.go index 17537b78c..e18916e1b 100644 --- a/adapters/http/registrations/handler_parser.go +++ b/adapters/http/pubsub/handler_parser.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package registrations +package pubsub import ( "encoding/hex" diff --git a/adapters/http/registrations/registrations.go b/adapters/http/pubsub/pubsub.go similarity index 99% rename from adapters/http/registrations/registrations.go rename to adapters/http/pubsub/pubsub.go index ffb6ffce5..d65be1def 100644 --- a/adapters/http/registrations/registrations.go +++ b/adapters/http/pubsub/pubsub.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package registrations +package pubsub import ( "fmt" diff --git a/adapters/http/registrations/registrations_test.go b/adapters/http/pubsub/pubsub_test.go similarity index 99% rename from adapters/http/registrations/registrations_test.go rename to adapters/http/pubsub/pubsub_test.go index c8c32eead..06f455738 100644 --- a/adapters/http/registrations/registrations_test.go +++ b/adapters/http/pubsub/pubsub_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package registrations +package pubsub import ( "bytes" diff --git a/adapters/http/registrations/reg_acknacker.go b/adapters/http/pubsub/reg_acknacker.go similarity index 97% rename from adapters/http/registrations/reg_acknacker.go rename to adapters/http/pubsub/reg_acknacker.go index 003cbd272..2fbd63f12 100644 --- a/adapters/http/registrations/reg_acknacker.go +++ b/adapters/http/pubsub/reg_acknacker.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package registrations +package pubsub import ( "fmt" From 5b4cb0ec5caaed01ac67d7769f841929ed5f75b5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 17:02:26 +0100 Subject: [PATCH 0346/2266] [broker] Refactor temporary local storage for router + define on for broker --- components/broker_storage.go | 57 +++++++++++ components/local_storage_test.go | 97 ------------------- components/router.go | 4 +- .../{local_storage.go => router_storage.go} | 50 +++++----- 4 files changed, 83 insertions(+), 125 deletions(-) create mode 100644 components/broker_storage.go delete mode 100644 components/local_storage_test.go rename components/{local_storage.go => router_storage.go} (50%) diff --git a/components/broker_storage.go b/components/broker_storage.go new file mode 100644 index 000000000..0125b67bc --- /dev/null +++ b/components/broker_storage.go @@ -0,0 +1,57 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "fmt" + "github.com/thethingsnetwork/core/lorawan" + "sync" +) + +type brokerStorage interface { + lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) + store(devAddr lorawan.DevAddr, entry brokerEntry) error +} + +type brokerEntry struct { + Id string + Url string + Nwskey lorawan.AES128Key +} + +type brokerDB struct { + sync.RWMutex + entries map[lorawan.DevAddr][]brokerEntry +} + +var ErrDeviceNotFound = fmt.Errorf("Device not found") + +// NewLocalDB constructs a new local brokerStorage +func NewBrokerStorage() (brokerStorage, error) { + return &brokerDB{entries: make(map[lorawan.DevAddr][]brokerEntry)}, nil +} + +// lookup implements the brokerStorage interface +func (db *brokerDB) lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { + db.RLock() + entries, ok := db.entries[devAddr] + db.RUnlock() + if !ok { + return nil, ErrDeviceNotFound + } + return entries, nil +} + +// store implements the brokerStorage interface +func (db *brokerDB) store(devAddr lorawan.DevAddr, entry brokerEntry) error { + db.Lock() + defer db.Unlock() + entries, ok := db.entries[devAddr] + if !ok { + db.entries[devAddr] = []brokerEntry{entry} + return nil + } + db.entries[devAddr] = append(entries, entry) + return nil +} diff --git a/components/local_storage_test.go b/components/local_storage_test.go deleted file mode 100644 index e9e4db3ad..000000000 --- a/components/local_storage_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan" - "testing" - "time" -) - -func TestAddressKeeper(t *testing.T) { - Convey("Local Address Keeper", t, func() { - Convey("NewLocalDB", func() { - Convey("NewLocalDB: valid", func() { - localDB, err := NewLocalDB(time.Hour * 100) - So(err, ShouldBeNil) - So(localDB, ShouldNotBeNil) - }) - - Convey("NewLocalDB: invalid", func() { - localDB, err := NewLocalDB(0) - So(err, ShouldBeNil) - So(localDB, ShouldBeNil) - }) - }) - - Convey("Store & Lookup", func() { - Convey("Store then Lookup same", func() { - localDB, err := NewLocalDB(time.Hour) - if err != nil { - panic(err) - } - - devAddr := lorawan.DevAddr([4]byte{1, 2, 3, 4}) - recipient := core.Recipient{Address: "MyAddress", Id: "MyId"} - - err = localDB.store(devAddr, recipient) - So(err, ShouldBeNil) - - recipients, err := localDB.lookup(devAddr) - So(err, ShouldBeNil) - So(recipients, ShouldResemble, []core.Recipient{recipient}) - - devAddr = lorawan.DevAddr([4]byte{3, 4, 5, 2}) - recipient2 := core.Recipient{Address: "MyAddress2", Id: "MyId2"} - err = localDB.store(devAddr, recipient, recipient2) - So(err, ShouldBeNil) - - recipients, err = localDB.lookup(devAddr) - So(err, ShouldBeNil) - So(recipients, ShouldResemble, []core.Recipient{recipient, recipient2}) - }) - - Convey("Invalid lookups", func() { - localDB, err := NewLocalDB(time.Millisecond) - if err != nil { - panic(err) - } - - devAddr := lorawan.DevAddr([4]byte{1, 2, 3, 4}) - recipient := core.Recipient{Address: "MyAddress", Id: "MyId"} - - err = localDB.store(devAddr, recipient) - So(err, ShouldBeNil) - - time.Sleep(10 * time.Millisecond) - - recipients, err := localDB.lookup(devAddr) - So(recipients, ShouldBeNil) - So(err, ShouldNotBeNil) - - recipients, err = localDB.lookup([4]byte{4, 5, 6, 7}) - So(err, ShouldNotBeNil) - So(recipients, ShouldBeNil) - }) - - Convey("Store existing", func() { - localDB, err := NewLocalDB(time.Hour) - if err != nil { - panic(err) - } - - devAddr := lorawan.DevAddr([4]byte{1, 2, 3, 4}) - recipient := core.Recipient{Address: "MyAddress", Id: "MyId"} - - err = localDB.store(devAddr, recipient) - So(err, ShouldBeNil) - err = localDB.store(devAddr, recipient) - So(err, ShouldNotBeNil) - }) - }) - - }) -} diff --git a/components/router.go b/components/router.go index d571416c5..60a4a089d 100644 --- a/components/router.go +++ b/components/router.go @@ -17,7 +17,7 @@ const ( type Router struct { loggers []log.Logger brokers []core.Recipient - db addressKeeper // Local storage that maps end-device addresses to broker addresses + db routerStorage // Local storage that maps end-device addresses to broker addresses } var ErrBadOptions = fmt.Errorf("Invalid supplied options") @@ -25,7 +25,7 @@ var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized co // NewRouter constructs a Router and setup its internal structure func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) { - localDB, err := NewLocalDB(EXPIRY_DELAY) + localDB, err := NewRouterStorage(EXPIRY_DELAY) if err != nil { return nil, err diff --git a/components/local_storage.go b/components/router_storage.go similarity index 50% rename from components/local_storage.go rename to components/router_storage.go index d57f97314..9cbcf2a1f 100644 --- a/components/local_storage.go +++ b/components/router_storage.go @@ -11,48 +11,46 @@ import ( "time" ) -type addressKeeper interface { +type routerStorage interface { lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error } -type reddisAddressKeeper struct{} // NOTE In a second time - -type localDB struct { +type routerDB struct { sync.RWMutex expiryDelay time.Duration - addresses map[lorawan.DevAddr]localEntry + entries map[lorawan.DevAddr]routerEntry } -type localEntry struct { +type routerEntry struct { recipients []core.Recipient until time.Time } -var ErrDeviceNotFound = fmt.Errorf("Device not found") var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") +var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") // NewLocalDB constructs a new local address keeper -func NewLocalDB(expiryDelay time.Duration) (*localDB, error) { - return &localDB{ +func NewRouterStorage(expiryDelay time.Duration) (routerStorage, error) { + return &routerDB{ expiryDelay: expiryDelay, - addresses: make(map[lorawan.DevAddr]localEntry), + entries: make(map[lorawan.DevAddr]routerEntry), }, nil } // lookup implements the addressKeeper interface -func (a *localDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { - a.RLock() - entry, ok := a.addresses[devAddr] - a.RUnlock() +func (db *routerDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { + db.RLock() + entry, ok := db.entries[devAddr] + db.RUnlock() if !ok { return nil, ErrDeviceNotFound } - if a.expiryDelay != 0 && entry.until.Before(time.Now()) { - a.Lock() - delete(a.addresses, devAddr) - a.Unlock() + if db.expiryDelay != 0 && entry.until.Before(time.Now()) { + db.Lock() + delete(db.entries, devAddr) + db.Unlock() return nil, ErrEntryExpired } @@ -60,19 +58,19 @@ func (a *localDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { } // store implements the addressKeeper interface -func (a *localDB) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error { - a.Lock() - _, ok := a.addresses[devAddr] +func (db *routerDB) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error { + db.Lock() + _, ok := db.entries[devAddr] if ok { - a.Unlock() - return fmt.Errorf("An entry already exists for that device") + db.Unlock() + return ErrAlreadyExists } - a.addresses[devAddr] = localEntry{ + db.entries[devAddr] = routerEntry{ recipients: recipients, - until: time.Now().Add(a.expiryDelay), + until: time.Now().Add(db.expiryDelay), } - a.Unlock() + db.Unlock() return nil } From 09553b376cdfd6c460651931a7e6d0fd76d0652f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 17:07:59 +0100 Subject: [PATCH 0347/2266] [broker] Add register method to broker --- components/broker.go | 18 +++++++++++++++--- components/broker_storage.go | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/components/broker.go b/components/broker.go index e0d1b41c0..dc14c6107 100644 --- a/components/broker.go +++ b/components/broker.go @@ -6,16 +6,19 @@ package components import ( "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/utils/log" ) type Broker struct { loggers []log.Logger - db addressKeeper + db brokerStorage } +var ErrInvalidRegistration = fmt.Errorf("Invalid registration") + func NewBroker(loggers ...log.Logger) (*Broker, error) { - localDB, err := NewLocalDB(EXPIRY_DELAY) + localDB, err := NewBrokerStorage() if err != nil { return nil, err @@ -36,7 +39,16 @@ func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) er } func (b *Broker) Register(r core.Registration) error { - return nil + id, okId := r.Recipient.Id.(string) + url, okUrl := r.Recipient.Address.(string) + nwsKey, okNwsKey := r.Options.(lorawan.AES128Key) + + if !(okId && okUrl && okNwsKey) { + return ErrInvalidRegistration + } + + entry := brokerEntry{Id: id, Url: url, NwsKey: nwsKey} + return b.db.store(r.DevAddr, entry) } func (b *Broker) log(format string, i ...interface{}) { diff --git a/components/broker_storage.go b/components/broker_storage.go index 0125b67bc..2267ac00b 100644 --- a/components/broker_storage.go +++ b/components/broker_storage.go @@ -17,7 +17,7 @@ type brokerStorage interface { type brokerEntry struct { Id string Url string - Nwskey lorawan.AES128Key + NwsKey lorawan.AES128Key } type brokerDB struct { From d4b5a4ea9fe997392b016e8e43e94b25c5529ce8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 17:34:44 +0100 Subject: [PATCH 0348/2266] [broker] Gather errors in one place --- components/broker_storage.go | 3 --- components/errors.go | 17 +++++++++++++++++ components/packet.go | 4 ---- components/router.go | 3 --- components/router_storage.go | 4 ---- 5 files changed, 17 insertions(+), 14 deletions(-) create mode 100644 components/errors.go diff --git a/components/broker_storage.go b/components/broker_storage.go index 2267ac00b..8da4ebd1e 100644 --- a/components/broker_storage.go +++ b/components/broker_storage.go @@ -4,7 +4,6 @@ package components import ( - "fmt" "github.com/thethingsnetwork/core/lorawan" "sync" ) @@ -25,8 +24,6 @@ type brokerDB struct { entries map[lorawan.DevAddr][]brokerEntry } -var ErrDeviceNotFound = fmt.Errorf("Device not found") - // NewLocalDB constructs a new local brokerStorage func NewBrokerStorage() (brokerStorage, error) { return &brokerDB{entries: make(map[lorawan.DevAddr][]brokerEntry)}, nil diff --git a/components/errors.go b/components/errors.go new file mode 100644 index 000000000..99b1acadd --- /dev/null +++ b/components/errors.go @@ -0,0 +1,17 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import "fmt" + +var ErrInvalidRegistration = fmt.Errorf("Invalid registration") +var ErrDeviceNotFound = fmt.Errorf("Device not found") +var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") +var ErrBadOptions = fmt.Errorf("Invalid supplied options") +var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") +var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") +var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") + +// returned by ConvertRXPK or ConvertTXPK if there's no data in the given packet +var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") diff --git a/components/packet.go b/components/packet.go index 39ab34ac5..df018545e 100644 --- a/components/packet.go +++ b/components/packet.go @@ -5,7 +5,6 @@ package components import ( "encoding/base64" - "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" @@ -14,9 +13,6 @@ import ( "strings" ) -// return by ConvertRXPK or ConvertTXPK if there's no data in the given packet -var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") - // ConvertRXPK create a core.Packet from a semtech.RXPK. It's an handy way to both decode the // frame payload and retrieve associated metadata from that packet func ConvertRXPK(p semtech.RXPK) (core.Packet, error) { diff --git a/components/router.go b/components/router.go index 60a4a089d..e97adc445 100644 --- a/components/router.go +++ b/components/router.go @@ -20,9 +20,6 @@ type Router struct { db routerStorage // Local storage that maps end-device addresses to broker addresses } -var ErrBadOptions = fmt.Errorf("Invalid supplied options") -var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") - // NewRouter constructs a Router and setup its internal structure func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) { localDB, err := NewRouterStorage(EXPIRY_DELAY) diff --git a/components/router_storage.go b/components/router_storage.go index 9cbcf2a1f..59d321441 100644 --- a/components/router_storage.go +++ b/components/router_storage.go @@ -4,7 +4,6 @@ package components import ( - "fmt" "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" "sync" @@ -27,9 +26,6 @@ type routerEntry struct { until time.Time } -var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") -var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") - // NewLocalDB constructs a new local address keeper func NewRouterStorage(expiryDelay time.Duration) (routerStorage, error) { return &routerDB{ From a77570f9aaefa1d440eeea882f9d5cd0142ef852 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 17:35:01 +0100 Subject: [PATCH 0349/2266] [broker] Implement HandleUp method from broker --- components/broker.go | 47 ++++++++++++++++++++++++++++++++++++++++---- core.go | 2 +- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/components/broker.go b/components/broker.go index dc14c6107..826c7e92a 100644 --- a/components/broker.go +++ b/components/broker.go @@ -15,8 +15,6 @@ type Broker struct { db brokerStorage } -var ErrInvalidRegistration = fmt.Errorf("Invalid registration") - func NewBroker(loggers ...log.Logger) (*Broker, error) { localDB, err := NewBrokerStorage() @@ -30,8 +28,49 @@ func NewBroker(loggers ...log.Logger) (*Broker, error) { }, nil } -func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, a core.Adapter) error { - return nil +func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { + // 1. Lookup for entries for the associated device + devAddr, err := p.DevAddr() + if err != nil { + return ErrInvalidPacket + } + entries, err := b.db.lookup(devAddr) + switch err { + case nil: + case ErrDeviceNotFound: + return an.Nack(p) + default: + return err + } + + // 2. Several handler might be associated to the same device, we distinguish them using MIC + // check. Only one should verify the MIC check. + var handler *core.Recipient + for _, entry := range entries { + ok, err := p.Payload.ValidateMIC(entry.NwsKey) + if err != nil { + b.log("Unexpected error: %v", err) + continue + } + if ok { + handler = &core.Recipient{ + Id: entry.Id, + Address: entry.Url, + } + break + } + } + if handler == nil { + return an.Nack(p) + } + + // 3. If one was found, we forward the packet and wait for the response + response, err := adapter.Send(p, *handler) + if err != nil { + an.Nack(p) + return err + } + return an.Ack(response) } func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) error { diff --git a/core.go b/core.go index dc3689cac..6d8526156 100644 --- a/core.go +++ b/core.go @@ -49,7 +49,7 @@ type Component interface { } type Adapter interface { - Send(p Packet, r ...Recipient) error + Send(p Packet, r ...Recipient) (Packet, error) Next() (Packet, AckNacker, error) NextRegistration() (Registration, AckNacker, error) } From ee5efc4d1567fa900ab662159f8482308879f3c0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:14:39 +0100 Subject: [PATCH 0350/2266] [broker] Add MultiLogger to utils/loggers --- utils/log/log.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/utils/log/log.go b/utils/log/log.go index 734f13ef6..ccd135f7e 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -38,8 +38,14 @@ func (l TestLogger) Log(format string, a ...interface{}) { l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, fmt.Sprintf(format, a...)) // Tag printed in yellow } -// VoidLogger can be used to deactivate logs by displaying nothing -type VoidLogger struct{} +// MultiLogger aggregates several loggers log to each of them +type MultiLogger struct { + loggers []Logger +} // Log implements the Logger interface -func (l VoidLogger) Log(format string, a ...interface{}) {} +func (l MultiLogger) Log(format string, a ...interface{}) { + for _, logger := range l.loggers { + logger.Log(format, a...) + } +} From 4dfd892dfae5d9aa4b8284c9c881709bc098ddfb Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:19:58 +0100 Subject: [PATCH 0351/2266] [broker] Make router compliant to send signature --- components/router.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/router.go b/components/router.go index e97adc445..de2a2c402 100644 --- a/components/router.go +++ b/components/router.go @@ -69,9 +69,11 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt return err } - // We don't handle downlink for now, so an is quite useless. - - return upAdapter.Send(p, brokers...) + response, err := upAdapter.Send(p, brokers...) + if err != nil { + return err + } + return an.Ack(response) } // ok ensure the router has been initialized by NewRouter() From f1771256490838dabf861088ee5b348b3d115224 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:20:44 +0100 Subject: [PATCH 0352/2266] [broker] Make Loggers an exported field for MultiLogger --- utils/log/log.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/log/log.go b/utils/log/log.go index ccd135f7e..60e84ef54 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -40,12 +40,12 @@ func (l TestLogger) Log(format string, a ...interface{}) { // MultiLogger aggregates several loggers log to each of them type MultiLogger struct { - loggers []Logger + Loggers []Logger } // Log implements the Logger interface func (l MultiLogger) Log(format string, a ...interface{}) { - for _, logger := range l.loggers { + for _, logger := range l.Loggers { logger.Log(format, a...) } } From 72561a8823a0fed099ba0578f62fa15f077c86e2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:23:06 +0100 Subject: [PATCH 0353/2266] [broker] Update loggers in adapters/http --- adapters/http/adapter.go | 13 ++++--------- adapters/http/adapter_test.go | 2 +- adapters/http/broadcast/broadcast_test.go | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/adapters/http/adapter.go b/adapters/http/adapter.go index 86be17db0..782bf31df 100644 --- a/adapters/http/adapter.go +++ b/adapters/http/adapter.go @@ -18,13 +18,14 @@ var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") type Adapter struct { - client http.Client - loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. + log.Logger } // NewAdapter constructs and allocate a new Broker <-> Handler http adapter func NewAdapter(loggers ...log.Logger) (*Adapter, error) { - return &Adapter{loggers: loggers}, nil + return &Adapter{ + Logger: log.MultiLogger{Loggers: loggers}, + }, nil } // Send implements the core.Adapter interface @@ -90,9 +91,3 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { return core.Packet{}, nil, nil } - -func (a *Adapter) Log(format string, i ...interface{}) { - for _, logger := range a.loggers { - logger.Log(format, i...) - } -} diff --git a/adapters/http/adapter_test.go b/adapters/http/adapter_test.go index 8875072eb..ff7f44f3e 100644 --- a/adapters/http/adapter_test.go +++ b/adapters/http/adapter_test.go @@ -8,8 +8,8 @@ import ( "encoding/json" "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/components" "github.com/thethingsnetwork/core/lorawan" - components "github.com/thethingsnetwork/core/refactored_components" "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" diff --git a/adapters/http/broadcast/broadcast_test.go b/adapters/http/broadcast/broadcast_test.go index deb5800bc..4ace1d720 100644 --- a/adapters/http/broadcast/broadcast_test.go +++ b/adapters/http/broadcast/broadcast_test.go @@ -8,8 +8,8 @@ import ( "encoding/json" "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/components" "github.com/thethingsnetwork/core/lorawan" - components "github.com/thethingsnetwork/core/refactored_components" "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" From e828619240a59e1f30b65dccae26fd42680c6d90 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:25:58 +0100 Subject: [PATCH 0354/2266] [broker] Update loggers in adapters/semtech --- adapters/semtech/adapter.go | 48 +++++++++++------------- adapters/semtech/build_utilities_test.go | 2 +- adapters/semtech/semtech_acknacker.go | 2 +- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/adapters/semtech/adapter.go b/adapters/semtech/adapter.go index cf2bea540..4c472ae13 100644 --- a/adapters/semtech/adapter.go +++ b/adapters/semtech/adapter.go @@ -6,16 +6,16 @@ package semtech import ( "fmt" "github.com/thethingsnetwork/core" - components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/components" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/log" "net" ) type Adapter struct { - loggers []log.Logger // 0 to several loggers to get feedback from the Adapter. - conn chan udpMsg - next chan rxpkMsg + log.Logger + conn chan udpMsg + next chan rxpkMsg } type udpMsg struct { @@ -37,16 +37,16 @@ var ErrInvalidPacket error = fmt.Errorf("Invalid packet supplied") // New constructs and allocates a new udp_sender adapter func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { a := Adapter{ - loggers: loggers, - conn: make(chan udpMsg), - next: make(chan rxpkMsg), + Logger: log.MultiLogger{Loggers: loggers}, + conn: make(chan udpMsg), + next: make(chan rxpkMsg), } // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.log("Unable to establish the connection: %v", err) + a.Log("Unable to establish the connection: %v", err) return nil, ErrInvalidPort } @@ -75,7 +75,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { msg := <-a.next packet, err := components.ConvertRXPK(msg.rxpk) if err != nil { - a.log("Invalid Packet") + a.Log("Invalid Packet") return core.Packet{}, nil, ErrInvalidPacket } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil @@ -89,19 +89,19 @@ func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { // listen Handle incoming packets and forward them func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() - a.log("Start listening on %s", conn.LocalAddr()) + a.Log("Start listening on %s", conn.LocalAddr()) for { buf := make([]byte, 128) n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection - a.log("Error: %v", err) + a.Log("Error: %v", err) continue } - a.log("Incoming datagram %x", buf[:n]) + a.Log("Incoming datagram %x", buf[:n]) pkt, err := semtech.Unmarshal(buf[:n]) if err != nil { - a.log("Error: %v", err) + a.Log("Error: %v", err) continue } @@ -113,10 +113,10 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PULL_ACK, }) if err != nil { - a.log("Unexpected error while marshaling PULL_ACK: %v", err) + a.Log("Unexpected error while marshaling PULL_ACK: %v", err) continue } - a.log("Sending PULL_ACK to %v", addr) + a.Log("Sending PULL_ACK to %v", addr) a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component pushAck, err := semtech.Marshal(semtech.Packet{ @@ -125,14 +125,14 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PUSH_ACK, }) if err != nil { - a.log("Unexpected error while marshaling PUSH_ACK: %v", err) + a.Log("Unexpected error while marshaling PUSH_ACK: %v", err) continue } - a.log("Sending PUSH_ACK to %v", addr) + a.Log("Sending PUSH_ACK to %v", addr) a.conn <- udpMsg{addr: addr, raw: pushAck} if pkt.Payload == nil { - a.log("Inconsistent PUSH_DATA packet %v", pkt) + a.Log("Inconsistent PUSH_DATA packet %v", pkt) continue } for _, rxpk := range pkt.Payload.RXPK { @@ -142,7 +142,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { } } default: - a.log("Unexpected packet received. Ignored: %v", pkt) + a.Log("Unexpected packet received. Ignored: %v", pkt) continue } } @@ -154,7 +154,7 @@ func (a *Adapter) monitorConnection() { for msg := range a.conn { if msg.conn != nil { // Change the connection if udpConn != nil { - a.log("Define new UDP connection") + a.Log("Define new UDP connection") udpConn.Close() } udpConn = msg.conn @@ -162,7 +162,7 @@ func (a *Adapter) monitorConnection() { if udpConn != nil && msg.raw != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.log("Unable to send udp message: %+v", err) + a.Log("Unable to send udp message: %+v", err) } } } @@ -170,9 +170,3 @@ func (a *Adapter) monitorConnection() { udpConn.Close() // Make sure we close the connection before leaving if we dare ever leave. } } - -func (a *Adapter) log(format string, i ...interface{}) { - for _, logger := range a.loggers { - logger.Log(format, i...) - } -} diff --git a/adapters/semtech/build_utilities_test.go b/adapters/semtech/build_utilities_test.go index 5c9bd9bd0..18bef4556 100644 --- a/adapters/semtech/build_utilities_test.go +++ b/adapters/semtech/build_utilities_test.go @@ -7,8 +7,8 @@ import ( "encoding/base64" "fmt" "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/components" "github.com/thethingsnetwork/core/lorawan" - components "github.com/thethingsnetwork/core/refactored_components" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" diff --git a/adapters/semtech/semtech_acknacker.go b/adapters/semtech/semtech_acknacker.go index d03b86c6d..36993de0d 100644 --- a/adapters/semtech/semtech_acknacker.go +++ b/adapters/semtech/semtech_acknacker.go @@ -6,7 +6,7 @@ package semtech import ( "fmt" "github.com/thethingsnetwork/core" - components "github.com/thethingsnetwork/core/refactored_components" + "github.com/thethingsnetwork/core/components" "github.com/thethingsnetwork/core/semtech" "net" ) From d19d566be2782836f3f73b5523db6fd6905b751d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:37:14 +0100 Subject: [PATCH 0355/2266] [broker] Update loggers in components --- components/broker.go | 16 +++++----------- components/router.go | 11 ++--------- 2 files changed, 7 insertions(+), 20 deletions(-) diff --git a/components/broker.go b/components/broker.go index 826c7e92a..a33046f02 100644 --- a/components/broker.go +++ b/components/broker.go @@ -11,8 +11,8 @@ import ( ) type Broker struct { - loggers []log.Logger - db brokerStorage + log.Logger + db brokerStorage } func NewBroker(loggers ...log.Logger) (*Broker, error) { @@ -23,8 +23,8 @@ func NewBroker(loggers ...log.Logger) (*Broker, error) { } return &Broker{ - loggers: loggers, - db: localDB, + Logger: log.MultiLogger{Loggers: loggers}, + db: localDB, }, nil } @@ -49,7 +49,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter for _, entry := range entries { ok, err := p.Payload.ValidateMIC(entry.NwsKey) if err != nil { - b.log("Unexpected error: %v", err) + b.Log("Unexpected error: %v", err) continue } if ok { @@ -89,9 +89,3 @@ func (b *Broker) Register(r core.Registration) error { entry := brokerEntry{Id: id, Url: url, NwsKey: nwsKey} return b.db.store(r.DevAddr, entry) } - -func (b *Broker) log(format string, i ...interface{}) { - for _, logger := range b.loggers { - logger.Log(format, i...) - } -} diff --git a/components/router.go b/components/router.go index de2a2c402..c45101308 100644 --- a/components/router.go +++ b/components/router.go @@ -15,7 +15,7 @@ const ( ) type Router struct { - loggers []log.Logger + log.Logger brokers []core.Recipient db routerStorage // Local storage that maps end-device addresses to broker addresses } @@ -33,7 +33,7 @@ func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) } return &Router{ - loggers: loggers, + Logger: log.MultiLogger{Loggers: loggers}, brokers: brokers, db: localDB, }, nil @@ -80,10 +80,3 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt func (r *Router) ok() bool { return r == nil && r.db != nil } - -// log broadcast the log message to all registered logger -func (r *Router) log(format string, i ...interface{}) { - for _, logger := range r.loggers { - logger.Log(format, i...) - } -} From 7a4ce79fb2c7cea6cb7e276cdfba3c40a52ad9fa Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:40:24 +0100 Subject: [PATCH 0356/2266] [broker] Update devplan and readme --- DEVELOPMENT_PLAN.md | 31 +++++++++---------------- README.md | 55 ++++++--------------------------------------- 2 files changed, 18 insertions(+), 68 deletions(-) diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index adf7b1247..65f869ee5 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -35,6 +35,7 @@ system will just forward messages using pre-configured end-device addresses. - [x] Create Mock DownAdapter - [x] Create Mock UpAdatper - [x] Test'em all + - [ ] Switch from local in-memory storage to Reddis - [x] UpAdapter - [x] Listen and forward incoming packets to Core router - [x] Keep track of existing UDP connections @@ -43,33 +44,23 @@ system will just forward messages using pre-configured end-device addresses. - [x] Listen and forward incoming packet to Core router - [x] Broadcast a packet to several brokers - [x] Send packet to given brokers (same as above ?) - - [ ] Bonus - - [ ] Switch from local in-memory storage to Reddis -- [ ] Basic Broker - - [ ] Core - - [ ] Lookup for associated device from Network Controller - - [ ] Send acknowledgement or rejection for given packet - - [ ] If necessary, forward packet to right handler - - [ ] Associate handlers to static device addresses - - [ ] Router adapter - - [ ] Listen to http request and forward valid request to core - - [ ] Respond with 200 OK (ack) or 404 Not Found (nack) - - [ ] Network Controller adapter - - [ ] Lookup for device from a packet - - [ ] Send back packet that are indeed handled by a handler +- [x] Basic Broker + - [x] Core + - [x] Lookup for associated device from Network Controller + - [x] Send acknowledgement or rejection for given packet + - [x] If necessary, forward packet to right handler + - [x] Associate handlers to static device addresses + - [ ] Switch from local in-memory storage to Reddis + - [x] Router adapter + - [x] Listen to http request and forward valid request to core + - [x] Respond with 200 OK (ack) or 404 Not Found (nack) - [ ] Handler adapter - [ ] Send packet to handler - [ ] Accept registrations of static devAddr from handlers - -- [ ] Minimalist Dumb Network-Controller - - [ ] Retrieve corresponding key (appSKey or nwskey) - - [ ] Compute MIC check of received packet - - ## Milestone 3 Support application registration for personalization. Applications provide a list of personalized device addresses along with the network session keys diff --git a/README.md b/README.md index 61fef1fcc..4953d02df 100644 --- a/README.md +++ b/README.md @@ -28,26 +28,14 @@ So far: -----> Handler -| adapters ------| rtr-brk-http -----------> Adapter - ------| gtw-rtr-udp -----------> Adapter - ------| brk-nwc-local -----------> Adapter - ------| brk-hdl-http -----------> Adapter - ------| brk-rtr-http -----------> Adapter - ------| ns-brk-local -----------> Adapter - ------| hdl-brk-http +-----| http ----------> Adapter +----------| pubsub +---------------> Adapter +----------| broadcast +---------------> Adapter +-----| semtech +---------------> Adapter -| lorawan -----| mac @@ -63,35 +51,6 @@ So far: -----| gateway ----------> Forwarder ----------> Imitator - --| testing ------| mock_components -----------> Router -----------> Broker -----------> Networkcontroller -----------> Handler - ------| mock_adapters -----------| rtr-brk-mock ----------------> Adapter - -----------| gtw-rtr-mock ----------------> Adapter - -----------| brk-nwc-mock ----------------> Adapter - -----------| brk-hdl-mock ----------------> Adapter - -----------| brk-rtr-mock ----------------> Adapter - -----------| ns-brk-mock ----------------> Adapter - -----------| hdl-brk-mock ----------------> Adapter ``` ## Development Plan From 5cbe56978bcb3d4cc07a91d2aa43c24e97932272 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:48:24 +0100 Subject: [PATCH 0357/2266] Integrate devAddr computing in core instead of lorawan package + split core methods from interfaces --- core.go | 56 +++++++++-------------------------------------------- core_def.go | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 47 deletions(-) create mode 100644 core_def.go diff --git a/core.go b/core.go index 6d8526156..b4b10dfcc 100644 --- a/core.go +++ b/core.go @@ -1,20 +1,21 @@ package core import ( - "encoding/json" "fmt" "github.com/thethingsnetwork/core/lorawan" ) -var ErrInvalidPacket error = fmt.Errorf("Invalid Packet") +func (p Packet) DevAddr() (lorawan.DevAddr, error) { + if p.Payload.MACPayload == nil { + return lorawan.DevAddr{}, fmt.Errorf("lorawan: MACPayload should not be empty") + } -type Packet struct { - Metadata Metadata - Payload lorawan.PHYPayload -} + macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return lorawan.DevAddr{}, fmt.Errorf("lorawan: unable to get address of a join message") + } -func (p Packet) DevAddr() (lorawan.DevAddr, error) { - return p.Payload.DevAddr() + return macpayload.FHDR.DevAddr, nil } func (p Packet) String() string { @@ -25,42 +26,3 @@ func (p Packet) String() string { str += fmt.Sprintf("\n\tPayload%+v\n}", p.Payload) return str } - -type Recipient struct { - Address interface{} - Id interface{} -} - -type Metadata interface { - json.Marshaler - json.Unmarshaler - String() string -} - -type AckNacker interface { - Ack(p Packet) error - Nack(p Packet) error -} - -type Component interface { - Register(reg Registration) error - HandleUp(p Packet, an AckNacker, upAdapter Adapter) error - HandleDown(p Packet, an AckNacker, downAdapter Adapter) error -} - -type Adapter interface { - Send(p Packet, r ...Recipient) (Packet, error) - Next() (Packet, AckNacker, error) - NextRegistration() (Registration, AckNacker, error) -} - -type Registration struct { - DevAddr lorawan.DevAddr - Recipient Recipient - Options interface{} -} - -type Router Component -type Broker Component -type Handler Component -type NetworkController Component diff --git a/core_def.go b/core_def.go new file mode 100644 index 000000000..779936af1 --- /dev/null +++ b/core_def.go @@ -0,0 +1,50 @@ +package core + +import ( + "encoding/json" + "github.com/thethingsnetwork/core/lorawan" +) + +type Packet struct { + Metadata Metadata + Payload lorawan.PHYPayload +} + +type Recipient struct { + Address interface{} + Id interface{} +} + +type Metadata interface { + json.Marshaler + json.Unmarshaler + String() string +} + +type AckNacker interface { + Ack(p Packet) error + Nack(p Packet) error +} + +type Component interface { + Register(reg Registration) error + HandleUp(p Packet, an AckNacker, upAdapter Adapter) error + HandleDown(p Packet, an AckNacker, downAdapter Adapter) error +} + +type Adapter interface { + Send(p Packet, r ...Recipient) (Packet, error) + Next() (Packet, AckNacker, error) + NextRegistration() (Registration, AckNacker, error) +} + +type Registration struct { + DevAddr lorawan.DevAddr + Recipient Recipient + Options interface{} +} + +type Router Component +type Broker Component +type Handler Component +type NetworkController Component From bef8d064f2057a7ca785030e4dda75eb645a7839 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 11 Jan 2016 18:49:05 +0100 Subject: [PATCH 0358/2266] Reset lorawan to original state --- lorawan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan b/lorawan index 9b752c4d9..8089543fb 160000 --- a/lorawan +++ b/lorawan @@ -1 +1 @@ -Subproject commit 9b752c4d969941a1a312ff1d86d5b1c6b5fb3519 +Subproject commit 8089543fb4bf52ed97539621ca43ea2ce33f907f From 878023aff44282b4de005d8f5a7e79fe123ef179 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 11:00:46 +0100 Subject: [PATCH 0359/2266] Update lorawan submodule --- lorawan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lorawan b/lorawan index 8089543fb..09e6c20be 160000 --- a/lorawan +++ b/lorawan @@ -1 +1 @@ -Subproject commit 8089543fb4bf52ed97539621ca43ea2ce33f907f +Subproject commit 09e6c20bec91bdd420fc89c111082c809dc8e05c From f68377d705ef8a60a8ee2da6f7fa17569dd39df7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 11:01:11 +0100 Subject: [PATCH 0360/2266] Fix import issues in simulators (refered to old folder structures) --- simulators/gateway/forwarder.go | 4 ++-- simulators/gateway/forwarder_test.go | 2 +- simulators/gateway/utils.go | 7 +++---- simulators/gateway/utils_test.go | 10 +--------- 4 files changed, 7 insertions(+), 16 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index b5cd0193c..d58c91dcc 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -5,7 +5,7 @@ package gateway import ( "fmt" - "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" "io" @@ -57,8 +57,8 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error } fwd := &Forwarder{ + Logger: log.DebugLogger{Tag: "Forwarder"}, Id: id, - Logger: log.VoidLogger{}, alti: 120, lati: 53.3702, long: 4.8952, diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index d84374b05..6e230325b 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -6,7 +6,7 @@ package gateway import ( "fmt" . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/semtech" "io" "testing" "time" diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index d2ba99f1e..51a241fde 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -8,9 +8,8 @@ import ( "encoding/base64" "encoding/binary" "fmt" - "github.com/brocaar/lorawan" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" "math" "math/rand" @@ -99,7 +98,7 @@ func generateData(frmData string) string { Major: lorawan.LoRaWANR1, } phyPayload.MACPayload = macPayload - phyPayload.SetMIC(core.GetNwSKey()) + phyPayload.SetMIC(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) raw, err := phyPayload.MarshalBinary() if err != nil { // Shouldn't be diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index f53396b1d..ef65b0cf4 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -5,7 +5,7 @@ package gateway import ( . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/lorawan/semtech" + "github.com/thethingsnetwork/core/semtech" "testing" ) @@ -116,11 +116,3 @@ func TestGenerateLsnr(t *testing.T) { So(len(values), ShouldBeGreaterThan, 5) }) } - -func TestGenerateRXPK(t *testing.T) { - Convey("The generateRXPK() function should generate a valid RXPK holding data from a device", t, func() { - rxpk := generateRXPK() - devAddr := rxpk.DevAddr() - So(devAddr, ShouldNotBeNil) - }) -} From 296835da203cf23dabfd3125fe898a4766f65f1d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 11:01:29 +0100 Subject: [PATCH 0361/2266] Skip component tests instead of showing a failure --- components/broker_test.go | 7 +++---- components/router_test.go | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/components/broker_test.go b/components/broker_test.go index bb8aa6b40..5a88a5478 100644 --- a/components/broker_test.go +++ b/components/broker_test.go @@ -4,18 +4,17 @@ package components import ( - . "github.com/thethingsnetwork/core/utils/testing" "testing" ) func TestNewBroker(t *testing.T) { - Ko(t, "TODO") + t.Skip("TODO") } func TestHandleUpBroker(t *testing.T) { - Ko(t, "TODO") + t.Skip("TODO") } func TestRegisterBroker(t *testing.T) { - Ko(t, "TODO") + t.Skip("TODO") } diff --git a/components/router_test.go b/components/router_test.go index 36f8cbdf4..c3f4be3e8 100644 --- a/components/router_test.go +++ b/components/router_test.go @@ -4,18 +4,17 @@ package components import ( - . "github.com/thethingsnetwork/core/utils/testing" "testing" ) func TestNewRouter(t *testing.T) { - Ko(t, "TODO") + t.Skip("TODO") } func TestSendRouter(t *testing.T) { - Ko(t, "TODO") + t.Skip("TODO") } func TestHandleUpRouter(t *testing.T) { - Ko(t, "TODO") + t.Skip("TODO") } From dbf6235106569e2a77ce806b8791af63ec7542c7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 11:01:50 +0100 Subject: [PATCH 0362/2266] Add little delays in test adapter to let connection be established / closed --- adapters/http/adapter.go | 2 +- adapters/http/adapter_test.go | 1 + adapters/http/broadcast/broadcast_test.go | 9 +++++---- adapters/http/pubsub/pubsub_test.go | 6 +++--- adapters/semtech/adapter_test.go | 1 + 5 files changed, 11 insertions(+), 8 deletions(-) diff --git a/adapters/http/adapter.go b/adapters/http/adapter.go index 782bf31df..88c871707 100644 --- a/adapters/http/adapter.go +++ b/adapters/http/adapter.go @@ -55,12 +55,12 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) - defer resp.Body.Close() if err != nil { // Non-blocking, buffered cherr <- err return } + defer resp.Body.Close() if resp.StatusCode != http.StatusOK { // Non-blocking, buffered cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) diff --git a/adapters/http/adapter_test.go b/adapters/http/adapter_test.go index ff7f44f3e..4d804d92b 100644 --- a/adapters/http/adapter_test.go +++ b/adapters/http/adapter_test.go @@ -46,6 +46,7 @@ func TestSend(t *testing.T) { for _, test := range tests { Desc(t, "Sending packet: %v", test.Packet) + <-time.After(time.Millisecond * 100) err := adapter.Send(test.Packet, s.Recipient) checkErrors(t, test.WantError, err) checkSend(t, test.WantPayload, s) diff --git a/adapters/http/broadcast/broadcast_test.go b/adapters/http/broadcast/broadcast_test.go index 4ace1d720..ea960966a 100644 --- a/adapters/http/broadcast/broadcast_test.go +++ b/adapters/http/broadcast/broadcast_test.go @@ -23,10 +23,10 @@ import ( func TestSend(t *testing.T) { packet, devAddr, payload := genSample() recipients := []core.Recipient{ - core.Recipient{Address: "0.0.0.0:3000", Id: "AlwaysReject"}, - core.Recipient{Address: "0.0.0.0:3001", Id: "AlwaysAccept"}, - core.Recipient{Address: "0.0.0.0:3002", Id: "AlwaysAccept"}, - core.Recipient{Address: "0.0.0.0:3003", Id: "AlwaysReject"}, + core.Recipient{Address: "0.0.0.0:3010", Id: "AlwaysReject"}, + core.Recipient{Address: "0.0.0.0:3011", Id: "AlwaysAccept"}, + core.Recipient{Address: "0.0.0.0:3012", Id: "AlwaysAccept"}, + core.Recipient{Address: "0.0.0.0:3013", Id: "AlwaysReject"}, } registrations := []core.Registration{ core.Registration{DevAddr: devAddr, Recipient: recipients[0]}, @@ -85,6 +85,7 @@ func TestSend(t *testing.T) { for _, test := range tests { // Describe Desc(t, "Sending packet %v to %v", test.Packet, test.Recipients) + <-time.After(time.Millisecond * 100) // Operate err := adapter.Send(test.Packet, test.Recipients...) diff --git a/adapters/http/pubsub/pubsub_test.go b/adapters/http/pubsub/pubsub_test.go index 06f455738..c105c2291 100644 --- a/adapters/http/pubsub/pubsub_test.go +++ b/adapters/http/pubsub/pubsub_test.go @@ -59,9 +59,8 @@ func TestNextRegistration(t *testing.T) { }, } - adapter, err := NewAdapter(3001, HandlerParser{}, log.TestLogger{Tag: "Adapter", T: t}) - client := &client{adapter: "0.0.0.0:3001"} - <-time.After(time.Millisecond * 200) + adapter, err := NewAdapter(3021, HandlerParser{}, log.TestLogger{Tag: "Adapter", T: t}) + client := &client{adapter: "0.0.0.0:3021"} if err != nil { panic(err) } @@ -69,6 +68,7 @@ func TestNextRegistration(t *testing.T) { for _, test := range tests { // Describe Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) + <-time.After(time.Millisecond * 100) // Build gotErr := make(chan error) diff --git a/adapters/semtech/adapter_test.go b/adapters/semtech/adapter_test.go index 8a576b306..cc3eb653d 100644 --- a/adapters/semtech/adapter_test.go +++ b/adapters/semtech/adapter_test.go @@ -87,6 +87,7 @@ func TestNext(t *testing.T) { for _, test := range tests { // Describe Desc(t, "Sending packet through adapter: %v", test.Packet) + <-time.After(time.Millisecond * 100) // Operate ack := server.send(test.Packet) From 31f7f45a1d11856a21b2f1ff862ca461a1b1800e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jan 2016 14:39:25 +0100 Subject: [PATCH 0363/2266] Update README --- README.md | 88 ++++++++++++++++++++----------------------------------- 1 file changed, 32 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index 4953d02df..52cffbd0e 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,42 @@ -The Things Network Core Architecture -==================================== +The Things Network +================== [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) -## How to Contribute - -- Open an issue and explain on what you're working -- Work in a separate branch forked from `develop` -- Bring your feature to life and make a PR -- Do not commit on `master` anymore - -## Folder structure - -So far: - -``` --> Router --> GatewayRouterAdapter --> RouterBrokerAdapter --> BrokerAddress --> GatewayAddress - --| components ------> Router ------> Broker ------> Networkcontroller ------> Handler - --| adapters ------| http -----------> Adapter -----------| pubsub ----------------> Adapter -----------| broadcast ----------------> Adapter ------| semtech ----------------> Adapter - --| lorawan ------| mac ------| semtech -----------> Payload -----------> Packet -----------> DeviceAddress -----------> RXPK -----------> TXPK -----------> Stat - --| simulators ------| gateway -----------> Forwarder -----------> Imitator -``` +![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) -## Development Plan +The Things Network is a global open crowdsourced Internet of Things data network. + +## Status + +This repository **will become** version 1.0 of The Things Network. It is **under heavy development** and currently it's APIs and code are not yet stable (pre 1.0). + +## Getting Started + +For the time being this repository is **development-only**. We are working hard on getting the network components ready and welcome all the help we can get. If you want to participate in our community, you should: + +- Register on the [forum](http://forum.thethingsnetwork.org) +- Join [Slack](https://slack.thethingsnetwork.org) +- Sign up for the [newsletter](http://thethingsnetwork.org/#team) (click the button that says *Join our team*) +- Read background information on the [wiki](http://thethingsnetwork.org/wiki) +- Send an email to @johanstokking (johan@thethingsnetwork.org) -See the [development plan](DEVELOPMENT_PLAN.md) +To get started with LoRaWAN and The Things Network, you can also have a look at a *demonstration version* of the network, which is documented on the [wiki](http://thethingsnetwork.org/wiki/). Development of the demonstration version is done in the [croft](https://github.com/TheThingsNetwork/croft) and [jolie](https://github.com/TheThingsNetwork/jolie) repositories. The demonstration version will soon be deprecated, but a compatible application will be built on top of the new infrastructure that is being built in this repository. -## Authors +## Contributing + +Source code for The Things Network is MIT licensed. We encourage users to make contributions on [Github](https://github.com/TheThingsNetwork/ttn) and to participate in discussions on [Slack](https://slack.thethingsnetwork.org). + +If you find bugs or documentation mistakes, please check [open issues](https://github.com/TheThingsNetwork/ttn/issues) before [creating a new issue](https://github.com/TheThingsNetwork/ttn/issues/new). Please be specific and give a detailed description of the issue. Explain the steps to reproduce the problem. If you're able to fix the issue yourself, please help the community by forking the repository and submitting a pull request with your fix. + +For contributing a feature, please open an issue that explains what you're working on. Work in your own fork of the repository and submit a pull request when you're done. + +If you want to contribute, but don't know where to start, you could have a look at issues with the label [*help wanted*](https://github.com/TheThingsNetwork/ttn/labels/help%20wanted) or [*difficulty/easy*](https://github.com/TheThingsNetwork/ttn/labels/difficulty%2Feasy). + +## Development Plan -See the [author's list](AUTHORS) +We have a [development plan](DEVELOPMENT_PLAN.md) document, but this will soon be migrated to Github issues. ## License -See the [license file](LICENSE) +Source code for The Things Network is released under the MIT License, which can be found in the [LICENSE](LICENSE) file. A list of authors can be found in the [AUTHORS](AUTHORS) file. From 76aa049111d8e94b820b29989ac44cf1f3b518b4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jan 2016 16:02:15 +0100 Subject: [PATCH 0364/2266] Add ROADMAP Moved items from DEVELOPMENT_PLAN to Github Issues ROADMAP should become a more high-level, long-term vision document. --- DEVELOPMENT_PLAN.md | 124 -------------------------------------------- README.md | 4 +- ROADMAP.md | 30 +++++++++++ 3 files changed, 32 insertions(+), 126 deletions(-) delete mode 100644 DEVELOPMENT_PLAN.md create mode 100644 ROADMAP.md diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md deleted file mode 100644 index 65f869ee5..000000000 --- a/DEVELOPMENT_PLAN.md +++ /dev/null @@ -1,124 +0,0 @@ -Development Plan -================ - -## Milestone 1 -Have a fake gateway able to mock the behavior of a physical gateway. This will be used -mainly for testing and ensuring the correctness of other components. - -- [ ] Fake gateway - - [x] Types, packages and data structures in use - - [x] Emit udp packets towards a server - - [x] Handle behavior described by the semtech protocol - - [x] Serialize json rxpk/stat object(s) - - [x] Generate json rxpl/stat object(s) - - [x] Update gateway statistics accordingly - - [x] Generate valid fake end-device packet - - [ ] Simulate fake end-devices traffic - - [ ] Complete tests set for stats functions - - [ ] Implements UDP ReadWriteCloser - - [ ] Move existing fake ReadWriteCloser to a proper file with handy constructor - - -## Milestone 2 -Handle an uplink process that can forward packet coming from a gateway to a simple end-server -(fake handler). We handle no MAC commands and we does not care about registration yet. The -system will just forward messages using pre-configured end-device addresses. - - -- [x] Basic Router - - [x] Core - - [x] Lookup for device address (only local) - - [x] Invalidate broker periodically (only local) - - [x] Acknowledge packet from gateway - - [x] Forward packet to brokers - - [x] Create Mock router - - [x] Create Mock DownAdapter - - [x] Create Mock UpAdatper - - [x] Test'em all - - [ ] Switch from local in-memory storage to Reddis - - [x] UpAdapter - - [x] Listen and forward incoming packets to Core router - - [x] Keep track of existing UDP connections - - [x] Send ack through existing UDP connection - - [x] DownAdapter - - [x] Listen and forward incoming packet to Core router - - [x] Broadcast a packet to several brokers - - [x] Send packet to given brokers (same as above ?) - - -- [x] Basic Broker - - [x] Core - - [x] Lookup for associated device from Network Controller - - [x] Send acknowledgement or rejection for given packet - - [x] If necessary, forward packet to right handler - - [x] Associate handlers to static device addresses - - [ ] Switch from local in-memory storage to Reddis - - [x] Router adapter - - [x] Listen to http request and forward valid request to core - - [x] Respond with 200 OK (ack) or 404 Not Found (nack) - - [ ] Handler adapter - - [ ] Send packet to handler - - [ ] Accept registrations of static devAddr from handlers - - -## Milestone 3 -Support application registration for personalization. Applications provide a list of -personalized device addresses along with the network session keys - -- [ ] Extend Router - - [ ] Detail the list of features - - -- [ ] Extend Broker - - [ ] Detail the list of features - - -- [ ] Extend Network-Controller - - [ ] Detail the list of features - - -- [ ] Minimalist Handler - - [ ] Detail the list of features - -## Milestone 4 -Allow transmission of downlink messages from an application. Messages will be shipped as -response after an uplink transmission from a node. - -- [ ] Extend Broker - - [ ] Detail the list of features - - -- [ ] Extend Handler - - [ ] Detail the list of features - - -- [ ] Fake minimalist Application server - - [ ] Detail the list of features - -## Milestone 5 -Handle OTAA and downlink accept message. We still not allow MAC commands from neither the -end-device nor a network controller. Also, no downlink payload can be sent by an application: -the only downlink message we accept is the join-accept / join-reject message sent during an - -- [ ] Extend Router - - [ ] Detail the list of features - - -- [ ] Extend Broker - - [ ] Detail the list of features - - -- [ ] Extend Network-Controller - - [ ] Detail the list of features - - -- [ ] Minimalist Handler - - [ ] Detail the list of features - - - -## Milestone 6 -Handle more complexe commands and their corresponding acknowledgement. - -- [ ] Extend Network Controller - - [ ] Detail the list of features diff --git a/README.md b/README.md index 52cffbd0e..e27f4c87d 100644 --- a/README.md +++ b/README.md @@ -33,9 +33,9 @@ For contributing a feature, please open an issue that explains what you're worki If you want to contribute, but don't know where to start, you could have a look at issues with the label [*help wanted*](https://github.com/TheThingsNetwork/ttn/labels/help%20wanted) or [*difficulty/easy*](https://github.com/TheThingsNetwork/ttn/labels/difficulty%2Feasy). -## Development Plan +## Roadmap -We have a [development plan](DEVELOPMENT_PLAN.md) document, but this will soon be migrated to Github issues. +We have a [roadmap](ROADMAP.md) for the coming months. This document will evolve as we further define our long-term vision. ## License diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 000000000..9481c0b78 --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,30 @@ +Roadmap +======= + +## Milestone 1 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%201) + +Have a gateway simulator that is able to mock the behavior of a physical gateway. This will be used for testing and ensuring the correctness of other components. + +## Milestone 2 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%202) + +Support for uplink messages (from a node to an application). A gateway can send received messages to a [Router](https://thethingsnetwork.github.io/docs/router/). The Router filters out messages that are part of other networks and routes "our" messages to to a [Broker](https://thethingsnetwork.github.io/docs/broker/). The Broker forwards the messages to a [Handler](https://thethingsnetwork.github.io/docs/handler/), which delivers them to the Application. + +We will not support any MAC commands, nor device or application registration. The system will just forward messages using pre-configured server and end-device addresses. + +## Milestone 3 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%203) + +Support application registration for personalization. Applications provide a list of personalized device addresses along with the network session keys. + +## Milestone 4 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%204) + +Support for downlink messages (from an application to a node). Messages will be shipped as a response to an uplink transmission from a (Class A) node. + +## Milestone 5 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%205) + +Support for Over-the-air Activation (OTAA). Devices with a globally unique end-device identifier (DevEUI), an application identifier (AppEUI) and an application key (AppKey) can send join-requests to the network. + +We still not allow MAC commands from neither the end-device nor a network controller. + +## Milestone 6 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%206) + +Support for LoRaWAN MAC commands. From 088ad25b409b2bf63ebbab9da287574b2d41d6a3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 16:12:46 +0100 Subject: [PATCH 0365/2266] Change folder structure. this basically breaks everything. --- core.go | 28 ----- {adapters => core/adapters}/http/adapter.go | 33 ++++-- .../adapters}/http/adapter_test.go | 9 +- .../adapters}/http/broadcast/broadcast.go | 0 .../http/broadcast/broadcast_test.go | 0 .../http/broadcast/void_acknacker.go | 0 .../adapters}/http/pubsub/handler_parser.go | 0 .../adapters}/http/pubsub/pubsub.go | 0 .../adapters}/http/pubsub/pubsub_test.go | 0 .../adapters}/http/pubsub/reg_acknacker.go | 0 {adapters => core/adapters}/protocols.md | 0 .../adapters}/semtech/adapter.go | 4 +- .../adapters}/semtech/adapter_test.go | 0 .../adapters}/semtech/build_utilities_test.go | 0 .../adapters}/semtech/semtech_acknacker.go | 0 {components => core}/build_utilities_test.go | 2 +- {components => core}/check_utilities_test.go | 7 +- {components => core/components}/broker.go | 0 .../components}/broker_storage.go | 0 .../components}/broker_test.go | 0 {components => core/components}/errors.go | 3 - {components => core/components}/packet.go | 0 .../components}/packet_test.go | 0 {components => core/components}/router.go | 14 +-- .../components}/router_storage.go | 0 .../components}/router_test.go | 0 core/core.go | 65 +++++++++++ {components => core}/metadata.go | 23 +--- {components => core}/metadata_test.go | 3 +- core/packet.go | 101 ++++++++++++++++++ core_def.go | 50 --------- 31 files changed, 207 insertions(+), 135 deletions(-) delete mode 100644 core.go rename {adapters => core/adapters}/http/adapter.go (75%) rename {adapters => core/adapters}/http/adapter_test.go (93%) rename {adapters => core/adapters}/http/broadcast/broadcast.go (100%) rename {adapters => core/adapters}/http/broadcast/broadcast_test.go (100%) rename {adapters => core/adapters}/http/broadcast/void_acknacker.go (100%) rename {adapters => core/adapters}/http/pubsub/handler_parser.go (100%) rename {adapters => core/adapters}/http/pubsub/pubsub.go (100%) rename {adapters => core/adapters}/http/pubsub/pubsub_test.go (100%) rename {adapters => core/adapters}/http/pubsub/reg_acknacker.go (100%) rename {adapters => core/adapters}/protocols.md (100%) rename {adapters => core/adapters}/semtech/adapter.go (97%) rename {adapters => core/adapters}/semtech/adapter_test.go (100%) rename {adapters => core/adapters}/semtech/build_utilities_test.go (100%) rename {adapters => core/adapters}/semtech/semtech_acknacker.go (100%) rename {components => core}/build_utilities_test.go (99%) rename {components => core}/check_utilities_test.go (90%) rename {components => core/components}/broker.go (100%) rename {components => core/components}/broker_storage.go (100%) rename {components => core/components}/broker_test.go (100%) rename {components => core/components}/errors.go (80%) rename {components => core/components}/packet.go (100%) rename {components => core/components}/packet_test.go (100%) rename {components => core/components}/router.go (81%) rename {components => core/components}/router_storage.go (100%) rename {components => core/components}/router_test.go (100%) create mode 100644 core/core.go rename {components => core}/metadata.go (53%) rename {components => core}/metadata_test.go (97%) create mode 100644 core/packet.go delete mode 100644 core_def.go diff --git a/core.go b/core.go deleted file mode 100644 index b4b10dfcc..000000000 --- a/core.go +++ /dev/null @@ -1,28 +0,0 @@ -package core - -import ( - "fmt" - "github.com/thethingsnetwork/core/lorawan" -) - -func (p Packet) DevAddr() (lorawan.DevAddr, error) { - if p.Payload.MACPayload == nil { - return lorawan.DevAddr{}, fmt.Errorf("lorawan: MACPayload should not be empty") - } - - macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return lorawan.DevAddr{}, fmt.Errorf("lorawan: unable to get address of a join message") - } - - return macpayload.FHDR.DevAddr, nil -} - -func (p Packet) String() string { - str := "Packet {" - if p.Metadata != nil { - str += fmt.Sprintf("\n\t%s}", p.Metadata.String()) - } - str += fmt.Sprintf("\n\tPayload%+v\n}", p.Payload) - return str -} diff --git a/adapters/http/adapter.go b/core/adapters/http/adapter.go similarity index 75% rename from adapters/http/adapter.go rename to core/adapters/http/adapter.go index 88c871707..4230a9196 100644 --- a/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -8,8 +8,9 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/utils/log" + "io" "net/http" "sync" ) @@ -29,21 +30,22 @@ func NewAdapter(loggers ...log.Logger) (*Adapter, error) { } // Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - return ErrInvalidPacket + return nil, ErrInvalidPacket } pl, err := p.Payload.MarshalBinary() if err != nil { - return ErrInvalidPacket + return nil, ErrInvalidPacket } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) // Prepare ground for parrallel http request nb := len(r) cherr := make(chan error, nb) + chresp := make(chan core.Packet, nb) wg := sync.WaitGroup{} wg.Add(nb) @@ -56,16 +58,27 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) if err != nil { - // Non-blocking, buffered cherr <- err return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - // Non-blocking, buffered cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) return } + + raw := make([]byte, resp.ContentLength) + n, err := resp.Body.Read(raw) + if err != nil && err != io.EOF { + cherr <- err + return + } + var packet core.Packet + if err := json.Unmarshal(raw[:n], &packet); err != nil { + cherr <- err + return + } + chresp <- packet }(recipient) } @@ -76,9 +89,13 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { errors = append(errors, <-cherr) } if errors != nil { - return fmt.Errorf("Errors: %v", errors) + return nil, fmt.Errorf("Errors: %v", errors) + } + var packets []core.Packet + for i := 0; i < len(chresp); i += 1 { + packets = append(packets, <-chresp) } - return nil + return packets, nil } // Next implements the core.Adapter interface diff --git a/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go similarity index 93% rename from adapters/http/adapter_test.go rename to core/adapters/http/adapter_test.go index 4d804d92b..e8b3a1f26 100644 --- a/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -7,8 +7,7 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/components" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" @@ -47,7 +46,7 @@ func TestSend(t *testing.T) { for _, test := range tests { Desc(t, "Sending packet: %v", test.Packet) <-time.After(time.Millisecond * 100) - err := adapter.Send(test.Packet, s.Recipient) + _, err := adapter.Send(test.Packet, s.Recipient) checkErrors(t, test.WantError, err) checkSend(t, test.WantPayload, s) } @@ -95,7 +94,7 @@ func genMockServer(port uint) MockServer { if err != nil && err != io.EOF { panic(err) } - w.Write(nil) + w.Write(body[:n]) // NOTE TEMPORARY, the response is supposed to be different go func() { payloads <- string(body[:n]) }() }) @@ -157,7 +156,7 @@ func genPHYPayload(msg string, devAddr [4]byte) lorawan.PHYPayload { func genCorePacket() core.Packet { return core.Packet{ Payload: genPHYPayload("myData", [4]byte{0x1, 0x2, 0x3, 0x4}), - Metadata: &components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, + Metadata: core.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, } } diff --git a/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go similarity index 100% rename from adapters/http/broadcast/broadcast.go rename to core/adapters/http/broadcast/broadcast.go diff --git a/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go similarity index 100% rename from adapters/http/broadcast/broadcast_test.go rename to core/adapters/http/broadcast/broadcast_test.go diff --git a/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go similarity index 100% rename from adapters/http/broadcast/void_acknacker.go rename to core/adapters/http/broadcast/void_acknacker.go diff --git a/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go similarity index 100% rename from adapters/http/pubsub/handler_parser.go rename to core/adapters/http/pubsub/handler_parser.go diff --git a/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go similarity index 100% rename from adapters/http/pubsub/pubsub.go rename to core/adapters/http/pubsub/pubsub.go diff --git a/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go similarity index 100% rename from adapters/http/pubsub/pubsub_test.go rename to core/adapters/http/pubsub/pubsub_test.go diff --git a/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go similarity index 100% rename from adapters/http/pubsub/reg_acknacker.go rename to core/adapters/http/pubsub/reg_acknacker.go diff --git a/adapters/protocols.md b/core/adapters/protocols.md similarity index 100% rename from adapters/protocols.md rename to core/adapters/protocols.md diff --git a/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go similarity index 97% rename from adapters/semtech/adapter.go rename to core/adapters/semtech/adapter.go index 4c472ae13..0adbaeef9 100644 --- a/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -63,8 +63,8 @@ func (a *Adapter) ok() bool { } // Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { - return ErrNotSupported +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { + return core.Packet{}, ErrNotSupported } // Next implements the core.Adapter interface diff --git a/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go similarity index 100% rename from adapters/semtech/adapter_test.go rename to core/adapters/semtech/adapter_test.go diff --git a/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go similarity index 100% rename from adapters/semtech/build_utilities_test.go rename to core/adapters/semtech/build_utilities_test.go diff --git a/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go similarity index 100% rename from adapters/semtech/semtech_acknacker.go rename to core/adapters/semtech/semtech_acknacker.go diff --git a/components/build_utilities_test.go b/core/build_utilities_test.go similarity index 99% rename from components/build_utilities_test.go rename to core/build_utilities_test.go index 5c5fb9496..6f470462a 100644 --- a/components/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package components +package core import ( "encoding/base64" diff --git a/components/check_utilities_test.go b/core/check_utilities_test.go similarity index 90% rename from components/check_utilities_test.go rename to core/check_utilities_test.go index 2a7c12a77..0434987fa 100644 --- a/components/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -1,10 +1,9 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package components +package core import ( - "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" @@ -12,8 +11,8 @@ import ( "testing" ) -// Checks that two core packets match -func checkPackets(t *testing.T, want core.Packet, got core.Packet) { +// Checks that two packets match +func checkPackets(t *testing.T, want Packet, got Packet) { if reflect.DeepEqual(want, got) { Ok(t, "Check packets") return diff --git a/components/broker.go b/core/components/broker.go similarity index 100% rename from components/broker.go rename to core/components/broker.go diff --git a/components/broker_storage.go b/core/components/broker_storage.go similarity index 100% rename from components/broker_storage.go rename to core/components/broker_storage.go diff --git a/components/broker_test.go b/core/components/broker_test.go similarity index 100% rename from components/broker_test.go rename to core/components/broker_test.go diff --git a/components/errors.go b/core/components/errors.go similarity index 80% rename from components/errors.go rename to core/components/errors.go index 99b1acadd..36988dbc6 100644 --- a/components/errors.go +++ b/core/components/errors.go @@ -12,6 +12,3 @@ var ErrBadOptions = fmt.Errorf("Invalid supplied options") var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") - -// returned by ConvertRXPK or ConvertTXPK if there's no data in the given packet -var ErrImpossibleConversion = fmt.Errorf("The given packet can't be converted") diff --git a/components/packet.go b/core/components/packet.go similarity index 100% rename from components/packet.go rename to core/components/packet.go diff --git a/components/packet_test.go b/core/components/packet_test.go similarity index 100% rename from components/packet_test.go rename to core/components/packet_test.go diff --git a/components/router.go b/core/components/router.go similarity index 81% rename from components/router.go rename to core/components/router.go index c45101308..b0cb98c3b 100644 --- a/components/router.go +++ b/core/components/router.go @@ -16,26 +16,20 @@ const ( type Router struct { log.Logger - brokers []core.Recipient - db routerStorage // Local storage that maps end-device addresses to broker addresses + db routerStorage // Local storage that maps end-device addresses to broker addresses } // NewRouter constructs a Router and setup its internal structure -func NewRouter(brokers []core.Recipient, loggers ...log.Logger) (*Router, error) { +func NewRouter(loggers ...log.Logger) (*Router, error) { localDB, err := NewRouterStorage(EXPIRY_DELAY) if err != nil { return nil, err } - if len(brokers) == 0 { - return nil, ErrBadOptions - } - return &Router{ - Logger: log.MultiLogger{Loggers: loggers}, - brokers: brokers, - db: localDB, + Logger: log.MultiLogger{Loggers: loggers}, + db: localDB, }, nil } diff --git a/components/router_storage.go b/core/components/router_storage.go similarity index 100% rename from components/router_storage.go rename to core/components/router_storage.go diff --git a/components/router_test.go b/core/components/router_test.go similarity index 100% rename from components/router_test.go rename to core/components/router_test.go diff --git a/core/core.go b/core/core.go new file mode 100644 index 000000000..e3f44d6ed --- /dev/null +++ b/core/core.go @@ -0,0 +1,65 @@ +package core + +import ( + "github.com/thethingsnetwork/core/lorawan" + "time" +) + +type Packet struct { + Metadata Metadata + Payload lorawan.PHYPayload +} + +type Recipient struct { + Address interface{} + Id interface{} +} + +type Metadata struct { + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +type AckNacker interface { + Ack(p Packet) error + Nack(p Packet) error +} + +type Component interface { + Register(reg Registration) error + HandleUp(p Packet, an AckNacker, upAdapter Adapter) error + HandleDown(p Packet, an AckNacker, downAdapter Adapter) error +} + +type Adapter interface { + Send(p Packet, r ...Recipient) (Packet, error) + Next() (Packet, AckNacker, error) + NextRegistration() (Registration, AckNacker, error) +} + +type Registration struct { + DevAddr lorawan.DevAddr + Recipient Recipient + Options interface{} +} + +type Router Component +type Broker Component +type Handler Component +type NetworkController Component diff --git a/components/metadata.go b/core/metadata.go similarity index 53% rename from components/metadata.go rename to core/metadata.go index cd121504a..29d704c28 100644 --- a/components/metadata.go +++ b/core/metadata.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package components +package core import ( "encoding/json" @@ -11,27 +11,6 @@ import ( "time" ) -type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) -} - type metadata Metadata // MarshalJSON implements the json.Marshal interface diff --git a/components/metadata_test.go b/core/metadata_test.go similarity index 97% rename from components/metadata_test.go rename to core/metadata_test.go index 4b83937b5..d9752850e 100644 --- a/components/metadata_test.go +++ b/core/metadata_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package components +package core import ( "encoding/json" @@ -77,7 +77,6 @@ var unmarshalTests = []struct { }, } -// The broker can handle an uplink packet func TestMarshaljson(t *testing.T) { for _, test := range commonTests { Desc(t, "Marshal medatadata: %s", test.Metadata.String()) diff --git a/core/packet.go b/core/packet.go new file mode 100644 index 000000000..a44d65446 --- /dev/null +++ b/core/packet.go @@ -0,0 +1,101 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package core + +import ( + "encoding/base64" + "fmt" + "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/core/utils/pointer" + "reflect" + "strings" +) + +var ErrImpossibleConversion error = fmt.Errorf("Illegal attempt to convert a packet") + +// DevAddr return a lorawan device address associated to the packet if any +func (p Packet) DevAddr() (lorawan.DevAddr, error) { + if p.Payload.MACPayload == nil { + return lorawan.DevAddr{}, fmt.Errorf("lorawan: MACPayload should not be empty") + } + + macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return lorawan.DevAddr{}, fmt.Errorf("lorawan: unable to get address of a join message") + } + + return macpayload.FHDR.DevAddr, nil +} + +// String returns a string representation of the packet +func (p Packet) String() string { + str := "Packet {" + str += fmt.Sprintf("\n\t%s}", p.Metadata.String()) + str += fmt.Sprintf("\n\tPayload%+v\n}", p.Payload) + return str +} + +// ConvertRXPK create a core.Packet from a semtech.RXPK. It's an handy way to both decode the +// frame payload and retrieve associated metadata from that packet +func ConvertRXPK(p semtech.RXPK) (Packet, error) { + packet := Packet{} + if p.Data == nil { + return packet, ErrImpossibleConversion + } + + encoded := *p.Data + switch len(encoded) % 4 { + case 2: + encoded += "==" + case 3: + encoded += "=" + } + + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return packet, err + } + + payload := lorawan.NewPHYPayload(true) + if err = payload.UnmarshalBinary(raw); err != nil { + return packet, err + } + + metadata := Metadata{} + rxpkValue := reflect.ValueOf(p) + rxpkStruct := rxpkValue.Type() + metas := reflect.ValueOf(&metadata).Elem() + for i := 0; i < rxpkStruct.NumField(); i += 1 { + field := rxpkStruct.Field(i).Name + if metas.FieldByName(field).CanSet() { + metas.FieldByName(field).Set(rxpkValue.Field(i)) + } + } + + return Packet{Metadata: metadata, Payload: payload}, nil +} + +// ConvertToTXPK converts a core Packet to a semtech TXPK packet using compatible metadata. +func ConvertToTXPK(p Packet) (semtech.TXPK, error) { + raw, err := p.Payload.MarshalBinary() + if err != nil { + return semtech.TXPK{}, ErrImpossibleConversion + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + + txpk := semtech.TXPK{Data: pointer.String(data)} + + metadataValue := reflect.ValueOf(p.Metadata).Elem() + metadataStruct := metadataValue.Type() + txpkStruct := reflect.ValueOf(&txpk).Elem() + for i := 0; i < metadataStruct.NumField(); i += 1 { + field := metadataStruct.Field(i).Name + if txpkStruct.FieldByName(field).CanSet() { + txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) + } + } + + return txpk, nil +} diff --git a/core_def.go b/core_def.go deleted file mode 100644 index 779936af1..000000000 --- a/core_def.go +++ /dev/null @@ -1,50 +0,0 @@ -package core - -import ( - "encoding/json" - "github.com/thethingsnetwork/core/lorawan" -) - -type Packet struct { - Metadata Metadata - Payload lorawan.PHYPayload -} - -type Recipient struct { - Address interface{} - Id interface{} -} - -type Metadata interface { - json.Marshaler - json.Unmarshaler - String() string -} - -type AckNacker interface { - Ack(p Packet) error - Nack(p Packet) error -} - -type Component interface { - Register(reg Registration) error - HandleUp(p Packet, an AckNacker, upAdapter Adapter) error - HandleDown(p Packet, an AckNacker, downAdapter Adapter) error -} - -type Adapter interface { - Send(p Packet, r ...Recipient) (Packet, error) - Next() (Packet, AckNacker, error) - NextRegistration() (Registration, AckNacker, error) -} - -type Registration struct { - DevAddr lorawan.DevAddr - Recipient Recipient - Options interface{} -} - -type Router Component -type Broker Component -type Handler Component -type NetworkController Component From 5b09104c7a9542590f686afca04d61633927807c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 13:33:34 +0100 Subject: [PATCH 0366/2266] Move packet & packet_test to core folder --- core/components/packet.go | 80 ---------------------------- core/{components => }/packet_test.go | 21 ++++---- 2 files changed, 10 insertions(+), 91 deletions(-) delete mode 100644 core/components/packet.go rename core/{components => }/packet_test.go (86%) diff --git a/core/components/packet.go b/core/components/packet.go deleted file mode 100644 index df018545e..000000000 --- a/core/components/packet.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "encoding/base64" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" - "reflect" - "strings" -) - -// ConvertRXPK create a core.Packet from a semtech.RXPK. It's an handy way to both decode the -// frame payload and retrieve associated metadata from that packet -func ConvertRXPK(p semtech.RXPK) (core.Packet, error) { - packet := core.Packet{} - if p.Data == nil { - return packet, ErrImpossibleConversion - } - - encoded := *p.Data - switch len(encoded) % 4 { - case 2: - encoded += "==" - case 3: - encoded += "=" - } - - raw, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return packet, err - } - - payload := lorawan.NewPHYPayload(true) - if err = payload.UnmarshalBinary(raw); err != nil { - return packet, err - } - - metadata := Metadata{} - rxpkValue := reflect.ValueOf(p) - rxpkStruct := rxpkValue.Type() - metas := reflect.ValueOf(&metadata).Elem() - for i := 0; i < rxpkStruct.NumField(); i += 1 { - field := rxpkStruct.Field(i).Name - if metas.FieldByName(field).CanSet() { - metas.FieldByName(field).Set(rxpkValue.Field(i)) - } - } - - return core.Packet{Metadata: &metadata, Payload: payload}, nil -} - -// ConvertToTXPK converts a core Packet to a semtech TXPK packet using compatible metadata. -func ConvertToTXPK(p core.Packet) (semtech.TXPK, error) { - raw, err := p.Payload.MarshalBinary() - if err != nil { - return semtech.TXPK{}, ErrImpossibleConversion - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - - txpk := semtech.TXPK{Data: pointer.String(data)} - if p.Metadata == nil { - return txpk, nil - } - - metadataValue := reflect.ValueOf(p.Metadata).Elem() - metadataStruct := metadataValue.Type() - txpkStruct := reflect.ValueOf(&txpk).Elem() - for i := 0; i < metadataStruct.NumField(); i += 1 { - field := metadataStruct.Field(i).Name - if txpkStruct.FieldByName(field).CanSet() { - txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) - } - } - - return txpk, nil -} diff --git a/core/components/packet_test.go b/core/packet_test.go similarity index 86% rename from core/components/packet_test.go rename to core/packet_test.go index c1529710e..703e579d6 100644 --- a/core/components/packet_test.go +++ b/core/packet_test.go @@ -1,10 +1,9 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package components +package core import ( - "github.com/thethingsnetwork/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" @@ -46,14 +45,14 @@ func TestConvertTXPK(t *testing.T) { // ---- Declaration type convertRXPKTest struct { - CorePacket core.Packet + CorePacket Packet RXPK semtech.RXPK WantError error } type convertToTXPKTest struct { TXPK semtech.TXPK - CorePacket core.Packet + CorePacket Packet WantError error } @@ -64,7 +63,7 @@ func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) metadata := genMetadata(rxpk) - test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} test.RXPK = rxpk return *test } @@ -79,7 +78,7 @@ func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { rxpk.Time = nil rxpk.Size = nil metadata := genMetadata(rxpk) - test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} test.RXPK = rxpk return *test } @@ -101,7 +100,7 @@ func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { metadata.Rssi = nil metadata.Stat = nil test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} return *test } @@ -110,7 +109,7 @@ func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { phyPayload := genPHYPayload(false) metadata := Metadata{} test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} return *test } @@ -126,7 +125,7 @@ func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { metadata.Fdev = nil metadata.Time = nil test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} return *test } @@ -135,7 +134,7 @@ func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { phyPayload := genPHYPayload(false) metadata := genFullMetadata() test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.Packet{Metadata: &metadata, Payload: phyPayload} + test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} return *test } @@ -143,6 +142,6 @@ func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { func genCoreNoPayload(test *convertToTXPKTest) convertToTXPKTest { metadata := genFullMetadata() test.TXPK = semtech.TXPK{} - test.CorePacket = core.Packet{Metadata: &metadata, Payload: lorawan.PHYPayload{}} + test.CorePacket = Packet{Metadata: metadata, Payload: lorawan.PHYPayload{}} return *test } From 670492e388395454c639c0270d664f88bd69ef65 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 14:07:10 +0100 Subject: [PATCH 0367/2266] Write tests for packet.MarshalJSON --- core/check_utilities_test.go | 14 ++++++++++++ core/packet_test.go | 42 ++++++++++++++++++++++++++++-------- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go index 0434987fa..410b6349d 100644 --- a/core/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -4,10 +4,12 @@ package core import ( + "fmt" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" . "github.com/thethingsnetwork/core/utils/testing" "reflect" + "regexp" "testing" ) @@ -57,3 +59,15 @@ func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { } Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) } + +// Check that obtained json strings contains the required field +func checkFields(t *testing.T, want []string, got []byte) { + for _, field := range want { + ok, err := regexp.Match(fmt.Sprintf("\"%s\":", field), got) + if !ok || err != nil { + Ko(t, "Expected field %s in %s", field, string(got)) + return + } + } + Ok(t, "Check fields") +} diff --git a/core/packet_test.go b/core/packet_test.go index 703e579d6..2ba605b6e 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -4,6 +4,7 @@ package core import ( + "encoding/json" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" @@ -32,7 +33,11 @@ func TestConvertTXPK(t *testing.T) { genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), genCoreExtraMetadata(&convertToTXPKTest{WantError: nil}), genCoreNoMetadata(&convertToTXPKTest{WantError: nil}), - genCoreNoPayload(&convertToTXPKTest{WantError: ErrImpossibleConversion}), + convertToTXPKTest{ + CorePacket: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, + TXPK: semtech.TXPK{}, + WantError: ErrImpossibleConversion, + }, } for _, test := range tests { @@ -43,6 +48,29 @@ func TestConvertTXPK(t *testing.T) { } } +func TestMarshalJSON(t *testing.T) { + tests := []marshalJSONTest{ + marshalJSONTest{ // Empty Payload + Packet: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, + WantFields: []string{}, + }, + marshalJSONTest{ // Empty Metadata + Packet: Packet{Metadata: Metadata{}, Payload: genPHYPayload(true)}, + WantFields: []string{"payload", "metadata"}, + }, + marshalJSONTest{ // With Metadata and Payload + Packet: Packet{Metadata: genFullMetadata(), Payload: genPHYPayload(true)}, + WantFields: []string{"payload", "metadata"}, + }, + } + + for _, test := range tests { + Desc(t, "Marshal packet to json: %s", test.Packet.String()) + raw, _ := json.Marshal(test.Packet) + checkFields(t, test.WantFields, raw) + } +} + // ---- Declaration type convertRXPKTest struct { CorePacket Packet @@ -55,6 +83,10 @@ type convertToTXPKTest struct { CorePacket Packet WantError error } +type marshalJSONTest struct { + Packet Packet + WantFields []string +} // ---- Build utilities @@ -137,11 +169,3 @@ func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} return *test } - -// Generates a test suite where the core packet has no payload -func genCoreNoPayload(test *convertToTXPKTest) convertToTXPKTest { - metadata := genFullMetadata() - test.TXPK = semtech.TXPK{} - test.CorePacket = Packet{Metadata: metadata, Payload: lorawan.PHYPayload{}} - return *test -} From 1fb3622297c9c62eca0cb5a57db699ce2d3425e9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 14:16:00 +0100 Subject: [PATCH 0368/2266] Write tests for packet.UnmarshalJSON --- core/packet_test.go | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/core/packet_test.go b/core/packet_test.go index 2ba605b6e..1d043a362 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -5,6 +5,7 @@ package core import ( "encoding/json" + "fmt" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" @@ -12,7 +13,7 @@ import ( "testing" ) -func TestConvertRXPK(t *testing.T) { +func TestConvertRXPKPacket(t *testing.T) { tests := []convertRXPKTest{ genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), @@ -27,7 +28,7 @@ func TestConvertRXPK(t *testing.T) { } } -func TestConvertTXPK(t *testing.T) { +func TestConvertTXPKPacket(t *testing.T) { tests := []convertToTXPKTest{ genCoreFullMetadata(&convertToTXPKTest{WantError: nil}), genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), @@ -48,7 +49,7 @@ func TestConvertTXPK(t *testing.T) { } } -func TestMarshalJSON(t *testing.T) { +func TestMarshalJSONPacket(t *testing.T) { tests := []marshalJSONTest{ marshalJSONTest{ // Empty Payload Packet: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, @@ -67,10 +68,39 @@ func TestMarshalJSON(t *testing.T) { for _, test := range tests { Desc(t, "Marshal packet to json: %s", test.Packet.String()) raw, _ := json.Marshal(test.Packet) + fmt.Println(string(raw)) checkFields(t, test.WantFields, raw) } } +func TestUnmarshalJSONPacket(t *testing.T) { + tests := []unmarshalJSONTest{ + unmarshalJSONTest{ + JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, + WantPacket: Packet{Metadata: Metadata{}, Payload: genPHYPayload(true)}, + }, + unmarshalJSONTest{ + JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{"chan":2,"codr":"4/6","fdev":3,"freq":863.125,"imme":false,"ipol":false,"lsnr":5.2,"modu":"LORA","ncrc":true,"powe":3,"prea":8,"rfch":2,"rssi":-27,"size":14,"stat":0,"tmst":1452690688207288535,"datr":"LORA","time":"2016-01-13T14:11:28.207288421+01:00"}}`, + WantPacket: Packet{Metadata: genFullMetadata(), Payload: genPHYPayload(true)}, + }, + unmarshalJSONTest{ + JSON: `invalid`, + WantPacket: Packet{}, + }, + unmarshalJSONTest{ + JSON: `{"metadata":{}}`, + WantPacket: Packet{}, + }, + } + + for _, test := range tests { + Desc(t, "Unmarshal json to packet: %s", test.JSON) + var packet Packet + json.Unmarshal([]byte(test.JSON), &packet) + checkPackets(t, test.WantPacket, packet) + } +} + // ---- Declaration type convertRXPKTest struct { CorePacket Packet @@ -83,11 +113,17 @@ type convertToTXPKTest struct { CorePacket Packet WantError error } + type marshalJSONTest struct { Packet Packet WantFields []string } +type unmarshalJSONTest struct { + JSON string + WantPacket Packet +} + // ---- Build utilities // Generates a test suite where the RXPK is fully complete From 8345c60ee934eb19ef871077fcb69e0b85dd648b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 14:16:21 +0100 Subject: [PATCH 0369/2266] Implements packet.MarshalJSON() --- core/packet.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/packet.go b/core/packet.go index a44d65446..c1b520e27 100644 --- a/core/packet.go +++ b/core/packet.go @@ -5,6 +5,7 @@ package core import ( "encoding/base64" + "encoding/json" "fmt" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" @@ -87,7 +88,7 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { txpk := semtech.TXPK{Data: pointer.String(data)} - metadataValue := reflect.ValueOf(p.Metadata).Elem() + metadataValue := reflect.ValueOf(p.Metadata) metadataStruct := metadataValue.Type() txpkStruct := reflect.ValueOf(&txpk).Elem() for i := 0; i < metadataStruct.NumField(); i += 1 { @@ -99,3 +100,19 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { return txpk, nil } + +// MarshalJSON implements the json.Marshaler interface +func (p Packet) MarshalJSON() ([]byte, error) { + rawMetadata, err := json.Marshal(p.Metadata) + if err != nil { + return nil, err + } + rawPayload, err := p.Payload.MarshalBinary() + if err != nil { + return nil, err + } + strPayload := base64.StdEncoding.EncodeToString(rawPayload) + return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil +} + +// UnmarshalJSON impements the json.Marshaler interface From 1f1709074a40533d9ed81c86c9b1d0d53f7acbe3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 15:27:57 +0100 Subject: [PATCH 0370/2266] Implement the Unmarshal JSON interface --- core/packet.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/core/packet.go b/core/packet.go index c1b520e27..22990ce18 100644 --- a/core/packet.go +++ b/core/packet.go @@ -116,3 +116,27 @@ func (p Packet) MarshalJSON() ([]byte, error) { } // UnmarshalJSON impements the json.Marshaler interface +func (p *Packet) UnmarshalJSON(raw []byte) error { + if p == nil { + return ErrImpossibleConversion + } + var proxy struct { + Payload string `json:"payload"` + Metadata Metadata + } + err := json.Unmarshal(raw, &proxy) + if err != nil { + return err + } + rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) + if err != nil { + return err + } + payload := new(lorawan.PHYPayload) + if err := payload.UnmarshalBinary(rawPayload); err != nil { + return err + } + p.Payload = *payload + p.Metadata = proxy.Metadata + return nil +} From 1a00c0d75bb5ced71fd6454570a10c2bbb4f7ab9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 16:01:57 +0100 Subject: [PATCH 0371/2266] Update adapter with new folder structure + add packet response to send methods --- core/adapters/http/adapter.go | 22 +++++--- core/adapters/http/broadcast/broadcast.go | 56 ++++++++++++++----- .../adapters/http/broadcast/broadcast_test.go | 30 ++++------ .../adapters/http/broadcast/void_acknacker.go | 2 +- core/adapters/http/pubsub/handler_parser.go | 2 +- core/adapters/http/pubsub/pubsub.go | 4 +- core/adapters/http/pubsub/pubsub_test.go | 2 +- core/adapters/http/pubsub/reg_acknacker.go | 2 +- core/adapters/semtech/adapter.go | 5 +- core/adapters/semtech/adapter_test.go | 4 +- core/adapters/semtech/build_utilities_test.go | 5 +- core/adapters/semtech/semtech_acknacker.go | 5 +- 12 files changed, 81 insertions(+), 58 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 4230a9196..bcf916bdc 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -30,15 +30,15 @@ func NewAdapter(loggers ...log.Logger) (*Adapter, error) { } // Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]core.Packet, error) { +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - return nil, ErrInvalidPacket + return core.Packet{}, ErrInvalidPacket } pl, err := p.Payload.MarshalBinary() if err != nil { - return nil, ErrInvalidPacket + return core.Packet{}, ErrInvalidPacket } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) @@ -89,13 +89,19 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]core.Packet, error errors = append(errors, <-cherr) } if errors != nil { - return nil, fmt.Errorf("Errors: %v", errors) + return core.Packet{}, fmt.Errorf("Errors: %v", errors) } - var packets []core.Packet - for i := 0; i < len(chresp); i += 1 { - packets = append(packets, <-chresp) + + if len(chresp) > 1 { + return core.Packet{}, fmt.Errorf("Several positive answer from servers") + } + select { + case packet := <-chresp: + return packet, nil + default: + return core.Packet{}, fmt.Errorf("Unexpected error. No response packet available") } - return packets, nil + } // Next implements the core.Adapter interface diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 75fe72eea..511b91ccd 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -8,9 +8,10 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core" - httpadapter "github.com/thethingsnetwork/core/adapters/http" + "github.com/thethingsnetwork/core/core" + httpadapter "github.com/thethingsnetwork/core/core/adapters/http" "github.com/thethingsnetwork/core/utils/log" + "io" "net/http" "sync" ) @@ -23,6 +24,7 @@ type Adapter struct { var ErrBadOptions = fmt.Errorf("Bad options provided") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") +var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") func NewAdapter(recipients []core.Recipient, loggers ...log.Logger) (*Adapter, error) { if len(recipients) == 0 { @@ -41,37 +43,38 @@ func NewAdapter(recipients []core.Recipient, loggers ...log.Logger) (*Adapter, e }, nil } -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) error { +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { if len(r) == 0 { return a.broadcast(p) } - err := a.Adapter.Send(p, r...) + packet, err := a.Adapter.Send(p, r...) if err == httpadapter.ErrInvalidPacket { - return ErrInvalidPacket + return core.Packet{}, ErrInvalidPacket } - return err + return packet, err } -func (a *Adapter) broadcast(p core.Packet) error { +func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - return ErrInvalidPacket + return core.Packet{}, ErrInvalidPacket } pl, err := p.Payload.MarshalBinary() if err != nil { - return ErrInvalidPacket + return core.Packet{}, ErrInvalidPacket } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) devAddr, err := p.DevAddr() if err != nil { - return ErrInvalidPacket + return core.Packet{}, ErrInvalidPacket } // Prepare ground for parrallel http request nb := len(a.recipients) cherr := make(chan error, nb) + chresp := make(chan core.Packet, nb) register := make(chan core.Recipient, nb) wg := sync.WaitGroup{} wg.Add(nb) @@ -85,19 +88,32 @@ func (a *Adapter) broadcast(p core.Packet) error { buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) - defer resp.Body.Close() if err != nil { - // Non-blocking, buffered cherr <- err return } + defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: a.Log("Recipient %v registered for given packet", recipient) + + raw := make([]byte, resp.ContentLength) + n, err := resp.Body.Read(raw) + if err != nil && err != io.EOF { + cherr <- err + return + } + var packet core.Packet + if err := json.Unmarshal(raw[:n], &packet); err != nil { + cherr <- err + return + } + register <- recipient + chresp <- packet case http.StatusNotFound: - a.Log("Recipient %v don't care much about packet", recipient) + a.Log("Recipient %v doesn't care much about packet", recipient) default: // Non-blocking, buffered cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) @@ -114,13 +130,23 @@ func (a *Adapter) broadcast(p core.Packet) error { errors = append(errors, err) } if errors != nil { - return fmt.Errorf("Errors: %v", errors) + return core.Packet{}, fmt.Errorf("Errors: %v", errors) + } + + if len(chresp) > 1 { // NOTE We consider several positive responses as an error + return core.Packet{}, ErrSeveralPositiveAnswers } for recipient := range register { a.registrations <- core.Registration{DevAddr: devAddr, Recipient: recipient} } - return nil + + select { + case packet := <-chresp: + return packet, nil + default: + return core.Packet{}, fmt.Errorf("Unexpected error. No response packet available") + } } func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index ea960966a..b5d259499 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -4,11 +4,8 @@ package broadcast import ( - "encoding/base64" "encoding/json" - "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/components" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/utils/log" "github.com/thethingsnetwork/core/utils/pointer" @@ -25,7 +22,7 @@ func TestSend(t *testing.T) { recipients := []core.Recipient{ core.Recipient{Address: "0.0.0.0:3010", Id: "AlwaysReject"}, core.Recipient{Address: "0.0.0.0:3011", Id: "AlwaysAccept"}, - core.Recipient{Address: "0.0.0.0:3012", Id: "AlwaysAccept"}, + core.Recipient{Address: "0.0.0.0:3012", Id: "AlwaysReject"}, core.Recipient{Address: "0.0.0.0:3013", Id: "AlwaysReject"}, } registrations := []core.Registration{ @@ -42,8 +39,8 @@ func TestSend(t *testing.T) { WantPayload string WantError error }{ - { // Send to two recipients a valid packet - Recipients: recipients[1:3], // TODO test with a rejection. Need better error handling + { // Send to recipient a valid packet + Recipients: recipients[1:2], // TODO test with a rejection. Need better error handling Packet: packet, WantRegistrations: []core.Registration{}, WantPayload: payload, @@ -52,7 +49,7 @@ func TestSend(t *testing.T) { { // Broadcast a valid packet Recipients: []core.Recipient{}, Packet: packet, - WantRegistrations: registrations[1:3], + WantRegistrations: registrations[1:2], WantPayload: payload, WantError: nil, }, @@ -88,7 +85,7 @@ func TestSend(t *testing.T) { <-time.After(time.Millisecond * 100) // Operate - err := adapter.Send(test.Packet, test.Recipients...) + _, err := adapter.Send(test.Packet, test.Recipients...) registrations := getRegistrations(adapter, test.WantRegistrations) payloads := getPayloads(servers) @@ -159,7 +156,7 @@ func genMockServer(recipient core.Recipient) chan string { w.Write(nil) case "AlwaysAccept": w.WriteHeader(http.StatusOK) - w.Write(nil) + w.Write(buf[:n]) // TODO, should respond another packet, not the same } go func() { chresp <- string(buf[:n]) }() }) @@ -209,20 +206,17 @@ func genSample() (core.Packet, lorawan.DevAddr, string) { } // 2. Generate a JSON payload received by the server - raw, err := payload.MarshalBinary() - if err != nil { - panic(err) + packet := core.Packet{ + Payload: payload, + Metadata: core.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, } - encoded := base64.StdEncoding.EncodeToString(raw) - metadata := components.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")} - rawMeta, err := json.Marshal(metadata) + jsonPayload, err := json.Marshal(packet) if err != nil { panic(err) } - jsonPayload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, encoded, string(rawMeta)) // 3. Return valuable info for the test - return core.Packet{Payload: payload, Metadata: &metadata}, devAddr, jsonPayload + return packet, devAddr, string(jsonPayload) } // Check utilities diff --git a/core/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go index d19ec8c8d..1ee84f201 100644 --- a/core/adapters/http/broadcast/void_acknacker.go +++ b/core/adapters/http/broadcast/void_acknacker.go @@ -4,7 +4,7 @@ package broadcast import ( - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" ) type voidAckNacker struct{} diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go index e18916e1b..095bf84da 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/pubsub/handler_parser.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "io" "net/http" diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index d65be1def..03b8bef25 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -5,8 +5,8 @@ package pubsub import ( "fmt" - "github.com/thethingsnetwork/core" - httpadapter "github.com/thethingsnetwork/core/adapters/http" + "github.com/thethingsnetwork/core/core" + httpadapter "github.com/thethingsnetwork/core/core/adapters/http" "github.com/thethingsnetwork/core/utils/log" "net/http" ) diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index c105c2291..12f83c059 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -6,7 +6,7 @@ package pubsub import ( "bytes" "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/utils/log" . "github.com/thethingsnetwork/core/utils/testing" diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 2fbd63f12..ed2537280 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -5,7 +5,7 @@ package pubsub import ( "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "net/http" "time" ) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 0adbaeef9..7e275b583 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -5,8 +5,7 @@ package semtech import ( "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/components" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/log" "net" @@ -73,7 +72,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { return core.Packet{}, nil, ErrNotInitialized } msg := <-a.next - packet, err := components.ConvertRXPK(msg.rxpk) + packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { a.Log("Invalid Packet") return core.Packet{}, nil, ErrInvalidPacket diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index cc3eb653d..a11be06e0 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -4,7 +4,7 @@ package semtech import ( - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/semtech" . "github.com/thethingsnetwork/core/utils/testing" "reflect" @@ -22,7 +22,7 @@ func TestSend(t *testing.T) { if err != nil { panic(err) } - err = adapter.Send(core.Packet{}) + _, err = adapter.Send(core.Packet{}) checkErrors(t, ErrNotSupported, err) } diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index 18bef4556..b9c4b786b 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -6,8 +6,7 @@ package semtech import ( "encoding/base64" "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/components" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/log" @@ -94,7 +93,7 @@ func genCorePacket(p semtech.Packet) core.Packet { if p.Payload == nil || len(p.Payload.RXPK) != 1 { panic("Expected a payload with one rxpk") } - packet, err := components.ConvertRXPK(p.Payload.RXPK[0]) + packet, err := core.ConvertRXPK(p.Payload.RXPK[0]) if err != nil { panic(err) } diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index 36993de0d..7b0b35879 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -5,8 +5,7 @@ package semtech import ( "fmt" - "github.com/thethingsnetwork/core" - "github.com/thethingsnetwork/core/components" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/semtech" "net" ) @@ -17,7 +16,7 @@ type semtechAckNacker struct { } func (an semtechAckNacker) Ack(p core.Packet) error { - txpk, err := components.ConvertToTXPK(p) + txpk, err := core.ConvertToTXPK(p) if err != nil { return err } From b38cc1d62c0c708d39bd7b991b354c2989e5bd81 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 16:04:51 +0100 Subject: [PATCH 0372/2266] Update components library with new folder structure + temporary disable UnmarshalJSON test --- core/components/broker.go | 2 +- core/components/router.go | 2 +- core/components/router_storage.go | 2 +- core/packet_test.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/components/broker.go b/core/components/broker.go index a33046f02..6fe038551 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -5,7 +5,7 @@ package components import ( "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/utils/log" ) diff --git a/core/components/router.go b/core/components/router.go index b0cb98c3b..d4b33580d 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -5,7 +5,7 @@ package components import ( "fmt" - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/utils/log" "time" ) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 59d321441..c3f79f269 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -4,7 +4,7 @@ package components import ( - "github.com/thethingsnetwork/core" + "github.com/thethingsnetwork/core/core" "github.com/thethingsnetwork/core/lorawan" "sync" "time" diff --git a/core/packet_test.go b/core/packet_test.go index 1d043a362..1cbf183a1 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -5,7 +5,6 @@ package core import ( "encoding/json" - "fmt" "github.com/thethingsnetwork/core/lorawan" "github.com/thethingsnetwork/core/semtech" "github.com/thethingsnetwork/core/utils/pointer" @@ -68,12 +67,13 @@ func TestMarshalJSONPacket(t *testing.T) { for _, test := range tests { Desc(t, "Marshal packet to json: %s", test.Packet.String()) raw, _ := json.Marshal(test.Packet) - fmt.Println(string(raw)) checkFields(t, test.WantFields, raw) } } func TestUnmarshalJSONPacket(t *testing.T) { + t.Skip("Discussion on github about implementation") + return tests := []unmarshalJSONTest{ unmarshalJSONTest{ JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, From 7ea072fa46f2b20df58e80b17d29591cd2618df5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 16:20:04 +0100 Subject: [PATCH 0373/2266] Rename repo to ttn instead of core --- core/adapters/http/adapter.go | 4 ++-- core/adapters/http/adapter_test.go | 10 +++++----- core/adapters/http/broadcast/broadcast.go | 6 +++--- core/adapters/http/broadcast/broadcast_test.go | 10 +++++----- core/adapters/http/broadcast/void_acknacker.go | 2 +- core/adapters/http/pubsub/handler_parser.go | 4 ++-- core/adapters/http/pubsub/pubsub.go | 6 +++--- core/adapters/http/pubsub/pubsub_test.go | 8 ++++---- core/adapters/http/pubsub/reg_acknacker.go | 2 +- core/adapters/semtech/adapter.go | 6 +++--- core/adapters/semtech/adapter_test.go | 6 +++--- core/adapters/semtech/build_utilities_test.go | 10 +++++----- core/adapters/semtech/semtech_acknacker.go | 4 ++-- core/build_utilities_test.go | 6 +++--- core/check_utilities_test.go | 6 +++--- core/components/broker.go | 6 +++--- core/components/broker_storage.go | 2 +- core/components/router.go | 4 ++-- core/components/router_storage.go | 4 ++-- core/core.go | 2 +- core/metadata.go | 4 ++-- core/metadata_test.go | 4 ++-- core/packet.go | 6 +++--- core/packet_test.go | 8 ++++---- semtech/decode_test.go | 2 +- semtech/encode_test.go | 2 +- simulators/gateway/forwarder.go | 6 +++--- simulators/gateway/forwarder_test.go | 2 +- simulators/gateway/utils.go | 6 +++--- simulators/gateway/utils_test.go | 2 +- 30 files changed, 75 insertions(+), 75 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index bcf916bdc..80b8f72a7 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -8,8 +8,8 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/utils/log" "io" "net/http" "sync" diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index e8b3a1f26..ccf419774 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -7,11 +7,11 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/utils/log" + "github.com/thethingsnetwork/ttn/utils/pointer" + . "github.com/thethingsnetwork/ttn/utils/testing" "io" "net/http" "testing" diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 511b91ccd..a8d418b43 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -8,9 +8,9 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core/core" - httpadapter "github.com/thethingsnetwork/core/core/adapters/http" - "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/ttn/core" + httpadapter "github.com/thethingsnetwork/ttn/core/adapters/http" + "github.com/thethingsnetwork/ttn/utils/log" "io" "net/http" "sync" diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index b5d259499..d966838ea 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -5,11 +5,11 @@ package broadcast import ( "encoding/json" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/utils/log" + "github.com/thethingsnetwork/ttn/utils/pointer" + . "github.com/thethingsnetwork/ttn/utils/testing" "io" "net/http" "reflect" diff --git a/core/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go index 1ee84f201..9d8884873 100644 --- a/core/adapters/http/broadcast/void_acknacker.go +++ b/core/adapters/http/broadcast/void_acknacker.go @@ -4,7 +4,7 @@ package broadcast import ( - "github.com/thethingsnetwork/core/core" + "github.com/thethingsnetwork/ttn/core" ) type voidAckNacker struct{} diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go index 095bf84da..ecccc2cf1 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/pubsub/handler_parser.go @@ -7,8 +7,8 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" "io" "net/http" "regexp" diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 03b8bef25..1130bad4c 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -5,9 +5,9 @@ package pubsub import ( "fmt" - "github.com/thethingsnetwork/core/core" - httpadapter "github.com/thethingsnetwork/core/core/adapters/http" - "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/ttn/core" + httpadapter "github.com/thethingsnetwork/ttn/core/adapters/http" + "github.com/thethingsnetwork/ttn/utils/log" "net/http" ) diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 12f83c059..5d92f7f16 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -6,10 +6,10 @@ package pubsub import ( "bytes" "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/utils/log" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/utils/log" + . "github.com/thethingsnetwork/ttn/utils/testing" "net/http" "reflect" "testing" diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index ed2537280..34c09af58 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -5,7 +5,7 @@ package pubsub import ( "fmt" - "github.com/thethingsnetwork/core/core" + "github.com/thethingsnetwork/ttn/core" "net/http" "time" ) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 7e275b583..9b124e24a 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -5,9 +5,9 @@ package semtech import ( "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/log" "net" ) diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index a11be06e0..58d8d987e 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -4,9 +4,9 @@ package semtech import ( - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/semtech" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/semtech" + . "github.com/thethingsnetwork/ttn/utils/testing" "reflect" "testing" "time" diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index b9c4b786b..2a9ada918 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -6,11 +6,11 @@ package semtech import ( "encoding/base64" "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/log" + "github.com/thethingsnetwork/ttn/utils/pointer" "net" "testing" "time" diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index 7b0b35879..b1d42ade9 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -5,8 +5,8 @@ package semtech import ( "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/semtech" "net" ) diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go index 6f470462a..9bf2fe418 100644 --- a/core/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -5,9 +5,9 @@ package core import ( "encoding/base64" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/pointer" "strings" "time" ) diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go index 410b6349d..3a35cee26 100644 --- a/core/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -5,9 +5,9 @@ package core import ( "fmt" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/pointer" + . "github.com/thethingsnetwork/ttn/utils/testing" "reflect" "regexp" "testing" diff --git a/core/components/broker.go b/core/components/broker.go index 6fe038551..45689209a 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -5,9 +5,9 @@ package components import ( "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/utils/log" ) type Broker struct { diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 8da4ebd1e..edc465b14 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -4,7 +4,7 @@ package components import ( - "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/ttn/lorawan" "sync" ) diff --git a/core/components/router.go b/core/components/router.go index d4b33580d..e226b453f 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -5,8 +5,8 @@ package components import ( "fmt" - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/utils/log" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/utils/log" "time" ) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index c3f79f269..b50108dac 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -4,8 +4,8 @@ package components import ( - "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/ttn/core" + "github.com/thethingsnetwork/ttn/lorawan" "sync" "time" ) diff --git a/core/core.go b/core/core.go index e3f44d6ed..d48a40ee5 100644 --- a/core/core.go +++ b/core/core.go @@ -1,7 +1,7 @@ package core import ( - "github.com/thethingsnetwork/core/lorawan" + "github.com/thethingsnetwork/ttn/lorawan" "time" ) diff --git a/core/metadata.go b/core/metadata.go index 29d704c28..e4872aa4e 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -6,8 +6,8 @@ package core import ( "encoding/json" "fmt" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/pointer" "time" ) diff --git a/core/metadata_test.go b/core/metadata_test.go index d9752850e..96f8b5044 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -5,8 +5,8 @@ package core import ( "encoding/json" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/utils/pointer" + . "github.com/thethingsnetwork/ttn/utils/testing" "testing" "time" ) diff --git a/core/packet.go b/core/packet.go index 22990ce18..eaa2a03ed 100644 --- a/core/packet.go +++ b/core/packet.go @@ -7,9 +7,9 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/pointer" "reflect" "strings" ) diff --git a/core/packet_test.go b/core/packet_test.go index 1cbf183a1..3f06d9730 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -5,10 +5,10 @@ package core import ( "encoding/json" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" - . "github.com/thethingsnetwork/core/utils/testing" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/pointer" + . "github.com/thethingsnetwork/ttn/utils/testing" "testing" ) diff --git a/semtech/decode_test.go b/semtech/decode_test.go index e7b2c7928..123ba79e3 100644 --- a/semtech/decode_test.go +++ b/semtech/decode_test.go @@ -5,7 +5,7 @@ package semtech import ( "bytes" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/utils/pointer" "reflect" "testing" "time" diff --git a/semtech/encode_test.go b/semtech/encode_test.go index e002a1530..b03c507a3 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -7,7 +7,7 @@ import ( "bytes" "errors" "fmt" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/utils/pointer" "io/ioutil" "testing" "time" diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index d58c91dcc..28b969396 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -5,9 +5,9 @@ package gateway import ( "fmt" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/log" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/log" + "github.com/thethingsnetwork/ttn/utils/pointer" "io" "time" ) diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 6e230325b..f0e42d21b 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -6,7 +6,7 @@ package gateway import ( "fmt" . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/ttn/semtech" "io" "testing" "time" diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 51a241fde..4466d4c95 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -8,9 +8,9 @@ import ( "encoding/base64" "encoding/binary" "fmt" - "github.com/thethingsnetwork/core/lorawan" - "github.com/thethingsnetwork/core/semtech" - "github.com/thethingsnetwork/core/utils/pointer" + "github.com/thethingsnetwork/ttn/lorawan" + "github.com/thethingsnetwork/ttn/semtech" + "github.com/thethingsnetwork/ttn/utils/pointer" "math" "math/rand" "strings" diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index ef65b0cf4..7f00b34fa 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -5,7 +5,7 @@ package gateway import ( . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/core/semtech" + "github.com/thethingsnetwork/ttn/semtech" "testing" ) From 2914b1eca6db2837365e4ecbfd99b2eeca670fbb Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 16:27:38 +0100 Subject: [PATCH 0374/2266] Update lorawan submodule dependency --- .gitmodules | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index c411a134d..2341457ef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "lorawan"] - path = lorawan - url = git@github.com:TheThingsNetwork/lorawan + path = lorawan + url = https://github.com/brocaar/lorawan.git + branch = master From 15b9d79595e5e14e6a98759e26695518ac2a5d0c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jan 2016 16:38:01 +0100 Subject: [PATCH 0375/2266] Use Logf instead of Log for formatted logs Fixes `go vet` issues Resolves #10 --- AUTHORS | 1 + core/adapters/http/adapter.go | 2 +- core/adapters/http/broadcast/broadcast.go | 6 ++--- core/adapters/http/pubsub/pubsub.go | 8 +++--- core/adapters/semtech/adapter.go | 26 ++++++++++---------- core/components/broker.go | 2 +- simulators/gateway/forwarder.go | 16 ++++++------ utils/log/log.go | 30 +++++++++++++++++++---- 8 files changed, 56 insertions(+), 35 deletions(-) diff --git a/AUTHORS b/AUTHORS index 3f2e71c87..7d6dc00ba 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ Matthias Benkort +Hylke Visser diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 80b8f72a7..5cf25ca52 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -53,7 +53,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) for _, recipient := range r { go func(recipient core.Recipient) { defer wg.Done() - a.Log("Post to %v", recipient) + a.Logf("Post to %v", recipient) buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index a8d418b43..005f02b62 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -84,7 +84,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { go func(recipient core.Recipient) { defer wg.Done() - a.Log("Post to %v", recipient) + a.Logf("Post to %v", recipient) buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) @@ -96,7 +96,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { switch resp.StatusCode { case http.StatusOK: - a.Log("Recipient %v registered for given packet", recipient) + a.Logf("Recipient %v registered for given packet", recipient) raw := make([]byte, resp.ContentLength) n, err := resp.Body.Read(raw) @@ -113,7 +113,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { register <- recipient chresp <- packet case http.StatusNotFound: - a.Log("Recipient %v doesn't care much about packet", recipient) + a.Logf("Recipient %v doesn't care much about packet", recipient) default: // Non-blocking, buffered cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 1130bad4c..28801e921 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -67,21 +67,21 @@ func (a *Adapter) listenRegistration(port uint) { Addr: fmt.Sprintf("0.0.0.0:%d", port), Handler: serveMux, } - a.Log("Start listening on %d", port) + a.Logf("Start listening on %d", port) err := server.ListenAndServe() - a.Log("HTTP connection lost: %v", err) + a.Logf("HTTP connection lost: %v", err) } // fail logs the given failure and sends an appropriate response to the client func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { - a.Log("registration request rejected: %s", msg) + a.Logf("registration request rejected: %s", msg) w.WriteHeader(http.StatusBadRequest) w.Write([]byte(msg)) } // handle request [PUT] on /end-device/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { - a.Log("Receive new registration request") + a.Logf("Receive new registration request") // Check the http method if req.Method != "PUT" { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 9b124e24a..1c967fa7f 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -45,7 +45,7 @@ func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.Log("Unable to establish the connection: %v", err) + a.Logf("Unable to establish the connection: %v", err) return nil, ErrInvalidPort } @@ -74,7 +74,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { msg := <-a.next packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { - a.Log("Invalid Packet") + a.Logf("Invalid Packet") return core.Packet{}, nil, ErrInvalidPacket } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil @@ -88,19 +88,19 @@ func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { // listen Handle incoming packets and forward them func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() - a.Log("Start listening on %s", conn.LocalAddr()) + a.Logf("Start listening on %s", conn.LocalAddr()) for { buf := make([]byte, 128) n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection - a.Log("Error: %v", err) + a.Logf("Error: %v", err) continue } - a.Log("Incoming datagram %x", buf[:n]) + a.Logf("Incoming datagram %x", buf[:n]) pkt, err := semtech.Unmarshal(buf[:n]) if err != nil { - a.Log("Error: %v", err) + a.Logf("Error: %v", err) continue } @@ -112,10 +112,10 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PULL_ACK, }) if err != nil { - a.Log("Unexpected error while marshaling PULL_ACK: %v", err) + a.Logf("Unexpected error while marshaling PULL_ACK: %v", err) continue } - a.Log("Sending PULL_ACK to %v", addr) + a.Logf("Sending PULL_ACK to %v", addr) a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component pushAck, err := semtech.Marshal(semtech.Packet{ @@ -124,14 +124,14 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PUSH_ACK, }) if err != nil { - a.Log("Unexpected error while marshaling PUSH_ACK: %v", err) + a.Logf("Unexpected error while marshaling PUSH_ACK: %v", err) continue } - a.Log("Sending PUSH_ACK to %v", addr) + a.Logf("Sending PUSH_ACK to %v", addr) a.conn <- udpMsg{addr: addr, raw: pushAck} if pkt.Payload == nil { - a.Log("Inconsistent PUSH_DATA packet %v", pkt) + a.Logf("Inconsistent PUSH_DATA packet %v", pkt) continue } for _, rxpk := range pkt.Payload.RXPK { @@ -141,7 +141,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { } } default: - a.Log("Unexpected packet received. Ignored: %v", pkt) + a.Logf("Unexpected packet received. Ignored: %v", pkt) continue } } @@ -161,7 +161,7 @@ func (a *Adapter) monitorConnection() { if udpConn != nil && msg.raw != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.Log("Unable to send udp message: %+v", err) + a.Logf("Unable to send udp message: %+v", err) } } } diff --git a/core/components/broker.go b/core/components/broker.go index 45689209a..69972820e 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -49,7 +49,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter for _, entry := range entries { ok, err := p.Payload.ValidateMIC(entry.NwsKey) if err != nil { - b.Log("Unexpected error: %v", err) + b.Logf("Unexpected error: %v", err) continue } if ok { diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 28b969396..384b0cb4e 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -80,8 +80,8 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error } // log wraps the Logger.log method, this is nothing more than a shortcut -func (fwd Forwarder) log(format string, a ...interface{}) { - fwd.Logger.Log(format, a...) // NOTE: concurrent-safe ? +func (fwd Forwarder) logf(format string, a ...interface{}) { + fwd.Logger.Logf(format, a...) // NOTE: concurrent-safe ? } // listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. @@ -89,16 +89,16 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { for { buf := make([]byte, 1024) n, err := adapter.Read(buf) - fwd.log("%d bytes received by adapter\n", n) + fwd.logf("%d bytes received by adapter\n", n) if err != nil { - fwd.log("Error: %+v", err) + fwd.logf("Error: %+v", err) fwd.quit <- err return // Connection lost / closed } - fwd.log("Forwarder unmarshals datagram %x\n", buf[:n]) + fwd.logf("Forwarder unmarshals datagram %x\n", buf[:n]) packet, err := semtech.Unmarshal(buf[:n]) if err != nil { - fwd.log("Error: %+v", err) + fwd.logf("Error: %+v", err) continue } @@ -108,7 +108,7 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { case semtech.PULL_RESP: fwd.commands <- command{cmd_RECVDWN, packet} default: - fwd.log("Forwarder ignores contingent packet %+v\n", packet) + fwd.logf("Forwarder ignores contingent packet %+v\n", packet) } } } @@ -118,7 +118,7 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { // This method consumes commands from the channel until it's closed. func (fwd *Forwarder) handleCommands() { for cmd := range fwd.commands { - fwd.log("Fowarder executes command: %v\n", cmd.name) + fwd.logf("Fowarder executes command: %v\n", cmd.name) switch cmd.name { case cmd_ACK: diff --git a/utils/log/log.go b/utils/log/log.go index 60e84ef54..bf27a38f7 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -12,7 +12,8 @@ import ( // Logger is a minimalist interface to represent logger type Logger interface { - Log(format string, a ...interface{}) + Log(format string) + Logf(format string, a ...interface{}) } // DebugLogger can be used in development to display loglines in the console @@ -21,7 +22,14 @@ type DebugLogger struct { } // Log implements the Logger interface -func (l DebugLogger) Log(format string, a ...interface{}) { +func (l DebugLogger) Log(str string) { + fmt.Printf("\033[33m[ %s ]\033[0m ", l.Tag) // Tag printed in yellow + fmt.Print(str) + fmt.Print("\n") +} + +// Logf implements the Logger interface +func (l DebugLogger) Logf(format string, a ...interface{}) { fmt.Printf("\033[33m[ %s ]\033[0m ", l.Tag) // Tag printed in yellow fmt.Printf(format, a...) fmt.Print("\n") @@ -34,7 +42,12 @@ type TestLogger struct { } // Log implements the Logger interface -func (l TestLogger) Log(format string, a ...interface{}) { +func (l TestLogger) Log(str string) { + l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, str) // Tag printed in yellow +} + +// Logf implements the Logger interface +func (l TestLogger) Logf(format string, a ...interface{}) { l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, fmt.Sprintf(format, a...)) // Tag printed in yellow } @@ -44,8 +57,15 @@ type MultiLogger struct { } // Log implements the Logger interface -func (l MultiLogger) Log(format string, a ...interface{}) { +func (l MultiLogger) Log(str string) { + for _, logger := range l.Loggers { + logger.Log(str) + } +} + +// Logf implements the Logger interface +func (l MultiLogger) Logf(format string, a ...interface{}) { for _, logger := range l.Loggers { - logger.Log(format, a...) + logger.Logf(format, a...) } } From 16ac51ecc0037a47d7eb8e0fca045e0db59fb6ac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jan 2016 16:40:24 +0100 Subject: [PATCH 0376/2266] Fix go vet issue with unreachable code --- core/packet_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packet_test.go b/core/packet_test.go index 3f06d9730..df4cd0043 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -73,7 +73,7 @@ func TestMarshalJSONPacket(t *testing.T) { func TestUnmarshalJSONPacket(t *testing.T) { t.Skip("Discussion on github about implementation") - return + tests := []unmarshalJSONTest{ unmarshalJSONTest{ JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, From 63a662bc6605250d230dee8f5f44c02b1b5f7f54 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 17:38:25 +0100 Subject: [PATCH 0377/2266] [refactor.semtech] Remove old tests and start rewritting them --- semtech/decode_test.go | 648 -------------------------- semtech/encode_test.go | 1002 ++++++---------------------------------- 2 files changed, 143 insertions(+), 1507 deletions(-) delete mode 100644 semtech/decode_test.go diff --git a/semtech/decode_test.go b/semtech/decode_test.go deleted file mode 100644 index 123ba79e3..000000000 --- a/semtech/decode_test.go +++ /dev/null @@ -1,648 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "bytes" - "github.com/thethingsnetwork/ttn/utils/pointer" - "reflect" - "testing" - "time" -) - -// ------------------------------------------------------------ -// ------------------------- Unmarshal (raw []byte) (Packet, error) -// ------------------------------------------------------------ - -// Unmarshal() with valid raw data and no payload (PUSH_ACK) -func TestUnmarshal1(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} - packet, err := Unmarshal(raw) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != PUSH_ACK { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload != nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.GatewayId != nil { - t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) - } -} - -// Unmarshal() with valid raw data and stat payload -func TestUnmarshal2(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] - payload := []byte(`{ - "stat": { - "time":"2014-01-12 08:59:28 GMT", - "lati":46.24000, - "long":3.25230, - "alti":145, - "rxnb":2, - "rxok":2, - "rxfw":2, - "ackr":100.0, - "dwnb":2, - "txnb":2 - } - }`) - packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if !bytes.Equal(gatewayId, packet.GatewayId) { - t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) - } - - if packet.Identifier != PUSH_DATA { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.Stat == nil { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } - - statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") - - stat := Stat{ - Time: &statTime, - Lati: pointer.Float64(46.24000), - Long: pointer.Float64(3.25230), - Alti: pointer.Int(145), - Rxnb: pointer.Uint(2), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Ackr: pointer.Float64(100.0), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - } - - if !reflect.DeepEqual(stat, *packet.Payload.Stat) { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } - -} - -// Unmarshal() with valid raw data and rxpk payloads -func TestUnmarshal3(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] - payload := []byte(`{ - "rxpk":[ - { - "chan":2, - "codr":"4/6", - "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", - "datr":"SF7BW125", - "freq":866.349812, - "lsnr":5.1, - "modu":"LORA", - "rfch":0, - "rssi":-35, - "size":32, - "stat":1, - "time":"2013-03-31T16:21:17.528002Z", - "tmst":3512348611 - },{ - "chan":9, - "data":"VEVTVF9QQUNLRVRfMTIzNA==", - "datr":50000, - "freq":869.1, - "modu":"FSK", - "rfch":1, - "rssi":-75, - "size":16, - "stat":1, - "time":"2013-03-31T16:21:17.530974Z", - "tmst":3512348514 - },{ - "chan":0, - "codr":"4/7", - "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass", - "datr":"SF10BW125", - "freq":863.00981, - "lsnr":5.5, - "modu":"LORA", - "rfch":0, - "rssi":-38, - "size":32, - "stat":1, - "time":"2013-03-31T16:21:17.532038Z", - "tmst":3316387610 - } - ] - }`) - - packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != PUSH_DATA { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if !bytes.Equal(gatewayId, packet.GatewayId) { - t.Errorf("Invalid parsed gatewayId: % x", packet.GatewayId) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.RXPK == nil || len(packet.Payload.RXPK) != 3 { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } - - rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") - - rxpk := RXPK{ - Chan: pointer.Uint(9), - Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), - Datr: pointer.String("50000"), - Freq: pointer.Float64(869.1), - Modu: pointer.String("FSK"), - Rfch: pointer.Uint(1), - Rssi: pointer.Int(-75), - Size: pointer.Uint(16), - Stat: pointer.Int(1), - Time: &rxpkTime, - Tmst: pointer.Uint(3512348514), - } - - if !reflect.DeepEqual(rxpk, (packet.Payload.RXPK)[1]) { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } -} - -// Unmarshal() with valid raw data and rxpk payloads + stat -func TestUnmarshal4(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] - payload := []byte(`{ - "rxpk":[ - { - "chan":2, - "codr":"4/6", - "data":"-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", - "datr":"SF7BW125", - "freq":866.349812, - "lsnr":5.1, - "modu":"LORA", - "rfch":0, - "rssi":-35, - "size":32, - "stat":1, - "time":"2013-03-31T16:21:17.528002Z", - "tmst":3512348611 - },{ - "chan":9, - "data":"VEVTVF9QQUNLRVRfMTIzNA==", - "datr":50000, - "freq":869.1, - "modu":"FSK", - "rfch":1, - "rssi":-75, - "size":16, - "stat":1, - "time":"2013-03-31T16:21:17.530974Z", - "tmst":3512348514 - },{ - "chan":0, - "codr":"4/7", - "data":"ysgRl452xNLep9S1NTIg2lomKDxUgn3DJ7DE+b00Ass", - "datr":"SF10BW125", - "freq":863.00981, - "lsnr":5.5, - "modu":"LORA", - "rfch":0, - "rssi":-38, - "size":32, - "stat":1, - "time":"2013-03-31T16:21:17.532038Z", - "tmst":3316387610 - } - ], - "stat": { - "time":"2014-01-12 08:59:28 GMT", - "lati":46.24000, - "long":3.25230, - "alti":145, - "rxnb":2, - "rxok":2, - "rxfw":2, - "ackr":100.0, - "dwnb":2, - "txnb":2 - } - }`) - - packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != PUSH_DATA { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.RXPK == nil || len(packet.Payload.RXPK) != 3 { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } - - if packet.Payload.Stat == nil { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } - - rxpkTime, _ := time.Parse(time.RFC3339, "2013-03-31T16:21:17.530974Z") - - rxpk := RXPK{ - Chan: pointer.Uint(9), - Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), - Datr: pointer.String("50000"), - Freq: pointer.Float64(869.1), - Modu: pointer.String("FSK"), - Rfch: pointer.Uint(1), - Rssi: pointer.Int(-75), - Size: pointer.Uint(16), - Stat: pointer.Int(1), - Time: &rxpkTime, - Tmst: pointer.Uint(3512348514), - } - - if !reflect.DeepEqual(rxpk, (packet.Payload.RXPK)[1]) { - t.Errorf("Invalid parsed payload RXPK: %#v", packet.Payload.RXPK) - } - - statTime, _ := time.Parse(time.RFC3339, "2014-01-12T08:59:28.000Z") - - stat := Stat{ - Time: &statTime, - Lati: pointer.Float64(46.24000), - Long: pointer.Float64(3.25230), - Alti: pointer.Int(145), - Rxnb: pointer.Uint(2), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Ackr: pointer.Float64(100.0), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - } - - if !reflect.DeepEqual(stat, *packet.Payload.Stat) { - t.Errorf("Invalid parsed payload Stat: %#v", packet.Payload.Stat) - } -} - -// Unmarshal() with valid raw data and txpk payload -func TestUnmarshal5(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_RESP} - - payload := []byte(`{ - "txpk":{ - "imme":true, - "freq":864.123456, - "rfch":0, - "powe":14, - "modu":"LORA", - "datr":"SF11BW125", - "codr":"4/6", - "ipol":false, - "size":32, - "data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v" - } - }`) - - packet, err := Unmarshal(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != PULL_RESP { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload == nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - return - } - - if !bytes.Equal(payload, packet.Payload.Raw) { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.Payload.TXPK == nil { - t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) - } - - txpk := TXPK{ - Imme: pointer.Bool(true), - Freq: pointer.Float64(864.123456), - Rfch: pointer.Uint(0), - Powe: pointer.Uint(14), - Modu: pointer.String("LORA"), - Datr: pointer.String("SF11BW125"), - Codr: pointer.String("4/6"), - Ipol: pointer.Bool(false), - Size: pointer.Uint(32), - Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), - } - - if !reflect.DeepEqual(txpk, *packet.Payload.TXPK) { - t.Errorf("Invalid parsed payload TXPK: %#v", packet.Payload.TXPK) - } -} - -// Unmarshal() with an invalid version number -func TestUnmarshal6(t *testing.T) { - raw := []byte{0x00, 0x14, 0x14, PUSH_ACK} - _, err := Unmarshal(raw) - - if err == nil { - t.Errorf("Successfully parsed an incorrect version number") - } -} - -// Unmarshal() with an invalid raw message -func TestUnmarshal7(t *testing.T) { - raw1 := []byte{VERSION} - var raw2 []byte - _, err1 := Unmarshal(raw1) - _, err2 := Unmarshal(raw2) - - if err1 == nil { - t.Errorf("Successfully parsed an raw message") - } - - if err2 == nil { - t.Errorf("Successfully parsed a nil raw message") - } -} - -// Unmarshal() with an invalid identifier -func TestUnmarshal8(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, 0xFF} - _, err := Unmarshal(raw) - - if err == nil { - t.Errorf("Successfully parsed an incorrect identifier") - } -} - -// Unmarshal() with an invalid payload -func TestUnmarshal9(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] - payload := []byte(`wrong`) - _, err := Unmarshal(append(append(raw, gatewayId...), payload...)) - if err == nil { - t.Errorf("Successfully parsed an incorrect payload") - } -} - -// Unmarshal() with an invalid date -func TestUnmarshal10(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_DATA} - gatewayId := []byte("qwerty1234")[0:8] - payload := []byte(`{ - "stat": { - "time":"null", - "lati":46.24000, - "long":3.25230, - "alti":145, - "rxnb":2, - "rxok":2, - "rxfw":2, - "ackr":100.0, - "dwnb":2, - "txnb":2 - } - }`) - _, err := Unmarshal(append(append(raw, gatewayId...), payload...)) - if err == nil { - t.Errorf("Successfully parsed an incorrect payload time") - } -} - -// Unmarshal() with valid raw data but a useless payload -func TestUnmarshal11(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PUSH_ACK} - payload := []byte(`{ - "stat": { - "time":"2014-01-12 08:59:28 GMT", - "lati":46.24000, - "long":3.25230, - "alti":145, - "rxnb":2, - "rxok":2, - "rxfw":2, - "ackr":100.0, - "dwnb":2, - "txnb":2 - } - }`) - packet, err := Unmarshal(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse a valid PUSH_ACK packet") - } - - if packet.Payload != nil { - t.Errorf("Parsed payload on a PUSH_ACK packet") - } -} - -// Unmarshal() with valid raw data but a useless payload -func TestUnmarshal12(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_ACK} - payload := []byte(`{ - "stat": { - "time":"2014-01-12 08:59:28 GMT", - "lati":46.24000, - "long":3.25230, - "alti":145, - "rxnb":2, - "rxok":2, - "rxfw":2, - "ackr":100.0, - "dwnb":2, - "txnb":2 - } - }`) - packet, err := Unmarshal(append(raw, payload...)) - - if err != nil { - t.Errorf("Failed to parse a valid PULL_ACK packet") - } - - if packet.Payload != nil { - t.Errorf("Parsed payload on a PULL_ACK packet") - } -} - -// Unmarshal() with valid raw data but a useless payload -func TestUnmarshal13(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} - gatewayId := []byte("qwerty1234")[0:8] - payload := []byte(`{ - "stat": { - "time":"2014-01-12 08:59:28 GMT", - "lati":46.24000, - "long":3.25230, - "alti":145, - "rxnb":2, - "rxok":2, - "rxfw":2, - "ackr":100.0, - "dwnb":2, - "txnb":2 - } - }`) - packet, err := Unmarshal(append(append(raw, gatewayId...), payload...)) - - if err != nil { - t.Errorf("Failed to parse a valid PULL_DATA packet") - } - - if packet.Payload != nil { - t.Errorf("Parsed payload on a PULL_DATA packet") - } -} - -// Unmarshal() with valid raw data and no payload (PULL_ACK) -func TestUnmarshal14(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_ACK} - packet, err := Unmarshal(raw) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != PULL_ACK { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload != nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if packet.GatewayId != nil { - t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) - } -} - -// Unmarshal() with valid raw data and no payload (PULL_DATA) -func TestUnmarshal15(t *testing.T) { - raw := []byte{VERSION, 0x14, 0x14, PULL_DATA} - gatewayId := []byte("qwerty1234")[0:8] - packet, err := Unmarshal(append(raw, gatewayId...)) - - if err != nil { - t.Errorf("Failed to parse with error: %#v", err) - } - - if packet.Version != VERSION { - t.Errorf("Invalid parsed version: %x", packet.Version) - } - - if !bytes.Equal([]byte{0x14, 0x14}, packet.Token) { - t.Errorf("Invalid parsed token: %x", packet.Token) - } - - if packet.Identifier != PULL_DATA { - t.Errorf("Invalid parsed identifier: %x", packet.Identifier) - } - - if packet.Payload != nil { - t.Errorf("Invalid parsed payload: % x", packet.Payload) - } - - if !bytes.Equal(gatewayId, packet.GatewayId) { - t.Errorf("Invalid parsed gateway id: % x", packet.GatewayId) - } -} diff --git a/semtech/encode_test.go b/semtech/encode_test.go index b03c507a3..dfaa11758 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -4,896 +4,180 @@ package semtech import ( - "bytes" - "errors" - "fmt" "github.com/thethingsnetwork/ttn/utils/pointer" - "io/ioutil" + . "github.com/thethingsnetwork/ttn/utils/testing" + "reflect" "testing" - "time" ) -// ----------------------------------------------------------------- -// ------------------------- Marshal (packet Packet) ([]byte, error) -// ----------------------------------------------------------------- -// ---------- PUSH_DATA -func checkMarshalPUSH_DATA(packet Packet, payload []byte) error { - raw, err := Marshal(packet) - - if err != nil { - return err - } - - if len(raw) < 12 { - return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) - } - - if raw[0] != packet.Version { - return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) - } - - if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) - } - - if raw[3] != packet.Identifier { - return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) - } - - if !bytes.Equal(raw[4:12], packet.GatewayId) { - return errors.New(fmt.Sprintf("Invalid raw gatewayId: % x", raw[4:12])) - } - - if packet.Payload != nil && !bytes.Equal(raw[12:], payload) { - return errors.New(fmt.Sprintf("Invalid raw payload: % x", raw[12:])) - } - - return err -} - -// Marshal a basic push_data packet with Stat payload -func TestMarshalPUSH_DATA1(t *testing.T) { - time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - - // { - // "stat": { - // "ackr": 100, - // "alti": 145, - // "dwnb": 2, - // "lati": 46.24, - // "long": 3.2523, - // "rxfw": 2, - // "rxnb": 2, - // "rxok": 2, - // "time": "2014-01-12 08:59:28 GMT", - // "txnb": 2 - // } - // } - payload, err := ioutil.ReadFile("./test_data/marshal_stat") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &time1, - }, - }, - } - - if err = checkMarshalPUSH_DATA(packet, payload); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Mashal a push_data packet with RXPK payload -func TestMarshalPUSH_DATA2(t *testing.T) { - time1, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") - time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") - - //{ - // "rxpk": [ - // { - // "chan": 2, - // "codr": "4/6", - // "data": "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", - // "datr": "SF7BW125", - // "freq": 866.349812, - // "lsnr": 5.1, - // "modu": "LORA", - // "rfch": 0, - // "rssi": -35, - // "size": 32, - // "stat": 1, - // "time": "2013-03-31T16:21:17.528002Z", - // "tmst": 3512348611 - // }, - // { - // "chan": 9, - // "data": "VEVTVF9QQUNLRVRfMTIzNA==", - // "datr": 50000, - // "freq": 869.1, - // "modu": "FSK", - // "rfch": 1, - // "rssi": -75, - // "size": 16, - // "stat": 1, - // "time": "2013-03-31T16:21:17.530974Z", - // "tmst": 3512348514 - // } - // ] - //} - payload, err := ioutil.ReadFile("./test_data/marshal_rxpk") - - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), - Datr: pointer.String("SF7BW125"), - Freq: pointer.Float64(866.349812), - Lsnr: pointer.Float64(5.1), - Modu: pointer.String("LORA"), - Rfch: pointer.Uint(0), - Rssi: pointer.Int(-35), - Size: pointer.Uint(32), - Stat: pointer.Int(1), - Time: &time1, - Tmst: pointer.Uint(3512348611), - }, - RXPK{ - Chan: pointer.Uint(9), - Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), - Datr: pointer.String("50000"), - Freq: pointer.Float64(869.1), - Modu: pointer.String("FSK"), - Rfch: pointer.Uint(1), - Rssi: pointer.Int(-75), - Size: pointer.Uint(16), - Stat: pointer.Int(1), - Time: &time2, - Tmst: pointer.Uint(3512348514), +func TestMarshalBinary(t *testing.T) { + tests := []struct { + Desc string + Packet Packet + WantError bool + WantHeader [12]byte + WantJSON string + }{ + { + Desc: "Invalid PUSH_DATA, invalid token", + Packet: Packet{ + Version: VERSION, + //No Token + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Chan: pointer.Uint(14), + Codr: pointer.String("4/7"), + Freq: pointer.Float64(873.14), + Rssi: pointer.Int(-42), + }, + }, }, }, + WantError: true, }, - } - - if err = checkMarshalPUSH_DATA(packet, payload); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Mashal a push_data packet with RXPK payload and Stat -func TestMarshalPUSH_DATA3(t *testing.T) { - time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - time2, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.528002Z") - time3, err := time.Parse(time.RFC3339Nano, "2013-03-31T16:21:17.530974Z") - - // { - // "rxpk": [ - // { - // "chan": 2, - // "codr": "4/6", - // "data": "-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84", - // "datr": "SF7BW125", - // "freq": 866.349812, - // "lsnr": 5.1, - // "modu": "LORA", - // "rfch": 0, - // "rssi": -35, - // "size": 32, - // "stat": 1, - // "time": "2013-03-31T16:21:17.528002Z", - // "tmst": 3512348611 - // }, - // { - // "chan": 9, - // "data": "VEVTVF9QQUNLRVRfMTIzNA==", - // "datr": 50000, - // "freq": 869.1, - // "modu": "FSK", - // "rfch": 1, - // "rssi": -75, - // "size": 16, - // "stat": 1, - // "time": "2013-03-31T16:21:17.530974Z", - // "tmst": 3512348514 - // } - // ], - // "stat": { - // "ackr": 100, - // "alti": 145, - // "dwnb": 2, - // "lati": 46.24, - // "long": 3.2523, - // "rxfw": 2, - // "rxnb": 2, - // "rxok": 2, - // "time": "2014-01-12 08:59:28 GMT", - // "txnb": 2 - // } - // } - payload, err := ioutil.ReadFile("./test_data/marshal_rxpk_stat") - - if err != nil { - t.Errorf("Unexpected error: %v", err) - return - } - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &time1, - }, - RXPK: []RXPK{ - RXPK{ - Time: &time2, - Tmst: pointer.Uint(3512348611), - Chan: pointer.Uint(2), - Rfch: pointer.Uint(0), - Freq: pointer.Float64(866.349812), - Stat: pointer.Int(1), - Modu: pointer.String("LORA"), - Datr: pointer.String("SF7BW125"), - Codr: pointer.String("4/6"), - Rssi: pointer.Int(-35), - Lsnr: pointer.Float64(5.1), - Size: pointer.Uint(32), - Data: pointer.String("-DS4CGaDCdG+48eJNM3Vai-zDpsR71Pn9CPA9uCON84"), - }, - RXPK{ - Chan: pointer.Uint(9), - Data: pointer.String("VEVTVF9QQUNLRVRfMTIzNA=="), - Datr: pointer.String("50000"), - Freq: pointer.Float64(869.1), - Modu: pointer.String("FSK"), - Rfch: pointer.Uint(1), - Rssi: pointer.Int(-75), - Size: pointer.Uint(16), - Stat: pointer.Int(1), - Time: &time3, - Tmst: pointer.Uint(3512348514), + { + Desc: "Invalid PUSH_DATA, invalid gateway id", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + // No Gateway id + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Chan: pointer.Uint(14), + Codr: pointer.String("4/7"), + Freq: pointer.Float64(873.14), + Rssi: pointer.Int(-42), + }, + }, }, }, + WantError: true, }, - } - - if err = checkMarshalPUSH_DATA(packet, payload); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal with an invalid GatewayId (too short) -func TestMarshalPUSH_DATA4(t *testing.T) { - time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - - // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} - payload, err := ioutil.ReadFile("./test_data/marshal_stat") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &time1, + { + Desc: "PUSH_DATA with no payload", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{}`, }, - } - - if err = checkMarshalPUSH_DATA(packet, payload); err == nil { - t.Errorf("Successfully mashalled a invalid packet") - } -} - -// Marshal with an invalid GatewayId (too long) -func TestMarshalPUSH_DATA5(t *testing.T) { - time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - - // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} - payload, err := ioutil.ReadFile("./test_data/marshal_stat") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &time1, + { + Desc: "PUSH_DATA with only basic typed-attributes uint, string, float64 and int", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Chan: pointer.Uint(14), + Codr: pointer.String("4/7"), + Freq: pointer.Float64(873.14), + Rssi: pointer.Int(-42), + }, + }, + }, }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`, }, - } - - if err = checkMarshalPUSH_DATA(packet, payload); err == nil { - t.Errorf("Successfully mashalled a invalid packet") - } -} - -// Marshal with an invalid TokenId (too short) -func TestMarshalPUSH_DATA6(t *testing.T) { - time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - - // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} - payload, err := ioutil.ReadFile("./test_data/marshal_stat") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &time1, + { + Desc: "PUSH_DATA with datr field and modu -> LORA", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("LORA"), + }, + }, + }, }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`, }, - } - - if err = checkMarshalPUSH_DATA(packet, payload); err == nil { - t.Errorf("Successfully mashalled a invalid packet") - } -} - -// Marshal with an invalid TokenId (too long) -func TestMarshalPUSH_DATA7(t *testing.T) { - time1, err := time.Parse(time.RFC3339, "2014-01-12T08:59:28Z") - - // {"stat":{"ackr":100,"alti":145,"dwnb":2,"lati":46.24,"long":3.2523,"rxfw":2,"rxnb":2,"rxok":2,"time":"2014-01-12 08:59:28 GMT","txnb":2}} - payload, err := ioutil.ReadFile("./test_data/marshal_stat") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14, 0x28}, - Identifier: PUSH_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, // Invalid - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float64(100.0), - Alti: pointer.Int(145), - Long: pointer.Float64(3.25230), - Rxok: pointer.Uint(2), - Rxfw: pointer.Uint(2), - Rxnb: pointer.Uint(2), - Lati: pointer.Float64(46.24), - Dwnb: pointer.Uint(2), - Txnb: pointer.Uint(2), - Time: &time1, + { + Desc: "PUSH_DATA with datr field and modu -> FSK", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Datr: pointer.String("50000"), + Modu: pointer.String("FSK"), + }, + }, + }, }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"modu":"FSK","datr":50000}]}`, }, } - if err = checkMarshalPUSH_DATA(packet, payload); err == nil { - t.Errorf("Successfully mashalled a invalid packet") + for _, test := range tests { + Desc(t, test.Desc) + raw, err := test.Packet.MarshalBinary() + checkErrors(t, test.WantError, err) + if test.WantError { + continue + } + checkHeaders(t, test.WantHeader, raw) + checkJSON(t, test.WantJSON, raw) } } -// ---------- PUSH_ACK -func checkMarshalACK(packet Packet) error { - raw, err := Marshal(packet) - - if err != nil { - return err - } - - if len(raw) != 4 { - return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) - } - - if raw[0] != packet.Version { - return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) - } - - if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) - } - - if raw[3] != packet.Identifier { - return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) - } - - return err -} - -// Marshal a basic push_ack packet -func TestMarshalPUSH_ACK1(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: nil, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a push_ack packet with extra useless gatewayId -func TestMarshalPUSH_ACK2(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a push_ack packet with extra useless Payload -func TestMarshalPUSH_ACK3(t *testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: pointer.Uint(14), - Rxnb: pointer.Uint(14), - Rxok: pointer.Uint(14), - }, - } - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: payload, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a push_ack with extra useless gatewayId and payload -func TestMarshalPUSH_ACK4(t *testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: pointer.Uint(14), - Rxnb: pointer.Uint(14), - Rxok: pointer.Uint(14), - }, - } - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PUSH_ACK, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: payload, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} +// ----- Check utilities -// Marshal a push_ack with an invalid token (too short) -func TestMarshalPUSH_ACK5(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: nil, - } - _, err := Marshal(packet) - if err == nil { - t.Errorf("Successfully marshalled an invalid packet") - } -} - -// Marshal a push_ack with an invalid token (too long) -func TestMarshalPUSH_ACK6(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0x9A, 0x7A, 0x7E}, - Identifier: PUSH_ACK, - GatewayId: nil, - Payload: nil, - } - _, err := Marshal(packet) - if err == nil { - t.Errorf("Successfully marshalled an invalid packet") - } -} - -// ---------- PULL_DATA -func checkMarshalPULL_DATA(packet Packet) error { - raw, err := Marshal(packet) - - if err != nil { - return err - } - - if len(raw) != 12 { - return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) - } - - if raw[0] != packet.Version { - return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) - } - - if !bytes.Equal(raw[1:3], packet.Token) { - return errors.New(fmt.Sprintf("Invalid raw token: %x", raw[1:3])) - } - - if raw[3] != packet.Identifier { - return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) - } - - if !bytes.Equal(raw[4:12], packet.GatewayId) { - return errors.New(fmt.Sprintf("Invalid raw gatewayId: % x", raw[4:12])) - } - - return err -} - -// Marshal a basic pull_data packet -func TestMarshalPULL_DATA1(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalPULL_DATA(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a pull_data packet with an extra useless Payload -func TestMarshalPULL_DATA2(t *testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: pointer.Uint(14), - Rxnb: pointer.Uint(14), - Rxok: pointer.Uint(14), - }, - } - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: payload, - } - if err := checkMarshalPULL_DATA(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a pull_data packet with an invalid token (too short) -func TestMarshalPULL_DATA3(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA}, - Identifier: PULL_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalPULL_DATA(packet); err == nil { - t.Errorf("Successfully marshalled a packet with an invalid token") - } -} - -// Marshal a pull_data packet with an invalid token (too long) -func TestMarshalPULL_DATA4(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14, 0x42}, - Identifier: PULL_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalPULL_DATA(packet); err == nil { - t.Errorf("Successfully marshalled a packet with an invalid token") - } -} - -// Marshal a pull_data packet with an invalid gatewayId (too short) -func TestMarshalPULL_DATA5(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalPULL_DATA(packet); err == nil { - t.Errorf("Successfully marshalled a packet with an invalid gatewayId") - } -} - -// Marshal a pull_data packet with an invalid gatewayId (too long) -func TestMarshalPULL_DATA6(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_DATA, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalPULL_DATA(packet); err == nil { - t.Errorf("Successfully marshalled a packet with an invalid gatewayId") - } -} - -// ---------- PULL_ACK -// Marshal a basic pull_ack packet -func TestMarshalPULL_ACK1(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_ACK, - GatewayId: nil, - Payload: nil, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a pull_ack packet with extra useless gatewayId -func TestMarshalPULL_ACK2(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_ACK, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: nil, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a pull_ack packet with extra useless Payload -func TestMarshalPULL_ACK3(t *testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: pointer.Uint(14), - Rxnb: pointer.Uint(14), - Rxok: pointer.Uint(14), - }, - } - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_ACK, - GatewayId: nil, - Payload: payload, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a pull_ack with extra useless gatewayId and payload -func TestMarshalPULL_ACK4(t *testing.T) { - payload := &Payload{ - Stat: &Stat{ - Rxfw: pointer.Uint(14), - Rxnb: pointer.Uint(14), - Rxok: pointer.Uint(14), - }, - } - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_ACK, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: payload, - } - if err := checkMarshalACK(packet); err != nil { - t.Errorf("Failed to marshal packet: %v", err) - } -} - -// Marshal a pull_ack with an invalid token (too short) -func TestMarshalPULL_ACK5(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA}, - Identifier: PULL_ACK, - GatewayId: nil, - Payload: nil, - } - _, err := Marshal(packet) - if err == nil { - t.Errorf("Successfully marshalled an invalid packet") - } -} - -// Marshal a pull_ack with an invalid token (too long) -func TestMarshalPULL_ACK6(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0x9A, 0x7A, 0x7E}, - Identifier: PULL_ACK, - GatewayId: nil, - Payload: nil, - } - _, err := Marshal(packet) - if err == nil { - t.Errorf("Successfully marshalled an invalid packet") - } -} - -// ---------- PULL_RESP -func checkMarshalPULL_RESP(packet Packet, payload []byte) error { - raw, err := Marshal(packet) - - if err != nil { - return err - } - - if len(raw) < 4 { - return errors.New(fmt.Sprintf("Invalid raw sequence length: %d", len(raw))) - } - - if raw[0] != packet.Version { - return errors.New(fmt.Sprintf("Invalid raw version: %x", raw[0])) - } - - if raw[3] != packet.Identifier { - return errors.New(fmt.Sprintf("Invalid raw identifier: %x", raw[3])) - } - - if packet.Payload != nil && !bytes.Equal(raw[4:], payload) { - return errors.New(fmt.Sprintf("Invalid raw payload: % x", raw[4:])) - } - - return err -} - -// Marshal() for a basic PULL_RESP packet with no payload -func TestMarshallPULL_RESP1(t *testing.T) { - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_RESP, - GatewayId: nil, - Payload: nil, - } - - if err := checkMarshalPULL_RESP(packet, make([]byte, 0)); err != nil { - t.Errorf("Failed to marshal packet: %v", err) +func checkErrors(t *testing.T, want bool, got error) { + if (!want && got == nil) || (want && got != nil) { + Ok(t, "Check errors") + return } + Ko(t, "Expected no error but got: %v", got) } -// Marshal() for a basic PULL_RESP packet with RXPK payload -func TestMarshallPULL_RESP2(t *testing.T) { - - // { - // "txpk": { - // "codr": "4/6", - // "data": "H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v", - // "datr": "SF11BW125", - // "freq": 864.123456, - // "imme": true, - // "ipol": false, - // "modu": "LORA", - // "powe": 14, - // "rfch": 0, - // "size": 32 - // } - // } - payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_RESP, - GatewayId: nil, - Payload: &Payload{ - TXPK: &TXPK{ - Imme: pointer.Bool(true), - Freq: pointer.Float64(864.123456), - Rfch: pointer.Uint(0), - Powe: pointer.Uint(14), - Modu: pointer.String("LORA"), - Datr: pointer.String("SF11BW125"), - Codr: pointer.String("4/6"), - Ipol: pointer.Bool(false), - Size: pointer.Uint(32), - Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), - }, - }, +func checkHeaders(t *testing.T, want [12]byte, got []byte) { + if len(got) < 12 { + Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got) + return } - - if err = checkMarshalPULL_RESP(packet, payload); err != nil { - t.Errorf("Failed to marshal packet: %v", err) + if !reflect.DeepEqual(want[:], got[:12]) { + Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got[:12]) + return } + Ok(t, "Check Headers") } -// Marshal() for a basic PULL_RESP packet with RXPK payload and useless gatewayId -func TestMarshallPULL_RESP3(t *testing.T) { - //{"txpk":{"codr":"4/6","data":"H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v","datr":"SF11BW125","freq":864.123456,"imme":true,"ipol":false,"modu":"LORA","powe":14,"rfch":0,"size":32}} - payload, err := ioutil.ReadFile("./test_data/marshal_txpk") - - packet := Packet{ - Version: VERSION, - Token: []byte{0xAA, 0x14}, - Identifier: PULL_RESP, - GatewayId: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, - Payload: &Payload{ - TXPK: &TXPK{ - Imme: pointer.Bool(true), - Freq: pointer.Float64(864.123456), - Rfch: pointer.Uint(0), - Powe: pointer.Uint(14), - Modu: pointer.String("LORA"), - Datr: pointer.String("SF11BW125"), - Codr: pointer.String("4/6"), - Ipol: pointer.Bool(false), - Size: pointer.Uint(32), - Data: pointer.String("H3P3N2i9qc4yt7rK7ldqoeCVJGBybzPY5h1Dd7P7p8v"), - }, - }, +func checkJSON(t *testing.T, want string, got []byte) { + l := len([]byte(want)) + if len(got) < l { + Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %v", want, got) + return } - - if err = checkMarshalPULL_RESP(packet, payload); err != nil { - t.Errorf("Failed to marshal packet: %v", err) + str := string(got[len(got)-l:]) + if want != str { + Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %s", want, str) + return } + Ok(t, "Check JSON") } From 18fbd7ff5ecef022feea6ed12f39a32159b16fac Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 17:39:13 +0100 Subject: [PATCH 0378/2266] [refactor.semtech] Rename to MarshalBinary (again breaking everything else :'() and fix marshal issue when payload is empty --- semtech/encode.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/semtech/encode.go b/semtech/encode.go index 561368f4b..7b549da0d 100644 --- a/semtech/encode.go +++ b/semtech/encode.go @@ -10,7 +10,7 @@ import ( ) // Marshal transforms a packet to a sequence of bytes. -func Marshal(packet Packet) ([]byte, error) { +func (packet Packet) MarshalBinary() ([]byte, error) { raw := append(make([]byte, 0), packet.Version) if packet.Identifier == PULL_RESP { @@ -31,12 +31,16 @@ func Marshal(packet Packet) ([]byte, error) { raw = append(raw, packet.GatewayId...) } - if packet.Payload != nil && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { - payload, err := json.Marshal(packet.Payload) - if err != nil { - return nil, err + if packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { + if packet.Payload != nil { + payload, err := json.Marshal(packet.Payload) + if err != nil { + return nil, err + } + raw = append(raw, payload...) + } else { + raw = append(raw, []byte("{}")...) } - raw = append(raw, payload...) } return raw, nil From 70ab7e2cec4d9e62af70dcde57921b461bea3cfb Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 17:51:39 +0100 Subject: [PATCH 0379/2266] [refactor.semtech] Fix typo in semtech package --- semtech/semtech.go | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/semtech/semtech.go b/semtech/semtech.go index 33c5775c5..15c04f05a 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -32,21 +32,21 @@ type RXPK struct { // TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omirtmepty"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional + Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway From d93c6a704addbec68c6d13f39c33306be5ed3c93 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 17:51:48 +0100 Subject: [PATCH 0380/2266] [refactor.semtech] Add some tests to encode --- semtech/encode_test.go | 107 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/semtech/encode_test.go b/semtech/encode_test.go index dfaa11758..10858ae72 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -8,6 +8,7 @@ import ( . "github.com/thethingsnetwork/ttn/utils/testing" "reflect" "testing" + "time" ) func TestMarshalBinary(t *testing.T) { @@ -132,6 +133,112 @@ func TestMarshalBinary(t *testing.T) { WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"modu":"FSK","datr":50000}]}`, }, + { + Desc: "PUSH_DATA with time field", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`, + }, + { + Desc: "PUSH_DATA with several RXPK", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Size: pointer.Uint(14), + }, + RXPK{ + Chan: pointer.Uint(14), + }, + }, + }, + }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"size":14},{"chan":14}]}`, + }, + { + Desc: "PUSH_DATA with several RXPK and Stat(basic fields)", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Size: pointer.Uint(14), + }, + }, + Stat: &Stat{ + Ackr: pointer.Float64(0.78), + Alti: pointer.Int(72), + Rxok: pointer.Uint(42), + }, + }, + }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`, + }, + { + Desc: "PUSH_DATA with Stat(time field)", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + Stat: &Stat{ + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`, + }, + { + Desc: "PUSH_DATA with rxpk and txpk (?)", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Codr: pointer.String("4/7"), + Rssi: pointer.Int(-42), + }, + }, + TXPK: &TXPK{ + Ipol: pointer.Bool(true), + Powe: pointer.Uint(12), + }, + }, + }, + WantError: false, + WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantJSON: `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`, + }, } for _, test := range tests { From e8aa338aeb44a6f69f8640763717d170cc727f8f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 09:30:03 +0100 Subject: [PATCH 0381/2266] Fix case sensitivity with go imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Thank you, OSX, with your case insensitive filesystem 😭 Oh, and I also ran `goimports` --- core/adapters/http/adapter.go | 5 +++-- core/adapters/http/adapter_test.go | 11 ++++++----- core/adapters/http/broadcast/broadcast.go | 7 ++++--- core/adapters/http/broadcast/broadcast_test.go | 11 ++++++----- core/adapters/http/broadcast/void_acknacker.go | 2 +- core/adapters/http/pubsub/handler_parser.go | 5 +++-- core/adapters/http/pubsub/pubsub.go | 7 ++++--- core/adapters/http/pubsub/pubsub_test.go | 9 +++++---- core/adapters/http/pubsub/reg_acknacker.go | 3 ++- core/adapters/semtech/adapter.go | 7 ++++--- core/adapters/semtech/adapter_test.go | 7 ++++--- core/adapters/semtech/build_utilities_test.go | 11 ++++++----- core/adapters/semtech/semtech_acknacker.go | 5 +++-- core/build_utilities_test.go | 7 ++++--- core/check_utilities_test.go | 7 ++++--- core/components/broker.go | 7 ++++--- core/components/broker_storage.go | 3 ++- core/components/router.go | 5 +++-- core/components/router_storage.go | 5 +++-- core/core.go | 3 ++- core/metadata.go | 5 +++-- core/metadata_test.go | 5 +++-- core/packet.go | 7 ++++--- core/packet_test.go | 9 +++++---- semtech/decode_test.go | 3 ++- semtech/encode_test.go | 3 ++- simulators/gateway/forwarder.go | 7 ++++--- simulators/gateway/forwarder_test.go | 5 +++-- simulators/gateway/imitator.go | 2 -- simulators/gateway/utils.go | 7 ++++--- simulators/gateway/utils_test.go | 5 +++-- 31 files changed, 106 insertions(+), 79 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 5cf25ca52..2fd0852e9 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -8,11 +8,12 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/utils/log" "io" "net/http" "sync" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/log" ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index ccf419774..844afb6b9 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -7,15 +7,16 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/utils/log" - "github.com/thethingsnetwork/ttn/utils/pointer" - . "github.com/thethingsnetwork/ttn/utils/testing" "io" "net/http" "testing" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) // Send(p core.Packet, r ...core.Recipient) error diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 005f02b62..4b686863d 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -8,12 +8,13 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/ttn/core" - httpadapter "github.com/thethingsnetwork/ttn/core/adapters/http" - "github.com/thethingsnetwork/ttn/utils/log" "io" "net/http" "sync" + + "github.com/TheThingsNetwork/ttn/core" + httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/log" ) type Adapter struct { diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index d966838ea..8f808c3d3 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -5,16 +5,17 @@ package broadcast import ( "encoding/json" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/utils/log" - "github.com/thethingsnetwork/ttn/utils/pointer" - . "github.com/thethingsnetwork/ttn/utils/testing" "io" "net/http" "reflect" "testing" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) func TestSend(t *testing.T) { diff --git a/core/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go index 9d8884873..a110f9aee 100644 --- a/core/adapters/http/broadcast/void_acknacker.go +++ b/core/adapters/http/broadcast/void_acknacker.go @@ -4,7 +4,7 @@ package broadcast import ( - "github.com/thethingsnetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core" ) type voidAckNacker struct{} diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go index ecccc2cf1..79784f028 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/pubsub/handler_parser.go @@ -7,12 +7,13 @@ import ( "encoding/hex" "encoding/json" "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" "io" "net/http" "regexp" "strings" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" ) type HandlerParser struct{} diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 28801e921..471079511 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -5,10 +5,11 @@ package pubsub import ( "fmt" - "github.com/thethingsnetwork/ttn/core" - httpadapter "github.com/thethingsnetwork/ttn/core/adapters/http" - "github.com/thethingsnetwork/ttn/utils/log" "net/http" + + "github.com/TheThingsNetwork/ttn/core" + httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/log" ) type Adapter struct { diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 5d92f7f16..c23650b43 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -6,14 +6,15 @@ package pubsub import ( "bytes" "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/utils/log" - . "github.com/thethingsnetwork/ttn/utils/testing" "net/http" "reflect" "testing" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/utils/log" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) // NextRegistration() (core.Registration, core.AckNacker, error) diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 34c09af58..0e8a0b0bc 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -5,9 +5,10 @@ package pubsub import ( "fmt" - "github.com/thethingsnetwork/ttn/core" "net/http" "time" + + "github.com/TheThingsNetwork/ttn/core" ) var ErrConnectionLost = fmt.Errorf("Connection has been lost") diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 1c967fa7f..9c1f4ab60 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -5,10 +5,11 @@ package semtech import ( "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/log" "net" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/log" ) type Adapter struct { diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index 58d8d987e..02ec68480 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -4,12 +4,13 @@ package semtech import ( - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/semtech" - . "github.com/thethingsnetwork/ttn/utils/testing" "reflect" "testing" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/semtech" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) func TestNewAdapter(t *testing.T) { diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index 2a9ada918..e64ccc231 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -6,14 +6,15 @@ package semtech import ( "encoding/base64" "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/log" - "github.com/thethingsnetwork/ttn/utils/pointer" "net" "testing" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/TheThingsNetwork/ttn/utils/pointer" ) // ----- build utilities diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index b1d42ade9..8bb30cfcb 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -5,9 +5,10 @@ package semtech import ( "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/semtech" "net" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/semtech" ) type semtechAckNacker struct { diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go index 9bf2fe418..df2ef25a6 100644 --- a/core/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -5,11 +5,12 @@ package core import ( "encoding/base64" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/pointer" "strings" "time" + + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" ) // Generate an RXPK packet using the given payload as Data diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go index 3a35cee26..bda8c38a1 100644 --- a/core/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -5,12 +5,13 @@ package core import ( "fmt" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/pointer" - . "github.com/thethingsnetwork/ttn/utils/testing" "reflect" "regexp" "testing" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) // Checks that two packets match diff --git a/core/components/broker.go b/core/components/broker.go index 69972820e..562ca92e0 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -5,9 +5,10 @@ package components import ( "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/utils/log" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/utils/log" ) type Broker struct { diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index edc465b14..1bc8598ae 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -4,8 +4,9 @@ package components import ( - "github.com/thethingsnetwork/ttn/lorawan" "sync" + + "github.com/TheThingsNetwork/ttn/lorawan" ) type brokerStorage interface { diff --git a/core/components/router.go b/core/components/router.go index e226b453f..6bda20b7d 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -5,9 +5,10 @@ package components import ( "fmt" - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/utils/log" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/log" ) const ( diff --git a/core/components/router_storage.go b/core/components/router_storage.go index b50108dac..c3efba164 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -4,10 +4,11 @@ package components import ( - "github.com/thethingsnetwork/ttn/core" - "github.com/thethingsnetwork/ttn/lorawan" "sync" "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/lorawan" ) type routerStorage interface { diff --git a/core/core.go b/core/core.go index d48a40ee5..7d05f76a7 100644 --- a/core/core.go +++ b/core/core.go @@ -1,8 +1,9 @@ package core import ( - "github.com/thethingsnetwork/ttn/lorawan" "time" + + "github.com/TheThingsNetwork/ttn/lorawan" ) type Packet struct { diff --git a/core/metadata.go b/core/metadata.go index e4872aa4e..b9080a0e8 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -6,9 +6,10 @@ package core import ( "encoding/json" "fmt" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/pointer" "time" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" ) type metadata Metadata diff --git a/core/metadata_test.go b/core/metadata_test.go index 96f8b5044..1edd2994a 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -5,10 +5,11 @@ package core import ( "encoding/json" - "github.com/thethingsnetwork/ttn/utils/pointer" - . "github.com/thethingsnetwork/ttn/utils/testing" "testing" "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) var commonTests = []struct { diff --git a/core/packet.go b/core/packet.go index eaa2a03ed..a250dfaa7 100644 --- a/core/packet.go +++ b/core/packet.go @@ -7,11 +7,12 @@ import ( "encoding/base64" "encoding/json" "fmt" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/pointer" "reflect" "strings" + + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" ) var ErrImpossibleConversion error = fmt.Errorf("Illegal attempt to convert a packet") diff --git a/core/packet_test.go b/core/packet_test.go index df4cd0043..66d7c2e10 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -5,11 +5,12 @@ package core import ( "encoding/json" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/pointer" - . "github.com/thethingsnetwork/ttn/utils/testing" "testing" + + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) func TestConvertRXPKPacket(t *testing.T) { diff --git a/semtech/decode_test.go b/semtech/decode_test.go index 123ba79e3..62d003219 100644 --- a/semtech/decode_test.go +++ b/semtech/decode_test.go @@ -5,10 +5,11 @@ package semtech import ( "bytes" - "github.com/thethingsnetwork/ttn/utils/pointer" "reflect" "testing" "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" ) // ------------------------------------------------------------ diff --git a/semtech/encode_test.go b/semtech/encode_test.go index b03c507a3..6dbb12e4e 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -7,10 +7,11 @@ import ( "bytes" "errors" "fmt" - "github.com/thethingsnetwork/ttn/utils/pointer" "io/ioutil" "testing" "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" ) // ----------------------------------------------------------------- diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 384b0cb4e..6737c5d7e 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -5,11 +5,12 @@ package gateway import ( "fmt" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/log" - "github.com/thethingsnetwork/ttn/utils/pointer" "io" "time" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/TheThingsNetwork/ttn/utils/pointer" ) type Forwarder struct { diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index f0e42d21b..2c4fed4e0 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -5,11 +5,12 @@ package gateway import ( "fmt" - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/ttn/semtech" "io" "testing" "time" + + "github.com/TheThingsNetwork/ttn/semtech" + . "github.com/smartystreets/goconvey/convey" ) type fakeAdapter struct { diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 98759a170..0c3e1b33d 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -2,5 +2,3 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway - -import () diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 4466d4c95..a545684cc 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -8,13 +8,14 @@ import ( "encoding/base64" "encoding/binary" "fmt" - "github.com/thethingsnetwork/ttn/lorawan" - "github.com/thethingsnetwork/ttn/semtech" - "github.com/thethingsnetwork/ttn/utils/pointer" "math" "math/rand" "strings" "time" + + "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" ) func genToken() []byte { diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 7f00b34fa..1ba730596 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -4,9 +4,10 @@ package gateway import ( - . "github.com/smartystreets/goconvey/convey" - "github.com/thethingsnetwork/ttn/semtech" "testing" + + "github.com/TheThingsNetwork/ttn/semtech" + . "github.com/smartystreets/goconvey/convey" ) func TestGenToken(t *testing.T) { From 9013fee7d18a89611585568ebe791a090e9a8a63 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jan 2016 16:48:46 +0100 Subject: [PATCH 0382/2266] Add Travis Configuration --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..296bc16a7 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: go + +go: + - 1.5 + +install: + - git submodule update --init + - go get github.com/brocaar/lorawan + - go get github.com/jacobsa/crypto/cmac + - go get github.com/smartystreets/goconvey/convey + +script: + - go test -v ./... + - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' + - go vet ./... From ba396512c22478956e8c05c8d666492bce5d65e0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 10:17:29 +0100 Subject: [PATCH 0383/2266] Add Travis build status badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e27f4c87d..81c666c0e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ The Things Network ================== -[![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) ![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) From 27e890ae04099b400b719abd2316b4cb97eccc89 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 10:29:49 +0100 Subject: [PATCH 0384/2266] Add Drone Configuration --- .drone.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .drone.yml diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 000000000..36a77feda --- /dev/null +++ b/.drone.yml @@ -0,0 +1,10 @@ +build: + image: golang + commands: + - git submodule update --init + - go get github.com/brocaar/lorawan + - go get github.com/jacobsa/crypto/cmac + - go get github.com/smartystreets/goconvey/convey + - go test -v ./... + - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' + - go vet ./... From 3de68686d184bafffddc9977dc89fcb264c9d955 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 12:55:56 +0100 Subject: [PATCH 0385/2266] Submodule initialization for Travis and Drone --- .drone.yml | 1 - .travis.yml | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 36a77feda..b27959f06 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,7 +1,6 @@ build: image: golang commands: - - git submodule update --init - go get github.com/brocaar/lorawan - go get github.com/jacobsa/crypto/cmac - go get github.com/smartystreets/goconvey/convey diff --git a/.travis.yml b/.travis.yml index 296bc16a7..80802968c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,10 @@ language: go go: - 1.5 +before_install: + - git submodule update --init --recursive + install: - - git submodule update --init - go get github.com/brocaar/lorawan - go get github.com/jacobsa/crypto/cmac - go get github.com/smartystreets/goconvey/convey From 8987c6add76edcd58c86e3dffdbaa4e80873d8c3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 12:59:23 +0100 Subject: [PATCH 0386/2266] Submodule initialization for Drone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This time I'll also stage the added lines 😅 --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index b27959f06..8372cdef7 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,6 +1,7 @@ build: image: golang commands: + - git submodule update --init --recursive - go get github.com/brocaar/lorawan - go get github.com/jacobsa/crypto/cmac - go get github.com/smartystreets/goconvey/convey From 6d4829ba6a960ccb0090fb5148a30192c7b0bf37 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 13:23:41 +0100 Subject: [PATCH 0387/2266] Update AUTHORS file --- AUTHORS | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 7d6dc00ba..96dbd5920 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,2 +1,12 @@ -Matthias Benkort +# This is the official list of The Things Network authors for copyright purposes. +# +# The copyright owners listed in this document agree to release their work under +# the MIT license that can be found in the LICENSE file. +# +# Names should be added to this file as +# Firstname Lastname +# +# Please keep the list sorted. + Hylke Visser +Matthias Benkort From 8cd3852d52a63b52a90a914f1c81e9f9f37999c0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 14:31:39 +0100 Subject: [PATCH 0388/2266] [refactor.semtech] Complete encode tests --- semtech/encode_test.go | 131 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 117 insertions(+), 14 deletions(-) diff --git a/semtech/encode_test.go b/semtech/encode_test.go index 10858ae72..00566bd41 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -16,7 +16,7 @@ func TestMarshalBinary(t *testing.T) { Desc string Packet Packet WantError bool - WantHeader [12]byte + WantHeader []byte WantJSON string }{ { @@ -68,7 +68,7 @@ func TestMarshalBinary(t *testing.T) { GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{}`, }, { @@ -90,7 +90,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`, }, { @@ -110,7 +110,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`, }, { @@ -130,7 +130,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"modu":"FSK","datr":50000}]}`, }, { @@ -149,7 +149,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`, }, { @@ -171,7 +171,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"size":14},{"chan":14}]}`, }, { @@ -195,7 +195,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`, }, { @@ -212,7 +212,7 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`, }, { @@ -236,9 +236,111 @@ func TestMarshalBinary(t *testing.T) { }, }, WantError: false, - WantHeader: [12]byte{1, 0x14, 0x42, 0, 1, 2, 3, 4, 5, 6, 7, 8}, + WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, WantJSON: `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`, }, + { + Desc: "PUSH_ACK valid", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_ACK, + }, + WantError: false, + WantHeader: []byte{1, 0x14, 0x42, PUSH_ACK}, + }, + { + Desc: "PUSH_ACK missing token", + Packet: Packet{ + Version: VERSION, + Identifier: PUSH_ACK, + }, + WantError: true, + }, + { + Desc: "PULL_DATA valid", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PULL_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + }, + WantError: false, + WantHeader: []byte{1, 0x14, 0x42, PULL_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + }, + { + Desc: "PULL_DATA missing token", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + }, + WantError: true, + }, + { + Desc: "PULL_DATA missing gatewayid", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PULL_DATA, + }, + WantError: true, + }, + { + Desc: "PULL_RESP with data", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Ipol: pointer.Bool(true), + Powe: pointer.Uint(12), + }, + }, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{"txpk":{"ipol":true,"powe":12}}`, + }, + { + Desc: "PULL_RESP empty payload", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{}, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{}`, + }, + { + Desc: "PULL_RESP no payload", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{}`, + }, + { + Desc: "PULL_ACK valid", + Packet: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PULL_ACK, + }, + WantError: false, + WantHeader: []byte{1, 0x14, 0x42, PULL_ACK}, + }, + { + Desc: "PULL_ACK missing token", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_ACK, + }, + WantError: true, + }, } for _, test := range tests { @@ -263,13 +365,14 @@ func checkErrors(t *testing.T, want bool, got error) { Ko(t, "Expected no error but got: %v", got) } -func checkHeaders(t *testing.T, want [12]byte, got []byte) { - if len(got) < 12 { +func checkHeaders(t *testing.T, want []byte, got []byte) { + l := len(want) + if len(got) < l { Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got) return } - if !reflect.DeepEqual(want[:], got[:12]) { - Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got[:12]) + if !reflect.DeepEqual(want[:], got[:l]) { + Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got[:l]) return } Ok(t, "Check Headers") From c3893d3d0cbf26c6c43bed4560c70868e53d2f74 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:11:47 +0100 Subject: [PATCH 0389/2266] [refactor.semtech] Move check utilities in a separated file --- semtech/check_utilities.go | 53 +++++++++++++++++++++++++++++ semtech/encode_test.go | 70 +++++++++++++++++--------------------- 2 files changed, 85 insertions(+), 38 deletions(-) create mode 100644 semtech/check_utilities.go diff --git a/semtech/check_utilities.go b/semtech/check_utilities.go new file mode 100644 index 000000000..c64412ecc --- /dev/null +++ b/semtech/check_utilities.go @@ -0,0 +1,53 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package semtech + +import ( + . "github.com/thethingsnetwork/ttn/utils/testing" + "reflect" + "testing" +) + +func checkErrors(t *testing.T, want bool, got error) { + if (!want && got == nil) || (want && got != nil) { + Ok(t, "Check errors") + return + } + Ko(t, "Received error does not match expectation. Got: %v", got) +} + +func checkHeaders(t *testing.T, want []byte, got []byte) { + l := len(want) + if len(got) < l { + Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got) + return + } + if !reflect.DeepEqual(want[:], got[:l]) { + Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got[:l]) + return + } + Ok(t, "Check Headers") +} + +func checkJSON(t *testing.T, want string, got []byte) { + l := len([]byte(want)) + if len(got) < l { + Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %v", want, got) + return + } + str := string(got[len(got)-l:]) + if want != str { + Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %s", want, str) + return + } + Ok(t, "Check JSON") +} + +func checkPackets(t *testing.T, want Packet, got Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", want.String(), got.String()) +} diff --git a/semtech/encode_test.go b/semtech/encode_test.go index 00566bd41..d356a5a9b 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -6,7 +6,6 @@ package semtech import ( "github.com/thethingsnetwork/ttn/utils/pointer" . "github.com/thethingsnetwork/ttn/utils/testing" - "reflect" "testing" "time" ) @@ -302,6 +301,38 @@ func TestMarshalBinary(t *testing.T) { WantHeader: []byte{1, 0, 0, PULL_RESP}, WantJSON: `{"txpk":{"ipol":true,"powe":12}}`, }, + { + Desc: "PULL_RESP with data and time", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Ipol: pointer.Bool(true), + Powe: pointer.Uint(12), + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{"txpk":{"ipol":true,"powe":12,"time":"2016-01-13T17:40:57.000000376Z"}}`, + }, + { + Desc: "PULL_RESP with time only", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{"txpk":{"time":"2016-01-13T17:40:57.000000376Z"}}`, + }, { Desc: "PULL_RESP empty payload", Packet: Packet{ @@ -354,40 +385,3 @@ func TestMarshalBinary(t *testing.T) { checkJSON(t, test.WantJSON, raw) } } - -// ----- Check utilities - -func checkErrors(t *testing.T, want bool, got error) { - if (!want && got == nil) || (want && got != nil) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected no error but got: %v", got) -} - -func checkHeaders(t *testing.T, want []byte, got []byte) { - l := len(want) - if len(got) < l { - Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got) - return - } - if !reflect.DeepEqual(want[:], got[:l]) { - Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got[:l]) - return - } - Ok(t, "Check Headers") -} - -func checkJSON(t *testing.T, want string, got []byte) { - l := len([]byte(want)) - if len(got) < l { - Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %v", want, got) - return - } - str := string(got[len(got)-l:]) - if want != str { - Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %s", want, str) - return - } - Ok(t, "Check JSON") -} From 2b78570be8258dc4b23b07b5146fdd37a9901694 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:12:15 +0100 Subject: [PATCH 0390/2266] [refactor.semtech] Remove character trailing in pointer.DumpPStruct --- utils/pointer/pointer.go | 1 - 1 file changed, 1 deletion(-) diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index fbcf79e63..eacc3f443 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -106,7 +106,6 @@ func DumpPStruct(s interface{}, multiline bool) string { } } - str = str[:len(str)-2] if multiline { str += "\n}" } else { From e4306ff1de3fe967477da7e8f34163f3fa27bf89 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:12:38 +0100 Subject: [PATCH 0391/2266] [refactor.semtech] Add String() method to semtech packets --- semtech/semtech.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/semtech/semtech.go b/semtech/semtech.go index 15c04f05a..8b539514b 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -7,6 +7,8 @@ package semtech import ( + "fmt" + "github.com/thethingsnetwork/ttn/utils/pointer" "time" ) @@ -72,6 +74,39 @@ type Packet struct { Payload *Payload // JSON payload transmitted if any, nil otherwise } +func (p *Packet) String() string { + if p == nil { + return "nil" + } + header := fmt.Sprintf("Version:%x,Token:%x,Identifier:%x,GatewayId:%x", p.Version, p.Token, p.Identifier, p.GatewayId) + if p.Payload == nil { + return fmt.Sprintf("Packet{%s}", header) + } + + var payload string + + if p.Payload.Stat != nil { + payload = fmt.Sprintf("%s,%s", payload, pointer.DumpPStruct(*p.Payload.Stat, false)) + } + + if p.Payload.TXPK != nil { + payload = fmt.Sprintf("%s,%s", payload, pointer.DumpPStruct(*p.Payload.TXPK, false)) + } + + var rxpk string + for _, r := range p.Payload.RXPK { + if rxpk == "" { + rxpk = fmt.Sprintf("%s", pointer.DumpPStruct(r, false)) + } else { + rxpk = fmt.Sprintf("%s,%s", rxpk, pointer.DumpPStruct(r, false)) + } + } + if rxpk != "" { + payload = fmt.Sprintf("%s,Rxpk:[%s]", payload, rxpk) + } + return fmt.Sprintf("Packet{%s,Payload:{%s}}", header, payload) +} + // Payload refers to the JSON payload sent by a gateway or a server. type Payload struct { Raw []byte `json:"-"` // The raw unparsed response From ad2c912b47ef50760a592be910a62500af6c4685 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:12:50 +0100 Subject: [PATCH 0392/2266] [refactor.semtech] Rewrite decode tests --- semtech/decode_test.go | 326 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 semtech/decode_test.go diff --git a/semtech/decode_test.go b/semtech/decode_test.go new file mode 100644 index 000000000..46cc3d44a --- /dev/null +++ b/semtech/decode_test.go @@ -0,0 +1,326 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package semtech + +import ( + "github.com/thethingsnetwork/ttn/utils/pointer" + . "github.com/thethingsnetwork/ttn/utils/testing" + "testing" + "time" +) + +func TestUnmarshalBinary(t *testing.T) { + tests := []struct { + Desc string + JSON string + Header []byte + WantPacket Packet + WantError bool + }{ + { + Desc: "Invalid PUSH_DATA, invalid gateway id", + Header: []byte{VERSION, 1, 2, PUSH_DATA, 1, 4, 5, 6}, + JSON: `{}`, + WantError: true, + }, + { + Desc: "PUSH_DATA with no payload", + Header: []byte{VERSION, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{}, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with only basic typed-attributes uint, string, float64 and int", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Chan: pointer.Uint(14), + Codr: pointer.String("4/7"), + Freq: pointer.Float64(873.14), + Rssi: pointer.Int(-42), + }, + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with datr field and modu -> LORA", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("LORA"), + }, + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with datr field and modu -> FSK", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"modu":"FSK","datr":50000}]}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Datr: pointer.String("50000"), + Modu: pointer.String("FSK"), + }, + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with time field", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with several RXPK", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"size":14},{"chan":14}]}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Size: pointer.Uint(14), + }, + RXPK{ + Chan: pointer.Uint(14), + }, + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with several RXPK and Stat(basic fields)", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Size: pointer.Uint(14), + }, + }, + Stat: &Stat{ + Ackr: pointer.Float64(0.78), + Alti: pointer.Int(72), + Rxok: pointer.Uint(42), + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with Stat(time field)", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + Stat: &Stat{ + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 0, time.UTC)), + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_DATA with rxpk and txpk (?)", + Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + JSON: `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + RXPK{ + Codr: pointer.String("4/7"), + Rssi: pointer.Int(-42), + }, + }, + TXPK: &TXPK{ + Ipol: pointer.Bool(true), + Powe: pointer.Uint(12), + }, + }, + }, + WantError: false, + }, + { + Desc: "PUSH_ACK valid", + Header: []byte{1, 0x14, 0x42, PUSH_ACK}, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PUSH_ACK, + }, + WantError: false, + }, + { + Desc: "PUSH_ACK missing token", + Header: []byte{1, PUSH_ACK}, + WantError: true, + }, + { + Desc: "PULL_DATA valid", + Header: []byte{1, 0x14, 0x42, PULL_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PULL_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + }, + WantError: false, + }, + { + Desc: "PULL_RESP with data", + Header: []byte{1, 0, 0, PULL_RESP}, + JSON: `{"txpk":{"ipol":true,"powe":12}}`, + WantPacket: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Ipol: pointer.Bool(true), + Powe: pointer.Uint(12), + }, + }, + }, + WantError: false, + }, + { + Desc: "PULL_RESP with data and time", + Header: []byte{1, 0, 0, PULL_RESP}, + JSON: `{"txpk":{"ipol":true,"powe":12,"time":"2016-01-13T17:40:57.000000376Z"}}`, + WantPacket: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Ipol: pointer.Bool(true), + Powe: pointer.Uint(12), + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + WantError: false, + }, + { + Desc: "PULL_RESP with time only", + Header: []byte{1, 0, 0, PULL_RESP}, + JSON: `{"txpk":{"time":"2016-01-13T17:40:57.000000376Z"}}`, + WantPacket: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), + }, + }, + }, + WantError: false, + }, + { + Desc: "PULL_RESP empty payload", + Header: []byte{1, 0, 0, PULL_RESP}, + JSON: `{}`, + WantPacket: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{}, + }, + WantError: false, + }, + { + Desc: "PULL_RESP no payload", + Header: []byte{1, 0, 0, PULL_RESP}, + JSON: ``, + WantPacket: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{}, + }, + WantError: false, + }, + { + Desc: "PULL_ACK valid", + Header: []byte{1, 0x14, 0x42, PULL_ACK}, + WantPacket: Packet{ + Version: VERSION, + Token: []byte{0x14, 0x42}, + Identifier: PULL_ACK, + }, + WantError: false, + }, + } + + for _, test := range tests { + Desc(t, test.Desc) + var packet Packet + err := packet.UnmarshalBinary(append(test.Header, []byte(test.JSON)...)) + checkErrors(t, test.WantError, err) + if test.WantError { + continue + } + checkPackets(t, test.WantPacket, packet) + } +} From 5abc8f40b345ce1ac9a7aae874619bd7f4b8a4d2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:13:05 +0100 Subject: [PATCH 0393/2266] [refactor.semtech] Fix errors coming from tests and make them pass --- semtech/decode.go | 65 ++++++++++++++++++++++++++++++----------------- 1 file changed, 41 insertions(+), 24 deletions(-) diff --git a/semtech/decode.go b/semtech/decode.go index 8902857eb..62e160ffa 100644 --- a/semtech/decode.go +++ b/semtech/decode.go @@ -12,46 +12,53 @@ import ( // Unmarshal parses a raw response from a server and turn in into a packet. // Will return an error if the response fields are incorrect. -func Unmarshal(raw []byte) (*Packet, error) { +func (p *Packet) UnmarshalBinary(raw []byte) error { size := len(raw) if size < 4 { - return nil, errors.New("Invalid raw data format") + return errors.New("Invalid raw data format") } - packet := &Packet{ + packet := Packet{ Version: raw[0], Token: raw[1:3], Identifier: raw[3], - GatewayId: nil, - Payload: nil, } if packet.Version != VERSION { - return nil, errors.New("Unreckognized protocol version") + return errors.New("Unreckognized protocol version") } if packet.Identifier > PULL_ACK { - return nil, errors.New("Unreckognized protocol identifier") + return errors.New("Unreckognized protocol identifier") + } + + if packet.Identifier == PULL_RESP { + packet.Token = nil } cursor := 4 if packet.Identifier == PULL_DATA || packet.Identifier == PUSH_DATA { if size < 12 { - return nil, errors.New("Invalid gateway identifier") + return errors.New("Invalid gateway identifier") } packet.GatewayId = raw[cursor:12] cursor = 12 } var err error - if size > cursor && (packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP) { + if packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { packet.Payload = new(Payload) - packet.Payload.Raw = raw[cursor:] - err = json.Unmarshal(raw[cursor:], packet.Payload) + if size > cursor { + err = json.Unmarshal(raw[cursor:], packet.Payload) + } + } + + if err == nil { + *p = packet } - return packet, err + return err } // UnmarshalJSON implements the Unmarshaler interface from encoding/json @@ -86,32 +93,42 @@ func (d *Datrparser) UnmarshalJSON(raw []byte) error { // UnmarshalJSON implements the Unmarshaler interface from encoding/json func (p *Payload) UnmarshalJSON(raw []byte) error { proxy := payloadProxy{ - ProxStat: &statProxy{}, - ProxTXPK: &txpkProxy{}, + ProxStat: new(statProxy), + ProxTXPK: new(txpkProxy), } if err := json.Unmarshal(raw, &proxy); err != nil { return err } - if proxy.ProxStat.Stat != nil { - if proxy.ProxStat.Time != nil { - proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.Value + if proxy.ProxStat.Time != nil { + if proxy.ProxStat.Stat == nil { + proxy.ProxStat.Stat = new(Stat) } - p.Stat = proxy.ProxStat.Stat + proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.Value } + p.Stat = proxy.ProxStat.Stat - if proxy.ProxTXPK.TXPK != nil { - if proxy.ProxTXPK.Time != nil { - proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.Value + if proxy.ProxTXPK.Time != nil { + if proxy.ProxTXPK.TXPK == nil { + proxy.ProxTXPK.TXPK = new(TXPK) } - if proxy.ProxTXPK.Datr != nil { - proxy.ProxTXPK.TXPK.Datr = &proxy.ProxTXPK.Datr.Value + proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.Value + } + + if proxy.ProxTXPK.Datr != nil { + if proxy.ProxTXPK.TXPK == nil { + proxy.ProxTXPK.TXPK = new(TXPK) } - p.TXPK = proxy.ProxTXPK.TXPK + proxy.ProxTXPK.TXPK.Datr = &proxy.ProxTXPK.Datr.Value } + p.TXPK = proxy.ProxTXPK.TXPK + for _, rxpk := range proxy.ProxRXPK { + if rxpk.RXPK == nil { + rxpk.RXPK = new(RXPK) + } if rxpk.Time != nil { rxpk.RXPK.Time = rxpk.Time.Value } From 8726e9ed1c7175404b2b50c73a8e1218103a9dd2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:28:52 +0100 Subject: [PATCH 0394/2266] [refactor.semtech] Change all Marshal and Unmarshal calls to MarshalBinary and UnmarshalBinary --- core/adapters/semtech/adapter.go | 11 ++++++----- core/adapters/semtech/build_utilities_test.go | 6 +++--- core/adapters/semtech/semtech_acknacker.go | 5 +++-- simulators/gateway/forwarder.go | 6 +++--- simulators/gateway/forwarder_test.go | 12 ++++++------ 5 files changed, 21 insertions(+), 19 deletions(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 9b124e24a..9743562dc 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -98,7 +98,8 @@ func (a *Adapter) listen(conn *net.UDPConn) { } a.Log("Incoming datagram %x", buf[:n]) - pkt, err := semtech.Unmarshal(buf[:n]) + pkt := new(semtech.Packet) + err = pkt.UnmarshalBinary(buf[:n]) if err != nil { a.Log("Error: %v", err) continue @@ -106,11 +107,11 @@ func (a *Adapter) listen(conn *net.UDPConn) { switch pkt.Identifier { case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK - pullAck, err := semtech.Marshal(semtech.Packet{ + pullAck, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, Identifier: semtech.PULL_ACK, - }) + }.MarshalBinary() if err != nil { a.Log("Unexpected error while marshaling PULL_ACK: %v", err) continue @@ -118,11 +119,11 @@ func (a *Adapter) listen(conn *net.UDPConn) { a.Log("Sending PULL_ACK to %v", addr) a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component - pushAck, err := semtech.Marshal(semtech.Packet{ + pushAck, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, Identifier: semtech.PUSH_ACK, - }) + }.MarshalBinary() if err != nil { a.Log("Unexpected error while marshaling PUSH_ACK: %v", err) continue diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index 2a9ada918..cc6499f21 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -42,8 +42,8 @@ func genMockServer(port uint) mockServer { if err != nil { panic(err) } - packet, err := semtech.Unmarshal(buf[:n]) - if err != nil { + packet := new(semtech.Packet) + if err = packet.UnmarshalBinary(buf[:n]); err != nil { panic(err) } response <- *packet @@ -55,7 +55,7 @@ func genMockServer(port uint) mockServer { // Send a packet through the udp mock server toward the adapter func (s mockServer) send(p semtech.Packet) semtech.Packet { - raw, err := semtech.Marshal(p) + raw, err := p.MarshalBinary() if err != nil { panic(err) } diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index b1d42ade9..c28092bf7 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -20,11 +20,12 @@ func (an semtechAckNacker) Ack(p core.Packet) error { if err != nil { return err } - raw, err := semtech.Marshal(semtech.Packet{ + + raw, err := semtech.Packet{ Version: semtech.VERSION, Identifier: semtech.PULL_RESP, Payload: &semtech.Payload{TXPK: &txpk}, - }) + }.MarshalBinary() addr, ok := an.recipient.Address.(*net.UDPAddr) if !ok { diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 28b969396..41e2f0efb 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -96,8 +96,8 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { return // Connection lost / closed } fwd.log("Forwarder unmarshals datagram %x\n", buf[:n]) - packet, err := semtech.Unmarshal(buf[:n]) - if err != nil { + packet := new(semtech.Packet) + if err = packet.UnmarshalBinary(buf[:n]); err != nil { fwd.log("Error: %+v", err) continue } @@ -169,7 +169,7 @@ func (fwd Forwarder) Forward(packet semtech.Packet) error { return fmt.Errorf("Unable to forward with identifier %x", packet.Identifier) } - raw, err := semtech.Marshal(packet) + raw, err := packet.MarshalBinary() if err != nil { return err } diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index f0e42d21b..431b7c240 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -116,7 +116,7 @@ func TestForwarder(t *testing.T) { checkValid := func(identifier byte) func() { return func() { pkt := generatePacket(identifier, fwd.Id) - raw, err := semtech.Marshal(pkt) + raw, err := pkt.MarshalBinary() if err != nil { t.Errorf("Unexpected error %+v\n", err) return @@ -169,7 +169,7 @@ func TestForwarder(t *testing.T) { // Simulate an ack and a valid response ack := generatePacket(semtech.PUSH_ACK, id) ack.Token = pkt.Token - raw, err := semtech.Marshal(ack) + raw, err := ack.MarshalBinary() if err != nil { panic(err) } @@ -177,8 +177,8 @@ func TestForwarder(t *testing.T) { // Simulate a resp resp := generatePacket(semtech.PULL_RESP, id) - resp.Token = []byte{0x0, 0x0} - raw, err = semtech.Marshal(resp) + resp.Payload = new(semtech.Payload) + raw, err = resp.MarshalBinary() if err != nil { panic(err) } @@ -202,7 +202,7 @@ func TestForwarder(t *testing.T) { // Simulate a resp resp := generatePacket(semtech.PULL_DATA, id) resp.Token = []byte{0x0, 0x0} - raw, err := semtech.Marshal(resp) + raw, err := resp.MarshalBinary() if err != nil { panic(err) } @@ -261,7 +261,7 @@ func TestForwarder(t *testing.T) { pkt := generatePacket(semtech.PUSH_DATA, id) ack := generatePacket(semtech.PUSH_ACK, id) ack.Token = pkt.Token - raw, err := semtech.Marshal(ack) + raw, err := ack.MarshalBinary() if err != nil { panic(err) } From e476b1cba7db80e690a3e790ac85cd91328b1860 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 13 Jan 2016 16:06:34 +0100 Subject: [PATCH 0395/2266] [integration] update integration folder to reflect new packet structure as well Conflicts: integration/router/main.go --- integration/router/main.go | 82 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 integration/router/main.go diff --git a/integration/router/main.go b/integration/router/main.go new file mode 100644 index 000000000..e0eba960e --- /dev/null +++ b/integration/router/main.go @@ -0,0 +1,82 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + . "github.com/thethingsnetwork/core/core" + "github.com/thethingsnetwork/core/core/adapters/http/broadcast" + "github.com/thethingsnetwork/core/core/adapters/semtech" + "github.com/thethingsnetwork/core/core/components" + "github.com/thethingsnetwork/core/utils/log" + "strconv" + "strings" +) + +func main() { + // Parse options + brokers, udpPort := parseOptions() + + // Instantiate all components + gtwAdapter, err := semtech.NewAdapter(uint(udpPort), log.DebugLogger{Tag: "Gateway Adapter"}) + if err != nil { + panic(err) + } + + brkAdapter, err := broadcast.NewAdapter(brokers, log.DebugLogger{Tag: "Broker Adapter"}) + if err != nil { + panic(err) + } + + router, err := components.NewRouter(log.DebugLogger{Tag: "Router"}) + if err != nil { + panic(err) + } + + // Bring the service to life + go func() { + for { + packet, an, err := gtwAdapter.Next() + if err != nil { + fmt.Println(err) + continue + } + go func(packet Packet, an AckNacker) { + if err := router.HandleUp(packet, an, brkAdapter); err != nil { + fmt.Println(err) + } + }(packet, an) + } + }() +} + +func parseOptions() (brokers []Recipient, udpPort uint64) { + var brokersFlag string + var udpPortFlag string + flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. + For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000 + `) + flag.StringVar(&udpPortFlag, "udpPort", "", "Udp port on which the router should listen to.") + flag.Parse() + + var err error + udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) + if err != nil { + panic(err) + } + if brokersFlag == "" { + panic("Need to provide at least one broker address") + } + + brokersStr := strings.Split(brokersFlag, ",") + for i := range brokersStr { + brokers = append(brokers, Recipient{ + Address: strings.Trim(brokersStr[i], " "), + Id: i, + }) + + } + return +} From 2644aa0dd0e08364bb5731bdee34e653226adca2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 16:49:40 +0100 Subject: [PATCH 0396/2266] [integration] Update old package references --- integration/router/main.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/integration/router/main.go b/integration/router/main.go index e0eba960e..c2a9dc4b1 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -6,11 +6,11 @@ package main import ( "flag" "fmt" - . "github.com/thethingsnetwork/core/core" - "github.com/thethingsnetwork/core/core/adapters/http/broadcast" - "github.com/thethingsnetwork/core/core/adapters/semtech" - "github.com/thethingsnetwork/core/core/components" - "github.com/thethingsnetwork/core/utils/log" + . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" + "github.com/TheThingsNetwork/ttn/core/adapters/semtech" + "github.com/TheThingsNetwork/ttn/core/components" + "github.com/TheThingsNetwork/ttn/utils/log" "strconv" "strings" ) From f223a6a0ce608c6ec89c1ffccd2186ab8bb197be Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 17:08:32 +0100 Subject: [PATCH 0397/2266] [integration] Review Ack and Nack interfaces --- .../adapters/http/broadcast/void_acknacker.go | 4 +-- core/adapters/http/pubsub/reg_acknacker.go | 4 +-- core/adapters/semtech/adapter.go | 4 +-- core/adapters/semtech/semtech_acknacker.go | 10 ++++-- core/components/broker.go | 17 +++++++--- core/components/router.go | 13 +++++-- core/core.go | 6 ++-- integration/router/main.go | 34 +++++++++++++++++++ 8 files changed, 73 insertions(+), 19 deletions(-) diff --git a/core/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go index a110f9aee..98f2f4f86 100644 --- a/core/adapters/http/broadcast/void_acknacker.go +++ b/core/adapters/http/broadcast/void_acknacker.go @@ -10,11 +10,11 @@ import ( type voidAckNacker struct{} // Ack implements the core.AckNacker interface -func (v voidAckNacker) Ack(p core.Packet) error { +func (v voidAckNacker) Ack(p ...core.Packet) error { return nil } // Nack implements the core.AckNacker interface -func (v voidAckNacker) Nack(p core.Packet) error { +func (v voidAckNacker) Nack() error { return nil } diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 0e8a0b0bc..0a514d27a 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -18,7 +18,7 @@ type regAckNacker struct { } // Ack implements the core.Acker interface -func (r regAckNacker) Ack(p core.Packet) error { +func (r regAckNacker) Ack(p ...core.Packet) error { select { case r.response <- regRes{statusCode: http.StatusOK}: return nil @@ -28,7 +28,7 @@ func (r regAckNacker) Ack(p core.Packet) error { } // Nack implements the core.Nacker interface -func (r regAckNacker) Nack(p core.Packet) error { +func (r regAckNacker) Nack() error { select { case r.response <- regRes{ statusCode: http.StatusConflict, diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index a321d5245..2de26bcbd 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -82,8 +82,8 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { } // NextRegistration implements the core.Adapter interface -func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, ErrNotSupported +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + return core.Registration{}, nil, ErrNotSupported } // listen Handle incoming packets and forward them diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index 8642ad8a8..86797d0cd 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -16,8 +16,12 @@ type semtechAckNacker struct { recipient core.Recipient } -func (an semtechAckNacker) Ack(p core.Packet) error { - txpk, err := core.ConvertToTXPK(p) +func (an semtechAckNacker) Ack(p ...core.Packet) error { + if len(p) == 0 { + return nil + } + + txpk, err := core.ConvertToTXPK(p[0]) if err != nil { return err } @@ -36,6 +40,6 @@ func (an semtechAckNacker) Ack(p core.Packet) error { return nil } -func (an semtechAckNacker) Nack(p core.Packet) error { +func (an semtechAckNacker) Nack() error { return nil } diff --git a/core/components/broker.go b/core/components/broker.go index 562ca92e0..01613e52b 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -33,14 +33,16 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter // 1. Lookup for entries for the associated device devAddr, err := p.DevAddr() if err != nil { + an.Nack() return ErrInvalidPacket } entries, err := b.db.lookup(devAddr) switch err { case nil: case ErrDeviceNotFound: - return an.Nack(p) + return an.Nack() default: + an.Nack() return err } @@ -62,13 +64,13 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter } } if handler == nil { - return an.Nack(p) + return an.Nack() } // 3. If one was found, we forward the packet and wait for the response response, err := adapter.Send(p, *handler) if err != nil { - an.Nack(p) + an.Nack() return err } return an.Ack(response) @@ -78,15 +80,20 @@ func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) er return fmt.Errorf("Not Implemented") } -func (b *Broker) Register(r core.Registration) error { +func (b *Broker) Register(r core.Registration, an core.AckNacker) error { id, okId := r.Recipient.Id.(string) url, okUrl := r.Recipient.Address.(string) nwsKey, okNwsKey := r.Options.(lorawan.AES128Key) if !(okId && okUrl && okNwsKey) { + an.Nack() return ErrInvalidRegistration } entry := brokerEntry{Id: id, Url: url, NwsKey: nwsKey} - return b.db.store(r.DevAddr, entry) + if err := b.db.store(r.DevAddr, entry); err != nil { + an.Nack() + return err + } + return an.Ack() } diff --git a/core/components/router.go b/core/components/router.go index 6bda20b7d..1283101cc 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -35,11 +35,16 @@ func NewRouter(loggers ...log.Logger) (*Router, error) { } // Register implements the core.Component interface -func (r *Router) Register(reg core.Registration) error { +func (r *Router) Register(reg core.Registration, an core.AckNacker) error { if !r.ok() { + an.Nack() return ErrNotInitialized } - return r.db.store(reg.DevAddr, reg.Recipient) + if err := r.db.store(reg.DevAddr, reg.Recipient); err != nil { + an.Nack() + return err + } + return an.Ack() } // HandleDown implements the core.Component interface @@ -50,22 +55,26 @@ func (r *Router) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.A // HandleUp implements the core.Component interface func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { if !r.ok() { + an.Nack() return ErrNotInitialized } // Lookup for an existing broker devAddr, err := p.DevAddr() if err != nil { + an.Nack() return err } brokers, err := r.db.lookup(devAddr) if err != ErrDeviceNotFound && err != ErrEntryExpired { + an.Nack() return err } response, err := upAdapter.Send(p, brokers...) if err != nil { + an.Nack() return err } return an.Ack(response) diff --git a/core/core.go b/core/core.go index 7d05f76a7..b902e526d 100644 --- a/core/core.go +++ b/core/core.go @@ -38,12 +38,12 @@ type Metadata struct { } type AckNacker interface { - Ack(p Packet) error - Nack(p Packet) error + Ack(p ...Packet) error + Nack() error } type Component interface { - Register(reg Registration) error + Register(reg Registration, an AckNacker) error HandleUp(p Packet, an AckNacker, upAdapter Adapter) error HandleDown(p Packet, an AckNacker, downAdapter Adapter) error } diff --git a/integration/router/main.go b/integration/router/main.go index c2a9dc4b1..69ca5821b 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -36,6 +36,8 @@ func main() { } // Bring the service to life + + // Listen uplink go func() { for { packet, an, err := gtwAdapter.Next() @@ -50,6 +52,38 @@ func main() { }(packet, an) } }() + + // Listen downlink + go func() { + for { + packet, an, err := brkAdapter.Next() + if err != nil { + fmt.Println(err) + continue + } + go func(packet Packet, an AckNacker) { + if err := router.HandleDown(packet, an, gtwAdapter); err != nil { + fmt.Println(err) + } + }(packet, an) + } + }() + + // Listen broker registrations + go func() { + for { + reg, an, err := brkAdapter.NextRegistration() + if err != nil { + fmt.Println(err) + continue + } + go func(reg Registration, an AckNacker) { + if err := router.Register(reg, an); err != nil { + fmt.Println(err) + } + }(reg, an) + } + }() } func parseOptions() (brokers []Recipient, udpPort uint64) { From 0317e18b04c8c28d8e746381946106fdecbce8cb Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 17:13:43 +0100 Subject: [PATCH 0398/2266] [integration] Finish router integration --- integration/router/main.go | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/integration/router/main.go b/integration/router/main.go index 69ca5821b..329e75c42 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -53,22 +53,6 @@ func main() { } }() - // Listen downlink - go func() { - for { - packet, an, err := brkAdapter.Next() - if err != nil { - fmt.Println(err) - continue - } - go func(packet Packet, an AckNacker) { - if err := router.HandleDown(packet, an, gtwAdapter); err != nil { - fmt.Println(err) - } - }(packet, an) - } - }() - // Listen broker registrations go func() { for { @@ -84,6 +68,8 @@ func main() { }(reg, an) } }() + + <-make(chan bool) } func parseOptions() (brokers []Recipient, udpPort uint64) { @@ -92,7 +78,7 @@ func parseOptions() (brokers []Recipient, udpPort uint64) { flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000 `) - flag.StringVar(&udpPortFlag, "udpPort", "", "Udp port on which the router should listen to.") + flag.StringVar(&udpPortFlag, "udp-port", "", "Udp port on which the router should listen to.") flag.Parse() var err error From 407e5db4de7e1654928c0be9493be4f2e72a44a9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 17:25:23 +0100 Subject: [PATCH 0399/2266] [integration] Write Dockerfile for router binary --- integration/router/Dockerfile | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 integration/router/Dockerfile diff --git a/integration/router/Dockerfile b/integration/router/Dockerfile new file mode 100644 index 000000000..b12a05bfb --- /dev/null +++ b/integration/router/Dockerfile @@ -0,0 +1,19 @@ +# The container will assume to have a $BROKER env var defined + +# Go, because go is life +FROM golang:latest + +# Dependencies, everything on master +RUN go get "github.com/TheThingsNetwork/core/..." + +# Actual files to build +RUN mkdir ~/TheThingsNetwork +ADD . ~/TheThingsNetwork +WORKDIR ~/TheThingsNetwork + +# Expose the port on which the gateway adapter will listen to +EXPOSE 33000/udp + +# Build & Launch +RUN go build . +CMD ./router --brokers $BROKERS --udp-port 33000 From 2f320152f1571bc251d333ee4643dcf42c020b67 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 18:24:16 +0100 Subject: [PATCH 0400/2266] Update a bit http adapters to match protocol description --- core/adapters/http/adapter.go | 2 +- core/adapters/http/pubsub/handler_parser.go | 2 +- core/adapters/http/pubsub/pubsub.go | 2 +- core/adapters/http/pubsub/pubsub_test.go | 2 +- core/adapters/http/pubsub/reg_acknacker.go | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 2fd0852e9..3097612d9 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -63,7 +63,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) return } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated { cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) return } diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go index 79784f028..729f0c8c1 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/pubsub/handler_parser.go @@ -20,7 +20,7 @@ type HandlerParser struct{} func (p HandlerParser) Parse(req *http.Request) (core.Registration, error) { // Check the query parameter - reg := regexp.MustCompile("end-device/([a-fA-F0-9]{8})$") + reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{8})$") query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { return core.Registration{}, fmt.Errorf("Incorrect end-device address format") diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 471079511..7650dca44 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -62,7 +62,7 @@ func (a *Adapter) listenRegistration(port uint) { serveMux := http.NewServeMux() // So far we only supports one endpoint [PUT] /end-device/:devAddr - serveMux.HandleFunc("/end-device/", a.handlePutEndDevice) + serveMux.HandleFunc("/end-devices/", a.handlePutEndDevice) server := http.Server{ Addr: fmt.Sprintf("0.0.0.0:%d", port), diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index c23650b43..5f7a4da11 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -131,7 +131,7 @@ func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { panic(err) } - request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-device/%s", c.adapter, devAddr), buf) + request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-devices/%s", c.adapter, devAddr), buf) if err != nil { panic(err) } diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 0a514d27a..7fb71e928 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -20,7 +20,7 @@ type regAckNacker struct { // Ack implements the core.Acker interface func (r regAckNacker) Ack(p ...core.Packet) error { select { - case r.response <- regRes{statusCode: http.StatusOK}: + case r.response <- regRes{statusCode: http.StatusAccepted}: return nil case <-time.After(time.Millisecond * 50): return ErrConnectionLost From 8e801e49bae97711a3e8f24a052abbe2a87c32cc Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 18:24:37 +0100 Subject: [PATCH 0401/2266] Write down information about http and semtech protocols used by adapters --- core/adapters/protocols.md | 89 +++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 2 deletions(-) diff --git a/core/adapters/protocols.md b/core/adapters/protocols.md index ba49753fe..7e9956171 100644 --- a/core/adapters/protocols.md +++ b/core/adapters/protocols.md @@ -1,11 +1,96 @@ semtech ~ udp ============= +Have a look at [this document](http://iot.semtech.com/resources/Server_Release_2.1.1/files.xml?action=download&file=LoRa%20gateway%20to%20network%20server%20interface%20definition.pdf) basic ~ http ============ +The basic http protocol relies seemingly on `http`. + +An adapter which implements this protocol should provide at least one end-point: + +- `[POST] /packets` + + +#### Request + +Packets are sent as a json payload of the following shape: + +```js + { + "payload": , + "metadata": { + "chan": ..., // Concentrator "IF" channel used for RX (unsigned integer) + "codr": ..., // LoRa ECC coding rate identifier + "datr": ..., // LoRa datarate identifier + "fdev": ..., // FSK frequency deviation (unsigned integer, in Hz) + "freq": ..., // RX Central frequency in MHx (unsigned float, Hz precision) + "imme": ..., // Send packet immediately (will ignore tmst & time) + "ipol": ..., // Lora modulation polarization inversion + "lsnr": ..., // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + "modu": ..., // Modulation identifier "LORA" + "ncrc": ..., // If true, disable the CRC of the physical layer (optional) + "powe": ..., // TX output power in dBm (unsigned integer, dBm precision) + "prea": ..., // RF preamble size (unsigned integer) + "rfch": ..., // Concentrator "RF chain" used for RX (unsigned integer) + "rssi": ..., // RSSI in dBm (signed integer, 1 dB precision) + "size": ..., // RF packet payload size in bytes (unsigned integer) + "stat": ..., // CRC status: 1 - OK, -1 = fail, 0 = no CRC + "time": ..., // UTC time of pkt RX, us precision, ISO 8601 'compact' format + "tmst": ... // Internal timestamp of "RX finished" event (32b unsigned) + } + } +``` + +All fields in metadata are optional, so is the metadata field itself. The payload should be a +base64 encoded binary representation of a Physical Payload as defined by the +[lorawan](https://github.com/brocaar/lorawan) go package + +#### Response + +The adapter may provide two answers to the demander. + +- An `HTTP 200 Ok.` means that the packet has been accepted and is handled by the server. + +- An `HTTP 404 Not Found.` means that the server doesn't take care of packet coming from the + end-device related to the packet. + +Another type of response could be misinterpreted by the sender. An `404` response doesn't +contain any body payload. However, a `200` might. In such a case, the response has the same +shape as the one described above: a plain `json` with an encoded physical payload and some +possible metadata. + +basic+pubsub ~ http +=================== + +The `pubsub` http adapter is an extension of the `basic` http adapter. In addition of the +behavior defined in the corresponding section, the `pubsub` adapter also provide the following +end-point: + +- `[PUT] /end-devices/:devAddr` + +where `:devAddr` identify a device address encoded as an hexadecimal string of 8 characters (2 +characters for a single byte), for instance: "09a3bc52". + +This end-point is used to register a handler for a given end-device such that every packet of +the network coming from that device will be forwarded via http to the handler. + +#### Request + +Requests are expected to come along with a `json` payload of the following shape: + +```js + { + "app_id": ..., // Application identifier represented by a string + "app_url": ..., // Webhook to which forward incoming data + "nws_key": ..., // The network session key associated to the device. + } +``` + + +#### Response + + -basic+registration ~ http -========================= basic+broadcast ~ http ====================== From 61db30c98f4e951402262bbaa37039a1d84699f0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 14 Jan 2016 18:26:31 +0100 Subject: [PATCH 0402/2266] Add missing part in protocols.md --- core/adapters/protocols.md | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/core/adapters/protocols.md b/core/adapters/protocols.md index 7e9956171..98528001c 100644 --- a/core/adapters/protocols.md +++ b/core/adapters/protocols.md @@ -80,17 +80,27 @@ Requests are expected to come along with a `json` payload of the following shape ```js { - "app_id": ..., // Application identifier represented by a string - "app_url": ..., // Webhook to which forward incoming data - "nws_key": ..., // The network session key associated to the device. + "app_id": ..., // Application identifier (string) + "app_url": ..., // Webhook to which forward incoming data (string) + "nws_key": ... // The network session key associated to the device (string, 32 characters) } ``` +The network session key `nws_key` is supposed to be an hexadecimal encoded version of the +associated network session key. #### Response +As a response, the emitter might consider three situations: +- `HTTP 202 Accepted.` as a confirmation of the registration +- `HTTP 400 Bad Request.` if the request or the parameters aren't valid + +- `HTTP 409 Conflict.` if for some reason, the end-device cannot be registered + +All those requests have empty payloads. basic+broadcast ~ http ====================== + From 83c0c216c0fff0800606b3e7359535da4aa211bd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 17:37:58 +0100 Subject: [PATCH 0403/2266] Add support for structured log entries --- utils/log/entry.go | 76 ++++++++++++++++++++++++++++++++++++++++++++++ utils/log/log.go | 18 ++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 utils/log/entry.go diff --git a/utils/log/entry.go b/utils/log/entry.go new file mode 100644 index 000000000..3fa3c8f96 --- /dev/null +++ b/utils/log/entry.go @@ -0,0 +1,76 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package log + +import ( + "fmt" + "strings" +) + +// Meta contains metadata for a log entry +type Meta map[string]interface{} + +func (m Meta) String() string { + if len(m) == 0 { + return "" + } + + metas := []string{} + for k, v := range m { + metas = append(metas, fmt.Sprintf("%s=%v", k, v)) + } + + return fmt.Sprint("\033[34m[ ", strings.Join(metas, ", "), " ]\033[0m") +} + +// The Level for this log entry +type Level uint8 + +// Log levels +const ( + PanicLevel Level = iota + FatalLevel + ErrorLevel + WarnLevel + InfoLevel + DebugLevel +) + +func (l Level) String() string { + switch l { + case PanicLevel: + return "\033[31m[ panic ]\033[0m" // Level printed in red + case FatalLevel: + return "\033[31m[ fatal ]\033[0m" // Level printed in red + case ErrorLevel: + return "\033[31m[ error ]\033[0m" // Level printed in red + case WarnLevel: + return "\033[33m[ warn ]\033[0m" // Level printed in yellow + case DebugLevel: + return "\033[34m[ debug ]\033[0m" // Level printed in blue + default: // Default is InfoLevel + return "\033[32m[ info ]\033[0m" // Level printed in green + } +} + +// entry type +type entry struct { + Level Level + Message string + Meta Meta +} + +// Log implements the Logger interface +func (e entry) String() string { + return fmt.Sprintf("%s %s %s", e.Level, e.Message, e.Meta) +} + +// Entry gives you a new log entry +func Entry(level Level, msg string, meta Meta) entry { + return entry{ + Level: level, + Message: msg, + Meta: meta, + } +} diff --git a/utils/log/log.go b/utils/log/log.go index bf27a38f7..562fddc9c 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -12,8 +12,9 @@ import ( // Logger is a minimalist interface to represent logger type Logger interface { - Log(format string) + Log(str string) Logf(format string, a ...interface{}) + LogEntry(level Level, msg string, meta Meta) } // DebugLogger can be used in development to display loglines in the console @@ -35,6 +36,11 @@ func (l DebugLogger) Logf(format string, a ...interface{}) { fmt.Print("\n") } +// LogEntry implements the Logger interface +func (l DebugLogger) LogEntry(level Level, msg string, meta Meta) { + l.Log(Entry(level, msg, meta).String()) +} + // TestLogger can be used in a test environnement to display log only on failure type TestLogger struct { Tag string @@ -51,6 +57,11 @@ func (l TestLogger) Logf(format string, a ...interface{}) { l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, fmt.Sprintf(format, a...)) // Tag printed in yellow } +// LogEntry implements the Logger interface +func (l TestLogger) LogEntry(level Level, msg string, meta Meta) { + l.Log(Entry(level, msg, meta).String()) +} + // MultiLogger aggregates several loggers log to each of them type MultiLogger struct { Loggers []Logger @@ -69,3 +80,8 @@ func (l MultiLogger) Logf(format string, a ...interface{}) { logger.Logf(format, a...) } } + +// LogEntry implements the Logger interface +func (l MultiLogger) LogEntry(level Level, msg string, meta Meta) { + l.Log(Entry(level, msg, meta).String()) +} From 04e6c516e33501c34cac5c8f8efdd14f6faab7b6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 18:46:03 +0100 Subject: [PATCH 0404/2266] Structured logging for HTTP adapter --- core/adapters/http/adapter.go | 2 +- core/adapters/http/broadcast/broadcast.go | 6 +++--- core/adapters/http/pubsub/pubsub.go | 10 ++++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 3097612d9..e88bdd0ce 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -54,7 +54,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) for _, recipient := range r { go func(recipient core.Recipient) { defer wg.Done() - a.Logf("Post to %v", recipient) + a.LogEntry(log.DebugLevel, "POST Request", log.Meta{"recipient": recipient}) buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 4b686863d..d981f6e2d 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -85,7 +85,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { go func(recipient core.Recipient) { defer wg.Done() - a.Logf("Post to %v", recipient) + a.LogEntry(log.DebugLevel, "POST Request", log.Meta{"recipient": recipient}) buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) @@ -97,7 +97,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { switch resp.StatusCode { case http.StatusOK: - a.Logf("Recipient %v registered for given packet", recipient) + a.LogEntry(log.DebugLevel, "Recipient registered for packet", log.Meta{"recipient": recipient, "devAddr": devAddr}) raw := make([]byte, resp.ContentLength) n, err := resp.Body.Read(raw) @@ -114,7 +114,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { register <- recipient chresp <- packet case http.StatusNotFound: - a.Logf("Recipient %v doesn't care much about packet", recipient) + a.LogEntry(log.DebugLevel, "Recipient not interested in packet", log.Meta{"recipient": recipient, "devAddr": devAddr}) default: // Non-blocking, buffered cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 7650dca44..269965e27 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -68,21 +68,20 @@ func (a *Adapter) listenRegistration(port uint) { Addr: fmt.Sprintf("0.0.0.0:%d", port), Handler: serveMux, } - a.Logf("Start listening on %d", port) + a.LogEntry(log.InfoLevel, "Starting server", log.Meta{"port": port}) err := server.ListenAndServe() - a.Logf("HTTP connection lost: %v", err) + a.LogEntry(log.WarnLevel, "HTTP connection lost", log.Meta{"error": err}) } // fail logs the given failure and sends an appropriate response to the client func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { - a.Logf("registration request rejected: %s", msg) w.WriteHeader(http.StatusBadRequest) w.Write([]byte(msg)) } // handle request [PUT] on /end-device/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { - a.Logf("Receive new registration request") + a.LogEntry(log.DebugLevel, "Receiving new registration request", log.Meta{"sender": req.RemoteAddr}) // Check the http method if req.Method != "PUT" { w.WriteHeader(http.StatusMethodNotAllowed) @@ -92,6 +91,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { + a.LogEntry(log.WarnLevel, "Received invalid content-type in request", log.Meta{"sender": req.RemoteAddr}) a.badRequest(w, "Incorrect content type") return } @@ -99,6 +99,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { // Parse body and query params config, err := a.Parse(req) if err != nil { + a.LogEntry(log.WarnLevel, "Received invalid body in request", log.Meta{"sender": req.RemoteAddr, "error": err.Error()}) a.badRequest(w, err.Error()) return } @@ -108,6 +109,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { a.registrations <- regReq{Registration: config, response: response} r, ok := <-response if !ok { + a.LogEntry(log.ErrorLevel, "Core server not responding", log.Meta{}) a.badRequest(w, "Core server not responding") return } From 400d81ffb988053d94386cfd564cc7e15ae672ca Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 19:08:33 +0100 Subject: [PATCH 0405/2266] Structured logging for Semtech adapter --- core/adapters/semtech/adapter.go | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 2de26bcbd..8bd2fd239 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -45,8 +45,9 @@ func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + a.LogEntry(log.InfoLevel, "Starting server", log.Meta{"port": port}) if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.Logf("Unable to establish the connection: %v", err) + a.LogEntry(log.ErrorLevel, "Unable to start server", log.Meta{"error": err}) return nil, ErrInvalidPort } @@ -75,7 +76,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { msg := <-a.next packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { - a.Logf("Invalid Packet") + a.LogEntry(log.DebugLevel, "Received invalid packet", log.Meta{}) return core.Packet{}, nil, ErrInvalidPacket } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil @@ -89,20 +90,20 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) // listen Handle incoming packets and forward them func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() - a.Logf("Start listening on %s", conn.LocalAddr()) + a.LogEntry(log.DebugLevel, "Starting accept loop", log.Meta{"address": conn.LocalAddr()}) for { buf := make([]byte, 128) n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection - a.Logf("Error: %v", err) + a.LogEntry(log.ErrorLevel, "Connection error", log.Meta{"error": err}) continue } - a.Logf("Incoming datagram %x", buf[:n]) + a.LogEntry(log.DebugLevel, "Incoming datagram", log.Meta{"datagram": buf[:n]}) pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) if err != nil { - a.Logf("Error: %v", err) + a.LogEntry(log.WarnLevel, "Invalid packet", log.Meta{"error": err}) continue } @@ -114,10 +115,10 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PULL_ACK, }.MarshalBinary() if err != nil { - a.Logf("Unexpected error while marshaling PULL_ACK: %v", err) + a.LogEntry(log.ErrorLevel, "Unexpected error while marshaling PULL_ACK", log.Meta{"error": err}) continue } - a.Logf("Sending PULL_ACK to %v", addr) + a.LogEntry(log.DebugLevel, "Sending PULL_ACK", log.Meta{"recipient": addr}) a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component pushAck, err := semtech.Packet{ @@ -126,14 +127,14 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PUSH_ACK, }.MarshalBinary() if err != nil { - a.Logf("Unexpected error while marshaling PUSH_ACK: %v", err) + a.LogEntry(log.ErrorLevel, "Unexpected error while marshaling PUSH_ACK", log.Meta{"error": err}) continue } - a.Logf("Sending PUSH_ACK to %v", addr) + a.LogEntry(log.DebugLevel, "Sending PUSH_ACK", log.Meta{"recipient": addr}) a.conn <- udpMsg{addr: addr, raw: pushAck} if pkt.Payload == nil { - a.Logf("Inconsistent PUSH_DATA packet %v", pkt) + a.LogEntry(log.ErrorLevel, "Invalid PUSH_DATA packet", log.Meta{"packet": pkt}) continue } for _, rxpk := range pkt.Payload.RXPK { @@ -143,7 +144,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { } } default: - a.Logf("Unexpected packet received. Ignored: %v", pkt) + a.LogEntry(log.DebugLevel, "Ignoring unexpected packet", log.Meta{"packet": pkt}) continue } } @@ -163,7 +164,7 @@ func (a *Adapter) monitorConnection() { if udpConn != nil && msg.raw != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.Logf("Unable to send udp message: %+v", err) + a.LogEntry(log.ErrorLevel, "Error while sending UDP message", log.Meta{"error": err}) } } } From 0a96f964ae6df85698b32caffe2204b5750a2416 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jan 2016 19:10:04 +0100 Subject: [PATCH 0406/2266] Structured logging for Broker component --- core/components/broker.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/components/broker.go b/core/components/broker.go index 01613e52b..9d72a7e35 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -52,7 +52,6 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter for _, entry := range entries { ok, err := p.Payload.ValidateMIC(entry.NwsKey) if err != nil { - b.Logf("Unexpected error: %v", err) continue } if ok { @@ -60,10 +59,12 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter Id: entry.Id, Address: entry.Url, } + b.LogEntry(log.DebugLevel, "Associated device with handler", log.Meta{"devAddr": devAddr, "handler": handler}) break } } if handler == nil { + b.LogEntry(log.WarnLevel, "Could not find handler for device", log.Meta{"devAddr": devAddr}) return an.Nack() } From 1fbd2aa10e76dbacda272d1d807efd0a0c703017 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 11:19:04 +0100 Subject: [PATCH 0407/2266] Add apex/log for structured logging --- .drone.yml | 1 + .travis.yml | 1 + core/adapters/http/adapter.go | 10 +- core/adapters/http/adapter_test.go | 11 ++- core/adapters/http/broadcast/broadcast.go | 15 +-- .../adapters/http/broadcast/broadcast_test.go | 10 +- core/adapters/http/pubsub/pubsub.go | 20 ++-- core/adapters/http/pubsub/pubsub_test.go | 10 +- core/adapters/semtech/adapter.go | 42 ++++---- core/adapters/semtech/adapter_test.go | 14 +-- core/adapters/semtech/build_utilities_test.go | 11 ++- core/components/broker.go | 16 ++-- core/components/router.go | 12 +-- integration/router/main.go | 19 ++-- utils/testing/log_handler.go | 95 +++++++++++++++++++ 15 files changed, 208 insertions(+), 79 deletions(-) create mode 100644 utils/testing/log_handler.go diff --git a/.drone.yml b/.drone.yml index 8372cdef7..fe706fba3 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,6 +2,7 @@ build: image: golang commands: - git submodule update --init --recursive + - go get github.com/apex/log - go get github.com/brocaar/lorawan - go get github.com/jacobsa/crypto/cmac - go get github.com/smartystreets/goconvey/convey diff --git a/.travis.yml b/.travis.yml index 80802968c..1f04bdd7a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,6 +7,7 @@ before_install: - git submodule update --init --recursive install: + - go get github.com/apex/log - go get github.com/brocaar/lorawan - go get github.com/jacobsa/crypto/cmac - go get github.com/smartystreets/goconvey/convey diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index e88bdd0ce..a6c3d794b 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -13,20 +13,20 @@ import ( "sync" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/apex/log" ) var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") type Adapter struct { - log.Logger + Ctx log.Interface } // NewAdapter constructs and allocate a new Broker <-> Handler http adapter -func NewAdapter(loggers ...log.Logger) (*Adapter, error) { +func NewAdapter(ctx log.Interface) (*Adapter, error) { return &Adapter{ - Logger: log.MultiLogger{Loggers: loggers}, + Ctx: ctx, }, nil } @@ -54,7 +54,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) for _, recipient := range r { go func(recipient core.Recipient) { defer wg.Done() - a.LogEntry(log.DebugLevel, "POST Request", log.Meta{"recipient": recipient}) + a.Ctx.WithField("recipient", recipient).Debug("POST Request") buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 844afb6b9..fe79ea149 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -14,9 +14,9 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/lorawan" - "github.com/TheThingsNetwork/ttn/utils/log" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/apex/log" ) // Send(p core.Packet, r ...core.Recipient) error @@ -39,7 +39,14 @@ func TestSend(t *testing.T) { } s := genMockServer(3100) - adapter, err := NewAdapter(log.TestLogger{Tag: "Adapter", T: t}) + + // Logging + log.SetHandler(NewLogHandler(t)) + ctx := log.WithFields(log.Fields{ + "tag": "Adapter", + }) + + adapter, err := NewAdapter(ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index d981f6e2d..dd14aa3a1 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -14,7 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/apex/log" ) type Adapter struct { @@ -27,12 +27,12 @@ var ErrBadOptions = fmt.Errorf("Bad options provided") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") -func NewAdapter(recipients []core.Recipient, loggers ...log.Logger) (*Adapter, error) { +func NewAdapter(recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { return nil, ErrBadOptions } - adapter, err := httpadapter.NewAdapter(loggers...) + adapter, err := httpadapter.NewAdapter(ctx) if err != nil { return nil, err } @@ -85,7 +85,10 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { go func(recipient core.Recipient) { defer wg.Done() - a.LogEntry(log.DebugLevel, "POST Request", log.Meta{"recipient": recipient}) + ctx := a.Ctx.WithField("recipient", recipient) + + ctx.Debug("POST Request") + buf := new(bytes.Buffer) buf.Write([]byte(payload)) resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) @@ -97,7 +100,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { switch resp.StatusCode { case http.StatusOK: - a.LogEntry(log.DebugLevel, "Recipient registered for packet", log.Meta{"recipient": recipient, "devAddr": devAddr}) + ctx.WithField("devAddr", devAddr).Debug("Recipient registered for packet") raw := make([]byte, resp.ContentLength) n, err := resp.Body.Read(raw) @@ -114,7 +117,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { register <- recipient chresp <- packet case http.StatusNotFound: - a.LogEntry(log.DebugLevel, "Recipient not interested in packet", log.Meta{"recipient": recipient, "devAddr": devAddr}) + ctx.WithField("devAddr", devAddr).Debug("Recipient not interested in packet") default: // Non-blocking, buffered cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index 8f808c3d3..4cdd60c3f 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -13,9 +13,9 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/lorawan" - "github.com/TheThingsNetwork/ttn/utils/log" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/apex/log" ) func TestSend(t *testing.T) { @@ -70,8 +70,14 @@ func TestSend(t *testing.T) { }, } + // Logging + log.SetHandler(NewLogHandler(t)) + ctx := log.WithFields(log.Fields{ + "tag": "Adapter", + }) + // Build - adapter, err := NewAdapter(recipients, log.TestLogger{Tag: "Adapter", T: t}) + adapter, err := NewAdapter(recipients, ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 269965e27..d84d7c378 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/apex/log" ) type Adapter struct { @@ -33,8 +33,8 @@ type regRes struct { } // NewAdapter constructs a new http adapter that also handle registrations via http requests -func NewAdapter(port uint, parser Parser, loggers ...log.Logger) (*Adapter, error) { - adapter, err := httpadapter.NewAdapter(loggers...) +func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { + adapter, err := httpadapter.NewAdapter(ctx) if err != nil { return nil, err } @@ -68,9 +68,9 @@ func (a *Adapter) listenRegistration(port uint) { Addr: fmt.Sprintf("0.0.0.0:%d", port), Handler: serveMux, } - a.LogEntry(log.InfoLevel, "Starting server", log.Meta{"port": port}) + a.Ctx.WithField("port", port).Info("Starting Server") err := server.ListenAndServe() - a.LogEntry(log.WarnLevel, "HTTP connection lost", log.Meta{"error": err}) + a.Ctx.WithError(err).Warn("HTTP connection lost") } // fail logs the given failure and sends an appropriate response to the client @@ -81,7 +81,9 @@ func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { // handle request [PUT] on /end-device/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { - a.LogEntry(log.DebugLevel, "Receiving new registration request", log.Meta{"sender": req.RemoteAddr}) + ctx := a.Ctx.WithField("sender", req.RemoteAddr) + + ctx.Debug("Receiving new registration request") // Check the http method if req.Method != "PUT" { w.WriteHeader(http.StatusMethodNotAllowed) @@ -91,7 +93,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - a.LogEntry(log.WarnLevel, "Received invalid content-type in request", log.Meta{"sender": req.RemoteAddr}) + ctx.Warn("Received invalid content-type in request") a.badRequest(w, "Incorrect content type") return } @@ -99,7 +101,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { // Parse body and query params config, err := a.Parse(req) if err != nil { - a.LogEntry(log.WarnLevel, "Received invalid body in request", log.Meta{"sender": req.RemoteAddr, "error": err.Error()}) + ctx.WithError(err).Warn("Received invalid body in request") a.badRequest(w, err.Error()) return } @@ -109,7 +111,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { a.registrations <- regReq{Registration: config, response: response} r, ok := <-response if !ok { - a.LogEntry(log.ErrorLevel, "Core server not responding", log.Meta{}) + ctx.Error("Core server not responding") a.badRequest(w, "Core server not responding") return } diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 5f7a4da11..4e620990e 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -13,8 +13,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/lorawan" - "github.com/TheThingsNetwork/ttn/utils/log" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/apex/log" ) // NextRegistration() (core.Registration, core.AckNacker, error) @@ -60,7 +60,13 @@ func TestNextRegistration(t *testing.T) { }, } - adapter, err := NewAdapter(3021, HandlerParser{}, log.TestLogger{Tag: "Adapter", T: t}) + // Logging + log.SetHandler(NewLogHandler(t)) + ctx := log.WithFields(log.Fields{ + "tag": "Adapter", + }) + + adapter, err := NewAdapter(3021, HandlerParser{}, ctx) client := &client{adapter: "0.0.0.0:3021"} if err != nil { panic(err) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 8bd2fd239..99480c55b 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -9,11 +9,11 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/apex/log" ) type Adapter struct { - log.Logger + Ctx log.Interface conn chan udpMsg next chan rxpkMsg } @@ -35,19 +35,19 @@ var ErrNotSupported error = fmt.Errorf("Unsupported operation") var ErrInvalidPacket error = fmt.Errorf("Invalid packet supplied") // New constructs and allocates a new udp_sender adapter -func NewAdapter(port uint, loggers ...log.Logger) (*Adapter, error) { +func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a := Adapter{ - Logger: log.MultiLogger{Loggers: loggers}, - conn: make(chan udpMsg), - next: make(chan rxpkMsg), + Ctx: ctx, + conn: make(chan udpMsg), + next: make(chan rxpkMsg), } // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - a.LogEntry(log.InfoLevel, "Starting server", log.Meta{"port": port}) + a.Ctx.WithField("port", port).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.LogEntry(log.ErrorLevel, "Unable to start server", log.Meta{"error": err}) + a.Ctx.WithError(err).Error("Unable to start server") return nil, ErrInvalidPort } @@ -76,7 +76,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { msg := <-a.next packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { - a.LogEntry(log.DebugLevel, "Received invalid packet", log.Meta{}) + a.Ctx.Debug("Received invalid packet") return core.Packet{}, nil, ErrInvalidPacket } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil @@ -90,20 +90,20 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) // listen Handle incoming packets and forward them func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() - a.LogEntry(log.DebugLevel, "Starting accept loop", log.Meta{"address": conn.LocalAddr()}) + a.Ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") for { buf := make([]byte, 128) n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection - a.LogEntry(log.ErrorLevel, "Connection error", log.Meta{"error": err}) + a.Ctx.WithError(err).Error("Connection error") continue } - a.LogEntry(log.DebugLevel, "Incoming datagram", log.Meta{"datagram": buf[:n]}) + a.Ctx.WithField("datagram", buf[:n]).Debug("Incoming datagram") pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) if err != nil { - a.LogEntry(log.WarnLevel, "Invalid packet", log.Meta{"error": err}) + a.Ctx.WithError(err).Warn("Invalid packet") continue } @@ -115,10 +115,10 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PULL_ACK, }.MarshalBinary() if err != nil { - a.LogEntry(log.ErrorLevel, "Unexpected error while marshaling PULL_ACK", log.Meta{"error": err}) + a.Ctx.WithError(err).Error("Unexpected error while marshaling PULL_ACK") continue } - a.LogEntry(log.DebugLevel, "Sending PULL_ACK", log.Meta{"recipient": addr}) + a.Ctx.WithField("recipient", addr).Debug("Sending PULL_ACK") a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component pushAck, err := semtech.Packet{ @@ -127,14 +127,14 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PUSH_ACK, }.MarshalBinary() if err != nil { - a.LogEntry(log.ErrorLevel, "Unexpected error while marshaling PUSH_ACK", log.Meta{"error": err}) + a.Ctx.WithError(err).Error("Unexpected error while marshaling PUSH_ACK") continue } - a.LogEntry(log.DebugLevel, "Sending PUSH_ACK", log.Meta{"recipient": addr}) + a.Ctx.WithField("Recipient", addr).Debug("Sending PUSH_ACK") a.conn <- udpMsg{addr: addr, raw: pushAck} if pkt.Payload == nil { - a.LogEntry(log.ErrorLevel, "Invalid PUSH_DATA packet", log.Meta{"packet": pkt}) + a.Ctx.WithField("packet", pkt).Warn("Invalid PUSH_DATA packet") continue } for _, rxpk := range pkt.Payload.RXPK { @@ -144,7 +144,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { } } default: - a.LogEntry(log.DebugLevel, "Ignoring unexpected packet", log.Meta{"packet": pkt}) + a.Ctx.WithField("packet", pkt).Debug("Ignoring unexpected packet") continue } } @@ -156,7 +156,7 @@ func (a *Adapter) monitorConnection() { for msg := range a.conn { if msg.conn != nil { // Change the connection if udpConn != nil { - a.Log("Define new UDP connection") + a.Ctx.Debug("Define new UDP connection") udpConn.Close() } udpConn = msg.conn @@ -164,7 +164,7 @@ func (a *Adapter) monitorConnection() { if udpConn != nil && msg.raw != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.LogEntry(log.ErrorLevel, "Error while sending UDP message", log.Meta{"error": err}) + a.Ctx.WithError(err).Error("Error while sending UDP message") } } } diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index 02ec68480..cf7416217 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -19,21 +19,15 @@ func TestNewAdapter(t *testing.T) { func TestSend(t *testing.T) { Desc(t, "Send is not supported") - adapter, err := NewAdapter(33000) - if err != nil { - panic(err) - } - _, err = adapter.Send(core.Packet{}) + adapter, _ := genAdapter(t, 33000) + _, err := adapter.Send(core.Packet{}) checkErrors(t, ErrNotSupported, err) } func TestNextRegistration(t *testing.T) { Desc(t, "Next registration is not supported") - adapter, err := NewAdapter(33001) - if err != nil { - panic(err) - } - _, _, err = adapter.NextRegistration() + adapter, _ := genAdapter(t, 33001) + _, _, err := adapter.NextRegistration() checkErrors(t, ErrNotSupported, err) } diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index 07485ac39..2d0d9deac 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -13,8 +13,9 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/log" "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/apex/log" ) // ----- build utilities @@ -72,7 +73,13 @@ func (s mockServer) send(p semtech.Packet) semtech.Packet { // Generates an adapter as well as a channel that behaves like the Next() methods (but can be used // in a select for timeout) func genAdapter(t *testing.T, port uint) (*Adapter, chan interface{}) { - adapter, err := NewAdapter(port, log.TestLogger{Tag: "Adapter", T: t}) + // Logging + log.SetHandler(NewLogHandler(t)) + ctx := log.WithFields(log.Fields{ + "tag": "Adapter", + }) + + adapter, err := NewAdapter(port, ctx) if err != nil { panic(err) } diff --git a/core/components/broker.go b/core/components/broker.go index 9d72a7e35..2365835d0 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -8,15 +8,15 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/lorawan" - "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/apex/log" ) type Broker struct { - log.Logger - db brokerStorage + Ctx log.Interface + db brokerStorage } -func NewBroker(loggers ...log.Logger) (*Broker, error) { +func NewBroker(ctx log.Interface) (*Broker, error) { localDB, err := NewBrokerStorage() if err != nil { @@ -24,8 +24,8 @@ func NewBroker(loggers ...log.Logger) (*Broker, error) { } return &Broker{ - Logger: log.MultiLogger{Loggers: loggers}, - db: localDB, + Ctx: ctx, + db: localDB, }, nil } @@ -59,12 +59,12 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter Id: entry.Id, Address: entry.Url, } - b.LogEntry(log.DebugLevel, "Associated device with handler", log.Meta{"devAddr": devAddr, "handler": handler}) + b.Ctx.WithFields(log.Fields{"devAddr": devAddr, "handler": handler}).Debug("Associated device with handler") break } } if handler == nil { - b.LogEntry(log.WarnLevel, "Could not find handler for device", log.Meta{"devAddr": devAddr}) + b.Ctx.WithField("devAddr", devAddr).Warn("Could not find handler for device") return an.Nack() } diff --git a/core/components/router.go b/core/components/router.go index 1283101cc..3f276c162 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -8,7 +8,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/log" + "github.com/apex/log" ) const ( @@ -16,12 +16,12 @@ const ( ) type Router struct { - log.Logger - db routerStorage // Local storage that maps end-device addresses to broker addresses + Ctx log.Interface + db routerStorage // Local storage that maps end-device addresses to broker addresses } // NewRouter constructs a Router and setup its internal structure -func NewRouter(loggers ...log.Logger) (*Router, error) { +func NewRouter(ctx log.Interface) (*Router, error) { localDB, err := NewRouterStorage(EXPIRY_DELAY) if err != nil { @@ -29,8 +29,8 @@ func NewRouter(loggers ...log.Logger) (*Router, error) { } return &Router{ - Logger: log.MultiLogger{Loggers: loggers}, - db: localDB, + Ctx: ctx, + db: localDB, }, nil } diff --git a/integration/router/main.go b/integration/router/main.go index 329e75c42..576de14b8 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -6,31 +6,38 @@ package main import ( "flag" "fmt" + "strconv" + "strings" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" "github.com/TheThingsNetwork/ttn/core/adapters/semtech" "github.com/TheThingsNetwork/ttn/core/components" - "github.com/TheThingsNetwork/ttn/utils/log" - "strconv" - "strings" + "github.com/apex/log" ) func main() { // Parse options brokers, udpPort := parseOptions() + // Create Logging Context + + ctx := log.WithFields(log.Fields{ + "component": "Router", + }) + // Instantiate all components - gtwAdapter, err := semtech.NewAdapter(uint(udpPort), log.DebugLogger{Tag: "Gateway Adapter"}) + gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter")) if err != nil { panic(err) } - brkAdapter, err := broadcast.NewAdapter(brokers, log.DebugLogger{Tag: "Broker Adapter"}) + brkAdapter, err := broadcast.NewAdapter(brokers, ctx.WithField("tag", "Broker Adapter")) if err != nil { panic(err) } - router, err := components.NewRouter(log.DebugLogger{Tag: "Router"}) + router, err := components.NewRouter(ctx.WithField("tag", "Router")) if err != nil { panic(err) } diff --git a/utils/testing/log_handler.go b/utils/testing/log_handler.go new file mode 100644 index 000000000..94bc19e89 --- /dev/null +++ b/utils/testing/log_handler.go @@ -0,0 +1,95 @@ +package testing + +import ( + "bytes" + "sort" + "sync" + "testing" + + "fmt" + + "github.com/apex/log" +) + +// colors. +const ( + none = 0 + red = 31 + green = 32 + yellow = 33 + blue = 34 + gray = 37 +) + +// Colors mapping. +var Colors = [...]int{ + log.DebugLevel: gray, + log.InfoLevel: blue, + log.WarnLevel: yellow, + log.ErrorLevel: red, + log.FatalLevel: red, +} + +// Strings mapping. +var Strings = [...]string{ + log.DebugLevel: "DEBUG", + log.InfoLevel: "INFO", + log.WarnLevel: "WARN", + log.ErrorLevel: "ERROR", + log.FatalLevel: "FATAL", +} + +// field used for sorting. +type field struct { + Name string + Value interface{} +} + +// by sorts projects by call count. +type byName []field + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } + +// LogHandler implementation. +type LogHandler struct { + mu sync.Mutex + T *testing.T +} + +// NewLogHandler handler. +func NewLogHandler(t *testing.T) *LogHandler { + return &LogHandler{ + T: t, + } +} + +// HandleLog implements log.Handler. +func (h *LogHandler) HandleLog(e *log.Entry) error { + color := Colors[e.Level] + level := Strings[e.Level] + + var fields []field + + for k, v := range e.Fields { + fields = append(fields, field{k, v}) + } + + sort.Sort(byName(fields)) + + h.mu.Lock() + defer h.mu.Unlock() + + buf := bytes.NewBuffer([]byte{}) + + fmt.Fprintf(buf, "\033[%dm%6s\033[0m %-25s", color, level, e.Message) + + for _, f := range fields { + fmt.Fprintf(buf, " \033[%dm%s\033[0m=%v", color, f.Name, f.Value) + } + + h.T.Log(buf.String()) + + return nil +} From 2d01e5899724a08469e5f8f6593c2c7d920fe856 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 11:34:34 +0100 Subject: [PATCH 0408/2266] Complete last part about adapters protocols --- core/adapters/protocols.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/adapters/protocols.md b/core/adapters/protocols.md index 98528001c..238cdb82a 100644 --- a/core/adapters/protocols.md +++ b/core/adapters/protocols.md @@ -104,3 +104,11 @@ All those requests have empty payloads. basic+broadcast ~ http ====================== +The `broadcast` http adapter is an extension of the `basic` http adapter. This adapter enables +network discovery through a simple convention. When no recipient is provided to the adapter for +a send request, it will seemly broadcast the request to every accessible recipient reachable. + +Thus, because it relies on the basic http protocol, it will ignore `404 Not Found` responses +from servers but, will generate a new registration demand for a `200 Ok` received. So far, a +maximum of only one positive anwer among all is expected. Positive acknowledgement for +different servers will lead to an error. From 5886ae39ff1f6e79b82e99ffc9e5a78fdcf99b00 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 13:35:45 +0100 Subject: [PATCH 0409/2266] Make the basic http adapter taking care of opening the connection (mandatory for incoming requests) --- core/adapters/http/adapter.go | 32 ++++++++++++++++--- core/adapters/http/adapter_test.go | 2 +- core/adapters/http/broadcast/broadcast.go | 4 +-- .../adapters/http/broadcast/broadcast_test.go | 2 +- core/adapters/http/pubsub/pubsub.go | 23 ++----------- 5 files changed, 34 insertions(+), 29 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index a6c3d794b..48ebc9bc6 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -20,14 +20,19 @@ var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") type Adapter struct { - Ctx log.Interface + serveMux *http.ServeMux + Ctx log.Interface } // NewAdapter constructs and allocate a new Broker <-> Handler http adapter -func NewAdapter(ctx log.Interface) (*Adapter, error) { - return &Adapter{ - Ctx: ctx, - }, nil +func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { + a := Adapter{ + serveMux: http.NewServeMux(), + Ctx: ctx, + } + + go func() { a.listenRequests(port) }() + return &a, nil } // Send implements the core.Adapter interface @@ -105,6 +110,23 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) } +// listenRequests handles incoming registration request sent through http to the adapter +func (a *Adapter) listenRequests(port uint) { + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: a.serveMux, + } + a.Ctx.WithField("port", port).Info("Starting Server") + err := server.ListenAndServe() + a.Ctx.WithError(err).Warn("HTTP connection lost") +} + +// RegisterEndpoint can be used by an external agent to register a handler to the adapter servemux +func (a *Adapter) RegisterEndpoint(url string, handler func(w http.ResponseWriter, req *http.Request)) { + a.Ctx.WithField("url", url).Info("Register new endpoint") + a.serveMux.HandleFunc(url, handler) +} + // Next implements the core.Adapter interface func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { // NOTE not implemented diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index fe79ea149..5015704f8 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -46,7 +46,7 @@ func TestSend(t *testing.T) { "tag": "Adapter", }) - adapter, err := NewAdapter(ctx) + adapter, err := NewAdapter(3101, ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index dd14aa3a1..315b1be59 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -27,12 +27,12 @@ var ErrBadOptions = fmt.Errorf("Bad options provided") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") -func NewAdapter(recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { +func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { return nil, ErrBadOptions } - adapter, err := httpadapter.NewAdapter(ctx) + adapter, err := httpadapter.NewAdapter(port, ctx) if err != nil { return nil, err } diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index 4cdd60c3f..e6c23e2ab 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -77,7 +77,7 @@ func TestSend(t *testing.T) { }) // Build - adapter, err := NewAdapter(recipients, ctx) + adapter, err := NewAdapter(3015, recipients, ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index d84d7c378..2d18b7729 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -4,7 +4,6 @@ package pubsub import ( - "fmt" "net/http" "github.com/TheThingsNetwork/ttn/core" @@ -34,7 +33,7 @@ type regRes struct { // NewAdapter constructs a new http adapter that also handle registrations via http requests func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { - adapter, err := httpadapter.NewAdapter(ctx) + adapter, err := httpadapter.NewAdapter(port, ctx) if err != nil { return nil, err } @@ -45,7 +44,8 @@ func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { registrations: make(chan regReq), } - go a.listenRegistration(port) + // So far we only supports one endpoint [PUT] /end-device/:devAddr + a.RegisterEndpoint("/end-devices/", a.handlePutEndDevice) return a, nil } @@ -56,23 +56,6 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) return request.Registration, regAckNacker{response: request.response}, nil } -// listenRegistration handles incoming registration request sent through http to the adapter -func (a *Adapter) listenRegistration(port uint) { - // Create a server multiplexer to handle request - serveMux := http.NewServeMux() - - // So far we only supports one endpoint [PUT] /end-device/:devAddr - serveMux.HandleFunc("/end-devices/", a.handlePutEndDevice) - - server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), - Handler: serveMux, - } - a.Ctx.WithField("port", port).Info("Starting Server") - err := server.ListenAndServe() - a.Ctx.WithError(err).Warn("HTTP connection lost") -} - // fail logs the given failure and sends an appropriate response to the client func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { w.WriteHeader(http.StatusBadRequest) From 139ded9621943190aa9bc0cd12d7ea147e3ee614 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 14:31:46 +0100 Subject: [PATCH 0410/2266] Make adapter a real external module for subcomponent + start implementation backbone of Next() --- core/adapters/http/adapter.go | 42 ++++++++++++------ core/adapters/http/broadcast/broadcast.go | 8 +--- core/adapters/http/packet_listener.go | 48 +++++++++++++++++++++ core/adapters/http/pubsub/handler_parser.go | 5 +++ core/adapters/http/pubsub/pubsub.go | 29 +++---------- core/adapters/http/utils.go | 12 ++++++ 6 files changed, 101 insertions(+), 43 deletions(-) create mode 100644 core/adapters/http/packet_listener.go create mode 100644 core/adapters/http/utils.go diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 48ebc9bc6..6f46ef6a9 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -20,14 +20,32 @@ var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") type Adapter struct { + Parser serveMux *http.ServeMux + packets chan pktReq Ctx log.Interface } +type Parser interface { + Parse(req *http.Request) (core.Packet, error) +} + +type pktReq struct { + core.Packet + response chan pktRes +} + +type pktRes struct { + statusCode int + content []byte +} + // NewAdapter constructs and allocate a new Broker <-> Handler http adapter -func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { +func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { a := Adapter{ + Parser: parser, serveMux: http.NewServeMux(), + packets: make(chan pktReq), Ctx: ctx, } @@ -110,17 +128,6 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) } -// listenRequests handles incoming registration request sent through http to the adapter -func (a *Adapter) listenRequests(port uint) { - server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), - Handler: a.serveMux, - } - a.Ctx.WithField("port", port).Info("Starting Server") - err := server.ListenAndServe() - a.Ctx.WithError(err).Warn("HTTP connection lost") -} - // RegisterEndpoint can be used by an external agent to register a handler to the adapter servemux func (a *Adapter) RegisterEndpoint(url string, handler func(w http.ResponseWriter, req *http.Request)) { a.Ctx.WithField("url", url).Info("Register new endpoint") @@ -137,3 +144,14 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { return core.Packet{}, nil, nil } + +// listenRequests handles incoming registration request sent through http to the adapter +func (a *Adapter) listenRequests(port uint) { + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: a.serveMux, + } + a.Ctx.WithField("port", port).Info("Starting Server") + err := server.ListenAndServe() + a.Ctx.WithError(err).Warn("HTTP connection lost") +} diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 315b1be59..fdfcd7b35 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -14,7 +14,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/apex/log" ) type Adapter struct { @@ -27,16 +26,11 @@ var ErrBadOptions = fmt.Errorf("Bad options provided") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") -func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { +func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient) (*Adapter, error) { if len(recipients) == 0 { return nil, ErrBadOptions } - adapter, err := httpadapter.NewAdapter(port, ctx) - if err != nil { - return nil, err - } - return &Adapter{ Adapter: adapter, recipients: recipients, diff --git a/core/adapters/http/packet_listener.go b/core/adapters/http/packet_listener.go new file mode 100644 index 000000000..1c8c54934 --- /dev/null +++ b/core/adapters/http/packet_listener.go @@ -0,0 +1,48 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "net/http" + + "github.com/TheThingsNetwork/ttn/core" +) + +func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { + ctx := a.Ctx.WithField("sender", req.RemoteAddr) + + ctx.Debug("Receiving new registration request") + // Check the http method + if req.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [POST] to transfer a packet")) + return + } + + // Parse body and query params + packet, err := a.Parse(req) + if err != nil { + ctx.WithError(err).Warn("Received invalid body in request") + BadRequest(w, err.Error()) + return + } + + // Send the packet and wait for ack / nack + response := make(chan pktRes) + a.packets <- pktReq{Packet: packet, response: response} + r, ok := <-response + if !ok { + ctx.Error("Core server not responding") + BadRequest(w, "Core server not responding") + return + } + w.WriteHeader(r.statusCode) + w.Write(r.content) +} + +type JSONPacketParser struct{} + +func (p JSONPacketParser) Parse(req *http.Request) (core.Packet, error) { + return core.Packet{}, nil +} diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go index 729f0c8c1..a77de7b89 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/pubsub/handler_parser.go @@ -19,6 +19,11 @@ import ( type HandlerParser struct{} func (p HandlerParser) Parse(req *http.Request) (core.Registration, error) { + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + return core.Registration{}, fmt.Errorf("Received invalid content-type in request") + } + // Check the query parameter reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{8})$") query := reg.FindStringSubmatch(req.RequestURI) diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 2d18b7729..117be5b27 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -8,7 +8,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/apex/log" ) type Adapter struct { @@ -32,12 +31,7 @@ type regRes struct { } // NewAdapter constructs a new http adapter that also handle registrations via http requests -func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { - adapter, err := httpadapter.NewAdapter(port, ctx) - if err != nil { - return nil, err - } - +func NewAdapter(adapter *httpadapter.Adapter, parser Parser) (*Adapter, error) { a := &Adapter{ Adapter: adapter, Parser: parser, @@ -56,17 +50,11 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) return request.Registration, regAckNacker{response: request.response}, nil } -// fail logs the given failure and sends an appropriate response to the client -func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(msg)) -} - // handle request [PUT] on /end-device/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { ctx := a.Ctx.WithField("sender", req.RemoteAddr) - ctx.Debug("Receiving new registration request") + // Check the http method if req.Method != "PUT" { w.WriteHeader(http.StatusMethodNotAllowed) @@ -74,18 +62,11 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { return } - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - ctx.Warn("Received invalid content-type in request") - a.badRequest(w, "Incorrect content type") - return - } - // Parse body and query params config, err := a.Parse(req) if err != nil { - ctx.WithError(err).Warn("Received invalid body in request") - a.badRequest(w, err.Error()) + ctx.WithError(err).Warn("Received invalid request") + httpadapter.BadRequest(w, err.Error()) return } @@ -95,7 +76,7 @@ func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { r, ok := <-response if !ok { ctx.Error("Core server not responding") - a.badRequest(w, "Core server not responding") + httpadapter.BadRequest(w, "Core server not responding") return } w.WriteHeader(r.statusCode) diff --git a/core/adapters/http/utils.go b/core/adapters/http/utils.go new file mode 100644 index 000000000..1fbdcaa02 --- /dev/null +++ b/core/adapters/http/utils.go @@ -0,0 +1,12 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import "net/http" + +// fail logs the given failure and sends an appropriate response to the client +func BadRequest(w http.ResponseWriter, msg string) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(msg)) +} From 203b06dc2d0643ddd2c076f133c9d9a073b82a46 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 15:12:06 +0100 Subject: [PATCH 0411/2266] HTTP Adapter does not error on non-critical faulty server This resolves #17 for now. --- core/adapters/http/adapter.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index a6c3d794b..e7c8b96fc 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -57,52 +57,68 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) a.Ctx.WithField("recipient", recipient).Debug("POST Request") buf := new(bytes.Buffer) buf.Write([]byte(payload)) + + // Send request resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) if err != nil { cherr <- err return } - defer resp.Body.Close() + + // Check response code if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated { cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) return } + // Process response body raw := make([]byte, resp.ContentLength) n, err := resp.Body.Read(raw) + defer resp.Body.Close() if err != nil && err != io.EOF { cherr <- err return } + + // Process packet var packet core.Packet if err := json.Unmarshal(raw[:n], &packet); err != nil { cherr <- err return } + chresp <- packet }(recipient) } // Wait for each request to be done, and return wg.Wait() + + // Collect errors var errors []error for i := 0; i < len(cherr); i += 1 { errors = append(errors, <-cherr) } - if errors != nil { - return core.Packet{}, fmt.Errorf("Errors: %v", errors) - } + // Check responses if len(chresp) > 1 { return core.Packet{}, fmt.Errorf("Several positive answer from servers") } + + // Get packet select { case packet := <-chresp: return packet, nil default: - return core.Packet{}, fmt.Errorf("Unexpected error. No response packet available") + return core.Packet{}, fmt.Errorf("No response packet available") + } + + // Return Errors + if errors != nil { + return core.Packet{}, fmt.Errorf("Errors: %v", errors) } + return core.Packet{}, fmt.Errorf("Unexpected error") } // Next implements the core.Adapter interface From 40bf5479940ca6c1be210e88aea624da3159d0b5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 15:15:54 +0100 Subject: [PATCH 0412/2266] Remove unreacable code --- core/adapters/http/adapter.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index e7c8b96fc..359902429 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -110,15 +110,11 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) case packet := <-chresp: return packet, nil default: + if errors != nil { + return core.Packet{}, fmt.Errorf("Errors: %v", errors) + } return core.Packet{}, fmt.Errorf("No response packet available") } - - // Return Errors - if errors != nil { - return core.Packet{}, fmt.Errorf("Errors: %v", errors) - } - - return core.Packet{}, fmt.Errorf("Unexpected error") } // Next implements the core.Adapter interface From 509b4a15d8b47b106c2068933c5d9dbe643b65fc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 14:14:24 +0100 Subject: [PATCH 0413/2266] Add func GetLogger for testing --- core/adapters/http/adapter_test.go | 6 +----- core/adapters/http/broadcast/broadcast_test.go | 6 +----- core/adapters/http/pubsub/pubsub_test.go | 6 +----- core/adapters/semtech/build_utilities_test.go | 6 +----- utils/testing/testing.go | 10 ++++++++++ 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index fe79ea149..846954430 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -16,7 +16,6 @@ import ( "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/apex/log" ) // Send(p core.Packet, r ...core.Recipient) error @@ -41,10 +40,7 @@ func TestSend(t *testing.T) { s := genMockServer(3100) // Logging - log.SetHandler(NewLogHandler(t)) - ctx := log.WithFields(log.Fields{ - "tag": "Adapter", - }) + ctx := GetLogger(t, "Adapter") adapter, err := NewAdapter(ctx) if err != nil { diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index 4cdd60c3f..e26987c01 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -15,7 +15,6 @@ import ( "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/apex/log" ) func TestSend(t *testing.T) { @@ -71,10 +70,7 @@ func TestSend(t *testing.T) { } // Logging - log.SetHandler(NewLogHandler(t)) - ctx := log.WithFields(log.Fields{ - "tag": "Adapter", - }) + ctx := GetLogger(t, "Adapter") // Build adapter, err := NewAdapter(recipients, ctx) diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 4e620990e..de1fb90e2 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -14,7 +14,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/lorawan" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/apex/log" ) // NextRegistration() (core.Registration, core.AckNacker, error) @@ -61,10 +60,7 @@ func TestNextRegistration(t *testing.T) { } // Logging - log.SetHandler(NewLogHandler(t)) - ctx := log.WithFields(log.Fields{ - "tag": "Adapter", - }) + ctx := GetLogger(t, "Adapter") adapter, err := NewAdapter(3021, HandlerParser{}, ctx) client := &client{adapter: "0.0.0.0:3021"} diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index 2d0d9deac..dabc3c5eb 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -15,7 +15,6 @@ import ( "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/apex/log" ) // ----- build utilities @@ -74,10 +73,7 @@ func (s mockServer) send(p semtech.Packet) semtech.Packet { // in a select for timeout) func genAdapter(t *testing.T, port uint) (*Adapter, chan interface{}) { // Logging - log.SetHandler(NewLogHandler(t)) - ctx := log.WithFields(log.Fields{ - "tag": "Adapter", - }) + ctx := GetLogger(t, "Adapter") adapter, err := NewAdapter(port, ctx) if err != nil { diff --git a/utils/testing/testing.go b/utils/testing/testing.go index f9b641eb0..f1faccd5a 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -8,8 +8,18 @@ package testing import ( "fmt" "testing" + + "github.com/apex/log" ) +func GetLogger(t *testing.T, tag string) log.Interface { + logger := &log.Logger{ + Handler: NewLogHandler(t), + Level: log.DebugLevel, + } + return logger.WithField("tag", "Adapter") +} + // Ok displays a green check symbol func Ok(t *testing.T, tag string) { t.Log(fmt.Sprintf("\033[32;1m\u2714 ok | %s\033[0m", tag)) From acf3d81c60b28c01436f17c6b9d9b54c983627f7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 14:18:32 +0100 Subject: [PATCH 0414/2266] Use dark gray color for debug --- utils/testing/log_handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/testing/log_handler.go b/utils/testing/log_handler.go index 94bc19e89..73af0e0b3 100644 --- a/utils/testing/log_handler.go +++ b/utils/testing/log_handler.go @@ -18,7 +18,7 @@ const ( green = 32 yellow = 33 blue = 34 - gray = 37 + gray = 90 ) // Colors mapping. From afc22e7641f827c839e8e20ee3bdc8daf2ca41ba Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 14:18:53 +0100 Subject: [PATCH 0415/2266] Add logging to Broker component --- core/components/broker.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/components/broker.go b/core/components/broker.go index 2365835d0..12d586874 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -33,13 +33,16 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter // 1. Lookup for entries for the associated device devAddr, err := p.DevAddr() if err != nil { + b.Ctx.Warn("Uplink Invalid") an.Nack() return ErrInvalidPacket } + ctx := b.Ctx.WithField("devAddr", devAddr) entries, err := b.db.lookup(devAddr) switch err { case nil: case ErrDeviceNotFound: + ctx.Warn("Uplink device not found") return an.Nack() default: an.Nack() @@ -59,12 +62,12 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter Id: entry.Id, Address: entry.Url, } - b.Ctx.WithFields(log.Fields{"devAddr": devAddr, "handler": handler}).Debug("Associated device with handler") + ctx.WithField("handler", handler).Debug("Associated device with handler") break } } if handler == nil { - b.Ctx.WithField("devAddr", devAddr).Warn("Could not find handler for device") + ctx.Warn("Could not find handler for device") return an.Nack() } @@ -86,15 +89,21 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { url, okUrl := r.Recipient.Address.(string) nwsKey, okNwsKey := r.Options.(lorawan.AES128Key) + ctx := b.Ctx.WithField("devAddr", r.DevAddr) + if !(okId && okUrl && okNwsKey) { + ctx.Warn("Invalid Registration") an.Nack() return ErrInvalidRegistration } entry := brokerEntry{Id: id, Url: url, NwsKey: nwsKey} if err := b.db.store(r.DevAddr, entry); err != nil { + ctx.WithError(err).Error("Failed Registration") an.Nack() return err } + + ctx.Debug("Successful Registration") return an.Ack() } From 3ab421d003b4c38c80ba09b3f48bf084da36d6fc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 14:19:08 +0100 Subject: [PATCH 0416/2266] Add logging to HTTP adapter --- core/adapters/http/adapter.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 359902429..6ac77bd11 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -35,14 +35,20 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { + a.Ctx.WithError(err).Warn("Invalid Packet") return core.Packet{}, ErrInvalidPacket } pl, err := p.Payload.MarshalBinary() if err != nil { + a.Ctx.WithError(err).Warn("Invalid Packet") return core.Packet{}, ErrInvalidPacket } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) + devAddr, _ := p.DevAddr() + ctx := a.Ctx.WithField("devAddr", devAddr) + ctx.Debug("Sending Packet") + // Prepare ground for parrallel http request nb := len(r) cherr := make(chan error, nb) @@ -54,7 +60,10 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) for _, recipient := range r { go func(recipient core.Recipient) { defer wg.Done() - a.Ctx.WithField("recipient", recipient).Debug("POST Request") + + ctx := ctx.WithField("recipient", recipient) + ctx.Debug("POST Request") + buf := new(bytes.Buffer) buf.Write([]byte(payload)) @@ -67,6 +76,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Check response code if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated { + ctx.WithField("response", resp.StatusCode).Warn("Unexpected response") cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) return } @@ -97,11 +107,14 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Collect errors var errors []error for i := 0; i < len(cherr); i += 1 { - errors = append(errors, <-cherr) + err := <-cherr + ctx.WithError(err).Error("POST Failed") + errors = append(errors, err) } // Check responses if len(chresp) > 1 { + ctx.WithField("response_count", len(chresp)).Error("Received Too many positive answers") return core.Packet{}, fmt.Errorf("Several positive answer from servers") } @@ -113,6 +126,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) if errors != nil { return core.Packet{}, fmt.Errorf("Errors: %v", errors) } + ctx.Error("No response packet available") return core.Packet{}, fmt.Errorf("No response packet available") } } From 4154f54e57bcfc1235d7bd0eb2ae5bd80d7081b0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jan 2016 14:45:06 +0100 Subject: [PATCH 0417/2266] Make logging context for adapters unexported Resolves #19 --- core/adapters/http/adapter.go | 10 +++---- core/adapters/http/broadcast/broadcast.go | 6 +++- core/adapters/http/pubsub/pubsub.go | 10 +++++-- core/adapters/semtech/adapter.go | 34 +++++++++++------------ core/components/broker.go | 10 +++---- 5 files changed, 39 insertions(+), 31 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 6ac77bd11..4b67546a8 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -20,13 +20,13 @@ var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") type Adapter struct { - Ctx log.Interface + ctx log.Interface } // NewAdapter constructs and allocate a new Broker <-> Handler http adapter func NewAdapter(ctx log.Interface) (*Adapter, error) { return &Adapter{ - Ctx: ctx, + ctx: ctx, }, nil } @@ -35,18 +35,18 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - a.Ctx.WithError(err).Warn("Invalid Packet") + a.ctx.WithError(err).Warn("Invalid Packet") return core.Packet{}, ErrInvalidPacket } pl, err := p.Payload.MarshalBinary() if err != nil { - a.Ctx.WithError(err).Warn("Invalid Packet") + a.ctx.WithError(err).Warn("Invalid Packet") return core.Packet{}, ErrInvalidPacket } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) devAddr, _ := p.DevAddr() - ctx := a.Ctx.WithField("devAddr", devAddr) + ctx := a.ctx.WithField("devAddr", devAddr) ctx.Debug("Sending Packet") // Prepare ground for parrallel http request diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index dd14aa3a1..02ccb27ea 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -18,6 +18,8 @@ import ( ) type Adapter struct { + ctx log.Interface + *httpadapter.Adapter recipients []core.Recipient registrations chan core.Registration @@ -38,6 +40,8 @@ func NewAdapter(recipients []core.Recipient, ctx log.Interface) (*Adapter, error } return &Adapter{ + ctx: ctx, + Adapter: adapter, recipients: recipients, registrations: make(chan core.Registration, len(recipients)), @@ -85,7 +89,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { go func(recipient core.Recipient) { defer wg.Done() - ctx := a.Ctx.WithField("recipient", recipient) + ctx := a.ctx.WithField("recipient", recipient) ctx.Debug("POST Request") diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index d84d7c378..4820847c5 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -13,6 +13,8 @@ import ( ) type Adapter struct { + ctx log.Interface + *httpadapter.Adapter Parser registrations chan regReq @@ -40,6 +42,8 @@ func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { } a := &Adapter{ + ctx: ctx, + Adapter: adapter, Parser: parser, registrations: make(chan regReq), @@ -68,9 +72,9 @@ func (a *Adapter) listenRegistration(port uint) { Addr: fmt.Sprintf("0.0.0.0:%d", port), Handler: serveMux, } - a.Ctx.WithField("port", port).Info("Starting Server") + a.ctx.WithField("port", port).Info("Starting Server") err := server.ListenAndServe() - a.Ctx.WithError(err).Warn("HTTP connection lost") + a.ctx.WithError(err).Warn("HTTP connection lost") } // fail logs the given failure and sends an appropriate response to the client @@ -81,7 +85,7 @@ func (a *Adapter) badRequest(w http.ResponseWriter, msg string) { // handle request [PUT] on /end-device/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { - ctx := a.Ctx.WithField("sender", req.RemoteAddr) + ctx := a.ctx.WithField("sender", req.RemoteAddr) ctx.Debug("Receiving new registration request") // Check the http method diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 99480c55b..b58e38a2a 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -13,7 +13,7 @@ import ( ) type Adapter struct { - Ctx log.Interface + ctx log.Interface conn chan udpMsg next chan rxpkMsg } @@ -37,7 +37,7 @@ var ErrInvalidPacket error = fmt.Errorf("Invalid packet supplied") // New constructs and allocates a new udp_sender adapter func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a := Adapter{ - Ctx: ctx, + ctx: ctx, conn: make(chan udpMsg), next: make(chan rxpkMsg), } @@ -45,9 +45,9 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - a.Ctx.WithField("port", port).Info("Starting Server") + a.ctx.WithField("port", port).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.Ctx.WithError(err).Error("Unable to start server") + a.ctx.WithError(err).Error("Unable to start server") return nil, ErrInvalidPort } @@ -76,7 +76,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { msg := <-a.next packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { - a.Ctx.Debug("Received invalid packet") + a.ctx.Debug("Received invalid packet") return core.Packet{}, nil, ErrInvalidPacket } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil @@ -90,20 +90,20 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) // listen Handle incoming packets and forward them func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() - a.Ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") + a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") for { buf := make([]byte, 128) n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection - a.Ctx.WithError(err).Error("Connection error") + a.ctx.WithError(err).Error("Connection error") continue } - a.Ctx.WithField("datagram", buf[:n]).Debug("Incoming datagram") + a.ctx.WithField("datagram", buf[:n]).Debug("Incoming datagram") pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) if err != nil { - a.Ctx.WithError(err).Warn("Invalid packet") + a.ctx.WithError(err).Warn("Invalid packet") continue } @@ -115,10 +115,10 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PULL_ACK, }.MarshalBinary() if err != nil { - a.Ctx.WithError(err).Error("Unexpected error while marshaling PULL_ACK") + a.ctx.WithError(err).Error("Unexpected error while marshaling PULL_ACK") continue } - a.Ctx.WithField("recipient", addr).Debug("Sending PULL_ACK") + a.ctx.WithField("recipient", addr).Debug("Sending PULL_ACK") a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component pushAck, err := semtech.Packet{ @@ -127,14 +127,14 @@ func (a *Adapter) listen(conn *net.UDPConn) { Identifier: semtech.PUSH_ACK, }.MarshalBinary() if err != nil { - a.Ctx.WithError(err).Error("Unexpected error while marshaling PUSH_ACK") + a.ctx.WithError(err).Error("Unexpected error while marshaling PUSH_ACK") continue } - a.Ctx.WithField("Recipient", addr).Debug("Sending PUSH_ACK") + a.ctx.WithField("Recipient", addr).Debug("Sending PUSH_ACK") a.conn <- udpMsg{addr: addr, raw: pushAck} if pkt.Payload == nil { - a.Ctx.WithField("packet", pkt).Warn("Invalid PUSH_DATA packet") + a.ctx.WithField("packet", pkt).Warn("Invalid PUSH_DATA packet") continue } for _, rxpk := range pkt.Payload.RXPK { @@ -144,7 +144,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { } } default: - a.Ctx.WithField("packet", pkt).Debug("Ignoring unexpected packet") + a.ctx.WithField("packet", pkt).Debug("Ignoring unexpected packet") continue } } @@ -156,7 +156,7 @@ func (a *Adapter) monitorConnection() { for msg := range a.conn { if msg.conn != nil { // Change the connection if udpConn != nil { - a.Ctx.Debug("Define new UDP connection") + a.ctx.Debug("Define new UDP connection") udpConn.Close() } udpConn = msg.conn @@ -164,7 +164,7 @@ func (a *Adapter) monitorConnection() { if udpConn != nil && msg.raw != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.Ctx.WithError(err).Error("Error while sending UDP message") + a.ctx.WithError(err).Error("Error while sending UDP message") } } } diff --git a/core/components/broker.go b/core/components/broker.go index 12d586874..1cc1934da 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -12,7 +12,7 @@ import ( ) type Broker struct { - Ctx log.Interface + ctx log.Interface db brokerStorage } @@ -24,7 +24,7 @@ func NewBroker(ctx log.Interface) (*Broker, error) { } return &Broker{ - Ctx: ctx, + ctx: ctx, db: localDB, }, nil } @@ -33,11 +33,11 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter // 1. Lookup for entries for the associated device devAddr, err := p.DevAddr() if err != nil { - b.Ctx.Warn("Uplink Invalid") + b.ctx.Warn("Uplink Invalid") an.Nack() return ErrInvalidPacket } - ctx := b.Ctx.WithField("devAddr", devAddr) + ctx := b.ctx.WithField("devAddr", devAddr) entries, err := b.db.lookup(devAddr) switch err { case nil: @@ -89,7 +89,7 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { url, okUrl := r.Recipient.Address.(string) nwsKey, okNwsKey := r.Options.(lorawan.AES128Key) - ctx := b.Ctx.WithField("devAddr", r.DevAddr) + ctx := b.ctx.WithField("devAddr", r.DevAddr) if !(okId && okUrl && okNwsKey) { ctx.Warn("Invalid Registration") From 443768308b2ef02e908117b76929855bb03a6e85 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 16:08:59 +0100 Subject: [PATCH 0418/2266] Write tests for next() method --- core/adapters/http/adapter_test.go | 131 +++++++++++++++++++++++++++-- 1 file changed, 125 insertions(+), 6 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 5015704f8..66dce075a 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -4,7 +4,7 @@ package http import ( - "encoding/base64" + "bytes" "encoding/json" "fmt" "io" @@ -17,6 +17,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" + "reflect" ) // Send(p core.Packet, r ...core.Recipient) error @@ -46,7 +47,7 @@ func TestSend(t *testing.T) { "tag": "Adapter", }) - adapter, err := NewAdapter(3101, ctx) + adapter, err := NewAdapter(3101, JSONPacketParser{}, ctx) if err != nil { panic(err) } @@ -60,6 +61,89 @@ func TestSend(t *testing.T) { } } +// Next() (core.Packet, an core.AckNacker, error) +func TestNext(t *testing.T) { + tests := []struct { + Payload string + IsNotFound bool + WantPacket core.Packet + WantStatus int + WantError error + }{ + { + Payload: genJSONPayload(genCorePacket()), + IsNotFound: false, + WantPacket: genCorePacket(), + WantStatus: http.StatusOK, + WantError: nil, + }, + { + Payload: genJSONPayload(genCorePacket()), + IsNotFound: true, + WantPacket: genCorePacket(), + WantStatus: http.StatusNotFound, + WantError: nil, + }, + { + Payload: "Patate", + IsNotFound: false, + WantPacket: core.Packet{}, + WantStatus: http.StatusBadRequest, + WantError: nil, + }, + } + // Build + log.SetHandler(NewLogHandler(t)) + ctx := log.WithFields(log.Fields{"tag": "Adapter"}) + adapter, err := NewAdapter(3102, JSONPacketParser{}, ctx) + if err != nil { + panic(err) + } + + c := client{adapter: "0.0.0.0:3102"} + + for _, test := range tests { + // Describe + Desc(t, "Send payload to the adapter %s", test.Payload) + <-time.After(time.Millisecond * 100) + + // Operate + gotPacket := make(chan core.Packet) + gotError := make(chan error) + go func() { + packet, an, err := adapter.Next() + if err != nil { + if test.IsNotFound { + an.Nack() + } else { + an.Ack(core.Packet{}) + } + } + gotError <- err + gotPacket <- packet + }() + + resp := c.send(test.Payload) + + // Check + select { + case err := <-gotError: + checkErrors(t, test.WantError, err) + case <-time.After(time.Millisecond * 250): + checkErrors(t, test.WantError, nil) + } + + select { + case packet := <-gotPacket: + checkPackets(t, test.WantPacket, packet) + case <-time.After(time.Millisecond * 250): + checkPackets(t, test.WantPacket, core.Packet{}) + } + + checkStatus(t, test.WantStatus, resp.StatusCode) + } +} + // Check utilities func checkErrors(t *testing.T, want error, got error) { if want == got { @@ -85,6 +169,22 @@ func checkSend(t *testing.T, want string, s MockServer) { Ok(t, "Check send result") } +func checkPackets(t *testing.T, want core.Packet, got core.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", want, got) +} + +func checkStatus(t *testing.T, want int, got int) { + if want == got { + Ok(t, "Check status") + return + } + Ko(t, "Expected status to be %d but got %d", want, got) +} + // Build utilities type MockServer struct { Recipient core.Recipient @@ -169,14 +269,33 @@ func genCorePacket() core.Packet { } func genJSONPayload(p core.Packet) string { - raw, err := p.Payload.MarshalBinary() + raw, err := json.Marshal(p) + if err != nil { + panic(err) + } + return string(raw) +} + +type client struct { + http.Client + adapter string +} + +// Operate utilities +// send is a convinient helper to send HTTP from a handler to the adapter +func (c *client) send(payload string) http.Response { + buf := new(bytes.Buffer) + if _, err := buf.WriteString(payload); err != nil { + panic(err) + } + request, err := http.NewRequest("POST", fmt.Sprintf("http://%s/packets/", c.adapter), buf) if err != nil { panic(err) } - payload := base64.StdEncoding.EncodeToString(raw) - metadatas, err := json.Marshal(p.Metadata) + request.Header.Set("Content-Type", "application/json") + resp, err := c.Do(request) if err != nil { panic(err) } - return fmt.Sprintf(`{"payload":"%s","metadata":%s}`, payload, string(metadatas)) + return *resp } From f7a41b597863b4d737c87fbfd552077a5ddcf955 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 17:24:27 +0100 Subject: [PATCH 0419/2266] Fix adapter tests --- core/adapters/http/adapter_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 66dce075a..580f2ce2d 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -104,7 +104,7 @@ func TestNext(t *testing.T) { for _, test := range tests { // Describe - Desc(t, "Send payload to the adapter %s", test.Payload) + Desc(t, "Send payload to the adapter %s. Will send ack ? %v", test.Payload, !test.IsNotFound) <-time.After(time.Millisecond * 100) // Operate @@ -112,11 +112,11 @@ func TestNext(t *testing.T) { gotError := make(chan error) go func() { packet, an, err := adapter.Next() - if err != nil { + if err == nil { if test.IsNotFound { an.Nack() } else { - an.Ack(core.Packet{}) + an.Ack() } } gotError <- err @@ -133,6 +133,10 @@ func TestNext(t *testing.T) { checkErrors(t, test.WantError, nil) } + checkStatus(t, test.WantStatus, resp.StatusCode) + + // NOTE: See https://github.com/brocaar/lorawan/issues/3 + continue select { case packet := <-gotPacket: checkPackets(t, test.WantPacket, packet) @@ -140,7 +144,6 @@ func TestNext(t *testing.T) { checkPackets(t, test.WantPacket, core.Packet{}) } - checkStatus(t, test.WantStatus, resp.StatusCode) } } @@ -288,7 +291,7 @@ func (c *client) send(payload string) http.Response { if _, err := buf.WriteString(payload); err != nil { panic(err) } - request, err := http.NewRequest("POST", fmt.Sprintf("http://%s/packets/", c.adapter), buf) + request, err := http.NewRequest("POST", fmt.Sprintf("http://%s/packets", c.adapter), buf) if err != nil { panic(err) } From 0364a3da6c7130cd6475e53d5c66e9104d7f0878 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 17:24:45 +0100 Subject: [PATCH 0420/2266] Write actual implementation for listen method --- core/adapters/http/adapter.go | 11 ++++-- core/adapters/http/packet_acknacker.go | 55 ++++++++++++++++++++++++++ core/adapters/http/packet_listener.go | 21 +++++++++- 3 files changed, 82 insertions(+), 5 deletions(-) create mode 100644 core/adapters/http/packet_acknacker.go diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 6f46ef6a9..867f70015 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -18,6 +18,7 @@ import ( var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") +var ErrNotImplemented = fmt.Errorf("Illegal call on non-implemented method") type Adapter struct { Parser @@ -49,7 +50,9 @@ func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { Ctx: ctx, } - go func() { a.listenRequests(port) }() + a.RegisterEndpoint("/packets", a.handlePostPacket) + go a.listenRequests(port) + return &a, nil } @@ -136,13 +139,13 @@ func (a *Adapter) RegisterEndpoint(url string, handler func(w http.ResponseWrite // Next implements the core.Adapter interface func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { - // NOTE not implemented - return core.Packet{}, nil, nil + pktReq := <-a.packets + return pktReq.Packet, packetAckNacker{response: pktReq.response}, nil } // NextRegistration implements the core.Adapter interface func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, nil + return core.Packet{}, nil, ErrNotImplemented } // listenRequests handles incoming registration request sent through http to the adapter diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go new file mode 100644 index 000000000..016c269ca --- /dev/null +++ b/core/adapters/http/packet_acknacker.go @@ -0,0 +1,55 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/TheThingsNetwork/ttn/core" +) + +var ErrConnectionLost = fmt.Errorf("Connection has been lost") +var ErrInvalidArguments = fmt.Errorf("Invalid arguments supplied") + +type packetAckNacker struct { + response chan pktRes // A channel dedicated to send back a response +} + +// Ack implements the core.Acker interface +func (an packetAckNacker) Ack(p ...core.Packet) error { + if len(p) > 1 { + return ErrInvalidArguments + } + var raw []byte + if len(p) == 1 { + var err error + raw, err = json.Marshal(p[0]) + if err != nil { + return err + } + } + + select { + case an.response <- pktRes{statusCode: http.StatusOK, content: raw}: + return nil + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } +} + +// Nack implements the core.Nacker interface +func (an packetAckNacker) Nack() error { + select { + case an.response <- pktRes{ + statusCode: http.StatusNotFound, + content: []byte(`{"message":"Not in charge of the associated device"}`), + }: + case <-time.After(time.Millisecond * 50): + return ErrConnectionLost + } + return nil +} diff --git a/core/adapters/http/packet_listener.go b/core/adapters/http/packet_listener.go index 1c8c54934..ef35fa336 100644 --- a/core/adapters/http/packet_listener.go +++ b/core/adapters/http/packet_listener.go @@ -4,6 +4,9 @@ package http import ( + "encoding/json" + "fmt" + "io" "net/http" "github.com/TheThingsNetwork/ttn/core" @@ -44,5 +47,21 @@ func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { type JSONPacketParser struct{} func (p JSONPacketParser) Parse(req *http.Request) (core.Packet, error) { - return core.Packet{}, nil + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + return core.Packet{}, fmt.Errorf("Received invalid content-type in request") + } + + // Check configuration in body + body := make([]byte, req.ContentLength) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + return core.Packet{}, err + } + packet := new(core.Packet) + if err := json.Unmarshal(body[:n], packet); err != nil { + return core.Packet{}, err + } + + return *packet, nil } From cf308fe86c0cd880fa4356b71c3658cab89514b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 17:49:32 +0100 Subject: [PATCH 0421/2266] Update adapters constructors signatures --- core/adapters/http/broadcast/broadcast.go | 5 ++++- core/adapters/http/broadcast/broadcast_test.go | 7 ++++++- core/adapters/http/pubsub/pubsub.go | 5 ++++- core/adapters/http/pubsub/pubsub_test.go | 7 ++++++- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index fdfcd7b35..efedb0706 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -14,10 +14,12 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/apex/log" ) type Adapter struct { *httpadapter.Adapter + ctx log.Interface recipients []core.Recipient registrations chan core.Registration } @@ -26,13 +28,14 @@ var ErrBadOptions = fmt.Errorf("Bad options provided") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") -func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient) (*Adapter, error) { +func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { return nil, ErrBadOptions } return &Adapter{ Adapter: adapter, + ctx: ctx, recipients: recipients, registrations: make(chan core.Registration, len(recipients)), }, nil diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index e6c23e2ab..8393662ac 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -77,7 +78,11 @@ func TestSend(t *testing.T) { }) // Build - adapter, err := NewAdapter(3015, recipients, ctx) + a, err := httpadapter.NewAdapter(3015, httpadapter.JSONPacketParser{}, ctx) + if err != nil { + panic(err) + } + adapter, err := NewAdapter(a, recipients, ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 117be5b27..55d6c1dfa 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -8,11 +8,13 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/apex/log" ) type Adapter struct { *httpadapter.Adapter Parser + ctx log.Interface registrations chan regReq } @@ -31,10 +33,11 @@ type regRes struct { } // NewAdapter constructs a new http adapter that also handle registrations via http requests -func NewAdapter(adapter *httpadapter.Adapter, parser Parser) (*Adapter, error) { +func NewAdapter(adapter *httpadapter.Adapter, parser Parser, ctx log.Interface) (*Adapter, error) { a := &Adapter{ Adapter: adapter, Parser: parser, + ctx: ctx, registrations: make(chan regReq), } diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 4e620990e..b04313f5b 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -12,6 +12,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/lorawan" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" @@ -65,8 +66,12 @@ func TestNextRegistration(t *testing.T) { ctx := log.WithFields(log.Fields{ "tag": "Adapter", }) + a, err := httpadapter.NewAdapter(3021, httpadapter.JSONPacketParser{}, ctx) + if err != nil { + panic(err) + } - adapter, err := NewAdapter(3021, HandlerParser{}, ctx) + adapter, err := NewAdapter(a, HandlerParser{}, ctx) client := &client{adapter: "0.0.0.0:3021"} if err != nil { panic(err) From 306297f4e72d6b7760200e9fdd2392cfc76e3b30 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 17:49:52 +0100 Subject: [PATCH 0422/2266] Add missing handler for new logger in router integration --- integration/router/main.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/integration/router/main.go b/integration/router/main.go index 576de14b8..66b246df2 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -6,22 +6,25 @@ package main import ( "flag" "fmt" + "os" "strconv" "strings" . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" "github.com/TheThingsNetwork/ttn/core/adapters/semtech" "github.com/TheThingsNetwork/ttn/core/components" "github.com/apex/log" + "github.com/apex/log/handlers/text" ) func main() { // Parse options - brokers, udpPort := parseOptions() + brokers, tcpPort, udpPort := parseOptions() // Create Logging Context - + log.SetHandler(text.New(os.Stdout)) ctx := log.WithFields(log.Fields{ "component": "Router", }) @@ -32,7 +35,12 @@ func main() { panic(err) } - brkAdapter, err := broadcast.NewAdapter(brokers, ctx.WithField("tag", "Broker Adapter")) + pktAdapter, err := http.NewAdapter(uint(tcpPort), http.JSONPacketParser{}, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + panic(err) + } + + brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) if err != nil { panic(err) } @@ -79,16 +87,22 @@ func main() { <-make(chan bool) } -func parseOptions() (brokers []Recipient, udpPort uint64) { +func parseOptions() (brokers []Recipient, tcpPort uint64, udpPort uint64) { var brokersFlag string var udpPortFlag string + var tcpPortFlag string flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000 `) - flag.StringVar(&udpPortFlag, "udp-port", "", "Udp port on which the router should listen to.") + flag.StringVar(&udpPortFlag, "udp-port", "", "UDP port on which the router should listen to.") + flag.StringVar(&tcpPortFlag, "tcp-port", "", "TCP port on which the router should listen to.") flag.Parse() var err error + tcpPort, err = strconv.ParseUint(tcpPortFlag, 10, 64) + if err != nil { + panic(err) + } udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) if err != nil { panic(err) From 489ccc9fdff334e0dddc1c29e11dbe562f5ab634 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 18:05:59 +0100 Subject: [PATCH 0423/2266] Use comments instead of continue such that go vet does not complain --- core/adapters/http/adapter_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 714e41582..d0325c74d 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -133,13 +133,12 @@ func TestNext(t *testing.T) { checkStatus(t, test.WantStatus, resp.StatusCode) // NOTE: See https://github.com/brocaar/lorawan/issues/3 - continue - select { - case packet := <-gotPacket: - checkPackets(t, test.WantPacket, packet) - case <-time.After(time.Millisecond * 250): - checkPackets(t, test.WantPacket, core.Packet{}) - } + //select { + //case packet := <-gotPacket: + // checkPackets(t, test.WantPacket, packet) + //case <-time.After(time.Millisecond * 250): + // checkPackets(t, test.WantPacket, core.Packet{}) + //} } } From 3329d4bf386bc91b980f457ef42beb671798cd08 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 18:24:15 +0100 Subject: [PATCH 0424/2266] [integration.broker] Update router integration Dockerfile --- integration/router/Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/integration/router/Dockerfile b/integration/router/Dockerfile index b12a05bfb..5bf81c037 100644 --- a/integration/router/Dockerfile +++ b/integration/router/Dockerfile @@ -4,7 +4,7 @@ FROM golang:latest # Dependencies, everything on master -RUN go get "github.com/TheThingsNetwork/core/..." +RUN go get "github.com/TheThingsNetwork/ttn/..." # Actual files to build RUN mkdir ~/TheThingsNetwork @@ -13,7 +13,8 @@ WORKDIR ~/TheThingsNetwork # Expose the port on which the gateway adapter will listen to EXPOSE 33000/udp +EXPOSE 3000/tcp # Build & Launch RUN go build . -CMD ./router --brokers $BROKERS --udp-port 33000 +CMD ./router --brokers $BROKERS --udp-port 33000 --tcp-port 3000 From 88ee313f9806c0c5b6e276f65fc2aff74abea8d7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 19:09:01 +0100 Subject: [PATCH 0425/2266] [integration.broker] Write broker integration file. No error handling though --- integration/broker/main.go | 107 +++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 integration/broker/main.go diff --git a/integration/broker/main.go b/integration/broker/main.go new file mode 100644 index 000000000..339e364f4 --- /dev/null +++ b/integration/broker/main.go @@ -0,0 +1,107 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "fmt" + "os" + "strconv" + + . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" + "github.com/TheThingsNetwork/ttn/core/components" + "github.com/apex/log" + "github.com/apex/log/handlers/text" +) + +func main() { + // Parse options + routersPort, handlersPort := parseOptions() + + // Create Logging Context + log.SetHandler(text.New(os.Stdout)) + ctx := log.WithFields(log.Fields{ + "component": "Router", + }) + + // Instantiate all components + rtrAdapter, err := http.NewAdapter(uint(routersPort), http.JSONPacketParser{}, ctx.WithField("tag", "Routers Adapter")) + if err != nil { + panic(err) + } + + hdlHTTPAdapter, err := http.NewAdapter(uint(handlersPort), http.JSONPacketParser{}, ctx.WithField("tag", "Handlers Adapter")) + if err != nil { + panic(err) + } + + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, pubsub.HandlerParser{}, ctx.WithField("tag", "Handlers Adapter")) + if err != nil { + panic(err) + } + + broker, err := components.NewBroker(ctx.WithField("tag", "Broker")) + if err != nil { + panic(err) + } + + // Bring the service to life + + // Listen to uplink + go func() { + for { + packet, an, err := rtrAdapter.Next() + if err != nil { + fmt.Println(err) + continue + } + go func(packet Packet, an AckNacker) { + if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { + fmt.Println(err) + } + }(packet, an) + } + }() + + // List to handler registrations + go func() { + for { + reg, an, err := hdlAdapter.NextRegistration() + if err != nil { + fmt.Println(err) + continue + } + go func(reg Registration, an AckNacker) { + if err := broker.Register(reg, an); err != nil { + fmt.Println(err) + } + }(reg, an) + } + }() + + <-make(chan bool) +} + +func parseOptions() (routersPort uint64, handlersPort uint64) { + var routersPortFlag string + var handlersPortFlag string + flag.StringVar(&routersPortFlag, "routers-port", "", "TCP port on which the broker should listen to for incoming uplink packets.") + flag.StringVar(&handlersPortFlag, "handlers-port", "", "TCP port on which the broker should listen to for incoming registrations and downlink packet.") + flag.Parse() + + var err error + routersPort, err = strconv.ParseUint(routersPortFlag, 10, 64) + if err != nil { + panic(err) + } + + handlersPort, err = strconv.ParseUint(handlersPortFlag, 10, 64) + if err != nil { + panic(err) + } + + return +} From bd5337a08dccb8b18d3e13f9a5171fe15a29ae82 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 15 Jan 2016 19:10:18 +0100 Subject: [PATCH 0426/2266] [integration.broker] Add Dockerfile to setup broker container --- integration/broker/Dockerfile | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 integration/broker/Dockerfile diff --git a/integration/broker/Dockerfile b/integration/broker/Dockerfile new file mode 100644 index 000000000..6553ab8ce --- /dev/null +++ b/integration/broker/Dockerfile @@ -0,0 +1,20 @@ +# The container will assume to have a $BROKER env var defined + +# Go, because go is life +FROM golang:latest + +# Dependencies, everything on master +RUN go get "github.com/TheThingsNetwork/ttn/..." + +# Actual files to build +RUN mkdir ~/TheThingsNetwork +ADD . ~/TheThingsNetwork +WORKDIR ~/TheThingsNetwork + +# Expose the port on which the adapters will listen to +EXPOSE 3000/tcp +EXPOSE 4000/tcp + +# Build & Launch +RUN go build . +CMD ./broker --routers-port 3000 --handlers-port 4000 From cdbd2d422e358181448c6bf81081c60d478468ac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 18 Jan 2016 09:59:54 +0100 Subject: [PATCH 0427/2266] Switch from bundled lorawan to brocaar/lorawan --- .drone.yml | 1 - .gitmodules | 4 ---- .travis.yml | 3 --- core/adapters/http/adapter_test.go | 5 +++-- core/adapters/http/broadcast/broadcast_test.go | 2 +- core/adapters/http/pubsub/handler_parser.go | 2 +- core/adapters/http/pubsub/pubsub_test.go | 2 +- core/adapters/semtech/build_utilities_test.go | 2 +- core/build_utilities_test.go | 2 +- core/components/broker.go | 2 +- core/components/broker_storage.go | 2 +- core/components/router_storage.go | 2 +- core/core.go | 2 +- core/packet.go | 2 +- core/packet_test.go | 2 +- lorawan | 1 - semtech/check_utilities.go | 3 ++- semtech/decode_test.go | 5 +++-- semtech/encode_test.go | 5 +++-- semtech/semtech.go | 3 ++- simulators/gateway/utils.go | 2 +- 21 files changed, 25 insertions(+), 29 deletions(-) delete mode 100644 .gitmodules delete mode 160000 lorawan diff --git a/.drone.yml b/.drone.yml index fe706fba3..22b857438 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,7 +1,6 @@ build: image: golang commands: - - git submodule update --init --recursive - go get github.com/apex/log - go get github.com/brocaar/lorawan - go get github.com/jacobsa/crypto/cmac diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 2341457ef..000000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "lorawan"] - path = lorawan - url = https://github.com/brocaar/lorawan.git - branch = master diff --git a/.travis.yml b/.travis.yml index 1f04bdd7a..2492f1765 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,6 @@ language: go go: - 1.5 -before_install: - - git submodule update --init --recursive - install: - go get github.com/apex/log - go get github.com/brocaar/lorawan diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index d0325c74d..dc2c1dfd0 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -12,12 +12,13 @@ import ( "testing" "time" + "reflect" + "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" - "reflect" + "github.com/brocaar/lorawan" ) // Send(p core.Packet, r ...core.Recipient) error diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index a632f657f..f53151393 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -13,9 +13,9 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestSend(t *testing.T) { diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/pubsub/handler_parser.go index a77de7b89..c58046eb3 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/pubsub/handler_parser.go @@ -13,7 +13,7 @@ import ( "strings" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/brocaar/lorawan" ) type HandlerParser struct{} diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index d9d82fb6b..400567c6b 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -13,8 +13,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/lorawan" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) // NextRegistration() (core.Registration, core.AckNacker, error) diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go index dabc3c5eb..73c8b5c78 100644 --- a/core/adapters/semtech/build_utilities_test.go +++ b/core/adapters/semtech/build_utilities_test.go @@ -11,10 +11,10 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) // ----- build utilities diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go index df2ef25a6..1ef992f30 100644 --- a/core/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -8,9 +8,9 @@ import ( "strings" "time" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" ) // Generate an RXPK packet using the given payload as Data diff --git a/core/components/broker.go b/core/components/broker.go index 1cc1934da..de324c9b5 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/apex/log" + "github.com/brocaar/lorawan" ) type Broker struct { diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 1bc8598ae..fdafce2cf 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -6,7 +6,7 @@ package components import ( "sync" - "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/brocaar/lorawan" ) type brokerStorage interface { diff --git a/core/components/router_storage.go b/core/components/router_storage.go index c3efba164..30b172301 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -8,7 +8,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/brocaar/lorawan" ) type routerStorage interface { diff --git a/core/core.go b/core/core.go index b902e526d..86c4e9254 100644 --- a/core/core.go +++ b/core/core.go @@ -3,7 +3,7 @@ package core import ( "time" - "github.com/TheThingsNetwork/ttn/lorawan" + "github.com/brocaar/lorawan" ) type Packet struct { diff --git a/core/packet.go b/core/packet.go index a250dfaa7..d5ec48010 100644 --- a/core/packet.go +++ b/core/packet.go @@ -10,9 +10,9 @@ import ( "reflect" "strings" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" ) var ErrImpossibleConversion error = fmt.Errorf("Illegal attempt to convert a packet") diff --git a/core/packet_test.go b/core/packet_test.go index 66d7c2e10..a1172d516 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -7,10 +7,10 @@ import ( "encoding/json" "testing" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestConvertRXPKPacket(t *testing.T) { diff --git a/lorawan b/lorawan deleted file mode 160000 index 09e6c20be..000000000 --- a/lorawan +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 09e6c20bec91bdd420fc89c111082c809dc8e05c diff --git a/semtech/check_utilities.go b/semtech/check_utilities.go index 1a8cfe307..7086e21c4 100644 --- a/semtech/check_utilities.go +++ b/semtech/check_utilities.go @@ -4,9 +4,10 @@ package semtech import ( - . "github.com/TheThingsNetwork/ttn/utils/testing" "reflect" "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" ) func checkErrors(t *testing.T, want bool, got error) { diff --git a/semtech/decode_test.go b/semtech/decode_test.go index 6198114ba..b27187296 100644 --- a/semtech/decode_test.go +++ b/semtech/decode_test.go @@ -4,10 +4,11 @@ package semtech import ( - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" "testing" "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) func TestUnmarshalBinary(t *testing.T) { diff --git a/semtech/encode_test.go b/semtech/encode_test.go index 4f2065198..9b7648484 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -4,10 +4,11 @@ package semtech import ( - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" "testing" "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) func TestMarshalBinary(t *testing.T) { diff --git a/semtech/semtech.go b/semtech/semtech.go index 73e342225..53303fec6 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -8,8 +8,9 @@ package semtech import ( "fmt" - "github.com/TheThingsNetwork/ttn/utils/pointer" "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" ) type DeviceAddress [4]byte diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index a545684cc..0f5bea203 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -13,9 +13,9 @@ import ( "strings" "time" - "github.com/TheThingsNetwork/ttn/lorawan" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" ) func genToken() []byte { From c0818f59d98f4f44566ac05bfd71c1b892ca65e0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 14:50:13 +0100 Subject: [PATCH 0428/2266] Fix typos and small errors discovered during integration testing --- core/adapters/http/broadcast/broadcast.go | 6 ++++-- core/adapters/http/packet_listener.go | 2 +- core/adapters/semtech/adapter.go | 2 +- core/components/broker.go | 1 + core/components/router.go | 2 +- integration/broker/main.go | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 01039041f..cc304744f 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -43,8 +43,10 @@ func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx l func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { if len(r) == 0 { + a.ctx.Info("No recipient provided. The packet will be broadcast") return a.broadcast(p) } + packet, err := a.Adapter.Send(p, r...) if err == httpadapter.ErrInvalidPacket { return core.Packet{}, ErrInvalidPacket @@ -84,11 +86,11 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { ctx := a.ctx.WithField("recipient", recipient) - ctx.Debug("POST Request") + ctx.Info("POST Request") buf := new(bytes.Buffer) buf.Write([]byte(payload)) - resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + resp, err := http.Post(fmt.Sprintf("http://%s/packets", recipient.Address.(string)), "application/json", buf) if err != nil { cherr <- err return diff --git a/core/adapters/http/packet_listener.go b/core/adapters/http/packet_listener.go index 225808532..e94eb3447 100644 --- a/core/adapters/http/packet_listener.go +++ b/core/adapters/http/packet_listener.go @@ -15,7 +15,7 @@ import ( func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { ctx := a.ctx.WithField("sender", req.RemoteAddr) - ctx.Debug("Receiving new registration request") + ctx.Info("Receiving new packet") // Check the http method if req.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index b58e38a2a..26ce66e39 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -92,7 +92,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") for { - buf := make([]byte, 128) + buf := make([]byte, 512) n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection a.ctx.WithError(err).Error("Connection error") diff --git a/core/components/broker.go b/core/components/broker.go index de324c9b5..17ac64986 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -30,6 +30,7 @@ func NewBroker(ctx log.Interface) (*Broker, error) { } func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { + b.ctx.Info("Handle uplink packet") // 1. Lookup for entries for the associated device devAddr, err := p.DevAddr() if err != nil { diff --git a/core/components/router.go b/core/components/router.go index 3f276c162..150d64a5d 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -82,5 +82,5 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt // ok ensure the router has been initialized by NewRouter() func (r *Router) ok() bool { - return r == nil && r.db != nil + return r != nil && r.db != nil } diff --git a/integration/broker/main.go b/integration/broker/main.go index 339e364f4..251d32982 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -24,7 +24,7 @@ func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) ctx := log.WithFields(log.Fields{ - "component": "Router", + "component": "Broker", }) // Instantiate all components From d00bfe2423d58cefb1179e333c98d44f43cbaebc Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 15:38:16 +0100 Subject: [PATCH 0429/2266] Improve log line feedback in semtech adapter test (use to String() ) --- core/adapters/semtech/adapter_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index cf7416217..9706db054 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -131,5 +131,5 @@ func checkResponses(t *testing.T, want semtech.Packet, got semtech.Packet) { Ok(t, "Check responses") return } - Ko(t, "Received response does not match expecatations.\nWant: %v\nGot: %v", want, got) + Ko(t, "Received response does not match expecatations.\nWant: %v\nGot: %v", want.String(), got.String()) } From a279f138ef47422cf38f8b27cc6ef4b65fcda6ac Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 16:23:42 +0100 Subject: [PATCH 0430/2266] Update dockerfiles and create small temporary script and Dockerfiles for test deployment --- Broker_Dockerfile | 16 ++++++++++++++++ Router_Dockerfile | 17 +++++++++++++++++ deploy.sh | 12 ++++++++++++ integration/broker/Dockerfile | 4 ++-- integration/router/Dockerfile | 6 +++--- 5 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 Broker_Dockerfile create mode 100644 Router_Dockerfile create mode 100755 deploy.sh diff --git a/Broker_Dockerfile b/Broker_Dockerfile new file mode 100644 index 000000000..9edb9ebbd --- /dev/null +++ b/Broker_Dockerfile @@ -0,0 +1,16 @@ +FROM golang:latest + +RUN go get github.com/brocaar/lorawan +RUN go get github.com/apex/log +RUN go get github.com/jacobsa/crypto/cmac +RUN mkdir -p /go/src/github.com/TheThingsNetwork/ttn +ADD . /go/src/github.com/TheThingsNetwork/ttn + +ENV HANDLERS_PORT=3000 +ENV ROUTERS_PORT=4000 + +EXPOSE 3000/tcp +EXPOSE 4000/tcp + +RUN go build github.com/TheThingsNetwork/ttn/integration/broker +CMD ./broker --handlers-port $HANDLERS_PORT --routers-port $ROUTERS_PORT diff --git a/Router_Dockerfile b/Router_Dockerfile new file mode 100644 index 000000000..1da4a1f41 --- /dev/null +++ b/Router_Dockerfile @@ -0,0 +1,17 @@ +FROM golang:latest + +RUN go get github.com/apex/log +RUN go get github.com/brocaar/lorawan +RUN go get github.com/jacobsa/crypto/cmac + +RUN mkdir -p /go/src/github.com/TheThingsNetwork/ttn +ADD . /go/src/github.com/TheThingsNetwork/ttn + +ENV UDP_PORT=33000 +ENV TCP_PORT=4000 + +EXPOSE 33000/udp +EXPOSE 4000/tcp + +RUN go build github.com/TheThingsNetwork/ttn/integration/router +CMD ./router --brokers $BROKERS --udp-port $UDP_PORT --tcp-port $TCP_PORT diff --git a/deploy.sh b/deploy.sh new file mode 100755 index 000000000..2ab7d5ca3 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env sh + +docker build -t broker -f Broker_Dockerfile . +docker build -t router -f Router_Dockerfile . + +echo "\n=== STARTING BROKER ===\n" +docker run broker & +sleep 2 && BROKER=$(docker inspect $(docker ps -q) | grep '"IPAddress"' | head -n 1 | grep -oE "([0-9]+\.?){4}") + +echo "\n=== STARTING ROUTER ===\n" +docker run -e BROKERS=$BROKER router & +unset BROKER diff --git a/integration/broker/Dockerfile b/integration/broker/Dockerfile index 6553ab8ce..2d34ac397 100644 --- a/integration/broker/Dockerfile +++ b/integration/broker/Dockerfile @@ -16,5 +16,5 @@ EXPOSE 3000/tcp EXPOSE 4000/tcp # Build & Launch -RUN go build . -CMD ./broker --routers-port 3000 --handlers-port 4000 +RUN go build -o broker . +CMD ./broker --routers-port 4000 --handlers-port 3000 diff --git a/integration/router/Dockerfile b/integration/router/Dockerfile index 5bf81c037..2501fcd7e 100644 --- a/integration/router/Dockerfile +++ b/integration/router/Dockerfile @@ -13,8 +13,8 @@ WORKDIR ~/TheThingsNetwork # Expose the port on which the gateway adapter will listen to EXPOSE 33000/udp -EXPOSE 3000/tcp +EXPOSE 4000/tcp # Build & Launch -RUN go build . -CMD ./router --brokers $BROKERS --udp-port 33000 --tcp-port 3000 +RUN go build -o router . +CMD ./router --brokers $BROKERS --udp-port 33000 --tcp-port 4000 From 8bde52b68ef429d45687fc975b03484efcbfcb9f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 16:54:01 +0100 Subject: [PATCH 0431/2266] [packet.unmarshal] Fix packet unmarshalling issues. Use of MType to distinguish packet type --- core/packet.go | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/core/packet.go b/core/packet.go index d5ec48010..314180747 100644 --- a/core/packet.go +++ b/core/packet.go @@ -133,11 +133,35 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { if err != nil { return err } - payload := new(lorawan.PHYPayload) + + // Try first to unmarshal as an uplink payload + payload := lorawan.NewPHYPayload(true) if err := payload.UnmarshalBinary(rawPayload); err != nil { return err } - p.Payload = *payload + + switch payload.MHDR.MType.String() { + case "JoinAccept": + fallthrough + case "UnconfirmedDataDown": + fallthrough + case "ConfirmedDataDown": + payload = lorawan.NewPHYPayload(false) + if err := payload.UnmarshalBinary(rawPayload); err != nil { + return err + } + case "JoinRequest": + fallthrough + case "UnconfirmedDataUp": + fallthrough + case "ConfirmedDataUp": + // Nothing, we handle them by default + + case "Proprietary": + return fmt.Errorf("Unsupported MType Proprietary") + } + + p.Payload = payload p.Metadata = proxy.Metadata return nil } From e95b4e6e9bd2c4a5db251e402e1cdf00d539daac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 18 Jan 2016 16:54:30 +0100 Subject: [PATCH 0432/2266] Fix logging issues that @ktorz had --- core/adapters/http/adapter_test.go | 4 +--- core/adapters/http/broadcast/broadcast.go | 4 ++-- core/adapters/http/packet_listener.go | 2 +- core/components/broker.go | 2 +- integration/broker/main.go | 1 + integration/router/main.go | 1 + 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index dc2c1dfd0..c751922a8 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -17,7 +17,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -91,8 +90,7 @@ func TestNext(t *testing.T) { }, } // Build - log.SetHandler(NewLogHandler(t)) - ctx := log.WithFields(log.Fields{"tag": "Adapter"}) + ctx := GetLogger(t, "Adapter") adapter, err := NewAdapter(3102, JSONPacketParser{}, ctx) if err != nil { panic(err) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index cc304744f..10258c36c 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -43,7 +43,7 @@ func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx l func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { if len(r) == 0 { - a.ctx.Info("No recipient provided. The packet will be broadcast") + a.ctx.Debug("No recipient provided. The packet will be broadcast") return a.broadcast(p) } @@ -86,7 +86,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { ctx := a.ctx.WithField("recipient", recipient) - ctx.Info("POST Request") + ctx.Debug("POST Request") buf := new(bytes.Buffer) buf.Write([]byte(payload)) diff --git a/core/adapters/http/packet_listener.go b/core/adapters/http/packet_listener.go index e94eb3447..4c4db1ef6 100644 --- a/core/adapters/http/packet_listener.go +++ b/core/adapters/http/packet_listener.go @@ -15,7 +15,7 @@ import ( func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { ctx := a.ctx.WithField("sender", req.RemoteAddr) - ctx.Info("Receiving new packet") + ctx.Debug("Receiving new packet") // Check the http method if req.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/core/components/broker.go b/core/components/broker.go index 17ac64986..853dd45dc 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -30,7 +30,7 @@ func NewBroker(ctx log.Interface) (*Broker, error) { } func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { - b.ctx.Info("Handle uplink packet") + b.ctx.Debug("Handle uplink packet") // 1. Lookup for entries for the associated device devAddr, err := p.DevAddr() if err != nil { diff --git a/integration/broker/main.go b/integration/broker/main.go index 251d32982..1995a3357 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -23,6 +23,7 @@ func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) + log.SetLevel(log.DebugLevel) ctx := log.WithFields(log.Fields{ "component": "Broker", }) diff --git a/integration/router/main.go b/integration/router/main.go index 66b246df2..e912efa30 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -25,6 +25,7 @@ func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) + log.SetLevel(log.DebugLevel) ctx := log.WithFields(log.Fields{ "component": "Router", }) From f6b9881ff36a0263ebb726c7abcfd7d7ed87dde3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 16:54:44 +0100 Subject: [PATCH 0433/2266] [packet.unmarshal] Re-enable skipped tests related to packet unmarshalling --- core/adapters/http/adapter_test.go | 13 ++++++------- core/build_utilities_test.go | 5 +++-- core/packet_test.go | 4 +--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index dc2c1dfd0..29d4ab3b1 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -133,13 +133,12 @@ func TestNext(t *testing.T) { checkStatus(t, test.WantStatus, resp.StatusCode) - // NOTE: See https://github.com/brocaar/lorawan/issues/3 - //select { - //case packet := <-gotPacket: - // checkPackets(t, test.WantPacket, packet) - //case <-time.After(time.Millisecond * 250): - // checkPackets(t, test.WantPacket, core.Packet{}) - //} + select { + case packet := <-gotPacket: + checkPackets(t, test.WantPacket, packet) + case <-time.After(time.Millisecond * 250): + checkPackets(t, test.WantPacket, core.Packet{}) + } } } diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go index 1ef992f30..4c81cceaa 100644 --- a/core/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -82,6 +82,7 @@ func genMetadata(RXPK semtech.RXPK) Metadata { // Generates a Metadata object with all field completed with relevant values func genFullMetadata() Metadata { + timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) return Metadata{ Chan: pointer.Uint(2), Codr: pointer.String("4/6"), @@ -99,8 +100,8 @@ func genFullMetadata() Metadata { Rssi: pointer.Int(-27), Size: pointer.Uint(14), Stat: pointer.Int(0), - Time: pointer.Time(time.Now()), - Tmst: pointer.Uint(uint(time.Now().UnixNano())), + Time: pointer.Time(timeRef), + Tmst: pointer.Uint(uint(timeRef.UnixNano())), } } diff --git a/core/packet_test.go b/core/packet_test.go index a1172d516..30c0da77f 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -73,15 +73,13 @@ func TestMarshalJSONPacket(t *testing.T) { } func TestUnmarshalJSONPacket(t *testing.T) { - t.Skip("Discussion on github about implementation") - tests := []unmarshalJSONTest{ unmarshalJSONTest{ JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, WantPacket: Packet{Metadata: Metadata{}, Payload: genPHYPayload(true)}, }, unmarshalJSONTest{ - JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{"chan":2,"codr":"4/6","fdev":3,"freq":863.125,"imme":false,"ipol":false,"lsnr":5.2,"modu":"LORA","ncrc":true,"powe":3,"prea":8,"rfch":2,"rssi":-27,"size":14,"stat":0,"tmst":1452690688207288535,"datr":"LORA","time":"2016-01-13T14:11:28.207288421+01:00"}}`, + JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{"chan":2,"codr":"4/6","fdev":3,"freq":863.125,"imme":false,"ipol":false,"lsnr":5.2,"modu":"LORA","ncrc":true,"powe":3,"prea":8,"rfch":2,"rssi":-27,"size":14,"stat":0,"tmst":1452694288207288421,"datr":"LORA","time":"2016-01-13T14:11:28.207288421Z"}}`, WantPacket: Packet{Metadata: genFullMetadata(), Payload: genPHYPayload(true)}, }, unmarshalJSONTest{ From ce502b07d917ce5d6a89b143c3a2a0d5e2b59e7f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 17:55:26 +0100 Subject: [PATCH 0434/2266] [simulator] Change forwarder.Forward such that it accepts rxpk instead of Packets --- simulators/gateway/forwarder.go | 10 +++-- simulators/gateway/forwarder_test.go | 55 +++++++++------------------- simulators/gateway/utils.go | 24 ++++++------ 3 files changed, 37 insertions(+), 52 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 7ebda850f..6c4237488 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -164,10 +164,14 @@ func (fwd *Forwarder) handleCommands() { } // Forward dispatch a packet to all connected routers. -func (fwd Forwarder) Forward(packet semtech.Packet) error { +func (fwd Forwarder) Forward(rxpks ...semtech.RXPK) error { fwd.commands <- command{cmd_RECVUP, nil} - if packet.Identifier != semtech.PUSH_DATA { - return fmt.Errorf("Unable to forward with identifier %x", packet.Identifier) + + packet := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PUSH_DATA, + GatewayId: fwd.Id[:], + Token: genToken(), } raw, err := packet.MarshalBinary() diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 3e187a723..3a2f279fa 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -62,6 +62,7 @@ func generatePacket(identifier byte, id [8]byte) semtech.Packet { return semtech.Packet{ Version: semtech.VERSION, Identifier: identifier, + Token: genToken(), } } } @@ -116,39 +117,15 @@ func TestForwarder(t *testing.T) { checkValid := func(identifier byte) func() { return func() { - pkt := generatePacket(identifier, fwd.Id) - raw, err := pkt.MarshalBinary() - if err != nil { - t.Errorf("Unexpected error %+v\n", err) - return - } - err = fwd.Forward(pkt) + rxpk := generateRXPK("MyData", generateDevAddr()) + err := fwd.Forward(rxpk) So(err, ShouldBeNil) - So(a1.written, ShouldResemble, raw) - So(a2.written, ShouldResemble, raw) - } - } - - checkInvalid := func(identifier byte) func() { - return func() { - err := fwd.Forward(generatePacket(identifier, fwd.Id)) - So(err, ShouldNotBeNil) + So(a1.written, ShouldNotBeNil) + So(a2.written, ShouldNotBeNil) } } Convey("Valid: PUSH_DATA", checkValid(semtech.PUSH_DATA)) - Convey("Invalid: PULL_DATA", checkInvalid(semtech.PULL_DATA)) - Convey("Invalid: PUSH_ACK", checkInvalid(semtech.PUSH_ACK)) - Convey("Invalid: PULL_ACK", checkInvalid(semtech.PULL_ACK)) - Convey("Invalid: PULL_RESP", checkInvalid(semtech.PULL_RESP)) - Convey("Invalid: wrong PUSH_DATA", func() { - pkt := generatePacket(semtech.PUSH_DATA, fwd.Id) - pkt.Token = []byte{0x14} - err := fwd.Forward(pkt) - So(err, ShouldNotBeNil) - So(len(a1.written), ShouldEqual, 0) - So(len(a2.written), ShouldEqual, 0) - }) }) Convey("Flush", t, func() { @@ -162,14 +139,13 @@ func TestForwarder(t *testing.T) { Convey("Store incoming valid packet", func() { // Make sure the connection is established - pkt := generatePacket(semtech.PUSH_DATA, id) - if err := fwd.Forward(pkt); err != nil { + rxpk := generateRXPK("MyData", generateDevAddr()) + if err := fwd.Forward(rxpk); err != nil { panic(err) } // Simulate an ack and a valid response ack := generatePacket(semtech.PUSH_ACK, id) - ack.Token = pkt.Token raw, err := ack.MarshalBinary() if err != nil { panic(err) @@ -178,7 +154,8 @@ func TestForwarder(t *testing.T) { // Simulate a resp resp := generatePacket(semtech.PULL_RESP, id) - resp.Payload = new(semtech.Payload) + resp.Token = nil + resp.Payload = &semtech.Payload{RXPK: []semtech.RXPK{rxpk}} raw, err = resp.MarshalBinary() if err != nil { panic(err) @@ -237,7 +214,7 @@ func TestForwarder(t *testing.T) { }) Convey("rxnb / rxok", func() { - fwd.Forward(generatePacket(semtech.PUSH_DATA, fwd.Id)) + fwd.Forward(generateRXPK("MyData", generateDevAddr())) stats := fwd.Stats() So(stats.Rxnb, ShouldNotBeNil) So(stats.Rxok, ShouldNotBeNil) @@ -246,7 +223,7 @@ func TestForwarder(t *testing.T) { }) Convey("rxfw", func() { - fwd.Forward(generatePacket(semtech.PUSH_DATA, fwd.Id)) + fwd.Forward(generateRXPK("MyData", generateDevAddr())) stats := fwd.Stats() So(stats.Rxfw, ShouldNotBeNil) So(*stats.Rxfw, ShouldEqual, *refStats.Rxfw+1) @@ -259,15 +236,19 @@ func TestForwarder(t *testing.T) { sendAndAck := func(a1Ack, a2Ack uint) { // Send packet + ack - pkt := generatePacket(semtech.PUSH_DATA, id) + fwd.Forward(generateRXPK("MyData", generateDevAddr())) ack := generatePacket(semtech.PUSH_ACK, id) + time.Sleep(50 * time.Millisecond) + + pkt := new(semtech.Packet) + if err := pkt.UnmarshalBinary(a1.written); err != nil { + panic(err) + } ack.Token = pkt.Token raw, err := ack.MarshalBinary() if err != nil { panic(err) } - fwd.Forward(pkt) - time.Sleep(50 * time.Millisecond) for i := uint(0); i < a1Ack; i += 1 { a1.Downlink <- raw } diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 0f5bea203..5e115cb43 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -81,10 +81,10 @@ func generateLsnr() float64 { } // Generates fake data from a device -func generateData(frmData string) string { +func generateData(frmData string, devAddr lorawan.DevAddr) string { macPayload := lorawan.NewMACPayload(true) macPayload.FHDR = lorawan.FHDR{ - DevAddr: generateDevAddr(), + DevAddr: devAddr, FCtrl: lorawan.FCtrl{}, FCnt: 0, } @@ -117,20 +117,20 @@ func generateDevAddr() lorawan.DevAddr { return lorawan.DevAddr(devAddr) } -func generateRXPK() semtech.RXPK { +func generateRXPK(data string, devAddr lorawan.DevAddr) semtech.RXPK { now := time.Now() return semtech.RXPK{ Time: &now, Tmst: pointer.Uint(uint(now.UnixNano())), Freq: pointer.Float64(generateFreq()), - Chan: pointer.Uint(0), // Irrelevant - Rfch: pointer.Uint(0), // Irrelevant - Stat: pointer.Int(1), // Assuming CRC was ok - Modu: pointer.String("LORA"), // For now, only consider LORA modulation - Datr: pointer.String(generateDatr()), // Arbitrary - Codr: pointer.String("4/5"), // Arbitrary - Rssi: pointer.Int(generateRssi()), // Arbitrary - Lsnr: pointer.Float64(generateLsnr()), // Arbitrary - Data: pointer.String(generateData("RXPKData")), // Arbitrary + Chan: pointer.Uint(0), // Irrelevant + Rfch: pointer.Uint(0), // Irrelevant + Stat: pointer.Int(1), // Assuming CRC was ok + Modu: pointer.String("LORA"), // For now, only consider LORA modulation + Datr: pointer.String(generateDatr()), // Arbitrary + Codr: pointer.String("4/5"), // Arbitrary + Rssi: pointer.Int(generateRssi()), // Arbitrary + Lsnr: pointer.Float64(generateLsnr()), // Arbitrary + Data: pointer.String(generateData(data, devAddr)), // Arbitrary } } From 81432f636531ddc28ee0a14c1f9be3f078e2affa Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 18 Jan 2016 19:04:02 +0100 Subject: [PATCH 0435/2266] [simulator] Add JSON conf parser to convert json files to a list of RXPK --- simulators/gateway/imitator.go | 61 ++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 0c3e1b33d..3e199a903 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -2,3 +2,64 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway + +import ( + "encoding/hex" + "encoding/json" + "fmt" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" + "io/ioutil" +) + +// RXPKFromConf read an input json file and parse it into a list of RXPK packets +func RXPKFromConf(filename string) ([]semtech.RXPK, error) { + content, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var conf []struct { + DevAddr string `json:"dev_addr"` + Metadata metadata `json:"metadata"` + Payload string `json:"payload"` + } + err = json.Unmarshal(content, &conf) + if err != nil { + return nil, err + } + var rxpks []semtech.RXPK + for _, c := range conf { + rxpk := semtech.RXPK(c.Metadata) + rawAddr, err := hex.DecodeString(c.DevAddr) + if err != nil { + return nil, err + } + var devAddr lorawan.DevAddr + copy(devAddr[:], rawAddr) + rxpk.Data = pointer.String(generateData(c.Payload, devAddr)) + rxpks = append(rxpks, rxpk) + } + + return rxpks, nil +} + +type metadata semtech.RXPK // metadata is just an alias used to mislead the UnmarshalJSON + +// UnmarshalJSON implements the json.Unmarshal interface +func (m *metadata) UnmarshalJSON(raw []byte) error { + if m == nil { + return fmt.Errorf("Cannot unmarshal in nil metadata") + } + payload := new(semtech.Payload) + rawPayload := append(append([]byte(`{"rxpk":[`), raw...), []byte(`]}`)...) + err := json.Unmarshal(rawPayload, payload) + if err != nil { + return err + } + if len(payload.RXPK) < 1 { + return fmt.Errorf("Unable to interpret raw bytes as valid metadata") + } + *m = metadata(payload.RXPK[0]) + return nil +} From 8412f3870dc992d973e0c38e6b158eb0593f2ba7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 16:14:46 +0100 Subject: [PATCH 0436/2266] [simulator] Add forgotten RXPK attributes in Forward func --- simulators/gateway/forwarder.go | 1 + 1 file changed, 1 insertion(+) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 6c4237488..6c8774005 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -172,6 +172,7 @@ func (fwd Forwarder) Forward(rxpks ...semtech.RXPK) error { Identifier: semtech.PUSH_DATA, GatewayId: fwd.Id[:], Token: genToken(), + Payload: &semtech.Payload{RXPK: rxpks}, } raw, err := packet.MarshalBinary() From f7c1e7b74e7952e35c6ee9a87539a9232a9617d9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 17:05:35 +0100 Subject: [PATCH 0437/2266] [simulator] Remove old logger from forwarder --- simulators/gateway/forwarder.go | 27 +++++++++++---------------- simulators/gateway/forwarder_test.go | 21 ++++++++++++--------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 6c8774005..5586ca30f 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -9,13 +9,13 @@ import ( "time" "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/log" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/apex/log" ) type Forwarder struct { - Id [8]byte // Gateway's Identifier - Logger log.Logger + ctx log.Interface + Id [8]byte // Gateway's Identifier alti int // GPS altitude in RX meters upnb uint // Number of upstream datagrams sent ackn uint // Number of upstream datagrams that were acknowledged @@ -48,7 +48,7 @@ const ( ) // NewForwarder create a forwarder instance bound to a set of routers. -func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error) { +func NewForwarder(id [8]byte, ctx log.Interface, adapters ...io.ReadWriteCloser) (*Forwarder, error) { if len(adapters) == 0 { return nil, fmt.Errorf("At least one adapter must be supplied") } @@ -58,8 +58,8 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error } fwd := &Forwarder{ - Logger: log.DebugLogger{Tag: "Forwarder"}, Id: id, + ctx: ctx, alti: 120, lati: 53.3702, long: 4.8952, @@ -80,26 +80,21 @@ func NewForwarder(id [8]byte, adapters ...io.ReadWriteCloser) (*Forwarder, error return fwd, nil } -// log wraps the Logger.log method, this is nothing more than a shortcut -func (fwd Forwarder) logf(format string, a ...interface{}) { - fwd.Logger.Logf(format, a...) // NOTE: concurrent-safe ? -} - // listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { for { buf := make([]byte, 1024) n, err := adapter.Read(buf) - fwd.logf("%d bytes received by adapter\n", n) + fwd.ctx.WithField("nb bytes", n).Debug("bytes received by adapter") if err != nil { - fwd.logf("Error: %+v", err) + fwd.ctx.WithError(err).Error("Connection lost / closed") fwd.quit <- err return // Connection lost / closed } - fwd.logf("Forwarder unmarshals datagram %x\n", buf[:n]) + fwd.ctx.WithField("datagram", buf[:n]).Debug("unmarshalling datagram") packet := new(semtech.Packet) if err = packet.UnmarshalBinary(buf[:n]); err != nil { - fwd.logf("Error: %+v", err) + fwd.ctx.WithError(err).Warn("Unable to unmarshal datagram to packet") continue } @@ -109,7 +104,7 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { case semtech.PULL_RESP: fwd.commands <- command{cmd_RECVDWN, packet} default: - fwd.logf("Forwarder ignores contingent packet %+v\n", packet) + fwd.ctx.WithField("packet", packet).Warn("Ignoring contingent packet") } } } @@ -119,7 +114,7 @@ func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { // This method consumes commands from the channel until it's closed. func (fwd *Forwarder) handleCommands() { for cmd := range fwd.commands { - fwd.logf("Fowarder executes command: %v\n", cmd.name) + fwd.ctx.WithField("command", cmd.name).Debug("executing command") switch cmd.name { case cmd_ACK: diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index 3a2f279fa..c31378f1c 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/semtech" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/goconvey/convey" ) @@ -68,9 +69,10 @@ func generatePacket(identifier byte, id [8]byte) semtech.Packet { } // initForwarder is a little helper used to instance adapters and forwarder for test purpose -func initForwarder(id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { +func initForwarder(t *testing.T, id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { a1, a2 := newFakeAdapter("adapter1"), newFakeAdapter("adapter2") - fwd, err := NewForwarder(id, a1, a2) + ctx := GetLogger(t, "Forwarder") + fwd, err := NewForwarder(id, ctx, a1, a2) if err != nil { panic(err) } @@ -78,24 +80,25 @@ func initForwarder(id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { } func TestForwarder(t *testing.T) { + ctx := GetLogger(t, "Forwarder") id := [8]byte{0x1, 0x3, 0x3, 0x7, 0x5, 0xA, 0xB, 0x1} Convey("NewForwarder", t, func() { Convey("Valid: one adapter", func() { - fwd, err := NewForwarder(id, newFakeAdapter("1")) + fwd, err := NewForwarder(id, ctx, newFakeAdapter("1")) So(err, ShouldBeNil) defer fwd.Stop() So(fwd, ShouldNotBeNil) }) Convey("Valid: two adapters", func() { - fwd, err := NewForwarder(id, newFakeAdapter("1"), newFakeAdapter("2")) + fwd, err := NewForwarder(id, ctx, newFakeAdapter("1"), newFakeAdapter("2")) So(err, ShouldBeNil) defer fwd.Stop() So(fwd, ShouldNotBeNil) }) Convey("Invalid: no adapter", func() { - fwd, err := NewForwarder(id) + fwd, err := NewForwarder(id, ctx) So(err, ShouldNotBeNil) So(fwd, ShouldBeNil) }) @@ -105,14 +108,14 @@ func TestForwarder(t *testing.T) { for i := 0; i < 300; i += 1 { adapters = append(adapters, newFakeAdapter(fmt.Sprintf("%d", i))) } - fwd, err := NewForwarder(id, adapters...) + fwd, err := NewForwarder(id, ctx, adapters...) So(fwd, ShouldBeNil) So(err, ShouldNotBeNil) }) }) Convey("Forward", t, func() { - fwd, a1, a2 := initForwarder(id) + fwd, a1, a2 := initForwarder(t, id) defer fwd.Stop() checkValid := func(identifier byte) func() { @@ -130,7 +133,7 @@ func TestForwarder(t *testing.T) { Convey("Flush", t, func() { // Make sure we use a complete new forwarder each time - fwd, a1, a2 := initForwarder(id) + fwd, a1, a2 := initForwarder(t, id) defer fwd.Stop() Convey("Init flush", func() { @@ -195,7 +198,7 @@ func TestForwarder(t *testing.T) { }) Convey("Stats", t, func() { - fwd, a1, a2 := initForwarder(id) + fwd, a1, a2 := initForwarder(t, id) defer fwd.Stop() refStats := fwd.Stats() From 3d2e82ad0aaa1a534e29b6fa1f62af862e494322 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 17:18:00 +0100 Subject: [PATCH 0438/2266] [simulator] Implement basic mock gateway -> mock accordingly to a schedule --- simulators/gateway/imitator.go | 74 ++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 3 deletions(-) diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 3e199a903..5a8e91d64 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -7,14 +7,82 @@ import ( "encoding/hex" "encoding/json" "fmt" + "io" + "io/ioutil" + "net" + "os" + "time" + "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/apex/log" + "github.com/apex/log/handlers/text" "github.com/brocaar/lorawan" - "io/ioutil" ) -// RXPKFromConf read an input json file and parse it into a list of RXPK packets -func RXPKFromConf(filename string) ([]semtech.RXPK, error) { +// MockWithSchedule will generate fake traffic based on a json file referencing packets +// +// The following shape is expected for the JSON file: +// [ +// { +// "dev_addr": "0102aabb", +// "payload": "My Data Payload", +// "metadata": { +// "rssi": -40, +// "modu": "LORA", +// "datr": "4/7", +// ... +// }, +// }, +// .... +// ] +// +// The imitator will fire udp packet every given interval of time to a set of router. Routers +// addresses are expected to contains the destination port as well. +func MockWithSchedule(filename string, delay time.Duration, routers ...string) { + rxpks, err := rxpkFromConf(filename) + if err != nil { + panic(err) + } + + var adapters []io.ReadWriteCloser + for _, router := range routers { + addr, err := net.ResolveUDPAddr("udp", router) + if err != nil { + panic(err) + } + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + panic(err) + } + adapters = append(adapters, conn) + } + + log.SetHandler(text.New(os.Stdout)) + log.SetLevel(log.DebugLevel) + ctx := log.WithFields(log.Fields{"Simulator": "Gateway"}) + + fwd, err := NewForwarder([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ctx, adapters...) + if err != nil { + panic(err) + } + + for { + for _, rxpk := range rxpks { + <-time.After(delay) + if err := fwd.Forward(rxpk); err != nil { + ctx.WithError(err).WithField("rxpk", rxpk).Warn("failed to forward") + } + } + } +} + +func MockRandomly() { + // TODO +} + +// rxpkFromConf read an input json file and parse it into a list of RXPK packets +func rxpkFromConf(filename string) ([]semtech.RXPK, error) { content, err := ioutil.ReadFile(filename) if err != nil { return nil, err From d4205f83044c822e2d7ec011ef20d8b462b8dc35 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 17:58:43 +0100 Subject: [PATCH 0439/2266] [simulator] Add an integration file for the scheduler imitator --- integration/scheduler/Dockerfile | 16 +++++++++++ integration/scheduler/schedule.json | 18 +++++++++++++ integration/scheduler/scheduler.go | 42 +++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+) create mode 100644 integration/scheduler/Dockerfile create mode 100644 integration/scheduler/schedule.json create mode 100644 integration/scheduler/scheduler.go diff --git a/integration/scheduler/Dockerfile b/integration/scheduler/Dockerfile new file mode 100644 index 000000000..7c79e4586 --- /dev/null +++ b/integration/scheduler/Dockerfile @@ -0,0 +1,16 @@ +# The container will assume to have a $ROUTERS env var defined + +# Go, because go is life +FROM golang:latest + +# Dependencies, everything on master +RUN go get "github.com/TheThingsNetwork/ttn/..." + +# Actual files to build +RUN mkdir ~/TheThingsNetwork +ADD . ~/TheThingsNetwork +WORKDIR ~/TheThingsNetwork + +# Build & Launch +RUN go build -o scheduler . +CMD ./scheduler --schedule "./schedule.json" --delay "1s" --routers $ROUTERS diff --git a/integration/scheduler/schedule.json b/integration/scheduler/schedule.json new file mode 100644 index 000000000..11334e73e --- /dev/null +++ b/integration/scheduler/schedule.json @@ -0,0 +1,18 @@ +[ + { + "dev_addr": "14abcd42", + "payload": "The Things Network - #1", + "metadata": { + "rssi": -20 + } + }, + { + "dev_addr": "14abcd42", + "payload": "The Things Network - #2", + "metadata": { + "rssi": -20, + "datr": "SF7BW125", + "modu": "LORA" + } + } +] diff --git a/integration/scheduler/scheduler.go b/integration/scheduler/scheduler.go new file mode 100644 index 000000000..7116b059e --- /dev/null +++ b/integration/scheduler/scheduler.go @@ -0,0 +1,42 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "strings" + + "github.com/TheThingsNetwork/ttn/simulators/gateway" + "time" +) + +func main() { + routers, delay, schedule := parseOptions() + gateway.MockWithSchedule(schedule, delay, routers...) +} + +func parseOptions() (routers []string, delay time.Duration, schedule string) { + var routersFlag string + var delayFlag string + flag.StringVar(&routersFlag, "routers", "", `Router addresses to which send packets. + For instance: 10.10.3.34:8080,thethingsnetwork.router.com:3000 + `) + flag.StringVar(&delayFlag, "delay", "", `Interval of time between 2 sending. + For instance: 500ms + `) + flag.StringVar(&schedule, "schedule", "", "JSON file defining the packets to schedule") + flag.Parse() + + var err error + if delay, err = time.ParseDuration(delayFlag); err != nil { + panic(err) + } + + if routersFlag == "" { + panic("Need to provide at least one router address") + } + + routers = strings.Split(routersFlag, ",") + return +} From a6af9cb62d2765e3790e684c45951abf952522ea Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 17:58:57 +0100 Subject: [PATCH 0440/2266] [simulator] Save debug code of udp_debugger --- integration/udp_debugger/Dockerfile | 12 ++++++ integration/udp_debugger/udp_debugger.go | 54 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 integration/udp_debugger/Dockerfile create mode 100644 integration/udp_debugger/udp_debugger.go diff --git a/integration/udp_debugger/Dockerfile b/integration/udp_debugger/Dockerfile new file mode 100644 index 000000000..138784c54 --- /dev/null +++ b/integration/udp_debugger/Dockerfile @@ -0,0 +1,12 @@ +# Should be placed in root folder til ttn isn't available on master + +FROM golang:latest + +RUN go get github.com/apex/log +RUN go get github.com/brocaar/lorawan +RUN go get github.com/jacobsa/crypto/cmac +RUN mkdir -p /go/src/github.com/TheThingsNetwork/ttn +ADD . /go/src/github.com/TheThingsNetwork/ttn + +RUN go build -o listener github.com/TheThingsNetwork/ttn/integration/udp_debugger +CMD ./listener diff --git a/integration/udp_debugger/udp_debugger.go b/integration/udp_debugger/udp_debugger.go new file mode 100644 index 000000000..4269e76d0 --- /dev/null +++ b/integration/udp_debugger/udp_debugger.go @@ -0,0 +1,54 @@ +package main + +import ( + "fmt" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/semtech" + "net" +) + +func main() { + addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:33000") + if err != nil { + panic(err) + } + + conn, err := net.ListenUDP("udp", addr) + if err != nil { + panic(err) + } + + go func() { + for { + fmt.Printf("\n*******************************\n") + buf := make([]byte, 512) + n, addr, err := conn.ReadFromUDP(buf) + if err != nil { + fmt.Println(err) + continue + } + fmt.Println("Msg from", addr) + pkt := new(semtech.Packet) + if err := pkt.UnmarshalBinary(buf[:n]); err != nil { + fmt.Println(err) + continue + } + + fmt.Printf("Received %x from %x with token %x\n", pkt.Identifier, pkt.GatewayId, pkt.Token) + + if pkt.Payload == nil || len(pkt.Payload.RXPK) < 1 { + fmt.Println("Unexpected packet payload") + continue + } + packet, err := core.ConvertRXPK(pkt.Payload.RXPK[0]) + if err != nil { + fmt.Println(err) + continue + } + fmt.Println(packet) + } + }() + + fmt.Println("Listening on 33000") + <-make(chan bool) +} From 401c64050ebe94cc2b4e0c38d54151170294dae8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 18:08:03 +0100 Subject: [PATCH 0441/2266] [simulator] Update travis script build such that Travis won't try to test integration packages --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2492f1765..aec7c6e36 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,6 @@ install: - go get github.com/smartystreets/goconvey/convey script: - - go test -v ./... + - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... From b25b005ef585eb9c53421826efe49a6190402269 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 18:09:18 +0100 Subject: [PATCH 0442/2266] [simulator] Remove legacy utils/log package --- utils/log/entry.go | 76 ---------------------------------------- utils/log/log.go | 87 ---------------------------------------------- 2 files changed, 163 deletions(-) delete mode 100644 utils/log/entry.go delete mode 100644 utils/log/log.go diff --git a/utils/log/entry.go b/utils/log/entry.go deleted file mode 100644 index 3fa3c8f96..000000000 --- a/utils/log/entry.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package log - -import ( - "fmt" - "strings" -) - -// Meta contains metadata for a log entry -type Meta map[string]interface{} - -func (m Meta) String() string { - if len(m) == 0 { - return "" - } - - metas := []string{} - for k, v := range m { - metas = append(metas, fmt.Sprintf("%s=%v", k, v)) - } - - return fmt.Sprint("\033[34m[ ", strings.Join(metas, ", "), " ]\033[0m") -} - -// The Level for this log entry -type Level uint8 - -// Log levels -const ( - PanicLevel Level = iota - FatalLevel - ErrorLevel - WarnLevel - InfoLevel - DebugLevel -) - -func (l Level) String() string { - switch l { - case PanicLevel: - return "\033[31m[ panic ]\033[0m" // Level printed in red - case FatalLevel: - return "\033[31m[ fatal ]\033[0m" // Level printed in red - case ErrorLevel: - return "\033[31m[ error ]\033[0m" // Level printed in red - case WarnLevel: - return "\033[33m[ warn ]\033[0m" // Level printed in yellow - case DebugLevel: - return "\033[34m[ debug ]\033[0m" // Level printed in blue - default: // Default is InfoLevel - return "\033[32m[ info ]\033[0m" // Level printed in green - } -} - -// entry type -type entry struct { - Level Level - Message string - Meta Meta -} - -// Log implements the Logger interface -func (e entry) String() string { - return fmt.Sprintf("%s %s %s", e.Level, e.Message, e.Meta) -} - -// Entry gives you a new log entry -func Entry(level Level, msg string, meta Meta) entry { - return entry{ - Level: level, - Message: msg, - Meta: meta, - } -} diff --git a/utils/log/log.go b/utils/log/log.go deleted file mode 100644 index 562fddc9c..000000000 --- a/utils/log/log.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package log provides some handy types and method to activate and deactivate specific log -// behavior within files in a transparent way. -package log - -import ( - "fmt" - "testing" -) - -// Logger is a minimalist interface to represent logger -type Logger interface { - Log(str string) - Logf(format string, a ...interface{}) - LogEntry(level Level, msg string, meta Meta) -} - -// DebugLogger can be used in development to display loglines in the console -type DebugLogger struct { - Tag string -} - -// Log implements the Logger interface -func (l DebugLogger) Log(str string) { - fmt.Printf("\033[33m[ %s ]\033[0m ", l.Tag) // Tag printed in yellow - fmt.Print(str) - fmt.Print("\n") -} - -// Logf implements the Logger interface -func (l DebugLogger) Logf(format string, a ...interface{}) { - fmt.Printf("\033[33m[ %s ]\033[0m ", l.Tag) // Tag printed in yellow - fmt.Printf(format, a...) - fmt.Print("\n") -} - -// LogEntry implements the Logger interface -func (l DebugLogger) LogEntry(level Level, msg string, meta Meta) { - l.Log(Entry(level, msg, meta).String()) -} - -// TestLogger can be used in a test environnement to display log only on failure -type TestLogger struct { - Tag string - T *testing.T -} - -// Log implements the Logger interface -func (l TestLogger) Log(str string) { - l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, str) // Tag printed in yellow -} - -// Logf implements the Logger interface -func (l TestLogger) Logf(format string, a ...interface{}) { - l.T.Logf("\033[33m[ %s ]\033[0m %s", l.Tag, fmt.Sprintf(format, a...)) // Tag printed in yellow -} - -// LogEntry implements the Logger interface -func (l TestLogger) LogEntry(level Level, msg string, meta Meta) { - l.Log(Entry(level, msg, meta).String()) -} - -// MultiLogger aggregates several loggers log to each of them -type MultiLogger struct { - Loggers []Logger -} - -// Log implements the Logger interface -func (l MultiLogger) Log(str string) { - for _, logger := range l.Loggers { - logger.Log(str) - } -} - -// Logf implements the Logger interface -func (l MultiLogger) Logf(format string, a ...interface{}) { - for _, logger := range l.Loggers { - logger.Logf(format, a...) - } -} - -// LogEntry implements the Logger interface -func (l MultiLogger) LogEntry(level Level, msg string, meta Meta) { - l.Log(Entry(level, msg, meta).String()) -} From 6dda52f411bcae9660c36662db7dc253cb92f262 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 19 Jan 2016 18:41:12 +0100 Subject: [PATCH 0443/2266] Fix time generated with wrong location --- simulators/gateway/utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 5e115cb43..24815e936 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -118,7 +118,7 @@ func generateDevAddr() lorawan.DevAddr { } func generateRXPK(data string, devAddr lorawan.DevAddr) semtech.RXPK { - now := time.Now() + now := time.Now().In(time.UTC) return semtech.RXPK{ Time: &now, Tmst: pointer.Uint(uint(now.UnixNano())), From f37ba0e62e557ecf52b063da347741b4d75e2613 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 20 Jan 2016 11:12:25 +0100 Subject: [PATCH 0444/2266] [handler] Create handler backbone --- core/components/handler.go | 41 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 core/components/handler.go diff --git a/core/components/handler.go b/core/components/handler.go new file mode 100644 index 000000000..9cad9ba7f --- /dev/null +++ b/core/components/handler.go @@ -0,0 +1,41 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/apex/log" +) + +var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") + +type Handler struct { + ctx log.Interface + db handlerStorage +} + +type handlerStorage interface { + store(data interface{}) error +} + +func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { + return &Handler{ + ctx: ctx, + db: db, + }, nil +} + +func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { + return nil +} + +func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { + return nil +} + +func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) error { + return ErrNotImplemented +} From ca1394e60dbe0c11825abe6347d149fed78fb394 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 20 Jan 2016 17:20:32 +0100 Subject: [PATCH 0445/2266] [handler] Draft handler buffering mechanism --- core/components/handler.go | 63 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/core/components/handler.go b/core/components/handler.go index 9cad9ba7f..f007daeb5 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -5,9 +5,11 @@ package components import ( "fmt" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" + //"github.com/brocaar/lorawan" ) var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") @@ -15,17 +17,34 @@ var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") type Handler struct { ctx log.Interface db handlerStorage + set chan<- uplinkBundle } type handlerStorage interface { - store(data interface{}) error +} + +type bundleId [20]byte + +type uplinkBundle struct { + id bundleId + an core.AckNacker + packet core.Packet } func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { - return &Handler{ + h := Handler{ ctx: ctx, db: db, - }, nil + } + + bundles := make(chan []uplinkBundle) + set := make(chan uplinkBundle) + + go h.consumeBundles(bundles) + go h.manageBuffers(bundles, set) + h.set = set + + return &h, nil } func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { @@ -39,3 +58,41 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) error { return ErrNotImplemented } + +func (h *Handler) consumeBundles(bundles <-chan []uplinkBundle) { + //for bundle := range bundles { + // Deduplicate + // DecryptPayload + // AddMeta + // AckOrNack each packets + // Store into mongo + //} +} + +// manageBuffers gather new incoming bundles that possess the same id +// It then flushs them once a given delay has passed since the reception of the first bundle. +func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplinkBundle) { + buffers := make(map[bundleId][]uplinkBundle) + alarm := make(chan bundleId) + + for { + select { + case id := <-alarm: + b := buffers[id] + delete(buffers, id) + go func(b []uplinkBundle) { bundles <- b }(b) + case bundle := <-set: + b := append(buffers[bundle.id], bundle) + if len(b) == 1 { + go setAlarm(alarm, bundle.id, time.Millisecond*300) + } + buffers[bundle.id] = b + } + } +} + +// setAlarm will trigger a message on the given channel after a given delay. +func setAlarm(alarm chan<- bundleId, id bundleId, delay time.Duration) { + <-time.After(delay) + alarm <- id +} From b78e6b573b0fd2ee688f981a97dfa9d254d9bc0d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 20 Jan 2016 17:58:50 +0100 Subject: [PATCH 0446/2266] [handler] Test handler internal mechanism --- core/components/handler.go | 2 + core/components/internal_mechanisms_test.go | 128 ++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100644 core/components/internal_mechanisms_test.go diff --git a/core/components/handler.go b/core/components/handler.go index f007daeb5..595210ce7 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -12,6 +12,8 @@ import ( //"github.com/brocaar/lorawan" ) +const BUFFER_DELAY = time.Millisecond * 300 + var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") type Handler struct { diff --git a/core/components/internal_mechanisms_test.go b/core/components/internal_mechanisms_test.go new file mode 100644 index 000000000..fcdce684d --- /dev/null +++ b/core/components/internal_mechanisms_test.go @@ -0,0 +1,128 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "reflect" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +// This illustrates the mechanism used by the handler to bufferize connections +// for a while before processing them. +// This is an analogy to what's done in the handler where +// bundleId -> _bundleId +// uplinkBundle -> _uplinkBundle + +func TestHandlerBuffering(t *testing.T) { + // Describe + Desc(t, "Generate fake bundle traffic") + + // Build + bundles := make(chan []_uplinkBundle) + set := make(chan _uplinkBundle) + received := new([][]_uplinkBundle) // There's a datarace here, but that's okay, chill. + + go _manageBuffers(bundles, set) + go _consumeBundles(bundles, received) + + b1_1 := _uplinkBundle{_bundleId(1), "bundle1_1"} + b1_2 := _uplinkBundle{_bundleId(1), "bundle1_2"} + b1_3 := _uplinkBundle{_bundleId(1), "bundle1_3"} + b1_4 := _uplinkBundle{_bundleId(1), "bundle1_4"} + + b2_1 := _uplinkBundle{_bundleId(2), "bundle2_1"} + b2_2 := _uplinkBundle{_bundleId(2), "bundle2_2"} + b2_3 := _uplinkBundle{_bundleId(2), "bundle2_3"} + + // Operate + // Expecting [ b1_1, b1_2, b1_3 ] + go func() { + set <- b1_1 + <-time.After(BUFFER_DELAY / 3) + set <- b1_2 + <-time.After(BUFFER_DELAY / 3) + set <- b1_3 + }() + + // Expecting [ b2_1, b2_2, b2_3 ] + go func() { + set <- b2_1 + <-time.After(BUFFER_DELAY / 4) + set <- b2_2 + <-time.After(BUFFER_DELAY / 4) + set <- b2_3 + }() + + // Expecting [ b1_4 ] + go func() { + <-time.After(BUFFER_DELAY * 2) + set <- b1_4 + }() + + // Check + <-time.After(BUFFER_DELAY * 4) + if len(*received) != 3 { + Ko(t, "Expected 3 bundles to have been received but got %d", len(*received)) + return + } + Ok(t, "Check bundles number") + + for _, bundles := range *received { + if reflect.DeepEqual(bundles, []_uplinkBundle{b1_1, b1_2, b1_3}) { + continue + } + if reflect.DeepEqual(bundles, []_uplinkBundle{b1_4}) { + continue + } + if reflect.DeepEqual(bundles, []_uplinkBundle{b2_1, b2_2, b2_3}) { + continue + } + Ko(t, "Collected bundles in a non-expected way: %v", bundles) + return + } + Ok(t, "Check bundles shapes") +} + +type _bundleId int +type _uplinkBundle struct { + id _bundleId + data string +} + +// _setAlarm ~> setAlarm +func _setAlarm(alarm chan<- _bundleId, id _bundleId, delay time.Duration) { + <-time.After(delay) + alarm <- id +} + +// _manageBuffers ~> manageBuffers +func _manageBuffers(bundles chan<- []_uplinkBundle, set <-chan _uplinkBundle) { + buffers := make(map[_bundleId][]_uplinkBundle) + alarm := make(chan _bundleId) + + for { + select { + case id := <-alarm: + b := buffers[id] + delete(buffers, id) + go func(b []_uplinkBundle) { bundles <- b }(b) + case bundle := <-set: + b := append(buffers[bundle.id], bundle) + if len(b) == 1 { + go _setAlarm(alarm, bundle.id, BUFFER_DELAY) + } + buffers[bundle.id] = b + } + } +} + +// _consumeBundles, just for the test +func _consumeBundles(bundles <-chan []_uplinkBundle, received *[][]_uplinkBundle) { + for b := range bundles { + *received = append(*received, b) + } +} From 2cb899386f674a15283700c96a20064f15e32c86 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 20 Jan 2016 18:35:47 +0100 Subject: [PATCH 0447/2266] [handler] Write down handler storage backbone --- core/components/handler.go | 3 -- core/components/handler_storage.go | 47 ++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 core/components/handler_storage.go diff --git a/core/components/handler.go b/core/components/handler.go index 595210ce7..2edc4ce37 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -22,9 +22,6 @@ type Handler struct { set chan<- uplinkBundle } -type handlerStorage interface { -} - type bundleId [20]byte type uplinkBundle struct { diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go new file mode 100644 index 000000000..3dee8b689 --- /dev/null +++ b/core/components/handler_storage.go @@ -0,0 +1,47 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "sync" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/brocaar/lorawan" +) + +type handlerStorage interface { + store(lorawan.EUI64, handlerEntry) error + partition([]core.Packet) ([]handlerPartition, error) +} + +type handlerPartition struct { + AppEUI lorawan.EUI64 + NwsKey lorawan.AES128Key + AppKey lorawan.AES128Key + DevAddr lorawan.DevAddr + Packets []core.Packet +} + +type handlerEntry struct { + NwsKey lorawan.AES128Key + AppKey lorawan.AES128Key + DevAddr lorawan.DevAddr +} + +type handlerDB struct { + sync.RWMutex + entries map[lorawan.EUI64]handlerEntry +} + +func NewHandlerDB() handlerStorage { + return &handlerDB{entries: make(map[lorawan.EUI64]handlerEntry)} +} + +func (db *handlerDB) store(appEUI lorawan.EUI64, entry handlerEntry) error { + return nil +} + +func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error) { + return nil, nil +} From cf4c188baed5b597b6661f74809b7f65ce3fa02a Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 11:58:49 +0100 Subject: [PATCH 0448/2266] [handler] Create partition method for the handler storage --- core/components/handler_storage.go | 73 ++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 13 deletions(-) diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 3dee8b689..168e7c465 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -11,37 +11,84 @@ import ( ) type handlerStorage interface { - store(lorawan.EUI64, handlerEntry) error + store(lorawan.DevAddr, handlerEntry) error partition([]core.Packet) ([]handlerPartition, error) } type handlerPartition struct { - AppEUI lorawan.EUI64 - NwsKey lorawan.AES128Key - AppKey lorawan.AES128Key - DevAddr lorawan.DevAddr + handlerEntry Packets []core.Packet } type handlerEntry struct { - NwsKey lorawan.AES128Key - AppKey lorawan.AES128Key + AppEUI lorawan.EUI64 + NwkSKey lorawan.AES128Key + AppSKey lorawan.AES128Key DevAddr lorawan.DevAddr } type handlerDB struct { - sync.RWMutex - entries map[lorawan.EUI64]handlerEntry + sync.RWMutex // Guards entries + entries map[lorawan.DevAddr][]handlerEntry } -func NewHandlerDB() handlerStorage { - return &handlerDB{entries: make(map[lorawan.EUI64]handlerEntry)} +// newHandlerDB construct a new local handlerStorage +func newHandlerDB() handlerStorage { + return &handlerDB{entries: make(map[lorawan.DevAddr][]handlerEntry)} } -func (db *handlerDB) store(appEUI lorawan.EUI64, entry handlerEntry) error { +// store implements the handlerStorage interface +func (db *handlerDB) store(devAddr lorawan.DevAddr, entry handlerEntry) error { + db.Lock() + db.entries[devAddr] = append(db.entries[devAddr], entry) + db.Unlock() return nil } +// partition implements the handlerStorage interface func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error) { - return nil, nil + // Create a map in order to do the partition + partitions := make(map[lorawan.EUI64]handlerPartition) + + db.RLock() // We require lock on the whole block because we don't want the entries to change while building the partition. + for _, packet := range packets { + // First, determine devAddr and get the macPayload. Those are mandatory. + devAddr, err := packet.DevAddr() + if err != nil { + return nil, ErrInvalidPacket + } + macPayload, ok := packet.Payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, ErrInvalidPacket + } + + // Now, get all tuples associated to that device address, and choose the right one + for _, entry := range db.entries[devAddr] { + // Try to decrypt the frame payload with those keys + key := entry.AppSKey + if macPayload.FPort == 0 { + key = entry.NwkSKey + } + err := macPayload.DecryptFRMPayload(key) + if err != nil { // Weren't the good keys + continue + } + // #Easy + packet.Payload.MACPayload = macPayload + partitions[entry.AppEUI] = handlerPartition{ + handlerEntry: entry, + Packets: append(partitions[entry.AppEUI].Packets, packet), + } + break // We don't need to look for other entries, we've found the right one + } + } + db.RUnlock() + + // Transform the map in a slice + res := make([]handlerPartition, 0, len(partitions)) + for _, p := range partitions { + res = append(res, p) + } + + return res, nil } From f1bb4de28264a2154c5ee10b7cb438c721a6d7cf Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 14:11:49 +0100 Subject: [PATCH 0449/2266] [handler] Design first sketch of handler_storage tests --- core/components/handler_storage_test.go | 109 ++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 core/components/handler_storage_test.go diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go new file mode 100644 index 000000000..9bfbd24fe --- /dev/null +++ b/core/components/handler_storage_test.go @@ -0,0 +1,109 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + "testing" +) + +func TestStoragePartition(t *testing.T) { + setup := []handlerEntry{ + { // App #1, Dev #1 + AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), + DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 1}), + }, + { // App #1, Dev #2 + AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), + DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 2}), + }, + { // App #1, Dev #3 + AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + NwkSKey: lorawan.AES128Key([16]byte{12, 0xa, 0xb, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{0xb, 15, 14, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), + DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 3}), + }, + { // App #2, Dev #1 + AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 5, 12, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 1, 11, 10, 0xc, 8, 7, 6, 5, 4, 3, 2, 1}), + DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 1}), + }, + { // App #2, Dev #2 + AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 5, 12, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 1, 11, 10, 0xc, 8, 7, 6, 5, 4, 3, 2, 1}), + DevAddr: lorawan.DevAddr([4]byte{0, 0xaf, 0x14, 1}), + }, + } + + tests := []struct { + Desc string + PacketsShape []handlerEntry + WantPartitions []partitionShape + WantError error + }{ + { + Desc: "", + PacketsShape: []handlerEntry{setup[0]}, + WantPartitions: []partitionShape{{setup[0], 1}}, + WantError: nil, + }, + } + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + db := genFilledHandlerStorage(setup) + packets := genPacketsFromHandlerEntries(test.PacketsShape) + + // Operate + partitions, err := db.partition(packets) + + // Check + checkErrors(t, test.WantError, err) + checkPartitions(t, test.WantPartitions, partitions) + } +} + +type partitionShape struct { + Entry handlerEntry + PacketNb int +} + +// ----- BUILD utilities + +func genFilledHandlerStorage(setup []handlerEntry) (db handlerStorage) { + db = newHandlerDB() + + for _, entry := range setup { + if err := db.store(entry.DevAddr, entry); err != nil { + panic(err) + } + } + + return db +} + +func genPacketsFromHandlerEntries(shapes []handlerEntry) []core.Packet { + return nil +} + +// ----- CHECK utilities + +func checkErrors(t *testing.T, want error, got error) { + +} + +func checkPartitions(t *testing.T, want []partitionShape, got []handlerPartition) { + +} From 056ecf89b91b5acd6ae5b62e67f2e6cf8ca88f57 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 14:36:16 +0100 Subject: [PATCH 0450/2266] [handler] Implement packet generation from handler entry --- core/components/handler_storage_test.go | 49 +++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 9bfbd24fe..2d2c6d276 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -5,12 +5,15 @@ package components import ( "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" "testing" + "time" ) func TestStoragePartition(t *testing.T) { + // CONVENTION below -> first DevAddr byte will be used as falue for FPort setup := []handlerEntry{ { // App #1, Dev #1 AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), @@ -22,13 +25,13 @@ func TestStoragePartition(t *testing.T) { AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 2}), + DevAddr: lorawan.DevAddr([4]byte{10, 0, 0, 2}), }, { // App #1, Dev #3 AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), NwkSKey: lorawan.AES128Key([16]byte{12, 0xa, 0xb, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), AppSKey: lorawan.AES128Key([16]byte{0xb, 15, 14, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 3}), + DevAddr: lorawan.DevAddr([4]byte{14, 0, 0, 3}), }, { // App #2, Dev #1 AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), @@ -40,7 +43,7 @@ func TestStoragePartition(t *testing.T) { AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 5, 12, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 1, 11, 10, 0xc, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{0, 0xaf, 0x14, 1}), + DevAddr: lorawan.DevAddr([4]byte{23, 0xaf, 0x14, 1}), }, } @@ -95,7 +98,45 @@ func genFilledHandlerStorage(setup []handlerEntry) (db handlerStorage) { } func genPacketsFromHandlerEntries(shapes []handlerEntry) []core.Packet { - return nil + var packets []core.Packet + for _, entry := range shapes { + + // Build the macPayload + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{DevAddr: entry.DevAddr} + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: []byte(time.Now().String()), + }} + macPayload.FPort = uint8(entry.DevAddr[0]) + key := entry.AppSKey + if macPayload.FPort == 0 { + key = entry.NwkSKey + } + if err := macPayload.EncryptFRMPayload(key); err != nil { + panic(err) + } + + // Build the physicalPayload + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + if err := phyPayload.SetMIC(entry.NwkSKey); err != nil { + panic(err) + } + + // Finally build the packet + packets = append(packets, core.Packet{ + Metadata: core.Metadata{ + Rssi: pointer.Int(-20), + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("Lora"), + }, + Payload: phyPayload, + }) + } + return packets } // ----- CHECK utilities From 38082bdcdfd6526458f905453bae634565646322 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 14:49:17 +0100 Subject: [PATCH 0451/2266] [handler] Write down check verification for handler partition method --- core/components/handler_storage_test.go | 29 +++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 2d2c6d276..a0d2b5bc4 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -8,6 +8,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" + "reflect" "testing" "time" ) @@ -79,7 +80,7 @@ func TestStoragePartition(t *testing.T) { } type partitionShape struct { - Entry handlerEntry + handlerEntry PacketNb int } @@ -122,6 +123,7 @@ func genPacketsFromHandlerEntries(shapes []handlerEntry) []core.Packet { MType: lorawan.ConfirmedDataUp, Major: lorawan.LoRaWANR1, } + phyPayload.MACPayload = macPayload if err := phyPayload.SetMIC(entry.NwkSKey); err != nil { panic(err) } @@ -142,9 +144,32 @@ func genPacketsFromHandlerEntries(shapes []handlerEntry) []core.Packet { // ----- CHECK utilities func checkErrors(t *testing.T, want error, got error) { - + if want == got { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be %v, but got %v", want, got) } func checkPartitions(t *testing.T, want []partitionShape, got []handlerPartition) { + if len(want) != len(got) { + Ko(t, "Expected %d partitions, but got %d", len(want), len(got)) + return + } +browseGot: + for _, gotPartition := range got { + for _, wantPartition := range want { // Find right wanted partition + if reflect.DeepEqual(wantPartition.handlerEntry, gotPartition.handlerEntry) { + if len(gotPartition.Packets) == wantPartition.PacketNb { + continue browseGot + } + Ko(t, "Partition don't match expectations.\nWant: %v\nGot: %v", wantPartition, gotPartition) + return + } + } + Ko(t, "Got a partition that wasn't expected: %v", gotPartition) + return + } + Ok(t, "Check partitions") } From 34083829ca4bf96eaca27fc515f27ee04d7d066b Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 15:03:55 +0100 Subject: [PATCH 0452/2266] [handler] Write test suite for storage partition --- core/components/errors.go | 3 +- core/components/handler_storage_test.go | 45 ++++++++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/core/components/errors.go b/core/components/errors.go index 36988dbc6..3ef1a625c 100644 --- a/core/components/errors.go +++ b/core/components/errors.go @@ -6,7 +6,8 @@ package components import "fmt" var ErrInvalidRegistration = fmt.Errorf("Invalid registration") -var ErrDeviceNotFound = fmt.Errorf("Device not found") +var ErrDeviceNotFound = fmt.Errorf("Device not found") // NOTE duplication with below +var ErrNotFound = fmt.Errorf("Requested entity not found") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrBadOptions = fmt.Errorf("Invalid supplied options") var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index a0d2b5bc4..1c8eb0a18 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -48,6 +48,13 @@ func TestStoragePartition(t *testing.T) { }, } + unknown := handlerEntry{ // App #1, Dev #4 + AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 23, 6, 7, 8, 9, 0x19, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{16, 0xba, 14, 13, 2, 11, 58, 9, 8, 7, 6, 5, 4, 3, 2, 1}), + DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 4}), + } + tests := []struct { Desc string PacketsShape []handlerEntry @@ -55,11 +62,47 @@ func TestStoragePartition(t *testing.T) { WantError error }{ { - Desc: "", + Desc: "1 packet -> 1 partition | 1 packet", PacketsShape: []handlerEntry{setup[0]}, WantPartitions: []partitionShape{{setup[0], 1}}, WantError: nil, }, + { + Desc: "1 unknown packet -> error not found", + PacketsShape: []handlerEntry{unknown}, + WantPartitions: nil, + WantError: ErrNotFound, + }, + { + Desc: "2 packets | diff DevAddr & diff AppEUI -> 2 partitions | 1 packet", + PacketsShape: []handlerEntry{setup[0], setup[4]}, + WantPartitions: []partitionShape{{setup[0], 1}, {setup[4], 1}}, + WantError: nil, + }, + { + Desc: "2 packets | same DevAddr & diff AppEUI -> 2 partitions | 1 packet", + PacketsShape: []handlerEntry{setup[0], setup[3]}, + WantPartitions: []partitionShape{{setup[0], 1}, {setup[3], 1}}, + WantError: nil, + }, + { + Desc: "3 packets | diff DevAddr & same AppEUI -> 3 partitions | 1 packet", + PacketsShape: []handlerEntry{setup[0], setup[1], setup[2]}, + WantPartitions: []partitionShape{{setup[0], 1}, {setup[1], 1}, {setup[2], 1}}, + WantError: nil, + }, + { + Desc: "3 packets | same DevAddr & same AppEUI -> 1 partitions | 3 packets", + PacketsShape: []handlerEntry{setup[0], setup[0], setup[0]}, + WantPartitions: []partitionShape{{setup[0], 3}}, + WantError: nil, + }, + { + Desc: "5 packets | same DevAddr & various AppEUI -> 2 partitions | 3 packets & 2 packets", + PacketsShape: []handlerEntry{setup[0], setup[0], setup[0], setup[3], setup[3]}, + WantPartitions: []partitionShape{{setup[0], 3}, {setup[3], 2}}, + WantError: nil, + }, } for _, test := range tests { From bb7e90cb643a8854e7ead7db9a8133019738befd Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 18:20:00 +0100 Subject: [PATCH 0453/2266] [handler] Format packages import in test --- core/components/handler_storage_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 1c8eb0a18..48c5b50a0 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -4,13 +4,14 @@ package components import ( + "reflect" + "testing" + "time" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" - "reflect" - "testing" - "time" ) func TestStoragePartition(t *testing.T) { @@ -52,7 +53,7 @@ func TestStoragePartition(t *testing.T) { AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 23, 6, 7, 8, 9, 0x19, 11, 12, 13, 14, 15, 16}), AppSKey: lorawan.AES128Key([16]byte{16, 0xba, 14, 13, 2, 11, 58, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 4}), + DevAddr: lorawan.DevAddr([4]byte{1, 0, 0, 4}), } tests := []struct { From 1f9baa907e479c66707898f32f2082d2738b13f3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 21 Jan 2016 18:20:37 +0100 Subject: [PATCH 0454/2266] [handler] Make tests pass. --- core/components/handler_storage.go | 32 ++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 168e7c465..f2271aab3 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -48,7 +48,7 @@ func (db *handlerDB) store(devAddr lorawan.DevAddr, entry handlerEntry) error { // partition implements the handlerStorage interface func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error) { // Create a map in order to do the partition - partitions := make(map[lorawan.EUI64]handlerPartition) + partitions := make(map[[20]byte]handlerPartition) db.RLock() // We require lock on the whole block because we don't want the entries to change while building the partition. for _, packet := range packets { @@ -57,29 +57,24 @@ func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error if err != nil { return nil, ErrInvalidPacket } - macPayload, ok := packet.Payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, ErrInvalidPacket - } // Now, get all tuples associated to that device address, and choose the right one for _, entry := range db.entries[devAddr] { - // Try to decrypt the frame payload with those keys - key := entry.AppSKey - if macPayload.FPort == 0 { - key = entry.NwkSKey - } - err := macPayload.DecryptFRMPayload(key) - if err != nil { // Weren't the good keys - continue + // Compute MIC check to find the right keys + ok, err := packet.Payload.ValidateMIC(entry.NwkSKey) + if err != nil || !ok { + continue // These aren't the droid you're looking for } + // #Easy - packet.Payload.MACPayload = macPayload - partitions[entry.AppEUI] = handlerPartition{ + var id [20]byte + copy(id[:16], entry.AppEUI[:]) + copy(id[16:], entry.DevAddr[:]) + partitions[id] = handlerPartition{ handlerEntry: entry, - Packets: append(partitions[entry.AppEUI].Packets, packet), + Packets: append(partitions[id].Packets, packet), } - break // We don't need to look for other entries, we've found the right one + break // We shouldn't look for other entries, we've found the right one } } db.RUnlock() @@ -90,5 +85,8 @@ func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error res = append(res, p) } + if len(res) == 0 { + return nil, ErrNotFound + } return res, nil } From cd5fa1353c95b8447791db7c25463be8732212be Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 25 Jan 2016 18:07:51 +0100 Subject: [PATCH 0455/2266] [handler] Extend definition of metadata --- core/core.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/core/core.go b/core/core.go index 86c4e9254..eb74eb6fc 100644 --- a/core/core.go +++ b/core/core.go @@ -17,24 +17,25 @@ type Recipient struct { } type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Group []Metadata `json:"metadata,omitempty"` // Gather metadata of several packet into one metadata structure } type AckNacker interface { From c74ea23f991d8f1635d6c31b72adc38ab56a2847 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 25 Jan 2016 18:08:05 +0100 Subject: [PATCH 0456/2266] [handler] Add Fcnt() method to packets --- core/packet.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/core/packet.go b/core/packet.go index 314180747..636793652 100644 --- a/core/packet.go +++ b/core/packet.go @@ -17,20 +17,34 @@ import ( var ErrImpossibleConversion error = fmt.Errorf("Illegal attempt to convert a packet") -// DevAddr return a lorawan device address associated to the packet if any +// DevAddr returns a lorawan device address associated to the packet if any func (p Packet) DevAddr() (lorawan.DevAddr, error) { if p.Payload.MACPayload == nil { - return lorawan.DevAddr{}, fmt.Errorf("lorawan: MACPayload should not be empty") + return lorawan.DevAddr{}, fmt.Errorf("MACPayload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return lorawan.DevAddr{}, fmt.Errorf("lorawan: unable to get address of a join message") + return lorawan.DevAddr{}, fmt.Errorf("Packet does not carry a MACPayload") } return macpayload.FHDR.DevAddr, nil } +// FCnt returns the frame counter of the given packet +func (p Packet) Fcnt() (uint16, error) { + if p.Payload.MACPayload == nil { + return 0, fmt.Errorf("MACPayload should not be empty") + } + + macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return 0, fmt.Errorf("Packet does not carry a MACPayload") + } + + return macpayload.FHDR.FCnt, nil +} + // String returns a string representation of the packet func (p Packet) String() string { str := "Packet {" From d39d35e11988391905c4c340d7cdf55fc9159954 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 25 Jan 2016 18:34:10 +0100 Subject: [PATCH 0457/2266] [handler] Implement handler core logic --- core/components/handler.go | 98 +++++++++++++++++++++++++----- core/components/handler_storage.go | 8 ++- 2 files changed, 90 insertions(+), 16 deletions(-) diff --git a/core/components/handler.go b/core/components/handler.go index 2edc4ce37..12712c193 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -4,12 +4,13 @@ package components import ( + "bytes" + "encoding/binary" "fmt" "time" "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" - //"github.com/brocaar/lorawan" ) const BUFFER_DELAY = time.Millisecond * 300 @@ -22,12 +23,14 @@ type Handler struct { set chan<- uplinkBundle } -type bundleId [20]byte +type bundleId [22]byte // AppEUI | DevAddr | FCnt type uplinkBundle struct { - id bundleId - an core.AckNacker - packet core.Packet + id bundleId + entry handlerEntry + packet core.Packet + adapter core.Adapter + chresp chan interface{} // Error or decrypted packet } func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { @@ -51,21 +54,88 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { } func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { - return nil + partition, err := h.db.partition([]core.Packet{p}) + if err != nil { + an.Nack() + return err + } + + fcnt, err := p.Fcnt() + if err != nil { + an.Nack() + return err + } + + chresp := make(chan interface{}) + var id bundleId + buf := new(bytes.Buffer) + buf.Write(partition[0].id[:]) // Partition is necessarily of length 1, associated to 1 packet, the same we gave + binary.Write(buf, binary.BigEndian, fcnt) + copy(id[:], buf.Bytes()) + h.set <- uplinkBundle{ + id: id, + packet: p, + entry: partition[0].handlerEntry, + adapter: upAdapter, + chresp: chresp, + } + + resp := <-chresp + switch resp.(type) { + case core.Packet: + an.Ack(resp.(core.Packet)) + return nil + case error: + an.Nack() + return resp.(error) + default: + an.Ack() + return nil + } } func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) error { return ErrNotImplemented } -func (h *Handler) consumeBundles(bundles <-chan []uplinkBundle) { - //for bundle := range bundles { - // Deduplicate - // DecryptPayload - // AddMeta - // AckOrNack each packets - // Store into mongo - //} +func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { + for bundles := range chbundles { + var packet *core.Packet + var sendToAdapter func(packet core.Packet) error + for _, bundle := range bundles { + if packet == nil { + *packet = core.Packet{ + Payload: bundle.packet.Payload, + Metadata: core.Metadata{ + Group: []core.Metadata{bundle.packet.Metadata}, + }, + } + // The handler assumes payload encrypted with AppSKey only ! + if err := packet.Payload.DecryptMACPayload(bundle.entry.AppSKey); err != nil { + for _, bundle := range bundles { + bundle.chresp <- err + } + break + } + + sendToAdapter = func(packet core.Packet) error { + // NOTE We'll have to look here for the downlink ! + _, err := bundle.adapter.Send(packet, core.Recipient{ + Address: bundle.entry.DevAddr, + Id: bundle.entry.AppEUI, + }) + return err + } + continue + } + packet.Metadata.Group = append(packet.Metadata.Group, bundle.packet.Metadata) + } + + err := sendToAdapter(*packet) + for _, bundle := range bundles { + bundle.chresp <- err + } + } } // manageBuffers gather new incoming bundles that possess the same id diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index f2271aab3..50019ec92 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -10,6 +10,8 @@ import ( "github.com/brocaar/lorawan" ) +type partitionId [20]byte + type handlerStorage interface { store(lorawan.DevAddr, handlerEntry) error partition([]core.Packet) ([]handlerPartition, error) @@ -17,6 +19,7 @@ type handlerStorage interface { type handlerPartition struct { handlerEntry + id partitionId Packets []core.Packet } @@ -48,7 +51,7 @@ func (db *handlerDB) store(devAddr lorawan.DevAddr, entry handlerEntry) error { // partition implements the handlerStorage interface func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error) { // Create a map in order to do the partition - partitions := make(map[[20]byte]handlerPartition) + partitions := make(map[partitionId]handlerPartition) db.RLock() // We require lock on the whole block because we don't want the entries to change while building the partition. for _, packet := range packets { @@ -67,11 +70,12 @@ func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error } // #Easy - var id [20]byte + var id partitionId copy(id[:16], entry.AppEUI[:]) copy(id[16:], entry.DevAddr[:]) partitions[id] = handlerPartition{ handlerEntry: entry, + id: id, Packets: append(partitions[id].Packets, packet), } break // We shouldn't look for other entries, we've found the right one From 860dc5e96487e5da76a88f387e84c5d09ab92ccb Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 10:38:51 +0100 Subject: [PATCH 0458/2266] [handler] Add handler registration implementation --- core/components/handler.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/core/components/handler.go b/core/components/handler.go index 12712c193..281ea89b3 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" + "github.com/brocaar/lorawan" ) const BUFFER_DELAY = time.Millisecond * 300 @@ -50,6 +51,30 @@ func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { } func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { + options, okOpts := reg.Options.(struct { + AppSKey lorawan.AES128Key + NwkSKey lorawan.AES128Key + }) + appEUI, okId := reg.Recipient.Id.(lorawan.EUI64) + + if !okId || !okOpts { + an.Nack() + return ErrBadOptions + } + + err := h.db.store(reg.DevAddr, handlerEntry{ + AppEUI: appEUI, + AppSKey: options.AppSKey, + NwkSKey: options.NwkSKey, + DevAddr: reg.DevAddr, + }) + + if err != nil { + an.Nack() + return err + } + + an.Ack() return nil } @@ -110,7 +135,7 @@ func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { Group: []core.Metadata{bundle.packet.Metadata}, }, } - // The handler assumes payload encrypted with AppSKey only ! + // The handler assumes payloads encrypted with AppSKey only ! if err := packet.Payload.DecryptMACPayload(bundle.entry.AppSKey); err != nil { for _, bundle := range bundles { bundle.chresp <- err From 7dae0090e0c14a549b0c3b49f2fbf7b8bfeb2d82 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 11:50:55 +0100 Subject: [PATCH 0459/2266] [handler] Define test structure for handler --- core/components/handler_test.go | 85 +++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 core/components/handler_test.go diff --git a/core/components/handler_test.go b/core/components/handler_test.go new file mode 100644 index 000000000..6bfaacc6d --- /dev/null +++ b/core/components/handler_test.go @@ -0,0 +1,85 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + // "reflect" + "testing" + "time" + + // "github.com/TheThingsNetwork/ttn/core" + // "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func TestHandleUp(t *testing.T) { + applications := make(map[lorawan.EUI64]lorawan.AES128Key) + applications[lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8})] = lorawan.AES128Key([16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) + applications[lorawan.EUI64([8]byte{9, 10, 11, 12, 13, 14, 15, 16})] = lorawan.AES128Key([16]byte{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}) + applications[lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 4, 4})] = lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) + + packets := []plannedPacket{ + { + AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + Data: "Packet 1 / Dev 1234 / App 12345678", + }, + { + AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + Data: "Packet 1 / Dev 1234 / App 12345678", + }, + } + + tests := []struct { + Schedule []schedule + WantAck map[[4]byte]bool + WantPackets map[[12]byte]string + WantError error + }{ + { + Schedule: []schedule{ + {time.Millisecond * 25, packets[0]}, + }, + WantAck: map[[4]byte]bool{ + [4]byte{1, 2, 3, 4}: true, + }, + WantError: nil, + WantPackets: map[[12]byte]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", + }, + }, + } + + for _, test := range tests { + // Describe + + // Build + handler := genNewHandler(t, applications) + + // Operate + + // Check + } +} + +type schedule struct { + Delay time.Duration + Packet plannedPacket +} + +type plannedPacket struct { + AppEUI lorawan.EUI64 + DevAddr lorawan.DevAddr + Data string +} + +func genNewHandler(t *testing.T, applications map[lorawan.EUI64]lorawan.AES128Key) Handler { + ctx := GetLogger(t, "Handler") + handler, err := NewHandler(newHandlerDB(), ctx) + if err != nil { + panic(err) + } +} From 36a54649598b22a19099a8dda732f5e446950289 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jan 2016 11:26:28 +0100 Subject: [PATCH 0460/2266] Add script for building binaries --- .drone.sec | 1 + .drone.yml | 8 +++++ .gitignore | 2 ++ build_binaries.sh | 87 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 98 insertions(+) create mode 100644 .drone.sec create mode 100755 build_binaries.sh diff --git a/.drone.sec b/.drone.sec new file mode 100644 index 000000000..ca46d022b --- /dev/null +++ b/.drone.sec @@ -0,0 +1 @@ +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.QMwm8no1b8LqvvCpXHvfZTJURtPVj1MtVaHvI_l4LX_0ocS3IZ6s2DQrj9iIgyI1_Fnxek1H7LL4Va1abOIP2AGTns44X8PHyfbgmlR_bKVbFIc8j6nhlbWCQRV_FLWhd2sQm-pHLK6yIeRJcJQ3AdrfZgsEMhOorGyxpt00ZIkEVRfRmkwigscMhnXuitJ71hlMfnbOZsWdd7SI6FIGEjhf_icL12hisaaD7xDQy06s0sL83kCbifLL_YumEMkWZu9KDHdCxL4lA8fF16-qhEnKbkRoj_28bx2D-92uikjSJ0KChLxae4MqioOwezq6_1_AGPQM61YtactDiV8Urw.HozkK1D_qqqQOWh6.npd88OpJDU5-6x1XDaHpQDe-Qv1R81F6KF5JLW_xvX9VU6B26Ix0oh97pstYh0qE3g8uAA2bYw0ZiBXnCwrxddYHUQFXVloycLDHBhzX2O-ihPoCgoFjA4Lu3JCfHZNZo3VsiKTCQah1B5Tfwv3vUVNofxg9zuDU9rHYDS7dRxpMtrUPpIntQX1fykz5LW8kaJg2rg0zSZ0_ZVMVhSz_SNDmFxcO-gPL6d8CNRPECxnIbmVAWYUEJp5piuqSS3Ax1vsMsr21.fDr19bn3CLN-t0x5uawr0g \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 22b857438..899cd58b8 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,3 +8,11 @@ build: - go test -v ./... - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... + - apt-get -qq update && apt-get install -y zip xz-utils + - ./build_binaries.sh +publish: + azure_storage: + account_key: $$AZURE_STORAGE_KEY + storage_account: thethingsnetwork + container: release + source: release/ diff --git a/.gitignore b/.gitignore index daf913b1b..e76b4c742 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,5 @@ _testmain.go *.exe *.test *.prof + +/release diff --git a/build_binaries.sh b/build_binaries.sh new file mode 100755 index 000000000..b6222dccc --- /dev/null +++ b/build_binaries.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +TTNROOT=${PWD} +RELEASEPATH=${RELEASEPATH:-$TTNROOT/release} + +mkdir -p $RELEASEPATH + +build_release() +{ + component=$1 + + export CGO_ENABLED=0 + export GOOS=$2 + export GOARCH=$3 + + release_name=$component-$GOOS-$GOARCH + + if [ "$GOOS" == "windows" ] + then + ext=".exe" + else + ext="" + fi + + binary_name=$release_name$ext + + echo "Building $component for $GOOS/$GOARCH" + + # Build + cd $TTNROOT + go build -a -installsuffix cgo -ldflags '-w' -o $RELEASEPATH/$binary_name ./integration/$component/main.go + + # Compress + cd $RELEASEPATH + tar -cvzf $release_name.tar.gz $binary_name + tar -cvJf $release_name.tar.xz $binary_name + zip $release_name.zip $binary_name + + # Delete Binary + rm $binary_name +} + +build_release router darwin 386 +build_release router darwin amd64 +build_release router linux 386 +build_release router linux amd64 +build_release router linux arm +build_release router windows 386 +build_release router windows amd64 + +build_release broker darwin 386 +build_release broker darwin amd64 +build_release broker linux 386 +build_release broker linux amd64 +build_release broker linux arm +build_release broker windows 386 +build_release broker windows amd64 + +# Prepare Releases +cd $RELEASEPATH + +# Commit Release +if [ "$CI_COMMIT" != "" ] +then + echo "Copying files for commit $CI_COMMIT" + mkdir $CI_COMMIT + cp ./router* $CI_COMMIT/ + cp ./broker* $CI_COMMIT/ +fi + +# Branch Release +if [ "$CI_BRANCH" != "" ] +then + echo "Copying files for branch $CI_BRANCH" + mkdir $CI_BRANCH + cp ./router* $CI_BRANCH/ + cp ./broker* $CI_BRANCH/ +fi + +# Tag Release +if [ "$CI_TAG" != "" ] +then + echo "Copying files for tag $CI_TAG" + mkdir $CI_TAG + cp ./router* $CI_TAG/ + cp ./broker* $CI_TAG/ +fi From a4d39f7004538400de8aa82f01fbe2ea810f1a2b Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 16:23:36 +0100 Subject: [PATCH 0461/2266] [handler] Continue implementing handler tests' logic. Checks are still missing --- core/components/handler_test.go | 208 ++++++++++++++++++++++++++++---- 1 file changed, 187 insertions(+), 21 deletions(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 6bfaacc6d..c93165905 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -8,40 +8,58 @@ import ( "testing" "time" - // "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core" // "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) func TestHandleUp(t *testing.T) { - applications := make(map[lorawan.EUI64]lorawan.AES128Key) - applications[lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8})] = lorawan.AES128Key([16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}) - applications[lorawan.EUI64([8]byte{9, 10, 11, 12, 13, 14, 15, 16})] = lorawan.AES128Key([16]byte{15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}) - applications[lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 4, 4})] = lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) + devices := []device{ + { + DevAddr: [4]byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + } + + applications := map[lorawan.EUI64]application{ + [8]byte{1, 2, 3, 4, 5, 6, 7, 8}: { + Devices: []device{}, + Registered: true, + }, + + [8]byte{9, 10, 11, 12, 13, 14, 15, 16}: { + Devices: []device{}, + Registered: true, + }, - packets := []plannedPacket{ + [8]byte{1, 1, 2, 2, 3, 3, 4, 4}: { + Devices: []device{}, + Registered: false, + }, + } + + packets := []packetShape{ { - AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - Data: "Packet 1 / Dev 1234 / App 12345678", + Device: devices[0], + Data: "Packet 1 / Dev 1234 / App 12345678", }, { - AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - Data: "Packet 1 / Dev 1234 / App 12345678", + Device: devices[0], + Data: "Packet 1 / Dev 1234 / App 12345678", }, } tests := []struct { - Schedule []schedule + Schedule schedule WantAck map[[4]byte]bool WantPackets map[[12]byte]string WantError error }{ { - Schedule: []schedule{ - {time.Millisecond * 25, packets[0]}, + Schedule: schedule{ + {time.Millisecond * 25, packets[0], nil}, }, WantAck: map[[4]byte]bool{ [4]byte{1, 2, 3, 4}: true, @@ -58,28 +76,176 @@ func TestHandleUp(t *testing.T) { // Build handler := genNewHandler(t, applications) + genPacketsFromSchedule(&test.Schedule) + chans := genComChannels("error", "ack", "nack", "packet") // Operate + go startSchedule(test.Schedule, handler, chans) // Check } } -type schedule struct { +type schedule []struct { Delay time.Duration - Packet plannedPacket + Shape packetShape + Packet *core.Packet } -type plannedPacket struct { - AppEUI lorawan.EUI64 +type device struct { DevAddr lorawan.DevAddr - Data string + AppSKey lorawan.AES128Key + NwkSKey lorawan.AES128Key } -func genNewHandler(t *testing.T, applications map[lorawan.EUI64]lorawan.AES128Key) Handler { +type packetShape struct { + Device device + Data string +} + +type application struct { + Devices []device + Registered bool +} + +func genPacketsFromSchedule(s *schedule) { + for i, entry := range *s { + // Build the macPayload + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{DevAddr: entry.Shape.Device.DevAddr} + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: []byte(entry.Shape.Data), + }} + macPayload.FPort = uint8(1) + if err := macPayload.EncryptFRMPayload(entry.Shape.Device.AppSKey); err != nil { + panic(err) + } + + // Build the physicalPayload + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + if err := phyPayload.SetMIC(entry.Shape.Device.NwkSKey); err != nil { + panic(err) + } + entry.Packet = &core.Packet{ + Payload: phyPayload, + Metadata: core.Metadata{}, + } + (*s)[i] = entry + } +} + +func genNewHandler(t *testing.T, applications map[lorawan.EUI64]application) *Handler { ctx := GetLogger(t, "Handler") handler, err := NewHandler(newHandlerDB(), ctx) if err != nil { panic(err) } + + for appEUI, app := range applications { + if !app.Registered { + continue + } + for _, device := range app.Devices { + handler.Register( + core.Registration{ + DevAddr: device.DevAddr, + Recipient: core.Recipient{ + Address: device.DevAddr, + Id: appEUI, + }, + Options: struct { + AppSKey lorawan.AES128Key + NwkSKey lorawan.AES128Key + }{ + AppSKey: device.AppSKey, + NwkSKey: device.NwkSKey, + }, + }, + voidAckNacker{}, + ) + } + } + return handler +} + +type voidAckNacker struct{} + +func (v voidAckNacker) Ack(packets ...core.Packet) error { + return nil +} +func (v voidAckNacker) Nack() error { + return nil +} + +func genComChannels(names ...string) map[string]chan interface{} { + chans := make(map[string]chan interface{}) + for _, name := range names { + chans[name] = make(chan interface{}) + } + return chans +} + +func startSchedule(s schedule, handler *Handler, chans map[string]chan interface{}) { + mockAn := chanAckNacker{AckChan: chans["ack"], NackChan: chans["nack"]} + mockAdapter := chanAdapter{PktChan: chans["packet"]} + + for _, event := range s { + <-time.After(event.Delay) + go func() { + err := handler.HandleUp(*event.Packet, mockAn, mockAdapter) + if err != nil { + chans["error"] <- err + } + }() + } +} + +type chanAckNacker struct { + AckChan chan interface{} + NackChan chan interface{} +} + +func (an chanAckNacker) Ack(packets ...core.Packet) error { + if len(packets) == 0 { + an.AckChan <- true + return nil + } + + for _, p := range packets { + an.AckChan <- p + } + return nil +} + +func (an chanAckNacker) Nack() error { + an.NackChan <- true + return nil +} + +type chanAdapter struct { + PktChan chan interface{} +} + +func (a chanAdapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { + a.PktChan <- struct { + Packet core.Packet + Recipient []core.Recipient + }{ + Packet: p, + Recipient: r, + } + return core.Packet{}, nil +} + +func (a chanAdapter) Next() (core.Packet, core.AckNacker, error) { + panic("Not Expected") +} + +func (a chanAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { + panic("Not Expected") } From 66287ad9b4b4ebee9c279529e085f7a87ce1fc49 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 16:34:03 +0100 Subject: [PATCH 0462/2266] [handler] Add backbone for check methods. Launch, nothing crashed. good point. --- core/components/handler_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index c93165905..365e81ac8 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -83,6 +83,9 @@ func TestHandleUp(t *testing.T) { go startSchedule(test.Schedule, handler, chans) // Check + checkChErrors(t, test.WantError, chans["error"]) + checkAcks(t, test.WantAck, chans["ack"], chans["nack"]) + checkPackets(t, test.WantPackets, chans["packet"]) } } @@ -249,3 +252,15 @@ func (a chanAdapter) Next() (core.Packet, core.AckNacker, error) { func (a chanAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { panic("Not Expected") } + +func checkChErrors(t *testing.T, want error, got chan interface{}) { + Ok(t, "YIPI") +} + +func checkAcks(t *testing.T, want map[[4]byte]bool, gotAck chan interface{}, gotNack chan interface{}) { + Ok(t, "YIPI") +} + +func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) { + Ok(t, "YIPI") +} From 02a8ccf7ea5006a97617299c3059402eea0f3b3b Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 17:48:02 +0100 Subject: [PATCH 0463/2266] [handler] Implement packet verification method --- core/components/handler_test.go | 52 ++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 365e81ac8..e14f92953 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -83,6 +83,7 @@ func TestHandleUp(t *testing.T) { go startSchedule(test.Schedule, handler, chans) // Check + <-time.After(time.Second * 2) checkChErrors(t, test.WantError, chans["error"]) checkAcks(t, test.WantAck, chans["ack"], chans["nack"]) checkPackets(t, test.WantPackets, chans["packet"]) @@ -262,5 +263,54 @@ func checkAcks(t *testing.T, want map[[4]byte]bool, gotAck chan interface{}, got } func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) { - Ok(t, "YIPI") + nb := 0 + for x := range got { + msg := x.(struct { + Packet core.Packet + Recipient []core.Recipient + }) + + if len(msg.Recipient) != 1 { + Ko(t, "Expected exactly one recipient but got %d", len(msg.Recipient)) + return + } + + appEUI := msg.Recipient[0].Id.(lorawan.EUI64) + devAddr, err := msg.Packet.DevAddr() + if err != nil { + Ko(t, "Unexpected error: %v", err) + return + } + + var id [12]byte + copy(id[:8], appEUI[:]) + copy(id[8:], devAddr[:]) + + wantData, ok := want[id] + if !ok { + Ko(t, "Received unexpected packet for app %x and from node %x", appEUI, devAddr) + return + } + + macPayload := msg.Packet.Payload.MACPayload.(*lorawan.MACPayload) + if len(macPayload.FRMPayload) != 1 { + Ko(t, "Invalid macpayload in received packet from node %x", devAddr) + return + } + + gotData := string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes) + if wantData != gotData { + Ko(t, "Received data don't match expectation.\nWant: %s\nGot: %s", wantData, gotData) + return + } + + nb += 1 + } + + if nb != len(want) { + Ko(t, "Handler sent %d packets whereas %d were/was expected", nb, len(want)) + return + } + + Ok(t, "Check packets") } From 3d526e1352906d82c148739b2ca39c7ecda86450 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 18:10:26 +0100 Subject: [PATCH 0464/2266] [handler] Add debug loglines in handler --- core/components/handler.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/components/handler.go b/core/components/handler.go index 281ea89b3..e3245b17a 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -79,14 +79,17 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { } func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { + h.ctx.Debug("Handling new uplink packet") partition, err := h.db.partition([]core.Packet{p}) if err != nil { + h.ctx.WithError(err).Debug("Unable to find entry") an.Nack() return err } fcnt, err := p.Fcnt() if err != nil { + h.ctx.WithError(err).Debug("Unable to retrieve fcnt") an.Nack() return err } @@ -97,6 +100,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap buf.Write(partition[0].id[:]) // Partition is necessarily of length 1, associated to 1 packet, the same we gave binary.Write(buf, binary.BigEndian, fcnt) copy(id[:], buf.Bytes()) + h.ctx.WithField("bundleId", id).Debug("Defining new bundle") h.set <- uplinkBundle{ id: id, packet: p, @@ -108,12 +112,15 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap resp := <-chresp switch resp.(type) { case core.Packet: + h.ctx.WithField("bundleId", id).Debug("Received response with packet. Sending ack") an.Ack(resp.(core.Packet)) return nil case error: + h.ctx.WithField("bundleId", id).WithError(resp.(error)).Debug("Received response. Sending Nack") an.Nack() return resp.(error) default: + h.ctx.WithField("bundleId", id).Debug("Received response. Sending ack") an.Ack() return nil } @@ -124,11 +131,15 @@ func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core. } func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { + ctx := h.ctx.WithField("goroutine", "consumer") + ctx.Debug("Starting bundle consumer") for bundles := range chbundles { var packet *core.Packet var sendToAdapter func(packet core.Packet) error + ctx.WithField("nb", len(bundles)).Debug("Consuming new bundles set") for _, bundle := range bundles { if packet == nil { + ctx.WithField("entry", bundle.entry).Debug("Preparing ground for given entry") *packet = core.Packet{ Payload: bundle.packet.Payload, Metadata: core.Metadata{ @@ -137,6 +148,7 @@ func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { } // The handler assumes payloads encrypted with AppSKey only ! if err := packet.Payload.DecryptMACPayload(bundle.entry.AppSKey); err != nil { + ctx.WithError(err).Debug("Unable to decrypt MAC Payload with given AppSKey") for _, bundle := range bundles { bundle.chresp <- err } @@ -157,6 +169,7 @@ func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { } err := sendToAdapter(*packet) + ctx.WithField("error", err).Debug("Sending to bundle adapter") for _, bundle := range bundles { bundle.chresp <- err } @@ -166,6 +179,9 @@ func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { // manageBuffers gather new incoming bundles that possess the same id // It then flushs them once a given delay has passed since the reception of the first bundle. func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplinkBundle) { + ctx := h.ctx.WithField("goroutine", "bufferer") + ctx.Debug("Starting uplink packets buffering") + buffers := make(map[bundleId][]uplinkBundle) alarm := make(chan bundleId) @@ -175,10 +191,12 @@ func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplink b := buffers[id] delete(buffers, id) go func(b []uplinkBundle) { bundles <- b }(b) + ctx.WithField("bundleId", id).Debug("Alarm done. Consuming collected bundles") case bundle := <-set: b := append(buffers[bundle.id], bundle) if len(b) == 1 { go setAlarm(alarm, bundle.id, time.Millisecond*300) + ctx.WithField("bundleId", bundle.id).Debug("Starting buffering. New alarm set") } buffers[bundle.id] = b } From 06c2e691e6ce36694f209361a8758ce4b796480e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 26 Jan 2016 19:08:53 +0100 Subject: [PATCH 0465/2266] [handler] Start debugging and fixing issues with handler and tests --- core/components/handler.go | 5 ++++- core/components/handler_test.go | 26 +++++++++----------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/core/components/handler.go b/core/components/handler.go index e3245b17a..9a90a1c7d 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -51,6 +51,7 @@ func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { } func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { + h.ctx.WithField("registration", reg).Debug("New registration request") options, okOpts := reg.Options.(struct { AppSKey lorawan.AES128Key NwkSKey lorawan.AES128Key @@ -133,6 +134,7 @@ func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core. func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { ctx := h.ctx.WithField("goroutine", "consumer") ctx.Debug("Starting bundle consumer") +browseBundles: for bundles := range chbundles { var packet *core.Packet var sendToAdapter func(packet core.Packet) error @@ -140,6 +142,7 @@ func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { for _, bundle := range bundles { if packet == nil { ctx.WithField("entry", bundle.entry).Debug("Preparing ground for given entry") + packet = new(core.Packet) *packet = core.Packet{ Payload: bundle.packet.Payload, Metadata: core.Metadata{ @@ -152,7 +155,7 @@ func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { for _, bundle := range bundles { bundle.chresp <- err } - break + continue browseBundles } sendToAdapter = func(packet core.Packet) error { diff --git a/core/components/handler_test.go b/core/components/handler_test.go index e14f92953..f6edff107 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -25,19 +25,9 @@ func TestHandleUp(t *testing.T) { applications := map[lorawan.EUI64]application{ [8]byte{1, 2, 3, 4, 5, 6, 7, 8}: { - Devices: []device{}, + Devices: []device{devices[0]}, Registered: true, }, - - [8]byte{9, 10, 11, 12, 13, 14, 15, 16}: { - Devices: []device{}, - Registered: true, - }, - - [8]byte{1, 1, 2, 2, 3, 3, 4, 4}: { - Devices: []device{}, - Registered: false, - }, } packets := []packetShape{ @@ -45,10 +35,6 @@ func TestHandleUp(t *testing.T) { Device: devices[0], Data: "Packet 1 / Dev 1234 / App 12345678", }, - { - Device: devices[0], - Data: "Packet 1 / Dev 1234 / App 12345678", - }, } tests := []struct { @@ -84,6 +70,12 @@ func TestHandleUp(t *testing.T) { // Check <-time.After(time.Second * 2) + go func() { + <-time.After(time.Millisecond * 250) + for _, ch := range chans { + close(ch) + } + }() checkChErrors(t, test.WantError, chans["error"]) checkAcks(t, test.WantAck, chans["ack"], chans["nack"]) checkPackets(t, test.WantPackets, chans["packet"]) @@ -189,7 +181,7 @@ func (v voidAckNacker) Nack() error { func genComChannels(names ...string) map[string]chan interface{} { chans := make(map[string]chan interface{}) for _, name := range names { - chans[name] = make(chan interface{}) + chans[name] = make(chan interface{}, 50) } return chans } @@ -308,7 +300,7 @@ func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) } if nb != len(want) { - Ko(t, "Handler sent %d packets whereas %d were/was expected", nb, len(want)) + Ko(t, "Handler sent %d packet(s) whereas %d were/was expected", nb, len(want)) return } From 27a03dd66346bf1aead1ad037809673475275121 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jan 2016 10:12:09 +0100 Subject: [PATCH 0466/2266] Fix directory creation --- build_binaries.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build_binaries.sh b/build_binaries.sh index b6222dccc..8379769ee 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -63,7 +63,7 @@ cd $RELEASEPATH if [ "$CI_COMMIT" != "" ] then echo "Copying files for commit $CI_COMMIT" - mkdir $CI_COMMIT + mkdir -p $CI_COMMIT cp ./router* $CI_COMMIT/ cp ./broker* $CI_COMMIT/ fi @@ -72,7 +72,7 @@ fi if [ "$CI_BRANCH" != "" ] then echo "Copying files for branch $CI_BRANCH" - mkdir $CI_BRANCH + mkdir -p $CI_BRANCH cp ./router* $CI_BRANCH/ cp ./broker* $CI_BRANCH/ fi @@ -81,7 +81,7 @@ fi if [ "$CI_TAG" != "" ] then echo "Copying files for tag $CI_TAG" - mkdir $CI_TAG + mkdir -p $CI_TAG cp ./router* $CI_TAG/ cp ./broker* $CI_TAG/ fi From 66c1f4e2da6ed95ad5ac33384b5bb2ae5125d620 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jan 2016 10:12:26 +0100 Subject: [PATCH 0467/2266] Update Secure Drone Environment --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index ca46d022b..d1d6cc58e 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.QMwm8no1b8LqvvCpXHvfZTJURtPVj1MtVaHvI_l4LX_0ocS3IZ6s2DQrj9iIgyI1_Fnxek1H7LL4Va1abOIP2AGTns44X8PHyfbgmlR_bKVbFIc8j6nhlbWCQRV_FLWhd2sQm-pHLK6yIeRJcJQ3AdrfZgsEMhOorGyxpt00ZIkEVRfRmkwigscMhnXuitJ71hlMfnbOZsWdd7SI6FIGEjhf_icL12hisaaD7xDQy06s0sL83kCbifLL_YumEMkWZu9KDHdCxL4lA8fF16-qhEnKbkRoj_28bx2D-92uikjSJ0KChLxae4MqioOwezq6_1_AGPQM61YtactDiV8Urw.HozkK1D_qqqQOWh6.npd88OpJDU5-6x1XDaHpQDe-Qv1R81F6KF5JLW_xvX9VU6B26Ix0oh97pstYh0qE3g8uAA2bYw0ZiBXnCwrxddYHUQFXVloycLDHBhzX2O-ihPoCgoFjA4Lu3JCfHZNZo3VsiKTCQah1B5Tfwv3vUVNofxg9zuDU9rHYDS7dRxpMtrUPpIntQX1fykz5LW8kaJg2rg0zSZ0_ZVMVhSz_SNDmFxcO-gPL6d8CNRPECxnIbmVAWYUEJp5piuqSS3Ax1vsMsr21.fDr19bn3CLN-t0x5uawr0g \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.ZUOdAoSbgRZrAhDm1F5jYir8YoFBqODidStrsLrn4wTTWRHSP6dE4YT7UEN2V8pCI_IcPPxUJxkc2hIQx1gihWbyFZ2RhFHHwQsEE-dOLBxTAx2IvafZbhTpDEhIxqky796c9vbN5bvUyb_QIieSsd6ipPdrtNoPWZdPIzBeoh7_ZhmDL0Y5q3BvKvEj-Vb07bpAPSb3feoSkgHCQM7_k0IQ830FspHNqwWPIqwk1rbggdESFwf3BHrvwia7JE2yfsiR5gDoRGIfAQGHGUu9Vl2SnyvqKY6PO9GqEPoHdsGtnLCWk5cskHzOjLg4M8o3EFerOVYS0akfnqycdg2Fgg.w-sxcvxlaitnjy4Q.pIp1K1xdrIbzbkEP_6aUppbWEmXL00aj08_8s1zWl6wadagq5j26V1hRoz1qsx2yXwpYo0IffaL8_9mJ8QUOJjG1CnU9-9GP26amOBflk7ms1Xs1eDbE79BRpgl9lReuMqedz7vQrJO7FqGhxdxH75YbehnnZ3GmPnu1aQLhGDu8kGK0YbBixeCdlVJBTcGeMWPq92_JaSQw06ouA4n-1XPrV5eNzv2s51ZEwj-rw87kALJTXkaDnC_fTeKcZMAv7Umi72Pa.MNjVKonuXk26ARXFL5Humg \ No newline at end of file From 1d6ff4d82b2d781617a1f14a947dc37a827508ca Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 11:57:26 +0100 Subject: [PATCH 0468/2266] [handler] Add error checker --- core/components/handler_test.go | 46 ++++++++++++++++++++++----------- 1 file changed, 31 insertions(+), 15 deletions(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index f6edff107..2c75c0f13 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -38,19 +38,19 @@ func TestHandleUp(t *testing.T) { } tests := []struct { - Schedule schedule + Schedule []event WantAck map[[4]byte]bool WantPackets map[[12]byte]string - WantError error + WantError []error }{ { - Schedule: schedule{ - {time.Millisecond * 25, packets[0], nil}, + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, }, WantAck: map[[4]byte]bool{ [4]byte{1, 2, 3, 4}: true, }, - WantError: nil, + WantError: []error{}, WantPackets: map[[12]byte]string{ [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", }, @@ -82,7 +82,7 @@ func TestHandleUp(t *testing.T) { } } -type schedule []struct { +type event struct { Delay time.Duration Shape packetShape Packet *core.Packet @@ -104,7 +104,7 @@ type application struct { Registered bool } -func genPacketsFromSchedule(s *schedule) { +func genPacketsFromSchedule(s *[]event) { for i, entry := range *s { // Build the macPayload macPayload := lorawan.NewMACPayload(true) @@ -186,18 +186,18 @@ func genComChannels(names ...string) map[string]chan interface{} { return chans } -func startSchedule(s schedule, handler *Handler, chans map[string]chan interface{}) { +func startSchedule(s []event, handler *Handler, chans map[string]chan interface{}) { mockAn := chanAckNacker{AckChan: chans["ack"], NackChan: chans["nack"]} mockAdapter := chanAdapter{PktChan: chans["packet"]} - for _, event := range s { - <-time.After(event.Delay) - go func() { - err := handler.HandleUp(*event.Packet, mockAn, mockAdapter) + for _, ev := range s { + <-time.After(ev.Delay) + go func(ev event) { + err := handler.HandleUp(*ev.Packet, mockAn, mockAdapter) if err != nil { chans["error"] <- err } - }() + }(ev) } } @@ -246,8 +246,24 @@ func (a chanAdapter) NextRegistration() (core.Registration, core.AckNacker, erro panic("Not Expected") } -func checkChErrors(t *testing.T, want error, got chan interface{}) { - Ok(t, "YIPI") +func checkChErrors(t *testing.T, want []error, got chan interface{}) { + nb := 0 +outer: + for gotErr := range got { + for wantErr := range want { + if wantErr == gotErr { + nb += 1 + continue outer + } + } + Ko(t, "Got error [%v] but was only expecting: [%v]", gotErr, want) + return + } + if nb != len(want) { + Ko(t, "Expected %d error(s) but got only %d", len(want), nb) + return + } + Ok(t, "Check errors") } func checkAcks(t *testing.T, want map[[4]byte]bool, gotAck chan interface{}, gotNack chan interface{}) { From d8ff4660c8eb06800e8f3ccc70958365b2ed60df Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 13:58:57 +0100 Subject: [PATCH 0469/2266] [handler] Add last verification method --- core/components/handler_test.go | 52 +++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 2c75c0f13..98052e720 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -39,18 +39,16 @@ func TestHandleUp(t *testing.T) { tests := []struct { Schedule []event - WantAck map[[4]byte]bool + WantNbAck int WantPackets map[[12]byte]string - WantError []error + WantErrors []error }{ { Schedule: []event{ event{time.Millisecond * 25, packets[0], nil}, }, - WantAck: map[[4]byte]bool{ - [4]byte{1, 2, 3, 4}: true, - }, - WantError: []error{}, + WantNbAck: 1, + WantErrors: []error{}, WantPackets: map[[12]byte]string{ [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", }, @@ -76,8 +74,8 @@ func TestHandleUp(t *testing.T) { close(ch) } }() - checkChErrors(t, test.WantError, chans["error"]) - checkAcks(t, test.WantAck, chans["ack"], chans["nack"]) + checkChErrors(t, test.WantErrors, chans["error"]) + checkAcks(t, test.WantNbAck, chans["ack"], chans["nack"]) checkPackets(t, test.WantPackets, chans["packet"]) } } @@ -207,14 +205,7 @@ type chanAckNacker struct { } func (an chanAckNacker) Ack(packets ...core.Packet) error { - if len(packets) == 0 { - an.AckChan <- true - return nil - } - - for _, p := range packets { - an.AckChan <- p - } + an.AckChan <- true return nil } @@ -266,8 +257,33 @@ outer: Ok(t, "Check errors") } -func checkAcks(t *testing.T, want map[[4]byte]bool, gotAck chan interface{}, gotNack chan interface{}) { - Ok(t, "YIPI") +func checkAcks(t *testing.T, want int, gotAck chan interface{}, gotNack chan interface{}) { + nbAck := 0 + nbNack := 0 +outer: + for { + select { + case _, ok := <-gotAck: + if !ok { + break outer + } + nbAck += 1 + + case _, ok := <-gotNack: + if !ok { + break outer + } + nbNack += 1 + default: + break outer + } + } + + if nbAck != want { + Ko(t, "Expected %d ack(s) but got %d", want, nbAck) + return + } + Ok(t, "Check acks") } func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) { From a6319b65694dfad509542dd763d4812be164d039 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 14:16:58 +0100 Subject: [PATCH 0470/2266] [handler] Fix macpayload decryption in handler process --- core/components/handler.go | 11 ++++++++++- core/components/handler_test.go | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/core/components/handler.go b/core/components/handler.go index 9a90a1c7d..3d1631fbc 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -150,7 +150,16 @@ browseBundles: }, } // The handler assumes payloads encrypted with AppSKey only ! - if err := packet.Payload.DecryptMACPayload(bundle.entry.AppSKey); err != nil { + payload, ok := packet.Payload.MACPayload.(*lorawan.MACPayload) + if !ok { + ctx.WithError(ErrInvalidPacket).Debug("Unable to extract MACPayload") + for _, bundle := range bundles { + bundle.chresp <- ErrInvalidPacket + } + continue browseBundles + } + + if err := payload.DecryptFRMPayload(bundle.entry.AppSKey); err != nil { ctx.WithError(err).Debug("Unable to decrypt MAC Payload with given AppSKey") for _, bundle := range bundles { bundle.chresp <- err diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 98052e720..50093005c 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -324,7 +324,7 @@ func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) gotData := string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes) if wantData != gotData { - Ko(t, "Received data don't match expectation.\nWant: %s\nGot: %s", wantData, gotData) + Ko(t, "Received data don't match expectations.\nWant: %s\nGot: %s", wantData, gotData) return } From cf5f915e4b77bb606425f13183c4ad1402ff3c21 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 15:10:12 +0100 Subject: [PATCH 0471/2266] [handler] Add second test in test suite + fix issue with ack check --- core/components/handler_test.go | 59 +++++++++++++++++---------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 50093005c..34c07c941 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -4,12 +4,11 @@ package components import ( - // "reflect" + "fmt" "testing" "time" "github.com/TheThingsNetwork/ttn/core" - // "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -38,17 +37,30 @@ func TestHandleUp(t *testing.T) { } tests := []struct { + Desc string Schedule []event WantNbAck int + WantNbNack int WantPackets map[[12]byte]string WantErrors []error }{ { + Desc: "Easy - one packet", Schedule: []event{ event{time.Millisecond * 25, packets[0], nil}, }, - WantNbAck: 1, - WantErrors: []error{}, + WantNbAck: 1, + WantPackets: map[[12]byte]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", + }, + }, + { + Desc: "Two packets from the same device within the time frame", + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, + event{time.Millisecond * 100, packets[0], nil}, + }, + WantNbAck: 2, WantPackets: map[[12]byte]string{ [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", }, @@ -57,6 +69,7 @@ func TestHandleUp(t *testing.T) { for _, test := range tests { // Describe + Desc(t, test.Desc) // Build handler := genNewHandler(t, applications) @@ -64,18 +77,18 @@ func TestHandleUp(t *testing.T) { chans := genComChannels("error", "ack", "nack", "packet") // Operate - go startSchedule(test.Schedule, handler, chans) + startSchedule(test.Schedule, handler, chans) // Check - <-time.After(time.Second * 2) go func() { - <-time.After(time.Millisecond * 250) + <-time.After(time.Second) for _, ch := range chans { close(ch) } }() checkChErrors(t, test.WantErrors, chans["error"]) - checkAcks(t, test.WantNbAck, chans["ack"], chans["nack"]) + checkAcks(t, test.WantNbAck, chans["ack"], "ack") + checkAcks(t, test.WantNbNack, chans["nack"], "nack") checkPackets(t, test.WantPackets, chans["packet"]) } } @@ -257,33 +270,21 @@ outer: Ok(t, "Check errors") } -func checkAcks(t *testing.T, want int, gotAck chan interface{}, gotNack chan interface{}) { - nbAck := 0 - nbNack := 0 -outer: +func checkAcks(t *testing.T, want int, got chan interface{}, kind string) { + nb := 0 for { - select { - case _, ok := <-gotAck: - if !ok { - break outer - } - nbAck += 1 - - case _, ok := <-gotNack: - if !ok { - break outer - } - nbNack += 1 - default: - break outer + a, ok := <-got + if !ok && a == nil { + break } + nb += 1 } - if nbAck != want { - Ko(t, "Expected %d ack(s) but got %d", want, nbAck) + if nb != want { + Ko(t, "Expected %d %s(s) but got %d", want, kind, nb) return } - Ok(t, "Check acks") + Ok(t, fmt.Sprintf("Check %s", kind)) } func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) { From 7457115e526454611c6e60972f5523110a2382d9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 17:49:59 +0100 Subject: [PATCH 0472/2266] [handler] Add additional test suites --- core/components/handler_test.go | 179 +++++++++++++++++++++++++++++--- 1 file changed, 164 insertions(+), 15 deletions(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 34c07c941..8c7e8abbf 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -20,13 +20,41 @@ func TestHandleUp(t *testing.T) { AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, }, + { + DevAddr: [4]byte{0, 0, 0, 2}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + }, + { + DevAddr: [4]byte{0, 0, 0, 3}, + AppSKey: [16]byte{1, 2, 3, 14, 42, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + NwkSKey: [16]byte{1, 2, 3, 42, 14, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + }, + { + DevAddr: [4]byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, + }, + { + DevAddr: [4]byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, + }, } applications := map[lorawan.EUI64]application{ [8]byte{1, 2, 3, 4, 5, 6, 7, 8}: { - Devices: []device{devices[0]}, + Devices: []device{devices[0], devices[1]}, Registered: true, }, + [8]byte{0, 9, 8, 7, 6, 5, 4, 3}: { + Devices: []device{devices[2], devices[3]}, + Registered: true, + }, + [8]byte{14, 14, 14, 14, 14, 14, 14, 14}: { + Devices: []device{devices[4]}, + Registered: false, + }, } packets := []packetShape{ @@ -34,6 +62,26 @@ func TestHandleUp(t *testing.T) { Device: devices[0], Data: "Packet 1 / Dev 1234 / App 12345678", }, + { + Device: devices[0], + Data: "Packet 2 / Dev 1234 / App 12345678", + }, + { + Device: devices[1], + Data: "Packet 1 / Dev 0002 / App 12345678", + }, + { + Device: devices[2], + Data: "Packet 1 / Dev 0003 / App 09876543", + }, + { + Device: devices[3], + Data: "Packet 1 / Dev 1234 / App 09876543", + }, + { + Device: devices[4], + Data: "Packet 1 / Dev 1234 / App 1414141414141414", + }, } tests := []struct { @@ -41,30 +89,126 @@ func TestHandleUp(t *testing.T) { Schedule []event WantNbAck int WantNbNack int - WantPackets map[[12]byte]string + WantPackets map[[12]byte][]string WantErrors []error }{ { - Desc: "Easy - one packet", + Desc: "1 packet", Schedule: []event{ event{time.Millisecond * 25, packets[0], nil}, }, WantNbAck: 1, - WantPackets: map[[12]byte]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: {"Packet 1 / Dev 1234 / App 12345678"}, }, }, { - Desc: "Two packets from the same device within the time frame", + Desc: "2 packets | same device | same payload | within time frame", Schedule: []event{ event{time.Millisecond * 25, packets[0], nil}, event{time.Millisecond * 100, packets[0], nil}, }, WantNbAck: 2, - WantPackets: map[[12]byte]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: "Packet 1 / Dev 1234 / App 12345678", + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ + packets[0].Data, + }, + }, + }, + { + Desc: "2 packets | same device | same payload | in 2 time frames", + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, + event{time.Millisecond * 750, packets[0], nil}, + }, + WantNbAck: 1, + WantNbNack: 1, + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ + packets[0].Data, + }, + }, + }, + { + Desc: "2 packets | same device | different payload | within time frame", + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, + event{time.Millisecond * 100, packets[1], nil}, + }, + WantNbAck: 2, + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ + packets[0].Data, + packets[1].Data, + }, + }, + }, + { + Desc: "3 packets | different device | same app | resp same payloads | within time frame", + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, + event{time.Millisecond * 50, packets[0], nil}, + event{time.Millisecond * 100, packets[2], nil}, + }, + WantNbAck: 3, + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ + packets[0].Data, + }, + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 2}: []string{ + packets[2].Data, + }, + }, + }, + { + Desc: "3 packets | different device | different app | resp same payloads | within time frame", + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, + event{time.Millisecond * 50, packets[2], nil}, + event{time.Millisecond * 100, packets[3], nil}, + }, + WantNbAck: 3, + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ + packets[0].Data, + }, + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 2}: []string{ + packets[2].Data, + }, + [12]byte{0, 9, 8, 7, 6, 5, 4, 3, 0, 0, 0, 3}: []string{ + packets[3].Data, + }, }, }, + { + Desc: "3 packets | different device | different app | resp same payloads | within time frame | dev address conflict", + Schedule: []event{ + event{time.Millisecond * 25, packets[0], nil}, + event{time.Millisecond * 50, packets[2], nil}, + event{time.Millisecond * 100, packets[4], nil}, + }, + WantNbAck: 3, + WantPackets: map[[12]byte][]string{ + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ + packets[0].Data, + }, + [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 2}: []string{ + packets[2].Data, + }, + [12]byte{0, 9, 8, 7, 6, 5, 4, 3, 1, 2, 3, 4}: []string{ + packets[4].Data, + }, + }, + }, + { + Desc: "1 packet | unknown application", + Schedule: []event{ + event{time.Millisecond * 25, packets[5], nil}, + }, + WantErrors: []error{ErrNotFound}, + WantNbNack: 1, + WantPackets: map[[12]byte][]string{}, + }, } for _, test := range tests { @@ -287,8 +431,9 @@ func checkAcks(t *testing.T, want int, got chan interface{}, kind string) { Ok(t, fmt.Sprintf("Check %s", kind)) } -func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) { +func checkPackets(t *testing.T, want map[[12]byte][]string, got chan interface{}) { nb := 0 +outer: for x := range got { msg := x.(struct { Packet core.Packet @@ -313,23 +458,27 @@ func checkPackets(t *testing.T, want map[[12]byte]string, got chan interface{}) wantData, ok := want[id] if !ok { - Ko(t, "Received unexpected packet for app %x and from node %x", appEUI, devAddr) + Ko(t, "Received unexpected packet for app %v and from node %v", appEUI, devAddr) return } macPayload := msg.Packet.Payload.MACPayload.(*lorawan.MACPayload) if len(macPayload.FRMPayload) != 1 { - Ko(t, "Invalid macpayload in received packet from node %x", devAddr) + Ko(t, "Invalid macpayload in received packet from node %v", devAddr) return } gotData := string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes) - if wantData != gotData { - Ko(t, "Received data don't match expectations.\nWant: %s\nGot: %s", wantData, gotData) - return + + for _, want := range wantData { + if want == gotData { + nb += 1 + continue outer + } } - nb += 1 + Ko(t, "Received data don't match expectations.\nWant: %v\nGot: %s", wantData, gotData) + return } if nb != len(want) { From f0277230e011ca0e3e18d4dc3178efe6992dab31 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 17:51:12 +0100 Subject: [PATCH 0473/2266] [handler] Move Err declaration errors.go --- core/components/handler.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/components/handler.go b/core/components/handler.go index 3d1631fbc..22e3d157f 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -6,7 +6,6 @@ package components import ( "bytes" "encoding/binary" - "fmt" "time" "github.com/TheThingsNetwork/ttn/core" @@ -16,8 +15,6 @@ import ( const BUFFER_DELAY = time.Millisecond * 300 -var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") - type Handler struct { ctx log.Interface db handlerStorage From d795ba8d1c9d1e785ed2330cac6b32eda581413c Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 28 Jan 2016 18:23:41 +0100 Subject: [PATCH 0474/2266] [handler] Fix late packet received error --- core/components/errors.go | 2 ++ core/components/handler.go | 16 ++++++++++++++-- core/components/handler_test.go | 7 ++++--- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/core/components/errors.go b/core/components/errors.go index 3ef1a625c..4464d0cb8 100644 --- a/core/components/errors.go +++ b/core/components/errors.go @@ -12,4 +12,6 @@ var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrBadOptions = fmt.Errorf("Invalid supplied options") var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") +var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") +var ErrAlreadyProcessed = fmt.Errorf("The packet reached the handler too late. It has already been processed.") diff --git a/core/components/handler.go b/core/components/handler.go index 22e3d157f..e9d7a29e7 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -191,17 +191,29 @@ func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplink ctx := h.ctx.WithField("goroutine", "bufferer") ctx.Debug("Starting uplink packets buffering") - buffers := make(map[bundleId][]uplinkBundle) - alarm := make(chan bundleId) + processed := make(map[[20]byte]bundleId) // AppEUI | DevAddr (without the frame counter) + buffers := make(map[bundleId][]uplinkBundle) // Associate bundleId to a list of bufferized bundles + alarm := make(chan bundleId) // Communication channel with sub-sequent alarm for { select { case id := <-alarm: b := buffers[id] delete(buffers, id) + var pid [20]byte + copy(pid[:], id[:20]) + processed[pid] = id go func(b []uplinkBundle) { bundles <- b }(b) ctx.WithField("bundleId", id).Debug("Alarm done. Consuming collected bundles") case bundle := <-set: + var pid [20]byte + copy(pid[:], bundle.id[:20]) + if processed[pid] == bundle.id { + ctx.WithField("bundleId", bundle.id).Debug("Reject already processed bundle") + go func(bundle uplinkBundle) { bundle.chresp <- ErrAlreadyProcessed }(bundle) + continue + } + b := append(buffers[bundle.id], bundle) if len(b) == 1 { go setAlarm(alarm, bundle.id, time.Millisecond*300) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 8c7e8abbf..7057e2fe6 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -37,8 +37,8 @@ func TestHandleUp(t *testing.T) { }, { DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 37, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 12, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, }, } @@ -123,6 +123,7 @@ func TestHandleUp(t *testing.T) { }, WantNbAck: 1, WantNbNack: 1, + WantErrors: []error{ErrAlreadyProcessed}, WantPackets: map[[12]byte][]string{ [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ packets[0].Data, @@ -398,7 +399,7 @@ func checkChErrors(t *testing.T, want []error, got chan interface{}) { nb := 0 outer: for gotErr := range got { - for wantErr := range want { + for _, wantErr := range want { if wantErr == gotErr { nb += 1 continue outer From 5bc4287f8a61ab12a86a0263c1d37ca0c4431b25 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 22 Jan 2016 15:37:12 +0100 Subject: [PATCH 0475/2266] Fix #24 -> Http adapter are know re-using same client for each request --- core/adapters/http/adapter.go | 11 +++++++---- core/adapters/http/broadcast/broadcast.go | 2 +- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 62612455b..07461cd1d 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -22,9 +22,11 @@ var ErrNotImplemented = fmt.Errorf("Illegal call on non-implemented method") type Adapter struct { Parser - serveMux *http.ServeMux - packets chan pktReq - ctx log.Interface + http.Client + sync.RWMutex // Guards clients + serveMux *http.ServeMux + packets chan pktReq + ctx log.Interface } type Parser interface { @@ -48,6 +50,7 @@ func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { serveMux: http.NewServeMux(), packets: make(chan pktReq), ctx: ctx, + Client: http.Client{}, } a.RegisterEndpoint("/packets", a.handlePostPacket) @@ -94,7 +97,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) buf.Write([]byte(payload)) // Send request - resp, err := http.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) + resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) if err != nil { cherr <- err return diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 10258c36c..dd4cbf98a 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -90,7 +90,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { buf := new(bytes.Buffer) buf.Write([]byte(payload)) - resp, err := http.Post(fmt.Sprintf("http://%s/packets", recipient.Address.(string)), "application/json", buf) + resp, err := a.Post(fmt.Sprintf("http://%s/packets", recipient.Address.(string)), "application/json", buf) if err != nil { cherr <- err return From 6c07437cef188300738c78c066b4c8e8bfa32444 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jan 2016 09:00:25 +0100 Subject: [PATCH 0476/2266] Update Drone security key --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index d1d6cc58e..470725f1e 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.ZUOdAoSbgRZrAhDm1F5jYir8YoFBqODidStrsLrn4wTTWRHSP6dE4YT7UEN2V8pCI_IcPPxUJxkc2hIQx1gihWbyFZ2RhFHHwQsEE-dOLBxTAx2IvafZbhTpDEhIxqky796c9vbN5bvUyb_QIieSsd6ipPdrtNoPWZdPIzBeoh7_ZhmDL0Y5q3BvKvEj-Vb07bpAPSb3feoSkgHCQM7_k0IQ830FspHNqwWPIqwk1rbggdESFwf3BHrvwia7JE2yfsiR5gDoRGIfAQGHGUu9Vl2SnyvqKY6PO9GqEPoHdsGtnLCWk5cskHzOjLg4M8o3EFerOVYS0akfnqycdg2Fgg.w-sxcvxlaitnjy4Q.pIp1K1xdrIbzbkEP_6aUppbWEmXL00aj08_8s1zWl6wadagq5j26V1hRoz1qsx2yXwpYo0IffaL8_9mJ8QUOJjG1CnU9-9GP26amOBflk7ms1Xs1eDbE79BRpgl9lReuMqedz7vQrJO7FqGhxdxH75YbehnnZ3GmPnu1aQLhGDu8kGK0YbBixeCdlVJBTcGeMWPq92_JaSQw06ouA4n-1XPrV5eNzv2s51ZEwj-rw87kALJTXkaDnC_fTeKcZMAv7Umi72Pa.MNjVKonuXk26ARXFL5Humg \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.EaEsQEMOMhq8hFqqnm2rnIIFizKzA6oVqkYD9D-eynxXivdOWF-U6PAFL8_3ppyyXo8IgRSr7bYWPtkLrjTUhLH7vibJ9zAMvSZJyQJFt9xRZRaMqjsVtu3FJg_8UXxKIOrrCBX9SHmBmDhrDPrwIQPWcJdpehHrEWcBIKD8gV9KBsS6OXmr9pGzCo6uRrZthoBJZ9JhUk_W8nJj0cdswzPm3iAv_-WgP1imodRjhwyGgPry0WvhB7861D1rpZ5JNgH3wc3-VYoS88QFwYRtB3TvzitglwSofrugQOQP6jRtfBEq14Z0d1KwHyEF5e6wJUaW9qseZo94kt0rNzBG3A.HhApIUuBDMis5Mc1.EGJ-9TP78pyF_noYVTXnN0sTbVXb4_JRZSTWMVyaoEtTaZOZMDEl1yBkRCQjjIR3W_3CfLNcqWVydURGT8ylg5F21kVeOMmHE3yPjrwIDBliLSiSF8KxJBIS8gD7TEzbGNRYoCIlETug_1SyVpjxF392OzhTJNznEKSX2XzpfajSbK18AItYxr5p0jTNP6GbIKOW1UPQlHr7celttsdpzWxYO9D46XdILmZfhW24Sndu3YFdYXr9oYKQAzKY3tbgtcpz8dKm.RjUaZVmUwhQobmCFruUIew \ No newline at end of file From bcf24d20ef3b051eadb798c25f5553bc092e1ab3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 29 Jan 2016 19:37:23 +0100 Subject: [PATCH 0477/2266] [in memory storage] Use of bolt for in-memory storage -> router --- core/components/errors.go | 2 + core/components/router_storage.go | 2 +- core/components/router_storage_bolt.go | 172 +++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 core/components/router_storage_bolt.go diff --git a/core/components/errors.go b/core/components/errors.go index 4464d0cb8..67b1170f8 100644 --- a/core/components/errors.go +++ b/core/components/errors.go @@ -15,3 +15,5 @@ var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") var ErrAlreadyProcessed = fmt.Errorf("The packet reached the handler too late. It has already been processed.") +var ErrNotMarshable = fmt.Errorf("The given data structure isn't marshable") +var ErrStorageUnreachable = fmt.Errorf("Something went seriously wrong with the storage") diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 30b172301..c592816b1 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -28,7 +28,7 @@ type routerEntry struct { } // NewLocalDB constructs a new local address keeper -func NewRouterStorage(expiryDelay time.Duration) (routerStorage, error) { +func NewRouterLocal(expiryDelay time.Duration) (routerStorage, error) { return &routerDB{ expiryDelay: expiryDelay, entries: make(map[lorawan.DevAddr]routerEntry), diff --git a/core/components/router_storage_bolt.go b/core/components/router_storage_bolt.go new file mode 100644 index 000000000..1f859c231 --- /dev/null +++ b/core/components/router_storage_bolt.go @@ -0,0 +1,172 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "bytes" + "encoding/binary" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/boltdb/bolt" + "github.com/brocaar/lorawan" +) + +const routerBucket = "brokers" + +type boltStorage struct { + db *bolt.DB + expiryDelay time.Duration +} + +func NewRouterBolt(expiryDelay time.Duration) (routerStorage, error) { + db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return nil, err + } + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(routerBucket)) + return err + }) + if err != nil { + return nil, err + } + + return boltStorage{ + expiryDelay: expiryDelay, + db: db, + }, nil +} + +func (s boltStorage) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { + var rawEntry []byte + entry := routerEntry{} + + err := s.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(routerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + rawEntry = bucket.Get(devAddr[:]) + if rawEntry == nil { + return ErrNotFound + } + return nil + }) + + if err != nil { + return nil, err + } + + err = entry.UnmarshalBinary(rawEntry) + if err != nil { + return nil, err + } + + if s.expiryDelay != 0 && entry.until.Before(time.Now()) { + err := s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(routerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + return bucket.Delete(devAddr[:]) + }) + if err != nil { + return nil, err + } + return nil, ErrEntryExpired + } + + return entry.recipients, nil +} + +func (s boltStorage) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error { + err := s.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(routerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + entry := bucket.Get(devAddr[:]) + if entry != nil { + return ErrAlreadyExists + } + + return nil + }) + + if err != nil { + return err + } + + err = s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(routerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + rawEntry, err := routerEntry{ + recipients: recipients, + until: time.Now().Add(s.expiryDelay), + }.MarshalBinary() + return bucket.Put(devAddr[:], rawEntry) + }) + + return err +} + +func (entry routerEntry) MarshalBinary() ([]byte, error) { + rawTime, err := entry.until.MarshalBinary() + if err != nil { + return nil, err + } + raw := new(bytes.Buffer) + + writeToRaw := func(content interface{}) { + if err != nil { + return + } + err = binary.Write(raw, binary.LittleEndian, content) + } + + writeToRaw(uint64(len(entry.recipients))) + + for _, recipient := range entry.recipients { + rawId := []byte(recipient.Id.(string)) + rawAddress := []byte(recipient.Address.(string)) + writeToRaw(uint64(len(rawId))) + writeToRaw(rawId) + writeToRaw(uint64(len(rawAddress))) + writeToRaw(rawAddress) + } + + writeToRaw(rawTime) + + if err != nil { + return nil, err + } + return raw.Bytes(), nil +} + +func (entry *routerEntry) UnmarshalBinary(data []byte) error { + if entry == nil || len(data) == 0 { + return ErrNotMarshable + } + cursor := 1 + for i := 0; i < int(data[0]); i += 1 { + lenId := int(data[cursor]) + cursor += 1 + id := data[cursor : cursor+lenId] + cursor += lenId + lenAddr := int(data[cursor]) + cursor := 1 + addr := data[cursor : cursor+lenAddr] + entry.recipients = append(entry.recipients, core.Recipient{ + Id: string(id), + Address: string(addr), + }) + cursor += lenAddr + } + + entry.until = time.Time{} + return entry.until.UnmarshalBinary(data[cursor:]) +} From 2f747981805ae00f7028e29a0157f05d7cd7eb1d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 14:21:55 +0100 Subject: [PATCH 0478/2266] brocaar/lorawan changed FCnt to uint32 --- core/packet.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packet.go b/core/packet.go index 636793652..c98f4d5ab 100644 --- a/core/packet.go +++ b/core/packet.go @@ -32,7 +32,7 @@ func (p Packet) DevAddr() (lorawan.DevAddr, error) { } // FCnt returns the frame counter of the given packet -func (p Packet) Fcnt() (uint16, error) { +func (p Packet) Fcnt() (uint32, error) { if p.Payload.MACPayload == nil { return 0, fmt.Errorf("MACPayload should not be empty") } From 2a8e73bae4a4a8727ff6500805a26ee86a518914 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 14:24:25 +0100 Subject: [PATCH 0479/2266] Split Drone build into multiple steps Only build and publish binaries for the develop and master branch --- .drone.sec | 2 +- .drone.yml | 29 ++++++++++++++++++----------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.drone.sec b/.drone.sec index 470725f1e..9df11ff47 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.EaEsQEMOMhq8hFqqnm2rnIIFizKzA6oVqkYD9D-eynxXivdOWF-U6PAFL8_3ppyyXo8IgRSr7bYWPtkLrjTUhLH7vibJ9zAMvSZJyQJFt9xRZRaMqjsVtu3FJg_8UXxKIOrrCBX9SHmBmDhrDPrwIQPWcJdpehHrEWcBIKD8gV9KBsS6OXmr9pGzCo6uRrZthoBJZ9JhUk_W8nJj0cdswzPm3iAv_-WgP1imodRjhwyGgPry0WvhB7861D1rpZ5JNgH3wc3-VYoS88QFwYRtB3TvzitglwSofrugQOQP6jRtfBEq14Z0d1KwHyEF5e6wJUaW9qseZo94kt0rNzBG3A.HhApIUuBDMis5Mc1.EGJ-9TP78pyF_noYVTXnN0sTbVXb4_JRZSTWMVyaoEtTaZOZMDEl1yBkRCQjjIR3W_3CfLNcqWVydURGT8ylg5F21kVeOMmHE3yPjrwIDBliLSiSF8KxJBIS8gD7TEzbGNRYoCIlETug_1SyVpjxF392OzhTJNznEKSX2XzpfajSbK18AItYxr5p0jTNP6GbIKOW1UPQlHr7celttsdpzWxYO9D46XdILmZfhW24Sndu3YFdYXr9oYKQAzKY3tbgtcpz8dKm.RjUaZVmUwhQobmCFruUIew \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.IOyfFjiw7NaksSBQuQxnww3PRVLSi87o2dSi2svEU4hNecposZAYVrMJrqZjmp5ZN5ztRuVCwr-JePjmH1G_XsBqUjGd1CydPPG8IGIyvQhGwlgbtWVq7VzpT-u3lfnf9h4ZmRaWlQswDB--Qj5dMk7H76KEsAN5h983jRt9uT905I53ohyElnFY-5xIJVD6puN66FCDsPLWhJwGTYJ1gc8uGnVvQ2OgjYwChNRTvshSxNH26VDu6-raPNiK8SNe_g_dJjp0TPxYReo5d1tohXOemMaYHQt6Xw5y6nZASMDOCULFVyUMRc4gx9tZfszTcPw7vSiPUA4m4Ajb_PB1LQ.KzfdX3JyVWmD64o6.sZuWhbZTyYy3groVr34n6doZKjfPHN65TVXBpm2XRGXtEIQA6SN8XwW5cJAsUrzjBe4Vu9-Y7Lpn_3xkqy43SxzOHUQs6WS0LaAqaeCyoKPTtjLKsJbUrhbCJNwdF2QzlRVHUeXDCA5UEZG7R3L0wKyFe5MJnyw_5Qt_VPIlWxkQ72q6Z9g74fwD0i45e5ofOKIqpZxQhz3TxKkP06hhGs4wCiTdFck01Q9dHoydtdsYCQYiC9ZnyL3wUnuISXlPDZL_Yq_m.B1igdtaYbRpcr12fjSxLAQ \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 899cd58b8..82944782a 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,18 +1,25 @@ build: - image: golang - commands: - - go get github.com/apex/log - - go get github.com/brocaar/lorawan - - go get github.com/jacobsa/crypto/cmac - - go get github.com/smartystreets/goconvey/convey - - go test -v ./... - - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - - go vet ./... - - apt-get -qq update && apt-get install -y zip xz-utils - - ./build_binaries.sh + test: + image: golang + commands: + - go get github.com/apex/log + - go get github.com/brocaar/lorawan + - go get github.com/jacobsa/crypto/cmac + - go get github.com/smartystreets/goconvey/convey + - go test -v ./... + - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' + - go vet ./... + release: + image: htdvisser/ttnbuild + commands: + - ./build_binaries.sh + when: + branch: [master, develop] publish: azure_storage: account_key: $$AZURE_STORAGE_KEY storage_account: thethingsnetwork container: release source: release/ + when: + branch: [master, develop] From 5867eaf36b14cbcc3f239c736c8ba07a1265e251 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 14:25:11 +0100 Subject: [PATCH 0480/2266] Fail build on error and use cleaner directory structure --- .drone.sec | 2 +- build_binaries.sh | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/.drone.sec b/.drone.sec index 9df11ff47..5597c5f49 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.IOyfFjiw7NaksSBQuQxnww3PRVLSi87o2dSi2svEU4hNecposZAYVrMJrqZjmp5ZN5ztRuVCwr-JePjmH1G_XsBqUjGd1CydPPG8IGIyvQhGwlgbtWVq7VzpT-u3lfnf9h4ZmRaWlQswDB--Qj5dMk7H76KEsAN5h983jRt9uT905I53ohyElnFY-5xIJVD6puN66FCDsPLWhJwGTYJ1gc8uGnVvQ2OgjYwChNRTvshSxNH26VDu6-raPNiK8SNe_g_dJjp0TPxYReo5d1tohXOemMaYHQt6Xw5y6nZASMDOCULFVyUMRc4gx9tZfszTcPw7vSiPUA4m4Ajb_PB1LQ.KzfdX3JyVWmD64o6.sZuWhbZTyYy3groVr34n6doZKjfPHN65TVXBpm2XRGXtEIQA6SN8XwW5cJAsUrzjBe4Vu9-Y7Lpn_3xkqy43SxzOHUQs6WS0LaAqaeCyoKPTtjLKsJbUrhbCJNwdF2QzlRVHUeXDCA5UEZG7R3L0wKyFe5MJnyw_5Qt_VPIlWxkQ72q6Z9g74fwD0i45e5ofOKIqpZxQhz3TxKkP06hhGs4wCiTdFck01Q9dHoydtdsYCQYiC9ZnyL3wUnuISXlPDZL_Yq_m.B1igdtaYbRpcr12fjSxLAQ \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.t0ldtWVp88gpedoECSlfzoD3W4SCMTtah-im1qJWTnUukbc1HzQI9HBSNM-KZ9Inj4YV7D1ihIp5DHl4PDw6VC_Z1QEbpWAAKJ4vd2-j2DppAYWTi-och8Jl-lwXL5vPLQXsen2EBWQAYTaPQndeZ81nCNk6UOVAIFLeBEMVh4ZjGLSPr2jmBCi50H0mGsN4bkkkuLEroF6zywEaZgJ121pX8Y-DuIWMTfMDdm6PSSauuELByPotoJEt6qfcxqBxEOiYQgE_hdCDjeB0UM6fFcIP_XwrPbRevrb2QDPtTEtvglw4v0U29SFBnvSYQasSIwDBDi8VhLTKLWHu9VfF6g.AEn8MpTeTPsF4Jli.Rt8H7PtsGw3genu-3UdY0B4YzYye3ym23kWQTYYz5AZXh6wl484kizeWkNvZbYf-d91Whu3Ri2MiXwLPZI9QVGzFIHG2CisUd6u03s4v8ctF7H8Brhd1wU2CcVIfDhxis7HJZLOXuh0lViCl1t5tFs4AzmcueRxzJUaFciCcj1BIG3TyxO6w3hfccVeYPWJp3o2dSQa7iUevWFWh4UWruLZs7h_ql59ps-Bu9DGEUFnjaEEhICq-YAS3az3tUEgG5d3_OhUx.Le3qKsSPpFqObt8x3F0KMw \ No newline at end of file diff --git a/build_binaries.sh b/build_binaries.sh index 8379769ee..85f34b3b8 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -1,5 +1,7 @@ #!/bin/bash +set -e + TTNROOT=${PWD} RELEASEPATH=${RELEASEPATH:-$TTNROOT/release} @@ -63,7 +65,7 @@ cd $RELEASEPATH if [ "$CI_COMMIT" != "" ] then echo "Copying files for commit $CI_COMMIT" - mkdir -p $CI_COMMIT + mkdir -p commit/$CI_COMMIT cp ./router* $CI_COMMIT/ cp ./broker* $CI_COMMIT/ fi @@ -72,7 +74,7 @@ fi if [ "$CI_BRANCH" != "" ] then echo "Copying files for branch $CI_BRANCH" - mkdir -p $CI_BRANCH + mkdir -p branch/$CI_BRANCH cp ./router* $CI_BRANCH/ cp ./broker* $CI_BRANCH/ fi @@ -81,7 +83,11 @@ fi if [ "$CI_TAG" != "" ] then echo "Copying files for tag $CI_TAG" - mkdir -p $CI_TAG + mkdir -p tag/$CI_TAG cp ./router* $CI_TAG/ cp ./broker* $CI_TAG/ fi + +# Remove Build Files +rm -f ./router* +rm -f ./broker* From 8b32f130e69cf56f9d16dc4b21d73a08444c8346 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 14:31:10 +0100 Subject: [PATCH 0481/2266] Also move build files to the right folder --- build_binaries.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build_binaries.sh b/build_binaries.sh index 85f34b3b8..a3088611d 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -66,8 +66,8 @@ if [ "$CI_COMMIT" != "" ] then echo "Copying files for commit $CI_COMMIT" mkdir -p commit/$CI_COMMIT - cp ./router* $CI_COMMIT/ - cp ./broker* $CI_COMMIT/ + cp ./router* commit/$CI_COMMIT/ + cp ./broker* commit/$CI_COMMIT/ fi # Branch Release @@ -75,8 +75,8 @@ if [ "$CI_BRANCH" != "" ] then echo "Copying files for branch $CI_BRANCH" mkdir -p branch/$CI_BRANCH - cp ./router* $CI_BRANCH/ - cp ./broker* $CI_BRANCH/ + cp ./router* branch/$CI_BRANCH/ + cp ./broker* branch/$CI_BRANCH/ fi # Tag Release @@ -84,8 +84,8 @@ if [ "$CI_TAG" != "" ] then echo "Copying files for tag $CI_TAG" mkdir -p tag/$CI_TAG - cp ./router* $CI_TAG/ - cp ./broker* $CI_TAG/ + cp ./router* tag/$CI_TAG/ + cp ./broker* tag/$CI_TAG/ fi # Remove Build Files From adda476f6bbc1aaec51ab9b8237220e675992203 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 17:21:27 +0100 Subject: [PATCH 0482/2266] Add decent CLI interaction to Router --- integration/router/main.go | 45 ++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/integration/router/main.go b/integration/router/main.go index e912efa30..e48665f2c 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -20,9 +20,6 @@ import ( ) func main() { - // Parse options - brokers, tcpPort, udpPort := parseOptions() - // Create Logging Context log.SetHandler(text.New(os.Stdout)) log.SetLevel(log.DebugLevel) @@ -30,25 +27,28 @@ func main() { "component": "Router", }) + // Parse options + brokers, tcpPort, udpPort := parseOptions() + // Instantiate all components gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Gateway Adapter") } pktAdapter, err := http.NewAdapter(uint(tcpPort), http.JSONPacketParser{}, ctx.WithField("tag", "Broker Adapter")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Broker Adapter") } brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Broker Adapter") } router, err := components.NewRouter(ctx.WithField("tag", "Router")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Router") } // Bring the service to life @@ -92,26 +92,37 @@ func parseOptions() (brokers []Recipient, tcpPort uint64, udpPort uint64) { var brokersFlag string var udpPortFlag string var tcpPortFlag string - flag.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. - For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000 - `) - flag.StringVar(&udpPortFlag, "udp-port", "", "UDP port on which the router should listen to.") - flag.StringVar(&tcpPortFlag, "tcp-port", "", "TCP port on which the router should listen to.") - flag.Parse() + + flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + + flags.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. + For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000`) + flags.StringVar(&udpPortFlag, "udp-port", "", "UDP port on which the router should listen to.") + flags.StringVar(&tcpPortFlag, "tcp-port", "", "TCP port on which the router should listen to.") + + flags.Parse(os.Args[1:]) var err error + + if tcpPortFlag == "" { + log.Fatal("No TCP listen port supplied using the -tcp-port flag") + } tcpPort, err = strconv.ParseUint(tcpPortFlag, 10, 64) if err != nil { - panic(err) + log.Fatal("Could not parse the value for -tcp-port") + } + + if udpPortFlag == "" { + log.Fatal("No UDP listen port supplied using the -udp-port flag.") } udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) if err != nil { - panic(err) + log.Fatal("Could not parse the value for -udp-port") } + if brokersFlag == "" { - panic("Need to provide at least one broker address") + log.Fatal("No broker address is supplied using -brokers flag.") } - brokersStr := strings.Split(brokersFlag, ",") for i := range brokersStr { brokers = append(brokers, Recipient{ From 57305cfacf3fcebd7ec262b4a3d7cf58adec628b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 17:30:04 +0100 Subject: [PATCH 0483/2266] Add decent CLI interaction to Broker --- integration/broker/main.go | 44 +++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/integration/broker/main.go b/integration/broker/main.go index 1995a3357..3d75acfba 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -5,7 +5,6 @@ package main import ( "flag" - "fmt" "os" "strconv" @@ -18,9 +17,6 @@ import ( ) func main() { - // Parse options - routersPort, handlersPort := parseOptions() - // Create Logging Context log.SetHandler(text.New(os.Stdout)) log.SetLevel(log.DebugLevel) @@ -28,25 +24,28 @@ func main() { "component": "Broker", }) + // Parse options + routersPort, handlersPort := parseOptions() + // Instantiate all components rtrAdapter, err := http.NewAdapter(uint(routersPort), http.JSONPacketParser{}, ctx.WithField("tag", "Routers Adapter")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Routers Adapter") } hdlHTTPAdapter, err := http.NewAdapter(uint(handlersPort), http.JSONPacketParser{}, ctx.WithField("tag", "Handlers Adapter")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Handlers Adapter") } hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, pubsub.HandlerParser{}, ctx.WithField("tag", "Handlers Adapter")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Handlers Adapter") } broker, err := components.NewBroker(ctx.WithField("tag", "Broker")) if err != nil { - panic(err) + ctx.WithError(err).Fatal("Could not start Broker") } // Bring the service to life @@ -56,12 +55,12 @@ func main() { for { packet, an, err := rtrAdapter.Next() if err != nil { - fmt.Println(err) + ctx.WithError(err).Error("Could not retrieve uplink") continue } go func(packet Packet, an AckNacker) { if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { - fmt.Println(err) + ctx.WithError(err).Error("Could not process uplink") } }(packet, an) } @@ -72,12 +71,12 @@ func main() { for { reg, an, err := hdlAdapter.NextRegistration() if err != nil { - fmt.Println(err) + ctx.WithError(err).Error("Could not retrieve registration") continue } go func(reg Registration, an AckNacker) { if err := broker.Register(reg, an); err != nil { - fmt.Println(err) + ctx.WithError(err).Error("Could not process registration") } }(reg, an) } @@ -89,19 +88,30 @@ func main() { func parseOptions() (routersPort uint64, handlersPort uint64) { var routersPortFlag string var handlersPortFlag string - flag.StringVar(&routersPortFlag, "routers-port", "", "TCP port on which the broker should listen to for incoming uplink packets.") - flag.StringVar(&handlersPortFlag, "handlers-port", "", "TCP port on which the broker should listen to for incoming registrations and downlink packet.") - flag.Parse() + + flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) + + flags.StringVar(&routersPortFlag, "routers-port", "", "TCP port on which the broker should listen to for incoming uplink packets.") + flags.StringVar(&handlersPortFlag, "handlers-port", "", "TCP port on which the broker should listen to for incoming registrations and downlink packet.") + + flags.Parse(os.Args[1:]) var err error + + if routersPortFlag == "" { + log.Fatal("No Router listen port supplied using the -routers-port flag") + } routersPort, err = strconv.ParseUint(routersPortFlag, 10, 64) if err != nil { - panic(err) + log.Fatal("Could not parse the value for -routers-port") } + if handlersPortFlag == "" { + log.Fatal("No Handler listen port supplied using the -handlers-port flag") + } handlersPort, err = strconv.ParseUint(handlersPortFlag, 10, 64) if err != nil { - panic(err) + log.Fatal("Could not parse the value for -handlers-port") } return From 847473a116f511064302106783b5065c5f46f4de Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 11:02:21 +0100 Subject: [PATCH 0484/2266] [in memory storage] Implement better broker in-memory storage --- core/components/broker_storage_bolt.go | 143 +++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100644 core/components/broker_storage_bolt.go diff --git a/core/components/broker_storage_bolt.go b/core/components/broker_storage_bolt.go new file mode 100644 index 000000000..345c62805 --- /dev/null +++ b/core/components/broker_storage_bolt.go @@ -0,0 +1,143 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "bytes" + "encoding/binary" + "io" + "time" + + "github.com/boltdb/bolt" + "github.com/brocaar/lorawan" +) + +const brokerBucket = "devices" + +type boltBrokerStorage struct { + db *bolt.DB +} + +func NewBrokerBolt(expiryDelay time.Duration) (brokerStorage, error) { + db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return nil, err + } + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(brokerBucket)) + return err + }) + if err != nil { + return nil, err + } + + return boltBrokerStorage{ + db: db, + }, nil +} + +func (s boltBrokerStorage) lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { + var rawEntry []byte + err := s.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(brokerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + rawEntry = bucket.Get(devAddr[:]) + if rawEntry == nil { + return ErrNotFound + } + return nil + }) + + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(rawEntry) + var entries []brokerEntry + for { + lenEntry := new(uint16) + if err := binary.Read(buf, binary.BigEndian, lenEntry); err != nil { + if err == io.EOF { + break + } + return nil, err + } + entry := new(brokerEntry) + if err := binary.Read(buf, binary.BigEndian, entry); err != nil { + return nil, err + } + entries = append(entries, *entry) + } + + return entries, nil +} + +func (s boltBrokerStorage) store(devAddr lorawan.DevAddr, entry brokerEntry) error { + marshalled, err := entry.MarshalBinary() + if err != nil { + return err + } + + err = s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(brokerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + buf := bytes.NewBuffer(bucket.Get(devAddr[:])) + binary.Write(buf, binary.BigEndian, len(marshalled)) + binary.Write(buf, binary.BigEndian, marshalled) + return bucket.Put(devAddr[:], buf.Bytes()) + }) + + return err +} + +func (entry brokerEntry) MarshalBinary() ([]byte, error) { + data := new(bytes.Buffer) + var err error + writeToData := func(content interface{}) { + if err != nil { + return + } + err = binary.Write(data, binary.BigEndian, content) + } + + writeToData(uint16(len(entry.Id))) + writeToData(entry.Id) + writeToData(uint16(len(entry.Url))) + writeToData(entry.Url) + writeToData(uint16(len(entry.NwsKey))) + writeToData(entry.NwsKey) + if err != nil { + return nil, err + } + return data.Bytes(), nil +} + +func (entry *brokerEntry) UnmarshalBinary(data []byte) error { + if entry == nil || len(data) < 3 { + return ErrNotUnmarshable + } + + buf := bytes.NewBuffer(data) + var err error + readFromData := func(to func(data []byte)) { + if err != nil { + return + } + lenTo := new(uint16) + if err = binary.Read(buf, binary.BigEndian, lenTo); err != nil { + return + } + to(buf.Next(int(*lenTo))) + } + + readFromData(func(data []byte) { entry.Id = string(data) }) + readFromData(func(data []byte) { entry.Url = string(data) }) + readFromData(func(data []byte) { copy(entry.NwsKey[:], data) }) + + return err +} From d9243e531c83e1e1ca2ca46cbfec18063ca3128d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 11:02:32 +0100 Subject: [PATCH 0485/2266] [in memory storage] Fix typo in error name --- core/components/errors.go | 2 +- core/components/router_storage_bolt.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/components/errors.go b/core/components/errors.go index 67b1170f8..19c91b822 100644 --- a/core/components/errors.go +++ b/core/components/errors.go @@ -15,5 +15,5 @@ var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") var ErrAlreadyProcessed = fmt.Errorf("The packet reached the handler too late. It has already been processed.") -var ErrNotMarshable = fmt.Errorf("The given data structure isn't marshable") +var ErrNotUnmarshable = fmt.Errorf("The given data structure isn't unmarshable") var ErrStorageUnreachable = fmt.Errorf("Something went seriously wrong with the storage") diff --git a/core/components/router_storage_bolt.go b/core/components/router_storage_bolt.go index 1f859c231..ae70b23c0 100644 --- a/core/components/router_storage_bolt.go +++ b/core/components/router_storage_bolt.go @@ -149,7 +149,7 @@ func (entry routerEntry) MarshalBinary() ([]byte, error) { func (entry *routerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) == 0 { - return ErrNotMarshable + return ErrNotUnmarshable } cursor := 1 for i := 0; i < int(data[0]); i += 1 { From 0b4d9f46992cb382758adc1a22b6e0a5796e95d8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 11:56:48 +0100 Subject: [PATCH 0486/2266] [in memory storage] Implement handler bolt storage. Now we need some refactor and tests --- core/components/handler_storage_bolt.go | 197 ++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 core/components/handler_storage_bolt.go diff --git a/core/components/handler_storage_bolt.go b/core/components/handler_storage_bolt.go new file mode 100644 index 000000000..665837416 --- /dev/null +++ b/core/components/handler_storage_bolt.go @@ -0,0 +1,197 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "bytes" + "encoding/binary" + "io" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/boltdb/bolt" + "github.com/brocaar/lorawan" +) + +const handlerBucket = "applications" + +type boltHandlerStorage struct { + db *bolt.DB +} + +func NewHandlerBolt() (handlerStorage, error) { + db, err := bolt.Open("handler_storage.db", 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return nil, err + } + err = db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(handlerBucket)) + return err + }) + if err != nil { + return nil, err + } + + return boltHandlerStorage{ + db: db, + }, nil +} + +func (s boltHandlerStorage) partition(packets []core.Packet) ([]handlerPartition, error) { + // Create a map in order to do the partition + partitions := make(map[partitionId]handlerPartition) + + for _, packet := range packets { + // First, determine devAddr, mandatory + devAddr, err := packet.DevAddr() + if err != nil { + return nil, ErrInvalidPacket + } + + entries, err := s.lookup(devAddr) + if err != nil { + return nil, err + } + + // Now get all tuples associated to that device address, and choose the right one + for _, entry := range entries { + // Compute MIC check to find the right keys + ok, err := packet.Payload.ValidateMIC(entry.NwkSKey) + if err != nil || !ok { + continue // These aren't the droids you're looking for + } + + // #Easy + var id partitionId + copy(id[:16], entry.AppEUI[:]) + copy(id[16:], entry.DevAddr[:]) + partitions[id] = handlerPartition{ + handlerEntry: entry, + id: id, + Packets: append(partitions[id].Packets, packet), + } + break // We shouldn't look for other entries, we've found the right one + } + } + + // Transform the map to a slice + res := make([]handlerPartition, 0, len(partitions)) + for _, p := range partitions { + res = append(res, p) + } + + if len(res) == 0 { + return nil, ErrNotFound + } + + return res, nil +} + +func (s boltHandlerStorage) lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { + var rawEntry []byte + err := s.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(handlerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + rawEntry = bucket.Get(devAddr[:]) + if rawEntry == nil { + return ErrNotFound + } + return nil + }) + + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(rawEntry) + var entries []handlerEntry + for { + lenEntry := new(uint16) + if err := binary.Read(buf, binary.BigEndian, lenEntry); err != nil { + if err == io.EOF { + break + } + return nil, err + } + entry := new(handlerEntry) + if err := binary.Read(buf, binary.BigEndian, entry); err != nil { + return nil, err + } + entries = append(entries, *entry) + } + + return entries, nil +} + +func (s boltHandlerStorage) store(devAddr lorawan.DevAddr, entry handlerEntry) error { + marshalled, err := entry.MarshalBinary() + if err != nil { + return err + } + + err = s.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(brokerBucket)) + if bucket == nil { + return ErrStorageUnreachable + } + buf := bytes.NewBuffer(bucket.Get(devAddr[:])) + binary.Write(buf, binary.BigEndian, len(marshalled)) + binary.Write(buf, binary.BigEndian, marshalled) + return bucket.Put(devAddr[:], buf.Bytes()) + }) + + return err +} + +func (entry handlerEntry) MarshalBinary() ([]byte, error) { + data := new(bytes.Buffer) + var err error + writeToData := func(content interface{}) { + if err != nil { + return + } + err = binary.Write(data, binary.BigEndian, content) + } + + writeToData(uint16(len(entry.AppEUI))) + writeToData(entry.AppEUI) + writeToData(uint16(len(entry.NwkSKey))) + writeToData(entry.NwkSKey) + writeToData(uint16(len(entry.AppSKey))) + writeToData(entry.AppSKey) + writeToData(uint16(len(entry.DevAddr))) + writeToData(entry.DevAddr) + if err != nil { + return nil, err + } + return data.Bytes(), nil +} + +func (entry *handlerEntry) UnmarshalBinary(data []byte) error { + if entry == nil || len(data) < 4 { + return ErrNotUnmarshable + } + + buf := bytes.NewBuffer(data) + var err error + readFromData := func(to func(data []byte)) { + if err != nil { + return + } + lenTo := new(uint16) + if err = binary.Read(buf, binary.BigEndian, lenTo); err != nil { + return + } + to(buf.Next(int(*lenTo))) + } + + readFromData(func(data []byte) { copy(entry.AppEUI[:], data) }) + readFromData(func(data []byte) { copy(entry.NwkSKey[:], data) }) + readFromData(func(data []byte) { copy(entry.AppSKey[:], data) }) + readFromData(func(data []byte) { copy(entry.DevAddr[:], data) }) + + return err +} From 0abdaaedffea0c8d4b4d2b0687669553ea3b7792 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 13:28:09 +0100 Subject: [PATCH 0487/2266] [in memory storage] Automatically fetch new dependencies in integration scripts --- .drone.yml | 7 ++----- .travis.yml | 5 +---- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/.drone.yml b/.drone.yml index 82944782a..5fe9e7a6e 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,11 +2,8 @@ build: test: image: golang commands: - - go get github.com/apex/log - - go get github.com/brocaar/lorawan - - go get github.com/jacobsa/crypto/cmac - - go get github.com/smartystreets/goconvey/convey - - go test -v ./... + - go get $(go list -f '{{join .Deps " "}}' ./... | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -v "TheThingsNetwork") + - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... release: diff --git a/.travis.yml b/.travis.yml index aec7c6e36..98b07514d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,10 +4,7 @@ go: - 1.5 install: - - go get github.com/apex/log - - go get github.com/brocaar/lorawan - - go get github.com/jacobsa/crypto/cmac - - go get github.com/smartystreets/goconvey/convey + - go get $(go list -f '{{join .Deps " "}}' ./... | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -v "TheThingsNetwork") script: - go list ./... | grep -v integration | xargs go test From 3c8819f7c6ea6c6c7403121651575da273d450dc Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 14:05:47 +0100 Subject: [PATCH 0488/2266] [in memory storage] Fix integration scripts -> previous one needed package to be already installed -__- --- .drone.yml | 2 +- .travis.yml | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 5fe9e7a6e..1fa754c08 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,7 +2,7 @@ build: test: image: golang commands: - - go get $(go list -f '{{join .Deps " "}}' ./... | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -v "TheThingsNetwork") + - go get $(comm -23 <(sort <(go list -f '{{join .Deps "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... diff --git a/.travis.yml b/.travis.yml index 98b07514d..18f1194a7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ go: - 1.5 install: - - go get $(go list -f '{{join .Deps " "}}' ./... | xargs go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' | grep -v "TheThingsNetwork") - + - go get $(comm -23 <(sort <(go list -f '{{join .Deps "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) script: - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' From e13d4d5139bfede97a5ff8fca3078f1fc3a8689d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 16:16:38 +0100 Subject: [PATCH 0489/2266] [in memory storage] Refactor multiple storages into a unified one --- core/components/bolt_storage.go | 119 ++++++++++++++ core/components/broker_storage_bolt.go | 143 ----------------- core/components/handler_storage.go | 96 ------------ core/components/handler_storage_bolt.go | 197 ------------------------ core/components/router_storage.go | 73 --------- core/components/router_storage_bolt.go | 172 --------------------- 6 files changed, 119 insertions(+), 681 deletions(-) create mode 100644 core/components/bolt_storage.go delete mode 100644 core/components/broker_storage_bolt.go delete mode 100644 core/components/handler_storage.go delete mode 100644 core/components/handler_storage_bolt.go delete mode 100644 core/components/router_storage.go delete mode 100644 core/components/router_storage_bolt.go diff --git a/core/components/bolt_storage.go b/core/components/bolt_storage.go new file mode 100644 index 000000000..53eff1c3b --- /dev/null +++ b/core/components/bolt_storage.go @@ -0,0 +1,119 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "bytes" + "encoding/binary" + "io" + "reflect" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/boltdb/bolt" + "github.com/brocaar/lorawan" +) + +type routerStorage interface { + Lookup(devAddr lorawan.DevAddr) (routerEntry, error) + Store(devAddr lorawan.DevAddr, entry routerEntry) error +} + +type brokerStorage interface { + Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) + Store(devAddr lorawan.DevAddr, entry brokerEntry) error +} + +type handlerStorage interface { + Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) + Store(devAddr lorawan.DevAddr, entry handlerEntry) error + Partition(packet ...core.Packet) ([]handlerPartition, error) +} + +type storageEntry interface { + MarshalBinary() ([]byte, error) + UnmarshalBinary(data []byte) error +} + +type routerEntry struct { + Recipients []core.Recipient + Until time.Time +} + +type brokerEntry struct { + Id string + NwkSKey lorawan.AES128Key + Url string +} + +type handlerEntry struct { + AppEUI lorawan.EUI64 + AppSKey lorawan.AES128Key + DevAddr lorawan.DevAddr + NwkSKey lorawan.AES128Key +} + +type handlerPartition struct { + handlerEntry + Id partitionId + Packets []core.Packet +} + +type partitionId [20]byte + +func store(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, entry storageEntry) error { + marshalled, err := entry.MarshalBinary() + if err != nil { + return err + } + + err = db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(bucketName) + if bucket == nil { + return ErrStorageUnreachable + } + buf := bytes.NewBuffer(bucket.Get(devAddr[:])) + binary.Write(buf, binary.BigEndian, uint16(len(marshalled))) + binary.Write(buf, binary.BigEndian, marshalled) + return bucket.Put(devAddr[:], buf.Bytes()) + }) + + return err +} + +func lookup(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, shape storageEntry) (interface{}, error) { + var rawEntry []byte + err := db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket(bucketName) + if bucket == nil { + return ErrStorageUnreachable + } + rawEntry = bucket.Get(devAddr[:]) + if rawEntry == nil { + return ErrNotFound + } + return nil + }) + + if err != nil { + return nil, err + } + + buf := bytes.NewBuffer(rawEntry) + entryType := reflect.TypeOf(shape).Elem() + entries := reflect.MakeSlice(reflect.SliceOf(entryType), 0, 0) + for { + lenEntry := new(uint16) + if err := binary.Read(buf, binary.BigEndian, lenEntry); err != nil { + if err == io.EOF { + break + } + panic(err) + } + entry := reflect.New(entryType).Interface() + entry.(storageEntry).UnmarshalBinary(buf.Next(int(*lenEntry))) + entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) + } + return entries.Interface(), nil +} diff --git a/core/components/broker_storage_bolt.go b/core/components/broker_storage_bolt.go deleted file mode 100644 index 345c62805..000000000 --- a/core/components/broker_storage_bolt.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "bytes" - "encoding/binary" - "io" - "time" - - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -const brokerBucket = "devices" - -type boltBrokerStorage struct { - db *bolt.DB -} - -func NewBrokerBolt(expiryDelay time.Duration) (brokerStorage, error) { - db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, err - } - err = db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucketIfNotExists([]byte(brokerBucket)) - return err - }) - if err != nil { - return nil, err - } - - return boltBrokerStorage{ - db: db, - }, nil -} - -func (s boltBrokerStorage) lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { - var rawEntry []byte - err := s.db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(brokerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - rawEntry = bucket.Get(devAddr[:]) - if rawEntry == nil { - return ErrNotFound - } - return nil - }) - - if err != nil { - return nil, err - } - - buf := bytes.NewBuffer(rawEntry) - var entries []brokerEntry - for { - lenEntry := new(uint16) - if err := binary.Read(buf, binary.BigEndian, lenEntry); err != nil { - if err == io.EOF { - break - } - return nil, err - } - entry := new(brokerEntry) - if err := binary.Read(buf, binary.BigEndian, entry); err != nil { - return nil, err - } - entries = append(entries, *entry) - } - - return entries, nil -} - -func (s boltBrokerStorage) store(devAddr lorawan.DevAddr, entry brokerEntry) error { - marshalled, err := entry.MarshalBinary() - if err != nil { - return err - } - - err = s.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(brokerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - buf := bytes.NewBuffer(bucket.Get(devAddr[:])) - binary.Write(buf, binary.BigEndian, len(marshalled)) - binary.Write(buf, binary.BigEndian, marshalled) - return bucket.Put(devAddr[:], buf.Bytes()) - }) - - return err -} - -func (entry brokerEntry) MarshalBinary() ([]byte, error) { - data := new(bytes.Buffer) - var err error - writeToData := func(content interface{}) { - if err != nil { - return - } - err = binary.Write(data, binary.BigEndian, content) - } - - writeToData(uint16(len(entry.Id))) - writeToData(entry.Id) - writeToData(uint16(len(entry.Url))) - writeToData(entry.Url) - writeToData(uint16(len(entry.NwsKey))) - writeToData(entry.NwsKey) - if err != nil { - return nil, err - } - return data.Bytes(), nil -} - -func (entry *brokerEntry) UnmarshalBinary(data []byte) error { - if entry == nil || len(data) < 3 { - return ErrNotUnmarshable - } - - buf := bytes.NewBuffer(data) - var err error - readFromData := func(to func(data []byte)) { - if err != nil { - return - } - lenTo := new(uint16) - if err = binary.Read(buf, binary.BigEndian, lenTo); err != nil { - return - } - to(buf.Next(int(*lenTo))) - } - - readFromData(func(data []byte) { entry.Id = string(data) }) - readFromData(func(data []byte) { entry.Url = string(data) }) - readFromData(func(data []byte) { copy(entry.NwsKey[:], data) }) - - return err -} diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go deleted file mode 100644 index 50019ec92..000000000 --- a/core/components/handler_storage.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "sync" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -type partitionId [20]byte - -type handlerStorage interface { - store(lorawan.DevAddr, handlerEntry) error - partition([]core.Packet) ([]handlerPartition, error) -} - -type handlerPartition struct { - handlerEntry - id partitionId - Packets []core.Packet -} - -type handlerEntry struct { - AppEUI lorawan.EUI64 - NwkSKey lorawan.AES128Key - AppSKey lorawan.AES128Key - DevAddr lorawan.DevAddr -} - -type handlerDB struct { - sync.RWMutex // Guards entries - entries map[lorawan.DevAddr][]handlerEntry -} - -// newHandlerDB construct a new local handlerStorage -func newHandlerDB() handlerStorage { - return &handlerDB{entries: make(map[lorawan.DevAddr][]handlerEntry)} -} - -// store implements the handlerStorage interface -func (db *handlerDB) store(devAddr lorawan.DevAddr, entry handlerEntry) error { - db.Lock() - db.entries[devAddr] = append(db.entries[devAddr], entry) - db.Unlock() - return nil -} - -// partition implements the handlerStorage interface -func (db *handlerDB) partition(packets []core.Packet) ([]handlerPartition, error) { - // Create a map in order to do the partition - partitions := make(map[partitionId]handlerPartition) - - db.RLock() // We require lock on the whole block because we don't want the entries to change while building the partition. - for _, packet := range packets { - // First, determine devAddr and get the macPayload. Those are mandatory. - devAddr, err := packet.DevAddr() - if err != nil { - return nil, ErrInvalidPacket - } - - // Now, get all tuples associated to that device address, and choose the right one - for _, entry := range db.entries[devAddr] { - // Compute MIC check to find the right keys - ok, err := packet.Payload.ValidateMIC(entry.NwkSKey) - if err != nil || !ok { - continue // These aren't the droid you're looking for - } - - // #Easy - var id partitionId - copy(id[:16], entry.AppEUI[:]) - copy(id[16:], entry.DevAddr[:]) - partitions[id] = handlerPartition{ - handlerEntry: entry, - id: id, - Packets: append(partitions[id].Packets, packet), - } - break // We shouldn't look for other entries, we've found the right one - } - } - db.RUnlock() - - // Transform the map in a slice - res := make([]handlerPartition, 0, len(partitions)) - for _, p := range partitions { - res = append(res, p) - } - - if len(res) == 0 { - return nil, ErrNotFound - } - return res, nil -} diff --git a/core/components/handler_storage_bolt.go b/core/components/handler_storage_bolt.go deleted file mode 100644 index 665837416..000000000 --- a/core/components/handler_storage_bolt.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "bytes" - "encoding/binary" - "io" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -const handlerBucket = "applications" - -type boltHandlerStorage struct { - db *bolt.DB -} - -func NewHandlerBolt() (handlerStorage, error) { - db, err := bolt.Open("handler_storage.db", 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, err - } - err = db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucketIfNotExists([]byte(handlerBucket)) - return err - }) - if err != nil { - return nil, err - } - - return boltHandlerStorage{ - db: db, - }, nil -} - -func (s boltHandlerStorage) partition(packets []core.Packet) ([]handlerPartition, error) { - // Create a map in order to do the partition - partitions := make(map[partitionId]handlerPartition) - - for _, packet := range packets { - // First, determine devAddr, mandatory - devAddr, err := packet.DevAddr() - if err != nil { - return nil, ErrInvalidPacket - } - - entries, err := s.lookup(devAddr) - if err != nil { - return nil, err - } - - // Now get all tuples associated to that device address, and choose the right one - for _, entry := range entries { - // Compute MIC check to find the right keys - ok, err := packet.Payload.ValidateMIC(entry.NwkSKey) - if err != nil || !ok { - continue // These aren't the droids you're looking for - } - - // #Easy - var id partitionId - copy(id[:16], entry.AppEUI[:]) - copy(id[16:], entry.DevAddr[:]) - partitions[id] = handlerPartition{ - handlerEntry: entry, - id: id, - Packets: append(partitions[id].Packets, packet), - } - break // We shouldn't look for other entries, we've found the right one - } - } - - // Transform the map to a slice - res := make([]handlerPartition, 0, len(partitions)) - for _, p := range partitions { - res = append(res, p) - } - - if len(res) == 0 { - return nil, ErrNotFound - } - - return res, nil -} - -func (s boltHandlerStorage) lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { - var rawEntry []byte - err := s.db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(handlerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - rawEntry = bucket.Get(devAddr[:]) - if rawEntry == nil { - return ErrNotFound - } - return nil - }) - - if err != nil { - return nil, err - } - - buf := bytes.NewBuffer(rawEntry) - var entries []handlerEntry - for { - lenEntry := new(uint16) - if err := binary.Read(buf, binary.BigEndian, lenEntry); err != nil { - if err == io.EOF { - break - } - return nil, err - } - entry := new(handlerEntry) - if err := binary.Read(buf, binary.BigEndian, entry); err != nil { - return nil, err - } - entries = append(entries, *entry) - } - - return entries, nil -} - -func (s boltHandlerStorage) store(devAddr lorawan.DevAddr, entry handlerEntry) error { - marshalled, err := entry.MarshalBinary() - if err != nil { - return err - } - - err = s.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(brokerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - buf := bytes.NewBuffer(bucket.Get(devAddr[:])) - binary.Write(buf, binary.BigEndian, len(marshalled)) - binary.Write(buf, binary.BigEndian, marshalled) - return bucket.Put(devAddr[:], buf.Bytes()) - }) - - return err -} - -func (entry handlerEntry) MarshalBinary() ([]byte, error) { - data := new(bytes.Buffer) - var err error - writeToData := func(content interface{}) { - if err != nil { - return - } - err = binary.Write(data, binary.BigEndian, content) - } - - writeToData(uint16(len(entry.AppEUI))) - writeToData(entry.AppEUI) - writeToData(uint16(len(entry.NwkSKey))) - writeToData(entry.NwkSKey) - writeToData(uint16(len(entry.AppSKey))) - writeToData(entry.AppSKey) - writeToData(uint16(len(entry.DevAddr))) - writeToData(entry.DevAddr) - if err != nil { - return nil, err - } - return data.Bytes(), nil -} - -func (entry *handlerEntry) UnmarshalBinary(data []byte) error { - if entry == nil || len(data) < 4 { - return ErrNotUnmarshable - } - - buf := bytes.NewBuffer(data) - var err error - readFromData := func(to func(data []byte)) { - if err != nil { - return - } - lenTo := new(uint16) - if err = binary.Read(buf, binary.BigEndian, lenTo); err != nil { - return - } - to(buf.Next(int(*lenTo))) - } - - readFromData(func(data []byte) { copy(entry.AppEUI[:], data) }) - readFromData(func(data []byte) { copy(entry.NwkSKey[:], data) }) - readFromData(func(data []byte) { copy(entry.AppSKey[:], data) }) - readFromData(func(data []byte) { copy(entry.DevAddr[:], data) }) - - return err -} diff --git a/core/components/router_storage.go b/core/components/router_storage.go deleted file mode 100644 index c592816b1..000000000 --- a/core/components/router_storage.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -type routerStorage interface { - lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) - store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error -} - -type routerDB struct { - sync.RWMutex - expiryDelay time.Duration - entries map[lorawan.DevAddr]routerEntry -} - -type routerEntry struct { - recipients []core.Recipient - until time.Time -} - -// NewLocalDB constructs a new local address keeper -func NewRouterLocal(expiryDelay time.Duration) (routerStorage, error) { - return &routerDB{ - expiryDelay: expiryDelay, - entries: make(map[lorawan.DevAddr]routerEntry), - }, nil -} - -// lookup implements the addressKeeper interface -func (db *routerDB) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { - db.RLock() - entry, ok := db.entries[devAddr] - db.RUnlock() - if !ok { - return nil, ErrDeviceNotFound - } - - if db.expiryDelay != 0 && entry.until.Before(time.Now()) { - db.Lock() - delete(db.entries, devAddr) - db.Unlock() - return nil, ErrEntryExpired - } - - return entry.recipients, nil -} - -// store implements the addressKeeper interface -func (db *routerDB) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error { - db.Lock() - _, ok := db.entries[devAddr] - if ok { - db.Unlock() - return ErrAlreadyExists - } - - db.entries[devAddr] = routerEntry{ - recipients: recipients, - until: time.Now().Add(db.expiryDelay), - } - - db.Unlock() - return nil -} diff --git a/core/components/router_storage_bolt.go b/core/components/router_storage_bolt.go deleted file mode 100644 index ae70b23c0..000000000 --- a/core/components/router_storage_bolt.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "bytes" - "encoding/binary" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -const routerBucket = "brokers" - -type boltStorage struct { - db *bolt.DB - expiryDelay time.Duration -} - -func NewRouterBolt(expiryDelay time.Duration) (routerStorage, error) { - db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, err - } - err = db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucketIfNotExists([]byte(routerBucket)) - return err - }) - if err != nil { - return nil, err - } - - return boltStorage{ - expiryDelay: expiryDelay, - db: db, - }, nil -} - -func (s boltStorage) lookup(devAddr lorawan.DevAddr) ([]core.Recipient, error) { - var rawEntry []byte - entry := routerEntry{} - - err := s.db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(routerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - rawEntry = bucket.Get(devAddr[:]) - if rawEntry == nil { - return ErrNotFound - } - return nil - }) - - if err != nil { - return nil, err - } - - err = entry.UnmarshalBinary(rawEntry) - if err != nil { - return nil, err - } - - if s.expiryDelay != 0 && entry.until.Before(time.Now()) { - err := s.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(routerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - return bucket.Delete(devAddr[:]) - }) - if err != nil { - return nil, err - } - return nil, ErrEntryExpired - } - - return entry.recipients, nil -} - -func (s boltStorage) store(devAddr lorawan.DevAddr, recipients ...core.Recipient) error { - err := s.db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(routerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - entry := bucket.Get(devAddr[:]) - if entry != nil { - return ErrAlreadyExists - } - - return nil - }) - - if err != nil { - return err - } - - err = s.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(routerBucket)) - if bucket == nil { - return ErrStorageUnreachable - } - rawEntry, err := routerEntry{ - recipients: recipients, - until: time.Now().Add(s.expiryDelay), - }.MarshalBinary() - return bucket.Put(devAddr[:], rawEntry) - }) - - return err -} - -func (entry routerEntry) MarshalBinary() ([]byte, error) { - rawTime, err := entry.until.MarshalBinary() - if err != nil { - return nil, err - } - raw := new(bytes.Buffer) - - writeToRaw := func(content interface{}) { - if err != nil { - return - } - err = binary.Write(raw, binary.LittleEndian, content) - } - - writeToRaw(uint64(len(entry.recipients))) - - for _, recipient := range entry.recipients { - rawId := []byte(recipient.Id.(string)) - rawAddress := []byte(recipient.Address.(string)) - writeToRaw(uint64(len(rawId))) - writeToRaw(rawId) - writeToRaw(uint64(len(rawAddress))) - writeToRaw(rawAddress) - } - - writeToRaw(rawTime) - - if err != nil { - return nil, err - } - return raw.Bytes(), nil -} - -func (entry *routerEntry) UnmarshalBinary(data []byte) error { - if entry == nil || len(data) == 0 { - return ErrNotUnmarshable - } - cursor := 1 - for i := 0; i < int(data[0]); i += 1 { - lenId := int(data[cursor]) - cursor += 1 - id := data[cursor : cursor+lenId] - cursor += lenId - lenAddr := int(data[cursor]) - cursor := 1 - addr := data[cursor : cursor+lenAddr] - entry.recipients = append(entry.recipients, core.Recipient{ - Id: string(id), - Address: string(addr), - }) - cursor += lenAddr - } - - entry.until = time.Time{} - return entry.until.UnmarshalBinary(data[cursor:]) -} From 01a437939b78160c8ab1b557879ebcc370d9e914 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 16:31:42 +0100 Subject: [PATCH 0490/2266] [in memory storage] Create handy read/writer for marshalling / unmarshalling storage entries --- core/components/entryReadWriter.go | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 core/components/entryReadWriter.go diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go new file mode 100644 index 000000000..5636084cd --- /dev/null +++ b/core/components/entryReadWriter.go @@ -0,0 +1,50 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "bytes" + "encoding/binary" +) + +type entryReadWriter struct { + err error + data bytes.Buffer +} + +func NewEntryReadWriter(buf []byte) *entryReadWriter { + return &entryReadWriter{ + err: nil, + data: bytes.NewBuffer(buf), + } +} + +func (w *entryReadWriter) Write(data interface{}) { + if w.err != nil { + return + } + w.err = binary.Write(w.data, binary.BigEndian, data) +} + +func (w *entryReadWriter) Read(to func(data []byte)) { + if w.err != nil { + return + } + lenTo := new(uint16) + if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil { + return + } + to(w.data.Next(int(*lenTo))) +} + +func (w entryReadWriter) Bytes() ([]byte, error) { + if w.err != nil { + return nil, w.err + } + return w.data.Bytes(), nil +} + +func (w entryReadWriter) Err() error { + return w.err +} From d49e31c8112acfc34684a8625639351ee156435d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 16:32:11 +0100 Subject: [PATCH 0491/2266] [in memory storage] Refactor broker storage --- core/components/broker_storage.go | 67 ++++++++++--------- .../{bolt_storage.go => storage.go} | 11 --- 2 files changed, 35 insertions(+), 43 deletions(-) rename core/components/{bolt_storage.go => storage.go} (91%) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index fdafce2cf..8529bdd16 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -4,52 +4,55 @@ package components import ( - "sync" - + "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) type brokerStorage interface { - lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) - store(devAddr lorawan.DevAddr, entry brokerEntry) error + Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) + Store(devAddr lorawan.DevAddr, entry brokerEntry) error +} + +type brokerBoltStorage struct { + *bolt.DB } type brokerEntry struct { - Id string - Url string - NwsKey lorawan.AES128Key + Id string + NwkSKey lorawan.AES128Key + Url string } -type brokerDB struct { - sync.RWMutex - entries map[lorawan.DevAddr][]brokerEntry +func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { + entries, err := lookup(s.DB, []byte("devices"), devAddr, &brokerEntry{}) + if err != nil { + return nil, err + } + return entries.([]brokerEntry), nil } -// NewLocalDB constructs a new local brokerStorage -func NewBrokerStorage() (brokerStorage, error) { - return &brokerDB{entries: make(map[lorawan.DevAddr][]brokerEntry)}, nil +func (s brokerBoltStorage) Store(devAddr lorawan.DevAddr, entry brokerEntry) error { + return store(s.DB, []byte("devices"), devAddr, &entry) } -// lookup implements the brokerStorage interface -func (db *brokerDB) lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { - db.RLock() - entries, ok := db.entries[devAddr] - db.RUnlock() - if !ok { - return nil, ErrDeviceNotFound - } - return entries, nil +func (entry brokerEntry) MarshalBinary() ([]byte, error) { + w := NewEntryReadWriter(nil) + w.Write(uint16(len(entry.Id))) + w.Write(entry.Id) + w.Write(uint16(len(entry.NwkSKey))) + w.Write(entry.NwkSKey) + w.Write(uint16(len(entry.Url))) + w.Write(entry.Url) + return w.Bytes() } -// store implements the brokerStorage interface -func (db *brokerDB) store(devAddr lorawan.DevAddr, entry brokerEntry) error { - db.Lock() - defer db.Unlock() - entries, ok := db.entries[devAddr] - if !ok { - db.entries[devAddr] = []brokerEntry{entry} - return nil +func (entry *brokerEntry) UnmarshalBinary(data []byte) error { + if entry == nil || len(data) < 3 { + return ErrNotUnmarshable } - db.entries[devAddr] = append(entries, entry) - return nil + r := NewEntryReadWriter(data) + r.Read(func(data []byte) { entry.Id = string(data) }) + r.Read(func(data []byte) { copy(entry.NwkSKey[:], data) }) + r.Read(func(data []byte) { entry.Url = string(data) }) + return r.Err() } diff --git a/core/components/bolt_storage.go b/core/components/storage.go similarity index 91% rename from core/components/bolt_storage.go rename to core/components/storage.go index 53eff1c3b..ba04b626a 100644 --- a/core/components/bolt_storage.go +++ b/core/components/storage.go @@ -20,11 +20,6 @@ type routerStorage interface { Store(devAddr lorawan.DevAddr, entry routerEntry) error } -type brokerStorage interface { - Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) - Store(devAddr lorawan.DevAddr, entry brokerEntry) error -} - type handlerStorage interface { Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) Store(devAddr lorawan.DevAddr, entry handlerEntry) error @@ -41,12 +36,6 @@ type routerEntry struct { Until time.Time } -type brokerEntry struct { - Id string - NwkSKey lorawan.AES128Key - Url string -} - type handlerEntry struct { AppEUI lorawan.EUI64 AppSKey lorawan.AES128Key From 6732c9ccd5d1f1767760bebd6529e08739156a05 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 16:52:21 +0100 Subject: [PATCH 0492/2266] [in memory storage] Refactor router storage --- core/components/router_storage.go | 84 +++++++++++++++++++++++++++++++ core/components/storage.go | 11 ---- 2 files changed, 84 insertions(+), 11 deletions(-) create mode 100644 core/components/router_storage.go diff --git a/core/components/router_storage.go b/core/components/router_storage.go new file mode 100644 index 000000000..c41f8ecdb --- /dev/null +++ b/core/components/router_storage.go @@ -0,0 +1,84 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/boltdb/bolt" + "github.com/brocaar/lorawan" +) + +type routerStorage interface { + Lookup(devAddr lorawan.DevAddr) (routerEntry, error) + Store(devAddr lorawan.DevAddr, entry routerEntry) error +} + +type routerBoltStorage struct { + *bolt.DB + expiryDelay time.Duration +} + +type routerEntry struct { + Recipients []core.Recipient + Until time.Time +} + +func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]routerEntry, error) { + entries, err := lookup(s.DB, []byte("brokers"), devAddr, &routerEntry{}) + if err != nil { + return nil, err + } + return entries.([]routerEntry), nil +} + +func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { + return store(s.DB, []byte("brokers"), devAddr, &entry) +} + +func (entry routerEntry) MarshalBinary() ([]byte, error) { + w := NewEntryReadWriter(nil) + w.Write(uint16(len(entry.Recipients))) + for _, r := range entry.Recipients { + rawId := []byte(r.Id.(string)) + rawAddress := []byte(r.Address.(string)) + w.Write(uint16(len(rawId))) + w.Write(rawId) + w.Write(uint16(len(rawAddress))) + w.Write(rawAddress) + } + rawTime, err := entry.Until.MarshalBinary() + if err != nil { + return nil, err + } + w.Write(uint16(len(rawTime))) + w.Write(rawTime) + return w.Bytes() +} + +func (entry *routerEntry) UnmarshalBinary(data []byte) error { + if entry == nil || len(data) < 1 { + return ErrNotUnmarshable + } + r := NewEntryReadWriter(data[0:]) + for i := 0; i < int(data[0]); i += 1 { + var id, address string + r.Read(func(data []byte) { id = string(data) }) + r.Read(func(data []byte) { address = string(address) }) + entry.Recipients = append(entry.Recipients, core.Recipient{ + Id: id, + Address: address, + }) + } + var err error + r.Read(func(data []byte) { + entry.Until = time.Time{} + err = entry.Until.UnmarshalBinary(data) + }) + if err != nil { + return err + } + return r.Err() +} diff --git a/core/components/storage.go b/core/components/storage.go index ba04b626a..d94fc7ffd 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -8,18 +8,12 @@ import ( "encoding/binary" "io" "reflect" - "time" "github.com/TheThingsNetwork/ttn/core" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) -type routerStorage interface { - Lookup(devAddr lorawan.DevAddr) (routerEntry, error) - Store(devAddr lorawan.DevAddr, entry routerEntry) error -} - type handlerStorage interface { Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) Store(devAddr lorawan.DevAddr, entry handlerEntry) error @@ -31,11 +25,6 @@ type storageEntry interface { UnmarshalBinary(data []byte) error } -type routerEntry struct { - Recipients []core.Recipient - Until time.Time -} - type handlerEntry struct { AppEUI lorawan.EUI64 AppSKey lorawan.AES128Key From cd2ef54a86deefc1ce7e1ca805bf2c0790a13da7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 17:19:02 +0100 Subject: [PATCH 0493/2266] [in memory storage] Refactor handler storage --- core/components/handler_storage.go | 122 +++++++++++++++++++++++++++++ core/components/storage.go | 22 ------ 2 files changed, 122 insertions(+), 22 deletions(-) create mode 100644 core/components/handler_storage.go diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go new file mode 100644 index 000000000..65d2afa12 --- /dev/null +++ b/core/components/handler_storage.go @@ -0,0 +1,122 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/boltdb/bolt" + "github.com/brocaar/lorawan" +) + +type handlerStorage interface { + Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) + Store(devAddr lorawan.DevAddr, entry handlerEntry) error + Partition(packet ...core.Packet) ([]handlerPartition, error) +} + +type handlerBoltStorage struct { + *bolt.DB +} + +type handlerEntry struct { + AppEUI lorawan.EUI64 + AppSKey lorawan.AES128Key + DevAddr lorawan.DevAddr + NwkSKey lorawan.AES128Key +} + +type handlerPartition struct { + handlerEntry + Id partitionId + Packets []core.Packet +} + +type partitionId [20]byte + +func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { + entries, err := lookup(s.DB, []byte("applications"), devAddr, &handlerEntry{}) + if err != nil { + return nil, err + } + return entries.([]handlerEntry), nil +} + +func (s handlerBoltStorage) Store(devAddr lorawan.DevAddr, entry handlerEntry) error { + return store(s.DB, []byte("applications"), devAddr, &entry) +} + +func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartition, error) { + // Create a map in order to do the partition + partitions := make(map[partitionId]handlerPartition) + + for _, packet := range packets { + // First, determine devAddr, mandatory + devAddr, err := packet.DevAddr() + if err != nil { + return nil, ErrInvalidPacket + } + + entries, err := s.Lookup(devAddr) + if err != nil { + return nil, err + } + + // Now get all tuples associated to that device address, and choose the right one + for _, entry := range entries { + // Compute MIC check to find the right keys + ok, err := packet.Payload.ValidateMIC(entry.NwkSKey) + if err != nil || !ok { + continue // These aren't the droids you're looking for + } + + // #Easy + var id partitionId + copy(id[:16], entry.AppEUI[:]) + copy(id[16:], entry.DevAddr[:]) + partitions[id] = handlerPartition{ + handlerEntry: entry, + Id: id, + Packets: append(partitions[id].Packets, packet), + } + break // We shouldn't look for other entries, we've found the right one + } + } + + // Transform the map to a slice + res := make([]handlerPartition, 0, len(partitions)) + for _, p := range partitions { + res = append(res, p) + } + + if len(res) == 0 { + return nil, ErrNotFound + } + + return res, nil +} + +func (entry handlerEntry) MarshalBinary() ([]byte, error) { + w := NewEntryReadWriter(nil) + w.Write(uint16(len(entry.AppEUI))) + w.Write(entry.AppEUI) + w.Write(uint16(len(entry.AppSKey))) + w.Write(entry.AppSKey) + w.Write(uint16(len(entry.DevAddr))) + w.Write(entry.DevAddr) + w.Write(uint16(len(entry.NwkSKey))) + w.Write(entry.NwkSKey) + return w.Bytes() +} + +func (entry *handlerEntry) UnmarshalBinary(data []byte) error { + if entry == nil || len(data) < 4 { + return ErrNotUnmarshable + } + r := NewEntryReadWriter(data) + r.Read(func(data []byte) { copy(entry.AppEUI[:], data) }) + r.Read(func(data []byte) { copy(entry.AppSKey[:], data) }) + r.Read(func(data []byte) { copy(entry.DevAddr[:], data) }) + r.Read(func(data []byte) { copy(entry.NwkSKey[:], data) }) + return r.Err() +} diff --git a/core/components/storage.go b/core/components/storage.go index d94fc7ffd..e7ec72b0e 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -9,37 +9,15 @@ import ( "io" "reflect" - "github.com/TheThingsNetwork/ttn/core" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) -type handlerStorage interface { - Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) - Store(devAddr lorawan.DevAddr, entry handlerEntry) error - Partition(packet ...core.Packet) ([]handlerPartition, error) -} - type storageEntry interface { MarshalBinary() ([]byte, error) UnmarshalBinary(data []byte) error } -type handlerEntry struct { - AppEUI lorawan.EUI64 - AppSKey lorawan.AES128Key - DevAddr lorawan.DevAddr - NwkSKey lorawan.AES128Key -} - -type handlerPartition struct { - handlerEntry - Id partitionId - Packets []core.Packet -} - -type partitionId [20]byte - func store(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { From f345b33141ecd773fc76c9e50c2e20e6cd56bd22 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 17:26:04 +0100 Subject: [PATCH 0494/2266] [in memory storage] Use of EntryReadWriter in storage.go --- core/components/storage.go | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/core/components/storage.go b/core/components/storage.go index e7ec72b0e..e979a4705 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -4,8 +4,6 @@ package components import ( - "bytes" - "encoding/binary" "io" "reflect" @@ -29,10 +27,14 @@ func store(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, entry storag if bucket == nil { return ErrStorageUnreachable } - buf := bytes.NewBuffer(bucket.Get(devAddr[:])) - binary.Write(buf, binary.BigEndian, uint16(len(marshalled))) - binary.Write(buf, binary.BigEndian, marshalled) - return bucket.Put(devAddr[:], buf.Bytes()) + w := NewEntryReadWriter(bucket.Get(devAddr[:])) + w.Write(uint16(len(marshalled))) + w.Write(marshalled) + data, err := w.Bytes() + if err != nil { + return err + } + return bucket.Put(devAddr[:], data) }) return err @@ -56,20 +58,21 @@ func lookup(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, shape stora return nil, err } - buf := bytes.NewBuffer(rawEntry) + r := NewEntryReadWriter(rawEntry) entryType := reflect.TypeOf(shape).Elem() entries := reflect.MakeSlice(reflect.SliceOf(entryType), 0, 0) for { - lenEntry := new(uint16) - if err := binary.Read(buf, binary.BigEndian, lenEntry); err != nil { + r.Read(func(data []byte) { + entry := reflect.New(entryType).Interface() + entry.(storageEntry).UnmarshalBinary(data) + entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) + }) + if err = r.Err(); err != nil { if err == io.EOF { break } - panic(err) + return nil, err } - entry := reflect.New(entryType).Interface() - entry.(storageEntry).UnmarshalBinary(buf.Next(int(*lenEntry))) - entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) } return entries.Interface(), nil } From 42abda8d0c5743726ffa4417ddc8dacf3a90070e Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 18:46:12 +0100 Subject: [PATCH 0495/2266] [in memory storage] Make the data length be part of the Write method for entry writer --- core/components/broker_storage.go | 3 --- core/components/entryReadWriter.go | 11 ++++++++++- core/components/handler_storage.go | 4 ---- core/components/router_storage.go | 5 +---- core/components/storage.go | 1 - 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 8529bdd16..3a3a3ea3b 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -37,11 +37,8 @@ func (s brokerBoltStorage) Store(devAddr lorawan.DevAddr, entry brokerEntry) err func (entry brokerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) - w.Write(uint16(len(entry.Id))) w.Write(entry.Id) - w.Write(uint16(len(entry.NwkSKey))) w.Write(entry.NwkSKey) - w.Write(uint16(len(entry.Url))) w.Write(entry.Url) return w.Bytes() } diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go index 5636084cd..ed4e612d9 100644 --- a/core/components/entryReadWriter.go +++ b/core/components/entryReadWriter.go @@ -10,7 +10,7 @@ import ( type entryReadWriter struct { err error - data bytes.Buffer + data *bytes.Buffer } func NewEntryReadWriter(buf []byte) *entryReadWriter { @@ -21,6 +21,15 @@ func NewEntryReadWriter(buf []byte) *entryReadWriter { } func (w *entryReadWriter) Write(data interface{}) { + raw, ok := data.([]byte) + if !ok { + raw = []byte(data.(string)) + } + w.DirectWrite(uint16(len(raw))) + w.DirectWrite(raw) +} + +func (w *entryReadWriter) DirectWrite(data interface{}) { if w.err != nil { return } diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 65d2afa12..a18bab548 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -98,13 +98,9 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio func (entry handlerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) - w.Write(uint16(len(entry.AppEUI))) w.Write(entry.AppEUI) - w.Write(uint16(len(entry.AppSKey))) w.Write(entry.AppSKey) - w.Write(uint16(len(entry.DevAddr))) w.Write(entry.DevAddr) - w.Write(uint16(len(entry.NwkSKey))) w.Write(entry.NwkSKey) return w.Bytes() } diff --git a/core/components/router_storage.go b/core/components/router_storage.go index c41f8ecdb..8cf24d716 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -40,20 +40,17 @@ func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) err func (entry routerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) - w.Write(uint16(len(entry.Recipients))) + w.DirectWrite(uint8(len(entry.Recipients))) for _, r := range entry.Recipients { rawId := []byte(r.Id.(string)) rawAddress := []byte(r.Address.(string)) - w.Write(uint16(len(rawId))) w.Write(rawId) - w.Write(uint16(len(rawAddress))) w.Write(rawAddress) } rawTime, err := entry.Until.MarshalBinary() if err != nil { return nil, err } - w.Write(uint16(len(rawTime))) w.Write(rawTime) return w.Bytes() } diff --git a/core/components/storage.go b/core/components/storage.go index e979a4705..4ea7934db 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -28,7 +28,6 @@ func store(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, entry storag return ErrStorageUnreachable } w := NewEntryReadWriter(bucket.Get(devAddr[:])) - w.Write(uint16(len(marshalled))) w.Write(marshalled) data, err := w.Bytes() if err != nil { From 8b5b8526ac0ffb93c51fe1bec5fc69aae9a3c106 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 19:19:12 +0100 Subject: [PATCH 0496/2266] [in memory storage] Impact changes related to in-memory storage in every files --- core/components/broker.go | 24 ++++++---------- core/components/handler.go | 18 ++++++------ core/components/handler_storage.go | 13 ++++++++- core/components/handler_storage_test.go | 11 +++++--- core/components/handler_test.go | 8 +++++- core/components/router.go | 37 +++++++------------------ core/components/router_storage.go | 31 ++++++++++++++++----- core/components/storage.go | 10 +++++++ 8 files changed, 88 insertions(+), 64 deletions(-) diff --git a/core/components/broker.go b/core/components/broker.go index 853dd45dc..af8d73a66 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -16,17 +16,11 @@ type Broker struct { db brokerStorage } -func NewBroker(ctx log.Interface) (*Broker, error) { - localDB, err := NewBrokerStorage() - - if err != nil { - return nil, err - } - +func NewBroker(db brokerStorage, ctx log.Interface) *Broker { return &Broker{ ctx: ctx, - db: localDB, - }, nil + db: db, + } } func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { @@ -39,7 +33,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter return ErrInvalidPacket } ctx := b.ctx.WithField("devAddr", devAddr) - entries, err := b.db.lookup(devAddr) + entries, err := b.db.Lookup(devAddr) switch err { case nil: case ErrDeviceNotFound: @@ -54,7 +48,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter // check. Only one should verify the MIC check. var handler *core.Recipient for _, entry := range entries { - ok, err := p.Payload.ValidateMIC(entry.NwsKey) + ok, err := p.Payload.ValidateMIC(entry.NwkSKey) if err != nil { continue } @@ -88,18 +82,18 @@ func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) er func (b *Broker) Register(r core.Registration, an core.AckNacker) error { id, okId := r.Recipient.Id.(string) url, okUrl := r.Recipient.Address.(string) - nwsKey, okNwsKey := r.Options.(lorawan.AES128Key) + nwkSKey, okNwkSKey := r.Options.(lorawan.AES128Key) ctx := b.ctx.WithField("devAddr", r.DevAddr) - if !(okId && okUrl && okNwsKey) { + if !(okId && okUrl && okNwkSKey) { ctx.Warn("Invalid Registration") an.Nack() return ErrInvalidRegistration } - entry := brokerEntry{Id: id, Url: url, NwsKey: nwsKey} - if err := b.db.store(r.DevAddr, entry); err != nil { + entry := brokerEntry{Id: id, Url: url, NwkSKey: nwkSKey} + if err := b.db.Store(r.DevAddr, entry); err != nil { ctx.WithError(err).Error("Failed Registration") an.Nack() return err diff --git a/core/components/handler.go b/core/components/handler.go index e9d7a29e7..c38f89af0 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -17,21 +17,21 @@ const BUFFER_DELAY = time.Millisecond * 300 type Handler struct { ctx log.Interface - db handlerStorage + db HandlerStorage set chan<- uplinkBundle } type bundleId [22]byte // AppEUI | DevAddr | FCnt type uplinkBundle struct { - id bundleId - entry handlerEntry - packet core.Packet adapter core.Adapter chresp chan interface{} // Error or decrypted packet + entry handlerEntry + id bundleId + packet core.Packet } -func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { +func NewHandler(db HandlerStorage, ctx log.Interface) *Handler { h := Handler{ ctx: ctx, db: db, @@ -44,7 +44,7 @@ func NewHandler(db handlerStorage, ctx log.Interface) (*Handler, error) { go h.manageBuffers(bundles, set) h.set = set - return &h, nil + return &h } func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { @@ -60,7 +60,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { return ErrBadOptions } - err := h.db.store(reg.DevAddr, handlerEntry{ + err := h.db.Store(reg.DevAddr, handlerEntry{ AppEUI: appEUI, AppSKey: options.AppSKey, NwkSKey: options.NwkSKey, @@ -78,7 +78,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { h.ctx.Debug("Handling new uplink packet") - partition, err := h.db.partition([]core.Packet{p}) + partition, err := h.db.Partition(p) if err != nil { h.ctx.WithError(err).Debug("Unable to find entry") an.Nack() @@ -95,7 +95,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap chresp := make(chan interface{}) var id bundleId buf := new(bytes.Buffer) - buf.Write(partition[0].id[:]) // Partition is necessarily of length 1, associated to 1 packet, the same we gave + buf.Write(partition[0].Id[:]) // Partition is necessarily of length 1, associated to 1 packet, the same we gave binary.Write(buf, binary.BigEndian, fcnt) copy(id[:], buf.Bytes()) h.ctx.WithField("bundleId", id).Debug("Defining new bundle") diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index a18bab548..9e5468827 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -4,12 +4,14 @@ package components import ( + "time" + "github.com/TheThingsNetwork/ttn/core" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) -type handlerStorage interface { +type HandlerStorage interface { Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) Store(devAddr lorawan.DevAddr, entry handlerEntry) error Partition(packet ...core.Packet) ([]handlerPartition, error) @@ -34,6 +36,15 @@ type handlerPartition struct { type partitionId [20]byte +func NewHandlerStorage() (HandlerStorage, error) { + db, err := bolt.Open("handler_storage.db", 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return nil, err + } + + return &handlerBoltStorage{DB: db}, nil +} + func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { entries, err := lookup(s.DB, []byte("applications"), devAddr, &handlerEntry{}) if err != nil { diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 48c5b50a0..0bae9cace 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -115,7 +115,7 @@ func TestStoragePartition(t *testing.T) { packets := genPacketsFromHandlerEntries(test.PacketsShape) // Operate - partitions, err := db.partition(packets) + partitions, err := db.Partition(packets...) // Check checkErrors(t, test.WantError, err) @@ -130,11 +130,14 @@ type partitionShape struct { // ----- BUILD utilities -func genFilledHandlerStorage(setup []handlerEntry) (db handlerStorage) { - db = newHandlerDB() +func genFilledHandlerStorage(setup []handlerEntry) HandlerStorage { + db, err := NewHandlerStorage() + if err != nil { + panic(err) + } for _, entry := range setup { - if err := db.store(entry.DevAddr, entry); err != nil { + if err := db.Store(entry.DevAddr, entry); err != nil { panic(err) } } diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 7057e2fe6..38eddbcde 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -293,7 +293,13 @@ func genPacketsFromSchedule(s *[]event) { func genNewHandler(t *testing.T, applications map[lorawan.EUI64]application) *Handler { ctx := GetLogger(t, "Handler") - handler, err := NewHandler(newHandlerDB(), ctx) + + db, err := NewHandlerStorage() + if err != nil { + panic(err) + } + + handler := NewHandler(db, ctx) if err != nil { panic(err) } diff --git a/core/components/router.go b/core/components/router.go index 150d64a5d..86df32743 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -16,31 +16,24 @@ const ( ) type Router struct { - Ctx log.Interface db routerStorage // Local storage that maps end-device addresses to broker addresses + ctx log.Interface } // NewRouter constructs a Router and setup its internal structure -func NewRouter(ctx log.Interface) (*Router, error) { - localDB, err := NewRouterStorage(EXPIRY_DELAY) - - if err != nil { - return nil, err - } - +func NewRouter(db routerStorage, ctx log.Interface) *Router { return &Router{ - Ctx: ctx, - db: localDB, - }, nil + db: db, + ctx: ctx, + } } // Register implements the core.Component interface func (r *Router) Register(reg core.Registration, an core.AckNacker) error { - if !r.ok() { - an.Nack() - return ErrNotInitialized + entry := routerEntry{ + Recipients: []core.Recipient{reg.Recipient}, } - if err := r.db.store(reg.DevAddr, reg.Recipient); err != nil { + if err := r.db.Store(reg.DevAddr, entry); err != nil { an.Nack() return err } @@ -54,11 +47,6 @@ func (r *Router) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.A // HandleUp implements the core.Component interface func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { - if !r.ok() { - an.Nack() - return ErrNotInitialized - } - // Lookup for an existing broker devAddr, err := p.DevAddr() if err != nil { @@ -66,21 +54,16 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt return err } - brokers, err := r.db.lookup(devAddr) + entries, err := r.db.Lookup(devAddr) if err != ErrDeviceNotFound && err != ErrEntryExpired { an.Nack() return err } - response, err := upAdapter.Send(p, brokers...) + response, err := upAdapter.Send(p, entries.Recipients...) if err != nil { an.Nack() return err } return an.Ack(response) } - -// ok ensure the router has been initialized by NewRouter() -func (r *Router) ok() bool { - return r != nil && r.db != nil -} diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 8cf24d716..782e4eb10 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -23,18 +23,35 @@ type routerBoltStorage struct { type routerEntry struct { Recipients []core.Recipient - Until time.Time + until time.Time } -func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]routerEntry, error) { +func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { entries, err := lookup(s.DB, []byte("brokers"), devAddr, &routerEntry{}) if err != nil { - return nil, err + return routerEntry{}, err + } + routerEntries := entries.([]routerEntry) + + if len(routerEntries) != 1 { + if err := flush(s.DB, []byte("brokers"), devAddr); err != nil { + return routerEntry{}, err + } + return routerEntry{}, ErrNotFound } - return entries.([]routerEntry), nil + + if s.expiryDelay != 0 && routerEntries[0].until.Before(time.Now()) { + if err := flush(s.DB, []byte("brokers"), devAddr); err != nil { + return routerEntry{}, err + } + return routerEntry{}, ErrEntryExpired + } + + return routerEntries[0], nil } func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { + entry.until = time.Now().Add(s.expiryDelay) return store(s.DB, []byte("brokers"), devAddr, &entry) } @@ -47,7 +64,7 @@ func (entry routerEntry) MarshalBinary() ([]byte, error) { w.Write(rawId) w.Write(rawAddress) } - rawTime, err := entry.Until.MarshalBinary() + rawTime, err := entry.until.MarshalBinary() if err != nil { return nil, err } @@ -71,8 +88,8 @@ func (entry *routerEntry) UnmarshalBinary(data []byte) error { } var err error r.Read(func(data []byte) { - entry.Until = time.Time{} - err = entry.Until.UnmarshalBinary(data) + entry.until = time.Time{} + err = entry.until.UnmarshalBinary(data) }) if err != nil { return err diff --git a/core/components/storage.go b/core/components/storage.go index 4ea7934db..3bb0be340 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -75,3 +75,13 @@ func lookup(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, shape stora } return entries.Interface(), nil } + +func flush(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr) error { + return db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket(bucketName) + if bucket == nil { + return ErrStorageUnreachable + } + return bucket.Delete(devAddr[:]) + }) +} From 48736ea915759ad16b79d65d1ebbf256451060cf Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 19:19:57 +0100 Subject: [PATCH 0497/2266] [in memory storage] Add .db to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index e76b4c742..ded963166 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ _testmain.go *.prof /release + +# Generated databases +*.db From 47a9f22f50f81e8fb838570df70e2d73414fb2fe Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 19:22:57 +0100 Subject: [PATCH 0498/2266] [in memory storage] Use of string instead of []byte for bucket name --- core/components/broker_storage.go | 4 ++-- core/components/handler_storage.go | 4 ++-- core/components/router_storage.go | 8 ++++---- core/components/storage.go | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 3a3a3ea3b..429028884 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -24,7 +24,7 @@ type brokerEntry struct { } func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { - entries, err := lookup(s.DB, []byte("devices"), devAddr, &brokerEntry{}) + entries, err := lookup(s.DB, "devices", devAddr, &brokerEntry{}) if err != nil { return nil, err } @@ -32,7 +32,7 @@ func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error } func (s brokerBoltStorage) Store(devAddr lorawan.DevAddr, entry brokerEntry) error { - return store(s.DB, []byte("devices"), devAddr, &entry) + return store(s.DB, "devices", devAddr, &entry) } func (entry brokerEntry) MarshalBinary() ([]byte, error) { diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 9e5468827..19d2940da 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -46,7 +46,7 @@ func NewHandlerStorage() (HandlerStorage, error) { } func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { - entries, err := lookup(s.DB, []byte("applications"), devAddr, &handlerEntry{}) + entries, err := lookup(s.DB, "applications", devAddr, &handlerEntry{}) if err != nil { return nil, err } @@ -54,7 +54,7 @@ func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, err } func (s handlerBoltStorage) Store(devAddr lorawan.DevAddr, entry handlerEntry) error { - return store(s.DB, []byte("applications"), devAddr, &entry) + return store(s.DB, "applications", devAddr, &entry) } func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartition, error) { diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 782e4eb10..dddad42c7 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -27,21 +27,21 @@ type routerEntry struct { } func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { - entries, err := lookup(s.DB, []byte("brokers"), devAddr, &routerEntry{}) + entries, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) if err != nil { return routerEntry{}, err } routerEntries := entries.([]routerEntry) if len(routerEntries) != 1 { - if err := flush(s.DB, []byte("brokers"), devAddr); err != nil { + if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } return routerEntry{}, ErrNotFound } if s.expiryDelay != 0 && routerEntries[0].until.Before(time.Now()) { - if err := flush(s.DB, []byte("brokers"), devAddr); err != nil { + if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } return routerEntry{}, ErrEntryExpired @@ -52,7 +52,7 @@ func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { entry.until = time.Now().Add(s.expiryDelay) - return store(s.DB, []byte("brokers"), devAddr, &entry) + return store(s.DB, "brokers", devAddr, &entry) } func (entry routerEntry) MarshalBinary() ([]byte, error) { diff --git a/core/components/storage.go b/core/components/storage.go index 3bb0be340..ae4c04387 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -16,14 +16,14 @@ type storageEntry interface { UnmarshalBinary(data []byte) error } -func store(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, entry storageEntry) error { +func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { return err } err = db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket(bucketName) + bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { return ErrStorageUnreachable } @@ -39,10 +39,10 @@ func store(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, entry storag return err } -func lookup(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, shape storageEntry) (interface{}, error) { +func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape storageEntry) (interface{}, error) { var rawEntry []byte err := db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket(bucketName) + bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { return ErrStorageUnreachable } @@ -76,9 +76,9 @@ func lookup(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr, shape stora return entries.Interface(), nil } -func flush(db *bolt.DB, bucketName []byte, devAddr lorawan.DevAddr) error { +func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { return db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket(bucketName) + bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { return ErrStorageUnreachable } From c1c1d663788d6ef5084492e22dcf9616f71a22a7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 19:31:55 +0100 Subject: [PATCH 0499/2266] [in memory storage] Extend type switch in entry read/writer --- core/components/entryReadWriter.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go index ed4e612d9..f17b73748 100644 --- a/core/components/entryReadWriter.go +++ b/core/components/entryReadWriter.go @@ -6,6 +6,9 @@ package components import ( "bytes" "encoding/binary" + "fmt" + + "github.com/brocaar/lorawan" ) type entryReadWriter struct { @@ -21,9 +24,23 @@ func NewEntryReadWriter(buf []byte) *entryReadWriter { } func (w *entryReadWriter) Write(data interface{}) { - raw, ok := data.([]byte) - if !ok { + var raw []byte + switch data.(type) { + case []byte: + raw = data.([]byte) + case lorawan.AES128Key: + data := data.(lorawan.AES128Key) + raw = data[:] + case lorawan.EUI64: + data := data.(lorawan.EUI64) + raw = data[:] + case lorawan.DevAddr: + data := data.(lorawan.DevAddr) + raw = data[:] + case string: raw = []byte(data.(string)) + default: + panic(fmt.Errorf("Unreckognized data type: %v", data)) } w.DirectWrite(uint16(len(raw))) w.DirectWrite(raw) From 656ff6985865bfe5dd60f3aff9769a1b7775c3ae Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 19:41:35 +0100 Subject: [PATCH 0500/2266] [in memory storage] Add bolt db initialization --- core/components/handler_storage.go | 4 ++++ core/components/storage.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 19d2940da..2a1a2edd9 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -42,6 +42,10 @@ func NewHandlerStorage() (HandlerStorage, error) { return nil, err } + if err := initDB(db, "applications"); err != nil { + return nil, err + } + return &handlerBoltStorage{DB: db}, nil } diff --git a/core/components/storage.go b/core/components/storage.go index ae4c04387..ec71baac6 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -16,6 +16,13 @@ type storageEntry interface { UnmarshalBinary(data []byte) error } +func initDB(db *bolt.DB, bucketName string) error { + return db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) + return err + }) +} + func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { From 77d54091618dfd4aa7086fac54890f915b8ebe84 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 19:56:42 +0100 Subject: [PATCH 0501/2266] [in memory storage] Fix entry read/writer initialisation buffer --- core/components/entryReadWriter.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go index f17b73748..9db885dd2 100644 --- a/core/components/entryReadWriter.go +++ b/core/components/entryReadWriter.go @@ -19,7 +19,7 @@ type entryReadWriter struct { func NewEntryReadWriter(buf []byte) *entryReadWriter { return &entryReadWriter{ err: nil, - data: bytes.NewBuffer(buf), + data: new(bytes.Buffer), } } @@ -50,13 +50,17 @@ func (w *entryReadWriter) DirectWrite(data interface{}) { if w.err != nil { return } + in := w.data.Next(w.data.Len()) + binary.Write(w.data, binary.BigEndian, in) w.err = binary.Write(w.data, binary.BigEndian, data) + fmt.Println(w.data.Bytes()) } func (w *entryReadWriter) Read(to func(data []byte)) { if w.err != nil { return } + lenTo := new(uint16) if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil { return From cc651781dbd2f2a6b35cabef8b7ce7fd6c7dca4d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 20:12:10 +0100 Subject: [PATCH 0502/2266] [in memory storage] Add close method to storages --- core/components/broker_storage.go | 5 +++++ core/components/entryReadWriter.go | 1 - core/components/handler_storage.go | 5 +++++ core/components/handler_storage_test.go | 3 +++ core/components/handler_test.go | 4 ++++ core/components/router_storage.go | 5 +++++ 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 429028884..b4fcb3825 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -9,6 +9,7 @@ import ( ) type brokerStorage interface { + Close() error Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) Store(devAddr lorawan.DevAddr, entry brokerEntry) error } @@ -35,6 +36,10 @@ func (s brokerBoltStorage) Store(devAddr lorawan.DevAddr, entry brokerEntry) err return store(s.DB, "devices", devAddr, &entry) } +func (s brokerBoltStorage) Close() error { + return s.DB.Close() +} + func (entry brokerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) w.Write(entry.Id) diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go index 9db885dd2..1fccb36ec 100644 --- a/core/components/entryReadWriter.go +++ b/core/components/entryReadWriter.go @@ -53,7 +53,6 @@ func (w *entryReadWriter) DirectWrite(data interface{}) { in := w.data.Next(w.data.Len()) binary.Write(w.data, binary.BigEndian, in) w.err = binary.Write(w.data, binary.BigEndian, data) - fmt.Println(w.data.Bytes()) } func (w *entryReadWriter) Read(to func(data []byte)) { diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 2a1a2edd9..73ce45aaf 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -12,6 +12,7 @@ import ( ) type HandlerStorage interface { + Close() error Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) Store(devAddr lorawan.DevAddr, entry handlerEntry) error Partition(packet ...core.Packet) ([]handlerPartition, error) @@ -111,6 +112,10 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio return res, nil } +func (s handlerBoltStorage) Close() error { + return s.DB.Close() +} + func (entry handlerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) w.Write(entry.AppEUI) diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 0bae9cace..967418a7a 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -120,6 +120,9 @@ func TestStoragePartition(t *testing.T) { // Check checkErrors(t, test.WantError, err) checkPartitions(t, test.WantPartitions, partitions) + if err := db.Close(); err != nil { + panic(err) + } } } diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 38eddbcde..5dada9831 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -235,6 +235,10 @@ func TestHandleUp(t *testing.T) { checkAcks(t, test.WantNbAck, chans["ack"], "ack") checkAcks(t, test.WantNbNack, chans["nack"], "nack") checkPackets(t, test.WantPackets, chans["packet"]) + + if err := handler.db.Close(); err != nil { + panic(err) + } } } diff --git a/core/components/router_storage.go b/core/components/router_storage.go index dddad42c7..dc1170701 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -12,6 +12,7 @@ import ( ) type routerStorage interface { + Close() error Lookup(devAddr lorawan.DevAddr) (routerEntry, error) Store(devAddr lorawan.DevAddr, entry routerEntry) error } @@ -55,6 +56,10 @@ func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) err return store(s.DB, "brokers", devAddr, &entry) } +func (s routerBoltStorage) Close() error { + return s.DB.Close() +} + func (entry routerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) w.DirectWrite(uint8(len(entry.Recipients))) From bcf0559ab52ffd23d4c46d82f60f7270d169e6f2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 20:34:18 +0100 Subject: [PATCH 0503/2266] [in memory storage] Fix typo in entryReadWriter --- core/components/entryReadWriter.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go index 1fccb36ec..8e160ba94 100644 --- a/core/components/entryReadWriter.go +++ b/core/components/entryReadWriter.go @@ -19,7 +19,7 @@ type entryReadWriter struct { func NewEntryReadWriter(buf []byte) *entryReadWriter { return &entryReadWriter{ err: nil, - data: new(bytes.Buffer), + data: bytes.NewBuffer(buf), } } @@ -51,6 +51,7 @@ func (w *entryReadWriter) DirectWrite(data interface{}) { return } in := w.data.Next(w.data.Len()) + w.data = new(bytes.Buffer) binary.Write(w.data, binary.BigEndian, in) w.err = binary.Write(w.data, binary.BigEndian, data) } From 4662fc851598fe3344139f4bfa07a5b8ae7528b2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 20:43:27 +0100 Subject: [PATCH 0504/2266] [in memory storage] Add reset method to start with a fresh database when needed --- core/components/broker_storage.go | 5 +++++ core/components/handler_storage.go | 5 +++++ core/components/handler_storage_test.go | 4 ++++ core/components/handler_test.go | 4 ++++ core/components/router_storage.go | 5 +++++ core/components/storage.go | 10 ++++++++++ 6 files changed, 33 insertions(+) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index b4fcb3825..ffd435138 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -11,6 +11,7 @@ import ( type brokerStorage interface { Close() error Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) + Reset() error Store(devAddr lorawan.DevAddr, entry brokerEntry) error } @@ -40,6 +41,10 @@ func (s brokerBoltStorage) Close() error { return s.DB.Close() } +func (s brokerBoltStorage) Reset() error { + return resetDB(s.DB, "devices") +} + func (entry brokerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) w.Write(entry.Id) diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 73ce45aaf..e8d0da2d9 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -14,6 +14,7 @@ import ( type HandlerStorage interface { Close() error Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) + Reset() error Store(devAddr lorawan.DevAddr, entry handlerEntry) error Partition(packet ...core.Packet) ([]handlerPartition, error) } @@ -116,6 +117,10 @@ func (s handlerBoltStorage) Close() error { return s.DB.Close() } +func (s handlerBoltStorage) Reset() error { + return resetDB(s.DB, "applications") +} + func (entry handlerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) w.Write(entry.AppEUI) diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 967418a7a..992556e29 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -139,6 +139,10 @@ func genFilledHandlerStorage(setup []handlerEntry) HandlerStorage { panic(err) } + if err := db.Reset(); err != nil { + panic(err) + } + for _, entry := range setup { if err := db.Store(entry.DevAddr, entry); err != nil { panic(err) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 5dada9831..800f7685d 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -303,6 +303,10 @@ func genNewHandler(t *testing.T, applications map[lorawan.EUI64]application) *Ha panic(err) } + if err := db.Reset(); err != nil { + panic(err) + } + handler := NewHandler(db, ctx) if err != nil { panic(err) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index dc1170701..d2dbd167b 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -14,6 +14,7 @@ import ( type routerStorage interface { Close() error Lookup(devAddr lorawan.DevAddr) (routerEntry, error) + Reset() error Store(devAddr lorawan.DevAddr, entry routerEntry) error } @@ -60,6 +61,10 @@ func (s routerBoltStorage) Close() error { return s.DB.Close() } +func (s routerBoltStorage) Reset() error { + return resetDB(s.DB, "brokers") +} + func (entry routerEntry) MarshalBinary() ([]byte, error) { w := NewEntryReadWriter(nil) w.DirectWrite(uint8(len(entry.Recipients))) diff --git a/core/components/storage.go b/core/components/storage.go index ec71baac6..90c0e87f3 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -92,3 +92,13 @@ func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { return bucket.Delete(devAddr[:]) }) } + +func resetDB(db *bolt.DB, bucketName string) error { + return db.Update(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket([]byte(bucketName)); err != nil { + return err + } + _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) + return err + }) +} From 70a73c19b3337be2f691d02a9324f43966d28a9d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 20:50:00 +0100 Subject: [PATCH 0505/2266] [in memory storage] Add storage constructors for each component --- core/components/broker.go | 4 ++-- core/components/broker_storage.go | 17 ++++++++++++++++- core/components/router.go | 4 ++-- core/components/router_storage.go | 15 ++++++++++++++- 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/core/components/broker.go b/core/components/broker.go index af8d73a66..19125d18c 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -13,10 +13,10 @@ import ( type Broker struct { ctx log.Interface - db brokerStorage + db BrokerStorage } -func NewBroker(db brokerStorage, ctx log.Interface) *Broker { +func NewBroker(db BrokerStorage, ctx log.Interface) *Broker { return &Broker{ ctx: ctx, db: db, diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index ffd435138..a4364ae48 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -4,11 +4,13 @@ package components import ( + "time" + "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) -type brokerStorage interface { +type BrokerStorage interface { Close() error Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) Reset() error @@ -25,6 +27,19 @@ type brokerEntry struct { Url string } +func NewBrokerStorage() (BrokerStorage, error) { + db, err := bolt.Open("broker_storage.db", 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return nil, err + } + + if err := initDB(db, "devices"); err != nil { + return nil, err + } + + return &brokerBoltStorage{DB: db}, nil +} + func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { entries, err := lookup(s.DB, "devices", devAddr, &brokerEntry{}) if err != nil { diff --git a/core/components/router.go b/core/components/router.go index 86df32743..c825b8f1d 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -16,12 +16,12 @@ const ( ) type Router struct { - db routerStorage // Local storage that maps end-device addresses to broker addresses + db RouterStorage // Local storage that maps end-device addresses to broker addresses ctx log.Interface } // NewRouter constructs a Router and setup its internal structure -func NewRouter(db routerStorage, ctx log.Interface) *Router { +func NewRouter(db RouterStorage, ctx log.Interface) *Router { return &Router{ db: db, ctx: ctx, diff --git a/core/components/router_storage.go b/core/components/router_storage.go index d2dbd167b..9192c036d 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -11,7 +11,7 @@ import ( "github.com/brocaar/lorawan" ) -type routerStorage interface { +type RouterStorage interface { Close() error Lookup(devAddr lorawan.DevAddr) (routerEntry, error) Reset() error @@ -28,6 +28,19 @@ type routerEntry struct { until time.Time } +func NewRouterStorage() (RouterStorage, error) { + db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return nil, err + } + + if err := initDB(db, "brokers"); err != nil { + return nil, err + } + + return &routerBoltStorage{DB: db}, nil +} + func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { entries, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) if err != nil { From bc5c1f48e34128f6f5808ce642043de9def7caa6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 20:51:16 +0100 Subject: [PATCH 0506/2266] [in memory storage] Update integration with new component constructors --- integration/broker/main.go | 6 ++++-- integration/router/main.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/integration/broker/main.go b/integration/broker/main.go index 3d75acfba..b773b4f64 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -43,11 +43,13 @@ func main() { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } - broker, err := components.NewBroker(ctx.WithField("tag", "Broker")) + db, err := components.NewBrokerStorage() if err != nil { - ctx.WithError(err).Fatal("Could not start Broker") + ctx.WithError(err).Fatal("Could not create a local storage") } + broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) + // Bring the service to life // Listen to uplink diff --git a/integration/router/main.go b/integration/router/main.go index e48665f2c..30402546d 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -46,11 +46,13 @@ func main() { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - router, err := components.NewRouter(ctx.WithField("tag", "Router")) + db, err := components.NewRouterStorage() if err != nil { - ctx.WithError(err).Fatal("Could not start Router") + ctx.WithError(err).Fatal("Could not create a local storage") } + router := components.NewRouter(db, ctx.WithField("tag", "Router")) + // Bring the service to life // Listen uplink From e8ac89afaa35f558423aebef8acebc85cd2f2d82 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 1 Feb 2016 21:10:14 +0100 Subject: [PATCH 0507/2266] Also import tests-only dependencies --- .drone.yml | 3 ++- .travis.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 1fa754c08..0b3dfda0b 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,7 +2,8 @@ build: test: image: golang commands: - - go get $(comm -23 <(sort <(go list -f '{{join .Deps "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) + - go get $(comm -23 <(sort <(go list -f '{{join .Imports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) + - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... diff --git a/.travis.yml b/.travis.yml index 18f1194a7..499210abe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,8 @@ go: - 1.5 install: - - go get $(comm -23 <(sort <(go list -f '{{join .Deps "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) + - go get $(comm -23 <(sort <(go list -f '{{join .Imports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) + - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) script: - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' From 39c2b1117bf5ec1b7731169ae7c4a42433c4e39f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 11:02:49 +0100 Subject: [PATCH 0508/2266] [doc] Enhance documentation of core types and interfaces --- core/core.go | 61 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 11 deletions(-) diff --git a/core/core.go b/core/core.go index eb74eb6fc..3e01e1b3f 100644 --- a/core/core.go +++ b/core/core.go @@ -1,3 +1,6 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package core import ( @@ -7,13 +10,19 @@ import ( ) type Packet struct { - Metadata Metadata - Payload lorawan.PHYPayload + Metadata Metadata // Metadata associated to packet. That object may change over requests + Payload lorawan.PHYPayload // The actual lorawan physical payload } type Recipient struct { - Address interface{} - Id interface{} + Address interface{} // The address of the recipient. The type depends on the context + Id interface{} // An optional ID for the recipient. The type depends on the context +} + +type Registration struct { + DevAddr lorawan.DevAddr // The device address which takes part in the registration + Recipient Recipient // The registration emitter to which it is bound + Options interface{} // Options that vary from different type of registration } type Metadata struct { @@ -35,30 +44,60 @@ type Metadata struct { Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) - Group []Metadata `json:"metadata,omitempty"` // Gather metadata of several packet into one metadata structure + Group []Metadata `json:"metadata,omitempty"` // Gather metadata of several packets into one metadata structure } +// AckNacker are mainly created by adapters as explicit callbacks for a given incoming request. +// +// The AckNacker encapsulates the logic which allows later component to answer to a recipient +// without even knowing about the protocol being used. This is only possible because all messages +// transmitted between component are relatively isomorph (to what one calls a Packet). type AckNacker interface { + // Ack acknowledges and terminates a connection by sending 0, 1 or several packets as an answer + // (depending on the component). + // + // Incidentally, that acknowledgement would serve as a downlink response for class A devices. Ack(p ...Packet) error + + // Nack rejects and terminates a connection. So far, there is no way to give more information + // about the reason that led to a rejection. Nack() error } type Component interface { + // Register explicitely requires a component to create a new entry in its registry which links a + // recipient to a device. Register(reg Registration, an AckNacker) error + + // HandleUp informs the component of an incoming uplink request. + // + // The component is thereby in charge of calling the given AckNacker accordingly to the + // processing. HandleUp(p Packet, an AckNacker, upAdapter Adapter) error + + // HandleDown informs the component of a spontaneous downlink request. + // + // This should be mistaken with a typical downlink message as defined for Class A devices. + // HandleDown should handle downlink request made without any uplink context (which basically + // means we won't probably use it before a while ~> Class B and Class C, or probably only for + // handlers). HandleDown(p Packet, an AckNacker, downAdapter Adapter) error } type Adapter interface { + // Send forwards a given packet to one or more recipients. It returns only when the request has + // been processed by the recipient and normally respond with a packet coming from the recipient. Send(p Packet, r ...Recipient) (Packet, error) + + // Next pulls a new packet received by the adapter. It blocks until a new packet is received. + // + // The adapter is in charge of creating a new AckNacker to reply to the recipient such that the + // communication and the request are still transparent for the component handling it. Next() (Packet, AckNacker, error) - NextRegistration() (Registration, AckNacker, error) -} -type Registration struct { - DevAddr lorawan.DevAddr - Recipient Recipient - Options interface{} + // NextRegistration pulls a new registration request received by the adapter. It blocks until a + // new registration demand is received. It follows a process similar to Next() + NextRegistration() (Registration, AckNacker, error) } type Router Component From e945e02c4e82f1bdfc9ebedd303189afc14eafbd Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 11:28:02 +0100 Subject: [PATCH 0509/2266] [doc] Add package documentation for core --- core/doc.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 core/doc.go diff --git a/core/doc.go b/core/doc.go new file mode 100644 index 000000000..ccb98d9d3 --- /dev/null +++ b/core/doc.go @@ -0,0 +1,34 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package core contains the core library of The Things Network. +// +// This library can be used to built an entire network or only a small part of it. It is mainly +// divided in three parts: +// +// Packet manipulation +// +// The core package itself defines packets as we see them in the network as well as methods to +// serialize, convert and represent them. +// +// Because packets are likely to change over requests, this package centralizes all definitions and +// information related to them and share by the whole network. Each component may take and use only +// what it needs to operate over packets. +// +// +// Adapters +// +// The subfolder adapters hold all protocol adapters one could use to make components communicate. +// Each adapter implement its own protocol based on a given transport or application layer such as +// UDP, HTTP, TCP, CoAP or any fantasy needed. +// +// +// Components +// +// In the subfolder components you may find implementation of core logic of the network. The +// communication process has been abstracted by the adapters in such a way that all components only +// care about the business logic and the packet management. +// +// Components are split in 4 categories: router, broker, handler and network controller. Refer to +// the related documentation to find more information. +package core From 21fbd51ca50f924dd5b703e21f55122ce7bf6ed9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 11:28:19 +0100 Subject: [PATCH 0510/2266] [doc] Fix small typo in simulator doc --- simulators/gateway/doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simulators/gateway/doc.go b/simulators/gateway/doc.go index 6373fe3ae..8b0cf24f9 100644 --- a/simulators/gateway/doc.go +++ b/simulators/gateway/doc.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// package gateway offers a dummy representation of a gateway. +// Package gateway offers a dummy representation of a gateway. // // The package can be used to create a dummy gateway. // Its former use is to provide a handy simulator for further testing of the whole network chain. From b0095eea9d94c85cdb743e0673c355dffa5705a4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 11:59:00 +0100 Subject: [PATCH 0511/2266] [doc] Enhance doc of metadata --- core/metadata.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/metadata.go b/core/metadata.go index b9080a0e8..55fb07f55 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -12,6 +12,9 @@ import ( "github.com/TheThingsNetwork/ttn/utils/pointer" ) +// metadata allows us to inherit Metadata in metadataProxy but only by extending the exported +// attributes of Metadata such that, they are still parsed by the json.Marshaller / +// json.Unmarshaller though we do not end up with a recursive hellish error. type metadata Metadata // MarshalJSON implements the json.Marshal interface @@ -19,6 +22,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { var d *semtech.Datrparser var t *semtech.Timeparser + // Handle datr which can be either an uint or string depending of the modulation if m.Datr != nil { d = new(semtech.Datrparser) if m.Modu != nil && *m.Modu == "FSK" { @@ -28,6 +32,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { } } + // Time :'( ... By default, we mashall them as RFC3339Nano and unmarshall them the best we can. if m.Time != nil { t = new(semtech.Timeparser) *t = semtech.Timeparser{Layout: time.RFC3339Nano, Value: m.Time} @@ -62,7 +67,7 @@ func (m *Metadata) UnmarshalJSON(raw []byte) error { return nil } -// String implement the io.Stringer interface +// String implements the io.Stringer interface func (m Metadata) String() string { return pointer.DumpPStruct(m, false) } From eb44b5976dfb8cbac4721793ddbc3f6ff7da19ee Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 12:12:18 +0100 Subject: [PATCH 0512/2266] [doc] Enhance packet comments --- core/packet.go | 34 +++++++++++++++++++++++++++------- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/core/packet.go b/core/packet.go index c98f4d5ab..4e5060082 100644 --- a/core/packet.go +++ b/core/packet.go @@ -31,7 +31,7 @@ func (p Packet) DevAddr() (lorawan.DevAddr, error) { return macpayload.FHDR.DevAddr, nil } -// FCnt returns the frame counter of the given packet +// FCnt returns the frame counter of the given packet if any func (p Packet) Fcnt() (uint32, error) { if p.Payload.MACPayload == nil { return 0, fmt.Errorf("MACPayload should not be empty") @@ -45,7 +45,7 @@ func (p Packet) Fcnt() (uint32, error) { return macpayload.FHDR.FCnt, nil } -// String returns a string representation of the packet +// String returns a string representation of the packet. It implements the io.Stringer interface func (p Packet) String() string { str := "Packet {" str += fmt.Sprintf("\n\t%s}", p.Metadata.String()) @@ -56,11 +56,13 @@ func (p Packet) String() string { // ConvertRXPK create a core.Packet from a semtech.RXPK. It's an handy way to both decode the // frame payload and retrieve associated metadata from that packet func ConvertRXPK(p semtech.RXPK) (Packet, error) { + // First, we have to get the physical payload which is encoded in the Data field packet := Packet{} if p.Data == nil { return packet, ErrImpossibleConversion } + // RXPK Data are base64 encoded, yet without the trailing "==" if any..... encoded := *p.Data switch len(encoded) % 4 { case 2: @@ -79,6 +81,8 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { return packet, err } + // Then, we interpret every other known field as a metadata and store them into an appropriate + // metadata object. metadata := Metadata{} rxpkValue := reflect.ValueOf(p) rxpkStruct := rxpkValue.Type() @@ -90,19 +94,23 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { } } + // At the end, our converted packet hold the same metadata than the RXPK packet but the Data + // which as been completely transformed into a lorawan Physical Payload. return Packet{Metadata: metadata, Payload: payload}, nil } // ConvertToTXPK converts a core Packet to a semtech TXPK packet using compatible metadata. func ConvertToTXPK(p Packet) (semtech.TXPK, error) { + // Step 1, convert the physical payload to a base64 string (without the padding) raw, err := p.Payload.MarshalBinary() if err != nil { return semtech.TXPK{}, ErrImpossibleConversion } data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - txpk := semtech.TXPK{Data: pointer.String(data)} + // Step 2, copy every compatible metadata from the packet to the TXPK packet. + // We are possibly loosing information here. metadataValue := reflect.ValueOf(p.Metadata) metadataStruct := metadataValue.Type() txpkStruct := reflect.ValueOf(&txpk).Elem() @@ -135,32 +143,40 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { if p == nil { return ErrImpossibleConversion } + + // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink + // packet. Thus, we'll assume it's an uplink packet (because that's the case most of the time) + // and check whether or not the unmarshalling process was okay. var proxy struct { Payload string `json:"payload"` Metadata Metadata } + err := json.Unmarshal(raw, &proxy) if err != nil { return err } + rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) if err != nil { return err } - // Try first to unmarshal as an uplink payload - payload := lorawan.NewPHYPayload(true) + payload := lorawan.NewPHYPayload(true) // true -> uplink if err := payload.UnmarshalBinary(rawPayload); err != nil { return err } + // Now, we check the nature of the decoded payload switch payload.MHDR.MType.String() { case "JoinAccept": fallthrough case "UnconfirmedDataDown": fallthrough case "ConfirmedDataDown": - payload = lorawan.NewPHYPayload(false) + // JoinAccept, UnconfirmedDataDown and ConfirmedDataDown are all downlink messages. + // We thus have to unmarshall properly + payload = lorawan.NewPHYPayload(false) // false -> downlink if err := payload.UnmarshalBinary(rawPayload); err != nil { return err } @@ -169,12 +185,16 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { case "UnconfirmedDataUp": fallthrough case "ConfirmedDataUp": - // Nothing, we handle them by default + // JoinRequest, UnconfirmedDataUp and ConfirmedDataUp are all uplink messages. + // There's nothing to do, we've already handled them. case "Proprietary": + // Proprietary can be either downlink or uplink. Right now, we do not have any message of + // that type and thus, we just don't know how to handle them. Let's throw an error. return fmt.Errorf("Unsupported MType Proprietary") } + // Packet = Payload + Metadata p.Payload = payload p.Metadata = proxy.Metadata return nil From 782f75a14da20a75cbdd98aa2f5c1800ebb9b62d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 13:35:07 +0100 Subject: [PATCH 0513/2266] [doc] Enhance documentation and comments of semtech adapter --- core/adapters/semtech/adapter.go | 24 +++++++++++++++------- core/adapters/semtech/doc.go | 12 +++++++++++ core/adapters/semtech/semtech_acknacker.go | 9 ++++++-- 3 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 core/adapters/semtech/doc.go diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 26ce66e39..7ca1ef57b 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -12,21 +12,26 @@ import ( "github.com/apex/log" ) +// Adapter represents a semtech adapter which sends and receives packet via UDP in respect of the +// semtech forwarder protocol type Adapter struct { - ctx log.Interface - conn chan udpMsg - next chan rxpkMsg + ctx log.Interface // Just a logger + conn chan udpMsg // Channel used to manage response transmissions made by multiple goroutines + next chan rxpkMsg // Incoming valid RXPK packets are pushed to this channel } +// udpMsg type materializes response messages transmitted towards existing recipients (commonly, +// gateways). type udpMsg struct { conn *net.UDPConn // Provide if you intent to change the current adapter connection addr *net.UDPAddr // The target recipient address raw []byte // The raw byte sequence that has to be sent } +// rxpkMsg type materializes valid uplink messages coming from a given recipient type rxpkMsg struct { - rxpk semtech.RXPK - recipient core.Recipient + rxpk semtech.RXPK // The actual RXPK message + recipient core.Recipient // The address and id of the source emitter } var ErrInvalidPort error = fmt.Errorf("Invalid port supplied. The connection might be already taken") @@ -34,7 +39,7 @@ var ErrNotInitialized error = fmt.Errorf("Illegal call on non-initialized adapte var ErrNotSupported error = fmt.Errorf("Unsupported operation") var ErrInvalidPacket error = fmt.Errorf("Invalid packet supplied") -// New constructs and allocates a new udp_sender adapter +// NewAdapter constructs and allocates a new semtech adapter func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a := Adapter{ ctx: ctx, @@ -63,7 +68,7 @@ func (a *Adapter) ok() bool { return a != nil && a.conn != nil && a.next != nil } -// Send implements the core.Adapter interface +// Send implements the core.Adapter interface. Not implemented for the semtech adapter. func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { return core.Packet{}, ErrNotSupported } @@ -151,6 +156,11 @@ func (a *Adapter) listen(conn *net.UDPConn) { } // monitorConnection manages udpConnection of the adapter and send message through that connection +// +// That function executes into a single goroutine and is the only one allowed to write UDP messages. +// Doing this makes sure that only 1 goroutine is interacting with the connection. It thereby allows +// the connection to be replaced at any moment (in case of failure for instance) without disturbing +// the ongoing process. func (a *Adapter) monitorConnection() { var udpConn *net.UDPConn for msg := range a.conn { diff --git a/core/adapters/semtech/doc.go b/core/adapters/semtech/doc.go new file mode 100644 index 000000000..a0fda9875 --- /dev/null +++ b/core/adapters/semtech/doc.go @@ -0,0 +1,12 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package adapters.semtech provides an Adapter which implements the semtech forwarder protocol. +// +// The protocol could be found in this document: +// https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/semtech.pdf +// +// This protocol and to some extend the adapter does not allow a spontaneous downlink transmission +// to be initiated. Response packets are only transmitted if are a response to uplink packets. The +// AckNacker handles that logic. +package semtech diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index 86797d0cd..f3ccc8f52 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -11,16 +11,19 @@ import ( "github.com/TheThingsNetwork/ttn/semtech" ) +// semtechAckNacker represents an AckNacker for a semtech request type semtechAckNacker struct { - conn chan udpMsg - recipient core.Recipient + conn chan udpMsg // The adapter downlink connection channel + recipient core.Recipient // The recipient to reach } +// Ack implements the core.Adapter interface func (an semtechAckNacker) Ack(p ...core.Packet) error { if len(p) == 0 { return nil } + // For the downlink, we have to send a PULL_RESP packet which hold a TXPK txpk, err := core.ConvertToTXPK(p[0]) if err != nil { return err @@ -40,6 +43,8 @@ func (an semtechAckNacker) Ack(p ...core.Packet) error { return nil } +// Ack implements the core.Adapter interface func (an semtechAckNacker) Nack() error { + // There's no notion of nack in the semtech protocol. You either reply something or you don't. return nil } From 554c85b5d26e19c5388dba292d502e1f76e0f7e8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 13:55:57 +0100 Subject: [PATCH 0514/2266] [doc] Create a 'documents' folder to hold reports, presentations and ressources --- .../protocols}/protocols.md | 4 +++- documents/protocols/semtech.pdf | Bin 0 -> 854079 bytes 2 files changed, 3 insertions(+), 1 deletion(-) rename {core/adapters => documents/protocols}/protocols.md (96%) create mode 100644 documents/protocols/semtech.pdf diff --git a/core/adapters/protocols.md b/documents/protocols/protocols.md similarity index 96% rename from core/adapters/protocols.md rename to documents/protocols/protocols.md index 238cdb82a..762bcbc98 100644 --- a/core/adapters/protocols.md +++ b/documents/protocols/protocols.md @@ -1,9 +1,11 @@ semtech ~ udp ============= -Have a look at [this document](http://iot.semtech.com/resources/Server_Release_2.1.1/files.xml?action=download&file=LoRa%20gateway%20to%20network%20server%20interface%20definition.pdf) + +Have a look at [this document](https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/semtech.pdf) basic ~ http ============ + The basic http protocol relies seemingly on `http`. An adapter which implements this protocol should provide at least one end-point: diff --git a/documents/protocols/semtech.pdf b/documents/protocols/semtech.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1295523a4edb9338aee928f5f95b829582eacb86 GIT binary patch literal 854079 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Ð#%z3DUv3A*LGg7 zFM-N^X3nV>OYVa4j_jfqn O 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%0A6?8qE5-5D&|C1897^AJ?VJ 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 z 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∾F?@N!KLzn%XkAiPUA!8feA{(BtK>quo;Ge9grP>{{*gYxE$!3 z^2ZAPR%Yz0x-V|M^ucHYr)i-T~%B!;|vywPs}#St2VDg(3vm_sJGF`m8pG)`&WR)z5~u}m2IF``s| z2j-DQqN>3gUNxWZ&(*PmjH9)Kl1^sa(Q|(7sNx#_z^@=uUHVk|L_-~sOo?AGbIuy6 z#A5uAujFZz=z5tNJ*W!AtLK$cf6C>8AKQxrr}^JgRXh@vEXY7Nx$1eM-q)E}=df z+-#8?{HauLJIPo??;75L=(f)V13ly^d7wLdfiQ{(^@3iS0Mm@2G-0}xeuXqLxahc9 zs@oo;Gz|(l1Ua~T)I`&kg&_ln1#C1{fMNGmpPn5JMkYqZVQmOXKwLO+5wYAroI>m^ zMyOJskqAsa79+Tq5nh@oEpdJC8?*!IJ!%^M0Zgpo`|2JX&+4}ul~i8=S^3eSV+MXv zzT{`ly@q$p8^#xnzQdsIC5|<#-au@pw%)Hc>3P%J;y&3nj|MO<4D5@q9QshL1`NCb z{9^qDz2@d#B5CPZ^2LRbl-7VyCPq%0``=Oe5|L5DVT60X)w*Cvg`6pr`|^VewNyo(j6wl6AgbvzOvU2y(JgnzK_iMcokY_l~5yKd2HdVJthSz30@jR~`E0(`b- zH8u@e+F#wqNK3hT=;9FB8$ff~K_HQ>i(j+{AXT^;%iL*P5VdlTh+bA-5Xi+M$45K{ z2|$C3bAaH}fy*f*vvc$ijTViN4@m=e(JaMX%SVik3o1lX z-9F+rcTf|3_ti}3nC$ukO`o~-?K+S+z#=A_LEXp)r-Rbbv2HE>(>!}slOO$^1^xXU zB>n!wQ_anGN91xyVk|cy$*1Lg_{?eqpW9sGf$tVcu-p)0GnpKX7oRh0!eigj_v4uR$n|}J7)M-eYwCHB4tq#ncGN{e& zY~AyFPqvP1bE7KZT|PcP_aJmmwjyNC9YfEvk1R(>71HE)H1~dH4(#&{{wBh{@{%C8 zQaL=?^%y_jrkp!Nxw6feXF1@1z+7LO6`??Ed*uxK@cK|&YeG!`=d7y@aSwc7CC4aj zCXP`|s#IF4$WZCu8 z&Tn`G_^)q4_B*lzm^FA#?6BqR49eND#?Pbgk+v58D~soCtAiHK9x_&$8U;%IW@@r| z-7rE0Mxe*qC}A!{@|LF!ZVJ*M{Hx<15N{ggN&)H0mL-1@mrPvr&l=Bo?c~nZS9JXj zFyv99IOHvfORrUw9oi*K!`SV@uB%{Fb(=qhK7&%|+Z_beeFWRDuE36aN@1Weh)x6- z!4`K#;Pypm4;+2(wXeJ{UK8jS*{4%>M{kT@n7mwc(NMnAo_pM+T3gyX$SL!aY2n=nQ%t?;Pu1!WEHv#vIH~DR$zQiID zZ+6e-HQOUBP-eLfC{agLDEjhVQkp$yQW^{Ws*sTL+>8JiS#? z{PPIc@wa5j6Ld+ePUJxybbp%{^{!}eq6+LU#bx%t@MOt#;Rb$f*tbdO^XrLk$1}7`lqbp!`r5f~5m6U-&%Y+^L+VQieGhFS zybwJTT?=alwddXu>O<=@=_BhC>+|SK3v~`{3?&T(3#IGh>C@^{6G6=v(!zV9xia5M z04~SS@@E;iEQ7co!q-cP4VX;$b`&@6E0wMLKR>pNwu1iL`l0yk`k8`GehUWWhvUKI zz`}s!!Eqz7qnN^9!d^mI!d*f=f?LB{LR-RWCY?fALT+ZP;jAHHVE>7qiuW6q?{18{l=Z!o>WXy(ir>v&ALz-y>KgoZYg8cPo4$c%)U(zH&@6!2%n^%-$4GL z*nP)?KE?MJ*T;^2`eqe7I{qZkfcg^0mR}5J^2e(gI@xesgX0@QpeL=Lirp@{0H#L+ zdxbX$UtmrweeF8C@68rB$0#d9AoDtCBZm&=U0&nsHvg8-?|}1^w~jG(V~=q20Mr`z z=wE@}pi`8Vrrk0OxK(|P^SLPtJy@9?V0gjexuD=%Hgo(SaCSL2l%e{q=7nOPL(+Kf z1#k7pt~;yKGGjUbh}LcJW`qq8ul(ad zH^N%p?~85mILIOH^kH3}o#CJlyEv6=nWXT|{%&>misj`J7EseeKg{lnc8qP=jo0=aSF8CU!?N3I({Xs&3FRWu)85y50LSCo zQZ(Xj-?QbFZ1oRTY{~T3d;~sshA9GmkjeUom^Z*libM1 zsB7G0pI=>XtL^z0Lq58I4;Hs`z#>mZAHQERF@M(p!+cNm{MiqN9?!4e58Axj6>#VA zrs4A3c_#WG+}~T^`;!N+Lg@%Kt-G-s`-<+B^bJ}k_;8EY4VU>9?1@+N58q(63IDzZ zXOV%(wYOX1Xvpc-M0Lzy-u$YU7T#gl|0T_g5vG3zvS> z@Gq~FuR%LsTr`6YH(Yo!Is?ct*IncYm#fSIafX7vQuuRCn>(QHOAiIDDK&C;QK)9+ zR8P?EgiGo9|J-84h=NkF?92$di4#Ki8S%Y6_>s3EK=b#4BEp{kcq9hx+{Jl8N6;^7 zJXXjAXr_pm{Gq4hWiJs{!VtwsYC!16nnpv`nR@HvXVY2Fn@hOxs8q&c0aXN8voI!a}9@ zegs0D1$KU0XkI=ZlUmMq%3^cVF3EeN;Tu*T1^WRwnSj3dD#xf%NIzm)7lB*H7%5q* zK=o`@Us4lEO=mErHXrA>SFf)gNS> z+JW)bt}tn3qvKsSFius=3}p@d&8YU$*ZdaQ+?nYQ`%%N+JEHO07x~uS`|+m62B6QG z$DhH1RdEQ_uYiBeySww}I8C-%JQlL}Ese;Fl%PFq8id&8riFM1ex;%9VU^1rtwZ3! zm$QrOA*U}fOoD)hk-f+bJ-Q@;w;-=I5XjrNoMLv4i6U7aFxi==c{HWttSl5fdQXEh_gUgQjtjS0 zh)LdetE&RuQI!{s6GxOa8yOEDWpxVj?>Gk{lr+dN-)PIvQNT8c?ohu|7|K-48}OdR z)M8{_5U~}+9x}tfx}yR11_tF8WY^-uvpLI1>lL*9aa^$+EfP0&YLh*G*17dS{W2jd zu0>O%D2Deoz(S!61bksdxJS~&L}`*si#-~Hr%O*P?|KreFcuXtu!eqT2Lhz3$8)2%%dL-R;DIbp>Ioa|y8ve>C93u0a9vpiw z&83k8o0cz#R1nU2rkVyT)~nxzG(Okd1Fo*Ut)}g=z9;DJP8+w6TRi%@V@v06qGMqP zD!kc07E4Pu^BG+sw|57%64>iQJ4!jGxuaDQgD!Re|0-9iWIT!zvk%Jo(`V2{T+zCW z&(p$8#VuRi-c%No}lb0c1uC($k9C=oogk%Ydp2>a)66`o_3;K+d!yG z+mOM9OE1^aUn0D664sm0Ejyr7KKLB#P0GN*e^R);8Vg+&h2Z9GV7!0L;={Ksqhe%k zxaE{ert7TveO1FxnitA?Uy~UW6-HTG^u6{Wd#!pb*dfM>LBp$^ArY<53gA|Uu~w$L z&m_knJ*PIy9%+0z?nFLFZD1fh;Wd+X*kHz{>(xQA_*<2do^^h-@I^ziBtEIBONDl3 zcD+)fA%gkCgWuCl(EFic>jZ_I-jJxH;Ar@PnP=Ha4??g(fGA^$+@FyxNai?Ve}`SO z67yvodhs(?;uD1{e`KFV(a0zdR}{Qm&;B?pGX4nG4H8r6QT}W1+4|L5wmLUG9_HKb zeVcf|wK%?px1PyucM&c7_~gPkr% zJ-z32UzqW`>0gsWbj6F%aL_tMd*assFIPLUzNSO|7rmDS@s;ZAm5vAQ`rAVnWdOQP zrUG+!e&<}8IQ}V5J5f#s`~E_sYDKxkeh=Ck7_FTj0$1P8ckJf%KdsYU45CSx5lv6} zUBpsJW;|zdnLV<{Qby#(C98FrPw>vWg%ZZH?90e8Xf1c!)IEruWWUX~=fc&*ZKdct zNa-yL6pVZN>W7q5t(Ko@CU<1Qqpm`?n1=!^92c4mkJrTUH=Gye!1`&IixOb`@lBs` zWjOJ=&voiuy344^m95S#^bf%C({;9U!`L<;5b!#jn@Pybr{jfZO%M8-BiNJh(>QsR z$KS4Ye97jmbef+AB-iA%3l5jL8Fr!dGJZQBU$r{zw;Wxf{pMuu5b*xZh<#1fuu{pZ zLg&}0p*WY8rX`6t=-_RTJ^jORc1RszycRKnbkIkooOXz_TmiKw&wnb*A{O^R!|A0w zvIu@g{6aB6H5UObu?;5D9JYh^SU)WWJ})NH(6xLfF*F*6SI{8+X!FNib$XYN<8&kT z;$?-##||y?t4fomMO9h3+QDsw#&o@@?EVXWgSTs-w1vjWMnk8Kd4%oFJp1H{N3LiJ z@>_$sgo~i&d;@8e=uSGXD($5vM_ww%5$2}9=M`{ybauDCL=4VJ!^nX!_ljjBzB6>` ziZ*`g(S&wV!ka)0E`eU|IFg-P0&Sg-=CMk&*UiqvH3%ivWq7_OUz6He!Z2oq_VmX9HsvGaQJW!mdJ&#U}u>r>beZoAj} zv6ZQ!yzx7Bz2ZefX;L2g`dmaGSJ^C5{6kG^G6*3Nr3wY{{(0@lF*N0`U{6sTc`>sNeNVLR{uFI`Tn5q9{%M zdSQH_eOm#L9mzsXvykH`YqF_WPu~D4Y7GFMc(&%j9*KH3rSCtT&>{U5eK1BxsOUzV zf&p>Jx*Q`@hbGLOAoDet%B0#%X+Bq61l?c!VMGT_a>RxQUOF_X?3LxPOWVuKBgy$ejGLtkOu#bVAep zDTY?r56cpDj^<7;Q%6#BlzydSVA*s8L%q&gK2n*WS)!~v!-vHq36x%-DLc|#P+cUM7hz9<@$+k1o)rxjwKtl@;C zx>hxGRw6?o53Y@`bMQ$wp}Jsv5=pW^8?BY?Z*+gfl2wH z#68>T!E!O&F%x2nGTKf-&S1Z$dcOsC(0VGN$Cy$p_8|@ z@?^~Q((#VNV=10S>V{j~XOR9GX*A}^-ikUblVghmVW>CEShH5tN-AzoHJ2cs!v!*s zWUEku3%!_SJhqB^DtSYwL>SaqW9w@Inq(Rp9aJE)24tS4j6|A9=jO)Yk7VJatHBCq z({)no_Kry(pRUW-b6fssX%~v{1y1mbxlHp@ijTMYWoE>|@HQH#2ilh0dDFUf~b48qT$zP6$AS2(1}yE7975 z#W@wlPe-b{C6{hqEt+ZU4%qEepowYA5oYB4jjk?%TdZmye z654)70=qHO>Y>9_DZ)Y5!^B!x+$3Xn1o!nMSSQUs2hUc$X~hJ6fkSn^QBd_ zs?BAoLvlJ5`<%_GTxFTZRi4Zv7cxxdeZA$qBfow#HT7=JfB30gl;xKqS>3bc< z7G|p*or7#stKMuee}4jJ&AFp3#(XA;{ig)l?30fbMe;kLMfi}spE7kIqvk>Unp?Jb6T8K>2*>+&h`pz zOA|9`pOM&UktJqyf2-JwO}$frlz7&>WSYBGS$e?$EpuD(l#-kEhxfVp;od)g~9~0c5zYf^OVx)vkgES z|ETa?LtC~!e+>lL_kRd@*PCOEc=nioxRttz)RO_f9MZuKidQ9TmT&; zHVckE0;gZOTd2`q94V489%C3{STmKVq{n=vwu5KmdqV+pV!V%8g4Xt|kL^EyXS{SLrY|5AtoCP+o$x1UgXV-o~ti4D0M zoZJ05a?%ifPMk**XptZEp0t)0Y^NVxZ!AdLl3by6#gE&vqsA{q-`@3Lu7^!vt|8PT zDHjasDHGxx4^;PSZ{3|*^=u7#EllehKQ9uM*Q-|8R9A@_8M|0`bYiU~eqzqnkKVwf zR1RlNDrQtH8>dBNmEWL34ZFKYN+qOMB_rAA?js3Y18By64)mc^DUuRZ!A=&EJ>O+QkzEZ4lrvn05JHYxEaGyFs z;Z_(<)^XJ8wl~sq@=mx_=pp$VYmEwK6pWS+X>TYXZ)z+@*)W$HDNOPu(NflW`(e$b z&1i_hC}@L9gM^1jB&W>uFefuz3iTKEnS+BtOK^kR6G*f<*H)7+u9R?AlogYBlD{4| zISnGM?s56M09}nXh?-jotldwR=8D3?ZeK%1~s8v^Phs_89Y-r&F_4b>wTk zwE3Kc5qlm-L}m8Jr?#XnFEk^hwH&%>^J9*_pNG;yL~yAm2Ec{8>6M2K>;DSMo+m5` zSq>~rr0W5g%Z+wJR+s^e|GDZX0_n(82TV&gw_2}Mv|YBmwRpf|tqFF|o0bGY`-X5p zdZ`U+i5u(pqA7;fyn}tgoQI386o*#DBYWGt0j3Yx_VPSDx}D6{w$}Vt-1M56_;UX9 zHC`6E-Q)AP70G>vUemR}L zW-l9`A!gBcD$3U0l>8ZO-iM-3`ca4UJIPhI-`%AlHx=zoC1vmK#?ZnNoM=u9TI6dX zJ@Nb>S+kZaS2TkJ7kGLW!7g{GGGjGvmbC|xwKBbJp{vx7nnk2}+>HMI+01H1A|9o^kuH!}X+l?OYa zc0H)*m0-pF%{>TMOIglzvBdglV2PT4H0NPpq2*8XqF1sbOUq*8DAsrHc9HIon>aMD zXo87s^r*_cT6{J;qH1Gnm`oZonT8^BOv_B3NjY1PEh8 zynQz9{A2jn_8oVtZQ{~vubrDMmc{lnG^@(hg&7?y#kkUf(o#)PtG<54{vwW;n)OXp zbNSo>OR^&9eBR5{R&%8=PLIT6JfowZhyH_bat~H8SY(FmL6mUkm<38BYoV0igpt`I z3&>JESW%vM)vlDD+IiS0nM#UsMY?E>ZV|DdM*{9Tc^@uEt*iR&J!5Fdu99$t>u@P< z%%UEPC{$6>dG-9{FWV7`CER}VE&qj-g%s33(|^^iBlb62@C`5a!BZP-zSnRqZ9ue` z%OcOlG9&ng5#gOzc1Tl(zECAeK7`y4(xRg8=3*g`)6|@jD(=#wjM!3kkd@zRrrG-q zjqy_8Rn1XpRE3V{%fo{8DCh~C)H>l(s+@my`GT;j7+=_p#x+i23+B6kyH?Cc(j?ld zp57+d4=ItNIYBv;;axhc9Bsi^~xS zn^XuShjAx7k+99CHd`(h3DU_K1Y(e%)U5_3IGsPzq~LuRK~iF_ImrI|mywxiZdBW+ zCk<3?YJz6RCXdnfU0IIo8Xa=_0rC!3##!$Wu8SlxZyV*Pb|)B>jY}h{Wz4cptT`#H z0(qpqM-Qh}M+3BkTdG^R^o=_V|9L$&Sg6Q-4Mwb~G;kx!?Au;#`n{`_E}?P}5b#(8 zsvq)K>jTI)sfVVbR9Zvkf+;^N1@cRfj~1N;SFSJ|W@!UU<}*4I8I7U)5*Ml^m^J zs(+|;7_Y;SO_JLr;cqUAz{18ZmZ2~rN#veO#(cfwUBsv6#rKoJ5H09ld{g-F06ENX zXa6mH>pYA|MwmcDGj40G$EBAUR-^ut@pG^|F25U?OJnTWp2&p!`0SfZ4FpeUatb;ioo zibVZ}6d!m6Qu>Iy)|BOix5@fbhVu_(DU(fj_uqz0<~|wD_|tlRKjgZy7ax6b1L;byzU0 zpWG^QODP&Is*zhrJ`EUc%V2Gc_$+H?fM#kK)feV3p!>1Bx@&10T0x%aGMz!+u&?w1 zbOP!6)MtrG*T{und;;_jndW#m=J^Pr9b{Rn-QVVmq>T6-(a!lvbm5Ph*p=68P@)0JhdTvjk2fH@rt9!s~GN<&T3oAl3v{320D0F?TQ9lZP z+{7=R7(Rd=v^jF(bf7^Kf%JLbG!bJyQ8$q*FCmK*hZN$s0my~fkhcCB#HzgZU4Y18 z8CrA`|Mj8KiqIwJKfF=!+o&1aqJU+ERF^PG9P6i4&OC1oY#61Xn6px$jDya$$LCg}G~kiWc+ibJd#kH5v;v zWrco<**x{xrH!z|oKsUPbw#5GOVeZ8Px>W5n%-YJxKy=tpE)r>nUd0kZeg-GO%5uJ zDOqb4JL_JsI*!&WNJ>UVaV#0?`@66(e^D@*>PWBrN-#MZ!sO#}YFu^9)~JA8zlVZs zvpmWRHZKNG>K>wK^aXZQpoy%WKdIYPaGSKw4#6n9%50SNYz4czOWDgvx3;{QtfTIm%3)@sAy1{GWTTmC*5lerd&!Wzf_;0LpW$v^ zq1OfC8Hs*@ozxXN2ES85wz#0X$vHh7u zn)Jlc33@WVFhz7nPbmfMnNLL%)0f`*=LX_gXzFpIr2Pni0chN zq|4WSvqHLwk?c`i#f`;WN-T)(cKgF5*+^DbG`DSyOHRht?L-qYr+@dBl3ey0%hzOa zU6$qulhM=6^6I~vwa)MDY^LO5kFC@4U8O+KPwYTVcsve=eV6O!4>mXQW>3Gdiai#i zWLv=8HM5LYYZUA^wul%d>Z5ZQcd!h5Me&4*wnmiF2W>H|nL4g6E^}Wn+an_f_uoyG zDk3|hJ}Ph6s|?CRc8vG5(0AG_7#M$)H7o3sA9<0DV;WtDRUBB812NT=6#AdoQ=h?Q zo|2ab;jU2MiJoI_jd`l#$_5gDqoBB|)cIS+Vn$Hwe8jx2qt1LeUx-Q7@C$qGE7YFb z>X{rDtl=A0CYPKspB?k<#O*DbGwWx3&D~`KG}N5=kywziO(m%Zb@mrh@lhMU zi#c#0V;+f`%}_1DXr`)ft=A}6v0brn(HNh{{_V!?sePpR%B!&9M|H&ar}~Dqk{>I( zVs$gE=5Hx#-d063d&y10zrUARLUQ9+79TzwbsztH>?+Ig^*%zCHlJ{hiF68Bmz%#Y zhe$JSocO#VRzLRIdd4wsZ^F%z2Gb|9aa z?jR#+zWf6Mu&kGXMGI71d}U;$*1pzjco=#W;rP z+@y_GQA|u{Uorxf=-siksMB_?KbLqzbxyI{zl}tdavZOw)6ss$J4#s8X*V`HM6or= zZM4s9WKDgNYQo|8jrBGGrH$$f7Wv} zU0jk|uC^8(bGt6l?uUF?nh82%*oE9e*ZRE@-%uehuP!5*60M}!$VToz2tLn#MsnS> z?po*bKgmk=uT`%6ZZm&>3irI7x;nuE!+3 z?r&O(kFB3<3*n@ve?Sj@nDNLQ46yJ?9vC|MhP=LRx`)Y4cLguTeM1kD=T)UY=L%5H zwxkSuU}5L}yvQTrl7=rc=JU=jLl}O+?R&Fi7vFui@e6vT7Mq^%35j|!dcmf}d0e+3 zkWYGp*dxhgaL_eAHOIi!9!0zZ`=>yAc-&hq8H#*V5aMDxdiC~dHe!% z4K847dVycjg#Yb~A<+!XsL(vf7EE{0bS~zy?14nsi_nG~p%1`gCq8b_D**1rF9A|~ z!R%9xx&wHS-LXW!EPM{Icn1}mntQAs&8GD&76$w- zc*DYt;TJxMS$-g<_yr$gXFo#)%3TSv12RJesF36q-1tX-81&wt2f@v~QV#=FEazyh ztkFC=fbJyXo+%#fF#qSF`K0@%7T$>@1;;4Rj8LQ;FkIgdWCL;Ig{qKb7c-hW;|zac z><6;&Nk5nhw2bIKa?e7Vc_QwYwXG0tiyfF_-GE1D`Ms0Ka5w@vz2b2}KJECqK{Yxd zohSb1=?Y{gGAxH8;1}NMr}INg^FbO~_o53(38cER73;i@&?BYVivAEr39|_2sJ$=F zj2E(qhF|6;naJm_(-)~LSHnq|t4rf@N-FSE7NIh}Nj?U@i%?GG5y>PbspK7{pAM z#(_*ICu9#bmYOoso~uhodY~=*3d{e^AtKXVY)D0WqA3>PjWk!7G0^zmOJowZxW>|x z=+Q_~#MqE{Xqo;S1(ZKFueid2qrm2PUajcSz0B~eqYd!mU-CP^?gHX3LOS%;QtNTe z6SE$R{u+hN->FFWJnnfz6z2uJ9=WD?(F=9&1l@0~`^;q2B zh^@IgJuE3==XrAH6EX)(qiu-nl=%5&f@Vz^J{@_L>|FCxg;;NjGrGEB4_z+{*!@cQOt#X z5hoDvn*Qht-F86VgOx4_IiZ_a# z|J6~362G3G!kbIYxFVN!T!Mp4%PP8%*k}_sE@VU#{+>m+ZV)#vE+6^3-p8X={iZHG zVstOO-gO&lKNIpx4rokA4G5eWu(6o+yNV{Kjyaut^wyHaccbtYlMdIh2b3ZMkP3G~lLZ=N^ z33%DG)?!9{eXs^j@ZG2e!o?hr6CV7~Yot!P%aGo){MDo6Hq*Z2R^w9bp#YGw(neY1 zVEq?EAkU%=u!77MS{5Svw5)>Ez6J(2@<*v@_|s&38*&=G(IVrR&tS=I(iai8q`@QW z3riRL(1M|-*m|U0Lq5b%z zfVnl&2WH&kesNbA3`UB{-^vvN59ft8!sVR8=NCbGXybXvP1h0ivcl&nCeU);^^xP8(nah3M4s3y zzj0`_eZRW;qxqb9d48qdYLsv71~g5%ByRo1e*z$$$5VJTtT6xC#_(>D^0^*fa>ISf z)JaFWi0HNlf1>?i{h;0NIeyV%zj5x?I=`~-uJ7!O(Y-o*@C4qz66l_~cya%#&K==a z9BzSuz0=P2@6+?UaRA@^W_0yc&#-J=xv{?stOnS*aEZPBy7n{G8{C?)eR+4k5jW4B zk*oi*QSC7`@^g)Td=Q}LKYY601@GMU@$o{C^!Aw%M_6wS@X}ia9RT6!*z{-W<`w70 zbNnE8f5A|OdDZfcxAF3!9~Bi2OoxlL?B(9a$|sfQKRpW!75F#8e~4)hZpIdNxdtcE zgFT1|ByV_~C&7pC=qUy!`3GSpE(8DZowuWtSilTNNgE~NQQ#1Tur<`10DDxzb9L{S z5MLPjHn&=!B?lg^{m(Xm`5`WBe}N~*E*=(f?J}$f5Q<~Y2)JSYKrK7Mp;#wD)5eli zy{I_wHn4-+JCLvh0q7X!eN4xfceP)}4Yt#pwzoiwE|Ps?i|?WgNRR+|9yCJTiVO?X z6pxSi07S0TOPuE_FA&+I&F|S!-`|QewU&Mm&e)8?gP8>LW`CFrawf!*rK^N4`#lBv zdQrAdK%JB1+e!^Sixl$mQ=nix#6sW0Df|ihac5h1w)oe{on?paJAey>1j;d5gbCvG z3JFGc7@>ypr-@-jfd_K|yLUF&CF2(tOnhE?Pva(LVmrnt9=x37#G^-e`vkU!gt7qR zhHy!sFkIq!1f%(4JqzLQ$wNK6gc3-o{o1(A>^BN`m_brnA*aOh9B1d^*#B7V2CDBt z`ZbH+==qi>BXarrDG0P!rlPcx=jt&0;W#^tuPm{;PS96|6pZlx+FN~MV`hwV{lKf$<8<0a#xSXz{VFY9KDv!q*aj7r2mjPkm z=G+y&o!g>;(6=!z(bcxqH_oHLvdP8FN|l z95<9`7Re!K9RLX>VIz1jyk~&$;I;(SZT%B2ZIb*UpcbTj0z-7Ge|KMM^DXxA*{oTQ zS)q?yFFOcu7#zeT^ex11^mZ>Js~ZTr&e;YG9$|WOmYpT>!h0Fg{H3zQpf>=h3uIGm zC#Q1rvYH}gKSB5?BoHx>VSoVH{!9#f%=>l&aL_dE1mC9zg$zHjdw->K*I54%LT9io zp>AvdZ(Jq$p^f*g4ej13Ce>bc#I^%rM^E|NVPAN)c77v-P$x%6ad65z6@)#(*RTB7 z?R{IXq)WQ0luxO>*YTCAoVY*d%yPb4?_9WLGa=nJ-d)*?|2TVWyaW9g|6Tm11&HSZ zw!$_N#*KV!ymSJR>4|=e6tL#IUb!MtyJetf3gk?^*+h`fz@kp@OnDz;n(#>h-I33^ z`1zENH0z-{zb6H^n1OYLJ{M4*0zegj*Mi{=mYoV{`N8dJIAOHp5x=56GHeH!+yQV! ze4S!+gX508tPw#kpRWO^eTXGSOAVV^wt5|!2ri_k8u;#RU2t{LZrjbNGO8_`q@K zfZ^Bz#yt2PFzvPu$1qy-#shIc0f@2gngO@1&p#@juiME-~*1x0gP1m9qa&%kV51D#V}y&p;*N+))h$8?Un-JRQVM^+qD9O!rN^& zAcQJ@pmL-D$2|EN0@wusjM(?y!TSYv%5Z~oY=h$b0~|p&!&k{Qw*tdi^D98IYXpdq z?VSVW@bWW+-*rQ<%LKtO1sM4O!kGb%ar8T|?!BYjeTDamS2&RB#pNCVjv4YRK(ng^ zz`+Ix#kPwAis|w@And)n3g%G$!3Y$B=Wy#)fcsCUr{b7_IG%xHZUACR07jtw4zzzo ztp_fdL1_Yr{Wcryx7miYyQknd0=iu+KMNuawpegtQY{=po}&?rLy5v0 z`q(X`ipo?N_W>6y80s@9f2_D#Xl}3{z%wG|P*sDgs3YhkFjG zotAcoNK)xH9U(Fs2$V2Aoh^GZLWNeg+JIE~0EMbp>I-XAofRS{2nDK0F$2q$H62@2 zw|k%zzuEFm%A?%w$VZs)h>P?pbi#>AA&pS|$Aw(3!yVego-K}0nLc>E?6{tA)@SGZ z@NK$9*`OX7?^SGusg{j74{!mp*a?;Q?UbE|PHH&c zD+in(a+f1z@N}A`78q?C7L@j0SoxSx+n+_?C9|<8otcLK0%wMU0`X+EA|!XWi4h zh%x$zLnOlHg&;(N^#f-YP7!V==7j=KQ$4}W&~R9EIjR)IL2c3?irYz|dR}jufke@z zg}TLvXUC|eJNhNk+JA^orSIw*Iyc{%Bb0%aP|(gPqZxod74$0;f^7~F36w?L26c<% zdU4|==Ex;UBmT*sbu)|@mmaBA9SSo6W|;jAmyu8A33meGlh_guQgl?S>Rw(Kh7hDHnK?7_&JhG% zD~n735I$X6&sEkVRhS346{iekd96oiC7vfQNglgihtByJF++3`5ELN< zrg1IA2vaGgcj_!AI|$Hma8!DHr?^;y(F6_8DfLa@DQ zIz0%ICL>N5AZC!u&ldG(&Ah~Na!W>uj8<|Ze0+rVeBrV94D+Qd|Mn#hO|*x%NEG}c^uvkBs+MKYnZL_7Df%Hj8!s zl9I;;fj;XiD1n;BTAB<7p6-XA(2fw%3;k3$O5(;Iml#QET7|rLOtV5!CklF)#LDr9 zQcpv%X1VS~V^pCc-Vj#bG$~t>q9fy!&dK`ay2B=y{49J{sWo^yWdaM$^7xGsiDeHg zKroc*{0ZludHTB>LuRTRS~{epd>p%=yt%G?}c+l3+-AK1^1dU}V!#!0jV6k~{$}%dv1s zg4nEo;3?qaj6N)ba-o^!1#7;(b1Un(f+C!-IV@sAb2FQ{vRXlOoD-o;I3vnKCr=KA z5LR1k>|5rNSuGi(;9{_H5w2Kidx#>dpb8c7C_zF~lm9q|F?Fq=41-sAHSOuAI+rJ; zKBzoyTtrlU8oDnZOJ6sFV^CL8_oKfJn_q#((U0-T=Ye7S{g3Z;(5P+v<{Ki5PcWD< zYDGhNVVTIrV#2zLf?yDaB$mLZSr8;9N#R|_VyueN(D@^{Wr<4$Qi?Wr`;{VEfc7S&;c{i3q`dF}$1)LTn9o=I zq=ouMneI)CY)IxwABTie z?CD<(J}Cc#)aBB6_PPF0=F7p(sBqPhGW(d{uTK&!d-if;Cgr)cC{WU|UeXEzH+iwR zr!S)65rmB8;U^eK*TsQF$mGw7kBevE($^AH?2ik|gN!BN z(R#L17hHP-`NA?BvZ^kt9P0^hYj8C5c6HPIs57WeQEVGX3UT^tmKDlcl})7h^p#X! zaWbGuvDXJ&j6+F1Q5ymZM#rCAl~dhJL;4bW=7TuVo=W=L1cLLXr=#q>hO>|uj`=a8J-Y(muKNxdGDbdt*pkV_RpCVCh&Te>@vXq-=7|jE$z< zd9jJVKkQ4&wiJI(%h=i@VK_n`ZB2^|tfcd2XXy>NH-BxR86}4r2)M2oyJuvx^l)=N zbov>ohKSv$bM}X@J^$aTGP8=AWukfa?MD=$hWdItV8dN_MC<01WLC@Spq5)V9+5p- zn7zD|OPQ2+U(P6Zx6}NCXFtE*UIwX}@S6!yQeYrppvw{TvshMn;W%k633Ntb8q@3B z`ac$wL;*=*@*)-`9nL(v(}=XKtvc`WS;#*q>xJ`*ROOdBNiO>6-Ln0UJ3K21&kbf> z9t6ZrCt<@I@9f2d27!C|5aL-moI<5a%nQYlKFnk$Q-||{LH9-u2A*_(#m|1U*CBqI z^L^oqy*JJ`*u(Uarb&SCa2X9!Pg6fD$k!vY3li{c>EAyzYgJ5OcQ=OLg_C?cI8|}3 zcHmI)a?D}3oz_@N(X489x_zuRPfHjBOhL@(#~XDo6ggTB*peNE1DJg|=@ z9T##kY#`?O3m#{h-%ZiK8vcE+>Hgqs-cCKpy|G;EtXaA)YJI@!h?1s$z#L`wV^*X@ zNl;(2Xmn7Ej(qxxeX5FqAdE0hPXFXovGr386IwoUW}+_bryvzK{=HTL zqmUTqFb=5~r<9jW@*dYeN^(#sLtD3H1UKuEnL1pk)}#padh;pCrY1ZiQi}z?Vz0@7 zJo>hjdHLW@zlPUZcjC70u_bM^}Ixj4K!d6OOwtd_Fx@=u)^fA?0c;~&!SiV`?Y5d;hK(s2| zfj9cehfu+~>W~z%`%~~$Pmz~Yy~tAjRy_Nicb1oyx0W{{l$8fAG*j;lYZL4YeSyu! zXXUCUtB7=3J?H)0yoKH#wEHnB zMUYw3nMGZ?u9!oKJ#nehdL&Kkp)7eQ{Um)xLy#su#U`aMMZ(MEjCDtDOWgixtp3lA zZNen3w}dTjNmd_Ex{IqvSeMkf310>2v~Etsx=uTJa0TY2+}0Li zzu}JsL|)j7PLUpX%Y%*>`bd5uD&njkb(0)iZ^;ji51h-D?e@j?M!pq`s+ez2JV}h! z9zNgI_t7?wkV1HM^_kh{0S|abo_0O88*&$Zd%ISy&YjwA;u-QA{Ax0MNxnoc=f~bT z^c>CyEBY!Q!HYer4`A{_=~|i5j9UHu-SAi$4VE&FrL=QN-uQD_-V6`gr{lxkMtk!_ zGB{Z&IjNH4^pttBqA6+W^m%&gwYrWs6MfoCTsZjJ4a z-B!N0nZ&o}S^si}n&? zRaSRxq<2!AQa73&V|b!WM=|r3DeInVKS`!z7is_E*h%OqZKbylJ109gJCEEi?wf~j zb(XUijn&b|nI1goE}ABrycX|a8*%ErbhnMFOx`qq3uVOT-)|hdPWI6E=zf=eQogOd zO*};2w;bM#`%?GN{J4D&eWrhE#vq6l5-;256toPSkJ{t3Uwl^Q97HQ6x?gZOyc}j! zxn{XOIo8-)FoojaZ}Pq!PidbBetDURcdtDS97Z~iwAFT}jJDEh{S1M9sb9C^vf(8Q zeyM6tkUzKlsIuKE{U1O>sz87@~^tuunN4lRnh7-K}~ zvI12tD$_3`wOP!RyV9sv#MHE(bx8G;pUsG?_ROvO_(KLy-c#oleTSQKsoLd?I(pNzuy5ljqaNIxdR@x%kSrqu|?52FpVUd1j%+ zee_kAuc<; z!B$_JX%jKCtT|u9Yv#3phlYVZ{lJZ2%BHaHbjq=O_a5exW-Y z-r~ZIn1{x4_Scb*7|{_as}<}Ww`wte)k4CukNU?GqJ}cP<;@C^3i`#tatG^@`cIKc zGvNsP2s_&bi;vRW@p1+na;*fsA?hqZV!c8q zjcpWQ8rwbvMglUSM>H@}iPFZ$`ulHj9YjDJ^$Yq6RKf_Nt)wJs+Ord%=0xF6ri3fy zX6t&%G14BAiQxY1(7!+E_36*1W-A#94(D2#{AG9U?!>R+d?MD6tVLRBq0|p{q*JFi zzbg%0clRfkJi=w@ofnp6G}*1UWIUM7OpBW%*ypL%sWO`|UAGl|7EdF#-cfZEc$*yW ztKUIAp+;B}Z5QeTQjXqPV@c#dt7f#mCOOyBmaG`pUSqt*^X&n6Zo?bzw=X&lBt7ot zBS&^3*#z-^Ak4c^enxaOW&_1~6s?u%&WH^QoNcq`0KP1Ezwc(RE0P|po_16rPwX9% zA`5^xsVc@eKfgwJ=(HIz>xF$K=@h7enoOY;aswoEkWJ3e(A;GQZ)r4yG}Q^)6;^Ps z1My~&-v+o~$%L?^{mbH}-Anvoiygl)Z*eAr z)Z+kWgvcgCXi^8Brh?t$U?19+L2D7v`pAeJ8lHW3#%vROVRw34>>}%LcG<+UonU5) zS!YUN4cAiYqKv()4;LtN0B01~kxc}^c;M3*epNHvWUxCUBKYG z0W`7Y0lwq+r)c$5To3_-AcBN2{e(ba1*hU?Q8u7+^SIVYLvzf=){j=H&{5Wp(1)CQ zC4BHH6OMCl^+X=HzeiYX17PbTTZR0i*a73#BkzQgbwl3H`v&;TzT)roln1+6*ipDQ zeDn=?SF-A$tRVOS_X2gc1GWiBT2Tgs!XN-hXKYCqB53p@aF>M*L4XKmfcS^U3M9|O zij;30ye^hfamE0@9mwRSt5PPmi?QKL#xLRnMmK@fl>S9;T=$N;srOM#!wNP|MQmto zR2d!B6ko^Y5<)kd@$OT<$`R(p_XeZ}5xEO`?x@F@i#SYLtDJ&y6-S;mY`nYMD!5*4 z;u%fTw3&2-X25B}Wd9LoMy88AW!z*2D1ZQyhX95+i$s)%Ds1U11R|}s{4E()X;|Em zcIKQPy*X&LLw+4!B0}Tc9QZs7$R$V~7C1c%dL8VRM%vy#b6WFHe(3A4D-ZrLI7f)z z4WRNC8UrGb01z)k*bM}1fLURZfL|bCX8adO&{0ZoG$C5kbwMAx5nSN+%GZRpa@+sU2Ax7} z+4Wu92^3SH?=hDKVlx%#K<4AzHeS_I$)eXvO+s`CAqX%kOA8o%IPjC=+>l;Obs#XN?OeB65{C%~iNTCC+i zh@B9dmX8|uPmNpu11j1@>tLib)UGVHnqE4J;FVqHRiQ7P)sbggJ^oHW+rEukxGx;P zoqBHwEv<=;M*hj?53LQv=2-hGU!xz8E7|%V^5(6@ERzr5<{v2h{+(T74q*I1a6W&z zy&F3;sy&>{P-uD3pPFcthcRy)vYL0YZAi(68!|9+g%-GLhf z#;jBt(Ge;I`Ael{tW@U$BD|%g;HXcz`Oc+gxKtXU5zBeMY$-OXbHRVIveqSS6blG6 zu9g0}Yp@1DYAontn?avOjB`e3&WyH20oHF~KK;pa{ce=}gD zDE)&MS?5Y@(ogwL%lB0Y2e401&QW;>b@#oCPaWeb)A;xh9zQXN6+mPL_7Jc=C4L5p z@{4DHJ|2KhI_Wj29jK6ps!?dk_;lug^9@3_OStEc|G|{))9r;V8IpeDV-%G^6jBS+ zRy>KcdIFu>XvN;vpVA--QPV+MOR+YnKVp4$4OH;~(Ap0=(yr@sP_tzdV>k!z~VwovC> zsvR%wqV;N4)!{BGyll8UvnNek&6GhO1%xP1>zAOd4}Lx(Ta5<>71H{*t<*qU&&Ac;9c@I_h)Rw?jDWR~c6t!UV;K~e> zxEAML;3HCvdQ4RSkWN9o`+pET)Tl1iE&HJ`~Kc2oIASjeE!n+ zU+7k*4chJ@bV^|_iq*OxRc3))klEvWLy+&v^_D^}%kII|a#Jx`w!&MgZO672JR4|I&AqhYGc@UHG`9NR#3-^lq0Z zE=AWlfw*5NA);D*X{_Q41`nKEuE)TbSg+do2;dDn)h|&avL^Fnb(h3Hz*X)0oWtCo z_&qhH_xX^$;pk;e(NHNkwiT1sqTEvuPb=?BlTIsxBWP0H6`}#(6(Y%eJ^r;OTxn0TPZXAW4&GB*`FJN7nF_psZ8qr!6_VhSOO)1Or0F(644-@?uYOTJ|FSctWT`5<+ z-WsiP&?D-tV$d&UeOI)bk-?sIx$Z_v&*9IzZMM>NJd1lI~x zNXirDeFBDf@!P3=_mY$J3XCd+jFp0hq75@2HZV8sjGY(5x7FA}G=m7xjFr-cWakm8 zNQuMgS*YgHTpxoXF!qQKJdTtqt`$F^C5o5*bjWke)E*`xGnM^)S~`TD`KEM_l_W4ZC> zz{jE3E=AG+BZur8KIaeQed6U$+Eq_~s63!=AoDHbm&Yjo|LS2%e()c^2Rr|Is`q#D zH<9Tt%x(wBPAlCIU&i8}kQvMxi&r3v)@N!vREs?R%IPm&o^D+~1y6R>e;j z@5&dMSwm@94vbliyPPSz^vb$n>iBElQYH0U)?({~^46j^w!~7!9N}SY3(}2^zOy49 zk=at{PT(A&y}2@t=*3Bdm>^Ij=EIE-mhMPuE)u@tw(e?k!1RRv&p2iP(L9=qsv&=T z4+AHZzaNqV(if@D%^#Y+R`e_Q`S)Go1I$oHoCJxf-9cC`iv4aB&gTPz|H!fXEV*^% zfKIl9`v&AG5%p1s(rwK*oGX-_FCUh`*3lm#ufK)B#?v3dkGKQJ{*DXaSGxntcqf2C zs5=)r1UBb}K-$$859&+$K?>=WUsJ*zu8Xz5Jh%;_8Dg$Huc5#e_!1WnA?LI~`Aql8 z2`Hh&hMlS91shgmha4l)EbLF+-v3;y-R00s*c!?4P=LZq_Wi#Fjk!iw<(sh!n@@VNM4|_0egp8tPkV=7g|=#bDvzhMK4vNDc@H;>$R&W|6hdc5KPaSn8dB{VI)sxr!Q}z@2I|oPkt2Y+ z8TVHK{iv>VW*5nAkLeTo8#YIX)eDf-P6SV>CuPO$gKv1_g>kz(_U>7GT=SP}kHzdz za>aMYKZOs}2Q{YFM@6Zt`GP%4+>DkoC#0wDLjcM{fI~!ptRjHQ?}IQ2+l$bgxRAuo z*3)cO&?8M@4{&*ZF{Tq88Y*qYKt(UuH&E7!iilLOXQcXX<{<+Wr=;%t1w4HJz%2w1 zSnL2Y1C9?UF#%?FP>CLrG}u*}bSUOK`^QQ$CjKi znxY(@)<1(fK{L5?eRCNEJY;|eHy~E=5Gu}S~Q;nIEkJE`LZb!*?V%-OMoYQ>HM`H^t}5a7HqOwvkp z<^Tpr=E$gf7zld&E)kML6xEfRis-bEa%&jWKgN`+>Bb8n?X_#Vig2WVb>JIa?v6JD zF8tfIABhhPctApX3cGI@?tH@}yO@~nY{T)r4UFGdZhXUYw=hxurusb6 zekiFBJp6`Kw=mKF(HDYT(!Y8FnrYX4G-$2&f#sej8d@K*v$yN8*I+dG*O&5$av3e~ z!K69CyYK7!x-FieDK$Y;#)dwviF;qF4QVG@uPm_QwepK@ock_!aU9TK9KVgowB{h; zXG|Ix=_h~**~{-BRCiK!2{*Y{(ifDNtVA|cvu#N!5yoy4Z8Vngigk)qJKqrSJO#kb zLmm_`Jq3CJY7k=J7N+u+`Clig``6n(p8e5E1z%&xdHD4(Y9YcL1d<|$YJ%jX{b$8l9?yQv1gco+l|b?agD+Oqgfu4 zcwt^9j6=Gu;L!sy#sosftmJ~t8*?Q1(2o~<5Z|zdy~#%|6D(nD8-6^Sc@No->iiHO zMo3TD#|inPH30vE-#>(vA*}Ks^n#3)!7b`9-M{{J&zDOlsjwjRSH^wz04zdP3ED#Mi*2O!7@;O^EPI-9jrGw8>90y1u&;Je41S-pPa zhK4(!^uNg1@c*>51%B?FPKSc_HkgX0>=-{MGki#RzZV^_|wsUQv4 z+|~V#EXUQ#FnSWrhc_Hb6~@)c2&rWvoYG+KQz37dSp$!gK*tr8pw$ha5{`=Xo`sCk zDV5<^gYBg`tcfTrB{IZ!)zk7t~IMr-2yfZ7gULrL7Nziklq@&hXL< zB8s|kov-o&8`HSvi;=DO%@)*~*_`vXx*9JeJ8ZPXTw7yZc+FOn$kKhtK2kmAs@b2s zPYdWjGv9p{`Ty{%yuX1yeUi%M1NKA@NurDq-0$lhF9S=5a*IkzsTd>v8h&|&h zo68q`KptP>fS*Vh?V@DHp6qhgmIJ;hPx}gI6PPqI352q{mf`+vlHJwDS&PTq;zs}h*4Uc~>S$Et zUQNR~lSq1OrEZfw^9__{qC*jyjy|S-jBb`Fv}Uk=*pIYMW4t4q!Lv?NAk$65!p-3W zTA9T5KxNL3J}T^(cWJ0YtCKktusZaZKQL0OyIEj5IbR4~Cn*u(m$>BnsH2X~2 zHrE;beudKmtW*4r8nXpjqo^zSWx05B{F+!F`o`i#v65=rE7LYiWCR~6f!DwT0laID zK`ccRNr;Yfk8yG8K#C&furFHly#r9zA8pkd56phQD=ND=8jj6rPfTueI2fJT8jsBW zTjsx+sQ(9RUjbED(`AVSw*+@WfZ*;PAh-p0cXxLPZVB!LcXxLP?(Xh9?BNgQ<@;s+ znd#}C?&-A-`<~iWb*k>AUaj)l6&rY({62eZg}^&xDIg}!kQ&nTY zxc=&v!)(Tcan0?HF+U%##T!{6{DwQ%SU+t?ten%RQe1)3(2&!JI=2wC;`vPr&GJ-e zoaP%VqvZjxY}u<=gW1wle0=a}-kQ?#;OlrsaViFT*_Uw|g*vIZvEa0h(6nWRI=VS) zx<9`EXS`ff+qtpGG!KP3;kj~L_PRV%;yF4bcBed3-9P-f=s2Q6o#EVAOqz@6IA5^_ zwxw!Jno6!|V)3%vT>1My5j2W5)aJ&b!8X8|YQ$lu{W>0@z{+d6fc5X0IS?9eFHXf_ z=k-s+$}@GHD@SKv4Nt?${iA~OkshbeOUZ4DDfPg1h&%2u*^N8FoOHKqeouEknD-UK zJh}HOzByU>X_pA=PrUQ5<7dShNqXaJe{A05ntCbJW&Zu!5DP10P)Wf=D2C@M=Tb3X z?(<^~#N-<1P#Gy;Qbs5Ss6cXq# z3uq&D#1z`8B5*{PBXcoxsRpPbh(wPgauf2X5~;xJrD`!mm&5-G+m+j_fXNuKBdTzh zOU0{@VE8Awgj}iust6R(n$!lnPw#Ftn3W@i%FW_6_5~vaOZAZfA}JaLTAu7X7y9Mc?^GkX!zzvk3B&zYrg!x3MKr9gIreO} ziJzjub)Lz24w*^j@7>?LPS!ZrUKX>79P3m7RDP;c$ye5E-TGr$9g@3eVZjmNk48)Z zoJ=6TA^c#KMN7;y8T6PN>H+yEd@VY+|E(znq^&NMn#mXIjpy>18^7H1il{EC0qH^d zXn7J*?DZ35s4l+jd5yA1P_insj(ykEE1h6Nwkg*c)S6u46%5k(Cbev}*CvWqGhBUe zr;>Yi_*LDlPl0gTsIlW~8siAhdjp~ibb}58QS|}&u<8has5(IGgR0Q?fj7ZVbg$x{ z%6BNgs>6K1ek1!EEAd^0Eu#A2jir|cUsU}i_Um8q5dR)l5bV7FRPL4sU-p3Z&j>G} z@MRx+cL4YXh(8mk2t!b*H05`!=%rjGzT?>k5w3)Yc-`Jdf6>l? zc~|2%6n)-`(bVgc_Zz@fjRIGB*(J{Ay1|yw1U2OHv`KP8)RejDFl)aS&sB*t1l(yZ z;IdUE8;b6CptaX5dh5v9uvp!$$KooVee3)Ns6Kx|O%Lf)-73Emn>gtKPoV)2qB9#Z zH#KJM7Z3^O!}C@MxGj0R@+Nu}AgaNTJz^POqA~4>4_MhT`TQHvGvDeD?-|RdeRo=)~e^7Mi0vZ!GMbg!;UFRA?QEMru6UG_(n2K|wAm&?mt3%v$8;u7LEYJoUJ1y;~97STmbQ9UE%_Ox`>BU3kUKxMm!- zJKQfc?(ze^PVx?0WkfM7$~%B$E3+dw+g`C3<(G* zRixN9}vhIOyGGbUAfc zn>pm8 zmmc0BEi|7=KYT3ywnHATtutKIS=jWNKAU*-foUGMC0jCHj{+H&?d@Rn#+L&u=u|yx z4#T|VCm+$IYVqhf(>ylY1lXuCSTfcT5{{cth1cpa^Cb+A z;sOUR{Ly3GPi-Bo?0MV5rE9L3Zsb&8Js4m0zE8}&X4xn}AuSysJU;1`6EY8~R?sq? z!%i2)#%*a4vlZ(^Vi(*D)~vWtBN#4=-ZLcg@C8$3n}3V#lB%F%&7q-he2Bk{_sB3; z-AckSG8%PlST_Pi#=j^r`P(h$pzPVeMX>xLH2THARZsu+u=IJ`+f_pb(rKIlsaU_V zwW{Pg5+|WzX{`zU&2Sa@5cXwf>3+iX)^a zzUqJ8$Dp(&(-L+L^l2nDdz?e*j}0~XvS(3cHo>ZAKFJ_5;{W0UB zBdZfxa{A&8XD-$W%?8dGwGCbYJqHr~uf~=+4w3aL)%&t}kmX2Q<~1yp9pwIaLA%Ht z_*;^Izn8K*FBWR?vuFaB;DhJrjImCupWM&W8_;!0$5nc<)5leQywSU^Sh>RDbSDL7f~1lajswSdSE61? z?xX~PfgLji%B_|wy2sbvZU&RO#|kY{$({OD&JLYzOrcWX<9+gqo#*R2X7(R1db2wI zcb8)#gHMm{v0Kr3Vr6<`8h43)5dChyP}MN8%yuM7%BMb> zq@~id=%MbnJ;+K~A3W>cAC!2)P%uo~cZA$@RE{S!b8ohExoNiQW&l<=scTXJPV4DK zXIKf%T$_iMiiYv%YWo&m14wr*X$ebX_bsaXC0;NTS+{{cf8suh-nGb$-2c<>_>W%# zz={1YLlq-R4~SBH7ApdaTofGCHIm_iBeM??-q}_u=xPc(5q!~AOjZ+Pvk!_=YhS|u z%AR(g2lza6n`|pAipsoxa0;pDp?cMmi8Kz-e=LA=hjMuAEn3;{9Bai&VLRbA4#-TJ z!8znT_$#{B{egwgUvJmMp|o>=v%JYey}1c~4c5&~&BUTRtOR%Te>L3oKMmV~4f}{z z`;GSWgdcBOYp!Nu3lL|#=LnkB8IdHYpW z@rz%XS7|lJr{WG}Ww$|Rh12TLd~!iIFgV7mA+ll7vTAWdKheu+3THjHL-yI>oU$Q% z!B?(LN^4;w2b6Yq+7sT>vOOBVaVbF<9X}mvWzTQhV|Ll`5z#Mct{2$M2tr-azASdN zLJ!4|T*Lm{VN(IVWg;h>^1Ts3J2-uADM`EM9LWk06JdP+`OxmWAxcBkiEeI5n+5FVSPc~G0V7HJ zl;vx4mZaATr_o*07Na24R~B4ifUP_{Pag(R_Ws2ic`7pF6s;XQfo>dfaI#g^6al9v zDkqAaD|oX}Xwj=F4_AkYlA@KzyNx;%N>`78WJ;}D5H>@j$eZ1j9B(hD=RQUpf>8|x z-}u#j*5ct6dowZg!IoBBcxspAXW%n7s8h7dZW^Gq!r*k_$QF+P63ls{ynXxrnmzS~ z@#H7`CV%wy|3lhMziDZrIuGewG>;OwHSiHS$H7w@^1tW8&~h+#b(b7;M{&! zs5Y*`=M5Bozz3z4UISNlZ}$lt+IyS3!A;j5+ zI0Mi3cT2iYNUZw?{lwf<-`Nx~t!z=>3?xSmuDNs|L3^G~Rr zrYEtft3g?UrCFna6OE%+42cbxpke)0$F;EZOVYI{NN-#LkyUys0~p%@aR&l{kkxJ0 z1|LGd5Fu|UzZm%)#>?d4kw*FL*$3oTL|TfPNlU>{yLZwnuM}KW=jkhc0}}Q!t4)EY ztDWZ$ygdUpZ%CUiFMtPP#;45{M7^iZ#aBq*Fy1S&7hJ&}zb%HfX!eWyqGmY3zK#d( zg&mv+iiMnrw|>tT#Jpy-d#`9XrVy0kZ6Utzq0BLQMr6K0vP)4s!~s_pUYOug_l~%4 z*Zecgyz)2M{~icF!qoM#w3oRx9Yc_dosgoLUQUU0vS)aGcew36hUA&sWGKMUFX~Rf zFbJg`C_NoCYR_FM0ZYdIp(Na#6nk4rJ6N|k`pB{!2@tXp_Ee1v7+_Zqw5q{e-1c!7 ztQ=zTh9K!j`E|(8Km?LS7cYzFGFRD#>Es;dClKn#k>bnghvq1Ki$19r&%A5aR8S+B zpBGCz%NZvAcOgsvjgFl-y`uhfF!JFqqakmwbaOu3RaFr;Gs0VbL4a16;!|_zEj^RGkew9o|(u& zBC3EYXO*(M22aym-;Gb)+$4j&P1aqnaYC%h;}ZUjEO+~ylUFUbSy=Y`g9d;5%;ec| zk(=l1eAW#^I|1M-W1#2E9wBLmNAOwT{J931q#I=dXqCJxmd&0%G_x5va!}J#z4ub_ zKxJEZqJ=refh{s<7}gbi-9+P8cHML`Mycb4-xj$1QZ~_b@?VAxjxowNJ>aEX)-#ms zWr>0S`$&=4;~Z5s{=QP4w`GESbYq1ijg8;{{{!B|z+??}S2tq~s>%!H!mb+bfKM}c zC0h}`U+I4yMB!lT@}N+Ar5%`1dvdstL-WE?%|*VuwjV`oyYh72LE*w4+K%QTrri3P@jZSzSn2X}Mg-?BJr_!m5xDM6<)SUQZlTF8 z`&Y1>JLkrER~HnYA0?|e_?g-$-nd4xc8OecE3W(*)8IaVc4yCj7pzqeqF4#x65EEI$(5QmklRD=Fw!BClRl=mMf)q;oEUXB9@Jjee1wSC|80yos$4!6|dBex#o?VNl)IA9AclgyT&LQsG*vAuRutlX&;Je$hUo#98%loydbnTFH@( zJ`?!-LN=qp%{N5Jzu0iYJ(890YEaA6{=22A#g`eI%Po8;Id^EcX_rXXD981uliJg= zAlI63IDOpGr*QfmdlREIB8I!is%eI4h5?tSbDx#_UC+a03qgN^48S78kgULI=tmWY zHq@shROyM$h}a#DNl?mZ6I^DTj>W+z)cbFWhu%QGb^1MDku|%pXB0`d%o&{`=SA)t z$w?plwz!iw_G2I=h_E(Wes+-neXRd{Gy5u*|B zsoy+Y{*vK{t3*)&n5mp8Vzns`FsLMEtAtCdY=xZ=DEOQz_aB!U)LAyGv%i?EHW2|d zney&Y@2obFSk_Rj8Ar1p;PqB8GRn$yA?eU%NO zI4-Mgg7sm97@(Vb+Ab2|{nedSEtYNYy^87UV1#_czKV*j%D&2c<7h@3p=y=0cFry$ zk=jwS@DLVH<&;Bk4DEk~!cDeJcrD{b+k0$R5&&c*P1gAVtThFy(w1!#Cuf%{EKS=~ z1mw+{B9_lp>$C>WT+5a#3|-sUy}1ts(W@jELdC80s>U5XIqIUnQ}W80RUuUn&+unU z0}rrL(h@z#r8-Otm|NODyDje7mXICAwMA~TuTZQMG*38f6!{2Ny7{KwK{xk2`kp(U zt&6i*ZR~JU9R6tuX|a6v!WsW^`bPweOtYEmCR3KpWbPP7Hbj@(mat)o{%T$ujD&j~ zS0-cJj|_LzFG_oLWHcw=d0JF~nFF~s>7Wj_B`{>TM`6S|>@dsm4TcXLIsVPT-SU}o zac?gzLpwg+%F$6)u5x&o{of)L)u-m~-Wgi=f$!}maz~WkZCVP~N$F>OAo7hhyCQTY zLuk!~pogE1@Tq1r)fYUAP2Pea#Kqh{$|clE$cuDzR)}F3nHJ@4r4q;aZzf&z{*l17 zKRC5LXiZS#A3$uecKksVtX8Vvs$~3}l1FuH2UvoncFq7Z8wX<)?FS4% z??~);Y2@vpn#m(4@fPz>n zw>f2KkdNb_h8Z|8mvj+7IESUACZogbGVq^K3cQdhHZ*oJ!I0aC-jL{S7QQ2esRkxG zHFDe0H|Z4g!k-LJB1;$xJ5&Ct*bREK9;7pGMm?I2aT;~v-HfOF8?pD;adYA57y$in~W#uQ=Oy_))Ytcz(18i6S;5jlY=ll zsX6&rQtI1*&XYuTL%iBvX01(Gl_@hS^54w0Vh9{C1iq&;`=jlWHN#MpcmHXh_6^zd zzG9LLWgEQD?#|wge?aoyeE!$sG2>&z=g(~Y1qE$~lrPHR1)sp6V6hRwQ9ONZ`VM~P z+5I2A7VtGLb}M#sI?HXsUBa0eRoS0P%FrhkNpMCm?Phh))&ia9Pp_2qM4w8@0A;8? zSjS}&UcfAs^D%zH+4LDU`EXL^J|5_1g(J89CHX2{ zaP5$sk3%6c3o7fjuwjDInX%a*y0m-J;BmjC>p zA@fx{tW}7$F#1*ZYg~k^b>3?w4yo>Y3w6C=-fx*T;|LCEzCd;L^C(|stHP6~BG=Ji0ZHy^E)tem2H>Hsbx(9QUe)sH(itqe;=I8eXAdXqBk$8fFp- zhCax;FDEmyd;z$t)kN}!a9O>x5A^LJ+^igCg9l?v8F=ZeMoy;I6H()-HU9w#w`jbn zbFuz5+10l;c$g&xvYot7t~A-yGdy`7WrImvlca86;Mc1H^-UFJz%85Q|UDO0Y^>2N~@$BoLPg`Uc)Ll+XJny{bnkZaO z^ICxl>rcfD7>g!5tuorl;ioJPI-e^?`(09-8LG6?*?I}07i3ogjBQF+j-7N_=FoYY z=(%!CyuMj4Z$D5EEogfk2wP2(T1}8#-T32sp`IE@pc1DoX6(twi;q10Bzuy_j9T}lrPu_5vMc0je`7~D&1IK>XO*QqRHT!cG6DY=h zMDQs}l_Wa<4N-_BlP&v@R0F`3{aOY)>y}R$_c@d8IR6n!@R)VQN33nq{2uRIAaC8Q z{yEAud-~$PP98N#R>^wW%vimF_Ylzd{P$? z#p&~;s_l(n5Jh7U;n~a@_?aZL3aQnbnqpy;_z3b^r2>?|K>IOpFtJWrYE+|fFo@hw(Ta}p61Ir7t8hS!+zZ}*R zAI(8$dilB0${jRWOCL0zO$ihL5r7g!Pi2RjuJbo`t%KKp|10b~ma0S*ED{&6Ykd6F znP80!=sq!KdWis@e48Qg)v5AD+H`&8JxB#mto8xCF3|W7v-EWzSYrVNJud2i2LZkx z-elg&ZCDsf2P-jq1Qo1tL=}uZki*|jKdj_6=nQKgyV#yu49_0}eu%ok893Xq;DT6| zaa9`fCf1X0#qM{77KsPNhEQx~owB|}6=i^rj38fLA|?+yH+bI&Ao+S8`gwH~t&f(F zB;1J5%`$c|3*4u0yp9H+wI0Y#oE=cde@J-lu`(J*bgo7&az5I?x$_x$2V;1u94;=p zZm=4Xo$ovS@SVnmpDOf|{(_M^&ocArpJt%@=j9LWq%U!pzHptnp&|8~A#4OOaKWmc z76tJOP^g0+Za*Q2quV(Pa^!*sf_JIOg~XqQgmOTa08i&2LAW(MMazcI)1{~8Wy9Ca5X@mB$c#eBbc@p_eyWf+|UhCbN43KJ#SX> zsy)=WnzpO}$xdV*v=r@HFHZEm$W+yQh>dced$rXLGEu*1_XzYgA|si$m+4}Es+hKm z_O-gz%9~G7HPVc>U@x5K;u4Kj1)3-F=67fwqB6UMwTP6fl@aEM)sc>eRKWf64|2c- zcRdL+RorCdRlwjfRLZNmUQ-$a{HB7Y0tV3`&w|o4t8*mf87jHCpgZ{|PoJ8{QPYjvQ|GB0Pp9iN4y576+QE57ZFeY|aY>SU!Pdl^Wj)@MX3zH&^hiAV6~$@~>YSWh2WA_0Fm z(bB%z=c|lt$y@gn=W~$F9$7qvcjKUumESGFuvwdH%pKpkiDdlebhhRE;m`aW+zP+L zh2=0EbAMn2Gu{!5v907!e~#v`sF%ze*WWB}>Wt^hRK3*QG+(_*Cv7nTnw@|OJLR8m z7@W5FwJq3Mls17*)p;tM-yUf zDawip?R$t<7k$;#pCeJ4v5pt!=ciGp>yp6?Gc*;&Q;0U@$_0r>LVVVswJBQ{pHH)2 zac$&BrsJ0abh3C6tejM|a>5l4#2u>;W0dXkJ&X8C81aVrj-e|x@ESH{M4%s-1TI!6 zwV(5EwIuws%qN?&Xk2uYWIBzorD$E|5OV^Cf>>{^h!IhLpK?$5=DiV1xNE?AEFibA zsxQ4JZMENF_4NKq)GhR;0pWOD%x@BH>v?QQd2?jSM;+-|scqX`Hd**w#PoH|Ce?A9 zGQD`63vQtfm32%e*WF*Bbsek;?qpxOAU>F#Grdw|hHAg5ox^wTuQY+q+=$)?UWtwm zE4Q9EPT^KaJjcOv2SaWO3;0q`)ECP-NPdlN#FZ{DNH6H8al-2JuapaIceLkJ7fy5{ zCU4h$umClumtTvX63qjS|q#CUq)9!ua#7tpbkAelexgODiL*zxGyKkjVq@MB3 zslKv{?VKeLGS{?6WQZ6ocr5fBP(|M735#FB3(hHf3;xwebu@yQm-8zA2eu>DSrM}UR8 zzDsH~K#kWeiid0B3OuQ_i}7+6?jCPd#B*=jPC?z2eb68`M7TK7y{v0iX?4gYpyrh| zv>@e|f>CE$SGCx`s1=UX8`l9w*1aC8V`44?BEOnF0 zOG|$$R;y7fJLs#%1}_AS*N0NXC$MlAB$SI;rHZVl(+!(el)ADVCzGg-pwUv$r>gV% zEUYkygNHFRvbJP2#86vi(JjjQ;1{BGbxKR6R8&ezON&lDJseAuftR0#)TCt8au~-6 zR}O*T1&B{!RwSG8LN8~OvxL{5SXUzVe=IhtYc#6MUYsx2i_hw|lk95XGn}1Q^eX3$ zcp5$t6}h>+M-~6>bhF0>!a#fV^Df;TmvLcLS|3wB>^TNxFO18%*y1^L^Bk7Vej!U& z=_(}}QFL8T;fw;N)DW{a8dbna5{pwB4OZpEoeowRxLTaajuO;aR+KHYT2{zY7%!U8 z&TDX*qU^GnW(-EnrqtkB=~1z-fd_>#hH--zsoUkYT@b63-1(b7X(_zWEmM=7C;W6Z z%|1~$SC#3eM9xNt*#0vWYxWsoD)9ZYzFz+6BB=h^$dYK)+^1w$#GVQ~SZQNEH2R~` zNw0(-{TLaD-|kA2`dgZJzaaC)fKqRXn)W;)(NzGzaOmz0JJItfR0D!)$HLjRX?~ZS z3#W8CC+1bP>$c6W9(BLt18(%EbjEZH?4rLSD_O0Cv^OYitWevXCp5W^Xz}VYx=Dd&^nSzM)$2pO<$okB)7F}D!DSFfuv`M$gj6SzOZazu3;7#hwx}(L0$Tw z;pFMng2?iIZku+BTO~FtQ{c3xq|{fpp>fy5oqZ!g&};LOVX~c4IvF4V zFUw_ZN2p_Vj$kz~W=AW|^vcj1MsHw|DW78{C~%QpUDDO_JaCagLI_8T-Bpv_752HF zFU_O?74X5UK-jhLM?p~u>oLC1iIZ}W&x;!sh|`neyXKw6-DGE;#NxOc-lJEP!jn?8 z@uv=afz$$fR{K$ovK*q7B1L}VDpdpkaO`<@o^vo-SB+)KGeX1Oufy$I+(a#VoprSQ z;)>q3Hl>`CH_t>tBHpsy&&>k6JMR^?4UDTqjlfNjQae~bR0ad_jyJkn7B(8pV zo22G`_+(sWF@?soIJz}63YkrcH?1jWy-%_WgAD5i_zDALgG?+TABd6*LHq|{5yW7k z)ct#>G>u1pvzW84q6Ha^&DVnH;`*N&$8T?+W|YW0OACv{Zk6dzOPmkZwlYa%+(HRC zi-6#RNalfx8RMqsE6LmQ?n(<1C$-@Xfb|Dl)ET>HHXrfApI4zA8uH&xL4-IH7~)h~IpzXEi=1tCUi zpv@KIQe@B&zY`7q3N01*5g#UzS3wv)G_3d-pvRs$Y#gWG?0kRv1e%?npI50WI{|H$ zoyehGC+B9;{Ls^gQQV^!-B3Kq&E8U7nw=Uk(xA9&12$)bED(?08NBhwpncygE(hKA z5<}2_L%d^z*_DBAOEy$(hO6uG(ND8bC`*N&i`Qpzq+T+{$8Exl(n1`hhZ#)@6ek^#ESK_X(AxhGAN$@0QDK-{+QBFaFsQ)0r}hy zqtBX3$FI){?cnQ4y3Q4@j%7j6skhE;8*B={ie7LY^qppc(l4{6laZRIw$~G@=@Rpw zj%LwSNWh$!sn+0s70ZTqPKs;{maMlpg|tlaVLP@ut}q9%T1=sPDI%U$4ma@m1uHNq zB93FE9bjVn*){;s2Ghytj(^A|zY6mMtybLB6DA?wg!RnfXw>l5gxQoDwCz=jm1Q^< z@qFqs{O)XZyP5UcMW~$?*hAMS_*9R(;pR7H{45gaaI|W8Ys381h})@8@(}2=g>)D2 zZg*;N;Ii{sB*NhcV0i1s{M3dE(usOyC}fDiA!OI9b(%uO+I|%$aQA=r@eIZbnC79j zwz%KiRUMiZbVuc1LGzbf_o`y`k0H=ZF<_Y|BQ8}#Eiswt`dg8Z+jSTfvA0i4Z%6AQ z4)2g^wBL=c_SkA8L_rQhxk1HK6I1l(eRofIAB(=DAly#uvuZE6`tgS2cMA&8X4a^H zP#_6k_M|hN*Qf@|vp>ik>V4;AYV%NPv+d^#JFu`^*ws4u#XFG11ASzATHt_T-uZ|E zRHknQCrwmQW#^Mof~&k?+W;}2c9A)k z4ipdDro)|LWO2^jBh_crgu4g^1fD2#)K85lEh7%MQ98!PMqdTRb$%Kp@$0;S0#|-J zYmx&Be-Vm@JF-)pZ0e3dp1d9yCN+GYsSrL>( zZX_5Ye64VH_z|G3&s(`JnOK-zJEm=#dZVG|1bNA0qR#qp*jlRQfOS+8qCF7X56ho$ z!EvOQNNV%_^>c_)1ORqn(T(=v!4+}4a`E8;B2wBj8R}M`8d)864;rQ$5z$>$@yk;H zZGb^{w*QY;lnJ^=t~;cui)O7`Cz>bj`Q(t(mzpm0h`R}4FEdaVs9~zHSvrb+HAGO~ ziLrpdEN{>#hqk@dWw^jd+Hxgr#{_3?25U*q!jxg%cA`ke3DQLKhI$;FHz@g675x^S zi#ABIdW6bLGPUR0%5EwOi{#OsN; z_=c`$IL)$=jmLRT6t z@pV0_zRkb-0w%{xYUbcaJvaHdfBy0hHm%cj>-shCGaQI{o}ycuX4~;IDt_8I&jsdB z$CFbnG~J&OhRMm}e~1NQL20H{lPgMJF%KW5x#}PEC(RRE7e8T+DVyle!Sj$eF%nb6gC)5h^uadFIK-$#5yApWivRnAc=p`PX#EBWBlCsaP)nb z{1?s?0T&h*3b?#SdE?R_V0C{Z%+ndu^4Ef_N7FDrN3M5;)#Qb)$P8GR_+n-DtATZD z1<(Aaa=k>7>5&v-u4th7hw%mTpC2{J!>}bqrSo$s=GINW>>x`dLmsmfvqsj5*T#II z{0QD5e}tTY>?KIFEG`k#1{Bp zkUkP~^<0=W_I2`4VQ>*~AvM4M)>V9LSi$reE)dHt7wZN0et0YL{SqS)K5igvNH-R# zy{N<$bZn8#2L6u(1l~;I?YT&OK?YdZ-XYxYyQE=ykq_o)eRtf-<4p&rccwO>WbIlu zK|hHbdmzPmu{e!|Sc9fyfMrwCIjJRV5udjKLER$yYcl#YJ7~}?2@+2J17C5% z06VrDBAxd&da!hG*8xyl(D^t})fDyU9)hr{kfPm|`b5=uuAT(f)}iZ;M#u>UUC=(d z{MZ@H{tL^0llRjRP$Q1nGSrtSGyEh95l0Yc1!T>o7(d>TL$3#9>CbO0i9BzMxW4hc5zQ(lIyMP9sL#kkj^dW(7JfVd#gm3q%oV97{O_D_3vv*I)9b3u(G3_-Ix5$Rp<>$az6=l(loi z6=7kP0dEeS9p;-7jpfJ}*^**v5igOL!-|gFHW$eT6KgSfeOG6QhYwuvFUE(X_W>r| z;+Ll?t$d}NVW>->lXlVm@;d#xxppVhO!k`^hUks*??;OlDU~ zW(R)_OXM+IJWEO*UT;Z{aAGAS9#O118uD&guFOPS@`HVTF+Ttgb^^uU33_VHFHIrZ@gTrzDx`g3np<#pvgHL9lzd2cJn5w|JRhEaOrbyJ{Mo@rHk!rl zVF-5?h%97(GU%<}5L_$}F@LE;?;n^Ue3AW0ptpt~{;tkzGrZG9_Q!$V8h~(ThR{`J zEr7Tc+&X&u6pBNeb921a3(?F3!GP?K1ijV!en8w^KlWmltzH;klN6=mM?l+Tao(F` zOr=SGOhoY&ZrF5L^^h-BW+@)t{6h2H?_ypKr4k=#e!QAMATZzbI%~zVfGdeMja%u{ zm_}b3wZ1IBU;cvU1@36tm3hm8|GRc}E1VIuvRTiHV~1)Zeq^aGfBsFFjk58=YgVR{ z0H(61xhj0mnBwnzCZYaEadLwhbSSJD&SUm7%zcuFS7QTO&~YtHIMRJlltiq=`(`S+ zX0*&Vl6^0VtyR{F5Z!bvaQwUWo`+%rhH8 zybNYUQ)Q7;WvQLC)X*K8VY)ge1OuMAYMlBP+T$e=^7B!N$1Y?c?oK`&o}V*>ll=(- zw?aNPlfv7=2mYnr3c`cXgx`Xu-hMR#%4(__uP$KEyv5gaz_q9ZY%BE0-TmOJ48#rR zk;^ymy}9#uO)-KHAXMS5peew|TVD8oQP)w0n}Vj!KRWZm|3kgyf$u;St_qqu{dmjc z-uw#V%nz@FCcGFl1^l?aS06{BOWSVtSCt2VkPdL09t4nre9n>~M+3YeSP|SywnV=~ zZzcYIX0Ar0f>J$Y&yUj7r6DWP;yf!b7165ApRj-k522_4>VEQE|79BVJXCU zIf4_qul>Sde6*xT2$$7mLmo7v>9_$^lfD+Y0j+dk*0HYk4lsc9x1 z@Ri!x_3UID&|yiXMlzHr60;yBWL5Bu)r_9b-&ID8_w%j{?1b?zaG9BJ{>lT{;XvZK z+bL3sc#K8*tYfo1cj8LmiJn)DWO6cJwI6nP>QW$+@FNmk_bWnbk|wuza8mB~0xE+N zCE-edSjPxX?kqD8xooKT3ue6!^NvS1`j*X>S-uJ+he_ZeYtK#U)}zcaOzR9v*8XwW zeU5R|r*H-Go<|E@N%Zz?*p10|05d~2Np3igLns)oS6uXi+N1;R>IHAp9&Gg(dWqRF zcCBYWIMPR0v zh9GLry3=%{@1IaP#q7K9^N~^~S1Dqg6LGnz&9%DfY_;L3)%5q8nH;roxnHYEMBt{A zmM;yUCU~xeBwlF9eNoZ1et-{I3F$A1Nlt_fpZ(~7-(H?{S*zJQYI*FgVex~Do~5xZ zC%G?6^;;|;{exQ80%86nOfRt-J~_jp?xOa9=w*BFMl$}55I>8l6`Hl|SThipS^!|^-SpUd4KNl{yg=9Ne3t) zv1rW+mqK95KXk7;c35ThE?=BE`?`-FRQ`bs!lnb$CNrW@PF$-g%2del=J5Pe=N!Ye zDpXql|Hvz|5W~A%F87@%O-0&%zP*CNy*wv-Z#L+Nv(>;A3Io@>kADFf($FmX}RY$^>AJwp0 z2tiCSjuSApNkwz&a4jeB8Iv=juFrbZ@Oz3tF$}W6K&lN`mP;=H!{0l-th1$-UG)~K>lET8K#|5-T?9y zZX*RUT^f^20ZK1=x?{BS0nmD&>7y*({!oWnBq4yx~!<%L%aO!jkei$ z{RnAW2Ngt+QNgkk#ieS@#-3JdnAb8LSHZD@OWZi|Lrm+g#Q-n-Ogb-qI^2duj-|DB zT#O`-k(NCKf3emsIz76prel!K2>W?J-K5-5MXaVKZTZ6V;vmDL&^>D>0AF5!Bx~1_hM2bT)tNo87s={r&Nx$c}bD0e6|<7Dav~sk-NfxRk5BaO0B+x+mhnyWF5ZkFgik!ni0voN?;J5Dk3?iVglkQY%%4rj{M(R!7?JalGdoNktChBujhm;#E%8ABRJiYB~4clvP^_bBdE%)Hn>NN6y|DW2AZ zSCyVO<`q3)8uIw#Du8=F!?v8mEQK{{a*XcAVb|$~nsxPI#(TrNXUY5^%_(a9z}qRH zW?tL4)7F}EF`Xcq#i^`izL=>geKlI=py{AVte~ZD&ESl~EiG}7_~y$h>fmc*Q3L<% ze5cK?bEd^%8}Y`DzWJteLPjUxedDF4w#LRyJ5Ed1mF@}A!N^t2)yApk_;cgE&Eo2Y zTL++Je+SadrWg}dui)jac6s#)Ux0)+9BAw zp!E@Zu_GJ|mt&TNQW}H;mA@z71;{Rk-LG z`Isg)SJidacDm}>C2E>n5R^NXt@-2upf=y%>v<72K%@Gsz8>+E*Ux@Fom zKih7?Z=zgy`EWkjI>g?uA4ogB^>L2ebKdWL-}!EM4~=k&F5o7ZQca&T#?Nk`*Nb*x zaSq!^aONBJ&|*iG?({ku1Us|u3=I-oJDXV|Yn$7MW?I(`ec=Dw>O8TbJ4&`s!XJ_I><^r;`iXs#k12 zc0z9D^5*Zgp$7(3h0%?^&eHznp<7WGL}%|8`;8}JGL1Dil( zztl)@1bs2Z9v0@F|LmY-x|w@j|-@tx^P5OVmfZbxILN?Is*=s%|CC zdfhaoMdbZtj#tamR5;rT_G z=Kr!|Ax{3p_8aQ&AaUL1vwEPT zzDQzihW63%*;Eb5wabraLfAvr^3qAw7R$ceKGE*1xUH5|Nzr_yEXKkpE26kRg%Wj7 z&IF?;HzWqG%5(BKckOuQNO8tMPIYSJ8pSfh)c-E(TEaFhVppNP)wNI_>NjaNFj+k5SO&b(WpFpT57DaNeBxefRob=xvpyX|iw`{&^l_%`MC8fS)? zt+is0T^XJwHc<9@d%bRhSnH(Ibxf^6=~V-0-*!DOE_dpB4>iw&uf4q1mCEA^&uTYU zORw>!KH6q~t^PJ7kIu6_5-Zc(e4J+^y1kBU-LBIy7UehH>>D*lg_*WKy{I!PexI#o zEa{_OF=q?3H(zjACGI&kk{$V0$B!zu$J-A?ucyf5V!e*x;FCzUdLOFmlvc*R7!a2fJTYnVn#d*YW9KvzaRBwZSp=0)12j z{W@g|LOxRR!?P@VIC`^0r;XQPv+^G>L;B9CB`3Uh z%9~;jfOnFB{cx=z(QAX_W9r@#dt`6X`n|b&_ z7Dao-N5APd%2TVb(WUBMKZdm&qW*q0{aw#lHAZrfX9~t$vi9*+$~3z*M&cyr`W(ov zQm5!J^HA#m80!P^S0TJEJ}|njqr8)2@`SJ2E*;_3`A4&6V3%R!<~DeCcleo2J+pi@ z-g7CpUZ1wX^6J@gqj1Ccar|tZszTZ1>X?a*rx^h(g$`$?=xX<+j=s?0-g ziMpPYMwFFO_sP~V*^W=C|A|}U*4OItVR&UIaMX1xWF1W*V8^d1v7tAT!rEHnJw@Kp zJ8Ug(p!|Nnp69pS;EU|9f7P~;*H<2;0ErFNZCBV8_4-jd{TOV=Xx+m8R6jYcY~ZZ6 zVies#|k}j za%xd;do{9*i>iLMTUS4w)K5dydxOdPXscdrseay3%j-lxxtq5M%KzT*_y^4YAy+(bU5Opq3mo}h!O{ey=dp+k|tKJoD zP}tvcomTm;3nNGUP`A|W6K=iSMC-7W}DU*zjmyAqY|tP6&HVHKNl$agr^ z6Xp_Vk?OCYP6_wLnD8@X-CIR^omM@K&e5Yly|pw{ud7DY=9D)beZNqzBfqTsFnXPK zrarIU77DKbZ>=SXdIw*A3u|?n^BzL@ z?F;=k=TIxDx)-%pQT3X6SOe4=d3a>c(b^mC>F^4>>gSvExmxdd=#&!sX}!8$;`C&{ z=(=L+UKxAT@cSgc^;V-3znZg#g`~d!)zxo1phq@pZExgEx1o8oeli?>A{>6AppJ9w zH=JHm>B{Ek)YHOhWx7bQ&~LMv{@ZSm_q`WtW-4fST`S6Bs(0b?(2A?;%~x!na`ZC6 zGL&d*lc(NR2MP}T?6kVq9Nj*I6^puNOm%Jbo^8bYorf&ld3#fj-14v;vnL!~nf2Ba z`^;k~^`K6GjypQo|EOv}8TLEC2ZwoGcx*MwYjkw8k^eh$xLd@7uinS}XIO*ReTtE% zxOrXmdnWbdss5j#$rz`@bZ^&;C~i-VO9KCqV8C`kyhljkTR-ZNQ z{ex=z*udGte)=DCeSVauv-SAtMLjk5A*CJR@!W?v!m-^(N7c13GX6&*3_-wIHmC+Yv zRi8qm;;Jf-sK7pG|9CrI$2;l1=yT-KHCBdt`l9T=zb|UGs{erJgjJ1t0)I4`7gN$! zJu7QyJxc$#NZlu>HUC8tev;Anbz5yY8nWh`#*vGZtr|>2=n}e|?x)LWB2A)u>7O*6 z#?uUXnI59~R7BbI36;_N^c}(%RF3c^RWMHLnVU7CO4f|U(_Yq!wV@xFU_Iy{JB{^6 z>@qf(C8_V7urBP6>{gb{(%3{cfIZ9Ru&dc7wvFAWzi2X2f6?S#{Y8@u{Y8_B`imx$ z^cPM3p}%PIPyI!c$@~<43Y((8#_@n>isuLRkmsOi!+sJ%m|TbsB9XThBgIH=iQB|& z+$U}qV|jwOOWegfiMz!Fo}|C4(M?PdQ+Q92DW2n}=YSpT7GR@DmTha#$|H5+-?k#WwOi|tiOaYMDCOoMo?DDO5+OsJ&ZrfALWn6mGT$U zFov0K(_>t#zkYFxnP4UwX=W$0t1;T_ZuT_p(OUZljdK{hm7aUspbshL;bCbV)H3;rm@D%HnWXS&F9VMjdkX1 zbB^(u`G)z1@rAkGTyK17er0}TthX|)sm2ENjR)gv>oMyw;~Vv*2V z8QZMcR<7}_HP4!7?65wtJ}}CymDWmQr?tvjWmKpyIvBgG&#cdk-Rg@D#vb)W2V;?HSqm_uO z(2m+7oK5PNs{CXjbi{cdol6M_=iz)i8bIw4lBffMdgHGvq)Mi4kfA&Ef>gcfRFrfY zor#pQ=p4v%E?tazFQH2z&t(X$pt}Phh0@+O(B7et;!3&>SN$(S91W)tsOJW{36k7Q zBO%ExGzOB4rF6(}H?%wn3@`z;--jTe{rBVAi3q*G0h1uXKM{J+WP~m>1)(cFfRIcN zBJ=_?JcP5E2;FEZLU(!?p$9!ekK*h!Fi3AO$PD@eJ%-SSvJm>xzY%)S;|OQa6A1n2 zNd)!Hw5O0V6X8sHnsQLWOJJWaV4wMPGA%&pK?~_Eq%5YlA;UYgRDWyhL!=kd3dpdM zK7tG%BlOT;YC0LbRfPB_v<^8xqi-P3MuaZ3iMBwhtzfi1n$b?tjMkSb=m+FKKtDmM zpUI{(7=h!?1jo6lH;ZF?yX9oOG|lB_vvPHxtMwV);}9t_wF4A_d|S!>psTCz4^ z!`9$Hj1v7-D9KD_lEvDxwupO~mlBx8ENahu%twhVfhEw1tQ~7d=dkvyJ@scDSO+?v zC9*{7#7<-<(n+i%>joLRgJIqJyHb*!&dx?$eO;;<*mfXlAH)Ws_Q7m0YFA&HY7OQc z1{u`%rX;(bU61@DnEI0YjqIdwC$fpS z_F0yVc6g3GM_t+T>;>w@X0aEk2g_kEQ8)H7n@!zWE}MgtO>7fg$~LntbOGDSwjo}@ zD(Et{i|wL8tP*-SknLu>X)xQv_8@&P+l%ylY#-9UXWt`zKl=e!9bgA2z$n?DE z$tAC6j%O})^vv_Tiuh}u*D1;KhUb0ibF+< z3R9Sr2y54YdWb}kNIjL!BSYCdGGX&Z;qTwX9n?aM7NZd#BgRmi7%Rq7Q)LV3T-d_9 zarPc@549417xyAQPK-m&@nSsU6T}4a!BS>W2Qg7hLcOq;)EV|N6Mv_QsdT1zSUgN8 zsBcnHH}R-=6!B?d8sgK%bUI7?OZ*Gv%n&nB=40Xs>L{KRPa%D#mX26HCN2TRrAFHl(|s6g8Z+F*QlLXBwk0! zZ-_T=?{A7XarQ0o79?3L79%B3t}PUWD0zigfjle4N?f~2tU`@Nq6pU(i(;g&7Hg1mtyqf^J{2V> z`3vzCa(*qoMmv8azQJ8=6x$(HsrVLm`kmN`S}H^(Qg(}dxV!Jg0bKQ?IEdE%NmQYH zTN-q>bjc>^Nm9zT)LD9^MQx-{!otV|nLzC% z0=1CsWqWERJ4o0UnJ5#g}N%whPuPE=|$aSZ`m8^ z@NcNE@^9$>h&vPbDvGS_pRP+nNJ0|s?Y><%1PFT|A%qYx?8p*AWD^jPT|{IeA|fJ# zpomC96a;#?8!GxDRl1rR~v;Fk`$Ui_Ii2OI`gHXuZit?ei@J$!!3sCPu-9a9;?^vcGNF0%iU|e~nt{ zEA`j$G*;_tkmqfEEo%9b{to5q@9OK+8lC!kNLjDHk30N8{{SUy&^I9eMtvjlY|=O3 z?1%b?NZ+h)re^v_`bU(dG#^s7>Dv%)*SF&ycIZ1$Lb+a!{2%Kd;~sYFyAgh(e?mej zLeiBYL*^w$g=EU#*jdu)+|Os8&lOO|6zj zx)E!{QysNt8d9no)hWhEGLos9kz!yJGin%KKlH;kMvX{mAq<2HRP{l)JFcg zMm^-IZ#1A(qoL6Vr8PDhlYrK2iZIj2#I;#Q7V>8sEs!(U$fdeQOQR)HS{bb<#b|A` zrrJ=V`N-eKXoC{k8f|g5oq^E}+7!yk=wx)lRo#qkNbhd+K+c{LM)rc#>mTVp!vonbtP8fO^~ zQ3qqT@i28V<`^Y(tx;-}qQ<$#Be?&0#ysSlZ_LNp$Bf62zQ9<3J6UKfLM@hIp=8^z zQL<~es6A)|aaG6&pL&r`PXg7E^^EA@;J>KQ2^8KNdKEW?y7BQk_E>z zPoxwDuA{s_uRt$q73dx4O|1ic0_f37eroc^<85j~6f@_BbhEdbN@W61S zj0lWGo>75O$OCnb5^fFLO8o+317o56gc!#fy^z&;GAr@qN)@iub~5YiWLDTcSz#Bk z!Zui8_hNASa#hlDqxhHGoBG$?VYvn$y zl>@Am(^wzZVtt%W4?`K}vog+RW!#*VaULt<7Oae0LKn|R%40ZMK^HH8%3O#eAL18ut%4^66z-iTEF(r1PPqSA#O%fSzv7dO8n!`fbXkwK!UU z_TNK%J&qPo+5d|>d>=;(Xl=Ew`UV_%P~01lz6m-zkG9~*gWCQxQnu1IsQK+UT0wc2 zo6w6`l_j{u$05z|jg?{2;#j=kx_A=MZ#x2l^{?c{cqGy1Wx~`7yed z{*I$FeMSF7{3MimXIAQ6q13;l?({v5E_4Q}y}PCswJFd9w0nW((LB_O6@2;y3O+%L z)8e3#t3kUbuy#+UiYzH*IBP$$9lat>-D~@ z*Cp%qn$YX%R7I<+)kVGav<$?XoLBHd%hIw?ax*O({Loy>K|B|lz8W-rON!I-v^><< zN^667Td4d5P3?G>qIHDUPtrPTohe?sR=XDIU9>LH{av-Lh)1hB!a_@-mE+o1l(ffH(I zw}TUW+8x>*n~eB9AP1evp*P6kK5)f!Z91O73{XTWDB=M;hX=tDHJK$6 zm?f%dv$ffXKde0g?fxW~A)A?D7&F5_W`<$R48xfjMlv&uU}hM_%rKmpVI(ud2xf*+ z%nZYr8E#}|xQUrzFf+ptW`-M?8E#@`7|hHtgqh(6W`;q`3^y<{3}R-uftg{DNEC_G zP9%vWYA;em3gTYjMO+s;;(p;r+!Q9&5oscgVnrQM2Vo=82w`*4oJ^4?@(^z)+95tz z45p#t7I6#0F=7nGh}*<%2q%aM2&aqb2qPjw4b(ch)JQB5OAtOKo}vt~R4k?b;yLjg z)f0aZe?a)Mco}Ek5N{y=TjDL`d0)IwP1Opy2)7HU3Q;b~DO2neJ86LULVSUg!{V>h zM6HTTnI3$ozFH5L8hWaEsv(T`#8YEWbx(D=-sAOnsjes0lZvpWrzXxe@H9YvMMpP# z+IiYh9nW;nbn4-m>6wLawxz>!C2eVTl80ls3lA@yk=x8O?^8OKwR2z&0{p5WY zq|^+gw1INH8^KF0z)PEu{voKT4X9~5;yb(_Bfb*^l@Eg2MV-C7!BJ{O-93o^#rrAZ zpLsu{Zr=T1s_tN_gJ7!9K~_~jR#4mCufbPw-c#OFl<57|`z=yVdrwo6_g^5c6cCr7 zB%j9@gLo|HD+%;f6(kl12J?f#;t@~qRYyDtJf?%kl1chfKxL^+WkpP7otVmezEoct z^u1!U7R+YZz6rhwQ2&a=l9|D(`=fmZ_)0(p zio4R7sS25w6YuDt0PlUP@+CGMI?cn1~9Qg_4!Og_oXImt{i$xJUjnO=&RUJRy} zUQ922m|prZy#$zE(wI-uKqmFTIra7W(8`KT+Ax{q>lu0m^mh}z3AJTD$pfD>gBH&Q zrQ|cEq%);-(p%~+sT;FO0kcXzSfvf}w*|4ZW@4$$#FERz(gwuR5j52a+;T05r2uDp z=slpb3qdS7Oe}d!Ecr|)n{ z)JNi4MK1+RFSYbr^jnak7^V#~On3b^`frf4STDx6yIsE>-)Ou(0eSA!?}Waes87VT zlk`bQxktYTp`x8uOgr6}c4{&0r0Wmq58-|k^W-!0G}Gtl^AK0$lg;Fl!{n0(@^Pt! z9@K*AP?zd-OdZ-cR*UK|%ZUeN;_9s2@c9kbVfE zVx;SskrMQy`ccr)G5zmIS9IjlztO)z$|?O6cuA2`O_0*}$XTIRPy!@?hVU#eRWYi7 zofI{tFg58HP?G>PB|-NmgPl^Co#MexHIU*3L8XA8bd;kAD#b7jXbI-1g!3F#i{gyh zps56-j**U96jP-zQ^hk==|+Z;fp`<63GyhWs#(cY&5UM9QDl|EWF;%fD$mG6?TV~? z7tmCK(ZT3|bVXD00-8!N3XMXPpqMHFOw||n(+^~oVDvZoBd+)=f%z)MxWRymFm5z% z#Qh95h9Ex77>4+8uvP-IRti|_Zp0O3)nLlC z#acdQt=`O9vXZrIkW~VcRZSyeL=cY}QA#%+H=d+?weB`TMN|1qQ>~1B#y&DJI9H)q ze|3L#gg5vHA!UdEZ#2|@)QqPXbFw*^V$B)m41^z;A5ez*q4^;-GCwjuLVT;amHM08 z%xzT9++*&c2If)o7|woUo-+?X#d3CaLHx(F3Lb_Ov% ziExR03LN-9;KqCq<8#!OiLrzHqg+J=@=x+lNMA4i7vTnp5gGKjg*wSU%RhrWx6AF+ zL++7#kbX!WLU=-+K$)lHDe5A>mETfVd0L*P0w&4MOp@t=?t$)<&)nF7xv>)yV<#rY zbSB1pCdTZ*(7;f{hk+RLm>63zF}4ld9Jm?rF@Z7Eg}HGwb7Mi^w!m#vD=;oljH@OD zCQxqRuE1T?Ikh}>7d1-#B=r-7pQj$Crm06#k03me`VBQq{WkSmYDglmQ?1{fR$O;H zsO8s~z4gmJCRNU;xSGH9U+j%})|2M~f1Vi?3vt|YZiVdgpH`GualGQwijqsO&v#KB zUtQ#Llv(jH|NZ&E`I-l^4}O=*pHn~mm+=4l4{Fvc|Gk<|=2d#IWnR@)zx-48|4+?7 zzY^bNmT}2>Q58qeuZ&#j)mm}r!q#|o9-gXwULwc&9;NE$e=o})_1FJY-1A?lDq_88P*wNF%f4W0D>KIic$&b7>+`CPeW$UN^8B`SZ+D=y=L z&t-elWy0>3RMh3Sv`hWQcXFU2lgx@;>dpPAt;qbL4dL;TzjLKW-{pQx^_<-eJ1&p)bI z#%qtA?fu7;Z#cgD;))^o_dmVe`Awiz3N_D#TW2rC__EQ#t-YM#XvX^EejFMQdxo*gj++w7lNneHme zzDzszQude2YONgo)SKY3uX2@+I&_J#@UrgV zzqZcW|D~P2e7kXl<^D%|h890}=2!nV{~kwvS$n?h&#&-5Q$MeZfAXpoS6!jH=T6|k zk1PIMv8?j9bEWb+bwBezL7C9r4=9y!Hl)j*wX2x7BC+Beh3z`WTuj6z6?gxXUP9g< zoxybGa{gcJ2XE&JZPZWM&R<4TiD#|U5BJvju}|fy3O_q<_6k0A&$0L=#^0ig(tgpb z>|f*a3tIZs)wj6vfA;>{^;X)h%6bLe!TvZT>WFmG)c)7xVXvGjqN!*~F(ON3ReI=D z6)i<8iW6-_2TBm#L^nziy+v=TA^M8G|p#ZWTDNHLQ9;ubN6OmUkSM*+3} zHPvMQois5?Orlz1vY1S@#l7NQsw1X}8I&#_6tk&;SSS`zhOmWAP1Ro5l*v9mSz?KJ znwp8F;u*?OJ7QC=+7X-b#P7xL=^F8xc#ZPKYVjtu5o^R6YA@au?@|Y`UaY5%;(f7! zI;nlJsSA7kbXEIeQ#Y|glv8)HQ|zK1YIkhvsdmSv>%;+Zfcl8T;&0Sf923WBfcTgA z7Y!C?LF^b~>iJ)K14VN$qq@cdMO_X|i$3pGZ^vet#{R=P&U8 zh8+Lh{zvF_zwLjXw))@jucN>EcbFReL+yI3rJJ42Vl6}McdQLCL*^1~nA-1HyH)LX ztlg&eJJ!aj{f@PXYQJM`lG^WBo1*qR)~2fcj895-oBEw@ASrVnU>&T~^_C z8OQBXaJ!7-cA3EKG7;^vCEBR+9}}V#+GrKD(Kg7_Rl z8tp!Zz1o_KXVCgqo3?S_!sn+y67#R zBHV{Q(~$d2I(yC46GzZy|Eeh#ys@KcPP{UF8X9^_MK?uKZ>5Ish&w5wI4ZEH4Dw26t8wAr!4bE zvzR?Arjt;8m15CX-=>=8T5~NKs>f0adh9y#neUnJQ7v=5`2pe^%niu1(cFl$o6XIX z#6B1S_Q7awZZmh_>@IT`!rkU>|xMoneBtWNc1l1!q;GFc|$ENllgVo#4;Y03c7mA^+FSyQG_J+*H-u7yoOdPCU= z>5XLu(woSpC_htX;%t`8LdiL@B~p}6NCqrUK1yyQ+u*9UvMu7uOC(ozkR7NAEKv_i zm4&j9blFq(r0TLr7LnR{y%)8Ry=8BzE&Isp$t(NI{>V8%4!~LENg~;kBwG%Z!;yZo zycywWIT}~Nej$FRyc6+>aw1BYEbk@1oGPbLn!HcmhqKe=bd)ng&OrQrc|YRH|0G*J zC?BL$*gN#2)Q?j?CKFcgbHu+$JxmRi?W1b2eJ3a_^`EKVke`HBl|6m)*wZ(YJ$>`o z)3+6S`d-7HzOC8QH=jLyTd}9_HSFoznmv8<+0!?VJ$;+Ar*8}P^vz*U-(2?eZO)#) zE!fjHhdq6B+0!?RJ$;+8r*9T}`Zi-v-z@g@ZN^@_)qY^3m36LqfpxCRb~%pia!jRN zR(`#4Y=s+$E}{!;fwINfqKD`KtD!7%J+{bg#Q-sY+Ob`3$#%Jc7$Sxszp~ERVwf0) z{KLfv*d1l5>xoffH1aEZ-LleN-zsi}&ArfS7mMOb+Z`*$i#rfMYr*dn6A@Q-JXYKd zJMLjSUY{*^l^5ayE^+NS7*OuFZ(4YvtP2#mb$Tc4>mfLZFCyj=%!*LtaGZ^EH;xAABio9D@&aw zwu-HYD-Y&0w%4`TUe{)O-GuFRs@N@dV{G^YRy&ogc0lZf)lL5N)b`Xt zin8n}>{p%SX$bqC!uH)@AL};kV_nS{Y78Y0&k(B0GlZ(Le_NdKx$!yGgRMV8vBpv3 zC_-iB>l#wu*Z^RyW)y?e~0}|N!``H^W z+5FV}6e-H?``I(k%ND;eTl@sJ?~T|?FPXjcyzHf?vzJ~nhExx1SyfpTp|bMIz9%4F z9acU7E1!b6^5jcnD_@JPd=prC6LAS!Z?b)FDr?Ky$XQ3$frU+%brG)zi*K^eUro01 zO=Y%hj&$Y!SCjq!(%9D5meAzqG`;}KUQM24Is??zntAlBd!!r&1i9wWpI zq^nV(A$uhz$vJWkQj~9E4faj+v2S7$`{a4qC(p}1d1>sEm&QJMHP|PwM$JAo`%nsL zRn}G6KpKeOt0wg$Rw_RU*vw&pBwW{Zhv=8uq-&rUJ!f<-@cbyj&HmoxHot( z_*L*^@Vk(({Gp_f9!gzYFNp6QoNXU;*Vv1#Jk(r-x)(%_gce1<3Oyf9il#<8hKunG z&eq#65*u9^eJ5NV-5lK>{USOjdNg`GdMea!as1+x$iZkRIwyQuJujRY7kwhUC%PhR zN8b##R&OHM-d!Hd3C0E+1Z!K%Ex+BxcCG#4NVwF=$M>0zW0u|28e>0g$J#sXGJCy~ z<>Wgn@wS#&>l|T~s#2^a_`I31lgosc!r z+F~a<`$FA9Md8xW=+L0h(H)zm$Pdi#Y&ku#bh-5KIhC3N8pf9eg$TtkvHwcNSU4f?I=|-1)&H!7ez)qm{V9 zQD_xq=nM0${`O+DqRzpcZX`G`I3##~aB*;LaJ{u8I5ny-t`goK*%{dyIT`sbnjX!H z=0@|Q!=e+SGozD3{i2K1+l#&*eLlK7x-WVrIxRXaTqW8l+AXv%^hI=gq%4$+XO6ZI z*%aA}9#bAZ5ndh@(YR>8=;-L9(WRlK(IP7wy|1k`*qUY8Xdf?ItLz5W9ybO3*X!1^ z$J;~f*={d)gtOa8a&-GE`#UGj+K(0x=L~b6cO!V`@1Rc?JIAeMuHQ|w`?zhLan5L4 z+S2*LIcgPH^_)}A8MlfXZ>_Q0yV-7@+u1EZe;I6_v|qBHwO_T@x=UaU7Fes?iS874 zx;x8$$(iiVcWrBpyUf{+C-kyg>W*<&xoezh_9**)Ty+v6sF5?s*=!xQPPiF%YkR9( z>O5-gXA886tdplOPfta+KbwY)SI<#AJJKKroOCr`?2D^9*Vb+`imlQ z9Swx;9Y})&4jRFVcO(?-eKd+$eJXU|M*1ztyqso2(e0-)aS&?Ffm%C6E^~E=6@nJwkeHB2+r8Vo+h~XP6YnQf?$dl4eeA2| zt42G0y3e3pzEoc=`h@2ueaf?v_URdV6Z(u-N;shB=q>1Ty`_E)9pd#5j`BRBFZCk5 zh>q#Q_2Kk)p84|?&-wXUFV=6TU!e;9&-zx4^d0*5 zno#pkv`jVsM9WfhOSEQcK8cpCW|C;l)f^Hn#|Rh!t%aIxujLv|j7+Vinhm10Hd-28 zwQG#Q#$c_`m|{%PdKwFjh*o4QHWq6GjU~oY+6~51W2ttdvCR0LHrRO1cu^Z-tT0~F zMj8i0{hh(i2&Wj=+>dKsvNqXS_8@ySazAR%wD;Mg%ceTnPFs5= z&MkIIRoTuGXPLFhDYny{btrp+HC2`TXg!qugnh<3h?4g>hf(e^=Y%ugS?;WvzjEF{ zXTKYZtIMp}ZjM`MZF2iqd)=|_q%vHMdpvusJI{4@x<{PB*5Y7XFg2JSY!u82<_0^u zt%F5w7k9P$67u8wxMi;Et_=!ztG&-Xi2Am;O|6q|ZMT8F&-%{(!s$HEoj-a0WP7u< zz$$ZQSx;Mwt(WGlw_dfj+DEOOcrr=0ZV$8j;mNGPbJ=GfcjBFT&J=b3^IVkB%NgTL zw2oL`In%Ax)>_=(IQvce9Xrn6&A)fF^B?JE?X|btr||qX;kzxi=Qw$GD(Wk+rs7FX za8jH!X9T|8B0J<%ar|~8douDqZRc9+?WOkn2)kLX>va=d>1=Sy9ou=?IqgK8RW7;X zgZb{WZhN=2I}lH5ynDYp)t!wJ`Z>Mar*YM*&Ios}`<1&Xm=x6UBp17j?IL&VBm3O3 zc6u<*U0~fG>}DOY_svU0Dc`v#gZ+Yo+#&9$vbFYPJI-Bi?ZmTu0waUwYvOChf5Yg3 zv7r`XjVS|GKO09H<>07AZE)14PB`jNA@!tm>W!lw^`(APpN8XT$ZLT$qOmv{Q!$PV znvA0f&7@h>R9mhsr)DBeTtnHSjc7|n{Qt?l#A6}|8y^-g!G5n0uh4k0QmmwjVwG4$ zcY*2OqDkUy@ebV+10m&GbIweTE+NzUY0CX7S9~hkT|lK(l?tzJ*lcd))Up{oc34w}e*s{>QhJ{@`2Y zdzN0|^?g?QR{2)bA9)?0)xLLpo9PYT0p9_7UpMtS^nrdxKSNv9@@urs5Qd=b20paI zkcOmkaPwaJ*!axYPoFRW|Ha?J--7ls`+mwiy3c&fe2hM09^G%gZSJB2<|pPpI%6I% z4`@})&&|)Z81t{@U$t2COY=*us`?*AEzUeCg;uR*ubRCyKP*9k=);!4uY5>m?^8dT zSr%`Nn7b63ptNLqvC$1x}HycdSFR)!G8}I|JF1ALL9JY6&o>kU-`-oCrk1bUt zscYvhwYxz3jJ8LW&37g{({Rjm=9HX%BnJv;oxRn`FKtk=Uwt2_wp6F1wZ-XYd!1n= z5!*XwbjhBw&Skx9Z^<60v_80Ng7#0e##qHAr{^x^Z#Ts%ex$YSwdSiQV?`EZtNXY1 z^K)rn)7;G^`_=v1UCP?xScm75ZQ1ttl2z7ptK8aS9kWi%-Hsa6@2jkr?bi4Xv364^ z?$WaOxl2)VmpRj{5q4oIS)~h#=Z-5|SK6R-_1rUa&)7L->!8-Q*kkc-mRqy%%uYj{ zu2FZU4k-4f_Wjmrd#yctZX`b$&kO?6)p^?C9 znh@4Q7!P3?4$Bh45}FX25JCu{F--_2AOx|o5G6I%M@dNaU8V1mP&0((Ff8js6OQ9} z2w?~zgb=qw6Ss#j#32rGh{HGx>tPu$%d!md>|536Z1(Jr{kMBIbJ}%E;+F2Md++;k zd7j6YQoC^KRYi9_&jI6VJmi~|gTXg3FR)$&Q-2(?|NY$mqsskQ{RbY%xsT2uR}qBV zegFLb`^1_D{awD z(lOL13)j#E(j#s&GAWEkmLl7c)4^EJox!5P>cP6f7C;)Tuprh83w$A9v<0DPFm`Ym z5XuDK0By#FQFz@R%z^q9gDsIgAwgIGO!Qt&mfIYh5Nm>w$OXq zp?K*LtBn+T)BcSv@cvsAhZ1WACJ3<}YuTCtMdmlW6J06Smkv+NB!83TZ3Du!f z${R@y-lhzZ2B|ds4DOyDEG7@=CfX0PPiFIB#=~J_M5MFB5lV^#$+bu{G7`B!A%P;P zg{$O-sR`CgS3=J>Ba_iMQ5V^cW_rqn4ZvPagEkn2(yuTIJtjnl2NwWSO)D5}HF`ff z##^GBo;!fY3{ev13-%IRfcE&oW0?DbuobBalXMfCLDw)y~uoI2IhAsvKDzPcq5bG z+vGv*U@45Y5fC|e*3$sUbt-b8Sa(4f5^Ryjd;^q^2Jc25D2N(78d)FA=bn1X;a>8m z+uSBtr5##i2%|y~w`TFG6N4LrTcUCB5k%1JVE3S5a0uciOMqyJ%nu^GidhPs zGI|*Es5BvM_%>ND#A!FgX=|8d%9#eKE)*Xr2WyqHLtw?x$h>^QeRn-oyb8Dg7TFW) zB}vQ%>|7VogQbi?O>`&HES1ttOch%i!6Qy~mR)!+2H@C_G=v{Y$&uPft@J38&WPke zL<|;7v5*gStQ$X`{@Mo`^-#MrcOQ+M>nLX|Z0x@!6j8Xl1l!P#diu%o2Bm zx@f-=6%&B9#{hlbj?M`~z-b7u>v6PItQpLTPV)zmaZgp`Kv;+-f_=ccaJPrRe+q#A zToJRQeu#K8TgTn^ZAw~SM9K$mSFp3vY@i6T;sN+}kx7^CO1soVcr$!EoJmiIml7^U(V2zZ(G_YBLBDi0cRPCz_ zuP_Z%QFt|cKXA+(aKqAJFd`*H;vsLE!Vkkw*!=L*un~^_@JaZ&XD^ZjIh4wF)Bf;I zus@O>CZv182%7*orwQkV>!l*e0AnAOrUHlIXK=4>c?II`%d1at6EYB19f;~I@Oi0J z#4N#D(?a_Pj{y;U3cZcqMi6usokdV|9-T)pbP-)dV$dI=KSWgM+vwZKIrJU$9V8Zg z7kwA`0Qyt(r$`+7GxTT32hm@kzd+*A9drlz5XNIXl7NYrh`bP!7n6r1s@_t)g?w1` z4b``hB-N~H7CEolR6Rtrs%_Oa@=?`}>M@e3dZOAx{zUZ;s((PTRYyQ}|1|k}@^z#z z*_v!c{%x{7*^U$?JCYs9Cz1z}2aw`qcd{G#WHOaZAtlL7GK2iPWGl zeog%vQl`G7zJ$D@ZdNxVw)$K^7TBp__ud4NGJyNCq zn)++VXH&kL@?E4l<$EdLLq3=C{gm$`H7P$x`2q6zlpm)25UEZ1QOb{yFQmMk@-|YJ z^5c{rBVSB;C*@tFKILyx9wL|0meQ7xj@lD2|$rrk-qgXq)lrrkxl(pJ+} zk?xoO{N;S)YwzoJ@9TE&>vsQAb-UP}So0s)`t<_&jQKO~I=RUw9=Kjg&Oxnq_aUqId6_u4G7(t!-KCtq zEh5cl2qyc^sBwRyKQq`8G~3F96TxL)wSCKvSv4Wdrwt`qGkq>@%eUn_vkv>?f_46E z_n}|o2tr-Sw=YcEGyL@dy|q4+P3l6G#EC6WY75?zE|QDPrbLIbeT~w%H0fv~NdBQM zRk|t7a9P~0R25oblLB|hMad%Jp=>Egkf9awHlNKGd)M7(hvsc)Gn^WXPJ2;P6&5$`@Gp7MRCvcG{3l!2hff5*9{(EC%n*)}B$dobtwvj-T zF*5yjb6|uxv1j>;0+WFY0VkIrW{}f?sz7sq_9gJ7m=##J%vy)ZIU-FgwQq%E~wUQ?sZQgB4#m0vdto_g?!)M@IiF)f3|4Hb+l?dG+?}s-1I{}sdNgzJ7N|0QR zW70P1(^_WPRPHXn={~kyWHLi5)DpSKWqG%K6ZR>HUXoAr?zxXyy+ec;&a$`2zV+E4 zHYH(=+~jY_p7qTV*}`M+eXK8mo%bGikH|5Id)6`I%TP*9zG~knQRW-+uCvX)7HE;f z^S)c&2ex@J%a`x#_I2CleMP=T?_*zu?=Bo0LMp!#bV2+s_!A+1`-7wY8qsBK4W(H# zg9(mi0E0k$zv{8Sj9&~K1(T`Ap-iE{pX<07yerVW&MpP-g*5zTFyBwIljLnb5BGy? zuy48d1M5_YKP`9`IPlE|i#QiwL$*ru)&inFv?3z5B&mVF&+G^dQnM{6=Y-^hc#4V! zUp4VCct-RK&9-!Im)o`9vP(pqRO>yZN_+-MWXoms%6ls5eRa|ZU+Iet=-CL+bsLVKILEZuZE^WD{>Zw#sW#9>`=C~KtA2Gkkb|iZEKd+ z!E(d?8@?@nD?cqQLGF$Dr~RA63Aq?Z59EsAA)O`rCI31|< z-E;H=#(l9o83+b$24<|!0_A~*0Pfv$h~Q5{aD&_wD?-l#cLI03YrK)T5zGo^@dd(C zU@7zvtnT9Wg9yeYoVRciIha7N$G*pz&3e197|4152VL|JHa|W5iD_J--mT3 z%bFW_5U!9;A*ZW|G4}?F8`Sdk zq1<4p9}_0YXa0hqfu9aFg$n#vh%q8JxM8gq8bZe4Rw&L-got1)vFbP4CIfqZzjau6 z0INn#u#w#KH~CxTPkg&{sUt7=$Y1QwX1BSFU`43dmK1mlYffUYh@68QeHN_tlRO`) z_Zh$gWkJ^J4|;<`!jY}q1{ogGg|67^#Dw5(aNl3a?T7k9B%ek^OKs%2W`PykXIF=OEd&Cf;oO2&(ov9DgI&b*slrB@?)?*%l|ioslKOLgx@kh z2gE4<{YoO>z7HZ7;dn_=^wJb9?`7n3aHK0*-bWNIFGJDtG8HZFUnwfy1%yI;$e$nq z`+g(f1kXcC~$*U=n6pI=jO=M@EUUPV6-h_gpQoYxe@X;2WSQ9+!23f9C? z22iF6y$LAu4MkPFuBeJ(tQ1h@AfU|810wtaR*y_z4S*>p6-@a}EQCdnx3MVp7sxw` z=J>9nIsR7B9Dk>%jeFQKb{F|i*ec-8XMj5&BgZo0L=$AhiN1jCWBcfb0eAi*nuHx; z|AeZs-^U>6%YZmvKtCGu;h6L2$7S4!7RtC2EtU}{`pK9-i}^TO8nYg=j=pjZJ$DW* zKlg!iapW&= zzN`72<|+Cs&4K0!{jKIW1w;20y!+qer^TZufOKC%PZf;&d-+N5=yO1~CFnn=l%;$I z`+$sYu@?Z{4q}NZZ>9`kFQyEqjAH7Pv6OFMTKOq{SXRn6Q@)9PEak6KzJq1U8ZP$n zl;5NrV1Ewi_P=7ElJ_)X6|$a-eMZ)Eu}WFb#a@+BFIFX^UhK0n>cy&M)Qf#i)_JiS zS?9$*FYCNm?fK>NE7%w0orhTc`5&Ku7yHur_47Z)n&sVwSj+jJo&P!Z6?w-w)^`5U z`A67S&+nYy!P;d-80(M~VN540!dR!Q2xI#5C+GhO>pK5?Es9+QgdK|+82{ zy&>-x$E?~HwJ%~etwx)I+2x&tm_z%L_GQee&C+IJZW(`Lq>R5YO2*$9E#q&D(Uxjo z!C395wXb5L{Ff+LK;G|%g|&6s*RY7Z#}6CUHfz6(eO=z&hy8o)W$jn7QF&h<_Lnk7 z$Hrxhj{OH2qhk{?M#ui6meexX4K1hjU{hM3b`blf_D${Au{pr&quBSfW7@yOzOS9o zzJ>ij`wi_)>}^2mbJ#oD?*cl17tr|{_H*q$Kg26wT*FW)?=vCBiC~9R&8IdRm&$p z4Kwd@tPn%Os_;;FCaS~>V!2o)>P4p*6r+ko^Fr9 zV}mxE&?X+*^gw+PYTpzm#Xa%#y_(Q6-jm^xgly*cTFdoSmSieyivsj46j)NJGFy{l zM=0a(+Eax}xqr{BXFAZ%WL!!-W%eYcTU{%t_V%WeXEvgGE@rnLa`xJs1a(cTK71w<-Ov8 zr;cy;REX>1199EKb2OjDJ>X0GFusVG<-7R>>oHYh$mVWxL2llvwO_Oz6U%%uZ{w>u zXP?e`>{un14LA6T-gRz`Tjv|W9tP_%f6LIs&2ZcNUEzrUV-%hXC*YqX;i-60JQDN3 zw+&*mNXyn0?^TIps58)K? z@^VzbYz^M~-rEqVTJJNj-|H8h-WspYTMsjiic7*K*t`&8ArX#Eh+D5O-ka^s^l=-}TI@h4tV}h!Ya+xN#qBBr(T=#(LL2ZclQZ2x&ql zGwW`CW1Ju25xT)q&)4;~@h!Z8BDi^~(m-$r?69MlpW^PwD-frbS06b8|`nj9OtzxM3X9k8}>}ayt2IS#+x9qHfAjPv_yM9ZPhrzV_O2FWnW}m)pD7 zb!HyxoTF}AA5kmLT588=rsL@(-BfpG-xz*s&7dv4G##W1owa>NT(29#E!_nj>(`&K zq^ZJ?#$?b@%ZMqEcNj$9j4#=)K&^EG>6u|8<~TNYpp-4{9qBG$n@kl@(NNE^JDY1B?#bgs z#z0$`31fvkCw!zUhu7k5c%kvkl%N~pwepN~5?-QPwoG;e^#{69H>`Vs4dZlZ6_73XSl)XO85$7tN>rFHwg^WeYx1N*&uy$5~$cq+8oXVV7uJ4*Wc z`}mG3+4szT-wo4UHnA@kKQ(ROdUMTnvTKVQ=~y?<;p65e{K!1k72DCEThJY{JGez3 zWg~26&x~%WV@c;V9qZc7&-795!2G=9pf{Dx?j6CGWY2?-&bl+X$NCxlfvsPEV7k{i zY-}+P!|!&jsnpTbb?iLQE%%+^h40M|JbIva_T4vE>L*PVjvJ0?=FH*8oi-z$2V=W~ zUo=&iG1=G71AR~5eIZeQfR_l^^(XiP`#4_6jGCvJ*^VRjKErZR-7KqPT+EcYhPl-_ z&Cwl&Oa^}1o!QZYTRQUCX=l2A+*D_}XWH#6pq*SQKh<$WmvPV?QyGj{A+5w+O0 zY~Jh$8aJG^mNs()FQFTZMbuMW4)x4BBu7OjA!iKTj3?1}N0l5EG;PV#?VDRW=dP32 zc~doYpW39;=?liYbcuDJu9eHy30g(B=_cqZOA;NW9#V7Es&0xFuiwz8cEy@Ds3&wP zt-n5O9;2S?*Ys<;1-gei>8S0zp}W;LrW=AI*8IeH26?hXFX4NvpN{tQ;39pKeaO@S zR!Qy5Wt$+g2-b*?(=$wk

Y5BzvEp;Y#Rn(><=1t!IkZMebtP1pAC0XZyKke4A}$ zve?@UV(Mli^y&3VcGyy`Z@89YMRY^m1(2HwCR>l5E#T67M|2WbrMsnX=p4fj`c4S& ztZo*c@5$5cn(lU;T_;V|#uh%mpKB5j5THoMU&mU`HVrWS=mzcuAKQ z?h7a6qpfpVrv>JB%HOk0>J~a`omHHrD+kcxfw86UMxTb+GB)B)-4wHIonr18B{Sri zaf&+Wd&-<@UYkAP!lF^_C%%%O3V)flI&@bh&NKV=q}1!~23 ztgqEQvP?1?wzMvr@sX*~Sj6mdH*qI-N5~d3&5In2qjMTRV$Srqd8a3@D??^h+ym}G z=NK_$E`z)|f?DHD0x+y4VA^};h!q(q#oO2``U617x!iWojQJtttf6zWqtN^icbcDc zuCl9raUDyRNxOyLa6aaD^|&7X!HRRia-;H3L&ht?0b$9J>nHP;AyP1{14q=Hl7sFyliWTnhT6A`i8DE@W(NZ zn~ktuE|}wRXGfm7-n0S77E_J4^$qvb!mMUsr5(rHbccO97H0FHBjWG4&B0`jNoPpQk0i|W_a zuOm6?Zgn^EarIU8RU{Ys82!Jrvvw58QFzm5fi-;#TFbntR?+0YfZRn^kvfGveM!;e z8x&3cHHArCQkYbe!lYhTm{c?J_rRpStT3rog-KloCiPLIP2o@ug+mP}9Ll9|D7V6) zNQFaD3Ws784#g@QidQ(4pl~Qr;ZPohLwOYr?i1lWPKjh0&n^qnx`o91=v&UDf({|eZCO; zFEI%E37IjWuPU1SR}@YDt1@Fk+ZAQLLuO2QSFpXP|z0hcOye^!7!0hcWtnMRi|NRQF{?b^npP z!x+6I?=VJxEblNzR~6O$XPWbx67+$hsGll|`aj7`3O!SF_1`PH`g6@!H67?b%X|vM zG*>k)EJjw*v6o~O9ZQo{bnInKR1?M0WmbiKM6;fPVHq-q!oDPPD6B!&%(2&G%^bTV zvnQ-sW>46cW%h)%%IpcdEVC!fs3_-sz@EN?;fgB$rlN{}U1mnuTZ$fjOVPu>rRd?e z6+Qf~6g_-K(Zj#3=;5=99{wFg51&)?@V}P#J!A9ozGv)j6_xuJipu>^QMrGqsNCD~ zzGv)L=a04N*dyRpSuy8iZWWUtbE}vaWNsCcD08cr56j#tCJDGzN6d>dtBU!g%&KBu zky%wtxy-6!J}tAVm9s(^RQ=)F_|uPjjq*IYPFltRgJEgedD(~)rx!LF4%H|3~ZS|5VG*9h^J)!{5Mt>B;|oVt?nL?b5Y=`;=q; z>XB1x-*2n#$tU87OsC;;yuQx44CmH>W+3yjYM_6BABYSL>x&$;W8_-@)xtOS9rO0X zwrXf&aJCcKL>bYfN1Q{>*#V7x<8u5!?m+Qn)z=Pt9@&T7Q|@K=mV2L!Ba6vOvWe^` z{p2utgIpyy$!AnLIZc&Q4OBDLLy1(7ic%xgB(zz8Hiytg2laWVJx5NHCsaJtY@>R} zP4^*HNZn~GC7ux{t{#WVg&Wp;wT^g4l77KuaiqIwSI}|66}^hf<1no2?{-$XMjRJi zJjnWk?P2Et+|}8 z=xZmgbw`P#+;yk1{Bm1QzU!uI-r+Pvoi0N;`JBQBGO1?rAsjnx)vd$!A^GUlzOnBd zy?O-EQQ_Pm5{Y7By5~{*ntk-El>?asF)%i8`?Vcs?7*UZw#T5C9BWRnhJAM6hW!?C z8)9Xi$h~@Tpn&jqWH@dPX!QBcBF7SeIa?sI9t{llY!P$LQeu;MNbC?#>_czt_d*m7 zXoz};IFM~WCerjp14K`TGuc@^u<5?%-k_G;yK+>xkKJdahEkDfN<`I=BuPL--G_KO zp^~VJR4p|l`wtG9a#G{eO){4(pyo*oBI&6sm3#(28L2!nlYBy@LIlLQZ@C|lw<$eY z@4ib}+_PjN8KFw3Du~53YMpuj?V7;Ki{uJ*fhs3k$uWr0U9yb4Vju0vuupZ|>TPkX zJ09yQ`;MGB_A_T)ua~Io)L)Kwvd%5ng)0Q)MCL$~A!=A3Aay&JRn7UX8P}3abX1Wi zFsEI*l&+vNs69HFuBKz@I=bsyEe;^;}SpXbl>%e{N%nBY+BaBEBu z3v>kAf}O!oa3(k(Tp}L_T3RE6m$V%!T1#6Ww~AXmtz_#!E2EV`HV0n^*Mo=T8SvG}CA>a1XeD5Hxu980<_U-%L*?Qz8~ISa;Q2 zd=wwTL--6)iFe{XL_Cp32ob`x>wD}odYXv>qWH0g(0XQl3)O>!{)v`IB#miCZwlw(A`eM~<7#|~$o1Kt_dFj3b1lik`r9=av<2($^LJqN zeL=0f&s23G@3a?KfOiM1BuVw-Pr~u&C)yyJTKB{MH2fFz{?zY4tq_%cokG1F8ucd9 zsP~&R>P@Cm@3&~w%cD_mDvf&iH0n*GQSVI}^$KXzdy7WB=``xiq*1SkM!i`y>Q&LG zw}M8!pV6qdl19CcXw<8wQLlzZz11}8t)Wq`jz+z;H0pg!quwVp>ebV~9MmyB0Z7+K zBVB^=bH<-SFX^{Ten6pE=yM9iLO-QYEHp!*Sm@tTC>ENd!`&R?-oF4;}IM(h+~0j`;iN zh(AF`{QY#qpQI!H0XpIzq$B>nLr47E>4^XD(GmYw6lR6Ld;Kr3AHj2U9RCd+$Nw`r zj=xUF@&5xI$KPOwu@m7xr|>2GS9I+DjE>!ZLC5aT>Dc|>(6Rdq3SYwigTj}L6#89< zoAkR50{UHtTlBjQ=@h^m<#r{I&J+4jZ*sspU4@!%Ti zIR=!+xaHXe*LFOMfNJ!-@$CKD9QzVwMY19VG_M>87-0$dcQx!1z3}XL4m=l6)4T%h z#6#TJ;cfIfy#jBKx8J*>gS_j;LGL9JX&6FEaXy}2H)~u2&)M-D8RxugZ;997#k>Mz z&;WT?yjwT}iNrbHA>&+qPc8YV0NK|?>qL07pRWt)&hQP4X`Dt8#G~o(4_Hq$R@Hjh zZmk&2#dz8nzqlGB-XYt>F|N=>KO579@MX|vRnwtMQac~+sQUd~DDI!~Y~t+?*Ziyg z4S(~~O+24mGbNJ;7~90sT^iT46OY~mO6p6nt$i5^|3+7OsGgb+pG(AQhL zfc2A`7)Bl;3xSAgT_7(Y$F@`@H5Y-lKxbekFdx_oV8{aU7?%ZT`8em*s;6njwTHNO z1<1$g*`%an2zgh0hz_cnKq9qcK0BW7Ycckzq^d1nrOM#b`>ehW?4|X+ua}HMdrRN= z`p8&xQk!0XY@F1ING{fIQ))V}AyQ}vk=eBsScuH^amfPT2=MYhq#j+KIZz zM{nv&bW7BTf}FZYwF>PGumkgGuWirQtJrz?yi|#ZQ6faVCT28qh9qL1Sn|Cg4A?vxZtNgd2sR-iFt3|%dshge zmc&9>iQY_B>O|N&8jtOk=^n*mXP6pr-s2xY_Dw9mnAp^f8Dnk%`_Ff4i@nRC)zu@xxPFN6o2k9Gmxbm-XNq-W6;HoAC-X9r%WI7-yj! z=!NPqu!~1N+YT&wXT0r)d?@rrc%!|9x6M214OQ#B>ol$Qs=V*O zot7{*F7~oLYhb<$@R-_m&z@ljSXvU8kB7#=x>>Kh+Fk7iGxFG*=XK*^&w=jpQNg1E zU9`4T7wvz6H{;D{v40F>s+~HKpY*@NCv-gjY}u~=Z8g>y@+YH-_+wwVFBZA-^Zo7k zWB)#0iV^#COzh4kEx4wM8&b)njYAfr};p9doYqH4p#@Q2}GwuAzL zfGTi?Tb)kzK9ARlx3a(%@Ub2?9+P6sb;OUwMpv_voo0ih-ut7Jq3X~&}d z11Q(8A@w9ew&)<^Wc?P|VTvSs(Q&em9Q2F*Vse}uA?I{Ux)8QPYW+46Z(Jg~$we|~ zoJO|M@Wv3@-5S*z+xS`=ql?DclwG71JH#%v@3m8ClrKK`xTXN@1#?&&R0KCTI>e2Va0zZ-ZSJrri%P>mq~c zpltYhYYJM!5m~SzNCsbNI)V$;P|$;{*0Yff;;fd`x1;f;X{wOF0-r!*Yfk<7zz!$s zj~^A|C-{}$=zr|@JXK;CCh~EsMb#o-pMRpJPxrd|&_9L0!Uq~5yd7_TwvF-7T;#p~ zz5l8)^e9m$Rq^}`Up7{P#sUje<6V9Yrn8>lQ`9xwj2m&Aq3sdwi$_m!Qd#;mtqk&| z`4xTzmWRoaw}?Xf0`ces^*l7uUrL786ks|7ff>;8ho@*?-7GSI4(g4X4zOZ2Vs4Dk zzE$bUAPn+F)niy&wX`O-dQ?m5%~&VaQ_s^cgY`9{R{ZT4+=JTd7W~8LoN^U?qdYep zf>|r`C6*ncdOT7$3f7B!TmyV-I=+qXKVO?;EHnozT==bQSQhN{Gx zV$i43rq^VHK66!9&OF;-)%hdPvPq@&P%WF7n(XQbSF9`1rFV_Hrd@Abo31@q8lWj^ z(RJXua7VjZTz0q4726b|c)+BeE>;d`iVIGfe_jp{#>kTtZa9UKWcd{bLrjl?k#1q`(2YAlAw&fUij8|PX+cncoGcEgtBhOj3 z$|kv<(IhVmm4(zq(@<%)v&5mVe(C6N3_9L8_DVwf^XK`1&plV-a|QSubVs;O-ArI1 zJXeRS+hqs(>vS93n7h#(a^Y@~YlNa)l`f@A>z2CnTvj)`W!wGI-Sep1-R9!D3S5Z0 zo=$@3gDWzD;L=lgHhsBLLt1f0qdgY6B$$Y6nFKb%2rybwb_X-qe1w+q7rT z0iJyck^%3&44y`L_FUSt-=%kteFBHW*PwfJMyW#D!^&w7tEV$c85sGDdr%#tkWmQL zGm02R5W=|6D27nR1I7c$#85JnkeN}%sDdnvYG9I9+7~y_zPOp*4c1BT2J5CXQ2j2w zC#;9gK=m&fi;P9+_ZWY{_zUP24+Zi#jG_riQc*OO)66s{4FX|7R;qG zWx**_rYx98Wy*q6uYd3Q_hA0@?_d7`eB=7i_1}jD*ME5Zhj9A!(d(md#`VzkAHzb* zBf}ypn--jX{owimETud$oI`nJSVnnd_zvZH;atk|!gndp3+GXu7yg9uys(zesimiL zYCWZMY8mL9T6J_zEhC*%tDYUrj)v{jziGe@YIhXuq%xz!-)4W8-3Nb%-OnC?UF;vS ze+YZnKVts~_Oe6lAHz8NC+ta>V1Lg3946T_>{&R#o@39!ZS0@3e-8f+dx^aQx3gE- zzkvS*`_I^a4*xFu3-%XqFZ+^x3ID4EMM5$BGNCe|68>(2HbDpXB|J`e0)IcDKA|4| zL4qa00uLtG5^V6lPOvA~;UPNf*Y5)_*b5DOOGKU6>uRv#It?=gvqdtm(uap<@S7@0ue^`N74CSO$os%rvvrozM%BN)Eg**A(I-{~)S|CzcVxtuKI%9G^MB2okT6qL&P%9ovPC*f>!cG}M!iOxLd zOMp{`WP5i6vZIpYMx89YELzGdO8zXOY^Xv>-JQChJg?w=`HMnsIeCX!x>#6QsFm*) zk!8B7tLirAOu>RwCqGtXQ?j^UQWTT3EzfhlRv=P?Bi=a=#sx;?Tym~B*B#m5={=vR zo&5^rwjGQ?>Qu>MZ`&Q?;5pjz_G%2s<0Vxb$}0XHf&IOdZJ#Zldaz85wraL;P^PrM zl^XJUsWImtP%^(Ks7SlLmOVj@x#0bSS4CrGhMeU4(voAvqFA{+!o>(I;dq-?6H&ZHuzTOP`uDxWC4 zD2kC2&e1ZU9mSktu8avZWHbxQ2jt8uW09sBD}P042ympDP1g6mz9*2491>2z)ZT}1(I zcO09JBVZwr194bEwL8@ms&z`62*)(wznh{QX-$wr=!|wU9R-e4$A#12)IIPxRX|Sh zj$9}ESC+j9Y`O)U>2u(+Vz4sZgnt761T0LS!=Hmy>ZkBeAqM;h@E<^7@B+L5g~Lnm z5_AoI1HXYH;8l1P`e*PuybeXeoA4&|8}K%~4Ml}zhGjy($!uY^K+#M9!k}+3JxmW2 z!^D|5^jl1VNkFkol1W0p%?vVw5R=))Y=h#M?aX$F75B}!H0XMqFfJGRW?VsB0hA8b z%t|ODP93L)^5Zmd8t7hJO1D5-M zepgkv%oR{g;@qHW0nbsVR>m;2ge@EJUBOG8MTA} z6|}4Y3b(9THUZUa@c_zZnXpU&3bXVB%3x`V7dq8wHn z$#p_87Ln0!9ua!(NzF&XSGkOICF0Cvp-^v0y z!7RttNNbE$Y~@%hEHjo6Vzf5jMJOJ`2X~c1k6f3*zBg|^${ABmQ5>WrmL#E~U^ru| zoXjO_rj_^0u?oSdnK4w+CmT;RFo zxzk9=;=bX&Nfvj(9hsUh}G=)SZe zH+}k+v0>bjbn~D{W63KgEo{2Hu8qmD)$V5q%zfsOblg0C%X6Q&J8~yv-nmbhPtB*d z<1NvY%s&k;VhBRzZ>cetlR~3xG4~|rBtQ4&k#NjBZC-S=!x>{DUG!jxpyX zLm-I&TGCn6w5Iy4>DGKx7q)99EuB`g)n;k4IIRO#ne~O0V>z@ATc@nE)&=V_=mRtf zbjh)tS=8n=OGIIx`He9R+qGZ_zrNGruJ5!mtP0D#WhtW{=(ImWV0~W}-B4iNxUaKb zS+}ik8^RkZ?^*?+yS>Wv`=VQ==8>YQ^xT50J7*2n2D_qz(vadvaa6F;&>~zd<`*v% zY!_^skBauwbBn|o?A+A`Jr;4F$j`MT-R-^Gn*($yWHod*DDO@-Xd956?fh^c-Q$Kn zps7I%)-VDrk+7Bm+tXOP>bEQ_mUU3KFawfqR$5)v5Zlly|VHvWvQ{%@*u_0j7yB4=K((+EIp#Q}u?*n%GfZaY|w||`24a}GB zuYBLPu&>7I)B(AiO8817iZ{FcEqQW(OVj1qgZi6%{#mso<;HwB|M=gD;=2?9a6oh9=Fc3 z&Qo@{UAMZ#gEiKa9T5}RNIAN*&YMTJZ>Yub+zR0=8igjJ*=PZ(MeS${+FfogC(%iC z4&6Zyz%s-GRf^?RDA6M{7E@trOow5hPdw;T4Epqzo6&L5`VG1WS~7pt@&GNyL>M6w z@Ng-5lgOczK zNW7OaEm8Bi{8)Z2Zw7E81ib8G?*K1{C}LumB2CGFlkQ*fkzJuQc`8dWb@?(nS?Q_t zn7T|aF!yJW>6K{!%QHPjbFn;1rY5l|-(*A&OwAZ%BBgHLR)!84%bTY(glr&F$dqK1 z$K+OIEj(9^E>Zx`nesU>Cd@F~WT-bxYlM-vG%* z-0A{S9W!9XWY%^@PqE??n}jD_%5!sPkuH&10%e^5E80Q2vL-$~kn~Wtk`h%^k=iU` zikNp3kqrs&lL^Y!#9Ybfr?I@lTd&0SPmvsBYIEM!9XIvFlbQdtwIT8rl;PdqAey!3E73ue3(lTmN#H7F583D(Xx{rDUJu+WYgoy1=_ZQ5v*V#Y#rOeUT5N14>p7?rN$IZ zf%zy9AAQm#22T_vrNr{nL?Sf2jG2l5t?ls+Q`V4ueqzdI%Cxve+yXe7Po1Ug^PW3r zHY+`4O}Ydu?m{BqPGpf{q^=#g;x32}vR-73@uIV4C5@;#2XY&?9;$?n_aoEQ-0sWYH8qx0pHLDB)$WrAx@Gs!|EK zuW*~Ip=74$MP7)HxEqpD$r*L+j_z(+-fpTUWlqwFOo)q9yE0o+XC(&Fq3AF*9U05l zX9l?w89Lrd>M(cq)|I5>Rt0whd7rwRiSvab6^|)y;SC`JU{3EyoZQF2&YfB3sk6x2 z)EMqbUHc~kSsUU4$tC|nIwVo24rg9S=5M{Yp$5lS;z|)acOmO-s)sw9!vGRb%303w z@Oy<~k%%8IVpB6f>dqbh4F zHF`j2j{EoFzxc2d;=@jefATvaek~s(n@iWP#Zme_{Q(rlNy?Z4pA5-LMhIeXVju27 zVftmsrG6Wnn-=MzFv+CkC|dy9Pm1M$8kW4(0~{skmnK1B32!Cq`YDL<5jX7&T z(}(`vtomy2Lv$qbD}?vY+rlo(k3Kf=j}u33Mx+pubNw9WQomCg!NK$g`qPhxo<=`q zKjkF^tMq~w3Gr3MpLuRX-hQp`*7xfB1bqFteo?>4-Q-|A1qTDIp{If>{ZpwB7x14( z*G_Vrf@Cp|FBQw{(mr~ex%;$VLJAA&S|rJd-C1dM)8eHJSz7N+W}4j?Etoa3KOQo? zH}DN7H)q7Wx+p>W)0bI`nY)SO;*x|TaY>mXv0KXjBuY{t;?#BvD(X(_IzDQCx{^7Q z5X*7iY*cP$x;bs)*IC?p8NZ(s!52s>1e}bqdSi+%QOK9xh|J&Bk>xud?F%C7W6F|0 zi5HdDbNE=jSj6E(WS*tP3OMzedJkVJim6wKN>d^@>W|Md_#Yb*rqi-3jtf@|k!hPB zC38wL(rYenOl8k;Mh&bCv&4KGON`~cxG|C1Yv2nH3=C})KjOC9kZefj8?ximDvj!! z^B=DmWJVn~>Z9hg;N7!Q>5b*QW`d%l~sAeC}{pD z*|2fjnYm?{Qt|J+;*1JpwUY@^oJQ_=;ssx798DC8^X|UO9;qD6Y)kCT+RF-0Sx?g% zcSSKbhi;CF+l-et*K4Qi8CjJA(y%Ii$sITBgVD=V&gvsYGUK|i(s(8o@zpowg5=DZ zkCzf+GhQXdC^zfJ()u#eKbGH^Z=oq2eb#o6%BnVIJ~A+i+^%>rTwSq@|&36Y)1 zCPsGIOfY8&TM;QD0wN+sq=*zLQba_glww|sNGU2JMMR{Clp-pnij*Qn3W{ivB4YB* z{O;J=UhVs}e|*=sm2;gRckY>Io_S`y=bky|q%5yInRzUnoHePSd)B7Zb*T-brSZk_ zXlldwm@Wt6E0eawmnH9r4vQ8}SDa1RZ~K5BE{)X9x|BK0_JsVdYf`tk9@DBQC*Epm(WIg|$zzhor0lOa zZ?C&lG^t{B#oiI)BaKp*7VJwM)ogdgx&dc1&Lxk@tPbx?`K%y+z}alS;&`NaUVdgm z#r6S51{|?HGCgf@+M(=lVL|fcms|m z?2R4IS`lxaIx~MtEH!Ipq{Oa7^?M~#+i=69nK5`*q?^%!@o)?~%(>`Zy`KGM-xh1xL zmSoM(Y2PfGUy%8BQDVyMaB0%~aQpny@YBg-3aT>eCpD~WkzG}3M}@X&87m787Ay-V z7n~07ten#JP)7gMy_K_*=ccYpIucGuHJLNArdBqoSlujIm{2gi;(WM$<-p1&MRO|K zR2--rnYE&_G-qUZV5iA>TazcZJCRYG-9IfcGokJA^6`~jE5{WSr<|;umv=CKVYemy zqN%xAhr-MAswyXDU(P!k9+)&gqpD>%<=ve2BL z6`NVyCUsG4SJ9S8StJ@em0dU7CUsQX?fJtpHZ7s^K@U&!g2axt82 z>tMcnr=E|_OWtSy-cY%)a!J~u+_Ta8rHA8X*@40)MN4zfN1Nm=j?PPYH?Mi_uIRW- z6`c{C7hN7*(yme3`Lfw9PYf-IZfv#Q_T-h(ZJkb}9H2^Wo#{+oxE>wN!lh`qnmPeR#u0X z$2Y_`4~n-v9-GthLc8>~o7x?YA1dO5&1#Qn}Os?FM zec6t@J8x={@@}fJgxwaHhf_sr~B`B_<$@~M^EhGr!%&Y5W2&GX^X@;Ry7Gdd^D z$?BMzo0^-J7#kn6Oj;4^Yv+i?ZFi-uk8F-?ZC1BnYu?cg%j5m~Er^^>Jrr&qJJ2>U z_fX2(tnsl;kui}mvAvNAk;$>M#mQ~AbZwrpEuJ51+G>6I%#xDm#oXh`6T)q>tJ+P7 zRK+G0naIJ&;hdf2Gus_*d!XInW^*G)BPVj+jtt4@leH8?h_jk?~`3zlooB0y*0Zk-aWP=wluanZG5;iby4b~q_gdowd<2~Bzr^b zQiraQ9qFHSIF?-1tWj4#yKdV0+~b8UT3*Q8o!&JbNIG7S-m-tenzYo|rj{qt)@Lov zncZwntYfTq;kM*}o%_~@3$oKAos)J&`p1e2`b0LQjLaRCHzt-AI~187_v4M?;b!T! zb_eFp$vzrum{pS7H(DK;mAOBbmUh17g_akbmnC@gKc8{<&u1L|^BIT#Yd+)fH~!fx zcK`S5il#>kivqgo;reat|ElSM`stQ?GvBtlUD*>dciO+UWgfJDOnn7VB*C)n;w;Vr zi@W>c?(Vj@ySux)yAKYF+u|_j;_mM5&ft%G0uBE3BRSTeS{hdw@1JLhdyJJ)+d{4he4lZF z*)~9@Ps*zj|EDLA|4vrz-g{9iM$=X8fz&6_Wbx{&e1&gq-4$xnXUFxz7VNA0=BG69 zQ>nB3k&nee#VAF6V}SUPZR0~=?1OCpXih$P&_v`|?@QnKsi2@^sXsFy9@4%f{i>I& zAr7bd_=y;qeNm~jtM*D@^l4|J&y)YEU;M9b6j)f?y;j<=24qjR%k*8LwcQB&MCSfJ z-R5{Ch&u_f&w9Dx;Xssr_brzbc{=m%o9>rNz5BMoiLBV?8F0phJe_;@jql5teD|&E zOJ(1E<8UCzM?M0x+vgQL`Fxrp{0$#lAR%CXbT4SJztvT?{Dkld(0zUC{Gw{)zdW}O z`D|=~*SFY``H%wLuC^qr`;vBfHa)$$Z++_O0iR<&r8{fy?&AR`dH?d>W4+%xp0jQc zJ*$1c$V0k^ygSy%uCAtTo4wcG#r?4s-^LU zuXSDmsrt=ZZD5GJAwD)?w||PQ!ePF;dR}Zdt!)|3T{5^+Hhuqq{LmltUANxiXk8m* zxINXkiiXW@XLz)y>hp>IL!T60UFCmH6d$pssDdtFHZeTUq`7<@WJrb@t7#$(5*k zl{-S;7Jsm|dGL8*QToe-{$c9Z9(4SWd5ZScj1#~5yF(gSg8ayiJ}+-9VOrFqvIyYX3X@YmMZ za@dvW_>MQW^CxDR-NlpDQ>@fwUNQ_1H0}1!dpQH=i~|j4Fu#yemRm=U<>t)8VipNy zc%;DBZQ0#UsHgs?ne-k+hx(#Z%aT!A4x4%o^hmr1>J{70T=Bz49??*N(hv^!^|JlBC_(7I?TADBq>(J0Kg%(T`LI`xr8E?X`?C z8TQwzD_|ZFG$wf$emqj`2OAeCKsxyYU`I|SaLT2DB&8xX!V60nU>sUwOMCg}a?i6H zuBy2z40DH2mk0S{bL14xgZ@Qf7`MchOyG+2idbDC|3r2)C4Mr9iId)<+HNB|vrwKO-k|>m z`t0pP%MRJ@H620M!n}x`FjZ01Zd+&N3__p^MDX@|S_{_-i^X6M55Ao9enJs91%g*v zx4U|Ie+Up(Kd{zm0e_ejUj#R#cgp5XAi2-6|h6eh9o1awV0^msDsGJda3}_YH zYG`7F2YL#`wZSq53_AZdrW_XRKwnj}s0dG$q$1x@(@;N&Ufj1~1_(!!Z|S_)@Dx5c z(2kxgehLBRC2_rR5p8?&#AHV$q_CCy+$it|@VFzM=T9Jp!y&Gq))5YFMJ8=OU0?Kx zkBDl?KGLt}wj0xUL;WzF+*J@Te9+!06>oiV=J`X9rms6U)e|01q;fAwpsXf}#LBn6 zXuqYcS?`E0<`nt$-1lb!op8;;okTd(D~ox_)#LjKd)Rbj@|fp=91!oiidt+qS5o`o zhREW<8`m=dP4D9y%#%x1f3uKh;^Dq3tmG6;*7&WBlFp=~CTq$vm6rdBHr8`-`98EOj;p?Trn+77=Hvy|cxlYMRHW5gMBV3S~VC}@nL&mH%ugywqLTUWu& zw2i->CvEe_?#u+ufsfIZIjc$#dzlH^3vMUT^4*K|8jY@4%~z)=XBAOC?)n>3y#hWlq@7V)oWuOC&pDQSI5uGma_?W!&4S{##bV>W|-01?2OKta93y_NbOuW zVZ2DU;LOI~(#8Hcs812M-}UK5%XOtiW$W~+u{ItU@R~QSxWFr;-NtwL_ENvVcyrD} z<=?@sB#>rK`L}N&x_zyQi4P2WOf%JKh@dWAyCd@}11D`<$0=3DLPxSZiBMrxqok2+ zf_u~f^;u`UiBJYEVbNM^8mE4M_uNX(f2P6e&qeUH`!#2k$lGG^wKWxWZ76TiPk7DM zam5JRBLDuTEn!l($)jU;!72;-v)pvpe*`R7Jxa${Atbe7_wI6xi&JPS=dx<7X5I5p zCceekbN5QEIh-1%Vf9M>Q8Cq(lSIb#XO6>&#KtMqLjJJNbSBm28bg+&YMS1(nLQ zZZlzOLpV^|(Sf2erD+&$4QZ(qv_#~t131z%d%qbvG5o{;*k}l6u>BRIl6>+cxrT~W z?)8~hL@opkpN6j6_og`0ki^i1uN3JIM(hxVJXUt?K+H z#caxqvlZA2t}#yep}3a)%Xg=ECUO1RY2`&O$nB8xZ>TIPAkF{8lrPDUC=?iTrV+|1jFbm!i0Z@ZTGt z+JMy~u$@75NT-4(3x9e)h_E8}l&nXq2H3_1XN2bZ{b<;~a0BVG8l_uhe5)l56(253 z9BCZHVQuL10+(Y1ah=1+NUGS(znne>Xft+zrV1>-jl>tSqMzYg$08JN;&(%+?ATD9 zF<0s5g3s-+9czYS119Sh6^$;8zn?uXuOSmpg&4(lEBK5q`oPC(JTy3U63P?bL+=}2 zYh{Fe!0mJ7547PbH{wKVseByrQ&mRllRSmYhwf0b+KwkH$$Oz$n~>kZIvYi}xcKMu-Dr5`XIV!V>$j{ID#yP8IeiXZ`uz4%^Avuk6l zdXIm1(?id{AL!$tx3YSp6zPe$lwvmF;PrQ&RG5+Vz*^d@G17wjaEr>8SY12?^TbEp z`@``?V!RLGghvz<4<+7OXz-(;gt2ilUriVWrEdoO`;L#sGNy+2Sdu&%7JEJ|uM@Ct z`hPWAn-Mw-&=&pNmsSGLBi=JqyZU|QYV0IeGlq<#`8poP9FG?;j!)ng9ZtWa@(SRE z@>GGC`GxG2(wV4s&oIDPabvRTcdDE)YbMKXWb8vhAIP~r^M<|4^eFmC{G84%e)dpj zfau3d>TCz*braRH*EYZ*`Q19xmZLx)${&gvdjnqCn)~}Aaf)AYo#41^*-dHD)i6x} z*YI?^j}0-Q_w11Ri9PXDbYw7x9%ZuAz(v8b(#C924xjWmkH{$0wfHjjVXuvH_JNzp zc*~4r{G^zk;@%qiz$7+mYh2s%EL!yMPJhHU_{MDHU`Dkqu!}~>yuC>FWRY_e9_Kc^ zwDvB9e%{JDu2c`$8$GZ2^0o+oFHAq&^HKbzaVepCV3_WW6^DrWo(PYBJx~Xg|+Ml6C zG;Uo(L*}$EoBO4Yb)xU#teH&HEzOL$OHbwO9jGE&hY&V|EzW z6f?u7MpRPn4N`vpwvKUq?T}t-cBGTnm>mK=^gN|WiN_3bUrp<}@MES; zoI|>asLi4QpT(KOML}%(?wB%K($YP?IDkbM_o;%FXWXNj_)6N0o$mNf+;_APJD8Qt zjZnQYRl@${>!)jc;n&`srUto{aUvJ&EaZG083N(*I2vde)b<~3Xt%#L#NIFyC`r1Z z^@;N&c4T<3R0dX{()$Sen?e?ewiMndhPOogIkMDe_WOlB5GF(TtZSQS+3q-RJ#OpN zEa(UFPXazrm7|FmL);xPjk3^}(dBnmpSJx#Trap%y+wF9g;(*iR;O^vYY`t}yDcST z(TU!U7gMlZF=h-lv3^)S2#5ziceOf5oONLGLSQ0As-LI&>2+Rz7MR1nD;`g>FQZP4 z2n1Pg+ei8n-Xci?d`BnJlPH1R_(^DufXdaP%=l8=tZYLl8&L~*6VFoPa^37zOk%%5lKmK;Ybv`IThhw$B2W3Q#n83+1RMWJV_W5|+s&{LsPbr%Le;!@Dg za!ZQhpYKPgT>?eAV|#ox2t90&)(s(USQ1@sD)2(e8gu=EU9=%y-9P84)Rbf`~ky zfI?1rI@JN|fh#&)fW{Ul8OP%Im~Oa$cj3m^mkH~q*E*f z90`Gx6oF8}e4)h!3pbUIcioedv+1vELZV(UkiiL@c`@A&Zlbp2=k#0JTa`pVoIIf@ zDbJzzw0eOi89gMT=^r+(=OA-|;8GNYkJw}P{I(x@5ZWkrR0UUkTRrZusk4${5C#M` zQh4>aJjCuU2zugna+?%C8d=;KMSxIl7_(-@u}y*GvuUkv=qH`fEq!tc&1HTFmX)VM zw+g=H^;LTmBS`2465Eu*>O5&RwpYZ5Tt-H;6(|KqGxy~OWMd4&yt({x+!{KoA}mQ> zy#Ja6Dy5ol`M%h+Juq{G#_5)^QELM}_JWTIqj#$EmHQ`@62qv*eK4ceD z+q>o|4v1)@ql?+$XM@q{!77phC~BY!A=Q$O-<_2* zqK{Ff4Gd1pl_a+XiAQDP#SR>a(F-Y-Fy4yNt^@jgf0QhtieOyiM4n!fSsSQss7Av! z9uAAz+$(W;51#Uw&H2tWg`ThhHxH(@{2?_@=4zgC_SOM znFf5kZ)=TXi(9!ezkRI%du@QdxNx11VExva7Vhl zNQ-{CUBIsHRUf{LM|)BhFwOFu!UgI6&{LpLLgSPNe_}Vw5bQ`r_jq)t5>OLofV}Ev zZn>kQI+(0R5`Pd^M8@_gdzNUXyZn7?(v>&YqsMPD>H|#cL_ZwN5(WrTe4;ujUW;U28#HC#Z# zplN|QZ%7Z}N=&59(s=d9)nU2yJZT_EI4^u##sri<1h%@~R5Jj}&j6D4?gT%0*u!*n zoi7%8D|PKdt87)B@9{^<&einEKK0wtW#N|^Z5Wp!bYI}9GwXS0`csaMv&g}Yu-#sR zvmX6NN@}NiJY%N577lmV)_gFWPGk4aS)E@uRP7|u?tQqeA)|B$G8eT-vIGHmjoHJ3 z{)I~wK-#j^M~~>!+)(IOEO5JKlp?p%BJ-a`SfAzissifE_dP*@dwhtsi#ZG^C%U0+ z>rpd6`%vzI8V4k9EC24FIji;KjYwsB=Nnb*BY`c^52PG|Ki@g?DrBnTqb=nN2+?c~ zy<&^#IViVcI+35zZ5el+3iZNKg;yfblYa%%|tVEf8 zbH@wt;d7E%){FpVyqfi*9>J-vgtZ4-d5Nlzzuq zE;Mkpa|oL%y~!+j#KuX(Nx#i2FAo4eYPFWj&RVL@8EL|vmrS#k*|bvk;!e%zqN@LK zxg6Tj9p{sktYo-Lw%E*&zQ!R{-CYUwU|C0gu@X8NhTM0sfA^geLN13n2mSnDF(%II zi?gl+(`Q1-mHdEYnJL3^1?RArZn^1D+Pqsy-&*Q9+yVQI;Vp0;{luhSe&0uJB9C5W zSQW={bpi53Zar}pSY!70py}6ylCP?EDgZUwh=% zMrE$QC`0VUdC=M_@J~uObFgv}`fzJ~mNA9l@ZzrGHtH9YAN zrcHyf_62ssAYGB_#H}Dko-l;8?;S_PZywMBR+5wbI)~#^rsodwY9ObNkPR>v?cpd( z_A~I#cYbeY*J}<6lx`$%K{F^O^#mMqk8wHS`>(QnQ@v|FtG&iU#DTiqD3mwC=bV2I z2298+uXR0~lwq^i&0|ltkgz{}-MB$!RYo3GycH%si_&VVL9zU)LysNr} zb1tt5g{Qce`jZVbL^AUl7C}YhMi$NR8+R{BB5Jh+9hSs5wN9g}_j{Mh0H)pH$qkZG zxb8qh-OL^?yia;ST(UfI=M5ZkyjB39sjV-$NK5DjH{#0Z=hqFZT5Yio(hWq$jRPEu z8H~x|1M_|NA1=mK5X@7<>2PVC3HFTyW@||Ba=i!C|i@f`hAK*E`2gf2kLD8*BINp(QF*|I7WGzjH%67bLo%eD5onvON>!xngmy63S$NGvg zrKz#3VD~cT?R%_5KQLfwRe&t7J3&EV`iGBjmp2t!r|L^eg6iW_Lf*f*TQt_s&_nZU z5!T%^Z=A_M5O?Qgab^JYXFPT!fkIIqTH*Bvwh#Y@u>-NAT02jcWk_7`y>)Qeqzl?? zaPekYVH?^e{0+$RRBNt#e(c)!b{!&G#_eF>E;4Vl-??xrOyH-H`9}uoHmf>@$}I?D zB0@{v=?rQgZWzmy3QVqsw^LS&*Lz=WkNnr1MakJ8>eVv8EUp)*%Lb@jWF~hs82`9u zC?5b08S;y_MGeX1P;qeWATNUDNGav{plTI7!s{5cetlc2quwKcA2sQg@)+9~4Xa~Z zb)Y*cXW*0Ba>&MR%5C$-!@@!07|Jrs; z8TyLtdg8uWd3@J)ij@<2=*S6tEZ05L9c#)t_{IFCe;C zF!5dKE17lAg*)W(6>Qzmj#0xJMpAODg7OE9R5!iOpi14+p-R~c8LtRc8RSIx@PF=; zk?wI5^N65cni%tty}rNO&p5+Pnyar;A`=h@`18B>3Y^sYUyO|G|Fkf%?_xOV1S@k| zpbgm#f4VWWk~e!c2D(&4YO9R$ZwUss&lr0W@DjWYZ++E$I}x@vf$c5i_LR?fj6-+h=!O{Bb_y&ZI6 zo}Sm~6k>h&k!tDyk?Ft06BKojh;6F8p}C3pwBJ>A9)9`YKZahxQcspclhfn z`+C6FtC0k;4hCl$pgW$|b5+zWxv96O=k#HS{1L@x!S7hTn%t=E%lr zlbu+yxSPP9Pff0i0Mn~f%$vw7YT!MZ$zFRYc1Oj0P+sbUVLMynj9;=aLomN_Ex-H| z2x%$f^(%$l4b4khJQ0STz8iKE+_Iz{{W0hVjV}$pAMUKeLh4txo8_rwvcH&TmZQb7 zyVmI?6IRInXblX$KUn&YJDd#Uav|5BnSc^)5zkZngp|}{YM6;e3(r}FM9Y>_2$PxL z{l+y5mhwy1TBT+W#aj|Uu;!4z+!GNy?}78cew8Y55dhl0rbXbPG3!dgO>a@H=e=fp z{lB7nVb{n{1RO2NuNcd25iaANv8NK^!CTho=Xv7>!Si z7iu~;>JZM2Q|CphVa+Te+Ms-&(hO%s0&@eOmm)@U?+kJ|C3@Qz{&%IwiE9|Ae?BIh z^@n3e1sW0-mMz4s*dKUy0RsEw*l73S051c2XU>27FNYW@<}8w*w-y3Xo5jU?#f*Og zVvMD|RDQ|^yKa-l37yIdAm@pR@CU~b2w`_%c?SKxBYLNzdNx6tjad`A#63sE6uJHB z^Ftch3&RiTskfR}9b%s;B_O7LzV(TRb=;J|S4iq}LXaO4yNT5?C?#?=XdDaIp$M-sIOI;!pciN4E=|5vG&D9Ji=yza?ePAZ2yfPjErYT`9yY%wGM#{F32ef$wD!whjl2*Q0gaA zd_J-;>Ua065BICnt~?ERm2w2tc+(S;H*-W(aypf7+sK{~QhW}f2;+J`^Gy|0Mq#c( z2HmYL0`Yb^Jvs5d=LK@~m0nAGP!2?wJaK!0ezWn534H-0Wv-_zfSyUh_X2Kg&Xzo2u3)^YIIu<%*c@9%L3cwCh9dVuB{UxNs!N4)W|htHNQK`ASFYpR z;thpbRDAc%&B&==f>;Ucg7c*@U)rlnn#R7?<7$(GyM3m6rURsKu~!VejL`<>;68Dw zTo?*?__PlsE1+*i=k3c_?ZAPoXZ^7+Nhx8LpR;y+R)yq?pl%eQxPsC9+7&7P2tDfn zeB#4%*i13bZOMQhQ-0DTQwMt4mKZq(-<@#ihka0!oNg^baYDJLxX+R{hf$5&@Ys74o!N-h3qiPq2D(#@3HoD7hKXjxvy1<@<#O?E( z=lKC{$nhbY3Rw^39ZoHI7DM9JDkl*|LzMcz?U#YgelXfdV$Mm{@%;+Zus8V?D#I{4 z5FjuQw+Sw7g-N)yxula+p~)XG`h`uMeX47bjCsSbilkYf+h<}& zGw+geSnpG!MC%j}jKBJ8XU%?Fx}}WwHVZ{>b_lPcFn}NRVmE@#s;7T&fr*LQ}8#QGkLE^akHs&+9%N{K5wsrR6(x0r@ zQ{cn@Fz-o)MU^`%~sX7OGg5}FZU;N z@!cJnT*iXscP9#nCF&3sVN=w0d0$2L*Bg|_kq%HTB>g!m7|p=!qL|Zm8gtvQGQDrG zvEIBxh@;qO-ZwIJK_+8-#{=Eniay@adcpQL*kq3^KH6_+%fa5nQD}c|VJ!S0+r%t* z>@F-`))q@F80p4)j-sh9bEtu3f-P{_jW^h@1R(h|u)LU2bdF3&$L4WfYzazKao}Y6 z1oI+W3FJv!gSiT<75AuYeY2>J^y4etXn-_%L`p9rr z95<&I%rM`aP5b33OVkTFrcP^*^tp*J>zt3Sou?ciKqw+fC|7`DUh_gn82w0DJwj1^ z`2j&mCv_D}xXGQ>F8eVuKK+61RUYNv(#cUJap4>Fho2v+?F3QRm?WYRy|QSoDf4b& z1m75n`;8%_7OHO|2@<^sf_Rk0UxY*~YNJ`^0psEb*)f#VL=t{_5gYL+swM02AO?|ni73u?gnB~i!V_lEfZ~Yq zF%+02k_HA5R>>$(I|36ib>TU)XjpMX(-;b263H%uNGCs(qdWHQCG$XRaRhJ-Wk^8? zK`KhJ6Cu$9dsj+%V9zvyMhZ1Gop?7d2sRlV)eC!9R(asWG=g0UwK|>nkW6e`FKjp& zJ<%I`S3!B;$}~b)3iV$)aa4X#L<%~pFE$ArO@zD@YHkt7g%rJ~;iVw0#Ui+(qYP?tib%^C$ixTi zJilR!0;LEjjYmBAx5SgBB9i!%$4Tu9dHKCuP^syjFNa~V1xojMP#f@Y1}<#azbA0c zNZQ}3n@~}ckq)Enhl>(Z-!7Rjdxr5!m?dgSnkN%UIoSFR8{E?0;?E|J!|0Q`ZJwfx z5EIO%`^5SLqbJxW8!3xZIB0hZOM^VylVm9yM-)t|k&H+TlgPMFByLBHO|Y<~fC_%1 z%#sY$+XHR>DIlynW4!RY!6|uF_YPHH7(K|4<$IO7X4$TAF19;jtq8cXC zQ`w^sn0GU7)qm9U3eJM{3*G{%LU7BOcO>Fla*tlmM~_esaZle-#aNkX%(pn`xLgL% zUSM*-|C=6*N4&{ASUSYgH;!){5G_+4W~(VNx;@a_U=mtHtV*{Hn_n?jOLuV zNzJz!C_Binv;TGp7~xB3!(4{7fo=xp2{aElx>=l$eA-H+@i~Y{1bw2B@7{cnkMA~e z1=pJKai&g~cb1r%RrGv0n2zeNAJZqfeMJ$QFOEl?(WoeYA?W6V>&P3?||>9!|$ zb}vo|>4vP(hwJX$dXX7{Zb)WLLUIaRCvvw;LMrY**5D4EEOm9k-{_sfw)G+O&h?vQ z&derX5oC{vHC0)*0Xpjrxz&-Lr~Od$qJEaHXVxoMKBOH>l^eAyk>V=XQMRLbLNpWU znI229r(%!c_>FytCK^kud2(a+%iTN9#hc;$QRgkx9Ei*mL&SI7Z|+w$uCegYCQ-{c ze0Nmk#fu9%ce&G(D{V_7W~NWFjFcU6r?+kK-^rZ9_m)w&zLiq9?pED=XzSUy;A&aB zOQ?CkNcno#D=q!rKDz_$cD}94m$&5syQ#7ce$23TK7})_t%yzCHGy4|rK2m(j;Ld=W!(*69n-Be@8*QJPC8US2p>ki7R^PIv0J?}zq zU2P4#O1|x4_tqvWrH_^D(jSmz0$Q5-0UN7AB76eaEJ;FVmVD}|{A*Vw-Ulmsy;36y zi(~3#wY%&=k=eptIHXZa6CY%!=DTsl9R6;1jUsF-qN~FgPVp7%VRU+059gHQjcd*U zdusr9&Zy1iJA8$Z+S^_Cg4?EekmU!Y0HL0sm&R*GSsCa~tT>#_&uLE~7Z2I`>aOv9 zXq}m<(3hlJa<)T>j^RZ{7{P ziEo@MEN5@n^3y2J9C>v)fc*o`)+eJ8lgmFk$9LF}ESaEasC8+sg=CkX%onXNUmXFU z3(&vyOxje5@Z=G^XGZ;)B4R|lx!SUQi|5{Qce4L;Ht+i8@?QE#@p9JfkG3bU$HDZ` zRO@qG3Ct)TTfqL@ApNxDBO$3)IR}Zw-|z9wdZK36HM=`Khza+*i$#y z)lvWJm%GUJyZx8kdAILKm4=wAZLS9sWX&M`ai8zBv&=x`%k8sd(ARU=7u1IZ5)?+Ht@5!=*nERO za^4U(r2M7Ww+TGa>=OLLI`$@7+58}${%Jd?bYi#!gJU)hCJ~1&jQ7#v| z6amfERD=2a_rx?aYsH>AW7qD_jF*9T#)s%^eH;{bAS;!fx25Fz-E3X!P6?`Gi%ik3 zTKZs)Ozx{?PIrOseXTA*dU{%`a82*M_g9ZvbhZw6zv?zk0n0yp24AK1%~@{8FM5lJ z4z2dz>$9`z1MhUY6n17OW4rQc445tT_ZW^4GU}}~3s}146!Va@%E?wyd^Lv^UCZwC z{8ohQ(igRl=3A;T<-sc!H`j?jt&$FCMoZ8Pj+Z4VC`u0#*zWo`Yu$DEh(%Jj0FJsl6TS32Yy>NZ}`^a*Es8(P* z=Qmjz)q>%zwpM;&GWXZZ7TH_=RkUN&uWDo3uC8rW*PSieYP8n_GooW};ow zQ~v3M-?SYtjW!zpAn`yf-Sm+KRLU&3pXH!!eBgNCNNs!3e$gJ~?b6#Snm_HHeWDSV zc{+PdJ$exFN^VfJU(8O$yf=PfGOgb%&HJr4uW;6_mUc#XOh`j}FL2_o{2|_5yQRM9 z1>e=5qTWTm`NK50Wt>OebAo5{Rm3#ey}`YqVdXXE3Ht!?iuH=sG}^u0y}e;!X`3hC z290BWlyP9=Z-dObG;KHf)6A%yBU*JQ{bhct*B12_)OjuGug0m4_ zGV(tSilZ+O-qhO=V=~)dv)|TZ&FmL`0gZ`$ggi!$bg(oszUWs|)MjaQVati*%-UbM zdv;sQE;$}igzJYangC40V zE*|lPNX@~ma36vjj;~D=#iHY+w|wU>0F=9klt@oeAfp+|b6^IeNoNM*ez*}Z;rkZ| zBcFu<=y##AKfi!_7s^Y22w#qJ7y3KuS-4`rhj{8smqtGe`_X;bzkJ3q?n39kY(Kx? zM{@|TNp=YDsWgtMO!=jy#xZXjz5=v;1;Aj^pYBhA1YY}1qLsn-zeWtc>l@$C&YOoJ zH@KTEnO}|Zr}&a}(7&2IuAIZKs5jJ_Z;bSsAOIM+D0T8CTbTl-oUTW4DTwf48JwJt4N*swR_@Wh(O9L1)` zsK;`|w8Xf_Jjo8ESTbklP;8(xlgqZ10!sr+eN+O=hgnBi2U#asXITIJHmzWvV50v; z&rZ)$&0Ni5%WTVX!Mc$Bn2D4bm$52aCU+!@DW@*GB*!7wBIhoLJPzGBW5dxg0p>iu zKDWNc+rzueJI_0=W5d9do=KT8Hj#RGa=3E%d?J3P`zD0H``U1HVrfcgOlgj3k!jRv zG%`)5ai)Q$^`@DosiwK6$)?$+=_S2RJ+mg3^)4Fn+LM|LS~VIq+ICuY+Lu}c8i;B| z3wEyB5Q`a2Wlb%v0j_nfe_S(~mbHxN8|CZLv}vp7svE2Cs-vr9s%fjNt8uHft9Yt9 zs=cdY7tO3Io94U*y*9l@ycWFry~ezTyr#r$GT9p^Eaz;AwfoYn&#ZAR##%6zqil+7 zjBWO8&}>y~RGY2V@YcZBjMkXfXx6ybDAw550_+E_0hcx{?3-Iw6jrBJ8J8K?YF7lB{LQ+p{kMN{#vpa{U%+x2#R1}gae;9@ z5`jg-OruPLOp{DAO#hS){g?#8tsk%NuJ5d0te@Gqu=A|tQ0AKFex*z0F6DCMw&c3!BI`h>&zQQO_~^>EI_diA zLOlDG_NeaTOgu5}J=gs0W1C=`W1DK4Rz1?X)Y@C!UOivETs^RIz;|G4)6&VUX{?E^ zsjN}ZZP9htMgARZ_4}ei8}ScQQ`xFEem;^~ct!ubQIjaW3gw>zs}k)X{PIVsw&8B0rbe*li=nD?4e47HbfNFnY zr^T&}n{HOgs)e&lC*}_=qyL(&;@B27i>(!0EIwL=|0UJRc2njl`$|Z(%(Kd~%(H&l z7|%UuZr`{};MtjUeR4&1IqHC47rRutWV&>`jJ%}2q;YTZ26!iWXL`qa7kOuUmvwA* zP5BP{4s0CqpYWdnw}I!t!;ks*%va|pU*tOz&nkk2y*p7}DFyo}K?PvhG)o`LD$63v zCd-J*PUfI`nI}LDUl9# zL*C)E9YpTHr+!t7ya<1f`eNZ*#jlrFtbRK8u=3*LTMyLHFS4KJI9q$r^$PY2=mrC| z*XAuS9;ZK8zm&b0zZ|{zGTxbaR_iUC-HCTf*)!VvUlCoQd>~Oh#_gQ>wY7}$%<)Wh zOlu$MUh3{`Y;T-zTy7lLUK3oidun&$Z_aHxZ$@v@Zk}ysZ?0{6^B<1guK3#N_h8;V z+&?@bUL!stJ|Ny6c;kD+O|H1e0b=`f-{yO`;LSQ@o+55%&6$W(o4)sCuN%g7m3F+U~Ju9#wuqLq8 zJ={ItJ=)#-RT+1qdu(f4VB7w-1;q1K_I~t+`L6!9^v?0#^6vhQ3?3H1WPscX5hzGE z%`(mNi|7~UFV(o`TbYpMhsXtU+{uBL+eQmI}(`lR9ua zFgP$dd@Z#Divy1X$2E}!EKcxaJ`Wb`U=Uw$S1?3SaIi?QSno`4!}frIn1h#tyn|sK zLo079MJsVDS1VsD?J^24iVa*dBu`LT@KF$EuzJu^Fh_7puzN7Fa99G9338iApd#Ho z%RCP&5i2JvD=Xb^Jeu#Rp%fvbBB=!@1uF&51!4sy1sDZQ1+fM61-S)bipIy1e@I;@ zT!>xBUHDvBTzFhK?ul$*ar+0I&gs zdU$#SdbpeDn*^Kqo0xWREg?L8W&KBenEmQ~OZ^=EE&cBO$RuGAOh)moq=Axjvn;bb z%tXwb%&g3G%6Qa~DPk0&qok>cCy6VG&xvA*C5ae`O^LCI^ojV18;Mi)S1xj4FLx~RJ7x~RBlyO{swZ)aRZw~YnF0HXDx^rAOoHX}FVv5gKhSW1V4>qY4} z7CG2|r4iCbVEhyRH#ji(k7AIDv}bw~c@qV?2jzhJK(nAI z&?;yUlm?0eU4k|}j61nDajs(WBJ<+)qx569BDSKoBDWOkMAiC6Thf4{WQnr(()S7o z(g;%M6yjv!l!Fw5loaGNKjwb0pxym|M{|ulh-8aIjGTzPh}4Y?8weCtDI}jsnJms3%{k5S&dc#X(h)-&W&PJYRyq3bA z%%1#;?5aePhQ(B#OT~*OMY`BY#a9JF<+(iQx02OwD_JWYD6BUv~ET!r{iI^|NGiPoUK zt|7KzdXR(AM7xAQ`%tUj)?sWQ37`mI0N()Mem&mVq$XS7K&pDwWyrUTy&o33LF?qh z{}I#ekXq@2XFrC!Av4o^Jf)m!BIf9A?8BSgEUd##cx-I`+rl)JGusD($JYpIs|VG^eOOuO#jY<OsB7I?C;)`qE8o`mBuia1pdP~=bF(QwuZ_g8Lr!+mgX zd(AfxFrN`O5;Rpw(4Q%deH{Z{HS+(Qv88w+mgtF;FA;rmgKC5SE4shu$o=t6z9ybP zPr`cw!wr-O915fiC=2MchZ1JT%pm{IiJ)sW3gH&4eJ#34Op-sI2e=0um2`nQCjh*Z zYn@{r9Eh=6HEoufQ`EE9gVv)WsI&-;&BMy62*;AvF4$oZ36G<*T{&G#qAsS1(MEIs z)fDcg@lo)Hl#2h)?2CenM1qlkNI=ZLn17L7pNUR_i@%g&`hSJOJ-dXG@wXCiDh$E$ zZ~G$K;XTNKgx~@Q@>maHp$Zzp;|_~38AGR>{BU&Neu9UWDOqgaE60)cQp=HoO19Gj zRgkK_sexC{!j4!?vsprx{k-?Osm-&A&G5IW)`;YUt3>*?6nux{)FO22Tf#BKK0~rd zygGIR>VKZtggHWYnu3=K75wlYtzWu!n5Q-*Ld6S>)cHq77TMbd$%)Ge!->pE*!s=B zK3Lg`*a6jnsSfG6EHRH<>3FmcE*C0Rb2{U{f}Aaj*c$pG<^K+$8s(Zh$gzh*c6>_} z^f7ypQz9%2Mba(Y?nWGGA%1S&{~Pd=87#8#<|oR}?r)RulbSs^+d2mH*OVGZxFz#< zJ$c~z-+&QsD#Q9Djz1S~i}I}loCBK#3mhZomyuS|g=*6{;WZ#~;H#jboGQOUb))i) z)(hoR^_~>vhsHhpfciv}^aCGJAUmS6AiirK(A40nCxAm5`rkOX|6d#*P%sYpWZxfN zxOQkxObvK+oLJ=|j0Uyoih=(#KruyS?m%ZZTNxY|GU^Az&4hsvJU)M9P#)rljii~( zq|&r71=-h3f6fF$pCgY8QYvC@J)(G{-ozpmznZ=zeA135x&Q<92K-7Wr(qG*0&D1l z_PoC1eK8mrwwy$Pp&2~}OoH1&+d^MJUO-j{E()IIyG~PBut!`aM(E;H?Rc}dsmM6b zHABt5YV>=H`qYS=hcN$QU`D{l^X_jZILW02@%3Bp%|YSAm7n{%lf@Yoxo zxmm3^4Xu73gR~7|KzlTz2-ese*U>}eQ9G_HMO9Xtw?Nx6-hQ2zzqYX?uxb!lMCx+e z=N9CdWhKL}`|V-SExVRbO7AW`ru#$8l0NoNDyhv<{#Yy?hZ84aEQv2iZlM~o39lti z32&V$2P?!{U^NyCy%U0KAFf`NtX|5!5WM)Wo8ZSW-Rk_{Wi;qSTv2>RgQ+ z3nLE$Cf;lIstSlQq#W*;d2UeFRm+TCqLD-#zb1*7NN$Ypk@Ak_0m9qskJJBQ>>Ge| zX_f`Ywr$(CtuwZ5+xDE%8QZpP+twM|-udstzTLPRu~C)zWhuM5zV5Dys)Rb2&cyDs z2}T)x!Sgz!?#7CQd* zF!9DV3ciupd}BrAKdiIltHw6s;-R(cicVLI?s+-BB=FD0g|DVYb-I*#;1;g*4L@R( zoX^ca)|zxRn(z)o5o6a1*$RxU2G4JF@4+nw+K+++(@E)d1@; zd;}bn^uJdlbhjl(_++7$0{OfghS2kJ4PGg^gU!Id!n#8j-7_y7gj^rW>z8Z=Z!w8a z5ptaI+D|&RME?rJh{M@IyC$C%n^pY@ROYCS;6DNWCxE6ihqMe`VJ(-Q)=e(3u-j-+ z_9qXrmSD~hLwrDw*w2+_N`Zl(sh7FkHN z>zAeDiAux~+r($Dfp4&w&nv7aiY=byk>^5NQSw`bz3-Jwe3TN{7kHQwfNw<@Yg4m}+0x~n^ zZ-kJX2Qdr2Y2OVs2nl$Bufp&ESp%{GX$9Ey!tE03CkoWH+w;&Wc%v&4tfb0in0Sg5 z(=yp4Syaeu7>_q~(Dq}+PW2N1%<06nsHh5?H}X-f;K{cALI>l z$Jodgs>GNSnWqf%W<-H4ohk}#R3E~)*8j)aK&F|2Oba8CHhK(g*ckdfFXVH!-`CuL zzp)lya}}QUA`I;*2ozF1(|_pyK=F5B{<0iVu0W#SheGmn5C0&RLm}ZGly6Np{v>3J zA5kTcX{{)YFYy#$jK4S!-gKCk!n620O!H3P2JhieQ2u|Qj|}q9e;c(Rzg7Etl5gxt zKmIpK`E-wLpj#y=UfwjSUKwYOVn6kVtr;V#5n zi%<$RU-=(o6GEXyF>%-2wu6ARX$Jv|vjdC=>IZ*f^fMQ`?h5W2j6z()guxCN4A`$S z;IREq5zB__DMo}&@9zI-LT<}-IEI-nlxX`_XIUeK`ah?GQi#Ph+PKXTU ziBb}+=$g{1YWj3YsFFEFh)q##6-sk*f3uP~UC7^`0;mls11-#lR6)Q08#N{iYGF1e z=Y3Bcx>F?{l*z=FFmYy&|NDgFsv6w;xPcz$1$CIuQEd&*Q}RIfe`jiH#V)4^yR|ho zX|HS2+0dpor$}o}6jdF~FFUS|^T@0o8uXLJUIWGj_JBVy?mFbwmI2w63Sd*shC-?9 z0iILq`+#0l`)?)3*93c$9#YT!l}aiXDf>aeUs6d`NhbVYP+rs~-2XQIGB<;|l*!AY zY-A2`EuNi5Sx*bZRjKWOa#icQqFU7f?WoXqL%pb$nlP$Vy{#(`_YzXq4QW=+qgAnh zd#i!^RExnXR}^NoEDgP~JgNRc=B1&bY4JIbX;`zY6k0*`thLo-Sq)oG`&^G@sq_;B8SDx&nO#NP}`lMKOQJ}dd-c%oFX@V~+`tM>0t|NS5 z7x98=8eW1$lnl1XI@JPOnrrTIPB=Tpi0Lk(K6EYODwH*_VchTS)KN{N`sF{BtYlU( zWB*O+L};qzFDI9}kXrsXY4SFe<*PNJd2d|hQNPTiVU=CUD!-ggZZ(zIWGh;wrFgEo z*pB)?$?nLEvt)}wUDv8y-X8EzD*LR?ohuNiH?!V?-mPAn(JivmdmK0aZBU+oj7uYn zLAo~K**7%-qn>H5qpBCk++2QuFXkJl8y?3Q={Mn6bU{5ZKL4){3y@>KycDyAwqE{; zjt67`!2`XRB<)-<7Qz)03*9I~;LCnUH`4BRvGDNj6xp{Me(FX zc*Md#B%>5xJW?_VvW&?%$G%My7d~o2y#=NdI1hPVY18C=e-6JAdx%%Yq7K2n zz0x)h{U*j}?>Id|HjDPH@$z`bRNo*jg zoLO*Xxy&iE1mtEToQYoJ^2iW9_HHTg1ku}F%mY6=ld4*}N3A%=_t20{_!@2&>DwKPAzPK)n+S30P zP!S= zep`eD5<``-GQYw~^*0ScRBpI`xWqJOuv>dEs>0LtRr~e*_Ph7jVwm05=|;=@!?o%P zXUSDnJIB_#&E-H>)hAnjkhP)s9{D8(Hy(Wp`ufBTmseXCP>*R%z1ji)hSg=$i|jk- zyKHBl?}RV3pJ9(xPZWRCP6J^^liO<8t?E03+YO*@+6vKaC!9PvV)od10#7I34B4wY z@pcNz?xly9Ql#5{mW0K2$TI=_?X5Qt z!4OHZ2yLR+NyL=eF`hk$FdBqx2{LHWNC`q}5eLQ0N)%F2%zCLsMA|W~IZc=!D}8&0 zd~4Kf;jo9D929T~(EF7f;&c&KaKvhv>Lo0!nM`u>n)9cZG$mV^$w&)DzsHR|rHe;( zH3Kh*N6fR1I5^d?iH;|d*Ol8eyK`w%-Vga~+CJp)mQ_cI@4}m<@RCOwm+pdjimH;$ z4n;3LJT!EdmpeSRI@hUf3E4HgmSi1%HLh&s+y3a0+Df)GHuupUsLp=v7q?lggWHm~ zBtGSBEL;}(96mX`o9DECZUU~CToJg1H;>`|ja_bTH`=OOmuo6dkKMN?k}Nvaq;8F) z%G0!`XBD+kAYX)r6)Kn|VL_%9k)E@BAo9fR3gQd(GD2SugDXHj0csCGgfl=}3p77m z7&S|%D&NL<)n3XV+EDJ|tj9f@*`0$^@k3iHJBvx6SMH*$mtM*s?#SDQc=stgn<4U2 z?qaN0`>C#1=IQh^iD&5d*eQQ%826O3i*>6!7LNj|*u~-mdNN-;XO;6u;ix_ik3N@g zqj2V*O(EW_-(2hjU=#2DNK*v$PWcHW6r7Fo$9!N=IMFElb?8= zghb&)r*Py`I26hq9TG^84j@tk60HV{)P_cB!=rE{Ryb769hDCtY5^9#I;7ao6G*_{ zedCYGREgA9jokb&igYItM~wSRv2Vc{>Fyazq)Q==NKf?xTqRZRjGW`{VzEqk6NV&z$do;RngK*jz@leAjlm<0K_ZR8A{U`i#-LCv5Ga1*Qp__b6e`ZD zVu6pdKqgsWQQX_-$}|Y1u@p(T6iMh5N%-^>3E%|zbS&{isg^=~A?Ea!+`@Vz?+m*^ zka9sLVi^8bN0O%@3Kuq=k8A|Ll=Pb0ZiK&-exLht2Edg7pX+mmz?BM{+j9oNl@yw9k9qbJBt?qPa9b4%=7 ztYrcBOC`{!U`gfeou){B!Q=t6J+nHO`jo0Yo$o!v=WA!=)~vGic~V_crC}#w-KgMg z>Q!&Xg3@iq34}=DkRBa4rBdbal!r&Vr zvS-wu{ z*0K=`rwyzMP)#RlIq+a5Z}!asY)SyDO8lHKi!B#cA)KucRuN-+#+3p}b6KGEE3-i@ zNNpuh&7^l5R=>uyk<&&{YhJaH)kYXU&&Hwyyh+66>8z0}vy1FeEv81+YGCbrzTN&w zCLPjjCSSw;$vPd@%uWQmZ*4=^DpHASlh@0I!^?)li!77(J;2P*AMeHz_o5ootOnSO zMBfW7vyL#66l6J8OtJ6Q0N)S(+v9u9?V?#5#|IEmr#)QB~V(7@;LmN>iR*x_ zmHO&ObOkTo=ApmK0e_VJZSTt+052STug@IO;gfksEv4K}-ih zZN%dtQ3nkiba@T&O8m-#<+x?W^H!IBE{m*sIW4mvj|80Ooi2Ut<~sFsT56RQ%d9_+ ziU-;)w(5QBZMEF2jfJ^PerYwha2tnRQgCC{;}4iNTa2u?U2t%3qcxB8?7hFXd;su| z!rxxM7<}>a_xtUNzs7xF@K4L%Bz~aqPtDvkeBkiU(BDLTAoWbv+*EvE^-TX9B7LBJ zLyqJ6iVGwLYKtMaLqybuBh3+sVFW_{8hl%e34d^bqAs&?5;4 zB2q*_k<5>mKno$frX-4tG>m^u^6xB3qM*e?G7-^8pc;?3%MUE2m=_^c)KErL5oJ}x ztON*lpPw#Pcx^}3{S~NHI}m(B+jogCtr^e7X_bF zbVkY&MW0h~M&1&IpZoQI)Fp~Pr}lvSIRwC{0D}}Xguti_6Xb0bj6o?DF=rT!K|LIC zYZ&;4f-GX}$Iv`gam2`=Go$b%O2Z&fodOL~Ton1B;ccNhQA#9MesL+OWz?Es>>ujC z5wH8f9F%eqKjJxLbP*NPl*f%u!<^?oYcFo}%&2M7<9es1w!hkLwjAtPTGR5=Hpj6d zNYcVg7USw^(!*o!7LrMLcE zkBmJu1XWX1j9E2wmg8AXXf?E!6I@K%C*vQDh1Vq5Qe=&Pz1CXOX^p1TCtK5QjEWm; zt;sc~#*M@METCyRMyu;HF6g{Qv+Gkc_DtI!H)&eBH+rXBvD;%dhi#Et%HC@?CT*^o z?X}vgHkWO&pAlQCHl}T^`OaMJxrJ0$*Dm*6T{_yfw(xviPo6?AYUvL{Keqs{1V-V& zJA^m$;_>0!#9N8C6tB$PU=AY=HgB&$-151{vM%HtO4=7TM>bEdVBCt`=DCluo~GT+ zx}3H>t^?iXIu3Q7sy$aa&)cxycFM=nGN)1VECfQcA;O0(hE)h$PqG+$uz1? zG3%84O}qMaFVmxVFY{dCQM^;`Q4GGwvk+vNeM-nW^T7QBwdrP9W&<062TMG^CUSbB z*zajS?A$ZbRo+5!3Jz*;M<`=vIFd87Ni?TuW$(ZKUXmJh)pXg7I?4Hwx zll@jC8>}|^NKVS6ww%IrC%ONshmyl_RKU|fM644P8YFKXm+GA{S<-u&3hvnn+)VGg z)lPyZ9i#D!3N}~hq{jx}Pg?7ZGilJ%T~Oo=bO(MI1MZV1l6>$J^(PTT`IRX^BQKSp zgdm+nYO*(qVR$h+BQ8Gl`FgVYEo%C`0ToJgDEeho`q{G|)U*HT4uTsNcth0*MKr|f zPJ3}n^o4Bw{aLUK9sBEAkArH?D}KfIfbyIC zkz5*Oq-|T>CVBFO2M-$*QpPU+-C-^R#k?;7PuAL3n z5BV3ZuP*h64!#z)^yrTr>+~2lq0o9vn-JQKS=*%8r(B~_M6L*zMM;mtxilF&)>wKM*_An6Ua2@C zy>muSm>1h(=9{grH=OrJjxDgA%M&>FGuY+l-Lkf+z8^mq$NnYRW-|jRUA?;gHeNAZ zU`~G8fY8~!iM0cX9!%3yzuXx!!%}yP9DwKtCiX~l16KMeJK^m7e|ZRR0+aZb%GIkEk5ty!C779$>R4vS%M#OOiWu3lMGH-aqo=oRt z+7$XXEO3fhb}Y)0+iZDU@zgWT^UP;f-aUDVk6B*-aSQCsscbi+k=IwW|()q z!XXZ>R*&@Xle_u7fctEflbC;-mze+SjTdCUh_5zIR!NdGJZ~OC#I&0|q!p5f5-Q?+ zpDYrIpA!Q!)>#|n*j-N7m=jZu%rTRO9J@`h@6HQN8O}4sw6x#uqAd4+yHtspL~<6&c>9lVrRfV}rxEReer=&|TesyFMr3 z+o9PV=<9IXRxsTu^LjRL24cVX}gt%FT+>CfH3X&5GhC*e)u~3Dxy6 zFv&K?Z=4|Iz z9LkreHXyJ9qSqG8@q&bBe_{njJw_#k=j7-);`sS65{(+_oBiwA6g9LIG%^Y+X$vc5 zRhQ9Jmy0R5rNPy{n6b-s+2hXg{*22mm;Ou-bND-;T0BVr9(oR)^Et2qIVufk%WqxG zPb14_2k47DpnlCm+DlgQVKm2A>H3#T$pU)7m^nT9XhP(<`a2nf{l|B0;dhtb-1CEu zbvUPnP3o{dbDnj0AzP1Eo+wA%f4f8Tw_>iuP!4R}T8KxR73%bhs?*LguLI=aDRc0^ z`FzOh=L{(58m|i|0{a+=XMSvg$2wN9*4>q})*UE{$<{X69Woz$;&zg$}Seab~uygWixtvXDV_XA}=FhzbNNs&yy8>I+(Q$?+y z@&)Awx)c)a1udm^Zfa8M(!jJtrJ-S&T={mzeC4><>oekm&Z>N_$?D49p{0QAmikWW zsa|Ei#H2*71l|-WAXv3TrE0S{u40p%pQKEFTG+PpV>&-v6QK-R5t?j@K=&5>B}ZH~ z_j8b|Et7+MyAEe#FV??Vd6@FNkn1S(P7bsrKN0M*yk%kJ^!&l;jT<;KV46UZBsqQ2 zL?YBADLRF*zE&nd)L2nN1}&A;SW-jkDaFTFZ%zIw75GqqQvxo<=x4h{S1um;h=fx^ zF5%Y^52qeoJoFLKYpb^qU5fm;iersq+Ki(JD zgLxa_vMd<=0_0l_5`te`z_u6GgWeM1v~C2G(>|!ydbXtp@=c+Bl+|&!*~InvVGo(% zaoaKLDMjD!6r(oP!Mn;$RXpmNUX6EL0k+iZTARb)q-?>w8ZS$sG5GpU5_wX@+Q}(^&VB ze+Lp@wB^PoKLGNy>sk0yv36LZmKp*nZXx+urw6F6B1BrL zsHN7w7Lc0ts@l{ADvRY7E{!x*b<09l6)y9h=AVs(>l8MDY_b_uw2LX`6HTn^QR@|7 z*Q6HsRvAr~U*q-bgqNkRQXRQE3%0_WxWIEw1;=y9k0@P*+2v~URwt*Pz+GkAQg$_N zb6%&w4+d`-KEL^Naw_<%>6g^b&L2`ARvud(pC9EPtKQ^%;=1+tEAba*PEsGR2~nUz z)JLJ(^1Sm373LI96%uUUG%^l1&%w0}i zXWwSA^W22+L)k)`LtI0;p>QE^A+sQ|pmiX(pc3tsIoKu#bU6q!ap>^qkgGAO(W;TB zq1fSCAX;GEpq?R~q2VFppyVOsp=aReVdx?0;cFm!Fnn1%2|reDM)s!mF88+feD;LH zFqCR-_M4Ak0czC^_byjL*1}prc?a{tSWoQgGOaVVPDDRdg5D@D zBdrEB0l6Ai1!SpX>zh46)ItaGO`eRe19sFs^cfL6*3KomfIJyy`FClU>Ekbh zuBN(xJ{fKKe`>hx0xSb_#6N<-8Fu*xY1r)|fZbLzCOv|}8GZT>X!z|yumK>Z7?SlK zq{DbuDnO0hiP=_Lf=G*kfUXdq>BB%b6kjGp~y5%7A^wFh<4Y4M(N6Y~A&YS|{>-!6U-q6-dzQ(hQ#D@QZq6MHmjs}!t z8s$H+deHl$Nz!{{&1etmh1B|+7O>h-9#E~JsMl<5p_kPf)1KA~tTk3J)_`Y36(Gk9 z0TzS4R;5>26-PZb86d@sGyqkdQeRXR>(4MAkgB1Hf3k*#K57b7<@h)td+lQ{`kLjg zv^A_fuNJs>GB3P$nqa0uXFtI-!`C3eT{u6!AD}zq7t)R62kNSBh6>oSfr|fi?MlFP zJwNdG_%=ZI$Tmp#3>yHyz8xUH@hmtteq9d$zv(pqzri&SzsVQQ;>@a7Z_I_yF4+^H zJ@Y$4YlsiX<~Sdi?=&Bvtx*op_b?x@tr>OzH->S=uv|0d3+M*W=1?=x_cR~)mFb4R z9+MwxGB3=&u@U_S;78;oi0@Q4z?D&l|4wZW@N>gfuiaW|FR(TI4Z#L9&tNx@??^Wo zp6R>)PJ^Al_^R(N{Ds!8qAS1)F*gLy@Q3a`$2**RATC(8@eZ(j9dYl%1^6x|Sem~> z$|nTRNG#w?!$B|f1;`B%H)Q9a93XweLNE0N%ncPcbm#CiU`@k)FE-3J?g3c`MCZ^h zFO#ofYJl6HZ@Dc~wY_fP3oL|&bAf(a07_u_F&0G;xVJD3o^>u#%+^4}Y^iT#s7DLnMi}^{!%=DM@lC>$DuyP?U5H0W1Y5lad2jEhb~U_Yfw;qOU1Y=I3X z41C9#^|uKHpARfwIIdI%N$mL ztYhhhvJDlhtQV%P&>c;m;ak(bi$6OL%^mB!o$#G;o${SCowD0#+-r2vU*DGZ-)ENh zv-204t~MR9I_RISyjJG4u819_I+Jv^sRGZ{s5l|^)9mG=*FG!f$99H-+(f8j!Cu2B zAGFD1haCmRFAQA3&#pj&Pm+Zc*3{6q-tgfk@9}~^TG>^<_V~Yhfzp2v=`$-6$G?Duk?B8vIT;C<8QK0bpPhh-EUcYP9O=cZ4V+CxOpNS| zP3UDzY|Wg_2{<^JS(yJb!AQW!@zZxUK0a8e|N5icGcPhBy??1Zz0CMro9JZ}O8zLW z&94-CEyM^}ftZXA#oogSv>A^umgQ zKf|KBm6x`{NZ)Z^XEisDa-2>2ecgY5doCnicOBDRZ#zwQU6URHGXR7SvjY~ZF3Mx{ zG})wsx7v6C?r)dURG4F|wHZM5I&lM9>#3wzLx`;nP5)*nPpZddLGTnwKoT-^W_C@Wb@#_EoUW=+4f& z%C5vP1J2iu;%9bQmvO&*B#HS;GID%%nYCm!>^94fUxAMDK;+hCFVq*p8VU@aA3XMY zPk*4A^*oK9k@sq_Kj@__3E<8E-fJ(pkxwd3Y}yKQ?3AOt)9h}$VN{{I{{dZhV()LPHT2|W`z`nSAoFW% z1)&0rlK17b+>C^;MfrX`;b?^299ab(5FTsSAL;3r5XP$9WtDi!_a~-JKGv!%1I6u* zl#LAZE{(z{6x`%)m}zD*Yz;L#qGfAXt!h=PVnUoS8PaQ4Z5A-AJsIi=a-Fo--B^bP^Z4qZFn*$$vt=6HS8%6ZIl!Nm@#9Hk!gST$Xg?~;0NDj;SHF*eJ zWLV{kFvTn5!R>QDgeZ;7_qMwy#S7>Cxx5f_bo@w;%b5qo!1wx#aJ7o7A8(GMhZ0&ZD@T&J;^i4 zgF-GT=hK_>r>#Af+E@$@xk zo$vu_z$g5eYf^-!%oAEiL-=X}Y&v!YuIrm{N^e^W26EW_-`PSXe__p-PO}>Ih`<^Yq$N`P-E(KVlTZAb6UVo}VDQtqn z^dXfy@Ujx$g0K1N;l2G{p8Hx6b<4H<8+~e)#aUIxxT>J1#_ckGC|R;3lc~VlU0$Ex zX|0{>eDUY^{+wum7+UO&nVCuPTj!YJZ|Y$*6E;j|%^1xh<)6hSL4#7IrE8e?6k#8^ zs+yUkNgXIEZ4k;7A$xgwXftG6#*yh&9xd?d-9@4%SgDDAE-7<|bS???f(~W7uosnx z8+?Z9M))f%s$nAcBK0x(m`5KuI*j^_du><;Q*l#2uHn?xxWZEXa_snDD4ZJU5?b_$ zW6K7nJn4#oN;s!H<+`e2vM|bLk zCLR)`aYuY=>CBaGZYEbJt@$G`4e87ePJ^7ZW~`wvXcvopYOc*K(T zt%q%SGIjY(Nre0QtS-Rl`h+1XH0|8oV40n(UiPKMMe+p?={;bS)U*!0GU_`y;F;Ld z2rI|85>6UB$o?AY5LIP1h4t8`oE?O_Gv-hZQ%X~@U+E{g$_>Rmrnz1XCkfgrYn#t4 zKk{RZrYYRi$W`>;9=Gu4TZG#yJXWq1gDGvehN7TrRe6jkrM)t_MRrxmI-I`NJM+;| z9BNF$Hb$fL%^}u?n5+UJT6cYIk1>v^-L`~U9uVnf&yP^A)YOF@ZPeW9)aHTn>V`H4 zY@=}(*Hrf0Er-H@ERy#SYZG&n-Q%&toMvI#Q`4LnIc6Q$S=kD?kW(upY|}QsJ4w=o zE#7Y(@m?bx>9sc`tKPAtsntA|uEeIt*d@1xLOP&!2J4Y3845ZM^AG3hk=%%#g#4I0 zrSzaF*^O~%Znp0%%OX?TQ`ES5W$~o=8A#1`GEuhUJ!jQTm0>0$s_)@T#HxxQi`uA< zRbkS1;{{QDkT0+=7w->Av>tRYyh=&kRG-yubvN5e59~2CNIe*baRbY)#2l(uNep2? zi`k^C60K?9TZ(ln%d(a!ygKNmutJkceNmEycKgo0fYf3c)sy!w+$yy3=u^QXm&jG* zPikD;&u&;QB?WeULSmf2lzSwcAsaa+X`CV0ODZ z^Xf8;v^|_OeyK4nv)VcVgBa72TaXqh+e?F56mrp&9z9Ll0Q$2!RuNDi-d*5<)yr+w z7%_B&5ofjOjpWG@=m;!JLS~^XM3N03G|%&jJI<6BFUS5Rvv3N))rs(<862!b*dMIZ ziT-satm$B@AwixZ`KBbPC*@gGsAizgz%6_rVWuSB5PpmTrcBI%8A3!4dwLZif`!HF zmDweW2h-G`P|O<2g6aN5-HBYcKerY55Xqoqp}UzkilU64n9bREQEz4 zxWEq7T>AT6VlO<)^hbpSLcUBwj zako8KmHSwooZHtX=tFFgsXiFRnvkIEnfYpsSYgWEF}DshSv{gq(x7}!GNsLf1iGWG z(Ky{L6`wNXpyFrN(mZC~IkpkGh06T+ zqq*Dm#w>E1wDulmQazLC>0bg+GL9Alec@jtW0A9Im>OwkiA`V{TXT7AneD)9(l!_b zRC&?^&-Ojnu4t{Etr;B$Vg%JBG*_{>5ig*5G4`r%GKl*!{Vaj2mlR?oh&&+deWDU1 zBnbPEbs*$~p}Qx|YHgmY+`*s0Es%DI+a#TW?pZfjdn*0Z!Ksk+h+ksv3AJ%IKzknj zy8R8oyuoG=^2pglJd*B#wHdWhwTU+zdyM@B!IxWeNZIGHa74|Bo)GW|SJ$!GL90@& zakU|{vM&`d-MJgdA*1Anh>nn)AUzjad>K-qS0emEQx;1Ki(+2ot@t#Px$dG6eMZ2eWiRl%~s zsSrJ+n?iG*pa#GOxS*>h%5SI!h#@zg{hIwi{oef^!5)je66QoKNYezFawi0f$d-^j z#4=>sa(|@$2>lV^kOi~LpBE8=?V|%nM3+_n(bimrPz?-05M@A?0U_=am_>F5q0I}( z&CA^qRD6aI)gc;E0wT=bB^g9Aga8V`mfw3K44DECeue~x=%5sGMf{Rll~nv08~I?b zpVvj&;qXR?X$GR-+GDvhiwse=7O@fpnlMtIj3J$F)|oi?AlN9_&7%Dp;R$)0tW(rI z@dj+qrQfJAUmTJfA`an8$oq5C1YTfNZV|#V!XgN&;14@c(Ziop zkKiAt7r{Zn@(}pQd_+9rh_5tzzkZ%6ynbesNO{C;5*|V9W$_|gs38Oc=DnHV30R&lVJ+})0Bdq@M=J)-`QU}y*@N|Y9aY0^fe&P>@q!Y4$^Nac_w!4AOyc}ez^ zdxrgX5TwC%!G`_jLWl-H20VF@43P}KbBhsF5r+|yKtKfvZi2i2m<2-#aRNg06k(xtoh`7hkF4<_sL5%IHCIYLD=|==?{bgEd8Ma(u zr_jSDYu^_205|FJtv|1!;8w&F_kcN4Ov0szJCSqy1U=$QqNUiP(4inqnxgO%dqp1a zqR66n&fSc;6Hn?;xDt5<9>=BV5pR>-IBQ2eLQ9gR=n;OU9iO6L&)W=l1Rl3js1|RN zbLu{~OsY^|%h!&mkEuB>0c$MjVAJ1&n$lk3fYlq34^g$hZ4z`&AT;_4`fCFU*H{bM>>v95vIT= z{tCnU#Wt=*VVB<-aK|-nMNyOgtSjh&aYUGONwJ||D^9Ny6?4lIrr-ayyhFQf*f z2A~FEBKN?}N22vZQEAd4YKLKHv{kA;MQ zF!zZtAi;p}3W}5ePRS)K1@<+j_dGO7N>$JmSqr;kPeND77GI0fZ3lzT(3=3C_AvhN z2ncD7)w|VQDe`VUM@qSDZ}pVD$)9HJgRW2mr$5ogU96(h)oJ*#;qOZ1+t%*%cwUiQ zQLYB&x7Nl)QQ25^*hpPI15bC&!(YmsbDFBs0*<{7--vH}Y;y^F>9n5Othdn(k6O;X zer>h0HvKH(3PN*P5LvO@sJAq|R+}Bz$0)CQW`OYt^1kb7y43EpiNd(Aan&f2)|zM$$_Qp3?I4Lru)p@wKb61)6 z9%?Vmo5^X5zT%7FUCh<5hl^1C+6F@P93AY+^GaB`v22{o=r_qV*1iQUt3c$2iXddJ zilAQ=YcCh&`lNa%{oIr!`+8dY>4U-%fz_OOv;kJqt2y=*0d`URC^I@I#>K7B2W-Q) zWZnlSw9BX+j87@6Q*}^@nR|~mDXeJKw3f0csmQ3|>)1{FQvRh{1sn@v3$(>kWWJFW z#ivfc21%^h>ZXRpqh~N*DTmXBMa8y_lg2wa^(UvRm@t7@>4J88Q(s_xgo1P4+SdG(dTrn87Bacb9(Ct2aW! zd#oCoL50qM=)gW=VzTm=ni`=Ssj;~*R(BE_ zCw7OWPV}tT0*)hD3PC=`mMHI7*o@gM){daTrLZ+X9f1Dn-5DDxFbYv9dda?@pAwlRPzTdeNXKMq7yu)H`N~HxiM0- z!9C^a`aMnT1!)L~;f7lP63%}#VRU4#gBTx}B07j=MSqO&Mb4ZkMdEs_{f2cf`=&t( z_s>zc>6OJIo?%x4v38kqEQ*wgZBw!w|FqqUHfQ1N3y?yrDWA z^L$iAHguNngOjFw$7+J zcL2C?+R`UjcVw%lf+wAqlfrCz_=h4@JEIP_x8WB6zQHGaVR~=2Quro6U@aezo!%CV zd&{5wfBKTYobb{H8Z;TN8i3e+k!u{P!)LWg!1ud6txyO1nh$(K z1F{(K$3{3+!dWP^hqv4gD_gv~M^h*4V^-`1Q$;Jp^+)*JAw^HSQs3cS?Z)_iJ8E%{ zkM2O{IsL8Jn^Jd&2f|{BG`?{>z?8PlMp3vvT{$>6!%-aL-Gs~D=-VS$W4214)Q(dN z^~pOaRo(#S{PA4`O+@eA(O*6m`oUQoJ8ret&?l+rhpN9{vL zC2Dz3Iem^WeM0c|BK`2zqJHl-=?6S=>}0PA`kwLT)pwFIf&Ig3cs zQvEs<>Iy)SUH9YPhnDTlpv{2~yQl6&E50wKnxfLt>zOG7#cT=`Mz>@2hrq-3qhMj4Cc+-d=*M_87|>; za=Ni{sCSfMVm+#8@fifFSiV7V)ak6u8f}AF2dJ1gx;SRW5{|jQvf)gBV8jZjwoz-8 zG-Na#WG-)I)7{+ym-QF_6g;+r{Al5os%2b^@b-$jio`-3Q(DJ^@SUtptKA;tF$#Mw z!?D@ko+ZNaZl?S9?JZagVTdDK6*+E zWs#Ob6q^nvrKD&^elR!-=fAEA7X_`8RP9)gF401J{%2Nq+!H%$e2h{jgL;pF{!r| zX=b#r^t8^G96vSOY*>F)9zK|Z*05oxA+TvS1H*g0Jc3mkvkX&u5)qfdYM;{G9X^aPrwVnrtjuKB3!so*PR@viyde0bCl zJAVfEhfyw-ZzCbh})SXqc(zI(1ji*s zY%9m{b}pDIlDhlYYQ3bJXHdq=wxg7T(pyVd+cw%B6MXXytlnn)#EA8=cxrAlRom5tA3wn~<&(jeE) zNi!T#k&pAvx2OJ{2h|L(^XPMmnYKG<Kg^i z$pj^?dAdw0LH6rbswlE@)y*3Mid}OKqlghTyPFE5v%bqh#?bWh$Hv2y1!>r$7$B|n zTwPVRyGupx%2_d8o8$hQ4>tK*NzKOFauG5eU&(kL8r@QR*S3h2lBbfb{aBHjs+=Yl z=aQ)r=CXN}mP~AmaWNjhq089=UJI|i!OY#O7k;P7DM#1{I&PRNeK?L{>y7VW7%sVJ zhVUZxZx~5NxFBMUlE=5MT$jQX(kNbQm0r}@A@@&7p+I5i&~EIoFK{~??gKL19gXo1 zuY1=+$pnm{JEO@Cuzbgf9)qJK#>vLEZQFM8#v9wVlZ|cL z*x0uD#^p1(wQDd2@Q=$Cwy z!K{RdB#p6W`P3!=~MNj{i8ZEjF5L{pE|t1vJ0NxX5a;#7^PZV| zCR>s6ItU-$m&0NR+();Wh?#5LlC{-Y>z7UJBiIIb)2e3su}qJ7;~Jz^hjuOY_2K<) znH4_NiPDS3%5QO&S8jwWoi_yqYR-=BU4G-re>lP>bHvf~!I+TT`aaeTz=rNhsgr{T zX!ot8Ob+xX5Ld5??s_L3U^e+MA3n}M~cuqD}EsZJz#8o9n>AB`% zGI8o4|BK%wxV%U>hhZo>;@fyLx@Z}vEa?g2C3vuRI#TiMW5+V#$=QEqiiMch7J|uV z2e*CuUTAV%wUrms(Y#hG%XqCPK-of73)FCw8G2ZEN?Eh_mRc&Vnum=hZXKuDBt97N z-sBx_3c0h1T7reR#Hr94Y_pAGSZ?B&-3S* z#~@F0^QoANF{6J@Uyj79tod7(2(m>X{^{2JyHYQEaA$QiKTyF^jkpFgH$Zjjsr&DqAVgGpN+{tW_+Q9Y>gG6iHb-NHyBYC z5sb-d&_G4sZAJ=jPAW2F?buCamTyf_m*1PB9=oJ|9dAkS2rg$b$9m)+Vq#{k1dq)i z7UzDxI24SqlD(P-pY1=MCT4FLk4@_rO(8DJ=fKnW(enxB8Vfx1&)#&s%O2al)AS_) z5MPczg6k|L510*ozAcg8^9nJA69%v2VO9zOPBYx!myn61!g6=&Jx39%=hLVN4}*A# z1LIHRU`(>U_Pix;sQgy>Tr`+AdJskXqGu306~kcZ9dd8W zu!NlYlUY;(*h0LDj~m`lK7l{aKl08Ses6yn7F-e6DZMUG^YUKDUQ0#)t7I&WEu*HW z9JMD&l}TqRBYA0j@YUtBHT>h$Nyw`MD0o+$JRFoItv=XGA7vdJU9@VS9?xK_>n%p+ zS_~i6OQmHaVqsjgoOGUq}AD#g^lLi*9AU2TEB z7O_s9X-^V{PoVJfJn;w{NAf(H(5Vt7oP3e|t*Iq}8{v@d&-7$?r;}{FzR6pJxC*<* zfP@b~O$t6=Q^2CFYKPKXLjIwnx8{NIAW0{xX>UMcPB2sNeoXUOV>j0ytBYAK`4BC` zYRAXgB5ja}e~7_OHCrvxTcSVb`R(Xw&knfj5_ouXk=TZ()*HwJ z=Di5%9sfi%+_UAA&JdFty^KUqcha~F+=A)Fqm+R#L1vy>h)qpFGyhKo`87MJ4}H z;u8d>Y)d05dkas8Zkdu!S@bx~G(F7|c3KYXcB%987>rc8GF)W@<(@dODB7WXkE2=D zMix6QOZQienLBQWHu51>a$3CAY{t+a$uIDKNSKqcoR3MF>|{gGk(+!KLWZ^4G43zR zl?b5CVRd-Yhx~EfvkI%sB0P33``^j({1!DlFP+G3T}ZH8w(`Bbgqj4zwHjWWorn(0 z#*Uz_%Dunhvb!EqL>dDfdJ#lF*e-f@pK`cfmDy5+;A72RwTOewLG?GUpY?v)Q2EI>vvv%vGnN+ zzxuA4b~=Dhx6jvkh*43La~43~657F?+hQ4^i~>e*oeh057wfl2hVfV%QuTA6k%&UHJZOUdiO`v!gHBWO!0=)sqi^EohMbM~pIAYymFH`gvq?A9?T-dKl{ zk&13hh0W&QaoHWn)ZflgST>f$no&k$U88d0$>1E!z=F97nE6ZyS($R`yUH@-sBA#w z{o%gY)AMY%BC~o*s-EjbyL(Zxsd7FoOtd5pACH5HrThLwO5;G^{R*;qb?H07V>eG{ zRmGZXjw8!-gNbBH3@c)@iS{kes4&lu4keucc6Di{Qy~>a)JHaXNd+ zuBPd6&68l2L+bj6j|7Lmlme{BA4(tSEFJCRKlPX8r`Vp+c;~XA3|@0#YHG?CC}GBg z-a;o=-$Cf$KekbQS_VldM?TQS2`S36V2WbN@69PynPmNUlpim6*Xv%lrKxu6<^2F@ zIhC6mEzlejRgLgq+ItX5E=~dVXNWPuR`W|rDKw+4Xd9O!L$4V5+%iDfwPQw&H67 z?=**ELL>zsh~gcq4M~vas{#*Ma&GP~N8dFQz`hK}`?3UmPzP(OAqdoH@u~k}ApRC@ zYXHZExgU3Ci(bRGrirw(hSE61<6ZP-6|9n>Qna*b;$4^SxfgZxnt#YyBRG~!(j1tG zYh$}?=79OF+wo{^%x+ER)W2b2e1l>YuX>jZiF3(g|JDQ@JZrl|v?V7VTu}N8qyTmN zUPeu2?jQhCNgaKOzNv0y@Ohm`IfCT3DrGaHr|Sg_TL^1Fkjyg!nOZh?q-5!qDX6+e z86hS^nxph_Kw3Hnwd`T#f>>pvnY3{4?}mY1xtbf-g=EX887gvs`T|U|$1fF%)?zAv za_kD~uJLEQ?oL*m_K+X0N z$HIPQ+C|QWiY@h+-FhM8fvTyQ<2;d)2Ex=}e{x;x>*65vkv)Xlr4fRP2RAHQp>|<5a`_Lv0`6r<{$tw|RaA-K;5-GwCT91(<)^)qOhxBu8a$ zfwNp}bwdM+IVsPXBZJNl(u|IoAbI7{HiGcwwnY9*I#K&YS(?b(!M$I;-D5Th*N;s6 zYx+F=Q8%gjW{KgF3_{^Lq{t_*l&d2{1eZ-vd(Uqj9}E@=usGCZZJ+m*?L^9G$>TVf(E2jrg8`?+=~BXme=&YbKl17O);%@1?7SyLOJIzpEx z@C;^MY0XSP%?N)aE=X!cIfp;f+@PH*qVzgLkf!fSMZwPH5W$ihpEYOT#fq(aUaD$( zHo3LR%Z~HQh_){V;UPMh$7nFF7fZz}As#g%7RiZ=mF$~JBb-Ilfsp5MNf97DS*;`< z!;r2b)?(p;Wn80;M@cguN0AP93l&BdExn0T(g&hKK~# zAY#t|zYMs)FqaCuv#zSFUpwXXOs>c@Qx~7rVOR$JaEeygY(l1d@(WZ&h)^O*pjI%p z`rD-&KUNR%?a%^(b$0x8xiLA8wVUAv*Iky^NsLmXxC@hJ&5VO*51PvMKbBjjVh`)@ z0zx`u4o#8qGQu!l)?Cy*x&E-gKF<#JIZMO_9Iq8Y@u4_j;{sChP#n_ccNsVWLFD~C zQn+498Fo*ob}*a1PP-}(4Lo0&e2<@2_$e}4{15DgQ*FpCYF1E6;X|pcK_p|0f!i7) z2WP*S=?%fRpN2A}ikN{9Nexu!+MiEua!x>qH0;P~GH!8tz!*+0rM=TIlPsm+LD;%~ zY=^LiFj4|@o+H#l9HmN%3nyHAuCm6C1^uxiP?jtHH6Xd8Oer1~#AYI&hv_by$k73CP`5gB1+>JnswOKS3Z0r;FPtQ9nC$Ei1i>p9&dqDE(>M-Fu zYD-EM4p}>(S2ayMqdR1;3-n01m)|~MOkU@ z{^<#o8D7_+MskmK#N;UEkgm4YhfuOjQVbh?u|B`pq+JdJ4ve|zSH1AGtU{5@K4VDz zThMfK5vp}0e#J-)GotcPK zy5YD|_%V*0H=|y^0Zu>YFzmnf$m^{3isjnjdTAc%r#bdO+H?h_QR|Hv!*zOt|4V_X zfMTr~t>mir&W;fF_J?Y&(AYz5H#diL+Z}sx>J9QwSKezj@LQwi4XtTA1;=qtYXzGo zZrg4<-_rquli%!LDNEZ5I$O(V_N5q{9`$$o1YPdAT*9k1?>YlZm zFYu_Kn|>^}+ZZ6tt{ws9QC5X&ue30h$&y6TlVMDQ>-+Lqv|FpZS1L;@DNw?s?F9<% zoima#)N&h~PnMQ-4wvxX>GaCCotfQ#z1+pV-KiK4%JL4d`9HQRZF*GVZ@7HQ3Yr$o zBl^eaZgv_sHnVT)aPX6dN|+l8%exp$y{d&-#K5sz!h96J%w}4l@(hD>5YV2M-l?8< zT3`;aLwM=j64C8o^>%0qpbwXkQuy zppbj>v@xj)r)z&KvGk9)YHxXISiFM6rw)Qt`G^XBKcid}?Y7VST5%#l$Hx!OxH)6v z0c4X>@3*bZPvw`x2UgOTucl+w?McjQ^=m=rl!N1=0wNJWStrs&^%bsKD7Iq?n<61+1YVv~-`j!di(94Sz>&jHNA|1kVuUwpuIZ4% zhe}sWsG6y)&1o*`&{KxUK4V$5me!*R0i#P$McqrVSX0Myeh&lX<5BOhx2|+sb9c9d z-S5jYG&)Iq-umN$H@UlNJ;~p^%kFR*qa#|E7 z&CwL-M$xLSgaRqbxHU8WcUs_%O2B(Jn1{QL1wqqZ>Xe8@EpLl(b*e>*0r2FEv%_HP z_yn%1CO`$1S8DIyU?B#e^g)ajPGvF%@b~$QolZ!ZtgWEw!=b6Iyg&FTZFc>whpRst z+jtC}pYeKHP_cTe+x<|FFEVwHz;B+tbryAXTKXjIYx89HRQ>#5+FPM5BrrH4ayE$} zGr+dJjq{s8mMj&10cPKW)xUQ*xs~$=Rtpab*{Os`hmaRVSMjXj?e>nb#oeO7wvDz_ znIf{t?dtLsXGivjZXLEA7KrR~c0Xk-A8Jy9l3_(;JP!?Xn9L#q5vaQRENJ&WmAONu zwf5)Du$Z1y_t$zL=73buIKiDFy0tDQU6mKCSP6ST_4R4ryBNvsV=V4v!YXKTc6pGr zU=gL3Wn?gDJP7n6ag@y|z-fnCK8Ib4(DX93p*=o__u~LRSxpKST?~GIZUr5pK#3&I z0^M;LUBdIK?J7@@lqPZKOJFL)0Mh`3bD2&bl0cl)=^8myx`vupI>!&HG3IrjxLqr& z;*iFhA4(6dTRQEJ^EaVB_&s!6djr1jCX$cXwL1Ue-?C+9@Xu<}43?lCh{dEp^IfFq zVaY&k?J})-m@r0lA@hYu%q`)i3`|9|G!l@j$NLAHxjusp^m3Xc<@g?n`>l}rIf&XM zy@-Sr>fb&BEHdE~ISRVJoIQ!vu+U3Hwwkh_sjCdfKlI$DHfmfbQ+~+20ez}jDTBbQ zdnTynwHJ&)9)pAg35Ib5@-2)V-1oBS`8ru4d0mM|cwkq=Q^lX2#jg1rh@U5^ZNaWc z*aNnS*aLK*6KbTFiN}bNJCLKQ{}8t-8D5J085K+GL7F(p=W`{H@I9Xwif>>;>9YkK zsQ+&2C}zQJVQ{dVmyGXKLxnf`!0{0)bV!0AR#g*|?Bd!kW-SYPKJ+NJ{R~i{M21Qh zqkWJ)#m~|tH4%|oeU~qd6Cpx|LKDLS6c~Pz+<_1(MTDeg?Y6_e?r6Tk=DN|n`p;v) z@=<)D`{l+uwULJO(e-OBzM2y9sG{m}iSnIDs@#j&fdJJ0Z>&mZ9TVg8WnkbxZQ8-Y7*i>>5!zB*zv%qjTS@mJnNzo3T#S4`~hPJ7Tl3ih@`wiZf4 zDDDF>5>!CYuI6dCIBr+^qW}UfLs8pT5$p1`xfqF5`<_ieau3aovX%`Z_iC=B(6+FZ zZt|xxh#uRTRVmu=t)yg z;EybXAFD~}YVijsLP4jqP3a;`kt#t*nA8Pz)YbZlQ_4Gf}_Cx9_}hlR=~~qLe71elZ0-R0`S& zsJV|&Gh&;S$!9;>!kw9yg$%N*9?`K(@)ix+73sTCJ@uI*kYg=R@K=r-A5N;T{KYre z-_T5EKSHQw#v!h`96zE;qPnDAQ73R4HBf~PrfNvBM6!Ybk<%Zb$@O_RF4|)@3SI6$ zl_j&!TR&o8kX2U7(_O9;gR2*QG@k&{M1?Q*l7#9kU{w$ z6)@_@>I$W}tI&VN3bTwX^GSTovqg{1g?vm5(z;7!eB>@3UUHz1m8Z8MWH_WX8=K2| z+QrME8)hZT7K~EobTu6c#n)}HRBT03ku6uNzQ=)Q{JXu!r-uA5Ai3+YK6SX8S)5L; zMg{4n@6e=cC?b=BK>?Fd?I>StEqqOTG^~L#rCH_fjk92*YJoO(7b*3cQr<&n=rx-r z8AU_i+hCD)rT?(C3U;>?2lBvPxUilUA$9p)Ti3sQ&fFinZ>N%6=r=+V;I@;u;Yqj| z3IYxBYgKGKrvr&i*V!9aov6gX*1gj@-6pq+V?Jy|f#nxf?AVRD?Un@&`44M!zS{a; zxixBe->ew5yEb^47MEYW%?h=yon7vp&OaYsj=S?doBed`=0`5b!uv0rPpc{yw-+#J z$`Q00#p?ROJ(=w0ivgE28tn|$bkCb8b@)Dh?*-cA?;#OUCHCcT z*?MSNpn3|^&!KvcszmCuuTJcH<_ycr@kF5^`BMY>^HYV|!-bTfaN;Gmeeab)BZ4DO z%+Q&UN{o>;@)XN{o<=n*&J*;fwmk=orFN)qw4xJe&1SO~{17C~vzkVzK2B@`JI(VQ zmNo8+o{n7mJ7%LVI@#XuE0vq8$~*ndy#PKS^;GdcZT=qm&Z3qkfhvOmr7&4j|KiWr z?6LmUo^d5Zr-GRhpD+*aLFD zy{7dm2E+-Kb;BTLqIo%Vh^?|>Y1Bv#X+Ym$sNdeGxi=YvN#O$qVDj=s_74s>l89C6kQ>HtEHq4wOq$Oqo&k zEeC&Jx}{#(p7oFvL3obH>%5Baeiqlh5u%Pijal4qC=6eQG`jqbUn9LX8C@vSlEdAI z(`H;HeP*=8Nk31e)U9Qm`enJXoo=4&Hatp)S-*;lOZ$u~sv@+L>SLM}w1aB|1aYKl zdh9*SRlTNdGB(&Z9)ck+vn`ncg=SqVz*{ajspxzP7U@^pJOM z&#XgQtF`NNdz(kue`2>?5oam;dSI<*jF+N6ViXElErYO;iI=C8u6Pq`?c(UY=mSb0 zL>@U0mN*~STOVhF>Yfw(_#SYX&7hdDeWb z81chCAvaXLUuv`jzaI5N%WJq74in9~f0C!})S8Jt31UHOVRdP4jwpCe^;~6pm{LkV zTode8c1{~;7~k$Bz2*_)`XI;dRN6f*7kwjx#i_Kq2`1L@Dhua-Rh79MiFzsKYrot% z5=$V59G7a|XtkBDX{&U-kNE^8*wRqx{9Fr(xKx@yQn4=TX>XYzX-b8pexsoH`9!^p7EhWzaWaF-#M)$O^cUrB#a;cxJZO$%@ZgwFww-u!7 z*e}L z=3!Lr=vK7yhAr1#mm^~OL)o$?Jx*?f%5IWVQk=9u4z%IC+cX`D&1fOJ)Qt(q$~%qI zc3|H#TAy3#CL!T;bU{Sv<{gs^*(%Q20kPgDHWB&$s29VhNQW;JVy@bE=FOE`CbU!? zPV$arc#gF=W)UyVO?^C+)Bg##^bWn>927~tIk}-mMK#x(B>gwM%?UkDxY_+zg2`9(i-tCgwjYDluc zZPt#mU!` zih=h!%Okzy)BF5M&6je*!{15k4#1buu1o0pO=m->bHj9H@nU1T+PtNfY=s4D)&Jf3 zxNtq?7MLGSP z-d_v)ijd`pajmj$Ha_t?TZuZ%KYP&$+G>^8Fm%Gu*|YlAp+Z1f=q@$e$c# zo)B*QBcGVLY3JK<1Y!>yp0@LO_VE0Oo zk)_CDw_x8AJHCx2nA^W9?q^otk|$RBrks0JuMgpYeo`@gIC?8?W#OAEY>cHSqtpFpzsK(eHHV^A#R_yCU71#UW@ z`Xzi2@4X&Pb5s2;L*hFTSR|j}csAkp-Qh1n622gh-Fto6lipbYC4`=M3V4kvLES#z z9;&2(Cf-I=NyLM&B<&>bKm-u%mIzKK`Z0eSE$x3*yE&&7m`3{4d3+O?4(uW^9Qv-o zfI^sQ()@F+pCN=?0pi%)?^Iw8F6sb1x1+)CN7heGCWh=`Kg)W3@c+cVG0gDw$p`A1 z81VeXpUQ^t2)Y&@oi`5RN8rlwzcKj41z11~T|2UX^7`PE@_S-XGeEgAE%u=E_`S9r8n}( zYGsqr$|t5ECX_&q&IcZx2;SZkx_kV(-3IgEdxMyXTqOYQ0=pm}0{kBhRMJ^sniy+F zT0#;pv|Tf|LX(eV7Kpn(NV$JWiGbb&_m2x@LV6%#;(I|5 zKX_MWK)apN5ft!f=S1HhpPaKKciR!{!tpz97}t*S3XzT}g{B45?o5J?cFUsLp}P1% zRT4jl6sULcf*z4!xS;PTcE7C+hW1YD9DeT;e(x6E=>FZH*U%DNTciM(eOGLIzLG*% z^`~Vc6#h52oXe9XjH9~9mEuZh#sDF zu5jbWJ3(-BLgD!@uk?UU#@nchoDk&NYu_JZMR~zk&rVRSaQDIEGQyEqBl7>qd4LwB zSwTm}4A>iE9NU%eaf;?{oFO}s zj27-m-#rY-AH;P0%i|P{%tA;RJK~%WoCSsI)v`3^WOiV!!_dEBLtPPUY|mRu>;?S5 zUpNPDO2zWdf86H~<dK$mD-rljBj+Q& zG@OuEMJHgGK_Se)PaN%k7d%en7afW{JOY{~06;%SAHt5(gW1S13U`NEbLg4Db8L6C zr~S}2;P7<*7X8d8294ux7pcxb^%ala*!>8@sks0$Ehb^5>z-blyqw2U zV}m}UhaN#&p z1()|D&7UwyGU1U+V$*)k2VxG)04dlhtPrZMx9CIkd8vTIh4{5SC&~jv>#x5~)D>R< z%+gs=74Wvs#&M2717OD;?>}FLl@F_I5`>lipgbVewnh{GQ(Got!ZrOq{YF62mF%o>hd#y2z((y7e4sVu$4)LtAdVanv%XPg9Mw|Wf1L%fXxIJ%Uc zQ%^Jm>bm$K8YprYQ{+c#$)n(3*(CV3QEu)2Ey+p!?gr4snZCb{D9O=>#IzvA<^mHf*mwsKY76#97Y=@Lvof#^ z_TAm{xor0WJWN7(lcwMrl;;D};?cM{`FK-gVlQ%xhVuYqtT3#@3FkA<2O5?RRDCX{ zE8b=P6Za;T5m_?uQMD&<_lzv66LQfAy-*<#WPZb{VkP2CC(r-~YY!GyXwniTthrt^ zZbnXo{h|JlC2_GK#JYZlKWw~Qj0{PJ^TyT?<3EW5sm>)l;9jw}hY^aO5<}Wzn99zW z6*#>K(GA@hKa|gpdspDNS>XE|_ov{5zsiKq4}Od=r9sr??e$V{^ZIxAV>}5@sk}l^ zWMe`4BN)X)tO$c97U<~z0_N@dOa9WZWUBw+ZBs4ZZ0bb&^982K%!g#nh05I;1(B|*iKMv0Ah|94T^7kvY=9E?m@x8fNqjV*=+3K#lxIA zDzhL~5Q=~o5d}OI&2?j8W@ha6buch;BjZY1W#>yt+lw^v+5X|=`y+Mzo0LalvUXmX z@)#E)h@b(Kfw63K)`=`psy>j}M8m52o~3_Y^7dyjE$5`{Oy2JO%pX17=QMmA>@+zc z)gMf0+3COh5g39{6D=qhz>1WN)1WfWJiXw6>~RS7WwU}h7IkHHI=krNA`!;$!K>+G zGR2`BtXju-R`S(VTugof*d9M+hl-iPA9y)^0M;g6ggwf??tZW<%|IeyG7L>PnHh0` z))L4d7X3-vT^Jc)@~<|d8<7!7Eb|66K#lh=3qKppCVS(0Z^>O(dp#?w6*nM^NhM-M zF>esC@EHy;unV2ZX=5I9eI_^1Q>UPy5MHlo?oCv-s zufj;h>~?56eDI(^bkXBhY5 z68B;o_re$Vaz=R9A~-}qldS1RBX9QJB7G4jMCiymvVp*K)ps;Pc&8ZmvMm7oeyd&J zgnYonbif4tzj>PUJQ+0EWChD|2$M^&H1@y=VZu9(5ii&gFIUB28b=>k@J441g8!qJH=;T>O`JBmp+xCtKu(>5rRFNVn{ z%E9Di!Ea|CvFExOz5FjtWR;+;%2!J-qW`mnVo%MoEZfTI7nY-fm!B;B0YId5ec?MQ zmhUmt>8`miRxIp!FFCKS?do=7`-~qtAFV#f=_R=@WGqLyFWuScT^5@xPvUnPEc%ha zG(b1bWaP{{S31Aw9UBXQ=pDakaSDxDgYDGp+{`;Q%e3g-KEMZKvZmmLm&G>*xC!8^ zo~&JM)L)mI2Wci+(`|-rdb3u~cZW#761d}Gp%=Mh2KazaUgf&Or?VHdAxw%lMz5nN#=wDJ7(XJ(sRV_r~y8hlR3FBjx1LvWo_!)w&~LX zcY}a#=*hd8Z@X;^-kAezMeY#y$S%GMAK(N6;r>$}nx!WO_yE`n1YR}jvB{p9OD{mI zOf65<6Mw!Mn_VD<*}@%Mx3qm@_utHawLzlnbF%TT|c zsZ06NE+nyMD`|chh^DTFjWGGAaPl#o8z}AbvVIx%@*W0i%QXFdY3NvLvL)Ab|P*%1p4_5eTICJL@bm&{l?;aL@ z;7`-c#ZZq7jMDOQdIlr)I;DptPRsg!I^0K)3VI9Q^zB$fxsE?;ACtHB`p&3vyjoJH zXIAMreF(yit=+Gd=JWAXXCZwH3w<8ME?2m?IBZUnwbb0;S!aDj;5TZHe~SxbmuzHE_4;nLyJhiIQE%Y)EuQ(*>B-9$nH#3-wv`T3?HM0ueKGdSmf51GVOQmKJGq9X~c-J#6WdNj;c{tPL|X} zIj(&5voO8XM3~V1lHD`=bUIHVhnDAybDFj`ZC570i*#vu*=X?EN=F5A(La^d%t1|w z!hiL+h5okS5n1a-2me{_9UZ$Bs-O>r+g$~>;271S;xhXCX3RLD9SXzN(?29}H?H-Xw+kfq?lX+kls%8&&pOH%$9#c7+pdB&ZiH$@tW0#UkVHl*H?U-@Z$gbi>fBhhtV%)o!~H^3u}O-_ct$ zqHnK$idUBW`QT%bNMticnXo@J=WLgsdbAj4*8GmhIRCx|o^J=vvC! z#h@q*Y7J;g5o&%)+9*30MkdqanQyVAVVq9H3k=a%%FA8C=_E6iV>SmYQMDPBf=eDo z$)v~JR8}fF-|DI&TkHe(+qwN?2|g%|DM<4R?@=Sx%i5xoZk|dna;p?J*#%`NvaqN% zjS2vmHbR3VLC`+F_I3OlOJwp{cqJ2|(vFGps8X;arBze~R2FI?=I^rbB_XEzq)9P; zDd==qb*^D;n+_2$p)gp~V!tQ^TT$nf=H@|z{a*#VeJJ=F8J&sGdK!CKeT3B1?BnZm z&shBWRdWVO$^OxB zwGGi{?e{ z+)Q9%_Vg-{N{h%Oj~Q7In~TOO6Gvy=@uh_qS?ZjGS`G5`t!XwTeAL6d4_@6W)YHP4 z(5pnwp?o!qjNT65W2jDc7OJi9NdVacjY*A8&HJ`56gk6%v?M!!E4eF!l3*64n54}3 z#wxuOLC5~c{^Fowr6m=7)fi>qZsF@0HobE*OUb4Z4h%LdfE+t3RwR$*M@laU3xG@S zV2I_=R}Sy&Wj3qTv&}MeWohC_Ys)|RhxsKAm6I^D%8Tzw(U0PAF{>9>FIT&BE311m zMJz`JwSkjXJ%!XKG$c?bHzeoA@GXw|p)!sX zl;D%C7aOv;)Ib;~QR^BLFXILPIBY`3_BadccAKznmWbW5`R(?)ET>s?+%!XGemvY& zIq~RI?k_4};ngZ>ML0S|5XT-umU1M|GnMHwl}S%3b@T&OEvk&hq^y#cnQ(O}Me#BK z+Ld~rJ&;Os?-v(YM<&htlGL?2?acN9G%bs56R8>w6?X&B;Gg*}P0#Ld8N;edeMeeW z;upX4i(F05eJteFM%~1Pv>TR*JN3F$=$(VQl1#?H)HHj>v}&~P+Ovv4`z=smMbs0n z6i<#H0D4zoOsq}+wr|LJn5~Q2A_S?V!%?Le2Wvb4C7CLteE1bfqRwY zo$;EVg-(bseE-U29>#ye(?F+|4j!UD;1W`Asss<$5^F~aAQTCeN}1D0(Q>b^cXhGY zU9p&J8F_s5`t`2#YP>)*l{oC@M~I^9Ss0oF$H~J`@WHrf3+LulEok95>u{gipED>R zQi}8Gq8X$NZWabX9w>-DPMG@5R;Uv?l2ueYZ8Mh7u8NOPn;vZ%wI1u;J$>tQjRZyi z82{PNKNa;a7w|UpHgptp6b#H1CgtIz!9+O2vVgPTPsG=Okl$tURCyi1(dyJDYagRe zGgvx8Y;ziEBHj}H7_|~#J|*H#bYI)TKxn~yVw!M+91m5eC8&GoUpMB#h>Icms3W3Y z1VLV|3m73uX=&w3jT2BcHSN@myxQQbk}vVHmN?fE;YvX!t!XfKE?z=zi*xH(EK5Eh zS;s|qAo8%a7qo_YfUC%Eb=!%s!L_kj3M~*?bX6O6$mbzaPS-8W%^fyR=lQbxSc*V- zV_2%ocdDe+b6<_9V5GS=ZnF;dk76j>h+p##wm(UyZtfc)9on=l{}ti5@{NZ7sUvD- zWGwa=4ns;8UsoA!!C5?>Y{-DOhLWVxWO-oRNf|%%#bfy|Xx2poTOi)wQ5W zMF3F=HICxYv=7>)x*ESQC=ydGHNzyNslmmdX7RX!)J2!EO#9?$0fXnv)lVSJW*VpB zQp0qNu-H7GOSj9sh)v{afm&6!4QLyzXvPW7e(2@>8SgaAyXwBT*E`|vILKnzsITk# zlJ-3bDSx~#6YYsAoy!p0pa#k7wp6bK5ups8pna4_MN#fmp80To$i#bmIIHJ`D=zWg zx<+(~5I7ez3tdfZxudA`h}TZ-DY&N}lfRmyZ)g+hDc6iIoWVT2`g?q6bor#GdQa=r zNp{&P*65cmXPq_)cHt}3h#%N>4%)wzb1qobd*J2ik(V!@EuLk*cfRvfNp#Vv)I=_v zF`qhzdw9!s(Otby?(}#peom@`%AY>_ncHoRe_1M(lGYz3D{nr0y`@KMhJD4xLYv&Z z>~THlEAY}_M5+%;-IT2i6Yior|D9X z(+qi}I;BbOI^~wSDyviSh^YUI!d2B(L4u{G$V#4EIG-rylrq~*O`)us=2$-GS!dUM zRX2|~K1cqNWD3ZMno3wN$!+LFqL!vK^-gi6CMdNcPldRoI-8HE51xl|92e$GmLO#5nU%KUw}$o-amw=DI@laiJ^vxVcTM|HdLQ)oL^|GnK%1+&;xb zuI3}`ZaHHIp)Z%l=61Lv+o{j#b{HpVF3as1LUlK_C24`LC8o(|y;Ljom$mXalRD%n z(R8J9*P`E%LbN}yA2aK$IMZD(4u$J#@op{+xh* z5=hR+dQ&zYqSwts+uxqCPj*&Ktv2>n4W<8bo?N5b=AblIh^eB}PWHjkwtV1& z8!MA7G@7}=HOOfCnDj__p`PWeiuzQQ5yPz%NM zzLCW(;dY`i<)o|lDPzlSjd^}L{d3@+yj5D*ep8EX%f;(z%YDaT zrS?=#o5j*g!&XL9!Tw)U1&nFQ4CaTaB3HVO2FB&Aj3jPCSCPx(4QdBR2UrK+#5ax0 zhBmu{?!-5}OK$hXTc+FW3GD>+lqY*VQ$1rnX+q9F_B+i2_B(04-2SruwEnvO{V!sl zZ7<5V=7*5CM@RfAJse+M?@cf8j*!z`KDMhH*Jag{o9^m!y24lOFV&uxug}#f0gVBn zuzY-rO##InIj{Olc}@J9RvwikmbwZRy`!A-%zIU(yoWHeby7~$ymvaL^<&y?)ucW% zPto&;4Q%*5095LdKzuT#!Z&W%qCf{rHIS8IO<%Qa3l7(p#% z8!ywbdm4nBI;ob$ov-XQYc8)8^rkg&D$eAzaMimOjdihx_sgBCS2mZeo0K5it0r|T z?&K_<QBMpyMGJHR)q=`A8w?SppKC|aRcp!;l3*q7cbdzdQq{^$ z!)!-!v?&nv;b8e0>=j!H+^3niork{;rG*)MgcrEo0e zM{v*83^3@KXLn-tMquKK>i4NXe&?s`wRen@d3&BgSxR~m4VBeXxntAh`@(M@y~8Lu z6#r5Nd`>w2YQG=hrA;r*Orf2LQ6&tdOq-!NN*G}(UW(ce4IZ4ULpjwJYi?t`fIZgYrNRlUfk zu-ZZFie-!JBjf7Fh#GiPciD^Zwt2~z>>+eA2%T*tyGC06Rw#?Gok?r|*z*!CtPI_JACJ!CiamN>@{7`pWne_7ZE_XZKv%{<6Irh482-ksm09|sHG z0P3Y5u)|irQU$^4g{Z!b`hrRn*t2Tu^4beNe6>ukS{11jSZ1;l+erQM=5uDQsZBwh zHVBhHZW+8n#V}@#rTysiHl*Gy^h2S*8w6p2`+;rldwCbwnoyE!^_$l<9ms|LZa3&o z?JDI6q?jIrYQ7CqOTN-_+uOL8)HAY69AwcPqv!le~4A$)8Yg%h}P3zL=w zMtY+DiJ1C9ua2{&FUB2EyGpiZKZMGURVCF~t06MS;c`N6&l1y+%*9|3iIBX(leEDT zQpie56C2_d7SfO&k|HH+h$=TTV9oLo4m@Sey7F`7#}~TAZ*;uUbc>)eHl0yus9a~P zhQOCMgyu6#7tM|=_`#6 zeCl?Ksp#MJPTzkvUUKtH)LzM(LD{kB(xx0cFuddr((dUy`I5CG@0MWe zMbFo7{dv!Ndc0EEtKgMNLMY~)w#170o;#7}$VMdZaqmlPG=H>>eb*ZI6u$dxcGsHm zL}}(+DZR;L)vGBb1Hv_?+o1dMHM)Y~OB>APQ+3OX#a9twYgTCXCc$I5tDqs2NtC<$ z+fc;h;J0R6L)M&|--ytU^Q|~smX<-&HNLuoVan`0q_ExM1da2RV2y;sHZcTNGnq&&Goz@Mfth`t=lY}I}i4sr@>5$qO{WU0sDfrL~Jf8EYsrR)K7zsC7M zh>&xNV&Z#}J@ugZ@WYGBD*$5@?m56|z0;r+GpmT}PB~MTXw-VclR~W3b+l!O*QhLz z@dwYL4|icOqb{WfY~#n*fbDnpB)?j(t=HZX1XA1}r>uP-2yPX^Ebm%I&cD@N+%qb@ zFuMp~fwA(zU&CfuZ%WT0t}Ghy>#hv2Lk zV}&Pk`%%v&@_^CCBW42~+}e4cs7u}Sx@`QJ8QeN_Z@29I>wc=KEIH`t|puEP@82 zA%kyYa7mcSgpC4vBT$pk{uJx(KgKxuedGC>oBnZJ8r76Cq#8qb42?8DFvOHZPdy|l zA=2|@?fw2nGN~&g-td^xj8s@EpfD7b4Gl;kI4K;G@>ULm%?*W0QO zy4&hWkhfLXx;5%aMc76(?IprVfVbKLzB4kbR~RmjZ(dxLsMRSlmulFkn$CK0OBaDt5!u`$;T z)XLu$CjUDZpH3yzvv!V5i{7JjnZ&OBJ{y1M_pz>*4BT9mo2~k`-xW;sesOi~+)_s10kGRJXqjH168&aIg|)-)XszZ>=3bev00ND6xj+g7t)0)~1t% zs2$9)Dy0f%-Dc;4zwB$L`s$=zTov}j_d%NQ7Qme;2wM`2^s!Y(%&T#j;s)O$Xk-u$ zSfBC@T%(eTbWLAAWf5|bEJpZJ2#%;nx~dJ0DT;far6ft`8P zZ^05=#4<|;Rc)LTyk>}gZJ*x^`&H>uA;R3T?apNB_Rl0ZXr`dE`crO13kZhEl^=0^c5F8XOZyph7%V&@zY0-|~U;Oo`w zk<(FQ7ZZq^-BXOqN)A7o*Z0T7^M$y&^Ut`^1^n=Rhum}-iYxh-q;$TCtA?uY%YK1Q zja`tQUK^frH@&iMXLdnI=vX*Kj_Y{uf}`9{Nuc|ry{ z$x!xt{YB5SVt7p)nsmKRRCPrO|LXTPb+C&4;_C>ZmdO}KKxFeabW@UyYvS85>rWQI zFq+XM*WWrR*>O{j(6u_8cV2{u@M9Emv>0G>jN!a+m@Bk}!u|n*OP-0>S0H*`6yss` z>7R4SzUTyQL%U&Q^`>Jq9J_@y5!y*__?lh~)IYI9n_#xeU-L7gM0*1^b;xdKOe}Wh zNsjsYo29lHHh6|phfcIijpEWQK2aX@_Q8NP>J#)uMa+#i`>f0GkayZ$Kfe}jzqNUl z+fMZXNq<%FhASe_HX076t;^0h^ z)&g4-*2$S^(?f25i13&tkoyalbnL8TZr>$iLq786moG|h9fcUzPQ=C!0_M`bGmgmA zS3HC~oYPvDlc1-24k;T8?4nSA?tBAaVwQBLh4uQE9GIicd6KPz zscK53rJjMs+d&H33a5U(Arx9`(==rv?=FFXyZos&HZsI5Cd5rk>oEbeQn@i%@nWeK z$|@)}S6>dbaXlph(m}!XZb|M?n-d~5<5WGPi@oQ^*#hs|s(~E60QxE3AC0Kp$zm25 zoYG9KP;*T12DM!puA-xqM2tQI8YII9@^pFzo> zKkYZDj=FID+5GF_y{{TicL`y~FdiLO0C{jZRQ_;F0q4S#HWQzjYnfql!~mzy7|rPN z1B)Bt0kZBANeNGYV{rK~>NKHVzW<;@bTTsY#8)HkUP<`my^kz}2LEI6>=y~I&07?L zc9`JPzq=s;SvRoSz1;|NgCg|Wy+-_ImI_I!JwuX{#=s|KerVJD?L-f*hhU46rVq~D zI_&KQ1XcTn&t7RjbBb97GN?qq+}nwt>_UcNZLkiIY>Elxs5}#8U+(S3pK~F@w>H=S zSTw}|{sr;R_hdd3rTC8@WU=Qd?;nRsnSm%Y3ptz4Ft|mGiqNA#5`b*58_j6rE!1jf`F6inf<@RbELt*>S^V|3{ou>ZZ0GI! z0Xfykc00_3oN|J|Eh;)OU9NvY>Kc(l)6+IfPE=n=?{ww|og;1Q0b3(?SAxT)3OUIV z4vahP9zAat{@5-}a0MAIn*3d zDUAU5X7kZTxKi^`wm+m`Hq(H{NN&?ve*@F!6<*y&V(c*Cg%hqPxhLLWP>%(Ip~uC1 zKeub^Tz?~d&BsT9ey#AqjT+;?5o|pU*X4({D#T^Kt(WBHzsKX{!1(g_FQr7+U~W*K z@SpzU&FKQ(qG@H(+C6$|4N8#76g8hg=4M^7r0-?ci1U7KhS&#vyAG4cFIYGGg8>5i z(zBtLp?jFtt*I)aw-4$F-AF1vufm`VpGuoz9bSH$LmiygyO(;xn((cD2Wx+&e_z8+ zdR5zQd1~Gpu+WCWGSr-*77bfPf^WBqoCGs_*k**n?vDLS(B+NaOImxls1A%%9@RIk z!IW#QKXAF9f-Bskq8jNg_@4$OQvz6ZzmTd5s}87@67bQ5^f$b2{8ij2@si|yQY%AA z73IK;>!o0EjvyDpz}KP@rQ9jZ1Ru)^(}{p_p?s-{xSzwh4k^QomieDzSrtgA{-8?XruvKF(AZ)SlFG!(1CyH3hUczBOdjVYh z?%pofGQyv++m_A^VyU*#qf{QCSyaej>wiZy6>{kM{}_O<@j|M90!U%wSycakEn#X( zaW1+3P zzMoL20kjHj_-{Ig1;bwZ{|Qfpq9SwIJ*?!%F+$J;ic%E1Iym&E9HFIrax<&AC{+mv9H;@gxw88`+Vos;7KZX}Rt@_VM zW4dN5R=AubFqHjEBghLmEr5Z#pLpz~?HHuIB>x$0jamc7OWMap=U3@;D!4h&zH|K? zftPQBy61kIh`$LrgNk=!A-RjVIEeJ7212ROoCgR7@=FiQ$4F>?vtCYR*-J z=VdJ$>!=Bsu2Q0-*%#L2xK&DPt>t>quc5Nsa6TPh3;rjiy134~PYmR?)S(jJHO1HE z(G%?qY38{^cnVB*FptuJ=?c~MVNTLqpl*Yyq!PSXCH+b>No7D^F_Rh_#Xl!jgtZtt zH>b9==4jEbH1Ia1n9P`~1b9o7AU?-@*vRYdAPe;zW<(;9W)Gt8Q=E;rXlG43f=G0* z#Alw%L`6z_jhP;%pDbbt9~vK964z~bIS+o~-qfU2K36L{b2OhEWF>}-U1-lpePE4E z2Xcd15^qYHlRk@MvXh(31yk!eZkRY;M{vdPnd_*d{A~|psusg1xJ+%Db^lTf~{-s0vVc7hh z7e&`vpDYp)1|YV^=aRY8v;IgV)Ac>O@vNl0=xoH6s0Cfyx{2T!;Tiuq!n~>F^Cc@7 zu@Qd=R6&c#c71b*ZP7J+>gIqZf`CacYmdQJH9076Pt4{lYY)%XSX3XJvNU;^zb;MF ztw={(7wgAET^H*{Hi@6T{C4X+j#d_&YPa-P!$Vc;ZwK3dl%?tW(qK`kWN9YYsr4ID z|0!cpCFZ>xFDVWv(91y3iUG28GDT?O09msDK{8CdY}pJ!9_s4Y7hoz-<;zNDCkZkI zI>{HN3c|j}qKeBKeH$oB#VVN1$xU*mSw}I3H*a8e*>3C#_MVs()*85E}B*~Iy6dTHd@#x z(jQlLX-3(;wBz`M`?0j1cAbw?_W;?@2_D0i2a5rg=@2(HZ z)17>TU-BM(gvYOO<3|<6A9zMkYd>t2%W!lE8L>{p=+NFf6E_AfJocOKmOZdqG`}qP zK8<~oaoMrFR=Hhb{BTA@w!ER>Uieu$&^B{9S`~v(I`AjYVUA$v`@Bx0yX#<)N)V;o zTf#m!Rz*FHTuUa`Pm%5JHfeV*9;ykvuPEKT-b;6}R&k{!a&ef6d-Y#j(9$+f9TF9K zI6s6@8D?4)IE0Rq-#hR?B#;fIH+AI+EVk!EX?qgs0$v+AF)=l=ieZUr$fE5IiAHkDCwz zC|y_jzy&T|Uw2KKIO!~!-XyM@XL!RPh`1QKv_CB|jBfyNPey>m)_U%$yAREpUFv%l z%|SA_5e!+g#xw$@P3;!iVfm^@n4mO0as{A1veCX16CP@guefLinp2@Q&txs(vkDyY zsICt(%ryUxBN(#@WQb{SL$#3Bd^W?8Y@$=41P`*&rVn?OQ2Jc<@L{2w^%RV*?v%gF zTa*p7hqqDdOv$HJHU*LmI@qN@MVxzqJm{_QxDpk-mlWKGWzr>z1=GS}pV$@MR-@?} z=+Ap2faTwkp zLeAA7=X0Blqno!eYj|^B0E<@9GA|}q2N&vFZ5-JSWWHQux?AG>hRpJQ&b zP6U2u-S0uSreTDA1YXlQz!+ceuoHZ$*CFfjox{@d!&y^^TtpzQ(;gyWjS_Zk?>ctN zrifMzM7P~hVEMWQRlC~1bt&oX2s?$`k*jA?TW!;|Q^GJE4@hlR( zlTi55=r~mn?@;)QIXxmadIb@V>jhWOKCW2t`dV|1nNtQTW3A$0tu6ox+>T&wKJo{c z-h7ts*yDsTUbpagL)_KVZV4dnq2zrsn4P_$_L$!oS!Fy^(?mVCY#O~HeTXl6h8%S9 z+rMMzT5);eni<(ZAbO^MLiUat_`GzW)0uc`IPyGe`rv=BwFaSOg3;*`SkG*NqA+Zl zcRx4GfUoB{c#lh5YIyJ)fg5;T4RR&;T@4lZ*O30P@f$cmZJm)xoQOb;+f)7lJ9G~D zzW(qBp0g`&!0VUH3_uK}pVq~nN@>Ju)^i6KRw3fgb9u~*C9gHhUei@KX4W=x?47tD z9_lQ-q@G*Sz!-NrU_6?xUb@R#Y8bcDGA-aY;qV$dPPI;4GERgn7?#bdMJ=dlISrdT z4SMiw!|@m;6J9hCDm+1-4uECFwv0)0f=hO4?P|-2GsTV)lMUoX^t2)Y_HNvegj*}wL;`c#OSuXe}}$v%dCCoVXE4m7%>ZC{L6Nu z2k?~d7x5hQg?2~DxgA20`3;3@HS;wXCYZP#B9QMLLapjY`L*{`-RIy5Z? zySnXMl41c{N9Ac+jA9F0CXED^iHRNORs~H9)F)2;Rl2V1bUjY}73p>se6{!kWmnwW zg11+GFMq$I+>fR`vNKc#A4as4fvOK^j_mYtyCm%NiSjnW1m;$+1a4y6cB>k`CR&Y> z%W%^YmFJG1l)8-R6g_AZE|!iv%hjj$8yeSZ4_hR#G~4BWG8VVXS8+U1T5?x{YTsTk z-Le#$L5t^`&kH5uwM)bqmGf{0@~Zn6b4bhN1Knwt^T?|~8An(s3TKQY*y*Z@dP-GV zQx+<03ra&yFYX(?_)4uj3l+izT7E;$68T3rrB>}J>-haz)hmsX^HTX+H^mkrC9qih zfzp(l5=2|6R7FXnq?lfL3L9j`SFBcMHVsOlS02I!=`|P6(kPXvd}>t6Wxy(u5N;Hy z(omWQ(pbv5))X>p6-y?DjpqR!im0@J^JK+*!1J!P3e%1Us^q;KT^3_%0heMnAF(|AJt+ZZV+Gio}-*PrZdt+AlWJfM%qFzYQ}25~}e{rcZEc-VPS z^U_nu5c9GLc%J4y#A%)Vwj|z1G0Lri!>|Ga?@%KI6UdkA#9fGwHq0enIf6JVe)KIl z9%Q^QUbK`IFZz*uP6wz!o33tJy}J7DhB{fha$DJ46Ac`@1fx8>kFjDEW(T?tl1xp zs^;m!jIxFBO`~Gi@l0c5*m0@FI)G#Sjx{FLL^<)aZA3X&A4+>n+#{5aO+?f&0S&>} zTQSfNuCXy3xULxVVWKPgmwcit2A5KzCro!lIo4tOQ+++9yc+y`c=f8dXUs9~I|co? zY&O38O%%mv;B#JgN(;XINX6EzGO58qX3v&08lY!m-F+G&^(U_s)&a zV5&9}&p4qwDxH?Oo%hulHsaKoa16h?NL^mWwHe-L(XK9(I*rzSKCp^Z;j8bE9Eu2G z%6hTHvT8ZIM0fh@NL@4RLbcOjZSe&CgaW2_wqNeBF=KGGGn}dHho&#<+B70 zgV_TF4TTNi#jwTLh3Z+m3}MGW6JH>#y{6<;lPd%J2*ds{&?*}!GOSLic96SEb+Kq} zRZ+h+Nbh{=F1y8FeSqS*+iK=nKdeJ=ozkIgKWqMwc4pr_g3DpNsF{~x4TYqWsK{jp zzL%gX7uF(@O@xoo;ZKArO_tU$9(16CbS}ikwDi4@wr4)}<4@(kf|&ZemWWu^yTs0^ zEtG)SANOfs&0;|M?aFd-OH5|HqSXN=ASOwLt21XMAQqCtnDzNo*|i>j-ey&LLKczY za;ItCs8~jvCVm(KsN}E=2^^$r0h1g_LrZgl)h#F0g-b=I%R5`BXs2new|JUGAj-uM zb(Px{K?{dk4q>(dw1jwzJOfa#QPG@PS?9HQy}MCq+2LjXd*f0jbAX!Mu$>c=F^;`0 zQx}e$2U-`-oLEJnu?U~UqsXL=!4V(nsM!(UbUM~m$v3*NWr;_teP@k}V*lYi4x^W) zXe$EMfndrbQwbxt^F$~H{V=X+*+%~sdN-p&EGfd)-iB{!y^-0i~X%+yaLTs0szA> zIozvG>DGkYOT+qR$Tk)*O zk2-ccrrV|)@fNfBCl0L|GO5#uj?^yaFmsxj?ws~b;M}~Konl8NLXhYS%Pl`{EGwP|K4W#I| z)?bAyf3NPpIKWB&xf>^3@QH?8jyhSa)g(?h=M!FDfAdPj@`ach)8SLcv4F>C+H>zq zzG+=35!fRNlJ-i>F7)?2Qy_mmy>JIb)N_`zqZ>EUjAq62&Lz$)>DLq0lq!Haer5VO zj8j|gY3Up8Sy+S%b&CA$vhI5+PG$?NiBi1=CElv_WXD=W%bJia<%+&ADP_9K^=Pc% z5>A7?%slUeC$)L&JkNw{wRvOHOaQ-LoUIzGUal<}0N-@++lD9}$9P>#H$V|6E*YOh zQz0ioV665yBuYH<$e(?mZh(ag>XkLG!Ud#`aZa%-(C4d5LWa`xL+xLlCfpjtn zX-YsGB<$MjRyiFD$*^#xkIxi{E3CuW%H$zPGu|>rQPM4BUUBwlk$ zG8kf_y%QqUd8!wjeyH4zD}87geb+~ug8}r80>8fiJ_EeFojb_J0J_So2prkC9QuuA zNI&33JF3Q7Y`j^0A6@WUQJ^a6=u$=8Sj4~J&qyRYf^G4ndvG~l783pn{!VQlxQ5&6 zZ{7@dP@YkH;O%TMp5i``^M@nYkTA6Ctd-d^F|_5~;Cmpei1jGbBhVHn zixqEa!NJf~9#Xqu1tVRUfjh41UeyMOnOSuawp_cg=H)XwOp1=h&9i8ZbgJo!9L;f} zWL`c9hNzV$O=%MzmwCd`e>iXQS#m*eZ?A%Epn~R6x2Kc>?+@^Ds2zhz13PRumjlU0 z1DrRgR292KcgI=Sl0HeECqg26B!^G-&}$^D?kBAWWX`oO#}iXHUdKJkH|^;gvmc}9m> zNt?O@3BXY`%F5yk3Of)dwPNX=~)NPysl zWFQ!pR}6^0;U&v}+RqVam}?nR$7)9zQ(Gg1U*1X_)lScc3&|Yq*y84INZc@kI}tgR z=J89fyT|9v`C4IX_1LXCw6bLAJ%dxm!r$t{LTb$!= zlvANafA@-B&hQGpL9DTQQf?JtVvI3Lk<0IBp7O$(m}8VVD(0w|&bn@b= z;zHG{PZ_V-K!s`|Lo{Z}eE!eBMn0o3my5$G_x+;5RV9Z3ZR@`()V?yW{y~z_gK#6$ zwA+*&{%JTQpWGoT032OM#Y*`^vzSo#K_?`i0*ovmqls}W^?=adDs>)zTDrL<{aK|> zd+&~Kr}(PL*?BVVaBaoywpMEF{^!DNy0h2J{iMM%TV$lnu^nNU{M1T#Bse3~+?EjB zgH(rBWrLAo%VJgZV40C@)+GtiGr9^g5qJEvhflfLH;^uM5tw`Z5|}*`Bs24dS!LXu zcVo`%rxKH6yK_3G5{w{uYL>}rWQk^*5&(}PF1V8p*0e2i$vX%i8h(_<9fd=zrde<^#E2bWK<7q&l}nw zRjNTa_LPUB6(DxS*VMclS38b+%{asHPY}1WdVDBYb4c^mX-IUP8tQpJ z!ZV8NhFt&oa`O4pqOt%EZF}=o`fgbMWI+Y;!59)#Db9r3{|$Fu7G0^cK?F7WfAB<$ zMSOjkuAB64ZjDX$9jW>vJFF{}begLMPO17sb_MYc%88a{eTXcC8QFDNmcJ0!eDtTg zt6)o+KTS93&&~|bC%SW;l}TG~DCDniwxdwRAv}^(SpO7Hm-jPIu`*6Pq4;6zTuAom zIg&Ex6@U}-Uh??pZSWv#c_KUH!IPRq1}31K8~CnqszsJjMTBeiG>YP#a6M)_y^4u z{{=vk_KN#lG*@X<*B+=_-tVdqgs6USEVq9amF+C-=kM+~Y2$QJQ1Z+OTP^`|fOdyy z+^T-N1xlCIVHwI}6Etq>A~K*|Q!KvH|DNb@%P4uaY02w~FSFboTli3~_5#A#-R1OB~qPbM7OGrLPt0M{?(ENM&9sQ`t@7g1K`p zhNf?gWbV>uu60QzXI?1~IOXRvV45R;<)g@XwI4N{wBEw9=p1xEY_o!!&a9;HbaYun zL>v{{1T~Pnm^aPHjAXH)7-sIC(-!EN8FA!eOzF=yc9`7bHVcy&vDw3A;OodUuDe7` z%osB@KB%s8oS)-9I!wX$^uo3hWU=a|*KWmnK3Xg|XfV~{(7Lct)TzR>TU0BT!jYcY z1iGbjmSPk*u2)N^Iq_TA|C>!DPYj1xz8D0@+MqNg5(a-c7xJ&LtzuO81eleU( zg?qNB)<}>L%Lu`YcJP5Mj8!LHua*{nmrT!tLr`Yq?cTW#Ot|Ay+;HYPNWX;}91ns- zDL;{_4kG2c947Tdy8cGJztQo>E<)C`d8XjeL6Y2?qokF{+kXdFFjra!!LTm!&gTN< zr>Xu{^;;F%XR|G04yMG^qyeeH4T^1Bv?0w-0tTSRwdFRcyrytU_&0krQwGu@k=*(a zR-Bk^(((VhIHN^8HM8WSA((1!gRc^^P9v`=((hVVdaTlN{rkqHE;MMFTMuBZjR7BA z&0C$gBXkf&311O>n@_GkiXOB@7`w805JaaJ(IanwspcQ0E9M^0e z*XIk+FkM$psf(wu$_xGf=3>_VTvwJ~6c5w*Zc9sTMc!4KTNnR77GJ*DUvINTXRlM& zRLta{LKp@O0vGGsh+HleUvW9`zkurvyZFzzsAF%(ik4-`Ds1b-Wsz&Qtrs zJAj+xr!swMVb)!JuZ;aXl2Qo}_cn)jH#Zruys6@VQ2ecqF^|V;vQE*)2@_|$Bki0e z^J-dvQ}!4G%Y2|{W9~yOCBW&hZLHth`%*85FWQ5=MEnU`qJyN*P#54;#W!aahqY!} z<-*jch~zPCDYx_fug?B0+uy^X}v zFL88q`FuuCc+FK-`c_A(1aU>`Qf!B!Tgd)Ab}V<3wqb32mlfnPWFnDPwI5qK^43&~ z#W3Je`6_?d71}ne=6zbCWbH`Sm#$nha3G{xw?A3P{lJ?NfHw(54$dNq9rF<-2vbZV zCJeF{^JS~djv?3NYfBoXj(Fd=@AalO z)AN$Ah24rTpf40=gd9pdo91!}@mn-CGWFq+9IUcBZTAQe*(PlZ6XxWqR>FAd)I`_WF5Cw$wAY zB6EQF93n0yF($+|x}>8aeDZNdwVZ2r}I zUxw~>$T!XQw z(rm~Y&stBg7en>$_{&`mBHkd21&D1Pqu$q`ZWz3^ZMoR+*y*_)j=D%E-r>=4d zByA1Z4t`XG7ZnXf5%ra|=GBfbxFM`zx!(<(Fo^>gdpYzY(tz8LW9;g3go$$ST>EoI0om*9G5u!Zpr-PyBr@hv2|QG)^!#srDdF=&QJ6gO7o0W=!qwRKD_kUwJeM z(1Y98*50nY-3V!0+jwi!3G4yqFqb%bH5(L&0CGckVMRS~EdPY+a| zz8^rka~g{s7iPS?9pZ$x-4fkPB#W%0?b7%cWqc64N}E<(F%^Q0A~csUcBQP&%-Tso zG_`0q!atYMkG0P2R4m@P>AdB$#O=-)3ppS2XAs>IAI1D(fc?=pF`H!`p3``a5+`z; z@mH_b+Y}KuMz?P}MUOIJHQ-sG>T678$%C_Q7PIVc*C>1ZhuuYV z74o^&j;pLiuFE&u50zYmKPzx8(W9FLziE?zoz~0<>$cZ?lmIo!AkFHb1lXvdQJC6~ zL*J;n;l;PspCYz#vpG?>SZDTI@;yFarUEg$S&G-1rtg--21YOHKd7+d%$nG7WNTa1 zTTb&F)iFOr%$&fu!CyvMi0u)MG9pgtYWXDX&AgVyTE9VZ_EIgg*VNh#u>I8n-^DG_ zh+Yw&#TTHCc%*w-4JQ?%DRCeq6VhcLQM-7(VTH1~jO?c$P(@vVO8%bCIkf7vyjvlO zeC%2|D#81Y`1f-AKFh)5`C4LHoUQkdIre?u<1NONYnf6O2 z0Zy!6Ip}tv4b6c|-&h~edELrO!KR@lQ`c9mZN`ZT9j6tOJV?Yp6S&8w8%E@FXVtIe z)T#$Wj+F~u%hRlX%QZ96A)u=DGiIws@vv3waCF?9`4!`LsW>_*sBYPLkQJ$F(ucRzT zC)=plMyQ9c%}L3HGZjeYV|<)CfrPd-)(SR+uF`=fLa*mh%`?J|nsa2FPi#vCt_`5r zkOqcBUyVk`7BV*26ccMKXTP^SJE2B=c-2!ikS?Z%K;6eY?Ex7lu`s%c0VEpX! z_xfsI{#-!B7$w~wD@7&fAmJ)irRiv+Dq2+)^}dD*t4oA~J}aP735vxX+aZw?aj7v% zm5Qt#s(T`GRrYL$1?TmL>sL;h0E3)gws;!9)?F}`Ba}9VTqP3P7f#tr*sVu+%DEq8 znlL~;JQu@s0Gbd zZDV#jmBYY5d<}9i7DqW7u%Iq}E6lyK*&@|N_95^HRVIXTmY+!$(EwJj{6qV7c!eGv z4^?-l?O}8h6-A{W(|1YT*3gqqhkTI))i$Q*kWvB@MphXoP0EAo*P90JIiDT)*B{d=hF!c;F90m@D;tB(*r&m1UBhrsAys{I!#cLt zT(A#PJLNgI3sxog1MrP!R=mXP6m?iPjmKsji756Li}Vlr&Z&(6@8e?!=X3A+#w9+F z+{O{UPO-+;`qr~DO?upC_4m{_E_>pMGSTAQXQ7o5lA99-m6F?4hK6c@kr?VqhD^o@ z12`QTo?c{+oF-YPozzW0C5iymNu2+izL`yYV2y`H3)y}jbkiN^OF$7E9&}E|CCHkP zvq@#^`ylT{t=@ZP*JMK78nY6jY!~(*U7awo$vP%&)SpZ$G#n!kifk9EN&56Du}|k^ zFH{|jkvMQ=*IXxV^)pq(xnAQKW2RS7WZexb=k=Hdc@gWF3Y(H;r%-Xa8DoYb4oISF zqBwk?G($n97}RyK&+VAwBC?cgg9)TOx<^KtaXuCDtMA<0>Kr&(kpZm z=A#WN+L;>A%XL#iLf4;4-KRgZzKX=7Yf%=OqSQ;flk}v>FCLlNS$AiX2cS<(Chkj| z<)ma8h3(Nu&|^s`)%2r*CY2pkH>r;5^~wsG%Be1o&yLaN8%G*fNUVLJI}$GgdXx zp4cBsC)jxDd57wmAM`mV_OLUEmya|t$n^End-Q0!ayh-jzXQJ8YlrTkPRzK{=6es? z8EX#qCjqR)2d!H+*e?d|OBZXrRGy7$qn>@9i|>7&eFdfoH@&xHY*wJHXOxYZH!F7v zOK4B5-YGoe&tsW8k>^zpdB>dI^{tEP>t&bTwyjrHk5|V~uZ+x{;p^HPo<*+hmDWXA zev2>8+1?-e2Y!CU>};U*tNaRVv5tQEu40N{@f7D<{n1(Yqy!c4j8-w$vzD;zFbe6y( z)>5Al(5&nf(M(cPdx*Edc&+Rzr75Z@Hs@alExlb?Jq@@s7t_rt92-9gHFlj-0OC3Q(_IXzB0KF3cp~%~Omr!`Z^lXhCllIT>G&efnQay<>D_U9`5{ zv2EMxbZon0Cmq{1EAH6nm>pXk+qP}nNxk))bH@0-QMGDb^Io&&+Vy9TU5m>FMb6Tj z=J(X6GHQ}>T%S&fiF}AeIvROg38xkGYDv808iPmG^KzwF$a4$|I@bu^dBm80f_e`U zZ+M@Uw?4s-ZtU)&o2}zi@f~g_ha{wA?a`RxE}U7yJT*l=Etkz6*8Sj}Qq$aPCNzQu zez32hZ?*c+v-lar5s#CR8gX7gLD$PKx0L;TI z^P;s6G4JND_87l=KFohLA@%XP0$awD9kBG-5>b;a>_tM{t0RHMsw$`8*=FxT-IQZV zsax8=oTthUgbI#FE%feG38wg+qF1YIt=7;tnQ8O&qm?_chebZ4w!CHO`L@1~H7DZW z#nI%`EmQ>J6FeYyQuBJOBvcF48*wU`Wt5c_$)ogMWJDy)Al*7~Qf==XXGq8UBb@vZ zPwQEu`0SKlwH&Wz$o~cK13y%9px@L^=0!4LQn1J=nD||9*5!8YhRN|A$D)7>n{Yqi z*Q&8)(PWwlom$!iGXIuVzv6MCiPtzzRbl&x^-f@QcT> zEixyR>1S=U!En|Gbxdz(bn$P?MNF;H$;y!atn!dg1bISgN{>0%lqZTkY87woPSefB4+&IyDUfA18WJJGlHI+jxo6N5Q| zp{}A9aSr8}u!u}?2XD;>tWQf?#@b8vjTL?NQBWDI+R?}>8*0wGjA2!eY2zU#5!e0+U##;}92UT^K)t$-l8rvdB z4mBH^8R`<6|E>k?Xn|xKG+W8$3S@N0a|KFNM$KNWVoC0s>%|OBR!6^*ne()-oy=Y^ zcZUbC5tyetD-Nl)-$UjZAg|As(RU`8mH4&EhcWGVZ;oGM8#O= zkqZ?Q?(p0tC@e9(*zwUxt01MNds;9Hu1R5x7+{P>dI^iBi;L59(~OSeqYg2Jf7Ame zE3AlIF&iRPQaIpA{8^MM1*4y>q;Rb`QeH(c<)0d z)0I+GA78Yn2B_J!DJ3)*cPS+|7+zK;pAHRCZ${S`mDMUpslC(aORNe>+5clhPXp_m z`i*BFwochgI#YB5gP7_!DkV#=tUi_Wmb{wkL)b^L=+_(h7K%zbJl!w(3GIDb(yId= z0+~M?V3Bpscov602FJ|QM-Eex^V-VnF$=3zG!qKj#8mX8&)lR>^SwzAz1;-p+=ANx z{B9JKt!*hg@hLmFCPb=v_5i|R(b(&6(|n71n1A0YzLE>B!c5cZ67_~;IfG4wf>Z*O z1E3}`&SZ)Z)gXRX{9Tye0Y-a6X91y4;{XrcfhsKJI0;{g-f#~rCj5}@{NGJ%f{*MT zQ5F!Q2>yvJDitBB_A6O)55e_wzrxayRK(3DDtw3=fluX4veZX6@=L}32Qn}^NOZCp!#9p zJ#Q1(#@zyTIe5E2-7jeIi2JnQ`C8$AgN)ofKm6 zjt3^VX-M`zq(bKVGcX+_`QW0T#tz=qKguy*7C$sVir^gU9UUA+H*pHW8O$RcnKZ&2 zEi67HQmN?7cgu(kNf8QJM0}W-GUpjeX{ipc8Nvq~NSweDi5YPAs^F!F7PvJP=NV>8 zm_N~Z0+zhcLWS^zm!4p#1>e+JoECV2H_x zJVTN<3%d6{GC>c3Z8dj1G>*!4D z$~p#1;IT95b!=rDS1=)L&lEO{lVEJykn(#=Y6q#OjguS_N*Sk*UokCaX|@U!@%+&vShKH$|EudC#A0`-bgp|6se*F)d!gN zG-T=f@?t0h1$|_|{QL{!C33~11mVm#kxa@e&Sd)v;=r#Sge_5}v<94*_SBRF)D??x zH_*<^p-v&+9-xXvvLK_l$)dP_3ug(L5N=gmNqFYAp(H<7yrT)05{p~TNNGR7Nz5M8 zJz}clK)d(D8(?-b6}M2*{-+FrDY0D0JcAriUq_yu8k3+-nDMYxnI z*uRYnq#wGxNlu~Tk_QXLqr^o&5pXFBaM^WYBz>d%d`r5N36*B6I2=&6eBFk)M1g(=}2dx2aups8gP>hS!qpKOFIVh;IV`0br5A6aWM;HODSm0 z;%R%ym7$`MLGt9>V*LWs9dy4)F(s_&@p&GvtZp|-2{KWp#Z#SAYX59G|-dp%jEJUu3=w@ zcwY#Ip9_*e$=anB??Lzb-Tvjs@xb|}+0r<^x+PB}?@SZTvK6p}t5c^2V0YUPV?m-z zLno=M#4Crc;m>KSmE6d{4~4c>I)3v`*G8<4sx1a)hWrF8(a7BS@^BfOQzQ7En%yn` zJeRwEf5ldyUv+%ypkSAu<5Wcu7bkTw25ZnM?JzGln?~>m-`NpywgN9Ms=Nl@iQtzd ztbR*aaPDBy+~=y_?5NBaxt`**uvKCw(A5u==cb)fGpcTvIW9 zl)I)&)vTO68N^^T$!vQwH96&Px~SwiM|ni`VHUQ*S=+}Dz&@s${W#$bOex&-o?WeW zU9Inekr<>2xpYgp#Vckm6XoZ&7b6V%X~Z#k{AS`cM<-O?ym!_rFai{7 zT}}zlJa15ho6}vPf9*Q;_Loc|s~u*=+=M`<+{otG`u_w-+PU&{0=ECuGn5a(7JEI0To zR%)IJJjL{mYV^phJm{#7Ra;YwSKB%F&3=D_7EAH~C$_77MN-^p9-q3FwBB>ncVS-) zg9c1L=*^Vr=r-c({hA}cK4KgL8Y4@mdvUpLShwp{P4nGm%O7m6ko?Pj5qwiC6c+NI zTYx%l7Z~&ua>5>XV(KI=GH5ymkG>y086Oo^_K9`o=Y_d$Fv`i%iqwc%!~l31NY$jC z0L33)sL=Fe0*anV{753*F9x($to9Sy#j>*T-#!Xl! zHF9r#J4IC&Hvhoe-Ac9sS1uz23L8)fg7L%83G!JwiL&EL3aN6%uPIeDz2>KkH|}o4 zIoEtj5$vTN+eSPvk8Ggcx9-KO-lEqs9JZ;jJ5OVGyxHxy;w9`z_~sgrn8jd|qWWRyHMYJMAuS>n0;t0ybIV5mHp+AP{@ztYBT6-u6E<2D>#HT>WSBgiK-H=# zylswXFC?j-C5{(tyIyd-ggr?NrOnbkV@_`7@wPl=zn5Gu0mHpKy$*aJK5PBKKi@1N z@eBc23+_#Lda9oVRg1$ zZ<%VY0*gAjKrsT_Dlm6Ww~NJ1T(*j+Mr<3hv&R_|;spoeE?}0%rr~hyzO0~oZTN{ zrcepfvi%Sg*CWd~Kk84~(!LlNYj%f-`J2beeF#6{KRniD;zb>n+sO@Dv#yzp#a36a z8;}is7_L_|ub*%<*MG8{f<1XN=S)GoS@`AV1pB%LBs~z# zR>-_DhfJ?%QCky~kdQXm6TQmM!e1a17(}PHC6=b<#Jqg->z40lED@Tsas1GYlj18p z2MkZ!oh67qh( z_KkD?h16c?GTOTxfTdc_1odL--!1aQ$9U0dZSD{A+wb>On7I3JI)S*%8@GUxkg0zi zpi1N6%||-Dm*=GMX;XD>_Ca+^=AddE)zF%V;JnOWVfj?*(NK0)lTgZP%XPcvU2N>S zWqF*Gsp3)X6Ys#0f|AoXX$e{-rJrYVZbIn2`di!NGE8hLJFCEUb*iL~&bEnSni8Mv zU&-d`HJYVjU=hPfg&WhoB(w=F!m!Pop3fBTu7`SpM`y6?>{QwVsenee70Vu~j|K{7 zMT>XkWOn{~l2>l3+D{5*H@#ykmjE0VD+NYHJCS}X4Y(XuZ%F}@j~M2LFJ^lab#44j z8v(tKMejDx#7FZ^E7J4nw|Uj2`N8=MnJ0LGap*d-hmu=Yk!u1)9Mg|qzQREE9Oe_T zCkS0(l6&e7I={ipd+Z02{nH@o8{`fM`TuGO{PC_ulU{>A$lhXw#z+dUUqOczzX1Zq zgo=ND@sP-|y+R5m)W1*){>Hv@cvaIGu({*(}pFpo-m2|5lk{P23KZau`p}WTXL)>SLj5jj2QO*@T z+p^_^&U=>Fso_NI{o;A~J!Tn}*Rn&hw#PHZ5N^l@vceGLP+AnQr7V8er_d7ECko>D zzD1@b#?{L-jsNerJ=W5|ON1d)bR2A|k*|1d6bPPZ_9=qgeh89lS%5%q*ZQ=+dWZ$G zed#6CurtO0*1i1}q4(;$KiSGANcQOg$w>wn0h>$k7K~&`!#KFzMSgY2yvRxlyZ$)=XW#qxZ)0BC1K$%vgRb_gVDvV_B>i zobT|~7ucGyIpNrgiQWcHIe4+#k&gkv1_+q`Q|3TW)q&HA<3+IF325QiBJkw<54g4n zUP8BUuMzwcZnt_s`)hZOexUF70}QNvxo^g!QDFT4we;xr)ZAeEM0sgCWVB=%Sb=IP zKs8&NFOECQ1SH5GlAuh_?4V5YLp~7Kb{$+dnK94MTf?3XJAVJcGzePouaQ2G{|o%D z$xTpTz>KHK4ho(F;)lR~3?RU^gY`yvhyVh}Yi>Cq-L>GmJ-JNafXKgneP%HHj3E7R}Tal+NHBRQmkhZ)LoTpfMGRB5D;XQUkMzG{go;JC*duxsgI>;TBZK zi99{Nu{>OBl%jj4xmWB{a8f;#zE6eCB&!lHwus#g z-iSWn0tyg-0!1)9hHrQ#W#myNPRS#CfJ8UYJ6eaGA{D$@#^Yo5`b zv$l1$XU|-0=O}6+qH{oBbTti-|GQ^qxs25(xKm7i#c;>!&)__J)L$!X#d8QnDRM#h zQXP`gKQfoAKRJ{B)M?ui7dPYvHcT>2Iq%rIgy4L$E0$Q-ifY~{u&b0Zh|kh*6nr6)o7s4~B8`7^jp-?Qz2<`08eZK~WFCb4UpcjFtP z-LpsEvlj}B1D3#`EnaPk_ODIi05fWY12FPiO^)(6WjRwKV9Jh>ce(i_3*NOlxR&`l z@1U_OQPgZ%&k174C18OLyG)f)mhx zd(xmiy-9867{NtJWY5*_g`Stsd`akDx=m84_TppI9vE~U4ec5H9#QR~V|W{Xaf9FURxhtg zxIN<9!}kDR?-Bo?uKX9=dIY4+HpH&T=pBwA7K-)cu_$EdT*#nX(B)nihOk3!BQrya_Sz}JIwYbIm$@+eaW zL9-zZ8kLj<)vKFvB+Dmoh~(1SD0fscod>$;Y2%p9o8tFD*Mo7hCnJe$OLKzznXDo{ zoc_oj8$YV&nX5~p&>g`(7*DOodTB|mQf`j1g|N3X)17Hdx>Vv6GvA5*UXfpoa2P7x zE$t3|ICy95c*4j+qv@WoDeRTf5t)3!++H}zHKyNMlW9tF)2+G{vj$|m7GZiGd+$94 zd;Q)NA2%{wTI~A^Sv*E@^lw>;ujE(E*r>&!OEPJ0+H_$P%Sf6d3(L4>&$0R|JvU-v zaL+EsEB;G2vw_qV0lJE*xrQt|8^hx81aCC{=&mz2Ubh?x*{8`Xt9o+oh$5Z5KebSy zE01WbFiKovA!=-@$xX-i_X13FgOGGT5g`c$uyBV%59wY)MA?a6>0F9qaKZ;n=geX37UJ+huQY(~~@&92r0qFyZ+kQwLDPjCmsD=*-ah4*ht?&@|0eW zxn~O?bif42;I2C5~w4jDpBpkN2 zZyev(zKL2wz~&>7hHwU={{I|dtQbz=-;xBvuy%oG_;%l%EFqHZ8^M%B354Mr!9r%B zOY3H#OW_&;ta-0}??HdWXmAw)T)mmF_?FPLP?O(IXAp&X#xp}5o4E++W>Az^9w3yM z?^Bg29R9TsKFpw(k~N0d7F9lJPh@7Ik8Zwd2AVt<2i zDhyDnoE0i%e29~aceeUzU~SikYK{Mzk})e9LBIq_)`%KH@=Bsy53B>Ss^1FT3gvSH zRe`GI7Xe2Bl8c{cU$Esz0apPEeZVv!{9D#9+?jyg0I)xGUs^N1hXJ84%U=PdVM0?R zFn?~pAZL8{1A;eXzD&y@Cy)4w6d(VR6TKnzk$jS%DLh*!C4$aFEh_(F4B(_aesK}z zSfcOF{B6*5aVlsfK zb~7cu7V#6qB_@m9xv!OOcWdB~3WPC5o~Z9WIZql~uxGpJ|NOWs{cBVknh} ziSB|DoM|aVf2dkb#+-mVQfV^O4}1^3N`VqY0N-${<<|O{5=_;LUQ`LI5wt$tJ6(## zi>H#8q)b$pq^w<-gvQ0Y3N*R&&HE#L-3gqa%z*rfs*SCkYNlVDV@7xQsH6Y%DbQWJ^pxW=?FD*eUDKdU|D;YDWtsL3;JN?!7DPkA_-IP`+xv&3 zCgVG}cYVyssFthAM*VZ-KjyQ#g_F94117D7lj`yrJ@vwNS#b~b;46B(&BgtbYLywi zDu2_h8t~&!dZ+(2j$00dQ}60K9R^TS?|OiHe=P26&HppO|Ko9KBng8AuHBC9Wyh^+ z`mE=7?W||rsC$2*@O%GrPy+h?9}FWHJFo0L;zw{dT2?>tK}XeVBIaBpsr0dbmp?!{ zLYUBO#NSl5NIX92&woN!;s@$OO#KK2RkfVX#m z{MZmY!oBgCcpmXK4|*l5EEc?$fAGROx#!HAu7J$d6gEA%H;v=w$K&pZIk^!;K)!Z4 zxj(ruk6nTMDPubsZ~~yWDrk;eh9KU`N*;89`{e63Lv(n=%59F^gtS75Yc&84FRLa%b|Y z4vlD8&hsp;{s={M2G7#Y{>@g=V9H?1kY_>FI^pY@0d?{NVHVV%n3LS=2GwcaWsBrF ziDSyM!*M@rQ=M`+dY6ZUa6`2^en6#NcY$S@HX=Ln*EBaFMKqIl7RStNNBrLG-0a*4 zxbXX^5ayHLdq(mhFINg>E#k_B;$>=69I4BNL`Hl&gPbI(TPjERS(`EU+w&`)W3EMS z)t9)S%u*rD=FNz&1Ju;>(vI)7I`?)vvYr)3+njUO`zOBlJi2QEp|=vUKq>wvP52d? zgp;BZO2<*<9>qz1?7fp~*5U~_#;j9G-u|yDEfq$9NnVaKg|{rSfEl^>Sj-8XHZvMO zRk(ek7}r01y?x1tC_>&rao5O-St5IJgOPlepCSgWXdOEYwW)1A;;sOKv`+&bO=MjS zN32>ZHvqvb(COrf+trh>t&QH*ZSmv`eRgp%uR=~z^O?J?4b#=FVz#sD$laxVl=q70 zipWu7p)hQE}zJj@OE=7-(Yr-#o)$zTq}tP5bp|+|9P13SH;XUR$;F0@Mm;8tj`M%%(l%H z&IXI&JHrOnN|<-M@q_#M%(~r9wac;*%EE3+VLyXtcndPoR>jE;{p@b%gx`oCtc}o~ zV9=+M!}E`p9)UpOvzy4--4t4FD!DDCAv!V#4Qe`7z`|t}L`xc!-PygqM6PD_geG?U zpwj+hBBrHNj~Z*#+@d%Ew1G!Ku`7 z0>J;b5@alEIeW{ctpBqjmDG`-@FHV7aHu8f8AChpL5e0gOs!1e;Quj(SWDE>gKLoR zmGXJLYmjAe3)D)WAfzSg(*FNdJ&XP)b!E>QBt9qzWZeG0p#O{g&-`C{kXZ?7g3||m zy8?6>7ImB=OiR=+g#Vkc4Ad~-e`}kxGV`ty{-=%qegWpr0tpS(&TKBUdAUBgfn4W{ zw|ZK6R3(IzJy|h*#h7fQI%f(IVT0IVpnuw!M2>Krevxy9w=@;+BYBGEA%seImk3q4 z_==)57WkFYCx+wSGdt0M>34ctwn}_xnopsg;TqM>3U8dZvbX^CNBKprU1{WUrPZG!&!`~JsduZlOao&4eQ7=;`otRlVEdjp!&yi1Zl}wLn%UA zq?1~?0e*?vDltFz2%5zzXNDh4cdjx|>AsN~<1KgAR0N1WiNB)DX$pHfjwc#R9o`Y) zB?_R{OhT~9UGDg1W91jmUrC=_K{tabic*D@SVlL8DVC{$I64$bmZ8N;iNX+OYCIVK zx3*lV&AiZ@B3=V!bl!H(Hi)x|EV0;2UKPSlN9KU8@_TaZqj=5$yx6IhIwe6x8>^-S{z}fom_f%J zKtPEo&&>2o4do!lwTn>XvCfFhpv|z(z|C0A@XS!2{IG;~g>WTs#bHO#A+_DXjI_j2 zw%uVo0Vk_sgblk|f<*1mu)(LrNQM}L!3vTUKFlkZBDNuUZ8!2V$P{5BB^rUP46w054Z}v0vw5;7=Jpxt&qZ~ z1SblYHB}&ZI$&g+3<*za4aYVJn_TY7l=t~P2T(hyS7=ik4sj~;}oc4yYd0Go4BBVOyt^3DxF805r`=x$z z92=H3xLZKLct$FIe05LAv$U+p0E1! zHScY2xK`6n`jWGQrE_EY%QSb(#gF9l{HT^KaB7`v>GnpM4b)| zDp3Oeaf1J-Dr?G zIUPCJ#?-8~qoE6}G(9aF?n-^JtZIG#0#)}@w}&M%g|{~q$(5|LnZGXaNp61x&aSQ} zl7km+Nd@~`|H_sX12ppbSfGzxN{_Pcq!@ASq8yD|xSjkQo?S*B`nj8w-K4R=Rh8Ah z<5u~1IIB2UeO)@rh{-n4k{*ELSK4}`L0f4RTB1bNRplh9wUusLLAxQFSi8P722z_G zC&5kmBvME0r7kDQO)L{^heZ_lv7J~CO!H=e5kXzdHy$$iwh}w^9}*4C+9POo>kG}S zr$^02Qt8%ww3`dop}weXtqfV~C)^bf zt?fkr;c{4bnpBD|fj4v__3&St8v- zaV~oj^?297$gIn6`8#px5!_k_iw0{M+CPO21ZnwXC>u3!8!vf#ka~QIt?z6?u=_lN z*=>Caw>-yli{@e_G>VBvahGf|oeTt<=Xwz0J}7)E+)COLStEh7k%BDr9N<3?fY`Vc zAXzOV!eHEEy^nY%zk|QScbxFS@SNd8;@`^#Bfg&kQ}n!!8a`BZEP^gijP>;tCywL&neQ>qH-BaFWWN5doZz#51$}N{m zz*Eu5M2L>l=n?AVRA=L|4G6atIeSBnigL84*+HC!r=e;dU-7{7`Y>9b^N=+rL!XK; z(TnYBKsh~a|5jH=nw6%uPH&eqGTwu4yS!c!DBn!bW4>;ex8#mvSvx69@4NKC*pAMOrrYOT(sV&q{-zk*AT^oAIj_ zja@1Rb!bi>-U(xRK1akxoQAPwmd|QAWrsB4+@g{Ta~*9Z&PeWx`XO0o95(5-A1ny_ z#Bw}Z196D9daysSzN8H+zWzXTz&}ERb$*plFqS{g$7c874cDeuo70E?@Yw$+`AY^1i`cB;E{$M^5mdB-HGJuXk% zAt-jcx_tI9W7%5To}MS?S^r*xp-Lu>WLJv`f)@_A#wD#Z^ZEpk)>X~k3TN4_?@IZATrlNbCR;US@Z+Y**|E^J502(WpE*Mi2*WG!? z!7bLqvQvz$*Yz!8EbR&0mSC70i5xkhn45^)IisNLq_Wp~7Mu4K#CE*b>Hcb0TJfDX zLfJRzIkrJ~tTFTW0eo1yXWbX^5GPvnJR~7dN(*5Vn{}u1NtfM+|7H5aPod%R+>ULz zPxC-$c@RSiIugT`BMo>lrkn0$(FB!2H{H8_?~gy=$C=rWw<^Z^2>@`xaic-8ZLO)4Bvj71p6KvZ?V}063;X4U6-){^3@#vi~DZ z`@z1Ss*@&L$&ULAuGc$wH3iWwlL1M8@Nx|D?H5nM8mj9dL}9h4=O&pZoMTJHuS(YF z*787;YMr_*$C<@%{CjJN!!8qx;QYIDP?@f`%!TPj+?Vx`nMlXKkZ*_`p@Ve#i^egc zF7tXFR~J#ouPSR=h)Evmt5_7-^=$;HU&2?iB24HPg9Fh zpZ;Cl2-9++H*a5W@zfUjmHBuYrYUcAdKkvBcKNs(=CYy*^q4+_wPFuc|KPSCuAUuA zzUrSt>|8^>;3}D~sA8>nr~P$mxPZ@HRC6XM=s1$T?q_Yd(c1ie(GyZv`rg;W{0X_K zlDq0BGsczbZcwaW@ZMJRL&J5uVEN&SulD%t%10%?E>_g;cf9#xSYe@3FBn- z>?qK?RJboM|2pW6y^qin|g9~<-(Zs*kn;QnC~=@+}~RmX}npW`5|J5GX=WuZ;||IR zsIVB#%_}WFH9%#_a*$H?ZCfXaC&Kji)+i+k&{_&CK<#bpI?OVHWq33BEF6idkbht6Ws32DSI0f|OL= zZ4qzvN21?L1HP@rvmS_E;H`rmygCl6XM8wz^(THwv*uBw9z5kuuw7-Py?b|?5r3HU zw=YZWcp!*wH$$@!*s`@0j1qhslsy>78$$dLda}DDMqqbzNFFnAK5Q434{s_8M8<1{ z$ncvk9%-a9Cj~d07jm(YwUJ-g3dP2~G{S_WqIHnp?{}LTzeJq|oEHqhy$&O~fAO31 zT-CfCEHeH5XmUhMr_!E!BOo=2B$|c-n(t5r$ho$7jnrVoX;r_)%%Y9EbxZs#ups3% z|99vy&>{PfcNn`3;nRM6ou)P3A%rPl?6tFvCV)K-u@WL@L!~QpOcuRsl{Gujw(B~T#Aj&U#zkNcUdntrx z<6x0Mf0ouv!%|W&=9EOlo0oXBf}`PU_dX)_h%z2vHbHV3d5lX`*Uee zp?P0v`gZ=M4DQOT(TNPQ-r4u(A}D-5TY-$ZPh=021ACMVlD4(ob2>ylm{ihnKyOa@ zOjWFU8}Y~vufhX2bP}(^vnRAF-_+v+in;60rx54{KIZ3(_;Om9`x9;+tw0J> zlV~DUfp&3^VI-S3L#N@(9D!gnA`hP@Bn|h8rwfwGQ>_yocCGEY6*lBwCmFc5Kk5W% zU@{YQlFE-K^1wWa(`Q^*S0FpkZ3|3J^H!5LTT_p@fKdey4By(k@L3_ZMVaU!_&tFo8zK zlUI>rkX1Jgp#mq--38*`j3I&k-JkOz77pCQu5)boQ(W1%gs_C1+0TT?IDeSEW5eVE zhx-pp7;;p1>E}77R~@TPOOAusP5wOng6B#%=hy7dly}KF-%T~!dftFPH=Z@tx!Zsv z;5*FmF_N?Bun@dB`T4*P>%*(+Ez{b~)|p1IvXQY^fgJvmty5-~DEnV$*{)u;jSpL& z4{wxrL;pLkoXoy|9|3Jx`VzICc+kH0u!Ells|_gFL`x5yLwhP zbv*xi@wZXGHuZ$_)V4n1U*y9|fN&5$sLJ$lklN#$e%its)_M1_p!l7Q8Y<7q0#NNv)MJiCnz9%H^Y`P(+; zQ^Vw_<5jq+MTxij;~eR$dM+QUTKwT*9gTIc-rps$g_1m7wHN3KC#S*IFd-H4J-`SX zcAVevOZH%5m{E*dqt}@-RrJF{5T`56qJ09P<;6Y^OhAFpTfgzq3s&eD_KB z$YA-tZQ?)WEAy0dAuvScF_QhWrNe-kRp74Hmv29%n<*gkG_9KgXd#&#p!bDDO)ErB zVnKcf_xe78{EBiYWEyrLlmJZjA~+sE8fO^~8110wChH#kXzToII*zaZ;bzfyT`GI&>Z|onfhiZc%*-P zrp>e5m|rG=h0ST_lP!fu z);rcaHP@hgoyHZuM^uy3{095__~zkNz5W%2ea>Z}hkPCRb3iK$Uo zzgj3(tq<`I3`wat7{6|j*`t; zu+9NRagR)nO!`EB(V*j1`oLc!Q(bthT%(5h#rdHu#WVm#O)pSZNKm1+h{o!KEI?h3 z+(4SxPb#0#0T$q2LT{_#LOZPv_AF)Lv3qLqy zQi?}ZMlkBtwCQE~+qrU+567HGRk>Mc#WbiHC^EPyPVlrtZ6QYcRxwvSkB1r@Kdh`W(i29g~;%XIFSP0RF02qBTyU~usuF9?CzcB3K=lc*VGcc|Yk$M(JCIbTO*v zP|{paChLy`>noeEwdJa?W^_MC(3`Is$Bfo_Z3^9@+|K(Y95}S0DiCmL+*aCfniF)T zs$eR^tE8%itNvD6809*jL!DEdtJ@HD1W;em`i4CqL+ku@z{*Kd(=w$b!sZzzn?{2g z4VRG74+-7c)`Q=d!()69dQ9!7Rh8h}t!$g_UBSpye8#pKdUe$`3h>qqc&{~0 zBI&tfd}ZZ981?{5{q^DCGMz+sM?x9*0iEi5=cA=hO43)KI*}mMU$;#Il+h=btL>Pm zjZVa5z5NcJx+k!CZV364(#%uQgD0t%Uu=oZCY{5SV(O?Oh_w!23;M`VV>2*sJm8QV zJ{dW_`MToGntbLpF`p>P*~QE0=;g-4u`+za{mtG7i%+g=h!_*!l72aXd`D#-fk$pI z#N(%H#4n)G)t66`X3kcQ_s?rVHj zsw0Q%7W{gzvtuCqHT&axcN)nd%1!ud;fK=~W&Km@`n;o$;G>U_ za3@o6Cke>@yzlVsSbW?N<+0)D_ghx?T2`BNjrn4Ap|F>nJ5KeT<4!)}P9jZE4meS` zB>a=!QuC94K={WbGyxE)7oS8F%RFh*S6nQZs+s6n8pnwJkzroTA0+FfSg!`F9mR0ID-`}(?Y_s z%Hfw~m4%z7fD*~tNv3o<;b(KCZf@d8}sd$=xqMasmDbW!-G8>6^7a zv1&NHU8Ajun}})ADw?|4OroiFY)FXP$*qkWEkiX={&p?Xyh`=rSJd0N>z4T_E+8*Lq!n44vM$+X|Dec*)HWfOEDH;4kF_gxyR2Pf_%Qp`I>LrT zBinoJX2P~aQ_`MmchZx-;~x^)>>n1{<{uH+>CcPo@sEw{=k>)uK5~%r(KlKn{F5Sw zc`flziM05qM~?Z6A}9UMNV~s0(&3*IImfY``|PibT=Z*^%N(EmfvD(@MEm*cq5~0^ zIi}NC_!mV7bL{jljb^c4|BC2v|LW*Se|>a}zacu#zbQJ=zco79zau)0#}{i5uL(Sc zyTe`nebGW*$NUFq{Yj4VA=1?xm01VgwMNVQC!(`?-AS&qsdLn=lVPkW$+dvn#cQL# zEn49}6IJ}3QD65wb9s*aH23}s(U89@nh2Pq3j^uVC7c(q(HcHS2d~$>ZeM|bzAgtc zLJP4T(|3|j89ooz2UL$$UG}gw{wepHJ(pV>x z2;|Z_NNXY1K3We0qe5pHuv=c|I&}o{qs!SY0~4aF0tL~vkf%o12WCV!!e%Lc1nki* zfzs%9yqgu>h3DMp-avJmu|$YaQQxD3CeF$kQD#A^2>j_@4U z?oS-$e%2mL9Iri`I91z{IE}gIm{pJn)gDWn<^2oW9ha>=nK+OA2JJg)+Y^_tr{I{@ zF<;;^$vr{sx%t-Gi}U;Q`d@o_{veKZA#pxpVW{8yVGQ=s(7^d4LWAe$g|g<4}6By5r_g@7^=lySR~`?~JEL3U3J z29kRiWADMbpkwlUXU*w&HrMH{&*dpT<7c)D9s}xIXxjX8e24apNhJ4)SKwOqg50*` zo-Soy*A3p!CDD6dm)xWEu{R49>iFEf8SlMP_g}o{O76Q-_gLIkuFGYTV`BJLpglGW z&yLuLz`0l+e24cuNx!-n8ymPB8_%c_W0Pw7#irB@j7_f@94o5HiaCMdvGSUcu{o*s zUNa_E35<(r>|Zq#V}Y8EViO%c5HP`MXWyQZ#7D+ zpu zIA+ysiM8?ClLYT6YqrPE)a;6NqR)F{7it<~T^zeOe$+I@&Ag^0%hw!G0@lOh7wjLO8XOd#fxRuwe{e|L9vl`g z1xCbY@tE`21@q!_gJa{>!SQiVa8f+Tu{$^=9z*%*@dd%6_~M{5zKr*P!SeXZWNZ%3 ziLXhnOTo(cx}X-{z~_-*B)*N$6v0LDJ;9~%{lOLSgTdAD!#s|``gjYUM|kZHZi=5| z9l@>fcAn?pj(7+ARd9FwTyS6fV(>uxa_~?>3^phF1zQsXd40#b6N$m>bC9{+ZHcVl znZ)p5XJRDUejza?*p(Q^b6#ssOsq{$Oy+*o+7i=hGZKZhnF$#&AsKTiPS)loX7gHE zJ1S98o1ai>CnS7`cT*FI+8K$3Y`Z*OEZ5o-OW2;-7HUfq%WG#PR`ECrX2E1TWaIze z_h0{x^3V3~C>_GNo_`HcGXzbYpcbf8)fuW?Emdc!bJc3qqXvm%>H;;xNCm)Rb(xxJ zq?PI#b)C9F-K=g?cdC2T{pvyWu-a1AtR7QOs_klrdQQEljxy3t;IbyF`Fhg&X#=&v zT2|R1ZMZh_skz#ir%JVP+C*)#HcczkWUWk_tyQSmdeSO1Me|V#skwU6652v-iMCu@ zh4yiaxFo;h@6i|dw*QNrWZ{qff4L2Pf;^;{()G9v9faG^8*m$XBW^xD(CBo#<`26TKaGq9br8nu9yhFX2vfB<@7Nj62ah+=B1f&FDk8 z8GYC^%rs1xiks0#a5Fj$H=~c@W^_7kMjyk?=nUM9K8~BwLeqTH^Fop7tER6CCAb-t zaWm?`&FD!3?ui$3%Y21uHBQ6!cDa^s&pqnf165kLi$h!{+ zmEvjfL!p|y_yUckNr(t_J+|sPFXX$vp{!Qwl?G*#vQ^ok>_(cROjn8&r&5kI2VALW#8W5*II-QB?;yJwZP?8qe4j1G zOmp-lvL3b-k;XrttcPvI3R_7dOL>HBh;PoXBkL$98)EzT0_>wdZp*(&{Zh%2*ct|s zExbVX#C9-}n4U4s5ppo2U!ocH2_sD*n$bMmb>CrHMzc5yvp5>F zI0iS{cbZ-|?GnZcru6aYlTzkmym-B9eD9>pbv3vqxu&=@Jt@_$Ns5P)YkKdb1YJcg zr=FCUtK3ztETEiTNm;C9y5>|jxhg5?I+x)Ya7B_yS?Q{~Iw@t)=BlSYB-5tqjmgxlxw3cSe*<-Gb?tDS(UYdRb{lD5@8mm3eL0{f-(lAw z*CAg^byM%e|B~uzrqoL51Sem+x0#*|TtiR34p-Y%X^N|X>r*R?k*(gYC*L_&r>oO_ zkof9aZN7`H3$Cv2)LMNinUW#n9mKg*>zt7;DwmZ}y_2h66_tFopE5xy;G_=Z9&=I$t6A!B zo^^F3&HU9pF{@*gsmhG*WLHYN(=0uyvF2=N%ux| z3(aFCz2EMtRCkfR*QtBeMzu*jq8?R`tEbe{>RGl<^}KqCmLIFuUmK(i(S~Uw$RmrW zm8-Qp*g~Z?RvWKP(xzzrT~2MfRz$kkYtbW=tb?CWk2I&NTr1b+XqB$~aN z6xRwZK++^FqSX=CX^XU_+6ry8Ru5k;*OS(uZ9;GBw5{4!wM^SV-pVPW?bh~b2k(u>^J`aJJ&tRolh1vhi1U+p5QKUPbHhnbkCqFJ^TQg@Jkj=2|*UF~--b}w_Ubgyx*qqbDIH?TFTv)!BB+uS?ddtl?| zXy#eZe)mE5LHg=pYPq5+?iTkkvSChUwak5zYNS&i3f%4P4r;ybo#$LD+!rZb)-*&~?B+*foNp zK&wBCB1M~jxNC%eB>D20>Wx~xcQskyDT<&J54(TkVw$?wUr1Kg#P;aUKzwSYxi~@k zsRU`1{bi(!;~gwmFA2Ni=L**$|7_O@e}${kuejPseLp|=*`8^hLXJtU zDIVD~ndbPMr_3|kZSzzp*|co=lntJcC*fJ>Swe4?dscadd)9i^dp5GidbYUJJ=>K6 z&o0ux*VE|9Qqnz5o+F;4Zja}O${s?>_GV?;&rqx7B;X+or7Zp7E~scG5hx zDjodWKT3y><7uXX-vV+i#qR-S`m$-xcJOZlsfE6Lia0CycY<6yw0*v*#%~4j?*vsV z`96MU{?nZ^s|6o^!(lM7P_;oYRdom% zRqH8jq_l<7_Nwi319JnAcUA3rrv91ws>zi0QsTTuN==j~Z+6-2vZ^ChM^NTy)zN29 zJ$tI^c-3+Gj@h=#_8t1hK%8p?oNLTD(O9ta6>*}GaF(%R=bMJJOghdo*Wvu~1)N{{ zW9Mtb&UXNIz5{V`xgKYh8wAt9ih&;V3BQ9_HH`W+g`OkmIbFQIa9`D=YeCU$dLAGg z`V2JdDDvqjY`sRDAhh*?GdhZv(6jT}P_&Ak7Yuaic*3lsYKZ}cm?S)Z&AiozT`~xT;Ozi_R2=+cu+6%L;4UU8KoJ*+w40v=n4(oU# zc#Vh=7JN1wE%aP`ZIH=!o>*pJrGYg%9LJ0&&5z?G$?FIku7*r@_QdAThoha)LGm^O zI|+MwVSgVu*b9gM0q|HH>jT`M_CC;itUIm^MVIJ#j&SiaK<|nLg4HN@`Pxt<_F*^G zh7Q&_h|2W4HWZWZ77YZZ;5j%2$uc91XC7fJ$yq($N=5N_{aG~p^HDsBFy+enMI(D* z%+)BKPADRI9L1es`m?ya@AiBaiY5{!_kn4I!ag8hiQ+ja@jEr%8nH4N$BHYfy5mc+ zR&{0kNZLv2ye8)*IYxn$v5j2WrtX-MYL`hLHhjz3ugB({gRit{%x4{85n(A|Mb#wx zWqo}quGjOqPrODHH|T3g71=@YR>BU#ZUXhcnAR=CckVB(RmHSUaeOYOy2VtdnBzdc z5z~1bXx-s>S=_1XEvEIPitNfx>xZ3Uik)=XNtZp-7~kZ0PSDp$d#p$CR?CliC?c{fMk5T48 z3Yz;s?{)c1AL#5^UsKoEZltH6_ZpjAFaIm+WI@%HYvGKlo;5IKy|b^;?b1&1-@d_+ z$>!|a3?CzVvr{a$?=jx(H=Y!$?T2;0wYTX0#Qm~U{Iyg3wYTejk@SOPT;y14@6h8M zVw2$`_KSM_;`-e&u$OQ3_N!!kN&1%Eh{bIGC8FNOWPOw;e=DK-CG?#V`gRG$zmnm4 zyAhYD4<)3pWQ>SMq$+BsaGW9LY{pHBLolgc?K>iHbA9ArCAs)zkcfnL^0-@tx@_8m^vx50>K zvSFLXp1@f~pqRkxzq7)KbBeCl>C?eJ>I@MQgoSz>c2Z1pF0az#C&f$WDx>b&s-8X5 z%Br3?>g&dO7s$*P`xSL#}}z^HpHWlRj; zDk(Ic?002V-Fu#-UzLzelvEfXdnlnXFQNG>q4_IWXn<^>Wcii$Ub4!-THU`&Xr4=G zo=Rx^OSXT8zfo)~*+rnUO-Uo6smI?+j-<#(4IJ0OzE^UJaN2mD?eW2q^8|`_vXJ6` z)MweMw@2=;$1Hh}zV;-+drF!1bMi2~&oZsoa-JT$IDW`u^))3~UZ(w;O#3jI_F?jL z-5zBpf%a1}?Q!HveP1AJh8`L}nf4@dM32Wj|1#|%SNAhCv&~k1lmi;4SMXB zHyPz=4PC?v>)GQ#YXX9Yna_y{3F zg0PUVgs_~jim;Zjp0JUyg|MBli?El_NN6G)Asi(fC!8XjCY&XlCtTA1gXrt51oE=$ z1`&o3h8aJP=qZ0)9$_qDJYf=Hit+PwLJ`49C@0JzR1!2opy$csb6tc`m-1XhSeo)& zL0FygtS2-OHW9WGcKn0b-3$8+93UJ@Dc9T!{H~R7g3#87o-_JbU)M>vK^ zd{Sm2W#45W)vzhW+ohOjmoM)V!&I``2ndX`4Gi@_7W@gUJc5a=S%a}Q8WBoT1cNa-pP4Hp}Ultq`77f6#O0DAm_GfO zO%hXEwpDL@>R%>LVgtT-C_Z8 zrBnlXI`MOmUxem=mnw-%#0MZhg)$F-@8uq$%@f7pkXg?-ynPDtQ%1=5o2T~A}5G8upEVKhx{DM{}3hb z74IURFR8>|!dtggiyp1vuUOVX1K0IR4UpGEJ`8yo3Jv+l5g-fFDKO`Z1h*i+$zbm94#@4` zX5@6D&FyG&Gg`RAO1^CQB}U*j*s25N`-2Cdou5c6*#eQX8{BBH8GU4(jnLVs_nJqQ zI0tr1ZL=)n@|Jd;(O-R(rh&_$;bzEb_~NG+fxpoqfJRmZcINW8 zi18M^$Q930f1lC4LpslXfiJE>-3eUMq=0X;hEV1p_!V#$zDpx!{t3#XUlvJcE@}Dz z+CG5%BIFk_N=@v!6!F;7mZt56N9fEpoWrv4XS_X*{2lmKJ>(4JXTVxAIEQA|jTr%R ze`$o>7y(2EW@|WXW3eWQC$aX&w;%iXBM;IN9D-bftc`TmeWKY4eKiLBs`1X zF2bnRa68QnX+=CcX?H^BP|VAnh*v`qbM8cJ8H$*2C%(nmeUT`M;oFFFSVPj7t<}`B-t(E+(>3Ca- zFHVD;llCO6h&;%ACwQXxF@Fo~e*if-;`{LBzY@~p?O(N{%IL|Ldp+CYe{)jk3Y?6!! zz_MmAd>Qf@k$wDO)@F7~e|3jNw##xw!Sf{AyPC_OzZA2X z4Ly3xvC5c^f#WE@g?nwfB-WshapFAjD90s~c^PB)5x3K{4jR^T-=&*)l%x!1%ZqHc z6yHdj<&P|jVaQ=_xupa%J`@q}uSUdUd%J-uoCWvU^E%@T=D8E&F2R`yXMvQqe^8W%26QL~v`B~&2kWQe@>=%CCXVQK$ zOuU-c1=G9YRMP*X^=|6pCDihZ3g(V|VJ_-Eg0b2t@o2R0?2t7wf1gh)!fU8?yY)%% z{op)sKk!rF7l`#8yNr_Kz_N7|$4r(@zcM%v+z2%ah z=dTmzf%}1<0(1F$QF1m)zNItvob#Y(9$M~(>^9^)a6j-<;8H}S*)(Dg;skLSIxj=p z>$*;HGuu4$ltND_^prwRDfE`w6&^hvCpf$GAjWDQMrj^KX&y#t9%gDD zMv3LO(Vk~f-U9h{jI*1WMi1626|1m=K92U{b1Yk41%Dg)o1_5oOI*@~^0ZqZ7H>t}$C<4v zO8!)$5fk6x`y$IpXqd@&CgMQ6{V{62%$#;J@&`jh7kCTG&%w#Mj%VER8b@-=3CNFv z?`9UW`8+NT$GLk6%Krp&`Wt3x1IpaZvY5#wE#E*q`53Z-w_nEFA8r#I%$5XlzQ#KT=`8f@ z1-F2I4gL-Ew1E$U!{XnOPrE70E@tVckn6y2;JkkUr~4mc&y4f9E-T z*F*CV$Vd28ZYhv1vyUMEZq_57;`WQ!jf>CejQr=&+UL;P=g`{a+$yn#&*RqrgIWT| z4NDgKjm?R)i{;EfZf+2=pU^!zA_qL8I7-uM)^ZLs^ZuATh?(>0?Z@7 z1GR3{89BumjSCpDi_-hB`H8S$o!KVG@adcEjdH9$3AO>*g&sxWJ5iK>Px=ruqBD>1 zFg}TkBIihR#XV>t%L^dii1y!zoI$Y0HQ+KSgS55rIa+$ix}0*}<$S3gZy#Z{yb1Zw zTt>Ve=k7+7Y2orxKb$~s1wVyz^p9|Y?qrsR@~%vZaV={BzA_m)e-2;!9W=iRZ41FR zKCO#8@%CkSQwI13etTU8+kARC&SHP(Xe`a+IF|NP#Oi4*n}3UQ{k`z1F8Iq!x)&qI z3JssI&h&C|9o4!X(f)7TLK=CVolp6!O>t=`=H*T>pTXDR4E{rwspOsDq44xO!9y`$ zcY?X@%T|s9C(#Dna)?fA7O!Q9PVa!Dh#@2Rv@ZUhPw3)yW?Jvqx|*yn!_L{q8zFb0 z7cZgiNkqceILF$|vBIh%|EJ&z#D|lpI}`kI+FjtqywY)wl*Sg7b~DNk2EPoQh@X~C zd5p7e{3_zWBHr7lt%W~cvb1!y6-U;ts!ZOv>nct4a+gssTx*X*;hXbq)Pm^cY%vG+JeSz4IMgI>Xk zc^+#epO3=a7wIssq*f=Q>z}aVH{cvmjg#8n_?#}{UevOlPw3{abA-1l+-B)4V!~cT z*Jm*Tzh>Fo20o0p4(k9gpL4@(MKl*}yz=609r!Hb%p0hM`&G+Ld`_o`^eS5ZDn~gh z*}V8g%*AN%T+CWR-p?2%A9WvqYW>hd={Vm&7IIc8!RLL_t0Qx zS^75Q9Q`Y}uNw`o#QJFcEyij$7&~6eW^e+m`{Rw+ufB!xw1~eYIgeK*OCv__Mck1N z!dD8xlQCjVhzy@1nq9>Bd;|O_Y-|ipL%YG_4f%d>-hZ-p?(tepY2bhMUVHENoVU|@ zyPU4ly;PF!H%U??NkUV}t!Sdt{VGWkT_hwGLueYpky}DHw-mW0O%g(qh)_wQ_x#rL z-FxPzGxPh5`OW+}<1^p=>}Nge*=s#(t!M3bzq|LqH0(pE`*aiD!oB)8Sl$eu370tk zZIMravlBLeX{a8~z_KczC9hLWa2=ZRX!i0Mb|#-9?}vThMn0|69{(Xe)6L=O!2$jJ z<<^?3Su5>KAGF@^Rjt7nVGO(5iym`)7~Toz6+ML92s%I6)#&HLxryb&)Or|s5%MBz<{-~O@A7>1>7uphW1mZr zmm=TIwF>nul%kK3mBZkgqOFb>BHt-`zf@49&qCY78{t@30UEd!-lX(ODwe_sFdv(*kZZvzuqXO=;d(R!VRtk+wC7tiKAP2d zu8n2@9D+V>^AOteEOIYc8T-!I?1Dd|?}mH`IYMrjZLd!kvzkBaa9w&Nuc^k{gP&8e zuM6v-UqIcsCD?mg;CwU%*;YEYkQU6tbWgO38e$kv?e|| zZ~&S*cxamKLQi|qHb*&vJP%HX0xMAZC2|fdt#mPWdBAVO)z5^q-+zgiA441V!^&Lo zbY$k0nGNgH>lW0)((jOU_dH(QqpRZina${3BAS#kRZ$tuRR(XtHX5Jk&**bhHqRyV zycM(e3jO9cp1oQ9{_98N+q1Qud@$RRzO*75yL81V-F20>V%he{Hpd3^y?xewg7AAI zk45fuWCNNKjAIG?G(qW2+2dge{Z1c!JM=xYmbW8`u*zGt)Le#UHe7_~snlwL<~%Gn z68VqeFK9l-=F;rxjGD@m;3;ZjreOIA{0g5Z#yx^3$7?OC&(=B+?S07_aZ&PLiPy-H zUz|LS8Th1X!ozXKP)(3U|HTpBwIJH_v-*tPqTj5tKjeshBFNzFUZVXWmJ`rVQo5_? zCpEH%DNEf$#D4+y^BJ95)cW~|e!oyEtKU%kN(A=NyWR9I(y?&*63prn^yN%eN(okz zj?o_M_o%%PLm`HyRWi5LPYU}Vu)Kt;Ohujm2P9N|AAEjA{7WNm!%tlI26#1=N8sA5 z+ueLiG@c=n@k(hx@4n8Ka{N9Sg*HxRbvWu1d*;7)2Yeg)MCN6UvpH4m9aTf7HJ$MD z9=uyutH;jbeLyDJ#{939t>xGQ{Q~r_sz!FB&%iv!=;3%iFs8lnSsl$6a2xzO`zzz+ z?%cd)1NV7>)AwN_ZS$VJA)`T1RKmHAF1 z8H__7M}N=7+fT7)jfSJPwk)O9(3gSnj?e`AC&;uP)8>|Fet>b#pYbKmJ)+cemz~f6TF1E`{vf{w8=CoR0oB&DDm?=C4F^JNy;C9><(< znHRTK<=`>oiPRbq@0HAih?JM)Kl>!}-Wk^LO%n<`)Mw%!N4ZE`i= ze;MQ~Z@0$syOBO!9}t}&`fa15zlUdZ`}rP?Z`S-u%AzOywoaZI>eI8l;7a`r8$9c{ zKxKbppzpCPRGQ6!ekR?Zma<9d4Wd>TG#@F$HE8z1-GM%zte*H(Z)15%(>(U3Hc?kB zn=AcouuM?d@hPM4kW4oEJxd;PC-ildrh(axPbbU$@Nis98EnO-J?vs`(e~qEiPAfu z@2I<$RHh}$9A(%84~4KhTxAMri9V6bF|)NLVP9G`T>a?V*Uq#4YmxEBeQuRmN`I`@ za`EY_Eurob?4L6$;d0GaW)c3M#GA7Zp5R)&;Y`(gTa>|rc-W{6t0EV`x%hk;xf3j- zKD`6T>k?|Kmf>eQ*V?Nz{m>iVQ|j|2@sWLB-$FFr8cOwz_ZvKYzoNjOsaeaPrfv51 zo9l%td;5KT&v1*c&$nm!lN@jIU4HgXRC{k3d=nn8H230Rvp+*~wXfg!-)IKI8JbJc zRKjL0R~$jDHOM_-JwHeD4>q-YcW>iY@yluieEpWQi^|@5Sg3!I)9>!5VR>Eea3s79 zTBWIthXE@4L;R|`j(j&u{b4HmWqkKp(t8&DYAkzUS(&<>J-u7=@wUr*R9C3CN7uOb zwUaj^m-h4xNu#pY&~J>5?$H+q`Fhu+rCtbU`g(^}$$!pyJ}dE~teeQ&98*g95pN4% zWBk|gJ|~7QKK?J>L>(72U*LH@HYug-)mrkCw#1EuTXEUVfy=H%rS#c?lRtBEW&PBj z-5i!cZiW7FmBSC+T>2nd>^Kp&Q-&p!=1W+JrV`9k`ZeK$*k6iGe`S=#rZc>f(vHa2 zB43Z?Yw#fYHt1I>Qyq{8!Ft%dhkOQXjphyH+mUaDz0ur5drHChx*_tfw5ki5^>7=t zjv#M`b-az9vzi~*W)o#+4P4a;8?s(ibslC zHRAC?9vP1mmaoBs=-Vh$9q?Qa{d=^n8TRxgxFwDZHkTorI3}v`N70@tv^*|74EqwL zHORxUDGhg1YYX=AI97!fXf0zM5~VPn198jmfN`rnq~-A({sGHTv~30c2f}#1UCs=o zZP8U&axL=?d>Na5@Cr10kgYOjyZX#wCP($DI|Ogoq?>xJm;q1LO|8abX(6Y*2XC}inZN4OFWf^)FEgj&mynIX|o`W~;xom_Vsa$EG@ zVo5YoS0nq>>V-Typ4(jg;W*B)8g&;VGhRN?i|&Q@p^0vjoODZ3g-{(>@OwM-xZ&CP-EcKS!Pqb7|E{ zSTc(Y>%!1N!|o7_p;kN-K0!|$Qe=RzC;C!UwUx6r29aD2q*JcqOGBk*`I*9?RF@LG*3VuT-WwAP<7|uz3&p4A>gY8_2gK z-wJ!9xrg?Yg7I}jr37c}ePHfkL~-VW=a83wOGLw|!7)SZbOQ2G|!kLE1oKFBGq zHC^fbgd9ijeLPIT=4|v^(KkU}hx|HmAR6W|xB)hzZgWbHg{R>Mdou!#MKg)g8gL}F znxV&ka0|Q)8m>DER>4E8KMea)cMbAzY)Zr3a0~WvJgdSAT#=ZEv^I>#f;I$qz_=wJ zQX0>tACPH7umaBmVLa}aGZScAbQP9d%e(_$#-<;<0?i&|tIT0EbC^+4ed-Ru8#bvP zXwc`piYA}ByU^D{rmp!0`E=wPk<-ZWIL?8WpjnR0xI{zgMLffI;&U2uTlC*zNxxE8 zBm30qg*+O|hmosczZjWl`}94!7v6^^UU95~pd0c4$P5f?BNM&g9N3Cl7a(5@yJE8$ zeoE;UA3O@r#Q#h1A-Dh=vWI^^wZ2w*d(jX>pEd_iLS}eyDwfOOU*S@` zoq+r!Hs`~O(2$WL>?2lg>O$)1|Y!hHE5)1)S)tM6w7CE>LjrmcFtPzr_2XW|<4Z1$$V)*yUepa(zb3xcb8r)k zXHlF7;%pOV@0)2G890b{0cM3MiChk{3ZghO#P)F{$#uc;ko6cab|G`s%;Q?)se2n_ zl+6Sf?^B;4{|xb&LuSjNJ>k>TV%4PLtnvtYA|Fv2yh2^FlZ-JHoV<_sXQEJ_Gpy2Q zME*8?hGPaA{pP(V&ymigbYU1eO?BQrUBHtxUp4+$=x3wfpq8>rHQq?{?UmssWl$OY z0W618x>*^WkK7YB$LHSIlB@TIGxgU4OrAbB^CzRZ2p-b+o#ZbnyX{If+jyR~jAvr` zJRh4t>Eb}2|1UvPozkJ$Z-ndMPJO!NsL#r3@~o^pPsw(hXVp^mQl#Boc{TD@EGMe$W${p#(pRXp1-VS@4b6_MK4Ghv=+#4G*q_n^s`n0~xrSP`vHS~i z1N0G^Q;?^jZ_CwJ;^9+D$E%#}sSLWsy%2pD_mIja!&Pb`4_5~G^embU*l$xt0j0FT zG=M(bj!h179`anJ8!gprMze$~E=)=*q0!RvaFJ5JM;?gH*rc7xr)bZc$lYnfS}c1~ z>k)lF!SA14sZVaR`V_ZI(te%iv#2#0%U3koF4t+y>5G@eTSvTAk85eEUx2<;_8}cX zxQ;gORM|zKmF|y;z0-JIv-gq}-OP#NxzU!=+p_xHb&%Sa^02uw6w&qD7@Iq_mcL5% z@;%p@t37hPaP61A>e zidR!hgI-+sYf5(!y_s+_Z+aX+&Oy#%vkBQ|@1uoq#5m{R62YAI%mTh+py zp_>CyOD%PGK85-mzUVMx_arQ2t!dq3;+Ci^A2BO_Ec#04GjpInS6P*Pk(ls|Jzl58 zr#l==zdp_O)D@i_h~+@$%p&$X_1O|`!E%e1x~xa^l9&&qu9m(GpG)-dtXRZV)TS+^ zZ&F%U+ZH@Q%U{EPSDh8X6UnTbpx(4JT1uOHYcJeg?u(j3sJkOx*Scz4>2lhpS#-0? zLFFvp3#g^*-EwA2Y{NGf%HUwU>XUXZrzP9zHJLVhwvHEVUaIkNkr|F3^>7OP>O{*M z!cUoLyOa4goEUCqT-3u&a3mZA``~RA(b$OQF63jWH4C{nBer_z(V&E;fT z)l4Rak1)zvTD2~jt06Jex{Hywuc;J)bT}Dfy!r{r zrD0Pt(mndSRc?*zzlQr<+Havbf7;>fh}h`Zj()BK%yO(~=x=I{TD< z>$vlhxN?IhxWA zlrA6-uSi;`S#CKITTU+m#-chguSje6YTMjAS@9qMH6}N%Dun#5~NoVQBw3aw{v$J=LKIIrrsjk+NiTxtx z`DQqrti`NGb2pk!w0|b{byZW;qG$(W+LONMx6g&dp<=Rs=~#Re_k}3^fx44LzfJ2; ztKQ&Sry=U6=%D_-mScZsIfCa}yLl4D)4v?PbCI;#IQG})PH33J+4-Tq?Kv;{QGY25 zxgPSd$mbv*hkQo3%5g%p&9Sq7hn^jx-@m)kY5E>%REVsazWVPXv&ZrM*X5}ecy@Pa z*>32|yRmZjX3<|APj{F<@zOU={YDARvim;ErcM1OHk zNet+%GNuMg+u7?aCj%Z8y2ob)6DVa$lbALA4%1w)@-Q$hr(g-zG%PW{cu=R zPuqZ>oAqDWWM?DKj%GOC61CN~r3TUK#*XV!7dvi>Tn;J9q}-mKof6e`T#x?#$jyiB zu2e7d`ipa~rS#YHUWi`7Uhjb)i1c^lcI9L=K2cNcb?Q>;Hl?o0&qR|Qe@W@P!PE5L zb!Okvf5Vj>hrAkjSadFg32?wbVCW95s?d(DZ?Qk7(QaWDB5Mr`hX|xE()x;gL0ty|ew%&w%B$ZbFUQ zg|JWd8mRsU9GOGi&+)$=%?dP6quB;O;kwT|HqH9@Q8qd90j2vW-R)QneV=R%a+tfn z_U7ew6EooIkp+_f$B|bImMI07jUFn`>ArPd&yLmI9x6peQeN^Tk}6V5%19$=A#LSU zIZJwrX1ktpxm+c~Wvon;DKcGVCpAh+N{*Gaap~w+uPd7X>zvol}p8!^JSnE$VmR?@Kl*8w>fKbW{)0S zq+8E(&aN&4&+mCwbyU97a_4}iqXwACa5}sVE`ayLB?B%i7;T<_E8trAHrxt#47hC673Leb4<3S9We~vh zfB{2>2c=+D*bugbU0^SG`Q-&y{xKL1$HIx14;?lnm;$H6*>Enr8{Q8e960i_0YM>L z4qt>D;AXfT?skqZ3--Z7FslrMf}vxEglSj`mWNefO;`^$Di}7PAZ!lX!j7;D?CwlP zhUde+@KQJs7Qm5k{4n)42~LGG;cajpyaz5CHuCbJ;S#tME*q};74Sv44!#LD!L4xn zC^u5!PWUz42M@ulGKxlBIdou@2g|^Uuqvz#8;lwDA2q6Rlc)fW zh7;jbI1A2oY#QAI7sEoh46cG}9h*gO!p-m_xD$Q@4>&fDepRM?m;)`$hm{>$q^iTZ zupw*;Tf_FF#`GVR>IA#O9L)|r<(_i?#?iRdUBzX({~yWSUHwaj?!U<8(j0yc{l9Y-a2Cs+RR0Is-$~=LTb|4G z|C!`Twf_gp{%#sZ#ITci!V@=kxZJ&EP#jU$E*c0S2?PibT!RL8cXziykRXFwa2qCg zaCe8nT?U6qaCe7+;0*2r89033`<)->)V)>r_pRzx``K&t>h9T7v!-|VUhCP;(NTPK zOu2(q-eWN%+Vz8OP&Kzstw1wF8f~Lc@DCx~H_cKAUriaB^3pfJc`9(q<5bbSBDB5t4-ZlNRP1A8HfE0}?Yc`cx!hDI!y`YG!GG9O@gkikZ__DF$i;IFfp_5$}E-q6B{Z&}7i9ZGeV z*?r@lWWY7ei0p^H@xzx7ah<8Z7tCuHkqz=`1ZJ8wY*GwkeG+2hJ#fGz6DdE&DuzC&b+oP-0?9w6xC$aFdgzqHyiFA#XMPiNJ2j-fkICkcFVq@qRp}ia zOX+TO!`u8S+rUHM;ZfIZm#4==2XNC@%ttJuZsv&l{m}=Zp5s};AitRbljPJGoIh+Ik^T+9xd8dRoq21(P%~Br{ss+HJ&-q?l6?eD*zN%v#wwu$Mcl4 z>-D6h3nK?;R%FLeX0m4_O0|1@3x``_nv4Hc%x^AX3b*G4Xf=D^wQYpZm={s&c*PMJ zQ(`)XR%e&7SnCwiS?HkK)A+TrQ!t-_c*N$$p)GBSWx0sjMAn9qIr~1j6|G9sB zs~~V&RqTQ~eQ)p1n{%D*I5p*szc;jFb%$b$g4*)|@@Q~D5A8BBIXNeJ5ZN{cHHyj3 z|J!=}aZw=lCzq1#Zs9(08vrU7)0w}Yd9=AG7hA|>I()o)^3y+MyID_NSXHjSsIYO& zP?~q+&IS)=*^FQLxz@BUaQ)f;i^TRWELf_ec%#-iZGo8-JLW7Stgu_dIPz@y%%BF~ zW#pB;z}O+$v1Ui+PdawK3_ev9+XklkSM(AY^VaCCjXlkIV{IQ_QLx>~579p&XiZpJ zhs5mL7fz#`tm&7-ixaXf=+lQi>a*=*>a>nNY4xc>w8rXg=zJ5)Y)e+)#TnT)Rv8B# zP1z2%i+vt-Wi}Lbv1tZPllU3S9%V+h2a6@gGsV@weo^z7hKXe>6_NJoYVPY}+xBJd zTDC=k(eoRfhjJV1g_Y}Fl!s7Y4|l!OO7mE_MTV8fvB*tZCIK)*swSY9$yw$ z_I-Rzdsv%OgloVehGP#9Ge&ARKA}CL%|&2gYhjyaoGzFyxQmr;nQmF^SqvyHf+yN! z+c?}?E?X{VpmQB_(r5ZRUKq>9;MQmEk=nCQ&?}ZXYw{~7h9&k4Y4&3_ zzAmaHvv{k})j4cTyJH`~5_jQIO2&wHry$2HH~3}d&-TyE?#DehrUK)l zwiL-eHyjUXv(0KtCEx%f%haKBk5(gAjAOKRN!8klBSssGg+(5h0wcQxMf3LY>O?w^ zAQ+{}@wXcm8_V^&Q~4M6D8~eRj|Lbs%kjev&;}q@OCfLfkdIJbUW`2+n4b4H6N^tg zvby22u@Y;}9eGLUcnE21Hd$ud`Sg&CP!wDg@rcc`g+kU9Kq}xlu59xrYAwCoabiEQ?Bph@ zD}6bA+2u1xqS&NZrZ~hf$tKx`mcVM+VtJ4@J&}`ssK{@edPULI<@mhq)|9lNM=u+cd!XZt(R0FN zxB||{SMs*AD55brW?IKb(d4#li_!C0^bbsbJW0T%nK?mjn?e6G+X(6s&5A{sAIrmP zcBfZ-YHGWz^2uc@lZ(mU7S3Clm>< z?dCzkUp)v6+8SQv6Q~{rMs6LfdanLRZde)oA#>7Bs<-R7*}`}$5>w&&d=OKS4a1LN za#`PN;q+u0&}pS`emP40zv?TVf#g;vq3U7SQOmv9ufedZ$Gg?ogh9V7X-i+6bhQ_h zA-&r`LnV~(%P&WIJCcT~{RO22g}^ zvK(o!tgzlRPvYRLB9)d)Aq_4c zPjc&gW~ZX3!?wwi1}osi7b{3-&|=%fOM?M8@wy7q2K3lAiPGC(8mi-jUs*5v0CD2A zUQmWK*c>N*CgGQx;;!Nz4QB~`ya08$aw-j{6#;fl8nop~0N?=;S775;KZcw#oXvy1kA zl+v|G+7e8}AYj=@#L{Y6NpyIE(w}***{#ftOFN;=$tSOHNqE@ zD?eeSA=Abve=`?EEHRd+t1eWEJBtxBlhcZi7;Jd@=U=3FVZVvO;_ALxa-75PuU)3I zw;)~=%K4^RI`B~(q|sumuBP}{Z0rYn%rjIME&Ztdf$>dpka-~E_|3Di9u#R11rS&- zK`1T3IQZTr$W5JU5M2><9!SW7mo$GWX1HGM8a*rM@9Z*Zd=aP>q$QL4yD=$ZSE8Z! zQ^vrU!DYF=)qLi{=%HnhcTb1S+PxL_0=?}n+b-MqmXz!|rSGip!Di&dTw%3ah`x<(`kZC7_n!`HV@|eAy{9F@Eri5n-ep(sK3njo49PvjkIRY?Wn<7Wl2UrRAGFDoED$5{{H_RiPc`A?j!62-&OE{V#CaL%5p!%P4^ zDBw}IRmIV>;t>tPDFCOqV5Q>w}f$0s^PAe%=~c0b6Dr;tBTF9 z&pGKLzLxyoFe3urpx=a&3NqqLt7{-hNW@dA^r2E6=x9)<5Ef1?`qs+gw$pJm^%DA? zcHXY}KCA&Xg2M0CS050ko$Hq(x9MTtR-P039&4Vvw+~}SN96VC_Mg8E@&1k{v!Gv+ z@kkW0U|V`y>hhD6CT!5uC76_U;FY;*T;ld80hRH%hHX;H@i#84W6#Tt~l_$5rIHmG$GB}~sYsB{t0LFbf5q&1%3^{6G1DiFw0d93qGX}KoL*F`Js$(HBKt~wI4D>*UWtAOD}!W2l7W1kX4b5(Vp5wt4E$&MqfO~m z8Mkf*vr1x$;s_XTTG1x?s<83fGWButjlx6G>uCvioZDAHDzfAlg@Gd6X$5$an}Hy8 zcJg?-ZTe&Mx(6Q_HyI}xFBu0J4;dF3Up9L-cQ$7>Z#G9ZPc~OJpAowew-KiiuMvk4 zj}ey)n8 zN88V;<2We+lEa%KUTSNR&(XKLKrfS=v4`^~zgN$}Sla@AA1Ow|#P5@^wsOxm9$iHP zW4BGtH2k=q6Jj^a@AyS?)c)e$(TJw2ZD`$bi)K&!H90pFO`Obc-fUgr-)~90y0UKykRqE;(>F~`NUF!nph;3CMDF-$4x`(Cib3V2|4NM&JP|N<< zN@Xm*2Ief%b_G0h88-O_Y53JV3sjBrRUz}5k<$ebT?0E7S-ZkM z+aCrUDS41N90(1IzS5K?#KXY1Dk!DceOAyW8{)d`eH`ea(GE76ueR2M*e$yshk9tW z7r{_h-G?$)1_W-9j+jv2MR4tL6@C1BE2YWx_m)~SYBrQKakB`;1$pTzDd{sHZsxbw z>meeWDDF_hI68&IK^co1rNM8h@y*qgPDV0oZFE33gm^Wz)GGHJ`y3d>>Ti1Gh#+Jr zWLt8UY;RFylyB4%u45dzP}KaR3lRWF-61tkA&D5x`c$DA=KZ(Q7iYYA;&l^L?MNP5 zGgO;8KksMptJjrp-9kb6N0g(sebYc@qFd{uaOxkrJd3RP$RqknWx9h#vw^3g6@kdL z#CFPP-TF#_$fXIMxAH82BRwNZw!D;(RS6a>Z_~M%f5mJ4{DtIpSsJliR96hF;Ya6lXMzZ6Nic8e@ zS8{OFvE$2HjBt!_m^F3OcTgFgYxw>3Q_h*VK}7Q3NQEMvGfkdFKG-?$o9v8%XsrY< z+}5H!_oM;>KJ0#YJ02zb!ro(<5ha}dfz+nfoSe_{QAYRi>hx@bV#yiv?61>@v|OVn z3iF=zMOFFV8<(Qr`-<~KKOD!G2!DW0g$}&a$~pOHsZPd_$B&=4`!CcJhqH0{i*?z1 z*>=By?8=d_jxyP@DO)B8gt)Lt!+egP$5=&ur99j=A)~Umw9Hlr(zz_5q9s(GJv(ko z0IA5Rw)9JK@bt@ax@H$Kbb3ntdizA-t>;uG2cH6cH&L0>VRCW)6I<{6{_lond8C0Z z%X3WZ^;Z#0fu&0|eSx#sj>>_<_dYA2h-|U#-QS?sLSY)R=soI9gjLdLQ_jnQNqxR2 z$=1Ruv^qAv%Q;@t+!?YRFSF|quNk-Huzyqbf;rrITKjG|YS2Pod7KdoeK5u0(0#nx z;w#>`59UFOCEKnyL?jBb4I1_zh+;h_=o{XDm?R(g$3>&VDBsnMM|epiSB7?HP)^qx z4@qdB{rMOb1^P(z{4r(?Gt)BtChpM1w3VI3jLuRrG9x{G%FL3GcV^R)z5&;rHnXsD_ zyx(Qowc{T03&PD9CL(BLYyK%YxPc^`R=pYR_0EWGS_TzH$ti=?rg4SSxsM;uQV-YV z5dV<364h?))=tP6I!$JlMkwnUfo+BYqm>QUaBC){?w=Gfi%65ajewg8!suOOSYd3t z5pS-3ex*v8EcE#7@%btZ&;>vealD(aPs?d!hEx_?vDmTL!7;wCxLy_SBvZ}E{cdvK zw=0V0O#A_8y5LgMX0u6Ne0BWOBgJ9#+xyq%(|um+v@uOD#32uUzF*4TJC15m;)ySL z{W))qi}K^J4r;ZIu5x#3p8qq?Am8tkRo=+hn0+w&4<;ja@OIB$o&>p%J748jxN1S7 zv>^!k5&ozpV;O>O%du5gDc_&j zs;Eakz0^L(qzU1sl#r%Xp@h>ACnPu0apd7_ea53F@M%CWrC%^b!w`>8Q{snSN4~MFN`<)! ztgcw}tCraJKP0^r{AZ$Nkr2#o13naw3wo8GnaHMoX0xRnhyQNK?X~uE8NO0`7RGQR zKZtlk>75|#g=E$*Wu*xnB%+0yLe@Z-KELJsHYOhb5QNGp6Pgnt%w(a?SCQh-#en;0 zYW%C1Kn=Yqxr`mA5k2X!^Shc}H1pcRj}KcZuDwexuRFBHFAU=IcXiL?$@=ulq%4}m|I%ttBJP>(Z3H=h8)!e&$ro2GjF!Jzhr!HA!<}`bs^!FRGPrMm@VRd`t%Xi~49^tpot}k)o;8nh zC3OE;ZZscqV~`g7lET@Hd;ddTmXwnk^GEOJ0Dg-wehUdw&5vQ)y`RZ`3`X$3FxhK= z{4t31qx9pZx@qrc`rgl5dXF*0hJBJnxjT>xOK-M+Tc@x@(^*OMZb(YLPU+h!g~OST z#7GNi(n8Z8mT}#bG09}1d0T%$d(+--pN&-YrmnB_+OkjHU}d8?85T9erS3_;{2tZT zjl(mg)fdox<98%g@e_+NK+bTg|J7S!-k5oD5;eO5l|0B~Q_+((u8l*ZLL=s~g#m#B z+Lc&O@${?4W9!$AHr#{`R)2MRif7mxwYE1yd4Oy}g$M~&)30_vojEIyXd(#bJHCFB zX_xsV)vojR_Iv5i^(_x%=m1Lr*i@n?Nm8hH`Qx*ioB`3hMx^~Z$bctM zIEXj2g9IZ$IFUER56uDfj++?mPP_G;srq_B+%qM~q1x+US;ATEurp?S-NEK}zJtvX z$o<1~NC>2Zy;rR=pQt_@=n`Ih26J9jefO6A8%uUsV}da0QqexG$i4Y zU>N-4xySSNCXW^`0N(_2IU)L99b~N%ZT;o1#fRB;ve`|Nv-wYFlg-v!`;v|4ZrIBx zZG;Zr+E5&Dj3Ri`S*0@NQwvq{nI?;;ldV_6heJaIiK`sYX6qT3Sqg}ktGca$T6xuT zQ_Ju98u3lJm}eKsjz0>%-nzw;WhJ2I!=>g!r{*Iu=Y4C=OHjynJMg7=VqX zK`e3-NX`~S&K60|7Dmn%OJ4Jnyyh2qO)z=QIe{=DA>;Q%NGX?BEAjRHuUIblx;02J z4fE}2+N&xv{A@LXnPfK}%6?1n2*gjD5+rE#O#4;c8BIc^dc~Op3H3o?G;VcAFwT{uFQj^-L$i z^v}04>*ik`*3A(&p>J~C#qH>)Ck>?KdII+Z^U0{9i~)F)x!*_j)i}EM6@GM2uAUc! zV#{dE{3<8~NUQ!Msrv+p{u&`D`$@)#I-(#4TgLRNocl#osYdrf1+7!h(!nnRei8J- z=kPC4o{cY%=>-x-0ZF2O%>AO6<`l7YYQGno`xfcVjuP2#lG1_;fAoDt^U|wF-Ew`+ z$@xy=vE(n?n=U8e7E1k~4cvYbdCBwe7{%Y-Pp6j5Wz?H-xSMj=TY}MC(*>tWWKjmh zM<1fNkba=JZmBXt-eOWWvPS_KT=1k&IkhLa>tFjFeHIQ$J;N96*83MuTu@v}`zYg} zAvcS6Ee*u}Li~#oo0&L5wxI4Ewu-dYlyYzcJ2q=ZnwLTHrU+F0EigMOK#fLBMwX{QbPR-D}k<2`bq}>^dsd z-bj=wU783QY{~?g2zk6?Y2PUcaCl4sZs||-l*kmNl#vvsl!_F)l*7DtEmWU``U<{8 zlv_tQR#ov$lN^5b!R;(tQ^tA&QCEAfe!vu<_?lM!y)H4M+zV3-$4-ljq(E7grYe(F zQtB|%OHh)1Lr|`qtet3Jz*3l^#Z;O+%V^63X|vT5(Po>IyVlAq%ePGx9mHKjhqQ~ai$N1d5?jm7PHsvmA^Cb2 zowt7x4B_afQ&1ErVuV{nH7dLmVzM5$-$5!J`)tdgo@r9bT8>T>lHtm2JICt1_|nlJ`MI1`E*sFD6@+$4ubl?ncMvyq49uvZ;{eh2 zKU*k2zlryy;6nFF%;P(rjD0f|+cy=vQ@dOJ)s>>s+qKdgeRh7zn+w0*@e%oRlGGsY z;9+9Io$u7?%5`q@WXC*;)t8?RVc%Ec*}E;HN@C6%&4iCRa^A4#yy3{fB%F{WoaiQ0 zlZz_B?QeM3-|(fsfv}e)<`c;GTDSFkSH*&9)>YFwiF`0i>i3bNMHiwbL-?`hF(Axu zodwzUULw>eIAl@+>_u_4ZKe+VtKTfews$itbW3sjQiV&VVKt_7AUeuT<{wfniejU8%f$Py#E%@@l+2Bii@x6e|5 zYsKicV%(ol-0u=mDz7C11wm;BAZDZyVBBvdiefKis6)vkDT1?=xeM`3Fo8n6_2sXi9H}>6k5vnkmbo^O z>VT9ta`w9&g;}q}qYrI3!|PK*>Rn2w*x_pfCh0!X6jFEZfuUJ<$ir-KyDw1FekVT; z%RpXwF0LejC*728?Jstt5o#m^KV2j&JGAQAU4!d!xz;MaMd5|jbGZh%5b5F%?|hXp zdlb|Ie9`-}zEeJ2F4;<4feO&uMcX+T^+Mm!=EWPHNjq2FgWC%xS;=EW^9dkxDQW&X zLMhai>SI@z>}N|28yPdrBLD0e=wj6>l+>`@r9PhWR#ITV%PCt}k^Ifh)P={F(>PCY z?-KR*Z{App$_aGur=ihrU;?n(0l~{G&EHgM+VP10DYjR@XcIit_JR*2^?rdX@~X;m zkTay}=TR}_O~I>i>~_=@0kg%;8fG-`)ldR&3JaTmpBdS`^q=8%?t^X*b7z{7 z>X9msb$3&*GA$49IiwsBr=?7N=4o&X8qAxPh|lK<>J)i26afN(MhIj-;OfZA@b28; zT_I0|^4L*&N|xu%7U|tiqzy&8}{+9nso1YkB&%_b% z09+Y%)hG$rs0eNSx|yQvMn={EFu&eW!J@N*__$lnSh3tN3Yn;o80Ot}FR=pJymE~E z+Wb26IPwkZOYd7x%h=b>Es{lUI0_54iWmG(l16#0X7#mpbu$5K!n!6$O-;9%B|_CQ z3u6bqcmB|$eO*WN+r!IP3?bbb?J-WIy<^7O)$G*aQ}QYQ$@IS^QW*}yYt`E+CkVT$ zBRIE{qobql$X)6({(Je0l?39)s-Zfg*=wsykJ)wH1*$%WzcV8Ky|(M{)w~1hL+-XE ze#Y(Hq{c9z${V&8J$X$l!TQ6L>7eF;6Qvx(i^!JD`PP`|F1XurU$nwxtmU4%(Ah70 zCL~st{2*>NX#>0(SGyUJ%f;)HmoJ-DdsFxXvzJ%9pt9`O*mAQs^gWhDFnhUsy;kBI^R3b-)!nQIr$q?U!RBvwwk~tq<4D3sydfA)7}DbQ{=j7 z16UN7T*;3goZN^;$$sMaHOW}fBWGVViY!>n?5e1u;_$gi_O!{tzVvwoG+Cin6N?C% z9ev&y)#rV?v?rde3Z1XRHuE&a5f^?Y7CbRYc2u#Ha!KSB^ z`=u+>>7J`yvVnyoj_vYGg)Rm+pYyx+5gul3*3tHbdK){Y8!|kCo9V~9-tY-VHK1mV zbnkCAAKrxf_1uH2o=tOyVZU_45$Zn=eYn3Adznwp}t2)qGckRRaqw7GipXE>cEKW)OnP>9IgU5iNBA$?TSt}2Ni(@p!) z&sFIK_kd%qNSz^pf7fU8YQn6eUG+BG7o2we+JbFv(%Yq&7$oDuim7W#%#Rf_$_wZ` zLSaR0Wxxlwogxemd(s(yo+SrF!S8G!^FZf$i^5DM-~CDfPS_z3i`@hF`_Xq%!fyuO z!*?z_AzgwY>>XFP?DG#M$oK#UR`2YtuTNVHWku98%bp<(yRSJtvDmP%)YK-3y~TI0 z|LZ3>34J}d=5KhP82fDWy{(PLL)YfmrsxRbvW^t#xW3<7t7!qtpg*|&b{?{~v)|^L zNpNG!;W;rpK`@(r>O0Q!n9^*%pKS?L<-a0rYFviR*P%OR32C+t^leHpsshhu-DQ8O z=@hL+UPE-B30KT3&7~8Mb29x>MO&soPlZ3b>!={Xk*6@*ijgljBS$5-j9eDZ9*ePk za%@2EhwDA8Ifne!*d~3$agb}kJ-6RZ%FK_vE9-#Up&wH-Cfow0ds$~BI%n4*J;3_V zqAaC*h~G71WC;O7QR?GUD(#N$!G@ihP;9h=aLujfdhQ~ExzLGL&`T$I*w^2Ha%6AZ zpq`!ZuEojDk(Bj2az`4jz1`WvKXG&pUO*UQ&dC~2M}-eW$=*uo!rfCv z`TWZLT2cARP30;;#UvMOk=YzplB#Dm{PtB#;qSTQ2R*^2f)&3CUqQjAMYo8eBo;=j zS3UNx0$N`MNCiE8eihvGfrW_pM<7PaIBc$G*HP+S)X>|2eBM`o{62o&tC9GdnQ-lD2Er}LLbHu^|zBeNk4Htabs?(RhW+QT@oW8p0w6$*ultwT~n+SJxx_CrDnU7 zc*2p@aw!cWty>Vj*53O2W2q}T zvANk(Ke4Xe1%HB(SnSJC$m=D#nl~MpiD8~8(i3QX>Q$}J6z}lG;py8^KgU+RM{;E^U$M_t~zpnxm?NU>+(vl4X^W=2Hu0|Ux*WOuD z{uYLb2(Q|C`MgT=?topf^FAl?Kiqz__qIG-ENDu~RaCb5*5-y75}_7aVDRS$?#1{n z2zBP{TZ3+t$0{ZI{GCUxTkdwR*si5U$cJg@I|^BH9ZU?{&U@O)@OkYxT~6B8RN7P= z24kU&AWNTEQ{OK5+V|h_0rcdR?WbH;-|Q!cXd`WNt7c8j>T32ZJ!0GTxKB2jPJg7g zhWXMZ9pQ0X|H|E3J8`hE=A_mtGKw!ab!`&tB3fvfh?vg$X0LyVLhPA zedoFy2Q^E;gCd~mU+Db!b*YHYg0k3U+~QITCX$ts4-j0pw}OK zRq9oc6R|o)BWO^+Cf8;tPa=mcqh%bgVXB~MB}UPs-aYtJaE)Ks=KAgjp!E#oX3jRv z``uo7KiS2$Ek|@ctADoCR5FmKfg|;#i{qRjrNGf$m**qP`Oh`I!%}s|^{ED$yZ8t2 zbHV`84j2v+tpTk_Qio6O%)TIg+>9n9iC<VgpJYu%XAyGUPx}wJjqpeYT_v*JF7X4M-6<0Vs4Qv+n6#vy|v>UPoADLwS0Ce#x zWstag%$?Cq-3~0|dYt~rULgogxwtUF zJzJ4jyIyWU0jrLlJ`x)}{L%8J7fxnhk?Y7Idh#(JDh?s1j2@4qFxi|<6!C%nYf-brg$J;9>KS5lSA1w;Tst9Ygab!Z#KB1qoUMoIFJ-KRS~o`K8;O& zWAeEXwLPyt+ja(VGbCO6x{_kP`*YoMZKK)zEnBbC-ZP5-il2iq zueiOFTIOe2k<}l#q06;*xg;mgB3DngmU{pzRc>M}+IFq$g1z}{XswOQW&K8^M=m3d z!MKm_`ocp7>u`NCt8);AEk~F0){nl!SbbrJdEE_uxEtiOEi?DWAufy?eqtr0bbQ8q z3%`26S`pIoUAi5v#aXBc9?xmbx8J^rp9s2HJdQT0_p|}dR9amM#yWt&(?fp;i==Li z%Rd=*Zg%IJvQW=PYC;Ie$&6R*!czj6Eu?<6Lq8aYp1`zqA^ zrKtIp3moHs4!`pd>NB0V*Qu{Z zf+?DL5v~GFzT1&z!7B<}Uw;xVODh|AS-q<9hx|RX6}V(5qvgrmn3Y)V%N=*BqZ!q? z80GlpLGDOXR&0XMCz)v3mf%OOm(KSGXtL73r_a^BU04gAzo0jV)nc~gWX)dBha!+w z)j7Su$WQA;S<6Q8#JFQ$#%a4f;VFYrHM~MIRJ?}=V$Vy<9(3$SeXD^C0(Q*6YQKtA zsA2)5!X=_$DTB^_M^k~`VaH`BQ`DJaVF|eAeL0isWZNuiTwl=Z7PdYm3HS`*bj-}l z>HflJ6)%#>=X9nb#<30uNfnv(2x|m25~xc4(4^mGU@&&}FGIN862x)|>Nyhf{42Hb zF71`;MDv9nt&O&pR9@WjmZ#Rf?_$zg6>Ep*`3yEi>o;YikKiJG{xeT-^ z(&VFupr1-djGIoMv(srHipa{} z1$7lpaq#&S5v=_y5}Pn)(`J4EpKU@zRhWM>kJe{}$4|XVgP~S;MgbR5IDU2Hoxdw> zMOA@jjfM&jQqPLNrSw`2^+O%3FUaesRHlynT&v!e+u~=Hsx3KrMGa8kd!Fd9yp=VW zrF^4+9&4H=F^F7+ih*hpsvK$&N)_sNM(If`^*#t$>YWs=6jEF&<|j3jdQL({f^weH zzoV73$#3r9GU0EClF_V_(Ufqhdv?t`>Gs$f-&QJX;uv4gXnOCBiRt-plbhhe*sj=D z*Gs*(0@d&&AtOM(_hAiH2R5R%Peo@MW^+(|DpQu{tK0GVRzo+}eD5I#;ngbhs{O^qEvl)GFPapa7Q}z*0fkn zd#3B*2A|HJWy%s-`@Q2l|DPKxAJ^`sAY>uZ`6U4n#`y9^yQ?+Jg$L$nbK9MXX%@DE zdsf-ivFx@f$3Lxa{KH}|=R(#;I~$+;G&GXhU+euio_gJv-j*O>bz;c&rL%4ra>|*R z6m40-*7JcbU#^`A*GVT>R4Y!%kJn0Ji!syTft$`=f3X*AI@t>j{6)>%Lr15P7e~pj zbA$1f>D5CNie^R>Pn@`F!s@SdJlVV*XChrG|G_Vr`K|e_Wg&GkpJXFunK85L9mY;J zhiNCqJA9v~ABuxx=?uIv6q$sm*>VLQ=SsaJ6wia$^D(A2UHC*vYdT!Lc1+MidYIYx zz|vK!Cbtac@llu6)aB5O*Ez*mQ8}Jps^xt&7Fc3eK!y}VXJwJf=GotCrW+#w?fW&# zYgRJQCpKfJ$D8xvz?Z3|D8;NX5tMK9xdAsLJl_@By9<_cN3(OW(d#|gkQ zmg7%XU_a`}?MIaMnkK801wxRsY_52CF;XNoN$38*aw`;li-&QS z^&*Ff3~2ZS3zc>V)&e`-SHl8c`DL{)wtaKrzztgzpNE=fFCC1+yLs(mHsLDmIo|Y` z^2wRjNqBmh=3E^=jC+Zz#vjXGmulSLL`!nlYkUh4%+Xspd`cj#;5PLCHnN=mp9YVY zom+tCe@2pnl9Qc_=l>qfqwM$JKkeK-Lr+JKfKK&G7M_W)F**_jdF1zTA0_KsUquhb z{|pbL5l7KR!SJHU$M5s{UR9)62SDFr>!P%p5-1bDe)}EoK7?iL@;2F86loDM z{13gXTi-e#SVv!)Rp)$OkB;BDl__sN^C-EHv*dEJbaWK-_A@C#QQM%t(k_o02nf&q z$ecTKcVMvU_eXP2V3+ye8R;4;3)ayB*xg3Zeyq27>Q_)oZDdVss0-cIt88QwX$IKr z+9fsz7=ie{-;*URNNRocnhFcg9ji6P}l0&j*Lg|H#2!FMxGQcxI5k)8j{DhIUQtGZHw{OCZoq|?n%3OTQr-u zYbV-|)T#toKGEN&6$1ksNtP}=4AqOqf0pJN;)$aKh=0Aj$hWCf*XtLg{$9^Kv(}Rl zmP{Q2j#g11Bxx=^6IAWNjyoA>-S(|O%LbEC~+2Ca!7(|Ek|B2*L_vqX)inFeg8$%&)USU@pjX)9GbkmrvY;~ z5Y6c(OUji&$o5{K!rbNIYhsPsVo^7)Dn~61vs*UF>&L1;ke8CV`?VG6q5T-$} zoSl?Di@S_DhwqVq3{l}>qri9p{i*+TxVM3|FG<^TEp>Zu+*?QW@j97xwt}Y?mzY;o zoKhlh{&Tu435nprPuvBAC~a>Q=O1B>WVwfc{rps{Kd*xPKFSg*sIbRpn9@OhOWNwp zp)!?`@oK(>FF1EgWW1ucp~8_j0-Jn(9}aeGkxx{BN~5hN-BN~J7<%FO@D;%4T{Hc_2z^v z5_hS?+)!K;cbfuIyB43rr}aDcZo zqzyvNrd^_vp99`0$}_RYG~yJ2b-=lK#9In>>av1JI~PdG(!p*xjPkK^wD9-k^92!C z(%Olt{V3jIs+#U-bP1|`G}AWx#!rMZI#oUI!Plk9V%;B<>9WfXhWNu9k+imrcn=iRwA?W*2SHM}mI=ID7SpX@P@gb&!uS9Kn1w(2%55}3tQT2_ zz>F9E!Waem$u%i`$E72JJ_e5DNIjV77oMgqXYKR517-!`PrKIrY}AXiFFe?p+|_2QvyF=C5XmgT-bRbYfOvfN%`Zc zmEV67RLJd@y~7g@QUOnFexBZ|O~3MfOmTbLCU5c^e>%Io>sJ*R)JqL!+t9cJbSU5B z82>qyH|gV@UZ^#CqBhpMR`ZIw!Vp%yr^}&4G?Y7SV1b>s6sA4Tk{+o%%GyXK28GU9 zb7)~fd-AvCZCDeDPj7_e*L%=k?jvOG-uUGmg#Dw2o17bb3)5yQ{1}P}tp*zqFTCJiyqt;!}-Aue)#sTkqjPM1MAz zG1;&aG?{^V+TGtGL=g||#kU^0;|p$G#h}}arz?)yVkvLB@S>q4aM%|#%?YttYZ--4 z(7WCC2RPy6VNy-cwV*^H^_<-t+u*fqIl*U1^f|q0+c(xIK+hZxGi}Z|pF8^eXJoBh zk&pnABv<>XSS2aXE6eksp!9@LAGH68fk6QROXBCrgjj)Yihf0rxREt(K0y*ST!hk9 z1*jrP3F9c)#3t}I5QVSSPm0gqX_g!@~TNB|NM3 z_dkJ^=mRr3HI2gTRnj%ixr!eiddvqjAL>56PdY?VsO5S5BgjFj;kilw6bW8VDRQ1H zeV*JFgcMc747}Ms$15SY|6z0X&-U*}=A8T-N`Cr_o zEI5gkWi_m3Govi2OD(t;zfUrqCz8Xud^&MV^piS56$K+2uZXRENbaF&_PHY7etqx| ze?#K_LcT}}(m)o6(vtuDXM}MPF21&;?TQd1*N(3F@WbppdB}oS%MgHbO&8Q>!K)GS z$D-iha9*$8@PfY2FymB8&L-2r0%FcpHDw zducD@B{S9x+kcg6c1y^*<}kZ4wKxozNiXAbpYMTKq#Fe<662PS3I6}61AX|v8v+Yw zbAU0H>|}seRH5r#35oH{-Y)05Ly(xilg}`r;CPEOE~H|h7Ia9XiYLjESu7- z&@w@l8q|>V*r;!lAw?7CjTSPZkYEzB8_25V4OuASSNuj)A-$h)`UE&f(LTd!-@bTI zXW|i%2LAKIldVVDef_g+D4V&eS8&MDO4X{^GM5cnEu>W%2Q69$OjtE(>7W{acz|IJ zRx+tZioqo94F75F7AdyE@jUFYP_53li}3?$8Ttp}p0oFRB@Qt`Cu8_Wl>fEAV1b8- zG5{wJA=&kc#dD_=j{3bZ6IYIl7Ld>tt5y;Yqzk*XYPd{nmrNu+b&9V7J4w@K3kqJz zCXFhUZgo>uPAQW{9|HV=m1lCE`neq#%g4jd`_2_*CZ7$7#?Mnyp zJ`q+(b8$PW+hkmq&S22l;Yg^l2%PEw8&+SICy=@%f^#mV9r^Fkv>DJ|4XDe zdAE&*stTp z5e-!Y@0>Yz;=m#6>^FDRv}Rl6=MAErGnK{DwfD%!|Hfyy1x)_~4@C+4Pda+W5lFhj zxk*pkx5^3%4h{+hukdhbsBI|ORGjn0}WInA4Qt6Vpm z!_)KcL6GlX<^S|Wk=~J2K$=TUqWG!o{mSUUa<`LxrM4xdZgmwLcL zQquLZvw|w!l?3g(Q1^bH2o_NHCyQ@8iO;O@_d)*&4{dzEb*mqE&<oc= zOdh#tCYendPszQhBb$?#|D{VsUHP5$qb*)_>Hdoq!R!BFg=PKmKg5c_Rz>{vy#Fbu zVb!2fjmO?#*YfqVKW{e`k)`u9XG$*wUkU2%o?@z@4+x$IuXWV$(Jjgjdr*QebMv1) zIOSXe?4eN2{r`i=fjjm8;N0xq1ol56PQ%;YK$jOZ5EE0tS0#j% z6o8!)j|!5^6ab21Lb)sZx_4h3S37Ge8u7|eoJ%}>CJ9MeTEqc%N4N_5UudVF{`p_> z19u=XYa~9NCeZuP|1$&qX^kQMCXPe7!nk#u)-6pO_4K(+Obrc80qg2)wxSv~DlTAU zNy&_0`FVGrN>~dEU5A+|7XGUVnG&B_I+_*ilLwC+`xn-LZ-a&(4epDX@`FdW9f`O9 zHVoT8Z~s490^<$)b(_flWF;h9$=5FZIi9XIs;Vw7s^n~1Xj!S_an_ar7EZ>C}`?O$561l4ux&$7mAV;otU6SV1O=5My@R66thL+<9JY3h5L`5+F%`? z(1nlTqqJ_L9zL3gL4Mb%%+s4n3+PH)ucsF>Gcha1G5IB4Hj;l&ie%~3J#RY?&1Ve4BM4n){D*kUxyoX0T_wFF zE{L{y+qmlBxVl=)%bT0a>x-0gcT&`rG!;M;ixf&ko-vfn!Lr}QrKV=5re>v{2*)@U zEX<}}&S10Aai4SGdjWH-^LlptuN}{6WqSK>O;CUJpH`QlvJwn0mV8YS@8H&xC)l26 zyZK;LRV9W#N9lANTUSk!)0~HDG`1SYmudvvb5DQ}3i=)jy1NhxIX;&oy(9YW#2w0! z+QHHB^9KIE8gl%biRQ<^-+uod#qC!A*khPI!}^atyU(o&o;{faP^P{F@ao!Ut)0pG=zZ3FBPqt|)`<1HC**zDliyMv8{P*LgZpzo={|vu)7~{Qv`O$m1Fy{&+Bs>2r zHUIAkO4&-fexIyHF0YagcRdtW^mNB7}4cIOjEoo!t2i%YdmXL zU)=3(CbqY4$VH;75s=>qU)%n0A`1^mRcxJu>2UxF2l7(4uAVh|0TZK;#u)6BY-S#I zdf%b30Kq6cMJ1jVkm7?$KuibHGAlBf4A)oC2D1G^Qu$)Z>hf%wyMxdHW$8?YH&(`% zuFONPeeRm;u4-@c_xF`@xYBf4ZCFxjG(D~=Q%jrKL)kz;Jn`MVL+FVSJU1P6Ryv{t zJlKbMId3gF>3a5|gp?iE1zY8?6%*D2SG6b-XvjWmKBTn2SsNu)uuvk|5!I0#voecs zWdS8tUIk?6yD+eHh31~!EQEW`mu*hG&8+xV;yQ3K#WXHn86;={cZHBYoy$ZM*o3Ys zt_6%RpTd3O@&~NNj^L|a92ba10=f!#K^3JOa<<*hz*9zv#fEn1xEgAO1>-TCSm?a4 zI`UD$)!YKyF`c1>sNHIk6U3aRnS#7*SqE7LQiX~M0xGj*#)(*|jAdE*6Ift$`(R;} zg^rmGO8!I{t(dbwGMAK+ChBteAP5?zSfC{k?qF-H?xykRRte7i$K1!UI;?zsyFBJ# zywHJ)*f#p57xyBP zt)gQ^XF_E*9$%wWx;((GgP_3Q{ddM+p|F^8wfGc+rdet-_JVl%T7Z{_2$~42vh?K> zQnAh{{<6`AkpGjcfGR(ruoquv5vOwAWP>j_LAfUJrC9Xx&#Re?3=6kryReh;L&gHm z$#M$62e&!#_d=ve@Nu;0n)3n={&CdB7=A|qk z78{ucCoL^sbqcPY_Fc3okz`7&VLCrGCb|kXB+!ql+8Pzi7{rRD-sUUNA3x}_b7~}- zLqN%dFQpSZn&&ZKp6V4-z4%Xy$#$J0rI|Gn9NpZv^p6HhOl{E32NcxGs`-k$x82mR zwJo-R@$e-95n|p#405Q$Fdn8R3$m$Q-D>rswk(3BRTB1j=e3Axe%nP_c zc`WaCk^(iAS0`xXr<^!5mN~!WaD}*Q3%X`^m&)XEm3Pt=tWZ)|N-28}w$|c3(ypgL zTM2Z#ZH?G9d=HBa>90wXg$;5!Dtu}V(J(eUX7MeMhOlvIgSJFyG4dN8S7H%p3%2-M zWtMq)%pfk7?Z|j=s$;K%HCP+bC(L()l`misXbQH(ShX$?l|=N+`0eV%la|QsqHuv_ z0!Dn8(xraf?^DuDV2}*>dbv{57nu z=w>pi(b#jg*0N=CR1tcp0|V}rBQ^z-0!EhMpiBUqbOs5YN*4Pc?pgU zj_uG4ie=Nl1wE&dL3q*wUT0&HUlZEw9O!J*TC2`-TySV`HrgC>Nw_3mgz7Zk6xrk+ z;I4zTCC;tNvVvRZ`r+Vx+-YF5$Yr0_*jqt)JT(z=K=bEn)=ABmtQpc);Yxa6I%8up zTf`8$r7S(`Lv17%bF!_wK8m?KPtvXYK2qOFB=|JBg!)XM5!aXkWs#d%?5%U?aArZ| zi%$P~gg@r{DFShTIxK;by{L9>td@YEEx9V(iZVULp;rEiwJCm^--TN}DXNA^fm@y5 zE#<@@1PSTH}_!jR|LsE=ssoJY1B1Bj>_UmF!}NW=RUl}8%(iz z5-&B+dit?gaQKFR_eIhM<#ee_^v!!TrcAHaK0?cU`+eeZVsfH1VWv<^h7`3G@&fnC zdv;S|=xr6=;d(gy@&n#hx!56$FNu~2x*mVL-MHICd=+#RG<5t;hGtor_{$qMWvTef z=&FKD{EY}T#m{-w{m;?u3o}WP#A^a)f)Xi5L6$RtZog&M#OO7$Bx@W9m2KaPOY&q< zohx@{;WaMkJ{eY5d}7OIK0RC7)XJK~RIR1gn%dGcs9D<7+O?qtZN&xCQnPomNzLAhB)$31c0I2Q6d1;Ba5zZ)SNta5zeaU$jEW5CGnCxalGj;TLxLTx+0q8f28K< zM`oJNoSqUP)sQm(fjJwz*8i22ml*h2aJ`6z5)iM{Ih$jcOH%X1sEL)CKvoJVBCe35 z3vdG9YEL!1GEbi5s_T_8K&(Pn#!Ju-!PYxDpP-k2(YZL9zUcb~M}Fd%BWXKZ(G1We zE*Lab9L?XHCOe^c4R|M^IlY-EuxpySwfRyvQgySv_ugFI`vT69DBk_A^}o;k>B61g zbc-|I+1_<;wJ&|idL?C9V}m*|NHu{g&e3%1zx_>>S5cShS{8lN6Re(e&oTjgi=}K$ zPV8AL62)QDe1E(me333|H`rJ4&5ONlo>Jds?o(YnqgCFlo?`PT51mXK%^L>2&hqkE zoI{x-I@Sip?6$GA18pK~qH6frwa}Mdoy)mu>Ey4MHXnZ`-nL8Uj~d-Y=nr$`F5{iV zoDw9NWzE##Dhf&p_bvkRqyS~KKPBjPKn0Kn)pDAu;}(URC;2Wm1aGw+a_Gzp+Yw|+ z;{`JsODn3Pk&YIfPInJx@-o&+?2k(!a3eLgMH^*x5xm9c*d1wAO4Sck_WfuK$7=4` zs()iO-L(yg6B1{rW$9c~I7ej4zTnIw%xBn1@Ka=?Ys#xF!*((^FlgnN94rj2A4(ViK5am&hg26*KYMnY%}FyF!Cp&$FpI^QzJo-y5Hr+%bOF59P8F2@T$MaR}|jy80T zjy7a^IlM_o8q2_Ka@5%G(2xQwpq88BSVd|%ZS@_RtZfM97;72PqJuM!XEe)-rfKI# zg;IPlnUepRp zd`4$VsJO9Kc$KexdBLBT16C*Fz=jj|MVskx|SrvJDv2IhOMLBh|xd%ok z|9vVpJ{#tyOY=&1rQWWt3Jn*;V-B80V=AU>C2AEkFL+K`Fj6KuS5v$*J_y*Uw(_nT zmsyUHBXvljwbhO}%n(`L=Box#rq!&;y0Muyzl61@%qM8?aK@Cwp025kPFdWx3Y8IS zmYyIKk@Pr|D#7Y#y{?%moIom8Kskt!&@NXnWf;QwzJJukldMc~70A4~5k>oJI61dL zA-ZUB4ypllW=J@t(vhmbi>HBo`b$$C&hm0y%xZ&^6F?`#d5ovo!jlr29cf(O)Jg7a ze#uE{pOl!4%x5@h7CL7o0+lXyZ&@U+f8;uS^gPZhw;&XG2%qFaiuxr1t*tuc_vq$F zI{>^EGP?Hrtl1yPUs}$_zrvoHu3x$>f_FtGKckW{n+ z4MsD5DB%d+o>L`)Ua>a0jsO=mErPTyCYyqOl;$F#h96O8XkBe$;}GoC<5l$4<^xJ| zsPrh1p+C^Q=`xOQ<>Rjpboo=FOi`i?&d)&bDQIW1tw zWTWGTV#mm2`uD5N>n{ZcoYBQws{v@MPB*|_gwT=G2#Zrnh(1j-P5<=`ijEQClc#?8 zNEPLrFbn#4Ede4iW1z{q<=HFVyHs0L%j7V5F{A@&m6MK7L6-H$?`tObNmIo&DgCrM z;WlX=bPp_h66^c=u$kxSre{|G(*{jiaP~j#1@8sbjU}N@W9n`q{STntq3iVE7?wVb z_1HB_t6DkIDMTN_3q;!lAL8yJ%aTG~*hqWat3lJL@rZR67b?v$I(^mp+&axQxF@1_ z{xXEKVCLY@e}cC-xANr)EnMZw9fFR<$5@x(p;&N#>;bW%you0<)Qr(Jp{OkHx%+Hf z4YX_F>}H$6f20PbrX)1|K-Xbh7_C$5&)2f8{=CC(3Kw>bJ=MXV5}ztQCG+Pt1*qIC zOvgGvqae)`b5CUkX$EIEbyIw3zX&=-o*Kk(XwaZy50kP~Rt{mCd5U|f-idRQrq4L~ zn?##rn{*%ZP8((bYcyVoJz#!+th(Y(@xIIu;%GRVP3O-DWgfcj7p%td{-M~TDqd$_ zboud3S^^?A{ZiA@XR`<9Ot3mW4*=9)A1(H)}$}bmu1YKE;JIXuEJJvgv zJEl8SfBqji^TveUY}$TfGj7t>AXpBC+I7oO6w5)}18L7XGU#T}%q3G*nN|i#u9Fc8 zIr_U~F~npC0V|}ebbA#(d zfrn)EyQThYcEZgax+-?g&q1x56-l=Vlr5+%6W@xYwkx?I_Y%H7P=4Xeq^IqBq%ca| z^WORi8Pv~cY+lzu_Y_qlX@-WhLIycycXBdR2Sa8Je__m|ty#W8-|2I>NQ!b3>&i9h zRdkcfqQRY$GKD_HwWF*V;=F^>Dl?DMF@Pp$NFPgbSv>mzA^|96ca}=BdQ*vYiR-h9 z1~}EwxKg5&>P)zk_Ea9K0j)K=(CgPRkcTVT!uz{bm!I^8M?N$(i;8F^9Kjz~GcK9< z>welhpe)+mFf9%Engsg%KQ*Jeby(-?6GG^Ql)(wMbDrOmU>gDo|hv^wvfb_+;z44NzdG)dUbhIUafC|n#Y=vgA7ZWVNi}Td`O-G zCFsZalr?Xrf!K9m(sBQ1{A+f-IPnsH{du15X`N5h{nhBb{~0jjS{Py@mDatav$4kE zARGXoa0>ee5L|s1=@i{=PRu67Qk%{X?ftr#lje4zaQq{^MUhoIvmA7!3Hb4lM4zS? zvS{C(#6R5B$&*#b%ODNQU`4My)C<(2i~Q#DE}u6sV}=0hPz-CMX^{_cO`}NuqBqwC z!dG^__MK^eXLG#H_D+zxGD;_bkt85wK+3cfq0u9uqY)^{BTz#i1*;|!S29ADT#Asa z9>!HN`U^q07Li0wcVr$3-@J14ijc~dLgi}_EVXl;l@7Vq^Zfe4>pS-6dCSxEO5LY- z_xbt#YX%bG0!xK920$wtYL1@x814ONqRXVcs~U@SW?uSsmfx3>EirHC#wtVR-Q=kS zOT8(EgLC(F6S%Ae4u{K~^2(#Bz~z7|eoE5=?ZLT0E4oT?h|lC2O%Oi8N>%$M)VIp= z3kDHszEsB4x)eKh^?4;z?CgBL?qKfXc7ORu#mflim>4fi6U0>G=FRlxO-ho!#}xUG z?SvNaB~Rk(uU$ADB@y&1C4r^ci0!sA*@euwW72fm)Mx_SwaNNmGCD$UQl!C}@$(JOwSg{U3eWw@% z^E7guJ-UsUs@o6;{W5}`JitMnaV4^Mga!%DxFLoGFyL|J1y*)?-b)T{$l`lW%}Vv_ zCQ@yL7lq#>5_bejY8DfAu-%SMU0ot%hG;IX_*w!vHf-Pc1O+&(IJ)>tYWIefjhb6F z-Ydl9NNA;I?zIup5I9@+6n3NfqQ-_np^HE_tag|ELF3gy+2eB~*v%xnDu=*}V2>;J z`4R^I>0kb_b;|3kj=-j`u2x}CkgBVDGN?qAXTI;#2A_9w@x18EmI@o!q0mU^Znyd8 z1RdmJwaXMz*uHMM;mk@LAi%AMRNquYw9g#KN_F+L4q;S6&3dU9muX8QDR9{j2olz3FO16E{m7+dg4|^AWx7*q?f(7sd_>Xno(HEkU`#67Khc)s_UZoptg6#>hYNNN2!<6jxyz8}}s-ogN z3lk=lul+jBmu$7Xcgf}aikhL#Himvwo0yyY7BOy|E^X4t`Se?H4(J=sERtuqZS-fr z=ii6K07Lmt5$n`gl(b-zcueNu-Cu-p{IA9w)CARLQmn@EUeyd|^fXZu!SejjoEl@8 zjE$K*^NiyID(rCAMa2A8TwQ4Z3rhgP6x*_MSLjv*j?usQ8kS(4>5W zE+0>o_1~&8B$Qph+8&nKAD)V1)!340P%3$Z67l>pXch{(K%+#>vwCXU+j$UVqkzI! zm!qpdmrV&^Tz=cpNZbrcmWsy9y&k}SIxhyY?>;f?T)rPahst~aPQ7EN!>rdGWIivt zTQ9zit^z#qbJdcCPRdjiASd1mU<#{185{9NZ`_1F)Bv1IX!Wfj7i{Q}P zDwJ5eI@n7pl>OBR@hhoNDxwsG;Kdjka1*I|iAl>)PDhakDc*2!M8Ao+m*;-T6rmrk zzBjx!WHUXJi_Ha2)G9CpjJS_T1oF*hSZvGnY2$QlWL@VXckQ%|_b#ZN>nh$9FE0RmH5gOEa3&4#RY6Fi26KhyqmML7`v+k(ZH5h*}^lsicvRwxI5{8*rqFuc_kb zzq_K*sXFsL&gEO)_dt2qTPP|VpFBc&IK>6 z)o^AiG<3x0v#~q<+ZBZi7ZiqpkV-+{0m&x&k94nuulR0NZv`~E9XbZz2`9D_&M6T& zWW~r|ad1Q5=z~w3QAaSMj(*84yasSHntp6;)LR}eknWPZZKEqX0lDphe;Pkt42u=U z`iayjq4I6FBujCdzvk6TX!w-Po{-Aq(>JR`21ucHK|bj|;8bEA0M{`YS$3UKlQ()l z9~g*OMYnSH*bEr(>4Qh|vudS?Vcp6QGIs{d+EwVqX(leP7cXwmxcijK<)XF(LdqC= zs*2KDGcf%=Q?1@B3(D?l%Hq!OBpn5a6GF<6mT3f-*dbOG>AaEy(s zV&O#{7>} z>aH;74Va__-HRgGxL%;&h#w9=`zu86?61G_VM^Y* zMjth`7nut~vJHS`wCv^0e^a@eDg2@&0`r2ow}Tee+-1bBL&YEJ!dZicdLa!1K}z(+ zb-Ov+>2~}!guhH%LLl+=q;njMnhM{= zZ}S`#W3jLV83+A!A2CP3LmBEZtfTc)F%J%1X+#Sc8?3DYD4*n*eF;PKh|Jl5CV<(H zFj0bGh=G2Hs7;P;i|b`Y;C}ZmLFzfDRP z4~d?i(w8wV)=iCF%SHh#j7an#K5!;KXcWoLHKxg z(7GrMeb)Z20_qo2R%64i!GkNFk zWha)Cua1J_!4VZfSPL_9)h{J2o)#%&sf(ufst2|#I`0vcdI zLv#xImxWFU`wv1jCZSHgw_#*muNss@c)qbZ^kf>I7(!XPj zak>>m#$80l&3NLdbhmqq9LwCDzzNFMpq--W1zRwTEYLM^*;x8rjHckwBd$2UGSFx& z%pv9e+y2||!nc#<0EOt(d}BAtUNxKfn#Ef7l%OkN%Udp=Vz0^;L0$7L)VJ)%hFQAb zlI!vS;nQ*ZkoiRfa)D`bl_LNJnkbi(;enjjh*WD&nOMj-(d*@6olrABMYiY1G=zYNo23qRkF@(t#Tif zHmzr^CLGl(5*hi2PaN-7BKkp-H1~eVF!(FT_RUdBQOb}eo2?lTHDfH+53KAJsbJoF zMEptQ$r?Qnlg4OdSYYs1);lwoFhh_wHTGrt6W~7v!Ny2nUExC}GXMN-?e{lpK76rf z5qBu7bhzoo*Zi^5`cqMHaK>t^`VX~-z#MHi4M(#RovX8*zLU;r0-#~71CU)G8jx8? zFWd`(=Pbj~(-;^F(hAbvSaBRy6!5mKAXj3yg_u3DMlvgPW&~}X_#&r>mcVUJ9^q*R zW$rh&;<|0|t{1VGjZ#wmTf0FkQtfga*i z@CC_noE4qM*EAk4r{UM+wO815DARZHE7iSu$-ygn`-ZO5rLP+uA#c)_dgfzNgs6%ah|7lLS{e?vQH&X7dIjuE<} z*0~d|LdAj(0Nua3M>)H$LTxD-1>y{e$XyaeY66Tt9aHrLJ?g-~O50nm@~BC#{*isb zehKARo-po4*H^NhdKSYw*JzJ&{iG3Jz(!K1YyK=xN z?DxOOx88h-rKsA%TeMHb2unLk^b;?8I(b3~obuN{=}%vc-o0)Q+8?dpqsb^M5Px=K z2I-AP5>DL)uX1;s$sbLy`|f?<5*pNmk2a&-;>KTD;D_%IBubNJXswl1DE`S7tU^a7 zsa(43yGU0-P-(Rp$kC`3QSfrJnaL@mQqe^8ccG3Npd}1K%9qkff#~e7$Tgx{Tig|h zo9ZN;Zi>W5WkzvHtNKsC~d@ z@8cA3@Z*c`!W9T`iACi%iyvW0>Q5h6+V8K__?j<^ozp#U*IQ$980@#KV~utOp?$MB zT;J4FcUtKhMF8eI-bb;y73FUE);mw?gYA*kds`#7t18)B6sLvgQJpU(`z+8bt>23K zVEYK-M{_glMm~uB>w5mt*UYYD)tqKQjM%h-k&zp75d|&}aX%}Ji|RAaS>P~i3y4K@ z(be%f$Ec|OfrTfpn^2{*#@rQK5g$nmH6~U0r&{bmghZcbZb0Xbrq5xiIyiHAg-v8PjU+P*7 zpIp5oD%8 zo5g;4mIR7=>R<8NhjI?TXmM6%r(oU_VRlsQ8KjWOrzucUP2PvB~>VoghT7rNRW z2S{D_~$`Yjds?)oyZzr?+`W5LUNy96~(@J!>+J> zVB-9*l;`HWZTL|P8fgB89h?eRNJt4a2samwHCYx*wnN68VF*WKb=`gI*Sw`oHb5n1 zHi??losJxcC45_z4pufoQ?4H{_FOCxYuzpR+EEAP(58o+FpZ#Dh#=tHy7pn9IHhJ9 z8LH9m`|I~1;JJhqA|fsKi1ljIWL2TN)W)RSi$2c+zT;^w^LD;--+RJ6zn933 zG!0gUq&0?H?`C;BQ^;YVhn;XONHM3>M


C5_tWgJoQ{=7mw-a!?)Q>2*5t51wXh_vv6)oKr-LrD*O7#OjG- zN&Mm!h6Ezb1pkF;Y>yrne9mlZ<3)a7IRm%#uv5#>LtfA7BeL4;dhu@V^*|+c)pN84 zFFxqlc3pU`;d+4I>xj|nGIRdr{#ej55~(7k@l!x6-FAZxKbSUd6xol0bP8!FU%jH+ zW(mFQs6DN~Mrl_moC={}T(3WdB%CF1ga>bmUHUhFG%2>Qg!aJHz5em*f;AXh*zw^C z$x{ba*MnzSZkA#%GXi;9Bi?9V%)HSQ6B_16+A)LOm;Z1W4Uq9sT9xT3M;)0S8%ziT z2dCU$4vBm(1N>K)-5y?fz^P}1)VB-HDZny_w+8~unR*H+)TcFZ11>+35VvsFB$5c1 zwR!zM?(Cwyfy$GPb~Y!{VBL`;Lt7ZrD6KhWj?M~|UzJczGnTEuIt)KVYbl6Vcr;Fr z+pZDUUXblx+>|gYGrRZD^knkmBFk>ts%X^Ygz?Mn9&^w0D$1ODgCI+iy;VzfH+4o9 zQu?ogVpYA)mY5JR#zEPaZ2P1-OVc`QEy?}R@S%r&bNvPqs)=nj7Hg{wjx#Iov2s#S z#ROz@0`>+oZ7#-A1E7yey;SD4 zo2gjd^xa*fF)0!!wv?(osyZr1jw{GAJ%kuf&P$dVxyLA zvmZOuW>EKl7!bWw-07ccjR82}{Y?^?GcsyzZO5)G~t%zIy)Ta+iU%*fMYDz#(ZSILjl#2ZofQmW@tRYYeq>PuTG$}nq z@{Mr)q~x0Bp3KJ*&#u?4Pw&BK)k>8ilE5i@I^YPwAK$2p?FuaJ0Ux*iEV~|JWca4K zbJ@Kw#Ul{w9lKGl-UqMm#)8(yMds)KJq}kad}zK(jdKY5b z{08jc_eu5c07>)s#Q}nI(hkLWed&S07=n`igba6K`vEI~bSP>3wd|0_ht|nfL?CM@ zaF44(P)1I_%-|gSY>C9I2?rF2+mP%YiaAu=b4Px$EU(nL*1jHRdY*R3Go>e^P1pia zq+sBMR~@R2H!A2AOEp&~tw2Ci_310vUT{u_DXU1h56~<^QkzKX0IlFI*E}`|&T$6) zK}Z-!$ej0IY^{g#9LHkcyE^LGPSvx8agjcvJ+iB}1yDC0!$Levl0QioCUY<(RkdO9 z0m$PMz&l_U!QPQwng4AHUp**CgHYp+cOEQ5Gb)>S8{Ubmhepv#K~)CcdUlhdDphyM zl>VvZ1BvPO0@okkd26S-jJj<9c!ag$p%;f&)sMUYJEVOZK;(g*zIH*T=)yTblP~>C zRgleDZHZMBAd~Lojqwm=?^q@VP8U<;U(UYN2rKm3E5CQWG^a{y7=$b1*IgR^S{uLP zi{x~}K=c`kNuS#$9cr39^%nhQ+v2I@!QMLesG`bqgo$GssjXe8kb_vL=6r(CmZ>=> z!3Nw2j$O=2B|iuRGn@vI6Gc@^(DVZ-=?@5 zbD+z|neYuDVI2wmeHv*F5|wJ*+U%7@82U&sN#lDwVlGX#7fgz|r$UA_Iu0cpMl{ZI z5>^oR+&aK7PHaRA8Gyh(vBY_4wl<&9q`pB^Ese~ASzwN~gfX}F3|;x^8*K?TaK@bF z&XjEwFEx34EzHCwJy)Vo=|+~%Gw1M$e+lH3j7xz4tcax)0K;YMss$C&+iIyL;UZ>K zNvwdBM_t^bjaU{lrgFNya3Mod%|hz_VfO-fyrsieifmXWDg!fn%)A7)i=1V^MbL*W zxkYWkxIlJpT+2zZ%`EiKUu#U4+9*UMiE+&;98(EgvdYGBGNjO>bXXk^5V}{#j;Vvfe z5By#bDukf|p~PqX%d#k2s#9POp6yMUOPEro_7BFRz*?kjEHjS@W!5i7TtDaBblit4 zb}M>!9Tp^L*R3^# zB4O#DE5$+hZR@ii{{T9uYVN$ zV!@Q>OSt2L>Iy)F=EZ4*^MkC@po8VQg2tI~o;?0I(CoXbP1!CZ-xnAdRR+z|#gk$z z%B&YOv@q3Fk$iX?(|pysDMsk>5t~ zQH&~TDZ6x2*1D${=Zfe9w=%@;|9%J;RY2QIgbf{~l}blAPA465y)J=Ex!f(Z|*#SZ&X&{?nD;u`m3EJb=yNGzL ziW@DPE+>I`_hG}y5oN6bR!QcF5|x@;cM0<|{~1^H9)+A*lDp&xui~U|qW68PVjkVU zht#M^*U^Q|62$VN6KxT9T(;JyYj^h{(yk>*GIYHfnYJ`HQ;)isJ%2R~Xm074Yw0a% zb;qlhS1>HQUDI8Ywsm}B>-#$i5hg_}g#JNx5+EugDf5rXDK8z1!N}%O_qRn6vZGc} z5`Z2F7>ZXF;G{we_#2413=^>lhYttf40aV6moBV3LHsFR*D28NUhf6Hio0Wq&4;`l zC{XjK3Fi?L*3wY{dKM;BVIVnEXx4=1Si%+-QgaA(S4ZYHq)xR z-=%^x>GFl`vemX*p22I5=CIeYIJrA49jNNOFJ$F@T&c`v^L0Kp{AFc@rW(EI+J9r$ zKySp|T;9X>Si@SvT160tA;YQnjS)K~tHzaaAvH|}=rKjnK^t}i_P_Rrzone4_t$R}Qo%p7~3l99H2Zl+THTQz|KF`P`X z?Jeo{M(N_4W_+FRtjV~MIVc^ko$!jQ5I`^})9{JniOD6{-Sd`snW8=oQ`YX{jCvmq=*HzyCv)>Dj|f}(?J0d3FgPw;-VlXG_1>}#+qJ7yKZ^%F{- zm5-posQ|VsD*PVRVD?K|t$ zX}0^SnSy(Tk2;7>%*X$EF+0hmW zAc9VV7~J7A>+zkdDEH^I)yw38+G@}T!V98mcW_SAsh(X}9kWbw@vileTE*%9OU^TI zl%UC`S_dZ#rL#WQr*gQrSy8~zu$OB5+dl%KUp6d{Uon@PiXoBO*o!n?J)weWndE@2 zT|2}wN;|MExjY@Dmb{lJXpTFDm<6&3YfZefPL+cUqEB4K)vS@t81pamwt!S@UHbMg zUi3pqBlo%^bg)x4!z9;7AB_tEz4$*{ZWQcAWCUi2tA4PFIciH>hNufRHNO>IUHI?oQfykQc%HkwWc(O%1~2Y@Uwgan(sdQhKPX{G!i zWn=OIN+r|ci;f#NrykZFUOpaJ>E@Dk#ld6XJK&uy;SN@^7|{fOjeHe+>ZoOMFgjGa zWqRl-#A5(7P-O7%+w|{RguJQcl>Q}%L_;CqM2)a~^6U?cQDDBG!MhhKi##KbPVLq6 zI@_n}?12B(*BjjLU|f$C>&Tq#)8Bdc;(5JI0UsEJbp(_3{A>6SLDt&x2EVZ@T8cfp zW6WQr7aLHu;lM#^jRUj=QbFjfJh~%@CPCg%TC67p9avBzGEOL7hz<#GCw}43A>D{* z!)ZfB3U}OQZ#Mc1aABEW0CTOQM?VD9^IT8Vi-8*i@MIU=@?WLO-A(pHn+{Tv{J!>Z;Vj~ zOb5qRfk_hShzvi(kB@=zQ$)lnIUa`b$&%6FINvs>SUs6)RmBN?kCp3|L*B379WkE; zbXo3GYp*X3bHD`?bxyS0zs5%?jmOlhVoqGgC)9EHM1Mjing-$KN%l%>z^S}IpkmOV zqDcp7Y+quy!Kl1QQCtFQ6Tr&{c|Me1sBWn)f#cELVvvphB8rF{q9~VIy2VtyDk~#% z%I}-GAr!fChUzh>(41w2{r;+7ktV7ssDKjt#muu!azWMMP+!?nE_^w{tu3_Z1a6Nm zOLU$-e&vKZN8rn=;lz53^pJ)9!f}Ej-Zpu25vj;5lQVXw&=Flm@5v=G--~xfQwwCE z%gU0T)Sm#_@*rzj2?*$g93HYJM35uYK1+2LIq`gp1&;hP!xqJ08>yaDR>wnt!*jE6 ziHP+p=ga%F&3U7XmZ{_?AI+ZIqC4LQBi%=vExP&`tH}th_Qpm`Yt9*M;bWy;j%>a&&>f6ZNM`;$xX34!L<;Ke@Qa17JpAY?`1#W2m77U z^D8u80OiTdVY5_;{K0_yCqiNH!S1OGgsYrfZ2{ytE2uQHDOod5%>rdq=eG zJ5%tZNl4pX5keX6eZ0xxnz21{|5tok1w>>dQ=e`UqJ1Arod2*9Z#=6uaL9^*H|z_M zEG7@H`$cQ7VZd(Bys+&>kuRs~2Qey$o;;U$Vt=`R*FI+0tn0;ZhJ zp*nkhdzm{?lzk^FBOZDl`(|LIf&Hk&$01RGBy{H#k!-m7Wjp$o?~Ptda_-#?J2?Ib zb@EtCfXtl?Y)h=ZB9G}DLof-&DA9Inc>V#8Ozawxd+W*YsA)XH6Q3KjoRXq-$`XqI z%tb;Vc>m}6N@zQXPwWn#v1Yva9jS=o7zrqB+gBtM193w_xZxi)%1~5AL7&9{l!lp3q+b=tY9FN2Yt&#EU z5l7b3Yc_7-)udKhuL!ibS{?04(|UY(nonbl9=_z97zu;|tpf=Uds3=kHIsf(@V+hk zFR0%3zV^l!$~Ju9#9F}MoY5T9!fEzC|7eljD8&Y_vqX7J6X1_SV9r%ng0=#Z{4pZ* zBHyQa-g1`a3)eQzWsV}L?q+TOKLAcZvA=hmqI_`$MOZkbxrHlPn8zjHhuIj@0hf5l3P=QvAA^YIp;OC``hoGV9yypX!(gV zcbqp298QLf-RHbk~WU?fr;t`1)i zUKCj!;p$xV-umE$-V1}xLbEzkZ}wgqY!eo!SL@e$uMKuZ4l0LyhXViG^_B0dz$XPq zBE6A7l&#b&-GkW%-DWS;C$sa|Lj_+mrwzsMXbh69p)gXuk|(Um{@4K-7_u$5$Q?4{ z;8tWpqq0AjvN)BG-JHnh8l8ee8N<#=&JoiDU3YTi{Bdf2{3`cyFj3D zOzn4E1WXl+F`59KL5}6wlf+#>zXXHjsJr&XRC^H;h9>eN3!%rLN{SOSaw5;-Y0r74 zbMe;8H@Dl_zrOPFe3+j8<7LnP;<{zevwM2Jez5VuHrutf3ExyO$X3v{rnXzEHnar*(itjD5pkk`r%1A8R$Wjygh}K#q{E%hcJ= z7)BpGrU^to6^GFM#pF~3*)0G=74ZVuWOc@pH3^b685j1%_VXaZH}H_xNc302PvVJq z<%fjQujjm3obz<>AB%JC3Q4idu%=^+Po|D7&SOE$Or_D>jebc?0IgdS@HyoJX;KB+ z=l|oCqwBX89N%;(`?tlP-~GZLBbx)a&bsTl^|!Y@0PgVZ+7AkV^gMtoH$NGgzu>iZ z-v1TRF{9C%ee56=!E}4Q1Q|81rqz)uTjQUHMx6jhsiWQF!WT<3)T`8^+@HOt z!AZ>k9$J$P9dV@0n-pS6TJ~3COtT(0i~}YxOxtWRJ50oym?j(sH7(3JCQ(|LRZR|4 zkVzQUByKvMaZ|%HZszlJCU&YXAb{+pR_s-NKmRe`%QJY18hM&8BqT@VG+*eDB3U+~ zmUyCl_&{NLa(@I>`@~~C{h5vwgx7ZyryT#C_Jv}Gutdqm#?JfvkS#5Wx<2BAh~UHU|)of3c{qG#yZHJcAp9| zL#(>MknnJ8VsLnPiZwfMQFxAZO>jMnJ?{ZNzvxR-VqURJ7kpg~>`e3>GG9M9?~TrTWXn=G4TV?ny>F@*mp! z0w)-%k^_y`_q5mNP}ezFD#_yKSDh(fy%((fE`#AcwG6J`m8Dh)ug&5&=p_;cs`QBNV-5!X+;l9zynim7ejND zwAKyWy)K*a#Tkl*4*`-G+&*Mjnj{EQ;YfNl1p+o^lYp%vHp#hLN#bf1W5%H>at_yD zbHRCcE;#=MQ9C46PfE6T_nsy-Qqx*cH!*Mu&Rqj?%z*8c3=Da^yrJNC#2CWm*4m8Jj4+fWT((yAdbM58Vl_fS!Uf8 za@>8PJj%_91%ZMJxzt@zl97kH$IGMLGx(YE)$(cSYmYXlq#|*C$@z&36RRuMRq#WL zhL$x{jFv|ijVhZ~G_9-kH_v%bUZ_pc&q9Yum z2l3kK#iS65#4ViyN}b3yJ&>wcT7<=0YEnP(cJi3;CnNM>pKO zV&e-pF5fsaI3->+cUJ9|dqMr-H2~bVrsLbTzwclEOS=5`U9Z0V+TPc&$F_>1XpEwD zgE=o!UPSmFUz*fI6Z1OG)X<~oJt{*^)%gNxpI|Dcn`Qx}hgse&OG;d{tB0n0MbISz zFVQhxo4}TnWb4LDAY#YDBmib&+lfK^Jds2!NS_HeUJ?;27*dvqA-G!krqY@o|k)LO>M|Us5>D zzLG*r?SL4aqHMFoB+JNo%vGJYbS>L7q4T==lkcxbCgEQnYF_`dp3C5qt8SY5z|B1` zA)dSoEmS>0HuBUy`%-ZzzJo?_wYW~)B6f)%izh^$5+mXwu|r&&J9R|t6{U!XY%HFE zw8+u7ARnLQ7>VQKEXAy4)-hX{F6IcsbulLxNHJ070D8nQj;#!*Gkx>Mka=Sy{H_d< zY)oFVG5HJwgPBOebI42*zCU}5Nq-wWQ~f*JG%s#Xk@6fgD|dBvb~0b?-+#u#l%6?+ z=@Q*D{dbH2gmdf==hzV33~rW4>*{~7r#M>7mvA@oUm_4XE=so8Ln0w!?1Dg9BHO>J$;>Yi2j7*H*lbYE9K)t zh8rd}sEuk9)5Oi>o5Y)$8`ww1z1$y|_qlHFbN+AKH-g8KB$lQbh_QpBfF6i~5O)xR zG{eLl6rqF`hQYynhQ%RdS*9drH_&a7Wk?jESip~pqQnX|9KgeBr0C^11>?vkqCf-H zh$sZ3H-``!ConO}0h^K)qbx${#B8RB2~GtR^-o1(=Jk_QSTNysNym_iKSeTYsk3=v zWQO@NI7r3hL4+7h$QwevP*0O>F8roW5J5z|ora=c#ef!MV>`9;wj@>*WJIB$pdOPOip zZ)Nv<`!d*)UDh)fMsCbrg6Z-$w5cV$_{mO|Fd~5s*A8>w!|57lKX{0NOR zbvDATWj|(_MzlG>(h+tM+rjp-43b(Bq#Z$qUx^4b57OXkDbR)7DcE1C{r#*|E9fWH zoV9lB4k2fB%tjz_6U%y)SdGbxGm5y&whkMIwkncF%E_}I}ETq&{*O2BV0 zNbF=D^LH}eHRylY6J<3WXS$hg@lU>^QT9FdR22GzXo=_#MMat}DGYNStU7tXl>`HZ zbRZ5^$JfPS+~*5w@zo|U8RD7viC03xQN%NGlUhvDM+V=h35kEA5dVY(qRc$n#mu7y z&E25cR{ZhRArK-j7wUVt5P7)}MqZluOXtkF7p|;hN*|td+2u3$Y~I1n?8iL^^R~sOh+e$lX4}QmoC|I=b{n72e|DXqPq{b-i`;rdFgF-rjd8$# z#NX>@qJmp15PC}V;(!8^)7o1HCQWHejN-i!R#u-AxljccrHymS561CPkD>Lv6v zcA9jFvVgvly;54BNWL(`n@D81V$6Ab1H0<7yx=@P5)Tl67U*am~xR%Htij+-I zHhM`c4j?roh+SurJISvca-5q_HTQ#5^`Q`PT!UnJrm|DT%h}6C2J0sL6c=f0LFJlU z^f&%TuD|=&9|G@9U*7w1_SlQtR^7R6`yH#cK^I6ouq^who_$~b2owVK?YG|k{jcA8 z6Mdjn*;b|qt)N8}g3Ij(6yqG@JY#~vG(@*VVI*3nloV8XstPVBSQK3y73zF-q4B=) zp(f!HWwvj2s7;u!v>Mm==7+kX@3;^94+q~V>~?n-9*Oowy(LV_NO@|QI%705-k4<^ zmA@>=8nUU;-Y^akbKbBfQ(7Q)KmvwjOD$4|#6-ywM(rGg<5MSVDf#n|j=VwFhZA?g z#S*5q5(4j73S8B&+KN-uKaLFN1HwkXfUxnMfbgmB2ZTw&28l=#5RQzg^@Hz3g!2L6 z?}vj)JlM?ij|RK)Le=YWlOp+qN%vd5RqN{>y85mIZP$H#)2s&vnH!cZ|K&4Fmu$_p zvaj4TWy*cMYo5-Yx%a}lo-_3NeXqau-dk^efVpsNwv|4D*3h8BV5t3o3{$Y&e=eK= zZ&0`fPeWirV0GcTLN@J6hZ+h;x<-bkx~7J%a9t51!y!f%wpVkPCt zz#3&79IecPt?;1oq5l)_=YdZ{-$ETQsyi5#d5v?28MJUdt(wAdL>(Bqt+(hMI#Wm- z?n1J9I&rx5K8IT;4!2GmZk;Gn;xc>jTN5S^z?E)CF9y*!<5u*gjkD@3z@b(Qrf% zeaE73oLH`(kw3{A^ZE~**lwfw-Wt!9eq3T&?SnuM;`qk)pWc zsqBlI9r-6&sJuuZ9<#a zCbh|JN}JkQ+L_QXL@!o0G&W0WlINDrO)f228taJtMEaTXQ1X$AhX=2ho>iVstWRz) z{dK7~nYToX^9LpQgIN9`>3D~^=iq~q{6Q>#P=K-YR$*qAkccZ16O5L676VBi(BHXcM5B&KV-@zV`lFk*lM$k77^ zAeDOC-QKhl>($IO0E1>1EGU2lVGqwZsaE2Ef0}o)Keb(Wftm0iIT8fHSipAq(^dHL zN)ng!J6p^x0n+_55XDypqWG!+aU26At{d15FWJV&%F(4e!kGi*pd7yozM?#jQZCQ) zDLVLqbloWrlD8^Kl&4#&x~gD9RYw)9!tv=C<#+5|LUK`OmXT=32lxX-NpB$hA)+yz z2wKsE{y6W}28QH<(1^)%q8PV=G|+DGq+E(Om1^(TFCpb* zd{VfLV>?OlVqXS>L-DS!7TD)lPj=#*!V=b9QED2N;WB8hSdE4#G0BGjdk)$Zy3xZT ztt3Phm#B(RCWSyUAxd0|2~iQF0NYtk1u)qVFh_Z6<;s;*KXHWP9L;_0#uBB8L9ix0 zwDt#}Q#3Hro<#Z@w&{1@v|@Qp{3m-KZ5%$V{Kr#oer1-qMOo6iqRs2A4Bh_fBQsj} z-n{=HI4?YZ@s%UbEAhvx#;u$*=7wY>HTI^f{1?r>sJ0|r;F4n1!&l6nwf5rYF`veI z{|d|5M=2k!6P9rCYiXLaC?1XuIs$+KiYft`@)}}FmyqhFW!)&Iih*jy70}BIqr_1y z{35=CU(GWVvYgiOTlg;i0MC)4ja>D{NkTu8q=;|?+^i+t_6Uv> z8o>+eFJDk``|aCz>~N)$g-@65m85K~Hf~;5#7!9Nhe&6H?zpa&ry! znOuW1#6&^H&{SQ;7)abE0y!d#5~5VW3{lt(DKB4%^e?(Lm%U4r5^$~WY*8@Xu;RT- zpKdhdPT9P6%9IDr?fhBi*lQYVmcWO4w%Ec-asVjwxd1B?Ij$_5DSaa zR1$57-63>zyZO6>d&DQGHR6w@=c%WpSEyb5R_P7u*U};CJ?Sgz6X^_f zQmR0=Bl#(>l%z_fTB(t;C6To)Z< ziUr%Yoi67fw8LgQSjeIyTZ}>*6w6=y0aMelV4$bDr#a|9*4>RDM zwoHliallSo&c}F{w31~AmL4COQN{U3SEuF#zmaOiN2X04OC}8{1zAIY)6s0G3c<@M zCV-5@m{Kk}@bS5iW4z*7mMQEX{C==;A_Rz+xGS;bc+P1k0dYw3`nLA-Mo~uyMXz zNNH)RhOZOO)ke}|IGdj+Opr(DW6bf^CHh6ye15KQm30GmBfnI5k=vv1w7%xfh)LN@ zQb{$TCG~_==^jSaTGtDA3Tx;`lxM(3xKZAq?4WjXd$iv&?{f#m&zR5jKU*ic(_&aA z)l3T68k_@1BPyM2tsL2gBu!^5$`p7Z&g*dvyKx#%t3ZjXyL;caYq6A45%rZ5UrhyW z7bnSPsgyFOF&9a*%?0KP^Bz+&B?i$VUJhqrz7Ni>Or5NBN|T-DsFstL&5NlAhm zU5=65IZmM<;4MbbO+{15$FqsUIj_LJPF3>DxO7#%9D?gr1;QnXP53#D5z z!WT}r#YPh>G;cL=0mXD%HdxR?;>0v!u4i_DQ?66LM+4Kn z`3RJpPS9XN_N6_~HZaxCzPPsLyq%k~oi9CG_5l(wk9V7I!fSihytNO`J97xG*zxUt z{Iz~a5B)omGzR!%P7ir>Aae{9ImoGqB6VVs>y;@&l%&5@XqRpQy*S`BLYN%L%+i0# z{8V^Udra?QySOg?EnU=YZzf2)M2{LYYCxU55xKFpQFLnlf{aXRqYE-h zu`ffTKm2%GQN|$EMV<^OF3N~@xUUIC6A7A<91&7D)jq6xYH@amP5>R;o_+LBPY()L z#J7Kt{V}-r@S(cw=P(Jf-;5c2LG_ue((^kozA4*`?yD#}h5ieoks$b6jz$V3w@%A+ zIG|gc%(-k!kIJ?Z&CyDrG8H@=^zRD>47?*wI#C;;?RpsK_$ICiXOiw2`euo?RU0ix zG&wkJ;0>>cmRI##2|1x8)S=2ywMKi?l#^D{HP+i?HMyERtyZh6)pG;4OufOp(S4)m z4)q@MKI=Z$-R?EgM)_sqC3BDa3+d19uhkyo8+UKGFi%llmmCf;`bhnDoz??=AKwWg zSbc>swYshtNLC`tA>eko;+Et_4|D|y!?-LVH&}9EXtd1X7ofsM7*>W~4a4wm*sw!K zb78x8!)dnMVA&SD%zD*=)^2dYP8}3eqe2q?7nv#{Km=d0RG;CV?n=hj2uyZ>^S5pO6Z@`#3Z$f&<~P&8j?1C zByC=zl-|!Mx%V^BU)H#LKiXNFk&0_G8sf4Yo{X6*b8W)*GHzXh$j6m%&>Xd-a3yEL z;FkI&#kac8t*9UCGfP=Hd(E#7r-~z~Pdc*;hQ|i4n32w2^{kPMh34x8OtRsGE< z2Irso?dA)breZ56iDb{ah^{oS*;cLH@C^Z4pvoy(`JFAI18{yJ$#nn99*+*nU{b6! zG9V+31Eb++VVu}#%m&loG+~xF*;oLsfL91@;!R+waFcj1xI?&G{05wap@2{d%7m1d z5q=?j0C>zTy9`enBJnEX7U(6&k%o1m1O-Wo1Blcg1h~Z|oP%NioHR$JoNs3!+B~I6 za5vC9kuqetm*6E7Me#UnMS}Ik>N*Wjnys~H9oh+vC7AXY{+G6tl5PQDGX)x{g;Xy^ zQ=~-|70`{PMObX$C}1vk-GdLhQv^9>^xz;sy>S$|h(`&CFlTBS+UvO{DDBO}97C(Q zqYRV^I4I-Ho`B~PJ^IxyJd1cLon~L{O{BI3>#~ow>7*LN*?qPvlo17Q=sawkZS!S_ z+a-B3(2f3r-m`+Gx(0A1PQk{|>LO1Pu3s`U+epvt`L~5Pw1F=lq6O}u>w7M}Nqiiy zb0#|lu3+Dx%Bh>|iDPt-v~)8K@rL)yS~_l_jW^+?%F4@?NIdF7g4jf|Hxk_xvdlOe zs0+nQ={j~(d7`d#Q+fGi)PoeHT2qUhx{i9p8ThxSo0I`L)ff3r$t1hv+_dX_Xw{w% zYll`NU%Hs{7`Xd3j%;HG4xdU4f)IIinTd0vkQIlF9acJHSmf%FvUPFuwO260@XI2@ zF0Zhs1z>c}L^u~V-UBApOq+L}Aw4nSN7>V(<}W>G&?_Ij79TnE3Bz~Q&%xFAOeSBl zT8Zv{F}eGdxKGAkwxjzl+wJV}S3zt?cQ4=sZU!Crl0K>o05uvfGPStsDvCxmb(0mW z3&#`gIxEg4>XMrPQ^#$hCW8({sI3)`-%om=)}Nh)?jOv8nV$uZ1|hFOo+~M-Az>W+ zZ5bGdnkX$Lr+kIb@_OOo?@fYew9H&`Da-et1UUC;IQj1Ewwmczo@dBUOt|gzh=q5= zYj=F`TIq#4VtgJ0vd?ZG3bJB?$$D(7pAigJI)_koZ0fy2MsM}E*u??uynBk8l-wgX~k zlEpAr(ubaXn!pmMD1n%C=cCbIb*fI`@rkqDsq!Id+xb?S0>Y+GGmaOS3n|q1)PIT% zqI#!g(%w!2_5rGz#`;*-3d=JB6Et}z}?1Q%fBvaQc{}Hf5<8MZ_2-@!_>#rueDcn zTi<4sn#0XIT_M*A_dOofqk2d9*7%P5R|d`v&I-;7ofF!J)G!RE(U=sTRIsaXL!=`* zt0+_SN%4mzFT@hDM0`p7-xFfutK`12&E+?i_f-7;oGS)ZRSqArtm>}n&h*Bb1R5Lv zTaG{e{~tpO2FAb`7z5+~Y1ICgj41Fb2lJ7#IU% zU<{0bF)#+k!1$j&D4eUMU_IJ4gAR(LHc|hFx32+@s=C%*=bYIm6Xqm9r2NUxNMj5b zLI}uD%14o=lv2PL<1};37xSM<2>F?unMo!wnMrdgrJ$5jYAL0Zo2HmcDb1yn(iD** z1xl?Fr6`mNQUxwWR76F}+;{JDCX815eNVspe980f{hoc+UVH7e*52pL&L|2cN1i}A zGja;$9F)UoSj0ogq@X<+?VQgRWmaSt$~l6&A(>3NC<{zo1&yHIis zQ-p>o(7?+%qAbAOX^=UEa`3J9Q4SSlGVb0d?70uUc$paxq4nNx6bQEVxVXe8F|0tpMe0=+8zuMDU@4kA=)^p?@|s z^KzlMyBK55hEC2O7BcywWN|lJlnX`6V#Fp3dN_Yrl=-48 zh+LsLV%Bqnm*zlQHp-!bkA==T!m2sKsyWcc@A8?>fi}(`676|{=L>l^$}iKGq4Qy3 z;lsigCL{<$FSDed~NS7?GP$3<&xGhG8 z(MV~M#Z^j_=36|DN(N-?lmSg9kEc~qj>Qw?9*KXu-2A!E)@gBxnC({fb zn?B@xP>lT!VOx^mT1m%M|<$@T9nTf)9(4|BRTg$%n_Zj}m=z1;0~_ zW$GLoBQrjRCky!^!6yk`EBIu=TRFGK*l&*+ON(jOqH}qQoRkTVOr_~ydE}vT)Jv$2 zY5{|qsa~{v0aspdUKXRhN=T&R&izz_Unk|FtrGnWY7|%HsLLVkM_C5R`@yTwm&;ZO znPR{Y`pO`)9JQC0p{c7?;(^v$d_5xIWdRXGz*rw(bI=(?$>72J`oS@agXKy3Sp}u z+Uii2iS~LiUbBb~*Xu-!SJ=cQORYZT)^)MaR4+zf4k<(2&)%SCkRtkRs7$$hx07o}5J>mSHwQxBJ{fy6({q4cMmnHf{3JM%o{&LwrV zbw+c2x$_Hk-ugOku~Ai5o9?{7rpB3DRp~Jrow?I`S&dJW)x{|6|=hCWDPhCxMW12Ik*zi`BRuwxRD)!Zu zL7j8z>{*%aI-hfSakJCcSnf1Du)m_N)^Hki&a$e;`WoCQt}S!cd#lh`iaX_~iyNKw z<=*90hEZPTENMo!a%T<3tmP_jgX{2$)_QMUnXlAv!j6CkhQy43x~jIc##aX4Iip6@ z)z&mSQ>!LA%a@lxcZ_uHKOJ3&m+{$q%NzMDxrc8Lz`GCV`ka_iY86H?%9rzSc&jjK zSzVyErmnc`qfRI`r(W)LVn%fs3Z>7e_ZiNzazA%7WIW|H^&j<6I`+ysu?G1rT#Iby zyJ)kNfDEg~^+mBGNAEv`oHJJqUma!2M&;+qZHF#jX{-J}Y#We`Kuk zk7yJ6Y~yTGZC|l{$@afc&W3a`Rs~;g=2O8V?Ua6mS2n&2@F#~??A~05`Tj_ed-%Ts z`H4RY@i(QRBt`i5i1F67M1FdLJlmE+H18F*7uQZRxuZW3{Ltqkx9-nfoSTsWDf69> zNJDj1dLP>G1}U#5NnRs=j}&>Myb=8S^7p|v$(z7`Aa4f$p?nqmE%|-$4-{CSBq?|& zQtnU|fPYE(3ix8B4ZK}xCs_$8*TCOZZi4?^xdk3kV6%;Ejd)ivYzDZ`)(jrB1;JZv z-vVD}`!@J`+j{Wt*uDe4fn}1!rm^Xyu>06d@LBA0;B(Y$lGHD%7*}1aE&*SvJ_Me} z|EWdQrG6DWU(E-1tB-&es0O%C^@01<0C(eZC!URrbg1n4P`dz6tyX_AB6T+OL7X8;?=q-SGiZ;+q_JV|5I2B#`1r zbfkh$bl}sRW4hxf;M*KO1K;U*2K<2IAo#P6--91>{DEZ0bB>GPmmGgX`xVDK;MW}2 z!QXSd2mZbTo_BoUxB>nlf~RPb_FKHg9n^jYepq`O{2lEY$=bUKLr6*(nlOr#gwY8) z|9|{jF_-Bs;d#^NrnfCWV~)9)QJx)sv*+6}BfD-d1TVIiqO7pjqx9RGQ3l}+?uRhS z4tob$zGnX#c&B{@_}A^<0AFSQCiq(TihJp8%O^6taW8n91D}E&8IBAw-~R%??6@rE z@+`_j8s1N}-(g;S7Q<1FNEm^+j7%5_K8nxFn$IBGAoUR|_7<0rv$WY;Lvt&=<;!TP zr@X{Vg*C-SEmr>^k{-^@cH$E}VJ*n;hQng`tRs9wVm0y4I|(t@50DP0l$0(Ga5l7 zX%vm7J82A!rExSKPw7wY_epL1lbh`$^uD2p(RSKJ zeRL3?>t3YObe=BLHM%L;BuyGErAjlTY-zESClyFuX`QrD+A8%(z0zLkfOJ$k4$XFw zq!q;f4P7ZI12&=_>w%@Hon+Q`oj2FQgif=bam%b{K5y2u)6F`|%nRvD*Uft28MFTK z0keM4NjCls{|9?Wh1;b^TS-L%NyX>Q@k=*zYe*{d;yMoXX0yF)k6D+en|0-IA!n;f zubN-ws;aiGPF7v6rs`qlRrQJLOVziQC7D;tLd(`K+qSIF>|fJn*2}BSx;9%#+3UvD zW!5dME37l>R@H5(+bvoX>NnNztUp+Pvi@=dH4JM=ZJ5)L(@@sX)UevL);o)rs0&SP z-U_pBoM+a?F0=MsGUWn=R$U>oO$t0t(%^dGp(;8psfb&qG*2p&>ZK0pm@LcFWv|>S zuaegTo8>OKS3WFX!qQ1poWLTbUhyl3mE(9ujJD<5ytb9Lt+wqflWkEBv!kk0tyb&R zZuPX17^lV!Ll6AM&5z5ED~v0P>xnyW&$4f~AF@AhzY#wsJ~Mt^d`0~F`0InF59)O+ zcGNmnJ2p7BIC>oCwInTDb7||f6A6?sC?O+ZenNf1#)Phf-h>wut|!_PGZT%(b%}ct zk0zc>JU7@jc*5Yc!HbaX{QQoez|D!AlHQ6uA$RM>{DhC65c2auemb}t*NQl`m^n1h^AU^@*=YRb4kDrL~lRth&$4~wEDIGuY z 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 literal 0 HcmV?d00001 From 83b34bb77293f4e61153139be1b2d3f2c3bd4343 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 14:00:22 +0100 Subject: [PATCH 0515/2266] [doc] Remove unused mutex --- core/adapters/http/adapter.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 07461cd1d..1054d1179 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -23,10 +23,9 @@ var ErrNotImplemented = fmt.Errorf("Illegal call on non-implemented method") type Adapter struct { Parser http.Client - sync.RWMutex // Guards clients - serveMux *http.ServeMux - packets chan pktReq - ctx log.Interface + serveMux *http.ServeMux + packets chan pktReq + ctx log.Interface } type Parser interface { From 33e09711456776ba9c1a5b5518a8b5745c87b030 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 14:32:21 +0100 Subject: [PATCH 0516/2266] [doc] Update adapters http doc + move parser in their own subdirectory --- core/adapters/http/adapter.go | 40 ++++++++--------- core/adapters/http/adapter_test.go | 8 ++-- .../adapters/http/broadcast/broadcast_test.go | 3 +- core/adapters/http/packet_acknacker.go | 5 ++- core/adapters/http/packet_listener.go | 28 +----------- core/adapters/http/parser/packet.go | 44 +++++++++++++++++++ .../registration.go} | 34 ++++++++++---- core/adapters/http/pubsub/pubsub.go | 17 +++---- core/adapters/http/pubsub/pubsub_test.go | 23 +++++----- core/adapters/http/utils.go | 2 +- 10 files changed, 119 insertions(+), 85 deletions(-) create mode 100644 core/adapters/http/parser/packet.go rename core/adapters/http/{pubsub/handler_parser.go => parser/registration.go} (61%) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 1054d1179..8c1ce2055 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -13,6 +13,7 @@ import ( "sync" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/apex/log" ) @@ -20,36 +21,35 @@ var ErrInvalidPort = fmt.Errorf("The given port is invalid") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrNotImplemented = fmt.Errorf("Illegal call on non-implemented method") +// Adapter type materializes an http adapter which implements the basic http protocol type Adapter struct { - Parser - http.Client - serveMux *http.ServeMux - packets chan pktReq - ctx log.Interface -} - -type Parser interface { - Parse(req *http.Request) (core.Packet, error) + parser.PacketParser // The adapter's parser contract + http.Client // Adapter is also an http client + serveMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints + packets chan pktReq // Channel used to "transforms" incoming request to something we can handle concurrently + ctx log.Interface // Just a logger, no one really cares about him. } +// Message sent through the packets channel when an incoming request arrives type pktReq struct { - core.Packet - response chan pktRes + core.Packet // The actual packet that has been parsed + response chan pktRes // A response channel waiting for an success or reject confirmation } +// Message sent through the response channel of a pktReq type pktRes struct { - statusCode int - content []byte + statusCode int // The http status code to set as an answer + content []byte // The response content. } -// NewAdapter constructs and allocate a new Broker <-> Handler http adapter -func NewAdapter(port uint, parser Parser, ctx log.Interface) (*Adapter, error) { +// NewAdapter constructs and allocates a new http adapter +func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adapter, error) { a := Adapter{ - Parser: parser, - serveMux: http.NewServeMux(), - packets: make(chan pktReq), - ctx: ctx, - Client: http.Client{}, + PacketParser: parser, + serveMux: http.NewServeMux(), + packets: make(chan pktReq), + ctx: ctx, + Client: http.Client{}, } a.RegisterEndpoint("/packets", a.handlePostPacket) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index c2f85ff52..326b62365 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -9,12 +9,12 @@ import ( "fmt" "io" "net/http" + "reflect" "testing" "time" - "reflect" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -44,7 +44,7 @@ func TestSend(t *testing.T) { // Logging ctx := GetLogger(t, "Adapter") - adapter, err := NewAdapter(3101, JSONPacketParser{}, ctx) + adapter, err := NewAdapter(3101, parser.JSON{}, ctx) if err != nil { panic(err) } @@ -91,7 +91,7 @@ func TestNext(t *testing.T) { } // Build ctx := GetLogger(t, "Adapter") - adapter, err := NewAdapter(3102, JSONPacketParser{}, ctx) + adapter, err := NewAdapter(3102, parser.JSON{}, ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index f53151393..761813a5f 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -74,7 +75,7 @@ func TestSend(t *testing.T) { ctx := GetLogger(t, "Adapter") // Build - a, err := httpadapter.NewAdapter(3015, httpadapter.JSONPacketParser{}, ctx) + a, err := httpadapter.NewAdapter(3015, parser.JSON{}, ctx) if err != nil { panic(err) } diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go index 016c269ca..6b92545cf 100644 --- a/core/adapters/http/packet_acknacker.go +++ b/core/adapters/http/packet_acknacker.go @@ -15,11 +15,12 @@ import ( var ErrConnectionLost = fmt.Errorf("Connection has been lost") var ErrInvalidArguments = fmt.Errorf("Invalid arguments supplied") +// packetAckNacker implements the AckNacker interface type packetAckNacker struct { response chan pktRes // A channel dedicated to send back a response } -// Ack implements the core.Acker interface +// Ack implements the core.AckNacker interface func (an packetAckNacker) Ack(p ...core.Packet) error { if len(p) > 1 { return ErrInvalidArguments @@ -41,7 +42,7 @@ func (an packetAckNacker) Ack(p ...core.Packet) error { } } -// Nack implements the core.Nacker interface +// Nack implements the core.AckNacker interface func (an packetAckNacker) Nack() error { select { case an.response <- pktRes{ diff --git a/core/adapters/http/packet_listener.go b/core/adapters/http/packet_listener.go index 4c4db1ef6..1a6b04890 100644 --- a/core/adapters/http/packet_listener.go +++ b/core/adapters/http/packet_listener.go @@ -4,14 +4,10 @@ package http import ( - "encoding/json" - "fmt" - "io" "net/http" - - "github.com/TheThingsNetwork/ttn/core" ) +// handlePostPacket defines an http handler over the adapter to handle POST request on /packets func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { ctx := a.ctx.WithField("sender", req.RemoteAddr) @@ -43,25 +39,3 @@ func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { w.WriteHeader(r.statusCode) w.Write(r.content) } - -type JSONPacketParser struct{} - -func (p JSONPacketParser) Parse(req *http.Request) (core.Packet, error) { - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - return core.Packet{}, fmt.Errorf("Received invalid content-type in request") - } - - // Check configuration in body - body := make([]byte, req.ContentLength) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - return core.Packet{}, err - } - packet := new(core.Packet) - if err := json.Unmarshal(body[:n], packet); err != nil { - return core.Packet{}, err - } - - return *packet, nil -} diff --git a/core/adapters/http/parser/packet.go b/core/adapters/http/parser/packet.go new file mode 100644 index 000000000..e8c16e6ea --- /dev/null +++ b/core/adapters/http/parser/packet.go @@ -0,0 +1,44 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package parser + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/TheThingsNetwork/ttn/core" +) + +// Parser gives a flexible way of parsing a request into a packet. +type PacketParser interface { + // Parse transforms a given http request into a Packet. The error handling is not under its + // responsibility. The parser can expect any query param, http method or header it needs. + Parse(req *http.Request) (core.Packet, error) +} + +// JSONPacket defines a parser for packet sent as JSON payload +type JSON struct{} + +// Parse implements the PacketParser interface +func (p JSON) Parse(req *http.Request) (core.Packet, error) { + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + return core.Packet{}, fmt.Errorf("Received invalid content-type in request") + } + + // Check configuration in body + body := make([]byte, req.ContentLength) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + return core.Packet{}, err + } + packet := new(core.Packet) + if err := json.Unmarshal(body[:n], packet); err != nil { + return core.Packet{}, err + } + + return *packet, nil +} diff --git a/core/adapters/http/pubsub/handler_parser.go b/core/adapters/http/parser/registration.go similarity index 61% rename from core/adapters/http/pubsub/handler_parser.go rename to core/adapters/http/parser/registration.go index c58046eb3..8785256d6 100644 --- a/core/adapters/http/pubsub/handler_parser.go +++ b/core/adapters/http/parser/registration.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package pubsub +package parser import ( "encoding/hex" @@ -16,9 +16,25 @@ import ( "github.com/brocaar/lorawan" ) -type HandlerParser struct{} +// Parser gives a flexible way of parsing a request into a registration. +type RegistrationParser interface { + // Parse transforms a given http request into a Registration. The error handling is not under its + // responsibility. The parser can expect any query param, http method or header it needs. + Parse(req *http.Request) (core.Registration, error) +} + +// PubSub materializes a parser for pubsub requests. +// +// It expects requests to be of the following shapes: +// +// Content-Type: application/json +// Method: PUT +// Path: end-devices/ where is 8 bytes hex encoded +// Params: app_id (string), app_url (string), nwks_key (16 bytes hex encoded) +type PubSub struct{} -func (p HandlerParser) Parse(req *http.Request) (core.Registration, error) { +// Parse implements the RegistrationParser interface +func (p PubSub) Parse(req *http.Request) (core.Registration, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { return core.Registration{}, fmt.Errorf("Received invalid content-type in request") @@ -42,16 +58,16 @@ func (p HandlerParser) Parse(req *http.Request) (core.Registration, error) { return core.Registration{}, err } params := &struct { - Id string `json:"app_id"` - Url string `json:"app_url"` - NwsKey string `json:"nws_key"` + Id string `json:"app_id"` + Url string `json:"app_url"` + NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { return core.Registration{}, err } - nwsKey, err := hex.DecodeString(params.NwsKey) - if err != nil || len(nwsKey) != 16 { + nwkSKey, err := hex.DecodeString(params.NwkSKey) + if err != nil || len(nwkSKey) != 16 { return core.Registration{}, fmt.Errorf("Incorrect network session key") } @@ -69,7 +85,7 @@ func (p HandlerParser) Parse(req *http.Request) (core.Registration, error) { Recipient: core.Recipient{Id: params.Id, Address: params.Url}, } options := lorawan.AES128Key{} - copy(options[:], nwsKey) + copy(options[:], nwkSKey) config.Options = options copy(config.DevAddr[:], devAddr) diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 165e65c0c..90f5f0630 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -8,20 +8,17 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/apex/log" ) type Adapter struct { *httpadapter.Adapter - Parser + parser.RegistrationParser ctx log.Interface registrations chan regReq } -type Parser interface { - Parse(req *http.Request) (core.Registration, error) -} - type regReq struct { core.Registration // The actual registration request response chan regRes // A dedicated channel to send back a response (ack or nack) @@ -33,12 +30,12 @@ type regRes struct { } // NewAdapter constructs a new http adapter that also handle registrations via http requests -func NewAdapter(adapter *httpadapter.Adapter, parser Parser, ctx log.Interface) (*Adapter, error) { +func NewAdapter(adapter *httpadapter.Adapter, parser parser.RegistrationParser, ctx log.Interface) (*Adapter, error) { a := &Adapter{ - Adapter: adapter, - Parser: parser, - ctx: ctx, - registrations: make(chan regReq), + Adapter: adapter, + RegistrationParser: parser, + ctx: ctx, + registrations: make(chan regReq), } // So far we only supports one endpoint [PUT] /end-device/:devAddr diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 400567c6b..104aed43c 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -23,7 +24,7 @@ func TestNextRegistration(t *testing.T) { AppId string AppUrl string DevAddr string - NwsKey string + NwkSKey string WantResult *core.Registration WantError error }{ @@ -31,7 +32,7 @@ func TestNextRegistration(t *testing.T) { { AppId: "appid", AppUrl: "myhandler.com:3000", - NwsKey: "000102030405060708090a0b0c0d0e0f", + NwkSKey: "000102030405060708090a0b0c0d0e0f", DevAddr: "14aab0a4", WantResult: &core.Registration{ DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), @@ -44,16 +45,16 @@ func TestNextRegistration(t *testing.T) { { AppId: "appid", AppUrl: "myhandler.com:3000", - NwsKey: "000102030405060708090a0b0c0d0e0f", + NwkSKey: "000102030405060708090a0b0c0d0e0f", DevAddr: "INVALID", WantResult: nil, WantError: nil, }, - // Invalid nwskey address + // Invalid NwkSKey address { AppId: "appid", AppUrl: "myhandler.com:3000", - NwsKey: "00112233445566778899af", + NwkSKey: "00112233445566778899af", DevAddr: "14aab0a4", WantResult: nil, WantError: nil, @@ -62,12 +63,12 @@ func TestNextRegistration(t *testing.T) { // Logging ctx := GetLogger(t, "Adapter") - a, err := httpadapter.NewAdapter(3021, httpadapter.JSONPacketParser{}, ctx) + a, err := httpadapter.NewAdapter(3021, parser.JSON{}, ctx) if err != nil { panic(err) } - adapter, err := NewAdapter(a, HandlerParser{}, ctx) + adapter, err := NewAdapter(a, parser.PubSub{}, ctx) client := &client{adapter: "0.0.0.0:3021"} if err != nil { panic(err) @@ -75,13 +76,13 @@ func TestNextRegistration(t *testing.T) { for _, test := range tests { // Describe - Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwsKey) + Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwkSKey) <-time.After(time.Millisecond * 100) // Build gotErr := make(chan error) gotConf := make(chan core.Registration) - go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwsKey) + go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwkSKey) // Operate go func() { @@ -133,9 +134,9 @@ type client struct { } // send is a convinient helper to send HTTP from a handler to the adapter -func (c *client) send(appId, appUrl, devAddr, nwsKey string) http.Response { +func (c *client) send(appId, appUrl, devAddr, nwkSKey string) http.Response { buf := new(bytes.Buffer) - if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nws_key":"%s"}`, appId, appUrl, nwsKey)); err != nil { + if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nwks_key":"%s"}`, appId, appUrl, nwkSKey)); err != nil { panic(err) } request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-devices/%s", c.adapter, devAddr), buf) diff --git a/core/adapters/http/utils.go b/core/adapters/http/utils.go index 1fbdcaa02..7d2ae2045 100644 --- a/core/adapters/http/utils.go +++ b/core/adapters/http/utils.go @@ -5,7 +5,7 @@ package http import "net/http" -// fail logs the given failure and sends an appropriate response to the client +// BadRequest logs the given failure and sends an appropriate response to the client func BadRequest(w http.ResponseWriter, msg string) { w.WriteHeader(http.StatusBadRequest) w.Write([]byte(msg)) From 04b9f4663715330c133fa069287acfee8469c865 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 14:54:44 +0100 Subject: [PATCH 0517/2266] [doc] enhance broadcast documentation --- core/adapters/http/adapter.go | 4 +++- core/adapters/http/broadcast/broadcast.go | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 8c1ce2055..87d681e2f 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -171,7 +171,9 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { return pktReq.Packet, packetAckNacker{response: pktReq.response}, nil } -// NextRegistration implements the core.Adapter interface +// NextRegistration implements the core.Adapter interface. Not implemented for this adapter. +// +// See broadcast and pubsub adapters for mechanisms to handle registrations. func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { return core.Packet{}, nil, ErrNotImplemented } diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index dd4cbf98a..b69c1b4b5 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -17,17 +17,21 @@ import ( "github.com/apex/log" ) +// Broadcast matereializes a extended basic http adapter which will also generate registration based +// on request responses. Whenever the adapter broadcast a request, it will trigger a registration +// demand for each new valid recipient. type Adapter struct { - *httpadapter.Adapter - ctx log.Interface - recipients []core.Recipient - registrations chan core.Registration + *httpadapter.Adapter // Composed of an original http adapter + ctx log.Interface // Just a logger + recipients []core.Recipient // A predefined list of recipients towards which broadcast messages + registrations chan core.Registration // Communication channel responsible for registration management } var ErrBadOptions = fmt.Errorf("Bad options provided") var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") +// NewAdapter promotes an existing basic adapter to a broadcast adapter using a list a of known recipient func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { return nil, ErrBadOptions @@ -41,6 +45,7 @@ func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx l }, nil } +// Send implements the Adapter interfaces func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { if len(r) == 0 { a.ctx.Debug("No recipient provided. The packet will be broadcast") @@ -54,6 +59,8 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) return packet, err } +// broadcast is merely a send where recipients are the predefined list used at instantiation time. +// Beside, a registration request will be triggered if one of the recipient reponses positively. func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) @@ -152,6 +159,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { } } +// NextRegistration implements the Adapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { registration := <-a.registrations return registration, voidAckNacker{}, nil From 831885663e61c4688032975acf76a36c71fc21f0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 15:20:36 +0100 Subject: [PATCH 0518/2266] [doc] Enhance pubsub adapter doc + fix typo in broadcast --- core/adapters/http/broadcast/broadcast.go | 2 +- core/adapters/http/pubsub/pubsub.go | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index b69c1b4b5..3d8a8b9f5 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -17,7 +17,7 @@ import ( "github.com/apex/log" ) -// Broadcast matereializes a extended basic http adapter which will also generate registration based +// Broadcast materializes an extended basic http adapter which will also generate registration based // on request responses. Whenever the adapter broadcast a request, it will trigger a registration // demand for each new valid recipient. type Adapter struct { diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index 90f5f0630..a6535afea 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -12,11 +12,15 @@ import ( "github.com/apex/log" ) +// Pubsub adapter materializes an extended basic http adapter which will also generate for each +// request made on a specific endpoint (here /end-devices). +// A Put request made on /end-devices with the appropriate parameters (refer to the parser doc for +// that) will end up in generating a registration event accessible via 'NextRegistration'. type Adapter struct { - *httpadapter.Adapter - parser.RegistrationParser - ctx log.Interface - registrations chan regReq + *httpadapter.Adapter // Composed of an original http adapter + parser.RegistrationParser // A registration parser to transform appropriate request into reg + ctx log.Interface // Just a logger + registrations chan regReq // Communication channel responsible for registrations management } type regReq struct { From 274fd7c7b2aca87883fbcc7a72f8366789b3075d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 15:44:43 +0100 Subject: [PATCH 0519/2266] [doc] Define package-level doc for http adapters, parsers and whatever --- core/adapters/http/broadcast/doc.go | 12 ++++++++++++ core/adapters/http/doc.go | 11 +++++++++++ core/adapters/http/parser/doc.go | 6 ++++++ core/adapters/http/pubsub/doc.go | 9 +++++++++ 4 files changed, 38 insertions(+) create mode 100644 core/adapters/http/broadcast/doc.go create mode 100644 core/adapters/http/doc.go create mode 100644 core/adapters/http/parser/doc.go create mode 100644 core/adapters/http/pubsub/doc.go diff --git a/core/adapters/http/broadcast/doc.go b/core/adapters/http/broadcast/doc.go new file mode 100644 index 000000000..957815a11 --- /dev/null +++ b/core/adapters/http/broadcast/doc.go @@ -0,0 +1,12 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package broadcast extends the basic http adapter to allow registrations and broadcasting of a +// packet. +// +// Registrations are implicit. They are generated during broadcast depending on the response of all +// recipients. Technically, only one recipient is expected to give a positive answer (if we put +// aside malicious one). For that positive response would be generated a given registration. +// +// Recipients to whom broadcast packets are defined once during the adapter's instantiation. +package broadcast diff --git a/core/adapters/http/doc.go b/core/adapters/http/doc.go new file mode 100644 index 000000000..b84172d25 --- /dev/null +++ b/core/adapters/http/doc.go @@ -0,0 +1,11 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package http provides adapter implementations which run on top of http. +// +// The different protocols and mechanisms used are defined in the following document: +// https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/protocols.md +// +// The basic http adapter module can be used as a brick to build something bigger. By default, it +// does not hold registrations but only sending and reception of packets. +package http diff --git a/core/adapters/http/parser/doc.go b/core/adapters/http/parser/doc.go new file mode 100644 index 000000000..96d0bd085 --- /dev/null +++ b/core/adapters/http/parser/doc.go @@ -0,0 +1,6 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package parser defines several Packet and Registration parsers that raise the abstraction level +// for adapters. +package parser diff --git a/core/adapters/http/pubsub/doc.go b/core/adapters/http/pubsub/doc.go new file mode 100644 index 000000000..4e3095c3d --- /dev/null +++ b/core/adapters/http/pubsub/doc.go @@ -0,0 +1,9 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package pubsub extend the basic http to allow registrations to be done by an http request on a +// given endpoint. +// +// The adapter basically register a new endpoint to an original basic http adapter and generate +// registrations from them. +package pubsub From fdd0854d42f8364e691c5c6c7975de4dcd2b29ee Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 15:48:56 +0100 Subject: [PATCH 0520/2266] [doc] Fix small typos in package-levle docs --- core/adapters/http/pubsub/doc.go | 2 +- core/adapters/semtech/doc.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/pubsub/doc.go b/core/adapters/http/pubsub/doc.go index 4e3095c3d..42115ae0d 100644 --- a/core/adapters/http/pubsub/doc.go +++ b/core/adapters/http/pubsub/doc.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// Package pubsub extend the basic http to allow registrations to be done by an http request on a +// Package pubsub extends the basic http to allow registrations to be done by an http request on a // given endpoint. // // The adapter basically register a new endpoint to an original basic http adapter and generate diff --git a/core/adapters/semtech/doc.go b/core/adapters/semtech/doc.go index a0fda9875..676e2ad87 100644 --- a/core/adapters/semtech/doc.go +++ b/core/adapters/semtech/doc.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// Package adapters.semtech provides an Adapter which implements the semtech forwarder protocol. +// Package semtech provides an Adapter which implements the semtech forwarder protocol. // // The protocol could be found in this document: // https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/semtech.pdf From 5148b60e0a405cbc18aef2046fcc68c711dca4a6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 16:27:19 +0100 Subject: [PATCH 0521/2266] [doc] Enhance documentation and comments of components --- core/components/broker.go | 9 ++++- core/components/doc.go | 61 ++++++++++++++++++++++++++++++ core/components/entryReadWriter.go | 22 ++++++++++- core/components/handler.go | 32 ++++++++++------ core/components/storage.go | 21 ++++++++-- 5 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 core/components/doc.go diff --git a/core/components/broker.go b/core/components/broker.go index 19125d18c..5f4306047 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -11,11 +11,13 @@ import ( "github.com/brocaar/lorawan" ) +// Broker type materializes the logic part handled by a broker type Broker struct { - ctx log.Interface - db BrokerStorage + ctx log.Interface // Just a logger + db BrokerStorage // Reference to the internal broker storage } +// NewBroker constructs a new broker from a given storage func NewBroker(db BrokerStorage, ctx log.Interface) *Broker { return &Broker{ ctx: ctx, @@ -23,6 +25,7 @@ func NewBroker(db BrokerStorage, ctx log.Interface) *Broker { } } +// HandleUp implements the core.Component interface func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { b.ctx.Debug("Handle uplink packet") // 1. Lookup for entries for the associated device @@ -75,10 +78,12 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter return an.Ack(response) } +// HandleDown implements the core.Component interface. Not implemented yet func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) error { return fmt.Errorf("Not Implemented") } +// Register implements the core.Component interface func (b *Broker) Register(r core.Registration, an core.AckNacker) error { id, okId := r.Recipient.Id.(string) url, okUrl := r.Recipient.Address.(string) diff --git a/core/components/doc.go b/core/components/doc.go new file mode 100644 index 000000000..de39935e7 --- /dev/null +++ b/core/components/doc.go @@ -0,0 +1,61 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package components offers implementations for all major components involved in the network. +// +// Router +// +// Routers are entry points of the network from the Nodes perspective. Packets transmitted by Nodes +// are forwarded to specific Routers from one or several Gateways. The Router then forwards those +// packets to one or several Brokers. The communication is bi-directional: Routers may also transfer +// packets from Broker to Gateways. +// +// Broker +// +// Brokers have a global vision of a network's part. They are in charge of several nodes, meaning +// that they will handle packets coming from those nodes (thereby, they are able to tell to Routers +// if they can handle a given packet). Several Routers may send packets coming from the same +// end-device (shared by several segments / Gateways), all duplicates are processed by the Broker +// and are sent to a corresponding Handler. +// +// A Broker is thereby able to check the integrity of a received packet and is closely communicating +// with a Network Server in order to administrate the related end-device. For a reference of +// magnitude, Brokers are designed to be in charge of a whole country or region (if the region has +// enough activity to deserve a dedicated Broker). Note that while brokers are able to verify the +// integrity of the packet (and therefore the identify of the end device), they are not able to read +// application data. +// +// Handler +// +// Handlers materialize the entry point to the network for Applications. They are secure referees +// which encode and decode data coming from application before transmitting them to a Broker of the +// network. Therefore, they are in charge of handling secret applications keys and only communicate +// an application id to Brokers as well as specific network session keys for each node (described in +// further sections). This way, the whole chain is able to forward a packet to the corresponding +// Handler without having any information about either the recipient (but a meaningless id) or the +// content. +// +// Because a given Handler is able to decrypt the data payload of a given packet, it could also +// implement mechanisms such as geolocation and send to the corresponding application some +// interesting meta-data alongside the data payload. Incidentally, a handler can only decrypt +// payload for packets related to applications registered to that handler. The handler is managing +// several secret application session keys and it uses these to encrypt and decrypt corresponding +// packet payloads. +// +// A Handler could be either part of an application or a standalone trusty server on which +// applications may register. The Things Network will provide Handlers as part of the whole network +// but - and this is true for any component - anyone could create its own implementation as long as +// it is compliant to the following specifications +// +// Network Controller +// +// Network controllers process MAC commands emitted by end-devices as well as taking care of the +// data rates and the frequency of the end-devices. They would emit commands to optimize +// the network by adjusting end-devices data rates / frequencies unless the node is requesting to +// keep its configuration as is. +// +// For the moment, a single Network controller will be associated for each Broker. No communication +// mechanisms between Network controllers is planned for the first version. Also, it won't be +// possible for a Broker to query another Network Server than the one it has been assigned to. Those +// features might be part of a second version. +package components diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go index 8e160ba94..8fb26def0 100644 --- a/core/components/entryReadWriter.go +++ b/core/components/entryReadWriter.go @@ -11,18 +11,31 @@ import ( "github.com/brocaar/lorawan" ) +// entryReadWriter offers convenient method to write and read successively from a bytes buffer. type entryReadWriter struct { err error data *bytes.Buffer } -func NewEntryReadWriter(buf []byte) *entryReadWriter { +// newEntryReadWriter create a new read/writer from an existing buffer. +// +// If a nil or empty buffer is supplied, reading from the read/writer will cause an error (io.EOF) +// Nevertheless, if a valid non-empty buffer is given, the read/writer will start reading from the +// beginning of that buffer, and will start writting at the end of it. +func newEntryReadWriter(buf []byte) *entryReadWriter { return &entryReadWriter{ err: nil, data: bytes.NewBuffer(buf), } } +// Write appends the given data at the end of the existing buffer. +// +// It does nothing if an error was previously noticed and panics if the given data are something +// different from: []byte, string, AES128Key, EUI64, DevAddr. +// +// Also, it writes the length of the given raw data encoded on 2 bytes before writting the data +// itself. In that way, data can be appended and read easily. func (w *entryReadWriter) Write(data interface{}) { var raw []byte switch data.(type) { @@ -46,6 +59,7 @@ func (w *entryReadWriter) Write(data interface{}) { w.DirectWrite(raw) } +// DirectWrite appends the given data at the end of the existing buffer (without the length). func (w *entryReadWriter) DirectWrite(data interface{}) { if w.err != nil { return @@ -56,6 +70,9 @@ func (w *entryReadWriter) DirectWrite(data interface{}) { w.err = binary.Write(w.data, binary.BigEndian, data) } +// Read retrieves next data from the given buffer. Implicitely, this implies the data to have been +// written using the Write method (len | data). Data are sent back through a callback as an array of +// bytes. func (w *entryReadWriter) Read(to func(data []byte)) { if w.err != nil { return @@ -68,6 +85,8 @@ func (w *entryReadWriter) Read(to func(data []byte)) { to(w.data.Next(int(*lenTo))) } +// Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and +// an error if any issue was encountered during the process. func (w entryReadWriter) Bytes() ([]byte, error) { if w.err != nil { return nil, w.err @@ -75,6 +94,7 @@ func (w entryReadWriter) Bytes() ([]byte, error) { return w.data.Bytes(), nil } +// Err just return the err status of the read-writer. func (w entryReadWriter) Err() error { return w.err } diff --git a/core/components/handler.go b/core/components/handler.go index c38f89af0..a9e723ef6 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -13,16 +13,18 @@ import ( "github.com/brocaar/lorawan" ) -const BUFFER_DELAY = time.Millisecond * 300 +const BUFFER_DELAY = time.Millisecond * 300 // Buffering delay. Timeframe to wait from the first received packet. type Handler struct { - ctx log.Interface - db HandlerStorage - set chan<- uplinkBundle + ctx log.Interface // Just a logger + db HandlerStorage // Reference to the handler internal storage + set chan<- uplinkBundle // Internal communication channel used to bufferise incoming packets } -type bundleId [22]byte // AppEUI | DevAddr | FCnt +type bundleId [16]byte // AppEUI(8) | DevAddr(4) | FCnt (4) +// Uplink bundles are created for each incoming packet. They help to caracterize a packet more +// precisely and postpone its processing. type uplinkBundle struct { adapter core.Adapter chresp chan interface{} // Error or decrypted packet @@ -31,6 +33,7 @@ type uplinkBundle struct { packet core.Packet } +// NewHandler constructs a new handler component. func NewHandler(db HandlerStorage, ctx log.Interface) *Handler { h := Handler{ ctx: ctx, @@ -47,6 +50,7 @@ func NewHandler(db HandlerStorage, ctx log.Interface) *Handler { return &h } +// Register implements the core.Register interface func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { h.ctx.WithField("registration", reg).Debug("New registration request") options, okOpts := reg.Options.(struct { @@ -76,6 +80,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { return nil } +// HandleUp implements the core.Component interface func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { h.ctx.Debug("Handling new uplink packet") partition, err := h.db.Partition(p) @@ -124,10 +129,13 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap } } -func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) error { - return ErrNotImplemented +// HandleDown implements the core.Component interface. Not implemented yet. +func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) (core.Packet, error) { + return core.Packet{}, ErrNotImplemented } +// consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, +// deduplicate them, and send a single enhanced packet to the upadapter for further processing. func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { ctx := h.ctx.WithField("goroutine", "consumer") ctx.Debug("Starting bundle consumer") @@ -191,7 +199,7 @@ func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplink ctx := h.ctx.WithField("goroutine", "bufferer") ctx.Debug("Starting uplink packets buffering") - processed := make(map[[20]byte]bundleId) // AppEUI | DevAddr (without the frame counter) + processed := make(map[[12]byte]bundleId) // AppEUI | DevAddr (without the frame counter) buffers := make(map[bundleId][]uplinkBundle) // Associate bundleId to a list of bufferized bundles alarm := make(chan bundleId) // Communication channel with sub-sequent alarm @@ -200,14 +208,14 @@ func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplink case id := <-alarm: b := buffers[id] delete(buffers, id) - var pid [20]byte - copy(pid[:], id[:20]) + var pid [12]byte + copy(pid[:], id[:12]) processed[pid] = id go func(b []uplinkBundle) { bundles <- b }(b) ctx.WithField("bundleId", id).Debug("Alarm done. Consuming collected bundles") case bundle := <-set: - var pid [20]byte - copy(pid[:], bundle.id[:20]) + var pid [12]byte + copy(pid[:], bundle.id[:12]) if processed[pid] == bundle.id { ctx.WithField("bundleId", bundle.id).Debug("Reject already processed bundle") go func(bundle uplinkBundle) { bundle.chresp <- ErrAlreadyProcessed }(bundle) diff --git a/core/components/storage.go b/core/components/storage.go index 90c0e87f3..98ac2db8d 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -11,11 +11,14 @@ import ( "github.com/brocaar/lorawan" ) +// storageEntry offers a friendly interface on which the storage will operate. +// Basically, a storageEntry is nothing more than a binary marshaller/unmarshaller. type storageEntry interface { - MarshalBinary() ([]byte, error) - UnmarshalBinary(data []byte) error + MarshalBinary() ([]byte, error) // implements binary.Marshaller interface + UnmarshalBinary(data []byte) error // implements binary.Unmarshaller interface } +// initDB initializes the given bolt database by creating (if not already exists) an empty bucket func initDB(db *bolt.DB, bucketName string) error { return db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) @@ -23,6 +26,7 @@ func initDB(db *bolt.DB, bucketName string) error { }) } +// store put a new entry in the given bolt database. func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { @@ -34,7 +38,7 @@ func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storag if bucket == nil { return ErrStorageUnreachable } - w := NewEntryReadWriter(bucket.Get(devAddr[:])) + w := newEntryReadWriter(bucket.Get(devAddr[:])) w.Write(marshalled) data, err := w.Bytes() if err != nil { @@ -46,7 +50,13 @@ func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storag return err } +// lookup retrieve a set of entry from a given bolt database. +// +// The shape is used as a template for retrieving and creating the data. All entries extracted from +// the database will be interpreted as instance of shape and the return result will be a slice of +// the same type of shape. func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape storageEntry) (interface{}, error) { + // First, lookup the raw entries var rawEntry []byte err := db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) @@ -64,7 +74,8 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora return nil, err } - r := NewEntryReadWriter(rawEntry) + // Then, interpret them as instance of 'shape' + r := newEntryReadWriter(rawEntry) entryType := reflect.TypeOf(shape).Elem() entries := reflect.MakeSlice(reflect.SliceOf(entryType), 0, 0) for { @@ -83,6 +94,7 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora return entries.Interface(), nil } +// flush empties each entry of a bucket associated to a given device func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { return db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) @@ -93,6 +105,7 @@ func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { }) } +// resetDB resets a given bucket from a given bolt database func resetDB(db *bolt.DB, bucketName string) error { return db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { From a7cd122cef0cb27002f6c14738ced8259c63ccb6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 16:30:40 +0100 Subject: [PATCH 0522/2266] [doc] Remove wrong caps for non exported method --- core/components/broker_storage.go | 4 ++-- core/components/handler_storage.go | 4 ++-- core/components/router_storage.go | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index a4364ae48..d51fa7b4c 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -61,7 +61,7 @@ func (s brokerBoltStorage) Reset() error { } func (entry brokerEntry) MarshalBinary() ([]byte, error) { - w := NewEntryReadWriter(nil) + w := newEntryReadWriter(nil) w.Write(entry.Id) w.Write(entry.NwkSKey) w.Write(entry.Url) @@ -72,7 +72,7 @@ func (entry *brokerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 3 { return ErrNotUnmarshable } - r := NewEntryReadWriter(data) + r := newEntryReadWriter(data) r.Read(func(data []byte) { entry.Id = string(data) }) r.Read(func(data []byte) { copy(entry.NwkSKey[:], data) }) r.Read(func(data []byte) { entry.Url = string(data) }) diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index e8d0da2d9..152a24b30 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -122,7 +122,7 @@ func (s handlerBoltStorage) Reset() error { } func (entry handlerEntry) MarshalBinary() ([]byte, error) { - w := NewEntryReadWriter(nil) + w := newEntryReadWriter(nil) w.Write(entry.AppEUI) w.Write(entry.AppSKey) w.Write(entry.DevAddr) @@ -134,7 +134,7 @@ func (entry *handlerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 4 { return ErrNotUnmarshable } - r := NewEntryReadWriter(data) + r := newEntryReadWriter(data) r.Read(func(data []byte) { copy(entry.AppEUI[:], data) }) r.Read(func(data []byte) { copy(entry.AppSKey[:], data) }) r.Read(func(data []byte) { copy(entry.DevAddr[:], data) }) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 9192c036d..0f440e49e 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -79,7 +79,7 @@ func (s routerBoltStorage) Reset() error { } func (entry routerEntry) MarshalBinary() ([]byte, error) { - w := NewEntryReadWriter(nil) + w := newEntryReadWriter(nil) w.DirectWrite(uint8(len(entry.Recipients))) for _, r := range entry.Recipients { rawId := []byte(r.Id.(string)) @@ -99,7 +99,7 @@ func (entry *routerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 1 { return ErrNotUnmarshable } - r := NewEntryReadWriter(data[0:]) + r := newEntryReadWriter(data[0:]) for i := 0; i < int(data[0]); i += 1 { var id, address string r.Read(func(data []byte) { id = string(data) }) From 104e4943e7fd446a05b5fd993841ab091c0430f8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 16:55:14 +0100 Subject: [PATCH 0523/2266] [doc] Finish documentation for components --- core/components/broker_storage.go | 22 +++++++++++++--- core/components/handler_storage.go | 42 +++++++++++++++++++++++------- core/components/router.go | 4 +-- core/components/router_storage.go | 22 +++++++++++++--- 4 files changed, 72 insertions(+), 18 deletions(-) diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index d51fa7b4c..552a21696 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -10,10 +10,18 @@ import ( "github.com/brocaar/lorawan" ) +// BrokerStorage manages the internal persistent state of a broker type BrokerStorage interface { + // Close properly ends the connection to the internal database Close() error + + // Lookup retrieves all entries associated to a given device Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) + + // Reset removes all entries stored in the storage Reset() error + + // Store creates a new entry and add it to the other entries (if any) Store(devAddr lorawan.DevAddr, entry brokerEntry) error } @@ -21,12 +29,14 @@ type brokerBoltStorage struct { *bolt.DB } +// brokerEntry stores all information that links a handler to a device type brokerEntry struct { - Id string - NwkSKey lorawan.AES128Key - Url string + Id string // The handler / application ID + NwkSKey lorawan.AES128Key // The network session key associated to the device + Url string // The webook url of the associated handler // NOTE This implies an http protocol. Should review. } +// NewBrokerStorage a new bolt broker in-memory storage func NewBrokerStorage() (BrokerStorage, error) { db, err := bolt.Open("broker_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { @@ -40,6 +50,7 @@ func NewBrokerStorage() (BrokerStorage, error) { return &brokerBoltStorage{DB: db}, nil } +// Lookup implements the brokerStorage interface func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { entries, err := lookup(s.DB, "devices", devAddr, &brokerEntry{}) if err != nil { @@ -48,18 +59,22 @@ func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error return entries.([]brokerEntry), nil } +// Store implements the brokerStorage interface func (s brokerBoltStorage) Store(devAddr lorawan.DevAddr, entry brokerEntry) error { return store(s.DB, "devices", devAddr, &entry) } +// Close implements the brokerStorage interface func (s brokerBoltStorage) Close() error { return s.DB.Close() } +// Reset implements the brokerStorage interface func (s brokerBoltStorage) Reset() error { return resetDB(s.DB, "devices") } +// MarshalBinary implements the entryStorage interface func (entry brokerEntry) MarshalBinary() ([]byte, error) { w := newEntryReadWriter(nil) w.Write(entry.Id) @@ -68,6 +83,7 @@ func (entry brokerEntry) MarshalBinary() ([]byte, error) { return w.Bytes() } +// UnmarshalBinary implements the entryStorage interface func (entry *brokerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 3 { return ErrNotUnmarshable diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 152a24b30..62dd14dc5 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -11,11 +11,23 @@ import ( "github.com/brocaar/lorawan" ) +// HandlerStorage manages the internal persistent state of a handler type HandlerStorage interface { + // Close properly ends the connection to the internal database Close() error + + // Lookup retrieves all entries associated to a given device Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) + + // Reset removes all entries stored in the storage Reset() error + + // Store creates a new entry and add it to the other entries (if any) Store(devAddr lorawan.DevAddr, entry handlerEntry) error + + // Partition split the packets given in argument in multiple set, each associated to a single + // device of a single app. Because packets may have the same address, the only way to + // distinguish them is to directly look at the network session key associated to each packet. Partition(packet ...core.Packet) ([]handlerPartition, error) } @@ -23,21 +35,24 @@ type handlerBoltStorage struct { *bolt.DB } +// handlerEntry stores all information that link an application to a device type handlerEntry struct { - AppEUI lorawan.EUI64 - AppSKey lorawan.AES128Key - DevAddr lorawan.DevAddr - NwkSKey lorawan.AES128Key + AppEUI lorawan.EUI64 // The application EUI + AppSKey lorawan.AES128Key // The application session key + DevAddr lorawan.DevAddr // The device address + NwkSKey lorawan.AES128Key // The network session key } +// handlerPartition are generated by the partition method. See that method for more details type handlerPartition struct { - handlerEntry - Id partitionId - Packets []core.Packet + handlerEntry // An actual handler entry + Id partitionId // The id of that partition + Packets []core.Packet // Packet that are part of that partition } -type partitionId [20]byte +type partitionId [12]byte // AppEUI(8) | DevAddr(4) +// NewHandlerStorage creates a new bolt handler in-memory storage func NewHandlerStorage() (HandlerStorage, error) { db, err := bolt.Open("handler_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { @@ -51,6 +66,7 @@ func NewHandlerStorage() (HandlerStorage, error) { return &handlerBoltStorage{DB: db}, nil } +// Lookup implements the handlerStorage interface func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { entries, err := lookup(s.DB, "applications", devAddr, &handlerEntry{}) if err != nil { @@ -59,10 +75,12 @@ func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, err return entries.([]handlerEntry), nil } +// Store implements the handlerStorage interface func (s handlerBoltStorage) Store(devAddr lorawan.DevAddr, entry handlerEntry) error { return store(s.DB, "applications", devAddr, &entry) } +// Partition implements the handlerStorage interface func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartition, error) { // Create a map in order to do the partition partitions := make(map[partitionId]handlerPartition) @@ -89,8 +107,8 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio // #Easy var id partitionId - copy(id[:16], entry.AppEUI[:]) - copy(id[16:], entry.DevAddr[:]) + copy(id[:8], entry.AppEUI[:]) + copy(id[8:], entry.DevAddr[:]) partitions[id] = handlerPartition{ handlerEntry: entry, Id: id, @@ -113,14 +131,17 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio return res, nil } +// Close implements the handlerStorage interface func (s handlerBoltStorage) Close() error { return s.DB.Close() } +// Reset implements the handlerStorage interface func (s handlerBoltStorage) Reset() error { return resetDB(s.DB, "applications") } +// MarshalBinary implements the storageEntry interface func (entry handlerEntry) MarshalBinary() ([]byte, error) { w := newEntryReadWriter(nil) w.Write(entry.AppEUI) @@ -130,6 +151,7 @@ func (entry handlerEntry) MarshalBinary() ([]byte, error) { return w.Bytes() } +// UnmarshalBinary implements the storageEntry interface func (entry *handlerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 4 { return ErrNotUnmarshable diff --git a/core/components/router.go b/core/components/router.go index c825b8f1d..014dfda17 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -12,12 +12,12 @@ import ( ) const ( - EXPIRY_DELAY = time.Hour * 8 + EXPIRY_DELAY = time.Hour * 8 // Life-time of an entry in the storage ) type Router struct { db RouterStorage // Local storage that maps end-device addresses to broker addresses - ctx log.Interface + ctx log.Interface // Just a logger } // NewRouter constructs a Router and setup its internal structure diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 0f440e49e..806fde222 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -11,23 +11,33 @@ import ( "github.com/brocaar/lorawan" ) +// RouterStorage manages the internal persistent state of a router type RouterStorage interface { + // Close properly ends the connection to the internal database Close() error + + // Lookup retrieves all entries associated to a given device Lookup(devAddr lorawan.DevAddr) (routerEntry, error) + + // Reset removes all entries stored in the storage Reset() error + + // Store creates a new entry and add it to the other entries (if any) Store(devAddr lorawan.DevAddr, entry routerEntry) error } type routerBoltStorage struct { *bolt.DB - expiryDelay time.Duration + expiryDelay time.Duration // Entry lifetime delay } +// routerEntry stores all information that link a device to a broker type routerEntry struct { - Recipients []core.Recipient - until time.Time + Recipients []core.Recipient // Recipients associated to a device. //NOTE why not only one ? + until time.Time // The moment until when the entry is still valid } +// NewRouterStorage creates a new router bolt in-memory storage func NewRouterStorage() (RouterStorage, error) { db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { @@ -41,6 +51,7 @@ func NewRouterStorage() (RouterStorage, error) { return &routerBoltStorage{DB: db}, nil } +// Lookup implements the RouterStorage interface func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { entries, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) if err != nil { @@ -65,19 +76,23 @@ func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) return routerEntries[0], nil } +// Store implements the RouterStorage interface func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { entry.until = time.Now().Add(s.expiryDelay) return store(s.DB, "brokers", devAddr, &entry) } +// Close implements the RouterStorage interface func (s routerBoltStorage) Close() error { return s.DB.Close() } +// Reset implements the RouterStorage interface func (s routerBoltStorage) Reset() error { return resetDB(s.DB, "brokers") } +// MarshalBinary implements the entryStorage interface func (entry routerEntry) MarshalBinary() ([]byte, error) { w := newEntryReadWriter(nil) w.DirectWrite(uint8(len(entry.Recipients))) @@ -95,6 +110,7 @@ func (entry routerEntry) MarshalBinary() ([]byte, error) { return w.Bytes() } +// UnmarshalBinary implements the entryStorage interface func (entry *routerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 1 { return ErrNotUnmarshable From d4da6b833d64688d12fcb2d954530e789cd0ce75 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 16:56:22 +0100 Subject: [PATCH 0524/2266] [doc] Fix small typos in utils package-doc header --- utils/pointer/pointer.go | 2 +- utils/testing/testing.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index eacc3f443..7dc11361c 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// package pointer provides helper method to quickly define pointer from basic go types +// Package pointer provides helper method to quickly define pointer from basic go types package pointer import ( diff --git a/utils/testing/testing.go b/utils/testing/testing.go index f1faccd5a..c56ec36f7 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// package testing offers some handy methods to display check and cross symbols with colors in test +// Package testing offers some handy methods to display check and cross symbols with colors in test // logs. package testing From f6c1d950933c5be1eaf586050fdbe33ff523070d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 2 Feb 2016 16:59:11 +0100 Subject: [PATCH 0525/2266] [doc] Update integration file accordingly to new change (parser in adapters) --- integration/broker/main.go | 7 ++++--- integration/router/main.go | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/integration/broker/main.go b/integration/broker/main.go index b773b4f64..39579a789 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -10,6 +10,7 @@ import ( . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" "github.com/TheThingsNetwork/ttn/core/components" "github.com/apex/log" @@ -28,17 +29,17 @@ func main() { routersPort, handlersPort := parseOptions() // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(routersPort), http.JSONPacketParser{}, ctx.WithField("tag", "Routers Adapter")) + rtrAdapter, err := http.NewAdapter(uint(routersPort), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } - hdlHTTPAdapter, err := http.NewAdapter(uint(handlersPort), http.JSONPacketParser{}, ctx.WithField("tag", "Handlers Adapter")) + hdlHTTPAdapter, err := http.NewAdapter(uint(handlersPort), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } - hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, pubsub.HandlerParser{}, ctx.WithField("tag", "Handlers Adapter")) + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } diff --git a/integration/router/main.go b/integration/router/main.go index 30402546d..e3bdc2679 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -13,6 +13,7 @@ import ( . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/TheThingsNetwork/ttn/core/adapters/semtech" "github.com/TheThingsNetwork/ttn/core/components" "github.com/apex/log" @@ -36,7 +37,7 @@ func main() { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } - pktAdapter, err := http.NewAdapter(uint(tcpPort), http.JSONPacketParser{}, ctx.WithField("tag", "Broker Adapter")) + pktAdapter, err := http.NewAdapter(uint(tcpPort), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } From 8bb6413ff3e0457941b3a5418b03434fcfb02169 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 4 Feb 2016 17:45:57 +0100 Subject: [PATCH 0526/2266] Upload mid-term report --- documents/reports/01_feb_2016.pdf | Bin 0 -> 114971 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 documents/reports/01_feb_2016.pdf diff --git a/documents/reports/01_feb_2016.pdf b/documents/reports/01_feb_2016.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9d68dc08ac9355b1d93a5a6c36e952991d727efa GIT binary patch literal 114971 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 z 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 Date: Mon, 8 Feb 2016 13:29:20 +0100 Subject: [PATCH 0527/2266] Update Drone secure file --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index 5597c5f49..d8acad8c2 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.t0ldtWVp88gpedoECSlfzoD3W4SCMTtah-im1qJWTnUukbc1HzQI9HBSNM-KZ9Inj4YV7D1ihIp5DHl4PDw6VC_Z1QEbpWAAKJ4vd2-j2DppAYWTi-och8Jl-lwXL5vPLQXsen2EBWQAYTaPQndeZ81nCNk6UOVAIFLeBEMVh4ZjGLSPr2jmBCi50H0mGsN4bkkkuLEroF6zywEaZgJ121pX8Y-DuIWMTfMDdm6PSSauuELByPotoJEt6qfcxqBxEOiYQgE_hdCDjeB0UM6fFcIP_XwrPbRevrb2QDPtTEtvglw4v0U29SFBnvSYQasSIwDBDi8VhLTKLWHu9VfF6g.AEn8MpTeTPsF4Jli.Rt8H7PtsGw3genu-3UdY0B4YzYye3ym23kWQTYYz5AZXh6wl484kizeWkNvZbYf-d91Whu3Ri2MiXwLPZI9QVGzFIHG2CisUd6u03s4v8ctF7H8Brhd1wU2CcVIfDhxis7HJZLOXuh0lViCl1t5tFs4AzmcueRxzJUaFciCcj1BIG3TyxO6w3hfccVeYPWJp3o2dSQa7iUevWFWh4UWruLZs7h_ql59ps-Bu9DGEUFnjaEEhICq-YAS3az3tUEgG5d3_OhUx.Le3qKsSPpFqObt8x3F0KMw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.pwY2fuQAy4hBnQtpTY5WnCj_n4mdU1vivumCzDiJA_FXxAw-UMx2-yxe4zbMRzbwIBHv2mhAfAwADpzV_fa5bDkMpgegJaVxWbTaLY4UD7vDN8u8d5qHip5PxNYh8EE8s9MI4EYh9jniJ0o4HWMnvmi0yhqcJiI1YGmd6NIBq145tvk_iCgTt7YUJiNerk8svEIIKwyvFsZO7ZVT7puwaSWdBYUgVYgePbgBcnyiKdJBjyYOqdVSP24LRyBn6CouXvwwFjMNJv9DFy0YNu201dQ1f3xmhBEC5Xg_lB_nQvSF13avSeK7lGSqsLJJY0z15xXDF7TbFnb_qyK3womOUw.1_6BbDcCOG1444LT.4drGa07w8IX31h36GXJgAWRnd76zzvx9gJi_uIC7O9GJghW95fn1sxuAuUT_cHageKm7uRiUzXXZtOeeMuB8cx-ElgMDaebMs7KP7lu_tA87uEHZlSgLW91WqA401IJ71HG_v3JCScJXv1LAspyBjL6Vvk8W8j_lzpwAlzbGx73VCM-yuK8Pw76uKZ1wlNaCXbwXI9sIYeV8mm2nkZH5WG8OQc2As9XWa_ZKo_-xkEP-MEFv7SuGhnGjWHs0qbdMxrmkqXY6.MS05HH5UdLLdc7D93yrkXQ \ No newline at end of file From df610c2eb307f59e071235ea2d0db2bcb18fd526 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 8 Feb 2016 14:26:33 +0100 Subject: [PATCH 0528/2266] Fix dependency selection for Drone --- .drone.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index 0b3dfda0b..2c1b58771 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,14 +2,16 @@ build: test: image: golang commands: - - go get $(comm -23 <(sort <(go list -f '{{join .Imports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - - go list ./... | grep -v integration | xargs go test + - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .Imports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' + - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .TestImports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' + - go list ./... | grep -v integration | xargs go test -v - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... release: image: htdvisser/ttnbuild commands: + - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .Imports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' + - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .TestImports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - ./build_binaries.sh when: branch: [master, develop] From c5af074424062e1b52041ff334efd87209780bbe Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 8 Feb 2016 14:44:41 +0100 Subject: [PATCH 0529/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index d8acad8c2..841c9cc70 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.pwY2fuQAy4hBnQtpTY5WnCj_n4mdU1vivumCzDiJA_FXxAw-UMx2-yxe4zbMRzbwIBHv2mhAfAwADpzV_fa5bDkMpgegJaVxWbTaLY4UD7vDN8u8d5qHip5PxNYh8EE8s9MI4EYh9jniJ0o4HWMnvmi0yhqcJiI1YGmd6NIBq145tvk_iCgTt7YUJiNerk8svEIIKwyvFsZO7ZVT7puwaSWdBYUgVYgePbgBcnyiKdJBjyYOqdVSP24LRyBn6CouXvwwFjMNJv9DFy0YNu201dQ1f3xmhBEC5Xg_lB_nQvSF13avSeK7lGSqsLJJY0z15xXDF7TbFnb_qyK3womOUw.1_6BbDcCOG1444LT.4drGa07w8IX31h36GXJgAWRnd76zzvx9gJi_uIC7O9GJghW95fn1sxuAuUT_cHageKm7uRiUzXXZtOeeMuB8cx-ElgMDaebMs7KP7lu_tA87uEHZlSgLW91WqA401IJ71HG_v3JCScJXv1LAspyBjL6Vvk8W8j_lzpwAlzbGx73VCM-yuK8Pw76uKZ1wlNaCXbwXI9sIYeV8mm2nkZH5WG8OQc2As9XWa_ZKo_-xkEP-MEFv7SuGhnGjWHs0qbdMxrmkqXY6.MS05HH5UdLLdc7D93yrkXQ \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.AcG0ZL_fM56k9Vd2wBH9lJrCjFMuW8fs_XJF_9YlU5S5PEWdotmfLAZeilOF99nw3RFra24sKo_sfsX4WGJPzutCOL8rAAY4QJCaVLjOJatO8m5LbLryEUAfFrbipI9ZWemThC6YHpdFNmLNS1QmRKMbwbRuXlh1WUVEtk2mP42COJMcjuAVChhg_qrfinxIaZLRmGRmJP2M_rjzK5v26CFcbXS8pRxR-tW5node7r_V9sj_J6Je6Qeok-yzSSCyXfAI6rR3T-_LIAjPUCJgpAEPUWrh6YdcpJx85bFsWn2hoghTy7wJydYjGVdoGi4WmzY6k7szockNVU8vosfp9g.WTTxM0AtX8cltzmw.qBrQoryiemZrWSERNjych0nWR8Mhc7oaIB2umU7h5niJGbrwTMkdBTsBc1NEca5zA4DWEB7rDZhHtvW2GsuaYn2vw3taQv7AtWBx5qFHoTiX1JKipOyRp7BELcJLH3boz9Phl4JI21Y_MGZ3FyhjEJBV5unxcj2gn4f8Czr4huoah3feWAISlxbAdDfcvIkVDf14Pp6z7DZ19fG3qob6L8xtG1N1XyEt3DsdJ5T2wg1rSxwE7dRI7BUKPJzTMn9Q5LO3dEY5.ERl5tu03lw6jsf5slM8NOA \ No newline at end of file From e10ce2fdd2af23dbe49f27fc2d7fd66fb8d8e8b8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 11:03:44 +0100 Subject: [PATCH 0530/2266] Check for ErrNotFound in lookup Temporary fix until ErrDeviceNotFound and ErrNotFound are merged --- core/components/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/router.go b/core/components/router.go index 014dfda17..4b2f64656 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -55,7 +55,7 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt } entries, err := r.db.Lookup(devAddr) - if err != ErrDeviceNotFound && err != ErrEntryExpired { + if err != ErrDeviceNotFound && err != ErrNotFound && err != ErrEntryExpired { an.Nack() return err } From b874c784b7ff413bfd274bb3c0b69a9232be4984 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 11:04:19 +0100 Subject: [PATCH 0531/2266] Logging with our logger instead of fmt.Println --- integration/router/main.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/integration/router/main.go b/integration/router/main.go index e3bdc2679..917d2f44c 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -5,7 +5,6 @@ package main import ( "flag" - "fmt" "os" "strconv" "strings" @@ -61,12 +60,12 @@ func main() { for { packet, an, err := gtwAdapter.Next() if err != nil { - fmt.Println(err) + ctx.WithError(err).Warn("Could not get next packet from gateway") continue } go func(packet Packet, an AckNacker) { if err := router.HandleUp(packet, an, brkAdapter); err != nil { - fmt.Println(err) + ctx.WithError(err).Warn("Could not process packet from gateway") } }(packet, an) } @@ -77,12 +76,12 @@ func main() { for { reg, an, err := brkAdapter.NextRegistration() if err != nil { - fmt.Println(err) + ctx.WithError(err).Warn("Could not get next registration from broker") continue } go func(reg Registration, an AckNacker) { if err := router.Register(reg, an); err != nil { - fmt.Println(err) + ctx.WithError(err).Warn("Could not process registration from broker") } }(reg, an) } From 7a2441c0078ebcd737d3627328715eb8cea052d8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 13:16:53 +0100 Subject: [PATCH 0532/2266] Don't log the datagram payload --- core/adapters/semtech/adapter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 7ca1ef57b..715fb9310 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -103,7 +103,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { a.ctx.WithError(err).Error("Connection error") continue } - a.ctx.WithField("datagram", buf[:n]).Debug("Incoming datagram") + a.ctx.Debug("Incoming datagram") pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) From 80d34b23a248b626dec6faf8fea4cf4de03639bd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 13:17:20 +0100 Subject: [PATCH 0533/2266] Prettier recipient logging --- core/adapters/http/adapter.go | 2 +- core/adapters/http/broadcast/broadcast.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 87d681e2f..ec761f6e8 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -89,7 +89,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) go func(recipient core.Recipient) { defer wg.Done() - ctx := ctx.WithField("recipient", recipient) + ctx := ctx.WithField("recipient", recipient.Address) ctx.Debug("POST Request") buf := new(bytes.Buffer) diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 3d8a8b9f5..c488f5120 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -91,7 +91,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { go func(recipient core.Recipient) { defer wg.Done() - ctx := a.ctx.WithField("recipient", recipient) + ctx := a.ctx.WithField("recipient", recipient.Address) ctx.Debug("POST Request") From d21252ea2a985c98b1605fca7909fbe3e97d4d31 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 13:19:21 +0100 Subject: [PATCH 0534/2266] Fix re-using of TCP connections between Router and Broker According to Go docs: "The default HTTP client's Transport does not attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections ("keep-alive") unless the Body is read to completion and is closed." This means that we should always read the entire response body and close it. --- core/adapters/http/adapter.go | 15 +++++++++++---- core/adapters/http/broadcast/broadcast.go | 15 +++++++++++---- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index ec761f6e8..5c2072090 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "net/http" "sync" @@ -102,6 +103,14 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) return } + defer func() { + // This is needed because the default HTTP client's Transport does not + // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections unless the Body + // is read to completion and is closed. + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + }() + // Check response code if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated { ctx.WithField("response", resp.StatusCode).Warn("Unexpected response") @@ -110,9 +119,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) } // Process response body - raw := make([]byte, resp.ContentLength) - n, err := resp.Body.Read(raw) - defer resp.Body.Close() + raw, err := ioutil.ReadAll(resp.Body) if err != nil && err != io.EOF { cherr <- err return @@ -120,7 +127,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Process packet var packet core.Packet - if err := json.Unmarshal(raw[:n], &packet); err != nil { + if err := json.Unmarshal(raw, &packet); err != nil { cherr <- err return } diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index c488f5120..18331a8ab 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -9,6 +9,7 @@ import ( "encoding/json" "fmt" "io" + "io/ioutil" "net/http" "sync" @@ -102,20 +103,26 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { cherr <- err return } - defer resp.Body.Close() + + defer func() { + // This is needed because the default HTTP client's Transport does not + // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections unless the Body + // is read to completion and is closed. + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + }() switch resp.StatusCode { case http.StatusOK: ctx.WithField("devAddr", devAddr).Debug("Recipient registered for packet") - raw := make([]byte, resp.ContentLength) - n, err := resp.Body.Read(raw) + raw, err := ioutil.ReadAll(resp.Body) if err != nil && err != io.EOF { cherr <- err return } var packet core.Packet - if err := json.Unmarshal(raw[:n], &packet); err != nil { + if err := json.Unmarshal(raw, &packet); err != nil { cherr <- err return } From a380b594bfb89f85707ab90ee3ab51fa52526b95 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 31 Jan 2016 15:22:28 +0100 Subject: [PATCH 0535/2266] Create minimal Dockerfiles with CI-built binaries --- integration/broker/Dockerfile | 29 ++++++++++++----------------- integration/router/Dockerfile | 29 ++++++++++++----------------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/integration/broker/Dockerfile b/integration/broker/Dockerfile index 2d34ac397..b7f3eca4f 100644 --- a/integration/broker/Dockerfile +++ b/integration/broker/Dockerfile @@ -1,20 +1,15 @@ -# The container will assume to have a $BROKER env var defined +FROM alpine -# Go, because go is life -FROM golang:latest +RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* -# Dependencies, everything on master -RUN go get "github.com/TheThingsNetwork/ttn/..." +RUN apk --update add curl tar \ + && mkdir /ttnsrc \ + && curl -sL -o /ttnsrc/broker-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.gz" \ + && tar -xf /ttnsrc/broker-linux-amd64.tar.gz -C /ttnsrc \ + && mv /ttnsrc/broker-linux-amd64 /usr/local/bin/broker \ + && chmod 755 /usr/local/bin/broker \ + && rm -rf /ttnsrc \ + && apk del curl tar \ + && rm -rf /var/cache/apk/* -# Actual files to build -RUN mkdir ~/TheThingsNetwork -ADD . ~/TheThingsNetwork -WORKDIR ~/TheThingsNetwork - -# Expose the port on which the adapters will listen to -EXPOSE 3000/tcp -EXPOSE 4000/tcp - -# Build & Launch -RUN go build -o broker . -CMD ./broker --routers-port 4000 --handlers-port 3000 +ENTRYPOINT ["/usr/local/bin/broker"] diff --git a/integration/router/Dockerfile b/integration/router/Dockerfile index 2501fcd7e..4e6b98adc 100644 --- a/integration/router/Dockerfile +++ b/integration/router/Dockerfile @@ -1,20 +1,15 @@ -# The container will assume to have a $BROKER env var defined +FROM alpine -# Go, because go is life -FROM golang:latest +RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* -# Dependencies, everything on master -RUN go get "github.com/TheThingsNetwork/ttn/..." +RUN apk --update add curl tar \ + && mkdir /ttnsrc \ + && curl -sL -o /ttnsrc/router-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.gz" \ + && tar -xf /ttnsrc/router-linux-amd64.tar.gz -C /ttnsrc \ + && mv /ttnsrc/router-linux-amd64 /usr/local/bin/router \ + && chmod 755 /usr/local/bin/router \ + && rm -rf /ttnsrc \ + && apk del curl tar \ + && rm -rf /var/cache/apk/* -# Actual files to build -RUN mkdir ~/TheThingsNetwork -ADD . ~/TheThingsNetwork -WORKDIR ~/TheThingsNetwork - -# Expose the port on which the gateway adapter will listen to -EXPOSE 33000/udp -EXPOSE 4000/tcp - -# Build & Launch -RUN go build -o router . -CMD ./router --brokers $BROKERS --udp-port 33000 --tcp-port 4000 +ENTRYPOINT ["/usr/local/bin/router"] From a15def37c2b338dd39467053358441e5e27564d5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 9 Feb 2016 14:23:28 +0100 Subject: [PATCH 0536/2266] Remove development Dockerfiles --- Broker_Dockerfile | 16 ---------------- Router_Dockerfile | 17 ----------------- deploy.sh | 12 ------------ 3 files changed, 45 deletions(-) delete mode 100644 Broker_Dockerfile delete mode 100644 Router_Dockerfile delete mode 100755 deploy.sh diff --git a/Broker_Dockerfile b/Broker_Dockerfile deleted file mode 100644 index 9edb9ebbd..000000000 --- a/Broker_Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -FROM golang:latest - -RUN go get github.com/brocaar/lorawan -RUN go get github.com/apex/log -RUN go get github.com/jacobsa/crypto/cmac -RUN mkdir -p /go/src/github.com/TheThingsNetwork/ttn -ADD . /go/src/github.com/TheThingsNetwork/ttn - -ENV HANDLERS_PORT=3000 -ENV ROUTERS_PORT=4000 - -EXPOSE 3000/tcp -EXPOSE 4000/tcp - -RUN go build github.com/TheThingsNetwork/ttn/integration/broker -CMD ./broker --handlers-port $HANDLERS_PORT --routers-port $ROUTERS_PORT diff --git a/Router_Dockerfile b/Router_Dockerfile deleted file mode 100644 index 1da4a1f41..000000000 --- a/Router_Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -FROM golang:latest - -RUN go get github.com/apex/log -RUN go get github.com/brocaar/lorawan -RUN go get github.com/jacobsa/crypto/cmac - -RUN mkdir -p /go/src/github.com/TheThingsNetwork/ttn -ADD . /go/src/github.com/TheThingsNetwork/ttn - -ENV UDP_PORT=33000 -ENV TCP_PORT=4000 - -EXPOSE 33000/udp -EXPOSE 4000/tcp - -RUN go build github.com/TheThingsNetwork/ttn/integration/router -CMD ./router --brokers $BROKERS --udp-port $UDP_PORT --tcp-port $TCP_PORT diff --git a/deploy.sh b/deploy.sh deleted file mode 100755 index 2ab7d5ca3..000000000 --- a/deploy.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /usr/bin/env sh - -docker build -t broker -f Broker_Dockerfile . -docker build -t router -f Router_Dockerfile . - -echo "\n=== STARTING BROKER ===\n" -docker run broker & -sleep 2 && BROKER=$(docker inspect $(docker ps -q) | grep '"IPAddress"' | head -n 1 | grep -oE "([0-9]+\.?){4}") - -echo "\n=== STARTING ROUTER ===\n" -docker run -e BROKERS=$BROKER router & -unset BROKER From 1daecad45d8ef3a2925b0fc5be80b4251837c14d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 9 Feb 2016 14:23:55 +0100 Subject: [PATCH 0537/2266] Add READMEs for Docker Hub --- integration/broker/README.md | 23 +++++++++++++++++++++++ integration/router/README.md | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 integration/broker/README.md create mode 100644 integration/router/README.md diff --git a/integration/broker/README.md b/integration/broker/README.md new file mode 100644 index 000000000..21b4b8637 --- /dev/null +++ b/integration/broker/README.md @@ -0,0 +1,23 @@ +# The Things Network Broker + +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) + +![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) + +The Things Network is a global open crowdsourced Internet of Things data network. + +## Status + +This image is a pre-1.0 version of The Things Network's Broker component. It is **under heavy development** and currently it's APIs and code are not yet stable. + +## Tags + +* [`latest`, `develop` (TheThingsNetwork/ttn - develop)](https://github.com/TheThingsNetwork/ttn/blob/develop/integration/broker/Dockerfile) + +## Usage + +``` +docker pull thethingsnetwork/broker + +docker run -p 3000:3000 -p 4000:4000 thethingsnetwork/broker --handlers-port 3000 --routers-port 4000 +``` diff --git a/integration/router/README.md b/integration/router/README.md new file mode 100644 index 000000000..90f8e0509 --- /dev/null +++ b/integration/router/README.md @@ -0,0 +1,23 @@ +# The Things Network Router + +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) + +![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) + +The Things Network is a global open crowdsourced Internet of Things data network. + +## Status + +This image is a pre-1.0 version of The Things Network's Router component. It is **under heavy development** and currently it's APIs and code are not yet stable. + +## Tags + +* [`latest`, `develop` (TheThingsNetwork/ttn - develop)](https://github.com/TheThingsNetwork/ttn/blob/develop/integration/router/Dockerfile) + +## Usage + +``` +docker pull thethingsnetwork/router + +docker run -p 33000:33000/udp -p 4000:4000 thethingsnetwork/router --udp-port 33000 --tcp-port 4000 --brokers "10.10.10.40:3000,broker.host.com:3000" +``` From ba60b3b030e4a94df232068fdcb8097f68dd61d4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 13:31:30 +0100 Subject: [PATCH 0538/2266] Change some ports --- integration/broker/README.md | 2 +- integration/router/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/broker/README.md b/integration/broker/README.md index 21b4b8637..285d2dbb0 100644 --- a/integration/broker/README.md +++ b/integration/broker/README.md @@ -19,5 +19,5 @@ This image is a pre-1.0 version of The Things Network's Broker component. It is ``` docker pull thethingsnetwork/broker -docker run -p 3000:3000 -p 4000:4000 thethingsnetwork/broker --handlers-port 3000 --routers-port 4000 +docker run -p 88642:88642 -p 88672:88672 thethingsnetwork/broker --handlers-port 88642 --routers-port 88672 ``` diff --git a/integration/router/README.md b/integration/router/README.md index 90f8e0509..c4a9e1051 100644 --- a/integration/router/README.md +++ b/integration/router/README.md @@ -19,5 +19,5 @@ This image is a pre-1.0 version of The Things Network's Router component. It is ``` docker pull thethingsnetwork/router -docker run -p 33000:33000/udp -p 4000:4000 thethingsnetwork/router --udp-port 33000 --tcp-port 4000 --brokers "10.10.10.40:3000,broker.host.com:3000" +docker run -p 88647:88647/udp -p 88627:88627 thethingsnetwork/router --udp-port 88647 --tcp-port 88627 --brokers "1.2.3.4:88672,broker.host.com:88672" ``` From d20e346054e32259198fd03e4752e3ffed606264 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 14:09:25 +0100 Subject: [PATCH 0539/2266] Ports in README --- integration/broker/README.md | 2 +- integration/router/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/integration/broker/README.md b/integration/broker/README.md index 285d2dbb0..5aaf73638 100644 --- a/integration/broker/README.md +++ b/integration/broker/README.md @@ -19,5 +19,5 @@ This image is a pre-1.0 version of The Things Network's Broker component. It is ``` docker pull thethingsnetwork/broker -docker run -p 88642:88642 -p 88672:88672 thethingsnetwork/broker --handlers-port 88642 --routers-port 88672 +docker run -p 8642:8642 -p 8672:8672 thethingsnetwork/broker --handlers-port 8642 --routers-port 8672 ``` diff --git a/integration/router/README.md b/integration/router/README.md index c4a9e1051..92057a7b7 100644 --- a/integration/router/README.md +++ b/integration/router/README.md @@ -19,5 +19,5 @@ This image is a pre-1.0 version of The Things Network's Router component. It is ``` docker pull thethingsnetwork/router -docker run -p 88647:88647/udp -p 88627:88627 thethingsnetwork/router --udp-port 88647 --tcp-port 88627 --brokers "1.2.3.4:88672,broker.host.com:88672" +docker run -p 8647:8647/udp -p 8627:8627 thethingsnetwork/router --udp-port 8647 --tcp-port 8627 --brokers "1.2.3.4:8672,broker.host.com:8672" ``` From 003f8c3093d82facb37b5a5a7ec0deb63f679095 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 10 Feb 2016 17:59:59 +0100 Subject: [PATCH 0540/2266] Simulate Random Nodes --- integration/mocker/main.go | 48 +++++++++++++++ simulators/gateway/imitator.go | 46 +++++++++++++- simulators/node/node.go | 108 +++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 integration/mocker/main.go create mode 100644 simulators/node/node.go diff --git a/integration/mocker/main.go b/integration/mocker/main.go new file mode 100644 index 000000000..56fb88a61 --- /dev/null +++ b/integration/mocker/main.go @@ -0,0 +1,48 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "flag" + "strings" + + "github.com/TheThingsNetwork/ttn/simulators/gateway" + "github.com/TheThingsNetwork/ttn/simulators/node" +) + +func main() { + routers := parseOptions() + + nodes := []node.LiveNode{ + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + node.New(), + } + + gateway.MockRandomly(nodes, routers...) +} + +func parseOptions() (routers []string) { + var routersFlag string + flag.StringVar(&routersFlag, "routers", "", `Router addresses to which send packets. + For instance: 10.10.3.34:8080,thethingsnetwork.router.com:3000 + `) + flag.Parse() + + if routersFlag == "" { + panic("Need to provide at least one router address") + } + + routers = strings.Split(routersFlag, ",") + return +} diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 5a8e91d64..fe0cfaa45 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -14,6 +14,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/simulators/node" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" "github.com/apex/log/handlers/text" @@ -77,8 +78,49 @@ func MockWithSchedule(filename string, delay time.Duration, routers ...string) { } } -func MockRandomly() { - // TODO +func MockRandomly(nodes []node.LiveNode, routers ...string) { + var adapters []io.ReadWriteCloser + for _, router := range routers { + addr, err := net.ResolveUDPAddr("udp", router) + if err != nil { + panic(err) + } + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + panic(err) + } + adapters = append(adapters, conn) + } + + log.SetHandler(text.New(os.Stdout)) + log.SetLevel(log.DebugLevel) + ctx := log.WithFields(log.Fields{"Simulator": "Gateway"}) + + fwd, err := NewForwarder([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ctx, adapters...) + if err != nil { + panic(err) + } + + messages := make(chan string) + + for _, n := range nodes { + go n.Start(messages) + } + + for { + message := <-messages + + rxpk := semtech.RXPK{ + Rssi: pointer.Int(-20), + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("LORA"), + Data: pointer.String(message), + } + + if err := fwd.Forward(rxpk); err != nil { + ctx.WithError(err).WithField("rxpk", rxpk).Warn("failed to forward") + } + } } // rxpkFromConf read an input json file and parse it into a list of RXPK packets diff --git a/simulators/node/node.go b/simulators/node/node.go new file mode 100644 index 000000000..3c8745182 --- /dev/null +++ b/simulators/node/node.go @@ -0,0 +1,108 @@ +package node + +import ( + "encoding/base64" + "fmt" + "math/rand" + "strings" + "time" + + "github.com/brocaar/lorawan" +) + +func randBytes(n int) []byte { + bytes := make([]byte, n) + for i := range bytes { + bytes[i] = byte(rand.Intn(255)) + } + return bytes +} + +type LiveNode interface { + Start(chan string) +} + +type Node struct { + DevAddr lorawan.DevAddr + AppEUI lorawan.EUI64 + NwkSKey lorawan.AES128Key + AppSKey lorawan.AES128Key + FCntUp uint32 +} + +func New() *Node { + devAddr := [4]byte{} + copy(devAddr[:], randBytes(4)) + + appEUI := [8]byte{} + copy(appEUI[:], randBytes(8)) + + nwkSKey := [16]byte{} + copy(nwkSKey[:], randBytes(16)) + + appSKey := [16]byte{} + copy(appSKey[:], randBytes(16)) + + return &Node{ + DevAddr: lorawan.DevAddr(devAddr), + AppEUI: lorawan.EUI64(appEUI), + NwkSKey: lorawan.AES128Key(nwkSKey), + AppSKey: lorawan.AES128Key(appSKey), + } +} + +func (node *Node) Start(messages chan string) { + for { + <-time.After(time.Duration(rand.Intn(10)) * time.Second) + messages <- node.NextMessage() + } +} + +func (node *Node) String() string { + return fmt.Sprintf("Node %s:\n AppEUI: %s\n NwkSKey: %s\n AppSKey: %s\n FCntUp: %d", node.DevAddr, node.AppEUI, node.NwkSKey, node.AppSKey, node.FCntUp) +} + +func (node *Node) NextMessage() string { + node.FCntUp++ + raw := node.BuildMessage([]byte(fmt.Sprintf("This is message %d.", node.FCntUp))) + return strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") +} + +func (node *Node) BuildMessage(data []byte) []byte { + uplink := true + + macPayload := lorawan.NewMACPayload(uplink) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: node.DevAddr, + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: node.FCntUp, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: data}} + + if err := macPayload.EncryptFRMPayload(node.AppSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(uplink) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(node.NwkSKey); err != nil { + panic(err) + } + + bytes, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + + return bytes +} From 28413abfc8ab71ef9bb80be24f9d2c0bdec1e674 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 11 Feb 2016 09:43:13 +0100 Subject: [PATCH 0541/2266] Update Node mocking - Message rate is now exponential and can be set using the `--interval` flag - Nodes are logging their actions - Number of nodes can be set using the `--nodes` flag --- integration/mocker/main.go | 37 ++++++++++++++++++++------------ simulators/gateway/imitator.go | 6 +----- simulators/node/node.go | 39 +++++++++++++++++++++------------- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/integration/mocker/main.go b/integration/mocker/main.go index 56fb88a61..1101219a1 100644 --- a/integration/mocker/main.go +++ b/integration/mocker/main.go @@ -5,31 +5,36 @@ package main import ( "flag" + "os" "strings" "github.com/TheThingsNetwork/ttn/simulators/gateway" "github.com/TheThingsNetwork/ttn/simulators/node" + "github.com/apex/log" + "github.com/apex/log/handlers/text" +) + +var ( + numNodes int + interval int ) func main() { routers := parseOptions() - nodes := []node.LiveNode{ - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), - node.New(), + log.SetHandler(text.New(os.Stdout)) + log.SetLevel(log.DebugLevel) + + nodeCtx := log.WithFields(log.Fields{"Simulator": "Node"}) + gatewayCtx := log.WithFields(log.Fields{"Simulator": "Gateway"}) + + nodes := []node.LiveNode{} + + for i := 0; i < numNodes; i++ { + nodes = append(nodes, node.New(interval, nodeCtx)) } - gateway.MockRandomly(nodes, routers...) + gateway.MockRandomly(nodes, gatewayCtx, routers...) } func parseOptions() (routers []string) { @@ -37,6 +42,10 @@ func parseOptions() (routers []string) { flag.StringVar(&routersFlag, "routers", "", `Router addresses to which send packets. For instance: 10.10.3.34:8080,thethingsnetwork.router.com:3000 `) + + flag.IntVar(&interval, "interval", 500, "Average time (in milliseconds) between node messages") + flag.IntVar(&numNodes, "nodes", 10, "Number of nodes to simulate") + flag.Parse() if routersFlag == "" { diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index fe0cfaa45..b189671e2 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -78,7 +78,7 @@ func MockWithSchedule(filename string, delay time.Duration, routers ...string) { } } -func MockRandomly(nodes []node.LiveNode, routers ...string) { +func MockRandomly(nodes []node.LiveNode, ctx log.Interface, routers ...string) { var adapters []io.ReadWriteCloser for _, router := range routers { addr, err := net.ResolveUDPAddr("udp", router) @@ -92,10 +92,6 @@ func MockRandomly(nodes []node.LiveNode, routers ...string) { adapters = append(adapters, conn) } - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - ctx := log.WithFields(log.Fields{"Simulator": "Gateway"}) - fwd, err := NewForwarder([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ctx, adapters...) if err != nil { panic(err) diff --git a/simulators/node/node.go b/simulators/node/node.go index 3c8745182..3a56c1915 100644 --- a/simulators/node/node.go +++ b/simulators/node/node.go @@ -7,6 +7,7 @@ import ( "strings" "time" + "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -23,14 +24,16 @@ type LiveNode interface { } type Node struct { - DevAddr lorawan.DevAddr - AppEUI lorawan.EUI64 - NwkSKey lorawan.AES128Key - AppSKey lorawan.AES128Key - FCntUp uint32 + DevAddr lorawan.DevAddr + AppEUI lorawan.EUI64 + NwkSKey lorawan.AES128Key + AppSKey lorawan.AES128Key + FCntUp uint32 + interval int + ctx log.Interface } -func New() *Node { +func New(interval int, ctx log.Interface) *Node { devAddr := [4]byte{} copy(devAddr[:], randBytes(4)) @@ -43,18 +46,23 @@ func New() *Node { appSKey := [16]byte{} copy(appSKey[:], randBytes(16)) - return &Node{ - DevAddr: lorawan.DevAddr(devAddr), - AppEUI: lorawan.EUI64(appEUI), - NwkSKey: lorawan.AES128Key(nwkSKey), - AppSKey: lorawan.AES128Key(appSKey), + node := &Node{ + DevAddr: lorawan.DevAddr(devAddr), + AppEUI: lorawan.EUI64(appEUI), + NwkSKey: lorawan.AES128Key(nwkSKey), + AppSKey: lorawan.AES128Key(appSKey), + interval: interval, } + + node.ctx = ctx.WithField("devAddr", node.DevAddr) + + return node } func (node *Node) Start(messages chan string) { for { - <-time.After(time.Duration(rand.Intn(10)) * time.Second) - messages <- node.NextMessage() + <-time.After(time.Duration(rand.ExpFloat64()*float64(node.interval)) * time.Millisecond) + node.NextMessage(messages) } } @@ -62,10 +70,11 @@ func (node *Node) String() string { return fmt.Sprintf("Node %s:\n AppEUI: %s\n NwkSKey: %s\n AppSKey: %s\n FCntUp: %d", node.DevAddr, node.AppEUI, node.NwkSKey, node.AppSKey, node.FCntUp) } -func (node *Node) NextMessage() string { +func (node *Node) NextMessage(messages chan string) { node.FCntUp++ raw := node.BuildMessage([]byte(fmt.Sprintf("This is message %d.", node.FCntUp))) - return strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + node.ctx.WithField("FCnt", node.FCntUp).Debug("Publishing message") + messages <- strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") } func (node *Node) BuildMessage(data []byte) []byte { From 981c35f80f3fb4cc68b9758290c4fa16b0db210c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 11 Feb 2016 11:19:45 +0100 Subject: [PATCH 0542/2266] Simple node test --- simulators/node/node_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 simulators/node/node_test.go diff --git a/simulators/node/node_test.go b/simulators/node/node_test.go new file mode 100644 index 000000000..97b2c0f49 --- /dev/null +++ b/simulators/node/node_test.go @@ -0,0 +1,32 @@ +package node + +import ( + "math/rand" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/goconvey/convey" +) + +func TestNewNode(t *testing.T) { + rand.Seed(1234567) // Let's use a static seed for testing + + Convey("Given a Node", t, func() { + ctx := GetLogger(t, "Node") + node := New(100, ctx) + + messages := make(chan string, 3) + + Convey("When sending three messages", func() { + node.NextMessage(messages) + node.NextMessage(messages) + node.NextMessage(messages) + + Convey("They should be published to the channel", func() { + So(len(messages), ShouldEqual, 3) + }) + }) + + }) + +} From 723a715a0476d212b62de3cbd1daf557becb1ab8 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 11 Feb 2016 15:50:52 +0100 Subject: [PATCH 0543/2266] Introduced api package with topics encoding/decoding --- AUTHORS | 1 + api/topics.go | 64 +++++++++++++++++++++++++++ api/topics_test.go | 107 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 172 insertions(+) create mode 100644 api/topics.go create mode 100644 api/topics_test.go diff --git a/AUTHORS b/AUTHORS index 96dbd5920..7371c433f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,4 +9,5 @@ # Please keep the list sorted. Hylke Visser +Johan Stokking Matthias Benkort diff --git a/api/topics.go b/api/topics.go new file mode 100644 index 000000000..ea8158e83 --- /dev/null +++ b/api/topics.go @@ -0,0 +1,64 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package api + +import ( + "errors" + "fmt" + "strings" +) + +// TopicType represents the type of a topic +type TopicType string + +const ( + // Devices indicates a topic for devices + Devices TopicType = "devices" +) + +// DeviceTopicType represents the type of a device topic +type DeviceTopicType string + +const ( + // Activations of devices + Activations DeviceTopicType = "activations" + // Uplink data from devices + Uplink DeviceTopicType = "up" + // Downlink data to devices + Downlink DeviceTopicType = "down" +) + +// DeviceTopic represents a publish/subscribe topic for application devices +type DeviceTopic struct { + AppEui string + DevEui string + Type DeviceTopicType +} + +// GetTopicType returns the type of the specified topic +func GetTopicType(topic string) (TopicType, error) { + parts := strings.Split(topic, "/") + if len(parts) < 2 { + return "", errors.New("Invalid format") + } + return TopicType(parts[1]), nil +} + +// DecodeDeviceTopic decodes the specified topic in a DeviceTopic structure +func DecodeDeviceTopic(topic string) (*DeviceTopic, error) { + parts := strings.Split(topic, "/") + if len(parts) < 4 { + return nil, errors.New("Invalid format") + } + if TopicType(parts[1]) != Devices { + return nil, errors.New("Not a device topic") + } + + return &DeviceTopic{parts[0], parts[2], DeviceTopicType(parts[3])}, nil +} + +// Encode encodes the DeviceTopic to a topic +func (t *DeviceTopic) Encode() (string, error) { + return fmt.Sprintf("%s/%s/%s/%s", t.AppEui, Devices, t.DevEui, t.Type), nil +} diff --git a/api/topics_test.go b/api/topics_test.go new file mode 100644 index 000000000..4118bc204 --- /dev/null +++ b/api/topics_test.go @@ -0,0 +1,107 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package api + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestGetType(t *testing.T) { + Convey("Given the topic 0101/somevalue", t, func() { + topic := "0101/somevalue" + Convey("Then the type is somevalue", func() { + tp, err := GetTopicType(topic) + So(err, ShouldBeNil) + So(tp, ShouldEqual, "somevalue") + }) + }) + + Convey("Given the topic 0101/devices/AA/up", t, func() { + topic := "0101/devices/AA/up" + Convey("Then the type is devices", func() { + tp, err := GetTopicType(topic) + So(err, ShouldBeNil) + So(tp, ShouldEqual, Devices) + }) + }) + + Convey("Given the topic 0101", t, func() { + topic := "0101" + Convey("Then the type cannot be determined", func() { + _, err := GetTopicType(topic) + So(err, ShouldNotBeNil) + }) + }) +} + +func TestDecodeDeviceTopic(t *testing.T) { + Convey("Given the topic 0101/devices/AA/up", t, func() { + topic := "0101/devices/AA/up" + Convey("Then the type is devices", func() { + tp, err := GetTopicType(topic) + So(err, ShouldBeNil) + So(tp, ShouldEqual, Devices) + }) + + Convey("Then the device topic can be decoded", func() { + tp, err := DecodeDeviceTopic(topic) + So(err, ShouldBeNil) + + Convey("And appEui is 0101, devEui is AA and type is Uplink", func() { + So(tp.AppEui, ShouldEqual, "0101") + So(tp.DevEui, ShouldEqual, "AA") + So(tp.Type, ShouldEqual, Uplink) + }) + }) + }) + + Convey("Given the topic 0101/devices", t, func() { + topic := "0101/devices" + Convey("Then the device topic cannot be decoded", func() { + _, err := DecodeDeviceTopic(topic) + So(err, ShouldNotBeNil) + }) + }) + + Convey("Given the topic 0101/aa/bb/cc", t, func() { + topic := "0101/aa/bb/cc" + Convey("Then the device topic cannot be decoded", func() { + _, err := DecodeDeviceTopic(topic) + So(err, ShouldNotBeNil) + }) + }) +} + +func TestEncodeDeviceTopic(t *testing.T) { + Convey("Given the appEui 0202, devEui BB and type Downlink", t, func() { + tp := &DeviceTopic{ + AppEui: "0202", + DevEui: "BB", + Type: Downlink, + } + + Convey("Then the topic is 0202/devices/BB/down", func() { + topic, err := tp.Encode() + So(err, ShouldBeNil) + So(topic, ShouldEqual, "0202/devices/BB/down") + }) + }) +} + +func TestRoundtrip(t *testing.T) { + Convey("Given the input 0303/devices/CC/activations", t, func() { + input := "0303/devices/CC/activations" + + Convey("Then the encoded output is the same as the decoded input", func() { + tp, err := DecodeDeviceTopic(input) + So(err, ShouldBeNil) + + output, err := tp.Encode() + So(err, ShouldBeNil) + So(output, ShouldEqual, input) + }) + }) +} From 28f801f3e1c31cfcf83025dda47642e58759ff40 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 11 Feb 2016 16:07:38 +0100 Subject: [PATCH 0544/2266] Consistent naming, see #36 --- api/topics.go | 6 +++--- api/topics_test.go | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/topics.go b/api/topics.go index ea8158e83..b22f36e20 100644 --- a/api/topics.go +++ b/api/topics.go @@ -31,8 +31,8 @@ const ( // DeviceTopic represents a publish/subscribe topic for application devices type DeviceTopic struct { - AppEui string - DevEui string + AppEUI string + DevEUI string Type DeviceTopicType } @@ -60,5 +60,5 @@ func DecodeDeviceTopic(topic string) (*DeviceTopic, error) { // Encode encodes the DeviceTopic to a topic func (t *DeviceTopic) Encode() (string, error) { - return fmt.Sprintf("%s/%s/%s/%s", t.AppEui, Devices, t.DevEui, t.Type), nil + return fmt.Sprintf("%s/%s/%s/%s", t.AppEUI, Devices, t.DevEUI, t.Type), nil } diff --git a/api/topics_test.go b/api/topics_test.go index 4118bc204..19cf088cd 100644 --- a/api/topics_test.go +++ b/api/topics_test.go @@ -51,8 +51,8 @@ func TestDecodeDeviceTopic(t *testing.T) { So(err, ShouldBeNil) Convey("And appEui is 0101, devEui is AA and type is Uplink", func() { - So(tp.AppEui, ShouldEqual, "0101") - So(tp.DevEui, ShouldEqual, "AA") + So(tp.AppEUI, ShouldEqual, "0101") + So(tp.DevEUI, ShouldEqual, "AA") So(tp.Type, ShouldEqual, Uplink) }) }) @@ -78,8 +78,8 @@ func TestDecodeDeviceTopic(t *testing.T) { func TestEncodeDeviceTopic(t *testing.T) { Convey("Given the appEui 0202, devEui BB and type Downlink", t, func() { tp := &DeviceTopic{ - AppEui: "0202", - DevEui: "BB", + AppEUI: "0202", + DevEUI: "BB", Type: Downlink, } From 176b5b9413e4db502b9efdf3d25009ec8ece9ff2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 11 Feb 2016 17:48:21 +0100 Subject: [PATCH 0545/2266] Add doc --- doc.go | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc.go diff --git a/doc.go b/doc.go new file mode 100644 index 000000000..6dc494d75 --- /dev/null +++ b/doc.go @@ -0,0 +1,4 @@ +/* +Package ttn contains the backend server systems for The Things Network. +*/ +package ttn From 84fd16cad782af228aaf23b43ef3157df49901db Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 12 Feb 2016 11:19:25 +0100 Subject: [PATCH 0546/2266] Update README with info about binaries and Docker Also added download links for CI-built binaries [skip CI] --- README.md | 12 ++++-- integration/broker/DOWNLOADS.md | 67 +++++++++++++++++++++++++++++++++ integration/router/DOWNLOADS.md | 67 +++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 integration/broker/DOWNLOADS.md create mode 100644 integration/router/DOWNLOADS.md diff --git a/README.md b/README.md index 81c666c0e..ca26d3dad 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,9 @@ The Things Network is a global open crowdsourced Internet of Things data network This repository **will become** version 1.0 of The Things Network. It is **under heavy development** and currently it's APIs and code are not yet stable (pre 1.0). -## Getting Started +## Getting Started With The Things Network -For the time being this repository is **development-only**. We are working hard on getting the network components ready and welcome all the help we can get. If you want to participate in our community, you should: +When you get started with The Things Network, you'll probably have some questions. Here are some things you can do to find the answer to them: - Register on the [forum](http://forum.thethingsnetwork.org) - Join [Slack](https://slack.thethingsnetwork.org) @@ -21,7 +21,13 @@ For the time being this repository is **development-only**. We are working hard - Read background information on the [wiki](http://thethingsnetwork.org/wiki) - Send an email to @johanstokking (johan@thethingsnetwork.org) -To get started with LoRaWAN and The Things Network, you can also have a look at a *demonstration version* of the network, which is documented on the [wiki](http://thethingsnetwork.org/wiki/). Development of the demonstration version is done in the [croft](https://github.com/TheThingsNetwork/croft) and [jolie](https://github.com/TheThingsNetwork/jolie) repositories. The demonstration version will soon be deprecated, but a compatible application will be built on top of the new infrastructure that is being built in this repository. +## Running the Network + +We are working hard on building a working version of the v1.0 backend. Currently, we have the following components working: + +- [x] **Router**: [[download](integration/router/DOWNLOADS.md)] [[docker](https://hub.docker.com/r/thethingsnetwork/router/)] +- [x] **Broker**: [[download](integration/broker/DOWNLOADS.md)] [[docker](https://hub.docker.com/r/thethingsnetwork/broker/)] +- [ ] **Handler**: *work in progress* ## Contributing diff --git a/integration/broker/DOWNLOADS.md b/integration/broker/DOWNLOADS.md new file mode 100644 index 000000000..3bb707781 --- /dev/null +++ b/integration/broker/DOWNLOADS.md @@ -0,0 +1,67 @@ +# The Things Network Broker + +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) + +![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) + +The Things Network is a global open crowdsourced Internet of Things data network. + +## Download Links +| OS-arch | master | develop | +| ------- | ------ | ------- | +| darwin-386 | [zip][dd32z] [tar.gz][dd32g] [tar.xz][dd32x] | [zip][md32z] [tar.gz][md32g] [tar.xz][md32x] | +| darwin-amd64 | [zip][dd64z] [tar.gz][dd64g] [tar.xz][dd64x] | [zip][md64z] [tar.gz][md64g] [tar.xz][md64x] | +| linux-386 | [zip][dl32z] [tar.gz][dl32g] [tar.xz][dl32x] | [zip][ml32z] [tar.gz][ml32g] [tar.xz][ml32x] | +| linux-amd64 | [zip][dl64z] [tar.gz][dl64g] [tar.xz][dl64x] | [zip][ml64z] [tar.gz][ml64g] [tar.xz][ml64x] | +| linux-arm | [zip][dlarmz] [tar.gz][dlarmg] [tar.xz][dlarmx] | [zip][mlarmz] [tar.gz][mlarmg] [tar.xz][mlarmx] | +| windows-386 | [zip][dw32z] [tar.gz][dw32g] [tar.xz][dw32x] | [zip][mw32z] [tar.gz][mw32g] [tar.xz][mw32x] | +| windows-amd64 | [zip][dw64z] [tar.gz][dw64g] [tar.xz][dw64x] | [zip][mw64z] [tar.gz][mw64g] [tar.xz][mw64x] | + +[dd32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.zip +[dd32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.tar.gz +[dd32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.tar.xz +[md32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.zip +[md32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.tar.gz +[md32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.tar.xz + +[dd64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.zip +[dd64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.tar.gz +[dd64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.tar.xz +[md64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.zip +[md64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.tar.gz +[md64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.tar.xz + +[dl32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.zip +[dl32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.tar.gz +[dl32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.tar.xz +[ml32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.zip +[ml32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.tar.gz +[ml32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.tar.xz + +[dl64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.zip +[dl64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.gz +[dl64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.xz +[ml64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.zip +[ml64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.tar.gz +[ml64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.tar.xz + +[dlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.zip +[dlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.tar.gz +[dlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.tar.xz +[mlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.zip +[mlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.tar.gz +[mlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.tar.xz + +[dw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.zip +[dw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.tar.gz +[dw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.tar.xz +[mw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.zip +[mw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.tar.gz +[mw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.tar.xz + +[dw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.zip +[dw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.tar.gz +[dw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.tar.xz +[mw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.zip +[mw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.tar.gz +[mw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.tar.xz diff --git a/integration/router/DOWNLOADS.md b/integration/router/DOWNLOADS.md new file mode 100644 index 000000000..3fc42537e --- /dev/null +++ b/integration/router/DOWNLOADS.md @@ -0,0 +1,67 @@ +# The Things Network Router + +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) + +![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) + +The Things Network is a global open crowdsourced Internet of Things data network. + +## Download Links +| OS-arch | master | develop | +| ------- | ------ | ------- | +| darwin-386 | [zip][dd32z] [tar.gz][dd32g] [tar.xz][dd32x] | [zip][md32z] [tar.gz][md32g] [tar.xz][md32x] | +| darwin-amd64 | [zip][dd64z] [tar.gz][dd64g] [tar.xz][dd64x] | [zip][md64z] [tar.gz][md64g] [tar.xz][md64x] | +| linux-386 | [zip][dl32z] [tar.gz][dl32g] [tar.xz][dl32x] | [zip][ml32z] [tar.gz][ml32g] [tar.xz][ml32x] | +| linux-amd64 | [zip][dl64z] [tar.gz][dl64g] [tar.xz][dl64x] | [zip][ml64z] [tar.gz][ml64g] [tar.xz][ml64x] | +| linux-arm | [zip][dlarmz] [tar.gz][dlarmg] [tar.xz][dlarmx] | [zip][mlarmz] [tar.gz][mlarmg] [tar.xz][mlarmx] | +| windows-386 | [zip][dw32z] [tar.gz][dw32g] [tar.xz][dw32x] | [zip][mw32z] [tar.gz][mw32g] [tar.xz][mw32x] | +| windows-amd64 | [zip][dw64z] [tar.gz][dw64g] [tar.xz][dw64x] | [zip][mw64z] [tar.gz][mw64g] [tar.xz][mw64x] | + +[dd32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.zip +[dd32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.tar.gz +[dd32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.tar.xz +[md32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.zip +[md32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.tar.gz +[md32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.tar.xz + +[dd64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.zip +[dd64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.tar.gz +[dd64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.tar.xz +[md64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.zip +[md64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.tar.gz +[md64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.tar.xz + +[dl32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.zip +[dl32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.tar.gz +[dl32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.tar.xz +[ml32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.zip +[ml32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.tar.gz +[ml32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.tar.xz + +[dl64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.zip +[dl64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.gz +[dl64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.xz +[ml64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.zip +[ml64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.tar.gz +[ml64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.tar.xz + +[dlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.zip +[dlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.tar.gz +[dlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.tar.xz +[mlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.zip +[mlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.tar.gz +[mlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.tar.xz + +[dw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.zip +[dw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.tar.gz +[dw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.tar.xz +[mw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.zip +[mw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.tar.gz +[mw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.tar.xz + +[dw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.zip +[dw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.tar.gz +[dw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.tar.xz +[mw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.zip +[mw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.tar.gz +[mw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.tar.xz From 9c8c9cd141a829a86ea96390148d477d2d74afbf Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 15 Feb 2016 11:19:13 +0100 Subject: [PATCH 0547/2266] [tests.router] Remove typo from doc comments --- core/adapters/http/pubsub/pubsub.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go index a6535afea..316256cb6 100644 --- a/core/adapters/http/pubsub/pubsub.go +++ b/core/adapters/http/pubsub/pubsub.go @@ -42,7 +42,7 @@ func NewAdapter(adapter *httpadapter.Adapter, parser parser.RegistrationParser, registrations: make(chan regReq), } - // So far we only supports one endpoint [PUT] /end-device/:devAddr + // So far we only supports one endpoint [PUT] /end-devices/:devAddr a.RegisterEndpoint("/end-devices/", a.handlePutEndDevice) return a, nil @@ -54,7 +54,7 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) return request.Registration, regAckNacker{response: request.response}, nil } -// handle request [PUT] on /end-device/:devAddr +// handle request [PUT] on /end-devices/:devAddr func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { ctx := a.ctx.WithField("sender", req.RemoteAddr) ctx.Debug("Receiving new registration request") From 33f3b85303218f6146c9210f6e7249ae4c35baa5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 15:01:22 +0100 Subject: [PATCH 0548/2266] [tests.router] Write shape for router storage tests --- core/components/router_storage_test.go | 98 ++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 core/components/router_storage_test.go diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go new file mode 100644 index 000000000..cb9b88e63 --- /dev/null +++ b/core/components/router_storage_test.go @@ -0,0 +1,98 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +type routerEntryShape struct { + Until time.Time + DevAddr lorawan.DevAddr + Address string +} + +func TestStorageExpiration(t *testing.T) { + tests := []struct { + Desc string + ExistingEntries []routerEntryShape + Store *routerEntryShape + Lookup lorawan.DevAddr + WantEntry *routerEntryShape + WantError []error + }{ + { + Desc: "No entry, Lookup address", + ExistingEntries: nil, + Store: nil, + Lookup: lorawan.DevAddr([4]byte{0, 0, 0, 1}), + WantEntry: nil, + WantError: []error{ErrNotFound}, + }, + } + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + db := genFilledRouterStorage(test.ExistingEntries) + cherr := make(chan interface{}, 2) + + // Operate + storeRouter(db, test.Store, cherr) + got := lookupRouter(db, test.Lookup, cherr) + + // Check + checkChErrors(t, test.WantError, cherr) + checkRouterEntries(t, test.WantEntry, got) + } +} + +// ----- BUILD utilities +func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { + db, err := NewRouterStorage() + if err != nil { + panic(err) + } + + if err := db.Reset(); err != nil { + panic(err) + } + + for i, shape := range setup { + entry := routerEntry{ + until: shape.Until, + Recipients: []core.Recipient{ + { + Address: shape.Address, + Id: i, + }, + }, + } + if err := db.Store(shape.DevAddr, entry); err != nil { + panic(err) + } + } + + return db +} + +// ----- OPERATE utilities +func storeRouter(db RouterStorage, entry *routerEntryShape, cherr chan interface{}) { + +} + +func lookupRouter(db RouterStorage, devAddr lorawan.DevAddr, cherr chan interface{}) routerEntry { + return routerEntry{} +} + +// ----- CHECK utilities +func checkRouterEntries(t *testing.T, want *routerEntryShape, got routerEntry) { +} From 19354185d5fdf5d84d474738dc2fdaa5fee5c48f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 15:23:18 +0100 Subject: [PATCH 0549/2266] [tests.router] Change router entry recipients to a single recipient --- core/components/router.go | 6 ++--- core/components/router_storage.go | 33 ++++++++++++-------------- core/components/router_storage_test.go | 8 +++---- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/core/components/router.go b/core/components/router.go index 4b2f64656..219d29ec5 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -30,9 +30,7 @@ func NewRouter(db RouterStorage, ctx log.Interface) *Router { // Register implements the core.Component interface func (r *Router) Register(reg core.Registration, an core.AckNacker) error { - entry := routerEntry{ - Recipients: []core.Recipient{reg.Recipient}, - } + entry := routerEntry{Recipient: reg.Recipient} if err := r.db.Store(reg.DevAddr, entry); err != nil { an.Nack() return err @@ -60,7 +58,7 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt return err } - response, err := upAdapter.Send(p, entries.Recipients...) + response, err := upAdapter.Send(p, entries.Recipient) if err != nil { an.Nack() return err diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 806fde222..541513c63 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -33,8 +33,8 @@ type routerBoltStorage struct { // routerEntry stores all information that link a device to a broker type routerEntry struct { - Recipients []core.Recipient // Recipients associated to a device. //NOTE why not only one ? - until time.Time // The moment until when the entry is still valid + Recipient core.Recipient // Recipient associated to a device. + until time.Time // The moment until when the entry is still valid } // NewRouterStorage creates a new router bolt in-memory storage @@ -94,18 +94,16 @@ func (s routerBoltStorage) Reset() error { // MarshalBinary implements the entryStorage interface func (entry routerEntry) MarshalBinary() ([]byte, error) { - w := newEntryReadWriter(nil) - w.DirectWrite(uint8(len(entry.Recipients))) - for _, r := range entry.Recipients { - rawId := []byte(r.Id.(string)) - rawAddress := []byte(r.Address.(string)) - w.Write(rawId) - w.Write(rawAddress) - } rawTime, err := entry.until.MarshalBinary() if err != nil { return nil, err } + rawId := []byte(entry.Recipient.Id.(string)) + rawAddress := []byte(entry.Recipient.Address.(string)) + + w := newEntryReadWriter(nil) + w.Write(rawId) + w.Write(rawAddress) w.Write(rawTime) return w.Bytes() } @@ -116,14 +114,13 @@ func (entry *routerEntry) UnmarshalBinary(data []byte) error { return ErrNotUnmarshable } r := newEntryReadWriter(data[0:]) - for i := 0; i < int(data[0]); i += 1 { - var id, address string - r.Read(func(data []byte) { id = string(data) }) - r.Read(func(data []byte) { address = string(address) }) - entry.Recipients = append(entry.Recipients, core.Recipient{ - Id: id, - Address: address, - }) + + var id, address string + r.Read(func(data []byte) { id = string(data) }) + r.Read(func(data []byte) { address = string(address) }) + entry.Recipient = core.Recipient{ + Id: id, + Address: address, } var err error r.Read(func(data []byte) { diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index cb9b88e63..386041c9f 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -69,11 +69,9 @@ func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { for i, shape := range setup { entry := routerEntry{ until: shape.Until, - Recipients: []core.Recipient{ - { - Address: shape.Address, - Id: i, - }, + Recipient: core.Recipient{ + Address: shape.Address, + Id: i, }, } if err := db.Store(shape.DevAddr, entry); err != nil { From 32f1ad438c281fbd628b739681921588ad35e303 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 15:30:26 +0100 Subject: [PATCH 0550/2266] [tests.router] Make storing of an existing entry trigger an error for the router --- core/components/router_storage.go | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 541513c63..ed774fb01 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -53,31 +53,28 @@ func NewRouterStorage() (RouterStorage, error) { // Lookup implements the RouterStorage interface func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { - entries, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) + entry, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) if err != nil { return routerEntry{}, err } - routerEntries := entries.([]routerEntry) + rentry := entry.(routerEntry) - if len(routerEntries) != 1 { - if err := flush(s.DB, "brokers", devAddr); err != nil { - return routerEntry{}, err - } - return routerEntry{}, ErrNotFound - } - - if s.expiryDelay != 0 && routerEntries[0].until.Before(time.Now()) { + if s.expiryDelay != 0 && rentry.until.Before(time.Now()) { if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } return routerEntry{}, ErrEntryExpired } - return routerEntries[0], nil + return rentry, nil } // Store implements the RouterStorage interface func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { + _, err := s.Lookup(devAddr) + if err != ErrNotFound && err != ErrEntryExpired { + return ErrAlreadyExists + } entry.until = time.Now().Add(s.expiryDelay) return store(s.DB, "brokers", devAddr, &entry) } From 86a7b942bf4f26e1f3aa09aa71bf8d17d9871880 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 16:15:36 +0100 Subject: [PATCH 0551/2266] [tests.router] Implements test utilities for router storage testing --- core/components/router_storage_test.go | 50 ++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index 386041c9f..c611224be 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -4,6 +4,7 @@ package components import ( + "reflect" "testing" "time" @@ -13,7 +14,6 @@ import ( ) type routerEntryShape struct { - Until time.Time DevAddr lorawan.DevAddr Address string } @@ -22,6 +22,7 @@ func TestStorageExpiration(t *testing.T) { tests := []struct { Desc string ExistingEntries []routerEntryShape + ExpiryDelay time.Duration Store *routerEntryShape Lookup lorawan.DevAddr WantEntry *routerEntryShape @@ -30,6 +31,7 @@ func TestStorageExpiration(t *testing.T) { { Desc: "No entry, Lookup address", ExistingEntries: nil, + ExpiryDelay: time.Minute, Store: nil, Lookup: lorawan.DevAddr([4]byte{0, 0, 0, 1}), WantEntry: nil, @@ -44,6 +46,10 @@ func TestStorageExpiration(t *testing.T) { // Build db := genFilledRouterStorage(test.ExistingEntries) cherr := make(chan interface{}, 2) + go func() { + time.After(time.Millisecond * 250) + close(cherr) + }() // Operate storeRouter(db, test.Store, cherr) @@ -52,6 +58,9 @@ func TestStorageExpiration(t *testing.T) { // Check checkChErrors(t, test.WantError, cherr) checkRouterEntries(t, test.WantEntry, got) + + // Clean + db.Close() } } @@ -68,7 +77,6 @@ func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { for i, shape := range setup { entry := routerEntry{ - until: shape.Until, Recipient: core.Recipient{ Address: shape.Address, Id: i, @@ -84,13 +92,49 @@ func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { // ----- OPERATE utilities func storeRouter(db RouterStorage, entry *routerEntryShape, cherr chan interface{}) { + if entry == nil { + return + } + + err := db.Store(entry.DevAddr, routerEntry{ + Recipient: core.Recipient{ + Address: entry.Address, + Id: "LikeICare", + }, + }) + if err != nil { + cherr <- err + } } func lookupRouter(db RouterStorage, devAddr lorawan.DevAddr, cherr chan interface{}) routerEntry { - return routerEntry{} + entry, err := db.Lookup(devAddr) + if err != nil { + cherr <- err + } + return entry } // ----- CHECK utilities func checkRouterEntries(t *testing.T, want *routerEntryShape, got routerEntry) { + if want != nil { + addr, ok := got.Recipient.Address.(string) + + if !ok { + Ko(t, "Unexpected recipient address format: %+v", got.Recipient.Address) + return + } + + if addr != want.Address { + Ko(t, "The retrieved address [%s] does not match expected [%s]", addr, want.Address) + return + } + } else { + if !reflect.DeepEqual(routerEntry{}, got) { + Ko(t, "No entry was exected but got: %v", got) + return + } + } + Ok(t, "Check router entries") } From c4d894db4accfeb9d19f35929738dd60eb1af39a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 16:36:53 +0100 Subject: [PATCH 0552/2266] [tests.router] Review test cases writting --- core/components/router_storage_test.go | 48 ++++++++++++++------------ 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index c611224be..eebafa2f7 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -14,29 +14,46 @@ import ( ) type routerEntryShape struct { + routerEntry DevAddr lorawan.DevAddr - Address string } func TestStorageExpiration(t *testing.T) { + devices := []lorawan.DevAddr{ + lorawan.DevAddr([4]byte{0, 0, 0, 1}), + } + + entries := []routerEntry{ + {Recipient: core.Recipient{Address: "MyAddress1", Id: ""}}, + } + tests := []struct { Desc string ExistingEntries []routerEntryShape ExpiryDelay time.Duration Store *routerEntryShape Lookup lorawan.DevAddr - WantEntry *routerEntryShape + WantEntry *routerEntry WantError []error }{ { Desc: "No entry, Lookup address", ExistingEntries: nil, ExpiryDelay: time.Minute, + Lookup: devices[0], Store: nil, - Lookup: lorawan.DevAddr([4]byte{0, 0, 0, 1}), WantEntry: nil, WantError: []error{ErrNotFound}, }, + { + Desc: "No entry, Store and Lookup same", + ExistingEntries: nil, + ExpiryDelay: time.Minute, + Store: &routerEntryShape{entries[0], devices[0]}, + Lookup: devices[0], + WantEntry: &entries[0], + WantError: nil, + }, } for _, test := range tests { @@ -75,14 +92,8 @@ func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { panic(err) } - for i, shape := range setup { - entry := routerEntry{ - Recipient: core.Recipient{ - Address: shape.Address, - Id: i, - }, - } - if err := db.Store(shape.DevAddr, entry); err != nil { + for _, shape := range setup { + if err := db.Store(shape.DevAddr, shape.routerEntry); err != nil { panic(err) } } @@ -96,14 +107,7 @@ func storeRouter(db RouterStorage, entry *routerEntryShape, cherr chan interface return } - err := db.Store(entry.DevAddr, routerEntry{ - Recipient: core.Recipient{ - Address: entry.Address, - Id: "LikeICare", - }, - }) - - if err != nil { + if err := db.Store(entry.DevAddr, entry.routerEntry); err != nil { cherr <- err } } @@ -117,7 +121,7 @@ func lookupRouter(db RouterStorage, devAddr lorawan.DevAddr, cherr chan interfac } // ----- CHECK utilities -func checkRouterEntries(t *testing.T, want *routerEntryShape, got routerEntry) { +func checkRouterEntries(t *testing.T, want *routerEntry, got routerEntry) { if want != nil { addr, ok := got.Recipient.Address.(string) @@ -126,8 +130,8 @@ func checkRouterEntries(t *testing.T, want *routerEntryShape, got routerEntry) { return } - if addr != want.Address { - Ko(t, "The retrieved address [%s] does not match expected [%s]", addr, want.Address) + if addr != want.Recipient.Address.(string) { + Ko(t, "The retrieved address [%s] does not match expected [%s]", addr, want.Recipient.Address) return } } else { From c232d3ec98795fc735384065c39f2fe7b8bcbb28 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 16:41:52 +0100 Subject: [PATCH 0553/2266] [tests.router] Fix entries access from router lookup --- core/components/router_storage.go | 11 ++++++++++- core/components/storage.go | 3 ++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index ed774fb01..07f090dca 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -57,7 +57,16 @@ func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) if err != nil { return routerEntry{}, err } - rentry := entry.(routerEntry) + entries := entry.([]routerEntry) + + if len(entries) != 1 { + if err := flush(s.DB, "brokers", devAddr); err != nil { + return routerEntry{}, err + } + return routerEntry{}, ErrNotFound + } + + rentry := entries[0] if s.expiryDelay != 0 && rentry.until.Before(time.Now()) { if err := flush(s.DB, "brokers", devAddr); err != nil { diff --git a/core/components/storage.go b/core/components/storage.go index 98ac2db8d..5ca0a112a 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -26,7 +26,8 @@ func initDB(db *bolt.DB, bucketName string) error { }) } -// store put a new entry in the given bolt database. +// store put a new entry in the given bolt database. It adds the entry to an existing set or create +// a new set containing one element. func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { From e0a97f035c04ede743a12734d2254f2e9450ab42 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 16:50:29 +0100 Subject: [PATCH 0554/2266] [tests.router] Use quotes instead of brackets in error message --- core/components/router_storage_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index eebafa2f7..9ae391e17 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -131,7 +131,7 @@ func checkRouterEntries(t *testing.T, want *routerEntry, got routerEntry) { } if addr != want.Recipient.Address.(string) { - Ko(t, "The retrieved address [%s] does not match expected [%s]", addr, want.Recipient.Address) + Ko(t, `The retrieved address "%s" does not match expected "%s"`, addr, want.Recipient.Address) return } } else { From f0325a86194bc7e14bd535ac3eca34d26171a2d0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 16:51:04 +0100 Subject: [PATCH 0555/2266] [tests.router] Fix oversight and typo in routerentry unmarshalling --- core/components/router_storage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 07f090dca..afc818fe6 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -119,11 +119,11 @@ func (entry *routerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 1 { return ErrNotUnmarshable } - r := newEntryReadWriter(data[0:]) + r := newEntryReadWriter(data) var id, address string r.Read(func(data []byte) { id = string(data) }) - r.Read(func(data []byte) { address = string(address) }) + r.Read(func(data []byte) { address = string(data) }) entry.Recipient = core.Recipient{ Id: id, Address: address, From 0e5c1d6fe1cec1861f102123160d543bc8936e5e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 16:55:24 +0100 Subject: [PATCH 0556/2266] [tests.router] Add delay between store and lookup --- core/components/router_storage_test.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index 9ae391e17..3a9596707 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -29,26 +29,29 @@ func TestStorageExpiration(t *testing.T) { tests := []struct { Desc string - ExistingEntries []routerEntryShape ExpiryDelay time.Duration + ExistingEntries []routerEntryShape Store *routerEntryShape + WaitDelay time.Duration Lookup lorawan.DevAddr WantEntry *routerEntry WantError []error }{ { Desc: "No entry, Lookup address", - ExistingEntries: nil, ExpiryDelay: time.Minute, + ExistingEntries: nil, Lookup: devices[0], + WaitDelay: 0, Store: nil, WantEntry: nil, WantError: []error{ErrNotFound}, }, { Desc: "No entry, Store and Lookup same", - ExistingEntries: nil, ExpiryDelay: time.Minute, + ExistingEntries: nil, + WaitDelay: 0, Store: &routerEntryShape{entries[0], devices[0]}, Lookup: devices[0], WantEntry: &entries[0], @@ -70,7 +73,7 @@ func TestStorageExpiration(t *testing.T) { // Operate storeRouter(db, test.Store, cherr) - got := lookupRouter(db, test.Lookup, cherr) + got := lookupRouter(db, test.WaitDelay, test.Lookup, cherr) // Check checkChErrors(t, test.WantError, cherr) @@ -112,7 +115,10 @@ func storeRouter(db RouterStorage, entry *routerEntryShape, cherr chan interface } } -func lookupRouter(db RouterStorage, devAddr lorawan.DevAddr, cherr chan interface{}) routerEntry { +func lookupRouter(db RouterStorage, delay time.Duration, devAddr lorawan.DevAddr, cherr chan interface{}) routerEntry { + if delay != 0 { + <-time.After(delay) + } entry, err := db.Lookup(devAddr) if err != nil { cherr <- err From ff2583fdb51ec459e5d2dab9e6712ebfac3f08a9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 17:06:45 +0100 Subject: [PATCH 0557/2266] [tests.router] Re-introduce expiry delay for router storage --- core/components/router.go | 5 ----- core/components/router_storage.go | 6 ++++-- integration/router/main.go | 3 ++- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/core/components/router.go b/core/components/router.go index 219d29ec5..be9ef5bdf 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -5,16 +5,11 @@ package components import ( "fmt" - "time" "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" ) -const ( - EXPIRY_DELAY = time.Hour * 8 // Life-time of an entry in the storage -) - type Router struct { db RouterStorage // Local storage that maps end-device addresses to broker addresses ctx log.Interface // Just a logger diff --git a/core/components/router_storage.go b/core/components/router_storage.go index afc818fe6..62920a922 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -4,6 +4,7 @@ package components import ( + "fmt" "time" "github.com/TheThingsNetwork/ttn/core" @@ -38,7 +39,7 @@ type routerEntry struct { } // NewRouterStorage creates a new router bolt in-memory storage -func NewRouterStorage() (RouterStorage, error) { +func NewRouterStorage(delay time.Duration) (RouterStorage, error) { db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { return nil, err @@ -48,7 +49,7 @@ func NewRouterStorage() (RouterStorage, error) { return nil, err } - return &routerBoltStorage{DB: db}, nil + return &routerBoltStorage{DB: db, expiryDelay: delay}, nil } // Lookup implements the RouterStorage interface @@ -68,6 +69,7 @@ func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) rentry := entries[0] + fmt.Println(s.expiryDelay, rentry.until.Before(time.Now()), rentry) if s.expiryDelay != 0 && rentry.until.Before(time.Now()) { if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err diff --git a/integration/router/main.go b/integration/router/main.go index 917d2f44c..6dff3aa21 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -8,6 +8,7 @@ import ( "os" "strconv" "strings" + "time" . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" @@ -46,7 +47,7 @@ func main() { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - db, err := components.NewRouterStorage() + db, err := components.NewRouterStorage(time.Hour * 8) if err != nil { ctx.WithError(err).Fatal("Could not create a local storage") } From c660f86dba054bd6b948c3b758fa12e5c87d3ad9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 17:54:59 +0100 Subject: [PATCH 0558/2266] [tests.router] Complete test suite --- core/components/router_storage_test.go | 95 ++++++++++++++++++++++---- 1 file changed, 82 insertions(+), 13 deletions(-) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index 3a9596707..2b7cdf190 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -21,18 +21,21 @@ type routerEntryShape struct { func TestStorageExpiration(t *testing.T) { devices := []lorawan.DevAddr{ lorawan.DevAddr([4]byte{0, 0, 0, 1}), + lorawan.DevAddr([4]byte{14, 15, 8, 42}), } entries := []routerEntry{ {Recipient: core.Recipient{Address: "MyAddress1", Id: ""}}, + {Recipient: core.Recipient{Address: "AnotherAddress", Id: ""}}, } tests := []struct { Desc string ExpiryDelay time.Duration ExistingEntries []routerEntryShape + WaitDelayS time.Duration Store *routerEntryShape - WaitDelay time.Duration + WaitDelayL time.Duration Lookup lorawan.DevAddr WantEntry *routerEntry WantError []error @@ -42,7 +45,6 @@ func TestStorageExpiration(t *testing.T) { ExpiryDelay: time.Minute, ExistingEntries: nil, Lookup: devices[0], - WaitDelay: 0, Store: nil, WantEntry: nil, WantError: []error{ErrNotFound}, @@ -51,12 +53,77 @@ func TestStorageExpiration(t *testing.T) { Desc: "No entry, Store and Lookup same", ExpiryDelay: time.Minute, ExistingEntries: nil, - WaitDelay: 0, Store: &routerEntryShape{entries[0], devices[0]}, Lookup: devices[0], WantEntry: &entries[0], WantError: nil, }, + { + Desc: "No entry, store, wait expiry, and lookup same", + ExpiryDelay: time.Millisecond, + ExistingEntries: nil, + Store: &routerEntryShape{entries[0], devices[0]}, + WaitDelayL: time.Millisecond * 250, + Lookup: devices[0], + WantEntry: nil, + WantError: []error{ErrEntryExpired}, + }, + { + Desc: "One entry, store same, lookup same", + ExpiryDelay: time.Minute, + ExistingEntries: []routerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &routerEntryShape{entries[1], devices[0]}, + Lookup: devices[0], + WantEntry: &entries[0], + WantError: []error{ErrAlreadyExists}, + }, + { + Desc: "One entry, store different, lookup newly stored", + ExpiryDelay: time.Minute, + ExistingEntries: []routerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &routerEntryShape{entries[1], devices[1]}, + Lookup: devices[1], + WantEntry: &entries[1], + WantError: nil, + }, + { + Desc: "One entry, store different, lookup first one", + ExpiryDelay: time.Minute, + ExistingEntries: []routerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &routerEntryShape{entries[1], devices[1]}, + Lookup: devices[0], + WantEntry: &entries[0], + }, + { + Desc: "One entry, store different, wait delay, lookup first one", + ExpiryDelay: time.Millisecond, + ExistingEntries: []routerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &routerEntryShape{entries[1], devices[1]}, + WaitDelayL: time.Millisecond, + Lookup: devices[0], + WantEntry: nil, + WantError: []error{ErrEntryExpired}, + }, + { + Desc: "One entry, wait delay, store same, lookup same", + ExpiryDelay: time.Millisecond * 100, + ExistingEntries: []routerEntryShape{ + {entries[0], devices[0]}, + }, + WaitDelayS: time.Millisecond * 200, + Store: &routerEntryShape{entries[1], devices[0]}, + Lookup: devices[0], + WantEntry: &entries[1], + WantError: nil, + }, } for _, test := range tests { @@ -64,18 +131,17 @@ func TestStorageExpiration(t *testing.T) { Desc(t, test.Desc) // Build - db := genFilledRouterStorage(test.ExistingEntries) + db := genFilledRouterStorage(test.ExistingEntries, test.ExpiryDelay) cherr := make(chan interface{}, 2) + // Operate + storeRouter(db, test.WaitDelayS, test.Store, cherr) + got := lookupRouter(db, test.WaitDelayL, test.Lookup, cherr) + + // Check go func() { time.After(time.Millisecond * 250) close(cherr) }() - - // Operate - storeRouter(db, test.Store, cherr) - got := lookupRouter(db, test.WaitDelay, test.Lookup, cherr) - - // Check checkChErrors(t, test.WantError, cherr) checkRouterEntries(t, test.WantEntry, got) @@ -85,8 +151,8 @@ func TestStorageExpiration(t *testing.T) { } // ----- BUILD utilities -func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { - db, err := NewRouterStorage() +func genFilledRouterStorage(setup []routerEntryShape, expiryDelay time.Duration) RouterStorage { + db, err := NewRouterStorage(expiryDelay) if err != nil { panic(err) } @@ -105,7 +171,10 @@ func genFilledRouterStorage(setup []routerEntryShape) RouterStorage { } // ----- OPERATE utilities -func storeRouter(db RouterStorage, entry *routerEntryShape, cherr chan interface{}) { +func storeRouter(db RouterStorage, delay time.Duration, entry *routerEntryShape, cherr chan interface{}) { + if delay != 0 { + <-time.After(delay) + } if entry == nil { return } From cbc596bada619434861f3fdd067f5b9047278b31 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 18:00:30 +0100 Subject: [PATCH 0559/2266] [tests.router] Make tests pass + protect operation from concurrent accesses --- core/components/router_storage.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 62920a922..95e09b47b 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -4,7 +4,7 @@ package components import ( - "fmt" + "sync" "time" "github.com/TheThingsNetwork/ttn/core" @@ -29,6 +29,7 @@ type RouterStorage interface { type routerBoltStorage struct { *bolt.DB + sync.Mutex // Guards the db storage to make Lookup and Store atomic actions expiryDelay time.Duration // Entry lifetime delay } @@ -54,6 +55,17 @@ func NewRouterStorage(delay time.Duration) (RouterStorage, error) { // Lookup implements the RouterStorage interface func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { + return s.lookup(devAddr, true) +} + +// lookup offers an indirection in order to avoid taking a lock if not needed +func (s routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEntry, error) { + // NOTE This works under the assumption that a read or write lock is already hold by the callee (e.g. Store) + if lock { + s.Lock() + defer s.Unlock() + } + entry, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) if err != nil { return routerEntry{}, err @@ -69,7 +81,6 @@ func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) rentry := entries[0] - fmt.Println(s.expiryDelay, rentry.until.Before(time.Now()), rentry) if s.expiryDelay != 0 && rentry.until.Before(time.Now()) { if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err @@ -82,7 +93,9 @@ func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) // Store implements the RouterStorage interface func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { - _, err := s.Lookup(devAddr) + s.Lock() + defer s.Unlock() + _, err := s.lookup(devAddr, false) if err != ErrNotFound && err != ErrEntryExpired { return ErrAlreadyExists } @@ -97,6 +110,8 @@ func (s routerBoltStorage) Close() error { // Reset implements the RouterStorage interface func (s routerBoltStorage) Reset() error { + s.Lock() + defer s.Unlock() return resetDB(s.DB, "brokers") } From 4197f5dd0c4256eaf8725b42faae0b6ba5022f58 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 19:24:32 +0100 Subject: [PATCH 0560/2266] [tests.router] Prepare backbone for router test --- core/components/handler_test.go | 55 +++++++++++---------- core/components/router_test.go | 84 ++++++++++++++++++++++++++++++--- 2 files changed, 108 insertions(+), 31 deletions(-) diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 800f7685d..d6855eefc 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -266,35 +266,40 @@ type application struct { func genPacketsFromSchedule(s *[]event) { for i, entry := range *s { - // Build the macPayload - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{DevAddr: entry.Shape.Device.DevAddr} - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: []byte(entry.Shape.Data), - }} - macPayload.FPort = uint8(1) - if err := macPayload.EncryptFRMPayload(entry.Shape.Device.AppSKey); err != nil { - panic(err) - } - - // Build the physicalPayload - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - if err := phyPayload.SetMIC(entry.Shape.Device.NwkSKey); err != nil { - panic(err) - } - entry.Packet = &core.Packet{ - Payload: phyPayload, - Metadata: core.Metadata{}, - } + entry.Packet = new(core.Packet) + *entry.Packet = genPacketFromShape(entry.Shape) (*s)[i] = entry } } +func genPacketFromShape(shape packetShape) core.Packet { + // Build the macPayload + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{DevAddr: shape.Device.DevAddr} + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: []byte(shape.Data), + }} + macPayload.FPort = uint8(1) + if err := macPayload.EncryptFRMPayload(shape.Device.AppSKey); err != nil { + panic(err) + } + + // Build the physicalPayload + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + if err := phyPayload.SetMIC(shape.Device.NwkSKey); err != nil { + panic(err) + } + return core.Packet{ + Payload: phyPayload, + Metadata: core.Metadata{}, + } +} + func genNewHandler(t *testing.T, applications map[lorawan.EUI64]application) *Handler { ctx := GetLogger(t, "Handler") diff --git a/core/components/router_test.go b/core/components/router_test.go index c3f4be3e8..a9f0b5f3c 100644 --- a/core/components/router_test.go +++ b/core/components/router_test.go @@ -5,16 +5,88 @@ package components import ( "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) -func TestNewRouter(t *testing.T) { - t.Skip("TODO") +func TestRouterHandleUp(t *testing.T) { + tests := []struct { + Desc string + Packet packetShape + WantRecipients []core.Recipient + WantError error + }{} + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + router := genNewRouter(t) + packet := genPacketFromShape(test.Packet) + + // Operate + recipients, err := handleRouterUp(router, packet) + + // Check + checkErrors(t, test.WantError, err) + checkRecipients(t, test.WantRecipients, recipients) + } +} + +type routerRecipient struct { + Address string +} + +// ----- BUILD utilities +func genNewRouter(t *testing.T) core.Router { + ctx := GetLogger(t, "Router") + + db, err := NewRouterStorage(time.Hour * 8) + if err != nil { + panic(err) + } + + if err := db.Reset(); err != nil { + panic(err) + } + + router := NewRouter(db, ctx) + if err != nil { + panic(err) + } + + return router +} + +// ----- OPERATE utilities +func handleRouterUp(router core.Router, packet core.Packet) ([]core.Recipient, error) { + an := voidAckNacker{} + adapter := routerAdapter{} + err := router.HandleUp(packet, an, adapter) + return adapter.Recipients, err +} + +type routerAdapter struct { + Recipients []core.Recipient } -func TestSendRouter(t *testing.T) { - t.Skip("TODO") +func (a routerAdapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { + a.Recipients = r + return core.Packet{}, nil } -func TestHandleUpRouter(t *testing.T) { - t.Skip("TODO") +func (a routerAdapter) Next() (core.Packet, core.AckNacker, error) { + panic("Unexpected call to Next()") +} + +func (a routerAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { + panic("Unexpected call to NextRegistration") +} + +// ----- Check utilities +func checkRecipients(t *testing.T, want []core.Recipient, got []core.Recipient) { + } From 1da8b164dc11082c1c93c1db3c3659501253f693 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 20:04:32 +0100 Subject: [PATCH 0561/2266] [tests.router] Write down router tests + make them pass --- core/components/router.go | 11 +++- core/components/router_test.go | 100 ++++++++++++++++++++++++++++----- 2 files changed, 95 insertions(+), 16 deletions(-) diff --git a/core/components/router.go b/core/components/router.go index be9ef5bdf..7eeadd19a 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -40,6 +40,7 @@ func (r *Router) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.A // HandleUp implements the core.Component interface func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { + var err error // Lookup for an existing broker devAddr, err := p.DevAddr() if err != nil { @@ -48,12 +49,18 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt } entries, err := r.db.Lookup(devAddr) - if err != ErrDeviceNotFound && err != ErrNotFound && err != ErrEntryExpired { + if err != nil && err != ErrNotFound && err != ErrEntryExpired { an.Nack() return err } - response, err := upAdapter.Send(p, entries.Recipient) + var response core.Packet + if err == nil { + response, err = upAdapter.Send(p, entries.Recipient) + } else { + response, err = upAdapter.Send(p) + } + if err != nil { an.Nack() return err diff --git a/core/components/router_test.go b/core/components/router_test.go index a9f0b5f3c..a8b65dc16 100644 --- a/core/components/router_test.go +++ b/core/components/router_test.go @@ -4,27 +4,82 @@ package components import ( + "reflect" "testing" "time" "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestRouterHandleUp(t *testing.T) { + devices := []device{ + { + DevAddr: [4]byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + { + DevAddr: [4]byte{0, 0, 0, 2}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + }, + } + + recipients := []core.Recipient{ + {Address: "Recipient1", Id: ""}, + {Address: "Recipient2", Id: ""}, + } + tests := []struct { - Desc string - Packet packetShape - WantRecipients []core.Recipient - WantError error - }{} + Desc string + KnownRecipients map[[4]byte]core.Recipient + Packet packetShape + WantRecipients []core.Recipient + WantError error + }{ + { + Desc: "0 known | Send #0", + Packet: packetShape{ + Device: devices[0], + Data: "MyData", + }, + WantRecipients: nil, + WantError: nil, + }, + { + Desc: "Know #0 | Send #0", + KnownRecipients: map[[4]byte]core.Recipient{ + devices[0].DevAddr: recipients[0], + }, + Packet: packetShape{ + Device: devices[0], + Data: "MyData", + }, + WantRecipients: []core.Recipient{recipients[0]}, + WantError: nil, + }, + { + Desc: "Know #1 | Send #0", + KnownRecipients: map[[4]byte]core.Recipient{ + devices[1].DevAddr: recipients[0], + }, + Packet: packetShape{ + Device: devices[0], + Data: "MyData", + }, + WantRecipients: nil, + WantError: nil, + }, + } for _, test := range tests { // Describe Desc(t, test.Desc) // Build - router := genNewRouter(t) + router := genNewRouter(t, test.KnownRecipients) packet := genPacketFromShape(test.Packet) // Operate @@ -33,6 +88,10 @@ func TestRouterHandleUp(t *testing.T) { // Check checkErrors(t, test.WantError, err) checkRecipients(t, test.WantRecipients, recipients) + + if err := router.db.Close(); err != nil { + panic(err) + } } } @@ -41,7 +100,7 @@ type routerRecipient struct { } // ----- BUILD utilities -func genNewRouter(t *testing.T) core.Router { +func genNewRouter(t *testing.T, knownRecipients map[[4]byte]core.Recipient) *Router { ctx := GetLogger(t, "Router") db, err := NewRouterStorage(time.Hour * 8) @@ -58,14 +117,23 @@ func genNewRouter(t *testing.T) core.Router { panic(err) } + for devAddr, recipient := range knownRecipients { + err := router.Register(core.Registration{ + DevAddr: lorawan.DevAddr(devAddr), + Recipient: recipient, + }, voidAckNacker{}) + if err != nil { + panic(err) + } + } + return router } // ----- OPERATE utilities func handleRouterUp(router core.Router, packet core.Packet) ([]core.Recipient, error) { - an := voidAckNacker{} - adapter := routerAdapter{} - err := router.HandleUp(packet, an, adapter) + adapter := &routerAdapter{} + err := router.HandleUp(packet, voidAckNacker{}, adapter) return adapter.Recipients, err } @@ -73,20 +141,24 @@ type routerAdapter struct { Recipients []core.Recipient } -func (a routerAdapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { +func (a *routerAdapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { a.Recipients = r return core.Packet{}, nil } -func (a routerAdapter) Next() (core.Packet, core.AckNacker, error) { +func (a *routerAdapter) Next() (core.Packet, core.AckNacker, error) { panic("Unexpected call to Next()") } -func (a routerAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { +func (a *routerAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { panic("Unexpected call to NextRegistration") } // ----- Check utilities func checkRecipients(t *testing.T, want []core.Recipient, got []core.Recipient) { - + if !reflect.DeepEqual(want, got) { + Ko(t, "Contacted recipients don't match expectations.\nWant: %v\nGot: %v", want, got) + return + } + Ok(t, "Check recipients") } From 6aa336864f47c7a3306fd263082c04f28cabc6f9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 16 Feb 2016 20:10:42 +0100 Subject: [PATCH 0562/2266] Use pointer receiver for routerBoltStorage -> mandatory for mutex to work correctly --- core/components/router_storage.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 95e09b47b..9608d9f94 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -54,12 +54,12 @@ func NewRouterStorage(delay time.Duration) (RouterStorage, error) { } // Lookup implements the RouterStorage interface -func (s routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { +func (s *routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { return s.lookup(devAddr, true) } // lookup offers an indirection in order to avoid taking a lock if not needed -func (s routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEntry, error) { +func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEntry, error) { // NOTE This works under the assumption that a read or write lock is already hold by the callee (e.g. Store) if lock { s.Lock() @@ -92,7 +92,7 @@ func (s routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEnt } // Store implements the RouterStorage interface -func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { +func (s *routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { s.Lock() defer s.Unlock() _, err := s.lookup(devAddr, false) @@ -104,12 +104,14 @@ func (s routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) err } // Close implements the RouterStorage interface -func (s routerBoltStorage) Close() error { +func (s *routerBoltStorage) Close() error { + s.Lock() + defer s.Unlock() return s.DB.Close() } // Reset implements the RouterStorage interface -func (s routerBoltStorage) Reset() error { +func (s *routerBoltStorage) Reset() error { s.Lock() defer s.Unlock() return resetDB(s.DB, "brokers") From 90e2945ef4a5e2ab6eada2b7635336b5284873b7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 11:02:03 +0100 Subject: [PATCH 0563/2266] [test.broker] Prepare ground for broker storage testing --- core/components/broker_storage_test.go | 127 +++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 core/components/broker_storage_test.go diff --git a/core/components/broker_storage_test.go b/core/components/broker_storage_test.go new file mode 100644 index 000000000..858f4f814 --- /dev/null +++ b/core/components/broker_storage_test.go @@ -0,0 +1,127 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package components + +import ( + "reflect" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +type brokerEntryShape struct { + brokerEntry + DevAddr lorawan.DevAddr +} + +func TestBrokerStorage(t *testing.T) { + devices := []lorawan.DevAddr{ + lorawan.DevAddr([4]byte{0, 0, 0, 1}), + lorawan.DevAddr([4]byte{14, 15, 8, 42}), + } + + tests := []struct { + Desc string + ExistingEntries []brokerEntryShape + Store *brokerEntryShape + Lookup lorawan.DevAddr + WantEntries []brokerEntry + WantError []error + }{ + { + Desc: "Default", + ExistingEntries: nil, + Store: nil, + Lookup: devices[0], + WantEntries: nil, + WantError: nil, + }, + } + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + db := genFilledBrokerStorage(test.ExistingEntries) + cherr := make(chan interface{}, 2) + + // Operate + storeBroker(db, test.Store, cherr) + got := lookupBroker(db, test.Lookup, cherr) + + // Check + go func() { + time.After(time.Millisecond * 250) + close(cherr) + }() + checkChErrors(t, test.WantError, cherr) + checkBrokerEntries(t, test.WantEntries, got) + + // Clean + db.Close() + } +} + +// ----- BUILD utilities +func genFilledBrokerStorage(setup []brokerEntryShape) BrokerStorage { + db, err := NewBrokerStorage() + if err != nil { + panic(err) + } + + if err := db.Reset(); err != nil { + panic(err) + } + + for _, shape := range setup { + if err := db.Store(shape.DevAddr, shape.brokerEntry); err != nil { + panic(err) + } + } + + return db +} + +// ----- OPERATE utilities +func storeBroker(db BrokerStorage, entry *brokerEntryShape, cherr chan interface{}) { + if entry == nil { + return + } + + if err := db.Store(entry.DevAddr, entry.brokerEntry); err != nil { + cherr <- err + } +} + +func lookupBroker(db BrokerStorage, devAddr lorawan.DevAddr, cherr chan interface{}) []brokerEntry { + entry, err := db.Lookup(devAddr) + if err != nil { + cherr <- err + } + return entry +} + +// ----- CHECK utilities +func checkBrokerEntries(t *testing.T, want []brokerEntry, got []brokerEntry) { + if len(want) != len(got) { + Ko(t, "Expecting %d entries but got %d.", len(want), len(got)) + return + } + +outer: + for _, gentry := range got { + for _, wentry := range want { + if reflect.DeepEqual(gentry, wentry) { + continue outer + } + } + Ko(t, "Got an unexpected entry: %+v\nExpected only: %+v", gentry, want) + return + } + + Ok(t, "Check broker entries") +} From ee22094b3c9e1852254995f179eb64462f603e6b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 11:06:44 +0100 Subject: [PATCH 0564/2266] [test.broker] Add test cases, works fine --- core/components/broker_storage_test.go | 49 ++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/core/components/broker_storage_test.go b/core/components/broker_storage_test.go index 858f4f814..52cca0704 100644 --- a/core/components/broker_storage_test.go +++ b/core/components/broker_storage_test.go @@ -23,6 +23,19 @@ func TestBrokerStorage(t *testing.T) { lorawan.DevAddr([4]byte{14, 15, 8, 42}), } + entries := []brokerEntry{ + { + Id: "MyId1", + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), + Url: "MyUrlThatWillSoonBerefactored", + }, + { + Id: "SecondId", + NwkSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 14, 14, 14, 14, 9, 14, 11, 12, 13, 14, 15}), + Url: "Url2", + }, + } + tests := []struct { Desc string ExistingEntries []brokerEntryShape @@ -32,13 +45,43 @@ func TestBrokerStorage(t *testing.T) { WantError []error }{ { - Desc: "Default", + Desc: "No entry | Store #0", ExistingEntries: nil, - Store: nil, + Store: &brokerEntryShape{entries[0], devices[0]}, Lookup: devices[0], - WantEntries: nil, + WantEntries: []brokerEntry{entries[0]}, WantError: nil, }, + { + Desc: "Entry #0 | Store another #0", + ExistingEntries: []brokerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &brokerEntryShape{entries[1], devices[0]}, + Lookup: devices[0], + WantEntries: []brokerEntry{entries[0], entries[1]}, + WantError: nil, + }, + { + Desc: "Entry #0 | Store #1 | Lookup #1", + ExistingEntries: []brokerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &brokerEntryShape{entries[1], devices[1]}, + Lookup: devices[1], + WantEntries: []brokerEntry{entries[1]}, + WantError: nil, + }, + { + Desc: "Entry #0 | Store #1 | Lookup #0", + ExistingEntries: []brokerEntryShape{ + {entries[0], devices[0]}, + }, + Store: &brokerEntryShape{entries[1], devices[1]}, + Lookup: devices[0], + WantEntries: []brokerEntry{entries[0]}, + WantError: nil, + }, } for _, test := range tests { From 94d1aaca7c2b65b843e41886acb0a9f7057ee765 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 11:37:07 +0100 Subject: [PATCH 0565/2266] [test.broker] Write broker test backbone --- core/components/broker_test.go | 139 +++++++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 6 deletions(-) diff --git a/core/components/broker_test.go b/core/components/broker_test.go index 5a88a5478..d96aab51f 100644 --- a/core/components/broker_test.go +++ b/core/components/broker_test.go @@ -5,16 +5,143 @@ package components import ( "testing" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) -func TestNewBroker(t *testing.T) { - t.Skip("TODO") +func TestBrokerHandleup(t *testing.T) { + devices := []device{ + { + DevAddr: [4]byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, + }, + { + DevAddr: [4]byte{0, 0, 0, 2}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, + }, + { + DevAddr: [4]byte{14, 14, 14, 14}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 11, 8, 11, 8, 8}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 10, 11, 8, 8, 8}, + }, + { + DevAddr: [4]byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 9, 7, 7, 9, 8, 8, 8, 3, 13, 8}, + NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 4, 7, 9, 9, 8, 8, 8, 9, 14, 8}, + }, + } + + tests := []struct { + Desc string + KnownRecipients []core.Registration + Packet packetShape + WantRecipients []core.Recipient + WantAck bool + WantError error + }{ + { + Desc: "0 known | Send #0", + Packet: packetShape{ + Device: devices[0], + Data: "MyData", + }, + WantRecipients: nil, + WantAck: false, + WantError: nil, + }, + } + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + broker := genNewBroker(t, test.KnownRecipients) + packet := genPacketFromShape(test.Packet) + + // Operate + recipients, ack, err := handleBrokerUp(broker, packet) + + // Check + checkErrors(t, test.WantError, err) + checkBrokerAcks(t, test.WantAck, ack) + checkRecipients(t, test.WantRecipients, recipients) + + if err := broker.db.Close(); err != nil { + panic(err) + } + } } -func TestHandleUpBroker(t *testing.T) { - t.Skip("TODO") +// ----- BUILD utilities +func genNewBroker(t *testing.T, knownRecipients []core.Registration) *Broker { + ctx := GetLogger(t, "Broker") + + db, err := NewBrokerStorage() + if err != nil { + panic(err) + } + + if err := db.Reset(); err != nil { + panic(err) + } + + broker := NewBroker(db, ctx) + if err != nil { + panic(err) + } + + for _, registration := range knownRecipients { + err := broker.Register(registration, voidAckNacker{}) + if err != nil { + panic(err) + } + } + + return broker +} + +// ----- OPERATE utilities +func handleBrokerUp(broker core.Broker, packet core.Packet) ([]core.Recipient, *bool, error) { + adapter := &routerAdapter{} + an := &brokerAckNacker{} + err := broker.HandleUp(packet, an, adapter) + return adapter.Recipients, an.HasAck, err +} + +type brokerAckNacker struct { + HasAck *bool } -func TestRegisterBroker(t *testing.T) { - t.Skip("TODO") +func (an *brokerAckNacker) Ack(packets ...core.Packet) error { + an.HasAck = new(bool) + *an.HasAck = true + return nil +} + +func (an *brokerAckNacker) Nack() error { + an.HasAck = new(bool) + *an.HasAck = false + return nil +} + +// ----- CHECK utilities +func checkBrokerAcks(t *testing.T, want bool, got *bool) { + if got == nil { + Ko(t, "No Ack or Nack was sent") + return + } + + expected, notExpected := "ack", "nack" + if !want { + expected, notExpected = notExpected, expected + } + if want != *got { + Ko(t, "Expected %s but got %s", expected, notExpected) + return + } + Ok(t, "Check acks") } From 203ffce84e87f6ec1285655eed4e0e7836bee92d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 11:41:50 +0100 Subject: [PATCH 0566/2266] [test.broker] Write broker test cases --- core/components/broker_test.go | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/core/components/broker_test.go b/core/components/broker_test.go index d96aab51f..9a6203d4a 100644 --- a/core/components/broker_test.go +++ b/core/components/broker_test.go @@ -8,6 +8,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestBrokerHandleup(t *testing.T) { @@ -34,6 +35,25 @@ func TestBrokerHandleup(t *testing.T) { }, } + recipients := []core.Registration{ + { + Recipient: core.Recipient{ + Address: "R0<->D0", + Id: "Id0", + }, + DevAddr: lorawan.DevAddr(devices[0].DevAddr), + Options: lorawan.AES128Key(devices[0].NwkSKey), + }, + { + Recipient: core.Recipient{ + Address: "R1<->D1", + Id: "Id1", + }, + DevAddr: lorawan.DevAddr(devices[1].DevAddr), + Options: lorawan.AES128Key(devices[1].NwkSKey), + }, + } + tests := []struct { Desc string KnownRecipients []core.Registration @@ -52,6 +72,60 @@ func TestBrokerHandleup(t *testing.T) { WantAck: false, WantError: nil, }, + { + Desc: "know #0 | Send #0", + KnownRecipients: []core.Registration{ + recipients[0], + }, + Packet: packetShape{ + Device: devices[0], + Data: "MyData", + }, + WantRecipients: []core.Recipient{recipients[0].Recipient}, + WantAck: true, + WantError: nil, + }, + { + Desc: "know #1 | Send #0", + KnownRecipients: []core.Registration{ + recipients[1], + }, + Packet: packetShape{ + Device: devices[0], + Data: "MyData", + }, + WantRecipients: nil, + WantAck: false, + WantError: nil, + }, + { + Desc: "know #0, #1 | Send #2", + KnownRecipients: []core.Registration{ + recipients[0], + recipients[1], + }, + Packet: packetShape{ + Device: devices[2], + Data: "MyData", + }, + WantRecipients: nil, + WantAck: false, + WantError: nil, + }, + { + Desc: "know #0, #1 | Send #3 (address == #1, nwkskey != #1)", + KnownRecipients: []core.Registration{ + recipients[0], + recipients[1], + }, + Packet: packetShape{ + Device: devices[3], + Data: "MyData", + }, + WantRecipients: nil, + WantAck: false, + WantError: nil, + }, } for _, test := range tests { From 88de354f1b41e9ab0ffca92fab1f06d47d2e2b9a Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 11:43:32 +0100 Subject: [PATCH 0567/2266] [test.broker] Fix small issue with error duplication --- core/components/broker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/components/broker.go b/core/components/broker.go index 5f4306047..e93ac7871 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -39,6 +39,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter entries, err := b.db.Lookup(devAddr) switch err { case nil: + case ErrNotFound: case ErrDeviceNotFound: ctx.Warn("Uplink device not found") return an.Nack() From fc41964b188b934bd082f56dadc6a5e23c32200e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 14:32:05 +0100 Subject: [PATCH 0568/2266] [refactor.errors] Use of a single packet as Ack instead of a variadic parameter --- core/adapters/http/adapter_test.go | 2 +- .../adapters/http/broadcast/broadcast_test.go | 2 +- .../adapters/http/broadcast/void_acknacker.go | 2 +- core/adapters/http/packet_acknacker.go | 19 +++++++++---------- core/adapters/http/pubsub/reg_acknacker.go | 2 +- core/adapters/semtech/semtech_acknacker.go | 6 +++--- core/components/broker.go | 4 ++-- core/components/broker_test.go | 2 +- core/components/handler.go | 8 +++++--- core/components/handler_test.go | 4 ++-- core/components/router.go | 4 ++-- core/core.go | 4 ++-- 12 files changed, 30 insertions(+), 29 deletions(-) diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 326b62365..195ed3231 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -112,7 +112,7 @@ func TestNext(t *testing.T) { if test.IsNotFound { an.Nack() } else { - an.Ack() + an.Ack(nil) } } gotError <- err diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index 761813a5f..3e9172214 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -127,7 +127,7 @@ func getRegistrations(adapter *Adapter, want []core.Registration) []core.Registr if err != nil { return } - an.Ack(core.Packet{}) + an.Ack(nil) ch <- r }() select { diff --git a/core/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go index 98f2f4f86..a20a4a97b 100644 --- a/core/adapters/http/broadcast/void_acknacker.go +++ b/core/adapters/http/broadcast/void_acknacker.go @@ -10,7 +10,7 @@ import ( type voidAckNacker struct{} // Ack implements the core.AckNacker interface -func (v voidAckNacker) Ack(p ...core.Packet) error { +func (v voidAckNacker) Ack(p *core.Packet) error { return nil } diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go index 6b92545cf..db65e5e07 100644 --- a/core/adapters/http/packet_acknacker.go +++ b/core/adapters/http/packet_acknacker.go @@ -21,17 +21,16 @@ type packetAckNacker struct { } // Ack implements the core.AckNacker interface -func (an packetAckNacker) Ack(p ...core.Packet) error { - if len(p) > 1 { - return ErrInvalidArguments +func (an packetAckNacker) Ack(p *core.Packet) error { + defer close(an.response) + if p == nil { + an.response <- pktRes{statusCode: http.StatusOK} + return nil } - var raw []byte - if len(p) == 1 { - var err error - raw, err = json.Marshal(p[0]) - if err != nil { - return err - } + + raw, err := json.Marshal(*p) + if err != nil { + return err } select { diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 7fb71e928..1dc0c8f17 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -18,7 +18,7 @@ type regAckNacker struct { } // Ack implements the core.Acker interface -func (r regAckNacker) Ack(p ...core.Packet) error { +func (r regAckNacker) Ack(p *core.Packet) error { select { case r.response <- regRes{statusCode: http.StatusAccepted}: return nil diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index f3ccc8f52..d1421f9c3 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -18,13 +18,13 @@ type semtechAckNacker struct { } // Ack implements the core.Adapter interface -func (an semtechAckNacker) Ack(p ...core.Packet) error { - if len(p) == 0 { +func (an semtechAckNacker) Ack(p *core.Packet) error { + if p == nil { return nil } // For the downlink, we have to send a PULL_RESP packet which hold a TXPK - txpk, err := core.ConvertToTXPK(p[0]) + txpk, err := core.ConvertToTXPK(*p) if err != nil { return err } diff --git a/core/components/broker.go b/core/components/broker.go index e93ac7871..dee01d0ba 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -76,7 +76,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter an.Nack() return err } - return an.Ack(response) + return an.Ack(&response) } // HandleDown implements the core.Component interface. Not implemented yet @@ -106,5 +106,5 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { } ctx.Debug("Successful Registration") - return an.Ack() + return an.Ack(nil) } diff --git a/core/components/broker_test.go b/core/components/broker_test.go index 9a6203d4a..a8555db45 100644 --- a/core/components/broker_test.go +++ b/core/components/broker_test.go @@ -190,7 +190,7 @@ type brokerAckNacker struct { HasAck *bool } -func (an *brokerAckNacker) Ack(packets ...core.Packet) error { +func (an *brokerAckNacker) Ack(p *core.Packet) error { an.HasAck = new(bool) *an.HasAck = true return nil diff --git a/core/components/handler.go b/core/components/handler.go index a9e723ef6..0ee7d1704 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -76,7 +76,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { return err } - an.Ack() + an.Ack(nil) return nil } @@ -116,7 +116,9 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap switch resp.(type) { case core.Packet: h.ctx.WithField("bundleId", id).Debug("Received response with packet. Sending ack") - an.Ack(resp.(core.Packet)) + pkt := new(core.Packet) + *pkt = resp.(core.Packet) + an.Ack(pkt) return nil case error: h.ctx.WithField("bundleId", id).WithError(resp.(error)).Debug("Received response. Sending Nack") @@ -124,7 +126,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap return resp.(error) default: h.ctx.WithField("bundleId", id).Debug("Received response. Sending ack") - an.Ack() + an.Ack(nil) return nil } } diff --git a/core/components/handler_test.go b/core/components/handler_test.go index d6855eefc..2a341b104 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -346,7 +346,7 @@ func genNewHandler(t *testing.T, applications map[lorawan.EUI64]application) *Ha type voidAckNacker struct{} -func (v voidAckNacker) Ack(packets ...core.Packet) error { +func (v voidAckNacker) Ack(packets *core.Packet) error { return nil } func (v voidAckNacker) Nack() error { @@ -381,7 +381,7 @@ type chanAckNacker struct { NackChan chan interface{} } -func (an chanAckNacker) Ack(packets ...core.Packet) error { +func (an chanAckNacker) Ack(packets *core.Packet) error { an.AckChan <- true return nil } diff --git a/core/components/router.go b/core/components/router.go index 7eeadd19a..bf1bfd1e9 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -30,7 +30,7 @@ func (r *Router) Register(reg core.Registration, an core.AckNacker) error { an.Nack() return err } - return an.Ack() + return an.Ack(nil) } // HandleDown implements the core.Component interface @@ -65,5 +65,5 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt an.Nack() return err } - return an.Ack(response) + return an.Ack(&response) } diff --git a/core/core.go b/core/core.go index 3e01e1b3f..b3c05c081 100644 --- a/core/core.go +++ b/core/core.go @@ -53,11 +53,11 @@ type Metadata struct { // without even knowing about the protocol being used. This is only possible because all messages // transmitted between component are relatively isomorph (to what one calls a Packet). type AckNacker interface { - // Ack acknowledges and terminates a connection by sending 0, 1 or several packets as an answer + // Ack acknowledges and terminates a connection by sending 0 or 1 packet as an answer // (depending on the component). // // Incidentally, that acknowledgement would serve as a downlink response for class A devices. - Ack(p ...Packet) error + Ack(p *Packet) error // Nack rejects and terminates a connection. So far, there is no way to give more information // about the reason that led to a rejection. From 9ae9456c02607794dfd3fa66a2f905682b5c2244 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 14:58:53 +0100 Subject: [PATCH 0569/2266] [refactor.errors] Create a simple error type --- utils/errors/errors.go | 64 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 utils/errors/errors.go diff --git a/utils/errors/errors.go b/utils/errors/errors.go new file mode 100644 index 000000000..3741466b4 --- /dev/null +++ b/utils/errors/errors.go @@ -0,0 +1,64 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package errors + +import ( + "fmt" + "time" +) + +type base struct { + Nature Nature // Kind of error, used a comparator + Timestamp time.Time // The moment the error was created +} + +// Failure states for fault that aren't recoverable by the system itself. +// They consist in unexpected behavior +type Failure struct { + base + Fault error // The source of the failure +} + +// Error states for fault that are explicitely created by the application in order to be handled +// elsewhere. They are recoverable and convey valuable pieces of information. +type Error struct { + base +} + +// Nature identify an error type with a simple tag +type Nature string + +// NewFailure creates a new Failure from a source error +func NewFailure(k Nature, src error) Failure { + return Failure{ + base: base{ + Nature: k, + Timestamp: time.Now(), + }, + Fault: src, + } +} + +// NewError creates a new Error +func NewError(k Nature) Error { + return Error{ + base: base{ + Nature: k, + Timestamp: time.Now(), + }, + } +} + +// Error implements the error built-in interface +func (err Failure) Error() string { + if err.Fault == nil { + return err.base.Error() + } + return fmt.Sprintf("%s: %s", err.Nature, err.Fault.Error()) +} + +// Error implements the error built-in interface +func (err base) Error() string { + return fmt.Sprintf("%s", err.Nature) +} From d4ad0a453de2b755a17f1f9e537fc07394de2cf4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 15:08:44 +0100 Subject: [PATCH 0570/2266] [refactor.errors] Join failures built from failures --- utils/errors/errors.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 3741466b4..fa98d7921 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -5,6 +5,7 @@ package errors import ( "fmt" + "reflect" "time" ) @@ -31,13 +32,22 @@ type Nature string // NewFailure creates a new Failure from a source error func NewFailure(k Nature, src error) Failure { - return Failure{ + failure := Failure{ base: base{ Nature: k, Timestamp: time.Now(), }, Fault: src, } + + // Pop one level if we made a failure from a failure + t := reflect.TypeOf(src) + tf := reflect.TypeOf(failure) + if t == tf { + failure.Fault = src.(Failure).Fault + } + + return failure } // NewError creates a new Error From c90ba8baa2906435366c84e0f630917f64affcb5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 15:15:27 +0100 Subject: [PATCH 0571/2266] [refactor.errors] Allow failure to be specified as error or string --- utils/errors/errors.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/utils/errors/errors.go b/utils/errors/errors.go index fa98d7921..dba74e763 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -31,13 +31,23 @@ type Error struct { type Nature string // NewFailure creates a new Failure from a source error -func NewFailure(k Nature, src error) Failure { +func NewFailure(k Nature, src interface{}) Failure { + var fault error + switch src.(type) { + case string: + fault = fmt.Errorf("%s", src.(string)) + case error: + fault = src.(error) + default: + panic("Unexpected error source") + } + failure := Failure{ base: base{ Nature: k, Timestamp: time.Now(), }, - Fault: src, + Fault: fault, } // Pop one level if we made a failure from a failure From 17d322480890170a7e2987ea43aa926af9a90589 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 15:44:00 +0100 Subject: [PATCH 0572/2266] [refactor.errors] Refactor errors in core package --- core/check_utilities_test.go | 10 ++++++---- core/errors.go | 9 +++++++++ core/metadata.go | 14 ++++++++++---- core/metadata_test.go | 4 ++-- core/packet.go | 35 +++++++++++++++++------------------ core/packet_test.go | 8 ++++---- utils/errors/errors.go | 9 +++------ 7 files changed, 51 insertions(+), 38 deletions(-) create mode 100644 core/errors.go diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go index bda8c38a1..b55efe38b 100644 --- a/core/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -24,12 +25,13 @@ func checkPackets(t *testing.T, want Packet, got Packet) { } // Checks that errors match -func checkErrors(t *testing.T, want error, got error) { - if got == want { - Ok(t, "check Errors") +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") return } - Ko(t, "Expected error to be %v but got %v", want, got) + + Ko(t, "Expected error to be %s but got %v", want, got) } // Checks that obtained json matches expected one diff --git a/core/errors.go b/core/errors.go new file mode 100644 index 000000000..f6ed7acef --- /dev/null +++ b/core/errors.go @@ -0,0 +1,9 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package core + +const ( + ErrInvalidPacket = "Invalid Packet" + ErrInvalidMetadata = "Invalid Metadata" +) diff --git a/core/metadata.go b/core/metadata.go index 55fb07f55..83b8e9c87 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -5,10 +5,10 @@ package core import ( "encoding/json" - "fmt" "time" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" ) @@ -38,22 +38,28 @@ func (m Metadata) MarshalJSON() ([]byte, error) { *t = semtech.Timeparser{Layout: time.RFC3339Nano, Value: m.Time} } - return json.Marshal(metadataProxy{ + data, err := json.Marshal(metadataProxy{ metadata: metadata(m), Datr: d, Time: t, }) + + if err != nil { + err = errors.NewFailure(ErrInvalidMetadata, err) + } + + return data, err } // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { if m == nil { - return fmt.Errorf("Cannot unmarshal nil Metadata") + return errors.NewFailure(ErrInvalidMetadata, "Cannot unmarshal nil Metadata") } proxy := metadataProxy{} if err := json.Unmarshal(raw, &proxy); err != nil { - return err + return errors.NewFailure(ErrInvalidMetadata, err) } *m = Metadata(proxy.metadata) if proxy.Time != nil { diff --git a/core/metadata_test.go b/core/metadata_test.go index 1edd2994a..ab7ced219 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -14,7 +14,7 @@ import ( var commonTests = []struct { Metadata Metadata - WantError error + WantError *string JSON string }{ { // Basic attributes, uint, string and float64 @@ -62,7 +62,7 @@ var commonTests = []struct { var unmarshalTests = []struct { Metadata Metadata - WantError error + WantError *string JSON string }{ { // Local time diff --git a/core/packet.go b/core/packet.go index 4e5060082..6630daee0 100644 --- a/core/packet.go +++ b/core/packet.go @@ -11,21 +11,20 @@ import ( "strings" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/brocaar/lorawan" ) -var ErrImpossibleConversion error = fmt.Errorf("Illegal attempt to convert a packet") - // DevAddr returns a lorawan device address associated to the packet if any func (p Packet) DevAddr() (lorawan.DevAddr, error) { if p.Payload.MACPayload == nil { - return lorawan.DevAddr{}, fmt.Errorf("MACPayload should not be empty") + return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidPacket, "MACPAyload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return lorawan.DevAddr{}, fmt.Errorf("Packet does not carry a MACPayload") + return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidPacket, "Packet does not carry a MACPayload") } return macpayload.FHDR.DevAddr, nil @@ -34,12 +33,12 @@ func (p Packet) DevAddr() (lorawan.DevAddr, error) { // FCnt returns the frame counter of the given packet if any func (p Packet) Fcnt() (uint32, error) { if p.Payload.MACPayload == nil { - return 0, fmt.Errorf("MACPayload should not be empty") + return 0, errors.NewFailure(ErrInvalidPacket, "MACPayload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return 0, fmt.Errorf("Packet does not carry a MACPayload") + return 0, errors.NewFailure(ErrInvalidPacket, "Packet does not carry a MACPayload") } return macpayload.FHDR.FCnt, nil @@ -59,7 +58,7 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { // First, we have to get the physical payload which is encoded in the Data field packet := Packet{} if p.Data == nil { - return packet, ErrImpossibleConversion + return packet, errors.NewFailure(ErrInvalidPacket, "There's no data in the packet") } // RXPK Data are base64 encoded, yet without the trailing "==" if any..... @@ -73,12 +72,12 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { - return packet, err + return packet, errors.NewFailure(ErrInvalidPacket, err) } payload := lorawan.NewPHYPayload(true) if err = payload.UnmarshalBinary(raw); err != nil { - return packet, err + return packet, errors.NewFailure(ErrInvalidPacket, err) } // Then, we interpret every other known field as a metadata and store them into an appropriate @@ -104,7 +103,7 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { // Step 1, convert the physical payload to a base64 string (without the padding) raw, err := p.Payload.MarshalBinary() if err != nil { - return semtech.TXPK{}, ErrImpossibleConversion + return semtech.TXPK{}, errors.NewFailure(ErrInvalidPacket, err) } data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") txpk := semtech.TXPK{Data: pointer.String(data)} @@ -128,11 +127,11 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { func (p Packet) MarshalJSON() ([]byte, error) { rawMetadata, err := json.Marshal(p.Metadata) if err != nil { - return nil, err + return nil, errors.NewFailure(ErrInvalidPacket, err) } rawPayload, err := p.Payload.MarshalBinary() if err != nil { - return nil, err + return nil, errors.NewFailure(ErrInvalidPacket, err) } strPayload := base64.StdEncoding.EncodeToString(rawPayload) return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil @@ -141,7 +140,7 @@ func (p Packet) MarshalJSON() ([]byte, error) { // UnmarshalJSON impements the json.Marshaler interface func (p *Packet) UnmarshalJSON(raw []byte) error { if p == nil { - return ErrImpossibleConversion + return errors.NewFailure(ErrInvalidPacket, "Cannot unmarshal a nil packet") } // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink @@ -154,17 +153,17 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { err := json.Unmarshal(raw, &proxy) if err != nil { - return err + return errors.NewFailure(ErrInvalidPacket, err) } rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) if err != nil { - return err + return errors.NewFailure(ErrInvalidPacket, err) } payload := lorawan.NewPHYPayload(true) // true -> uplink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return err + return errors.NewFailure(ErrInvalidPacket, err) } // Now, we check the nature of the decoded payload @@ -178,7 +177,7 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { // We thus have to unmarshall properly payload = lorawan.NewPHYPayload(false) // false -> downlink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return err + return errors.NewFailure(ErrInvalidPacket, err) } case "JoinRequest": fallthrough @@ -191,7 +190,7 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { case "Proprietary": // Proprietary can be either downlink or uplink. Right now, we do not have any message of // that type and thus, we just don't know how to handle them. Let's throw an error. - return fmt.Errorf("Unsupported MType Proprietary") + return errors.NewFailure(ErrInvalidPacket, "Unsupported MType 'Proprietary'") } // Packet = Payload + Metadata diff --git a/core/packet_test.go b/core/packet_test.go index 30c0da77f..77db3b8e9 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -17,7 +17,7 @@ func TestConvertRXPKPacket(t *testing.T) { tests := []convertRXPKTest{ genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithNoData(&convertRXPKTest{WantError: ErrImpossibleConversion}), + genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(ErrInvalidPacket)}), } for _, test := range tests { @@ -37,7 +37,7 @@ func TestConvertTXPKPacket(t *testing.T) { convertToTXPKTest{ CorePacket: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, TXPK: semtech.TXPK{}, - WantError: ErrImpossibleConversion, + WantError: pointer.String(ErrInvalidPacket), }, } @@ -104,13 +104,13 @@ func TestUnmarshalJSONPacket(t *testing.T) { type convertRXPKTest struct { CorePacket Packet RXPK semtech.RXPK - WantError error + WantError *string } type convertToTXPKTest struct { TXPK semtech.TXPK CorePacket Packet - WantError error + WantError *string } type marshalJSONTest struct { diff --git a/utils/errors/errors.go b/utils/errors/errors.go index dba74e763..a0d541cce 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -10,7 +10,7 @@ import ( ) type base struct { - Nature Nature // Kind of error, used a comparator + Nature string // Kind of error, used a comparator Timestamp time.Time // The moment the error was created } @@ -27,11 +27,8 @@ type Error struct { base } -// Nature identify an error type with a simple tag -type Nature string - // NewFailure creates a new Failure from a source error -func NewFailure(k Nature, src interface{}) Failure { +func NewFailure(k string, src interface{}) Failure { var fault error switch src.(type) { case string: @@ -61,7 +58,7 @@ func NewFailure(k Nature, src interface{}) Failure { } // NewError creates a new Error -func NewError(k Nature) Error { +func NewError(k string) Error { return Error{ base: base{ Nature: k, From c3d710e89f721d135fd1433e824f4ed890870c62 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 16:06:11 +0100 Subject: [PATCH 0573/2266] [refactor.errors] Move errors declaration into a separated package --- core/{ => errors}/errors.go | 5 ++++- core/metadata.go | 1 + core/packet.go | 1 + core/packet_test.go | 1 + 4 files changed, 7 insertions(+), 1 deletion(-) rename core/{ => errors}/errors.go (55%) diff --git a/core/errors.go b/core/errors/errors.go similarity index 55% rename from core/errors.go rename to core/errors/errors.go index f6ed7acef..bf081aaaa 100644 --- a/core/errors.go +++ b/core/errors/errors.go @@ -1,9 +1,12 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package core +// Package errors references all failures nature referenced in the core library +package errors const ( ErrInvalidPacket = "Invalid Packet" ErrInvalidMetadata = "Invalid Metadata" + ErrInvalidParam = "Invalid Parameter" + ErrNotSupported = "Unsupported Operation" ) diff --git a/core/metadata.go b/core/metadata.go index 83b8e9c87..0c197fbef 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -7,6 +7,7 @@ import ( "encoding/json" "time" + . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/core/packet.go b/core/packet.go index 6630daee0..41bd2677e 100644 --- a/core/packet.go +++ b/core/packet.go @@ -10,6 +10,7 @@ import ( "reflect" "strings" + . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/core/packet_test.go b/core/packet_test.go index 77db3b8e9..3556a804b 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "testing" + . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" From 764f430175999ecee404ad4790967f45192abc74 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 16:13:22 +0100 Subject: [PATCH 0574/2266] [refactor.errors] Refactor errors in semtech adapter --- core/adapters/semtech/adapter.go | 23 +++++-------------- core/adapters/semtech/adapter_test.go | 26 +++++++++++----------- core/adapters/semtech/semtech_acknacker.go | 6 +++-- core/errors/errors.go | 12 +++++----- 4 files changed, 29 insertions(+), 38 deletions(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 715fb9310..67be50828 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -8,7 +8,9 @@ import ( "net" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -34,11 +36,6 @@ type rxpkMsg struct { recipient core.Recipient // The address and id of the source emitter } -var ErrInvalidPort error = fmt.Errorf("Invalid port supplied. The connection might be already taken") -var ErrNotInitialized error = fmt.Errorf("Illegal call on non-initialized adapter") -var ErrNotSupported error = fmt.Errorf("Unsupported operation") -var ErrInvalidPacket error = fmt.Errorf("Invalid packet supplied") - // NewAdapter constructs and allocates a new semtech adapter func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a := Adapter{ @@ -53,7 +50,7 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a.ctx.WithField("port", port).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.ctx.WithError(err).Error("Unable to start server") - return nil, ErrInvalidPort + return nil, errors.NewFailure(ErrInvalidParam, fmt.Sprintf("Invalid port %v", port)) } go a.monitorConnection() @@ -63,33 +60,25 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { return &a, nil } -// ok controls whether or not the adapter has been initialized via NewAdapter() -func (a *Adapter) ok() bool { - return a != nil && a.conn != nil && a.next != nil -} - // Send implements the core.Adapter interface. Not implemented for the semtech adapter. func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - return core.Packet{}, ErrNotSupported + return core.Packet{}, errors.NewFailure(ErrNotSupported, "Send not supported on semtech adapter") } // Next implements the core.Adapter interface func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { - if !a.ok() { - return core.Packet{}, nil, ErrNotInitialized - } msg := <-a.next packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { a.ctx.Debug("Received invalid packet") - return core.Packet{}, nil, ErrInvalidPacket + return core.Packet{}, nil, errors.NewFailure(ErrInvalidPacket, err) } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil } // NextRegistration implements the core.Adapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return core.Registration{}, nil, ErrNotSupported + return core.Registration{}, nil, errors.NewFailure(ErrNotSupported, "NextRegistration not supported on semtech adapter") } // listen Handle incoming packets and forward them diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index 9706db054..11a733d97 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -9,26 +9,25 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) -func TestNewAdapter(t *testing.T) { - Ok(t, "pending") -} - func TestSend(t *testing.T) { Desc(t, "Send is not supported") adapter, _ := genAdapter(t, 33000) _, err := adapter.Send(core.Packet{}) - checkErrors(t, ErrNotSupported, err) + checkErrors(t, pointer.String(ErrNotSupported), err) } func TestNextRegistration(t *testing.T) { Desc(t, "Next registration is not supported") adapter, _ := genAdapter(t, 33001) _, _, err := adapter.NextRegistration() - checkErrors(t, ErrNotSupported, err) + checkErrors(t, pointer.String(ErrNotSupported), err) } func TestNext(t *testing.T) { @@ -40,7 +39,7 @@ func TestNext(t *testing.T) { Packet semtech.Packet WantAck semtech.Packet WantNext core.Packet - WantError error + WantError *string }{ { // Valid uplink PUSH_DATA Adapter: adapter, @@ -75,7 +74,7 @@ func TestNext(t *testing.T) { Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), WantAck: genPUSH_ACK([]byte{0x22, 0x35}), WantNext: core.Packet{}, - WantError: ErrInvalidPacket, + WantError: pointer.String(ErrInvalidPacket), }, } @@ -95,7 +94,7 @@ func TestNext(t *testing.T) { } } -// ----- operate utilities +// ----- OPERATE utilities func getNextPacket(next chan interface{}) (core.Packet, error) { select { case i := <-next: @@ -109,13 +108,14 @@ func getNextPacket(next chan interface{}) (core.Packet, error) { } } -// ----- check utilities -func checkErrors(t *testing.T, want error, got error) { - if want == got { +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { Ok(t, "Check errors") return } - Ko(t, "Expected error to be %v but got %v", want, got) + + Ko(t, "Expected error to be %s but got %v", want, got) } func checkCorePackets(t *testing.T, want core.Packet, got core.Packet) { diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index d1421f9c3..ec005069f 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -8,7 +8,9 @@ import ( "net" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" ) // semtechAckNacker represents an AckNacker for a semtech request @@ -26,7 +28,7 @@ func (an semtechAckNacker) Ack(p *core.Packet) error { // For the downlink, we have to send a PULL_RESP packet which hold a TXPK txpk, err := core.ConvertToTXPK(*p) if err != nil { - return err + return errors.NewFailure(ErrInvalidPacket, err) } raw, err := semtech.Packet{ @@ -37,7 +39,7 @@ func (an semtechAckNacker) Ack(p *core.Packet) error { addr, ok := an.recipient.Address.(*net.UDPAddr) if !ok { - return fmt.Errorf("Recipient address was invalid. Expected UDPAddr but got: %v", an.recipient.Address) + return errors.NewFailure(ErrInvalidRecipient, fmt.Sprintf("Expected UDPAddr but got: %v", an.recipient.Address)) } an.conn <- udpMsg{raw: raw, addr: addr} return nil diff --git a/core/errors/errors.go b/core/errors/errors.go index bf081aaaa..d9f82e7f1 100644 --- a/core/errors/errors.go +++ b/core/errors/errors.go @@ -1,12 +1,12 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -// Package errors references all failures nature referenced in the core library -package errors +package core const ( - ErrInvalidPacket = "Invalid Packet" - ErrInvalidMetadata = "Invalid Metadata" - ErrInvalidParam = "Invalid Parameter" - ErrNotSupported = "Unsupported Operation" + ErrInvalidPacket = "Invalid Packet" + ErrInvalidMetadata = "Invalid Metadata" + ErrInvalidParam = "Invalid Parameter" + ErrInvalidRecipient = "Invalid Recipient" + ErrNotSupported = "Unsupported Operation" ) From 44d482ae8ff593779a7a939b7db86100a5466db2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 17:50:40 +0100 Subject: [PATCH 0575/2266] [refactor.errors] Refactor errors in http adapter --- core/adapters/http/adapter.go | 24 +++++++------- core/adapters/http/adapter_test.go | 15 +++++---- core/adapters/http/broadcast/broadcast.go | 33 ++++++++----------- .../adapters/http/broadcast/broadcast_test.go | 16 +++++---- core/adapters/http/packet_acknacker.go | 12 +++---- core/adapters/http/parser/packet.go | 9 ++--- core/adapters/http/parser/registration.go | 19 ++++++----- core/adapters/http/pubsub/pubsub_test.go | 10 +++--- core/adapters/http/pubsub/reg_acknacker.go | 9 +++-- core/errors/errors.go | 14 +++++--- 10 files changed, 81 insertions(+), 80 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 5c2072090..e6222606f 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -15,13 +15,11 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) -var ErrInvalidPort = fmt.Errorf("The given port is invalid") -var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") -var ErrNotImplemented = fmt.Errorf("Illegal call on non-implemented method") - // Adapter type materializes an http adapter which implements the basic http protocol type Adapter struct { parser.PacketParser // The adapter's parser contract @@ -65,12 +63,12 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) m, err := json.Marshal(p.Metadata) if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, ErrInvalidPacket + return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) } pl, err := p.Payload.MarshalBinary() if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, ErrInvalidPacket + return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) @@ -140,17 +138,17 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) wg.Wait() // Collect errors - var errors []error + var errs []error for i := 0; i < len(cherr); i += 1 { err := <-cherr ctx.WithError(err).Error("POST Failed") - errors = append(errors, err) + errs = append(errs, err) } // Check responses if len(chresp) > 1 { - ctx.WithField("response_count", len(chresp)).Error("Received Too many positive answers") - return core.Packet{}, fmt.Errorf("Several positive answer from servers") + ctx.WithField("response_count", len(chresp)).Error("Received too many positive answers") + return core.Packet{}, errors.NewFailure(ErrUnexpectedResponse, "Received too many positive answers") } // Get packet @@ -158,8 +156,8 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) case packet := <-chresp: return packet, nil default: - if errors != nil { - return core.Packet{}, fmt.Errorf("Errors: %v", errors) + if errs != nil { + return core.Packet{}, errors.NewFailure(ErrUnableToSend, fmt.Sprintf("%+v", errs)) } ctx.Error("No response packet available") return core.Packet{}, fmt.Errorf("No response packet available") @@ -182,7 +180,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { // // See broadcast and pubsub adapters for mechanisms to handle registrations. func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, ErrNotImplemented + return core.Packet{}, nil, errors.NewFailure(ErrNotSupported, "NextRegistration not supported for http adapter") } // listenRequests handles incoming registration request sent through http to the adapter diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 195ed3231..6da15f895 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -15,6 +15,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -25,7 +27,7 @@ func TestSend(t *testing.T) { tests := []struct { Packet core.Packet WantPayload string - WantError error + WantError *string }{ { genCorePacket(), @@ -35,7 +37,7 @@ func TestSend(t *testing.T) { { core.Packet{}, "", - ErrInvalidPacket, + pointer.String(ErrInvalidPacket), }, } @@ -65,7 +67,7 @@ func TestNext(t *testing.T) { IsNotFound bool WantPacket core.Packet WantStatus int - WantError error + WantError *string }{ { Payload: genJSONPayload(genCorePacket()), @@ -142,12 +144,13 @@ func TestNext(t *testing.T) { } // Check utilities -func checkErrors(t *testing.T, want error, got error) { - if want == got { +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { Ok(t, "Check errors") return } - Ko(t, "Expected error to be {%v} but got {%v}", want, got) + + Ko(t, "Expected error to be %s but got %v", want, got) } func checkSend(t *testing.T, want string, s MockServer) { diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 18331a8ab..acc14ce70 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -15,6 +15,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -28,14 +30,10 @@ type Adapter struct { registrations chan core.Registration // Communication channel responsible for registration management } -var ErrBadOptions = fmt.Errorf("Bad options provided") -var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") -var ErrSeveralPositiveAnswers = fmt.Errorf("Several positive response for a given packet") - // NewAdapter promotes an existing basic adapter to a broadcast adapter using a list a of known recipient func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { - return nil, ErrBadOptions + return nil, errors.NewFailure(ErrInvalidParam, "Must give at least one recipient") } return &Adapter{ @@ -52,12 +50,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) a.ctx.Debug("No recipient provided. The packet will be broadcast") return a.broadcast(p) } - - packet, err := a.Adapter.Send(p, r...) - if err == httpadapter.ErrInvalidPacket { - return core.Packet{}, ErrInvalidPacket - } - return packet, err + return a.Adapter.Send(p, r...) } // broadcast is merely a send where recipients are the predefined list used at instantiation time. @@ -66,17 +59,17 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - return core.Packet{}, ErrInvalidPacket + return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) } pl, err := p.Payload.MarshalBinary() if err != nil { - return core.Packet{}, ErrInvalidPacket + return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) devAddr, err := p.DevAddr() if err != nil { - return core.Packet{}, ErrInvalidPacket + return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) } // Prepare ground for parrallel http request @@ -142,16 +135,16 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { wg.Wait() close(cherr) close(register) - var errors []error + var errs []error for err := range cherr { - errors = append(errors, err) + errs = append(errs, err) } - if errors != nil { - return core.Packet{}, fmt.Errorf("Errors: %v", errors) + if errs != nil { + return core.Packet{}, errors.NewFailure(ErrUnableToSend, fmt.Sprintf("%+v", errs)) } if len(chresp) > 1 { // NOTE We consider several positive responses as an error - return core.Packet{}, ErrSeveralPositiveAnswers + return core.Packet{}, errors.NewFailure(ErrUnexpectedResponse, "Received too many positive answers") } for recipient := range register { @@ -162,7 +155,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { case packet := <-chresp: return packet, nil default: - return core.Packet{}, fmt.Errorf("Unexpected error. No response packet available") + return core.Packet{}, errors.NewFailure(ErrUnexpectedResponse, "No response packet available") } } diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index 3e9172214..8fdd3c6b5 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -14,6 +14,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -39,7 +41,7 @@ func TestSend(t *testing.T) { Packet core.Packet WantRegistrations []core.Registration WantPayload string - WantError error + WantError *string }{ { // Send to recipient a valid packet Recipients: recipients[1:2], // TODO test with a rejection. Need better error handling @@ -60,14 +62,14 @@ func TestSend(t *testing.T) { Packet: core.Packet{}, WantRegistrations: []core.Registration{}, WantPayload: "", - WantError: ErrInvalidPacket, + WantError: pointer.String(ErrInvalidPacket), }, { // Broadcast an invalid packet Recipients: []core.Recipient{}, Packet: core.Packet{}, WantRegistrations: []core.Registration{}, WantPayload: "", - WantError: ErrInvalidPacket, + WantError: pointer.String(ErrInvalidPacket), }, } @@ -229,13 +231,13 @@ func genSample() (core.Packet, lorawan.DevAddr, string) { } // Check utilities - -func checkErrors(t *testing.T, want error, got error) { - if want == got { +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { Ok(t, "Check errors") return } - Ko(t, "Expected error %v but got %v", want, got) + + Ko(t, "Expected error to be %s but got %v", want, got) } func checkRegistrations(t *testing.T, want []core.Registration, got []core.Registration) { diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go index db65e5e07..adec3cdcf 100644 --- a/core/adapters/http/packet_acknacker.go +++ b/core/adapters/http/packet_acknacker.go @@ -5,16 +5,14 @@ package http import ( "encoding/json" - "fmt" "net/http" "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" ) -var ErrConnectionLost = fmt.Errorf("Connection has been lost") -var ErrInvalidArguments = fmt.Errorf("Invalid arguments supplied") - // packetAckNacker implements the AckNacker interface type packetAckNacker struct { response chan pktRes // A channel dedicated to send back a response @@ -30,14 +28,14 @@ func (an packetAckNacker) Ack(p *core.Packet) error { raw, err := json.Marshal(*p) if err != nil { - return err + return errors.NewFailure(ErrInvalidPacket, err) } select { case an.response <- pktRes{statusCode: http.StatusOK, content: raw}: return nil case <-time.After(time.Millisecond * 50): - return ErrConnectionLost + return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") } } @@ -49,7 +47,7 @@ func (an packetAckNacker) Nack() error { content: []byte(`{"message":"Not in charge of the associated device"}`), }: case <-time.After(time.Millisecond * 50): - return ErrConnectionLost + return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") } return nil } diff --git a/core/adapters/http/parser/packet.go b/core/adapters/http/parser/packet.go index e8c16e6ea..ef643a024 100644 --- a/core/adapters/http/parser/packet.go +++ b/core/adapters/http/parser/packet.go @@ -5,11 +5,12 @@ package parser import ( "encoding/json" - "fmt" "io" "net/http" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" ) // Parser gives a flexible way of parsing a request into a packet. @@ -26,18 +27,18 @@ type JSON struct{} func (p JSON) Parse(req *http.Request) (core.Packet, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return core.Packet{}, fmt.Errorf("Received invalid content-type in request") + return core.Packet{}, errors.NewFailure(ErrInvalidRequest, "Received invalid content-type in request") } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return core.Packet{}, err + return core.Packet{}, errors.NewFailure(ErrInvalidRequest, err) } packet := new(core.Packet) if err := json.Unmarshal(body[:n], packet); err != nil { - return core.Packet{}, err + return core.Packet{}, errors.NewFailure(ErrInvalidRequest, err) } return *packet, nil diff --git a/core/adapters/http/parser/registration.go b/core/adapters/http/parser/registration.go index 8785256d6..94630dafe 100644 --- a/core/adapters/http/parser/registration.go +++ b/core/adapters/http/parser/registration.go @@ -6,13 +6,14 @@ package parser import ( "encoding/hex" "encoding/json" - "fmt" "io" "net/http" "regexp" "strings" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -37,25 +38,25 @@ type PubSub struct{} func (p PubSub) Parse(req *http.Request) (core.Registration, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return core.Registration{}, fmt.Errorf("Received invalid content-type in request") + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Received invalid content-type in request") } // Check the query parameter reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{8})$") query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { - return core.Registration{}, fmt.Errorf("Incorrect end-device address format") + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect end-device address format") } devAddr, err := hex.DecodeString(query[1]) if err != nil { - return core.Registration{}, err + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, err) } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return core.Registration{}, err + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, err) } params := &struct { Id string `json:"app_id"` @@ -63,21 +64,21 @@ func (p PubSub) Parse(req *http.Request) (core.Registration, error) { NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { - return core.Registration{}, err + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, err) } nwkSKey, err := hex.DecodeString(params.NwkSKey) if err != nil || len(nwkSKey) != 16 { - return core.Registration{}, fmt.Errorf("Incorrect network session key") + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect network session key") } params.Id = strings.Trim(params.Id, " ") params.Url = strings.Trim(params.Url, " ") if len(params.Id) <= 0 { - return core.Registration{}, fmt.Errorf("Incorrect application id") + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect application id") } if len(params.Url) <= 0 { - return core.Registration{}, fmt.Errorf("Incorrect application url") + return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect application url") } // Create registration diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go index 104aed43c..619603df3 100644 --- a/core/adapters/http/pubsub/pubsub_test.go +++ b/core/adapters/http/pubsub/pubsub_test.go @@ -14,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -26,7 +27,7 @@ func TestNextRegistration(t *testing.T) { DevAddr string NwkSKey string WantResult *core.Registration - WantError error + WantError *string }{ // Valid device address { @@ -108,12 +109,13 @@ func TestNextRegistration(t *testing.T) { } } -func checkErrors(t *testing.T, want error, got error) { - if want == got { +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { Ok(t, "Check errors") return } - Ko(t, "Expected error to be {%v} but got {%v}", want, got) + + Ko(t, "Expected error to be %s but got %v", want, got) } func checkRegistrationResult(t *testing.T, want, got *core.Registration) { diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 1dc0c8f17..fd90fe36e 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -4,15 +4,14 @@ package pubsub import ( - "fmt" "net/http" "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" ) -var ErrConnectionLost = fmt.Errorf("Connection has been lost") - type regAckNacker struct { response chan regRes // A channel dedicated to send back a response } @@ -23,7 +22,7 @@ func (r regAckNacker) Ack(p *core.Packet) error { case r.response <- regRes{statusCode: http.StatusAccepted}: return nil case <-time.After(time.Millisecond * 50): - return ErrConnectionLost + return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") } } @@ -36,6 +35,6 @@ func (r regAckNacker) Nack() error { }: return nil case <-time.After(time.Millisecond * 50): - return ErrConnectionLost + return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") } } diff --git a/core/errors/errors.go b/core/errors/errors.go index d9f82e7f1..0a120c07e 100644 --- a/core/errors/errors.go +++ b/core/errors/errors.go @@ -4,9 +4,13 @@ package core const ( - ErrInvalidPacket = "Invalid Packet" - ErrInvalidMetadata = "Invalid Metadata" - ErrInvalidParam = "Invalid Parameter" - ErrInvalidRecipient = "Invalid Recipient" - ErrNotSupported = "Unsupported Operation" + ErrInvalidPacket = "Invalid Packet" + ErrInvalidMetadata = "Invalid Metadata" + ErrInvalidParam = "Invalid Parameter" + ErrInvalidRecipient = "Invalid Recipient" + ErrInvalidRequest = "Invalid Request" + ErrNotSupported = "Unsupported Operation" + ErrUnableToSend = "Unable to Send" + ErrUnexpectedResponse = "Unexpected Response" + ErrConnectionLost = "Connection Lost" ) From 7372fd01b363459984f36716c1c6673a07eb6f21 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 19:24:29 +0100 Subject: [PATCH 0576/2266] [refactor.errors] Reduce the amount of different errors --- core/adapters/http/adapter.go | 10 +++--- core/adapters/http/adapter_test.go | 2 +- core/adapters/http/broadcast/broadcast.go | 14 ++++---- .../adapters/http/broadcast/broadcast_test.go | 4 +-- core/adapters/http/packet_acknacker.go | 6 ++-- core/adapters/http/parser/packet.go | 6 ++-- core/adapters/http/parser/registration.go | 16 +++++----- core/adapters/http/pubsub/reg_acknacker.go | 4 +-- core/adapters/semtech/adapter.go | 4 +-- core/adapters/semtech/adapter_test.go | 2 +- core/adapters/semtech/semtech_acknacker.go | 4 +-- core/errors/errors.go | 14 +++----- core/metadata.go | 6 ++-- core/packet.go | 32 +++++++++---------- core/packet_test.go | 4 +-- 15 files changed, 62 insertions(+), 66 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index e6222606f..de3ae5143 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -63,12 +63,12 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) m, err := json.Marshal(p.Metadata) if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } pl, err := p.Payload.MarshalBinary() if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) @@ -148,7 +148,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Check responses if len(chresp) > 1 { ctx.WithField("response_count", len(chresp)).Error("Received too many positive answers") - return core.Packet{}, errors.NewFailure(ErrUnexpectedResponse, "Received too many positive answers") + return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "Received too many positive answers") } // Get packet @@ -157,10 +157,10 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) return packet, nil default: if errs != nil { - return core.Packet{}, errors.NewFailure(ErrUnableToSend, fmt.Sprintf("%+v", errs)) + return core.Packet{}, errors.NewFailure(ErrFailedOperation, fmt.Sprintf("%+v", errs)) } ctx.Error("No response packet available") - return core.Packet{}, fmt.Errorf("No response packet available") + return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "No response packet available") } } diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go index 6da15f895..d3b747acb 100644 --- a/core/adapters/http/adapter_test.go +++ b/core/adapters/http/adapter_test.go @@ -37,7 +37,7 @@ func TestSend(t *testing.T) { { core.Packet{}, "", - pointer.String(ErrInvalidPacket), + pointer.String(ErrInvalidStructure), }, } diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index acc14ce70..b4f76dd2c 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -33,7 +33,7 @@ type Adapter struct { // NewAdapter promotes an existing basic adapter to a broadcast adapter using a list a of known recipient func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { - return nil, errors.NewFailure(ErrInvalidParam, "Must give at least one recipient") + return nil, errors.NewFailure(ErrInvalidStructure, "Must give at least one recipient") } return &Adapter{ @@ -59,17 +59,17 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } pl, err := p.Payload.MarshalBinary() if err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) devAddr, err := p.DevAddr() if err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidPacket, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } // Prepare ground for parrallel http request @@ -140,11 +140,11 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { errs = append(errs, err) } if errs != nil { - return core.Packet{}, errors.NewFailure(ErrUnableToSend, fmt.Sprintf("%+v", errs)) + return core.Packet{}, errors.NewFailure(ErrFailedOperation, fmt.Sprintf("%+v", errs)) } if len(chresp) > 1 { // NOTE We consider several positive responses as an error - return core.Packet{}, errors.NewFailure(ErrUnexpectedResponse, "Received too many positive answers") + return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "Received too many positive answers") } for recipient := range register { @@ -155,7 +155,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { case packet := <-chresp: return packet, nil default: - return core.Packet{}, errors.NewFailure(ErrUnexpectedResponse, "No response packet available") + return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "No response packet available") } } diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go index 8fdd3c6b5..247f0cb4d 100644 --- a/core/adapters/http/broadcast/broadcast_test.go +++ b/core/adapters/http/broadcast/broadcast_test.go @@ -62,14 +62,14 @@ func TestSend(t *testing.T) { Packet: core.Packet{}, WantRegistrations: []core.Registration{}, WantPayload: "", - WantError: pointer.String(ErrInvalidPacket), + WantError: pointer.String(ErrInvalidStructure), }, { // Broadcast an invalid packet Recipients: []core.Recipient{}, Packet: core.Packet{}, WantRegistrations: []core.Registration{}, WantPayload: "", - WantError: pointer.String(ErrInvalidPacket), + WantError: pointer.String(ErrInvalidStructure), }, } diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go index adec3cdcf..4de4309c5 100644 --- a/core/adapters/http/packet_acknacker.go +++ b/core/adapters/http/packet_acknacker.go @@ -28,14 +28,14 @@ func (an packetAckNacker) Ack(p *core.Packet) error { raw, err := json.Marshal(*p) if err != nil { - return errors.NewFailure(ErrInvalidPacket, err) + return errors.NewFailure(ErrInvalidStructure, err) } select { case an.response <- pktRes{statusCode: http.StatusOK, content: raw}: return nil case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") + return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") } } @@ -47,7 +47,7 @@ func (an packetAckNacker) Nack() error { content: []byte(`{"message":"Not in charge of the associated device"}`), }: case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") + return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") } return nil } diff --git a/core/adapters/http/parser/packet.go b/core/adapters/http/parser/packet.go index ef643a024..577d3b5c6 100644 --- a/core/adapters/http/parser/packet.go +++ b/core/adapters/http/parser/packet.go @@ -27,18 +27,18 @@ type JSON struct{} func (p JSON) Parse(req *http.Request) (core.Packet, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return core.Packet{}, errors.NewFailure(ErrInvalidRequest, "Received invalid content-type in request") + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, "Received invalid content-type in request") } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return core.Packet{}, errors.NewFailure(ErrInvalidRequest, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } packet := new(core.Packet) if err := json.Unmarshal(body[:n], packet); err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidRequest, err) + return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) } return *packet, nil diff --git a/core/adapters/http/parser/registration.go b/core/adapters/http/parser/registration.go index 94630dafe..fec2c4bdb 100644 --- a/core/adapters/http/parser/registration.go +++ b/core/adapters/http/parser/registration.go @@ -38,25 +38,25 @@ type PubSub struct{} func (p PubSub) Parse(req *http.Request) (core.Registration, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Received invalid content-type in request") + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Received invalid content-type in request") } // Check the query parameter reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{8})$") query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect end-device address format") + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect end-device address format") } devAddr, err := hex.DecodeString(query[1]) if err != nil { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, err) + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, err) } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, err) + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, err) } params := &struct { Id string `json:"app_id"` @@ -64,21 +64,21 @@ func (p PubSub) Parse(req *http.Request) (core.Registration, error) { NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, err) + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, err) } nwkSKey, err := hex.DecodeString(params.NwkSKey) if err != nil || len(nwkSKey) != 16 { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect network session key") + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect network session key") } params.Id = strings.Trim(params.Id, " ") params.Url = strings.Trim(params.Url, " ") if len(params.Id) <= 0 { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect application id") + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect application id") } if len(params.Url) <= 0 { - return core.Registration{}, errors.NewFailure(ErrInvalidRequest, "Incorrect application url") + return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect application url") } // Create registration diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index fd90fe36e..03efdebc8 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -22,7 +22,7 @@ func (r regAckNacker) Ack(p *core.Packet) error { case r.response <- regRes{statusCode: http.StatusAccepted}: return nil case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") + return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") } } @@ -35,6 +35,6 @@ func (r regAckNacker) Nack() error { }: return nil case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrConnectionLost, "No response was given to the acknacker") + return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") } } diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 67be50828..90fc2c09b 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -50,7 +50,7 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a.ctx.WithField("port", port).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.NewFailure(ErrInvalidParam, fmt.Sprintf("Invalid port %v", port)) + return nil, errors.NewFailure(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) } go a.monitorConnection() @@ -71,7 +71,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { a.ctx.Debug("Received invalid packet") - return core.Packet{}, nil, errors.NewFailure(ErrInvalidPacket, err) + return core.Packet{}, nil, errors.NewFailure(ErrInvalidStructure, err) } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil } diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go index 11a733d97..7ff520424 100644 --- a/core/adapters/semtech/adapter_test.go +++ b/core/adapters/semtech/adapter_test.go @@ -74,7 +74,7 @@ func TestNext(t *testing.T) { Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), WantAck: genPUSH_ACK([]byte{0x22, 0x35}), WantNext: core.Packet{}, - WantError: pointer.String(ErrInvalidPacket), + WantError: pointer.String(ErrInvalidStructure), }, } diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index ec005069f..fa3c5cc8b 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -28,7 +28,7 @@ func (an semtechAckNacker) Ack(p *core.Packet) error { // For the downlink, we have to send a PULL_RESP packet which hold a TXPK txpk, err := core.ConvertToTXPK(*p) if err != nil { - return errors.NewFailure(ErrInvalidPacket, err) + return errors.NewFailure(ErrInvalidStructure, err) } raw, err := semtech.Packet{ @@ -39,7 +39,7 @@ func (an semtechAckNacker) Ack(p *core.Packet) error { addr, ok := an.recipient.Address.(*net.UDPAddr) if !ok { - return errors.NewFailure(ErrInvalidRecipient, fmt.Sprintf("Expected UDPAddr but got: %v", an.recipient.Address)) + return errors.NewFailure(ErrInvalidStructure, fmt.Sprintf("Expected UDPAddr but got: %v", an.recipient.Address)) } an.conn <- udpMsg{raw: raw, addr: addr} return nil diff --git a/core/errors/errors.go b/core/errors/errors.go index 0a120c07e..49a09f104 100644 --- a/core/errors/errors.go +++ b/core/errors/errors.go @@ -4,13 +4,9 @@ package core const ( - ErrInvalidPacket = "Invalid Packet" - ErrInvalidMetadata = "Invalid Metadata" - ErrInvalidParam = "Invalid Parameter" - ErrInvalidRecipient = "Invalid Recipient" - ErrInvalidRequest = "Invalid Request" - ErrNotSupported = "Unsupported Operation" - ErrUnableToSend = "Unable to Send" - ErrUnexpectedResponse = "Unexpected Response" - ErrConnectionLost = "Connection Lost" + ErrInvalidStructure = "Invalid Structure" + ErrNotSupported = "Unsupported Operation" + ErrNotFound = "Entity Not Found" + ErrWrongBehavior = "Unexpected Behavior" + ErrFailedOperation = "Unsuccessful Operation" ) diff --git a/core/metadata.go b/core/metadata.go index 0c197fbef..4bfccfeb5 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -46,7 +46,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { }) if err != nil { - err = errors.NewFailure(ErrInvalidMetadata, err) + err = errors.NewFailure(ErrInvalidStructure, err) } return data, err @@ -55,12 +55,12 @@ func (m Metadata) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { if m == nil { - return errors.NewFailure(ErrInvalidMetadata, "Cannot unmarshal nil Metadata") + return errors.NewFailure(ErrInvalidStructure, "Cannot unmarshal nil Metadata") } proxy := metadataProxy{} if err := json.Unmarshal(raw, &proxy); err != nil { - return errors.NewFailure(ErrInvalidMetadata, err) + return errors.NewFailure(ErrInvalidStructure, err) } *m = Metadata(proxy.metadata) if proxy.Time != nil { diff --git a/core/packet.go b/core/packet.go index 41bd2677e..f49ec3eb3 100644 --- a/core/packet.go +++ b/core/packet.go @@ -20,12 +20,12 @@ import ( // DevAddr returns a lorawan device address associated to the packet if any func (p Packet) DevAddr() (lorawan.DevAddr, error) { if p.Payload.MACPayload == nil { - return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidPacket, "MACPAyload should not be empty") + return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidStructure, "MACPAyload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidPacket, "Packet does not carry a MACPayload") + return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidStructure, "Packet does not carry a MACPayload") } return macpayload.FHDR.DevAddr, nil @@ -34,12 +34,12 @@ func (p Packet) DevAddr() (lorawan.DevAddr, error) { // FCnt returns the frame counter of the given packet if any func (p Packet) Fcnt() (uint32, error) { if p.Payload.MACPayload == nil { - return 0, errors.NewFailure(ErrInvalidPacket, "MACPayload should not be empty") + return 0, errors.NewFailure(ErrInvalidStructure, "MACPayload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return 0, errors.NewFailure(ErrInvalidPacket, "Packet does not carry a MACPayload") + return 0, errors.NewFailure(ErrInvalidStructure, "Packet does not carry a MACPayload") } return macpayload.FHDR.FCnt, nil @@ -59,7 +59,7 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { // First, we have to get the physical payload which is encoded in the Data field packet := Packet{} if p.Data == nil { - return packet, errors.NewFailure(ErrInvalidPacket, "There's no data in the packet") + return packet, errors.NewFailure(ErrInvalidStructure, "There's no data in the packet") } // RXPK Data are base64 encoded, yet without the trailing "==" if any..... @@ -73,12 +73,12 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { - return packet, errors.NewFailure(ErrInvalidPacket, err) + return packet, errors.NewFailure(ErrInvalidStructure, err) } payload := lorawan.NewPHYPayload(true) if err = payload.UnmarshalBinary(raw); err != nil { - return packet, errors.NewFailure(ErrInvalidPacket, err) + return packet, errors.NewFailure(ErrInvalidStructure, err) } // Then, we interpret every other known field as a metadata and store them into an appropriate @@ -104,7 +104,7 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { // Step 1, convert the physical payload to a base64 string (without the padding) raw, err := p.Payload.MarshalBinary() if err != nil { - return semtech.TXPK{}, errors.NewFailure(ErrInvalidPacket, err) + return semtech.TXPK{}, errors.NewFailure(ErrInvalidStructure, err) } data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") txpk := semtech.TXPK{Data: pointer.String(data)} @@ -128,11 +128,11 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { func (p Packet) MarshalJSON() ([]byte, error) { rawMetadata, err := json.Marshal(p.Metadata) if err != nil { - return nil, errors.NewFailure(ErrInvalidPacket, err) + return nil, errors.NewFailure(ErrInvalidStructure, err) } rawPayload, err := p.Payload.MarshalBinary() if err != nil { - return nil, errors.NewFailure(ErrInvalidPacket, err) + return nil, errors.NewFailure(ErrInvalidStructure, err) } strPayload := base64.StdEncoding.EncodeToString(rawPayload) return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil @@ -141,7 +141,7 @@ func (p Packet) MarshalJSON() ([]byte, error) { // UnmarshalJSON impements the json.Marshaler interface func (p *Packet) UnmarshalJSON(raw []byte) error { if p == nil { - return errors.NewFailure(ErrInvalidPacket, "Cannot unmarshal a nil packet") + return errors.NewFailure(ErrInvalidStructure, "Cannot unmarshal a nil packet") } // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink @@ -154,17 +154,17 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { err := json.Unmarshal(raw, &proxy) if err != nil { - return errors.NewFailure(ErrInvalidPacket, err) + return errors.NewFailure(ErrInvalidStructure, err) } rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) if err != nil { - return errors.NewFailure(ErrInvalidPacket, err) + return errors.NewFailure(ErrInvalidStructure, err) } payload := lorawan.NewPHYPayload(true) // true -> uplink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.NewFailure(ErrInvalidPacket, err) + return errors.NewFailure(ErrInvalidStructure, err) } // Now, we check the nature of the decoded payload @@ -178,7 +178,7 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { // We thus have to unmarshall properly payload = lorawan.NewPHYPayload(false) // false -> downlink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.NewFailure(ErrInvalidPacket, err) + return errors.NewFailure(ErrInvalidStructure, err) } case "JoinRequest": fallthrough @@ -191,7 +191,7 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { case "Proprietary": // Proprietary can be either downlink or uplink. Right now, we do not have any message of // that type and thus, we just don't know how to handle them. Let's throw an error. - return errors.NewFailure(ErrInvalidPacket, "Unsupported MType 'Proprietary'") + return errors.NewFailure(ErrInvalidStructure, "Unsupported MType 'Proprietary'") } // Packet = Payload + Metadata diff --git a/core/packet_test.go b/core/packet_test.go index 3556a804b..d7cf07ee2 100644 --- a/core/packet_test.go +++ b/core/packet_test.go @@ -18,7 +18,7 @@ func TestConvertRXPKPacket(t *testing.T) { tests := []convertRXPKTest{ genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(ErrInvalidPacket)}), + genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(ErrInvalidStructure)}), } for _, test := range tests { @@ -38,7 +38,7 @@ func TestConvertTXPKPacket(t *testing.T) { convertToTXPKTest{ CorePacket: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, TXPK: semtech.TXPK{}, - WantError: pointer.String(ErrInvalidPacket), + WantError: pointer.String(ErrInvalidStructure), }, } From 0d7e73f3f8a70c85bd5c2822b6649b1fbd8fcbd3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 17 Feb 2016 19:24:41 +0100 Subject: [PATCH 0577/2266] [refactor.errors] Refactor errors in component --- core/components/broker.go | 32 +++++++++++----------- core/components/broker_storage.go | 6 +++-- core/components/broker_storage_test.go | 2 +- core/components/broker_test.go | 2 +- core/components/errors.go | 19 -------------- core/components/handler.go | 24 ++++++++++------- core/components/handler_storage.go | 8 +++--- core/components/handler_storage_test.go | 14 +++++----- core/components/handler_test.go | 12 +++++---- core/components/router.go | 4 ++- core/components/router_storage.go | 17 +++++++----- core/components/router_storage_test.go | 11 ++++---- core/components/router_test.go | 2 +- core/components/storage.go | 35 ++++++++++++++++--------- 14 files changed, 99 insertions(+), 89 deletions(-) delete mode 100644 core/components/errors.go diff --git a/core/components/broker.go b/core/components/broker.go index dee01d0ba..dded6fe56 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -4,9 +4,9 @@ package components import ( - "fmt" - "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -33,19 +33,19 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter if err != nil { b.ctx.Warn("Uplink Invalid") an.Nack() - return ErrInvalidPacket + return errors.NewFailure(ErrInvalidStructure, err) } ctx := b.ctx.WithField("devAddr", devAddr) entries, err := b.db.Lookup(devAddr) - switch err { - case nil: - case ErrNotFound: - case ErrDeviceNotFound: - ctx.Warn("Uplink device not found") - return an.Nack() - default: - an.Nack() - return err + if err != nil { + switch err.(errors.Failure).Nature { + case ErrNotFound: + ctx.Warn("Uplink device not found") + return an.Nack() + default: + an.Nack() + return errors.NewFailure(ErrNotFound, err) + } } // 2. Several handler might be associated to the same device, we distinguish them using MIC @@ -74,14 +74,14 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter response, err := adapter.Send(p, *handler) if err != nil { an.Nack() - return err + return errors.NewFailure(ErrFailedOperation, err) } return an.Ack(&response) } // HandleDown implements the core.Component interface. Not implemented yet func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) error { - return fmt.Errorf("Not Implemented") + return errors.NewFailure(ErrNotSupported, "HandleDown not supported on broker") } // Register implements the core.Component interface @@ -95,14 +95,14 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { if !(okId && okUrl && okNwkSKey) { ctx.Warn("Invalid Registration") an.Nack() - return ErrInvalidRegistration + return errors.NewFailure(ErrInvalidStructure, "Invalid registration params") } entry := brokerEntry{Id: id, Url: url, NwkSKey: nwkSKey} if err := b.db.Store(r.DevAddr, entry); err != nil { ctx.WithError(err).Error("Failed Registration") an.Nack() - return err + return errors.NewFailure(ErrFailedOperation, err) } ctx.Debug("Successful Registration") diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 552a21696..3552f3f65 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -6,6 +6,8 @@ package components import ( "time" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) @@ -40,7 +42,7 @@ type brokerEntry struct { func NewBrokerStorage() (BrokerStorage, error) { db, err := bolt.Open("broker_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { - return nil, err + return nil, errors.NewFailure(ErrFailedOperation, err) } if err := initDB(db, "devices"); err != nil { @@ -86,7 +88,7 @@ func (entry brokerEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the entryStorage interface func (entry *brokerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 3 { - return ErrNotUnmarshable + return errors.NewFailure(ErrInvalidStructure, "invalid broker entry") } r := newEntryReadWriter(data) r.Read(func(data []byte) { entry.Id = string(data) }) diff --git a/core/components/broker_storage_test.go b/core/components/broker_storage_test.go index 52cca0704..1c9e6b322 100644 --- a/core/components/broker_storage_test.go +++ b/core/components/broker_storage_test.go @@ -42,7 +42,7 @@ func TestBrokerStorage(t *testing.T) { Store *brokerEntryShape Lookup lorawan.DevAddr WantEntries []brokerEntry - WantError []error + WantError []string }{ { Desc: "No entry | Store #0", diff --git a/core/components/broker_test.go b/core/components/broker_test.go index a8555db45..1bc470f2b 100644 --- a/core/components/broker_test.go +++ b/core/components/broker_test.go @@ -60,7 +60,7 @@ func TestBrokerHandleup(t *testing.T) { Packet packetShape WantRecipients []core.Recipient WantAck bool - WantError error + WantError *string }{ { Desc: "0 known | Send #0", diff --git a/core/components/errors.go b/core/components/errors.go deleted file mode 100644 index 19c91b822..000000000 --- a/core/components/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import "fmt" - -var ErrInvalidRegistration = fmt.Errorf("Invalid registration") -var ErrDeviceNotFound = fmt.Errorf("Device not found") // NOTE duplication with below -var ErrNotFound = fmt.Errorf("Requested entity not found") -var ErrInvalidPacket = fmt.Errorf("The given packet is invalid") -var ErrBadOptions = fmt.Errorf("Invalid supplied options") -var ErrNotInitialized = fmt.Errorf("Illegal operation call on non initialized component") -var ErrEntryExpired = fmt.Errorf("An entry exists but has expired") -var ErrNotImplemented = fmt.Errorf("Ilegal call on non implemented method") -var ErrAlreadyExists = fmt.Errorf("An entry already exists for that device") -var ErrAlreadyProcessed = fmt.Errorf("The packet reached the handler too late. It has already been processed.") -var ErrNotUnmarshable = fmt.Errorf("The given data structure isn't unmarshable") -var ErrStorageUnreachable = fmt.Errorf("Something went seriously wrong with the storage") diff --git a/core/components/handler.go b/core/components/handler.go index 0ee7d1704..616760b73 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -9,6 +9,8 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -61,7 +63,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { if !okId || !okOpts { an.Nack() - return ErrBadOptions + return errors.NewFailure(ErrInvalidStructure, "Invalid registration options") } err := h.db.Store(reg.DevAddr, handlerEntry{ @@ -73,11 +75,10 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { if err != nil { an.Nack() - return err + return errors.NewFailure(ErrFailedOperation, err) } - an.Ack(nil) - return nil + return an.Ack(nil) } // HandleUp implements the core.Component interface @@ -123,7 +124,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap case error: h.ctx.WithField("bundleId", id).WithError(resp.(error)).Debug("Received response. Sending Nack") an.Nack() - return resp.(error) + return errors.NewFailure(ErrFailedOperation, resp.(error)) default: h.ctx.WithField("bundleId", id).Debug("Received response. Sending ack") an.Ack(nil) @@ -133,7 +134,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap // HandleDown implements the core.Component interface. Not implemented yet. func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) (core.Packet, error) { - return core.Packet{}, ErrNotImplemented + return core.Packet{}, errors.NewFailure(ErrNotSupported, "HandleDown not supported on handler") } // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, @@ -159,9 +160,10 @@ browseBundles: // The handler assumes payloads encrypted with AppSKey only ! payload, ok := packet.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - ctx.WithError(ErrInvalidPacket).Debug("Unable to extract MACPayload") + err := errors.NewFailure(ErrInvalidStructure, "Unable to extract MACPayload") + ctx.WithError(err).Debug("Unable to extract MACPayload") for _, bundle := range bundles { - bundle.chresp <- ErrInvalidPacket + bundle.chresp <- err } continue browseBundles } @@ -169,7 +171,7 @@ browseBundles: if err := payload.DecryptFRMPayload(bundle.entry.AppSKey); err != nil { ctx.WithError(err).Debug("Unable to decrypt MAC Payload with given AppSKey") for _, bundle := range bundles { - bundle.chresp <- err + bundle.chresp <- errors.NewFailure(ErrInvalidStructure, err) } continue browseBundles } @@ -220,7 +222,9 @@ func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplink copy(pid[:], bundle.id[:12]) if processed[pid] == bundle.id { ctx.WithField("bundleId", bundle.id).Debug("Reject already processed bundle") - go func(bundle uplinkBundle) { bundle.chresp <- ErrAlreadyProcessed }(bundle) + go func(bundle uplinkBundle) { + bundle.chresp <- errors.NewFailure(ErrFailedOperation, "Already processed") + }(bundle) continue } diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 62dd14dc5..115f79e4e 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -7,6 +7,8 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) @@ -89,7 +91,7 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio // First, determine devAddr, mandatory devAddr, err := packet.DevAddr() if err != nil { - return nil, ErrInvalidPacket + return nil, errors.NewFailure(ErrInvalidStructure, err) } entries, err := s.Lookup(devAddr) @@ -125,7 +127,7 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio } if len(res) == 0 { - return nil, ErrNotFound + return nil, errors.NewFailure(ErrNotFound, "") } return res, nil @@ -154,7 +156,7 @@ func (entry handlerEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the storageEntry interface func (entry *handlerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 4 { - return ErrNotUnmarshable + return errors.NewFailure(ErrInvalidStructure, "Invalid handler entry") } r := newEntryReadWriter(data) r.Read(func(data []byte) { copy(entry.AppEUI[:], data) }) diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index 992556e29..ac37cefb6 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -9,6 +9,8 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -60,7 +62,7 @@ func TestStoragePartition(t *testing.T) { Desc string PacketsShape []handlerEntry WantPartitions []partitionShape - WantError error + WantError *string }{ { Desc: "1 packet -> 1 partition | 1 packet", @@ -72,7 +74,7 @@ func TestStoragePartition(t *testing.T) { Desc: "1 unknown packet -> error not found", PacketsShape: []handlerEntry{unknown}, WantPartitions: nil, - WantError: ErrNotFound, + WantError: pointer.String(ErrNotFound), }, { Desc: "2 packets | diff DevAddr & diff AppEUI -> 2 partitions | 1 packet", @@ -196,13 +198,13 @@ func genPacketsFromHandlerEntries(shapes []handlerEntry) []core.Packet { } // ----- CHECK utilities - -func checkErrors(t *testing.T, want error, got error) { - if want == got { +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { Ok(t, "Check errors") return } - Ko(t, "Expected error to be %v, but got %v", want, got) + + Ko(t, "Expected error to be %s but got %v", want, got) } func checkPartitions(t *testing.T, want []partitionShape, got []handlerPartition) { diff --git a/core/components/handler_test.go b/core/components/handler_test.go index 2a341b104..e6eaa30fe 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -9,6 +9,8 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -90,7 +92,7 @@ func TestHandleUp(t *testing.T) { WantNbAck int WantNbNack int WantPackets map[[12]byte][]string - WantErrors []error + WantErrors []string }{ { Desc: "1 packet", @@ -123,7 +125,7 @@ func TestHandleUp(t *testing.T) { }, WantNbAck: 1, WantNbNack: 1, - WantErrors: []error{ErrAlreadyProcessed}, + WantErrors: []string{ErrFailedOperation}, WantPackets: map[[12]byte][]string{ [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ packets[0].Data, @@ -206,7 +208,7 @@ func TestHandleUp(t *testing.T) { Schedule: []event{ event{time.Millisecond * 25, packets[5], nil}, }, - WantErrors: []error{ErrNotFound}, + WantErrors: []string{ErrNotFound}, WantNbNack: 1, WantPackets: map[[12]byte][]string{}, }, @@ -414,12 +416,12 @@ func (a chanAdapter) NextRegistration() (core.Registration, core.AckNacker, erro panic("Not Expected") } -func checkChErrors(t *testing.T, want []error, got chan interface{}) { +func checkChErrors(t *testing.T, want []string, got chan interface{}) { nb := 0 outer: for gotErr := range got { for _, wantErr := range want { - if wantErr == gotErr { + if got != nil && wantErr == gotErr.(errors.Failure).Nature { nb += 1 continue outer } diff --git a/core/components/router.go b/core/components/router.go index bf1bfd1e9..41ee32afd 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -7,6 +7,8 @@ import ( "fmt" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -49,7 +51,7 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt } entries, err := r.db.Lookup(devAddr) - if err != nil && err != ErrNotFound && err != ErrEntryExpired { + if err != nil && err.(errors.Failure).Nature != ErrNotFound { an.Nack() return err } diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 9608d9f94..7a436eb09 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -4,10 +4,13 @@ package components import ( + "fmt" "sync" "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) @@ -43,7 +46,7 @@ type routerEntry struct { func NewRouterStorage(delay time.Duration) (RouterStorage, error) { db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { - return nil, err + return nil, errors.NewFailure(ErrFailedOperation, err) } if err := initDB(db, "brokers"); err != nil { @@ -76,7 +79,7 @@ func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEn if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } - return routerEntry{}, ErrNotFound + return routerEntry{}, errors.NewFailure(ErrNotFound, fmt.Sprintf("device %v", devAddr)) } rentry := entries[0] @@ -85,7 +88,7 @@ func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEn if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } - return routerEntry{}, ErrEntryExpired + return routerEntry{}, errors.NewFailure(ErrNotFound, fmt.Sprintf("device %v [expired]", devAddr)) } return rentry, nil @@ -96,8 +99,8 @@ func (s *routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) er s.Lock() defer s.Unlock() _, err := s.lookup(devAddr, false) - if err != ErrNotFound && err != ErrEntryExpired { - return ErrAlreadyExists + if err != nil && err.(errors.Failure).Nature != ErrNotFound { + return errors.NewFailure(ErrFailedOperation, "Already exists") } entry.until = time.Now().Add(s.expiryDelay) return store(s.DB, "brokers", devAddr, &entry) @@ -136,7 +139,7 @@ func (entry routerEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the entryStorage interface func (entry *routerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 1 { - return ErrNotUnmarshable + return errors.NewFailure(ErrInvalidStructure, "invalid router entry") } r := newEntryReadWriter(data) @@ -153,7 +156,7 @@ func (entry *routerEntry) UnmarshalBinary(data []byte) error { err = entry.until.UnmarshalBinary(data) }) if err != nil { - return err + return errors.NewFailure(ErrInvalidStructure, err) } return r.Err() } diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index 2b7cdf190..398bc0244 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -9,6 +9,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -38,7 +39,7 @@ func TestStorageExpiration(t *testing.T) { WaitDelayL time.Duration Lookup lorawan.DevAddr WantEntry *routerEntry - WantError []error + WantError []string }{ { Desc: "No entry, Lookup address", @@ -47,7 +48,7 @@ func TestStorageExpiration(t *testing.T) { Lookup: devices[0], Store: nil, WantEntry: nil, - WantError: []error{ErrNotFound}, + WantError: []string{ErrNotFound}, }, { Desc: "No entry, Store and Lookup same", @@ -66,7 +67,7 @@ func TestStorageExpiration(t *testing.T) { WaitDelayL: time.Millisecond * 250, Lookup: devices[0], WantEntry: nil, - WantError: []error{ErrEntryExpired}, + WantError: []string{ErrNotFound}, }, { Desc: "One entry, store same, lookup same", @@ -77,7 +78,7 @@ func TestStorageExpiration(t *testing.T) { Store: &routerEntryShape{entries[1], devices[0]}, Lookup: devices[0], WantEntry: &entries[0], - WantError: []error{ErrAlreadyExists}, + WantError: []string{ErrFailedOperation}, }, { Desc: "One entry, store different, lookup newly stored", @@ -110,7 +111,7 @@ func TestStorageExpiration(t *testing.T) { WaitDelayL: time.Millisecond, Lookup: devices[0], WantEntry: nil, - WantError: []error{ErrEntryExpired}, + WantError: []string{ErrNotFound}, }, { Desc: "One entry, wait delay, store same, lookup same", diff --git a/core/components/router_test.go b/core/components/router_test.go index a8b65dc16..3410629b9 100644 --- a/core/components/router_test.go +++ b/core/components/router_test.go @@ -37,7 +37,7 @@ func TestRouterHandleUp(t *testing.T) { KnownRecipients map[[4]byte]core.Recipient Packet packetShape WantRecipients []core.Recipient - WantError error + WantError *string }{ { Desc: "0 known | Send #0", diff --git a/core/components/storage.go b/core/components/storage.go index 5ca0a112a..8e8d0390a 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -4,9 +4,12 @@ package components import ( + "fmt" "io" "reflect" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/boltdb/bolt" "github.com/brocaar/lorawan" ) @@ -31,21 +34,24 @@ func initDB(db *bolt.DB, bucketName string) error { func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { - return err + return errors.NewFailure(ErrInvalidStructure, err) } err = db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return ErrStorageUnreachable + return errors.NewFailure(ErrFailedOperation, "storage unreachable") } w := newEntryReadWriter(bucket.Get(devAddr[:])) w.Write(marshalled) data, err := w.Bytes() if err != nil { - return err + return errors.NewFailure(ErrInvalidStructure, err) + } + if err := bucket.Put(devAddr[:], data); err != nil { + return errors.NewFailure(ErrFailedOperation, err) } - return bucket.Put(devAddr[:], data) + return nil }) return err @@ -62,11 +68,11 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora err := db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return ErrStorageUnreachable + return errors.NewFailure(ErrFailedOperation, "storage unreachable") } rawEntry = bucket.Get(devAddr[:]) if rawEntry == nil { - return ErrNotFound + return errors.NewFailure(ErrNotFound, fmt.Sprintf("%+v", devAddr)) } return nil }) @@ -89,7 +95,7 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora if err == io.EOF { break } - return nil, err + return nil, errors.NewFailure(ErrFailedOperation, err) } } return entries.Interface(), nil @@ -100,9 +106,12 @@ func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { return db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return ErrStorageUnreachable + return errors.NewFailure(ErrFailedOperation, "storage unreachable") } - return bucket.Delete(devAddr[:]) + if err := bucket.Delete(devAddr[:]); err != nil { + return errors.NewFailure(ErrFailedOperation, err) + } + return nil }) } @@ -110,9 +119,11 @@ func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { func resetDB(db *bolt.DB, bucketName string) error { return db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { - return err + return errors.NewFailure(ErrFailedOperation, err) } - _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) - return err + if _, err := tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil { + return errors.NewFailure(ErrFailedOperation, err) + } + return nil }) } From f5174926e30970cdee99bd2052a458af48c8453c Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 09:49:53 +0100 Subject: [PATCH 0578/2266] [refactor.errors] Fix small issue introduced with refactor -> have to check when no error occurs --- core/components/router_storage.go | 2 +- core/components/router_storage_test.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index 7a436eb09..acee89871 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -99,7 +99,7 @@ func (s *routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) er s.Lock() defer s.Unlock() _, err := s.lookup(devAddr, false) - if err != nil && err.(errors.Failure).Nature != ErrNotFound { + if err == nil || err != nil && err.(errors.Failure).Nature != ErrNotFound { return errors.NewFailure(ErrFailedOperation, "Already exists") } entry.until = time.Now().Add(s.expiryDelay) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index 398bc0244..cc931b20b 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -23,6 +23,7 @@ func TestStorageExpiration(t *testing.T) { devices := []lorawan.DevAddr{ lorawan.DevAddr([4]byte{0, 0, 0, 1}), lorawan.DevAddr([4]byte{14, 15, 8, 42}), + lorawan.DevAddr([4]byte{14, 15, 8, 79}), } entries := []routerEntry{ @@ -73,10 +74,10 @@ func TestStorageExpiration(t *testing.T) { Desc: "One entry, store same, lookup same", ExpiryDelay: time.Minute, ExistingEntries: []routerEntryShape{ - {entries[0], devices[0]}, + {entries[0], devices[2]}, }, - Store: &routerEntryShape{entries[1], devices[0]}, - Lookup: devices[0], + Store: &routerEntryShape{entries[1], devices[2]}, + Lookup: devices[2], WantEntry: &entries[0], WantError: []string{ErrFailedOperation}, }, From 6b6973c5320794be7c515838fa94f209b3d36645 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 09:53:44 +0100 Subject: [PATCH 0579/2266] [refactor.errors] Rename NewFailure to New, we won't consider any other type of error --- core/adapters/http/adapter.go | 12 +++--- core/adapters/http/broadcast/broadcast.go | 14 +++---- core/adapters/http/packet_acknacker.go | 6 +-- core/adapters/http/parser/packet.go | 6 +-- core/adapters/http/parser/registration.go | 16 ++++---- core/adapters/http/pubsub/reg_acknacker.go | 4 +- core/adapters/semtech/adapter.go | 8 ++-- core/adapters/semtech/semtech_acknacker.go | 4 +- core/components/broker.go | 12 +++--- core/components/broker_storage.go | 4 +- core/components/handler.go | 14 +++---- core/components/handler_storage.go | 6 +-- core/components/router_storage.go | 12 +++--- core/components/storage.go | 22 +++++------ core/metadata.go | 6 +-- core/packet.go | 32 ++++++++-------- utils/errors/errors.go | 44 ++++------------------ 17 files changed, 97 insertions(+), 125 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index de3ae5143..447bd184c 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -63,12 +63,12 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) m, err := json.Marshal(p.Metadata) if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } pl, err := p.Payload.MarshalBinary() if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) @@ -148,7 +148,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Check responses if len(chresp) > 1 { ctx.WithField("response_count", len(chresp)).Error("Received too many positive answers") - return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "Received too many positive answers") + return core.Packet{}, errors.New(ErrWrongBehavior, "Received too many positive answers") } // Get packet @@ -157,10 +157,10 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) return packet, nil default: if errs != nil { - return core.Packet{}, errors.NewFailure(ErrFailedOperation, fmt.Sprintf("%+v", errs)) + return core.Packet{}, errors.New(ErrFailedOperation, fmt.Sprintf("%+v", errs)) } ctx.Error("No response packet available") - return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "No response packet available") + return core.Packet{}, errors.New(ErrWrongBehavior, "No response packet available") } } @@ -180,7 +180,7 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { // // See broadcast and pubsub adapters for mechanisms to handle registrations. func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, errors.NewFailure(ErrNotSupported, "NextRegistration not supported for http adapter") + return core.Packet{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported for http adapter") } // listenRequests handles incoming registration request sent through http to the adapter diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index b4f76dd2c..a9c274002 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -33,7 +33,7 @@ type Adapter struct { // NewAdapter promotes an existing basic adapter to a broadcast adapter using a list a of known recipient func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { if len(recipients) == 0 { - return nil, errors.NewFailure(ErrInvalidStructure, "Must give at least one recipient") + return nil, errors.New(ErrInvalidStructure, "Must give at least one recipient") } return &Adapter{ @@ -59,17 +59,17 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } pl, err := p.Payload.MarshalBinary() if err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) devAddr, err := p.DevAddr() if err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } // Prepare ground for parrallel http request @@ -140,11 +140,11 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { errs = append(errs, err) } if errs != nil { - return core.Packet{}, errors.NewFailure(ErrFailedOperation, fmt.Sprintf("%+v", errs)) + return core.Packet{}, errors.New(ErrFailedOperation, fmt.Sprintf("%+v", errs)) } if len(chresp) > 1 { // NOTE We consider several positive responses as an error - return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "Received too many positive answers") + return core.Packet{}, errors.New(ErrWrongBehavior, "Received too many positive answers") } for recipient := range register { @@ -155,7 +155,7 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { case packet := <-chresp: return packet, nil default: - return core.Packet{}, errors.NewFailure(ErrWrongBehavior, "No response packet available") + return core.Packet{}, errors.New(ErrWrongBehavior, "No response packet available") } } diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go index 4de4309c5..f3a7c97a6 100644 --- a/core/adapters/http/packet_acknacker.go +++ b/core/adapters/http/packet_acknacker.go @@ -28,14 +28,14 @@ func (an packetAckNacker) Ack(p *core.Packet) error { raw, err := json.Marshal(*p) if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } select { case an.response <- pktRes{statusCode: http.StatusOK, content: raw}: return nil case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") } } @@ -47,7 +47,7 @@ func (an packetAckNacker) Nack() error { content: []byte(`{"message":"Not in charge of the associated device"}`), }: case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") } return nil } diff --git a/core/adapters/http/parser/packet.go b/core/adapters/http/parser/packet.go index 577d3b5c6..fab9df804 100644 --- a/core/adapters/http/parser/packet.go +++ b/core/adapters/http/parser/packet.go @@ -27,18 +27,18 @@ type JSON struct{} func (p JSON) Parse(req *http.Request) (core.Packet, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, "Received invalid content-type in request") + return core.Packet{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } packet := new(core.Packet) if err := json.Unmarshal(body[:n], packet); err != nil { - return core.Packet{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, errors.New(ErrInvalidStructure, err) } return *packet, nil diff --git a/core/adapters/http/parser/registration.go b/core/adapters/http/parser/registration.go index fec2c4bdb..2c2a3e0b5 100644 --- a/core/adapters/http/parser/registration.go +++ b/core/adapters/http/parser/registration.go @@ -38,25 +38,25 @@ type PubSub struct{} func (p PubSub) Parse(req *http.Request) (core.Registration, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Received invalid content-type in request") + return core.Registration{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") } // Check the query parameter reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{8})$") query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect end-device address format") + return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect end-device address format") } devAddr, err := hex.DecodeString(query[1]) if err != nil { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Registration{}, errors.New(ErrInvalidStructure, err) } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Registration{}, errors.New(ErrInvalidStructure, err) } params := &struct { Id string `json:"app_id"` @@ -64,21 +64,21 @@ func (p PubSub) Parse(req *http.Request) (core.Registration, error) { NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, err) + return core.Registration{}, errors.New(ErrInvalidStructure, err) } nwkSKey, err := hex.DecodeString(params.NwkSKey) if err != nil || len(nwkSKey) != 16 { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect network session key") + return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect network session key") } params.Id = strings.Trim(params.Id, " ") params.Url = strings.Trim(params.Url, " ") if len(params.Id) <= 0 { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect application id") + return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect application id") } if len(params.Url) <= 0 { - return core.Registration{}, errors.NewFailure(ErrInvalidStructure, "Incorrect application url") + return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect application url") } // Create registration diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go index 03efdebc8..c554ea42e 100644 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ b/core/adapters/http/pubsub/reg_acknacker.go @@ -22,7 +22,7 @@ func (r regAckNacker) Ack(p *core.Packet) error { case r.response <- regRes{statusCode: http.StatusAccepted}: return nil case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") } } @@ -35,6 +35,6 @@ func (r regAckNacker) Nack() error { }: return nil case <-time.After(time.Millisecond * 50): - return errors.NewFailure(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") } } diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 90fc2c09b..c8dd9564e 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -50,7 +50,7 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a.ctx.WithField("port", port).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.NewFailure(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) + return nil, errors.New(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) } go a.monitorConnection() @@ -62,7 +62,7 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { // Send implements the core.Adapter interface. Not implemented for the semtech adapter. func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - return core.Packet{}, errors.NewFailure(ErrNotSupported, "Send not supported on semtech adapter") + return core.Packet{}, errors.New(ErrNotSupported, "Send not supported on semtech adapter") } // Next implements the core.Adapter interface @@ -71,14 +71,14 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { packet, err := core.ConvertRXPK(msg.rxpk) if err != nil { a.ctx.Debug("Received invalid packet") - return core.Packet{}, nil, errors.NewFailure(ErrInvalidStructure, err) + return core.Packet{}, nil, errors.New(ErrInvalidStructure, err) } return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil } // NextRegistration implements the core.Adapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return core.Registration{}, nil, errors.NewFailure(ErrNotSupported, "NextRegistration not supported on semtech adapter") + return core.Registration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on semtech adapter") } // listen Handle incoming packets and forward them diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go index fa3c5cc8b..a02b254a2 100644 --- a/core/adapters/semtech/semtech_acknacker.go +++ b/core/adapters/semtech/semtech_acknacker.go @@ -28,7 +28,7 @@ func (an semtechAckNacker) Ack(p *core.Packet) error { // For the downlink, we have to send a PULL_RESP packet which hold a TXPK txpk, err := core.ConvertToTXPK(*p) if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } raw, err := semtech.Packet{ @@ -39,7 +39,7 @@ func (an semtechAckNacker) Ack(p *core.Packet) error { addr, ok := an.recipient.Address.(*net.UDPAddr) if !ok { - return errors.NewFailure(ErrInvalidStructure, fmt.Sprintf("Expected UDPAddr but got: %v", an.recipient.Address)) + return errors.New(ErrInvalidStructure, fmt.Sprintf("Expected UDPAddr but got: %v", an.recipient.Address)) } an.conn <- udpMsg{raw: raw, addr: addr} return nil diff --git a/core/components/broker.go b/core/components/broker.go index dded6fe56..91ba20d28 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -33,7 +33,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter if err != nil { b.ctx.Warn("Uplink Invalid") an.Nack() - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } ctx := b.ctx.WithField("devAddr", devAddr) entries, err := b.db.Lookup(devAddr) @@ -44,7 +44,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter return an.Nack() default: an.Nack() - return errors.NewFailure(ErrNotFound, err) + return errors.New(ErrNotFound, err) } } @@ -74,14 +74,14 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter response, err := adapter.Send(p, *handler) if err != nil { an.Nack() - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } return an.Ack(&response) } // HandleDown implements the core.Component interface. Not implemented yet func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) error { - return errors.NewFailure(ErrNotSupported, "HandleDown not supported on broker") + return errors.New(ErrNotSupported, "HandleDown not supported on broker") } // Register implements the core.Component interface @@ -95,14 +95,14 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { if !(okId && okUrl && okNwkSKey) { ctx.Warn("Invalid Registration") an.Nack() - return errors.NewFailure(ErrInvalidStructure, "Invalid registration params") + return errors.New(ErrInvalidStructure, "Invalid registration params") } entry := brokerEntry{Id: id, Url: url, NwkSKey: nwkSKey} if err := b.db.Store(r.DevAddr, entry); err != nil { ctx.WithError(err).Error("Failed Registration") an.Nack() - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } ctx.Debug("Successful Registration") diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go index 3552f3f65..a150b00c3 100644 --- a/core/components/broker_storage.go +++ b/core/components/broker_storage.go @@ -42,7 +42,7 @@ type brokerEntry struct { func NewBrokerStorage() (BrokerStorage, error) { db, err := bolt.Open("broker_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { - return nil, errors.NewFailure(ErrFailedOperation, err) + return nil, errors.New(ErrFailedOperation, err) } if err := initDB(db, "devices"); err != nil { @@ -88,7 +88,7 @@ func (entry brokerEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the entryStorage interface func (entry *brokerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 3 { - return errors.NewFailure(ErrInvalidStructure, "invalid broker entry") + return errors.New(ErrInvalidStructure, "invalid broker entry") } r := newEntryReadWriter(data) r.Read(func(data []byte) { entry.Id = string(data) }) diff --git a/core/components/handler.go b/core/components/handler.go index 616760b73..e5c82898f 100644 --- a/core/components/handler.go +++ b/core/components/handler.go @@ -63,7 +63,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { if !okId || !okOpts { an.Nack() - return errors.NewFailure(ErrInvalidStructure, "Invalid registration options") + return errors.New(ErrInvalidStructure, "Invalid registration options") } err := h.db.Store(reg.DevAddr, handlerEntry{ @@ -75,7 +75,7 @@ func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { if err != nil { an.Nack() - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } return an.Ack(nil) @@ -124,7 +124,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap case error: h.ctx.WithField("bundleId", id).WithError(resp.(error)).Debug("Received response. Sending Nack") an.Nack() - return errors.NewFailure(ErrFailedOperation, resp.(error)) + return errors.New(ErrFailedOperation, resp.(error)) default: h.ctx.WithField("bundleId", id).Debug("Received response. Sending ack") an.Ack(nil) @@ -134,7 +134,7 @@ func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adap // HandleDown implements the core.Component interface. Not implemented yet. func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) (core.Packet, error) { - return core.Packet{}, errors.NewFailure(ErrNotSupported, "HandleDown not supported on handler") + return core.Packet{}, errors.New(ErrNotSupported, "HandleDown not supported on handler") } // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, @@ -160,7 +160,7 @@ browseBundles: // The handler assumes payloads encrypted with AppSKey only ! payload, ok := packet.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - err := errors.NewFailure(ErrInvalidStructure, "Unable to extract MACPayload") + err := errors.New(ErrInvalidStructure, "Unable to extract MACPayload") ctx.WithError(err).Debug("Unable to extract MACPayload") for _, bundle := range bundles { bundle.chresp <- err @@ -171,7 +171,7 @@ browseBundles: if err := payload.DecryptFRMPayload(bundle.entry.AppSKey); err != nil { ctx.WithError(err).Debug("Unable to decrypt MAC Payload with given AppSKey") for _, bundle := range bundles { - bundle.chresp <- errors.NewFailure(ErrInvalidStructure, err) + bundle.chresp <- errors.New(ErrInvalidStructure, err) } continue browseBundles } @@ -223,7 +223,7 @@ func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplink if processed[pid] == bundle.id { ctx.WithField("bundleId", bundle.id).Debug("Reject already processed bundle") go func(bundle uplinkBundle) { - bundle.chresp <- errors.NewFailure(ErrFailedOperation, "Already processed") + bundle.chresp <- errors.New(ErrFailedOperation, "Already processed") }(bundle) continue } diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 115f79e4e..07605d6ef 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -91,7 +91,7 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio // First, determine devAddr, mandatory devAddr, err := packet.DevAddr() if err != nil { - return nil, errors.NewFailure(ErrInvalidStructure, err) + return nil, errors.New(ErrInvalidStructure, err) } entries, err := s.Lookup(devAddr) @@ -127,7 +127,7 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio } if len(res) == 0 { - return nil, errors.NewFailure(ErrNotFound, "") + return nil, errors.New(ErrNotFound, "") } return res, nil @@ -156,7 +156,7 @@ func (entry handlerEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the storageEntry interface func (entry *handlerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 4 { - return errors.NewFailure(ErrInvalidStructure, "Invalid handler entry") + return errors.New(ErrInvalidStructure, "Invalid handler entry") } r := newEntryReadWriter(data) r.Read(func(data []byte) { copy(entry.AppEUI[:], data) }) diff --git a/core/components/router_storage.go b/core/components/router_storage.go index acee89871..c9b9b4885 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -46,7 +46,7 @@ type routerEntry struct { func NewRouterStorage(delay time.Duration) (RouterStorage, error) { db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) if err != nil { - return nil, errors.NewFailure(ErrFailedOperation, err) + return nil, errors.New(ErrFailedOperation, err) } if err := initDB(db, "brokers"); err != nil { @@ -79,7 +79,7 @@ func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEn if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } - return routerEntry{}, errors.NewFailure(ErrNotFound, fmt.Sprintf("device %v", devAddr)) + return routerEntry{}, errors.New(ErrNotFound, fmt.Sprintf("device %v", devAddr)) } rentry := entries[0] @@ -88,7 +88,7 @@ func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEn if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } - return routerEntry{}, errors.NewFailure(ErrNotFound, fmt.Sprintf("device %v [expired]", devAddr)) + return routerEntry{}, errors.New(ErrNotFound, fmt.Sprintf("device %v [expired]", devAddr)) } return rentry, nil @@ -100,7 +100,7 @@ func (s *routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) er defer s.Unlock() _, err := s.lookup(devAddr, false) if err == nil || err != nil && err.(errors.Failure).Nature != ErrNotFound { - return errors.NewFailure(ErrFailedOperation, "Already exists") + return errors.New(ErrFailedOperation, "Already exists") } entry.until = time.Now().Add(s.expiryDelay) return store(s.DB, "brokers", devAddr, &entry) @@ -139,7 +139,7 @@ func (entry routerEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the entryStorage interface func (entry *routerEntry) UnmarshalBinary(data []byte) error { if entry == nil || len(data) < 1 { - return errors.NewFailure(ErrInvalidStructure, "invalid router entry") + return errors.New(ErrInvalidStructure, "invalid router entry") } r := newEntryReadWriter(data) @@ -156,7 +156,7 @@ func (entry *routerEntry) UnmarshalBinary(data []byte) error { err = entry.until.UnmarshalBinary(data) }) if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } return r.Err() } diff --git a/core/components/storage.go b/core/components/storage.go index 8e8d0390a..7a997210d 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -34,22 +34,22 @@ func initDB(db *bolt.DB, bucketName string) error { func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { marshalled, err := entry.MarshalBinary() if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } err = db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.NewFailure(ErrFailedOperation, "storage unreachable") + return errors.New(ErrFailedOperation, "storage unreachable") } w := newEntryReadWriter(bucket.Get(devAddr[:])) w.Write(marshalled) data, err := w.Bytes() if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } if err := bucket.Put(devAddr[:], data); err != nil { - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } return nil }) @@ -68,11 +68,11 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora err := db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.NewFailure(ErrFailedOperation, "storage unreachable") + return errors.New(ErrFailedOperation, "storage unreachable") } rawEntry = bucket.Get(devAddr[:]) if rawEntry == nil { - return errors.NewFailure(ErrNotFound, fmt.Sprintf("%+v", devAddr)) + return errors.New(ErrNotFound, fmt.Sprintf("%+v", devAddr)) } return nil }) @@ -95,7 +95,7 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora if err == io.EOF { break } - return nil, errors.NewFailure(ErrFailedOperation, err) + return nil, errors.New(ErrFailedOperation, err) } } return entries.Interface(), nil @@ -106,10 +106,10 @@ func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { return db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.NewFailure(ErrFailedOperation, "storage unreachable") + return errors.New(ErrFailedOperation, "storage unreachable") } if err := bucket.Delete(devAddr[:]); err != nil { - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } return nil }) @@ -119,10 +119,10 @@ func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { func resetDB(db *bolt.DB, bucketName string) error { return db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } if _, err := tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil { - return errors.NewFailure(ErrFailedOperation, err) + return errors.New(ErrFailedOperation, err) } return nil }) diff --git a/core/metadata.go b/core/metadata.go index 4bfccfeb5..8f4234e9d 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -46,7 +46,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { }) if err != nil { - err = errors.NewFailure(ErrInvalidStructure, err) + err = errors.New(ErrInvalidStructure, err) } return data, err @@ -55,12 +55,12 @@ func (m Metadata) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { if m == nil { - return errors.NewFailure(ErrInvalidStructure, "Cannot unmarshal nil Metadata") + return errors.New(ErrInvalidStructure, "Cannot unmarshal nil Metadata") } proxy := metadataProxy{} if err := json.Unmarshal(raw, &proxy); err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } *m = Metadata(proxy.metadata) if proxy.Time != nil { diff --git a/core/packet.go b/core/packet.go index f49ec3eb3..9064b0af5 100644 --- a/core/packet.go +++ b/core/packet.go @@ -20,12 +20,12 @@ import ( // DevAddr returns a lorawan device address associated to the packet if any func (p Packet) DevAddr() (lorawan.DevAddr, error) { if p.Payload.MACPayload == nil { - return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidStructure, "MACPAyload should not be empty") + return lorawan.DevAddr{}, errors.New(ErrInvalidStructure, "MACPAyload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return lorawan.DevAddr{}, errors.NewFailure(ErrInvalidStructure, "Packet does not carry a MACPayload") + return lorawan.DevAddr{}, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") } return macpayload.FHDR.DevAddr, nil @@ -34,12 +34,12 @@ func (p Packet) DevAddr() (lorawan.DevAddr, error) { // FCnt returns the frame counter of the given packet if any func (p Packet) Fcnt() (uint32, error) { if p.Payload.MACPayload == nil { - return 0, errors.NewFailure(ErrInvalidStructure, "MACPayload should not be empty") + return 0, errors.New(ErrInvalidStructure, "MACPayload should not be empty") } macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) if !ok { - return 0, errors.NewFailure(ErrInvalidStructure, "Packet does not carry a MACPayload") + return 0, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") } return macpayload.FHDR.FCnt, nil @@ -59,7 +59,7 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { // First, we have to get the physical payload which is encoded in the Data field packet := Packet{} if p.Data == nil { - return packet, errors.NewFailure(ErrInvalidStructure, "There's no data in the packet") + return packet, errors.New(ErrInvalidStructure, "There's no data in the packet") } // RXPK Data are base64 encoded, yet without the trailing "==" if any..... @@ -73,12 +73,12 @@ func ConvertRXPK(p semtech.RXPK) (Packet, error) { raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { - return packet, errors.NewFailure(ErrInvalidStructure, err) + return packet, errors.New(ErrInvalidStructure, err) } payload := lorawan.NewPHYPayload(true) if err = payload.UnmarshalBinary(raw); err != nil { - return packet, errors.NewFailure(ErrInvalidStructure, err) + return packet, errors.New(ErrInvalidStructure, err) } // Then, we interpret every other known field as a metadata and store them into an appropriate @@ -104,7 +104,7 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { // Step 1, convert the physical payload to a base64 string (without the padding) raw, err := p.Payload.MarshalBinary() if err != nil { - return semtech.TXPK{}, errors.NewFailure(ErrInvalidStructure, err) + return semtech.TXPK{}, errors.New(ErrInvalidStructure, err) } data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") txpk := semtech.TXPK{Data: pointer.String(data)} @@ -128,11 +128,11 @@ func ConvertToTXPK(p Packet) (semtech.TXPK, error) { func (p Packet) MarshalJSON() ([]byte, error) { rawMetadata, err := json.Marshal(p.Metadata) if err != nil { - return nil, errors.NewFailure(ErrInvalidStructure, err) + return nil, errors.New(ErrInvalidStructure, err) } rawPayload, err := p.Payload.MarshalBinary() if err != nil { - return nil, errors.NewFailure(ErrInvalidStructure, err) + return nil, errors.New(ErrInvalidStructure, err) } strPayload := base64.StdEncoding.EncodeToString(rawPayload) return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil @@ -141,7 +141,7 @@ func (p Packet) MarshalJSON() ([]byte, error) { // UnmarshalJSON impements the json.Marshaler interface func (p *Packet) UnmarshalJSON(raw []byte) error { if p == nil { - return errors.NewFailure(ErrInvalidStructure, "Cannot unmarshal a nil packet") + return errors.New(ErrInvalidStructure, "Cannot unmarshal a nil packet") } // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink @@ -154,17 +154,17 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { err := json.Unmarshal(raw, &proxy) if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) if err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } payload := lorawan.NewPHYPayload(true) // true -> uplink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } // Now, we check the nature of the decoded payload @@ -178,7 +178,7 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { // We thus have to unmarshall properly payload = lorawan.NewPHYPayload(false) // false -> downlink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.NewFailure(ErrInvalidStructure, err) + return errors.New(ErrInvalidStructure, err) } case "JoinRequest": fallthrough @@ -191,7 +191,7 @@ func (p *Packet) UnmarshalJSON(raw []byte) error { case "Proprietary": // Proprietary can be either downlink or uplink. Right now, we do not have any message of // that type and thus, we just don't know how to handle them. Let's throw an error. - return errors.NewFailure(ErrInvalidStructure, "Unsupported MType 'Proprietary'") + return errors.New(ErrInvalidStructure, "Unsupported MType 'Proprietary'") } // Packet = Payload + Metadata diff --git a/utils/errors/errors.go b/utils/errors/errors.go index a0d541cce..ba1868ea3 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -9,26 +9,15 @@ import ( "time" ) -type base struct { +// Failure states for fault that occurs during a process. +type Failure struct { Nature string // Kind of error, used a comparator Timestamp time.Time // The moment the error was created -} - -// Failure states for fault that aren't recoverable by the system itself. -// They consist in unexpected behavior -type Failure struct { - base - Fault error // The source of the failure -} - -// Error states for fault that are explicitely created by the application in order to be handled -// elsewhere. They are recoverable and convey valuable pieces of information. -type Error struct { - base + Fault error // The source of the failure } // NewFailure creates a new Failure from a source error -func NewFailure(k string, src interface{}) Failure { +func New(k string, src interface{}) Failure { var fault error switch src.(type) { case string: @@ -40,11 +29,9 @@ func NewFailure(k string, src interface{}) Failure { } failure := Failure{ - base: base{ - Nature: k, - Timestamp: time.Now(), - }, - Fault: fault, + Nature: k, + Timestamp: time.Now(), + Fault: fault, } // Pop one level if we made a failure from a failure @@ -57,25 +44,10 @@ func NewFailure(k string, src interface{}) Failure { return failure } -// NewError creates a new Error -func NewError(k string) Error { - return Error{ - base: base{ - Nature: k, - Timestamp: time.Now(), - }, - } -} - // Error implements the error built-in interface func (err Failure) Error() string { if err.Fault == nil { - return err.base.Error() + return fmt.Sprintf("%s", err.Nature) } return fmt.Sprintf("%s: %s", err.Nature, err.Fault.Error()) } - -// Error implements the error built-in interface -func (err base) Error() string { - return fmt.Sprintf("%s", err.Nature) -} From 00618a3aebd47abf1945538650f663483a7620a7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 10:11:46 +0100 Subject: [PATCH 0580/2266] [refactor.errors] Change ErrNotFound to ErrWrongBehavior (same meaning) --- core/components/broker.go | 4 ++-- core/components/handler_storage.go | 2 +- core/components/handler_storage_test.go | 2 +- core/components/handler_test.go | 2 +- core/components/router.go | 6 +++--- core/components/router_storage.go | 6 +++--- core/components/router_storage_test.go | 6 +++--- core/components/storage.go | 2 +- core/errors/errors.go | 15 +++++++++++---- 9 files changed, 26 insertions(+), 19 deletions(-) diff --git a/core/components/broker.go b/core/components/broker.go index 91ba20d28..c8f78352a 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -39,12 +39,12 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter entries, err := b.db.Lookup(devAddr) if err != nil { switch err.(errors.Failure).Nature { - case ErrNotFound: + case ErrWrongBehavior: ctx.Warn("Uplink device not found") return an.Nack() default: an.Nack() - return errors.New(ErrNotFound, err) + return errors.New(ErrFailedOperation, err) } } diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go index 07605d6ef..0d49e5e3e 100644 --- a/core/components/handler_storage.go +++ b/core/components/handler_storage.go @@ -127,7 +127,7 @@ func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartitio } if len(res) == 0 { - return nil, errors.New(ErrNotFound, "") + return nil, errors.New(ErrWrongBehavior, "No partition found") } return res, nil diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go index ac37cefb6..ebb2508ea 100644 --- a/core/components/handler_storage_test.go +++ b/core/components/handler_storage_test.go @@ -74,7 +74,7 @@ func TestStoragePartition(t *testing.T) { Desc: "1 unknown packet -> error not found", PacketsShape: []handlerEntry{unknown}, WantPartitions: nil, - WantError: pointer.String(ErrNotFound), + WantError: pointer.String(ErrWrongBehavior), }, { Desc: "2 packets | diff DevAddr & diff AppEUI -> 2 partitions | 1 packet", diff --git a/core/components/handler_test.go b/core/components/handler_test.go index e6eaa30fe..44f2c5cdd 100644 --- a/core/components/handler_test.go +++ b/core/components/handler_test.go @@ -208,7 +208,7 @@ func TestHandleUp(t *testing.T) { Schedule: []event{ event{time.Millisecond * 25, packets[5], nil}, }, - WantErrors: []string{ErrNotFound}, + WantErrors: []string{ErrWrongBehavior}, WantNbNack: 1, WantPackets: map[[12]byte][]string{}, }, diff --git a/core/components/router.go b/core/components/router.go index 41ee32afd..3659b50c7 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -50,15 +50,15 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt return err } - entries, err := r.db.Lookup(devAddr) - if err != nil && err.(errors.Failure).Nature != ErrNotFound { + entry, err := r.db.Lookup(devAddr) + if err != nil && err.(errors.Failure).Nature != ErrWrongBehavior { an.Nack() return err } var response core.Packet if err == nil { - response, err = upAdapter.Send(p, entries.Recipient) + response, err = upAdapter.Send(p, entry.Recipient) } else { response, err = upAdapter.Send(p) } diff --git a/core/components/router_storage.go b/core/components/router_storage.go index c9b9b4885..801dac38f 100644 --- a/core/components/router_storage.go +++ b/core/components/router_storage.go @@ -79,7 +79,7 @@ func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEn if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } - return routerEntry{}, errors.New(ErrNotFound, fmt.Sprintf("device %v", devAddr)) + return routerEntry{}, errors.New(ErrWrongBehavior, fmt.Sprintf("Not Found %+v", devAddr)) } rentry := entries[0] @@ -88,7 +88,7 @@ func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEn if err := flush(s.DB, "brokers", devAddr); err != nil { return routerEntry{}, err } - return routerEntry{}, errors.New(ErrNotFound, fmt.Sprintf("device %v [expired]", devAddr)) + return routerEntry{}, errors.New(ErrWrongBehavior, fmt.Sprintf("Not Found %+v", devAddr)) } return rentry, nil @@ -99,7 +99,7 @@ func (s *routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) er s.Lock() defer s.Unlock() _, err := s.lookup(devAddr, false) - if err == nil || err != nil && err.(errors.Failure).Nature != ErrNotFound { + if err == nil || err != nil && err.(errors.Failure).Nature != ErrWrongBehavior { return errors.New(ErrFailedOperation, "Already exists") } entry.until = time.Now().Add(s.expiryDelay) diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go index cc931b20b..d3f86ea1e 100644 --- a/core/components/router_storage_test.go +++ b/core/components/router_storage_test.go @@ -49,7 +49,7 @@ func TestStorageExpiration(t *testing.T) { Lookup: devices[0], Store: nil, WantEntry: nil, - WantError: []string{ErrNotFound}, + WantError: []string{ErrWrongBehavior}, }, { Desc: "No entry, Store and Lookup same", @@ -68,7 +68,7 @@ func TestStorageExpiration(t *testing.T) { WaitDelayL: time.Millisecond * 250, Lookup: devices[0], WantEntry: nil, - WantError: []string{ErrNotFound}, + WantError: []string{ErrWrongBehavior}, }, { Desc: "One entry, store same, lookup same", @@ -112,7 +112,7 @@ func TestStorageExpiration(t *testing.T) { WaitDelayL: time.Millisecond, Lookup: devices[0], WantEntry: nil, - WantError: []string{ErrNotFound}, + WantError: []string{ErrWrongBehavior}, }, { Desc: "One entry, wait delay, store same, lookup same", diff --git a/core/components/storage.go b/core/components/storage.go index 7a997210d..e6bcc3556 100644 --- a/core/components/storage.go +++ b/core/components/storage.go @@ -72,7 +72,7 @@ func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape stora } rawEntry = bucket.Get(devAddr[:]) if rawEntry == nil { - return errors.New(ErrNotFound, fmt.Sprintf("%+v", devAddr)) + return errors.New(ErrWrongBehavior, fmt.Sprintf("Not found %+v", devAddr)) } return nil }) diff --git a/core/errors/errors.go b/core/errors/errors.go index 49a09f104..bd95fb8ac 100644 --- a/core/errors/errors.go +++ b/core/errors/errors.go @@ -4,9 +4,16 @@ package core const ( + // Unsuccessful operation due to an unexpected parameter or under-relying structure. + // Another call won't change a thing, the inputs are wrong anyway. ErrInvalidStructure = "Invalid Structure" - ErrNotSupported = "Unsupported Operation" - ErrNotFound = "Entity Not Found" - ErrWrongBehavior = "Unexpected Behavior" - ErrFailedOperation = "Unsuccessful Operation" + + // Attempt to access an unimplemented method or an unsupported operation. Fatal. + ErrNotSupported = "Unsupported Operation" + + // The operation went well though the result is unexpected and wrong. + ErrWrongBehavior = "Unexpected Behavior" + + // Something happend during the processing. Another attempt might success. + ErrFailedOperation = "Unsuccessful Operation" ) From fb844e4c0b41a4a5cac4657887518ace8779b742 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 14:37:23 +0100 Subject: [PATCH 0581/2266] [refactor] [simulator] Periodically add `stat` to forwarded packets --- simulators/gateway/forwarder.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 5586ca30f..5e4fb90b7 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -47,6 +47,8 @@ const ( cmd_STATS commandName = "Stats" ) +var statTimer <-chan time.Time + // NewForwarder create a forwarder instance bound to a set of routers. func NewForwarder(id [8]byte, ctx log.Interface, adapters ...io.ReadWriteCloser) (*Forwarder, error) { if len(adapters) == 0 { @@ -72,6 +74,8 @@ func NewForwarder(id [8]byte, ctx log.Interface, adapters ...io.ReadWriteCloser) go fwd.handleCommands() + statTimer = time.Tick(5 * time.Second) + // Star listening to each adapter Read() method for i, adapter := range fwd.adapters { go fwd.listenAdapter(adapter, i) @@ -170,6 +174,13 @@ func (fwd Forwarder) Forward(rxpks ...semtech.RXPK) error { Payload: &semtech.Payload{RXPK: rxpks}, } + select { + case <-statTimer: + stats := fwd.Stats() + packet.Payload.Stat = &stats + default: + } + raw, err := packet.MarshalBinary() if err != nil { return err From f113661326faa2967ef41a2e0c603dddff3d9cdb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 17:19:08 +0100 Subject: [PATCH 0582/2266] [refactor] Change structure of statuspage --- core/adapters/http/adapter.go | 6 +- core/adapters/http/broadcast/broadcast.go | 6 +- core/adapters/http/statuspage/statuspage.go | 63 ++++++++++----------- core/components/broker.go | 8 +-- core/components/router.go | 2 +- 5 files changed, 40 insertions(+), 45 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 607d39103..a07802d6b 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -61,7 +61,7 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { stats.MarkMeter("http_adapter.send") - stats.UpdateHistogram("http_adapter.send_recipients", int64(len(r))) + stats.UpdateHistogram("http_adapter.send.recipients", int64(len(r))) // Generate payload from core packet m, err := json.Marshal(p.Metadata) @@ -139,9 +139,9 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) } // Wait for each request to be done, and return - stats.IncCounter("http_adapter.waiting_for_send") + stats.IncCounter("http_adapter.send.waiting") wg.Wait() - stats.DecCounter("http_adapter.waiting_for_send") + stats.DecCounter("http_adapter.send.waiting") // Collect errors var errs []error diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 0f347eb64..a44c1f3ab 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -58,7 +58,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Beside, a registration request will be triggered if one of the recipient reponses positively. func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { stats.MarkMeter("http_adapter.broadcast") - stats.UpdateHistogram("http_adapter.broadcast_recipients", int64(len(a.recipients))) + stats.UpdateHistogram("http_adapter.broadcast.recipients", int64(len(a.recipients))) // Generate payload from core packet m, err := json.Marshal(p.Metadata) @@ -136,9 +136,9 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { } // Wait for each request to be done, and return - stats.IncCounter("http_adapter.waiting_for_broadcast") + stats.IncCounter("http_adapter.broadcast.waiting") wg.Wait() - stats.DecCounter("http_adapter.waiting_for_broadcast") + stats.DecCounter("http_adapter.broadcast.waiting") close(cherr) close(register) var errs []error diff --git a/core/adapters/http/statuspage/statuspage.go b/core/adapters/http/statuspage/statuspage.go index bdaceb215..5e3f9a3ee 100644 --- a/core/adapters/http/statuspage/statuspage.go +++ b/core/adapters/http/statuspage/statuspage.go @@ -11,6 +11,7 @@ import ( "fmt" "net" "net/http" + "strings" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/apex/log" @@ -61,36 +62,48 @@ func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { return } - allStats := statData{} + allStats := make(map[string]interface{}) metrics.Each(func(name string, i interface{}) { + // Make sure we put things in the right place + thisStat := allStats + for _, path := range strings.Split(name, ".") { + if thisStat[path] == nil { + thisStat[path] = make(map[string]interface{}) + } + if _, ok := thisStat[path].(map[string]interface{}); ok { + thisStat = thisStat[path].(map[string]interface{}) + } else { + ctx.Errorf("Error building %s stat", name) + return + } + } + switch metric := i.(type) { case metrics.Counter: m := metric.Snapshot() - allStats[name] = m.Count() + thisStat["count"] = m.Count() case metrics.Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) - allStats[name] = histData{ - Avg: h.Mean(), - Min: h.Min(), - Max: h.Max(), - P25: ps[0], - P50: ps[1], - P75: ps[2], - } + thisStat["avg"] = h.Mean() + thisStat["min"] = h.Min() + thisStat["max"] = h.Max() + thisStat["p_25"] = ps[0] + thisStat["p_50"] = ps[1] + thisStat["p_75"] = ps[2] case metrics.Meter: m := metric.Snapshot() - allStats[name] = meterData{ - Rate1: m.Rate1(), - Rate5: m.Rate5(), - Rate15: m.Rate15(), - Count: m.Count(), - } + + thisStat["rate_1"] = m.Rate1() + thisStat["rate_5"] = m.Rate5() + thisStat["rate_15"] = m.Rate15() + thisStat["count"] = m.Count() + } }) @@ -103,21 +116,3 @@ func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) w.Write(response) } - -type statData map[string]interface{} - -type meterData struct { - Rate1 float64 `json:"rate_1"` - Rate5 float64 `json:"rate_5"` - Rate15 float64 `json:"rate_15"` - Count int64 `json:"count"` -} - -type histData struct { - Avg float64 `json:"avg"` - Min int64 `json:"min"` - Max int64 `json:"max"` - P25 float64 `json:"p_25"` - P50 float64 `json:"p_50"` - P75 float64 `json:"p_75"` -} diff --git a/core/components/broker.go b/core/components/broker.go index e5007c0ed..e0bb4b0b9 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -44,7 +44,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter if err != nil { switch err.(errors.Failure).Nature { case ErrWrongBehavior: - stats.MarkMeter("broker.uplink.device_not_registered") + stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") ctx.Warn("Uplink device not found") return an.Nack() default: @@ -53,7 +53,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter return errors.New(ErrFailedOperation, err) } } - stats.UpdateHistogram("broker.handlers_per_dev_addr", int64(len(entries))) + stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) // 2. Several handler might be associated to the same device, we distinguish them using MIC // check. Only one should verify the MIC check. @@ -68,13 +68,13 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter Id: entry.Id, Address: entry.Url, } - stats.MarkMeter("broker.uplink.match_handler") + stats.MarkMeter("broker.uplink.handler_lookup.match") ctx.WithField("handler", handler).Debug("Associated device with handler") break } } if handler == nil { - stats.MarkMeter("broker.uplink.no_match_handler") + stats.MarkMeter("broker.uplink.handler_lookup.no_match") ctx.Warn("Could not find handler for device") return an.Nack() } diff --git a/core/components/router.go b/core/components/router.go index 8b0e4ce02..1b731bfc5 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -56,7 +56,7 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt // Lookup for an existing broker devAddr, err := p.DevAddr() if err != nil { - stats.MarkMeter("broker.uplink.invalid_packet") + stats.MarkMeter("broker.uplink.invalid") r.ctx.Warn("Invalid uplink packet") an.Nack() return err From c3192679d67e44dda9fec3f435e8cf80956757ac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 18:30:35 +0100 Subject: [PATCH 0583/2266] [refactor] [semtech_adapter] increase buffer size We have to support the Semtech forwarder that can send datagrams of up to 4550 bytes. --- core/adapters/semtech/adapter.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 4b0716ca6..cd11b5f50 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -87,7 +87,11 @@ func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") for { - buf := make([]byte, 512) + // The Semtech packet forwarder uses a buffer size of ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) + // Where NB_PKT_MAX = 8 and STATUS_SIZE = 200 + // We are collecting statistics to see if we can reduce the buffer size (see below). + buf := make([]byte, 4550) + n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection a.ctx.WithError(err).Error("Connection error") @@ -95,6 +99,9 @@ func (a *Adapter) listen(conn *net.UDPConn) { } a.ctx.Debug("Incoming datagram") + // Collect statistics for buffer size reduction + stats.UpdateHistogram("semtech_adapter.push_data.size", int64(len(buf))) + pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) if err != nil { From 991a38e127c987bbe9cc62a0912df07363b25972 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 19:26:50 +0100 Subject: [PATCH 0584/2266] [refactor] Get the actual size of the datagram --- core/adapters/semtech/adapter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index cd11b5f50..ccc0c08cf 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -100,7 +100,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { a.ctx.Debug("Incoming datagram") // Collect statistics for buffer size reduction - stats.UpdateHistogram("semtech_adapter.push_data.size", int64(len(buf))) + stats.UpdateHistogram("semtech_adapter.push_data.size", int64(n)) pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) From f39f8f5323138922a5387d96d8a501e72b17ea43 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 19:30:26 +0100 Subject: [PATCH 0585/2266] [refactor] Simulator sends multiple RXPKs in one PUSH_DATA --- simulators/gateway/imitator.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index b189671e2..f3f824cf3 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "net" "os" "time" @@ -104,17 +105,27 @@ func MockRandomly(nodes []node.LiveNode, ctx log.Interface, routers ...string) { } for { - message := <-messages + rxpks := [8]semtech.RXPK{} + numPks := rand.Intn(8) + 1 - rxpk := semtech.RXPK{ - Rssi: pointer.Int(-20), - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("LORA"), - Data: pointer.String(message), + for i := 0; i < numPks; i++ { + message := <-messages + + rxpk := semtech.RXPK{ + Rssi: pointer.Int(-20), + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("LORA"), + Data: pointer.String(message), + } + + rxpks[i] = rxpk } - if err := fwd.Forward(rxpk); err != nil { - ctx.WithError(err).WithField("rxpk", rxpk).Warn("failed to forward") + err := fwd.Forward(rxpks[:numPks]...) + if err != nil { + ctx.WithError(err).Warn("failed to forward") + } else { + ctx.Debugf("Forwarded %d packets.", numPks) } } } From 0b878757c46528dfdc7f71380c5b615be91165cf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 11:00:41 +0100 Subject: [PATCH 0586/2266] [refactor] Include build date and git commit in binaries --- build_binaries.sh | 24 +++++++++++++++++++----- integration/broker/main.go | 7 +++++++ integration/router/main.go | 7 +++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/build_binaries.sh b/build_binaries.sh index a3088611d..6b53eb8cb 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -7,6 +7,8 @@ RELEASEPATH=${RELEASEPATH:-$TTNROOT/release} mkdir -p $RELEASEPATH +git_commit=$(git rev-parse HEAD) + build_release() { component=$1 @@ -26,17 +28,29 @@ build_release() binary_name=$release_name$ext - echo "Building $component for $GOOS/$GOARCH" + build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ) + + echo "$build_date - Building $component for $GOOS/$GOARCH..." # Build cd $TTNROOT - go build -a -installsuffix cgo -ldflags '-w' -o $RELEASEPATH/$binary_name ./integration/$component/main.go + + go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./integration/$component/main.go # Compress cd $RELEASEPATH - tar -cvzf $release_name.tar.gz $binary_name - tar -cvJf $release_name.tar.xz $binary_name - zip $release_name.zip $binary_name + + echo -n " - Compressing tar.gz... " + tar -czf $release_name.tar.gz $binary_name + echo " Done" + + echo -n " - Compressing tar.xz... " + tar -cJf $release_name.tar.xz $binary_name + echo " Done" + + echo -n " - Compressing zip... " + zip -q $release_name.zip $binary_name > /dev/null + echo " Done" # Delete Binary rm $binary_name diff --git a/integration/broker/main.go b/integration/broker/main.go index 80bba59aa..fcdd424bb 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -18,6 +18,11 @@ import ( "github.com/apex/log/handlers/text" ) +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) @@ -29,6 +34,8 @@ func main() { // Parse options routersPort, handlersPort := parseOptions() + ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") + // Instantiate all components rtrAdapter, err := http.NewAdapter(uint(routersPort), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) if err != nil { diff --git a/integration/router/main.go b/integration/router/main.go index c6c3cacaf..9505f0e3d 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -21,6 +21,11 @@ import ( "github.com/apex/log/handlers/text" ) +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) @@ -32,6 +37,8 @@ func main() { // Parse options brokers, tcpPort, udpPort := parseOptions() + ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") + // Instantiate all components gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter")) if err != nil { From 68678240ad1f539836580543bd112a1b5a69054f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:18:23 +0100 Subject: [PATCH 0587/2266] [refactor] [cli] Add main and version command --- cmd/root.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/version.go | 27 +++++++++++++++++++ main.go | 21 +++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 cmd/root.go create mode 100644 cmd/version.go create mode 100644 main.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 000000000..3ef3bc8a1 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,71 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/apex/log" + "github.com/apex/log/handlers/cli" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +var ctx log.Interface + +// RootCmd is executed when ttn is executed without a subcommand +var RootCmd = &cobra.Command{ + Use: "ttn", + Short: "The Things Network", + Long: `The Things Network's backend server`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var logLevel = log.InfoLevel + if viper.GetBool("debug") { + logLevel = log.DebugLevel + } + ctx = &log.Logger{ + Level: logLevel, + Handler: cli.New(os.Stdout), + } + }, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yaml\")") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + + viper.SetConfigName(".ttn") // name of config file (without extension) + viper.AddConfigPath("$HOME") // adding home directory as first search path + viper.SetEnvPrefix("ttn") // set environment prefix + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() // read in environment variables that match + + viper.BindEnv("debug") + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..2afa08df5 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get the version", + Long: `Get the version`, + Run: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "commit": viper.GetString("gitCommit"), + "build date": viper.GetString("buildDate"), + }).Infof("You are running %s of The Things Network.", viper.GetString("version")) + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..53e4d81ed --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "github.com/TheThingsNetwork/ttn/cmd" + "github.com/spf13/viper" +) + +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + +func main() { + viper.Set("version", "a development version") + viper.Set("git-commit", gitCommit) + viper.Set("build-date", buildDate) + cmd.Execute() +} From 6e905d5e46d3ec3ddee94c2e48f1ae71e5a0aa7e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:18:48 +0100 Subject: [PATCH 0588/2266] [refactor] [cli] Migrate Router and Broker to viper --- cmd/broker.go | 114 ++++++++++++++++++++++++ cmd/router.go | 128 +++++++++++++++++++++++++++ integration/broker/DOWNLOADS.md | 67 -------------- integration/broker/Dockerfile | 15 ---- integration/broker/README.md | 23 ----- integration/broker/main.go | 134 ---------------------------- integration/router/DOWNLOADS.md | 67 -------------- integration/router/Dockerfile | 15 ---- integration/router/README.md | 23 ----- integration/router/main.go | 151 -------------------------------- 10 files changed, 242 insertions(+), 495 deletions(-) create mode 100644 cmd/broker.go create mode 100644 cmd/router.go delete mode 100644 integration/broker/DOWNLOADS.md delete mode 100644 integration/broker/Dockerfile delete mode 100644 integration/broker/README.md delete mode 100644 integration/broker/main.go delete mode 100644 integration/router/DOWNLOADS.md delete mode 100644 integration/router/Dockerfile delete mode 100644 integration/router/README.md delete mode 100644 integration/router/main.go diff --git a/cmd/broker.go b/cmd/broker.go new file mode 100644 index 000000000..15dfe1c5e --- /dev/null +++ b/cmd/broker.go @@ -0,0 +1,114 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" + "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" + "github.com/TheThingsNetwork/ttn/core/components" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// brokerCmd represents the router command +var brokerCmd = &cobra.Command{ + Use: "broker", + Short: "The Things Network broker", + Long: ` +The broker is responsible for finding the right handler for uplink packets it +receives from routers. This means that handlers have to register applications +and personalized devices (with their network session keys) with the router. + `, + PreRun: func(cmd *cobra.Command, args []string) { + ctx = ctx.WithField("cmd", "broker") + ctx.WithFields(log.Fields{ + "database": viper.GetString("broker.database"), + "routers-port": viper.GetInt("broker.routers-port"), + "handlers-port": viper.GetInt("broker.handlers-port"), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Instantiate all components + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Routers Adapter") + } + + hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Handlers Adapter") + } + + _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Handlers Adapter") + } + + db, err := components.NewBrokerStorage() + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) + + // Bring the service to life + + // Listen to uplink + go func() { + for { + packet, an, err := rtrAdapter.Next() + if err != nil { + ctx.WithError(err).Error("Could not retrieve uplink") + continue + } + go func(packet core.Packet, an core.AckNacker) { + if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { + ctx.WithError(err).Error("Could not process uplink") + } + }(packet, an) + } + }() + + // List to handler registrations + go func() { + for { + reg, an, err := hdlAdapter.NextRegistration() + if err != nil { + ctx.WithError(err).Error("Could not retrieve registration") + continue + } + go func(reg core.Registration, an core.AckNacker) { + if err := broker.Register(reg, an); err != nil { + ctx.WithError(err).Error("Could not process registration") + } + }(reg, an) + } + }() + + <-make(chan bool) + }, +} + +func init() { + RootCmd.AddCommand(brokerCmd) + + brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") + brokerCmd.Flags().Int("routers-port", 1690, "TCP port for connections from routers") + brokerCmd.Flags().Int("handlers-port", 1790, "TCP port for connections from handlers") + + viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) + viper.BindPFlag("broker.routers-port", brokerCmd.Flags().Lookup("routers-port")) + viper.BindPFlag("broker.handlers-port", brokerCmd.Flags().Lookup("handlers-port")) +} diff --git a/cmd/router.go b/cmd/router.go new file mode 100644 index 000000000..014abe28e --- /dev/null +++ b/cmd/router.go @@ -0,0 +1,128 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" + "github.com/TheThingsNetwork/ttn/core/adapters/semtech" + "github.com/TheThingsNetwork/ttn/core/components" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// routerCmd represents the router command +var routerCmd = &cobra.Command{ + Use: "router", + Short: "The Things Network router", + Long: `The router accepts connections from gateways and forwards uplink packets to one +or more brokers. The router is also responsible for monitoring gateways, +collecting statistics from gateways and for enforcing TTN's fair use policy when +the gateway's duty cycle is (almost) full.`, + PreRun: func(cmd *cobra.Command, args []string) { + ctx = ctx.WithField("cmd", "router") + ctx.WithFields(log.Fields{ + "database": viper.GetString("router.database"), + "gateways-port": viper.GetInt("router.gateways-port"), + "brokers": viper.GetString("router.brokers"), + "brokers-port": viper.GetInt("router.brokers-port"), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("tag", "Gateway Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Gateway Adapter") + } + + pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + var brokers []core.Recipient + brokersStr := strings.Split(viper.GetString("router.brokers"), ",") + for i := range brokersStr { + brokers = append(brokers, core.Recipient{ + Address: strings.Trim(brokersStr[i], " "), + Id: i, + }) + } + + brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + db, err := components.NewRouterStorage(time.Hour * 8) + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + router := components.NewRouter(db, ctx.WithField("tag", "Router")) + + // Bring the service to life + + // Listen uplink + go func() { + for { + packet, an, err := gtwAdapter.Next() + if err != nil { + ctx.WithError(err).Warn("Could not get next packet from gateway") + continue + } + go func(packet core.Packet, an core.AckNacker) { + if err := router.HandleUp(packet, an, brkAdapter); err != nil { + ctx.WithError(err).Warn("Could not process packet from gateway") + } + }(packet, an) + } + }() + + // Listen broker registrations + go func() { + for { + reg, an, err := brkAdapter.NextRegistration() + if err != nil { + ctx.WithError(err).Warn("Could not get next registration from broker") + continue + } + go func(reg core.Registration, an core.AckNacker) { + if err := router.Register(reg, an); err != nil { + ctx.WithError(err).Warn("Could not process registration from broker") + } + }(reg, an) + } + }() + + <-make(chan bool) + }, +} + +func init() { + RootCmd.AddCommand(routerCmd) + + routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") + routerCmd.Flags().Int("gateways-port", 1680, "UDP port for connections from gateways") + routerCmd.Flags().String("brokers", "localhost:1690", "Comma-separated list of brokers") + routerCmd.Flags().Int("brokers-port", 1780, "TCP port for connections from brokers") + + viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) + viper.BindPFlag("router.gateways-port", routerCmd.Flags().Lookup("gateways-port")) + viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) + viper.BindPFlag("router.brokers-port", routerCmd.Flags().Lookup("brokers-port")) +} diff --git a/integration/broker/DOWNLOADS.md b/integration/broker/DOWNLOADS.md deleted file mode 100644 index 3bb707781..000000000 --- a/integration/broker/DOWNLOADS.md +++ /dev/null @@ -1,67 +0,0 @@ -# The Things Network Broker - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Download Links -| OS-arch | master | develop | -| ------- | ------ | ------- | -| darwin-386 | [zip][dd32z] [tar.gz][dd32g] [tar.xz][dd32x] | [zip][md32z] [tar.gz][md32g] [tar.xz][md32x] | -| darwin-amd64 | [zip][dd64z] [tar.gz][dd64g] [tar.xz][dd64x] | [zip][md64z] [tar.gz][md64g] [tar.xz][md64x] | -| linux-386 | [zip][dl32z] [tar.gz][dl32g] [tar.xz][dl32x] | [zip][ml32z] [tar.gz][ml32g] [tar.xz][ml32x] | -| linux-amd64 | [zip][dl64z] [tar.gz][dl64g] [tar.xz][dl64x] | [zip][ml64z] [tar.gz][ml64g] [tar.xz][ml64x] | -| linux-arm | [zip][dlarmz] [tar.gz][dlarmg] [tar.xz][dlarmx] | [zip][mlarmz] [tar.gz][mlarmg] [tar.xz][mlarmx] | -| windows-386 | [zip][dw32z] [tar.gz][dw32g] [tar.xz][dw32x] | [zip][mw32z] [tar.gz][mw32g] [tar.xz][mw32x] | -| windows-amd64 | [zip][dw64z] [tar.gz][dw64g] [tar.xz][dw64x] | [zip][mw64z] [tar.gz][mw64g] [tar.xz][mw64x] | - -[dd32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.zip -[dd32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.tar.gz -[dd32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.tar.xz -[md32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.zip -[md32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.tar.gz -[md32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.tar.xz - -[dd64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.zip -[dd64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.tar.gz -[dd64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.tar.xz -[md64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.zip -[md64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.tar.gz -[md64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.tar.xz - -[dl32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.zip -[dl32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.tar.gz -[dl32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.tar.xz -[ml32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.zip -[ml32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.tar.gz -[ml32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.tar.xz - -[dl64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.zip -[dl64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.gz -[dl64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.xz -[ml64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.zip -[ml64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.tar.gz -[ml64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.tar.xz - -[dlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.zip -[dlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.tar.gz -[dlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.tar.xz -[mlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.zip -[mlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.tar.gz -[mlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.tar.xz - -[dw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.zip -[dw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.tar.gz -[dw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.tar.xz -[mw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.zip -[mw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.tar.gz -[mw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.tar.xz - -[dw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.zip -[dw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.tar.gz -[dw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.tar.xz -[mw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.zip -[mw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.tar.gz -[mw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.tar.xz diff --git a/integration/broker/Dockerfile b/integration/broker/Dockerfile deleted file mode 100644 index b7f3eca4f..000000000 --- a/integration/broker/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine - -RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* - -RUN apk --update add curl tar \ - && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/broker-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.gz" \ - && tar -xf /ttnsrc/broker-linux-amd64.tar.gz -C /ttnsrc \ - && mv /ttnsrc/broker-linux-amd64 /usr/local/bin/broker \ - && chmod 755 /usr/local/bin/broker \ - && rm -rf /ttnsrc \ - && apk del curl tar \ - && rm -rf /var/cache/apk/* - -ENTRYPOINT ["/usr/local/bin/broker"] diff --git a/integration/broker/README.md b/integration/broker/README.md deleted file mode 100644 index 5aaf73638..000000000 --- a/integration/broker/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# The Things Network Broker - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Status - -This image is a pre-1.0 version of The Things Network's Broker component. It is **under heavy development** and currently it's APIs and code are not yet stable. - -## Tags - -* [`latest`, `develop` (TheThingsNetwork/ttn - develop)](https://github.com/TheThingsNetwork/ttn/blob/develop/integration/broker/Dockerfile) - -## Usage - -``` -docker pull thethingsnetwork/broker - -docker run -p 8642:8642 -p 8672:8672 thethingsnetwork/broker --handlers-port 8642 --routers-port 8672 -``` diff --git a/integration/broker/main.go b/integration/broker/main.go deleted file mode 100644 index fcdd424bb..000000000 --- a/integration/broker/main.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "os" - "strconv" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" - "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" - "github.com/TheThingsNetwork/ttn/core/components" - "github.com/apex/log" - "github.com/apex/log/handlers/text" -) - -var ( - gitCommit = "unknown" - buildDate = "unknown" -) - -func main() { - // Create Logging Context - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - ctx := log.WithFields(log.Fields{ - "component": "Broker", - }) - - // Parse options - routersPort, handlersPort := parseOptions() - - ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") - - // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(routersPort), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Routers Adapter") - } - - hdlHTTPAdapter, err := http.NewAdapter(uint(handlersPort), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Handlers Adapter") - } - - _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Handlers Adapter") - } - - db, err := components.NewBrokerStorage() - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) - - // Bring the service to life - - // Listen to uplink - go func() { - for { - packet, an, err := rtrAdapter.Next() - if err != nil { - ctx.WithError(err).Error("Could not retrieve uplink") - continue - } - go func(packet Packet, an AckNacker) { - if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { - ctx.WithError(err).Error("Could not process uplink") - } - }(packet, an) - } - }() - - // List to handler registrations - go func() { - for { - reg, an, err := hdlAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Error("Could not retrieve registration") - continue - } - go func(reg Registration, an AckNacker) { - if err := broker.Register(reg, an); err != nil { - ctx.WithError(err).Error("Could not process registration") - } - }(reg, an) - } - }() - - <-make(chan bool) -} - -func parseOptions() (routersPort uint64, handlersPort uint64) { - var routersPortFlag string - var handlersPortFlag string - - flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - - flags.StringVar(&routersPortFlag, "routers-port", "", "TCP port on which the broker should listen to for incoming uplink packets.") - flags.StringVar(&handlersPortFlag, "handlers-port", "", "TCP port on which the broker should listen to for incoming registrations and downlink packet.") - - flags.Parse(os.Args[1:]) - - var err error - - if routersPortFlag == "" { - log.Fatal("No Router listen port supplied using the -routers-port flag") - } - routersPort, err = strconv.ParseUint(routersPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -routers-port") - } - - if handlersPortFlag == "" { - log.Fatal("No Handler listen port supplied using the -handlers-port flag") - } - handlersPort, err = strconv.ParseUint(handlersPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -handlers-port") - } - - return -} diff --git a/integration/router/DOWNLOADS.md b/integration/router/DOWNLOADS.md deleted file mode 100644 index 3fc42537e..000000000 --- a/integration/router/DOWNLOADS.md +++ /dev/null @@ -1,67 +0,0 @@ -# The Things Network Router - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Download Links -| OS-arch | master | develop | -| ------- | ------ | ------- | -| darwin-386 | [zip][dd32z] [tar.gz][dd32g] [tar.xz][dd32x] | [zip][md32z] [tar.gz][md32g] [tar.xz][md32x] | -| darwin-amd64 | [zip][dd64z] [tar.gz][dd64g] [tar.xz][dd64x] | [zip][md64z] [tar.gz][md64g] [tar.xz][md64x] | -| linux-386 | [zip][dl32z] [tar.gz][dl32g] [tar.xz][dl32x] | [zip][ml32z] [tar.gz][ml32g] [tar.xz][ml32x] | -| linux-amd64 | [zip][dl64z] [tar.gz][dl64g] [tar.xz][dl64x] | [zip][ml64z] [tar.gz][ml64g] [tar.xz][ml64x] | -| linux-arm | [zip][dlarmz] [tar.gz][dlarmg] [tar.xz][dlarmx] | [zip][mlarmz] [tar.gz][mlarmg] [tar.xz][mlarmx] | -| windows-386 | [zip][dw32z] [tar.gz][dw32g] [tar.xz][dw32x] | [zip][mw32z] [tar.gz][mw32g] [tar.xz][mw32x] | -| windows-amd64 | [zip][dw64z] [tar.gz][dw64g] [tar.xz][dw64x] | [zip][mw64z] [tar.gz][mw64g] [tar.xz][mw64x] | - -[dd32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.zip -[dd32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.tar.gz -[dd32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.tar.xz -[md32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.zip -[md32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.tar.gz -[md32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.tar.xz - -[dd64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.zip -[dd64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.tar.gz -[dd64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.tar.xz -[md64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.zip -[md64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.tar.gz -[md64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.tar.xz - -[dl32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.zip -[dl32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.tar.gz -[dl32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.tar.xz -[ml32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.zip -[ml32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.tar.gz -[ml32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.tar.xz - -[dl64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.zip -[dl64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.gz -[dl64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.xz -[ml64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.zip -[ml64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.tar.gz -[ml64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.tar.xz - -[dlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.zip -[dlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.tar.gz -[dlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.tar.xz -[mlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.zip -[mlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.tar.gz -[mlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.tar.xz - -[dw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.zip -[dw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.tar.gz -[dw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.tar.xz -[mw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.zip -[mw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.tar.gz -[mw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.tar.xz - -[dw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.zip -[dw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.tar.gz -[dw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.tar.xz -[mw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.zip -[mw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.tar.gz -[mw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.tar.xz diff --git a/integration/router/Dockerfile b/integration/router/Dockerfile deleted file mode 100644 index 4e6b98adc..000000000 --- a/integration/router/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine - -RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* - -RUN apk --update add curl tar \ - && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/router-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.gz" \ - && tar -xf /ttnsrc/router-linux-amd64.tar.gz -C /ttnsrc \ - && mv /ttnsrc/router-linux-amd64 /usr/local/bin/router \ - && chmod 755 /usr/local/bin/router \ - && rm -rf /ttnsrc \ - && apk del curl tar \ - && rm -rf /var/cache/apk/* - -ENTRYPOINT ["/usr/local/bin/router"] diff --git a/integration/router/README.md b/integration/router/README.md deleted file mode 100644 index 92057a7b7..000000000 --- a/integration/router/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# The Things Network Router - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Status - -This image is a pre-1.0 version of The Things Network's Router component. It is **under heavy development** and currently it's APIs and code are not yet stable. - -## Tags - -* [`latest`, `develop` (TheThingsNetwork/ttn - develop)](https://github.com/TheThingsNetwork/ttn/blob/develop/integration/router/Dockerfile) - -## Usage - -``` -docker pull thethingsnetwork/router - -docker run -p 8647:8647/udp -p 8627:8627 thethingsnetwork/router --udp-port 8647 --tcp-port 8627 --brokers "1.2.3.4:8672,broker.host.com:8672" -``` diff --git a/integration/router/main.go b/integration/router/main.go deleted file mode 100644 index 9505f0e3d..000000000 --- a/integration/router/main.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "os" - "strconv" - "strings" - "time" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" - "github.com/TheThingsNetwork/ttn/core/adapters/semtech" - "github.com/TheThingsNetwork/ttn/core/components" - "github.com/apex/log" - "github.com/apex/log/handlers/text" -) - -var ( - gitCommit = "unknown" - buildDate = "unknown" -) - -func main() { - // Create Logging Context - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - ctx := log.WithFields(log.Fields{ - "component": "Router", - }) - - // Parse options - brokers, tcpPort, udpPort := parseOptions() - - ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") - - // Instantiate all components - gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Gateway Adapter") - } - - pktAdapter, err := http.NewAdapter(uint(tcpPort), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - db, err := components.NewRouterStorage(time.Hour * 8) - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - router := components.NewRouter(db, ctx.WithField("tag", "Router")) - - // Bring the service to life - - // Listen uplink - go func() { - for { - packet, an, err := gtwAdapter.Next() - if err != nil { - ctx.WithError(err).Warn("Could not get next packet from gateway") - continue - } - go func(packet Packet, an AckNacker) { - if err := router.HandleUp(packet, an, brkAdapter); err != nil { - ctx.WithError(err).Warn("Could not process packet from gateway") - } - }(packet, an) - } - }() - - // Listen broker registrations - go func() { - for { - reg, an, err := brkAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Warn("Could not get next registration from broker") - continue - } - go func(reg Registration, an AckNacker) { - if err := router.Register(reg, an); err != nil { - ctx.WithError(err).Warn("Could not process registration from broker") - } - }(reg, an) - } - }() - - <-make(chan bool) -} - -func parseOptions() (brokers []Recipient, tcpPort uint64, udpPort uint64) { - var brokersFlag string - var udpPortFlag string - var tcpPortFlag string - - flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - - flags.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. - For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000`) - flags.StringVar(&udpPortFlag, "udp-port", "", "UDP port on which the router should listen to.") - flags.StringVar(&tcpPortFlag, "tcp-port", "", "TCP port on which the router should listen to.") - - flags.Parse(os.Args[1:]) - - var err error - - if tcpPortFlag == "" { - log.Fatal("No TCP listen port supplied using the -tcp-port flag") - } - tcpPort, err = strconv.ParseUint(tcpPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -tcp-port") - } - - if udpPortFlag == "" { - log.Fatal("No UDP listen port supplied using the -udp-port flag.") - } - udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -udp-port") - } - - if brokersFlag == "" { - log.Fatal("No broker address is supplied using -brokers flag.") - } - brokersStr := strings.Split(brokersFlag, ",") - for i := range brokersStr { - brokers = append(brokers, Recipient{ - Address: strings.Trim(brokersStr[i], " "), - Id: i, - }) - - } - return -} From faea13758687cfa67954862c63d351449038e1ac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:19:05 +0100 Subject: [PATCH 0589/2266] [refactor] [cli] Update build script --- build_binaries.sh | 46 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/build_binaries.sh b/build_binaries.sh index 6b53eb8cb..0db1388cf 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -11,13 +11,11 @@ git_commit=$(git rev-parse HEAD) build_release() { - component=$1 - export CGO_ENABLED=0 - export GOOS=$2 - export GOARCH=$3 + export GOOS=$1 + export GOARCH=$2 - release_name=$component-$GOOS-$GOARCH + release_name=ttn-$GOOS-$GOARCH if [ "$GOOS" == "windows" ] then @@ -30,12 +28,12 @@ build_release() build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ) - echo "$build_date - Building $component for $GOOS/$GOARCH..." + echo "$build_date - Building ttn for $GOOS/$GOARCH..." # Build cd $TTNROOT - go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./integration/$component/main.go + go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./main.go # Compress cd $RELEASEPATH @@ -56,21 +54,13 @@ build_release() rm $binary_name } -build_release router darwin 386 -build_release router darwin amd64 -build_release router linux 386 -build_release router linux amd64 -build_release router linux arm -build_release router windows 386 -build_release router windows amd64 - -build_release broker darwin 386 -build_release broker darwin amd64 -build_release broker linux 386 -build_release broker linux amd64 -build_release broker linux arm -build_release broker windows 386 -build_release broker windows amd64 +build_release darwin 386 +build_release darwin amd64 +build_release linux 386 +build_release linux amd64 +build_release linux arm +build_release windows 386 +build_release windows amd64 # Prepare Releases cd $RELEASEPATH @@ -80,8 +70,7 @@ if [ "$CI_COMMIT" != "" ] then echo "Copying files for commit $CI_COMMIT" mkdir -p commit/$CI_COMMIT - cp ./router* commit/$CI_COMMIT/ - cp ./broker* commit/$CI_COMMIT/ + cp ./ttn* commit/$CI_COMMIT/ fi # Branch Release @@ -89,8 +78,7 @@ if [ "$CI_BRANCH" != "" ] then echo "Copying files for branch $CI_BRANCH" mkdir -p branch/$CI_BRANCH - cp ./router* branch/$CI_BRANCH/ - cp ./broker* branch/$CI_BRANCH/ + cp ./ttn* branch/$CI_BRANCH/ fi # Tag Release @@ -98,10 +86,8 @@ if [ "$CI_TAG" != "" ] then echo "Copying files for tag $CI_TAG" mkdir -p tag/$CI_TAG - cp ./router* tag/$CI_TAG/ - cp ./broker* tag/$CI_TAG/ + cp ./ttn* tag/$CI_TAG/ fi # Remove Build Files -rm -f ./router* -rm -f ./broker* +rm -f ./ttn* From 29193a2636b3348acb544e0b3d11c7d6bb956c2a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:21:53 +0100 Subject: [PATCH 0590/2266] [refactor] [cli] Add empty Handler command --- cmd/handler.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 cmd/handler.go diff --git a/cmd/handler.go b/cmd/handler.go new file mode 100644 index 000000000..7d292b967 --- /dev/null +++ b/cmd/handler.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// handlerCmd represents the handler command +var handlerCmd = &cobra.Command{ + Use: "handler", + Short: "The Things Network handler", + Long: ` +The default handler is the bridge between The Things Network and applications. +`, + PreRun: func(cmd *cobra.Command, args []string) { + ctx = ctx.WithField("cmd", "handler") + ctx.WithFields(log.Fields{ + "database": viper.GetString("handler.database"), + "brokers-port": viper.GetInt("handler.brokers-port"), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Fatal("The handler's not ready yet, but we're working on it!") + + // TODO: Add logic + }, +} + +func init() { + RootCmd.AddCommand(handlerCmd) + + handlerCmd.Flags().String("database", "boltdb:/tmp/ttn_handler.db", "Database connection") + handlerCmd.Flags().Int("brokers-port", 1691, "TCP port for connections from brokers") + + viper.BindPFlag("handler.database", handlerCmd.Flags().Lookup("database")) + viper.BindPFlag("handler.brokers-port", handlerCmd.Flags().Lookup("brokers-port")) + +} From d2e888e8dd1a67d8a5d41ea01dea6881830621dc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:22:22 +0100 Subject: [PATCH 0591/2266] [refactor] [cli] Add Dockerfile for new main --- Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..193c7b2c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine + +RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* + +RUN apk --update add curl tar \ + && mkdir /ttnsrc \ + && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ + && tar -xf /ttnsrc/ttn-linux-amd64.tar.gz -C /ttnsrc \ + && mv /ttnsrc/ttn-linux-amd64 /usr/local/bin/ttn \ + && chmod 755 /usr/local/bin/ttn \ + && rm -rf /ttnsrc \ + && apk del curl tar \ + && rm -rf /var/cache/apk/* + +ENTRYPOINT ["/usr/local/bin/ttn"] From b5cd8df6e3e5a8ff71ec295c7550dde740ae0d8a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:44:36 +0100 Subject: [PATCH 0592/2266] [refactor] [cli] Add local (docker) builds --- Dockerfile.local | 9 ++++++ build_binaries.sh | 78 +++++++++++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 Dockerfile.local diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 000000000..9acac189e --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,9 @@ +# USAGE: +# ./build_binaries.sh linux amd64 +# docker build -t local/ttn -f Dockerfile.local . + +FROM alpine +RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* +ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn +RUN chmod 755 /usr/local/bin/ttn +ENTRYPOINT ["/usr/local/bin/ttn"] diff --git a/build_binaries.sh b/build_binaries.sh index 0db1388cf..b36e4bae5 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -50,44 +50,56 @@ build_release() zip -q $release_name.zip $binary_name > /dev/null echo " Done" - # Delete Binary - rm $binary_name + # Delete Binary in CI build + if [ "$CI" != "" ] + then + rm $binary_name + fi } -build_release darwin 386 -build_release darwin amd64 -build_release linux 386 -build_release linux amd64 -build_release linux arm -build_release windows 386 -build_release windows amd64 - -# Prepare Releases -cd $RELEASEPATH - -# Commit Release -if [ "$CI_COMMIT" != "" ] +if [[ "$1" != "" ]] && [[ "$2" != "" ]] then - echo "Copying files for commit $CI_COMMIT" - mkdir -p commit/$CI_COMMIT - cp ./ttn* commit/$CI_COMMIT/ + build_release $1 $2 +else + build_release darwin 386 + build_release darwin amd64 + build_release linux 386 + build_release linux amd64 + build_release linux arm + build_release windows 386 + build_release windows amd64 fi -# Branch Release -if [ "$CI_BRANCH" != "" ] +# Prepare Releases in CI build +if [ "$CI" != "" ] then - echo "Copying files for branch $CI_BRANCH" - mkdir -p branch/$CI_BRANCH - cp ./ttn* branch/$CI_BRANCH/ -fi -# Tag Release -if [ "$CI_TAG" != "" ] -then - echo "Copying files for tag $CI_TAG" - mkdir -p tag/$CI_TAG - cp ./ttn* tag/$CI_TAG/ + cd $RELEASEPATH + + # Commit Release + if [ "$CI_COMMIT" != "" ] + then + echo "Copying files for commit $CI_COMMIT" + mkdir -p commit/$CI_COMMIT + cp ./ttn* commit/$CI_COMMIT/ + fi + + # Branch Release + if [ "$CI_BRANCH" != "" ] + then + echo "Copying files for branch $CI_BRANCH" + mkdir -p branch/$CI_BRANCH + cp ./ttn* branch/$CI_BRANCH/ + fi + + # Tag Release + if [ "$CI_TAG" != "" ] + then + echo "Copying files for tag $CI_TAG" + mkdir -p tag/$CI_TAG + cp ./ttn* tag/$CI_TAG/ + fi + + # Remove Build Files + rm -f ./ttn* fi - -# Remove Build Files -rm -f ./ttn* From 79a9da8281149f0fe47961668d8cfb66022bfd37 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:48:40 +0100 Subject: [PATCH 0593/2266] [refactor] [cli] Remove old download/docker information from README [skip CI] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca26d3dad..eb452e629 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ When you get started with The Things Network, you'll probably have some question We are working hard on building a working version of the v1.0 backend. Currently, we have the following components working: -- [x] **Router**: [[download](integration/router/DOWNLOADS.md)] [[docker](https://hub.docker.com/r/thethingsnetwork/router/)] -- [x] **Broker**: [[download](integration/broker/DOWNLOADS.md)] [[docker](https://hub.docker.com/r/thethingsnetwork/broker/)] +- [x] **Router** +- [x] **Broker** - [ ] **Handler**: *work in progress* ## Contributing From e20472e9b82a64804f76a8d9a5a6491910fb9339 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 17:00:23 +0100 Subject: [PATCH 0594/2266] [refactor] [cli] Set gateway-router port to 1700 --- cmd/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/router.go b/cmd/router.go index 014abe28e..214013ad2 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -117,7 +117,7 @@ func init() { RootCmd.AddCommand(routerCmd) routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") - routerCmd.Flags().Int("gateways-port", 1680, "UDP port for connections from gateways") + routerCmd.Flags().Int("gateways-port", 1700, "UDP port for connections from gateways") routerCmd.Flags().String("brokers", "localhost:1690", "Comma-separated list of brokers") routerCmd.Flags().Int("brokers-port", 1780, "TCP port for connections from brokers") From e843c3974eb55136e4532af67fda7ddb4ccf01d2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 17:27:37 +0100 Subject: [PATCH 0595/2266] [refactor] Don't build for arm Hopefully a temporary fix because jacobsa/crypto currently doesn't build on arm --- build_binaries.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/build_binaries.sh b/build_binaries.sh index b36e4bae5..815435ba3 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -65,7 +65,6 @@ else build_release darwin amd64 build_release linux 386 build_release linux amd64 - build_release linux arm build_release windows 386 build_release windows amd64 fi From 23c5e089ccc189ce149f7861a20e49c16bd0f348 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 13:08:11 +0100 Subject: [PATCH 0596/2266] [refactor] Use new Azure storage container --- .drone.sec | 2 +- .drone.yml | 2 +- Dockerfile | 2 +- build_binaries.sh | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.drone.sec b/.drone.sec index 841c9cc70..c3d2e86a7 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.AcG0ZL_fM56k9Vd2wBH9lJrCjFMuW8fs_XJF_9YlU5S5PEWdotmfLAZeilOF99nw3RFra24sKo_sfsX4WGJPzutCOL8rAAY4QJCaVLjOJatO8m5LbLryEUAfFrbipI9ZWemThC6YHpdFNmLNS1QmRKMbwbRuXlh1WUVEtk2mP42COJMcjuAVChhg_qrfinxIaZLRmGRmJP2M_rjzK5v26CFcbXS8pRxR-tW5node7r_V9sj_J6Je6Qeok-yzSSCyXfAI6rR3T-_LIAjPUCJgpAEPUWrh6YdcpJx85bFsWn2hoghTy7wJydYjGVdoGi4WmzY6k7szockNVU8vosfp9g.WTTxM0AtX8cltzmw.qBrQoryiemZrWSERNjych0nWR8Mhc7oaIB2umU7h5niJGbrwTMkdBTsBc1NEca5zA4DWEB7rDZhHtvW2GsuaYn2vw3taQv7AtWBx5qFHoTiX1JKipOyRp7BELcJLH3boz9Phl4JI21Y_MGZ3FyhjEJBV5unxcj2gn4f8Czr4huoah3feWAISlxbAdDfcvIkVDf14Pp6z7DZ19fG3qob6L8xtG1N1XyEt3DsdJ5T2wg1rSxwE7dRI7BUKPJzTMn9Q5LO3dEY5.ERl5tu03lw6jsf5slM8NOA \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.YxeKX7Hg1A0oLKO-iu495m2xvpxlGC-H7G6n6AFGdWGi7auIVik9UOV0Odmp-Ntco6uYkpFaDg9xuLNpyy4BaVj3RdwiDHEW1DsA2CzE6O94sH5viVB_eyFgRBblDV1QqznCCR_HMQKT2yjlgC2Ml33BzfoT77Ybh6tfF5QytbFgjWTsv_UqEIfTR-4gm7PCTVcJvZOW0CfclGHlw-3oHGfenOdjTDZgRXtLJCbzo1VreeVCnORMYs3J2e74-MpVkDrF_7EzXjaWkyqVTbMpODTn7MC4Rzq5Fq8VMWWzCLYIsYADnTMvkHp4Qhd8H4ZhUkkM1oJEyQGyzSgHZneoJQ.iJED9qzoTatbkyHG.V-4SyWRwdeFKXGNnWe8s_bCw7_qDUi1LkA71kUOqgPdzsv7I_OsyL9Dcz9VCf_zve_ZMWkNKOczyHRLSCSugjoSuZftsEIrnqaU4DL_L2XNGsYneWxwU3JCDCTZch7tow8H2oYPxm0hIrDAbcKdUxsFIIrVs5PNlg8vMqgOFM0Y8aXGTqDJIEbBxqADQFnmMsQtRY0Leg-WEzEpA6F6_Evm0R5FJc3b9LeWrD8s9ytvd4A5j-UODEOvCNXq0jWnWl4C_Gm4e.s1OgDCnDeTZBilECyze_Iw \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 2c1b58771..a8428d8ae 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,7 +18,7 @@ build: publish: azure_storage: account_key: $$AZURE_STORAGE_KEY - storage_account: thethingsnetwork + storage_account: ttnreleases container: release source: release/ when: diff --git a/Dockerfile b/Dockerfile index 193c7b2c8..691551dde 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* RUN apk --update add curl tar \ && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ + && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://ttnreleases.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ && tar -xf /ttnsrc/ttn-linux-amd64.tar.gz -C /ttnsrc \ && mv /ttnsrc/ttn-linux-amd64 /usr/local/bin/ttn \ && chmod 755 /usr/local/bin/ttn \ diff --git a/build_binaries.sh b/build_binaries.sh index 815435ba3..330710fe7 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -65,8 +65,6 @@ else build_release darwin amd64 build_release linux 386 build_release linux amd64 - build_release windows 386 - build_release windows amd64 fi # Prepare Releases in CI build From 1e71de59ffa8b2e16cefd89fd4c02f17ffd1efcd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 13:09:24 +0100 Subject: [PATCH 0597/2266] [refactor] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index c3d2e86a7..dc223baa2 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.YxeKX7Hg1A0oLKO-iu495m2xvpxlGC-H7G6n6AFGdWGi7auIVik9UOV0Odmp-Ntco6uYkpFaDg9xuLNpyy4BaVj3RdwiDHEW1DsA2CzE6O94sH5viVB_eyFgRBblDV1QqznCCR_HMQKT2yjlgC2Ml33BzfoT77Ybh6tfF5QytbFgjWTsv_UqEIfTR-4gm7PCTVcJvZOW0CfclGHlw-3oHGfenOdjTDZgRXtLJCbzo1VreeVCnORMYs3J2e74-MpVkDrF_7EzXjaWkyqVTbMpODTn7MC4Rzq5Fq8VMWWzCLYIsYADnTMvkHp4Qhd8H4ZhUkkM1oJEyQGyzSgHZneoJQ.iJED9qzoTatbkyHG.V-4SyWRwdeFKXGNnWe8s_bCw7_qDUi1LkA71kUOqgPdzsv7I_OsyL9Dcz9VCf_zve_ZMWkNKOczyHRLSCSugjoSuZftsEIrnqaU4DL_L2XNGsYneWxwU3JCDCTZch7tow8H2oYPxm0hIrDAbcKdUxsFIIrVs5PNlg8vMqgOFM0Y8aXGTqDJIEbBxqADQFnmMsQtRY0Leg-WEzEpA6F6_Evm0R5FJc3b9LeWrD8s9ytvd4A5j-UODEOvCNXq0jWnWl4C_Gm4e.s1OgDCnDeTZBilECyze_Iw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.bCBA4ySZzb5nPaUgEQUBumqcTn2DqZbqlsXXkQXF0-bkYAm64h0uxjTTv5o-Wr-ESVAQumkDCFI8B1l5o5MIHkT7ErFoiN2K-UA2RnTRlwRg7cNwnWM9WbxTS2jUqW5VRY_eQy15f9lg2FZWzDUETwqBGc3LnOKDFAXCgQErwlMPIIzZ2GqwfZezx62N7UTAzQIU81NmEueA4O8ZX_IQ8TA5DixmTi922SamTX9lC51YjJtGmcV3ZgcE9iyQfXnjYvRWF9irNerYvVhCJDheK40MXgVhTCy9JSgQkVSI2mCUUVlV_sYUBOF4-s1DK06j6iuVXW7g8xmmzI6pGB9tsA.jpIxTYwazSp8gV7d.QBqpIbxYo4H7iNCsFvZHfZCjsExsLn-TZTTvyh3naGS8CqzeNelpCQaaiS71n2UE6j6goF3OQLbWcQgNczUbHt7D4ctPiTz8LziPflERNqN04s0RX2Zp234B8gosslHhOIRu9bVeWZOXLzzjp4jt5QUytSeTwgJZffwgUaXPXP0vY7E8CI4uDht7bzmxR4kf0g67vkkV174dlwqEccfX1vpC_-VpJ3DI7l1io8jQGrgzO2PRjCg2xD70nLvBKsdEw6v3V_bT.kxPSWRPvw38ojb1HZLqoyQ \ No newline at end of file From f083f908bab12c1945bf604102d9a20d8dee6338 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 13:54:20 +0100 Subject: [PATCH 0598/2266] [refactor] Add /healthz endpoint in HTTP adapter Resolves #55 --- core/adapters/http/adapter.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index a07802d6b..f69c24b95 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -52,6 +52,11 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap Client: http.Client{}, } + a.RegisterEndpoint("/healthz", func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + }) + a.RegisterEndpoint("/packets", a.handlePostPacket) go a.listenRequests(port) From 91bb5906f505dd9e103dc83acbbbfbfedad72634 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 14:52:12 +0100 Subject: [PATCH 0599/2266] [refactor] Set timeout of HTTP adapter --- core/adapters/http/adapter.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index f69c24b95..f69c7204a 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "net/http" "sync" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" @@ -49,7 +50,9 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap serveMux: http.NewServeMux(), packets: make(chan pktReq), ctx: ctx, - Client: http.Client{}, + Client: http.Client{ + Timeout: 6 * time.Second, + }, } a.RegisterEndpoint("/healthz", func(w http.ResponseWriter, req *http.Request) { From a1c8b1456c63ada7d36e2216322ae87c8b50520a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 15:18:35 +0100 Subject: [PATCH 0600/2266] [refactor] Remove unnecessary log metadata Closes #57 --- cmd/broker.go | 11 +++++------ cmd/handler.go | 1 - cmd/router.go | 11 +++++------ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 15dfe1c5e..f21c0b6b7 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -25,7 +25,6 @@ receives from routers. This means that handlers have to register applications and personalized devices (with their network session keys) with the router. `, PreRun: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("cmd", "broker") ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), "routers-port": viper.GetInt("broker.routers-port"), @@ -36,22 +35,22 @@ and personalized devices (with their network session keys) with the router. ctx.Info("Starting") // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } - hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) + hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } - _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) + _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("adapter", "statuspage-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("adapter", "handler-pubsub")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } @@ -61,7 +60,7 @@ and personalized devices (with their network session keys) with the router. ctx.WithError(err).Fatal("Could not create a local storage") } - broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) + broker := components.NewBroker(db, ctx) // Bring the service to life diff --git a/cmd/handler.go b/cmd/handler.go index 7d292b967..d2874df7c 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -17,7 +17,6 @@ var handlerCmd = &cobra.Command{ The default handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("cmd", "handler") ctx.WithFields(log.Fields{ "database": viper.GetString("handler.database"), "brokers-port": viper.GetInt("handler.brokers-port"), diff --git a/cmd/router.go b/cmd/router.go index 214013ad2..a938b0cee 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -28,7 +28,6 @@ or more brokers. The router is also responsible for monitoring gateways, collecting statistics from gateways and for enforcing TTN's fair use policy when the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("cmd", "router") ctx.WithFields(log.Fields{ "database": viper.GetString("router.database"), "gateways-port": viper.GetInt("router.gateways-port"), @@ -39,17 +38,17 @@ the gateway's duty cycle is (almost) full.`, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("tag", "Gateway Adapter")) + gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } - pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) + pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) + _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("adapter", "statuspage-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } @@ -63,7 +62,7 @@ the gateway's duty cycle is (almost) full.`, }) } - brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) + brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("adapter", "broker-broadcast")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } @@ -73,7 +72,7 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(err).Fatal("Could not create a local storage") } - router := components.NewRouter(db, ctx.WithField("tag", "Router")) + router := components.NewRouter(db, ctx) // Bring the service to life From 53f8bfc50ed72d4e092b457be84b9af6121ddd3d Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 11:27:27 +0100 Subject: [PATCH 0601/2266] [refactor] Create backbone for mosquitto adapter --- core/adapters/mosquitto/adapter.go | 33 +++++++++++++++++++++++++ core/adapters/mosquitto/adapter_test.go | 4 +++ core/adapters/mosquitto/doc.go | 5 ++++ 3 files changed, 42 insertions(+) create mode 100644 core/adapters/mosquitto/adapter.go create mode 100644 core/adapters/mosquitto/adapter_test.go create mode 100644 core/adapters/mosquitto/doc.go diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go new file mode 100644 index 000000000..f331d21e9 --- /dev/null +++ b/core/adapters/mosquitto/adapter.go @@ -0,0 +1,33 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mosquitto + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/apex/log" +) + +type Adapter struct { + ctx log.Interface +} + +// NewAdapter constructs a new mqtt adapter +func NewAdapter() (*Adapter, error) { + return nil, nil +} + +// Send implements the core.Adapter interface +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { + return core.Packet{}, nil +} + +// Next implements the core.Adapter interface +func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { + return core.Packet{}, nil, nil +} + +// NextRegistration implements the core.Adapter interface +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + return core.Registration{}, nil, nil +} diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go new file mode 100644 index 000000000..179fb2903 --- /dev/null +++ b/core/adapters/mosquitto/adapter_test.go @@ -0,0 +1,4 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mosquitto diff --git a/core/adapters/mosquitto/doc.go b/core/adapters/mosquitto/doc.go new file mode 100644 index 000000000..5f194104c --- /dev/null +++ b/core/adapters/mosquitto/doc.go @@ -0,0 +1,5 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package mosquitto provides an MQTT adapter that connect to an mqtt broker +package mosquitto From 932249b2fc53606f98aa873c49243567f869f1ed Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 13:48:29 +0100 Subject: [PATCH 0602/2266] [refactor] Write adapter test backbone --- core/adapters/mosquitto/adapter.go | 14 +++ core/adapters/mosquitto/adapter_test.go | 123 ++++++++++++++++++++++++ 2 files changed, 137 insertions(+) diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go index f331d21e9..cb867cdee 100644 --- a/core/adapters/mosquitto/adapter.go +++ b/core/adapters/mosquitto/adapter.go @@ -6,12 +6,26 @@ package mosquitto import ( "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" + "github.com/brocaar/lorawan" ) type Adapter struct { ctx log.Interface } +type PersonnalizedActivation struct { + DevAddr lorawan.DevAddr + NwkSKey lorawan.AES128Key + AppSKey lorawan.AES128Key +} + +const ( + TOPIC_ACTIVATIONS string = "activations" + TOPIC_UPLINK = "up" + TOPIC_DOWNLINK = "down" + RESOURCE = "devices" +) + // NewAdapter constructs a new mqtt adapter func NewAdapter() (*Adapter, error) { return nil, nil diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index 179fb2903..81530c784 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -2,3 +2,126 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package mosquitto + +import ( + "encoding/hex" + "fmt" + "testing" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +/** + +- Setup a client +- Create topic on the client and register to them +- Publish to the registration topic and see if nextRegistrationIsTriggered +- Publish to registered topics and see if next() trigger things +- Publish to unregistered topics and make sure next() isn't triggered +- Send() to a given topic and see if client received + +*/ + +type publicationShape struct { + AppEUI string + DevEUI string + Topic string + Content interface{} +} + +type packetShape struct { + DevAddr lorawan.DevAddr + Data string +} + +func TestNext(t *testing.T) { + devices := []PersonnalizedActivation{ + { + DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 1}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + AppSKey: lorawan.AES128Key([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), + }, + { + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + NwkSKey: lorawan.AES128Key([16]byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}), + AppSKey: lorawan.AES128Key([16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}), + }, + } + + applications := []lorawan.EUI64{ + lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 4, 4}), + } + + tests := []struct { + Desc string + Registrations []publicationShape + Publication publicationShape + WantPacket packetShape + WantError *string + }{ + { + Desc: "Register #0 | Publish #0 -> #0", + Registrations: []publicationShape{ + { + AppEUI: hex.EncodeToString(applications[0][:]), + DevEUI: "personnalized", + Topic: TOPIC_ACTIVATIONS, + Content: devices[0], + }, + }, + Publication: publicationShape{ + AppEUI: hex.EncodeToString(applications[0][:]), + DevEUI: fmt.Sprintf("%s%s", hex.EncodeToString([]byte{0, 0, 0, 0}), hex.EncodeToString(devices[0].DevAddr[:])), + Topic: TOPIC_UPLINK, + Content: "Data", + }, + WantPacket: packetShape{ + DevAddr: devices[0].DevAddr, + Data: "Data", + }, + WantError: nil, + }, + } + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + adapter, mosquitto := genAdapter(t, test.Registrations) + + // Operate + mosquitto.Publish(test.Publication) + packet, _, err := adapter.Next() + + // Check + checkErrors(t, test.WantError, err) + checkPackets(t, test.WantPacket, packet) + } +} + +// ----- BUILD utilities +type Mosquitto struct { +} + +func (m *Mosquitto) Publish(p publicationShape) { + +} + +func genAdapter(t *testing.T, registrations []publicationShape) (*Adapter, *Mosquitto) { + return nil, nil +} + +// ----- OPERATE utilities + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + +} + +func checkPackets(t *testing.T, want packetShape, got core.Packet) { + +} From 3f41328434f5c9dc551f469864f39cfacac8fa7e Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 14:46:29 +0100 Subject: [PATCH 0603/2266] [refactor] Define build utilities --- core/adapters/mosquitto/adapter.go | 2 +- core/adapters/mosquitto/adapter_test.go | 42 +++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go index cb867cdee..3cdf0f546 100644 --- a/core/adapters/mosquitto/adapter.go +++ b/core/adapters/mosquitto/adapter.go @@ -27,7 +27,7 @@ const ( ) // NewAdapter constructs a new mqtt adapter -func NewAdapter() (*Adapter, error) { +func NewAdapter(mqttBroker string, ctx log.Interface) (*Adapter, error) { return nil, nil } diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index 81530c784..07f928939 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -4,10 +4,13 @@ package mosquitto import ( + "bytes" "encoding/hex" "fmt" + "os/exec" "testing" + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -91,7 +94,10 @@ func TestNext(t *testing.T) { Desc(t, test.Desc) // Build - adapter, mosquitto := genAdapter(t, test.Registrations) + if err := exec.Command("sh", "-c", "mosquitto -p 33333").Run(); err != nil { + panic(err) + } + adapter, mosquitto := genAdapter(t, test.Registrations, 33333) // Operate mosquitto.Publish(test.Publication) @@ -105,14 +111,44 @@ func TestNext(t *testing.T) { // ----- BUILD utilities type Mosquitto struct { + MQTT *MQTT.Client } func (m *Mosquitto) Publish(p publicationShape) { } -func genAdapter(t *testing.T, registrations []publicationShape) (*Adapter, *Mosquitto) { - return nil, nil +func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adapter, *Mosquitto) { + mqttBroker := fmt.Sprintf("tcp://localhost:%d", port) + + // Prepare client + opts := MQTT.NewClientOptions() + opts.AddBroker(mqttBroker) + opts.SetClientID("TestClient") + client := MQTT.NewClient(opts) + if token := client.Connect(); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + for _, r := range registrations { + topic := fmt.Sprintf("%s/%s/%s/%s", r.AppEUI, RESOURCE, r.DevEUI, TOPIC_ACTIVATIONS) + dev := r.Content.(PersonnalizedActivation) + buf := new(bytes.Buffer) + buf.Write(dev.DevAddr[:]) + buf.Write(dev.NwkSKey[:]) + buf.Write(dev.AppSKey[:]) + client.Publish(topic, 2, true, buf.Bytes()) + } + mosquitto := &Mosquitto{MQTT: client} + + // Prepare adapter + ctx := GetLogger(t, "Adapter") + adapter, err := NewAdapter(mqttBroker, ctx) + if err != nil { + panic(err) + } + + // Send them all + return adapter, mosquitto } // ----- OPERATE utilities From 6da1aa4812c089eeab670caf534dddf74bc19101 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 14:46:36 +0100 Subject: [PATCH 0604/2266] [refactor] Define operate utilities --- core/adapters/mosquitto/adapter_test.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index 07f928939..956f56949 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -115,7 +115,10 @@ type Mosquitto struct { } func (m *Mosquitto) Publish(p publicationShape) { - + topic := fmt.Sprintf("%s/%s/%s/%s", p.AppEUI, RESOURCE, p.DevEUI, TOPIC_ACTIVATIONS) + if token := m.MQTT.Publish(topic, 2, true, p.Content); token.Wait() && token.Error() != nil { + panic(token.Error()) + } } func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adapter, *Mosquitto) { From 146b911da892b523f9727b249a705f0c16091047 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 14:55:25 +0100 Subject: [PATCH 0605/2266] [refactor] Create check utilities methods --- core/adapters/mosquitto/adapter_test.go | 48 ++++++++++++++++++------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index 956f56949..08dcb2db2 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -12,21 +12,11 @@ import ( MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) -/** - -- Setup a client -- Create topic on the client and register to them -- Publish to the registration topic and see if nextRegistrationIsTriggered -- Publish to registered topics and see if next() trigger things -- Publish to unregistered topics and make sure next() isn't triggered -- Send() to a given topic and see if client received - -*/ - type publicationShape struct { AppEUI string DevEUI string @@ -154,13 +144,45 @@ func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adap return adapter, mosquitto } -// ----- OPERATE utilities - // ----- CHECK utilities func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be %s but got %v", want, got) } func checkPackets(t *testing.T, want packetShape, got core.Packet) { + devAddr, err := got.DevAddr() + if err != nil { + Ko(t, "Received a wrongly formatted packet: %+v", got) + return + } + + if devAddr != want.DevAddr { + Ko(t, "Expected address [%v] but got [%v]", want.DevAddr, devAddr) + return + } + + data, ok := got.Payload.MACPayload.(*lorawan.MACPayload) + if !ok { + Ko(t, "Received a wrongly formatted packet: %+v", got) + return + } + + if len(data.FRMPayload) != 1 { + Ko(t, "Received a wrongly formatted packet: %+v", got) + return + } + + msg := string(data.FRMPayload[0].(*lorawan.DataPayload).Bytes) + + if want.Data != msg { + Ko(t, `Expected msg to be "%s" but got "%s"`, want.Data, msg) + return + } + Ok(t, "Check packets") } From e6f9ac14e1e8718ba40ffc66b98f99342c4ec10b Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 15:41:46 +0100 Subject: [PATCH 0606/2266] [refactor] Remove mosquitto from test file -> Will assume it as peer-dependency --- core/adapters/mosquitto/adapter_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index 08dcb2db2..eb3ea0498 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -7,7 +7,6 @@ import ( "bytes" "encoding/hex" "fmt" - "os/exec" "testing" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" @@ -84,9 +83,6 @@ func TestNext(t *testing.T) { Desc(t, test.Desc) // Build - if err := exec.Command("sh", "-c", "mosquitto -p 33333").Run(); err != nil { - panic(err) - } adapter, mosquitto := genAdapter(t, test.Registrations, 33333) // Operate From e023fac2d1b519e8fd1246731e407a0427b31c6a Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 15:51:27 +0100 Subject: [PATCH 0607/2266] [refactor] Complete Next tests + add timeout handling --- core/adapters/mosquitto/adapter_test.go | 82 +++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 5 deletions(-) diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index eb3ea0498..160fc21e1 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -7,7 +7,9 @@ import ( "bytes" "encoding/hex" "fmt" + "reflect" "testing" + "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" @@ -51,11 +53,11 @@ func TestNext(t *testing.T) { Desc string Registrations []publicationShape Publication publicationShape - WantPacket packetShape + WantPacket *packetShape WantError *string }{ { - Desc: "Register #0 | Publish #0 -> #0", + Desc: "Register #0 | Publish with #0", Registrations: []publicationShape{ { AppEUI: hex.EncodeToString(applications[0][:]), @@ -70,12 +72,50 @@ func TestNext(t *testing.T) { Topic: TOPIC_UPLINK, Content: "Data", }, - WantPacket: packetShape{ + WantPacket: &packetShape{ DevAddr: devices[0].DevAddr, Data: "Data", }, WantError: nil, }, + { + Desc: "Register #0 | Publish with #1", + Registrations: []publicationShape{ + { + AppEUI: hex.EncodeToString(applications[0][:]), + DevEUI: "personnalized", + Topic: TOPIC_ACTIVATIONS, + Content: devices[0], + }, + }, + Publication: publicationShape{ + AppEUI: hex.EncodeToString(applications[0][:]), + DevEUI: fmt.Sprintf("%s%s", hex.EncodeToString([]byte{0, 0, 0, 0}), hex.EncodeToString(devices[0].DevAddr[:])), + Topic: TOPIC_UPLINK, + Content: "Data", + }, + WantPacket: nil, + WantError: nil, + }, + { + Desc: "Register #0 | Publish in void", + Registrations: []publicationShape{ + { + AppEUI: hex.EncodeToString(applications[0][:]), + DevEUI: "somethingElse", + Topic: TOPIC_ACTIVATIONS, + Content: devices[0], + }, + }, + Publication: publicationShape{ + AppEUI: hex.EncodeToString(applications[0][:]), + DevEUI: fmt.Sprintf("%s%s", hex.EncodeToString([]byte{0, 0, 0, 0}), hex.EncodeToString(devices[0].DevAddr[:])), + Topic: TOPIC_UPLINK, + Content: "Data", + }, + WantPacket: nil, + WantError: nil, + }, } for _, test := range tests { @@ -87,7 +127,7 @@ func TestNext(t *testing.T) { // Operate mosquitto.Publish(test.Publication) - packet, _, err := adapter.Next() + packet, err := nextPacket(adapter) // Check checkErrors(t, test.WantError, err) @@ -140,6 +180,29 @@ func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adap return adapter, mosquitto } +// ----- OPERATE utilities +func nextPacket(adapter *Adapter) (core.Packet, error) { + nextPkt := make(chan interface{}) + go func() { + packet, _, err := adapter.Next() + nextPkt <- struct { + Packet core.Packet + Error error + }{packet, err} + }() + + select { + case itf := <-nextPkt: + res := itf.(struct { + Packet core.Packet + Error error + }) + return res.Packet, res.Error + case <-time.After(200 * time.Millisecond): + return core.Packet{}, nil + } +} + // ----- CHECK utilities func checkErrors(t *testing.T, want *string, got error) { if want == nil && got == nil || got.(errors.Failure).Nature == *want { @@ -150,7 +213,16 @@ func checkErrors(t *testing.T, want *string, got error) { Ko(t, "Expected error to be %s but got %v", want, got) } -func checkPackets(t *testing.T, want packetShape, got core.Packet) { +func checkPackets(t *testing.T, want *packetShape, got core.Packet) { + if want == nil { + if !reflect.DeepEqual(got, core.Packet{}) { + Ko(t, "Expected no packet but received %+v", got) + return + } + Ok(t, "Check packets") + return + } + devAddr, err := got.DevAddr() if err != nil { Ko(t, "Received a wrongly formatted packet: %+v", got) From 0b5143de0da9e5151eed66ff0881dfcf36c05106 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 16:14:10 +0100 Subject: [PATCH 0608/2266] [refactor] Implement contructors and nextRegistration --- core/adapters/mosquitto/adapter.go | 67 +++++++++++++++++++++++-- core/adapters/mosquitto/adapter_test.go | 6 +-- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go index 3cdf0f546..b8b2dfaac 100644 --- a/core/adapters/mosquitto/adapter.go +++ b/core/adapters/mosquitto/adapter.go @@ -4,13 +4,20 @@ package mosquitto import ( + "fmt" + "strings" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/brocaar/lorawan" ) type Adapter struct { - ctx log.Interface + ctx log.Interface + registrations chan core.Registration } type PersonnalizedActivation struct { @@ -24,11 +31,62 @@ const ( TOPIC_UPLINK = "up" TOPIC_DOWNLINK = "down" RESOURCE = "devices" + PERSONNALIZED = "personnalized" ) // NewAdapter constructs a new mqtt adapter -func NewAdapter(mqttBroker string, ctx log.Interface) (*Adapter, error) { - return nil, nil +func NewAdapter(client *MQTT.Client, ctx log.Interface) (*Adapter, error) { + a := &Adapter{ + ctx: ctx, + registrations: make(chan core.Registration), + } + + token := client.Subscribe(fmt.Sprintf("+/devices/+/activations"), 2, a.handleActivation) + if token.Wait() && token.Error() != nil { + ctx.WithError(token.Error()).Error("Unable to instantiate the adapter") + return nil, errors.New(ErrFailedOperation, token.Error()) + } + + return a, nil +} + +func (a *Adapter) handleActivation(client *MQTT.Client, message MQTT.Message) { + topicInfos := strings.Split(message.Topic(), "/") + appEUI := topicInfos[0] + devEUI := topicInfos[2] + + if devEUI != PERSONNALIZED { + a.ctx.WithField("Device Address", devEUI).Warn("OTAA not yet supported. Unable to register device") + return + } + + payload := message.Payload() + if len(payload) != 20 { + a.ctx.WithField("Payload", payload).Error("Invalid registration payload") + return + } + + var devAddr lorawan.DevAddr + var nwkSKey lorawan.AES128Key + var appSKey lorawan.AES128Key + copy(devAddr[:], message.Payload()[:4]) + copy(nwkSKey[:], message.Payload()[4:12]) + copy(appSKey[:], message.Payload()[12:]) + + a.registrations <- core.Registration{ + DevAddr: devAddr, + Recipient: core.Recipient{ + Id: appEUI, + Address: "DoestNotMatterWillBeRefactored", + }, + Options: struct { + NwkSKey lorawan.AES128Key + AppSKey lorawan.AES128Key + }{ + NwkSKey: nwkSKey, + AppSKey: appSKey, + }, + } } // Send implements the core.Adapter interface @@ -43,5 +101,6 @@ func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { // NextRegistration implements the core.Adapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return core.Registration{}, nil, nil + r := <-a.registrations + return r, nil, nil } diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index 160fc21e1..ea78d200d 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -148,11 +148,9 @@ func (m *Mosquitto) Publish(p publicationShape) { } func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adapter, *Mosquitto) { - mqttBroker := fmt.Sprintf("tcp://localhost:%d", port) - // Prepare client opts := MQTT.NewClientOptions() - opts.AddBroker(mqttBroker) + opts.AddBroker(fmt.Sprintf("tcp://localhost:%d", port)) opts.SetClientID("TestClient") client := MQTT.NewClient(opts) if token := client.Connect(); token.Wait() && token.Error() != nil { @@ -171,7 +169,7 @@ func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adap // Prepare adapter ctx := GetLogger(t, "Adapter") - adapter, err := NewAdapter(mqttBroker, ctx) + adapter, err := NewAdapter(client, ctx) if err != nil { panic(err) } From efcd29d618e0a7c9c65fb0e9932de2c57e53eff3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 22:38:16 +0100 Subject: [PATCH 0609/2266] [refactor] Fix oversight and close client between tests --- core/adapters/mosquitto/adapter.go | 6 +++--- core/adapters/mosquitto/adapter_test.go | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go index b8b2dfaac..ad190b1f8 100644 --- a/core/adapters/mosquitto/adapter.go +++ b/core/adapters/mosquitto/adapter.go @@ -61,7 +61,7 @@ func (a *Adapter) handleActivation(client *MQTT.Client, message MQTT.Message) { } payload := message.Payload() - if len(payload) != 20 { + if len(payload) != 36 { a.ctx.WithField("Payload", payload).Error("Invalid registration payload") return } @@ -70,8 +70,8 @@ func (a *Adapter) handleActivation(client *MQTT.Client, message MQTT.Message) { var nwkSKey lorawan.AES128Key var appSKey lorawan.AES128Key copy(devAddr[:], message.Payload()[:4]) - copy(nwkSKey[:], message.Payload()[4:12]) - copy(appSKey[:], message.Payload()[12:]) + copy(nwkSKey[:], message.Payload()[4:20]) + copy(appSKey[:], message.Payload()[20:]) a.registrations <- core.Registration{ DevAddr: devAddr, diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go index ea78d200d..04d5f5224 100644 --- a/core/adapters/mosquitto/adapter_test.go +++ b/core/adapters/mosquitto/adapter_test.go @@ -132,6 +132,9 @@ func TestNext(t *testing.T) { // Check checkErrors(t, test.WantError, err) checkPackets(t, test.WantPacket, packet) + + mosquitto.MQTT.Disconnect(0) + <-time.After(100 * time.Millisecond) } } From 1e205ea99b7c4dc80883a4c77b210c8bf5925f43 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 18 Feb 2016 22:59:51 +0100 Subject: [PATCH 0610/2266] [refactor] Continue Next() and NextRegistration method -> Need rethink definition of a packet --- core/adapters/mosquitto/adapter.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go index ad190b1f8..c4227e910 100644 --- a/core/adapters/mosquitto/adapter.go +++ b/core/adapters/mosquitto/adapter.go @@ -4,6 +4,7 @@ package mosquitto import ( + "encoding/hex" "fmt" "strings" @@ -41,7 +42,7 @@ func NewAdapter(client *MQTT.Client, ctx log.Interface) (*Adapter, error) { registrations: make(chan core.Registration), } - token := client.Subscribe(fmt.Sprintf("+/devices/+/activations"), 2, a.handleActivation) + token := client.Subscribe(fmt.Sprintf("+/%s/+/%s", RESOURCE, TOPIC_ACTIVATIONS), 2, a.handleActivation) if token.Wait() && token.Error() != nil { ctx.WithError(token.Error()).Error("Unable to instantiate the adapter") return nil, errors.New(ErrFailedOperation, token.Error()) @@ -73,6 +74,13 @@ func (a *Adapter) handleActivation(client *MQTT.Client, message MQTT.Message) { copy(nwkSKey[:], message.Payload()[4:20]) copy(appSKey[:], message.Payload()[20:]) + devEUI = fmt.Sprintf("00000000%s", hex.EncodeToString(devAddr[:])) + token := client.Subscribe(fmt.Sprintf("%s/%s/%s/%s", appEUI, RESOURCE, devEUI, TOPIC_UPLINK), 2, a.handleReception) + if token.Wait() && token.Error() != nil { + a.ctx.WithError(token.Error()).Error("Unable to subscribe") + //NOTE dedicated channel for errors ? Handled by nextRegistration ? + } + a.registrations <- core.Registration{ DevAddr: devAddr, Recipient: core.Recipient{ @@ -89,6 +97,16 @@ func (a *Adapter) handleActivation(client *MQTT.Client, message MQTT.Message) { } } +func (a *Adapter) handleReception(client *MQTT.Client, message MQTT.Message) { + topicInfos := strings.Split(message.Topic(), "/") + //appEUI := topicInfos[0] + devEUI := topicInfos[2] + + if devEUI == PERSONNALIZED { + return + } +} + // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { return core.Packet{}, nil From dcae5aa97ad7023a4cc8e6bd1304de030cc65479 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 18 Feb 2016 16:31:28 +0100 Subject: [PATCH 0611/2266] [statuspage] Add status page endpoint to router and broker --- core/adapters/http/statuspage/statuspage.go | 110 ++++++++++++++++++++ integration/broker/main.go | 6 ++ integration/router/main.go | 6 ++ utils/stats/stats.go | 13 +++ 4 files changed, 135 insertions(+) create mode 100644 core/adapters/http/statuspage/statuspage.go create mode 100644 utils/stats/stats.go diff --git a/core/adapters/http/statuspage/statuspage.go b/core/adapters/http/statuspage/statuspage.go new file mode 100644 index 000000000..fda8ce9ad --- /dev/null +++ b/core/adapters/http/statuspage/statuspage.go @@ -0,0 +1,110 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package statuspage extends the basic http to show statistics on GET /status +// +// The adapter registers a new endpoint [/status/] to an original basic http adapter and serves statistics on it. +package statuspage + +import ( + "encoding/json" + "net/http" + "strings" + + httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/apex/log" + "github.com/rcrowley/go-metrics" +) + +// Adapter extending the default http adapter +type Adapter struct { + *httpadapter.Adapter // The original http adapter + ctx log.Interface // Logging context +} + +// NewAdapter constructs a new http adapter that also handles status requests +func NewAdapter(adapter *httpadapter.Adapter, ctx log.Interface) (*Adapter, error) { + a := &Adapter{ + Adapter: adapter, + ctx: ctx, + } + + a.RegisterEndpoint("/status/", a.handleStatus) + + return a, nil +} + +// handle request [GET] on /status +func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { + ctx := a.ctx.WithField("sender", req.RemoteAddr) + ctx.Debug("Receiving new status request") + + // Check the http method + if req.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [GET] to get the status")) + return + } + + if strings.Split(req.RemoteAddr, ":")[0] != "127.0.0.1" { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte("Status is only available from the local host")) + return + } + + allStats := statData{} + + metrics.Each(func(name string, i interface{}) { + switch metric := i.(type) { + + case metrics.Histogram: + h := metric.Snapshot() + ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) + + allStats[name] = histData{ + Avg: h.Mean(), + Min: h.Min(), + Max: h.Max(), + P25: ps[0], + P50: ps[1], + P75: ps[2], + } + + case metrics.Meter: + m := metric.Snapshot() + allStats[name] = meterData{ + Rate1: m.Rate1(), + Rate5: m.Rate5(), + Rate15: m.Rate15(), + Count: m.Count(), + } + } + }) + + response, err := json.Marshal(allStats) + if err != nil { + panic(err) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(response) +} + +type statData map[string]interface{} + +type meterData struct { + Rate1 float64 `json:"rate_1"` + Rate5 float64 `json:"rate_5"` + Rate15 float64 `json:"rate_15"` + Count int64 `json:"count"` +} + +type histData struct { + Avg float64 `json:"avg"` + Min int64 `json:"min"` + Max int64 `json:"max"` + P25 float64 `json:"p_25"` + P50 float64 `json:"p_50"` + P75 float64 `json:"p_75"` +} diff --git a/integration/broker/main.go b/integration/broker/main.go index 39579a789..80bba59aa 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" + "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" "github.com/TheThingsNetwork/ttn/core/components" "github.com/apex/log" "github.com/apex/log/handlers/text" @@ -39,6 +40,11 @@ func main() { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } + _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") diff --git a/integration/router/main.go b/integration/router/main.go index 6dff3aa21..c6c3cacaf 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -14,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" "github.com/TheThingsNetwork/ttn/core/adapters/semtech" "github.com/TheThingsNetwork/ttn/core/components" "github.com/apex/log" @@ -42,6 +43,11 @@ func main() { ctx.WithError(err).Fatal("Could not start Broker Adapter") } + _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") diff --git a/utils/stats/stats.go b/utils/stats/stats.go new file mode 100644 index 000000000..e87a3dcaa --- /dev/null +++ b/utils/stats/stats.go @@ -0,0 +1,13 @@ +package stats + +import "github.com/rcrowley/go-metrics" + +// MarkMeter registers an event +func MarkMeter(name string) { + metrics.GetOrRegisterMeter(name, metrics.DefaultRegistry).Mark(1) +} + +// UpdateHistogram registers a new value for a histogram +func UpdateHistogram(name string, value int64) { + metrics.GetOrRegisterHistogram(name, metrics.DefaultRegistry, metrics.NewUniformSample(1000)).Update(value) +} From c8f7be990a435ce838fccff306db9f63af493c52 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 18 Feb 2016 16:32:16 +0100 Subject: [PATCH 0612/2266] [statuspage] Add stats collection to semtech, http and broadcast adapters --- core/adapters/http/adapter.go | 4 ++++ core/adapters/http/broadcast/broadcast.go | 4 ++++ core/adapters/semtech/adapter.go | 3 +++ 3 files changed, 11 insertions(+) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 447bd184c..708e9f0d7 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -17,6 +17,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" ) @@ -59,6 +60,9 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { + stats.MarkMeter("http_adapter.send") + stats.UpdateHistogram("http_adapter.send_recipients", int64(len(r))) + // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index a9c274002..bdf960020 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -17,6 +17,7 @@ import ( httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" ) @@ -56,6 +57,9 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // broadcast is merely a send where recipients are the predefined list used at instantiation time. // Beside, a registration request will be triggered if one of the recipient reponses positively. func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { + stats.MarkMeter("http_adapter.broadcast") + stats.UpdateHistogram("http_adapter.broadcast_recipients", int64(len(a.recipients))) + // Generate payload from core packet m, err := json.Marshal(p.Metadata) if err != nil { diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index c8dd9564e..4b0716ca6 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -11,6 +11,7 @@ import ( . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" ) @@ -103,6 +104,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { switch pkt.Identifier { case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK + stats.MarkMeter("semtech_adapter.pull_data") pullAck, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, @@ -115,6 +117,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { a.ctx.WithField("recipient", addr).Debug("Sending PULL_ACK") a.conn <- udpMsg{addr: addr, raw: pullAck} case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component + stats.MarkMeter("semtech_adapter.push_data") pushAck, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, From 664752606277752db76ec47aa08ca899a3138fc1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 18 Feb 2016 16:32:44 +0100 Subject: [PATCH 0613/2266] [statuspage] Add stats collection to router and broker components --- core/components/broker.go | 20 +++++++++++++++++++- core/components/router.go | 17 +++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/core/components/broker.go b/core/components/broker.go index c8f78352a..e5007c0ed 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -7,6 +7,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -27,10 +28,13 @@ func NewBroker(db BrokerStorage, ctx log.Interface) *Broker { // HandleUp implements the core.Component interface func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { - b.ctx.Debug("Handle uplink packet") + stats.MarkMeter("broker.uplink.in") + b.ctx.Debug("Handling uplink packet") + // 1. Lookup for entries for the associated device devAddr, err := p.DevAddr() if err != nil { + stats.MarkMeter("broker.uplink.invalid") b.ctx.Warn("Uplink Invalid") an.Nack() return errors.New(ErrInvalidStructure, err) @@ -40,13 +44,16 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter if err != nil { switch err.(errors.Failure).Nature { case ErrWrongBehavior: + stats.MarkMeter("broker.uplink.device_not_registered") ctx.Warn("Uplink device not found") return an.Nack() default: + b.ctx.Warn("Database lookup failed") an.Nack() return errors.New(ErrFailedOperation, err) } } + stats.UpdateHistogram("broker.handlers_per_dev_addr", int64(len(entries))) // 2. Several handler might be associated to the same device, we distinguish them using MIC // check. Only one should verify the MIC check. @@ -61,11 +68,13 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter Id: entry.Id, Address: entry.Url, } + stats.MarkMeter("broker.uplink.match_handler") ctx.WithField("handler", handler).Debug("Associated device with handler") break } } if handler == nil { + stats.MarkMeter("broker.uplink.no_match_handler") ctx.Warn("Could not find handler for device") return an.Nack() } @@ -73,9 +82,12 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter // 3. If one was found, we forward the packet and wait for the response response, err := adapter.Send(p, *handler) if err != nil { + stats.MarkMeter("broker.uplink.bad_handler_response") an.Nack() return errors.New(ErrFailedOperation, err) } + + stats.MarkMeter("broker.uplink.ok") return an.Ack(&response) } @@ -86,6 +98,9 @@ func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) er // Register implements the core.Component interface func (b *Broker) Register(r core.Registration, an core.AckNacker) error { + stats.MarkMeter("broker.registration.in") + b.ctx.Debug("Handling registration") + id, okId := r.Recipient.Id.(string) url, okUrl := r.Recipient.Address.(string) nwkSKey, okNwkSKey := r.Options.(lorawan.AES128Key) @@ -93,6 +108,7 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { ctx := b.ctx.WithField("devAddr", r.DevAddr) if !(okId && okUrl && okNwkSKey) { + stats.MarkMeter("broker.registration.invalid") ctx.Warn("Invalid Registration") an.Nack() return errors.New(ErrInvalidStructure, "Invalid registration params") @@ -100,11 +116,13 @@ func (b *Broker) Register(r core.Registration, an core.AckNacker) error { entry := brokerEntry{Id: id, Url: url, NwkSKey: nwkSKey} if err := b.db.Store(r.DevAddr, entry); err != nil { + stats.MarkMeter("broker.registration.failed") ctx.WithError(err).Error("Failed Registration") an.Nack() return errors.New(ErrFailedOperation, err) } + stats.MarkMeter("broker.registration.ok") ctx.Debug("Successful Registration") return an.Ack(nil) } diff --git a/core/components/router.go b/core/components/router.go index 3659b50c7..8b0e4ce02 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -9,6 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" ) @@ -27,11 +28,16 @@ func NewRouter(db RouterStorage, ctx log.Interface) *Router { // Register implements the core.Component interface func (r *Router) Register(reg core.Registration, an core.AckNacker) error { + stats.MarkMeter("router.registration.in") + r.ctx.Debug("Handling registration") + entry := routerEntry{Recipient: reg.Recipient} if err := r.db.Store(reg.DevAddr, entry); err != nil { + stats.MarkMeter("router.registration.failed") an.Nack() return err } + stats.MarkMeter("router.registration.ok") return an.Ack(nil) } @@ -42,16 +48,23 @@ func (r *Router) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.A // HandleUp implements the core.Component interface func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { + stats.MarkMeter("router.uplink.in") + r.ctx.Debug("Handling uplink packet") + var err error + // Lookup for an existing broker devAddr, err := p.DevAddr() if err != nil { + stats.MarkMeter("broker.uplink.invalid_packet") + r.ctx.Warn("Invalid uplink packet") an.Nack() return err } entry, err := r.db.Lookup(devAddr) if err != nil && err.(errors.Failure).Nature != ErrWrongBehavior { + r.ctx.Warn("Database lookup failed") an.Nack() return err } @@ -64,8 +77,12 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt } if err != nil { + stats.MarkMeter("router.uplink.bad_broker_response") + r.ctx.WithError(err).Warn("Invalid response from Broker") an.Nack() return err } + + stats.MarkMeter("router.uplink.ok") return an.Ack(&response) } From 79c6721bd4d78847ef6bc278524c7df0ae48bce3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 18 Feb 2016 16:53:49 +0100 Subject: [PATCH 0614/2266] [statuspage] Track how many broadcasts/sends are waiting --- core/adapters/http/adapter.go | 2 ++ core/adapters/http/broadcast/broadcast.go | 2 ++ core/adapters/http/statuspage/statuspage.go | 4 ++++ utils/stats/stats.go | 8 ++++++++ 4 files changed, 16 insertions(+) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 708e9f0d7..607d39103 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -139,7 +139,9 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) } // Wait for each request to be done, and return + stats.IncCounter("http_adapter.waiting_for_send") wg.Wait() + stats.DecCounter("http_adapter.waiting_for_send") // Collect errors var errs []error diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index bdf960020..0f347eb64 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -136,7 +136,9 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { } // Wait for each request to be done, and return + stats.IncCounter("http_adapter.waiting_for_broadcast") wg.Wait() + stats.DecCounter("http_adapter.waiting_for_broadcast") close(cherr) close(register) var errs []error diff --git a/core/adapters/http/statuspage/statuspage.go b/core/adapters/http/statuspage/statuspage.go index fda8ce9ad..5dc499512 100644 --- a/core/adapters/http/statuspage/statuspage.go +++ b/core/adapters/http/statuspage/statuspage.go @@ -57,6 +57,10 @@ func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { metrics.Each(func(name string, i interface{}) { switch metric := i.(type) { + case metrics.Counter: + m := metric.Snapshot() + allStats[name] = m.Count() + case metrics.Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) diff --git a/utils/stats/stats.go b/utils/stats/stats.go index e87a3dcaa..8ecce4013 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -11,3 +11,11 @@ func MarkMeter(name string) { func UpdateHistogram(name string, value int64) { metrics.GetOrRegisterHistogram(name, metrics.DefaultRegistry, metrics.NewUniformSample(1000)).Update(value) } + +func IncCounter(name string) { + metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Inc(1) +} + +func DecCounter(name string) { + metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Dec(1) +} From ba900debdf8c7b1a42f1bd17fdee6123781a14ab Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 10:56:23 +0100 Subject: [PATCH 0615/2266] [statuspage] Add documentation to stats util --- utils/stats/stats.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 8ecce4013..d20beda1a 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -1,3 +1,7 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package stats supports the collection of metrics from a running component. package stats import "github.com/rcrowley/go-metrics" @@ -12,10 +16,12 @@ func UpdateHistogram(name string, value int64) { metrics.GetOrRegisterHistogram(name, metrics.DefaultRegistry, metrics.NewUniformSample(1000)).Update(value) } +// IncCounter increments a counter by 1 func IncCounter(name string) { metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Inc(1) } +// DecCounter decrements a counter by 1 func DecCounter(name string) { metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Dec(1) } From 3842d3b1e3c0f558e8e81a3be8cb287f6bc9c8c3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 11:29:41 +0100 Subject: [PATCH 0616/2266] [statuspage] Fix localhost detection in statuspage --- core/adapters/http/statuspage/statuspage.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/adapters/http/statuspage/statuspage.go b/core/adapters/http/statuspage/statuspage.go index 5dc499512..bdaceb215 100644 --- a/core/adapters/http/statuspage/statuspage.go +++ b/core/adapters/http/statuspage/statuspage.go @@ -8,8 +8,9 @@ package statuspage import ( "encoding/json" + "fmt" + "net" "net/http" - "strings" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/apex/log" @@ -46,9 +47,17 @@ func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { return } - if strings.Split(req.RemoteAddr, ":")[0] != "127.0.0.1" { + remoteHost, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + //The HTTP server did not set RemoteAddr to IP:port, which would be very very strange. + w.WriteHeader(http.StatusInternalServerError) + return + } + + remoteIP := net.ParseIP(remoteHost) + if remoteIP == nil || !remoteIP.IsLoopback() { w.WriteHeader(http.StatusForbidden) - w.Write([]byte("Status is only available from the local host")) + w.Write([]byte(fmt.Sprintf("Status is only available from the local host, not from %s", remoteIP))) return } From 8510558a26b743ec1228e1e49f43db810a3576b9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 14:37:23 +0100 Subject: [PATCH 0617/2266] [simulator] Periodically add `stat` to forwarded packets --- simulators/gateway/forwarder.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 5586ca30f..5e4fb90b7 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -47,6 +47,8 @@ const ( cmd_STATS commandName = "Stats" ) +var statTimer <-chan time.Time + // NewForwarder create a forwarder instance bound to a set of routers. func NewForwarder(id [8]byte, ctx log.Interface, adapters ...io.ReadWriteCloser) (*Forwarder, error) { if len(adapters) == 0 { @@ -72,6 +74,8 @@ func NewForwarder(id [8]byte, ctx log.Interface, adapters ...io.ReadWriteCloser) go fwd.handleCommands() + statTimer = time.Tick(5 * time.Second) + // Star listening to each adapter Read() method for i, adapter := range fwd.adapters { go fwd.listenAdapter(adapter, i) @@ -170,6 +174,13 @@ func (fwd Forwarder) Forward(rxpks ...semtech.RXPK) error { Payload: &semtech.Payload{RXPK: rxpks}, } + select { + case <-statTimer: + stats := fwd.Stats() + packet.Payload.Stat = &stats + default: + } + raw, err := packet.MarshalBinary() if err != nil { return err From d02cb5f65b3ef10fecb6221e48c70c20d7960509 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 17:19:08 +0100 Subject: [PATCH 0618/2266] Change structure of statuspage --- core/adapters/http/adapter.go | 6 +- core/adapters/http/broadcast/broadcast.go | 6 +- core/adapters/http/statuspage/statuspage.go | 63 ++++++++++----------- core/components/broker.go | 8 +-- core/components/router.go | 2 +- 5 files changed, 40 insertions(+), 45 deletions(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index 607d39103..a07802d6b 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -61,7 +61,7 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { stats.MarkMeter("http_adapter.send") - stats.UpdateHistogram("http_adapter.send_recipients", int64(len(r))) + stats.UpdateHistogram("http_adapter.send.recipients", int64(len(r))) // Generate payload from core packet m, err := json.Marshal(p.Metadata) @@ -139,9 +139,9 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) } // Wait for each request to be done, and return - stats.IncCounter("http_adapter.waiting_for_send") + stats.IncCounter("http_adapter.send.waiting") wg.Wait() - stats.DecCounter("http_adapter.waiting_for_send") + stats.DecCounter("http_adapter.send.waiting") // Collect errors var errs []error diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go index 0f347eb64..a44c1f3ab 100644 --- a/core/adapters/http/broadcast/broadcast.go +++ b/core/adapters/http/broadcast/broadcast.go @@ -58,7 +58,7 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) // Beside, a registration request will be triggered if one of the recipient reponses positively. func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { stats.MarkMeter("http_adapter.broadcast") - stats.UpdateHistogram("http_adapter.broadcast_recipients", int64(len(a.recipients))) + stats.UpdateHistogram("http_adapter.broadcast.recipients", int64(len(a.recipients))) // Generate payload from core packet m, err := json.Marshal(p.Metadata) @@ -136,9 +136,9 @@ func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { } // Wait for each request to be done, and return - stats.IncCounter("http_adapter.waiting_for_broadcast") + stats.IncCounter("http_adapter.broadcast.waiting") wg.Wait() - stats.DecCounter("http_adapter.waiting_for_broadcast") + stats.DecCounter("http_adapter.broadcast.waiting") close(cherr) close(register) var errs []error diff --git a/core/adapters/http/statuspage/statuspage.go b/core/adapters/http/statuspage/statuspage.go index bdaceb215..5e3f9a3ee 100644 --- a/core/adapters/http/statuspage/statuspage.go +++ b/core/adapters/http/statuspage/statuspage.go @@ -11,6 +11,7 @@ import ( "fmt" "net" "net/http" + "strings" httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/apex/log" @@ -61,36 +62,48 @@ func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { return } - allStats := statData{} + allStats := make(map[string]interface{}) metrics.Each(func(name string, i interface{}) { + // Make sure we put things in the right place + thisStat := allStats + for _, path := range strings.Split(name, ".") { + if thisStat[path] == nil { + thisStat[path] = make(map[string]interface{}) + } + if _, ok := thisStat[path].(map[string]interface{}); ok { + thisStat = thisStat[path].(map[string]interface{}) + } else { + ctx.Errorf("Error building %s stat", name) + return + } + } + switch metric := i.(type) { case metrics.Counter: m := metric.Snapshot() - allStats[name] = m.Count() + thisStat["count"] = m.Count() case metrics.Histogram: h := metric.Snapshot() ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) - allStats[name] = histData{ - Avg: h.Mean(), - Min: h.Min(), - Max: h.Max(), - P25: ps[0], - P50: ps[1], - P75: ps[2], - } + thisStat["avg"] = h.Mean() + thisStat["min"] = h.Min() + thisStat["max"] = h.Max() + thisStat["p_25"] = ps[0] + thisStat["p_50"] = ps[1] + thisStat["p_75"] = ps[2] case metrics.Meter: m := metric.Snapshot() - allStats[name] = meterData{ - Rate1: m.Rate1(), - Rate5: m.Rate5(), - Rate15: m.Rate15(), - Count: m.Count(), - } + + thisStat["rate_1"] = m.Rate1() + thisStat["rate_5"] = m.Rate5() + thisStat["rate_15"] = m.Rate15() + thisStat["count"] = m.Count() + } }) @@ -103,21 +116,3 @@ func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) w.Write(response) } - -type statData map[string]interface{} - -type meterData struct { - Rate1 float64 `json:"rate_1"` - Rate5 float64 `json:"rate_5"` - Rate15 float64 `json:"rate_15"` - Count int64 `json:"count"` -} - -type histData struct { - Avg float64 `json:"avg"` - Min int64 `json:"min"` - Max int64 `json:"max"` - P25 float64 `json:"p_25"` - P50 float64 `json:"p_50"` - P75 float64 `json:"p_75"` -} diff --git a/core/components/broker.go b/core/components/broker.go index e5007c0ed..e0bb4b0b9 100644 --- a/core/components/broker.go +++ b/core/components/broker.go @@ -44,7 +44,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter if err != nil { switch err.(errors.Failure).Nature { case ErrWrongBehavior: - stats.MarkMeter("broker.uplink.device_not_registered") + stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") ctx.Warn("Uplink device not found") return an.Nack() default: @@ -53,7 +53,7 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter return errors.New(ErrFailedOperation, err) } } - stats.UpdateHistogram("broker.handlers_per_dev_addr", int64(len(entries))) + stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) // 2. Several handler might be associated to the same device, we distinguish them using MIC // check. Only one should verify the MIC check. @@ -68,13 +68,13 @@ func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter Id: entry.Id, Address: entry.Url, } - stats.MarkMeter("broker.uplink.match_handler") + stats.MarkMeter("broker.uplink.handler_lookup.match") ctx.WithField("handler", handler).Debug("Associated device with handler") break } } if handler == nil { - stats.MarkMeter("broker.uplink.no_match_handler") + stats.MarkMeter("broker.uplink.handler_lookup.no_match") ctx.Warn("Could not find handler for device") return an.Nack() } diff --git a/core/components/router.go b/core/components/router.go index 8b0e4ce02..1b731bfc5 100644 --- a/core/components/router.go +++ b/core/components/router.go @@ -56,7 +56,7 @@ func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapt // Lookup for an existing broker devAddr, err := p.DevAddr() if err != nil { - stats.MarkMeter("broker.uplink.invalid_packet") + stats.MarkMeter("broker.uplink.invalid") r.ctx.Warn("Invalid uplink packet") an.Nack() return err From ff56be7aea743f7097d377a1e0edba147385013a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 18:30:35 +0100 Subject: [PATCH 0619/2266] [semtech_adapter] increase buffer size We have to support the Semtech forwarder that can send datagrams of up to 4550 bytes. --- core/adapters/semtech/adapter.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index 4b0716ca6..cd11b5f50 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -87,7 +87,11 @@ func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") for { - buf := make([]byte, 512) + // The Semtech packet forwarder uses a buffer size of ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) + // Where NB_PKT_MAX = 8 and STATUS_SIZE = 200 + // We are collecting statistics to see if we can reduce the buffer size (see below). + buf := make([]byte, 4550) + n, addr, err := conn.ReadFromUDP(buf) if err != nil { // Problem with the connection a.ctx.WithError(err).Error("Connection error") @@ -95,6 +99,9 @@ func (a *Adapter) listen(conn *net.UDPConn) { } a.ctx.Debug("Incoming datagram") + // Collect statistics for buffer size reduction + stats.UpdateHistogram("semtech_adapter.push_data.size", int64(len(buf))) + pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) if err != nil { From afb31fce235a01020c99081600b284f1a55b843c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 19:26:50 +0100 Subject: [PATCH 0620/2266] Get the actual size of the datagram --- core/adapters/semtech/adapter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go index cd11b5f50..ccc0c08cf 100644 --- a/core/adapters/semtech/adapter.go +++ b/core/adapters/semtech/adapter.go @@ -100,7 +100,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { a.ctx.Debug("Incoming datagram") // Collect statistics for buffer size reduction - stats.UpdateHistogram("semtech_adapter.push_data.size", int64(len(buf))) + stats.UpdateHistogram("semtech_adapter.push_data.size", int64(n)) pkt := new(semtech.Packet) err = pkt.UnmarshalBinary(buf[:n]) From a114fa43f66193cdcd0e2f6ab649c69525ae8b41 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 19 Feb 2016 19:30:26 +0100 Subject: [PATCH 0621/2266] Simulator sends multiple RXPKs in one PUSH_DATA --- simulators/gateway/imitator.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index b189671e2..f3f824cf3 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "io/ioutil" + "math/rand" "net" "os" "time" @@ -104,17 +105,27 @@ func MockRandomly(nodes []node.LiveNode, ctx log.Interface, routers ...string) { } for { - message := <-messages + rxpks := [8]semtech.RXPK{} + numPks := rand.Intn(8) + 1 - rxpk := semtech.RXPK{ - Rssi: pointer.Int(-20), - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("LORA"), - Data: pointer.String(message), + for i := 0; i < numPks; i++ { + message := <-messages + + rxpk := semtech.RXPK{ + Rssi: pointer.Int(-20), + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("LORA"), + Data: pointer.String(message), + } + + rxpks[i] = rxpk } - if err := fwd.Forward(rxpk); err != nil { - ctx.WithError(err).WithField("rxpk", rxpk).Warn("failed to forward") + err := fwd.Forward(rxpks[:numPks]...) + if err != nil { + ctx.WithError(err).Warn("failed to forward") + } else { + ctx.Debugf("Forwarded %d packets.", numPks) } } } From ec8c7c15acc429d1a0ec409426f0e3fc7ad37fa3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 11:00:41 +0100 Subject: [PATCH 0622/2266] Include build date and git commit in binaries --- build_binaries.sh | 24 +++++++++++++++++++----- integration/broker/main.go | 7 +++++++ integration/router/main.go | 7 +++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/build_binaries.sh b/build_binaries.sh index a3088611d..6b53eb8cb 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -7,6 +7,8 @@ RELEASEPATH=${RELEASEPATH:-$TTNROOT/release} mkdir -p $RELEASEPATH +git_commit=$(git rev-parse HEAD) + build_release() { component=$1 @@ -26,17 +28,29 @@ build_release() binary_name=$release_name$ext - echo "Building $component for $GOOS/$GOARCH" + build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ) + + echo "$build_date - Building $component for $GOOS/$GOARCH..." # Build cd $TTNROOT - go build -a -installsuffix cgo -ldflags '-w' -o $RELEASEPATH/$binary_name ./integration/$component/main.go + + go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./integration/$component/main.go # Compress cd $RELEASEPATH - tar -cvzf $release_name.tar.gz $binary_name - tar -cvJf $release_name.tar.xz $binary_name - zip $release_name.zip $binary_name + + echo -n " - Compressing tar.gz... " + tar -czf $release_name.tar.gz $binary_name + echo " Done" + + echo -n " - Compressing tar.xz... " + tar -cJf $release_name.tar.xz $binary_name + echo " Done" + + echo -n " - Compressing zip... " + zip -q $release_name.zip $binary_name > /dev/null + echo " Done" # Delete Binary rm $binary_name diff --git a/integration/broker/main.go b/integration/broker/main.go index 80bba59aa..fcdd424bb 100644 --- a/integration/broker/main.go +++ b/integration/broker/main.go @@ -18,6 +18,11 @@ import ( "github.com/apex/log/handlers/text" ) +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) @@ -29,6 +34,8 @@ func main() { // Parse options routersPort, handlersPort := parseOptions() + ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") + // Instantiate all components rtrAdapter, err := http.NewAdapter(uint(routersPort), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) if err != nil { diff --git a/integration/router/main.go b/integration/router/main.go index c6c3cacaf..9505f0e3d 100644 --- a/integration/router/main.go +++ b/integration/router/main.go @@ -21,6 +21,11 @@ import ( "github.com/apex/log/handlers/text" ) +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + func main() { // Create Logging Context log.SetHandler(text.New(os.Stdout)) @@ -32,6 +37,8 @@ func main() { // Parse options brokers, tcpPort, udpPort := parseOptions() + ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") + // Instantiate all components gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter")) if err != nil { From 33722328e3d63824fde8330d649dcc067cd0e0c8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:00:16 +0100 Subject: [PATCH 0623/2266] [refactor] Define new core interfaces. More modular --- refactor/interfaces.go | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 refactor/interfaces.go diff --git a/refactor/interfaces.go b/refactor/interfaces.go new file mode 100644 index 000000000..19f46a6f5 --- /dev/null +++ b/refactor/interfaces.go @@ -0,0 +1,67 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding" + "time" + + "github.com/brocaar/lorawan" +) + +type Component interface { + Register(reg Registration, an AckNacker) error + HandleUp(p []byte, an AckNacker, upAdapter Adapter) error + HandleDown(p []byte, an AckNacker, downAdapter Adapter) error +} + +type AckNacker interface { + Ack(p *Packet) error + Nack() error +} + +type Adapter interface { + Send(p Packet, r ...Recipient) ([]byte, error) + //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) + Next() ([]byte, AckNacker, error) + NextRegistration() (Registration, AckNacker, error) +} + +type Recipient []byte + +type Packet interface { + encoding.BinaryMarshaler + Payload() encoding.BinaryMarshaler + Metadata() []Metadata + + FCnt() (uint32, error) + AppEUI() (lorawan.EUI64, error) + DevEUI() (lorawan.EUI64, error) +} + +type Registration struct { + Recipient Recipient + Options interface{} +} + +type Metadata struct { + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} From 6efd005cce9616fff24b5e9911e2c552a9b2a9d9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:00:43 +0100 Subject: [PATCH 0624/2266] [refactor] Move metadata and old packet into RPacket --- refactor/metadata.go | 92 +++++++++++++++++++++++++ refactor/rpacket.go | 159 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 251 insertions(+) create mode 100644 refactor/metadata.go create mode 100644 refactor/rpacket.go diff --git a/refactor/metadata.go b/refactor/metadata.go new file mode 100644 index 000000000..0b306b366 --- /dev/null +++ b/refactor/metadata.go @@ -0,0 +1,92 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding/json" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" +) + +// metadata allows us to inherit Metadata in metadataProxy but only by extending the exported +// attributes of Metadata such that, they are still parsed by the json.Marshaller / +// json.Unmarshaller though we do not end up with a recursive hellish error. +type metadata Metadata + +// MarshalJSON implements the json.Marshal interface +func (m Metadata) MarshalJSON() ([]byte, error) { + var d *semtech.Datrparser + var t *semtech.Timeparser + + // Handle datr which can be either an uint or string depending of the modulation + if m.Datr != nil { + d = new(semtech.Datrparser) + if m.Modu != nil && *m.Modu == "FSK" { + *d = semtech.Datrparser{Kind: "uint", Value: *m.Datr} + } else { + *d = semtech.Datrparser{Kind: "string", Value: *m.Datr} + } + } + + // Time :'( ... By default, we mashall them as RFC3339Nano and unmarshall them the best we can. + if m.Time != nil { + t = new(semtech.Timeparser) + *t = semtech.Timeparser{Layout: time.RFC3339Nano, Value: m.Time} + } + + data, err := json.Marshal(metadataProxy{ + metadata: metadata(m), + Datr: d, + Time: t, + }) + + if err != nil { + err = errors.New(ErrInvalidStructure, err) + } + + return data, err +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (m *Metadata) UnmarshalJSON(raw []byte) error { + if m == nil { + return errors.New(ErrInvalidStructure, "Cannot unmarshal nil Metadata") + } + + proxy := metadataProxy{} + if err := json.Unmarshal(raw, &proxy); err != nil { + return errors.New(ErrInvalidStructure, err) + } + *m = Metadata(proxy.metadata) + if proxy.Time != nil { + m.Time = proxy.Time.Value + } + + if proxy.Datr != nil { + m.Datr = &proxy.Datr.Value + } + + return nil +} + +// String implements the io.Stringer interface +func (m Metadata) String() string { + return pointer.DumpPStruct(m, false) +} + +// type metadataProxy is used to conveniently marshal and unmarshal Metadata structure. +// +// Datr field could be either string or uint depending on the Modu field. +// Time field could be parsed in a lot of different way depending of the time format. +// This proxy make sure that everything is marshaled and unmarshaled to the right thing and allow +// the Metadata struct to be user-friendly. +type metadataProxy struct { + metadata + Datr *semtech.Datrparser `json:"datr,omitempty"` + Time *semtech.Timeparser `json:"time,omitempty"` +} diff --git a/refactor/rpacket.go b/refactor/rpacket.go new file mode 100644 index 000000000..55ed1b9e4 --- /dev/null +++ b/refactor/rpacket.go @@ -0,0 +1,159 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding" + "encoding/base64" + "encoding/json" + "fmt" + + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// type RPacket materializes packets manipulated by the router and corresponding adapter handlers +type RPacket struct { + metadata Metadata + payload lorawan.PHYPayload +} + +func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) RPacket { + return RPacket{ + metadata: metadata, + payload: payload, + } +} + +// FCnt implements the core.Packet interface +func (p RPacket) FCnt() (uint32, error) { + if p.payload.MACPayload == nil { + return 0, errors.New(ErrInvalidStructure, "MACPayload should not be empty") + } + + macpayload, ok := p.payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return 0, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") + } + + return macpayload.FHDR.FCnt, nil +} + +// Payload implements the core.Packet interface +func (p RPacket) Payload() encoding.BinaryMarshaler { + return p.payload +} + +// Metadata implements the core.Packet interface +func (p RPacket) Metadata() []Metadata { + return []Metadata{p.metadata} +} + +// AppEUI implements the core.Packet interface +func (p RPacket) AppEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, errors.New(ErrNotSupported, "AppEUI not supported for RPacket") +} + +// DevEUI implements the core.Packet interface +func (p RPacket) DevEUI() (lorawan.EUI64, error) { + if p.payload.MACPayload == nil { + return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "MACPAyload should not be empty") + } + + macpayload, ok := p.payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") + } + + var devEUI lorawan.EUI64 + copy(devEUI[4:], macpayload.FHDR.DevAddr[:]) + return devEUI, nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p RPacket) MarshalBinary() ([]byte, error) { + return json.Marshal(p) +} + +// MarshalBinary implements the encoding.BinaryUnMarshaler interface +func (p *RPacket) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, p) +} + +// MarshalJSON implements the json.Marshaler interface +func (p RPacket) MarshalJSON() ([]byte, error) { + rawMetadata, err := json.Marshal(p.metadata) + if err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + rawPayload, err := p.payload.MarshalBinary() + if err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + strPayload := base64.StdEncoding.EncodeToString(rawPayload) + return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil +} + +// UnmarshalJSON impements the json.Unmarshaler interface +func (p *RPacket) UnmarshalJSON(raw []byte) error { + if p == nil { + return errors.New(ErrInvalidStructure, "Cannot unmarshal a nil packet") + } + + // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink + // packet. Thus, we'll assume it's an uplink packet (because that's the case most of the time) + // and check whether or not the unmarshalling process was okay. + var proxy struct { + Payload string `json:"payload"` + Metadata Metadata + } + + err := json.Unmarshal(raw, &proxy) + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + + rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + + payload := lorawan.NewPHYPayload(true) // true -> uplink + if err := payload.UnmarshalBinary(rawPayload); err != nil { + return errors.New(ErrInvalidStructure, err) + } + + // Now, we check the nature of the decoded payload + switch payload.MHDR.MType.String() { + case "JoinAccept": + fallthrough + case "UnconfirmedDataDown": + fallthrough + case "ConfirmedDataDown": + // JoinAccept, UnconfirmedDataDown and ConfirmedDataDown are all downlink messages. + // We thus have to unmarshall properly + payload = lorawan.NewPHYPayload(false) // false -> downlink + if err := payload.UnmarshalBinary(rawPayload); err != nil { + return errors.New(ErrInvalidStructure, err) + } + case "JoinRequest": + fallthrough + case "UnconfirmedDataUp": + fallthrough + case "ConfirmedDataUp": + // JoinRequest, UnconfirmedDataUp and ConfirmedDataUp are all uplink messages. + // There's nothing to do, we've already handled them. + + case "Proprietary": + // Proprietary can be either downlink or uplink. Right now, we do not have any message of + // that type and thus, we just don't know how to handle them. Let's throw an error. + return errors.New(ErrInvalidStructure, "Unsupported MType 'Proprietary'") + } + + // Packet = Payload + Metadata + p.payload = payload + p.metadata = proxy.Metadata + return nil +} From 503a2b893a1ba5f8e0009ab7ff67a0dce4339101 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:01:01 +0100 Subject: [PATCH 0625/2266] [refactor] Prepare ground for broker and handler packets as well --- refactor/bpacket.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ refactor/hpacket.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 refactor/bpacket.go create mode 100644 refactor/hpacket.go diff --git a/refactor/bpacket.go b/refactor/bpacket.go new file mode 100644 index 000000000..d4cc2be72 --- /dev/null +++ b/refactor/bpacket.go @@ -0,0 +1,48 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding" + + "github.com/brocaar/lorawan" +) + +// type BPacket materializes packets manipulated by the broker and corresponding adapter handlers +type BPacket struct{} + +// FCnt implements the core.Packet interface +func (p BPacket) FCnt() (uint32, error) { + return 0, nil +} + +// Payload implements the core.Packet interface +func (p BPacket) Payload() encoding.BinaryMarshaler { + return nil +} + +// Metadata implements the core.Packet interface +func (p BPacket) Metadata() []Metadata { + return nil +} + +// AppEUI implements the core.Packet interface +func (p BPacket) AppEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, nil +} + +// DevEUI implements the core.Packet interface +func (p BPacket) DevEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p BPacket) MarshalBinary() ([]byte, error) { + return nil, nil +} + +// MarshalBinary implements the encoding.BinaryUnMarshaler interface +func (p *BPacket) UnmarshalBinary(data []byte) error { + return nil +} diff --git a/refactor/hpacket.go b/refactor/hpacket.go new file mode 100644 index 000000000..c57ce9a15 --- /dev/null +++ b/refactor/hpacket.go @@ -0,0 +1,48 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding" + + "github.com/brocaar/lorawan" +) + +// type HPacket materializes packets manipulated by the handler and corresponding adapter handlers +type HPacket struct{} + +// FCnt implements the core.Packet interface +func (p HPacket) FCnt() (uint32, error) { + return 0, nil +} + +// Payload implements the core.Packet interface +func (p HPacket) Payload() encoding.BinaryMarshaler { + return nil +} + +// Metadata implements the core.Packet interface +func (p HPacket) Metadata() []Metadata { + return nil +} + +// AppEUI implements the core.Packet interface +func (p HPacket) AppEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, nil +} + +// DevEUI implements the core.Packet interface +func (p HPacket) DevEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p HPacket) MarshalBinary() ([]byte, error) { + return nil, nil +} + +// MarshalBinary implements the encoding.BinaryUnMarshaler interface +func (p *HPacket) UnmarshalBinary(data []byte) error { + return nil +} From e816fdd68493372587cb77f65fc0bdf6a3cdaf0c Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:01:25 +0100 Subject: [PATCH 0626/2266] [refactor] Move old errors into new folder --- refactor/errors/errors.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 refactor/errors/errors.go diff --git a/refactor/errors/errors.go b/refactor/errors/errors.go new file mode 100644 index 000000000..238564103 --- /dev/null +++ b/refactor/errors/errors.go @@ -0,0 +1,19 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package errors + +const ( + // Unsuccessful operation due to an unexpected parameter or under-relying structure. + // Another call won't change a thing, the inputs are wrong anyway. + ErrInvalidStructure = "Invalid Structure" + + // Attempt to access an unimplemented method or an unsupported operation. Fatal. + ErrNotSupported = "Unsupported Operation" + + // The operation went well though the result is unexpected and wrong. + ErrWrongBehavior = "Unexpected Behavior" + + // Something happend during the processing. Another attempt might success. + ErrFailedOperation = "Unsuccessful Operation" +) From b4f2b48b01f79c1cf08ecd9324e8ed0806b42711 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:02:04 +0100 Subject: [PATCH 0627/2266] [refactor] Rewrite semtech adapter as a udp adapter. Extract the protocol processing into a 'Handler' --- refactor/adapters/udp/adapter.go | 219 ++++++++++++++++++++++++++ refactor/adapters/udp/udpAckNacker.go | 30 ++++ 2 files changed, 249 insertions(+) create mode 100644 refactor/adapters/udp/adapter.go create mode 100644 refactor/adapters/udp/udpAckNacker.go diff --git a/refactor/adapters/udp/adapter.go b/refactor/adapters/udp/adapter.go new file mode 100644 index 000000000..2c5da2bdd --- /dev/null +++ b/refactor/adapters/udp/adapter.go @@ -0,0 +1,219 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "fmt" + "net" + + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" +) + +// Adapter represents a udp adapter which sends and receives packet via UDP +type Adapter struct { + Handler + ctx log.Interface // Just a logger + conn chan UdpMsg // Channel used to manage response transmissions made by multiple goroutines + next chan OutMsg // Incoming valid packets are pushed to this channel and consume by an outsider + ack chan AckMsg // Channel used to consume ack or nack sent to the adapter +} + +// Handler represents a datagram and packet handler used by the adapter to process packets +type Handler interface { + // HandleAck handles a positive response to a transmitter + HandleAck(p *core.Packet, resp <-chan HandlerMsg) + + // HandleNack handles a negative response to a transmitter + HandleNack(resp <-chan HandlerMsg) + + // HandleDatagram handles incoming datagram from a gateway transmitter to the network + HandleDatagram(data []byte, resp <-chan HandlerMsg) +} + +// HandlerMsg type materializes response messages emitted by the UdpHandler +type HandlerMsg struct { + Data []byte + Type HandlerMsgType +} + +type HandlerMsgType byte + +const ( + HANDLER_RESP HandlerMsgType = iota // A response towards the udp transmitter + HANDLER_OUT // A response towards the network + HANDLER_ERROR // An error during the process +) + +// AckMsg type materializes ack or nack messages flowing into the Ack channel +type AckMsg struct { + Packet *core.Packet + Type AckMsgType + Addr *net.UDPAddr + Cherr chan<- error +} + +type AckMsgType bool + +const ( + AN_ACK AckMsgType = true + AN_NACK AckMsgType = false +) + +// UdpMsg type materializes response messages transmitted towards existing recipients (commonly, gateways). +type UdpMsg struct { + Data []byte // The raw byte sequence that has to be sent + Conn *net.UDPConn // Provide if you intent to change the current adapter connection + Addr *net.UDPAddr // The target recipient address +} + +// OutMsg type materializes valid uplink messages coming from a given recipient +type OutMsg struct { + Data []byte // The actual message + Addr *net.UDPAddr // The address of the source emitter +} + +// NewAdapter constructs and allocates a new udp adapter +func NewAdapter(port uint, handler Handler, ctx log.Interface) (*Adapter, error) { + a := Adapter{ + Handler: handler, + ctx: ctx, + conn: make(chan UdpMsg), + next: make(chan OutMsg), + } + + // Create the udp connection and start listening with a goroutine + var udpConn *net.UDPConn + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + a.ctx.WithField("port", port).Info("Starting Server") + if udpConn, err = net.ListenUDP("udp", addr); err != nil { + a.ctx.WithError(err).Error("Unable to start server") + return nil, errors.New(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) + } + + go a.monitorConnection() + go a.consumeAck() + a.conn <- UdpMsg{Conn: udpConn} + go a.listen(udpConn) // Terminates when the connection is closed + + return &a, nil +} + +// Send implements the core.Adapter interface. Not implemented for the udp adapter. +func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]byte, error) { + return nil, errors.New(ErrNotSupported, "Send not supported on udp adapter") +} + +// Next implements the core.Adapter interface +func (a *Adapter) Next() ([]byte, core.AckNacker, error) { + msg := <-a.next + return msg.Data, udpAckNacker{ + Addr: msg.Addr, + }, nil +} + +// NextRegistration implements the core.Adapter interface +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + return core.Registration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") +} + +// listen Handle incoming packets and forward them. Runs in its own goroutine. +func (a *Adapter) listen(conn *net.UDPConn) { + defer conn.Close() + a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") + for { + buf := make([]byte, 5000) + n, addr, err := conn.ReadFromUDP(buf) + if err != nil { // Problem with the connection + a.ctx.WithError(err).Error("Connection error") + continue + } + + a.ctx.Debug("Incoming datagram") + chresp := make(chan HandlerMsg) + go a.HandleDatagram(buf[:n], chresp) + a.handleResp(addr, chresp) + } +} + +// monitorConnection manages udpConnection of the adapter and send message through that connection +// +// That function executes into a single goroutine and is the only one allowed to write UDP messages. +// Doing this makes sure that only 1 goroutine is interacting with the connection. It thereby allows +// the connection to be replaced at any moment (in case of failure for instance) without disturbing +// the ongoing process. +func (a *Adapter) monitorConnection() { + var udpConn *net.UDPConn + for msg := range a.conn { + if msg.Conn != nil { // Change the connection + if udpConn != nil { + a.ctx.Debug("Define new UDP connection") + udpConn.Close() + } + udpConn = msg.Conn + } + + if udpConn != nil && msg.Data != nil { // Send the given udp message + if _, err := udpConn.WriteToUDP(msg.Data, msg.Addr); err != nil { + a.ctx.WithError(err).Error("Error while sending UDP message") + } + } + } + if udpConn != nil { + udpConn.Close() // Make sure we close the connection before leaving if we dare ever leave. + } +} + +// handleResp consumes message from chresp and forward them to the adapter via the Out or Udp +// channel. +// +// The function is called each time a chan HandlerMsg is created (meaning that we need to handle an +// uplink or a response) to handle the response(s) coming from the message handler. +func (a *Adapter) handleResp(addr *net.UDPAddr, chresp <-chan HandlerMsg) error { + for msg := range chresp { + switch msg.Type { + case HANDLER_OUT: + a.conn <- UdpMsg{ + Data: msg.Data, + Addr: addr, + } + case HANDLER_RESP: + a.next <- OutMsg{ + Data: msg.Data, + Addr: addr, + } + case HANDLER_ERROR: + err := fmt.Errorf(string(msg.Data)) + a.ctx.WithError(err).Error("Unable to handle response") + return errors.New(ErrFailedOperation, err) + default: + err := errors.New(ErrFailedOperation, "Internal unexpected error while handling response") + a.ctx.Error(err.Error()) + return err + } + } + return nil +} + +// consumeAck consumes messages from the Ack channel and forward them to handleResp +// +// The function is launched in its own goroutine and run concurrently with other consumers of the +// adapter. It basically pipes acknowledgement to the right channels after having derouted the +// processing to the handler. +func (a *Adapter) consumeAck() { + for msg := range a.ack { + chresp := make(chan HandlerMsg) + switch msg.Type { + case AN_ACK: + go a.HandleAck(msg.Packet, chresp) + case AN_NACK: + go a.HandleNack(chresp) + } + err := a.handleResp(msg.Addr, chresp) + msg.Cherr <- err + close(msg.Cherr) + } +} diff --git a/refactor/adapters/udp/udpAckNacker.go b/refactor/adapters/udp/udpAckNacker.go new file mode 100644 index 000000000..e9069cc96 --- /dev/null +++ b/refactor/adapters/udp/udpAckNacker.go @@ -0,0 +1,30 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "net" + + core "github.com/TheThingsNetwork/ttn/refactor" +) + +// udpAckNacker represents an AckNacker for a udp adapter +type udpAckNacker struct { + Chack chan<- AckMsg + Addr *net.UDPAddr // The actual udp address related to that +} + +// Ack implements the core.Adapter interface +func (an udpAckNacker) Ack(p *core.Packet) error { + cherr := make(chan error) + an.Chack <- AckMsg{Type: AN_ACK, Addr: an.Addr, Packet: p, Cherr: cherr} + return <-cherr +} + +// Ack implements the core.Adapter interface +func (an udpAckNacker) Nack() error { + cherr := make(chan error) + an.Chack <- AckMsg{Type: AN_NACK, Addr: an.Addr, Packet: nil} + return <-cherr +} From e14977a28da009445965ca38a08f224aabb6a7cf Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:03:16 +0100 Subject: [PATCH 0628/2266] [refactor] Write down the semtech udp-handler. Conversion methods between semtech and RPacket has been moved there --- refactor/adapters/udp/handlers/semtech.go | 182 ++++++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 refactor/adapters/udp/handlers/semtech.go diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go new file mode 100644 index 000000000..0589463ab --- /dev/null +++ b/refactor/adapters/udp/handlers/semtech.go @@ -0,0 +1,182 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "encoding/base64" + "fmt" + "reflect" + "strings" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/brocaar/lorawan" +) + +type Semtech struct{} + +// HandleNack implements the udp.Handler interface +func (s Semtech) HandleNack(chresp chan<- udp.HandlerMsg) { + close(chresp) // There is no notion of nack in the semtech protocol +} + +// HandleAck implements the udp.Handler interface +func (s Semtech) HandleAck(packet core.Packet, chresp chan<- udp.HandlerMsg) { + defer close(chresp) + + // For the downlink, we have to send a PULL_RESP packet which hold a TXPK. + txpk, err := packet2txpk(packet) + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + return + } + + // Step 3, marshal the packet and send it to the gateway + raw, err := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{TXPK: &txpk}, + }.MarshalBinary() + + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + return + } + + chresp <- udp.HandlerMsg{Type: udp.HANDLER_RESP, Data: raw} +} + +// HandleDatagram implements the udp.Handler interface +func (s Semtech) HandleDatagram(data []byte, chresp chan<- udp.HandlerMsg) { + defer close(chresp) + pkt := new(semtech.Packet) + err := pkt.UnmarshalBinary(data) + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + return + } + + switch pkt.Identifier { + case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK + stats.MarkMeter("semtech_adapter.pull_data") + pullAck, err := semtech.Packet{ + Version: semtech.VERSION, + Token: pkt.Token, + Identifier: semtech.PULL_ACK, + }.MarshalBinary() + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + return + } + chresp <- udp.HandlerMsg{Type: udp.HANDLER_RESP, Data: pullAck} + case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component + stats.MarkMeter("semtech_adapter.push_data") + pushAck, err := semtech.Packet{ + Version: semtech.VERSION, + Token: pkt.Token, + Identifier: semtech.PUSH_ACK, + }.MarshalBinary() + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + return + } + chresp <- udp.HandlerMsg{Type: udp.HANDLER_RESP, Data: pushAck} + + if pkt.Payload == nil { + return + } + + for _, rxpk := range pkt.Payload.RXPK { + pktOut, err := rxpk2packet(rxpk) + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + continue + } + rawPkt, err := pktOut.MarshalBinary() + if err != nil { + chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + continue + } + chresp <- udp.HandlerMsg{Type: udp.HANDLER_OUT, Data: rawPkt} + } + default: + } +} + +func rxpk2packet(p semtech.RXPK) (core.Packet, error) { + // First, we have to get the physical payload which is encoded in the Data field + if p.Data == nil { + return nil, errors.New(ErrInvalidStructure, "There's no data in the packet") + } + + // RXPK Data are base64 encoded, yet without the trailing "==" if any..... + encoded := *p.Data + switch len(encoded) % 4 { + case 2: + encoded += "==" + case 3: + encoded += "=" + } + + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + + payload := lorawan.NewPHYPayload(true) + if err = payload.UnmarshalBinary(raw); err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + + // Then, we interpret every other known field as a metadata and store them into an appropriate + // metadata object. + metadata := core.Metadata{} + rxpkValue := reflect.ValueOf(p) + rxpkStruct := rxpkValue.Type() + metas := reflect.ValueOf(&metadata).Elem() + for i := 0; i < rxpkStruct.NumField(); i += 1 { + field := rxpkStruct.Field(i).Name + if metas.FieldByName(field).CanSet() { + metas.FieldByName(field).Set(rxpkValue.Field(i)) + } + } + + // At the end, our converted packet hold the same metadata than the RXPK packet but the Data + // which as been completely transformed into a lorawan Physical Payload. + return core.NewRPacket(payload, metadata), nil +} + +func packet2txpk(p core.Packet) (semtech.TXPK, error) { + // Step 1, convert the physical payload to a base64 string (without the padding) + raw, err := p.Payload().MarshalBinary() + if err != nil { + return semtech.TXPK{}, err + } + + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + txpk := semtech.TXPK{Data: pointer.String(data)} + + // Step 2, copy every compatible metadata from the packet to the TXPK packet. + // We are possibly loosing information here. + m := p.Metadata() + if len(m) != 1 { + return semtech.TXPK{}, fmt.Errorf("Invalid metadata structure") + } + metadataValue := reflect.ValueOf(m[0]) + metadataStruct := metadataValue.Type() + txpkStruct := reflect.ValueOf(&txpk).Elem() + for i := 0; i < metadataStruct.NumField(); i += 1 { + field := metadataStruct.Field(i).Name + if txpkStruct.FieldByName(field).CanSet() { + txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) + } + } + + return txpk, nil +} From dd705b7c12c78a6a2a4930d3e9a0ea035215d381 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:26:57 +0100 Subject: [PATCH 0629/2266] [refactor] Re-introduce stringer interface --- refactor/interfaces.go | 2 ++ refactor/rpacket.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 19f46a6f5..5999bb6bd 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -5,6 +5,7 @@ package refactor import ( "encoding" + "fmt" "time" "github.com/brocaar/lorawan" @@ -32,6 +33,7 @@ type Recipient []byte type Packet interface { encoding.BinaryMarshaler + fmt.Stringer Payload() encoding.BinaryMarshaler Metadata() []Metadata diff --git a/refactor/rpacket.go b/refactor/rpacket.go index 55ed1b9e4..cdef38321 100644 --- a/refactor/rpacket.go +++ b/refactor/rpacket.go @@ -27,6 +27,14 @@ func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) RPacket { } } +// String implements the Stringer interface +func (p RPacket) String() string { + str := "Packet {" + str += fmt.Sprintf("\n\t%s}", p.metadata.String()) + str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) + return str +} + // FCnt implements the core.Packet interface func (p RPacket) FCnt() (uint32, error) { if p.payload.MACPayload == nil { From 7e785c490e37b5b5134268709af3876b63f955e9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:27:24 +0100 Subject: [PATCH 0630/2266] [refactor] Bring back tests and fix needed parts in tests --- refactor/build_utilities_test.go | 142 ++++++++++++++++++++++++++ refactor/check_utilities_test.go | 87 ++++++++++++++++ refactor/metadata_test.go | 98 ++++++++++++++++++ refactor/packet_test.go | 169 +++++++++++++++++++++++++++++++ 4 files changed, 496 insertions(+) create mode 100644 refactor/build_utilities_test.go create mode 100644 refactor/check_utilities_test.go create mode 100644 refactor/metadata_test.go create mode 100644 refactor/packet_test.go diff --git a/refactor/build_utilities_test.go b/refactor/build_utilities_test.go new file mode 100644 index 000000000..389fa117f --- /dev/null +++ b/refactor/build_utilities_test.go @@ -0,0 +1,142 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding/base64" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" +) + +// Generate an RXPK packet using the given payload as Data +func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + + return semtech.RXPK{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Data: pointer.String(data), + Freq: pointer.Float64(863.125), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(uint(len([]byte(data)))), + Stat: pointer.Int(0), + Time: pointer.Time(time.Now()), + Tmst: pointer.Uint(uint(time.Now().UnixNano())), + } +} + +// Generates a TXPK packet using the given payload and the given metadata +func genTXPK(phyPayload lorawan.PHYPayload, metadata Metadata) semtech.TXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + return semtech.TXPK{ + Codr: metadata.Codr, + Data: pointer.String(data), + Datr: metadata.Datr, + Fdev: metadata.Fdev, + Freq: metadata.Freq, + Imme: metadata.Imme, + Ipol: metadata.Ipol, + Modu: metadata.Modu, + Ncrc: metadata.Ncrc, + Powe: metadata.Powe, + Prea: metadata.Prea, + Rfch: metadata.Rfch, + Size: metadata.Size, + Time: metadata.Time, + Tmst: metadata.Tmst, + } +} + +// Generate a Metadata object that matches RXPK metadata +func genMetadata(RXPK semtech.RXPK) Metadata { + return Metadata{ + Chan: RXPK.Chan, + Codr: RXPK.Codr, + Freq: RXPK.Freq, + Lsnr: RXPK.Lsnr, + Modu: RXPK.Modu, + Rfch: RXPK.Rfch, + Rssi: RXPK.Rssi, + Size: RXPK.Size, + Stat: RXPK.Stat, + Time: RXPK.Time, + Tmst: RXPK.Tmst, + } +} + +// Generates a Metadata object with all field completed with relevant values +func genFullMetadata() Metadata { + timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) + return Metadata{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Datr: pointer.String("LORA"), + Fdev: pointer.Uint(3), + Freq: pointer.Float64(863.125), + Imme: pointer.Bool(false), + Ipol: pointer.Bool(false), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Ncrc: pointer.Bool(true), + Powe: pointer.Uint(3), + Prea: pointer.Uint(8), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(14), + Stat: pointer.Int(0), + Time: pointer.Time(timeRef), + Tmst: pointer.Uint(uint(timeRef.UnixNano())), + } +} + +// Generate a Physical payload representing an uplink or downlink message +func genPHYPayload(uplink bool) lorawan.PHYPayload { + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(uplink) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(uplink) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + return payload +} diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go new file mode 100644 index 000000000..33edad4c5 --- /dev/null +++ b/refactor/check_utilities_test.go @@ -0,0 +1,87 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "fmt" + "reflect" + "regexp" + "testing" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +// Checks that two packets match +func checkPackets(t *testing.T, want Packet, got Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + + wantStr := "{}" + if want != nil { + wantStr = want.String() + } + + gotStr := "{}" + if got != nil { + gotStr = got.String() + } + + Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", wantStr, gotStr) +} + +// Checks that errors match +func checkErrors(t *testing.T, want *string, got error) { + if want == nil && got == nil || got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + + Ko(t, "Expected error to be %s but got %v", want, got) +} + +// Checks that obtained json matches expected one +func checkJSON(t *testing.T, want string, got []byte) { + str := string(got) + if str == want { + Ok(t, "check JSON") + return + } + Ko(t, "Marshaled data does not match expectations.\nWant: %s\nGot: %s", want, str) + return +} + +// Checks that obtained metadata matches expected one +func checkMetadata(t *testing.T, want Metadata, got Metadata) { + if reflect.DeepEqual(want, got) { + Ok(t, "check Metadata") + return + } + Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) +} + +// Checks that obtained TXPK matches expeceted one +func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { + if reflect.DeepEqual(want, got) { + Ok(t, "check TXPKs") + return + } + Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) +} + +// Check that obtained json strings contains the required field +func checkFields(t *testing.T, want []string, got []byte) { + for _, field := range want { + ok, err := regexp.Match(fmt.Sprintf("\"%s\":", field), got) + if !ok || err != nil { + Ko(t, "Expected field %s in %s", field, string(got)) + return + } + } + Ok(t, "Check fields") +} diff --git a/refactor/metadata_test.go b/refactor/metadata_test.go new file mode 100644 index 000000000..345e4e8a2 --- /dev/null +++ b/refactor/metadata_test.go @@ -0,0 +1,98 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding/json" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +var commonTests = []struct { + Metadata Metadata + WantError *string + JSON string +}{ + { // Basic attributes, uint, string and float64 + Metadata{Chan: pointer.Uint(2), Codr: pointer.String("4/6"), Freq: pointer.Float64(864.125)}, + nil, + `{"chan":2,"codr":"4/6","freq":864.125}`, + }, + + { // Basic attributes #2, int and bool + Metadata{Imme: pointer.Bool(true), Rssi: pointer.Int(-54)}, + nil, + `{"imme":true,"rssi":-54}`, + }, + + { // Datr attr, FSK type + Metadata{Datr: pointer.String("50000"), Modu: pointer.String("FSK")}, + nil, + `{"modu":"FSK","datr":50000}`, + }, + + { // Datr attr, lora modulation + Metadata{Datr: pointer.String("SF7BW125"), Modu: pointer.String("LORA")}, + nil, + `{"modu":"LORA","datr":"SF7BW125"}`, + }, + + { // Time attr + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC))}, + nil, + `{"time":"2016-01-06T15:11:12.000000142Z"}`, + }, + + { // Mixed + Metadata{ + Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC)), + Modu: pointer.String("FSK"), + Datr: pointer.String("50000"), + Size: pointer.Uint(14), + Lsnr: pointer.Float64(5.7), + }, + nil, + `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, + }, +} + +var unmarshalTests = []struct { + Metadata Metadata + WantError *string + JSON string +}{ + { // Local time + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 0, time.UTC))}, + nil, + `{"time":"2016-01-06 15:11:12 GMT"}`, + }, + + { // RFC3339 time + Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142000000, time.UTC))}, + nil, + `{"time":"2016-01-06T15:11:12.142000Z"}`, + }, +} + +func TestMarshaljson(t *testing.T) { + for _, test := range commonTests { + Desc(t, "Marshal medatadata: %s", test.Metadata.String()) + raw, err := json.Marshal(test.Metadata) + checkErrors(t, test.WantError, err) + checkJSON(t, test.JSON, raw) + } +} + +func TestUnmarshalJSON(t *testing.T) { + for _, test := range append(commonTests, unmarshalTests...) { + Desc(t, "Unmarshal json: %s", test.JSON) + metadata := Metadata{} + err := json.Unmarshal([]byte(test.JSON), &metadata) + checkErrors(t, test.WantError, err) + checkMetadata(t, test.Metadata, metadata) + } +} diff --git a/refactor/packet_test.go b/refactor/packet_test.go new file mode 100644 index 000000000..0418bfb6e --- /dev/null +++ b/refactor/packet_test.go @@ -0,0 +1,169 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "encoding/json" + "testing" + + "github.com/TheThingsNetwork/ttn/semtech" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func TestMarshalJSONRPacket(t *testing.T) { + tests := []marshalJSONTest{ + marshalJSONTest{ // Empty Payload + Packet: NewRPacket(lorawan.PHYPayload{}, genFullMetadata()), + WantFields: []string{}, + }, + marshalJSONTest{ // Empty Metadata + Packet: NewRPacket(genPHYPayload(true), Metadata{}), + WantFields: []string{"payload", "metadata"}, + }, + marshalJSONTest{ // With Metadata and Payload + Packet: NewRPacket(genPHYPayload(true), genFullMetadata()), + WantFields: []string{"payload", "metadata"}, + }, + } + + for _, test := range tests { + Desc(t, "Marshal packet to json: %s", test.Packet.String()) + raw, _ := json.Marshal(test.Packet) + checkFields(t, test.WantFields, raw) + } +} + +func TestUnmarshalJSONPacket(t *testing.T) { + tests := []unmarshalJSONTest{ + unmarshalJSONTest{ + JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, + WantPacket: NewRPacket(genPHYPayload(true), Metadata{}), + }, + unmarshalJSONTest{ + JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{"chan":2,"codr":"4/6","fdev":3,"freq":863.125,"imme":false,"ipol":false,"lsnr":5.2,"modu":"LORA","ncrc":true,"powe":3,"prea":8,"rfch":2,"rssi":-27,"size":14,"stat":0,"tmst":1452694288207288421,"datr":"LORA","time":"2016-01-13T14:11:28.207288421Z"}}`, + WantPacket: NewRPacket(genPHYPayload(true), genFullMetadata()), + }, + unmarshalJSONTest{ + JSON: `invalid`, + WantPacket: nil, + }, + unmarshalJSONTest{ + JSON: `{"metadata":{}}`, + WantPacket: nil, + }, + } + + for _, test := range tests { + Desc(t, "Unmarshal json to packet: %s", test.JSON) + var packet Packet + json.Unmarshal([]byte(test.JSON), &packet) + checkPackets(t, test.WantPacket, packet) + } +} + +// ---- Declaration +type convertRXPKTest struct { + CorePacket Packet + RXPK semtech.RXPK + WantError *string +} + +type convertToTXPKTest struct { + TXPK semtech.TXPK + CorePacket Packet + WantError *string +} + +type marshalJSONTest struct { + Packet Packet + WantFields []string +} + +type unmarshalJSONTest struct { + JSON string + WantPacket Packet +} + +// ---- Build utilities + +// Generates a test suite where the RXPK is fully complete +func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { + phyPayload := genPHYPayload(true) + rxpk := genRXPK(phyPayload) + metadata := genMetadata(rxpk) + test.CorePacket = NewRPacket(phyPayload, metadata) + test.RXPK = rxpk + return *test +} + +// Generates a test suite where the RXPK contains partial metadata +func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { + phyPayload := genPHYPayload(true) + rxpk := genRXPK(phyPayload) + rxpk.Codr = nil + rxpk.Rfch = nil + rxpk.Rssi = nil + rxpk.Time = nil + rxpk.Size = nil + metadata := genMetadata(rxpk) + test.CorePacket = NewRPacket(phyPayload, metadata) + test.RXPK = rxpk + return *test +} + +// Generates a test suite where the RXPK contains no data +func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { + rxpk := genRXPK(genPHYPayload(true)) + rxpk.Data = nil + test.RXPK = rxpk + return *test +} + +// Generates a test suite where the core packet has all txpk metadata +func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + metadata.Chan = nil + metadata.Lsnr = nil + metadata.Rssi = nil + metadata.Stat = nil + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = NewRPacket(phyPayload, metadata) + return *test +} + +// Generates a test suite where the core packet has no metadata +func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := Metadata{} + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = NewRPacket(phyPayload, metadata) + return *test +} + +// Generates a test suite where the core packet has partial metadata but all supported +func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + metadata.Chan = nil + metadata.Lsnr = nil + metadata.Rssi = nil + metadata.Stat = nil + metadata.Modu = nil + metadata.Fdev = nil + metadata.Time = nil + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = NewRPacket(phyPayload, metadata) + return *test +} + +// Generates a test suite where the core packet has extra metadata not supported by txpk +func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = NewRPacket(phyPayload, metadata) + return *test +} From c6440860c0afb382058bfe2b234a0ddc3219ee19 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 15:49:23 +0100 Subject: [PATCH 0631/2266] [refactor] Make tests pass again --- refactor/check_utilities_test.go | 22 +++++++++++++--------- refactor/packet_test.go | 8 ++++---- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go index 33edad4c5..71fb8800c 100644 --- a/refactor/check_utilities_test.go +++ b/refactor/check_utilities_test.go @@ -17,22 +17,26 @@ import ( // Checks that two packets match func checkPackets(t *testing.T, want Packet, got Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") + if want == nil { + if got == nil { + Ok(t, "Check packets") + return + } + Ko(t, "No packet was expected but got %s", got.String()) return } - wantStr := "{}" - if want != nil { - wantStr = want.String() + if got == nil { + Ko(t, "Was expecting %s but got nothing", want.String()) + return } - gotStr := "{}" - if got != nil { - gotStr = got.String() + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return } - Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", wantStr, gotStr) + Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) } // Checks that errors match diff --git a/refactor/packet_test.go b/refactor/packet_test.go index 0418bfb6e..9f2966fa6 100644 --- a/refactor/packet_test.go +++ b/refactor/packet_test.go @@ -35,7 +35,7 @@ func TestMarshalJSONRPacket(t *testing.T) { } } -func TestUnmarshalJSONPacket(t *testing.T) { +func TestUnmarshalJSONRPacket(t *testing.T) { tests := []unmarshalJSONTest{ unmarshalJSONTest{ JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, @@ -47,17 +47,17 @@ func TestUnmarshalJSONPacket(t *testing.T) { }, unmarshalJSONTest{ JSON: `invalid`, - WantPacket: nil, + WantPacket: RPacket{}, }, unmarshalJSONTest{ JSON: `{"metadata":{}}`, - WantPacket: nil, + WantPacket: RPacket{}, }, } for _, test := range tests { Desc(t, "Unmarshal json to packet: %s", test.JSON) - var packet Packet + packet := RPacket{} json.Unmarshal([]byte(test.JSON), &packet) checkPackets(t, test.WantPacket, packet) } From 62aefa32799d26d4f9bd55afead59c9fef0552de Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:18:23 +0100 Subject: [PATCH 0632/2266] [cli] Add main and version command --- cmd/root.go | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++ cmd/version.go | 27 +++++++++++++++++++ main.go | 21 +++++++++++++++ 3 files changed, 119 insertions(+) create mode 100644 cmd/root.go create mode 100644 cmd/version.go create mode 100644 main.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 000000000..3ef3bc8a1 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,71 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/apex/log" + "github.com/apex/log/handlers/cli" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +var ctx log.Interface + +// RootCmd is executed when ttn is executed without a subcommand +var RootCmd = &cobra.Command{ + Use: "ttn", + Short: "The Things Network", + Long: `The Things Network's backend server`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var logLevel = log.InfoLevel + if viper.GetBool("debug") { + logLevel = log.DebugLevel + } + ctx = &log.Logger{ + Level: logLevel, + Handler: cli.New(os.Stdout), + } + }, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yaml\")") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + + viper.SetConfigName(".ttn") // name of config file (without extension) + viper.AddConfigPath("$HOME") // adding home directory as first search path + viper.SetEnvPrefix("ttn") // set environment prefix + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.AutomaticEnv() // read in environment variables that match + + viper.BindEnv("debug") + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 000000000..2afa08df5 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get the version", + Long: `Get the version`, + Run: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "commit": viper.GetString("gitCommit"), + "build date": viper.GetString("buildDate"), + }).Infof("You are running %s of The Things Network.", viper.GetString("version")) + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/main.go b/main.go new file mode 100644 index 000000000..53e4d81ed --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "github.com/TheThingsNetwork/ttn/cmd" + "github.com/spf13/viper" +) + +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + +func main() { + viper.Set("version", "a development version") + viper.Set("git-commit", gitCommit) + viper.Set("build-date", buildDate) + cmd.Execute() +} From df200bf1022ec3f064a172f899454c1399353542 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:18:48 +0100 Subject: [PATCH 0633/2266] [cli] Migrate Router and Broker to viper --- cmd/broker.go | 114 ++++++++++++++++++++++++ cmd/router.go | 128 +++++++++++++++++++++++++++ integration/broker/DOWNLOADS.md | 67 -------------- integration/broker/Dockerfile | 15 ---- integration/broker/README.md | 23 ----- integration/broker/main.go | 134 ---------------------------- integration/router/DOWNLOADS.md | 67 -------------- integration/router/Dockerfile | 15 ---- integration/router/README.md | 23 ----- integration/router/main.go | 151 -------------------------------- 10 files changed, 242 insertions(+), 495 deletions(-) create mode 100644 cmd/broker.go create mode 100644 cmd/router.go delete mode 100644 integration/broker/DOWNLOADS.md delete mode 100644 integration/broker/Dockerfile delete mode 100644 integration/broker/README.md delete mode 100644 integration/broker/main.go delete mode 100644 integration/router/DOWNLOADS.md delete mode 100644 integration/router/Dockerfile delete mode 100644 integration/router/README.md delete mode 100644 integration/router/main.go diff --git a/cmd/broker.go b/cmd/broker.go new file mode 100644 index 000000000..15dfe1c5e --- /dev/null +++ b/cmd/broker.go @@ -0,0 +1,114 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" + "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" + "github.com/TheThingsNetwork/ttn/core/components" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// brokerCmd represents the router command +var brokerCmd = &cobra.Command{ + Use: "broker", + Short: "The Things Network broker", + Long: ` +The broker is responsible for finding the right handler for uplink packets it +receives from routers. This means that handlers have to register applications +and personalized devices (with their network session keys) with the router. + `, + PreRun: func(cmd *cobra.Command, args []string) { + ctx = ctx.WithField("cmd", "broker") + ctx.WithFields(log.Fields{ + "database": viper.GetString("broker.database"), + "routers-port": viper.GetInt("broker.routers-port"), + "handlers-port": viper.GetInt("broker.handlers-port"), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Instantiate all components + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Routers Adapter") + } + + hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Handlers Adapter") + } + + _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Handlers Adapter") + } + + db, err := components.NewBrokerStorage() + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) + + // Bring the service to life + + // Listen to uplink + go func() { + for { + packet, an, err := rtrAdapter.Next() + if err != nil { + ctx.WithError(err).Error("Could not retrieve uplink") + continue + } + go func(packet core.Packet, an core.AckNacker) { + if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { + ctx.WithError(err).Error("Could not process uplink") + } + }(packet, an) + } + }() + + // List to handler registrations + go func() { + for { + reg, an, err := hdlAdapter.NextRegistration() + if err != nil { + ctx.WithError(err).Error("Could not retrieve registration") + continue + } + go func(reg core.Registration, an core.AckNacker) { + if err := broker.Register(reg, an); err != nil { + ctx.WithError(err).Error("Could not process registration") + } + }(reg, an) + } + }() + + <-make(chan bool) + }, +} + +func init() { + RootCmd.AddCommand(brokerCmd) + + brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") + brokerCmd.Flags().Int("routers-port", 1690, "TCP port for connections from routers") + brokerCmd.Flags().Int("handlers-port", 1790, "TCP port for connections from handlers") + + viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) + viper.BindPFlag("broker.routers-port", brokerCmd.Flags().Lookup("routers-port")) + viper.BindPFlag("broker.handlers-port", brokerCmd.Flags().Lookup("handlers-port")) +} diff --git a/cmd/router.go b/cmd/router.go new file mode 100644 index 000000000..014abe28e --- /dev/null +++ b/cmd/router.go @@ -0,0 +1,128 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" + "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" + "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" + "github.com/TheThingsNetwork/ttn/core/adapters/semtech" + "github.com/TheThingsNetwork/ttn/core/components" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// routerCmd represents the router command +var routerCmd = &cobra.Command{ + Use: "router", + Short: "The Things Network router", + Long: `The router accepts connections from gateways and forwards uplink packets to one +or more brokers. The router is also responsible for monitoring gateways, +collecting statistics from gateways and for enforcing TTN's fair use policy when +the gateway's duty cycle is (almost) full.`, + PreRun: func(cmd *cobra.Command, args []string) { + ctx = ctx.WithField("cmd", "router") + ctx.WithFields(log.Fields{ + "database": viper.GetString("router.database"), + "gateways-port": viper.GetInt("router.gateways-port"), + "brokers": viper.GetString("router.brokers"), + "brokers-port": viper.GetInt("router.brokers-port"), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("tag", "Gateway Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Gateway Adapter") + } + + pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + var brokers []core.Recipient + brokersStr := strings.Split(viper.GetString("router.brokers"), ",") + for i := range brokersStr { + brokers = append(brokers, core.Recipient{ + Address: strings.Trim(brokersStr[i], " "), + Id: i, + }) + } + + brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Broker Adapter") + } + + db, err := components.NewRouterStorage(time.Hour * 8) + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + router := components.NewRouter(db, ctx.WithField("tag", "Router")) + + // Bring the service to life + + // Listen uplink + go func() { + for { + packet, an, err := gtwAdapter.Next() + if err != nil { + ctx.WithError(err).Warn("Could not get next packet from gateway") + continue + } + go func(packet core.Packet, an core.AckNacker) { + if err := router.HandleUp(packet, an, brkAdapter); err != nil { + ctx.WithError(err).Warn("Could not process packet from gateway") + } + }(packet, an) + } + }() + + // Listen broker registrations + go func() { + for { + reg, an, err := brkAdapter.NextRegistration() + if err != nil { + ctx.WithError(err).Warn("Could not get next registration from broker") + continue + } + go func(reg core.Registration, an core.AckNacker) { + if err := router.Register(reg, an); err != nil { + ctx.WithError(err).Warn("Could not process registration from broker") + } + }(reg, an) + } + }() + + <-make(chan bool) + }, +} + +func init() { + RootCmd.AddCommand(routerCmd) + + routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") + routerCmd.Flags().Int("gateways-port", 1680, "UDP port for connections from gateways") + routerCmd.Flags().String("brokers", "localhost:1690", "Comma-separated list of brokers") + routerCmd.Flags().Int("brokers-port", 1780, "TCP port for connections from brokers") + + viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) + viper.BindPFlag("router.gateways-port", routerCmd.Flags().Lookup("gateways-port")) + viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) + viper.BindPFlag("router.brokers-port", routerCmd.Flags().Lookup("brokers-port")) +} diff --git a/integration/broker/DOWNLOADS.md b/integration/broker/DOWNLOADS.md deleted file mode 100644 index 3bb707781..000000000 --- a/integration/broker/DOWNLOADS.md +++ /dev/null @@ -1,67 +0,0 @@ -# The Things Network Broker - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Download Links -| OS-arch | master | develop | -| ------- | ------ | ------- | -| darwin-386 | [zip][dd32z] [tar.gz][dd32g] [tar.xz][dd32x] | [zip][md32z] [tar.gz][md32g] [tar.xz][md32x] | -| darwin-amd64 | [zip][dd64z] [tar.gz][dd64g] [tar.xz][dd64x] | [zip][md64z] [tar.gz][md64g] [tar.xz][md64x] | -| linux-386 | [zip][dl32z] [tar.gz][dl32g] [tar.xz][dl32x] | [zip][ml32z] [tar.gz][ml32g] [tar.xz][ml32x] | -| linux-amd64 | [zip][dl64z] [tar.gz][dl64g] [tar.xz][dl64x] | [zip][ml64z] [tar.gz][ml64g] [tar.xz][ml64x] | -| linux-arm | [zip][dlarmz] [tar.gz][dlarmg] [tar.xz][dlarmx] | [zip][mlarmz] [tar.gz][mlarmg] [tar.xz][mlarmx] | -| windows-386 | [zip][dw32z] [tar.gz][dw32g] [tar.xz][dw32x] | [zip][mw32z] [tar.gz][mw32g] [tar.xz][mw32x] | -| windows-amd64 | [zip][dw64z] [tar.gz][dw64g] [tar.xz][dw64x] | [zip][mw64z] [tar.gz][mw64g] [tar.xz][mw64x] | - -[dd32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.zip -[dd32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.tar.gz -[dd32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-386.tar.xz -[md32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.zip -[md32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.tar.gz -[md32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-386.tar.xz - -[dd64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.zip -[dd64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.tar.gz -[dd64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-darwin-amd64.tar.xz -[md64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.zip -[md64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.tar.gz -[md64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-darwin-amd64.tar.xz - -[dl32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.zip -[dl32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.tar.gz -[dl32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-386.tar.xz -[ml32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.zip -[ml32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.tar.gz -[ml32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-386.tar.xz - -[dl64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.zip -[dl64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.gz -[dl64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.xz -[ml64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.zip -[ml64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.tar.gz -[ml64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-amd64.tar.xz - -[dlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.zip -[dlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.tar.gz -[dlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-arm.tar.xz -[mlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.zip -[mlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.tar.gz -[mlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-linux-arm.tar.xz - -[dw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.zip -[dw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.tar.gz -[dw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-386.tar.xz -[mw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.zip -[mw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.tar.gz -[mw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-386.tar.xz - -[dw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.zip -[dw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.tar.gz -[dw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-windows-amd64.tar.xz -[mw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.zip -[mw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.tar.gz -[mw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/broker-windows-amd64.tar.xz diff --git a/integration/broker/Dockerfile b/integration/broker/Dockerfile deleted file mode 100644 index b7f3eca4f..000000000 --- a/integration/broker/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine - -RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* - -RUN apk --update add curl tar \ - && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/broker-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/broker-linux-amd64.tar.gz" \ - && tar -xf /ttnsrc/broker-linux-amd64.tar.gz -C /ttnsrc \ - && mv /ttnsrc/broker-linux-amd64 /usr/local/bin/broker \ - && chmod 755 /usr/local/bin/broker \ - && rm -rf /ttnsrc \ - && apk del curl tar \ - && rm -rf /var/cache/apk/* - -ENTRYPOINT ["/usr/local/bin/broker"] diff --git a/integration/broker/README.md b/integration/broker/README.md deleted file mode 100644 index 5aaf73638..000000000 --- a/integration/broker/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# The Things Network Broker - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Status - -This image is a pre-1.0 version of The Things Network's Broker component. It is **under heavy development** and currently it's APIs and code are not yet stable. - -## Tags - -* [`latest`, `develop` (TheThingsNetwork/ttn - develop)](https://github.com/TheThingsNetwork/ttn/blob/develop/integration/broker/Dockerfile) - -## Usage - -``` -docker pull thethingsnetwork/broker - -docker run -p 8642:8642 -p 8672:8672 thethingsnetwork/broker --handlers-port 8642 --routers-port 8672 -``` diff --git a/integration/broker/main.go b/integration/broker/main.go deleted file mode 100644 index fcdd424bb..000000000 --- a/integration/broker/main.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "os" - "strconv" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" - "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" - "github.com/TheThingsNetwork/ttn/core/components" - "github.com/apex/log" - "github.com/apex/log/handlers/text" -) - -var ( - gitCommit = "unknown" - buildDate = "unknown" -) - -func main() { - // Create Logging Context - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - ctx := log.WithFields(log.Fields{ - "component": "Broker", - }) - - // Parse options - routersPort, handlersPort := parseOptions() - - ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") - - // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(routersPort), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Routers Adapter") - } - - hdlHTTPAdapter, err := http.NewAdapter(uint(handlersPort), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Handlers Adapter") - } - - _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Handlers Adapter") - } - - db, err := components.NewBrokerStorage() - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) - - // Bring the service to life - - // Listen to uplink - go func() { - for { - packet, an, err := rtrAdapter.Next() - if err != nil { - ctx.WithError(err).Error("Could not retrieve uplink") - continue - } - go func(packet Packet, an AckNacker) { - if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { - ctx.WithError(err).Error("Could not process uplink") - } - }(packet, an) - } - }() - - // List to handler registrations - go func() { - for { - reg, an, err := hdlAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Error("Could not retrieve registration") - continue - } - go func(reg Registration, an AckNacker) { - if err := broker.Register(reg, an); err != nil { - ctx.WithError(err).Error("Could not process registration") - } - }(reg, an) - } - }() - - <-make(chan bool) -} - -func parseOptions() (routersPort uint64, handlersPort uint64) { - var routersPortFlag string - var handlersPortFlag string - - flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - - flags.StringVar(&routersPortFlag, "routers-port", "", "TCP port on which the broker should listen to for incoming uplink packets.") - flags.StringVar(&handlersPortFlag, "handlers-port", "", "TCP port on which the broker should listen to for incoming registrations and downlink packet.") - - flags.Parse(os.Args[1:]) - - var err error - - if routersPortFlag == "" { - log.Fatal("No Router listen port supplied using the -routers-port flag") - } - routersPort, err = strconv.ParseUint(routersPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -routers-port") - } - - if handlersPortFlag == "" { - log.Fatal("No Handler listen port supplied using the -handlers-port flag") - } - handlersPort, err = strconv.ParseUint(handlersPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -handlers-port") - } - - return -} diff --git a/integration/router/DOWNLOADS.md b/integration/router/DOWNLOADS.md deleted file mode 100644 index 3fc42537e..000000000 --- a/integration/router/DOWNLOADS.md +++ /dev/null @@ -1,67 +0,0 @@ -# The Things Network Router - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Download Links -| OS-arch | master | develop | -| ------- | ------ | ------- | -| darwin-386 | [zip][dd32z] [tar.gz][dd32g] [tar.xz][dd32x] | [zip][md32z] [tar.gz][md32g] [tar.xz][md32x] | -| darwin-amd64 | [zip][dd64z] [tar.gz][dd64g] [tar.xz][dd64x] | [zip][md64z] [tar.gz][md64g] [tar.xz][md64x] | -| linux-386 | [zip][dl32z] [tar.gz][dl32g] [tar.xz][dl32x] | [zip][ml32z] [tar.gz][ml32g] [tar.xz][ml32x] | -| linux-amd64 | [zip][dl64z] [tar.gz][dl64g] [tar.xz][dl64x] | [zip][ml64z] [tar.gz][ml64g] [tar.xz][ml64x] | -| linux-arm | [zip][dlarmz] [tar.gz][dlarmg] [tar.xz][dlarmx] | [zip][mlarmz] [tar.gz][mlarmg] [tar.xz][mlarmx] | -| windows-386 | [zip][dw32z] [tar.gz][dw32g] [tar.xz][dw32x] | [zip][mw32z] [tar.gz][mw32g] [tar.xz][mw32x] | -| windows-amd64 | [zip][dw64z] [tar.gz][dw64g] [tar.xz][dw64x] | [zip][mw64z] [tar.gz][mw64g] [tar.xz][mw64x] | - -[dd32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.zip -[dd32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.tar.gz -[dd32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-386.tar.xz -[md32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.zip -[md32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.tar.gz -[md32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-386.tar.xz - -[dd64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.zip -[dd64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.tar.gz -[dd64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-darwin-amd64.tar.xz -[md64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.zip -[md64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.tar.gz -[md64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-darwin-amd64.tar.xz - -[dl32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.zip -[dl32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.tar.gz -[dl32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-386.tar.xz -[ml32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.zip -[ml32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.tar.gz -[ml32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-386.tar.xz - -[dl64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.zip -[dl64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.gz -[dl64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.xz -[ml64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.zip -[ml64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.tar.gz -[ml64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-amd64.tar.xz - -[dlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.zip -[dlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.tar.gz -[dlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-arm.tar.xz -[mlarmz]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.zip -[mlarmg]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.tar.gz -[mlarmx]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-linux-arm.tar.xz - -[dw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.zip -[dw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.tar.gz -[dw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-386.tar.xz -[mw32z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.zip -[mw32g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.tar.gz -[mw32x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-386.tar.xz - -[dw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.zip -[dw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.tar.gz -[dw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-windows-amd64.tar.xz -[mw64z]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.zip -[mw64g]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.tar.gz -[mw64x]: https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/router-windows-amd64.tar.xz diff --git a/integration/router/Dockerfile b/integration/router/Dockerfile deleted file mode 100644 index 4e6b98adc..000000000 --- a/integration/router/Dockerfile +++ /dev/null @@ -1,15 +0,0 @@ -FROM alpine - -RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* - -RUN apk --update add curl tar \ - && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/router-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/router-linux-amd64.tar.gz" \ - && tar -xf /ttnsrc/router-linux-amd64.tar.gz -C /ttnsrc \ - && mv /ttnsrc/router-linux-amd64 /usr/local/bin/router \ - && chmod 755 /usr/local/bin/router \ - && rm -rf /ttnsrc \ - && apk del curl tar \ - && rm -rf /var/cache/apk/* - -ENTRYPOINT ["/usr/local/bin/router"] diff --git a/integration/router/README.md b/integration/router/README.md deleted file mode 100644 index 92057a7b7..000000000 --- a/integration/router/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# The Things Network Router - -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) - -![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - -The Things Network is a global open crowdsourced Internet of Things data network. - -## Status - -This image is a pre-1.0 version of The Things Network's Router component. It is **under heavy development** and currently it's APIs and code are not yet stable. - -## Tags - -* [`latest`, `develop` (TheThingsNetwork/ttn - develop)](https://github.com/TheThingsNetwork/ttn/blob/develop/integration/router/Dockerfile) - -## Usage - -``` -docker pull thethingsnetwork/router - -docker run -p 8647:8647/udp -p 8627:8627 thethingsnetwork/router --udp-port 8647 --tcp-port 8627 --brokers "1.2.3.4:8672,broker.host.com:8672" -``` diff --git a/integration/router/main.go b/integration/router/main.go deleted file mode 100644 index 9505f0e3d..000000000 --- a/integration/router/main.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "os" - "strconv" - "strings" - "time" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" - "github.com/TheThingsNetwork/ttn/core/adapters/semtech" - "github.com/TheThingsNetwork/ttn/core/components" - "github.com/apex/log" - "github.com/apex/log/handlers/text" -) - -var ( - gitCommit = "unknown" - buildDate = "unknown" -) - -func main() { - // Create Logging Context - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - ctx := log.WithFields(log.Fields{ - "component": "Router", - }) - - // Parse options - brokers, tcpPort, udpPort := parseOptions() - - ctx.WithField("git commit", gitCommit).WithField("build date", buildDate).Info("Starting The Things Network") - - // Instantiate all components - gtwAdapter, err := semtech.NewAdapter(uint(udpPort), ctx.WithField("tag", "Gateway Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Gateway Adapter") - } - - pktAdapter, err := http.NewAdapter(uint(tcpPort), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - db, err := components.NewRouterStorage(time.Hour * 8) - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - router := components.NewRouter(db, ctx.WithField("tag", "Router")) - - // Bring the service to life - - // Listen uplink - go func() { - for { - packet, an, err := gtwAdapter.Next() - if err != nil { - ctx.WithError(err).Warn("Could not get next packet from gateway") - continue - } - go func(packet Packet, an AckNacker) { - if err := router.HandleUp(packet, an, brkAdapter); err != nil { - ctx.WithError(err).Warn("Could not process packet from gateway") - } - }(packet, an) - } - }() - - // Listen broker registrations - go func() { - for { - reg, an, err := brkAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Warn("Could not get next registration from broker") - continue - } - go func(reg Registration, an AckNacker) { - if err := router.Register(reg, an); err != nil { - ctx.WithError(err).Warn("Could not process registration from broker") - } - }(reg, an) - } - }() - - <-make(chan bool) -} - -func parseOptions() (brokers []Recipient, tcpPort uint64, udpPort uint64) { - var brokersFlag string - var udpPortFlag string - var tcpPortFlag string - - flags := flag.NewFlagSet(os.Args[0], flag.ExitOnError) - - flags.StringVar(&brokersFlag, "brokers", "", `Broker addresses to which broadcast packets. - For instance: 10.10.3.34:8080,thethingsnetwork.broker.com:3000`) - flags.StringVar(&udpPortFlag, "udp-port", "", "UDP port on which the router should listen to.") - flags.StringVar(&tcpPortFlag, "tcp-port", "", "TCP port on which the router should listen to.") - - flags.Parse(os.Args[1:]) - - var err error - - if tcpPortFlag == "" { - log.Fatal("No TCP listen port supplied using the -tcp-port flag") - } - tcpPort, err = strconv.ParseUint(tcpPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -tcp-port") - } - - if udpPortFlag == "" { - log.Fatal("No UDP listen port supplied using the -udp-port flag.") - } - udpPort, err = strconv.ParseUint(udpPortFlag, 10, 64) - if err != nil { - log.Fatal("Could not parse the value for -udp-port") - } - - if brokersFlag == "" { - log.Fatal("No broker address is supplied using -brokers flag.") - } - brokersStr := strings.Split(brokersFlag, ",") - for i := range brokersStr { - brokers = append(brokers, Recipient{ - Address: strings.Trim(brokersStr[i], " "), - Id: i, - }) - - } - return -} From 70c0c410d00abb7d8e939e10a032c70f612f3033 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:19:05 +0100 Subject: [PATCH 0634/2266] [cli] Update build script --- build_binaries.sh | 46 ++++++++++++++++------------------------------ 1 file changed, 16 insertions(+), 30 deletions(-) diff --git a/build_binaries.sh b/build_binaries.sh index 6b53eb8cb..0db1388cf 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -11,13 +11,11 @@ git_commit=$(git rev-parse HEAD) build_release() { - component=$1 - export CGO_ENABLED=0 - export GOOS=$2 - export GOARCH=$3 + export GOOS=$1 + export GOARCH=$2 - release_name=$component-$GOOS-$GOARCH + release_name=ttn-$GOOS-$GOARCH if [ "$GOOS" == "windows" ] then @@ -30,12 +28,12 @@ build_release() build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ) - echo "$build_date - Building $component for $GOOS/$GOARCH..." + echo "$build_date - Building ttn for $GOOS/$GOARCH..." # Build cd $TTNROOT - go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./integration/$component/main.go + go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./main.go # Compress cd $RELEASEPATH @@ -56,21 +54,13 @@ build_release() rm $binary_name } -build_release router darwin 386 -build_release router darwin amd64 -build_release router linux 386 -build_release router linux amd64 -build_release router linux arm -build_release router windows 386 -build_release router windows amd64 - -build_release broker darwin 386 -build_release broker darwin amd64 -build_release broker linux 386 -build_release broker linux amd64 -build_release broker linux arm -build_release broker windows 386 -build_release broker windows amd64 +build_release darwin 386 +build_release darwin amd64 +build_release linux 386 +build_release linux amd64 +build_release linux arm +build_release windows 386 +build_release windows amd64 # Prepare Releases cd $RELEASEPATH @@ -80,8 +70,7 @@ if [ "$CI_COMMIT" != "" ] then echo "Copying files for commit $CI_COMMIT" mkdir -p commit/$CI_COMMIT - cp ./router* commit/$CI_COMMIT/ - cp ./broker* commit/$CI_COMMIT/ + cp ./ttn* commit/$CI_COMMIT/ fi # Branch Release @@ -89,8 +78,7 @@ if [ "$CI_BRANCH" != "" ] then echo "Copying files for branch $CI_BRANCH" mkdir -p branch/$CI_BRANCH - cp ./router* branch/$CI_BRANCH/ - cp ./broker* branch/$CI_BRANCH/ + cp ./ttn* branch/$CI_BRANCH/ fi # Tag Release @@ -98,10 +86,8 @@ if [ "$CI_TAG" != "" ] then echo "Copying files for tag $CI_TAG" mkdir -p tag/$CI_TAG - cp ./router* tag/$CI_TAG/ - cp ./broker* tag/$CI_TAG/ + cp ./ttn* tag/$CI_TAG/ fi # Remove Build Files -rm -f ./router* -rm -f ./broker* +rm -f ./ttn* From 9308fd882fe0e8fb833a7e164d8e5156f57d97ca Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:21:53 +0100 Subject: [PATCH 0635/2266] [cli] Add empty Handler command --- cmd/handler.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 cmd/handler.go diff --git a/cmd/handler.go b/cmd/handler.go new file mode 100644 index 000000000..7d292b967 --- /dev/null +++ b/cmd/handler.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// handlerCmd represents the handler command +var handlerCmd = &cobra.Command{ + Use: "handler", + Short: "The Things Network handler", + Long: ` +The default handler is the bridge between The Things Network and applications. +`, + PreRun: func(cmd *cobra.Command, args []string) { + ctx = ctx.WithField("cmd", "handler") + ctx.WithFields(log.Fields{ + "database": viper.GetString("handler.database"), + "brokers-port": viper.GetInt("handler.brokers-port"), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Fatal("The handler's not ready yet, but we're working on it!") + + // TODO: Add logic + }, +} + +func init() { + RootCmd.AddCommand(handlerCmd) + + handlerCmd.Flags().String("database", "boltdb:/tmp/ttn_handler.db", "Database connection") + handlerCmd.Flags().Int("brokers-port", 1691, "TCP port for connections from brokers") + + viper.BindPFlag("handler.database", handlerCmd.Flags().Lookup("database")) + viper.BindPFlag("handler.brokers-port", handlerCmd.Flags().Lookup("brokers-port")) + +} From b23bcfb023b8ac1dc85e1184f24116efe7dd10a8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:22:22 +0100 Subject: [PATCH 0636/2266] [cli] Add Dockerfile for new main --- Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..193c7b2c8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM alpine + +RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* + +RUN apk --update add curl tar \ + && mkdir /ttnsrc \ + && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ + && tar -xf /ttnsrc/ttn-linux-amd64.tar.gz -C /ttnsrc \ + && mv /ttnsrc/ttn-linux-amd64 /usr/local/bin/ttn \ + && chmod 755 /usr/local/bin/ttn \ + && rm -rf /ttnsrc \ + && apk del curl tar \ + && rm -rf /var/cache/apk/* + +ENTRYPOINT ["/usr/local/bin/ttn"] From 559391871d39e984b70f9415d2a6565be01762e9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:44:36 +0100 Subject: [PATCH 0637/2266] [cli] Add local (docker) builds --- Dockerfile.local | 9 ++++++ build_binaries.sh | 78 +++++++++++++++++++++++++++-------------------- 2 files changed, 54 insertions(+), 33 deletions(-) create mode 100644 Dockerfile.local diff --git a/Dockerfile.local b/Dockerfile.local new file mode 100644 index 000000000..9acac189e --- /dev/null +++ b/Dockerfile.local @@ -0,0 +1,9 @@ +# USAGE: +# ./build_binaries.sh linux amd64 +# docker build -t local/ttn -f Dockerfile.local . + +FROM alpine +RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* +ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn +RUN chmod 755 /usr/local/bin/ttn +ENTRYPOINT ["/usr/local/bin/ttn"] diff --git a/build_binaries.sh b/build_binaries.sh index 0db1388cf..b36e4bae5 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -50,44 +50,56 @@ build_release() zip -q $release_name.zip $binary_name > /dev/null echo " Done" - # Delete Binary - rm $binary_name + # Delete Binary in CI build + if [ "$CI" != "" ] + then + rm $binary_name + fi } -build_release darwin 386 -build_release darwin amd64 -build_release linux 386 -build_release linux amd64 -build_release linux arm -build_release windows 386 -build_release windows amd64 - -# Prepare Releases -cd $RELEASEPATH - -# Commit Release -if [ "$CI_COMMIT" != "" ] +if [[ "$1" != "" ]] && [[ "$2" != "" ]] then - echo "Copying files for commit $CI_COMMIT" - mkdir -p commit/$CI_COMMIT - cp ./ttn* commit/$CI_COMMIT/ + build_release $1 $2 +else + build_release darwin 386 + build_release darwin amd64 + build_release linux 386 + build_release linux amd64 + build_release linux arm + build_release windows 386 + build_release windows amd64 fi -# Branch Release -if [ "$CI_BRANCH" != "" ] +# Prepare Releases in CI build +if [ "$CI" != "" ] then - echo "Copying files for branch $CI_BRANCH" - mkdir -p branch/$CI_BRANCH - cp ./ttn* branch/$CI_BRANCH/ -fi -# Tag Release -if [ "$CI_TAG" != "" ] -then - echo "Copying files for tag $CI_TAG" - mkdir -p tag/$CI_TAG - cp ./ttn* tag/$CI_TAG/ + cd $RELEASEPATH + + # Commit Release + if [ "$CI_COMMIT" != "" ] + then + echo "Copying files for commit $CI_COMMIT" + mkdir -p commit/$CI_COMMIT + cp ./ttn* commit/$CI_COMMIT/ + fi + + # Branch Release + if [ "$CI_BRANCH" != "" ] + then + echo "Copying files for branch $CI_BRANCH" + mkdir -p branch/$CI_BRANCH + cp ./ttn* branch/$CI_BRANCH/ + fi + + # Tag Release + if [ "$CI_TAG" != "" ] + then + echo "Copying files for tag $CI_TAG" + mkdir -p tag/$CI_TAG + cp ./ttn* tag/$CI_TAG/ + fi + + # Remove Build Files + rm -f ./ttn* fi - -# Remove Build Files -rm -f ./ttn* From f91c638ff47ac056469788135e582b1eb52f9ea6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 16:48:40 +0100 Subject: [PATCH 0638/2266] [cli] Remove old download/docker information from README [skip CI] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ca26d3dad..eb452e629 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,8 @@ When you get started with The Things Network, you'll probably have some question We are working hard on building a working version of the v1.0 backend. Currently, we have the following components working: -- [x] **Router**: [[download](integration/router/DOWNLOADS.md)] [[docker](https://hub.docker.com/r/thethingsnetwork/router/)] -- [x] **Broker**: [[download](integration/broker/DOWNLOADS.md)] [[docker](https://hub.docker.com/r/thethingsnetwork/broker/)] +- [x] **Router** +- [x] **Broker** - [ ] **Handler**: *work in progress* ## Contributing From ee42e7d59c7a4ab86797468eebcacde0eba44a31 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 17:00:23 +0100 Subject: [PATCH 0639/2266] [cli] Set gateway-router port to 1700 --- cmd/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/router.go b/cmd/router.go index 014abe28e..214013ad2 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -117,7 +117,7 @@ func init() { RootCmd.AddCommand(routerCmd) routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") - routerCmd.Flags().Int("gateways-port", 1680, "UDP port for connections from gateways") + routerCmd.Flags().Int("gateways-port", 1700, "UDP port for connections from gateways") routerCmd.Flags().String("brokers", "localhost:1690", "Comma-separated list of brokers") routerCmd.Flags().Int("brokers-port", 1780, "TCP port for connections from brokers") From 13b14a002b692b585360586181522378853715bb Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 17:06:33 +0100 Subject: [PATCH 0640/2266] [refactor] Re-introduce semtech tests + test correction + make them pass --- refactor/adapters/udp/adapter.go | 10 +- .../udp/handlers/build_utilities_test.go | 202 ++++++++++++++++++ refactor/adapters/udp/handlers/semtech.go | 8 +- .../adapters/udp/handlers/semtech_test.go | 146 +++++++++++++ 4 files changed, 359 insertions(+), 7 deletions(-) create mode 100644 refactor/adapters/udp/handlers/build_utilities_test.go create mode 100644 refactor/adapters/udp/handlers/semtech_test.go diff --git a/refactor/adapters/udp/adapter.go b/refactor/adapters/udp/adapter.go index 2c5da2bdd..8c93912aa 100644 --- a/refactor/adapters/udp/adapter.go +++ b/refactor/adapters/udp/adapter.go @@ -25,13 +25,13 @@ type Adapter struct { // Handler represents a datagram and packet handler used by the adapter to process packets type Handler interface { // HandleAck handles a positive response to a transmitter - HandleAck(p *core.Packet, resp <-chan HandlerMsg) + HandleAck(p *core.Packet, resp chan<- HandlerMsg) // HandleNack handles a negative response to a transmitter - HandleNack(resp <-chan HandlerMsg) + HandleNack(resp chan<- HandlerMsg) // HandleDatagram handles incoming datagram from a gateway transmitter to the network - HandleDatagram(data []byte, resp <-chan HandlerMsg) + HandleDatagram(data []byte, resp chan<- HandlerMsg) } // HandlerMsg type materializes response messages emitted by the UdpHandler @@ -175,12 +175,12 @@ func (a *Adapter) monitorConnection() { func (a *Adapter) handleResp(addr *net.UDPAddr, chresp <-chan HandlerMsg) error { for msg := range chresp { switch msg.Type { - case HANDLER_OUT: + case HANDLER_RESP: a.conn <- UdpMsg{ Data: msg.Data, Addr: addr, } - case HANDLER_RESP: + case HANDLER_OUT: a.next <- OutMsg{ Data: msg.Data, Addr: addr, diff --git a/refactor/adapters/udp/handlers/build_utilities_test.go b/refactor/adapters/udp/handlers/build_utilities_test.go new file mode 100644 index 000000000..1ea304707 --- /dev/null +++ b/refactor/adapters/udp/handlers/build_utilities_test.go @@ -0,0 +1,202 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "encoding/base64" + "fmt" + "net" + "testing" + "time" + + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +// ----- build utilities +type mockServer struct { + conn *net.UDPConn + response chan semtech.Packet +} + +// Generate a mock server that will send packet through a udp connection and communicate back +// received packet. +func genMockServer(port uint) mockServer { + addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) + if err != nil { + panic(err) + } + + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + panic(err) + } + + response := make(chan semtech.Packet) + go func() { + for { + buf := make([]byte, 5000) + n, _, err := conn.ReadFromUDP(buf) + if err != nil { + panic(err) + } + packet := new(semtech.Packet) + if err = packet.UnmarshalBinary(buf[:n]); err != nil { + panic(err) + } + response <- *packet + } + }() + + return mockServer{conn: conn, response: response} +} + +// Send a packet through the udp mock server toward the adapter +func (s mockServer) send(p semtech.Packet) semtech.Packet { + raw, err := p.MarshalBinary() + if err != nil { + panic(err) + } + s.conn.Write(raw) + select { + case packet := <-s.response: + return packet + case <-time.After(100 * time.Millisecond): + return semtech.Packet{} + } +} + +// Generates an adapter as well as a channel that behaves like the Next() methods (but can be used +// in a select for timeout) +func genAdapter(t *testing.T, port uint) (*udp.Adapter, chan interface{}) { + // Logging + ctx := GetLogger(t, "Adapter") + + adapter, err := udp.NewAdapter(port, Semtech{}, ctx) + if err != nil { + panic(err) + } + next := make(chan interface{}) + go func() { + for { + packet, _, err := adapter.Next() + next <- struct { + err error + packet []byte + }{err: err, packet: packet} + } + }() + return adapter, next +} + +// Generate a core packet from a semtech packet that has one RXPK +func genCorePacket(p semtech.Packet) core.Packet { + if p.Payload == nil || len(p.Payload.RXPK) != 1 { + panic("Expected a payload with one rxpk") + } + packet, err := rxpk2packet(p.Payload.RXPK[0]) + if err != nil { + panic(err) + } + return packet +} + +func genPUSH_DATANoRXPK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Token: token, + Identifier: semtech.PUSH_DATA, + } +} + +func genPUSH_DATANoPayload(token []byte) semtech.Packet { + packet := genPUSH_DATAWithRXPK(token) + packet.Payload.RXPK[0].Data = nil + return packet +} + +func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { + packet := genPUSH_DATANoRXPK(token) + packet.Payload = &semtech.Payload{ + RXPK: []semtech.RXPK{ + semtech.RXPK{ + Rssi: pointer.Int(-60), + Codr: pointer.String("4/7"), + Data: pointer.String(genRXPKData()), + }, + }, + } + return packet +} + +func genPULL_ACK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + Identifier: semtech.PULL_ACK, + } +} + +func genPUSH_ACK(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + Identifier: semtech.PUSH_ACK, + } +} + +func genPULL_DATA(token []byte) semtech.Packet { + return semtech.Packet{ + Version: semtech.VERSION, + Token: token, + GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, + Identifier: semtech.PULL_DATA, + } +} + +func genRXPKData() string { + // 1. Generate a PHYPayload + nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + // 2. Generate a JSON payload received by the server + raw, err := payload.MarshalBinary() + if err != nil { + panic(err) + } + return base64.StdEncoding.EncodeToString(raw) +} diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index 0589463ab..f6e8386d8 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -27,11 +27,15 @@ func (s Semtech) HandleNack(chresp chan<- udp.HandlerMsg) { } // HandleAck implements the udp.Handler interface -func (s Semtech) HandleAck(packet core.Packet, chresp chan<- udp.HandlerMsg) { +func (s Semtech) HandleAck(packet *core.Packet, chresp chan<- udp.HandlerMsg) { defer close(chresp) + if packet == nil { + return + } + // For the downlink, we have to send a PULL_RESP packet which hold a TXPK. - txpk, err := packet2txpk(packet) + txpk, err := packet2txpk(*packet) if err != nil { chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} return diff --git a/refactor/adapters/udp/handlers/semtech_test.go b/refactor/adapters/udp/handlers/semtech_test.go new file mode 100644 index 000000000..3665cf2d3 --- /dev/null +++ b/refactor/adapters/udp/handlers/semtech_test.go @@ -0,0 +1,146 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "reflect" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestSend(t *testing.T) { + Desc(t, "Send is not supported") + adapter, _ := genAdapter(t, 33000) + _, err := adapter.Send(core.RPacket{}) + checkErrors(t, pointer.String(ErrNotSupported), err) +} + +func TestNextRegistration(t *testing.T) { + Desc(t, "Next registration is not supported") + adapter, _ := genAdapter(t, 33001) + _, _, err := adapter.NextRegistration() + checkErrors(t, pointer.String(ErrNotSupported), err) +} + +func TestNext(t *testing.T) { + adapter, next := genAdapter(t, 33002) + server := genMockServer(33002) + + tests := []struct { + Adapter *udp.Adapter + Packet semtech.Packet + WantAck semtech.Packet + WantNext core.Packet + WantError *string + }{ + { // Valid uplink PUSH_DATA + Adapter: adapter, + Packet: genPUSH_DATAWithRXPK([]byte{0x14, 0x42}), + WantAck: genPUSH_ACK([]byte{0x14, 0x42}), + WantNext: genCorePacket(genPUSH_DATAWithRXPK([]byte{0x14, 0x42})), + WantError: nil, + }, + { // Invalid uplink packet + Adapter: adapter, + Packet: genPUSH_ACK([]byte{0x22, 0x35}), + WantAck: semtech.Packet{}, + WantNext: core.RPacket{}, + WantError: nil, + }, + { // Uplink PUSH_DATA with no RXPK + Adapter: adapter, + Packet: genPUSH_DATANoRXPK([]byte{0x22, 0x35}), + WantAck: genPUSH_ACK([]byte{0x22, 0x35}), + WantNext: core.RPacket{}, + WantError: nil, + }, + { // Uplink PULL_DATA + Adapter: adapter, + Packet: genPULL_DATA([]byte{0x62, 0xfa}), + WantAck: genPULL_ACK([]byte{0x62, 0xfa}), + WantNext: core.RPacket{}, + WantError: nil, + }, + { // Uplink PUSH_DATA with no encoded payload + Adapter: adapter, + Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), + WantAck: genPUSH_ACK([]byte{0x22, 0x35}), + WantNext: core.RPacket{}, + WantError: nil, + }, + } + + for _, test := range tests { + // Describe + Desc(t, "Sending packet through adapter: %v", test.Packet) + <-time.After(time.Millisecond * 100) + + // Operate + ack := server.send(test.Packet) + packet, err := getNextPacket(next) + + // Check + checkErrors(t, test.WantError, err) + checkCorePackets(t, test.WantNext, packet) + checkResponses(t, test.WantAck, ack) + } +} + +// ----- OPERATE utilities +func getNextPacket(next chan interface{}) (core.Packet, error) { + select { + case i := <-next: + res := i.(struct { + err error + packet []byte + }) + var packet core.RPacket + err := packet.UnmarshalBinary(res.packet) + if err != nil { + panic(err) + } + return packet, res.err + case <-time.After(100 * time.Millisecond): + return core.RPacket{}, nil + } +} + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil && want != nil { + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil && got == nil || got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkCorePackets(t *testing.T, want core.Packet, got core.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check core packets") + return + } + Ko(t, "Received core packet does not match expecatations.\nWant: %v\nGot: %v", want, got) +} + +func checkResponses(t *testing.T, want semtech.Packet, got semtech.Packet) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check responses") + return + } + Ko(t, "Received response does not match expecatations.\nWant: %v\nGot: %v", want.String(), got.String()) +} From dbcfc3a7617f81b2e234808e2f89d0be51b9fe75 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 22 Feb 2016 17:27:37 +0100 Subject: [PATCH 0641/2266] Don't build for arm Hopefully a temporary fix because jacobsa/crypto currently doesn't build on arm --- build_binaries.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/build_binaries.sh b/build_binaries.sh index b36e4bae5..815435ba3 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -65,7 +65,6 @@ else build_release darwin amd64 build_release linux 386 build_release linux amd64 - build_release linux arm build_release windows 386 build_release windows amd64 fi From ace763c816d68e22b18400fae3a41a3205a98fbf Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 17:31:30 +0100 Subject: [PATCH 0642/2266] [refactor] Re-introduce tests for semtech conversions + make them pass --- .../udp/handlers/build_utilities_test.go | 189 +++++++++++++++++- .../adapters/udp/handlers/conversions_test.go | 99 +++++++++ refactor/adapters/udp/handlers/semtech.go | 5 +- refactor/build_utilities_test.go | 70 ------- refactor/check_utilities_test.go | 11 - refactor/packet_test.go | 95 --------- 6 files changed, 285 insertions(+), 184 deletions(-) create mode 100644 refactor/adapters/udp/handlers/conversions_test.go diff --git a/refactor/adapters/udp/handlers/build_utilities_test.go b/refactor/adapters/udp/handlers/build_utilities_test.go index 1ea304707..37a0cc79b 100644 --- a/refactor/adapters/udp/handlers/build_utilities_test.go +++ b/refactor/adapters/udp/handlers/build_utilities_test.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "fmt" "net" + "strings" "testing" "time" @@ -160,12 +161,11 @@ func genPULL_DATA(token []byte) semtech.Packet { } } -func genRXPKData() string { - // 1. Generate a PHYPayload +func genPHYPayload(uplink bool) lorawan.PHYPayload { nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - macPayload := lorawan.NewMACPayload(true) + macPayload := lorawan.NewMACPayload(uplink) macPayload.FHDR = lorawan.FHDR{ DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), FCtrl: lorawan.FCtrl{ @@ -176,13 +176,13 @@ func genRXPKData() string { FCnt: 0, } macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} if err := macPayload.EncryptFRMPayload(appSKey); err != nil { panic(err) } - payload := lorawan.NewPHYPayload(true) + payload := lorawan.NewPHYPayload(uplink) payload.MHDR = lorawan.MHDR{ MType: lorawan.ConfirmedDataUp, Major: lorawan.LoRaWANR1, @@ -193,6 +193,13 @@ func genRXPKData() string { panic(err) } + return payload +} + +func genRXPKData() string { + // 1. Generate a physical payload + payload := genPHYPayload(true) + // 2. Generate a JSON payload received by the server raw, err := payload.MarshalBinary() if err != nil { @@ -200,3 +207,175 @@ func genRXPKData() string { } return base64.StdEncoding.EncodeToString(raw) } + +// Generate a Metadata object that matches RXPK metadata +func genMetadata(RXPK semtech.RXPK) core.Metadata { + return core.Metadata{ + Chan: RXPK.Chan, + Codr: RXPK.Codr, + Freq: RXPK.Freq, + Lsnr: RXPK.Lsnr, + Modu: RXPK.Modu, + Rfch: RXPK.Rfch, + Rssi: RXPK.Rssi, + Size: RXPK.Size, + Stat: RXPK.Stat, + Time: RXPK.Time, + Tmst: RXPK.Tmst, + } +} + +// Generates a Metadata object with all field completed with relevant values +func genFullMetadata() core.Metadata { + timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) + return core.Metadata{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Datr: pointer.String("LORA"), + Fdev: pointer.Uint(3), + Freq: pointer.Float64(863.125), + Imme: pointer.Bool(false), + Ipol: pointer.Bool(false), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Ncrc: pointer.Bool(true), + Powe: pointer.Uint(3), + Prea: pointer.Uint(8), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(14), + Stat: pointer.Int(0), + Time: pointer.Time(timeRef), + Tmst: pointer.Uint(uint(timeRef.UnixNano())), + } +} + +// Generate an RXPK packet using the given payload as Data +func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + + return semtech.RXPK{ + Chan: pointer.Uint(2), + Codr: pointer.String("4/6"), + Data: pointer.String(data), + Freq: pointer.Float64(863.125), + Lsnr: pointer.Float64(5.2), + Modu: pointer.String("LORA"), + Rfch: pointer.Uint(2), + Rssi: pointer.Int(-27), + Size: pointer.Uint(uint(len([]byte(data)))), + Stat: pointer.Int(0), + Time: pointer.Time(time.Now()), + Tmst: pointer.Uint(uint(time.Now().UnixNano())), + } +} + +// Generates a TXPK packet using the given payload and the given metadata +func genTXPK(phyPayload lorawan.PHYPayload, metadata core.Metadata) semtech.TXPK { + raw, err := phyPayload.MarshalBinary() + if err != nil { + panic(err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + return semtech.TXPK{ + Codr: metadata.Codr, + Data: pointer.String(data), + Datr: metadata.Datr, + Fdev: metadata.Fdev, + Freq: metadata.Freq, + Imme: metadata.Imme, + Ipol: metadata.Ipol, + Modu: metadata.Modu, + Ncrc: metadata.Ncrc, + Powe: metadata.Powe, + Prea: metadata.Prea, + Rfch: metadata.Rfch, + Size: metadata.Size, + Time: metadata.Time, + Tmst: metadata.Tmst, + } +} + +// Generates a test suite where the RXPK is fully complete +func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { + phyPayload := genPHYPayload(true) + rxpk := genRXPK(phyPayload) + metadata := genMetadata(rxpk) + test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.RXPK = rxpk + return *test +} + +// Generates a test suite where the RXPK contains partial metadata +func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { + phyPayload := genPHYPayload(true) + rxpk := genRXPK(phyPayload) + rxpk.Codr = nil + rxpk.Rfch = nil + rxpk.Rssi = nil + rxpk.Time = nil + rxpk.Size = nil + metadata := genMetadata(rxpk) + test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.RXPK = rxpk + return *test +} + +// Generates a test suite where the RXPK contains no data +func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { + rxpk := genRXPK(genPHYPayload(true)) + rxpk.Data = nil + test.RXPK = rxpk + return *test +} + +// Generates a test suite where the core packet has all txpk metadata +func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + metadata.Chan = nil + metadata.Lsnr = nil + metadata.Rssi = nil + metadata.Stat = nil + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.NewRPacket(phyPayload, metadata) + return *test +} + +// Generates a test suite where the core packet has no metadata +func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := core.Metadata{} + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.NewRPacket(phyPayload, metadata) + return *test +} + +// Generates a test suite where the core packet has partial metadata but all supported +func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + metadata.Chan = nil + metadata.Lsnr = nil + metadata.Rssi = nil + metadata.Stat = nil + metadata.Modu = nil + metadata.Fdev = nil + metadata.Time = nil + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.NewRPacket(phyPayload, metadata) + return *test +} + +// Generates a test suite where the core packet has extra metadata not supported by txpk +func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { + phyPayload := genPHYPayload(false) + metadata := genFullMetadata() + test.TXPK = genTXPK(phyPayload, metadata) + test.CorePacket = core.NewRPacket(phyPayload, metadata) + return *test +} diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/refactor/adapters/udp/handlers/conversions_test.go new file mode 100644 index 000000000..256278003 --- /dev/null +++ b/refactor/adapters/udp/handlers/conversions_test.go @@ -0,0 +1,99 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "reflect" + "testing" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +type convertRXPKTest struct { + CorePacket core.Packet + RXPK semtech.RXPK + WantError *string +} + +func TestConvertRXPKPacket(t *testing.T) { + tests := []convertRXPKTest{ + genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), + genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), + genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(ErrInvalidStructure)}), + } + + for _, test := range tests { + Desc(t, "Convert RXPK: %s", pointer.DumpPStruct(test.RXPK, false)) + packet, err := rxpk2packet(test.RXPK) + checkErrors(t, test.WantError, err) + checkPackets(t, test.CorePacket, packet) + } +} + +type convertToTXPKTest struct { + TXPK semtech.TXPK + CorePacket core.Packet + WantError *string +} + +func TestConvertTXPKPacket(t *testing.T) { + tests := []convertToTXPKTest{ + genCoreFullMetadata(&convertToTXPKTest{WantError: nil}), + genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), + genCoreExtraMetadata(&convertToTXPKTest{WantError: nil}), + genCoreNoMetadata(&convertToTXPKTest{WantError: nil}), + convertToTXPKTest{ + CorePacket: core.NewRPacket(lorawan.PHYPayload{}, core.Metadata{}), + TXPK: semtech.TXPK{}, + WantError: pointer.String(ErrInvalidStructure), + }, + } + + for _, test := range tests { + Desc(t, "Convert to TXPK: %s", test.CorePacket.String()) + txpk, err := packet2txpk(test.CorePacket) + checkErrors(t, test.WantError, err) + checkTXPKs(t, test.TXPK, txpk) + } +} + +// ----- CHECK utilities + +// Checks that obtained TXPK matches expeceted one +func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { + if reflect.DeepEqual(want, got) { + Ok(t, "check TXPKs") + return + } + Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) +} + +// Checks that two packets match +func checkPackets(t *testing.T, want core.Packet, got core.Packet) { + if want == nil { + if got == nil { + Ok(t, "Check packets") + return + } + Ko(t, "No packet was expected but got %s", got.String()) + return + } + + if got == nil { + Ko(t, "Was expecting %s but got nothing", want.String()) + return + } + + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + + Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) +} diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index f6e8386d8..e77d93898 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -5,7 +5,6 @@ package handlers import ( "encoding/base64" - "fmt" "reflect" "strings" @@ -160,7 +159,7 @@ func packet2txpk(p core.Packet) (semtech.TXPK, error) { // Step 1, convert the physical payload to a base64 string (without the padding) raw, err := p.Payload().MarshalBinary() if err != nil { - return semtech.TXPK{}, err + return semtech.TXPK{}, errors.New(ErrInvalidStructure, err) } data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") @@ -170,7 +169,7 @@ func packet2txpk(p core.Packet) (semtech.TXPK, error) { // We are possibly loosing information here. m := p.Metadata() if len(m) != 1 { - return semtech.TXPK{}, fmt.Errorf("Invalid metadata structure") + return semtech.TXPK{}, errors.New(ErrInvalidStructure, "Invalid metadata structure") } metadataValue := reflect.ValueOf(m[0]) metadataStruct := metadataValue.Type() diff --git a/refactor/build_utilities_test.go b/refactor/build_utilities_test.go index 389fa117f..ba7f938a1 100644 --- a/refactor/build_utilities_test.go +++ b/refactor/build_utilities_test.go @@ -4,82 +4,12 @@ package refactor import ( - "encoding/base64" - "strings" "time" - "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/brocaar/lorawan" ) -// Generate an RXPK packet using the given payload as Data -func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - - return semtech.RXPK{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Data: pointer.String(data), - Freq: pointer.Float64(863.125), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(uint(len([]byte(data)))), - Stat: pointer.Int(0), - Time: pointer.Time(time.Now()), - Tmst: pointer.Uint(uint(time.Now().UnixNano())), - } -} - -// Generates a TXPK packet using the given payload and the given metadata -func genTXPK(phyPayload lorawan.PHYPayload, metadata Metadata) semtech.TXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - return semtech.TXPK{ - Codr: metadata.Codr, - Data: pointer.String(data), - Datr: metadata.Datr, - Fdev: metadata.Fdev, - Freq: metadata.Freq, - Imme: metadata.Imme, - Ipol: metadata.Ipol, - Modu: metadata.Modu, - Ncrc: metadata.Ncrc, - Powe: metadata.Powe, - Prea: metadata.Prea, - Rfch: metadata.Rfch, - Size: metadata.Size, - Time: metadata.Time, - Tmst: metadata.Tmst, - } -} - -// Generate a Metadata object that matches RXPK metadata -func genMetadata(RXPK semtech.RXPK) Metadata { - return Metadata{ - Chan: RXPK.Chan, - Codr: RXPK.Codr, - Freq: RXPK.Freq, - Lsnr: RXPK.Lsnr, - Modu: RXPK.Modu, - Rfch: RXPK.Rfch, - Rssi: RXPK.Rssi, - Size: RXPK.Size, - Stat: RXPK.Stat, - Time: RXPK.Time, - Tmst: RXPK.Tmst, - } -} - // Generates a Metadata object with all field completed with relevant values func genFullMetadata() Metadata { timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go index 71fb8800c..24929d7a0 100644 --- a/refactor/check_utilities_test.go +++ b/refactor/check_utilities_test.go @@ -9,9 +9,7 @@ import ( "regexp" "testing" - "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -69,15 +67,6 @@ func checkMetadata(t *testing.T, want Metadata, got Metadata) { Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) } -// Checks that obtained TXPK matches expeceted one -func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { - if reflect.DeepEqual(want, got) { - Ok(t, "check TXPKs") - return - } - Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) -} - // Check that obtained json strings contains the required field func checkFields(t *testing.T, want []string, got []byte) { for _, field := range want { diff --git a/refactor/packet_test.go b/refactor/packet_test.go index 9f2966fa6..266fe6058 100644 --- a/refactor/packet_test.go +++ b/refactor/packet_test.go @@ -7,7 +7,6 @@ import ( "encoding/json" "testing" - "github.com/TheThingsNetwork/ttn/semtech" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -64,18 +63,6 @@ func TestUnmarshalJSONRPacket(t *testing.T) { } // ---- Declaration -type convertRXPKTest struct { - CorePacket Packet - RXPK semtech.RXPK - WantError *string -} - -type convertToTXPKTest struct { - TXPK semtech.TXPK - CorePacket Packet - WantError *string -} - type marshalJSONTest struct { Packet Packet WantFields []string @@ -85,85 +72,3 @@ type unmarshalJSONTest struct { JSON string WantPacket Packet } - -// ---- Build utilities - -// Generates a test suite where the RXPK is fully complete -func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { - phyPayload := genPHYPayload(true) - rxpk := genRXPK(phyPayload) - metadata := genMetadata(rxpk) - test.CorePacket = NewRPacket(phyPayload, metadata) - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the RXPK contains partial metadata -func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { - phyPayload := genPHYPayload(true) - rxpk := genRXPK(phyPayload) - rxpk.Codr = nil - rxpk.Rfch = nil - rxpk.Rssi = nil - rxpk.Time = nil - rxpk.Size = nil - metadata := genMetadata(rxpk) - test.CorePacket = NewRPacket(phyPayload, metadata) - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the RXPK contains no data -func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { - rxpk := genRXPK(genPHYPayload(true)) - rxpk.Data = nil - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the core packet has all txpk metadata -func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - metadata.Chan = nil - metadata.Lsnr = nil - metadata.Rssi = nil - metadata.Stat = nil - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = NewRPacket(phyPayload, metadata) - return *test -} - -// Generates a test suite where the core packet has no metadata -func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := Metadata{} - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = NewRPacket(phyPayload, metadata) - return *test -} - -// Generates a test suite where the core packet has partial metadata but all supported -func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - metadata.Chan = nil - metadata.Lsnr = nil - metadata.Rssi = nil - metadata.Stat = nil - metadata.Modu = nil - metadata.Fdev = nil - metadata.Time = nil - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = NewRPacket(phyPayload, metadata) - return *test -} - -// Generates a test suite where the core packet has extra metadata not supported by txpk -func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = NewRPacket(phyPayload, metadata) - return *test -} From ab80a7ddaead7f434f2de58ac00a8deab13ec95b Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 19:23:39 +0100 Subject: [PATCH 0643/2266] [refactor] Change registration struct for an interface --- refactor/adapters/udp/adapter.go | 2 +- refactor/adapters/udp/udpRegistration.go | 39 ++++++++++++++++++++++++ refactor/interfaces.go | 13 +++++--- 3 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 refactor/adapters/udp/udpRegistration.go diff --git a/refactor/adapters/udp/adapter.go b/refactor/adapters/udp/adapter.go index 8c93912aa..5e04049c3 100644 --- a/refactor/adapters/udp/adapter.go +++ b/refactor/adapters/udp/adapter.go @@ -117,7 +117,7 @@ func (a *Adapter) Next() ([]byte, core.AckNacker, error) { // NextRegistration implements the core.Adapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return core.Registration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return udpRegistration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") } // listen Handle incoming packets and forward them. Runs in its own goroutine. diff --git a/refactor/adapters/udp/udpRegistration.go b/refactor/adapters/udp/udpRegistration.go new file mode 100644 index 000000000..3907b3595 --- /dev/null +++ b/refactor/adapters/udp/udpRegistration.go @@ -0,0 +1,39 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// udpRegistration is a blank type which implements the core.Registration interface +type udpRegistration struct{} + +// Recipient implements the core.Registration inteface +func (r udpRegistration) Recipient() core.Recipient { + return core.Recipient{} +} + +// AppEUI implements the core.Registration interface +func (r udpRegistration) AppEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") +} + +// DevEUI implements the core.Registration interface +func (r udpRegistration) DevEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") +} + +// AppSKey implements the core.Registration interface +func (r udpRegistration) AppSKey() (lorawan.AES128Key, error) { + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") +} + +// NwkSKey implements the core.Registration interface +func (r udpRegistration) NwkSKey() (lorawan.AES128Key, error) { + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") +} diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 5999bb6bd..16a5eb4b6 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -29,8 +29,6 @@ type Adapter interface { NextRegistration() (Registration, AckNacker, error) } -type Recipient []byte - type Packet interface { encoding.BinaryMarshaler fmt.Stringer @@ -42,11 +40,16 @@ type Packet interface { DevEUI() (lorawan.EUI64, error) } -type Registration struct { - Recipient Recipient - Options interface{} +type Registration interface { + Recipient() Recipient + AppEUI() (lorawan.EUI64, error) + AppSKey() (lorawan.AES128Key, error) + DevEUI() (lorawan.EUI64, error) + NwkSKey() (lorawan.AES128Key, error) } +type Recipient []byte + type Metadata struct { Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier From 783769a820ca59627208763f74c3a7e719eb6e8a Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 19:26:03 +0100 Subject: [PATCH 0644/2266] [refactor] Move entry read-writer to utils --- utils/readwriter/readwriter.go | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 utils/readwriter/readwriter.go diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go new file mode 100644 index 000000000..2ddc09109 --- /dev/null +++ b/utils/readwriter/readwriter.go @@ -0,0 +1,100 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package readwriter + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/brocaar/lorawan" +) + +// entryReadWriter offers convenient method to write and read successively from a bytes buffer. +type Interface struct { + err error + data *bytes.Buffer +} + +// newEntryReadWriter create a new read/writer from an existing buffer. +// +// If a nil or empty buffer is supplied, reading from the read/writer will cause an error (io.EOF) +// Nevertheless, if a valid non-empty buffer is given, the read/writer will start reading from the +// beginning of that buffer, and will start writting at the end of it. +func New(buf []byte) *Interface { + return &Interface{ + err: nil, + data: bytes.NewBuffer(buf), + } +} + +// Write appends the given data at the end of the existing buffer. +// +// It does nothing if an error was previously noticed and panics if the given data are something +// different from: []byte, string, AES128Key, EUI64, DevAddr. +// +// Also, it writes the length of the given raw data encoded on 2 bytes before writting the data +// itself. In that way, data can be appended and read easily. +func (w *Interface) Write(data interface{}) { + var raw []byte + switch data.(type) { + case []byte: + raw = data.([]byte) + case lorawan.AES128Key: + data := data.(lorawan.AES128Key) + raw = data[:] + case lorawan.EUI64: + data := data.(lorawan.EUI64) + raw = data[:] + case lorawan.DevAddr: + data := data.(lorawan.DevAddr) + raw = data[:] + case string: + raw = []byte(data.(string)) + default: + panic(fmt.Errorf("Unreckognized data type: %v", data)) + } + w.DirectWrite(uint16(len(raw))) + w.DirectWrite(raw) +} + +// DirectWrite appends the given data at the end of the existing buffer (without the length). +func (w *Interface) DirectWrite(data interface{}) { + if w.err != nil { + return + } + in := w.data.Next(w.data.Len()) + w.data = new(bytes.Buffer) + binary.Write(w.data, binary.BigEndian, in) + w.err = binary.Write(w.data, binary.BigEndian, data) +} + +// Read retrieves next data from the given buffer. Implicitely, this implies the data to have been +// written using the Write method (len | data). Data are sent back through a callback as an array of +// bytes. +func (w *Interface) Read(to func(data []byte)) { + if w.err != nil { + return + } + + lenTo := new(uint16) + if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil { + return + } + to(w.data.Next(int(*lenTo))) +} + +// Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and +// an error if any issue was encountered during the process. +func (w Interface) Bytes() ([]byte, error) { + if w.err != nil { + return nil, w.err + } + return w.data.Bytes(), nil +} + +// Err just return the err status of the read-writer. +func (w Interface) Err() error { + return w.err +} From 2743f250869b326da6a5e95ebb031d91fe9cea49 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 19:36:30 +0100 Subject: [PATCH 0645/2266] [refactor] Fix error message in udpRegistration --- refactor/adapters/udp/udpRegistration.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/refactor/adapters/udp/udpRegistration.go b/refactor/adapters/udp/udpRegistration.go index 3907b3595..291559181 100644 --- a/refactor/adapters/udp/udpRegistration.go +++ b/refactor/adapters/udp/udpRegistration.go @@ -20,20 +20,20 @@ func (r udpRegistration) Recipient() core.Recipient { // AppEUI implements the core.Registration interface func (r udpRegistration) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return lorawan.EUI64{}, errors.New(ErrNotSupported, "AppEUI not supported on udp registration") } // DevEUI implements the core.Registration interface func (r udpRegistration) DevEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return lorawan.EUI64{}, errors.New(ErrNotSupported, "DevEUI not supported on udp registration") } // AppSKey implements the core.Registration interface func (r udpRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "AppSKey not supported on udp registration") } // NwkSKey implements the core.Registration interface func (r udpRegistration) NwkSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NwkSKey not supported on udp registration") } From 9749dca0a41aff8f77b42644b7a9e66a05bab533 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 20:28:01 +0100 Subject: [PATCH 0646/2266] [refactor] Re-implement http adapter. The broadcaster is dead or more exactly, has been merged into the adapter. The http recipient now holds the contact method as well such that the Send method is completly protocol agnostic. Data are sent as an octet-stream and the interpretation is under the receiver responsabilities. Handlers to listen on given endpoints can be registered and can also generate registrations --- refactor/adapters/http/http.go | 234 +++++++++++++++++++++ refactor/adapters/http/httpAckNacker.go | 65 ++++++ refactor/adapters/http/httpRecipient.go | 30 +++ refactor/adapters/http/httpRegistration.go | 44 ++++ refactor/adapters/http/regAckNacker.go | 49 +++++ 5 files changed, 422 insertions(+) create mode 100644 refactor/adapters/http/http.go create mode 100644 refactor/adapters/http/httpAckNacker.go create mode 100644 refactor/adapters/http/httpRecipient.go create mode 100644 refactor/adapters/http/httpRegistration.go create mode 100644 refactor/adapters/http/regAckNacker.go diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go new file mode 100644 index 000000000..4ab2da1bb --- /dev/null +++ b/refactor/adapters/http/http.go @@ -0,0 +1,234 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "net/http" + "sync" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/apex/log" + "github.com/brocaar/lorawan" +) + +// Adapter type materializes an http adapter which implements the basic http protocol +type Adapter struct { + http.Client // Adapter is also an http client + ctx log.Interface // Just a logger, no one really cares about him. + packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently + recipients []core.Recipient // Known recipient used for broadcast if any + registrations chan RegReq // Incoming registrations + serveMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints +} + +// Message sent through the response channel of a pktReq or regReq +type MsgRes struct { + StatusCode int // The http status code to set as an answer + Content []byte // The response content. +} + +// Message sent through the packets channel when an incoming request arrives +type PktReq struct { + Packet []byte // The actual packet that has been parsed + Chresp chan MsgRes // A response channel waiting for an success or reject confirmation +} + +// Message sent through the registration channel when an incoming registration arrives +type RegReq struct { + Registration core.Registration + Chresp chan MsgRes +} + +// NewAdapter constructs and allocates a new http adapter +func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { + a := Adapter{ + Client: http.Client{}, + ctx: ctx, + packets: make(chan PktReq), + recipients: recipients, + serveMux: http.NewServeMux(), + } + + go a.listenRequests(port) + + return &a, nil +} + +// Send implements the core.Adapter interface +func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { + stats.MarkMeter("http_adapter.send") + stats.UpdateHistogram("http_adapter.send_recipients", int64(len(recipients))) + + // Marshal the packet to raw binary data + data, err := p.MarshalBinary() + if err != nil { + a.ctx.WithError(err).Warn("Invalid Packet") + return nil, errors.New(ErrInvalidStructure, err) + } + + // Try to define a more helpful context + var devEUI *lorawan.EUI64 + var ctx log.Interface + d, err := p.DevEUI() + if err != nil { + a.ctx.WithError(err).Warn("Unable to retrieve devEUI") + ctx = a.ctx + } else { + ctx = a.ctx.WithField("devEUI", devEUI) + devEUI = &d + } + ctx.Debug("Sending Packet") + + nb := len(recipients) + isBroadcast := false + + // Determine whether it's a broadcast or a direct send + if nb == 0 { + // If no recipient was supplied, try with the known one, otherwise quit. + recipients = a.recipients + nb = len(recipients) + isBroadcast = true + if nb == 0 { + return nil, errors.New(ErrFailedOperation, "No recipient found") + } + } + + // Prepare ground for parrallel http request + cherr := make(chan error, nb) + chresp := make(chan []byte, nb) + wg := sync.WaitGroup{} + wg.Add(nb) + + // Run each request + for _, recipient := range recipients { + go func(rawRecipient core.Recipient) { + defer wg.Done() + + // Get the actual recipient + recipient := new(httpRecipient) + if err := recipient.UnmarshalBinary(rawRecipient); err != nil { + ctx.WithError(err).Warn("Unable to interpret recipient as httpRecipient") + return + } + ctx := ctx.WithField("recipient", recipient.Url) + + // Send request + ctx.Debugf("%s Request", recipient.Method) + buf := new(bytes.Buffer) + buf.Write(data) + resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Url), "application/octet-stream", buf) + if err != nil { + cherr <- err + return + } + defer func() { + // This is needed because the default HTTP client's Transport does not + // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections unless the Body + // is read to completion and is closed. + io.Copy(ioutil.Discard, resp.Body) + resp.Body.Close() + }() + + // Check response code + switch resp.StatusCode { + case http.StatusOK: + ctx.Debug("Recipient registered for packet") + data, err := ioutil.ReadAll(resp.Body) + if err != nil && err != io.EOF { + cherr <- err + return + } + chresp <- data + if isBroadcast { // Generate registration on broadcast + a.registrations <- RegReq{ + Registration: httpRegistration{ + recipient: rawRecipient, + devEUI: devEUI, + }, + Chresp: nil, + } + } + case http.StatusNotFound: + ctx.Debug("Recipient not interested in packet") + cherr <- errors.New(ErrWrongBehavior, "Recipient not interested") + default: + cherr <- errors.New(ErrFailedOperation, fmt.Sprintf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode)) + } + }(recipient) + } + + // Wait for each request to be done + stats.IncCounter("http_adapter.waiting_for_send") + wg.Wait() + stats.DecCounter("http_adapter.waiting_for_send") + close(cherr) + close(chresp) + + // Collect errors and see if everything went well + var errored uint8 + for i := 0; i < len(cherr); i += 1 { + err := <-cherr + if err.(errors.Failure).Nature != ErrWrongBehavior { + errored += 1 + ctx.WithError(err).Error("POST Failed") + } + } + + // Collect response + if len(chresp) > 1 { + return nil, errors.New(ErrWrongBehavior, "Received too many positive answers") + } + + if len(chresp) == 0 && errored != 0 { + return nil, errors.New(ErrFailedOperation, "Unable to send to any recipient") + } + + if len(chresp) == 0 && errored == 0 { + return nil, errors.New(ErrWrongBehavior, "No recipient gave a positive answer") + } + + return <-chresp, nil +} + +// Next implements the core.Adapter interface +func (a *Adapter) Next() ([]byte, core.AckNacker, error) { + p := <-a.packets + return p.Packet, httpAckNacker{Chresp: p.Chresp}, nil +} + +// NextRegistration implements the core.Adapter interface. Not implemented for this adapter. +// +// See broadcast and pubsub adapters for mechanisms to handle registrations. +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + r := <-a.registrations + return r.Registration, regAckNacker{Chresp: r.Chresp}, nil +} + +type Handler func(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) + +// Bind registers a handler to a specific endpoint +func (a *Adapter) Bind(url string, handle Handler) { + a.ctx.WithField("url", url).Info("Register new endpoint") + a.serveMux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { + handle(w, a.registrations, req) + }) +} + +// listenRequests handles incoming registration request sent through http to the adapter +func (a *Adapter) listenRequests(port uint) { + server := http.Server{ + Addr: fmt.Sprintf("0.0.0.0:%d", port), + Handler: a.serveMux, + } + a.ctx.WithField("port", port).Info("Starting Server") + err := server.ListenAndServe() + a.ctx.WithError(err).Warn("HTTP connection lost") +} diff --git a/refactor/adapters/http/httpAckNacker.go b/refactor/adapters/http/httpAckNacker.go new file mode 100644 index 000000000..159b56fbd --- /dev/null +++ b/refactor/adapters/http/httpAckNacker.go @@ -0,0 +1,65 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "encoding/json" + "net/http" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// httpAckNacker implements the AckNacker interface +type httpAckNacker struct { + Chresp chan<- MsgRes // A channel dedicated to send back a response +} + +// Ack implements the core.AckNacker interface +func (an httpAckNacker) Ack(p *core.Packet) error { + if an.Chresp == nil { + return nil + } + defer close(an.Chresp) + + if p == nil { + an.Chresp <- MsgRes{StatusCode: http.StatusOK} + return nil + } + + raw, err := json.Marshal(*p) + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + + select { + case an.Chresp <- MsgRes{ + StatusCode: http.StatusOK, + Content: raw, + }: + return nil + case <-time.After(time.Millisecond * 50): + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + } +} + +// Nack implements the core.AckNacker interface +func (an httpAckNacker) Nack() error { + if an.Chresp == nil { + return nil + } + defer close(an.Chresp) + + select { + case an.Chresp <- MsgRes{ + StatusCode: http.StatusNotFound, + Content: []byte(`{"message":"Not in charge of the associated device"}`), + }: + case <-time.After(time.Millisecond * 50): + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + } + return nil +} diff --git a/refactor/adapters/http/httpRecipient.go b/refactor/adapters/http/httpRecipient.go new file mode 100644 index 000000000..d7ea2b957 --- /dev/null +++ b/refactor/adapters/http/httpRecipient.go @@ -0,0 +1,30 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "github.com/TheThingsNetwork/ttn/utils/readwriter" +) + +// HttpRecipient materializes recipients manipulated by the http adapter +type httpRecipient struct { + Url string + Method string +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (h *httpRecipient) MarshalBinary() ([]byte, error) { + w := readwriter.New(nil) + w.Write(h.Url) + w.Write(h.Method) + return w.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (h httpRecipient) UnmarshalBinary(data []byte) error { + r := readwriter.New(data) + r.Read(func(data []byte) { h.Url = string(data) }) + r.Read(func(data []byte) { h.Method = string(data) }) + return r.Err() +} diff --git a/refactor/adapters/http/httpRegistration.go b/refactor/adapters/http/httpRegistration.go new file mode 100644 index 000000000..987aa57f6 --- /dev/null +++ b/refactor/adapters/http/httpRegistration.go @@ -0,0 +1,44 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +type httpRegistration struct { + recipient core.Recipient + devEUI *lorawan.EUI64 +} + +// Recipient implements the core.Registration inteface +func (r httpRegistration) Recipient() core.Recipient { + return r.recipient +} + +// AppEUI implements the core.Registration interface +func (r httpRegistration) AppEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, errors.New(ErrNotSupported, "AppEUI not supported on http registration") +} + +// DevEUI implements the core.Registration interface +func (r httpRegistration) DevEUI() (lorawan.EUI64, error) { + if r.devEUI == nil { + return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "DevEUI not accessible on this registration") + } + return *r.devEUI, nil +} + +// AppSKey implements the core.Registration interface +func (r httpRegistration) AppSKey() (lorawan.AES128Key, error) { + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "AppSKey not supported on http registration") +} + +// NwkSKey implements the core.Registration interface +func (r httpRegistration) NwkSKey() (lorawan.AES128Key, error) { + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") +} diff --git a/refactor/adapters/http/regAckNacker.go b/refactor/adapters/http/regAckNacker.go new file mode 100644 index 000000000..f99473a49 --- /dev/null +++ b/refactor/adapters/http/regAckNacker.go @@ -0,0 +1,49 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "net/http" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// An ackNacker for http registrations +type regAckNacker struct { + Chresp chan<- MsgRes // A channel dedicated to send back a response +} + +// Ack implements the core.Acker interface +func (r regAckNacker) Ack(p *core.Packet) error { + if r.Chresp == nil { + return nil + } + defer close(r.Chresp) + + select { + case r.Chresp <- MsgRes{StatusCode: http.StatusAccepted}: + return nil + case <-time.After(time.Millisecond * 50): + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + } +} + +// Nack implements the core.Nacker interface +func (r regAckNacker) Nack() error { + if r.Chresp == nil { + return nil + } + select { + case r.Chresp <- MsgRes{ + StatusCode: http.StatusConflict, + Content: []byte("Unable to register the given device"), + }: + return nil + case <-time.After(time.Millisecond * 50): + return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + } +} From f603f08ffa829635ed85d0cac0ed6dba6afa3aa7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 22:22:18 +0100 Subject: [PATCH 0647/2266] [refactor] Fix error checking in udp test --- refactor/adapters/udp/handlers/semtech_test.go | 14 +++++++++++--- refactor/adapters/udp/udpRegistration.go | 2 +- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/refactor/adapters/udp/handlers/semtech_test.go b/refactor/adapters/udp/handlers/semtech_test.go index 3665cf2d3..9b7001adc 100644 --- a/refactor/adapters/udp/handlers/semtech_test.go +++ b/refactor/adapters/udp/handlers/semtech_test.go @@ -116,16 +116,24 @@ func getNextPacket(next chan interface{}) (core.Packet, error) { // ----- CHECK utilities func checkErrors(t *testing.T, want *string, got error) { - if got == nil && want != nil { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } Ko(t, "Expected error to be {%s} but got nothing", *want) return } - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") + if want == nil { + Ko(t, "Expected no error but got {%v}", got) return } + if got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } Ko(t, "Expected error to be {%s} but got {%v}", *want, got) } diff --git a/refactor/adapters/udp/udpRegistration.go b/refactor/adapters/udp/udpRegistration.go index 291559181..7aed8cbfa 100644 --- a/refactor/adapters/udp/udpRegistration.go +++ b/refactor/adapters/udp/udpRegistration.go @@ -15,7 +15,7 @@ type udpRegistration struct{} // Recipient implements the core.Registration inteface func (r udpRegistration) Recipient() core.Recipient { - return core.Recipient{} + return nil } // AppEUI implements the core.Registration interface From 69fbbf67613cf70d5d22b1d6a9b3635b023ed0ac Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 22:22:38 +0100 Subject: [PATCH 0648/2266] [refactor] Fix error checking in core --- refactor/check_utilities_test.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go index 24929d7a0..0d5174d8a 100644 --- a/refactor/check_utilities_test.go +++ b/refactor/check_utilities_test.go @@ -39,12 +39,25 @@ func checkPackets(t *testing.T, want Packet, got Packet) { // Checks that errors match func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) return } - Ko(t, "Expected error to be %s but got %v", want, got) + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) } // Checks that obtained json matches expected one From 902036b084d0e9aa17eda4afa80f1b0e68239190 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 22:22:58 +0100 Subject: [PATCH 0649/2266] [refactor] Change type Recipient for an interface --- refactor/interfaces.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 16a5eb4b6..a8894282f 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -48,7 +48,9 @@ type Registration interface { NwkSKey() (lorawan.AES128Key, error) } -type Recipient []byte +type Recipient interface { + encoding.BinaryMarshaler +} type Metadata struct { Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) From d67710f48874b05e761204df66d60b6044b2d06f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 22:23:23 +0100 Subject: [PATCH 0650/2266] [refactor] Make http compliant to new Recipient interface + fix errors in Send method --- refactor/adapters/http/http.go | 38 +++++++++++++------------ refactor/adapters/http/httpRecipient.go | 4 +-- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index 4ab2da1bb..fb16f47dc 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -50,11 +50,12 @@ type RegReq struct { // NewAdapter constructs and allocates a new http adapter func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { a := Adapter{ - Client: http.Client{}, - ctx: ctx, - packets: make(chan PktReq), - recipients: recipients, - serveMux: http.NewServeMux(), + Client: http.Client{}, + ctx: ctx, + packets: make(chan PktReq), + recipients: recipients, + registrations: make(chan RegReq), + serveMux: http.NewServeMux(), } go a.listenRequests(port) @@ -87,10 +88,9 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } ctx.Debug("Sending Packet") + // Determine whether it's a broadcast or a direct send nb := len(recipients) isBroadcast := false - - // Determine whether it's a broadcast or a direct send if nb == 0 { // If no recipient was supplied, try with the known one, otherwise quit. recipients = a.recipients @@ -113,9 +113,9 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err defer wg.Done() // Get the actual recipient - recipient := new(httpRecipient) - if err := recipient.UnmarshalBinary(rawRecipient); err != nil { - ctx.WithError(err).Warn("Unable to interpret recipient as httpRecipient") + recipient, ok := rawRecipient.(httpRecipient) + if !ok { + ctx.WithField("recipient", rawRecipient).Warn("Unable to interpret recipient as httpRecipient") return } ctx := ctx.WithField("recipient", recipient.Url) @@ -148,13 +148,15 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } chresp <- data if isBroadcast { // Generate registration on broadcast - a.registrations <- RegReq{ - Registration: httpRegistration{ - recipient: rawRecipient, - devEUI: devEUI, - }, - Chresp: nil, - } + go func() { + a.registrations <- RegReq{ + Registration: httpRegistration{ + recipient: rawRecipient, + devEUI: devEUI, + }, + Chresp: nil, + } + }() } case http.StatusNotFound: ctx.Debug("Recipient not interested in packet") @@ -188,7 +190,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } if len(chresp) == 0 && errored != 0 { - return nil, errors.New(ErrFailedOperation, "Unable to send to any recipient") + return nil, errors.New(ErrFailedOperation, "No positive response from recipients but got unexpected answer") } if len(chresp) == 0 && errored == 0 { diff --git a/refactor/adapters/http/httpRecipient.go b/refactor/adapters/http/httpRecipient.go index d7ea2b957..f7b663dc5 100644 --- a/refactor/adapters/http/httpRecipient.go +++ b/refactor/adapters/http/httpRecipient.go @@ -14,7 +14,7 @@ type httpRecipient struct { } // MarshalBinary implements the encoding.BinaryMarshaler interface -func (h *httpRecipient) MarshalBinary() ([]byte, error) { +func (h httpRecipient) MarshalBinary() ([]byte, error) { w := readwriter.New(nil) w.Write(h.Url) w.Write(h.Method) @@ -22,7 +22,7 @@ func (h *httpRecipient) MarshalBinary() ([]byte, error) { } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (h httpRecipient) UnmarshalBinary(data []byte) error { +func (h *httpRecipient) UnmarshalBinary(data []byte) error { r := readwriter.New(data) r.Read(func(data []byte) { h.Url = string(data) }) r.Read(func(data []byte) { h.Method = string(data) }) From 36c4ab8369b37842bbf7f2ea9c27f157b30edb04 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 22:23:32 +0100 Subject: [PATCH 0651/2266] [refactor] Add package root doc --- refactor/adapters/http/doc.go | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 refactor/adapters/http/doc.go diff --git a/refactor/adapters/http/doc.go b/refactor/adapters/http/doc.go new file mode 100644 index 000000000..aed3c9d11 --- /dev/null +++ b/refactor/adapters/http/doc.go @@ -0,0 +1,11 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +// Package http provides adapter implementations which run on top of http. +// +// The different protocols and mechanisms used are defined in the following document: +// https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/protocols.md +// +// The basic http adapter module can be used as a brick to build something bigger by registering +// specific endpoints. +package http From 90c5b144ed89cb522372e0fd538967dd57af2598 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 22 Feb 2016 22:24:10 +0100 Subject: [PATCH 0652/2266] [refactor] Re-use test from broadcast in http adapter --- refactor/adapters/http/http_test.go | 282 ++++++++++++++++++++++ refactor/adapters/http/testPacket_test.go | 63 +++++ 2 files changed, 345 insertions(+) create mode 100644 refactor/adapters/http/http_test.go create mode 100644 refactor/adapters/http/testPacket_test.go diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go new file mode 100644 index 000000000..1ec5bf4b4 --- /dev/null +++ b/refactor/adapters/http/http_test.go @@ -0,0 +1,282 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "io" + "net/http" + "reflect" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +type testRecipient struct { + httpRecipient + Behavior string +} + +type testRegistration struct { + Recipient testRecipient + DevEUI lorawan.EUI64 +} + +func TestSend(t *testing.T) { + recipients := []testRecipient{ + testRecipient{ + httpRecipient: httpRecipient{ + Url: "0.0.0.0:3010", + Method: "POST", + }, + Behavior: "AlwaysReject", + }, + testRecipient{ + httpRecipient: httpRecipient{ + Url: "0.0.0.0:3011", + Method: "POST", + }, + Behavior: "AlwaysAccept", + }, + testRecipient{ + httpRecipient: httpRecipient{ + Url: "0.0.0.0:3012", + Method: "POST", + }, + Behavior: "AlwaysReject", + }, + testRecipient{ + httpRecipient: httpRecipient{ + Url: "0.0.0.0:3013", + Method: "POST", + }, + Behavior: "AlwaysReject", + }, + } + + tests := []struct { + Recipients []testRecipient + Packet testPacket + WantRegistrations []testRegistration + WantPayload string + WantError *string + }{ + { // Send to recipient a valid packet + Recipients: recipients[1:2], // TODO test with a rejection. Need better error handling + Packet: testPacket{ + devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + payload: "payload", + }, + WantRegistrations: nil, + WantPayload: "payload", + WantError: nil, + }, + { // Broadcast a valid packet + Recipients: nil, + Packet: testPacket{ + devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + payload: "payload", + }, + WantRegistrations: []testRegistration{ + { + Recipient: recipients[1], + DevEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + }, + }, + WantPayload: "payload", + WantError: nil, + }, + { // Send to two recipients an invalid packet + Recipients: recipients[:2], + Packet: testPacket{}, + WantRegistrations: nil, + WantPayload: "", + WantError: pointer.String(ErrInvalidStructure), + }, + { // Broadcast an invalid packet + Recipients: nil, + Packet: testPacket{}, + WantRegistrations: nil, + WantPayload: "", + WantError: pointer.String(ErrInvalidStructure), + }, + } + + // Logging + ctx := GetLogger(t, "Adapter") + + // Build + adapter, err := NewAdapter(3015, toHttpRecipient(recipients), ctx) + if err != nil { + panic(err) + } + var servers []chan string + for _, r := range recipients { + servers = append(servers, genMockServer(r)) + } + + for _, test := range tests { + // Describe + Desc(t, "Sending packet %v to %v", test.Packet, test.Recipients) + + // Operate + _, err := adapter.Send(test.Packet, toHttpRecipient(test.Recipients)...) + registrations := getRegistrations(adapter, test.WantRegistrations) + payloads := getPayloads(servers) + + // Check + <-time.After(time.Second) + checkErrors(t, test.WantError, err) + checkPayloads(t, test.WantPayload, payloads) + checkRegistrations(t, test.WantRegistrations, registrations) + } +} + +// Convert testRecipient to core.Recipient +func toHttpRecipient(recipients []testRecipient) []core.Recipient { + var https []core.Recipient + for _, r := range recipients { + https = append(https, r.httpRecipient) + } + return https +} + +// Operate utilities +func getPayloads(chpayloads []chan string) []string { + var got []string + for _, ch := range chpayloads { + select { + case payload := <-ch: + got = append(got, payload) + case <-time.After(50 * time.Millisecond): + } + } + return got +} + +func getRegistrations(adapter *Adapter, want []testRegistration) []core.Registration { + var got []core.Registration + for range want { + ch := make(chan core.Registration) + go func() { + r, an, err := adapter.NextRegistration() + if err != nil { + return + } + an.Ack(nil) + ch <- r + }() + select { + case r := <-ch: + got = append(got, r) + case <-time.After(50 * time.Millisecond): + } + } + return got +} + +// Build utilities + +func genMockServer(recipient core.Recipient) chan string { + chresp := make(chan string) + serveMux := http.NewServeMux() + serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { + if req.Header.Get("Content-Type") != "application/octet-stream" { + w.WriteHeader(http.StatusBadRequest) + w.Write(nil) + return + } + + buf := make([]byte, req.ContentLength) + n, err := req.Body.Read(buf) + if err != nil && err != io.EOF { + w.WriteHeader(http.StatusBadRequest) + w.Write(nil) + return + } + + switch recipient.(testRecipient).Behavior { + case "AlwaysReject": + w.WriteHeader(http.StatusNotFound) + w.Write(nil) + case "AlwaysAccept": + w.Header().Add("Content-Type", "application/octet-stream") + w.WriteHeader(http.StatusOK) + w.Write(buf[:n]) // TODO, should respond another packet, not the same + } + go func() { chresp <- string(buf[:n]) }() + }) + + server := http.Server{ + Addr: recipient.(testRecipient).Url, + Handler: serveMux, + } + go server.ListenAndServe() + return chresp +} + +// Check utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkRegistrations(t *testing.T, want []testRegistration, got []core.Registration) { + if len(want) != len(got) { + Ko(t, "Expected %d registrations but got %d", len(want), len(got)) + return + } + +outer: + for _, rw := range want { + for _, rg := range got { + devEUI, err := rg.DevEUI() + if err != nil { + Ko(t, "Got an invalid registration %+v", rg) + return + } + if devEUI != rw.DevEUI { + Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, devEUI) + } + if reflect.DeepEqual(rw.Recipient.httpRecipient, rg.Recipient()) { + continue outer + } + } + Ko(t, "Registrations don't match expectation.\nWant: %v\nGot: %v", want, got) + return + } + Ok(t, "Check registrations") +} + +func checkPayloads(t *testing.T, want string, got []string) { + for _, payload := range got { + if want != payload { + Ko(t, "Paylaod don't match expectation.\nWant: %s\nGot: %s", want, payload) + return + } + } + Ok(t, "Check payloads") +} diff --git a/refactor/adapters/http/testPacket_test.go b/refactor/adapters/http/testPacket_test.go new file mode 100644 index 000000000..93993ece7 --- /dev/null +++ b/refactor/adapters/http/testPacket_test.go @@ -0,0 +1,63 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "encoding" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// type testPacket materializes packets manipulated by the broker and corresponding adapter handlers +type testPacket struct { + devEUI lorawan.EUI64 + payload string +} + +// FCnt implements the core.Packet interface +func (p testPacket) FCnt() (uint32, error) { + return 0, nil +} + +// Payload implements the core.Packet interface +func (p testPacket) Payload() encoding.BinaryMarshaler { + return nil +} + +// Metadata implements the core.Packet interface +func (p testPacket) Metadata() []core.Metadata { + return nil +} + +// AppEUI implements the core.Packet interface +func (p testPacket) AppEUI() (lorawan.EUI64, error) { + return lorawan.EUI64{}, nil +} + +// DevEUI implements the core.Packet interface +func (p testPacket) DevEUI() (lorawan.EUI64, error) { + return p.devEUI, nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p testPacket) MarshalBinary() ([]byte, error) { + if p.payload == "" { + return nil, errors.New(ErrInvalidStructure, "Fake error") + } + + return []byte(p.payload), nil +} + +// MarshalBinary implements the encoding.BinaryUnMarshaler interface +func (p *testPacket) UnmarshalBinary(data []byte) error { + return nil +} + +// String implements the core.Packet interface +func (p testPacket) String() string { + return p.payload +} From 13d225a9feb23399040a159cc6d5f7a1a0c5dce3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 10:17:11 +0100 Subject: [PATCH 0653/2266] [refactor] Use of an interface for HttpRecipient (so that handlers can define their own recipient) --- refactor/adapters/http/http.go | 8 +++---- refactor/adapters/http/httpRecipient.go | 30 ++++++++++++++++++++----- refactor/adapters/http/http_test.go | 18 +++++++-------- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index fb16f47dc..25ce126d5 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -113,18 +113,18 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err defer wg.Done() // Get the actual recipient - recipient, ok := rawRecipient.(httpRecipient) + recipient, ok := rawRecipient.(HttpRecipient) if !ok { ctx.WithField("recipient", rawRecipient).Warn("Unable to interpret recipient as httpRecipient") return } - ctx := ctx.WithField("recipient", recipient.Url) + ctx := ctx.WithField("recipient", recipient.Url()) // Send request - ctx.Debugf("%s Request", recipient.Method) + ctx.Debugf("%s Request", recipient.Method()) buf := new(bytes.Buffer) buf.Write(data) - resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Url), "application/octet-stream", buf) + resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Url()), "application/octet-stream", buf) if err != nil { cherr <- err return diff --git a/refactor/adapters/http/httpRecipient.go b/refactor/adapters/http/httpRecipient.go index f7b663dc5..8ab9dc108 100644 --- a/refactor/adapters/http/httpRecipient.go +++ b/refactor/adapters/http/httpRecipient.go @@ -4,27 +4,45 @@ package http import ( + "encoding" + "github.com/TheThingsNetwork/ttn/utils/readwriter" ) +type HttpRecipient interface { + encoding.BinaryMarshaler + Url() string + Method() string +} + // HttpRecipient materializes recipients manipulated by the http adapter type httpRecipient struct { - Url string - Method string + url string + method string +} + +// Url implements the HttpRecipient interface +func (h httpRecipient) Url() string { + return h.url +} + +// Method implements the HttpRecipient interface +func (h httpRecipient) Method() string { + return h.method } // MarshalBinary implements the encoding.BinaryMarshaler interface func (h httpRecipient) MarshalBinary() ([]byte, error) { w := readwriter.New(nil) - w.Write(h.Url) - w.Write(h.Method) + w.Write(h.url) + w.Write(h.method) return w.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (h *httpRecipient) UnmarshalBinary(data []byte) error { r := readwriter.New(data) - r.Read(func(data []byte) { h.Url = string(data) }) - r.Read(func(data []byte) { h.Method = string(data) }) + r.Read(func(data []byte) { h.url = string(data) }) + r.Read(func(data []byte) { h.method = string(data) }) return r.Err() } diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go index 1ec5bf4b4..2094a3e8a 100644 --- a/refactor/adapters/http/http_test.go +++ b/refactor/adapters/http/http_test.go @@ -32,29 +32,29 @@ func TestSend(t *testing.T) { recipients := []testRecipient{ testRecipient{ httpRecipient: httpRecipient{ - Url: "0.0.0.0:3010", - Method: "POST", + url: "0.0.0.0:3010", + method: "POST", }, Behavior: "AlwaysReject", }, testRecipient{ httpRecipient: httpRecipient{ - Url: "0.0.0.0:3011", - Method: "POST", + url: "0.0.0.0:3011", + method: "POST", }, Behavior: "AlwaysAccept", }, testRecipient{ httpRecipient: httpRecipient{ - Url: "0.0.0.0:3012", - Method: "POST", + url: "0.0.0.0:3012", + method: "POST", }, Behavior: "AlwaysReject", }, testRecipient{ httpRecipient: httpRecipient{ - Url: "0.0.0.0:3013", - Method: "POST", + url: "0.0.0.0:3013", + method: "POST", }, Behavior: "AlwaysReject", }, @@ -214,7 +214,7 @@ func genMockServer(recipient core.Recipient) chan string { }) server := http.Server{ - Addr: recipient.(testRecipient).Url, + Addr: recipient.(HttpRecipient).Url(), Handler: serveMux, } go server.ListenAndServe() From 2dd853fa0810629cee6bab3a32454672ce544051 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 10:49:01 +0100 Subject: [PATCH 0654/2266] [refactor] Move type declaration at top of http file --- refactor/adapters/http/http.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index 25ce126d5..22fa4eed4 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -29,6 +29,9 @@ type Adapter struct { serveMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints } +// Handler defines endpoint-specific handler. +type Handler func(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) + // Message sent through the response channel of a pktReq or regReq type MsgRes struct { StatusCode int // The http status code to set as an answer @@ -214,8 +217,6 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) return r.Registration, regAckNacker{Chresp: r.Chresp}, nil } -type Handler func(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) - // Bind registers a handler to a specific endpoint func (a *Adapter) Bind(url string, handle Handler) { a.ctx.WithField("url", url).Info("Register new endpoint") From 0efaf87ed14d37cbebaacbdb42446197989238ce Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 10:52:44 +0100 Subject: [PATCH 0655/2266] [refactor] Define pubsub registration implementation --- .../http/handlers/pubsubRegistration.go | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 refactor/adapters/http/handlers/pubsubRegistration.go diff --git a/refactor/adapters/http/handlers/pubsubRegistration.go b/refactor/adapters/http/handlers/pubsubRegistration.go new file mode 100644 index 000000000..842903ef1 --- /dev/null +++ b/refactor/adapters/http/handlers/pubsubRegistration.go @@ -0,0 +1,45 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// type pubSubRegistration implements the core.Registration interface +type pubSubRegistration struct { + recipient HttpRecipient + appEui lorawan.EUI64 + nwksKey lorawan.AES128Key + devEUI lorawan.EUI64 +} + +// Recipient implements` the core.Registration interface +func (r pubSubRegistration) Recipient() core.Recipient { + return r.recipient +} + +// AppEUI implements the core.Registration interface +func (r pubSubRegistration) AppEUI() (lorawan.EUI64, error) { + return r.appEui, nil +} + +// AppSKey implements the core.Registration interface +func (r pubSubRegistration) AppSKey() (lorawan.AES128Key, error) { + return lorawan.AES128Key{}, errors.New(ErrNotSupported, "AppSKey noy supported on pubsub registration") +} + +// DevEUI implements the core.Registration interface +func (r pubSubRegistration) DevEUI() (lorawan.EUI64, error) { + return r.devEUI.nil +} + +// NwkSKey implement the core.Registration interface +func (r pubSubRegistration) NwkSKey() (lorawan.AES128Key, error) { + return r.nwksKey, nil +} From 3a4a7e325c61f289ab003ceae9782eae9afaf099 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:05:47 +0100 Subject: [PATCH 0656/2266] [refactor] Reduce responsibility of packet interface. Handlers are in charge of casting --- refactor/adapters/udp/handlers/semtech.go | 14 ++++++++------ refactor/interfaces.go | 6 ------ refactor/rpacket.go | 23 ++--------------------- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index e77d93898..0d2ccef50 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -156,8 +156,14 @@ func rxpk2packet(p semtech.RXPK) (core.Packet, error) { } func packet2txpk(p core.Packet) (semtech.TXPK, error) { + // Interpret the packet as a Router Packet. + rPkt, ok := p.(core.RPacket) + if !ok { + return semtech.TXPK{}, errors.New(ErrInvalidStructure, "Unable to interpret packet as a RPacket") + } + // Step 1, convert the physical payload to a base64 string (without the padding) - raw, err := p.Payload().MarshalBinary() + raw, err := rPkt.Payload().MarshalBinary() if err != nil { return semtech.TXPK{}, errors.New(ErrInvalidStructure, err) } @@ -167,11 +173,7 @@ func packet2txpk(p core.Packet) (semtech.TXPK, error) { // Step 2, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. - m := p.Metadata() - if len(m) != 1 { - return semtech.TXPK{}, errors.New(ErrInvalidStructure, "Invalid metadata structure") - } - metadataValue := reflect.ValueOf(m[0]) + metadataValue := reflect.ValueOf(rPkt.Metadata()) metadataStruct := metadataValue.Type() txpkStruct := reflect.ValueOf(&txpk).Elem() for i := 0; i < metadataStruct.NumField(); i += 1 { diff --git a/refactor/interfaces.go b/refactor/interfaces.go index a8894282f..77ede1f5e 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -32,12 +32,6 @@ type Adapter interface { type Packet interface { encoding.BinaryMarshaler fmt.Stringer - Payload() encoding.BinaryMarshaler - Metadata() []Metadata - - FCnt() (uint32, error) - AppEUI() (lorawan.EUI64, error) - DevEUI() (lorawan.EUI64, error) } type Registration interface { diff --git a/refactor/rpacket.go b/refactor/rpacket.go index cdef38321..b9ec01627 100644 --- a/refactor/rpacket.go +++ b/refactor/rpacket.go @@ -35,33 +35,14 @@ func (p RPacket) String() string { return str } -// FCnt implements the core.Packet interface -func (p RPacket) FCnt() (uint32, error) { - if p.payload.MACPayload == nil { - return 0, errors.New(ErrInvalidStructure, "MACPayload should not be empty") - } - - macpayload, ok := p.payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return 0, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") - } - - return macpayload.FHDR.FCnt, nil -} - // Payload implements the core.Packet interface func (p RPacket) Payload() encoding.BinaryMarshaler { return p.payload } // Metadata implements the core.Packet interface -func (p RPacket) Metadata() []Metadata { - return []Metadata{p.metadata} -} - -// AppEUI implements the core.Packet interface -func (p RPacket) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(ErrNotSupported, "AppEUI not supported for RPacket") +func (p RPacket) Metadata() Metadata { + return p.metadata } // DevEUI implements the core.Packet interface From 0f1050988e1a2f5f4293a3079c58bae6e050e6d7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:13:42 +0100 Subject: [PATCH 0657/2266] [refactor] Use of addressable interface to describe packet with a DevEUI --- refactor/adapters/http/http.go | 13 ++++++++----- refactor/interfaces.go | 4 ++++ refactor/rpacket.go | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index 22fa4eed4..551da21d5 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -81,13 +81,16 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Try to define a more helpful context var devEUI *lorawan.EUI64 var ctx log.Interface - d, err := p.DevEUI() - if err != nil { + addressable, ok := p.(core.Addressable) + if ok { + if d, err := addressable.DevEUI(); err == nil { + ctx = a.ctx.WithField("devEUI", devEUI) + devEUI = &d + } + } + if devEUI == nil { a.ctx.WithError(err).Warn("Unable to retrieve devEUI") ctx = a.ctx - } else { - ctx = a.ctx.WithField("devEUI", devEUI) - devEUI = &d } ctx.Debug("Sending Packet") diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 77ede1f5e..f07828860 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -34,6 +34,10 @@ type Packet interface { fmt.Stringer } +type Addressable interface { + DevEUI() (lorawan.EUI64, error) +} + type Registration interface { Recipient() Recipient AppEUI() (lorawan.EUI64, error) diff --git a/refactor/rpacket.go b/refactor/rpacket.go index b9ec01627..9e81a5e26 100644 --- a/refactor/rpacket.go +++ b/refactor/rpacket.go @@ -45,7 +45,7 @@ func (p RPacket) Metadata() Metadata { return p.metadata } -// DevEUI implements the core.Packet interface +// DevEUI implements the core.Addressable interface func (p RPacket) DevEUI() (lorawan.EUI64, error) { if p.payload.MACPayload == nil { return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "MACPAyload should not be empty") From 8d924296c38a577ecaa0328e2b4351fbfe45d0b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:27:47 +0100 Subject: [PATCH 0658/2266] [refactor] Define backbone of pubsub handler --- refactor/adapters/http/handlers/pubsub.go | 35 +++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 refactor/adapters/http/handlers/pubsub.go diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go new file mode 100644 index 000000000..c32f887ab --- /dev/null +++ b/refactor/adapters/http/handlers/pubsub.go @@ -0,0 +1,35 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "net/http" + + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" +) + +// Pubsub defines an handler to handle application | devEUI registration on a component. +// +// It listens to request of the form: [PUT] /end-devices/:devEUI +// where devEUI is a 8 bytes hex-encoded address. +// +// It also looks for params: +// +// - app_eui (8 bytes hex-encoded string) +// - app_url (http address as string) +// - nwks_key (16 bytes hex-encoded string) +// +// It fails with an http 400 Bad Request. if one of the parameter is missing or invalid +// It succeeds with an http 2xx if the request is valid (the response status is under the +// ackNacker responsibility. +// It can possibly fails with another status depending of the AckNacker response. +func PubSub(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) { + +} + +// parse extracts params from the request and fails if the request is invalid. +func parse(req *http.Request) (core.Registration, error) { + return nil, nil +} From 41c079aec4dcdc2952bfd6ac56f600f5bb6bad83 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:28:09 +0100 Subject: [PATCH 0659/2266] [refactor] Add constructor to httpRecipient and fix typos in pubsubRegistrations --- refactor/adapters/http/handlers/pubsubRegistration.go | 10 +++++----- refactor/adapters/http/httpRecipient.go | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/refactor/adapters/http/handlers/pubsubRegistration.go b/refactor/adapters/http/handlers/pubsubRegistration.go index 842903ef1..0b279e73f 100644 --- a/refactor/adapters/http/handlers/pubsubRegistration.go +++ b/refactor/adapters/http/handlers/pubsubRegistration.go @@ -14,8 +14,8 @@ import ( // type pubSubRegistration implements the core.Registration interface type pubSubRegistration struct { recipient HttpRecipient - appEui lorawan.EUI64 - nwksKey lorawan.AES128Key + appEUI lorawan.EUI64 + nwkSKey lorawan.AES128Key devEUI lorawan.EUI64 } @@ -26,7 +26,7 @@ func (r pubSubRegistration) Recipient() core.Recipient { // AppEUI implements the core.Registration interface func (r pubSubRegistration) AppEUI() (lorawan.EUI64, error) { - return r.appEui, nil + return r.appEUI, nil } // AppSKey implements the core.Registration interface @@ -36,10 +36,10 @@ func (r pubSubRegistration) AppSKey() (lorawan.AES128Key, error) { // DevEUI implements the core.Registration interface func (r pubSubRegistration) DevEUI() (lorawan.EUI64, error) { - return r.devEUI.nil + return r.devEUI, nil } // NwkSKey implement the core.Registration interface func (r pubSubRegistration) NwkSKey() (lorawan.AES128Key, error) { - return r.nwksKey, nil + return r.nwkSKey, nil } diff --git a/refactor/adapters/http/httpRecipient.go b/refactor/adapters/http/httpRecipient.go index 8ab9dc108..6f402fead 100644 --- a/refactor/adapters/http/httpRecipient.go +++ b/refactor/adapters/http/httpRecipient.go @@ -15,6 +15,11 @@ type HttpRecipient interface { Method() string } +// NewHttpRecipient constructs a new HttpRecipient +func NewHttpRecipient(url string, method string) HttpRecipient { + return httpRecipient{url: url, method: method} +} + // HttpRecipient materializes recipients manipulated by the http adapter type httpRecipient struct { url string From d84813618a3d3e1dd5e12706646a9474ed71ad29 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:28:38 +0100 Subject: [PATCH 0660/2266] [refactor] Re-implement and fix pubsub parse() method --- refactor/adapters/http/handlers/pubsub.go | 69 ++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go index c32f887ab..0a1cd8eac 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/refactor/adapters/http/handlers/pubsub.go @@ -4,10 +4,18 @@ package handlers import ( + "encoding/hex" + "encoding/json" + "io" "net/http" + "regexp" + "strings" + . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" ) // Pubsub defines an handler to handle application | devEUI registration on a component. @@ -31,5 +39,64 @@ func PubSub(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) { // parse extracts params from the request and fails if the request is invalid. func parse(req *http.Request) (core.Registration, error) { - return nil, nil + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") + } + + // Check the query parameter + reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{16})$") // 8-bytes, hex-encoded -> 16 chars + query := reg.FindStringSubmatch(req.RequestURI) + if len(query) < 2 { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect end-device address format") + } + devEUI, err := hex.DecodeString(query[1]) + if err != nil { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, err) + } + + // Check configuration in body + body := make([]byte, req.ContentLength) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, err) + } + params := &struct { + AppEUI string `json:"app_eui"` + Url string `json:"app_url"` + NwkSKey string `json:"nwks_key"` + }{} + if err := json.Unmarshal(body[:n], params); err != nil { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, err) + } + + // Verify each request parameter + nwkSKey, err := hex.DecodeString(params.NwkSKey) + if err != nil || len(nwkSKey) != 16 { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect network session key") + } + + appEUI, err := hex.DecodeString(params.AppEUI) + if err != nil || len(appEUI) != 8 { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect application eui") + } + + params.Url = strings.Trim(params.Url, " ") + if len(params.Url) <= 0 { + return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect application url") + } + + // Create actual registration + registration := pubSubRegistration{ + recipient: NewHttpRecipient(params.Url, "PUT"), + appEUI: lorawan.EUI64{}, + devEUI: lorawan.EUI64{}, + nwkSKey: lorawan.AES128Key{}, + } + + copy(registration.appEUI[:], appEUI[:]) + copy(registration.nwkSKey[:], nwkSKey[:]) + copy(registration.devEUI[:], devEUI[:]) + + return registration, nil } From 5cd42f3fbd3babae5224a1b563ca54a0a3d9091a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:41:17 +0100 Subject: [PATCH 0661/2266] [refactor] Rewrite type http.Handler as an interface which also supply the Url --- refactor/adapters/http/http.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index 551da21d5..b0a42e9b0 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -30,7 +30,10 @@ type Adapter struct { } // Handler defines endpoint-specific handler. -type Handler func(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) +type Handler interface { + Url() string + Handle(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) +} // Message sent through the response channel of a pktReq or regReq type MsgRes struct { @@ -221,10 +224,11 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) } // Bind registers a handler to a specific endpoint -func (a *Adapter) Bind(url string, handle Handler) { - a.ctx.WithField("url", url).Info("Register new endpoint") - a.serveMux.HandleFunc(url, func(w http.ResponseWriter, req *http.Request) { - handle(w, a.registrations, req) +func (a *Adapter) Bind(h Handler) { + a.ctx.WithField("url", h.Url()).Info("Register new endpoint") + a.serveMux.HandleFunc(h.Url(), func(w http.ResponseWriter, req *http.Request) { + a.ctx.WithField("url", h.Url()).Debug("Handle new request") + h.Handle(w, a.registrations, req) }) } From c4375d2f5db8fda0033e7fe3ac0ae214dc7822a7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:41:32 +0100 Subject: [PATCH 0662/2266] [refactor] Implement the Handle() method for pubsub --- refactor/adapters/http/handlers/pubsub.go | 41 +++++++++++++++++++++-- refactor/adapters/http/utils.go | 12 +++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 refactor/adapters/http/utils.go diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go index 0a1cd8eac..56ae096fc 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/refactor/adapters/http/handlers/pubsub.go @@ -33,12 +33,49 @@ import ( // It succeeds with an http 2xx if the request is valid (the response status is under the // ackNacker responsibility. // It can possibly fails with another status depending of the AckNacker response. -func PubSub(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) { +// +// The PubSub handler generates registration where: +// - AppEUI is available +// - DevEUI is available +// - NwkSKey is available +// - Recipient can be interpreted as an HttpRecipient (Url + Method) +type PubSub struct{} + +// Url implements the http.Handler interface +func (p PubSub) Url() string { + return "/end-devices/" +} + +// Handle implements the http.Handler interface +func (p PubSub) Handle(w http.ResponseWriter, chreg chan<- RegReq, req *http.Request) { + // Check the http method + if req.Method != "PUT" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [PUT] to register a device")) + return + } + // Parse body and query params + registration, err := p.parse(req) + if err != nil { + BadRequest(w, err.Error()) + return + } + + // Send the registration and wait for ack / nack + chresp := make(chan MsgRes) + chreg <- RegReq{Registration: registration, Chresp: chresp} + r, ok := <-chresp + if !ok { + BadRequest(w, "Core server not responding") + return + } + w.WriteHeader(r.StatusCode) + w.Write(r.Content) } // parse extracts params from the request and fails if the request is invalid. -func parse(req *http.Request) (core.Registration, error) { +func (p PubSub) parse(req *http.Request) (core.Registration, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") diff --git a/refactor/adapters/http/utils.go b/refactor/adapters/http/utils.go new file mode 100644 index 000000000..7d2ae2045 --- /dev/null +++ b/refactor/adapters/http/utils.go @@ -0,0 +1,12 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import "net/http" + +// BadRequest logs the given failure and sends an appropriate response to the client +func BadRequest(w http.ResponseWriter, msg string) { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(msg)) +} From c50265f0538295d7d935d3058dac52090b8a6abc Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 11:52:48 +0100 Subject: [PATCH 0663/2266] [refactor] Merge testPacket file into http_test and fix some oversight in http Send() --- refactor/adapters/http/http.go | 6 +-- refactor/adapters/http/http_test.go | 25 ++++++++- refactor/adapters/http/testPacket_test.go | 63 ----------------------- 3 files changed, 27 insertions(+), 67 deletions(-) delete mode 100644 refactor/adapters/http/testPacket_test.go diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index b0a42e9b0..cfb51fc2f 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -92,7 +92,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } } if devEUI == nil { - a.ctx.WithError(err).Warn("Unable to retrieve devEUI") + a.ctx.Warn("Unable to retrieve devEUI") ctx = a.ctx } ctx.Debug("Sending Packet") @@ -135,7 +135,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err buf.Write(data) resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Url()), "application/octet-stream", buf) if err != nil { - cherr <- err + cherr <- errors.New(ErrFailedOperation, err) return } defer func() { @@ -152,7 +152,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err ctx.Debug("Recipient registered for packet") data, err := ioutil.ReadAll(resp.Body) if err != nil && err != io.EOF { - cherr <- err + cherr <- errors.New(ErrFailedOperation, err) return } chresp <- data diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go index 2094a3e8a..92083a791 100644 --- a/refactor/adapters/http/http_test.go +++ b/refactor/adapters/http/http_test.go @@ -28,6 +28,30 @@ type testRegistration struct { DevEUI lorawan.EUI64 } +type testPacket struct { + devEUI lorawan.EUI64 + payload string +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p testPacket) MarshalBinary() ([]byte, error) { + if p.payload == "" { + return nil, errors.New(ErrInvalidStructure, "Fake error") + } + + return []byte(p.payload), nil +} + +// DevEUI implements the core.Addressable interface +func (p testPacket) DevEUI() (lorawan.EUI64, error) { + return p.devEUI, nil +} + +// String implements the core.Packet interface +func (p testPacket) String() string { + return p.payload +} + func TestSend(t *testing.T) { recipients := []testRecipient{ testRecipient{ @@ -182,7 +206,6 @@ func getRegistrations(adapter *Adapter, want []testRegistration) []core.Registra } // Build utilities - func genMockServer(recipient core.Recipient) chan string { chresp := make(chan string) serveMux := http.NewServeMux() diff --git a/refactor/adapters/http/testPacket_test.go b/refactor/adapters/http/testPacket_test.go deleted file mode 100644 index 93993ece7..000000000 --- a/refactor/adapters/http/testPacket_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "encoding" - - . "github.com/TheThingsNetwork/ttn/core/errors" - core "github.com/TheThingsNetwork/ttn/refactor" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// type testPacket materializes packets manipulated by the broker and corresponding adapter handlers -type testPacket struct { - devEUI lorawan.EUI64 - payload string -} - -// FCnt implements the core.Packet interface -func (p testPacket) FCnt() (uint32, error) { - return 0, nil -} - -// Payload implements the core.Packet interface -func (p testPacket) Payload() encoding.BinaryMarshaler { - return nil -} - -// Metadata implements the core.Packet interface -func (p testPacket) Metadata() []core.Metadata { - return nil -} - -// AppEUI implements the core.Packet interface -func (p testPacket) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, nil -} - -// DevEUI implements the core.Packet interface -func (p testPacket) DevEUI() (lorawan.EUI64, error) { - return p.devEUI, nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p testPacket) MarshalBinary() ([]byte, error) { - if p.payload == "" { - return nil, errors.New(ErrInvalidStructure, "Fake error") - } - - return []byte(p.payload), nil -} - -// MarshalBinary implements the encoding.BinaryUnMarshaler interface -func (p *testPacket) UnmarshalBinary(data []byte) error { - return nil -} - -// String implements the core.Packet interface -func (p testPacket) String() string { - return p.payload -} From 7879a4b4b5daf2fc25b4fb8561121deb2ab8f795 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 13:08:11 +0100 Subject: [PATCH 0664/2266] Use new Azure storage container --- .drone.sec | 2 +- .drone.yml | 2 +- Dockerfile | 2 +- build_binaries.sh | 2 -- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.drone.sec b/.drone.sec index 841c9cc70..c3d2e86a7 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.AcG0ZL_fM56k9Vd2wBH9lJrCjFMuW8fs_XJF_9YlU5S5PEWdotmfLAZeilOF99nw3RFra24sKo_sfsX4WGJPzutCOL8rAAY4QJCaVLjOJatO8m5LbLryEUAfFrbipI9ZWemThC6YHpdFNmLNS1QmRKMbwbRuXlh1WUVEtk2mP42COJMcjuAVChhg_qrfinxIaZLRmGRmJP2M_rjzK5v26CFcbXS8pRxR-tW5node7r_V9sj_J6Je6Qeok-yzSSCyXfAI6rR3T-_LIAjPUCJgpAEPUWrh6YdcpJx85bFsWn2hoghTy7wJydYjGVdoGi4WmzY6k7szockNVU8vosfp9g.WTTxM0AtX8cltzmw.qBrQoryiemZrWSERNjych0nWR8Mhc7oaIB2umU7h5niJGbrwTMkdBTsBc1NEca5zA4DWEB7rDZhHtvW2GsuaYn2vw3taQv7AtWBx5qFHoTiX1JKipOyRp7BELcJLH3boz9Phl4JI21Y_MGZ3FyhjEJBV5unxcj2gn4f8Czr4huoah3feWAISlxbAdDfcvIkVDf14Pp6z7DZ19fG3qob6L8xtG1N1XyEt3DsdJ5T2wg1rSxwE7dRI7BUKPJzTMn9Q5LO3dEY5.ERl5tu03lw6jsf5slM8NOA \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.YxeKX7Hg1A0oLKO-iu495m2xvpxlGC-H7G6n6AFGdWGi7auIVik9UOV0Odmp-Ntco6uYkpFaDg9xuLNpyy4BaVj3RdwiDHEW1DsA2CzE6O94sH5viVB_eyFgRBblDV1QqznCCR_HMQKT2yjlgC2Ml33BzfoT77Ybh6tfF5QytbFgjWTsv_UqEIfTR-4gm7PCTVcJvZOW0CfclGHlw-3oHGfenOdjTDZgRXtLJCbzo1VreeVCnORMYs3J2e74-MpVkDrF_7EzXjaWkyqVTbMpODTn7MC4Rzq5Fq8VMWWzCLYIsYADnTMvkHp4Qhd8H4ZhUkkM1oJEyQGyzSgHZneoJQ.iJED9qzoTatbkyHG.V-4SyWRwdeFKXGNnWe8s_bCw7_qDUi1LkA71kUOqgPdzsv7I_OsyL9Dcz9VCf_zve_ZMWkNKOczyHRLSCSugjoSuZftsEIrnqaU4DL_L2XNGsYneWxwU3JCDCTZch7tow8H2oYPxm0hIrDAbcKdUxsFIIrVs5PNlg8vMqgOFM0Y8aXGTqDJIEbBxqADQFnmMsQtRY0Leg-WEzEpA6F6_Evm0R5FJc3b9LeWrD8s9ytvd4A5j-UODEOvCNXq0jWnWl4C_Gm4e.s1OgDCnDeTZBilECyze_Iw \ No newline at end of file diff --git a/.drone.yml b/.drone.yml index 2c1b58771..a8428d8ae 100644 --- a/.drone.yml +++ b/.drone.yml @@ -18,7 +18,7 @@ build: publish: azure_storage: account_key: $$AZURE_STORAGE_KEY - storage_account: thethingsnetwork + storage_account: ttnreleases container: release source: release/ when: diff --git a/Dockerfile b/Dockerfile index 193c7b2c8..691551dde 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* RUN apk --update add curl tar \ && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://thethingsnetwork.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ + && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://ttnreleases.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ && tar -xf /ttnsrc/ttn-linux-amd64.tar.gz -C /ttnsrc \ && mv /ttnsrc/ttn-linux-amd64 /usr/local/bin/ttn \ && chmod 755 /usr/local/bin/ttn \ diff --git a/build_binaries.sh b/build_binaries.sh index 815435ba3..330710fe7 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -65,8 +65,6 @@ else build_release darwin amd64 build_release linux 386 build_release linux amd64 - build_release windows 386 - build_release windows amd64 fi # Prepare Releases in CI build From 9663216b3522499570458e3d3c1ad650fdefdb83 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 13:09:24 +0100 Subject: [PATCH 0665/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index c3d2e86a7..dc223baa2 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.YxeKX7Hg1A0oLKO-iu495m2xvpxlGC-H7G6n6AFGdWGi7auIVik9UOV0Odmp-Ntco6uYkpFaDg9xuLNpyy4BaVj3RdwiDHEW1DsA2CzE6O94sH5viVB_eyFgRBblDV1QqznCCR_HMQKT2yjlgC2Ml33BzfoT77Ybh6tfF5QytbFgjWTsv_UqEIfTR-4gm7PCTVcJvZOW0CfclGHlw-3oHGfenOdjTDZgRXtLJCbzo1VreeVCnORMYs3J2e74-MpVkDrF_7EzXjaWkyqVTbMpODTn7MC4Rzq5Fq8VMWWzCLYIsYADnTMvkHp4Qhd8H4ZhUkkM1oJEyQGyzSgHZneoJQ.iJED9qzoTatbkyHG.V-4SyWRwdeFKXGNnWe8s_bCw7_qDUi1LkA71kUOqgPdzsv7I_OsyL9Dcz9VCf_zve_ZMWkNKOczyHRLSCSugjoSuZftsEIrnqaU4DL_L2XNGsYneWxwU3JCDCTZch7tow8H2oYPxm0hIrDAbcKdUxsFIIrVs5PNlg8vMqgOFM0Y8aXGTqDJIEbBxqADQFnmMsQtRY0Leg-WEzEpA6F6_Evm0R5FJc3b9LeWrD8s9ytvd4A5j-UODEOvCNXq0jWnWl4C_Gm4e.s1OgDCnDeTZBilECyze_Iw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.bCBA4ySZzb5nPaUgEQUBumqcTn2DqZbqlsXXkQXF0-bkYAm64h0uxjTTv5o-Wr-ESVAQumkDCFI8B1l5o5MIHkT7ErFoiN2K-UA2RnTRlwRg7cNwnWM9WbxTS2jUqW5VRY_eQy15f9lg2FZWzDUETwqBGc3LnOKDFAXCgQErwlMPIIzZ2GqwfZezx62N7UTAzQIU81NmEueA4O8ZX_IQ8TA5DixmTi922SamTX9lC51YjJtGmcV3ZgcE9iyQfXnjYvRWF9irNerYvVhCJDheK40MXgVhTCy9JSgQkVSI2mCUUVlV_sYUBOF4-s1DK06j6iuVXW7g8xmmzI6pGB9tsA.jpIxTYwazSp8gV7d.QBqpIbxYo4H7iNCsFvZHfZCjsExsLn-TZTTvyh3naGS8CqzeNelpCQaaiS71n2UE6j6goF3OQLbWcQgNczUbHt7D4ctPiTz8LziPflERNqN04s0RX2Zp234B8gosslHhOIRu9bVeWZOXLzzjp4jt5QUytSeTwgJZffwgUaXPXP0vY7E8CI4uDht7bzmxR4kf0g67vkkV174dlwqEccfX1vpC_-VpJ3DI7l1io8jQGrgzO2PRjCg2xD70nLvBKsdEw6v3V_bT.kxPSWRPvw38ojb1HZLqoyQ \ No newline at end of file From 0efc26e9ed186b8708d4c58e194f98b8e653d8e1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 13:12:05 +0100 Subject: [PATCH 0666/2266] [refactor] Add precisions to pubsub documentation --- refactor/adapters/http/handlers/pubsub.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go index 56ae096fc..c918cf9b0 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/refactor/adapters/http/handlers/pubsub.go @@ -18,11 +18,13 @@ import ( "github.com/brocaar/lorawan" ) -// Pubsub defines an handler to handle application | devEUI registration on a component. +// Pubsub defines a handler to handle application | devEUI registration on a component. // // It listens to request of the form: [PUT] /end-devices/:devEUI // where devEUI is a 8 bytes hex-encoded address. // +// It expects a Content-Type = application/json +// // It also looks for params: // // - app_eui (8 bytes hex-encoded string) From 68511356b51ea752c06d3d1814dee660c7698dc2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 13:20:07 +0100 Subject: [PATCH 0667/2266] [refactor] Write backbone of collect handler. Add the packet chan to the Handler.Handle method() --- refactor/adapters/http/handlers/collect.go | 44 ++++++++++++++++++++++ refactor/adapters/http/handlers/pubsub.go | 2 +- refactor/adapters/http/http.go | 4 +- 3 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 refactor/adapters/http/handlers/collect.go diff --git a/refactor/adapters/http/handlers/collect.go b/refactor/adapters/http/handlers/collect.go new file mode 100644 index 000000000..90e89114b --- /dev/null +++ b/refactor/adapters/http/handlers/collect.go @@ -0,0 +1,44 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + //"encoding/hex" + //"encoding/json" + //"io" + "net/http" + //"regexp" + //"strings" + + //. "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + //"github.com/TheThingsNetwork/ttn/utils/errors" + //"github.com/brocaar/lorawan" +) + +// Collect defines a handler for retrieving raw packets sent by a POST request. +// +// It listens to requests of the form: [POST] /packets/ +// +// It expects an http header Content-Type = application/octet-stream +// +// The body is expected to a binary marshaling of the given packet +// +// This handler does not generate any registration. +type Collect struct{} + +// Url implements the http.Handler interface +func (p Collect) Url() string { + return "/packets/" +} + +// Handle implements the http.Handler interface +func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { +} + +// parse extracts params from the request and fails if the request is invalid. +func (p Collect) parse(req *http.Request) (core.Registration, error) { + return nil, nil +} diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go index c918cf9b0..5af652a3b 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/refactor/adapters/http/handlers/pubsub.go @@ -49,7 +49,7 @@ func (p PubSub) Url() string { } // Handle implements the http.Handler interface -func (p PubSub) Handle(w http.ResponseWriter, chreg chan<- RegReq, req *http.Request) { +func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { // Check the http method if req.Method != "PUT" { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index cfb51fc2f..6d4cb0bdd 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -32,7 +32,7 @@ type Adapter struct { // Handler defines endpoint-specific handler. type Handler interface { Url() string - Handle(w http.ResponseWriter, reg chan<- RegReq, req *http.Request) + Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) } // Message sent through the response channel of a pktReq or regReq @@ -228,7 +228,7 @@ func (a *Adapter) Bind(h Handler) { a.ctx.WithField("url", h.Url()).Info("Register new endpoint") a.serveMux.HandleFunc(h.Url(), func(w http.ResponseWriter, req *http.Request) { a.ctx.WithField("url", h.Url()).Debug("Handle new request") - h.Handle(w, a.registrations, req) + h.Handle(w, a.packets, a.registrations, req) }) } From a472953e87cb0b57f70bcbfef899e3b4e0d4e7eb Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 13:22:18 +0100 Subject: [PATCH 0668/2266] [refactor] Re-define parse method for collect adapter --- refactor/adapters/http/handlers/collect.go | 28 ++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/http/handlers/collect.go b/refactor/adapters/http/handlers/collect.go index 90e89114b..f9ec2018d 100644 --- a/refactor/adapters/http/handlers/collect.go +++ b/refactor/adapters/http/handlers/collect.go @@ -12,7 +12,7 @@ import ( //"strings" //. "github.com/TheThingsNetwork/ttn/core/errors" - core "github.com/TheThingsNetwork/ttn/refactor" + // core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" //"github.com/TheThingsNetwork/ttn/utils/errors" //"github.com/brocaar/lorawan" @@ -36,9 +36,33 @@ func (p Collect) Url() string { // Handle implements the http.Handler interface func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { + // Check the http method + if req.Method != "POST" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [POST] to transfer a packet")) + return + } + + // Parse body and query params + data, err := p.parse(req) + if err != nil { + BadRequest(w, err.Error()) + return + } + + // Send the packet and wait for ack / nack + chresp := make(chan MsgRes) + chpkt <- PktReq{Packet: data, Chresp: chresp} + r, ok := <-chresp + if !ok { + BadRequest(w, "Core server not responding") + return + } + w.WriteHeader(r.StatusCode) + w.Write(r.Content) } // parse extracts params from the request and fails if the request is invalid. -func (p Collect) parse(req *http.Request) (core.Registration, error) { +func (p Collect) parse(req *http.Request) ([]byte, error) { return nil, nil } From e07cf109da0022ddf37e8a8f7a0842a67584628c Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 13:27:56 +0100 Subject: [PATCH 0669/2266] [refactor] Implement collect parse method and finalize collect Handler --- refactor/adapters/http/handlers/collect.go | 25 +++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/refactor/adapters/http/handlers/collect.go b/refactor/adapters/http/handlers/collect.go index f9ec2018d..22cdf7b99 100644 --- a/refactor/adapters/http/handlers/collect.go +++ b/refactor/adapters/http/handlers/collect.go @@ -4,18 +4,12 @@ package handlers import ( - //"encoding/hex" - //"encoding/json" - //"io" + "io" "net/http" - //"regexp" - //"strings" - //. "github.com/TheThingsNetwork/ttn/core/errors" - // core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core/errors" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" - //"github.com/TheThingsNetwork/ttn/utils/errors" - //"github.com/brocaar/lorawan" + "github.com/TheThingsNetwork/ttn/utils/errors" ) // Collect defines a handler for retrieving raw packets sent by a POST request. @@ -64,5 +58,16 @@ func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- // parse extracts params from the request and fails if the request is invalid. func (p Collect) parse(req *http.Request) ([]byte, error) { - return nil, nil + // Check Content-type + if req.Header.Get("Content-Type") != "application/octet-stream" { + return nil, errors.New(ErrInvalidStructure, "Received invalid content-type in request") + } + + // Check configuration in body + body := make([]byte, req.ContentLength) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + return nil, errors.New(ErrInvalidStructure, err) + } + return body[:n], nil } From 5fdfce68a98ab009730b4feb81a581e2877a5583 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 13:34:33 +0100 Subject: [PATCH 0670/2266] [refactor] Re-implement the statuspage handler --- refactor/adapters/http/handlers/statuspage.go | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 refactor/adapters/http/handlers/statuspage.go diff --git a/refactor/adapters/http/handlers/statuspage.go b/refactor/adapters/http/handlers/statuspage.go new file mode 100644 index 000000000..307aad9e1 --- /dev/null +++ b/refactor/adapters/http/handlers/statuspage.go @@ -0,0 +1,112 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "encoding/json" + "fmt" + "net" + "net/http" + + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/rcrowley/go-metrics" +) + +// StatusPage shows statistic on GEt request +// +// It listens to requests of the form: [GET] /status/ +// +// No body or query param are expected +// +// This handler does not generate any registration. +type StatusPage struct{} + +// Url implements the http.Handler interface +func (p StatusPage) Url() string { + return "/status/" +} + +// Handle implements the http.Handler interface +func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { + // Check the http method + if req.Method != "GET" { + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte("Unreckognized HTTP method. Please use [GET] to transfer a packet")) + return + } + + remoteHost, _, err := net.SplitHostPort(req.RemoteAddr) + if err != nil { + //The HTTP server did not set RemoteAddr to IP:port, which would be very very strange. + w.WriteHeader(http.StatusInternalServerError) + return + } + + remoteIP := net.ParseIP(remoteHost) + if remoteIP == nil || !remoteIP.IsLoopback() { + w.WriteHeader(http.StatusForbidden) + w.Write([]byte(fmt.Sprintf("Status is only available from the local host, not from %s", remoteIP))) + return + } + + allStats := statData{} + + metrics.Each(func(name string, i interface{}) { + switch metric := i.(type) { + + case metrics.Counter: + m := metric.Snapshot() + allStats[name] = m.Count() + + case metrics.Histogram: + h := metric.Snapshot() + ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) + + allStats[name] = histData{ + Avg: h.Mean(), + Min: h.Min(), + Max: h.Max(), + P25: ps[0], + P50: ps[1], + P75: ps[2], + } + + case metrics.Meter: + m := metric.Snapshot() + allStats[name] = meterData{ + Rate1: m.Rate1(), + Rate5: m.Rate5(), + Rate15: m.Rate15(), + Count: m.Count(), + } + } + }) + + response, err := json.Marshal(allStats) + if err != nil { + panic(err) + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(response) +} + +type statData map[string]interface{} + +type meterData struct { + Rate1 float64 `json:"rate_1"` + Rate5 float64 `json:"rate_5"` + Rate15 float64 `json:"rate_15"` + Count int64 `json:"count"` +} + +type histData struct { + Avg float64 `json:"avg"` + Min int64 `json:"min"` + Max int64 `json:"max"` + P25 float64 `json:"p_25"` + P50 float64 `json:"p_50"` + P75 float64 `json:"p_75"` +} From b886c5bcd1cc637f6249ec8fbcf34353cc4a565d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 13:54:20 +0100 Subject: [PATCH 0671/2266] Add /healthz endpoint in HTTP adapter Resolves #55 --- core/adapters/http/adapter.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index a07802d6b..f69c24b95 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -52,6 +52,11 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap Client: http.Client{}, } + a.RegisterEndpoint("/healthz", func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusOK) + w.Write([]byte("ok")) + }) + a.RegisterEndpoint("/packets", a.handlePostPacket) go a.listenRequests(port) From eae3bc9174aa923eb916c85dc0d1126116ce49c7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 14:09:20 +0100 Subject: [PATCH 0672/2266] [refactor] Fix wrong use of json marshaller in httpAckNacker --- refactor/adapters/http/httpAckNacker.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/refactor/adapters/http/httpAckNacker.go b/refactor/adapters/http/httpAckNacker.go index 159b56fbd..5a5abaffd 100644 --- a/refactor/adapters/http/httpAckNacker.go +++ b/refactor/adapters/http/httpAckNacker.go @@ -4,7 +4,6 @@ package http import ( - "encoding/json" "net/http" "time" @@ -30,7 +29,7 @@ func (an httpAckNacker) Ack(p *core.Packet) error { return nil } - raw, err := json.Marshal(*p) + data, err := (*p).MarshalBinary() if err != nil { return errors.New(ErrInvalidStructure, err) } @@ -38,7 +37,7 @@ func (an httpAckNacker) Ack(p *core.Packet) error { select { case an.Chresp <- MsgRes{ StatusCode: http.StatusOK, - Content: raw, + Content: data, }: return nil case <-time.After(time.Millisecond * 50): From d9c5eefae07bf0e34d34c4c493cd72fda89edd74 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 14:22:29 +0100 Subject: [PATCH 0673/2266] [refactor] Rewrite collect tests backbone --- .../adapters/http/handlers/collect_test.go | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 refactor/adapters/http/handlers/collect_test.go diff --git a/refactor/adapters/http/handlers/collect_test.go b/refactor/adapters/http/handlers/collect_test.go new file mode 100644 index 000000000..a48e7a792 --- /dev/null +++ b/refactor/adapters/http/handlers/collect_test.go @@ -0,0 +1,201 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "bytes" + // "fmt" + "io" + "net/http" + "reflect" + "testing" + "time" + + core "github.com/TheThingsNetwork/ttn/refactor" + //. "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + // "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestCollect(t *testing.T) { + tests := []struct { + Desc string + Payload struct { + ContentType string + Method string + Content string + } + ShouldAck bool + AckPacket *core.Packet + + WantResponse []byte + WantPacket []byte + WantError *string + }{} + + var port uint = 3000 + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + + // CreateAdapter + // Bind Collect Handler + // Create Http Client + + // Operate + // Send Payload + Content-type + Method + // Try Next() + // If Next Succeed -> Send given response + + // Check + // Get server response from client + // Get server statusCode from client + // Check Errors + // Check StatusCode + // Check Content + // Check Next packet[] + port += 1 + } + +} + +// ----- BUILD utilities +func createAdapter(t *testing.T, port uint) *Adapter { + adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + if err != nil { + panic(err) + } + adapter.Bind(Collect{}) + return adapter +} + +type testClient struct { + http.Client +} + +func (c testClient) Send(payload string, url string, method string, contentType string) (int, []byte) { + buf := new(bytes.Buffer) + if _, err := buf.Write([]byte(payload)); err != nil { + panic(err) + } + + request, err := http.NewRequest(method, url, buf) + if err != nil { + panic(err) + } + request.Header.Set("Content-Type", contentType) + + chresp := make(chan struct { + StatusCode int + Content []byte + }) + go func() { + resp, err := c.Do(request) + if err != nil { + panic(err) + } + + data := make([]byte, 2048) + n, err := resp.Body.Read(data) + if err != nil && err != io.EOF { + panic(err) + } + + chresp <- struct { + StatusCode int + Content []byte + }{resp.StatusCode, data[:n]} + }() + + select { + case resp := <-chresp: + return resp.StatusCode, resp.Content + case <-time.After(time.Millisecond * 100): + return 0, nil + } +} + +// ----- OPERATE utilities +func tryNext(adapter core.Adapter, shouldAck bool, packet *core.Packet) ([]byte, error) { + chresp := make(chan struct { + Packet []byte + Error error + }) + go func() { + pkt, an, err := adapter.Next() + defer func() { + chresp <- struct { + Packet []byte + Error error + }{pkt, err} + }() + if err != nil { + return + } + + if shouldAck { + an.Ack(packet) + } else { + an.Nack() + } + }() + + select { + case resp := <-chresp: + return resp.Packet, resp.Error + case <-time.After(time.Millisecond * 100): + return nil, nil + } +} + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkStatusCode(t *testing.T, want int, got int) { + if want == got { + Ok(t, "Check status code") + return + } + Ko(t, "Expected status code to be %d but got %d", want, got) +} + +func checkContent(t *testing.T, want string, got []byte) { + if reflect.DeepEqual([]byte(want), got) { + Ok(t, "Check content") + return + } + Ko(t, "Received content does not match expectations.\nWant: %v\nGot: %v", want, got) +} + +func checkPacket(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packet") + return + } + Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", want, got) +} From 2652ccaa27d39ffa41b3f5e70e9aab071afe0583 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 14:46:31 +0100 Subject: [PATCH 0674/2266] [refactor] Finalize test procedure for collect --- .../adapters/http/handlers/collect_test.go | 104 ++++++++++-------- 1 file changed, 57 insertions(+), 47 deletions(-) diff --git a/refactor/adapters/http/handlers/collect_test.go b/refactor/adapters/http/handlers/collect_test.go index a48e7a792..97abb7cf6 100644 --- a/refactor/adapters/http/handlers/collect_test.go +++ b/refactor/adapters/http/handlers/collect_test.go @@ -12,28 +12,27 @@ import ( "testing" "time" + . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" - //. "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - // "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" + //"github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) func TestCollect(t *testing.T) { tests := []struct { - Desc string - Payload struct { - ContentType string - Method string - Content string - } - ShouldAck bool - AckPacket *core.Packet - - WantResponse []byte - WantPacket []byte - WantError *string + Desc string + Payload string + ContentType string + Method string + ShouldAck bool + AckPacket *core.Packet + + WantContent []byte + WantStatusCode int + WantPacket []byte + WantError *string }{} var port uint = 3000 @@ -42,35 +41,58 @@ func TestCollect(t *testing.T) { Desc(t, test.Desc) // Build - - // CreateAdapter - // Bind Collect Handler - // Create Http Client + handler := Collect{} + adapter := createAdapter(t, port, handler) + client := testClient{} // Operate - // Send Payload + Content-type + Method - // Try Next() - // If Next Succeed -> Send given response + chresp := client.Send(test.Payload, handler.Url(), test.Method, test.ContentType) + packet, err := tryNext(adapter, test.ShouldAck, test.AckPacket) + var statusCode int + var content []byte + select { + case resp := <-chresp: + statusCode = resp.StatusCode + content = resp.Content + case <-time.After(time.Millisecond * 100): + } // Check - // Get server response from client - // Get server statusCode from client - // Check Errors - // Check StatusCode - // Check Content - // Check Next packet[] + checkErrors(t, test.WantError, err) + checkStatusCode(t, test.WantStatusCode, statusCode) + checkContent(t, test.WantContent, content) + checkPacket(t, test.WantPacket, packet) port += 1 } } +// ----- TYPES utilities +type testPacket struct { + payload string +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p testPacket) MarshalBinary() ([]byte, error) { + if p.payload == "" { + return nil, errors.New(ErrInvalidStructure, "Fake error") + } + + return []byte(p.payload), nil +} + +// String implements the core.Packet interface +func (p testPacket) String() string { + return p.payload +} + // ----- BUILD utilities -func createAdapter(t *testing.T, port uint) *Adapter { +func createAdapter(t *testing.T, port uint, handler Handler) *Adapter { adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) if err != nil { panic(err) } - adapter.Bind(Collect{}) + adapter.Bind(handler) return adapter } @@ -78,7 +100,7 @@ type testClient struct { http.Client } -func (c testClient) Send(payload string, url string, method string, contentType string) (int, []byte) { +func (c testClient) Send(payload string, url string, method string, contentType string) chan MsgRes { buf := new(bytes.Buffer) if _, err := buf.Write([]byte(payload)); err != nil { panic(err) @@ -90,10 +112,7 @@ func (c testClient) Send(payload string, url string, method string, contentType } request.Header.Set("Content-Type", contentType) - chresp := make(chan struct { - StatusCode int - Content []byte - }) + chresp := make(chan MsgRes) go func() { resp, err := c.Do(request) if err != nil { @@ -106,18 +125,9 @@ func (c testClient) Send(payload string, url string, method string, contentType panic(err) } - chresp <- struct { - StatusCode int - Content []byte - }{resp.StatusCode, data[:n]} + chresp <- MsgRes{resp.StatusCode, data[:n]} }() - - select { - case resp := <-chresp: - return resp.StatusCode, resp.Content - case <-time.After(time.Millisecond * 100): - return 0, nil - } + return chresp } // ----- OPERATE utilities @@ -184,7 +194,7 @@ func checkStatusCode(t *testing.T, want int, got int) { Ko(t, "Expected status code to be %d but got %d", want, got) } -func checkContent(t *testing.T, want string, got []byte) { +func checkContent(t *testing.T, want []byte, got []byte) { if reflect.DeepEqual([]byte(want), got) { Ok(t, "Check content") return From 61420450c7d3c19b5a995515566500c2eda37101 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 14:52:12 +0100 Subject: [PATCH 0675/2266] Set timeout of HTTP adapter --- core/adapters/http/adapter.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go index f69c24b95..f69c7204a 100644 --- a/core/adapters/http/adapter.go +++ b/core/adapters/http/adapter.go @@ -12,6 +12,7 @@ import ( "io/ioutil" "net/http" "sync" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" @@ -49,7 +50,9 @@ func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adap serveMux: http.NewServeMux(), packets: make(chan pktReq), ctx: ctx, - Client: http.Client{}, + Client: http.Client{ + Timeout: 6 * time.Second, + }, } a.RegisterEndpoint("/healthz", func(w http.ResponseWriter, req *http.Request) { From 8465a06db2d8ad3d8b6f2b08b98a0fda04a611c8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 23 Feb 2016 15:18:35 +0100 Subject: [PATCH 0676/2266] Remove unnecessary log metadata Closes #57 --- cmd/broker.go | 11 +++++------ cmd/handler.go | 1 - cmd/router.go | 11 +++++------ 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 15dfe1c5e..f21c0b6b7 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -25,7 +25,6 @@ receives from routers. This means that handlers have to register applications and personalized devices (with their network session keys) with the router. `, PreRun: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("cmd", "broker") ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), "routers-port": viper.GetInt("broker.routers-port"), @@ -36,22 +35,22 @@ and personalized devices (with their network session keys) with the router. ctx.Info("Starting") // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("tag", "Routers Adapter")) + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } - hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("tag", "Handlers Adapter")) + hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } - _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("tag", "Broker Adapter")) + _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("adapter", "statuspage-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("tag", "Handlers Adapter")) + hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("adapter", "handler-pubsub")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } @@ -61,7 +60,7 @@ and personalized devices (with their network session keys) with the router. ctx.WithError(err).Fatal("Could not create a local storage") } - broker := components.NewBroker(db, ctx.WithField("tag", "Broker")) + broker := components.NewBroker(db, ctx) // Bring the service to life diff --git a/cmd/handler.go b/cmd/handler.go index 7d292b967..d2874df7c 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -17,7 +17,6 @@ var handlerCmd = &cobra.Command{ The default handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("cmd", "handler") ctx.WithFields(log.Fields{ "database": viper.GetString("handler.database"), "brokers-port": viper.GetInt("handler.brokers-port"), diff --git a/cmd/router.go b/cmd/router.go index 214013ad2..a938b0cee 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -28,7 +28,6 @@ or more brokers. The router is also responsible for monitoring gateways, collecting statistics from gateways and for enforcing TTN's fair use policy when the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("cmd", "router") ctx.WithFields(log.Fields{ "database": viper.GetString("router.database"), "gateways-port": viper.GetInt("router.gateways-port"), @@ -39,17 +38,17 @@ the gateway's duty cycle is (almost) full.`, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("tag", "Gateway Adapter")) + gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } - pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("tag", "Broker Adapter")) + pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("tag", "Broker Adapter")) + _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("adapter", "statuspage-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } @@ -63,7 +62,7 @@ the gateway's duty cycle is (almost) full.`, }) } - brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("tag", "Broker Adapter")) + brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("adapter", "broker-broadcast")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } @@ -73,7 +72,7 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(err).Fatal("Could not create a local storage") } - router := components.NewRouter(db, ctx.WithField("tag", "Router")) + router := components.NewRouter(db, ctx) // Bring the service to life From 1a9e7ed7b6d1997634710ca2bdaa490c5fc30877 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 15:23:23 +0100 Subject: [PATCH 0677/2266] [refactor] Review error messages returned by http acknackers --- refactor/adapters/http/httpAckNacker.go | 6 +++--- refactor/adapters/http/regAckNacker.go | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/refactor/adapters/http/httpAckNacker.go b/refactor/adapters/http/httpAckNacker.go index 5a5abaffd..fbf194459 100644 --- a/refactor/adapters/http/httpAckNacker.go +++ b/refactor/adapters/http/httpAckNacker.go @@ -41,7 +41,7 @@ func (an httpAckNacker) Ack(p *core.Packet) error { }: return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrFailedOperation, "No response was given to the acknacker") } } @@ -55,10 +55,10 @@ func (an httpAckNacker) Nack() error { select { case an.Chresp <- MsgRes{ StatusCode: http.StatusNotFound, - Content: []byte(`{"message":"Not in charge of the associated device"}`), + Content: []byte(ErrInvalidStructure), }: case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrFailedOperation, "No response was given to the acknacker") } return nil } diff --git a/refactor/adapters/http/regAckNacker.go b/refactor/adapters/http/regAckNacker.go index f99473a49..5b1e60def 100644 --- a/refactor/adapters/http/regAckNacker.go +++ b/refactor/adapters/http/regAckNacker.go @@ -28,7 +28,7 @@ func (r regAckNacker) Ack(p *core.Packet) error { case r.Chresp <- MsgRes{StatusCode: http.StatusAccepted}: return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrFailedOperation, "No response was given to the acknacker") } } @@ -40,10 +40,10 @@ func (r regAckNacker) Nack() error { select { case r.Chresp <- MsgRes{ StatusCode: http.StatusConflict, - Content: []byte("Unable to register the given device"), + Content: []byte(ErrInvalidStructure), }: return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") + return errors.New(ErrFailedOperation, "No response was given to the acknacker") } } From 45ed48a713a43c40dec2e9e76707ee0951147994 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 16:05:13 +0100 Subject: [PATCH 0678/2266] [refactor] Remove pointer to interface since interface is enough --- refactor/adapters/http/httpAckNacker.go | 4 ++-- refactor/adapters/http/regAckNacker.go | 2 +- refactor/adapters/udp/adapter.go | 4 ++-- refactor/adapters/udp/handlers/semtech.go | 4 ++-- refactor/adapters/udp/udpAckNacker.go | 2 +- refactor/interfaces.go | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/refactor/adapters/http/httpAckNacker.go b/refactor/adapters/http/httpAckNacker.go index fbf194459..95d44a0f2 100644 --- a/refactor/adapters/http/httpAckNacker.go +++ b/refactor/adapters/http/httpAckNacker.go @@ -18,7 +18,7 @@ type httpAckNacker struct { } // Ack implements the core.AckNacker interface -func (an httpAckNacker) Ack(p *core.Packet) error { +func (an httpAckNacker) Ack(p core.Packet) error { if an.Chresp == nil { return nil } @@ -29,7 +29,7 @@ func (an httpAckNacker) Ack(p *core.Packet) error { return nil } - data, err := (*p).MarshalBinary() + data, err := p.MarshalBinary() if err != nil { return errors.New(ErrInvalidStructure, err) } diff --git a/refactor/adapters/http/regAckNacker.go b/refactor/adapters/http/regAckNacker.go index 5b1e60def..9ccb1bf80 100644 --- a/refactor/adapters/http/regAckNacker.go +++ b/refactor/adapters/http/regAckNacker.go @@ -18,7 +18,7 @@ type regAckNacker struct { } // Ack implements the core.Acker interface -func (r regAckNacker) Ack(p *core.Packet) error { +func (r regAckNacker) Ack(p core.Packet) error { if r.Chresp == nil { return nil } diff --git a/refactor/adapters/udp/adapter.go b/refactor/adapters/udp/adapter.go index 5e04049c3..9d9cd8853 100644 --- a/refactor/adapters/udp/adapter.go +++ b/refactor/adapters/udp/adapter.go @@ -25,7 +25,7 @@ type Adapter struct { // Handler represents a datagram and packet handler used by the adapter to process packets type Handler interface { // HandleAck handles a positive response to a transmitter - HandleAck(p *core.Packet, resp chan<- HandlerMsg) + HandleAck(p core.Packet, resp chan<- HandlerMsg) // HandleNack handles a negative response to a transmitter HandleNack(resp chan<- HandlerMsg) @@ -50,7 +50,7 @@ const ( // AckMsg type materializes ack or nack messages flowing into the Ack channel type AckMsg struct { - Packet *core.Packet + Packet core.Packet Type AckMsgType Addr *net.UDPAddr Cherr chan<- error diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index 0d2ccef50..2bf2389ab 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -26,7 +26,7 @@ func (s Semtech) HandleNack(chresp chan<- udp.HandlerMsg) { } // HandleAck implements the udp.Handler interface -func (s Semtech) HandleAck(packet *core.Packet, chresp chan<- udp.HandlerMsg) { +func (s Semtech) HandleAck(packet core.Packet, chresp chan<- udp.HandlerMsg) { defer close(chresp) if packet == nil { @@ -34,7 +34,7 @@ func (s Semtech) HandleAck(packet *core.Packet, chresp chan<- udp.HandlerMsg) { } // For the downlink, we have to send a PULL_RESP packet which hold a TXPK. - txpk, err := packet2txpk(*packet) + txpk, err := packet2txpk(packet) if err != nil { chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} return diff --git a/refactor/adapters/udp/udpAckNacker.go b/refactor/adapters/udp/udpAckNacker.go index e9069cc96..2a4c7a644 100644 --- a/refactor/adapters/udp/udpAckNacker.go +++ b/refactor/adapters/udp/udpAckNacker.go @@ -16,7 +16,7 @@ type udpAckNacker struct { } // Ack implements the core.Adapter interface -func (an udpAckNacker) Ack(p *core.Packet) error { +func (an udpAckNacker) Ack(p core.Packet) error { cherr := make(chan error) an.Chack <- AckMsg{Type: AN_ACK, Addr: an.Addr, Packet: p, Cherr: cherr} return <-cherr diff --git a/refactor/interfaces.go b/refactor/interfaces.go index f07828860..1a5d75e6d 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -18,7 +18,7 @@ type Component interface { } type AckNacker interface { - Ack(p *Packet) error + Ack(p Packet) error Nack() error } From dcc881a48488ba46b44b812f71611e9b2bfcee17 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 16:19:18 +0100 Subject: [PATCH 0679/2266] [refactor] Write collect test suite and move helpers into separated file --- .../adapters/http/handlers/collect_test.go | 225 ++++++------------ .../adapters/http/handlers/helpers_test.go | 166 +++++++++++++ 2 files changed, 234 insertions(+), 157 deletions(-) create mode 100644 refactor/adapters/http/handlers/helpers_test.go diff --git a/refactor/adapters/http/handlers/collect_test.go b/refactor/adapters/http/handlers/collect_test.go index 97abb7cf6..0cbdb9728 100644 --- a/refactor/adapters/http/handlers/collect_test.go +++ b/refactor/adapters/http/handlers/collect_test.go @@ -4,19 +4,12 @@ package handlers import ( - "bytes" - // "fmt" - "io" "net/http" - "reflect" "testing" "time" . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" - //"github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -27,13 +20,76 @@ func TestCollect(t *testing.T) { ContentType string Method string ShouldAck bool - AckPacket *core.Packet + AckPacket core.Packet - WantContent []byte + WantContent string WantStatusCode int WantPacket []byte WantError *string - }{} + }{ + { + Desc: "Valid Payload. Invalid ContentType. Valid Method. Nack.", + Payload: "Patate", + ContentType: "application/patate", + Method: "POST", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusBadRequest, + WantPacket: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Invalid Method. Nack.", + Payload: "Patate", + ContentType: "application/octet-stream", + Method: "PUT", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusMethodNotAllowed, + WantPacket: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Nack.", + Payload: "Patate", + ContentType: "application/octet-stream", + Method: "POST", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusNotFound, + WantPacket: []byte("Patate"), + WantError: nil, + }, + { + Desc: "Invalid Ack. Valid ContentType. Valid Method.", + Payload: "Patate", + ContentType: "application/octet-stream", + Method: "POST", + ShouldAck: true, + AckPacket: testPacket{payload: ""}, + + WantContent: ErrFailedOperation, + WantStatusCode: http.StatusBadRequest, + WantPacket: []byte("Patate"), + WantError: nil, + }, + { + Desc: "Valid Ack. Valid ContentType. Valid Method.", + Payload: "Patate", + ContentType: "application/octet-stream", + Method: "POST", + ShouldAck: true, + AckPacket: testPacket{payload: "Response"}, + + WantContent: "Response", + WantStatusCode: http.StatusOK, + WantPacket: []byte("Patate"), + WantError: nil, + }, + } var port uint = 3000 for _, test := range tests { @@ -41,12 +97,11 @@ func TestCollect(t *testing.T) { Desc(t, test.Desc) // Build - handler := Collect{} - adapter := createAdapter(t, port, handler) + adapter, url := createAdapter(t, port) client := testClient{} // Operate - chresp := client.Send(test.Payload, handler.Url(), test.Method, test.ContentType) + chresp := client.Send(test.Payload, url, test.Method, test.ContentType) packet, err := tryNext(adapter, test.ShouldAck, test.AckPacket) var statusCode int var content []byte @@ -64,148 +119,4 @@ func TestCollect(t *testing.T) { checkPacket(t, test.WantPacket, packet) port += 1 } - -} - -// ----- TYPES utilities -type testPacket struct { - payload string -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p testPacket) MarshalBinary() ([]byte, error) { - if p.payload == "" { - return nil, errors.New(ErrInvalidStructure, "Fake error") - } - - return []byte(p.payload), nil -} - -// String implements the core.Packet interface -func (p testPacket) String() string { - return p.payload -} - -// ----- BUILD utilities -func createAdapter(t *testing.T, port uint, handler Handler) *Adapter { - adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) - if err != nil { - panic(err) - } - adapter.Bind(handler) - return adapter -} - -type testClient struct { - http.Client -} - -func (c testClient) Send(payload string, url string, method string, contentType string) chan MsgRes { - buf := new(bytes.Buffer) - if _, err := buf.Write([]byte(payload)); err != nil { - panic(err) - } - - request, err := http.NewRequest(method, url, buf) - if err != nil { - panic(err) - } - request.Header.Set("Content-Type", contentType) - - chresp := make(chan MsgRes) - go func() { - resp, err := c.Do(request) - if err != nil { - panic(err) - } - - data := make([]byte, 2048) - n, err := resp.Body.Read(data) - if err != nil && err != io.EOF { - panic(err) - } - - chresp <- MsgRes{resp.StatusCode, data[:n]} - }() - return chresp -} - -// ----- OPERATE utilities -func tryNext(adapter core.Adapter, shouldAck bool, packet *core.Packet) ([]byte, error) { - chresp := make(chan struct { - Packet []byte - Error error - }) - go func() { - pkt, an, err := adapter.Next() - defer func() { - chresp <- struct { - Packet []byte - Error error - }{pkt, err} - }() - if err != nil { - return - } - - if shouldAck { - an.Ack(packet) - } else { - an.Nack() - } - }() - - select { - case resp := <-chresp: - return resp.Packet, resp.Error - case <-time.After(time.Millisecond * 100): - return nil, nil - } -} - -// ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - -func checkStatusCode(t *testing.T, want int, got int) { - if want == got { - Ok(t, "Check status code") - return - } - Ko(t, "Expected status code to be %d but got %d", want, got) -} - -func checkContent(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual([]byte(want), got) { - Ok(t, "Check content") - return - } - Ko(t, "Received content does not match expectations.\nWant: %v\nGot: %v", want, got) -} - -func checkPacket(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packet") - return - } - Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", want, got) } diff --git a/refactor/adapters/http/handlers/helpers_test.go b/refactor/adapters/http/handlers/helpers_test.go new file mode 100644 index 000000000..6e4b2af69 --- /dev/null +++ b/refactor/adapters/http/handlers/helpers_test.go @@ -0,0 +1,166 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "bytes" + "fmt" + "io" + "net/http" + "reflect" + "strings" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +// ----- TYPES utilities +type testPacket struct { + payload string +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p testPacket) MarshalBinary() ([]byte, error) { + if p.payload == "" { + return nil, errors.New(ErrInvalidStructure, "Fake error") + } + + return []byte(p.payload), nil +} + +// String implements the core.Packet interface +func (p testPacket) String() string { + return p.payload +} + +// ----- BUILD utilities +func createAdapter(t *testing.T, port uint) (*Adapter, string) { + adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + if err != nil { + panic(err) + } + <-time.After(time.Millisecond * 250) // Let the connection starts + handler := Collect{} + adapter.Bind(handler) + return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.Url()) +} + +type testClient struct { + http.Client +} + +func (c testClient) Send(payload string, url string, method string, contentType string) chan MsgRes { + buf := new(bytes.Buffer) + if _, err := buf.Write([]byte(payload)); err != nil { + panic(err) + } + + request, err := http.NewRequest(method, url, buf) + if err != nil { + panic(err) + } + request.Header.Set("Content-Type", contentType) + + chresp := make(chan MsgRes) + go func() { + resp, err := c.Do(request) + if err != nil { + panic(err) + } + + data := make([]byte, 2048) + n, err := resp.Body.Read(data) + if err != nil && err != io.EOF { + panic(err) + } + + chresp <- MsgRes{resp.StatusCode, data[:n]} + }() + return chresp +} + +// ----- OPERATE utilities +func tryNext(adapter core.Adapter, shouldAck bool, packet core.Packet) ([]byte, error) { + chresp := make(chan struct { + Packet []byte + Error error + }) + go func() { + pkt, an, err := adapter.Next() + defer func() { + chresp <- struct { + Packet []byte + Error error + }{pkt, err} + }() + if err != nil { + return + } + + if shouldAck { + an.Ack(packet) + } else { + an.Nack() + } + }() + + select { + case resp := <-chresp: + return resp.Packet, resp.Error + case <-time.After(time.Millisecond * 100): + return nil, nil + } +} + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkStatusCode(t *testing.T, want int, got int) { + if want == got { + Ok(t, "Check status code") + return + } + Ko(t, "Expected status code to be %d but got %d", want, got) +} + +func checkContent(t *testing.T, want string, got []byte) { + if strings.Contains(string(got), want) { + Ok(t, "Check content") + return + } + Ko(t, "Received content does not match expectations.\nWant: %s\nGot: %s", want, string(got)) +} + +func checkPacket(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check packet") + return + } + Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", want, got) +} From 430754f89cf947fc28906063f7b9624986cc3283 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 16:19:28 +0100 Subject: [PATCH 0680/2266] [refactor] Fix to make tests pass --- refactor/adapters/http/handlers/collect.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/http/handlers/collect.go b/refactor/adapters/http/handlers/collect.go index 22cdf7b99..9212606f6 100644 --- a/refactor/adapters/http/handlers/collect.go +++ b/refactor/adapters/http/handlers/collect.go @@ -32,8 +32,9 @@ func (p Collect) Url() string { func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { // Check the http method if req.Method != "POST" { + err := errors.New(ErrInvalidStructure, "Unreckognized HTTP method. Please use [POST] to transfer a packet") w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [POST] to transfer a packet")) + w.Write([]byte(err.Error())) return } @@ -49,7 +50,8 @@ func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- chpkt <- PktReq{Packet: data, Chresp: chresp} r, ok := <-chresp if !ok { - BadRequest(w, "Core server not responding") + err := errors.New(ErrFailedOperation, "Core server not responding") + BadRequest(w, err.Error()) return } w.WriteHeader(r.StatusCode) From e369658c31c61db68a067394d6d1db076ff59a92 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 16:28:25 +0100 Subject: [PATCH 0681/2266] [refactor] Write backbone scheme for pubsub tests --- .../adapters/http/handlers/collect_test.go | 2 +- .../adapters/http/handlers/helpers_test.go | 54 ++++++++++++++++- .../adapters/http/handlers/pubsub_test.go | 60 +++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 refactor/adapters/http/handlers/pubsub_test.go diff --git a/refactor/adapters/http/handlers/collect_test.go b/refactor/adapters/http/handlers/collect_test.go index 0cbdb9728..2eef025ed 100644 --- a/refactor/adapters/http/handlers/collect_test.go +++ b/refactor/adapters/http/handlers/collect_test.go @@ -97,7 +97,7 @@ func TestCollect(t *testing.T) { Desc(t, test.Desc) // Build - adapter, url := createAdapter(t, port) + adapter, url := createCollectAdapter(t, port) client := testClient{} // Operate diff --git a/refactor/adapters/http/handlers/helpers_test.go b/refactor/adapters/http/handlers/helpers_test.go index 6e4b2af69..ee690d40e 100644 --- a/refactor/adapters/http/handlers/helpers_test.go +++ b/refactor/adapters/http/handlers/helpers_test.go @@ -40,7 +40,18 @@ func (p testPacket) String() string { } // ----- BUILD utilities -func createAdapter(t *testing.T, port uint) (*Adapter, string) { +func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { + adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + if err != nil { + panic(err) + } + <-time.After(time.Millisecond * 250) // Let the connection starts + handler := PubSub{} + adapter.Bind(handler) + return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.Url()) +} + +func createCollectAdapter(t *testing.T, port uint) (*Adapter, string) { adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) if err != nil { panic(err) @@ -118,6 +129,39 @@ func tryNext(adapter core.Adapter, shouldAck bool, packet core.Packet) ([]byte, } } +func tryNextRegistration(adapter core.Adapter, shouldAck bool, packet core.Packet) (core.Registration, error) { + chresp := make(chan struct { + Registration core.Registration + Error error + }) + go func() { + reg, an, err := adapter.NextRegistration() + defer func() { + chresp <- struct { + Registration core.Registration + Error error + }{reg, err} + }() + + if err != nil { + return + } + + if shouldAck { + an.Ack(packet) + } else { + an.Nack() + } + }() + + select { + case resp := <-chresp: + return resp.Registration, resp.Error + case <-time.After(time.Millisecond * 100): + return nil, nil + } +} + // ----- CHECK utilities func checkErrors(t *testing.T, want *string, got error) { if got == nil { @@ -164,3 +208,11 @@ func checkPacket(t *testing.T, want []byte, got []byte) { } Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", want, got) } + +func checkRegistration(t *testing.T, want core.Registration, got core.Registration) { + if !reflect.DeepEqual(want, got) { + Ko(t, "Received registration does not match expectations.\nWant: %v\nGot: %v", want, got) + return + } + Ok(t, "check Registrations") +} diff --git a/refactor/adapters/http/handlers/pubsub_test.go b/refactor/adapters/http/handlers/pubsub_test.go new file mode 100644 index 000000000..a3b6ef4ab --- /dev/null +++ b/refactor/adapters/http/handlers/pubsub_test.go @@ -0,0 +1,60 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "fmt" + "testing" + "time" + + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestPubSub(t *testing.T) { + tests := []struct { + Desc string + Payload string + ContentType string + Method string + DevEUI string + ShouldAck bool + AckPacket core.Packet + + WantContent string + WantStatusCode int + WantRegistration core.Registration + WantError *string + }{} + + var port uint = 4000 + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + adapter, url := createPubSubAdapter(t, port) + port += 1 + client := testClient{} + + // Operate + url = fmt.Sprintf("%s/%s", url, test.DevEUI) + chresp := client.Send(test.Payload, url, test.Method, test.ContentType) + registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) + var statusCode int + var content []byte + select { + case resp := <-chresp: + statusCode = resp.StatusCode + content = resp.Content + case <-time.After(time.Millisecond * 100): + } + + // Check + checkErrors(t, test.WantError, err) + checkStatusCode(t, test.WantStatusCode, statusCode) + checkContent(t, test.WantContent, content) + checkRegistration(t, test.WantRegistration, registration) + } +} From f69708d97728d0f1176c31e3715836a31db27505 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 17:29:19 +0100 Subject: [PATCH 0682/2266] [refactor] Write down test suite for pubsub --- .../adapters/http/handlers/pubsub_test.go | 97 ++++++++++++++++++- 1 file changed, 95 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/http/handlers/pubsub_test.go b/refactor/adapters/http/handlers/pubsub_test.go index a3b6ef4ab..92eda2fd2 100644 --- a/refactor/adapters/http/handlers/pubsub_test.go +++ b/refactor/adapters/http/handlers/pubsub_test.go @@ -5,11 +5,15 @@ package handlers import ( "fmt" + "net/http" "testing" "time" + . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestPubSub(t *testing.T) { @@ -26,7 +30,96 @@ func TestPubSub(t *testing.T) { WantStatusCode int WantRegistration core.Registration WantError *string - }{} + }{ + { + Desc: "Invalid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", + Payload: "TheThingsNetwork", + ContentType: "application/json", + Method: "PUT", + DevEUI: "0000000011223344", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusBadRequest, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid DevEUI. Nack", + Payload: `{"app_eui":"0011223344556677","nwks_key":"00112233445566778899001122334455","app_url":"url"}`, + ContentType: "text/plain", + Method: "PUT", + DevEUI: "0000000011223344", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusBadRequest, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid DevEUI. Nack", + Payload: `{"app_eui":"0011223344556677","nwks_key":"00112233445566778899001122334455","app_url":"url"}`, + ContentType: "application/json", + Method: "POST", + DevEUI: "0000000011223344", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusMethodNotAllowed, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid DevEUI. Nack", + Payload: `{"app_eui":"0011223344556677","nwks_key":"00112233445566778899001122334455","app_url":"url"}`, + ContentType: "application/json", + Method: "PUT", + DevEUI: "12345678", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusBadRequest, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", + Payload: `{"app_eui":"0001020304050607","nwks_key":"00010203040506070809000102030405","app_url":"url"}`, + ContentType: "application/json", + Method: "PUT", + DevEUI: "0000000001020304", + ShouldAck: false, + + WantContent: ErrInvalidStructure, + WantStatusCode: http.StatusConflict, + WantRegistration: pubSubRegistration{ + recipient: NewHttpRecipient("url", "PUT"), + appEUI: lorawan.EUI64([8]byte{0, 1, 2, 3, 4, 5, 6, 7}), + nwkSKey: lorawan.AES128Key([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}), + devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + }, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Ack", + Payload: `{"app_eui":"0001020304050607","nwks_key":"00010203040506070809000102030405","app_url":"url"}`, + ContentType: "application/json", + Method: "PUT", + DevEUI: "0000000001020304", + ShouldAck: true, + + WantContent: "", + WantStatusCode: http.StatusAccepted, + WantRegistration: pubSubRegistration{ + recipient: NewHttpRecipient("url", "PUT"), + appEUI: lorawan.EUI64([8]byte{0, 1, 2, 3, 4, 5, 6, 7}), + nwkSKey: lorawan.AES128Key([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}), + devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + }, + WantError: nil, + }, + } var port uint = 4000 for _, test := range tests { @@ -39,7 +132,7 @@ func TestPubSub(t *testing.T) { client := testClient{} // Operate - url = fmt.Sprintf("%s/%s", url, test.DevEUI) + url = fmt.Sprintf("%s%s", url, test.DevEUI) chresp := client.Send(test.Payload, url, test.Method, test.ContentType) registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) var statusCode int From e5634b6e8022e623feed56f08ddfd2986ee29860 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 17:29:31 +0100 Subject: [PATCH 0683/2266] [refactor] Fix small issues and make tests pass --- refactor/adapters/http/handlers/pubsub.go | 5 +++-- refactor/adapters/http/regAckNacker.go | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go index 5af652a3b..16a05222d 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/refactor/adapters/http/handlers/pubsub.go @@ -52,8 +52,9 @@ func (p PubSub) Url() string { func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { // Check the http method if req.Method != "PUT" { + err := errors.New(ErrInvalidStructure, "Unreckognized HTTP method. Please use [PUT] to register a device") w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [PUT] to register a device")) + w.Write([]byte(err.Error())) return } @@ -106,7 +107,7 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, err) + return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Unable to unmarshal the request body") } // Verify each request parameter diff --git a/refactor/adapters/http/regAckNacker.go b/refactor/adapters/http/regAckNacker.go index 9ccb1bf80..89c1f3c08 100644 --- a/refactor/adapters/http/regAckNacker.go +++ b/refactor/adapters/http/regAckNacker.go @@ -40,7 +40,7 @@ func (r regAckNacker) Nack() error { select { case r.Chresp <- MsgRes{ StatusCode: http.StatusConflict, - Content: []byte(ErrInvalidStructure), + Content: []byte(errors.New(ErrInvalidStructure, "Unable to register device").Error()), }: return nil case <-time.After(time.Millisecond * 50): From 9a0b7bffccc957a529433ef4c7279d670d218ce3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 17:53:17 +0100 Subject: [PATCH 0684/2266] [refactor] Re-write mqtt adapter backbone --- refactor/adapters/mqtt/mqtt.go | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 refactor/adapters/mqtt/mqtt.go diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go new file mode 100644 index 000000000..71af72b1a --- /dev/null +++ b/refactor/adapters/mqtt/mqtt.go @@ -0,0 +1,73 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + + //. "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + //"github.com/TheThingsNetwork/ttn/utils/errors" + //"github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/apex/log" + //"github.com/brocaar/lorawan" +) + +// Adapter type materializes an mqtt adapter which implements the basic mqtt protocol +type Adapter struct { + ctx log.Interface + packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently + registrations chan RegReq // Incoming registrations +} + +// Handler defines topic-specific handler. +type Handler interface { + Topic() string + Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) +} + +// Message sent through the response channel of a pktReq or regReq +type MsgRes []byte // The response content. + +// Message sent through the packets channel when an incoming request arrives +type PktReq struct { + Packet []byte // The actual packet that has been parsed + Chresp chan MsgRes // A response channel waiting for an success or reject confirmation +} + +// Message sent through the registration channel when an incoming registration arrives +type RegReq struct { + Registration core.Registration + Chresp chan MsgRes +} + +// NewAdapter constructs and allocates a new mqtt adapter +func NewAdapter(ctx log.Interface) (*Adapter, error) { + adapter := &Adapter{ + ctx: ctx, + packets: make(chan PktReq), + registrations: make(chan RegReq), + } + + return adapter, nil +} + +// Send implements the core.Adapter interface +func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { + return nil, nil +} + +// Next implements the core.Adapter interface +func (a *Adapter) Next() ([]byte, core.AckNacker, error) { + return nil, nil, nil +} + +// NextRegistration implements the core.Adapter interface. Not implemented for this adapters. +func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { + return nil, nil, nil +} + +// Bind registers a handler to a specific endpoint +func (a *Adapter) Bind(h Handler) { +} From c0a9b28ab3f5c57d0954105e6f8f572b42ea4f7e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 18:09:12 +0100 Subject: [PATCH 0685/2266] [refactor] Add a NewClient() method to generates an MQTT client easily --- refactor/adapters/mqtt/mqtt.go | 40 ++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 7 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 71af72b1a..3548016b8 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -4,18 +4,18 @@ package mqtt import ( - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "fmt" - //. "github.com/TheThingsNetwork/ttn/core/errors" + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" - //"github.com/TheThingsNetwork/ttn/utils/errors" - //"github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" - //"github.com/brocaar/lorawan" ) // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { + *MQTT.Client ctx log.Interface packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently registrations chan RegReq // Incoming registrations @@ -42,15 +42,41 @@ type RegReq struct { Chresp chan MsgRes } +// MQTT Schemes available +type Scheme string + +const ( + Tcp Scheme = "tcp" + Tls Scheme = "tls" + WebSocket Scheme = "ws" +) + // NewAdapter constructs and allocates a new mqtt adapter -func NewAdapter(ctx log.Interface) (*Adapter, error) { +func NewAdapter(client *MQTT.Client, ctx log.Interface) *Adapter { adapter := &Adapter{ + Client: client, ctx: ctx, packets: make(chan PktReq), registrations: make(chan RegReq), } - return adapter, nil + return adapter +} + +// NewClient generates a new paho MQTT client from an id and a broker url +// +// The broker url is expected to contain a port if needed such as mybroker.com:87354 +// +// The scheme has to be the same as the one used by the broker: tcp, tls or web socket +func NewClient(id string, broker string, scheme Scheme) (*MQTT.Client, error) { + opts := MQTT.NewClientOptions() + opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) + opts.SetClientID(id) + client := MQTT.NewClient(opts) + if token := client.Connect(); token.Wait() && token.Error() != nil { + return nil, errors.New(ErrFailedOperation, token.Error()) + } + return client, nil } // Send implements the core.Adapter interface From 41b23e6f1c33d2a258292c55136db307767cd4db Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 18:17:12 +0100 Subject: [PATCH 0686/2266] [refactor] Define Bind() method --- refactor/adapters/mqtt/mqtt.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 3548016b8..f0932aac5 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -52,6 +52,8 @@ const ( ) // NewAdapter constructs and allocates a new mqtt adapter +// +// The client is expected to be already connected to the right broker and ready to be used. func NewAdapter(client *MQTT.Client, ctx log.Interface) *Adapter { adapter := &Adapter{ Client: client, @@ -95,5 +97,16 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) } // Bind registers a handler to a specific endpoint -func (a *Adapter) Bind(h Handler) { +func (a *Adapter) Bind(h Handler) error { + ctx := a.ctx.WithField("topic", h.Topic()) + ctx.Info("Subscribe new handler") + token := a.Subscribe(h.Topic(), 2, func(client *MQTT.Client, msg MQTT.Message) { + ctx.Debug("Handle new mqtt message") + h.Handle(client, a.packets, a.registrations, msg) + }) + if token.Wait() && token.Error() != nil { + ctx.WithError(token.Error()).Error("Unable to Subscribe") + return errors.New(ErrFailedOperation, token.Error()) + } + return nil } From ce57ed3a16326e39c4f75ad5469d55e05a9d9281 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 18:23:23 +0100 Subject: [PATCH 0687/2266] [refactor] Implements a very basic mqttAckNacker --- refactor/adapters/mqtt/mqtt.go | 3 +- refactor/adapters/mqtt/mqttAckNacker.go | 46 +++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 refactor/adapters/mqtt/mqttAckNacker.go diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index f0932aac5..acbbf2bca 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -88,7 +88,8 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Next implements the core.Adapter interface func (a *Adapter) Next() ([]byte, core.AckNacker, error) { - return nil, nil, nil + p := <-a.packets + return p.Packet, mqttAckNacker{Chresp: p.Chresp}, nil } // NextRegistration implements the core.Adapter interface. Not implemented for this adapters. diff --git a/refactor/adapters/mqtt/mqttAckNacker.go b/refactor/adapters/mqtt/mqttAckNacker.go new file mode 100644 index 000000000..afb325fdc --- /dev/null +++ b/refactor/adapters/mqtt/mqttAckNacker.go @@ -0,0 +1,46 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// mqttAckNacker implements the core.AckNacker interface +type mqttAckNacker struct { + Chresp chan<- MsgRes // A channel dedicated to send back a response +} + +// Ack implements the core.AckNacker interface +func (an mqttAckNacker) Ack(p core.Packet) error { + if an.Chresp == nil { + return nil + } + defer close(an.Chresp) + + if p == nil { + return nil + } + + data, err := p.MarshalBinary() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + + select { + case an.Chresp <- MsgRes(data): + return nil + case <-time.After(time.Millisecond * 50): + return errors.New(ErrFailedOperation, "No response was given to the acknacker") + } +} + +// Nack implements the core.AckNacker interface +func (an mqttAckNacker) Nack() error { + return nil +} From 13c9a9a3aa4b421a4179dd76f34a18dc40f2e7c8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 19:17:42 +0100 Subject: [PATCH 0688/2266] [refactor] Implement mqtt adapter Send() method --- refactor/adapters/mqtt/mqtt.go | 88 ++++++++++++++++++++++++- refactor/adapters/mqtt/mqttRecipient.go | 15 +++++ refactor/adapters/mqtt/mqttStorage.go | 12 ++++ 3 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 refactor/adapters/mqtt/mqttRecipient.go create mode 100644 refactor/adapters/mqtt/mqttStorage.go diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index acbbf2bca..89c003e13 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -5,17 +5,20 @@ package mqtt import ( "fmt" + "sync" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" ) // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { *MQTT.Client + db Storage // Internal storage to store all packets received by the broker ctx log.Interface packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently registrations chan RegReq // Incoming registrations @@ -54,9 +57,10 @@ const ( // NewAdapter constructs and allocates a new mqtt adapter // // The client is expected to be already connected to the right broker and ready to be used. -func NewAdapter(client *MQTT.Client, ctx log.Interface) *Adapter { +func NewAdapter(client *MQTT.Client, db Storage, ctx log.Interface) *Adapter { adapter := &Adapter{ Client: client, + db: db, ctx: ctx, packets: make(chan PktReq), registrations: make(chan RegReq), @@ -83,7 +87,87 @@ func NewClient(id string, broker string, scheme Scheme) (*MQTT.Client, error) { // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { - return nil, nil + stats.MarkMeter("mqtt_adapter.send") + stats.UpdateHistogram("mqtt_adapter.send_recipients", int64(len(recipients))) + + // Marshal the packet to raw binary data + data, err := p.MarshalBinary() + if err != nil { + a.ctx.WithError(err).Warn("Invalid Packet") + return nil, errors.New(ErrInvalidStructure, err) + } + + a.ctx.Debug("Sending Packet") + + // Prepare gorund for parrallel mqtt publication + nb := len(recipients) + cherr := make(chan error, nb) + chresp := make(chan []byte, nb) + wg := sync.WaitGroup{} + wg.Add(2 * nb) + + for _, r := range recipients { + // Get the actual recipient + recipient, ok := r.(MqttRecipient) + if !ok { + a.ctx.WithField("recipient", r).Warn("Unable to interpret recipient as mqttRecipient") + continue + } + + // Publish on each topic + go func(recipient MqttRecipient) { + defer wg.Done() + + ctx := a.ctx.WithField("topic", recipient.TopicUp()) + + // Publish packet + token := a.Publish(recipient.TopicUp(), 2, false, data) + if token.Wait() && token.Error() != nil { + ctx.WithError(token.Error()).Error("Unable to publish") + cherr <- errors.New(ErrFailedOperation, token.Error()) + return + } + }(recipient) + + // Pull responses from each down topic, expecting only one + go func(recipient MqttRecipient) { + defer wg.Done() + + ctx := a.ctx.WithField("topic", recipient.TopicDown()) + + data, err := a.db.Pull(recipient.TopicDown()) + if err != nil { + ctx.WithError(err).Warn("Unable to pull response") + return + } + chresp <- data + }(recipient) + } + + // Wait for each request to be done + stats.IncCounter("mqtt_adapter.waiting_for_send") + wg.Wait() + stats.DecCounter("mqtt_adapter.waiting_for_send") + close(cherr) + close(chresp) + + // Collect errors + errored := len(cherr) + + // Collect response + if len(chresp) > 1 { + return nil, errors.New(ErrWrongBehavior, "Received too many positive answers") + } + + if len(chresp) == 0 && errored != 0 { + return nil, errors.New(ErrFailedOperation, "No positive response from recipients but got unexpected answers") + } + + if len(chresp) == 0 && errored == 0 { + return nil, errors.New(ErrWrongBehavior, "No recipient gave a positive answer") + } + + return <-chresp, nil } // Next implements the core.Adapter interface diff --git a/refactor/adapters/mqtt/mqttRecipient.go b/refactor/adapters/mqtt/mqttRecipient.go new file mode 100644 index 000000000..eb5c03826 --- /dev/null +++ b/refactor/adapters/mqtt/mqttRecipient.go @@ -0,0 +1,15 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding" +) + +// MqttRecipient describes recipient manipulated by the mqtt adapter +type MqttRecipient interface { + encoding.BinaryMarshaler + TopicUp() string + TopicDown() string +} diff --git a/refactor/adapters/mqtt/mqttStorage.go b/refactor/adapters/mqtt/mqttStorage.go new file mode 100644 index 000000000..8392b43d3 --- /dev/null +++ b/refactor/adapters/mqtt/mqttStorage.go @@ -0,0 +1,12 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import () + +// Storage defines an interface for the mqtt adapter +type Storage interface { + Push(topic string, data []byte) error + Pull(topic string) ([]byte, error) +} From e6eee8cbe709196276124667416878fecc83c81d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 19:41:16 +0100 Subject: [PATCH 0689/2266] [refactor] Move storage to an utils and bind it to an object --- utils/storage/storage.go | 144 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 utils/storage/storage.go diff --git a/utils/storage/storage.go b/utils/storage/storage.go new file mode 100644 index 000000000..2d86fbb8e --- /dev/null +++ b/utils/storage/storage.go @@ -0,0 +1,144 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding" + "fmt" + "io" + "reflect" + "time" + + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" + "github.com/boltdb/bolt" +) + +// storageEntry offers a friendly interface on which the storage will operate. +// Basically, a storageEntry is nothing more than a binary marshaller/unmarshaller. +type storageEntry interface { + encoding.BinaryMarshaler + encoding.BinaryUnmarshaler +} + +type Interface struct { + db *bolt.DB +} + +// New creates a new storage instance ready-to-use +func New(name string) (Interface, error) { + db, err := bolt.Open(name, 0600, &bolt.Options{Timeout: time.Second}) + if err != nil { + return Interface{}, errors.New(ErrFailedOperation, err) + } + return Interface{db}, nil +} + +// initDB initializes the given bolt bucket by creating (if not already exists) an empty bucket +func (itf Interface) init(bucketName string) error { + return itf.db.Update(func(tx *bolt.Tx) error { + _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) + return err + }) +} + +// store put a new entry in the given bolt database. It adds the entry to an existing set or create +// a new set containing one element. +func (itf Interface) store(bucketName string, key []byte, entry storageEntry) error { + marshalled, err := entry.MarshalBinary() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + + err = itf.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(bucketName)) + if bucket == nil { + return errors.New(ErrFailedOperation, "storage unreachable") + } + w := readwriter.New(bucket.Get(key)) + w.Write(marshalled) + data, err := w.Bytes() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + if err := bucket.Put(key, data); err != nil { + return errors.New(ErrFailedOperation, err) + } + return nil + }) + + return err +} + +// lookup retrieve a set of entry from a given bolt database. +// +// The shape is used as a template for retrieving and creating the data. All entries extracted from +// the database will be interpreted as instance of shape and the return result will be a slice of +// the same type of shape. +func (itf Interface) lookup(bucketName string, key []byte, shape storageEntry) (interface{}, error) { + // First, lookup the raw entries + var rawEntry []byte + err := itf.db.View(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(bucketName)) + if bucket == nil { + return errors.New(ErrFailedOperation, "storage unreachable") + } + rawEntry = bucket.Get(key) + if rawEntry == nil { + return errors.New(ErrWrongBehavior, fmt.Sprintf("Not found %+v", key)) + } + return nil + }) + + if err != nil { + return nil, err + } + + // Then, interpret them as instance of 'shape' + r := readwriter.New(rawEntry) + entryType := reflect.TypeOf(shape).Elem() + entries := reflect.MakeSlice(reflect.SliceOf(entryType), 0, 0) + for { + r.Read(func(data []byte) { + entry := reflect.New(entryType).Interface() + entry.(storageEntry).UnmarshalBinary(data) + entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) + }) + if err = r.Err(); err != nil { + if err == io.EOF { + break + } + return nil, errors.New(ErrFailedOperation, err) + } + } + return entries.Interface(), nil +} + +// flush empties each entry of a bucket associated to a given device +func (itf Interface) flush(bucketName string, key []byte) error { + return itf.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(bucketName)) + if bucket == nil { + return errors.New(ErrFailedOperation, "storage unreachable") + } + if err := bucket.Delete(key); err != nil { + return errors.New(ErrFailedOperation, err) + } + return nil + }) +} + +// resetDB resets a given bucket from a given bolt database +func (itf Interface) reset(bucketName string) error { + return itf.db.Update(func(tx *bolt.Tx) error { + if err := tx.DeleteBucket([]byte(bucketName)); err != nil { + return errors.New(ErrFailedOperation, err) + } + if _, err := tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil { + return errors.New(ErrFailedOperation, err) + } + return nil + }) +} From 4f354d32ff2b71e73f2eb94ec1368b73d662ca00 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 20:13:35 +0100 Subject: [PATCH 0690/2266] [refactor] Allow Store() to store several entries at once in utils/storage --- utils/storage/storage.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 2d86fbb8e..49190e8c2 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -18,7 +18,7 @@ import ( // storageEntry offers a friendly interface on which the storage will operate. // Basically, a storageEntry is nothing more than a binary marshaller/unmarshaller. -type storageEntry interface { +type StorageEntry interface { encoding.BinaryMarshaler encoding.BinaryUnmarshaler } @@ -37,7 +37,7 @@ func New(name string) (Interface, error) { } // initDB initializes the given bolt bucket by creating (if not already exists) an empty bucket -func (itf Interface) init(bucketName string) error { +func (itf Interface) Init(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) return err @@ -46,19 +46,26 @@ func (itf Interface) init(bucketName string) error { // store put a new entry in the given bolt database. It adds the entry to an existing set or create // a new set containing one element. -func (itf Interface) store(bucketName string, key []byte, entry storageEntry) error { - marshalled, err := entry.MarshalBinary() - if err != nil { - return errors.New(ErrInvalidStructure, err) +func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry) error { + var marshalled [][]byte + + for _, entry := range entries { + m, err := entry.MarshalBinary() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + marshalled = append(marshalled, m) } - err = itf.db.Update(func(tx *bolt.Tx) error { + err := itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { return errors.New(ErrFailedOperation, "storage unreachable") } w := readwriter.New(bucket.Get(key)) - w.Write(marshalled) + for _, m := range marshalled { + w.Write(m) + } data, err := w.Bytes() if err != nil { return errors.New(ErrInvalidStructure, err) @@ -77,7 +84,7 @@ func (itf Interface) store(bucketName string, key []byte, entry storageEntry) er // The shape is used as a template for retrieving and creating the data. All entries extracted from // the database will be interpreted as instance of shape and the return result will be a slice of // the same type of shape. -func (itf Interface) lookup(bucketName string, key []byte, shape storageEntry) (interface{}, error) { +func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) (interface{}, error) { // First, lookup the raw entries var rawEntry []byte err := itf.db.View(func(tx *bolt.Tx) error { @@ -103,7 +110,7 @@ func (itf Interface) lookup(bucketName string, key []byte, shape storageEntry) ( for { r.Read(func(data []byte) { entry := reflect.New(entryType).Interface() - entry.(storageEntry).UnmarshalBinary(data) + entry.(StorageEntry).UnmarshalBinary(data) entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) }) if err = r.Err(); err != nil { @@ -117,7 +124,7 @@ func (itf Interface) lookup(bucketName string, key []byte, shape storageEntry) ( } // flush empties each entry of a bucket associated to a given device -func (itf Interface) flush(bucketName string, key []byte) error { +func (itf Interface) Flush(bucketName string, key []byte) error { return itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { @@ -131,7 +138,7 @@ func (itf Interface) flush(bucketName string, key []byte) error { } // resetDB resets a given bucket from a given bolt database -func (itf Interface) reset(bucketName string) error { +func (itf Interface) Reset(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { return errors.New(ErrFailedOperation, err) From 26a4cbd6f940ff5ca052b91469eaf3dd15e0f745 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 20:27:41 +0100 Subject: [PATCH 0691/2266] [refactor] Add Replace() method to utils/storage which combines Delete + Store --- utils/storage/storage.go | 47 +++++++++++++++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 6 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 49190e8c2..9cdc38c41 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -36,7 +36,7 @@ func New(name string) (Interface, error) { return Interface{db}, nil } -// initDB initializes the given bolt bucket by creating (if not already exists) an empty bucket +// Init initializes the given bolt bucket by creating (if not already exists) an empty bucket func (itf Interface) Init(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) @@ -44,8 +44,8 @@ func (itf Interface) Init(bucketName string) error { }) } -// store put a new entry in the given bolt database. It adds the entry to an existing set or create -// a new set containing one element. +// Store put a new set of entries in the given bolt database. It adds the entries to an existing set +// or create a new set. func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry) error { var marshalled [][]byte @@ -79,7 +79,7 @@ func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry return err } -// lookup retrieve a set of entry from a given bolt database. +// Lookup retrieves a set of entry from a given bolt database. // // The shape is used as a template for retrieving and creating the data. All entries extracted from // the database will be interpreted as instance of shape and the return result will be a slice of @@ -123,7 +123,7 @@ func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) ( return entries.Interface(), nil } -// flush empties each entry of a bucket associated to a given device +// Flush empties each entry of a bucket associated to a given device func (itf Interface) Flush(bucketName string, key []byte) error { return itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) @@ -137,7 +137,42 @@ func (itf Interface) Flush(bucketName string, key []byte) error { }) } -// resetDB resets a given bucket from a given bolt database +// Replace stores entries in the database by replacing them by a new set +func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEntry) error { + var marshalled [][]byte + + for _, entry := range entries { + m, err := entry.MarshalBinary() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + marshalled = append(marshalled, m) + } + + return itf.db.Update(func(tx *bolt.Tx) error { + bucket := tx.Bucket([]byte(bucketName)) + if bucket == nil { + return errors.New(ErrFailedOperation, "storage unreachable") + } + if err := bucket.Delete(key); err != nil { + return errors.New(ErrFailedOperation, err) + } + w := readwriter.New(bucket.Get(key)) + for _, m := range marshalled { + w.Write(m) + } + data, err := w.Bytes() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + if err := bucket.Put(key, data); err != nil { + return errors.New(ErrFailedOperation, err) + } + return nil + }) +} + +// Reset resets a given bucket from a given bolt database func (itf Interface) Reset(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { From 36ca4e932cc0f6b2030b9bf63e6aa0673e7cc805 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 20:38:34 +0100 Subject: [PATCH 0692/2266] [refactor] Implement the mqtt storage --- refactor/adapters/mqtt/mqttStorage.go | 88 ++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/refactor/adapters/mqtt/mqttStorage.go b/refactor/adapters/mqtt/mqttStorage.go index 8392b43d3..4f9cd28a3 100644 --- a/refactor/adapters/mqtt/mqttStorage.go +++ b/refactor/adapters/mqtt/mqttStorage.go @@ -3,10 +3,96 @@ package mqtt -import () +import ( + "fmt" + + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + db "github.com/TheThingsNetwork/ttn/utils/storage" +) // Storage defines an interface for the mqtt adapter type Storage interface { Push(topic string, data []byte) error Pull(topic string) ([]byte, error) } + +// type storage materializes a concrete mqtt.Storage +type storage struct { + db.Interface + Name string +} + +// Storage entry implements the storage.StorageEntry interface +type storageEntry struct { + Data []byte +} + +// NewStorage creates a new mqtt.Storage +func NewStorage(name string) (Storage, error) { + itf, err := db.New(name) + if err != nil { + return nil, errors.New(ErrFailedOperation, err) + } + + tableName := "mqtt_adapter" + if err := itf.Init(tableName); err != nil { + return nil, errors.New(ErrFailedOperation, err) + } + return storage{Interface: itf, Name: tableName}, nil +} + +// Push implements the Storage interface +func (s storage) Push(topic string, data []byte) error { + err := s.Store(s.Name, []byte(topic), []db.StorageEntry{&storageEntry{data}}) + if err != nil { + return errors.New(ErrFailedOperation, err) + } + return nil +} + +// Pull implements the Storage interface +func (s storage) Pull(topic string) ([]byte, error) { + entries, err := s.Lookup(s.Name, []byte(topic), &storageEntry{}) + if err != nil { + return nil, errors.New(ErrFailedOperation, err) + } + + packets, ok := entries.([]*storageEntry) + if !ok { + return nil, errors.New(ErrFailedOperation, "Unable to retrieve data from db") + } + + // NOTE: one day, those entry will be more complicated, with a ttl. + // Here's the place where we should check for that. Cheers. + if len(packets) == 0 { + return nil, errors.New(ErrWrongBehavior, fmt.Sprintf("Entry not found for %s", topic)) + } + + pkt := packets[0] + + var newEntries []db.StorageEntry + for _, p := range packets[1:] { + newEntries = append(newEntries, p) + } + + if err := s.Replace(s.Name, []byte(topic), newEntries); err != nil { + return nil, errors.New(ErrFailedOperation, "Unable to restore data in db") + } + + return pkt.Data, nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e storageEntry) MarshalBinary() ([]byte, error) { + return e.Data, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *storageEntry) UnmarshalBinary(data []byte) error { + if e == nil { + return errors.New(ErrInvalidStructure, "Unable to unmarshal nil entry") + } + e.Data = data + return nil +} From 1121047f343579d98185910b15bcc7e7d773bc1b Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 23 Feb 2016 20:57:02 +0100 Subject: [PATCH 0693/2266] [refactor] Remove storage from mqtt recipient -> will be moved into handler --- refactor/adapters/mqtt/mqtt.go | 44 ++++++++++++++++++++------- refactor/adapters/mqtt/mqttStorage.go | 4 +++ 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 89c003e13..4f474f983 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -6,6 +6,7 @@ package mqtt import ( "fmt" "sync" + "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" . "github.com/TheThingsNetwork/ttn/core/errors" @@ -18,7 +19,6 @@ import ( // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { *MQTT.Client - db Storage // Internal storage to store all packets received by the broker ctx log.Interface packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently registrations chan RegReq // Incoming registrations @@ -57,10 +57,9 @@ const ( // NewAdapter constructs and allocates a new mqtt adapter // // The client is expected to be already connected to the right broker and ready to be used. -func NewAdapter(client *MQTT.Client, db Storage, ctx log.Interface) *Adapter { +func NewAdapter(client *MQTT.Client, ctx log.Interface) *Adapter { adapter := &Adapter{ Client: client, - db: db, ctx: ctx, packets: make(chan PktReq), registrations: make(chan RegReq), @@ -110,7 +109,22 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Get the actual recipient recipient, ok := r.(MqttRecipient) if !ok { - a.ctx.WithField("recipient", r).Warn("Unable to interpret recipient as mqttRecipient") + err := errors.New(ErrInvalidStructure, "Unable to interpret recipient as mqttRecipient") + a.ctx.WithField("recipient", r).Warn(err.Error()) + cherr <- err + continue + } + + // Subscribe to down channel (before publishing anything) + chdown := make(chan []byte) + token := a.Subscribe(recipient.TopicDown(), 2, func(client *MQTT.Client, msg MQTT.Message) { + chdown <- msg.Payload() + }) + if token.Wait() && token.Error() != nil { + err := errors.New(ErrFailedOperation, "Unable to subscribe to down topic") + a.ctx.WithField("recipient", recipient).Warn(err.Error()) + cherr <- err + close(chdown) continue } @@ -130,18 +144,26 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err }(recipient) // Pull responses from each down topic, expecting only one - go func(recipient MqttRecipient) { + go func(recipient MqttRecipient, chdown <-chan []byte) { defer wg.Done() ctx := a.ctx.WithField("topic", recipient.TopicDown()) - data, err := a.db.Pull(recipient.TopicDown()) - if err != nil { - ctx.WithError(err).Warn("Unable to pull response") - return + defer func(ctx log.Interface) { + if token := a.Unsubscribe(recipient.TopicDown()); token.Wait() && token.Error() != nil { + ctx.Warn("Unable to unsubscribe topic") + } + }(ctx) + + // Forward the downlink response received if any + select { + case data, ok := <-chdown: + if ok { + chresp <- data + } + case <-time.After(2 * time.Second): // Timeout } - chresp <- data - }(recipient) + }(recipient, chdown) } // Wait for each request to be done diff --git a/refactor/adapters/mqtt/mqttStorage.go b/refactor/adapters/mqtt/mqttStorage.go index 4f9cd28a3..c4e90688f 100644 --- a/refactor/adapters/mqtt/mqttStorage.go +++ b/refactor/adapters/mqtt/mqttStorage.go @@ -11,6 +11,10 @@ import ( db "github.com/TheThingsNetwork/ttn/utils/storage" ) +// !!!!!!!!!!!!!!!!!!!!!!!! +// NOTE THIS IS NOT THE ADAPTER ROLE -> SHOULD BE MOVED TO HANDLER +// !!!!!!!!!!!!!!!!!!!!!!!! + // Storage defines an interface for the mqtt adapter type Storage interface { Push(topic string, data []byte) error From 6cb4ae8f4d66b01c8a99f02fe855dfc19f436109 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 10:29:06 +0100 Subject: [PATCH 0694/2266] [refactor] Write mqtt tests backbone --- refactor/adapters/mqtt/mqtt_test.go | 54 +++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 refactor/adapters/mqtt/mqtt_test.go diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go new file mode 100644 index 000000000..b0844fd0b --- /dev/null +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -0,0 +1,54 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + //"github.com/TheThingsNetwork/ttn/utils/pointer" +) + +func TestMQTTSend(t *testing.T) { + tests := []struct { + Desc string // Test Description + Packet []byte // Handy representation of the packet to send + Recipient []testRecipient // List of recipient to send + + WantData []byte // Expected Data on the recipient + WantResponse []byte // Expected Response from the Send method + WantError *string // Expected error nature returned by the Send method + }{} + + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + // Generate new adapter + // Generate reception servers + + // Operate + // Send data to recipients + // Retrieve data from servers + + // Check + // Check if data has been received + // Check if response is valid + // Check if error is valid + } +} + +// ----- TYPE utilities +type testRecipient struct { + Response []byte + TopicUp string + TopicDown string +} + +// ----- BUILD utilities + +// ----- OPERATE utilities + +// ----- CHECK utilities From 00e2cdb8db8794647b9678fdaf158418fa9ba615 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 10:29:32 +0100 Subject: [PATCH 0695/2266] [refactor] Write test checks methods --- refactor/adapters/mqtt/mqtt_test.go | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index b0844fd0b..206f314b5 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -4,8 +4,10 @@ package mqtt import ( + "reflect" "testing" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" //"github.com/TheThingsNetwork/ttn/utils/pointer" ) @@ -52,3 +54,40 @@ type testRecipient struct { // ----- OPERATE utilities // ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == *want { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkResponses(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check responses") + return + } + Ko(t, "Received response does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) +} + +func checkData(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check data") + return + } + Ko(t, "Received data does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) +} From 49b37278dbf3ca6384b0e71638fe41f8ccb9ec85 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:07:18 +0100 Subject: [PATCH 0696/2266] [refactor] Write build and operate test helpers --- refactor/adapters/mqtt/mqtt_test.go | 79 +++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 206f314b5..389b7fa20 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -6,7 +6,11 @@ package mqtt import ( "reflect" "testing" + "time" + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + . "github.com/TheThingsNetwork/ttn/core/errors" + core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" //"github.com/TheThingsNetwork/ttn/utils/pointer" @@ -39,6 +43,9 @@ func TestMQTTSend(t *testing.T) { // Check if data has been received // Check if response is valid // Check if error is valid + + // Clean + // Disconnect clients } } @@ -49,9 +56,81 @@ type testRecipient struct { TopicDown string } +type testPacket struct { + payload []byte +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p testPacket) MarshalBinary() ([]byte, error) { + if p.payload == nil { + return nil, errors.New(ErrInvalidStructure, "Fake error") + } + + return p.payload, nil +} + +// String implements the core.Packet interface +func (p testPacket) String() string { + return string(p.payload) +} + // ----- BUILD utilities +func createAdapter(t *testing.T) (*MQTT.Client, core.Adapter) { + client, err := NewClient("testClient", "0.0.0.0", Tcp) + if err != nil { + panic(err) + } + + adapter := NewAdapter(client, GetLogger(t, "adapter")) + return client, adapter +} + +func createServers(recipients []testRecipient) (*MQTT.Client, chan []byte) { + client, err := NewClient("FakeServerClient", "0.0.0.0", Tcp) + if err != nil { + panic(err) + } + + chresp := make(chan []byte) + for _, r := range recipients { + token := client.Subscribe(r.TopicUp, 2, func(client *MQTT.Client, msg MQTT.Message) { + chresp <- msg.Payload() + }) + if token.Wait() && token.Error() != nil { + panic(token.Error()) + } + } + return client, chresp +} // ----- OPERATE utilities +func trySend(adapter core.Adapter, packet []byte, recipients []testRecipient) ([]byte, error) { + // Convert testRecipient to core.Recipient using the mqtt recipient + var coreRecipients []core.Recipient + for _, r := range recipients { + coreRecipients = append(coreRecipients, NewRecipient(r.TopicUp, r.TopicDown)) + } + + // Try send the packet + chresp := make(chan struct { + Data []byte + Error error + }) + go func() { + data, err := adapter.Send(testPacket{packet}, coreRecipients...) + chresp <- struct { + Data []byte + Error error + }{data, err} + }() + + select { + case resp := <-chresp: + return resp.Data, resp.Error + case <-time.After(time.Millisecond * 200): + return nil, nil + } +} // ----- CHECK utilities func checkErrors(t *testing.T, want *string, got error) { From 35bfcde35666db38ae63215337d5dad5ea158a40 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:16:27 +0100 Subject: [PATCH 0697/2266] [refactor] Implements mqtt recipient --- refactor/adapters/mqtt/mqttRecipient.go | 46 +++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/refactor/adapters/mqtt/mqttRecipient.go b/refactor/adapters/mqtt/mqttRecipient.go index eb5c03826..256e8a750 100644 --- a/refactor/adapters/mqtt/mqttRecipient.go +++ b/refactor/adapters/mqtt/mqttRecipient.go @@ -5,11 +5,57 @@ package mqtt import ( "encoding" + + . "github.com/TheThingsNetwork/ttn/core/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" ) // MqttRecipient describes recipient manipulated by the mqtt adapter type MqttRecipient interface { encoding.BinaryMarshaler + encoding.BinaryUnmarshaler TopicUp() string TopicDown() string } + +// mqttRecipient implements the MqttRecipient interface +type mqttRecipient struct { + up string + down string +} + +// NewRecipient creates a new MQTT recipient from two topics +func NewRecipient(up string, down string) MqttRecipient { + return &mqttRecipient{up: up, down: down} +} + +// TopicUp implements the MqttRecipient interface +func (r mqttRecipient) TopicUp() string { + return r.up +} + +// TopicDown implements the MqttRecipient interface +func (r mqttRecipient) TopicDown() string { + return r.down +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (r mqttRecipient) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(r.up) + rw.Write(r.down) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (r *mqttRecipient) UnmarshalBinary(data []byte) error { + if r == nil { + return errors.New(ErrInvalidStructure, "Cannot unmarshal nil structure") + } + + rw := readwriter.New(data) + rw.Read(func(data []byte) { r.up = string(data) }) + rw.Read(func(data []byte) { r.down = string(data) }) + return rw.Err() +} From 759aba9a69f52b70095c29cc7f57a83c7da2fb9c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:21:13 +0100 Subject: [PATCH 0698/2266] [refactor] Write test procedure --- refactor/adapters/mqtt/mqtt_test.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 389b7fa20..54171ec52 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -18,9 +18,9 @@ import ( func TestMQTTSend(t *testing.T) { tests := []struct { - Desc string // Test Description - Packet []byte // Handy representation of the packet to send - Recipient []testRecipient // List of recipient to send + Desc string // Test Description + Packet []byte // Handy representation of the packet to send + Recipients []testRecipient // List of recipient to send WantData []byte // Expected Data on the recipient WantResponse []byte // Expected Response from the Send method @@ -32,20 +32,25 @@ func TestMQTTSend(t *testing.T) { Desc(t, test.Desc) // Build - // Generate new adapter - // Generate reception servers + aclient, adapter := createAdapter(t) + sclient, chresp := createServers(test.Recipients) // Operate - // Send data to recipients - // Retrieve data from servers + data, err := trySend(adapter, test.Packet, test.Recipients) + var resp []byte + select { + case resp = <-chresp: + case <-time.After(time.Millisecond * 100): + } // Check - // Check if data has been received - // Check if response is valid - // Check if error is valid + checkErrors(t, test.WantError, err) + checkData(t, test.WantData, data) + checkResponses(t, test.WantResponse, resp) // Clean - // Disconnect clients + aclient.Disconnect(0) + sclient.Disconnect(0) } } From f0e78b2c3b393fbd6b7c670c26124ccfb2db0835 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:31:10 +0100 Subject: [PATCH 0699/2266] [refactor] Add missing response in test servers --- refactor/adapters/mqtt/mqtt_test.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 54171ec52..a444ce9ac 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -16,6 +16,8 @@ import ( //"github.com/TheThingsNetwork/ttn/utils/pointer" ) +const brokerUrl = "0.0.0.0:1683" + func TestMQTTSend(t *testing.T) { tests := []struct { Desc string // Test Description @@ -81,7 +83,7 @@ func (p testPacket) String() string { // ----- BUILD utilities func createAdapter(t *testing.T) (*MQTT.Client, core.Adapter) { - client, err := NewClient("testClient", "0.0.0.0", Tcp) + client, err := NewClient("testClient", brokerUrl, Tcp) if err != nil { panic(err) } @@ -91,14 +93,20 @@ func createAdapter(t *testing.T) (*MQTT.Client, core.Adapter) { } func createServers(recipients []testRecipient) (*MQTT.Client, chan []byte) { - client, err := NewClient("FakeServerClient", "0.0.0.0", Tcp) + client, err := NewClient("FakeServerClient", brokerUrl, Tcp) if err != nil { panic(err) } - chresp := make(chan []byte) + chresp := make(chan []byte, len(recipients)) for _, r := range recipients { token := client.Subscribe(r.TopicUp, 2, func(client *MQTT.Client, msg MQTT.Message) { + if r.Response != nil { + token := client.Publish(r.TopicDown, 2, false, r.Response) + if token.Wait() && token.Error() != nil { + panic(token.Error()) + } + } chresp <- msg.Payload() }) if token.Wait() && token.Error() != nil { From 1debbab3adce343c26b33975731ac54201a54a44 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:40:04 +0100 Subject: [PATCH 0700/2266] [refactor] Fix timeout delays in test procedure + write first test procedure --- refactor/adapters/mqtt/mqtt_test.go | 52 ++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index a444ce9ac..7097763c9 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -12,8 +12,8 @@ import ( . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - //"github.com/TheThingsNetwork/ttn/utils/pointer" ) const brokerUrl = "0.0.0.0:1683" @@ -27,7 +27,23 @@ func TestMQTTSend(t *testing.T) { WantData []byte // Expected Data on the recipient WantResponse []byte // Expected Response from the Send method WantError *string // Expected error nature returned by the Send method - }{} + }{ + { + Desc: "1 packet | 1 recipient | No response", + Packet: []byte("TheThingsNetwork"), + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up1", + TopicDown: "down1", + }, + }, + + WantData: []byte("TheThingsNetwork"), + WantResponse: nil, + WantError: pointer.String(ErrWrongBehavior), + }, + } for _, test := range tests { // Describe @@ -36,12 +52,13 @@ func TestMQTTSend(t *testing.T) { // Build aclient, adapter := createAdapter(t) sclient, chresp := createServers(test.Recipients) + <-time.After(time.Millisecond * 50) // Operate - data, err := trySend(adapter, test.Packet, test.Recipients) - var resp []byte + resp, err := trySend(adapter, test.Packet, test.Recipients) + var data []byte select { - case resp = <-chresp: + case data = <-chresp: case <-time.After(time.Millisecond * 100): } @@ -53,6 +70,7 @@ func TestMQTTSend(t *testing.T) { // Clean aclient.Disconnect(0) sclient.Disconnect(0) + <-time.After(time.Millisecond * 50) } } @@ -100,18 +118,20 @@ func createServers(recipients []testRecipient) (*MQTT.Client, chan []byte) { chresp := make(chan []byte, len(recipients)) for _, r := range recipients { - token := client.Subscribe(r.TopicUp, 2, func(client *MQTT.Client, msg MQTT.Message) { - if r.Response != nil { - token := client.Publish(r.TopicDown, 2, false, r.Response) - if token.Wait() && token.Error() != nil { - panic(token.Error()) + go func(r testRecipient) { + token := client.Subscribe(r.TopicUp, 2, func(client *MQTT.Client, msg MQTT.Message) { + if r.Response != nil { + token := client.Publish(r.TopicDown, 2, false, r.Response) + if token.Wait() && token.Error() != nil { + panic(token.Error()) + } } + chresp <- msg.Payload() + }) + if token.Wait() && token.Error() != nil { + panic(token.Error()) } - chresp <- msg.Payload() - }) - if token.Wait() && token.Error() != nil { - panic(token.Error()) - } + }(r) } return client, chresp } @@ -140,7 +160,7 @@ func trySend(adapter core.Adapter, packet []byte, recipients []testRecipient) ([ select { case resp := <-chresp: return resp.Data, resp.Error - case <-time.After(time.Millisecond * 200): + case <-time.After(time.Millisecond * 1250): return nil, nil } } From 48ceba3d4d9eb60147665f4513ab0dd1c57cf0fd Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:52:33 +0100 Subject: [PATCH 0701/2266] [refactor] Write other test suites --- refactor/adapters/mqtt/mqtt_test.go | 75 +++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 7097763c9..725b7253a 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -39,6 +39,81 @@ func TestMQTTSend(t *testing.T) { }, }, + WantData: []byte("TheThingsNetwork"), + WantResponse: nil, + WantError: pointer.String(ErrWrongBehavior), + }, + { + Desc: "invalid packet | 1 recipient | No response", + Packet: nil, + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up1", + TopicDown: "down1", + }, + }, + + WantData: nil, + WantResponse: nil, + WantError: pointer.String(ErrInvalidStructure), + }, + { + Desc: "1 packet | 2 recipient | No response", + Packet: []byte("TheThingsNetwork"), + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up1", + TopicDown: "down1", + }, + { + Response: nil, + TopicUp: "up2", + TopicDown: "down2", + }, + }, + + WantData: []byte("TheThingsNetwork"), + WantResponse: nil, + WantError: pointer.String(ErrWrongBehavior), + }, + { + Desc: "1 packet | 2 recipients | #1 answer ", + Packet: []byte("TheThingsNetwork"), + Recipients: []testRecipient{ + { + Response: []byte("IoT Rocks"), + TopicUp: "up1", + TopicDown: "down1", + }, + { + Response: nil, + TopicUp: "up2", + TopicDown: "down2", + }, + }, + + WantData: []byte("TheThingsNetwork"), + WantResponse: []byte("IoT Rocks"), + WantError: nil, + }, + { + Desc: "1 packet | 2 recipients | both answers ", + Packet: []byte("TheThingsNetwork"), + Recipients: []testRecipient{ + { + Response: []byte("IoT Rocks"), + TopicUp: "up1", + TopicDown: "down1", + }, + { + Response: []byte("IoT Rocks"), + TopicUp: "up2", + TopicDown: "down2", + }, + }, + WantData: []byte("TheThingsNetwork"), WantResponse: nil, WantError: pointer.String(ErrWrongBehavior), From 1f82424fd45c11dac80de9943f683cb6dfba8948 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 11:53:02 +0100 Subject: [PATCH 0702/2266] [refactor] Decrease timeout delay to 1 second in mqtt send() --- refactor/adapters/mqtt/mqtt.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 4f474f983..0845f86aa 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -98,7 +98,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err a.ctx.Debug("Sending Packet") - // Prepare gorund for parrallel mqtt publication + // Prepare ground for parrallel mqtt publications nb := len(recipients) cherr := make(chan error, nb) chresp := make(chan []byte, nb) @@ -161,7 +161,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err if ok { chresp <- data } - case <-time.After(2 * time.Second): // Timeout + case <-time.After(time.Second): // Timeout } }(recipient, chdown) } From 679be42031a959cd724434606b67a8e47a7d5de6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 13:31:18 +0100 Subject: [PATCH 0703/2266] [refactor] Add test for mqttRecipient marshaling --- refactor/adapters/mqtt/mqtt_test.go | 43 +++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 725b7253a..e232c0a8b 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -4,6 +4,7 @@ package mqtt import ( + "fmt" "reflect" "testing" "time" @@ -120,9 +121,9 @@ func TestMQTTSend(t *testing.T) { }, } - for _, test := range tests { + for i, test := range tests { // Describe - Desc(t, test.Desc) + Desc(t, fmt.Sprintf("#%d: %s", i, test.Desc)) // Build aclient, adapter := createAdapter(t) @@ -149,6 +150,44 @@ func TestMQTTSend(t *testing.T) { } } +func TestMQTTRecipient(t *testing.T) { + { + Desc(t, "Marshal / Unmarshal valid recipient") + rm := NewRecipient("topicup", "topicdown") + ru := new(mqttRecipient) + data, err := rm.MarshalBinary() + if err == nil { + err = ru.UnmarshalBinary(data) + } + checkErrors(t, nil, err) + } + + { + Desc(t, "Unmarshal from nil pointer") + rm := NewRecipient("topicup", "topicdown") + var ru *mqttRecipient + data, err := rm.MarshalBinary() + if err == nil { + err = ru.UnmarshalBinary(data) + } + checkErrors(t, pointer.String(ErrInvalidStructure), err) + } + + { + Desc(t, "Unmarshal nil data") + ru := new(mqttRecipient) + err := ru.UnmarshalBinary(nil) + checkErrors(t, pointer.String(ErrInvalidStructure), err) + } + + { + Desc(t, "Unmarshal wrong data") + ru := new(mqttRecipient) + err := ru.UnmarshalBinary([]byte{1, 2, 3, 4}) + checkErrors(t, pointer.String(ErrInvalidStructure), err) + } +} + // ----- TYPE utilities type testRecipient struct { Response []byte From 489f92074cf5765f69a78766299597bb9ef1e177 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 13:31:31 +0100 Subject: [PATCH 0704/2266] [refactor] Fix mqttRecipient and make tests pass --- refactor/adapters/mqtt/mqttRecipient.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/mqtt/mqttRecipient.go b/refactor/adapters/mqtt/mqttRecipient.go index 256e8a750..98f636b96 100644 --- a/refactor/adapters/mqtt/mqttRecipient.go +++ b/refactor/adapters/mqtt/mqttRecipient.go @@ -45,7 +45,12 @@ func (r mqttRecipient) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write(r.up) rw.Write(r.down) - return rw.Bytes() + + data, err := rw.Bytes() + if err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + return data, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface @@ -57,5 +62,10 @@ func (r *mqttRecipient) UnmarshalBinary(data []byte) error { rw := readwriter.New(data) rw.Read(func(data []byte) { r.up = string(data) }) rw.Read(func(data []byte) { r.down = string(data) }) - return rw.Err() + + err := rw.Err() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + return nil } From 1eb69337cb20a46a80d15622a0d6bceaaa245748 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 13:49:09 +0100 Subject: [PATCH 0705/2266] [refactor] Add GetRecipient() to adapter interfaces -> will be used in components --- refactor/adapters/http/http.go | 9 +++++++++ refactor/adapters/mqtt/mqtt.go | 9 +++++++++ refactor/adapters/udp/{adapter.go => udp.go} | 5 +++++ refactor/interfaces.go | 3 ++- 4 files changed, 25 insertions(+), 1 deletion(-) rename refactor/adapters/udp/{adapter.go => udp.go} (97%) diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index 6d4cb0bdd..b79b1c20f 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -209,6 +209,15 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err return <-chresp, nil } +// GetRecipient implements the core.Adapter interface +func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { + recipient := new(httpRecipient) + if err := recipient.UnmarshalBinary(raw); err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + return *recipient, nil +} + // Next implements the core.Adapter interface func (a *Adapter) Next() ([]byte, core.AckNacker, error) { p := <-a.packets diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 0845f86aa..3ef76a7e1 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -192,6 +192,15 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err return <-chresp, nil } +// GetRecipient implements the core.Adapter interface +func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { + recipient := new(mqttRecipient) + if err := recipient.UnmarshalBinary(raw); err != nil { + return nil, errors.New(ErrInvalidStructure, err) + } + return *recipient, nil +} + // Next implements the core.Adapter interface func (a *Adapter) Next() ([]byte, core.AckNacker, error) { p := <-a.packets diff --git a/refactor/adapters/udp/adapter.go b/refactor/adapters/udp/udp.go similarity index 97% rename from refactor/adapters/udp/adapter.go rename to refactor/adapters/udp/udp.go index 9d9cd8853..6d75af3cc 100644 --- a/refactor/adapters/udp/adapter.go +++ b/refactor/adapters/udp/udp.go @@ -107,6 +107,11 @@ func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]byte, error) { return nil, errors.New(ErrNotSupported, "Send not supported on udp adapter") } +// GetRecipient implements the core.Adapter interface +func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { + return nil, errors.New(ErrNotSupported, "GetRecipient not supported on udp adapter") +} + // Next implements the core.Adapter interface func (a *Adapter) Next() ([]byte, core.AckNacker, error) { msg := <-a.next diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 1a5d75e6d..d4b8fbd7d 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -24,9 +24,10 @@ type AckNacker interface { type Adapter interface { Send(p Packet, r ...Recipient) ([]byte, error) - //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) + GetRecipient(raw []byte) (Recipient, error) Next() ([]byte, AckNacker, error) NextRegistration() (Registration, AckNacker, error) + //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) } type Packet interface { From 6ab48b1be6059b3f8b1f3dcc3f64c57a5fff0a27 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 13:49:37 +0100 Subject: [PATCH 0706/2266] [refactor] Wait for connections before to start before launching test suite in http_test --- refactor/adapters/http/http_test.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go index 92083a791..ecf4d9b8d 100644 --- a/refactor/adapters/http/http_test.go +++ b/refactor/adapters/http/http_test.go @@ -56,28 +56,28 @@ func TestSend(t *testing.T) { recipients := []testRecipient{ testRecipient{ httpRecipient: httpRecipient{ - url: "0.0.0.0:3010", + url: "0.0.0.0:3110", method: "POST", }, Behavior: "AlwaysReject", }, testRecipient{ httpRecipient: httpRecipient{ - url: "0.0.0.0:3011", + url: "0.0.0.0:3111", method: "POST", }, Behavior: "AlwaysAccept", }, testRecipient{ httpRecipient: httpRecipient{ - url: "0.0.0.0:3012", + url: "0.0.0.0:3112", method: "POST", }, Behavior: "AlwaysReject", }, testRecipient{ httpRecipient: httpRecipient{ - url: "0.0.0.0:3013", + url: "0.0.0.0:3113", method: "POST", }, Behavior: "AlwaysReject", @@ -136,7 +136,7 @@ func TestSend(t *testing.T) { ctx := GetLogger(t, "Adapter") // Build - adapter, err := NewAdapter(3015, toHttpRecipient(recipients), ctx) + adapter, err := NewAdapter(3115, toHttpRecipient(recipients), ctx) if err != nil { panic(err) } @@ -144,6 +144,7 @@ func TestSend(t *testing.T) { for _, r := range recipients { servers = append(servers, genMockServer(r)) } + <-time.After(100 * time.Millisecond) for _, test := range tests { // Describe From d6cb76f693f3cb0d10626a275f516968fe6de817 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 14:00:38 +0100 Subject: [PATCH 0707/2266] [refactor] Add small delay for disconnecting mqtt client --- refactor/adapters/mqtt/mqtt_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index e232c0a8b..23c6f36e8 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -144,8 +144,8 @@ func TestMQTTSend(t *testing.T) { checkResponses(t, test.WantResponse, resp) // Clean - aclient.Disconnect(0) - sclient.Disconnect(0) + aclient.Disconnect(250) + sclient.Disconnect(250) <-time.After(time.Millisecond * 50) } } From 7fe4e2c3cad9a895bd207023bd750e838e7bcd1a Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 15:27:22 +0100 Subject: [PATCH 0708/2266] [refactor] Tweak a bit udp adapter to make it more consistent with http and mqtt one --- .../udp/handlers/build_utilities_test.go | 3 +- .../adapters/udp/handlers/conversions_test.go | 2 +- refactor/adapters/udp/handlers/semtech.go | 122 ++++++------ refactor/adapters/udp/udp.go | 177 ++++++------------ refactor/adapters/udp/udpAckNacker.go | 26 ++- 5 files changed, 130 insertions(+), 200 deletions(-) diff --git a/refactor/adapters/udp/handlers/build_utilities_test.go b/refactor/adapters/udp/handlers/build_utilities_test.go index 37a0cc79b..0be525088 100644 --- a/refactor/adapters/udp/handlers/build_utilities_test.go +++ b/refactor/adapters/udp/handlers/build_utilities_test.go @@ -78,10 +78,11 @@ func genAdapter(t *testing.T, port uint) (*udp.Adapter, chan interface{}) { // Logging ctx := GetLogger(t, "Adapter") - adapter, err := udp.NewAdapter(port, Semtech{}, ctx) + adapter, err := udp.NewAdapter(port, ctx) if err != nil { panic(err) } + adapter.Bind(Semtech{}) next := make(chan interface{}) go func() { for { diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/refactor/adapters/udp/handlers/conversions_test.go index 256278003..74522f176 100644 --- a/refactor/adapters/udp/handlers/conversions_test.go +++ b/refactor/adapters/udp/handlers/conversions_test.go @@ -38,7 +38,7 @@ func TestConvertRXPKPacket(t *testing.T) { type convertToTXPKTest struct { TXPK semtech.TXPK - CorePacket core.Packet + CorePacket core.RPacket WantError *string } diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index 2bf2389ab..b98a68860 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "reflect" "strings" + "time" . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" @@ -20,93 +21,90 @@ import ( type Semtech struct{} -// HandleNack implements the udp.Handler interface -func (s Semtech) HandleNack(chresp chan<- udp.HandlerMsg) { - close(chresp) // There is no notion of nack in the semtech protocol -} - -// HandleAck implements the udp.Handler interface -func (s Semtech) HandleAck(packet core.Packet, chresp chan<- udp.HandlerMsg) { - defer close(chresp) - - if packet == nil { - return - } - - // For the downlink, we have to send a PULL_RESP packet which hold a TXPK. - txpk, err := packet2txpk(packet) - if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} - return - } - - // Step 3, marshal the packet and send it to the gateway - raw, err := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{TXPK: &txpk}, - }.MarshalBinary() - - if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} - return - } - - chresp <- udp.HandlerMsg{Type: udp.HANDLER_RESP, Data: raw} -} - // HandleDatagram implements the udp.Handler interface -func (s Semtech) HandleDatagram(data []byte, chresp chan<- udp.HandlerMsg) { - defer close(chresp) +func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg udp.MsgUdp) { pkt := new(semtech.Packet) - err := pkt.UnmarshalBinary(data) + err := pkt.UnmarshalBinary(msg.Data) if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + // TODO Log error return } switch pkt.Identifier { case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK stats.MarkMeter("semtech_adapter.pull_data") - pullAck, err := semtech.Packet{ + data, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, Identifier: semtech.PULL_ACK, }.MarshalBinary() if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + // TODO Log error return } - chresp <- udp.HandlerMsg{Type: udp.HANDLER_RESP, Data: pullAck} + conn <- udp.MsgUdp{ + Addr: msg.Addr, + Data: data, + } case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component stats.MarkMeter("semtech_adapter.push_data") - pushAck, err := semtech.Packet{ + data, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, Identifier: semtech.PUSH_ACK, }.MarshalBinary() if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} + // TODO Log error return } - chresp <- udp.HandlerMsg{Type: udp.HANDLER_RESP, Data: pushAck} + conn <- udp.MsgUdp{ + Addr: msg.Addr, + Data: data, + } if pkt.Payload == nil { return } for _, rxpk := range pkt.Payload.RXPK { - pktOut, err := rxpk2packet(rxpk) - if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} - continue - } - rawPkt, err := pktOut.MarshalBinary() - if err != nil { - chresp <- udp.HandlerMsg{Type: udp.HANDLER_ERROR, Data: []byte(err.Error())} - continue - } - chresp <- udp.HandlerMsg{Type: udp.HANDLER_OUT, Data: rawPkt} + go func(rxpk semtech.RXPK) { + pktOut, err := rxpk2packet(rxpk) + if err != nil { + // TODO Log error + return + } + data, err := pktOut.MarshalBinary() + if err != nil { + // TODO Log error + return + } + chresp := make(chan udp.MsgRes) + packets <- udp.MsgReq{Data: data, Chresp: chresp} + select { + case resp := <-chresp: + pkt := new(core.RPacket) + if err := pkt.UnmarshalBinary(resp); err != nil { + return + } + txpk, err := packet2txpk(*pkt) + if err != nil { + // TODO Log error + return + } + + data, err := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{TXPK: &txpk}, + }.MarshalBinary() + if err != nil { + // TODO Log error + return + } + conn <- udp.MsgUdp{Addr: msg.Addr, Data: data} + case <-time.After(time.Second * 2): + } + }(rxpk) } default: } @@ -155,15 +153,9 @@ func rxpk2packet(p semtech.RXPK) (core.Packet, error) { return core.NewRPacket(payload, metadata), nil } -func packet2txpk(p core.Packet) (semtech.TXPK, error) { - // Interpret the packet as a Router Packet. - rPkt, ok := p.(core.RPacket) - if !ok { - return semtech.TXPK{}, errors.New(ErrInvalidStructure, "Unable to interpret packet as a RPacket") - } - +func packet2txpk(p core.RPacket) (semtech.TXPK, error) { // Step 1, convert the physical payload to a base64 string (without the padding) - raw, err := rPkt.Payload().MarshalBinary() + raw, err := p.Payload().MarshalBinary() if err != nil { return semtech.TXPK{}, errors.New(ErrInvalidStructure, err) } @@ -173,7 +165,7 @@ func packet2txpk(p core.Packet) (semtech.TXPK, error) { // Step 2, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. - metadataValue := reflect.ValueOf(rPkt.Metadata()) + metadataValue := reflect.ValueOf(p.Metadata()) metadataStruct := metadataValue.Type() txpkStruct := reflect.ValueOf(&txpk).Elem() for i := 0; i < metadataStruct.NumField(); i += 1 { diff --git a/refactor/adapters/udp/udp.go b/refactor/adapters/udp/udp.go index 6d75af3cc..fd1c6a496 100644 --- a/refactor/adapters/udp/udp.go +++ b/refactor/adapters/udp/udp.go @@ -15,74 +15,40 @@ import ( // Adapter represents a udp adapter which sends and receives packet via UDP type Adapter struct { - Handler - ctx log.Interface // Just a logger - conn chan UdpMsg // Channel used to manage response transmissions made by multiple goroutines - next chan OutMsg // Incoming valid packets are pushed to this channel and consume by an outsider - ack chan AckMsg // Channel used to consume ack or nack sent to the adapter + ctx log.Interface // Just a logger + conn chan MsgUdp // Channel used to manage response transmissions made by multiple goroutines + packets chan MsgReq // Incoming valid packets are pushed to this channel and consume by an outsider + handlers chan interface{} // Manage handlers, could be either a Handler or a []byte (new handler or handling action) } // Handler represents a datagram and packet handler used by the adapter to process packets type Handler interface { - // HandleAck handles a positive response to a transmitter - HandleAck(p core.Packet, resp chan<- HandlerMsg) - - // HandleNack handles a negative response to a transmitter - HandleNack(resp chan<- HandlerMsg) - - // HandleDatagram handles incoming datagram from a gateway transmitter to the network - HandleDatagram(data []byte, resp chan<- HandlerMsg) -} - -// HandlerMsg type materializes response messages emitted by the UdpHandler -type HandlerMsg struct { - Data []byte - Type HandlerMsgType -} - -type HandlerMsgType byte - -const ( - HANDLER_RESP HandlerMsgType = iota // A response towards the udp transmitter - HANDLER_OUT // A response towards the network - HANDLER_ERROR // An error during the process -) - -// AckMsg type materializes ack or nack messages flowing into the Ack channel -type AckMsg struct { - Packet core.Packet - Type AckMsgType - Addr *net.UDPAddr - Cherr chan<- error + // Handle handles incoming datagram from a gateway transmitter to the network + Handle(conn chan<- MsgUdp, chresp chan<- MsgReq, msg MsgUdp) } -type AckMsgType bool - -const ( - AN_ACK AckMsgType = true - AN_NACK AckMsgType = false -) - -// UdpMsg type materializes response messages transmitted towards existing recipients (commonly, gateways). -type UdpMsg struct { +// MsgUdp type materializes response messages transmitted towards existing recipients (commonly, gateways). +type MsgUdp struct { Data []byte // The raw byte sequence that has to be sent - Conn *net.UDPConn // Provide if you intent to change the current adapter connection Addr *net.UDPAddr // The target recipient address } // OutMsg type materializes valid uplink messages coming from a given recipient -type OutMsg struct { - Data []byte // The actual message - Addr *net.UDPAddr // The address of the source emitter +type MsgReq struct { + Data []byte // The actual message + Chresp chan MsgRes // A dedicated response channel } +// Message sent through the response channel of MsgReq +type MsgRes []byte // The actual message + // NewAdapter constructs and allocates a new udp adapter -func NewAdapter(port uint, handler Handler, ctx log.Interface) (*Adapter, error) { +func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a := Adapter{ - Handler: handler, - ctx: ctx, - conn: make(chan UdpMsg), - next: make(chan OutMsg), + ctx: ctx, + conn: make(chan MsgUdp), + packets: make(chan MsgReq), + handlers: make(chan interface{}), } // Create the udp connection and start listening with a goroutine @@ -94,10 +60,9 @@ func NewAdapter(port uint, handler Handler, ctx log.Interface) (*Adapter, error) return nil, errors.New(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) } - go a.monitorConnection() - go a.consumeAck() - a.conn <- UdpMsg{Conn: udpConn} - go a.listen(udpConn) // Terminates when the connection is closed + go a.monitorConnection(udpConn) + go a.monitorHandlers() + go a.listen(udpConn) return &a, nil } @@ -114,10 +79,8 @@ func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { // Next implements the core.Adapter interface func (a *Adapter) Next() ([]byte, core.AckNacker, error) { - msg := <-a.next - return msg.Data, udpAckNacker{ - Addr: msg.Addr, - }, nil + msg := <-a.packets + return msg.Data, udpAckNacker{Chresp: msg.Chresp}, nil } // NextRegistration implements the core.Adapter interface @@ -125,7 +88,13 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) return udpRegistration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") } -// listen Handle incoming packets and forward them. Runs in its own goroutine. +func (a *Adapter) Bind(h Handler) { + a.handlers <- h +} + +// listen Handle incoming packets and forward them. +// +// Runs in its own goroutine. func (a *Adapter) listen(conn *net.UDPConn) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") @@ -138,30 +107,19 @@ func (a *Adapter) listen(conn *net.UDPConn) { } a.ctx.Debug("Incoming datagram") - chresp := make(chan HandlerMsg) - go a.HandleDatagram(buf[:n], chresp) - a.handleResp(addr, chresp) + a.handlers <- MsgUdp{Addr: addr, Data: buf[:n]} } } // monitorConnection manages udpConnection of the adapter and send message through that connection // // That function executes into a single goroutine and is the only one allowed to write UDP messages. -// Doing this makes sure that only 1 goroutine is interacting with the connection. It thereby allows -// the connection to be replaced at any moment (in case of failure for instance) without disturbing -// the ongoing process. -func (a *Adapter) monitorConnection() { - var udpConn *net.UDPConn +// Doing this makes sure that only 1 goroutine is interacting with the connection. +// +// Runs in its own goroutine +func (a *Adapter) monitorConnection(udpConn *net.UDPConn) { for msg := range a.conn { - if msg.Conn != nil { // Change the connection - if udpConn != nil { - a.ctx.Debug("Define new UDP connection") - udpConn.Close() - } - udpConn = msg.Conn - } - - if udpConn != nil && msg.Data != nil { // Send the given udp message + if msg.Data != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.Data, msg.Addr); err != nil { a.ctx.WithError(err).Error("Error while sending UDP message") } @@ -172,53 +130,24 @@ func (a *Adapter) monitorConnection() { } } -// handleResp consumes message from chresp and forward them to the adapter via the Out or Udp -// channel. +// monitorHandlers manages handler registration and execution concurrently. One can pass a new +// handler through the handlers channel to declare a new one or, send directly data through the +// channel to ask every defined handler to handle them. // -// The function is called each time a chan HandlerMsg is created (meaning that we need to handle an -// uplink or a response) to handle the response(s) coming from the message handler. -func (a *Adapter) handleResp(addr *net.UDPAddr, chresp <-chan HandlerMsg) error { - for msg := range chresp { - switch msg.Type { - case HANDLER_RESP: - a.conn <- UdpMsg{ - Data: msg.Data, - Addr: addr, +// Runs in its own goroutine +func (a *Adapter) monitorHandlers() { + var handlers []Handler + + for msg := range a.handlers { + switch msg.(type) { + case Handler: + handlers = append(handlers, msg.(Handler)) + case MsgUdp: + for _, h := range handlers { + go func(h Handler, msg MsgUdp) { + h.Handle(a.conn, a.packets, msg) + }(h, msg.(MsgUdp)) } - case HANDLER_OUT: - a.next <- OutMsg{ - Data: msg.Data, - Addr: addr, - } - case HANDLER_ERROR: - err := fmt.Errorf(string(msg.Data)) - a.ctx.WithError(err).Error("Unable to handle response") - return errors.New(ErrFailedOperation, err) - default: - err := errors.New(ErrFailedOperation, "Internal unexpected error while handling response") - a.ctx.Error(err.Error()) - return err - } - } - return nil -} - -// consumeAck consumes messages from the Ack channel and forward them to handleResp -// -// The function is launched in its own goroutine and run concurrently with other consumers of the -// adapter. It basically pipes acknowledgement to the right channels after having derouted the -// processing to the handler. -func (a *Adapter) consumeAck() { - for msg := range a.ack { - chresp := make(chan HandlerMsg) - switch msg.Type { - case AN_ACK: - go a.HandleAck(msg.Packet, chresp) - case AN_NACK: - go a.HandleNack(chresp) } - err := a.handleResp(msg.Addr, chresp) - msg.Cherr <- err - close(msg.Cherr) } } diff --git a/refactor/adapters/udp/udpAckNacker.go b/refactor/adapters/udp/udpAckNacker.go index 2a4c7a644..883adb8d4 100644 --- a/refactor/adapters/udp/udpAckNacker.go +++ b/refactor/adapters/udp/udpAckNacker.go @@ -4,27 +4,35 @@ package udp import ( - "net" + "time" + . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" ) // udpAckNacker represents an AckNacker for a udp adapter type udpAckNacker struct { - Chack chan<- AckMsg - Addr *net.UDPAddr // The actual udp address related to that + Chresp chan<- MsgRes } // Ack implements the core.Adapter interface func (an udpAckNacker) Ack(p core.Packet) error { - cherr := make(chan error) - an.Chack <- AckMsg{Type: AN_ACK, Addr: an.Addr, Packet: p, Cherr: cherr} - return <-cherr + defer close(an.Chresp) + data, err := p.MarshalBinary() + if err != nil { + return errors.New(ErrInvalidStructure, err) + } + select { + case an.Chresp <- MsgRes(data): + return nil + case <-time.After(time.Millisecond * 50): + return errors.New(ErrFailedOperation, "Unable to send ack") + } } // Ack implements the core.Adapter interface func (an udpAckNacker) Nack() error { - cherr := make(chan error) - an.Chack <- AckMsg{Type: AN_NACK, Addr: an.Addr, Packet: nil} - return <-cherr + defer close(an.Chresp) + return nil } From a4d7dd4e21ce2447f17f5772cb501f28761e7c9c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 17:19:19 +0100 Subject: [PATCH 0709/2266] [refactor] Moved error natures in utils/errors and impact changes --- refactor/adapters/http/handlers/collect.go | 9 +++-- .../adapters/http/handlers/collect_test.go | 10 +++--- .../adapters/http/handlers/helpers_test.go | 5 ++- refactor/adapters/http/handlers/pubsub.go | 19 +++++----- .../http/handlers/pubsubRegistration.go | 3 +- .../adapters/http/handlers/pubsub_test.go | 12 +++---- refactor/adapters/http/http.go | 23 ++++++------ refactor/adapters/http/httpAckNacker.go | 9 +++-- refactor/adapters/http/httpRegistration.go | 9 +++-- refactor/adapters/http/http_test.go | 9 +++-- refactor/adapters/http/regAckNacker.go | 7 ++-- refactor/adapters/mqtt/mqtt.go | 21 ++++++----- refactor/adapters/mqtt/mqttAckNacker.go | 9 +++-- refactor/adapters/mqtt/mqttRecipient.go | 15 ++------ refactor/adapters/mqtt/mqtt_test.go | 19 +++++----- .../adapters/udp/handlers/conversions_test.go | 6 ++-- refactor/adapters/udp/handlers/semtech.go | 9 +++-- .../adapters/udp/handlers/semtech_test.go | 7 ++-- refactor/adapters/udp/udp.go | 9 +++-- refactor/adapters/udp/udpAckNacker.go | 5 ++- refactor/adapters/udp/udpRegistration.go | 9 +++-- refactor/check_utilities_test.go | 2 +- refactor/metadata.go | 7 ++-- refactor/rpacket.go | 21 ++++++----- utils/errors/errors.go | 15 ++++++-- utils/readwriter/readwriter.go | 8 +++-- utils/storage/storage.go | 35 +++++++++---------- 27 files changed, 150 insertions(+), 162 deletions(-) diff --git a/refactor/adapters/http/handlers/collect.go b/refactor/adapters/http/handlers/collect.go index 9212606f6..2b3734980 100644 --- a/refactor/adapters/http/handlers/collect.go +++ b/refactor/adapters/http/handlers/collect.go @@ -7,7 +7,6 @@ import ( "io" "net/http" - . "github.com/TheThingsNetwork/ttn/core/errors" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -32,7 +31,7 @@ func (p Collect) Url() string { func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { // Check the http method if req.Method != "POST" { - err := errors.New(ErrInvalidStructure, "Unreckognized HTTP method. Please use [POST] to transfer a packet") + err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [POST] to transfer a packet") w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte(err.Error())) return @@ -50,7 +49,7 @@ func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- chpkt <- PktReq{Packet: data, Chresp: chresp} r, ok := <-chresp if !ok { - err := errors.New(ErrFailedOperation, "Core server not responding") + err := errors.New(errors.Operational, "Core server not responding") BadRequest(w, err.Error()) return } @@ -62,14 +61,14 @@ func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- func (p Collect) parse(req *http.Request) ([]byte, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/octet-stream" { - return nil, errors.New(ErrInvalidStructure, "Received invalid content-type in request") + return nil, errors.New(errors.Structural, "Received invalid content-type in request") } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } return body[:n], nil } diff --git a/refactor/adapters/http/handlers/collect_test.go b/refactor/adapters/http/handlers/collect_test.go index 2eef025ed..d84e90cf5 100644 --- a/refactor/adapters/http/handlers/collect_test.go +++ b/refactor/adapters/http/handlers/collect_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -34,7 +34,7 @@ func TestCollect(t *testing.T) { Method: "POST", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusBadRequest, WantPacket: nil, WantError: nil, @@ -46,7 +46,7 @@ func TestCollect(t *testing.T) { Method: "PUT", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusMethodNotAllowed, WantPacket: nil, WantError: nil, @@ -58,7 +58,7 @@ func TestCollect(t *testing.T) { Method: "POST", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusNotFound, WantPacket: []byte("Patate"), WantError: nil, @@ -71,7 +71,7 @@ func TestCollect(t *testing.T) { ShouldAck: true, AckPacket: testPacket{payload: ""}, - WantContent: ErrFailedOperation, + WantContent: string(errors.Operational), WantStatusCode: http.StatusBadRequest, WantPacket: []byte("Patate"), WantError: nil, diff --git a/refactor/adapters/http/handlers/helpers_test.go b/refactor/adapters/http/handlers/helpers_test.go index ee690d40e..6deeaae6f 100644 --- a/refactor/adapters/http/handlers/helpers_test.go +++ b/refactor/adapters/http/handlers/helpers_test.go @@ -13,7 +13,6 @@ import ( "testing" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -28,7 +27,7 @@ type testPacket struct { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p testPacket) MarshalBinary() ([]byte, error) { if p.payload == "" { - return nil, errors.New(ErrInvalidStructure, "Fake error") + return nil, errors.New(errors.Structural, "Fake error") } return []byte(p.payload), nil @@ -178,7 +177,7 @@ func checkErrors(t *testing.T, want *string, got error) { return } - if got.(errors.Failure).Nature == *want { + if got.(errors.Failure).Nature == errors.Nature(*want) { Ok(t, "Check errors") return } diff --git a/refactor/adapters/http/handlers/pubsub.go b/refactor/adapters/http/handlers/pubsub.go index 16a05222d..18d8aa52f 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/refactor/adapters/http/handlers/pubsub.go @@ -11,7 +11,6 @@ import ( "regexp" "strings" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -52,7 +51,7 @@ func (p PubSub) Url() string { func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { // Check the http method if req.Method != "PUT" { - err := errors.New(ErrInvalidStructure, "Unreckognized HTTP method. Please use [PUT] to register a device") + err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [PUT] to register a device") w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte(err.Error())) return @@ -81,25 +80,25 @@ func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- func (p PubSub) parse(req *http.Request) (core.Registration, error) { // Check Content-type if req.Header.Get("Content-Type") != "application/json" { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") + return pubSubRegistration{}, errors.New(errors.Structural, "Received invalid content-type in request") } // Check the query parameter reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{16})$") // 8-bytes, hex-encoded -> 16 chars query := reg.FindStringSubmatch(req.RequestURI) if len(query) < 2 { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect end-device address format") + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect end-device address format") } devEUI, err := hex.DecodeString(query[1]) if err != nil { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, err) + return pubSubRegistration{}, errors.New(errors.Structural, err) } // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) if err != nil && err != io.EOF { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, err) + return pubSubRegistration{}, errors.New(errors.Structural, err) } params := &struct { AppEUI string `json:"app_eui"` @@ -107,23 +106,23 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Unable to unmarshal the request body") + return pubSubRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") } // Verify each request parameter nwkSKey, err := hex.DecodeString(params.NwkSKey) if err != nil || len(nwkSKey) != 16 { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect network session key") + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect network session key") } appEUI, err := hex.DecodeString(params.AppEUI) if err != nil || len(appEUI) != 8 { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect application eui") + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") } params.Url = strings.Trim(params.Url, " ") if len(params.Url) <= 0 { - return pubSubRegistration{}, errors.New(ErrInvalidStructure, "Incorrect application url") + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application url") } // Create actual registration diff --git a/refactor/adapters/http/handlers/pubsubRegistration.go b/refactor/adapters/http/handlers/pubsubRegistration.go index 0b279e73f..ddf9b76a4 100644 --- a/refactor/adapters/http/handlers/pubsubRegistration.go +++ b/refactor/adapters/http/handlers/pubsubRegistration.go @@ -4,7 +4,6 @@ package handlers import ( - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -31,7 +30,7 @@ func (r pubSubRegistration) AppEUI() (lorawan.EUI64, error) { // AppSKey implements the core.Registration interface func (r pubSubRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "AppSKey noy supported on pubsub registration") + return lorawan.AES128Key{}, errors.New(errors.Implementation, "AppSKey noy supported on pubsub registration") } // DevEUI implements the core.Registration interface diff --git a/refactor/adapters/http/handlers/pubsub_test.go b/refactor/adapters/http/handlers/pubsub_test.go index 92eda2fd2..29b500161 100644 --- a/refactor/adapters/http/handlers/pubsub_test.go +++ b/refactor/adapters/http/handlers/pubsub_test.go @@ -9,9 +9,9 @@ import ( "testing" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -39,7 +39,7 @@ func TestPubSub(t *testing.T) { DevEUI: "0000000011223344", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusBadRequest, WantRegistration: nil, WantError: nil, @@ -52,7 +52,7 @@ func TestPubSub(t *testing.T) { DevEUI: "0000000011223344", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusBadRequest, WantRegistration: nil, WantError: nil, @@ -65,7 +65,7 @@ func TestPubSub(t *testing.T) { DevEUI: "0000000011223344", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusMethodNotAllowed, WantRegistration: nil, WantError: nil, @@ -78,7 +78,7 @@ func TestPubSub(t *testing.T) { DevEUI: "12345678", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusBadRequest, WantRegistration: nil, WantError: nil, @@ -91,7 +91,7 @@ func TestPubSub(t *testing.T) { DevEUI: "0000000001020304", ShouldAck: false, - WantContent: ErrInvalidStructure, + WantContent: string(errors.Structural), WantStatusCode: http.StatusConflict, WantRegistration: pubSubRegistration{ recipient: NewHttpRecipient("url", "PUT"), diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index b79b1c20f..eda710798 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -11,7 +11,6 @@ import ( "net/http" "sync" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" @@ -78,7 +77,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err data, err := p.MarshalBinary() if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } // Try to define a more helpful context @@ -106,7 +105,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err nb = len(recipients) isBroadcast = true if nb == 0 { - return nil, errors.New(ErrFailedOperation, "No recipient found") + return nil, errors.New(errors.Structural, "No recipient found") } } @@ -135,7 +134,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err buf.Write(data) resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Url()), "application/octet-stream", buf) if err != nil { - cherr <- errors.New(ErrFailedOperation, err) + cherr <- errors.New(errors.Operational, err) return } defer func() { @@ -152,7 +151,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err ctx.Debug("Recipient registered for packet") data, err := ioutil.ReadAll(resp.Body) if err != nil && err != io.EOF { - cherr <- errors.New(ErrFailedOperation, err) + cherr <- errors.New(errors.Operational, err) return } chresp <- data @@ -169,9 +168,9 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } case http.StatusNotFound: ctx.Debug("Recipient not interested in packet") - cherr <- errors.New(ErrWrongBehavior, "Recipient not interested") + cherr <- errors.New(errors.Behavioural, "Recipient not interested") default: - cherr <- errors.New(ErrFailedOperation, fmt.Sprintf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode)) + cherr <- errors.New(errors.Operational, fmt.Sprintf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode)) } }(recipient) } @@ -187,7 +186,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err var errored uint8 for i := 0; i < len(cherr); i += 1 { err := <-cherr - if err.(errors.Failure).Nature != ErrWrongBehavior { + if err.(errors.Failure).Nature != errors.Behavioural { errored += 1 ctx.WithError(err).Error("POST Failed") } @@ -195,15 +194,15 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Collect response if len(chresp) > 1 { - return nil, errors.New(ErrWrongBehavior, "Received too many positive answers") + return nil, errors.New(errors.Behavioural, "Received too many positive answers") } if len(chresp) == 0 && errored != 0 { - return nil, errors.New(ErrFailedOperation, "No positive response from recipients but got unexpected answer") + return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answer") } if len(chresp) == 0 && errored == 0 { - return nil, errors.New(ErrWrongBehavior, "No recipient gave a positive answer") + return nil, errors.New(errors.Behavioural, "No recipient gave a positive answer") } return <-chresp, nil @@ -213,7 +212,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { recipient := new(httpRecipient) if err := recipient.UnmarshalBinary(raw); err != nil { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } return *recipient, nil } diff --git a/refactor/adapters/http/httpAckNacker.go b/refactor/adapters/http/httpAckNacker.go index 95d44a0f2..0cda08a89 100644 --- a/refactor/adapters/http/httpAckNacker.go +++ b/refactor/adapters/http/httpAckNacker.go @@ -7,7 +7,6 @@ import ( "net/http" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -31,7 +30,7 @@ func (an httpAckNacker) Ack(p core.Packet) error { data, err := p.MarshalBinary() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } select { @@ -41,7 +40,7 @@ func (an httpAckNacker) Ack(p core.Packet) error { }: return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrFailedOperation, "No response was given to the acknacker") + return errors.New(errors.Operational, "No response was given to the acknacker") } } @@ -55,10 +54,10 @@ func (an httpAckNacker) Nack() error { select { case an.Chresp <- MsgRes{ StatusCode: http.StatusNotFound, - Content: []byte(ErrInvalidStructure), + Content: []byte(errors.Structural), }: case <-time.After(time.Millisecond * 50): - return errors.New(ErrFailedOperation, "No response was given to the acknacker") + return errors.New(errors.Operational, "No response was given to the acknacker") } return nil } diff --git a/refactor/adapters/http/httpRegistration.go b/refactor/adapters/http/httpRegistration.go index 987aa57f6..2dcbf4b20 100644 --- a/refactor/adapters/http/httpRegistration.go +++ b/refactor/adapters/http/httpRegistration.go @@ -5,7 +5,6 @@ package http import ( core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/errors" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -22,23 +21,23 @@ func (r httpRegistration) Recipient() core.Recipient { // AppEUI implements the core.Registration interface func (r httpRegistration) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(ErrNotSupported, "AppEUI not supported on http registration") + return lorawan.EUI64{}, errors.New(errors.Implementation, "AppEUI not supported on http registration") } // DevEUI implements the core.Registration interface func (r httpRegistration) DevEUI() (lorawan.EUI64, error) { if r.devEUI == nil { - return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "DevEUI not accessible on this registration") + return lorawan.EUI64{}, errors.New(errors.Implementation, "DevEUI not accessible on this registration") } return *r.devEUI, nil } // AppSKey implements the core.Registration interface func (r httpRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "AppSKey not supported on http registration") + return lorawan.AES128Key{}, errors.New(errors.Implementation, "AppSKey not supported on http registration") } // NwkSKey implements the core.Registration interface func (r httpRegistration) NwkSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return lorawan.AES128Key{}, errors.New(errors.Implementation, "NextRegistration not supported on udp adapter") } diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go index ecf4d9b8d..68e7a0beb 100644 --- a/refactor/adapters/http/http_test.go +++ b/refactor/adapters/http/http_test.go @@ -10,7 +10,6 @@ import ( "testing" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -36,7 +35,7 @@ type testPacket struct { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p testPacket) MarshalBinary() ([]byte, error) { if p.payload == "" { - return nil, errors.New(ErrInvalidStructure, "Fake error") + return nil, errors.New(errors.Structural, "Fake error") } return []byte(p.payload), nil @@ -121,14 +120,14 @@ func TestSend(t *testing.T) { Packet: testPacket{}, WantRegistrations: nil, WantPayload: "", - WantError: pointer.String(ErrInvalidStructure), + WantError: pointer.String(string(errors.Structural)), }, { // Broadcast an invalid packet Recipients: nil, Packet: testPacket{}, WantRegistrations: nil, WantPayload: "", - WantError: pointer.String(ErrInvalidStructure), + WantError: pointer.String(string(errors.Structural)), }, } @@ -261,7 +260,7 @@ func checkErrors(t *testing.T, want *string, got error) { return } - if got.(errors.Failure).Nature == *want { + if got.(errors.Failure).Nature == errors.Nature(*want) { Ok(t, "Check errors") return } diff --git a/refactor/adapters/http/regAckNacker.go b/refactor/adapters/http/regAckNacker.go index 89c1f3c08..5f3bb51a5 100644 --- a/refactor/adapters/http/regAckNacker.go +++ b/refactor/adapters/http/regAckNacker.go @@ -7,7 +7,6 @@ import ( "net/http" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -28,7 +27,7 @@ func (r regAckNacker) Ack(p core.Packet) error { case r.Chresp <- MsgRes{StatusCode: http.StatusAccepted}: return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrFailedOperation, "No response was given to the acknacker") + return errors.New(errors.Operational, "No response was given to the acknacker") } } @@ -40,10 +39,10 @@ func (r regAckNacker) Nack() error { select { case r.Chresp <- MsgRes{ StatusCode: http.StatusConflict, - Content: []byte(errors.New(ErrInvalidStructure, "Unable to register device").Error()), + Content: []byte(errors.New(errors.Structural, "Unable to register device").Error()), }: return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrFailedOperation, "No response was given to the acknacker") + return errors.New(errors.Operational, "No response was given to the acknacker") } } diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 3ef76a7e1..5ef6242a1 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -9,7 +9,6 @@ import ( "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" @@ -79,7 +78,7 @@ func NewClient(id string, broker string, scheme Scheme) (*MQTT.Client, error) { opts.SetClientID(id) client := MQTT.NewClient(opts) if token := client.Connect(); token.Wait() && token.Error() != nil { - return nil, errors.New(ErrFailedOperation, token.Error()) + return nil, errors.New(errors.Operational, token.Error()) } return client, nil } @@ -93,7 +92,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err data, err := p.MarshalBinary() if err != nil { a.ctx.WithError(err).Warn("Invalid Packet") - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } a.ctx.Debug("Sending Packet") @@ -109,7 +108,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Get the actual recipient recipient, ok := r.(MqttRecipient) if !ok { - err := errors.New(ErrInvalidStructure, "Unable to interpret recipient as mqttRecipient") + err := errors.New(errors.Structural, "Unable to interpret recipient as mqttRecipient") a.ctx.WithField("recipient", r).Warn(err.Error()) cherr <- err continue @@ -121,7 +120,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err chdown <- msg.Payload() }) if token.Wait() && token.Error() != nil { - err := errors.New(ErrFailedOperation, "Unable to subscribe to down topic") + err := errors.New(errors.Operational, "Unable to subscribe to down topic") a.ctx.WithField("recipient", recipient).Warn(err.Error()) cherr <- err close(chdown) @@ -138,7 +137,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err token := a.Publish(recipient.TopicUp(), 2, false, data) if token.Wait() && token.Error() != nil { ctx.WithError(token.Error()).Error("Unable to publish") - cherr <- errors.New(ErrFailedOperation, token.Error()) + cherr <- errors.New(errors.Operational, token.Error()) return } }(recipient) @@ -178,15 +177,15 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Collect response if len(chresp) > 1 { - return nil, errors.New(ErrWrongBehavior, "Received too many positive answers") + return nil, errors.New(errors.Behavioural, "Received too many positive answers") } if len(chresp) == 0 && errored != 0 { - return nil, errors.New(ErrFailedOperation, "No positive response from recipients but got unexpected answers") + return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answers") } if len(chresp) == 0 && errored == 0 { - return nil, errors.New(ErrWrongBehavior, "No recipient gave a positive answer") + return nil, errors.New(errors.Behavioural, "No recipient gave a positive answer") } return <-chresp, nil @@ -196,7 +195,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { recipient := new(mqttRecipient) if err := recipient.UnmarshalBinary(raw); err != nil { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } return *recipient, nil } @@ -222,7 +221,7 @@ func (a *Adapter) Bind(h Handler) error { }) if token.Wait() && token.Error() != nil { ctx.WithError(token.Error()).Error("Unable to Subscribe") - return errors.New(ErrFailedOperation, token.Error()) + return errors.New(errors.Operational, token.Error()) } return nil } diff --git a/refactor/adapters/mqtt/mqttAckNacker.go b/refactor/adapters/mqtt/mqttAckNacker.go index afb325fdc..ec8199213 100644 --- a/refactor/adapters/mqtt/mqttAckNacker.go +++ b/refactor/adapters/mqtt/mqttAckNacker.go @@ -6,7 +6,6 @@ package mqtt import ( "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -29,18 +28,22 @@ func (an mqttAckNacker) Ack(p core.Packet) error { data, err := p.MarshalBinary() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } select { case an.Chresp <- MsgRes(data): return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrFailedOperation, "No response was given to the acknacker") + return errors.New(errors.Operational, "No response was given to the acknacker") } } // Nack implements the core.AckNacker interface func (an mqttAckNacker) Nack() error { + if an.Chresp == nil { + return nil + } + defer close(an.Chresp) return nil } diff --git a/refactor/adapters/mqtt/mqttRecipient.go b/refactor/adapters/mqtt/mqttRecipient.go index 98f636b96..316978eae 100644 --- a/refactor/adapters/mqtt/mqttRecipient.go +++ b/refactor/adapters/mqtt/mqttRecipient.go @@ -6,7 +6,6 @@ package mqtt import ( "encoding" - . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" ) @@ -46,26 +45,18 @@ func (r mqttRecipient) MarshalBinary() ([]byte, error) { rw.Write(r.up) rw.Write(r.down) - data, err := rw.Bytes() - if err != nil { - return nil, errors.New(ErrInvalidStructure, err) - } - return data, nil + return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (r *mqttRecipient) UnmarshalBinary(data []byte) error { if r == nil { - return errors.New(ErrInvalidStructure, "Cannot unmarshal nil structure") + return errors.New(errors.Structural, "Cannot unmarshal nil structure") } rw := readwriter.New(data) rw.Read(func(data []byte) { r.up = string(data) }) rw.Read(func(data []byte) { r.down = string(data) }) - err := rw.Err() - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - return nil + return rw.Err() } diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 23c6f36e8..547bdf8e1 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -10,7 +10,6 @@ import ( "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -42,7 +41,7 @@ func TestMQTTSend(t *testing.T) { WantData: []byte("TheThingsNetwork"), WantResponse: nil, - WantError: pointer.String(ErrWrongBehavior), + WantError: pointer.String(string(errors.Behavioural)), }, { Desc: "invalid packet | 1 recipient | No response", @@ -57,7 +56,7 @@ func TestMQTTSend(t *testing.T) { WantData: nil, WantResponse: nil, - WantError: pointer.String(ErrInvalidStructure), + WantError: pointer.String(string(errors.Structural)), }, { Desc: "1 packet | 2 recipient | No response", @@ -77,7 +76,7 @@ func TestMQTTSend(t *testing.T) { WantData: []byte("TheThingsNetwork"), WantResponse: nil, - WantError: pointer.String(ErrWrongBehavior), + WantError: pointer.String(string(errors.Behavioural)), }, { Desc: "1 packet | 2 recipients | #1 answer ", @@ -117,7 +116,7 @@ func TestMQTTSend(t *testing.T) { WantData: []byte("TheThingsNetwork"), WantResponse: nil, - WantError: pointer.String(ErrWrongBehavior), + WantError: pointer.String(string(errors.Behavioural)), }, } @@ -170,21 +169,21 @@ func TestMQTTRecipient(t *testing.T) { if err == nil { err = ru.UnmarshalBinary(data) } - checkErrors(t, pointer.String(ErrInvalidStructure), err) + checkErrors(t, pointer.String(string(errors.Structural)), err) } { Desc(t, "Unmarshal nil data") ru := new(mqttRecipient) err := ru.UnmarshalBinary(nil) - checkErrors(t, pointer.String(ErrInvalidStructure), err) + checkErrors(t, pointer.String(string(errors.Structural)), err) } { Desc(t, "Unmarshal wrong data") ru := new(mqttRecipient) err := ru.UnmarshalBinary([]byte{1, 2, 3, 4}) - checkErrors(t, pointer.String(ErrInvalidStructure), err) + checkErrors(t, pointer.String(string(errors.Structural)), err) } } @@ -202,7 +201,7 @@ type testPacket struct { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p testPacket) MarshalBinary() ([]byte, error) { if p.payload == nil { - return nil, errors.New(ErrInvalidStructure, "Fake error") + return nil, errors.New(errors.Structural, "Fake error") } return p.payload, nil @@ -295,7 +294,7 @@ func checkErrors(t *testing.T, want *string, got error) { return } - if got.(errors.Failure).Nature == *want { + if got.(errors.Failure).Nature == errors.Nature(*want) { Ok(t, "Check errors") return } diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/refactor/adapters/udp/handlers/conversions_test.go index 74522f176..121500201 100644 --- a/refactor/adapters/udp/handlers/conversions_test.go +++ b/refactor/adapters/udp/handlers/conversions_test.go @@ -7,9 +7,9 @@ import ( "reflect" "testing" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -25,7 +25,7 @@ func TestConvertRXPKPacket(t *testing.T) { tests := []convertRXPKTest{ genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(ErrInvalidStructure)}), + genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(string(errors.Structural))}), } for _, test := range tests { @@ -51,7 +51,7 @@ func TestConvertTXPKPacket(t *testing.T) { convertToTXPKTest{ CorePacket: core.NewRPacket(lorawan.PHYPayload{}, core.Metadata{}), TXPK: semtech.TXPK{}, - WantError: pointer.String(ErrInvalidStructure), + WantError: pointer.String(string(errors.Structural)), }, } diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index b98a68860..437de4522 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -9,7 +9,6 @@ import ( "strings" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" "github.com/TheThingsNetwork/ttn/semtech" @@ -113,7 +112,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u func rxpk2packet(p semtech.RXPK) (core.Packet, error) { // First, we have to get the physical payload which is encoded in the Data field if p.Data == nil { - return nil, errors.New(ErrInvalidStructure, "There's no data in the packet") + return nil, errors.New(errors.Structural, "There's no data in the packet") } // RXPK Data are base64 encoded, yet without the trailing "==" if any..... @@ -127,12 +126,12 @@ func rxpk2packet(p semtech.RXPK) (core.Packet, error) { raw, err := base64.StdEncoding.DecodeString(encoded) if err != nil { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } payload := lorawan.NewPHYPayload(true) if err = payload.UnmarshalBinary(raw); err != nil { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } // Then, we interpret every other known field as a metadata and store them into an appropriate @@ -157,7 +156,7 @@ func packet2txpk(p core.RPacket) (semtech.TXPK, error) { // Step 1, convert the physical payload to a base64 string (without the padding) raw, err := p.Payload().MarshalBinary() if err != nil { - return semtech.TXPK{}, errors.New(ErrInvalidStructure, err) + return semtech.TXPK{}, errors.New(errors.Structural, err) } data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") diff --git a/refactor/adapters/udp/handlers/semtech_test.go b/refactor/adapters/udp/handlers/semtech_test.go index 9b7001adc..cfc8801c2 100644 --- a/refactor/adapters/udp/handlers/semtech_test.go +++ b/refactor/adapters/udp/handlers/semtech_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" "github.com/TheThingsNetwork/ttn/semtech" @@ -21,14 +20,14 @@ func TestSend(t *testing.T) { Desc(t, "Send is not supported") adapter, _ := genAdapter(t, 33000) _, err := adapter.Send(core.RPacket{}) - checkErrors(t, pointer.String(ErrNotSupported), err) + checkErrors(t, pointer.String(string(errors.Implementation)), err) } func TestNextRegistration(t *testing.T) { Desc(t, "Next registration is not supported") adapter, _ := genAdapter(t, 33001) _, _, err := adapter.NextRegistration() - checkErrors(t, pointer.String(ErrNotSupported), err) + checkErrors(t, pointer.String(string(errors.Implementation)), err) } func TestNext(t *testing.T) { @@ -130,7 +129,7 @@ func checkErrors(t *testing.T, want *string, got error) { return } - if got.(errors.Failure).Nature == *want { + if got.(errors.Failure).Nature == errors.Nature(*want) { Ok(t, "Check errors") return } diff --git a/refactor/adapters/udp/udp.go b/refactor/adapters/udp/udp.go index fd1c6a496..ac5df1cb7 100644 --- a/refactor/adapters/udp/udp.go +++ b/refactor/adapters/udp/udp.go @@ -8,7 +8,6 @@ import ( "net" core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/errors" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -57,7 +56,7 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a.ctx.WithField("port", port).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.New(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) + return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid port %v", port)) } go a.monitorConnection(udpConn) @@ -69,12 +68,12 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { // Send implements the core.Adapter interface. Not implemented for the udp adapter. func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]byte, error) { - return nil, errors.New(ErrNotSupported, "Send not supported on udp adapter") + return nil, errors.New(errors.Implementation, "Send not supported on udp adapter") } // GetRecipient implements the core.Adapter interface func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { - return nil, errors.New(ErrNotSupported, "GetRecipient not supported on udp adapter") + return nil, errors.New(errors.Implementation, "GetRecipient not supported on udp adapter") } // Next implements the core.Adapter interface @@ -85,7 +84,7 @@ func (a *Adapter) Next() ([]byte, core.AckNacker, error) { // NextRegistration implements the core.Adapter interface func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return udpRegistration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on udp adapter") + return udpRegistration{}, nil, errors.New(errors.Implementation, "NextRegistration not supported on udp adapter") } func (a *Adapter) Bind(h Handler) { diff --git a/refactor/adapters/udp/udpAckNacker.go b/refactor/adapters/udp/udpAckNacker.go index 883adb8d4..7d95b4d53 100644 --- a/refactor/adapters/udp/udpAckNacker.go +++ b/refactor/adapters/udp/udpAckNacker.go @@ -6,7 +6,6 @@ package udp import ( "time" - . "github.com/TheThingsNetwork/ttn/core/errors" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -21,13 +20,13 @@ func (an udpAckNacker) Ack(p core.Packet) error { defer close(an.Chresp) data, err := p.MarshalBinary() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } select { case an.Chresp <- MsgRes(data): return nil case <-time.After(time.Millisecond * 50): - return errors.New(ErrFailedOperation, "Unable to send ack") + return errors.New(errors.Operational, "Unable to send ack") } } diff --git a/refactor/adapters/udp/udpRegistration.go b/refactor/adapters/udp/udpRegistration.go index 7aed8cbfa..ec5feff5e 100644 --- a/refactor/adapters/udp/udpRegistration.go +++ b/refactor/adapters/udp/udpRegistration.go @@ -5,7 +5,6 @@ package udp import ( core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/errors" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -20,20 +19,20 @@ func (r udpRegistration) Recipient() core.Recipient { // AppEUI implements the core.Registration interface func (r udpRegistration) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(ErrNotSupported, "AppEUI not supported on udp registration") + return lorawan.EUI64{}, errors.New(errors.Implementation, "AppEUI not supported on udp registration") } // DevEUI implements the core.Registration interface func (r udpRegistration) DevEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(ErrNotSupported, "DevEUI not supported on udp registration") + return lorawan.EUI64{}, errors.New(errors.Implementation, "DevEUI not supported on udp registration") } // AppSKey implements the core.Registration interface func (r udpRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "AppSKey not supported on udp registration") + return lorawan.AES128Key{}, errors.New(errors.Implementation, "AppSKey not supported on udp registration") } // NwkSKey implements the core.Registration interface func (r udpRegistration) NwkSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(ErrNotSupported, "NwkSKey not supported on udp registration") + return lorawan.AES128Key{}, errors.New(errors.Implementation, "NwkSKey not supported on udp registration") } diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go index 0d5174d8a..906aaf114 100644 --- a/refactor/check_utilities_test.go +++ b/refactor/check_utilities_test.go @@ -53,7 +53,7 @@ func checkErrors(t *testing.T, want *string, got error) { return } - if got.(errors.Failure).Nature == *want { + if got.(errors.Failure).Nature == errors.Nature(*want) { Ok(t, "Check errors") return } diff --git a/refactor/metadata.go b/refactor/metadata.go index 0b306b366..e3ac73b56 100644 --- a/refactor/metadata.go +++ b/refactor/metadata.go @@ -7,7 +7,6 @@ import ( "encoding/json" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -46,7 +45,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { }) if err != nil { - err = errors.New(ErrInvalidStructure, err) + err = errors.New(errors.Structural, err) } return data, err @@ -55,12 +54,12 @@ func (m Metadata) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { if m == nil { - return errors.New(ErrInvalidStructure, "Cannot unmarshal nil Metadata") + return errors.New(errors.Structural, "Cannot unmarshal nil Metadata") } proxy := metadataProxy{} if err := json.Unmarshal(raw, &proxy); err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } *m = Metadata(proxy.metadata) if proxy.Time != nil { diff --git a/refactor/rpacket.go b/refactor/rpacket.go index 9e81a5e26..bfb772228 100644 --- a/refactor/rpacket.go +++ b/refactor/rpacket.go @@ -9,7 +9,6 @@ import ( "encoding/json" "fmt" - . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -48,12 +47,12 @@ func (p RPacket) Metadata() Metadata { // DevEUI implements the core.Addressable interface func (p RPacket) DevEUI() (lorawan.EUI64, error) { if p.payload.MACPayload == nil { - return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "MACPAyload should not be empty") + return lorawan.EUI64{}, errors.New(errors.Structural, "MACPAyload should not be empty") } macpayload, ok := p.payload.MACPayload.(*lorawan.MACPayload) if !ok { - return lorawan.EUI64{}, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") + return lorawan.EUI64{}, errors.New(errors.Structural, "Packet does not carry a MACPayload") } var devEUI lorawan.EUI64 @@ -75,11 +74,11 @@ func (p *RPacket) UnmarshalBinary(data []byte) error { func (p RPacket) MarshalJSON() ([]byte, error) { rawMetadata, err := json.Marshal(p.metadata) if err != nil { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } rawPayload, err := p.payload.MarshalBinary() if err != nil { - return nil, errors.New(ErrInvalidStructure, err) + return nil, errors.New(errors.Structural, err) } strPayload := base64.StdEncoding.EncodeToString(rawPayload) return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil @@ -88,7 +87,7 @@ func (p RPacket) MarshalJSON() ([]byte, error) { // UnmarshalJSON impements the json.Unmarshaler interface func (p *RPacket) UnmarshalJSON(raw []byte) error { if p == nil { - return errors.New(ErrInvalidStructure, "Cannot unmarshal a nil packet") + return errors.New(errors.Structural, "Cannot unmarshal a nil packet") } // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink @@ -101,17 +100,17 @@ func (p *RPacket) UnmarshalJSON(raw []byte) error { err := json.Unmarshal(raw, &proxy) if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } payload := lorawan.NewPHYPayload(true) // true -> uplink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } // Now, we check the nature of the decoded payload @@ -125,7 +124,7 @@ func (p *RPacket) UnmarshalJSON(raw []byte) error { // We thus have to unmarshall properly payload = lorawan.NewPHYPayload(false) // false -> downlink if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } case "JoinRequest": fallthrough @@ -138,7 +137,7 @@ func (p *RPacket) UnmarshalJSON(raw []byte) error { case "Proprietary": // Proprietary can be either downlink or uplink. Right now, we do not have any message of // that type and thus, we just don't know how to handle them. Let's throw an error. - return errors.New(ErrInvalidStructure, "Unsupported MType 'Proprietary'") + return errors.New(errors.Implementation, "Unsupported MType 'Proprietary'") } // Packet = Payload + Metadata diff --git a/utils/errors/errors.go b/utils/errors/errors.go index ba1868ea3..0f7d9e6ff 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -9,15 +9,24 @@ import ( "time" ) +type Nature string + +const ( + Structural Nature = "Invalid structure" // Requests, parameters or inputs are wrong. Retry won't work. + Behavioural = "Wrong but expected behavior" // No error but the result isn't the one expected + Operational = "Invalid operation" // An operation failed due to external causes, a retry could work + Implementation = "Illegal call" // Method not implemented or unsupported for the given structure +) + // Failure states for fault that occurs during a process. type Failure struct { - Nature string // Kind of error, used a comparator + Nature Nature // Kind of error, used a comparator Timestamp time.Time // The moment the error was created Fault error // The source of the failure } // NewFailure creates a new Failure from a source error -func New(k string, src interface{}) Failure { +func New(nat Nature, src interface{}) Failure { var fault error switch src.(type) { case string: @@ -29,7 +38,7 @@ func New(k string, src interface{}) Failure { } failure := Failure{ - Nature: k, + Nature: nat, Timestamp: time.Now(), Fault: fault, } diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 2ddc09109..e6acb26ea 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -8,6 +8,7 @@ import ( "encoding/binary" "fmt" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -89,12 +90,15 @@ func (w *Interface) Read(to func(data []byte)) { // an error if any issue was encountered during the process. func (w Interface) Bytes() ([]byte, error) { if w.err != nil { - return nil, w.err + return nil, errors.New(errors.Structural, w.err) } return w.data.Bytes(), nil } // Err just return the err status of the read-writer. func (w Interface) Err() error { - return w.err + if w.err != nil { + return errors.New(errors.Structural, w.err) + } + return nil } diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 9cdc38c41..dcc1af60c 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -10,7 +10,6 @@ import ( "reflect" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" "github.com/boltdb/bolt" @@ -31,7 +30,7 @@ type Interface struct { func New(name string) (Interface, error) { db, err := bolt.Open(name, 0600, &bolt.Options{Timeout: time.Second}) if err != nil { - return Interface{}, errors.New(ErrFailedOperation, err) + return Interface{}, errors.New(errors.Operational, err) } return Interface{db}, nil } @@ -52,7 +51,7 @@ func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry for _, entry := range entries { m, err := entry.MarshalBinary() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } marshalled = append(marshalled, m) } @@ -60,7 +59,7 @@ func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry err := itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") + return errors.New(errors.Operational, "storage unreachable") } w := readwriter.New(bucket.Get(key)) for _, m := range marshalled { @@ -68,10 +67,10 @@ func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry } data, err := w.Bytes() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } if err := bucket.Put(key, data); err != nil { - return errors.New(ErrFailedOperation, err) + return errors.New(errors.Operational, err) } return nil }) @@ -90,11 +89,11 @@ func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) ( err := itf.db.View(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") + return errors.New(errors.Operational, "storage unreachable") } rawEntry = bucket.Get(key) if rawEntry == nil { - return errors.New(ErrWrongBehavior, fmt.Sprintf("Not found %+v", key)) + return errors.New(errors.Behavioural, fmt.Sprintf("Not found %+v", key)) } return nil }) @@ -117,7 +116,7 @@ func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) ( if err == io.EOF { break } - return nil, errors.New(ErrFailedOperation, err) + return nil, errors.New(errors.Operational, err) } } return entries.Interface(), nil @@ -128,10 +127,10 @@ func (itf Interface) Flush(bucketName string, key []byte) error { return itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") + return errors.New(errors.Operational, "storage unreachable") } if err := bucket.Delete(key); err != nil { - return errors.New(ErrFailedOperation, err) + return errors.New(errors.Operational, err) } return nil }) @@ -144,7 +143,7 @@ func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEnt for _, entry := range entries { m, err := entry.MarshalBinary() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } marshalled = append(marshalled, m) } @@ -152,10 +151,10 @@ func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEnt return itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") + return errors.New(errors.Operational, "storage unreachable") } if err := bucket.Delete(key); err != nil { - return errors.New(ErrFailedOperation, err) + return errors.New(errors.Operational, err) } w := readwriter.New(bucket.Get(key)) for _, m := range marshalled { @@ -163,10 +162,10 @@ func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEnt } data, err := w.Bytes() if err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } if err := bucket.Put(key, data); err != nil { - return errors.New(ErrFailedOperation, err) + return errors.New(errors.Operational, err) } return nil }) @@ -176,10 +175,10 @@ func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEnt func (itf Interface) Reset(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { - return errors.New(ErrFailedOperation, err) + return errors.New(errors.Operational, err) } if _, err := tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil { - return errors.New(ErrFailedOperation, err) + return errors.New(errors.Operational, err) } return nil }) From c9d9ecf2558924cf1bf5f8a65515b2c960c86c38 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 24 Feb 2016 17:50:20 +0100 Subject: [PATCH 0710/2266] [refactor] Implement mqtt activations handler --- refactor/adapters/mqtt/handlers/activation.go | 74 +++++++++++++++++++ .../mqtt/handlers/activationRegistration.go | 42 +++++++++++ 2 files changed, 116 insertions(+) create mode 100644 refactor/adapters/mqtt/handlers/activation.go create mode 100644 refactor/adapters/mqtt/handlers/activationRegistration.go diff --git a/refactor/adapters/mqtt/handlers/activation.go b/refactor/adapters/mqtt/handlers/activation.go new file mode 100644 index 000000000..a4c4f7de9 --- /dev/null +++ b/refactor/adapters/mqtt/handlers/activation.go @@ -0,0 +1,74 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "encoding/hex" + "fmt" + "strings" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/brocaar/lorawan" +) + +type Activation struct{} + +func (a Activation) Topic() string { + return "+/devices/+/activations" +} + +func (a Activation) Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) { + topicInfos := strings.Split(msg.Topic(), "/") + appEUIStr := topicInfos[0] + devEUIStr := topicInfos[2] + + if devEUIStr != "personalized" { + // TODO Log warning + //a.ctx.WithField("Device Address", devEUI).Warn("OTAA not yet supported. Unable to register device") + return + } + + payload := msg.Payload() + if len(payload) != 36 { + // TODO Log warning + //a.ctx.WithField("Payload", payload).Error("Invalid registration payload") + return + } + + var appEUI lorawan.EUI64 + var devEUI lorawan.EUI64 + var nwkSKey lorawan.AES128Key + var appSKey lorawan.AES128Key + copy(appEUI[:], []byte(appEUIStr)) + copy(devEUI[4:], msg.Payload()[:4]) + copy(nwkSKey[:], msg.Payload()[4:20]) + copy(appSKey[:], msg.Payload()[20:]) + + devEUIStr = hex.EncodeToString(devEUI[:]) + topic := fmt.Sprintf("%s/%s/%s/%s", appEUIStr, "devices", devEUIStr, "up") + token := client.Subscribe(topic, 2, a.handleReception(chpkt)) + if token.Wait() && token.Error() != nil { + // TODO Log Error + // a.ctx.WithError(token.Error()).Error("Unable to subscribe") + return + } + + chreg <- RegReq{ + Registration: activationRegistration{ + recipient: NewRecipient(topic, "DO_NOT_USE_THIS_TOPIC"), + devEUI: devEUI, + appEUI: appEUI, + nwkSKey: nwkSKey, + appSKey: appSKey, + }, + Chresp: nil, + } +} + +func (a Activation) handleReception(chpkt chan<- PktReq) MQTT.MessageHandler { + return func(client *MQTT.Client, msg MQTT.Message) { + // TODO + } +} diff --git a/refactor/adapters/mqtt/handlers/activationRegistration.go b/refactor/adapters/mqtt/handlers/activationRegistration.go new file mode 100644 index 000000000..5a941a220 --- /dev/null +++ b/refactor/adapters/mqtt/handlers/activationRegistration.go @@ -0,0 +1,42 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/brocaar/lorawan" +) + +type activationRegistration struct { + recipient core.Recipient + devEUI lorawan.EUI64 + appEUI lorawan.EUI64 + nwkSKey lorawan.AES128Key + appSKey lorawan.AES128Key +} + +// Recipient implements the core.Registration interface +func (r activationRegistration) Recipient() core.Recipient { + return r.recipient +} + +// AppEUI implements the core.Registration interface +func (r activationRegistration) AppEUI() (lorawan.EUI64, error) { + return r.appEUI, nil +} + +// DevEUI implements the core.Registration interface +func (r activationRegistration) DevEUI() (lorawan.EUI64, error) { + return r.devEUI, nil +} + +// AppSKey implements the core.Registration interface +func (r activationRegistration) AppSKey() (lorawan.AES128Key, error) { + return r.appSKey, nil +} + +// NwkSKey implements the core.Registration interface +func (r activationRegistration) NwkSKey() (lorawan.AES128Key, error) { + return r.nwkSKey, nil +} From adad1c0ce8cc3f810dd875f7fe24b4d80c3c5aa9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 10:53:09 +0100 Subject: [PATCH 0711/2266] [refactor] Abstract a bit the mqtt/paho client to make testing more convenient --- refactor/adapters/mqtt/mqtt.go | 27 +++------------ refactor/adapters/mqtt/mqttClient.go | 50 ++++++++++++++++++++++++++++ refactor/adapters/mqtt/mqtt_test.go | 6 ++-- 3 files changed, 58 insertions(+), 25 deletions(-) create mode 100644 refactor/adapters/mqtt/mqttClient.go diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index 5ef6242a1..d50a598bf 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -4,7 +4,6 @@ package mqtt import ( - "fmt" "sync" "time" @@ -17,7 +16,7 @@ import ( // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { - *MQTT.Client + Client ctx log.Interface packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently registrations chan RegReq // Incoming registrations @@ -26,7 +25,7 @@ type Adapter struct { // Handler defines topic-specific handler. type Handler interface { Topic() string - Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) + Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) } // Message sent through the response channel of a pktReq or regReq @@ -56,7 +55,7 @@ const ( // NewAdapter constructs and allocates a new mqtt adapter // // The client is expected to be already connected to the right broker and ready to be used. -func NewAdapter(client *MQTT.Client, ctx log.Interface) *Adapter { +func NewAdapter(client Client, ctx log.Interface) *Adapter { adapter := &Adapter{ Client: client, ctx: ctx, @@ -67,22 +66,6 @@ func NewAdapter(client *MQTT.Client, ctx log.Interface) *Adapter { return adapter } -// NewClient generates a new paho MQTT client from an id and a broker url -// -// The broker url is expected to contain a port if needed such as mybroker.com:87354 -// -// The scheme has to be the same as the one used by the broker: tcp, tls or web socket -func NewClient(id string, broker string, scheme Scheme) (*MQTT.Client, error) { - opts := MQTT.NewClientOptions() - opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) - opts.SetClientID(id) - client := MQTT.NewClient(opts) - if token := client.Connect(); token.Wait() && token.Error() != nil { - return nil, errors.New(errors.Operational, token.Error()) - } - return client, nil -} - // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { stats.MarkMeter("mqtt_adapter.send") @@ -116,7 +99,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Subscribe to down channel (before publishing anything) chdown := make(chan []byte) - token := a.Subscribe(recipient.TopicDown(), 2, func(client *MQTT.Client, msg MQTT.Message) { + token := a.Subscribe(recipient.TopicDown(), 2, func(client Client, msg MQTT.Message) { chdown <- msg.Payload() }) if token.Wait() && token.Error() != nil { @@ -215,7 +198,7 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) func (a *Adapter) Bind(h Handler) error { ctx := a.ctx.WithField("topic", h.Topic()) ctx.Info("Subscribe new handler") - token := a.Subscribe(h.Topic(), 2, func(client *MQTT.Client, msg MQTT.Message) { + token := a.Subscribe(h.Topic(), 2, func(client Client, msg MQTT.Message) { ctx.Debug("Handle new mqtt message") h.Handle(client, a.packets, a.registrations, msg) }) diff --git a/refactor/adapters/mqtt/mqttClient.go b/refactor/adapters/mqtt/mqttClient.go new file mode 100644 index 000000000..081fb03be --- /dev/null +++ b/refactor/adapters/mqtt/mqttClient.go @@ -0,0 +1,50 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +type Client interface { + Connect() MQTT.Token + Disconnect(quiesce uint) + IsConnected() bool + Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token + Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token + Unsubscribe(topics ...string) MQTT.Token +} + +// NewClient generates a new paho MQTT client from an id and a broker url +// +// The broker url is expected to contain a port if needed such as mybroker.com:87354 +// +// The scheme has to be the same as the one used by the broker: tcp, tls or web socket +func NewClient(id string, broker string, scheme Scheme) (Client, error) { + opts := MQTT.NewClientOptions() + opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) + opts.SetClientID(id) + c := MQTT.NewClient(opts) + if token := c.Connect(); token.Wait() && token.Error() != nil { + return nil, errors.New(errors.Operational, token.Error()) + } + return client{c}, nil +} + +// Type client abstract the MQTT client to provide a valid interface. +// This is needed because of the signature of the Subscribe() method which is using a plain MQTT +// type instead of an interface. +type client struct { + *MQTT.Client +} + +// Subscribe just implements the interface and forward the call to the actual MQTT client +func (c client) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { + return c.Client.Subscribe(topic, qos, func(c *MQTT.Client, m MQTT.Message) { + callback(client{c}, m) + }) +} diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 547bdf8e1..0c8d540bf 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -213,7 +213,7 @@ func (p testPacket) String() string { } // ----- BUILD utilities -func createAdapter(t *testing.T) (*MQTT.Client, core.Adapter) { +func createAdapter(t *testing.T) (Client, core.Adapter) { client, err := NewClient("testClient", brokerUrl, Tcp) if err != nil { panic(err) @@ -223,7 +223,7 @@ func createAdapter(t *testing.T) (*MQTT.Client, core.Adapter) { return client, adapter } -func createServers(recipients []testRecipient) (*MQTT.Client, chan []byte) { +func createServers(recipients []testRecipient) (Client, chan []byte) { client, err := NewClient("FakeServerClient", brokerUrl, Tcp) if err != nil { panic(err) @@ -232,7 +232,7 @@ func createServers(recipients []testRecipient) (*MQTT.Client, chan []byte) { chresp := make(chan []byte, len(recipients)) for _, r := range recipients { go func(r testRecipient) { - token := client.Subscribe(r.TopicUp, 2, func(client *MQTT.Client, msg MQTT.Message) { + token := client.Subscribe(r.TopicUp, 2, func(client Client, msg MQTT.Message) { if r.Response != nil { token := client.Publish(r.TopicDown, 2, false, r.Response) if token.Wait() && token.Error() != nil { From e783af40cec26047ce2c52bc1c134be8f28a17ff Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 11:58:59 +0100 Subject: [PATCH 0712/2266] [refactor] Fix function signature with new Interface declaration + add error return type --- refactor/adapters/mqtt/handlers/activation.go | 13 +++++++------ refactor/adapters/mqtt/mqtt.go | 6 ++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/refactor/adapters/mqtt/handlers/activation.go b/refactor/adapters/mqtt/handlers/activation.go index a4c4f7de9..3d0876703 100644 --- a/refactor/adapters/mqtt/handlers/activation.go +++ b/refactor/adapters/mqtt/handlers/activation.go @@ -19,7 +19,7 @@ func (a Activation) Topic() string { return "+/devices/+/activations" } -func (a Activation) Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) { +func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { topicInfos := strings.Split(msg.Topic(), "/") appEUIStr := topicInfos[0] devEUIStr := topicInfos[2] @@ -27,14 +27,14 @@ func (a Activation) Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan< if devEUIStr != "personalized" { // TODO Log warning //a.ctx.WithField("Device Address", devEUI).Warn("OTAA not yet supported. Unable to register device") - return + return nil } payload := msg.Payload() if len(payload) != 36 { // TODO Log warning //a.ctx.WithField("Payload", payload).Error("Invalid registration payload") - return + return nil } var appEUI lorawan.EUI64 @@ -52,7 +52,7 @@ func (a Activation) Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan< if token.Wait() && token.Error() != nil { // TODO Log Error // a.ctx.WithError(token.Error()).Error("Unable to subscribe") - return + return nil } chreg <- RegReq{ @@ -65,10 +65,11 @@ func (a Activation) Handle(client *MQTT.Client, chpkt chan<- PktReq, chreg chan< }, Chresp: nil, } + return nil } -func (a Activation) handleReception(chpkt chan<- PktReq) MQTT.MessageHandler { - return func(client *MQTT.Client, msg MQTT.Message) { +func (a Activation) handleReception(chpkt chan<- PktReq) func(client Client, msg MQTT.Message) { + return func(client Client, msg MQTT.Message) { // TODO } } diff --git a/refactor/adapters/mqtt/mqtt.go b/refactor/adapters/mqtt/mqtt.go index d50a598bf..f03dfaae0 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/refactor/adapters/mqtt/mqtt.go @@ -25,7 +25,7 @@ type Adapter struct { // Handler defines topic-specific handler. type Handler interface { Topic() string - Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) + Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error } // Message sent through the response channel of a pktReq or regReq @@ -200,7 +200,9 @@ func (a *Adapter) Bind(h Handler) error { ctx.Info("Subscribe new handler") token := a.Subscribe(h.Topic(), 2, func(client Client, msg MQTT.Message) { ctx.Debug("Handle new mqtt message") - h.Handle(client, a.packets, a.registrations, msg) + if err := h.Handle(client, a.packets, a.registrations, msg); err != nil { + ctx.WithError(err).Warn("Unable to handle mqtt message") + } }) if token.Wait() && token.Error() != nil { ctx.WithError(token.Error()).Error("Unable to Subscribe") From 825603246389260bad90ab1e7511fae7af5de1ce Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 13:00:23 +0100 Subject: [PATCH 0713/2266] [refactor] Write activation tests backbone + check methods --- .../adapters/mqtt/handlers/activation_test.go | 190 ++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 refactor/adapters/mqtt/handlers/activation_test.go diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go new file mode 100644 index 000000000..066b5cd38 --- /dev/null +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -0,0 +1,190 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "reflect" + "testing" + + //MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +// func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) { + +func TestActivation(t *testing.T) { + tests := []struct { + Desc string // The test's description + Client Client // An mqtt client to mock (or not) the behavior + Topic string // The topic to which the message is addressed + Payload []byte // The message's payload + + WantError *string // The expected error from the handler + WantSubscription *string // The topic to which a subscription is expected + WantRegistration core.Registration // The expected registration towards the adapter + WantPacket []byte // The expected packet towards the adapter + }{} + + for i, test := range tests { + // Describe + Desc(t, "#%d: %s", i, test.Desc) + + // Build + // Generate a mock Client + // Generate a mock Consumer + // Generate a handler + + // Operate + // err := Handle message, client and channels to the handler + // Retrieve subscriptions + // Retrieve registrations + + // Check + // Check errors + // Check subscriptions + // Check registrations + // Check Packets + } +} + +// ----- TYPE utilities + +// ----- BUILD utilities + +// ----- OPERATE utilities + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check Errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == errors.Nature(*want) { + Ok(t, "Check Errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkRegistrations(t *testing.T, want core.Registration, got core.Registration) { + // Check if interfaces are nil + if got == nil { + if want == nil { + Ok(t, "Check Registrations") + return + } + Ko(t, "Expected registration to be %v but got nothing", want) + return + } + if want == nil { + Ko(t, "Expected no registration but got %v", got) + return + } + + // Check recipient topicUp + rWant, ok := want.Recipient().(MqttRecipient) + if !ok { + panic("Expected test to be made with MQTTRecipient") + } + rGot, ok := got.Recipient().(MqttRecipient) + if !ok { + Ko(t, "Recipient isn't MqttRecipient: %v", got.Recipient()) + return + } + if rWant.TopicUp() != rGot.TopicUp() { + Ko(t, "Recipients got different topics up.\nWant: %s\nGot: %s", rWant.TopicUp(), rGot.TopicUp()) + return + } + + // Check DevEUIs + deWant, err := want.DevEUI() + if err != nil { + panic("Expected devEUI to be accessible in test registration") + } + deGot, err := got.DevEUI() + if err != nil || !reflect.DeepEqual(deWant, deGot) { + Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", deWant, deGot) + return + } + + // Check AppEUIs + aeWant, err := want.AppEUI() + if err != nil { + panic("Expected appEUI to be accessible in test registration") + } + aeGot, err := got.AppEUI() + if err != nil || !reflect.DeepEqual(aeWant, aeGot) { + Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", aeWant, aeGot) + return + } + + // Check Network Session Keys + nkWant, err := want.NwkSKey() + if err != nil { + panic("Expected nwkSKey to be accessible in test registration") + } + nkGot, err := got.NwkSKey() + if err != nil || !reflect.DeepEqual(nkWant, nkGot) { + Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", nkWant, nkGot) + return + } + + // Check Application Session Keys + akWant, err := want.AppSKey() + if err != nil { + panic("Expected nwkSKey to be accessible in test registration") + } + akGot, err := got.AppSKey() + if err != nil || !reflect.DeepEqual(akWant, akGot) { + Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", akWant, akGot) + return + } + + // Pheeew + Ok(t, "Check Registrations") +} + +func checkSubscriptions(t *testing.T, want *string, got *string) { + // Check if interfaces are nil + if got == nil { + if want == nil { + Ok(t, "Check Subscriptions") + return + } + Ko(t, "Expected subscription to be %s but got nothing", *want) + return + } + if want == nil { + Ko(t, "Expected no subscription but got %v", *got) + return + } + + if *want != *got { + Ko(t, "Expected subscription to be %s but got %s", *want, *got) + return + } + + Ok(t, "Check Subscriptions") +} + +func checkPackets(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check Packets") + return + } + Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) +} From 103d8c9527e84a6c21e8457eb1b0e7b8fc264521 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 13:38:27 +0100 Subject: [PATCH 0714/2266] [refactor] Define mock interfaces for testing --- .../adapters/mqtt/handlers/activation_test.go | 4 - .../adapters/mqtt/handlers/helpers_test.go | 161 ++++++++++++++++++ 2 files changed, 161 insertions(+), 4 deletions(-) create mode 100644 refactor/adapters/mqtt/handlers/helpers_test.go diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go index 066b5cd38..cce3ca02b 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -7,7 +7,6 @@ import ( "reflect" "testing" - //MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -51,8 +50,6 @@ func TestActivation(t *testing.T) { } } -// ----- TYPE utilities - // ----- BUILD utilities // ----- OPERATE utilities @@ -159,7 +156,6 @@ func checkRegistrations(t *testing.T, want core.Registration, got core.Registrat } func checkSubscriptions(t *testing.T, want *string, got *string) { - // Check if interfaces are nil if got == nil { if want == nil { Ok(t, "Check Subscriptions") diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/refactor/adapters/mqtt/handlers/helpers_test.go new file mode 100644 index 000000000..7426d0259 --- /dev/null +++ b/refactor/adapters/mqtt/handlers/helpers_test.go @@ -0,0 +1,161 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "fmt" + "time" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/pointer" +) + +// ----- TYPE utilities + +// testToken gives a fake implementation of MQTT.Token +// +// Provide a failure if you need to simulate an Error() result. +type testToken struct { + MQTT.Token + Failure *string +} + +func (t testToken) Wait() bool { + return true +} + +func (t testToken) WaitTimeout(d time.Duration) bool { + <-time.After(d) + return true +} + +func (t testToken) Error() error { + if t.Failure == nil { + return nil + } + return fmt.Errorf(*t.Failure) +} + +// testMessage gives a fake implementation of MQTT.Message +// +// provide payload and topic. Other methods are constants. +type testMessage struct { + payload interface{} + topic string +} + +func (m testMessage) Duplicate() bool { + return false +} +func (m testMessage) Qos() byte { + return 2 +} +func (m testMessage) Retained() bool { + return false +} +func (m testMessage) Topic() string { + return m.topic +} +func (m testMessage) MessageID() uint16 { + return 0 +} +func (m testMessage) Payload() []byte { + return m.payload.([]byte) +} + +// testClient gives a fake implementation of a MQTT.ClientInt +// +// It saves the last subscription, unsubscriptions and publication call made +// +// It can also fails on demand (use the newTestClient method to define which methods should fail) +type testClient struct { + Subscription *string + Unsubscriptions []string + Publication MQTT.Message + + failures map[string]*string + connected bool +} + +func newTestClient(failConnect bool, failPublish bool, failSubscribe bool, failUnsubscribe bool) Client { + client := testClient{failures: make(map[string]*string), connected: true} + + if failConnect { + client.failures["Connect"] = pointer.String("MockError -> Failed to connect") + } + + if failPublish { + client.failures["Publish"] = pointer.String("MockError -> Failed to publish") + } + + if failSubscribe { + client.failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") + } + + if failUnsubscribe { + client.failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") + } + + return &client +} + +func (c *testClient) Connect() MQTT.Token { + c.connected = true + return testToken{Failure: c.failures["Connect"]} +} + +func (c *testClient) Disconnect(quiesce uint) { + <-time.After(time.Duration(quiesce)) + c.connected = false + return +} + +func (c testClient) IsConnected() bool { + return c.connected +} + +func (c *testClient) Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token { + c.Publication = testMessage{payload: payload, topic: topic} + return testToken{Failure: c.failures["Publish"]} +} + +func (c *testClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { + c.Subscription = &topic + return testToken{Failure: c.failures["Subscribe"]} +} + +func (c *testClient) Unsubscribe(topics ...string) MQTT.Token { + c.Unsubscriptions = topics + return testToken{Failure: c.failures["Unsubscribe"]} +} + +// testConsumer generates a component which consumes messages from two channels and make the last +// result available + +type testConsumer struct { + Packet []byte + Registration core.Registration +} + +func newTestConsumer() (*testConsumer, chan PktReq, chan RegReq) { + chpkt := make(chan PktReq) + chreg := make(chan RegReq) + consumer := testConsumer{} + + go func() { + for msg := range chpkt { + consumer.Packet = msg.Packet + } + }() + + go func() { + for msg := range chreg { + consumer.Registration = msg.Registration + } + }() + + return &consumer, chpkt, chreg +} From bcbf45c1dd74eabfc3aa02c064d1a8e65974d394 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 13:53:14 +0100 Subject: [PATCH 0715/2266] [refactor] Implement test procedure --- .../adapters/mqtt/handlers/activation_test.go | 166 ++---------------- .../adapters/mqtt/handlers/helpers_test.go | 135 ++++++++++++++ 2 files changed, 149 insertions(+), 152 deletions(-) diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go index cce3ca02b..d84428062 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -4,12 +4,9 @@ package handlers import ( - "reflect" "testing" core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -17,10 +14,10 @@ import ( func TestActivation(t *testing.T) { tests := []struct { - Desc string // The test's description - Client Client // An mqtt client to mock (or not) the behavior - Topic string // The topic to which the message is addressed - Payload []byte // The message's payload + Desc string // The test's description + Client *testClient // An mqtt client to mock (or not) the behavior + Topic string // The topic to which the message is addressed + Payload []byte // The message's payload WantError *string // The expected error from the handler WantSubscription *string // The topic to which a subscription is expected @@ -33,154 +30,19 @@ func TestActivation(t *testing.T) { Desc(t, "#%d: %s", i, test.Desc) // Build - // Generate a mock Client - // Generate a mock Consumer - // Generate a handler + consumer, chpkt, chreg := newTestConsumer() + handler := Activation{} // Operate - // err := Handle message, client and channels to the handler - // Retrieve subscriptions - // Retrieve registrations + err := handler.Handle(test.Client, chpkt, chreg, testMessage{ + payload: test.Payload, + topic: test.Topic, + }) // Check - // Check errors - // Check subscriptions - // Check registrations - // Check Packets + checkErrors(t, test.WantError, err) + checkSubscriptions(t, test.WantSubscription, test.Client.Subscription) + checkRegistrations(t, test.WantRegistration, consumer.Registration) + checkPackets(t, test.WantPacket, consumer.Packet) } } - -// ----- BUILD utilities - -// ----- OPERATE utilities - -// ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check Errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check Errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - -func checkRegistrations(t *testing.T, want core.Registration, got core.Registration) { - // Check if interfaces are nil - if got == nil { - if want == nil { - Ok(t, "Check Registrations") - return - } - Ko(t, "Expected registration to be %v but got nothing", want) - return - } - if want == nil { - Ko(t, "Expected no registration but got %v", got) - return - } - - // Check recipient topicUp - rWant, ok := want.Recipient().(MqttRecipient) - if !ok { - panic("Expected test to be made with MQTTRecipient") - } - rGot, ok := got.Recipient().(MqttRecipient) - if !ok { - Ko(t, "Recipient isn't MqttRecipient: %v", got.Recipient()) - return - } - if rWant.TopicUp() != rGot.TopicUp() { - Ko(t, "Recipients got different topics up.\nWant: %s\nGot: %s", rWant.TopicUp(), rGot.TopicUp()) - return - } - - // Check DevEUIs - deWant, err := want.DevEUI() - if err != nil { - panic("Expected devEUI to be accessible in test registration") - } - deGot, err := got.DevEUI() - if err != nil || !reflect.DeepEqual(deWant, deGot) { - Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", deWant, deGot) - return - } - - // Check AppEUIs - aeWant, err := want.AppEUI() - if err != nil { - panic("Expected appEUI to be accessible in test registration") - } - aeGot, err := got.AppEUI() - if err != nil || !reflect.DeepEqual(aeWant, aeGot) { - Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", aeWant, aeGot) - return - } - - // Check Network Session Keys - nkWant, err := want.NwkSKey() - if err != nil { - panic("Expected nwkSKey to be accessible in test registration") - } - nkGot, err := got.NwkSKey() - if err != nil || !reflect.DeepEqual(nkWant, nkGot) { - Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", nkWant, nkGot) - return - } - - // Check Application Session Keys - akWant, err := want.AppSKey() - if err != nil { - panic("Expected nwkSKey to be accessible in test registration") - } - akGot, err := got.AppSKey() - if err != nil || !reflect.DeepEqual(akWant, akGot) { - Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", akWant, akGot) - return - } - - // Pheeew - Ok(t, "Check Registrations") -} - -func checkSubscriptions(t *testing.T, want *string, got *string) { - if got == nil { - if want == nil { - Ok(t, "Check Subscriptions") - return - } - Ko(t, "Expected subscription to be %s but got nothing", *want) - return - } - if want == nil { - Ko(t, "Expected no subscription but got %v", *got) - return - } - - if *want != *got { - Ko(t, "Expected subscription to be %s but got %s", *want, *got) - return - } - - Ok(t, "Check Subscriptions") -} - -func checkPackets(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check Packets") - return - } - Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) -} diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/refactor/adapters/mqtt/handlers/helpers_test.go index 7426d0259..b29c8a64f 100644 --- a/refactor/adapters/mqtt/handlers/helpers_test.go +++ b/refactor/adapters/mqtt/handlers/helpers_test.go @@ -5,12 +5,16 @@ package handlers import ( "fmt" + "reflect" + "testing" "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" ) // ----- TYPE utilities @@ -159,3 +163,134 @@ func newTestConsumer() (*testConsumer, chan PktReq, chan RegReq) { return &consumer, chpkt, chreg } + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check Errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == errors.Nature(*want) { + Ok(t, "Check Errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkRegistrations(t *testing.T, want core.Registration, got core.Registration) { + // Check if interfaces are nil + if got == nil { + if want == nil { + Ok(t, "Check Registrations") + return + } + Ko(t, "Expected registration to be %v but got nothing", want) + return + } + if want == nil { + Ko(t, "Expected no registration but got %v", got) + return + } + + // Check recipient topicUp + rWant, ok := want.Recipient().(MqttRecipient) + if !ok { + panic("Expected test to be made with MQTTRecipient") + } + rGot, ok := got.Recipient().(MqttRecipient) + if !ok { + Ko(t, "Recipient isn't MqttRecipient: %v", got.Recipient()) + return + } + if rWant.TopicUp() != rGot.TopicUp() { + Ko(t, "Recipients got different topics up.\nWant: %s\nGot: %s", rWant.TopicUp(), rGot.TopicUp()) + return + } + + // Check DevEUIs + deWant, err := want.DevEUI() + if err != nil { + panic("Expected devEUI to be accessible in test registration") + } + deGot, err := got.DevEUI() + if err != nil || !reflect.DeepEqual(deWant, deGot) { + Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", deWant, deGot) + return + } + + // Check AppEUIs + aeWant, err := want.AppEUI() + if err != nil { + panic("Expected appEUI to be accessible in test registration") + } + aeGot, err := got.AppEUI() + if err != nil || !reflect.DeepEqual(aeWant, aeGot) { + Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", aeWant, aeGot) + return + } + + // Check Network Session Keys + nkWant, err := want.NwkSKey() + if err != nil { + panic("Expected nwkSKey to be accessible in test registration") + } + nkGot, err := got.NwkSKey() + if err != nil || !reflect.DeepEqual(nkWant, nkGot) { + Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", nkWant, nkGot) + return + } + + // Check Application Session Keys + akWant, err := want.AppSKey() + if err != nil { + panic("Expected nwkSKey to be accessible in test registration") + } + akGot, err := got.AppSKey() + if err != nil || !reflect.DeepEqual(akWant, akGot) { + Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", akWant, akGot) + return + } + + // Pheeew + Ok(t, "Check Registrations") +} + +func checkSubscriptions(t *testing.T, want *string, got *string) { + if got == nil { + if want == nil { + Ok(t, "Check Subscriptions") + return + } + Ko(t, "Expected subscription to be %s but got nothing", *want) + return + } + if want == nil { + Ko(t, "Expected no subscription but got %v", *got) + return + } + + if *want != *got { + Ko(t, "Expected subscription to be %s but got %s", *want, *got) + return + } + + Ok(t, "Check Subscriptions") +} + +func checkPackets(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check Packets") + return + } + Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) +} From 2126b22ef55a789cbce3b4038174ceccf17c6f57 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 14:06:30 +0100 Subject: [PATCH 0716/2266] [refactor] Update mockClient constructors to improve readibility --- .../adapters/mqtt/handlers/helpers_test.go | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/refactor/adapters/mqtt/handlers/helpers_test.go index b29c8a64f..e0441591f 100644 --- a/refactor/adapters/mqtt/handlers/helpers_test.go +++ b/refactor/adapters/mqtt/handlers/helpers_test.go @@ -84,22 +84,31 @@ type testClient struct { connected bool } -func newTestClient(failConnect bool, failPublish bool, failSubscribe bool, failUnsubscribe bool) Client { +func newTestClient(failures ...string) *testClient { client := testClient{failures: make(map[string]*string), connected: true} - if failConnect { + isFailure := func(x string) bool { + for _, f := range failures { + if f == x { + return true + } + } + return false + } + + if isFailure("Connect") { client.failures["Connect"] = pointer.String("MockError -> Failed to connect") } - if failPublish { + if isFailure("Publish") { client.failures["Publish"] = pointer.String("MockError -> Failed to publish") } - if failSubscribe { + if isFailure("Subscribe") { client.failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") } - if failUnsubscribe { + if isFailure("Unsubscribe") { client.failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") } From 0b9324bb94e5edb13e59164d58d840e7064b9cab Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 14:37:24 +0100 Subject: [PATCH 0717/2266] [refactor] Write test suite --- .../adapters/mqtt/handlers/activation_test.go | 123 +++++++++++++++++- 1 file changed, 119 insertions(+), 4 deletions(-) diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go index d84428062..88cdb0c9d 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -7,12 +7,14 @@ import ( "testing" core "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) -// func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) { - -func TestActivation(t *testing.T) { +func TestActivationHandle(t *testing.T) { tests := []struct { Desc string // The test's description Client *testClient // An mqtt client to mock (or not) the behavior @@ -23,7 +25,120 @@ func TestActivation(t *testing.T) { WantSubscription *string // The topic to which a subscription is expected WantRegistration core.Registration // The expected registration towards the adapter WantPacket []byte // The expected packet towards the adapter - }{} + }{ + { + Desc: "Ok client | Valid Topic | Valid Payload", + Client: newTestClient(), + Topic: "0101010101010101/devices/personalized/activations", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + }, + + WantError: nil, + WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/up"), + WantRegistration: activationRegistration{ + recipient: NewRecipient("0101010101010101/devices/0000000002020202/up", "WHATEVER"), + devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 2, 2, 2, 2}), + appEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + nwkSKey: lorawan.AES128Key([16]byte{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}), + appSKey: lorawan.AES128Key([16]byte{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}), + }, + WantPacket: nil, + }, + { + Desc: "Ok client | Invalid Topic #1 | Valid Payload", + Client: newTestClient(), + Topic: "PleaseRegisterMyDevice", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + }, + + WantError: pointer.String(string(errors.Structural)), + WantSubscription: nil, + WantRegistration: nil, + WantPacket: nil, + }, + { + Desc: "Ok client | Invalid Topic #2 | Valid Payload", + Client: newTestClient(), + Topic: "0101010101010101/devices/0001020304050607/activations", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + }, + + WantError: pointer.String(string(errors.Implementation)), + WantSubscription: nil, + WantRegistration: nil, + WantPacket: nil, + }, + { + Desc: "Ok client | Invalid Topic #3 | Valid Payload", + Client: newTestClient(), + Topic: "01010101/devices/personalized/activations", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + }, + + WantError: pointer.String(string(errors.Structural)), + WantSubscription: nil, + WantRegistration: nil, + WantPacket: nil, + }, + { + Desc: "Ok client | Valid Topic | Invalid Payload #1", + Client: newTestClient(), + Topic: "0101010101010101/devices/personalized/activations", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + 01, 02, 03, 04, + }, + + WantError: pointer.String(string(errors.Structural)), + WantSubscription: nil, + WantRegistration: nil, + WantPacket: nil, + }, + { + Desc: "Ok client | Valid Topic | Invalid Payload #2", + Client: newTestClient(), + Topic: "0101010101010101/devices/personalized/activations", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + }, + + WantError: pointer.String(string(errors.Structural)), + WantSubscription: nil, + WantRegistration: nil, + WantPacket: nil, + }, + { + Desc: "Valid inputs | Client -> Fail Subscribe", + Client: newTestClient("Subscribe"), + Topic: "0101010101010101/devices/personalized/activations", + Payload: []byte{ // DevEUI | NwkSKey | AppSKey + 02, 02, 02, 02, + 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, + 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, + }, + + WantError: pointer.String(string(errors.Operational)), + WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/up"), + WantRegistration: nil, + WantPacket: nil, + }, + } for i, test := range tests { // Describe From 8305955891db19229f6e147a67739a9cb6f1e5eb Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 14:52:30 +0100 Subject: [PATCH 0718/2266] [refactor] Also test topic() method --- .../adapters/mqtt/handlers/activation_test.go | 16 ++++++++++++++++ refactor/adapters/mqtt/handlers/helpers_test.go | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go index 88cdb0c9d..27a03dbd4 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -14,6 +14,22 @@ import ( "github.com/brocaar/lorawan" ) +func TestActionTopic(t *testing.T) { + wantTopic := "+/devices/+/activations" + + // Describe + Desc(t, "Topic should equal: %s", wantTopic) + + // Build + handler := Activation{} + + // Operate + topic := handler.Topic() + + // Check + checkTopics(t, wantTopic, topic) +} + func TestActivationHandle(t *testing.T) { tests := []struct { Desc string // The test's description diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/refactor/adapters/mqtt/handlers/helpers_test.go index e0441591f..75119a7fb 100644 --- a/refactor/adapters/mqtt/handlers/helpers_test.go +++ b/refactor/adapters/mqtt/handlers/helpers_test.go @@ -193,9 +193,19 @@ func checkErrors(t *testing.T, want *string, got error) { Ok(t, "Check Errors") return } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) } +func checkTopics(t *testing.T, want string, got string) { + if want == got { + Ok(t, "Check Topics") + return + } + + Ko(t, "Topic does not match expectation.\nWant: %s\nGot: %s", want, got) +} + func checkRegistrations(t *testing.T, want core.Registration, got core.Registration) { // Check if interfaces are nil if got == nil { From f045216f8e7647fbc68266a50f3fa9a3ee43f0b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 14:57:55 +0100 Subject: [PATCH 0719/2266] [refactor] Fix issues in activation, make it pass --- refactor/adapters/mqtt/handlers/activation.go | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/refactor/adapters/mqtt/handlers/activation.go b/refactor/adapters/mqtt/handlers/activation.go index 3d0876703..f00ec8c00 100644 --- a/refactor/adapters/mqtt/handlers/activation.go +++ b/refactor/adapters/mqtt/handlers/activation.go @@ -10,49 +10,57 @@ import ( MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) type Activation struct{} +// Topic implements the mqtt.Handler interface func (a Activation) Topic() string { return "+/devices/+/activations" } +// Handle implements the mqtt.Handler interface func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { topicInfos := strings.Split(msg.Topic(), "/") + + if len(topicInfos) != 4 { + return errors.New(errors.Structural, "Invalid given topic") + } + appEUIStr := topicInfos[0] devEUIStr := topicInfos[2] if devEUIStr != "personalized" { - // TODO Log warning - //a.ctx.WithField("Device Address", devEUI).Warn("OTAA not yet supported. Unable to register device") - return nil + return errors.New(errors.Implementation, "OTAA not yet supported. Unable to register device") } payload := msg.Payload() if len(payload) != 36 { - // TODO Log warning - //a.ctx.WithField("Payload", payload).Error("Invalid registration payload") - return nil + return errors.New(errors.Structural, "Invalid registration payload") } var appEUI lorawan.EUI64 var devEUI lorawan.EUI64 var nwkSKey lorawan.AES128Key var appSKey lorawan.AES128Key - copy(appEUI[:], []byte(appEUIStr)) copy(devEUI[4:], msg.Payload()[:4]) copy(nwkSKey[:], msg.Payload()[4:20]) copy(appSKey[:], msg.Payload()[20:]) + data, err := hex.DecodeString(appEUIStr) + if err != nil || len(data) != 8 { + return errors.New(errors.Structural, "Invalid application EUI") + } + copy(appEUI[:], data[:]) + devEUIStr = hex.EncodeToString(devEUI[:]) topic := fmt.Sprintf("%s/%s/%s/%s", appEUIStr, "devices", devEUIStr, "up") token := client.Subscribe(topic, 2, a.handleReception(chpkt)) if token.Wait() && token.Error() != nil { // TODO Log Error - // a.ctx.WithError(token.Error()).Error("Unable to subscribe") - return nil + return errors.New(errors.Operational, token.Error()) } chreg <- RegReq{ From 580aca532a706681ddc0699a2ddedfba249b6d34 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 15:12:09 +0100 Subject: [PATCH 0720/2266] [refactor] Use of a facade in the storage util --- utils/storage/storage.go | 44 ++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index dcc1af60c..e14aba18a 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -15,14 +15,24 @@ import ( "github.com/boltdb/bolt" ) -// storageEntry offers a friendly interface on which the storage will operate. -// Basically, a storageEntry is nothing more than a binary marshaller/unmarshaller. -type StorageEntry interface { +// Entry offers a friendly interface on which the storage will operate. +// Basically, a Entry is nothing more than a binary marshaller/unmarshaller. +type Entry interface { encoding.BinaryMarshaler encoding.BinaryUnmarshaler } -type Interface struct { +type Interface interface { + Init(name string) error + Store(name string, key []byte, entries []Entry) error + Replace(name string, key []byte, entries []Entry) error + Lookup(name string, key []byte, shape Entry) (interface{}, error) + Flush(name string, key []byte) error + Reset(name string) error + Close() error +} + +type store struct { db *bolt.DB } @@ -30,13 +40,13 @@ type Interface struct { func New(name string) (Interface, error) { db, err := bolt.Open(name, 0600, &bolt.Options{Timeout: time.Second}) if err != nil { - return Interface{}, errors.New(errors.Operational, err) + return nil, errors.New(errors.Operational, err) } - return Interface{db}, nil + return store{db}, nil } // Init initializes the given bolt bucket by creating (if not already exists) an empty bucket -func (itf Interface) Init(bucketName string) error { +func (itf store) Init(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) return err @@ -45,7 +55,7 @@ func (itf Interface) Init(bucketName string) error { // Store put a new set of entries in the given bolt database. It adds the entries to an existing set // or create a new set. -func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry) error { +func (itf store) Store(bucketName string, key []byte, entries []Entry) error { var marshalled [][]byte for _, entry := range entries { @@ -83,7 +93,7 @@ func (itf Interface) Store(bucketName string, key []byte, entries []StorageEntry // The shape is used as a template for retrieving and creating the data. All entries extracted from // the database will be interpreted as instance of shape and the return result will be a slice of // the same type of shape. -func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) (interface{}, error) { +func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{}, error) { // First, lookup the raw entries var rawEntry []byte err := itf.db.View(func(tx *bolt.Tx) error { @@ -109,7 +119,7 @@ func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) ( for { r.Read(func(data []byte) { entry := reflect.New(entryType).Interface() - entry.(StorageEntry).UnmarshalBinary(data) + entry.(Entry).UnmarshalBinary(data) entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) }) if err = r.Err(); err != nil { @@ -123,7 +133,7 @@ func (itf Interface) Lookup(bucketName string, key []byte, shape StorageEntry) ( } // Flush empties each entry of a bucket associated to a given device -func (itf Interface) Flush(bucketName string, key []byte) error { +func (itf store) Flush(bucketName string, key []byte) error { return itf.db.Update(func(tx *bolt.Tx) error { bucket := tx.Bucket([]byte(bucketName)) if bucket == nil { @@ -137,7 +147,7 @@ func (itf Interface) Flush(bucketName string, key []byte) error { } // Replace stores entries in the database by replacing them by a new set -func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEntry) error { +func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { var marshalled [][]byte for _, entry := range entries { @@ -172,7 +182,7 @@ func (itf Interface) Replace(bucketName string, key []byte, entries []StorageEnt } // Reset resets a given bucket from a given bolt database -func (itf Interface) Reset(bucketName string) error { +func (itf store) Reset(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { if err := tx.DeleteBucket([]byte(bucketName)); err != nil { return errors.New(errors.Operational, err) @@ -183,3 +193,11 @@ func (itf Interface) Reset(bucketName string) error { return nil }) } + +// Close terminates the db connection +func (itf store) Close() error { + if err := itf.db.Close(); err != nil { + return errors.New(errors.Operational, err) + } + return nil +} From cf9fae805a731de10ed32dbc859eed8b3fc37feb Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 15:13:30 +0100 Subject: [PATCH 0721/2266] [refactor] Remove old and not accurate mqttStorage --- refactor/adapters/mqtt/mqttStorage.go | 102 -------------------------- 1 file changed, 102 deletions(-) delete mode 100644 refactor/adapters/mqtt/mqttStorage.go diff --git a/refactor/adapters/mqtt/mqttStorage.go b/refactor/adapters/mqtt/mqttStorage.go deleted file mode 100644 index c4e90688f..000000000 --- a/refactor/adapters/mqtt/mqttStorage.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - db "github.com/TheThingsNetwork/ttn/utils/storage" -) - -// !!!!!!!!!!!!!!!!!!!!!!!! -// NOTE THIS IS NOT THE ADAPTER ROLE -> SHOULD BE MOVED TO HANDLER -// !!!!!!!!!!!!!!!!!!!!!!!! - -// Storage defines an interface for the mqtt adapter -type Storage interface { - Push(topic string, data []byte) error - Pull(topic string) ([]byte, error) -} - -// type storage materializes a concrete mqtt.Storage -type storage struct { - db.Interface - Name string -} - -// Storage entry implements the storage.StorageEntry interface -type storageEntry struct { - Data []byte -} - -// NewStorage creates a new mqtt.Storage -func NewStorage(name string) (Storage, error) { - itf, err := db.New(name) - if err != nil { - return nil, errors.New(ErrFailedOperation, err) - } - - tableName := "mqtt_adapter" - if err := itf.Init(tableName); err != nil { - return nil, errors.New(ErrFailedOperation, err) - } - return storage{Interface: itf, Name: tableName}, nil -} - -// Push implements the Storage interface -func (s storage) Push(topic string, data []byte) error { - err := s.Store(s.Name, []byte(topic), []db.StorageEntry{&storageEntry{data}}) - if err != nil { - return errors.New(ErrFailedOperation, err) - } - return nil -} - -// Pull implements the Storage interface -func (s storage) Pull(topic string) ([]byte, error) { - entries, err := s.Lookup(s.Name, []byte(topic), &storageEntry{}) - if err != nil { - return nil, errors.New(ErrFailedOperation, err) - } - - packets, ok := entries.([]*storageEntry) - if !ok { - return nil, errors.New(ErrFailedOperation, "Unable to retrieve data from db") - } - - // NOTE: one day, those entry will be more complicated, with a ttl. - // Here's the place where we should check for that. Cheers. - if len(packets) == 0 { - return nil, errors.New(ErrWrongBehavior, fmt.Sprintf("Entry not found for %s", topic)) - } - - pkt := packets[0] - - var newEntries []db.StorageEntry - for _, p := range packets[1:] { - newEntries = append(newEntries, p) - } - - if err := s.Replace(s.Name, []byte(topic), newEntries); err != nil { - return nil, errors.New(ErrFailedOperation, "Unable to restore data in db") - } - - return pkt.Data, nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e storageEntry) MarshalBinary() ([]byte, error) { - return e.Data, nil -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *storageEntry) UnmarshalBinary(data []byte) error { - if e == nil { - return errors.New(ErrInvalidStructure, "Unable to unmarshal nil entry") - } - e.Data = data - return nil -} From 74efb4d32568d9b99e194ca6d08b277703ea889f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 15:19:52 +0100 Subject: [PATCH 0722/2266] [refactor] Write handler backbone --- refactor/components/handler/handler.go | 31 ++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 refactor/components/handler/handler.go diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go new file mode 100644 index 000000000..dcff9b872 --- /dev/null +++ b/refactor/components/handler/handler.go @@ -0,0 +1,31 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + . "github.com/TheThingsNetwork/ttn/refactor" +) + +// component implements the core.Component interface +type component struct{} + +// New construct a new Handler component from ... +func New() (Component, error) { + return nil, nil +} + +// Register implements the core.Component interface +func (h component) Register(reg Registration, an AckNacker) error { + return nil +} + +// HandleUp implements the core.Component interface +func (h component) HandleUp(p []byte, an AckNacker, up Adapter) error { + return nil +} + +// HandleDown implements the core.Component interface +func (h component) HandleDown(p []byte, an AckNacker, down Adapter) error { + return nil +} From 73adf91443f85602c2c17f0a0c0e46b321daab9f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 18:15:53 +0100 Subject: [PATCH 0723/2266] [refactor] Move metadata in their own file --- refactor/interfaces.go | 22 ---------------------- refactor/metadata.go | 23 +++++++++++++++++++++++ 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/refactor/interfaces.go b/refactor/interfaces.go index d4b8fbd7d..284321bd9 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -6,7 +6,6 @@ package refactor import ( "encoding" "fmt" - "time" "github.com/brocaar/lorawan" ) @@ -50,24 +49,3 @@ type Registration interface { type Recipient interface { encoding.BinaryMarshaler } - -type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) -} diff --git a/refactor/metadata.go b/refactor/metadata.go index e3ac73b56..fe8f1dca0 100644 --- a/refactor/metadata.go +++ b/refactor/metadata.go @@ -12,6 +12,29 @@ import ( "github.com/TheThingsNetwork/ttn/utils/pointer" ) +type Metadata struct { + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +// TODO -> Metadata should implements a less byte-consuming MarshalBinary method MarshalJSON() + // metadata allows us to inherit Metadata in metadataProxy but only by extending the exported // attributes of Metadata such that, they are still parsed by the json.Marshaller / // json.Unmarshaller though we do not end up with a recursive hellish error. From 39bf129c4ed9ef8497b92080e93cef96f324615c Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 18:17:55 +0100 Subject: [PATCH 0724/2266] [refactor] Rewrite RPacket --- refactor/bpacket.go | 48 --------------- refactor/hpacket.go | 48 --------------- refactor/packets.go | 116 ++++++++++++++++++++++++++++++++++ refactor/rpacket.go | 147 -------------------------------------------- 4 files changed, 116 insertions(+), 243 deletions(-) delete mode 100644 refactor/bpacket.go delete mode 100644 refactor/hpacket.go create mode 100644 refactor/packets.go delete mode 100644 refactor/rpacket.go diff --git a/refactor/bpacket.go b/refactor/bpacket.go deleted file mode 100644 index d4cc2be72..000000000 --- a/refactor/bpacket.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "encoding" - - "github.com/brocaar/lorawan" -) - -// type BPacket materializes packets manipulated by the broker and corresponding adapter handlers -type BPacket struct{} - -// FCnt implements the core.Packet interface -func (p BPacket) FCnt() (uint32, error) { - return 0, nil -} - -// Payload implements the core.Packet interface -func (p BPacket) Payload() encoding.BinaryMarshaler { - return nil -} - -// Metadata implements the core.Packet interface -func (p BPacket) Metadata() []Metadata { - return nil -} - -// AppEUI implements the core.Packet interface -func (p BPacket) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, nil -} - -// DevEUI implements the core.Packet interface -func (p BPacket) DevEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p BPacket) MarshalBinary() ([]byte, error) { - return nil, nil -} - -// MarshalBinary implements the encoding.BinaryUnMarshaler interface -func (p *BPacket) UnmarshalBinary(data []byte) error { - return nil -} diff --git a/refactor/hpacket.go b/refactor/hpacket.go deleted file mode 100644 index c57ce9a15..000000000 --- a/refactor/hpacket.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "encoding" - - "github.com/brocaar/lorawan" -) - -// type HPacket materializes packets manipulated by the handler and corresponding adapter handlers -type HPacket struct{} - -// FCnt implements the core.Packet interface -func (p HPacket) FCnt() (uint32, error) { - return 0, nil -} - -// Payload implements the core.Packet interface -func (p HPacket) Payload() encoding.BinaryMarshaler { - return nil -} - -// Metadata implements the core.Packet interface -func (p HPacket) Metadata() []Metadata { - return nil -} - -// AppEUI implements the core.Packet interface -func (p HPacket) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, nil -} - -// DevEUI implements the core.Packet interface -func (p HPacket) DevEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p HPacket) MarshalBinary() ([]byte, error) { - return nil, nil -} - -// MarshalBinary implements the encoding.BinaryUnMarshaler interface -func (p *HPacket) UnmarshalBinary(data []byte) error { - return nil -} diff --git a/refactor/packets.go b/refactor/packets.go new file mode 100644 index 000000000..92b83579d --- /dev/null +++ b/refactor/packets.go @@ -0,0 +1,116 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" + "github.com/brocaar/lorawan" +) + +type RPacket interface { + Packet + Metadata() Metadata + Payload() lorawan.PHYPayload +} + +// rpacket implements the core.RPacket interface +type rpacket struct { + metadata Metadata + payload lorawan.PHYPayload +} + +// Metadata implements the core.RPacket interface +func (p rpacket) Metadata() Metadata { + return p.metadata +} + +// Payload implements the core.RPacket interface +func (p rpacket) Payload() lorawan.PHYPayload { + return p.payload +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p rpacket) MarshalBinary() ([]byte, error) { + var mtype byte + switch p.payload.MHDR.MType { + case lorawan.JoinRequest: + fallthrough + case lorawan.UnconfirmedDataUp: + fallthrough + case lorawan.ConfirmedDataUp: + mtype = 1 // Up + case lorawan.JoinAccept: + fallthrough + case lorawan.UnconfirmedDataDown: + fallthrough + case lorawan.ConfirmedDataDown: + mtype = 2 // Down + default: + msg := fmt.Sprintf("Unsupported mtype: %s", p.payload.MHDR.MType.String()) + return nil, errors.New(errors.Implementation, msg) + } + + dataMetadata, err := p.metadata.MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + dataPayload, err := p.payload.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw := readwriter.New(nil) + rw.Write([]byte{mtype}) + rw.Write(dataMetadata) + rw.Write(dataPayload) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *rpacket) UnmarshalBinary(data []byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil packet") + } + var isUp bool + rw := readwriter.New(data) + rw.Read(func(data []byte) { + if data[0] == 1 { + isUp = true + } + }) + + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + + var dataPayload []byte + rw.Read(func(data []byte) { dataPayload = data }) + + if rw.Err() != nil { + return errors.New(errors.Structural, rw.Err()) + } + + p.metadata = Metadata{} + if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { + return errors.New(errors.Structural, err) + } + + p.payload = lorawan.NewPHYPayload(isUp) + if err := p.payload.UnmarshalBinary(dataPayload); err != nil { + return errors.New(errors.Structural, err) + } + + return nil +} + +// String implements the Stringer interface +func (p rpacket) String() string { + str := "Packet {" + str += fmt.Sprintf("\n\t%s}", p.metadata.String()) + str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) + return str +} diff --git a/refactor/rpacket.go b/refactor/rpacket.go deleted file mode 100644 index bfb772228..000000000 --- a/refactor/rpacket.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "encoding" - "encoding/base64" - "encoding/json" - "fmt" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// type RPacket materializes packets manipulated by the router and corresponding adapter handlers -type RPacket struct { - metadata Metadata - payload lorawan.PHYPayload -} - -func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) RPacket { - return RPacket{ - metadata: metadata, - payload: payload, - } -} - -// String implements the Stringer interface -func (p RPacket) String() string { - str := "Packet {" - str += fmt.Sprintf("\n\t%s}", p.metadata.String()) - str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) - return str -} - -// Payload implements the core.Packet interface -func (p RPacket) Payload() encoding.BinaryMarshaler { - return p.payload -} - -// Metadata implements the core.Packet interface -func (p RPacket) Metadata() Metadata { - return p.metadata -} - -// DevEUI implements the core.Addressable interface -func (p RPacket) DevEUI() (lorawan.EUI64, error) { - if p.payload.MACPayload == nil { - return lorawan.EUI64{}, errors.New(errors.Structural, "MACPAyload should not be empty") - } - - macpayload, ok := p.payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return lorawan.EUI64{}, errors.New(errors.Structural, "Packet does not carry a MACPayload") - } - - var devEUI lorawan.EUI64 - copy(devEUI[4:], macpayload.FHDR.DevAddr[:]) - return devEUI, nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p RPacket) MarshalBinary() ([]byte, error) { - return json.Marshal(p) -} - -// MarshalBinary implements the encoding.BinaryUnMarshaler interface -func (p *RPacket) UnmarshalBinary(data []byte) error { - return json.Unmarshal(data, p) -} - -// MarshalJSON implements the json.Marshaler interface -func (p RPacket) MarshalJSON() ([]byte, error) { - rawMetadata, err := json.Marshal(p.metadata) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - rawPayload, err := p.payload.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - strPayload := base64.StdEncoding.EncodeToString(rawPayload) - return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil -} - -// UnmarshalJSON impements the json.Unmarshaler interface -func (p *RPacket) UnmarshalJSON(raw []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal a nil packet") - } - - // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink - // packet. Thus, we'll assume it's an uplink packet (because that's the case most of the time) - // and check whether or not the unmarshalling process was okay. - var proxy struct { - Payload string `json:"payload"` - Metadata Metadata - } - - err := json.Unmarshal(raw, &proxy) - if err != nil { - return errors.New(errors.Structural, err) - } - - rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) - if err != nil { - return errors.New(errors.Structural, err) - } - - payload := lorawan.NewPHYPayload(true) // true -> uplink - if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.New(errors.Structural, err) - } - - // Now, we check the nature of the decoded payload - switch payload.MHDR.MType.String() { - case "JoinAccept": - fallthrough - case "UnconfirmedDataDown": - fallthrough - case "ConfirmedDataDown": - // JoinAccept, UnconfirmedDataDown and ConfirmedDataDown are all downlink messages. - // We thus have to unmarshall properly - payload = lorawan.NewPHYPayload(false) // false -> downlink - if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.New(errors.Structural, err) - } - case "JoinRequest": - fallthrough - case "UnconfirmedDataUp": - fallthrough - case "ConfirmedDataUp": - // JoinRequest, UnconfirmedDataUp and ConfirmedDataUp are all uplink messages. - // There's nothing to do, we've already handled them. - - case "Proprietary": - // Proprietary can be either downlink or uplink. Right now, we do not have any message of - // that type and thus, we just don't know how to handle them. Let's throw an error. - return errors.New(errors.Implementation, "Unsupported MType 'Proprietary'") - } - - // Packet = Payload + Metadata - p.payload = payload - p.metadata = proxy.Metadata - return nil -} From 6eafd6966d8a60a2912c17bffe3be6e0ffb5ab19 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 19:09:07 +0100 Subject: [PATCH 0725/2266] [refactor] Write and implement BPacket + small change to RPacket --- refactor/packets.go | 80 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/refactor/packets.go b/refactor/packets.go index 92b83579d..c9c7e5ca5 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -15,6 +15,7 @@ type RPacket interface { Packet Metadata() Metadata Payload() lorawan.PHYPayload + DevEUI() lorawan.EUI64 } // rpacket implements the core.RPacket interface @@ -23,6 +24,30 @@ type rpacket struct { payload lorawan.PHYPayload } +// NewRPacket construct a new router packet given a payload and metadata +func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) { + packet := rpacket{payload: payload, metadata: metadata} + + // Check and extract the devEUI + if payload.MACPayload == nil { + return nil, errors.New(errors.Structural, "MACPAyload should not be empty") + } + + _, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") + } + + return &packet, nil +} + +// DevEUI implements the core.BPacket interface +func (p rpacket) DevEUI() lorawan.EUI64 { + var devEUI lorawan.EUI64 + copy(devEUI[4:], p.payload.MACPayload.(*lorawan.MACPayload).FHDR.DevAddr[:]) + return devEUI +} + // Metadata implements the core.RPacket interface func (p rpacket) Metadata() Metadata { return p.metadata @@ -114,3 +139,58 @@ func (p rpacket) String() string { str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) return str } + +type BPacket interface { + Commands() []lorawan.MACCommand + DevEUI() lorawan.EUI64 + FCnt() uint32 + Metadata() Metadata + Payload() []byte + ValidateMIC(key lorawan.AES128Key) (bool, error) +} + +// bpacket implements the core.BPacket interface +type bpacket struct { + rpacket +} + +// NewBPacket constructs a new broker packets given a payload and metadata +func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) { + packet, err := NewRPacket(payload, metadata) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + macPayload := packet.Payload().MACPayload.(*lorawan.MACPayload) + if len(macPayload.FRMPayload) != 1 { + return nil, errors.New(errors.Structural, "Invalid frame payload. Expected exactly 1") + } + + _, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) + if !ok { + return nil, errors.New(errors.Structural, "Invalid frame payload. Expected only data") + } + + return bpacket{rpacket: packet.(rpacket)}, nil +} + +// FCnt implements the core.BPacket interface +func (p bpacket) FCnt() uint32 { + return p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt +} + +// Payload implements the core.BPacket interface +func (p bpacket) Payload() []byte { + macPayload := p.rpacket.payload.MACPayload.(*lorawan.MACPayload) + return macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes +} + +// ValidateMIC implements the core.BPacket interface +func (p bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { + return p.rpacket.payload.ValidateMIC(key) +} + +// Commands implements the core.BPacket interface +func (p bpacket) Commands() []lorawan.MACCommand { + return p.rpacket.payload.MACPayload.(*lorawan.MACPayload).FHDR.FOpts +} From 957121bdf8f8bfad95a492d591c7746c627bea6d Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 19:41:06 +0100 Subject: [PATCH 0726/2266] [refactor] Write and implements HPacket --- refactor/packets.go | 93 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/refactor/packets.go b/refactor/packets.go index c9c7e5ca5..b828c0a1d 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -141,6 +141,7 @@ func (p rpacket) String() string { } type BPacket interface { + Packet Commands() []lorawan.MACCommand DevEUI() lorawan.EUI64 FCnt() uint32 @@ -194,3 +195,95 @@ func (p bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { func (p bpacket) Commands() []lorawan.MACCommand { return p.rpacket.payload.MACPayload.(*lorawan.MACPayload).FHDR.FOpts } + +type HPacket interface { + Packet + AppEUI() lorawan.EUI64 + DevEUI() lorawan.EUI64 + Payload() []byte // FRMPayload + Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up +} + +// hpacket implements the HPacket interface +type hpacket struct { + appEUI lorawan.EUI64 + devEUI lorawan.EUI64 + payload []byte + metadata Metadata +} + +// NewHPacket constructs a new Handler packet +func NewHPacket(a lorawan.EUI64, d lorawan.EUI64, p []byte, m Metadata) HPacket { + if p == nil { + p = make([]byte, 0) + } + return &hpacket{ + appEUI: a, + devEUI: d, + payload: p, + metadata: m, + } +} + +// AppEUI implements the core.HPacket interface +func (p hpacket) AppEUI() lorawan.EUI64 { + return p.appEUI +} + +// DevEUI implements the core.HPacket interface +func (p hpacket) DevEUI() lorawan.EUI64 { + return p.devEUI +} + +// Payload implements the core.HPacket interface +func (p hpacket) Payload() []byte { + return p.payload +} + +// Metadata implements the core.Metadata interface +func (p hpacket) Metadata() Metadata { + return p.metadata +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p hpacket) MarshalBinary() ([]byte, error) { + dataMetadata, err := p.Metadata().MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw := readwriter.New(nil) + rw.Write(p.appEUI) + rw.Write(p.devEUI) + rw.Write(p.payload) + rw.Write(dataMetadata) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *hpacket) UnmarshalBinary(data []byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil hpacket") + } + + rw := readwriter.New(data) + rw.Read(func(data []byte) { copy(p.appEUI[:], data) }) + rw.Read(func(data []byte) { copy(p.devEUI[:], data) }) + rw.Read(func(data []byte) { p.payload = data }) + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { + return errors.New(errors.Structural, err) + } + return nil +} + +// String implements the fmt.Stringer interface +func (p hpacket) String() string { + str := "Packet {" + str += fmt.Sprintf("\n\t%s}", p.metadata.String()) + str += fmt.Sprintf("\n\tAppEUI:%+x\n,", p.appEUI) + str += fmt.Sprintf("\n\tDevEUI:%+x\n,", p.devEUI) + str += fmt.Sprintf("\n\tPayload:%v\n}", p.Payload) + return str +} From 9115ecdbb0a623a48010982e954d3bfe27279c5a Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 20:06:22 +0100 Subject: [PATCH 0727/2266] [refactor] Write and implement APacket --- refactor/packets.go | 96 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/refactor/packets.go b/refactor/packets.go index b828c0a1d..a8c17fbbd 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -5,6 +5,7 @@ package refactor import ( "fmt" + "io" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" @@ -287,3 +288,98 @@ func (p hpacket) String() string { str += fmt.Sprintf("\n\tPayload:%v\n}", p.Payload) return str } + +type APacket interface { + Packet + Payload() []byte + Metadata() []Metadata +} + +// apacket implements the core.APacket interface +type apacket struct { + payload []byte + metadata []Metadata +} + +// NewAPacket constructs a new application packet +func NewAPacket(payload []byte, metadata []Metadata) (APacket, error) { + if len(payload) == 0 { + return nil, errors.New(errors.Structural, "Application packet must hold a payload") + } + + return &apacket{payload: payload, metadata: metadata}, nil +} + +// Payload implements the core.APacket interface +func (p apacket) Payload() []byte { + return p.payload +} + +// Metadata implements the core.Metadata interface +func (p apacket) Metadata() []Metadata { + return p.metadata +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p apacket) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + for _, m := range p.metadata { + data, err := m.MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + rw.Write(data) + } + data, err := rw.Bytes() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw = readwriter.New(nil) + rw.Write(p.payload) + rw.Write(data) + + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *apacket) UnmarshalBinary(data []byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil apacket") + } + + var dataMetadata []byte + rw := readwriter.New(data) + rw.Read(func(data []byte) { p.payload = data }) + rw.Read(func(data []byte) { dataMetadata = data }) + if rw.Err() != nil { + return errors.New(errors.Structural, rw.Err()) + } + + p.metadata = make([]Metadata, 0) + rw = readwriter.New(dataMetadata) + for { + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + if rw.Err() != nil { + err, ok := rw.Err().(errors.Failure) + if ok && err.Fault == io.EOF { + break + } + return errors.New(errors.Structural, rw.Err()) + } + metadata := new(Metadata) + if err := metadata.UnmarshalJSON(dataMetadata); err != nil { + return errors.New(errors.Structural, err) + } + + p.metadata = append(p.metadata, *metadata) + } + + return nil +} + +// String implements the fmt.Stringer interface +func (p apacket) String() string { + return "TODO" +} From ece23de650efb6a1cbb8a9dacd5e6e8979215b43 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 25 Feb 2016 20:41:01 +0100 Subject: [PATCH 0728/2266] [refactor] Write and implements acceptPacket and joinPacket --- refactor/packets.go | 184 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 36 deletions(-) diff --git a/refactor/packets.go b/refactor/packets.go index a8c17fbbd..97fdd2e39 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -207,40 +207,25 @@ type HPacket interface { // hpacket implements the HPacket interface type hpacket struct { - appEUI lorawan.EUI64 - devEUI lorawan.EUI64 - payload []byte + *basehpacket metadata Metadata } // NewHPacket constructs a new Handler packet -func NewHPacket(a lorawan.EUI64, d lorawan.EUI64, p []byte, m Metadata) HPacket { - if p == nil { - p = make([]byte, 0) +func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, metadata Metadata) HPacket { + if payload == nil { + payload = make([]byte, 0) } return &hpacket{ - appEUI: a, - devEUI: d, - payload: p, - metadata: m, + basehpacket: &basehpacket{ + appEUI: appEUI, + devEUI: devEUI, + payload: payload, + }, + metadata: metadata, } } -// AppEUI implements the core.HPacket interface -func (p hpacket) AppEUI() lorawan.EUI64 { - return p.appEUI -} - -// DevEUI implements the core.HPacket interface -func (p hpacket) DevEUI() lorawan.EUI64 { - return p.devEUI -} - -// Payload implements the core.HPacket interface -func (p hpacket) Payload() []byte { - return p.payload -} - // Metadata implements the core.Metadata interface func (p hpacket) Metadata() Metadata { return p.metadata @@ -253,24 +238,22 @@ func (p hpacket) MarshalBinary() ([]byte, error) { return nil, errors.New(errors.Structural, err) } - rw := readwriter.New(nil) - rw.Write(p.appEUI) - rw.Write(p.devEUI) - rw.Write(p.payload) + data, err := p.basehpacket.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw := readwriter.New(data) rw.Write(dataMetadata) return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil hpacket") - } - rw := readwriter.New(data) - rw.Read(func(data []byte) { copy(p.appEUI[:], data) }) - rw.Read(func(data []byte) { copy(p.devEUI[:], data) }) - rw.Read(func(data []byte) { p.payload = data }) + rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) + rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) + rw.Read(func(data []byte) { p.basehpacket.payload = data }) var dataMetadata []byte rw.Read(func(data []byte) { dataMetadata = data }) if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { @@ -383,3 +366,132 @@ func (p *apacket) UnmarshalBinary(data []byte) error { func (p apacket) String() string { return "TODO" } + +type JoinPacket interface { + Packet + AppEUI() lorawan.EUI64 + DevEUI() lorawan.EUI64 + DevNonce() [2]byte + Metadata() Metadata // Rssi + DutyCycle +} + +// joinPacket implements the core.JoinPacket interface +type joinpacket struct { + *basehpacket + metadata Metadata +} + +// NewJoinPacket constructs a new JoinPacket +func NewJoinPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, metadata Metadata) JoinPacket { + return &joinpacket{ + basehpacket: &basehpacket{ + appEUI: appEUI, + devEUI: devEUI, + payload: devNonce[:], + }, + metadata: metadata, + } +} + +// DevNonce implements the core.JoinPacket interface +func (p joinpacket) DevNonce() [2]byte { + return [2]byte{p.basehpacket.payload[0], p.basehpacket.payload[1]} +} + +// Metadata implements the core.JoinPacket interface +func (p joinpacket) Metadata() Metadata { + return p.metadata +} + +// String implements the fmt.Stringer interface +func (p joinpacket) String() string { + return "TODO" +} + +type AcceptPacket interface { + Packet + AppEUI() lorawan.EUI64 + DevEUI() lorawan.EUI64 + Payload() []byte + NwkSKey() lorawan.AES128Key +} + +// acceptpacket implements the core.AcceptPacket interface +type acceptpacket struct { + *basehpacket + nwkSKey lorawan.AES128Key +} + +// NewAcceptPacket constructs a new AcceptPacket +func NewAcceptPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, nwkSKey lorawan.AES128Key) (AcceptPacket, error) { + if len(payload) == 0 { + return nil, errors.New(errors.Structural, "Payload cannot be empty") + } + + return &acceptpacket{ + basehpacket: &basehpacket{ + appEUI: appEUI, + devEUI: devEUI, + payload: payload, + }, + nwkSKey: nwkSKey, + }, nil +} + +// NwkSKey implements the core.AcceptPacket interface +func (p acceptpacket) NwkSKey() lorawan.AES128Key { + return p.nwkSKey +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p acceptpacket) MarshalBinary() ([]byte, error) { + data, err := p.basehpacket.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + rw := readwriter.New(data) + rw.Write(p.nwkSKey) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *acceptpacket) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) + rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) + rw.Read(func(data []byte) { p.basehpacket.payload = data }) + rw.Read(func(data []byte) { copy(p.nwkSKey[:], data) }) + return rw.Err() +} + +// String implements the fmt.Stringer interface +func (p acceptpacket) String() string { + return "TODO" +} + +// basehpacket is used to compose other packets +type basehpacket struct { + appEUI lorawan.EUI64 + devEUI lorawan.EUI64 + payload []byte +} + +func (p basehpacket) AppEUI() lorawan.EUI64 { + return p.appEUI +} + +func (p basehpacket) DevEUI() lorawan.EUI64 { + return p.devEUI +} + +func (p basehpacket) Payload() []byte { + return p.payload +} + +func (p basehpacket) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(p.appEUI) + rw.Write(p.devEUI) + rw.Write(p.payload) + return rw.Bytes() +} From 6edbff83f16a694e7c6c9650e57e7358d356edc9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 10:53:36 +0100 Subject: [PATCH 0729/2266] [refactor] Add Types to packets marshallings --- refactor/interfaces.go | 8 +- refactor/packets.go | 327 ++++++++++++++++++++++++++++++----------- 2 files changed, 242 insertions(+), 93 deletions(-) diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 284321bd9..9f6eb5ed9 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -23,10 +23,10 @@ type AckNacker interface { type Adapter interface { Send(p Packet, r ...Recipient) ([]byte, error) + //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) GetRecipient(raw []byte) (Recipient, error) Next() ([]byte, AckNacker, error) NextRegistration() (Registration, AckNacker, error) - //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) } type Packet interface { @@ -35,15 +35,11 @@ type Packet interface { } type Addressable interface { - DevEUI() (lorawan.EUI64, error) + DevEUI() lorawan.EUI64 } type Registration interface { Recipient() Recipient - AppEUI() (lorawan.EUI64, error) - AppSKey() (lorawan.AES128Key, error) - DevEUI() (lorawan.EUI64, error) - NwkSKey() (lorawan.AES128Key, error) } type Recipient interface { diff --git a/refactor/packets.go b/refactor/packets.go index 97fdd2e39..f64856791 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -12,6 +12,15 @@ import ( "github.com/brocaar/lorawan" ) +const ( + TYPE_RPACKET byte = iota + TYPE_BPACKET + TYPE_HPACKET + TYPE_APACKET + TYPE_JPACKET + TYPE_CPACKET +) + type RPacket interface { Packet Metadata() Metadata @@ -21,15 +30,11 @@ type RPacket interface { // rpacket implements the core.RPacket interface type rpacket struct { - metadata Metadata - payload lorawan.PHYPayload + *baserpacket } // NewRPacket construct a new router packet given a payload and metadata func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) { - packet := rpacket{payload: payload, metadata: metadata} - - // Check and extract the devEUI if payload.MACPayload == nil { return nil, errors.New(errors.Structural, "MACPAyload should not be empty") } @@ -39,62 +44,22 @@ func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") } - return &packet, nil -} - -// DevEUI implements the core.BPacket interface -func (p rpacket) DevEUI() lorawan.EUI64 { - var devEUI lorawan.EUI64 - copy(devEUI[4:], p.payload.MACPayload.(*lorawan.MACPayload).FHDR.DevAddr[:]) - return devEUI -} - -// Metadata implements the core.RPacket interface -func (p rpacket) Metadata() Metadata { - return p.metadata -} - -// Payload implements the core.RPacket interface -func (p rpacket) Payload() lorawan.PHYPayload { - return p.payload + return &rpacket{ + &baserpacket{ + payload: payload, + metadata: metadata, + }, + }, nil } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p rpacket) MarshalBinary() ([]byte, error) { - var mtype byte - switch p.payload.MHDR.MType { - case lorawan.JoinRequest: - fallthrough - case lorawan.UnconfirmedDataUp: - fallthrough - case lorawan.ConfirmedDataUp: - mtype = 1 // Up - case lorawan.JoinAccept: - fallthrough - case lorawan.UnconfirmedDataDown: - fallthrough - case lorawan.ConfirmedDataDown: - mtype = 2 // Down - default: - msg := fmt.Sprintf("Unsupported mtype: %s", p.payload.MHDR.MType.String()) - return nil, errors.New(errors.Implementation, msg) - } - - dataMetadata, err := p.metadata.MarshalJSON() + data, err := p.baserpacket.MarshalBinary() if err != nil { return nil, errors.New(errors.Structural, err) } - dataPayload, err := p.payload.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - rw := readwriter.New(nil) - rw.Write([]byte{mtype}) - rw.Write(dataMetadata) - rw.Write(dataPayload) - return rw.Bytes() + return append([]byte{TYPE_RPACKET}, data...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface @@ -102,8 +67,13 @@ func (p *rpacket) UnmarshalBinary(data []byte) error { if p == nil { return errors.New(errors.Structural, "Cannot unmarshal nil packet") } + + if len(data) < 1 || data[0] != TYPE_RPACKET { + return errors.New(errors.Structural, "Not a Router packet") + } + var isUp bool - rw := readwriter.New(data) + rw := readwriter.New(data[1:]) rw.Read(func(data []byte) { if data[0] == 1 { isUp = true @@ -153,27 +123,34 @@ type BPacket interface { // bpacket implements the core.BPacket interface type bpacket struct { - rpacket + baserpacket } // NewBPacket constructs a new broker packets given a payload and metadata func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) { - packet, err := NewRPacket(payload, metadata) - if err != nil { - return nil, errors.New(errors.Structural, err) + if payload.MACPayload == nil { + return nil, errors.New(errors.Structural, "MACPAyload should not be empty") + } + + macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") } - macPayload := packet.Payload().MACPayload.(*lorawan.MACPayload) if len(macPayload.FRMPayload) != 1 { return nil, errors.New(errors.Structural, "Invalid frame payload. Expected exactly 1") } - _, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) - if !ok { + if _, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload); !ok { return nil, errors.New(errors.Structural, "Invalid frame payload. Expected only data") } - return bpacket{rpacket: packet.(rpacket)}, nil + return &bpacket{ + baserpacket: baserpacket{ + payload: payload, + metadata: metadata, + }, + }, nil } // FCnt implements the core.BPacket interface @@ -183,18 +160,74 @@ func (p bpacket) FCnt() uint32 { // Payload implements the core.BPacket interface func (p bpacket) Payload() []byte { - macPayload := p.rpacket.payload.MACPayload.(*lorawan.MACPayload) + macPayload := p.baserpacket.payload.MACPayload.(*lorawan.MACPayload) return macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes } // ValidateMIC implements the core.BPacket interface func (p bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { - return p.rpacket.payload.ValidateMIC(key) + return p.baserpacket.payload.ValidateMIC(key) } // Commands implements the core.BPacket interface func (p bpacket) Commands() []lorawan.MACCommand { - return p.rpacket.payload.MACPayload.(*lorawan.MACPayload).FHDR.FOpts + return p.baserpacket.payload.MACPayload.(*lorawan.MACPayload).FHDR.FOpts +} + +// String implements the fmt.Stringer interface +func (p bpacket) String() string { + return "TODO" +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p bpacket) MarshalBinary() ([]byte, error) { + data, err := p.baserpacket.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + return append([]byte{TYPE_BPACKET}, data...), nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *bpacket) UnmarshalBinary(data []byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil packet") + } + + if len(data) < 1 || data[0] != TYPE_BPACKET { + return errors.New(errors.Structural, "Not a Router packet") + } + + var isUp bool + rw := readwriter.New(data[1:]) + rw.Read(func(data []byte) { + if data[0] == 1 { + isUp = true + } + }) + + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + + var dataPayload []byte + rw.Read(func(data []byte) { dataPayload = data }) + + if rw.Err() != nil { + return errors.New(errors.Structural, rw.Err()) + } + + p.metadata = Metadata{} + if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { + return errors.New(errors.Structural, err) + } + + p.payload = lorawan.NewPHYPayload(isUp) + if err := p.payload.UnmarshalBinary(dataPayload); err != nil { + return errors.New(errors.Structural, err) + } + + return nil } type HPacket interface { @@ -243,14 +276,22 @@ func (p hpacket) MarshalBinary() ([]byte, error) { return nil, errors.New(errors.Structural, err) } - rw := readwriter.New(data) + rw := readwriter.New(append([]byte{TYPE_HPACKET}, data...)) rw.Write(dataMetadata) return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil packet") + } + + if len(data) < 1 || data[0] != TYPE_HPACKET { + return errors.New(errors.Structural, "Not a Handler packet") + } + + rw := readwriter.New(data[1:]) rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) rw.Read(func(data []byte) { p.basehpacket.payload = data }) @@ -318,7 +359,7 @@ func (p apacket) MarshalBinary() ([]byte, error) { return nil, errors.New(errors.Structural, err) } - rw = readwriter.New(nil) + rw = readwriter.New([]byte{TYPE_APACKET}) rw.Write(p.payload) rw.Write(data) @@ -331,8 +372,12 @@ func (p *apacket) UnmarshalBinary(data []byte) error { return errors.New(errors.Structural, "Cannot unmarshal nil apacket") } + if len(data) < 1 || data[0] != TYPE_APACKET { + return errors.New(errors.Structural, "Not an Application packet") + } + var dataMetadata []byte - rw := readwriter.New(data) + rw := readwriter.New(data[1:]) rw.Read(func(data []byte) { p.payload = data }) rw.Read(func(data []byte) { dataMetadata = data }) if rw.Err() != nil { @@ -367,7 +412,7 @@ func (p apacket) String() string { return "TODO" } -type JoinPacket interface { +type JPacket interface { Packet AppEUI() lorawan.EUI64 DevEUI() lorawan.EUI64 @@ -376,14 +421,14 @@ type JoinPacket interface { } // joinPacket implements the core.JoinPacket interface -type joinpacket struct { +type jpacket struct { *basehpacket metadata Metadata } // NewJoinPacket constructs a new JoinPacket -func NewJoinPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, metadata Metadata) JoinPacket { - return &joinpacket{ +func NewJPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, metadata Metadata) JPacket { + return &jpacket{ basehpacket: &basehpacket{ appEUI: appEUI, devEUI: devEUI, @@ -394,21 +439,60 @@ func NewJoinPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, } // DevNonce implements the core.JoinPacket interface -func (p joinpacket) DevNonce() [2]byte { +func (p jpacket) DevNonce() [2]byte { return [2]byte{p.basehpacket.payload[0], p.basehpacket.payload[1]} } // Metadata implements the core.JoinPacket interface -func (p joinpacket) Metadata() Metadata { +func (p jpacket) Metadata() Metadata { return p.metadata } +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p jpacket) MarshalBinary() ([]byte, error) { + dataMetadata, err := p.Metadata().MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + data, err := p.basehpacket.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw := readwriter.New(append([]byte{TYPE_JPACKET}, data...)) + rw.Write(dataMetadata) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *jpacket) UnmarshalBinary(data []byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil packet") + } + + if len(data) < 1 || data[0] != TYPE_JPACKET { + return errors.New(errors.Structural, "Not a JoinRequst packet") + } + + rw := readwriter.New(data[1:]) + rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) + rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) + rw.Read(func(data []byte) { p.basehpacket.payload = data }) + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { + return errors.New(errors.Structural, err) + } + return nil +} + // String implements the fmt.Stringer interface -func (p joinpacket) String() string { +func (p jpacket) String() string { return "TODO" } -type AcceptPacket interface { +type CPacket interface { Packet AppEUI() lorawan.EUI64 DevEUI() lorawan.EUI64 @@ -417,18 +501,18 @@ type AcceptPacket interface { } // acceptpacket implements the core.AcceptPacket interface -type acceptpacket struct { +type cpacket struct { *basehpacket nwkSKey lorawan.AES128Key } -// NewAcceptPacket constructs a new AcceptPacket -func NewAcceptPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, nwkSKey lorawan.AES128Key) (AcceptPacket, error) { +// NewAcceptPacket constructs a new CPacket +func NewCPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, nwkSKey lorawan.AES128Key) (CPacket, error) { if len(payload) == 0 { return nil, errors.New(errors.Structural, "Payload cannot be empty") } - return &acceptpacket{ + return &cpacket{ basehpacket: &basehpacket{ appEUI: appEUI, devEUI: devEUI, @@ -439,24 +523,32 @@ func NewAcceptPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, } // NwkSKey implements the core.AcceptPacket interface -func (p acceptpacket) NwkSKey() lorawan.AES128Key { +func (p cpacket) NwkSKey() lorawan.AES128Key { return p.nwkSKey } // MarshalBinary implements the encoding.BinaryMarshaler interface -func (p acceptpacket) MarshalBinary() ([]byte, error) { +func (p cpacket) MarshalBinary() ([]byte, error) { data, err := p.basehpacket.MarshalBinary() if err != nil { return nil, errors.New(errors.Structural, err) } - rw := readwriter.New(data) + rw := readwriter.New(append([]byte{TYPE_CPACKET}, data...)) rw.Write(p.nwkSKey) return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *acceptpacket) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) +func (p *cpacket) UnmarshalBinary(data []byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil packet") + } + + if len(data) < 1 || data[0] != TYPE_CPACKET { + return errors.New(errors.Structural, "Not a JoinAccept packet") + } + + rw := readwriter.New(data[1:]) rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) rw.Read(func(data []byte) { p.basehpacket.payload = data }) @@ -465,10 +557,71 @@ func (p *acceptpacket) UnmarshalBinary(data []byte) error { } // String implements the fmt.Stringer interface -func (p acceptpacket) String() string { +func (p cpacket) String() string { return "TODO" } +// baserpacket is used to compose other packets +type baserpacket struct { + payload lorawan.PHYPayload + metadata Metadata +} + +// Metadata implements the core.RPacket interface +func (p baserpacket) Metadata() Metadata { + return p.metadata +} + +// Payload implements the core.RPacket interface +func (p baserpacket) Payload() lorawan.PHYPayload { + return p.payload +} + +// DevEUI implements the core.RPacket interface +func (p baserpacket) DevEUI() lorawan.EUI64 { + var devEUI lorawan.EUI64 + copy(devEUI[4:], p.payload.MACPayload.(*lorawan.MACPayload).FHDR.DevAddr[:]) + return devEUI +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p baserpacket) MarshalBinary() ([]byte, error) { + var mtype byte + switch p.payload.MHDR.MType { + case lorawan.JoinRequest: + fallthrough + case lorawan.UnconfirmedDataUp: + fallthrough + case lorawan.ConfirmedDataUp: + mtype = 1 // Up + case lorawan.JoinAccept: + fallthrough + case lorawan.UnconfirmedDataDown: + fallthrough + case lorawan.ConfirmedDataDown: + mtype = 2 // Down + default: + msg := fmt.Sprintf("Unsupported mtype: %s", p.payload.MHDR.MType.String()) + return nil, errors.New(errors.Implementation, msg) + } + + dataMetadata, err := p.metadata.MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + dataPayload, err := p.payload.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw := readwriter.New(nil) + rw.Write([]byte{mtype}) + rw.Write(dataMetadata) + rw.Write(dataPayload) + return rw.Bytes() +} + // basehpacket is used to compose other packets type basehpacket struct { appEUI lorawan.EUI64 From 2b8ad3b1b31cdb8af1f72c6f2d89a737e8d77456 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 10:59:33 +0100 Subject: [PATCH 0730/2266] [refactor] Use of an interface for readwriter + allow to write a single byte --- utils/readwriter/readwriter.go | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index e6acb26ea..84fcf81c0 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -12,8 +12,15 @@ import ( "github.com/brocaar/lorawan" ) +type Interface interface { + Write(data interface{}) + Read(to func(data []byte)) + Bytes() ([]byte, error) + Err() error +} + // entryReadWriter offers convenient method to write and read successively from a bytes buffer. -type Interface struct { +type rw struct { err error data *bytes.Buffer } @@ -23,8 +30,8 @@ type Interface struct { // If a nil or empty buffer is supplied, reading from the read/writer will cause an error (io.EOF) // Nevertheless, if a valid non-empty buffer is given, the read/writer will start reading from the // beginning of that buffer, and will start writting at the end of it. -func New(buf []byte) *Interface { - return &Interface{ +func New(buf []byte) Interface { + return &rw{ err: nil, data: bytes.NewBuffer(buf), } @@ -37,9 +44,11 @@ func New(buf []byte) *Interface { // // Also, it writes the length of the given raw data encoded on 2 bytes before writting the data // itself. In that way, data can be appended and read easily. -func (w *Interface) Write(data interface{}) { +func (w *rw) Write(data interface{}) { var raw []byte switch data.(type) { + case byte: + raw = []byte{data.(byte)} case []byte: raw = data.([]byte) case lorawan.AES128Key: @@ -56,12 +65,12 @@ func (w *Interface) Write(data interface{}) { default: panic(fmt.Errorf("Unreckognized data type: %v", data)) } - w.DirectWrite(uint16(len(raw))) - w.DirectWrite(raw) + w.directWrite(uint16(len(raw))) + w.directWrite(raw) } -// DirectWrite appends the given data at the end of the existing buffer (without the length). -func (w *Interface) DirectWrite(data interface{}) { +// directWrite appends the given data at the end of the existing buffer (without the length). +func (w *rw) directWrite(data interface{}) { if w.err != nil { return } @@ -74,7 +83,7 @@ func (w *Interface) DirectWrite(data interface{}) { // Read retrieves next data from the given buffer. Implicitely, this implies the data to have been // written using the Write method (len | data). Data are sent back through a callback as an array of // bytes. -func (w *Interface) Read(to func(data []byte)) { +func (w *rw) Read(to func(data []byte)) { if w.err != nil { return } @@ -88,7 +97,7 @@ func (w *Interface) Read(to func(data []byte)) { // Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and // an error if any issue was encountered during the process. -func (w Interface) Bytes() ([]byte, error) { +func (w rw) Bytes() ([]byte, error) { if w.err != nil { return nil, errors.New(errors.Structural, w.err) } @@ -96,7 +105,7 @@ func (w Interface) Bytes() ([]byte, error) { } // Err just return the err status of the read-writer. -func (w Interface) Err() error { +func (w rw) Err() error { if w.err != nil { return errors.New(errors.Structural, w.err) } From 12a3d6cfcd30e2ca1247771bcd73441fdfe2e7d1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 11:44:55 +0100 Subject: [PATCH 0731/2266] [refactor] Change errors EOF to Behavioural in readwriter --- utils/readwriter/readwriter.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 84fcf81c0..39347e5c4 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -7,6 +7,7 @@ import ( "bytes" "encoding/binary" "fmt" + "io" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" @@ -40,7 +41,7 @@ func New(buf []byte) Interface { // Write appends the given data at the end of the existing buffer. // // It does nothing if an error was previously noticed and panics if the given data are something -// different from: []byte, string, AES128Key, EUI64, DevAddr. +// different from: byte, []byte, string, AES128Key, EUI64, DevAddr. // // Also, it writes the length of the given raw data encoded on 2 bytes before writting the data // itself. In that way, data can be appended and read easily. @@ -99,7 +100,7 @@ func (w *rw) Read(to func(data []byte)) { // an error if any issue was encountered during the process. func (w rw) Bytes() ([]byte, error) { if w.err != nil { - return nil, errors.New(errors.Structural, w.err) + return nil, w.Err() } return w.data.Bytes(), nil } @@ -107,6 +108,9 @@ func (w rw) Bytes() ([]byte, error) { // Err just return the err status of the read-writer. func (w rw) Err() error { if w.err != nil { + if w.err == io.EOF { + return errors.New(errors.Behavioural, w.err) + } return errors.New(errors.Structural, w.err) } return nil From b98f796908228a70eecc480f5149b0115bd32dbb Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 11:45:05 +0100 Subject: [PATCH 0732/2266] [refactor] Implement tests for readwriter --- utils/readwriter/readwriter_test.go | 222 ++++++++++++++++++++++++++++ 1 file changed, 222 insertions(+) create mode 100644 utils/readwriter/readwriter_test.go diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go new file mode 100644 index 000000000..a6e27acac --- /dev/null +++ b/utils/readwriter/readwriter_test.go @@ -0,0 +1,222 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package readwriter + +import ( + "reflect" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func TestReadWriter(t *testing.T) { + { + Desc(t, "Write to an empty buffer") + rw := New(nil) + rw.Write([]byte{1, 2, 3, 4}) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // ------------- + + { + Desc(t, "Write to a non empty buffer") + rw := New([]byte{1, 2, 3, 4}) + rw.Write([]byte{1, 2}) + checkErrors(t, nil, rw.Err()) + } + + // ------------- + + { + Desc(t, "Append to an existing buffer") + rw := New(nil) + rw.Write([]byte{1, 2, 3, 4}) + data, _ := rw.Bytes() + + rw = New(data) + rw.Write([]byte{5, 6}) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) + rw.Read(func(data []byte) { checkData(t, []byte{5, 6}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // ------------- + + { + Desc(t, "Read from empty buffer") + rw := New(nil) + rw.Read(func(data []byte) { checkNotCalled(t) }) + checkErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) + } + + // -------------- + + { + Desc(t, "Write after read from non empty") + rw := New(nil) + rw.Write([]byte{1, 2}) + rw.Write([]byte{3, 4}) + data, _ := rw.Bytes() + + rw = New(data) + rw.Read(func(data []byte) {}) + rw.Write([]byte{5, 6}) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{3, 4}, data) }) + rw.Read(func(data []byte) { checkData(t, []byte{5, 6}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write single byte") + rw := New(nil) + rw.Write(byte(1)) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{1}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write string") + rw := New(nil) + rw.Write("TheThingsNetwork") + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte("TheThingsNetwork"), data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write lorawan.AES128Key") + rw := New(nil) + rw.Write(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6})) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write lorawan.EUI64") + rw := New(nil) + rw.Write(lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4, 5, 6, 7, 8}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write lorawan.DevAddr") + rw := New(nil) + rw.Write(lorawan.DevAddr([4]byte{1, 2, 3, 4})) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write empty slice") + rw := New(nil) + rw.Write([]byte{}) + data, err := rw.Bytes() + checkErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{}, data) }) + checkErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write invalid data") + rw := New(nil) + chwait := make(chan bool) + go func() { + defer func() { + recover() + close(chwait) + }() + rw.Write(14) + checkNotCalled(t) + }() + <-chwait + } +} + +// ----- CHECK utilities +func checkErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == errors.Nature(*want) { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} + +func checkData(t *testing.T, want []byte, got []byte) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check data") + return + } + Ko(t, "Expected data to be %v but got %v", want, got) +} + +func checkNotCalled(t *testing.T) { + Ko(t, "Unexpected call on method") +} From a61d25c773a3de748ddcb81c776b56aa9df809ed Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 11:47:48 +0100 Subject: [PATCH 0733/2266] [refactor] Move checkErrors test utils in errors package --- utils/errors/checks/checks.go | 33 +++++++++++++++++++++++++++++++++ utils/errors/errors.go | 6 +++--- 2 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 utils/errors/checks/checks.go diff --git a/utils/errors/checks/checks.go b/utils/errors/checks/checks.go new file mode 100644 index 000000000..568b7ea7f --- /dev/null +++ b/utils/errors/checks/checks.go @@ -0,0 +1,33 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package checks + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func CheckErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(Failure).Nature == Nature(*want) { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 0f7d9e6ff..64750f59c 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -13,9 +13,9 @@ type Nature string const ( Structural Nature = "Invalid structure" // Requests, parameters or inputs are wrong. Retry won't work. - Behavioural = "Wrong but expected behavior" // No error but the result isn't the one expected - Operational = "Invalid operation" // An operation failed due to external causes, a retry could work - Implementation = "Illegal call" // Method not implemented or unsupported for the given structure + Behavioural Nature = "Wrong but expected behavior" // No error but the result isn't the one expected + Operational Nature = "Invalid operation" // An operation failed due to external causes, a retry could work + Implementation Nature = "Illegal call" // Method not implemented or unsupported for the given structure ) // Failure states for fault that occurs during a process. From 78f0652ea169dc6fc418da4af79c8396bb04d76a Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 13:16:59 +0100 Subject: [PATCH 0734/2266] [refactor] Use of utils/errors/checks.CheckErrors instead of redeclaring checkErrors each time --- .../adapters/http/handlers/collect_test.go | 3 +- .../adapters/http/handlers/helpers_test.go | 22 ------ .../adapters/http/handlers/pubsub_test.go | 3 +- refactor/adapters/http/http_test.go | 25 +------ .../adapters/mqtt/handlers/activation_test.go | 3 +- .../adapters/mqtt/handlers/helpers_test.go | 24 ------- refactor/adapters/mqtt/mqttRecipient.go | 5 +- refactor/adapters/mqtt/mqtt_test.go | 33 ++------- .../adapters/udp/handlers/conversions_test.go | 5 +- .../adapters/udp/handlers/semtech_test.go | 29 ++------ refactor/check_utilities_test.go | 24 ------- refactor/errors/errors.go | 19 ----- refactor/metadata_test.go | 5 +- refactor/packet_test.go | 70 +------------------ utils/readwriter/readwriter_test.go | 63 ++++++----------- 15 files changed, 51 insertions(+), 282 deletions(-) delete mode 100644 refactor/errors/errors.go diff --git a/refactor/adapters/http/handlers/collect_test.go b/refactor/adapters/http/handlers/collect_test.go index d84e90cf5..da0221284 100644 --- a/refactor/adapters/http/handlers/collect_test.go +++ b/refactor/adapters/http/handlers/collect_test.go @@ -10,6 +10,7 @@ import ( core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -113,7 +114,7 @@ func TestCollect(t *testing.T) { } // Check - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkStatusCode(t, test.WantStatusCode, statusCode) checkContent(t, test.WantContent, content) checkPacket(t, test.WantPacket, packet) diff --git a/refactor/adapters/http/handlers/helpers_test.go b/refactor/adapters/http/handlers/helpers_test.go index 6deeaae6f..06e4aeeef 100644 --- a/refactor/adapters/http/handlers/helpers_test.go +++ b/refactor/adapters/http/handlers/helpers_test.go @@ -162,28 +162,6 @@ func tryNextRegistration(adapter core.Adapter, shouldAck bool, packet core.Packe } // ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - func checkStatusCode(t *testing.T, want int, got int) { if want == got { Ok(t, "Check status code") diff --git a/refactor/adapters/http/handlers/pubsub_test.go b/refactor/adapters/http/handlers/pubsub_test.go index 29b500161..ed59f7c9d 100644 --- a/refactor/adapters/http/handlers/pubsub_test.go +++ b/refactor/adapters/http/handlers/pubsub_test.go @@ -12,6 +12,7 @@ import ( core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -145,7 +146,7 @@ func TestPubSub(t *testing.T) { } // Check - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkStatusCode(t, test.WantStatusCode, statusCode) checkContent(t, test.WantContent, content) checkRegistration(t, test.WantRegistration, registration) diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go index 68e7a0beb..5f103582c 100644 --- a/refactor/adapters/http/http_test.go +++ b/refactor/adapters/http/http_test.go @@ -12,6 +12,7 @@ import ( core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -156,7 +157,7 @@ func TestSend(t *testing.T) { // Check <-time.After(time.Second) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkPayloads(t, test.WantPayload, payloads) checkRegistrations(t, test.WantRegistrations, registrations) } @@ -245,28 +246,6 @@ func genMockServer(recipient core.Recipient) chan string { } // Check utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - func checkRegistrations(t *testing.T, want []testRegistration, got []core.Registration) { if len(want) != len(got) { Ko(t, "Expected %d registrations but got %d", len(want), len(got)) diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go index 27a03dbd4..8e8272a8f 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -9,6 +9,7 @@ import ( core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -171,7 +172,7 @@ func TestActivationHandle(t *testing.T) { }) // Check - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkSubscriptions(t, test.WantSubscription, test.Client.Subscription) checkRegistrations(t, test.WantRegistration, consumer.Registration) checkPackets(t, test.WantPacket, consumer.Packet) diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/refactor/adapters/mqtt/handlers/helpers_test.go index 75119a7fb..d7513284b 100644 --- a/refactor/adapters/mqtt/handlers/helpers_test.go +++ b/refactor/adapters/mqtt/handlers/helpers_test.go @@ -12,7 +12,6 @@ import ( MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -174,29 +173,6 @@ func newTestConsumer() (*testConsumer, chan PktReq, chan RegReq) { } // ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check Errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check Errors") - return - } - - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - func checkTopics(t *testing.T, want string, got string) { if want == got { Ok(t, "Check Topics") diff --git a/refactor/adapters/mqtt/mqttRecipient.go b/refactor/adapters/mqtt/mqttRecipient.go index 316978eae..efdcab5d0 100644 --- a/refactor/adapters/mqtt/mqttRecipient.go +++ b/refactor/adapters/mqtt/mqttRecipient.go @@ -58,5 +58,8 @@ func (r *mqttRecipient) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { r.up = string(data) }) rw.Read(func(data []byte) { r.down = string(data) }) - return rw.Err() + if err := rw.Err(); err != nil { + return errors.New(errors.Structural, err) + } + return nil } diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 0c8d540bf..857e3ed73 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -12,6 +12,7 @@ import ( MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -138,7 +139,7 @@ func TestMQTTSend(t *testing.T) { } // Check - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkData(t, test.WantData, data) checkResponses(t, test.WantResponse, resp) @@ -158,7 +159,7 @@ func TestMQTTRecipient(t *testing.T) { if err == nil { err = ru.UnmarshalBinary(data) } - checkErrors(t, nil, err) + CheckErrors(t, nil, err) } { @@ -169,21 +170,21 @@ func TestMQTTRecipient(t *testing.T) { if err == nil { err = ru.UnmarshalBinary(data) } - checkErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) } { Desc(t, "Unmarshal nil data") ru := new(mqttRecipient) err := ru.UnmarshalBinary(nil) - checkErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) } { Desc(t, "Unmarshal wrong data") ru := new(mqttRecipient) err := ru.UnmarshalBinary([]byte{1, 2, 3, 4}) - checkErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) } } @@ -279,28 +280,6 @@ func trySend(adapter core.Adapter, packet []byte, recipients []testRecipient) ([ } // ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - func checkResponses(t *testing.T, want []byte, got []byte) { if reflect.DeepEqual(want, got) { Ok(t, "Check responses") diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/refactor/adapters/udp/handlers/conversions_test.go index 121500201..74118047d 100644 --- a/refactor/adapters/udp/handlers/conversions_test.go +++ b/refactor/adapters/udp/handlers/conversions_test.go @@ -10,6 +10,7 @@ import ( core "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -31,7 +32,7 @@ func TestConvertRXPKPacket(t *testing.T) { for _, test := range tests { Desc(t, "Convert RXPK: %s", pointer.DumpPStruct(test.RXPK, false)) packet, err := rxpk2packet(test.RXPK) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkPackets(t, test.CorePacket, packet) } } @@ -58,7 +59,7 @@ func TestConvertTXPKPacket(t *testing.T) { for _, test := range tests { Desc(t, "Convert to TXPK: %s", test.CorePacket.String()) txpk, err := packet2txpk(test.CorePacket) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkTXPKs(t, test.TXPK, txpk) } } diff --git a/refactor/adapters/udp/handlers/semtech_test.go b/refactor/adapters/udp/handlers/semtech_test.go index cfc8801c2..a2f2e704d 100644 --- a/refactor/adapters/udp/handlers/semtech_test.go +++ b/refactor/adapters/udp/handlers/semtech_test.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -20,14 +21,14 @@ func TestSend(t *testing.T) { Desc(t, "Send is not supported") adapter, _ := genAdapter(t, 33000) _, err := adapter.Send(core.RPacket{}) - checkErrors(t, pointer.String(string(errors.Implementation)), err) + CheckErrors(t, pointer.String(string(errors.Implementation)), err) } func TestNextRegistration(t *testing.T) { Desc(t, "Next registration is not supported") adapter, _ := genAdapter(t, 33001) _, _, err := adapter.NextRegistration() - checkErrors(t, pointer.String(string(errors.Implementation)), err) + CheckErrors(t, pointer.String(string(errors.Implementation)), err) } func TestNext(t *testing.T) { @@ -88,7 +89,7 @@ func TestNext(t *testing.T) { packet, err := getNextPacket(next) // Check - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkCorePackets(t, test.WantNext, packet) checkResponses(t, test.WantAck, ack) } @@ -114,28 +115,6 @@ func getNextPacket(next chan interface{}) (core.Packet, error) { } // ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - func checkCorePackets(t *testing.T, want core.Packet, got core.Packet) { if reflect.DeepEqual(want, got) { Ok(t, "Check core packets") diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go index 906aaf114..ed7db9c86 100644 --- a/refactor/check_utilities_test.go +++ b/refactor/check_utilities_test.go @@ -9,7 +9,6 @@ import ( "regexp" "testing" - "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -37,29 +36,6 @@ func checkPackets(t *testing.T, want Packet, got Packet) { Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) } -// Checks that errors match -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - // Checks that obtained json matches expected one func checkJSON(t *testing.T, want string, got []byte) { str := string(got) diff --git a/refactor/errors/errors.go b/refactor/errors/errors.go deleted file mode 100644 index 238564103..000000000 --- a/refactor/errors/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package errors - -const ( - // Unsuccessful operation due to an unexpected parameter or under-relying structure. - // Another call won't change a thing, the inputs are wrong anyway. - ErrInvalidStructure = "Invalid Structure" - - // Attempt to access an unimplemented method or an unsupported operation. Fatal. - ErrNotSupported = "Unsupported Operation" - - // The operation went well though the result is unexpected and wrong. - ErrWrongBehavior = "Unexpected Behavior" - - // Something happend during the processing. Another attempt might success. - ErrFailedOperation = "Unsuccessful Operation" -) diff --git a/refactor/metadata_test.go b/refactor/metadata_test.go index 345e4e8a2..8ee402e89 100644 --- a/refactor/metadata_test.go +++ b/refactor/metadata_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -82,7 +83,7 @@ func TestMarshaljson(t *testing.T) { for _, test := range commonTests { Desc(t, "Marshal medatadata: %s", test.Metadata.String()) raw, err := json.Marshal(test.Metadata) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkJSON(t, test.JSON, raw) } } @@ -92,7 +93,7 @@ func TestUnmarshalJSON(t *testing.T) { Desc(t, "Unmarshal json: %s", test.JSON) metadata := Metadata{} err := json.Unmarshal([]byte(test.JSON), &metadata) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkMetadata(t, test.Metadata, metadata) } } diff --git a/refactor/packet_test.go b/refactor/packet_test.go index 266fe6058..da39f2850 100644 --- a/refactor/packet_test.go +++ b/refactor/packet_test.go @@ -3,72 +3,6 @@ package refactor -import ( - "encoding/json" - "testing" +import () - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestMarshalJSONRPacket(t *testing.T) { - tests := []marshalJSONTest{ - marshalJSONTest{ // Empty Payload - Packet: NewRPacket(lorawan.PHYPayload{}, genFullMetadata()), - WantFields: []string{}, - }, - marshalJSONTest{ // Empty Metadata - Packet: NewRPacket(genPHYPayload(true), Metadata{}), - WantFields: []string{"payload", "metadata"}, - }, - marshalJSONTest{ // With Metadata and Payload - Packet: NewRPacket(genPHYPayload(true), genFullMetadata()), - WantFields: []string{"payload", "metadata"}, - }, - } - - for _, test := range tests { - Desc(t, "Marshal packet to json: %s", test.Packet.String()) - raw, _ := json.Marshal(test.Packet) - checkFields(t, test.WantFields, raw) - } -} - -func TestUnmarshalJSONRPacket(t *testing.T) { - tests := []unmarshalJSONTest{ - unmarshalJSONTest{ - JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, - WantPacket: NewRPacket(genPHYPayload(true), Metadata{}), - }, - unmarshalJSONTest{ - JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{"chan":2,"codr":"4/6","fdev":3,"freq":863.125,"imme":false,"ipol":false,"lsnr":5.2,"modu":"LORA","ncrc":true,"powe":3,"prea":8,"rfch":2,"rssi":-27,"size":14,"stat":0,"tmst":1452694288207288421,"datr":"LORA","time":"2016-01-13T14:11:28.207288421Z"}}`, - WantPacket: NewRPacket(genPHYPayload(true), genFullMetadata()), - }, - unmarshalJSONTest{ - JSON: `invalid`, - WantPacket: RPacket{}, - }, - unmarshalJSONTest{ - JSON: `{"metadata":{}}`, - WantPacket: RPacket{}, - }, - } - - for _, test := range tests { - Desc(t, "Unmarshal json to packet: %s", test.JSON) - packet := RPacket{} - json.Unmarshal([]byte(test.JSON), &packet) - checkPackets(t, test.WantPacket, packet) - } -} - -// ---- Declaration -type marshalJSONTest struct { - Packet Packet - WantFields []string -} - -type unmarshalJSONTest struct { - JSON string - WantPacket Packet -} +// TODO diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go index a6e27acac..2722d6e6f 100644 --- a/utils/readwriter/readwriter_test.go +++ b/utils/readwriter/readwriter_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -19,11 +20,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write([]byte{1, 2, 3, 4}) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // ------------- @@ -32,7 +33,7 @@ func TestReadWriter(t *testing.T) { Desc(t, "Write to a non empty buffer") rw := New([]byte{1, 2, 3, 4}) rw.Write([]byte{1, 2}) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // ------------- @@ -46,12 +47,12 @@ func TestReadWriter(t *testing.T) { rw = New(data) rw.Write([]byte{5, 6}) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) rw.Read(func(data []byte) { checkData(t, []byte{5, 6}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // ------------- @@ -60,7 +61,7 @@ func TestReadWriter(t *testing.T) { Desc(t, "Read from empty buffer") rw := New(nil) rw.Read(func(data []byte) { checkNotCalled(t) }) - checkErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) + CheckErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) } // -------------- @@ -76,12 +77,12 @@ func TestReadWriter(t *testing.T) { rw.Read(func(data []byte) {}) rw.Write([]byte{5, 6}) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{3, 4}, data) }) rw.Read(func(data []byte) { checkData(t, []byte{5, 6}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -91,11 +92,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write(byte(1)) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{1}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -105,11 +106,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write("TheThingsNetwork") data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte("TheThingsNetwork"), data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -119,11 +120,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6})) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -133,11 +134,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write(lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4, 5, 6, 7, 8}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -147,11 +148,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write(lorawan.DevAddr([4]byte{1, 2, 3, 4})) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -161,11 +162,11 @@ func TestReadWriter(t *testing.T) { rw := New(nil) rw.Write([]byte{}) data, err := rw.Bytes() - checkErrors(t, nil, err) + CheckErrors(t, nil, err) rw = New(data) rw.Read(func(data []byte) { checkData(t, []byte{}, data) }) - checkErrors(t, nil, rw.Err()) + CheckErrors(t, nil, rw.Err()) } // -------------- @@ -187,28 +188,6 @@ func TestReadWriter(t *testing.T) { } // ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - func checkData(t *testing.T, want []byte, got []byte) { if reflect.DeepEqual(want, got) { Ok(t, "Check data") From 41addd0b88b04e4753fbdcf398562489900feec6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 14:19:28 +0100 Subject: [PATCH 0735/2266] [refactor] Move packets interfaces to a separated file and use of basepacket to compose the interface implementations --- refactor/packets.go | 580 ++++++++++++++++++--------------- refactor/packets_interfaces.go | 55 ++++ 2 files changed, 366 insertions(+), 269 deletions(-) create mode 100644 refactor/packets_interfaces.go diff --git a/refactor/packets.go b/refactor/packets.go index f64856791..42f66f404 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -5,7 +5,6 @@ package refactor import ( "fmt" - "io" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" @@ -21,16 +20,35 @@ const ( TYPE_CPACKET ) -type RPacket interface { - Packet - Metadata() Metadata - Payload() lorawan.PHYPayload - DevEUI() lorawan.EUI64 +// --------------------------------- +// +// ----- HELPERS ------------------- +// +// --------------------------------- +func ensureUnmarshal(p interface{}, data []byte, t byte) error { + if p == nil { + return errors.New(errors.Structural, "Cannot unmarshal nil packet") + } + + if len(data) < 1 || data[0] != t { + return errors.New(errors.Structural, "Not an expected packet") + } + + return nil } +// --------------------------------- +// +// ----- RPACKET ------------------- +// +// --------------------------------- +// +// +// // rpacket implements the core.RPacket interface type rpacket struct { - *baserpacket + baserpacket + basempacket } // NewRPacket construct a new router packet given a payload and metadata @@ -45,62 +63,38 @@ func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) } return &rpacket{ - &baserpacket{ - payload: payload, - metadata: metadata, - }, + baserpacket{payload: payload}, + basempacket{metadata: metadata}, }, nil } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p rpacket) MarshalBinary() ([]byte, error) { - data, err := p.baserpacket.MarshalBinary() + data := []byte{TYPE_RPACKET} + dataBaseR, err := p.baserpacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err + } + dataBaseM, err := p.basempacket.Marshal() + if err != nil { + return nil, err } - return append([]byte{TYPE_RPACKET}, data...), nil + return append(data, append(dataBaseR, dataBaseM...)...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *rpacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil packet") - } - - if len(data) < 1 || data[0] != TYPE_RPACKET { - return errors.New(errors.Structural, "Not a Router packet") - } - - var isUp bool - rw := readwriter.New(data[1:]) - rw.Read(func(data []byte) { - if data[0] == 1 { - isUp = true - } - }) - - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - - var dataPayload []byte - rw.Read(func(data []byte) { dataPayload = data }) - - if rw.Err() != nil { - return errors.New(errors.Structural, rw.Err()) - } - - p.metadata = Metadata{} - if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { - return errors.New(errors.Structural, err) + if err := ensureUnmarshal(p, data, TYPE_RPACKET); err != nil { + return err } - p.payload = lorawan.NewPHYPayload(isUp) - if err := p.payload.UnmarshalBinary(dataPayload); err != nil { - return errors.New(errors.Structural, err) + rest, err := p.baserpacket.Unmarshal(data[1:]) + if err != nil { + return err } - - return nil + _, err = p.basempacket.Unmarshal(rest) + return err } // String implements the Stringer interface @@ -111,19 +105,18 @@ func (p rpacket) String() string { return str } -type BPacket interface { - Packet - Commands() []lorawan.MACCommand - DevEUI() lorawan.EUI64 - FCnt() uint32 - Metadata() Metadata - Payload() []byte - ValidateMIC(key lorawan.AES128Key) (bool, error) -} - +// --------------------------------- +// +// ----- BPACKET ------------------- +// +// --------------------------------- +// +// +// // bpacket implements the core.BPacket interface type bpacket struct { baserpacket + basempacket } // NewBPacket constructs a new broker packets given a payload and metadata @@ -146,10 +139,8 @@ func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) } return &bpacket{ - baserpacket: baserpacket{ - payload: payload, - metadata: metadata, - }, + baserpacket: baserpacket{payload: payload}, + basempacket: basempacket{metadata: metadata}, }, nil } @@ -181,67 +172,46 @@ func (p bpacket) String() string { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p bpacket) MarshalBinary() ([]byte, error) { - data, err := p.baserpacket.MarshalBinary() + data := []byte{TYPE_BPACKET} + dataBaseR, err := p.baserpacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err + } + dataBaseM, err := p.basempacket.Marshal() + if err != nil { + return nil, err } - return append([]byte{TYPE_BPACKET}, data...), nil + return append(data, append(dataBaseR, dataBaseM...)...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *bpacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil packet") + if err := ensureUnmarshal(p, data, TYPE_BPACKET); err != nil { + return err } - if len(data) < 1 || data[0] != TYPE_BPACKET { - return errors.New(errors.Structural, "Not a Router packet") - } - - var isUp bool - rw := readwriter.New(data[1:]) - rw.Read(func(data []byte) { - if data[0] == 1 { - isUp = true - } - }) - - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - - var dataPayload []byte - rw.Read(func(data []byte) { dataPayload = data }) - - if rw.Err() != nil { - return errors.New(errors.Structural, rw.Err()) - } - - p.metadata = Metadata{} - if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { - return errors.New(errors.Structural, err) - } - - p.payload = lorawan.NewPHYPayload(isUp) - if err := p.payload.UnmarshalBinary(dataPayload); err != nil { - return errors.New(errors.Structural, err) + rest, err := p.baserpacket.Unmarshal(data[1:]) + if err != nil { + return err } - - return nil -} - -type HPacket interface { - Packet - AppEUI() lorawan.EUI64 - DevEUI() lorawan.EUI64 - Payload() []byte // FRMPayload - Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up + _, err = p.basempacket.Unmarshal(rest) + return err } +// --------------------------------- +// +// ----- HPACKET ------------------- +// +// --------------------------------- +// +// +// // hpacket implements the HPacket interface type hpacket struct { - *basehpacket - metadata Metadata + basehpacket + baseapacket + basempacket } // NewHPacket constructs a new Handler packet @@ -250,57 +220,53 @@ func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, meta payload = make([]byte, 0) } return &hpacket{ - basehpacket: &basehpacket{ - appEUI: appEUI, - devEUI: devEUI, + basehpacket: basehpacket{ + appEUI: appEUI, + devEUI: devEUI, + }, + baseapacket: baseapacket{ payload: payload, }, - metadata: metadata, + basempacket: basempacket{metadata: metadata}, } } -// Metadata implements the core.Metadata interface -func (p hpacket) Metadata() Metadata { - return p.metadata -} - // MarshalBinary implements the encoding.BinaryMarshaler interface func (p hpacket) MarshalBinary() ([]byte, error) { - dataMetadata, err := p.Metadata().MarshalJSON() + data := []byte{TYPE_HPACKET} + dataBaseH, err := p.basehpacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err } - - data, err := p.basehpacket.MarshalBinary() + dataBaseA, err := p.baseapacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err + } + dataBaseM, err := p.basempacket.Marshal() + if err != nil { + return nil, err } - rw := readwriter.New(append([]byte{TYPE_HPACKET}, data...)) - rw.Write(dataMetadata) - return rw.Bytes() + // data | dataBaseH | dataBaseA | dataBaseM + return append(append(data, append(dataBaseH, dataBaseA...)...), dataBaseM...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil packet") + if err := ensureUnmarshal(p, data, TYPE_HPACKET); err != nil { + return err } - if len(data) < 1 || data[0] != TYPE_HPACKET { - return errors.New(errors.Structural, "Not a Handler packet") + rest, err := p.basehpacket.Unmarshal(data[1:]) + if err != nil { + return err } - - rw := readwriter.New(data[1:]) - rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) - rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) - rw.Read(func(data []byte) { p.basehpacket.payload = data }) - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { - return errors.New(errors.Structural, err) + rest, err = p.baseapacket.Unmarshal(rest) + if err != nil { + return err } - return nil + _, err = p.basempacket.Unmarshal(rest) + return err } // String implements the fmt.Stringer interface @@ -313,15 +279,17 @@ func (p hpacket) String() string { return str } -type APacket interface { - Packet - Payload() []byte - Metadata() []Metadata -} - +// --------------------------------- +// +// ----- APACKET ------------------- +// +// --------------------------------- +// +// +// // apacket implements the core.APacket interface type apacket struct { - payload []byte + baseapacket metadata []Metadata } @@ -331,12 +299,10 @@ func NewAPacket(payload []byte, metadata []Metadata) (APacket, error) { return nil, errors.New(errors.Structural, "Application packet must hold a payload") } - return &apacket{payload: payload, metadata: metadata}, nil -} - -// Payload implements the core.APacket interface -func (p apacket) Payload() []byte { - return p.payload + return &apacket{ + baseapacket: baseapacket{payload: payload}, + metadata: metadata, + }, nil } // Metadata implements the core.Metadata interface @@ -354,44 +320,38 @@ func (p apacket) MarshalBinary() ([]byte, error) { } rw.Write(data) } - data, err := rw.Bytes() + dataMetadata, err := rw.Bytes() if err != nil { return nil, errors.New(errors.Structural, err) } + dataBaseA, err := p.baseapacket.Marshal() + if err != nil { + return nil, err + } - rw = readwriter.New([]byte{TYPE_APACKET}) - rw.Write(p.payload) - rw.Write(data) - - return rw.Bytes() + data := []byte{TYPE_APACKET} + return append(append(data, dataBaseA...), dataMetadata...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *apacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil apacket") + if err := ensureUnmarshal(p, data, TYPE_APACKET); err != nil { + return err } - if len(data) < 1 || data[0] != TYPE_APACKET { - return errors.New(errors.Structural, "Not an Application packet") - } - - var dataMetadata []byte - rw := readwriter.New(data[1:]) - rw.Read(func(data []byte) { p.payload = data }) - rw.Read(func(data []byte) { dataMetadata = data }) - if rw.Err() != nil { - return errors.New(errors.Structural, rw.Err()) + rest, err := p.baseapacket.Unmarshal(data[1:]) + if err != nil { + return err } p.metadata = make([]Metadata, 0) - rw = readwriter.New(dataMetadata) + rw := readwriter.New(rest) for { var dataMetadata []byte rw.Read(func(data []byte) { dataMetadata = data }) if rw.Err() != nil { err, ok := rw.Err().(errors.Failure) - if ok && err.Fault == io.EOF { + if ok && err.Nature == errors.Behavioural { break } return errors.New(errors.Structural, rw.Err()) @@ -412,79 +372,74 @@ func (p apacket) String() string { return "TODO" } -type JPacket interface { - Packet - AppEUI() lorawan.EUI64 - DevEUI() lorawan.EUI64 - DevNonce() [2]byte - Metadata() Metadata // Rssi + DutyCycle -} - +// --------------------------------- +// +// ----- JPACKET ------------------- +// +// --------------------------------- +// +// +// // joinPacket implements the core.JoinPacket interface type jpacket struct { - *basehpacket - metadata Metadata + baseapacket baseapacket + basehpacket + basempacket } // NewJoinPacket constructs a new JoinPacket func NewJPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, metadata Metadata) JPacket { return &jpacket{ - basehpacket: &basehpacket{ - appEUI: appEUI, - devEUI: devEUI, - payload: devNonce[:], + basehpacket: basehpacket{ + appEUI: appEUI, + devEUI: devEUI, }, - metadata: metadata, + baseapacket: baseapacket{payload: devNonce[:]}, + basempacket: basempacket{metadata: metadata}, } } // DevNonce implements the core.JoinPacket interface func (p jpacket) DevNonce() [2]byte { - return [2]byte{p.basehpacket.payload[0], p.basehpacket.payload[1]} -} - -// Metadata implements the core.JoinPacket interface -func (p jpacket) Metadata() Metadata { - return p.metadata + return [2]byte{p.baseapacket.payload[0], p.baseapacket.payload[1]} } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p jpacket) MarshalBinary() ([]byte, error) { - dataMetadata, err := p.Metadata().MarshalJSON() + data := []byte{TYPE_JPACKET} + dataBaseH, err := p.basehpacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err } - - data, err := p.basehpacket.MarshalBinary() + dataBaseA, err := p.baseapacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err + } + dataBaseM, err := p.basempacket.Marshal() + if err != nil { + return nil, err } - rw := readwriter.New(append([]byte{TYPE_JPACKET}, data...)) - rw.Write(dataMetadata) - return rw.Bytes() + // data | dataBaseH | dataBaseA | dataBaseM + return append(append(data, append(dataBaseH, dataBaseA...)...), dataBaseM...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *jpacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil packet") + if err := ensureUnmarshal(p, data, TYPE_JPACKET); err != nil { + return err } - if len(data) < 1 || data[0] != TYPE_JPACKET { - return errors.New(errors.Structural, "Not a JoinRequst packet") + rest, err := p.basehpacket.Unmarshal(data[1:]) + if err != nil { + return err } - - rw := readwriter.New(data[1:]) - rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) - rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) - rw.Read(func(data []byte) { p.basehpacket.payload = data }) - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { - return errors.New(errors.Structural, err) + rest, err = p.baseapacket.Unmarshal(rest) + if err != nil { + return err } - return nil + _, err = p.basempacket.Unmarshal(rest) + return err } // String implements the fmt.Stringer interface @@ -492,18 +447,11 @@ func (p jpacket) String() string { return "TODO" } -type CPacket interface { - Packet - AppEUI() lorawan.EUI64 - DevEUI() lorawan.EUI64 - Payload() []byte - NwkSKey() lorawan.AES128Key -} - // acceptpacket implements the core.AcceptPacket interface type cpacket struct { - *basehpacket - nwkSKey lorawan.AES128Key + basehpacket + baseapacket + nwkSKey baseapacket } // NewAcceptPacket constructs a new CPacket @@ -513,47 +461,58 @@ func NewCPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, nwkS } return &cpacket{ - basehpacket: &basehpacket{ - appEUI: appEUI, - devEUI: devEUI, - payload: payload, + basehpacket: basehpacket{ + appEUI: appEUI, + devEUI: devEUI, }, - nwkSKey: nwkSKey, + baseapacket: baseapacket{payload: payload}, + nwkSKey: baseapacket{payload: nwkSKey[:]}, }, nil } // NwkSKey implements the core.AcceptPacket interface func (p cpacket) NwkSKey() lorawan.AES128Key { - return p.nwkSKey + var key lorawan.AES128Key + copy(key[:], p.nwkSKey.payload) + return key } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p cpacket) MarshalBinary() ([]byte, error) { - data, err := p.basehpacket.MarshalBinary() + data := []byte{TYPE_CPACKET} + dataBaseH, err := p.basehpacket.Marshal() if err != nil { - return nil, errors.New(errors.Structural, err) + return nil, err } - rw := readwriter.New(append([]byte{TYPE_CPACKET}, data...)) - rw.Write(p.nwkSKey) - return rw.Bytes() + dataBaseA, err := p.baseapacket.Marshal() + if err != nil { + return nil, err + } + dataBaseN, err := p.nwkSKey.Marshal() + if err != nil { + return nil, err + } + + // data | dataBaseH | dataBaseA | dataBaseN + return append(append(data, append(dataBaseH, dataBaseA...)...), dataBaseN...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *cpacket) UnmarshalBinary(data []byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil packet") + if err := ensureUnmarshal(p, data, TYPE_CPACKET); err != nil { + return err } - if len(data) < 1 || data[0] != TYPE_CPACKET { - return errors.New(errors.Structural, "Not a JoinAccept packet") + rest, err := p.basehpacket.Unmarshal(data[1:]) + if err != nil { + return err } - - rw := readwriter.New(data[1:]) - rw.Read(func(data []byte) { copy(p.basehpacket.appEUI[:], data) }) - rw.Read(func(data []byte) { copy(p.basehpacket.devEUI[:], data) }) - rw.Read(func(data []byte) { p.basehpacket.payload = data }) - rw.Read(func(data []byte) { copy(p.nwkSKey[:], data) }) - return rw.Err() + rest, err = p.baseapacket.Unmarshal(rest) + if err != nil { + return err + } + _, err = p.nwkSKey.Unmarshal(rest) + return err } // String implements the fmt.Stringer interface @@ -561,18 +520,59 @@ func (p cpacket) String() string { return "TODO" } -// baserpacket is used to compose other packets -type baserpacket struct { - payload lorawan.PHYPayload +// -------------------------------------- +// -------------------------------------- +// -------------------------------------- +// ----- BASE PACKETS ------------------- +// +// All base packet are small components that are used by packets above to define accessors and +// marshaling / unmarshaling methods on a struct. +// All Unmarshal methods return the remaining unconsumed bytes from the input data such that one +// could actually chain calls for different basexxxpacket +// +// -------------------------------------- +// -------------------------------------- +// -------------------------------------- + +// basempacket is used to compose other packets +type basempacket struct { metadata Metadata } -// Metadata implements the core.RPacket interface -func (p baserpacket) Metadata() Metadata { +func (p basempacket) Metadata() Metadata { return p.metadata } -// Payload implements the core.RPacket interface +func (p basempacket) Marshal() ([]byte, error) { + dataMetadata, err := p.metadata.MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + rw := readwriter.New(nil) + rw.Write(dataMetadata) + return rw.Bytes() +} + +func (p *basempacket) Unmarshal(data []byte) ([]byte, error) { + rw := readwriter.New(data) + + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + + p.metadata = Metadata{} + if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { + return nil, errors.New(errors.Structural, err) + } + + return rw.Bytes() +} + +// baserpacket is used to compose other packets +type baserpacket struct { + payload lorawan.PHYPayload +} + func (p baserpacket) Payload() lorawan.PHYPayload { return p.payload } @@ -584,8 +584,8 @@ func (p baserpacket) DevEUI() lorawan.EUI64 { return devEUI } -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p baserpacket) MarshalBinary() ([]byte, error) { +// Marshal transforms the given basepacket to binaries +func (p baserpacket) Marshal() ([]byte, error) { var mtype byte switch p.payload.MHDR.MType { case lorawan.JoinRequest: @@ -605,11 +605,6 @@ func (p baserpacket) MarshalBinary() ([]byte, error) { return nil, errors.New(errors.Implementation, msg) } - dataMetadata, err := p.metadata.MarshalJSON() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - dataPayload, err := p.payload.MarshalBinary() if err != nil { return nil, errors.New(errors.Structural, err) @@ -617,16 +612,40 @@ func (p baserpacket) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write([]byte{mtype}) - rw.Write(dataMetadata) rw.Write(dataPayload) return rw.Bytes() } +// Unmarshal hydrates the given basepacket from binaries data. +func (p *baserpacket) Unmarshal(data []byte) ([]byte, error) { + var isUp bool + rw := readwriter.New(data) + rw.Read(func(data []byte) { + if data[0] == 1 { + isUp = true + } + }) + + var dataPayload []byte + rw.Read(func(data []byte) { dataPayload = data }) + + data, err := rw.Bytes() + if err != nil { + return nil, errors.New(errors.Structural, rw.Err()) + } + + p.payload = lorawan.NewPHYPayload(isUp) + if err := p.payload.UnmarshalBinary(dataPayload); err != nil { + return nil, errors.New(errors.Structural, err) + } + + return data, nil +} + // basehpacket is used to compose other packets type basehpacket struct { - appEUI lorawan.EUI64 - devEUI lorawan.EUI64 - payload []byte + appEUI lorawan.EUI64 + devEUI lorawan.EUI64 } func (p basehpacket) AppEUI() lorawan.EUI64 { @@ -637,14 +656,37 @@ func (p basehpacket) DevEUI() lorawan.EUI64 { return p.devEUI } -func (p basehpacket) Payload() []byte { +func (p basehpacket) Marshal() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(p.appEUI) + rw.Write(p.devEUI) + return rw.Bytes() +} + +func (p *basehpacket) Unmarshal(data []byte) ([]byte, error) { + rw := readwriter.New(data) + rw.Read(func(data []byte) { copy(p.appEUI[:], data) }) + rw.Read(func(data []byte) { copy(p.devEUI[:], data) }) + return rw.Bytes() +} + +// baseapacket is used to compose other packets +type baseapacket struct { + payload []byte +} + +func (p baseapacket) Payload() []byte { return p.payload } -func (p basehpacket) MarshalBinary() ([]byte, error) { +func (p baseapacket) Marshal() ([]byte, error) { rw := readwriter.New(nil) - rw.Write(p.appEUI) - rw.Write(p.devEUI) rw.Write(p.payload) return rw.Bytes() } + +func (p *baseapacket) Unmarshal(data []byte) ([]byte, error) { + rw := readwriter.New(data) + rw.Read(func(data []byte) { p.payload = data }) + return rw.Bytes() +} diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go new file mode 100644 index 000000000..7f5f928ce --- /dev/null +++ b/refactor/packets_interfaces.go @@ -0,0 +1,55 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "github.com/brocaar/lorawan" +) + +type RPacket interface { + Packet + Metadata() Metadata + Payload() lorawan.PHYPayload + DevEUI() lorawan.EUI64 +} + +type BPacket interface { + Packet + Commands() []lorawan.MACCommand + DevEUI() lorawan.EUI64 + FCnt() uint32 + Metadata() Metadata + Payload() []byte + ValidateMIC(key lorawan.AES128Key) (bool, error) +} + +type HPacket interface { + Packet + AppEUI() lorawan.EUI64 + DevEUI() lorawan.EUI64 + Payload() []byte // FRMPayload + Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up +} + +type APacket interface { + Packet + Payload() []byte + Metadata() []Metadata +} + +type JPacket interface { + Packet + AppEUI() lorawan.EUI64 + DevEUI() lorawan.EUI64 + DevNonce() [2]byte + Metadata() Metadata // Rssi + DutyCycle +} + +type CPacket interface { + Packet + AppEUI() lorawan.EUI64 + DevEUI() lorawan.EUI64 + Payload() []byte + NwkSKey() lorawan.AES128Key +} From ca78e5fa2e75e7e046c6e9e46f3dca9b52741c85 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 14:31:02 +0100 Subject: [PATCH 0736/2266] [refactor] Define the famous UnmarshalPacket method and pass packets' types unexported --- refactor/packets.go | 96 +++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/refactor/packets.go b/refactor/packets.go index 42f66f404..578a94b16 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -4,6 +4,7 @@ package refactor import ( + "encoding" "fmt" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -12,12 +13,12 @@ import ( ) const ( - TYPE_RPACKET byte = iota - TYPE_BPACKET - TYPE_HPACKET - TYPE_APACKET - TYPE_JPACKET - TYPE_CPACKET + type_rpacket byte = iota + type_bpacket + type_hpacket + type_apacket + type_jpacket + type_cpacket ) // --------------------------------- @@ -37,14 +38,51 @@ func ensureUnmarshal(p interface{}, data []byte, t byte) error { return nil } -// --------------------------------- +// UnmarshalPacket takes raw binary data and try to marshal it into a given packet interface: // -// ----- RPACKET ------------------- +// - RPacket +// - BPacket +// - HPacket +// - APacket +// - JPacket +// - CPacket // +// It returns an interface so that its easy and handy to perform a type assertion out of it. +// If data are wrong or, if the packet is not unmarshalable, it returns an error. +func UnmarshalPacket(data []byte) (interface{}, error) { + if len(data) < 1 { + return nil, errors.New(errors.Structural, "Cannot unmarshal, not a packet") + } + + var packet interface { + encoding.BinaryUnmarshaler + } + + switch data[0] { + case type_rpacket: + packet = new(rpacket) + case type_bpacket: + packet = new(bpacket) + case type_hpacket: + packet = new(hpacket) + case type_apacket: + packet = new(apacket) + case type_jpacket: + packet = new(jpacket) + case type_cpacket: + packet = new(cpacket) + } + + err := packet.UnmarshalBinary(data) + return packet, err +} + // --------------------------------- // +// ----- RPACKET ------------------- // -// +// --------------------------------- + // rpacket implements the core.RPacket interface type rpacket struct { baserpacket @@ -70,7 +108,7 @@ func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) // MarshalBinary implements the encoding.BinaryMarshaler interface func (p rpacket) MarshalBinary() ([]byte, error) { - data := []byte{TYPE_RPACKET} + data := []byte{type_rpacket} dataBaseR, err := p.baserpacket.Marshal() if err != nil { return nil, err @@ -85,7 +123,7 @@ func (p rpacket) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *rpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, TYPE_RPACKET); err != nil { + if err := ensureUnmarshal(p, data, type_rpacket); err != nil { return err } @@ -110,9 +148,7 @@ func (p rpacket) String() string { // ----- BPACKET ------------------- // // --------------------------------- -// -// -// + // bpacket implements the core.BPacket interface type bpacket struct { baserpacket @@ -172,7 +208,7 @@ func (p bpacket) String() string { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p bpacket) MarshalBinary() ([]byte, error) { - data := []byte{TYPE_BPACKET} + data := []byte{type_bpacket} dataBaseR, err := p.baserpacket.Marshal() if err != nil { return nil, err @@ -187,7 +223,7 @@ func (p bpacket) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *bpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, TYPE_BPACKET); err != nil { + if err := ensureUnmarshal(p, data, type_bpacket); err != nil { return err } @@ -204,9 +240,7 @@ func (p *bpacket) UnmarshalBinary(data []byte) error { // ----- HPACKET ------------------- // // --------------------------------- -// -// -// + // hpacket implements the HPacket interface type hpacket struct { basehpacket @@ -233,7 +267,7 @@ func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, meta // MarshalBinary implements the encoding.BinaryMarshaler interface func (p hpacket) MarshalBinary() ([]byte, error) { - data := []byte{TYPE_HPACKET} + data := []byte{type_hpacket} dataBaseH, err := p.basehpacket.Marshal() if err != nil { return nil, err @@ -253,7 +287,7 @@ func (p hpacket) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, TYPE_HPACKET); err != nil { + if err := ensureUnmarshal(p, data, type_hpacket); err != nil { return err } @@ -284,9 +318,7 @@ func (p hpacket) String() string { // ----- APACKET ------------------- // // --------------------------------- -// -// -// + // apacket implements the core.APacket interface type apacket struct { baseapacket @@ -329,13 +361,13 @@ func (p apacket) MarshalBinary() ([]byte, error) { return nil, err } - data := []byte{TYPE_APACKET} + data := []byte{type_apacket} return append(append(data, dataBaseA...), dataMetadata...), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *apacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, TYPE_APACKET); err != nil { + if err := ensureUnmarshal(p, data, type_apacket); err != nil { return err } @@ -377,9 +409,7 @@ func (p apacket) String() string { // ----- JPACKET ------------------- // // --------------------------------- -// -// -// + // joinPacket implements the core.JoinPacket interface type jpacket struct { baseapacket baseapacket @@ -406,7 +436,7 @@ func (p jpacket) DevNonce() [2]byte { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p jpacket) MarshalBinary() ([]byte, error) { - data := []byte{TYPE_JPACKET} + data := []byte{type_jpacket} dataBaseH, err := p.basehpacket.Marshal() if err != nil { return nil, err @@ -426,7 +456,7 @@ func (p jpacket) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *jpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, TYPE_JPACKET); err != nil { + if err := ensureUnmarshal(p, data, type_jpacket); err != nil { return err } @@ -479,7 +509,7 @@ func (p cpacket) NwkSKey() lorawan.AES128Key { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p cpacket) MarshalBinary() ([]byte, error) { - data := []byte{TYPE_CPACKET} + data := []byte{type_cpacket} dataBaseH, err := p.basehpacket.Marshal() if err != nil { return nil, err @@ -499,7 +529,7 @@ func (p cpacket) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *cpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, TYPE_CPACKET); err != nil { + if err := ensureUnmarshal(p, data, type_cpacket); err != nil { return err } From 287454f1ae1eaea50f9eaee0ee82947a8b5e4f8b Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 15:16:38 +0100 Subject: [PATCH 0737/2266] [refactor] Move DevEUI() in the Packet Interface instead of repeating it each time --- refactor/interfaces.go | 5 +- refactor/packets.go | 299 ++++++++++++--------------------- refactor/packets_interfaces.go | 5 - 3 files changed, 109 insertions(+), 200 deletions(-) diff --git a/refactor/interfaces.go b/refactor/interfaces.go index 9f6eb5ed9..db744f6f2 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -30,14 +30,11 @@ type Adapter interface { } type Packet interface { + DevEUI() lorawan.EUI64 encoding.BinaryMarshaler fmt.Stringer } -type Addressable interface { - DevEUI() lorawan.EUI64 -} - type Registration interface { Recipient() Recipient } diff --git a/refactor/packets.go b/refactor/packets.go index 578a94b16..026de701b 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -26,16 +26,38 @@ const ( // ----- HELPERS ------------------- // // --------------------------------- -func ensureUnmarshal(p interface{}, data []byte, t byte) error { - if p == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil packet") + +// marshalBases is used to marshal in chain several bases which compose a bigger Packet struct +func marshalBases(t byte, bases ...baseMarshaler) ([]byte, error) { + data := []byte{t} + + for _, base := range bases { + dataBase, err := base.Marshal() + if err != nil { + return nil, err + } + data = append(data, dataBase...) } + return data, nil +} +// unmarshalBases do the reverse operation of marshalBases +func unmarshalBases(t byte, data []byte, bases ...baseUnmarshaler) error { if len(data) < 1 || data[0] != t { return errors.New(errors.Structural, "Not an expected packet") } - return nil + var rest []byte + var err error + + rest = data + for _, base := range bases { + if rest, err = base.Unmarshal(rest); err != nil { + return err + } + } + + return err } // UnmarshalPacket takes raw binary data and try to marshal it into a given packet interface: @@ -108,31 +130,12 @@ func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) // MarshalBinary implements the encoding.BinaryMarshaler interface func (p rpacket) MarshalBinary() ([]byte, error) { - data := []byte{type_rpacket} - dataBaseR, err := p.baserpacket.Marshal() - if err != nil { - return nil, err - } - dataBaseM, err := p.basempacket.Marshal() - if err != nil { - return nil, err - } - - return append(data, append(dataBaseR, dataBaseM...)...), nil + return marshalBases(type_rpacket, p.baserpacket, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *rpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, type_rpacket); err != nil { - return err - } - - rest, err := p.baserpacket.Unmarshal(data[1:]) - if err != nil { - return err - } - _, err = p.basempacket.Unmarshal(rest) - return err + return unmarshalBases(type_rpacket, data, &p.baserpacket, &p.basempacket) } // String implements the Stringer interface @@ -208,31 +211,12 @@ func (p bpacket) String() string { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p bpacket) MarshalBinary() ([]byte, error) { - data := []byte{type_bpacket} - dataBaseR, err := p.baserpacket.Marshal() - if err != nil { - return nil, err - } - dataBaseM, err := p.basempacket.Marshal() - if err != nil { - return nil, err - } - - return append(data, append(dataBaseR, dataBaseM...)...), nil + return marshalBases(type_bpacket, p.baserpacket, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *bpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, type_bpacket); err != nil { - return err - } - - rest, err := p.baserpacket.Unmarshal(data[1:]) - if err != nil { - return err - } - _, err = p.basempacket.Unmarshal(rest) - return err + return unmarshalBases(type_bpacket, data, &p.baserpacket, &p.basempacket) } // --------------------------------- @@ -267,40 +251,12 @@ func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, meta // MarshalBinary implements the encoding.BinaryMarshaler interface func (p hpacket) MarshalBinary() ([]byte, error) { - data := []byte{type_hpacket} - dataBaseH, err := p.basehpacket.Marshal() - if err != nil { - return nil, err - } - dataBaseA, err := p.baseapacket.Marshal() - if err != nil { - return nil, err - } - dataBaseM, err := p.basempacket.Marshal() - if err != nil { - return nil, err - } - - // data | dataBaseH | dataBaseA | dataBaseM - return append(append(data, append(dataBaseH, dataBaseA...)...), dataBaseM...), nil + return marshalBases(type_hpacket, p.basehpacket, p.baseapacket, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, type_hpacket); err != nil { - return err - } - - rest, err := p.basehpacket.Unmarshal(data[1:]) - if err != nil { - return err - } - rest, err = p.baseapacket.Unmarshal(rest) - if err != nil { - return err - } - _, err = p.basempacket.Unmarshal(rest) - return err + return unmarshalBases(type_hpacket, data, &p.basehpacket, &p.baseapacket, &p.basempacket) } // String implements the fmt.Stringer interface @@ -322,81 +278,38 @@ func (p hpacket) String() string { // apacket implements the core.APacket interface type apacket struct { baseapacket - metadata []Metadata + devEUI baseapacket + basegpacket } // NewAPacket constructs a new application packet -func NewAPacket(payload []byte, metadata []Metadata) (APacket, error) { +func NewAPacket(payload []byte, devEUI lorawan.EUI64, metadata []Metadata) (APacket, error) { if len(payload) == 0 { return nil, errors.New(errors.Structural, "Application packet must hold a payload") } return &apacket{ baseapacket: baseapacket{payload: payload}, - metadata: metadata, + devEUI: baseapacket{payload: devEUI[:]}, + basegpacket: basegpacket{metadata: metadata}, }, nil } -// Metadata implements the core.Metadata interface -func (p apacket) Metadata() []Metadata { - return p.metadata +// DevEUI implements the core.APacket interface +func (p apacket) DevEUI() lorawan.EUI64 { + var devEUI lorawan.EUI64 + copy(devEUI[:], p.devEUI.payload) + return devEUI } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p apacket) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - for _, m := range p.metadata { - data, err := m.MarshalJSON() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - rw.Write(data) - } - dataMetadata, err := rw.Bytes() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - dataBaseA, err := p.baseapacket.Marshal() - if err != nil { - return nil, err - } - - data := []byte{type_apacket} - return append(append(data, dataBaseA...), dataMetadata...), nil + return marshalBases(type_apacket, p.baseapacket, p.devEUI, p.basegpacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *apacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, type_apacket); err != nil { - return err - } - - rest, err := p.baseapacket.Unmarshal(data[1:]) - if err != nil { - return err - } - - p.metadata = make([]Metadata, 0) - rw := readwriter.New(rest) - for { - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - if rw.Err() != nil { - err, ok := rw.Err().(errors.Failure) - if ok && err.Nature == errors.Behavioural { - break - } - return errors.New(errors.Structural, rw.Err()) - } - metadata := new(Metadata) - if err := metadata.UnmarshalJSON(dataMetadata); err != nil { - return errors.New(errors.Structural, err) - } - - p.metadata = append(p.metadata, *metadata) - } - - return nil + return unmarshalBases(type_apacket, data, &p.baseapacket, &p.devEUI, &p.basegpacket) } // String implements the fmt.Stringer interface @@ -436,40 +349,12 @@ func (p jpacket) DevNonce() [2]byte { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p jpacket) MarshalBinary() ([]byte, error) { - data := []byte{type_jpacket} - dataBaseH, err := p.basehpacket.Marshal() - if err != nil { - return nil, err - } - dataBaseA, err := p.baseapacket.Marshal() - if err != nil { - return nil, err - } - dataBaseM, err := p.basempacket.Marshal() - if err != nil { - return nil, err - } - - // data | dataBaseH | dataBaseA | dataBaseM - return append(append(data, append(dataBaseH, dataBaseA...)...), dataBaseM...), nil + return marshalBases(type_jpacket, p.basehpacket, p.baseapacket, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *jpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, type_jpacket); err != nil { - return err - } - - rest, err := p.basehpacket.Unmarshal(data[1:]) - if err != nil { - return err - } - rest, err = p.baseapacket.Unmarshal(rest) - if err != nil { - return err - } - _, err = p.basempacket.Unmarshal(rest) - return err + return unmarshalBases(type_jpacket, data, &p.basehpacket, &p.baseapacket, &p.basempacket) } // String implements the fmt.Stringer interface @@ -509,40 +394,12 @@ func (p cpacket) NwkSKey() lorawan.AES128Key { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p cpacket) MarshalBinary() ([]byte, error) { - data := []byte{type_cpacket} - dataBaseH, err := p.basehpacket.Marshal() - if err != nil { - return nil, err - } - dataBaseA, err := p.baseapacket.Marshal() - if err != nil { - return nil, err - } - dataBaseN, err := p.nwkSKey.Marshal() - if err != nil { - return nil, err - } - - // data | dataBaseH | dataBaseA | dataBaseN - return append(append(data, append(dataBaseH, dataBaseA...)...), dataBaseN...), nil + return marshalBases(type_cpacket, p.basehpacket, p.baseapacket, p.nwkSKey) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *cpacket) UnmarshalBinary(data []byte) error { - if err := ensureUnmarshal(p, data, type_cpacket); err != nil { - return err - } - - rest, err := p.basehpacket.Unmarshal(data[1:]) - if err != nil { - return err - } - rest, err = p.baseapacket.Unmarshal(rest) - if err != nil { - return err - } - _, err = p.nwkSKey.Unmarshal(rest) - return err + return unmarshalBases(type_cpacket, data, &p.basehpacket, &p.baseapacket, &p.nwkSKey) } // String implements the fmt.Stringer interface @@ -563,6 +420,20 @@ func (p cpacket) String() string { // -------------------------------------- // -------------------------------------- // -------------------------------------- +// +// basempacket -> metadata Metadata +// baserpacket -> payload lorawan.PHYPayload +// baseapacket -> payload []byte +// basehpacket -> appEUI lorawan.EUI64, devEUI lorawan.EUI64 +// (ALWAYS LAST) basegpacket -> metadata []Metadata + +type baseMarshaler interface { + Marshal() ([]byte, error) +} + +type baseUnmarshaler interface { + Unmarshal(data []byte) ([]byte, error) +} // basempacket is used to compose other packets type basempacket struct { @@ -720,3 +591,49 @@ func (p *baseapacket) Unmarshal(data []byte) ([]byte, error) { rw.Read(func(data []byte) { p.payload = data }) return rw.Bytes() } + +// basegpacket is used to compose other packets +type basegpacket struct { + metadata []Metadata +} + +func (p basegpacket) Metadata() []Metadata { + return p.metadata +} + +func (p basegpacket) Marshal() ([]byte, error) { + rw := readwriter.New(nil) + for _, m := range p.metadata { + data, err := m.MarshalJSON() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + rw.Write(data) + } + return rw.Bytes() +} + +func (p *basegpacket) Unmarshal(data []byte) ([]byte, error) { + p.metadata = make([]Metadata, 0) + rw := readwriter.New(data) + + for { + var dataMetadata []byte + rw.Read(func(data []byte) { dataMetadata = data }) + if rw.Err() != nil { + err, ok := rw.Err().(errors.Failure) + if ok && err.Nature == errors.Behavioural { + break + } + return nil, errors.New(errors.Structural, rw.Err()) + } + metadata := new(Metadata) + if err := metadata.UnmarshalJSON(dataMetadata); err != nil { + return nil, errors.New(errors.Structural, err) + } + + p.metadata = append(p.metadata, *metadata) + } + + return nil, nil +} diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go index 7f5f928ce..7045cccf5 100644 --- a/refactor/packets_interfaces.go +++ b/refactor/packets_interfaces.go @@ -11,13 +11,11 @@ type RPacket interface { Packet Metadata() Metadata Payload() lorawan.PHYPayload - DevEUI() lorawan.EUI64 } type BPacket interface { Packet Commands() []lorawan.MACCommand - DevEUI() lorawan.EUI64 FCnt() uint32 Metadata() Metadata Payload() []byte @@ -27,7 +25,6 @@ type BPacket interface { type HPacket interface { Packet AppEUI() lorawan.EUI64 - DevEUI() lorawan.EUI64 Payload() []byte // FRMPayload Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up } @@ -41,7 +38,6 @@ type APacket interface { type JPacket interface { Packet AppEUI() lorawan.EUI64 - DevEUI() lorawan.EUI64 DevNonce() [2]byte Metadata() Metadata // Rssi + DutyCycle } @@ -49,7 +45,6 @@ type JPacket interface { type CPacket interface { Packet AppEUI() lorawan.EUI64 - DevEUI() lorawan.EUI64 Payload() []byte NwkSKey() lorawan.AES128Key } From 85ff6cfd11df1109ee93c9691bd0d361ba6958bb Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 16:36:10 +0100 Subject: [PATCH 0738/2266] [refactor] Define proper registrations interfaces hierarchy --- .../adapters/http/handlers/helpers_test.go | 6 +++ .../http/handlers/pubsubRegistration.go | 22 ++++------ refactor/adapters/http/http.go | 17 +------- refactor/adapters/http/httpRegistration.go | 27 ++---------- refactor/adapters/http/http_test.go | 13 ++---- .../mqtt/handlers/activationRegistration.go | 26 ++++++------ .../adapters/mqtt/handlers/activation_test.go | 8 ++-- .../adapters/mqtt/handlers/helpers_test.go | 42 +++++++------------ refactor/adapters/mqtt/mqtt_test.go | 6 +++ .../udp/handlers/build_utilities_test.go | 12 +++--- .../adapters/udp/handlers/conversions_test.go | 6 --- refactor/adapters/udp/handlers/semtech.go | 12 ++++-- .../adapters/udp/handlers/semtech_test.go | 21 ++++++---- refactor/adapters/udp/udpRegistration.go | 20 +-------- refactor/interfaces.go | 1 + refactor/registrations_interfaces.go | 19 +++++++++ 16 files changed, 109 insertions(+), 149 deletions(-) create mode 100644 refactor/registrations_interfaces.go diff --git a/refactor/adapters/http/handlers/helpers_test.go b/refactor/adapters/http/handlers/helpers_test.go index 06e4aeeef..ae073768c 100644 --- a/refactor/adapters/http/handlers/helpers_test.go +++ b/refactor/adapters/http/handlers/helpers_test.go @@ -17,6 +17,7 @@ import ( . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) // ----- TYPES utilities @@ -38,6 +39,11 @@ func (p testPacket) String() string { return p.payload } +// DevEUI implements the devEUI +func (p testPacket) DevEUI() lorawan.EUI64 { + return lorawan.EUI64{} +} + // ----- BUILD utilities func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) diff --git a/refactor/adapters/http/handlers/pubsubRegistration.go b/refactor/adapters/http/handlers/pubsubRegistration.go index ddf9b76a4..93083362b 100644 --- a/refactor/adapters/http/handlers/pubsubRegistration.go +++ b/refactor/adapters/http/handlers/pubsubRegistration.go @@ -6,7 +6,6 @@ package handlers import ( core "github.com/TheThingsNetwork/ttn/refactor" . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -18,27 +17,22 @@ type pubSubRegistration struct { devEUI lorawan.EUI64 } -// Recipient implements` the core.Registration interface +// Recipient implements the core.Registration interface func (r pubSubRegistration) Recipient() core.Recipient { return r.recipient } // AppEUI implements the core.Registration interface -func (r pubSubRegistration) AppEUI() (lorawan.EUI64, error) { - return r.appEUI, nil -} - -// AppSKey implements the core.Registration interface -func (r pubSubRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(errors.Implementation, "AppSKey noy supported on pubsub registration") +func (r pubSubRegistration) AppEUI() lorawan.EUI64 { + return r.appEUI } // DevEUI implements the core.Registration interface -func (r pubSubRegistration) DevEUI() (lorawan.EUI64, error) { - return r.devEUI, nil +func (r pubSubRegistration) DevEUI() lorawan.EUI64 { + return r.devEUI } -// NwkSKey implement the core.Registration interface -func (r pubSubRegistration) NwkSKey() (lorawan.AES128Key, error) { - return r.nwkSKey, nil +// NwkSKey implements the core.Registration interface +func (r pubSubRegistration) NwkSKey() lorawan.AES128Key { + return r.nwkSKey } diff --git a/refactor/adapters/http/http.go b/refactor/adapters/http/http.go index eda710798..733d3b562 100644 --- a/refactor/adapters/http/http.go +++ b/refactor/adapters/http/http.go @@ -15,7 +15,6 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" - "github.com/brocaar/lorawan" ) // Adapter type materializes an http adapter which implements the basic http protocol @@ -81,19 +80,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } // Try to define a more helpful context - var devEUI *lorawan.EUI64 - var ctx log.Interface - addressable, ok := p.(core.Addressable) - if ok { - if d, err := addressable.DevEUI(); err == nil { - ctx = a.ctx.WithField("devEUI", devEUI) - devEUI = &d - } - } - if devEUI == nil { - a.ctx.Warn("Unable to retrieve devEUI") - ctx = a.ctx - } + ctx := a.ctx.WithField("devEUI", p.DevEUI()) ctx.Debug("Sending Packet") // Determine whether it's a broadcast or a direct send @@ -160,7 +147,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err a.registrations <- RegReq{ Registration: httpRegistration{ recipient: rawRecipient, - devEUI: devEUI, + devEUI: p.DevEUI(), }, Chresp: nil, } diff --git a/refactor/adapters/http/httpRegistration.go b/refactor/adapters/http/httpRegistration.go index 2dcbf4b20..e62c31bf3 100644 --- a/refactor/adapters/http/httpRegistration.go +++ b/refactor/adapters/http/httpRegistration.go @@ -5,13 +5,12 @@ package http import ( core "github.com/TheThingsNetwork/ttn/refactor" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) type httpRegistration struct { recipient core.Recipient - devEUI *lorawan.EUI64 + devEUI lorawan.EUI64 } // Recipient implements the core.Registration inteface @@ -19,25 +18,7 @@ func (r httpRegistration) Recipient() core.Recipient { return r.recipient } -// AppEUI implements the core.Registration interface -func (r httpRegistration) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(errors.Implementation, "AppEUI not supported on http registration") -} - -// DevEUI implements the core.Registration interface -func (r httpRegistration) DevEUI() (lorawan.EUI64, error) { - if r.devEUI == nil { - return lorawan.EUI64{}, errors.New(errors.Implementation, "DevEUI not accessible on this registration") - } - return *r.devEUI, nil -} - -// AppSKey implements the core.Registration interface -func (r httpRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(errors.Implementation, "AppSKey not supported on http registration") -} - -// NwkSKey implements the core.Registration interface -func (r httpRegistration) NwkSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(errors.Implementation, "NextRegistration not supported on udp adapter") +// DevEUI implements the core.RRegistration interface +func (r httpRegistration) DevEUI() lorawan.EUI64 { + return r.devEUI } diff --git a/refactor/adapters/http/http_test.go b/refactor/adapters/http/http_test.go index 5f103582c..4830b789c 100644 --- a/refactor/adapters/http/http_test.go +++ b/refactor/adapters/http/http_test.go @@ -43,8 +43,8 @@ func (p testPacket) MarshalBinary() ([]byte, error) { } // DevEUI implements the core.Addressable interface -func (p testPacket) DevEUI() (lorawan.EUI64, error) { - return p.devEUI, nil +func (p testPacket) DevEUI() lorawan.EUI64 { + return p.devEUI } // String implements the core.Packet interface @@ -255,13 +255,8 @@ func checkRegistrations(t *testing.T, want []testRegistration, got []core.Regist outer: for _, rw := range want { for _, rg := range got { - devEUI, err := rg.DevEUI() - if err != nil { - Ko(t, "Got an invalid registration %+v", rg) - return - } - if devEUI != rw.DevEUI { - Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, devEUI) + if rg.DevEUI() != rw.DevEUI { + Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, rg.DevEUI()) } if reflect.DeepEqual(rw.Recipient.httpRecipient, rg.Recipient()) { continue outer diff --git a/refactor/adapters/mqtt/handlers/activationRegistration.go b/refactor/adapters/mqtt/handlers/activationRegistration.go index 5a941a220..2113054c6 100644 --- a/refactor/adapters/mqtt/handlers/activationRegistration.go +++ b/refactor/adapters/mqtt/handlers/activationRegistration.go @@ -16,27 +16,27 @@ type activationRegistration struct { appSKey lorawan.AES128Key } -// Recipient implements the core.Registration interface +// Recipient implements the core.HRegistration interface func (r activationRegistration) Recipient() core.Recipient { return r.recipient } -// AppEUI implements the core.Registration interface -func (r activationRegistration) AppEUI() (lorawan.EUI64, error) { - return r.appEUI, nil +// AppEUI implements the core.HRegistration interface +func (r activationRegistration) AppEUI() lorawan.EUI64 { + return r.appEUI } -// DevEUI implements the core.Registration interface -func (r activationRegistration) DevEUI() (lorawan.EUI64, error) { - return r.devEUI, nil +// DevEUI implements the core.HRegistration interface +func (r activationRegistration) DevEUI() lorawan.EUI64 { + return r.devEUI } -// AppSKey implements the core.Registration interface -func (r activationRegistration) AppSKey() (lorawan.AES128Key, error) { - return r.appSKey, nil +// AppSKey implements the core.HRegistration interface +func (r activationRegistration) AppSKey() lorawan.AES128Key { + return r.appSKey } -// NwkSKey implements the core.Registration interface -func (r activationRegistration) NwkSKey() (lorawan.AES128Key, error) { - return r.nwkSKey, nil +// NwkSKey implements the core.HRegistration interface +func (r activationRegistration) NwkSKey() lorawan.AES128Key { + return r.nwkSKey } diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/refactor/adapters/mqtt/handlers/activation_test.go index 8e8272a8f..1fbef6192 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/refactor/adapters/mqtt/handlers/activation_test.go @@ -38,10 +38,10 @@ func TestActivationHandle(t *testing.T) { Topic string // The topic to which the message is addressed Payload []byte // The message's payload - WantError *string // The expected error from the handler - WantSubscription *string // The topic to which a subscription is expected - WantRegistration core.Registration // The expected registration towards the adapter - WantPacket []byte // The expected packet towards the adapter + WantError *string // The expected error from the handler + WantSubscription *string // The topic to which a subscription is expected + WantRegistration core.HRegistration // The expected registration towards the adapter + WantPacket []byte // The expected packet towards the adapter }{ { Desc: "Ok client | Valid Topic | Valid Payload", diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/refactor/adapters/mqtt/handlers/helpers_test.go index d7513284b..7aa2af950 100644 --- a/refactor/adapters/mqtt/handlers/helpers_test.go +++ b/refactor/adapters/mqtt/handlers/helpers_test.go @@ -182,7 +182,7 @@ func checkTopics(t *testing.T, want string, got string) { Ko(t, "Topic does not match expectation.\nWant: %s\nGot: %s", want, got) } -func checkRegistrations(t *testing.T, want core.Registration, got core.Registration) { +func checkRegistrations(t *testing.T, want core.HRegistration, got core.Registration) { // Check if interfaces are nil if got == nil { if want == nil { @@ -213,46 +213,32 @@ func checkRegistrations(t *testing.T, want core.Registration, got core.Registrat } // Check DevEUIs - deWant, err := want.DevEUI() - if err != nil { - panic("Expected devEUI to be accessible in test registration") + if !reflect.DeepEqual(want.DevEUI(), got.DevEUI()) { + Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", want.DevEUI(), got.DevEUI()) + return } - deGot, err := got.DevEUI() - if err != nil || !reflect.DeepEqual(deWant, deGot) { - Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", deWant, deGot) + + rgot, ok := got.(core.HRegistration) + if !ok { + Ko(t, "Expected to receive an HRegistration but got %+v", got) return } // Check AppEUIs - aeWant, err := want.AppEUI() - if err != nil { - panic("Expected appEUI to be accessible in test registration") - } - aeGot, err := got.AppEUI() - if err != nil || !reflect.DeepEqual(aeWant, aeGot) { - Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", aeWant, aeGot) + if !reflect.DeepEqual(want.AppEUI(), rgot.AppEUI()) { + Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", want.AppEUI(), rgot.AppEUI()) return } // Check Network Session Keys - nkWant, err := want.NwkSKey() - if err != nil { - panic("Expected nwkSKey to be accessible in test registration") - } - nkGot, err := got.NwkSKey() - if err != nil || !reflect.DeepEqual(nkWant, nkGot) { - Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", nkWant, nkGot) + if !reflect.DeepEqual(want.NwkSKey(), rgot.NwkSKey()) { + Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", want.NwkSKey(), rgot.NwkSKey()) return } // Check Application Session Keys - akWant, err := want.AppSKey() - if err != nil { - panic("Expected nwkSKey to be accessible in test registration") - } - akGot, err := got.AppSKey() - if err != nil || !reflect.DeepEqual(akWant, akGot) { - Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", akWant, akGot) + if !reflect.DeepEqual(want.AppSKey(), rgot.AppSKey()) { + Ko(t, "Registrations' appSKey are different.\nWant: %v\nGot: %v", want.AppSKey(), rgot.AppSKey()) return } diff --git a/refactor/adapters/mqtt/mqtt_test.go b/refactor/adapters/mqtt/mqtt_test.go index 857e3ed73..29f2d620b 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/refactor/adapters/mqtt/mqtt_test.go @@ -15,6 +15,7 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) const brokerUrl = "0.0.0.0:1683" @@ -213,6 +214,11 @@ func (p testPacket) String() string { return string(p.payload) } +// DevEUI implements the core.Packet interface +func (p testPacket) DevEUI() lorawan.EUI64 { + return lorawan.EUI64{} +} + // ----- BUILD utilities func createAdapter(t *testing.T) (Client, core.Adapter) { client, err := NewClient("testClient", brokerUrl, Tcp) diff --git a/refactor/adapters/udp/handlers/build_utilities_test.go b/refactor/adapters/udp/handlers/build_utilities_test.go index 0be525088..8036230df 100644 --- a/refactor/adapters/udp/handlers/build_utilities_test.go +++ b/refactor/adapters/udp/handlers/build_utilities_test.go @@ -306,7 +306,7 @@ func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) metadata := genMetadata(rxpk) - test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) test.RXPK = rxpk return *test } @@ -321,7 +321,7 @@ func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { rxpk.Time = nil rxpk.Size = nil metadata := genMetadata(rxpk) - test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) test.RXPK = rxpk return *test } @@ -343,7 +343,7 @@ func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { metadata.Rssi = nil metadata.Stat = nil test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) return *test } @@ -352,7 +352,7 @@ func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { phyPayload := genPHYPayload(false) metadata := core.Metadata{} test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) return *test } @@ -368,7 +368,7 @@ func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { metadata.Fdev = nil metadata.Time = nil test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) return *test } @@ -377,6 +377,6 @@ func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { phyPayload := genPHYPayload(false) metadata := genFullMetadata() test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) return *test } diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/refactor/adapters/udp/handlers/conversions_test.go index 74118047d..0a2d318cd 100644 --- a/refactor/adapters/udp/handlers/conversions_test.go +++ b/refactor/adapters/udp/handlers/conversions_test.go @@ -13,7 +13,6 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" ) type convertRXPKTest struct { @@ -49,11 +48,6 @@ func TestConvertTXPKPacket(t *testing.T) { genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), genCoreExtraMetadata(&convertToTXPKTest{WantError: nil}), genCoreNoMetadata(&convertToTXPKTest{WantError: nil}), - convertToTXPKTest{ - CorePacket: core.NewRPacket(lorawan.PHYPayload{}, core.Metadata{}), - TXPK: semtech.TXPK{}, - WantError: pointer.String(string(errors.Structural)), - }, } for _, test := range tests { diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index 437de4522..bf0fad4c0 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -81,11 +81,15 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u packets <- udp.MsgReq{Data: data, Chresp: chresp} select { case resp := <-chresp: - pkt := new(core.RPacket) - if err := pkt.UnmarshalBinary(resp); err != nil { + itf, err := core.UnmarshalPacket(resp) + if err != nil { + return + } + pkt, ok := itf.(core.RPacket) // NOTE Here we'll handle join-accept + if !ok { return } - txpk, err := packet2txpk(*pkt) + txpk, err := packet2txpk(pkt) if err != nil { // TODO Log error return @@ -149,7 +153,7 @@ func rxpk2packet(p semtech.RXPK) (core.Packet, error) { // At the end, our converted packet hold the same metadata than the RXPK packet but the Data // which as been completely transformed into a lorawan Physical Payload. - return core.NewRPacket(payload, metadata), nil + return core.NewRPacket(payload, metadata) } func packet2txpk(p core.RPacket) (semtech.TXPK, error) { diff --git a/refactor/adapters/udp/handlers/semtech_test.go b/refactor/adapters/udp/handlers/semtech_test.go index a2f2e704d..8d13fd7be 100644 --- a/refactor/adapters/udp/handlers/semtech_test.go +++ b/refactor/adapters/udp/handlers/semtech_test.go @@ -20,7 +20,7 @@ import ( func TestSend(t *testing.T) { Desc(t, "Send is not supported") adapter, _ := genAdapter(t, 33000) - _, err := adapter.Send(core.RPacket{}) + _, err := adapter.Send(nil) CheckErrors(t, pointer.String(string(errors.Implementation)), err) } @@ -53,28 +53,28 @@ func TestNext(t *testing.T) { Adapter: adapter, Packet: genPUSH_ACK([]byte{0x22, 0x35}), WantAck: semtech.Packet{}, - WantNext: core.RPacket{}, + WantNext: nil, WantError: nil, }, { // Uplink PUSH_DATA with no RXPK Adapter: adapter, Packet: genPUSH_DATANoRXPK([]byte{0x22, 0x35}), WantAck: genPUSH_ACK([]byte{0x22, 0x35}), - WantNext: core.RPacket{}, + WantNext: nil, WantError: nil, }, { // Uplink PULL_DATA Adapter: adapter, Packet: genPULL_DATA([]byte{0x62, 0xfa}), WantAck: genPULL_ACK([]byte{0x62, 0xfa}), - WantNext: core.RPacket{}, + WantNext: nil, WantError: nil, }, { // Uplink PUSH_DATA with no encoded payload Adapter: adapter, Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), WantAck: genPUSH_ACK([]byte{0x22, 0x35}), - WantNext: core.RPacket{}, + WantNext: nil, WantError: nil, }, } @@ -103,14 +103,17 @@ func getNextPacket(next chan interface{}) (core.Packet, error) { err error packet []byte }) - var packet core.RPacket - err := packet.UnmarshalBinary(res.packet) + itf, err := core.UnmarshalPacket(res.packet) if err != nil { panic(err) } - return packet, res.err + pkt, ok := itf.(core.RPacket) + if !ok { + return itf.(core.Packet), res.err + } + return pkt, res.err case <-time.After(100 * time.Millisecond): - return core.RPacket{}, nil + return nil, nil } } diff --git a/refactor/adapters/udp/udpRegistration.go b/refactor/adapters/udp/udpRegistration.go index ec5feff5e..54fe6c26b 100644 --- a/refactor/adapters/udp/udpRegistration.go +++ b/refactor/adapters/udp/udpRegistration.go @@ -5,7 +5,6 @@ package udp import ( core "github.com/TheThingsNetwork/ttn/refactor" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -17,22 +16,7 @@ func (r udpRegistration) Recipient() core.Recipient { return nil } -// AppEUI implements the core.Registration interface -func (r udpRegistration) AppEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(errors.Implementation, "AppEUI not supported on udp registration") -} - // DevEUI implements the core.Registration interface -func (r udpRegistration) DevEUI() (lorawan.EUI64, error) { - return lorawan.EUI64{}, errors.New(errors.Implementation, "DevEUI not supported on udp registration") -} - -// AppSKey implements the core.Registration interface -func (r udpRegistration) AppSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(errors.Implementation, "AppSKey not supported on udp registration") -} - -// NwkSKey implements the core.Registration interface -func (r udpRegistration) NwkSKey() (lorawan.AES128Key, error) { - return lorawan.AES128Key{}, errors.New(errors.Implementation, "NwkSKey not supported on udp registration") +func (r udpRegistration) DevEUI() lorawan.EUI64 { + return lorawan.EUI64{} } diff --git a/refactor/interfaces.go b/refactor/interfaces.go index db744f6f2..492777a29 100644 --- a/refactor/interfaces.go +++ b/refactor/interfaces.go @@ -37,6 +37,7 @@ type Packet interface { type Registration interface { Recipient() Recipient + DevEUI() lorawan.EUI64 } type Recipient interface { diff --git a/refactor/registrations_interfaces.go b/refactor/registrations_interfaces.go new file mode 100644 index 000000000..514af9327 --- /dev/null +++ b/refactor/registrations_interfaces.go @@ -0,0 +1,19 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package refactor + +import ( + "github.com/brocaar/lorawan" +) + +type BRegistration interface { + Registration + AppEUI() lorawan.EUI64 + NwkSKey() lorawan.AES128Key +} + +type HRegistration interface { + BRegistration + AppSKey() lorawan.AES128Key +} From b19b511583873a58149530189d56ef598c72be31 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 16:36:49 +0100 Subject: [PATCH 0739/2266] [refactor] Fix oversight in Unmarshal for basepacket --- refactor/packets.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/refactor/packets.go b/refactor/packets.go index 026de701b..b9f90716e 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -50,7 +50,7 @@ func unmarshalBases(t byte, data []byte, bases ...baseUnmarshaler) error { var rest []byte var err error - rest = data + rest = data[1:] for _, base := range bases { if rest, err = base.Unmarshal(rest); err != nil { return err @@ -512,13 +512,17 @@ func (p baserpacket) Marshal() ([]byte, error) { } rw := readwriter.New(nil) - rw.Write([]byte{mtype}) + rw.Write(mtype) rw.Write(dataPayload) return rw.Bytes() } // Unmarshal hydrates the given basepacket from binaries data. func (p *baserpacket) Unmarshal(data []byte) ([]byte, error) { + if len(data) < 1 { + return nil, errors.New(errors.Structural, "Not a valid packet") + } + var isUp bool rw := readwriter.New(data) rw.Read(func(data []byte) { From a07a810c0a6a77ca4a19fbcf37b73f67c2fe56bd Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 16:40:19 +0100 Subject: [PATCH 0740/2266] [refactor] Rename core files and move a bit declarations --- refactor/{interfaces.go => core.go} | 22 -------------------- refactor/packets_interfaces.go | 9 ++++++++ refactor/{packet_test.go => packets_test.go} | 0 refactor/registrations_interfaces.go | 11 ++++++++++ 4 files changed, 20 insertions(+), 22 deletions(-) rename refactor/{interfaces.go => core.go} (70%) rename refactor/{packet_test.go => packets_test.go} (100%) diff --git a/refactor/interfaces.go b/refactor/core.go similarity index 70% rename from refactor/interfaces.go rename to refactor/core.go index 492777a29..7fa8db879 100644 --- a/refactor/interfaces.go +++ b/refactor/core.go @@ -3,13 +3,6 @@ package refactor -import ( - "encoding" - "fmt" - - "github.com/brocaar/lorawan" -) - type Component interface { Register(reg Registration, an AckNacker) error HandleUp(p []byte, an AckNacker, upAdapter Adapter) error @@ -28,18 +21,3 @@ type Adapter interface { Next() ([]byte, AckNacker, error) NextRegistration() (Registration, AckNacker, error) } - -type Packet interface { - DevEUI() lorawan.EUI64 - encoding.BinaryMarshaler - fmt.Stringer -} - -type Registration interface { - Recipient() Recipient - DevEUI() lorawan.EUI64 -} - -type Recipient interface { - encoding.BinaryMarshaler -} diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go index 7045cccf5..4fe53ffb6 100644 --- a/refactor/packets_interfaces.go +++ b/refactor/packets_interfaces.go @@ -4,9 +4,18 @@ package refactor import ( + "encoding" + "fmt" + "github.com/brocaar/lorawan" ) +type Packet interface { + DevEUI() lorawan.EUI64 + encoding.BinaryMarshaler + fmt.Stringer +} + type RPacket interface { Packet Metadata() Metadata diff --git a/refactor/packet_test.go b/refactor/packets_test.go similarity index 100% rename from refactor/packet_test.go rename to refactor/packets_test.go diff --git a/refactor/registrations_interfaces.go b/refactor/registrations_interfaces.go index 514af9327..c904e37e3 100644 --- a/refactor/registrations_interfaces.go +++ b/refactor/registrations_interfaces.go @@ -4,9 +4,20 @@ package refactor import ( + "encoding" + "github.com/brocaar/lorawan" ) +type Recipient interface { + encoding.BinaryMarshaler +} + +type Registration interface { + Recipient() Recipient + DevEUI() lorawan.EUI64 +} + type BRegistration interface { Registration AppEUI() lorawan.EUI64 From 9a50ad34dccf5b7d09bd1c9b5d99ca0be5e1ab61 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 17:47:02 +0100 Subject: [PATCH 0741/2266] [refactor] Enhance HPacket interface to allow payload decryption --- refactor/packets.go | 40 ++++++++++++++++++++++++++++------ refactor/packets_interfaces.go | 4 ++-- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/refactor/packets.go b/refactor/packets.go index b9f90716e..18e10ecad 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -228,35 +228,61 @@ func (p *bpacket) UnmarshalBinary(data []byte) error { // hpacket implements the HPacket interface type hpacket struct { basehpacket - baseapacket + payload baserpacket basempacket } // NewHPacket constructs a new Handler packet -func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, metadata Metadata) HPacket { - if payload == nil { - payload = make([]byte, 0) +func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload lorawan.PHYPayload, metadata Metadata) (HPacket, error) { + if payload.MACPayload == nil { + return nil, errors.New(errors.Structural, "MACPAyload should not be empty") + } + + macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") + } + + if len(macPayload.FRMPayload) != 1 { + return nil, errors.New(errors.Structural, "Invalid frame payload. Expected exactly 1") } + + if _, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload); !ok { + return nil, errors.New(errors.Structural, "Invalid frame payload. Expected only data") + } + return &hpacket{ basehpacket: basehpacket{ appEUI: appEUI, devEUI: devEUI, }, - baseapacket: baseapacket{ + payload: baserpacket{ payload: payload, }, basempacket: basempacket{metadata: metadata}, + }, nil +} + +// Payload implements the core.HPacket interface +func (p hpacket) Payload(key lorawan.AES128Key) ([]byte, error) { + macPayload := p.payload.payload.MACPayload.(*lorawan.MACPayload) + if err := macPayload.DecryptFRMPayload(key); err != nil { + return nil, errors.New(errors.Structural, err) + } + if len(macPayload.FRMPayload) != 1 { + return nil, errors.New(errors.Structural, "Unexpected Frame payload") } + return macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, nil } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p hpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_hpacket, p.basehpacket, p.baseapacket, p.basempacket) + return marshalBases(type_hpacket, p.basehpacket, p.payload, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_hpacket, data, &p.basehpacket, &p.baseapacket, &p.basempacket) + return unmarshalBases(type_hpacket, data, &p.basehpacket, &p.payload, &p.basempacket) } // String implements the fmt.Stringer interface diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go index 4fe53ffb6..25d19320e 100644 --- a/refactor/packets_interfaces.go +++ b/refactor/packets_interfaces.go @@ -34,8 +34,8 @@ type BPacket interface { type HPacket interface { Packet AppEUI() lorawan.EUI64 - Payload() []byte // FRMPayload - Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up + Payload(appSKey lorawan.AES128Key) ([]byte, error) // Unencrypted FRMPayload + Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up } type APacket interface { From c176a6c1caaf38d7c31505bc3197608dd6c346a8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 17:59:22 +0100 Subject: [PATCH 0742/2266] [refactor] Start to re-write handler properly with downlink --- refactor/components/handler/devStorage.go | 23 ++++ refactor/components/handler/handler.go | 145 +++++++++++++++++++++- 2 files changed, 165 insertions(+), 3 deletions(-) create mode 100644 refactor/components/handler/devStorage.go diff --git a/refactor/components/handler/devStorage.go b/refactor/components/handler/devStorage.go new file mode 100644 index 000000000..3c612617a --- /dev/null +++ b/refactor/components/handler/devStorage.go @@ -0,0 +1,23 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/brocaar/lorawan" +) + +type devStorage interface { + Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) +} + +type devEntry struct { + AppSKey lorawan.AES128Key + NwkSKey lorawan.AES128Key + Recipient Recipient +} + +type appEntry struct { + AppKey lorawan.AES128Key +} diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index dcff9b872..8f3a98eea 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -5,10 +5,25 @@ package handler import ( . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/brocaar/lorawan" ) // component implements the core.Component interface -type component struct{} +type component struct { + ctx log.Interface + devices devStorage + set chan<- bundle +} + +type bundle struct { + adapter Adapter + chresp chan interface{} + entry devEntry + id [16]byte + packet HPacket +} // New construct a new Handler component from ... func New() (Component, error) { @@ -21,8 +36,132 @@ func (h component) Register(reg Registration, an AckNacker) error { } // HandleUp implements the core.Component interface -func (h component) HandleUp(p []byte, an AckNacker, up Adapter) error { - return nil +func (h component) HandleUp(data []byte, an AckNacker, up Adapter) error { + itf, err := UnmarshalPacket(data) + if err != nil { + return errors.New(errors.Structural, data) + } + + switch itf.(type) { + case HPacket: + // 0. Retrieve the handler packet + packet := itf.(HPacket) + appEUI := packet.AppEUI() + devEUI := packet.DevEUI() + + // 1. Lookup for the associated AppSKey + Recipient + entry, err := h.devices.Lookup(appEUI, devEUI) + if err != nil { + return errors.New(errors.Operational, err) + } + + // 2. Prepare a channel to receive the response from the consumer + chresp := make(chan interface{}) + + // 3. Create a "bundle" which holds info waiting for other related packets + var bundleId [16]byte // AppEUI(8) | DevEUI(8) + copy(bundleId[:8], appEUI[:]) + copy(bundleId[8:], devEUI[:]) + + // 4. Send the actual bundle to the consumer + ctx := h.ctx.WithField("BundleID", bundleId) + ctx.Debug("Define new bundle") + h.set <- bundle{ + id: bundleId, + packet: packet, + entry: entry, + adapter: up, + chresp: chresp, + } + + // 5. Wait for the response. Could be an error, a packet or nothing. + // We'll respond to a maximum of one node. The handler will use the + // rssi + gateway's duty cycle to select to best fit. + // All other channels will get a nil response. + // If there's an error, all channels get the error. + resp := <-chresp + switch resp.(type) { + case Packet: + ctx.Debug("Received response with packet. Sending Ack") + an.Ack(resp.(Packet)) + case error: + ctx.WithError(resp.(error)).Warn("Received errored response. Sending Ack") + an.Nack() + return errors.New(errors.Operational, resp.(error)) + default: + ctx.Debug("Received empty response. Sending empty Ack") + an.Ack(nil) + } + + return nil + case JPacket: + return errors.New(errors.Implementation, "Join Request not yet implemented") + default: + return errors.New(errors.Structural, "Unhandled packet type") + } +} + +// consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, +// deduplicate them, and send a single enhanced packet to the upadapter for further processing. +func (h component) consumeBundles(chbundle <-chan []bundle) { + ctx := h.ctx.WithField("goroutine", "consumer") + ctx.Debug("Starting bundle consumer") + +browseBundles: + for bundles := range chbundle { + var metadata []Metadata + var devEUI lorawan.EUI64 + var payload []byte + var adapter Adapter + var recipient Recipient + + for i, bundle := range bundles { + // We only decrypt the payload of the first bundle's packet. + // We assume all the other to be equal and we'll merely collect + // metadata from other bundle. + if i == 0 { + var err error + payload, err = bundle.packet.Payload(bundle.entry.AppSKey) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + devEUI = bundle.packet.DevEUI() + adapter = bundle.adapter + recipient = bundle.entry.Recipient + } + + // And append metadata for each of them + metadata = append(metadata, bundle.packet.Metadata()) + } + + // Then create an application-level packet + packet, err := NewAPacket(payload, devEUI, metadata) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + // And send it + _, err = adapter.Send(packet, recipient) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + // Then respond to node -> no response for the moment + for _, bundle := range bundles { + bundle.chresp <- nil + } + } +} + +func (h component) abortConsume(fault error, bundles []bundle) { + err := errors.New(errors.Structural, fault) + h.ctx.WithError(err).Debug("Unable to consume bundle") + for _, bundle := range bundles { + bundle.chresp <- err + } } // HandleDown implements the core.Component interface From 17bbb6912ca74101c2209f66eb164200283b5576 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 21:27:39 +0100 Subject: [PATCH 0743/2266] [refactor] Allow uint to be written in the readwriter --- utils/readwriter/readwriter.go | 32 ++++++++++++--------- utils/readwriter/readwriter_test.go | 44 ++++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 39347e5c4..82ccf3bdf 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -46,28 +46,34 @@ func New(buf []byte) Interface { // Also, it writes the length of the given raw data encoded on 2 bytes before writting the data // itself. In that way, data can be appended and read easily. func (w *rw) Write(data interface{}) { - var raw []byte + var dataLen uint16 switch data.(type) { - case byte: - raw = []byte{data.(byte)} + case uint8: + dataLen = 1 + case uint16: + dataLen = 2 + case uint32: + dataLen = 4 + case uint64: + dataLen = 8 case []byte: - raw = data.([]byte) + dataLen = uint16(len(data.([]byte))) case lorawan.AES128Key: - data := data.(lorawan.AES128Key) - raw = data[:] + dataLen = uint16(len(data.(lorawan.AES128Key))) case lorawan.EUI64: - data := data.(lorawan.EUI64) - raw = data[:] + dataLen = uint16(len(data.(lorawan.EUI64))) case lorawan.DevAddr: - data := data.(lorawan.DevAddr) - raw = data[:] + dataLen = uint16(len(data.(lorawan.DevAddr))) case string: - raw = []byte(data.(string)) + str := data.(string) + w.directWrite(uint16(len(str))) + w.directWrite([]byte(str)) + return default: panic(fmt.Errorf("Unreckognized data type: %v", data)) } - w.directWrite(uint16(len(raw))) - w.directWrite(raw) + w.directWrite(dataLen) + w.directWrite(data) } // directWrite appends the given data at the end of the existing buffer (without the length). diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go index 2722d6e6f..bb02384ae 100644 --- a/utils/readwriter/readwriter_test.go +++ b/utils/readwriter/readwriter_test.go @@ -171,6 +171,48 @@ func TestReadWriter(t *testing.T) { // -------------- + { + Desc(t, "Write uint16") + rw := New(nil) + rw.Write(uint16(35)) + data, err := rw.Bytes() + CheckErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{0, 35}, data) }) + CheckErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write uint32") + rw := New(nil) + rw.Write(uint32(4567)) + data, err := rw.Bytes() + CheckErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{0, 0, 17, 215}, data) }) + CheckErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Write uint64") + rw := New(nil) + rw.Write(uint64(324675)) + data, err := rw.Bytes() + CheckErrors(t, nil, err) + + rw = New(data) + rw.Read(func(data []byte) { checkData(t, []byte{0, 0, 0, 0, 0, 4, 244, 67}, data) }) + CheckErrors(t, nil, rw.Err()) + } + + // -------------- + { Desc(t, "Write invalid data") rw := New(nil) @@ -180,7 +222,7 @@ func TestReadWriter(t *testing.T) { recover() close(chwait) }() - rw.Write(14) + rw.Write(34.7) checkNotCalled(t) }() <-chwait From defb0da64d2e489794ff8ca09c16e671b598e631 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 21:36:37 +0100 Subject: [PATCH 0744/2266] [refactor] Continue implementing handler. Now we reached the same point as before --- refactor/components/handler/handler.go | 112 ++++++++++++++++++++----- refactor/packets.go | 15 ++-- refactor/packets_interfaces.go | 1 + 3 files changed, 102 insertions(+), 26 deletions(-) diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index 8f3a98eea..de80f2440 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -4,12 +4,18 @@ package handler import ( + "reflect" + "time" + . "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" "github.com/apex/log" "github.com/brocaar/lorawan" ) +const buffer_delay time.Duration = time.Millisecond * 300 + // component implements the core.Component interface type component struct { ctx log.Interface @@ -18,11 +24,11 @@ type component struct { } type bundle struct { - adapter Adapter - chresp chan interface{} - entry devEntry - id [16]byte - packet HPacket + Adapter Adapter + Chresp chan interface{} + Entry devEntry + Id [20]byte + Packet HPacket } // New construct a new Handler component from ... @@ -59,19 +65,26 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) error { chresp := make(chan interface{}) // 3. Create a "bundle" which holds info waiting for other related packets - var bundleId [16]byte // AppEUI(8) | DevEUI(8) - copy(bundleId[:8], appEUI[:]) - copy(bundleId[8:], devEUI[:]) + var bundleId [20]byte // AppEUI(8) | DevEUI(8) + rw := readwriter.New(nil) + rw.Write(appEUI) + rw.Write(devEUI) + rw.Write(packet.FCnt()) + data, err := rw.Bytes() + if err != nil { + return errors.New(errors.Structural, err) + } + copy(bundleId[:], data[:]) // 4. Send the actual bundle to the consumer ctx := h.ctx.WithField("BundleID", bundleId) ctx.Debug("Define new bundle") h.set <- bundle{ - id: bundleId, - packet: packet, - entry: entry, - adapter: up, - chresp: chresp, + Id: bundleId, + Packet: packet, + Entry: entry, + Adapter: up, + Chresp: chresp, } // 5. Wait for the response. Could be an error, a packet or nothing. @@ -104,7 +117,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) error { // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, // deduplicate them, and send a single enhanced packet to the upadapter for further processing. func (h component) consumeBundles(chbundle <-chan []bundle) { - ctx := h.ctx.WithField("goroutine", "consumer") + ctx := h.ctx.WithField("goroutine", "bundle consumer") ctx.Debug("Starting bundle consumer") browseBundles: @@ -121,18 +134,18 @@ browseBundles: // metadata from other bundle. if i == 0 { var err error - payload, err = bundle.packet.Payload(bundle.entry.AppSKey) + payload, err = bundle.Packet.Payload(bundle.Entry.AppSKey) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } - devEUI = bundle.packet.DevEUI() - adapter = bundle.adapter - recipient = bundle.entry.Recipient + devEUI = bundle.Packet.DevEUI() + adapter = bundle.Adapter + recipient = bundle.Entry.Recipient } // And append metadata for each of them - metadata = append(metadata, bundle.packet.Metadata()) + metadata = append(metadata, bundle.Packet.Metadata()) } // Then create an application-level packet @@ -151,19 +164,76 @@ browseBundles: // Then respond to node -> no response for the moment for _, bundle := range bundles { - bundle.chresp <- nil + bundle.Chresp <- nil } } } +// Abort consume forward the given error to all bundle recipients func (h component) abortConsume(fault error, bundles []bundle) { err := errors.New(errors.Structural, fault) h.ctx.WithError(err).Debug("Unable to consume bundle") for _, bundle := range bundles { - bundle.chresp <- err + bundle.Chresp <- err } } +// consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) +// It then flushes them once a given delay has passed since the reception of the first bundle. +func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { + ctx := h.ctx.WithField("goroutine", "set consumer") + ctx.Debug("Starting packets buffering") + + processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? + buffers := make(map[[20]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles + alarm := make(chan [20]byte) // Communication channel with subsequent alarms + + for { + select { + case id := <-alarm: + // Get all bundles + bundles := buffers[id] + delete(buffers, id) + + // Register the last processed entry + var pid [16]byte + copy(pid[:], id[:16]) + processed[pid] = id[16:] + + // Actually send the bundle to the be processed + go func(bundles []bundle) { chbundles <- bundles }(bundles) + ctx.WithField("BundleID", id).Debug("Consuming collected bundles") + case b := <-chset: + ctx = ctx.WithField("BundleID", b.Id) + + // Check if bundle has already been processed + var pid [16]byte + copy(pid[:], b.Id[:16]) + if reflect.DeepEqual(processed[pid], b.Id[16:]) { + ctx.Debug("Reject already processed bundle") + go func(b bundle) { + b.Chresp <- errors.New(errors.Behavioural, "Already processed") + }(b) + continue + } + + // Add the bundle to the stack, and set the alarm if its the first + bundles := append(buffers[b.Id], b) + if len(bundles) == 1 { + go setAlarm(alarm, b.Id, buffer_delay) + ctx.Debug("Buffering started -> new alarm set") + } + buffers[b.Id] = bundles + } + } +} + +// setAlarm will trigger a message on the given channel after the given delay +func setAlarm(alarm chan<- [20]byte, id [20]byte, delay time.Duration) { + <-time.After(delay) + alarm <- id +} + // HandleDown implements the core.Component interface func (h component) HandleDown(p []byte, an AckNacker, down Adapter) error { return nil diff --git a/refactor/packets.go b/refactor/packets.go index 18e10ecad..77d1099e7 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -183,11 +183,6 @@ func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) }, nil } -// FCnt implements the core.BPacket interface -func (p bpacket) FCnt() uint32 { - return p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt -} - // Payload implements the core.BPacket interface func (p bpacket) Payload() []byte { macPayload := p.baserpacket.payload.MACPayload.(*lorawan.MACPayload) @@ -275,6 +270,11 @@ func (p hpacket) Payload(key lorawan.AES128Key) ([]byte, error) { return macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, nil } +// FCnt implements the core.HPacket interface +func (p hpacket) FCnt() uint32 { + return p.payload.FCnt() +} + // MarshalBinary implements the encoding.BinaryMarshaler interface func (p hpacket) MarshalBinary() ([]byte, error) { return marshalBases(type_hpacket, p.basehpacket, p.payload, p.basempacket) @@ -511,6 +511,11 @@ func (p baserpacket) DevEUI() lorawan.EUI64 { return devEUI } +// FCnt implements the core.BPacket interface +func (p baserpacket) FCnt() uint32 { + return p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt +} + // Marshal transforms the given basepacket to binaries func (p baserpacket) Marshal() ([]byte, error) { var mtype byte diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go index 25d19320e..85e07e0ba 100644 --- a/refactor/packets_interfaces.go +++ b/refactor/packets_interfaces.go @@ -34,6 +34,7 @@ type BPacket interface { type HPacket interface { Packet AppEUI() lorawan.EUI64 + FCnt() uint32 Payload(appSKey lorawan.AES128Key) ([]byte, error) // Unencrypted FRMPayload Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up } From 454aa0dd35daff2748bf5649fec054cd76e2a195 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 21:58:41 +0100 Subject: [PATCH 0745/2266] [refactor] Implement handler.Register() method --- refactor/components/handler/devStorage.go | 1 + refactor/components/handler/handler.go | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/refactor/components/handler/devStorage.go b/refactor/components/handler/devStorage.go index 3c612617a..c1b16b442 100644 --- a/refactor/components/handler/devStorage.go +++ b/refactor/components/handler/devStorage.go @@ -10,6 +10,7 @@ import ( type devStorage interface { Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) + Store(r HRegistration) error } type devEntry struct { diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index de80f2440..260ecf640 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -37,7 +37,25 @@ func New() (Component, error) { } // Register implements the core.Component interface -func (h component) Register(reg Registration, an AckNacker) error { +func (h component) Register(reg Registration, an AckNacker) (err error) { + h.ctx.WithField("registration", reg).Debug("New registration request") + defer func() { + if err != nil { + an.Nack() + } else { + an.Ack(nil) + } + + }() + + hreg, ok := reg.(HRegistration) + if !ok { + return errors.New(errors.Structural, "Not a Handler registration") + } + + if err = h.devices.Store(hreg); err != nil { + return errors.New(errors.Operational, err) + } return nil } From 05e2e77a4ea25d8440f07f46b27f8e11b7c4377e Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 22:04:21 +0100 Subject: [PATCH 0746/2266] [refactor] Add small comment on the processed map --- refactor/components/handler/handler.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index 260ecf640..04e1237ec 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -202,6 +202,9 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { ctx := h.ctx.WithField("goroutine", "set consumer") ctx.Debug("Starting packets buffering") + // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture + // with a ttl for each entry. Processed is merely there to avoid late packets from being + // processed again. The TTL could be only of several seconds or minutes. processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? buffers := make(map[[20]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles alarm := make(chan [20]byte) // Communication channel with subsequent alarms From 63191729cdf61e4e8009ece9feee7d5c4a29c9e9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 23:05:47 +0100 Subject: [PATCH 0747/2266] [refactor] Implement downlink part in handler --- refactor/components/handler/handler.go | 48 ++++++++++++++++++++------ 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index 04e1237ec..58829e338 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -20,6 +20,7 @@ const buffer_delay time.Duration = time.Millisecond * 300 type component struct { ctx log.Interface devices devStorage + packets pktStorage set chan<- bundle } @@ -39,14 +40,7 @@ func New() (Component, error) { // Register implements the core.Component interface func (h component) Register(reg Registration, an AckNacker) (err error) { h.ctx.WithField("registration", reg).Debug("New registration request") - defer func() { - if err != nil { - an.Nack() - } else { - an.Ack(nil) - } - - }() + defer ensureAckNack(an, nil, &err) hreg, ok := reg.(HRegistration) if !ok { @@ -60,7 +54,11 @@ func (h component) Register(reg Registration, an AckNacker) (err error) { } // HandleUp implements the core.Component interface -func (h component) HandleUp(data []byte, an AckNacker, up Adapter) error { +func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { + // Make sure we don't forget the AckNacker + var ack Packet + defer ensureAckNack(an, &ack, &err) + itf, err := UnmarshalPacket(data) if err != nil { return errors.New(errors.Structural, data) @@ -128,7 +126,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) error { case JPacket: return errors.New(errors.Implementation, "Join Request not yet implemented") default: - return errors.New(errors.Structural, "Unhandled packet type") + return errors.New(errors.Implementation, "Unhandled packet type") } } @@ -256,6 +254,34 @@ func setAlarm(alarm chan<- [20]byte, id [20]byte, delay time.Duration) { } // HandleDown implements the core.Component interface -func (h component) HandleDown(p []byte, an AckNacker, down Adapter) error { +func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err error) { + // Make sure we don't forget the AckNacker + var ack Packet + defer ensureAckNack(an, &ack, &err) + + // Unmarshal the given packet and see what gift we get + itf, err := UnmarshalPacket(data) + if err != nil { + return errors.New(errors.Structural, data) + } return nil + + switch itf.(type) { + case HPacket: + return h.packets.Push(itf.(HPacket)) + default: + return errors.New(errors.Implementation, "Unhandled packet type") + } +} + +func ensureAckNack(an AckNacker, ack *Packet, err *error) { + if err != nil && *err != nil { + an.Nack() + } else { + var p Packet + if ack != nil { + p = *ack + } + an.Ack(p) + } } From 44c8f85411fb35995b3b6813bfc830590cb1fee4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 26 Feb 2016 23:06:07 +0100 Subject: [PATCH 0748/2266] [refactor] Rewrite old mqttStorage as pktStorage --- refactor/components/handler/pktStorage.go | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 refactor/components/handler/pktStorage.go diff --git a/refactor/components/handler/pktStorage.go b/refactor/components/handler/pktStorage.go new file mode 100644 index 000000000..72eb925bf --- /dev/null +++ b/refactor/components/handler/pktStorage.go @@ -0,0 +1,108 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + dbutil "github.com/TheThingsNetwork/ttn/utils/storage" + "github.com/brocaar/lorawan" +) + +type PktStorage interface { + Push(p HPacket) error + Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, error) +} + +type pktStorage struct { + dbutil.Interface + Name string +} + +type pktEntry struct { + HPacket +} + +// NewPktStorage creates a new PktStorage +func NewStorage(name string) (PktStorage, error) { + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + tableName := "pkt_storage" + if err := itf.Init(tableName); err != nil { + return nil, errors.New(errors.Operational, err) + } + return pktStorage{Interface: itf, Name: tableName}, nil +} + +func keyFromEUIs(appEUI lorawan.EUI64, devEUI lorawan.EUI64) []byte { + return append(appEUI[:], devEUI[:]...) +} + +// Push implements the PktStorage interface +func (s pktStorage) Push(p HPacket) error { + err := s.Store(s.Name, keyFromEUIs(p.AppEUI(), p.DevEUI()), []dbutil.Entry{&pktEntry{p}}) + if err != nil { + return errors.New(errors.Operational, err) + } + return nil +} + +// Pull implements the PktStorage interface +func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, error) { + key := keyFromEUIs(appEUI, devEUI) + + entries, err := s.Lookup(s.Name, key, &pktEntry{}) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + packets, ok := entries.([]*pktEntry) + if !ok { + return nil, errors.New(errors.Operational, "Unable to retrieve data from db") + } + + // NOTE: one day, those entries will be more complicated, with a ttl. + // Here's the place where we should check for that. Cheers. + if len(packets) == 0 { + return nil, errors.New(errors.Behavioural, fmt.Sprintf("Entry not found for %v", key)) + } + + pkt := packets[0] + + var newEntries []dbutil.Entry + for _, p := range packets[1:] { + newEntries = append(newEntries, p) + } + + if err := s.Replace(s.Name, key, newEntries); err != nil { + // TODO This is critical... we've just lost a packet + return nil, errors.New(errors.Operational, "Unable to restore data in db") + } + + return pkt.HPacket, nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e pktEntry) MarshalBinary() ([]byte, error) { + return e.HPacket.MarshalBinary() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *pktEntry) UnmarshalBinary(data []byte) error { + itf, err := UnmarshalPacket(data) + if err != nil { + return errors.New(errors.Structural, err) + } + packet, ok := itf.(HPacket) + if !ok { + return errors.New(errors.Structural, "Not a Handler packet") + } + e.HPacket = packet + return nil +} From c90b799d4f79a927f2126113544856e1802f6a8b Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 27 Feb 2016 00:20:17 +0100 Subject: [PATCH 0749/2266] [refactor] Implement duty cycle and rssi control in handler --- refactor/components/handler/handler.go | 57 ++++++++++++++++++++------ refactor/metadata.go | 2 + 2 files changed, 47 insertions(+), 12 deletions(-) diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index 58829e338..2d5d201ab 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -11,10 +11,10 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" "github.com/apex/log" - "github.com/brocaar/lorawan" ) const buffer_delay time.Duration = time.Millisecond * 300 +const max_duty_cycle = 90 // 90% // component implements the core.Component interface type component struct { @@ -130,6 +130,18 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } } +func computeScore(dutyCycle uint, rssi int) uint { + if dutyCycle > max_duty_cycle { + return 0 + } + + if dutyCycle > 2*max_duty_cycle/3 { + return uint(1000 - rssi) + } + + return uint(10000 - rssi) +} + // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, // deduplicate them, and send a single enhanced packet to the upadapter for further processing. func (h component) consumeBundles(chbundle <-chan []bundle) { @@ -139,10 +151,9 @@ func (h component) consumeBundles(chbundle <-chan []bundle) { browseBundles: for bundles := range chbundle { var metadata []Metadata - var devEUI lorawan.EUI64 var payload []byte - var adapter Adapter - var recipient Recipient + var bestBundle bundle + var bestScore uint for i, bundle := range bundles { // We only decrypt the payload of the first bundle's packet. @@ -155,24 +166,42 @@ browseBundles: go h.abortConsume(err, bundles) continue browseBundles } - devEUI = bundle.Packet.DevEUI() - adapter = bundle.Adapter - recipient = bundle.Entry.Recipient + bestBundle = bundle } - // And append metadata for each of them + // Append metadata for each of them metadata = append(metadata, bundle.Packet.Metadata()) + + // And try to find the best recipient to which answer + duty := bundle.Packet.Metadata().Duty + rssi := bundle.Packet.Metadata().Rssi + if duty == nil || rssi == nil { + continue + } + score := computeScore(*duty, *rssi) + if score > bestScore { + bestScore = score + bestBundle = bundle + } } // Then create an application-level packet - packet, err := NewAPacket(payload, devEUI, metadata) + packet, err := NewAPacket(payload, bestBundle.Packet.DevEUI(), metadata) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + // And send it to the wild open + // we don't expect a response from the adapter, end of the chain. + _, err = bestBundle.Adapter.Send(packet, bestBundle.Entry.Recipient) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } - // And send it - _, err = adapter.Send(packet, recipient) + // Now handle the downlink + down, err := h.packets.Pull(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI()) if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -180,7 +209,11 @@ browseBundles: // Then respond to node -> no response for the moment for _, bundle := range bundles { - bundle.Chresp <- nil + if bundle.Id == bestBundle.Id { + bundle.Chresp <- down + } else { + bundle.Chresp <- nil + } } } } diff --git a/refactor/metadata.go b/refactor/metadata.go index fe8f1dca0..c68fce7c5 100644 --- a/refactor/metadata.go +++ b/refactor/metadata.go @@ -16,8 +16,10 @@ type Metadata struct { Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Duty *uint `json:"duty,omitempty"` // DutyCycle of the gateway (uint between 0 and 100) Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) From d8bfc575e5299efc832ae0fbebc22b1d47c5da06 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 27 Feb 2016 01:25:05 +0100 Subject: [PATCH 0750/2266] [refactor] Rewrite and implement broker --- refactor/components/broker/broker.go | 172 ++++++++++++++++++++++++++ refactor/components/broker/storage.go | 22 ++++ refactor/core.go | 11 ++ refactor/packets.go | 6 - refactor/packets_interfaces.go | 2 +- 5 files changed, 206 insertions(+), 7 deletions(-) create mode 100644 refactor/components/broker/broker.go create mode 100644 refactor/components/broker/storage.go diff --git a/refactor/components/broker/broker.go b/refactor/components/broker/broker.go new file mode 100644 index 000000000..60ed76b42 --- /dev/null +++ b/refactor/components/broker/broker.go @@ -0,0 +1,172 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/apex/log" +) + +// component implements the core.Component interface +type component struct { + Storage + ctx log.Interface + Controller NetworkController +} + +// New construct a new Broker component from ... +func New() (Component, error) { + return nil, nil +} + +// Register implements the core.Component interface +func (b component) Register(reg Registration, an AckNacker) (err error) { + defer ensureAckNack(an, nil, &err) + stats.MarkMeter("broker.registration.in") + b.ctx.Debug("Handling registration") + + breg, ok := reg.(BRegistration) + if !ok { + return errors.New(errors.Structural, "Not a Broker registration") + } + + if err := b.Store(breg); err != nil { + return errors.New(errors.Operational, err) + } + return nil +} + +// HandleUp implements the core.Component interface +func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { + // Make sure we don't forget the AckNacker + var ack Packet + defer ensureAckNack(an, &ack, &err) + + // Get some logs / analytics + stats.MarkMeter("boker.uplink.in") + b.ctx.Debug("Handling uplink packet") + + // Extract the given packet + itf, err := UnmarshalPacket(data) + if err != nil { + stats.MarkMeter("broker.uplink.invalid") + b.ctx.Warn("Uplink Invalid") + return errors.New(errors.Structural, err) + } + + switch itf.(type) { + case BPacket: + // NOTE So far, we're not using the Frame Counter. This has to be done to get a correct + // behavior. The frame counter needs to be used to ensure we're not processing a wrong or + // late packet. + + // 0. Retrieve the packet + packet := itf.(BPacket) + ctx := b.ctx.WithField("DevEUI", packet.DevEUI()) + + // 1. Check whether we should handle it + entries, err := b.Lookup(packet.DevEUI()) + if err != nil { + switch err.(errors.Failure).Nature { + case errors.Behavioural: + stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") + ctx.Warn("Uplink device not found") + default: + b.ctx.Warn("Database lookup failed") + } + return err + } + stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) + + // 2. Several handlers might be associated to the same device, we distinguish them using + // MIC check. Only one should verify the MIC check + + var mEntry *entry + for _, entry := range entries { + ok, err := packet.ValidateMIC(entry.NwkSKey) + if err != nil { + continue + } + if ok { + mEntry = &entry + stats.MarkMeter("broker.uplink.handler_lookup.match") + ctx.WithField("handler", entry.Recipient).Debug("Associated device with handler") + break + } + } + if mEntry == nil { + stats.MarkMeter("broker.uplink.handler_lookup.no_match") + err := errors.New(errors.Behavioural, "Could not find handler for device") + ctx.Warn(err.Error()) + return err + } + + // It does matter here to use the DevEUI from the entry and not from the packet. + // The packet actually holds a DevAddr and the real DevEUI has been determined thanks + // to the MIC check + + // 3. If one was found, we notify the network controller + if err := b.Controller.HandleCommands(packet); err != nil { + ctx.WithError(err).Error("Failed to handle mac commands") + // Shall we return ? Sounds quite safe to keep going + } + b.Controller.UpdateFCntUp(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt()) + + // 4. Then we forward the packet to the handler and wait for the response + hpacket, err := NewHPacket(mEntry.AppEUI, mEntry.DevEUI, packet.Payload(), packet.Metadata()) + if err != nil { + return errors.New(errors.Structural, err) + } + resp, err := up.Send(hpacket, mEntry.Recipient) + if err != nil { + stats.MarkMeter("broker.uplink.bad_handler_response") + return errors.New(errors.Operational, err) + } + stats.MarkMeter("broker.uplink.ok") + + // 5. If a response was sent, i.e. a downlink data, we notify the network controller + var bpacket BPacket + if resp != nil { + itf, err := UnmarshalPacket(resp) + if err != nil { + return errors.New(errors.Operational, err) + } + var ok bool + bpacket, ok = itf.(BPacket) + if !ok { + return errors.New(errors.Operational, "Received unexpected response") + } + b.Controller.UpdateFCntDown(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt()) + } + + // 6. And finally, we acknowledge the answer + ack = b.Controller.MergeCommands(mEntry.AppEUI, mEntry.DevEUI, bpacket) + case JPacket: + // TODO + return errors.New(errors.Implementation, "Join Request not yet implemented") + default: + return errors.New(errors.Implementation, "Unreckognized packet type") + } + + return nil +} + +// HandleDown implements the core.Component interface +func (b component) HandleDown(data []byte, an AckNacker, down Adapter) error { + return errors.New(errors.Implementation, "Handle Down not implemented on broker") +} + +func ensureAckNack(an AckNacker, ack *Packet, err *error) { + if err != nil && *err != nil { + an.Nack() + } else { + var p Packet + if ack != nil { + p = *ack + } + an.Ack(p) + } +} diff --git a/refactor/components/broker/storage.go b/refactor/components/broker/storage.go new file mode 100644 index 000000000..d2013920a --- /dev/null +++ b/refactor/components/broker/storage.go @@ -0,0 +1,22 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/brocaar/lorawan" +) + +type Storage interface { + Lookup(devEUI lorawan.EUI64) ([]entry, error) + Store(reg BRegistration) error + Close() error +} + +type entry struct { + Recipient Recipient + AppEUI lorawan.EUI64 + DevEUI lorawan.EUI64 + NwkSKey lorawan.AES128Key +} diff --git a/refactor/core.go b/refactor/core.go index 7fa8db879..2c3940ded 100644 --- a/refactor/core.go +++ b/refactor/core.go @@ -3,12 +3,23 @@ package refactor +import ( + "github.com/brocaar/lorawan" +) + type Component interface { Register(reg Registration, an AckNacker) error HandleUp(p []byte, an AckNacker, upAdapter Adapter) error HandleDown(p []byte, an AckNacker, downAdapter Adapter) error } +type NetworkController interface { + HandleCommands(packet BPacket) error + UpdateFCntUp(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) + UpdateFCntDown(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) + MergeCommands(appEUI lorawan.EUI64, devEUI lorawan.EUI64, pkt BPacket) RPacket +} + type AckNacker interface { Ack(p Packet) error Nack() error diff --git a/refactor/packets.go b/refactor/packets.go index 77d1099e7..2e8cdcd60 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -183,12 +183,6 @@ func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) }, nil } -// Payload implements the core.BPacket interface -func (p bpacket) Payload() []byte { - macPayload := p.baserpacket.payload.MACPayload.(*lorawan.MACPayload) - return macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes -} - // ValidateMIC implements the core.BPacket interface func (p bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { return p.baserpacket.payload.ValidateMIC(key) diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go index 85e07e0ba..df044461e 100644 --- a/refactor/packets_interfaces.go +++ b/refactor/packets_interfaces.go @@ -27,7 +27,7 @@ type BPacket interface { Commands() []lorawan.MACCommand FCnt() uint32 Metadata() Metadata - Payload() []byte + Payload() lorawan.PHYPayload ValidateMIC(key lorawan.AES128Key) (bool, error) } From 14d76ea8eac0d5a09b43f8c799a0e47920815c57 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 27 Feb 2016 16:21:35 +0100 Subject: [PATCH 0751/2266] [refactor] Implements router backbone + register method --- refactor/components/router/router.go | 50 +++++++++++++++++++++++++++ refactor/components/router/storage.go | 20 +++++++++++ 2 files changed, 70 insertions(+) create mode 100644 refactor/components/router/router.go create mode 100644 refactor/components/router/storage.go diff --git a/refactor/components/router/router.go b/refactor/components/router/router.go new file mode 100644 index 000000000..bd72294ef --- /dev/null +++ b/refactor/components/router/router.go @@ -0,0 +1,50 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/apex/log" +) + +type component struct { + Storage + ctx log.Interface +} + +// Register implements the core.Component interface +func (r component) Register(reg Registration, an AckNacker) (err error) { + defer ensureAckNack(an, nil, &err) + stats.MarkMeter("router.registration.in") + r.ctx.Debug("Handling registration") + + if err := r.Store(reg); err != nil { + return errors.New(errors.Operational, err) + } + return nil +} + +// HandleUp implements the core.Component interface +func (r component) HandleUp(p Packet, an AckNacker, up Adapter) error { + return nil +} + +// HandleDown implements the core.Component interface +func (r component) HandleDown(p Packet, an AckNacker, up Adapter) error { + return errors.New(errors.Implementation, "Handle down not implemented on router") +} + +func ensureAckNack(an AckNacker, ack *Packet, err *error) { + if err != nil && *err != nil { + an.Nack() + } else { + var p Packet + if ack != nil { + p = *ack + } + an.Ack(p) + } +} diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go new file mode 100644 index 000000000..083bcf02b --- /dev/null +++ b/refactor/components/router/storage.go @@ -0,0 +1,20 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/brocaar/lorawan" +) + +type Storage interface { + Lookup(devEUI lorawan.EUI64) ([]entry, error) + Store(reg Registration) error + Close() error +} + +type entry struct { + Recipient Recipient + DevEUI lorawan.EUI64 +} From da76362018a510419f5c182e2e3bbe001df99209 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 27 Feb 2016 16:22:39 +0100 Subject: [PATCH 0752/2266] [refactor] Fix small typo in broker stat tag --- refactor/components/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refactor/components/broker/broker.go b/refactor/components/broker/broker.go index 60ed76b42..0ab4146f8 100644 --- a/refactor/components/broker/broker.go +++ b/refactor/components/broker/broker.go @@ -46,7 +46,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { defer ensureAckNack(an, &ack, &err) // Get some logs / analytics - stats.MarkMeter("boker.uplink.in") + stats.MarkMeter("broker.uplink.in") b.ctx.Debug("Handling uplink packet") // Extract the given packet From b1496e7269bd252807c17c518af4dd44f02c833c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 27 Feb 2016 16:42:12 +0100 Subject: [PATCH 0753/2266] [refactor] Add GatewayId to router packet --- .../udp/handlers/build_utilities_test.go | 14 +++++++------- .../adapters/udp/handlers/conversions_test.go | 2 +- refactor/adapters/udp/handlers/semtech.go | 6 +++--- refactor/packets.go | 18 +++++++++++++----- refactor/packets_interfaces.go | 7 +++++++ 5 files changed, 31 insertions(+), 16 deletions(-) diff --git a/refactor/adapters/udp/handlers/build_utilities_test.go b/refactor/adapters/udp/handlers/build_utilities_test.go index 8036230df..743a6a9f1 100644 --- a/refactor/adapters/udp/handlers/build_utilities_test.go +++ b/refactor/adapters/udp/handlers/build_utilities_test.go @@ -101,7 +101,7 @@ func genCorePacket(p semtech.Packet) core.Packet { if p.Payload == nil || len(p.Payload.RXPK) != 1 { panic("Expected a payload with one rxpk") } - packet, err := rxpk2packet(p.Payload.RXPK[0]) + packet, err := rxpk2packet(p.Payload.RXPK[0], p.GatewayId) if err != nil { panic(err) } @@ -306,7 +306,7 @@ func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { phyPayload := genPHYPayload(true) rxpk := genRXPK(phyPayload) metadata := genMetadata(rxpk) - test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) test.RXPK = rxpk return *test } @@ -321,7 +321,7 @@ func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { rxpk.Time = nil rxpk.Size = nil metadata := genMetadata(rxpk) - test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) test.RXPK = rxpk return *test } @@ -343,7 +343,7 @@ func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { metadata.Rssi = nil metadata.Stat = nil test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) return *test } @@ -352,7 +352,7 @@ func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { phyPayload := genPHYPayload(false) metadata := core.Metadata{} test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) return *test } @@ -368,7 +368,7 @@ func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { metadata.Fdev = nil metadata.Time = nil test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) return *test } @@ -377,6 +377,6 @@ func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { phyPayload := genPHYPayload(false) metadata := genFullMetadata() test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, metadata) + test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) return *test } diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/refactor/adapters/udp/handlers/conversions_test.go index 0a2d318cd..58e685611 100644 --- a/refactor/adapters/udp/handlers/conversions_test.go +++ b/refactor/adapters/udp/handlers/conversions_test.go @@ -30,7 +30,7 @@ func TestConvertRXPKPacket(t *testing.T) { for _, test := range tests { Desc(t, "Convert RXPK: %s", pointer.DumpPStruct(test.RXPK, false)) - packet, err := rxpk2packet(test.RXPK) + packet, err := rxpk2packet(test.RXPK, []byte{1, 2, 3, 4, 5, 6, 7, 8}) CheckErrors(t, test.WantError, err) checkPackets(t, test.CorePacket, packet) } diff --git a/refactor/adapters/udp/handlers/semtech.go b/refactor/adapters/udp/handlers/semtech.go index bf0fad4c0..2c812dde4 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/refactor/adapters/udp/handlers/semtech.go @@ -67,7 +67,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u for _, rxpk := range pkt.Payload.RXPK { go func(rxpk semtech.RXPK) { - pktOut, err := rxpk2packet(rxpk) + pktOut, err := rxpk2packet(rxpk, pkt.GatewayId) if err != nil { // TODO Log error return @@ -113,7 +113,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u } } -func rxpk2packet(p semtech.RXPK) (core.Packet, error) { +func rxpk2packet(p semtech.RXPK, gid []byte) (core.Packet, error) { // First, we have to get the physical payload which is encoded in the Data field if p.Data == nil { return nil, errors.New(errors.Structural, "There's no data in the packet") @@ -153,7 +153,7 @@ func rxpk2packet(p semtech.RXPK) (core.Packet, error) { // At the end, our converted packet hold the same metadata than the RXPK packet but the Data // which as been completely transformed into a lorawan Physical Payload. - return core.NewRPacket(payload, metadata) + return core.NewRPacket(payload, gid, metadata) } func packet2txpk(p core.RPacket) (semtech.TXPK, error) { diff --git a/refactor/packets.go b/refactor/packets.go index 2e8cdcd60..0522ab1e5 100644 --- a/refactor/packets.go +++ b/refactor/packets.go @@ -19,6 +19,7 @@ const ( type_apacket type_jpacket type_cpacket + type_spacket ) // --------------------------------- @@ -109,10 +110,11 @@ func UnmarshalPacket(data []byte) (interface{}, error) { type rpacket struct { baserpacket basempacket + gatewayId baseapacket } // NewRPacket construct a new router packet given a payload and metadata -func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) { +func NewRPacket(payload lorawan.PHYPayload, gatewayId []byte, metadata Metadata) (RPacket, error) { if payload.MACPayload == nil { return nil, errors.New(errors.Structural, "MACPAyload should not be empty") } @@ -123,19 +125,25 @@ func NewRPacket(payload lorawan.PHYPayload, metadata Metadata) (RPacket, error) } return &rpacket{ - baserpacket{payload: payload}, - basempacket{metadata: metadata}, + baserpacket: baserpacket{payload: payload}, + basempacket: basempacket{metadata: metadata}, + gatewayId: baseapacket{payload: gatewayId}, }, nil } +// GatewayId implements the core.RPacket interface +func (p rpacket) GatewayId() []byte { + return p.gatewayId.payload +} + // MarshalBinary implements the encoding.BinaryMarshaler interface func (p rpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_rpacket, p.baserpacket, p.basempacket) + return marshalBases(type_rpacket, p.baserpacket, p.basempacket, p.gatewayId) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *rpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_rpacket, data, &p.baserpacket, &p.basempacket) + return unmarshalBases(type_rpacket, data, &p.baserpacket, &p.basempacket, &p.gatewayId) } // String implements the Stringer interface diff --git a/refactor/packets_interfaces.go b/refactor/packets_interfaces.go index df044461e..fbdceae59 100644 --- a/refactor/packets_interfaces.go +++ b/refactor/packets_interfaces.go @@ -18,10 +18,17 @@ type Packet interface { type RPacket interface { Packet + GatewayId() []byte Metadata() Metadata Payload() lorawan.PHYPayload } +type SPacket interface { + Packet + GatewayId() []byte + Metadata() Metadata +} + type BPacket interface { Packet Commands() []lorawan.MACCommand From 6399ec163452dc50e757f312f5c917fe1a3396cc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 27 Feb 2016 17:22:19 +0100 Subject: [PATCH 0754/2266] [refactor] Quickly implement router handleUp --- refactor/components/router/router.go | 81 ++++++++++++++++++++++++++- refactor/components/router/storage.go | 3 +- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/refactor/components/router/router.go b/refactor/components/router/router.go index bd72294ef..cd1cfa2d8 100644 --- a/refactor/components/router/router.go +++ b/refactor/components/router/router.go @@ -28,12 +28,89 @@ func (r component) Register(reg Registration, an AckNacker) (err error) { } // HandleUp implements the core.Component interface -func (r component) HandleUp(p Packet, an AckNacker, up Adapter) error { +func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { + // Make sure we don't forget the AckNacker + var ack Packet + defer ensureAckNack(an, &ack, &err) + + // Get some logs / analytics + stats.MarkMeter("router.uplink.in") + r.ctx.Debug("Handling uplink packet") + + // Extract the given packet + itf, err := UnmarshalPacket(data) + if err != nil { + stats.MarkMeter("router.uplink.invalid") + r.ctx.Warn("Uplink Invalid") + return errors.New(errors.Structural, err) + } + + switch itf.(type) { + case RPacket: + packet := itf.(RPacket) + + // Lookup for an existing broker + // NOTE We are still assuming only one broker associated to one device address. + // We should find a mechanism to make sure that the broker in database is really + // associated to the device to avoid trouble during overlaping. + // Keeping track of the last FCnt maybe ? Having an overlap on the frame counter + the + // device address might be less likely. + entry, err := r.Lookup(packet.DevEUI()) + if err != nil && err.(errors.Failure).Nature != errors.Behavioural { + r.ctx.Warn("Database lookup failed") + return errors.New(errors.Operational, err) + } + + var recipient Recipient + if err == nil { + recipient = entry.Recipient + } + + // TODO -> Add Gateway Metadata to packet + + bpacket, err := NewBPacket(packet.Payload(), packet.Metadata()) + if err != nil { + r.ctx.WithError(err).Warn("Unable to create router packet") + return errors.New(errors.Structural, err) + } + + response, err := up.Send(bpacket, recipient) + + if err != nil { + stats.MarkMeter("router.uplink.bad_broker_response") + r.ctx.WithError(err).Warn("Invalid response from Broker") + return errors.New(errors.Operational, err) + } + + itf, err := UnmarshalPacket(response) + if err != nil { + stats.MarkMeter("router.uplink.bad_broker_response") + r.ctx.WithError(err).Warn("Invalid response from Broker") + return errors.New(errors.Structural, err) + } + + switch itf.(type) { + case RPacket: + ack = itf.(RPacket) + case nil: + default: + return errors.New(errors.Implementation, "Unexpected packet type") + } + + stats.MarkMeter("router.uplink.ok") + case SPacket: + return errors.New(errors.Implementation, "Stats packet not yet implemented") + case JPacket: + return errors.New(errors.Implementation, "Join Request not yet implemented") + default: + return errors.New(errors.Implementation, "Unreckognized packet type") + } + return nil } // HandleDown implements the core.Component interface -func (r component) HandleDown(p Packet, an AckNacker, up Adapter) error { +func (r component) HandleDown(data []byte, an AckNacker, up Adapter) error { return errors.New(errors.Implementation, "Handle down not implemented on router") } diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go index 083bcf02b..cd40102b7 100644 --- a/refactor/components/router/storage.go +++ b/refactor/components/router/storage.go @@ -9,12 +9,11 @@ import ( ) type Storage interface { - Lookup(devEUI lorawan.EUI64) ([]entry, error) + Lookup(devEUI lorawan.EUI64) (entry, error) Store(reg Registration) error Close() error } type entry struct { Recipient Recipient - DevEUI lorawan.EUI64 } From 58e21172a3136ba97ca7116b664e2a7395f16f16 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 11:51:41 +0100 Subject: [PATCH 0755/2266] [refactor] implements router_storage constructor and backbone --- refactor/components/router/storage.go | 36 ++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go index cd40102b7..16d0edf3c 100644 --- a/refactor/components/router/storage.go +++ b/refactor/components/router/storage.go @@ -4,16 +4,50 @@ package router import ( + "time" + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + dbutil "github.com/TheThingsNetwork/ttn/utils/storage" "github.com/brocaar/lorawan" ) type Storage interface { Lookup(devEUI lorawan.EUI64) (entry, error) Store(reg Registration) error - Close() error } type entry struct { Recipient Recipient + until time.Time +} + +type storage struct { + dbutil.Interface + Name string +} + +// newStorage creates a new internal storage for the router +func newStorage(name string, delay time.Duration) (Storage, error) { + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + tableName := "brokers" + if err := itf.Init(tableName); err != nil { + return nil, errors.New(errors.Operational, err) + } + + return storage{Interface: itf, Name: tableName}, nil +} + +// Lookup implements the router.Storage interface +func (s storage) Lookup(devEUI lorawan.EUI64) (entry, error) { + return entry{}, nil +} + +// Store implements the router.Storage interface +func (s storage) Store(reg Registration) error { + return nil } From 59413833705a6c9e9cd799ba1cdf176311eaf3c9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 12:25:43 +0100 Subject: [PATCH 0756/2266] [refactor] Add TryRead() to readwriter to allow read to return error --- utils/readwriter/readwriter.go | 24 ++++++++++++--- utils/readwriter/readwriter_test.go | 47 +++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 82ccf3bdf..49560f220 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -15,6 +15,7 @@ import ( type Interface interface { Write(data interface{}) + TryRead(to func(data []byte) error) Read(to func(data []byte)) Bytes() ([]byte, error) Err() error @@ -91,15 +92,25 @@ func (w *rw) directWrite(data interface{}) { // written using the Write method (len | data). Data are sent back through a callback as an array of // bytes. func (w *rw) Read(to func(data []byte)) { + w.err = w.read(func(data []byte) error { to(data); return nil }) +} + +// TryRead retrieves the next data from the given buffer but differs from Read in the way it listen +// to the callback returns for a possible error. +func (w *rw) TryRead(to func(data []byte) error) { + w.err = w.read(to) +} + +func (w *rw) read(to func(data []byte) error) error { if w.err != nil { - return + return w.err } lenTo := new(uint16) - if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil { - return + if err := binary.Read(w.data, binary.BigEndian, lenTo); err != nil { + return err } - to(w.data.Next(int(*lenTo))) + return to(w.data.Next(int(*lenTo))) } // Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and @@ -117,7 +128,10 @@ func (w rw) Err() error { if w.err == io.EOF { return errors.New(errors.Behavioural, w.err) } - return errors.New(errors.Structural, w.err) + if failure, ok := w.err.(errors.Failure); ok { + return failure + } + return errors.New(errors.Operational, w.err) } return nil } diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go index bb02384ae..39417ddf5 100644 --- a/utils/readwriter/readwriter_test.go +++ b/utils/readwriter/readwriter_test.go @@ -4,6 +4,7 @@ package readwriter import ( + "fmt" "reflect" "testing" @@ -227,6 +228,52 @@ func TestReadWriter(t *testing.T) { }() <-chwait } + + // -------------- + + { + Desc(t, "Try read with no error") + rw := New(nil) + rw.Write("TTN") + data, _ := rw.Bytes() + + rw = New(data) + rw.TryRead(func(data []byte) error { + checkData(t, []byte("TTN"), data) + return nil + }) + CheckErrors(t, nil, rw.Err()) + } + + // -------------- + + { + Desc(t, "Try read with classic error") + rw := New(nil) + rw.Write("TTN") + data, _ := rw.Bytes() + + rw = New(data) + rw.TryRead(func(data []byte) error { + return fmt.Errorf("My Error") + }) + CheckErrors(t, pointer.String(string(errors.Operational)), rw.Err()) + } + + // -------------- + + { + Desc(t, "Try read with failure") + rw := New(nil) + rw.Write("TTN") + data, _ := rw.Bytes() + + rw = New(data) + rw.TryRead(func(data []byte) error { + return errors.New(errors.Behavioural, "Don't feel like to read") + }) + CheckErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) + } } // ----- CHECK utilities From 1d8e22272cd6280993dd182fcd48bf27366e6abd Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 12:29:43 +0100 Subject: [PATCH 0757/2266] [refactor] Move Interface to a private field in handler/pktStorage --- refactor/components/handler/pktStorage.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/refactor/components/handler/pktStorage.go b/refactor/components/handler/pktStorage.go index 72eb925bf..f3de2c295 100644 --- a/refactor/components/handler/pktStorage.go +++ b/refactor/components/handler/pktStorage.go @@ -18,7 +18,7 @@ type PktStorage interface { } type pktStorage struct { - dbutil.Interface + db dbutil.Interface Name string } @@ -37,7 +37,7 @@ func NewStorage(name string) (PktStorage, error) { if err := itf.Init(tableName); err != nil { return nil, errors.New(errors.Operational, err) } - return pktStorage{Interface: itf, Name: tableName}, nil + return pktStorage{db: itf, Name: tableName}, nil } func keyFromEUIs(appEUI lorawan.EUI64, devEUI lorawan.EUI64) []byte { @@ -46,7 +46,7 @@ func keyFromEUIs(appEUI lorawan.EUI64, devEUI lorawan.EUI64) []byte { // Push implements the PktStorage interface func (s pktStorage) Push(p HPacket) error { - err := s.Store(s.Name, keyFromEUIs(p.AppEUI(), p.DevEUI()), []dbutil.Entry{&pktEntry{p}}) + err := s.db.Store(s.Name, keyFromEUIs(p.AppEUI(), p.DevEUI()), []dbutil.Entry{&pktEntry{p}}) if err != nil { return errors.New(errors.Operational, err) } @@ -57,7 +57,7 @@ func (s pktStorage) Push(p HPacket) error { func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, error) { key := keyFromEUIs(appEUI, devEUI) - entries, err := s.Lookup(s.Name, key, &pktEntry{}) + entries, err := s.db.Lookup(s.Name, key, &pktEntry{}) if err != nil { return nil, errors.New(errors.Operational, err) } @@ -80,7 +80,7 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, e newEntries = append(newEntries, p) } - if err := s.Replace(s.Name, key, newEntries); err != nil { + if err := s.db.Replace(s.Name, key, newEntries); err != nil { // TODO This is critical... we've just lost a packet return nil, errors.New(errors.Operational, "Unable to restore data in db") } From 609801e30a24f1efe9c0290647c27aab8b2a41cc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 12:29:53 +0100 Subject: [PATCH 0758/2266] [refactor] Re-implement router lookup --- refactor/components/router/router.go | 6 ++- refactor/components/router/storage.go | 66 +++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 6 deletions(-) diff --git a/refactor/components/router/router.go b/refactor/components/router/router.go index cd1cfa2d8..36da65f1a 100644 --- a/refactor/components/router/router.go +++ b/refactor/components/router/router.go @@ -63,7 +63,11 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { var recipient Recipient if err == nil { - recipient = entry.Recipient + rawRecipient := entry.Recipient + if recipient, err = up.GetRecipient(rawRecipient); err != nil { + r.ctx.Warn("Unable to retrieve Recipient") + return errors.New(errors.Operational, err) + } } // TODO -> Add Gateway Metadata to packet diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go index 16d0edf3c..d8bfa5b84 100644 --- a/refactor/components/router/storage.go +++ b/refactor/components/router/storage.go @@ -4,10 +4,12 @@ package router import ( + "sync" "time" . "github.com/TheThingsNetwork/ttn/refactor" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" "github.com/brocaar/lorawan" ) @@ -18,13 +20,15 @@ type Storage interface { } type entry struct { - Recipient Recipient + Recipient []byte until time.Time } type storage struct { - dbutil.Interface - Name string + sync.Mutex + db dbutil.Interface + Name string + ExpiryDelay time.Duration } // newStorage creates a new internal storage for the router @@ -39,15 +43,67 @@ func newStorage(name string, delay time.Duration) (Storage, error) { return nil, errors.New(errors.Operational, err) } - return storage{Interface: itf, Name: tableName}, nil + return storage{db: itf, ExpiryDelay: delay, Name: tableName}, nil } // Lookup implements the router.Storage interface func (s storage) Lookup(devEUI lorawan.EUI64) (entry, error) { - return entry{}, nil + return s.lookup(devEUI, true) +} + +// lookup offers an indirection in order to avoid taking a lock if not needed +func (s storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { + // NOTE This works under the assumption that a read or write lock is already held by + // the callee (e.g. Store() + if lock { + s.Lock() + defer s.Unlock() + } + + itf, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) + if err != nil { + return entry{}, errors.New(errors.Operational, err) + } + entries := itf.([]*entry) + + if len(entries) != 1 { + if err := s.db.Flush(s.Name, devEUI[:]); err != nil { + return entry{}, errors.New(errors.Operational, err) + } + return entry{}, errors.New(errors.Behavioural, "Not Found") + } + + e := entries[0] + + if s.ExpiryDelay != 0 && e.until.Before(time.Now()) { + if err := s.db.Flush(s.Name, devEUI[:]); err != nil { + return entry{}, errors.New(errors.Operational, err) + } + return entry{}, errors.New(errors.Behavioural, "Not Found") + } + + return *e, nil } // Store implements the router.Storage interface func (s storage) Store(reg Registration) error { return nil } + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e entry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.Recipient) + rw.Write(e.until) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *entry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.Recipient = data }) + rw.TryRead(func(data []byte) error { + return e.until.UnmarshalBinary(data) + }) + return rw.Err() +} From 23ff940e4ea11e2f128ff6df0a118c02bb1c75d4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 13:04:36 +0100 Subject: [PATCH 0759/2266] [refactor] re-Implement router/storage.Store() method --- refactor/components/router/storage.go | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go index d8bfa5b84..363ca1398 100644 --- a/refactor/components/router/storage.go +++ b/refactor/components/router/storage.go @@ -87,7 +87,24 @@ func (s storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { // Store implements the router.Storage interface func (s storage) Store(reg Registration) error { - return nil + devEUI := reg.DevEUI() + recipient, err := reg.Recipient().MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) + } + + s.Lock() + defer s.Unlock() + + _, err = s.lookup(devEUI, false) + if err == nil || err != nil && err.(errors.Failure).Nature != errors.Behavioural { + return errors.New(errors.Structural, "Already exists") + } + return s.db.Store(s.Name, devEUI[:], []dbutil.Entry{&entry{ + Recipient: recipient, + until: time.Now().Add(s.ExpiryDelay), + }}) + } // MarshalBinary implements the encoding.BinaryMarshaler interface From 1a30af251cf5ddb4be8a2ed2df5e02653e28dc73 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 18:35:27 +0100 Subject: [PATCH 0760/2266] [refactor] Make NewStorage a public export in components/router --- refactor/components/router/storage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go index 363ca1398..8c39ff4f1 100644 --- a/refactor/components/router/storage.go +++ b/refactor/components/router/storage.go @@ -31,8 +31,8 @@ type storage struct { ExpiryDelay time.Duration } -// newStorage creates a new internal storage for the router -func newStorage(name string, delay time.Duration) (Storage, error) { +// NewStorage creates a new internal storage for the router +func NewStorage(name string, delay time.Duration) (Storage, error) { itf, err := dbutil.New(name) if err != nil { return nil, errors.New(errors.Operational, err) From c4d8dfa0f1112cd377b5c7d823ffb99b950758a2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 19:19:43 +0100 Subject: [PATCH 0761/2266] [refactor] Re-implement broker storage --- refactor/components/broker/storage.go | 77 ++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 1 deletion(-) diff --git a/refactor/components/broker/storage.go b/refactor/components/broker/storage.go index d2013920a..617573bd1 100644 --- a/refactor/components/broker/storage.go +++ b/refactor/components/broker/storage.go @@ -5,6 +5,9 @@ package broker import ( . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" + dbutil "github.com/TheThingsNetwork/ttn/utils/storage" "github.com/brocaar/lorawan" ) @@ -15,8 +18,80 @@ type Storage interface { } type entry struct { - Recipient Recipient + Recipient []byte AppEUI lorawan.EUI64 DevEUI lorawan.EUI64 NwkSKey lorawan.AES128Key } + +type storage struct { + db dbutil.Interface + Name string +} + +// NewStorage constructs a new broker storage +func NewStorage(name string) (Storage, error) { + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + tableName := "handlers" + if err := itf.Init(tableName); err != nil { + return nil, errors.New(errors.Operational, err) + } + + return storage{db: itf, Name: tableName}, nil +} + +// Lookup implements the broker.Storage interface +func (s storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { + entries, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + return entries.([]entry), nil +} + +// Store implements the broker.Storage interface +func (s storage) Store(reg BRegistration) error { + data, err := reg.Recipient().MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) + } + + key := reg.DevEUI() + return s.db.Store(s.Name, key[:], []dbutil.Entry{ + &entry{ + Recipient: data, + AppEUI: reg.AppEUI(), + DevEUI: key, + NwkSKey: reg.NwkSKey(), + }, + }) +} + +// Close implements the broker.Storage interface +func (s storage) Close() error { + return s.db.Close() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e entry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.Recipient) + rw.Write(e.AppEUI) + rw.Write(e.DevEUI) + rw.Write(e.NwkSKey) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *entry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.Recipient = data }) + rw.Read(func(data []byte) { copy(e.AppEUI[:], data) }) + rw.Read(func(data []byte) { copy(e.DevEUI[:], data) }) + rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + return rw.Err() +} From d4f43ed5c1ec71a876cc125d59a726a9f61bca82 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 19:22:23 +0100 Subject: [PATCH 0762/2266] [refactor] Fix recipient unmarshaling in broker Handle() --- refactor/components/broker/broker.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/refactor/components/broker/broker.go b/refactor/components/broker/broker.go index 0ab4146f8..8b09038dd 100644 --- a/refactor/components/broker/broker.go +++ b/refactor/components/broker/broker.go @@ -120,7 +120,11 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if err != nil { return errors.New(errors.Structural, err) } - resp, err := up.Send(hpacket, mEntry.Recipient) + recipient, err := up.GetRecipient(mEntry.Recipient) + if err != nil { + return errors.New(errors.Structural, err) + } + resp, err := up.Send(hpacket, recipient) if err != nil { stats.MarkMeter("broker.uplink.bad_handler_response") return errors.New(errors.Operational, err) From d1fe67de610f29ca7fe687eba7dc4a06cf3419e5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 20:13:13 +0100 Subject: [PATCH 0763/2266] [refactor] Add mosquitto to travis --- .travis.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 499210abe..3dd056dfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,12 +1,25 @@ language: go +sudo: required + go: - 1.5 +before_install: + - wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key + - sudo apt-key add mosquitto-repo.gpg.key + - wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list + - sudo apt-get update + install: + - sudo apt-get install mosquitto mosquitto-clients - go get $(comm -23 <(sort <(go list -f '{{join .Imports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) + +before_script: + - mosquitto -p 1683 1>/dev/null 2>/dev/null & + script: - - go list ./... | grep -v integration | xargs go test - - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - - go vet ./... + - go list ./... | grep -v integration | xargs go test + - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' + - go vet ./... From 8b8b7b10ff8535d217d1de37fc926c6a371214c4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 20:59:34 +0100 Subject: [PATCH 0764/2266] [refactor] Write backbone of handler devStorage --- refactor/components/handler/devStorage.go | 62 ++++++++++++++++++++++- refactor/components/handler/pktStorage.go | 2 +- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/refactor/components/handler/devStorage.go b/refactor/components/handler/devStorage.go index c1b16b442..41fe29e47 100644 --- a/refactor/components/handler/devStorage.go +++ b/refactor/components/handler/devStorage.go @@ -5,20 +5,78 @@ package handler import ( . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/readwriter" + dbutil "github.com/TheThingsNetwork/ttn/utils/storage" "github.com/brocaar/lorawan" ) -type devStorage interface { +type DevStorage interface { Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) Store(r HRegistration) error } type devEntry struct { + Recipient []byte AppSKey lorawan.AES128Key NwkSKey lorawan.AES128Key - Recipient Recipient } type appEntry struct { AppKey lorawan.AES128Key } + +type devStorage struct { + db dbutil.Interface +} + +// NewDevStorage creates a new Device Storage for handler +func NewDevStorage(name string) (DevStorage, error) { + return nil, nil +} + +// Lookup implements the handler.DevStorage interface +func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { + return devEntry{}, nil +} + +// Store implements the handler.DevStorage interface +func (s devStorage) Store(reg HRegistration) error { + return nil +} + +// Close implements the handler.DevStorage interface +func (s devStorage) Close() error { + return s.db.Close() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e devEntry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.Recipient) + rw.Write(e.AppSKey) + rw.Write(e.NwkSKey) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *devEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.Recipient = data }) + rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) + rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + return rw.Err() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e appEntry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.AppKey) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *appEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) + return rw.Err() +} diff --git a/refactor/components/handler/pktStorage.go b/refactor/components/handler/pktStorage.go index f3de2c295..78b0edf32 100644 --- a/refactor/components/handler/pktStorage.go +++ b/refactor/components/handler/pktStorage.go @@ -27,7 +27,7 @@ type pktEntry struct { } // NewPktStorage creates a new PktStorage -func NewStorage(name string) (PktStorage, error) { +func NewPktStorage(name string) (PktStorage, error) { itf, err := dbutil.New(name) if err != nil { return nil, errors.New(errors.Operational, err) From 3d78e338f2ae11e4ef7832e9f636ffbaa06cca97 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 21:33:00 +0100 Subject: [PATCH 0765/2266] [refactor] Remove the need of Init function + add automatic bucket creation when missing --- utils/storage/storage.go | 57 ++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 19 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index e14aba18a..384b546fc 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -8,6 +8,7 @@ import ( "fmt" "io" "reflect" + "strings" "time" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -22,8 +23,11 @@ type Entry interface { encoding.BinaryUnmarshaler } +// The storage Interface provides an abstraction on top of the bolt database to store and access +// data in a local in-memory database. +// All "table" or "bucket" in a database can be accessed by their name as a string where the dot +// "." is use as a level-separator to select or create nested buckets. type Interface interface { - Init(name string) error Store(name string, key []byte, entries []Entry) error Replace(name string, key []byte, entries []Entry) error Lookup(name string, key []byte, shape Entry) (interface{}, error) @@ -45,12 +49,27 @@ func New(name string) (Interface, error) { return store{db}, nil } -// Init initializes the given bolt bucket by creating (if not already exists) an empty bucket -func (itf store) Init(bucketName string) error { - return itf.db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) - return err - }) +func getBucket(tx *bolt.Tx, name string) (*bolt.Bucket, error) { + path := strings.Split(name, ".") + if len(path) < 1 { + return nil, errors.New(errors.Structural, "Invalid bucket name") + } + var next interface { + CreateBucketIfNotExists(b []byte) (*bolt.Bucket, error) + Bucket(b []byte) *bolt.Bucket + } + + var err error + next = tx + for _, bname := range path { + next = next.Bucket([]byte(bname)) + if next == nil { + if next, err = next.CreateBucketIfNotExists([]byte(bname)); err != nil { + return nil, errors.New(errors.Operational, err) + } + } + } + return next.(*bolt.Bucket), nil } // Store put a new set of entries in the given bolt database. It adds the entries to an existing set @@ -67,9 +86,9 @@ func (itf store) Store(bucketName string, key []byte, entries []Entry) error { } err := itf.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(errors.Operational, "storage unreachable") + bucket, err := getBucket(tx, bucketName) + if err != nil { + return err } w := readwriter.New(bucket.Get(key)) for _, m := range marshalled { @@ -97,9 +116,9 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} // First, lookup the raw entries var rawEntry []byte err := itf.db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(errors.Operational, "storage unreachable") + bucket, err := getBucket(tx, bucketName) + if err != nil { + return err } rawEntry = bucket.Get(key) if rawEntry == nil { @@ -135,9 +154,9 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} // Flush empties each entry of a bucket associated to a given device func (itf store) Flush(bucketName string, key []byte) error { return itf.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(errors.Operational, "storage unreachable") + bucket, err := getBucket(tx, bucketName) + if err != nil { + return err } if err := bucket.Delete(key); err != nil { return errors.New(errors.Operational, err) @@ -159,9 +178,9 @@ func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { } return itf.db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(errors.Operational, "storage unreachable") + bucket, err := getBucket(tx, bucketName) + if err != nil { + return err } if err := bucket.Delete(key); err != nil { return errors.New(errors.Operational, err) From 766d702309e15a763e9f531cb9c053ae4d952272 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:10:33 +0100 Subject: [PATCH 0766/2266] [refactor] Implements handler devStorage --- refactor/components/handler/devStorage.go | 39 ++++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/refactor/components/handler/devStorage.go b/refactor/components/handler/devStorage.go index 41fe29e47..f95b2907a 100644 --- a/refactor/components/handler/devStorage.go +++ b/refactor/components/handler/devStorage.go @@ -4,7 +4,10 @@ package handler import ( + "fmt" + . "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" "github.com/brocaar/lorawan" @@ -26,22 +29,50 @@ type appEntry struct { } type devStorage struct { - db dbutil.Interface + db dbutil.Interface + Name string } // NewDevStorage creates a new Device Storage for handler func NewDevStorage(name string) (DevStorage, error) { - return nil, nil + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + return devStorage{db: itf, Name: "entry"}, nil } // Lookup implements the handler.DevStorage interface func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { - return devEntry{}, nil + itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), &devEntry{}) + if err != nil { + return devEntry{}, errors.New(errors.Operational, err) + } + entries, ok := itf.([]devEntry) + if !ok || len(entries) != 1 { + return devEntry{}, errors.New(errors.Structural, "Invalid stored entry") + } + return entries[0], nil } // Store implements the handler.DevStorage interface func (s devStorage) Store(reg HRegistration) error { - return nil + appEUI := reg.AppEUI() + devEUI := reg.DevEUI() + data, err := reg.Recipient().MarshalBinary() + if err != nil { + return errors.New(errors.Structural, "Cannot marshal recipient") + } + + e := []dbutil.Entry{ + &devEntry{ + Recipient: data, + AppSKey: reg.AppSKey(), + NwkSKey: reg.NwkSKey(), + }, + } + return s.db.Store(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), e) } // Close implements the handler.DevStorage interface From b8678165cc3ac3be3fe5f6d3d1f65064e319d82f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:12:15 +0100 Subject: [PATCH 0767/2266] [refactor] Fix handler access to recipient --- refactor/components/handler/handler.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index 2d5d201ab..78a52eade 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -194,7 +194,13 @@ browseBundles: // And send it to the wild open // we don't expect a response from the adapter, end of the chain. - _, err = bestBundle.Adapter.Send(packet, bestBundle.Entry.Recipient) + recipient, err := bestBundle.Adapter.GetRecipient(bestBundle.Entry.Recipient) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + _, err = bestBundle.Adapter.Send(packet, recipient) if err != nil { go h.abortConsume(err, bundles) continue browseBundles From 0b07ed7ff5646766c3d76c4018c4e192739ba4dc Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:13:18 +0100 Subject: [PATCH 0768/2266] [refactor] Remove use of 'Init()' method in storages --- refactor/components/broker/storage.go | 7 +------ refactor/components/handler/pktStorage.go | 7 +------ refactor/components/router/storage.go | 7 +------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/refactor/components/broker/storage.go b/refactor/components/broker/storage.go index 617573bd1..0ef46fb7d 100644 --- a/refactor/components/broker/storage.go +++ b/refactor/components/broker/storage.go @@ -36,12 +36,7 @@ func NewStorage(name string) (Storage, error) { return nil, errors.New(errors.Operational, err) } - tableName := "handlers" - if err := itf.Init(tableName); err != nil { - return nil, errors.New(errors.Operational, err) - } - - return storage{db: itf, Name: tableName}, nil + return storage{db: itf, Name: "handlers"}, nil } // Lookup implements the broker.Storage interface diff --git a/refactor/components/handler/pktStorage.go b/refactor/components/handler/pktStorage.go index 78b0edf32..bf59deb0c 100644 --- a/refactor/components/handler/pktStorage.go +++ b/refactor/components/handler/pktStorage.go @@ -32,12 +32,7 @@ func NewPktStorage(name string) (PktStorage, error) { if err != nil { return nil, errors.New(errors.Operational, err) } - - tableName := "pkt_storage" - if err := itf.Init(tableName); err != nil { - return nil, errors.New(errors.Operational, err) - } - return pktStorage{db: itf, Name: tableName}, nil + return pktStorage{db: itf, Name: "pktStorage"}, nil } func keyFromEUIs(appEUI lorawan.EUI64, devEUI lorawan.EUI64) []byte { diff --git a/refactor/components/router/storage.go b/refactor/components/router/storage.go index 8c39ff4f1..135a89f52 100644 --- a/refactor/components/router/storage.go +++ b/refactor/components/router/storage.go @@ -38,12 +38,7 @@ func NewStorage(name string, delay time.Duration) (Storage, error) { return nil, errors.New(errors.Operational, err) } - tableName := "brokers" - if err := itf.Init(tableName); err != nil { - return nil, errors.New(errors.Operational, err) - } - - return storage{db: itf, ExpiryDelay: delay, Name: tableName}, nil + return storage{db: itf, ExpiryDelay: delay, Name: "broker"}, nil } // Lookup implements the router.Storage interface From 8069c06d36326a8b04c726018773940fcd1724c4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:16:32 +0100 Subject: [PATCH 0769/2266] [refactor] Add constructor to broker --- refactor/components/broker/broker.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/refactor/components/broker/broker.go b/refactor/components/broker/broker.go index 8b09038dd..093547968 100644 --- a/refactor/components/broker/broker.go +++ b/refactor/components/broker/broker.go @@ -17,9 +17,9 @@ type component struct { Controller NetworkController } -// New construct a new Broker component from ... -func New() (Component, error) { - return nil, nil +// New construct a new Broker component +func New(db Storage, ctx log.Interface) Component { + return component{Storage: db, ctx: ctx} } // Register implements the core.Component interface From d90b011289f9d491d58cb4bb2be72bdcc3d351c2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:19:28 +0100 Subject: [PATCH 0770/2266] [refactor] Add constructor to router --- refactor/components/router/router.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/refactor/components/router/router.go b/refactor/components/router/router.go index 36da65f1a..4aa76f2bc 100644 --- a/refactor/components/router/router.go +++ b/refactor/components/router/router.go @@ -15,6 +15,11 @@ type component struct { ctx log.Interface } +// New constructs a new router +func New(db Storage, ctx log.Interface) Component { + return component{Storage: db, ctx: ctx} +} + // Register implements the core.Component interface func (r component) Register(reg Registration, an AckNacker) (err error) { defer ensureAckNack(an, nil, &err) From f5237c1d43c93581ce0b312714318abcbc3bfa98 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:23:52 +0100 Subject: [PATCH 0771/2266] [refactor] Add constructor to handler --- refactor/components/handler/handler.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/refactor/components/handler/handler.go b/refactor/components/handler/handler.go index 78a52eade..465dae7d9 100644 --- a/refactor/components/handler/handler.go +++ b/refactor/components/handler/handler.go @@ -19,8 +19,8 @@ const max_duty_cycle = 90 // 90% // component implements the core.Component interface type component struct { ctx log.Interface - devices devStorage - packets pktStorage + devices DevStorage + packets PktStorage set chan<- bundle } @@ -32,9 +32,22 @@ type bundle struct { Packet HPacket } -// New construct a new Handler component from ... -func New() (Component, error) { - return nil, nil +// New construct a new Handler +func New(devDb DevStorage, pktDb PktStorage, ctx log.Interface) Component { + h := component{ + ctx: ctx, + devices: devDb, + packets: pktDb, + } + + set := make(chan bundle) + bundles := make(chan []bundle) + + h.set = set + go h.consumeBundles(bundles) + go h.consumeSet(bundles, set) + + return h } // Register implements the core.Component interface From fea0c4b889f7aaca268582dad811df290a6da92c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:24:02 +0100 Subject: [PATCH 0772/2266] [refactor] Add blank controller --- refactor/components/controller/controller.go | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 refactor/components/controller/controller.go diff --git a/refactor/components/controller/controller.go b/refactor/components/controller/controller.go new file mode 100644 index 000000000..252afb5b9 --- /dev/null +++ b/refactor/components/controller/controller.go @@ -0,0 +1,4 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package controller From 22697778d8182622e71c377b71cfc7e9a801b46d Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:32:56 +0100 Subject: [PATCH 0773/2266] [refactor] Replaced core with new refactored version --- core/adapters/http/adapter.go | 209 ------- core/adapters/http/adapter_test.go | 301 ---------- core/adapters/http/broadcast/broadcast.go | 172 ------ .../adapters/http/broadcast/broadcast_test.go | 265 --------- core/adapters/http/broadcast/doc.go | 12 - .../adapters/http/broadcast/void_acknacker.go | 20 - core/adapters/http/doc.go | 4 +- .../adapters/http/handlers/collect.go | 2 +- .../adapters/http/handlers/collect_test.go | 2 +- .../adapters/http/handlers/helpers_test.go | 4 +- .../adapters/http/handlers/pubsub.go | 4 +- .../http/handlers/pubsubRegistration.go | 4 +- .../adapters/http/handlers/pubsub_test.go | 4 +- .../adapters/http/handlers/statuspage.go | 2 +- {refactor => core}/adapters/http/http.go | 2 +- .../adapters/http/httpAckNacker.go | 2 +- .../adapters/http/httpRecipient.go | 0 .../adapters/http/httpRegistration.go | 2 +- {refactor => core}/adapters/http/http_test.go | 2 +- core/adapters/http/packet_acknacker.go | 53 -- core/adapters/http/packet_listener.go | 41 -- core/adapters/http/parser/doc.go | 6 - core/adapters/http/parser/packet.go | 45 -- core/adapters/http/parser/registration.go | 94 ---- core/adapters/http/pubsub/doc.go | 9 - core/adapters/http/pubsub/pubsub.go | 88 --- core/adapters/http/pubsub/pubsub_test.go | 154 ------ core/adapters/http/pubsub/reg_acknacker.go | 40 -- .../adapters/http/regAckNacker.go | 2 +- core/adapters/http/statuspage/statuspage.go | 118 ---- core/adapters/mosquitto/adapter.go | 124 ----- core/adapters/mosquitto/adapter_test.go | 257 --------- core/adapters/mosquitto/doc.go | 5 - .../adapters/mqtt/handlers/activation.go | 2 +- .../mqtt/handlers/activationRegistration.go | 2 +- .../adapters/mqtt/handlers/activation_test.go | 4 +- .../adapters/mqtt/handlers/helpers_test.go | 4 +- {refactor => core}/adapters/mqtt/mqtt.go | 2 +- .../adapters/mqtt/mqttAckNacker.go | 2 +- .../adapters/mqtt/mqttClient.go | 0 .../adapters/mqtt/mqttRecipient.go | 0 {refactor => core}/adapters/mqtt/mqtt_test.go | 2 +- core/adapters/semtech/adapter.go | 183 ------- core/adapters/semtech/adapter_test.go | 135 ----- core/adapters/semtech/build_utilities_test.go | 200 ------- core/adapters/semtech/doc.go | 12 - core/adapters/semtech/semtech_acknacker.go | 52 -- .../udp/handlers/build_utilities_test.go | 4 +- .../adapters/udp/handlers/conversions_test.go | 2 +- .../adapters/udp/handlers/semtech.go | 4 +- .../adapters/udp/handlers/semtech_test.go | 4 +- {refactor => core}/adapters/udp/udp.go | 2 +- .../adapters/udp/udpAckNacker.go | 2 +- .../adapters/udp/udpRegistration.go | 2 +- core/build_utilities_test.go | 70 --- core/check_utilities_test.go | 35 +- core/components/broker.go | 128 ----- .../components/broker/broker.go | 2 +- .../components/broker/storage.go | 2 +- core/components/broker_storage.go | 98 ---- core/components/broker_storage_test.go | 170 ------ core/components/broker_test.go | 221 -------- .../components/controller/controller.go | 0 core/components/doc.go | 61 --- core/components/entryReadWriter.go | 100 ---- core/components/handler.go | 245 --------- .../components/handler/devStorage.go | 2 +- .../components/handler/handler.go | 2 +- .../components/handler/pktStorage.go | 2 +- core/components/handler_storage.go | 167 ------ core/components/handler_storage_test.go | 231 -------- core/components/handler_test.go | 512 ------------------ core/components/internal_mechanisms_test.go | 128 ----- core/components/router.go | 88 --- .../components/router/router.go | 2 +- .../components/router/storage.go | 2 +- core/components/router_storage.go | 162 ------ core/components/router_storage_test.go | 221 -------- core/components/router_test.go | 164 ------ core/components/storage.go | 129 ----- core/core.go | 100 +--- core/doc.go | 34 -- core/errors/errors.go | 19 - core/metadata.go | 32 +- core/metadata_test.go | 5 +- core/packet.go | 201 ------- core/packet_test.go | 207 ------- {refactor => core}/packets.go | 2 +- {refactor => core}/packets_interfaces.go | 2 +- {refactor => core}/packets_test.go | 2 +- .../registrations_interfaces.go | 2 +- refactor/adapters/http/doc.go | 11 - refactor/adapters/http/utils.go | 12 - refactor/build_utilities_test.go | 72 --- refactor/check_utilities_test.go | 69 --- refactor/core.go | 34 -- refactor/metadata.go | 116 ---- refactor/metadata_test.go | 99 ---- 98 files changed, 107 insertions(+), 6525 deletions(-) delete mode 100644 core/adapters/http/adapter.go delete mode 100644 core/adapters/http/adapter_test.go delete mode 100644 core/adapters/http/broadcast/broadcast.go delete mode 100644 core/adapters/http/broadcast/broadcast_test.go delete mode 100644 core/adapters/http/broadcast/doc.go delete mode 100644 core/adapters/http/broadcast/void_acknacker.go rename {refactor => core}/adapters/http/handlers/collect.go (97%) rename {refactor => core}/adapters/http/handlers/collect_test.go (98%) rename {refactor => core}/adapters/http/handlers/helpers_test.go (97%) rename {refactor => core}/adapters/http/handlers/pubsub.go (97%) rename {refactor => core}/adapters/http/handlers/pubsubRegistration.go (89%) rename {refactor => core}/adapters/http/handlers/pubsub_test.go (97%) rename {refactor => core}/adapters/http/handlers/statuspage.go (97%) rename {refactor => core}/adapters/http/http.go (99%) rename {refactor => core}/adapters/http/httpAckNacker.go (96%) rename {refactor => core}/adapters/http/httpRecipient.go (100%) rename {refactor => core}/adapters/http/httpRegistration.go (91%) rename {refactor => core}/adapters/http/http_test.go (99%) delete mode 100644 core/adapters/http/packet_acknacker.go delete mode 100644 core/adapters/http/packet_listener.go delete mode 100644 core/adapters/http/parser/doc.go delete mode 100644 core/adapters/http/parser/packet.go delete mode 100644 core/adapters/http/parser/registration.go delete mode 100644 core/adapters/http/pubsub/doc.go delete mode 100644 core/adapters/http/pubsub/pubsub.go delete mode 100644 core/adapters/http/pubsub/pubsub_test.go delete mode 100644 core/adapters/http/pubsub/reg_acknacker.go rename {refactor => core}/adapters/http/regAckNacker.go (95%) delete mode 100644 core/adapters/http/statuspage/statuspage.go delete mode 100644 core/adapters/mosquitto/adapter.go delete mode 100644 core/adapters/mosquitto/adapter_test.go delete mode 100644 core/adapters/mosquitto/doc.go rename {refactor => core}/adapters/mqtt/handlers/activation.go (97%) rename {refactor => core}/adapters/mqtt/handlers/activationRegistration.go (95%) rename {refactor => core}/adapters/mqtt/handlers/activation_test.go (98%) rename {refactor => core}/adapters/mqtt/handlers/helpers_test.go (98%) rename {refactor => core}/adapters/mqtt/mqtt.go (99%) rename {refactor => core}/adapters/mqtt/mqttAckNacker.go (95%) rename {refactor => core}/adapters/mqtt/mqttClient.go (100%) rename {refactor => core}/adapters/mqtt/mqttRecipient.go (100%) rename {refactor => core}/adapters/mqtt/mqtt_test.go (99%) delete mode 100644 core/adapters/semtech/adapter.go delete mode 100644 core/adapters/semtech/adapter_test.go delete mode 100644 core/adapters/semtech/build_utilities_test.go delete mode 100644 core/adapters/semtech/doc.go delete mode 100644 core/adapters/semtech/semtech_acknacker.go rename {refactor => core}/adapters/udp/handlers/build_utilities_test.go (98%) rename {refactor => core}/adapters/udp/handlers/conversions_test.go (98%) rename {refactor => core}/adapters/udp/handlers/semtech.go (97%) rename {refactor => core}/adapters/udp/handlers/semtech_test.go (97%) rename {refactor => core}/adapters/udp/udp.go (99%) rename {refactor => core}/adapters/udp/udpAckNacker.go (94%) rename {refactor => core}/adapters/udp/udpRegistration.go (91%) delete mode 100644 core/components/broker.go rename {refactor => core}/components/broker/broker.go (99%) rename {refactor => core}/components/broker/storage.go (98%) delete mode 100644 core/components/broker_storage.go delete mode 100644 core/components/broker_storage_test.go delete mode 100644 core/components/broker_test.go rename {refactor => core}/components/controller/controller.go (100%) delete mode 100644 core/components/doc.go delete mode 100644 core/components/entryReadWriter.go delete mode 100644 core/components/handler.go rename {refactor => core}/components/handler/devStorage.go (98%) rename {refactor => core}/components/handler/handler.go (99%) rename {refactor => core}/components/handler/pktStorage.go (98%) delete mode 100644 core/components/handler_storage.go delete mode 100644 core/components/handler_storage_test.go delete mode 100644 core/components/handler_test.go delete mode 100644 core/components/internal_mechanisms_test.go delete mode 100644 core/components/router.go rename {refactor => core}/components/router/router.go (98%) rename {refactor => core}/components/router/storage.go (98%) delete mode 100644 core/components/router_storage.go delete mode 100644 core/components/router_storage_test.go delete mode 100644 core/components/router_test.go delete mode 100644 core/components/storage.go delete mode 100644 core/doc.go delete mode 100644 core/errors/errors.go delete mode 100644 core/packet.go delete mode 100644 core/packet_test.go rename {refactor => core}/packets.go (99%) rename {refactor => core}/packets_interfaces.go (98%) rename {refactor => core}/packets_test.go (90%) rename {refactor => core}/registrations_interfaces.go (96%) delete mode 100644 refactor/adapters/http/doc.go delete mode 100644 refactor/adapters/http/utils.go delete mode 100644 refactor/build_utilities_test.go delete mode 100644 refactor/check_utilities_test.go delete mode 100644 refactor/core.go delete mode 100644 refactor/metadata.go delete mode 100644 refactor/metadata_test.go diff --git a/core/adapters/http/adapter.go b/core/adapters/http/adapter.go deleted file mode 100644 index f69c7204a..000000000 --- a/core/adapters/http/adapter.go +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" -) - -// Adapter type materializes an http adapter which implements the basic http protocol -type Adapter struct { - parser.PacketParser // The adapter's parser contract - http.Client // Adapter is also an http client - serveMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints - packets chan pktReq // Channel used to "transforms" incoming request to something we can handle concurrently - ctx log.Interface // Just a logger, no one really cares about him. -} - -// Message sent through the packets channel when an incoming request arrives -type pktReq struct { - core.Packet // The actual packet that has been parsed - response chan pktRes // A response channel waiting for an success or reject confirmation -} - -// Message sent through the response channel of a pktReq -type pktRes struct { - statusCode int // The http status code to set as an answer - content []byte // The response content. -} - -// NewAdapter constructs and allocates a new http adapter -func NewAdapter(port uint, parser parser.PacketParser, ctx log.Interface) (*Adapter, error) { - a := Adapter{ - PacketParser: parser, - serveMux: http.NewServeMux(), - packets: make(chan pktReq), - ctx: ctx, - Client: http.Client{ - Timeout: 6 * time.Second, - }, - } - - a.RegisterEndpoint("/healthz", func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - }) - - a.RegisterEndpoint("/packets", a.handlePostPacket) - go a.listenRequests(port) - - return &a, nil -} - -// Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - stats.MarkMeter("http_adapter.send") - stats.UpdateHistogram("http_adapter.send.recipients", int64(len(r))) - - // Generate payload from core packet - m, err := json.Marshal(p.Metadata) - if err != nil { - a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - pl, err := p.Payload.MarshalBinary() - if err != nil { - a.ctx.WithError(err).Warn("Invalid Packet") - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) - - devAddr, _ := p.DevAddr() - ctx := a.ctx.WithField("devAddr", devAddr) - ctx.Debug("Sending Packet") - - // Prepare ground for parrallel http request - nb := len(r) - cherr := make(chan error, nb) - chresp := make(chan core.Packet, nb) - wg := sync.WaitGroup{} - wg.Add(nb) - - // Run each request - for _, recipient := range r { - go func(recipient core.Recipient) { - defer wg.Done() - - ctx := ctx.WithField("recipient", recipient.Address) - ctx.Debug("POST Request") - - buf := new(bytes.Buffer) - buf.Write([]byte(payload)) - - // Send request - resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Address.(string)), "application/json", buf) - if err != nil { - cherr <- err - return - } - - defer func() { - // This is needed because the default HTTP client's Transport does not - // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections unless the Body - // is read to completion and is closed. - io.Copy(ioutil.Discard, resp.Body) - resp.Body.Close() - }() - - // Check response code - if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusCreated { - ctx.WithField("response", resp.StatusCode).Warn("Unexpected response") - cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) - return - } - - // Process response body - raw, err := ioutil.ReadAll(resp.Body) - if err != nil && err != io.EOF { - cherr <- err - return - } - - // Process packet - var packet core.Packet - if err := json.Unmarshal(raw, &packet); err != nil { - cherr <- err - return - } - - chresp <- packet - }(recipient) - } - - // Wait for each request to be done, and return - stats.IncCounter("http_adapter.send.waiting") - wg.Wait() - stats.DecCounter("http_adapter.send.waiting") - - // Collect errors - var errs []error - for i := 0; i < len(cherr); i += 1 { - err := <-cherr - ctx.WithError(err).Error("POST Failed") - errs = append(errs, err) - } - - // Check responses - if len(chresp) > 1 { - ctx.WithField("response_count", len(chresp)).Error("Received too many positive answers") - return core.Packet{}, errors.New(ErrWrongBehavior, "Received too many positive answers") - } - - // Get packet - select { - case packet := <-chresp: - return packet, nil - default: - if errs != nil { - return core.Packet{}, errors.New(ErrFailedOperation, fmt.Sprintf("%+v", errs)) - } - ctx.Error("No response packet available") - return core.Packet{}, errors.New(ErrWrongBehavior, "No response packet available") - } -} - -// RegisterEndpoint can be used by an external agent to register a handler to the adapter servemux -func (a *Adapter) RegisterEndpoint(url string, handler func(w http.ResponseWriter, req *http.Request)) { - a.ctx.WithField("url", url).Info("Register new endpoint") - a.serveMux.HandleFunc(url, handler) -} - -// Next implements the core.Adapter interface -func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { - pktReq := <-a.packets - return pktReq.Packet, packetAckNacker{response: pktReq.response}, nil -} - -// NextRegistration implements the core.Adapter interface. Not implemented for this adapter. -// -// See broadcast and pubsub adapters for mechanisms to handle registrations. -func (a *Adapter) NextRegistration() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported for http adapter") -} - -// listenRequests handles incoming registration request sent through http to the adapter -func (a *Adapter) listenRequests(port uint) { - server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), - Handler: a.serveMux, - } - a.ctx.WithField("port", port).Info("Starting Server") - err := server.ListenAndServe() - a.ctx.WithError(err).Warn("HTTP connection lost") -} diff --git a/core/adapters/http/adapter_test.go b/core/adapters/http/adapter_test.go deleted file mode 100644 index d3b747acb..000000000 --- a/core/adapters/http/adapter_test.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -// Send(p core.Packet, r ...core.Recipient) error -func TestSend(t *testing.T) { - tests := []struct { - Packet core.Packet - WantPayload string - WantError *string - }{ - { - genCorePacket(), - genJSONPayload(genCorePacket()), - nil, - }, - { - core.Packet{}, - "", - pointer.String(ErrInvalidStructure), - }, - } - - s := genMockServer(3100) - - // Logging - ctx := GetLogger(t, "Adapter") - - adapter, err := NewAdapter(3101, parser.JSON{}, ctx) - if err != nil { - panic(err) - } - - for _, test := range tests { - Desc(t, "Sending packet: %v", test.Packet) - <-time.After(time.Millisecond * 100) - _, err := adapter.Send(test.Packet, s.Recipient) - checkErrors(t, test.WantError, err) - checkSend(t, test.WantPayload, s) - } -} - -// Next() (core.Packet, an core.AckNacker, error) -func TestNext(t *testing.T) { - tests := []struct { - Payload string - IsNotFound bool - WantPacket core.Packet - WantStatus int - WantError *string - }{ - { - Payload: genJSONPayload(genCorePacket()), - IsNotFound: false, - WantPacket: genCorePacket(), - WantStatus: http.StatusOK, - WantError: nil, - }, - { - Payload: genJSONPayload(genCorePacket()), - IsNotFound: true, - WantPacket: genCorePacket(), - WantStatus: http.StatusNotFound, - WantError: nil, - }, - { - Payload: "Patate", - IsNotFound: false, - WantPacket: core.Packet{}, - WantStatus: http.StatusBadRequest, - WantError: nil, - }, - } - // Build - ctx := GetLogger(t, "Adapter") - adapter, err := NewAdapter(3102, parser.JSON{}, ctx) - if err != nil { - panic(err) - } - - c := client{adapter: "0.0.0.0:3102"} - - for _, test := range tests { - // Describe - Desc(t, "Send payload to the adapter %s. Will send ack ? %v", test.Payload, !test.IsNotFound) - <-time.After(time.Millisecond * 100) - - // Operate - gotPacket := make(chan core.Packet) - gotError := make(chan error) - go func() { - packet, an, err := adapter.Next() - if err == nil { - if test.IsNotFound { - an.Nack() - } else { - an.Ack(nil) - } - } - gotError <- err - gotPacket <- packet - }() - - resp := c.send(test.Payload) - - // Check - select { - case err := <-gotError: - checkErrors(t, test.WantError, err) - case <-time.After(time.Millisecond * 250): - checkErrors(t, test.WantError, nil) - } - - checkStatus(t, test.WantStatus, resp.StatusCode) - - select { - case packet := <-gotPacket: - checkPackets(t, test.WantPacket, packet) - case <-time.After(time.Millisecond * 250): - checkPackets(t, test.WantPacket, core.Packet{}) - } - - } -} - -// Check utilities -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - - Ko(t, "Expected error to be %s but got %v", want, got) -} - -func checkSend(t *testing.T, want string, s MockServer) { - select { - case got := <-s.Payloads: - if want != got { - Ko(t, "Received payload does not match expectations.\nWant: %s\nGot: %s", want, got) - return - } - case <-time.After(time.Millisecond * 100): - if want != "" { - Ko(t, "Expected payload %s to be sent but got nothing", want) - return - } - } - Ok(t, "Check send result") -} - -func checkPackets(t *testing.T, want core.Packet, got core.Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") - return - } - Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", want, got) -} - -func checkStatus(t *testing.T, want int, got int) { - if want == got { - Ok(t, "Check status") - return - } - Ko(t, "Expected status to be %d but got %d", want, got) -} - -// Build utilities -type MockServer struct { - Recipient core.Recipient - Payloads chan string -} - -func genMockServer(port uint) MockServer { - addr := fmt.Sprintf("0.0.0.0:%d", port) - payloads := make(chan string) - - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - body := make([]byte, 256) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - panic(err) - } - w.Write(body[:n]) // NOTE TEMPORARY, the response is supposed to be different - go func() { payloads <- string(body[:n]) }() - }) - - go func() { - server := http.Server{ - Handler: serveMux, - Addr: addr, - } - server.ListenAndServe() - }() - - <-time.After(time.Millisecond * 50) - - return MockServer{ - Recipient: core.Recipient{ - Address: addr, - Id: "Mock server", - }, - Payloads: payloads, - } -} - -// Generate a Physical payload representing an uplink message -func genPHYPayload(msg string, devAddr [4]byte) lorawan.PHYPayload { - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr(devAddr), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(msg)}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(true) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} - -func genCorePacket() core.Packet { - return core.Packet{ - Payload: genPHYPayload("myData", [4]byte{0x1, 0x2, 0x3, 0x4}), - Metadata: core.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, - } -} - -func genJSONPayload(p core.Packet) string { - raw, err := json.Marshal(p) - if err != nil { - panic(err) - } - return string(raw) -} - -type client struct { - http.Client - adapter string -} - -// Operate utilities -// send is a convinient helper to send HTTP from a handler to the adapter -func (c *client) send(payload string) http.Response { - buf := new(bytes.Buffer) - if _, err := buf.WriteString(payload); err != nil { - panic(err) - } - request, err := http.NewRequest("POST", fmt.Sprintf("http://%s/packets", c.adapter), buf) - if err != nil { - panic(err) - } - request.Header.Set("Content-Type", "application/json") - resp, err := c.Do(request) - if err != nil { - panic(err) - } - return *resp -} diff --git a/core/adapters/http/broadcast/broadcast.go b/core/adapters/http/broadcast/broadcast.go deleted file mode 100644 index a44c1f3ab..000000000 --- a/core/adapters/http/broadcast/broadcast.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broadcast - -import ( - "bytes" - "encoding/base64" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "net/http" - "sync" - - "github.com/TheThingsNetwork/ttn/core" - httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" -) - -// Broadcast materializes an extended basic http adapter which will also generate registration based -// on request responses. Whenever the adapter broadcast a request, it will trigger a registration -// demand for each new valid recipient. -type Adapter struct { - *httpadapter.Adapter // Composed of an original http adapter - ctx log.Interface // Just a logger - recipients []core.Recipient // A predefined list of recipients towards which broadcast messages - registrations chan core.Registration // Communication channel responsible for registration management -} - -// NewAdapter promotes an existing basic adapter to a broadcast adapter using a list a of known recipient -func NewAdapter(adapter *httpadapter.Adapter, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { - if len(recipients) == 0 { - return nil, errors.New(ErrInvalidStructure, "Must give at least one recipient") - } - - return &Adapter{ - Adapter: adapter, - ctx: ctx, - recipients: recipients, - registrations: make(chan core.Registration, len(recipients)), - }, nil -} - -// Send implements the Adapter interfaces -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - if len(r) == 0 { - a.ctx.Debug("No recipient provided. The packet will be broadcast") - return a.broadcast(p) - } - return a.Adapter.Send(p, r...) -} - -// broadcast is merely a send where recipients are the predefined list used at instantiation time. -// Beside, a registration request will be triggered if one of the recipient reponses positively. -func (a *Adapter) broadcast(p core.Packet) (core.Packet, error) { - stats.MarkMeter("http_adapter.broadcast") - stats.UpdateHistogram("http_adapter.broadcast.recipients", int64(len(a.recipients))) - - // Generate payload from core packet - m, err := json.Marshal(p.Metadata) - if err != nil { - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - pl, err := p.Payload.MarshalBinary() - if err != nil { - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - payload := fmt.Sprintf(`{"payload":"%s","metadata":%s}`, base64.StdEncoding.EncodeToString(pl), m) - - devAddr, err := p.DevAddr() - if err != nil { - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - - // Prepare ground for parrallel http request - nb := len(a.recipients) - cherr := make(chan error, nb) - chresp := make(chan core.Packet, nb) - register := make(chan core.Recipient, nb) - wg := sync.WaitGroup{} - wg.Add(nb) - - // Run each request - for _, recipient := range a.recipients { - go func(recipient core.Recipient) { - defer wg.Done() - - ctx := a.ctx.WithField("recipient", recipient.Address) - - ctx.Debug("POST Request") - - buf := new(bytes.Buffer) - buf.Write([]byte(payload)) - resp, err := a.Post(fmt.Sprintf("http://%s/packets", recipient.Address.(string)), "application/json", buf) - if err != nil { - cherr <- err - return - } - - defer func() { - // This is needed because the default HTTP client's Transport does not - // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections unless the Body - // is read to completion and is closed. - io.Copy(ioutil.Discard, resp.Body) - resp.Body.Close() - }() - - switch resp.StatusCode { - case http.StatusOK: - ctx.WithField("devAddr", devAddr).Debug("Recipient registered for packet") - - raw, err := ioutil.ReadAll(resp.Body) - if err != nil && err != io.EOF { - cherr <- err - return - } - var packet core.Packet - if err := json.Unmarshal(raw, &packet); err != nil { - cherr <- err - return - } - - register <- recipient - chresp <- packet - case http.StatusNotFound: - ctx.WithField("devAddr", devAddr).Debug("Recipient not interested in packet") - default: - // Non-blocking, buffered - cherr <- fmt.Errorf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode) - } - }(recipient) - } - - // Wait for each request to be done, and return - stats.IncCounter("http_adapter.broadcast.waiting") - wg.Wait() - stats.DecCounter("http_adapter.broadcast.waiting") - close(cherr) - close(register) - var errs []error - for err := range cherr { - errs = append(errs, err) - } - if errs != nil { - return core.Packet{}, errors.New(ErrFailedOperation, fmt.Sprintf("%+v", errs)) - } - - if len(chresp) > 1 { // NOTE We consider several positive responses as an error - return core.Packet{}, errors.New(ErrWrongBehavior, "Received too many positive answers") - } - - for recipient := range register { - a.registrations <- core.Registration{DevAddr: devAddr, Recipient: recipient} - } - - select { - case packet := <-chresp: - return packet, nil - default: - return core.Packet{}, errors.New(ErrWrongBehavior, "No response packet available") - } -} - -// NextRegistration implements the Adapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - registration := <-a.registrations - return registration, voidAckNacker{}, nil -} diff --git a/core/adapters/http/broadcast/broadcast_test.go b/core/adapters/http/broadcast/broadcast_test.go deleted file mode 100644 index 247f0cb4d..000000000 --- a/core/adapters/http/broadcast/broadcast_test.go +++ /dev/null @@ -1,265 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broadcast - -import ( - "encoding/json" - "io" - "net/http" - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestSend(t *testing.T) { - packet, devAddr, payload := genSample() - recipients := []core.Recipient{ - core.Recipient{Address: "0.0.0.0:3010", Id: "AlwaysReject"}, - core.Recipient{Address: "0.0.0.0:3011", Id: "AlwaysAccept"}, - core.Recipient{Address: "0.0.0.0:3012", Id: "AlwaysReject"}, - core.Recipient{Address: "0.0.0.0:3013", Id: "AlwaysReject"}, - } - registrations := []core.Registration{ - core.Registration{DevAddr: devAddr, Recipient: recipients[0]}, - core.Registration{DevAddr: devAddr, Recipient: recipients[1]}, - core.Registration{DevAddr: devAddr, Recipient: recipients[2]}, - core.Registration{DevAddr: devAddr, Recipient: recipients[3]}, - } - - tests := []struct { - Recipients []core.Recipient - Packet core.Packet - WantRegistrations []core.Registration - WantPayload string - WantError *string - }{ - { // Send to recipient a valid packet - Recipients: recipients[1:2], // TODO test with a rejection. Need better error handling - Packet: packet, - WantRegistrations: []core.Registration{}, - WantPayload: payload, - WantError: nil, - }, - { // Broadcast a valid packet - Recipients: []core.Recipient{}, - Packet: packet, - WantRegistrations: registrations[1:2], - WantPayload: payload, - WantError: nil, - }, - { // Send to two recipients an invalid packet - Recipients: recipients[:2], - Packet: core.Packet{}, - WantRegistrations: []core.Registration{}, - WantPayload: "", - WantError: pointer.String(ErrInvalidStructure), - }, - { // Broadcast an invalid packet - Recipients: []core.Recipient{}, - Packet: core.Packet{}, - WantRegistrations: []core.Registration{}, - WantPayload: "", - WantError: pointer.String(ErrInvalidStructure), - }, - } - - // Logging - ctx := GetLogger(t, "Adapter") - - // Build - a, err := httpadapter.NewAdapter(3015, parser.JSON{}, ctx) - if err != nil { - panic(err) - } - adapter, err := NewAdapter(a, recipients, ctx) - if err != nil { - panic(err) - } - var servers []chan string - for _, r := range recipients { - servers = append(servers, genMockServer(r)) - } - - for _, test := range tests { - // Describe - Desc(t, "Sending packet %v to %v", test.Packet, test.Recipients) - <-time.After(time.Millisecond * 100) - - // Operate - _, err := adapter.Send(test.Packet, test.Recipients...) - registrations := getRegistrations(adapter, test.WantRegistrations) - payloads := getPayloads(servers) - - // Check - checkErrors(t, test.WantError, err) - checkPayloads(t, test.WantPayload, payloads) - checkRegistrations(t, test.WantRegistrations, registrations) - } -} - -// Operate utilities -func getPayloads(chpayloads []chan string) []string { - var got []string - for _, ch := range chpayloads { - select { - case payload := <-ch: - got = append(got, payload) - case <-time.After(50 * time.Millisecond): - } - } - return got -} - -func getRegistrations(adapter *Adapter, want []core.Registration) []core.Registration { - var got []core.Registration - for range want { - ch := make(chan core.Registration) - go func() { - r, an, err := adapter.NextRegistration() - if err != nil { - return - } - an.Ack(nil) - ch <- r - }() - select { - case r := <-ch: - got = append(got, r) - case <-time.After(50 * time.Millisecond): - } - } - return got -} - -// Build utilities - -func genMockServer(recipient core.Recipient) chan string { - chresp := make(chan string) - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - if req.Header.Get("Content-Type") != "application/json" { - w.WriteHeader(http.StatusBadRequest) - w.Write(nil) - return - } - - buf := make([]byte, req.ContentLength) - n, err := req.Body.Read(buf) - if err != nil && err != io.EOF { - w.WriteHeader(http.StatusBadRequest) - w.Write(nil) - return - } - - switch recipient.Id { - case "AlwaysReject": - w.WriteHeader(http.StatusNotFound) - w.Write(nil) - case "AlwaysAccept": - w.WriteHeader(http.StatusOK) - w.Write(buf[:n]) // TODO, should respond another packet, not the same - } - go func() { chresp <- string(buf[:n]) }() - }) - - server := http.Server{ - Addr: recipient.Address.(string), - Handler: serveMux, - } - go server.ListenAndServe() - return chresp -} - -// Generate a Physical payload representing an uplink message -func genSample() (core.Packet, lorawan.DevAddr, string) { - - // 1. Generate a PHYPayload - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - devAddr := lorawan.DevAddr([4]byte{0x1, 0x14, 0x2, 0x42}) - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: devAddr, - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(true) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - // 2. Generate a JSON payload received by the server - packet := core.Packet{ - Payload: payload, - Metadata: core.Metadata{Rssi: pointer.Int(-20), Modu: pointer.String("LORA")}, - } - jsonPayload, err := json.Marshal(packet) - if err != nil { - panic(err) - } - - // 3. Return valuable info for the test - return packet, devAddr, string(jsonPayload) -} - -// Check utilities -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - - Ko(t, "Expected error to be %s but got %v", want, got) -} - -func checkRegistrations(t *testing.T, want []core.Registration, got []core.Registration) { -outer: - for _, rw := range want { - for _, rg := range got { - if reflect.DeepEqual(rg, rw) { - continue outer - } - } - Ko(t, "Registrations don't match expectation.\nWant: %v\nGot: %v", want, got) - return - } - Ok(t, "Check registrations") -} - -func checkPayloads(t *testing.T, want string, got []string) { - for _, payload := range got { - if want != payload { - Ko(t, "Paylaod don't match expectation.\nWant: %s\nGot: %s", want, payload) - return - } - } - Ok(t, "Check payloads") -} diff --git a/core/adapters/http/broadcast/doc.go b/core/adapters/http/broadcast/doc.go deleted file mode 100644 index 957815a11..000000000 --- a/core/adapters/http/broadcast/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package broadcast extends the basic http adapter to allow registrations and broadcasting of a -// packet. -// -// Registrations are implicit. They are generated during broadcast depending on the response of all -// recipients. Technically, only one recipient is expected to give a positive answer (if we put -// aside malicious one). For that positive response would be generated a given registration. -// -// Recipients to whom broadcast packets are defined once during the adapter's instantiation. -package broadcast diff --git a/core/adapters/http/broadcast/void_acknacker.go b/core/adapters/http/broadcast/void_acknacker.go deleted file mode 100644 index a20a4a97b..000000000 --- a/core/adapters/http/broadcast/void_acknacker.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broadcast - -import ( - "github.com/TheThingsNetwork/ttn/core" -) - -type voidAckNacker struct{} - -// Ack implements the core.AckNacker interface -func (v voidAckNacker) Ack(p *core.Packet) error { - return nil -} - -// Nack implements the core.AckNacker interface -func (v voidAckNacker) Nack() error { - return nil -} diff --git a/core/adapters/http/doc.go b/core/adapters/http/doc.go index b84172d25..aed3c9d11 100644 --- a/core/adapters/http/doc.go +++ b/core/adapters/http/doc.go @@ -6,6 +6,6 @@ // The different protocols and mechanisms used are defined in the following document: // https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/protocols.md // -// The basic http adapter module can be used as a brick to build something bigger. By default, it -// does not hold registrations but only sending and reception of packets. +// The basic http adapter module can be used as a brick to build something bigger by registering +// specific endpoints. package http diff --git a/refactor/adapters/http/handlers/collect.go b/core/adapters/http/handlers/collect.go similarity index 97% rename from refactor/adapters/http/handlers/collect.go rename to core/adapters/http/handlers/collect.go index 2b3734980..b122b4b48 100644 --- a/refactor/adapters/http/handlers/collect.go +++ b/core/adapters/http/handlers/collect.go @@ -7,7 +7,7 @@ import ( "io" "net/http" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" ) diff --git a/refactor/adapters/http/handlers/collect_test.go b/core/adapters/http/handlers/collect_test.go similarity index 98% rename from refactor/adapters/http/handlers/collect_test.go rename to core/adapters/http/handlers/collect_test.go index da0221284..fd692f808 100644 --- a/refactor/adapters/http/handlers/collect_test.go +++ b/core/adapters/http/handlers/collect_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" . "github.com/TheThingsNetwork/ttn/utils/testing" diff --git a/refactor/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go similarity index 97% rename from refactor/adapters/http/handlers/helpers_test.go rename to core/adapters/http/handlers/helpers_test.go index ae073768c..2e01989e1 100644 --- a/refactor/adapters/http/handlers/helpers_test.go +++ b/core/adapters/http/handlers/helpers_test.go @@ -13,8 +13,8 @@ import ( "testing" "time" - core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" diff --git a/refactor/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go similarity index 97% rename from refactor/adapters/http/handlers/pubsub.go rename to core/adapters/http/handlers/pubsub.go index 18d8aa52f..194ba39ef 100644 --- a/refactor/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -11,8 +11,8 @@ import ( "regexp" "strings" - core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) diff --git a/refactor/adapters/http/handlers/pubsubRegistration.go b/core/adapters/http/handlers/pubsubRegistration.go similarity index 89% rename from refactor/adapters/http/handlers/pubsubRegistration.go rename to core/adapters/http/handlers/pubsubRegistration.go index 93083362b..78f82de84 100644 --- a/refactor/adapters/http/handlers/pubsubRegistration.go +++ b/core/adapters/http/handlers/pubsubRegistration.go @@ -4,8 +4,8 @@ package handlers import ( - core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/brocaar/lorawan" ) diff --git a/refactor/adapters/http/handlers/pubsub_test.go b/core/adapters/http/handlers/pubsub_test.go similarity index 97% rename from refactor/adapters/http/handlers/pubsub_test.go rename to core/adapters/http/handlers/pubsub_test.go index ed59f7c9d..dedd48aa8 100644 --- a/refactor/adapters/http/handlers/pubsub_test.go +++ b/core/adapters/http/handlers/pubsub_test.go @@ -9,8 +9,8 @@ import ( "testing" "time" - core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" . "github.com/TheThingsNetwork/ttn/utils/testing" diff --git a/refactor/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go similarity index 97% rename from refactor/adapters/http/handlers/statuspage.go rename to core/adapters/http/handlers/statuspage.go index 307aad9e1..b5486518e 100644 --- a/refactor/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -9,7 +9,7 @@ import ( "net" "net/http" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/http" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/rcrowley/go-metrics" ) diff --git a/refactor/adapters/http/http.go b/core/adapters/http/http.go similarity index 99% rename from refactor/adapters/http/http.go rename to core/adapters/http/http.go index 733d3b562..13a492fc9 100644 --- a/refactor/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -11,7 +11,7 @@ import ( "net/http" "sync" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" diff --git a/refactor/adapters/http/httpAckNacker.go b/core/adapters/http/httpAckNacker.go similarity index 96% rename from refactor/adapters/http/httpAckNacker.go rename to core/adapters/http/httpAckNacker.go index 0cda08a89..9700cab36 100644 --- a/refactor/adapters/http/httpAckNacker.go +++ b/core/adapters/http/httpAckNacker.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" ) diff --git a/refactor/adapters/http/httpRecipient.go b/core/adapters/http/httpRecipient.go similarity index 100% rename from refactor/adapters/http/httpRecipient.go rename to core/adapters/http/httpRecipient.go diff --git a/refactor/adapters/http/httpRegistration.go b/core/adapters/http/httpRegistration.go similarity index 91% rename from refactor/adapters/http/httpRegistration.go rename to core/adapters/http/httpRegistration.go index e62c31bf3..b2ad51533 100644 --- a/refactor/adapters/http/httpRegistration.go +++ b/core/adapters/http/httpRegistration.go @@ -4,7 +4,7 @@ package http import ( - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/brocaar/lorawan" ) diff --git a/refactor/adapters/http/http_test.go b/core/adapters/http/http_test.go similarity index 99% rename from refactor/adapters/http/http_test.go rename to core/adapters/http/http_test.go index 4830b789c..80125295c 100644 --- a/refactor/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -10,7 +10,7 @@ import ( "testing" "time" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/core/adapters/http/packet_acknacker.go b/core/adapters/http/packet_acknacker.go deleted file mode 100644 index f3a7c97a6..000000000 --- a/core/adapters/http/packet_acknacker.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "encoding/json" - "net/http" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// packetAckNacker implements the AckNacker interface -type packetAckNacker struct { - response chan pktRes // A channel dedicated to send back a response -} - -// Ack implements the core.AckNacker interface -func (an packetAckNacker) Ack(p *core.Packet) error { - defer close(an.response) - if p == nil { - an.response <- pktRes{statusCode: http.StatusOK} - return nil - } - - raw, err := json.Marshal(*p) - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - - select { - case an.response <- pktRes{statusCode: http.StatusOK, content: raw}: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") - } -} - -// Nack implements the core.AckNacker interface -func (an packetAckNacker) Nack() error { - select { - case an.response <- pktRes{ - statusCode: http.StatusNotFound, - content: []byte(`{"message":"Not in charge of the associated device"}`), - }: - case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") - } - return nil -} diff --git a/core/adapters/http/packet_listener.go b/core/adapters/http/packet_listener.go deleted file mode 100644 index 1a6b04890..000000000 --- a/core/adapters/http/packet_listener.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" -) - -// handlePostPacket defines an http handler over the adapter to handle POST request on /packets -func (a *Adapter) handlePostPacket(w http.ResponseWriter, req *http.Request) { - ctx := a.ctx.WithField("sender", req.RemoteAddr) - - ctx.Debug("Receiving new packet") - // Check the http method - if req.Method != "POST" { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [POST] to transfer a packet")) - return - } - - // Parse body and query params - packet, err := a.Parse(req) - if err != nil { - ctx.WithError(err).Warn("Received invalid body in request") - BadRequest(w, err.Error()) - return - } - - // Send the packet and wait for ack / nack - response := make(chan pktRes) - a.packets <- pktReq{Packet: packet, response: response} - r, ok := <-response - if !ok { - ctx.Error("Core server not responding") - BadRequest(w, "Core server not responding") - return - } - w.WriteHeader(r.statusCode) - w.Write(r.content) -} diff --git a/core/adapters/http/parser/doc.go b/core/adapters/http/parser/doc.go deleted file mode 100644 index 96d0bd085..000000000 --- a/core/adapters/http/parser/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package parser defines several Packet and Registration parsers that raise the abstraction level -// for adapters. -package parser diff --git a/core/adapters/http/parser/packet.go b/core/adapters/http/parser/packet.go deleted file mode 100644 index fab9df804..000000000 --- a/core/adapters/http/parser/packet.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package parser - -import ( - "encoding/json" - "io" - "net/http" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// Parser gives a flexible way of parsing a request into a packet. -type PacketParser interface { - // Parse transforms a given http request into a Packet. The error handling is not under its - // responsibility. The parser can expect any query param, http method or header it needs. - Parse(req *http.Request) (core.Packet, error) -} - -// JSONPacket defines a parser for packet sent as JSON payload -type JSON struct{} - -// Parse implements the PacketParser interface -func (p JSON) Parse(req *http.Request) (core.Packet, error) { - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - return core.Packet{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") - } - - // Check configuration in body - body := make([]byte, req.ContentLength) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - packet := new(core.Packet) - if err := json.Unmarshal(body[:n], packet); err != nil { - return core.Packet{}, errors.New(ErrInvalidStructure, err) - } - - return *packet, nil -} diff --git a/core/adapters/http/parser/registration.go b/core/adapters/http/parser/registration.go deleted file mode 100644 index 2c2a3e0b5..000000000 --- a/core/adapters/http/parser/registration.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package parser - -import ( - "encoding/hex" - "encoding/json" - "io" - "net/http" - "regexp" - "strings" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// Parser gives a flexible way of parsing a request into a registration. -type RegistrationParser interface { - // Parse transforms a given http request into a Registration. The error handling is not under its - // responsibility. The parser can expect any query param, http method or header it needs. - Parse(req *http.Request) (core.Registration, error) -} - -// PubSub materializes a parser for pubsub requests. -// -// It expects requests to be of the following shapes: -// -// Content-Type: application/json -// Method: PUT -// Path: end-devices/ where is 8 bytes hex encoded -// Params: app_id (string), app_url (string), nwks_key (16 bytes hex encoded) -type PubSub struct{} - -// Parse implements the RegistrationParser interface -func (p PubSub) Parse(req *http.Request) (core.Registration, error) { - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - return core.Registration{}, errors.New(ErrInvalidStructure, "Received invalid content-type in request") - } - - // Check the query parameter - reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{8})$") - query := reg.FindStringSubmatch(req.RequestURI) - if len(query) < 2 { - return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect end-device address format") - } - devAddr, err := hex.DecodeString(query[1]) - if err != nil { - return core.Registration{}, errors.New(ErrInvalidStructure, err) - } - - // Check configuration in body - body := make([]byte, req.ContentLength) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - return core.Registration{}, errors.New(ErrInvalidStructure, err) - } - params := &struct { - Id string `json:"app_id"` - Url string `json:"app_url"` - NwkSKey string `json:"nwks_key"` - }{} - if err := json.Unmarshal(body[:n], params); err != nil { - return core.Registration{}, errors.New(ErrInvalidStructure, err) - } - - nwkSKey, err := hex.DecodeString(params.NwkSKey) - if err != nil || len(nwkSKey) != 16 { - return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect network session key") - } - - params.Id = strings.Trim(params.Id, " ") - params.Url = strings.Trim(params.Url, " ") - if len(params.Id) <= 0 { - return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect application id") - } - if len(params.Url) <= 0 { - return core.Registration{}, errors.New(ErrInvalidStructure, "Incorrect application url") - } - - // Create registration - config := core.Registration{ - Recipient: core.Recipient{Id: params.Id, Address: params.Url}, - } - options := lorawan.AES128Key{} - copy(options[:], nwkSKey) - config.Options = options - copy(config.DevAddr[:], devAddr) - - return config, nil -} diff --git a/core/adapters/http/pubsub/doc.go b/core/adapters/http/pubsub/doc.go deleted file mode 100644 index 42115ae0d..000000000 --- a/core/adapters/http/pubsub/doc.go +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package pubsub extends the basic http to allow registrations to be done by an http request on a -// given endpoint. -// -// The adapter basically register a new endpoint to an original basic http adapter and generate -// registrations from them. -package pubsub diff --git a/core/adapters/http/pubsub/pubsub.go b/core/adapters/http/pubsub/pubsub.go deleted file mode 100644 index 316256cb6..000000000 --- a/core/adapters/http/pubsub/pubsub.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package pubsub - -import ( - "net/http" - - "github.com/TheThingsNetwork/ttn/core" - httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/apex/log" -) - -// Pubsub adapter materializes an extended basic http adapter which will also generate for each -// request made on a specific endpoint (here /end-devices). -// A Put request made on /end-devices with the appropriate parameters (refer to the parser doc for -// that) will end up in generating a registration event accessible via 'NextRegistration'. -type Adapter struct { - *httpadapter.Adapter // Composed of an original http adapter - parser.RegistrationParser // A registration parser to transform appropriate request into reg - ctx log.Interface // Just a logger - registrations chan regReq // Communication channel responsible for registrations management -} - -type regReq struct { - core.Registration // The actual registration request - response chan regRes // A dedicated channel to send back a response (ack or nack) -} - -type regRes struct { - statusCode int // The response status, 200 for ack 4xx for nack - content []byte // The response content -} - -// NewAdapter constructs a new http adapter that also handle registrations via http requests -func NewAdapter(adapter *httpadapter.Adapter, parser parser.RegistrationParser, ctx log.Interface) (*Adapter, error) { - a := &Adapter{ - Adapter: adapter, - RegistrationParser: parser, - ctx: ctx, - registrations: make(chan regReq), - } - - // So far we only supports one endpoint [PUT] /end-devices/:devAddr - a.RegisterEndpoint("/end-devices/", a.handlePutEndDevice) - - return a, nil -} - -// NextRegistration implements the core.Adapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - request := <-a.registrations - return request.Registration, regAckNacker{response: request.response}, nil -} - -// handle request [PUT] on /end-devices/:devAddr -func (a *Adapter) handlePutEndDevice(w http.ResponseWriter, req *http.Request) { - ctx := a.ctx.WithField("sender", req.RemoteAddr) - ctx.Debug("Receiving new registration request") - - // Check the http method - if req.Method != "PUT" { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [PUT] to register a device")) - return - } - - // Parse body and query params - config, err := a.Parse(req) - if err != nil { - ctx.WithError(err).Warn("Received invalid request") - httpadapter.BadRequest(w, err.Error()) - return - } - - // Send the registration and wait for ack / nack - response := make(chan regRes) - a.registrations <- regReq{Registration: config, response: response} - r, ok := <-response - if !ok { - ctx.Error("Core server not responding") - httpadapter.BadRequest(w, "Core server not responding") - return - } - w.WriteHeader(r.statusCode) - w.Write(r.content) -} diff --git a/core/adapters/http/pubsub/pubsub_test.go b/core/adapters/http/pubsub/pubsub_test.go deleted file mode 100644 index 619603df3..000000000 --- a/core/adapters/http/pubsub/pubsub_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package pubsub - -import ( - "bytes" - "fmt" - "net/http" - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -// NextRegistration() (core.Registration, core.AckNacker, error) -func TestNextRegistration(t *testing.T) { - tests := []struct { - AppId string - AppUrl string - DevAddr string - NwkSKey string - WantResult *core.Registration - WantError *string - }{ - // Valid device address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwkSKey: "000102030405060708090a0b0c0d0e0f", - DevAddr: "14aab0a4", - WantResult: &core.Registration{ - DevAddr: lorawan.DevAddr([4]byte{0x14, 0xaa, 0xb0, 0xa4}), - Recipient: core.Recipient{Id: "appid", Address: "myhandler.com:3000"}, - Options: lorawan.AES128Key([16]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8, 0x9, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf}), - }, - WantError: nil, - }, - // Invalid device address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwkSKey: "000102030405060708090a0b0c0d0e0f", - DevAddr: "INVALID", - WantResult: nil, - WantError: nil, - }, - // Invalid NwkSKey address - { - AppId: "appid", - AppUrl: "myhandler.com:3000", - NwkSKey: "00112233445566778899af", - DevAddr: "14aab0a4", - WantResult: nil, - WantError: nil, - }, - } - - // Logging - ctx := GetLogger(t, "Adapter") - a, err := httpadapter.NewAdapter(3021, parser.JSON{}, ctx) - if err != nil { - panic(err) - } - - adapter, err := NewAdapter(a, parser.PubSub{}, ctx) - client := &client{adapter: "0.0.0.0:3021"} - if err != nil { - panic(err) - } - - for _, test := range tests { - // Describe - Desc(t, "Trying to register %s -> %s, %s, %s", test.DevAddr, test.AppId, test.AppUrl, test.NwkSKey) - <-time.After(time.Millisecond * 100) - - // Build - gotErr := make(chan error) - gotConf := make(chan core.Registration) - go client.send(test.AppId, test.AppUrl, test.DevAddr, test.NwkSKey) - - // Operate - go func() { - config, _, err := adapter.NextRegistration() - gotErr <- err - gotConf <- config - }() - - // Check - select { - case err := <-gotErr: - checkErrors(t, test.WantError, err) - case <-time.After(time.Millisecond * 250): - checkErrors(t, test.WantError, nil) - } - - select { - case conf := <-gotConf: - checkRegistrationResult(t, test.WantResult, &conf) - case <-time.After(time.Millisecond * 250): - checkRegistrationResult(t, test.WantResult, nil) - } - } -} - -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - - Ko(t, "Expected error to be %s but got %v", want, got) -} - -func checkRegistrationResult(t *testing.T, want, got *core.Registration) { - if !reflect.DeepEqual(want, got) { - Ko(t, "Received configuration doesn't match expectations") - return - } - - Ok(t, "Check registration result") -} - -// Operate utilities - -// Wrapper around the http client -type client struct { - http.Client - adapter string -} - -// send is a convinient helper to send HTTP from a handler to the adapter -func (c *client) send(appId, appUrl, devAddr, nwkSKey string) http.Response { - buf := new(bytes.Buffer) - if _, err := buf.WriteString(fmt.Sprintf(`{"app_id":"%s","app_url":"%s","nwks_key":"%s"}`, appId, appUrl, nwkSKey)); err != nil { - panic(err) - } - request, err := http.NewRequest("PUT", fmt.Sprintf("http://%s/end-devices/%s", c.adapter, devAddr), buf) - if err != nil { - panic(err) - } - request.Header.Set("Content-Type", "application/json") - resp, err := c.Do(request) - if err != nil { - panic(err) - } - return *resp -} diff --git a/core/adapters/http/pubsub/reg_acknacker.go b/core/adapters/http/pubsub/reg_acknacker.go deleted file mode 100644 index c554ea42e..000000000 --- a/core/adapters/http/pubsub/reg_acknacker.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package pubsub - -import ( - "net/http" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -type regAckNacker struct { - response chan regRes // A channel dedicated to send back a response -} - -// Ack implements the core.Acker interface -func (r regAckNacker) Ack(p *core.Packet) error { - select { - case r.response <- regRes{statusCode: http.StatusAccepted}: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") - } -} - -// Nack implements the core.Nacker interface -func (r regAckNacker) Nack() error { - select { - case r.response <- regRes{ - statusCode: http.StatusConflict, - content: []byte("Unable to register the given device"), - }: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(ErrWrongBehavior, "No response was given to the acknacker") - } -} diff --git a/refactor/adapters/http/regAckNacker.go b/core/adapters/http/regAckNacker.go similarity index 95% rename from refactor/adapters/http/regAckNacker.go rename to core/adapters/http/regAckNacker.go index 5f3bb51a5..6341a48d2 100644 --- a/refactor/adapters/http/regAckNacker.go +++ b/core/adapters/http/regAckNacker.go @@ -7,7 +7,7 @@ import ( "net/http" "time" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" ) diff --git a/core/adapters/http/statuspage/statuspage.go b/core/adapters/http/statuspage/statuspage.go deleted file mode 100644 index 5e3f9a3ee..000000000 --- a/core/adapters/http/statuspage/statuspage.go +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package statuspage extends the basic http to show statistics on GET /status -// -// The adapter registers a new endpoint [/status/] to an original basic http adapter and serves statistics on it. -package statuspage - -import ( - "encoding/json" - "fmt" - "net" - "net/http" - "strings" - - httpadapter "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/apex/log" - "github.com/rcrowley/go-metrics" -) - -// Adapter extending the default http adapter -type Adapter struct { - *httpadapter.Adapter // The original http adapter - ctx log.Interface // Logging context -} - -// NewAdapter constructs a new http adapter that also handles status requests -func NewAdapter(adapter *httpadapter.Adapter, ctx log.Interface) (*Adapter, error) { - a := &Adapter{ - Adapter: adapter, - ctx: ctx, - } - - a.RegisterEndpoint("/status/", a.handleStatus) - - return a, nil -} - -// handle request [GET] on /status -func (a *Adapter) handleStatus(w http.ResponseWriter, req *http.Request) { - ctx := a.ctx.WithField("sender", req.RemoteAddr) - ctx.Debug("Receiving new status request") - - // Check the http method - if req.Method != "GET" { - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [GET] to get the status")) - return - } - - remoteHost, _, err := net.SplitHostPort(req.RemoteAddr) - if err != nil { - //The HTTP server did not set RemoteAddr to IP:port, which would be very very strange. - w.WriteHeader(http.StatusInternalServerError) - return - } - - remoteIP := net.ParseIP(remoteHost) - if remoteIP == nil || !remoteIP.IsLoopback() { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte(fmt.Sprintf("Status is only available from the local host, not from %s", remoteIP))) - return - } - - allStats := make(map[string]interface{}) - - metrics.Each(func(name string, i interface{}) { - // Make sure we put things in the right place - thisStat := allStats - for _, path := range strings.Split(name, ".") { - if thisStat[path] == nil { - thisStat[path] = make(map[string]interface{}) - } - if _, ok := thisStat[path].(map[string]interface{}); ok { - thisStat = thisStat[path].(map[string]interface{}) - } else { - ctx.Errorf("Error building %s stat", name) - return - } - } - - switch metric := i.(type) { - - case metrics.Counter: - m := metric.Snapshot() - thisStat["count"] = m.Count() - - case metrics.Histogram: - h := metric.Snapshot() - ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) - - thisStat["avg"] = h.Mean() - thisStat["min"] = h.Min() - thisStat["max"] = h.Max() - thisStat["p_25"] = ps[0] - thisStat["p_50"] = ps[1] - thisStat["p_75"] = ps[2] - - case metrics.Meter: - m := metric.Snapshot() - - thisStat["rate_1"] = m.Rate1() - thisStat["rate_5"] = m.Rate5() - thisStat["rate_15"] = m.Rate15() - thisStat["count"] = m.Count() - - } - }) - - response, err := json.Marshal(allStats) - if err != nil { - panic(err) - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(response) -} diff --git a/core/adapters/mosquitto/adapter.go b/core/adapters/mosquitto/adapter.go deleted file mode 100644 index c4227e910..000000000 --- a/core/adapters/mosquitto/adapter.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mosquitto - -import ( - "encoding/hex" - "fmt" - "strings" - - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/apex/log" - "github.com/brocaar/lorawan" -) - -type Adapter struct { - ctx log.Interface - registrations chan core.Registration -} - -type PersonnalizedActivation struct { - DevAddr lorawan.DevAddr - NwkSKey lorawan.AES128Key - AppSKey lorawan.AES128Key -} - -const ( - TOPIC_ACTIVATIONS string = "activations" - TOPIC_UPLINK = "up" - TOPIC_DOWNLINK = "down" - RESOURCE = "devices" - PERSONNALIZED = "personnalized" -) - -// NewAdapter constructs a new mqtt adapter -func NewAdapter(client *MQTT.Client, ctx log.Interface) (*Adapter, error) { - a := &Adapter{ - ctx: ctx, - registrations: make(chan core.Registration), - } - - token := client.Subscribe(fmt.Sprintf("+/%s/+/%s", RESOURCE, TOPIC_ACTIVATIONS), 2, a.handleActivation) - if token.Wait() && token.Error() != nil { - ctx.WithError(token.Error()).Error("Unable to instantiate the adapter") - return nil, errors.New(ErrFailedOperation, token.Error()) - } - - return a, nil -} - -func (a *Adapter) handleActivation(client *MQTT.Client, message MQTT.Message) { - topicInfos := strings.Split(message.Topic(), "/") - appEUI := topicInfos[0] - devEUI := topicInfos[2] - - if devEUI != PERSONNALIZED { - a.ctx.WithField("Device Address", devEUI).Warn("OTAA not yet supported. Unable to register device") - return - } - - payload := message.Payload() - if len(payload) != 36 { - a.ctx.WithField("Payload", payload).Error("Invalid registration payload") - return - } - - var devAddr lorawan.DevAddr - var nwkSKey lorawan.AES128Key - var appSKey lorawan.AES128Key - copy(devAddr[:], message.Payload()[:4]) - copy(nwkSKey[:], message.Payload()[4:20]) - copy(appSKey[:], message.Payload()[20:]) - - devEUI = fmt.Sprintf("00000000%s", hex.EncodeToString(devAddr[:])) - token := client.Subscribe(fmt.Sprintf("%s/%s/%s/%s", appEUI, RESOURCE, devEUI, TOPIC_UPLINK), 2, a.handleReception) - if token.Wait() && token.Error() != nil { - a.ctx.WithError(token.Error()).Error("Unable to subscribe") - //NOTE dedicated channel for errors ? Handled by nextRegistration ? - } - - a.registrations <- core.Registration{ - DevAddr: devAddr, - Recipient: core.Recipient{ - Id: appEUI, - Address: "DoestNotMatterWillBeRefactored", - }, - Options: struct { - NwkSKey lorawan.AES128Key - AppSKey lorawan.AES128Key - }{ - NwkSKey: nwkSKey, - AppSKey: appSKey, - }, - } -} - -func (a *Adapter) handleReception(client *MQTT.Client, message MQTT.Message) { - topicInfos := strings.Split(message.Topic(), "/") - //appEUI := topicInfos[0] - devEUI := topicInfos[2] - - if devEUI == PERSONNALIZED { - return - } -} - -// Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - return core.Packet{}, nil -} - -// Next implements the core.Adapter interface -func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { - return core.Packet{}, nil, nil -} - -// NextRegistration implements the core.Adapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - r := <-a.registrations - return r, nil, nil -} diff --git a/core/adapters/mosquitto/adapter_test.go b/core/adapters/mosquitto/adapter_test.go deleted file mode 100644 index 04d5f5224..000000000 --- a/core/adapters/mosquitto/adapter_test.go +++ /dev/null @@ -1,257 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mosquitto - -import ( - "bytes" - "encoding/hex" - "fmt" - "reflect" - "testing" - "time" - - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -type publicationShape struct { - AppEUI string - DevEUI string - Topic string - Content interface{} -} - -type packetShape struct { - DevAddr lorawan.DevAddr - Data string -} - -func TestNext(t *testing.T) { - devices := []PersonnalizedActivation{ - { - DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 1}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}), - }, - { - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - NwkSKey: lorawan.AES128Key([16]byte{2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}), - AppSKey: lorawan.AES128Key([16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}), - }, - } - - applications := []lorawan.EUI64{ - lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 4, 4}), - } - - tests := []struct { - Desc string - Registrations []publicationShape - Publication publicationShape - WantPacket *packetShape - WantError *string - }{ - { - Desc: "Register #0 | Publish with #0", - Registrations: []publicationShape{ - { - AppEUI: hex.EncodeToString(applications[0][:]), - DevEUI: "personnalized", - Topic: TOPIC_ACTIVATIONS, - Content: devices[0], - }, - }, - Publication: publicationShape{ - AppEUI: hex.EncodeToString(applications[0][:]), - DevEUI: fmt.Sprintf("%s%s", hex.EncodeToString([]byte{0, 0, 0, 0}), hex.EncodeToString(devices[0].DevAddr[:])), - Topic: TOPIC_UPLINK, - Content: "Data", - }, - WantPacket: &packetShape{ - DevAddr: devices[0].DevAddr, - Data: "Data", - }, - WantError: nil, - }, - { - Desc: "Register #0 | Publish with #1", - Registrations: []publicationShape{ - { - AppEUI: hex.EncodeToString(applications[0][:]), - DevEUI: "personnalized", - Topic: TOPIC_ACTIVATIONS, - Content: devices[0], - }, - }, - Publication: publicationShape{ - AppEUI: hex.EncodeToString(applications[0][:]), - DevEUI: fmt.Sprintf("%s%s", hex.EncodeToString([]byte{0, 0, 0, 0}), hex.EncodeToString(devices[0].DevAddr[:])), - Topic: TOPIC_UPLINK, - Content: "Data", - }, - WantPacket: nil, - WantError: nil, - }, - { - Desc: "Register #0 | Publish in void", - Registrations: []publicationShape{ - { - AppEUI: hex.EncodeToString(applications[0][:]), - DevEUI: "somethingElse", - Topic: TOPIC_ACTIVATIONS, - Content: devices[0], - }, - }, - Publication: publicationShape{ - AppEUI: hex.EncodeToString(applications[0][:]), - DevEUI: fmt.Sprintf("%s%s", hex.EncodeToString([]byte{0, 0, 0, 0}), hex.EncodeToString(devices[0].DevAddr[:])), - Topic: TOPIC_UPLINK, - Content: "Data", - }, - WantPacket: nil, - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - adapter, mosquitto := genAdapter(t, test.Registrations, 33333) - - // Operate - mosquitto.Publish(test.Publication) - packet, err := nextPacket(adapter) - - // Check - checkErrors(t, test.WantError, err) - checkPackets(t, test.WantPacket, packet) - - mosquitto.MQTT.Disconnect(0) - <-time.After(100 * time.Millisecond) - } -} - -// ----- BUILD utilities -type Mosquitto struct { - MQTT *MQTT.Client -} - -func (m *Mosquitto) Publish(p publicationShape) { - topic := fmt.Sprintf("%s/%s/%s/%s", p.AppEUI, RESOURCE, p.DevEUI, TOPIC_ACTIVATIONS) - if token := m.MQTT.Publish(topic, 2, true, p.Content); token.Wait() && token.Error() != nil { - panic(token.Error()) - } -} - -func genAdapter(t *testing.T, registrations []publicationShape, port int) (*Adapter, *Mosquitto) { - // Prepare client - opts := MQTT.NewClientOptions() - opts.AddBroker(fmt.Sprintf("tcp://localhost:%d", port)) - opts.SetClientID("TestClient") - client := MQTT.NewClient(opts) - if token := client.Connect(); token.Wait() && token.Error() != nil { - panic(token.Error()) - } - for _, r := range registrations { - topic := fmt.Sprintf("%s/%s/%s/%s", r.AppEUI, RESOURCE, r.DevEUI, TOPIC_ACTIVATIONS) - dev := r.Content.(PersonnalizedActivation) - buf := new(bytes.Buffer) - buf.Write(dev.DevAddr[:]) - buf.Write(dev.NwkSKey[:]) - buf.Write(dev.AppSKey[:]) - client.Publish(topic, 2, true, buf.Bytes()) - } - mosquitto := &Mosquitto{MQTT: client} - - // Prepare adapter - ctx := GetLogger(t, "Adapter") - adapter, err := NewAdapter(client, ctx) - if err != nil { - panic(err) - } - - // Send them all - return adapter, mosquitto -} - -// ----- OPERATE utilities -func nextPacket(adapter *Adapter) (core.Packet, error) { - nextPkt := make(chan interface{}) - go func() { - packet, _, err := adapter.Next() - nextPkt <- struct { - Packet core.Packet - Error error - }{packet, err} - }() - - select { - case itf := <-nextPkt: - res := itf.(struct { - Packet core.Packet - Error error - }) - return res.Packet, res.Error - case <-time.After(200 * time.Millisecond): - return core.Packet{}, nil - } -} - -// ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - - Ko(t, "Expected error to be %s but got %v", want, got) -} - -func checkPackets(t *testing.T, want *packetShape, got core.Packet) { - if want == nil { - if !reflect.DeepEqual(got, core.Packet{}) { - Ko(t, "Expected no packet but received %+v", got) - return - } - Ok(t, "Check packets") - return - } - - devAddr, err := got.DevAddr() - if err != nil { - Ko(t, "Received a wrongly formatted packet: %+v", got) - return - } - - if devAddr != want.DevAddr { - Ko(t, "Expected address [%v] but got [%v]", want.DevAddr, devAddr) - return - } - - data, ok := got.Payload.MACPayload.(*lorawan.MACPayload) - if !ok { - Ko(t, "Received a wrongly formatted packet: %+v", got) - return - } - - if len(data.FRMPayload) != 1 { - Ko(t, "Received a wrongly formatted packet: %+v", got) - return - } - - msg := string(data.FRMPayload[0].(*lorawan.DataPayload).Bytes) - - if want.Data != msg { - Ko(t, `Expected msg to be "%s" but got "%s"`, want.Data, msg) - return - } - - Ok(t, "Check packets") -} diff --git a/core/adapters/mosquitto/doc.go b/core/adapters/mosquitto/doc.go deleted file mode 100644 index 5f194104c..000000000 --- a/core/adapters/mosquitto/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package mosquitto provides an MQTT adapter that connect to an mqtt broker -package mosquitto diff --git a/refactor/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go similarity index 97% rename from refactor/adapters/mqtt/handlers/activation.go rename to core/adapters/mqtt/handlers/activation.go index f00ec8c00..63b07df65 100644 --- a/refactor/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -9,7 +9,7 @@ import ( "strings" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) diff --git a/refactor/adapters/mqtt/handlers/activationRegistration.go b/core/adapters/mqtt/handlers/activationRegistration.go similarity index 95% rename from refactor/adapters/mqtt/handlers/activationRegistration.go rename to core/adapters/mqtt/handlers/activationRegistration.go index 2113054c6..de30c4c3b 100644 --- a/refactor/adapters/mqtt/handlers/activationRegistration.go +++ b/core/adapters/mqtt/handlers/activationRegistration.go @@ -4,7 +4,7 @@ package handlers import ( - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/brocaar/lorawan" ) diff --git a/refactor/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go similarity index 98% rename from refactor/adapters/mqtt/handlers/activation_test.go rename to core/adapters/mqtt/handlers/activation_test.go index 1fbef6192..f664a7d95 100644 --- a/refactor/adapters/mqtt/handlers/activation_test.go +++ b/core/adapters/mqtt/handlers/activation_test.go @@ -6,8 +6,8 @@ package handlers import ( "testing" - core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/refactor/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go similarity index 98% rename from refactor/adapters/mqtt/handlers/helpers_test.go rename to core/adapters/mqtt/handlers/helpers_test.go index 7aa2af950..9539e1dc9 100644 --- a/refactor/adapters/mqtt/handlers/helpers_test.go +++ b/core/adapters/mqtt/handlers/helpers_test.go @@ -10,8 +10,8 @@ import ( "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - core "github.com/TheThingsNetwork/ttn/refactor" - . "github.com/TheThingsNetwork/ttn/refactor/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) diff --git a/refactor/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go similarity index 99% rename from refactor/adapters/mqtt/mqtt.go rename to core/adapters/mqtt/mqtt.go index f03dfaae0..cf8a80eab 100644 --- a/refactor/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -8,7 +8,7 @@ import ( "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" diff --git a/refactor/adapters/mqtt/mqttAckNacker.go b/core/adapters/mqtt/mqttAckNacker.go similarity index 95% rename from refactor/adapters/mqtt/mqttAckNacker.go rename to core/adapters/mqtt/mqttAckNacker.go index ec8199213..98657a44e 100644 --- a/refactor/adapters/mqtt/mqttAckNacker.go +++ b/core/adapters/mqtt/mqttAckNacker.go @@ -6,7 +6,7 @@ package mqtt import ( "time" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" ) diff --git a/refactor/adapters/mqtt/mqttClient.go b/core/adapters/mqtt/mqttClient.go similarity index 100% rename from refactor/adapters/mqtt/mqttClient.go rename to core/adapters/mqtt/mqttClient.go diff --git a/refactor/adapters/mqtt/mqttRecipient.go b/core/adapters/mqtt/mqttRecipient.go similarity index 100% rename from refactor/adapters/mqtt/mqttRecipient.go rename to core/adapters/mqtt/mqttRecipient.go diff --git a/refactor/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go similarity index 99% rename from refactor/adapters/mqtt/mqtt_test.go rename to core/adapters/mqtt/mqtt_test.go index 29f2d620b..89379b387 100644 --- a/refactor/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -10,7 +10,7 @@ import ( "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/core/adapters/semtech/adapter.go b/core/adapters/semtech/adapter.go deleted file mode 100644 index ccc0c08cf..000000000 --- a/core/adapters/semtech/adapter.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "fmt" - "net" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" -) - -// Adapter represents a semtech adapter which sends and receives packet via UDP in respect of the -// semtech forwarder protocol -type Adapter struct { - ctx log.Interface // Just a logger - conn chan udpMsg // Channel used to manage response transmissions made by multiple goroutines - next chan rxpkMsg // Incoming valid RXPK packets are pushed to this channel -} - -// udpMsg type materializes response messages transmitted towards existing recipients (commonly, -// gateways). -type udpMsg struct { - conn *net.UDPConn // Provide if you intent to change the current adapter connection - addr *net.UDPAddr // The target recipient address - raw []byte // The raw byte sequence that has to be sent -} - -// rxpkMsg type materializes valid uplink messages coming from a given recipient -type rxpkMsg struct { - rxpk semtech.RXPK // The actual RXPK message - recipient core.Recipient // The address and id of the source emitter -} - -// NewAdapter constructs and allocates a new semtech adapter -func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { - a := Adapter{ - ctx: ctx, - conn: make(chan udpMsg), - next: make(chan rxpkMsg), - } - - // Create the udp connection and start listening with a goroutine - var udpConn *net.UDPConn - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - a.ctx.WithField("port", port).Info("Starting Server") - if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.New(ErrInvalidStructure, fmt.Sprintf("Invalid port %v", port)) - } - - go a.monitorConnection() - a.conn <- udpMsg{conn: udpConn} - go a.listen(udpConn) // Terminates when the connection is closed - - return &a, nil -} - -// Send implements the core.Adapter interface. Not implemented for the semtech adapter. -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - return core.Packet{}, errors.New(ErrNotSupported, "Send not supported on semtech adapter") -} - -// Next implements the core.Adapter interface -func (a *Adapter) Next() (core.Packet, core.AckNacker, error) { - msg := <-a.next - packet, err := core.ConvertRXPK(msg.rxpk) - if err != nil { - a.ctx.Debug("Received invalid packet") - return core.Packet{}, nil, errors.New(ErrInvalidStructure, err) - } - return packet, semtechAckNacker{recipient: msg.recipient, conn: a.conn}, nil -} - -// NextRegistration implements the core.Adapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return core.Registration{}, nil, errors.New(ErrNotSupported, "NextRegistration not supported on semtech adapter") -} - -// listen Handle incoming packets and forward them -func (a *Adapter) listen(conn *net.UDPConn) { - defer conn.Close() - a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") - for { - // The Semtech packet forwarder uses a buffer size of ((540 * NB_PKT_MAX) + 30 + STATUS_SIZE) - // Where NB_PKT_MAX = 8 and STATUS_SIZE = 200 - // We are collecting statistics to see if we can reduce the buffer size (see below). - buf := make([]byte, 4550) - - n, addr, err := conn.ReadFromUDP(buf) - if err != nil { // Problem with the connection - a.ctx.WithError(err).Error("Connection error") - continue - } - a.ctx.Debug("Incoming datagram") - - // Collect statistics for buffer size reduction - stats.UpdateHistogram("semtech_adapter.push_data.size", int64(n)) - - pkt := new(semtech.Packet) - err = pkt.UnmarshalBinary(buf[:n]) - if err != nil { - a.ctx.WithError(err).Warn("Invalid packet") - continue - } - - switch pkt.Identifier { - case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK - stats.MarkMeter("semtech_adapter.pull_data") - pullAck, err := semtech.Packet{ - Version: semtech.VERSION, - Token: pkt.Token, - Identifier: semtech.PULL_ACK, - }.MarshalBinary() - if err != nil { - a.ctx.WithError(err).Error("Unexpected error while marshaling PULL_ACK") - continue - } - a.ctx.WithField("recipient", addr).Debug("Sending PULL_ACK") - a.conn <- udpMsg{addr: addr, raw: pullAck} - case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component - stats.MarkMeter("semtech_adapter.push_data") - pushAck, err := semtech.Packet{ - Version: semtech.VERSION, - Token: pkt.Token, - Identifier: semtech.PUSH_ACK, - }.MarshalBinary() - if err != nil { - a.ctx.WithError(err).Error("Unexpected error while marshaling PUSH_ACK") - continue - } - a.ctx.WithField("Recipient", addr).Debug("Sending PUSH_ACK") - a.conn <- udpMsg{addr: addr, raw: pushAck} - - if pkt.Payload == nil { - a.ctx.WithField("packet", pkt).Warn("Invalid PUSH_DATA packet") - continue - } - for _, rxpk := range pkt.Payload.RXPK { - a.next <- rxpkMsg{ - rxpk: rxpk, - recipient: core.Recipient{Address: addr, Id: pkt.GatewayId}, - } - } - default: - a.ctx.WithField("packet", pkt).Debug("Ignoring unexpected packet") - continue - } - } -} - -// monitorConnection manages udpConnection of the adapter and send message through that connection -// -// That function executes into a single goroutine and is the only one allowed to write UDP messages. -// Doing this makes sure that only 1 goroutine is interacting with the connection. It thereby allows -// the connection to be replaced at any moment (in case of failure for instance) without disturbing -// the ongoing process. -func (a *Adapter) monitorConnection() { - var udpConn *net.UDPConn - for msg := range a.conn { - if msg.conn != nil { // Change the connection - if udpConn != nil { - a.ctx.Debug("Define new UDP connection") - udpConn.Close() - } - udpConn = msg.conn - } - - if udpConn != nil && msg.raw != nil { // Send the given udp message - if _, err := udpConn.WriteToUDP(msg.raw, msg.addr); err != nil { - a.ctx.WithError(err).Error("Error while sending UDP message") - } - } - } - if udpConn != nil { - udpConn.Close() // Make sure we close the connection before leaving if we dare ever leave. - } -} diff --git a/core/adapters/semtech/adapter_test.go b/core/adapters/semtech/adapter_test.go deleted file mode 100644 index 7ff520424..000000000 --- a/core/adapters/semtech/adapter_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestSend(t *testing.T) { - Desc(t, "Send is not supported") - adapter, _ := genAdapter(t, 33000) - _, err := adapter.Send(core.Packet{}) - checkErrors(t, pointer.String(ErrNotSupported), err) -} - -func TestNextRegistration(t *testing.T) { - Desc(t, "Next registration is not supported") - adapter, _ := genAdapter(t, 33001) - _, _, err := adapter.NextRegistration() - checkErrors(t, pointer.String(ErrNotSupported), err) -} - -func TestNext(t *testing.T) { - adapter, next := genAdapter(t, 33002) - server := genMockServer(33002) - - tests := []struct { - Adapter *Adapter - Packet semtech.Packet - WantAck semtech.Packet - WantNext core.Packet - WantError *string - }{ - { // Valid uplink PUSH_DATA - Adapter: adapter, - Packet: genPUSH_DATAWithRXPK([]byte{0x14, 0x42}), - WantAck: genPUSH_ACK([]byte{0x14, 0x42}), - WantNext: genCorePacket(genPUSH_DATAWithRXPK([]byte{0x14, 0x42})), - WantError: nil, - }, - { // Invalid uplink packet - Adapter: adapter, - Packet: genPUSH_ACK([]byte{0x22, 0x35}), - WantAck: semtech.Packet{}, - WantNext: core.Packet{}, - WantError: nil, - }, - { // Uplink PUSH_DATA with no RXPK - Adapter: adapter, - Packet: genPUSH_DATANoRXPK([]byte{0x22, 0x35}), - WantAck: genPUSH_ACK([]byte{0x22, 0x35}), - WantNext: core.Packet{}, - WantError: nil, - }, - { // Uplink PULL_DATA - Adapter: adapter, - Packet: genPULL_DATA([]byte{0x62, 0xfa}), - WantAck: genPULL_ACK([]byte{0x62, 0xfa}), - WantNext: core.Packet{}, - WantError: nil, - }, - { // Uplink PUSH_DATA with no encoded payload - Adapter: adapter, - Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), - WantAck: genPUSH_ACK([]byte{0x22, 0x35}), - WantNext: core.Packet{}, - WantError: pointer.String(ErrInvalidStructure), - }, - } - - for _, test := range tests { - // Describe - Desc(t, "Sending packet through adapter: %v", test.Packet) - <-time.After(time.Millisecond * 100) - - // Operate - ack := server.send(test.Packet) - packet, err := getNextPacket(next) - - // Check - checkErrors(t, test.WantError, err) - checkCorePackets(t, test.WantNext, packet) - checkResponses(t, test.WantAck, ack) - } -} - -// ----- OPERATE utilities -func getNextPacket(next chan interface{}) (core.Packet, error) { - select { - case i := <-next: - res := i.(struct { - err error - packet core.Packet - }) - return res.packet, res.err - case <-time.After(100 * time.Millisecond): - return core.Packet{}, nil - } -} - -// ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - - Ko(t, "Expected error to be %s but got %v", want, got) -} - -func checkCorePackets(t *testing.T, want core.Packet, got core.Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check core packets") - return - } - Ko(t, "Received core packet does not match expecatations.\nWant: %v\nGot: %v", want, got) -} - -func checkResponses(t *testing.T, want semtech.Packet, got semtech.Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check responses") - return - } - Ko(t, "Received response does not match expecatations.\nWant: %v\nGot: %v", want.String(), got.String()) -} diff --git a/core/adapters/semtech/build_utilities_test.go b/core/adapters/semtech/build_utilities_test.go deleted file mode 100644 index 73c8b5c78..000000000 --- a/core/adapters/semtech/build_utilities_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "encoding/base64" - "fmt" - "net" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -// ----- build utilities -type mockServer struct { - conn *net.UDPConn - response chan semtech.Packet -} - -// Generate a mock server that will send packet through a udp connection and communicate back -// received packet. -func genMockServer(port uint) mockServer { - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { - panic(err) - } - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - panic(err) - } - - response := make(chan semtech.Packet) - go func() { - for { - buf := make([]byte, 256) - n, _, err := conn.ReadFromUDP(buf) - if err != nil { - panic(err) - } - packet := new(semtech.Packet) - if err = packet.UnmarshalBinary(buf[:n]); err != nil { - panic(err) - } - response <- *packet - } - }() - - return mockServer{conn: conn, response: response} -} - -// Send a packet through the udp mock server toward the adapter -func (s mockServer) send(p semtech.Packet) semtech.Packet { - raw, err := p.MarshalBinary() - if err != nil { - panic(err) - } - s.conn.Write(raw) - select { - case packet := <-s.response: - return packet - case <-time.After(100 * time.Millisecond): - return semtech.Packet{} - } -} - -// Generates an adapter as well as a channel that behaves like the Next() methods (but can be used -// in a select for timeout) -func genAdapter(t *testing.T, port uint) (*Adapter, chan interface{}) { - // Logging - ctx := GetLogger(t, "Adapter") - - adapter, err := NewAdapter(port, ctx) - if err != nil { - panic(err) - } - next := make(chan interface{}) - go func() { - for { - packet, _, err := adapter.Next() - next <- struct { - err error - packet core.Packet - }{err: err, packet: packet} - } - }() - return adapter, next -} - -// Generate a core packet from a semtech packet that has one RXPK -func genCorePacket(p semtech.Packet) core.Packet { - if p.Payload == nil || len(p.Payload.RXPK) != 1 { - panic("Expected a payload with one rxpk") - } - packet, err := core.ConvertRXPK(p.Payload.RXPK[0]) - if err != nil { - panic(err) - } - return packet -} - -func genPUSH_DATANoRXPK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Token: token, - Identifier: semtech.PUSH_DATA, - } -} - -func genPUSH_DATANoPayload(token []byte) semtech.Packet { - packet := genPUSH_DATAWithRXPK(token) - packet.Payload.RXPK[0].Data = nil - return packet -} - -func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { - packet := genPUSH_DATANoRXPK(token) - packet.Payload = &semtech.Payload{ - RXPK: []semtech.RXPK{ - semtech.RXPK{ - Rssi: pointer.Int(-60), - Codr: pointer.String("4/7"), - Data: pointer.String(genRXPKData()), - }, - }, - } - return packet -} - -func genPULL_ACK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - Identifier: semtech.PULL_ACK, - } -} - -func genPUSH_ACK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - Identifier: semtech.PUSH_ACK, - } -} - -func genPULL_DATA(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Identifier: semtech.PULL_DATA, - } -} - -func genRXPKData() string { - // 1. Generate a PHYPayload - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("My Data")}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(true) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - // 2. Generate a JSON payload received by the server - raw, err := payload.MarshalBinary() - if err != nil { - panic(err) - } - return base64.StdEncoding.EncodeToString(raw) -} diff --git a/core/adapters/semtech/doc.go b/core/adapters/semtech/doc.go deleted file mode 100644 index 676e2ad87..000000000 --- a/core/adapters/semtech/doc.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package semtech provides an Adapter which implements the semtech forwarder protocol. -// -// The protocol could be found in this document: -// https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/semtech.pdf -// -// This protocol and to some extend the adapter does not allow a spontaneous downlink transmission -// to be initiated. Response packets are only transmitted if are a response to uplink packets. The -// AckNacker handles that logic. -package semtech diff --git a/core/adapters/semtech/semtech_acknacker.go b/core/adapters/semtech/semtech_acknacker.go deleted file mode 100644 index a02b254a2..000000000 --- a/core/adapters/semtech/semtech_acknacker.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "fmt" - "net" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// semtechAckNacker represents an AckNacker for a semtech request -type semtechAckNacker struct { - conn chan udpMsg // The adapter downlink connection channel - recipient core.Recipient // The recipient to reach -} - -// Ack implements the core.Adapter interface -func (an semtechAckNacker) Ack(p *core.Packet) error { - if p == nil { - return nil - } - - // For the downlink, we have to send a PULL_RESP packet which hold a TXPK - txpk, err := core.ConvertToTXPK(*p) - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - - raw, err := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{TXPK: &txpk}, - }.MarshalBinary() - - addr, ok := an.recipient.Address.(*net.UDPAddr) - if !ok { - return errors.New(ErrInvalidStructure, fmt.Sprintf("Expected UDPAddr but got: %v", an.recipient.Address)) - } - an.conn <- udpMsg{raw: raw, addr: addr} - return nil -} - -// Ack implements the core.Adapter interface -func (an semtechAckNacker) Nack() error { - // There's no notion of nack in the semtech protocol. You either reply something or you don't. - return nil -} diff --git a/refactor/adapters/udp/handlers/build_utilities_test.go b/core/adapters/udp/handlers/build_utilities_test.go similarity index 98% rename from refactor/adapters/udp/handlers/build_utilities_test.go rename to core/adapters/udp/handlers/build_utilities_test.go index 743a6a9f1..d7d274abd 100644 --- a/refactor/adapters/udp/handlers/build_utilities_test.go +++ b/core/adapters/udp/handlers/build_utilities_test.go @@ -11,8 +11,8 @@ import ( "testing" "time" - core "github.com/TheThingsNetwork/ttn/refactor" - "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/udp" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" diff --git a/refactor/adapters/udp/handlers/conversions_test.go b/core/adapters/udp/handlers/conversions_test.go similarity index 98% rename from refactor/adapters/udp/handlers/conversions_test.go rename to core/adapters/udp/handlers/conversions_test.go index 58e685611..3871c095a 100644 --- a/refactor/adapters/udp/handlers/conversions_test.go +++ b/core/adapters/udp/handlers/conversions_test.go @@ -7,7 +7,7 @@ import ( "reflect" "testing" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" diff --git a/refactor/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go similarity index 97% rename from refactor/adapters/udp/handlers/semtech.go rename to core/adapters/udp/handlers/semtech.go index 2c812dde4..2a7814867 100644 --- a/refactor/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -9,8 +9,8 @@ import ( "strings" "time" - core "github.com/TheThingsNetwork/ttn/refactor" - "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/udp" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/refactor/adapters/udp/handlers/semtech_test.go b/core/adapters/udp/handlers/semtech_test.go similarity index 97% rename from refactor/adapters/udp/handlers/semtech_test.go rename to core/adapters/udp/handlers/semtech_test.go index 8d13fd7be..0697232d4 100644 --- a/refactor/adapters/udp/handlers/semtech_test.go +++ b/core/adapters/udp/handlers/semtech_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - core "github.com/TheThingsNetwork/ttn/refactor" - "github.com/TheThingsNetwork/ttn/refactor/adapters/udp" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/udp" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" diff --git a/refactor/adapters/udp/udp.go b/core/adapters/udp/udp.go similarity index 99% rename from refactor/adapters/udp/udp.go rename to core/adapters/udp/udp.go index ac5df1cb7..a3d03a1e1 100644 --- a/refactor/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -7,7 +7,7 @@ import ( "fmt" "net" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) diff --git a/refactor/adapters/udp/udpAckNacker.go b/core/adapters/udp/udpAckNacker.go similarity index 94% rename from refactor/adapters/udp/udpAckNacker.go rename to core/adapters/udp/udpAckNacker.go index 7d95b4d53..a2d9fc6cb 100644 --- a/refactor/adapters/udp/udpAckNacker.go +++ b/core/adapters/udp/udpAckNacker.go @@ -6,7 +6,7 @@ package udp import ( "time" - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" ) diff --git a/refactor/adapters/udp/udpRegistration.go b/core/adapters/udp/udpRegistration.go similarity index 91% rename from refactor/adapters/udp/udpRegistration.go rename to core/adapters/udp/udpRegistration.go index 54fe6c26b..f72b5adf5 100644 --- a/refactor/adapters/udp/udpRegistration.go +++ b/core/adapters/udp/udpRegistration.go @@ -4,7 +4,7 @@ package udp import ( - core "github.com/TheThingsNetwork/ttn/refactor" + "github.com/TheThingsNetwork/ttn/core" "github.com/brocaar/lorawan" ) diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go index 4c81cceaa..f4d5fbe53 100644 --- a/core/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -4,82 +4,12 @@ package core import ( - "encoding/base64" - "strings" "time" - "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/brocaar/lorawan" ) -// Generate an RXPK packet using the given payload as Data -func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - - return semtech.RXPK{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Data: pointer.String(data), - Freq: pointer.Float64(863.125), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(uint(len([]byte(data)))), - Stat: pointer.Int(0), - Time: pointer.Time(time.Now()), - Tmst: pointer.Uint(uint(time.Now().UnixNano())), - } -} - -// Generates a TXPK packet using the given payload and the given metadata -func genTXPK(phyPayload lorawan.PHYPayload, metadata Metadata) semtech.TXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - return semtech.TXPK{ - Codr: metadata.Codr, - Data: pointer.String(data), - Datr: metadata.Datr, - Fdev: metadata.Fdev, - Freq: metadata.Freq, - Imme: metadata.Imme, - Ipol: metadata.Ipol, - Modu: metadata.Modu, - Ncrc: metadata.Ncrc, - Powe: metadata.Powe, - Prea: metadata.Prea, - Rfch: metadata.Rfch, - Size: metadata.Size, - Time: metadata.Time, - Tmst: metadata.Tmst, - } -} - -// Generate a Metadata object that matches RXPK metadata -func genMetadata(RXPK semtech.RXPK) Metadata { - return Metadata{ - Chan: RXPK.Chan, - Codr: RXPK.Codr, - Freq: RXPK.Freq, - Lsnr: RXPK.Lsnr, - Modu: RXPK.Modu, - Rfch: RXPK.Rfch, - Rssi: RXPK.Rssi, - Size: RXPK.Size, - Stat: RXPK.Stat, - Time: RXPK.Time, - Tmst: RXPK.Tmst, - } -} - // Generates a Metadata object with all field completed with relevant values func genFullMetadata() Metadata { timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go index b55efe38b..045b6541e 100644 --- a/core/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -9,29 +9,31 @@ import ( "regexp" "testing" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) // Checks that two packets match func checkPackets(t *testing.T, want Packet, got Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") + if want == nil { + if got == nil { + Ok(t, "Check packets") + return + } + Ko(t, "No packet was expected but got %s", got.String()) return } - Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) -} -// Checks that errors match -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") + if got == nil { + Ko(t, "Was expecting %s but got nothing", want.String()) return } - Ko(t, "Expected error to be %s but got %v", want, got) + if reflect.DeepEqual(want, got) { + Ok(t, "Check packets") + return + } + + Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) } // Checks that obtained json matches expected one @@ -54,15 +56,6 @@ func checkMetadata(t *testing.T, want Metadata, got Metadata) { Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) } -// Checks that obtained TXPK matches expeceted one -func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { - if reflect.DeepEqual(want, got) { - Ok(t, "check TXPKs") - return - } - Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) -} - // Check that obtained json strings contains the required field func checkFields(t *testing.T, want []string, got []byte) { for _, field := range want { diff --git a/core/components/broker.go b/core/components/broker.go deleted file mode 100644 index e0bb4b0b9..000000000 --- a/core/components/broker.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" - "github.com/brocaar/lorawan" -) - -// Broker type materializes the logic part handled by a broker -type Broker struct { - ctx log.Interface // Just a logger - db BrokerStorage // Reference to the internal broker storage -} - -// NewBroker constructs a new broker from a given storage -func NewBroker(db BrokerStorage, ctx log.Interface) *Broker { - return &Broker{ - ctx: ctx, - db: db, - } -} - -// HandleUp implements the core.Component interface -func (b *Broker) HandleUp(p core.Packet, an core.AckNacker, adapter core.Adapter) error { - stats.MarkMeter("broker.uplink.in") - b.ctx.Debug("Handling uplink packet") - - // 1. Lookup for entries for the associated device - devAddr, err := p.DevAddr() - if err != nil { - stats.MarkMeter("broker.uplink.invalid") - b.ctx.Warn("Uplink Invalid") - an.Nack() - return errors.New(ErrInvalidStructure, err) - } - ctx := b.ctx.WithField("devAddr", devAddr) - entries, err := b.db.Lookup(devAddr) - if err != nil { - switch err.(errors.Failure).Nature { - case ErrWrongBehavior: - stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") - ctx.Warn("Uplink device not found") - return an.Nack() - default: - b.ctx.Warn("Database lookup failed") - an.Nack() - return errors.New(ErrFailedOperation, err) - } - } - stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) - - // 2. Several handler might be associated to the same device, we distinguish them using MIC - // check. Only one should verify the MIC check. - var handler *core.Recipient - for _, entry := range entries { - ok, err := p.Payload.ValidateMIC(entry.NwkSKey) - if err != nil { - continue - } - if ok { - handler = &core.Recipient{ - Id: entry.Id, - Address: entry.Url, - } - stats.MarkMeter("broker.uplink.handler_lookup.match") - ctx.WithField("handler", handler).Debug("Associated device with handler") - break - } - } - if handler == nil { - stats.MarkMeter("broker.uplink.handler_lookup.no_match") - ctx.Warn("Could not find handler for device") - return an.Nack() - } - - // 3. If one was found, we forward the packet and wait for the response - response, err := adapter.Send(p, *handler) - if err != nil { - stats.MarkMeter("broker.uplink.bad_handler_response") - an.Nack() - return errors.New(ErrFailedOperation, err) - } - - stats.MarkMeter("broker.uplink.ok") - return an.Ack(&response) -} - -// HandleDown implements the core.Component interface. Not implemented yet -func (b *Broker) HandleDown(p core.Packet, an core.AckNacker, a core.Adapter) error { - return errors.New(ErrNotSupported, "HandleDown not supported on broker") -} - -// Register implements the core.Component interface -func (b *Broker) Register(r core.Registration, an core.AckNacker) error { - stats.MarkMeter("broker.registration.in") - b.ctx.Debug("Handling registration") - - id, okId := r.Recipient.Id.(string) - url, okUrl := r.Recipient.Address.(string) - nwkSKey, okNwkSKey := r.Options.(lorawan.AES128Key) - - ctx := b.ctx.WithField("devAddr", r.DevAddr) - - if !(okId && okUrl && okNwkSKey) { - stats.MarkMeter("broker.registration.invalid") - ctx.Warn("Invalid Registration") - an.Nack() - return errors.New(ErrInvalidStructure, "Invalid registration params") - } - - entry := brokerEntry{Id: id, Url: url, NwkSKey: nwkSKey} - if err := b.db.Store(r.DevAddr, entry); err != nil { - stats.MarkMeter("broker.registration.failed") - ctx.WithError(err).Error("Failed Registration") - an.Nack() - return errors.New(ErrFailedOperation, err) - } - - stats.MarkMeter("broker.registration.ok") - ctx.Debug("Successful Registration") - return an.Ack(nil) -} diff --git a/refactor/components/broker/broker.go b/core/components/broker/broker.go similarity index 99% rename from refactor/components/broker/broker.go rename to core/components/broker/broker.go index 093547968..5912092ad 100644 --- a/refactor/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -4,7 +4,7 @@ package broker import ( - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" diff --git a/refactor/components/broker/storage.go b/core/components/broker/storage.go similarity index 98% rename from refactor/components/broker/storage.go rename to core/components/broker/storage.go index 0ef46fb7d..5f9aecad3 100644 --- a/refactor/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -4,7 +4,7 @@ package broker import ( - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" diff --git a/core/components/broker_storage.go b/core/components/broker_storage.go deleted file mode 100644 index a150b00c3..000000000 --- a/core/components/broker_storage.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "time" - - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -// BrokerStorage manages the internal persistent state of a broker -type BrokerStorage interface { - // Close properly ends the connection to the internal database - Close() error - - // Lookup retrieves all entries associated to a given device - Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) - - // Reset removes all entries stored in the storage - Reset() error - - // Store creates a new entry and add it to the other entries (if any) - Store(devAddr lorawan.DevAddr, entry brokerEntry) error -} - -type brokerBoltStorage struct { - *bolt.DB -} - -// brokerEntry stores all information that links a handler to a device -type brokerEntry struct { - Id string // The handler / application ID - NwkSKey lorawan.AES128Key // The network session key associated to the device - Url string // The webook url of the associated handler // NOTE This implies an http protocol. Should review. -} - -// NewBrokerStorage a new bolt broker in-memory storage -func NewBrokerStorage() (BrokerStorage, error) { - db, err := bolt.Open("broker_storage.db", 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, errors.New(ErrFailedOperation, err) - } - - if err := initDB(db, "devices"); err != nil { - return nil, err - } - - return &brokerBoltStorage{DB: db}, nil -} - -// Lookup implements the brokerStorage interface -func (s brokerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]brokerEntry, error) { - entries, err := lookup(s.DB, "devices", devAddr, &brokerEntry{}) - if err != nil { - return nil, err - } - return entries.([]brokerEntry), nil -} - -// Store implements the brokerStorage interface -func (s brokerBoltStorage) Store(devAddr lorawan.DevAddr, entry brokerEntry) error { - return store(s.DB, "devices", devAddr, &entry) -} - -// Close implements the brokerStorage interface -func (s brokerBoltStorage) Close() error { - return s.DB.Close() -} - -// Reset implements the brokerStorage interface -func (s brokerBoltStorage) Reset() error { - return resetDB(s.DB, "devices") -} - -// MarshalBinary implements the entryStorage interface -func (entry brokerEntry) MarshalBinary() ([]byte, error) { - w := newEntryReadWriter(nil) - w.Write(entry.Id) - w.Write(entry.NwkSKey) - w.Write(entry.Url) - return w.Bytes() -} - -// UnmarshalBinary implements the entryStorage interface -func (entry *brokerEntry) UnmarshalBinary(data []byte) error { - if entry == nil || len(data) < 3 { - return errors.New(ErrInvalidStructure, "invalid broker entry") - } - r := newEntryReadWriter(data) - r.Read(func(data []byte) { entry.Id = string(data) }) - r.Read(func(data []byte) { copy(entry.NwkSKey[:], data) }) - r.Read(func(data []byte) { entry.Url = string(data) }) - return r.Err() -} diff --git a/core/components/broker_storage_test.go b/core/components/broker_storage_test.go deleted file mode 100644 index 1c9e6b322..000000000 --- a/core/components/broker_storage_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "reflect" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -type brokerEntryShape struct { - brokerEntry - DevAddr lorawan.DevAddr -} - -func TestBrokerStorage(t *testing.T) { - devices := []lorawan.DevAddr{ - lorawan.DevAddr([4]byte{0, 0, 0, 1}), - lorawan.DevAddr([4]byte{14, 15, 8, 42}), - } - - entries := []brokerEntry{ - { - Id: "MyId1", - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}), - Url: "MyUrlThatWillSoonBerefactored", - }, - { - Id: "SecondId", - NwkSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 14, 14, 14, 14, 9, 14, 11, 12, 13, 14, 15}), - Url: "Url2", - }, - } - - tests := []struct { - Desc string - ExistingEntries []brokerEntryShape - Store *brokerEntryShape - Lookup lorawan.DevAddr - WantEntries []brokerEntry - WantError []string - }{ - { - Desc: "No entry | Store #0", - ExistingEntries: nil, - Store: &brokerEntryShape{entries[0], devices[0]}, - Lookup: devices[0], - WantEntries: []brokerEntry{entries[0]}, - WantError: nil, - }, - { - Desc: "Entry #0 | Store another #0", - ExistingEntries: []brokerEntryShape{ - {entries[0], devices[0]}, - }, - Store: &brokerEntryShape{entries[1], devices[0]}, - Lookup: devices[0], - WantEntries: []brokerEntry{entries[0], entries[1]}, - WantError: nil, - }, - { - Desc: "Entry #0 | Store #1 | Lookup #1", - ExistingEntries: []brokerEntryShape{ - {entries[0], devices[0]}, - }, - Store: &brokerEntryShape{entries[1], devices[1]}, - Lookup: devices[1], - WantEntries: []brokerEntry{entries[1]}, - WantError: nil, - }, - { - Desc: "Entry #0 | Store #1 | Lookup #0", - ExistingEntries: []brokerEntryShape{ - {entries[0], devices[0]}, - }, - Store: &brokerEntryShape{entries[1], devices[1]}, - Lookup: devices[0], - WantEntries: []brokerEntry{entries[0]}, - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - db := genFilledBrokerStorage(test.ExistingEntries) - cherr := make(chan interface{}, 2) - - // Operate - storeBroker(db, test.Store, cherr) - got := lookupBroker(db, test.Lookup, cherr) - - // Check - go func() { - time.After(time.Millisecond * 250) - close(cherr) - }() - checkChErrors(t, test.WantError, cherr) - checkBrokerEntries(t, test.WantEntries, got) - - // Clean - db.Close() - } -} - -// ----- BUILD utilities -func genFilledBrokerStorage(setup []brokerEntryShape) BrokerStorage { - db, err := NewBrokerStorage() - if err != nil { - panic(err) - } - - if err := db.Reset(); err != nil { - panic(err) - } - - for _, shape := range setup { - if err := db.Store(shape.DevAddr, shape.brokerEntry); err != nil { - panic(err) - } - } - - return db -} - -// ----- OPERATE utilities -func storeBroker(db BrokerStorage, entry *brokerEntryShape, cherr chan interface{}) { - if entry == nil { - return - } - - if err := db.Store(entry.DevAddr, entry.brokerEntry); err != nil { - cherr <- err - } -} - -func lookupBroker(db BrokerStorage, devAddr lorawan.DevAddr, cherr chan interface{}) []brokerEntry { - entry, err := db.Lookup(devAddr) - if err != nil { - cherr <- err - } - return entry -} - -// ----- CHECK utilities -func checkBrokerEntries(t *testing.T, want []brokerEntry, got []brokerEntry) { - if len(want) != len(got) { - Ko(t, "Expecting %d entries but got %d.", len(want), len(got)) - return - } - -outer: - for _, gentry := range got { - for _, wentry := range want { - if reflect.DeepEqual(gentry, wentry) { - continue outer - } - } - Ko(t, "Got an unexpected entry: %+v\nExpected only: %+v", gentry, want) - return - } - - Ok(t, "Check broker entries") -} diff --git a/core/components/broker_test.go b/core/components/broker_test.go deleted file mode 100644 index 1bc470f2b..000000000 --- a/core/components/broker_test.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestBrokerHandleup(t *testing.T) { - devices := []device{ - { - DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - }, - { - DevAddr: [4]byte{0, 0, 0, 2}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - }, - { - DevAddr: [4]byte{14, 14, 14, 14}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 11, 8, 11, 8, 8}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 10, 11, 8, 8, 8}, - }, - { - DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 9, 7, 7, 9, 8, 8, 8, 3, 13, 8}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 4, 7, 9, 9, 8, 8, 8, 9, 14, 8}, - }, - } - - recipients := []core.Registration{ - { - Recipient: core.Recipient{ - Address: "R0<->D0", - Id: "Id0", - }, - DevAddr: lorawan.DevAddr(devices[0].DevAddr), - Options: lorawan.AES128Key(devices[0].NwkSKey), - }, - { - Recipient: core.Recipient{ - Address: "R1<->D1", - Id: "Id1", - }, - DevAddr: lorawan.DevAddr(devices[1].DevAddr), - Options: lorawan.AES128Key(devices[1].NwkSKey), - }, - } - - tests := []struct { - Desc string - KnownRecipients []core.Registration - Packet packetShape - WantRecipients []core.Recipient - WantAck bool - WantError *string - }{ - { - Desc: "0 known | Send #0", - Packet: packetShape{ - Device: devices[0], - Data: "MyData", - }, - WantRecipients: nil, - WantAck: false, - WantError: nil, - }, - { - Desc: "know #0 | Send #0", - KnownRecipients: []core.Registration{ - recipients[0], - }, - Packet: packetShape{ - Device: devices[0], - Data: "MyData", - }, - WantRecipients: []core.Recipient{recipients[0].Recipient}, - WantAck: true, - WantError: nil, - }, - { - Desc: "know #1 | Send #0", - KnownRecipients: []core.Registration{ - recipients[1], - }, - Packet: packetShape{ - Device: devices[0], - Data: "MyData", - }, - WantRecipients: nil, - WantAck: false, - WantError: nil, - }, - { - Desc: "know #0, #1 | Send #2", - KnownRecipients: []core.Registration{ - recipients[0], - recipients[1], - }, - Packet: packetShape{ - Device: devices[2], - Data: "MyData", - }, - WantRecipients: nil, - WantAck: false, - WantError: nil, - }, - { - Desc: "know #0, #1 | Send #3 (address == #1, nwkskey != #1)", - KnownRecipients: []core.Registration{ - recipients[0], - recipients[1], - }, - Packet: packetShape{ - Device: devices[3], - Data: "MyData", - }, - WantRecipients: nil, - WantAck: false, - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - broker := genNewBroker(t, test.KnownRecipients) - packet := genPacketFromShape(test.Packet) - - // Operate - recipients, ack, err := handleBrokerUp(broker, packet) - - // Check - checkErrors(t, test.WantError, err) - checkBrokerAcks(t, test.WantAck, ack) - checkRecipients(t, test.WantRecipients, recipients) - - if err := broker.db.Close(); err != nil { - panic(err) - } - } -} - -// ----- BUILD utilities -func genNewBroker(t *testing.T, knownRecipients []core.Registration) *Broker { - ctx := GetLogger(t, "Broker") - - db, err := NewBrokerStorage() - if err != nil { - panic(err) - } - - if err := db.Reset(); err != nil { - panic(err) - } - - broker := NewBroker(db, ctx) - if err != nil { - panic(err) - } - - for _, registration := range knownRecipients { - err := broker.Register(registration, voidAckNacker{}) - if err != nil { - panic(err) - } - } - - return broker -} - -// ----- OPERATE utilities -func handleBrokerUp(broker core.Broker, packet core.Packet) ([]core.Recipient, *bool, error) { - adapter := &routerAdapter{} - an := &brokerAckNacker{} - err := broker.HandleUp(packet, an, adapter) - return adapter.Recipients, an.HasAck, err -} - -type brokerAckNacker struct { - HasAck *bool -} - -func (an *brokerAckNacker) Ack(p *core.Packet) error { - an.HasAck = new(bool) - *an.HasAck = true - return nil -} - -func (an *brokerAckNacker) Nack() error { - an.HasAck = new(bool) - *an.HasAck = false - return nil -} - -// ----- CHECK utilities -func checkBrokerAcks(t *testing.T, want bool, got *bool) { - if got == nil { - Ko(t, "No Ack or Nack was sent") - return - } - - expected, notExpected := "ack", "nack" - if !want { - expected, notExpected = notExpected, expected - } - if want != *got { - Ko(t, "Expected %s but got %s", expected, notExpected) - return - } - Ok(t, "Check acks") -} diff --git a/refactor/components/controller/controller.go b/core/components/controller/controller.go similarity index 100% rename from refactor/components/controller/controller.go rename to core/components/controller/controller.go diff --git a/core/components/doc.go b/core/components/doc.go deleted file mode 100644 index de39935e7..000000000 --- a/core/components/doc.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package components offers implementations for all major components involved in the network. -// -// Router -// -// Routers are entry points of the network from the Nodes perspective. Packets transmitted by Nodes -// are forwarded to specific Routers from one or several Gateways. The Router then forwards those -// packets to one or several Brokers. The communication is bi-directional: Routers may also transfer -// packets from Broker to Gateways. -// -// Broker -// -// Brokers have a global vision of a network's part. They are in charge of several nodes, meaning -// that they will handle packets coming from those nodes (thereby, they are able to tell to Routers -// if they can handle a given packet). Several Routers may send packets coming from the same -// end-device (shared by several segments / Gateways), all duplicates are processed by the Broker -// and are sent to a corresponding Handler. -// -// A Broker is thereby able to check the integrity of a received packet and is closely communicating -// with a Network Server in order to administrate the related end-device. For a reference of -// magnitude, Brokers are designed to be in charge of a whole country or region (if the region has -// enough activity to deserve a dedicated Broker). Note that while brokers are able to verify the -// integrity of the packet (and therefore the identify of the end device), they are not able to read -// application data. -// -// Handler -// -// Handlers materialize the entry point to the network for Applications. They are secure referees -// which encode and decode data coming from application before transmitting them to a Broker of the -// network. Therefore, they are in charge of handling secret applications keys and only communicate -// an application id to Brokers as well as specific network session keys for each node (described in -// further sections). This way, the whole chain is able to forward a packet to the corresponding -// Handler without having any information about either the recipient (but a meaningless id) or the -// content. -// -// Because a given Handler is able to decrypt the data payload of a given packet, it could also -// implement mechanisms such as geolocation and send to the corresponding application some -// interesting meta-data alongside the data payload. Incidentally, a handler can only decrypt -// payload for packets related to applications registered to that handler. The handler is managing -// several secret application session keys and it uses these to encrypt and decrypt corresponding -// packet payloads. -// -// A Handler could be either part of an application or a standalone trusty server on which -// applications may register. The Things Network will provide Handlers as part of the whole network -// but - and this is true for any component - anyone could create its own implementation as long as -// it is compliant to the following specifications -// -// Network Controller -// -// Network controllers process MAC commands emitted by end-devices as well as taking care of the -// data rates and the frequency of the end-devices. They would emit commands to optimize -// the network by adjusting end-devices data rates / frequencies unless the node is requesting to -// keep its configuration as is. -// -// For the moment, a single Network controller will be associated for each Broker. No communication -// mechanisms between Network controllers is planned for the first version. Also, it won't be -// possible for a Broker to query another Network Server than the one it has been assigned to. Those -// features might be part of a second version. -package components diff --git a/core/components/entryReadWriter.go b/core/components/entryReadWriter.go deleted file mode 100644 index 8fb26def0..000000000 --- a/core/components/entryReadWriter.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "bytes" - "encoding/binary" - "fmt" - - "github.com/brocaar/lorawan" -) - -// entryReadWriter offers convenient method to write and read successively from a bytes buffer. -type entryReadWriter struct { - err error - data *bytes.Buffer -} - -// newEntryReadWriter create a new read/writer from an existing buffer. -// -// If a nil or empty buffer is supplied, reading from the read/writer will cause an error (io.EOF) -// Nevertheless, if a valid non-empty buffer is given, the read/writer will start reading from the -// beginning of that buffer, and will start writting at the end of it. -func newEntryReadWriter(buf []byte) *entryReadWriter { - return &entryReadWriter{ - err: nil, - data: bytes.NewBuffer(buf), - } -} - -// Write appends the given data at the end of the existing buffer. -// -// It does nothing if an error was previously noticed and panics if the given data are something -// different from: []byte, string, AES128Key, EUI64, DevAddr. -// -// Also, it writes the length of the given raw data encoded on 2 bytes before writting the data -// itself. In that way, data can be appended and read easily. -func (w *entryReadWriter) Write(data interface{}) { - var raw []byte - switch data.(type) { - case []byte: - raw = data.([]byte) - case lorawan.AES128Key: - data := data.(lorawan.AES128Key) - raw = data[:] - case lorawan.EUI64: - data := data.(lorawan.EUI64) - raw = data[:] - case lorawan.DevAddr: - data := data.(lorawan.DevAddr) - raw = data[:] - case string: - raw = []byte(data.(string)) - default: - panic(fmt.Errorf("Unreckognized data type: %v", data)) - } - w.DirectWrite(uint16(len(raw))) - w.DirectWrite(raw) -} - -// DirectWrite appends the given data at the end of the existing buffer (without the length). -func (w *entryReadWriter) DirectWrite(data interface{}) { - if w.err != nil { - return - } - in := w.data.Next(w.data.Len()) - w.data = new(bytes.Buffer) - binary.Write(w.data, binary.BigEndian, in) - w.err = binary.Write(w.data, binary.BigEndian, data) -} - -// Read retrieves next data from the given buffer. Implicitely, this implies the data to have been -// written using the Write method (len | data). Data are sent back through a callback as an array of -// bytes. -func (w *entryReadWriter) Read(to func(data []byte)) { - if w.err != nil { - return - } - - lenTo := new(uint16) - if w.err = binary.Read(w.data, binary.BigEndian, lenTo); w.err != nil { - return - } - to(w.data.Next(int(*lenTo))) -} - -// Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and -// an error if any issue was encountered during the process. -func (w entryReadWriter) Bytes() ([]byte, error) { - if w.err != nil { - return nil, w.err - } - return w.data.Bytes(), nil -} - -// Err just return the err status of the read-writer. -func (w entryReadWriter) Err() error { - return w.err -} diff --git a/core/components/handler.go b/core/components/handler.go deleted file mode 100644 index e5c82898f..000000000 --- a/core/components/handler.go +++ /dev/null @@ -1,245 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "bytes" - "encoding/binary" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/apex/log" - "github.com/brocaar/lorawan" -) - -const BUFFER_DELAY = time.Millisecond * 300 // Buffering delay. Timeframe to wait from the first received packet. - -type Handler struct { - ctx log.Interface // Just a logger - db HandlerStorage // Reference to the handler internal storage - set chan<- uplinkBundle // Internal communication channel used to bufferise incoming packets -} - -type bundleId [16]byte // AppEUI(8) | DevAddr(4) | FCnt (4) - -// Uplink bundles are created for each incoming packet. They help to caracterize a packet more -// precisely and postpone its processing. -type uplinkBundle struct { - adapter core.Adapter - chresp chan interface{} // Error or decrypted packet - entry handlerEntry - id bundleId - packet core.Packet -} - -// NewHandler constructs a new handler component. -func NewHandler(db HandlerStorage, ctx log.Interface) *Handler { - h := Handler{ - ctx: ctx, - db: db, - } - - bundles := make(chan []uplinkBundle) - set := make(chan uplinkBundle) - - go h.consumeBundles(bundles) - go h.manageBuffers(bundles, set) - h.set = set - - return &h -} - -// Register implements the core.Register interface -func (h *Handler) Register(reg core.Registration, an core.AckNacker) error { - h.ctx.WithField("registration", reg).Debug("New registration request") - options, okOpts := reg.Options.(struct { - AppSKey lorawan.AES128Key - NwkSKey lorawan.AES128Key - }) - appEUI, okId := reg.Recipient.Id.(lorawan.EUI64) - - if !okId || !okOpts { - an.Nack() - return errors.New(ErrInvalidStructure, "Invalid registration options") - } - - err := h.db.Store(reg.DevAddr, handlerEntry{ - AppEUI: appEUI, - AppSKey: options.AppSKey, - NwkSKey: options.NwkSKey, - DevAddr: reg.DevAddr, - }) - - if err != nil { - an.Nack() - return errors.New(ErrFailedOperation, err) - } - - return an.Ack(nil) -} - -// HandleUp implements the core.Component interface -func (h *Handler) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { - h.ctx.Debug("Handling new uplink packet") - partition, err := h.db.Partition(p) - if err != nil { - h.ctx.WithError(err).Debug("Unable to find entry") - an.Nack() - return err - } - - fcnt, err := p.Fcnt() - if err != nil { - h.ctx.WithError(err).Debug("Unable to retrieve fcnt") - an.Nack() - return err - } - - chresp := make(chan interface{}) - var id bundleId - buf := new(bytes.Buffer) - buf.Write(partition[0].Id[:]) // Partition is necessarily of length 1, associated to 1 packet, the same we gave - binary.Write(buf, binary.BigEndian, fcnt) - copy(id[:], buf.Bytes()) - h.ctx.WithField("bundleId", id).Debug("Defining new bundle") - h.set <- uplinkBundle{ - id: id, - packet: p, - entry: partition[0].handlerEntry, - adapter: upAdapter, - chresp: chresp, - } - - resp := <-chresp - switch resp.(type) { - case core.Packet: - h.ctx.WithField("bundleId", id).Debug("Received response with packet. Sending ack") - pkt := new(core.Packet) - *pkt = resp.(core.Packet) - an.Ack(pkt) - return nil - case error: - h.ctx.WithField("bundleId", id).WithError(resp.(error)).Debug("Received response. Sending Nack") - an.Nack() - return errors.New(ErrFailedOperation, resp.(error)) - default: - h.ctx.WithField("bundleId", id).Debug("Received response. Sending ack") - an.Ack(nil) - return nil - } -} - -// HandleDown implements the core.Component interface. Not implemented yet. -func (h *Handler) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) (core.Packet, error) { - return core.Packet{}, errors.New(ErrNotSupported, "HandleDown not supported on handler") -} - -// consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, -// deduplicate them, and send a single enhanced packet to the upadapter for further processing. -func (h *Handler) consumeBundles(chbundles <-chan []uplinkBundle) { - ctx := h.ctx.WithField("goroutine", "consumer") - ctx.Debug("Starting bundle consumer") -browseBundles: - for bundles := range chbundles { - var packet *core.Packet - var sendToAdapter func(packet core.Packet) error - ctx.WithField("nb", len(bundles)).Debug("Consuming new bundles set") - for _, bundle := range bundles { - if packet == nil { - ctx.WithField("entry", bundle.entry).Debug("Preparing ground for given entry") - packet = new(core.Packet) - *packet = core.Packet{ - Payload: bundle.packet.Payload, - Metadata: core.Metadata{ - Group: []core.Metadata{bundle.packet.Metadata}, - }, - } - // The handler assumes payloads encrypted with AppSKey only ! - payload, ok := packet.Payload.MACPayload.(*lorawan.MACPayload) - if !ok { - err := errors.New(ErrInvalidStructure, "Unable to extract MACPayload") - ctx.WithError(err).Debug("Unable to extract MACPayload") - for _, bundle := range bundles { - bundle.chresp <- err - } - continue browseBundles - } - - if err := payload.DecryptFRMPayload(bundle.entry.AppSKey); err != nil { - ctx.WithError(err).Debug("Unable to decrypt MAC Payload with given AppSKey") - for _, bundle := range bundles { - bundle.chresp <- errors.New(ErrInvalidStructure, err) - } - continue browseBundles - } - - sendToAdapter = func(packet core.Packet) error { - // NOTE We'll have to look here for the downlink ! - _, err := bundle.adapter.Send(packet, core.Recipient{ - Address: bundle.entry.DevAddr, - Id: bundle.entry.AppEUI, - }) - return err - } - continue - } - packet.Metadata.Group = append(packet.Metadata.Group, bundle.packet.Metadata) - } - - err := sendToAdapter(*packet) - ctx.WithField("error", err).Debug("Sending to bundle adapter") - for _, bundle := range bundles { - bundle.chresp <- err - } - } -} - -// manageBuffers gather new incoming bundles that possess the same id -// It then flushs them once a given delay has passed since the reception of the first bundle. -func (h *Handler) manageBuffers(bundles chan<- []uplinkBundle, set <-chan uplinkBundle) { - ctx := h.ctx.WithField("goroutine", "bufferer") - ctx.Debug("Starting uplink packets buffering") - - processed := make(map[[12]byte]bundleId) // AppEUI | DevAddr (without the frame counter) - buffers := make(map[bundleId][]uplinkBundle) // Associate bundleId to a list of bufferized bundles - alarm := make(chan bundleId) // Communication channel with sub-sequent alarm - - for { - select { - case id := <-alarm: - b := buffers[id] - delete(buffers, id) - var pid [12]byte - copy(pid[:], id[:12]) - processed[pid] = id - go func(b []uplinkBundle) { bundles <- b }(b) - ctx.WithField("bundleId", id).Debug("Alarm done. Consuming collected bundles") - case bundle := <-set: - var pid [12]byte - copy(pid[:], bundle.id[:12]) - if processed[pid] == bundle.id { - ctx.WithField("bundleId", bundle.id).Debug("Reject already processed bundle") - go func(bundle uplinkBundle) { - bundle.chresp <- errors.New(ErrFailedOperation, "Already processed") - }(bundle) - continue - } - - b := append(buffers[bundle.id], bundle) - if len(b) == 1 { - go setAlarm(alarm, bundle.id, time.Millisecond*300) - ctx.WithField("bundleId", bundle.id).Debug("Starting buffering. New alarm set") - } - buffers[bundle.id] = b - } - } -} - -// setAlarm will trigger a message on the given channel after a given delay. -func setAlarm(alarm chan<- bundleId, id bundleId, delay time.Duration) { - <-time.After(delay) - alarm <- id -} diff --git a/refactor/components/handler/devStorage.go b/core/components/handler/devStorage.go similarity index 98% rename from refactor/components/handler/devStorage.go rename to core/components/handler/devStorage.go index f95b2907a..36af53314 100644 --- a/refactor/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -6,7 +6,7 @@ package handler import ( "fmt" - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" diff --git a/refactor/components/handler/handler.go b/core/components/handler/handler.go similarity index 99% rename from refactor/components/handler/handler.go rename to core/components/handler/handler.go index 465dae7d9..018a888d5 100644 --- a/refactor/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -7,7 +7,7 @@ import ( "reflect" "time" - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" "github.com/apex/log" diff --git a/refactor/components/handler/pktStorage.go b/core/components/handler/pktStorage.go similarity index 98% rename from refactor/components/handler/pktStorage.go rename to core/components/handler/pktStorage.go index bf59deb0c..1183fa103 100644 --- a/refactor/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -6,7 +6,7 @@ package handler import ( "fmt" - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" "github.com/brocaar/lorawan" diff --git a/core/components/handler_storage.go b/core/components/handler_storage.go deleted file mode 100644 index 0d49e5e3e..000000000 --- a/core/components/handler_storage.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -// HandlerStorage manages the internal persistent state of a handler -type HandlerStorage interface { - // Close properly ends the connection to the internal database - Close() error - - // Lookup retrieves all entries associated to a given device - Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) - - // Reset removes all entries stored in the storage - Reset() error - - // Store creates a new entry and add it to the other entries (if any) - Store(devAddr lorawan.DevAddr, entry handlerEntry) error - - // Partition split the packets given in argument in multiple set, each associated to a single - // device of a single app. Because packets may have the same address, the only way to - // distinguish them is to directly look at the network session key associated to each packet. - Partition(packet ...core.Packet) ([]handlerPartition, error) -} - -type handlerBoltStorage struct { - *bolt.DB -} - -// handlerEntry stores all information that link an application to a device -type handlerEntry struct { - AppEUI lorawan.EUI64 // The application EUI - AppSKey lorawan.AES128Key // The application session key - DevAddr lorawan.DevAddr // The device address - NwkSKey lorawan.AES128Key // The network session key -} - -// handlerPartition are generated by the partition method. See that method for more details -type handlerPartition struct { - handlerEntry // An actual handler entry - Id partitionId // The id of that partition - Packets []core.Packet // Packet that are part of that partition -} - -type partitionId [12]byte // AppEUI(8) | DevAddr(4) - -// NewHandlerStorage creates a new bolt handler in-memory storage -func NewHandlerStorage() (HandlerStorage, error) { - db, err := bolt.Open("handler_storage.db", 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, err - } - - if err := initDB(db, "applications"); err != nil { - return nil, err - } - - return &handlerBoltStorage{DB: db}, nil -} - -// Lookup implements the handlerStorage interface -func (s handlerBoltStorage) Lookup(devAddr lorawan.DevAddr) ([]handlerEntry, error) { - entries, err := lookup(s.DB, "applications", devAddr, &handlerEntry{}) - if err != nil { - return nil, err - } - return entries.([]handlerEntry), nil -} - -// Store implements the handlerStorage interface -func (s handlerBoltStorage) Store(devAddr lorawan.DevAddr, entry handlerEntry) error { - return store(s.DB, "applications", devAddr, &entry) -} - -// Partition implements the handlerStorage interface -func (s handlerBoltStorage) Partition(packets ...core.Packet) ([]handlerPartition, error) { - // Create a map in order to do the partition - partitions := make(map[partitionId]handlerPartition) - - for _, packet := range packets { - // First, determine devAddr, mandatory - devAddr, err := packet.DevAddr() - if err != nil { - return nil, errors.New(ErrInvalidStructure, err) - } - - entries, err := s.Lookup(devAddr) - if err != nil { - return nil, err - } - - // Now get all tuples associated to that device address, and choose the right one - for _, entry := range entries { - // Compute MIC check to find the right keys - ok, err := packet.Payload.ValidateMIC(entry.NwkSKey) - if err != nil || !ok { - continue // These aren't the droids you're looking for - } - - // #Easy - var id partitionId - copy(id[:8], entry.AppEUI[:]) - copy(id[8:], entry.DevAddr[:]) - partitions[id] = handlerPartition{ - handlerEntry: entry, - Id: id, - Packets: append(partitions[id].Packets, packet), - } - break // We shouldn't look for other entries, we've found the right one - } - } - - // Transform the map to a slice - res := make([]handlerPartition, 0, len(partitions)) - for _, p := range partitions { - res = append(res, p) - } - - if len(res) == 0 { - return nil, errors.New(ErrWrongBehavior, "No partition found") - } - - return res, nil -} - -// Close implements the handlerStorage interface -func (s handlerBoltStorage) Close() error { - return s.DB.Close() -} - -// Reset implements the handlerStorage interface -func (s handlerBoltStorage) Reset() error { - return resetDB(s.DB, "applications") -} - -// MarshalBinary implements the storageEntry interface -func (entry handlerEntry) MarshalBinary() ([]byte, error) { - w := newEntryReadWriter(nil) - w.Write(entry.AppEUI) - w.Write(entry.AppSKey) - w.Write(entry.DevAddr) - w.Write(entry.NwkSKey) - return w.Bytes() -} - -// UnmarshalBinary implements the storageEntry interface -func (entry *handlerEntry) UnmarshalBinary(data []byte) error { - if entry == nil || len(data) < 4 { - return errors.New(ErrInvalidStructure, "Invalid handler entry") - } - r := newEntryReadWriter(data) - r.Read(func(data []byte) { copy(entry.AppEUI[:], data) }) - r.Read(func(data []byte) { copy(entry.AppSKey[:], data) }) - r.Read(func(data []byte) { copy(entry.DevAddr[:], data) }) - r.Read(func(data []byte) { copy(entry.NwkSKey[:], data) }) - return r.Err() -} diff --git a/core/components/handler_storage_test.go b/core/components/handler_storage_test.go deleted file mode 100644 index ebb2508ea..000000000 --- a/core/components/handler_storage_test.go +++ /dev/null @@ -1,231 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestStoragePartition(t *testing.T) { - // CONVENTION below -> first DevAddr byte will be used as falue for FPort - setup := []handlerEntry{ - { // App #1, Dev #1 - AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 1}), - }, - { // App #1, Dev #2 - AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{10, 0, 0, 2}), - }, - { // App #1, Dev #3 - AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - NwkSKey: lorawan.AES128Key([16]byte{12, 0xa, 0xb, 1, 2, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{0xb, 15, 14, 14, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{14, 0, 0, 3}), - }, - { // App #2, Dev #1 - AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), - NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 5, 12, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 1, 11, 10, 0xc, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{0, 0, 0, 1}), - }, - { // App #2, Dev #2 - AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), - NwkSKey: lorawan.AES128Key([16]byte{0, 0xa, 0xb, 5, 12, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{14, 14, 14, 14, 1, 11, 10, 0xc, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{23, 0xaf, 0x14, 1}), - }, - } - - unknown := handlerEntry{ // App #1, Dev #4 - AppEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 23, 6, 7, 8, 9, 0x19, 11, 12, 13, 14, 15, 16}), - AppSKey: lorawan.AES128Key([16]byte{16, 0xba, 14, 13, 2, 11, 58, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - DevAddr: lorawan.DevAddr([4]byte{1, 0, 0, 4}), - } - - tests := []struct { - Desc string - PacketsShape []handlerEntry - WantPartitions []partitionShape - WantError *string - }{ - { - Desc: "1 packet -> 1 partition | 1 packet", - PacketsShape: []handlerEntry{setup[0]}, - WantPartitions: []partitionShape{{setup[0], 1}}, - WantError: nil, - }, - { - Desc: "1 unknown packet -> error not found", - PacketsShape: []handlerEntry{unknown}, - WantPartitions: nil, - WantError: pointer.String(ErrWrongBehavior), - }, - { - Desc: "2 packets | diff DevAddr & diff AppEUI -> 2 partitions | 1 packet", - PacketsShape: []handlerEntry{setup[0], setup[4]}, - WantPartitions: []partitionShape{{setup[0], 1}, {setup[4], 1}}, - WantError: nil, - }, - { - Desc: "2 packets | same DevAddr & diff AppEUI -> 2 partitions | 1 packet", - PacketsShape: []handlerEntry{setup[0], setup[3]}, - WantPartitions: []partitionShape{{setup[0], 1}, {setup[3], 1}}, - WantError: nil, - }, - { - Desc: "3 packets | diff DevAddr & same AppEUI -> 3 partitions | 1 packet", - PacketsShape: []handlerEntry{setup[0], setup[1], setup[2]}, - WantPartitions: []partitionShape{{setup[0], 1}, {setup[1], 1}, {setup[2], 1}}, - WantError: nil, - }, - { - Desc: "3 packets | same DevAddr & same AppEUI -> 1 partitions | 3 packets", - PacketsShape: []handlerEntry{setup[0], setup[0], setup[0]}, - WantPartitions: []partitionShape{{setup[0], 3}}, - WantError: nil, - }, - { - Desc: "5 packets | same DevAddr & various AppEUI -> 2 partitions | 3 packets & 2 packets", - PacketsShape: []handlerEntry{setup[0], setup[0], setup[0], setup[3], setup[3]}, - WantPartitions: []partitionShape{{setup[0], 3}, {setup[3], 2}}, - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - db := genFilledHandlerStorage(setup) - packets := genPacketsFromHandlerEntries(test.PacketsShape) - - // Operate - partitions, err := db.Partition(packets...) - - // Check - checkErrors(t, test.WantError, err) - checkPartitions(t, test.WantPartitions, partitions) - if err := db.Close(); err != nil { - panic(err) - } - } -} - -type partitionShape struct { - handlerEntry - PacketNb int -} - -// ----- BUILD utilities - -func genFilledHandlerStorage(setup []handlerEntry) HandlerStorage { - db, err := NewHandlerStorage() - if err != nil { - panic(err) - } - - if err := db.Reset(); err != nil { - panic(err) - } - - for _, entry := range setup { - if err := db.Store(entry.DevAddr, entry); err != nil { - panic(err) - } - } - - return db -} - -func genPacketsFromHandlerEntries(shapes []handlerEntry) []core.Packet { - var packets []core.Packet - for _, entry := range shapes { - - // Build the macPayload - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{DevAddr: entry.DevAddr} - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: []byte(time.Now().String()), - }} - macPayload.FPort = uint8(entry.DevAddr[0]) - key := entry.AppSKey - if macPayload.FPort == 0 { - key = entry.NwkSKey - } - if err := macPayload.EncryptFRMPayload(key); err != nil { - panic(err) - } - - // Build the physicalPayload - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - if err := phyPayload.SetMIC(entry.NwkSKey); err != nil { - panic(err) - } - - // Finally build the packet - packets = append(packets, core.Packet{ - Metadata: core.Metadata{ - Rssi: pointer.Int(-20), - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("Lora"), - }, - Payload: phyPayload, - }) - } - return packets -} - -// ----- CHECK utilities -func checkErrors(t *testing.T, want *string, got error) { - if want == nil && got == nil || got.(errors.Failure).Nature == *want { - Ok(t, "Check errors") - return - } - - Ko(t, "Expected error to be %s but got %v", want, got) -} - -func checkPartitions(t *testing.T, want []partitionShape, got []handlerPartition) { - if len(want) != len(got) { - Ko(t, "Expected %d partitions, but got %d", len(want), len(got)) - return - } - -browseGot: - for _, gotPartition := range got { - for _, wantPartition := range want { // Find right wanted partition - if reflect.DeepEqual(wantPartition.handlerEntry, gotPartition.handlerEntry) { - if len(gotPartition.Packets) == wantPartition.PacketNb { - continue browseGot - } - Ko(t, "Partition don't match expectations.\nWant: %v\nGot: %v", wantPartition, gotPartition) - return - } - } - Ko(t, "Got a partition that wasn't expected: %v", gotPartition) - return - } - Ok(t, "Check partitions") -} diff --git a/core/components/handler_test.go b/core/components/handler_test.go deleted file mode 100644 index 44f2c5cdd..000000000 --- a/core/components/handler_test.go +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestHandleUp(t *testing.T) { - devices := []device{ - { - DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - }, - { - DevAddr: [4]byte{0, 0, 0, 2}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - }, - { - DevAddr: [4]byte{0, 0, 0, 3}, - AppSKey: [16]byte{1, 2, 3, 14, 42, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - NwkSKey: [16]byte{1, 2, 3, 42, 14, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - }, - { - DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, - }, - { - DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 37, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 12, 6, 7, 8, 9, 8, 8, 8, 8, 0xaa, 0xbb}, - }, - } - - applications := map[lorawan.EUI64]application{ - [8]byte{1, 2, 3, 4, 5, 6, 7, 8}: { - Devices: []device{devices[0], devices[1]}, - Registered: true, - }, - [8]byte{0, 9, 8, 7, 6, 5, 4, 3}: { - Devices: []device{devices[2], devices[3]}, - Registered: true, - }, - [8]byte{14, 14, 14, 14, 14, 14, 14, 14}: { - Devices: []device{devices[4]}, - Registered: false, - }, - } - - packets := []packetShape{ - { - Device: devices[0], - Data: "Packet 1 / Dev 1234 / App 12345678", - }, - { - Device: devices[0], - Data: "Packet 2 / Dev 1234 / App 12345678", - }, - { - Device: devices[1], - Data: "Packet 1 / Dev 0002 / App 12345678", - }, - { - Device: devices[2], - Data: "Packet 1 / Dev 0003 / App 09876543", - }, - { - Device: devices[3], - Data: "Packet 1 / Dev 1234 / App 09876543", - }, - { - Device: devices[4], - Data: "Packet 1 / Dev 1234 / App 1414141414141414", - }, - } - - tests := []struct { - Desc string - Schedule []event - WantNbAck int - WantNbNack int - WantPackets map[[12]byte][]string - WantErrors []string - }{ - { - Desc: "1 packet", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - }, - WantNbAck: 1, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: {"Packet 1 / Dev 1234 / App 12345678"}, - }, - }, - { - Desc: "2 packets | same device | same payload | within time frame", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - event{time.Millisecond * 100, packets[0], nil}, - }, - WantNbAck: 2, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ - packets[0].Data, - }, - }, - }, - { - Desc: "2 packets | same device | same payload | in 2 time frames", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - event{time.Millisecond * 750, packets[0], nil}, - }, - WantNbAck: 1, - WantNbNack: 1, - WantErrors: []string{ErrFailedOperation}, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ - packets[0].Data, - }, - }, - }, - { - Desc: "2 packets | same device | different payload | within time frame", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - event{time.Millisecond * 100, packets[1], nil}, - }, - WantNbAck: 2, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ - packets[0].Data, - packets[1].Data, - }, - }, - }, - { - Desc: "3 packets | different device | same app | resp same payloads | within time frame", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - event{time.Millisecond * 50, packets[0], nil}, - event{time.Millisecond * 100, packets[2], nil}, - }, - WantNbAck: 3, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ - packets[0].Data, - }, - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 2}: []string{ - packets[2].Data, - }, - }, - }, - { - Desc: "3 packets | different device | different app | resp same payloads | within time frame", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - event{time.Millisecond * 50, packets[2], nil}, - event{time.Millisecond * 100, packets[3], nil}, - }, - WantNbAck: 3, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ - packets[0].Data, - }, - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 2}: []string{ - packets[2].Data, - }, - [12]byte{0, 9, 8, 7, 6, 5, 4, 3, 0, 0, 0, 3}: []string{ - packets[3].Data, - }, - }, - }, - { - Desc: "3 packets | different device | different app | resp same payloads | within time frame | dev address conflict", - Schedule: []event{ - event{time.Millisecond * 25, packets[0], nil}, - event{time.Millisecond * 50, packets[2], nil}, - event{time.Millisecond * 100, packets[4], nil}, - }, - WantNbAck: 3, - WantPackets: map[[12]byte][]string{ - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4}: []string{ - packets[0].Data, - }, - [12]byte{1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 0, 2}: []string{ - packets[2].Data, - }, - [12]byte{0, 9, 8, 7, 6, 5, 4, 3, 1, 2, 3, 4}: []string{ - packets[4].Data, - }, - }, - }, - { - Desc: "1 packet | unknown application", - Schedule: []event{ - event{time.Millisecond * 25, packets[5], nil}, - }, - WantErrors: []string{ErrWrongBehavior}, - WantNbNack: 1, - WantPackets: map[[12]byte][]string{}, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - handler := genNewHandler(t, applications) - genPacketsFromSchedule(&test.Schedule) - chans := genComChannels("error", "ack", "nack", "packet") - - // Operate - startSchedule(test.Schedule, handler, chans) - - // Check - go func() { - <-time.After(time.Second) - for _, ch := range chans { - close(ch) - } - }() - checkChErrors(t, test.WantErrors, chans["error"]) - checkAcks(t, test.WantNbAck, chans["ack"], "ack") - checkAcks(t, test.WantNbNack, chans["nack"], "nack") - checkPackets(t, test.WantPackets, chans["packet"]) - - if err := handler.db.Close(); err != nil { - panic(err) - } - } -} - -type event struct { - Delay time.Duration - Shape packetShape - Packet *core.Packet -} - -type device struct { - DevAddr lorawan.DevAddr - AppSKey lorawan.AES128Key - NwkSKey lorawan.AES128Key -} - -type packetShape struct { - Device device - Data string -} - -type application struct { - Devices []device - Registered bool -} - -func genPacketsFromSchedule(s *[]event) { - for i, entry := range *s { - entry.Packet = new(core.Packet) - *entry.Packet = genPacketFromShape(entry.Shape) - (*s)[i] = entry - } -} - -func genPacketFromShape(shape packetShape) core.Packet { - // Build the macPayload - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{DevAddr: shape.Device.DevAddr} - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: []byte(shape.Data), - }} - macPayload.FPort = uint8(1) - if err := macPayload.EncryptFRMPayload(shape.Device.AppSKey); err != nil { - panic(err) - } - - // Build the physicalPayload - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - if err := phyPayload.SetMIC(shape.Device.NwkSKey); err != nil { - panic(err) - } - return core.Packet{ - Payload: phyPayload, - Metadata: core.Metadata{}, - } -} - -func genNewHandler(t *testing.T, applications map[lorawan.EUI64]application) *Handler { - ctx := GetLogger(t, "Handler") - - db, err := NewHandlerStorage() - if err != nil { - panic(err) - } - - if err := db.Reset(); err != nil { - panic(err) - } - - handler := NewHandler(db, ctx) - if err != nil { - panic(err) - } - - for appEUI, app := range applications { - if !app.Registered { - continue - } - for _, device := range app.Devices { - handler.Register( - core.Registration{ - DevAddr: device.DevAddr, - Recipient: core.Recipient{ - Address: device.DevAddr, - Id: appEUI, - }, - Options: struct { - AppSKey lorawan.AES128Key - NwkSKey lorawan.AES128Key - }{ - AppSKey: device.AppSKey, - NwkSKey: device.NwkSKey, - }, - }, - voidAckNacker{}, - ) - } - } - return handler -} - -type voidAckNacker struct{} - -func (v voidAckNacker) Ack(packets *core.Packet) error { - return nil -} -func (v voidAckNacker) Nack() error { - return nil -} - -func genComChannels(names ...string) map[string]chan interface{} { - chans := make(map[string]chan interface{}) - for _, name := range names { - chans[name] = make(chan interface{}, 50) - } - return chans -} - -func startSchedule(s []event, handler *Handler, chans map[string]chan interface{}) { - mockAn := chanAckNacker{AckChan: chans["ack"], NackChan: chans["nack"]} - mockAdapter := chanAdapter{PktChan: chans["packet"]} - - for _, ev := range s { - <-time.After(ev.Delay) - go func(ev event) { - err := handler.HandleUp(*ev.Packet, mockAn, mockAdapter) - if err != nil { - chans["error"] <- err - } - }(ev) - } -} - -type chanAckNacker struct { - AckChan chan interface{} - NackChan chan interface{} -} - -func (an chanAckNacker) Ack(packets *core.Packet) error { - an.AckChan <- true - return nil -} - -func (an chanAckNacker) Nack() error { - an.NackChan <- true - return nil -} - -type chanAdapter struct { - PktChan chan interface{} -} - -func (a chanAdapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - a.PktChan <- struct { - Packet core.Packet - Recipient []core.Recipient - }{ - Packet: p, - Recipient: r, - } - return core.Packet{}, nil -} - -func (a chanAdapter) Next() (core.Packet, core.AckNacker, error) { - panic("Not Expected") -} - -func (a chanAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { - panic("Not Expected") -} - -func checkChErrors(t *testing.T, want []string, got chan interface{}) { - nb := 0 -outer: - for gotErr := range got { - for _, wantErr := range want { - if got != nil && wantErr == gotErr.(errors.Failure).Nature { - nb += 1 - continue outer - } - } - Ko(t, "Got error [%v] but was only expecting: [%v]", gotErr, want) - return - } - if nb != len(want) { - Ko(t, "Expected %d error(s) but got only %d", len(want), nb) - return - } - Ok(t, "Check errors") -} - -func checkAcks(t *testing.T, want int, got chan interface{}, kind string) { - nb := 0 - for { - a, ok := <-got - if !ok && a == nil { - break - } - nb += 1 - } - - if nb != want { - Ko(t, "Expected %d %s(s) but got %d", want, kind, nb) - return - } - Ok(t, fmt.Sprintf("Check %s", kind)) -} - -func checkPackets(t *testing.T, want map[[12]byte][]string, got chan interface{}) { - nb := 0 -outer: - for x := range got { - msg := x.(struct { - Packet core.Packet - Recipient []core.Recipient - }) - - if len(msg.Recipient) != 1 { - Ko(t, "Expected exactly one recipient but got %d", len(msg.Recipient)) - return - } - - appEUI := msg.Recipient[0].Id.(lorawan.EUI64) - devAddr, err := msg.Packet.DevAddr() - if err != nil { - Ko(t, "Unexpected error: %v", err) - return - } - - var id [12]byte - copy(id[:8], appEUI[:]) - copy(id[8:], devAddr[:]) - - wantData, ok := want[id] - if !ok { - Ko(t, "Received unexpected packet for app %v and from node %v", appEUI, devAddr) - return - } - - macPayload := msg.Packet.Payload.MACPayload.(*lorawan.MACPayload) - if len(macPayload.FRMPayload) != 1 { - Ko(t, "Invalid macpayload in received packet from node %v", devAddr) - return - } - - gotData := string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes) - - for _, want := range wantData { - if want == gotData { - nb += 1 - continue outer - } - } - - Ko(t, "Received data don't match expectations.\nWant: %v\nGot: %s", wantData, gotData) - return - } - - if nb != len(want) { - Ko(t, "Handler sent %d packet(s) whereas %d were/was expected", nb, len(want)) - return - } - - Ok(t, "Check packets") -} diff --git a/core/components/internal_mechanisms_test.go b/core/components/internal_mechanisms_test.go deleted file mode 100644 index fcdce684d..000000000 --- a/core/components/internal_mechanisms_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "reflect" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// This illustrates the mechanism used by the handler to bufferize connections -// for a while before processing them. -// This is an analogy to what's done in the handler where -// bundleId -> _bundleId -// uplinkBundle -> _uplinkBundle - -func TestHandlerBuffering(t *testing.T) { - // Describe - Desc(t, "Generate fake bundle traffic") - - // Build - bundles := make(chan []_uplinkBundle) - set := make(chan _uplinkBundle) - received := new([][]_uplinkBundle) // There's a datarace here, but that's okay, chill. - - go _manageBuffers(bundles, set) - go _consumeBundles(bundles, received) - - b1_1 := _uplinkBundle{_bundleId(1), "bundle1_1"} - b1_2 := _uplinkBundle{_bundleId(1), "bundle1_2"} - b1_3 := _uplinkBundle{_bundleId(1), "bundle1_3"} - b1_4 := _uplinkBundle{_bundleId(1), "bundle1_4"} - - b2_1 := _uplinkBundle{_bundleId(2), "bundle2_1"} - b2_2 := _uplinkBundle{_bundleId(2), "bundle2_2"} - b2_3 := _uplinkBundle{_bundleId(2), "bundle2_3"} - - // Operate - // Expecting [ b1_1, b1_2, b1_3 ] - go func() { - set <- b1_1 - <-time.After(BUFFER_DELAY / 3) - set <- b1_2 - <-time.After(BUFFER_DELAY / 3) - set <- b1_3 - }() - - // Expecting [ b2_1, b2_2, b2_3 ] - go func() { - set <- b2_1 - <-time.After(BUFFER_DELAY / 4) - set <- b2_2 - <-time.After(BUFFER_DELAY / 4) - set <- b2_3 - }() - - // Expecting [ b1_4 ] - go func() { - <-time.After(BUFFER_DELAY * 2) - set <- b1_4 - }() - - // Check - <-time.After(BUFFER_DELAY * 4) - if len(*received) != 3 { - Ko(t, "Expected 3 bundles to have been received but got %d", len(*received)) - return - } - Ok(t, "Check bundles number") - - for _, bundles := range *received { - if reflect.DeepEqual(bundles, []_uplinkBundle{b1_1, b1_2, b1_3}) { - continue - } - if reflect.DeepEqual(bundles, []_uplinkBundle{b1_4}) { - continue - } - if reflect.DeepEqual(bundles, []_uplinkBundle{b2_1, b2_2, b2_3}) { - continue - } - Ko(t, "Collected bundles in a non-expected way: %v", bundles) - return - } - Ok(t, "Check bundles shapes") -} - -type _bundleId int -type _uplinkBundle struct { - id _bundleId - data string -} - -// _setAlarm ~> setAlarm -func _setAlarm(alarm chan<- _bundleId, id _bundleId, delay time.Duration) { - <-time.After(delay) - alarm <- id -} - -// _manageBuffers ~> manageBuffers -func _manageBuffers(bundles chan<- []_uplinkBundle, set <-chan _uplinkBundle) { - buffers := make(map[_bundleId][]_uplinkBundle) - alarm := make(chan _bundleId) - - for { - select { - case id := <-alarm: - b := buffers[id] - delete(buffers, id) - go func(b []_uplinkBundle) { bundles <- b }(b) - case bundle := <-set: - b := append(buffers[bundle.id], bundle) - if len(b) == 1 { - go _setAlarm(alarm, bundle.id, BUFFER_DELAY) - } - buffers[bundle.id] = b - } - } -} - -// _consumeBundles, just for the test -func _consumeBundles(bundles <-chan []_uplinkBundle, received *[][]_uplinkBundle) { - for b := range bundles { - *received = append(*received, b) - } -} diff --git a/core/components/router.go b/core/components/router.go deleted file mode 100644 index 1b731bfc5..000000000 --- a/core/components/router.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" -) - -type Router struct { - db RouterStorage // Local storage that maps end-device addresses to broker addresses - ctx log.Interface // Just a logger -} - -// NewRouter constructs a Router and setup its internal structure -func NewRouter(db RouterStorage, ctx log.Interface) *Router { - return &Router{ - db: db, - ctx: ctx, - } -} - -// Register implements the core.Component interface -func (r *Router) Register(reg core.Registration, an core.AckNacker) error { - stats.MarkMeter("router.registration.in") - r.ctx.Debug("Handling registration") - - entry := routerEntry{Recipient: reg.Recipient} - if err := r.db.Store(reg.DevAddr, entry); err != nil { - stats.MarkMeter("router.registration.failed") - an.Nack() - return err - } - stats.MarkMeter("router.registration.ok") - return an.Ack(nil) -} - -// HandleDown implements the core.Component interface -func (r *Router) HandleDown(p core.Packet, an core.AckNacker, downAdapter core.Adapter) error { - return fmt.Errorf("TODO. Not Implemented") -} - -// HandleUp implements the core.Component interface -func (r *Router) HandleUp(p core.Packet, an core.AckNacker, upAdapter core.Adapter) error { - stats.MarkMeter("router.uplink.in") - r.ctx.Debug("Handling uplink packet") - - var err error - - // Lookup for an existing broker - devAddr, err := p.DevAddr() - if err != nil { - stats.MarkMeter("broker.uplink.invalid") - r.ctx.Warn("Invalid uplink packet") - an.Nack() - return err - } - - entry, err := r.db.Lookup(devAddr) - if err != nil && err.(errors.Failure).Nature != ErrWrongBehavior { - r.ctx.Warn("Database lookup failed") - an.Nack() - return err - } - - var response core.Packet - if err == nil { - response, err = upAdapter.Send(p, entry.Recipient) - } else { - response, err = upAdapter.Send(p) - } - - if err != nil { - stats.MarkMeter("router.uplink.bad_broker_response") - r.ctx.WithError(err).Warn("Invalid response from Broker") - an.Nack() - return err - } - - stats.MarkMeter("router.uplink.ok") - return an.Ack(&response) -} diff --git a/refactor/components/router/router.go b/core/components/router/router.go similarity index 98% rename from refactor/components/router/router.go rename to core/components/router/router.go index 4aa76f2bc..e1d3bc84f 100644 --- a/refactor/components/router/router.go +++ b/core/components/router/router.go @@ -4,7 +4,7 @@ package router import ( - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" diff --git a/refactor/components/router/storage.go b/core/components/router/storage.go similarity index 98% rename from refactor/components/router/storage.go rename to core/components/router/storage.go index 135a89f52..22ddf9f3d 100644 --- a/refactor/components/router/storage.go +++ b/core/components/router/storage.go @@ -7,7 +7,7 @@ import ( "sync" "time" - . "github.com/TheThingsNetwork/ttn/refactor" + . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" diff --git a/core/components/router_storage.go b/core/components/router_storage.go deleted file mode 100644 index 801dac38f..000000000 --- a/core/components/router_storage.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -// RouterStorage manages the internal persistent state of a router -type RouterStorage interface { - // Close properly ends the connection to the internal database - Close() error - - // Lookup retrieves all entries associated to a given device - Lookup(devAddr lorawan.DevAddr) (routerEntry, error) - - // Reset removes all entries stored in the storage - Reset() error - - // Store creates a new entry and add it to the other entries (if any) - Store(devAddr lorawan.DevAddr, entry routerEntry) error -} - -type routerBoltStorage struct { - *bolt.DB - sync.Mutex // Guards the db storage to make Lookup and Store atomic actions - expiryDelay time.Duration // Entry lifetime delay -} - -// routerEntry stores all information that link a device to a broker -type routerEntry struct { - Recipient core.Recipient // Recipient associated to a device. - until time.Time // The moment until when the entry is still valid -} - -// NewRouterStorage creates a new router bolt in-memory storage -func NewRouterStorage(delay time.Duration) (RouterStorage, error) { - db, err := bolt.Open("router_storage.db", 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, errors.New(ErrFailedOperation, err) - } - - if err := initDB(db, "brokers"); err != nil { - return nil, err - } - - return &routerBoltStorage{DB: db, expiryDelay: delay}, nil -} - -// Lookup implements the RouterStorage interface -func (s *routerBoltStorage) Lookup(devAddr lorawan.DevAddr) (routerEntry, error) { - return s.lookup(devAddr, true) -} - -// lookup offers an indirection in order to avoid taking a lock if not needed -func (s *routerBoltStorage) lookup(devAddr lorawan.DevAddr, lock bool) (routerEntry, error) { - // NOTE This works under the assumption that a read or write lock is already hold by the callee (e.g. Store) - if lock { - s.Lock() - defer s.Unlock() - } - - entry, err := lookup(s.DB, "brokers", devAddr, &routerEntry{}) - if err != nil { - return routerEntry{}, err - } - entries := entry.([]routerEntry) - - if len(entries) != 1 { - if err := flush(s.DB, "brokers", devAddr); err != nil { - return routerEntry{}, err - } - return routerEntry{}, errors.New(ErrWrongBehavior, fmt.Sprintf("Not Found %+v", devAddr)) - } - - rentry := entries[0] - - if s.expiryDelay != 0 && rentry.until.Before(time.Now()) { - if err := flush(s.DB, "brokers", devAddr); err != nil { - return routerEntry{}, err - } - return routerEntry{}, errors.New(ErrWrongBehavior, fmt.Sprintf("Not Found %+v", devAddr)) - } - - return rentry, nil -} - -// Store implements the RouterStorage interface -func (s *routerBoltStorage) Store(devAddr lorawan.DevAddr, entry routerEntry) error { - s.Lock() - defer s.Unlock() - _, err := s.lookup(devAddr, false) - if err == nil || err != nil && err.(errors.Failure).Nature != ErrWrongBehavior { - return errors.New(ErrFailedOperation, "Already exists") - } - entry.until = time.Now().Add(s.expiryDelay) - return store(s.DB, "brokers", devAddr, &entry) -} - -// Close implements the RouterStorage interface -func (s *routerBoltStorage) Close() error { - s.Lock() - defer s.Unlock() - return s.DB.Close() -} - -// Reset implements the RouterStorage interface -func (s *routerBoltStorage) Reset() error { - s.Lock() - defer s.Unlock() - return resetDB(s.DB, "brokers") -} - -// MarshalBinary implements the entryStorage interface -func (entry routerEntry) MarshalBinary() ([]byte, error) { - rawTime, err := entry.until.MarshalBinary() - if err != nil { - return nil, err - } - rawId := []byte(entry.Recipient.Id.(string)) - rawAddress := []byte(entry.Recipient.Address.(string)) - - w := newEntryReadWriter(nil) - w.Write(rawId) - w.Write(rawAddress) - w.Write(rawTime) - return w.Bytes() -} - -// UnmarshalBinary implements the entryStorage interface -func (entry *routerEntry) UnmarshalBinary(data []byte) error { - if entry == nil || len(data) < 1 { - return errors.New(ErrInvalidStructure, "invalid router entry") - } - r := newEntryReadWriter(data) - - var id, address string - r.Read(func(data []byte) { id = string(data) }) - r.Read(func(data []byte) { address = string(data) }) - entry.Recipient = core.Recipient{ - Id: id, - Address: address, - } - var err error - r.Read(func(data []byte) { - entry.until = time.Time{} - err = entry.until.UnmarshalBinary(data) - }) - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - return r.Err() -} diff --git a/core/components/router_storage_test.go b/core/components/router_storage_test.go deleted file mode 100644 index d3f86ea1e..000000000 --- a/core/components/router_storage_test.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -type routerEntryShape struct { - routerEntry - DevAddr lorawan.DevAddr -} - -func TestStorageExpiration(t *testing.T) { - devices := []lorawan.DevAddr{ - lorawan.DevAddr([4]byte{0, 0, 0, 1}), - lorawan.DevAddr([4]byte{14, 15, 8, 42}), - lorawan.DevAddr([4]byte{14, 15, 8, 79}), - } - - entries := []routerEntry{ - {Recipient: core.Recipient{Address: "MyAddress1", Id: ""}}, - {Recipient: core.Recipient{Address: "AnotherAddress", Id: ""}}, - } - - tests := []struct { - Desc string - ExpiryDelay time.Duration - ExistingEntries []routerEntryShape - WaitDelayS time.Duration - Store *routerEntryShape - WaitDelayL time.Duration - Lookup lorawan.DevAddr - WantEntry *routerEntry - WantError []string - }{ - { - Desc: "No entry, Lookup address", - ExpiryDelay: time.Minute, - ExistingEntries: nil, - Lookup: devices[0], - Store: nil, - WantEntry: nil, - WantError: []string{ErrWrongBehavior}, - }, - { - Desc: "No entry, Store and Lookup same", - ExpiryDelay: time.Minute, - ExistingEntries: nil, - Store: &routerEntryShape{entries[0], devices[0]}, - Lookup: devices[0], - WantEntry: &entries[0], - WantError: nil, - }, - { - Desc: "No entry, store, wait expiry, and lookup same", - ExpiryDelay: time.Millisecond, - ExistingEntries: nil, - Store: &routerEntryShape{entries[0], devices[0]}, - WaitDelayL: time.Millisecond * 250, - Lookup: devices[0], - WantEntry: nil, - WantError: []string{ErrWrongBehavior}, - }, - { - Desc: "One entry, store same, lookup same", - ExpiryDelay: time.Minute, - ExistingEntries: []routerEntryShape{ - {entries[0], devices[2]}, - }, - Store: &routerEntryShape{entries[1], devices[2]}, - Lookup: devices[2], - WantEntry: &entries[0], - WantError: []string{ErrFailedOperation}, - }, - { - Desc: "One entry, store different, lookup newly stored", - ExpiryDelay: time.Minute, - ExistingEntries: []routerEntryShape{ - {entries[0], devices[0]}, - }, - Store: &routerEntryShape{entries[1], devices[1]}, - Lookup: devices[1], - WantEntry: &entries[1], - WantError: nil, - }, - { - Desc: "One entry, store different, lookup first one", - ExpiryDelay: time.Minute, - ExistingEntries: []routerEntryShape{ - {entries[0], devices[0]}, - }, - Store: &routerEntryShape{entries[1], devices[1]}, - Lookup: devices[0], - WantEntry: &entries[0], - }, - { - Desc: "One entry, store different, wait delay, lookup first one", - ExpiryDelay: time.Millisecond, - ExistingEntries: []routerEntryShape{ - {entries[0], devices[0]}, - }, - Store: &routerEntryShape{entries[1], devices[1]}, - WaitDelayL: time.Millisecond, - Lookup: devices[0], - WantEntry: nil, - WantError: []string{ErrWrongBehavior}, - }, - { - Desc: "One entry, wait delay, store same, lookup same", - ExpiryDelay: time.Millisecond * 100, - ExistingEntries: []routerEntryShape{ - {entries[0], devices[0]}, - }, - WaitDelayS: time.Millisecond * 200, - Store: &routerEntryShape{entries[1], devices[0]}, - Lookup: devices[0], - WantEntry: &entries[1], - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - db := genFilledRouterStorage(test.ExistingEntries, test.ExpiryDelay) - cherr := make(chan interface{}, 2) - // Operate - storeRouter(db, test.WaitDelayS, test.Store, cherr) - got := lookupRouter(db, test.WaitDelayL, test.Lookup, cherr) - - // Check - go func() { - time.After(time.Millisecond * 250) - close(cherr) - }() - checkChErrors(t, test.WantError, cherr) - checkRouterEntries(t, test.WantEntry, got) - - // Clean - db.Close() - } -} - -// ----- BUILD utilities -func genFilledRouterStorage(setup []routerEntryShape, expiryDelay time.Duration) RouterStorage { - db, err := NewRouterStorage(expiryDelay) - if err != nil { - panic(err) - } - - if err := db.Reset(); err != nil { - panic(err) - } - - for _, shape := range setup { - if err := db.Store(shape.DevAddr, shape.routerEntry); err != nil { - panic(err) - } - } - - return db -} - -// ----- OPERATE utilities -func storeRouter(db RouterStorage, delay time.Duration, entry *routerEntryShape, cherr chan interface{}) { - if delay != 0 { - <-time.After(delay) - } - if entry == nil { - return - } - - if err := db.Store(entry.DevAddr, entry.routerEntry); err != nil { - cherr <- err - } -} - -func lookupRouter(db RouterStorage, delay time.Duration, devAddr lorawan.DevAddr, cherr chan interface{}) routerEntry { - if delay != 0 { - <-time.After(delay) - } - entry, err := db.Lookup(devAddr) - if err != nil { - cherr <- err - } - return entry -} - -// ----- CHECK utilities -func checkRouterEntries(t *testing.T, want *routerEntry, got routerEntry) { - if want != nil { - addr, ok := got.Recipient.Address.(string) - - if !ok { - Ko(t, "Unexpected recipient address format: %+v", got.Recipient.Address) - return - } - - if addr != want.Recipient.Address.(string) { - Ko(t, `The retrieved address "%s" does not match expected "%s"`, addr, want.Recipient.Address) - return - } - } else { - if !reflect.DeepEqual(routerEntry{}, got) { - Ko(t, "No entry was exected but got: %v", got) - return - } - } - Ok(t, "Check router entries") -} diff --git a/core/components/router_test.go b/core/components/router_test.go deleted file mode 100644 index 3410629b9..000000000 --- a/core/components/router_test.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestRouterHandleUp(t *testing.T) { - devices := []device{ - { - DevAddr: [4]byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}, - }, - { - DevAddr: [4]byte{0, 0, 0, 2}, - AppSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - NwkSKey: [16]byte{1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 8, 8, 8, 8, 8, 8}, - }, - } - - recipients := []core.Recipient{ - {Address: "Recipient1", Id: ""}, - {Address: "Recipient2", Id: ""}, - } - - tests := []struct { - Desc string - KnownRecipients map[[4]byte]core.Recipient - Packet packetShape - WantRecipients []core.Recipient - WantError *string - }{ - { - Desc: "0 known | Send #0", - Packet: packetShape{ - Device: devices[0], - Data: "MyData", - }, - WantRecipients: nil, - WantError: nil, - }, - { - Desc: "Know #0 | Send #0", - KnownRecipients: map[[4]byte]core.Recipient{ - devices[0].DevAddr: recipients[0], - }, - Packet: packetShape{ - Device: devices[0], - Data: "MyData", - }, - WantRecipients: []core.Recipient{recipients[0]}, - WantError: nil, - }, - { - Desc: "Know #1 | Send #0", - KnownRecipients: map[[4]byte]core.Recipient{ - devices[1].DevAddr: recipients[0], - }, - Packet: packetShape{ - Device: devices[0], - Data: "MyData", - }, - WantRecipients: nil, - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - router := genNewRouter(t, test.KnownRecipients) - packet := genPacketFromShape(test.Packet) - - // Operate - recipients, err := handleRouterUp(router, packet) - - // Check - checkErrors(t, test.WantError, err) - checkRecipients(t, test.WantRecipients, recipients) - - if err := router.db.Close(); err != nil { - panic(err) - } - } -} - -type routerRecipient struct { - Address string -} - -// ----- BUILD utilities -func genNewRouter(t *testing.T, knownRecipients map[[4]byte]core.Recipient) *Router { - ctx := GetLogger(t, "Router") - - db, err := NewRouterStorage(time.Hour * 8) - if err != nil { - panic(err) - } - - if err := db.Reset(); err != nil { - panic(err) - } - - router := NewRouter(db, ctx) - if err != nil { - panic(err) - } - - for devAddr, recipient := range knownRecipients { - err := router.Register(core.Registration{ - DevAddr: lorawan.DevAddr(devAddr), - Recipient: recipient, - }, voidAckNacker{}) - if err != nil { - panic(err) - } - } - - return router -} - -// ----- OPERATE utilities -func handleRouterUp(router core.Router, packet core.Packet) ([]core.Recipient, error) { - adapter := &routerAdapter{} - err := router.HandleUp(packet, voidAckNacker{}, adapter) - return adapter.Recipients, err -} - -type routerAdapter struct { - Recipients []core.Recipient -} - -func (a *routerAdapter) Send(p core.Packet, r ...core.Recipient) (core.Packet, error) { - a.Recipients = r - return core.Packet{}, nil -} - -func (a *routerAdapter) Next() (core.Packet, core.AckNacker, error) { - panic("Unexpected call to Next()") -} - -func (a *routerAdapter) NextRegistration() (core.Registration, core.AckNacker, error) { - panic("Unexpected call to NextRegistration") -} - -// ----- Check utilities -func checkRecipients(t *testing.T, want []core.Recipient, got []core.Recipient) { - if !reflect.DeepEqual(want, got) { - Ko(t, "Contacted recipients don't match expectations.\nWant: %v\nGot: %v", want, got) - return - } - Ok(t, "Check recipients") -} diff --git a/core/components/storage.go b/core/components/storage.go deleted file mode 100644 index e6bcc3556..000000000 --- a/core/components/storage.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package components - -import ( - "fmt" - "io" - "reflect" - - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/boltdb/bolt" - "github.com/brocaar/lorawan" -) - -// storageEntry offers a friendly interface on which the storage will operate. -// Basically, a storageEntry is nothing more than a binary marshaller/unmarshaller. -type storageEntry interface { - MarshalBinary() ([]byte, error) // implements binary.Marshaller interface - UnmarshalBinary(data []byte) error // implements binary.Unmarshaller interface -} - -// initDB initializes the given bolt database by creating (if not already exists) an empty bucket -func initDB(db *bolt.DB, bucketName string) error { - return db.Update(func(tx *bolt.Tx) error { - _, err := tx.CreateBucketIfNotExists([]byte(bucketName)) - return err - }) -} - -// store put a new entry in the given bolt database. It adds the entry to an existing set or create -// a new set containing one element. -func store(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, entry storageEntry) error { - marshalled, err := entry.MarshalBinary() - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - - err = db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") - } - w := newEntryReadWriter(bucket.Get(devAddr[:])) - w.Write(marshalled) - data, err := w.Bytes() - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - if err := bucket.Put(devAddr[:], data); err != nil { - return errors.New(ErrFailedOperation, err) - } - return nil - }) - - return err -} - -// lookup retrieve a set of entry from a given bolt database. -// -// The shape is used as a template for retrieving and creating the data. All entries extracted from -// the database will be interpreted as instance of shape and the return result will be a slice of -// the same type of shape. -func lookup(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr, shape storageEntry) (interface{}, error) { - // First, lookup the raw entries - var rawEntry []byte - err := db.View(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") - } - rawEntry = bucket.Get(devAddr[:]) - if rawEntry == nil { - return errors.New(ErrWrongBehavior, fmt.Sprintf("Not found %+v", devAddr)) - } - return nil - }) - - if err != nil { - return nil, err - } - - // Then, interpret them as instance of 'shape' - r := newEntryReadWriter(rawEntry) - entryType := reflect.TypeOf(shape).Elem() - entries := reflect.MakeSlice(reflect.SliceOf(entryType), 0, 0) - for { - r.Read(func(data []byte) { - entry := reflect.New(entryType).Interface() - entry.(storageEntry).UnmarshalBinary(data) - entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) - }) - if err = r.Err(); err != nil { - if err == io.EOF { - break - } - return nil, errors.New(ErrFailedOperation, err) - } - } - return entries.Interface(), nil -} - -// flush empties each entry of a bucket associated to a given device -func flush(db *bolt.DB, bucketName string, devAddr lorawan.DevAddr) error { - return db.Update(func(tx *bolt.Tx) error { - bucket := tx.Bucket([]byte(bucketName)) - if bucket == nil { - return errors.New(ErrFailedOperation, "storage unreachable") - } - if err := bucket.Delete(devAddr[:]); err != nil { - return errors.New(ErrFailedOperation, err) - } - return nil - }) -} - -// resetDB resets a given bucket from a given bolt database -func resetDB(db *bolt.DB, bucketName string) error { - return db.Update(func(tx *bolt.Tx) error { - if err := tx.DeleteBucket([]byte(bucketName)); err != nil { - return errors.New(ErrFailedOperation, err) - } - if _, err := tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil { - return errors.New(ErrFailedOperation, err) - } - return nil - }) -} diff --git a/core/core.go b/core/core.go index b3c05c081..709630bc0 100644 --- a/core/core.go +++ b/core/core.go @@ -4,103 +4,31 @@ package core import ( - "time" - "github.com/brocaar/lorawan" ) -type Packet struct { - Metadata Metadata // Metadata associated to packet. That object may change over requests - Payload lorawan.PHYPayload // The actual lorawan physical payload -} - -type Recipient struct { - Address interface{} // The address of the recipient. The type depends on the context - Id interface{} // An optional ID for the recipient. The type depends on the context -} - -type Registration struct { - DevAddr lorawan.DevAddr // The device address which takes part in the registration - Recipient Recipient // The registration emitter to which it is bound - Options interface{} // Options that vary from different type of registration +type Component interface { + Register(reg Registration, an AckNacker) error + HandleUp(p []byte, an AckNacker, upAdapter Adapter) error + HandleDown(p []byte, an AckNacker, downAdapter Adapter) error } -type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) - Group []Metadata `json:"metadata,omitempty"` // Gather metadata of several packets into one metadata structure +type NetworkController interface { + HandleCommands(packet BPacket) error + UpdateFCntUp(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) + UpdateFCntDown(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) + MergeCommands(appEUI lorawan.EUI64, devEUI lorawan.EUI64, pkt BPacket) RPacket } -// AckNacker are mainly created by adapters as explicit callbacks for a given incoming request. -// -// The AckNacker encapsulates the logic which allows later component to answer to a recipient -// without even knowing about the protocol being used. This is only possible because all messages -// transmitted between component are relatively isomorph (to what one calls a Packet). type AckNacker interface { - // Ack acknowledges and terminates a connection by sending 0 or 1 packet as an answer - // (depending on the component). - // - // Incidentally, that acknowledgement would serve as a downlink response for class A devices. - Ack(p *Packet) error - - // Nack rejects and terminates a connection. So far, there is no way to give more information - // about the reason that led to a rejection. + Ack(p Packet) error Nack() error } -type Component interface { - // Register explicitely requires a component to create a new entry in its registry which links a - // recipient to a device. - Register(reg Registration, an AckNacker) error - - // HandleUp informs the component of an incoming uplink request. - // - // The component is thereby in charge of calling the given AckNacker accordingly to the - // processing. - HandleUp(p Packet, an AckNacker, upAdapter Adapter) error - - // HandleDown informs the component of a spontaneous downlink request. - // - // This should be mistaken with a typical downlink message as defined for Class A devices. - // HandleDown should handle downlink request made without any uplink context (which basically - // means we won't probably use it before a while ~> Class B and Class C, or probably only for - // handlers). - HandleDown(p Packet, an AckNacker, downAdapter Adapter) error -} - type Adapter interface { - // Send forwards a given packet to one or more recipients. It returns only when the request has - // been processed by the recipient and normally respond with a packet coming from the recipient. - Send(p Packet, r ...Recipient) (Packet, error) - - // Next pulls a new packet received by the adapter. It blocks until a new packet is received. - // - // The adapter is in charge of creating a new AckNacker to reply to the recipient such that the - // communication and the request are still transparent for the component handling it. - Next() (Packet, AckNacker, error) - - // NextRegistration pulls a new registration request received by the adapter. It blocks until a - // new registration demand is received. It follows a process similar to Next() + Send(p Packet, r ...Recipient) ([]byte, error) + //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) + GetRecipient(raw []byte) (Recipient, error) + Next() ([]byte, AckNacker, error) NextRegistration() (Registration, AckNacker, error) } - -type Router Component -type Broker Component -type Handler Component -type NetworkController Component diff --git a/core/doc.go b/core/doc.go deleted file mode 100644 index ccb98d9d3..000000000 --- a/core/doc.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package core contains the core library of The Things Network. -// -// This library can be used to built an entire network or only a small part of it. It is mainly -// divided in three parts: -// -// Packet manipulation -// -// The core package itself defines packets as we see them in the network as well as methods to -// serialize, convert and represent them. -// -// Because packets are likely to change over requests, this package centralizes all definitions and -// information related to them and share by the whole network. Each component may take and use only -// what it needs to operate over packets. -// -// -// Adapters -// -// The subfolder adapters hold all protocol adapters one could use to make components communicate. -// Each adapter implement its own protocol based on a given transport or application layer such as -// UDP, HTTP, TCP, CoAP or any fantasy needed. -// -// -// Components -// -// In the subfolder components you may find implementation of core logic of the network. The -// communication process has been abstracted by the adapters in such a way that all components only -// care about the business logic and the packet management. -// -// Components are split in 4 categories: router, broker, handler and network controller. Refer to -// the related documentation to find more information. -package core diff --git a/core/errors/errors.go b/core/errors/errors.go deleted file mode 100644 index bd95fb8ac..000000000 --- a/core/errors/errors.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -const ( - // Unsuccessful operation due to an unexpected parameter or under-relying structure. - // Another call won't change a thing, the inputs are wrong anyway. - ErrInvalidStructure = "Invalid Structure" - - // Attempt to access an unimplemented method or an unsupported operation. Fatal. - ErrNotSupported = "Unsupported Operation" - - // The operation went well though the result is unexpected and wrong. - ErrWrongBehavior = "Unexpected Behavior" - - // Something happend during the processing. Another attempt might success. - ErrFailedOperation = "Unsuccessful Operation" -) diff --git a/core/metadata.go b/core/metadata.go index 8f4234e9d..6c4a85cc6 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -7,12 +7,36 @@ import ( "encoding/json" "time" - . "github.com/TheThingsNetwork/ttn/core/errors" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" ) +type Metadata struct { + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Duty *uint `json:"duty,omitempty"` // DutyCycle of the gateway (uint between 0 and 100) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) +} + +// TODO -> Metadata should implements a less byte-consuming MarshalBinary method MarshalJSON() + // metadata allows us to inherit Metadata in metadataProxy but only by extending the exported // attributes of Metadata such that, they are still parsed by the json.Marshaller / // json.Unmarshaller though we do not end up with a recursive hellish error. @@ -46,7 +70,7 @@ func (m Metadata) MarshalJSON() ([]byte, error) { }) if err != nil { - err = errors.New(ErrInvalidStructure, err) + err = errors.New(errors.Structural, err) } return data, err @@ -55,12 +79,12 @@ func (m Metadata) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface func (m *Metadata) UnmarshalJSON(raw []byte) error { if m == nil { - return errors.New(ErrInvalidStructure, "Cannot unmarshal nil Metadata") + return errors.New(errors.Structural, "Cannot unmarshal nil Metadata") } proxy := metadataProxy{} if err := json.Unmarshal(raw, &proxy); err != nil { - return errors.New(ErrInvalidStructure, err) + return errors.New(errors.Structural, err) } *m = Metadata(proxy.metadata) if proxy.Time != nil { diff --git a/core/metadata_test.go b/core/metadata_test.go index ab7ced219..2353bce21 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -82,7 +83,7 @@ func TestMarshaljson(t *testing.T) { for _, test := range commonTests { Desc(t, "Marshal medatadata: %s", test.Metadata.String()) raw, err := json.Marshal(test.Metadata) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkJSON(t, test.JSON, raw) } } @@ -92,7 +93,7 @@ func TestUnmarshalJSON(t *testing.T) { Desc(t, "Unmarshal json: %s", test.JSON) metadata := Metadata{} err := json.Unmarshal([]byte(test.JSON), &metadata) - checkErrors(t, test.WantError, err) + CheckErrors(t, test.WantError, err) checkMetadata(t, test.Metadata, metadata) } } diff --git a/core/packet.go b/core/packet.go deleted file mode 100644 index 9064b0af5..000000000 --- a/core/packet.go +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding/base64" - "encoding/json" - "fmt" - "reflect" - "strings" - - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/brocaar/lorawan" -) - -// DevAddr returns a lorawan device address associated to the packet if any -func (p Packet) DevAddr() (lorawan.DevAddr, error) { - if p.Payload.MACPayload == nil { - return lorawan.DevAddr{}, errors.New(ErrInvalidStructure, "MACPAyload should not be empty") - } - - macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return lorawan.DevAddr{}, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") - } - - return macpayload.FHDR.DevAddr, nil -} - -// FCnt returns the frame counter of the given packet if any -func (p Packet) Fcnt() (uint32, error) { - if p.Payload.MACPayload == nil { - return 0, errors.New(ErrInvalidStructure, "MACPayload should not be empty") - } - - macpayload, ok := p.Payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return 0, errors.New(ErrInvalidStructure, "Packet does not carry a MACPayload") - } - - return macpayload.FHDR.FCnt, nil -} - -// String returns a string representation of the packet. It implements the io.Stringer interface -func (p Packet) String() string { - str := "Packet {" - str += fmt.Sprintf("\n\t%s}", p.Metadata.String()) - str += fmt.Sprintf("\n\tPayload%+v\n}", p.Payload) - return str -} - -// ConvertRXPK create a core.Packet from a semtech.RXPK. It's an handy way to both decode the -// frame payload and retrieve associated metadata from that packet -func ConvertRXPK(p semtech.RXPK) (Packet, error) { - // First, we have to get the physical payload which is encoded in the Data field - packet := Packet{} - if p.Data == nil { - return packet, errors.New(ErrInvalidStructure, "There's no data in the packet") - } - - // RXPK Data are base64 encoded, yet without the trailing "==" if any..... - encoded := *p.Data - switch len(encoded) % 4 { - case 2: - encoded += "==" - case 3: - encoded += "=" - } - - raw, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return packet, errors.New(ErrInvalidStructure, err) - } - - payload := lorawan.NewPHYPayload(true) - if err = payload.UnmarshalBinary(raw); err != nil { - return packet, errors.New(ErrInvalidStructure, err) - } - - // Then, we interpret every other known field as a metadata and store them into an appropriate - // metadata object. - metadata := Metadata{} - rxpkValue := reflect.ValueOf(p) - rxpkStruct := rxpkValue.Type() - metas := reflect.ValueOf(&metadata).Elem() - for i := 0; i < rxpkStruct.NumField(); i += 1 { - field := rxpkStruct.Field(i).Name - if metas.FieldByName(field).CanSet() { - metas.FieldByName(field).Set(rxpkValue.Field(i)) - } - } - - // At the end, our converted packet hold the same metadata than the RXPK packet but the Data - // which as been completely transformed into a lorawan Physical Payload. - return Packet{Metadata: metadata, Payload: payload}, nil -} - -// ConvertToTXPK converts a core Packet to a semtech TXPK packet using compatible metadata. -func ConvertToTXPK(p Packet) (semtech.TXPK, error) { - // Step 1, convert the physical payload to a base64 string (without the padding) - raw, err := p.Payload.MarshalBinary() - if err != nil { - return semtech.TXPK{}, errors.New(ErrInvalidStructure, err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - txpk := semtech.TXPK{Data: pointer.String(data)} - - // Step 2, copy every compatible metadata from the packet to the TXPK packet. - // We are possibly loosing information here. - metadataValue := reflect.ValueOf(p.Metadata) - metadataStruct := metadataValue.Type() - txpkStruct := reflect.ValueOf(&txpk).Elem() - for i := 0; i < metadataStruct.NumField(); i += 1 { - field := metadataStruct.Field(i).Name - if txpkStruct.FieldByName(field).CanSet() { - txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) - } - } - - return txpk, nil -} - -// MarshalJSON implements the json.Marshaler interface -func (p Packet) MarshalJSON() ([]byte, error) { - rawMetadata, err := json.Marshal(p.Metadata) - if err != nil { - return nil, errors.New(ErrInvalidStructure, err) - } - rawPayload, err := p.Payload.MarshalBinary() - if err != nil { - return nil, errors.New(ErrInvalidStructure, err) - } - strPayload := base64.StdEncoding.EncodeToString(rawPayload) - return []byte(fmt.Sprintf(`{"payload":"%s","metadata":%s}`, strPayload, string(rawMetadata))), nil -} - -// UnmarshalJSON impements the json.Marshaler interface -func (p *Packet) UnmarshalJSON(raw []byte) error { - if p == nil { - return errors.New(ErrInvalidStructure, "Cannot unmarshal a nil packet") - } - - // The payload is a bit tricky to unmarshal as we do not know if its an uplink or downlink - // packet. Thus, we'll assume it's an uplink packet (because that's the case most of the time) - // and check whether or not the unmarshalling process was okay. - var proxy struct { - Payload string `json:"payload"` - Metadata Metadata - } - - err := json.Unmarshal(raw, &proxy) - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - - rawPayload, err := base64.StdEncoding.DecodeString(proxy.Payload) - if err != nil { - return errors.New(ErrInvalidStructure, err) - } - - payload := lorawan.NewPHYPayload(true) // true -> uplink - if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.New(ErrInvalidStructure, err) - } - - // Now, we check the nature of the decoded payload - switch payload.MHDR.MType.String() { - case "JoinAccept": - fallthrough - case "UnconfirmedDataDown": - fallthrough - case "ConfirmedDataDown": - // JoinAccept, UnconfirmedDataDown and ConfirmedDataDown are all downlink messages. - // We thus have to unmarshall properly - payload = lorawan.NewPHYPayload(false) // false -> downlink - if err := payload.UnmarshalBinary(rawPayload); err != nil { - return errors.New(ErrInvalidStructure, err) - } - case "JoinRequest": - fallthrough - case "UnconfirmedDataUp": - fallthrough - case "ConfirmedDataUp": - // JoinRequest, UnconfirmedDataUp and ConfirmedDataUp are all uplink messages. - // There's nothing to do, we've already handled them. - - case "Proprietary": - // Proprietary can be either downlink or uplink. Right now, we do not have any message of - // that type and thus, we just don't know how to handle them. Let's throw an error. - return errors.New(ErrInvalidStructure, "Unsupported MType 'Proprietary'") - } - - // Packet = Payload + Metadata - p.Payload = payload - p.Metadata = proxy.Metadata - return nil -} diff --git a/core/packet_test.go b/core/packet_test.go deleted file mode 100644 index d7cf07ee2..000000000 --- a/core/packet_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding/json" - "testing" - - . "github.com/TheThingsNetwork/ttn/core/errors" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestConvertRXPKPacket(t *testing.T) { - tests := []convertRXPKTest{ - genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(ErrInvalidStructure)}), - } - - for _, test := range tests { - Desc(t, "Convert RXPK: %s", pointer.DumpPStruct(test.RXPK, false)) - packet, err := ConvertRXPK(test.RXPK) - checkErrors(t, test.WantError, err) - checkPackets(t, test.CorePacket, packet) - } -} - -func TestConvertTXPKPacket(t *testing.T) { - tests := []convertToTXPKTest{ - genCoreFullMetadata(&convertToTXPKTest{WantError: nil}), - genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), - genCoreExtraMetadata(&convertToTXPKTest{WantError: nil}), - genCoreNoMetadata(&convertToTXPKTest{WantError: nil}), - convertToTXPKTest{ - CorePacket: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, - TXPK: semtech.TXPK{}, - WantError: pointer.String(ErrInvalidStructure), - }, - } - - for _, test := range tests { - Desc(t, "Convert to TXPK: %s", test.CorePacket.String()) - txpk, err := ConvertToTXPK(test.CorePacket) - checkErrors(t, test.WantError, err) - checkTXPKs(t, test.TXPK, txpk) - } -} - -func TestMarshalJSONPacket(t *testing.T) { - tests := []marshalJSONTest{ - marshalJSONTest{ // Empty Payload - Packet: Packet{Metadata: genFullMetadata(), Payload: lorawan.PHYPayload{}}, - WantFields: []string{}, - }, - marshalJSONTest{ // Empty Metadata - Packet: Packet{Metadata: Metadata{}, Payload: genPHYPayload(true)}, - WantFields: []string{"payload", "metadata"}, - }, - marshalJSONTest{ // With Metadata and Payload - Packet: Packet{Metadata: genFullMetadata(), Payload: genPHYPayload(true)}, - WantFields: []string{"payload", "metadata"}, - }, - } - - for _, test := range tests { - Desc(t, "Marshal packet to json: %s", test.Packet.String()) - raw, _ := json.Marshal(test.Packet) - checkFields(t, test.WantFields, raw) - } -} - -func TestUnmarshalJSONPacket(t *testing.T) { - tests := []unmarshalJSONTest{ - unmarshalJSONTest{ - JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{}}`, - WantPacket: Packet{Metadata: Metadata{}, Payload: genPHYPayload(true)}, - }, - unmarshalJSONTest{ - JSON: `{"payload":"gAQDAgEAAAAK4mTU97VqDnU=","metadata":{"chan":2,"codr":"4/6","fdev":3,"freq":863.125,"imme":false,"ipol":false,"lsnr":5.2,"modu":"LORA","ncrc":true,"powe":3,"prea":8,"rfch":2,"rssi":-27,"size":14,"stat":0,"tmst":1452694288207288421,"datr":"LORA","time":"2016-01-13T14:11:28.207288421Z"}}`, - WantPacket: Packet{Metadata: genFullMetadata(), Payload: genPHYPayload(true)}, - }, - unmarshalJSONTest{ - JSON: `invalid`, - WantPacket: Packet{}, - }, - unmarshalJSONTest{ - JSON: `{"metadata":{}}`, - WantPacket: Packet{}, - }, - } - - for _, test := range tests { - Desc(t, "Unmarshal json to packet: %s", test.JSON) - var packet Packet - json.Unmarshal([]byte(test.JSON), &packet) - checkPackets(t, test.WantPacket, packet) - } -} - -// ---- Declaration -type convertRXPKTest struct { - CorePacket Packet - RXPK semtech.RXPK - WantError *string -} - -type convertToTXPKTest struct { - TXPK semtech.TXPK - CorePacket Packet - WantError *string -} - -type marshalJSONTest struct { - Packet Packet - WantFields []string -} - -type unmarshalJSONTest struct { - JSON string - WantPacket Packet -} - -// ---- Build utilities - -// Generates a test suite where the RXPK is fully complete -func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { - phyPayload := genPHYPayload(true) - rxpk := genRXPK(phyPayload) - metadata := genMetadata(rxpk) - test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the RXPK contains partial metadata -func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { - phyPayload := genPHYPayload(true) - rxpk := genRXPK(phyPayload) - rxpk.Codr = nil - rxpk.Rfch = nil - rxpk.Rssi = nil - rxpk.Time = nil - rxpk.Size = nil - metadata := genMetadata(rxpk) - test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the RXPK contains no data -func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { - rxpk := genRXPK(genPHYPayload(true)) - rxpk.Data = nil - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the core packet has all txpk metadata -func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - metadata.Chan = nil - metadata.Lsnr = nil - metadata.Rssi = nil - metadata.Stat = nil - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} - return *test -} - -// Generates a test suite where the core packet has no metadata -func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := Metadata{} - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} - return *test -} - -// Generates a test suite where the core packet has partial metadata but all supported -func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - metadata.Chan = nil - metadata.Lsnr = nil - metadata.Rssi = nil - metadata.Stat = nil - metadata.Modu = nil - metadata.Fdev = nil - metadata.Time = nil - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} - return *test -} - -// Generates a test suite where the core packet has extra metadata not supported by txpk -func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket = Packet{Metadata: metadata, Payload: phyPayload} - return *test -} diff --git a/refactor/packets.go b/core/packets.go similarity index 99% rename from refactor/packets.go rename to core/packets.go index 0522ab1e5..7c6ad78a4 100644 --- a/refactor/packets.go +++ b/core/packets.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package refactor +package core import ( "encoding" diff --git a/refactor/packets_interfaces.go b/core/packets_interfaces.go similarity index 98% rename from refactor/packets_interfaces.go rename to core/packets_interfaces.go index fbdceae59..9b4f91849 100644 --- a/refactor/packets_interfaces.go +++ b/core/packets_interfaces.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package refactor +package core import ( "encoding" diff --git a/refactor/packets_test.go b/core/packets_test.go similarity index 90% rename from refactor/packets_test.go rename to core/packets_test.go index da39f2850..427823d95 100644 --- a/refactor/packets_test.go +++ b/core/packets_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package refactor +package core import () diff --git a/refactor/registrations_interfaces.go b/core/registrations_interfaces.go similarity index 96% rename from refactor/registrations_interfaces.go rename to core/registrations_interfaces.go index c904e37e3..c2d86b9ef 100644 --- a/refactor/registrations_interfaces.go +++ b/core/registrations_interfaces.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package refactor +package core import ( "encoding" diff --git a/refactor/adapters/http/doc.go b/refactor/adapters/http/doc.go deleted file mode 100644 index aed3c9d11..000000000 --- a/refactor/adapters/http/doc.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package http provides adapter implementations which run on top of http. -// -// The different protocols and mechanisms used are defined in the following document: -// https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/protocols.md -// -// The basic http adapter module can be used as a brick to build something bigger by registering -// specific endpoints. -package http diff --git a/refactor/adapters/http/utils.go b/refactor/adapters/http/utils.go deleted file mode 100644 index 7d2ae2045..000000000 --- a/refactor/adapters/http/utils.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import "net/http" - -// BadRequest logs the given failure and sends an appropriate response to the client -func BadRequest(w http.ResponseWriter, msg string) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(msg)) -} diff --git a/refactor/build_utilities_test.go b/refactor/build_utilities_test.go deleted file mode 100644 index ba7f938a1..000000000 --- a/refactor/build_utilities_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/brocaar/lorawan" -) - -// Generates a Metadata object with all field completed with relevant values -func genFullMetadata() Metadata { - timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) - return Metadata{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Datr: pointer.String("LORA"), - Fdev: pointer.Uint(3), - Freq: pointer.Float64(863.125), - Imme: pointer.Bool(false), - Ipol: pointer.Bool(false), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Ncrc: pointer.Bool(true), - Powe: pointer.Uint(3), - Prea: pointer.Uint(8), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(14), - Stat: pointer.Int(0), - Time: pointer.Time(timeRef), - Tmst: pointer.Uint(uint(timeRef.UnixNano())), - } -} - -// Generate a Physical payload representing an uplink or downlink message -func genPHYPayload(uplink bool) lorawan.PHYPayload { - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(uplink) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(uplink) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} diff --git a/refactor/check_utilities_test.go b/refactor/check_utilities_test.go deleted file mode 100644 index ed7db9c86..000000000 --- a/refactor/check_utilities_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "fmt" - "reflect" - "regexp" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// Checks that two packets match -func checkPackets(t *testing.T, want Packet, got Packet) { - if want == nil { - if got == nil { - Ok(t, "Check packets") - return - } - Ko(t, "No packet was expected but got %s", got.String()) - return - } - - if got == nil { - Ko(t, "Was expecting %s but got nothing", want.String()) - return - } - - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") - return - } - - Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) -} - -// Checks that obtained json matches expected one -func checkJSON(t *testing.T, want string, got []byte) { - str := string(got) - if str == want { - Ok(t, "check JSON") - return - } - Ko(t, "Marshaled data does not match expectations.\nWant: %s\nGot: %s", want, str) - return -} - -// Checks that obtained metadata matches expected one -func checkMetadata(t *testing.T, want Metadata, got Metadata) { - if reflect.DeepEqual(want, got) { - Ok(t, "check Metadata") - return - } - Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) -} - -// Check that obtained json strings contains the required field -func checkFields(t *testing.T, want []string, got []byte) { - for _, field := range want { - ok, err := regexp.Match(fmt.Sprintf("\"%s\":", field), got) - if !ok || err != nil { - Ko(t, "Expected field %s in %s", field, string(got)) - return - } - } - Ok(t, "Check fields") -} diff --git a/refactor/core.go b/refactor/core.go deleted file mode 100644 index 2c3940ded..000000000 --- a/refactor/core.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "github.com/brocaar/lorawan" -) - -type Component interface { - Register(reg Registration, an AckNacker) error - HandleUp(p []byte, an AckNacker, upAdapter Adapter) error - HandleDown(p []byte, an AckNacker, downAdapter Adapter) error -} - -type NetworkController interface { - HandleCommands(packet BPacket) error - UpdateFCntUp(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) - UpdateFCntDown(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) - MergeCommands(appEUI lorawan.EUI64, devEUI lorawan.EUI64, pkt BPacket) RPacket -} - -type AckNacker interface { - Ack(p Packet) error - Nack() error -} - -type Adapter interface { - Send(p Packet, r ...Recipient) ([]byte, error) - //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) - GetRecipient(raw []byte) (Recipient, error) - Next() ([]byte, AckNacker, error) - NextRegistration() (Registration, AckNacker, error) -} diff --git a/refactor/metadata.go b/refactor/metadata.go deleted file mode 100644 index c68fce7c5..000000000 --- a/refactor/metadata.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "encoding/json" - "time" - - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" -) - -type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Duty *uint `json:"duty,omitempty"` // DutyCycle of the gateway (uint between 0 and 100) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) -} - -// TODO -> Metadata should implements a less byte-consuming MarshalBinary method MarshalJSON() - -// metadata allows us to inherit Metadata in metadataProxy but only by extending the exported -// attributes of Metadata such that, they are still parsed by the json.Marshaller / -// json.Unmarshaller though we do not end up with a recursive hellish error. -type metadata Metadata - -// MarshalJSON implements the json.Marshal interface -func (m Metadata) MarshalJSON() ([]byte, error) { - var d *semtech.Datrparser - var t *semtech.Timeparser - - // Handle datr which can be either an uint or string depending of the modulation - if m.Datr != nil { - d = new(semtech.Datrparser) - if m.Modu != nil && *m.Modu == "FSK" { - *d = semtech.Datrparser{Kind: "uint", Value: *m.Datr} - } else { - *d = semtech.Datrparser{Kind: "string", Value: *m.Datr} - } - } - - // Time :'( ... By default, we mashall them as RFC3339Nano and unmarshall them the best we can. - if m.Time != nil { - t = new(semtech.Timeparser) - *t = semtech.Timeparser{Layout: time.RFC3339Nano, Value: m.Time} - } - - data, err := json.Marshal(metadataProxy{ - metadata: metadata(m), - Datr: d, - Time: t, - }) - - if err != nil { - err = errors.New(errors.Structural, err) - } - - return data, err -} - -// UnmarshalJSON implements the json.Unmarshaler interface -func (m *Metadata) UnmarshalJSON(raw []byte) error { - if m == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil Metadata") - } - - proxy := metadataProxy{} - if err := json.Unmarshal(raw, &proxy); err != nil { - return errors.New(errors.Structural, err) - } - *m = Metadata(proxy.metadata) - if proxy.Time != nil { - m.Time = proxy.Time.Value - } - - if proxy.Datr != nil { - m.Datr = &proxy.Datr.Value - } - - return nil -} - -// String implements the io.Stringer interface -func (m Metadata) String() string { - return pointer.DumpPStruct(m, false) -} - -// type metadataProxy is used to conveniently marshal and unmarshal Metadata structure. -// -// Datr field could be either string or uint depending on the Modu field. -// Time field could be parsed in a lot of different way depending of the time format. -// This proxy make sure that everything is marshaled and unmarshaled to the right thing and allow -// the Metadata struct to be user-friendly. -type metadataProxy struct { - metadata - Datr *semtech.Datrparser `json:"datr,omitempty"` - Time *semtech.Timeparser `json:"time,omitempty"` -} diff --git a/refactor/metadata_test.go b/refactor/metadata_test.go deleted file mode 100644 index 8ee402e89..000000000 --- a/refactor/metadata_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package refactor - -import ( - "encoding/json" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -var commonTests = []struct { - Metadata Metadata - WantError *string - JSON string -}{ - { // Basic attributes, uint, string and float64 - Metadata{Chan: pointer.Uint(2), Codr: pointer.String("4/6"), Freq: pointer.Float64(864.125)}, - nil, - `{"chan":2,"codr":"4/6","freq":864.125}`, - }, - - { // Basic attributes #2, int and bool - Metadata{Imme: pointer.Bool(true), Rssi: pointer.Int(-54)}, - nil, - `{"imme":true,"rssi":-54}`, - }, - - { // Datr attr, FSK type - Metadata{Datr: pointer.String("50000"), Modu: pointer.String("FSK")}, - nil, - `{"modu":"FSK","datr":50000}`, - }, - - { // Datr attr, lora modulation - Metadata{Datr: pointer.String("SF7BW125"), Modu: pointer.String("LORA")}, - nil, - `{"modu":"LORA","datr":"SF7BW125"}`, - }, - - { // Time attr - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC))}, - nil, - `{"time":"2016-01-06T15:11:12.000000142Z"}`, - }, - - { // Mixed - Metadata{ - Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC)), - Modu: pointer.String("FSK"), - Datr: pointer.String("50000"), - Size: pointer.Uint(14), - Lsnr: pointer.Float64(5.7), - }, - nil, - `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, - }, -} - -var unmarshalTests = []struct { - Metadata Metadata - WantError *string - JSON string -}{ - { // Local time - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 0, time.UTC))}, - nil, - `{"time":"2016-01-06 15:11:12 GMT"}`, - }, - - { // RFC3339 time - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142000000, time.UTC))}, - nil, - `{"time":"2016-01-06T15:11:12.142000Z"}`, - }, -} - -func TestMarshaljson(t *testing.T) { - for _, test := range commonTests { - Desc(t, "Marshal medatadata: %s", test.Metadata.String()) - raw, err := json.Marshal(test.Metadata) - CheckErrors(t, test.WantError, err) - checkJSON(t, test.JSON, raw) - } -} - -func TestUnmarshalJSON(t *testing.T) { - for _, test := range append(commonTests, unmarshalTests...) { - Desc(t, "Unmarshal json: %s", test.JSON) - metadata := Metadata{} - err := json.Unmarshal([]byte(test.JSON), &metadata) - CheckErrors(t, test.WantError, err) - checkMetadata(t, test.Metadata, metadata) - } -} From d01d7d55a978b79af87ff3f0fdc12e5a5db2bfb2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:36:08 +0100 Subject: [PATCH 0774/2266] [refactor] remove typo in handler HandleDown --- core/components/handler/handler.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 018a888d5..57110ae34 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -316,7 +316,6 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro if err != nil { return errors.New(errors.Structural, data) } - return nil switch itf.(type) { case HPacket: From afe51ceb26061bd8fb47a5a248d0450405efe7b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:37:58 +0100 Subject: [PATCH 0775/2266] [refactor] Use pointer receiver in router storage -> Mutex needs it --- core/components/router/storage.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 22ddf9f3d..a5a00ef39 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -38,16 +38,16 @@ func NewStorage(name string, delay time.Duration) (Storage, error) { return nil, errors.New(errors.Operational, err) } - return storage{db: itf, ExpiryDelay: delay, Name: "broker"}, nil + return &storage{db: itf, ExpiryDelay: delay, Name: "broker"}, nil } // Lookup implements the router.Storage interface -func (s storage) Lookup(devEUI lorawan.EUI64) (entry, error) { +func (s *storage) Lookup(devEUI lorawan.EUI64) (entry, error) { return s.lookup(devEUI, true) } // lookup offers an indirection in order to avoid taking a lock if not needed -func (s storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { +func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { // NOTE This works under the assumption that a read or write lock is already held by // the callee (e.g. Store() if lock { @@ -81,7 +81,7 @@ func (s storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { } // Store implements the router.Storage interface -func (s storage) Store(reg Registration) error { +func (s *storage) Store(reg Registration) error { devEUI := reg.DevEUI() recipient, err := reg.Recipient().MarshalBinary() if err != nil { From 8eec850bd85b36fc94dac0ed1f70d3dcc90f75ba Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:38:17 +0100 Subject: [PATCH 0776/2266] [refactor] Replace ttn package with main to be consistent with the cmd / cli --- doc.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc.go b/doc.go index 6dc494d75..9577d37b1 100644 --- a/doc.go +++ b/doc.go @@ -1,4 +1,4 @@ /* Package ttn contains the backend server systems for The Things Network. */ -package ttn +package main From c633825feb3fed493b1651637e06d8d3f23badcd Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:48:19 +0100 Subject: [PATCH 0777/2266] [refactor] Integrate API changes to broker command --- cmd/broker.go | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index f21c0b6b7..7e763bfa0 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -6,10 +6,8 @@ package cmd import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/core/adapters/http/pubsub" - "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" - "github.com/TheThingsNetwork/ttn/core/components" + "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" + "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -35,32 +33,26 @@ and personalized devices (with their network session keys) with the router. ctx.Info("Starting") // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), parser.JSON{}, ctx.WithField("adapter", "router-http")) + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), nil, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } + rtrAdapter.Bind(handlers.Collect{}) - hdlHTTPAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), parser.JSON{}, ctx.WithField("adapter", "handler-http")) + hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), nil, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } + hdlAdapter.Bind(handlers.Collect{}) + hdlAdapter.Bind(handlers.PubSub{}) + hdlAdapter.Bind(handlers.StatusPage{}) - _, err = statuspage.NewAdapter(hdlHTTPAdapter, ctx.WithField("adapter", "statuspage-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - hdlAdapter, err := pubsub.NewAdapter(hdlHTTPAdapter, parser.PubSub{}, ctx.WithField("adapter", "handler-pubsub")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Handlers Adapter") - } - - db, err := components.NewBrokerStorage() + db, err := broker.NewStorage("broker_storage.db") // TODO Use a cli flag if err != nil { ctx.WithError(err).Fatal("Could not create a local storage") } - broker := components.NewBroker(db, ctx) + broker := broker.New(db, ctx) // Bring the service to life @@ -72,7 +64,7 @@ and personalized devices (with their network session keys) with the router. ctx.WithError(err).Error("Could not retrieve uplink") continue } - go func(packet core.Packet, an core.AckNacker) { + go func(packet []byte, an core.AckNacker) { if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { ctx.WithError(err).Error("Could not process uplink") } From 33a0468083fcde578bef57920fe99997904b9d0d Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:55:50 +0100 Subject: [PATCH 0778/2266] [refactor] Integrate API changes to router command --- cmd/router.go | 38 ++++++++++++++------------------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/cmd/router.go b/cmd/router.go index a938b0cee..c30fa6006 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -4,16 +4,16 @@ package cmd import ( + "fmt" "strings" "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/broadcast" - "github.com/TheThingsNetwork/ttn/core/adapters/http/parser" - "github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage" - "github.com/TheThingsNetwork/ttn/core/adapters/semtech" - "github.com/TheThingsNetwork/ttn/core/components" + httpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" + "github.com/TheThingsNetwork/ttn/core/adapters/udp" + udpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/udp/handlers" + "github.com/TheThingsNetwork/ttn/core/components/router" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -38,41 +38,31 @@ the gateway's duty cycle is (almost) full.`, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwAdapter, err := semtech.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("adapter", "gateway-semtech")) + gtwAdapter, err := udp.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } - - pktAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), parser.JSON{}, ctx.WithField("adapter", "broker-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - _, err = statuspage.NewAdapter(pktAdapter, ctx.WithField("adapter", "statuspage-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } + gtwAdapter.Bind(udpHandlers.Semtech{}) var brokers []core.Recipient brokersStr := strings.Split(viper.GetString("router.brokers"), ",") for i := range brokersStr { - brokers = append(brokers, core.Recipient{ - Address: strings.Trim(brokersStr[i], " "), - Id: i, - }) + url := fmt.Sprintf("%s/packets", strings.Trim(brokersStr[i], " ")) + brokers = append(brokers, http.NewHttpRecipient(url, "POST")) } - brkAdapter, err := broadcast.NewAdapter(pktAdapter, brokers, ctx.WithField("adapter", "broker-broadcast")) + brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), brokers, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } + brkAdapter.Bind(httpHandlers.StatusPage{}) - db, err := components.NewRouterStorage(time.Hour * 8) + db, err := router.NewStorage("router_storage.db", time.Hour*8) // TODO use cli flag if err != nil { ctx.WithError(err).Fatal("Could not create a local storage") } - router := components.NewRouter(db, ctx) + router := router.New(db, ctx) // Bring the service to life @@ -84,7 +74,7 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(err).Warn("Could not get next packet from gateway") continue } - go func(packet core.Packet, an core.AckNacker) { + go func(packet []byte, an core.AckNacker) { if err := router.HandleUp(packet, an, brkAdapter); err != nil { ctx.WithError(err).Warn("Could not process packet from gateway") } From 305ce6846688af013b949afdfc27bede99aac5cb Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 28 Feb 2016 22:59:27 +0100 Subject: [PATCH 0779/2266] [refactor] Fix udp debugger using old convertRXPK method --- integration/udp_debugger/udp_debugger.go | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/integration/udp_debugger/udp_debugger.go b/integration/udp_debugger/udp_debugger.go index 4269e76d0..1177483df 100644 --- a/integration/udp_debugger/udp_debugger.go +++ b/integration/udp_debugger/udp_debugger.go @@ -2,8 +2,8 @@ package main import ( "fmt" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" "net" ) @@ -40,12 +40,7 @@ func main() { fmt.Println("Unexpected packet payload") continue } - packet, err := core.ConvertRXPK(pkt.Payload.RXPK[0]) - if err != nil { - fmt.Println(err) - continue - } - fmt.Println(packet) + fmt.Printf(pointer.DumpPStruct(pkt.Payload.RXPK[0], true)) } }() From 25f113d2d28c79895188196be5724d8c546a282f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 11:12:02 +0100 Subject: [PATCH 0780/2266] Add MQTT to drone.yml [Skip CI] --- .drone.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.drone.yml b/.drone.yml index a8428d8ae..0e00cc378 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,3 +1,7 @@ +compose: + mqtt: + image: ansi/mosquitto + command: /usr/local/sbin/mosquitto -p 1683 build: test: image: golang From eaa93a05033edddfeee5ec592070e0995ec46e5b Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 11:12:24 +0100 Subject: [PATCH 0781/2266] [cli-handler] Add handler integration to the cli --- cmd/handler.go | 108 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index d2874df7c..26082d463 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -4,6 +4,12 @@ package cmd import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" + httpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" + "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + mqttHandlers "github.com/TheThingsNetwork/ttn/core/adapters/mqtt/handlers" + "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -18,24 +24,112 @@ The default handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "database": viper.GetString("handler.database"), - "brokers-port": viper.GetInt("handler.brokers-port"), + "devicesDatabase": viper.GetString("handler.dev-database"), + "packetsDatabase": viper.GetString("handler.pkt-database"), + "brokers-port": viper.GetInt("handler.brokers-port"), + "apps-client": viper.GetString("handler.apps-client"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { - ctx.Fatal("The handler's not ready yet, but we're working on it!") + ctx.Info("Starting") - // TODO: Add logic + // ----- Start Adapters + brkAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.brokers-port")), nil, ctx.WithField("adapter", "broker-adapter")) + if err != nil { + ctx.WithError(err).Fatal("Could not start broker adapter") + } + brkAdapter.Bind(httpHandlers.StatusPage{}) + brkAdapter.Bind(httpHandlers.Healthz{}) + + mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.apps-client"), mqtt.Tcp) + if err != nil { + ctx.WithError(err).Fatal("Could not start mqtt client") + } + appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) + appAdapter.Bind(mqttHandlers.Activation{}) + + // Instantiate in-memory storages + devicesDB, err := handler.NewDevStorage(viper.GetString("handler.dev-database")) + if err != nil { + ctx.WithError(err).Fatal("Could not create local devices storage") + } + + packetsDB, err := handler.NewPktStorage(viper.GetString("handler.pkt-database")) + if err != nil { + ctx.WithError(err).Fatal("Could not create local packets storage") + } + + // Instantiate the actual handler + handler := handler.New(devicesDB, packetsDB, ctx) + + // Bring the service to life + + // Listen uplink + go func() { + for { + packet, an, err := brkAdapter.Next() + if err != nil { + ctx.WithError(err).Warn("Could not get next packet fom brokers") + continue + } + + go func(packet []byte, an core.AckNacker) { + if err := handler.HandleUp(packet, an, appAdapter); err != nil { + ctx.WithError(err).Warn("Could not process packet from brokers") + } + }(packet, an) + } + }() + + // Listen downlink + go func() { + for { + packet, an, err := appAdapter.Next() + if err != nil { + ctx.WithError(err).Warn("Could not get next packet fom applications") + continue + } + + go func(packet []byte, an core.AckNacker) { + if err := handler.HandleDown(packet, an, brkAdapter); err != nil { + ctx.WithError(err).Warn("Could not process packet from applications") + } + }(packet, an) + } + }() + + // Listen registrations + go func() { + for { + reg, an, err := appAdapter.NextRegistration() + if err != nil { + ctx.WithError(err).Warn("Could not get next registration from applications") + continue + } + + go func(reg core.Registration, an core.AckNacker) { + if err := handler.Register(reg, an); err != nil { + ctx.WithError(err).Warn("Could not process registration from applications") + } + }(reg, an) + } + }() + + // Wait + <-make(chan bool) }, } func init() { RootCmd.AddCommand(handlerCmd) - handlerCmd.Flags().String("database", "boltdb:/tmp/ttn_handler.db", "Database connection") + handlerCmd.Flags().String("dev-database", "boltdb:/tmp/ttn_handler_devices.db", "Devices Database connection") + handlerCmd.Flags().String("pkt-database", "boltdb:/tmp/ttn_handler_packets.db", "Packets Database connection") handlerCmd.Flags().Int("brokers-port", 1691, "TCP port for connections from brokers") + handlerCmd.Flags().String("apps-client", "localhost:1883", "Uri of the applications mqtt") - viper.BindPFlag("handler.database", handlerCmd.Flags().Lookup("database")) + viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) + viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) viper.BindPFlag("handler.brokers-port", handlerCmd.Flags().Lookup("brokers-port")) - + viper.BindPFlag("handler.apps-client", handlerCmd.Flags().Lookup("apps-client")) } From 96285ccee3b9034700d2fde4eb975621f45c0822 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 11:12:57 +0100 Subject: [PATCH 0782/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index dc223baa2..2641e7f43 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.bCBA4ySZzb5nPaUgEQUBumqcTn2DqZbqlsXXkQXF0-bkYAm64h0uxjTTv5o-Wr-ESVAQumkDCFI8B1l5o5MIHkT7ErFoiN2K-UA2RnTRlwRg7cNwnWM9WbxTS2jUqW5VRY_eQy15f9lg2FZWzDUETwqBGc3LnOKDFAXCgQErwlMPIIzZ2GqwfZezx62N7UTAzQIU81NmEueA4O8ZX_IQ8TA5DixmTi922SamTX9lC51YjJtGmcV3ZgcE9iyQfXnjYvRWF9irNerYvVhCJDheK40MXgVhTCy9JSgQkVSI2mCUUVlV_sYUBOF4-s1DK06j6iuVXW7g8xmmzI6pGB9tsA.jpIxTYwazSp8gV7d.QBqpIbxYo4H7iNCsFvZHfZCjsExsLn-TZTTvyh3naGS8CqzeNelpCQaaiS71n2UE6j6goF3OQLbWcQgNczUbHt7D4ctPiTz8LziPflERNqN04s0RX2Zp234B8gosslHhOIRu9bVeWZOXLzzjp4jt5QUytSeTwgJZffwgUaXPXP0vY7E8CI4uDht7bzmxR4kf0g67vkkV174dlwqEccfX1vpC_-VpJ3DI7l1io8jQGrgzO2PRjCg2xD70nLvBKsdEw6v3V_bT.kxPSWRPvw38ojb1HZLqoyQ \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.AW1GTRi8Dq4eU1d0sM6mFjfyL4eLDnnufjW9ZCzZ7d1dDj1TcvbueI9PVRSfQK0sXaM5P8seHXeu2VY_D5bry5mAxM8LEdgvPZnBwMV8O6AMlqi-F8YQNg13RdPfK_8U9D-zLKeE7FsBDE2tPieqwPGZq14E_pQre_ARmUVwNxUllVcZjqK4VSORkkGwWLVFY2QBwb4b5uCUBvEr7JUSR-MRESjxvjU_Y1zVMQhP5eu_D4GmRAdYcrQyjTZdhAqNepRs6n8NFS9Rrykd7KBHiNKieD3HL6tFrl-pzBIrQpVjFLvbgTnxHGUATrUVSmJF7SngcSzJfUbxFpbC12QOKQ.GgRViVdsyHpGMA2r.eYrRejM3rqtUsQ7iWFHt4M85_Jael-qB9t07MHO16fMzJGD23wn3WcQHah5mfFe5ZLXWO5ZKOrmZTD9gok1tb9aMO1UCB1Tpd6V-XfI2hMLizlqBEJrLQL94bqTDa2LeBtNqFN0HUiTX5uMf-MAulLTjhl1_igRA8ZTQu6GYDJ0z2U-6Att-zRfj9SQHFw1eIpVmq34rwYh3J1o5aHCr4Xq9u2L4CQxKEmJ1axNx3UO7GKHKZ5169TLdxplZ0bIGuppVgk3m.ZDDpizJRoLR5RxtTw8YkRw \ No newline at end of file From 4126a6ab46e26fb6e95af3324c71fca50963db0c Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 11:17:04 +0100 Subject: [PATCH 0783/2266] [hotfix] Remove undesired caps in packets String() method --- core/packets.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/packets.go b/core/packets.go index 7c6ad78a4..5be6ce813 100644 --- a/core/packets.go +++ b/core/packets.go @@ -293,7 +293,7 @@ func (p hpacket) String() string { str += fmt.Sprintf("\n\t%s}", p.metadata.String()) str += fmt.Sprintf("\n\tAppEUI:%+x\n,", p.appEUI) str += fmt.Sprintf("\n\tDevEUI:%+x\n,", p.devEUI) - str += fmt.Sprintf("\n\tPayload:%v\n}", p.Payload) + str += fmt.Sprintf("\n\tPayload:%v\n}", p.payload) return str } From 281175ed26e7911915454730b5fbd23f33c76690 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 11:36:16 +0100 Subject: [PATCH 0784/2266] [db-flag] Use CLI flags for database locations --- cmd/broker.go | 28 +++++++++++++++++++++++--- cmd/handler.go | 53 +++++++++++++++++++++++++++++++++++++++++++------- cmd/router.go | 23 +++++++++++++++++++--- 3 files changed, 91 insertions(+), 13 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 522e8b153..fd41403aa 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -4,6 +4,10 @@ package cmd import ( + "fmt" + "path/filepath" + "strings" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" @@ -48,9 +52,27 @@ and personalized devices (with their network session keys) with the router. hdlAdapter.Bind(handlers.PubSub{}) hdlAdapter.Bind(handlers.StatusPage{}) - db, err := broker.NewStorage("broker_storage.db") // TODO Use a cli flag - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") + // Instantiate Storage + + var db broker.Storage + + dbString := viper.GetString("broker.database") + switch { + case strings.HasPrefix(dbString, "boltdb:"): + + dbPath, err := filepath.Abs(dbString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid database path") + } + + db, err = broker.NewStorage(dbPath) + if err != nil { + ctx.WithError(err).Fatal("Could not create local storage") + } + + ctx.WithField("database", dbPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } broker := broker.New(db, ctx) diff --git a/cmd/handler.go b/cmd/handler.go index 26082d463..e724e6e3c 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -4,6 +4,10 @@ package cmd import ( + "fmt" + "path/filepath" + "strings" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" httpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" @@ -48,15 +52,50 @@ The default handler is the bridge between The Things Network and applications. appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) - // Instantiate in-memory storages - devicesDB, err := handler.NewDevStorage(viper.GetString("handler.dev-database")) - if err != nil { - ctx.WithError(err).Fatal("Could not create local devices storage") + // Instantiate in-memory devices storage + + var devicesDB handler.DevStorage + + devDBString := viper.GetString("handler.dev-database") + switch { + case strings.HasPrefix(devDBString, "boltdb:"): + + devDBPath, err := filepath.Abs(devDBString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid devices database path") + } + + devicesDB, err = handler.NewDevStorage(devDBPath) + if err != nil { + ctx.WithError(err).Fatal("Could not create local devices storage") + } + + ctx.WithField("database", devDBPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local devices storage") } - packetsDB, err := handler.NewPktStorage(viper.GetString("handler.pkt-database")) - if err != nil { - ctx.WithError(err).Fatal("Could not create local packets storage") + // Instantiate in-memory packets storage + + var packetsDB handler.PktStorage + + pktDBString := viper.GetString("handler.pkt-database") + switch { + case strings.HasPrefix(pktDBString, "boltdb:"): + + pktDBPath, err := filepath.Abs(pktDBString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid packets database path") + } + + packetsDB, err = handler.NewPktStorage(pktDBPath) + if err != nil { + ctx.WithError(err).Fatal("Could not create local packets storage") + } + + ctx.WithField("database", pktDBPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local packets storage") } // Instantiate the actual handler diff --git a/cmd/router.go b/cmd/router.go index 4a424cd4f..b42e4d752 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "path/filepath" "strings" "time" @@ -58,9 +59,25 @@ the gateway's duty cycle is (almost) full.`, brkAdapter.Bind(httpHandlers.StatusPage{}) brkAdapter.Bind(httpHandlers.Healthz{}) - db, err := router.NewStorage("router_storage.db", time.Hour*8) // TODO use cli flag - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") + var db router.Storage + + dbString := viper.GetString("router.database") + switch { + case strings.HasPrefix(dbString, "boltdb:"): + + dbPath, err := filepath.Abs(dbString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid database path") + } + + db, err = router.NewStorage(dbPath, time.Hour*8) // TODO use cli flag + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + ctx.WithField("database", dbPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } router := router.New(db, ctx) From 7a17721c2ee933b9a973e161252053b038c4ef10 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 11:48:17 +0100 Subject: [PATCH 0785/2266] Add coveralls to travis integration --- .travis.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3dd056dfa..4ff05f99a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ language: go sudo: required go: - - 1.5 + - tip before_install: - wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key @@ -13,6 +13,8 @@ before_install: install: - sudo apt-get install mosquitto mosquitto-clients + - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi + - go get github.com/mattn/goveralls - go get $(comm -23 <(sort <(go list -f '{{join .Imports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) @@ -23,3 +25,7 @@ script: - go list ./... | grep -v integration | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... + +after_success: + - go test -v -covermode=count -coverprofile=coverage.out + - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN From d75c7b5c400a961c2d3be022758d96bcd1aa6ccc Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 13:39:51 +0100 Subject: [PATCH 0786/2266] Add script to compute test coverage of each package --- .drone.yml | 2 +- .travis.yml | 4 ++-- coverage.sh | 11 +++++++++++ 3 files changed, 14 insertions(+), 3 deletions(-) create mode 100755 coverage.sh diff --git a/.drone.yml b/.drone.yml index 0e00cc378..bdec5e428 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ build: commands: - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .Imports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .TestImports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - - go list ./... | grep -v integration | xargs go test -v + - go list ./... | grep -vE 'cmd|integration|ttn$' | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... release: diff --git a/.travis.yml b/.travis.yml index 4ff05f99a..ae3687f38 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,10 +22,10 @@ before_script: - mosquitto -p 1683 1>/dev/null 2>/dev/null & script: - - go list ./... | grep -v integration | xargs go test + - go list ./... | grep -vE 'cmd|integration|ttn$' | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... after_success: - - go test -v -covermode=count -coverprofile=coverage.out + - source ./coverage.sh - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN diff --git a/coverage.sh b/coverage.sh new file mode 100755 index 000000000..34ac440ce --- /dev/null +++ b/coverage.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +mkdir .cover +for pkg in $(go list ./... | grep -vE 'cmd|integration|ttn$') +do + profile=".cover/$(echo $pkg | grep -oE 'ttn/.*' | sed 's/\///g').cover" + go test -cover -coverprofile=$profile $pkg +done +echo "mode: set" > coverage.out && cat .cover/*.cover | grep -v mode: | sort -r | \ +awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out +rm -r .cover From afb408e65c4c74bd6aaa9a360035a8331b921adc Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 15:23:51 +0100 Subject: [PATCH 0787/2266] [test-utils/storage] Write down tests for utils/storage --- utils/storage/storage_test.go | 214 ++++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 utils/storage/storage_test.go diff --git a/utils/storage/storage_test.go b/utils/storage/storage_test.go new file mode 100644 index 000000000..8e3672145 --- /dev/null +++ b/utils/storage/storage_test.go @@ -0,0 +1,214 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "os" + "reflect" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const database = "TestStoreAndLookup.db" + +func TestStoreAndLookup(t *testing.T) { + var itf Interface + + { + if err := os.Remove(database); err != nil { + panic(err) + } + } + + defer func() { + if itf == nil { + return + } + if err := itf.Close(); err != nil { + panic(err) + } + }() + + // -------------------- + + { + Desc(t, "Open database in the current folder") + var err error + itf, err = New(database) + CheckErrors(t, nil, err) + } + + // -------------------- + + { + Desc(t, "Open database in a forbidden place") + _, err := New("/usr/bin") + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // -------------------- + + { + Desc(t, "Store then lookup in a 1-level bucket") + err := itf.Store("bucket", []byte{1, 2, 3}, []Entry{&testEntry{Data: "TTN"}}) + CheckErrors(t, nil, err) + + entries, err := itf.Lookup("bucket", []byte{1, 2, 3}, &testEntry{}) + CheckErrors(t, nil, err) + CheckEntries(t, []testEntry{testEntry{Data: "TTN"}}, entries) + } + + // ------------------- + + { + Desc(t, "Store then lookup in a nested bucket") + err := itf.Store("nested.bucket", []byte{14, 42}, []Entry{&testEntry{Data: "IoT"}}) + CheckErrors(t, nil, err) + + entries, err := itf.Lookup("nested.bucket", []byte{14, 42}, &testEntry{}) + CheckErrors(t, nil, err) + CheckEntries(t, []testEntry{testEntry{Data: "IoT"}}, entries) + } + + // ------------------- + + { + Desc(t, "Lookup in non-existing bucket") + entries, err := itf.Lookup("DoesntExist", []byte{1, 2, 3}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Lookup a non-existing key") + entries, err := itf.Lookup("bucket", []byte{9, 9, 9, 9, 9}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Flush an 1-level bucket entry") + itf.Store("bucket", []byte{1, 1, 1}, []Entry{&testEntry{Data: "TTN"}}) + err := itf.Flush("bucket", []byte{1, 1, 1}) + CheckErrors(t, nil, err) + entries, err := itf.Lookup("bucket", []byte{1, 1, 1}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Flush a nested bucket entry") + itf.Store("nested.bucket", []byte{2, 2, 2}, []Entry{&testEntry{Data: "TTN"}}) + err := itf.Flush("nested.bucket", []byte{2, 2, 2}) + CheckErrors(t, nil, err) + entries, err := itf.Lookup("nested.bucket", []byte{2, 2, 2}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Reset a 1-level bucket") + itf.Store("mybucket", []byte{1, 1, 1}, []Entry{&testEntry{Data: "TTN"}}) + err := itf.Reset("mybucket") + CheckErrors(t, nil, err) + entries, err := itf.Lookup("mybucket", []byte{1, 1, 1}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Reset a nested bucket") + itf.Store("mybucket.nested", []byte{2, 2, 2}, []Entry{&testEntry{Data: "TTN"}}) + err := itf.Reset("mybucket.nested") + CheckErrors(t, nil, err) + entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Reset a nested bucket parent") + itf.Store("mybucket.nested", []byte{2, 2, 2}, []Entry{&testEntry{Data: "TTN"}}) + err := itf.Reset("mybucket") + CheckErrors(t, nil, err) + entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Replace an existing entry in a bucket") + itf.Store("anotherbucket", []byte{14, 14, 14}, []Entry{&testEntry{Data: "I don't like IoT"}}) + err := itf.Replace("anotherbucket", []byte{14, 14, 14}, []Entry{&testEntry{Data: "IoT is Awesome"}}) + CheckErrors(t, nil, err) + entries, err := itf.Lookup("anotherbucket", []byte{14, 14, 14}, &testEntry{}) + CheckErrors(t, nil, err) + CheckEntries(t, []testEntry{{Data: "IoT is Awesome"}}, entries) + } + + // ------------------- + + { + Desc(t, "Store several entries under the same key") + itf.Store("several", []byte{1, 1, 1}, []Entry{&testEntry{Data: "FirstEntry"}}) + itf.Store("several", []byte{1, 1, 1}, []Entry{&testEntry{Data: "SecondEntry"}, &testEntry{Data: "ThirdEntry"}}) + entries, err := itf.Lookup("several", []byte{1, 1, 1}, &testEntry{}) + CheckErrors(t, nil, err) + CheckEntries(t, []testEntry{{Data: "FirstEntry"}, {Data: "SecondEntry"}, {Data: "ThirdEntry"}}, entries) + } +} + +// ----- Type Utilities + +type testEntry struct { + Data string +} + +func (e testEntry) MarshalBinary() ([]byte, error) { + return []byte(e.Data), nil +} + +func (e *testEntry) UnmarshalBinary(data []byte) error { + e.Data = string(data) + return nil +} + +// ----- Check Utilities + +func CheckEntries(t *testing.T, want []testEntry, got interface{}) { + if want == nil && got == nil { + Ok(t, "Check Entries") + return + } + + entries, ok := got.([]testEntry) + if !ok { + Ko(t, "Expected []testEntry but got %+v", got) + return + } + + if !reflect.DeepEqual(want, entries) { + Ko(t, "Retrieved entries don't match expectations.\nWant: %v\nGot: %v", want, entries) + return + } + Ok(t, "Check Entries") +} From 26583f2f76c5af7ec430cd49e9da0d14999c15e1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 15:23:57 +0100 Subject: [PATCH 0788/2266] [test-utils/storage] Make them pass --- utils/storage/storage.go | 40 +++++++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 384b546fc..d8a54f81d 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -49,27 +49,31 @@ func New(name string) (Interface, error) { return store{db}, nil } +// getBucket retrieve a bucket based on a string. The name might present several levels, all +// separated by dot "." which indicates nested buckets. If no bucket is found along the path, they +// are created, otherwise, the existing one is used. func getBucket(tx *bolt.Tx, name string) (*bolt.Bucket, error) { path := strings.Split(name, ".") if len(path) < 1 { return nil, errors.New(errors.Structural, "Invalid bucket name") } - var next interface { + var cursor interface { CreateBucketIfNotExists(b []byte) (*bolt.Bucket, error) Bucket(b []byte) *bolt.Bucket } var err error - next = tx + cursor = tx for _, bname := range path { - next = next.Bucket([]byte(bname)) + next := cursor.Bucket([]byte(bname)) if next == nil { - if next, err = next.CreateBucketIfNotExists([]byte(bname)); err != nil { + if next, err = cursor.CreateBucketIfNotExists([]byte(bname)); err != nil { return nil, errors.New(errors.Operational, err) } } + cursor = next } - return next.(*bolt.Bucket), nil + return cursor.(*bolt.Bucket), nil } // Store put a new set of entries in the given bolt database. It adds the entries to an existing set @@ -142,7 +146,8 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) }) if err = r.Err(); err != nil { - if err == io.EOF { + failure, ok := err.(errors.Failure) + if ok && failure.Nature == errors.Behavioural && failure.Fault == io.EOF { break } return nil, errors.New(errors.Operational, err) @@ -151,7 +156,7 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} return entries.Interface(), nil } -// Flush empties each entry of a bucket associated to a given device +// Flush remove an entry from a bucket func (itf store) Flush(bucketName string, key []byte) error { return itf.db.Update(func(tx *bolt.Tx) error { bucket, err := getBucket(tx, bucketName) @@ -203,10 +208,27 @@ func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { // Reset resets a given bucket from a given bolt database func (itf store) Reset(bucketName string) error { return itf.db.Update(func(tx *bolt.Tx) error { - if err := tx.DeleteBucket([]byte(bucketName)); err != nil { + path := strings.Split(bucketName, ".") + + var cursor interface { + DeleteBucket(name []byte) error + CreateBucketIfNotExists(name []byte) (*bolt.Bucket, error) + } + + if len(path) == 1 { + cursor = tx + } else { + var err error + cursor, err = getBucket(tx, strings.Join(path[:len(path)-1], ".")) + if err != nil { + return err + } + } + + if err := cursor.DeleteBucket([]byte(path[len(path)-1])); err != nil { return errors.New(errors.Operational, err) } - if _, err := tx.CreateBucketIfNotExists([]byte(bucketName)); err != nil { + if _, err := cursor.CreateBucketIfNotExists([]byte(path[len(path)-1])); err != nil { return errors.New(errors.Operational, err) } return nil From 3db167571b895bee47725ff8e2620d2aa4dfc631 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 15:40:40 +0100 Subject: [PATCH 0789/2266] [hotfix] Delete the test database AFTER the tests. --- utils/storage/storage_test.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/utils/storage/storage_test.go b/utils/storage/storage_test.go index 8e3672145..48411b155 100644 --- a/utils/storage/storage_test.go +++ b/utils/storage/storage_test.go @@ -18,13 +18,6 @@ const database = "TestStoreAndLookup.db" func TestStoreAndLookup(t *testing.T) { var itf Interface - - { - if err := os.Remove(database); err != nil { - panic(err) - } - } - defer func() { if itf == nil { return @@ -32,6 +25,7 @@ func TestStoreAndLookup(t *testing.T) { if err := itf.Close(); err != nil { panic(err) } + os.Remove(database) }() // -------------------- From d1292248b6dee00d54b0575fe3dba9ed547d8e01 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 15:43:56 +0100 Subject: [PATCH 0790/2266] Implements missing NextRegistration() method in mqtt adapter --- core/adapters/mqtt/mqtt.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index cf8a80eab..a8eee7f50 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -191,7 +191,8 @@ func (a *Adapter) Next() ([]byte, core.AckNacker, error) { // NextRegistration implements the core.Adapter interface. Not implemented for this adapters. func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return nil, nil, nil + r := <-a.registrations + return r.Registration, mqttAckNacker{Chresp: r.Chresp}, nil } // Bind registers a handler to a specific endpoint From 86f39b3955e0e895157ca74e4ee2113f9cfd6fbb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 16:56:59 +0100 Subject: [PATCH 0791/2266] [testing] Create tests for Status Page --- .../adapters/http/handlers/statuspage_test.go | 62 +++++++++++++++++++ utils/testing/response_writer.go | 33 ++++++++++ 2 files changed, 95 insertions(+) create mode 100644 core/adapters/http/handlers/statuspage_test.go create mode 100644 utils/testing/response_writer.go diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go new file mode 100644 index 000000000..82a0cc3a9 --- /dev/null +++ b/core/adapters/http/handlers/statuspage_test.go @@ -0,0 +1,62 @@ +package handlers + +import ( + "net/http" + "testing" + + . "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/stats" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/smartystreets/assertions" +) + +func TestURL(t *testing.T) { + a := assertions.New(t) + + s := StatusPage{} + + a.So(s.Url(), assertions.ShouldEqual, "/status/") +} + +func TestHandler(t *testing.T) { + a := assertions.New(t) + + s := StatusPage{} + + // Only GET allowed + r1, _ := http.NewRequest("POST", "/status/", nil) + r1.RemoteAddr = "127.0.0.1:12345" + rw1 := NewResponseWriter() + s.Handle(&rw1, make(chan<- PktReq), make(chan<- RegReq), r1) + a.So(rw1.TheStatus, assertions.ShouldEqual, 405) + + // Only Localhost allowed + r2, _ := http.NewRequest("GET", "/status/", nil) + r2.RemoteAddr = "12.34.56.78:12345" + rw2 := NewResponseWriter() + s.Handle(&rw2, make(chan<- PktReq), make(chan<- RegReq), r2) + a.So(rw2.TheStatus, assertions.ShouldEqual, 403) + + // Initially Empty + r3, _ := http.NewRequest("GET", "/status/", nil) + r3.RemoteAddr = "127.0.0.1:12345" + rw3 := NewResponseWriter() + s.Handle(&rw3, make(chan<- PktReq), make(chan<- RegReq), r3) + a.So(rw3.TheStatus, assertions.ShouldEqual, 200) + a.So(string(rw3.TheBody), assertions.ShouldEqual, "{}") + + // Create some stats + stats.IncCounter("this.is-a-counter") + stats.UpdateHistogram("and.this.is.a-histogram", 123) + stats.MarkMeter("and.this.is.a-meter") + + // Not Empty anymore + r4, _ := http.NewRequest("GET", "/status/", nil) + r4.RemoteAddr = "127.0.0.1:12345" + rw4 := NewResponseWriter() + s.Handle(&rw4, make(chan<- PktReq), make(chan<- RegReq), r4) + a.So(rw4.TheStatus, assertions.ShouldEqual, 200) + a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "\"is-a-counter\"") + a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "p_50") + a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "rate_15") +} diff --git a/utils/testing/response_writer.go b/utils/testing/response_writer.go new file mode 100644 index 000000000..853af0a2b --- /dev/null +++ b/utils/testing/response_writer.go @@ -0,0 +1,33 @@ +package testing + +import "net/http" + +// ResponseWriter mocks http.ResponseWriter +type responseWriter struct { + TheHeaders *http.Header + TheStatus int + TheBody []byte +} + +// Header implements http.ResponseWriter +func (rw *responseWriter) Header() http.Header { + return *rw.TheHeaders +} + +// Write implements http.ResponseWriter +func (rw *responseWriter) Write(m []byte) (int, error) { + rw.TheBody = m + return len(m), nil +} + +// WriteHeader implements http.ResponseWriter +func (rw *responseWriter) WriteHeader(h int) { + rw.TheStatus = h +} + +func NewResponseWriter() responseWriter { + h := http.Header{} + return responseWriter{ + TheHeaders: &h, + } +} From aab341daa3309d854150b3b4e7c0e849a77940a1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 16:57:12 +0100 Subject: [PATCH 0792/2266] [testing] Create test for HTTP utils --- core/adapters/http/handlers/statuspage_test.go | 4 ++-- core/adapters/http/utils_test.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 core/adapters/http/utils_test.go diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go index 82a0cc3a9..957594999 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/handlers/statuspage_test.go @@ -10,7 +10,7 @@ import ( "github.com/smartystreets/assertions" ) -func TestURL(t *testing.T) { +func TestStatusPageURL(t *testing.T) { a := assertions.New(t) s := StatusPage{} @@ -18,7 +18,7 @@ func TestURL(t *testing.T) { a.So(s.Url(), assertions.ShouldEqual, "/status/") } -func TestHandler(t *testing.T) { +func TestStatusPageHandler(t *testing.T) { a := assertions.New(t) s := StatusPage{} diff --git a/core/adapters/http/utils_test.go b/core/adapters/http/utils_test.go new file mode 100644 index 000000000..aba26832d --- /dev/null +++ b/core/adapters/http/utils_test.go @@ -0,0 +1,16 @@ +package http + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/smartystreets/assertions" +) + +func TestBadRequest(t *testing.T) { + rw := ResponseWriter{} + BadRequest(&rw, "Test") + a := assertions.New(t) + a.So(rw.TheStatus, assertions.ShouldEqual, 400) + a.So(string(rw.TheBody), assertions.ShouldEqual, "Test") +} From e44a358ba6a3df36bf0872869a445f51eeebffcd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 17:00:50 +0100 Subject: [PATCH 0793/2266] [testing] Create test for Healthz handler --- core/adapters/http/handlers/healthz_test.go | 32 +++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 core/adapters/http/handlers/healthz_test.go diff --git a/core/adapters/http/handlers/healthz_test.go b/core/adapters/http/handlers/healthz_test.go new file mode 100644 index 000000000..d8659edf2 --- /dev/null +++ b/core/adapters/http/handlers/healthz_test.go @@ -0,0 +1,32 @@ +package handlers + +import ( + "net/http" + "testing" + + . "github.com/TheThingsNetwork/ttn/core/adapters/http" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/smartystreets/assertions" +) + +func TestHealthzURL(t *testing.T) { + a := assertions.New(t) + + h := Healthz{} + + a.So(h.Url(), assertions.ShouldEqual, "/healthz") +} + +func TestHealthzHandle(t *testing.T) { + a := assertions.New(t) + + h := Healthz{} + + req, _ := http.NewRequest("GET", "/healthz", nil) + req.RemoteAddr = "127.0.0.1:12345" + rw := NewResponseWriter() + + h.Handle(&rw, make(chan<- PktReq), make(chan<- RegReq), req) + a.So(rw.TheStatus, assertions.ShouldEqual, 200) + a.So(string(rw.TheBody), assertions.ShouldEqual, "ok") +} From ad27fc669dce83483b890980b62b1b1a961b312e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 17:02:59 +0100 Subject: [PATCH 0794/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index 2641e7f43..614361244 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.AW1GTRi8Dq4eU1d0sM6mFjfyL4eLDnnufjW9ZCzZ7d1dDj1TcvbueI9PVRSfQK0sXaM5P8seHXeu2VY_D5bry5mAxM8LEdgvPZnBwMV8O6AMlqi-F8YQNg13RdPfK_8U9D-zLKeE7FsBDE2tPieqwPGZq14E_pQre_ARmUVwNxUllVcZjqK4VSORkkGwWLVFY2QBwb4b5uCUBvEr7JUSR-MRESjxvjU_Y1zVMQhP5eu_D4GmRAdYcrQyjTZdhAqNepRs6n8NFS9Rrykd7KBHiNKieD3HL6tFrl-pzBIrQpVjFLvbgTnxHGUATrUVSmJF7SngcSzJfUbxFpbC12QOKQ.GgRViVdsyHpGMA2r.eYrRejM3rqtUsQ7iWFHt4M85_Jael-qB9t07MHO16fMzJGD23wn3WcQHah5mfFe5ZLXWO5ZKOrmZTD9gok1tb9aMO1UCB1Tpd6V-XfI2hMLizlqBEJrLQL94bqTDa2LeBtNqFN0HUiTX5uMf-MAulLTjhl1_igRA8ZTQu6GYDJ0z2U-6Att-zRfj9SQHFw1eIpVmq34rwYh3J1o5aHCr4Xq9u2L4CQxKEmJ1axNx3UO7GKHKZ5169TLdxplZ0bIGuppVgk3m.ZDDpizJRoLR5RxtTw8YkRw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.b5RzQwQ6SOJwAonWkWnVbLSn5NzDAWQBywlcC4y_EkMmo596lG5DRfTcKaJnl2_psAyEPtijE2kX5_n8ebAyb0cuujdi9MLlQQcA_fN_twpmyb4K_yPmjuXZUI-4E9ZawIAbCMx-W8VKfYidb5mNlL8hVR8Ut4nJwFonWAmSNrOvt7ERVDOlYzEEfRuqszC0P_Q4E5FgBGJhIRLxTjUgkI4H9oGspynAoaAvm2mc-VuHpYW17ELENPInOarSbSXHWYtzXZ2fD7b7kygcj8vNfLJ2h55vBPbcloWSbv8PmX0y5Tj6jlG_VkhVlQZzT4noP1RJWJqxIb1RZfwU_3vetw._KD9zQ5YEsDs728C.QLPY-fFN_Ng7Ccprx-XDnGh20QNiH8e6DJrYw7zuhrVuw_J_Jd7GGSMGZ5d4BP3xhEJFbJWVFCdwXNqsibBECQgWLrWUKDnGrsFZBi6o9qlSP42XjLrudsRHzTlRiWOqIp3sJ5NQ9idwTZvzPejv8qfM83CRfKC_LpRQTGBW6WnIBhqzbCG-Z7y6NzhVicsjX9_Q_talwKmXs-7aqXKnS242yGZuMyp9G78_3mFnbfk4aEAy0iM4BQcOE6qS7VsTXpjqQN6O.wdDlDWO_1zoCXdoJ0jsz-A \ No newline at end of file From fe6cefac96437b65dcca68ff27e20cee7417b695 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 17:13:29 +0100 Subject: [PATCH 0795/2266] Fix utils_test --- core/adapters/http/utils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/http/utils_test.go b/core/adapters/http/utils_test.go index aba26832d..a6c520e05 100644 --- a/core/adapters/http/utils_test.go +++ b/core/adapters/http/utils_test.go @@ -8,7 +8,7 @@ import ( ) func TestBadRequest(t *testing.T) { - rw := ResponseWriter{} + rw := NewResponseWriter() BadRequest(&rw, "Test") a := assertions.New(t) a.So(rw.TheStatus, assertions.ShouldEqual, 400) From f1a00caa828acfe306b21f7a5f2a62ba3b7bb9c9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 15:43:56 +0100 Subject: [PATCH 0796/2266] Implements missing NextRegistration() method in mqtt adapter --- core/adapters/mqtt/mqtt.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index cf8a80eab..a8eee7f50 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -191,7 +191,8 @@ func (a *Adapter) Next() ([]byte, core.AckNacker, error) { // NextRegistration implements the core.Adapter interface. Not implemented for this adapters. func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return nil, nil, nil + r := <-a.registrations + return r.Registration, mqttAckNacker{Chresp: r.Chresp}, nil } // Bind registers a handler to a specific endpoint From 5e9f38c5b00e3f834c8ac5ee8ac7541c9e25607f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 16:25:54 +0100 Subject: [PATCH 0797/2266] [handler-downlink] Implements the handleReception in activation adapter -> now produces APacket to handler --- core/adapters/mqtt/handlers/activation.go | 43 ++++++++++++++++--- .../adapters/mqtt/handlers/activation_test.go | 4 +- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 63b07df65..aeeebdc8b 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -9,6 +9,7 @@ import ( "strings" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" @@ -56,16 +57,17 @@ func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegR copy(appEUI[:], data[:]) devEUIStr = hex.EncodeToString(devEUI[:]) - topic := fmt.Sprintf("%s/%s/%s/%s", appEUIStr, "devices", devEUIStr, "up") - token := client.Subscribe(topic, 2, a.handleReception(chpkt)) + topic := fmt.Sprintf("%s/%s/%s", appEUIStr, "devices", devEUIStr) + topicUp := fmt.Sprintf("%s/%s", topic, "up") + topicDown := fmt.Sprintf("%s/%s", topic, "down") + token := client.Subscribe(topicDown, 2, a.handleReception(chpkt)) if token.Wait() && token.Error() != nil { - // TODO Log Error return errors.New(errors.Operational, token.Error()) } chreg <- RegReq{ Registration: activationRegistration{ - recipient: NewRecipient(topic, "DO_NOT_USE_THIS_TOPIC"), + recipient: NewRecipient(topicUp, "DO_NOT_USE_THIS_TOPIC"), devEUI: devEUI, appEUI: appEUI, nwkSKey: nwkSKey, @@ -76,8 +78,39 @@ func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegR return nil } +// Handle an incoming downlink packet on the registered channel func (a Activation) handleReception(chpkt chan<- PktReq) func(client Client, msg MQTT.Message) { return func(client Client, msg MQTT.Message) { - // TODO + infos := strings.Split(msg.Topic(), "/") + + if len(infos) != 4 { + return + } + + appEUIRaw, erra := hex.DecodeString(infos[0]) + devEUIRaw, errd := hex.DecodeString(infos[2]) + if erra != nil || errd != nil { + return + } + + var appEUI lorawan.EUI64 + copy(appEUI[:], appEUIRaw) + var devEUI lorawan.EUI64 + copy(devEUI[:], devEUIRaw) + + apacket, err := core.NewAPacket(appEUI, devEUI, msg.Payload(), nil) + if err != nil { + return + } + + data, err := apacket.MarshalBinary() + if err != nil { + return + } + + chpkt <- PktReq{ + Packet: data, + Chresp: nil, + } } } diff --git a/core/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go index f664a7d95..7f8d2985a 100644 --- a/core/adapters/mqtt/handlers/activation_test.go +++ b/core/adapters/mqtt/handlers/activation_test.go @@ -54,7 +54,7 @@ func TestActivationHandle(t *testing.T) { }, WantError: nil, - WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/up"), + WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/down"), WantRegistration: activationRegistration{ recipient: NewRecipient("0101010101010101/devices/0000000002020202/up", "WHATEVER"), devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 2, 2, 2, 2}), @@ -151,7 +151,7 @@ func TestActivationHandle(t *testing.T) { }, WantError: pointer.String(string(errors.Operational)), - WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/up"), + WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/down"), WantRegistration: nil, WantPacket: nil, }, From 6abed66a1c14cfedc6453602719eb6ff524d5598 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 16:27:41 +0100 Subject: [PATCH 0798/2266] [handler-downlink] Distinguish cases where packet is sent on nil Chresp in mqttAckNacker --- core/adapters/mqtt/mqttAckNacker.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/adapters/mqtt/mqttAckNacker.go b/core/adapters/mqtt/mqttAckNacker.go index 98657a44e..3f20483d3 100644 --- a/core/adapters/mqtt/mqttAckNacker.go +++ b/core/adapters/mqtt/mqttAckNacker.go @@ -17,9 +17,12 @@ type mqttAckNacker struct { // Ack implements the core.AckNacker interface func (an mqttAckNacker) Ack(p core.Packet) error { - if an.Chresp == nil { + if an.Chresp == nil && p == nil { return nil } + if an.Chresp == nil && p != nil { + return errors.New(errors.Structural, "Unable to send any packet through this acknacker") + } defer close(an.Chresp) if p == nil { From 77f13cd2b338766b7500bd61d28cfd8585521dd5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 16:27:55 +0100 Subject: [PATCH 0799/2266] [handler-downlink] Add appEUI to APacket --- core/packets.go | 20 ++++++++------------ core/packets_interfaces.go | 1 + 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/core/packets.go b/core/packets.go index 5be6ce813..e8eddfe56 100644 --- a/core/packets.go +++ b/core/packets.go @@ -306,38 +306,34 @@ func (p hpacket) String() string { // apacket implements the core.APacket interface type apacket struct { baseapacket - devEUI baseapacket + basehpacket basegpacket } // NewAPacket constructs a new application packet -func NewAPacket(payload []byte, devEUI lorawan.EUI64, metadata []Metadata) (APacket, error) { +func NewAPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, metadata []Metadata) (APacket, error) { if len(payload) == 0 { return nil, errors.New(errors.Structural, "Application packet must hold a payload") } return &apacket{ + basehpacket: basehpacket{ + devEUI: devEUI, + appEUI: appEUI, + }, baseapacket: baseapacket{payload: payload}, - devEUI: baseapacket{payload: devEUI[:]}, basegpacket: basegpacket{metadata: metadata}, }, nil } -// DevEUI implements the core.APacket interface -func (p apacket) DevEUI() lorawan.EUI64 { - var devEUI lorawan.EUI64 - copy(devEUI[:], p.devEUI.payload) - return devEUI -} - // MarshalBinary implements the encoding.BinaryMarshaler interface func (p apacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_apacket, p.baseapacket, p.devEUI, p.basegpacket) + return marshalBases(type_apacket, p.basehpacket, p.baseapacket, p.basegpacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *apacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_apacket, data, &p.baseapacket, &p.devEUI, &p.basegpacket) + return unmarshalBases(type_apacket, data, &p.basehpacket, &p.baseapacket, &p.basegpacket) } // String implements the fmt.Stringer interface diff --git a/core/packets_interfaces.go b/core/packets_interfaces.go index 9b4f91849..1aa5f095b 100644 --- a/core/packets_interfaces.go +++ b/core/packets_interfaces.go @@ -48,6 +48,7 @@ type HPacket interface { type APacket interface { Packet + AppEUI() lorawan.EUI64 Payload() []byte Metadata() []Metadata } From 6dd127071f4f9cbcd028cbc45ccf90cff042b850 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 16:57:57 +0100 Subject: [PATCH 0800/2266] [handler-downlink] Write additional tests for mqtt/handlers --- .../adapters/mqtt/handlers/activation_test.go | 90 ++++++++++++++++++- core/adapters/mqtt/handlers/helpers_test.go | 19 +++- 2 files changed, 105 insertions(+), 4 deletions(-) diff --git a/core/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go index 7f8d2985a..4637a0e10 100644 --- a/core/adapters/mqtt/handlers/activation_test.go +++ b/core/adapters/mqtt/handlers/activation_test.go @@ -5,6 +5,7 @@ package handlers import ( "testing" + "time" "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" @@ -41,7 +42,7 @@ func TestActivationHandle(t *testing.T) { WantError *string // The expected error from the handler WantSubscription *string // The topic to which a subscription is expected WantRegistration core.HRegistration // The expected registration towards the adapter - WantPacket []byte // The expected packet towards the adapter + WantPacket core.Packet // The expected packet towards the adapter }{ { Desc: "Ok client | Valid Topic | Valid Payload", @@ -170,6 +171,7 @@ func TestActivationHandle(t *testing.T) { payload: test.Payload, topic: test.Topic, }) + <-time.After(time.Millisecond * 100) // Check CheckErrors(t, test.WantError, err) @@ -178,3 +180,89 @@ func TestActivationHandle(t *testing.T) { checkPackets(t, test.WantPacket, consumer.Packet) } } + +func TestHandleReception(t *testing.T) { + packet, _ := core.NewAPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + []byte{1, 2, 3, 4}, + nil, + ) + + tests := []struct { + Desc string + Client *testClient + Payload []byte + Topic string + + WantPacket core.Packet + }{ + { + Desc: "Valid Payload | Valid Topic", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/0202020202020202/down", + + WantPacket: packet, + }, + { + Desc: "Valid Payload | Invalid Topic #2", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/0202020202020202/down/again", + + WantPacket: nil, + }, + { + Desc: "Valid Payload | Invalid Topic", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/0202020202020202", + + WantPacket: nil, + }, + { + Desc: "Valid Payload | Invalid AppEUI", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "010101/devices/0202020202020202/down", + + WantPacket: nil, + }, + { + Desc: "Valid Payload | Invalid DevEUI", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/020202/down", + + WantPacket: nil, + }, + { + Desc: "Invalid Payload | Valid Topic", + Client: newTestClient(), + Payload: []byte{}, + Topic: "0101010101010101/devices/0202020202020202/down", + + WantPacket: nil, + }, + } + for i, test := range tests { + // Describe + Desc(t, "#%d: %s", i, test.Desc) + + // Build + consumer, chpkt, _ := newTestConsumer() + handler := Activation{} + + // Operate + f := handler.handleReception(chpkt) + f(test.Client, testMessage{ + test.Payload, + test.Topic, + }) + <-time.After(time.Millisecond * 100) + + // Check + checkPackets(t, test.WantPacket, consumer.Packet) + } +} diff --git a/core/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go index 9539e1dc9..0ae30da8e 100644 --- a/core/adapters/mqtt/handlers/helpers_test.go +++ b/core/adapters/mqtt/handlers/helpers_test.go @@ -268,10 +268,23 @@ func checkSubscriptions(t *testing.T, want *string, got *string) { Ok(t, "Check Subscriptions") } -func checkPackets(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { +func checkPackets(t *testing.T, want core.Packet, got []byte) { + if want == nil { + if got == nil { + Ok(t, "Check Packets") + return + } + Ko(t, "Expected no packet but got %v", got) + return + } + + data, err := want.MarshalBinary() + if err != nil { + panic(err) + } + if reflect.DeepEqual(data, got) { Ok(t, "Check Packets") return } - Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) + Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", data, got) } From 01baca0befbaed25e8617179d035f1a3b558d26f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 16:58:08 +0100 Subject: [PATCH 0801/2266] [handler-downlink] Fix activation handler to make test pass --- core/adapters/mqtt/handlers/activation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index aeeebdc8b..0ffd25e09 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -89,7 +89,7 @@ func (a Activation) handleReception(chpkt chan<- PktReq) func(client Client, msg appEUIRaw, erra := hex.DecodeString(infos[0]) devEUIRaw, errd := hex.DecodeString(infos[2]) - if erra != nil || errd != nil { + if erra != nil || errd != nil || len(appEUIRaw) != 8 || len(devEUIRaw) != 8 { return } From 1c523afd4396126ae622955846e5453dad8270ed Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 17:24:06 +0100 Subject: [PATCH 0802/2266] [handler-downlink] Use of a APacket then HPAcket in downlink --- core/components/handler/devStorage.go | 3 ++ core/components/handler/handler.go | 40 +++++++++++++++++++++++---- core/components/handler/pktStorage.go | 18 ++++++------ 3 files changed, 47 insertions(+), 14 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 36af53314..7faacd2f1 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -20,6 +20,7 @@ type DevStorage interface { type devEntry struct { Recipient []byte + DevAddr lorawan.DevAddr AppSKey lorawan.AES128Key NwkSKey lorawan.AES128Key } @@ -84,6 +85,7 @@ func (s devStorage) Close() error { func (e devEntry) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write(e.Recipient) + rw.Write(e.DevAddr) rw.Write(e.AppSKey) rw.Write(e.NwkSKey) return rw.Bytes() @@ -93,6 +95,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { func (e *devEntry) UnmarshalBinary(data []byte) error { rw := readwriter.New(data) rw.Read(func(data []byte) { e.Recipient = data }) + rw.Read(func(data []byte) { copy(e.DevAddr[:], data) }) rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) return rw.Err() diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 57110ae34..39a35296e 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" "github.com/apex/log" + "github.com/brocaar/lorawan" ) const buffer_delay time.Duration = time.Millisecond * 300 @@ -199,7 +200,7 @@ browseBundles: } // Then create an application-level packet - packet, err := NewAPacket(payload, bestBundle.Packet.DevEUI(), metadata) + packet, err := NewAPacket(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI(), payload, metadata) if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -226,10 +227,39 @@ browseBundles: continue browseBundles } - // Then respond to node -> no response for the moment + // Then respond to node for _, bundle := range bundles { if bundle.Id == bestBundle.Id { - bundle.Chresp <- down + macPayload := lorawan.NewMACPayload(false) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: bestBundle.Entry.DevAddr, + // TODO Take care of the Adaptative Rate and other stuff + FCnt: bestBundle.Packet.FCnt() + 1, + } + macPayload.FPort = 1 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: down.Payload(), + }} + + if err := macPayload.EncryptFRMPayload(bestBundle.Entry.AppSKey); err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + payload := lorawan.NewPHYPayload(false) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + resp, err := NewHPacket(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI(), payload, Metadata{}) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + bundle.Chresp <- resp } else { bundle.Chresp <- nil } @@ -318,8 +348,8 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro } switch itf.(type) { - case HPacket: - return h.packets.Push(itf.(HPacket)) + case APacket: + return h.packets.Push(itf.(APacket)) default: return errors.New(errors.Implementation, "Unhandled packet type") } diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 1183fa103..bf8ede34c 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -13,8 +13,8 @@ import ( ) type PktStorage interface { - Push(p HPacket) error - Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, error) + Push(p APacket) error + Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) } type pktStorage struct { @@ -23,7 +23,7 @@ type pktStorage struct { } type pktEntry struct { - HPacket + APacket } // NewPktStorage creates a new PktStorage @@ -40,7 +40,7 @@ func keyFromEUIs(appEUI lorawan.EUI64, devEUI lorawan.EUI64) []byte { } // Push implements the PktStorage interface -func (s pktStorage) Push(p HPacket) error { +func (s pktStorage) Push(p APacket) error { err := s.db.Store(s.Name, keyFromEUIs(p.AppEUI(), p.DevEUI()), []dbutil.Entry{&pktEntry{p}}) if err != nil { return errors.New(errors.Operational, err) @@ -49,7 +49,7 @@ func (s pktStorage) Push(p HPacket) error { } // Pull implements the PktStorage interface -func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, error) { +func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { key := keyFromEUIs(appEUI, devEUI) entries, err := s.db.Lookup(s.Name, key, &pktEntry{}) @@ -80,12 +80,12 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (HPacket, e return nil, errors.New(errors.Operational, "Unable to restore data in db") } - return pkt.HPacket, nil + return pkt.APacket, nil } // MarshalBinary implements the encoding.BinaryMarshaler interface func (e pktEntry) MarshalBinary() ([]byte, error) { - return e.HPacket.MarshalBinary() + return e.APacket.MarshalBinary() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface @@ -94,10 +94,10 @@ func (e *pktEntry) UnmarshalBinary(data []byte) error { if err != nil { return errors.New(errors.Structural, err) } - packet, ok := itf.(HPacket) + packet, ok := itf.(APacket) if !ok { return errors.New(errors.Structural, "Not a Handler packet") } - e.HPacket = packet + e.APacket = packet return nil } From 6ab9c96ecb7367722778de9b1f4d39f9458daf28 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 18:21:51 +0100 Subject: [PATCH 0803/2266] [test-handler] Add String() method to APackets --- core/packets.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/packets.go b/core/packets.go index e8eddfe56..6e389384c 100644 --- a/core/packets.go +++ b/core/packets.go @@ -338,7 +338,13 @@ func (p *apacket) UnmarshalBinary(data []byte) error { // String implements the fmt.Stringer interface func (p apacket) String() string { - return "TODO" + return fmt.Sprintf( + "APacket{AppEUI:%v,DevEUI:%v,Payload:%v,Metadata:%v", + p.AppEUI(), + p.DevEUI(), + p.Payload(), + p.Metadata(), + ) } // --------------------------------- From 428e8f9969cc620047b2f813adbb6a0d019864e0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 18:22:19 +0100 Subject: [PATCH 0804/2266] [test-handler] Add Close() method to PktStorage --- core/components/handler/pktStorage.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index bf8ede34c..1904ae866 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -15,6 +15,7 @@ import ( type PktStorage interface { Push(p APacket) error Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) + Close() error } type pktStorage struct { @@ -83,6 +84,11 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, e return pkt.APacket, nil } +// Close implements the PktStorage interface +func (s pktStorage) Close() error { + return s.db.Close() +} + // MarshalBinary implements the encoding.BinaryMarshaler interface func (e pktEntry) MarshalBinary() ([]byte, error) { return e.APacket.MarshalBinary() From ce90406d5924b65c805f84e08bcf23f704d441f2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 18:22:36 +0100 Subject: [PATCH 0805/2266] [test-handler] Add First tests to packet storage --- core/components/handler/pktStorage_test.go | 86 ++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 core/components/handler/pktStorage_test.go diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go new file mode 100644 index 000000000..794bad1ce --- /dev/null +++ b/core/components/handler/pktStorage_test.go @@ -0,0 +1,86 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "os" + "reflect" + "testing" + + . "github.com/TheThingsNetwork/ttn/core" + //"github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +const pktDB = "TestPktStorage.db" + +func TestPushPullNormal(t *testing.T) { + var db PktStorage + defer func() { + os.Remove(pktDB) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + var err error + db, err = NewPktStorage(pktDB) + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Push and Pull a valid APacket") + p, _ := NewAPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + []byte("TheThingsNetwork"), + []Metadata{}, + ) + err := db.Push(p) + CheckErrors(t, nil, err) + + a, err := db.Pull(p.AppEUI(), p.DevEUI()) + CheckErrors(t, nil, err) + CheckPackets(t, p, a) + } + + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ + // ------------------ +} + +func TestPushPullErrors(t *testing.T) { + Ko(t, "TODO") +} + +//type mockStorage struct { +// Store(name string, key []byte, entries []Entry) error +// Replace(name string, key []byte, entries []Entry) error +// Lookup(name string, key []byte, shape Entry) (interface{}, error) +// Flush(name string, key []byte) error +// Reset(name string) error +// Close() error +//} + +// ----- CHECK utilities +func CheckPackets(t *testing.T, want APacket, got APacket) { + if reflect.DeepEqual(want, got) { + Ok(t, "Check Packets") + return + } + Ko(t, "Obtained packet doesn't match expectations.\nWant: %s\nGot: %s", want, got) +} From b9da3ab55d373eb75879d84033db125a1818af32 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 18:22:51 +0100 Subject: [PATCH 0806/2266] [test-handler] Fix uncessary pointer reference to pktEntry --- core/components/handler/pktStorage.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 1904ae866..768d9db60 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -58,7 +58,7 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, e return nil, errors.New(errors.Operational, err) } - packets, ok := entries.([]*pktEntry) + packets, ok := entries.([]pktEntry) if !ok { return nil, errors.New(errors.Operational, "Unable to retrieve data from db") } @@ -73,7 +73,7 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, e var newEntries []dbutil.Entry for _, p := range packets[1:] { - newEntries = append(newEntries, p) + newEntries = append(newEntries, &p) } if err := s.db.Replace(s.Name, key, newEntries); err != nil { From 55ef936ffb4ab5229709a58c6547d73dcb208ec0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 29 Feb 2016 18:29:57 +0100 Subject: [PATCH 0807/2266] [test-handler] Test several push/pull --- core/components/handler/handler_test.go | 19 ++++++++++ core/components/handler/pktStorage_test.go | 42 +++++++++++++++------- 2 files changed, 48 insertions(+), 13 deletions(-) create mode 100644 core/components/handler/handler_test.go diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go new file mode 100644 index 000000000..7977027a6 --- /dev/null +++ b/core/components/handler/handler_test.go @@ -0,0 +1,19 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + // . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestRegister(t *testing.T) { +} + +func TestHandleDown(t *testing.T) { +} + +func TestHandleUp(t *testing.T) { +} diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 794bad1ce..805829063 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -51,6 +51,35 @@ func TestPushPullNormal(t *testing.T) { } // ------------------ + + { + Desc(t, "Push two packets") + p1, _ := NewAPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + []byte("TheThingsNetwork1"), + []Metadata{}, + ) + p2, _ := NewAPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + []byte("TheThingsNetwork2"), + []Metadata{}, + ) + err := db.Push(p1) + CheckErrors(t, nil, err) + err = db.Push(p2) + CheckErrors(t, nil, err) + + a, err := db.Pull(p1.AppEUI(), p1.DevEUI()) + CheckErrors(t, nil, err) + CheckPackets(t, p1, a) + + a, err = db.Pull(p1.AppEUI(), p1.DevEUI()) + CheckErrors(t, nil, err) + CheckPackets(t, p2, a) + } + // ------------------ // ------------------ // ------------------ @@ -63,19 +92,6 @@ func TestPushPullNormal(t *testing.T) { // ------------------ } -func TestPushPullErrors(t *testing.T) { - Ko(t, "TODO") -} - -//type mockStorage struct { -// Store(name string, key []byte, entries []Entry) error -// Replace(name string, key []byte, entries []Entry) error -// Lookup(name string, key []byte, shape Entry) (interface{}, error) -// Flush(name string, key []byte) error -// Reset(name string) error -// Close() error -//} - // ----- CHECK utilities func CheckPackets(t *testing.T, want APacket, got APacket) { if reflect.DeepEqual(want, got) { From e11d33c77610d976b4e0940ab95a3db0c0cb932c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 29 Feb 2016 19:42:19 +0100 Subject: [PATCH 0808/2266] Implement first part of packets test Still to do: - Error cases - String() - Different MTypes --- core/packets_test.go | 243 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 241 insertions(+), 2 deletions(-) diff --git a/core/packets_test.go b/core/packets_test.go index 427823d95..5143ffca1 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -3,6 +3,245 @@ package core -import () +import ( + "math/rand" + "testing" -// TODO + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func randBytes(n int) []byte { + bytes := make([]byte, n) + for i := range bytes { + bytes[i] = byte(rand.Intn(255)) + } + return bytes +} + +func newEUI() lorawan.EUI64 { + devEUI := [8]byte{} + copy(devEUI[:], randBytes(8)) + return devEUI +} + +func simplePayload() (payload lorawan.PHYPayload, devAddr lorawan.DevAddr, key lorawan.AES128Key) { + copy(devAddr[:], randBytes(4)) + copy(key[:], randBytes(16)) + + payload = newPayload(devAddr, []byte("PLD123"), key, key) + return +} + +func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, nwkSKey lorawan.AES128Key) lorawan.PHYPayload { + uplink := true + + macPayload := lorawan.NewMACPayload(uplink) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: devAddr, + FCtrl: lorawan.FCtrl{ + ADR: false, + ADRACKReq: false, + ACK: false, + }, + FCnt: 1, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: data}} + + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + panic(err) + } + + payload := lorawan.NewPHYPayload(uplink) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + if err := payload.SetMIC(nwkSKey); err != nil { + panic(err) + } + + return payload +} + +func marshalUnmarshal(t *testing.T, input Packet) interface{} { + binary, err := input.MarshalBinary() + if err != nil { + Ko(t, "Unexpected error {%s}.", err) + } + + gOutput, err := UnmarshalPacket(binary) + if err != nil { + Ko(t, "Unexpected error {%s}.", err) + } + + return gOutput +} + +func TestPacket(t *testing.T) { + input := basegpacket{ + metadata: []Metadata{ + Metadata{ + Codr: pointer.String("4/6"), + }, + }, + } + + binary, _ := input.Marshal() + + output := basegpacket{ + metadata: []Metadata{}, + } + + _, err := output.Unmarshal(binary) + if err != nil { + Ko(t, "Unexpected error {%s}.", err) + } +} + +func TestRPacket(t *testing.T) { + a := New(t) + + payload, devAddr, _ := simplePayload() + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + + gOutput := marshalUnmarshal(t, input) + + output, ok := gOutput.(RPacket) + if !ok { + Ko(t, "Didn't get an RPacket back") + } + + a.So(output.Payload(), ShouldResemble, payload) + a.So(output.GatewayId(), ShouldResemble, gwEUI) + a.So(output.Metadata(), ShouldResemble, Metadata{}) + outputDevEUI := output.DevEUI() + a.So(outputDevEUI[4:], ShouldResemble, devAddr[:]) +} + +func TestSPacket(t *testing.T) { + // Nope +} + +func TestBPacket(t *testing.T) { + a := New(t) + + payload, _, key := simplePayload() + input, _ := NewBPacket(payload, Metadata{}) + + gOutput := marshalUnmarshal(t, input) + + output, ok := gOutput.(BPacket) + if !ok { + Ko(t, "Didn't get an BPacket back") + } + + a.So(output.Payload(), ShouldResemble, payload) + a.So(output.Metadata(), ShouldResemble, Metadata{}) + outputValidateMIC, _ := output.ValidateMIC(key) + a.So(outputValidateMIC, ShouldBeTrue) + a.So(output.Commands(), ShouldBeEmpty) +} + +func TestHPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + payload, _, key := simplePayload() + + input, _ := NewHPacket(appEUI, devEUI, payload, Metadata{}) + + gOutput := marshalUnmarshal(t, input) + + output, ok := gOutput.(HPacket) + if !ok { + Ko(t, "Didn't get an HPacket back") + } + + a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) + a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) + outPayload, _ := output.Payload(key) + a.So(string(outPayload), ShouldResemble, "PLD123") + a.So(output.Metadata(), ShouldResemble, Metadata{}) + a.So(output.FCnt(), ShouldEqual, 1) +} + +func TestAPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + payload := []byte("PLD123") + + input, _ := NewAPacket(appEUI, devEUI, payload, []Metadata{}) + + gOutput := marshalUnmarshal(t, input) + + output, ok := gOutput.(APacket) + if !ok { + Ko(t, "Didn't get an APacket back") + } + + a.So(output.Payload(), ShouldResemble, payload) + a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) + a.So(output.Payload(), ShouldResemble, payload) + a.So(output.Metadata(), ShouldBeEmpty) +} + +func TestJPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + devNonce := [2]byte{} + copy(devEUI[:], randBytes(2)) + + input := NewJPacket(appEUI, devEUI, devNonce, Metadata{}) + + gOutput := marshalUnmarshal(t, input) + + output, ok := gOutput.(JPacket) + if !ok { + Ko(t, "Didn't get an JPacket back") + } + + a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) + a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) + a.So(output.DevNonce(), ShouldEqual, devNonce) + a.So(output.Metadata(), ShouldResemble, Metadata{}) +} + +func TestCPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + payload := []byte("PLD123") + + nwkSKey := [16]byte{} + copy(devEUI[:], randBytes(16)) + + input, _ := NewCPacket(appEUI, devEUI, payload, nwkSKey) + + gOutput := marshalUnmarshal(t, input) + + output, ok := gOutput.(CPacket) + if !ok { + Ko(t, "Didn't get an CPacket back") + } + + a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) + a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) + a.So(output.Payload(), ShouldResemble, payload) + outputNwkSKey := output.NwkSKey() + a.So(outputNwkSKey[:], ShouldResemble, nwkSKey[:]) +} From 0a787dd03e71a01090cc34d4c35be3bc0f48fad9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 10:01:29 +0100 Subject: [PATCH 0809/2266] [test-handler] Finalize test of 'normal' behaviour for pktStorage --- core/components/handler/pktStorage_test.go | 43 ++++++++++++++++++---- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 805829063..07ff671ba 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -9,8 +9,9 @@ import ( "testing" . "github.com/TheThingsNetwork/ttn/core" - //"github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -81,15 +82,43 @@ func TestPushPullNormal(t *testing.T) { } // ------------------ + + { + Desc(t, "Pull a non existing entry") + p, err := db.Pull(lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}), lorawan.EUI64([8]byte{2, 3, 4, 2, 3, 4, 2, 3})) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckPackets(t, nil, p) + } + // ------------------ + + { + Desc(t, "Close the storage") + err := db.Close() + CheckErrors(t, nil, err) + } + // ------------------ + + { + Desc(t, "Push after close") + p, _ := NewAPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + []byte("TheThingsNetwork"), + []Metadata{}, + ) + err := db.Push(p) + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + // ------------------ - // ------------------ - // ------------------ - // ------------------ - // ------------------ - // ------------------ - // ------------------ + + { + Desc(t, "Pull after close") + _, err := db.Pull(lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2})) + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } } // ----- CHECK utilities From 40a5a751423bfbac94a1d6d39efbea73d695720a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 10:01:48 +0100 Subject: [PATCH 0810/2266] [test-handler] Fix pktStorage and storageUtil -> make test pass --- core/components/handler/pktStorage.go | 2 +- utils/storage/storage.go | 28 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 768d9db60..35eb9041c 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -55,7 +55,7 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, e entries, err := s.db.Lookup(s.Name, key, &pktEntry{}) if err != nil { - return nil, errors.New(errors.Operational, err) + return nil, err // Operational || Behavioural } packets, ok := entries.([]pktEntry) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index d8a54f81d..70b5ccbd5 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -49,6 +49,18 @@ func New(name string) (Interface, error) { return store{db}, nil } +// Make sure we return a failure +func ensureErr(err error) error { + if err == nil { + return nil + } + _, ok := err.(errors.Failure) + if !ok { + err = errors.New(errors.Operational, err) + } + return err +} + // getBucket retrieve a bucket based on a string. The name might present several levels, all // separated by dot "." which indicates nested buckets. If no bucket is found along the path, they // are created, otherwise, the existing one is used. @@ -108,7 +120,7 @@ func (itf store) Store(bucketName string, key []byte, entries []Entry) error { return nil }) - return err + return ensureErr(err) } // Lookup retrieves a set of entry from a given bolt database. @@ -132,7 +144,7 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} }) if err != nil { - return nil, err + return nil, ensureErr(err) } // Then, interpret them as instance of 'shape' @@ -158,7 +170,7 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} // Flush remove an entry from a bucket func (itf store) Flush(bucketName string, key []byte) error { - return itf.db.Update(func(tx *bolt.Tx) error { + return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { bucket, err := getBucket(tx, bucketName) if err != nil { return err @@ -167,7 +179,7 @@ func (itf store) Flush(bucketName string, key []byte) error { return errors.New(errors.Operational, err) } return nil - }) + })) } // Replace stores entries in the database by replacing them by a new set @@ -182,7 +194,7 @@ func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { marshalled = append(marshalled, m) } - return itf.db.Update(func(tx *bolt.Tx) error { + return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { bucket, err := getBucket(tx, bucketName) if err != nil { return err @@ -202,12 +214,12 @@ func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { return errors.New(errors.Operational, err) } return nil - }) + })) } // Reset resets a given bucket from a given bolt database func (itf store) Reset(bucketName string) error { - return itf.db.Update(func(tx *bolt.Tx) error { + return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { path := strings.Split(bucketName, ".") var cursor interface { @@ -232,7 +244,7 @@ func (itf store) Reset(bucketName string) error { return errors.New(errors.Operational, err) } return nil - }) + })) } // Close terminates the db connection From 6db200a891093b80e711eaa32e6d94d22f0416b1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 10:04:05 +0100 Subject: [PATCH 0811/2266] [test-handler] Add blank test files where needed to also take them in account during coverage --- core/adapters/udp/udp_test.go | 4 ++++ core/components/broker/broker_test.go | 4 ++++ core/components/router/router_test.go | 4 ++++ 3 files changed, 12 insertions(+) create mode 100644 core/adapters/udp/udp_test.go create mode 100644 core/components/broker/broker_test.go create mode 100644 core/components/router/router_test.go diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go new file mode 100644 index 000000000..92d304cd3 --- /dev/null +++ b/core/adapters/udp/udp_test.go @@ -0,0 +1,4 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go new file mode 100644 index 000000000..9c183f8cd --- /dev/null +++ b/core/components/broker/broker_test.go @@ -0,0 +1,4 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go new file mode 100644 index 000000000..dca7dd239 --- /dev/null +++ b/core/components/router/router_test.go @@ -0,0 +1,4 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router From f2495c45cd095ec69836bd6a4ca2841588050b75 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 10:12:14 +0100 Subject: [PATCH 0812/2266] [test-handler] Remove unecessary lines to make travis build --- core/components/handler/handler_test.go | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 7977027a6..aadbfdc78 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -2,18 +2,3 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handler - -import ( - "testing" - - // . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestRegister(t *testing.T) { -} - -func TestHandleDown(t *testing.T) { -} - -func TestHandleUp(t *testing.T) { -} From b1ce94a607ccfc51b88a69753f74f64381912fe2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Mar 2016 10:57:47 +0100 Subject: [PATCH 0813/2266] [testing] Add tests for error cases to core/packets --- core/packets_test.go | 243 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 202 insertions(+), 41 deletions(-) diff --git a/core/packets_test.go b/core/packets_test.go index 5143ffca1..28a321521 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -70,38 +69,130 @@ func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, } func marshalUnmarshal(t *testing.T, input Packet) interface{} { + a := New(t) + binary, err := input.MarshalBinary() - if err != nil { - Ko(t, "Unexpected error {%s}.", err) - } + a.So(err, ShouldBeNil) gOutput, err := UnmarshalPacket(binary) - if err != nil { - Ko(t, "Unexpected error {%s}.", err) - } + a.So(err, ShouldBeNil) + + a.So(gOutput, ShouldHaveSameTypeAs, input) return gOutput } +func TestInvalidMarshalBases(t *testing.T) { + // Only err when Metadata Marshal returns err +} + +func TestBaseMarshalUnmarshal(t *testing.T) { + a := New(t) + + s := uint(123) + mpkt := basempacket{metadata: Metadata{Size: &s}} + + payload, _, _ := simplePayload() + rpkt := baserpacket{payload: payload} + hpkt := basehpacket{ + appEUI: newEUI(), + devEUI: newEUI(), + } + apkt := baseapacket{ + payload: []byte{0x01, 0x02, 0x03}, + } + gpkt := basegpacket{metadata: []Metadata{ + Metadata{Size: &s}, + }} + + binmpkt, err1 := mpkt.Marshal() + a.So(err1, ShouldBeNil) + binrpkt, err2 := rpkt.Marshal() + a.So(err2, ShouldBeNil) + binhpkt, err3 := hpkt.Marshal() + a.So(err3, ShouldBeNil) + binapkt, err4 := apkt.Marshal() + a.So(err4, ShouldBeNil) + bingpkt, err5 := gpkt.Marshal() + a.So(err5, ShouldBeNil) + + newmpkt := basempacket{} + newrpkt := baserpacket{} + newhpkt := basehpacket{} + newapkt := baseapacket{} + newgpkt := basegpacket{} + + _, err6 := newmpkt.Unmarshal(binmpkt) + a.So(err6, ShouldBeNil) + a.So(*newmpkt.Metadata().Size, ShouldEqual, s) + + _, err7 := newrpkt.Unmarshal(binrpkt) + a.So(err7, ShouldBeNil) + // a.So() + + _, err8 := newhpkt.Unmarshal(binhpkt) + a.So(err8, ShouldBeNil) + + _, err9 := newapkt.Unmarshal(binapkt) + a.So(err9, ShouldBeNil) + + _, erra := newgpkt.Unmarshal(bingpkt) + a.So(erra, ShouldBeNil) + a.So(*newgpkt.Metadata()[0].Size, ShouldEqual, s) + +} + +func TestInvalidUnmarshalBases(t *testing.T) { + a := New(t) + + p := basempacket{} + + err1 := unmarshalBases(0x01, []byte{}, &p) + a.So(err1, ShouldNotBeNil) + + err2 := unmarshalBases(0x01, []byte{0x02}, &p) + a.So(err2, ShouldNotBeNil) + + err3 := unmarshalBases(0x01, []byte{0x01}, &p) + a.So(err3, ShouldNotBeNil) +} + +func TestInvalidUnmarshalPacket(t *testing.T) { + a := New(t) + _, err := UnmarshalPacket([]byte{}) + a.So(err, ShouldNotBeNil) +} + func TestPacket(t *testing.T) { - input := basegpacket{ - metadata: []Metadata{ - Metadata{ - Codr: pointer.String("4/6"), - }, + a := New(t) + input := basempacket{ + metadata: Metadata{ + Codr: pointer.String("4/6"), }, } binary, _ := input.Marshal() - output := basegpacket{ - metadata: []Metadata{}, + output := basempacket{ + metadata: Metadata{}, } _, err := output.Unmarshal(binary) - if err != nil { - Ko(t, "Unexpected error {%s}.", err) - } + a.So(err, ShouldBeNil) +} + +func TestInvalidRPacket(t *testing.T) { + a := New(t) + + // No MACPayload + _, err1 := NewRPacket(lorawan.PHYPayload{}, []byte{}, Metadata{}) + a.So(err1, ShouldNotBeNil) + + // Not a MACPayload + _, err2 := NewRPacket(lorawan.PHYPayload{ + MACPayload: &lorawan.JoinRequestPayload{}, + }, []byte{}, Metadata{}) + a.So(err2, ShouldNotBeNil) } func TestRPacket(t *testing.T) { @@ -115,22 +206,51 @@ func TestRPacket(t *testing.T) { gOutput := marshalUnmarshal(t, input) - output, ok := gOutput.(RPacket) - if !ok { - Ko(t, "Didn't get an RPacket back") - } + output := gOutput.(RPacket) a.So(output.Payload(), ShouldResemble, payload) a.So(output.GatewayId(), ShouldResemble, gwEUI) a.So(output.Metadata(), ShouldResemble, Metadata{}) outputDevEUI := output.DevEUI() a.So(outputDevEUI[4:], ShouldResemble, devAddr[:]) + + // TODO: Different MTypes } func TestSPacket(t *testing.T) { // Nope } +func TestInvalidBPacket(t *testing.T) { + a := New(t) + + // No MACPayload + _, err1 := NewBPacket(lorawan.PHYPayload{}, Metadata{}) + a.So(err1, ShouldNotBeNil) + + // Not a MACPayload + _, err2 := NewBPacket(lorawan.PHYPayload{ + MACPayload: &lorawan.JoinRequestPayload{}, + }, Metadata{}) + a.So(err2, ShouldNotBeNil) + + // Not enough FRMPayloads + _, err3 := NewBPacket(lorawan.PHYPayload{ + MACPayload: &lorawan.MACPayload{}, + }, Metadata{}) + a.So(err3, ShouldNotBeNil) + + // FRMPayload is not DataPayload + _, err4 := NewBPacket(lorawan.PHYPayload{ + MACPayload: &lorawan.MACPayload{ + FRMPayload: []lorawan.Payload{ + &lorawan.JoinRequestPayload{}, + }, + }, + }, Metadata{}) + a.So(err4, ShouldNotBeNil) +} + func TestBPacket(t *testing.T) { a := New(t) @@ -139,10 +259,7 @@ func TestBPacket(t *testing.T) { gOutput := marshalUnmarshal(t, input) - output, ok := gOutput.(BPacket) - if !ok { - Ko(t, "Didn't get an BPacket back") - } + output := gOutput.(BPacket) a.So(output.Payload(), ShouldResemble, payload) a.So(output.Metadata(), ShouldResemble, Metadata{}) @@ -151,6 +268,39 @@ func TestBPacket(t *testing.T) { a.So(output.Commands(), ShouldBeEmpty) } +func TestInvalidHPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + + // No MACPayload + _, err1 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{}, Metadata{}) + a.So(err1, ShouldNotBeNil) + + // Not a MACPayload + _, err2 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{ + MACPayload: &lorawan.JoinRequestPayload{}, + }, Metadata{}) + a.So(err2, ShouldNotBeNil) + + // Not enough FRMPayloads + _, err3 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{ + MACPayload: &lorawan.MACPayload{}, + }, Metadata{}) + a.So(err3, ShouldNotBeNil) + + // FRMPayload is not DataPayload + _, err4 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{ + MACPayload: &lorawan.MACPayload{ + FRMPayload: []lorawan.Payload{ + &lorawan.JoinRequestPayload{}, + }, + }, + }, Metadata{}) + a.So(err4, ShouldNotBeNil) +} + func TestHPacket(t *testing.T) { a := New(t) @@ -162,10 +312,7 @@ func TestHPacket(t *testing.T) { gOutput := marshalUnmarshal(t, input) - output, ok := gOutput.(HPacket) - if !ok { - Ko(t, "Didn't get an HPacket back") - } + output := gOutput.(HPacket) a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) @@ -175,6 +322,17 @@ func TestHPacket(t *testing.T) { a.So(output.FCnt(), ShouldEqual, 1) } +func TestInvalidAPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + + // No Payload + _, err1 := NewAPacket(appEUI, devEUI, []byte{}, []Metadata{}) + a.So(err1, ShouldNotBeNil) +} + func TestAPacket(t *testing.T) { a := New(t) @@ -186,10 +344,7 @@ func TestAPacket(t *testing.T) { gOutput := marshalUnmarshal(t, input) - output, ok := gOutput.(APacket) - if !ok { - Ko(t, "Didn't get an APacket back") - } + output := gOutput.(APacket) a.So(output.Payload(), ShouldResemble, payload) a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) @@ -209,10 +364,7 @@ func TestJPacket(t *testing.T) { gOutput := marshalUnmarshal(t, input) - output, ok := gOutput.(JPacket) - if !ok { - Ko(t, "Didn't get an JPacket back") - } + output := gOutput.(JPacket) a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) @@ -220,6 +372,18 @@ func TestJPacket(t *testing.T) { a.So(output.Metadata(), ShouldResemble, Metadata{}) } +func TestInvalidCPacket(t *testing.T) { + a := New(t) + + appEUI := newEUI() + devEUI := newEUI() + nwkSKey := lorawan.AES128Key{} + + // No Payload + _, err1 := NewCPacket(appEUI, devEUI, []byte{}, nwkSKey) + a.So(err1, ShouldNotBeNil) +} + func TestCPacket(t *testing.T) { a := New(t) @@ -234,10 +398,7 @@ func TestCPacket(t *testing.T) { gOutput := marshalUnmarshal(t, input) - output, ok := gOutput.(CPacket) - if !ok { - Ko(t, "Didn't get an CPacket back") - } + output := gOutput.(CPacket) a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) From 8c3f7c5bb0f05c5ca7aaa5eb78c3f917d03db9ec Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 11:29:40 +0100 Subject: [PATCH 0814/2266] [test-handler] Return behavioural error after bucket lookup not found --- utils/storage/storage.go | 3 +++ utils/storage/storage_test.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 70b5ccbd5..ab2f3003c 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -134,6 +134,9 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} err := itf.db.View(func(tx *bolt.Tx) error { bucket, err := getBucket(tx, bucketName) if err != nil { + if err.(errors.Failure).Fault == bolt.ErrTxNotWritable { + return errors.New(errors.Behavioural, fmt.Sprintf("Not found %+v", key)) + } return err } rawEntry = bucket.Get(key) diff --git a/utils/storage/storage_test.go b/utils/storage/storage_test.go index 48411b155..074326084 100644 --- a/utils/storage/storage_test.go +++ b/utils/storage/storage_test.go @@ -74,7 +74,7 @@ func TestStoreAndLookup(t *testing.T) { { Desc(t, "Lookup in non-existing bucket") entries, err := itf.Lookup("DoesntExist", []byte{1, 2, 3}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) CheckEntries(t, nil, entries) } @@ -143,7 +143,7 @@ func TestStoreAndLookup(t *testing.T) { err := itf.Reset("mybucket") CheckErrors(t, nil, err) entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) CheckEntries(t, nil, entries) } From a70ab96f2d8969d9766e3c6d4f08458fc83f5478 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 11:37:33 +0100 Subject: [PATCH 0815/2266] [test-handler] Write tests for devStorage --- core/components/handler/devStorage_test.go | 162 +++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 core/components/handler/devStorage_test.go diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go new file mode 100644 index 000000000..7717b296b --- /dev/null +++ b/core/components/handler/devStorage_test.go @@ -0,0 +1,162 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "os" + "reflect" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +const devDB = "TestDevStorage.db" + +func TestLookupStore(t *testing.T) { + var db DevStorage + defer func() { + os.Remove(devDB) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + var err error + db, err = NewDevStorage(devDB) + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Store and Lookup a registration") + r := newTestRegistration( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + ) + + err := db.StorePersonalized(r) + CheckErrors(t, nil, err) + entry, err := db.Lookup(r.AppEUI(), r.DevEUI()) + CheckErrors(t, nil, err) + CheckEntries(t, r, entry) + } + + // ------------------ + + { + Desc(t, "Lookup a non-existing registration") + r := newTestRegistration( + [8]byte{1, 1, 1, 1, 1, 1, 1, 2}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 3}, + ) + _, err := db.Lookup(r.AppEUI(), r.DevEUI()) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + } + + // ------------------ + + { + Desc(t, "Store twice the same registration") + r := newTestRegistration( + [8]byte{3, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{4, 2, 2, 2, 2, 2, 2, 2}, + ) + err := db.StorePersonalized(r) + CheckErrors(t, nil, err) + err = db.StorePersonalized(r) + CheckErrors(t, nil, err) + entry, err := db.Lookup(r.AppEUI(), r.DevEUI()) + CheckErrors(t, nil, err) + CheckEntries(t, r, entry) + } + + // ------------------ + + { + Desc(t, "Store Activated") + r := newTestRegistration( + [8]byte{3, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{4, 2, 2, 2, 2, 2, 2, 2}, + ) + err := db.StoreActivated(r) + CheckErrors(t, pointer.String(string(errors.Implementation)), err) + } + + // ------------------ + + { + Desc(t, "Close the storage") + err := db.Close() + CheckErrors(t, nil, err) + } + +} + +// ----- TYPE utilities +type testRegistration struct { + appEUI lorawan.EUI64 + devEUI lorawan.EUI64 +} + +func newTestRegistration(appEUI [8]byte, devEUI [8]byte) testRegistration { + r := testRegistration{} + copy(r.appEUI[:], appEUI[:]) + copy(r.devEUI[:], devEUI[:]) + return r +} + +func (r testRegistration) Recipient() Recipient { + return time.Date(2016, 3, 1, 11, 13, 6, 0, time.UTC) +} + +func (r testRegistration) RawRecipient() []byte { + data, _ := r.Recipient().MarshalBinary() + return data +} + +func (r testRegistration) AppEUI() lorawan.EUI64 { + return r.appEUI +} + +func (r testRegistration) DevEUI() lorawan.EUI64 { + return r.devEUI +} + +func (r testRegistration) DevAddr() lorawan.DevAddr { + devAddr := lorawan.DevAddr{} + copy(devAddr[:], r.devEUI[4:]) + return devAddr +} + +func (r testRegistration) NwkSKey() lorawan.AES128Key { + return lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}) +} + +func (r testRegistration) AppSKey() lorawan.AES128Key { + return lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}) +} + +// ----- CHECK utilities +func CheckEntries(t *testing.T, want testRegistration, got devEntry) { + wantEntry := devEntry{ + Recipient: want.RawRecipient(), + DevAddr: want.DevAddr(), + NwkSKey: want.NwkSKey(), + AppSKey: want.AppSKey(), + } + + if reflect.DeepEqual(wantEntry, got) { + Ok(t, "Check Entries") + return + } + Ko(t, "Check Entries") +} From 408591a8607e99a17db67f3ac9f288f3d5158e5d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 11:37:55 +0100 Subject: [PATCH 0816/2266] [test-handler] Make tests pass --- core/components/handler/devStorage.go | 20 +++++++++++++++----- core/components/handler/handler.go | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 7faacd2f1..b71d98b8a 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -15,7 +15,9 @@ import ( type DevStorage interface { Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) - Store(r HRegistration) error + StorePersonalized(r HRegistration) error + StoreActivated(r HRegistration) error + Close() error } type devEntry struct { @@ -48,7 +50,7 @@ func NewDevStorage(name string) (DevStorage, error) { func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), &devEntry{}) if err != nil { - return devEntry{}, errors.New(errors.Operational, err) + return devEntry{}, err // Behavioural || Operational } entries, ok := itf.([]devEntry) if !ok || len(entries) != 1 { @@ -57,10 +59,12 @@ func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry return entries[0], nil } -// Store implements the handler.DevStorage interface -func (s devStorage) Store(reg HRegistration) error { +// StorePersonalized implements the handler.DevStorage interface +func (s devStorage) StorePersonalized(reg HRegistration) error { appEUI := reg.AppEUI() devEUI := reg.DevEUI() + devAddr := lorawan.DevAddr{} + copy(devAddr[:], devEUI[4:]) data, err := reg.Recipient().MarshalBinary() if err != nil { return errors.New(errors.Structural, "Cannot marshal recipient") @@ -71,9 +75,15 @@ func (s devStorage) Store(reg HRegistration) error { Recipient: data, AppSKey: reg.AppSKey(), NwkSKey: reg.NwkSKey(), + DevAddr: devAddr, }, } - return s.db.Store(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), e) + return s.db.Replace(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), e) +} + +// StoreActivated implements the handler.DevStorage interface +func (s devStorage) StoreActivated(reg HRegistration) error { + return errors.New(errors.Implementation, "Not implemented yet") } // Close implements the handler.DevStorage interface diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 39a35296e..6d3ad218f 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -61,7 +61,7 @@ func (h component) Register(reg Registration, an AckNacker) (err error) { return errors.New(errors.Structural, "Not a Handler registration") } - if err = h.devices.Store(hreg); err != nil { + if err = h.devices.StorePersonalized(hreg); err != nil { return errors.New(errors.Operational, err) } return nil From f1fcb19b6ff32c17838990e9f9c67c6d65ac575f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 11:38:18 +0100 Subject: [PATCH 0817/2266] [test-handler] Use of fatal instead of basic log on utils/testing Ko() --- utils/testing/testing.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/utils/testing/testing.go b/utils/testing/testing.go index c56ec36f7..4f593afec 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -27,8 +27,7 @@ func Ok(t *testing.T, tag string) { // Ko fails the test and display a red cross symbol func Ko(t *testing.T, format string, a ...interface{}) { - t.Errorf("\033[31;1m\u2718 ko | \033[0m\033[31m%s\033[0m", fmt.Sprintf(format, a...)) - t.Fail() + t.Fatalf("\033[31;1m\u2718 ko | \033[0m\033[31m%s\033[0m", fmt.Sprintf(format, a...)) } // Desc displays the provided description in cyan From 66c01084a3096da04bdfaa7f78d703099795c261 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Mar 2016 13:35:48 +0100 Subject: [PATCH 0818/2266] Update file headers with copyright --- core/adapters/http/doc.go | 2 +- core/adapters/http/handlers/collect.go | 2 +- core/adapters/http/handlers/collect_test.go | 2 +- core/adapters/http/handlers/healthz.go | 2 +- core/adapters/http/handlers/healthz_test.go | 3 +++ core/adapters/http/handlers/helpers_test.go | 2 +- core/adapters/http/handlers/pubsub.go | 2 +- core/adapters/http/handlers/pubsubRegistration.go | 2 +- core/adapters/http/handlers/pubsub_test.go | 2 +- core/adapters/http/handlers/statuspage.go | 2 +- core/adapters/http/handlers/statuspage_test.go | 3 +++ core/adapters/http/http.go | 2 +- core/adapters/http/httpAckNacker.go | 2 +- core/adapters/http/httpRecipient.go | 2 +- core/adapters/http/httpRegistration.go | 2 +- core/adapters/http/http_test.go | 2 +- core/adapters/http/regAckNacker.go | 2 +- core/adapters/http/utils.go | 2 +- core/adapters/http/utils_test.go | 3 +++ core/adapters/mqtt/handlers/activation.go | 2 +- core/adapters/mqtt/handlers/activationRegistration.go | 2 +- core/adapters/mqtt/handlers/activation_test.go | 2 +- core/adapters/mqtt/handlers/helpers_test.go | 2 +- core/adapters/mqtt/mqtt.go | 2 +- core/adapters/mqtt/mqttAckNacker.go | 2 +- core/adapters/mqtt/mqttClient.go | 2 +- core/adapters/mqtt/mqttRecipient.go | 2 +- core/adapters/mqtt/mqtt_test.go | 2 +- core/adapters/udp/handlers/build_utilities_test.go | 2 +- core/adapters/udp/handlers/conversions_test.go | 2 +- core/adapters/udp/handlers/semtech.go | 2 +- core/adapters/udp/handlers/semtech_test.go | 2 +- core/adapters/udp/udp.go | 2 +- core/adapters/udp/udpAckNacker.go | 2 +- core/adapters/udp/udpRegistration.go | 2 +- core/build_utilities_test.go | 2 +- core/check_utilities_test.go | 2 +- core/components/broker/broker.go | 2 +- core/components/broker/storage.go | 2 +- core/components/controller/controller.go | 2 +- core/components/handler/devStorage.go | 2 +- core/components/handler/handler.go | 2 +- core/components/handler/pktStorage.go | 2 +- core/components/router/router.go | 2 +- core/components/router/storage.go | 2 +- core/core.go | 2 +- core/metadata.go | 2 +- core/metadata_test.go | 2 +- core/packets.go | 2 +- core/packets_interfaces.go | 2 +- core/packets_test.go | 2 +- core/registrations_interfaces.go | 2 +- doc.go | 3 +++ integration/mocker/main.go | 2 +- integration/scheduler/scheduler.go | 2 +- integration/udp_debugger/udp_debugger.go | 6 +++++- semtech/check_utilities.go | 2 +- semtech/decode.go | 2 +- semtech/decode_test.go | 2 +- semtech/encode.go | 2 +- semtech/encode_test.go | 2 +- semtech/proxies.go | 2 +- semtech/semtech.go | 2 +- simulators/gateway/doc.go | 2 +- simulators/gateway/forwarder.go | 2 +- simulators/gateway/forwarder_test.go | 2 +- simulators/gateway/imitator.go | 2 +- simulators/gateway/utils.go | 2 +- simulators/gateway/utils_test.go | 2 +- simulators/node/node.go | 3 +++ simulators/node/node_test.go | 3 +++ utils/errors/checks/checks.go | 2 +- utils/errors/errors.go | 2 +- utils/pointer/pointer.go | 2 +- utils/readwriter/readwriter.go | 2 +- utils/readwriter/readwriter_test.go | 2 +- utils/stats/stats.go | 2 +- utils/storage/storage.go | 2 +- utils/storage/storage_test.go | 2 +- utils/testing/log_handler.go | 3 +++ utils/testing/response_writer.go | 3 +++ utils/testing/testing.go | 2 +- 82 files changed, 102 insertions(+), 74 deletions(-) diff --git a/core/adapters/http/doc.go b/core/adapters/http/doc.go index aed3c9d11..c1dda9a0e 100644 --- a/core/adapters/http/doc.go +++ b/core/adapters/http/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package http provides adapter implementations which run on top of http. diff --git a/core/adapters/http/handlers/collect.go b/core/adapters/http/handlers/collect.go index b122b4b48..30bcd30d2 100644 --- a/core/adapters/http/handlers/collect.go +++ b/core/adapters/http/handlers/collect.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/collect_test.go b/core/adapters/http/handlers/collect_test.go index fd692f808..fead8eff8 100644 --- a/core/adapters/http/handlers/collect_test.go +++ b/core/adapters/http/handlers/collect_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/healthz.go b/core/adapters/http/handlers/healthz.go index cc6bc8777..132481416 100644 --- a/core/adapters/http/handlers/healthz.go +++ b/core/adapters/http/handlers/healthz.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/healthz_test.go b/core/adapters/http/handlers/healthz_test.go index d8659edf2..0348bfb36 100644 --- a/core/adapters/http/handlers/healthz_test.go +++ b/core/adapters/http/handlers/healthz_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handlers import ( diff --git a/core/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go index 2e01989e1..71181e754 100644 --- a/core/adapters/http/handlers/helpers_test.go +++ b/core/adapters/http/handlers/helpers_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index 194ba39ef..db8ae962c 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/pubsubRegistration.go b/core/adapters/http/handlers/pubsubRegistration.go index 78f82de84..d7c02b6b4 100644 --- a/core/adapters/http/handlers/pubsubRegistration.go +++ b/core/adapters/http/handlers/pubsubRegistration.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/pubsub_test.go b/core/adapters/http/handlers/pubsub_test.go index dedd48aa8..2c69dfc5d 100644 --- a/core/adapters/http/handlers/pubsub_test.go +++ b/core/adapters/http/handlers/pubsub_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go index 5c6df7349..fc8d351ea 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go index 957594999..135b0e038 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/handlers/statuspage_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handlers import ( diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 4bf0e51b4..7c3db035a 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/httpAckNacker.go b/core/adapters/http/httpAckNacker.go index 9700cab36..1d3c70532 100644 --- a/core/adapters/http/httpAckNacker.go +++ b/core/adapters/http/httpAckNacker.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/httpRecipient.go b/core/adapters/http/httpRecipient.go index 6f402fead..bbbf2dcb3 100644 --- a/core/adapters/http/httpRecipient.go +++ b/core/adapters/http/httpRecipient.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/httpRegistration.go b/core/adapters/http/httpRegistration.go index b2ad51533..07c70f9b8 100644 --- a/core/adapters/http/httpRegistration.go +++ b/core/adapters/http/httpRegistration.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index 80125295c..81b78e2ae 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/regAckNacker.go b/core/adapters/http/regAckNacker.go index 6341a48d2..bfce965ae 100644 --- a/core/adapters/http/regAckNacker.go +++ b/core/adapters/http/regAckNacker.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/utils.go b/core/adapters/http/utils.go index 7d2ae2045..8a721f350 100644 --- a/core/adapters/http/utils.go +++ b/core/adapters/http/utils.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package http diff --git a/core/adapters/http/utils_test.go b/core/adapters/http/utils_test.go index a6c520e05..73761d023 100644 --- a/core/adapters/http/utils_test.go +++ b/core/adapters/http/utils_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package http import ( diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 0ffd25e09..987dbe0ab 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/mqtt/handlers/activationRegistration.go b/core/adapters/mqtt/handlers/activationRegistration.go index de30c4c3b..da104cd4b 100644 --- a/core/adapters/mqtt/handlers/activationRegistration.go +++ b/core/adapters/mqtt/handlers/activationRegistration.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go index 4637a0e10..473ec2414 100644 --- a/core/adapters/mqtt/handlers/activation_test.go +++ b/core/adapters/mqtt/handlers/activation_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go index 0ae30da8e..47149485e 100644 --- a/core/adapters/mqtt/handlers/helpers_test.go +++ b/core/adapters/mqtt/handlers/helpers_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index a8eee7f50..fa05eeb80 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package mqtt diff --git a/core/adapters/mqtt/mqttAckNacker.go b/core/adapters/mqtt/mqttAckNacker.go index 3f20483d3..fe20a04a1 100644 --- a/core/adapters/mqtt/mqttAckNacker.go +++ b/core/adapters/mqtt/mqttAckNacker.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package mqtt diff --git a/core/adapters/mqtt/mqttClient.go b/core/adapters/mqtt/mqttClient.go index 081fb03be..c40a61d1d 100644 --- a/core/adapters/mqtt/mqttClient.go +++ b/core/adapters/mqtt/mqttClient.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package mqtt diff --git a/core/adapters/mqtt/mqttRecipient.go b/core/adapters/mqtt/mqttRecipient.go index efdcab5d0..454e048fc 100644 --- a/core/adapters/mqtt/mqttRecipient.go +++ b/core/adapters/mqtt/mqttRecipient.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package mqtt diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 89379b387..c00a27054 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package mqtt diff --git a/core/adapters/udp/handlers/build_utilities_test.go b/core/adapters/udp/handlers/build_utilities_test.go index d7d274abd..59a206fc6 100644 --- a/core/adapters/udp/handlers/build_utilities_test.go +++ b/core/adapters/udp/handlers/build_utilities_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/udp/handlers/conversions_test.go b/core/adapters/udp/handlers/conversions_test.go index 3871c095a..3e85f5a1e 100644 --- a/core/adapters/udp/handlers/conversions_test.go +++ b/core/adapters/udp/handlers/conversions_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index 2a7814867..432b248b9 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/udp/handlers/semtech_test.go b/core/adapters/udp/handlers/semtech_test.go index 0697232d4..5b1bfe994 100644 --- a/core/adapters/udp/handlers/semtech_test.go +++ b/core/adapters/udp/handlers/semtech_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index a3d03a1e1..ba1ff5dfe 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package udp diff --git a/core/adapters/udp/udpAckNacker.go b/core/adapters/udp/udpAckNacker.go index a2d9fc6cb..fede90f8d 100644 --- a/core/adapters/udp/udpAckNacker.go +++ b/core/adapters/udp/udpAckNacker.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package udp diff --git a/core/adapters/udp/udpRegistration.go b/core/adapters/udp/udpRegistration.go index f72b5adf5..6ec8c06cc 100644 --- a/core/adapters/udp/udpRegistration.go +++ b/core/adapters/udp/udpRegistration.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package udp diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go index f4d5fbe53..3e37af552 100644 --- a/core/build_utilities_test.go +++ b/core/build_utilities_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go index 045b6541e..e7122a03f 100644 --- a/core/check_utilities_test.go +++ b/core/check_utilities_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 5912092ad..558657113 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package broker diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 5f9aecad3..aac37578c 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package broker diff --git a/core/components/controller/controller.go b/core/components/controller/controller.go index 252afb5b9..a383f59cd 100644 --- a/core/components/controller/controller.go +++ b/core/components/controller/controller.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package controller diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 7faacd2f1..4a43617c3 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handler diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 39a35296e..b82ef7b48 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handler diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index bf8ede34c..57c2f80be 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handler diff --git a/core/components/router/router.go b/core/components/router/router.go index e1d3bc84f..79595cb34 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package router diff --git a/core/components/router/storage.go b/core/components/router/storage.go index a5a00ef39..5bdaf695a 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package router diff --git a/core/core.go b/core/core.go index 709630bc0..11804a521 100644 --- a/core/core.go +++ b/core/core.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/metadata.go b/core/metadata.go index 6c4a85cc6..bc46bd91a 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/metadata_test.go b/core/metadata_test.go index 2353bce21..0962af4dc 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/packets.go b/core/packets.go index e8eddfe56..41061f340 100644 --- a/core/packets.go +++ b/core/packets.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/packets_interfaces.go b/core/packets_interfaces.go index 1aa5f095b..247b0f4e6 100644 --- a/core/packets_interfaces.go +++ b/core/packets_interfaces.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/packets_test.go b/core/packets_test.go index 28a321521..6fe9d9346 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/core/registrations_interfaces.go b/core/registrations_interfaces.go index c2d86b9ef..fede88696 100644 --- a/core/registrations_interfaces.go +++ b/core/registrations_interfaces.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package core diff --git a/doc.go b/doc.go index 9577d37b1..d2b3deb6b 100644 --- a/doc.go +++ b/doc.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + /* Package ttn contains the backend server systems for The Things Network. */ diff --git a/integration/mocker/main.go b/integration/mocker/main.go index 1101219a1..259fe6925 100644 --- a/integration/mocker/main.go +++ b/integration/mocker/main.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package main diff --git a/integration/scheduler/scheduler.go b/integration/scheduler/scheduler.go index 7116b059e..cf3727a28 100644 --- a/integration/scheduler/scheduler.go +++ b/integration/scheduler/scheduler.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package main diff --git a/integration/udp_debugger/udp_debugger.go b/integration/udp_debugger/udp_debugger.go index 1177483df..19491b001 100644 --- a/integration/udp_debugger/udp_debugger.go +++ b/integration/udp_debugger/udp_debugger.go @@ -1,10 +1,14 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package main import ( "fmt" + "net" + "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/pointer" - "net" ) func main() { diff --git a/semtech/check_utilities.go b/semtech/check_utilities.go index 7086e21c4..75b0c152a 100644 --- a/semtech/check_utilities.go +++ b/semtech/check_utilities.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package semtech diff --git a/semtech/decode.go b/semtech/decode.go index 62e160ffa..5cffa38d5 100644 --- a/semtech/decode.go +++ b/semtech/decode.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package semtech diff --git a/semtech/decode_test.go b/semtech/decode_test.go index b27187296..3a0a93858 100644 --- a/semtech/decode_test.go +++ b/semtech/decode_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package semtech diff --git a/semtech/encode.go b/semtech/encode.go index 7b549da0d..14c2eba57 100644 --- a/semtech/encode.go +++ b/semtech/encode.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package semtech diff --git a/semtech/encode_test.go b/semtech/encode_test.go index 9b7648484..1d7210cf6 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package semtech diff --git a/semtech/proxies.go b/semtech/proxies.go index 934ce72d5..45c40564f 100644 --- a/semtech/proxies.go +++ b/semtech/proxies.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package semtech diff --git a/semtech/semtech.go b/semtech/semtech.go index 53303fec6..58dc64b70 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package semtech provides useful methods and types to handle communications with a gateway. diff --git a/simulators/gateway/doc.go b/simulators/gateway/doc.go index 8b0cf24f9..a579b6bc5 100644 --- a/simulators/gateway/doc.go +++ b/simulators/gateway/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package gateway offers a dummy representation of a gateway. diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go index 5e4fb90b7..a400518a6 100644 --- a/simulators/gateway/forwarder.go +++ b/simulators/gateway/forwarder.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go index c31378f1c..98eda7f7b 100644 --- a/simulators/gateway/forwarder_test.go +++ b/simulators/gateway/forwarder_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index f3f824cf3..984ea76cc 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go index 24815e936..9a1b5bdc3 100644 --- a/simulators/gateway/utils.go +++ b/simulators/gateway/utils.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go index 1ba730596..d7e7d6993 100644 --- a/simulators/gateway/utils_test.go +++ b/simulators/gateway/utils_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package gateway diff --git a/simulators/node/node.go b/simulators/node/node.go index 3a56c1915..3a51935cb 100644 --- a/simulators/node/node.go +++ b/simulators/node/node.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package node import ( diff --git a/simulators/node/node_test.go b/simulators/node/node_test.go index 97b2c0f49..63f9ea5d5 100644 --- a/simulators/node/node_test.go +++ b/simulators/node/node_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package node import ( diff --git a/utils/errors/checks/checks.go b/utils/errors/checks/checks.go index 568b7ea7f..b48d9789c 100644 --- a/utils/errors/checks/checks.go +++ b/utils/errors/checks/checks.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package checks diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 64750f59c..1da1231fc 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package errors diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 7dc11361c..166a712d0 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package pointer provides helper method to quickly define pointer from basic go types diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 49560f220..10e49a33d 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package readwriter diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go index 39417ddf5..6412fba77 100644 --- a/utils/readwriter/readwriter_test.go +++ b/utils/readwriter/readwriter_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package readwriter diff --git a/utils/stats/stats.go b/utils/stats/stats.go index d20beda1a..721ebe8f4 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package stats supports the collection of metrics from a running component. diff --git a/utils/storage/storage.go b/utils/storage/storage.go index d8a54f81d..9b9aee9a3 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package storage diff --git a/utils/storage/storage_test.go b/utils/storage/storage_test.go index 48411b155..f689ac18b 100644 --- a/utils/storage/storage_test.go +++ b/utils/storage/storage_test.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package storage diff --git a/utils/testing/log_handler.go b/utils/testing/log_handler.go index 73af0e0b3..8e8a54a0e 100644 --- a/utils/testing/log_handler.go +++ b/utils/testing/log_handler.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package testing import ( diff --git a/utils/testing/response_writer.go b/utils/testing/response_writer.go index 853af0a2b..6f21db791 100644 --- a/utils/testing/response_writer.go +++ b/utils/testing/response_writer.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package testing import "net/http" diff --git a/utils/testing/testing.go b/utils/testing/testing.go index c56ec36f7..98da6502d 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -1,4 +1,4 @@ -// Copyright © 2015 The Things Network +// Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package testing offers some handy methods to display check and cross symbols with colors in test From d52e08eb10d3e3da792a9099d707b891287562f5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 13:41:42 +0100 Subject: [PATCH 0819/2266] [test-handler] Write mock handler component for testing --- core/components/handler/handler_test.go | 119 ++++++++++++++++++++++++ 1 file changed, 119 insertions(+) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index aadbfdc78..ff22216bb 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -2,3 +2,122 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handler + +import ( + // "os" + // "reflect" + "testing" + // "time" + // + . "github.com/TheThingsNetwork/ttn/core" + // "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + // "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func TestRegister(t *testing.T) { + { + Desc(t, "Registration valid HRegistration") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + an := newMockAckNacker() + r := newTestRegistration( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + ) + + err := handler.Register(r, an) + CheckErrors(t, nil, err) + } +} + +// ----- TYPE utilities + +// +// MOCK DEV STORAGE +// +type mockDevStorage struct { + Failures map[string]error + LookupEntry devEntry + Personalized HRegistration + Activated HRegistration +} + +func newMockDevStorage(failures ...string) *mockDevStorage { + return &mockDevStorage{ + Failures: make(map[string]error), + } +} + +func (s mockDevStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { + if s.Failures["Lookup"] != nil { + return devEntry{}, s.Failures["Lookup"] + } + return s.LookupEntry, nil +} + +func (s *mockDevStorage) StorePersonalized(r HRegistration) error { + s.Personalized = r + return s.Failures["StorePersonalized"] +} + +func (s *mockDevStorage) StoreActivated(r HRegistration) error { + s.Activated = r + return s.Failures["StoreActivated"] +} + +func (s mockDevStorage) Close() error { + return s.Failures["Close"] +} + +// +// MOCK PKT STORAGE +// +type mockPktStorage struct { + Failures map[string]error + PullEntry APacket + Pushed APacket +} + +func newMockPktStorage() *mockPktStorage { + return &mockPktStorage{ + Failures: make(map[string]error), + } +} + +func (s *mockPktStorage) Push(p APacket) error { + s.Pushed = p + return s.Failures["Push"] +} + +func (s *mockPktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { + if s.Failures["Pull"] != nil { + return nil, s.Failures["Pull"] + } + return s.PullEntry, nil +} + +func (s *mockPktStorage) Close() error { + return s.Failures["Close"] +} + +// +// MOCK ACK/NACKER +// +type mockAckNacker struct{} + +func newMockAckNacker() *mockAckNacker { + return &mockAckNacker{} +} + +func (an mockAckNacker) Ack(p Packet) error { + return nil +} + +func (an mockAckNacker) Nack() error { + return nil +} From e9cf253ddd978cc4a0d7608d7816c2890329209f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 14:34:29 +0100 Subject: [PATCH 0820/2266] [test-handler] Fix issue in readwriter when there's no data to read, but still a valid data length --- utils/readwriter/readwriter.go | 6 +++++- utils/readwriter/readwriter_test.go | 11 +++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 49560f220..436c276eb 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -110,7 +110,11 @@ func (w *rw) read(to func(data []byte) error) error { if err := binary.Read(w.data, binary.BigEndian, lenTo); err != nil { return err } - return to(w.data.Next(int(*lenTo))) + next := w.data.Next(int(*lenTo)) + if len(next) != int(*lenTo) { + return errors.New(errors.Structural, "Not enough data to read") + } + return to(next) } // Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go index 39417ddf5..d8890b3d4 100644 --- a/utils/readwriter/readwriter_test.go +++ b/utils/readwriter/readwriter_test.go @@ -274,6 +274,17 @@ func TestReadWriter(t *testing.T) { }) CheckErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) } + + // ------------- + + { + Desc(t, "Not enough data to read") + rw := New([]byte{2, 3}) + rw.Read(func(data []byte) { + checkNotCalled(t) + }) + CheckErrors(t, pointer.String(string(errors.Structural)), rw.Err()) + } } // ----- CHECK utilities From 7e45fb861e68468609bf64d9e01e608dc3bffb81 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 14:43:44 +0100 Subject: [PATCH 0821/2266] [test-handler] Add tests of Register() method --- core/components/handler/handler_test.go | 120 ++++++++++++++++++++++-- 1 file changed, 113 insertions(+), 7 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index ff22216bb..6c4b473bf 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -4,34 +4,75 @@ package handler import ( - // "os" - // "reflect" + "reflect" "testing" // "time" - // + . "github.com/TheThingsNetwork/ttn/core" - // "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - // "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) func TestRegister(t *testing.T) { { - Desc(t, "Registration valid HRegistration") + Desc(t, "Register valid HRegistration") devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) an := newMockAckNacker() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) r := newTestRegistration( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, ) err := handler.Register(r, an) + CheckErrors(t, nil, err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, r, devStorage.Personalized) + } + + // -------------------- + + { + Desc(t, "Register invalid HRegistration") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + + err := handler.Register(nil, an) + + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + } + + // -------------------- + + { + Desc(t, "Register valid HRegistration | devStorage fails") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + r := newTestRegistration( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + ) + + devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") + err := handler.Register(r, an) + + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, r, devStorage.Personalized) } } @@ -121,3 +162,68 @@ func (an mockAckNacker) Ack(p Packet) error { func (an mockAckNacker) Nack() error { return nil } + +// +// MOCK ADAPTER +// +type mockAdapter struct { + Failures map[string]error + SentPkt Packet + SentRecipients []Recipient + SendData []byte + Recipient Recipient + NextPacket []byte + NextAckNacker AckNacker + NextReg Registration +} + +func newMockAdapter() *mockAdapter { + return &mockAdapter{ + Failures: make(map[string]error), + } +} + +func (a mockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { + a.SentPkt = p + a.SentRecipients = r + if a.Failures["Send"] != nil { + return nil, a.Failures["Send"] + } + return a.SendData, nil +} + +func (a mockAdapter) GetRecipient(raw []byte) (Recipient, error) { + if a.Failures["GetRecipient"] != nil { + return nil, a.Failures["Send"] + } + return a.Recipient, nil +} + +func (a *mockAdapter) Next() ([]byte, AckNacker, error) { + if a.Failures["Next"] != nil { + return nil, nil, a.Failures["Next"] + } + return a.NextPacket, a.NextAckNacker, nil +} + +func (a *mockAdapter) NextRegistration() (Registration, AckNacker, error) { + if a.Failures["NextRegistration"] != nil { + return nil, nil, a.Failures["NextRegistration"] + } + return a.NextReg, a.NextAckNacker, nil +} + +// ----- CHECK utilities +func CheckPushed(t *testing.T, want APacket, got APacket) { + if !reflect.DeepEqual(want, got) { + Ko(t, "Pushed packet does not match expectations.\nWant: %s\nGot: %s", want, got) + } + Ok(t, "Check Pushed") +} + +func CheckPersonalized(t *testing.T, want HRegistration, got HRegistration) { + if !reflect.DeepEqual(want, got) { + Ko(t, "Personalized packet does not match expectations.\nWant: %s\nGot: %s", want, got) + } + Ok(t, "Check Personalized") +} From 3dcca03ddb41de6078f80de0620a375669bfb978 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 14:44:00 +0100 Subject: [PATCH 0822/2266] [test-handler] Add tests of HandleDown method --- core/components/handler/handler_test.go | 68 +++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 6c4b473bf..bed90e7c5 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -76,6 +76,74 @@ func TestRegister(t *testing.T) { } } +func TestHandleDown(t *testing.T) { + { + Desc(t, "Handle downlink APacket") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + pkt, _ := NewAPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + []byte("TheThingsNetwork"), + []Metadata{}, + ) + + data, _ := pkt.MarshalBinary() + err := handler.HandleDown(data, an, adapter) + + CheckErrors(t, nil, err) + CheckPushed(t, pkt, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + } + + // -------------------- + + { + Desc(t, "Handle downlink wrong data") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + + err := handler.HandleDown([]byte{1, 2, 3}, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + } + + // -------------------- + + { + Desc(t, "Handle downlink wrong packet type") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + pkt := NewJPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + [2]byte{14, 42}, + Metadata{}, + ) + data, _ := pkt.MarshalBinary() + + err := handler.HandleDown(data, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Implementation)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + } +} + // ----- TYPE utilities // From e1c7eb5f83f051f26bc54807f7fe60e9b58303ef Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 14:44:11 +0100 Subject: [PATCH 0823/2266] [test-handler] Fix handler accordingly to tests --- core/components/handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 6d3ad218f..988cf7952 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -344,7 +344,7 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro // Unmarshal the given packet and see what gift we get itf, err := UnmarshalPacket(data) if err != nil { - return errors.New(errors.Structural, data) + return errors.New(errors.Structural, err) } switch itf.(type) { From f9a6513c02f5dc1f459b1b97735e512b7d251d0b Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 15:50:14 +0100 Subject: [PATCH 0824/2266] [test-handler] Start writing tests for HandleUp -> Write mock interfaces and first test --- core/components/handler/handler_test.go | 133 +++++++++++++++++++++--- 1 file changed, 121 insertions(+), 12 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index bed90e7c5..3e28393b8 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -4,6 +4,7 @@ package handler import ( + "fmt" "reflect" "testing" // "time" @@ -98,6 +99,9 @@ func TestHandleDown(t *testing.T) { CheckErrors(t, nil, err) CheckPushed(t, pkt, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, nil, an.Acked) + CheckSent(t, nil, adapter.SentPkt) + CheckRecipients(t, nil, adapter.SentRecipients) } // -------------------- @@ -116,6 +120,9 @@ func TestHandleDown(t *testing.T) { CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckPushed(t, nil, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, nil, an.Acked) + CheckSent(t, nil, adapter.SentPkt) + CheckRecipients(t, nil, adapter.SentRecipients) } // -------------------- @@ -141,6 +148,58 @@ func TestHandleDown(t *testing.T) { CheckErrors(t, pointer.String(string(errors.Implementation)), err) CheckPushed(t, nil, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, nil, an.Acked) + CheckSent(t, nil, adapter.SentPkt) + CheckRecipients(t, nil, adapter.SentRecipients) + } +} + +func TestHandleUp(t *testing.T) { + { + Desc(t, "Handle uplink with 1 packet | No Associated App") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + + devStorage.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock: Not Found") + pktStorage.Failures["Pull"] = errors.New(errors.Behavioural, "Mock: Not Found") + // adapter.Failures["Send"] = + // adapter.Failures["GetRecipient"] = + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, nil, an.Acked) + fmt.Printf("%+v\n", adapter) + CheckSent(t, nil, adapter.SentPkt) + CheckRecipients(t, nil, adapter.SentRecipients) + } + + // -------------------- + + { + + //sentPkt, _ := NewAPacket( + // inPkt.AppEUI(), + // inPkt.DevEUI(), + // []byte("Payload"), + // []Metadata{inPkt.Metadata()}, + //) } } @@ -217,17 +276,20 @@ func (s *mockPktStorage) Close() error { // // MOCK ACK/NACKER // -type mockAckNacker struct{} +type mockAckNacker struct { + Acked Packet +} func newMockAckNacker() *mockAckNacker { return &mockAckNacker{} } -func (an mockAckNacker) Ack(p Packet) error { +func (an *mockAckNacker) Ack(p Packet) error { + an.Acked = p return nil } -func (an mockAckNacker) Nack() error { +func (an *mockAckNacker) Nack() error { return nil } @@ -251,7 +313,7 @@ func newMockAdapter() *mockAdapter { } } -func (a mockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { +func (a *mockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { a.SentPkt = p a.SentRecipients = r if a.Failures["Send"] != nil { @@ -260,7 +322,7 @@ func (a mockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { return a.SendData, nil } -func (a mockAdapter) GetRecipient(raw []byte) (Recipient, error) { +func (a *mockAdapter) GetRecipient(raw []byte) (Recipient, error) { if a.Failures["GetRecipient"] != nil { return nil, a.Failures["Send"] } @@ -281,17 +343,64 @@ func (a *mockAdapter) NextRegistration() (Registration, AckNacker, error) { return a.NextReg, a.NextAckNacker, nil } +// ----- BUILD utilities +func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadata, appSKey [16]byte) HPacket { + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + FCnt: 0, + } + macPayload.FPort = 10 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + + var key lorawan.AES128Key + copy(key[:], appSKey[:]) + if err := macPayload.EncryptFRMPayload(key); err != nil { + panic(err) + } + + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.ConfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + + var appEUIp lorawan.EUI64 + var devEUIp lorawan.EUI64 + copy(appEUIp[:], appEUI[:]) + copy(devEUIp[:], devEUI[:]) + + packet, err := NewHPacket(appEUIp, devEUIp, phyPayload, metadata) + if err != nil { + panic(err) + } + return packet +} + // ----- CHECK utilities -func CheckPushed(t *testing.T, want APacket, got APacket) { +func check(t *testing.T, want, got interface{}, name string) { if !reflect.DeepEqual(want, got) { - Ko(t, "Pushed packet does not match expectations.\nWant: %s\nGot: %s", want, got) + Ko(t, "%s don't match expectations.\nWant: %v\nGot: %v", name, want, got) } - Ok(t, "Check Pushed") + Ok(t, fmt.Sprintf("Check %s", name)) +} + +func CheckPushed(t *testing.T, want APacket, got APacket) { + check(t, want, got, "Pushed") } func CheckPersonalized(t *testing.T, want HRegistration, got HRegistration) { - if !reflect.DeepEqual(want, got) { - Ko(t, "Personalized packet does not match expectations.\nWant: %s\nGot: %s", want, got) - } - Ok(t, "Check Personalized") + check(t, want, got, "Personalized") +} + +func CheckAcks(t *testing.T, want Packet, got Packet) { + check(t, want, got, "Acks") +} + +func CheckRecipients(t *testing.T, want []Recipient, got []Recipient) { + check(t, want, got, "Recipients") +} + +func CheckSent(t *testing.T, want Packet, got Packet) { + check(t, want, got, "Sent") } From 94711db317732a5f97ff7fe65f4e6c08f23fa54d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 15:50:31 +0100 Subject: [PATCH 0825/2266] [test-handler] Fix few things according to HandleUp tests --- core/components/handler/handler.go | 67 +++++++++++++++++------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 988cf7952..bbfa089e4 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -4,6 +4,7 @@ package handler import ( + "fmt" "reflect" "time" @@ -88,7 +89,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // 1. Lookup for the associated AppSKey + Recipient entry, err := h.devices.Lookup(appEUI, devEUI) if err != nil { - return errors.New(errors.Operational, err) + return err } // 2. Prepare a channel to receive the response from the consumer @@ -124,7 +125,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // If there's an error, all channels get the error. resp := <-chresp switch resp.(type) { - case Packet: + case BPacket: ctx.Debug("Received response with packet. Sending Ack") an.Ack(resp.(Packet)) case error: @@ -164,6 +165,7 @@ func (h component) consumeBundles(chbundle <-chan []bundle) { browseBundles: for bundles := range chbundle { + ctx.WithField("BundleID", bundles[0].Id).Debug("Consume new bundle") var metadata []Metadata var payload []byte var bestBundle bundle @@ -222,7 +224,7 @@ browseBundles: // Now handle the downlink down, err := h.packets.Pull(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI()) - if err != nil { + if err != nil && err.(errors.Failure).Nature != errors.Behavioural { go h.abortConsume(err, bundles) continue browseBundles } @@ -230,33 +232,38 @@ browseBundles: // Then respond to node for _, bundle := range bundles { if bundle.Id == bestBundle.Id { - macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: bestBundle.Entry.DevAddr, - // TODO Take care of the Adaptative Rate and other stuff - FCnt: bestBundle.Packet.FCnt() + 1, - } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: down.Payload(), - }} - - if err := macPayload.EncryptFRMPayload(bestBundle.Entry.AppSKey); err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } - - payload := lorawan.NewPHYPayload(false) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - resp, err := NewHPacket(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI(), payload, Metadata{}) - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles + var resp Packet + + if down != nil && err == nil { + macPayload := lorawan.NewMACPayload(false) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: bestBundle.Entry.DevAddr, + // TODO Take care of the Adaptative Rate and other stuff + FCnt: bestBundle.Packet.FCnt() + 1, + } + macPayload.FPort = 1 + fmt.Println(down) + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: down.Payload(), + }} + + if err := macPayload.EncryptFRMPayload(bestBundle.Entry.AppSKey); err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + + payload := lorawan.NewPHYPayload(false) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + resp, err = NewBPacket(payload, Metadata{}) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } } bundle.Chresp <- resp From 6da07f97e8b766725ea747c08573c77030ce4f70 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 18:59:05 +0100 Subject: [PATCH 0826/2266] [test-handler] Write down main tests for handler --- core/components/handler/devStorage_test.go | 69 +++- core/components/handler/handler_test.go | 389 +++++++++++++++++++-- 2 files changed, 415 insertions(+), 43 deletions(-) diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 7717b296b..53193678a 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -7,7 +7,6 @@ import ( "os" "reflect" "testing" - "time" . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -38,9 +37,10 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Store and Lookup a registration") - r := newTestRegistration( + r := newMockRegistration( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + newMockRecipient("MyRecipient"), ) err := db.StorePersonalized(r) @@ -54,9 +54,10 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Lookup a non-existing registration") - r := newTestRegistration( + r := newMockRegistration( [8]byte{1, 1, 1, 1, 1, 1, 1, 2}, [8]byte{2, 2, 2, 2, 2, 2, 2, 3}, + newMockRecipient("MyRecipient"), ) _, err := db.Lookup(r.AppEUI(), r.DevEUI()) CheckErrors(t, pointer.String(string(errors.Behavioural)), err) @@ -66,9 +67,10 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Store twice the same registration") - r := newTestRegistration( + r := newMockRegistration( [8]byte{3, 1, 1, 1, 1, 1, 1, 1}, [8]byte{4, 2, 2, 2, 2, 2, 2, 2}, + newMockRecipient("MyRecipient"), ) err := db.StorePersonalized(r) CheckErrors(t, nil, err) @@ -83,9 +85,10 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Store Activated") - r := newTestRegistration( + r := newMockRegistration( [8]byte{3, 1, 1, 1, 1, 1, 1, 1}, [8]byte{4, 2, 2, 2, 2, 2, 2, 2}, + newMockRecipient("MyRecipient"), ) err := db.StoreActivated(r) CheckErrors(t, pointer.String(string(errors.Implementation)), err) @@ -102,51 +105,79 @@ func TestLookupStore(t *testing.T) { } // ----- TYPE utilities -type testRegistration struct { - appEUI lorawan.EUI64 - devEUI lorawan.EUI64 +type mockRegistration struct { + appEUI lorawan.EUI64 + devEUI lorawan.EUI64 + recipient Recipient } -func newTestRegistration(appEUI [8]byte, devEUI [8]byte) testRegistration { - r := testRegistration{} +func newMockRegistration(appEUI [8]byte, devEUI [8]byte, recipient Recipient) mockRegistration { + r := mockRegistration{recipient: recipient} copy(r.appEUI[:], appEUI[:]) copy(r.devEUI[:], devEUI[:]) return r } -func (r testRegistration) Recipient() Recipient { - return time.Date(2016, 3, 1, 11, 13, 6, 0, time.UTC) +func (r mockRegistration) Recipient() Recipient { + return r.recipient } -func (r testRegistration) RawRecipient() []byte { +func (r mockRegistration) RawRecipient() []byte { data, _ := r.Recipient().MarshalBinary() return data } -func (r testRegistration) AppEUI() lorawan.EUI64 { +func (r mockRegistration) AppEUI() lorawan.EUI64 { return r.appEUI } -func (r testRegistration) DevEUI() lorawan.EUI64 { +func (r mockRegistration) DevEUI() lorawan.EUI64 { return r.devEUI } -func (r testRegistration) DevAddr() lorawan.DevAddr { +func (r mockRegistration) DevAddr() lorawan.DevAddr { devAddr := lorawan.DevAddr{} copy(devAddr[:], r.devEUI[4:]) return devAddr } -func (r testRegistration) NwkSKey() lorawan.AES128Key { +func (r mockRegistration) NwkSKey() lorawan.AES128Key { return lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}) } -func (r testRegistration) AppSKey() lorawan.AES128Key { +func (r mockRegistration) AppSKey() lorawan.AES128Key { return lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}) } +type mockRecipient struct { + Failures map[string]error + Data string +} + +func newMockRecipient(data string) *mockRecipient { + return &mockRecipient{ + Data: data, + Failures: make(map[string]error), + } +} + +func (r *mockRecipient) MarshalBinary() ([]byte, error) { + if r.Failures["MarshalBinary"] != nil { + return nil, r.Failures["MarshalBinary"] + } + return []byte(r.Data), nil +} + +func (r *mockRecipient) UnmarshalBinary(data []byte) error { + r.Data = string(data) + if r.Failures["UnmarshalBinary"] != nil { + return r.Failures["UnmarshalBinary"] + } + return nil +} + // ----- CHECK utilities -func CheckEntries(t *testing.T, want testRegistration, got devEntry) { +func CheckEntries(t *testing.T, want mockRegistration, got devEntry) { wantEntry := devEntry{ Recipient: want.RawRecipient(), DevAddr: want.DevAddr(), diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 3e28393b8..d6098dfc0 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -6,8 +6,9 @@ package handler import ( "fmt" "reflect" + "sync" "testing" - // "time" + "time" . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -25,9 +26,10 @@ func TestRegister(t *testing.T) { pktStorage := newMockPktStorage() an := newMockAckNacker() handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - r := newTestRegistration( + r := newMockRegistration( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + newMockRecipient("recipient"), ) err := handler.Register(r, an) @@ -63,9 +65,10 @@ func TestRegister(t *testing.T) { pktStorage := newMockPktStorage() an := newMockAckNacker() handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - r := newTestRegistration( + r := newMockRegistration( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + newMockRecipient("recipient"), ) devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") @@ -99,7 +102,7 @@ func TestHandleDown(t *testing.T) { CheckErrors(t, nil, err) CheckPushed(t, pkt, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, nil, an.Acked) + CheckAcks(t, true, an.Acked) CheckSent(t, nil, adapter.SentPkt) CheckRecipients(t, nil, adapter.SentRecipients) } @@ -120,7 +123,7 @@ func TestHandleDown(t *testing.T) { CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckPushed(t, nil, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, nil, an.Acked) + CheckAcks(t, false, an.Acked) CheckSent(t, nil, adapter.SentPkt) CheckRecipients(t, nil, adapter.SentRecipients) } @@ -148,7 +151,7 @@ func TestHandleDown(t *testing.T) { CheckErrors(t, pointer.String(string(errors.Implementation)), err) CheckPushed(t, nil, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, nil, an.Acked) + CheckAcks(t, false, an.Acked) CheckSent(t, nil, adapter.SentPkt) CheckRecipients(t, nil, adapter.SentRecipients) } @@ -171,36 +174,309 @@ func TestHandleUp(t *testing.T) { Duty: pointer.Uint(5), Rssi: pointer.Int(-25), }, + 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() devStorage.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock: Not Found") pktStorage.Failures["Pull"] = errors.New(errors.Behavioural, "Mock: Not Found") - // adapter.Failures["Send"] = - // adapter.Failures["GetRecipient"] = err := handler.HandleUp(dataIn, an, adapter) CheckErrors(t, pointer.String(string(errors.Behavioural)), err) CheckPushed(t, nil, pktStorage.Pushed) CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, nil, an.Acked) - fmt.Printf("%+v\n", adapter) + CheckAcks(t, false, an.Acked) CheckSent(t, nil, adapter.SentPkt) CheckRecipients(t, nil, adapter.SentRecipients) } + { + Desc(t, "Handle uplink with invalid data") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + + err := handler.HandleUp([]byte{1, 2, 3}, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, false, an.Acked) + CheckSent(t, nil, adapter.SentPkt) + CheckRecipients(t, nil, adapter.SentRecipients) + } + + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | No downlink ready") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + + adapter.Recipient = recipient + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, nil, err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, true, an.Acked) + CheckSent(t, pktSent, adapter.SentPkt) + CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + } + + // -------------------- + + { + Desc(t, "Handle uplink with 2 packets in a row | No downlink ready") + + // Handler + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + + // Recipient + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + + // First Packet + adapter1 := newMockAdapter() + adapter1.Recipient = recipient + an1 := newMockAckNacker() + inPkt1 := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(75), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn1, _ := inPkt1.MarshalBinary() + + // Second Packet + adapter2 := newMockAdapter() + adapter2.Recipient = recipient + an2 := newMockAckNacker() + inPkt2 := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(0), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn2, _ := inPkt2.MarshalBinary() + + // Expected response + pktSent, _ := NewAPacket( + inPkt1.AppEUI(), + inPkt1.DevEUI(), + []byte("Payload"), + []Metadata{inPkt1.Metadata(), inPkt2.Metadata()}, + ) + + // Fake response from the storage + done := sync.WaitGroup{} + done.Add(2) + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + go func() { + defer done.Done() + err := handler.HandleUp(dataIn1, an1, adapter1) + CheckErrors(t, nil, err) + CheckAcks(t, true, an1.Acked) + CheckSent(t, nil, adapter1.SentPkt) + CheckRecipients(t, nil, adapter1.SentRecipients) + }() + + go func() { + <-time.After(time.Millisecond * 50) + defer done.Done() + err := handler.HandleUp(dataIn2, an2, adapter2) + CheckErrors(t, nil, err) + CheckAcks(t, true, an2.Acked) + CheckSent(t, pktSent, adapter2.SentPkt) // Adapter2 because the adapter of the best bundle even if they are supposed to be identical + CheckRecipients(t, []Recipient{recipient}, adapter2.SentRecipients) + }() + + done.Wait() + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + } + // -------------------- { + Desc(t, "Handle uplink with 1 packet | One downlink response") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + brkResp := newBPacket( + [4]byte{2, 2, 2, 2}, + "Downlink", + Metadata{}, + 11, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + appResp, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Downlink"), + []Metadata{}, + ) + + adapter.Recipient = recipient + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + pktStorage.PullEntry = appResp + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, nil, err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, brkResp, an.Acked) + CheckSent(t, pktSent, adapter.SentPkt) + CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + } + + // --------------- + + { + Desc(t, "Handle a late uplink | No downlink ready") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an2 := newMockAckNacker() + an1 := newMockAckNacker() + adapter1 := newMockAdapter() + adapter2 := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + + adapter1.Recipient = recipient + adapter2.Recipient = recipient + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + + done := sync.WaitGroup{} + done.Add(2) + + go func() { + defer done.Done() + err := handler.HandleUp(dataIn, an1, adapter1) + CheckErrors(t, nil, err) + CheckAcks(t, true, an1.Acked) + CheckSent(t, pktSent, adapter1.SentPkt) + }() + + go func() { + defer done.Done() + <-time.After(2 * buffer_delay) + err := handler.HandleUp(dataIn, an2, adapter2) + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an2.Acked) + CheckSent(t, nil, adapter2.SentPkt) + }() + + done.Wait() - //sentPkt, _ := NewAPacket( - // inPkt.AppEUI(), - // inPkt.DevEUI(), - // []byte("Payload"), - // []Metadata{inPkt.Metadata()}, - //) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) } + } // ----- TYPE utilities @@ -277,7 +553,10 @@ func (s *mockPktStorage) Close() error { // MOCK ACK/NACKER // type mockAckNacker struct { - Acked Packet + Acked struct { + Ack *bool + Packet Packet + } } func newMockAckNacker() *mockAckNacker { @@ -285,11 +564,24 @@ func newMockAckNacker() *mockAckNacker { } func (an *mockAckNacker) Ack(p Packet) error { - an.Acked = p + an.Acked = struct { + Ack *bool + Packet Packet + }{ + Ack: pointer.Bool(true), + Packet: p, + } return nil } func (an *mockAckNacker) Nack() error { + an.Acked = struct { + Ack *bool + Packet Packet + }{ + Ack: pointer.Bool(false), + Packet: nil, + } return nil } @@ -344,12 +636,12 @@ func (a *mockAdapter) NextRegistration() (Registration, AckNacker, error) { } // ----- BUILD utilities -func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadata, appSKey [16]byte) HPacket { +func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) HPacket { macPayload := lorawan.NewMACPayload(true) macPayload.FHDR = lorawan.FHDR{ - FCnt: 0, + FCnt: fcnt, } - macPayload.FPort = 10 + macPayload.FPort = 1 macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} var key lorawan.AES128Key @@ -360,7 +652,7 @@ func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadat phyPayload := lorawan.NewPHYPayload(true) phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, + MType: lorawan.UnconfirmedDataUp, Major: lorawan.LoRaWANR1, } phyPayload.MACPayload = macPayload @@ -377,6 +669,39 @@ func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadat return packet } +func newBPacket(rawDevAddr [4]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) BPacket { + var devAddr lorawan.DevAddr + copy(devAddr[:], rawDevAddr[:]) + + macPayload := lorawan.NewMACPayload(false) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: devAddr, + FCnt: fcnt, + } + macPayload.FPort = 1 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + + var key lorawan.AES128Key + copy(key[:], appSKey[:]) + if err := macPayload.EncryptFRMPayload(key); err != nil { + panic(err) + } + + phyPayload := lorawan.NewPHYPayload(false) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + + packet, err := NewBPacket(phyPayload, metadata) + if err != nil { + panic(err) + } + return packet + +} + // ----- CHECK utilities func check(t *testing.T, want, got interface{}, name string) { if !reflect.DeepEqual(want, got) { @@ -393,8 +718,24 @@ func CheckPersonalized(t *testing.T, want HRegistration, got HRegistration) { check(t, want, got, "Personalized") } -func CheckAcks(t *testing.T, want Packet, got Packet) { - check(t, want, got, "Acks") +func CheckAcks(t *testing.T, want interface{}, gotItf interface{}) { + got := gotItf.(struct { + Ack *bool + Packet Packet + }) + + if got.Ack == nil { + Ko(t, "Invalid ack got: %+v", got) + } + + switch want.(type) { + case bool: + check(t, want.(bool), *(got.Ack), "Acks") + case Packet: + check(t, want.(Packet), got.Packet, "Acks") + default: + panic("Unexpect ack wanted") + } } func CheckRecipients(t *testing.T, want []Recipient, got []Recipient) { From c3cac729a58d51e82f7ad53f9710fdefb9563166 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 18:59:19 +0100 Subject: [PATCH 0827/2266] [test-handler] Fix handler accordingly --- core/components/handler/handler.go | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index bbfa089e4..e2586a026 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -4,7 +4,6 @@ package handler import ( - "fmt" "reflect" "time" @@ -76,7 +75,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { itf, err := UnmarshalPacket(data) if err != nil { - return errors.New(errors.Structural, data) + return errors.New(errors.Structural, err) } switch itf.(type) { @@ -127,14 +126,12 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { switch resp.(type) { case BPacket: ctx.Debug("Received response with packet. Sending Ack") - an.Ack(resp.(Packet)) + ack = resp.(Packet) case error: ctx.WithError(resp.(error)).Warn("Received errored response. Sending Ack") - an.Nack() return errors.New(errors.Operational, resp.(error)) default: ctx.Debug("Received empty response. Sending empty Ack") - an.Ack(nil) } return nil @@ -151,10 +148,10 @@ func computeScore(dutyCycle uint, rssi int) uint { } if dutyCycle > 2*max_duty_cycle/3 { - return uint(1000 - rssi) + return uint(1000 + rssi) } - return uint(10000 - rssi) + return uint(10000 + rssi) } // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, @@ -168,7 +165,7 @@ browseBundles: ctx.WithField("BundleID", bundles[0].Id).Debug("Consume new bundle") var metadata []Metadata var payload []byte - var bestBundle bundle + var bestBundle int var bestScore uint for i, bundle := range bundles { @@ -182,7 +179,7 @@ browseBundles: go h.abortConsume(err, bundles) continue browseBundles } - bestBundle = bundle + bestBundle = i } // Append metadata for each of them @@ -197,12 +194,14 @@ browseBundles: score := computeScore(*duty, *rssi) if score > bestScore { bestScore = score - bestBundle = bundle + bestBundle = i } } // Then create an application-level packet - packet, err := NewAPacket(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI(), payload, metadata) + appEUI := bundles[bestBundle].Packet.AppEUI() + devEUI := bundles[bestBundle].Packet.DevEUI() + packet, err := NewAPacket(appEUI, devEUI, payload, metadata) if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -210,44 +209,46 @@ browseBundles: // And send it to the wild open // we don't expect a response from the adapter, end of the chain. - recipient, err := bestBundle.Adapter.GetRecipient(bestBundle.Entry.Recipient) + adapter := bundles[bestBundle].Adapter + entry := bundles[bestBundle].Entry + recipient, err := adapter.GetRecipient(entry.Recipient) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } - _, err = bestBundle.Adapter.Send(packet, recipient) + _, err = adapter.Send(packet, recipient) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } // Now handle the downlink - down, err := h.packets.Pull(bestBundle.Packet.AppEUI(), bestBundle.Packet.DevEUI()) + down, err := h.packets.Pull(appEUI, devEUI) if err != nil && err.(errors.Failure).Nature != errors.Behavioural { go h.abortConsume(err, bundles) continue browseBundles } // Then respond to node - for _, bundle := range bundles { - if bundle.Id == bestBundle.Id { + for i, bundle := range bundles { + if i == bestBundle { var resp Packet if down != nil && err == nil { + fcnt := bundles[bestBundle].Packet.FCnt() + 1 macPayload := lorawan.NewMACPayload(false) macPayload.FHDR = lorawan.FHDR{ - DevAddr: bestBundle.Entry.DevAddr, + DevAddr: entry.DevAddr, // TODO Take care of the Adaptative Rate and other stuff - FCnt: bestBundle.Packet.FCnt() + 1, + FCnt: fcnt, } macPayload.FPort = 1 - fmt.Println(down) macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ Bytes: down.Payload(), }} - if err := macPayload.EncryptFRMPayload(bestBundle.Entry.AppSKey); err != nil { + if err := macPayload.EncryptFRMPayload(entry.AppSKey); err != nil { go h.abortConsume(err, bundles) continue browseBundles } From 6684130e465ec94e8f915058c1035c4555276d99 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 1 Mar 2016 19:07:27 +0100 Subject: [PATCH 0828/2266] [test-handler] Enhance test coverage for handler --- core/components/handler/handler_test.go | 187 +++++++++++++++++++++++- 1 file changed, 186 insertions(+), 1 deletion(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index d6098dfc0..e0f48fac7 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -477,6 +477,191 @@ func TestHandleUp(t *testing.T) { CheckPersonalized(t, nil, devStorage.Personalized) } + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | No downlink ready | No Metadata ") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{}, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + + adapter.Recipient = recipient + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, nil, err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, true, an.Acked) + CheckSent(t, pktSent, adapter.SentPkt) + CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + } + + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail sending ") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + + adapter.Recipient = recipient + adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, false, an.Acked) + CheckSent(t, pktSent, adapter.SentPkt) + CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + } + + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail GetRecipient") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + + adapter.Recipient = recipient + adapter.Failures["GetRecipient"] = errors.New(errors.Operational, "Mock Error: Unable to get recipient") + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, false, an.Acked) + CheckSent(t, nil, adapter.SentPkt) + CheckRecipients(t, nil, adapter.SentRecipients) + } + + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | No downlink ready | PktStorage fails to pull") + + devStorage := newMockDevStorage() + pktStorage := newMockPktStorage() + an := newMockAckNacker() + adapter := newMockAdapter() + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + recipient := newMockRecipient("TowardsInfinity") + dataRecipient, _ := recipient.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + + adapter.Recipient = recipient + devStorage.LookupEntry = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + pktStorage.Failures["Pull"] = errors.New(errors.Operational, "Mock Error: Failed to Pull") + err := handler.HandleUp(dataIn, an, adapter) + + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckPushed(t, nil, pktStorage.Pushed) + CheckPersonalized(t, nil, devStorage.Personalized) + CheckAcks(t, false, an.Acked) + CheckSent(t, pktSent, adapter.SentPkt) + CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + } } // ----- TYPE utilities @@ -616,7 +801,7 @@ func (a *mockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { func (a *mockAdapter) GetRecipient(raw []byte) (Recipient, error) { if a.Failures["GetRecipient"] != nil { - return nil, a.Failures["Send"] + return nil, a.Failures["GetRecipient"] } return a.Recipient, nil } From 3c826e1110934f00bee21b85c86159b63addfef3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 10:56:09 +0100 Subject: [PATCH 0829/2266] [refactor/handler-test] Remove Mocks from devStorage_test and put them in their own package --- core/components/handler/devStorage_test.go | 135 ++++++--------------- core/mocks/mock.go | 103 ++++++++++++++++ 2 files changed, 142 insertions(+), 96 deletions(-) create mode 100644 core/mocks/mock.go diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 53193678a..35e380cad 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -9,6 +9,7 @@ import ( "testing" . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -37,15 +38,16 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Store and Lookup a registration") - r := newMockRegistration( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - newMockRecipient("MyRecipient"), - ) + // Build + r := NewMockRegistration() + + // Operate err := db.StorePersonalized(r) CheckErrors(t, nil, err) entry, err := db.Lookup(r.AppEUI(), r.DevEUI()) + + // Check CheckErrors(t, nil, err) CheckEntries(t, r, entry) } @@ -54,12 +56,15 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Lookup a non-existing registration") - r := newMockRegistration( - [8]byte{1, 1, 1, 1, 1, 1, 1, 2}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 3}, - newMockRecipient("MyRecipient"), - ) + + // Build + r := NewMockRegistration() + r.OutAppEUI = lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) + + // Operate _, err := db.Lookup(r.AppEUI(), r.DevEUI()) + + // Check CheckErrors(t, pointer.String(string(errors.Behavioural)), err) } @@ -67,16 +72,18 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Store twice the same registration") - r := newMockRegistration( - [8]byte{3, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{4, 2, 2, 2, 2, 2, 2, 2}, - newMockRecipient("MyRecipient"), - ) + + // Build + r := NewMockRegistration() + r.OutAppEUI = lorawan.EUI64([8]byte{1, 4, 1, 4, 1, 4, 1, 4}) + + // Operate + _ = db.StorePersonalized(r) err := db.StorePersonalized(r) CheckErrors(t, nil, err) - err = db.StorePersonalized(r) - CheckErrors(t, nil, err) entry, err := db.Lookup(r.AppEUI(), r.DevEUI()) + + // Check CheckErrors(t, nil, err) CheckEntries(t, r, entry) } @@ -85,12 +92,15 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Store Activated") - r := newMockRegistration( - [8]byte{3, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{4, 2, 2, 2, 2, 2, 2, 2}, - newMockRecipient("MyRecipient"), - ) + + // Build + r := NewMockRegistration() + r.OutAppEUI = lorawan.EUI64([8]byte{6, 6, 6, 7, 8, 6, 7, 6}) + + // Operate err := db.StoreActivated(r) + + // Check CheckErrors(t, pointer.String(string(errors.Implementation)), err) } @@ -104,83 +114,16 @@ func TestLookupStore(t *testing.T) { } -// ----- TYPE utilities -type mockRegistration struct { - appEUI lorawan.EUI64 - devEUI lorawan.EUI64 - recipient Recipient -} - -func newMockRegistration(appEUI [8]byte, devEUI [8]byte, recipient Recipient) mockRegistration { - r := mockRegistration{recipient: recipient} - copy(r.appEUI[:], appEUI[:]) - copy(r.devEUI[:], devEUI[:]) - return r -} - -func (r mockRegistration) Recipient() Recipient { - return r.recipient -} - -func (r mockRegistration) RawRecipient() []byte { - data, _ := r.Recipient().MarshalBinary() - return data -} - -func (r mockRegistration) AppEUI() lorawan.EUI64 { - return r.appEUI -} - -func (r mockRegistration) DevEUI() lorawan.EUI64 { - return r.devEUI -} - -func (r mockRegistration) DevAddr() lorawan.DevAddr { - devAddr := lorawan.DevAddr{} - copy(devAddr[:], r.devEUI[4:]) - return devAddr -} - -func (r mockRegistration) NwkSKey() lorawan.AES128Key { - return lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}) -} - -func (r mockRegistration) AppSKey() lorawan.AES128Key { - return lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}) -} - -type mockRecipient struct { - Failures map[string]error - Data string -} - -func newMockRecipient(data string) *mockRecipient { - return &mockRecipient{ - Data: data, - Failures: make(map[string]error), - } -} - -func (r *mockRecipient) MarshalBinary() ([]byte, error) { - if r.Failures["MarshalBinary"] != nil { - return nil, r.Failures["MarshalBinary"] - } - return []byte(r.Data), nil -} - -func (r *mockRecipient) UnmarshalBinary(data []byte) error { - r.Data = string(data) - if r.Failures["UnmarshalBinary"] != nil { - return r.Failures["UnmarshalBinary"] - } - return nil -} - // ----- CHECK utilities -func CheckEntries(t *testing.T, want mockRegistration, got devEntry) { +func CheckEntries(t *testing.T, want *MockRegistration, got devEntry) { + // NOTE This only works in the case of Personalized devices + var devAddr lorawan.DevAddr + devEUI := want.DevEUI() + copy(devAddr[:], devEUI[4:]) + wantEntry := devEntry{ Recipient: want.RawRecipient(), - DevAddr: want.DevAddr(), + DevAddr: devAddr, NwkSKey: want.NwkSKey(), AppSKey: want.AppSKey(), } diff --git a/core/mocks/mock.go b/core/mocks/mock.go new file mode 100644 index 000000000..5715392d8 --- /dev/null +++ b/core/mocks/mock.go @@ -0,0 +1,103 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mock + +import ( + . "github.com/TheThingsNetwork/ttn/core" + "github.com/brocaar/lorawan" +) + +// MockRecipient implements the core.Recipient interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockRecipient struct { + Failures map[string]error + InUnmarshalBinary []byte // Data received by UnmarshalBinary() + OutMarshalBinary []byte // Data spit out by MarshalBinary() +} + +// NewMockRecipient constructs a new mock recipient. +func NewMockRecipient() *MockRecipient { + return &MockRecipient{ + OutMarshalBinary: []byte("MockRecipientData"), + Failures: make(map[string]error), + } +} + +func (r *MockRecipient) MarshalBinary() ([]byte, error) { + if r.Failures["MarshalBinary"] != nil { + return nil, r.Failures["MarshalBinary"] + } + return r.OutMarshalBinary, nil +} + +func (r *MockRecipient) UnmarshalBinary(data []byte) error { + r.InUnmarshalBinary = data + if r.Failures["UnmarshalBinary"] != nil { + return r.Failures["UnmarshalBinary"] + } + return nil +} + +// MockRegistration implements the core.Recipient interface +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockRegistration struct { + OutAppEUI lorawan.EUI64 + OutDevEUI lorawan.EUI64 + OutNwkSKey lorawan.AES128Key + OutAppSKey lorawan.AES128Key + OutRecipient Recipient +} + +func NewMockRegistration() *MockRegistration { + return &MockRegistration{ + OutAppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + OutDevEUI: lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + OutNwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), + OutAppSKey: lorawan.AES128Key([16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}), + OutRecipient: NewMockRecipient(), + } +} + +func (r *MockRegistration) Recipient() Recipient { + return r.OutRecipient +} + +func (r *MockRegistration) RawRecipient() []byte { + data, _ := r.Recipient().MarshalBinary() + return data +} + +func (r *MockRegistration) AppEUI() lorawan.EUI64 { + return r.OutAppEUI +} + +func (r *MockRegistration) DevEUI() lorawan.EUI64 { + return r.OutDevEUI +} + +func (r *MockRegistration) NwkSKey() lorawan.AES128Key { + return r.OutNwkSKey +} + +func (r *MockRegistration) AppSKey() lorawan.AES128Key { + return r.OutAppSKey +} + +//func (r *MockRegistration) DevAddr() lorawan.DevAddr { +// devAddr := lorawan.DevAddr{} +// copy(devAddr[:], r.devEUI[4:]) +// return devAddr +//} From 560c743c1a72979159e846fbdff39a77268db034 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 11:00:00 +0100 Subject: [PATCH 0830/2266] [refactor/handler-test] Add some comments in pktStorage_test --- core/components/handler/pktStorage_test.go | 37 ++++++++++++++++++++-- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 07ff671ba..643e04dca 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -37,16 +37,21 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Push and Pull a valid APacket") + + // Build p, _ := NewAPacket( lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), []byte("TheThingsNetwork"), []Metadata{}, ) + + // Operate err := db.Push(p) CheckErrors(t, nil, err) - a, err := db.Pull(p.AppEUI(), p.DevEUI()) + + // Check CheckErrors(t, nil, err) CheckPackets(t, p, a) } @@ -55,6 +60,8 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Push two packets") + + // Build p1, _ := NewAPacket( lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), @@ -67,6 +74,8 @@ func TestPushPullNormal(t *testing.T) { []byte("TheThingsNetwork2"), []Metadata{}, ) + + // Operate & Check err := db.Push(p1) CheckErrors(t, nil, err) err = db.Push(p2) @@ -85,7 +94,15 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Pull a non existing entry") - p, err := db.Pull(lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}), lorawan.EUI64([8]byte{2, 3, 4, 2, 3, 4, 2, 3})) + + // Build + appEUI := lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) + devEUI := lorawan.EUI64([8]byte{2, 3, 4, 2, 3, 4, 2, 3}) + + // Operate + p, err := db.Pull(appEUI, devEUI) + + // Check CheckErrors(t, pointer.String(string(errors.Behavioural)), err) CheckPackets(t, nil, p) } @@ -102,13 +119,19 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Push after close") + + // Build p, _ := NewAPacket( lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), []byte("TheThingsNetwork"), []Metadata{}, ) + + // Operate err := db.Push(p) + + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) } @@ -116,7 +139,15 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Pull after close") - _, err := db.Pull(lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2})) + + // Build + appEUI := lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) + devEUI := lorawan.EUI64([8]byte{2, 3, 4, 2, 3, 4, 2, 3}) + + // Operate + _, err := db.Pull(appEUI, devEUI) + + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) } } From ac860f1dbe67299bf6946afb35c4e23eceabeb34 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 11:09:09 +0100 Subject: [PATCH 0831/2266] [refactor/handler-test] Separate mock handler interfaces from the original file --- core/components/handler/mocks_test.go | 103 ++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 core/components/handler/mocks_test.go diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go new file mode 100644 index 000000000..8b9144d38 --- /dev/null +++ b/core/components/handler/mocks_test.go @@ -0,0 +1,103 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + . "github.com/TheThingsNetwork/ttn/core" + "github.com/brocaar/lorawan" +) + +// mockDevStorage implements the handler.DevStorage interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type mockDevStorage struct { + Failures map[string]error + OutLookup devEntry + InLookupAppEUI lorawan.EUI64 + InLookupDevEUI lorawan.EUI64 + InStorePersonalized HRegistration + InStoreActivated HRegistration +} + +func newMockDevStorage() *mockDevStorage { + return &mockDevStorage{ + Failures: make(map[string]error), + OutLookup: devEntry{ + Recipient: []byte("MockDevStorageRecipient"), + DevAddr: lorawan.DevAddr([4]byte{9, 9, 1, 4}), + AppSKey: lorawan.AES128Key([16]byte{6, 6, 4, 3, 2, 2, 0, 9, 8, 7, 6, 3, 1, 9, 6, 14}), + NwkSKey: lorawan.AES128Key([16]byte{7, 2, 3, 3, 5, 6, 7, 0, 9, 0, 1, 2, 7, 4, 5, 5}), + }, + } +} + +func (s *mockDevStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { + s.InLookupAppEUI = appEUI + s.InLookupDevEUI = devEUI + if s.Failures["Lookup"] != nil { + return devEntry{}, s.Failures["Lookup"] + } + return s.OutLookup, nil +} + +func (s *mockDevStorage) StorePersonalized(r HRegistration) error { + s.InStorePersonalized = r + return s.Failures["StorePersonalized"] +} + +func (s *mockDevStorage) StoreActivated(r HRegistration) error { + s.InStoreActivated = r + return s.Failures["StoreActivated"] +} + +func (s *mockDevStorage) Close() error { + return s.Failures["Close"] +} + +// mockPktStorage implements the handler.PktStorage interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type mockPktStorage struct { + Failures map[string]error + OutPull APacket + InPullAppEUI lorawan.EUI64 + InPullDevEUI lorawan.EUI64 + InPush APacket +} + +func newMockPktStorage() *mockPktStorage { + return &mockPktStorage{ + Failures: make(map[string]error), + } +} + +func (s *mockPktStorage) Push(p APacket) error { + s.Pushed = p + return s.Failures["Push"] +} + +func (s *mockPktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { + if s.Failures["Pull"] != nil { + return nil, s.Failures["Pull"] + } + return s.PullEntry, nil +} + +func (s *mockPktStorage) Close() error { + return s.Failures["Close"] +} From 40356e1889f7eb9aeaf9ef16e89ffc5578f62c98 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 12:02:33 +0100 Subject: [PATCH 0832/2266] [refactor/handler-test] Split mock and checks for handler_test aswell --- core/components/handler/devStorage_test.go | 22 - core/components/handler/handler_test.go | 710 +++++++-------------- core/components/handler/helpers_test.go | 108 ++++ core/components/handler/mocks_test.go | 6 +- core/components/handler/pktStorage_test.go | 10 - core/mocks/mock.go | 156 ++++- 6 files changed, 504 insertions(+), 508 deletions(-) create mode 100644 core/components/handler/helpers_test.go diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 35e380cad..561861143 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -5,7 +5,6 @@ package handler import ( "os" - "reflect" "testing" . "github.com/TheThingsNetwork/ttn/core" @@ -113,24 +112,3 @@ func TestLookupStore(t *testing.T) { } } - -// ----- CHECK utilities -func CheckEntries(t *testing.T, want *MockRegistration, got devEntry) { - // NOTE This only works in the case of Personalized devices - var devAddr lorawan.DevAddr - devEUI := want.DevEUI() - copy(devAddr[:], devEUI[4:]) - - wantEntry := devEntry{ - Recipient: want.RawRecipient(), - DevAddr: devAddr, - NwkSKey: want.NwkSKey(), - AppSKey: want.AppSKey(), - } - - if reflect.DeepEqual(wantEntry, got) { - Ok(t, "Check Entries") - return - } - Ko(t, "Check Entries") -} diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index e0f48fac7..e2241fac0 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -4,13 +4,12 @@ package handler import ( - "fmt" - "reflect" "sync" "testing" "time" . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -22,21 +21,20 @@ func TestRegister(t *testing.T) { { Desc(t, "Register valid HRegistration") + // Build devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - an := newMockAckNacker() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - r := newMockRegistration( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - newMockRecipient("recipient"), - ) + an := NewMockAckNacker() + r := NewMockRegistration() + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.Register(r, an) + // Check CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, r, devStorage.Personalized) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, r, devStorage.InStorePersonalized) } // -------------------- @@ -44,16 +42,19 @@ func TestRegister(t *testing.T) { { Desc(t, "Register invalid HRegistration") + // Build devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - an := newMockAckNacker() + an := NewMockAckNacker() handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Operate err := handler.Register(nil, an) + // Checks CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) } // -------------------- @@ -61,22 +62,21 @@ func TestRegister(t *testing.T) { { Desc(t, "Register valid HRegistration | devStorage fails") + // Build devStorage := newMockDevStorage() + devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") pktStorage := newMockPktStorage() - an := newMockAckNacker() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - r := newMockRegistration( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - newMockRecipient("recipient"), - ) + an := NewMockAckNacker() + r := NewMockRegistration() - devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.Register(r, an) + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, r, devStorage.Personalized) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, r, devStorage.InStorePersonalized) } } @@ -84,27 +84,30 @@ func TestHandleDown(t *testing.T) { { Desc(t, "Handle downlink APacket") + // Build devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + an := NewMockAckNacker() + adapter := NewMockAdapter() pkt, _ := NewAPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, []byte("TheThingsNetwork"), []Metadata{}, ) - data, _ := pkt.MarshalBinary() + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleDown(data, an, adapter) + // Check CheckErrors(t, nil, err) - CheckPushed(t, pkt, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, true, an.Acked) - CheckSent(t, nil, adapter.SentPkt) - CheckRecipients(t, nil, adapter.SentRecipients) + CheckPushed(t, pkt, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, true, an.InAck) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } // -------------------- @@ -112,20 +115,23 @@ func TestHandleDown(t *testing.T) { { Desc(t, "Handle downlink wrong data") + // Build devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + an := NewMockAckNacker() + adapter := NewMockAdapter() + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleDown([]byte{1, 2, 3}, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, nil, adapter.SentPkt) - CheckRecipients(t, nil, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } // -------------------- @@ -133,11 +139,11 @@ func TestHandleDown(t *testing.T) { { Desc(t, "Handle downlink wrong packet type") + // Build devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + an := NewMockAckNacker() + adapter := NewMockAdapter() pkt := NewJPacket( lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), @@ -146,14 +152,17 @@ func TestHandleDown(t *testing.T) { ) data, _ := pkt.MarshalBinary() + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleDown(data, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Implementation)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, nil, adapter.SentPkt) - CheckRecipients(t, nil, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } } @@ -161,11 +170,13 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | No Associated App") + // Build devStorage := newMockDevStorage() + devStorage.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock: Not Found") pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + pktStorage.Failures["Pull"] = errors.New(errors.Behavioural, "Mock: Not Found") + an := NewMockAckNacker() + adapter := NewMockAdapter() inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -179,35 +190,39 @@ func TestHandleUp(t *testing.T) { ) dataIn, _ := inPkt.MarshalBinary() - devStorage.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock: Not Found") - pktStorage.Failures["Pull"] = errors.New(errors.Behavioural, "Mock: Not Found") + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Behavioural)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, nil, adapter.SentPkt) - CheckRecipients(t, nil, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } { Desc(t, "Handle uplink with invalid data") + // Build devStorage := newMockDevStorage() pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + an := NewMockAckNacker() + adapter := NewMockAdapter() + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp([]byte{1, 2, 3}, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, nil, adapter.SentPkt) - CheckRecipients(t, nil, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } // -------------------- @@ -215,11 +230,10 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | No downlink ready") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + dataRecipient, _ := adapter.OutGetRecipient.MarshalBinary() inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -232,30 +246,32 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() pktSent, _ := NewAPacket( inPkt.AppEUI(), inPkt.DevEUI(), []byte("Payload"), []Metadata{inPkt.Metadata()}, ) - - adapter.Recipient = recipient - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, true, an.Acked) - CheckSent(t, pktSent, adapter.SentPkt) - CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, true, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{adapter.OutGetRecipient}, adapter.InSendRecipients) } // -------------------- @@ -263,19 +279,14 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 2 packets in a row | No downlink ready") - // Handler - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - - // Recipient - recipient := newMockRecipient("TowardsInfinity") + // Build + recipient := NewMockRecipient() dataRecipient, _ := recipient.MarshalBinary() // First Packet - adapter1 := newMockAdapter() - adapter1.Recipient = recipient - an1 := newMockAckNacker() + adapter1 := NewMockAdapter() + adapter1.OutGetRecipient = recipient + an1 := NewMockAckNacker() inPkt1 := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -290,9 +301,9 @@ func TestHandleUp(t *testing.T) { dataIn1, _ := inPkt1.MarshalBinary() // Second Packet - adapter2 := newMockAdapter() - adapter2.Recipient = recipient - an2 := newMockAckNacker() + adapter2 := NewMockAdapter() + adapter2.OutGetRecipient = recipient + an2 := NewMockAckNacker() inPkt2 := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -314,37 +325,44 @@ func TestHandleUp(t *testing.T) { []Metadata{inPkt1.Metadata(), inPkt2.Metadata()}, ) - // Fake response from the storage - done := sync.WaitGroup{} - done.Add(2) - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + done := sync.WaitGroup{} + done.Add(2) go func() { defer done.Done() err := handler.HandleUp(dataIn1, an1, adapter1) + // Check CheckErrors(t, nil, err) - CheckAcks(t, true, an1.Acked) - CheckSent(t, nil, adapter1.SentPkt) - CheckRecipients(t, nil, adapter1.SentRecipients) + CheckAcks(t, true, an1.InAck) + CheckSent(t, nil, adapter1.InSendPacket) + CheckRecipients(t, nil, adapter1.InSendRecipients) }() go func() { <-time.After(time.Millisecond * 50) defer done.Done() err := handler.HandleUp(dataIn2, an2, adapter2) + // Check CheckErrors(t, nil, err) - CheckAcks(t, true, an2.Acked) - CheckSent(t, pktSent, adapter2.SentPkt) // Adapter2 because the adapter of the best bundle even if they are supposed to be identical - CheckRecipients(t, []Recipient{recipient}, adapter2.SentRecipients) + CheckAcks(t, true, an2.InAck) + CheckSent(t, pktSent, adapter2.InSendPacket) // Adapter2 because the adapter of the best bundle even if they are supposed to be identical + CheckRecipients(t, []Recipient{recipient}, adapter2.InSendRecipients) }() + // Check done.Wait() - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) } // -------------------- @@ -352,11 +370,12 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | One downlink response") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -369,8 +388,6 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() pktSent, _ := NewAPacket( inPkt.AppEUI(), inPkt.DevEUI(), @@ -391,22 +408,27 @@ func TestHandleUp(t *testing.T) { []Metadata{}, ) - adapter.Recipient = recipient - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } - pktStorage.PullEntry = appResp + pktStorage := newMockPktStorage() + pktStorage.OutPull = appResp + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, brkResp, an.Acked) - CheckSent(t, pktSent, adapter.SentPkt) - CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, brkResp, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) } // --------------- @@ -414,13 +436,15 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle a late uplink | No downlink ready") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an2 := newMockAckNacker() - an1 := newMockAckNacker() - adapter1 := newMockAdapter() - adapter2 := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an2 := NewMockAckNacker() + an1 := NewMockAckNacker() + adapter1 := NewMockAdapter() + adapter1.OutGetRecipient = recipient + adapter2 := NewMockAdapter() + adapter2.OutGetRecipient = recipient inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -433,48 +457,47 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() pktSent, _ := NewAPacket( inPkt.AppEUI(), inPkt.DevEUI(), []byte("Payload"), []Metadata{inPkt.Metadata()}, ) - - adapter1.Recipient = recipient - adapter2.Recipient = recipient - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) done := sync.WaitGroup{} done.Add(2) - go func() { defer done.Done() err := handler.HandleUp(dataIn, an1, adapter1) + // Check CheckErrors(t, nil, err) - CheckAcks(t, true, an1.Acked) - CheckSent(t, pktSent, adapter1.SentPkt) + CheckAcks(t, true, an1.InAck) + CheckSent(t, pktSent, adapter1.InSendPacket) }() - go func() { defer done.Done() <-time.After(2 * buffer_delay) err := handler.HandleUp(dataIn, an2, adapter2) + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an2.Acked) - CheckSent(t, nil, adapter2.SentPkt) + CheckAcks(t, false, an2.InAck) + CheckSent(t, nil, adapter2.InSendPacket) }() + // Check done.Wait() - - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) } // -------------------- @@ -482,11 +505,11 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | No downlink ready | No Metadata ") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -496,8 +519,6 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() pktSent, _ := NewAPacket( inPkt.AppEUI(), inPkt.DevEUI(), @@ -505,21 +526,27 @@ func TestHandleUp(t *testing.T) { []Metadata{inPkt.Metadata()}, ) - adapter.Recipient = recipient - devStorage.LookupEntry = devEntry{ + adapter.OutGetRecipient = recipient + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, true, an.Acked) - CheckSent(t, pktSent, adapter.SentPkt) - CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, true, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) } // -------------------- @@ -527,11 +554,13 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail sending ") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient + adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -544,8 +573,6 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() pktSent, _ := NewAPacket( inPkt.AppEUI(), inPkt.DevEUI(), @@ -553,22 +580,26 @@ func TestHandleUp(t *testing.T) { []Metadata{inPkt.Metadata()}, ) - adapter.Recipient = recipient - adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, pktSent, adapter.SentPkt) - CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) } // -------------------- @@ -576,11 +607,13 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail GetRecipient") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient + adapter.Failures["GetRecipient"] = errors.New(errors.Operational, "Mock Error: Unable to get recipient") inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -593,25 +626,27 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() - adapter.Recipient = recipient - adapter.Failures["GetRecipient"] = errors.New(errors.Operational, "Mock Error: Unable to get recipient") - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, nil, adapter.SentPkt) - CheckRecipients(t, nil, adapter.SentRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } // -------------------- @@ -619,11 +654,12 @@ func TestHandleUp(t *testing.T) { { Desc(t, "Handle uplink with 1 packet | No downlink ready | PktStorage fails to pull") - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := newMockAckNacker() - adapter := newMockAdapter() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + // Build + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -636,8 +672,6 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() - recipient := newMockRecipient("TowardsInfinity") - dataRecipient, _ := recipient.MarshalBinary() pktSent, _ := NewAPacket( inPkt.AppEUI(), inPkt.DevEUI(), @@ -645,288 +679,26 @@ func TestHandleUp(t *testing.T) { []Metadata{inPkt.Metadata()}, ) - adapter.Recipient = recipient - devStorage.LookupEntry = devEntry{ + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ Recipient: dataRecipient, DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } + pktStorage := newMockPktStorage() pktStorage.Failures["Pull"] = errors.New(errors.Operational, "Mock Error: Failed to Pull") + + // Operate + handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) + // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.Pushed) - CheckPersonalized(t, nil, devStorage.Personalized) - CheckAcks(t, false, an.Acked) - CheckSent(t, pktSent, adapter.SentPkt) - CheckRecipients(t, []Recipient{recipient}, adapter.SentRecipients) - } -} - -// ----- TYPE utilities - -// -// MOCK DEV STORAGE -// -type mockDevStorage struct { - Failures map[string]error - LookupEntry devEntry - Personalized HRegistration - Activated HRegistration -} - -func newMockDevStorage(failures ...string) *mockDevStorage { - return &mockDevStorage{ - Failures: make(map[string]error), - } -} - -func (s mockDevStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { - if s.Failures["Lookup"] != nil { - return devEntry{}, s.Failures["Lookup"] - } - return s.LookupEntry, nil -} - -func (s *mockDevStorage) StorePersonalized(r HRegistration) error { - s.Personalized = r - return s.Failures["StorePersonalized"] -} - -func (s *mockDevStorage) StoreActivated(r HRegistration) error { - s.Activated = r - return s.Failures["StoreActivated"] -} - -func (s mockDevStorage) Close() error { - return s.Failures["Close"] -} - -// -// MOCK PKT STORAGE -// -type mockPktStorage struct { - Failures map[string]error - PullEntry APacket - Pushed APacket -} - -func newMockPktStorage() *mockPktStorage { - return &mockPktStorage{ - Failures: make(map[string]error), - } -} - -func (s *mockPktStorage) Push(p APacket) error { - s.Pushed = p - return s.Failures["Push"] -} - -func (s *mockPktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { - if s.Failures["Pull"] != nil { - return nil, s.Failures["Pull"] - } - return s.PullEntry, nil -} - -func (s *mockPktStorage) Close() error { - return s.Failures["Close"] -} - -// -// MOCK ACK/NACKER -// -type mockAckNacker struct { - Acked struct { - Ack *bool - Packet Packet - } -} - -func newMockAckNacker() *mockAckNacker { - return &mockAckNacker{} -} - -func (an *mockAckNacker) Ack(p Packet) error { - an.Acked = struct { - Ack *bool - Packet Packet - }{ - Ack: pointer.Bool(true), - Packet: p, - } - return nil -} - -func (an *mockAckNacker) Nack() error { - an.Acked = struct { - Ack *bool - Packet Packet - }{ - Ack: pointer.Bool(false), - Packet: nil, - } - return nil -} - -// -// MOCK ADAPTER -// -type mockAdapter struct { - Failures map[string]error - SentPkt Packet - SentRecipients []Recipient - SendData []byte - Recipient Recipient - NextPacket []byte - NextAckNacker AckNacker - NextReg Registration -} - -func newMockAdapter() *mockAdapter { - return &mockAdapter{ - Failures: make(map[string]error), + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) } } - -func (a *mockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { - a.SentPkt = p - a.SentRecipients = r - if a.Failures["Send"] != nil { - return nil, a.Failures["Send"] - } - return a.SendData, nil -} - -func (a *mockAdapter) GetRecipient(raw []byte) (Recipient, error) { - if a.Failures["GetRecipient"] != nil { - return nil, a.Failures["GetRecipient"] - } - return a.Recipient, nil -} - -func (a *mockAdapter) Next() ([]byte, AckNacker, error) { - if a.Failures["Next"] != nil { - return nil, nil, a.Failures["Next"] - } - return a.NextPacket, a.NextAckNacker, nil -} - -func (a *mockAdapter) NextRegistration() (Registration, AckNacker, error) { - if a.Failures["NextRegistration"] != nil { - return nil, nil, a.Failures["NextRegistration"] - } - return a.NextReg, a.NextAckNacker, nil -} - -// ----- BUILD utilities -func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) HPacket { - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - FCnt: fcnt, - } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - - var key lorawan.AES128Key - copy(key[:], appSKey[:]) - if err := macPayload.EncryptFRMPayload(key); err != nil { - panic(err) - } - - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - - var appEUIp lorawan.EUI64 - var devEUIp lorawan.EUI64 - copy(appEUIp[:], appEUI[:]) - copy(devEUIp[:], devEUI[:]) - - packet, err := NewHPacket(appEUIp, devEUIp, phyPayload, metadata) - if err != nil { - panic(err) - } - return packet -} - -func newBPacket(rawDevAddr [4]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) BPacket { - var devAddr lorawan.DevAddr - copy(devAddr[:], rawDevAddr[:]) - - macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: devAddr, - FCnt: fcnt, - } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - - var key lorawan.AES128Key - copy(key[:], appSKey[:]) - if err := macPayload.EncryptFRMPayload(key); err != nil { - panic(err) - } - - phyPayload := lorawan.NewPHYPayload(false) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - - packet, err := NewBPacket(phyPayload, metadata) - if err != nil { - panic(err) - } - return packet - -} - -// ----- CHECK utilities -func check(t *testing.T, want, got interface{}, name string) { - if !reflect.DeepEqual(want, got) { - Ko(t, "%s don't match expectations.\nWant: %v\nGot: %v", name, want, got) - } - Ok(t, fmt.Sprintf("Check %s", name)) -} - -func CheckPushed(t *testing.T, want APacket, got APacket) { - check(t, want, got, "Pushed") -} - -func CheckPersonalized(t *testing.T, want HRegistration, got HRegistration) { - check(t, want, got, "Personalized") -} - -func CheckAcks(t *testing.T, want interface{}, gotItf interface{}) { - got := gotItf.(struct { - Ack *bool - Packet Packet - }) - - if got.Ack == nil { - Ko(t, "Invalid ack got: %+v", got) - } - - switch want.(type) { - case bool: - check(t, want.(bool), *(got.Ack), "Acks") - case Packet: - check(t, want.(Packet), got.Packet, "Acks") - default: - panic("Unexpect ack wanted") - } -} - -func CheckRecipients(t *testing.T, want []Recipient, got []Recipient) { - check(t, want, got, "Recipients") -} - -func CheckSent(t *testing.T, want Packet, got Packet) { - check(t, want, got, "Sent") -} diff --git a/core/components/handler/helpers_test.go b/core/components/handler/helpers_test.go new file mode 100644 index 000000000..6d166cf8f --- /dev/null +++ b/core/components/handler/helpers_test.go @@ -0,0 +1,108 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/brocaar/lorawan" +) + +// ----- BUILD utilities +func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) HPacket { + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + FCnt: fcnt, + } + macPayload.FPort = 1 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + + var key lorawan.AES128Key + copy(key[:], appSKey[:]) + if err := macPayload.EncryptFRMPayload(key); err != nil { + panic(err) + } + + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + + var appEUIp lorawan.EUI64 + var devEUIp lorawan.EUI64 + copy(appEUIp[:], appEUI[:]) + copy(devEUIp[:], devEUI[:]) + + packet, err := NewHPacket(appEUIp, devEUIp, phyPayload, metadata) + if err != nil { + panic(err) + } + return packet +} + +func newBPacket(rawDevAddr [4]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) BPacket { + var devAddr lorawan.DevAddr + copy(devAddr[:], rawDevAddr[:]) + + macPayload := lorawan.NewMACPayload(false) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: devAddr, + FCnt: fcnt, + } + macPayload.FPort = 1 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + + var key lorawan.AES128Key + copy(key[:], appSKey[:]) + if err := macPayload.EncryptFRMPayload(key); err != nil { + panic(err) + } + + phyPayload := lorawan.NewPHYPayload(false) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + + packet, err := NewBPacket(phyPayload, metadata) + if err != nil { + panic(err) + } + return packet + +} + +// ----- CHECK utilities +func CheckPushed(t *testing.T, want APacket, got APacket) { + Check(t, want, got, "Pushed") +} + +func CheckPersonalized(t *testing.T, want HRegistration, got HRegistration) { + Check(t, want, got, "Personalized") +} + +func CheckPackets(t *testing.T, want APacket, got APacket) { + Check(t, want, got, "Packets") +} + +func CheckEntries(t *testing.T, want *MockRegistration, got devEntry) { + // NOTE This only works in the case of Personalized devices + var devAddr lorawan.DevAddr + devEUI := want.DevEUI() + copy(devAddr[:], devEUI[4:]) + + wantEntry := devEntry{ + Recipient: want.RawRecipient(), + DevAddr: devAddr, + NwkSKey: want.NwkSKey(), + AppSKey: want.AppSKey(), + } + + Check(t, wantEntry, got, "Entries") +} diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 8b9144d38..c322f934c 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -87,15 +87,17 @@ func newMockPktStorage() *mockPktStorage { } func (s *mockPktStorage) Push(p APacket) error { - s.Pushed = p + s.InPush = p return s.Failures["Push"] } func (s *mockPktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { + s.InPullAppEUI = appEUI + s.InPullDevEUI = devEUI if s.Failures["Pull"] != nil { return nil, s.Failures["Pull"] } - return s.PullEntry, nil + return s.OutPull, nil } func (s *mockPktStorage) Close() error { diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 643e04dca..8d751f71c 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -5,7 +5,6 @@ package handler import ( "os" - "reflect" "testing" . "github.com/TheThingsNetwork/ttn/core" @@ -151,12 +150,3 @@ func TestPushPullNormal(t *testing.T) { CheckErrors(t, pointer.String(string(errors.Operational)), err) } } - -// ----- CHECK utilities -func CheckPackets(t *testing.T, want APacket, got APacket) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check Packets") - return - } - Ko(t, "Obtained packet doesn't match expectations.\nWant: %s\nGot: %s", want, got) -} diff --git a/core/mocks/mock.go b/core/mocks/mock.go index 5715392d8..5a96b7f5d 100644 --- a/core/mocks/mock.go +++ b/core/mocks/mock.go @@ -4,7 +4,13 @@ package mock import ( + "fmt" + "reflect" + "testing" + . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) @@ -96,8 +102,148 @@ func (r *MockRegistration) AppSKey() lorawan.AES128Key { return r.OutAppSKey } -//func (r *MockRegistration) DevAddr() lorawan.DevAddr { -// devAddr := lorawan.DevAddr{} -// copy(devAddr[:], r.devEUI[4:]) -// return devAddr -//} +// MockAckNacker implements the core.AckNacker interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockAckNacker struct { + InAck struct { + Ack *bool + Packet Packet + } +} + +func NewMockAckNacker() *MockAckNacker { + return &MockAckNacker{} +} + +func (an *MockAckNacker) Ack(p Packet) error { + an.InAck = struct { + Ack *bool + Packet Packet + }{ + Ack: pointer.Bool(true), + Packet: p, + } + return nil +} + +func (an *MockAckNacker) Nack() error { + an.InAck = struct { + Ack *bool + Packet Packet + }{ + Ack: pointer.Bool(false), + Packet: nil, + } + return nil +} + +// MockAdapter implements the core.Adapter interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockAdapter struct { + Failures map[string]error + InSendPacket Packet + InSendRecipients []Recipient + InGetRecipient []byte + OutSend []byte + OutGetRecipient Recipient + OutNextPacket []byte + OutNextAckNacker AckNacker + OutNextRegReg Registration + OutNextRegAckNacker AckNacker +} + +func NewMockAdapter() *MockAdapter { + return &MockAdapter{ + Failures: make(map[string]error), + OutSend: []byte("MockAdapterSend"), + OutGetRecipient: NewMockRecipient(), + OutNextPacket: []byte("MockAdapterNextPacket"), + OutNextAckNacker: NewMockAckNacker(), + OutNextRegReg: NewMockRegistration(), + OutNextRegAckNacker: NewMockAckNacker(), + } +} + +func (a *MockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { + a.InSendPacket = p + a.InSendRecipients = r + if a.Failures["Send"] != nil { + return nil, a.Failures["Send"] + } + return a.OutSend, nil +} + +func (a *MockAdapter) GetRecipient(raw []byte) (Recipient, error) { + a.InGetRecipient = raw + if a.Failures["GetRecipient"] != nil { + return nil, a.Failures["GetRecipient"] + } + return a.OutGetRecipient, nil +} + +func (a *MockAdapter) Next() ([]byte, AckNacker, error) { + if a.Failures["Next"] != nil { + return nil, nil, a.Failures["Next"] + } + return a.OutNextPacket, a.OutNextAckNacker, nil +} + +func (a *MockAdapter) NextRegistration() (Registration, AckNacker, error) { + if a.Failures["NextRegistration"] != nil { + return nil, nil, a.Failures["NextRegistration"] + } + return a.OutNextRegReg, a.OutNextRegAckNacker, nil +} + +// ----- CHECK utilities + +func Check(t *testing.T, want, got interface{}, name string) { + if !reflect.DeepEqual(want, got) { + Ko(t, "%s don't match expectations.\nWant: %v\nGot: %v", name, want, got) + } + Ok(t, fmt.Sprintf("Check %s", name)) +} + +func CheckAcks(t *testing.T, want interface{}, gotItf interface{}) { + got := gotItf.(struct { + Ack *bool + Packet Packet + }) + + if got.Ack == nil { + Ko(t, "Invalid ack got: %+v", got) + } + + switch want.(type) { + case bool: + Check(t, want.(bool), *(got.Ack), "Acks") + case Packet: + Check(t, want.(Packet), got.Packet, "Acks") + default: + panic("Unexpect ack wanted") + } +} + +func CheckRecipients(t *testing.T, want []Recipient, got []Recipient) { + Check(t, want, got, "Recipients") +} + +func CheckSent(t *testing.T, want Packet, got Packet) { + Check(t, want, got, "Sent") +} From 99b52c9232d0989dee7315e5e276359ca57bbeb4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:02:47 +0100 Subject: [PATCH 0833/2266] [test-router] Write down test for router storage --- core/components/router/helpers_test.go | 21 +++ core/components/router/storage_test.go | 211 +++++++++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 core/components/router/helpers_test.go create mode 100644 core/components/router/storage_test.go diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go new file mode 100644 index 000000000..87eef0501 --- /dev/null +++ b/core/components/router/helpers_test.go @@ -0,0 +1,21 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/core/mocks" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func CheckEntries(t *testing.T, want entry, got entry) { + tmin := want.until.Add(-time.Second) + tmax := want.until.Add(time.Second) + if !tmin.Before(got.until) || !got.until.Before(tmax) { + Ko(t, "Unexpected expiry time.\nWant: %s\nGot: %s", want.until, got.until) + } + Check(t, want.Recipient, got.Recipient, "Recipients") +} diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go new file mode 100644 index 000000000..f547d75d9 --- /dev/null +++ b/core/components/router/storage_test.go @@ -0,0 +1,211 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "os" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const storageDB = "TestRouterStorage.db" + +func TestStoreAndLookup(t *testing.T) { + defer func() { + os.Remove(storageDB) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + db, err := NewStorage(storageDB, time.Hour) + CheckErrors(t, nil, err) + err = db.Close() + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Store then lookup a registration") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + r := NewMockRegistration() + + // Operate + err := db.Store(r) + CheckErrors(t, nil, err) + gotEntry, err := db.Lookup(r.DevEUI()) + + // Expectations + wantEntry := entry{ + Recipient: r.RawRecipient(), + until: time.Now().Add(time.Hour), + } + + // Check + CheckErrors(t, nil, err) + CheckEntries(t, wantEntry, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Store an existing entry") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + r := NewMockRegistration() + + // Operate + err := db.Store(r) + gotEntry, _ := db.Lookup(r.DevEUI()) + + // Expectations + wantEntry := entry{ + Recipient: r.RawRecipient(), + until: time.Now().Add(time.Hour), + } + + // Checks + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckEntries(t, wantEntry, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Lookup non-existing entry") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + devEUI := NewMockRegistration().DevEUI() + devEUI[1] = 14 + + // Operate + gotEntry, err := db.Lookup(devEUI) + + // Checks + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, entry{}, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Lookup an expired entry") + + // Build + db, _ := NewStorage(storageDB, time.Millisecond*100) + r := NewMockRegistration() + r.OutDevEUI[0] = 12 + + // Operate + _ = db.Store(r) + <-time.After(time.Millisecond * 200) + gotEntry, err := db.Lookup(r.DevEUI()) + + // Checks + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, entry{}, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Store above an expired entry") + + // Build + db, _ := NewStorage(storageDB, time.Millisecond*100) + r := NewMockRegistration() + r.OutDevEUI[4] = 27 + + // Operate + _ = db.Store(r) + <-time.After(time.Millisecond * 200) + err := db.Store(r) + CheckErrors(t, nil, err) + gotEntry, err := db.Lookup(r.DevEUI()) + + // Expectations + wantEntry := entry{ + Recipient: r.RawRecipient(), + until: time.Now().Add(time.Millisecond * 200), + } + + // Checks + CheckErrors(t, nil, err) + CheckEntries(t, wantEntry, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Store on a closed database") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + _ = db.Close() + r := NewMockRegistration() + r.OutDevEUI[5] = 9 + + // Operate + err := db.Store(r) + + // Checks + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // ------------------ + + { + Desc(t, "Lookup on a closed database") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + _ = db.Close() + devEUI := NewMockRegistration().DevEUI() + + // Operate + gotEntry, err := db.Lookup(devEUI) + + // Checks + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckEntries(t, entry{}, gotEntry) + } + + // ------------------ + + { + Desc(t, "Store an invalid recipient") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + r := NewMockRegistration() + r.OutDevEUI[7] = 99 + r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") + + // Operate & Check + err := db.Store(r) + CheckErrors(t, pointer.String(string(errors.Structural)), err) + gotEntry, err := db.Lookup(r.DevEUI()) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, entry{}, gotEntry) + + _ = db.Close() + } +} From d7f0e2d71bca00e613c8676cc4bb1f2a40e77f01 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:03:12 +0100 Subject: [PATCH 0834/2266] [test-router] Fix issues in router/storage --- core/components/router/storage.go | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 5bdaf695a..4cda35759 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -17,6 +17,7 @@ import ( type Storage interface { Lookup(devEUI lorawan.EUI64) (entry, error) Store(reg Registration) error + Close() error } type entry struct { @@ -57,9 +58,9 @@ func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { itf, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) if err != nil { - return entry{}, errors.New(errors.Operational, err) + return entry{}, err } - entries := itf.([]*entry) + entries := itf.([]entry) if len(entries) != 1 { if err := s.db.Flush(s.Name, devEUI[:]); err != nil { @@ -77,7 +78,7 @@ func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { return entry{}, errors.New(errors.Behavioural, "Not Found") } - return *e, nil + return e, nil } // Store implements the router.Storage interface @@ -93,7 +94,10 @@ func (s *storage) Store(reg Registration) error { _, err = s.lookup(devEUI, false) if err == nil || err != nil && err.(errors.Failure).Nature != errors.Behavioural { - return errors.New(errors.Structural, "Already exists") + if err == nil { + return errors.New(errors.Structural, "Already exists") + } + return err } return s.db.Store(s.Name, devEUI[:], []dbutil.Entry{&entry{ Recipient: recipient, @@ -102,11 +106,21 @@ func (s *storage) Store(reg Registration) error { } +// Close implements the router.Storage interface +func (s *storage) Close() error { + return s.db.Close() +} + // MarshalBinary implements the encoding.BinaryMarshaler interface func (e entry) MarshalBinary() ([]byte, error) { + data, err := e.until.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + rw := readwriter.New(nil) rw.Write(e.Recipient) - rw.Write(e.until) + rw.Write(data) return rw.Bytes() } From 5d79c2afbfc33d5f5b9897b713da704cebff3f00 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:18:23 +0100 Subject: [PATCH 0835/2266] [test-router] Implement router mock storage --- core/components/router/mock_test.go | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 core/components/router/mock_test.go diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go new file mode 100644 index 000000000..fb392ea5e --- /dev/null +++ b/core/components/router/mock_test.go @@ -0,0 +1,45 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "time" + + . "github.com/TheThingsNetwork/ttn/core" + "github.com/brocaar/lorawan" +) + +type mockStorage struct { + Failures map[string]error + InLookup lorawan.EUI64 + OutLookup entry + InStore Registration +} + +func newMockStorage() *mockStorage { + return &mockStorage{ + Failures: make(map[string]error), + OutLookup: entry{ + Recipient: []byte("MockStorageRecipient"), + until: time.Date(2016, 2, 3, 14, 16, 22, 0, time.UTC), + }, + } +} + +func (s *mockStorage) Lookup(devEUI lorawan.EUI64) (entry, error) { + s.InLookup = devEUI + if s.Failures["Lookup"] != nil { + return entry{}, s.Failures["Lookup"] + } + return s.OutLookup, nil +} + +func (s *mockStorage) Store(reg Registration) error { + s.InStore = reg + return s.Failures["Store"] +} + +func (s *mockStorage) Close() error { + return s.Failures["Close"] +} From bd62fa22e0ae26c236f22d768b53af482a87f000 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:28:57 +0100 Subject: [PATCH 0836/2266] [test-router] Implement test for Register() method --- core/components/router/helpers_test.go | 5 +++ core/components/router/router_test.go | 52 ++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 87eef0501..3c022f2ff 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + . "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/mocks" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -19,3 +20,7 @@ func CheckEntries(t *testing.T, want entry, got entry) { } Check(t, want.Recipient, got.Recipient, "Recipients") } + +func CheckRegistrations(t *testing.T, want Registration, got Registration) { + Check(t, want, got, "Registrations") +} diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index dca7dd239..fa49b8cc4 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -2,3 +2,55 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package router + +import ( + "testing" + //"time" + + . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestRegister(t *testing.T) { + { + Desc(t, "Register an entry") + + // Build + an := NewMockAckNacker() + store := newMockStorage() + r := NewMockRegistration() + + // Operate + router := New(store, GetLogger(t, "Router")) + err := router.Register(r, an) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, store.InStore, r) + } + + // ------------------- + + { + Desc(t, "Register an entry | Store failed") + + // Build + an := NewMockAckNacker() + store := newMockStorage() + store.Failures["Store"] = errors.New(errors.Structural, "Mock Error: Store Failed") + r := NewMockRegistration() + + // Operate + router := New(store, GetLogger(t, "Router")) + err := router.Register(r, an) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, store.InStore, r) + } +} From 00f220682587a8e7137be03d5e192182b91d386b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:29:13 +0100 Subject: [PATCH 0837/2266] [test-router] Fix small issues in Register() method according to test results --- core/components/router/router.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 79595cb34..7944a92d4 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -25,11 +25,7 @@ func (r component) Register(reg Registration, an AckNacker) (err error) { defer ensureAckNack(an, nil, &err) stats.MarkMeter("router.registration.in") r.ctx.Debug("Handling registration") - - if err := r.Store(reg); err != nil { - return errors.New(errors.Operational, err) - } - return nil + return r.Store(reg) } // HandleUp implements the core.Component interface From 9a6a5c2ba7072ddc8ff95b4454e32f03c04f391c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:35:01 +0100 Subject: [PATCH 0838/2266] [test-router] Write tests for HandleDown method --- core/components/router/router_test.go | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index fa49b8cc4..46e583fd3 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -51,6 +51,28 @@ func TestRegister(t *testing.T) { // Check CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckAcks(t, false, an.InAck) - CheckRegistrations(t, store.InStore, r) + CheckRegistrations(t, r, store.InStore) + } +} + +func TestHandleDown(t *testing.T) { + { + Desc(t, "Try Handle Down") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + + // Operate + router := New(store, GetLogger(t, "Router")) + err := router.HandleDown([]byte{1, 2, 3}, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Implementation)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) } } From c5897ecd6edbced88f43f631aa1c43ac6b87dc18 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 14:35:08 +0100 Subject: [PATCH 0839/2266] [test-router] Make them pass --- core/components/router/router.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 7944a92d4..2e4619806 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -115,7 +115,8 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } // HandleDown implements the core.Component interface -func (r component) HandleDown(data []byte, an AckNacker, up Adapter) error { +func (r component) HandleDown(data []byte, an AckNacker, up Adapter) (err error) { + defer ensureAckNack(an, nil, &err) return errors.New(errors.Implementation, "Handle down not implemented on router") } From d5fef9bb6da459334fec10bbcfea427bbcbad2e6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Mar 2016 15:06:29 +0100 Subject: [PATCH 0840/2266] Use TmpDir for storage tests --- core/components/handler/devStorage_test.go | 5 +++-- core/components/handler/pktStorage_test.go | 5 +++-- utils/storage/storage_test.go | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 561861143..8e6ea7b8f 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -5,6 +5,7 @@ package handler import ( "os" + "path" "testing" . "github.com/TheThingsNetwork/ttn/core" @@ -21,7 +22,7 @@ const devDB = "TestDevStorage.db" func TestLookupStore(t *testing.T) { var db DevStorage defer func() { - os.Remove(devDB) + os.Remove(path.Join(os.TempDir(), devDB)) }() // ------------------ @@ -29,7 +30,7 @@ func TestLookupStore(t *testing.T) { { Desc(t, "Create a new storage") var err error - db, err = NewDevStorage(devDB) + db, err = NewDevStorage(path.Join(os.TempDir(), devDB)) CheckErrors(t, nil, err) } diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 8d751f71c..1961694a9 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -5,6 +5,7 @@ package handler import ( "os" + "path" "testing" . "github.com/TheThingsNetwork/ttn/core" @@ -20,7 +21,7 @@ const pktDB = "TestPktStorage.db" func TestPushPullNormal(t *testing.T) { var db PktStorage defer func() { - os.Remove(pktDB) + os.Remove(path.Join(os.TempDir(), pktDB)) }() // ------------------ @@ -28,7 +29,7 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Create a new storage") var err error - db, err = NewPktStorage(pktDB) + db, err = NewPktStorage(path.Join(os.TempDir(), pktDB)) CheckErrors(t, nil, err) } diff --git a/utils/storage/storage_test.go b/utils/storage/storage_test.go index 135872d68..2b25bcfe3 100644 --- a/utils/storage/storage_test.go +++ b/utils/storage/storage_test.go @@ -5,6 +5,7 @@ package storage import ( "os" + "path" "reflect" "testing" @@ -25,15 +26,15 @@ func TestStoreAndLookup(t *testing.T) { if err := itf.Close(); err != nil { panic(err) } - os.Remove(database) + os.Remove(path.Join(os.TempDir(), database)) }() // -------------------- { - Desc(t, "Open database in the current folder") + Desc(t, "Open database in tmp folder") var err error - itf, err = New(database) + itf, err = New(path.Join(os.TempDir(), database)) CheckErrors(t, nil, err) } From 5516839ff6abe25ca34f9730bc4925d1f7a2ed13 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Mar 2016 15:57:36 +0100 Subject: [PATCH 0841/2266] Fix build information --- main.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index 53e4d81ed..8cdb2c9e8 100644 --- a/main.go +++ b/main.go @@ -14,8 +14,8 @@ var ( ) func main() { - viper.Set("version", "a development version") - viper.Set("git-commit", gitCommit) - viper.Set("build-date", buildDate) + viper.Set("version", "v0") + viper.Set("gitCommit", gitCommit) + viper.Set("buildDate", buildDate) cmd.Execute() } From 018f0633d017257bad2409b11f373b31d24baad9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 16:04:13 +0100 Subject: [PATCH 0842/2266] [test-router] Implement test for HandleUp() method in router --- core/components/router/helpers_test.go | 47 +++++ core/components/router/router_test.go | 250 ++++++++++++++++++++++++- 2 files changed, 296 insertions(+), 1 deletion(-) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 3c022f2ff..1e869aeab 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -10,8 +10,55 @@ import ( . "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/mocks" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) +// ----- BUILD utilities +func newRPacket(rawDevAddr [4]byte, payload string, gatewayId []byte) RPacket { + var devAddr lorawan.DevAddr + copy(devAddr[:], rawDevAddr[:]) + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR.DevAddr = devAddr + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + macPayload.FPort = 1 + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MACPayload = macPayload + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + + packet, err := NewRPacket(phyPayload, gatewayId, Metadata{}) + if err != nil { + panic(err) + } + return packet +} + +func newBPacket(rawDevAddr [4]byte, payload string) BPacket { + var devAddr lorawan.DevAddr + copy(devAddr[:], rawDevAddr[:]) + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR.DevAddr = devAddr + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + macPayload.FPort = 1 + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MACPayload = macPayload + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + + packet, err := NewBPacket(phyPayload, Metadata{}) + if err != nil { + panic(err) + } + return packet +} + +// ----- CHECK utilities func CheckEntries(t *testing.T, want entry, got entry) { tmin := want.until.Add(-time.Second) tmax := want.until.Add(time.Second) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 46e583fd3..69f400507 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -5,8 +5,9 @@ package router import ( "testing" - //"time" + "time" + . "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" @@ -76,3 +77,250 @@ func TestHandleDown(t *testing.T) { CheckRecipients(t, nil, adapter.InSendRecipients) } } + +func TestHandleUp(t *testing.T) { + { + Desc(t, "Send an unknown packet | No downlink") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutSend = nil + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send an unknown packet | With Downlink") + + // Build + an := NewMockAckNacker() + resp := newRPacket( + [4]byte{2, 3, 2, 3}, + "Response", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ) + dataResp, _ := resp.MarshalBinary() + adapter := NewMockAdapter() + adapter.OutSend = dataResp + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, resp, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send invalid data") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + + // Operate + router := New(store, GetLogger(t, "Router")) + err := router.HandleUp([]byte{1, 2, 3}, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send an unknown packet | No downlink | Storage fail lookup ") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutSend = nil + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Operational, "Mock Error: Lookup failed") + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send known packet | No downlink") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutSend = nil + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + store := newMockStorage() + store.OutLookup = entry{ + Recipient: dataRecipient, + until: time.Now().Add(time.Hour), + } + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send unknown packet | Get wrong recipient from db") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Invalid recipient") + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + store := newMockStorage() + store.OutLookup = entry{ + Recipient: dataRecipient, + until: time.Now().Add(time.Hour), + } + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send unknown packet | Sending fails") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not found") + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send unknown packet | Get invalid downlink response") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutSend = []byte{1, 2, 3} + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not found") + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } +} From 2cd86c97f797cca98599444ec95032a30d570319 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 16:04:22 +0100 Subject: [PATCH 0843/2266] [test-router] Fix last issues in router --- core/components/router/router.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 2e4619806..7a09655bc 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -79,7 +79,13 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return errors.New(errors.Structural, err) } - response, err := up.Send(bpacket, recipient) + var response []byte + + if recipient == nil { + response, err = up.Send(bpacket) + } else { + response, err = up.Send(bpacket, recipient) + } if err != nil { stats.MarkMeter("router.uplink.bad_broker_response") @@ -87,17 +93,21 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return errors.New(errors.Operational, err) } + // No response, stop there + if response == nil { + return nil + } + itf, err := UnmarshalPacket(response) if err != nil { stats.MarkMeter("router.uplink.bad_broker_response") r.ctx.WithError(err).Warn("Invalid response from Broker") - return errors.New(errors.Structural, err) + return errors.New(errors.Operational, err) } switch itf.(type) { case RPacket: ack = itf.(RPacket) - case nil: default: return errors.New(errors.Implementation, "Unexpected packet type") } From 2d94e8accc7856c68c97ca5d8ec7a500bb10db3e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 17:33:34 +0100 Subject: [PATCH 0844/2266] [test-broker] Write tests for broker storage --- core/components/broker/helpers_test.go | 21 +++ core/components/broker/storage_test.go | 179 +++++++++++++++++++++++++ 2 files changed, 200 insertions(+) create mode 100644 core/components/broker/helpers_test.go create mode 100644 core/components/broker/storage_test.go diff --git a/core/components/broker/helpers_test.go b/core/components/broker/helpers_test.go new file mode 100644 index 000000000..052ae2b9e --- /dev/null +++ b/core/components/broker/helpers_test.go @@ -0,0 +1,21 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" + //"github.com/brocaar/lorawan" +) + +// ----- CHECK utilities +func CheckEntries(t *testing.T, want []entry, got []entry) { + Check(t, want, got, "Entries") +} + +func CheckRegistrations(t *testing.T, want Registration, got Registration) { + Check(t, want, got, "Registrations") +} diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go new file mode 100644 index 000000000..b0006203e --- /dev/null +++ b/core/components/broker/storage_test.go @@ -0,0 +1,179 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "os" + "testing" + + . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const storageDB = "TestBrokerStorage.db" + +func TestRegister(t *testing.T) { + defer func() { + os.Remove(storageDB) + }() + + // ------------------- + + { + Desc(t, "Create a new storage") + db, err := NewStorage(storageDB) + CheckErrors(t, nil, err) + err = db.Close() + CheckErrors(t, nil, err) + } + + // ------------------- + + { + Desc(t, "Store then lookup a registration") + + // Build + db, _ := NewStorage(storageDB) + r := NewMockRegistration() + + // Operate + err := db.Store(r) + CheckErrors(t, nil, err) + entries, err := db.Lookup(r.DevEUI()) + + // Expectations + want := []entry{ + { + AppEUI: r.AppEUI(), + DevEUI: r.DevEUI(), + NwkSKey: r.NwkSKey(), + Recipient: r.RawRecipient(), + }, + } + + // Check + CheckErrors(t, nil, err) + CheckEntries(t, want, entries) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Store entries with same DevEUI") + + // Build + db, _ := NewStorage(storageDB) + r := NewMockRegistration() + r.OutDevEUI[0] = 34 + + // Operate + err := db.Store(r) + CheckErrors(t, nil, err) + err = db.Store(r) + CheckErrors(t, nil, err) + entries, err := db.Lookup(r.DevEUI()) + + // Expectations + want := []entry{ + { + AppEUI: r.AppEUI(), + DevEUI: r.DevEUI(), + NwkSKey: r.NwkSKey(), + Recipient: r.RawRecipient(), + }, + { + AppEUI: r.AppEUI(), + DevEUI: r.DevEUI(), + NwkSKey: r.NwkSKey(), + Recipient: r.RawRecipient(), + }, + } + + // Check + CheckErrors(t, nil, err) + CheckEntries(t, want, entries) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Lookup non-existing entry") + + // Build + db, _ := NewStorage(storageDB) + devEUI := NewMockRegistration().DevEUI() + devEUI[1] = 98 + + // Operate + entries, err := db.Lookup(devEUI) + + // Checks + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Store on a closed database") + + // Build + db, _ := NewStorage(storageDB) + _ = db.Close() + r := NewMockRegistration() + r.OutDevEUI[5] = 9 + + // Operate + err := db.Store(r) + + // Checks + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // ------------------- + + { + Desc(t, "Lookup on a closed database") + + // Build + db, _ := NewStorage(storageDB) + _ = db.Close() + devEUI := NewMockRegistration().DevEUI() + + // Operate + entries, err := db.Lookup(devEUI) + + // Checks + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckEntries(t, nil, entries) + } + + // ------------------- + + { + Desc(t, "Store an invalid recipient") + + // Build + db, _ := NewStorage(storageDB) + r := NewMockRegistration() + r.OutDevEUI[7] = 99 + r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") + + // Operate & Check + err := db.Store(r) + CheckErrors(t, pointer.String(string(errors.Structural)), err) + entries, err := db.Lookup(r.DevEUI()) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckEntries(t, nil, entries) + + _ = db.Close() + } +} From 0e5df47f2fa860e465861c7e2cb9f2ae00ee277d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 17:34:32 +0100 Subject: [PATCH 0845/2266] [test-broker] Make them pass --- core/components/broker/storage.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index aac37578c..7ecc7c41b 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -43,7 +43,7 @@ func NewStorage(name string) (Storage, error) { func (s storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { entries, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) if err != nil { - return nil, errors.New(errors.Operational, err) + return nil, err } return entries.([]entry), nil } From 7c1e88c53a109e34283c6ccdc69663a748cc8649 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 17:38:30 +0100 Subject: [PATCH 0846/2266] [test-broker] implement broker mock storage interface --- core/components/broker/mock_test.go | 47 +++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 core/components/broker/mock_test.go diff --git a/core/components/broker/mock_test.go b/core/components/broker/mock_test.go new file mode 100644 index 000000000..a98f37e00 --- /dev/null +++ b/core/components/broker/mock_test.go @@ -0,0 +1,47 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + . "github.com/TheThingsNetwork/ttn/core" + "github.com/brocaar/lorawan" +) + +type mockStorage struct { + Failures map[string]error + InLookup lorawan.EUI64 + OutLookup []entry + InStore BRegistration +} + +func newMockStorage() *mockStorage { + return &mockStorage{ + Failures: make(map[string]error), + OutLookup: []entry{ + { + Recipient: []byte("MockStorageRecipient"), + AppEUI: lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 1, 1}), + DevEUI: lorawan.EUI64([8]byte{2, 3, 4, 5, 6, 7, 5, 4}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5}), + }, + }, + } +} + +func (s *mockStorage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { + s.InLookup = devEUI + if s.Failures["Lookup"] != nil { + return nil, s.Failures["Lookup"] + } + return s.OutLookup, nil +} + +func (s *mockStorage) Store(reg BRegistration) error { + s.InStore = reg + return s.Failures["Store"] +} + +func (s *mockStorage) Close() error { + return s.Failures["Close"] +} From bfa1467841c5c312e3d7d6233234bb2b63df5b56 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 17:50:12 +0100 Subject: [PATCH 0847/2266] [test-broker] Implement tests for Register() and HandleDown() in broker --- core/components/broker/broker_test.go | 98 ++++++++++++++++++++++++++ core/components/broker/storage_test.go | 2 +- core/mocks/mock.go | 33 ++++++++- 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 9c183f8cd..0127b2b71 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -2,3 +2,101 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package broker + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestRegister(t *testing.T) { + { + Desc(t, "Register an entry") + + // Build + an := NewMockAckNacker() + store := newMockStorage() + r := NewMockRegistration() + + // Operate + broker := New(store, GetLogger(t, "Router")) + err := broker.Register(r, an) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, r, store.InStore) + } + + // ------------------- + + { + Desc(t, "Register an entry | store failed") + + // Build + an := NewMockAckNacker() + store := newMockStorage() + store.Failures["Store"] = errors.New(errors.Structural, "Mock Error: Store Failed") + r := NewMockRegistration() + + // Operate + broker := New(store, GetLogger(t, "Router")) + err := broker.Register(r, an) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, r, store.InStore) + } + + // ------------------- + + { + Desc(t, "Register an entry | Wrong registration") + + // Build + an := NewMockAckNacker() + store := newMockStorage() + r := NewMockRRegistration() + + // Operate + broker := New(store, GetLogger(t, "Router")) + err := broker.Register(r, an) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + } +} + +func TestHandleDown(t *testing.T) { + { + Desc(t, "Try Handle Down") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleDown([]byte{1, 2, 3}, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Implementation)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } +} + +func TestHandleUp(t *testing.T) { + +} diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index b0006203e..6ea1e6e8a 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -17,7 +17,7 @@ import ( const storageDB = "TestBrokerStorage.db" -func TestRegister(t *testing.T) { +func TestStorage(t *testing.T) { defer func() { os.Remove(storageDB) }() diff --git a/core/mocks/mock.go b/core/mocks/mock.go index 5a96b7f5d..c6465df4c 100644 --- a/core/mocks/mock.go +++ b/core/mocks/mock.go @@ -53,7 +53,7 @@ func (r *MockRecipient) UnmarshalBinary(data []byte) error { return nil } -// MockRegistration implements the core.Recipient interface +// MockRegistration implements the core.HRegistration interface // // It also stores the last arguments of each function call in appropriated // attributes. Because there's no computation going on, the expected / wanted @@ -102,6 +102,37 @@ func (r *MockRegistration) AppSKey() lorawan.AES128Key { return r.OutAppSKey } +// MockRRegistration implements the core.Registration interface +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockRRegistration struct { + OutDevEUI lorawan.EUI64 + OutRecipient Recipient +} + +func NewMockRRegistration() *MockRRegistration { + return &MockRRegistration{ + OutDevEUI: lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + OutRecipient: NewMockRecipient(), + } +} + +func (r *MockRRegistration) Recipient() Recipient { + return r.OutRecipient +} + +func (r *MockRRegistration) RawRecipient() []byte { + data, _ := r.Recipient().MarshalBinary() + return data +} + +func (r *MockRRegistration) DevEUI() lorawan.EUI64 { + return r.OutDevEUI +} + // MockAckNacker implements the core.AckNacker interface // // It declares a `Failures` attributes that can be used to From 21d0d12b24d52d52dbafbd48d14b2a72c0930fa8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 17:50:36 +0100 Subject: [PATCH 0848/2266] [test-broker] Fix issues in broker Register and HandleDown() --- core/components/broker/broker.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 558657113..ff2f43fcd 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -33,10 +33,7 @@ func (b component) Register(reg Registration, an AckNacker) (err error) { return errors.New(errors.Structural, "Not a Broker registration") } - if err := b.Store(breg); err != nil { - return errors.New(errors.Operational, err) - } - return nil + return b.Store(breg) } // HandleUp implements the core.Component interface @@ -159,7 +156,8 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } // HandleDown implements the core.Component interface -func (b component) HandleDown(data []byte, an AckNacker, down Adapter) error { +func (b component) HandleDown(data []byte, an AckNacker, down Adapter) (err error) { + defer ensureAckNack(an, nil, &err) return errors.New(errors.Implementation, "Handle Down not implemented on broker") } From 1fd97c0d11834b1732078b1d85ef41f5a3c499b2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 18:26:58 +0100 Subject: [PATCH 0849/2266] [test-broker] Implement test for HandleUp() method --- core/components/broker/broker_test.go | 239 +++++++++++++++++++++++++ core/components/broker/helpers_test.go | 34 +++- 2 files changed, 272 insertions(+), 1 deletion(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 0127b2b71..a471ef383 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -12,6 +12,7 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestRegister(t *testing.T) { @@ -98,5 +99,243 @@ func TestHandleDown(t *testing.T) { } func TestHandleUp(t *testing.T) { + { + Desc(t, "Send an unknown packet") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + data, _ := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ).MarshalBinary() + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send an invalid packet") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleUp([]byte{1, 2, 3}, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send packet, get 2 entries, no valid MIC") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + store.OutLookup = []entry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + }, + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 5, 5, 5, 5}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 6, 6, 11, 12, 13, 14, 12, 16}), + }, + } + data, _ := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ).MarshalBinary() + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + // ------------------- + + { + Desc(t, "Send packet, get 2 entries, 1 valid MIC | No downlink") + + // Build + an := NewMockAckNacker() + recipient := NewMockRecipient() + adapter := NewMockAdapter() + adapter.OutSend = nil + adapter.OutGetRecipient = recipient + store := newMockStorage() + store.OutLookup = []entry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + }, + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ := bpacket.MarshalBinary() + hpacket, _ := NewHPacket( + store.OutLookup[1].AppEUI, + store.OutLookup[1].DevEUI, + bpacket.Payload(), + bpacket.Metadata(), + ) + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, hpacket, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to get recipient") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutSend = nil + adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Unable to get recipient") + store := newMockStorage() + store.OutLookup = []entry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + }, + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ := bpacket.MarshalBinary() + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to send") + + // Build + an := NewMockAckNacker() + recipient := NewMockRecipient() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient + adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") + store := newMockStorage() + store.OutLookup = []entry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), + }, + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ := bpacket.MarshalBinary() + hpacket, _ := NewHPacket( + store.OutLookup[1].AppEUI, + store.OutLookup[1].DevEUI, + bpacket.Payload(), + bpacket.Metadata(), + ) + + // Operate + broker := New(store, GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, hpacket, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + } } diff --git a/core/components/broker/helpers_test.go b/core/components/broker/helpers_test.go index 052ae2b9e..254349e04 100644 --- a/core/components/broker/helpers_test.go +++ b/core/components/broker/helpers_test.go @@ -8,9 +8,41 @@ import ( . "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/mocks" - //"github.com/brocaar/lorawan" + "github.com/brocaar/lorawan" ) +// ----- BUILD utilities +func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint32) BPacket { + var devAddr lorawan.DevAddr + copy(devAddr[:], rawDevAddr[:]) + + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + FCnt: fcnt, + DevAddr: devAddr, + } + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} + macPayload.FPort = 1 + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MACPayload = macPayload + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + + var key lorawan.AES128Key + copy(key[:], nwkSKey[:]) + if err := phyPayload.SetMIC(key); err != nil { + panic(err) + } + + packet, err := NewBPacket(phyPayload, Metadata{}) + if err != nil { + panic(err) + } + return packet +} + // ----- CHECK utilities func CheckEntries(t *testing.T, want []entry, got []entry) { Check(t, want, got, "Entries") From 4431c079430bc73015e584022ace4132b8aca6fe Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 18:27:25 +0100 Subject: [PATCH 0850/2266] [test-broker] Fix issues in broker related to HandleUp(). Also comment non operational parts --- core/components/broker/broker.go | 40 ++++++++++++++++---------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index ff2f43fcd..8ed979dce 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -106,11 +106,11 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // to the MIC check // 3. If one was found, we notify the network controller - if err := b.Controller.HandleCommands(packet); err != nil { - ctx.WithError(err).Error("Failed to handle mac commands") - // Shall we return ? Sounds quite safe to keep going - } - b.Controller.UpdateFCntUp(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt()) + //if err := b.Controller.HandleCommands(packet); err != nil { + // ctx.WithError(err).Error("Failed to handle mac commands") + // Shall we return ? Sounds quite safe to keep going + //} + //b.Controller.UpdateFCntUp(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt()) // 4. Then we forward the packet to the handler and wait for the response hpacket, err := NewHPacket(mEntry.AppEUI, mEntry.DevEUI, packet.Payload(), packet.Metadata()) @@ -121,7 +121,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if err != nil { return errors.New(errors.Structural, err) } - resp, err := up.Send(hpacket, recipient) + _, err = up.Send(hpacket, recipient) if err != nil { stats.MarkMeter("broker.uplink.bad_handler_response") return errors.New(errors.Operational, err) @@ -129,22 +129,22 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { stats.MarkMeter("broker.uplink.ok") // 5. If a response was sent, i.e. a downlink data, we notify the network controller - var bpacket BPacket - if resp != nil { - itf, err := UnmarshalPacket(resp) - if err != nil { - return errors.New(errors.Operational, err) - } - var ok bool - bpacket, ok = itf.(BPacket) - if !ok { - return errors.New(errors.Operational, "Received unexpected response") - } - b.Controller.UpdateFCntDown(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt()) - } + //var bpacket BPacket + //if resp != nil { + // itf, err := UnmarshalPacket(resp) + // if err != nil { + // return errors.New(errors.Operational, err) + // } + // var ok bool + // bpacket, ok = itf.(BPacket) + // if !ok { + // return errors.New(errors.Operational, "Received unexpected response") + // } + // //b.Controller.UpdateFCntDown(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt()) + //} // 6. And finally, we acknowledge the answer - ack = b.Controller.MergeCommands(mEntry.AppEUI, mEntry.DevEUI, bpacket) + //ack = b.Controller.MergeCommands(mEntry.AppEUI, mEntry.DevEUI, bpacket) case JPacket: // TODO return errors.New(errors.Implementation, "Join Request not yet implemented") From aa95fadda2c18c06defda374bd7c4b0e3b3e79c4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 18:44:45 +0100 Subject: [PATCH 0851/2266] [golint] Add comment lines and review variable notation --- core/core.go | 9 +++++ core/metadata.go | 2 + core/packets.go | 66 ++++++++++++++++---------------- core/packets_interfaces.go | 12 +++++- core/packets_test.go | 2 +- core/registrations_interfaces.go | 6 +++ 6 files changed, 61 insertions(+), 36 deletions(-) diff --git a/core/core.go b/core/core.go index 11804a521..fa871a9ce 100644 --- a/core/core.go +++ b/core/core.go @@ -7,12 +7,17 @@ import ( "github.com/brocaar/lorawan" ) +// Component materializes a core component of the network: Router, Broker or Handler. +// +// With two adapters, it can communicates with others components and thus create a distributed +// network of components. type Component interface { Register(reg Registration, an AckNacker) error HandleUp(p []byte, an AckNacker, upAdapter Adapter) error HandleDown(p []byte, an AckNacker, downAdapter Adapter) error } +// NetworkController is directly used by the broker to manage nodes lifecycle. type NetworkController interface { HandleCommands(packet BPacket) error UpdateFCntUp(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) @@ -20,11 +25,15 @@ type NetworkController interface { MergeCommands(appEUI lorawan.EUI64, devEUI lorawan.EUI64, pkt BPacket) RPacket } +// AckNacker represents an interface that allow adapters to decouple their protocol from the +// behaviour expected by the caller. type AckNacker interface { Ack(p Packet) error Nack() error } +// Adapter handles communications between components. They implement a specific communication +// protocol which is completely hidden from the outside. type Adapter interface { Send(p Packet, r ...Recipient) ([]byte, error) //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) diff --git a/core/metadata.go b/core/metadata.go index bc46bd91a..2eaf927b8 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -12,6 +12,8 @@ import ( "github.com/TheThingsNetwork/ttn/utils/pointer" ) +// Metadata are carried by any type of packets. They constitute additional informations on the +// packet itself or, about its context (gateway, duty cycle, etc ...) type Metadata struct { Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier diff --git a/core/packets.go b/core/packets.go index 578ff7fb5..f87229862 100644 --- a/core/packets.go +++ b/core/packets.go @@ -13,13 +13,13 @@ import ( ) const ( - type_rpacket byte = iota - type_bpacket - type_hpacket - type_apacket - type_jpacket - type_cpacket - type_spacket + typeRPacket byte = iota + typeBPacket + typeHPacket + typeAPacket + typeJPacket + typeCPacket + typeSPacket ) // --------------------------------- @@ -82,17 +82,17 @@ func UnmarshalPacket(data []byte) (interface{}, error) { } switch data[0] { - case type_rpacket: + case typeRPacket: packet = new(rpacket) - case type_bpacket: + case typeBPacket: packet = new(bpacket) - case type_hpacket: + case typeHPacket: packet = new(hpacket) - case type_apacket: + case typeAPacket: packet = new(apacket) - case type_jpacket: + case typeJPacket: packet = new(jpacket) - case type_cpacket: + case typeCPacket: packet = new(cpacket) } @@ -110,11 +110,11 @@ func UnmarshalPacket(data []byte) (interface{}, error) { type rpacket struct { baserpacket basempacket - gatewayId baseapacket + gatewayID baseapacket } // NewRPacket construct a new router packet given a payload and metadata -func NewRPacket(payload lorawan.PHYPayload, gatewayId []byte, metadata Metadata) (RPacket, error) { +func NewRPacket(payload lorawan.PHYPayload, gatewayID []byte, metadata Metadata) (RPacket, error) { if payload.MACPayload == nil { return nil, errors.New(errors.Structural, "MACPAyload should not be empty") } @@ -127,23 +127,23 @@ func NewRPacket(payload lorawan.PHYPayload, gatewayId []byte, metadata Metadata) return &rpacket{ baserpacket: baserpacket{payload: payload}, basempacket: basempacket{metadata: metadata}, - gatewayId: baseapacket{payload: gatewayId}, + gatewayID: baseapacket{payload: gatewayID}, }, nil } -// GatewayId implements the core.RPacket interface -func (p rpacket) GatewayId() []byte { - return p.gatewayId.payload +// gatewayID implements the core.RPacket interface +func (p rpacket) GatewayID() []byte { + return p.gatewayID.payload } // MarshalBinary implements the encoding.BinaryMarshaler interface func (p rpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_rpacket, p.baserpacket, p.basempacket, p.gatewayId) + return marshalBases(typeRPacket, p.baserpacket, p.basempacket, p.gatewayID) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *rpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_rpacket, data, &p.baserpacket, &p.basempacket, &p.gatewayId) + return unmarshalBases(typeRPacket, data, &p.baserpacket, &p.basempacket, &p.gatewayID) } // String implements the Stringer interface @@ -208,12 +208,12 @@ func (p bpacket) String() string { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p bpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_bpacket, p.baserpacket, p.basempacket) + return marshalBases(typeBPacket, p.baserpacket, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *bpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_bpacket, data, &p.baserpacket, &p.basempacket) + return unmarshalBases(typeBPacket, data, &p.baserpacket, &p.basempacket) } // --------------------------------- @@ -279,12 +279,12 @@ func (p hpacket) FCnt() uint32 { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p hpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_hpacket, p.basehpacket, p.payload, p.basempacket) + return marshalBases(typeHPacket, p.basehpacket, p.payload, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *hpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_hpacket, data, &p.basehpacket, &p.payload, &p.basempacket) + return unmarshalBases(typeHPacket, data, &p.basehpacket, &p.payload, &p.basempacket) } // String implements the fmt.Stringer interface @@ -328,12 +328,12 @@ func NewAPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, meta // MarshalBinary implements the encoding.BinaryMarshaler interface func (p apacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_apacket, p.basehpacket, p.baseapacket, p.basegpacket) + return marshalBases(typeAPacket, p.basehpacket, p.baseapacket, p.basegpacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *apacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_apacket, data, &p.basehpacket, &p.baseapacket, &p.basegpacket) + return unmarshalBases(typeAPacket, data, &p.basehpacket, &p.baseapacket, &p.basegpacket) } // String implements the fmt.Stringer interface @@ -360,7 +360,7 @@ type jpacket struct { basempacket } -// NewJoinPacket constructs a new JoinPacket +// NewJPacket constructs a new JoinPacket func NewJPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, metadata Metadata) JPacket { return &jpacket{ basehpacket: basehpacket{ @@ -379,12 +379,12 @@ func (p jpacket) DevNonce() [2]byte { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p jpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_jpacket, p.basehpacket, p.baseapacket, p.basempacket) + return marshalBases(typeJPacket, p.basehpacket, p.baseapacket, p.basempacket) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *jpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_jpacket, data, &p.basehpacket, &p.baseapacket, &p.basempacket) + return unmarshalBases(typeJPacket, data, &p.basehpacket, &p.baseapacket, &p.basempacket) } // String implements the fmt.Stringer interface @@ -399,7 +399,7 @@ type cpacket struct { nwkSKey baseapacket } -// NewAcceptPacket constructs a new CPacket +// NewCPacket constructs a new CPacket func NewCPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, nwkSKey lorawan.AES128Key) (CPacket, error) { if len(payload) == 0 { return nil, errors.New(errors.Structural, "Payload cannot be empty") @@ -424,12 +424,12 @@ func (p cpacket) NwkSKey() lorawan.AES128Key { // MarshalBinary implements the encoding.BinaryMarshaler interface func (p cpacket) MarshalBinary() ([]byte, error) { - return marshalBases(type_cpacket, p.basehpacket, p.baseapacket, p.nwkSKey) + return marshalBases(typeCPacket, p.basehpacket, p.baseapacket, p.nwkSKey) } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (p *cpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(type_cpacket, data, &p.basehpacket, &p.baseapacket, &p.nwkSKey) + return unmarshalBases(typeCPacket, data, &p.basehpacket, &p.baseapacket, &p.nwkSKey) } // String implements the fmt.Stringer interface diff --git a/core/packets_interfaces.go b/core/packets_interfaces.go index 247b0f4e6..b9c6684ec 100644 --- a/core/packets_interfaces.go +++ b/core/packets_interfaces.go @@ -10,25 +10,29 @@ import ( "github.com/brocaar/lorawan" ) +// Packet represents the core base of a Packet type Packet interface { DevEUI() lorawan.EUI64 encoding.BinaryMarshaler fmt.Stringer } +// RPacket represents packets manipulated by the router that hold Data type RPacket interface { Packet - GatewayId() []byte + GatewayID() []byte Metadata() Metadata Payload() lorawan.PHYPayload } +// SPacket represents packets manipulated by the router that hold Stats type SPacket interface { Packet - GatewayId() []byte + GatewayID() []byte Metadata() Metadata } +// BPacket represents packets manipulated by the broker that hold Data type BPacket interface { Packet Commands() []lorawan.MACCommand @@ -38,6 +42,7 @@ type BPacket interface { ValidateMIC(key lorawan.AES128Key) (bool, error) } +// HPacket represents packets manipulated by the handler that hold Data type HPacket interface { Packet AppEUI() lorawan.EUI64 @@ -46,6 +51,7 @@ type HPacket interface { Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up } +// APacket represents packets sent towards an application type APacket interface { Packet AppEUI() lorawan.EUI64 @@ -53,6 +59,7 @@ type APacket interface { Metadata() []Metadata } +// JPacket represents join request packets type JPacket interface { Packet AppEUI() lorawan.EUI64 @@ -60,6 +67,7 @@ type JPacket interface { Metadata() Metadata // Rssi + DutyCycle } +// CPacket represents join accept (confirmation) packets type CPacket interface { Packet AppEUI() lorawan.EUI64 diff --git a/core/packets_test.go b/core/packets_test.go index 6fe9d9346..a16ed29d9 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -209,7 +209,7 @@ func TestRPacket(t *testing.T) { output := gOutput.(RPacket) a.So(output.Payload(), ShouldResemble, payload) - a.So(output.GatewayId(), ShouldResemble, gwEUI) + a.So(output.GatewayID(), ShouldResemble, gwEUI) a.So(output.Metadata(), ShouldResemble, Metadata{}) outputDevEUI := output.DevEUI() a.So(outputDevEUI[4:], ShouldResemble, devAddr[:]) diff --git a/core/registrations_interfaces.go b/core/registrations_interfaces.go index fede88696..e45cf271a 100644 --- a/core/registrations_interfaces.go +++ b/core/registrations_interfaces.go @@ -9,21 +9,27 @@ import ( "github.com/brocaar/lorawan" ) +// Recipient represents the recipient manipulated by adapters type Recipient interface { encoding.BinaryMarshaler } +// Registration represents the first-level of registration, used by router and router adapters type Registration interface { Recipient() Recipient DevEUI() lorawan.EUI64 } +// BRegistration represents the second-level of registrations, used by the broker and broker +// adapters type BRegistration interface { Registration AppEUI() lorawan.EUI64 NwkSKey() lorawan.AES128Key } +// HRegistration represents the third-level of registrations, used bt the handler and handler +// adapters type HRegistration interface { BRegistration AppSKey() lorawan.AES128Key From 371c8f85f9a0ff6d9c7b3c1a279b38d336e8d3db Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 19:05:03 +0100 Subject: [PATCH 0852/2266] [golint] Apply linter to components --- core/components/broker/storage.go | 1 + core/components/handler/devStorage.go | 1 + core/components/handler/handler.go | 32 ++++++++++++------------- core/components/handler/handler_test.go | 2 +- core/components/handler/pktStorage.go | 1 + core/components/router/helpers_test.go | 4 ++-- core/components/router/storage.go | 1 + 7 files changed, 23 insertions(+), 19 deletions(-) diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 7ecc7c41b..5c63a5103 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -11,6 +11,7 @@ import ( "github.com/brocaar/lorawan" ) +// Storage gives a facade for manipulating the broker database type Storage interface { Lookup(devEUI lorawan.EUI64) ([]entry, error) Store(reg BRegistration) error diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index a65505e86..d7f9b6d43 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -13,6 +13,7 @@ import ( "github.com/brocaar/lorawan" ) +// DevStorage gives a facade to manipulate the handler devices database type DevStorage interface { Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) StorePersonalized(r HRegistration) error diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c61ff384a..ea3d03987 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -14,8 +14,8 @@ import ( "github.com/brocaar/lorawan" ) -const buffer_delay time.Duration = time.Millisecond * 300 -const max_duty_cycle = 90 // 90% +const bufferDelay time.Duration = time.Millisecond * 300 +const maxDutyCycle = 90 // 90% // component implements the core.Component interface type component struct { @@ -29,7 +29,7 @@ type bundle struct { Adapter Adapter Chresp chan interface{} Entry devEntry - Id [20]byte + ID [20]byte Packet HPacket } @@ -95,7 +95,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { chresp := make(chan interface{}) // 3. Create a "bundle" which holds info waiting for other related packets - var bundleId [20]byte // AppEUI(8) | DevEUI(8) + var bundleID [20]byte // AppEUI(8) | DevEUI(8) rw := readwriter.New(nil) rw.Write(appEUI) rw.Write(devEUI) @@ -104,13 +104,13 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if err != nil { return errors.New(errors.Structural, err) } - copy(bundleId[:], data[:]) + copy(bundleID[:], data[:]) // 4. Send the actual bundle to the consumer - ctx := h.ctx.WithField("BundleID", bundleId) + ctx := h.ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") h.set <- bundle{ - Id: bundleId, + ID: bundleID, Packet: packet, Entry: entry, Adapter: up, @@ -143,11 +143,11 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } func computeScore(dutyCycle uint, rssi int) uint { - if dutyCycle > max_duty_cycle { + if dutyCycle > maxDutyCycle { return 0 } - if dutyCycle > 2*max_duty_cycle/3 { + if dutyCycle > 2*maxDutyCycle/3 { return uint(1000 + rssi) } @@ -162,7 +162,7 @@ func (h component) consumeBundles(chbundle <-chan []bundle) { browseBundles: for bundles := range chbundle { - ctx.WithField("BundleID", bundles[0].Id).Debug("Consume new bundle") + ctx.WithField("BundleID", bundles[0].ID).Debug("Consume new bundle") var metadata []Metadata var payload []byte var bestBundle int @@ -313,12 +313,12 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { go func(bundles []bundle) { chbundles <- bundles }(bundles) ctx.WithField("BundleID", id).Debug("Consuming collected bundles") case b := <-chset: - ctx = ctx.WithField("BundleID", b.Id) + ctx = ctx.WithField("BundleID", b.ID) // Check if bundle has already been processed var pid [16]byte - copy(pid[:], b.Id[:16]) - if reflect.DeepEqual(processed[pid], b.Id[16:]) { + copy(pid[:], b.ID[:16]) + if reflect.DeepEqual(processed[pid], b.ID[16:]) { ctx.Debug("Reject already processed bundle") go func(b bundle) { b.Chresp <- errors.New(errors.Behavioural, "Already processed") @@ -327,12 +327,12 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { } // Add the bundle to the stack, and set the alarm if its the first - bundles := append(buffers[b.Id], b) + bundles := append(buffers[b.ID], b) if len(bundles) == 1 { - go setAlarm(alarm, b.Id, buffer_delay) + go setAlarm(alarm, b.ID, bufferDelay) ctx.Debug("Buffering started -> new alarm set") } - buffers[b.Id] = bundles + buffers[b.ID] = bundles } } } diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index e2241fac0..a42b5c666 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -486,7 +486,7 @@ func TestHandleUp(t *testing.T) { }() go func() { defer done.Done() - <-time.After(2 * buffer_delay) + <-time.After(2 * bufferDelay) err := handler.HandleUp(dataIn, an2, adapter2) // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 6fbec4364..a3717b55a 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -12,6 +12,7 @@ import ( "github.com/brocaar/lorawan" ) +// PktStorage gives a facade to manipulate the handler packets database type PktStorage interface { Push(p APacket) error Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 1e869aeab..2b1970f0d 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -14,7 +14,7 @@ import ( ) // ----- BUILD utilities -func newRPacket(rawDevAddr [4]byte, payload string, gatewayId []byte) RPacket { +func newRPacket(rawDevAddr [4]byte, payload string, gatewayID []byte) RPacket { var devAddr lorawan.DevAddr copy(devAddr[:], rawDevAddr[:]) @@ -29,7 +29,7 @@ func newRPacket(rawDevAddr [4]byte, payload string, gatewayId []byte) RPacket { Major: lorawan.LoRaWANR1, } - packet, err := NewRPacket(phyPayload, gatewayId, Metadata{}) + packet, err := NewRPacket(phyPayload, gatewayID, Metadata{}) if err != nil { panic(err) } diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 4cda35759..b22739ea5 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -14,6 +14,7 @@ import ( "github.com/brocaar/lorawan" ) +// Storage gives a facade to manipulate the router database type Storage interface { Lookup(devEUI lorawan.EUI64) (entry, error) Store(reg Registration) error From e571070aa9d49d07281f03b75dde7a1d1a81cb4a Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 19:05:13 +0100 Subject: [PATCH 0853/2266] [golint] Apply linter to http adapter --- core/adapters/http/handlers/collect.go | 4 +-- core/adapters/http/handlers/collect_test.go | 2 +- core/adapters/http/handlers/healthz.go | 6 ++-- core/adapters/http/handlers/healthz_test.go | 2 +- core/adapters/http/handlers/helpers_test.go | 4 +-- core/adapters/http/handlers/pubsub.go | 14 +++++----- .../http/handlers/pubsubRegistration.go | 2 +- core/adapters/http/handlers/pubsub_test.go | 6 ++-- core/adapters/http/handlers/statuspage.go | 4 +-- .../adapters/http/handlers/statuspage_test.go | 2 +- core/adapters/http/http.go | 28 +++++++++---------- core/adapters/http/httpRecipient.go | 27 +++++++++--------- core/adapters/http/http_test.go | 22 +++++++-------- 13 files changed, 62 insertions(+), 61 deletions(-) diff --git a/core/adapters/http/handlers/collect.go b/core/adapters/http/handlers/collect.go index 30bcd30d2..923b90c11 100644 --- a/core/adapters/http/handlers/collect.go +++ b/core/adapters/http/handlers/collect.go @@ -22,8 +22,8 @@ import ( // This handler does not generate any registration. type Collect struct{} -// Url implements the http.Handler interface -func (p Collect) Url() string { +// URL implements the http.Handler interface +func (p Collect) URL() string { return "/packets/" } diff --git a/core/adapters/http/handlers/collect_test.go b/core/adapters/http/handlers/collect_test.go index fead8eff8..9ea02c362 100644 --- a/core/adapters/http/handlers/collect_test.go +++ b/core/adapters/http/handlers/collect_test.go @@ -118,6 +118,6 @@ func TestCollect(t *testing.T) { checkStatusCode(t, test.WantStatusCode, statusCode) checkContent(t, test.WantContent, content) checkPacket(t, test.WantPacket, packet) - port += 1 + port++ } } diff --git a/core/adapters/http/handlers/healthz.go b/core/adapters/http/handlers/healthz.go index 132481416..fe26e7978 100644 --- a/core/adapters/http/handlers/healthz.go +++ b/core/adapters/http/handlers/healthz.go @@ -9,7 +9,7 @@ import ( . "github.com/TheThingsNetwork/ttn/core/adapters/http" ) -// Collect defines a handler to ping adapters via a GET request. +// Healthz defines a handler to ping adapters via a GET request. // // It listens to requests of the form: [GET] /healthz/ // @@ -18,8 +18,8 @@ import ( // This handler does not generate any registration. type Healthz struct{} -// Url implements the http.Handler interface -func (p Healthz) Url() string { +// URL implements the http.Handler interface +func (p Healthz) URL() string { return "/healthz" } diff --git a/core/adapters/http/handlers/healthz_test.go b/core/adapters/http/handlers/healthz_test.go index 0348bfb36..06b2b4db5 100644 --- a/core/adapters/http/handlers/healthz_test.go +++ b/core/adapters/http/handlers/healthz_test.go @@ -17,7 +17,7 @@ func TestHealthzURL(t *testing.T) { h := Healthz{} - a.So(h.Url(), assertions.ShouldEqual, "/healthz") + a.So(h.URL(), assertions.ShouldEqual, "/healthz") } func TestHealthzHandle(t *testing.T) { diff --git a/core/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go index 71181e754..e2549e127 100644 --- a/core/adapters/http/handlers/helpers_test.go +++ b/core/adapters/http/handlers/helpers_test.go @@ -53,7 +53,7 @@ func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { <-time.After(time.Millisecond * 250) // Let the connection starts handler := PubSub{} adapter.Bind(handler) - return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.Url()) + return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) } func createCollectAdapter(t *testing.T, port uint) (*Adapter, string) { @@ -64,7 +64,7 @@ func createCollectAdapter(t *testing.T, port uint) (*Adapter, string) { <-time.After(time.Millisecond * 250) // Let the connection starts handler := Collect{} adapter.Bind(handler) - return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.Url()) + return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) } type testClient struct { diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index db8ae962c..68593ed18 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -17,7 +17,7 @@ import ( "github.com/brocaar/lorawan" ) -// Pubsub defines a handler to handle application | devEUI registration on a component. +// PubSub defines a handler to handle application | devEUI registration on a component. // // It listens to request of the form: [PUT] /end-devices/:devEUI // where devEUI is a 8 bytes hex-encoded address. @@ -42,8 +42,8 @@ import ( // - Recipient can be interpreted as an HttpRecipient (Url + Method) type PubSub struct{} -// Url implements the http.Handler interface -func (p PubSub) Url() string { +// URL implements the http.Handler interface +func (p PubSub) URL() string { return "/end-devices/" } @@ -102,7 +102,7 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { } params := &struct { AppEUI string `json:"app_eui"` - Url string `json:"app_url"` + URL string `json:"app_url"` NwkSKey string `json:"nwks_key"` }{} if err := json.Unmarshal(body[:n], params); err != nil { @@ -120,14 +120,14 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") } - params.Url = strings.Trim(params.Url, " ") - if len(params.Url) <= 0 { + params.URL = strings.Trim(params.URL, " ") + if len(params.URL) <= 0 { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application url") } // Create actual registration registration := pubSubRegistration{ - recipient: NewHttpRecipient(params.Url, "PUT"), + recipient: NewRecipient(params.URL, "PUT"), appEUI: lorawan.EUI64{}, devEUI: lorawan.EUI64{}, nwkSKey: lorawan.AES128Key{}, diff --git a/core/adapters/http/handlers/pubsubRegistration.go b/core/adapters/http/handlers/pubsubRegistration.go index d7c02b6b4..41f39e380 100644 --- a/core/adapters/http/handlers/pubsubRegistration.go +++ b/core/adapters/http/handlers/pubsubRegistration.go @@ -11,7 +11,7 @@ import ( // type pubSubRegistration implements the core.Registration interface type pubSubRegistration struct { - recipient HttpRecipient + recipient Recipient appEUI lorawan.EUI64 nwkSKey lorawan.AES128Key devEUI lorawan.EUI64 diff --git a/core/adapters/http/handlers/pubsub_test.go b/core/adapters/http/handlers/pubsub_test.go index 2c69dfc5d..9c6bee06a 100644 --- a/core/adapters/http/handlers/pubsub_test.go +++ b/core/adapters/http/handlers/pubsub_test.go @@ -95,7 +95,7 @@ func TestPubSub(t *testing.T) { WantContent: string(errors.Structural), WantStatusCode: http.StatusConflict, WantRegistration: pubSubRegistration{ - recipient: NewHttpRecipient("url", "PUT"), + recipient: NewRecipient("url", "PUT"), appEUI: lorawan.EUI64([8]byte{0, 1, 2, 3, 4, 5, 6, 7}), nwkSKey: lorawan.AES128Key([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}), devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), @@ -113,7 +113,7 @@ func TestPubSub(t *testing.T) { WantContent: "", WantStatusCode: http.StatusAccepted, WantRegistration: pubSubRegistration{ - recipient: NewHttpRecipient("url", "PUT"), + recipient: NewRecipient("url", "PUT"), appEUI: lorawan.EUI64([8]byte{0, 1, 2, 3, 4, 5, 6, 7}), nwkSKey: lorawan.AES128Key([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}), devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), @@ -129,7 +129,7 @@ func TestPubSub(t *testing.T) { // Build adapter, url := createPubSubAdapter(t, port) - port += 1 + port++ client := testClient{} // Operate diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go index fc8d351ea..3f7e356b1 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -23,8 +23,8 @@ import ( // This handler does not generate any registration. type StatusPage struct{} -// Url implements the http.Handler interface -func (p StatusPage) Url() string { +// URL implements the http.Handler interface +func (p StatusPage) URL() string { return "/status/" } diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go index 135b0e038..e3f5c3bf2 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/handlers/statuspage_test.go @@ -18,7 +18,7 @@ func TestStatusPageURL(t *testing.T) { s := StatusPage{} - a.So(s.Url(), assertions.ShouldEqual, "/status/") + a.So(s.URL(), assertions.ShouldEqual, "/status/") } func TestStatusPageHandler(t *testing.T) { diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 7c3db035a..4cd813ecd 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -30,23 +30,23 @@ type Adapter struct { // Handler defines endpoint-specific handler. type Handler interface { - Url() string + URL() string Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) } -// Message sent through the response channel of a pktReq or regReq +// MsgRes are sent through the response channel of a pktReq or regReq type MsgRes struct { StatusCode int // The http status code to set as an answer Content []byte // The response content. } -// Message sent through the packets channel when an incoming request arrives +// PktReq are sent through the packets channel when an incoming request arrives type PktReq struct { Packet []byte // The actual packet that has been parsed Chresp chan MsgRes // A response channel waiting for an success or reject confirmation } -// Message sent through the registration channel when an incoming registration arrives +// RegReq are sent through the registration channel when an incoming registration arrives type RegReq struct { Registration core.Registration Chresp chan MsgRes @@ -109,18 +109,18 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err defer wg.Done() // Get the actual recipient - recipient, ok := rawRecipient.(HttpRecipient) + recipient, ok := rawRecipient.(Recipient) if !ok { - ctx.WithField("recipient", rawRecipient).Warn("Unable to interpret recipient as httpRecipient") + ctx.WithField("recipient", rawRecipient).Warn("Unable to interpret recipient as Recipient") return } - ctx := ctx.WithField("recipient", recipient.Url()) + ctx := ctx.WithField("recipient", recipient.URL()) // Send request ctx.Debugf("%s Request", recipient.Method()) buf := new(bytes.Buffer) buf.Write(data) - resp, err := a.Post(fmt.Sprintf("http://%s", recipient.Url()), "application/octet-stream", buf) + resp, err := a.Post(fmt.Sprintf("http://%s", recipient.URL()), "application/octet-stream", buf) if err != nil { cherr <- errors.New(errors.Operational, err) return @@ -172,10 +172,10 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Collect errors and see if everything went well var errored uint8 - for i := 0; i < len(cherr); i += 1 { + for i := 0; i < len(cherr); i++ { err := <-cherr if err.(errors.Failure).Nature != errors.Behavioural { - errored += 1 + errored++ ctx.WithError(err).Error("POST Failed") } } @@ -198,7 +198,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // GetRecipient implements the core.Adapter interface func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { - recipient := new(httpRecipient) + recipient := new(recipient) if err := recipient.UnmarshalBinary(raw); err != nil { return nil, errors.New(errors.Structural, err) } @@ -221,9 +221,9 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) // Bind registers a handler to a specific endpoint func (a *Adapter) Bind(h Handler) { - a.ctx.WithField("url", h.Url()).Info("Register new endpoint") - a.serveMux.HandleFunc(h.Url(), func(w http.ResponseWriter, req *http.Request) { - a.ctx.WithField("url", h.Url()).Debug("Handle new request") + a.ctx.WithField("url", h.URL()).Info("Register new endpoint") + a.serveMux.HandleFunc(h.URL(), func(w http.ResponseWriter, req *http.Request) { + a.ctx.WithField("url", h.URL()).Debug("Handle new request") h.Handle(w, a.packets, a.registrations, req) }) } diff --git a/core/adapters/http/httpRecipient.go b/core/adapters/http/httpRecipient.go index bbbf2dcb3..31d8265d0 100644 --- a/core/adapters/http/httpRecipient.go +++ b/core/adapters/http/httpRecipient.go @@ -9,35 +9,36 @@ import ( "github.com/TheThingsNetwork/ttn/utils/readwriter" ) -type HttpRecipient interface { +// Recipient represents the recipient used by the http adapter +type Recipient interface { encoding.BinaryMarshaler - Url() string + URL() string Method() string } -// NewHttpRecipient constructs a new HttpRecipient -func NewHttpRecipient(url string, method string) HttpRecipient { - return httpRecipient{url: url, method: method} +// NewRecipient constructs a new HttpRecipient +func NewRecipient(url string, method string) Recipient { + return recipient{url: url, method: method} } -// HttpRecipient materializes recipients manipulated by the http adapter -type httpRecipient struct { +// Recipient materializes recipients manipulated by the http adapter +type recipient struct { url string method string } -// Url implements the HttpRecipient interface -func (h httpRecipient) Url() string { +// Url implements the Recipient interface +func (h recipient) URL() string { return h.url } -// Method implements the HttpRecipient interface -func (h httpRecipient) Method() string { +// Method implements the Recipient interface +func (h recipient) Method() string { return h.method } // MarshalBinary implements the encoding.BinaryMarshaler interface -func (h httpRecipient) MarshalBinary() ([]byte, error) { +func (h recipient) MarshalBinary() ([]byte, error) { w := readwriter.New(nil) w.Write(h.url) w.Write(h.method) @@ -45,7 +46,7 @@ func (h httpRecipient) MarshalBinary() ([]byte, error) { } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (h *httpRecipient) UnmarshalBinary(data []byte) error { +func (h *recipient) UnmarshalBinary(data []byte) error { r := readwriter.New(data) r.Read(func(data []byte) { h.url = string(data) }) r.Read(func(data []byte) { h.method = string(data) }) diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index 81b78e2ae..deddce66c 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -19,7 +19,7 @@ import ( ) type testRecipient struct { - httpRecipient + Recipient Behavior string } @@ -55,28 +55,28 @@ func (p testPacket) String() string { func TestSend(t *testing.T) { recipients := []testRecipient{ testRecipient{ - httpRecipient: httpRecipient{ + Recipient: recipient{ url: "0.0.0.0:3110", method: "POST", }, Behavior: "AlwaysReject", }, testRecipient{ - httpRecipient: httpRecipient{ + Recipient: recipient{ url: "0.0.0.0:3111", method: "POST", }, Behavior: "AlwaysAccept", }, testRecipient{ - httpRecipient: httpRecipient{ + Recipient: recipient{ url: "0.0.0.0:3112", method: "POST", }, Behavior: "AlwaysReject", }, testRecipient{ - httpRecipient: httpRecipient{ + Recipient: recipient{ url: "0.0.0.0:3113", method: "POST", }, @@ -136,7 +136,7 @@ func TestSend(t *testing.T) { ctx := GetLogger(t, "Adapter") // Build - adapter, err := NewAdapter(3115, toHttpRecipient(recipients), ctx) + adapter, err := NewAdapter(3115, toHTTPRecipient(recipients), ctx) if err != nil { panic(err) } @@ -151,7 +151,7 @@ func TestSend(t *testing.T) { Desc(t, "Sending packet %v to %v", test.Packet, test.Recipients) // Operate - _, err := adapter.Send(test.Packet, toHttpRecipient(test.Recipients)...) + _, err := adapter.Send(test.Packet, toHTTPRecipient(test.Recipients)...) registrations := getRegistrations(adapter, test.WantRegistrations) payloads := getPayloads(servers) @@ -164,10 +164,10 @@ func TestSend(t *testing.T) { } // Convert testRecipient to core.Recipient -func toHttpRecipient(recipients []testRecipient) []core.Recipient { +func toHTTPRecipient(recipients []testRecipient) []core.Recipient { var https []core.Recipient for _, r := range recipients { - https = append(https, r.httpRecipient) + https = append(https, r.Recipient) } return https } @@ -238,7 +238,7 @@ func genMockServer(recipient core.Recipient) chan string { }) server := http.Server{ - Addr: recipient.(HttpRecipient).Url(), + Addr: recipient.(Recipient).URL(), Handler: serveMux, } go server.ListenAndServe() @@ -258,7 +258,7 @@ outer: if rg.DevEUI() != rw.DevEUI { Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, rg.DevEUI()) } - if reflect.DeepEqual(rw.Recipient.httpRecipient, rg.Recipient()) { + if reflect.DeepEqual(rw.Recipient.Recipient, rg.Recipient()) { continue outer } } From 47e0e091b21e78afa8e6b76543a6ae4e1294aa19 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 19:10:42 +0100 Subject: [PATCH 0854/2266] [golint] Apply linter to udp adapter --- .../udp/handlers/build_utilities_test.go | 16 ++++++------- core/adapters/udp/handlers/semtech.go | 15 ++++++------ core/adapters/udp/handlers/semtech_test.go | 20 ++++++++-------- core/adapters/udp/udp.go | 23 ++++++++++--------- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/core/adapters/udp/handlers/build_utilities_test.go b/core/adapters/udp/handlers/build_utilities_test.go index 59a206fc6..f82b8d150 100644 --- a/core/adapters/udp/handlers/build_utilities_test.go +++ b/core/adapters/udp/handlers/build_utilities_test.go @@ -108,7 +108,7 @@ func genCorePacket(p semtech.Packet) core.Packet { return packet } -func genPUSH_DATANoRXPK(token []byte) semtech.Packet { +func genPUSHDATANoRXPK(token []byte) semtech.Packet { return semtech.Packet{ Version: semtech.VERSION, GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, @@ -117,14 +117,14 @@ func genPUSH_DATANoRXPK(token []byte) semtech.Packet { } } -func genPUSH_DATANoPayload(token []byte) semtech.Packet { - packet := genPUSH_DATAWithRXPK(token) +func genPUSHDATANoPayload(token []byte) semtech.Packet { + packet := genPUSHDATAWithRXPK(token) packet.Payload.RXPK[0].Data = nil return packet } -func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { - packet := genPUSH_DATANoRXPK(token) +func genPUSHDATAWithRXPK(token []byte) semtech.Packet { + packet := genPUSHDATANoRXPK(token) packet.Payload = &semtech.Payload{ RXPK: []semtech.RXPK{ semtech.RXPK{ @@ -137,7 +137,7 @@ func genPUSH_DATAWithRXPK(token []byte) semtech.Packet { return packet } -func genPULL_ACK(token []byte) semtech.Packet { +func genPULLACK(token []byte) semtech.Packet { return semtech.Packet{ Version: semtech.VERSION, Token: token, @@ -145,7 +145,7 @@ func genPULL_ACK(token []byte) semtech.Packet { } } -func genPUSH_ACK(token []byte) semtech.Packet { +func genPUSHACK(token []byte) semtech.Packet { return semtech.Packet{ Version: semtech.VERSION, Token: token, @@ -153,7 +153,7 @@ func genPUSH_ACK(token []byte) semtech.Packet { } } -func genPULL_DATA(token []byte) semtech.Packet { +func genPULLDATA(token []byte) semtech.Packet { return semtech.Packet{ Version: semtech.VERSION, Token: token, diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index 432b248b9..bff33f507 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -18,10 +18,11 @@ import ( "github.com/brocaar/lorawan" ) +// Semtech implements the Semtech protocol and make a bridge between gateways and routers type Semtech struct{} -// HandleDatagram implements the udp.Handler interface -func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg udp.MsgUdp) { +// Handle implements the udp.Handler interface +func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg udp.MsgUDP) { pkt := new(semtech.Packet) err := pkt.UnmarshalBinary(msg.Data) if err != nil { @@ -41,7 +42,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u // TODO Log error return } - conn <- udp.MsgUdp{ + conn <- udp.MsgUDP{ Addr: msg.Addr, Data: data, } @@ -56,7 +57,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u // TODO Log error return } - conn <- udp.MsgUdp{ + conn <- udp.MsgUDP{ Addr: msg.Addr, Data: data, } @@ -104,7 +105,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUdp, packets chan<- udp.MsgReq, msg u // TODO Log error return } - conn <- udp.MsgUdp{Addr: msg.Addr, Data: data} + conn <- udp.MsgUDP{Addr: msg.Addr, Data: data} case <-time.After(time.Second * 2): } }(rxpk) @@ -144,7 +145,7 @@ func rxpk2packet(p semtech.RXPK, gid []byte) (core.Packet, error) { rxpkValue := reflect.ValueOf(p) rxpkStruct := rxpkValue.Type() metas := reflect.ValueOf(&metadata).Elem() - for i := 0; i < rxpkStruct.NumField(); i += 1 { + for i := 0; i < rxpkStruct.NumField(); i++ { field := rxpkStruct.Field(i).Name if metas.FieldByName(field).CanSet() { metas.FieldByName(field).Set(rxpkValue.Field(i)) @@ -171,7 +172,7 @@ func packet2txpk(p core.RPacket) (semtech.TXPK, error) { metadataValue := reflect.ValueOf(p.Metadata()) metadataStruct := metadataValue.Type() txpkStruct := reflect.ValueOf(&txpk).Elem() - for i := 0; i < metadataStruct.NumField(); i += 1 { + for i := 0; i < metadataStruct.NumField(); i++ { field := metadataStruct.Field(i).Name if txpkStruct.FieldByName(field).CanSet() { txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) diff --git a/core/adapters/udp/handlers/semtech_test.go b/core/adapters/udp/handlers/semtech_test.go index 5b1bfe994..107677b9e 100644 --- a/core/adapters/udp/handlers/semtech_test.go +++ b/core/adapters/udp/handlers/semtech_test.go @@ -44,36 +44,36 @@ func TestNext(t *testing.T) { }{ { // Valid uplink PUSH_DATA Adapter: adapter, - Packet: genPUSH_DATAWithRXPK([]byte{0x14, 0x42}), - WantAck: genPUSH_ACK([]byte{0x14, 0x42}), - WantNext: genCorePacket(genPUSH_DATAWithRXPK([]byte{0x14, 0x42})), + Packet: genPUSHDATAWithRXPK([]byte{0x14, 0x42}), + WantAck: genPUSHACK([]byte{0x14, 0x42}), + WantNext: genCorePacket(genPUSHDATAWithRXPK([]byte{0x14, 0x42})), WantError: nil, }, { // Invalid uplink packet Adapter: adapter, - Packet: genPUSH_ACK([]byte{0x22, 0x35}), + Packet: genPUSHACK([]byte{0x22, 0x35}), WantAck: semtech.Packet{}, WantNext: nil, WantError: nil, }, { // Uplink PUSH_DATA with no RXPK Adapter: adapter, - Packet: genPUSH_DATANoRXPK([]byte{0x22, 0x35}), - WantAck: genPUSH_ACK([]byte{0x22, 0x35}), + Packet: genPUSHDATANoRXPK([]byte{0x22, 0x35}), + WantAck: genPUSHACK([]byte{0x22, 0x35}), WantNext: nil, WantError: nil, }, { // Uplink PULL_DATA Adapter: adapter, - Packet: genPULL_DATA([]byte{0x62, 0xfa}), - WantAck: genPULL_ACK([]byte{0x62, 0xfa}), + Packet: genPULLDATA([]byte{0x62, 0xfa}), + WantAck: genPULLACK([]byte{0x62, 0xfa}), WantNext: nil, WantError: nil, }, { // Uplink PUSH_DATA with no encoded payload Adapter: adapter, - Packet: genPUSH_DATANoPayload([]byte{0x22, 0x35}), - WantAck: genPUSH_ACK([]byte{0x22, 0x35}), + Packet: genPUSHDATANoPayload([]byte{0x22, 0x35}), + WantAck: genPUSHACK([]byte{0x22, 0x35}), WantNext: nil, WantError: nil, }, diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index ba1ff5dfe..99f5d824b 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -15,7 +15,7 @@ import ( // Adapter represents a udp adapter which sends and receives packet via UDP type Adapter struct { ctx log.Interface // Just a logger - conn chan MsgUdp // Channel used to manage response transmissions made by multiple goroutines + conn chan MsgUDP // Channel used to manage response transmissions made by multiple goroutines packets chan MsgReq // Incoming valid packets are pushed to this channel and consume by an outsider handlers chan interface{} // Manage handlers, could be either a Handler or a []byte (new handler or handling action) } @@ -23,29 +23,29 @@ type Adapter struct { // Handler represents a datagram and packet handler used by the adapter to process packets type Handler interface { // Handle handles incoming datagram from a gateway transmitter to the network - Handle(conn chan<- MsgUdp, chresp chan<- MsgReq, msg MsgUdp) + Handle(conn chan<- MsgUDP, chresp chan<- MsgReq, msg MsgUDP) } -// MsgUdp type materializes response messages transmitted towards existing recipients (commonly, gateways). -type MsgUdp struct { +// MsgUDP type materializes response messages transmitted towards existing recipients (commonly, gateways). +type MsgUDP struct { Data []byte // The raw byte sequence that has to be sent Addr *net.UDPAddr // The target recipient address } -// OutMsg type materializes valid uplink messages coming from a given recipient +// MsgReq type materializes valid uplink messages coming from a given recipient type MsgReq struct { Data []byte // The actual message Chresp chan MsgRes // A dedicated response channel } -// Message sent through the response channel of MsgReq +// MsgRes qre sent through the response channel of MsgReq type MsgRes []byte // The actual message // NewAdapter constructs and allocates a new udp adapter func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { a := Adapter{ ctx: ctx, - conn: make(chan MsgUdp), + conn: make(chan MsgUDP), packets: make(chan MsgReq), handlers: make(chan interface{}), } @@ -87,6 +87,7 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) return udpRegistration{}, nil, errors.New(errors.Implementation, "NextRegistration not supported on udp adapter") } +// Bind is used to register a new handler to the adapter func (a *Adapter) Bind(h Handler) { a.handlers <- h } @@ -106,7 +107,7 @@ func (a *Adapter) listen(conn *net.UDPConn) { } a.ctx.Debug("Incoming datagram") - a.handlers <- MsgUdp{Addr: addr, Data: buf[:n]} + a.handlers <- MsgUDP{Addr: addr, Data: buf[:n]} } } @@ -141,11 +142,11 @@ func (a *Adapter) monitorHandlers() { switch msg.(type) { case Handler: handlers = append(handlers, msg.(Handler)) - case MsgUdp: + case MsgUDP: for _, h := range handlers { - go func(h Handler, msg MsgUdp) { + go func(h Handler, msg MsgUDP) { h.Handle(a.conn, a.packets, msg) - }(h, msg.(MsgUdp)) + }(h, msg.(MsgUDP)) } } } From 3d7196d1d14d7ae4ae880b0864f5103d8a88a74e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 2 Mar 2016 19:17:43 +0100 Subject: [PATCH 0855/2266] [golint] Apply linter to adapters --- core/adapters/mqtt/handlers/activation.go | 1 + core/adapters/mqtt/handlers/helpers_test.go | 4 ++-- core/adapters/mqtt/mqtt.go | 21 +++++++++--------- core/adapters/mqtt/mqttClient.go | 1 + core/adapters/mqtt/mqttRecipient.go | 24 ++++++++++----------- core/adapters/mqtt/mqtt_test.go | 14 ++++++------ 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 987dbe0ab..9de5bce52 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -15,6 +15,7 @@ import ( "github.com/brocaar/lorawan" ) +// Activation handles communication between a handler and an application via MQTT type Activation struct{} // Topic implements the mqtt.Handler interface diff --git a/core/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go index 47149485e..7c0c9e808 100644 --- a/core/adapters/mqtt/handlers/helpers_test.go +++ b/core/adapters/mqtt/handlers/helpers_test.go @@ -198,11 +198,11 @@ func checkRegistrations(t *testing.T, want core.HRegistration, got core.Registra } // Check recipient topicUp - rWant, ok := want.Recipient().(MqttRecipient) + rWant, ok := want.Recipient().(Recipient) if !ok { panic("Expected test to be made with MQTTRecipient") } - rGot, ok := got.Recipient().(MqttRecipient) + rGot, ok := got.Recipient().(Recipient) if !ok { Ko(t, "Recipient isn't MqttRecipient: %v", got.Recipient()) return diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index fa05eeb80..e6048be2e 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -28,27 +28,28 @@ type Handler interface { Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error } -// Message sent through the response channel of a pktReq or regReq +// MsgRes are sent through the response channel of a pktReq or regReq type MsgRes []byte // The response content. -// Message sent through the packets channel when an incoming request arrives +// PktReq are sent through the packets channel when an incoming request arrives type PktReq struct { Packet []byte // The actual packet that has been parsed Chresp chan MsgRes // A response channel waiting for an success or reject confirmation } -// Message sent through the registration channel when an incoming registration arrives +// RegReq are sent through the registration channel when an incoming registration arrives type RegReq struct { Registration core.Registration Chresp chan MsgRes } -// MQTT Schemes available +// Scheme defines all MQTT communication schemes available type Scheme string +// The following constants are used as scheme identifers const ( - Tcp Scheme = "tcp" - Tls Scheme = "tls" + TCP Scheme = "tcp" + TLS Scheme = "tls" WebSocket Scheme = "ws" ) @@ -89,7 +90,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err for _, r := range recipients { // Get the actual recipient - recipient, ok := r.(MqttRecipient) + recipient, ok := r.(Recipient) if !ok { err := errors.New(errors.Structural, "Unable to interpret recipient as mqttRecipient") a.ctx.WithField("recipient", r).Warn(err.Error()) @@ -111,7 +112,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } // Publish on each topic - go func(recipient MqttRecipient) { + go func(recipient Recipient) { defer wg.Done() ctx := a.ctx.WithField("topic", recipient.TopicUp()) @@ -126,7 +127,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err }(recipient) // Pull responses from each down topic, expecting only one - go func(recipient MqttRecipient, chdown <-chan []byte) { + go func(recipient Recipient, chdown <-chan []byte) { defer wg.Done() ctx := a.ctx.WithField("topic", recipient.TopicDown()) @@ -176,7 +177,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // GetRecipient implements the core.Adapter interface func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { - recipient := new(mqttRecipient) + recipient := new(recipient) if err := recipient.UnmarshalBinary(raw); err != nil { return nil, errors.New(errors.Structural, err) } diff --git a/core/adapters/mqtt/mqttClient.go b/core/adapters/mqtt/mqttClient.go index c40a61d1d..7dc3c3ee1 100644 --- a/core/adapters/mqtt/mqttClient.go +++ b/core/adapters/mqtt/mqttClient.go @@ -10,6 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" ) +// Client abstracts the interface of the paho client to allow an easier testing type Client interface { Connect() MQTT.Token Disconnect(quiesce uint) diff --git a/core/adapters/mqtt/mqttRecipient.go b/core/adapters/mqtt/mqttRecipient.go index 454e048fc..ae6bfde33 100644 --- a/core/adapters/mqtt/mqttRecipient.go +++ b/core/adapters/mqtt/mqttRecipient.go @@ -10,37 +10,37 @@ import ( "github.com/TheThingsNetwork/ttn/utils/readwriter" ) -// MqttRecipient describes recipient manipulated by the mqtt adapter -type MqttRecipient interface { +// Recipient describes recipient manipulated by the mqtt adapter +type Recipient interface { encoding.BinaryMarshaler encoding.BinaryUnmarshaler TopicUp() string TopicDown() string } -// mqttRecipient implements the MqttRecipient interface -type mqttRecipient struct { +// recipient implements the MqttRecipient interface +type recipient struct { up string down string } // NewRecipient creates a new MQTT recipient from two topics -func NewRecipient(up string, down string) MqttRecipient { - return &mqttRecipient{up: up, down: down} +func NewRecipient(up string, down string) Recipient { + return &recipient{up: up, down: down} } -// TopicUp implements the MqttRecipient interface -func (r mqttRecipient) TopicUp() string { +// TopicUp implements the Recipient interface +func (r recipient) TopicUp() string { return r.up } -// TopicDown implements the MqttRecipient interface -func (r mqttRecipient) TopicDown() string { +// TopicDown implements the Recipient interface +func (r recipient) TopicDown() string { return r.down } // MarshalBinary implements the encoding.BinaryMarshaler interface -func (r mqttRecipient) MarshalBinary() ([]byte, error) { +func (r recipient) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write(r.up) rw.Write(r.down) @@ -49,7 +49,7 @@ func (r mqttRecipient) MarshalBinary() ([]byte, error) { } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (r *mqttRecipient) UnmarshalBinary(data []byte) error { +func (r *recipient) UnmarshalBinary(data []byte) error { if r == nil { return errors.New(errors.Structural, "Cannot unmarshal nil structure") } diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index c00a27054..397f4b65c 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -18,7 +18,7 @@ import ( "github.com/brocaar/lorawan" ) -const brokerUrl = "0.0.0.0:1683" +const brokerURL = "0.0.0.0:1683" func TestMQTTSend(t *testing.T) { tests := []struct { @@ -155,7 +155,7 @@ func TestMQTTRecipient(t *testing.T) { { Desc(t, "Marshal / Unmarshal valid recipient") rm := NewRecipient("topicup", "topicdown") - ru := new(mqttRecipient) + ru := new(recipient) data, err := rm.MarshalBinary() if err == nil { err = ru.UnmarshalBinary(data) @@ -166,7 +166,7 @@ func TestMQTTRecipient(t *testing.T) { { Desc(t, "Unmarshal from nil pointer") rm := NewRecipient("topicup", "topicdown") - var ru *mqttRecipient + var ru *recipient data, err := rm.MarshalBinary() if err == nil { err = ru.UnmarshalBinary(data) @@ -176,14 +176,14 @@ func TestMQTTRecipient(t *testing.T) { { Desc(t, "Unmarshal nil data") - ru := new(mqttRecipient) + ru := new(recipient) err := ru.UnmarshalBinary(nil) CheckErrors(t, pointer.String(string(errors.Structural)), err) } { Desc(t, "Unmarshal wrong data") - ru := new(mqttRecipient) + ru := new(recipient) err := ru.UnmarshalBinary([]byte{1, 2, 3, 4}) CheckErrors(t, pointer.String(string(errors.Structural)), err) } @@ -221,7 +221,7 @@ func (p testPacket) DevEUI() lorawan.EUI64 { // ----- BUILD utilities func createAdapter(t *testing.T) (Client, core.Adapter) { - client, err := NewClient("testClient", brokerUrl, Tcp) + client, err := NewClient("testClient", brokerURL, TCP) if err != nil { panic(err) } @@ -231,7 +231,7 @@ func createAdapter(t *testing.T) (Client, core.Adapter) { } func createServers(recipients []testRecipient) (Client, chan []byte) { - client, err := NewClient("FakeServerClient", brokerUrl, Tcp) + client, err := NewClient("FakeServerClient", brokerURL, TCP) if err != nil { panic(err) } From f8d318349de9ee5b08cff30e793cf53ba3e7d0e5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 11:27:07 +0100 Subject: [PATCH 0856/2266] [issue#34] Define a RRegistration, and make necessary changes in the router --- core/components/router/mock_test.go | 4 ++-- core/components/router/router.go | 10 +++++++++- core/components/router/router_test.go | 26 +++++++++++++++++++++++--- core/components/router/storage.go | 4 ++-- core/mocks/mock.go | 20 ++++++++++++++++++++ core/registrations_interfaces.go | 16 ++++++++++++++-- 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go index fb392ea5e..122f15c23 100644 --- a/core/components/router/mock_test.go +++ b/core/components/router/mock_test.go @@ -14,7 +14,7 @@ type mockStorage struct { Failures map[string]error InLookup lorawan.EUI64 OutLookup entry - InStore Registration + InStore RRegistration } func newMockStorage() *mockStorage { @@ -35,7 +35,7 @@ func (s *mockStorage) Lookup(devEUI lorawan.EUI64) (entry, error) { return s.OutLookup, nil } -func (s *mockStorage) Store(reg Registration) error { +func (s *mockStorage) Store(reg RRegistration) error { s.InStore = reg return s.Failures["Store"] } diff --git a/core/components/router/router.go b/core/components/router/router.go index 7a09655bc..8a8a53216 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -25,7 +25,15 @@ func (r component) Register(reg Registration, an AckNacker) (err error) { defer ensureAckNack(an, nil, &err) stats.MarkMeter("router.registration.in") r.ctx.Debug("Handling registration") - return r.Store(reg) + + rreg, ok := reg.(RRegistration) + if !ok { + err = errors.New(errors.Structural, "Unexpected registration type") + r.ctx.WithError(err).Warn("Unable to register") + return err + } + + return r.Store(rreg) } // HandleUp implements the core.Component interface diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 69f400507..9df903a68 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -22,7 +22,7 @@ func TestRegister(t *testing.T) { // Build an := NewMockAckNacker() store := newMockStorage() - r := NewMockRegistration() + r := NewMockRRegistration() // Operate router := New(store, GetLogger(t, "Router")) @@ -31,7 +31,27 @@ func TestRegister(t *testing.T) { // Check CheckErrors(t, nil, err) CheckAcks(t, true, an.InAck) - CheckRegistrations(t, store.InStore, r) + CheckRegistrations(t, r, store.InStore) + } + + // ------------------- + + { + Desc(t, "Register an entry, wrong registration type") + + // Build + an := NewMockAckNacker() + store := newMockStorage() + r := NewMockARegistration() + + // Operate + router := New(store, GetLogger(t, "Router")) + err := router.Register(r, an) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) } // ------------------- @@ -43,7 +63,7 @@ func TestRegister(t *testing.T) { an := NewMockAckNacker() store := newMockStorage() store.Failures["Store"] = errors.New(errors.Structural, "Mock Error: Store Failed") - r := NewMockRegistration() + r := NewMockRRegistration() // Operate router := New(store, GetLogger(t, "Router")) diff --git a/core/components/router/storage.go b/core/components/router/storage.go index b22739ea5..e8f127621 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -17,7 +17,7 @@ import ( // Storage gives a facade to manipulate the router database type Storage interface { Lookup(devEUI lorawan.EUI64) (entry, error) - Store(reg Registration) error + Store(reg RRegistration) error Close() error } @@ -83,7 +83,7 @@ func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { } // Store implements the router.Storage interface -func (s *storage) Store(reg Registration) error { +func (s *storage) Store(reg RRegistration) error { devEUI := reg.DevEUI() recipient, err := reg.Recipient().MarshalBinary() if err != nil { diff --git a/core/mocks/mock.go b/core/mocks/mock.go index c6465df4c..433f28874 100644 --- a/core/mocks/mock.go +++ b/core/mocks/mock.go @@ -133,6 +133,26 @@ func (r *MockRRegistration) DevEUI() lorawan.EUI64 { return r.OutDevEUI } +// MockARegistration implements the core.Registration interface +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockARegistration struct { + OutRecipient Recipient +} + +func NewMockARegistration() *MockARegistration { + return &MockARegistration{ + OutRecipient: NewMockRecipient(), + } +} + +func (r *MockARegistration) Recipient() Recipient { + return r.OutRecipient +} + // MockAckNacker implements the core.AckNacker interface // // It declares a `Failures` attributes that can be used to diff --git a/core/registrations_interfaces.go b/core/registrations_interfaces.go index e45cf271a..1c61580e7 100644 --- a/core/registrations_interfaces.go +++ b/core/registrations_interfaces.go @@ -14,20 +14,32 @@ type Recipient interface { encoding.BinaryMarshaler } -// Registration represents the first-level of registration, used by router and router adapters +// Registration gives an elementary base for each other registration levels type Registration interface { Recipient() Recipient +} + +// RRegistration represents the first-level of registration, used by router and router adapters +type RRegistration interface { + Registration DevEUI() lorawan.EUI64 } // BRegistration represents the second-level of registrations, used by the broker and broker // adapters type BRegistration interface { - Registration + RRegistration AppEUI() lorawan.EUI64 NwkSKey() lorawan.AES128Key } +// ARegistration represents another second-level of registrations, used betwen handler and broker to +// register application before OTAA +type ARegistration interface { + Registration + AppEUI() lorawan.EUI64 +} + // HRegistration represents the third-level of registrations, used bt the handler and handler // adapters type HRegistration interface { From ce90d7a4ad80c7c34d4af8435147fef35dba54a9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 11:27:57 +0100 Subject: [PATCH 0857/2266] [issue#34] Fix comment in udpRegistration --- core/adapters/udp/udpRegistration.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/adapters/udp/udpRegistration.go b/core/adapters/udp/udpRegistration.go index 6ec8c06cc..e0f97f7d1 100644 --- a/core/adapters/udp/udpRegistration.go +++ b/core/adapters/udp/udpRegistration.go @@ -8,15 +8,15 @@ import ( "github.com/brocaar/lorawan" ) -// udpRegistration is a blank type which implements the core.Registration interface +// udpRegistration is a blank type which implements the core.RRegistration interface type udpRegistration struct{} -// Recipient implements the core.Registration inteface +// Recipient implements the core.RRegistration inteface func (r udpRegistration) Recipient() core.Recipient { return nil } -// DevEUI implements the core.Registration interface +// DevEUI implements the core.RRegistration interface func (r udpRegistration) DevEUI() lorawan.EUI64 { return lorawan.EUI64{} } From f190856055a2fea14cc895d06011be9b8821f71f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 11:34:27 +0100 Subject: [PATCH 0858/2266] Fix timeouts in adapter tests --- core/adapters/http/http_test.go | 3 +-- core/adapters/mqtt/mqtt.go | 4 +++- core/adapters/mqtt/mqtt_test.go | 4 +++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index deddce66c..567685d9c 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -144,7 +144,7 @@ func TestSend(t *testing.T) { for _, r := range recipients { servers = append(servers, genMockServer(r)) } - <-time.After(100 * time.Millisecond) + <-time.After(200 * time.Millisecond) for _, test := range tests { // Describe @@ -156,7 +156,6 @@ func TestSend(t *testing.T) { payloads := getPayloads(servers) // Check - <-time.After(time.Second) CheckErrors(t, test.WantError, err) checkPayloads(t, test.WantPayload, payloads) checkRegistrations(t, test.WantRegistrations, registrations) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index e6048be2e..923877857 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -14,6 +14,8 @@ import ( "github.com/apex/log" ) +var timeout = time.Second + // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { Client @@ -144,7 +146,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err if ok { chresp <- data } - case <-time.After(time.Second): // Timeout + case <-time.After(timeout): // Timeout } }(recipient, chdown) } diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 397f4b65c..e97ef556e 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -21,6 +21,8 @@ import ( const brokerURL = "0.0.0.0:1683" func TestMQTTSend(t *testing.T) { + timeout = 100 * time.Millisecond + tests := []struct { Desc string // Test Description Packet []byte // Handy representation of the packet to send @@ -280,7 +282,7 @@ func trySend(adapter core.Adapter, packet []byte, recipients []testRecipient) ([ select { case resp := <-chresp: return resp.Data, resp.Error - case <-time.After(time.Millisecond * 1250): + case <-time.After(timeout + time.Millisecond*100): return nil, nil } } From 89fdc4618e5d9cd88ed42b2dc82ebeee869dbd5f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 11:37:01 +0100 Subject: [PATCH 0859/2266] Fix cmd and add tests --- .drone.yml | 2 +- .travis.yml | 2 +- cmd/broker_test.go | 12 ++++++++++++ cmd/handler.go | 2 +- cmd/handler_test.go | 12 ++++++++++++ cmd/router.go | 2 +- cmd/router_test.go | 12 ++++++++++++ cmd/version_test.go | 28 ++++++++++++++++++++++++++++ 8 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 cmd/broker_test.go create mode 100644 cmd/handler_test.go create mode 100644 cmd/router_test.go create mode 100644 cmd/version_test.go diff --git a/.drone.yml b/.drone.yml index bdec5e428..55d4c0f97 100644 --- a/.drone.yml +++ b/.drone.yml @@ -8,7 +8,7 @@ build: commands: - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .Imports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .TestImports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - - go list ./... | grep -vE 'cmd|integration|ttn$' | xargs go test + - go list ./... | grep -vE 'integration|ttn$' | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... release: diff --git a/.travis.yml b/.travis.yml index ae3687f38..ce9594d63 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,7 +22,7 @@ before_script: - mosquitto -p 1683 1>/dev/null 2>/dev/null & script: - - go list ./... | grep -vE 'cmd|integration|ttn$' | xargs go test + - go list ./... | grep -vE 'integration|ttn$' | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... diff --git a/cmd/broker_test.go b/cmd/broker_test.go new file mode 100644 index 000000000..13e3df43b --- /dev/null +++ b/cmd/broker_test.go @@ -0,0 +1,12 @@ +package cmd + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestBrokerCmd(t *testing.T) { + a := New(t) + a.So(brokerCmd.IsAvailableCommand(), ShouldBeTrue) +} diff --git a/cmd/handler.go b/cmd/handler.go index e724e6e3c..eaa92d700 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -45,7 +45,7 @@ The default handler is the bridge between The Things Network and applications. brkAdapter.Bind(httpHandlers.StatusPage{}) brkAdapter.Bind(httpHandlers.Healthz{}) - mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.apps-client"), mqtt.Tcp) + mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.apps-client"), mqtt.TCP) if err != nil { ctx.WithError(err).Fatal("Could not start mqtt client") } diff --git a/cmd/handler_test.go b/cmd/handler_test.go new file mode 100644 index 000000000..a7867336e --- /dev/null +++ b/cmd/handler_test.go @@ -0,0 +1,12 @@ +package cmd + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestHandlerCmd(t *testing.T) { + a := New(t) + a.So(handlerCmd.IsAvailableCommand(), ShouldBeTrue) +} diff --git a/cmd/router.go b/cmd/router.go index b42e4d752..9ffcf22fd 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -49,7 +49,7 @@ the gateway's duty cycle is (almost) full.`, brokersStr := strings.Split(viper.GetString("router.brokers"), ",") for i := range brokersStr { url := fmt.Sprintf("%s/packets", strings.Trim(brokersStr[i], " ")) - brokers = append(brokers, http.NewHttpRecipient(url, "POST")) + brokers = append(brokers, http.NewRecipient(url, "POST")) } brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), brokers, ctx.WithField("adapter", "broker-http")) diff --git a/cmd/router_test.go b/cmd/router_test.go new file mode 100644 index 000000000..2553af4de --- /dev/null +++ b/cmd/router_test.go @@ -0,0 +1,12 @@ +package cmd + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestRouterCmd(t *testing.T) { + a := New(t) + a.So(routerCmd.IsAvailableCommand(), ShouldBeTrue) +} diff --git a/cmd/version_test.go b/cmd/version_test.go new file mode 100644 index 000000000..3c2b07014 --- /dev/null +++ b/cmd/version_test.go @@ -0,0 +1,28 @@ +package cmd + +import ( + "testing" + + "github.com/apex/log" + "github.com/apex/log/handlers/memory" + . "github.com/smartystreets/assertions" + "github.com/spf13/viper" +) + +func TestVersionCmd(t *testing.T) { + a := New(t) + + h := memory.New() + + ctx = &log.Logger{ + Level: log.DebugLevel, + Handler: h, + } + + viper.Set("version", "v-test") + versionCmd.Run(versionCmd, []string{}) + + a.So(h.Entries, ShouldHaveLength, 1) + a.So(h.Entries[0].Message, ShouldContainSubstring, "The Things Network") + a.So(h.Entries[0].Message, ShouldContainSubstring, "v-test") +} From a13a1c4387596c3cccb0d0edd2bb70c5e84a77a5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 11:37:53 +0100 Subject: [PATCH 0860/2266] Use TempDir for storage tests --- core/components/broker/storage_test.go | 3 +++ core/components/router/storage_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index 6ea1e6e8a..7fc1f5719 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -5,6 +5,7 @@ package broker import ( "os" + "path" "testing" . "github.com/TheThingsNetwork/ttn/core" @@ -18,6 +19,8 @@ import ( const storageDB = "TestBrokerStorage.db" func TestStorage(t *testing.T) { + storageDB := path.Join(os.TempDir(), storageDB) + defer func() { os.Remove(storageDB) }() diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index f547d75d9..404e226cd 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -5,6 +5,7 @@ package router import ( "os" + "path" "testing" "time" @@ -18,6 +19,8 @@ import ( const storageDB = "TestRouterStorage.db" func TestStoreAndLookup(t *testing.T) { + storageDB := path.Join(os.TempDir(), storageDB) + defer func() { os.Remove(storageDB) }() From 68afb14c71c8fc2f5550fa685d6a473c8ccdcd2c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 12:55:53 +0100 Subject: [PATCH 0861/2266] [ci] Update Drone Build --- .drone.yml | 50 ++++++++++++++++++++++++++++++++++++++++------- build_binaries.sh | 7 +++++++ 2 files changed, 50 insertions(+), 7 deletions(-) diff --git a/.drone.yml b/.drone.yml index 55d4c0f97..4ccf8aeaf 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,24 +1,45 @@ +matrix: + GO_VERSION: + - 1.5 + - 1.6 + GOOS: + - linux + - darwin + - windows + GOARCH: + - "386" + - amd64 + compose: mqtt: image: ansi/mosquitto command: /usr/local/sbin/mosquitto -p 1683 + build: test: - image: golang + image: golang:$$GO_VERSION commands: - - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .Imports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .TestImports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' + - go get -t -d ./... - go list ./... | grep -vE 'integration|ttn$' | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... + when: + matrix: + GOOS: linux + GOARCH: amd64 release: image: htdvisser/ttnbuild commands: - - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .Imports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - - bash -c 'go get $(comm -23 <(sort <(go list -f '"'"'{{join .TestImports "\n"}}'"'"' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork)' - - ./build_binaries.sh + # ttnbuild already contains dependencies, so we copy them: + - rsync -a /go/src/ /drone/src/ + # and get the ones that we missed in ttnbuild + - GOOS=$$GOOS GOARCH=$$GOARCH go get -d -v ./... + - ./build_binaries.sh $$GOOS $$GOARCH when: - branch: [master, develop] + # branch: [master, develop] + matrix: + GO_VERSION: 1.6 + publish: azure_storage: account_key: $$AZURE_STORAGE_KEY @@ -27,3 +48,18 @@ publish: source: release/ when: branch: [master, develop] + matrix: + GO_VERSION: 1.6 + docker: + username: $$DOCKER_USER + password: $$DOCKER_PASSWORD + email: $$DOCKER_EMAIL + repo: thethingsnetwork/ttn + tag: $$BRANCH + file: Dockerfile.local + when: + branch: [master, develop] + matrix: + GO_VERSION: 1.6 + GOOS: linux + GOARCH: amd64 diff --git a/build_binaries.sh b/build_binaries.sh index 330710fe7..f7b7f239f 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -9,6 +9,13 @@ mkdir -p $RELEASEPATH git_commit=$(git rev-parse HEAD) +echo "Preparing build..." +echo "CI_COMMIT: $CI_COMMIT" +echo "CI_BRANCH: $CI_BRANCH" +echo "CI_TAG: $CI_TAG" +echo "git_commit: $git_commit" +echo "" + build_release() { export CGO_ENABLED=0 From 012aa9a1978005fb34bbb165c54aacc4c746c678 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 12:57:21 +0100 Subject: [PATCH 0862/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index 614361244..fa037a952 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.b5RzQwQ6SOJwAonWkWnVbLSn5NzDAWQBywlcC4y_EkMmo596lG5DRfTcKaJnl2_psAyEPtijE2kX5_n8ebAyb0cuujdi9MLlQQcA_fN_twpmyb4K_yPmjuXZUI-4E9ZawIAbCMx-W8VKfYidb5mNlL8hVR8Ut4nJwFonWAmSNrOvt7ERVDOlYzEEfRuqszC0P_Q4E5FgBGJhIRLxTjUgkI4H9oGspynAoaAvm2mc-VuHpYW17ELENPInOarSbSXHWYtzXZ2fD7b7kygcj8vNfLJ2h55vBPbcloWSbv8PmX0y5Tj6jlG_VkhVlQZzT4noP1RJWJqxIb1RZfwU_3vetw._KD9zQ5YEsDs728C.QLPY-fFN_Ng7Ccprx-XDnGh20QNiH8e6DJrYw7zuhrVuw_J_Jd7GGSMGZ5d4BP3xhEJFbJWVFCdwXNqsibBECQgWLrWUKDnGrsFZBi6o9qlSP42XjLrudsRHzTlRiWOqIp3sJ5NQ9idwTZvzPejv8qfM83CRfKC_LpRQTGBW6WnIBhqzbCG-Z7y6NzhVicsjX9_Q_talwKmXs-7aqXKnS242yGZuMyp9G78_3mFnbfk4aEAy0iM4BQcOE6qS7VsTXpjqQN6O.wdDlDWO_1zoCXdoJ0jsz-A \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.SCv3UWUxADBXbTs-oeHNUF6pU5T0E8zgSfHCWnhaN0ohIOtnIgo1t84RDih4t_zen0DHfrPTMRCku2XnIQFZoG6R34j1bJGlAwwvHgDOp_zSMe6oMxXIIWHJ0OuVl7H3VV76z-40f0x1O_rfuI6EmBw_Q4-HTp9Y_BQ00n3kvPNTcy6B4bCKHs3OYm4uMFA5Ay8qMvhVaMqPoe4qK4LYQBG32A_OmT-Nw4uMhBe794fbvLDFSceEVLS6NAbxcGNCdRDh2UzeKdBr9zwjzINqS01TRQnAlNhWm5VYLtUuoonSjJrnrbSI1EXp7HeJHWnc18wsun5aT-3ddjDZH3bzHg.H5v5acJjJrHZVKpr.34Bda-0y0UBl8a_e7TKE32GM2lq7_8wuMxEciznP1zdR_xsrpBmSDo908GucJTEgboDNF6_MVENGpm5e3FgtwzVML73vQ4dbc0xjWOjtJk70ZBWCHy58d5PbOw3y55sUYaQaOEKZWi352nToF3mh1wqtbBxVnLLuhOxylBKbm90BFNTE5k_1W3g9vMlcI9C1irMFaR97rAEo_TbAzZsuDG1Qj7gdr8qvQom0WTCOEKoDfH-7dMPuZq7gE3O68tEgZOoDBFl5BRQKcozySErOizVH5FaH3jS6TRcPrxI7Q2FuIFwF_MjPudX8eJv0pG7c-nsjYFUTb-cZO1fZ1Ej3a7EMIywxTngi4YNbhg0oYrCbmdbqsOImdXxJB9UjXKSPLOpRzhydLJeRRV6uEshtuQ.t2yPGKdQod2NPNgNMxkLQA \ No newline at end of file From f14561329c134a6dd9ac17f16506b098a79de5ef Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 12:59:09 +0100 Subject: [PATCH 0863/2266] [ci] Only build or master and develop [Skip CI] --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 4ccf8aeaf..046c8e707 100644 --- a/.drone.yml +++ b/.drone.yml @@ -36,7 +36,7 @@ build: - GOOS=$$GOOS GOARCH=$$GOARCH go get -d -v ./... - ./build_binaries.sh $$GOOS $$GOARCH when: - # branch: [master, develop] + branch: [master, develop] matrix: GO_VERSION: 1.6 From 756231d15a1333920064730d78e13aece2a87050 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 12:59:43 +0100 Subject: [PATCH 0864/2266] [ci] Update .drone.sec [Skip CI] --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index fa037a952..a1e0862a1 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.SCv3UWUxADBXbTs-oeHNUF6pU5T0E8zgSfHCWnhaN0ohIOtnIgo1t84RDih4t_zen0DHfrPTMRCku2XnIQFZoG6R34j1bJGlAwwvHgDOp_zSMe6oMxXIIWHJ0OuVl7H3VV76z-40f0x1O_rfuI6EmBw_Q4-HTp9Y_BQ00n3kvPNTcy6B4bCKHs3OYm4uMFA5Ay8qMvhVaMqPoe4qK4LYQBG32A_OmT-Nw4uMhBe794fbvLDFSceEVLS6NAbxcGNCdRDh2UzeKdBr9zwjzINqS01TRQnAlNhWm5VYLtUuoonSjJrnrbSI1EXp7HeJHWnc18wsun5aT-3ddjDZH3bzHg.H5v5acJjJrHZVKpr.34Bda-0y0UBl8a_e7TKE32GM2lq7_8wuMxEciznP1zdR_xsrpBmSDo908GucJTEgboDNF6_MVENGpm5e3FgtwzVML73vQ4dbc0xjWOjtJk70ZBWCHy58d5PbOw3y55sUYaQaOEKZWi352nToF3mh1wqtbBxVnLLuhOxylBKbm90BFNTE5k_1W3g9vMlcI9C1irMFaR97rAEo_TbAzZsuDG1Qj7gdr8qvQom0WTCOEKoDfH-7dMPuZq7gE3O68tEgZOoDBFl5BRQKcozySErOizVH5FaH3jS6TRcPrxI7Q2FuIFwF_MjPudX8eJv0pG7c-nsjYFUTb-cZO1fZ1Ej3a7EMIywxTngi4YNbhg0oYrCbmdbqsOImdXxJB9UjXKSPLOpRzhydLJeRRV6uEshtuQ.t2yPGKdQod2NPNgNMxkLQA \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.PC9QaNYHia0MTTlzFvjnZn1fdFFvDPv3sJyTZKhK1B8sWwtfPgI71MMMAFGhBd0vKr4w1xiTrCws8xKVsboXZTjGs21xNCf4dcHuzEOAof7K8ICf2I8nbbyBStR-Jy2mnjdPYyGnzIFmwu8xMlbPhzYdOXJ70xNACzZ3ig10z9dmbu0-XdXfWqhYua5aXaOmGLRDI1eTwvlwS78q3-A1kinhtIdpHeLVW4oBsLKEuPg7h7lkIsPKB-N1ymVaJ1jZGY8yhzDw0WR10yjwvxCswwGGJpxAkjS9iDuoVwUafkBCBbdX5RwzLePr1qY0zveYDmTn87Yppyp0Hp1EWF0A4Q.35_dBRjwa3LxW55a.eUBcCd1eAn4RF85kofjMxEdFmwlhg9ySuyzNcnTVcmklpzTxl7MOcZTHlGaFTXAD0n3rqCe18mbVfOlqeE_sckmGKcC3gSMfbBdkPFDl9GK-GtWaP7OmDX3loPRLJX2V_VitAbSIm3lAbKARLWTu-eNwVU5DZ-fNayMHi2UIFX7Jx0jlS4SHnCAJzVv5DEf02WUWXJkJTa31Lm8L36shkd4AfwGTRVIOvcqOFeYHmBPM0Q6K5siDPiCjfHpakryem1ty8fZZprsxspy0WqY26cVAxP54MuD5zDnRg9aXJ0m6uvdLP1l3fe6PUSfvnkGIfUW716larc60rbws32eQtkceq5bwLE1SfU0egDqWieKI5_mU9Rih_R6Z71Dk_26BAd3Ue30O2iF0_QfYBIJlIw.4XwKRD3SR51oJBQTerJQrw \ No newline at end of file From 4163d939f830cf417f0ac8c3aacd35bbefef914c Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 13:12:35 +0100 Subject: [PATCH 0865/2266] [issue#34] Add mock for all registration interfaces type --- core/mocks/{mock.go => mocks.go} | 115 ++++++++++++++++++------------- 1 file changed, 67 insertions(+), 48 deletions(-) rename core/mocks/{mock.go => mocks.go} (72%) diff --git a/core/mocks/mock.go b/core/mocks/mocks.go similarity index 72% rename from core/mocks/mock.go rename to core/mocks/mocks.go index 433f28874..3b3e998bf 100644 --- a/core/mocks/mock.go +++ b/core/mocks/mocks.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package mock +package mocks import ( "fmt" @@ -53,104 +53,123 @@ func (r *MockRecipient) UnmarshalBinary(data []byte) error { return nil } -// MockRegistration implements the core.HRegistration interface +// MockRegistration implements the core.Registration interface // // It also stores the last arguments of each function call in appropriated // attributes. Because there's no computation going on, the expected / wanted // responses should also be defined. Default values are provided but can be changed // if needed. type MockRegistration struct { - OutAppEUI lorawan.EUI64 - OutDevEUI lorawan.EUI64 - OutNwkSKey lorawan.AES128Key - OutAppSKey lorawan.AES128Key OutRecipient Recipient } -func NewMockRegistration() *MockRegistration { - return &MockRegistration{ - OutAppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - OutDevEUI: lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - OutNwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), - OutAppSKey: lorawan.AES128Key([16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}), +func NewMockRegistration() MockRegistration { + return MockRegistration{ OutRecipient: NewMockRecipient(), } } -func (r *MockRegistration) Recipient() Recipient { - return r.OutRecipient -} - -func (r *MockRegistration) RawRecipient() []byte { +func (r MockRegistration) RawRecipient() []byte { data, _ := r.Recipient().MarshalBinary() return data } -func (r *MockRegistration) AppEUI() lorawan.EUI64 { - return r.OutAppEUI +func (r MockRegistration) Recipient() Recipient { + return r.OutRecipient } -func (r *MockRegistration) DevEUI() lorawan.EUI64 { - return r.OutDevEUI +// MockARegistration implements the core.ARegistration interface +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockARegistration struct { + MockRegistration + OutAppEUI lorawan.EUI64 } -func (r *MockRegistration) NwkSKey() lorawan.AES128Key { - return r.OutNwkSKey +func NewMockARegistration() MockARegistration { + return MockARegistration{ + MockRegistration: NewMockRegistration(), + OutAppEUI: lorawan.EUI64([8]byte{9, 0, 9, 2, 2, 2, 3, 4}), + } } -func (r *MockRegistration) AppSKey() lorawan.AES128Key { - return r.OutAppSKey +func (r MockARegistration) AppEUI() lorawan.EUI64 { + return r.OutAppEUI } -// MockRRegistration implements the core.Registration interface +// MockRRegistration implements the core.RRegistration interface // // It also stores the last arguments of each function call in appropriated // attributes. Because there's no computation going on, the expected / wanted // responses should also be defined. Default values are provided but can be changed // if needed. type MockRRegistration struct { - OutDevEUI lorawan.EUI64 - OutRecipient Recipient + MockRegistration + OutDevEUI lorawan.EUI64 } -func NewMockRRegistration() *MockRRegistration { - return &MockRRegistration{ - OutDevEUI: lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - OutRecipient: NewMockRecipient(), +func NewMockRRegistration() MockRRegistration { + return MockRRegistration{ + MockRegistration: NewMockRegistration(), + OutDevEUI: lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), } } -func (r *MockRRegistration) Recipient() Recipient { - return r.OutRecipient +func (r MockRRegistration) DevEUI() lorawan.EUI64 { + return r.OutDevEUI } -func (r *MockRRegistration) RawRecipient() []byte { - data, _ := r.Recipient().MarshalBinary() - return data +// MockBRegistration implements the core.BRegistration interface +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockBRegistration struct { + MockRRegistration + OutAppEUI lorawan.EUI64 + OutNwkSKey lorawan.AES128Key } -func (r *MockRRegistration) DevEUI() lorawan.EUI64 { - return r.OutDevEUI +func NewMockBRegistration() MockBRegistration { + return MockBRegistration{ + MockRRegistration: NewMockRRegistration(), + OutAppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + OutNwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), + } +} + +func (r MockBRegistration) AppEUI() lorawan.EUI64 { + return r.OutAppEUI +} + +func (r MockBRegistration) NwkSKey() lorawan.AES128Key { + return r.OutNwkSKey } -// MockARegistration implements the core.Registration interface +// MockHRegistration implements the core.HRegistration interface // // It also stores the last arguments of each function call in appropriated // attributes. Because there's no computation going on, the expected / wanted // responses should also be defined. Default values are provided but can be changed // if needed. -type MockARegistration struct { - OutRecipient Recipient +type MockHRegistration struct { + MockBRegistration + OutAppSKey lorawan.AES128Key } -func NewMockARegistration() *MockARegistration { - return &MockARegistration{ - OutRecipient: NewMockRecipient(), +func NewMockHRegistration() MockHRegistration { + return MockHRegistration{ + MockBRegistration: NewMockBRegistration(), + OutAppSKey: lorawan.AES128Key([16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}), } } -func (r *MockARegistration) Recipient() Recipient { - return r.OutRecipient +func (r MockHRegistration) AppSKey() lorawan.AES128Key { + return r.OutAppSKey } // MockAckNacker implements the core.AckNacker interface @@ -226,7 +245,7 @@ func NewMockAdapter() *MockAdapter { OutGetRecipient: NewMockRecipient(), OutNextPacket: []byte("MockAdapterNextPacket"), OutNextAckNacker: NewMockAckNacker(), - OutNextRegReg: NewMockRegistration(), + OutNextRegReg: NewMockHRegistration(), OutNextRegAckNacker: NewMockAckNacker(), } } From c2d47a6c21a925ba3095657c3abd9479cb50bcd3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 13:20:51 +0100 Subject: [PATCH 0866/2266] [ci] Use other drone-azure-storage plugin [Skip CI] --- .drone.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.drone.yml b/.drone.yml index 046c8e707..e88269ec4 100644 --- a/.drone.yml +++ b/.drone.yml @@ -42,6 +42,7 @@ build: publish: azure_storage: + image: htdvisser/drone-azure-storage account_key: $$AZURE_STORAGE_KEY storage_account: ttnreleases container: release From 7964523fd688333e91f3c0875df22aefc23ed287 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 13:21:29 +0100 Subject: [PATCH 0867/2266] [ci] Update .drone.sec after changing Azure Storage plugin --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index a1e0862a1..edc1b52bb 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.PC9QaNYHia0MTTlzFvjnZn1fdFFvDPv3sJyTZKhK1B8sWwtfPgI71MMMAFGhBd0vKr4w1xiTrCws8xKVsboXZTjGs21xNCf4dcHuzEOAof7K8ICf2I8nbbyBStR-Jy2mnjdPYyGnzIFmwu8xMlbPhzYdOXJ70xNACzZ3ig10z9dmbu0-XdXfWqhYua5aXaOmGLRDI1eTwvlwS78q3-A1kinhtIdpHeLVW4oBsLKEuPg7h7lkIsPKB-N1ymVaJ1jZGY8yhzDw0WR10yjwvxCswwGGJpxAkjS9iDuoVwUafkBCBbdX5RwzLePr1qY0zveYDmTn87Yppyp0Hp1EWF0A4Q.35_dBRjwa3LxW55a.eUBcCd1eAn4RF85kofjMxEdFmwlhg9ySuyzNcnTVcmklpzTxl7MOcZTHlGaFTXAD0n3rqCe18mbVfOlqeE_sckmGKcC3gSMfbBdkPFDl9GK-GtWaP7OmDX3loPRLJX2V_VitAbSIm3lAbKARLWTu-eNwVU5DZ-fNayMHi2UIFX7Jx0jlS4SHnCAJzVv5DEf02WUWXJkJTa31Lm8L36shkd4AfwGTRVIOvcqOFeYHmBPM0Q6K5siDPiCjfHpakryem1ty8fZZprsxspy0WqY26cVAxP54MuD5zDnRg9aXJ0m6uvdLP1l3fe6PUSfvnkGIfUW716larc60rbws32eQtkceq5bwLE1SfU0egDqWieKI5_mU9Rih_R6Z71Dk_26BAd3Ue30O2iF0_QfYBIJlIw.4XwKRD3SR51oJBQTerJQrw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.ezujQATdxeT7ygXK-Igx_rojtfkSoyYNrJx2vTA5Wqe0kS2NixnQivd6-ix17ZXywWykGPEUACH96rbmlQ8rikQ35xfdfvxXp-a9fbDvEUlqoRXgXs-yup6lO83bh4wFqF5HTvfAVbg8uesGEc6BiV4xp_yKHmpeK8_HKeJLzFlJk9baNPMdXaE_lrAmTkYe5GhkV16AkzbalwUYFB83w4wwRA6hyxOrR_SlMjcBR9HG45WJghabJtlyCDD6QJG5kMySf9Z6-Yoeg6PbWZVkgD7NYUxnGhvlfzEbdk0FIWZ43q1fG3qXvxq0yH0lzFfMWHMlmxjA3fkIJEj1CvZ1jg.ALqPQUYXkf2AqeZV.efA43bCPMUsNzs1Ou8_x3m5ujWyT6783V4k_dqitPQDPH7nxmIEFa30MWi9NMbqexXBb0ka4mK5agfUgFvXZYAdbjJYNMqPfXyhcWcieJVOlwFrg2NcxClYm0vAGm-MB6CdtgpkoMxy3eoEQ3MEbrBH9ZIdZqmY3wjWjwOdBwkJNt93vlDmkPCbLH7jmpCSqfJXA40lmhfV9vqT16Bg4bBQB927f6kCsIiYNA_RLHwM2w0FDMRFRHXUiQrXVpXW_hHeAsTt9vcFpUOQoZ-lANSNMOaOm6itCx1lWpLgESPCpsb_s79cjbYemoQvySUCw-yoMTEbxB4yCUc-QHZPT4uv9j8Uu7n8JfEf1o7CIYwYXurUC3y_QUwyjKBYq_KMfAbhhAhb4bH1he9FRpgPqeA.P6kayG2Ddq0JfGK2fxiUnw \ No newline at end of file From f0603db31ae54a243e1dc4e79825777f928bd5d9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 13:40:37 +0100 Subject: [PATCH 0868/2266] [issue#34] Update broker to support Application registrations --- core/components/broker/broker.go | 16 +- core/components/broker/broker_test.go | 257 +++++++++++++++---------- core/components/broker/helpers_test.go | 20 +- core/components/broker/mock_test.go | 48 +++-- core/components/broker/storage.go | 94 +++++++-- core/components/broker/storage_test.go | 199 ++++++++++++++++--- 6 files changed, 461 insertions(+), 173 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 8ed979dce..6c0b9d6b1 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -28,12 +28,16 @@ func (b component) Register(reg Registration, an AckNacker) (err error) { stats.MarkMeter("broker.registration.in") b.ctx.Debug("Handling registration") - breg, ok := reg.(BRegistration) - if !ok { - return errors.New(errors.Structural, "Not a Broker registration") + switch reg.(type) { + case BRegistration: + err = b.StoreDevice(reg.(BRegistration)) + case ARegistration: + err = b.StoreApplication(reg.(ARegistration)) + default: + err = errors.New(errors.Structural, "Not a Broker registration") } - return b.Store(breg) + return err } // HandleUp implements the core.Component interface @@ -65,7 +69,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { ctx := b.ctx.WithField("DevEUI", packet.DevEUI()) // 1. Check whether we should handle it - entries, err := b.Lookup(packet.DevEUI()) + entries, err := b.LookupDevices(packet.DevEUI()) if err != nil { switch err.(errors.Failure).Nature { case errors.Behavioural: @@ -81,7 +85,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // 2. Several handlers might be associated to the same device, we distinguish them using // MIC check. Only one should verify the MIC check - var mEntry *entry + var mEntry *devEntry for _, entry := range entries { ok, err := packet.ValidateMIC(entry.NwkSKey) if err != nil { diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index a471ef383..039598483 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -6,107 +6,154 @@ package broker import ( "testing" - . "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" + testutil "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" ) func TestRegister(t *testing.T) { { - Desc(t, "Register an entry") + testutil.Desc(t, "Register a device") // Build - an := NewMockAckNacker() + an := mocks.NewMockAckNacker() store := newMockStorage() - r := NewMockRegistration() + r := mocks.NewMockBRegistration() // Operate - broker := New(store, GetLogger(t, "Router")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.Register(r, an) // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, r, store.InStore) + errutil.CheckErrors(t, nil, err) + mocks.CheckAcks(t, true, an.InAck) + CheckRegistrations(t, r, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) } // ------------------- { - Desc(t, "Register an entry | store failed") + testutil.Desc(t, "Register an application") // Build - an := NewMockAckNacker() + an := mocks.NewMockAckNacker() store := newMockStorage() - store.Failures["Store"] = errors.New(errors.Structural, "Mock Error: Store Failed") - r := NewMockRegistration() + r := mocks.NewMockARegistration() // Operate - broker := New(store, GetLogger(t, "Router")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.Register(r, an) // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, r, store.InStore) + errutil.CheckErrors(t, nil, err) + mocks.CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, r, store.InStoreApp) } // ------------------- { - Desc(t, "Register an entry | Wrong registration") + testutil.Desc(t, "Register a device | store failed") // Build - an := NewMockAckNacker() + an := mocks.NewMockAckNacker() store := newMockStorage() - r := NewMockRRegistration() + store.Failures["StoreDevice"] = errors.New(errors.Structural, "Mock Error: Store Failed") + r := mocks.NewMockBRegistration() // Operate - broker := New(store, GetLogger(t, "Router")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.Register(r, an) // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, r, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + } + + // ------------------- + + { + testutil.Desc(t, "Register an application | store failed") + + // Build + an := mocks.NewMockAckNacker() + store := newMockStorage() + store.Failures["StoreApplication"] = errors.New(errors.Structural, "Mock Error: Store Failed") + r := mocks.NewMockARegistration() + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.Register(r, an) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, r, store.InStoreApp) + } + + // ------------------- + + { + testutil.Desc(t, "Register an entry | Wrong registration") + + // Build + an := mocks.NewMockAckNacker() + store := newMockStorage() + r := mocks.NewMockRRegistration() + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.Register(r, an) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) } } func TestHandleDown(t *testing.T) { { - Desc(t, "Try Handle Down") + testutil.Desc(t, "Try Handle Down") // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + adapter := mocks.NewMockAdapter() store := newMockStorage() // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleDown([]byte{1, 2, 3}, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Implementation)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) + errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, nil, adapter.InSendPacket) + mocks.CheckRecipients(t, nil, adapter.InSendRecipients) } } func TestHandleUp(t *testing.T) { { - Desc(t, "Send an unknown packet") + testutil.Desc(t, "Send an unknown packet") // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + adapter := mocks.NewMockAdapter() store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + store.Failures["LookupDevices"] = errors.New(errors.Behavioural, "Mock Error: Not Found") data, _ := newBPacket( [4]byte{2, 3, 2, 3}, "Payload", @@ -115,49 +162,51 @@ func TestHandleUp(t *testing.T) { ).MarshalBinary() // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleUp(data, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) + errutil.CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, nil, adapter.InSendPacket) + mocks.CheckRecipients(t, nil, adapter.InSendRecipients) } // ------------------- { - Desc(t, "Send an invalid packet") + testutil.Desc(t, "Send an invalid packet") // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + adapter := mocks.NewMockAdapter() store := newMockStorage() // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleUp([]byte{1, 2, 3}, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, nil, adapter.InSendPacket) + mocks.CheckRecipients(t, nil, adapter.InSendRecipients) } // ------------------- { - Desc(t, "Send packet, get 2 entries, no valid MIC") + testutil.Desc(t, "Send packet, get 2 entries, no valid MIC") // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + adapter := mocks.NewMockAdapter() store := newMockStorage() - store.OutLookup = []entry{ + store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), @@ -179,30 +228,31 @@ func TestHandleUp(t *testing.T) { ).MarshalBinary() // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleUp(data, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) + errutil.CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, nil, adapter.InSendPacket) + mocks.CheckRecipients(t, nil, adapter.InSendRecipients) } // ------------------- { - Desc(t, "Send packet, get 2 entries, 1 valid MIC | No downlink") + testutil.Desc(t, "Send packet, get 2 entries, 1 valid MIC | No downlink") // Build - an := NewMockAckNacker() - recipient := NewMockRecipient() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + recipient := mocks.NewMockRecipient() + adapter := mocks.NewMockAdapter() adapter.OutSend = nil adapter.OutGetRecipient = recipient store := newMockStorage() - store.OutLookup = []entry{ + store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), @@ -223,37 +273,38 @@ func TestHandleUp(t *testing.T) { 5, ) data, _ := bpacket.MarshalBinary() - hpacket, _ := NewHPacket( - store.OutLookup[1].AppEUI, - store.OutLookup[1].DevEUI, + hpacket, _ := core.NewHPacket( + store.OutLookupDevices[1].AppEUI, + store.OutLookupDevices[1].DevEUI, bpacket.Payload(), bpacket.Metadata(), ) // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleUp(data, an, adapter) // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, hpacket, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + errutil.CheckErrors(t, nil, err) + mocks.CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, hpacket, adapter.InSendPacket) + mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) } // ------------------- { - Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to get recipient") + testutil.Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to get recipient") // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + adapter := mocks.NewMockAdapter() adapter.OutSend = nil adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Unable to get recipient") store := newMockStorage() - store.OutLookup = []entry{ + store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), @@ -276,30 +327,31 @@ func TestHandleUp(t *testing.T) { data, _ := bpacket.MarshalBinary() // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleUp(data, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, nil, adapter.InSendPacket) + mocks.CheckRecipients(t, nil, adapter.InSendRecipients) } // ------------------- { - Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to send") + testutil.Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to send") // Build - an := NewMockAckNacker() - recipient := NewMockRecipient() - adapter := NewMockAdapter() + an := mocks.NewMockAckNacker() + recipient := mocks.NewMockRecipient() + adapter := mocks.NewMockAdapter() adapter.OutGetRecipient = recipient adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") store := newMockStorage() - store.OutLookup = []entry{ + store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), @@ -320,22 +372,23 @@ func TestHandleUp(t *testing.T) { 5, ) data, _ := bpacket.MarshalBinary() - hpacket, _ := NewHPacket( - store.OutLookup[1].AppEUI, - store.OutLookup[1].DevEUI, + hpacket, _ := core.NewHPacket( + store.OutLookupDevices[1].AppEUI, + store.OutLookupDevices[1].DevEUI, bpacket.Payload(), bpacket.Metadata(), ) // Operate - broker := New(store, GetLogger(t, "Broker")) + broker := New(store, testutil.GetLogger(t, "Broker")) err := broker.HandleUp(data, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, hpacket, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, hpacket, adapter.InSendPacket) + mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) } } diff --git a/core/components/broker/helpers_test.go b/core/components/broker/helpers_test.go index 254349e04..266ab84c5 100644 --- a/core/components/broker/helpers_test.go +++ b/core/components/broker/helpers_test.go @@ -6,13 +6,13 @@ package broker import ( "testing" - . "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/brocaar/lorawan" ) // ----- BUILD utilities -func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint32) BPacket { +func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint32) core.BPacket { var devAddr lorawan.DevAddr copy(devAddr[:], rawDevAddr[:]) @@ -36,7 +36,7 @@ func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint3 panic(err) } - packet, err := NewBPacket(phyPayload, Metadata{}) + packet, err := core.NewBPacket(phyPayload, core.Metadata{}) if err != nil { panic(err) } @@ -44,10 +44,14 @@ func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint3 } // ----- CHECK utilities -func CheckEntries(t *testing.T, want []entry, got []entry) { - Check(t, want, got, "Entries") +func CheckDevEntries(t *testing.T, want []devEntry, got []devEntry) { + mocks.Check(t, want, got, "DevEntries") } -func CheckRegistrations(t *testing.T, want Registration, got Registration) { - Check(t, want, got, "Registrations") +func CheckAppEntries(t *testing.T, want appEntry, got appEntry) { + mocks.Check(t, want, got, "AppEntries") +} + +func CheckRegistrations(t *testing.T, want core.Registration, got core.Registration) { + mocks.Check(t, want, got, "Registrations") } diff --git a/core/components/broker/mock_test.go b/core/components/broker/mock_test.go index a98f37e00..c4a4a02ce 100644 --- a/core/components/broker/mock_test.go +++ b/core/components/broker/mock_test.go @@ -4,21 +4,28 @@ package broker import ( - . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/brocaar/lorawan" ) type mockStorage struct { - Failures map[string]error - InLookup lorawan.EUI64 - OutLookup []entry - InStore BRegistration + Failures map[string]error + InLookupDevices lorawan.EUI64 + InLookupApp lorawan.EUI64 + InStoreDevices core.BRegistration + InStoreApp core.ARegistration + OutLookupDevices []devEntry + OutLookupApp appEntry } func newMockStorage() *mockStorage { return &mockStorage{ Failures: make(map[string]error), - OutLookup: []entry{ + OutLookupApp: appEntry{ + Recipient: []byte("MockStorageRecipient"), + AppEUI: lorawan.EUI64([8]byte{5, 5, 5, 5, 5, 5, 6, 6}), + }, + OutLookupDevices: []devEntry{ { Recipient: []byte("MockStorageRecipient"), AppEUI: lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 1, 1}), @@ -29,17 +36,30 @@ func newMockStorage() *mockStorage { } } -func (s *mockStorage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { - s.InLookup = devEUI - if s.Failures["Lookup"] != nil { - return nil, s.Failures["Lookup"] +func (s *mockStorage) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { + s.InLookupDevices = devEUI + if s.Failures["LookupDevices"] != nil { + return nil, s.Failures["LookupDevices"] + } + return s.OutLookupDevices, nil +} + +func (s *mockStorage) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { + s.InLookupApp = appEUI + if s.Failures["LookupApplication"] != nil { + return appEntry{}, s.Failures["LookupApplication"] } - return s.OutLookup, nil + return s.OutLookupApp, nil +} + +func (s *mockStorage) StoreDevice(reg core.BRegistration) error { + s.InStoreDevices = reg + return s.Failures["StoreDevice"] } -func (s *mockStorage) Store(reg BRegistration) error { - s.InStore = reg - return s.Failures["Store"] +func (s *mockStorage) StoreApplication(reg core.ARegistration) error { + s.InStoreApp = reg + return s.Failures["StoreApplication"] } func (s *mockStorage) Close() error { diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 5c63a5103..a63079b2b 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -4,7 +4,7 @@ package broker import ( - . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" @@ -13,21 +13,29 @@ import ( // Storage gives a facade for manipulating the broker database type Storage interface { - Lookup(devEUI lorawan.EUI64) ([]entry, error) - Store(reg BRegistration) error + LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) + LookupApplication(appEUI lorawan.EUI64) (appEntry, error) + StoreDevice(reg core.BRegistration) error + StoreApplication(reg core.ARegistration) error Close() error } -type entry struct { +type devEntry struct { Recipient []byte AppEUI lorawan.EUI64 DevEUI lorawan.EUI64 NwkSKey lorawan.AES128Key } +type appEntry struct { + Recipient []byte + AppEUI lorawan.EUI64 +} + type storage struct { - db dbutil.Interface - Name string + db dbutil.Interface + Devices string + Applications string } // NewStorage constructs a new broker storage @@ -37,43 +45,75 @@ func NewStorage(name string) (Storage, error) { return nil, errors.New(errors.Operational, err) } - return storage{db: itf, Name: "handlers"}, nil + return storage{db: itf, Devices: "Devices", Applications: "Applications"}, nil } -// Lookup implements the broker.Storage interface -func (s storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { - entries, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) +// LookupDevices implements the broker.Storage interface +func (s storage) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { + entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) if err != nil { return nil, err } - return entries.([]entry), nil + return entries.([]devEntry), nil +} + +// LookupApplication implements the broker.Storage interface +func (s storage) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { + itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{}) + if err != nil { + return appEntry{}, err + } + + entries := itf.([]appEntry) + if len(entries) != 1 { + // NOTE Shall we reset the entry ? + return appEntry{}, errors.New(errors.Structural, "Invalid application entries") + } + + return entries[0], nil } -// Store implements the broker.Storage interface -func (s storage) Store(reg BRegistration) error { +// StoreDevice implements the broker.Storage interface +func (s storage) StoreDevice(reg core.BRegistration) error { data, err := reg.Recipient().MarshalBinary() if err != nil { return errors.New(errors.Structural, err) } - key := reg.DevEUI() - return s.db.Store(s.Name, key[:], []dbutil.Entry{ - &entry{ + devEUI := reg.DevEUI() + return s.db.Store(s.Devices, devEUI[:], []dbutil.Entry{ + &devEntry{ Recipient: data, AppEUI: reg.AppEUI(), - DevEUI: key, + DevEUI: devEUI, NwkSKey: reg.NwkSKey(), }, }) } +// StoreApplication implements the broker.Storage interface +func (s storage) StoreApplication(reg core.ARegistration) error { + data, err := reg.Recipient().MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) + } + + appEUI := reg.AppEUI() + return s.db.Replace(s.Applications, appEUI[:], []dbutil.Entry{ + &appEntry{ + Recipient: data, + AppEUI: appEUI, + }, + }) +} + // Close implements the broker.Storage interface func (s storage) Close() error { return s.db.Close() } // MarshalBinary implements the encoding.BinaryMarshaler interface -func (e entry) MarshalBinary() ([]byte, error) { +func (e devEntry) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write(e.Recipient) rw.Write(e.AppEUI) @@ -83,7 +123,7 @@ func (e entry) MarshalBinary() ([]byte, error) { } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *entry) UnmarshalBinary(data []byte) error { +func (e *devEntry) UnmarshalBinary(data []byte) error { rw := readwriter.New(data) rw.Read(func(data []byte) { e.Recipient = data }) rw.Read(func(data []byte) { copy(e.AppEUI[:], data) }) @@ -91,3 +131,19 @@ func (e *entry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) return rw.Err() } + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e appEntry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.Recipient) + rw.Write(e.AppEUI) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *appEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.Recipient = data }) + rw.Read(func(data []byte) { copy(e.AppEUI[:], data) }) + return rw.Err() +} diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index 6ea1e6e8a..03c0b485a 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -17,7 +17,7 @@ import ( const storageDB = "TestBrokerStorage.db" -func TestStorage(t *testing.T) { +func TestStorageDevice(t *testing.T) { defer func() { os.Remove(storageDB) }() @@ -39,15 +39,15 @@ func TestStorage(t *testing.T) { // Build db, _ := NewStorage(storageDB) - r := NewMockRegistration() + r := NewMockBRegistration() // Operate - err := db.Store(r) + err := db.StoreDevice(r) CheckErrors(t, nil, err) - entries, err := db.Lookup(r.DevEUI()) + entries, err := db.LookupDevices(r.DevEUI()) // Expectations - want := []entry{ + want := []devEntry{ { AppEUI: r.AppEUI(), DevEUI: r.DevEUI(), @@ -58,7 +58,7 @@ func TestStorage(t *testing.T) { // Check CheckErrors(t, nil, err) - CheckEntries(t, want, entries) + CheckDevEntries(t, want, entries) _ = db.Close() } @@ -69,18 +69,18 @@ func TestStorage(t *testing.T) { // Build db, _ := NewStorage(storageDB) - r := NewMockRegistration() + r := NewMockBRegistration() r.OutDevEUI[0] = 34 // Operate - err := db.Store(r) + err := db.StoreDevice(r) CheckErrors(t, nil, err) - err = db.Store(r) + err = db.StoreDevice(r) CheckErrors(t, nil, err) - entries, err := db.Lookup(r.DevEUI()) + entries, err := db.LookupDevices(r.DevEUI()) // Expectations - want := []entry{ + want := []devEntry{ { AppEUI: r.AppEUI(), DevEUI: r.DevEUI(), @@ -97,7 +97,7 @@ func TestStorage(t *testing.T) { // Check CheckErrors(t, nil, err) - CheckEntries(t, want, entries) + CheckDevEntries(t, want, entries) _ = db.Close() } @@ -108,15 +108,15 @@ func TestStorage(t *testing.T) { // Build db, _ := NewStorage(storageDB) - devEUI := NewMockRegistration().DevEUI() + devEUI := NewMockBRegistration().DevEUI() devEUI[1] = 98 // Operate - entries, err := db.Lookup(devEUI) + entries, err := db.LookupDevices(devEUI) // Checks CheckErrors(t, pointer.String(string(errors.Behavioural)), err) - CheckEntries(t, nil, entries) + CheckDevEntries(t, nil, entries) _ = db.Close() } @@ -128,11 +128,11 @@ func TestStorage(t *testing.T) { // Build db, _ := NewStorage(storageDB) _ = db.Close() - r := NewMockRegistration() + r := NewMockBRegistration() r.OutDevEUI[5] = 9 // Operate - err := db.Store(r) + err := db.StoreDevice(r) // Checks CheckErrors(t, pointer.String(string(errors.Operational)), err) @@ -146,14 +146,14 @@ func TestStorage(t *testing.T) { // Build db, _ := NewStorage(storageDB) _ = db.Close() - devEUI := NewMockRegistration().DevEUI() + devEUI := NewMockBRegistration().DevEUI() // Operate - entries, err := db.Lookup(devEUI) + entries, err := db.LookupDevices(devEUI) // Checks CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckEntries(t, nil, entries) + CheckDevEntries(t, nil, entries) } // ------------------- @@ -163,16 +163,167 @@ func TestStorage(t *testing.T) { // Build db, _ := NewStorage(storageDB) - r := NewMockRegistration() + r := NewMockBRegistration() r.OutDevEUI[7] = 99 r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") // Operate & Check - err := db.Store(r) + err := db.StoreDevice(r) CheckErrors(t, pointer.String(string(errors.Structural)), err) - entries, err := db.Lookup(r.DevEUI()) + entries, err := db.LookupDevices(r.DevEUI()) CheckErrors(t, pointer.String(string(errors.Behavioural)), err) - CheckEntries(t, nil, entries) + CheckDevEntries(t, nil, entries) + + _ = db.Close() + } +} + +func TestStorageApplication(t *testing.T) { + defer func() { + os.Remove(storageDB) + }() + + // ------------------- + + { + Desc(t, "Create a new storage") + db, err := NewStorage(storageDB) + CheckErrors(t, nil, err) + err = db.Close() + CheckErrors(t, nil, err) + } + + // ------------------- + + { + Desc(t, "Store then lookup a registration") + + // Build + db, _ := NewStorage(storageDB) + r := NewMockARegistration() + + // Operate + err := db.StoreApplication(r) + CheckErrors(t, nil, err) + entry, err := db.LookupApplication(r.AppEUI()) + + // Expectations + want := appEntry{ + AppEUI: r.AppEUI(), + Recipient: r.RawRecipient(), + } + + // Check + CheckErrors(t, nil, err) + CheckAppEntries(t, want, entry) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Store entries with same AppEUI") + + // Build + db, _ := NewStorage(storageDB) + r1 := NewMockARegistration() + r1.OutAppEUI[0] = 34 + r2 := NewMockARegistration() + r2.OutAppEUI[0] = 34 + r2.OutRecipient = NewMockRecipient() + r2.OutRecipient.(*MockRecipient).OutMarshalBinary = []byte{88, 99, 77, 66} + + // Operate + err := db.StoreApplication(r1) + CheckErrors(t, nil, err) + err = db.StoreApplication(r2) + CheckErrors(t, nil, err) + entry, err := db.LookupApplication(r1.AppEUI()) + + // Expectations + want := appEntry{ + AppEUI: r2.AppEUI(), + Recipient: r2.RawRecipient(), + } + + // Check + CheckErrors(t, nil, err) + CheckAppEntries(t, want, entry) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Lookup non-existing entry") + + // Build + db, _ := NewStorage(storageDB) + appEUI := NewMockARegistration().AppEUI() + appEUI[1] = 98 + + // Operate + entry, err := db.LookupApplication(appEUI) + + // Checks + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckAppEntries(t, appEntry{}, entry) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Store on a closed database") + + // Build + db, _ := NewStorage(storageDB) + _ = db.Close() + r := NewMockARegistration() + r.OutAppEUI[5] = 9 + + // Operate + err := db.StoreApplication(r) + + // Checks + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // ------------------- + + { + Desc(t, "Lookup on a closed database") + + // Build + db, _ := NewStorage(storageDB) + _ = db.Close() + appEUI := NewMockARegistration().AppEUI() + + // Operate + entry, err := db.LookupApplication(appEUI) + + // Checks + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAppEntries(t, appEntry{}, entry) + } + + // ------------------- + + { + Desc(t, "Store an invalid recipient") + + // Build + db, _ := NewStorage(storageDB) + r := NewMockARegistration() + r.OutAppEUI[7] = 99 + r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") + + // Operate & Check + err := db.StoreApplication(r) + CheckErrors(t, pointer.String(string(errors.Structural)), err) + entry, err := db.LookupApplication(r.AppEUI()) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckAppEntries(t, appEntry{}, entry) _ = db.Close() } From 5cf4dda3fa5eb51de1f4431b9a3431b5771a8146 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 13:53:54 +0100 Subject: [PATCH 0869/2266] [issue#34] Spread registration changes to the whole library --- core/adapters/http/httpRegistration.go | 2 +- core/adapters/http/http_test.go | 10 +++++----- core/adapters/mqtt/handlers/helpers_test.go | 12 ++++++------ core/components/handler/devStorage_test.go | 8 ++++---- core/components/handler/handler_test.go | 4 ++-- core/components/handler/helpers_test.go | 2 +- core/components/router/storage_test.go | 16 ++++++++-------- 7 files changed, 27 insertions(+), 27 deletions(-) diff --git a/core/adapters/http/httpRegistration.go b/core/adapters/http/httpRegistration.go index 07c70f9b8..4f9ad55e0 100644 --- a/core/adapters/http/httpRegistration.go +++ b/core/adapters/http/httpRegistration.go @@ -13,7 +13,7 @@ type httpRegistration struct { devEUI lorawan.EUI64 } -// Recipient implements the core.Registration inteface +// Recipient implements the core.RRegistration inteface func (r httpRegistration) Recipient() core.Recipient { return r.recipient } diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index deddce66c..6e689bc98 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -185,17 +185,17 @@ func getPayloads(chpayloads []chan string) []string { return got } -func getRegistrations(adapter *Adapter, want []testRegistration) []core.Registration { - var got []core.Registration +func getRegistrations(adapter *Adapter, want []testRegistration) []core.RRegistration { + var got []core.RRegistration for range want { - ch := make(chan core.Registration) + ch := make(chan core.RRegistration) go func() { r, an, err := adapter.NextRegistration() if err != nil { return } an.Ack(nil) - ch <- r + ch <- r.(core.RRegistration) }() select { case r := <-ch: @@ -246,7 +246,7 @@ func genMockServer(recipient core.Recipient) chan string { } // Check utilities -func checkRegistrations(t *testing.T, want []testRegistration, got []core.Registration) { +func checkRegistrations(t *testing.T, want []testRegistration, got []core.RRegistration) { if len(want) != len(got) { Ko(t, "Expected %d registrations but got %d", len(want), len(got)) return diff --git a/core/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go index 7c0c9e808..ce7f01d3e 100644 --- a/core/adapters/mqtt/handlers/helpers_test.go +++ b/core/adapters/mqtt/handlers/helpers_test.go @@ -212,18 +212,18 @@ func checkRegistrations(t *testing.T, want core.HRegistration, got core.Registra return } - // Check DevEUIs - if !reflect.DeepEqual(want.DevEUI(), got.DevEUI()) { - Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", want.DevEUI(), got.DevEUI()) - return - } - rgot, ok := got.(core.HRegistration) if !ok { Ko(t, "Expected to receive an HRegistration but got %+v", got) return } + // Check DevEUIs + if !reflect.DeepEqual(want.DevEUI(), rgot.DevEUI()) { + Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", want.DevEUI(), rgot.DevEUI()) + return + } + // Check AppEUIs if !reflect.DeepEqual(want.AppEUI(), rgot.AppEUI()) { Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", want.AppEUI(), rgot.AppEUI()) diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 8e6ea7b8f..87170854a 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -40,7 +40,7 @@ func TestLookupStore(t *testing.T) { Desc(t, "Store and Lookup a registration") // Build - r := NewMockRegistration() + r := NewMockHRegistration() // Operate err := db.StorePersonalized(r) @@ -58,7 +58,7 @@ func TestLookupStore(t *testing.T) { Desc(t, "Lookup a non-existing registration") // Build - r := NewMockRegistration() + r := NewMockHRegistration() r.OutAppEUI = lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) // Operate @@ -74,7 +74,7 @@ func TestLookupStore(t *testing.T) { Desc(t, "Store twice the same registration") // Build - r := NewMockRegistration() + r := NewMockHRegistration() r.OutAppEUI = lorawan.EUI64([8]byte{1, 4, 1, 4, 1, 4, 1, 4}) // Operate @@ -94,7 +94,7 @@ func TestLookupStore(t *testing.T) { Desc(t, "Store Activated") // Build - r := NewMockRegistration() + r := NewMockHRegistration() r.OutAppEUI = lorawan.EUI64([8]byte{6, 6, 6, 7, 8, 6, 7, 6}) // Operate diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index a42b5c666..0d9b19422 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -25,7 +25,7 @@ func TestRegister(t *testing.T) { devStorage := newMockDevStorage() pktStorage := newMockPktStorage() an := NewMockAckNacker() - r := NewMockRegistration() + r := NewMockHRegistration() // Operate handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) @@ -67,7 +67,7 @@ func TestRegister(t *testing.T) { devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") pktStorage := newMockPktStorage() an := NewMockAckNacker() - r := NewMockRegistration() + r := NewMockHRegistration() // Operate handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) diff --git a/core/components/handler/helpers_test.go b/core/components/handler/helpers_test.go index 6d166cf8f..46fe94a48 100644 --- a/core/components/handler/helpers_test.go +++ b/core/components/handler/helpers_test.go @@ -91,7 +91,7 @@ func CheckPackets(t *testing.T, want APacket, got APacket) { Check(t, want, got, "Packets") } -func CheckEntries(t *testing.T, want *MockRegistration, got devEntry) { +func CheckEntries(t *testing.T, want MockHRegistration, got devEntry) { // NOTE This only works in the case of Personalized devices var devAddr lorawan.DevAddr devEUI := want.DevEUI() diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index f547d75d9..2b4836fb8 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -39,7 +39,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Hour) - r := NewMockRegistration() + r := NewMockRRegistration() // Operate err := db.Store(r) @@ -65,7 +65,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Hour) - r := NewMockRegistration() + r := NewMockRRegistration() // Operate err := db.Store(r) @@ -90,7 +90,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Hour) - devEUI := NewMockRegistration().DevEUI() + devEUI := NewMockRRegistration().DevEUI() devEUI[1] = 14 // Operate @@ -109,7 +109,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Millisecond*100) - r := NewMockRegistration() + r := NewMockRRegistration() r.OutDevEUI[0] = 12 // Operate @@ -130,7 +130,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Millisecond*100) - r := NewMockRegistration() + r := NewMockRRegistration() r.OutDevEUI[4] = 27 // Operate @@ -160,7 +160,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Hour) _ = db.Close() - r := NewMockRegistration() + r := NewMockRRegistration() r.OutDevEUI[5] = 9 // Operate @@ -178,7 +178,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Hour) _ = db.Close() - devEUI := NewMockRegistration().DevEUI() + devEUI := NewMockRRegistration().DevEUI() // Operate gotEntry, err := db.Lookup(devEUI) @@ -195,7 +195,7 @@ func TestStoreAndLookup(t *testing.T) { // Build db, _ := NewStorage(storageDB, time.Hour) - r := NewMockRegistration() + r := NewMockRRegistration() r.OutDevEUI[7] = 99 r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") From c9a5e456fa99bf0f1eb89b5195c182e05f0d384a Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 14:22:04 +0100 Subject: [PATCH 0870/2266] [issue#34] Implement new handler to handle application registrations from handlers --- core/adapters/http/handlers/applications.go | 120 ++++++++++++++ .../http/handlers/applicationsRegistration.go | 26 +++ .../http/handlers/applications_test.go | 150 ++++++++++++++++++ core/adapters/http/handlers/helpers_test.go | 11 ++ core/adapters/http/handlers/pubsub.go | 1 + 5 files changed, 308 insertions(+) create mode 100644 core/adapters/http/handlers/applications.go create mode 100644 core/adapters/http/handlers/applicationsRegistration.go create mode 100644 core/adapters/http/handlers/applications_test.go diff --git a/core/adapters/http/handlers/applications.go b/core/adapters/http/handlers/applications.go new file mode 100644 index 000000000..678ab7fbb --- /dev/null +++ b/core/adapters/http/handlers/applications.go @@ -0,0 +1,120 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "encoding/hex" + "encoding/json" + "io" + "net/http" + "regexp" + "strings" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// Applications defines a handler to handle application registration on a component. +// +// It listens to request of the form: [PUT] /applications/:appEUI +// where appEUI is a 8 bytes hex-encoded address. +// +// It expects a Content-Type = application/json +// +// It also looks for params: +// +// - app_url (http address as string) +// +// It fails with an http 400 Bad Request. if one of the parameter is missing or invalid +// It succeeds with an http 2xx if the request is valid (the response status is under the +// ackNacker responsibility). +// It can possibly fails with another status depending of the AckNacker response. +// +// The PubSub handler generates registration where: +// - AppEUI is available +// - Recipient can be interpreted as an HttpRecipient (Url + Method) +type Applications struct{} + +// URL implements the http.Handler interface +func (p Applications) URL() string { + return "/applications/" +} + +// Handle implements the http.Handler interface +func (p Applications) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { + // Check the http method + if req.Method != "PUT" { + err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [PUT] to register a device") + w.WriteHeader(http.StatusMethodNotAllowed) + w.Write([]byte(err.Error())) + return + } + + // Parse body and query params + registration, err := p.parse(req) + if err != nil { + BadRequest(w, err.Error()) + return + } + + // Send the registration and wait for ack / nack + chresp := make(chan MsgRes) + chreg <- RegReq{Registration: registration, Chresp: chresp} + r, ok := <-chresp + if !ok { + BadRequest(w, "Core server not responding") + return + } + w.WriteHeader(r.StatusCode) + w.Write(r.Content) +} + +// parse extracts params from the request and fails if the request is invalid. +func (p Applications) parse(req *http.Request) (core.Registration, error) { + // Check Content-type + if req.Header.Get("Content-Type") != "application/json" { + return applicationsRegistration{}, errors.New(errors.Structural, "Received invalid content-type in request") + } + + // Check the query parameter + reg := regexp.MustCompile("applications/([a-fA-F0-9]{16})$") // 8-bytes, hex-encoded -> 16 chars + query := reg.FindStringSubmatch(req.RequestURI) + if len(query) < 2 { + return applicationsRegistration{}, errors.New(errors.Structural, "Incorrect application identifier format") + } + appEUI, err := hex.DecodeString(query[1]) + if err != nil { + return applicationsRegistration{}, errors.New(errors.Structural, err) + } + + // Check configuration in body + body := make([]byte, req.ContentLength) + n, err := req.Body.Read(body) + if err != nil && err != io.EOF { + return applicationsRegistration{}, errors.New(errors.Structural, err) + } + defer req.Body.Close() + params := &struct { + URL string `json:"app_url"` + }{} + if err := json.Unmarshal(body[:n], params); err != nil { + return applicationsRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") + } + + // Verify each request parameter + params.URL = strings.Trim(params.URL, " ") + if len(params.URL) <= 0 { + return applicationsRegistration{}, errors.New(errors.Structural, "Incorrect application url") + } + + // Create actual registration + registration := applicationsRegistration{ + recipient: NewRecipient(params.URL, "PUT"), + appEUI: lorawan.EUI64{}, + } + copy(registration.appEUI[:], appEUI[:]) + return registration, nil +} diff --git a/core/adapters/http/handlers/applicationsRegistration.go b/core/adapters/http/handlers/applicationsRegistration.go new file mode 100644 index 000000000..421f4a3d6 --- /dev/null +++ b/core/adapters/http/handlers/applicationsRegistration.go @@ -0,0 +1,26 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/brocaar/lorawan" +) + +// applicationsRegistration implements the core.ARegistration interface +type applicationsRegistration struct { + recipient Recipient + appEUI lorawan.EUI64 +} + +// Recipient implements the core.ARegistration interface +func (r applicationsRegistration) Recipient() core.Recipient { + return r.recipient +} + +// AppEUI implements the core.ARegistration interface +func (r applicationsRegistration) AppEUI() lorawan.EUI64 { + return r.appEUI +} diff --git a/core/adapters/http/handlers/applications_test.go b/core/adapters/http/handlers/applications_test.go new file mode 100644 index 000000000..2b3285e1e --- /dev/null +++ b/core/adapters/http/handlers/applications_test.go @@ -0,0 +1,150 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "fmt" + "net/http" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func TestApplications(t *testing.T) { + tests := []struct { + Desc string + Payload string + ContentType string + Method string + AppEUI string + ShouldAck bool + AckPacket core.Packet + + WantContent string + WantStatusCode int + WantRegistration core.Registration + WantError *string + }{ + { + Desc: "Invalid Payload. Valid ContentType. Valid Method. Valid AppEUI. Nack", + Payload: "TheThingsNetwork", + ContentType: "application/json", + Method: "PUT", + AppEUI: "0000000011223344", + ShouldAck: false, + + WantContent: string(errors.Structural), + WantStatusCode: http.StatusBadRequest, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid AppEUI. Nack", + Payload: `{"app_url":"url"}`, + ContentType: "text/plain", + Method: "PUT", + AppEUI: "0000000011223344", + ShouldAck: false, + + WantContent: string(errors.Structural), + WantStatusCode: http.StatusBadRequest, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid AppEUI. Nack", + Payload: `{"app_url":"url"}`, + ContentType: "application/json", + Method: "POST", + AppEUI: "0000000011223344", + ShouldAck: false, + + WantContent: string(errors.Structural), + WantStatusCode: http.StatusMethodNotAllowed, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid AppEUI. Nack", + Payload: `{"app_url":"url"}`, + ContentType: "application/json", + Method: "PUT", + AppEUI: "12345678", + ShouldAck: false, + + WantContent: string(errors.Structural), + WantStatusCode: http.StatusBadRequest, + WantRegistration: nil, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Valid AppEUI. Nack", + Payload: `{"app_url":"url"}`, + ContentType: "application/json", + Method: "PUT", + AppEUI: "0000000001020304", + ShouldAck: false, + + WantContent: string(errors.Structural), + WantStatusCode: http.StatusConflict, + WantRegistration: applicationsRegistration{ + recipient: NewRecipient("url", "PUT"), + appEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + }, + WantError: nil, + }, + { + Desc: "Valid Payload. Valid ContentType. Valid Method. Valid AppEUI. Ack", + Payload: `{"app_url":"url"}`, + ContentType: "application/json", + Method: "PUT", + AppEUI: "0000000001020304", + ShouldAck: true, + + WantContent: "", + WantStatusCode: http.StatusAccepted, + WantRegistration: applicationsRegistration{ + recipient: NewRecipient("url", "PUT"), + appEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), + }, + WantError: nil, + }, + } + + var port uint = 4200 + for _, test := range tests { + // Describe + Desc(t, test.Desc) + + // Build + adapter, url := createApplicationsAdapter(t, port) + port++ + client := testClient{} + + // Operate + url = fmt.Sprintf("%s%s", url, test.AppEUI) + chresp := client.Send(test.Payload, url, test.Method, test.ContentType) + registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) + var statusCode int + var content []byte + select { + case resp := <-chresp: + statusCode = resp.StatusCode + content = resp.Content + case <-time.After(time.Millisecond * 100): + } + + // Check + CheckErrors(t, test.WantError, err) + checkStatusCode(t, test.WantStatusCode, statusCode) + checkContent(t, test.WantContent, content) + checkRegistration(t, test.WantRegistration, registration) + } +} diff --git a/core/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go index e2549e127..2791ab894 100644 --- a/core/adapters/http/handlers/helpers_test.go +++ b/core/adapters/http/handlers/helpers_test.go @@ -56,6 +56,17 @@ func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) } +func createApplicationsAdapter(t *testing.T, port uint) (*Adapter, string) { + adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + if err != nil { + panic(err) + } + <-time.After(time.Millisecond * 250) // Let the connection starts + handler := Applications{} + adapter.Bind(handler) + return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) +} + func createCollectAdapter(t *testing.T, port uint) (*Adapter, string) { adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) if err != nil { diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index 68593ed18..5c4e8f004 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -100,6 +100,7 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { if err != nil && err != io.EOF { return pubSubRegistration{}, errors.New(errors.Structural, err) } + defer req.Body.Close() params := &struct { AppEUI string `json:"app_eui"` URL string `json:"app_url"` From 32206f91a0560ed31ecc7517758e19b4fbf0b6f7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 14:22:24 +0100 Subject: [PATCH 0871/2266] [issue#34] Increase test coverage by testing xxxRegistration files --- .../http/handlers/registrations_test.go | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 core/adapters/http/handlers/registrations_test.go diff --git a/core/adapters/http/handlers/registrations_test.go b/core/adapters/http/handlers/registrations_test.go new file mode 100644 index 000000000..937f63397 --- /dev/null +++ b/core/adapters/http/handlers/registrations_test.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/adapters/http" + mocks "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/brocaar/lorawan" +) + +func TestPubSubRegistration(t *testing.T) { + recipient := http.NewRecipient("url", "method") + devEUI := lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) + nwkSKey := lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}) + + reg := pubSubRegistration{ + recipient: recipient, + devEUI: devEUI, + appEUI: appEUI, + nwkSKey: nwkSKey, + } + + mocks.Check(t, recipient, reg.Recipient(), "Recipients") + mocks.Check(t, devEUI, reg.DevEUI(), "DevEUIs") + mocks.Check(t, appEUI, reg.AppEUI(), "AppEUIs") + mocks.Check(t, nwkSKey, reg.NwkSKey(), "NwkSKeys") +} + +func TestApplicationsRegistration(t *testing.T) { + recipient := http.NewRecipient("url", "method") + appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) + + reg := applicationsRegistration{ + recipient: recipient, + appEUI: appEUI, + } + + mocks.Check(t, recipient, reg.Recipient(), "Recipients") + mocks.Check(t, appEUI, reg.AppEUI(), "AppEUIs") +} From 55a96d5e2314ac75280bea67e777e5f07e4d8b73 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 14:23:17 +0100 Subject: [PATCH 0872/2266] [issue#34] Add Applications handler to broker cli --- cmd/broker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/broker.go b/cmd/broker.go index fd41403aa..375445fd1 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -50,6 +50,7 @@ and personalized devices (with their network session keys) with the router. } hdlAdapter.Bind(handlers.Collect{}) hdlAdapter.Bind(handlers.PubSub{}) + hdlAdapter.Bind(handlers.Applications{}) hdlAdapter.Bind(handlers.StatusPage{}) // Instantiate Storage From ed15143c8719e6d0da0aa8de32beca3900f2601c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 17:56:16 +0100 Subject: [PATCH 0873/2266] [handler] Add /packets endpoint to Broker adapter --- cmd/handler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/handler.go b/cmd/handler.go index eaa92d700..b1369748b 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -42,6 +42,7 @@ The default handler is the bridge between The Things Network and applications. if err != nil { ctx.WithError(err).Fatal("Could not start broker adapter") } + brkAdapter.Bind(httpHandlers.Collect{}) brkAdapter.Bind(httpHandlers.StatusPage{}) brkAdapter.Bind(httpHandlers.Healthz{}) From f63aadceba278db2f3480ab9ea71cb5373ce7b5b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 17:56:49 +0100 Subject: [PATCH 0874/2266] [router] Use trailing / for "/packets/" endpoint --- cmd/router.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/router.go b/cmd/router.go index 9ffcf22fd..46b311097 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -48,7 +48,7 @@ the gateway's duty cycle is (almost) full.`, var brokers []core.Recipient brokersStr := strings.Split(viper.GetString("router.brokers"), ",") for i := range brokersStr { - url := fmt.Sprintf("%s/packets", strings.Trim(brokersStr[i], " ")) + url := fmt.Sprintf("%s/packets/", strings.Trim(brokersStr[i], " ")) brokers = append(brokers, http.NewRecipient(url, "POST")) } @@ -70,7 +70,7 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(err).Fatal("Invalid database path") } - db, err = router.NewStorage(dbPath, time.Hour*8) // TODO use cli flag + db, err = router.NewStorage(dbPath, time.Hour*8) if err != nil { ctx.WithError(err).Fatal("Could not create a local storage") } From c59a93c86e3467d0bde44b93697a1d29190df16f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 18:00:18 +0100 Subject: [PATCH 0875/2266] [dutycycle] Start implementing a duty cycle manager --- core/components/router/dutyManager.go | 155 ++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 core/components/router/dutyManager.go diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go new file mode 100644 index 000000000..a91753007 --- /dev/null +++ b/core/components/router/dutyManager.go @@ -0,0 +1,155 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "math" + "regexp" + "strconv" + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + dbutil "github.com/TheThingsNetwork/ttn/utils/storage" +) + +// DutyManager provides an interface to manipulate and compute gateways duty-cycles. +type DutyManager interface { +} + +type dutyManager struct { + sync.Mutex + db dbutil.Interface + CycleLength time.Duration // Duration upon which the duty-cycle is evaluated + MaxDutyCycle map[subBand]float64 // The percentage max duty cycle accepted for each sub-band +} + +// Available sub-bands +const ( + europe1 subBand = iota + europe2 +) + +type subBand byte + +// Available regions for LoRaWAN +const ( + Europe region = iota + US + China +) + +type region byte + +// NewDutyManager constructs a new gateway manager from +func NewDutyManager(cycleLength time.Duration, r region) (DutyManager, error) { + var maxDuty map[subBand]float64 + switch r { + case Europe: + maxDuty = map[subBand]float64{ + europe1: 0.01, + europe2: 0.1, + } + default: + return nil, errors.New(errors.Implementation, "Region not supported") + } + + return &dutyManager{ + CycleLength: cycleLength, + MaxDutyCycle: maxDuty, + }, nil +} + +// GetSubBand returns the subband associated to a given frequency +func GetSubBand(freq float64) (subBand, error) { + if int(freq) == 868 { + return europe1, nil + } + return 0, errors.New(errors.Structural, "Unknown frequency") +} + +// Update update an entry with the corresponding time-on-air +// +// Datr represents a LoRaWAN data-rate indicator of the form SFxxBWyyy, +// where xx C [[7;12]] and yyy C { 125, 250, 500 } +// Codr represents a LoRaWAN code rate indicator fo the form 4/x with x C [[5;8]] +func (m dutyManager) Update(id []byte, freq float64, size uint, datr string, codr string) error { + sub, err := GetSubBand(freq) + if err != nil { + return err + } + key := fmt.Sprintf("%+x", id) + + // Compute the ime-on-air + timeOnAir, err := computeTOA(size, datr, codr) + if err != nil { + return err + } + + // Lookup and update the entry + m.Lock() + defer m.Unlock() + itf, err := m.db.Lookup(key, []byte("entry"), &dutyEntry{}) + if err != nil { + return err + } + entry := itf.([]dutyEntry)[0] + + // If the previous cycle is done, we create a new one + if entry.Until.Before(time.Now()) { + entry.Until = time.Now() + entry.OnAir[sub] = timeOnAir + } else { + entry.OnAir[sub] += timeOnAir + } + + return m.db.Replace(key, []byte("entry"), []dbutil.Entry{&entry}) +} + +func computeTOA(size uint, datr string, codr string) (time.Duration, error) { + // Ensure the datr and codr are correct + var cr float64 + switch codr { + case "4/5": + cr = 4.0 / 5.0 + case "4/6": + cr = 4.0 / 6.0 + case "4/7": + cr = 4.0 / 7.0 + case "4/8": + cr = 4.0 / 8.0 + default: + return 0, errors.New(errors.Structural, "Invalid Codr") + } + + re := regexp.MustCompile("^SF(7|8|9|10|11|12)BW(125|250|500)$") + matches := re.FindStringSubmatch(datr) + + if len(matches) != 3 { + return 0, errors.New(errors.Structural, "Invalid Datr") + } + + // Compute bitrate, Page 10: http://www.semtech.com/images/datasheet/an1200.22.pdf + sf, _ := strconv.ParseFloat(matches[1], 64) + bw, _ := strconv.ParseUint(matches[2], 10, 64) + bitrate := sf * cr * float64(bw) / math.Pow(2, sf) + + return time.Duration(float64(size) / bitrate), nil +} + +type dutyEntry struct { + Until time.Time + OnAir map[subBand]time.Duration +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e dutyEntry) MarshalBinary() ([]byte, error) { + return nil, nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *dutyEntry) UnmarshalBinary(data []byte) error { + return nil +} From 16dbb7a8cf702f02010efef5d98cc1e8fd0965ee Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 3 Mar 2016 18:26:43 +0100 Subject: [PATCH 0876/2266] [dutycycle] Implements lookup method in dutyManager --- core/components/router/dutyManager.go | 47 ++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index a91753007..83aba35fb 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -20,7 +20,7 @@ type DutyManager interface { } type dutyManager struct { - sync.Mutex + sync.RWMutex db dbutil.Interface CycleLength time.Duration // Duration upon which the duty-cycle is evaluated MaxDutyCycle map[subBand]float64 // The percentage max duty cycle accepted for each sub-band @@ -28,8 +28,8 @@ type dutyManager struct { // Available sub-bands const ( - europe1 subBand = iota - europe2 + Europe1 subBand = iota + Europe2 ) type subBand byte @@ -49,8 +49,8 @@ func NewDutyManager(cycleLength time.Duration, r region) (DutyManager, error) { switch r { case Europe: maxDuty = map[subBand]float64{ - europe1: 0.01, - europe2: 0.1, + Europe1: 0.01, + Europe2: 0.1, } default: return nil, errors.New(errors.Implementation, "Region not supported") @@ -65,7 +65,7 @@ func NewDutyManager(cycleLength time.Duration, r region) (DutyManager, error) { // GetSubBand returns the subband associated to a given frequency func GetSubBand(freq float64) (subBand, error) { if int(freq) == 868 { - return europe1, nil + return Europe1, nil } return 0, errors.New(errors.Structural, "Unknown frequency") } @@ -75,7 +75,7 @@ func GetSubBand(freq float64) (subBand, error) { // Datr represents a LoRaWAN data-rate indicator of the form SFxxBWyyy, // where xx C [[7;12]] and yyy C { 125, 250, 500 } // Codr represents a LoRaWAN code rate indicator fo the form 4/x with x C [[5;8]] -func (m dutyManager) Update(id []byte, freq float64, size uint, datr string, codr string) error { +func (m *dutyManager) Update(id []byte, freq float64, size uint, datr string, codr string) error { sub, err := GetSubBand(freq) if err != nil { return err @@ -108,6 +108,37 @@ func (m dutyManager) Update(id []byte, freq float64, size uint, datr string, cod return m.db.Replace(key, []byte("entry"), []dbutil.Entry{&entry}) } +// Lookup returns the current bandwidth usages for a set of subband +// +// The usage is an integer between 0 and 100 (maybe above 100 if the usage exceed the limitation). +// The closest to 0, the more usage we have +func (m *dutyManager) Lookup(id []byte) (map[subBand]uint, error) { + m.RLock() + defer m.RUnlock() + + // Lookup the entry + itf, err := m.db.Lookup(fmt.Sprintf("%+x", id), []byte("entry"), &dutyEntry{}) + if err != nil { + return nil, err + } + entry := itf.([]dutyEntry)[0] + + // For each sub-band, compute the remaining time-on-air available + cycles := make(map[subBand]uint) + if entry.Until.After(time.Now()) { + for s, toa := range entry.OnAir { + // The actual duty cycle + dutyCycle := float64(toa.Nanoseconds()) / float64(m.CycleLength.Nanoseconds()) + // Now, how full are we comparing to the limitation, in percent + cycles[s] = uint(100 * dutyCycle / m.MaxDutyCycle[s]) + } + } + + return cycles, nil +} + +// computeTOA computes the time-on-air given a size in byte, a LoRaWAN datr identifier, an LoRa Codr +// identifier. func computeTOA(size uint, datr string, codr string) (time.Duration, error) { // Ensure the datr and codr are correct var cr float64 @@ -136,7 +167,7 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { bw, _ := strconv.ParseUint(matches[2], 10, 64) bitrate := sf * cr * float64(bw) / math.Pow(2, sf) - return time.Duration(float64(size) / bitrate), nil + return time.Duration(float64(size*8) / bitrate), nil } type dutyEntry struct { From f5b70d2511555d0f30f0bc518683c012cc63d710 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 09:08:20 +0100 Subject: [PATCH 0877/2266] [simulator] Print node info before starting [Skip CI] --- simulators/gateway/imitator.go | 1 + 1 file changed, 1 insertion(+) diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go index 984ea76cc..32727a329 100644 --- a/simulators/gateway/imitator.go +++ b/simulators/gateway/imitator.go @@ -101,6 +101,7 @@ func MockRandomly(nodes []node.LiveNode, ctx log.Interface, routers ...string) { messages := make(chan string) for _, n := range nodes { + ctx.Infof("Created node: %s", n.(fmt.Stringer).String()) go n.Start(messages) } From d4074def4e7cd200fe8927bcb948b78b86880bd7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 09:17:37 +0100 Subject: [PATCH 0878/2266] Update HTTP adapter to include bind IP in NewAdapter --- cmd/broker.go | 6 ++++-- cmd/handler.go | 3 ++- cmd/router.go | 3 ++- core/adapters/http/handlers/helpers_test.go | 9 ++++++--- core/adapters/http/http.go | 10 +++++----- core/adapters/http/http_test.go | 2 +- 6 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 375445fd1..21a68353b 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -37,14 +37,16 @@ and personalized devices (with their network session keys) with the router. ctx.Info("Starting") // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.routers-port")), nil, ctx.WithField("adapter", "router-http")) + rtrNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("broker.routers-port")) + rtrAdapter, err := http.NewAdapter(rtrNet, nil, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } rtrAdapter.Bind(handlers.Collect{}) rtrAdapter.Bind(handlers.Healthz{}) - hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.handlers-port")), nil, ctx.WithField("adapter", "handler-http")) + hdlNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("broker.handlers-port")) + hdlAdapter, err := http.NewAdapter(hdlNet, nil, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } diff --git a/cmd/handler.go b/cmd/handler.go index b1369748b..ed3e8d88c 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -38,7 +38,8 @@ The default handler is the bridge between The Things Network and applications. ctx.Info("Starting") // ----- Start Adapters - brkAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.brokers-port")), nil, ctx.WithField("adapter", "broker-adapter")) + brkNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("handler.brokers-port")) + brkAdapter, err := http.NewAdapter(brkNet, nil, ctx.WithField("adapter", "broker-adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start broker adapter") } diff --git a/cmd/router.go b/cmd/router.go index 46b311097..deef12166 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -52,7 +52,8 @@ the gateway's duty cycle is (almost) full.`, brokers = append(brokers, http.NewRecipient(url, "POST")) } - brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.brokers-port")), brokers, ctx.WithField("adapter", "broker-http")) + brkNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("router.brokers-port")) + brkAdapter, err := http.NewAdapter(brkNet, brokers, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } diff --git a/core/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go index 2791ab894..87a4d78f1 100644 --- a/core/adapters/http/handlers/helpers_test.go +++ b/core/adapters/http/handlers/helpers_test.go @@ -46,7 +46,8 @@ func (p testPacket) DevEUI() lorawan.EUI64 { // ----- BUILD utilities func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { - adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + net := fmt.Sprintf("0.0.0.0:%d", port) + adapter, err := NewAdapter(net, nil, GetLogger(t, "Adapter")) if err != nil { panic(err) } @@ -57,7 +58,8 @@ func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { } func createApplicationsAdapter(t *testing.T, port uint) (*Adapter, string) { - adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + net := fmt.Sprintf("0.0.0.0:%d", port) + adapter, err := NewAdapter(net, nil, GetLogger(t, "Adapter")) if err != nil { panic(err) } @@ -68,7 +70,8 @@ func createApplicationsAdapter(t *testing.T, port uint) (*Adapter, string) { } func createCollectAdapter(t *testing.T, port uint) (*Adapter, string) { - adapter, err := NewAdapter(port, nil, GetLogger(t, "Adapter")) + net := fmt.Sprintf("0.0.0.0:%d", port) + adapter, err := NewAdapter(net, nil, GetLogger(t, "Adapter")) if err != nil { panic(err) } diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 4cd813ecd..69f0bddb4 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -53,7 +53,7 @@ type RegReq struct { } // NewAdapter constructs and allocates a new http adapter -func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { +func NewAdapter(net string, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { a := Adapter{ Client: http.Client{Timeout: 6 * time.Second}, ctx: ctx, @@ -63,7 +63,7 @@ func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Ada serveMux: http.NewServeMux(), } - go a.listenRequests(port) + go a.listenRequests(net) return &a, nil } @@ -229,12 +229,12 @@ func (a *Adapter) Bind(h Handler) { } // listenRequests handles incoming registration request sent through http to the adapter -func (a *Adapter) listenRequests(port uint) { +func (a *Adapter) listenRequests(net string) { server := http.Server{ - Addr: fmt.Sprintf("0.0.0.0:%d", port), + Addr: net, Handler: a.serveMux, } - a.ctx.WithField("port", port).Info("Starting Server") + a.ctx.WithField("bind", net).Info("Starting Server") err := server.ListenAndServe() a.ctx.WithError(err).Warn("HTTP connection lost") } diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index 283666e2b..b1957cbbe 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -136,7 +136,7 @@ func TestSend(t *testing.T) { ctx := GetLogger(t, "Adapter") // Build - adapter, err := NewAdapter(3115, toHTTPRecipient(recipients), ctx) + adapter, err := NewAdapter("0.0.0.0:3115", toHTTPRecipient(recipients), ctx) if err != nil { panic(err) } From 3648f4397dd5d3de09e1bf9b4b272a92377b2d99 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 09:23:35 +0100 Subject: [PATCH 0879/2266] Update UDP adapter to include bind IP in NewAdapter --- cmd/router.go | 3 ++- core/adapters/udp/handlers/build_utilities_test.go | 3 ++- core/adapters/udp/udp.go | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/cmd/router.go b/cmd/router.go index deef12166..379440ded 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -39,7 +39,8 @@ the gateway's duty cycle is (almost) full.`, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwAdapter, err := udp.NewAdapter(uint(viper.GetInt("router.gateways-port")), ctx.WithField("adapter", "gateway-semtech")) + gtwNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("router.gateways-port")) + gtwAdapter, err := udp.NewAdapter(gtwNet, ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } diff --git a/core/adapters/udp/handlers/build_utilities_test.go b/core/adapters/udp/handlers/build_utilities_test.go index f82b8d150..ba91eb9d4 100644 --- a/core/adapters/udp/handlers/build_utilities_test.go +++ b/core/adapters/udp/handlers/build_utilities_test.go @@ -78,7 +78,8 @@ func genAdapter(t *testing.T, port uint) (*udp.Adapter, chan interface{}) { // Logging ctx := GetLogger(t, "Adapter") - adapter, err := udp.NewAdapter(port, ctx) + net := fmt.Sprintf("0.0.0.0:%d", port) + adapter, err := udp.NewAdapter(net, ctx) if err != nil { panic(err) } diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 99f5d824b..6502e8d62 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -42,7 +42,7 @@ type MsgReq struct { type MsgRes []byte // The actual message // NewAdapter constructs and allocates a new udp adapter -func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { +func NewAdapter(bindNet string, ctx log.Interface) (*Adapter, error) { a := Adapter{ ctx: ctx, conn: make(chan MsgUDP), @@ -52,11 +52,11 @@ func NewAdapter(port uint, ctx log.Interface) (*Adapter, error) { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - a.ctx.WithField("port", port).Info("Starting Server") + addr, err := net.ResolveUDPAddr("udp", bindNet) + a.ctx.WithField("bind", bindNet).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid port %v", port)) + return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid bind address %v", bindNet)) } go a.monitorConnection(udpConn) From 7719aa435859cfc474403df5af34fde4a0ecfe1a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 19:50:01 +0100 Subject: [PATCH 0880/2266] Add trailing / to /healthz endpoint --- core/adapters/http/handlers/healthz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/http/handlers/healthz.go b/core/adapters/http/handlers/healthz.go index fe26e7978..f142209a7 100644 --- a/core/adapters/http/handlers/healthz.go +++ b/core/adapters/http/handlers/healthz.go @@ -20,7 +20,7 @@ type Healthz struct{} // URL implements the http.Handler interface func (p Healthz) URL() string { - return "/healthz" + return "/healthz/" } // Handle implements the http.Handler interface From 426d800ae836057b075bd1aae55afef14536b5ac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 19:54:27 +0100 Subject: [PATCH 0881/2266] [statuspage] Remove IP check This is not the handler's job --- core/adapters/http/handlers/statuspage.go | 18 +----------------- core/adapters/http/handlers/statuspage_test.go | 7 ------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go index 3f7e356b1..5d04a58bc 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -5,8 +5,6 @@ package handlers import ( "encoding/json" - "fmt" - "net" "net/http" "strings" @@ -33,21 +31,7 @@ func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg cha // Check the http method if req.Method != "GET" { w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Unreckognized HTTP method. Please use [GET] to transfer a packet")) - return - } - - remoteHost, _, err := net.SplitHostPort(req.RemoteAddr) - if err != nil { - //The HTTP server did not set RemoteAddr to IP:port, which would be very very strange. - w.WriteHeader(http.StatusInternalServerError) - return - } - - remoteIP := net.ParseIP(remoteHost) - if remoteIP == nil || !remoteIP.IsLoopback() { - w.WriteHeader(http.StatusForbidden) - w.Write([]byte(fmt.Sprintf("Status is only available from the local host, not from %s", remoteIP))) + w.Write([]byte("Use [GET] to request the status")) return } diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go index e3f5c3bf2..e267deb49 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/handlers/statuspage_test.go @@ -33,13 +33,6 @@ func TestStatusPageHandler(t *testing.T) { s.Handle(&rw1, make(chan<- PktReq), make(chan<- RegReq), r1) a.So(rw1.TheStatus, assertions.ShouldEqual, 405) - // Only Localhost allowed - r2, _ := http.NewRequest("GET", "/status/", nil) - r2.RemoteAddr = "12.34.56.78:12345" - rw2 := NewResponseWriter() - s.Handle(&rw2, make(chan<- PktReq), make(chan<- RegReq), r2) - a.So(rw2.TheStatus, assertions.ShouldEqual, 403) - // Initially Empty r3, _ := http.NewRequest("GET", "/status/", nil) r3.RemoteAddr = "127.0.0.1:12345" From ad2f42aed8060a11f52710af4d950249498027fe Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 19:59:30 +0100 Subject: [PATCH 0882/2266] [cli] Rename ports to create more consistency --- cmd/broker.go | 21 ++++++++++----------- cmd/handler.go | 22 +++++++++++----------- cmd/router.go | 23 +++++++++++------------ 3 files changed, 32 insertions(+), 34 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 21a68353b..e37058093 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -29,24 +29,22 @@ and personalized devices (with their network session keys) with the router. PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), - "routers-port": viper.GetInt("broker.routers-port"), - "handlers-port": viper.GetInt("broker.handlers-port"), + "uplink-port": viper.GetInt("broker.uplink-port"), + "downlink-port": viper.GetInt("broker.downlink-port"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") // Instantiate all components - rtrNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("broker.routers-port")) - rtrAdapter, err := http.NewAdapter(rtrNet, nil, ctx.WithField("adapter", "router-http")) + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.uplink-port")), nil, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } rtrAdapter.Bind(handlers.Collect{}) rtrAdapter.Bind(handlers.Healthz{}) - hdlNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("broker.handlers-port")) - hdlAdapter, err := http.NewAdapter(hdlNet, nil, ctx.WithField("adapter", "handler-http")) + hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.downlink-port")), nil, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } @@ -122,10 +120,11 @@ func init() { RootCmd.AddCommand(brokerCmd) brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") - brokerCmd.Flags().Int("routers-port", 1690, "TCP port for connections from routers") - brokerCmd.Flags().Int("handlers-port", 1790, "TCP port for connections from handlers") - viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) - viper.BindPFlag("broker.routers-port", brokerCmd.Flags().Lookup("routers-port")) - viper.BindPFlag("broker.handlers-port", brokerCmd.Flags().Lookup("handlers-port")) + + brokerCmd.Flags().Int("uplink-port", 1881, "The UDP port for the uplink") + viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) + + brokerCmd.Flags().Int("downlink-port", 1781, "The port for the downlink") + viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index ed3e8d88c..589308d38 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -30,16 +30,15 @@ The default handler is the bridge between The Things Network and applications. ctx.WithFields(log.Fields{ "devicesDatabase": viper.GetString("handler.dev-database"), "packetsDatabase": viper.GetString("handler.pkt-database"), - "brokers-port": viper.GetInt("handler.brokers-port"), - "apps-client": viper.GetString("handler.apps-client"), + "uplink-port": viper.GetInt("handler.uplink-port"), + "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") // ----- Start Adapters - brkNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("handler.brokers-port")) - brkAdapter, err := http.NewAdapter(brkNet, nil, ctx.WithField("adapter", "broker-adapter")) + brkAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.uplink-port")), nil, ctx.WithField("adapter", "broker-adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start broker adapter") } @@ -47,9 +46,9 @@ The default handler is the bridge between The Things Network and applications. brkAdapter.Bind(httpHandlers.StatusPage{}) brkAdapter.Bind(httpHandlers.Healthz{}) - mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.apps-client"), mqtt.TCP) + mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) if err != nil { - ctx.WithError(err).Fatal("Could not start mqtt client") + ctx.WithError(err).Fatal("Could not start MQTT client") } appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) @@ -166,11 +165,12 @@ func init() { handlerCmd.Flags().String("dev-database", "boltdb:/tmp/ttn_handler_devices.db", "Devices Database connection") handlerCmd.Flags().String("pkt-database", "boltdb:/tmp/ttn_handler_packets.db", "Packets Database connection") - handlerCmd.Flags().Int("brokers-port", 1691, "TCP port for connections from brokers") - handlerCmd.Flags().String("apps-client", "localhost:1883", "Uri of the applications mqtt") - viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) - viper.BindPFlag("handler.brokers-port", handlerCmd.Flags().Lookup("brokers-port")) - viper.BindPFlag("handler.apps-client", handlerCmd.Flags().Lookup("apps-client")) + + handlerCmd.Flags().Int("uplink-port", 1882, "The UDP port for the uplink") + viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) + + handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) } diff --git a/cmd/router.go b/cmd/router.go index 379440ded..79cc8e9f1 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -31,16 +31,15 @@ the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ "database": viper.GetString("router.database"), - "gateways-port": viper.GetInt("router.gateways-port"), + "uplink-port": viper.GetInt("router.uplink-port"), + "downlink-port": viper.GetInt("router.downlink-port"), "brokers": viper.GetString("router.brokers"), - "brokers-port": viper.GetInt("router.brokers-port"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("router.gateways-port")) - gtwAdapter, err := udp.NewAdapter(gtwNet, ctx.WithField("adapter", "gateway-semtech")) + gtwAdapter, err := udp.NewAdapter(uint(viper.GetInt("router.uplink-port")), ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } @@ -53,8 +52,7 @@ the gateway's duty cycle is (almost) full.`, brokers = append(brokers, http.NewRecipient(url, "POST")) } - brkNet := fmt.Sprintf("0.0.0.0:%d", viper.GetInt("router.brokers-port")) - brkAdapter, err := http.NewAdapter(brkNet, brokers, ctx.WithField("adapter", "broker-http")) + brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.downlink-port")), brokers, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } @@ -126,12 +124,13 @@ func init() { RootCmd.AddCommand(routerCmd) routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") - routerCmd.Flags().Int("gateways-port", 1700, "UDP port for connections from gateways") - routerCmd.Flags().String("brokers", "localhost:1690", "Comma-separated list of brokers") - routerCmd.Flags().Int("brokers-port", 1780, "TCP port for connections from brokers") - viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) - viper.BindPFlag("router.gateways-port", routerCmd.Flags().Lookup("gateways-port")) + + routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") + viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) + + routerCmd.Flags().Int("downlink-port", 1780, "The port for the downlink") + viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) + routerCmd.Flags().String("brokers", ":1881", "Comma-separated list of brokers") viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) - viper.BindPFlag("router.brokers-port", routerCmd.Flags().Lookup("brokers-port")) } From 30e9a0b68c1f5686265bc40f61c5ee0b3ee8a119 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 20:02:04 +0100 Subject: [PATCH 0883/2266] [statuspage] Move status endpoints to separate adapter Status pages can be switched off by passing 0 for port. Resolves #56 --- cmd/broker.go | 13 +++++++++++-- cmd/handler.go | 13 +++++++++++-- cmd/router.go | 14 ++++++++++++-- 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index e37058093..34a142e12 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -42,7 +42,6 @@ and personalized devices (with their network session keys) with the router. ctx.WithError(err).Fatal("Could not start Routers Adapter") } rtrAdapter.Bind(handlers.Collect{}) - rtrAdapter.Bind(handlers.Healthz{}) hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.downlink-port")), nil, ctx.WithField("adapter", "handler-http")) if err != nil { @@ -51,8 +50,15 @@ and personalized devices (with their network session keys) with the router. hdlAdapter.Bind(handlers.Collect{}) hdlAdapter.Bind(handlers.PubSub{}) hdlAdapter.Bind(handlers.Applications{}) - hdlAdapter.Bind(handlers.StatusPage{}) + if viper.GetInt("broker.status-port") > 0 { + statusAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.status-port")), nil, ctx.WithField("adapter", "status-http")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Status Adapter") + } + statusAdapter.Bind(handlers.StatusPage{}) + statusAdapter.Bind(handlers.Healthz{}) + } // Instantiate Storage var db broker.Storage @@ -122,6 +128,9 @@ func init() { brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) + brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") + viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) + brokerCmd.Flags().Int("uplink-port", 1881, "The UDP port for the uplink") viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) diff --git a/cmd/handler.go b/cmd/handler.go index 589308d38..b88cbb1d3 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -43,8 +43,6 @@ The default handler is the bridge between The Things Network and applications. ctx.WithError(err).Fatal("Could not start broker adapter") } brkAdapter.Bind(httpHandlers.Collect{}) - brkAdapter.Bind(httpHandlers.StatusPage{}) - brkAdapter.Bind(httpHandlers.Healthz{}) mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) if err != nil { @@ -53,6 +51,14 @@ The default handler is the bridge between The Things Network and applications. appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) + if viper.GetInt("handler.status-port") > 0 { + statusAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.status-port")), nil, ctx.WithField("adapter", "status-http")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Status Adapter") + } + statusAdapter.Bind(httpHandlers.StatusPage{}) + statusAdapter.Bind(httpHandlers.Healthz{}) + } // Instantiate in-memory devices storage var devicesDB handler.DevStorage @@ -168,6 +174,9 @@ func init() { viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) + handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") + viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) + handlerCmd.Flags().Int("uplink-port", 1882, "The UDP port for the uplink") viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) diff --git a/cmd/router.go b/cmd/router.go index 79cc8e9f1..b2f2e9d2d 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -56,8 +56,15 @@ the gateway's duty cycle is (almost) full.`, if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - brkAdapter.Bind(httpHandlers.StatusPage{}) - brkAdapter.Bind(httpHandlers.Healthz{}) + + if viper.GetInt("router.status-port") > 0 { + statusAdapter, err := http.NewAdapter(uint(viper.GetInt("router.status-port")), nil, ctx.WithField("adapter", "status-http")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Status Adapter") + } + statusAdapter.Bind(httpHandlers.StatusPage{}) + statusAdapter.Bind(httpHandlers.Healthz{}) + } var db router.Storage @@ -126,6 +133,9 @@ func init() { routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) + routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") + viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) + routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) From 94de77969318542b4acc1d4d1069ec4634f5d4e2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 20:50:29 +0100 Subject: [PATCH 0884/2266] Fix test for 05e45fe7 --- core/adapters/http/handlers/healthz_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/http/handlers/healthz_test.go b/core/adapters/http/handlers/healthz_test.go index 06b2b4db5..280576ad3 100644 --- a/core/adapters/http/handlers/healthz_test.go +++ b/core/adapters/http/handlers/healthz_test.go @@ -17,7 +17,7 @@ func TestHealthzURL(t *testing.T) { h := Healthz{} - a.So(h.URL(), assertions.ShouldEqual, "/healthz") + a.So(h.URL(), assertions.ShouldEqual, "/healthz/") } func TestHealthzHandle(t *testing.T) { From b1f4696b3290404ad9e1bb6362f8c206c379c18c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 09:34:33 +0100 Subject: [PATCH 0885/2266] Use CLI flag for bind address --- cmd/broker.go | 26 +++++++++++++++++++++----- cmd/handler.go | 19 ++++++++++++++++--- cmd/router.go | 28 +++++++++++++++++++++++----- 3 files changed, 60 insertions(+), 13 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 34a142e12..4a6d0f8e9 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -27,23 +27,32 @@ receives from routers. This means that handlers have to register applications and personalized devices (with their network session keys) with the router. `, PreRun: func(cmd *cobra.Command, args []string) { + var statusServer string + if viper.GetInt("broker.status-port") > 0 { + statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) + } else { + statusServer = "disabled" + } ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), - "uplink-port": viper.GetInt("broker.uplink-port"), - "downlink-port": viper.GetInt("broker.downlink-port"), + "status-server": statusServer, + "uplink": fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")), + "downlink": fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.uplink-port")), nil, ctx.WithField("adapter", "router-http")) + rtrNet := fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")) + rtrAdapter, err := http.NewAdapter(rtrNet, nil, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } rtrAdapter.Bind(handlers.Collect{}) - hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.downlink-port")), nil, ctx.WithField("adapter", "handler-http")) + hdlNet := fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")) + hdlAdapter, err := http.NewAdapter(hdlNet, nil, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } @@ -52,7 +61,8 @@ and personalized devices (with their network session keys) with the router. hdlAdapter.Bind(handlers.Applications{}) if viper.GetInt("broker.status-port") > 0 { - statusAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.status-port")), nil, ctx.WithField("adapter", "status-http")) + statusNet := fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) + statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Status Adapter") } @@ -128,12 +138,18 @@ func init() { brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) + brokerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") + viper.BindPFlag("broker.status-bind-address", brokerCmd.Flags().Lookup("status-bind-address")) viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) + brokerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from routers") brokerCmd.Flags().Int("uplink-port", 1881, "The UDP port for the uplink") + viper.BindPFlag("broker.uplink-bind-address", brokerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) + brokerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from brokers") brokerCmd.Flags().Int("downlink-port", 1781, "The port for the downlink") + viper.BindPFlag("broker.downlink-bind-address", brokerCmd.Flags().Lookup("downlink-bind-address")) viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index b88cbb1d3..8c89e70ab 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -27,10 +27,17 @@ var handlerCmd = &cobra.Command{ The default handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { + var statusServer string + if viper.GetInt("handler.status-port") > 0 { + statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) + } else { + statusServer = "disabled" + } ctx.WithFields(log.Fields{ "devicesDatabase": viper.GetString("handler.dev-database"), "packetsDatabase": viper.GetString("handler.pkt-database"), - "uplink-port": viper.GetInt("handler.uplink-port"), + "status-server": statusServer, + "uplink": fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")), "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") }, @@ -38,7 +45,8 @@ The default handler is the bridge between The Things Network and applications. ctx.Info("Starting") // ----- Start Adapters - brkAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.uplink-port")), nil, ctx.WithField("adapter", "broker-adapter")) + brkNet := fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")) + brkAdapter, err := http.NewAdapter(brkNet, nil, ctx.WithField("adapter", "broker-adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start broker adapter") } @@ -52,7 +60,8 @@ The default handler is the bridge between The Things Network and applications. appAdapter.Bind(mqttHandlers.Activation{}) if viper.GetInt("handler.status-port") > 0 { - statusAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.status-port")), nil, ctx.WithField("adapter", "status-http")) + statusNet := fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) + statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Status Adapter") } @@ -174,10 +183,14 @@ func init() { viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) + handlerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") + viper.BindPFlag("handler.status-bind-address", handlerCmd.Flags().Lookup("status-bind-address")) viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) + handlerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from brokers") handlerCmd.Flags().Int("uplink-port", 1882, "The UDP port for the uplink") + viper.BindPFlag("handler.uplink-bind-address", handlerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") diff --git a/cmd/router.go b/cmd/router.go index b2f2e9d2d..07357cc41 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -29,17 +29,26 @@ or more brokers. The router is also responsible for monitoring gateways, collecting statistics from gateways and for enforcing TTN's fair use policy when the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { + var statusServer string + if viper.GetInt("router.status-port") > 0 { + statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) + } else { + statusServer = "disabled" + } + ctx.WithFields(log.Fields{ "database": viper.GetString("router.database"), - "uplink-port": viper.GetInt("router.uplink-port"), - "downlink-port": viper.GetInt("router.downlink-port"), + "status-server": statusServer, + "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")), + "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")), "brokers": viper.GetString("router.brokers"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwAdapter, err := udp.NewAdapter(uint(viper.GetInt("router.uplink-port")), ctx.WithField("adapter", "gateway-semtech")) + gtwNet := fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")) + gtwAdapter, err := udp.NewAdapter(gtwNet, ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } @@ -52,13 +61,15 @@ the gateway's duty cycle is (almost) full.`, brokers = append(brokers, http.NewRecipient(url, "POST")) } - brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.downlink-port")), brokers, ctx.WithField("adapter", "broker-http")) + brkNet := fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")) + brkAdapter, err := http.NewAdapter(brkNet, brokers, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } if viper.GetInt("router.status-port") > 0 { - statusAdapter, err := http.NewAdapter(uint(viper.GetInt("router.status-port")), nil, ctx.WithField("adapter", "status-http")) + statusNet := fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) + statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Status Adapter") } @@ -133,14 +144,21 @@ func init() { routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) + routerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") + viper.BindPFlag("router.status-bind-address", routerCmd.Flags().Lookup("status-bind-address")) viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) + routerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from gateways") routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") + viper.BindPFlag("router.uplink-bind-address", routerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) + routerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from routers") routerCmd.Flags().Int("downlink-port", 1780, "The port for the downlink") + viper.BindPFlag("router.downlink-bind-address", routerCmd.Flags().Lookup("downlink-bind-address")) viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) + routerCmd.Flags().String("brokers", ":1881", "Comma-separated list of brokers") viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) } From 39a2e97671d73037ffa16caf9fe94ff6e4e358f4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Mar 2016 19:59:30 +0100 Subject: [PATCH 0886/2266] [cli] Rename ports to create more consistency --- cmd/broker.go | 37 ++++++------------------------------- cmd/handler.go | 30 ++++-------------------------- cmd/router.go | 40 ++++++---------------------------------- 3 files changed, 16 insertions(+), 91 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 4a6d0f8e9..e37058093 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -27,48 +27,32 @@ receives from routers. This means that handlers have to register applications and personalized devices (with their network session keys) with the router. `, PreRun: func(cmd *cobra.Command, args []string) { - var statusServer string - if viper.GetInt("broker.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) - } else { - statusServer = "disabled" - } ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), - "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")), - "downlink": fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")), + "uplink-port": viper.GetInt("broker.uplink-port"), + "downlink-port": viper.GetInt("broker.downlink-port"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") // Instantiate all components - rtrNet := fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")) - rtrAdapter, err := http.NewAdapter(rtrNet, nil, ctx.WithField("adapter", "router-http")) + rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.uplink-port")), nil, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } rtrAdapter.Bind(handlers.Collect{}) + rtrAdapter.Bind(handlers.Healthz{}) - hdlNet := fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")) - hdlAdapter, err := http.NewAdapter(hdlNet, nil, ctx.WithField("adapter", "handler-http")) + hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.downlink-port")), nil, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } hdlAdapter.Bind(handlers.Collect{}) hdlAdapter.Bind(handlers.PubSub{}) hdlAdapter.Bind(handlers.Applications{}) + hdlAdapter.Bind(handlers.StatusPage{}) - if viper.GetInt("broker.status-port") > 0 { - statusNet := fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) - statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Status Adapter") - } - statusAdapter.Bind(handlers.StatusPage{}) - statusAdapter.Bind(handlers.Healthz{}) - } // Instantiate Storage var db broker.Storage @@ -138,18 +122,9 @@ func init() { brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) - brokerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") - brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") - viper.BindPFlag("broker.status-bind-address", brokerCmd.Flags().Lookup("status-bind-address")) - viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) - - brokerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from routers") brokerCmd.Flags().Int("uplink-port", 1881, "The UDP port for the uplink") - viper.BindPFlag("broker.uplink-bind-address", brokerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) - brokerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from brokers") brokerCmd.Flags().Int("downlink-port", 1781, "The port for the downlink") - viper.BindPFlag("broker.downlink-bind-address", brokerCmd.Flags().Lookup("downlink-bind-address")) viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index 8c89e70ab..589308d38 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -27,17 +27,10 @@ var handlerCmd = &cobra.Command{ The default handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { - var statusServer string - if viper.GetInt("handler.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) - } else { - statusServer = "disabled" - } ctx.WithFields(log.Fields{ "devicesDatabase": viper.GetString("handler.dev-database"), "packetsDatabase": viper.GetString("handler.pkt-database"), - "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")), + "uplink-port": viper.GetInt("handler.uplink-port"), "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") }, @@ -45,12 +38,13 @@ The default handler is the bridge between The Things Network and applications. ctx.Info("Starting") // ----- Start Adapters - brkNet := fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")) - brkAdapter, err := http.NewAdapter(brkNet, nil, ctx.WithField("adapter", "broker-adapter")) + brkAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.uplink-port")), nil, ctx.WithField("adapter", "broker-adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start broker adapter") } brkAdapter.Bind(httpHandlers.Collect{}) + brkAdapter.Bind(httpHandlers.StatusPage{}) + brkAdapter.Bind(httpHandlers.Healthz{}) mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) if err != nil { @@ -59,15 +53,6 @@ The default handler is the bridge between The Things Network and applications. appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) - if viper.GetInt("handler.status-port") > 0 { - statusNet := fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) - statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Status Adapter") - } - statusAdapter.Bind(httpHandlers.StatusPage{}) - statusAdapter.Bind(httpHandlers.Healthz{}) - } // Instantiate in-memory devices storage var devicesDB handler.DevStorage @@ -183,14 +168,7 @@ func init() { viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) - handlerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") - handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") - viper.BindPFlag("handler.status-bind-address", handlerCmd.Flags().Lookup("status-bind-address")) - viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) - - handlerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from brokers") handlerCmd.Flags().Int("uplink-port", 1882, "The UDP port for the uplink") - viper.BindPFlag("handler.uplink-bind-address", handlerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") diff --git a/cmd/router.go b/cmd/router.go index 07357cc41..79cc8e9f1 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -29,26 +29,17 @@ or more brokers. The router is also responsible for monitoring gateways, collecting statistics from gateways and for enforcing TTN's fair use policy when the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { - var statusServer string - if viper.GetInt("router.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) - } else { - statusServer = "disabled" - } - ctx.WithFields(log.Fields{ "database": viper.GetString("router.database"), - "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")), - "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")), + "uplink-port": viper.GetInt("router.uplink-port"), + "downlink-port": viper.GetInt("router.downlink-port"), "brokers": viper.GetString("router.brokers"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwNet := fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")) - gtwAdapter, err := udp.NewAdapter(gtwNet, ctx.WithField("adapter", "gateway-semtech")) + gtwAdapter, err := udp.NewAdapter(uint(viper.GetInt("router.uplink-port")), ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } @@ -61,21 +52,12 @@ the gateway's duty cycle is (almost) full.`, brokers = append(brokers, http.NewRecipient(url, "POST")) } - brkNet := fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")) - brkAdapter, err := http.NewAdapter(brkNet, brokers, ctx.WithField("adapter", "broker-http")) + brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.downlink-port")), brokers, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - - if viper.GetInt("router.status-port") > 0 { - statusNet := fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) - statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Status Adapter") - } - statusAdapter.Bind(httpHandlers.StatusPage{}) - statusAdapter.Bind(httpHandlers.Healthz{}) - } + brkAdapter.Bind(httpHandlers.StatusPage{}) + brkAdapter.Bind(httpHandlers.Healthz{}) var db router.Storage @@ -144,21 +126,11 @@ func init() { routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) - routerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") - routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") - viper.BindPFlag("router.status-bind-address", routerCmd.Flags().Lookup("status-bind-address")) - viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) - - routerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from gateways") routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") - viper.BindPFlag("router.uplink-bind-address", routerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) - routerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from routers") routerCmd.Flags().Int("downlink-port", 1780, "The port for the downlink") - viper.BindPFlag("router.downlink-bind-address", routerCmd.Flags().Lookup("downlink-bind-address")) viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) - routerCmd.Flags().String("brokers", ":1881", "Comma-separated list of brokers") viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) } From 7787d0ca83f81d16277e7f47b058ff651f7cff08 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 09:34:33 +0100 Subject: [PATCH 0887/2266] Use CLI flag for bind address --- cmd/broker.go | 37 +++++++++++++++++++++++++++++++------ cmd/handler.go | 30 ++++++++++++++++++++++++++---- cmd/router.go | 40 ++++++++++++++++++++++++++++++++++------ 3 files changed, 91 insertions(+), 16 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index e37058093..4a6d0f8e9 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -27,32 +27,48 @@ receives from routers. This means that handlers have to register applications and personalized devices (with their network session keys) with the router. `, PreRun: func(cmd *cobra.Command, args []string) { + var statusServer string + if viper.GetInt("broker.status-port") > 0 { + statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) + } else { + statusServer = "disabled" + } ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), - "uplink-port": viper.GetInt("broker.uplink-port"), - "downlink-port": viper.GetInt("broker.downlink-port"), + "status-server": statusServer, + "uplink": fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")), + "downlink": fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") // Instantiate all components - rtrAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.uplink-port")), nil, ctx.WithField("adapter", "router-http")) + rtrNet := fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")) + rtrAdapter, err := http.NewAdapter(rtrNet, nil, ctx.WithField("adapter", "router-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Routers Adapter") } rtrAdapter.Bind(handlers.Collect{}) - rtrAdapter.Bind(handlers.Healthz{}) - hdlAdapter, err := http.NewAdapter(uint(viper.GetInt("broker.downlink-port")), nil, ctx.WithField("adapter", "handler-http")) + hdlNet := fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")) + hdlAdapter, err := http.NewAdapter(hdlNet, nil, ctx.WithField("adapter", "handler-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Handlers Adapter") } hdlAdapter.Bind(handlers.Collect{}) hdlAdapter.Bind(handlers.PubSub{}) hdlAdapter.Bind(handlers.Applications{}) - hdlAdapter.Bind(handlers.StatusPage{}) + if viper.GetInt("broker.status-port") > 0 { + statusNet := fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) + statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Status Adapter") + } + statusAdapter.Bind(handlers.StatusPage{}) + statusAdapter.Bind(handlers.Healthz{}) + } // Instantiate Storage var db broker.Storage @@ -122,9 +138,18 @@ func init() { brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) + brokerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") + brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") + viper.BindPFlag("broker.status-bind-address", brokerCmd.Flags().Lookup("status-bind-address")) + viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) + + brokerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from routers") brokerCmd.Flags().Int("uplink-port", 1881, "The UDP port for the uplink") + viper.BindPFlag("broker.uplink-bind-address", brokerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) + brokerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from brokers") brokerCmd.Flags().Int("downlink-port", 1781, "The port for the downlink") + viper.BindPFlag("broker.downlink-bind-address", brokerCmd.Flags().Lookup("downlink-bind-address")) viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index 589308d38..8c89e70ab 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -27,10 +27,17 @@ var handlerCmd = &cobra.Command{ The default handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { + var statusServer string + if viper.GetInt("handler.status-port") > 0 { + statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) + } else { + statusServer = "disabled" + } ctx.WithFields(log.Fields{ "devicesDatabase": viper.GetString("handler.dev-database"), "packetsDatabase": viper.GetString("handler.pkt-database"), - "uplink-port": viper.GetInt("handler.uplink-port"), + "status-server": statusServer, + "uplink": fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")), "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") }, @@ -38,13 +45,12 @@ The default handler is the bridge between The Things Network and applications. ctx.Info("Starting") // ----- Start Adapters - brkAdapter, err := http.NewAdapter(uint(viper.GetInt("handler.uplink-port")), nil, ctx.WithField("adapter", "broker-adapter")) + brkNet := fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")) + brkAdapter, err := http.NewAdapter(brkNet, nil, ctx.WithField("adapter", "broker-adapter")) if err != nil { ctx.WithError(err).Fatal("Could not start broker adapter") } brkAdapter.Bind(httpHandlers.Collect{}) - brkAdapter.Bind(httpHandlers.StatusPage{}) - brkAdapter.Bind(httpHandlers.Healthz{}) mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) if err != nil { @@ -53,6 +59,15 @@ The default handler is the bridge between The Things Network and applications. appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) + if viper.GetInt("handler.status-port") > 0 { + statusNet := fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) + statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Status Adapter") + } + statusAdapter.Bind(httpHandlers.StatusPage{}) + statusAdapter.Bind(httpHandlers.Healthz{}) + } // Instantiate in-memory devices storage var devicesDB handler.DevStorage @@ -168,7 +183,14 @@ func init() { viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) + handlerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") + handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") + viper.BindPFlag("handler.status-bind-address", handlerCmd.Flags().Lookup("status-bind-address")) + viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) + + handlerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from brokers") handlerCmd.Flags().Int("uplink-port", 1882, "The UDP port for the uplink") + viper.BindPFlag("handler.uplink-bind-address", handlerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") diff --git a/cmd/router.go b/cmd/router.go index 79cc8e9f1..07357cc41 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -29,17 +29,26 @@ or more brokers. The router is also responsible for monitoring gateways, collecting statistics from gateways and for enforcing TTN's fair use policy when the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { + var statusServer string + if viper.GetInt("router.status-port") > 0 { + statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) + } else { + statusServer = "disabled" + } + ctx.WithFields(log.Fields{ "database": viper.GetString("router.database"), - "uplink-port": viper.GetInt("router.uplink-port"), - "downlink-port": viper.GetInt("router.downlink-port"), + "status-server": statusServer, + "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")), + "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")), "brokers": viper.GetString("router.brokers"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwAdapter, err := udp.NewAdapter(uint(viper.GetInt("router.uplink-port")), ctx.WithField("adapter", "gateway-semtech")) + gtwNet := fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")) + gtwAdapter, err := udp.NewAdapter(gtwNet, ctx.WithField("adapter", "gateway-semtech")) if err != nil { ctx.WithError(err).Fatal("Could not start Gateway Adapter") } @@ -52,12 +61,21 @@ the gateway's duty cycle is (almost) full.`, brokers = append(brokers, http.NewRecipient(url, "POST")) } - brkAdapter, err := http.NewAdapter(uint(viper.GetInt("router.downlink-port")), brokers, ctx.WithField("adapter", "broker-http")) + brkNet := fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")) + brkAdapter, err := http.NewAdapter(brkNet, brokers, ctx.WithField("adapter", "broker-http")) if err != nil { ctx.WithError(err).Fatal("Could not start Broker Adapter") } - brkAdapter.Bind(httpHandlers.StatusPage{}) - brkAdapter.Bind(httpHandlers.Healthz{}) + + if viper.GetInt("router.status-port") > 0 { + statusNet := fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) + statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) + if err != nil { + ctx.WithError(err).Fatal("Could not start Status Adapter") + } + statusAdapter.Bind(httpHandlers.StatusPage{}) + statusAdapter.Bind(httpHandlers.Healthz{}) + } var db router.Storage @@ -126,11 +144,21 @@ func init() { routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) + routerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") + routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") + viper.BindPFlag("router.status-bind-address", routerCmd.Flags().Lookup("status-bind-address")) + viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) + + routerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from gateways") routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") + viper.BindPFlag("router.uplink-bind-address", routerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) + routerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from routers") routerCmd.Flags().Int("downlink-port", 1780, "The port for the downlink") + viper.BindPFlag("router.downlink-bind-address", routerCmd.Flags().Lookup("downlink-bind-address")) viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) + routerCmd.Flags().String("brokers", ":1881", "Comma-separated list of brokers") viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) } From ee4ac5429d9125d7f1ef665d28976b3eba5ae3ba Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 11:28:04 +0100 Subject: [PATCH 0888/2266] [cli] Only warn for real potential problems --- cmd/broker.go | 6 ++++-- cmd/handler.go | 9 ++++++--- cmd/router.go | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 4a6d0f8e9..597c798a3 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -106,7 +106,8 @@ and personalized devices (with their network session keys) with the router. } go func(packet []byte, an core.AckNacker) { if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { - ctx.WithError(err).Error("Could not process uplink") + // We can't do anything with this packet, so we're ignoring it. + ctx.WithError(err).Debug("Could not process uplink") } }(packet, an) } @@ -122,7 +123,8 @@ and personalized devices (with their network session keys) with the router. } go func(reg core.Registration, an core.AckNacker) { if err := broker.Register(reg, an); err != nil { - ctx.WithError(err).Error("Could not process registration") + // We can't do anything with this registration, so we're ignoring it. + ctx.WithError(err).Debug("Could not process registration") } }(reg, an) } diff --git a/cmd/handler.go b/cmd/handler.go index 8c89e70ab..44b9bf404 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -130,7 +130,8 @@ The default handler is the bridge between The Things Network and applications. go func(packet []byte, an core.AckNacker) { if err := handler.HandleUp(packet, an, appAdapter); err != nil { - ctx.WithError(err).Warn("Could not process packet from brokers") + // We can't do anything with this packet, so we're ignoring it. + ctx.WithError(err).Debug("Could not process packet from brokers") } }(packet, an) } @@ -147,7 +148,8 @@ The default handler is the bridge between The Things Network and applications. go func(packet []byte, an core.AckNacker) { if err := handler.HandleDown(packet, an, brkAdapter); err != nil { - ctx.WithError(err).Warn("Could not process packet from applications") + // We can't do anything with this packet, so we're ignoring it. + ctx.WithError(err).Debug("Could not process packet from applications") } }(packet, an) } @@ -164,7 +166,8 @@ The default handler is the bridge between The Things Network and applications. go func(reg core.Registration, an core.AckNacker) { if err := handler.Register(reg, an); err != nil { - ctx.WithError(err).Warn("Could not process registration from applications") + // We can't do anything with this registration, so we're ignoring it. + ctx.WithError(err).Debug("Could not process registration from applications") } }(reg, an) } diff --git a/cmd/router.go b/cmd/router.go index 07357cc41..1a2de1689 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -112,7 +112,8 @@ the gateway's duty cycle is (almost) full.`, } go func(packet []byte, an core.AckNacker) { if err := router.HandleUp(packet, an, brkAdapter); err != nil { - ctx.WithError(err).Warn("Could not process packet from gateway") + // We can't do anything with this packet, so we're ignoring it. + ctx.WithError(err).Debug("Could not process packet from gateway") } }(packet, an) } @@ -128,7 +129,8 @@ the gateway's duty cycle is (almost) full.`, } go func(reg core.Registration, an core.AckNacker) { if err := router.Register(reg, an); err != nil { - ctx.WithError(err).Warn("Could not process registration from broker") + // We can't do anything with this registration, so we're ignoring it. + ctx.WithError(err).Debug("Could not process registration from broker") } }(reg, an) } From 01bcc7c838cddfa2d7a9a5fcf66a931cca26897c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Mar 2016 12:52:44 +0100 Subject: [PATCH 0889/2266] Change logging in components/adapters to better reflect actual severity --- core/adapters/http/http.go | 2 +- core/components/broker/broker.go | 12 ++++++------ core/components/router/router.go | 12 +++++++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 69f0bddb4..e370d1584 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -176,7 +176,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err err := <-cherr if err.(errors.Failure).Nature != errors.Behavioural { errored++ - ctx.WithError(err).Error("POST Failed") + ctx.WithError(err).Warn("POST Failed") } } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 6c0b9d6b1..feab2a970 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -74,7 +74,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { switch err.(errors.Failure).Nature { case errors.Behavioural: stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") - ctx.Warn("Uplink device not found") + ctx.Debug("Uplink device not found") default: b.ctx.Warn("Database lookup failed") } @@ -93,15 +93,15 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } if ok { mEntry = &entry - stats.MarkMeter("broker.uplink.handler_lookup.match") - ctx.WithField("handler", entry.Recipient).Debug("Associated device with handler") + stats.MarkMeter("broker.uplink.handler_lookup.mic_match") + ctx.WithField("handler", entry.Recipient).Debug("MIC check associated device with handler") break } } if mEntry == nil { - stats.MarkMeter("broker.uplink.handler_lookup.no_match") - err := errors.New(errors.Behavioural, "Could not find handler for device") - ctx.Warn(err.Error()) + stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") + err := errors.New(errors.Behavioural, "MIC check returned no matches") + ctx.Debug(err.Error()) return err } diff --git a/core/components/router/router.go b/core/components/router/router.go index 8a8a53216..cd11e25f6 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -96,9 +96,15 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } if err != nil { - stats.MarkMeter("router.uplink.bad_broker_response") - r.ctx.WithError(err).Warn("Invalid response from Broker") - return errors.New(errors.Operational, err) + switch err.(errors.Failure).Nature { + case errors.Behavioural: + stats.MarkMeter("router.uplink.negative_broker_response") + r.ctx.WithError(err).Debug("Negative response from Broker") + default: + stats.MarkMeter("router.uplink.bad_broker_response") + r.ctx.WithError(err).Warn("Invalid response from Broker") + } + return err } // No response, stop there From 72817b99fef3a54a2b86399b0b29c30b90872f15 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 14:30:57 +0100 Subject: [PATCH 0890/2266] [handler-broker] Split Component interface into several more dedicated interfaces --- core/core.go | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/core/core.go b/core/core.go index fa871a9ce..6c8c88f9b 100644 --- a/core/core.go +++ b/core/core.go @@ -14,7 +14,23 @@ import ( type Component interface { Register(reg Registration, an AckNacker) error HandleUp(p []byte, an AckNacker, upAdapter Adapter) error - HandleDown(p []byte, an AckNacker, downAdapter Adapter) error + HandleDown(p []byte, an AckNacker) error +} + +type Handler interface { + Register(reg Registration, an AckNacker, s Subscriber) error + HandleUp(p []byte, an AckNacker, upAdapter Adapter) error + HandleDown(p []byte, an AckNacker, down Adapter) error +} + +type Router interface { + Register(reg Registration, an AckNacker) error + HandleUp(p []byte, an AckNacker, up Adapter) error +} + +type Broker interface { + Register(reg Registration, an AckNacker) error + HandleUp(p []byte, an AckNacker, up Adapter) error } // NetworkController is directly used by the broker to manage nodes lifecycle. @@ -41,3 +57,7 @@ type Adapter interface { Next() ([]byte, AckNacker, error) NextRegistration() (Registration, AckNacker, error) } + +type Subscriber interface { + Subscribe(reg Registration, recipient Recipient) error +} From 118f3d5fa5c956af06af951bb4c286beb31bb7c9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 14:31:22 +0100 Subject: [PATCH 0891/2266] [handler-broker] Implement the core.Subscriber interface with the http adapter --- core/adapters/http/http.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 4cd813ecd..f91d9a831 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -5,6 +5,7 @@ package http import ( "bytes" + "encoding/json" "fmt" "io" "io/ioutil" @@ -68,6 +69,34 @@ func NewAdapter(port uint, recipients []core.Recipient, ctx log.Interface) (*Ada return &a, nil } +// Register implements the core.Subscriber interface +func (a *Adapter) Subscribe(r core.Registration) error { + jsonMarshaler, ok := r.(json.Marshaler) + if !ok { + return errors.New(errors.Structural, "Unable to marshal registration") + } + httpRecipient, ok := r.Recipient().(Recipient) + if !ok { + return errors.New(errors.Structural, "Invalid recipient") + } + + data, err := jsonMarshaler.MarshalJSON() + if err != nil { + return errors.New(errors.Structural, err) + } + buf := new(bytes.Buffer) + buf.Write(data) + resp, err := a.Post(fmt.Sprintf("http://%s", httpRecipient.URL()), "application/json", buf) + if err != nil { + return errors.New(errors.Operational, err) + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusAccepted || resp.StatusCode != http.StatusOK { + return errors.New(errors.Operational, "Unable to subscribe") + } + return nil +} + // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { stats.MarkMeter("http_adapter.send") From 0b16dfa853175408432799009b5ee424712e94e5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 14:35:39 +0100 Subject: [PATCH 0892/2266] [handler-broker] Fix typo in core interfaces --- core/core.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/core.go b/core/core.go index 6c8c88f9b..96fa658ba 100644 --- a/core/core.go +++ b/core/core.go @@ -59,5 +59,5 @@ type Adapter interface { } type Subscriber interface { - Subscribe(reg Registration, recipient Recipient) error + Subscribe(reg Registration) error } From b4d3431602ade22813973fae6281675b9f35365c Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 14:36:15 +0100 Subject: [PATCH 0893/2266] [handler-broker] Make component conform to new interfaces --- core/components/broker/broker.go | 12 +++--------- core/components/handler/handler.go | 14 +++++++++++--- core/components/router/router.go | 12 +++--------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 6c0b9d6b1..486ec7d96 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -18,11 +18,11 @@ type component struct { } // New construct a new Broker component -func New(db Storage, ctx log.Interface) Component { +func New(db Storage, ctx log.Interface) Broker { return component{Storage: db, ctx: ctx} } -// Register implements the core.Component interface +// Register implements the core.Broker interface func (b component) Register(reg Registration, an AckNacker) (err error) { defer ensureAckNack(an, nil, &err) stats.MarkMeter("broker.registration.in") @@ -40,7 +40,7 @@ func (b component) Register(reg Registration, an AckNacker) (err error) { return err } -// HandleUp implements the core.Component interface +// HandleUp implements the core.Broker interface func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Make sure we don't forget the AckNacker var ack Packet @@ -159,12 +159,6 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return nil } -// HandleDown implements the core.Component interface -func (b component) HandleDown(data []byte, an AckNacker, down Adapter) (err error) { - defer ensureAckNack(an, nil, &err) - return errors.New(errors.Implementation, "Handle Down not implemented on broker") -} - func ensureAckNack(an AckNacker, ack *Packet, err *error) { if err != nil && *err != nil { an.Nack() diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index ea3d03987..afb800253 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -19,6 +19,7 @@ const maxDutyCycle = 90 // 90% // component implements the core.Component interface type component struct { + broker Recipient ctx log.Interface devices DevStorage packets PktStorage @@ -34,11 +35,12 @@ type bundle struct { } // New construct a new Handler -func New(devDb DevStorage, pktDb PktStorage, ctx log.Interface) Component { +func New(devDb DevStorage, pktDb PktStorage, broker Recipient, ctx log.Interface) Handler { h := component{ ctx: ctx, devices: devDb, packets: pktDb, + broker: broker, } set := make(chan bundle) @@ -52,7 +54,7 @@ func New(devDb DevStorage, pktDb PktStorage, ctx log.Interface) Component { } // Register implements the core.Component interface -func (h component) Register(reg Registration, an AckNacker) (err error) { +func (h component) Register(reg Registration, an AckNacker, sub Subscriber) (err error) { h.ctx.WithField("registration", reg).Debug("New registration request") defer ensureAckNack(an, nil, &err) @@ -64,7 +66,13 @@ func (h component) Register(reg Registration, an AckNacker) (err error) { if err = h.devices.StorePersonalized(hreg); err != nil { return errors.New(errors.Operational, err) } - return nil + + return sub.Subscribe(brokerRegistration{ + recipient: h.broker, + appEUI: hreg.AppEUI(), + devEUI: hreg.DevEUI(), + nwkSKey: hreg.NwkSKey(), + }) } // HandleUp implements the core.Component interface diff --git a/core/components/router/router.go b/core/components/router/router.go index 8a8a53216..2f246998d 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -16,11 +16,11 @@ type component struct { } // New constructs a new router -func New(db Storage, ctx log.Interface) Component { +func New(db Storage, ctx log.Interface) Router { return component{Storage: db, ctx: ctx} } -// Register implements the core.Component interface +// Register implements the core.Router interface func (r component) Register(reg Registration, an AckNacker) (err error) { defer ensureAckNack(an, nil, &err) stats.MarkMeter("router.registration.in") @@ -36,7 +36,7 @@ func (r component) Register(reg Registration, an AckNacker) (err error) { return r.Store(rreg) } -// HandleUp implements the core.Component interface +// HandleUp implements the core.Router interface func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Make sure we don't forget the AckNacker var ack Packet @@ -132,12 +132,6 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return nil } -// HandleDown implements the core.Component interface -func (r component) HandleDown(data []byte, an AckNacker, up Adapter) (err error) { - defer ensureAckNack(an, nil, &err) - return errors.New(errors.Implementation, "Handle down not implemented on router") -} - func ensureAckNack(an AckNacker, ack *Packet, err *error) { if err != nil && *err != nil { an.Nack() From 5933d2dfbec92b178a5289801c9df35ad6de5da9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:33:02 +0100 Subject: [PATCH 0894/2266] [handler-broker] Remove HandleDown() test methods from broker and router --- core/components/broker/broker_test.go | 23 ----------------------- core/components/router/router_test.go | 22 ---------------------- 2 files changed, 45 deletions(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 039598483..348bd5161 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -122,29 +122,6 @@ func TestRegister(t *testing.T) { } } -func TestHandleDown(t *testing.T) { - { - testutil.Desc(t, "Try Handle Down") - - // Build - an := mocks.NewMockAckNacker() - adapter := mocks.NewMockAdapter() - store := newMockStorage() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleDown([]byte{1, 2, 3}, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, nil, adapter.InSendPacket) - mocks.CheckRecipients(t, nil, adapter.InSendRecipients) - } -} - func TestHandleUp(t *testing.T) { { testutil.Desc(t, "Send an unknown packet") diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 9df903a68..848863a33 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -76,28 +76,6 @@ func TestRegister(t *testing.T) { } } -func TestHandleDown(t *testing.T) { - { - Desc(t, "Try Handle Down") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - store := newMockStorage() - - // Operate - router := New(store, GetLogger(t, "Router")) - err := router.HandleDown([]byte{1, 2, 3}, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Implementation)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } -} - func TestHandleUp(t *testing.T) { { Desc(t, "Send an unknown packet | No downlink") From 3a70c2fb069bd20ca36d1271c5d6616845ee30d5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:34:09 +0100 Subject: [PATCH 0895/2266] [handler-broker] Introduce JSONRecipient interface --- core/registrations_interfaces.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/registrations_interfaces.go b/core/registrations_interfaces.go index 1c61580e7..214778b5d 100644 --- a/core/registrations_interfaces.go +++ b/core/registrations_interfaces.go @@ -5,6 +5,7 @@ package core import ( "encoding" + "encoding/json" "github.com/brocaar/lorawan" ) @@ -14,6 +15,12 @@ type Recipient interface { encoding.BinaryMarshaler } +// JSONRecipient extends the actual Recipient to also support json marshalin/unmarshaling +type JSONRecipient interface { + Recipient + json.Marshaler +} + // Registration gives an elementary base for each other registration levels type Registration interface { Recipient() Recipient From 5dae33421a82c2b318fbc5f21a781a4f316d1844 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:36:01 +0100 Subject: [PATCH 0896/2266] [handler-broker] Implement registration propagation to broker --- core/components/handler/brokerRegistration.go | 59 ++++++++++++++ .../handler/brokerRegistration_test.go | 61 +++++++++++++++ core/components/handler/devStorage_test.go | 1 - core/components/handler/handler.go | 4 +- core/components/handler/handler_test.go | 78 ++++++++++++------- core/components/handler/helpers_test.go | 14 ++++ 6 files changed, 188 insertions(+), 29 deletions(-) create mode 100644 core/components/handler/brokerRegistration.go create mode 100644 core/components/handler/brokerRegistration_test.go diff --git a/core/components/handler/brokerRegistration.go b/core/components/handler/brokerRegistration.go new file mode 100644 index 000000000..ea349ed5a --- /dev/null +++ b/core/components/handler/brokerRegistration.go @@ -0,0 +1,59 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "encoding/json" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// type brokerRegistration implements the core.BRegistration interface +type brokerRegistration struct { + recipient core.JSONRecipient + appEUI lorawan.EUI64 + nwkSKey lorawan.AES128Key + devEUI lorawan.EUI64 +} + +// Recipient implements the core.BRegistration interface +func (r brokerRegistration) Recipient() core.Recipient { + return r.recipient +} + +// AppEUI implements the core.BRegistration interface +func (r brokerRegistration) AppEUI() lorawan.EUI64 { + return r.appEUI +} + +// DevEUI implements the core.BRegistration interface +func (r brokerRegistration) DevEUI() lorawan.EUI64 { + return r.devEUI +} + +// NwkSKey implements the core.BRegistration interface +func (r brokerRegistration) NwkSKey() lorawan.AES128Key { + return r.nwkSKey +} + +// MarshalJSON implements the encoding/json.Marshaler interface +func (r brokerRegistration) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(struct { + Recipient core.JSONRecipient `json:"recipient"` + AppEUI lorawan.EUI64 `json:"app_eui"` + NwkSKey lorawan.AES128Key `json:"nwks_key"` + DevEUI lorawan.EUI64 `json:"dev_eui"` + }{ + Recipient: r.recipient, + AppEUI: r.appEUI, + NwkSKey: r.nwkSKey, + DevEUI: r.devEUI, + }) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + return data, nil +} diff --git a/core/components/handler/brokerRegistration_test.go b/core/components/handler/brokerRegistration_test.go new file mode 100644 index 000000000..a8fda5fa5 --- /dev/null +++ b/core/components/handler/brokerRegistration_test.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + mocks "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" +) + +func TestRegistration(t *testing.T) { + recipient := mocks.NewMockJSONRecipient() + devEUI := lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) + nwkSKey := lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}) + + reg := brokerRegistration{ + recipient: recipient, + devEUI: devEUI, + appEUI: appEUI, + nwkSKey: nwkSKey, + } + + mocks.Check(t, recipient, reg.Recipient(), "Recipients") + mocks.Check(t, devEUI, reg.DevEUI(), "DevEUIs") + mocks.Check(t, appEUI, reg.AppEUI(), "AppEUIs") + mocks.Check(t, nwkSKey, reg.NwkSKey(), "NwkSKeys") + +} + +func TestRegistrationMarshalUnmarshal(t *testing.T) { + { + reg := brokerRegistration{ + recipient: mocks.NewMockJSONRecipient(), + devEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + appEUI: lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}), + nwkSKey: lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}), + } + + _, err := reg.MarshalJSON() + errutil.CheckErrors(t, nil, err) + } + + { + reg := brokerRegistration{ + recipient: mocks.NewMockJSONRecipient(), + devEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + appEUI: lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}), + nwkSKey: lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}), + } + reg.recipient.(*mocks.MockJSONRecipient).OutMarshalJSON = []byte("InvalidJSON") + + _, err := reg.MarshalJSON() + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + } +} diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 87170854a..5d445f117 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -8,7 +8,6 @@ import ( "path" "testing" - . "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index afb800253..37529adcf 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -19,7 +19,7 @@ const maxDutyCycle = 90 // 90% // component implements the core.Component interface type component struct { - broker Recipient + broker JSONRecipient ctx log.Interface devices DevStorage packets PktStorage @@ -35,7 +35,7 @@ type bundle struct { } // New construct a new Handler -func New(devDb DevStorage, pktDb PktStorage, broker Recipient, ctx log.Interface) Handler { +func New(devDb DevStorage, pktDb PktStorage, broker JSONRecipient, ctx log.Interface) Handler { h := component{ ctx: ctx, devices: devDb, diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 0d9b19422..f3649bc0f 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -26,15 +26,23 @@ func TestRegister(t *testing.T) { pktStorage := newMockPktStorage() an := NewMockAckNacker() r := NewMockHRegistration() + broker := NewMockJSONRecipient() + br := NewMockBRegistration() + br.OutRecipient = broker + br.OutDevEUI = r.DevEUI() + br.OutAppEUI = r.AppEUI() + br.OutNwkSKey = r.NwkSKey() + sub := NewMockSubscriber() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - err := handler.Register(r, an) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) + err := handler.Register(r, an, sub) // Check CheckErrors(t, nil, err) CheckPushed(t, nil, pktStorage.InPush) CheckPersonalized(t, r, devStorage.InStorePersonalized) + CheckSubscriptions(t, br, sub.InSubscribeRegistration) } // -------------------- @@ -46,15 +54,18 @@ func TestRegister(t *testing.T) { devStorage := newMockDevStorage() pktStorage := newMockPktStorage() an := NewMockAckNacker() - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + broker := NewMockJSONRecipient() + sub := NewMockSubscriber() // Operate - err := handler.Register(nil, an) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) + err := handler.Register(nil, an, sub) // Checks CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckPushed(t, nil, pktStorage.InPush) CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckSubscriptions(t, nil, sub.InSubscribeRegistration) } // -------------------- @@ -68,10 +79,12 @@ func TestRegister(t *testing.T) { pktStorage := newMockPktStorage() an := NewMockAckNacker() r := NewMockHRegistration() + broker := NewMockJSONRecipient() + sub := NewMockSubscriber() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) - err := handler.Register(r, an) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) + err := handler.Register(r, an, sub) // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) @@ -96,9 +109,10 @@ func TestHandleDown(t *testing.T) { []Metadata{}, ) data, _ := pkt.MarshalBinary() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleDown(data, an, adapter) // Check @@ -120,9 +134,10 @@ func TestHandleDown(t *testing.T) { pktStorage := newMockPktStorage() an := NewMockAckNacker() adapter := NewMockAdapter() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleDown([]byte{1, 2, 3}, an, adapter) // Check @@ -151,9 +166,10 @@ func TestHandleDown(t *testing.T) { Metadata{}, ) data, _ := pkt.MarshalBinary() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleDown(data, an, adapter) // Check @@ -189,9 +205,10 @@ func TestHandleUp(t *testing.T) { [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn, _ := inPkt.MarshalBinary() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check @@ -211,9 +228,10 @@ func TestHandleUp(t *testing.T) { pktStorage := newMockPktStorage() an := NewMockAckNacker() adapter := NewMockAdapter() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp([]byte{1, 2, 3}, an, adapter) // Check @@ -260,9 +278,10 @@ func TestHandleUp(t *testing.T) { NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check @@ -280,7 +299,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle uplink with 2 packets in a row | No downlink ready") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() // First Packet @@ -333,9 +352,10 @@ func TestHandleUp(t *testing.T) { NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) done := sync.WaitGroup{} done.Add(2) go func() { @@ -371,7 +391,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle uplink with 1 packet | One downlink response") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() an := NewMockAckNacker() adapter := NewMockAdapter() @@ -417,9 +437,10 @@ func TestHandleUp(t *testing.T) { } pktStorage := newMockPktStorage() pktStorage.OutPull = appResp + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check @@ -437,7 +458,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle a late uplink | No downlink ready") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() an2 := NewMockAckNacker() an1 := NewMockAckNacker() @@ -471,9 +492,10 @@ func TestHandleUp(t *testing.T) { NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) done := sync.WaitGroup{} done.Add(2) go func() { @@ -506,7 +528,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle uplink with 1 packet | No downlink ready | No Metadata ") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() an := NewMockAckNacker() adapter := NewMockAdapter() @@ -535,9 +557,10 @@ func TestHandleUp(t *testing.T) { NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check @@ -555,7 +578,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail sending ") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() an := NewMockAckNacker() adapter := NewMockAdapter() @@ -588,9 +611,10 @@ func TestHandleUp(t *testing.T) { NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check @@ -608,7 +632,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail GetRecipient") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() an := NewMockAckNacker() adapter := NewMockAdapter() @@ -635,9 +659,10 @@ func TestHandleUp(t *testing.T) { NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, } pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check @@ -655,7 +680,7 @@ func TestHandleUp(t *testing.T) { Desc(t, "Handle uplink with 1 packet | No downlink ready | PktStorage fails to pull") // Build - recipient := NewMockRecipient() + recipient := NewMockJSONRecipient() dataRecipient, _ := recipient.MarshalBinary() an := NewMockAckNacker() adapter := NewMockAdapter() @@ -688,9 +713,10 @@ func TestHandleUp(t *testing.T) { } pktStorage := newMockPktStorage() pktStorage.Failures["Pull"] = errors.New(errors.Operational, "Mock Error: Failed to Pull") + broker := NewMockJSONRecipient() // Operate - handler := New(devStorage, pktStorage, GetLogger(t, "Handler")) + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) err := handler.HandleUp(dataIn, an, adapter) // Check diff --git a/core/components/handler/helpers_test.go b/core/components/handler/helpers_test.go index 46fe94a48..cfe377539 100644 --- a/core/components/handler/helpers_test.go +++ b/core/components/handler/helpers_test.go @@ -106,3 +106,17 @@ func CheckEntries(t *testing.T, want MockHRegistration, got devEntry) { Check(t, wantEntry, got, "Entries") } + +func CheckSubscriptions(t *testing.T, want BRegistration, got Registration) { + var mockGot BRegistration + bgot, ok := got.(BRegistration) + if got != nil && ok { + r := NewMockBRegistration() + r.OutRecipient = bgot.Recipient() + r.OutDevEUI = bgot.DevEUI() + r.OutAppEUI = bgot.AppEUI() + r.OutNwkSKey = bgot.NwkSKey() + mockGot = r + } + Check(t, want, mockGot, "Subscriptions") +} From 6e1aa8e412ad5efe18fe4fc2950ba5ec3fbbcb63 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:37:10 +0100 Subject: [PATCH 0897/2266] [handler-broker] Define new necessary mock to test JSONRecipient correctly --- core/mocks/mocks.go | 56 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 3b3e998bf..46fa3496f 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -53,6 +53,34 @@ func (r *MockRecipient) UnmarshalBinary(data []byte) error { return nil } +type MockJSONRecipient struct { + *MockRecipient + InUnmarshalJSON []byte + OutMarshalJSON []byte +} + +func NewMockJSONRecipient() *MockJSONRecipient { + return &MockJSONRecipient{ + MockRecipient: NewMockRecipient(), + OutMarshalJSON: []byte(`{"out":"MockJSONRecipientData"}`), + } +} + +func (r *MockJSONRecipient) MarshalJSON() ([]byte, error) { + if r.Failures["MarshalJSON"] != nil { + return nil, r.Failures["MarshalJSON"] + } + return r.OutMarshalJSON, nil +} + +func (r *MockJSONRecipient) UnmarshalJSON(data []byte) error { + r.InUnmarshalJSON = data + if r.Failures["UnmarshalJSON"] != nil { + return r.Failures["UnmarshalJSON"] + } + return nil +} + // MockRegistration implements the core.Registration interface // // It also stores the last arguments of each function call in appropriated @@ -281,11 +309,37 @@ func (a *MockAdapter) NextRegistration() (Registration, AckNacker, error) { return a.OutNextRegReg, a.OutNextRegAckNacker, nil } +// MockSubscriber implements the core.Subscriber interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockSubscriber struct { + Failures map[string]error + InSubscribeRegistration Registration +} + +func NewMockSubscriber() *MockSubscriber { + return &MockSubscriber{ + Failures: make(map[string]error), + } +} + +func (s *MockSubscriber) Subscribe(reg Registration) error { + s.InSubscribeRegistration = reg + return s.Failures["Subscribe"] +} + // ----- CHECK utilities func Check(t *testing.T, want, got interface{}, name string) { if !reflect.DeepEqual(want, got) { - Ko(t, "%s don't match expectations.\nWant: %v\nGot: %v", name, want, got) + Ko(t, "%s don't match expectations.\nWant: %+v\nGot: %+v", name, want, got) } Ok(t, fmt.Sprintf("Check %s", name)) } From 6ad2603778217ba44cfcf50a81f7a39cd71a5da5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:45:12 +0100 Subject: [PATCH 0898/2266] [handler-broker] Change pubsub and http handler recipient to pass parameter through json --- core/adapters/http/handlers/pubsub.go | 48 ++++++++------- core/adapters/http/handlers/pubsub_test.go | 68 +++++++++++++++++----- core/adapters/http/httpRecipient.go | 18 ++++++ 3 files changed, 99 insertions(+), 35 deletions(-) diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index 5c4e8f004..d73e7bfe3 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -8,7 +8,6 @@ import ( "encoding/json" "io" "net/http" - "regexp" "strings" "github.com/TheThingsNetwork/ttn/core" @@ -19,15 +18,19 @@ import ( // PubSub defines a handler to handle application | devEUI registration on a component. // -// It listens to request of the form: [PUT] /end-devices/:devEUI +// It listens to request of the form: [PUT] /end-devices // where devEUI is a 8 bytes hex-encoded address. // // It expects a Content-Type = application/json // // It also looks for params: // +// - dev_eui (8 bytes hex-encoded string) // - app_eui (8 bytes hex-encoded string) -// - app_url (http address as string) +// - recipient { +// - url (http address as string) +// - method (http verb as string) +// - } // - nwks_key (16 bytes hex-encoded string) // // It fails with an http 400 Bad Request. if one of the parameter is missing or invalid @@ -83,17 +86,6 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { return pubSubRegistration{}, errors.New(errors.Structural, "Received invalid content-type in request") } - // Check the query parameter - reg := regexp.MustCompile("end-devices/([a-fA-F0-9]{16})$") // 8-bytes, hex-encoded -> 16 chars - query := reg.FindStringSubmatch(req.RequestURI) - if len(query) < 2 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect end-device address format") - } - devEUI, err := hex.DecodeString(query[1]) - if err != nil { - return pubSubRegistration{}, errors.New(errors.Structural, err) - } - // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) @@ -101,11 +93,15 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { return pubSubRegistration{}, errors.New(errors.Structural, err) } defer req.Body.Close() - params := &struct { - AppEUI string `json:"app_eui"` - URL string `json:"app_url"` + params := new(struct { + AppEUI string `json:"app_eui"` + DevEUI string `json:"dev_eui"` + Recipient struct { + URL string `json:"url"` + Method string `json:"method"` + } `json:"recipient"` NwkSKey string `json:"nwks_key"` - }{} + }) if err := json.Unmarshal(body[:n], params); err != nil { return pubSubRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") } @@ -121,14 +117,24 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") } - params.URL = strings.Trim(params.URL, " ") - if len(params.URL) <= 0 { + devEUI, err := hex.DecodeString(params.DevEUI) + if err != nil || len(devEUI) != 8 { + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect device eui") + } + + params.Recipient.URL = strings.Trim(params.Recipient.URL, " ") + if len(params.Recipient.URL) <= 0 { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application url") } + params.Recipient.Method = strings.Trim(params.Recipient.Method, " ") + if len(params.Recipient.Method) <= 0 { + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application method") + } + // Create actual registration registration := pubSubRegistration{ - recipient: NewRecipient(params.URL, "PUT"), + recipient: NewRecipient(params.Recipient.URL, params.Recipient.Method), appEUI: lorawan.EUI64{}, devEUI: lorawan.EUI64{}, nwkSKey: lorawan.AES128Key{}, diff --git a/core/adapters/http/handlers/pubsub_test.go b/core/adapters/http/handlers/pubsub_test.go index 9c6bee06a..e29fcc9df 100644 --- a/core/adapters/http/handlers/pubsub_test.go +++ b/core/adapters/http/handlers/pubsub_test.go @@ -37,7 +37,6 @@ func TestPubSub(t *testing.T) { Payload: "TheThingsNetwork", ContentType: "application/json", Method: "PUT", - DevEUI: "0000000011223344", ShouldAck: false, WantContent: string(errors.Structural), @@ -46,11 +45,19 @@ func TestPubSub(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid DevEUI. Nack", - Payload: `{"app_eui":"0011223344556677","nwks_key":"00112233445566778899001122334455","app_url":"url"}`, + Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid DevEUI. Nack", + Payload: `{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304", + "recipient": { + "url": "url", + "method": "PUT" + } + }`, ContentType: "text/plain", Method: "PUT", - DevEUI: "0000000011223344", + DevEUI: "", ShouldAck: false, WantContent: string(errors.Structural), @@ -59,11 +66,19 @@ func TestPubSub(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid DevEUI. Nack", - Payload: `{"app_eui":"0011223344556677","nwks_key":"00112233445566778899001122334455","app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid DevEUI. Nack", + Payload: `{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304", + "recipient": { + "url": "url", + "method": "PUT" + } + }`, ContentType: "application/json", Method: "POST", - DevEUI: "0000000011223344", + DevEUI: "0000000001020304", ShouldAck: false, WantContent: string(errors.Structural), @@ -72,8 +87,16 @@ func TestPubSub(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid DevEUI. Nack", - Payload: `{"app_eui":"0011223344556677","nwks_key":"00112233445566778899001122334455","app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid DevEUI. Nack", + Payload: `{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000001144", + "recipient": { + "url": "url", + "method": "PUT" + } + }`, ContentType: "application/json", Method: "PUT", DevEUI: "12345678", @@ -85,8 +108,16 @@ func TestPubSub(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", - Payload: `{"app_eui":"0001020304050607","nwks_key":"00010203040506070809000102030405","app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", + Payload: `{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304", + "recipient": { + "url": "url", + "method": "PUT" + } + }`, ContentType: "application/json", Method: "PUT", DevEUI: "0000000001020304", @@ -103,8 +134,16 @@ func TestPubSub(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Ack", - Payload: `{"app_eui":"0001020304050607","nwks_key":"00010203040506070809000102030405","app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Ack", + Payload: `{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304", + "recipient": { + "url": "url", + "method": "PUT" + } + }`, ContentType: "application/json", Method: "PUT", DevEUI: "0000000001020304", @@ -133,7 +172,7 @@ func TestPubSub(t *testing.T) { client := testClient{} // Operate - url = fmt.Sprintf("%s%s", url, test.DevEUI) + url = fmt.Sprintf("%s", url) chresp := client.Send(test.Payload, url, test.Method, test.ContentType) registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) var statusCode int @@ -146,6 +185,7 @@ func TestPubSub(t *testing.T) { } // Check + t.Log(string(content)) CheckErrors(t, test.WantError, err) checkStatusCode(t, test.WantStatusCode, statusCode) checkContent(t, test.WantContent, content) diff --git a/core/adapters/http/httpRecipient.go b/core/adapters/http/httpRecipient.go index 31d8265d0..705a9c729 100644 --- a/core/adapters/http/httpRecipient.go +++ b/core/adapters/http/httpRecipient.go @@ -5,12 +5,15 @@ package http import ( "encoding" + "encoding/json" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" ) // Recipient represents the recipient used by the http adapter type Recipient interface { + json.Marshaler encoding.BinaryMarshaler URL() string Method() string @@ -52,3 +55,18 @@ func (h *recipient) UnmarshalBinary(data []byte) error { r.Read(func(data []byte) { h.method = string(data) }) return r.Err() } + +// MarshalJSON implements the encoding/json.Marshaler interface +func (h recipient) MarshalJSON() ([]byte, error) { + data, err := json.Marshal(struct { + Url string `json:"url"` + Method string `json:"method"` + }{ + Url: h.url, + Method: h.method, + }) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + return data, nil +} From 5e79dc2745a064933f93d424dfe465866e3201b9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:45:42 +0100 Subject: [PATCH 0899/2266] [handler-broker] Also updates the http/applications in prevision to support the same pattern --- core/adapters/http/handlers/applications.go | 24 ++++++-------- .../http/handlers/applications_test.go | 31 +++++++------------ 2 files changed, 21 insertions(+), 34 deletions(-) diff --git a/core/adapters/http/handlers/applications.go b/core/adapters/http/handlers/applications.go index 678ab7fbb..be0bfdc64 100644 --- a/core/adapters/http/handlers/applications.go +++ b/core/adapters/http/handlers/applications.go @@ -8,7 +8,6 @@ import ( "encoding/json" "io" "net/http" - "regexp" "strings" "github.com/TheThingsNetwork/ttn/core" @@ -79,17 +78,6 @@ func (p Applications) parse(req *http.Request) (core.Registration, error) { return applicationsRegistration{}, errors.New(errors.Structural, "Received invalid content-type in request") } - // Check the query parameter - reg := regexp.MustCompile("applications/([a-fA-F0-9]{16})$") // 8-bytes, hex-encoded -> 16 chars - query := reg.FindStringSubmatch(req.RequestURI) - if len(query) < 2 { - return applicationsRegistration{}, errors.New(errors.Structural, "Incorrect application identifier format") - } - appEUI, err := hex.DecodeString(query[1]) - if err != nil { - return applicationsRegistration{}, errors.New(errors.Structural, err) - } - // Check configuration in body body := make([]byte, req.ContentLength) n, err := req.Body.Read(body) @@ -97,9 +85,10 @@ func (p Applications) parse(req *http.Request) (core.Registration, error) { return applicationsRegistration{}, errors.New(errors.Structural, err) } defer req.Body.Close() - params := &struct { - URL string `json:"app_url"` - }{} + params := new(struct { + URL string `json:"app_url"` + AppEUI string `json:"app_eui"` + }) if err := json.Unmarshal(body[:n], params); err != nil { return applicationsRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") } @@ -110,6 +99,11 @@ func (p Applications) parse(req *http.Request) (core.Registration, error) { return applicationsRegistration{}, errors.New(errors.Structural, "Incorrect application url") } + appEUI, err := hex.DecodeString(params.AppEUI) + if err != nil || len(appEUI) != 8 { + return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") + } + // Create actual registration registration := applicationsRegistration{ recipient: NewRecipient(params.URL, "PUT"), diff --git a/core/adapters/http/handlers/applications_test.go b/core/adapters/http/handlers/applications_test.go index 2b3285e1e..65cada330 100644 --- a/core/adapters/http/handlers/applications_test.go +++ b/core/adapters/http/handlers/applications_test.go @@ -23,7 +23,6 @@ func TestApplications(t *testing.T) { Payload string ContentType string Method string - AppEUI string ShouldAck bool AckPacket core.Packet @@ -33,11 +32,10 @@ func TestApplications(t *testing.T) { WantError *string }{ { - Desc: "Invalid Payload. Valid ContentType. Valid Method. Valid AppEUI. Nack", + Desc: "Invalid Payload. Valid ContentType. Valid Method. Nack", Payload: "TheThingsNetwork", ContentType: "application/json", Method: "PUT", - AppEUI: "0000000011223344", ShouldAck: false, WantContent: string(errors.Structural), @@ -46,11 +44,10 @@ func TestApplications(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid AppEUI. Nack", - Payload: `{"app_url":"url"}`, + Desc: "Valid Payload. Invalid ContentType. Valid Method. Nack", + Payload: `{"app_eui":"0000000011223344","app_url":"url"}`, ContentType: "text/plain", Method: "PUT", - AppEUI: "0000000011223344", ShouldAck: false, WantContent: string(errors.Structural), @@ -59,11 +56,10 @@ func TestApplications(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid AppEUI. Nack", - Payload: `{"app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Invalid Method. Nack", + Payload: `{"app_eui":"0000000011223344","app_url":"url"}`, ContentType: "application/json", Method: "POST", - AppEUI: "0000000011223344", ShouldAck: false, WantContent: string(errors.Structural), @@ -72,11 +68,10 @@ func TestApplications(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid AppEUI. Nack", - Payload: `{"app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Valid Method. Nack", + Payload: `{"app_eui":"000011223344","app_url":"url"}`, ContentType: "application/json", Method: "PUT", - AppEUI: "12345678", ShouldAck: false, WantContent: string(errors.Structural), @@ -85,11 +80,10 @@ func TestApplications(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Valid Method. Valid AppEUI. Nack", - Payload: `{"app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Valid Method. Nack", + Payload: `{"app_eui":"0000000001020304","app_url":"url"}`, ContentType: "application/json", Method: "PUT", - AppEUI: "0000000001020304", ShouldAck: false, WantContent: string(errors.Structural), @@ -101,11 +95,10 @@ func TestApplications(t *testing.T) { WantError: nil, }, { - Desc: "Valid Payload. Valid ContentType. Valid Method. Valid AppEUI. Ack", - Payload: `{"app_url":"url"}`, + Desc: "Valid Payload. Valid ContentType. Valid Method. Ack", + Payload: `{"app_eui":"0000000001020304","app_url":"url"}`, ContentType: "application/json", Method: "PUT", - AppEUI: "0000000001020304", ShouldAck: true, WantContent: "", @@ -129,7 +122,7 @@ func TestApplications(t *testing.T) { client := testClient{} // Operate - url = fmt.Sprintf("%s%s", url, test.AppEUI) + url = fmt.Sprintf("%s", url) chresp := client.Send(test.Payload, url, test.Method, test.ContentType) registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) var statusCode int From 9bf5d674e43fb5db832b5386f1238c5e99658c16 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:46:10 +0100 Subject: [PATCH 0900/2266] [handler-broker] Fix wrongly used 'or' operator in http adapter --- core/adapters/http/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index f91d9a831..db421018c 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -91,7 +91,7 @@ func (a *Adapter) Subscribe(r core.Registration) error { return errors.New(errors.Operational, err) } defer resp.Body.Close() - if resp.StatusCode != http.StatusAccepted || resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { return errors.New(errors.Operational, "Unable to subscribe") } return nil From ea8310b420e68b1b1064a78ec6bfc298182e7b57 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 4 Mar 2016 23:46:25 +0100 Subject: [PATCH 0901/2266] [handler-broker] Add broker-client to the handler command cli --- cmd/handler.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index b1369748b..f15a8abed 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -99,8 +99,11 @@ The default handler is the bridge between The Things Network and applications. ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local packets storage") } + // Instantiate the broker to which is bound the handler + broker := http.NewRecipient(viper.GetString("handler.broker-client"), "PUT") + // Instantiate the actual handler - handler := handler.New(devicesDB, packetsDB, ctx) + handler := handler.New(devicesDB, packetsDB, broker, ctx) // Bring the service to life @@ -147,11 +150,11 @@ The default handler is the bridge between The Things Network and applications. continue } - go func(reg core.Registration, an core.AckNacker) { - if err := handler.Register(reg, an); err != nil { + go func(reg core.Registration, an core.AckNacker, s core.Subscriber) { + if err := handler.Register(reg, an, s); err != nil { ctx.WithError(err).Warn("Could not process registration from applications") } - }(reg, an) + }(reg, an, brkAdapter) } }() From 11a1ccebabea0d4b6d75deca6279b1cb753115f8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 00:11:59 +0100 Subject: [PATCH 0902/2266] [dutycycle] Add missing database instantiation in constructor --- core/components/router/dutyManager.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index 83aba35fb..f1e8d46e5 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -44,7 +44,7 @@ const ( type region byte // NewDutyManager constructs a new gateway manager from -func NewDutyManager(cycleLength time.Duration, r region) (DutyManager, error) { +func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyManager, error) { var maxDuty map[subBand]float64 switch r { case Europe: @@ -56,7 +56,14 @@ func NewDutyManager(cycleLength time.Duration, r region) (DutyManager, error) { return nil, errors.New(errors.Implementation, "Region not supported") } + // Try to start a database + db, err := dbutil.New(filepath) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + return &dutyManager{ + db: db, CycleLength: cycleLength, MaxDutyCycle: maxDuty, }, nil From c7404fefe19580a5babeb48a3ee836e1f136495c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 00:12:16 +0100 Subject: [PATCH 0903/2266] [dutycycle] Properly define Europe frequencies --- core/components/router/dutyManager.go | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index f1e8d46e5..15a0e739f 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -28,8 +28,9 @@ type dutyManager struct { // Available sub-bands const ( - Europe1 subBand = iota - Europe2 + EuropeRX1_A subBand = iota + EuropeRX1_B + EuropeRX2 ) type subBand byte @@ -49,8 +50,9 @@ func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyM switch r { case Europe: maxDuty = map[subBand]float64{ - Europe1: 0.01, - Europe2: 0.1, + EuropeRX1_A: 0.01, // 1% dutycycle + EuropeRX1_B: 0.01, // 1% dutycycle + EuropeRX2: 0.1, // 10% dutycycle } default: return nil, errors.New(errors.Implementation, "Region not supported") @@ -71,8 +73,19 @@ func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyM // GetSubBand returns the subband associated to a given frequency func GetSubBand(freq float64) (subBand, error) { + // EuropeRX1_A -> 868.1 MHz -> 868.9 MHz if int(freq) == 868 { - return Europe1, nil + return EuropeRX1_A, nil + } + + // EuropeRX1_B -> 867.1 MHz -> 867.9 MHz + if int(freq) == 869 { + return EuropeRX1_B, nil + } + + // EuropeRX2 -> 869.5 MHz + if math.Floor(freq*10.0) == 8695.0 { + return EuropeRX2, nil } return 0, errors.New(errors.Structural, "Unknown frequency") } From 36d19c96c6aa3f5af4b463ba20d0f99a742fdbe1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 00:15:41 +0100 Subject: [PATCH 0904/2266] [dutycycle] Implement BinaryMarshaler/Unmarshaler for dutyEntry --- core/components/router/dutyManager.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index 15a0e739f..278c0ce1a 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -4,6 +4,7 @@ package router import ( + "encoding/json" "fmt" "math" "regexp" @@ -191,16 +192,23 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { } type dutyEntry struct { - Until time.Time - OnAir map[subBand]time.Duration + Until time.Time `json:"until"` + OnAir map[subBand]time.Duration `json:"on_air"` } // MarshalBinary implements the encoding.BinaryMarshaler interface func (e dutyEntry) MarshalBinary() ([]byte, error) { - return nil, nil + data, err := json.Marshal(e) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + return data, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *dutyEntry) UnmarshalBinary(data []byte) error { + if err := json.Unmarshal(data, e); err != nil { + return errors.New(errors.Structural, err) + } return nil } From b915d6ee8d7f8fb78f4941a4fd08149ea0afff5e Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 00:20:12 +0100 Subject: [PATCH 0905/2266] [dutycycle] Actually define the DutyManager interface + add Close method --- core/components/router/dutyManager.go | 46 ++++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index 278c0ce1a..0ef0aaf23 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -18,6 +18,9 @@ import ( // DutyManager provides an interface to manipulate and compute gateways duty-cycles. type DutyManager interface { + Update(id []byte, freq float64, size uint, datr string, codr string) error + Lookup(id []byte) (map[subBand]uint, error) + Close() error } type dutyManager struct { @@ -45,6 +48,25 @@ const ( type region byte +// GetSubBand returns the subband associated to a given frequency +func GetSubBand(freq float64) (subBand, error) { + // EuropeRX1_A -> 868.1 MHz -> 868.9 MHz + if int(freq) == 868 { + return EuropeRX1_A, nil + } + + // EuropeRX1_B -> 867.1 MHz -> 867.9 MHz + if int(freq) == 869 { + return EuropeRX1_B, nil + } + + // EuropeRX2 -> 869.5 MHz + if math.Floor(freq*10.0) == 8695.0 { + return EuropeRX2, nil + } + return 0, errors.New(errors.Structural, "Unknown frequency") +} + // NewDutyManager constructs a new gateway manager from func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyManager, error) { var maxDuty map[subBand]float64 @@ -72,25 +94,6 @@ func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyM }, nil } -// GetSubBand returns the subband associated to a given frequency -func GetSubBand(freq float64) (subBand, error) { - // EuropeRX1_A -> 868.1 MHz -> 868.9 MHz - if int(freq) == 868 { - return EuropeRX1_A, nil - } - - // EuropeRX1_B -> 867.1 MHz -> 867.9 MHz - if int(freq) == 869 { - return EuropeRX1_B, nil - } - - // EuropeRX2 -> 869.5 MHz - if math.Floor(freq*10.0) == 8695.0 { - return EuropeRX2, nil - } - return 0, errors.New(errors.Structural, "Unknown frequency") -} - // Update update an entry with the corresponding time-on-air // // Datr represents a LoRaWAN data-rate indicator of the form SFxxBWyyy, @@ -158,6 +161,11 @@ func (m *dutyManager) Lookup(id []byte) (map[subBand]uint, error) { return cycles, nil } +// Close releases the database access +func (m *dutyManager) Close() error { + return m.db.Close() +} + // computeTOA computes the time-on-air given a size in byte, a LoRaWAN datr identifier, an LoRa Codr // identifier. func computeTOA(size uint, datr string, codr string) (time.Duration, error) { From a7e08fded94b5377c321a47612b1b49c20b1ca80 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 00:29:40 +0100 Subject: [PATCH 0906/2266] [dutycycle] Add test for GetSubBand method + quickfix dutyManager --- core/components/router/dutyManager.go | 2 +- core/components/router/dutyManager_test.go | 43 ++++++++++++++++++++++ core/components/router/helpers_test.go | 4 ++ 3 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 core/components/router/dutyManager_test.go diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index 0ef0aaf23..36ae78734 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -56,7 +56,7 @@ func GetSubBand(freq float64) (subBand, error) { } // EuropeRX1_B -> 867.1 MHz -> 867.9 MHz - if int(freq) == 869 { + if int(freq) == 867 { return EuropeRX1_B, nil } diff --git a/core/components/router/dutyManager_test.go b/core/components/router/dutyManager_test.go new file mode 100644 index 000000000..20c4d5c5e --- /dev/null +++ b/core/components/router/dutyManager_test.go @@ -0,0 +1,43 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestGetSubBand(t *testing.T) { + { + Desc(t, "Test EuropeRX1_A") + sb, err := GetSubBand(868.38) + errutil.CheckErrors(t, nil, err) + CheckSubBands(t, EuropeRX1_A, sb) + } + + { + Desc(t, "Test EuropeRX1_B") + sb, err := GetSubBand(867.127) + errutil.CheckErrors(t, nil, err) + CheckSubBands(t, EuropeRX1_B, sb) + } + + { + Desc(t, "Test EuropeRX2") + sb, err := GetSubBand(869.567) + errutil.CheckErrors(t, nil, err) + CheckSubBands(t, EuropeRX2, sb) + } + + { + Desc(t, "Test Unknown") + sb, err := GetSubBand(433.5) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckSubBands(t, 0, sb) + } +} diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 2b1970f0d..96f40e147 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -71,3 +71,7 @@ func CheckEntries(t *testing.T, want entry, got entry) { func CheckRegistrations(t *testing.T, want Registration, got Registration) { Check(t, want, got, "Registrations") } + +func CheckSubBands(t *testing.T, want subBand, got subBand) { + Check(t, want, got, "SubBands") +} From bfa44a697be638b42b5b451dc6afa4aa139871c6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 01:28:13 +0100 Subject: [PATCH 0907/2266] [dutycycle] Add test for NewDutyCycle method + add check to ensure cycleLength > 0 --- core/components/router/dutyManager.go | 4 ++++ core/components/router/dutyManager_test.go | 28 ++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index 36ae78734..52b8ce597 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -81,6 +81,10 @@ func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyM return nil, errors.New(errors.Implementation, "Region not supported") } + if cycleLength == 0 { + return nil, errors.New(errors.Structural, "Invalid cycleLength. Should be > 0") + } + // Try to start a database db, err := dbutil.New(filepath) if err != nil { diff --git a/core/components/router/dutyManager_test.go b/core/components/router/dutyManager_test.go index 20c4d5c5e..0ea6cbcdd 100644 --- a/core/components/router/dutyManager_test.go +++ b/core/components/router/dutyManager_test.go @@ -4,7 +4,10 @@ package router import ( + "os" + "path" "testing" + "time" "github.com/TheThingsNetwork/ttn/utils/errors" errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" @@ -12,6 +15,8 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/testing" ) +var dutyManagerDB = path.Join(os.TempDir(), "TestDutyCycleStorage.db") + func TestGetSubBand(t *testing.T) { { Desc(t, "Test EuropeRX1_A") @@ -41,3 +46,26 @@ func TestGetSubBand(t *testing.T) { CheckSubBands(t, 0, sb) } } + +func TestNewManager(t *testing.T) { + defer func() { + os.Remove(dutyManagerDB) + }() + { + Desc(t, "Europe with valid cycleLength") + _, err := NewDutyManager(dutyManagerDB, time.Minute, Europe) + errutil.CheckErrors(t, nil, err) + } + + { + Desc(t, "Europe with invalid cycleLength") + _, err := NewDutyManager(dutyManagerDB, 0, Europe) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + } + + { + Desc(t, "Not europe with valid cycleLength") + _, err := NewDutyManager(dutyManagerDB, time.Minute, China) + errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) + } +} From f71bf0031d00a55e334f03e371d73276d3f09400 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 03:30:19 +0100 Subject: [PATCH 0908/2266] [dutycycle] Add test for dutyCycleManager --- core/components/router/dutyManager_test.go | 156 ++++++++++++++++++++- core/components/router/helpers_test.go | 4 + core/components/router/mock_test.go | 1 + 3 files changed, 159 insertions(+), 2 deletions(-) diff --git a/core/components/router/dutyManager_test.go b/core/components/router/dutyManager_test.go index 0ea6cbcdd..6dfc368e1 100644 --- a/core/components/router/dutyManager_test.go +++ b/core/components/router/dutyManager_test.go @@ -43,7 +43,7 @@ func TestGetSubBand(t *testing.T) { Desc(t, "Test Unknown") sb, err := GetSubBand(433.5) errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckSubBands(t, 0, sb) + CheckSubBands(t, "", sb) } } @@ -53,7 +53,9 @@ func TestNewManager(t *testing.T) { }() { Desc(t, "Europe with valid cycleLength") - _, err := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, err := NewDutyManager(dutyManagerDB, time.Minute, Europe) + errutil.CheckErrors(t, nil, err) + err = m.Close() errutil.CheckErrors(t, nil, err) } @@ -69,3 +71,153 @@ func TestNewManager(t *testing.T) { errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) } } + +func TestUpdateAndLookup(t *testing.T) { + defer func() { + os.Remove(dutyManagerDB) + }() + { + Desc(t, "Update unsupported frequency") + + // Build + m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + + // Operate + err := m.Update([]byte{1, 2, 3}, 433.65, 100, "SF8BW125", "4/5") + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + + // Clean + m.Close() + } + { + Desc(t, "Update invalid datr") + + // Build + m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + + // Operate + err := m.Update([]byte{1, 2, 3}, 868.1, 100, "SF3BW125", "4/5") + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + + // Clean + m.Close() + } + { + Desc(t, "Update invalid codr") + + // Build + m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + + // Operate + err := m.Update([]byte{1, 2, 3}, 869.5, 100, "SF8BW125", "14") + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + + // Clean + m.Close() + } + { + Desc(t, "Update once then lookup") + + // Build + m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + + // Operate + err := m.Update([]byte{1, 2, 3}, 868.5, 14, "SF8BW125", "4/5") + errutil.CheckErrors(t, nil, err) + bands, err := m.Lookup([]byte{1, 2, 3}) + + // Expectation + want := map[subBand]uint{ + EuropeRX1_A: 5, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckUsages(t, want, bands) + + // Clean + m.Close() + } + { + Desc(t, "Update several then lookup") + + // Build + m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + + // Operate + err := m.Update([]byte{4, 5, 6}, 868.523, 14, "SF8BW125", "4/5") + errutil.CheckErrors(t, nil, err) + err = m.Update([]byte{4, 5, 6}, 868.123, 42, "SF9BW125", "4/5") + errutil.CheckErrors(t, nil, err) + err = m.Update([]byte{4, 5, 6}, 867.785, 42, "SF8BW125", "4/6") + errutil.CheckErrors(t, nil, err) + bands, err := m.Lookup([]byte{4, 5, 6}) + + // Expectation + want := map[subBand]uint{ + EuropeRX1_A: 37, + EuropeRX1_B: 21, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckUsages(t, want, bands) + + // Clean + m.Close() + } + { + Desc(t, "Update out of cycle then lookup") + + // Build + m, _ := NewDutyManager(dutyManagerDB, 250*time.Millisecond, Europe) + + // Operate + err := m.Update([]byte{16, 2, 3}, 868.523, 14, "SF8BW125", "4/7") + errutil.CheckErrors(t, nil, err) + <-time.After(300 * time.Millisecond) + err = m.Update([]byte{16, 2, 3}, 868.123, 42, "SF9BW125", "4/5") + errutil.CheckErrors(t, nil, err) + bands, err := m.Lookup([]byte{16, 2, 3}) + + // Expectation + want := map[subBand]uint{ + EuropeRX1_A: 7645, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckUsages(t, want, bands) + + // Clean + m.Close() + } + { + Desc(t, "Lookup out of cycle") + + // Build + m, _ := NewDutyManager(dutyManagerDB, time.Millisecond, Europe) + + // Operate + err := m.Update([]byte{1, 2, 35}, 868.523, 14, "SF8BW125", "4/8") + errutil.CheckErrors(t, nil, err) + <-time.After(300 * time.Millisecond) + bands, err := m.Lookup([]byte{1, 2, 35}) + + // Expectation + want := map[subBand]uint{} + + // Check + errutil.CheckErrors(t, nil, err) + CheckUsages(t, want, bands) + + // Clean + m.Close() + } +} diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 96f40e147..6f4e919c7 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -75,3 +75,7 @@ func CheckRegistrations(t *testing.T, want Registration, got Registration) { func CheckSubBands(t *testing.T, want subBand, got subBand) { Check(t, want, got, "SubBands") } + +func CheckUsages(t *testing.T, want map[subBand]uint, got map[subBand]uint) { + Check(t, want, got, "Usages") +} diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go index 122f15c23..817c62aa5 100644 --- a/core/components/router/mock_test.go +++ b/core/components/router/mock_test.go @@ -10,6 +10,7 @@ import ( "github.com/brocaar/lorawan" ) +// MockStorage to fake the Storage interface type mockStorage struct { Failures map[string]error InLookup lorawan.EUI64 From aaabbf25ad9a3d34a7c4bfb8d0bba7050de84291 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 03:30:39 +0100 Subject: [PATCH 0909/2266] [dutycycle] Fix issues in the manager, according to tests --- core/components/router/dutyManager.go | 36 +++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/core/components/router/dutyManager.go b/core/components/router/dutyManager.go index 52b8ce597..4d3fa1866 100644 --- a/core/components/router/dutyManager.go +++ b/core/components/router/dutyManager.go @@ -26,18 +26,19 @@ type DutyManager interface { type dutyManager struct { sync.RWMutex db dbutil.Interface + bucket string CycleLength time.Duration // Duration upon which the duty-cycle is evaluated MaxDutyCycle map[subBand]float64 // The percentage max duty cycle accepted for each sub-band } // Available sub-bands const ( - EuropeRX1_A subBand = iota - EuropeRX1_B - EuropeRX2 + EuropeRX1_A subBand = "EURX1A" + EuropeRX1_B subBand = "EURX1B" + EuropeRX2 subBand = "EURX2" ) -type subBand byte +type subBand string // Available regions for LoRaWAN const ( @@ -64,7 +65,7 @@ func GetSubBand(freq float64) (subBand, error) { if math.Floor(freq*10.0) == 8695.0 { return EuropeRX2, nil } - return 0, errors.New(errors.Structural, "Unknown frequency") + return "", errors.New(errors.Structural, "Unknown frequency") } // NewDutyManager constructs a new gateway manager from @@ -93,6 +94,7 @@ func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyM return &dutyManager{ db: db, + bucket: "cycles", CycleLength: cycleLength, MaxDutyCycle: maxDuty, }, nil @@ -108,7 +110,6 @@ func (m *dutyManager) Update(id []byte, freq float64, size uint, datr string, co if err != nil { return err } - key := fmt.Sprintf("%+x", id) // Compute the ime-on-air timeOnAir, err := computeTOA(size, datr, codr) @@ -119,21 +120,29 @@ func (m *dutyManager) Update(id []byte, freq float64, size uint, datr string, co // Lookup and update the entry m.Lock() defer m.Unlock() - itf, err := m.db.Lookup(key, []byte("entry"), &dutyEntry{}) - if err != nil { + itf, err := m.db.Lookup(m.bucket, id, &dutyEntry{}) + + var entry dutyEntry + if err == nil { + entry = itf.([]dutyEntry)[0] + } else if err.(errors.Failure).Nature == errors.Behavioural { + entry = dutyEntry{ + Until: time.Unix(0, 0), + OnAir: make(map[subBand]time.Duration), + } + } else { return err } - entry := itf.([]dutyEntry)[0] // If the previous cycle is done, we create a new one if entry.Until.Before(time.Now()) { - entry.Until = time.Now() + entry.Until = time.Now().Add(m.CycleLength) entry.OnAir[sub] = timeOnAir } else { entry.OnAir[sub] += timeOnAir } - return m.db.Replace(key, []byte("entry"), []dbutil.Entry{&entry}) + return m.db.Replace(m.bucket, id, []dbutil.Entry{&entry}) } // Lookup returns the current bandwidth usages for a set of subband @@ -145,7 +154,7 @@ func (m *dutyManager) Lookup(id []byte) (map[subBand]uint, error) { defer m.RUnlock() // Lookup the entry - itf, err := m.db.Lookup(fmt.Sprintf("%+x", id), []byte("entry"), &dutyEntry{}) + itf, err := m.db.Lookup(m.bucket, id, &dutyEntry{}) if err != nil { return nil, err } @@ -156,6 +165,7 @@ func (m *dutyManager) Lookup(id []byte) (map[subBand]uint, error) { if entry.Until.After(time.Now()) { for s, toa := range entry.OnAir { // The actual duty cycle + fmt.Println(s, toa) dutyCycle := float64(toa.Nanoseconds()) / float64(m.CycleLength.Nanoseconds()) // Now, how full are we comparing to the limitation, in percent cycles[s] = uint(100 * dutyCycle / m.MaxDutyCycle[s]) @@ -200,7 +210,7 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { bw, _ := strconv.ParseUint(matches[2], 10, 64) bitrate := sf * cr * float64(bw) / math.Pow(2, sf) - return time.Duration(float64(size*8) / bitrate), nil + return time.ParseDuration(fmt.Sprintf("%fms", float64(size*8)/bitrate)) } type dutyEntry struct { From 59c699e6433300625e91b843adee7ef1df80abbe Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 03:34:59 +0100 Subject: [PATCH 0910/2266] [dutycycle] Move dutyCycle in its own package --- core/components/router/helpers_test.go | 8 -------- .../router => dutycycle}/dutyManager.go | 2 +- .../router => dutycycle}/dutyManager_test.go | 2 +- core/dutycycle/helpers_test.go | 19 +++++++++++++++++++ 4 files changed, 21 insertions(+), 10 deletions(-) rename core/{components/router => dutycycle}/dutyManager.go (99%) rename core/{components/router => dutycycle}/dutyManager_test.go (99%) create mode 100644 core/dutycycle/helpers_test.go diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 6f4e919c7..2b1970f0d 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -71,11 +71,3 @@ func CheckEntries(t *testing.T, want entry, got entry) { func CheckRegistrations(t *testing.T, want Registration, got Registration) { Check(t, want, got, "Registrations") } - -func CheckSubBands(t *testing.T, want subBand, got subBand) { - Check(t, want, got, "SubBands") -} - -func CheckUsages(t *testing.T, want map[subBand]uint, got map[subBand]uint) { - Check(t, want, got, "Usages") -} diff --git a/core/components/router/dutyManager.go b/core/dutycycle/dutyManager.go similarity index 99% rename from core/components/router/dutyManager.go rename to core/dutycycle/dutyManager.go index 4d3fa1866..a03634b96 100644 --- a/core/components/router/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package router +package dutycycle import ( "encoding/json" diff --git a/core/components/router/dutyManager_test.go b/core/dutycycle/dutyManager_test.go similarity index 99% rename from core/components/router/dutyManager_test.go rename to core/dutycycle/dutyManager_test.go index 6dfc368e1..ab61c1c5d 100644 --- a/core/components/router/dutyManager_test.go +++ b/core/dutycycle/dutyManager_test.go @@ -1,7 +1,7 @@ // Copyright © 2015 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package router +package dutycycle import ( "os" diff --git a/core/dutycycle/helpers_test.go b/core/dutycycle/helpers_test.go new file mode 100644 index 000000000..b2a614bd0 --- /dev/null +++ b/core/dutycycle/helpers_test.go @@ -0,0 +1,19 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package dutycycle + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/core/mocks" +) + +// ----- CHECK utilities +func CheckSubBands(t *testing.T, want subBand, got subBand) { + Check(t, want, got, "SubBands") +} + +func CheckUsages(t *testing.T, want map[subBand]uint, got map[subBand]uint) { + Check(t, want, got, "Usages") +} From 5722188f8eaafb54e49837a882700de7523c4b5f Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 5 Mar 2016 03:36:45 +0100 Subject: [PATCH 0911/2266] [dutycycle] Rename constructor to avoid redundancy with package name --- core/dutycycle/dutyManager.go | 5 ++--- core/dutycycle/dutyManager_test.go | 20 ++++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index a03634b96..7270c925f 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -68,8 +68,8 @@ func GetSubBand(freq float64) (subBand, error) { return "", errors.New(errors.Structural, "Unknown frequency") } -// NewDutyManager constructs a new gateway manager from -func NewDutyManager(filepath string, cycleLength time.Duration, r region) (DutyManager, error) { +// NewManager constructs a new gateway manager from +func NewManager(filepath string, cycleLength time.Duration, r region) (DutyManager, error) { var maxDuty map[subBand]float64 switch r { case Europe: @@ -165,7 +165,6 @@ func (m *dutyManager) Lookup(id []byte) (map[subBand]uint, error) { if entry.Until.After(time.Now()) { for s, toa := range entry.OnAir { // The actual duty cycle - fmt.Println(s, toa) dutyCycle := float64(toa.Nanoseconds()) / float64(m.CycleLength.Nanoseconds()) // Now, how full are we comparing to the limitation, in percent cycles[s] = uint(100 * dutyCycle / m.MaxDutyCycle[s]) diff --git a/core/dutycycle/dutyManager_test.go b/core/dutycycle/dutyManager_test.go index ab61c1c5d..e661e766b 100644 --- a/core/dutycycle/dutyManager_test.go +++ b/core/dutycycle/dutyManager_test.go @@ -53,7 +53,7 @@ func TestNewManager(t *testing.T) { }() { Desc(t, "Europe with valid cycleLength") - m, err := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, err := NewManager(dutyManagerDB, time.Minute, Europe) errutil.CheckErrors(t, nil, err) err = m.Close() errutil.CheckErrors(t, nil, err) @@ -61,13 +61,13 @@ func TestNewManager(t *testing.T) { { Desc(t, "Europe with invalid cycleLength") - _, err := NewDutyManager(dutyManagerDB, 0, Europe) + _, err := NewManager(dutyManagerDB, 0, Europe) errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) } { Desc(t, "Not europe with valid cycleLength") - _, err := NewDutyManager(dutyManagerDB, time.Minute, China) + _, err := NewManager(dutyManagerDB, time.Minute, China) errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) } } @@ -80,7 +80,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Update unsupported frequency") // Build - m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, _ := NewManager(dutyManagerDB, time.Minute, Europe) // Operate err := m.Update([]byte{1, 2, 3}, 433.65, 100, "SF8BW125", "4/5") @@ -95,7 +95,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Update invalid datr") // Build - m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, _ := NewManager(dutyManagerDB, time.Minute, Europe) // Operate err := m.Update([]byte{1, 2, 3}, 868.1, 100, "SF3BW125", "4/5") @@ -110,7 +110,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Update invalid codr") // Build - m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, _ := NewManager(dutyManagerDB, time.Minute, Europe) // Operate err := m.Update([]byte{1, 2, 3}, 869.5, 100, "SF8BW125", "14") @@ -125,7 +125,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Update once then lookup") // Build - m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, _ := NewManager(dutyManagerDB, time.Minute, Europe) // Operate err := m.Update([]byte{1, 2, 3}, 868.5, 14, "SF8BW125", "4/5") @@ -148,7 +148,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Update several then lookup") // Build - m, _ := NewDutyManager(dutyManagerDB, time.Minute, Europe) + m, _ := NewManager(dutyManagerDB, time.Minute, Europe) // Operate err := m.Update([]byte{4, 5, 6}, 868.523, 14, "SF8BW125", "4/5") @@ -176,7 +176,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Update out of cycle then lookup") // Build - m, _ := NewDutyManager(dutyManagerDB, 250*time.Millisecond, Europe) + m, _ := NewManager(dutyManagerDB, 250*time.Millisecond, Europe) // Operate err := m.Update([]byte{16, 2, 3}, 868.523, 14, "SF8BW125", "4/7") @@ -202,7 +202,7 @@ func TestUpdateAndLookup(t *testing.T) { Desc(t, "Lookup out of cycle") // Build - m, _ := NewDutyManager(dutyManagerDB, time.Millisecond, Europe) + m, _ := NewManager(dutyManagerDB, time.Millisecond, Europe) // Operate err := m.Update([]byte{1, 2, 35}, 868.523, 14, "SF8BW125", "4/8") From c0a8326fbbab03727693861c5eb19fa7cb95a0f3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Mar 2016 14:18:56 +0100 Subject: [PATCH 0912/2266] [cli] Update cli flags for Handler - TTN Broker is the broker where the handler will register devices - Defaults to localhost broker --- cmd/handler.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index ad3bb20b7..47984bc51 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -38,6 +38,7 @@ The default handler is the bridge between The Things Network and applications. "packetsDatabase": viper.GetString("handler.pkt-database"), "status-server": statusServer, "uplink": fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")), + "ttn-broker": viper.GetString("handler.ttn-broker"), "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") }, @@ -115,7 +116,7 @@ The default handler is the bridge between The Things Network and applications. } // Instantiate the broker to which is bound the handler - broker := http.NewRecipient(viper.GetString("handler.broker-client"), "PUT") + broker := http.NewRecipient(viper.GetString("handler.ttn-broker"), "PUT") // Instantiate the actual handler handler := handler.New(devicesDB, packetsDB, broker, ctx) @@ -199,6 +200,9 @@ func init() { viper.BindPFlag("handler.uplink-bind-address", handlerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) - handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker (uplink)") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) + + handlerCmd.Flags().String("ttn-broker", "localhost:1781", "The address of the TTN broker (downlink)") + viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) } From 1d5a2be47f7d01523042b954036bddedf1e083f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 13:59:43 +0100 Subject: [PATCH 0913/2266] [network-controller] Properly introduce frame counter in broker --- core/components/broker/broker.go | 66 ++++++++++++------------ core/components/broker/storage.go | 34 ++++++------ core/components/controller/controller.go | 4 -- core/core.go | 22 -------- core/packets_interfaces.go | 1 + 5 files changed, 53 insertions(+), 74 deletions(-) delete mode 100644 core/components/controller/controller.go diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 253d11a2f..1d6c8ce24 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -12,14 +12,13 @@ import ( // component implements the core.Component interface type component struct { - Storage - ctx log.Interface - Controller NetworkController + NetworkController + ctx log.Interface } // New construct a new Broker component -func New(db Storage, ctx log.Interface) Broker { - return component{Storage: db, ctx: ctx} +func New(controller NetworkController, ctx log.Interface) Broker { + return component{NetworkController: controller, ctx: ctx} } // Register implements the core.Broker interface @@ -60,10 +59,6 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { switch itf.(type) { case BPacket: - // NOTE So far, we're not using the Frame Counter. This has to be done to get a correct - // behavior. The frame counter needs to be used to ensure we're not processing a wrong or - // late packet. - // 0. Retrieve the packet packet := itf.(BPacket) ctx := b.ctx.WithField("DevEUI", packet.DevEUI()) @@ -84,10 +79,11 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // 2. Several handlers might be associated to the same device, we distinguish them using // MIC check. Only one should verify the MIC check - var mEntry *devEntry for _, entry := range entries { - ok, err := packet.ValidateMIC(entry.NwkSKey) + // The device only stores a 16-bits counter but could reflect a 32-bits one. + // We keep track of the real counter in the network controller. + ok, err := packet.ValidateMIC(entry.NwkSKey, entry.FCnt) if err != nil { continue } @@ -109,12 +105,9 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check - // 3. If one was found, we notify the network controller - //if err := b.Controller.HandleCommands(packet); err != nil { - // ctx.WithError(err).Error("Failed to handle mac commands") - // Shall we return ? Sounds quite safe to keep going - //} - //b.Controller.UpdateFCntUp(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt()) + // We can avoid checking the error, this has been done during the MIC check above + fcnt, _ := packet.FCnt(mEntry.FCnt) + b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, fcnt, "up") // 4. Then we forward the packet to the handler and wait for the response hpacket, err := NewHPacket(mEntry.AppEUI, mEntry.DevEUI, packet.Payload(), packet.Metadata()) @@ -125,7 +118,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if err != nil { return errors.New(errors.Structural, err) } - _, err = up.Send(hpacket, recipient) + resp, err := up.Send(hpacket, recipient) if err != nil { stats.MarkMeter("broker.uplink.bad_handler_response") return errors.New(errors.Operational, err) @@ -133,22 +126,31 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { stats.MarkMeter("broker.uplink.ok") // 5. If a response was sent, i.e. a downlink data, we notify the network controller - //var bpacket BPacket - //if resp != nil { - // itf, err := UnmarshalPacket(resp) - // if err != nil { - // return errors.New(errors.Operational, err) - // } - // var ok bool - // bpacket, ok = itf.(BPacket) - // if !ok { - // return errors.New(errors.Operational, "Received unexpected response") - // } - // //b.Controller.UpdateFCntDown(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt()) - //} + var bpacket BPacket + if resp != nil { + itf, err := UnmarshalPacket(resp) + if err != nil { + return errors.New(errors.Operational, err) + } + var ok bool + bpacket, ok = itf.(BPacket) + if !ok { + return errors.New(errors.Operational, "Received unexpected response") + } + + fcnt, err := bpacket.FCnt(mEntry.FcntDown) + if err != nil { + return errors.New(errors.Structural, "Invalid frame counter for downlink") + } + b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, fcnt, "down") + } // 6. And finally, we acknowledge the answer - //ack = b.Controller.MergeCommands(mEntry.AppEUI, mEntry.DevEUI, bpacket) + rpacket, err := NewRPacket(bpacket.Payload(), bpacket.Metadata()) + if err != nil { + return errors.New(errors.Structural, "Invalid downlink packet from the handler") + } + ack = rpacket case JPacket: // TODO return errors.New(errors.Implementation, "Join Request not yet implemented") diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index a63079b2b..208d7b1c5 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -11,8 +11,9 @@ import ( "github.com/brocaar/lorawan" ) -// Storage gives a facade for manipulating the broker database -type Storage interface { +// NetworkController gives a facade for manipulating the broker databases and devices +type NetworkController interface { + UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) StoreDevice(reg core.BRegistration) error @@ -25,6 +26,7 @@ type devEntry struct { AppEUI lorawan.EUI64 DevEUI lorawan.EUI64 NwkSKey lorawan.AES128Key + FCnt uint32 } type appEntry struct { @@ -32,24 +34,24 @@ type appEntry struct { AppEUI lorawan.EUI64 } -type storage struct { +type controller struct { db dbutil.Interface Devices string Applications string } -// NewStorage constructs a new broker storage -func NewStorage(name string) (Storage, error) { +// NewNetworkController constructs a new broker controller +func NewNetworkController(name string) (NetworkController, error) { itf, err := dbutil.New(name) if err != nil { return nil, errors.New(errors.Operational, err) } - return storage{db: itf, Devices: "Devices", Applications: "Applications"}, nil + return controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil } -// LookupDevices implements the broker.Storage interface -func (s storage) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { +// LookupDevices implements the broker.NetworkController interface +func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) if err != nil { return nil, err @@ -57,8 +59,8 @@ func (s storage) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { return entries.([]devEntry), nil } -// LookupApplication implements the broker.Storage interface -func (s storage) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { +// LookupApplication implements the broker.NetworkController interface +func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{}) if err != nil { return appEntry{}, err @@ -73,8 +75,8 @@ func (s storage) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { return entries[0], nil } -// StoreDevice implements the broker.Storage interface -func (s storage) StoreDevice(reg core.BRegistration) error { +// StoreDevice implements the broker.NetworkController interface +func (s controller) StoreDevice(reg core.BRegistration) error { data, err := reg.Recipient().MarshalBinary() if err != nil { return errors.New(errors.Structural, err) @@ -91,8 +93,8 @@ func (s storage) StoreDevice(reg core.BRegistration) error { }) } -// StoreApplication implements the broker.Storage interface -func (s storage) StoreApplication(reg core.ARegistration) error { +// StoreApplication implements the broker.NetworkController interface +func (s controller) StoreApplication(reg core.ARegistration) error { data, err := reg.Recipient().MarshalBinary() if err != nil { return errors.New(errors.Structural, err) @@ -107,8 +109,8 @@ func (s storage) StoreApplication(reg core.ARegistration) error { }) } -// Close implements the broker.Storage interface -func (s storage) Close() error { +// Close implements the broker.NetworkController interface +func (s controller) Close() error { return s.db.Close() } diff --git a/core/components/controller/controller.go b/core/components/controller/controller.go deleted file mode 100644 index a383f59cd..000000000 --- a/core/components/controller/controller.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package controller diff --git a/core/core.go b/core/core.go index 96fa658ba..cf86b5294 100644 --- a/core/core.go +++ b/core/core.go @@ -3,20 +3,6 @@ package core -import ( - "github.com/brocaar/lorawan" -) - -// Component materializes a core component of the network: Router, Broker or Handler. -// -// With two adapters, it can communicates with others components and thus create a distributed -// network of components. -type Component interface { - Register(reg Registration, an AckNacker) error - HandleUp(p []byte, an AckNacker, upAdapter Adapter) error - HandleDown(p []byte, an AckNacker) error -} - type Handler interface { Register(reg Registration, an AckNacker, s Subscriber) error HandleUp(p []byte, an AckNacker, upAdapter Adapter) error @@ -33,14 +19,6 @@ type Broker interface { HandleUp(p []byte, an AckNacker, up Adapter) error } -// NetworkController is directly used by the broker to manage nodes lifecycle. -type NetworkController interface { - HandleCommands(packet BPacket) error - UpdateFCntUp(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) - UpdateFCntDown(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) - MergeCommands(appEUI lorawan.EUI64, devEUI lorawan.EUI64, pkt BPacket) RPacket -} - // AckNacker represents an interface that allow adapters to decouple their protocol from the // behaviour expected by the caller. type AckNacker interface { diff --git a/core/packets_interfaces.go b/core/packets_interfaces.go index b9c6684ec..8036d5cde 100644 --- a/core/packets_interfaces.go +++ b/core/packets_interfaces.go @@ -36,6 +36,7 @@ type SPacket interface { type BPacket interface { Packet Commands() []lorawan.MACCommand + ComputeFCnt(wholeCnt uint32) error FCnt() uint32 Metadata() Metadata Payload() lorawan.PHYPayload From 06ba59fbd8046be5be1a8bfdccdcb8ea0eab58a8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 15:17:33 +0100 Subject: [PATCH 0914/2266] [network-controller] Implement necessary method in packets + add some tests --- core/packets.go | 24 ++++++++++++++++++++++-- core/packets_test.go | 30 +++++++++++++++++++++--------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/core/packets.go b/core/packets.go index f87229862..77696e121 100644 --- a/core/packets.go +++ b/core/packets.go @@ -6,6 +6,7 @@ package core import ( "encoding" "fmt" + "math" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" @@ -192,7 +193,7 @@ func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) } // ValidateMIC implements the core.BPacket interface -func (p bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { +func (p *bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { return p.baserpacket.payload.ValidateMIC(key) } @@ -274,7 +275,7 @@ func (p hpacket) Payload(key lorawan.AES128Key) ([]byte, error) { // FCnt implements the core.HPacket interface func (p hpacket) FCnt() uint32 { - return p.payload.FCnt() + return p.payload.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt } // MarshalBinary implements the encoding.BinaryMarshaler interface @@ -515,6 +516,25 @@ func (p baserpacket) DevEUI() lorawan.EUI64 { return devEUI } +// ComputeFCnt implements the core.BPacket interface +func (p *baserpacket) ComputeFCnt(wholeCnt uint32) error { + upperSup := uint32(math.Pow(2, 16)) + fcnt := p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt + diff := fcnt - (wholeCnt % (upperSup + 1)) + var offset uint32 + if diff >= 0 { + offset = diff + } else { + offset = upperSup + diff + } + if offset > upperSup/4 { + return errors.New(errors.Structural, "Gap too big, counter is errored") + } + + p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt + offset + return nil +} + // FCnt implements the core.BPacket interface func (p baserpacket) FCnt() uint32 { return p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt diff --git a/core/packets_test.go b/core/packets_test.go index a16ed29d9..7ad227737 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -26,15 +26,15 @@ func newEUI() lorawan.EUI64 { return devEUI } -func simplePayload() (payload lorawan.PHYPayload, devAddr lorawan.DevAddr, key lorawan.AES128Key) { +func simplePayload(fcnt uint32) (payload lorawan.PHYPayload, devAddr lorawan.DevAddr, key lorawan.AES128Key) { copy(devAddr[:], randBytes(4)) copy(key[:], randBytes(16)) - payload = newPayload(devAddr, []byte("PLD123"), key, key) + payload = newPayload(devAddr, []byte("PLD123"), key, key, fcnt) return } -func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, nwkSKey lorawan.AES128Key) lorawan.PHYPayload { +func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, nwkSKey lorawan.AES128Key, fcnt uint32) lorawan.PHYPayload { uplink := true macPayload := lorawan.NewMACPayload(uplink) @@ -45,7 +45,7 @@ func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, ADRACKReq: false, ACK: false, }, - FCnt: 1, + FCnt: fcnt, } macPayload.FPort = 10 macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: data}} @@ -92,7 +92,7 @@ func TestBaseMarshalUnmarshal(t *testing.T) { s := uint(123) mpkt := basempacket{metadata: Metadata{Size: &s}} - payload, _, _ := simplePayload() + payload, _, _ := simplePayload(1) rpkt := baserpacket{payload: payload} hpkt := basehpacket{ appEUI: newEUI(), @@ -198,7 +198,7 @@ func TestInvalidRPacket(t *testing.T) { func TestRPacket(t *testing.T) { a := New(t) - payload, devAddr, _ := simplePayload() + payload, devAddr, _ := simplePayload(1) gwEUI := []byte{} copy(gwEUI[:], randBytes(8)) @@ -249,20 +249,32 @@ func TestInvalidBPacket(t *testing.T) { }, }, Metadata{}) a.So(err4, ShouldNotBeNil) + + // FCnt out of bound + var wholeCnt uint32 = 78765436 + payload, _, _ := simplePayload(1) + payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65537 + 65536/2 + input, _ := NewBPacket(payload, Metadata{}) + err := input.ComputeFCnt(wholeCnt) + a.So(err, ShouldNotBeNil) } func TestBPacket(t *testing.T) { a := New(t) - payload, _, key := simplePayload() + var wholeCnt uint32 = 78765436 + payload, _, key := simplePayload(wholeCnt + 1) + payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65537 + 1 input, _ := NewBPacket(payload, Metadata{}) gOutput := marshalUnmarshal(t, input) - output := gOutput.(BPacket) a.So(output.Payload(), ShouldResemble, payload) a.So(output.Metadata(), ShouldResemble, Metadata{}) + err := output.ComputeFCnt(wholeCnt) + a.So(err, ShouldBeNil) + a.So(output.FCnt(), ShouldEqual, wholeCnt+1) outputValidateMIC, _ := output.ValidateMIC(key) a.So(outputValidateMIC, ShouldBeTrue) a.So(output.Commands(), ShouldBeEmpty) @@ -306,7 +318,7 @@ func TestHPacket(t *testing.T) { appEUI := newEUI() devEUI := newEUI() - payload, _, key := simplePayload() + payload, _, key := simplePayload(1) input, _ := NewHPacket(appEUI, devEUI, payload, Metadata{}) From b118edade1b044545fed09227e7295431913cf96 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 15:35:09 +0100 Subject: [PATCH 0915/2266] [network-controller] Distinguish FCnt entry for uplink and downlink --- core/components/broker/broker.go | 19 +++++++++---------- core/components/broker/storage.go | 3 ++- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 1d6c8ce24..318ddf00f 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -83,7 +83,10 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { for _, entry := range entries { // The device only stores a 16-bits counter but could reflect a 32-bits one. // We keep track of the real counter in the network controller. - ok, err := packet.ValidateMIC(entry.NwkSKey, entry.FCnt) + if err := packet.ComputeFCnt(entry.FCntUp); err != nil { + continue + } + ok, err := packet.ValidateMIC(entry.NwkSKey) if err != nil { continue } @@ -105,9 +108,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check - // We can avoid checking the error, this has been done during the MIC check above - fcnt, _ := packet.FCnt(mEntry.FCnt) - b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, fcnt, "up") + b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt(), "up") // 4. Then we forward the packet to the handler and wait for the response hpacket, err := NewHPacket(mEntry.AppEUI, mEntry.DevEUI, packet.Payload(), packet.Metadata()) @@ -137,16 +138,14 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if !ok { return errors.New(errors.Operational, "Received unexpected response") } - - fcnt, err := bpacket.FCnt(mEntry.FcntDown) - if err != nil { - return errors.New(errors.Structural, "Invalid frame counter for downlink") + if err := bpacket.ComputeFCnt(mEntry.FCntDown); err != nil { + return errors.New(errors.Structural, "Received invalid response > frame counter incorrect") } - b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, fcnt, "down") + b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt(), "down") } // 6. And finally, we acknowledge the answer - rpacket, err := NewRPacket(bpacket.Payload(), bpacket.Metadata()) + rpacket, err := NewRPacket(bpacket.Payload(), []byte{}, bpacket.Metadata()) if err != nil { return errors.New(errors.Structural, "Invalid downlink packet from the handler") } diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 208d7b1c5..2216d8f20 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -26,7 +26,8 @@ type devEntry struct { AppEUI lorawan.EUI64 DevEUI lorawan.EUI64 NwkSKey lorawan.AES128Key - FCnt uint32 + FCntUp uint32 + FCntDown uint32 } type appEntry struct { From eec5ef401ed1cbbbceb40af625d1d03570ad96ac Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 16:04:26 +0100 Subject: [PATCH 0916/2266] [network-controller] Rewrite tests and names to be consisten with new network controller --- core/components/broker/broker.go | 10 +++--- core/components/broker/broker_test.go | 22 ++++++------- core/components/broker/mock_test.go | 32 +++++++++++++------ core/components/broker/storage.go | 41 +++++++++++++++++++++++- core/components/broker/storage_test.go | 44 +++++++++++++------------- 5 files changed, 101 insertions(+), 48 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 318ddf00f..db683eb2b 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -145,11 +145,13 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } // 6. And finally, we acknowledge the answer - rpacket, err := NewRPacket(bpacket.Payload(), []byte{}, bpacket.Metadata()) - if err != nil { - return errors.New(errors.Structural, "Invalid downlink packet from the handler") + if bpacket != nil { + rpacket, err := NewRPacket(bpacket.Payload(), []byte{}, bpacket.Metadata()) + if err != nil { + return errors.New(errors.Structural, "Invalid downlink packet from the handler") + } + ack = rpacket } - ack = rpacket case JPacket: // TODO return errors.New(errors.Implementation, "Join Request not yet implemented") diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 348bd5161..2daeafba0 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -21,7 +21,7 @@ func TestRegister(t *testing.T) { // Build an := mocks.NewMockAckNacker() - store := newMockStorage() + store := newMockController() r := mocks.NewMockBRegistration() // Operate @@ -42,7 +42,7 @@ func TestRegister(t *testing.T) { // Build an := mocks.NewMockAckNacker() - store := newMockStorage() + store := newMockController() r := mocks.NewMockARegistration() // Operate @@ -63,7 +63,7 @@ func TestRegister(t *testing.T) { // Build an := mocks.NewMockAckNacker() - store := newMockStorage() + store := newMockController() store.Failures["StoreDevice"] = errors.New(errors.Structural, "Mock Error: Store Failed") r := mocks.NewMockBRegistration() @@ -85,7 +85,7 @@ func TestRegister(t *testing.T) { // Build an := mocks.NewMockAckNacker() - store := newMockStorage() + store := newMockController() store.Failures["StoreApplication"] = errors.New(errors.Structural, "Mock Error: Store Failed") r := mocks.NewMockARegistration() @@ -107,7 +107,7 @@ func TestRegister(t *testing.T) { // Build an := mocks.NewMockAckNacker() - store := newMockStorage() + store := newMockController() r := mocks.NewMockRRegistration() // Operate @@ -129,7 +129,7 @@ func TestHandleUp(t *testing.T) { // Build an := mocks.NewMockAckNacker() adapter := mocks.NewMockAdapter() - store := newMockStorage() + store := newMockController() store.Failures["LookupDevices"] = errors.New(errors.Behavioural, "Mock Error: Not Found") data, _ := newBPacket( [4]byte{2, 3, 2, 3}, @@ -159,7 +159,7 @@ func TestHandleUp(t *testing.T) { // Build an := mocks.NewMockAckNacker() adapter := mocks.NewMockAdapter() - store := newMockStorage() + store := newMockController() // Operate broker := New(store, testutil.GetLogger(t, "Broker")) @@ -182,7 +182,7 @@ func TestHandleUp(t *testing.T) { // Build an := mocks.NewMockAckNacker() adapter := mocks.NewMockAdapter() - store := newMockStorage() + store := newMockController() store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, @@ -228,7 +228,7 @@ func TestHandleUp(t *testing.T) { adapter := mocks.NewMockAdapter() adapter.OutSend = nil adapter.OutGetRecipient = recipient - store := newMockStorage() + store := newMockController() store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, @@ -280,7 +280,7 @@ func TestHandleUp(t *testing.T) { adapter := mocks.NewMockAdapter() adapter.OutSend = nil adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Unable to get recipient") - store := newMockStorage() + store := newMockController() store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, @@ -327,7 +327,7 @@ func TestHandleUp(t *testing.T) { adapter := mocks.NewMockAdapter() adapter.OutGetRecipient = recipient adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") - store := newMockStorage() + store := newMockController() store.OutLookupDevices = []devEntry{ { Recipient: []byte{1, 2, 3}, diff --git a/core/components/broker/mock_test.go b/core/components/broker/mock_test.go index c4a4a02ce..4b91a45a8 100644 --- a/core/components/broker/mock_test.go +++ b/core/components/broker/mock_test.go @@ -8,26 +8,30 @@ import ( "github.com/brocaar/lorawan" ) -type mockStorage struct { +type mockController struct { Failures map[string]error InLookupDevices lorawan.EUI64 InLookupApp lorawan.EUI64 InStoreDevices core.BRegistration InStoreApp core.ARegistration + InUpdateAppEUI lorawan.EUI64 + InUpdateDevEUI lorawan.EUI64 + InUpdateFCnt uint32 + InUpdateDir string OutLookupDevices []devEntry OutLookupApp appEntry } -func newMockStorage() *mockStorage { - return &mockStorage{ +func newMockController() *mockController { + return &mockController{ Failures: make(map[string]error), OutLookupApp: appEntry{ - Recipient: []byte("MockStorageRecipient"), + Recipient: []byte("MockControllerRecipient"), AppEUI: lorawan.EUI64([8]byte{5, 5, 5, 5, 5, 5, 6, 6}), }, OutLookupDevices: []devEntry{ { - Recipient: []byte("MockStorageRecipient"), + Recipient: []byte("MockControllerRecipient"), AppEUI: lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 1, 1}), DevEUI: lorawan.EUI64([8]byte{2, 3, 4, 5, 6, 7, 5, 4}), NwkSKey: lorawan.AES128Key([16]byte{1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5}), @@ -36,7 +40,7 @@ func newMockStorage() *mockStorage { } } -func (s *mockStorage) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { +func (s *mockController) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { s.InLookupDevices = devEUI if s.Failures["LookupDevices"] != nil { return nil, s.Failures["LookupDevices"] @@ -44,7 +48,7 @@ func (s *mockStorage) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { return s.OutLookupDevices, nil } -func (s *mockStorage) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { +func (s *mockController) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { s.InLookupApp = appEUI if s.Failures["LookupApplication"] != nil { return appEntry{}, s.Failures["LookupApplication"] @@ -52,16 +56,24 @@ func (s *mockStorage) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) return s.OutLookupApp, nil } -func (s *mockStorage) StoreDevice(reg core.BRegistration) error { +func (s *mockController) StoreDevice(reg core.BRegistration) error { s.InStoreDevices = reg return s.Failures["StoreDevice"] } -func (s *mockStorage) StoreApplication(reg core.ARegistration) error { +func (s *mockController) StoreApplication(reg core.ARegistration) error { s.InStoreApp = reg return s.Failures["StoreApplication"] } -func (s *mockStorage) Close() error { +func (s *mockController) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error { + s.InUpdateAppEUI = appEUI + s.InUpdateDevEUI = devEUI + s.InUpdateFCnt = fcnt + s.InUpdateDir = dir + return s.Failures["UpdateFCnt"] +} + +func (s *mockController) Close() error { return s.Failures["Close"] } diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 2216d8f20..e585aeb25 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -4,6 +4,8 @@ package broker import ( + "sync" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/readwriter" @@ -13,7 +15,7 @@ import ( // NetworkController gives a facade for manipulating the broker databases and devices type NetworkController interface { - UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) + UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) StoreDevice(reg core.BRegistration) error @@ -36,6 +38,7 @@ type appEntry struct { } type controller struct { + sync.RWMutex db dbutil.Interface Devices string Applications string @@ -53,6 +56,8 @@ func NewNetworkController(name string) (NetworkController, error) { // LookupDevices implements the broker.NetworkController interface func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { + s.RLock() + defer s.RUnlock() entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) if err != nil { return nil, err @@ -62,6 +67,8 @@ func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { // LookupApplication implements the broker.NetworkController interface func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { + s.RLock() + defer s.RUnlock() itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{}) if err != nil { return appEntry{}, err @@ -76,8 +83,38 @@ func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { return entries[0], nil } +// UpdateFCnt implements the broker.NetworkController interface +func (s controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error { + s.Lock() + defer s.Unlock() + itf, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) + if err != nil { + return err + } + entries := itf.([]devEntry) + + var newEntries []dbutil.Entry + for _, e := range entries { + if e.AppEUI == appEUI { + switch dir { + case "up": + e.FCntUp = fcnt + case "down": + e.FCntDown = fcnt + default: + return errors.New(errors.Implementation, "Unreckognized direction") + } + } + newEntries = append(newEntries, &e) + } + + return s.db.Replace(s.Devices, devEUI[:], newEntries) +} + // StoreDevice implements the broker.NetworkController interface func (s controller) StoreDevice(reg core.BRegistration) error { + s.Lock() + defer s.Unlock() data, err := reg.Recipient().MarshalBinary() if err != nil { return errors.New(errors.Structural, err) @@ -96,6 +133,8 @@ func (s controller) StoreDevice(reg core.BRegistration) error { // StoreApplication implements the broker.NetworkController interface func (s controller) StoreApplication(reg core.ARegistration) error { + s.Lock() + defer s.Unlock() data, err := reg.Recipient().MarshalBinary() if err != nil { return errors.New(errors.Structural, err) diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index 7c8ad3cdb..848ff8956 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -16,19 +16,19 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/testing" ) -const storageDB = "TestBrokerStorage.db" +const NetworkControllerDB = "TestBrokerNetworkController.db" -func TestStorageDevice(t *testing.T) { - storageDB := path.Join(os.TempDir(), storageDB) +func TestNetworkControllerDevice(t *testing.T) { + NetworkControllerDB := path.Join(os.TempDir(), NetworkControllerDB) defer func() { - os.Remove(storageDB) + os.Remove(NetworkControllerDB) }() // ------------------- { - Desc(t, "Create a new storage") - db, err := NewStorage(storageDB) + Desc(t, "Create a new NetworkController") + db, err := NewNetworkController(NetworkControllerDB) CheckErrors(t, nil, err) err = db.Close() CheckErrors(t, nil, err) @@ -40,7 +40,7 @@ func TestStorageDevice(t *testing.T) { Desc(t, "Store then lookup a registration") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) r := NewMockBRegistration() // Operate @@ -70,7 +70,7 @@ func TestStorageDevice(t *testing.T) { Desc(t, "Store entries with same DevEUI") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) r := NewMockBRegistration() r.OutDevEUI[0] = 34 @@ -109,7 +109,7 @@ func TestStorageDevice(t *testing.T) { Desc(t, "Lookup non-existing entry") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) devEUI := NewMockBRegistration().DevEUI() devEUI[1] = 98 @@ -128,7 +128,7 @@ func TestStorageDevice(t *testing.T) { Desc(t, "Store on a closed database") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) _ = db.Close() r := NewMockBRegistration() r.OutDevEUI[5] = 9 @@ -146,7 +146,7 @@ func TestStorageDevice(t *testing.T) { Desc(t, "Lookup on a closed database") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) _ = db.Close() devEUI := NewMockBRegistration().DevEUI() @@ -164,7 +164,7 @@ func TestStorageDevice(t *testing.T) { Desc(t, "Store an invalid recipient") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) r := NewMockBRegistration() r.OutDevEUI[7] = 99 r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") @@ -180,16 +180,16 @@ func TestStorageDevice(t *testing.T) { } } -func TestStorageApplication(t *testing.T) { +func TestNetworkControllerApplication(t *testing.T) { defer func() { - os.Remove(storageDB) + os.Remove(NetworkControllerDB) }() // ------------------- { - Desc(t, "Create a new storage") - db, err := NewStorage(storageDB) + Desc(t, "Create a new NetworkController") + db, err := NewNetworkController(NetworkControllerDB) CheckErrors(t, nil, err) err = db.Close() CheckErrors(t, nil, err) @@ -201,7 +201,7 @@ func TestStorageApplication(t *testing.T) { Desc(t, "Store then lookup a registration") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) r := NewMockARegistration() // Operate @@ -227,7 +227,7 @@ func TestStorageApplication(t *testing.T) { Desc(t, "Store entries with same AppEUI") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) r1 := NewMockARegistration() r1.OutAppEUI[0] = 34 r2 := NewMockARegistration() @@ -260,7 +260,7 @@ func TestStorageApplication(t *testing.T) { Desc(t, "Lookup non-existing entry") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) appEUI := NewMockARegistration().AppEUI() appEUI[1] = 98 @@ -279,7 +279,7 @@ func TestStorageApplication(t *testing.T) { Desc(t, "Store on a closed database") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) _ = db.Close() r := NewMockARegistration() r.OutAppEUI[5] = 9 @@ -297,7 +297,7 @@ func TestStorageApplication(t *testing.T) { Desc(t, "Lookup on a closed database") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) _ = db.Close() appEUI := NewMockARegistration().AppEUI() @@ -315,7 +315,7 @@ func TestStorageApplication(t *testing.T) { Desc(t, "Store an invalid recipient") // Build - db, _ := NewStorage(storageDB) + db, _ := NewNetworkController(NetworkControllerDB) r := NewMockARegistration() r.OutAppEUI[7] = 99 r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") From 8bd49ec19c53c7adba66523addfee1325765f8e2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 16:53:15 +0100 Subject: [PATCH 0917/2266] [network-controller] Implement new tests for updateFCnt method() --- core/components/broker/storage_test.go | 177 +++++++++++++++++++++++++ 1 file changed, 177 insertions(+) diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index 848ff8956..23a690e7c 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -55,6 +55,8 @@ func TestNetworkControllerDevice(t *testing.T) { DevEUI: r.DevEUI(), NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), + FCntUp: 0, + FCntDown: 0, }, } @@ -88,12 +90,16 @@ func TestNetworkControllerDevice(t *testing.T) { DevEUI: r.DevEUI(), NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), + FCntUp: 0, + FCntDown: 0, }, { AppEUI: r.AppEUI(), DevEUI: r.DevEUI(), NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), + FCntUp: 0, + FCntDown: 0, }, } @@ -178,6 +184,177 @@ func TestNetworkControllerDevice(t *testing.T) { _ = db.Close() } + + // ------------------- + + { + Desc(t, "Update counter up of an entry -> one device") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + r := NewMockBRegistration() + r.OutDevEUI[4] = 0xba + + // Operate + err := db.StoreDevice(r) + CheckErrors(t, nil, err) + err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "up") + entries, err2 := db.LookupDevices(r.DevEUI()) + + // Expectations + want := []devEntry{ + { + AppEUI: r.AppEUI(), + DevEUI: r.DevEUI(), + NwkSKey: r.NwkSKey(), + Recipient: r.RawRecipient(), + FCntUp: 14, + FCntDown: 0, + }, + } + + // Check + CheckErrors(t, nil, err1) + CheckErrors(t, nil, err2) + CheckDevEntries(t, want, entries) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Update counter down of an entry -> one device") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + r := NewMockBRegistration() + r.OutDevEUI[4] = 0xbb + + // Operate + err := db.StoreDevice(r) + CheckErrors(t, nil, err) + err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "down") + entries, err2 := db.LookupDevices(r.DevEUI()) + + // Expectations + want := []devEntry{ + { + AppEUI: r.AppEUI(), + DevEUI: r.DevEUI(), + NwkSKey: r.NwkSKey(), + Recipient: r.RawRecipient(), + FCntUp: 0, + FCntDown: 14, + }, + } + + // Check + CheckErrors(t, nil, err1) + CheckErrors(t, nil, err2) + CheckDevEntries(t, want, entries) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Update counter with wrong direction") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + r := NewMockBRegistration() + r.OutDevEUI[4] = 0xbd + + // Operate + err := db.StoreDevice(r) + CheckErrors(t, nil, err) + err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "patate") + entries, err2 := db.LookupDevices(r.DevEUI()) + + // Expectations + want := []devEntry{ + { + AppEUI: r.AppEUI(), + DevEUI: r.DevEUI(), + NwkSKey: r.NwkSKey(), + Recipient: r.RawRecipient(), + FCntUp: 0, + FCntDown: 0, + }, + } + + // Checks + CheckErrors(t, pointer.String(string(errors.Implementation)), err1) + CheckErrors(t, nil, err2) + CheckDevEntries(t, want, entries) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Update counter -> fail to lookup") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + r := NewMockBRegistration() + r.OutDevEUI[4] = 0xde + + // Operate + err := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "up") + + // Checks + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Update counter several entries") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + r1 := NewMockBRegistration() + r1.OutDevEUI[3] = 0xbb + r2 := NewMockBRegistration() + r2.OutDevEUI[3] = 0xbb + r2.OutAppEUI[4] = 14 + + // Operate + err := db.StoreDevice(r1) + CheckErrors(t, nil, err) + err = db.StoreDevice(r2) + CheckErrors(t, nil, err) + err1 := db.UpdateFCnt(r2.AppEUI(), r2.DevEUI(), 14, "up") + entries, err2 := db.LookupDevices(r2.DevEUI()) + + // Expectations + want := []devEntry{ + { + AppEUI: r1.AppEUI(), + DevEUI: r1.DevEUI(), + NwkSKey: r1.NwkSKey(), + Recipient: r1.RawRecipient(), + FCntUp: 0, + FCntDown: 0, + }, + { + AppEUI: r2.AppEUI(), + DevEUI: r2.DevEUI(), + NwkSKey: r2.NwkSKey(), + Recipient: r2.RawRecipient(), + FCntUp: 14, + FCntDown: 0, + }, + } + + // Check + CheckErrors(t, nil, err1) + CheckErrors(t, nil, err2) + CheckDevEntries(t, want, entries) + _ = db.Close() + } } func TestNetworkControllerApplication(t *testing.T) { From dbd68c3fe8fd9df24ef29ac656c6e2e1171606d4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 16:53:43 +0100 Subject: [PATCH 0918/2266] [network-controller] Fix issues according to test results --- core/components/broker/storage.go | 34 +++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index e585aeb25..372072b47 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -4,6 +4,8 @@ package broker import ( + "bytes" + "encoding/binary" "sync" "github.com/TheThingsNetwork/ttn/core" @@ -95,17 +97,19 @@ func (s controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt var newEntries []dbutil.Entry for _, e := range entries { - if e.AppEUI == appEUI { + entry := new(devEntry) + *entry = e + if entry.AppEUI == appEUI { switch dir { case "up": - e.FCntUp = fcnt + entry.FCntUp = fcnt case "down": - e.FCntDown = fcnt + entry.FCntDown = fcnt default: return errors.New(errors.Implementation, "Unreckognized direction") } } - newEntries = append(newEntries, &e) + newEntries = append(newEntries, entry) } return s.db.Replace(s.Devices, devEUI[:], newEntries) @@ -161,6 +165,8 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.AppEUI) rw.Write(e.DevEUI) rw.Write(e.NwkSKey) + rw.Write(e.FCntUp) + rw.Write(e.FCntDown) return rw.Bytes() } @@ -171,6 +177,26 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.AppEUI[:], data) }) rw.Read(func(data []byte) { copy(e.DevEUI[:], data) }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + rw.TryRead(func(data []byte) error { + buf := new(bytes.Buffer) + buf.Write(data) + fcnt := new(uint32) + if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil { + return err + } + e.FCntUp = *fcnt + return nil + }) + rw.TryRead(func(data []byte) error { + buf := new(bytes.Buffer) + buf.Write(data) + fcnt := new(uint32) + if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil { + return err + } + e.FCntDown = *fcnt + return nil + }) return rw.Err() } From c20db9bc273bc4ff159a2d7bbb4836fb80eba4a3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 17:15:14 +0100 Subject: [PATCH 0919/2266] [network-controller] Fix counter rollup boundaries --- core/packets.go | 2 +- core/packets_test.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/packets.go b/core/packets.go index 77696e121..4ae786d59 100644 --- a/core/packets.go +++ b/core/packets.go @@ -520,7 +520,7 @@ func (p baserpacket) DevEUI() lorawan.EUI64 { func (p *baserpacket) ComputeFCnt(wholeCnt uint32) error { upperSup := uint32(math.Pow(2, 16)) fcnt := p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt - diff := fcnt - (wholeCnt % (upperSup + 1)) + diff := fcnt - (wholeCnt % upperSup) var offset uint32 if diff >= 0 { offset = diff diff --git a/core/packets_test.go b/core/packets_test.go index 7ad227737..a12cf93aa 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -253,7 +253,7 @@ func TestInvalidBPacket(t *testing.T) { // FCnt out of bound var wholeCnt uint32 = 78765436 payload, _, _ := simplePayload(1) - payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65537 + 65536/2 + payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65536 + 65536/2 input, _ := NewBPacket(payload, Metadata{}) err := input.ComputeFCnt(wholeCnt) a.So(err, ShouldNotBeNil) @@ -264,7 +264,7 @@ func TestBPacket(t *testing.T) { var wholeCnt uint32 = 78765436 payload, _, key := simplePayload(wholeCnt + 1) - payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65537 + 1 + payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65536 + 1 input, _ := NewBPacket(payload, Metadata{}) gOutput := marshalUnmarshal(t, input) From d99ddc036ca8df44a60e4215e2027130e294fe86 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 17:34:54 +0100 Subject: [PATCH 0920/2266] [hotfix] Indicate protocol endpoints in the http adapter --- core/adapters/http/http.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index f13a9aceb..9791b3d20 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -86,7 +86,7 @@ func (a *Adapter) Subscribe(r core.Registration) error { } buf := new(bytes.Buffer) buf.Write(data) - resp, err := a.Post(fmt.Sprintf("http://%s", httpRecipient.URL()), "application/json", buf) + resp, err := a.Post(fmt.Sprintf("http://%s/end-devices", httpRecipient.URL()), "application/json", buf) if err != nil { return errors.New(errors.Operational, err) } @@ -149,7 +149,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err ctx.Debugf("%s Request", recipient.Method()) buf := new(bytes.Buffer) buf.Write(data) - resp, err := a.Post(fmt.Sprintf("http://%s", recipient.URL()), "application/octet-stream", buf) + resp, err := a.Post(fmt.Sprintf("http://%s/packets", recipient.URL()), "application/octet-stream", buf) if err != nil { cherr <- errors.New(errors.Operational, err) return From bdd0bab7dbd983eefbbfdc540ef16bdda6acc97e Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 18:04:22 +0100 Subject: [PATCH 0921/2266] [network-controller] Add new test to completely test the broker uplink / downlink --- core/components/broker/broker_test.go | 251 +++++++++++++++++++++++++ core/components/broker/helpers_test.go | 39 ++++ 2 files changed, 290 insertions(+) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 2daeafba0..c2e4e4eb9 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -149,6 +149,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) + CheckCounters(t, 0, store.InUpdateFCnt) + CheckDirections(t, "", store.InUpdateDir) } // ------------------- @@ -172,6 +174,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) + CheckCounters(t, 0, store.InUpdateFCnt) + CheckDirections(t, "", store.InUpdateDir) } // ------------------- @@ -215,6 +219,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) + CheckCounters(t, 0, store.InUpdateFCnt) + CheckDirections(t, "", store.InUpdateDir) } // ------------------- @@ -268,6 +274,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) + CheckCounters(t, 5, store.InUpdateFCnt) + CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -314,6 +322,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) + CheckCounters(t, 5, store.InUpdateFCnt) + CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -367,5 +377,246 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) + CheckCounters(t, 5, store.InUpdateFCnt) + CheckDirections(t, "up", store.InUpdateDir) + } + + // ------------------- + + { + testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 valid downlink") + + // Build + an := mocks.NewMockAckNacker() + recipient := mocks.NewMockRecipient() + adapter := mocks.NewMockAdapter() + resp := newBPacketDown(1) + data, _ := resp.MarshalBinary() + adapter.OutSend = data + adapter.OutGetRecipient = recipient + store := newMockController() + store.OutLookupDevices = []devEntry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Uplink", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ = bpacket.MarshalBinary() + hpacket, _ := core.NewHPacket( + store.OutLookupDevices[0].AppEUI, + store.OutLookupDevices[0].DevEUI, + bpacket.Payload(), + bpacket.Metadata(), + ) + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + errutil.CheckErrors(t, nil, err) + mocks.CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, hpacket, adapter.InSendPacket) + mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) + CheckCounters(t, 1, store.InUpdateFCnt) + CheckDirections(t, "down", store.InUpdateDir) + } + + // ------------------- + + { + testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 invalid downlink") + + // Build + an := mocks.NewMockAckNacker() + recipient := mocks.NewMockRecipient() + adapter := mocks.NewMockAdapter() + adapter.OutSend = []byte{1, 2, 3} + adapter.OutGetRecipient = recipient + store := newMockController() + store.OutLookupDevices = []devEntry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Uplink", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ := bpacket.MarshalBinary() + hpacket, _ := core.NewHPacket( + store.OutLookupDevices[0].AppEUI, + store.OutLookupDevices[0].DevEUI, + bpacket.Payload(), + bpacket.Metadata(), + ) + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, hpacket, adapter.InSendPacket) + mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) + CheckCounters(t, 5, store.InUpdateFCnt) + CheckDirections(t, "up", store.InUpdateDir) + } + + // ------------------- + + { + testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 unhandled downlink ") + + // Build + an := mocks.NewMockAckNacker() + recipient := mocks.NewMockRecipient() + adapter := mocks.NewMockAdapter() + resp, _ := core.NewAPacket( + lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + []byte{1, 2}, + nil, + ) + data, _ := resp.MarshalBinary() + adapter.OutSend = data + adapter.OutGetRecipient = recipient + store := newMockController() + store.OutLookupDevices = []devEntry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Uplink", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ = bpacket.MarshalBinary() + hpacket, _ := core.NewHPacket( + store.OutLookupDevices[0].AppEUI, + store.OutLookupDevices[0].DevEUI, + bpacket.Payload(), + bpacket.Metadata(), + ) + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, hpacket, adapter.InSendPacket) + mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) + CheckCounters(t, 5, store.InUpdateFCnt) + CheckDirections(t, "up", store.InUpdateDir) + } + + // ------------------- + + { + testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 downlink invalid counter") + + // Build + an := mocks.NewMockAckNacker() + recipient := mocks.NewMockRecipient() + adapter := mocks.NewMockAdapter() + resp := newBPacketDown(55000) + data, _ := resp.MarshalBinary() + adapter.OutSend = data + adapter.OutGetRecipient = recipient + store := newMockController() + store.OutLookupDevices = []devEntry{ + { + Recipient: []byte{1, 2, 3}, + AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), + DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), + NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), + }, + } + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Uplink", + [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, + 5, + ) + data, _ = bpacket.MarshalBinary() + hpacket, _ := core.NewHPacket( + store.OutLookupDevices[0].AppEUI, + store.OutLookupDevices[0].DevEUI, + bpacket.Payload(), + bpacket.Metadata(), + ) + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, hpacket, adapter.InSendPacket) + mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) + CheckCounters(t, 5, store.InUpdateFCnt) + CheckDirections(t, "up", store.InUpdateDir) + } + + // ------------------- + + { + testutil.Desc(t, "Send unhandled packet type") + + // Build + an := mocks.NewMockAckNacker() + adapter := mocks.NewMockAdapter() + store := newMockController() + apacket, _ := core.NewAPacket( + lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + []byte{1, 2}, + nil, + ) + data, _ := apacket.MarshalBinary() + + // Operate + broker := New(store, testutil.GetLogger(t, "Broker")) + err := broker.HandleUp(data, an, adapter) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) + mocks.CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStoreDevices) + CheckRegistrations(t, nil, store.InStoreApp) + mocks.CheckSent(t, nil, adapter.InSendPacket) + mocks.CheckRecipients(t, nil, adapter.InSendRecipients) + CheckCounters(t, 0, store.InUpdateFCnt) + CheckDirections(t, "", store.InUpdateDir) } } diff --git a/core/components/broker/helpers_test.go b/core/components/broker/helpers_test.go index 266ab84c5..a3e94f4d8 100644 --- a/core/components/broker/helpers_test.go +++ b/core/components/broker/helpers_test.go @@ -36,11 +36,42 @@ func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint3 panic(err) } + phyPayload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = fcnt % 65536 // only 16-bits + + packet, err := core.NewBPacket(phyPayload, core.Metadata{}) + if err != nil { + panic(err) + } + return packet +} + +func newBPacketDown(fcnt uint32) core.BPacket { + macPayload := lorawan.NewMACPayload(false) + macPayload.FHDR = lorawan.FHDR{ + FCnt: fcnt, + DevAddr: lorawan.DevAddr([4]byte{1, 1, 1, 1}), + } + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("downlink")}} + macPayload.FPort = 1 + phyPayload := lorawan.NewPHYPayload(false) + phyPayload.MACPayload = macPayload + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + } + + if err := phyPayload.SetMIC(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6})); err != nil { + panic(err) + } + + phyPayload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = fcnt % 65536 // only 16-bits + packet, err := core.NewBPacket(phyPayload, core.Metadata{}) if err != nil { panic(err) } return packet + } // ----- CHECK utilities @@ -55,3 +86,11 @@ func CheckAppEntries(t *testing.T, want appEntry, got appEntry) { func CheckRegistrations(t *testing.T, want core.Registration, got core.Registration) { mocks.Check(t, want, got, "Registrations") } + +func CheckCounters(t *testing.T, want uint32, got uint32) { + mocks.Check(t, want, got, "Counters") +} + +func CheckDirections(t *testing.T, want string, got string) { + mocks.Check(t, want, got, "Directions") +} From 1d1ce5b8423d401cb59361537f4c04d0603a5400 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 18:04:35 +0100 Subject: [PATCH 0922/2266] [network-controller] Change error type in broker --- core/components/broker/broker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index db683eb2b..33d28ed07 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -139,7 +139,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return errors.New(errors.Operational, "Received unexpected response") } if err := bpacket.ComputeFCnt(mEntry.FCntDown); err != nil { - return errors.New(errors.Structural, "Received invalid response > frame counter incorrect") + return errors.New(errors.Operational, "Received invalid response > frame counter incorrect") } b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt(), "down") } From f493115972be006a9cb3c51a1b0dc45fa1de1145 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 18:07:05 +0100 Subject: [PATCH 0923/2266] [network-controller] Use of pointer receiver to actually use the mutex in broker storage --- core/components/broker/storage.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 372072b47..48ca3add9 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -53,11 +53,11 @@ func NewNetworkController(name string) (NetworkController, error) { return nil, errors.New(errors.Operational, err) } - return controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil + return &controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil } // LookupDevices implements the broker.NetworkController interface -func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { +func (s *controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { s.RLock() defer s.RUnlock() entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) @@ -68,7 +68,7 @@ func (s controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { } // LookupApplication implements the broker.NetworkController interface -func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { +func (s *controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { s.RLock() defer s.RUnlock() itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{}) @@ -86,7 +86,7 @@ func (s controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { } // UpdateFCnt implements the broker.NetworkController interface -func (s controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error { +func (s *controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error { s.Lock() defer s.Unlock() itf, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) @@ -116,7 +116,7 @@ func (s controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt } // StoreDevice implements the broker.NetworkController interface -func (s controller) StoreDevice(reg core.BRegistration) error { +func (s *controller) StoreDevice(reg core.BRegistration) error { s.Lock() defer s.Unlock() data, err := reg.Recipient().MarshalBinary() @@ -136,7 +136,7 @@ func (s controller) StoreDevice(reg core.BRegistration) error { } // StoreApplication implements the broker.NetworkController interface -func (s controller) StoreApplication(reg core.ARegistration) error { +func (s *controller) StoreApplication(reg core.ARegistration) error { s.Lock() defer s.Unlock() data, err := reg.Recipient().MarshalBinary() @@ -154,7 +154,7 @@ func (s controller) StoreApplication(reg core.ARegistration) error { } // Close implements the broker.NetworkController interface -func (s controller) Close() error { +func (s *controller) Close() error { return s.db.Close() } From b9ec432d62fd539e51aded998a018b01b9c44919 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 18:11:53 +0100 Subject: [PATCH 0924/2266] [network-controller] Fix broker cli -> use of NetworkController instead of Storage --- cmd/broker.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 597c798a3..7a6fab2c1 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -71,7 +71,7 @@ and personalized devices (with their network session keys) with the router. } // Instantiate Storage - var db broker.Storage + var db broker.NetworkController dbString := viper.GetString("broker.database") switch { @@ -82,7 +82,7 @@ and personalized devices (with their network session keys) with the router. ctx.WithError(err).Fatal("Invalid database path") } - db, err = broker.NewStorage(dbPath) + db, err = broker.NewNetworkController(dbPath) if err != nil { ctx.WithError(err).Fatal("Could not create local storage") } From 596dc876dc7f158d8478c0e708b4d6fb69c742c3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 18:54:46 +0100 Subject: [PATCH 0925/2266] [hotfix] Use of different mqtt client to avoid collisions --- core/adapters/mqtt/mqtt_test.go | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index e97ef556e..30474d38d 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -130,7 +130,7 @@ func TestMQTTSend(t *testing.T) { // Build aclient, adapter := createAdapter(t) - sclient, chresp := createServers(test.Recipients) + sclients, chresp := createServers(test.Recipients) <-time.After(time.Millisecond * 50) // Operate @@ -148,8 +148,10 @@ func TestMQTTSend(t *testing.T) { // Clean aclient.Disconnect(250) - sclient.Disconnect(250) - <-time.After(time.Millisecond * 50) + for _, sclient := range sclients { + sclient.Disconnect(250) + } + <-time.After(time.Millisecond * 100) } } @@ -232,15 +234,17 @@ func createAdapter(t *testing.T) (Client, core.Adapter) { return client, adapter } -func createServers(recipients []testRecipient) (Client, chan []byte) { - client, err := NewClient("FakeServerClient", brokerURL, TCP) - if err != nil { - panic(err) - } - +func createServers(recipients []testRecipient) ([]Client, chan []byte) { + var clients []Client chresp := make(chan []byte, len(recipients)) - for _, r := range recipients { - go func(r testRecipient) { + for i, r := range recipients { + client, err := NewClient(fmt.Sprintf("FakeServerClient%d", i), brokerURL, TCP) + if err != nil { + panic(err) + } + clients = append(clients, client) + + go func(r testRecipient, client Client) { token := client.Subscribe(r.TopicUp, 2, func(client Client, msg MQTT.Message) { if r.Response != nil { token := client.Publish(r.TopicDown, 2, false, r.Response) @@ -253,9 +257,9 @@ func createServers(recipients []testRecipient) (Client, chan []byte) { if token.Wait() && token.Error() != nil { panic(token.Error()) } - }(r) + }(r, client) } - return client, chresp + return clients, chresp } // ----- OPERATE utilities From 632704052138eab77f3a4168549dbdefa48bc2fa Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:01:24 +0100 Subject: [PATCH 0926/2266] Remove trailing slashes in http handlers urls --- core/adapters/http/handlers/applications.go | 5 +++-- core/adapters/http/handlers/collect.go | 4 ++-- core/adapters/http/handlers/healthz.go | 4 ++-- core/adapters/http/handlers/pubsub.go | 2 +- core/adapters/http/handlers/statuspage.go | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/core/adapters/http/handlers/applications.go b/core/adapters/http/handlers/applications.go index be0bfdc64..47725e2a4 100644 --- a/core/adapters/http/handlers/applications.go +++ b/core/adapters/http/handlers/applications.go @@ -18,7 +18,7 @@ import ( // Applications defines a handler to handle application registration on a component. // -// It listens to request of the form: [PUT] /applications/:appEUI +// It listens to request of the form: [PUT] /applications // where appEUI is a 8 bytes hex-encoded address. // // It expects a Content-Type = application/json @@ -26,6 +26,7 @@ import ( // It also looks for params: // // - app_url (http address as string) +// - app_eui (application identifier as 8-bytes hex-encoded string) // // It fails with an http 400 Bad Request. if one of the parameter is missing or invalid // It succeeds with an http 2xx if the request is valid (the response status is under the @@ -39,7 +40,7 @@ type Applications struct{} // URL implements the http.Handler interface func (p Applications) URL() string { - return "/applications/" + return "/applications" } // Handle implements the http.Handler interface diff --git a/core/adapters/http/handlers/collect.go b/core/adapters/http/handlers/collect.go index 923b90c11..1b885de12 100644 --- a/core/adapters/http/handlers/collect.go +++ b/core/adapters/http/handlers/collect.go @@ -13,7 +13,7 @@ import ( // Collect defines a handler for retrieving raw packets sent by a POST request. // -// It listens to requests of the form: [POST] /packets/ +// It listens to requests of the form: [POST] /packets // // It expects an http header Content-Type = application/octet-stream // @@ -24,7 +24,7 @@ type Collect struct{} // URL implements the http.Handler interface func (p Collect) URL() string { - return "/packets/" + return "/packets" } // Handle implements the http.Handler interface diff --git a/core/adapters/http/handlers/healthz.go b/core/adapters/http/handlers/healthz.go index f142209a7..4ed177bdb 100644 --- a/core/adapters/http/handlers/healthz.go +++ b/core/adapters/http/handlers/healthz.go @@ -11,7 +11,7 @@ import ( // Healthz defines a handler to ping adapters via a GET request. // -// It listens to requests of the form: [GET] /healthz/ +// It listens to requests of the form: [GET] /healthz // // // This handler does not generate any packet. @@ -20,7 +20,7 @@ type Healthz struct{} // URL implements the http.Handler interface func (p Healthz) URL() string { - return "/healthz/" + return "/healthz" } // Handle implements the http.Handler interface diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index d73e7bfe3..2149403e0 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -47,7 +47,7 @@ type PubSub struct{} // URL implements the http.Handler interface func (p PubSub) URL() string { - return "/end-devices/" + return "/end-devices" } // Handle implements the http.Handler interface diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go index 5d04a58bc..6c64c84c0 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -14,7 +14,7 @@ import ( // StatusPage shows statistic on GEt request // -// It listens to requests of the form: [GET] /status/ +// It listens to requests of the form: [GET] /status // // No body or query param are expected // @@ -23,7 +23,7 @@ type StatusPage struct{} // URL implements the http.Handler interface func (p StatusPage) URL() string { - return "/status/" + return "/status" } // Handle implements the http.Handler interface From f3b285b23cf91abc2039a7603b5148d68fc7470c Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:22:26 +0100 Subject: [PATCH 0927/2266] [test/http-adapter] Introduce a mock packet type --- core/mocks/mocks.go | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 46fa3496f..fc3a5cecd 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -335,6 +335,47 @@ func (s *MockSubscriber) Subscribe(reg Registration) error { return s.Failures["Subscribe"] } +// MockPacket implements the core.Packet interface +// +// It declares a `Failures` attributes that can be used to +// simulate failures on demand, associating the name of the method +// which needs to fail with the actual failure. +// +// It also stores the last arguments of each function call in appropriated +// attributes. Because there's no computation going on, the expected / wanted +// responses should also be defined. Default values are provided but can be changed +// if needed. +type MockPacket struct { + Failures map[string]error + OutMarshalBinary []byte + OutString string + OutDevEUI lorawan.EUI64 +} + +func NewMockPacket() *MockPacket { + return &MockPacket{ + Failures: make(map[string]error), + OutMarshalBinary: []byte("MockPacketBinary"), + OutString: "MockPacket", + OutDevEUI: lorawan.EUI64([8]byte{1, 2, 1, 2, 4, 4, 5, 5}), + } +} + +func (p *MockPacket) DevEUI() lorawan.EUI64 { + return p.OutDevEUI +} + +func (p *MockPacket) MarshalBinary() ([]byte, error) { + if p.Failures["MarshalBinary"] != nil { + return nil, p.Failures["MarshalBinary"] + } + return p.OutMarshalBinary, nil +} + +func (p *MockPacket) String() string { + return p.OutString +} + // ----- CHECK utilities func Check(t *testing.T, want, got interface{}, name string) { From 21f6d8bdbb3dd042f9184b8af320b0d69117adb6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:22:56 +0100 Subject: [PATCH 0928/2266] [test/http-adapter] Split http test and define a helpers_test to be consistent with other packages --- core/adapters/http/helpers_test.go | 60 ++++++++++++++++++++++++++++++ core/adapters/http/http_test.go | 34 ----------------- 2 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 core/adapters/http/helpers_test.go diff --git a/core/adapters/http/helpers_test.go b/core/adapters/http/helpers_test.go new file mode 100644 index 000000000..53f520750 --- /dev/null +++ b/core/adapters/http/helpers_test.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "reflect" + "testing" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +// Check utilities +func checkRegistrations(t *testing.T, want []testRegistration, got []core.RRegistration) { + if len(want) != len(got) { + Ko(t, "Expected %d registrations but got %d", len(want), len(got)) + } + +outer: + for _, rw := range want { + for _, rg := range got { + if rg.DevEUI() != rw.DevEUI { + Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, rg.DevEUI()) + } + if reflect.DeepEqual(rw.Recipient.Recipient, rg.Recipient()) { + continue outer + } + } + Ko(t, "Registrations don't match expectation.\nWant: %v\nGot: %v", want, got) + } + Ok(t, "Check registrations") +} + +func checkPayloads(t *testing.T, want string, got []string) { + for _, payload := range got { + if want != payload { + Ko(t, "Paylaod don't match expectation.\nWant: %s\nGot: %s", want, payload) + } + } + Ok(t, "Check payloads") +} + +func CheckResps(t *testing.T, want *MsgRes, got chan MsgRes) { + if want == nil { + if len(got) == 0 { + Ok(t, "Check Resps") + return + } + Ko(t, "Expected no message response but got one") + } + + if len(got) < 1 { + Ko(t, "Expected one message but got none") + } + + msg := <-got + mocks.Check(t, *want, msg, "Resps") +} diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index b1957cbbe..e1a477460 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -6,7 +6,6 @@ package http import ( "io" "net/http" - "reflect" "testing" "time" @@ -243,36 +242,3 @@ func genMockServer(recipient core.Recipient) chan string { go server.ListenAndServe() return chresp } - -// Check utilities -func checkRegistrations(t *testing.T, want []testRegistration, got []core.RRegistration) { - if len(want) != len(got) { - Ko(t, "Expected %d registrations but got %d", len(want), len(got)) - return - } - -outer: - for _, rw := range want { - for _, rg := range got { - if rg.DevEUI() != rw.DevEUI { - Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, rg.DevEUI()) - } - if reflect.DeepEqual(rw.Recipient.Recipient, rg.Recipient()) { - continue outer - } - } - Ko(t, "Registrations don't match expectation.\nWant: %v\nGot: %v", want, got) - return - } - Ok(t, "Check registrations") -} - -func checkPayloads(t *testing.T, want string, got []string) { - for _, payload := range got { - if want != payload { - Ko(t, "Paylaod don't match expectation.\nWant: %s\nGot: %s", want, payload) - return - } - } - Ok(t, "Check payloads") -} From f34afe1c23a9c5608f5e30f9123334820effeeb0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:35:58 +0100 Subject: [PATCH 0929/2266] [test/http-adapter] Write test for http acknacker --- core/adapters/http/acknacker_test.go | 163 +++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 core/adapters/http/acknacker_test.go diff --git a/core/adapters/http/acknacker_test.go b/core/adapters/http/acknacker_test.go new file mode 100644 index 000000000..0986fe1eb --- /dev/null +++ b/core/adapters/http/acknacker_test.go @@ -0,0 +1,163 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package http + +import ( + "net/http" + "testing" + "time" + + // "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestHTTPAckNacker(t *testing.T) { + { + Desc(t, "Ack a nil packet") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + + // Operate + err := an.Ack(nil) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusOK, + Content: nil, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Ack on a nil chresp") + + // Build + an := httpAckNacker{Chresp: nil} + + // Operate + err := an.Ack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, nil) + } + + // -------------------- + + { + Desc(t, "Ack a valid packet") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + p := mocks.NewMockPacket() + p.OutMarshalBinary = []byte{14, 14, 14} + + // Operate + err := an.Ack(p) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusOK, + Content: p.OutMarshalBinary, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Ack an invalid packet") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + p := mocks.NewMockPacket() + p.Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error") + + // Operate + err := an.Ack(p) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckResps(t, nil, chresp) + } + + // -------------------- + + { + Desc(t, "Don't consume chresp") + + // Build + chresp := make(chan MsgRes) + an := httpAckNacker{Chresp: chresp} + + // Operate + cherr := make(chan error) + go func() { + cherr <- an.Ack(nil) + }() + + // Check + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 100): + } + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // -------------------- + + { + Desc(t, "Nack") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + + // Operate + err := an.Nack() + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusNotFound, + Content: []byte(errors.Structural), + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Nack on a nil chresp") + + // Build + an := httpAckNacker{Chresp: nil} + + // Operate + err := an.Nack() + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, nil) + } +} From f017af919b9032af70176fb572aa26e6e3e05da6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:36:09 +0100 Subject: [PATCH 0930/2266] [test/http-adapter] Fix issue regarding to previous tests --- core/adapters/http/httpAckNacker.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/core/adapters/http/httpAckNacker.go b/core/adapters/http/httpAckNacker.go index 1d3c70532..8657b3fcc 100644 --- a/core/adapters/http/httpAckNacker.go +++ b/core/adapters/http/httpAckNacker.go @@ -23,14 +23,13 @@ func (an httpAckNacker) Ack(p core.Packet) error { } defer close(an.Chresp) - if p == nil { - an.Chresp <- MsgRes{StatusCode: http.StatusOK} - return nil - } - - data, err := p.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) + var data []byte + if p != nil { + var err error + data, err = p.MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) + } } select { @@ -56,8 +55,8 @@ func (an httpAckNacker) Nack() error { StatusCode: http.StatusNotFound, Content: []byte(errors.Structural), }: + return nil case <-time.After(time.Millisecond * 50): return errors.New(errors.Operational, "No response was given to the acknacker") } - return nil } From 9a941dbd1e689411cad1a5e477a668c66e9abec9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:40:58 +0100 Subject: [PATCH 0931/2266] [test/http-adapter] Add tests for reg Acknacker --- core/adapters/http/acknacker_test.go | 177 ++++++++++++++++++++++++++- 1 file changed, 176 insertions(+), 1 deletion(-) diff --git a/core/adapters/http/acknacker_test.go b/core/adapters/http/acknacker_test.go index 0986fe1eb..6c6a14bec 100644 --- a/core/adapters/http/acknacker_test.go +++ b/core/adapters/http/acknacker_test.go @@ -101,7 +101,7 @@ func TestHTTPAckNacker(t *testing.T) { // -------------------- { - Desc(t, "Don't consume chresp") + Desc(t, "Don't consume chresp on Ack") // Build chresp := make(chan MsgRes) @@ -160,4 +160,179 @@ func TestHTTPAckNacker(t *testing.T) { errutil.CheckErrors(t, nil, err) CheckResps(t, nil, nil) } + + // -------------------- + + { + Desc(t, "Don't consume chresp on Nack") + + // Build + chresp := make(chan MsgRes) + an := httpAckNacker{Chresp: chresp} + + // Operate + cherr := make(chan error) + go func() { + cherr <- an.Nack() + }() + + // Check + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 100): + } + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + } +} + +func TestRegAckNacker(t *testing.T) { + { + Desc(t, "Ack a nil packet") + + // Build + chresp := make(chan MsgRes, 1) + an := regAckNacker{Chresp: chresp} + + // Operate + err := an.Ack(nil) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusAccepted, + Content: nil, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Ack on a nil chresp") + + // Build + an := regAckNacker{Chresp: nil} + + // Operate + err := an.Ack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, nil) + } + + // -------------------- + + { + Desc(t, "Ack a valid packet") + + // Build + chresp := make(chan MsgRes, 1) + an := regAckNacker{Chresp: chresp} + p := mocks.NewMockPacket() + p.OutMarshalBinary = []byte{14, 14, 14} + + // Operate + err := an.Ack(p) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusAccepted, + Content: nil, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Don't consume chresp on Ack") + + // Build + chresp := make(chan MsgRes) + an := regAckNacker{Chresp: chresp} + + // Operate + cherr := make(chan error) + go func() { + cherr <- an.Ack(nil) + }() + + // Check + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 100): + } + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // -------------------- + + { + Desc(t, "Nack") + + // Build + chresp := make(chan MsgRes, 1) + an := regAckNacker{Chresp: chresp} + + // Operate + err := an.Nack() + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusConflict, + Content: []byte(errors.Structural), + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Nack on a nil chresp") + + // Build + an := regAckNacker{Chresp: nil} + + // Operate + err := an.Nack() + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, nil) + } + + // -------------------- + + { + Desc(t, "Don't consume chresp on Nack") + + // Build + chresp := make(chan MsgRes) + an := regAckNacker{Chresp: chresp} + + // Operate + cherr := make(chan error) + go func() { + cherr <- an.Nack() + }() + + // Check + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 100): + } + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + } } From f0de9d21359b6170d8b61c89f6885624d9bfd5d3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:41:11 +0100 Subject: [PATCH 0932/2266] [test/http-adapter] Change error message in RegAckNacker on Nack() --- core/adapters/http/regAckNacker.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/http/regAckNacker.go b/core/adapters/http/regAckNacker.go index bfce965ae..4bf8f2275 100644 --- a/core/adapters/http/regAckNacker.go +++ b/core/adapters/http/regAckNacker.go @@ -39,7 +39,7 @@ func (r regAckNacker) Nack() error { select { case r.Chresp <- MsgRes{ StatusCode: http.StatusConflict, - Content: []byte(errors.New(errors.Structural, "Unable to register device").Error()), + Content: []byte(errors.Structural), }: return nil case <-time.After(time.Millisecond * 50): From 184ee1eb09930cd1127951af9b5479d56f9f30e8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:41:26 +0100 Subject: [PATCH 0933/2266] [test/http-adapter] Fix tests for healthz and statuspage handlers --- core/adapters/http/handlers/healthz_test.go | 2 +- core/adapters/http/handlers/statuspage_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/adapters/http/handlers/healthz_test.go b/core/adapters/http/handlers/healthz_test.go index 280576ad3..06b2b4db5 100644 --- a/core/adapters/http/handlers/healthz_test.go +++ b/core/adapters/http/handlers/healthz_test.go @@ -17,7 +17,7 @@ func TestHealthzURL(t *testing.T) { h := Healthz{} - a.So(h.URL(), assertions.ShouldEqual, "/healthz/") + a.So(h.URL(), assertions.ShouldEqual, "/healthz") } func TestHealthzHandle(t *testing.T) { diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go index e267deb49..5ef2235ba 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/handlers/statuspage_test.go @@ -18,7 +18,7 @@ func TestStatusPageURL(t *testing.T) { s := StatusPage{} - a.So(s.URL(), assertions.ShouldEqual, "/status/") + a.So(s.URL(), assertions.ShouldEqual, "/status") } func TestStatusPageHandler(t *testing.T) { @@ -27,14 +27,14 @@ func TestStatusPageHandler(t *testing.T) { s := StatusPage{} // Only GET allowed - r1, _ := http.NewRequest("POST", "/status/", nil) + r1, _ := http.NewRequest("POST", "/status", nil) r1.RemoteAddr = "127.0.0.1:12345" rw1 := NewResponseWriter() s.Handle(&rw1, make(chan<- PktReq), make(chan<- RegReq), r1) a.So(rw1.TheStatus, assertions.ShouldEqual, 405) // Initially Empty - r3, _ := http.NewRequest("GET", "/status/", nil) + r3, _ := http.NewRequest("GET", "/status", nil) r3.RemoteAddr = "127.0.0.1:12345" rw3 := NewResponseWriter() s.Handle(&rw3, make(chan<- PktReq), make(chan<- RegReq), r3) @@ -47,7 +47,7 @@ func TestStatusPageHandler(t *testing.T) { stats.MarkMeter("and.this.is.a-meter") // Not Empty anymore - r4, _ := http.NewRequest("GET", "/status/", nil) + r4, _ := http.NewRequest("GET", "/status", nil) r4.RemoteAddr = "127.0.0.1:12345" rw4 := NewResponseWriter() s.Handle(&rw4, make(chan<- PktReq), make(chan<- RegReq), r4) From 186dcc112494cb0535619b1a35165f21047ce2ef Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:52:12 +0100 Subject: [PATCH 0934/2266] [test/http-adapter] Add tests for HTTPRecipient --- core/adapters/http/acknacker_test.go | 43 ++++++++++++++++++++++++++++ core/adapters/http/helpers_test.go | 8 ++++++ 2 files changed, 51 insertions(+) diff --git a/core/adapters/http/acknacker_test.go b/core/adapters/http/acknacker_test.go index 6c6a14bec..4a9d08991 100644 --- a/core/adapters/http/acknacker_test.go +++ b/core/adapters/http/acknacker_test.go @@ -336,3 +336,46 @@ func TestRegAckNacker(t *testing.T) { errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) } } + +func TestRecipient(t *testing.T) { + + // -------------------- + + { + Desc(t, "Test Marshal / Unmarshal binary") + + // Build + r := NewRecipient("url", "method") + + // Operate + data, err := r.MarshalBinary() + + // Check + errutil.CheckErrors(t, nil, err) + + // Build + r2 := new(recipient) + err = r2.UnmarshalBinary(data) + + // Check + errutil.CheckErrors(t, nil, err) + CheckRecipients(t, r, *r2) + } + + // -------------------- + + { + Desc(t, "Test Marshal JSON") + + // Build + r := NewRecipient("localhost", "PUT") + + // Operate + data, err := r.MarshalJSON() + + // Check + errutil.CheckErrors(t, nil, err) + CheckJSONs(t, []byte(`{"url":"localhost","method":"PUT"}`), data) + } + +} diff --git a/core/adapters/http/helpers_test.go b/core/adapters/http/helpers_test.go index 53f520750..2c45aa943 100644 --- a/core/adapters/http/helpers_test.go +++ b/core/adapters/http/helpers_test.go @@ -58,3 +58,11 @@ func CheckResps(t *testing.T, want *MsgRes, got chan MsgRes) { msg := <-got mocks.Check(t, *want, msg, "Resps") } + +func CheckRecipients(t *testing.T, want Recipient, got Recipient) { + mocks.Check(t, want, got, "Recipients") +} + +func CheckJSONs(t *testing.T, want []byte, got []byte) { + mocks.Check(t, want, got, "JSON") +} From aafa8036a8194dc97f4b294e285f9d9666f7c4ff Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 19:56:15 +0100 Subject: [PATCH 0935/2266] [test/http-adapter] Use of correct http verbs in http adapters --- core/adapters/http/http.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 9791b3d20..fdd538541 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -86,7 +86,12 @@ func (a *Adapter) Subscribe(r core.Registration) error { } buf := new(bytes.Buffer) buf.Write(data) - resp, err := a.Post(fmt.Sprintf("http://%s/end-devices", httpRecipient.URL()), "application/json", buf) + req, err := http.NewRequest(httpRecipient.Method(), fmt.Sprintf("http://%s/end-devices", httpRecipient.URL()), buf) + if err != nil { + return errors.New(errors.Operational, err) + } + req.Header.Add("content-type", "application/json") + resp, err := a.Do(req) if err != nil { return errors.New(errors.Operational, err) } @@ -149,7 +154,14 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err ctx.Debugf("%s Request", recipient.Method()) buf := new(bytes.Buffer) buf.Write(data) - resp, err := a.Post(fmt.Sprintf("http://%s/packets", recipient.URL()), "application/octet-stream", buf) + req, err := http.NewRequest(recipient.Method(), fmt.Sprintf("http://%s/packets", recipient.URL()), buf) + if err != nil { + cherr <- errors.New(errors.Operational, err) + return + } + req.Header.Add("content-type", "application/octet-stream") + resp, err := a.Do(req) + if err != nil { cherr <- errors.New(errors.Operational, err) return From 31f51d1da758397ffa5fc9a2df22ad7e2872ae9a Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 20:23:07 +0100 Subject: [PATCH 0936/2266] [test/http-adapter] Add marshalJSON to mcok registration --- core/mocks/mocks.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index fc3a5cecd..68545ac88 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -88,12 +88,16 @@ func (r *MockJSONRecipient) UnmarshalJSON(data []byte) error { // responses should also be defined. Default values are provided but can be changed // if needed. type MockRegistration struct { - OutRecipient Recipient + Failures map[string]error + OutRecipient Recipient + OutMarshalJSON []byte } func NewMockRegistration() MockRegistration { return MockRegistration{ - OutRecipient: NewMockRecipient(), + Failures: make(map[string]error), + OutRecipient: NewMockRecipient(), + OutMarshalJSON: []byte(`{"out":"MockRegistration"}`), } } @@ -106,6 +110,13 @@ func (r MockRegistration) Recipient() Recipient { return r.OutRecipient } +func (r MockRegistration) MarshalJSON() ([]byte, error) { + if r.Failures["MarshalJSON"] != nil { + return nil, r.Failures["MarshalJSON"] + } + return r.OutMarshalJSON, nil +} + // MockARegistration implements the core.ARegistration interface // // It also stores the last arguments of each function call in appropriated From 4f22c7214a8669ca87774a3cf5ff78bb614a7137 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 7 Mar 2016 20:36:18 +0100 Subject: [PATCH 0937/2266] [test/http-adapter] Add tests for subscribe method --- core/adapters/http/helpers_test.go | 8 +++ core/adapters/http/http_test.go | 95 ++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) diff --git a/core/adapters/http/helpers_test.go b/core/adapters/http/helpers_test.go index 2c45aa943..ee1c07406 100644 --- a/core/adapters/http/helpers_test.go +++ b/core/adapters/http/helpers_test.go @@ -66,3 +66,11 @@ func CheckRecipients(t *testing.T, want Recipient, got Recipient) { func CheckJSONs(t *testing.T, want []byte, got []byte) { mocks.Check(t, want, got, "JSON") } + +func CheckMethods(t *testing.T, want string, got string) { + mocks.Check(t, want, got, "Methods") +} + +func CheckContentTypes(t *testing.T, want string, got string) { + mocks.Check(t, want, got, "ContentTypes") +} diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index e1a477460..7c160d1a9 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -161,6 +162,100 @@ func TestSend(t *testing.T) { } } +func TestSubscribe(t *testing.T) { + { + Desc(t, "Subscribe a valid registration") + + // Build + r := mocks.NewMockRegistration() + r.OutRecipient = NewRecipient("0.0.0.0:4777", "PUT") + a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) + serveMux := http.NewServeMux() + serveMux.HandleFunc("/end-devices", func(w http.ResponseWriter, req *http.Request) { + // Check + CheckContentTypes(t, req.Header.Get("Content-Type"), "application/json") + CheckMethods(t, req.Method, r.OutRecipient.(Recipient).Method()) + + buf := make([]byte, req.ContentLength) + n, err := req.Body.Read(buf) + if err == io.EOF { + err = nil + } + CheckErrors(t, nil, err) + CheckJSONs(t, r.OutMarshalJSON, buf[:n]) + }) + go http.ListenAndServe(r.OutRecipient.(Recipient).URL(), serveMux) + <-time.After(time.Millisecond * 100) + + // Operate + err := a.Subscribe(r) + <-time.After(time.Millisecond * 50) + + // Check + CheckErrors(t, nil, err) + } + + // -------------------- + + { + Desc(t, "Subscribe an invalid registration -> Invalid recipient") + + // Build + r := mocks.NewMockRegistration() + r.OutRecipient = NewRecipient("0.0.0.0:4777", "PUT") + r.Failures["MarshalJSON"] = errors.New(errors.Structural, "Mock Error") + a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) + + // Operate + err := a.Subscribe(r) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + } + + // -------------------- + + { + Desc(t, "Subscribe an invalid registration -> MarshalJSON fails") + + // Build + r := mocks.NewMockRegistration() + r.Failures["MarshalJSON"] = errors.New(errors.Structural, "Mock Error") + a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) + + // Operate + err := a.Subscribe(r) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + } + + // -------------------- + + { + Desc(t, "Subscribe a valid registration | Refused by server") + + // Build + r := mocks.NewMockRegistration() + r.OutRecipient = NewRecipient("0.0.0.0:4778", "PUT") + a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) + serveMux := http.NewServeMux() + serveMux.HandleFunc("/end-devices", func(w http.ResponseWriter, req *http.Request) { + w.WriteHeader(http.StatusBadRequest) + w.Write(nil) + }) + go http.ListenAndServe(r.OutRecipient.(Recipient).URL(), serveMux) + <-time.After(time.Millisecond * 100) + + // Operate + err := a.Subscribe(r) + <-time.After(time.Millisecond * 50) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } +} + // Convert testRecipient to core.Recipient func toHTTPRecipient(recipients []testRecipient) []core.Recipient { var https []core.Recipient From 1f0b7f4d4061655982f1b6334a52444e1613b4e2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 09:56:44 +0100 Subject: [PATCH 0938/2266] [hotfix] Use of handler own address as subscription target --- core/adapters/http/handlers/pubsub.go | 4 +-- core/adapters/http/http.go | 29 +++++++++++++++++-- core/components/handler/brokerRegistration.go | 16 +++++----- .../handler/brokerRegistration_test.go | 24 ++++----------- 4 files changed, 41 insertions(+), 32 deletions(-) diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index 2149403e0..060c2faa6 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -94,12 +94,12 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { } defer req.Body.Close() params := new(struct { - AppEUI string `json:"app_eui"` - DevEUI string `json:"dev_eui"` Recipient struct { URL string `json:"url"` Method string `json:"method"` } `json:"recipient"` + AppEUI string `json:"app_eui"` + DevEUI string `json:"dev_eui"` NwkSKey string `json:"nwks_key"` }) if err := json.Unmarshal(body[:n], params); err != nil { diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index fdd538541..306181af5 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -27,6 +27,7 @@ type Adapter struct { recipients []core.Recipient // Known recipient used for broadcast if any registrations chan RegReq // Incoming registrations serveMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints + net string // Address on which is listening the adapter http server } // Handler defines endpoint-specific handler. @@ -62,6 +63,7 @@ func NewAdapter(net string, recipients []core.Recipient, ctx log.Interface) (*Ad recipients: recipients, registrations: make(chan RegReq), serveMux: http.NewServeMux(), + net: net, } go a.listenRequests(net) @@ -71,6 +73,7 @@ func NewAdapter(net string, recipients []core.Recipient, ctx log.Interface) (*Ad // Register implements the core.Subscriber interface func (a *Adapter) Subscribe(r core.Registration) error { + // 1. Type assertions and convertions jsonMarshaler, ok := r.(json.Marshaler) if !ok { return errors.New(errors.Structural, "Unable to marshal registration") @@ -80,12 +83,30 @@ func (a *Adapter) Subscribe(r core.Registration) error { return errors.New(errors.Structural, "Invalid recipient") } - data, err := jsonMarshaler.MarshalJSON() + // 2. Marshaling + data, err := json.Marshal(struct { + Recipient struct { + Method string `json:"method"` + URL string `json:"url"` + } `json:"recipient"` + json.Marshaler + }{ + Recipient: struct { + Method string `json:"method"` + URL string `json:"url"` + }{ + Method: "POST", + URL: a.net, + }, + Marshaler: jsonMarshaler, + }) if err != nil { return errors.New(errors.Structural, err) } buf := new(bytes.Buffer) buf.Write(data) + + // 3. Send Request req, err := http.NewRequest(httpRecipient.Method(), fmt.Sprintf("http://%s/end-devices", httpRecipient.URL()), buf) if err != nil { return errors.New(errors.Operational, err) @@ -96,8 +117,12 @@ func (a *Adapter) Subscribe(r core.Registration) error { return errors.New(errors.Operational, err) } defer resp.Body.Close() + + // 4. Handle response -> resp body isn't relevant if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { - return errors.New(errors.Operational, "Unable to subscribe") + errData := make([]byte, resp.ContentLength) + resp.Body.Read(errData) + return errors.New(errors.Operational, string(errData)) } return nil } diff --git a/core/components/handler/brokerRegistration.go b/core/components/handler/brokerRegistration.go index ea349ed5a..c2c5d8883 100644 --- a/core/components/handler/brokerRegistration.go +++ b/core/components/handler/brokerRegistration.go @@ -13,7 +13,7 @@ import ( // type brokerRegistration implements the core.BRegistration interface type brokerRegistration struct { - recipient core.JSONRecipient + recipient core.Recipient appEUI lorawan.EUI64 nwkSKey lorawan.AES128Key devEUI lorawan.EUI64 @@ -42,15 +42,13 @@ func (r brokerRegistration) NwkSKey() lorawan.AES128Key { // MarshalJSON implements the encoding/json.Marshaler interface func (r brokerRegistration) MarshalJSON() ([]byte, error) { data, err := json.Marshal(struct { - Recipient core.JSONRecipient `json:"recipient"` - AppEUI lorawan.EUI64 `json:"app_eui"` - NwkSKey lorawan.AES128Key `json:"nwks_key"` - DevEUI lorawan.EUI64 `json:"dev_eui"` + AppEUI lorawan.EUI64 `json:"app_eui"` + NwkSKey lorawan.AES128Key `json:"nwks_key"` + DevEUI lorawan.EUI64 `json:"dev_eui"` }{ - Recipient: r.recipient, - AppEUI: r.appEUI, - NwkSKey: r.nwkSKey, - DevEUI: r.devEUI, + AppEUI: r.appEUI, + NwkSKey: r.nwkSKey, + DevEUI: r.devEUI, }) if err != nil { return nil, errors.New(errors.Structural, err) diff --git a/core/components/handler/brokerRegistration_test.go b/core/components/handler/brokerRegistration_test.go index a8fda5fa5..e594a01ad 100644 --- a/core/components/handler/brokerRegistration_test.go +++ b/core/components/handler/brokerRegistration_test.go @@ -7,14 +7,12 @@ import ( "testing" mocks "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/brocaar/lorawan" ) func TestRegistration(t *testing.T) { - recipient := mocks.NewMockJSONRecipient() + recipient := mocks.NewMockRecipient() devEUI := lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) nwkSKey := lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}) @@ -33,29 +31,17 @@ func TestRegistration(t *testing.T) { } -func TestRegistrationMarshalUnmarshal(t *testing.T) { +func TestRegistrationMarshal(t *testing.T) { { reg := brokerRegistration{ - recipient: mocks.NewMockJSONRecipient(), + recipient: mocks.NewMockRecipient(), devEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), appEUI: lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}), nwkSKey: lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}), } - _, err := reg.MarshalJSON() + data, err := reg.MarshalJSON() + t.Log(string(data)) errutil.CheckErrors(t, nil, err) } - - { - reg := brokerRegistration{ - recipient: mocks.NewMockJSONRecipient(), - devEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - appEUI: lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}), - nwkSKey: lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}), - } - reg.recipient.(*mocks.MockJSONRecipient).OutMarshalJSON = []byte("InvalidJSON") - - _, err := reg.MarshalJSON() - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - } } From 3a0943c7a2f558b9ae4d7b77bf85027fd01e3c5b Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 10:58:58 +0100 Subject: [PATCH 0939/2266] [hotfix] Use of dedicated field for registration information in personalized subscriptions --- core/adapters/http/handlers/pubsub.go | 14 ++++---- core/adapters/http/handlers/pubsub_test.go | 40 ++++++++++++++-------- core/adapters/http/http.go | 4 +-- core/adapters/http/http_test.go | 4 ++- 4 files changed, 38 insertions(+), 24 deletions(-) diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index 060c2faa6..06cb5ee4c 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -98,26 +98,28 @@ func (p PubSub) parse(req *http.Request) (core.Registration, error) { URL string `json:"url"` Method string `json:"method"` } `json:"recipient"` - AppEUI string `json:"app_eui"` - DevEUI string `json:"dev_eui"` - NwkSKey string `json:"nwks_key"` + Registration struct { + AppEUI string `json:"app_eui"` + DevEUI string `json:"dev_eui"` + NwkSKey string `json:"nwks_key"` + } `json:"registration"` }) if err := json.Unmarshal(body[:n], params); err != nil { return pubSubRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") } // Verify each request parameter - nwkSKey, err := hex.DecodeString(params.NwkSKey) + nwkSKey, err := hex.DecodeString(params.Registration.NwkSKey) if err != nil || len(nwkSKey) != 16 { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect network session key") } - appEUI, err := hex.DecodeString(params.AppEUI) + appEUI, err := hex.DecodeString(params.Registration.AppEUI) if err != nil || len(appEUI) != 8 { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") } - devEUI, err := hex.DecodeString(params.DevEUI) + devEUI, err := hex.DecodeString(params.Registration.DevEUI) if err != nil || len(devEUI) != 8 { return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect device eui") } diff --git a/core/adapters/http/handlers/pubsub_test.go b/core/adapters/http/handlers/pubsub_test.go index e29fcc9df..5d9078388 100644 --- a/core/adapters/http/handlers/pubsub_test.go +++ b/core/adapters/http/handlers/pubsub_test.go @@ -47,9 +47,11 @@ func TestPubSub(t *testing.T) { { Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid DevEUI. Nack", Payload: `{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304", + "registration":{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304" + }, "recipient": { "url": "url", "method": "PUT" @@ -68,9 +70,11 @@ func TestPubSub(t *testing.T) { { Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid DevEUI. Nack", Payload: `{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304", + "registration":{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304" + }, "recipient": { "url": "url", "method": "PUT" @@ -89,9 +93,11 @@ func TestPubSub(t *testing.T) { { Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid DevEUI. Nack", Payload: `{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000001144", + "registration":{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000001144" + }, "recipient": { "url": "url", "method": "PUT" @@ -110,9 +116,11 @@ func TestPubSub(t *testing.T) { { Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", Payload: `{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304", + "registration":{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304" + }, "recipient": { "url": "url", "method": "PUT" @@ -136,9 +144,11 @@ func TestPubSub(t *testing.T) { { Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Ack", Payload: `{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304", + "registration":{ + "app_eui":"0001020304050607", + "nwks_key":"00010203040506070809000102030405", + "dev_eui": "0000000001020304" + }, "recipient": { "url": "url", "method": "PUT" diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 306181af5..c87d43fd7 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -89,7 +89,7 @@ func (a *Adapter) Subscribe(r core.Registration) error { Method string `json:"method"` URL string `json:"url"` } `json:"recipient"` - json.Marshaler + Registration json.Marshaler `json:"registration"` }{ Recipient: struct { Method string `json:"method"` @@ -98,7 +98,7 @@ func (a *Adapter) Subscribe(r core.Registration) error { Method: "POST", URL: a.net, }, - Marshaler: jsonMarshaler, + Registration: jsonMarshaler, }) if err != nil { return errors.New(errors.Structural, err) diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index 7c160d1a9..12a6b723f 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -4,6 +4,7 @@ package http import ( + "fmt" "io" "net/http" "testing" @@ -182,7 +183,8 @@ func TestSubscribe(t *testing.T) { err = nil } CheckErrors(t, nil, err) - CheckJSONs(t, r.OutMarshalJSON, buf[:n]) + wantJSON := []byte(fmt.Sprintf(`{"recipient":{"method":"POST","url":"0.0.0.0:4776"},"registration":%s}`, r.OutMarshalJSON)) + CheckJSONs(t, wantJSON, buf[:n]) }) go http.ListenAndServe(r.OutRecipient.(Recipient).URL(), serveMux) <-time.After(time.Millisecond * 100) From 92cc8ac1ebec4908b42f732383b8ad748207609d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 11:28:01 +0100 Subject: [PATCH 0940/2266] [hotfix] Correctly compute the bundle ID in handler --- core/components/handler/handler.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 37529adcf..66ee20aa5 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -4,12 +4,13 @@ package handler import ( + "bytes" + "encoding/binary" "reflect" "time" . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -103,14 +104,14 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { chresp := make(chan interface{}) // 3. Create a "bundle" which holds info waiting for other related packets - var bundleID [20]byte // AppEUI(8) | DevEUI(8) - rw := readwriter.New(nil) - rw.Write(appEUI) - rw.Write(devEUI) - rw.Write(packet.FCnt()) - data, err := rw.Bytes() - if err != nil { - return errors.New(errors.Structural, err) + var bundleID [20]byte // AppEUI(8) | DevEUI(8) | FCnt + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, appEUI[:]) + binary.Write(buf, binary.BigEndian, devEUI[:]) + binary.Write(buf, binary.BigEndian, packet.FCnt()) + data := buf.Bytes() + if len(data) != 20 { + return errors.New(errors.Structural, "Unable to generate bundleID") } copy(bundleID[:], data[:]) From ec2b1de838571338ad5040fff79845c4fec0629a Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 11:41:54 +0100 Subject: [PATCH 0941/2266] [test/packets] Add test about String() and several mType --- core/packets_test.go | 180 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 158 insertions(+), 22 deletions(-) diff --git a/core/packets_test.go b/core/packets_test.go index a12cf93aa..0636c98e2 100644 --- a/core/packets_test.go +++ b/core/packets_test.go @@ -26,17 +26,15 @@ func newEUI() lorawan.EUI64 { return devEUI } -func simplePayload(fcnt uint32) (payload lorawan.PHYPayload, devAddr lorawan.DevAddr, key lorawan.AES128Key) { +func simplePayload(fcnt uint32, uplink bool) (payload lorawan.PHYPayload, devAddr lorawan.DevAddr, key lorawan.AES128Key) { copy(devAddr[:], randBytes(4)) copy(key[:], randBytes(16)) - payload = newPayload(devAddr, []byte("PLD123"), key, key, fcnt) + payload = newPayload(devAddr, []byte("PLD123"), key, key, fcnt, uplink) return } -func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, nwkSKey lorawan.AES128Key, fcnt uint32) lorawan.PHYPayload { - uplink := true - +func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, nwkSKey lorawan.AES128Key, fcnt uint32, uplink bool) lorawan.PHYPayload { macPayload := lorawan.NewMACPayload(uplink) macPayload.FHDR = lorawan.FHDR{ DevAddr: devAddr, @@ -92,7 +90,7 @@ func TestBaseMarshalUnmarshal(t *testing.T) { s := uint(123) mpkt := basempacket{metadata: Metadata{Size: &s}} - payload, _, _ := simplePayload(1) + payload, _, _ := simplePayload(1, true) rpkt := baserpacket{payload: payload} hpkt := basehpacket{ appEUI: newEUI(), @@ -195,26 +193,93 @@ func TestInvalidRPacket(t *testing.T) { a.So(err2, ShouldNotBeNil) } -func TestRPacket(t *testing.T) { +func checkRPacket(t *testing.T, output RPacket, p lorawan.PHYPayload, gid []byte, m Metadata, d lorawan.DevAddr) { a := New(t) - payload, devAddr, _ := simplePayload(1) - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) + a.So(output.Payload(), ShouldResemble, p) + a.So(output.GatewayID(), ShouldResemble, gid) + a.So(output.Metadata(), ShouldResemble, m) + outputDevEUI := output.DevEUI() + a.So(outputDevEUI[4:], ShouldResemble, d[:]) +} - input, _ := NewRPacket(payload, gwEUI, Metadata{}) +func TestRPacket(t *testing.T) { + { // UnconfirmedDataUp + payload, devAddr, _ := simplePayload(1, true) + payload.MHDR.MType = lorawan.UnconfirmedDataUp + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + gOutput := marshalUnmarshal(t, input) + + checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) + } - gOutput := marshalUnmarshal(t, input) + { // ConfirmedDataUp + payload, devAddr, _ := simplePayload(1, true) + payload.MHDR.MType = lorawan.ConfirmedDataUp + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + gOutput := marshalUnmarshal(t, input) - output := gOutput.(RPacket) + checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) + } - a.So(output.Payload(), ShouldResemble, payload) - a.So(output.GatewayID(), ShouldResemble, gwEUI) - a.So(output.Metadata(), ShouldResemble, Metadata{}) - outputDevEUI := output.DevEUI() - a.So(outputDevEUI[4:], ShouldResemble, devAddr[:]) + { // UnconfirmedDataDown + payload, devAddr, _ := simplePayload(1, false) + payload.MHDR.MType = lorawan.UnconfirmedDataDown + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + gOutput := marshalUnmarshal(t, input) + + checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) + } - // TODO: Different MTypes + { // ConfirmedDataDown + payload, devAddr, _ := simplePayload(1, false) + payload.MHDR.MType = lorawan.ConfirmedDataDown + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + gOutput := marshalUnmarshal(t, input) + + checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) + } + + { // JoinRequest + payload, _, _ := simplePayload(1, true) + payload.MHDR.MType = lorawan.Proprietary + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + + _, err := input.MarshalBinary() + New(t).So(err, ShouldNotBeNil) + } + + { // JoinAccept + payload, _, _ := simplePayload(1, false) + payload.MHDR.MType = lorawan.Proprietary + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + + _, err := input.MarshalBinary() + New(t).So(err, ShouldNotBeNil) + } + + { // Proprietary + payload, _, _ := simplePayload(1, false) + payload.MHDR.MType = lorawan.Proprietary + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + + _, err := input.MarshalBinary() + New(t).So(err, ShouldNotBeNil) + } } func TestSPacket(t *testing.T) { @@ -252,7 +317,7 @@ func TestInvalidBPacket(t *testing.T) { // FCnt out of bound var wholeCnt uint32 = 78765436 - payload, _, _ := simplePayload(1) + payload, _, _ := simplePayload(1, true) payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65536 + 65536/2 input, _ := NewBPacket(payload, Metadata{}) err := input.ComputeFCnt(wholeCnt) @@ -263,7 +328,7 @@ func TestBPacket(t *testing.T) { a := New(t) var wholeCnt uint32 = 78765436 - payload, _, key := simplePayload(wholeCnt + 1) + payload, _, key := simplePayload(wholeCnt+1, true) payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65536 + 1 input, _ := NewBPacket(payload, Metadata{}) @@ -318,7 +383,7 @@ func TestHPacket(t *testing.T) { appEUI := newEUI() devEUI := newEUI() - payload, _, key := simplePayload(1) + payload, _, key := simplePayload(1, true) input, _ := NewHPacket(appEUI, devEUI, payload, Metadata{}) @@ -418,3 +483,74 @@ func TestCPacket(t *testing.T) { outputNwkSKey := output.NwkSKey() a.So(outputNwkSKey[:], ShouldResemble, nwkSKey[:]) } + +func TestString(t *testing.T) { + a := New(t) + + { // RPacket + payload, _, _ := simplePayload(1, true) + gwEUI := []byte{} + copy(gwEUI[:], randBytes(8)) + + input, _ := NewRPacket(payload, gwEUI, Metadata{}) + + a.So(input.String(), ShouldNotEqual, "TODO") + a.So(input.String(), ShouldNotEqual, "") + } + + { // CPacket + appEUI := newEUI() + devEUI := newEUI() + payload := []byte("PLD123") + nwkSKey := [16]byte{} + copy(devEUI[:], randBytes(16)) + + input, _ := NewCPacket(appEUI, devEUI, payload, nwkSKey) + + a.So(input.String(), ShouldNotEqual, "TODO") + a.So(input.String(), ShouldNotEqual, "") + } + + { // JPacket + appEUI := newEUI() + devEUI := newEUI() + devNonce := [2]byte{} + copy(devEUI[:], randBytes(2)) + + input := NewJPacket(appEUI, devEUI, devNonce, Metadata{}) + + a.So(input.String(), ShouldNotEqual, "TODO") + a.So(input.String(), ShouldNotEqual, "") + } + { // APacket + appEUI := newEUI() + devEUI := newEUI() + payload := []byte("PLD123") + + input, _ := NewAPacket(appEUI, devEUI, payload, []Metadata{}) + + a.So(input.String(), ShouldNotEqual, "TODO") + a.So(input.String(), ShouldNotEqual, "") + } + + { // HPacket + + appEUI := newEUI() + devEUI := newEUI() + payload, _, _ := simplePayload(1, true) + + input, _ := NewHPacket(appEUI, devEUI, payload, Metadata{}) + + a.So(input.String(), ShouldNotEqual, "TODO") + a.So(input.String(), ShouldNotEqual, "") + } + + { // BPacket + payload, _, _ := simplePayload(1, true) + + input, _ := NewBPacket(payload, Metadata{}) + + a.So(input.String(), ShouldNotEqual, "TODO") + a.So(input.String(), ShouldNotEqual, "") + } +} From 8e8570ed79bdfdf5c2097e8101b03a1b84228d32 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 11:42:22 +0100 Subject: [PATCH 0942/2266] [test/packets] Implements necessary features to make tests pass --- core/packets.go | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/core/packets.go b/core/packets.go index 4ae786d59..658c952bd 100644 --- a/core/packets.go +++ b/core/packets.go @@ -149,7 +149,7 @@ func (p *rpacket) UnmarshalBinary(data []byte) error { // String implements the Stringer interface func (p rpacket) String() string { - str := "Packet {" + str := "RPacket {" str += fmt.Sprintf("\n\t%s}", p.metadata.String()) str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) return str @@ -204,7 +204,10 @@ func (p bpacket) Commands() []lorawan.MACCommand { // String implements the fmt.Stringer interface func (p bpacket) String() string { - return "TODO" + str := "BPacket {" + str += fmt.Sprintf("\n\t%s}", p.metadata.String()) + str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) + return str } // MarshalBinary implements the encoding.BinaryMarshaler interface @@ -390,7 +393,13 @@ func (p *jpacket) UnmarshalBinary(data []byte) error { // String implements the fmt.Stringer interface func (p jpacket) String() string { - return "TODO" + return fmt.Sprintf( + "JPacket{AppEUI:%v,DevEUI:%v,DevNonce:%v,Metadata:%v", + p.AppEUI(), + p.DevEUI(), + p.DevNonce(), + p.Metadata(), + ) } // acceptpacket implements the core.AcceptPacket interface @@ -435,7 +444,13 @@ func (p *cpacket) UnmarshalBinary(data []byte) error { // String implements the fmt.Stringer interface func (p cpacket) String() string { - return "TODO" + return fmt.Sprintf( + "CPacket{AppEUI:%v,DevEUI:%v,Payload:%v,NwkSKey:%v", + p.AppEUI(), + p.DevEUI(), + p.Payload(), + p.NwkSKey(), + ) } // -------------------------------------- @@ -544,14 +559,10 @@ func (p baserpacket) FCnt() uint32 { func (p baserpacket) Marshal() ([]byte, error) { var mtype byte switch p.payload.MHDR.MType { - case lorawan.JoinRequest: - fallthrough case lorawan.UnconfirmedDataUp: fallthrough case lorawan.ConfirmedDataUp: mtype = 1 // Up - case lorawan.JoinAccept: - fallthrough case lorawan.UnconfirmedDataDown: fallthrough case lorawan.ConfirmedDataDown: From 9b6a267767d0bf842b0c1458885a9bbdeecca950 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 11:44:34 +0100 Subject: [PATCH 0943/2266] [hotfix] Launch client disconnection in goroutine to avoid paho panics --- core/adapters/mqtt/mqtt_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 30474d38d..57c68c546 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -147,11 +147,11 @@ func TestMQTTSend(t *testing.T) { checkResponses(t, test.WantResponse, resp) // Clean - aclient.Disconnect(250) + go aclient.Disconnect(250) for _, sclient := range sclients { - sclient.Disconnect(250) + go sclient.Disconnect(250) } - <-time.After(time.Millisecond * 100) + <-time.After(time.Millisecond * 400) } } From ca6a8f568e97b9d5d05c6f4a33d3c723c213be1d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:09:03 +0100 Subject: [PATCH 0944/2266] [fix/uplink-handler] Remove wrong endpoint suffix in router CLI --- cmd/router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/router.go b/cmd/router.go index 1a2de1689..8e7e853d9 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -57,7 +57,7 @@ the gateway's duty cycle is (almost) full.`, var brokers []core.Recipient brokersStr := strings.Split(viper.GetString("router.brokers"), ",") for i := range brokersStr { - url := fmt.Sprintf("%s/packets/", strings.Trim(brokersStr[i], " ")) + url := strings.Trim(brokersStr[i], " ") brokers = append(brokers, http.NewRecipient(url, "POST")) } From f314682ee0a6848d4a4ea63c074bfcbe20058e6e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:09:53 +0100 Subject: [PATCH 0945/2266] [fix/uplink-handler] Unlock sync token when the recipient is errored + avoid subscribing when no topic down --- core/adapters/mqtt/handlers/activation.go | 2 +- core/adapters/mqtt/mqtt.go | 33 ++++++++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 9de5bce52..2f7fa226a 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -68,7 +68,7 @@ func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegR chreg <- RegReq{ Registration: activationRegistration{ - recipient: NewRecipient(topicUp, "DO_NOT_USE_THIS_TOPIC"), + recipient: NewRecipient(topicUp, ""), devEUI: devEUI, appEUI: appEUI, nwkSKey: nwkSKey, diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 923877857..6d7563b2d 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -96,21 +96,25 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err if !ok { err := errors.New(errors.Structural, "Unable to interpret recipient as mqttRecipient") a.ctx.WithField("recipient", r).Warn(err.Error()) + wg.Done() + wg.Done() cherr <- err continue } // Subscribe to down channel (before publishing anything) chdown := make(chan []byte) - token := a.Subscribe(recipient.TopicDown(), 2, func(client Client, msg MQTT.Message) { - chdown <- msg.Payload() - }) - if token.Wait() && token.Error() != nil { - err := errors.New(errors.Operational, "Unable to subscribe to down topic") - a.ctx.WithField("recipient", recipient).Warn(err.Error()) - cherr <- err - close(chdown) - continue + if recipient.TopicDown() != "" { + token := a.Subscribe(recipient.TopicDown(), 2, func(client Client, msg MQTT.Message) { + chdown <- msg.Payload() + }) + if token.Wait() && token.Error() != nil { + err := errors.New(errors.Operational, "Unable to subscribe to down topic") + a.ctx.WithField("recipient", recipient).Warn(err.Error()) + cherr <- err + close(chdown) + continue + } } // Publish on each topic @@ -120,6 +124,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err ctx := a.ctx.WithField("topic", recipient.TopicUp()) // Publish packet + ctx.WithField("data", data).Debug("Publish data to mqtt") token := a.Publish(recipient.TopicUp(), 2, false, data) if token.Wait() && token.Error() != nil { ctx.WithError(token.Error()).Error("Unable to publish") @@ -128,12 +133,20 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } }(recipient) + // Avoid waiting for response when there's no topic down + if recipient.TopicDown() == "" { + a.ctx.WithField("recipient", recipient).Debug("No response expected from mqtt recipient") + wg.Done() + continue + } + // Pull responses from each down topic, expecting only one go func(recipient Recipient, chdown <-chan []byte) { defer wg.Done() ctx := a.ctx.WithField("topic", recipient.TopicDown()) + ctx.Debug("Wait for mqtt response") defer func(ctx log.Interface) { if token := a.Unsubscribe(recipient.TopicDown()); token.Wait() && token.Error() != nil { ctx.Warn("Unable to unsubscribe topic") @@ -183,7 +196,7 @@ func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { if err := recipient.UnmarshalBinary(raw); err != nil { return nil, errors.New(errors.Structural, err) } - return *recipient, nil + return recipient, nil } // Next implements the core.Adapter interface From e8c5de460dd8887f4a41ebee22e375c7f4aa9409 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:10:34 +0100 Subject: [PATCH 0946/2266] [fix/uplink-handler] Avoid sending an empty slice as a response when there's no response --- core/adapters/http/http.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index c87d43fd7..6d9b33514 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -208,7 +208,9 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err cherr <- errors.New(errors.Operational, err) return } - chresp <- data + if len(data) > 0 { + chresp <- data + } if isBroadcast { // Generate registration on broadcast go func() { a.registrations <- RegReq{ From c69d0fa8ab1a13bf89bebcaeafd01f72ff50d27e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:11:18 +0100 Subject: [PATCH 0947/2266] [fix/uplink-handler] Add a test in the handler to test two different packets in a row --- core/components/handler/handler_test.go | 94 +++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index f3649bc0f..46d8a2820 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -727,4 +727,98 @@ func TestHandleUp(t *testing.T) { CheckSent(t, pktSent, adapter.InSendPacket) CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) } + + // -------------------- + + { + Desc(t, "Handle uplink with 2 different packets from same app | No downlink ready") + + // Build + recipient := NewMockJSONRecipient() + dataRecipient, _ := recipient.MarshalBinary() + + // First Packet + adapter1 := NewMockAdapter() + adapter1.OutGetRecipient = recipient + an1 := NewMockAckNacker() + inPkt1 := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "PayloadPacket1", + Metadata{ + Duty: pointer.Uint(75), + Rssi: pointer.Int(-25), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn1, _ := inPkt1.MarshalBinary() + + // Second Packet + adapter2 := NewMockAdapter() + adapter2.OutGetRecipient = recipient + an2 := NewMockAckNacker() + inPkt2 := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "PayloadPacket2", + Metadata{ + Duty: pointer.Uint(5), + Rssi: pointer.Int(0), + }, + 11, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn2, _ := inPkt2.MarshalBinary() + + // Expected responses + pktSent1, _ := NewAPacket( + inPkt1.AppEUI(), + inPkt1.DevEUI(), + []byte("PayloadPacket1"), + []Metadata{inPkt1.Metadata()}, + ) + + pktSent2, _ := NewAPacket( + inPkt1.AppEUI(), + inPkt1.DevEUI(), + []byte("PayloadPacket2"), + []Metadata{inPkt2.Metadata()}, + ) + + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + pktStorage := newMockPktStorage() + broker := NewMockJSONRecipient() + + // Operate #1 + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) + err := handler.HandleUp(dataIn1, an1, adapter1) + + // Check #1 + CheckErrors(t, nil, err) + CheckAcks(t, true, an1.InAck) + CheckSent(t, pktSent1, adapter1.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter1.InSendRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + + // Operate #2 + <-time.After(150 * time.Millisecond) + err = handler.HandleUp(dataIn2, an2, adapter2) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an2.InAck) + CheckSent(t, pktSent2, adapter2.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter2.InSendRecipients) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + } + } From f29bac52068bc73521bac698779f9039584a058e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:11:52 +0100 Subject: [PATCH 0948/2266] [fix/uplink-handler] Avoid failing when there's no response from the sender --- core/components/broker/broker.go | 3 +-- core/components/handler/handler.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 33d28ed07..ee9aa27cc 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -107,7 +107,6 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // It does matter here to use the DevEUI from the entry and not from the packet. // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check - b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt(), "up") // 4. Then we forward the packet to the handler and wait for the response @@ -120,7 +119,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return errors.New(errors.Structural, err) } resp, err := up.Send(hpacket, recipient) - if err != nil { + if err != nil && err.(errors.Failure).Nature != errors.Behavioural { stats.MarkMeter("broker.uplink.bad_handler_response") return errors.New(errors.Operational, err) } diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 66ee20aa5..28900df70 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -95,6 +95,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { devEUI := packet.DevEUI() // 1. Lookup for the associated AppSKey + Recipient + h.ctx.WithField("appEUI", appEUI).WithField("devEUI", devEUI).Debug("Perform lookup") entry, err := h.devices.Lookup(appEUI, devEUI) if err != nil { return err @@ -227,7 +228,7 @@ browseBundles: } _, err = adapter.Send(packet, recipient) - if err != nil { + if err != nil && err.(errors.Failure).Nature != errors.Behavioural { go h.abortConsume(err, bundles) continue browseBundles } From 76392d67ea2863387c0e96b370896f1edba684df Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:40:19 +0100 Subject: [PATCH 0949/2266] [refactor/errors] Introduce NotFound error Nature --- utils/errors/errors.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 1da1231fc..76b2103cc 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -12,10 +12,11 @@ import ( type Nature string const ( - Structural Nature = "Invalid structure" // Requests, parameters or inputs are wrong. Retry won't work. - Behavioural Nature = "Wrong but expected behavior" // No error but the result isn't the one expected - Operational Nature = "Invalid operation" // An operation failed due to external causes, a retry could work - Implementation Nature = "Illegal call" // Method not implemented or unsupported for the given structure + Structural Nature = "Invalid structure" // Requests, parameters or inputs are wrong. Retry won't work. + NotFound Nature = "Unable to found entity" // Failed to lookup something, somewhere + Behavioural Nature = "Wrong behavior or result" // No error but the result isn't the one expected + Operational Nature = "Invalid operation" // An operation failed due to external causes, a retry could work + Implementation Nature = "Illegal call" // Method not implemented or unsupported for the given structure ) // Failure states for fault that occurs during a process. From d4f2b76d506755d19ac05ce73c85b0103c7ea6e2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 16:52:17 +0100 Subject: [PATCH 0950/2266] [refactor/errors] Update utils to use new NotFound error Nature --- utils/storage/storage.go | 7 +++---- utils/storage/storage_test.go | 14 +++++++------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/utils/storage/storage.go b/utils/storage/storage.go index 1cdb46f0b..f86851c00 100644 --- a/utils/storage/storage.go +++ b/utils/storage/storage.go @@ -6,7 +6,6 @@ package storage import ( "encoding" "fmt" - "io" "reflect" "strings" "time" @@ -135,13 +134,13 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} bucket, err := getBucket(tx, bucketName) if err != nil { if err.(errors.Failure).Fault == bolt.ErrTxNotWritable { - return errors.New(errors.Behavioural, fmt.Sprintf("Not found %+v", key)) + return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) } return err } rawEntry = bucket.Get(key) if rawEntry == nil { - return errors.New(errors.Behavioural, fmt.Sprintf("Not found %+v", key)) + return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) } return nil }) @@ -162,7 +161,7 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} }) if err = r.Err(); err != nil { failure, ok := err.(errors.Failure) - if ok && failure.Nature == errors.Behavioural && failure.Fault == io.EOF { + if ok && failure.Nature == errors.Behavioural { break } return nil, errors.New(errors.Operational, err) diff --git a/utils/storage/storage_test.go b/utils/storage/storage_test.go index 2b25bcfe3..8ced02c45 100644 --- a/utils/storage/storage_test.go +++ b/utils/storage/storage_test.go @@ -75,7 +75,7 @@ func TestStoreAndLookup(t *testing.T) { { Desc(t, "Lookup in non-existing bucket") entries, err := itf.Lookup("DoesntExist", []byte{1, 2, 3}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } @@ -84,7 +84,7 @@ func TestStoreAndLookup(t *testing.T) { { Desc(t, "Lookup a non-existing key") entries, err := itf.Lookup("bucket", []byte{9, 9, 9, 9, 9}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } @@ -96,7 +96,7 @@ func TestStoreAndLookup(t *testing.T) { err := itf.Flush("bucket", []byte{1, 1, 1}) CheckErrors(t, nil, err) entries, err := itf.Lookup("bucket", []byte{1, 1, 1}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } @@ -108,7 +108,7 @@ func TestStoreAndLookup(t *testing.T) { err := itf.Flush("nested.bucket", []byte{2, 2, 2}) CheckErrors(t, nil, err) entries, err := itf.Lookup("nested.bucket", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } @@ -120,7 +120,7 @@ func TestStoreAndLookup(t *testing.T) { err := itf.Reset("mybucket") CheckErrors(t, nil, err) entries, err := itf.Lookup("mybucket", []byte{1, 1, 1}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } @@ -132,7 +132,7 @@ func TestStoreAndLookup(t *testing.T) { err := itf.Reset("mybucket.nested") CheckErrors(t, nil, err) entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } @@ -144,7 +144,7 @@ func TestStoreAndLookup(t *testing.T) { err := itf.Reset("mybucket") CheckErrors(t, nil, err) entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, nil, entries) } From 62d78b593e75aefd5ba1ac88951efa106f2bef1f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 17:08:32 +0100 Subject: [PATCH 0951/2266] [refactor/errors] Update adapters to use NotFound error nature --- core/adapters/http/http.go | 18 ++++++++++++------ core/adapters/mqtt/mqtt.go | 7 +++---- core/adapters/mqtt/mqtt_test.go | 4 ++-- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 6d9b33514..68eeedcc7 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -224,7 +224,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } case http.StatusNotFound: ctx.Debug("Recipient not interested in packet") - cherr <- errors.New(errors.Behavioural, "Recipient not interested") + cherr <- errors.New(errors.NotFound, "Recipient not interested") default: cherr <- errors.New(errors.Operational, fmt.Sprintf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode)) } @@ -238,13 +238,16 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err close(cherr) close(chresp) - // Collect errors and see if everything went well var errored uint8 + var notFound uint8 for i := 0; i < len(cherr); i++ { err := <-cherr - if err.(errors.Failure).Nature != errors.Behavioural { + if err.(errors.Failure).Nature != errors.NotFound { errored++ ctx.WithError(err).Warn("POST Failed") + } else { + notFound++ + ctx.WithError(err).Debug("Packet destination not found") } } @@ -253,14 +256,17 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err return nil, errors.New(errors.Behavioural, "Received too many positive answers") } - if len(chresp) == 0 && errored != 0 { + if len(chresp) == 0 && errored > 0 { return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answer") } - if len(chresp) == 0 && errored == 0 { - return nil, errors.New(errors.Behavioural, "No recipient gave a positive answer") + if len(chresp) == 0 && notFound > 0 { + return nil, errors.New(errors.NotFound, "No available recipient found") } + if len(chresp) == 0 { + return nil, nil + } return <-chresp, nil } diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 6d7563b2d..4d49bf9fc 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -179,14 +179,13 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err return nil, errors.New(errors.Behavioural, "Received too many positive answers") } - if len(chresp) == 0 && errored != 0 { + if len(chresp) == 0 && errored > 0 { return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answers") } - if len(chresp) == 0 && errored == 0 { - return nil, errors.New(errors.Behavioural, "No recipient gave a positive answer") + if len(chresp) == 0 { + return nil, nil } - return <-chresp, nil } diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 57c68c546..530c886ab 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -45,7 +45,7 @@ func TestMQTTSend(t *testing.T) { WantData: []byte("TheThingsNetwork"), WantResponse: nil, - WantError: pointer.String(string(errors.Behavioural)), + WantError: nil, }, { Desc: "invalid packet | 1 recipient | No response", @@ -80,7 +80,7 @@ func TestMQTTSend(t *testing.T) { WantData: []byte("TheThingsNetwork"), WantResponse: nil, - WantError: pointer.String(string(errors.Behavioural)), + WantError: nil, }, { Desc: "1 packet | 2 recipients | #1 answer ", From bbffd9f5788f17529fc6a24818751e6a6533dc04 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 17:21:37 +0100 Subject: [PATCH 0952/2266] [refactor/errors] Update components to use NotFound error nature --- core/components/broker/broker.go | 6 +++--- core/components/broker/broker_test.go | 6 +++--- core/components/broker/storage_test.go | 10 +++++----- core/components/handler/devStorage.go | 2 +- core/components/handler/devStorage_test.go | 2 +- core/components/handler/handler.go | 6 ++++-- core/components/handler/handler_test.go | 6 +++--- core/components/handler/pktStorage.go | 4 ++-- core/components/handler/pktStorage_test.go | 2 +- core/components/router/router.go | 4 ++-- core/components/router/router_test.go | 10 +++++----- core/components/router/storage.go | 13 +++++++------ core/components/router/storage_test.go | 6 +++--- 13 files changed, 40 insertions(+), 37 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index ee9aa27cc..1ac5fd7c3 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -67,7 +67,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { entries, err := b.LookupDevices(packet.DevEUI()) if err != nil { switch err.(errors.Failure).Nature { - case errors.Behavioural: + case errors.NotFound: stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") ctx.Debug("Uplink device not found") default: @@ -99,7 +99,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } if mEntry == nil { stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") - err := errors.New(errors.Behavioural, "MIC check returned no matches") + err := errors.New(errors.NotFound, "MIC check returned no matches") ctx.Debug(err.Error()) return err } @@ -119,7 +119,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { return errors.New(errors.Structural, err) } resp, err := up.Send(hpacket, recipient) - if err != nil && err.(errors.Failure).Nature != errors.Behavioural { + if err != nil && err.(errors.Failure).Nature != errors.NotFound { stats.MarkMeter("broker.uplink.bad_handler_response") return errors.New(errors.Operational, err) } diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index c2e4e4eb9..7e99d3b32 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -130,7 +130,7 @@ func TestHandleUp(t *testing.T) { an := mocks.NewMockAckNacker() adapter := mocks.NewMockAdapter() store := newMockController() - store.Failures["LookupDevices"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + store.Failures["LookupDevices"] = errors.New(errors.NotFound, "Mock Error: Not Found") data, _ := newBPacket( [4]byte{2, 3, 2, 3}, "Payload", @@ -143,7 +143,7 @@ func TestHandleUp(t *testing.T) { err := broker.HandleUp(data, an, adapter) // Check - errutil.CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + errutil.CheckErrors(t, pointer.String(string(errors.NotFound)), err) mocks.CheckAcks(t, false, an.InAck) CheckRegistrations(t, nil, store.InStoreDevices) CheckRegistrations(t, nil, store.InStoreApp) @@ -213,7 +213,7 @@ func TestHandleUp(t *testing.T) { err := broker.HandleUp(data, an, adapter) // Check - errutil.CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + errutil.CheckErrors(t, pointer.String(string(errors.NotFound)), err) mocks.CheckAcks(t, false, an.InAck) CheckRegistrations(t, nil, store.InStoreDevices) CheckRegistrations(t, nil, store.InStoreApp) diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index 23a690e7c..be59080dc 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -123,7 +123,7 @@ func TestNetworkControllerDevice(t *testing.T) { entries, err := db.LookupDevices(devEUI) // Checks - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckDevEntries(t, nil, entries) _ = db.Close() } @@ -179,7 +179,7 @@ func TestNetworkControllerDevice(t *testing.T) { err := db.StoreDevice(r) CheckErrors(t, pointer.String(string(errors.Structural)), err) entries, err := db.LookupDevices(r.DevEUI()) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckDevEntries(t, nil, entries) _ = db.Close() @@ -304,7 +304,7 @@ func TestNetworkControllerDevice(t *testing.T) { err := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "up") // Checks - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) _ = db.Close() } @@ -445,7 +445,7 @@ func TestNetworkControllerApplication(t *testing.T) { entry, err := db.LookupApplication(appEUI) // Checks - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckAppEntries(t, appEntry{}, entry) _ = db.Close() } @@ -501,7 +501,7 @@ func TestNetworkControllerApplication(t *testing.T) { err := db.StoreApplication(r) CheckErrors(t, pointer.String(string(errors.Structural)), err) entry, err := db.LookupApplication(r.AppEUI()) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckAppEntries(t, appEntry{}, entry) _ = db.Close() diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index d7f9b6d43..a0a78db82 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -51,7 +51,7 @@ func NewDevStorage(name string) (DevStorage, error) { func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), &devEntry{}) if err != nil { - return devEntry{}, err // Behavioural || Operational + return devEntry{}, err // Operational || NotFound } entries, ok := itf.([]devEntry) if !ok || len(entries) != 1 { diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 5d445f117..3043e84fb 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -64,7 +64,7 @@ func TestLookupStore(t *testing.T) { _, err := db.Lookup(r.AppEUI(), r.DevEUI()) // Check - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) } // ------------------ diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 28900df70..e102a909c 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -228,14 +228,14 @@ browseBundles: } _, err = adapter.Send(packet, recipient) - if err != nil && err.(errors.Failure).Nature != errors.Behavioural { + if err != nil { go h.abortConsume(err, bundles) continue browseBundles } // Now handle the downlink down, err := h.packets.Pull(appEUI, devEUI) - if err != nil && err.(errors.Failure).Nature != errors.Behavioural { + if err != nil && err.(errors.Failure).Nature != errors.NotFound { go h.abortConsume(err, bundles) continue browseBundles } @@ -277,6 +277,8 @@ browseBundles: } } + // TODO handle confirmed data down + bundle.Chresp <- resp } else { bundle.Chresp <- nil diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 46d8a2820..f3367cfdc 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -188,9 +188,9 @@ func TestHandleUp(t *testing.T) { // Build devStorage := newMockDevStorage() - devStorage.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock: Not Found") + devStorage.Failures["Lookup"] = errors.New(errors.NotFound, "Mock: Not Found") pktStorage := newMockPktStorage() - pktStorage.Failures["Pull"] = errors.New(errors.Behavioural, "Mock: Not Found") + pktStorage.Failures["Pull"] = errors.New(errors.NotFound, "Mock: Not Found") an := NewMockAckNacker() adapter := NewMockAdapter() inPkt := newHPacket( @@ -212,7 +212,7 @@ func TestHandleUp(t *testing.T) { err := handler.HandleUp(dataIn, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckPushed(t, nil, pktStorage.InPush) CheckPersonalized(t, nil, devStorage.InStorePersonalized) CheckAcks(t, false, an.InAck) diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index a3717b55a..bc8df6751 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -56,7 +56,7 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, e entries, err := s.db.Lookup(s.Name, key, &pktEntry{}) if err != nil { - return nil, err // Operational || Behavioural + return nil, err // Operational || NotFound } packets, ok := entries.([]pktEntry) @@ -67,7 +67,7 @@ func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, e // NOTE: one day, those entries will be more complicated, with a ttl. // Here's the place where we should check for that. Cheers. if len(packets) == 0 { - return nil, errors.New(errors.Behavioural, fmt.Sprintf("Entry not found for %v", key)) + return nil, errors.New(errors.NotFound, fmt.Sprintf("Entry not found for %v", key)) } pkt := packets[0] diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 1961694a9..f79b7f744 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -103,7 +103,7 @@ func TestPushPullNormal(t *testing.T) { p, err := db.Pull(appEUI, devEUI) // Check - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckPackets(t, nil, p) } diff --git a/core/components/router/router.go b/core/components/router/router.go index ba1c8dea7..7e5f7d760 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -65,7 +65,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Keeping track of the last FCnt maybe ? Having an overlap on the frame counter + the // device address might be less likely. entry, err := r.Lookup(packet.DevEUI()) - if err != nil && err.(errors.Failure).Nature != errors.Behavioural { + if err != nil && err.(errors.Failure).Nature != errors.NotFound { r.ctx.Warn("Database lookup failed") return errors.New(errors.Operational, err) } @@ -97,7 +97,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if err != nil { switch err.(errors.Failure).Nature { - case errors.Behavioural: + case errors.NotFound: stats.MarkMeter("router.uplink.negative_broker_response") r.ctx.WithError(err).Debug("Negative response from Broker") default: diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 848863a33..ba8c1f141 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -85,7 +85,7 @@ func TestHandleUp(t *testing.T) { adapter := NewMockAdapter() adapter.OutSend = nil store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") data, err := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", @@ -121,7 +121,7 @@ func TestHandleUp(t *testing.T) { adapter := NewMockAdapter() adapter.OutSend = dataResp store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") data, err := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", @@ -150,7 +150,7 @@ func TestHandleUp(t *testing.T) { an := NewMockAckNacker() adapter := NewMockAdapter() store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not Found") + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") // Operate router := New(store, GetLogger(t, "Router")) @@ -272,7 +272,7 @@ func TestHandleUp(t *testing.T) { adapter := NewMockAdapter() adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not found") + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not found") data, err := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", @@ -302,7 +302,7 @@ func TestHandleUp(t *testing.T) { adapter := NewMockAdapter() adapter.OutSend = []byte{1, 2, 3} store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Behavioural, "Mock Error: Not found") + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not found") data, err := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", diff --git a/core/components/router/storage.go b/core/components/router/storage.go index e8f127621..2752977e3 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -67,7 +67,7 @@ func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { if err := s.db.Flush(s.Name, devEUI[:]); err != nil { return entry{}, errors.New(errors.Operational, err) } - return entry{}, errors.New(errors.Behavioural, "Not Found") + return entry{}, errors.New(errors.NotFound, "Not Found") } e := entries[0] @@ -76,7 +76,7 @@ func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { if err := s.db.Flush(s.Name, devEUI[:]); err != nil { return entry{}, errors.New(errors.Operational, err) } - return entry{}, errors.New(errors.Behavioural, "Not Found") + return entry{}, errors.New(errors.NotFound, "Not Found") } return e, nil @@ -94,12 +94,13 @@ func (s *storage) Store(reg RRegistration) error { defer s.Unlock() _, err = s.lookup(devEUI, false) - if err == nil || err != nil && err.(errors.Failure).Nature != errors.Behavioural { - if err == nil { - return errors.New(errors.Structural, "Already exists") - } + if err == nil { + return errors.New(errors.Structural, "Already exists") + } + if err.(errors.Failure).Nature != errors.NotFound { return err } + return s.db.Store(s.Name, devEUI[:], []dbutil.Entry{&entry{ Recipient: recipient, until: time.Now().Add(s.ExpiryDelay), diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index 180acca43..adeff64d3 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -100,7 +100,7 @@ func TestStoreAndLookup(t *testing.T) { gotEntry, err := db.Lookup(devEUI) // Checks - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, entry{}, gotEntry) _ = db.Close() } @@ -121,7 +121,7 @@ func TestStoreAndLookup(t *testing.T) { gotEntry, err := db.Lookup(r.DevEUI()) // Checks - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, entry{}, gotEntry) _ = db.Close() } @@ -206,7 +206,7 @@ func TestStoreAndLookup(t *testing.T) { err := db.Store(r) CheckErrors(t, pointer.String(string(errors.Structural)), err) gotEntry, err := db.Lookup(r.DevEUI()) - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) + CheckErrors(t, pointer.String(string(errors.NotFound)), err) CheckEntries(t, entry{}, gotEntry) _ = db.Close() From 4b284f7182cb5aca9d15964f4e6db9681231c41d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 17:54:09 +0100 Subject: [PATCH 0953/2266] [refactor/errors] Update duty cycle manager with NotFound error nature --- core/dutycycle/dutyManager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 7270c925f..9baf36960 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -125,7 +125,7 @@ func (m *dutyManager) Update(id []byte, freq float64, size uint, datr string, co var entry dutyEntry if err == nil { entry = itf.([]dutyEntry)[0] - } else if err.(errors.Failure).Nature == errors.Behavioural { + } else if err.(errors.Failure).Nature == errors.NotFound { entry = dutyEntry{ Until: time.Unix(0, 0), OnAir: make(map[subBand]time.Duration), From a7e9e93eaed152ea0afcefab745a3b65d38a4ce1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 18:18:11 +0100 Subject: [PATCH 0954/2266] [refactor/nack] Add an error to Nack arguments --- core/adapters/http/acknacker_test.go | 12 ++++++------ core/adapters/http/handlers/helpers_test.go | 4 ++-- core/adapters/http/httpAckNacker.go | 2 +- core/adapters/http/regAckNacker.go | 2 +- core/adapters/mqtt/mqttAckNacker.go | 2 +- core/adapters/udp/udpAckNacker.go | 2 +- core/components/broker/broker.go | 2 +- core/components/handler/handler.go | 2 +- core/components/router/router.go | 2 +- core/core.go | 2 +- core/mocks/mocks.go | 9 ++++++--- 11 files changed, 22 insertions(+), 19 deletions(-) diff --git a/core/adapters/http/acknacker_test.go b/core/adapters/http/acknacker_test.go index 4a9d08991..55daa040f 100644 --- a/core/adapters/http/acknacker_test.go +++ b/core/adapters/http/acknacker_test.go @@ -132,7 +132,7 @@ func TestHTTPAckNacker(t *testing.T) { an := httpAckNacker{Chresp: chresp} // Operate - err := an.Nack() + err := an.Nack(nil) // Expectation want := &MsgRes{ @@ -154,7 +154,7 @@ func TestHTTPAckNacker(t *testing.T) { an := httpAckNacker{Chresp: nil} // Operate - err := an.Nack() + err := an.Nack(nil) // Check errutil.CheckErrors(t, nil, err) @@ -173,7 +173,7 @@ func TestHTTPAckNacker(t *testing.T) { // Operate cherr := make(chan error) go func() { - cherr <- an.Nack() + cherr <- an.Nack(nil) }() // Check @@ -283,7 +283,7 @@ func TestRegAckNacker(t *testing.T) { an := regAckNacker{Chresp: chresp} // Operate - err := an.Nack() + err := an.Nack(nil) // Expectation want := &MsgRes{ @@ -305,7 +305,7 @@ func TestRegAckNacker(t *testing.T) { an := regAckNacker{Chresp: nil} // Operate - err := an.Nack() + err := an.Nack(nil) // Check errutil.CheckErrors(t, nil, err) @@ -324,7 +324,7 @@ func TestRegAckNacker(t *testing.T) { // Operate cherr := make(chan error) go func() { - cherr <- an.Nack() + cherr <- an.Nack(nil) }() // Check diff --git a/core/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go index 87a4d78f1..81fe19eca 100644 --- a/core/adapters/http/handlers/helpers_test.go +++ b/core/adapters/http/handlers/helpers_test.go @@ -136,7 +136,7 @@ func tryNext(adapter core.Adapter, shouldAck bool, packet core.Packet) ([]byte, if shouldAck { an.Ack(packet) } else { - an.Nack() + an.Nack(nil) } }() @@ -169,7 +169,7 @@ func tryNextRegistration(adapter core.Adapter, shouldAck bool, packet core.Packe if shouldAck { an.Ack(packet) } else { - an.Nack() + an.Nack(nil) } }() diff --git a/core/adapters/http/httpAckNacker.go b/core/adapters/http/httpAckNacker.go index 8657b3fcc..453022b00 100644 --- a/core/adapters/http/httpAckNacker.go +++ b/core/adapters/http/httpAckNacker.go @@ -44,7 +44,7 @@ func (an httpAckNacker) Ack(p core.Packet) error { } // Nack implements the core.AckNacker interface -func (an httpAckNacker) Nack() error { +func (an httpAckNacker) Nack(err error) error { if an.Chresp == nil { return nil } diff --git a/core/adapters/http/regAckNacker.go b/core/adapters/http/regAckNacker.go index 4bf8f2275..6164b7474 100644 --- a/core/adapters/http/regAckNacker.go +++ b/core/adapters/http/regAckNacker.go @@ -32,7 +32,7 @@ func (r regAckNacker) Ack(p core.Packet) error { } // Nack implements the core.Nacker interface -func (r regAckNacker) Nack() error { +func (r regAckNacker) Nack(err error) error { if r.Chresp == nil { return nil } diff --git a/core/adapters/mqtt/mqttAckNacker.go b/core/adapters/mqtt/mqttAckNacker.go index fe20a04a1..5888808af 100644 --- a/core/adapters/mqtt/mqttAckNacker.go +++ b/core/adapters/mqtt/mqttAckNacker.go @@ -43,7 +43,7 @@ func (an mqttAckNacker) Ack(p core.Packet) error { } // Nack implements the core.AckNacker interface -func (an mqttAckNacker) Nack() error { +func (an mqttAckNacker) Nack(err error) error { if an.Chresp == nil { return nil } diff --git a/core/adapters/udp/udpAckNacker.go b/core/adapters/udp/udpAckNacker.go index fede90f8d..b32701796 100644 --- a/core/adapters/udp/udpAckNacker.go +++ b/core/adapters/udp/udpAckNacker.go @@ -31,7 +31,7 @@ func (an udpAckNacker) Ack(p core.Packet) error { } // Ack implements the core.Adapter interface -func (an udpAckNacker) Nack() error { +func (an udpAckNacker) Nack(err error) error { defer close(an.Chresp) return nil } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 1ac5fd7c3..052993431 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -163,7 +163,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { func ensureAckNack(an AckNacker, ack *Packet, err *error) { if err != nil && *err != nil { - an.Nack() + an.Nack(*err) } else { var p Packet if ack != nil { diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index e102a909c..c5480b213 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -377,7 +377,7 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro func ensureAckNack(an AckNacker, ack *Packet, err *error) { if err != nil && *err != nil { - an.Nack() + an.Nack(*err) } else { var p Packet if ack != nil { diff --git a/core/components/router/router.go b/core/components/router/router.go index 7e5f7d760..24bdd81a6 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -140,7 +140,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { func ensureAckNack(an AckNacker, ack *Packet, err *error) { if err != nil && *err != nil { - an.Nack() + an.Nack(*err) } else { var p Packet if ack != nil { diff --git a/core/core.go b/core/core.go index cf86b5294..7e391e837 100644 --- a/core/core.go +++ b/core/core.go @@ -23,7 +23,7 @@ type Broker interface { // behaviour expected by the caller. type AckNacker interface { Ack(p Packet) error - Nack() error + Nack(err error) error } // Adapter handles communications between components. They implement a specific communication diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 68545ac88..1e789f451 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -225,6 +225,7 @@ type MockAckNacker struct { InAck struct { Ack *bool Packet Packet + Error error } } @@ -236,6 +237,7 @@ func (an *MockAckNacker) Ack(p Packet) error { an.InAck = struct { Ack *bool Packet Packet + Error error }{ Ack: pointer.Bool(true), Packet: p, @@ -243,13 +245,14 @@ func (an *MockAckNacker) Ack(p Packet) error { return nil } -func (an *MockAckNacker) Nack() error { +func (an *MockAckNacker) Nack(err error) error { an.InAck = struct { Ack *bool Packet Packet + Error error }{ - Ack: pointer.Bool(false), - Packet: nil, + Ack: pointer.Bool(false), + Error: err, } return nil } From 583f7e9f4097eff3988b17f77d526c115cc7e216 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 18:37:14 +0100 Subject: [PATCH 0955/2266] [refactor/nack] Distinguish error status code in http AckNacker --- core/adapters/http/httpAckNacker.go | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/core/adapters/http/httpAckNacker.go b/core/adapters/http/httpAckNacker.go index 453022b00..82c199d2f 100644 --- a/core/adapters/http/httpAckNacker.go +++ b/core/adapters/http/httpAckNacker.go @@ -50,11 +50,28 @@ func (an httpAckNacker) Nack(err error) error { } defer close(an.Chresp) + var code int + var content []byte + + if err == nil { + code = http.StatusInternalServerError + content = []byte("Unknown Internal Error") + } else { + switch err.(errors.Failure).Nature { + case errors.NotFound: + code = http.StatusNotFound + case errors.Behavioural: + code = http.StatusNotAcceptable + case errors.Implementation: + code = http.StatusNotImplemented + default: + code = http.StatusInternalServerError + } + content = []byte(err.Error()) + } + select { - case an.Chresp <- MsgRes{ - StatusCode: http.StatusNotFound, - Content: []byte(errors.Structural), - }: + case an.Chresp <- MsgRes{StatusCode: code, Content: content}: return nil case <-time.After(time.Millisecond * 50): return errors.New(errors.Operational, "No response was given to the acknacker") From 70434cf02fc776aa4e70aa5bb7b1c0ef8173d6ae Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 18:37:22 +0100 Subject: [PATCH 0956/2266] [refactor/nack] Add related test cases --- core/adapters/http/acknacker_test.go | 100 ++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/acknacker_test.go b/core/adapters/http/acknacker_test.go index 55daa040f..a8abe65bb 100644 --- a/core/adapters/http/acknacker_test.go +++ b/core/adapters/http/acknacker_test.go @@ -125,7 +125,7 @@ func TestHTTPAckNacker(t *testing.T) { // -------------------- { - Desc(t, "Nack") + Desc(t, "Nack no error") // Build chresp := make(chan MsgRes, 1) @@ -134,10 +134,106 @@ func TestHTTPAckNacker(t *testing.T) { // Operate err := an.Nack(nil) + // Expectation + want := &MsgRes{ + StatusCode: http.StatusInternalServerError, + Content: []byte("Unknown Internal Error"), + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Nack NotFound error") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + e := errors.New(errors.NotFound, "Not Found") + + // Operate + err := an.Nack(e) + // Expectation want := &MsgRes{ StatusCode: http.StatusNotFound, - Content: []byte(errors.Structural), + Content: []byte(e.Error()), + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Nack Behavioural error") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + e := errors.New(errors.Behavioural, "Behavioural") + + // Operate + err := an.Nack(e) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusNotAcceptable, + Content: []byte(e.Error()), + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Nack Operational error") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + e := errors.New(errors.Operational, "Operational") + + // Operate + err := an.Nack(e) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusInternalServerError, + Content: []byte(e.Error()), + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, want, chresp) + } + + // -------------------- + + { + Desc(t, "Nack Implementation error") + + // Build + chresp := make(chan MsgRes, 1) + an := httpAckNacker{Chresp: chresp} + e := errors.New(errors.Implementation, "Implementation") + + // Operate + err := an.Nack(e) + + // Expectation + want := &MsgRes{ + StatusCode: http.StatusNotImplemented, + Content: []byte(e.Error()), } // Check From 1241a4ed7a74bfaebd047a12efde1d65ec0f2090 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 18:44:12 +0100 Subject: [PATCH 0957/2266] [refactor/nack] Fix last errored parts due to changes --- core/adapters/http/handlers/collect_test.go | 4 ++-- core/mocks/mocks.go | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/adapters/http/handlers/collect_test.go b/core/adapters/http/handlers/collect_test.go index 9ea02c362..33333ae5f 100644 --- a/core/adapters/http/handlers/collect_test.go +++ b/core/adapters/http/handlers/collect_test.go @@ -59,8 +59,8 @@ func TestCollect(t *testing.T) { Method: "POST", ShouldAck: false, - WantContent: string(errors.Structural), - WantStatusCode: http.StatusNotFound, + WantContent: "Unknown", + WantStatusCode: http.StatusInternalServerError, WantPacket: []byte("Patate"), WantError: nil, }, diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 1e789f451..852ae50b5 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -403,6 +403,7 @@ func CheckAcks(t *testing.T, want interface{}, gotItf interface{}) { got := gotItf.(struct { Ack *bool Packet Packet + Error error }) if got.Ack == nil { From c2ce8215e8a0eee7c53d15cb082fef7170eb6b2e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 8 Mar 2016 18:52:11 +0100 Subject: [PATCH 0958/2266] [refactor/nack] Add nil check in udpAckNacker --- core/adapters/udp/udpAckNacker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/adapters/udp/udpAckNacker.go b/core/adapters/udp/udpAckNacker.go index b32701796..30791d9ed 100644 --- a/core/adapters/udp/udpAckNacker.go +++ b/core/adapters/udp/udpAckNacker.go @@ -18,6 +18,9 @@ type udpAckNacker struct { // Ack implements the core.Adapter interface func (an udpAckNacker) Ack(p core.Packet) error { defer close(an.Chresp) + if p == nil { + return nil + } data, err := p.MarshalBinary() if err != nil { return errors.New(errors.Structural, err) From a60f7b46b3a67e03dab8e3a2a11ff6224a634af3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 11:20:38 +0100 Subject: [PATCH 0959/2266] [tests/udp] Add mock and helpers for udp tests --- core/adapters/udp/helpers_test.go | 43 +++++++++++++++++++++++++++++++ core/adapters/udp/mock_test.go | 32 +++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 core/adapters/udp/helpers_test.go create mode 100644 core/adapters/udp/mock_test.go diff --git a/core/adapters/udp/helpers_test.go b/core/adapters/udp/helpers_test.go new file mode 100644 index 000000000..4d1f04b31 --- /dev/null +++ b/core/adapters/udp/helpers_test.go @@ -0,0 +1,43 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/mocks" +) + +// tryNext attempts to get a next packet from the adapter. It timeouts after a given delay if +// nothing is ready. +func tryNext(adapter *Adapter) ([]byte, error) { + chresp := make(chan struct { + Packet []byte + Error error + }) + go func() { + packet, an, err := adapter.Next() + if err != nil { + an.Nack(nil) + } else { + an.Ack(nil) + } + chresp <- struct { + Packet []byte + Error error + }{packet, err} + }() + + select { + case resp := <-chresp: + return resp.Packet, resp.Error + case <-time.After(time.Millisecond * 75): + return nil, nil + } +} + +func CheckPackets(t *testing.T, want []byte, got []byte) { + mocks.Check(t, want, got, "Packets") +} diff --git a/core/adapters/udp/mock_test.go b/core/adapters/udp/mock_test.go new file mode 100644 index 000000000..f62000047 --- /dev/null +++ b/core/adapters/udp/mock_test.go @@ -0,0 +1,32 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +// MockHandler implements the udp.Handler interface. +type MockHandler struct { + OutMsgUDP []byte + OutMsgReq []byte + InMsg MsgUDP + InChresp []byte +} + +// Handle implements the udp.Handler interface +func (h *MockHandler) Handle(conn chan<- MsgUDP, next chan<- MsgReq, msg MsgUDP) { + h.InMsg = msg + if h.OutMsgReq != nil { + chresp := make(chan MsgRes) + next <- MsgReq{ + Data: h.OutMsgReq, + Chresp: chresp, + } + h.InChresp = <-chresp + } + + if h.OutMsgUDP != nil { + conn <- MsgUDP{ + Data: h.OutMsgUDP, + Addr: msg.Addr, + } + } +} From d0867934b5d26b2929b425152bf901f0dfc5ed0c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 11:20:59 +0100 Subject: [PATCH 0960/2266] [tests/udp] Write down tests for udp adapter --- core/adapters/udp/udp_test.go | 176 ++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index 92d304cd3..99a1588bb 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -2,3 +2,179 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package udp + +import ( + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + testutil "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestNext(t *testing.T) { + { + testutil.Desc(t, "Send a packet when no handler is defined") + + // Build + addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2016") + conn, _ := net.DialUDP("udp", nil, addr) + adapter, errNew := NewAdapter("0.0.0.0:2016", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + <-time.After(time.Millisecond * 25) + _, errWrite := conn.Write([]byte{1, 2, 3, 4}) + + // Operate + packet, errNext := tryNext(adapter) + + // Check + errutil.CheckErrors(t, nil, errWrite) + CheckPackets(t, nil, packet) + errutil.CheckErrors(t, nil, errNext) + } + + // -------------------- + + { + testutil.Desc(t, "Start adapter on a busy connection") + + // Build + addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2017") + _, _ = net.ListenUDP("udp", addr) + _, errNew := NewAdapter("0.0.0.0:2017", testutil.GetLogger(t, "Adapter")) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), errNew) + } + + // -------------------- + + { + testutil.Desc(t, "Attach a handler to the adapter and fake udp reception") + + // Build + addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2018") + conn, _ := net.DialUDP("udp", nil, addr) + handler := &MockHandler{} + adapter, errNew := NewAdapter("0.0.0.0:2018", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + adapter.Bind(handler) + _, errWrite := conn.Write([]byte{1, 2, 3, 4}) + <-time.After(time.Millisecond * 25) + + // Check + errutil.CheckErrors(t, nil, errWrite) + CheckPackets(t, []byte{1, 2, 3, 4}, handler.InMsg.Data) + } + + // -------------------- + + { + testutil.Desc(t, "Send next data through the handler") + + // Build + addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2019") + conn, _ := net.DialUDP("udp", nil, addr) + handler := &MockHandler{} + handler.OutMsgReq = []byte{14, 42, 14, 42} + adapter, errNew := NewAdapter("0.0.0.0:2019", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + adapter.Bind(handler) + _, errWrite := conn.Write([]byte{1, 2, 3, 4}) + <-time.After(time.Millisecond * 25) + packet, errNext := tryNext(adapter) + + // Check + errutil.CheckErrors(t, nil, errWrite) + errutil.CheckErrors(t, nil, errNext) + CheckPackets(t, []byte{1, 2, 3, 4}, handler.InMsg.Data) + CheckPackets(t, nil, handler.InChresp) + CheckPackets(t, []byte{14, 42, 14, 42}, packet) + } + + // -------------------- + + { + testutil.Desc(t, "Send next data back through the connection") + + // Build + addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2020") + conn, _ := net.DialUDP("udp", nil, addr) + read := make([]byte, 20) + handler := &MockHandler{} + handler.OutMsgUDP = []byte{14, 42, 14, 42} + adapter, errNew := NewAdapter("0.0.0.0:2020", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + adapter.Bind(handler) + _, errWrite := conn.Write([]byte{1, 2, 3, 4}) + <-time.After(time.Millisecond * 25) + packet, errNext := tryNext(adapter) + n, errRead := conn.Read(read) + + // Check + errutil.CheckErrors(t, nil, errWrite) + errutil.CheckErrors(t, nil, errRead) + errutil.CheckErrors(t, nil, errNext) + CheckPackets(t, []byte{1, 2, 3, 4}, handler.InMsg.Data) + CheckPackets(t, read[:n], []byte{14, 42, 14, 42}) + CheckPackets(t, nil, packet) + } +} + +func TestNotImplemented(t *testing.T) { + { + testutil.Desc(t, "NextRegistration ~> not implemented") + + // Build + adapter, errNew := NewAdapter("0.0.0.0:2021", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + _, _, errNext := adapter.NextRegistration() + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errNext) + } + + // -------------------- + + { + testutil.Desc(t, "Send ~> not implemented") + + // Build + adapter, errNew := NewAdapter("0.0.0.0:2022", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + _, errSend := adapter.Send(nil) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errSend) + } + + // -------------------- + + { + testutil.Desc(t, "GetRecipient ~> not implemented") + + // Build + adapter, errNew := NewAdapter("0.0.0.0:2023", testutil.GetLogger(t, "Adapter")) + errutil.CheckErrors(t, nil, errNew) + + // Operate + _, errGet := adapter.GetRecipient(nil) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errGet) + } +} From d089d8941d3037245bb58bd437c1159fd83c2050 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 11:21:34 +0100 Subject: [PATCH 0961/2266] [tests/udp] Change error nature on new + wait for goroutine to have started before returning --- core/adapters/udp/udp.go | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 6502e8d62..9d469772f 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -6,6 +6,7 @@ package udp import ( "fmt" "net" + "sync" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -56,12 +57,17 @@ func NewAdapter(bindNet string, ctx log.Interface) (*Adapter, error) { a.ctx.WithField("bind", bindNet).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid bind address %v", bindNet)) + return nil, errors.New(errors.Operational, fmt.Sprintf("Invalid bind address %v", bindNet)) } - go a.monitorConnection(udpConn) - go a.monitorHandlers() - go a.listen(udpConn) + waitStart := &sync.WaitGroup{} + waitStart.Add(3) + + go a.monitorConnection(udpConn, waitStart) + go a.monitorHandlers(waitStart) + go a.listen(udpConn, waitStart) + + waitStart.Wait() return &a, nil } @@ -95,9 +101,10 @@ func (a *Adapter) Bind(h Handler) { // listen Handle incoming packets and forward them. // // Runs in its own goroutine. -func (a *Adapter) listen(conn *net.UDPConn) { +func (a *Adapter) listen(conn *net.UDPConn, ready *sync.WaitGroup) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") + ready.Done() for { buf := make([]byte, 5000) n, addr, err := conn.ReadFromUDP(buf) @@ -117,7 +124,8 @@ func (a *Adapter) listen(conn *net.UDPConn) { // Doing this makes sure that only 1 goroutine is interacting with the connection. // // Runs in its own goroutine -func (a *Adapter) monitorConnection(udpConn *net.UDPConn) { +func (a *Adapter) monitorConnection(udpConn *net.UDPConn, ready *sync.WaitGroup) { + ready.Done() for msg := range a.conn { if msg.Data != nil { // Send the given udp message if _, err := udpConn.WriteToUDP(msg.Data, msg.Addr); err != nil { @@ -135,9 +143,10 @@ func (a *Adapter) monitorConnection(udpConn *net.UDPConn) { // channel to ask every defined handler to handle them. // // Runs in its own goroutine -func (a *Adapter) monitorHandlers() { +func (a *Adapter) monitorHandlers(ready *sync.WaitGroup) { var handlers []Handler + ready.Done() for msg := range a.handlers { switch msg.(type) { case Handler: From 5375c1d3e3c6f7e3d4862679f24a0b6378792cb7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 11:41:04 +0100 Subject: [PATCH 0962/2266] [tests/udp] Write tests for udpRegistration and udpAckNacker --- core/adapters/udp/helpers_test.go | 16 ++++ core/adapters/udp/udp_test.go | 119 ++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/core/adapters/udp/helpers_test.go b/core/adapters/udp/helpers_test.go index 4d1f04b31..49eefb903 100644 --- a/core/adapters/udp/helpers_test.go +++ b/core/adapters/udp/helpers_test.go @@ -7,7 +7,9 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/brocaar/lorawan" ) // tryNext attempts to get a next packet from the adapter. It timeouts after a given delay if @@ -38,6 +40,20 @@ func tryNext(adapter *Adapter) ([]byte, error) { } } +// ----- CHECK utilities + func CheckPackets(t *testing.T, want []byte, got []byte) { mocks.Check(t, want, got, "Packets") } + +func CheckResps(t *testing.T, want MsgRes, got MsgRes) { + mocks.Check(t, want, got, "Responses") +} + +func CheckRecipients(t *testing.T, want core.Recipient, got core.Recipient) { + mocks.Check(t, want, got, "Recipients") +} + +func CheckDevEUIs(t *testing.T, want lorawan.EUI64, got lorawan.EUI64) { + mocks.Check(t, want, got, "DevEUIs") +} diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index 99a1588bb..52a7145f3 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -8,10 +8,12 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" testutil "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestNext(t *testing.T) { @@ -178,3 +180,120 @@ func TestNotImplemented(t *testing.T) { errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errGet) } } + +func TestUDPRegistration(t *testing.T) { + reg := udpRegistration{} + CheckRecipients(t, nil, reg.Recipient()) + CheckDevEUIs(t, lorawan.EUI64{}, reg.DevEUI()) +} + +func TestUDPAckNacker(t *testing.T) { + { + testutil.Desc(t, "Ack nil packet") + + // Build + chresp := make(chan MsgRes) + an := udpAckNacker{Chresp: chresp} + + // Operate + var resp MsgRes + go func() { + select { + case resp = <-chresp: + case <-time.After(time.Millisecond * 25): + } + }() + err := an.Ack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, resp) + } + + // -------------------- + + { + testutil.Desc(t, "Ack valid packet") + + // Build + chresp := make(chan MsgRes) + an := udpAckNacker{Chresp: chresp} + pkt := mocks.NewMockPacket() + + // Operate + var resp MsgRes + go func() { + select { + case resp = <-chresp: + case <-time.After(time.Millisecond * 25): + } + }() + err := an.Ack(pkt) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, pkt.OutMarshalBinary, resp) + } + + // -------------------- + + { + testutil.Desc(t, "Ack invalid packet") + + // Build + chresp := make(chan MsgRes) + an := udpAckNacker{Chresp: chresp} + pkt := mocks.NewMockPacket() + pkt.Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error") + + // Operate + var resp MsgRes + go func() { + select { + case resp = <-chresp: + case <-time.After(time.Millisecond * 25): + } + }() + err := an.Ack(pkt) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckResps(t, nil, resp) + } + + // -------------------- + + { + testutil.Desc(t, "Ack not consumed") + + // Build + chresp := make(chan MsgRes) + an := udpAckNacker{Chresp: chresp} + pkt := mocks.NewMockPacket() + + // Operate + err := an.Ack(pkt) + resp, _ := <-chresp + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckResps(t, nil, resp) + } + + // -------------------- + + { + testutil.Desc(t, "Nack nil error") + + // Build + chresp := make(chan MsgRes) + an := udpAckNacker{Chresp: chresp} + + // Operate + err := an.Nack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + } + +} From 6643178ab7eccc6c4b3b7695a7862646868ee83a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 9 Mar 2016 13:02:46 +0100 Subject: [PATCH 0963/2266] [stats] Add stats collection to Handler component Related to #72 --- core/components/handler/handler.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c5480b213..37dfdee32 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -11,6 +11,7 @@ import ( . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -33,6 +34,7 @@ type bundle struct { Entry devEntry ID [20]byte Packet HPacket + Time time.Time } // New construct a new Handler @@ -58,9 +60,11 @@ func New(devDb DevStorage, pktDb PktStorage, broker JSONRecipient, ctx log.Inter func (h component) Register(reg Registration, an AckNacker, sub Subscriber) (err error) { h.ctx.WithField("registration", reg).Debug("New registration request") defer ensureAckNack(an, nil, &err) + stats.MarkMeter("handler.registration.in") hreg, ok := reg.(HRegistration) if !ok { + stats.MarkMeter("handler.registration.invalid") return errors.New(errors.Structural, "Not a Handler registration") } @@ -81,14 +85,18 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Make sure we don't forget the AckNacker var ack Packet defer ensureAckNack(an, &ack, &err) + stats.MarkMeter("handler.uplink.in") itf, err := UnmarshalPacket(data) if err != nil { + stats.MarkMeter("handler.uplink.invalid") return errors.New(errors.Structural, err) } switch itf.(type) { case HPacket: + stats.MarkMeter("handler.uplink.data") + // 0. Retrieve the handler packet packet := itf.(HPacket) appEUI := packet.AppEUI() @@ -125,6 +133,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { Entry: entry, Adapter: up, Chresp: chresp, + Time: time.Now(), } // 5. Wait for the response. Could be an error, a packet or nothing. @@ -135,19 +144,25 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { resp := <-chresp switch resp.(type) { case BPacket: + stats.MarkMeter("handler.uplink.ack.with_response") + stats.MarkMeter("handler.downlink.out") ctx.Debug("Received response with packet. Sending Ack") ack = resp.(Packet) case error: + stats.MarkMeter("handler.uplink.error") ctx.WithError(resp.(error)).Warn("Received errored response. Sending Ack") return errors.New(errors.Operational, resp.(error)) default: + stats.MarkMeter("handler.uplink.ack.without_response") ctx.Debug("Received empty response. Sending empty Ack") } return nil case JPacket: + stats.MarkMeter("handler.uplink.join_request") return errors.New(errors.Implementation, "Join Request not yet implemented") default: + stats.MarkMeter("handler.uplink.unknown") return errors.New(errors.Implementation, "Unhandled packet type") } } @@ -177,6 +192,9 @@ browseBundles: var payload []byte var bestBundle int var bestScore uint + var firstTime time.Time + + stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) for i, bundle := range bundles { // We only decrypt the payload of the first bundle's packet. @@ -190,6 +208,11 @@ browseBundles: continue browseBundles } bestBundle = i + firstTime = bundle.Time + stats.MarkMeter("handler.uplink.in.unique") + } else { + diff := bundle.Time.Sub(firstTime).Nanoseconds() + stats.UpdateHistogram("handler.uplink.duplicate.delay", diff/1000) } // Append metadata for each of them @@ -232,6 +255,7 @@ browseBundles: go h.abortConsume(err, bundles) continue browseBundles } + stats.MarkMeter("handler.uplink.out") // Now handle the downlink down, err := h.packets.Pull(appEUI, devEUI) @@ -246,6 +270,7 @@ browseBundles: var resp Packet if down != nil && err == nil { + stats.MarkMeter("handler.downlink.pull") fcnt := bundles[bestBundle].Packet.FCnt() + 1 macPayload := lorawan.NewMACPayload(false) macPayload.FHDR = lorawan.FHDR{ @@ -290,6 +315,7 @@ browseBundles: // Abort consume forward the given error to all bundle recipients func (h component) abortConsume(fault error, bundles []bundle) { err := errors.New(errors.Structural, fault) + stats.MarkMeter("handler.uplink.invalid") h.ctx.WithError(err).Debug("Unable to consume bundle") for _, bundle := range bundles { bundle.Chresp <- err @@ -360,10 +386,12 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro // Make sure we don't forget the AckNacker var ack Packet defer ensureAckNack(an, &ack, &err) + stats.MarkMeter("handler.downlink.in") // Unmarshal the given packet and see what gift we get itf, err := UnmarshalPacket(data) if err != nil { + stats.MarkMeter("handler.downlink.invalid") return errors.New(errors.Structural, err) } @@ -371,6 +399,7 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro case APacket: return h.packets.Push(itf.(APacket)) default: + stats.MarkMeter("handler.downlink.invalid") return errors.New(errors.Implementation, "Unhandled packet type") } } From 4f72877a3c6e311dfb65cbaae426b210d0d408d5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 9 Mar 2016 15:10:59 +0100 Subject: [PATCH 0964/2266] [hotfix] Fix cli doc for uplink-port --- cmd/broker.go | 2 +- cmd/handler.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 7a6fab2c1..6d149bcc9 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -146,7 +146,7 @@ func init() { viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) brokerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from routers") - brokerCmd.Flags().Int("uplink-port", 1881, "The UDP port for the uplink") + brokerCmd.Flags().Int("uplink-port", 1881, "The port for the uplink") viper.BindPFlag("broker.uplink-bind-address", brokerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) diff --git a/cmd/handler.go b/cmd/handler.go index 47984bc51..e26d99d1a 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -196,7 +196,7 @@ func init() { viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) handlerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from brokers") - handlerCmd.Flags().Int("uplink-port", 1882, "The UDP port for the uplink") + handlerCmd.Flags().Int("uplink-port", 1882, "The port for the uplink") viper.BindPFlag("handler.uplink-bind-address", handlerCmd.Flags().Lookup("uplink-bind-address")) viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) From d0b82a62ff7e7c421a5396eb258acfaf9bb282a4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 14:09:08 +0100 Subject: [PATCH 0965/2266] [issue/#62] Try to broadcast if a send to a dedicated broker failed --- core/components/router/router.go | 43 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 24bdd81a6..549ba7643 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -59,40 +59,43 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { packet := itf.(RPacket) // Lookup for an existing broker - // NOTE We are still assuming only one broker associated to one device address. - // We should find a mechanism to make sure that the broker in database is really - // associated to the device to avoid trouble during overlaping. - // Keeping track of the last FCnt maybe ? Having an overlap on the frame counter + the - // device address might be less likely. - entry, err := r.Lookup(packet.DevEUI()) + entries, err := r.Lookup(packet.DevEUI()) if err != nil && err.(errors.Failure).Nature != errors.NotFound { r.ctx.Warn("Database lookup failed") return errors.New(errors.Operational, err) } - var recipient Recipient - if err == nil { - rawRecipient := entry.Recipient - if recipient, err = up.GetRecipient(rawRecipient); err != nil { - r.ctx.Warn("Unable to retrieve Recipient") - return errors.New(errors.Operational, err) - } - } - // TODO -> Add Gateway Metadata to packet - bpacket, err := NewBPacket(packet.Payload(), packet.Metadata()) if err != nil { r.ctx.WithError(err).Warn("Unable to create router packet") return errors.New(errors.Structural, err) } + // Send packet to broker(s) var response []byte - - if recipient == nil { + if err != nil { + // No Recipient available -> broadcast response, err = up.Send(bpacket) } else { - response, err = up.Send(bpacket, recipient) + // Recipients are available + var recipients []Recipient + for _, e := range entries { + // Get the actual broker + recipient, err := up.GetRecipient(e.Recipient) + if err != nil { + r.ctx.Warn("Unable to retrieve Recipient") + return err + } + recipients = append(recipients, recipient) + } + + // Send the packet + response, err = up.Send(bpacket, recipients...) + if err != nil && err.(errors.Failure).Nature == errors.NotFound { + // Might be a collision with the dev addr, we better broadcast + response, err = up.Send(bpacket) + } } if err != nil { @@ -125,8 +128,8 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { default: return errors.New(errors.Implementation, "Unexpected packet type") } - stats.MarkMeter("router.uplink.ok") + case SPacket: return errors.New(errors.Implementation, "Stats packet not yet implemented") case JPacket: From b458345a4d5a6545601909d0cda73f866d2d1cfc Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 14:45:02 +0100 Subject: [PATCH 0966/2266] [issue/#62] Adapt storage to handle several entries per devEUI --- core/components/router/storage.go | 61 ++++++++++++------------------- 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 2752977e3..b7e8df8a2 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -4,6 +4,7 @@ package router import ( + "fmt" "sync" "time" @@ -16,7 +17,7 @@ import ( // Storage gives a facade to manipulate the router database type Storage interface { - Lookup(devEUI lorawan.EUI64) (entry, error) + Lookup(devEUI lorawan.EUI64) ([]entry, error) Store(reg RRegistration) error Close() error } @@ -44,42 +45,36 @@ func NewStorage(name string, delay time.Duration) (Storage, error) { } // Lookup implements the router.Storage interface -func (s *storage) Lookup(devEUI lorawan.EUI64) (entry, error) { - return s.lookup(devEUI, true) -} - -// lookup offers an indirection in order to avoid taking a lock if not needed -func (s *storage) lookup(devEUI lorawan.EUI64, lock bool) (entry, error) { - // NOTE This works under the assumption that a read or write lock is already held by - // the callee (e.g. Store() - if lock { - s.Lock() - defer s.Unlock() - } - +func (s *storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { + s.Lock() + defer s.Unlock() itf, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) if err != nil { - return entry{}, err + return nil, err } entries := itf.([]entry) - if len(entries) != 1 { - if err := s.db.Flush(s.Name, devEUI[:]); err != nil { - return entry{}, errors.New(errors.Operational, err) + if s.ExpiryDelay != 0 { + var newEntries []dbutil.Entry + var filtered []entry + for _, e := range entries { + if e.until.After(time.Now()) { + newEntry := new(entry) + *newEntry = e + newEntries = append(newEntries, newEntry) + filtered = append(filtered, e) + } } - return entry{}, errors.New(errors.NotFound, "Not Found") - } - - e := entries[0] - - if s.ExpiryDelay != 0 && e.until.Before(time.Now()) { - if err := s.db.Flush(s.Name, devEUI[:]); err != nil { - return entry{}, errors.New(errors.Operational, err) + if err := s.db.Replace(s.Name, devEUI[:], newEntries); err != nil { + return nil, errors.New(errors.Operational, err) } - return entry{}, errors.New(errors.NotFound, "Not Found") + entries = filtered } - return e, nil + if len(entries) == 0 { + return nil, errors.New(errors.NotFound, fmt.Sprintf("No entry for: %v", devEUI[:])) + } + return entries, nil } // Store implements the router.Storage interface @@ -92,20 +87,10 @@ func (s *storage) Store(reg RRegistration) error { s.Lock() defer s.Unlock() - - _, err = s.lookup(devEUI, false) - if err == nil { - return errors.New(errors.Structural, "Already exists") - } - if err.(errors.Failure).Nature != errors.NotFound { - return err - } - return s.db.Store(s.Name, devEUI[:], []dbutil.Entry{&entry{ Recipient: recipient, until: time.Now().Add(s.ExpiryDelay), }}) - } // Close implements the router.Storage interface From e6b5e291912cc977db4934a7a47f835d895b1f1f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 14:45:21 +0100 Subject: [PATCH 0967/2266] [issue/#62] Rewrite tests to take several entries --- core/components/router/helpers_test.go | 17 +++++---- core/components/router/mock_test.go | 14 ++++---- core/components/router/router.go | 2 +- core/components/router/router_test.go | 18 ++++++---- core/components/router/storage_test.go | 49 ++++++++------------------ 5 files changed, 45 insertions(+), 55 deletions(-) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 2b1970f0d..b0e0437eb 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -59,13 +59,18 @@ func newBPacket(rawDevAddr [4]byte, payload string) BPacket { } // ----- CHECK utilities -func CheckEntries(t *testing.T, want entry, got entry) { - tmin := want.until.Add(-time.Second) - tmax := want.until.Add(time.Second) - if !tmin.Before(got.until) || !got.until.Before(tmax) { - Ko(t, "Unexpected expiry time.\nWant: %s\nGot: %s", want.until, got.until) +func CheckEntries(t *testing.T, want []entry, got []entry) { + for i, w := range want { + if i >= len(got) { + Ko(t, "Didn't got enough entries: %v", got) + } + tmin := w.until.Add(-time.Second) + tmax := w.until.Add(time.Second) + if !tmin.Before(got[i].until) || !got[i].until.Before(tmax) { + Ko(t, "Unexpected expiry time.\nWant: %s\nGot: %s", w.until, got[i].until) + } + Check(t, w.Recipient, got[i].Recipient, "Recipients") } - Check(t, want.Recipient, got.Recipient, "Recipients") } func CheckRegistrations(t *testing.T, want Registration, got Registration) { diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go index 817c62aa5..ea15294a8 100644 --- a/core/components/router/mock_test.go +++ b/core/components/router/mock_test.go @@ -14,24 +14,26 @@ import ( type mockStorage struct { Failures map[string]error InLookup lorawan.EUI64 - OutLookup entry + OutLookup []entry InStore RRegistration } func newMockStorage() *mockStorage { return &mockStorage{ Failures: make(map[string]error), - OutLookup: entry{ - Recipient: []byte("MockStorageRecipient"), - until: time.Date(2016, 2, 3, 14, 16, 22, 0, time.UTC), + OutLookup: []entry{ + { + Recipient: []byte("MockStorageRecipient"), + until: time.Date(2016, 2, 3, 14, 16, 22, 0, time.UTC), + }, }, } } -func (s *mockStorage) Lookup(devEUI lorawan.EUI64) (entry, error) { +func (s *mockStorage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { s.InLookup = devEUI if s.Failures["Lookup"] != nil { - return entry{}, s.Failures["Lookup"] + return nil, s.Failures["Lookup"] } return s.OutLookup, nil } diff --git a/core/components/router/router.go b/core/components/router/router.go index 549ba7643..f83e8feac 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -85,7 +85,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { recipient, err := up.GetRecipient(e.Recipient) if err != nil { r.ctx.Warn("Unable to retrieve Recipient") - return err + return errors.New(errors.Structural, err) } recipients = append(recipients, recipient) } diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index ba8c1f141..2722cdea7 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -205,9 +205,11 @@ func TestHandleUp(t *testing.T) { recipient := NewMockRecipient() dataRecipient, _ := recipient.MarshalBinary() store := newMockStorage() - store.OutLookup = entry{ - Recipient: dataRecipient, - until: time.Now().Add(time.Hour), + store.OutLookup = []entry{ + { + Recipient: dataRecipient, + until: time.Now().Add(time.Hour), + }, } data, err := newRPacket( [4]byte{2, 3, 2, 3}, @@ -240,9 +242,11 @@ func TestHandleUp(t *testing.T) { recipient := NewMockRecipient() dataRecipient, _ := recipient.MarshalBinary() store := newMockStorage() - store.OutLookup = entry{ - Recipient: dataRecipient, - until: time.Now().Add(time.Hour), + store.OutLookup = []entry{ + { + Recipient: dataRecipient, + until: time.Now().Add(time.Hour), + }, } data, err := newRPacket( [4]byte{2, 3, 2, 3}, @@ -255,7 +259,7 @@ func TestHandleUp(t *testing.T) { err = router.HandleUp(data, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckAcks(t, false, an.InAck) CheckRegistrations(t, nil, store.InStore) CheckSent(t, nil, adapter.InSendPacket) diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index adeff64d3..cc57f6f56 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -50,9 +50,11 @@ func TestStoreAndLookup(t *testing.T) { gotEntry, err := db.Lookup(r.DevEUI()) // Expectations - wantEntry := entry{ - Recipient: r.RawRecipient(), - until: time.Now().Add(time.Hour), + wantEntry := []entry{ + { + Recipient: r.RawRecipient(), + until: time.Now().Add(time.Hour), + }, } // Check @@ -63,31 +65,6 @@ func TestStoreAndLookup(t *testing.T) { // ------------------ - { - Desc(t, "Store an existing entry") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - r := NewMockRRegistration() - - // Operate - err := db.Store(r) - gotEntry, _ := db.Lookup(r.DevEUI()) - - // Expectations - wantEntry := entry{ - Recipient: r.RawRecipient(), - until: time.Now().Add(time.Hour), - } - - // Checks - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckEntries(t, wantEntry, gotEntry) - _ = db.Close() - } - - // ------------------ - { Desc(t, "Lookup non-existing entry") @@ -101,7 +78,7 @@ func TestStoreAndLookup(t *testing.T) { // Checks CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, entry{}, gotEntry) + CheckEntries(t, nil, gotEntry) _ = db.Close() } @@ -122,7 +99,7 @@ func TestStoreAndLookup(t *testing.T) { // Checks CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, entry{}, gotEntry) + CheckEntries(t, nil, gotEntry) _ = db.Close() } @@ -144,9 +121,11 @@ func TestStoreAndLookup(t *testing.T) { gotEntry, err := db.Lookup(r.DevEUI()) // Expectations - wantEntry := entry{ - Recipient: r.RawRecipient(), - until: time.Now().Add(time.Millisecond * 200), + wantEntry := []entry{ + { + Recipient: r.RawRecipient(), + until: time.Now().Add(time.Millisecond * 200), + }, } // Checks @@ -188,7 +167,7 @@ func TestStoreAndLookup(t *testing.T) { // Checks CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckEntries(t, entry{}, gotEntry) + CheckEntries(t, nil, gotEntry) } // ------------------ @@ -207,7 +186,7 @@ func TestStoreAndLookup(t *testing.T) { CheckErrors(t, pointer.String(string(errors.Structural)), err) gotEntry, err := db.Lookup(r.DevEUI()) CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, entry{}, gotEntry) + CheckEntries(t, nil, gotEntry) _ = db.Close() } From bcc1bf51cb96c6a411be2246b07c7ce79aa8d83f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 15:25:29 +0100 Subject: [PATCH 0968/2266] [issue/#62] Add some more tests to ensure new router behaviours --- core/components/router/mock_test.go | 69 ++++++++++++++++++++++ core/components/router/router_test.go | 81 ++++++++++++++++++++++++++ core/components/router/storage_test.go | 38 ++++++++++++ 3 files changed, 188 insertions(+) diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go index ea15294a8..dbcfa1ed6 100644 --- a/core/components/router/mock_test.go +++ b/core/components/router/mock_test.go @@ -7,6 +7,7 @@ import ( "time" . "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/brocaar/lorawan" ) @@ -46,3 +47,71 @@ func (s *mockStorage) Store(reg RRegistration) error { func (s *mockStorage) Close() error { return s.Failures["Close"] } + +// MockRouterAdapter extends functionality of the mocks.MockAdapter. +// +// A list of failures can be defined to handle successive call to a method (at each call, an error +// get out from the list) +type mockRouterAdapter struct { + Failures map[string][]error + InSendPacket Packet + InSendRecipients []Recipient + InGetRecipient []byte + OutSend []byte + OutGetRecipient Recipient + OutNextPacket []byte + OutNextAckNacker AckNacker + OutNextRegReg Registration + OutNextRegAckNacker AckNacker +} + +func newMockRouterAdapter() *mockRouterAdapter { + return &mockRouterAdapter{ + Failures: make(map[string][]error), + OutSend: []byte("MockAdapterSend"), + OutGetRecipient: NewMockRecipient(), + OutNextPacket: []byte("MockAdapterNextPacket"), + OutNextAckNacker: NewMockAckNacker(), + OutNextRegReg: NewMockHRegistration(), + OutNextRegAckNacker: NewMockAckNacker(), + } +} + +func (a *mockRouterAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { + a.InSendPacket = p + a.InSendRecipients = r + if len(a.Failures["Send"]) > 0 { + err := a.Failures["Send"][0] + a.Failures["Send"] = a.Failures["Send"][1:] + return nil, err + } + return a.OutSend, nil +} + +func (a *mockRouterAdapter) GetRecipient(raw []byte) (Recipient, error) { + a.InGetRecipient = raw + if len(a.Failures["GetRecipient"]) > 0 { + err := a.Failures["GetRecipient"][0] + a.Failures["GetRecipient"] = a.Failures["GetRecipient"][1:] + return nil, err + } + return a.OutGetRecipient, nil +} + +func (a *mockRouterAdapter) Next() ([]byte, AckNacker, error) { + if len(a.Failures["Next"]) > 0 { + err := a.Failures["Next"][0] + a.Failures["Next"] = a.Failures["Next"][1:] + return nil, nil, err + } + return a.OutNextPacket, a.OutNextAckNacker, nil +} + +func (a *mockRouterAdapter) NextRegistration() (Registration, AckNacker, error) { + if len(a.Failures["NextRegistration"]) > 0 { + err := a.Failures["NextRegistration"][0] + a.Failures["NextRegistration"] = a.Failures["NextRegistration"][1:] + return nil, nil, err + } + return a.OutNextRegReg, a.OutNextRegAckNacker, nil +} diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 2722cdea7..1fdccb0d4 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -325,4 +325,85 @@ func TestHandleUp(t *testing.T) { CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) } + + // ------------------- + + { + Desc(t, "Send a known packet, get not found, and broadcast") + + // Build + an := NewMockAckNacker() + adapter := newMockRouterAdapter() + adapter.OutSend = nil + adapter.Failures["Send"] = []error{ + errors.New(errors.NotFound, "Mock Error"), + } + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + store := newMockStorage() + store.OutLookup = []entry{ + { + Recipient: dataRecipient, + until: time.Now().Add(time.Hour), + }, + } + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } + + // ------------------- + + { + Desc(t, "Send a known packet, get not found, and broadcast, still not found") + + // Build + an := NewMockAckNacker() + adapter := newMockRouterAdapter() + adapter.OutSend = nil + adapter.Failures["Send"] = []error{ + errors.New(errors.NotFound, "Mock Error"), + errors.New(errors.NotFound, "Mock Error"), + } + recipient := NewMockRecipient() + dataRecipient, _ := recipient.MarshalBinary() + store := newMockStorage() + store.OutLookup = []entry{ + { + Recipient: dataRecipient, + until: time.Now().Add(time.Hour), + }, + } + data, err := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + ).MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + + // Operate + router := New(store, GetLogger(t, "Router")) + err = router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.NotFound)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + } } diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index cc57f6f56..12440e694 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -190,4 +190,42 @@ func TestStoreAndLookup(t *testing.T) { _ = db.Close() } + + // ------------------ + + { + Desc(t, "Store two entries in a row") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + r1 := NewMockRRegistration() + r1.OutDevEUI[3] = 42 + r2 := NewMockRRegistration() + r2.OutDevEUI[3] = 42 + r2.OutRecipient.(*MockRecipient).OutMarshalBinary = []byte("Second recipient") + + // Operate + err := db.Store(r1) + CheckErrors(t, nil, err) + err = db.Store(r2) + CheckErrors(t, nil, err) + gotEntries, err := db.Lookup(r1.DevEUI()) + CheckErrors(t, nil, err) + + // Expectations + wantEntries := []entry{ + { + Recipient: r1.RawRecipient(), + until: time.Now().Add(time.Hour), + }, + { + Recipient: r2.RawRecipient(), + until: time.Now().Add(time.Hour), + }, + } + + // Check + CheckEntries(t, wantEntries, gotEntries) + _ = db.Close() + } } From 1ebe37cba27d808ded3a13ee4c8952a3fad2013d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 15:25:52 +0100 Subject: [PATCH 0969/2266] [issue/#62] Fix broadcast not being done in router --- core/components/router/router.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index f83e8feac..aaf0679b3 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -64,6 +64,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { r.ctx.Warn("Database lookup failed") return errors.New(errors.Operational, err) } + shouldBroadcast := err != nil // TODO -> Add Gateway Metadata to packet bpacket, err := NewBPacket(packet.Payload(), packet.Metadata()) @@ -74,7 +75,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Send packet to broker(s) var response []byte - if err != nil { + if shouldBroadcast { // No Recipient available -> broadcast response, err = up.Send(bpacket) } else { From b9cc9379775e93ebf36cee3d5f577153bc1b67c7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 16:49:25 +0100 Subject: [PATCH 0970/2266] [fix/dutyManager] Count preamble in the time on air --- core/dutycycle/dutyManager.go | 26 +++++++++++++++++--------- core/dutycycle/dutyManager_test.go | 8 ++++---- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 9baf36960..41beefde6 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -183,16 +183,16 @@ func (m *dutyManager) Close() error { // identifier. func computeTOA(size uint, datr string, codr string) (time.Duration, error) { // Ensure the datr and codr are correct - var cr float64 + var rc float64 switch codr { case "4/5": - cr = 4.0 / 5.0 + rc = 4.0 / 5.0 case "4/6": - cr = 4.0 / 6.0 + rc = 4.0 / 6.0 case "4/7": - cr = 4.0 / 7.0 + rc = 4.0 / 7.0 case "4/8": - cr = 4.0 / 8.0 + rc = 4.0 / 8.0 default: return 0, errors.New(errors.Structural, "Invalid Codr") } @@ -204,12 +204,20 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { return 0, errors.New(errors.Structural, "Invalid Datr") } - // Compute bitrate, Page 10: http://www.semtech.com/images/datasheet/an1200.22.pdf + // Additional variables needed to compute times on air + s := float64(size) sf, _ := strconv.ParseFloat(matches[1], 64) - bw, _ := strconv.ParseUint(matches[2], 10, 64) - bitrate := sf * cr * float64(bw) / math.Pow(2, sf) + bw, _ := strconv.ParseFloat(matches[2], 64) + var de float64 + if bw == 125 && (sf == 11 || sf == 12) { + de = 1.0 + } + + // Compute toa, Page 7: http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf + payloadNb := 8.0 + math.Max(0, 4*math.Ceil((2*s-sf-6)/(sf-2*de))/rc) + timeOnAir := (payloadNb + 12.25) * math.Pow(2, sf) / bw // in ms - return time.ParseDuration(fmt.Sprintf("%fms", float64(size*8)/bitrate)) + return time.ParseDuration(fmt.Sprintf("%fms", timeOnAir)) } type dutyEntry struct { diff --git a/core/dutycycle/dutyManager_test.go b/core/dutycycle/dutyManager_test.go index e661e766b..07a8fbba6 100644 --- a/core/dutycycle/dutyManager_test.go +++ b/core/dutycycle/dutyManager_test.go @@ -134,7 +134,7 @@ func TestUpdateAndLookup(t *testing.T) { // Expectation want := map[subBand]uint{ - EuropeRX1_A: 5, + EuropeRX1_A: 10, } // Check @@ -161,8 +161,8 @@ func TestUpdateAndLookup(t *testing.T) { // Expectation want := map[subBand]uint{ - EuropeRX1_A: 37, - EuropeRX1_B: 21, + EuropeRX1_A: 51, + EuropeRX1_B: 25, } // Check @@ -188,7 +188,7 @@ func TestUpdateAndLookup(t *testing.T) { // Expectation want := map[subBand]uint{ - EuropeRX1_A: 7645, + EuropeRX1_A: 9871, } // Check From 0edc6a22467403053c85e82933525ab8fa306cd8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 17:03:05 +0100 Subject: [PATCH 0971/2266] [fix/dutyManager] Introduce right europe sub bands --- core/dutycycle/dutyManager.go | 45 ++++++++++++++++++++---------- core/dutycycle/dutyManager_test.go | 20 ++++++------- 2 files changed, 40 insertions(+), 25 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 41beefde6..36c393907 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -33,9 +33,11 @@ type dutyManager struct { // Available sub-bands const ( - EuropeRX1_A subBand = "EURX1A" - EuropeRX1_B subBand = "EURX1B" - EuropeRX2 subBand = "EURX2" + EuropeG subBand = "europe g" + EuropeG1 = "europe g1" + EuropeG2 = "europe g2" + EuropeG3 = "europe g3" + EuropeG4 = "europe g4" ) type subBand string @@ -51,20 +53,31 @@ type region byte // GetSubBand returns the subband associated to a given frequency func GetSubBand(freq float64) (subBand, error) { - // EuropeRX1_A -> 868.1 MHz -> 868.9 MHz - if int(freq) == 868 { - return EuropeRX1_A, nil + // g 865.0 – 868.0 MHz 1% or LBT+AFA, 25 mW (=14dBm) + if freq >= 865.0 && freq < 868.0 { + return EuropeG, nil } - // EuropeRX1_B -> 867.1 MHz -> 867.9 MHz - if int(freq) == 867 { - return EuropeRX1_B, nil + // g1 868.0 – 868.6 MHz 1% or LBT+AFA, 25 mW + if freq >= 868.0 && freq < 868.6 { + return EuropeG1, nil } - // EuropeRX2 -> 869.5 MHz - if math.Floor(freq*10.0) == 8695.0 { - return EuropeRX2, nil + // g2 868.7 – 869.2 MHz 0.1% or LBT+AFA, 25 mW + if freq >= 868.7 && freq < 869.2 { + return EuropeG2, nil } + + // g3 869.4 – 869.65 MHz 10% or LBT+AFA, 500 mW (=27dBm) + if freq >= 869.4 && freq < 869.65 { + return EuropeG3, nil + } + + // g4 869.7 – 870.0 MHz 1% or LBT+AFA, 25 mW + if freq >= 869.7 && freq < 870 { + return EuropeG4, nil + } + return "", errors.New(errors.Structural, "Unknown frequency") } @@ -74,9 +87,11 @@ func NewManager(filepath string, cycleLength time.Duration, r region) (DutyManag switch r { case Europe: maxDuty = map[subBand]float64{ - EuropeRX1_A: 0.01, // 1% dutycycle - EuropeRX1_B: 0.01, // 1% dutycycle - EuropeRX2: 0.1, // 10% dutycycle + EuropeG: 0.01, + EuropeG1: 0.01, + EuropeG2: 0.001, + EuropeG3: 0.1, + EuropeG4: 0.01, } default: return nil, errors.New(errors.Implementation, "Region not supported") diff --git a/core/dutycycle/dutyManager_test.go b/core/dutycycle/dutyManager_test.go index 07a8fbba6..7f91146eb 100644 --- a/core/dutycycle/dutyManager_test.go +++ b/core/dutycycle/dutyManager_test.go @@ -19,24 +19,24 @@ var dutyManagerDB = path.Join(os.TempDir(), "TestDutyCycleStorage.db") func TestGetSubBand(t *testing.T) { { - Desc(t, "Test EuropeRX1_A") + Desc(t, "Test EuropeG1") sb, err := GetSubBand(868.38) errutil.CheckErrors(t, nil, err) - CheckSubBands(t, EuropeRX1_A, sb) + CheckSubBands(t, EuropeG1, sb) } { - Desc(t, "Test EuropeRX1_B") + Desc(t, "Test EuropeG") sb, err := GetSubBand(867.127) errutil.CheckErrors(t, nil, err) - CheckSubBands(t, EuropeRX1_B, sb) + CheckSubBands(t, EuropeG, sb) } { - Desc(t, "Test EuropeRX2") + Desc(t, "Test EuropeG3") sb, err := GetSubBand(869.567) errutil.CheckErrors(t, nil, err) - CheckSubBands(t, EuropeRX2, sb) + CheckSubBands(t, EuropeG3, sb) } { @@ -134,7 +134,7 @@ func TestUpdateAndLookup(t *testing.T) { // Expectation want := map[subBand]uint{ - EuropeRX1_A: 10, + EuropeG1: 10, } // Check @@ -161,8 +161,8 @@ func TestUpdateAndLookup(t *testing.T) { // Expectation want := map[subBand]uint{ - EuropeRX1_A: 51, - EuropeRX1_B: 25, + EuropeG1: 51, + EuropeG: 25, } // Check @@ -188,7 +188,7 @@ func TestUpdateAndLookup(t *testing.T) { // Expectation want := map[subBand]uint{ - EuropeRX1_A: 9871, + EuropeG1: 9871, } // Check From e890faac9c2093921d21d2ff34e33e21e68993c0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 17:49:15 +0100 Subject: [PATCH 0972/2266] [feature/downlink] Properly add duty cycles to metadata + add tests to check whether (un)marshal is done ok --- core/dutycycle/dutyManager.go | 6 +++-- core/metadata.go | 41 ++++++++++++++++++----------------- core/metadata_test.go | 18 +++++++++++++++ 3 files changed, 43 insertions(+), 22 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 36c393907..005a8b585 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -19,10 +19,12 @@ import ( // DutyManager provides an interface to manipulate and compute gateways duty-cycles. type DutyManager interface { Update(id []byte, freq float64, size uint, datr string, codr string) error - Lookup(id []byte) (map[subBand]uint, error) + Lookup(id []byte) (Cycles, error) Close() error } +type Cycles map[subBand]uint + type dutyManager struct { sync.RWMutex db dbutil.Interface @@ -164,7 +166,7 @@ func (m *dutyManager) Update(id []byte, freq float64, size uint, datr string, co // // The usage is an integer between 0 and 100 (maybe above 100 if the usage exceed the limitation). // The closest to 0, the more usage we have -func (m *dutyManager) Lookup(id []byte) (map[subBand]uint, error) { +func (m *dutyManager) Lookup(id []byte) (Cycles, error) { m.RLock() defer m.RUnlock() diff --git a/core/metadata.go b/core/metadata.go index 2eaf927b8..b2bd4a616 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -7,6 +7,7 @@ import ( "encoding/json" "time" + "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -15,26 +16,26 @@ import ( // Metadata are carried by any type of packets. They constitute additional informations on the // packet itself or, about its context (gateway, duty cycle, etc ...) type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Duty *uint `json:"duty,omitempty"` // DutyCycle of the gateway (uint between 0 and 100) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Duty *dutycycle.Cycles `json:"duty,omitempty"` // DutyCycles of the gateway (uint between 0 and 100) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } // TODO -> Metadata should implements a less byte-consuming MarshalBinary method MarshalJSON() diff --git a/core/metadata_test.go b/core/metadata_test.go index 0962af4dc..698cf438d 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -5,9 +5,11 @@ package core import ( "encoding/json" + "fmt" "testing" "time" + "github.com/TheThingsNetwork/ttn/core/dutycycle" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -59,6 +61,11 @@ var commonTests = []struct { nil, `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, }, + { // Duty + Metadata{Duty: genCycles()}, + nil, + fmt.Sprintf(`{"duty":%s}`, genCyclesPayload()), + }, } var unmarshalTests = []struct { @@ -97,3 +104,14 @@ func TestUnmarshalJSON(t *testing.T) { checkMetadata(t, test.Metadata, metadata) } } + +func genCycles() *dutycycle.Cycles { + cycles := make(dutycycle.Cycles) + cycles[dutycycle.EuropeG1] = 44 + cycles[dutycycle.EuropeG3] = 12 + return &cycles +} + +func genCyclesPayload() string { + return fmt.Sprintf(`{"%s":44,"%s":12}`, dutycycle.EuropeG1, dutycycle.EuropeG3) +} From 7ad6558ee8f113b8cd9efb0c2e39f20557740754 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 18:04:55 +0100 Subject: [PATCH 0973/2266] [feature/downlink] Take care of duty cycles of gateway in router --- core/components/router/router.go | 39 ++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index aaf0679b3..0cf816816 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -5,6 +5,7 @@ package router import ( . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" @@ -12,12 +13,13 @@ import ( type component struct { Storage - ctx log.Interface + Manager dutycycle.DutyManager + ctx log.Interface } // New constructs a new router -func New(db Storage, ctx log.Interface) Router { - return component{Storage: db, ctx: ctx} +func New(db Storage, dm dutycycle.DutyManager, ctx log.Interface) Router { + return component{Storage: db, Manager: dm, ctx: ctx} } // Register implements the core.Router interface @@ -66,7 +68,16 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } shouldBroadcast := err != nil - // TODO -> Add Gateway Metadata to packet + // Add Gateway duty metadata + // TODO add gateway location + metadata := packet.Metadata() + cycles, err := r.Manager.Lookup(packet.GatewayID()) + if err != nil { + r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") + } else { + metadata.Duty = &cycles + } + bpacket, err := NewBPacket(packet.Payload(), packet.Metadata()) if err != nil { r.ctx.WithError(err).Warn("Unable to create router packet") @@ -125,6 +136,26 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { switch itf.(type) { case RPacket: + // Update downlink metadata for the related gateway + metadata := itf.(RPacket).Metadata() + freq := metadata.Freq + datr := metadata.Datr + codr := metadata.Codr + size := metadata.Size + + if freq == nil || datr == nil || codr == nil || size == nil { + err := errors.New(errors.Operational, "Missing mandatory metadata in response") + stats.MarkMeter("router.uplink.bad_broker_response") + r.ctx.WithError(err).Warn("Invalid response from Broker") + return err + } + + if err := r.Manager.Update(packet.GatewayID(), *freq, *size, *datr, *codr); err != nil { + r.ctx.WithError(err).Warn("Unable to update duty cycle") + return errors.New(errors.Operational, err) + } + + // Finally, define the ack to be sent ack = itf.(RPacket) default: return errors.New(errors.Implementation, "Unexpected packet type") From 3ff83e18423e3cb28ab787467e6e4371eeb020a2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 18:15:07 +0100 Subject: [PATCH 0974/2266] [feature/downlink] Create a mock interface for dutyManager --- core/components/router/mock_test.go | 41 +++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go index dbcfa1ed6..2bab78ec3 100644 --- a/core/components/router/mock_test.go +++ b/core/components/router/mock_test.go @@ -7,6 +7,7 @@ import ( "time" . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/dutycycle" . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/brocaar/lorawan" ) @@ -48,6 +49,46 @@ func (s *mockStorage) Close() error { return s.Failures["Close"] } +// mockDutyManager implements the dutycycle.DutyManager interface +type mockDutyManager struct { + Failures map[string]error + InUpdateId []byte + InUpdateFreq float64 + InUpdateSize uint + InUpdateDatr string + InUpdateCodr string + InLookupId []byte + OutLookup dutycycle.Cycles +} + +func newMockDutyManager() *mockDutyManager { + return &mockDutyManager{ + Failures: make(map[string]error), + OutLookup: make(dutycycle.Cycles), + } +} + +func (m *mockDutyManager) Update(id []byte, freq float64, size uint, datr string, codr string) error { + m.InUpdateId = id + m.InUpdateFreq = freq + m.InUpdateSize = size + m.InUpdateDatr = datr + m.InUpdateCodr = codr + return m.Failures["Update"] +} + +func (m *mockDutyManager) Lookup(id []byte) (dutycycle.Cycles, error) { + m.InLookupId = id + if m.Failures["Lookup"] != nil { + return nil, m.Failures["Lookup"] + } + return m.OutLookup, nil +} + +func (m *mockDutyManager) Close() error { + return m.Failures["Close"] +} + // MockRouterAdapter extends functionality of the mocks.MockAdapter. // // A list of failures can be defined to handle successive call to a method (at each call, an error From a55747b8fad1bbc526d230f684abd801943a6853 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 18:30:26 +0100 Subject: [PATCH 0975/2266] [feature/downlink] Rewrite tests to accept DutyManager in the router --- core/components/router/helpers_test.go | 8 +- core/components/router/router_test.go | 133 +++++++++++++++++-------- 2 files changed, 99 insertions(+), 42 deletions(-) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index b0e0437eb..75a9e74ce 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -14,7 +14,7 @@ import ( ) // ----- BUILD utilities -func newRPacket(rawDevAddr [4]byte, payload string, gatewayID []byte) RPacket { +func newRPacket(rawDevAddr [4]byte, payload string, gatewayID []byte, metadata Metadata) RPacket { var devAddr lorawan.DevAddr copy(devAddr[:], rawDevAddr[:]) @@ -29,7 +29,7 @@ func newRPacket(rawDevAddr [4]byte, payload string, gatewayID []byte) RPacket { Major: lorawan.LoRaWANR1, } - packet, err := NewRPacket(phyPayload, gatewayID, Metadata{}) + packet, err := NewRPacket(phyPayload, gatewayID, metadata) if err != nil { panic(err) } @@ -76,3 +76,7 @@ func CheckEntries(t *testing.T, want []entry, got []entry) { func CheckRegistrations(t *testing.T, want Registration, got Registration) { Check(t, want, got, "Registrations") } + +func CheckIDs(t *testing.T, want []byte, got []byte) { + Check(t, want, got, "IDs") +} diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 1fdccb0d4..0f851d5f7 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -23,9 +23,10 @@ func TestRegister(t *testing.T) { an := NewMockAckNacker() store := newMockStorage() r := NewMockRRegistration() + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) + router := New(store, m, GetLogger(t, "Router")) err := router.Register(r, an) // Check @@ -43,9 +44,10 @@ func TestRegister(t *testing.T) { an := NewMockAckNacker() store := newMockStorage() r := NewMockARegistration() + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) + router := New(store, m, GetLogger(t, "Router")) err := router.Register(r, an) // Check @@ -64,9 +66,10 @@ func TestRegister(t *testing.T) { store := newMockStorage() store.Failures["Store"] = errors.New(errors.Structural, "Mock Error: Store Failed") r := NewMockRRegistration() + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) + router := New(store, m, GetLogger(t, "Router")) err := router.Register(r, an) // Check @@ -86,16 +89,19 @@ func TestHandleUp(t *testing.T) { adapter.OutSend = nil store := newMockStorage() store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, nil, err) @@ -103,6 +109,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -116,22 +124,31 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Response", []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{ + Freq: pointer.Float64(868.42), + Size: pointer.Uint(14), + Codr: pointer.String("4/5"), + Datr: pointer.String("SF8BW125"), + }, ) dataResp, _ := resp.MarshalBinary() adapter := NewMockAdapter() adapter.OutSend = dataResp store := newMockStorage() store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, nil, err) @@ -139,6 +156,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, inPacket.GatewayID(), m.InUpdateId) } // ------------------- @@ -151,9 +170,10 @@ func TestHandleUp(t *testing.T) { adapter := NewMockAdapter() store := newMockStorage() store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) + router := New(store, m, GetLogger(t, "Router")) err := router.HandleUp([]byte{1, 2, 3}, an, adapter) // Check @@ -162,6 +182,7 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, nil, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -175,15 +196,18 @@ func TestHandleUp(t *testing.T) { adapter.OutSend = nil store := newMockStorage() store.Failures["Lookup"] = errors.New(errors.Operational, "Mock Error: Lookup failed") - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) @@ -191,6 +215,7 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, nil, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -211,16 +236,19 @@ func TestHandleUp(t *testing.T) { until: time.Now().Add(time.Hour), }, } - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, nil, err) @@ -228,6 +256,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -248,15 +278,18 @@ func TestHandleUp(t *testing.T) { until: time.Now().Add(time.Hour), }, } - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, pointer.String(string(errors.Structural)), err) @@ -264,6 +297,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, nil, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -277,16 +312,19 @@ func TestHandleUp(t *testing.T) { adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") store := newMockStorage() store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not found") - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) @@ -294,6 +332,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -307,16 +347,19 @@ func TestHandleUp(t *testing.T) { adapter.OutSend = []byte{1, 2, 3} store := newMockStorage() store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not found") - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) @@ -324,6 +367,8 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) } // ------------------- @@ -347,16 +392,19 @@ func TestHandleUp(t *testing.T) { until: time.Now().Add(time.Hour), }, } - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, nil, err) @@ -388,16 +436,19 @@ func TestHandleUp(t *testing.T) { until: time.Now().Add(time.Hour), }, } - data, err := newRPacket( + inPacket := newRPacket( [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - ).MarshalBinary() + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() // Operate - router := New(store, GetLogger(t, "Router")) - err = router.HandleUp(data, an, adapter) + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) // Check CheckErrors(t, pointer.String(string(errors.NotFound)), err) @@ -405,5 +456,7 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStore) CheckSent(t, bpacket, adapter.InSendPacket) CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) } } From c1900ee4c256bd7900c34b92d7d241a813fe1fdb Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 18:37:23 +0100 Subject: [PATCH 0976/2266] [feature/downlink] Add tests to verify new router features --- core/components/router/router_test.go | 130 ++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 0f851d5f7..c3c9f07a5 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -459,4 +459,134 @@ func TestHandleUp(t *testing.T) { CheckIDs(t, inPacket.GatewayID(), m.InLookupId) CheckIDs(t, nil, m.InUpdateId) } + + // ------------------- + + { + Desc(t, "Send an unknown packet | No Downlink | Fail to lookup gateway") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutSend = nil + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") + inPacket := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() + m.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) + } + + // ------------------- + + { + Desc(t, "Send an unknown packet | Missing Metadata in downlink") + + // Build + an := NewMockAckNacker() + resp := newRPacket( + [4]byte{2, 3, 2, 3}, + "Response", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{ + Size: pointer.Uint(14), + Codr: pointer.String("4/5"), + Datr: pointer.String("SF8BW125"), + }, + ) + dataResp, _ := resp.MarshalBinary() + adapter := NewMockAdapter() + adapter.OutSend = dataResp + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") + inPacket := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) + } + + // ------------------- + + { + Desc(t, "Send an unknown packet | Fail to update metadata") + + // Build + an := NewMockAckNacker() + resp := newRPacket( + [4]byte{2, 3, 2, 3}, + "Response", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{ + Freq: pointer.Float64(868.14), + Size: pointer.Uint(14), + Codr: pointer.String("4/5"), + Datr: pointer.String("SF8BW125"), + }, + ) + dataResp, _ := resp.MarshalBinary() + adapter := NewMockAdapter() + adapter.OutSend = dataResp + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") + inPacket := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() + bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + m := newMockDutyManager() + m.Failures["Update"] = errors.New(errors.Operational, "Mock Error: Update") + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, bpacket, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, inPacket.GatewayID(), m.InUpdateId) + } } From 164e19c5f50178b3169ea31304607a3da5dfc685 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 19:55:19 +0100 Subject: [PATCH 0977/2266] [feature/downlink] Add method to compute a duty State from a duty value --- core/dutycycle/dutyManager.go | 50 ++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 005a8b585..a8d15e6a0 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -44,6 +44,15 @@ const ( type subBand string +type State uint + +const ( + StateHighlyAvailable State = iota + StateAvailable + StateWarning + StateBlocked +) + // Available regions for LoRaWAN const ( Europe region = iota @@ -214,17 +223,13 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { return 0, errors.New(errors.Structural, "Invalid Codr") } - re := regexp.MustCompile("^SF(7|8|9|10|11|12)BW(125|250|500)$") - matches := re.FindStringSubmatch(datr) - - if len(matches) != 3 { - return 0, errors.New(errors.Structural, "Invalid Datr") + sf, bw, err := parseDatr(datr) + if err != nil { + return 0, err } // Additional variables needed to compute times on air s := float64(size) - sf, _ := strconv.ParseFloat(matches[1], 64) - bw, _ := strconv.ParseFloat(matches[2], 64) var de float64 if bw == 125 && (sf == 11 || sf == 12) { de = 1.0 @@ -237,6 +242,37 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { return time.ParseDuration(fmt.Sprintf("%fms", timeOnAir)) } +func parseDatr(datr string) (float64, float64, error) { + re := regexp.MustCompile("^SF(7|8|9|10|11|12)BW(125|250|500)$") + matches := re.FindStringSubmatch(datr) + + if len(matches) != 3 { + return 0, 0, errors.New(errors.Structural, "Invalid Datr") + } + + sf, _ := strconv.ParseFloat(matches[1], 64) + bw, _ := strconv.ParseFloat(matches[2], 64) + + return sf, bw, nil +} + +func StateFromDuty(duty uint) State { + if duty >= 100 { + return StateBlocked + } + + if duty > 85 { + return StateWarning + } + + if duty > 30 { + return StateAvailable + } + + return StateHighlyAvailable + +} + type dutyEntry struct { Until time.Time `json:"until"` OnAir map[subBand]time.Duration `json:"on_air"` From f48c029f8d3f0d042108bb09f8108557e6fa8a98 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 19:55:46 +0100 Subject: [PATCH 0978/2266] [feature/downlink] Use of 2 dutyRX metadata for each window. --- core/components/router/router.go | 12 ++++++++- core/metadata.go | 42 ++++++++++++++++---------------- core/metadata_test.go | 18 -------------- 3 files changed, 32 insertions(+), 40 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 0cf816816..0d99de377 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -71,11 +71,21 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Add Gateway duty metadata // TODO add gateway location metadata := packet.Metadata() + if metadata.Freq == nil { + return errors.New(errors.Structural, "Missing mandatory frequency in metadata") + } + cycles, err := r.Manager.Lookup(packet.GatewayID()) if err != nil { r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") } else { - metadata.Duty = &cycles + sb1, err := dutycycle.GetSubBand(*metadata.Freq) + if err != nil { + return errors.New(errors.Structural, "Unhandled uplink signal frequency") + } + + rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) + metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 } bpacket, err := NewBPacket(packet.Payload(), packet.Metadata()) diff --git a/core/metadata.go b/core/metadata.go index b2bd4a616..2f483eca3 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -7,7 +7,6 @@ import ( "encoding/json" "time" - "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -16,26 +15,27 @@ import ( // Metadata are carried by any type of packets. They constitute additional informations on the // packet itself or, about its context (gateway, duty cycle, etc ...) type Metadata struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Duty *dutycycle.Cycles `json:"duty,omitempty"` // DutyCycles of the gateway (uint between 0 and 100) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier + Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + DutyRX1 *uint `json:"dut1,omitempty"` // DutyCycle state of the gateway on RX1 (dutyManager.State, see core/dutymanager) + DutyRX2 *uint `json:"dut2,omitempty"` // DutyCycle state of the gateway on RX2 (dutyManager.State, see core/dutyManager) + Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come + Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } // TODO -> Metadata should implements a less byte-consuming MarshalBinary method MarshalJSON() diff --git a/core/metadata_test.go b/core/metadata_test.go index 698cf438d..0962af4dc 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -5,11 +5,9 @@ package core import ( "encoding/json" - "fmt" "testing" "time" - "github.com/TheThingsNetwork/ttn/core/dutycycle" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -61,11 +59,6 @@ var commonTests = []struct { nil, `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, }, - { // Duty - Metadata{Duty: genCycles()}, - nil, - fmt.Sprintf(`{"duty":%s}`, genCyclesPayload()), - }, } var unmarshalTests = []struct { @@ -104,14 +97,3 @@ func TestUnmarshalJSON(t *testing.T) { checkMetadata(t, test.Metadata, metadata) } } - -func genCycles() *dutycycle.Cycles { - cycles := make(dutycycle.Cycles) - cycles[dutycycle.EuropeG1] = 44 - cycles[dutycycle.EuropeG3] = 12 - return &cycles -} - -func genCyclesPayload() string { - return fmt.Sprintf(`{"%s":44,"%s":12}`, dutycycle.EuropeG1, dutycycle.EuropeG3) -} From f0667caff6d602766e68e4fa3912ce356f8dfff4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 21:29:54 +0100 Subject: [PATCH 0979/2266] [feature/downlink] Make ParseDatr public --- core/dutycycle/dutyManager.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index a8d15e6a0..e8f811ceb 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -223,7 +223,7 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { return 0, errors.New(errors.Structural, "Invalid Codr") } - sf, bw, err := parseDatr(datr) + sf, bw, err := ParseDatr(datr) if err != nil { return 0, err } @@ -242,7 +242,7 @@ func computeTOA(size uint, datr string, codr string) (time.Duration, error) { return time.ParseDuration(fmt.Sprintf("%fms", timeOnAir)) } -func parseDatr(datr string) (float64, float64, error) { +func ParseDatr(datr string) (float64, float64, error) { re := regexp.MustCompile("^SF(7|8|9|10|11|12)BW(125|250|500)$") matches := re.FindStringSubmatch(datr) From f94b3b195245613b0696041ed4ba84ec25d6cd5d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 21:30:08 +0100 Subject: [PATCH 0980/2266] [feature/downlink] Implement a score computer for the handler --- core/components/handler/scoreComputer.go | 121 +++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 core/components/handler/scoreComputer.go diff --git a/core/components/handler/scoreComputer.go b/core/components/handler/scoreComputer.go new file mode 100644 index 000000000..befb24973 --- /dev/null +++ b/core/components/handler/scoreComputer.go @@ -0,0 +1,121 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/dutycycle" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +type scoreComputer struct { + chrx1 chan inScore + chrx2 chan inScore + chflush chan chan *outScore +} + +type inScore struct { + ID int + Score int +} + +type outScore struct { + ID int + IsRX2 bool +} + +func newScoreComputer(datr *string) (*scoreComputer, error) { + if datr == nil { + return nil, errors.New(errors.Structural, "Missing mandatory metadata datr") + } + sf, _, err := ParseDatr(*datr) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + c := &scoreComputer{ + chrx1: make(chan inScore), + chrx2: make(chan inScore), + chflush: make(chan chan *outScore), + } + + go watchUpdate(uint(sf), c.chrx1, c.chrx2, c.chflush) + return c, nil +} + +func (c *scoreComputer) Update(i int, metadata core.Metadata) { + // And try to find the best recipient to which answer + dutyRX1 := metadata.DutyRX1 + dutyRX2 := metadata.DutyRX2 + lsnr := metadata.Lsnr + rssi := metadata.Rssi + + if dutyRX1 == nil || dutyRX2 == nil || lsnr == nil || rssi == nil { + // NOTE We could possibly compute something if we had some of them (but not all). + // However, for now, we expect them all + return + } + + c.chrx1 <- inScore{ID: i, Score: computeScore(State(*dutyRX1), *lsnr, *rssi)} + c.chrx2 <- inScore{ID: i, Score: computeScore(State(*dutyRX2), *lsnr, *rssi)} +} + +func (c *scoreComputer) Get() *outScore { + chout := make(chan *outScore) + c.chflush <- chout + return <-chout +} + +func watchUpdate(sf uint, chrx1 <-chan inScore, chrx2 <-chan inScore, chflush <-chan chan *outScore) { + var sRX1, sRX2 inScore + + for { + select { + case rx1 := <-chrx1: + if rx1.Score > sRX1.Score { + sRX1 = rx1 + } + case rx2 := <-chrx2: + if rx2.Score > sRX2.Score { + sRX2 = rx2 + } + case flush := <-chflush: + if sRX1.Score > 0 && (sf == 7 || sf == 8) { // Favor RX1 on SF7 & SF8 + flush <- &outScore{ID: sRX1.ID, IsRX2: false} + return + } + if sRX2.Score > 0 { + flush <- &outScore{ID: sRX2.ID, IsRX2: true} + return + } + flush <- nil + return + } + } +} + +func computeScore(duty State, lsnr float64, rssi int) int { + var score int + + switch duty { + case StateHighlyAvailable: + score += 3000 + case StateAvailable: + score += 2000 + case StateWarning: + score += 1000 + case StateBlocked: + return 0 + } + + if lsnr > 5.0 { + score += 200 + } else if lsnr > 3.0 { + score += 100 + } + + score -= rssi + + return score +} From 2cf8d65f6f8bb9f207efb52ccb62115c55fae183 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 9 Mar 2016 21:30:30 +0100 Subject: [PATCH 0981/2266] [feature/downlink] Make use of the scoreComputer to select right response window for downlink --- core/components/handler/handler.go | 156 +++++++++++++++-------------- 1 file changed, 79 insertions(+), 77 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 37dfdee32..b6dc420c9 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -11,13 +11,13 @@ import ( . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/brocaar/lorawan" ) const bufferDelay time.Duration = time.Millisecond * 300 -const maxDutyCycle = 90 // 90% // component implements the core.Component interface type component struct { @@ -167,18 +167,6 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { } } -func computeScore(dutyCycle uint, rssi int) uint { - if dutyCycle > maxDutyCycle { - return 0 - } - - if dutyCycle > 2*maxDutyCycle/3 { - return uint(1000 + rssi) - } - - return uint(10000 + rssi) -} - // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, // deduplicate them, and send a single enhanced packet to the upadapter for further processing. func (h component) consumeBundles(chbundle <-chan []bundle) { @@ -190,10 +178,19 @@ browseBundles: ctx.WithField("BundleID", bundles[0].ID).Debug("Consume new bundle") var metadata []Metadata var payload []byte - var bestBundle int - var bestScore uint var firstTime time.Time + if len(bundles) < 1 { + continue browseBundles + } + b := bundles[0] + + computer, err := newScoreComputer(b.Packet.Metadata().Datr) + if err != nil { + go h.abortConsume(err, bundles) + continue browseBundles + } + stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) for i, bundle := range bundles { @@ -207,7 +204,6 @@ browseBundles: go h.abortConsume(err, bundles) continue browseBundles } - bestBundle = i firstTime = bundle.Time stats.MarkMeter("handler.uplink.in.unique") } else { @@ -217,24 +213,11 @@ browseBundles: // Append metadata for each of them metadata = append(metadata, bundle.Packet.Metadata()) - - // And try to find the best recipient to which answer - duty := bundle.Packet.Metadata().Duty - rssi := bundle.Packet.Metadata().Rssi - if duty == nil || rssi == nil { - continue - } - score := computeScore(*duty, *rssi) - if score > bestScore { - bestScore = score - bestBundle = i - } + computer.Update(i, bundle.Packet.Metadata()) } // Then create an application-level packet - appEUI := bundles[bestBundle].Packet.AppEUI() - devEUI := bundles[bestBundle].Packet.DevEUI() - packet, err := NewAPacket(appEUI, devEUI, payload, metadata) + packet, err := NewAPacket(b.Packet.AppEUI(), b.Packet.DevEUI(), payload, metadata) if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -242,69 +225,35 @@ browseBundles: // And send it to the wild open // we don't expect a response from the adapter, end of the chain. - adapter := bundles[bestBundle].Adapter - entry := bundles[bestBundle].Entry - recipient, err := adapter.GetRecipient(entry.Recipient) + recipient, err := b.Adapter.GetRecipient(b.Entry.Recipient) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } - _, err = adapter.Send(packet, recipient) + _, err = b.Adapter.Send(packet, recipient) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } stats.MarkMeter("handler.uplink.out") - // Now handle the downlink - down, err := h.packets.Pull(appEUI, devEUI) + // Now handle the downlink and respond to node + best := computer.Get() + down, err := h.packets.Pull(b.Packet.AppEUI(), b.Packet.DevEUI()) if err != nil && err.(errors.Failure).Nature != errors.NotFound { go h.abortConsume(err, bundles) continue browseBundles } - - // Then respond to node for i, bundle := range bundles { - if i == bestBundle { - var resp Packet - - if down != nil && err == nil { - stats.MarkMeter("handler.downlink.pull") - fcnt := bundles[bestBundle].Packet.FCnt() + 1 - macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: entry.DevAddr, - // TODO Take care of the Adaptative Rate and other stuff - FCnt: fcnt, - } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: down.Payload(), - }} - - if err := macPayload.EncryptFRMPayload(entry.AppSKey); err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } - - payload := lorawan.NewPHYPayload(false) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - resp, err = NewBPacket(payload, Metadata{}) - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } + if best != nil && best.ID == i && down != nil && err == nil { + stats.MarkMeter("handler.downlink.pull") + bpacket, err := h.buildDownlink(down, bundle.Packet, bundle.Entry, best.IsRX2) + if err != nil { + go h.abortConsume(errors.New(errors.Structural, err), bundles) + continue browseBundles } - - // TODO handle confirmed data down - - bundle.Chresp <- resp + bundle.Chresp <- bpacket } else { bundle.Chresp <- nil } @@ -322,6 +271,59 @@ func (h component) abortConsume(fault error, bundles []bundle) { } } +// constructs a downlink packet from something we pulled from the gathered downlink, and, the actual +// uplink. +func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 bool) (BPacket, error) { + fcnt := up.FCnt() + 1 + macPayload := lorawan.NewMACPayload(false) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: entry.DevAddr, + FCnt: fcnt, + } + macPayload.FPort = 1 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: down.Payload(), + }} + + if err := macPayload.EncryptFRMPayload(entry.AppSKey); err != nil { + return nil, err + } + + payload := lorawan.NewPHYPayload(false) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, // TODO Handle Confirmed data down + Major: lorawan.LoRaWANR1, + } + payload.MACPayload = macPayload + + data, err := payload.MarshalBinary() + if err != nil { + return nil, err + } + pmetadata := up.Metadata() + + if pmetadata.Tmst == nil || pmetadata.Freq == nil || pmetadata.Codr == nil || pmetadata.Datr == nil { + return nil, errors.New(errors.Structural, "Missing mandatory metadata in uplink packet") + } + + metadata := Metadata{ + Freq: pmetadata.Freq, + Codr: pmetadata.Codr, + Datr: pmetadata.Datr, + Size: pointer.Uint(uint(len(data))), + Tmst: pointer.Uint(*pmetadata.Tmst + 1000), + } + + if isRX2 { // Should we reply on RX2, metadata aren't the same + // TODO Handle different regions with non hard-coded values + metadata.Freq = pointer.Float64(869.5) + metadata.Datr = pointer.String("SF9BW125") + metadata.Tmst = pointer.Uint(*pmetadata.Tmst + 2000) + } + + return NewBPacket(payload, metadata) +} + // consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) // It then flushes them once a given delay has passed since the reception of the first bundle. func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { From a9c353e7b8e15585bb4cd4ba63481b0523b5af3f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 11:09:20 +0100 Subject: [PATCH 0982/2266] [feature/downlink] Fix router tests to handle added metadata --- core/components/router/helpers_test.go | 4 +- core/components/router/router.go | 2 +- core/components/router/router_test.go | 84 +++++++++++++++++++------- 3 files changed, 65 insertions(+), 25 deletions(-) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 75a9e74ce..c969c6e25 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -36,7 +36,7 @@ func newRPacket(rawDevAddr [4]byte, payload string, gatewayID []byte, metadata M return packet } -func newBPacket(rawDevAddr [4]byte, payload string) BPacket { +func newBPacket(rawDevAddr [4]byte, payload string, metadata Metadata) BPacket { var devAddr lorawan.DevAddr copy(devAddr[:], rawDevAddr[:]) @@ -51,7 +51,7 @@ func newBPacket(rawDevAddr [4]byte, payload string) BPacket { Major: lorawan.LoRaWANR1, } - packet, err := NewBPacket(phyPayload, Metadata{}) + packet, err := NewBPacket(phyPayload, metadata) if err != nil { panic(err) } diff --git a/core/components/router/router.go b/core/components/router/router.go index 0d99de377..243df7004 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -88,7 +88,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 } - bpacket, err := NewBPacket(packet.Payload(), packet.Metadata()) + bpacket, err := NewBPacket(packet.Payload(), metadata) if err != nil { r.ctx.WithError(err).Warn("Unable to create router packet") return errors.New(errors.Structural, err) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index c3c9f07a5..8b21418e3 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -93,10 +93,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -140,10 +144,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -200,7 +208,7 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() m := newMockDutyManager() @@ -240,10 +248,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -282,7 +294,7 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() m := newMockDutyManager() @@ -316,10 +328,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -351,10 +367,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -396,10 +416,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -440,10 +464,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -475,10 +503,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5)}, + ) m := newMockDutyManager() m.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") @@ -522,10 +554,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() // Operate @@ -569,10 +605,14 @@ func TestHandleUp(t *testing.T) { [4]byte{2, 3, 2, 3}, "Payload", []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, + Metadata{Freq: pointer.Float64(865.5)}, ) data, _ := inPacket.MarshalBinary() - bpacket := newBPacket([4]byte{2, 3, 2, 3}, "Payload") + bpacket := newBPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, + ) m := newMockDutyManager() m.Failures["Update"] = errors.New(errors.Operational, "Mock Error: Update") From 77eb0058c3e372ce0ed50437b20da7eec0633884 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 11:53:23 +0100 Subject: [PATCH 0983/2266] [feature/downlink] Move score computer in dutycycle package + add comments --- core/components/handler/scoreComputer.go | 121 ---------------------- core/dutycycle/scoreComputer.go | 122 +++++++++++++++++++++++ 2 files changed, 122 insertions(+), 121 deletions(-) delete mode 100644 core/components/handler/scoreComputer.go create mode 100644 core/dutycycle/scoreComputer.go diff --git a/core/components/handler/scoreComputer.go b/core/components/handler/scoreComputer.go deleted file mode 100644 index befb24973..000000000 --- a/core/components/handler/scoreComputer.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/dutycycle" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -type scoreComputer struct { - chrx1 chan inScore - chrx2 chan inScore - chflush chan chan *outScore -} - -type inScore struct { - ID int - Score int -} - -type outScore struct { - ID int - IsRX2 bool -} - -func newScoreComputer(datr *string) (*scoreComputer, error) { - if datr == nil { - return nil, errors.New(errors.Structural, "Missing mandatory metadata datr") - } - sf, _, err := ParseDatr(*datr) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - c := &scoreComputer{ - chrx1: make(chan inScore), - chrx2: make(chan inScore), - chflush: make(chan chan *outScore), - } - - go watchUpdate(uint(sf), c.chrx1, c.chrx2, c.chflush) - return c, nil -} - -func (c *scoreComputer) Update(i int, metadata core.Metadata) { - // And try to find the best recipient to which answer - dutyRX1 := metadata.DutyRX1 - dutyRX2 := metadata.DutyRX2 - lsnr := metadata.Lsnr - rssi := metadata.Rssi - - if dutyRX1 == nil || dutyRX2 == nil || lsnr == nil || rssi == nil { - // NOTE We could possibly compute something if we had some of them (but not all). - // However, for now, we expect them all - return - } - - c.chrx1 <- inScore{ID: i, Score: computeScore(State(*dutyRX1), *lsnr, *rssi)} - c.chrx2 <- inScore{ID: i, Score: computeScore(State(*dutyRX2), *lsnr, *rssi)} -} - -func (c *scoreComputer) Get() *outScore { - chout := make(chan *outScore) - c.chflush <- chout - return <-chout -} - -func watchUpdate(sf uint, chrx1 <-chan inScore, chrx2 <-chan inScore, chflush <-chan chan *outScore) { - var sRX1, sRX2 inScore - - for { - select { - case rx1 := <-chrx1: - if rx1.Score > sRX1.Score { - sRX1 = rx1 - } - case rx2 := <-chrx2: - if rx2.Score > sRX2.Score { - sRX2 = rx2 - } - case flush := <-chflush: - if sRX1.Score > 0 && (sf == 7 || sf == 8) { // Favor RX1 on SF7 & SF8 - flush <- &outScore{ID: sRX1.ID, IsRX2: false} - return - } - if sRX2.Score > 0 { - flush <- &outScore{ID: sRX2.ID, IsRX2: true} - return - } - flush <- nil - return - } - } -} - -func computeScore(duty State, lsnr float64, rssi int) int { - var score int - - switch duty { - case StateHighlyAvailable: - score += 3000 - case StateAvailable: - score += 2000 - case StateWarning: - score += 1000 - case StateBlocked: - return 0 - } - - if lsnr > 5.0 { - score += 200 - } else if lsnr > 3.0 { - score += 100 - } - - score -= rssi - - return score -} diff --git a/core/dutycycle/scoreComputer.go b/core/dutycycle/scoreComputer.go new file mode 100644 index 000000000..fb2519bcd --- /dev/null +++ b/core/dutycycle/scoreComputer.go @@ -0,0 +1,122 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package dutycycle + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// ScoreComputer enables an external component to manipulate metadata associated to several targets +// in order to determine which target is the most suitable for a downlink response. +// It considers two windows RX1 and RX2 with the following conventions: +// +// For SF7 & SF8, RX1, the algorithm favors RX1 +// +// For SF9+ or, if no target is available on RX1, then RX2 is used +// +// Within RX1 or RX2, the SNR is considered first (the higher the better), then the RSSI on a lower +// plan. +type ScoreComputer struct { + sf uint +} + +// BestTarget represents the best result that has been computed after all updates. +type BestTarget struct { + ID int // The ID provided during updates + IsRX2 bool // Whether it should use RX2 +} + +type scores struct { + rx1 struct { + ID int + Score int + } + rx2 struct { + ID int + Score int + } +} + +// NewScoreComputer constructs a new ScoreComputer and initiate an empty scores table +func NewScoreComputer(datr *string) (*ScoreComputer, scores, error) { + if datr == nil { + return nil, scores{}, errors.New(errors.Structural, "Missing mandatory metadata datr") + } + sf, _, err := ParseDatr(*datr) + if err != nil { + return nil, scores{}, errors.New(errors.Structural, err) + } + + return &ScoreComputer{sf: uint(sf)}, scores{}, nil +} + +// Update computes the score associated to the given target and update the internal score +// accordingly whether it is better than the existing one +func (c *ScoreComputer) Update(s scores, id int, metadata core.Metadata) scores { + dutyRX1, dutyRX2 := metadata.DutyRX1, metadata.DutyRX2 + lsnr, rssi := metadata.Lsnr, metadata.Rssi + + if dutyRX1 == nil || dutyRX2 == nil || lsnr == nil || rssi == nil { + // NOTE We could possibly compute something if we had some of them (but not all). + // However, for now, we expect them all + return s + } + + rx1 := computeScore(State(*dutyRX1), *lsnr, *rssi) + if rx1 > s.rx1.Score { + s.rx1.Score, s.rx1.ID = rx1, id + } + + rx2 := computeScore(State(*dutyRX2), *lsnr, *rssi) + if rx2 > s.rx2.Score { + s.rx2.Score, s.rx2.ID = rx2, id + } + + return s +} + +// Get returns the best score according to the configured spread factor and all updates. +// It returns nil if none of the target is available for a response +func (c *ScoreComputer) Get(s scores) *BestTarget { + if s.rx1.Score > 0 && (c.sf == 7 || c.sf == 8) { // Favor RX1 on SF7 & SF8 + return &BestTarget{ID: s.rx1.ID, IsRX2: false} + } + if s.rx2.Score > 0 { + return &BestTarget{ID: s.rx2.ID, IsRX2: true} + } + return nil +} + +func computeScore(duty State, lsnr float64, rssi int) int { + var score int + + // Most importance on the duty cycle + switch duty { + case StateHighlyAvailable: + score += 5000 + case StateAvailable: + score += 4000 + case StateWarning: + score += 3000 + case StateBlocked: + return 0 + } + + // Then, we favor lsnr + if lsnr > 5.0 { + score += 1000 + } else if lsnr > 4.0 { + score += 750 + } else if lsnr > 3.0 { + score += 500 + } + + // Rssi, negative will lower the score again. + // For similar lsnr, this will make the difference. + // We don't expect rssi to be lower than 500 though ... + score += rssi + + return score +} From 96aaa29a6a8896cb5599b98884e22e723e2d5d81 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 11:53:52 +0100 Subject: [PATCH 0984/2266] [feature/downlink] Adapt handler to new ScoreComputer --- core/components/handler/handler.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index b6dc420c9..119d22646 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -10,6 +10,7 @@ import ( "time" . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/TheThingsNetwork/ttn/utils/stats" @@ -185,7 +186,7 @@ browseBundles: } b := bundles[0] - computer, err := newScoreComputer(b.Packet.Metadata().Datr) + computer, scores, err := dutycycle.NewScoreComputer(b.Packet.Metadata().Datr) if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -213,7 +214,7 @@ browseBundles: // Append metadata for each of them metadata = append(metadata, bundle.Packet.Metadata()) - computer.Update(i, bundle.Packet.Metadata()) + scores = computer.Update(scores, i, bundle.Packet.Metadata()) } // Then create an application-level packet @@ -239,7 +240,7 @@ browseBundles: stats.MarkMeter("handler.uplink.out") // Now handle the downlink and respond to node - best := computer.Get() + best := computer.Get(scores) down, err := h.packets.Pull(b.Packet.AppEUI(), b.Packet.DevEUI()) if err != nil && err.(errors.Failure).Nature != errors.NotFound { go h.abortConsume(err, bundles) From b4dd454c4acfc08cbe92596760f23cc340c04302 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 13:53:12 +0100 Subject: [PATCH 0985/2266] [feature/downlink] Fix handler tests --- core/components/handler/handler.go | 4 + core/components/handler/handler_test.go | 157 +++++++++++------------- 2 files changed, 73 insertions(+), 88 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 119d22646..4a177fcdb 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -315,6 +315,10 @@ func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 Tmst: pointer.Uint(*pmetadata.Tmst + 1000), } + h.ctx.Debugf("IsRX2: %v", isRX2) + h.ctx.Debugf("old tmst: %v", *pmetadata.Tmst) + h.ctx.Debugf("new tmst: %v", *metadata.Tmst) + if isRX2 { // Should we reply on RX2, metadata aren't the same // TODO Handle different regions with non hard-coded values metadata.Freq = pointer.Float64(869.5) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index f3367cfdc..b660901e8 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -4,11 +4,11 @@ package handler import ( - "sync" "testing" "time" . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/dutycycle" . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" @@ -198,8 +198,7 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -257,8 +256,7 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -311,8 +309,11 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(75), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), + DutyRX1: pointer.Uint(uint(dutycycle.StateWarning)), + DutyRX2: pointer.Uint(uint(dutycycle.StateWarning)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -328,15 +329,18 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(0), + Datr: pointer.String("SF7BW125"), + DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) dataIn2, _ := inPkt2.MarshalBinary() - // Expected response + // Expected Sent pktSent, _ := NewAPacket( inPkt1.AppEUI(), inPkt1.DevEUI(), @@ -356,31 +360,38 @@ func TestHandleUp(t *testing.T) { // Operate handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - done := sync.WaitGroup{} - done.Add(2) + cherr := make(chan bool) go func() { - defer done.Done() + var ok bool + defer func(ok *bool) { cherr <- *ok }(&ok) err := handler.HandleUp(dataIn1, an1, adapter1) // Check CheckErrors(t, nil, err) CheckAcks(t, true, an1.InAck) - CheckSent(t, nil, adapter1.InSendPacket) - CheckRecipients(t, nil, adapter1.InSendRecipients) + CheckSent(t, pktSent, adapter1.InSendPacket) // We actually transfer to the first bundle + CheckRecipients(t, []Recipient{recipient}, adapter1.InSendRecipients) + ok = true }() go func() { <-time.After(time.Millisecond * 50) - defer done.Done() + var ok bool + defer func(ok *bool) { cherr <- *ok }(&ok) err := handler.HandleUp(dataIn2, an2, adapter2) // Check CheckErrors(t, nil, err) CheckAcks(t, true, an2.InAck) - CheckSent(t, pktSent, adapter2.InSendPacket) // Adapter2 because the adapter of the best bundle even if they are supposed to be identical - CheckRecipients(t, []Recipient{recipient}, adapter2.InSendRecipients) + CheckSent(t, nil, adapter2.InSendPacket) + CheckRecipients(t, nil, adapter2.InSendRecipients) + ok = true }() // Check - done.Wait() + ok1 := <-cherr + ok2 := <-cherr + if !(ok1 && ok2) { + return + } CheckPushed(t, nil, pktStorage.InPush) CheckPersonalized(t, nil, devStorage.InStorePersonalized) } @@ -396,13 +407,20 @@ func TestHandleUp(t *testing.T) { an := NewMockAckNacker() adapter := NewMockAdapter() adapter.OutGetRecipient = recipient + tmst := time.Now() inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), + Freq: pointer.Float64(865.5), + Tmst: pointer.Uint(uint(tmst.Unix() * 1000)), + Codr: pointer.String("4/5"), + DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -417,7 +435,13 @@ func TestHandleUp(t *testing.T) { brkResp := newBPacket( [4]byte{2, 2, 2, 2}, "Downlink", - Metadata{}, + Metadata{ + Datr: pointer.String("SF7BW125"), + Freq: pointer.Float64(865.5), + Tmst: pointer.Uint(uint(tmst.Add(time.Second).Unix() * 1000)), + Codr: pointer.String("4/5"), + Size: pointer.Uint(21), + }, 11, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) @@ -471,8 +495,7 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -496,80 +519,35 @@ func TestHandleUp(t *testing.T) { // Operate handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - done := sync.WaitGroup{} - done.Add(2) + cherr := make(chan bool) go func() { - defer done.Done() + var ok bool + defer func(ok *bool) { cherr <- *ok }(&ok) err := handler.HandleUp(dataIn, an1, adapter1) // Check CheckErrors(t, nil, err) CheckAcks(t, true, an1.InAck) CheckSent(t, pktSent, adapter1.InSendPacket) + ok = true }() go func() { - defer done.Done() + var ok bool + defer func(ok *bool) { cherr <- *ok }(&ok) <-time.After(2 * bufferDelay) err := handler.HandleUp(dataIn, an2, adapter2) // Check CheckErrors(t, pointer.String(string(errors.Operational)), err) CheckAcks(t, false, an2.InAck) CheckSent(t, nil, adapter2.InSendPacket) + ok = true }() // Check - done.Wait() - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | No downlink ready | No Metadata ") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{}, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - - adapter.OutGetRecipient = recipient - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + if !(<-cherr && <-cherr) { + return } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, nil, err) CheckPushed(t, nil, pktStorage.InPush) CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, true, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) } // -------------------- @@ -589,8 +567,7 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -643,8 +620,7 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -690,8 +666,7 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -746,8 +721,11 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "PayloadPacket1", Metadata{ - Duty: pointer.Uint(75), - Rssi: pointer.Int(-25), + Datr: pointer.String("SF7BW125"), + DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, @@ -763,8 +741,11 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "PayloadPacket2", Metadata{ - Duty: pointer.Uint(5), - Rssi: pointer.Int(0), + Datr: pointer.String("SF7BW125"), + DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), }, 11, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, From ceea512a23215118d5cd835ba0967e548b0520b1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 14:52:35 +0100 Subject: [PATCH 0986/2266] [feature/downlink] Enhance test coverage of dutycycle --- core/dutycycle/dutyManager_test.go | 99 +++++++++- core/dutycycle/helpers_test.go | 8 + core/dutycycle/scoreComputer.go | 2 +- core/dutycycle/scoreComputer_test.go | 281 +++++++++++++++++++++++++++ 4 files changed, 386 insertions(+), 4 deletions(-) create mode 100644 core/dutycycle/scoreComputer_test.go diff --git a/core/dutycycle/dutyManager_test.go b/core/dutycycle/dutyManager_test.go index 7f91146eb..d8b661d4d 100644 --- a/core/dutycycle/dutyManager_test.go +++ b/core/dutycycle/dutyManager_test.go @@ -18,6 +18,15 @@ import ( var dutyManagerDB = path.Join(os.TempDir(), "TestDutyCycleStorage.db") func TestGetSubBand(t *testing.T) { + { + Desc(t, "Test EuropeG") + sb, err := GetSubBand(867.127) + errutil.CheckErrors(t, nil, err) + CheckSubBands(t, EuropeG, sb) + } + + // -------------------- + { Desc(t, "Test EuropeG1") sb, err := GetSubBand(868.38) @@ -25,13 +34,17 @@ func TestGetSubBand(t *testing.T) { CheckSubBands(t, EuropeG1, sb) } + // -------------------- + { - Desc(t, "Test EuropeG") - sb, err := GetSubBand(867.127) + Desc(t, "Test EuropeG2") + sb, err := GetSubBand(868.865) errutil.CheckErrors(t, nil, err) - CheckSubBands(t, EuropeG, sb) + CheckSubBands(t, EuropeG2, sb) } + // -------------------- + { Desc(t, "Test EuropeG3") sb, err := GetSubBand(869.567) @@ -39,6 +52,17 @@ func TestGetSubBand(t *testing.T) { CheckSubBands(t, EuropeG3, sb) } + // -------------------- + + { + Desc(t, "Test EuropeG4") + sb, err := GetSubBand(869.856) + errutil.CheckErrors(t, nil, err) + CheckSubBands(t, EuropeG4, sb) + } + + // -------------------- + { Desc(t, "Test Unknown") sb, err := GetSubBand(433.5) @@ -51,6 +75,7 @@ func TestNewManager(t *testing.T) { defer func() { os.Remove(dutyManagerDB) }() + { Desc(t, "Europe with valid cycleLength") m, err := NewManager(dutyManagerDB, time.Minute, Europe) @@ -59,12 +84,16 @@ func TestNewManager(t *testing.T) { errutil.CheckErrors(t, nil, err) } + // -------------------- + { Desc(t, "Europe with invalid cycleLength") _, err := NewManager(dutyManagerDB, 0, Europe) errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) } + // -------------------- + { Desc(t, "Not europe with valid cycleLength") _, err := NewManager(dutyManagerDB, time.Minute, China) @@ -76,6 +105,7 @@ func TestUpdateAndLookup(t *testing.T) { defer func() { os.Remove(dutyManagerDB) }() + { Desc(t, "Update unsupported frequency") @@ -91,6 +121,9 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // -------------------- + { Desc(t, "Update invalid datr") @@ -106,6 +139,9 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // -------------------- + { Desc(t, "Update invalid codr") @@ -121,6 +157,9 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // -------------------- + { Desc(t, "Update once then lookup") @@ -144,6 +183,9 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // -------------------- + { Desc(t, "Update several then lookup") @@ -172,6 +214,9 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // -------------------- + { Desc(t, "Update out of cycle then lookup") @@ -198,6 +243,9 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // -------------------- + { Desc(t, "Lookup out of cycle") @@ -220,4 +268,49 @@ func TestUpdateAndLookup(t *testing.T) { // Clean m.Close() } + + // ------------------- + + { + Desc(t, "Update on sf11 et sf12 with a 125 bandwidth -> optimization for low datarate") + + // Build + m, _ := NewManager(dutyManagerDB, time.Minute, Europe) + + // Operate + err := m.Update([]byte{4, 12, 6}, 868.523, 14, "SF11BW125", "4/5") + errutil.CheckErrors(t, nil, err) + err = m.Update([]byte{4, 12, 6}, 868.523, 42, "SF12BW125", "4/5") + errutil.CheckErrors(t, nil, err) + bands, err := m.Lookup([]byte{4, 12, 6}) + + // Expectation + want := map[subBand]uint{ + EuropeG1: 384, + } + + // Check + errutil.CheckErrors(t, nil, err) + CheckUsages(t, want, bands) + + // Clean + m.Close() + } +} + +func TestStateFromDuty(t *testing.T) { + Desc(t, "Duty = 100 -> Blocked") + CheckStates(t, StateBlocked, StateFromDuty(100)) + + Desc(t, "Duty = 324 -> Blocked") + CheckStates(t, StateBlocked, StateFromDuty(324)) + + Desc(t, "Duty = 90 -> Warning") + CheckStates(t, StateWarning, StateFromDuty(90)) + + Desc(t, "Duty = 50 -> Available") + CheckStates(t, StateAvailable, StateFromDuty(50)) + + Desc(t, "Duty = 3 -> Highly Available") + CheckStates(t, StateHighlyAvailable, StateFromDuty(3)) } diff --git a/core/dutycycle/helpers_test.go b/core/dutycycle/helpers_test.go index b2a614bd0..057a1a34c 100644 --- a/core/dutycycle/helpers_test.go +++ b/core/dutycycle/helpers_test.go @@ -17,3 +17,11 @@ func CheckSubBands(t *testing.T, want subBand, got subBand) { func CheckUsages(t *testing.T, want map[subBand]uint, got map[subBand]uint) { Check(t, want, got, "Usages") } + +func CheckBestTargets(t *testing.T, want *BestTarget, got *BestTarget) { + Check(t, want, got, "Targets") +} + +func CheckStates(t *testing.T, want State, got State) { + Check(t, want, got, "States") +} diff --git a/core/dutycycle/scoreComputer.go b/core/dutycycle/scoreComputer.go index fb2519bcd..12b9c12cc 100644 --- a/core/dutycycle/scoreComputer.go +++ b/core/dutycycle/scoreComputer.go @@ -97,7 +97,7 @@ func computeScore(duty State, lsnr float64, rssi int) int { case StateHighlyAvailable: score += 5000 case StateAvailable: - score += 4000 + score += 4900 case StateWarning: score += 3000 case StateBlocked: diff --git a/core/dutycycle/scoreComputer_test.go b/core/dutycycle/scoreComputer_test.go new file mode 100644 index 000000000..243a0d971 --- /dev/null +++ b/core/dutycycle/scoreComputer_test.go @@ -0,0 +1,281 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package dutycycle + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestNewScoreComputer(t *testing.T) { + { + Desc(t, "Nil datr as argument") + _, _, err := NewScoreComputer(nil) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + } + + // -------------------- + + { + Desc(t, "Invalid datr as argument") + _, _, err := NewScoreComputer(pointer.String("TheThingsNetwork")) + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + } + + // -------------------- + + { + Desc(t, "Valid datr") + _, _, err := NewScoreComputer(pointer.String("SF8BW250")) + errutil.CheckErrors(t, nil, err) + } +} + +func TestUpdateGet(t *testing.T) { + t.Log(` + The following set of tests follows the given notation: + Desc := SF | Upgrade + SF := { SF7, SF8, SF9, SF10, SF11, SF12 } + Upgrade := ... | UpgradeDesc + UpgradeDesc := (ID, State, State, Rssi, Lsnr) + ID := uint + State := { Ha, Av, Wa, Bl } + Rssi := int + Lsnr := int + `) + + { + Desc(t, "SF7 | ...") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + got := c.Get(s) + + // Check + CheckBestTargets(t, nil, got) + } + + // -------------------- + + { + Desc(t, "SF7 | (1, Av, Bl, -25, 5.0)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateBlocked)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: false}, got) + } + + // -------------------- + + { + Desc(t, "SF7 | (1, Bl, Ha, -25, 5.0)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateBlocked)), + DutyRX2: pointer.Uint(uint(StateHighlyAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) + } + + // -------------------- + + { + Desc(t, "SF7 | (1, Bl, Bl, -25, 5.0)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateBlocked)), + DutyRX2: pointer.Uint(uint(StateBlocked)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, nil, got) + } + + // -------------------- + + { + Desc(t, "SF9 | (1, Av, Av, -25, 5.0) ") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF9BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) + } + + // -------------------- + + { + Desc(t, "SF10 | (1, Av, Av, -25, 5.0) :: (2, Av, Av, -25, 3.0)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF10BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + s = c.Update(s, 2, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(3.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) + } + + // -------------------- + + { + Desc(t, "SF10 | (1, Av, Bl, -25, 5.0)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF10BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateBlocked)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, nil, got) + } + + // -------------------- + + { + Desc(t, "SF8 | (1, Wa, Av, -25, 5.0) :: (2, Av, Av, -25, 5.0)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF8BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateWarning)), + DutyRX2: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + s = c.Update(s, 2, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.0), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, &BestTarget{ID: 2, IsRX2: false}, got) + } + + // -------------------- + + { + Desc(t, "SF12 | (1, Av, Av, -25, 5.1) :: (2, Ha, Ha, -25, 3.4)") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF12BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + DutyRX2: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.1), + }) + s = c.Update(s, 2, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateHighlyAvailable)), + DutyRX2: pointer.Uint(uint(StateHighlyAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(3.4), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) + } + + // -------------------- + + { + Desc(t, "Missing metadata in update") + + // Build + c, s, err := NewScoreComputer(pointer.String("SF12BW125")) + errutil.CheckErrors(t, nil, err) + + // Operate + s = c.Update(s, 1, core.Metadata{ + DutyRX1: pointer.Uint(uint(StateAvailable)), + Rssi: pointer.Int(-25), + Lsnr: pointer.Float64(5.1), + }) + got := c.Get(s) + + // Check + CheckBestTargets(t, nil, got) + } +} From cef19b2dab5d6fc564b0bc2ac458a49137d664c5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 14:59:55 +0100 Subject: [PATCH 0987/2266] [feature/downlink] Update router command to take DutyManager in account --- cmd/router.go | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/cmd/router.go b/cmd/router.go index 8e7e853d9..226a54eec 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -15,6 +15,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/udp" udpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/udp/handlers" "github.com/TheThingsNetwork/ttn/core/components/router" + "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -37,7 +38,8 @@ the gateway's duty cycle is (almost) full.`, } ctx.WithFields(log.Fields{ - "database": viper.GetString("router.database"), + "db-brokers": viper.GetString("router.db_brokers"), + "db-gateways": viper.GetString("router.db_gateways"), "status-server": statusServer, "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")), "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")), @@ -79,7 +81,7 @@ the gateway's duty cycle is (almost) full.`, var db router.Storage - dbString := viper.GetString("router.database") + dbString := viper.GetString("router.db_brokers") switch { case strings.HasPrefix(dbString, "boltdb:"): @@ -98,7 +100,28 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } - router := router.New(db, ctx) + var dm dutycycle.DutyManager + + dmString := viper.GetString("router.db_gateways") + switch { + case strings.HasPrefix(dmString, "boltdb:"): + + dmPath, err := filepath.Abs(dmString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid database path") + } + + dm, err = dutycycle.NewManager(dmPath, time.Hour, dutycycle.Europe) + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + ctx.WithField("database", dmPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") + } + + router := router.New(db, dm, ctx) // Bring the service to life @@ -143,8 +166,11 @@ the gateway's duty cycle is (almost) full.`, func init() { RootCmd.AddCommand(routerCmd) - routerCmd.Flags().String("database", "boltdb:/tmp/ttn_router.db", "Database connection") - viper.BindPFlag("router.database", routerCmd.Flags().Lookup("database")) + routerCmd.Flags().String("db_brokers", "boltdb:/tmp/ttn_router_brokers.db", "Database connection of known brokers") + viper.BindPFlag("router.db_brokers", routerCmd.Flags().Lookup("db_brokers")) + + routerCmd.Flags().String("db_gateways", "boltdb:/tmp/ttn_router_gateways.db", "Database connection of managed gateways") + viper.BindPFlag("router.db_gateways", routerCmd.Flags().Lookup("db_gateways")) routerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") From 7849d6bc1f01bae038b145868244abce6234beff Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 15:38:49 +0100 Subject: [PATCH 0988/2266] [feature/downlink] Add new test cases to router and handler to cover RX1 & RX2 management --- core/components/handler/handler.go | 5 +- core/components/handler/handler_test.go | 149 +++++++++++++++++++++++- core/components/router/router_test.go | 66 +++++++++++ 3 files changed, 214 insertions(+), 6 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 4a177fcdb..90a1a83db 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -152,7 +152,7 @@ func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { case error: stats.MarkMeter("handler.uplink.error") ctx.WithError(resp.(error)).Warn("Received errored response. Sending Ack") - return errors.New(errors.Operational, resp.(error)) + return resp.(error) default: stats.MarkMeter("handler.uplink.ack.without_response") ctx.Debug("Received empty response. Sending empty Ack") @@ -263,8 +263,7 @@ browseBundles: } // Abort consume forward the given error to all bundle recipients -func (h component) abortConsume(fault error, bundles []bundle) { - err := errors.New(errors.Structural, fault) +func (h component) abortConsume(err error, bundles []bundle) { stats.MarkMeter("handler.uplink.invalid") h.ctx.WithError(err).Debug("Unable to consume bundle") for _, bundle := range bundles { diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index b660901e8..822fcd398 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -536,7 +536,7 @@ func TestHandleUp(t *testing.T) { <-time.After(2 * bufferDelay) err := handler.HandleUp(dataIn, an2, adapter2) // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckErrors(t, pointer.String(string(errors.Behavioural)), err) CheckAcks(t, false, an2.InAck) CheckSent(t, nil, adapter2.InSendPacket) ok = true @@ -614,7 +614,7 @@ func TestHandleUp(t *testing.T) { an := NewMockAckNacker() adapter := NewMockAdapter() adapter.OutGetRecipient = recipient - adapter.Failures["GetRecipient"] = errors.New(errors.Operational, "Mock Error: Unable to get recipient") + adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Unable to get recipient") inPkt := newHPacket( [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -642,7 +642,7 @@ func TestHandleUp(t *testing.T) { err := handler.HandleUp(dataIn, an, adapter) // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckPushed(t, nil, pktStorage.InPush) CheckPersonalized(t, nil, devStorage.InStorePersonalized) CheckAcks(t, false, an.InAck) @@ -802,4 +802,147 @@ func TestHandleUp(t *testing.T) { CheckPersonalized(t, nil, devStorage.InStorePersonalized) } + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | One downlink response, mising metadata in uplink") + + // Build + recipient := NewMockJSONRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Datr: pointer.String("SF7BW125"), + Freq: pointer.Float64(865.5), + DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + appResp, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Downlink"), + []Metadata{}, + ) + + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + pktStorage := newMockPktStorage() + pktStorage.OutPull = appResp + broker := NewMockJSONRecipient() + + // Operate + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) + err := handler.HandleUp(dataIn, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, false, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + } + + // -------------------- + + { + Desc(t, "Handle uplink with 1 packet | One downlink response | Only RX2 available") + + // Build + recipient := NewMockJSONRecipient() + dataRecipient, _ := recipient.MarshalBinary() + an := NewMockAckNacker() + adapter := NewMockAdapter() + adapter.OutGetRecipient = recipient + tmst := time.Now() + inPkt := newHPacket( + [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, + [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, + "Payload", + Metadata{ + Datr: pointer.String("SF7BW125"), + Freq: pointer.Float64(865.5), + Tmst: pointer.Uint(uint(tmst.Unix() * 1000)), + Codr: pointer.String("4/5"), + DutyRX1: pointer.Uint(uint(dutycycle.StateBlocked)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), + }, + 10, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + dataIn, _ := inPkt.MarshalBinary() + pktSent, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Payload"), + []Metadata{inPkt.Metadata()}, + ) + brkResp := newBPacket( + [4]byte{2, 2, 2, 2}, + "Downlink", + Metadata{ + Datr: pointer.String("SF9BW125"), + Freq: pointer.Float64(869.5), + Tmst: pointer.Uint(uint(tmst.Add(2*time.Second).Unix() * 1000)), + Codr: pointer.String("4/5"), + Size: pointer.Uint(21), + }, + 11, + [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + ) + appResp, _ := NewAPacket( + inPkt.AppEUI(), + inPkt.DevEUI(), + []byte("Downlink"), + []Metadata{}, + ) + + devStorage := newMockDevStorage() + devStorage.OutLookup = devEntry{ + Recipient: dataRecipient, + DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), + AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, + NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + } + pktStorage := newMockPktStorage() + pktStorage.OutPull = appResp + broker := NewMockJSONRecipient() + + // Operate + handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) + err := handler.HandleUp(dataIn, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckPushed(t, nil, pktStorage.InPush) + CheckPersonalized(t, nil, devStorage.InStorePersonalized) + CheckAcks(t, brkResp, an.InAck) + CheckSent(t, pktSent, adapter.InSendPacket) + CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) + } } diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 8b21418e3..77f0f7596 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -629,4 +629,70 @@ func TestHandleUp(t *testing.T) { CheckIDs(t, inPacket.GatewayID(), m.InLookupId) CheckIDs(t, inPacket.GatewayID(), m.InUpdateId) } + + // ------------------- + + { + Desc(t, "Send an unknown packet | No Metadata") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") + inPacket := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{}, + ) + data, _ := inPacket.MarshalBinary() + m := newMockDutyManager() + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, nil, m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) + } + + // ------------------- + + { + Desc(t, "Send an unknown packet | Unsupported frequency") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") + inPacket := newRPacket( + [4]byte{2, 3, 2, 3}, + "Payload", + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{Freq: pointer.Float64(333.5)}, + ) + data, _ := inPacket.MarshalBinary() + m := newMockDutyManager() + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, inPacket.GatewayID(), m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) + } } From 2e7884390e68ba54936294ba8d81c3ec88717095 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 15:44:44 +0100 Subject: [PATCH 0989/2266] [hotfix] Remove non-necessary debug lines in handler --- core/components/handler/handler.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 90a1a83db..72bfbace8 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -314,10 +314,6 @@ func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 Tmst: pointer.Uint(*pmetadata.Tmst + 1000), } - h.ctx.Debugf("IsRX2: %v", isRX2) - h.ctx.Debugf("old tmst: %v", *pmetadata.Tmst) - h.ctx.Debugf("new tmst: %v", *metadata.Tmst) - if isRX2 { // Should we reply on RX2, metadata aren't the same // TODO Handle different regions with non hard-coded values metadata.Freq = pointer.Float64(869.5) From 1539a3b58a72c0aa5fb0e0ae6bd5c5c4e6e98364 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 15:47:06 +0100 Subject: [PATCH 0990/2266] Add coverall badge to README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index eb452e629..214a229c8 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ The Things Network ![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) +[![Coverage Status](https://coveralls.io/repos/github/TheThingsNetwork/ttn/badge.svg?branch=develop)](https://coveralls.io/github/TheThingsNetwork/ttn?branch=develop) + The Things Network is a global open crowdsourced Internet of Things data network. ## Status From 561595aab156d7fc4a8f6f01f92731e99d028256 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 15:48:36 +0100 Subject: [PATCH 0991/2266] Change badge's position -_____- --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 214a229c8..dd08d8607 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ The Things Network ================== -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) [![Coverage Status](https://coveralls.io/repos/github/TheThingsNetwork/ttn/badge.svg?branch=develop)](https://coveralls.io/github/TheThingsNetwork/ttn?branch=develop) ![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) -[![Coverage Status](https://coveralls.io/repos/github/TheThingsNetwork/ttn/badge.svg?branch=develop)](https://coveralls.io/github/TheThingsNetwork/ttn?branch=develop) The Things Network is a global open crowdsourced Internet of Things data network. From a01c4c7d7ce1db32dc36f9ae7f7d04498b9487a2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 17:28:49 +0100 Subject: [PATCH 0992/2266] [hotfix] Make duty cycles available and set to 0 when no data is available on gateway --- core/components/router/router.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 243df7004..01a0baa2b 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -78,16 +78,17 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { cycles, err := r.Manager.Lookup(packet.GatewayID()) if err != nil { r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") - } else { - sb1, err := dutycycle.GetSubBand(*metadata.Freq) - if err != nil { - return errors.New(errors.Structural, "Unhandled uplink signal frequency") - } + cycles = make(dutycycle.Cycles) + } - rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) - metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 + sb1, err := dutycycle.GetSubBand(*metadata.Freq) + if err != nil { + return errors.New(errors.Structural, "Unhandled uplink signal frequency") } + rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) + metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 + bpacket, err := NewBPacket(packet.Payload(), metadata) if err != nil { r.ctx.WithError(err).Warn("Unable to create router packet") From 3176ef2f0e6062db41b67e2ba36b9307c0c7237e Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 17:29:47 +0100 Subject: [PATCH 0993/2266] [hotfix] Add logs to handler + avoid pulling from down packets when no gateway is available --- core/components/handler/handler.go | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 72bfbace8..d9535c4ad 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -185,6 +185,7 @@ browseBundles: continue browseBundles } b := bundles[0] + h.ctx.WithField("Metadata", b.Packet.Metadata()).Debug("Considering first packet") computer, scores, err := dutycycle.NewScoreComputer(b.Packet.Metadata().Datr) if err != nil { @@ -240,12 +241,18 @@ browseBundles: stats.MarkMeter("handler.uplink.out") // Now handle the downlink and respond to node + h.ctx.Debug("Looking for downlink response") best := computer.Get(scores) - down, err := h.packets.Pull(b.Packet.AppEUI(), b.Packet.DevEUI()) + h.ctx.WithField("Bundle", best).Debug("Determine best gateway") + var down APacket + if best != nil { // Avoid pulling when there's no gateway available for an answer + down, err = h.packets.Pull(b.Packet.AppEUI(), b.Packet.DevEUI()) + } if err != nil && err.(errors.Failure).Nature != errors.NotFound { go h.abortConsume(err, bundles) continue browseBundles } + h.ctx.WithField("Packet", down).Debug("Pull downlink from storage") for i, bundle := range bundles { if best != nil && best.ID == i && down != nil && err == nil { stats.MarkMeter("handler.downlink.pull") @@ -390,6 +397,8 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro defer ensureAckNack(an, &ack, &err) stats.MarkMeter("handler.downlink.in") + h.ctx.Debug("Handle downlink message") + // Unmarshal the given packet and see what gift we get itf, err := UnmarshalPacket(data) if err != nil { @@ -399,7 +408,9 @@ func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err erro switch itf.(type) { case APacket: - return h.packets.Push(itf.(APacket)) + apacket := itf.(APacket) + h.ctx.WithField("DevEUI", apacket.DevEUI()).WithField("AppEUI", apacket.AppEUI()).Debug("Save downlink for later") + return h.packets.Push(apacket) default: stats.MarkMeter("handler.downlink.invalid") return errors.New(errors.Implementation, "Unhandled packet type") From 9f1dfcddf527bedbdc71cac0c4398b3dc0e9c651 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 18:00:58 +0100 Subject: [PATCH 0994/2266] [fix/#75] Split the activation mqtt handler in two dedicated handlers --- core/adapters/mqtt/handlers/activation.go | 48 +----- .../adapters/mqtt/handlers/activation_test.go | 107 +------------ core/adapters/mqtt/handlers/downlink.go | 59 +++++++ core/adapters/mqtt/handlers/downlink_test.go | 144 ++++++++++++++++++ 4 files changed, 208 insertions(+), 150 deletions(-) create mode 100644 core/adapters/mqtt/handlers/downlink.go create mode 100644 core/adapters/mqtt/handlers/downlink_test.go diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 2f7fa226a..5ce1677e3 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -9,7 +9,6 @@ import ( "strings" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" @@ -58,17 +57,11 @@ func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegR copy(appEUI[:], data[:]) devEUIStr = hex.EncodeToString(devEUI[:]) - topic := fmt.Sprintf("%s/%s/%s", appEUIStr, "devices", devEUIStr) - topicUp := fmt.Sprintf("%s/%s", topic, "up") - topicDown := fmt.Sprintf("%s/%s", topic, "down") - token := client.Subscribe(topicDown, 2, a.handleReception(chpkt)) - if token.Wait() && token.Error() != nil { - return errors.New(errors.Operational, token.Error()) - } + topic := fmt.Sprintf("%s/%s/%s/up", appEUIStr, "devices", devEUIStr) chreg <- RegReq{ Registration: activationRegistration{ - recipient: NewRecipient(topicUp, ""), + recipient: NewRecipient(topic, ""), devEUI: devEUI, appEUI: appEUI, nwkSKey: nwkSKey, @@ -78,40 +71,3 @@ func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegR } return nil } - -// Handle an incoming downlink packet on the registered channel -func (a Activation) handleReception(chpkt chan<- PktReq) func(client Client, msg MQTT.Message) { - return func(client Client, msg MQTT.Message) { - infos := strings.Split(msg.Topic(), "/") - - if len(infos) != 4 { - return - } - - appEUIRaw, erra := hex.DecodeString(infos[0]) - devEUIRaw, errd := hex.DecodeString(infos[2]) - if erra != nil || errd != nil || len(appEUIRaw) != 8 || len(devEUIRaw) != 8 { - return - } - - var appEUI lorawan.EUI64 - copy(appEUI[:], appEUIRaw) - var devEUI lorawan.EUI64 - copy(devEUI[:], devEUIRaw) - - apacket, err := core.NewAPacket(appEUI, devEUI, msg.Payload(), nil) - if err != nil { - return - } - - data, err := apacket.MarshalBinary() - if err != nil { - return - } - - chpkt <- PktReq{ - Packet: data, - Chresp: nil, - } - } -} diff --git a/core/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go index 473ec2414..5dbad6522 100644 --- a/core/adapters/mqtt/handlers/activation_test.go +++ b/core/adapters/mqtt/handlers/activation_test.go @@ -16,7 +16,7 @@ import ( "github.com/brocaar/lorawan" ) -func TestActionTopic(t *testing.T) { +func TestActivationTopic(t *testing.T) { wantTopic := "+/devices/+/activations" // Describe @@ -55,9 +55,9 @@ func TestActivationHandle(t *testing.T) { }, WantError: nil, - WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/down"), + WantSubscription: nil, WantRegistration: activationRegistration{ - recipient: NewRecipient("0101010101010101/devices/0000000002020202/up", "WHATEVER"), + recipient: NewRecipient("0101010101010101/devices/0000000002020202/up", ""), devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 2, 2, 2, 2}), appEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), nwkSKey: lorawan.AES128Key([16]byte{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}), @@ -141,21 +141,6 @@ func TestActivationHandle(t *testing.T) { WantRegistration: nil, WantPacket: nil, }, - { - Desc: "Valid inputs | Client -> Fail Subscribe", - Client: newTestClient("Subscribe"), - Topic: "0101010101010101/devices/personalized/activations", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - }, - - WantError: pointer.String(string(errors.Operational)), - WantSubscription: pointer.String("0101010101010101/devices/0000000002020202/down"), - WantRegistration: nil, - WantPacket: nil, - }, } for i, test := range tests { @@ -180,89 +165,3 @@ func TestActivationHandle(t *testing.T) { checkPackets(t, test.WantPacket, consumer.Packet) } } - -func TestHandleReception(t *testing.T) { - packet, _ := core.NewAPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - []byte{1, 2, 3, 4}, - nil, - ) - - tests := []struct { - Desc string - Client *testClient - Payload []byte - Topic string - - WantPacket core.Packet - }{ - { - Desc: "Valid Payload | Valid Topic", - Client: newTestClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/0202020202020202/down", - - WantPacket: packet, - }, - { - Desc: "Valid Payload | Invalid Topic #2", - Client: newTestClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/0202020202020202/down/again", - - WantPacket: nil, - }, - { - Desc: "Valid Payload | Invalid Topic", - Client: newTestClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/0202020202020202", - - WantPacket: nil, - }, - { - Desc: "Valid Payload | Invalid AppEUI", - Client: newTestClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "010101/devices/0202020202020202/down", - - WantPacket: nil, - }, - { - Desc: "Valid Payload | Invalid DevEUI", - Client: newTestClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/020202/down", - - WantPacket: nil, - }, - { - Desc: "Invalid Payload | Valid Topic", - Client: newTestClient(), - Payload: []byte{}, - Topic: "0101010101010101/devices/0202020202020202/down", - - WantPacket: nil, - }, - } - for i, test := range tests { - // Describe - Desc(t, "#%d: %s", i, test.Desc) - - // Build - consumer, chpkt, _ := newTestConsumer() - handler := Activation{} - - // Operate - f := handler.handleReception(chpkt) - f(test.Client, testMessage{ - test.Payload, - test.Topic, - }) - <-time.After(time.Millisecond * 100) - - // Check - checkPackets(t, test.WantPacket, consumer.Packet) - } -} diff --git a/core/adapters/mqtt/handlers/downlink.go b/core/adapters/mqtt/handlers/downlink.go new file mode 100644 index 000000000..b86f49507 --- /dev/null +++ b/core/adapters/mqtt/handlers/downlink.go @@ -0,0 +1,59 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "encoding/hex" + "strings" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +// Downlink handles communication between a handler and an application via MQTT +type Downlink struct{} + +// Topic implements the mqtt.Handler interface +func (a Downlink) Topic() string { + return "+/devices/+/down" +} + +// Handle implements the mqtt.Handler interface +func (a Downlink) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { + infos := strings.Split(msg.Topic(), "/") + + if len(infos) != 4 { + return errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") + } + + appEUIRaw, erra := hex.DecodeString(infos[0]) + devEUIRaw, errd := hex.DecodeString(infos[2]) + if erra != nil || errd != nil || len(appEUIRaw) != 8 || len(devEUIRaw) != 8 { + return errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI") + } + + var appEUI lorawan.EUI64 + copy(appEUI[:], appEUIRaw) + var devEUI lorawan.EUI64 + copy(devEUI[:], devEUIRaw) + + apacket, err := core.NewAPacket(appEUI, devEUI, msg.Payload(), nil) + if err != nil { + return errors.New(errors.Structural, err) + } + + data, err := apacket.MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) + } + + chpkt <- PktReq{ + Packet: data, + Chresp: nil, + } + return nil +} diff --git a/core/adapters/mqtt/handlers/downlink_test.go b/core/adapters/mqtt/handlers/downlink_test.go new file mode 100644 index 000000000..c865db0dd --- /dev/null +++ b/core/adapters/mqtt/handlers/downlink_test.go @@ -0,0 +1,144 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func TestDownlinkTopic(t *testing.T) { + wantTopic := "+/devices/+/down" + + // Describe + Desc(t, "Topic should equal: %s", wantTopic) + + // Build + handler := Downlink{} + + // Operate + topic := handler.Topic() + + // Check + checkTopics(t, wantTopic, topic) +} + +func TestDownlinkHandle(t *testing.T) { + packet, _ := core.NewAPacket( + lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), + lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + []byte{1, 2, 3, 4}, + nil, + ) + + tests := []struct { + Desc string // The test's description + Client *testClient // An mqtt client to mock (or not) the behavior + Topic string // The topic to which the message is addressed + Payload []byte // The message's payload + + WantError *string // The expected error from the handler + WantSubscription *string // The topic to which a subscription is expected + WantRegistration core.HRegistration // The expected registration towards the adapter + WantPacket core.Packet // The expected packet towards the adapter + }{ + + { + Desc: "Valid Payload | Valid Topic", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/0202020202020202/down", + + WantError: nil, + WantPacket: packet, + WantSubscription: nil, + WantRegistration: nil, + }, + { + Desc: "Valid Payload | Invalid Topic #2", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/0202020202020202/down/again", + + WantError: pointer.String(string(errors.Structural)), + WantPacket: nil, + WantSubscription: nil, + WantRegistration: nil, + }, + { + Desc: "Valid Payload | Invalid Topic", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/0202020202020202", + + WantError: pointer.String(string(errors.Structural)), + WantPacket: nil, + WantSubscription: nil, + WantRegistration: nil, + }, + { + Desc: "Valid Payload | Invalid AppEUI", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "010101/devices/0202020202020202/down", + + WantError: pointer.String(string(errors.Structural)), + WantPacket: nil, + WantSubscription: nil, + WantRegistration: nil, + }, + { + Desc: "Valid Payload | Invalid DevEUI", + Client: newTestClient(), + Payload: []byte{1, 2, 3, 4}, + Topic: "0101010101010101/devices/020202/down", + + WantError: pointer.String(string(errors.Structural)), + WantPacket: nil, + WantSubscription: nil, + WantRegistration: nil, + }, + { + Desc: "Invalid Payload | Valid Topic", + Client: newTestClient(), + Payload: []byte{}, + Topic: "0101010101010101/devices/0202020202020202/down", + + WantError: pointer.String(string(errors.Structural)), + WantPacket: nil, + WantSubscription: nil, + WantRegistration: nil, + }, + } + + for i, test := range tests { + // Describe + Desc(t, "#%d: %s", i, test.Desc) + + // Build + consumer, chpkt, chreg := newTestConsumer() + handler := Downlink{} + + // Operate + err := handler.Handle(test.Client, chpkt, chreg, testMessage{ + payload: test.Payload, + topic: test.Topic, + }) + <-time.After(time.Millisecond * 100) + + // Check + CheckErrors(t, test.WantError, err) + checkSubscriptions(t, test.WantSubscription, test.Client.Subscription) + checkRegistrations(t, test.WantRegistration, consumer.Registration) + checkPackets(t, test.WantPacket, consumer.Packet) + } +} From 8ac64f369086836a0c014758381617678bb17880 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 18:02:21 +0100 Subject: [PATCH 0995/2266] [fix/#75] Add Downlink handler to the application adapter of the handler --- cmd/handler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/handler.go b/cmd/handler.go index e26d99d1a..eeffc6fec 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -59,6 +59,7 @@ The default handler is the bridge between The Things Network and applications. } appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) + appAdapter.Bind(mqttHandlers.Downlink{}) if viper.GetInt("handler.status-port") > 0 { statusNet := fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) From fc91ab18d416cc7458d5f2a1d725af4a76dfc7e9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 18:07:23 +0100 Subject: [PATCH 0996/2266] [hotfix] Fix tests that broke due to recent on-the-fly modifications --- core/components/handler/handler_test.go | 6 +++++- core/components/router/router_test.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 822fcd398..614d2f147 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -666,7 +666,11 @@ func TestHandleUp(t *testing.T) { [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, "Payload", Metadata{ - Datr: pointer.String("SF7BW125"), + DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), + DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), + Rssi: pointer.Int(-20), + Lsnr: pointer.Float64(5.0), + Datr: pointer.String("SF7BW125"), }, 10, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 77f0f7596..5e1217d40 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -509,7 +509,7 @@ func TestHandleUp(t *testing.T) { bpacket := newBPacket( [4]byte{2, 3, 2, 3}, "Payload", - Metadata{Freq: pointer.Float64(865.5)}, + Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, ) m := newMockDutyManager() m.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") From ac8d09a1530dc9e6a003348bb55812eac3f5be0c Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 18:22:23 +0100 Subject: [PATCH 0997/2266] [issue/#61] Add error return to http handlers --- core/adapters/http/handlers/applications.go | 10 ++++++---- core/adapters/http/handlers/collect.go | 9 +++++---- core/adapters/http/handlers/healthz.go | 3 ++- core/adapters/http/handlers/pubsub.go | 12 +++++++----- core/adapters/http/handlers/statuspage.go | 11 +++++++---- core/adapters/http/http.go | 10 +++++++--- 6 files changed, 34 insertions(+), 21 deletions(-) diff --git a/core/adapters/http/handlers/applications.go b/core/adapters/http/handlers/applications.go index 47725e2a4..8787c1f42 100644 --- a/core/adapters/http/handlers/applications.go +++ b/core/adapters/http/handlers/applications.go @@ -44,20 +44,20 @@ func (p Applications) URL() string { } // Handle implements the http.Handler interface -func (p Applications) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { +func (p Applications) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { // Check the http method if req.Method != "PUT" { err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [PUT] to register a device") w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte(err.Error())) - return + return err } // Parse body and query params registration, err := p.parse(req) if err != nil { BadRequest(w, err.Error()) - return + return err } // Send the registration and wait for ack / nack @@ -65,11 +65,13 @@ func (p Applications) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg c chreg <- RegReq{Registration: registration, Chresp: chresp} r, ok := <-chresp if !ok { + err := errors.New(errors.Operational, "Core server not responding") BadRequest(w, "Core server not responding") - return + return err } w.WriteHeader(r.StatusCode) w.Write(r.Content) + return nil } // parse extracts params from the request and fails if the request is invalid. diff --git a/core/adapters/http/handlers/collect.go b/core/adapters/http/handlers/collect.go index 1b885de12..a0f66936b 100644 --- a/core/adapters/http/handlers/collect.go +++ b/core/adapters/http/handlers/collect.go @@ -28,20 +28,20 @@ func (p Collect) URL() string { } // Handle implements the http.Handler interface -func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { +func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { // Check the http method if req.Method != "POST" { err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [POST] to transfer a packet") w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte(err.Error())) - return + return err } // Parse body and query params data, err := p.parse(req) if err != nil { BadRequest(w, err.Error()) - return + return err } // Send the packet and wait for ack / nack @@ -51,10 +51,11 @@ func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- if !ok { err := errors.New(errors.Operational, "Core server not responding") BadRequest(w, err.Error()) - return + return err } w.WriteHeader(r.StatusCode) w.Write(r.Content) + return nil } // parse extracts params from the request and fails if the request is invalid. diff --git a/core/adapters/http/handlers/healthz.go b/core/adapters/http/handlers/healthz.go index 4ed177bdb..18e147eaa 100644 --- a/core/adapters/http/handlers/healthz.go +++ b/core/adapters/http/handlers/healthz.go @@ -24,7 +24,8 @@ func (p Healthz) URL() string { } // Handle implements the http.Handler interface -func (p Healthz) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { +func (p Healthz) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) + return nil } diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go index 06cb5ee4c..6f5a66481 100644 --- a/core/adapters/http/handlers/pubsub.go +++ b/core/adapters/http/handlers/pubsub.go @@ -51,20 +51,20 @@ func (p PubSub) URL() string { } // Handle implements the http.Handler interface -func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { +func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { // Check the http method if req.Method != "PUT" { err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [PUT] to register a device") w.WriteHeader(http.StatusMethodNotAllowed) w.Write([]byte(err.Error())) - return + return err } // Parse body and query params registration, err := p.parse(req) if err != nil { BadRequest(w, err.Error()) - return + return err } // Send the registration and wait for ack / nack @@ -72,11 +72,13 @@ func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- chreg <- RegReq{Registration: registration, Chresp: chresp} r, ok := <-chresp if !ok { - BadRequest(w, "Core server not responding") - return + err := errors.New(errors.Operational, "Core server not responding") + BadRequest(w, err.Error()) + return err } w.WriteHeader(r.StatusCode) w.Write(r.Content) + return nil } // parse extracts params from the request and fails if the request is invalid. diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go index 6c64c84c0..962efa404 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -9,6 +9,7 @@ import ( "strings" . "github.com/TheThingsNetwork/ttn/core/adapters/http" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/rcrowley/go-metrics" ) @@ -27,12 +28,13 @@ func (p StatusPage) URL() string { } // Handle implements the http.Handler interface -func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) { +func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { // Check the http method if req.Method != "GET" { + err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [GET] to request the status") w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte("Use [GET] to request the status")) - return + w.Write([]byte(err.Error())) + return err } allStats := make(map[string]interface{}) @@ -81,10 +83,11 @@ func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg cha response, err := json.Marshal(allStats) if err != nil { - panic(err) + return errors.New(errors.Structural, err) } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) w.Write(response) + return nil } diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index 68eeedcc7..ca453cb49 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -33,7 +33,7 @@ type Adapter struct { // Handler defines endpoint-specific handler. type Handler interface { URL() string - Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) + Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error } // MsgRes are sent through the response channel of a pktReq or regReq @@ -297,8 +297,12 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) func (a *Adapter) Bind(h Handler) { a.ctx.WithField("url", h.URL()).Info("Register new endpoint") a.serveMux.HandleFunc(h.URL(), func(w http.ResponseWriter, req *http.Request) { - a.ctx.WithField("url", h.URL()).Debug("Handle new request") - h.Handle(w, a.packets, a.registrations, req) + ctx := a.ctx.WithField("url", h.URL()) + ctx.Debug("Handle new request") + err := h.Handle(w, a.packets, a.registrations, req) + if err != nil { + ctx.WithError(err).Debug("Failed to handle the request") + } }) } From 7b133f899cd6d5f042d944ef2c0d8b8a5a96ca32 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 10 Mar 2016 18:25:57 +0100 Subject: [PATCH 0998/2266] [issue/#61] Add as return value to udp handlers --- core/adapters/udp/handlers/semtech.go | 15 +++++++-------- core/adapters/udp/mock_test.go | 3 ++- core/adapters/udp/udp.go | 6 ++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index bff33f507..6fbd20a05 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -22,12 +22,11 @@ import ( type Semtech struct{} // Handle implements the udp.Handler interface -func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg udp.MsgUDP) { +func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg udp.MsgUDP) error { pkt := new(semtech.Packet) err := pkt.UnmarshalBinary(msg.Data) if err != nil { - // TODO Log error - return + return errors.New(errors.Structural, err) } switch pkt.Identifier { @@ -39,8 +38,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u Identifier: semtech.PULL_ACK, }.MarshalBinary() if err != nil { - // TODO Log error - return + return errors.New(errors.Structural, err) } conn <- udp.MsgUDP{ Addr: msg.Addr, @@ -54,8 +52,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u Identifier: semtech.PUSH_ACK, }.MarshalBinary() if err != nil { - // TODO Log error - return + return errors.New(errors.Structural, err) } conn <- udp.MsgUDP{ Addr: msg.Addr, @@ -63,7 +60,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u } if pkt.Payload == nil { - return + return errors.New(errors.Structural, "Unable to process empty PUSH_DATA payload") } for _, rxpk := range pkt.Payload.RXPK { @@ -111,7 +108,9 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u }(rxpk) } default: + return errors.New(errors.Implementation, "Unhandled packet type") } + return nil } func rxpk2packet(p semtech.RXPK, gid []byte) (core.Packet, error) { diff --git a/core/adapters/udp/mock_test.go b/core/adapters/udp/mock_test.go index f62000047..c4a243ecf 100644 --- a/core/adapters/udp/mock_test.go +++ b/core/adapters/udp/mock_test.go @@ -12,7 +12,7 @@ type MockHandler struct { } // Handle implements the udp.Handler interface -func (h *MockHandler) Handle(conn chan<- MsgUDP, next chan<- MsgReq, msg MsgUDP) { +func (h *MockHandler) Handle(conn chan<- MsgUDP, next chan<- MsgReq, msg MsgUDP) error { h.InMsg = msg if h.OutMsgReq != nil { chresp := make(chan MsgRes) @@ -29,4 +29,5 @@ func (h *MockHandler) Handle(conn chan<- MsgUDP, next chan<- MsgReq, msg MsgUDP) Addr: msg.Addr, } } + return nil } diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 9d469772f..efb5ecf0b 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -24,7 +24,7 @@ type Adapter struct { // Handler represents a datagram and packet handler used by the adapter to process packets type Handler interface { // Handle handles incoming datagram from a gateway transmitter to the network - Handle(conn chan<- MsgUDP, chresp chan<- MsgReq, msg MsgUDP) + Handle(conn chan<- MsgUDP, chresp chan<- MsgReq, msg MsgUDP) error } // MsgUDP type materializes response messages transmitted towards existing recipients (commonly, gateways). @@ -154,7 +154,9 @@ func (a *Adapter) monitorHandlers(ready *sync.WaitGroup) { case MsgUDP: for _, h := range handlers { go func(h Handler, msg MsgUDP) { - h.Handle(a.conn, a.packets, msg) + if err := h.Handle(a.conn, a.packets, msg); err != nil { + a.ctx.WithError(err).Debug("Unable to handle request") + } }(h, msg.(MsgUDP)) } } From 2ea9992f34d224bfcfe40d66dfd73754eefbff43 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 11:05:58 +0100 Subject: [PATCH 0999/2266] Fix ENV parser --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 3ef3bc8a1..50b92c74e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -59,7 +59,7 @@ func initConfig() { viper.SetConfigName(".ttn") // name of config file (without extension) viper.AddConfigPath("$HOME") // adding home directory as first search path viper.SetEnvPrefix("ttn") // set environment prefix - viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() // read in environment variables that match viper.BindEnv("debug") From 8c1736bb41993982eaa4ca9806b56a31085d0449 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 11:25:07 +0100 Subject: [PATCH 1000/2266] [fix/paho-panic] Fix (maybe eventually ?) paho panic when disconnecting an already disconnected client --- .travis.yml | 2 +- core/adapters/mqtt/mqttClient.go | 11 +++++++++++ core/adapters/mqtt/mqtt_test.go | 8 ++++---- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index ce9594d63..8837bfa98 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,7 +19,7 @@ install: - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) before_script: - - mosquitto -p 1683 1>/dev/null 2>/dev/null & + - mosquitto -p 1883 1>/dev/null 2>/dev/null & script: - go list ./... | grep -vE 'integration|ttn$' | xargs go test diff --git a/core/adapters/mqtt/mqttClient.go b/core/adapters/mqtt/mqttClient.go index 7dc3c3ee1..d0013a51c 100644 --- a/core/adapters/mqtt/mqttClient.go +++ b/core/adapters/mqtt/mqttClient.go @@ -5,6 +5,7 @@ package mqtt import ( "fmt" + "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -49,3 +50,13 @@ func (c client) Subscribe(topic string, qos byte, callback func(c Client, m MQTT callback(client{c}, m) }) } + +// Disconnect implements the interface and ensure that the disconnection won't panic if already +// closed +func (c client) Disconnect(quiesce uint) { + go func() { + defer func() { recover() }() + c.Client.Disconnect(quiesce) + }() + <-time.After(time.Millisecond * time.Duration(quiesce)) +} diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 530c886ab..7108cc2f8 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -18,7 +18,7 @@ import ( "github.com/brocaar/lorawan" ) -const brokerURL = "0.0.0.0:1683" +const brokerURL = "0.0.0.0:1883" func TestMQTTSend(t *testing.T) { timeout = 100 * time.Millisecond @@ -147,11 +147,11 @@ func TestMQTTSend(t *testing.T) { checkResponses(t, test.WantResponse, resp) // Clean - go aclient.Disconnect(250) + aclient.Disconnect(200) for _, sclient := range sclients { - go sclient.Disconnect(250) + sclient.Disconnect(200) } - <-time.After(time.Millisecond * 400) + <-time.After(time.Millisecond * 100) } } From 8205f5af5c33542dc1044bb714fb31e3fe25baa2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 11:57:44 +0100 Subject: [PATCH 1001/2266] [test/semtech+mqtt] Test String() method in semtech package --- semtech/semtech.go | 4 +- semtech/semtech_test.go | 146 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 semtech/semtech_test.go diff --git a/semtech/semtech.go b/semtech/semtech.go index 58dc64b70..718dd4052 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -79,7 +79,7 @@ func (p *Packet) String() string { if p == nil { return "nil" } - header := fmt.Sprintf("Version:%x,Token:%x,Identifier:%x,GatewayId:%x", p.Version, p.Token, p.Identifier, p.GatewayId) + header := fmt.Sprintf("Version:%x,Token:%v,Identifier:%x,GatewayId:%v", p.Version, p.Token, p.Identifier, p.GatewayId) if p.Payload == nil { return fmt.Sprintf("Packet{%s}", header) } @@ -103,7 +103,7 @@ func (p *Packet) String() string { } } if rxpk != "" { - payload = fmt.Sprintf("%s,Rxpk:[%s]", payload, rxpk) + payload = fmt.Sprintf("%s,RXPK:[%s]", payload, rxpk) } return fmt.Sprintf("Packet{%s,Payload:{%s}}", header, payload) } diff --git a/semtech/semtech_test.go b/semtech/semtech_test.go new file mode 100644 index 000000000..e6f22d5bb --- /dev/null +++ b/semtech/semtech_test.go @@ -0,0 +1,146 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package semtech + +import ( + "fmt" + "strings" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/pointer" + testutil "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestString(t *testing.T) { + { + testutil.Desc(t, "No Payload") + + packet := Packet{ + Version: VERSION, + Token: []byte{1, 2}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + str := packet.String() + CheckStrings(t, "Packet", str) + CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) + CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) + CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) + CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) + } + + // -------------------- + + { + testutil.Desc(t, "With Stat Payload") + + packet := Packet{ + Version: VERSION, + Token: []byte{1, 2}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + Stat: &Stat{ + Ackr: pointer.Float64(3.4), + Alti: pointer.Int(14), + }, + }, + } + + str := packet.String() + CheckStrings(t, "Packet", str) + CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) + CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) + CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) + CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) + CheckStrings(t, "Payload", str) + CheckStrings(t, "Stat", str) + CheckStrings(t, "Ackr", str) + CheckStrings(t, "Alti", str) + } + + // -------------------- + + { + testutil.Desc(t, "With TXPK Payload") + + packet := Packet{ + Version: VERSION, + Token: []byte{1, 2}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + TXPK: &TXPK{ + Freq: pointer.Float64(883.445), + Codr: pointer.String("4/5"), + }, + }, + } + + str := packet.String() + CheckStrings(t, "Packet", str) + CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) + CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) + CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) + CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) + CheckStrings(t, "Payload", str) + CheckStrings(t, "TXPK", str) + CheckStrings(t, "Codr", str) + CheckStrings(t, "Freq", str) + } + + // -------------------- + + { + testutil.Desc(t, "With RXPK Payloads") + + packet := Packet{ + Version: VERSION, + Token: []byte{1, 2}, + Identifier: PUSH_DATA, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: &Payload{ + RXPK: []RXPK{ + { + Freq: pointer.Float64(883.445), + Codr: pointer.String("4/5"), + }, + { + Freq: pointer.Float64(883.445), + Codr: pointer.String("4/5"), + }, + }, + }, + } + + str := packet.String() + CheckStrings(t, "Packet", str) + CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) + CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) + CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) + CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) + CheckStrings(t, "Payload", str) + CheckStrings(t, "RXPK", str) + CheckStrings(t, "Codr", str) + CheckStrings(t, "Freq", str) + } + + // -------------------- + + { + testutil.Desc(t, "Nil payload") + + var packet *Packet + str := packet.String() + CheckStrings(t, "nil", str) + } +} + +func CheckStrings(t *testing.T, want string, got string) { + if !strings.Contains(got, want) { + testutil.Ko(t, "Expected %s to contain \"%s\"", got, want) + } + testutil.Ok(t, "Check Strings") +} From 3a5a8785f10bcd3eba91157bab0cc57a8e46a5ab Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 13:05:53 +0100 Subject: [PATCH 1002/2266] [test/semtech+mqtt] Improve test coverage of semtech packet --- semtech/decode_test.go | 27 ++++++++++++++++ semtech/encode_test.go | 32 +++++++++++++++++++ .../{check_utilities.go => helpers_test.go} | 0 3 files changed, 59 insertions(+) rename semtech/{check_utilities.go => helpers_test.go} (100%) diff --git a/semtech/decode_test.go b/semtech/decode_test.go index 3a0a93858..73a29d948 100644 --- a/semtech/decode_test.go +++ b/semtech/decode_test.go @@ -265,6 +265,21 @@ func TestUnmarshalBinary(t *testing.T) { }, WantError: false, }, + { + Desc: "PULL_RESP with datr only", + Header: []byte{1, 0, 0, PULL_RESP}, + JSON: `{"txpk":{"datr":"SF7BW125"}}`, + WantPacket: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Datr: pointer.String("SF7BW125"), + }, + }, + }, + WantError: false, + }, { Desc: "PULL_RESP with time only", Header: []byte{1, 0, 0, PULL_RESP}, @@ -312,6 +327,18 @@ func TestUnmarshalBinary(t *testing.T) { }, WantError: false, }, + { + Desc: "Unreckognized version", + Header: []byte{VERSION + 14, 1, 2, PUSH_DATA, 1, 4, 5, 6}, + JSON: `{}`, + WantError: true, + }, + { + Desc: "Unreckognized Identifier", + Header: []byte{VERSION, 1, 2, 178, 1, 4, 5, 6}, + JSON: `{}`, + WantError: true, + }, } for _, test := range tests { diff --git a/semtech/encode_test.go b/semtech/encode_test.go index 1d7210cf6..0ff79d7a7 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -334,6 +334,38 @@ func TestMarshalBinary(t *testing.T) { WantHeader: []byte{1, 0, 0, PULL_RESP}, WantJSON: `{"txpk":{"time":"2016-01-13T17:40:57.000000376Z"}}`, }, + { + Desc: "PULL_RESP with datr field and modu -> LORA", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Datr: pointer.String("SF7BW125"), + Modu: pointer.String("LORA"), + }, + }, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{"txpk":{"modu":"LORA","datr":"SF7BW125"}}`, + }, + { + Desc: "PULL_RESP with datr field and modu -> FSK", + Packet: Packet{ + Version: VERSION, + Identifier: PULL_RESP, + Payload: &Payload{ + TXPK: &TXPK{ + Datr: pointer.String("50000"), + Modu: pointer.String("FSK"), + }, + }, + }, + WantError: false, + WantHeader: []byte{1, 0, 0, PULL_RESP}, + WantJSON: `{"txpk":{"modu":"FSK","datr":50000}}`, + }, { Desc: "PULL_RESP empty payload", Packet: Packet{ diff --git a/semtech/check_utilities.go b/semtech/helpers_test.go similarity index 100% rename from semtech/check_utilities.go rename to semtech/helpers_test.go From 1c13b5b4711ba401b8470335a0b57f9162f901a8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 13:33:25 +0100 Subject: [PATCH 1003/2266] [test/semtech+mqtt] Move Mock MQTT client up to the mqtt package --- .../adapters/mqtt/handlers/activation_test.go | 20 +-- core/adapters/mqtt/handlers/downlink_test.go | 18 +- core/adapters/mqtt/handlers/helpers_test.go | 132 -------------- core/adapters/mqtt/handlers/mocks_test.go | 146 +++++++++++++++ core/adapters/mqtt/mocks_test.go | 168 ++++++++++++++++++ 5 files changed, 333 insertions(+), 151 deletions(-) create mode 100644 core/adapters/mqtt/handlers/mocks_test.go create mode 100644 core/adapters/mqtt/mocks_test.go diff --git a/core/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go index 5dbad6522..3b5383ec9 100644 --- a/core/adapters/mqtt/handlers/activation_test.go +++ b/core/adapters/mqtt/handlers/activation_test.go @@ -1,4 +1,4 @@ -// Copyright © 2016 The Things Network +// Copyright © 2016 T//e Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. package handlers @@ -35,7 +35,7 @@ func TestActivationTopic(t *testing.T) { func TestActivationHandle(t *testing.T) { tests := []struct { Desc string // The test's description - Client *testClient // An mqtt client to mock (or not) the behavior + Client *MockClient // An mqtt client to mock (or not) the behavior Topic string // The topic to which the message is addressed Payload []byte // The message's payload @@ -46,7 +46,7 @@ func TestActivationHandle(t *testing.T) { }{ { Desc: "Ok client | Valid Topic | Valid Payload", - Client: newTestClient(), + Client: NewMockClient(), Topic: "0101010101010101/devices/personalized/activations", Payload: []byte{ // DevEUI | NwkSKey | AppSKey 02, 02, 02, 02, @@ -67,7 +67,7 @@ func TestActivationHandle(t *testing.T) { }, { Desc: "Ok client | Invalid Topic #1 | Valid Payload", - Client: newTestClient(), + Client: NewMockClient(), Topic: "PleaseRegisterMyDevice", Payload: []byte{ // DevEUI | NwkSKey | AppSKey 02, 02, 02, 02, @@ -82,7 +82,7 @@ func TestActivationHandle(t *testing.T) { }, { Desc: "Ok client | Invalid Topic #2 | Valid Payload", - Client: newTestClient(), + Client: NewMockClient(), Topic: "0101010101010101/devices/0001020304050607/activations", Payload: []byte{ // DevEUI | NwkSKey | AppSKey 02, 02, 02, 02, @@ -97,7 +97,7 @@ func TestActivationHandle(t *testing.T) { }, { Desc: "Ok client | Invalid Topic #3 | Valid Payload", - Client: newTestClient(), + Client: NewMockClient(), Topic: "01010101/devices/personalized/activations", Payload: []byte{ // DevEUI | NwkSKey | AppSKey 02, 02, 02, 02, @@ -112,7 +112,7 @@ func TestActivationHandle(t *testing.T) { }, { Desc: "Ok client | Valid Topic | Invalid Payload #1", - Client: newTestClient(), + Client: NewMockClient(), Topic: "0101010101010101/devices/personalized/activations", Payload: []byte{ // DevEUI | NwkSKey | AppSKey 02, 02, 02, 02, @@ -128,7 +128,7 @@ func TestActivationHandle(t *testing.T) { }, { Desc: "Ok client | Valid Topic | Invalid Payload #2", - Client: newTestClient(), + Client: NewMockClient(), Topic: "0101010101010101/devices/personalized/activations", Payload: []byte{ // DevEUI | NwkSKey | AppSKey 02, 02, @@ -152,7 +152,7 @@ func TestActivationHandle(t *testing.T) { handler := Activation{} // Operate - err := handler.Handle(test.Client, chpkt, chreg, testMessage{ + err := handler.Handle(test.Client, chpkt, chreg, MockMessage{ payload: test.Payload, topic: test.Topic, }) @@ -160,7 +160,7 @@ func TestActivationHandle(t *testing.T) { // Check CheckErrors(t, test.WantError, err) - checkSubscriptions(t, test.WantSubscription, test.Client.Subscription) + checkSubscriptions(t, test.WantSubscription, test.Client.InSubscribe) checkRegistrations(t, test.WantRegistration, consumer.Registration) checkPackets(t, test.WantPacket, consumer.Packet) } diff --git a/core/adapters/mqtt/handlers/downlink_test.go b/core/adapters/mqtt/handlers/downlink_test.go index c865db0dd..65c668535 100644 --- a/core/adapters/mqtt/handlers/downlink_test.go +++ b/core/adapters/mqtt/handlers/downlink_test.go @@ -42,7 +42,7 @@ func TestDownlinkHandle(t *testing.T) { tests := []struct { Desc string // The test's description - Client *testClient // An mqtt client to mock (or not) the behavior + Client *MockClient // An mqtt client to mock (or not) the behavior Topic string // The topic to which the message is addressed Payload []byte // The message's payload @@ -54,7 +54,7 @@ func TestDownlinkHandle(t *testing.T) { { Desc: "Valid Payload | Valid Topic", - Client: newTestClient(), + Client: NewMockClient(), Payload: []byte{1, 2, 3, 4}, Topic: "0101010101010101/devices/0202020202020202/down", @@ -65,7 +65,7 @@ func TestDownlinkHandle(t *testing.T) { }, { Desc: "Valid Payload | Invalid Topic #2", - Client: newTestClient(), + Client: NewMockClient(), Payload: []byte{1, 2, 3, 4}, Topic: "0101010101010101/devices/0202020202020202/down/again", @@ -76,7 +76,7 @@ func TestDownlinkHandle(t *testing.T) { }, { Desc: "Valid Payload | Invalid Topic", - Client: newTestClient(), + Client: NewMockClient(), Payload: []byte{1, 2, 3, 4}, Topic: "0101010101010101/devices/0202020202020202", @@ -87,7 +87,7 @@ func TestDownlinkHandle(t *testing.T) { }, { Desc: "Valid Payload | Invalid AppEUI", - Client: newTestClient(), + Client: NewMockClient(), Payload: []byte{1, 2, 3, 4}, Topic: "010101/devices/0202020202020202/down", @@ -98,7 +98,7 @@ func TestDownlinkHandle(t *testing.T) { }, { Desc: "Valid Payload | Invalid DevEUI", - Client: newTestClient(), + Client: NewMockClient(), Payload: []byte{1, 2, 3, 4}, Topic: "0101010101010101/devices/020202/down", @@ -109,7 +109,7 @@ func TestDownlinkHandle(t *testing.T) { }, { Desc: "Invalid Payload | Valid Topic", - Client: newTestClient(), + Client: NewMockClient(), Payload: []byte{}, Topic: "0101010101010101/devices/0202020202020202/down", @@ -129,7 +129,7 @@ func TestDownlinkHandle(t *testing.T) { handler := Downlink{} // Operate - err := handler.Handle(test.Client, chpkt, chreg, testMessage{ + err := handler.Handle(test.Client, chpkt, chreg, MockMessage{ payload: test.Payload, topic: test.Topic, }) @@ -137,7 +137,7 @@ func TestDownlinkHandle(t *testing.T) { // Check CheckErrors(t, test.WantError, err) - checkSubscriptions(t, test.WantSubscription, test.Client.Subscription) + checkSubscriptions(t, test.WantSubscription, test.Client.InSubscribe) checkRegistrations(t, test.WantRegistration, consumer.Registration) checkPackets(t, test.WantPacket, consumer.Packet) } diff --git a/core/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go index ce7f01d3e..9cf001002 100644 --- a/core/adapters/mqtt/handlers/helpers_test.go +++ b/core/adapters/mqtt/handlers/helpers_test.go @@ -4,146 +4,14 @@ package handlers import ( - "fmt" "reflect" "testing" - "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) -// ----- TYPE utilities - -// testToken gives a fake implementation of MQTT.Token -// -// Provide a failure if you need to simulate an Error() result. -type testToken struct { - MQTT.Token - Failure *string -} - -func (t testToken) Wait() bool { - return true -} - -func (t testToken) WaitTimeout(d time.Duration) bool { - <-time.After(d) - return true -} - -func (t testToken) Error() error { - if t.Failure == nil { - return nil - } - return fmt.Errorf(*t.Failure) -} - -// testMessage gives a fake implementation of MQTT.Message -// -// provide payload and topic. Other methods are constants. -type testMessage struct { - payload interface{} - topic string -} - -func (m testMessage) Duplicate() bool { - return false -} -func (m testMessage) Qos() byte { - return 2 -} -func (m testMessage) Retained() bool { - return false -} -func (m testMessage) Topic() string { - return m.topic -} -func (m testMessage) MessageID() uint16 { - return 0 -} -func (m testMessage) Payload() []byte { - return m.payload.([]byte) -} - -// testClient gives a fake implementation of a MQTT.ClientInt -// -// It saves the last subscription, unsubscriptions and publication call made -// -// It can also fails on demand (use the newTestClient method to define which methods should fail) -type testClient struct { - Subscription *string - Unsubscriptions []string - Publication MQTT.Message - - failures map[string]*string - connected bool -} - -func newTestClient(failures ...string) *testClient { - client := testClient{failures: make(map[string]*string), connected: true} - - isFailure := func(x string) bool { - for _, f := range failures { - if f == x { - return true - } - } - return false - } - - if isFailure("Connect") { - client.failures["Connect"] = pointer.String("MockError -> Failed to connect") - } - - if isFailure("Publish") { - client.failures["Publish"] = pointer.String("MockError -> Failed to publish") - } - - if isFailure("Subscribe") { - client.failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") - } - - if isFailure("Unsubscribe") { - client.failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") - } - - return &client -} - -func (c *testClient) Connect() MQTT.Token { - c.connected = true - return testToken{Failure: c.failures["Connect"]} -} - -func (c *testClient) Disconnect(quiesce uint) { - <-time.After(time.Duration(quiesce)) - c.connected = false - return -} - -func (c testClient) IsConnected() bool { - return c.connected -} - -func (c *testClient) Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token { - c.Publication = testMessage{payload: payload, topic: topic} - return testToken{Failure: c.failures["Publish"]} -} - -func (c *testClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { - c.Subscription = &topic - return testToken{Failure: c.failures["Subscribe"]} -} - -func (c *testClient) Unsubscribe(topics ...string) MQTT.Token { - c.Unsubscriptions = topics - return testToken{Failure: c.failures["Unsubscribe"]} -} - // testConsumer generates a component which consumes messages from two channels and make the last // result available diff --git a/core/adapters/mqtt/handlers/mocks_test.go b/core/adapters/mqtt/handlers/mocks_test.go new file mode 100644 index 000000000..60c551b62 --- /dev/null +++ b/core/adapters/mqtt/handlers/mocks_test.go @@ -0,0 +1,146 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handlers + +import ( + "fmt" + "time" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/utils/pointer" +) + +// ----- TYPE utilities + +// testToken gives a fake implementation of MQTT.Token +// +// Provide a failure if you need to simulate an Error() result. +type MockToken struct { + MQTT.Token + Failure *string +} + +func (t MockToken) Wait() bool { + return true +} + +func (t MockToken) WaitTimeout(d time.Duration) bool { + <-time.After(d) + return true +} + +func (t MockToken) Error() error { + if t.Failure == nil { + return nil + } + return fmt.Errorf(*t.Failure) +} + +// MockMessage gives a fake implementation of MQTT.Message +// +// provide payload and topic. Other methods are constants. +type MockMessage struct { + payload interface{} + topic string +} + +func (m MockMessage) Duplicate() bool { + return false +} +func (m MockMessage) Qos() byte { + return 2 +} +func (m MockMessage) Retained() bool { + return false +} +func (m MockMessage) Topic() string { + return m.topic +} +func (m MockMessage) MessageID() uint16 { + return 0 +} +func (m MockMessage) Payload() []byte { + switch m.payload.(type) { + case []byte: + return m.payload.([]byte) + default: + return nil + } +} + +// MockClient gives a fake implementation of a MQTT.ClientInt +// +// It saves the last subscription, unsubscriptions and publication call made +// +// It can also fails on demand (use the newMockClient method to define which methods should fail) +type MockClient struct { + InSubscribe *string + InPublish MQTT.Message + InUnsubscribe []string + + Failures map[string]*string + connected bool +} + +func NewMockClient(failures ...string) *MockClient { + client := MockClient{Failures: make(map[string]*string), connected: true, InPublish: MockMessage{}} + + isFailure := func(x string) bool { + for _, f := range failures { + if f == x { + return true + } + } + return false + } + + if isFailure("Connect") { + client.Failures["Connect"] = pointer.String("MockError -> Failed to connect") + } + + if isFailure("Publish") { + client.Failures["Publish"] = pointer.String("MockError -> Failed to publish") + } + + if isFailure("Subscribe") { + client.Failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") + } + + if isFailure("Unsubscribe") { + client.Failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") + } + + return &client +} + +func (c *MockClient) Connect() MQTT.Token { + c.connected = true + return MockToken{Failure: c.Failures["Connect"]} +} + +func (c *MockClient) Disconnect(quiesce uint) { + <-time.After(time.Duration(quiesce)) + c.connected = false + return +} + +func (c MockClient) IsConnected() bool { + return c.connected +} + +func (c *MockClient) Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token { + c.InPublish = MockMessage{payload: payload, topic: topic} + return MockToken{Failure: c.Failures["Publish"]} +} + +func (c *MockClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { + c.InSubscribe = &topic + return MockToken{Failure: c.Failures["Subscribe"]} +} + +func (c *MockClient) Unsubscribe(topics ...string) MQTT.Token { + c.InUnsubscribe = topics + return MockToken{Failure: c.Failures["Unsubscribe"]} +} diff --git a/core/adapters/mqtt/mocks_test.go b/core/adapters/mqtt/mocks_test.go new file mode 100644 index 000000000..cf84f20ff --- /dev/null +++ b/core/adapters/mqtt/mocks_test.go @@ -0,0 +1,168 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +// ----- TYPE utilities + +// testToken gives a fake implementation of MQTT.Token +// +// Provide a failure if you need to simulate an Error() result. +type MockToken struct { + MQTT.Token + Failure *string +} + +func (t MockToken) Wait() bool { + return true +} + +func (t MockToken) WaitTimeout(d time.Duration) bool { + <-time.After(d) + return true +} + +func (t MockToken) Error() error { + if t.Failure == nil { + return nil + } + return fmt.Errorf(*t.Failure) +} + +// MockMessage gives a fake implementation of MQTT.Message +// +// provide payload and topic. Other methods are constants. +type MockMessage struct { + payload interface{} + topic string +} + +func (m MockMessage) Duplicate() bool { + return false +} +func (m MockMessage) Qos() byte { + return 2 +} +func (m MockMessage) Retained() bool { + return false +} +func (m MockMessage) Topic() string { + return m.topic +} +func (m MockMessage) MessageID() uint16 { + return 0 +} +func (m MockMessage) Payload() []byte { + switch m.payload.(type) { + case []byte: + return m.payload.([]byte) + default: + return nil + } +} + +// MockClient gives a fake implementation of a MQTT.ClientInt +// +// It saves the last subscription, unsubscriptions and publication call made +// +// It can also fails on demand (use the newMockClient method to define which methods should fail) +type MockClient struct { + InSubscribe *string + InPublish MQTT.Message + InUnsubscribe []string + + Failures map[string]*string + connected bool +} + +func NewMockClient(failures ...string) *MockClient { + client := MockClient{Failures: make(map[string]*string), connected: true, InPublish: MockMessage{}} + + isFailure := func(x string) bool { + for _, f := range failures { + if f == x { + return true + } + } + return false + } + + if isFailure("Connect") { + client.Failures["Connect"] = pointer.String("MockError -> Failed to connect") + } + + if isFailure("Publish") { + client.Failures["Publish"] = pointer.String("MockError -> Failed to publish") + } + + if isFailure("Subscribe") { + client.Failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") + } + + if isFailure("Unsubscribe") { + client.Failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") + } + + return &client +} + +func (c *MockClient) Connect() MQTT.Token { + c.connected = true + return MockToken{Failure: c.Failures["Connect"]} +} + +func (c *MockClient) Disconnect(quiesce uint) { + <-time.After(time.Duration(quiesce)) + c.connected = false + return +} + +func (c MockClient) IsConnected() bool { + return c.connected +} + +func (c *MockClient) Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token { + c.InPublish = MockMessage{payload: payload, topic: topic} + return MockToken{Failure: c.Failures["Publish"]} +} + +func (c *MockClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { + c.InSubscribe = &topic + return MockToken{Failure: c.Failures["Subscribe"]} +} + +func (c *MockClient) Unsubscribe(topics ...string) MQTT.Token { + c.InUnsubscribe = topics + return MockToken{Failure: c.Failures["Unsubscribe"]} +} + +// ----- CHECK utilities + +func checkSubscriptions(t *testing.T, want *string, got *string) { + if got == nil { + if want == nil { + Ok(t, "Check Subscriptions") + return + } + Ko(t, "Expected subscription to be %s but got nothing", *want) + } + if want == nil { + Ko(t, "Expected no subscription but got %v", *got) + } + + if *want != *got { + Ko(t, "Expected subscription to be %s but got %s", *want, *got) + } + + Ok(t, "Check Subscriptions") +} From 0482a8d80f1dffc1298af70de8a276beb1d54d88 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 14:38:25 +0100 Subject: [PATCH 1004/2266] [test/semtech+mqtt] Enhance test coverage of mqtt methods + fix --- core/adapters/mqtt/mocks_test.go | 33 ++++- core/adapters/mqtt/mqtt.go | 2 + core/adapters/mqtt/mqtt_test.go | 234 +++++++++++++++++++++++++++++-- 3 files changed, 251 insertions(+), 18 deletions(-) diff --git a/core/adapters/mqtt/mocks_test.go b/core/adapters/mqtt/mocks_test.go index cf84f20ff..6bebe9c79 100644 --- a/core/adapters/mqtt/mocks_test.go +++ b/core/adapters/mqtt/mocks_test.go @@ -15,7 +15,30 @@ import ( // ----- TYPE utilities -// testToken gives a fake implementation of MQTT.Token +// MockHandler provides a fake implementation of a mqtt.Handler +type MockHandler struct { + Failures map[string]error + OutTopic string + InMessage MQTT.Message +} + +func NewMockHandler() *MockHandler { + return &MockHandler{ + Failures: make(map[string]error), + OutTopic: "MockTopic", + } +} + +func (h *MockHandler) Topic() string { + return h.OutTopic +} + +func (h *MockHandler) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { + h.InMessage = msg + return h.Failures["Handle"] +} + +// MockToken gives a fake implementation of MQTT.Token // // Provide a failure if you need to simulate an Error() result. type MockToken struct { @@ -77,9 +100,10 @@ func (m MockMessage) Payload() []byte { // // It can also fails on demand (use the newMockClient method to define which methods should fail) type MockClient struct { - InSubscribe *string - InPublish MQTT.Message - InUnsubscribe []string + InSubscribe *string + InPublish MQTT.Message + InUnsubscribe []string + InSubscribeCallBack func(c Client, m MQTT.Message) Failures map[string]*string connected bool @@ -138,6 +162,7 @@ func (c *MockClient) Publish(topic string, qos byte, retained bool, payload inte func (c *MockClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { c.InSubscribe = &topic + c.InSubscribeCallBack = callback return MockToken{Failure: c.Failures["Subscribe"]} } diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 4d49bf9fc..389928d9a 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -111,6 +111,8 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err if token.Wait() && token.Error() != nil { err := errors.New(errors.Operational, "Unable to subscribe to down topic") a.ctx.WithField("recipient", recipient).Warn(err.Error()) + wg.Done() + wg.Done() cherr <- err close(chdown) continue diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 7108cc2f8..6ad1ae1de 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -5,12 +5,12 @@ package mqtt import ( "fmt" - "reflect" "testing" "time" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -47,6 +47,21 @@ func TestMQTTSend(t *testing.T) { WantResponse: nil, WantError: nil, }, + { + Desc: "1 packet | 1 recipient | No down topic", + Packet: []byte("TheThingsNetwork"), + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up1", + TopicDown: "", + }, + }, + + WantData: []byte("TheThingsNetwork"), + WantResponse: nil, + WantError: nil, + }, { Desc: "invalid packet | 1 recipient | No response", Packet: nil, @@ -147,11 +162,202 @@ func TestMQTTSend(t *testing.T) { checkResponses(t, test.WantResponse, resp) // Clean - aclient.Disconnect(200) + <-time.After(time.Millisecond * 500) + aclient.Disconnect(0) for _, sclient := range sclients { - sclient.Disconnect(200) + sclient.Disconnect(0) + } + } +} + +func TestSendErrorCases(t *testing.T) { + tests := []struct { + Desc string // Test Description + Packet []byte // Handy representation of the packet to send + Recipients []testRecipient // List of recipient to send + Client *MockClient // A mocked version of the client + + WantData []byte // Expected Data on the recipient + WantError *string // Expected error nature returned by the Send method + }{ + { + Desc: "1 packet | 1 Recipient | Error on publish", + Packet: []byte("TheThingsNetwork"), + Client: NewMockClient("Publish"), + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up", + TopicDown: "down", + }, + }, + + WantData: []byte("TheThingsNetwork"), + WantError: pointer.String(string(errors.Operational)), + }, + { + Desc: "1 packet | 1 Recipient | Error on Subscribe", + Packet: []byte("TheThingsNetwork"), + Client: NewMockClient("Subscribe"), + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up", + TopicDown: "down", + }, + }, + + WantData: nil, + WantError: pointer.String(string(errors.Operational)), + }, + { + Desc: "1 packet | 1 Recipient | Error on Unsubscribe", + Packet: []byte("TheThingsNetwork"), + Client: NewMockClient("Unsubscribe"), + Recipients: []testRecipient{ + { + Response: nil, + TopicUp: "up", + TopicDown: "down", + }, + }, + + WantData: []byte("TheThingsNetwork"), + WantError: nil, + }, + } + + for i, test := range tests { + // Describe + Desc(t, fmt.Sprintf("#%d: %s", i, test.Desc)) + + // Build + adapter := NewAdapter(test.Client, GetLogger(t, "Adapter")) + + // Operate + _, err := trySend(adapter, test.Packet, test.Recipients) + + // Check + CheckErrors(t, test.WantError, err) + checkData(t, test.WantData, test.Client.InPublish.Payload()) + } +} + +func TestOtherMethods(t *testing.T) { + { + // Describe + Desc(t, "Get Recipient | Wrong data") + + // Build + adapter := NewAdapter(NewMockClient(), GetLogger(t, "Adapter")) + + // Operate + _, err := adapter.GetRecipient([]byte{}) + + // Check + CheckErrors(t, pointer.String(string(errors.Structural)), err) + } + + // -------------------- + + { + // Describe + Desc(t, "Get Recipient | Valid Recipient") + + // Build + adapter := NewAdapter(NewMockClient(), GetLogger(t, "Adapter")) + data, _ := NewRecipient("up", "down").MarshalBinary() + + // Operate + recipient, err := adapter.GetRecipient(data) + + // Check + CheckErrors(t, nil, err) + checkRecipients(t, NewRecipient("up", "down"), recipient) + + } + + // -------------------- + + { + // Describe + Desc(t, "Send invalid recipients") + + // Build + adapter := NewAdapter(NewMockClient(), GetLogger(t, "Adapter")) + + // Operate + _, err := adapter.Send(mocks.NewMockPacket(), mocks.NewMockRecipient()) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // -------------------- + + { + // Describe + Desc(t, "Bind a new handler") + + // Build + client := NewMockClient() + adapter := NewAdapter(client, GetLogger(t, "Adapter")) + handler := NewMockHandler() + msg := MockMessage{ + topic: "MessageTopic", + payload: []byte{1, 2, 3, 4}, } - <-time.After(time.Millisecond * 100) + + // Operate + err := adapter.Bind(handler) + client.InSubscribeCallBack(client, msg) + + // Check + CheckErrors(t, nil, err) + checkMessages(t, msg, handler.InMessage) + } + + // -------------------- + + { + // Describe + Desc(t, "Bind a new handler | fails to handle") + + // Build + client := NewMockClient() + adapter := NewAdapter(client, GetLogger(t, "Adapter")) + handler := NewMockHandler() + handler.Failures["Handle"] = errors.New(errors.Operational, "Mock Error") + msg := MockMessage{ + topic: "MessageTopic", + payload: []byte{1, 2, 3, 4}, + } + + // Operate + err := adapter.Bind(handler) + client.InSubscribeCallBack(client, msg) + + // Check + CheckErrors(t, nil, err) + checkMessages(t, msg, handler.InMessage) + } + + // -------------------- + + { + // Describe + Desc(t, "Bind a new handler | fails to subscribe") + + // Build + client := NewMockClient("Subscribe") + adapter := NewAdapter(client, GetLogger(t, "Adapter")) + handler := NewMockHandler() + + // Operate + err := adapter.Bind(handler) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) } } @@ -293,17 +499,17 @@ func trySend(adapter core.Adapter, packet []byte, recipients []testRecipient) ([ // ----- CHECK utilities func checkResponses(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check responses") - return - } - Ko(t, "Received response does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) + mocks.Check(t, want, got, "Responses") } func checkData(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check data") - return - } - Ko(t, "Received data does not match expectations.\nWant: %s\nGot: %s", string(want), string(got)) + mocks.Check(t, want, got, "Data") +} + +func checkRecipients(t *testing.T, want core.Recipient, got core.Recipient) { + mocks.Check(t, want, got, "Recipients") +} + +func checkMessages(t *testing.T, want MQTT.Message, got MQTT.Message) { + mocks.Check(t, want, got, "Messages") } From 7d879f47cdd129efd5cab177f61a4a6651471953 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 14:47:19 +0100 Subject: [PATCH 1005/2266] [test/semtech+mqtt] Add tests for mqttAckNacker --- core/adapters/mqtt/mqttAckNacker.go | 5 +- core/adapters/mqtt/mqttAckNacker_test.go | 163 +++++++++++++++++++++++ 2 files changed, 164 insertions(+), 4 deletions(-) create mode 100644 core/adapters/mqtt/mqttAckNacker_test.go diff --git a/core/adapters/mqtt/mqttAckNacker.go b/core/adapters/mqtt/mqttAckNacker.go index 5888808af..ae27b3749 100644 --- a/core/adapters/mqtt/mqttAckNacker.go +++ b/core/adapters/mqtt/mqttAckNacker.go @@ -17,12 +17,9 @@ type mqttAckNacker struct { // Ack implements the core.AckNacker interface func (an mqttAckNacker) Ack(p core.Packet) error { - if an.Chresp == nil && p == nil { + if an.Chresp == nil || p == nil { return nil } - if an.Chresp == nil && p != nil { - return errors.New(errors.Structural, "Unable to send any packet through this acknacker") - } defer close(an.Chresp) if p == nil { diff --git a/core/adapters/mqtt/mqttAckNacker_test.go b/core/adapters/mqtt/mqttAckNacker_test.go new file mode 100644 index 000000000..67bdd35c1 --- /dev/null +++ b/core/adapters/mqtt/mqttAckNacker_test.go @@ -0,0 +1,163 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestAckNacker(t *testing.T) { + { + Desc(t, "Ack a nil packet") + + // Build + chresp := make(chan MsgRes, 1) + an := mqttAckNacker{Chresp: chresp} + + // Operate + err := an.Ack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, chresp) + } + + // -------------------- + + { + Desc(t, "Ack on a nil chresp") + + // Build + an := mqttAckNacker{Chresp: nil} + + // Operate + err := an.Ack(mocks.NewMockPacket()) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, nil) + } + + // -------------------- + + { + Desc(t, "Ack a valid packet") + + // Build + chresp := make(chan MsgRes, 1) + an := mqttAckNacker{Chresp: chresp} + p := mocks.NewMockPacket() + p.OutMarshalBinary = []byte{14, 14, 14} + + // Operate + err := an.Ack(p) + + // Expectation + want := MsgRes(p.OutMarshalBinary) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, &want, chresp) + } + + // -------------------- + + { + Desc(t, "Ack an invalid packet") + + // Build + chresp := make(chan MsgRes, 1) + an := mqttAckNacker{Chresp: chresp} + p := mocks.NewMockPacket() + p.Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error") + + // Operate + err := an.Ack(p) + + // Check + errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckResps(t, nil, chresp) + } + + // -------------------- + + { + Desc(t, "Don't consume chresp on Ack") + + // Build + chresp := make(chan MsgRes) + an := mqttAckNacker{Chresp: chresp} + + // Operate + cherr := make(chan error) + go func() { + cherr <- an.Ack(mocks.NewMockPacket()) + }() + + // Check + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 100): + } + errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) + } + + // -------------------- + + { + Desc(t, "Nack no error") + + // Build + chresp := make(chan MsgRes, 1) + an := mqttAckNacker{Chresp: chresp} + + // Operate + err := an.Nack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, chresp) + } + + // -------------------- + + { + Desc(t, "Nack on a nil chresp") + + // Build + an := mqttAckNacker{Chresp: nil} + + // Operate + err := an.Nack(nil) + + // Check + errutil.CheckErrors(t, nil, err) + CheckResps(t, nil, nil) + } +} + +func CheckResps(t *testing.T, want *MsgRes, got chan MsgRes) { + if want == nil { + if len(got) == 0 { + Ok(t, "Check Resps") + return + } + Ko(t, "Expected no message response but got one") + } + + if len(got) < 1 { + Ko(t, "Expected one message but got none") + } + + msg := <-got + mocks.Check(t, *want, msg, "Resps") +} From 33daacbb4502e466844ef6f45951c1884d8e0861 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 15:24:41 +0100 Subject: [PATCH 1006/2266] [issues/#45+#49] Split HandleUp router's method --- core/components/router/router.go | 217 ++++++++++++++++--------------- 1 file changed, 110 insertions(+), 107 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 01a0baa2b..d450b92da 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -49,145 +49,148 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { r.ctx.Debug("Handling uplink packet") // Extract the given packet - itf, err := UnmarshalPacket(data) - if err != nil { - stats.MarkMeter("router.uplink.invalid") - r.ctx.Warn("Uplink Invalid") - return errors.New(errors.Structural, err) - } - + itf, _ := UnmarshalPacket(data) switch itf.(type) { case RPacket: - packet := itf.(RPacket) - - // Lookup for an existing broker - entries, err := r.Lookup(packet.DevEUI()) - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - r.ctx.Warn("Database lookup failed") - return errors.New(errors.Operational, err) - } - shouldBroadcast := err != nil + ack, err = r.handleDataUp(itf.(RPacket), up) + case SPacket: + default: + stats.MarkMeter("router.uplink.invalid") + err = errors.New(errors.Structural, "Unreckognized packet type") + } - // Add Gateway duty metadata - // TODO add gateway location - metadata := packet.Metadata() - if metadata.Freq == nil { - return errors.New(errors.Structural, "Missing mandatory frequency in metadata") - } + if err != nil { + r.ctx.WithError(err).Debug("Unable to process uplink") + } + return err +} - cycles, err := r.Manager.Lookup(packet.GatewayID()) - if err != nil { - r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") - cycles = make(dutycycle.Cycles) - } +// handleDataUp handle an upcoming message which carries a data frame payload +func (r component) handleDataUp(packet RPacket, up Adapter) (Packet, error) { + // Lookup for an existing broker + entries, err := r.Lookup(packet.DevEUI()) + if err != nil && err.(errors.Failure).Nature != errors.NotFound { + r.ctx.Warn("Database lookup failed") + return nil, errors.New(errors.Operational, err) + } + shouldBroadcast := err != nil - sb1, err := dutycycle.GetSubBand(*metadata.Freq) - if err != nil { - return errors.New(errors.Structural, "Unhandled uplink signal frequency") - } + // Add Gateway duty metadata + // TODO add gateway location + metadata := packet.Metadata() + if metadata.Freq == nil { + return nil, errors.New(errors.Structural, "Missing mandatory frequency in metadata") + } - rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) - metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 + cycles, err := r.Manager.Lookup(packet.GatewayID()) + if err != nil { + r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") + cycles = make(dutycycle.Cycles) + } - bpacket, err := NewBPacket(packet.Payload(), metadata) - if err != nil { - r.ctx.WithError(err).Warn("Unable to create router packet") - return errors.New(errors.Structural, err) - } + sb1, err := dutycycle.GetSubBand(*metadata.Freq) + if err != nil { + return nil, errors.New(errors.Structural, "Unhandled uplink signal frequency") + } - // Send packet to broker(s) - var response []byte - if shouldBroadcast { - // No Recipient available -> broadcast - response, err = up.Send(bpacket) - } else { - // Recipients are available - var recipients []Recipient - for _, e := range entries { - // Get the actual broker - recipient, err := up.GetRecipient(e.Recipient) - if err != nil { - r.ctx.Warn("Unable to retrieve Recipient") - return errors.New(errors.Structural, err) - } - recipients = append(recipients, recipient) - } + rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) + metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 - // Send the packet - response, err = up.Send(bpacket, recipients...) - if err != nil && err.(errors.Failure).Nature == errors.NotFound { - // Might be a collision with the dev addr, we better broadcast - response, err = up.Send(bpacket) - } - } + bpacket, err := NewBPacket(packet.Payload(), metadata) + if err != nil { + r.ctx.WithError(err).Warn("Unable to create router packet") + return nil, errors.New(errors.Structural, err) + } - if err != nil { - switch err.(errors.Failure).Nature { - case errors.NotFound: - stats.MarkMeter("router.uplink.negative_broker_response") - r.ctx.WithError(err).Debug("Negative response from Broker") - default: - stats.MarkMeter("router.uplink.bad_broker_response") - r.ctx.WithError(err).Warn("Invalid response from Broker") + // Send packet to broker(s) + var response []byte + if shouldBroadcast { + // No Recipient available -> broadcast + response, err = up.Send(bpacket) + } else { + // Recipients are available + var recipients []Recipient + for _, e := range entries { + // Get the actual broker + recipient, err := up.GetRecipient(e.Recipient) + if err != nil { + r.ctx.Warn("Unable to retrieve Recipient") + return nil, errors.New(errors.Structural, err) } - return err + recipients = append(recipients, recipient) } - // No response, stop there - if response == nil { - return nil + // Send the packet + response, err = up.Send(bpacket, recipients...) + if err != nil && err.(errors.Failure).Nature == errors.NotFound { + // Might be a collision with the dev addr, we better broadcast + response, err = up.Send(bpacket) } + } - itf, err := UnmarshalPacket(response) - if err != nil { + if err != nil { + switch err.(errors.Failure).Nature { + case errors.NotFound: + stats.MarkMeter("router.uplink.negative_broker_response") + default: stats.MarkMeter("router.uplink.bad_broker_response") - r.ctx.WithError(err).Warn("Invalid response from Broker") - return errors.New(errors.Operational, err) } + return nil, err + } - switch itf.(type) { - case RPacket: - // Update downlink metadata for the related gateway - metadata := itf.(RPacket).Metadata() - freq := metadata.Freq - datr := metadata.Datr - codr := metadata.Codr - size := metadata.Size - - if freq == nil || datr == nil || codr == nil || size == nil { - err := errors.New(errors.Operational, "Missing mandatory metadata in response") - stats.MarkMeter("router.uplink.bad_broker_response") - r.ctx.WithError(err).Warn("Invalid response from Broker") - return err - } + return r.handleDataDown(response, packet.GatewayID()) +} - if err := r.Manager.Update(packet.GatewayID(), *freq, *size, *datr, *codr); err != nil { - r.ctx.WithError(err).Warn("Unable to update duty cycle") - return errors.New(errors.Operational, err) - } +// handleDataDown controls that data received from an uplink are okay. +// It also updates metadata about the related gateway +func (r component) handleDataDown(data []byte, gatewayID []byte) (Packet, error) { + if data == nil { + return nil, nil + } - // Finally, define the ack to be sent - ack = itf.(RPacket) - default: - return errors.New(errors.Implementation, "Unexpected packet type") + itf, err := UnmarshalPacket(data) + if err != nil { + stats.MarkMeter("router.uplink.bad_broker_response") + return nil, errors.New(errors.Operational, err) + } + switch itf.(type) { + case RPacket: + // Update downlink metadata for the related gateway + metadata := itf.(RPacket).Metadata() + freq := metadata.Freq + datr := metadata.Datr + codr := metadata.Codr + size := metadata.Size + + if freq == nil || datr == nil || codr == nil || size == nil { + stats.MarkMeter("router.uplink.bad_broker_response") + return nil, errors.New(errors.Operational, "Missing mandatory metadata in response") } - stats.MarkMeter("router.uplink.ok") - case SPacket: - return errors.New(errors.Implementation, "Stats packet not yet implemented") - case JPacket: - return errors.New(errors.Implementation, "Join Request not yet implemented") + if err := r.Manager.Update(gatewayID, *freq, *size, *datr, *codr); err != nil { + return nil, errors.New(errors.Operational, err) + } + + // Finally, define the ack to be sent + return itf.(RPacket), nil default: - return errors.New(errors.Implementation, "Unreckognized packet type") + stats.MarkMeter("router.uplink.bad_broker_response") + return nil, errors.New(errors.Implementation, "Unexpected packet type") } +} +// HandleStat implements the core.Router interface +func (r component) handleStat(packet SPacket, gatewayID []byte) error { return nil } +// ensureAckNack is used to make sure we Ack / Nack correctly in the HandleUp method. +// The method will probably change or be moved outside the router itself. func ensureAckNack(an AckNacker, ack *Packet, err *error) { if err != nil && *err != nil { an.Nack(*err) } else { + stats.MarkMeter("router.uplink.ok") var p Packet if ack != nil { p = *ack From 0642b456433f62802a46768d92fe1f8f0f0cda0f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 12:11:53 +0100 Subject: [PATCH 1007/2266] Don't collect stats when disabled --- cmd/broker.go | 2 ++ cmd/handler.go | 2 ++ cmd/router.go | 2 ++ utils/stats/stats.go | 19 +++++++++++++++---- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 6d149bcc9..341ad6d5b 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" "github.com/TheThingsNetwork/ttn/core/components/broker" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -32,6 +33,7 @@ and personalized devices (with their network session keys) with the router. statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) } else { statusServer = "disabled" + stats.Enabled = false } ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), diff --git a/cmd/handler.go b/cmd/handler.go index eeffc6fec..cefdb8937 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -14,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" mqttHandlers "github.com/TheThingsNetwork/ttn/core/adapters/mqtt/handlers" "github.com/TheThingsNetwork/ttn/core/components/handler" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -32,6 +33,7 @@ The default handler is the bridge between The Things Network and applications. statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) } else { statusServer = "disabled" + stats.Enabled = false } ctx.WithFields(log.Fields{ "devicesDatabase": viper.GetString("handler.dev-database"), diff --git a/cmd/router.go b/cmd/router.go index 226a54eec..5e5157768 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -16,6 +16,7 @@ import ( udpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/udp/handlers" "github.com/TheThingsNetwork/ttn/core/components/router" "github.com/TheThingsNetwork/ttn/core/dutycycle" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -35,6 +36,7 @@ the gateway's duty cycle is (almost) full.`, statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) } else { statusServer = "disabled" + stats.Enabled = false } ctx.WithFields(log.Fields{ diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 721ebe8f4..6253109cb 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -6,22 +6,33 @@ package stats import "github.com/rcrowley/go-metrics" +// Enabled activates stats collection +var Enabled = true + // MarkMeter registers an event func MarkMeter(name string) { - metrics.GetOrRegisterMeter(name, metrics.DefaultRegistry).Mark(1) + if Enabled { + metrics.GetOrRegisterMeter(name, metrics.DefaultRegistry).Mark(1) + } } // UpdateHistogram registers a new value for a histogram func UpdateHistogram(name string, value int64) { - metrics.GetOrRegisterHistogram(name, metrics.DefaultRegistry, metrics.NewUniformSample(1000)).Update(value) + if Enabled { + metrics.GetOrRegisterHistogram(name, metrics.DefaultRegistry, metrics.NewUniformSample(1000)).Update(value) + } } // IncCounter increments a counter by 1 func IncCounter(name string) { - metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Inc(1) + if Enabled { + metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Inc(1) + } } // DecCounter decrements a counter by 1 func DecCounter(name string) { - metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Dec(1) + if Enabled { + metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Dec(1) + } } From ba6a00cbd6e2622fdb180ef648ed542bb38afc2f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:27:43 +0100 Subject: [PATCH 1008/2266] Store strings in the metrics collector - Add string type - Create custom Registry - That supports TTLs --- core/adapters/http/handlers/statuspage.go | 8 +- .../adapters/http/handlers/statuspage_test.go | 3 + utils/stats/registry.go | 159 ++++++++++++++++++ utils/stats/stats.go | 20 ++- utils/stats/string.go | 107 ++++++++++++ 5 files changed, 292 insertions(+), 5 deletions(-) create mode 100644 utils/stats/registry.go create mode 100644 utils/stats/string.go diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/handlers/statuspage.go index 962efa404..716d42b5e 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/handlers/statuspage.go @@ -10,6 +10,7 @@ import ( . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/rcrowley/go-metrics" ) @@ -39,7 +40,7 @@ func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg cha allStats := make(map[string]interface{}) - metrics.Each(func(name string, i interface{}) { + stats.Registry.Each(func(name string, i interface{}) { // Make sure we put things in the right place thisStat := allStats for _, path := range strings.Split(name, ".") { @@ -78,6 +79,11 @@ func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg cha thisStat["rate_15"] = m.Rate15() thisStat["count"] = m.Count() + case stats.String: + m := metric.Snapshot() + for t, v := range m.Get() { + thisStat[t] = v + } } }) diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/handlers/statuspage_test.go index 5ef2235ba..e186cb09f 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/handlers/statuspage_test.go @@ -45,6 +45,7 @@ func TestStatusPageHandler(t *testing.T) { stats.IncCounter("this.is-a-counter") stats.UpdateHistogram("and.this.is.a-histogram", 123) stats.MarkMeter("and.this.is.a-meter") + stats.SetString("and.this.is.a-string", "with_key", "with_value") // Not Empty anymore r4, _ := http.NewRequest("GET", "/status", nil) @@ -55,4 +56,6 @@ func TestStatusPageHandler(t *testing.T) { a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "\"is-a-counter\"") a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "p_50") a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "rate_15") + a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "with_key") + a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "with_value") } diff --git a/utils/stats/registry.go b/utils/stats/registry.go new file mode 100644 index 000000000..719c31513 --- /dev/null +++ b/utils/stats/registry.go @@ -0,0 +1,159 @@ +package stats + +import ( + "fmt" + "reflect" + "sync" + + "github.com/rcrowley/go-metrics" +) + +// Ticker has a Tick() func +type Ticker interface { + Tick() + SetDefaultTTL(uint) + SetTTL(string, uint) +} + +// Registry is the default metrics registry +var Registry = NewRegistry() + +// TTNRegistry is a mutex-protected map of names to metrics. +// It is basically an extension of https://github.com/rcrowley/go-metrics/blob/master/registry.go +type TTNRegistry struct { + metrics map[string]interface{} + timeouts map[string]uint + defaultTTL uint + mutex sync.Mutex +} + +// NewRegistry reates a new registry and starts a goroutine for the TTL. +func NewRegistry() metrics.Registry { + return &TTNRegistry{ + metrics: make(map[string]interface{}), + timeouts: make(map[string]uint), + defaultTTL: 0, + } +} + +// Each calls the given function for each registered metric. +func (r *TTNRegistry) Each(f func(string, interface{})) { + for name, i := range r.registered() { + f(name, i) + } +} + +// Get the metric by the given name or nil if none is registered. +func (r *TTNRegistry) Get(name string) interface{} { + r.mutex.Lock() + defer r.mutex.Unlock() + return r.metrics[name] +} + +// GetOrRegister gets an existing metric or creates and registers a new one. Threadsafe +// alternative to calling Get and Register on failure. +// The interface can be the metric to register if not found in registry, +// or a function returning the metric for lazy instantiation. +func (r *TTNRegistry) GetOrRegister(name string, i interface{}) interface{} { + r.mutex.Lock() + defer r.mutex.Unlock() + if metric, ok := r.metrics[name]; ok { + return metric + } + if v := reflect.ValueOf(i); v.Kind() == reflect.Func { + i = v.Call(nil)[0].Interface() + } + r.register(name, i) + return i +} + +// Register the given metric under the given name. Returns a DuplicateMetric +// if a metric by the given name is already registered. +func (r *TTNRegistry) Register(name string, i interface{}) error { + r.mutex.Lock() + defer r.mutex.Unlock() + return r.register(name, i) +} + +// RunHealthchecks runs all registered healthchecks. +func (r *TTNRegistry) RunHealthchecks() { + r.mutex.Lock() + defer r.mutex.Unlock() + for _, i := range r.metrics { + if h, ok := i.(metrics.Healthcheck); ok { + h.Check() + } + } +} + +// Unregister the metric with the given name. +func (r *TTNRegistry) Unregister(name string) { + r.mutex.Lock() + defer r.mutex.Unlock() + delete(r.metrics, name) +} + +// UnregisterAll unregisters all metrics. (Mostly for testing.) +func (r *TTNRegistry) UnregisterAll() { + r.mutex.Lock() + defer r.mutex.Unlock() + for name := range r.metrics { + delete(r.metrics, name) + } +} + +// Tick decreases the TTL of all metrics that have one +// If the TTL becomes zero, the metric is deleted +func (r *TTNRegistry) Tick() { + r.mutex.Lock() + defer r.mutex.Unlock() + for name, ttl := range r.timeouts { + r.timeouts[name] = ttl - 1 + if ttl == 0 { + delete(r.metrics, name) + delete(r.timeouts, name) + } + } +} + +// SetDefaultTTL sets a default TTL of a number of ticks for all new metrics +func (r *TTNRegistry) SetDefaultTTL(ticks uint) { + r.defaultTTL = ticks +} + +// SetTTL sets a TTL of a number of ticks for a given metric +func (r *TTNRegistry) SetTTL(name string, ticks uint) { + r.mutex.Lock() + defer r.mutex.Unlock() + if _, ok := r.metrics[name]; ok { + // Ignore if the existing ttl is higher than the new ttl + if ttl, ok := r.timeouts[name]; ok && ttl > ticks { + return + } + r.timeouts[name] = ticks + } +} + +func (r *TTNRegistry) register(name string, i interface{}) error { + if _, ok := r.metrics[name]; ok { + return fmt.Errorf("duplicate metric: %s", name) + } + switch i.(type) { + case metrics.Counter, metrics.Gauge, metrics.GaugeFloat64, metrics.Healthcheck, metrics.Histogram, metrics.Meter, metrics.Timer, String: + r.metrics[name] = i + if r.defaultTTL > 0 { + r.timeouts[name] = r.defaultTTL + } + } + return nil +} + +func (r *TTNRegistry) registered() map[string]interface{} { + r.mutex.Lock() + defer r.mutex.Unlock() + metrics := make(map[string]interface{}, len(r.metrics)) + for name, i := range r.metrics { + metrics[name] = i + } + return metrics +} diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 6253109cb..2a1af9943 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -12,27 +12,39 @@ var Enabled = true // MarkMeter registers an event func MarkMeter(name string) { if Enabled { - metrics.GetOrRegisterMeter(name, metrics.DefaultRegistry).Mark(1) + metrics.GetOrRegisterMeter(name, Registry).Mark(1) } } // UpdateHistogram registers a new value for a histogram func UpdateHistogram(name string, value int64) { if Enabled { - metrics.GetOrRegisterHistogram(name, metrics.DefaultRegistry, metrics.NewUniformSample(1000)).Update(value) + metrics.GetOrRegisterHistogram(name, Registry, metrics.NewUniformSample(1000)).Update(value) } } // IncCounter increments a counter by 1 func IncCounter(name string) { if Enabled { - metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Inc(1) + metrics.GetOrRegisterCounter(name, Registry).Inc(1) } } // DecCounter decrements a counter by 1 func DecCounter(name string) { if Enabled { - metrics.GetOrRegisterCounter(name, metrics.DefaultRegistry).Dec(1) + metrics.GetOrRegisterCounter(name, Registry).Dec(1) } } + +// SetString ... +func SetString(name, tag, value string) { + if Enabled { + GetOrRegisterString(name, Registry).Set(tag, value) + } +} + +// SetTimeout ... +func SetTimeout(name string, ttl uint) { + Registry.(Ticker).SetTTL(name, ttl) +} diff --git a/utils/stats/string.go b/utils/stats/string.go new file mode 100644 index 000000000..2bb03c76f --- /dev/null +++ b/utils/stats/string.go @@ -0,0 +1,107 @@ +package stats + +import ( + "sync" + + "github.com/rcrowley/go-metrics" +) + +// String stores a string +type String interface { + Get() map[string]string + Set(string, string) + Snapshot() String +} + +// GetOrRegisterString returns an existing String or constructs and registers a +// new StandardString. +func GetOrRegisterString(name string, r metrics.Registry) String { + if nil == r { + r = metrics.DefaultRegistry + } + return r.GetOrRegister(name, NewString).(String) +} + +// NewString constructs a new StandardString. +func NewString() String { + if metrics.UseNilMetrics { + return NilString{} + } + s := newStandardString() + return s +} + +// NewRegisteredString constructs and registers a new StandardString. +func NewRegisteredString(name string, r metrics.Registry) String { + c := NewString() + if nil == r { + r = metrics.DefaultRegistry + } + r.Register(name, c) + return c +} + +// StringSnapshot is a read-only copy of another String. +type StringSnapshot struct { + values map[string]string +} + +// Get returns the value of events at the time the snapshot was taken. +func (m *StringSnapshot) Get() map[string]string { return m.values } + +// Set panics. +func (*StringSnapshot) Set(t, v string) { + panic("Set called on a StringSnapshot") +} + +// Snapshot returns the snapshot. +func (m *StringSnapshot) Snapshot() String { return m } + +// NilString is a no-op String. +type NilString struct{} + +// Get is a no-op. +func (NilString) Get() map[string]string { return map[string]string{} } + +// Set is a no-op. +func (NilString) Set(t, s string) {} + +// Snapshot is a no-op. +func (NilString) Snapshot() String { return NilString{} } + +// StandardString is the standard implementation of a String. +type StandardString struct { + lock sync.RWMutex + snapshot *StringSnapshot +} + +func newStandardString() *StandardString { + return &StandardString{ + snapshot: &StringSnapshot{ + values: map[string]string{}, + }, + } +} + +// Get returns the number of events recorded. +func (m *StandardString) Get() map[string]string { + m.lock.RLock() + value := m.snapshot.values + m.lock.RUnlock() + return value +} + +// Set sets the String to the given value. +func (m *StandardString) Set(tag, str string) { + m.lock.Lock() + defer m.lock.Unlock() + m.snapshot.values[tag] = str +} + +// Snapshot returns a read-only copy of the string. +func (m *StandardString) Snapshot() String { + m.lock.RLock() + snapshot := *m.snapshot + m.lock.RUnlock() + return &snapshot +} From bb13c135a74237181f95c90a4c12559e56e005dd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:28:07 +0100 Subject: [PATCH 1009/2266] Initialize stats registry in cmds --- cmd/broker.go | 1 + cmd/handler.go | 1 + cmd/root.go | 13 +++++++++++++ cmd/router.go | 1 + 4 files changed, 16 insertions(+) diff --git a/cmd/broker.go b/cmd/broker.go index 341ad6d5b..c1401ab37 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -31,6 +31,7 @@ and personalized devices (with their network session keys) with the router. var statusServer string if viper.GetInt("broker.status-port") > 0 { statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) + initStats() } else { statusServer = "disabled" stats.Enabled = false diff --git a/cmd/handler.go b/cmd/handler.go index cefdb8937..3a356cb71 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -31,6 +31,7 @@ The default handler is the bridge between The Things Network and applications. var statusServer string if viper.GetInt("handler.status-port") > 0 { statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) + initStats() } else { statusServer = "disabled" stats.Enabled = false diff --git a/cmd/root.go b/cmd/root.go index 50b92c74e..5063612a8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,7 +7,9 @@ import ( "fmt" "os" "strings" + "time" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/apex/log/handlers/cli" "github.com/spf13/cobra" @@ -69,3 +71,14 @@ func initConfig() { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } + +func initStats() { + statsTicker := stats.Registry.(stats.Ticker) + statsTicker.SetDefaultTTL(60) + go func() { + c := time.Tick(1 * time.Minute) + for _ = range c { + statsTicker.Tick() + } + }() +} diff --git a/cmd/router.go b/cmd/router.go index 5e5157768..1542ba97c 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -34,6 +34,7 @@ the gateway's duty cycle is (almost) full.`, var statusServer string if viper.GetInt("router.status-port") > 0 { statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) + initStats() } else { statusServer = "disabled" stats.Enabled = false From 9e773e271e3ac34807755813c12bde8795ffea57 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:28:27 +0100 Subject: [PATCH 1010/2266] Improve stats in router component --- core/components/router/router.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/components/router/router.go b/core/components/router/router.go index 01a0baa2b..a722d8cb3 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -72,6 +72,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // TODO add gateway location metadata := packet.Metadata() if metadata.Freq == nil { + stats.MarkMeter("router.uplink.not_supported") return errors.New(errors.Structural, "Missing mandatory frequency in metadata") } @@ -83,6 +84,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { sb1, err := dutycycle.GetSubBand(*metadata.Freq) if err != nil { + stats.MarkMeter("router.uplink.not_supported") return errors.New(errors.Structural, "Unhandled uplink signal frequency") } @@ -91,6 +93,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { bpacket, err := NewBPacket(packet.Payload(), metadata) if err != nil { + stats.MarkMeter("router.uplink.not_supported") r.ctx.WithError(err).Warn("Unable to create router packet") return errors.New(errors.Structural, err) } @@ -119,6 +122,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // Might be a collision with the dev addr, we better broadcast response, err = up.Send(bpacket) } + stats.MarkMeter("router.uplink.out") } if err != nil { From a11afabbbedaf955700188599616d162cf0fee0a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:28:57 +0100 Subject: [PATCH 1011/2266] Improve stats in http adapter --- core/adapters/http/http.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index ca453cb49..ff68bc056 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -129,9 +129,6 @@ func (a *Adapter) Subscribe(r core.Registration) error { // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { - stats.MarkMeter("http_adapter.send") - stats.UpdateHistogram("http_adapter.send_recipients", int64(len(recipients))) - // Marshal the packet to raw binary data data, err := p.MarshalBinary() if err != nil { @@ -156,6 +153,13 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err } } + if isBroadcast { + stats.MarkMeter("http_adapter.broadcast") + } else { + stats.MarkMeter("http_adapter.send") + } + stats.UpdateHistogram("http_adapter.send_recipients", int64(len(recipients))) + // Prepare ground for parrallel http request cherr := make(chan error, nb) chresp := make(chan []byte, nb) From 3835d4634c43357f85f6f364821937f649168dff Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:31:21 +0100 Subject: [PATCH 1012/2266] Improve stats in Semtech adapter --- core/adapters/udp/handlers/semtech.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index 6fbd20a05..5ce44a3ce 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -5,6 +5,7 @@ package handlers import ( "encoding/base64" + "fmt" "reflect" "strings" "time" @@ -32,6 +33,9 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u switch pkt.Identifier { case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK stats.MarkMeter("semtech_adapter.pull_data") + stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.pull_data", pkt.GatewayId)) + stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_pull_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) + data, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, @@ -46,6 +50,9 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u } case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component stats.MarkMeter("semtech_adapter.push_data") + stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) + stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) + data, err := semtech.Packet{ Version: semtech.VERSION, Token: pkt.Token, From 89e9040e531f7c2ffa32ce9d94772fccafcf98a9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 15:35:28 +0100 Subject: [PATCH 1013/2266] [issues/#45+#49] Implement lazy MarshalBinary / UnmarshalBinary in Metadata --- core/metadata.go | 10 ++++++++++ core/metadata_test.go | 23 ++++++++++++++++------- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/core/metadata.go b/core/metadata.go index 2f483eca3..9b19e0bdf 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -101,6 +101,16 @@ func (m *Metadata) UnmarshalJSON(raw []byte) error { return nil } +// MarshalBinary implements the binary.Marshaler interface +func (m Metadata) MarshalBinary() ([]byte, error) { + return m.MarshalJSON() +} + +// UnmarshalBinary implements the binary.Unmarshaler interface +func (m *Metadata) UnmarshalBinary(data []byte) error { + return m.UnmarshalJSON(data) +} + // String implements the io.Stringer interface func (m Metadata) String() string { return pointer.DumpPStruct(m, false) diff --git a/core/metadata_test.go b/core/metadata_test.go index 0962af4dc..381a1f955 100644 --- a/core/metadata_test.go +++ b/core/metadata_test.go @@ -82,18 +82,27 @@ var unmarshalTests = []struct { func TestMarshaljson(t *testing.T) { for _, test := range commonTests { Desc(t, "Marshal medatadata: %s", test.Metadata.String()) - raw, err := json.Marshal(test.Metadata) - CheckErrors(t, test.WantError, err) - checkJSON(t, test.JSON, raw) + rawJ, errJ := json.Marshal(test.Metadata) + rawB, errB := test.Metadata.MarshalBinary() + + CheckErrors(t, test.WantError, errJ) + CheckErrors(t, test.WantError, errB) + checkJSON(t, test.JSON, rawJ) + checkJSON(t, test.JSON, rawB) } } func TestUnmarshalJSON(t *testing.T) { for _, test := range append(commonTests, unmarshalTests...) { Desc(t, "Unmarshal json: %s", test.JSON) - metadata := Metadata{} - err := json.Unmarshal([]byte(test.JSON), &metadata) - CheckErrors(t, test.WantError, err) - checkMetadata(t, test.Metadata, metadata) + metadataJ := Metadata{} + errJ := json.Unmarshal([]byte(test.JSON), &metadataJ) + metadataB := Metadata{} + errB := metadataB.UnmarshalBinary([]byte(test.JSON)) + + CheckErrors(t, test.WantError, errJ) + CheckErrors(t, test.WantError, errB) + checkMetadata(t, test.Metadata, metadataJ) + checkMetadata(t, test.Metadata, metadataB) } } From 238f9924c11da0cf60b5d55e01820fe32968630e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:36:33 +0100 Subject: [PATCH 1014/2266] Improve stats in broker component --- core/components/broker/broker.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 052993431..b21a77593 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -137,7 +137,9 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if !ok { return errors.New(errors.Operational, "Received unexpected response") } + stats.MarkMeter("broker.downlink.in") if err := bpacket.ComputeFCnt(mEntry.FCntDown); err != nil { + stats.MarkMeter("broker.downlink.invalid") return errors.New(errors.Operational, "Received invalid response > frame counter incorrect") } b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt(), "down") @@ -149,6 +151,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if err != nil { return errors.New(errors.Structural, "Invalid downlink packet from the handler") } + stats.MarkMeter("broker.downlink.out") ack = rpacket } case JPacket: From dd167dcaad4a45c402195fc40d998e30652a5e59 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:45:34 +0100 Subject: [PATCH 1015/2266] Switch to automated build --- .drone.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.drone.yml b/.drone.yml index e88269ec4..1bb6ea282 100644 --- a/.drone.yml +++ b/.drone.yml @@ -51,13 +51,9 @@ publish: branch: [master, develop] matrix: GO_VERSION: 1.6 - docker: - username: $$DOCKER_USER - password: $$DOCKER_PASSWORD - email: $$DOCKER_EMAIL + dockerhub: repo: thethingsnetwork/ttn - tag: $$BRANCH - file: Dockerfile.local + token: $$DOCKER_TOKEN when: branch: [master, develop] matrix: From 826c7fcae85cb02f38c5cdcf5abee15df60ba452 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:46:04 +0100 Subject: [PATCH 1016/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index edc1b52bb..c70f4575e 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.ezujQATdxeT7ygXK-Igx_rojtfkSoyYNrJx2vTA5Wqe0kS2NixnQivd6-ix17ZXywWykGPEUACH96rbmlQ8rikQ35xfdfvxXp-a9fbDvEUlqoRXgXs-yup6lO83bh4wFqF5HTvfAVbg8uesGEc6BiV4xp_yKHmpeK8_HKeJLzFlJk9baNPMdXaE_lrAmTkYe5GhkV16AkzbalwUYFB83w4wwRA6hyxOrR_SlMjcBR9HG45WJghabJtlyCDD6QJG5kMySf9Z6-Yoeg6PbWZVkgD7NYUxnGhvlfzEbdk0FIWZ43q1fG3qXvxq0yH0lzFfMWHMlmxjA3fkIJEj1CvZ1jg.ALqPQUYXkf2AqeZV.efA43bCPMUsNzs1Ou8_x3m5ujWyT6783V4k_dqitPQDPH7nxmIEFa30MWi9NMbqexXBb0ka4mK5agfUgFvXZYAdbjJYNMqPfXyhcWcieJVOlwFrg2NcxClYm0vAGm-MB6CdtgpkoMxy3eoEQ3MEbrBH9ZIdZqmY3wjWjwOdBwkJNt93vlDmkPCbLH7jmpCSqfJXA40lmhfV9vqT16Bg4bBQB927f6kCsIiYNA_RLHwM2w0FDMRFRHXUiQrXVpXW_hHeAsTt9vcFpUOQoZ-lANSNMOaOm6itCx1lWpLgESPCpsb_s79cjbYemoQvySUCw-yoMTEbxB4yCUc-QHZPT4uv9j8Uu7n8JfEf1o7CIYwYXurUC3y_QUwyjKBYq_KMfAbhhAhb4bH1he9FRpgPqeA.P6kayG2Ddq0JfGK2fxiUnw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.ge7xpomnwY3RpSSwb6p5JReL_MfR-bojzeDo6V9r82cDz33GtaCCGNDYvqdFKJbIGUEVYz4V_F0oFRJKqfA_X9ldWZNHo6UnivXsgK7rF9JqE3V7vpAuSrGOQCxI2BLeOOtlEQRn0SsJ8-pIVVSuXiV7FqqrwF7UR9tAlON1znUQPVZOS4-Biq2Z2IsVoyR83GbEtT4Hes5GbAMBSq3sfppYYwM0Z3GzuMVTy4Ge63BiF7OkJTyYhT_MLd7SJYUzOIlGbfHjeWLVP9s-CONBhj9WF0AGONidYh2uiQP8lX3A8cmau9FOpzuPneJFzc8CO5uzlyiNGX0v1Rrpx8pKiw.ChF-_qRCWZJgXxb-.1occmJYjbgv7eJkXUNNSxDTeWPE-LYOcpgCFW4NaUPFATHebFS08ye5NN_6mqFT7kjFCEOD0p17FFWLROu15_DbXPwrz5Jb1nhsAClROuQDomnT01_nv5pU0KJR3AH9V-938DgiAuVD8emDiA5LsTZBoIA7xcoqL3cMcGQuqFm81Ee8pM8NHAdidQ2W3HfjV_GlXvNee5VDOyQGIDZV8ezHOOno32ZNqgLLN7EMdaSmqabdKkdCpBATlFRvYrYuyxL4iFED5OqY_OhfiamIIjkSO4c84BfnIUQAWeLTjRpZngy0HCqerH8qJflJ9H7qT3nKDG97tCGBf6TuDHI7gkiBeN4yP3tam7dBBnE7C-ZjU-tdT-hyj5BI73Oqi-kYJqjNMjX2SDV3mYZ5Qpf4g8s1lIbPQ44fTlmf26DKoYVlwDXR3tQb1ZKg3THVzwAkzulWK0w2FjrtXS8BrYJU9T2ttA4e7.Om9vSn9tB83f1d8sOzJg7A \ No newline at end of file From 4ebc3ea6ffcb0373c9c4866d18588a3a187bd13d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:46:36 +0100 Subject: [PATCH 1017/2266] Use default mosquitto settings --- .drone.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 1bb6ea282..22362a1fe 100644 --- a/.drone.yml +++ b/.drone.yml @@ -13,7 +13,6 @@ matrix: compose: mqtt: image: ansi/mosquitto - command: /usr/local/sbin/mosquitto -p 1683 build: test: From 59cc5cc59c9ce0026f16f6ff3f4b31f211fe15b9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 15:46:52 +0100 Subject: [PATCH 1018/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index c70f4575e..53b2bcfed 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.ge7xpomnwY3RpSSwb6p5JReL_MfR-bojzeDo6V9r82cDz33GtaCCGNDYvqdFKJbIGUEVYz4V_F0oFRJKqfA_X9ldWZNHo6UnivXsgK7rF9JqE3V7vpAuSrGOQCxI2BLeOOtlEQRn0SsJ8-pIVVSuXiV7FqqrwF7UR9tAlON1znUQPVZOS4-Biq2Z2IsVoyR83GbEtT4Hes5GbAMBSq3sfppYYwM0Z3GzuMVTy4Ge63BiF7OkJTyYhT_MLd7SJYUzOIlGbfHjeWLVP9s-CONBhj9WF0AGONidYh2uiQP8lX3A8cmau9FOpzuPneJFzc8CO5uzlyiNGX0v1Rrpx8pKiw.ChF-_qRCWZJgXxb-.1occmJYjbgv7eJkXUNNSxDTeWPE-LYOcpgCFW4NaUPFATHebFS08ye5NN_6mqFT7kjFCEOD0p17FFWLROu15_DbXPwrz5Jb1nhsAClROuQDomnT01_nv5pU0KJR3AH9V-938DgiAuVD8emDiA5LsTZBoIA7xcoqL3cMcGQuqFm81Ee8pM8NHAdidQ2W3HfjV_GlXvNee5VDOyQGIDZV8ezHOOno32ZNqgLLN7EMdaSmqabdKkdCpBATlFRvYrYuyxL4iFED5OqY_OhfiamIIjkSO4c84BfnIUQAWeLTjRpZngy0HCqerH8qJflJ9H7qT3nKDG97tCGBf6TuDHI7gkiBeN4yP3tam7dBBnE7C-ZjU-tdT-hyj5BI73Oqi-kYJqjNMjX2SDV3mYZ5Qpf4g8s1lIbPQ44fTlmf26DKoYVlwDXR3tQb1ZKg3THVzwAkzulWK0w2FjrtXS8BrYJU9T2ttA4e7.Om9vSn9tB83f1d8sOzJg7A \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Y2WrgXywyqW5rp6imJA8wPr45z6TXdLm0G-UGifYBRP8nyFFccII41C_pOTy9n9jAXRxbfFBXkk31VqNB4h22tvFDSzo90v2N8J_wIJ_xNPXGAfAoaLG0uKA6ok7nbHznrx3q71JMoEocwWqUXauw6KwXWQjLEeQHA4gQR_mn__MCgl4KupMSFdAz8lh7t3KWtrSFA93tkp5JLIpH-nooF0BK0eF8n29ChVE3kAVMcSEsSrcp6HUaky4Ufp92oI8wE-3iOAqrG-kaDoSB0pRJthZkOXEkLLjYqeUvTVnRnXj76Cny9A2-VnyLNO88tXo9C3OiaMX9pd4UzcSX-Aziw.dD5v7ynLVgOVCTDJ.II4nbQEBgG9C8WwQgU_z8y_U51wdi2QxJkjkGZuQtbHFLvpxH70VZtH6An8OhT2_m_4cVFWQOcvm5jQ8SMuwL5IRqAq6_o9JPBFZ34jCpZEv8AWzJ-OquKiU6fdxQuTyWHQcnlkzh8zUzyNKz2zIL9m0hgwWhNg0lwXjAPu8QcstGtXSa9wB25k4qK-QeBO_Y3c0q57lV5FUp8mYjALbZZR1eshT28D3C0dVT_zd-4rjEpe9GHorvu-eJWjnYn0yQZ5ohAEcnFFiygWHRKm5ClSwRTSiMXz3yZaCnDpFo_gnhlmmCQihNjOwzH3_bB6bNbWIEm1mKVf16TMb4bMAhK-YXoZxg9DkekiRbXD8oibtxufm8nUSQ25wVCfjtMM9LIgxFyIv2iZBtdFdyk9XTdMUa5Ivb3iBTR41yJYzHNYu7regZjFhB5ZdWhSngQeqQx21TsOgXr83TRRI4WkA-4Ttf7NA.rA_xvJBc_blqGzJqkmdn-g \ No newline at end of file From 89d058f0706c7283d46f7aba6f0137af374854e2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 15:56:07 +0100 Subject: [PATCH 1019/2266] [issues/#45+#49] Add SPacket processing in router --- core/components/router/mock_test.go | 25 +++++++++++++++++---- core/components/router/router.go | 15 +++++++------ core/components/router/storage.go | 35 ++++++++++++++++++++++++----- core/metadata.go | 3 +++ 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go index 2bab78ec3..b5963a4b6 100644 --- a/core/components/router/mock_test.go +++ b/core/components/router/mock_test.go @@ -14,10 +14,13 @@ import ( // MockStorage to fake the Storage interface type mockStorage struct { - Failures map[string]error - InLookup lorawan.EUI64 - OutLookup []entry - InStore RRegistration + Failures map[string]error + InLookup lorawan.EUI64 + InLookupStats []byte + OutLookupStats Metadata + OutLookup []entry + InStore RRegistration + InUpdateStats SPacket } func newMockStorage() *mockStorage { @@ -29,6 +32,7 @@ func newMockStorage() *mockStorage { until: time.Date(2016, 2, 3, 14, 16, 22, 0, time.UTC), }, }, + OutLookupStats: Metadata{}, } } @@ -40,11 +44,24 @@ func (s *mockStorage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { return s.OutLookup, nil } +func (s *mockStorage) LookupStats(id []byte) (Metadata, error) { + s.InLookupStats = id + if s.Failures["LookupStats"] != nil { + return Metadata{}, s.Failures["LookupStats"] + } + return s.OutLookupStats, nil +} + func (s *mockStorage) Store(reg RRegistration) error { s.InStore = reg return s.Failures["Store"] } +func (s *mockStorage) UpdateStats(stats SPacket) error { + s.InUpdateStats = stats + return s.Failures["UpdateStats"] +} + func (s *mockStorage) Close() error { return s.Failures["Close"] } diff --git a/core/components/router/router.go b/core/components/router/router.go index d450b92da..ba24393c0 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -54,6 +54,7 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { case RPacket: ack, err = r.handleDataUp(itf.(RPacket), up) case SPacket: + err = r.UpdateStats(itf.(SPacket)) default: stats.MarkMeter("router.uplink.invalid") err = errors.New(errors.Structural, "Unreckognized packet type") @@ -75,13 +76,18 @@ func (r component) handleDataUp(packet RPacket, up Adapter) (Packet, error) { } shouldBroadcast := err != nil - // Add Gateway duty metadata - // TODO add gateway location metadata := packet.Metadata() if metadata.Freq == nil { return nil, errors.New(errors.Structural, "Missing mandatory frequency in metadata") } + // Add Gateway location metadata + gmeta, _ := r.LookupStats(packet.GatewayID()) + metadata.Lati = gmeta.Lati + metadata.Long = gmeta.Long + metadata.Alti = gmeta.Alti + + // Add Gateway duty metadata cycles, err := r.Manager.Lookup(packet.GatewayID()) if err != nil { r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") @@ -179,11 +185,6 @@ func (r component) handleDataDown(data []byte, gatewayID []byte) (Packet, error) } } -// HandleStat implements the core.Router interface -func (r component) handleStat(packet SPacket, gatewayID []byte) error { - return nil -} - // ensureAckNack is used to make sure we Ack / Nack correctly in the HandleUp method. // The method will probably change or be moved outside the router itself. func ensureAckNack(an AckNacker, ack *Packet, err *error) { diff --git a/core/components/router/storage.go b/core/components/router/storage.go index b7e8df8a2..5b812957f 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -18,6 +18,8 @@ import ( // Storage gives a facade to manipulate the router database type Storage interface { Lookup(devEUI lorawan.EUI64) ([]entry, error) + LookupStats(id []byte) (Metadata, error) + UpdateStats(stats SPacket) error Store(reg RRegistration) error Close() error } @@ -30,10 +32,14 @@ type entry struct { type storage struct { sync.Mutex db dbutil.Interface - Name string ExpiryDelay time.Duration } +const ( + dbBrokers = "brokers" + dbGateway = "gateways" +) + // NewStorage creates a new internal storage for the router func NewStorage(name string, delay time.Duration) (Storage, error) { itf, err := dbutil.New(name) @@ -41,14 +47,14 @@ func NewStorage(name string, delay time.Duration) (Storage, error) { return nil, errors.New(errors.Operational, err) } - return &storage{db: itf, ExpiryDelay: delay, Name: "broker"}, nil + return &storage{db: itf, ExpiryDelay: delay}, nil } // Lookup implements the router.Storage interface func (s *storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { s.Lock() defer s.Unlock() - itf, err := s.db.Lookup(s.Name, devEUI[:], &entry{}) + itf, err := s.db.Lookup(dbBrokers, devEUI[:], &entry{}) if err != nil { return nil, err } @@ -65,7 +71,7 @@ func (s *storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { filtered = append(filtered, e) } } - if err := s.db.Replace(s.Name, devEUI[:], newEntries); err != nil { + if err := s.db.Replace(dbBrokers, devEUI[:], newEntries); err != nil { return nil, errors.New(errors.Operational, err) } entries = filtered @@ -87,12 +93,31 @@ func (s *storage) Store(reg RRegistration) error { s.Lock() defer s.Unlock() - return s.db.Store(s.Name, devEUI[:], []dbutil.Entry{&entry{ + return s.db.Store(dbBrokers, devEUI[:], []dbutil.Entry{&entry{ Recipient: recipient, until: time.Now().Add(s.ExpiryDelay), }}) } +// UpdateStats implements the router.Storage interface +func (s *storage) UpdateStats(stats SPacket) error { + metadata := stats.Metadata() + return s.db.Replace(dbGateway, stats.GatewayID(), []dbutil.Entry{&metadata}) +} + +// LookupStats implements the router.Storage interface +func (s *storage) LookupStats(id []byte) (Metadata, error) { + itf, err := s.db.Lookup(dbGateway, id, &Metadata{}) + if err != nil { + return Metadata{}, err + } + entries := itf.([]Metadata) + if len(entries) == 0 { + return Metadata{}, errors.New(errors.NotFound, "Not entry found for given gateway") + } + return entries[0], nil +} + // Close implements the router.Storage interface func (s *storage) Close() error { return s.db.Close() diff --git a/core/metadata.go b/core/metadata.go index 9b19e0bdf..cfab0cb51 100644 --- a/core/metadata.go +++ b/core/metadata.go @@ -15,6 +15,7 @@ import ( // Metadata are carried by any type of packets. They constitute additional informations on the // packet itself or, about its context (gateway, duty cycle, etc ...) type Metadata struct { + Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier @@ -25,6 +26,8 @@ type Metadata struct { Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion + Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long *float64 `json:"long,omitempty"` // GPS longitude of the gateway in degree (float, E is +) Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) From d48c2ef957544008f6b0f874ae2e9ab2a0c95726 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 16:46:38 +0100 Subject: [PATCH 1020/2266] [issues/#45+#49] Add SPacket type --- core/packets.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/core/packets.go b/core/packets.go index 658c952bd..ff0df0903 100644 --- a/core/packets.go +++ b/core/packets.go @@ -95,6 +95,8 @@ func UnmarshalPacket(data []byte) (interface{}, error) { packet = new(jpacket) case typeCPacket: packet = new(cpacket) + case typeSPacket: + packet = new(spacket) } err := packet.UnmarshalBinary(data) @@ -155,6 +157,56 @@ func (p rpacket) String() string { return str } +// --------------------------------- +// +// ----- SPACKET ------------------- +// +// --------------------------------- +type spacket struct { + basempacket + gatewayID baseapacket +} + +// NewSPacket constructs a new stats packet from a gateway id and metadata +func NewSPacket(gatewayID []byte, metadata Metadata) (SPacket, error) { + if len(gatewayID) != 8 { + return nil, errors.New(errors.Structural, "Invalid gatewayId. Should be 8 bytes") + } + return &spacket{ + basempacket: basempacket{metadata: metadata}, + gatewayID: baseapacket{payload: gatewayID}, + }, nil +} + +// GatewayID implements the core.SPacket interface +func (p spacket) GatewayID() []byte { + return p.gatewayID.payload +} + +// DevEUI implements the core.SPacket interface +// TODO THIS IS NOT VIABLE. WE NEED TO PROPERLY DEFINE MESSAGES BETWEEN PROCESSES. +func (p spacket) DevEUI() lorawan.EUI64 { + return lorawan.EUI64{} +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (p spacket) MarshalBinary() ([]byte, error) { + return marshalBases(typeSPacket, p.basempacket, p.gatewayID) +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (p *spacket) UnmarshalBinary(data []byte) error { + return unmarshalBases(typeSPacket, data, &p.basempacket, &p.gatewayID) +} + +// String implements the fmt.Stringer interface +func (p spacket) String() string { + str := "SPacket {" + str += fmt.Sprintf("\n\t%s}", p.metadata.String()) + str += fmt.Sprintf("\n\tGatewayID%+v\n}", p.gatewayID.payload) + return str +} + // --------------------------------- // // ----- BPACKET ------------------- From 96fc22fb18bb848eea8fab20f1e103e118641ec3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 16:46:50 +0100 Subject: [PATCH 1021/2266] [issues/#45+#49] Handle Stats packet in semtech adapter --- core/adapters/udp/handlers/semtech.go | 67 ++++++++++++++++++--------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index 6fbd20a05..16b8a8ffe 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -63,6 +63,20 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u return errors.New(errors.Structural, "Unable to process empty PUSH_DATA payload") } + // Handle stat payload + if pkt.Payload.Stat != nil { + spacket, err := core.NewSPacket(pkt.GatewayId, extractMetadata(*pkt.Payload.Stat)) + if err == nil { + data, err := spacket.MarshalBinary() + if err == nil { + go func() { + packets <- udp.MsgReq{Data: data, Chresp: nil} + }() + } + } + } + + // Handle rxpks payloads for _, rxpk := range pkt.Payload.RXPK { go func(rxpk semtech.RXPK) { pktOut, err := rxpk2packet(rxpk, pkt.GatewayId) @@ -137,23 +151,9 @@ func rxpk2packet(p semtech.RXPK, gid []byte) (core.Packet, error) { if err = payload.UnmarshalBinary(raw); err != nil { return nil, errors.New(errors.Structural, err) } - - // Then, we interpret every other known field as a metadata and store them into an appropriate - // metadata object. - metadata := core.Metadata{} - rxpkValue := reflect.ValueOf(p) - rxpkStruct := rxpkValue.Type() - metas := reflect.ValueOf(&metadata).Elem() - for i := 0; i < rxpkStruct.NumField(); i++ { - field := rxpkStruct.Field(i).Name - if metas.FieldByName(field).CanSet() { - metas.FieldByName(field).Set(rxpkValue.Field(i)) - } - } - // At the end, our converted packet hold the same metadata than the RXPK packet but the Data // which as been completely transformed into a lorawan Physical Payload. - return core.NewRPacket(payload, gid, metadata) + return core.NewRPacket(payload, gid, extractMetadata(p)) } func packet2txpk(p core.RPacket) (semtech.TXPK, error) { @@ -168,15 +168,36 @@ func packet2txpk(p core.RPacket) (semtech.TXPK, error) { // Step 2, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. - metadataValue := reflect.ValueOf(p.Metadata()) - metadataStruct := metadataValue.Type() - txpkStruct := reflect.ValueOf(&txpk).Elem() - for i := 0; i < metadataStruct.NumField(); i++ { - field := metadataStruct.Field(i).Name - if txpkStruct.FieldByName(field).CanSet() { - txpkStruct.FieldByName(field).Set(metadataValue.Field(i)) + injectMetadata(&txpk, p.Metadata()) + + return txpk, nil +} + +func injectMetadata(ptr interface{}, metadata core.Metadata) { + v := reflect.ValueOf(metadata) + t := v.Type() + d := reflect.ValueOf(ptr).Elem() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i).Name + if d.FieldByName(field).CanSet() { + d.FieldByName(field).Set(v.Field(i)) } } +} - return txpk, nil +func extractMetadata(xpk interface{}) core.Metadata { + metadata := core.Metadata{} + v := reflect.ValueOf(xpk) + t := v.Type() + m := reflect.ValueOf(&metadata).Elem() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i).Name + if m.FieldByName(field).CanSet() { + m.FieldByName(field).Set(v.Field(i)) + } + } + + return metadata } From cbe3a74b020e432dba8ca62df2b0edbcb73ddd47 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 16:47:05 +0100 Subject: [PATCH 1022/2266] [issues/#45+#49] Test router handleUp with stat packet --- core/components/router/helpers_test.go | 4 ++ core/components/router/router_test.go | 65 ++++++++++++++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index c969c6e25..29d46dda0 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -80,3 +80,7 @@ func CheckRegistrations(t *testing.T, want Registration, got Registration) { func CheckIDs(t *testing.T, want []byte, got []byte) { Check(t, want, got, "IDs") } + +func CheckStats(t *testing.T, want SPacket, got SPacket) { + Check(t, want, got, "Stats") +} diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 5e1217d40..10dea01ec 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -695,4 +695,69 @@ func TestHandleUp(t *testing.T) { CheckIDs(t, inPacket.GatewayID(), m.InLookupId) CheckIDs(t, nil, m.InUpdateId) } + + // ------------------- + + { + Desc(t, "Send a valid stat packet") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + inPacket, _ := NewSPacket( + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{Alti: pointer.Int(14)}, + ) + data, _ := inPacket.MarshalBinary() + m := newMockDutyManager() + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, nil, err) + CheckAcks(t, true, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckStats(t, inPacket, store.InUpdateStats) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, nil, m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) + CheckIDs(t, nil, store.InLookupStats) + } + + // ------------------- + + { + Desc(t, "Send a valid stat packet | unable to update") + + // Build + an := NewMockAckNacker() + adapter := NewMockAdapter() + store := newMockStorage() + store.Failures["UpdateStats"] = errors.New(errors.Operational, "Mock Error") + inPacket, _ := NewSPacket( + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata{Alti: pointer.Int(14)}, + ) + data, _ := inPacket.MarshalBinary() + m := newMockDutyManager() + + // Operate + router := New(store, m, GetLogger(t, "Router")) + err := router.HandleUp(data, an, adapter) + + // Check + CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckAcks(t, false, an.InAck) + CheckRegistrations(t, nil, store.InStore) + CheckStats(t, inPacket, store.InUpdateStats) + CheckSent(t, nil, adapter.InSendPacket) + CheckRecipients(t, nil, adapter.InSendRecipients) + CheckIDs(t, nil, m.InLookupId) + CheckIDs(t, nil, m.InUpdateId) + CheckIDs(t, nil, store.InLookupStats) + } } From 005a642802992b7a6a67c7e1ba623a6c2901539b Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 17:06:20 +0100 Subject: [PATCH 1023/2266] [issues/#45+#49] Add tests to verify storage with Stats methods --- core/components/router/helpers_test.go | 4 ++ core/components/router/storage_test.go | 51 ++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go index 29d46dda0..bcac00600 100644 --- a/core/components/router/helpers_test.go +++ b/core/components/router/helpers_test.go @@ -84,3 +84,7 @@ func CheckIDs(t *testing.T, want []byte, got []byte) { func CheckStats(t *testing.T, want SPacket, got SPacket) { Check(t, want, got, "Stats") } + +func CheckMetadata(t *testing.T, want Metadata, got Metadata) { + Check(t, want, got, "Metadata") +} diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index 12440e694..672b533db 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" @@ -229,3 +230,53 @@ func TestStoreAndLookup(t *testing.T) { _ = db.Close() } } + +func TestUpdateAndLookup(t *testing.T) { + storageDB := path.Join(os.TempDir(), storageDB) + + defer func() { + os.Remove(storageDB) + }() + + // ------------------ + + { + Desc(t, "Store then lookup stats") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + stats, _ := core.NewSPacket( + []byte{1, 2, 3, 4, 5, 6, 7, 8}, + core.Metadata{ + Alti: pointer.Int(14), + }, + ) + + // Operate + errUpdate := db.UpdateStats(stats) + got, errLookup := db.LookupStats(stats.GatewayID()) + + // Check + CheckErrors(t, nil, errUpdate) + CheckErrors(t, nil, errLookup) + CheckMetadata(t, stats.Metadata(), got) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Lookup stats from unknown gateway") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + + // Operate + got, errLookup := db.LookupStats([]byte{1, 2, 2, 3, 3, 4, 4, 1}) + + // Check + CheckErrors(t, pointer.String(string(errors.NotFound)), errLookup) + CheckMetadata(t, core.Metadata{}, got) + _ = db.Close() + } +} From a6f77501b4142c4e26e64b1060dd52079ae2f79a Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 11 Mar 2016 17:23:39 +0100 Subject: [PATCH 1024/2266] [issues/#45+#49] Enhance error feedback in semtech adapter --- core/adapters/udp/handlers/semtech.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index 16b8a8ffe..7678d005d 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -7,6 +7,7 @@ import ( "encoding/base64" "reflect" "strings" + "sync" "time" "github.com/TheThingsNetwork/ttn/core" @@ -77,16 +78,20 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u } // Handle rxpks payloads + cherr := make(chan error, len(pkt.Payload.RXPK)) + wait := sync.WaitGroup{} + wait.Add(len(pkt.Payload.RXPK)) for _, rxpk := range pkt.Payload.RXPK { go func(rxpk semtech.RXPK) { + defer wait.Done() pktOut, err := rxpk2packet(rxpk, pkt.GatewayId) if err != nil { - // TODO Log error + cherr <- errors.New(errors.Structural, err) return } data, err := pktOut.MarshalBinary() if err != nil { - // TODO Log error + cherr <- errors.New(errors.Structural, err) return } chresp := make(chan udp.MsgRes) @@ -95,15 +100,17 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u case resp := <-chresp: itf, err := core.UnmarshalPacket(resp) if err != nil { + cherr <- errors.New(errors.Structural, err) return } pkt, ok := itf.(core.RPacket) // NOTE Here we'll handle join-accept if !ok { + cherr <- errors.New(errors.Structural, "Unhandled packet type") return } txpk, err := packet2txpk(pkt) if err != nil { - // TODO Log error + cherr <- errors.New(errors.Structural, err) return } @@ -113,7 +120,7 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u Payload: &semtech.Payload{TXPK: &txpk}, }.MarshalBinary() if err != nil { - // TODO Log error + cherr <- errors.New(errors.Structural, err) return } conn <- udp.MsgUDP{Addr: msg.Addr, Data: data} @@ -121,6 +128,11 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u } }(rxpk) } + wait.Wait() + close(cherr) + if err := <-cherr; err != nil { + return err + } default: return errors.New(errors.Implementation, "Unhandled packet type") } From 9f4656d25d7ea5fcc6485c29318a8020123c85ee Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 17:23:42 +0100 Subject: [PATCH 1025/2266] Update Drone Build - Skip Test, this is Travis's responsibility - Don't publish individual commits to Azure - Only use Go 1.6 - Only build Docker for master [Skip CI] --- .drone.yml | 42 ++++++++++++------------------------------ Dockerfile | 2 +- build_binaries.sh | 8 -------- 3 files changed, 13 insertions(+), 39 deletions(-) diff --git a/.drone.yml b/.drone.yml index 22362a1fe..76b605ef0 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,7 +1,4 @@ matrix: - GO_VERSION: - - 1.5 - - 1.6 GOOS: - linux - darwin @@ -15,29 +12,16 @@ compose: image: ansi/mosquitto build: - test: - image: golang:$$GO_VERSION - commands: - - go get -t -d ./... - - go list ./... | grep -vE 'integration|ttn$' | xargs go test - - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - - go vet ./... - when: - matrix: - GOOS: linux - GOARCH: amd64 - release: - image: htdvisser/ttnbuild - commands: - # ttnbuild already contains dependencies, so we copy them: - - rsync -a /go/src/ /drone/src/ - # and get the ones that we missed in ttnbuild - - GOOS=$$GOOS GOARCH=$$GOARCH go get -d -v ./... - - ./build_binaries.sh $$GOOS $$GOARCH - when: - branch: [master, develop] - matrix: - GO_VERSION: 1.6 + image: htdvisser/ttnbuild + pull: true + commands: + # ttnbuild already contains dependencies, so we copy them: + - rsync -a /go/src/ /drone/src/ + # and get the ones that we missed in ttnbuild + - GOOS=$$GOOS GOARCH=$$GOARCH go get -d -v ./... + - ./build_binaries.sh $$GOOS $$GOARCH + when: + branch: [master, develop] publish: azure_storage: @@ -48,14 +32,12 @@ publish: source: release/ when: branch: [master, develop] - matrix: - GO_VERSION: 1.6 + dockerhub: repo: thethingsnetwork/ttn token: $$DOCKER_TOKEN when: - branch: [master, develop] + branch: master matrix: - GO_VERSION: 1.6 GOOS: linux GOARCH: amd64 diff --git a/Dockerfile b/Dockerfile index 691551dde..d2573ab7d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* RUN apk --update add curl tar \ && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://ttnreleases.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/develop/ttn-linux-amd64.tar.gz" \ + && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://ttnreleases.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/ttn-linux-amd64.tar.gz" \ && tar -xf /ttnsrc/ttn-linux-amd64.tar.gz -C /ttnsrc \ && mv /ttnsrc/ttn-linux-amd64 /usr/local/bin/ttn \ && chmod 755 /usr/local/bin/ttn \ diff --git a/build_binaries.sh b/build_binaries.sh index f7b7f239f..7d7b81845 100755 --- a/build_binaries.sh +++ b/build_binaries.sh @@ -80,14 +80,6 @@ then cd $RELEASEPATH - # Commit Release - if [ "$CI_COMMIT" != "" ] - then - echo "Copying files for commit $CI_COMMIT" - mkdir -p commit/$CI_COMMIT - cp ./ttn* commit/$CI_COMMIT/ - fi - # Branch Release if [ "$CI_BRANCH" != "" ] then From 7c207198d9729d787fc6adb4576e91aa8a0ce9bc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 17:24:05 +0100 Subject: [PATCH 1026/2266] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index 53b2bcfed..e80cbdd01 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.Y2WrgXywyqW5rp6imJA8wPr45z6TXdLm0G-UGifYBRP8nyFFccII41C_pOTy9n9jAXRxbfFBXkk31VqNB4h22tvFDSzo90v2N8J_wIJ_xNPXGAfAoaLG0uKA6ok7nbHznrx3q71JMoEocwWqUXauw6KwXWQjLEeQHA4gQR_mn__MCgl4KupMSFdAz8lh7t3KWtrSFA93tkp5JLIpH-nooF0BK0eF8n29ChVE3kAVMcSEsSrcp6HUaky4Ufp92oI8wE-3iOAqrG-kaDoSB0pRJthZkOXEkLLjYqeUvTVnRnXj76Cny9A2-VnyLNO88tXo9C3OiaMX9pd4UzcSX-Aziw.dD5v7ynLVgOVCTDJ.II4nbQEBgG9C8WwQgU_z8y_U51wdi2QxJkjkGZuQtbHFLvpxH70VZtH6An8OhT2_m_4cVFWQOcvm5jQ8SMuwL5IRqAq6_o9JPBFZ34jCpZEv8AWzJ-OquKiU6fdxQuTyWHQcnlkzh8zUzyNKz2zIL9m0hgwWhNg0lwXjAPu8QcstGtXSa9wB25k4qK-QeBO_Y3c0q57lV5FUp8mYjALbZZR1eshT28D3C0dVT_zd-4rjEpe9GHorvu-eJWjnYn0yQZ5ohAEcnFFiygWHRKm5ClSwRTSiMXz3yZaCnDpFo_gnhlmmCQihNjOwzH3_bB6bNbWIEm1mKVf16TMb4bMAhK-YXoZxg9DkekiRbXD8oibtxufm8nUSQ25wVCfjtMM9LIgxFyIv2iZBtdFdyk9XTdMUa5Ivb3iBTR41yJYzHNYu7regZjFhB5ZdWhSngQeqQx21TsOgXr83TRRI4WkA-4Ttf7NA.rA_xvJBc_blqGzJqkmdn-g \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.sTgJXLRHZ9Rfpv7ZieOv3j08f6ZxqQ8utgbIKkeaBL3SYy8NnFeijZMdVIZveZRJYlDbBS_HYwhx-Dzprm8qTYttrbtdJCus1XgJqwRTBewYaqJt6ayb6rr5MGAZ5RTeMBG-16VSqd1jw8U2K6pf4EbODqNBtBbZsP5s5CXOiNK--CkYAPUjORGNuN07jPfBE4JSQlEAujs3ZsEm9AhJo5Pngx3yfgSYTiIsI_tbMSH8acXJKLzJdkGoJzb_XjNdsQ8sJbWzWvO2bM733qtkzkUfFVjk91kdJDPnE2CsBn11zvLd8NqtrLW57yiRKXGKIdfDduJqFo-zxARUe67I6Q.iW65t1QJ-Mo92VKc.yC9RQpWxKFdySIIx1g7JJ0UBuSz_008VleQu3LQ_tbKGDpeOzrj8qtG5-DT0Bg0vqb4jvugCJ1b1E1X8-b5eFntll78XZ1IF6x-m3UrbXuRxOQ9nhN54gBXvVJf6YWUzsmy0PQtmLBZIuNMEbDiCga_l-HFQ42iv9KCeSySq2b7Z3jkC3J0JJOuIKn_Bjxjcp1YQP41r0NlG8r23HNPuaZr2kotWtDi3V93aVy68g0gk3HuCb3KLSyoTDXdoKHtgM5AncMj7wCA5OTKLdO5hyrL4waqCU5WeDTclXgKSyPuB0UeGV8VXkb3ZGzlRnO4G6hvYUUCPvRHlcFFNjptCAiF-u7bseajXc44afxd_67ixL1iJfkf48OWsYIBu16Dy2oBOpi48Hfys7bKRBI0KUgZVJzGOJpZVeWYWfseK9KL0Hpz2Fy2maIxvaPl_f5E8lX0OK8FVfWjIVggBwmOKBIF7bX4A.NA0JDh9UAYfBNzQQF0iwwQ \ No newline at end of file From a6215354188389534622e343b4b3c419f92e7efd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 18:14:18 +0100 Subject: [PATCH 1027/2266] [stats] Use distinct meters for stat and uplink --- core/components/router/router.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index b7dda0a52..a1d70287b 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -45,15 +45,16 @@ func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { defer ensureAckNack(an, &ack, &err) // Get some logs / analytics - stats.MarkMeter("router.uplink.in") r.ctx.Debug("Handling uplink packet") // Extract the given packet itf, _ := UnmarshalPacket(data) switch itf.(type) { case RPacket: + stats.MarkMeter("router.uplink.in") ack, err = r.handleDataUp(itf.(RPacket), up) case SPacket: + stats.MarkMeter("router.stat.in") err = r.UpdateStats(itf.(SPacket)) default: stats.MarkMeter("router.uplink.invalid") From fdf97d88e61456eb9777919b5a98ef65acc02f95 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 18:16:01 +0100 Subject: [PATCH 1028/2266] [stats] Move initialization to stats package --- cmd/broker.go | 2 +- cmd/handler.go | 2 +- cmd/root.go | 13 ------------- cmd/router.go | 2 +- utils/stats/stats.go | 16 +++++++++++++++- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index c1401ab37..bb341fcc9 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -31,7 +31,7 @@ and personalized devices (with their network session keys) with the router. var statusServer string if viper.GetInt("broker.status-port") > 0 { statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) - initStats() + stats.Initialize() } else { statusServer = "disabled" stats.Enabled = false diff --git a/cmd/handler.go b/cmd/handler.go index 3a356cb71..526493707 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -31,7 +31,7 @@ The default handler is the bridge between The Things Network and applications. var statusServer string if viper.GetInt("handler.status-port") > 0 { statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) - initStats() + stats.Initialize() } else { statusServer = "disabled" stats.Enabled = false diff --git a/cmd/root.go b/cmd/root.go index 5063612a8..50b92c74e 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,9 +7,7 @@ import ( "fmt" "os" "strings" - "time" - "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/apex/log/handlers/cli" "github.com/spf13/cobra" @@ -71,14 +69,3 @@ func initConfig() { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } - -func initStats() { - statsTicker := stats.Registry.(stats.Ticker) - statsTicker.SetDefaultTTL(60) - go func() { - c := time.Tick(1 * time.Minute) - for _ = range c { - statsTicker.Tick() - } - }() -} diff --git a/cmd/router.go b/cmd/router.go index 1542ba97c..1e20821fd 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -34,7 +34,7 @@ the gateway's duty cycle is (almost) full.`, var statusServer string if viper.GetInt("router.status-port") > 0 { statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) - initStats() + stats.Initialize() } else { statusServer = "disabled" stats.Enabled = false diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 2a1af9943..24fcd7221 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -4,11 +4,25 @@ // Package stats supports the collection of metrics from a running component. package stats -import "github.com/rcrowley/go-metrics" +import ( + "time" + + "github.com/rcrowley/go-metrics" +) // Enabled activates stats collection var Enabled = true +// Initialize the stats +func Initialize() { + go func() { + c := time.Tick(1 * time.Minute) + for _ = range c { + Registry.(Ticker).Tick() + } + }() +} + // MarkMeter registers an event func MarkMeter(name string) { if Enabled { From d91b22339ab86296c7667d28109249d4ee8c951d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Mar 2016 18:16:47 +0100 Subject: [PATCH 1029/2266] [stats] Registry TTLs --- utils/stats/registry.go | 11 ++++++++++- utils/stats/stats.go | 5 +++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/utils/stats/registry.go b/utils/stats/registry.go index 719c31513..c2c927413 100644 --- a/utils/stats/registry.go +++ b/utils/stats/registry.go @@ -13,8 +13,12 @@ type Ticker interface { Tick() SetDefaultTTL(uint) SetTTL(string, uint) + Renew(string) } +// DefaultTTL for the registry +var DefaultTTL uint = 0 + // Registry is the default metrics registry var Registry = NewRegistry() @@ -32,7 +36,7 @@ func NewRegistry() metrics.Registry { return &TTNRegistry{ metrics: make(map[string]interface{}), timeouts: make(map[string]uint), - defaultTTL: 0, + defaultTTL: DefaultTTL, } } @@ -134,6 +138,11 @@ func (r *TTNRegistry) SetTTL(name string, ticks uint) { } } +// Renew sets the TTL of a metric to the default value +func (r *TTNRegistry) Renew(name string) { + r.SetTTL(name, r.defaultTTL) +} + func (r *TTNRegistry) register(name string, i interface{}) error { if _, ok := r.metrics[name]; ok { return fmt.Errorf("duplicate metric: %s", name) diff --git a/utils/stats/stats.go b/utils/stats/stats.go index 24fcd7221..0fea929d7 100644 --- a/utils/stats/stats.go +++ b/utils/stats/stats.go @@ -27,6 +27,7 @@ func Initialize() { func MarkMeter(name string) { if Enabled { metrics.GetOrRegisterMeter(name, Registry).Mark(1) + Registry.(Ticker).Renew(name) } } @@ -34,6 +35,7 @@ func MarkMeter(name string) { func UpdateHistogram(name string, value int64) { if Enabled { metrics.GetOrRegisterHistogram(name, Registry, metrics.NewUniformSample(1000)).Update(value) + Registry.(Ticker).Renew(name) } } @@ -41,6 +43,7 @@ func UpdateHistogram(name string, value int64) { func IncCounter(name string) { if Enabled { metrics.GetOrRegisterCounter(name, Registry).Inc(1) + Registry.(Ticker).Renew(name) } } @@ -48,6 +51,7 @@ func IncCounter(name string) { func DecCounter(name string) { if Enabled { metrics.GetOrRegisterCounter(name, Registry).Dec(1) + Registry.(Ticker).Renew(name) } } @@ -55,6 +59,7 @@ func DecCounter(name string) { func SetString(name, tag, value string) { if Enabled { GetOrRegisterString(name, Registry).Set(tag, value) + Registry.(Ticker).Renew(name) } } From 77f1fc78df044d188aa5b7a2ab119ea658879751 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 12 Mar 2016 21:44:37 +0100 Subject: [PATCH 1030/2266] [hotfix-paho] [hotfix] Use of unique IDs for mock Cient in MQTT --- core/adapters/mqtt/mqtt_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 6ad1ae1de..cec3b587a 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -162,7 +162,6 @@ func TestMQTTSend(t *testing.T) { checkResponses(t, test.WantResponse, resp) // Clean - <-time.After(time.Millisecond * 500) aclient.Disconnect(0) for _, sclient := range sclients { sclient.Disconnect(0) @@ -444,7 +443,7 @@ func createServers(recipients []testRecipient) ([]Client, chan []byte) { var clients []Client chresp := make(chan []byte, len(recipients)) for i, r := range recipients { - client, err := NewClient(fmt.Sprintf("FakeServerClient%d", i), brokerURL, TCP) + client, err := NewClient(fmt.Sprintf("Client%d%d", i, time.Now().UnixNano()), brokerURL, TCP) if err != nil { panic(err) } From 48e958fe3a117eb65e4f8599b9ada2af219a1a5b Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 10:19:01 +0100 Subject: [PATCH 1031/2266] [fix/paho] Temporary make use of fixed paho library --- core/adapters/mqtt/handlers/activation.go | 2 +- core/adapters/mqtt/handlers/downlink.go | 2 +- core/adapters/mqtt/handlers/mocks_test.go | 2 +- core/adapters/mqtt/mocks_test.go | 2 +- core/adapters/mqtt/mqtt.go | 2 +- core/adapters/mqtt/mqttClient.go | 2 +- core/adapters/mqtt/mqtt_test.go | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 5ce1677e3..4acef8648 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -8,7 +8,7 @@ import ( "fmt" "strings" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" diff --git a/core/adapters/mqtt/handlers/downlink.go b/core/adapters/mqtt/handlers/downlink.go index b86f49507..9e921d1e5 100644 --- a/core/adapters/mqtt/handlers/downlink.go +++ b/core/adapters/mqtt/handlers/downlink.go @@ -7,7 +7,7 @@ import ( "encoding/hex" "strings" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" diff --git a/core/adapters/mqtt/handlers/mocks_test.go b/core/adapters/mqtt/handlers/mocks_test.go index 60c551b62..4ce864531 100644 --- a/core/adapters/mqtt/handlers/mocks_test.go +++ b/core/adapters/mqtt/handlers/mocks_test.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/pointer" ) diff --git a/core/adapters/mqtt/mocks_test.go b/core/adapters/mqtt/mocks_test.go index 6bebe9c79..36a3f6bd8 100644 --- a/core/adapters/mqtt/mocks_test.go +++ b/core/adapters/mqtt/mocks_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 389928d9a..ce6ab7d10 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -7,7 +7,7 @@ import ( "sync" "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" diff --git a/core/adapters/mqtt/mqttClient.go b/core/adapters/mqtt/mqttClient.go index d0013a51c..0bc9d36c7 100644 --- a/core/adapters/mqtt/mqttClient.go +++ b/core/adapters/mqtt/mqttClient.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" "github.com/TheThingsNetwork/ttn/utils/errors" ) diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index cec3b587a..c63d89d65 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + MQTT "github.com/KtorZ/paho.mqtt.golang" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" From 2460f8a8a677e42839e0aa45fd807a9d9c53fb92 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 10:30:49 +0100 Subject: [PATCH 1032/2266] [fix/paho] Remove paho workaround and make use of fixed library --- core/adapters/mqtt/handlers/activation.go | 2 +- core/adapters/mqtt/handlers/downlink.go | 2 +- core/adapters/mqtt/handlers/downlink_test.go | 1 - core/adapters/mqtt/handlers/mocks_test.go | 4 +- core/adapters/mqtt/mocks_test.go | 13 ++-- core/adapters/mqtt/mqtt.go | 27 +++++++-- core/adapters/mqtt/mqttClient.go | 62 -------------------- core/adapters/mqtt/mqtt_test.go | 14 ++--- 8 files changed, 42 insertions(+), 83 deletions(-) delete mode 100644 core/adapters/mqtt/mqttClient.go diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go index 4acef8648..6fe4e3d0e 100644 --- a/core/adapters/mqtt/handlers/activation.go +++ b/core/adapters/mqtt/handlers/activation.go @@ -23,7 +23,7 @@ func (a Activation) Topic() string { } // Handle implements the mqtt.Handler interface -func (a Activation) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { +func (a Activation) Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { topicInfos := strings.Split(msg.Topic(), "/") if len(topicInfos) != 4 { diff --git a/core/adapters/mqtt/handlers/downlink.go b/core/adapters/mqtt/handlers/downlink.go index 9e921d1e5..c56cde9bd 100644 --- a/core/adapters/mqtt/handlers/downlink.go +++ b/core/adapters/mqtt/handlers/downlink.go @@ -23,7 +23,7 @@ func (a Downlink) Topic() string { } // Handle implements the mqtt.Handler interface -func (a Downlink) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { +func (a Downlink) Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { infos := strings.Split(msg.Topic(), "/") if len(infos) != 4 { diff --git a/core/adapters/mqtt/handlers/downlink_test.go b/core/adapters/mqtt/handlers/downlink_test.go index 65c668535..5f0a6d31e 100644 --- a/core/adapters/mqtt/handlers/downlink_test.go +++ b/core/adapters/mqtt/handlers/downlink_test.go @@ -8,7 +8,6 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/core/adapters/mqtt/handlers/mocks_test.go b/core/adapters/mqtt/handlers/mocks_test.go index 4ce864531..61e5d0526 100644 --- a/core/adapters/mqtt/handlers/mocks_test.go +++ b/core/adapters/mqtt/handlers/mocks_test.go @@ -8,7 +8,6 @@ import ( "time" MQTT "github.com/KtorZ/paho.mqtt.golang" - . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/utils/pointer" ) @@ -76,6 +75,7 @@ func (m MockMessage) Payload() []byte { // // It can also fails on demand (use the newMockClient method to define which methods should fail) type MockClient struct { + MQTT.Client InSubscribe *string InPublish MQTT.Message InUnsubscribe []string @@ -135,7 +135,7 @@ func (c *MockClient) Publish(topic string, qos byte, retained bool, payload inte return MockToken{Failure: c.Failures["Publish"]} } -func (c *MockClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { +func (c *MockClient) Subscribe(topic string, qos byte, callback MQTT.MessageHandler) MQTT.Token { c.InSubscribe = &topic return MockToken{Failure: c.Failures["Subscribe"]} } diff --git a/core/adapters/mqtt/mocks_test.go b/core/adapters/mqtt/mocks_test.go index 36a3f6bd8..b142a39ec 100644 --- a/core/adapters/mqtt/mocks_test.go +++ b/core/adapters/mqtt/mocks_test.go @@ -33,7 +33,7 @@ func (h *MockHandler) Topic() string { return h.OutTopic } -func (h *MockHandler) Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { +func (h *MockHandler) Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { h.InMessage = msg return h.Failures["Handle"] } @@ -100,17 +100,22 @@ func (m MockMessage) Payload() []byte { // // It can also fails on demand (use the newMockClient method to define which methods should fail) type MockClient struct { + MQTT.Client InSubscribe *string InPublish MQTT.Message InUnsubscribe []string - InSubscribeCallBack func(c Client, m MQTT.Message) + InSubscribeCallBack func(c MQTT.Client, m MQTT.Message) Failures map[string]*string connected bool } func NewMockClient(failures ...string) *MockClient { - client := MockClient{Failures: make(map[string]*string), connected: true, InPublish: MockMessage{}} + client := MockClient{ + Failures: make(map[string]*string), + connected: true, + InPublish: MockMessage{}, + } isFailure := func(x string) bool { for _, f := range failures { @@ -160,7 +165,7 @@ func (c *MockClient) Publish(topic string, qos byte, retained bool, payload inte return MockToken{Failure: c.Failures["Publish"]} } -func (c *MockClient) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { +func (c *MockClient) Subscribe(topic string, qos byte, callback MQTT.MessageHandler) MQTT.Token { c.InSubscribe = &topic c.InSubscribeCallBack = callback return MockToken{Failure: c.Failures["Subscribe"]} diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index ce6ab7d10..ed3f09cb4 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -4,6 +4,7 @@ package mqtt import ( + "fmt" "sync" "time" @@ -18,7 +19,7 @@ var timeout = time.Second // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { - Client + MQTT.Client ctx log.Interface packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently registrations chan RegReq // Incoming registrations @@ -27,7 +28,7 @@ type Adapter struct { // Handler defines topic-specific handler. type Handler interface { Topic() string - Handle(client Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error + Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error } // MsgRes are sent through the response channel of a pktReq or regReq @@ -58,7 +59,7 @@ const ( // NewAdapter constructs and allocates a new mqtt adapter // // The client is expected to be already connected to the right broker and ready to be used. -func NewAdapter(client Client, ctx log.Interface) *Adapter { +func NewAdapter(client MQTT.Client, ctx log.Interface) *Adapter { adapter := &Adapter{ Client: client, ctx: ctx, @@ -69,6 +70,22 @@ func NewAdapter(client Client, ctx log.Interface) *Adapter { return adapter } +// NewClient generates a new paho MQTT client from an id and a broker url +// +// The broker url is expected to contain a port if needed such as mybroker.com:87354 +// +// The scheme has to be the same as the one used by the broker: tcp, tls or web socket +func NewClient(id string, broker string, scheme Scheme) (MQTT.Client, error) { + opts := MQTT.NewClientOptions() + opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) + opts.SetClientID(id) + c := MQTT.NewClient(opts) + if token := c.Connect(); token.Wait() && token.Error() != nil { + return nil, errors.New(errors.Operational, token.Error()) + } + return c, nil +} + // Send implements the core.Adapter interface func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { stats.MarkMeter("mqtt_adapter.send") @@ -105,7 +122,7 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err // Subscribe to down channel (before publishing anything) chdown := make(chan []byte) if recipient.TopicDown() != "" { - token := a.Subscribe(recipient.TopicDown(), 2, func(client Client, msg MQTT.Message) { + token := a.Subscribe(recipient.TopicDown(), 2, func(client MQTT.Client, msg MQTT.Message) { chdown <- msg.Payload() }) if token.Wait() && token.Error() != nil { @@ -216,7 +233,7 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) func (a *Adapter) Bind(h Handler) error { ctx := a.ctx.WithField("topic", h.Topic()) ctx.Info("Subscribe new handler") - token := a.Subscribe(h.Topic(), 2, func(client Client, msg MQTT.Message) { + token := a.Subscribe(h.Topic(), 2, func(client MQTT.Client, msg MQTT.Message) { ctx.Debug("Handle new mqtt message") if err := h.Handle(client, a.packets, a.registrations, msg); err != nil { ctx.WithError(err).Warn("Unable to handle mqtt message") diff --git a/core/adapters/mqtt/mqttClient.go b/core/adapters/mqtt/mqttClient.go deleted file mode 100644 index 0bc9d36c7..000000000 --- a/core/adapters/mqtt/mqttClient.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - "time" - - MQTT "github.com/KtorZ/paho.mqtt.golang" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// Client abstracts the interface of the paho client to allow an easier testing -type Client interface { - Connect() MQTT.Token - Disconnect(quiesce uint) - IsConnected() bool - Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token - Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token - Unsubscribe(topics ...string) MQTT.Token -} - -// NewClient generates a new paho MQTT client from an id and a broker url -// -// The broker url is expected to contain a port if needed such as mybroker.com:87354 -// -// The scheme has to be the same as the one used by the broker: tcp, tls or web socket -func NewClient(id string, broker string, scheme Scheme) (Client, error) { - opts := MQTT.NewClientOptions() - opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) - opts.SetClientID(id) - c := MQTT.NewClient(opts) - if token := c.Connect(); token.Wait() && token.Error() != nil { - return nil, errors.New(errors.Operational, token.Error()) - } - return client{c}, nil -} - -// Type client abstract the MQTT client to provide a valid interface. -// This is needed because of the signature of the Subscribe() method which is using a plain MQTT -// type instead of an interface. -type client struct { - *MQTT.Client -} - -// Subscribe just implements the interface and forward the call to the actual MQTT client -func (c client) Subscribe(topic string, qos byte, callback func(c Client, m MQTT.Message)) MQTT.Token { - return c.Client.Subscribe(topic, qos, func(c *MQTT.Client, m MQTT.Message) { - callback(client{c}, m) - }) -} - -// Disconnect implements the interface and ensure that the disconnection won't panic if already -// closed -func (c client) Disconnect(quiesce uint) { - go func() { - defer func() { recover() }() - c.Client.Disconnect(quiesce) - }() - <-time.After(time.Millisecond * time.Duration(quiesce)) -} diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index c63d89d65..2e8ff7cd4 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -162,9 +162,9 @@ func TestMQTTSend(t *testing.T) { checkResponses(t, test.WantResponse, resp) // Clean - aclient.Disconnect(0) + aclient.Disconnect(250) for _, sclient := range sclients { - sclient.Disconnect(0) + sclient.Disconnect(250) } } } @@ -429,7 +429,7 @@ func (p testPacket) DevEUI() lorawan.EUI64 { } // ----- BUILD utilities -func createAdapter(t *testing.T) (Client, core.Adapter) { +func createAdapter(t *testing.T) (MQTT.Client, core.Adapter) { client, err := NewClient("testClient", brokerURL, TCP) if err != nil { panic(err) @@ -439,8 +439,8 @@ func createAdapter(t *testing.T) (Client, core.Adapter) { return client, adapter } -func createServers(recipients []testRecipient) ([]Client, chan []byte) { - var clients []Client +func createServers(recipients []testRecipient) ([]MQTT.Client, chan []byte) { + var clients []MQTT.Client chresp := make(chan []byte, len(recipients)) for i, r := range recipients { client, err := NewClient(fmt.Sprintf("Client%d%d", i, time.Now().UnixNano()), brokerURL, TCP) @@ -449,8 +449,8 @@ func createServers(recipients []testRecipient) ([]Client, chan []byte) { } clients = append(clients, client) - go func(r testRecipient, client Client) { - token := client.Subscribe(r.TopicUp, 2, func(client Client, msg MQTT.Message) { + go func(r testRecipient, client MQTT.Client) { + token := client.Subscribe(r.TopicUp, 2, func(client MQTT.Client, msg MQTT.Message) { if r.Response != nil { token := client.Publish(r.TopicDown, 2, false, r.Response) if token.Wait() && token.Error() != nil { From 071cb80eeef808a8f32530730fa67394a0cdf6da Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 15:50:31 +0100 Subject: [PATCH 1033/2266] [fixes] Remove error from udp semtech when no downlink is sent --- core/adapters/udp/handlers/semtech.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go index 9dfe6478b..5268a24ef 100644 --- a/core/adapters/udp/handlers/semtech.go +++ b/core/adapters/udp/handlers/semtech.go @@ -104,7 +104,12 @@ func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg u chresp := make(chan udp.MsgRes) packets <- udp.MsgReq{Data: data, Chresp: chresp} select { - case resp := <-chresp: + case resp, ok := <-chresp: + if !ok { + // No response + return + } + itf, err := core.UnmarshalPacket(resp) if err != nil { cherr <- errors.New(errors.Structural, err) From edd5faa8383d5745e5c7df1fc9070ca5de71b807 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 15:51:10 +0100 Subject: [PATCH 1034/2266] [fixes] Allow mqtt adapter to reconnect --- core/adapters/mqtt/mqtt.go | 26 +++++++++++++++++++++++--- core/adapters/mqtt/mqtt_test.go | 4 ++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index ed3f09cb4..9ed76f6f5 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -19,6 +19,7 @@ var timeout = time.Second // Adapter type materializes an mqtt adapter which implements the basic mqtt protocol type Adapter struct { + sync.RWMutex MQTT.Client ctx log.Interface packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently @@ -75,15 +76,19 @@ func NewAdapter(client MQTT.Client, ctx log.Interface) *Adapter { // The broker url is expected to contain a port if needed such as mybroker.com:87354 // // The scheme has to be the same as the one used by the broker: tcp, tls or web socket -func NewClient(id string, broker string, scheme Scheme) (MQTT.Client, error) { +func NewClient(id string, broker string, scheme Scheme) (MQTT.Client, <-chan error, error) { opts := MQTT.NewClientOptions() + cherr := make(chan error) opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) opts.SetClientID(id) + opts.SetConnectionLostHandler(func(client MQTT.Client, reason error) { + cherr <- reason + }) c := MQTT.NewClient(opts) if token := c.Connect(); token.Wait() && token.Error() != nil { - return nil, errors.New(errors.Operational, token.Error()) + return nil, nil, errors.New(errors.Operational, token.Error()) } - return c, nil + return c, cherr, nil } // Send implements the core.Adapter interface @@ -106,6 +111,8 @@ func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, err chresp := make(chan []byte, nb) wg := sync.WaitGroup{} wg.Add(2 * nb) + a.RLock() + defer a.RUnlock() for _, r := range recipients { // Get the actual recipient @@ -231,6 +238,8 @@ func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) // Bind registers a handler to a specific endpoint func (a *Adapter) Bind(h Handler) error { + a.RLock() + defer a.RUnlock() ctx := a.ctx.WithField("topic", h.Topic()) ctx.Info("Subscribe new handler") token := a.Subscribe(h.Topic(), 2, func(client MQTT.Client, msg MQTT.Message) { @@ -245,3 +254,14 @@ func (a *Adapter) Bind(h Handler) error { } return nil } + +// Reconnect allows the adapter to be reconnected with a new client +func (a *Adapter) Reconnect() error { + a.Lock() + defer a.Unlock() + a.Disconnect(25) + if token := a.Connect(); token.Wait() && token.Error() != nil { + return errors.New(errors.Operational, token.Error()) + } + return nil +} diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 2e8ff7cd4..933904c6f 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -430,7 +430,7 @@ func (p testPacket) DevEUI() lorawan.EUI64 { // ----- BUILD utilities func createAdapter(t *testing.T) (MQTT.Client, core.Adapter) { - client, err := NewClient("testClient", brokerURL, TCP) + client, _, err := NewClient("testClient", brokerURL, TCP) if err != nil { panic(err) } @@ -443,7 +443,7 @@ func createServers(recipients []testRecipient) ([]MQTT.Client, chan []byte) { var clients []MQTT.Client chresp := make(chan []byte, len(recipients)) for i, r := range recipients { - client, err := NewClient(fmt.Sprintf("Client%d%d", i, time.Now().UnixNano()), brokerURL, TCP) + client, _, err := NewClient(fmt.Sprintf("Client%d%d", i, time.Now().UnixNano()), brokerURL, TCP) if err != nil { panic(err) } From 73185d8bc7e89b4610d05bae281dbcc4fb46b623 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 15:51:25 +0100 Subject: [PATCH 1035/2266] [fixes] Handle mqtt adapter reconnection in cmd --- cmd/handler.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/cmd/handler.go b/cmd/handler.go index 526493707..f68da29dc 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -7,6 +7,7 @@ import ( "fmt" "path/filepath" "strings" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" @@ -56,14 +57,36 @@ The default handler is the bridge between The Things Network and applications. } brkAdapter.Bind(httpHandlers.Collect{}) - mqttClient, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) + mqttClient, cherr, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) if err != nil { ctx.WithError(err).Fatal("Could not start MQTT client") } + appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) appAdapter.Bind(mqttHandlers.Activation{}) appAdapter.Bind(mqttHandlers.Downlink{}) + // Reconnect on failures + go func() { + monitor: + for { + ctx.WithError(<-cherr).Debug("Connection lost on mqtt adapter. Trying to reconnect") + var delay time.Duration = 25 * time.Millisecond + for { + if err := appAdapter.Reconnect(); err != nil { + ctx.WithError(err).Debug("Unable to reconnect") + delay *= 10 + if delay > 10000*delay { + ctx.Fatal("All attempts to reconnect failed") + } + } else { + continue monitor + } + <-time.After(delay * time.Millisecond) + } + } + }() + if viper.GetInt("handler.status-port") > 0 { statusNet := fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) From 18c90ec0cf023fcdceb546d3196bafbc9d5e13f2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 15:51:56 +0100 Subject: [PATCH 1036/2266] [fixes] Remove frame counter from handler --- core/components/handler/handler.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index d9535c4ad..eedb0b92f 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -281,12 +281,8 @@ func (h component) abortConsume(err error, bundles []bundle) { // constructs a downlink packet from something we pulled from the gathered downlink, and, the actual // uplink. func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 bool) (BPacket, error) { - fcnt := up.FCnt() + 1 macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: entry.DevAddr, - FCnt: fcnt, - } + macPayload.FHDR = lorawan.FHDR{DevAddr: entry.DevAddr} macPayload.FPort = 1 macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ Bytes: down.Payload(), From 59b81b704dc0bd9dcfa65970db96c7f987056ea0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 17:00:12 +0100 Subject: [PATCH 1037/2266] [fixes] Make the handler responsible for the downlink framecounter --- core/components/broker/broker.go | 10 +++--- core/components/broker/storage.go | 25 ++------------ core/components/handler/devStorage.go | 50 ++++++++++++++++++++++++--- core/components/handler/handler.go | 10 +++++- core/packets.go | 5 +++ core/packets_interfaces.go | 1 + 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index b21a77593..db52df115 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -107,7 +107,7 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { // It does matter here to use the DevEUI from the entry and not from the packet. // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check - b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt(), "up") + b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt()) // 4. Then we forward the packet to the handler and wait for the response hpacket, err := NewHPacket(mEntry.AppEUI, mEntry.DevEUI, packet.Payload(), packet.Metadata()) @@ -137,12 +137,12 @@ func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { if !ok { return errors.New(errors.Operational, "Received unexpected response") } + + // TODO Compute mic check stats.MarkMeter("broker.downlink.in") - if err := bpacket.ComputeFCnt(mEntry.FCntDown); err != nil { - stats.MarkMeter("broker.downlink.invalid") - return errors.New(errors.Operational, "Received invalid response > frame counter incorrect") + if err := bpacket.SetMIC(mEntry.NwkSKey); err != nil { + return errors.New(errors.Structural, "Unable to set response MIC") } - b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, bpacket.FCnt(), "down") } // 6. And finally, we acknowledge the answer diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go index 48ca3add9..ce50e704a 100644 --- a/core/components/broker/storage.go +++ b/core/components/broker/storage.go @@ -17,11 +17,11 @@ import ( // NetworkController gives a facade for manipulating the broker databases and devices type NetworkController interface { - UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) StoreDevice(reg core.BRegistration) error StoreApplication(reg core.ARegistration) error + UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error Close() error } @@ -31,7 +31,6 @@ type devEntry struct { DevEUI lorawan.EUI64 NwkSKey lorawan.AES128Key FCntUp uint32 - FCntDown uint32 } type appEntry struct { @@ -86,7 +85,7 @@ func (s *controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { } // UpdateFCnt implements the broker.NetworkController interface -func (s *controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error { +func (s *controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { s.Lock() defer s.Unlock() itf, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) @@ -100,14 +99,7 @@ func (s *controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt entry := new(devEntry) *entry = e if entry.AppEUI == appEUI { - switch dir { - case "up": - entry.FCntUp = fcnt - case "down": - entry.FCntDown = fcnt - default: - return errors.New(errors.Implementation, "Unreckognized direction") - } + entry.FCntUp = fcnt } newEntries = append(newEntries, entry) } @@ -166,7 +158,6 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.DevEUI) rw.Write(e.NwkSKey) rw.Write(e.FCntUp) - rw.Write(e.FCntDown) return rw.Bytes() } @@ -187,16 +178,6 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { e.FCntUp = *fcnt return nil }) - rw.TryRead(func(data []byte) error { - buf := new(bytes.Buffer) - buf.Write(data) - fcnt := new(uint32) - if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil { - return err - } - e.FCntDown = *fcnt - return nil - }) return rw.Err() } diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index a0a78db82..6b8fe7b99 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -4,7 +4,10 @@ package handler import ( + "bytes" + "encoding/binary" "fmt" + "sync" . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -15,6 +18,7 @@ import ( // DevStorage gives a facade to manipulate the handler devices database type DevStorage interface { + UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) StorePersonalized(r HRegistration) error StoreActivated(r HRegistration) error @@ -26,6 +30,7 @@ type devEntry struct { DevAddr lorawan.DevAddr AppSKey lorawan.AES128Key NwkSKey lorawan.AES128Key + FCntDown uint32 } type appEntry struct { @@ -33,6 +38,7 @@ type appEntry struct { } type devStorage struct { + sync.RWMutex db dbutil.Interface Name string } @@ -44,11 +50,20 @@ func NewDevStorage(name string) (DevStorage, error) { return nil, errors.New(errors.Operational, err) } - return devStorage{db: itf, Name: "entry"}, nil + return &devStorage{db: itf, Name: "entry"}, nil } // Lookup implements the handler.DevStorage interface -func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { +func (s *devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { + return s.lookup(appEUI, devEUI, true) +} + +// lookup allow other method to re-use lookup while holding the lock +func (s *devStorage) lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64, shouldLock bool) (devEntry, error) { + if shouldLock { + s.RLock() + defer s.RUnlock() + } itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), &devEntry{}) if err != nil { return devEntry{}, err // Operational || NotFound @@ -61,7 +76,9 @@ func (s devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry } // StorePersonalized implements the handler.DevStorage interface -func (s devStorage) StorePersonalized(reg HRegistration) error { +func (s *devStorage) StorePersonalized(reg HRegistration) error { + s.Lock() + defer s.Unlock() appEUI := reg.AppEUI() devEUI := reg.DevEUI() devAddr := lorawan.DevAddr{} @@ -82,13 +99,25 @@ func (s devStorage) StorePersonalized(reg HRegistration) error { return s.db.Replace(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), e) } +// UpdateFCnt implements the handler.DevStorage interface +func (s *devStorage) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { + s.Lock() + defer s.Unlock() + devEntry, err := s.lookup(appEUI, devEUI, false) + if err != nil { + return err + } + devEntry.FCntDown = fcnt + return s.db.Replace(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), []dbutil.Entry{&devEntry}) +} + // StoreActivated implements the handler.DevStorage interface -func (s devStorage) StoreActivated(reg HRegistration) error { +func (s *devStorage) StoreActivated(reg HRegistration) error { return errors.New(errors.Implementation, "Not implemented yet") } // Close implements the handler.DevStorage interface -func (s devStorage) Close() error { +func (s *devStorage) Close() error { return s.db.Close() } @@ -99,6 +128,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.DevAddr) rw.Write(e.AppSKey) rw.Write(e.NwkSKey) + rw.Write(e.FCntDown) return rw.Bytes() } @@ -109,6 +139,16 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.DevAddr[:], data) }) rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + rw.TryRead(func(data []byte) error { + buf := new(bytes.Buffer) + buf.Write(data) + fcnt := new(uint32) + if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil { + return err + } + e.FCntDown = *fcnt + return nil + }) return rw.Err() } diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index eedb0b92f..179efb009 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -256,11 +256,16 @@ browseBundles: for i, bundle := range bundles { if best != nil && best.ID == i && down != nil && err == nil { stats.MarkMeter("handler.downlink.pull") + bpacket, err := h.buildDownlink(down, bundle.Packet, bundle.Entry, best.IsRX2) if err != nil { go h.abortConsume(errors.New(errors.Structural, err), bundles) continue browseBundles } + if err := h.devices.UpdateFCnt(b.Packet.AppEUI(), b.Packet.DevEUI(), bpacket.FCnt()); err != nil { + go h.abortConsume(errors.New(errors.Structural, err), bundles) + continue browseBundles + } bundle.Chresp <- bpacket } else { bundle.Chresp <- nil @@ -282,7 +287,10 @@ func (h component) abortConsume(err error, bundles []bundle) { // uplink. func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 bool) (BPacket, error) { macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{DevAddr: entry.DevAddr} + macPayload.FHDR = lorawan.FHDR{ + FCnt: entry.FCntDown + 1, + DevAddr: entry.DevAddr, + } macPayload.FPort = 1 macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ Bytes: down.Payload(), diff --git a/core/packets.go b/core/packets.go index ff0df0903..d07aef92e 100644 --- a/core/packets.go +++ b/core/packets.go @@ -249,6 +249,11 @@ func (p *bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { return p.baserpacket.payload.ValidateMIC(key) } +// SetMIC implements the core.BPacket interface +func (p *bpacket) SetMIC(key lorawan.AES128Key) error { + return p.payload.SetMIC(key) +} + // Commands implements the core.BPacket interface func (p bpacket) Commands() []lorawan.MACCommand { return p.baserpacket.payload.MACPayload.(*lorawan.MACPayload).FHDR.FOpts diff --git a/core/packets_interfaces.go b/core/packets_interfaces.go index 8036d5cde..2de0cc816 100644 --- a/core/packets_interfaces.go +++ b/core/packets_interfaces.go @@ -40,6 +40,7 @@ type BPacket interface { FCnt() uint32 Metadata() Metadata Payload() lorawan.PHYPayload + SetMIC(key lorawan.AES128Key) error ValidateMIC(key lorawan.AES128Key) (bool, error) } From e87cae4946ad9fbe0a216c44c13909cea68d6a46 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 17:13:52 +0100 Subject: [PATCH 1038/2266] [fixes] Fix handler tests with FCnt --- core/components/handler/handler_test.go | 7 +++++-- core/components/handler/mocks_test.go | 10 ++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 614d2f147..5cfc80351 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -442,7 +442,7 @@ func TestHandleUp(t *testing.T) { Codr: pointer.String("4/5"), Size: pointer.Uint(21), }, - 11, + 4, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) appResp, _ := NewAPacket( @@ -458,6 +458,7 @@ func TestHandleUp(t *testing.T) { DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + FCntDown: 3, } pktStorage := newMockPktStorage() pktStorage.OutPull = appResp @@ -852,6 +853,7 @@ func TestHandleUp(t *testing.T) { DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + FCntDown: 3, } pktStorage := newMockPktStorage() pktStorage.OutPull = appResp @@ -916,7 +918,7 @@ func TestHandleUp(t *testing.T) { Codr: pointer.String("4/5"), Size: pointer.Uint(21), }, - 11, + 15, [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, ) appResp, _ := NewAPacket( @@ -932,6 +934,7 @@ func TestHandleUp(t *testing.T) { DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, + FCntDown: 14, } pktStorage := newMockPktStorage() pktStorage.OutPull = appResp diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index c322f934c..8e46ff8a5 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -25,6 +25,9 @@ type mockDevStorage struct { InLookupDevEUI lorawan.EUI64 InStorePersonalized HRegistration InStoreActivated HRegistration + InUpdateAppEUI lorawan.EUI64 + InUpdateDevEUI lorawan.EUI64 + InUpdateFcnt uint32 } func newMockDevStorage() *mockDevStorage { @@ -48,6 +51,13 @@ func (s *mockDevStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (dev return s.OutLookup, nil } +func (s *mockDevStorage) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { + s.InUpdateAppEUI = appEUI + s.InUpdateDevEUI = devEUI + s.InUpdateFcnt = fcnt + return s.Failures["UpdateFCnt"] +} + func (s *mockDevStorage) StorePersonalized(r HRegistration) error { s.InStorePersonalized = r return s.Failures["StorePersonalized"] From 20d21f1aee54b46bd32f138ebd4c0b0760030f69 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 14 Mar 2016 17:18:40 +0100 Subject: [PATCH 1039/2266] [fixes] Fix broker tests when removing FCntDown --- core/components/broker/broker_test.go | 63 +------------------- core/components/broker/mock_test.go | 4 +- core/components/broker/storage_test.go | 82 +------------------------- 3 files changed, 5 insertions(+), 144 deletions(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 7e99d3b32..15ede4b02 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -150,7 +150,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) CheckCounters(t, 0, store.InUpdateFCnt) - CheckDirections(t, "", store.InUpdateDir) } // ------------------- @@ -175,7 +174,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) CheckCounters(t, 0, store.InUpdateFCnt) - CheckDirections(t, "", store.InUpdateDir) } // ------------------- @@ -220,7 +218,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) CheckCounters(t, 0, store.InUpdateFCnt) - CheckDirections(t, "", store.InUpdateDir) } // ------------------- @@ -275,7 +272,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) CheckCounters(t, 5, store.InUpdateFCnt) - CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -323,7 +319,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) CheckCounters(t, 5, store.InUpdateFCnt) - CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -378,7 +373,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) CheckCounters(t, 5, store.InUpdateFCnt) - CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -428,8 +422,7 @@ func TestHandleUp(t *testing.T) { CheckRegistrations(t, nil, store.InStoreApp) mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 1, store.InUpdateFCnt) - CheckDirections(t, "down", store.InUpdateDir) + CheckCounters(t, 5, store.InUpdateFCnt) } // ------------------- @@ -478,7 +471,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) CheckCounters(t, 5, store.InUpdateFCnt) - CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -534,58 +526,6 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, hpacket, adapter.InSendPacket) mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) CheckCounters(t, 5, store.InUpdateFCnt) - CheckDirections(t, "up", store.InUpdateDir) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 downlink invalid counter") - - // Build - an := mocks.NewMockAckNacker() - recipient := mocks.NewMockRecipient() - adapter := mocks.NewMockAdapter() - resp := newBPacketDown(55000) - data, _ := resp.MarshalBinary() - adapter.OutSend = data - adapter.OutGetRecipient = recipient - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Uplink", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ = bpacket.MarshalBinary() - hpacket, _ := core.NewHPacket( - store.OutLookupDevices[0].AppEUI, - store.OutLookupDevices[0].DevEUI, - bpacket.Payload(), - bpacket.Metadata(), - ) - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, hpacket, adapter.InSendPacket) - mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - CheckDirections(t, "up", store.InUpdateDir) } // ------------------- @@ -617,6 +557,5 @@ func TestHandleUp(t *testing.T) { mocks.CheckSent(t, nil, adapter.InSendPacket) mocks.CheckRecipients(t, nil, adapter.InSendRecipients) CheckCounters(t, 0, store.InUpdateFCnt) - CheckDirections(t, "", store.InUpdateDir) } } diff --git a/core/components/broker/mock_test.go b/core/components/broker/mock_test.go index 4b91a45a8..0c7df344a 100644 --- a/core/components/broker/mock_test.go +++ b/core/components/broker/mock_test.go @@ -17,7 +17,6 @@ type mockController struct { InUpdateAppEUI lorawan.EUI64 InUpdateDevEUI lorawan.EUI64 InUpdateFCnt uint32 - InUpdateDir string OutLookupDevices []devEntry OutLookupApp appEntry } @@ -66,11 +65,10 @@ func (s *mockController) StoreApplication(reg core.ARegistration) error { return s.Failures["StoreApplication"] } -func (s *mockController) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32, dir string) error { +func (s *mockController) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { s.InUpdateAppEUI = appEUI s.InUpdateDevEUI = devEUI s.InUpdateFCnt = fcnt - s.InUpdateDir = dir return s.Failures["UpdateFCnt"] } diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go index be59080dc..5a5cf0f09 100644 --- a/core/components/broker/storage_test.go +++ b/core/components/broker/storage_test.go @@ -56,7 +56,6 @@ func TestNetworkControllerDevice(t *testing.T) { NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), FCntUp: 0, - FCntDown: 0, }, } @@ -91,7 +90,6 @@ func TestNetworkControllerDevice(t *testing.T) { NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), FCntUp: 0, - FCntDown: 0, }, { AppEUI: r.AppEUI(), @@ -99,7 +97,6 @@ func TestNetworkControllerDevice(t *testing.T) { NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), FCntUp: 0, - FCntDown: 0, }, } @@ -198,7 +195,7 @@ func TestNetworkControllerDevice(t *testing.T) { // Operate err := db.StoreDevice(r) CheckErrors(t, nil, err) - err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "up") + err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14) entries, err2 := db.LookupDevices(r.DevEUI()) // Expectations @@ -209,7 +206,6 @@ func TestNetworkControllerDevice(t *testing.T) { NwkSKey: r.NwkSKey(), Recipient: r.RawRecipient(), FCntUp: 14, - FCntDown: 0, }, } @@ -222,76 +218,6 @@ func TestNetworkControllerDevice(t *testing.T) { // ------------------- - { - Desc(t, "Update counter down of an entry -> one device") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - r.OutDevEUI[4] = 0xbb - - // Operate - err := db.StoreDevice(r) - CheckErrors(t, nil, err) - err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "down") - entries, err2 := db.LookupDevices(r.DevEUI()) - - // Expectations - want := []devEntry{ - { - AppEUI: r.AppEUI(), - DevEUI: r.DevEUI(), - NwkSKey: r.NwkSKey(), - Recipient: r.RawRecipient(), - FCntUp: 0, - FCntDown: 14, - }, - } - - // Check - CheckErrors(t, nil, err1) - CheckErrors(t, nil, err2) - CheckDevEntries(t, want, entries) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Update counter with wrong direction") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - r.OutDevEUI[4] = 0xbd - - // Operate - err := db.StoreDevice(r) - CheckErrors(t, nil, err) - err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "patate") - entries, err2 := db.LookupDevices(r.DevEUI()) - - // Expectations - want := []devEntry{ - { - AppEUI: r.AppEUI(), - DevEUI: r.DevEUI(), - NwkSKey: r.NwkSKey(), - Recipient: r.RawRecipient(), - FCntUp: 0, - FCntDown: 0, - }, - } - - // Checks - CheckErrors(t, pointer.String(string(errors.Implementation)), err1) - CheckErrors(t, nil, err2) - CheckDevEntries(t, want, entries) - _ = db.Close() - } - - // ------------------- - { Desc(t, "Update counter -> fail to lookup") @@ -301,7 +227,7 @@ func TestNetworkControllerDevice(t *testing.T) { r.OutDevEUI[4] = 0xde // Operate - err := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14, "up") + err := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14) // Checks CheckErrors(t, pointer.String(string(errors.NotFound)), err) @@ -326,7 +252,7 @@ func TestNetworkControllerDevice(t *testing.T) { CheckErrors(t, nil, err) err = db.StoreDevice(r2) CheckErrors(t, nil, err) - err1 := db.UpdateFCnt(r2.AppEUI(), r2.DevEUI(), 14, "up") + err1 := db.UpdateFCnt(r2.AppEUI(), r2.DevEUI(), 14) entries, err2 := db.LookupDevices(r2.DevEUI()) // Expectations @@ -337,7 +263,6 @@ func TestNetworkControllerDevice(t *testing.T) { NwkSKey: r1.NwkSKey(), Recipient: r1.RawRecipient(), FCntUp: 0, - FCntDown: 0, }, { AppEUI: r2.AppEUI(), @@ -345,7 +270,6 @@ func TestNetworkControllerDevice(t *testing.T) { NwkSKey: r2.NwkSKey(), Recipient: r2.RawRecipient(), FCntUp: 14, - FCntDown: 0, }, } From 8debb30ef8f754b83e159c4deeeb81bce006fa54 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 11:30:08 +0100 Subject: [PATCH 1040/2266] [refactor/grpc] Move udp/semtech adapter to grpc --- core/adapters/udp/conversions.go | 187 +++++++++ .../udp/handlers/build_utilities_test.go | 383 ------------------ .../adapters/udp/handlers/conversions_test.go | 94 ----- core/adapters/udp/handlers/semtech.go | 227 ----------- core/adapters/udp/handlers/semtech_test.go | 135 ------ core/adapters/udp/helpers_test.go | 59 --- core/adapters/udp/mock_test.go | 33 -- core/adapters/udp/udp.go | 242 ++++++----- core/adapters/udp/udpAckNacker.go | 40 -- core/adapters/udp/udpRegistration.go | 22 - core/adapters/udp/udp_test.go | 299 -------------- 11 files changed, 317 insertions(+), 1404 deletions(-) create mode 100644 core/adapters/udp/conversions.go delete mode 100644 core/adapters/udp/handlers/build_utilities_test.go delete mode 100644 core/adapters/udp/handlers/conversions_test.go delete mode 100644 core/adapters/udp/handlers/semtech.go delete mode 100644 core/adapters/udp/handlers/semtech_test.go delete mode 100644 core/adapters/udp/helpers_test.go delete mode 100644 core/adapters/udp/mock_test.go delete mode 100644 core/adapters/udp/udpAckNacker.go delete mode 100644 core/adapters/udp/udpRegistration.go delete mode 100644 core/adapters/udp/udp_test.go diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go new file mode 100644 index 000000000..956ddb6b6 --- /dev/null +++ b/core/adapters/udp/conversions.go @@ -0,0 +1,187 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "encoding/base64" + "reflect" + "strings" + + "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" +) + +func (a adapter) newDataRouterReq(rxpk semtech.RXPK, gid []byte) (*core.DataRouterReq, error) { + // First, we have to get the physical payload which is encoded in the Data field + if rxpk.Data == nil { + return nil, errors.New(errors.Structural, "There's no data in the packet") + } + + // RXPK Data are base64 encoded, yet without the trailing "==" if any..... + encoded := *rxpk.Data + switch len(encoded) % 4 { + case 2: + encoded += "==" + case 3: + encoded += "=" + } + raw, err := base64.StdEncoding.DecodeString(encoded) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + payload := lorawan.NewPHYPayload(true) + if err = payload.UnmarshalBinary(raw); err != nil { + return nil, errors.New(errors.Structural, err) + } + + macpayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + // TODO OTAA join request payloads + return nil, errors.New(errors.Implementation, "Unhandled Physical payload. Expected a MACPayload") + } + if len(macpayload.FRMPayload) != 1 { + // TODO Handle pure MAC Commands payloads (FType = 0) + return nil, errors.New(errors.Implementation, "Unhandled Physical payload. Expected a Data Payload") + } + frmpayload, err := macpayload.FRMPayload[0].MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + var fopts [][]byte + for _, cmd := range macpayload.FHDR.FOpts { + if data, err := cmd.MarshalBinary(); err == nil { // We just ignore invalid MAC Commands + fopts = append(fopts, data) + } + } + + // At the end, our converted packet hold the same metadata than the RXPK packet but the Data + // which as been completely transformed into a lorawan Physical Payload. + return &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + }, + FOpts: fopts, + }, + FPort: uint32(macpayload.FPort), + FRMPayload: frmpayload, + }, + MIC: payload.MIC[:], + }, + Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), + GatewayID: gid, + }, nil +} + +func (a adapter) newTXPK(resp core.DataRouterRes) (semtech.TXPK, error) { + // Step 0: validate the response + var p *core.LoRaWANData + + // Validation::0 -> Payload is present + if p = resp.Payload; p == nil { + return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Payload") + } + + // Validation::1 -> All required fields are there + if p.MHDR == nil || p.MACPayload == nil || p.MACPayload.FHDR == nil || p.MACPayload.FHDR.FCtrl == nil { + return semtech.TXPK{}, errors.New(errors.Structural, "Invalid Payload Structure") + } + + // Validation::2 -> Metadata is present + if resp.Metadata == nil { + return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Metadata") + } + + // Validation::3 -> The MIC is 4-bytes long + if len(p.MIC) != 4 { + return semtech.TXPK{}, errors.New(errors.Structural, "Invalid MIC") + } + + // Validation::4 -> Device address is 4-bytes long + if len(p.MACPayload.FHDR.DevAddr) != 4 { + return semtech.TXPK{}, errors.New(errors.Structural, "Invalid Device Address") + } + + mac, mhdr, fhdr, fctrl := p.MACPayload, p.MHDR, p.MACPayload.FHDR, p.MACPayload.FHDR.FCtrl + + // Step 1: create a new LoRaWAN payload + macpayload := lorawan.NewMACPayload(false) + macpayload.FPort = uint8(mac.FPort) // Validation::1 + copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) // Validation::1 && Validation::4 + macpayload.FHDR.FCnt = fhdr.FCnt // Validation::1 + for _, data := range fhdr.FOpts { // Validation::1 + cmd := new(lorawan.MACCommand) + if err := cmd.UnmarshalBinary(data); err == nil { // We ignore invalid commands + macpayload.FHDR.FOpts = append(macpayload.FHDR.FOpts, *cmd) + } + } + macpayload.FHDR.FCtrl.ADR = fctrl.ADR // Validation::1 + macpayload.FHDR.FCtrl.ACK = fctrl.Ack // Validation::1 + macpayload.FHDR.FCtrl.ADRACKReq = fctrl.ADRAckReq // Validation::1 + macpayload.FHDR.FCtrl.FPending = fctrl.FPending // Validation::1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ // Validation::1 + Bytes: mac.FRMPayload, + }} + payload := lorawan.NewPHYPayload(false) + payload.MHDR.MType = lorawan.MType(mhdr.MType) // Validation::1 + payload.MHDR.Major = lorawan.Major(mhdr.Major) // Validation::1 + copy(payload.MIC[:], resp.Payload.MIC) // Validation::1 && Validation::3 + payload.MACPayload = macpayload + + // Step2: Convert the physical payload to a base64 string (without the padding) + raw, err := payload.MarshalBinary() + if err != nil { + return semtech.TXPK{}, errors.New(errors.Structural, err) + } + data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + txpk := semtech.TXPK{Data: pointer.String(data)} + + // Step 3, copy every compatible metadata from the packet to the TXPK packet. + // We are possibly loosing information here. + injectMetadata(&txpk, *resp.Metadata) // Validation::2 + return txpk, nil +} + +func injectMetadata(xpk interface{}, src interface{}) interface{} { + v := reflect.ValueOf(src) + t := v.Type() + d := reflect.ValueOf(xpk).Elem() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i).Name + if d.FieldByName(field).CanSet() { + d.FieldByName(field).Set(v.Field(i)) + } + } + return xpk +} + +func extractMetadata(xpk interface{}, target interface{}) interface{} { + v := reflect.ValueOf(xpk) + t := v.Type() + m := reflect.ValueOf(target).Elem() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i).Name + if m.FieldByName(field).CanSet() { + m.FieldByName(field).Set(v.Field(i)) + } + } + return target +} diff --git a/core/adapters/udp/handlers/build_utilities_test.go b/core/adapters/udp/handlers/build_utilities_test.go deleted file mode 100644 index ba91eb9d4..000000000 --- a/core/adapters/udp/handlers/build_utilities_test.go +++ /dev/null @@ -1,383 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "encoding/base64" - "fmt" - "net" - "strings" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/udp" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -// ----- build utilities -type mockServer struct { - conn *net.UDPConn - response chan semtech.Packet -} - -// Generate a mock server that will send packet through a udp connection and communicate back -// received packet. -func genMockServer(port uint) mockServer { - addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("0.0.0.0:%d", port)) - if err != nil { - panic(err) - } - - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - panic(err) - } - - response := make(chan semtech.Packet) - go func() { - for { - buf := make([]byte, 5000) - n, _, err := conn.ReadFromUDP(buf) - if err != nil { - panic(err) - } - packet := new(semtech.Packet) - if err = packet.UnmarshalBinary(buf[:n]); err != nil { - panic(err) - } - response <- *packet - } - }() - - return mockServer{conn: conn, response: response} -} - -// Send a packet through the udp mock server toward the adapter -func (s mockServer) send(p semtech.Packet) semtech.Packet { - raw, err := p.MarshalBinary() - if err != nil { - panic(err) - } - s.conn.Write(raw) - select { - case packet := <-s.response: - return packet - case <-time.After(100 * time.Millisecond): - return semtech.Packet{} - } -} - -// Generates an adapter as well as a channel that behaves like the Next() methods (but can be used -// in a select for timeout) -func genAdapter(t *testing.T, port uint) (*udp.Adapter, chan interface{}) { - // Logging - ctx := GetLogger(t, "Adapter") - - net := fmt.Sprintf("0.0.0.0:%d", port) - adapter, err := udp.NewAdapter(net, ctx) - if err != nil { - panic(err) - } - adapter.Bind(Semtech{}) - next := make(chan interface{}) - go func() { - for { - packet, _, err := adapter.Next() - next <- struct { - err error - packet []byte - }{err: err, packet: packet} - } - }() - return adapter, next -} - -// Generate a core packet from a semtech packet that has one RXPK -func genCorePacket(p semtech.Packet) core.Packet { - if p.Payload == nil || len(p.Payload.RXPK) != 1 { - panic("Expected a payload with one rxpk") - } - packet, err := rxpk2packet(p.Payload.RXPK[0], p.GatewayId) - if err != nil { - panic(err) - } - return packet -} - -func genPUSHDATANoRXPK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Token: token, - Identifier: semtech.PUSH_DATA, - } -} - -func genPUSHDATANoPayload(token []byte) semtech.Packet { - packet := genPUSHDATAWithRXPK(token) - packet.Payload.RXPK[0].Data = nil - return packet -} - -func genPUSHDATAWithRXPK(token []byte) semtech.Packet { - packet := genPUSHDATANoRXPK(token) - packet.Payload = &semtech.Payload{ - RXPK: []semtech.RXPK{ - semtech.RXPK{ - Rssi: pointer.Int(-60), - Codr: pointer.String("4/7"), - Data: pointer.String(genRXPKData()), - }, - }, - } - return packet -} - -func genPULLACK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - Identifier: semtech.PULL_ACK, - } -} - -func genPUSHACK(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - Identifier: semtech.PUSH_ACK, - } -} - -func genPULLDATA(token []byte) semtech.Packet { - return semtech.Packet{ - Version: semtech.VERSION, - Token: token, - GatewayId: []byte{0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8}, - Identifier: semtech.PULL_DATA, - } -} - -func genPHYPayload(uplink bool) lorawan.PHYPayload { - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(uplink) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(uplink) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} - -func genRXPKData() string { - // 1. Generate a physical payload - payload := genPHYPayload(true) - - // 2. Generate a JSON payload received by the server - raw, err := payload.MarshalBinary() - if err != nil { - panic(err) - } - return base64.StdEncoding.EncodeToString(raw) -} - -// Generate a Metadata object that matches RXPK metadata -func genMetadata(RXPK semtech.RXPK) core.Metadata { - return core.Metadata{ - Chan: RXPK.Chan, - Codr: RXPK.Codr, - Freq: RXPK.Freq, - Lsnr: RXPK.Lsnr, - Modu: RXPK.Modu, - Rfch: RXPK.Rfch, - Rssi: RXPK.Rssi, - Size: RXPK.Size, - Stat: RXPK.Stat, - Time: RXPK.Time, - Tmst: RXPK.Tmst, - } -} - -// Generates a Metadata object with all field completed with relevant values -func genFullMetadata() core.Metadata { - timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) - return core.Metadata{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Datr: pointer.String("LORA"), - Fdev: pointer.Uint(3), - Freq: pointer.Float64(863.125), - Imme: pointer.Bool(false), - Ipol: pointer.Bool(false), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Ncrc: pointer.Bool(true), - Powe: pointer.Uint(3), - Prea: pointer.Uint(8), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(14), - Stat: pointer.Int(0), - Time: pointer.Time(timeRef), - Tmst: pointer.Uint(uint(timeRef.UnixNano())), - } -} - -// Generate an RXPK packet using the given payload as Data -func genRXPK(phyPayload lorawan.PHYPayload) semtech.RXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - - return semtech.RXPK{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Data: pointer.String(data), - Freq: pointer.Float64(863.125), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(uint(len([]byte(data)))), - Stat: pointer.Int(0), - Time: pointer.Time(time.Now()), - Tmst: pointer.Uint(uint(time.Now().UnixNano())), - } -} - -// Generates a TXPK packet using the given payload and the given metadata -func genTXPK(phyPayload lorawan.PHYPayload, metadata core.Metadata) semtech.TXPK { - raw, err := phyPayload.MarshalBinary() - if err != nil { - panic(err) - } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - return semtech.TXPK{ - Codr: metadata.Codr, - Data: pointer.String(data), - Datr: metadata.Datr, - Fdev: metadata.Fdev, - Freq: metadata.Freq, - Imme: metadata.Imme, - Ipol: metadata.Ipol, - Modu: metadata.Modu, - Ncrc: metadata.Ncrc, - Powe: metadata.Powe, - Prea: metadata.Prea, - Rfch: metadata.Rfch, - Size: metadata.Size, - Time: metadata.Time, - Tmst: metadata.Tmst, - } -} - -// Generates a test suite where the RXPK is fully complete -func genRXPKWithFullMetadata(test *convertRXPKTest) convertRXPKTest { - phyPayload := genPHYPayload(true) - rxpk := genRXPK(phyPayload) - metadata := genMetadata(rxpk) - test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the RXPK contains partial metadata -func genRXPKWithPartialMetadata(test *convertRXPKTest) convertRXPKTest { - phyPayload := genPHYPayload(true) - rxpk := genRXPK(phyPayload) - rxpk.Codr = nil - rxpk.Rfch = nil - rxpk.Rssi = nil - rxpk.Time = nil - rxpk.Size = nil - metadata := genMetadata(rxpk) - test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the RXPK contains no data -func genRXPKWithNoData(test *convertRXPKTest) convertRXPKTest { - rxpk := genRXPK(genPHYPayload(true)) - rxpk.Data = nil - test.RXPK = rxpk - return *test -} - -// Generates a test suite where the core packet has all txpk metadata -func genCoreFullMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - metadata.Chan = nil - metadata.Lsnr = nil - metadata.Rssi = nil - metadata.Stat = nil - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) - return *test -} - -// Generates a test suite where the core packet has no metadata -func genCoreNoMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := core.Metadata{} - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) - return *test -} - -// Generates a test suite where the core packet has partial metadata but all supported -func genCorePartialMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - metadata.Chan = nil - metadata.Lsnr = nil - metadata.Rssi = nil - metadata.Stat = nil - metadata.Modu = nil - metadata.Fdev = nil - metadata.Time = nil - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) - return *test -} - -// Generates a test suite where the core packet has extra metadata not supported by txpk -func genCoreExtraMetadata(test *convertToTXPKTest) convertToTXPKTest { - phyPayload := genPHYPayload(false) - metadata := genFullMetadata() - test.TXPK = genTXPK(phyPayload, metadata) - test.CorePacket, _ = core.NewRPacket(phyPayload, []byte{1, 2, 3, 4, 5, 6, 7, 8}, metadata) - return *test -} diff --git a/core/adapters/udp/handlers/conversions_test.go b/core/adapters/udp/handlers/conversions_test.go deleted file mode 100644 index 3e85f5a1e..000000000 --- a/core/adapters/udp/handlers/conversions_test.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "reflect" - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -type convertRXPKTest struct { - CorePacket core.Packet - RXPK semtech.RXPK - WantError *string -} - -func TestConvertRXPKPacket(t *testing.T) { - tests := []convertRXPKTest{ - genRXPKWithFullMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithPartialMetadata(&convertRXPKTest{WantError: nil}), - genRXPKWithNoData(&convertRXPKTest{WantError: pointer.String(string(errors.Structural))}), - } - - for _, test := range tests { - Desc(t, "Convert RXPK: %s", pointer.DumpPStruct(test.RXPK, false)) - packet, err := rxpk2packet(test.RXPK, []byte{1, 2, 3, 4, 5, 6, 7, 8}) - CheckErrors(t, test.WantError, err) - checkPackets(t, test.CorePacket, packet) - } -} - -type convertToTXPKTest struct { - TXPK semtech.TXPK - CorePacket core.RPacket - WantError *string -} - -func TestConvertTXPKPacket(t *testing.T) { - tests := []convertToTXPKTest{ - genCoreFullMetadata(&convertToTXPKTest{WantError: nil}), - genCorePartialMetadata(&convertToTXPKTest{WantError: nil}), - genCoreExtraMetadata(&convertToTXPKTest{WantError: nil}), - genCoreNoMetadata(&convertToTXPKTest{WantError: nil}), - } - - for _, test := range tests { - Desc(t, "Convert to TXPK: %s", test.CorePacket.String()) - txpk, err := packet2txpk(test.CorePacket) - CheckErrors(t, test.WantError, err) - checkTXPKs(t, test.TXPK, txpk) - } -} - -// ----- CHECK utilities - -// Checks that obtained TXPK matches expeceted one -func checkTXPKs(t *testing.T, want semtech.TXPK, got semtech.TXPK) { - if reflect.DeepEqual(want, got) { - Ok(t, "check TXPKs") - return - } - Ko(t, "Converted TXPK does not match expectations. \nWant: %s\nGot: %s", pointer.DumpPStruct(want, false), pointer.DumpPStruct(got, false)) -} - -// Checks that two packets match -func checkPackets(t *testing.T, want core.Packet, got core.Packet) { - if want == nil { - if got == nil { - Ok(t, "Check packets") - return - } - Ko(t, "No packet was expected but got %s", got.String()) - return - } - - if got == nil { - Ko(t, "Was expecting %s but got nothing", want.String()) - return - } - - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") - return - } - - Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) -} diff --git a/core/adapters/udp/handlers/semtech.go b/core/adapters/udp/handlers/semtech.go deleted file mode 100644 index 5268a24ef..000000000 --- a/core/adapters/udp/handlers/semtech.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "encoding/base64" - "fmt" - "reflect" - "strings" - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/udp" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/brocaar/lorawan" -) - -// Semtech implements the Semtech protocol and make a bridge between gateways and routers -type Semtech struct{} - -// Handle implements the udp.Handler interface -func (s Semtech) Handle(conn chan<- udp.MsgUDP, packets chan<- udp.MsgReq, msg udp.MsgUDP) error { - pkt := new(semtech.Packet) - err := pkt.UnmarshalBinary(msg.Data) - if err != nil { - return errors.New(errors.Structural, err) - } - - switch pkt.Identifier { - case semtech.PULL_DATA: // PULL_DATA -> Respond to the recipient with an ACK - stats.MarkMeter("semtech_adapter.pull_data") - stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.pull_data", pkt.GatewayId)) - stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_pull_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) - - data, err := semtech.Packet{ - Version: semtech.VERSION, - Token: pkt.Token, - Identifier: semtech.PULL_ACK, - }.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - conn <- udp.MsgUDP{ - Addr: msg.Addr, - Data: data, - } - case semtech.PUSH_DATA: // PUSH_DATA -> Transfer all RXPK to the component - stats.MarkMeter("semtech_adapter.push_data") - stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) - stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) - - data, err := semtech.Packet{ - Version: semtech.VERSION, - Token: pkt.Token, - Identifier: semtech.PUSH_ACK, - }.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - conn <- udp.MsgUDP{ - Addr: msg.Addr, - Data: data, - } - - if pkt.Payload == nil { - return errors.New(errors.Structural, "Unable to process empty PUSH_DATA payload") - } - - // Handle stat payload - if pkt.Payload.Stat != nil { - spacket, err := core.NewSPacket(pkt.GatewayId, extractMetadata(*pkt.Payload.Stat)) - if err == nil { - data, err := spacket.MarshalBinary() - if err == nil { - go func() { - packets <- udp.MsgReq{Data: data, Chresp: nil} - }() - } - } - } - - // Handle rxpks payloads - cherr := make(chan error, len(pkt.Payload.RXPK)) - wait := sync.WaitGroup{} - wait.Add(len(pkt.Payload.RXPK)) - for _, rxpk := range pkt.Payload.RXPK { - go func(rxpk semtech.RXPK) { - defer wait.Done() - pktOut, err := rxpk2packet(rxpk, pkt.GatewayId) - if err != nil { - cherr <- errors.New(errors.Structural, err) - return - } - data, err := pktOut.MarshalBinary() - if err != nil { - cherr <- errors.New(errors.Structural, err) - return - } - chresp := make(chan udp.MsgRes) - packets <- udp.MsgReq{Data: data, Chresp: chresp} - select { - case resp, ok := <-chresp: - if !ok { - // No response - return - } - - itf, err := core.UnmarshalPacket(resp) - if err != nil { - cherr <- errors.New(errors.Structural, err) - return - } - pkt, ok := itf.(core.RPacket) // NOTE Here we'll handle join-accept - if !ok { - cherr <- errors.New(errors.Structural, "Unhandled packet type") - return - } - txpk, err := packet2txpk(pkt) - if err != nil { - cherr <- errors.New(errors.Structural, err) - return - } - - data, err := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{TXPK: &txpk}, - }.MarshalBinary() - if err != nil { - cherr <- errors.New(errors.Structural, err) - return - } - conn <- udp.MsgUDP{Addr: msg.Addr, Data: data} - case <-time.After(time.Second * 2): - } - }(rxpk) - } - wait.Wait() - close(cherr) - if err := <-cherr; err != nil { - return err - } - default: - return errors.New(errors.Implementation, "Unhandled packet type") - } - return nil -} - -func rxpk2packet(p semtech.RXPK, gid []byte) (core.Packet, error) { - // First, we have to get the physical payload which is encoded in the Data field - if p.Data == nil { - return nil, errors.New(errors.Structural, "There's no data in the packet") - } - - // RXPK Data are base64 encoded, yet without the trailing "==" if any..... - encoded := *p.Data - switch len(encoded) % 4 { - case 2: - encoded += "==" - case 3: - encoded += "=" - } - - raw, err := base64.StdEncoding.DecodeString(encoded) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - payload := lorawan.NewPHYPayload(true) - if err = payload.UnmarshalBinary(raw); err != nil { - return nil, errors.New(errors.Structural, err) - } - // At the end, our converted packet hold the same metadata than the RXPK packet but the Data - // which as been completely transformed into a lorawan Physical Payload. - return core.NewRPacket(payload, gid, extractMetadata(p)) -} - -func packet2txpk(p core.RPacket) (semtech.TXPK, error) { - // Step 1, convert the physical payload to a base64 string (without the padding) - raw, err := p.Payload().MarshalBinary() - if err != nil { - return semtech.TXPK{}, errors.New(errors.Structural, err) - } - - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") - txpk := semtech.TXPK{Data: pointer.String(data)} - - // Step 2, copy every compatible metadata from the packet to the TXPK packet. - // We are possibly loosing information here. - injectMetadata(&txpk, p.Metadata()) - - return txpk, nil -} - -func injectMetadata(ptr interface{}, metadata core.Metadata) { - v := reflect.ValueOf(metadata) - t := v.Type() - d := reflect.ValueOf(ptr).Elem() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i).Name - if d.FieldByName(field).CanSet() { - d.FieldByName(field).Set(v.Field(i)) - } - } -} - -func extractMetadata(xpk interface{}) core.Metadata { - metadata := core.Metadata{} - v := reflect.ValueOf(xpk) - t := v.Type() - m := reflect.ValueOf(&metadata).Elem() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i).Name - if m.FieldByName(field).CanSet() { - m.FieldByName(field).Set(v.Field(i)) - } - } - - return metadata -} diff --git a/core/adapters/udp/handlers/semtech_test.go b/core/adapters/udp/handlers/semtech_test.go deleted file mode 100644 index 107677b9e..000000000 --- a/core/adapters/udp/handlers/semtech_test.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "reflect" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/udp" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestSend(t *testing.T) { - Desc(t, "Send is not supported") - adapter, _ := genAdapter(t, 33000) - _, err := adapter.Send(nil) - CheckErrors(t, pointer.String(string(errors.Implementation)), err) -} - -func TestNextRegistration(t *testing.T) { - Desc(t, "Next registration is not supported") - adapter, _ := genAdapter(t, 33001) - _, _, err := adapter.NextRegistration() - CheckErrors(t, pointer.String(string(errors.Implementation)), err) -} - -func TestNext(t *testing.T) { - adapter, next := genAdapter(t, 33002) - server := genMockServer(33002) - - tests := []struct { - Adapter *udp.Adapter - Packet semtech.Packet - WantAck semtech.Packet - WantNext core.Packet - WantError *string - }{ - { // Valid uplink PUSH_DATA - Adapter: adapter, - Packet: genPUSHDATAWithRXPK([]byte{0x14, 0x42}), - WantAck: genPUSHACK([]byte{0x14, 0x42}), - WantNext: genCorePacket(genPUSHDATAWithRXPK([]byte{0x14, 0x42})), - WantError: nil, - }, - { // Invalid uplink packet - Adapter: adapter, - Packet: genPUSHACK([]byte{0x22, 0x35}), - WantAck: semtech.Packet{}, - WantNext: nil, - WantError: nil, - }, - { // Uplink PUSH_DATA with no RXPK - Adapter: adapter, - Packet: genPUSHDATANoRXPK([]byte{0x22, 0x35}), - WantAck: genPUSHACK([]byte{0x22, 0x35}), - WantNext: nil, - WantError: nil, - }, - { // Uplink PULL_DATA - Adapter: adapter, - Packet: genPULLDATA([]byte{0x62, 0xfa}), - WantAck: genPULLACK([]byte{0x62, 0xfa}), - WantNext: nil, - WantError: nil, - }, - { // Uplink PUSH_DATA with no encoded payload - Adapter: adapter, - Packet: genPUSHDATANoPayload([]byte{0x22, 0x35}), - WantAck: genPUSHACK([]byte{0x22, 0x35}), - WantNext: nil, - WantError: nil, - }, - } - - for _, test := range tests { - // Describe - Desc(t, "Sending packet through adapter: %v", test.Packet) - <-time.After(time.Millisecond * 100) - - // Operate - ack := server.send(test.Packet) - packet, err := getNextPacket(next) - - // Check - CheckErrors(t, test.WantError, err) - checkCorePackets(t, test.WantNext, packet) - checkResponses(t, test.WantAck, ack) - } -} - -// ----- OPERATE utilities -func getNextPacket(next chan interface{}) (core.Packet, error) { - select { - case i := <-next: - res := i.(struct { - err error - packet []byte - }) - itf, err := core.UnmarshalPacket(res.packet) - if err != nil { - panic(err) - } - pkt, ok := itf.(core.RPacket) - if !ok { - return itf.(core.Packet), res.err - } - return pkt, res.err - case <-time.After(100 * time.Millisecond): - return nil, nil - } -} - -// ----- CHECK utilities -func checkCorePackets(t *testing.T, want core.Packet, got core.Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check core packets") - return - } - Ko(t, "Received core packet does not match expecatations.\nWant: %v\nGot: %v", want, got) -} - -func checkResponses(t *testing.T, want semtech.Packet, got semtech.Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check responses") - return - } - Ko(t, "Received response does not match expecatations.\nWant: %v\nGot: %v", want.String(), got.String()) -} diff --git a/core/adapters/udp/helpers_test.go b/core/adapters/udp/helpers_test.go deleted file mode 100644 index 49eefb903..000000000 --- a/core/adapters/udp/helpers_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/brocaar/lorawan" -) - -// tryNext attempts to get a next packet from the adapter. It timeouts after a given delay if -// nothing is ready. -func tryNext(adapter *Adapter) ([]byte, error) { - chresp := make(chan struct { - Packet []byte - Error error - }) - go func() { - packet, an, err := adapter.Next() - if err != nil { - an.Nack(nil) - } else { - an.Ack(nil) - } - chresp <- struct { - Packet []byte - Error error - }{packet, err} - }() - - select { - case resp := <-chresp: - return resp.Packet, resp.Error - case <-time.After(time.Millisecond * 75): - return nil, nil - } -} - -// ----- CHECK utilities - -func CheckPackets(t *testing.T, want []byte, got []byte) { - mocks.Check(t, want, got, "Packets") -} - -func CheckResps(t *testing.T, want MsgRes, got MsgRes) { - mocks.Check(t, want, got, "Responses") -} - -func CheckRecipients(t *testing.T, want core.Recipient, got core.Recipient) { - mocks.Check(t, want, got, "Recipients") -} - -func CheckDevEUIs(t *testing.T, want lorawan.EUI64, got lorawan.EUI64) { - mocks.Check(t, want, got, "DevEUIs") -} diff --git a/core/adapters/udp/mock_test.go b/core/adapters/udp/mock_test.go deleted file mode 100644 index c4a243ecf..000000000 --- a/core/adapters/udp/mock_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -// MockHandler implements the udp.Handler interface. -type MockHandler struct { - OutMsgUDP []byte - OutMsgReq []byte - InMsg MsgUDP - InChresp []byte -} - -// Handle implements the udp.Handler interface -func (h *MockHandler) Handle(conn chan<- MsgUDP, next chan<- MsgReq, msg MsgUDP) error { - h.InMsg = msg - if h.OutMsgReq != nil { - chresp := make(chan MsgRes) - next <- MsgReq{ - Data: h.OutMsgReq, - Chresp: chresp, - } - h.InChresp = <-chresp - } - - if h.OutMsgUDP != nil { - conn <- MsgUDP{ - Data: h.OutMsgUDP, - Addr: msg.Addr, - } - } - return nil -} diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index efb5ecf0b..8eb9d1618 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -7,104 +7,54 @@ import ( "fmt" "net" "sync" + "time" - "github.com/TheThingsNetwork/ttn/core" + "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" + "golang.org/x/net/context" ) -// Adapter represents a udp adapter which sends and receives packet via UDP -type Adapter struct { - ctx log.Interface // Just a logger - conn chan MsgUDP // Channel used to manage response transmissions made by multiple goroutines - packets chan MsgReq // Incoming valid packets are pushed to this channel and consume by an outsider - handlers chan interface{} // Manage handlers, could be either a Handler or a []byte (new handler or handling action) +type adapter struct { + ctx log.Interface } -// Handler represents a datagram and packet handler used by the adapter to process packets -type Handler interface { - // Handle handles incoming datagram from a gateway transmitter to the network - Handle(conn chan<- MsgUDP, chresp chan<- MsgReq, msg MsgUDP) error -} - -// MsgUDP type materializes response messages transmitted towards existing recipients (commonly, gateways). -type MsgUDP struct { - Data []byte // The raw byte sequence that has to be sent - Addr *net.UDPAddr // The target recipient address -} - -// MsgReq type materializes valid uplink messages coming from a given recipient -type MsgReq struct { - Data []byte // The actual message - Chresp chan MsgRes // A dedicated response channel -} - -// MsgRes qre sent through the response channel of MsgReq -type MsgRes []byte // The actual message - -// NewAdapter constructs and allocates a new udp adapter -func NewAdapter(bindNet string, ctx log.Interface) (*Adapter, error) { - a := Adapter{ - ctx: ctx, - conn: make(chan MsgUDP), - packets: make(chan MsgReq), - handlers: make(chan interface{}), - } +type replier func(data []byte) error +// Starts constructs and launches a new udp adapter +func Start(bindNet string, router core.RouterClient, ctx log.Interface) error { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", bindNet) - a.ctx.WithField("bind", bindNet).Info("Starting Server") if udpConn, err = net.ListenUDP("udp", addr); err != nil { - a.ctx.WithError(err).Error("Unable to start server") - return nil, errors.New(errors.Operational, fmt.Sprintf("Invalid bind address %v", bindNet)) + ctx.WithError(err).Error("Unable to start server") + return errors.New(errors.Operational, fmt.Sprintf("Invalid bind address %v", bindNet)) } - waitStart := &sync.WaitGroup{} - waitStart.Add(3) - - go a.monitorConnection(udpConn, waitStart) - go a.monitorHandlers(waitStart) - go a.listen(udpConn, waitStart) - - waitStart.Wait() + ctx.WithField("bind", bindNet).Info("Starting Server") - return &a, nil + a := adapter{ctx: ctx} + go a.listen(udpConn, router) + return nil } -// Send implements the core.Adapter interface. Not implemented for the udp adapter. -func (a *Adapter) Send(p core.Packet, r ...core.Recipient) ([]byte, error) { - return nil, errors.New(errors.Implementation, "Send not supported on udp adapter") -} - -// GetRecipient implements the core.Adapter interface -func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { - return nil, errors.New(errors.Implementation, "GetRecipient not supported on udp adapter") -} - -// Next implements the core.Adapter interface -func (a *Adapter) Next() ([]byte, core.AckNacker, error) { - msg := <-a.packets - return msg.Data, udpAckNacker{Chresp: msg.Chresp}, nil -} - -// NextRegistration implements the core.Adapter interface -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - return udpRegistration{}, nil, errors.New(errors.Implementation, "NextRegistration not supported on udp adapter") -} - -// Bind is used to register a new handler to the adapter -func (a *Adapter) Bind(h Handler) { - a.handlers <- h +// makeReply curryfies a writing to udp connection by binding the address and connection +func makeReply(addr *net.UDPAddr, conn *net.UDPConn) replier { + return func(data []byte) error { + _, err := conn.WriteToUDP(data, addr) + return err + } } // listen Handle incoming packets and forward them. // // Runs in its own goroutine. -func (a *Adapter) listen(conn *net.UDPConn, ready *sync.WaitGroup) { +func (a adapter) listen(conn *net.UDPConn, router core.RouterClient) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") - ready.Done() + for { buf := make([]byte, 5000) n, addr, err := conn.ReadFromUDP(buf) @@ -114,51 +64,119 @@ func (a *Adapter) listen(conn *net.UDPConn, ready *sync.WaitGroup) { } a.ctx.Debug("Incoming datagram") - a.handlers <- MsgUDP{Addr: addr, Data: buf[:n]} + go func(data []byte, reply replier, router core.RouterClient) { + pkt := new(semtech.Packet) + if err := pkt.UnmarshalBinary(data); err != nil { + a.ctx.WithError(err).Debug("Unable to handle datagram") + } + + switch pkt.Identifier { + case semtech.PULL_DATA: + err = a.handlePullData(*pkt, reply) + case semtech.PUSH_DATA: + err = a.handlePushData(*pkt, reply, router) + default: + err = errors.New(errors.Implementation, "Unhandled packet type") + } + + if err != nil { + a.ctx.WithError(err).Debug("Unable to handle datagram") + } + }(buf[:n], makeReply(addr, conn), router) } } -// monitorConnection manages udpConnection of the adapter and send message through that connection -// -// That function executes into a single goroutine and is the only one allowed to write UDP messages. -// Doing this makes sure that only 1 goroutine is interacting with the connection. -// -// Runs in its own goroutine -func (a *Adapter) monitorConnection(udpConn *net.UDPConn, ready *sync.WaitGroup) { - ready.Done() - for msg := range a.conn { - if msg.Data != nil { // Send the given udp message - if _, err := udpConn.WriteToUDP(msg.Data, msg.Addr); err != nil { - a.ctx.WithError(err).Error("Error while sending UDP message") +// Handle a PULL_DATA packet coming from a gateway +func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { + stats.MarkMeter("semtech_adapter.pull_data") + stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.pull_data", pkt.GatewayId)) + stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_pull_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) + + data, err := semtech.Packet{ + Version: semtech.VERSION, + Token: pkt.Token, + Identifier: semtech.PULL_ACK, + }.MarshalBinary() + + if err != nil { + return errors.New(errors.Structural, err) + } + + return reply(data) +} + +// Handle a PUSH_DATA packet coming from a gateway +func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.RouterClient) error { + stats.MarkMeter("semtech_adapter.push_data") + stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) + stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) + + // AckNowledge with a PUSH_ACK + data, err := semtech.Packet{ + Version: semtech.VERSION, + Token: pkt.Token, + Identifier: semtech.PUSH_ACK, + }.MarshalBinary() + if err != nil || reply(data) != nil || pkt.Payload == nil { + return errors.New(errors.Operational, "Unable to process PUSH_DATA packet") + } + + // Process Stat payload + if pkt.Payload.Stat != nil { + go router.HandleStats(context.Background(), &core.StatsReq{ + GatewayID: pkt.GatewayId, + Metadata: extractMetadata(*pkt.Payload.Stat, new(core.StatsMetadata)).(*core.StatsMetadata), + }) + } + + // Process rxpks payloads + cherr := make(chan error, len(pkt.Payload.RXPK)) + wait := sync.WaitGroup{} + wait.Add(len(pkt.Payload.RXPK)) + for _, rxpk := range pkt.Payload.RXPK { + go func(rxpk semtech.RXPK) { + defer wait.Done() + if err := a.handleDataUp(rxpk, pkt.GatewayId, reply, router); err != nil { + cherr <- err } - } + }(rxpk) + } + + // Retrieve any errors + wait.Wait() + close(cherr) + return <-cherr +} + +func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier, router core.RouterClient) error { + dataRouterReq, err := a.newDataRouterReq(rxpk, gid) + if err != nil { + return errors.New(errors.Structural, err) } - if udpConn != nil { - udpConn.Close() // Make sure we close the connection before leaving if we dare ever leave. + resp, err := router.HandleData(context.Background(), dataRouterReq) + if err != nil { + errors.New(errors.Operational, err) } + return a.handleDataDown(resp, reply) } -// monitorHandlers manages handler registration and execution concurrently. One can pass a new -// handler through the handlers channel to declare a new one or, send directly data through the -// channel to ask every defined handler to handle them. -// -// Runs in its own goroutine -func (a *Adapter) monitorHandlers(ready *sync.WaitGroup) { - var handlers []Handler - - ready.Done() - for msg := range a.handlers { - switch msg.(type) { - case Handler: - handlers = append(handlers, msg.(Handler)) - case MsgUDP: - for _, h := range handlers { - go func(h Handler, msg MsgUDP) { - if err := h.Handle(a.conn, a.packets, msg); err != nil { - a.ctx.WithError(err).Debug("Unable to handle request") - } - }(h, msg.(MsgUDP)) - } - } +func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { + if resp == nil { // No response + return nil + } + + txpk, err := a.newTXPK(*resp) + if err != nil { + return errors.New(errors.Structural, err) + } + + data, err := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{TXPK: &txpk}, + }.MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) } + return reply(data) } diff --git a/core/adapters/udp/udpAckNacker.go b/core/adapters/udp/udpAckNacker.go deleted file mode 100644 index 30791d9ed..000000000 --- a/core/adapters/udp/udpAckNacker.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// udpAckNacker represents an AckNacker for a udp adapter -type udpAckNacker struct { - Chresp chan<- MsgRes -} - -// Ack implements the core.Adapter interface -func (an udpAckNacker) Ack(p core.Packet) error { - defer close(an.Chresp) - if p == nil { - return nil - } - data, err := p.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - select { - case an.Chresp <- MsgRes(data): - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(errors.Operational, "Unable to send ack") - } -} - -// Ack implements the core.Adapter interface -func (an udpAckNacker) Nack(err error) error { - defer close(an.Chresp) - return nil -} diff --git a/core/adapters/udp/udpRegistration.go b/core/adapters/udp/udpRegistration.go deleted file mode 100644 index e0f97f7d1..000000000 --- a/core/adapters/udp/udpRegistration.go +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -// udpRegistration is a blank type which implements the core.RRegistration interface -type udpRegistration struct{} - -// Recipient implements the core.RRegistration inteface -func (r udpRegistration) Recipient() core.Recipient { - return nil -} - -// DevEUI implements the core.RRegistration interface -func (r udpRegistration) DevEUI() lorawan.EUI64 { - return lorawan.EUI64{} -} diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go deleted file mode 100644 index 52a7145f3..000000000 --- a/core/adapters/udp/udp_test.go +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "net" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - testutil "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestNext(t *testing.T) { - { - testutil.Desc(t, "Send a packet when no handler is defined") - - // Build - addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2016") - conn, _ := net.DialUDP("udp", nil, addr) - adapter, errNew := NewAdapter("0.0.0.0:2016", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - <-time.After(time.Millisecond * 25) - _, errWrite := conn.Write([]byte{1, 2, 3, 4}) - - // Operate - packet, errNext := tryNext(adapter) - - // Check - errutil.CheckErrors(t, nil, errWrite) - CheckPackets(t, nil, packet) - errutil.CheckErrors(t, nil, errNext) - } - - // -------------------- - - { - testutil.Desc(t, "Start adapter on a busy connection") - - // Build - addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2017") - _, _ = net.ListenUDP("udp", addr) - _, errNew := NewAdapter("0.0.0.0:2017", testutil.GetLogger(t, "Adapter")) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), errNew) - } - - // -------------------- - - { - testutil.Desc(t, "Attach a handler to the adapter and fake udp reception") - - // Build - addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2018") - conn, _ := net.DialUDP("udp", nil, addr) - handler := &MockHandler{} - adapter, errNew := NewAdapter("0.0.0.0:2018", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - adapter.Bind(handler) - _, errWrite := conn.Write([]byte{1, 2, 3, 4}) - <-time.After(time.Millisecond * 25) - - // Check - errutil.CheckErrors(t, nil, errWrite) - CheckPackets(t, []byte{1, 2, 3, 4}, handler.InMsg.Data) - } - - // -------------------- - - { - testutil.Desc(t, "Send next data through the handler") - - // Build - addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2019") - conn, _ := net.DialUDP("udp", nil, addr) - handler := &MockHandler{} - handler.OutMsgReq = []byte{14, 42, 14, 42} - adapter, errNew := NewAdapter("0.0.0.0:2019", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - adapter.Bind(handler) - _, errWrite := conn.Write([]byte{1, 2, 3, 4}) - <-time.After(time.Millisecond * 25) - packet, errNext := tryNext(adapter) - - // Check - errutil.CheckErrors(t, nil, errWrite) - errutil.CheckErrors(t, nil, errNext) - CheckPackets(t, []byte{1, 2, 3, 4}, handler.InMsg.Data) - CheckPackets(t, nil, handler.InChresp) - CheckPackets(t, []byte{14, 42, 14, 42}, packet) - } - - // -------------------- - - { - testutil.Desc(t, "Send next data back through the connection") - - // Build - addr, _ := net.ResolveUDPAddr("udp", "0.0.0.0:2020") - conn, _ := net.DialUDP("udp", nil, addr) - read := make([]byte, 20) - handler := &MockHandler{} - handler.OutMsgUDP = []byte{14, 42, 14, 42} - adapter, errNew := NewAdapter("0.0.0.0:2020", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - adapter.Bind(handler) - _, errWrite := conn.Write([]byte{1, 2, 3, 4}) - <-time.After(time.Millisecond * 25) - packet, errNext := tryNext(adapter) - n, errRead := conn.Read(read) - - // Check - errutil.CheckErrors(t, nil, errWrite) - errutil.CheckErrors(t, nil, errRead) - errutil.CheckErrors(t, nil, errNext) - CheckPackets(t, []byte{1, 2, 3, 4}, handler.InMsg.Data) - CheckPackets(t, read[:n], []byte{14, 42, 14, 42}) - CheckPackets(t, nil, packet) - } -} - -func TestNotImplemented(t *testing.T) { - { - testutil.Desc(t, "NextRegistration ~> not implemented") - - // Build - adapter, errNew := NewAdapter("0.0.0.0:2021", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - _, _, errNext := adapter.NextRegistration() - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errNext) - } - - // -------------------- - - { - testutil.Desc(t, "Send ~> not implemented") - - // Build - adapter, errNew := NewAdapter("0.0.0.0:2022", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - _, errSend := adapter.Send(nil) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errSend) - } - - // -------------------- - - { - testutil.Desc(t, "GetRecipient ~> not implemented") - - // Build - adapter, errNew := NewAdapter("0.0.0.0:2023", testutil.GetLogger(t, "Adapter")) - errutil.CheckErrors(t, nil, errNew) - - // Operate - _, errGet := adapter.GetRecipient(nil) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), errGet) - } -} - -func TestUDPRegistration(t *testing.T) { - reg := udpRegistration{} - CheckRecipients(t, nil, reg.Recipient()) - CheckDevEUIs(t, lorawan.EUI64{}, reg.DevEUI()) -} - -func TestUDPAckNacker(t *testing.T) { - { - testutil.Desc(t, "Ack nil packet") - - // Build - chresp := make(chan MsgRes) - an := udpAckNacker{Chresp: chresp} - - // Operate - var resp MsgRes - go func() { - select { - case resp = <-chresp: - case <-time.After(time.Millisecond * 25): - } - }() - err := an.Ack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, resp) - } - - // -------------------- - - { - testutil.Desc(t, "Ack valid packet") - - // Build - chresp := make(chan MsgRes) - an := udpAckNacker{Chresp: chresp} - pkt := mocks.NewMockPacket() - - // Operate - var resp MsgRes - go func() { - select { - case resp = <-chresp: - case <-time.After(time.Millisecond * 25): - } - }() - err := an.Ack(pkt) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, pkt.OutMarshalBinary, resp) - } - - // -------------------- - - { - testutil.Desc(t, "Ack invalid packet") - - // Build - chresp := make(chan MsgRes) - an := udpAckNacker{Chresp: chresp} - pkt := mocks.NewMockPacket() - pkt.Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error") - - // Operate - var resp MsgRes - go func() { - select { - case resp = <-chresp: - case <-time.After(time.Millisecond * 25): - } - }() - err := an.Ack(pkt) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckResps(t, nil, resp) - } - - // -------------------- - - { - testutil.Desc(t, "Ack not consumed") - - // Build - chresp := make(chan MsgRes) - an := udpAckNacker{Chresp: chresp} - pkt := mocks.NewMockPacket() - - // Operate - err := an.Ack(pkt) - resp, _ := <-chresp - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckResps(t, nil, resp) - } - - // -------------------- - - { - testutil.Desc(t, "Nack nil error") - - // Build - chresp := make(chan MsgRes) - an := udpAckNacker{Chresp: chresp} - - // Operate - err := an.Nack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - } - -} From 8b7a89387df4f9693ba9616565226a1e9bd65761 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 14:04:39 +0100 Subject: [PATCH 1041/2266] [refactor/grpc] Make Router implements the grpc RouterServer interface --- core/components/router/helpers_test.go | 90 --- core/components/router/mock_test.go | 175 ------ core/components/router/router.go | 201 +++---- core/components/router/router_test.go | 763 ------------------------- core/components/router/storage.go | 131 +++-- core/components/router/storage_test.go | 282 --------- 6 files changed, 153 insertions(+), 1489 deletions(-) delete mode 100644 core/components/router/helpers_test.go delete mode 100644 core/components/router/mock_test.go delete mode 100644 core/components/router/router_test.go delete mode 100644 core/components/router/storage_test.go diff --git a/core/components/router/helpers_test.go b/core/components/router/helpers_test.go deleted file mode 100644 index bcac00600..000000000 --- a/core/components/router/helpers_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -// ----- BUILD utilities -func newRPacket(rawDevAddr [4]byte, payload string, gatewayID []byte, metadata Metadata) RPacket { - var devAddr lorawan.DevAddr - copy(devAddr[:], rawDevAddr[:]) - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR.DevAddr = devAddr - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - macPayload.FPort = 1 - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MACPayload = macPayload - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - - packet, err := NewRPacket(phyPayload, gatewayID, metadata) - if err != nil { - panic(err) - } - return packet -} - -func newBPacket(rawDevAddr [4]byte, payload string, metadata Metadata) BPacket { - var devAddr lorawan.DevAddr - copy(devAddr[:], rawDevAddr[:]) - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR.DevAddr = devAddr - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - macPayload.FPort = 1 - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MACPayload = macPayload - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - - packet, err := NewBPacket(phyPayload, metadata) - if err != nil { - panic(err) - } - return packet -} - -// ----- CHECK utilities -func CheckEntries(t *testing.T, want []entry, got []entry) { - for i, w := range want { - if i >= len(got) { - Ko(t, "Didn't got enough entries: %v", got) - } - tmin := w.until.Add(-time.Second) - tmax := w.until.Add(time.Second) - if !tmin.Before(got[i].until) || !got[i].until.Before(tmax) { - Ko(t, "Unexpected expiry time.\nWant: %s\nGot: %s", w.until, got[i].until) - } - Check(t, w.Recipient, got[i].Recipient, "Recipients") - } -} - -func CheckRegistrations(t *testing.T, want Registration, got Registration) { - Check(t, want, got, "Registrations") -} - -func CheckIDs(t *testing.T, want []byte, got []byte) { - Check(t, want, got, "IDs") -} - -func CheckStats(t *testing.T, want SPacket, got SPacket) { - Check(t, want, got, "Stats") -} - -func CheckMetadata(t *testing.T, want Metadata, got Metadata) { - Check(t, want, got, "Metadata") -} diff --git a/core/components/router/mock_test.go b/core/components/router/mock_test.go deleted file mode 100644 index b5963a4b6..000000000 --- a/core/components/router/mock_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "time" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/brocaar/lorawan" -) - -// MockStorage to fake the Storage interface -type mockStorage struct { - Failures map[string]error - InLookup lorawan.EUI64 - InLookupStats []byte - OutLookupStats Metadata - OutLookup []entry - InStore RRegistration - InUpdateStats SPacket -} - -func newMockStorage() *mockStorage { - return &mockStorage{ - Failures: make(map[string]error), - OutLookup: []entry{ - { - Recipient: []byte("MockStorageRecipient"), - until: time.Date(2016, 2, 3, 14, 16, 22, 0, time.UTC), - }, - }, - OutLookupStats: Metadata{}, - } -} - -func (s *mockStorage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { - s.InLookup = devEUI - if s.Failures["Lookup"] != nil { - return nil, s.Failures["Lookup"] - } - return s.OutLookup, nil -} - -func (s *mockStorage) LookupStats(id []byte) (Metadata, error) { - s.InLookupStats = id - if s.Failures["LookupStats"] != nil { - return Metadata{}, s.Failures["LookupStats"] - } - return s.OutLookupStats, nil -} - -func (s *mockStorage) Store(reg RRegistration) error { - s.InStore = reg - return s.Failures["Store"] -} - -func (s *mockStorage) UpdateStats(stats SPacket) error { - s.InUpdateStats = stats - return s.Failures["UpdateStats"] -} - -func (s *mockStorage) Close() error { - return s.Failures["Close"] -} - -// mockDutyManager implements the dutycycle.DutyManager interface -type mockDutyManager struct { - Failures map[string]error - InUpdateId []byte - InUpdateFreq float64 - InUpdateSize uint - InUpdateDatr string - InUpdateCodr string - InLookupId []byte - OutLookup dutycycle.Cycles -} - -func newMockDutyManager() *mockDutyManager { - return &mockDutyManager{ - Failures: make(map[string]error), - OutLookup: make(dutycycle.Cycles), - } -} - -func (m *mockDutyManager) Update(id []byte, freq float64, size uint, datr string, codr string) error { - m.InUpdateId = id - m.InUpdateFreq = freq - m.InUpdateSize = size - m.InUpdateDatr = datr - m.InUpdateCodr = codr - return m.Failures["Update"] -} - -func (m *mockDutyManager) Lookup(id []byte) (dutycycle.Cycles, error) { - m.InLookupId = id - if m.Failures["Lookup"] != nil { - return nil, m.Failures["Lookup"] - } - return m.OutLookup, nil -} - -func (m *mockDutyManager) Close() error { - return m.Failures["Close"] -} - -// MockRouterAdapter extends functionality of the mocks.MockAdapter. -// -// A list of failures can be defined to handle successive call to a method (at each call, an error -// get out from the list) -type mockRouterAdapter struct { - Failures map[string][]error - InSendPacket Packet - InSendRecipients []Recipient - InGetRecipient []byte - OutSend []byte - OutGetRecipient Recipient - OutNextPacket []byte - OutNextAckNacker AckNacker - OutNextRegReg Registration - OutNextRegAckNacker AckNacker -} - -func newMockRouterAdapter() *mockRouterAdapter { - return &mockRouterAdapter{ - Failures: make(map[string][]error), - OutSend: []byte("MockAdapterSend"), - OutGetRecipient: NewMockRecipient(), - OutNextPacket: []byte("MockAdapterNextPacket"), - OutNextAckNacker: NewMockAckNacker(), - OutNextRegReg: NewMockHRegistration(), - OutNextRegAckNacker: NewMockAckNacker(), - } -} - -func (a *mockRouterAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { - a.InSendPacket = p - a.InSendRecipients = r - if len(a.Failures["Send"]) > 0 { - err := a.Failures["Send"][0] - a.Failures["Send"] = a.Failures["Send"][1:] - return nil, err - } - return a.OutSend, nil -} - -func (a *mockRouterAdapter) GetRecipient(raw []byte) (Recipient, error) { - a.InGetRecipient = raw - if len(a.Failures["GetRecipient"]) > 0 { - err := a.Failures["GetRecipient"][0] - a.Failures["GetRecipient"] = a.Failures["GetRecipient"][1:] - return nil, err - } - return a.OutGetRecipient, nil -} - -func (a *mockRouterAdapter) Next() ([]byte, AckNacker, error) { - if len(a.Failures["Next"]) > 0 { - err := a.Failures["Next"][0] - a.Failures["Next"] = a.Failures["Next"][1:] - return nil, nil, err - } - return a.OutNextPacket, a.OutNextAckNacker, nil -} - -func (a *mockRouterAdapter) NextRegistration() (Registration, AckNacker, error) { - if len(a.Failures["NextRegistration"]) > 0 { - err := a.Failures["NextRegistration"][0] - a.Failures["NextRegistration"] = a.Failures["NextRegistration"][1:] - return nil, nil, err - } - return a.OutNextRegReg, a.OutNextRegAckNacker, nil -} diff --git a/core/components/router/router.go b/core/components/router/router.go index a1d70287b..e84a2c9e2 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -4,137 +4,110 @@ package router import ( - . "github.com/TheThingsNetwork/ttn/core" + "github.com/KtorZ/rpc/core" "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" + "golang.org/x/net/context" ) type component struct { Storage - Manager dutycycle.DutyManager + manager dutycycle.DutyManager + brokers []core.BrokerClient ctx log.Interface } // New constructs a new router -func New(db Storage, dm dutycycle.DutyManager, ctx log.Interface) Router { - return component{Storage: db, Manager: dm, ctx: ctx} +func New(db Storage, dm dutycycle.DutyManager, brokers []core.BrokerClient, ctx log.Interface) core.RouterServer { + return component{Storage: db, manager: dm, brokers: brokers, ctx: ctx} } -// Register implements the core.Router interface -func (r component) Register(reg Registration, an AckNacker) (err error) { - defer ensureAckNack(an, nil, &err) - stats.MarkMeter("router.registration.in") - r.ctx.Debug("Handling registration") - - rreg, ok := reg.(RRegistration) - if !ok { - err = errors.New(errors.Structural, "Unexpected registration type") - r.ctx.WithError(err).Warn("Unable to register") - return err +// HandleStats implements the core.RouterClient interface +func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.StatsRes, error) { + if req == nil { + return nil, errors.New(errors.Structural, "Invalid nil stats request") } - return r.Store(rreg) -} + if len(req.GatewayID) != 8 { + return nil, errors.New(errors.Structural, "Invalid gateway identifier") + } + + if req.Metadata == nil { + return nil, errors.New(errors.Structural, "Missing mandatory Metadata") + } -// HandleUp implements the core.Router interface -func (r component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { - // Make sure we don't forget the AckNacker - var ack Packet - defer ensureAckNack(an, &ack, &err) + stats.MarkMeter("router.stat.in") + return nil, r.UpdateStats(req.GatewayID, *req.Metadata) +} +// HandleData implements the core.RouterClient interface +func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*core.DataRouterRes, error) { // Get some logs / analytics r.ctx.Debug("Handling uplink packet") + stats.MarkMeter("router.uplink.in") - // Extract the given packet - itf, _ := UnmarshalPacket(data) - switch itf.(type) { - case RPacket: - stats.MarkMeter("router.uplink.in") - ack, err = r.handleDataUp(itf.(RPacket), up) - case SPacket: - stats.MarkMeter("router.stat.in") - err = r.UpdateStats(itf.(SPacket)) - default: - stats.MarkMeter("router.uplink.invalid") - err = errors.New(errors.Structural, "Unreckognized packet type") - } - + // Validate coming data + _, _, fhdr, _, err := core.ValidateLoRaWANData(req.Payload) if err != nil { - r.ctx.WithError(err).Debug("Unable to process uplink") + return nil, errors.New(errors.Structural, err) + } + if req.Metadata == nil { + return nil, errors.New(errors.Structural, "Missing mandatory Metadata") + } + if len(req.GatewayID) != 8 { + return nil, errors.New(errors.Structural, "Invalid gatewayID") } - return err -} -// handleDataUp handle an upcoming message which carries a data frame payload -func (r component) handleDataUp(packet RPacket, up Adapter) (Packet, error) { // Lookup for an existing broker - entries, err := r.Lookup(packet.DevEUI()) + entries, err := r.Lookup(fhdr.DevAddr) if err != nil && err.(errors.Failure).Nature != errors.NotFound { r.ctx.Warn("Database lookup failed") return nil, errors.New(errors.Operational, err) } shouldBroadcast := err != nil - metadata := packet.Metadata() - if metadata.Freq == nil { - stats.MarkMeter("router.uplink.not_supported") - return nil, errors.New(errors.Structural, "Missing mandatory frequency in metadata") - } - // Add Gateway location metadata - gmeta, _ := r.LookupStats(packet.GatewayID()) - metadata.Lati = gmeta.Lati - metadata.Long = gmeta.Long - metadata.Alti = gmeta.Alti + if gmeta, err := r.LookupStats(req.GatewayID); err == nil { + req.Metadata.Latitude = gmeta.Latitude + req.Metadata.Longitude = gmeta.Longitude + req.Metadata.Altitude = gmeta.Altitude + } // Add Gateway duty metadata - cycles, err := r.Manager.Lookup(packet.GatewayID()) + cycles, err := r.manager.Lookup(req.GatewayID) if err != nil { r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") cycles = make(dutycycle.Cycles) } - sb1, err := dutycycle.GetSubBand(*metadata.Freq) + sb1, err := dutycycle.GetSubBand(float64(req.Metadata.Frequency)) if err != nil { stats.MarkMeter("router.uplink.not_supported") return nil, errors.New(errors.Structural, "Unhandled uplink signal frequency") } rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) - metadata.DutyRX1, metadata.DutyRX2 = &rx1, &rx2 + req.Metadata.DutyRX1, req.Metadata.DutyRX2 = uint32(rx1), uint32(rx2) - bpacket, err := NewBPacket(packet.Payload(), metadata) - if err != nil { - stats.MarkMeter("router.uplink.not_supported") - r.ctx.WithError(err).Warn("Unable to create router packet") - return nil, errors.New(errors.Structural, err) - } + bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} // Send packet to broker(s) - var response []byte + var response *core.DataBrokerRes if shouldBroadcast { // No Recipient available -> broadcast - response, err = up.Send(bpacket) + response, err = r.send(bpacket, r.brokers...) } else { // Recipients are available - var recipients []Recipient + var brokers []core.BrokerClient for _, e := range entries { - // Get the actual broker - recipient, err := up.GetRecipient(e.Recipient) - if err != nil { - r.ctx.Warn("Unable to retrieve Recipient") - return nil, errors.New(errors.Structural, err) - } - recipients = append(recipients, recipient) + brokers = append(brokers, r.brokers[e.BrokerIndex]) } - - // Send the packet - response, err = up.Send(bpacket, recipients...) + response, err = r.send(bpacket, brokers...) if err != nil && err.(errors.Failure).Nature == errors.NotFound { // Might be a collision with the dev addr, we better broadcast - response, err = up.Send(bpacket) + response, err = r.send(bpacket, r.brokers...) } stats.MarkMeter("router.uplink.out") } @@ -149,58 +122,50 @@ func (r component) handleDataUp(packet RPacket, up Adapter) (Packet, error) { return nil, err } - return r.handleDataDown(response, packet.GatewayID()) + return r.handleDataDown(response, req.GatewayID) } -// handleDataDown controls that data received from an uplink are okay. -// It also updates metadata about the related gateway -func (r component) handleDataDown(data []byte, gatewayID []byte) (Packet, error) { - if data == nil { +func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*core.DataRouterRes, error) { + if req == nil { // No response return nil, nil } - itf, err := UnmarshalPacket(data) - if err != nil { + // Update downlink metadata for the related gateway + if req.Metadata == nil { stats.MarkMeter("router.uplink.bad_broker_response") + return nil, errors.New(errors.Structural, "Missing mandatory Metadata in response") + } + freq := float64(req.Metadata.Frequency) + datr := req.Metadata.DataRate + codr := req.Metadata.CodingRate + size := uint(req.Metadata.PayloadSize) + if err := r.manager.Update(gatewayID, freq, size, datr, codr); err != nil { return nil, errors.New(errors.Operational, err) } - switch itf.(type) { - case RPacket: - // Update downlink metadata for the related gateway - metadata := itf.(RPacket).Metadata() - freq := metadata.Freq - datr := metadata.Datr - codr := metadata.Codr - size := metadata.Size - - if freq == nil || datr == nil || codr == nil || size == nil { - stats.MarkMeter("router.uplink.bad_broker_response") - return nil, errors.New(errors.Operational, "Missing mandatory metadata in response") - } - if err := r.Manager.Update(gatewayID, *freq, *size, *datr, *codr); err != nil { - return nil, errors.New(errors.Operational, err) - } - - // Finally, define the ack to be sent - return itf.(RPacket), nil - default: - stats.MarkMeter("router.uplink.bad_broker_response") - return nil, errors.New(errors.Implementation, "Unexpected packet type") - } + // Send Back the response + return &core.DataRouterRes{Payload: req.Payload, Metadata: req.Metadata}, nil } -// ensureAckNack is used to make sure we Ack / Nack correctly in the HandleUp method. -// The method will probably change or be moved outside the router itself. -func ensureAckNack(an AckNacker, ack *Packet, err *error) { - if err != nil && *err != nil { - an.Nack(*err) - } else { - stats.MarkMeter("router.uplink.ok") - var p Packet - if ack != nil { - p = *ack - } - an.Ack(p) - } +func (r component) send(req *core.DataBrokerReq, brokers ...core.BrokerClient) (*core.DataBrokerRes, error) { + + return nil, nil } + +// Register implements the core.Router interface +//func (r component) Register(reg Registration, an AckNacker) (err error) { +// defer ensureAckNack(an, nil, &err) +// stats.MarkMeter("router.registration.in") +// r.ctx.Debug("Handling registration") +// +// rreg, ok := reg.(RRegistration) +// if !ok { +// err = errors.New(errors.Structural, "Unexpected registration type") +// r.ctx.WithError(err).Warn("Unable to register") +// return err +// } +// +// return r.Store(rreg) +//} +// handleDataDown controls that data received from an uplink are okay. +// It also updates metadata about the related gateway diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go deleted file mode 100644 index 10dea01ec..000000000 --- a/core/components/router/router_test.go +++ /dev/null @@ -1,763 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestRegister(t *testing.T) { - { - Desc(t, "Register an entry") - - // Build - an := NewMockAckNacker() - store := newMockStorage() - r := NewMockRRegistration() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.Register(r, an) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, r, store.InStore) - } - - // ------------------- - - { - Desc(t, "Register an entry, wrong registration type") - - // Build - an := NewMockAckNacker() - store := newMockStorage() - r := NewMockARegistration() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.Register(r, an) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - } - - // ------------------- - - { - Desc(t, "Register an entry | Store failed") - - // Build - an := NewMockAckNacker() - store := newMockStorage() - store.Failures["Store"] = errors.New(errors.Structural, "Mock Error: Store Failed") - r := NewMockRRegistration() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.Register(r, an) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, r, store.InStore) - } -} - -func TestHandleUp(t *testing.T) { - { - Desc(t, "Send an unknown packet | No downlink") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutSend = nil - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | With Downlink") - - // Build - an := NewMockAckNacker() - resp := newRPacket( - [4]byte{2, 3, 2, 3}, - "Response", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{ - Freq: pointer.Float64(868.42), - Size: pointer.Uint(14), - Codr: pointer.String("4/5"), - Datr: pointer.String("SF8BW125"), - }, - ) - dataResp, _ := resp.MarshalBinary() - adapter := NewMockAdapter() - adapter.OutSend = dataResp - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, resp, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, inPacket.GatewayID(), m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send invalid data") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp([]byte{1, 2, 3}, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | No downlink | Storage fail lookup ") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutSend = nil - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.Operational, "Mock Error: Lookup failed") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send known packet | No downlink") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutSend = nil - recipient := NewMockRecipient() - dataRecipient, _ := recipient.MarshalBinary() - store := newMockStorage() - store.OutLookup = []entry{ - { - Recipient: dataRecipient, - until: time.Now().Add(time.Hour), - }, - } - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send unknown packet | Get wrong recipient from db") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Invalid recipient") - recipient := NewMockRecipient() - dataRecipient, _ := recipient.MarshalBinary() - store := newMockStorage() - store.OutLookup = []entry{ - { - Recipient: dataRecipient, - until: time.Now().Add(time.Hour), - }, - } - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send unknown packet | Sending fails") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send unknown packet | Get invalid downlink response") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutSend = []byte{1, 2, 3} - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send a known packet, get not found, and broadcast") - - // Build - an := NewMockAckNacker() - adapter := newMockRouterAdapter() - adapter.OutSend = nil - adapter.Failures["Send"] = []error{ - errors.New(errors.NotFound, "Mock Error"), - } - recipient := NewMockRecipient() - dataRecipient, _ := recipient.MarshalBinary() - store := newMockStorage() - store.OutLookup = []entry{ - { - Recipient: dataRecipient, - until: time.Now().Add(time.Hour), - }, - } - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } - - // ------------------- - - { - Desc(t, "Send a known packet, get not found, and broadcast, still not found") - - // Build - an := NewMockAckNacker() - adapter := newMockRouterAdapter() - adapter.OutSend = nil - adapter.Failures["Send"] = []error{ - errors.New(errors.NotFound, "Mock Error"), - errors.New(errors.NotFound, "Mock Error"), - } - recipient := NewMockRecipient() - dataRecipient, _ := recipient.MarshalBinary() - store := newMockStorage() - store.OutLookup = []entry{ - { - Recipient: dataRecipient, - until: time.Now().Add(time.Hour), - }, - } - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | No Downlink | Fail to lookup gateway") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutSend = nil - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - m.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | Missing Metadata in downlink") - - // Build - an := NewMockAckNacker() - resp := newRPacket( - [4]byte{2, 3, 2, 3}, - "Response", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{ - Size: pointer.Uint(14), - Codr: pointer.String("4/5"), - Datr: pointer.String("SF8BW125"), - }, - ) - dataResp, _ := resp.MarshalBinary() - adapter := NewMockAdapter() - adapter.OutSend = dataResp - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | Fail to update metadata") - - // Build - an := NewMockAckNacker() - resp := newRPacket( - [4]byte{2, 3, 2, 3}, - "Response", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{ - Freq: pointer.Float64(868.14), - Size: pointer.Uint(14), - Codr: pointer.String("4/5"), - Datr: pointer.String("SF8BW125"), - }, - ) - dataResp, _ := resp.MarshalBinary() - adapter := NewMockAdapter() - adapter.OutSend = dataResp - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(865.5)}, - ) - data, _ := inPacket.MarshalBinary() - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - Metadata{Freq: pointer.Float64(865.5), DutyRX1: pointer.Uint(0), DutyRX2: pointer.Uint(0)}, - ) - m := newMockDutyManager() - m.Failures["Update"] = errors.New(errors.Operational, "Mock Error: Update") - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, bpacket, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, inPacket.GatewayID(), m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | No Metadata") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{}, - ) - data, _ := inPacket.MarshalBinary() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, nil, m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send an unknown packet | Unsupported frequency") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - store := newMockStorage() - store.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error: Not Found") - inPacket := newRPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Freq: pointer.Float64(333.5)}, - ) - data, _ := inPacket.MarshalBinary() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, inPacket.GatewayID(), m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - } - - // ------------------- - - { - Desc(t, "Send a valid stat packet") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - store := newMockStorage() - inPacket, _ := NewSPacket( - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Alti: pointer.Int(14)}, - ) - data, _ := inPacket.MarshalBinary() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckStats(t, inPacket, store.InUpdateStats) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, nil, m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - CheckIDs(t, nil, store.InLookupStats) - } - - // ------------------- - - { - Desc(t, "Send a valid stat packet | unable to update") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - store := newMockStorage() - store.Failures["UpdateStats"] = errors.New(errors.Operational, "Mock Error") - inPacket, _ := NewSPacket( - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata{Alti: pointer.Int(14)}, - ) - data, _ := inPacket.MarshalBinary() - m := newMockDutyManager() - - // Operate - router := New(store, m, GetLogger(t, "Router")) - err := router.HandleUp(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStore) - CheckStats(t, inPacket, store.InUpdateStats) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - CheckIDs(t, nil, m.InLookupId) - CheckIDs(t, nil, m.InUpdateId) - CheckIDs(t, nil, store.InLookupStats) - } -} diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 5b812957f..21db8a1fc 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -4,29 +4,29 @@ package router import ( + "bytes" + "encoding/binary" "fmt" "sync" "time" - . "github.com/TheThingsNetwork/ttn/core" + "github.com/KtorZ/rpc/core" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" - "github.com/brocaar/lorawan" ) // Storage gives a facade to manipulate the router database type Storage interface { - Lookup(devEUI lorawan.EUI64) ([]entry, error) - LookupStats(id []byte) (Metadata, error) - UpdateStats(stats SPacket) error - Store(reg RRegistration) error + Lookup(devAddr []byte) ([]entry, error) + //Store(reg RRegistration) error + LookupStats(gid []byte) (core.StatsMetadata, error) + UpdateStats(gid []byte, metadata core.StatsMetadata) error Close() error } type entry struct { - Recipient []byte - until time.Time + BrokerIndex int + until time.Time } type storage struct { @@ -50,11 +50,29 @@ func NewStorage(name string, delay time.Duration) (Storage, error) { return &storage{db: itf, ExpiryDelay: delay}, nil } +// UpdateStats implements the router.Storage interface +func (s *storage) UpdateStats(gid []byte, metadata core.StatsMetadata) error { + return s.db.Replace(dbGateway, gid, []dbutil.Entry{&metadata}) +} + +// LookupStats implements the router.Storage interface +func (s *storage) LookupStats(gid []byte) (core.StatsMetadata, error) { + itf, err := s.db.Lookup(dbGateway, gid, &core.StatsMetadata{}) + if err != nil { + return core.StatsMetadata{}, err + } + entries := itf.([]core.StatsMetadata) + if len(entries) == 0 { + return core.StatsMetadata{}, errors.New(errors.NotFound, "Not entry found for given gateway") + } + return entries[0], nil +} + // Lookup implements the router.Storage interface -func (s *storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { +func (s *storage) Lookup(devAddr []byte) ([]entry, error) { s.Lock() defer s.Unlock() - itf, err := s.db.Lookup(dbBrokers, devEUI[:], &entry{}) + itf, err := s.db.Lookup(dbBrokers, devAddr, &entry{}) if err != nil { return nil, err } @@ -71,77 +89,68 @@ func (s *storage) Lookup(devEUI lorawan.EUI64) ([]entry, error) { filtered = append(filtered, e) } } - if err := s.db.Replace(dbBrokers, devEUI[:], newEntries); err != nil { + if err := s.db.Replace(dbBrokers, devAddr, newEntries); err != nil { return nil, errors.New(errors.Operational, err) } entries = filtered } if len(entries) == 0 { - return nil, errors.New(errors.NotFound, fmt.Sprintf("No entry for: %v", devEUI[:])) + return nil, errors.New(errors.NotFound, fmt.Sprintf("No entry for: %v", devAddr)) } return entries, nil } -// Store implements the router.Storage interface -func (s *storage) Store(reg RRegistration) error { - devEUI := reg.DevEUI() - recipient, err := reg.Recipient().MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - - s.Lock() - defer s.Unlock() - return s.db.Store(dbBrokers, devEUI[:], []dbutil.Entry{&entry{ - Recipient: recipient, - until: time.Now().Add(s.ExpiryDelay), - }}) -} - -// UpdateStats implements the router.Storage interface -func (s *storage) UpdateStats(stats SPacket) error { - metadata := stats.Metadata() - return s.db.Replace(dbGateway, stats.GatewayID(), []dbutil.Entry{&metadata}) -} - -// LookupStats implements the router.Storage interface -func (s *storage) LookupStats(id []byte) (Metadata, error) { - itf, err := s.db.Lookup(dbGateway, id, &Metadata{}) - if err != nil { - return Metadata{}, err - } - entries := itf.([]Metadata) - if len(entries) == 0 { - return Metadata{}, errors.New(errors.NotFound, "Not entry found for given gateway") - } - return entries[0], nil -} +// +//// Store implements the router.Storage interface +//func (s *storage) Store(reg RRegistration) error { +// devEUI := reg.DevEUI() +// recipient, err := reg.Recipient().MarshalBinary() +// if err != nil { +// return errors.New(errors.Structural, err) +// } +// +// s.Lock() +// defer s.Unlock() +// return s.db.Store(dbBrokers, devEUI[:], []dbutil.Entry{&entry{ +// Recipient: recipient, +// until: time.Now().Add(s.ExpiryDelay), +// }}) +//} // Close implements the router.Storage interface func (s *storage) Close() error { return s.db.Close() } -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e entry) MarshalBinary() ([]byte, error) { +// Marshal implements the proto.Marshaler interface +func (e entry) Marshal() ([]byte, error) { data, err := e.until.MarshalBinary() if err != nil { return nil, errors.New(errors.Structural, err) } - rw := readwriter.New(nil) - rw.Write(e.Recipient) - rw.Write(data) - return rw.Bytes() + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, uint16(e.BrokerIndex)) + binary.Write(buf, binary.BigEndian, data) + return buf.Bytes(), nil } -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *entry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { e.Recipient = data }) - rw.TryRead(func(data []byte) error { - return e.until.UnmarshalBinary(data) - }) - return rw.Err() +// Unmarshal implements the proto.Unmarshaler interface +func (e *entry) Unmarshal(data []byte) error { + buf := bytes.NewBuffer(data) + + // e.until + tdata := new([]byte) + binary.Read(buf, binary.BigEndian, tdata) + if err := e.until.UnmarshalBinary(*tdata); err != nil { + return errors.New(errors.Structural, err) + } + + // e.Broker + index := new(uint16) + binary.Read(buf, binary.BigEndian, index) + e.BrokerIndex = int(*index) + + return nil } diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go deleted file mode 100644 index 672b533db..000000000 --- a/core/components/router/storage_test.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "os" - "path" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const storageDB = "TestRouterStorage.db" - -func TestStoreAndLookup(t *testing.T) { - storageDB := path.Join(os.TempDir(), storageDB) - - defer func() { - os.Remove(storageDB) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - db, err := NewStorage(storageDB, time.Hour) - CheckErrors(t, nil, err) - err = db.Close() - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Store then lookup a registration") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - r := NewMockRRegistration() - - // Operate - err := db.Store(r) - CheckErrors(t, nil, err) - gotEntry, err := db.Lookup(r.DevEUI()) - - // Expectations - wantEntry := []entry{ - { - Recipient: r.RawRecipient(), - until: time.Now().Add(time.Hour), - }, - } - - // Check - CheckErrors(t, nil, err) - CheckEntries(t, wantEntry, gotEntry) - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Lookup non-existing entry") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - devEUI := NewMockRRegistration().DevEUI() - devEUI[1] = 14 - - // Operate - gotEntry, err := db.Lookup(devEUI) - - // Checks - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, gotEntry) - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Lookup an expired entry") - - // Build - db, _ := NewStorage(storageDB, time.Millisecond*100) - r := NewMockRRegistration() - r.OutDevEUI[0] = 12 - - // Operate - _ = db.Store(r) - <-time.After(time.Millisecond * 200) - gotEntry, err := db.Lookup(r.DevEUI()) - - // Checks - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, gotEntry) - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Store above an expired entry") - - // Build - db, _ := NewStorage(storageDB, time.Millisecond*100) - r := NewMockRRegistration() - r.OutDevEUI[4] = 27 - - // Operate - _ = db.Store(r) - <-time.After(time.Millisecond * 200) - err := db.Store(r) - CheckErrors(t, nil, err) - gotEntry, err := db.Lookup(r.DevEUI()) - - // Expectations - wantEntry := []entry{ - { - Recipient: r.RawRecipient(), - until: time.Now().Add(time.Millisecond * 200), - }, - } - - // Checks - CheckErrors(t, nil, err) - CheckEntries(t, wantEntry, gotEntry) - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Store on a closed database") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - _ = db.Close() - r := NewMockRRegistration() - r.OutDevEUI[5] = 9 - - // Operate - err := db.Store(r) - - // Checks - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // ------------------ - - { - Desc(t, "Lookup on a closed database") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - _ = db.Close() - devEUI := NewMockRRegistration().DevEUI() - - // Operate - gotEntry, err := db.Lookup(devEUI) - - // Checks - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckEntries(t, nil, gotEntry) - } - - // ------------------ - - { - Desc(t, "Store an invalid recipient") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - r := NewMockRRegistration() - r.OutDevEUI[7] = 99 - r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") - - // Operate & Check - err := db.Store(r) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - gotEntry, err := db.Lookup(r.DevEUI()) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, gotEntry) - - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Store two entries in a row") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - r1 := NewMockRRegistration() - r1.OutDevEUI[3] = 42 - r2 := NewMockRRegistration() - r2.OutDevEUI[3] = 42 - r2.OutRecipient.(*MockRecipient).OutMarshalBinary = []byte("Second recipient") - - // Operate - err := db.Store(r1) - CheckErrors(t, nil, err) - err = db.Store(r2) - CheckErrors(t, nil, err) - gotEntries, err := db.Lookup(r1.DevEUI()) - CheckErrors(t, nil, err) - - // Expectations - wantEntries := []entry{ - { - Recipient: r1.RawRecipient(), - until: time.Now().Add(time.Hour), - }, - { - Recipient: r2.RawRecipient(), - until: time.Now().Add(time.Hour), - }, - } - - // Check - CheckEntries(t, wantEntries, gotEntries) - _ = db.Close() - } -} - -func TestUpdateAndLookup(t *testing.T) { - storageDB := path.Join(os.TempDir(), storageDB) - - defer func() { - os.Remove(storageDB) - }() - - // ------------------ - - { - Desc(t, "Store then lookup stats") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - stats, _ := core.NewSPacket( - []byte{1, 2, 3, 4, 5, 6, 7, 8}, - core.Metadata{ - Alti: pointer.Int(14), - }, - ) - - // Operate - errUpdate := db.UpdateStats(stats) - got, errLookup := db.LookupStats(stats.GatewayID()) - - // Check - CheckErrors(t, nil, errUpdate) - CheckErrors(t, nil, errLookup) - CheckMetadata(t, stats.Metadata(), got) - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Lookup stats from unknown gateway") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - - // Operate - got, errLookup := db.LookupStats([]byte{1, 2, 2, 3, 3, 4, 4, 1}) - - // Check - CheckErrors(t, pointer.String(string(errors.NotFound)), errLookup) - CheckMetadata(t, core.Metadata{}, got) - _ = db.Close() - } -} From b1d9820d677b1852feef5218f8f747a085e60667 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 14:07:38 +0100 Subject: [PATCH 1042/2266] [refactor/grpc] Use of RouterServer interface instead of RouterClient in udp --- core/adapters/udp/udp.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 8eb9d1618..a1d33c63b 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -24,7 +24,7 @@ type adapter struct { type replier func(data []byte) error // Starts constructs and launches a new udp adapter -func Start(bindNet string, router core.RouterClient, ctx log.Interface) error { +func Start(bindNet string, router core.RouterServer, ctx log.Interface) error { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn addr, err := net.ResolveUDPAddr("udp", bindNet) @@ -51,7 +51,7 @@ func makeReply(addr *net.UDPAddr, conn *net.UDPConn) replier { // listen Handle incoming packets and forward them. // // Runs in its own goroutine. -func (a adapter) listen(conn *net.UDPConn, router core.RouterClient) { +func (a adapter) listen(conn *net.UDPConn, router core.RouterServer) { defer conn.Close() a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") @@ -64,7 +64,7 @@ func (a adapter) listen(conn *net.UDPConn, router core.RouterClient) { } a.ctx.Debug("Incoming datagram") - go func(data []byte, reply replier, router core.RouterClient) { + go func(data []byte, reply replier, router core.RouterServer) { pkt := new(semtech.Packet) if err := pkt.UnmarshalBinary(data); err != nil { a.ctx.WithError(err).Debug("Unable to handle datagram") @@ -106,7 +106,7 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { } // Handle a PUSH_DATA packet coming from a gateway -func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.RouterClient) error { +func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.RouterServer) error { stats.MarkMeter("semtech_adapter.push_data") stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) @@ -148,7 +148,7 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.R return <-cherr } -func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier, router core.RouterClient) error { +func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier, router core.RouterServer) error { dataRouterReq, err := a.newDataRouterReq(rxpk, gid) if err != nil { return errors.New(errors.Structural, err) From a5006c488605540ee7f27cf81e20c04ca5a26eaf Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 14:08:13 +0100 Subject: [PATCH 1043/2266] [refactor/grpc] Move the LoRaWANData validation to core package. Re-used in components/router --- core/adapters/udp/conversions.go | 51 ++++++++++---------------------- 1 file changed, 15 insertions(+), 36 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 956ddb6b6..561bf847a 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -91,57 +91,36 @@ func (a adapter) newDataRouterReq(rxpk semtech.RXPK, gid []byte) (*core.DataRout func (a adapter) newTXPK(resp core.DataRouterRes) (semtech.TXPK, error) { // Step 0: validate the response - var p *core.LoRaWANData - - // Validation::0 -> Payload is present - if p = resp.Payload; p == nil { - return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Payload") - } - - // Validation::1 -> All required fields are there - if p.MHDR == nil || p.MACPayload == nil || p.MACPayload.FHDR == nil || p.MACPayload.FHDR.FCtrl == nil { - return semtech.TXPK{}, errors.New(errors.Structural, "Invalid Payload Structure") + mac, mhdr, fhdr, fctrl, err := core.ValidateLoRaWANData(resp.Payload) + if err != nil { + return semtech.TXPK{}, errors.New(errors.Structural, err) } - - // Validation::2 -> Metadata is present if resp.Metadata == nil { return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Metadata") } - // Validation::3 -> The MIC is 4-bytes long - if len(p.MIC) != 4 { - return semtech.TXPK{}, errors.New(errors.Structural, "Invalid MIC") - } - - // Validation::4 -> Device address is 4-bytes long - if len(p.MACPayload.FHDR.DevAddr) != 4 { - return semtech.TXPK{}, errors.New(errors.Structural, "Invalid Device Address") - } - - mac, mhdr, fhdr, fctrl := p.MACPayload, p.MHDR, p.MACPayload.FHDR, p.MACPayload.FHDR.FCtrl - // Step 1: create a new LoRaWAN payload macpayload := lorawan.NewMACPayload(false) - macpayload.FPort = uint8(mac.FPort) // Validation::1 - copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) // Validation::1 && Validation::4 - macpayload.FHDR.FCnt = fhdr.FCnt // Validation::1 - for _, data := range fhdr.FOpts { // Validation::1 + macpayload.FPort = uint8(mac.FPort) + copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) + macpayload.FHDR.FCnt = fhdr.FCnt + for _, data := range fhdr.FOpts { cmd := new(lorawan.MACCommand) if err := cmd.UnmarshalBinary(data); err == nil { // We ignore invalid commands macpayload.FHDR.FOpts = append(macpayload.FHDR.FOpts, *cmd) } } - macpayload.FHDR.FCtrl.ADR = fctrl.ADR // Validation::1 - macpayload.FHDR.FCtrl.ACK = fctrl.Ack // Validation::1 - macpayload.FHDR.FCtrl.ADRACKReq = fctrl.ADRAckReq // Validation::1 - macpayload.FHDR.FCtrl.FPending = fctrl.FPending // Validation::1 - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ // Validation::1 + macpayload.FHDR.FCtrl.ADR = fctrl.ADR + macpayload.FHDR.FCtrl.ACK = fctrl.Ack + macpayload.FHDR.FCtrl.ADRACKReq = fctrl.ADRAckReq + macpayload.FHDR.FCtrl.FPending = fctrl.FPending + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ Bytes: mac.FRMPayload, }} payload := lorawan.NewPHYPayload(false) - payload.MHDR.MType = lorawan.MType(mhdr.MType) // Validation::1 - payload.MHDR.Major = lorawan.Major(mhdr.Major) // Validation::1 - copy(payload.MIC[:], resp.Payload.MIC) // Validation::1 && Validation::3 + payload.MHDR.MType = lorawan.MType(mhdr.MType) + payload.MHDR.Major = lorawan.Major(mhdr.Major) + copy(payload.MIC[:], resp.Payload.MIC) payload.MACPayload = macpayload // Step2: Convert the physical payload to a base64 string (without the padding) From a8da33a1dc906e8a555087e813c029de6f4634bd Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 14:34:02 +0100 Subject: [PATCH 1044/2266] [refactor/grpc] Finalize router by moving logic from http~>send to router --- core/components/router/router.go | 117 ++++++++++++++++++++++++------ core/components/router/storage.go | 27 +++---- 2 files changed, 104 insertions(+), 40 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index e84a2c9e2..236f5956e 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -4,6 +4,9 @@ package router import ( + "strings" + "sync" + "github.com/KtorZ/rpc/core" "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -97,17 +100,19 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co var response *core.DataBrokerRes if shouldBroadcast { // No Recipient available -> broadcast - response, err = r.send(bpacket, r.brokers...) + stats.MarkMeter("router.broadcast") + response, err = r.send(bpacket, true, r.brokers...) } else { // Recipients are available + stats.MarkMeter("router.send") var brokers []core.BrokerClient for _, e := range entries { brokers = append(brokers, r.brokers[e.BrokerIndex]) } - response, err = r.send(bpacket, brokers...) + response, err = r.send(bpacket, false, brokers...) if err != nil && err.(errors.Failure).Nature == errors.NotFound { // Might be a collision with the dev addr, we better broadcast - response, err = r.send(bpacket, r.brokers...) + response, err = r.send(bpacket, true, r.brokers...) } stats.MarkMeter("router.uplink.out") } @@ -147,25 +152,91 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c return &core.DataRouterRes{Payload: req.Payload, Metadata: req.Metadata}, nil } -func (r component) send(req *core.DataBrokerReq, brokers ...core.BrokerClient) (*core.DataBrokerRes, error) { +func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...core.BrokerClient) (*core.DataBrokerRes, error) { + // Define a more helpful context + ctx := r.ctx.WithField("devAddr", req.Payload.MACPayload.FHDR.DevAddr) + ctx.Debug("Sending Packet") + nb := len(brokers) + stats.UpdateHistogram("router.send_recipients", int64(nb)) + + // Prepare ground for parrallel requests + cherr := make(chan error, nb) + chresp := make(chan struct { + Response *core.DataBrokerRes + BrokerIndex int + }, nb) + wg := sync.WaitGroup{} + wg.Add(nb) + + // Run each request + for i, broker := range brokers { + go func(index int, broker core.BrokerClient) { + defer wg.Done() + + // Send request + resp, err := broker.HandleData(context.Background(), req) + + // Handle error + if err != nil { + if strings.Contains(err.Error(), string(errors.NotFound)) { // FIXME Find a better way to analyze the error + cherr <- errors.New(errors.NotFound, "Broker not responsible for the node") + return + } + cherr <- errors.New(errors.Operational, err) + return + } + + // Transfer the response + chresp <- struct { + Response *core.DataBrokerRes + BrokerIndex int + }{resp, index} + }(i, broker) + } - return nil, nil -} + // Wait for each request to be done + stats.IncCounter("router.waiting_for_send") + wg.Wait() + stats.DecCounter("router.waiting_for_send") + close(cherr) + close(chresp) + + var errored uint8 + var notFound uint8 + for i := 0; i < len(cherr); i++ { + err := <-cherr + if err.(errors.Failure).Nature != errors.NotFound { + errored++ + ctx.WithError(err).Warn("Failed to contact broker") + } else { + notFound++ + ctx.WithError(err).Debug("Packet destination not found") + } + } + + // Collect response + if len(chresp) > 1 { + return nil, errors.New(errors.Behavioural, "Received too many positive answers") + } + + if len(chresp) == 0 && errored > 0 { + return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answer") + } -// Register implements the core.Router interface -//func (r component) Register(reg Registration, an AckNacker) (err error) { -// defer ensureAckNack(an, nil, &err) -// stats.MarkMeter("router.registration.in") -// r.ctx.Debug("Handling registration") -// -// rreg, ok := reg.(RRegistration) -// if !ok { -// err = errors.New(errors.Structural, "Unexpected registration type") -// r.ctx.WithError(err).Warn("Unable to register") -// return err -// } -// -// return r.Store(rreg) -//} -// handleDataDown controls that data received from an uplink are okay. -// It also updates metadata about the related gateway + if len(chresp) == 0 && notFound > 0 { + return nil, errors.New(errors.NotFound, "No available recipient found") + } + + if len(chresp) == 0 { + return nil, nil + } + + resp := <-chresp + // Save the broker for later if it was a broadcast + if isBroadcast { + if err := r.Store(req.Payload.MACPayload.FHDR.DevAddr, resp.BrokerIndex); err != nil { + r.ctx.WithError(err).Warn("Failed to store accepted broker") + } + } + return resp.Response, nil +} diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 21db8a1fc..1ac195b8c 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -18,7 +18,7 @@ import ( // Storage gives a facade to manipulate the router database type Storage interface { Lookup(devAddr []byte) ([]entry, error) - //Store(reg RRegistration) error + Store(devAddr []byte, brokerIndex int) error LookupStats(gid []byte) (core.StatsMetadata, error) UpdateStats(gid []byte, metadata core.StatsMetadata) error Close() error @@ -101,22 +101,15 @@ func (s *storage) Lookup(devAddr []byte) ([]entry, error) { return entries, nil } -// -//// Store implements the router.Storage interface -//func (s *storage) Store(reg RRegistration) error { -// devEUI := reg.DevEUI() -// recipient, err := reg.Recipient().MarshalBinary() -// if err != nil { -// return errors.New(errors.Structural, err) -// } -// -// s.Lock() -// defer s.Unlock() -// return s.db.Store(dbBrokers, devEUI[:], []dbutil.Entry{&entry{ -// Recipient: recipient, -// until: time.Now().Add(s.ExpiryDelay), -// }}) -//} +// Store implements the router.Storage interface +func (s *storage) Store(devAddr []byte, brokerIndex int) error { + s.Lock() + defer s.Unlock() + return s.db.Store(dbBrokers, devAddr, []dbutil.Entry{&entry{ + BrokerIndex: brokerIndex, + until: time.Now().Add(s.ExpiryDelay), + }}) +} // Close implements the router.Storage interface func (s *storage) Close() error { From b53ebdc7046bb2d8cc75c78c34c9a75770fb86f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 14:37:54 +0100 Subject: [PATCH 1045/2266] [refactor/grpc] Fix comments in udp adapter & router storage -> golint --- core/adapters/udp/udp.go | 2 +- core/components/router/storage.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index a1d33c63b..fa0ffae6f 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -23,7 +23,7 @@ type adapter struct { type replier func(data []byte) error -// Starts constructs and launches a new udp adapter +// Start constructs and launches a new udp adapter func Start(bindNet string, router core.RouterServer, ctx log.Interface) error { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 1ac195b8c..7d940496d 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -116,8 +116,8 @@ func (s *storage) Close() error { return s.db.Close() } -// Marshal implements the proto.Marshaler interface -func (e entry) Marshal() ([]byte, error) { +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e entry) MarshalBinary() ([]byte, error) { data, err := e.until.MarshalBinary() if err != nil { return nil, errors.New(errors.Structural, err) @@ -129,8 +129,8 @@ func (e entry) Marshal() ([]byte, error) { return buf.Bytes(), nil } -// Unmarshal implements the proto.Unmarshaler interface -func (e *entry) Unmarshal(data []byte) error { +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *entry) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) // e.until From fc40fb3b370d459443e4e11fa17bdcbcf1ee3050 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 17:04:46 +0100 Subject: [PATCH 1046/2266] [refactor/grpc] Make Broker implements the grpc BrokerServer interface --- core/components/broker/broker.go | 274 ++++++------ core/components/broker/broker_test.go | 561 ------------------------- core/components/broker/controller.go | 145 +++++++ core/components/broker/helpers_test.go | 96 ----- core/components/broker/mock_test.go | 77 ---- core/components/broker/storage.go | 198 --------- core/components/broker/storage_test.go | 433 ------------------- 7 files changed, 288 insertions(+), 1496 deletions(-) delete mode 100644 core/components/broker/broker_test.go create mode 100644 core/components/broker/controller.go delete mode 100644 core/components/broker/helpers_test.go delete mode 100644 core/components/broker/mock_test.go delete mode 100644 core/components/broker/storage.go delete mode 100644 core/components/broker/storage_test.go diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index db52df115..051a0fe8f 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -4,10 +4,18 @@ package broker import ( - . "github.com/TheThingsNetwork/ttn/core" + "fmt" + "regexp" + "strings" + "time" + + "github.com/KtorZ/rpc/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" + "github.com/brocaar/lorawan" + "golang.org/x/net/context" + "google.golang.org/grpc" ) // component implements the core.Component interface @@ -17,161 +25,165 @@ type component struct { } // New construct a new Broker component -func New(controller NetworkController, ctx log.Interface) Broker { +func New(controller NetworkController, ctx log.Interface) core.BrokerServer { return component{NetworkController: controller, ctx: ctx} } -// Register implements the core.Broker interface -func (b component) Register(reg Registration, an AckNacker) (err error) { - defer ensureAckNack(an, nil, &err) - stats.MarkMeter("broker.registration.in") - b.ctx.Debug("Handling registration") - - switch reg.(type) { - case BRegistration: - err = b.StoreDevice(reg.(BRegistration)) - case ARegistration: - err = b.StoreApplication(reg.(ARegistration)) - default: - err = errors.New(errors.Structural, "Not a Broker registration") - } - - return err -} - -// HandleUp implements the core.Broker interface -func (b component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { - // Make sure we don't forget the AckNacker - var ack Packet - defer ensureAckNack(an, &ack, &err) - +// HandleData implements the core.RouterClient interface +func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*core.DataBrokerRes, error) { // Get some logs / analytics stats.MarkMeter("broker.uplink.in") b.ctx.Debug("Handling uplink packet") - // Extract the given packet - itf, err := UnmarshalPacket(data) + // Validate incoming data + uplinkPayload, err := core.NewLoRaWANData(req.Payload, true) if err != nil { - stats.MarkMeter("broker.uplink.invalid") - b.ctx.Warn("Uplink Invalid") - return errors.New(errors.Structural, err) + return nil, errors.New(errors.Structural, err) } + devAddr := req.Payload.MACPayload.FHDR.DevAddr // No nil ref, ensured by NewLoRaWANData() + ctx := b.ctx.WithField("DevAddr", devAddr) - switch itf.(type) { - case BPacket: - // 0. Retrieve the packet - packet := itf.(BPacket) - ctx := b.ctx.WithField("DevEUI", packet.DevEUI()) - - // 1. Check whether we should handle it - entries, err := b.LookupDevices(packet.DevEUI()) + // Check whether we should handle it + entries, err := b.LookupDevices(devAddr) + if err != nil { + switch err.(errors.Failure).Nature { + case errors.NotFound: + stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") + ctx.Debug("Uplink device not found") + default: + b.ctx.Warn("Database lookup failed") + } + return nil, err + } + stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) + + // Several handlers might be associated to the same device, we distinguish them using + // MIC check. Only one should verify the MIC check + // The device only stores a 16-bits counter but could reflect a 32-bits one. + // The counter is used for the MIC computation, thus, we're gonna try both 16-bits and + // 32-bits counters. + // We keep track of the real counter in the network controller. + fhdr := &uplinkPayload.MACPayload.(*lorawan.MACPayload).FHDR // No nil ref, ensured by NewLoRaWANData() + fcnt16 := fhdr.FCnt // Keep a reference to the original counter + + var mEntry *devEntry + for _, entry := range entries { + // retrieve the network session key + key := lorawan.AES128Key(entry.NwkSKey) + + // Check with 16-bits counters + fhdr.FCnt = fcnt16 + ok, err := uplinkPayload.ValidateMIC(key) if err != nil { - switch err.(errors.Failure).Nature { - case errors.NotFound: - stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") - ctx.Debug("Uplink device not found") - default: - b.ctx.Warn("Database lookup failed") - } - return err + continue } - stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) - - // 2. Several handlers might be associated to the same device, we distinguish them using - // MIC check. Only one should verify the MIC check - var mEntry *devEntry - for _, entry := range entries { - // The device only stores a 16-bits counter but could reflect a 32-bits one. - // We keep track of the real counter in the network controller. - if err := packet.ComputeFCnt(entry.FCntUp); err != nil { - continue - } - ok, err := packet.ValidateMIC(entry.NwkSKey) + + if !ok && entry.FCntUp > 65535 { // Check with 32-bits counter + fcnt32, err := b.WholeCounter(fcnt16, entry.FCntUp) if err != nil { continue } - if ok { - mEntry = &entry - stats.MarkMeter("broker.uplink.handler_lookup.mic_match") - ctx.WithField("handler", entry.Recipient).Debug("MIC check associated device with handler") - break - } + fhdr.FCnt = fcnt32 + ok, err = uplinkPayload.ValidateMIC(key) } - if mEntry == nil { - stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") - err := errors.New(errors.NotFound, "MIC check returned no matches") - ctx.Debug(err.Error()) - return err + + if ok { + mEntry = &entry + stats.MarkMeter("broker.uplink.handler_lookup.mic_match") + ctx.WithField("handler", entry.HandlerNet).Debug("MIC check succeeded") + break // We stop at the first valid check ... } + } - // It does matter here to use the DevEUI from the entry and not from the packet. - // The packet actually holds a DevAddr and the real DevEUI has been determined thanks - // to the MIC check - b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, packet.FCnt()) + if mEntry == nil { + stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") + err := errors.New(errors.NotFound, "MIC check returned no matches") + ctx.Debug(err.Error()) + return nil, err + } - // 4. Then we forward the packet to the handler and wait for the response - hpacket, err := NewHPacket(mEntry.AppEUI, mEntry.DevEUI, packet.Payload(), packet.Metadata()) - if err != nil { - return errors.New(errors.Structural, err) - } - recipient, err := up.GetRecipient(mEntry.Recipient) - if err != nil { - return errors.New(errors.Structural, err) - } - resp, err := up.Send(hpacket, recipient) - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - stats.MarkMeter("broker.uplink.bad_handler_response") - return errors.New(errors.Operational, err) - } - stats.MarkMeter("broker.uplink.ok") + // It does matter here to use the DevEUI from the entry and not from the packet. + // The packet actually holds a DevAddr and the real DevEUI has been determined thanks + // to the MIC check + persistence + if err := b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, fhdr.FCnt); err != nil { + return nil, err + } - // 5. If a response was sent, i.e. a downlink data, we notify the network controller - var bpacket BPacket - if resp != nil { - itf, err := UnmarshalPacket(resp) - if err != nil { - return errors.New(errors.Operational, err) - } - var ok bool - bpacket, ok = itf.(BPacket) - if !ok { - return errors.New(errors.Operational, "Received unexpected response") - } + // Then we forward the packet to the handler and wait for the response + conn, err := grpc.Dial(mEntry.HandlerNet, grpc.WithInsecure(), grpc.WithTimeout(time.Second*2)) + if err != nil { + return nil, err + } + defer conn.Close() + handler := core.NewHandlerClient(conn) + resp, err := handler.HandleData(context.Background(), &core.DataHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + DevEUI: mEntry.DevEUI, + AppEUI: mEntry.AppEUI, + FCnt: fhdr.FCnt, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + }) + + if err != nil && !strings.Contains(err.Error(), string(errors.NotFound)) { // FIXME Find better way to analyze error + stats.MarkMeter("broker.uplink.bad_handler_response") + return nil, errors.New(errors.Operational, err) + } + stats.MarkMeter("broker.uplink.ok") - // TODO Compute mic check - stats.MarkMeter("broker.downlink.in") - if err := bpacket.SetMIC(mEntry.NwkSKey); err != nil { - return errors.New(errors.Structural, "Unable to set response MIC") - } - } + // No response, we stop here and propagate the "no answer". + // In case of confirmed data, the handler is in charge of creating the confirmation + if resp == nil { + return nil, nil + } - // 6. And finally, we acknowledge the answer - if bpacket != nil { - rpacket, err := NewRPacket(bpacket.Payload(), []byte{}, bpacket.Metadata()) - if err != nil { - return errors.New(errors.Structural, "Invalid downlink packet from the handler") - } - stats.MarkMeter("broker.downlink.out") - ack = rpacket - } - case JPacket: - // TODO - return errors.New(errors.Implementation, "Join Request not yet implemented") - default: - return errors.New(errors.Implementation, "Unreckognized packet type") + // If a response was sent, i.e. a downlink data, we need to compute the right MIC + downlinkPayload, err := core.NewLoRaWANData(resp.Payload, false) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + stats.MarkMeter("broker.downlink.in") + if err := downlinkPayload.SetMIC(lorawan.AES128Key(mEntry.NwkSKey)); err != nil { + return nil, errors.New(errors.Structural, "Unable to set response MIC") } + resp.Payload.MIC = downlinkPayload.MIC[:] - return nil + // And finally, we acknowledge the answer + return &core.DataBrokerRes{ + Payload: resp.Payload, + Metadata: resp.Metadata, + }, nil } -func ensureAckNack(an AckNacker, ack *Packet, err *error) { - if err != nil && *err != nil { - an.Nack(*err) - } else { - var p Packet - if ack != nil { - p = *ack - } - an.Ack(p) +// Register implements the core.BrokerServer interface +func (b component) SubscribePersonalized(bctx context.Context, req *core.SubBrokerReq) (*core.SubBrokerRes, error) { + b.ctx.Debug("Handling personalized subscription") + + // Ensure the entry is valid + if len(req.AppEUI) != 8 { + return nil, errors.New(errors.Structural, "Invalid Application EUI") + } + + if len(req.DevEUI) != 8 { + return nil, errors.New(errors.Structural, "Invalid Device EUI") } + + var nwkSKey [16]byte + if len(req.NwkSKey) != 16 { + return nil, errors.New(errors.Structural, "Invalid Network Session Key") + } + copy(nwkSKey[:], req.NwkSKey) + + re := regexp.MustCompile("^[-\\w]+\\.([-\\w]+\\.?)+:\\d+$") + if !re.MatchString(req.HandlerNet) { + return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) + } + + return nil, b.StoreDevice(devEntry{ + HandlerNet: req.HandlerNet, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + NwkSKey: nwkSKey, + FCntUp: 0, + }) } diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go deleted file mode 100644 index 15ede4b02..000000000 --- a/core/components/broker/broker_test.go +++ /dev/null @@ -1,561 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - testutil "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestRegister(t *testing.T) { - { - testutil.Desc(t, "Register a device") - - // Build - an := mocks.NewMockAckNacker() - store := newMockController() - r := mocks.NewMockBRegistration() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.Register(r, an) - - // Check - errutil.CheckErrors(t, nil, err) - mocks.CheckAcks(t, true, an.InAck) - CheckRegistrations(t, r, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - } - - // ------------------- - - { - testutil.Desc(t, "Register an application") - - // Build - an := mocks.NewMockAckNacker() - store := newMockController() - r := mocks.NewMockARegistration() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.Register(r, an) - - // Check - errutil.CheckErrors(t, nil, err) - mocks.CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, r, store.InStoreApp) - } - - // ------------------- - - { - testutil.Desc(t, "Register a device | store failed") - - // Build - an := mocks.NewMockAckNacker() - store := newMockController() - store.Failures["StoreDevice"] = errors.New(errors.Structural, "Mock Error: Store Failed") - r := mocks.NewMockBRegistration() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.Register(r, an) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, r, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - } - - // ------------------- - - { - testutil.Desc(t, "Register an application | store failed") - - // Build - an := mocks.NewMockAckNacker() - store := newMockController() - store.Failures["StoreApplication"] = errors.New(errors.Structural, "Mock Error: Store Failed") - r := mocks.NewMockARegistration() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.Register(r, an) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, r, store.InStoreApp) - } - - // ------------------- - - { - testutil.Desc(t, "Register an entry | Wrong registration") - - // Build - an := mocks.NewMockAckNacker() - store := newMockController() - r := mocks.NewMockRRegistration() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.Register(r, an) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - } -} - -func TestHandleUp(t *testing.T) { - { - testutil.Desc(t, "Send an unknown packet") - - // Build - an := mocks.NewMockAckNacker() - adapter := mocks.NewMockAdapter() - store := newMockController() - store.Failures["LookupDevices"] = errors.New(errors.NotFound, "Mock Error: Not Found") - data, _ := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ).MarshalBinary() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.NotFound)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, nil, adapter.InSendPacket) - mocks.CheckRecipients(t, nil, adapter.InSendRecipients) - CheckCounters(t, 0, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send an invalid packet") - - // Build - an := mocks.NewMockAckNacker() - adapter := mocks.NewMockAdapter() - store := newMockController() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp([]byte{1, 2, 3}, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, nil, adapter.InSendPacket) - mocks.CheckRecipients(t, nil, adapter.InSendRecipients) - CheckCounters(t, 0, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 2 entries, no valid MIC") - - // Build - an := mocks.NewMockAckNacker() - adapter := mocks.NewMockAdapter() - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - }, - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 5, 5, 5, 5}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 6, 6, 11, 12, 13, 14, 12, 16}), - }, - } - data, _ := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ).MarshalBinary() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.NotFound)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, nil, adapter.InSendPacket) - mocks.CheckRecipients(t, nil, adapter.InSendRecipients) - CheckCounters(t, 0, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 2 entries, 1 valid MIC | No downlink") - - // Build - an := mocks.NewMockAckNacker() - recipient := mocks.NewMockRecipient() - adapter := mocks.NewMockAdapter() - adapter.OutSend = nil - adapter.OutGetRecipient = recipient - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - }, - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ := bpacket.MarshalBinary() - hpacket, _ := core.NewHPacket( - store.OutLookupDevices[1].AppEUI, - store.OutLookupDevices[1].DevEUI, - bpacket.Payload(), - bpacket.Metadata(), - ) - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, nil, err) - mocks.CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, hpacket, adapter.InSendPacket) - mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to get recipient") - - // Build - an := mocks.NewMockAckNacker() - adapter := mocks.NewMockAdapter() - adapter.OutSend = nil - adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Unable to get recipient") - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - }, - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ := bpacket.MarshalBinary() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, nil, adapter.InSendPacket) - mocks.CheckRecipients(t, nil, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 2 entries, 1 valid MIC | Fails to send") - - // Build - an := mocks.NewMockAckNacker() - recipient := mocks.NewMockRecipient() - adapter := mocks.NewMockAdapter() - adapter.OutGetRecipient = recipient - adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - DevEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - NwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}), - }, - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Payload", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ := bpacket.MarshalBinary() - hpacket, _ := core.NewHPacket( - store.OutLookupDevices[1].AppEUI, - store.OutLookupDevices[1].DevEUI, - bpacket.Payload(), - bpacket.Metadata(), - ) - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, hpacket, adapter.InSendPacket) - mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 valid downlink") - - // Build - an := mocks.NewMockAckNacker() - recipient := mocks.NewMockRecipient() - adapter := mocks.NewMockAdapter() - resp := newBPacketDown(1) - data, _ := resp.MarshalBinary() - adapter.OutSend = data - adapter.OutGetRecipient = recipient - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Uplink", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ = bpacket.MarshalBinary() - hpacket, _ := core.NewHPacket( - store.OutLookupDevices[0].AppEUI, - store.OutLookupDevices[0].DevEUI, - bpacket.Payload(), - bpacket.Metadata(), - ) - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, nil, err) - mocks.CheckAcks(t, true, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, hpacket, adapter.InSendPacket) - mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 invalid downlink") - - // Build - an := mocks.NewMockAckNacker() - recipient := mocks.NewMockRecipient() - adapter := mocks.NewMockAdapter() - adapter.OutSend = []byte{1, 2, 3} - adapter.OutGetRecipient = recipient - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Uplink", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ := bpacket.MarshalBinary() - hpacket, _ := core.NewHPacket( - store.OutLookupDevices[0].AppEUI, - store.OutLookupDevices[0].DevEUI, - bpacket.Payload(), - bpacket.Metadata(), - ) - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, hpacket, adapter.InSendPacket) - mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send packet, get 1 entry, 1 valid MIC | 1 unhandled downlink ") - - // Build - an := mocks.NewMockAckNacker() - recipient := mocks.NewMockRecipient() - adapter := mocks.NewMockAdapter() - resp, _ := core.NewAPacket( - lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - []byte{1, 2}, - nil, - ) - data, _ := resp.MarshalBinary() - adapter.OutSend = data - adapter.OutGetRecipient = recipient - store := newMockController() - store.OutLookupDevices = []devEntry{ - { - Recipient: []byte{1, 2, 3}, - AppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 5, 5, 5, 5}), - DevEUI: lorawan.EUI64([8]byte{4, 4, 4, 4, 2, 3, 2, 3}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}), - }, - } - bpacket := newBPacket( - [4]byte{2, 3, 2, 3}, - "Uplink", - [16]byte{1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8}, - 5, - ) - data, _ = bpacket.MarshalBinary() - hpacket, _ := core.NewHPacket( - store.OutLookupDevices[0].AppEUI, - store.OutLookupDevices[0].DevEUI, - bpacket.Payload(), - bpacket.Metadata(), - ) - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, hpacket, adapter.InSendPacket) - mocks.CheckRecipients(t, []core.Recipient{recipient}, adapter.InSendRecipients) - CheckCounters(t, 5, store.InUpdateFCnt) - } - - // ------------------- - - { - testutil.Desc(t, "Send unhandled packet type") - - // Build - an := mocks.NewMockAckNacker() - adapter := mocks.NewMockAdapter() - store := newMockController() - apacket, _ := core.NewAPacket( - lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - []byte{1, 2}, - nil, - ) - data, _ := apacket.MarshalBinary() - - // Operate - broker := New(store, testutil.GetLogger(t, "Broker")) - err := broker.HandleUp(data, an, adapter) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) - mocks.CheckAcks(t, false, an.InAck) - CheckRegistrations(t, nil, store.InStoreDevices) - CheckRegistrations(t, nil, store.InStoreApp) - mocks.CheckSent(t, nil, adapter.InSendPacket) - mocks.CheckRecipients(t, nil, adapter.InSendRecipients) - CheckCounters(t, 0, store.InUpdateFCnt) - } -} diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go new file mode 100644 index 000000000..63bd18ec1 --- /dev/null +++ b/core/components/broker/controller.go @@ -0,0 +1,145 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "bytes" + "encoding/binary" + "math" + "reflect" + "sync" + + "github.com/TheThingsNetwork/ttn/utils/errors" + dbutil "github.com/TheThingsNetwork/ttn/utils/storage" +) + +// NetworkController gives a facade for manipulating the broker databases and devices +type NetworkController interface { + LookupDevices(devAddr []byte) ([]devEntry, error) + WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) + StoreDevice(entry devEntry) error + UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error + Close() error +} + +type devEntry struct { + HandlerNet string + AppEUI []byte + DevEUI []byte + NwkSKey [16]byte + FCntUp uint32 +} + +type controller struct { + sync.RWMutex + db dbutil.Interface + Devices string + Applications string +} + +// NewNetworkController constructs a new broker controller +func NewNetworkController(name string) (NetworkController, error) { + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + return &controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil +} + +// LookupDevices implements the broker.NetworkController interface +func (s *controller) LookupDevices(devAddr []byte) ([]devEntry, error) { + s.RLock() + defer s.RUnlock() + entries, err := s.db.Lookup(s.Devices, append([]byte{0, 0, 0, 0}, devAddr...), &devEntry{}) + if err != nil { + return nil, err + } + return entries.([]devEntry), nil +} + +// WholeCounter implements the broker.NetworkController interface +func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { + upperSup := uint32(math.Pow(2, 16)) + diff := devCnt - (entryCnt % upperSup) + var offset uint32 + if diff >= 0 { + offset = diff + } else { + offset = upperSup + diff + } + if offset > upperSup/4 { + return 0, errors.New(errors.Structural, "Gap too big, counter is errored") + } + return entryCnt + offset, nil +} + +// UpdateFCnt implements the broker.NetworkController interface +func (s *controller) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { + s.Lock() + defer s.Unlock() + itf, err := s.db.Lookup(s.Devices, devEUI, &devEntry{}) + if err != nil { + return err + } + entries := itf.([]devEntry) + + var newEntries []dbutil.Entry + for _, e := range entries { + entry := new(devEntry) + *entry = e + if reflect.DeepEqual(entry.AppEUI, appEUI) { + entry.FCntUp = fcnt + } + newEntries = append(newEntries, entry) + } + + return s.db.Replace(s.Devices, devEUI, newEntries) +} + +// StoreDevice implements the broker.NetworkController interface +func (s *controller) StoreDevice(entry devEntry) error { + s.Lock() + defer s.Unlock() + return s.db.Store(s.Devices, entry.DevEUI, []dbutil.Entry{&entry}) +} + +// Close implements the broker.NetworkController interface +func (s *controller) Close() error { + return s.db.Close() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e devEntry) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, e.AppEUI) // 8 + binary.Write(buf, binary.BigEndian, e.DevEUI) // 8 + binary.Write(buf, binary.BigEndian, e.NwkSKey) // 16 + binary.Write(buf, binary.BigEndian, e.FCntUp) // 4 + if len(buf.Bytes()) != 36 { + return nil, errors.New(errors.Structural, "Device entry was invalid. Cannot Marshal") + } + binary.Write(buf, binary.BigEndian, []byte(e.HandlerNet)) + return buf.Bytes(), nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *devEntry) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + e.AppEUI = make([]byte, 8, 8) + binary.Read(buf, binary.BigEndian, &e.AppEUI) + e.DevEUI = make([]byte, 8, 8) + binary.Read(buf, binary.BigEndian, &e.DevEUI) + binary.Read(buf, binary.BigEndian, &e.NwkSKey) // fixed-length array + if err := binary.Read(buf, binary.BigEndian, &e.FCntUp); err != nil { + return errors.New(errors.Structural, err) + } + + var handler []byte + if err := binary.Read(buf, binary.BigEndian, &handler); err != nil { + return errors.New(errors.Structural, err) + } + e.HandlerNet = string(handler) + return nil +} diff --git a/core/components/broker/helpers_test.go b/core/components/broker/helpers_test.go deleted file mode 100644 index a3e94f4d8..000000000 --- a/core/components/broker/helpers_test.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/brocaar/lorawan" -) - -// ----- BUILD utilities -func newBPacket(rawDevAddr [4]byte, payload string, nwkSKey [16]byte, fcnt uint32) core.BPacket { - var devAddr lorawan.DevAddr - copy(devAddr[:], rawDevAddr[:]) - - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - FCnt: fcnt, - DevAddr: devAddr, - } - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - macPayload.FPort = 1 - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MACPayload = macPayload - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - - var key lorawan.AES128Key - copy(key[:], nwkSKey[:]) - if err := phyPayload.SetMIC(key); err != nil { - panic(err) - } - - phyPayload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = fcnt % 65536 // only 16-bits - - packet, err := core.NewBPacket(phyPayload, core.Metadata{}) - if err != nil { - panic(err) - } - return packet -} - -func newBPacketDown(fcnt uint32) core.BPacket { - macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ - FCnt: fcnt, - DevAddr: lorawan.DevAddr([4]byte{1, 1, 1, 1}), - } - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte("downlink")}} - macPayload.FPort = 1 - phyPayload := lorawan.NewPHYPayload(false) - phyPayload.MACPayload = macPayload - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - } - - if err := phyPayload.SetMIC(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6})); err != nil { - panic(err) - } - - phyPayload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = fcnt % 65536 // only 16-bits - - packet, err := core.NewBPacket(phyPayload, core.Metadata{}) - if err != nil { - panic(err) - } - return packet - -} - -// ----- CHECK utilities -func CheckDevEntries(t *testing.T, want []devEntry, got []devEntry) { - mocks.Check(t, want, got, "DevEntries") -} - -func CheckAppEntries(t *testing.T, want appEntry, got appEntry) { - mocks.Check(t, want, got, "AppEntries") -} - -func CheckRegistrations(t *testing.T, want core.Registration, got core.Registration) { - mocks.Check(t, want, got, "Registrations") -} - -func CheckCounters(t *testing.T, want uint32, got uint32) { - mocks.Check(t, want, got, "Counters") -} - -func CheckDirections(t *testing.T, want string, got string) { - mocks.Check(t, want, got, "Directions") -} diff --git a/core/components/broker/mock_test.go b/core/components/broker/mock_test.go deleted file mode 100644 index 0c7df344a..000000000 --- a/core/components/broker/mock_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -type mockController struct { - Failures map[string]error - InLookupDevices lorawan.EUI64 - InLookupApp lorawan.EUI64 - InStoreDevices core.BRegistration - InStoreApp core.ARegistration - InUpdateAppEUI lorawan.EUI64 - InUpdateDevEUI lorawan.EUI64 - InUpdateFCnt uint32 - OutLookupDevices []devEntry - OutLookupApp appEntry -} - -func newMockController() *mockController { - return &mockController{ - Failures: make(map[string]error), - OutLookupApp: appEntry{ - Recipient: []byte("MockControllerRecipient"), - AppEUI: lorawan.EUI64([8]byte{5, 5, 5, 5, 5, 5, 6, 6}), - }, - OutLookupDevices: []devEntry{ - { - Recipient: []byte("MockControllerRecipient"), - AppEUI: lorawan.EUI64([8]byte{1, 1, 2, 2, 3, 3, 1, 1}), - DevEUI: lorawan.EUI64([8]byte{2, 3, 4, 5, 6, 7, 5, 4}), - NwkSKey: lorawan.AES128Key([16]byte{1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 4, 4, 5, 5}), - }, - }, - } -} - -func (s *mockController) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { - s.InLookupDevices = devEUI - if s.Failures["LookupDevices"] != nil { - return nil, s.Failures["LookupDevices"] - } - return s.OutLookupDevices, nil -} - -func (s *mockController) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { - s.InLookupApp = appEUI - if s.Failures["LookupApplication"] != nil { - return appEntry{}, s.Failures["LookupApplication"] - } - return s.OutLookupApp, nil -} - -func (s *mockController) StoreDevice(reg core.BRegistration) error { - s.InStoreDevices = reg - return s.Failures["StoreDevice"] -} - -func (s *mockController) StoreApplication(reg core.ARegistration) error { - s.InStoreApp = reg - return s.Failures["StoreApplication"] -} - -func (s *mockController) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { - s.InUpdateAppEUI = appEUI - s.InUpdateDevEUI = devEUI - s.InUpdateFCnt = fcnt - return s.Failures["UpdateFCnt"] -} - -func (s *mockController) Close() error { - return s.Failures["Close"] -} diff --git a/core/components/broker/storage.go b/core/components/broker/storage.go deleted file mode 100644 index ce50e704a..000000000 --- a/core/components/broker/storage.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "bytes" - "encoding/binary" - "sync" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" - dbutil "github.com/TheThingsNetwork/ttn/utils/storage" - "github.com/brocaar/lorawan" -) - -// NetworkController gives a facade for manipulating the broker databases and devices -type NetworkController interface { - LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) - LookupApplication(appEUI lorawan.EUI64) (appEntry, error) - StoreDevice(reg core.BRegistration) error - StoreApplication(reg core.ARegistration) error - UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error - Close() error -} - -type devEntry struct { - Recipient []byte - AppEUI lorawan.EUI64 - DevEUI lorawan.EUI64 - NwkSKey lorawan.AES128Key - FCntUp uint32 -} - -type appEntry struct { - Recipient []byte - AppEUI lorawan.EUI64 -} - -type controller struct { - sync.RWMutex - db dbutil.Interface - Devices string - Applications string -} - -// NewNetworkController constructs a new broker controller -func NewNetworkController(name string) (NetworkController, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - - return &controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil -} - -// LookupDevices implements the broker.NetworkController interface -func (s *controller) LookupDevices(devEUI lorawan.EUI64) ([]devEntry, error) { - s.RLock() - defer s.RUnlock() - entries, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) - if err != nil { - return nil, err - } - return entries.([]devEntry), nil -} - -// LookupApplication implements the broker.NetworkController interface -func (s *controller) LookupApplication(appEUI lorawan.EUI64) (appEntry, error) { - s.RLock() - defer s.RUnlock() - itf, err := s.db.Lookup(s.Applications, appEUI[:], &appEntry{}) - if err != nil { - return appEntry{}, err - } - - entries := itf.([]appEntry) - if len(entries) != 1 { - // NOTE Shall we reset the entry ? - return appEntry{}, errors.New(errors.Structural, "Invalid application entries") - } - - return entries[0], nil -} - -// UpdateFCnt implements the broker.NetworkController interface -func (s *controller) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { - s.Lock() - defer s.Unlock() - itf, err := s.db.Lookup(s.Devices, devEUI[:], &devEntry{}) - if err != nil { - return err - } - entries := itf.([]devEntry) - - var newEntries []dbutil.Entry - for _, e := range entries { - entry := new(devEntry) - *entry = e - if entry.AppEUI == appEUI { - entry.FCntUp = fcnt - } - newEntries = append(newEntries, entry) - } - - return s.db.Replace(s.Devices, devEUI[:], newEntries) -} - -// StoreDevice implements the broker.NetworkController interface -func (s *controller) StoreDevice(reg core.BRegistration) error { - s.Lock() - defer s.Unlock() - data, err := reg.Recipient().MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - - devEUI := reg.DevEUI() - return s.db.Store(s.Devices, devEUI[:], []dbutil.Entry{ - &devEntry{ - Recipient: data, - AppEUI: reg.AppEUI(), - DevEUI: devEUI, - NwkSKey: reg.NwkSKey(), - }, - }) -} - -// StoreApplication implements the broker.NetworkController interface -func (s *controller) StoreApplication(reg core.ARegistration) error { - s.Lock() - defer s.Unlock() - data, err := reg.Recipient().MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - - appEUI := reg.AppEUI() - return s.db.Replace(s.Applications, appEUI[:], []dbutil.Entry{ - &appEntry{ - Recipient: data, - AppEUI: appEUI, - }, - }) -} - -// Close implements the broker.NetworkController interface -func (s *controller) Close() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e devEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.Recipient) - rw.Write(e.AppEUI) - rw.Write(e.DevEUI) - rw.Write(e.NwkSKey) - rw.Write(e.FCntUp) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *devEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { e.Recipient = data }) - rw.Read(func(data []byte) { copy(e.AppEUI[:], data) }) - rw.Read(func(data []byte) { copy(e.DevEUI[:], data) }) - rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) - rw.TryRead(func(data []byte) error { - buf := new(bytes.Buffer) - buf.Write(data) - fcnt := new(uint32) - if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil { - return err - } - e.FCntUp = *fcnt - return nil - }) - return rw.Err() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e appEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.Recipient) - rw.Write(e.AppEUI) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *appEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { e.Recipient = data }) - rw.Read(func(data []byte) { copy(e.AppEUI[:], data) }) - return rw.Err() -} diff --git a/core/components/broker/storage_test.go b/core/components/broker/storage_test.go deleted file mode 100644 index 5a5cf0f09..000000000 --- a/core/components/broker/storage_test.go +++ /dev/null @@ -1,433 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const NetworkControllerDB = "TestBrokerNetworkController.db" - -func TestNetworkControllerDevice(t *testing.T) { - NetworkControllerDB := path.Join(os.TempDir(), NetworkControllerDB) - defer func() { - os.Remove(NetworkControllerDB) - }() - - // ------------------- - - { - Desc(t, "Create a new NetworkController") - db, err := NewNetworkController(NetworkControllerDB) - CheckErrors(t, nil, err) - err = db.Close() - CheckErrors(t, nil, err) - } - - // ------------------- - - { - Desc(t, "Store then lookup a registration") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - - // Operate - err := db.StoreDevice(r) - CheckErrors(t, nil, err) - entries, err := db.LookupDevices(r.DevEUI()) - - // Expectations - want := []devEntry{ - { - AppEUI: r.AppEUI(), - DevEUI: r.DevEUI(), - NwkSKey: r.NwkSKey(), - Recipient: r.RawRecipient(), - FCntUp: 0, - }, - } - - // Check - CheckErrors(t, nil, err) - CheckDevEntries(t, want, entries) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Store entries with same DevEUI") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - r.OutDevEUI[0] = 34 - - // Operate - err := db.StoreDevice(r) - CheckErrors(t, nil, err) - err = db.StoreDevice(r) - CheckErrors(t, nil, err) - entries, err := db.LookupDevices(r.DevEUI()) - - // Expectations - want := []devEntry{ - { - AppEUI: r.AppEUI(), - DevEUI: r.DevEUI(), - NwkSKey: r.NwkSKey(), - Recipient: r.RawRecipient(), - FCntUp: 0, - }, - { - AppEUI: r.AppEUI(), - DevEUI: r.DevEUI(), - NwkSKey: r.NwkSKey(), - Recipient: r.RawRecipient(), - FCntUp: 0, - }, - } - - // Check - CheckErrors(t, nil, err) - CheckDevEntries(t, want, entries) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Lookup non-existing entry") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - devEUI := NewMockBRegistration().DevEUI() - devEUI[1] = 98 - - // Operate - entries, err := db.LookupDevices(devEUI) - - // Checks - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckDevEntries(t, nil, entries) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Store on a closed database") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - _ = db.Close() - r := NewMockBRegistration() - r.OutDevEUI[5] = 9 - - // Operate - err := db.StoreDevice(r) - - // Checks - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // ------------------- - - { - Desc(t, "Lookup on a closed database") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - _ = db.Close() - devEUI := NewMockBRegistration().DevEUI() - - // Operate - entries, err := db.LookupDevices(devEUI) - - // Checks - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckDevEntries(t, nil, entries) - } - - // ------------------- - - { - Desc(t, "Store an invalid recipient") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - r.OutDevEUI[7] = 99 - r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") - - // Operate & Check - err := db.StoreDevice(r) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - entries, err := db.LookupDevices(r.DevEUI()) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckDevEntries(t, nil, entries) - - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Update counter up of an entry -> one device") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - r.OutDevEUI[4] = 0xba - - // Operate - err := db.StoreDevice(r) - CheckErrors(t, nil, err) - err1 := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14) - entries, err2 := db.LookupDevices(r.DevEUI()) - - // Expectations - want := []devEntry{ - { - AppEUI: r.AppEUI(), - DevEUI: r.DevEUI(), - NwkSKey: r.NwkSKey(), - Recipient: r.RawRecipient(), - FCntUp: 14, - }, - } - - // Check - CheckErrors(t, nil, err1) - CheckErrors(t, nil, err2) - CheckDevEntries(t, want, entries) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Update counter -> fail to lookup") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockBRegistration() - r.OutDevEUI[4] = 0xde - - // Operate - err := db.UpdateFCnt(r.AppEUI(), r.DevEUI(), 14) - - // Checks - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Update counter several entries") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r1 := NewMockBRegistration() - r1.OutDevEUI[3] = 0xbb - r2 := NewMockBRegistration() - r2.OutDevEUI[3] = 0xbb - r2.OutAppEUI[4] = 14 - - // Operate - err := db.StoreDevice(r1) - CheckErrors(t, nil, err) - err = db.StoreDevice(r2) - CheckErrors(t, nil, err) - err1 := db.UpdateFCnt(r2.AppEUI(), r2.DevEUI(), 14) - entries, err2 := db.LookupDevices(r2.DevEUI()) - - // Expectations - want := []devEntry{ - { - AppEUI: r1.AppEUI(), - DevEUI: r1.DevEUI(), - NwkSKey: r1.NwkSKey(), - Recipient: r1.RawRecipient(), - FCntUp: 0, - }, - { - AppEUI: r2.AppEUI(), - DevEUI: r2.DevEUI(), - NwkSKey: r2.NwkSKey(), - Recipient: r2.RawRecipient(), - FCntUp: 14, - }, - } - - // Check - CheckErrors(t, nil, err1) - CheckErrors(t, nil, err2) - CheckDevEntries(t, want, entries) - _ = db.Close() - } -} - -func TestNetworkControllerApplication(t *testing.T) { - defer func() { - os.Remove(NetworkControllerDB) - }() - - // ------------------- - - { - Desc(t, "Create a new NetworkController") - db, err := NewNetworkController(NetworkControllerDB) - CheckErrors(t, nil, err) - err = db.Close() - CheckErrors(t, nil, err) - } - - // ------------------- - - { - Desc(t, "Store then lookup a registration") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockARegistration() - - // Operate - err := db.StoreApplication(r) - CheckErrors(t, nil, err) - entry, err := db.LookupApplication(r.AppEUI()) - - // Expectations - want := appEntry{ - AppEUI: r.AppEUI(), - Recipient: r.RawRecipient(), - } - - // Check - CheckErrors(t, nil, err) - CheckAppEntries(t, want, entry) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Store entries with same AppEUI") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r1 := NewMockARegistration() - r1.OutAppEUI[0] = 34 - r2 := NewMockARegistration() - r2.OutAppEUI[0] = 34 - r2.OutRecipient = NewMockRecipient() - r2.OutRecipient.(*MockRecipient).OutMarshalBinary = []byte{88, 99, 77, 66} - - // Operate - err := db.StoreApplication(r1) - CheckErrors(t, nil, err) - err = db.StoreApplication(r2) - CheckErrors(t, nil, err) - entry, err := db.LookupApplication(r1.AppEUI()) - - // Expectations - want := appEntry{ - AppEUI: r2.AppEUI(), - Recipient: r2.RawRecipient(), - } - - // Check - CheckErrors(t, nil, err) - CheckAppEntries(t, want, entry) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Lookup non-existing entry") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - appEUI := NewMockARegistration().AppEUI() - appEUI[1] = 98 - - // Operate - entry, err := db.LookupApplication(appEUI) - - // Checks - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckAppEntries(t, appEntry{}, entry) - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Store on a closed database") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - _ = db.Close() - r := NewMockARegistration() - r.OutAppEUI[5] = 9 - - // Operate - err := db.StoreApplication(r) - - // Checks - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // ------------------- - - { - Desc(t, "Lookup on a closed database") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - _ = db.Close() - appEUI := NewMockARegistration().AppEUI() - - // Operate - entry, err := db.LookupApplication(appEUI) - - // Checks - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckAppEntries(t, appEntry{}, entry) - } - - // ------------------- - - { - Desc(t, "Store an invalid recipient") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - r := NewMockARegistration() - r.OutAppEUI[7] = 99 - r.OutRecipient.(*MockRecipient).Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error: MarshalBinary") - - // Operate & Check - err := db.StoreApplication(r) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - entry, err := db.LookupApplication(r.AppEUI()) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckAppEntries(t, appEntry{}, entry) - - _ = db.Close() - } -} From 0b094cc51d1b07b3b0631e92b6c1f3049e792554 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 17:05:38 +0100 Subject: [PATCH 1047/2266] [refactor/grpc] Move creation of LoRaWANData to core package --- core/adapters/udp/conversions.go | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 561bf847a..ef361239f 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -91,37 +91,15 @@ func (a adapter) newDataRouterReq(rxpk semtech.RXPK, gid []byte) (*core.DataRout func (a adapter) newTXPK(resp core.DataRouterRes) (semtech.TXPK, error) { // Step 0: validate the response - mac, mhdr, fhdr, fctrl, err := core.ValidateLoRaWANData(resp.Payload) - if err != nil { - return semtech.TXPK{}, errors.New(errors.Structural, err) - } if resp.Metadata == nil { return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Metadata") } // Step 1: create a new LoRaWAN payload - macpayload := lorawan.NewMACPayload(false) - macpayload.FPort = uint8(mac.FPort) - copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) - macpayload.FHDR.FCnt = fhdr.FCnt - for _, data := range fhdr.FOpts { - cmd := new(lorawan.MACCommand) - if err := cmd.UnmarshalBinary(data); err == nil { // We ignore invalid commands - macpayload.FHDR.FOpts = append(macpayload.FHDR.FOpts, *cmd) - } + payload, err := core.NewLoRaWANData(resp.Payload, false) + if err != nil { + return semtech.TXPK{}, errors.New(errors.Structural, err) } - macpayload.FHDR.FCtrl.ADR = fctrl.ADR - macpayload.FHDR.FCtrl.ACK = fctrl.Ack - macpayload.FHDR.FCtrl.ADRACKReq = fctrl.ADRAckReq - macpayload.FHDR.FCtrl.FPending = fctrl.FPending - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: mac.FRMPayload, - }} - payload := lorawan.NewPHYPayload(false) - payload.MHDR.MType = lorawan.MType(mhdr.MType) - payload.MHDR.Major = lorawan.Major(mhdr.Major) - copy(payload.MIC[:], resp.Payload.MIC) - payload.MACPayload = macpayload // Step2: Convert the physical payload to a base64 string (without the padding) raw, err := payload.MarshalBinary() From 22826cfcaa4c0af0deabd9d8e0cf4b2936882f91 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 17:26:25 +0100 Subject: [PATCH 1048/2266] [refactor/grpc] Replace old core package with new grpc generated --- core/adapters/udp/conversions.go | 2 +- core/adapters/udp/udp.go | 2 +- core/broker.pb.go | 1046 +++++++++++++++ core/build_utilities_test.go | 72 -- core/check_utilities_test.go | 69 - core/components/broker/broker.go | 2 +- core/components/router/router.go | 2 +- core/components/router/storage.go | 2 +- core/core.go | 94 +- core/core.pb.go | 772 +++++++++++ core/handler.pb.go | 776 +++++++++++ core/lorawan.pb.go | 1976 +++++++++++++++++++++++++++++ core/metadata.go | 132 -- core/metadata_test.go | 108 -- core/packets.go | 766 ----------- core/packets_interfaces.go | 78 -- core/packets_test.go | 556 -------- core/protos/broker.proto | 29 + core/protos/core.proto | 27 + core/protos/handler.proto | 23 + core/protos/lorawan.proto | 57 + core/protos/router.proto | 28 + core/registrations_interfaces.go | 55 - core/router.pb.go | 977 ++++++++++++++ 24 files changed, 5781 insertions(+), 1870 deletions(-) create mode 100644 core/broker.pb.go delete mode 100644 core/build_utilities_test.go delete mode 100644 core/check_utilities_test.go create mode 100644 core/core.pb.go create mode 100644 core/handler.pb.go create mode 100644 core/lorawan.pb.go delete mode 100644 core/metadata.go delete mode 100644 core/metadata_test.go delete mode 100644 core/packets.go delete mode 100644 core/packets_interfaces.go delete mode 100644 core/packets_test.go create mode 100644 core/protos/broker.proto create mode 100644 core/protos/core.proto create mode 100644 core/protos/handler.proto create mode 100644 core/protos/lorawan.proto create mode 100644 core/protos/router.proto delete mode 100644 core/registrations_interfaces.go create mode 100644 core/router.pb.go diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index ef361239f..4989feb79 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -8,7 +8,7 @@ import ( "reflect" "strings" - "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index fa0ffae6f..ede990fa6 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -9,7 +9,7 @@ import ( "sync" "time" - "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" diff --git a/core/broker.pb.go b/core/broker.pb.go new file mode 100644 index 000000000..c05a0af1b --- /dev/null +++ b/core/broker.pb.go @@ -0,0 +1,1046 @@ +// Code generated by protoc-gen-gogo. +// source: broker.proto +// DO NOT EDIT! + +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + broker.proto + core.proto + handler.proto + router.proto + lorawan.proto + + It has these top-level messages: + DataBrokerReq + DataBrokerRes + SubBrokerReq + SubBrokerRes + Metadata + StatsMetadata + DataHandlerReq + DataHandlerRes + DataRouterReq + DataRouterRes + StatsReq + StatsRes + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings +*/ +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type DataBrokerReq struct { + Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *DataBrokerReq) Reset() { *m = DataBrokerReq{} } +func (m *DataBrokerReq) String() string { return proto.CompactTextString(m) } +func (*DataBrokerReq) ProtoMessage() {} +func (*DataBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{0} } + +func (m *DataBrokerReq) GetPayload() *LoRaWANData { + if m != nil { + return m.Payload + } + return nil +} + +func (m *DataBrokerReq) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type DataBrokerRes struct { + Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *DataBrokerRes) Reset() { *m = DataBrokerRes{} } +func (m *DataBrokerRes) String() string { return proto.CompactTextString(m) } +func (*DataBrokerRes) ProtoMessage() {} +func (*DataBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{1} } + +func (m *DataBrokerRes) GetPayload() *LoRaWANData { + if m != nil { + return m.Payload + } + return nil +} + +func (m *DataBrokerRes) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type SubBrokerReq struct { + HandlerNet string `protobuf:"bytes,1,opt,name=HandlerNet,json=handlerNet,proto3" json:"HandlerNet,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` +} + +func (m *SubBrokerReq) Reset() { *m = SubBrokerReq{} } +func (m *SubBrokerReq) String() string { return proto.CompactTextString(m) } +func (*SubBrokerReq) ProtoMessage() {} +func (*SubBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } + +type SubBrokerRes struct { +} + +func (m *SubBrokerRes) Reset() { *m = SubBrokerRes{} } +func (m *SubBrokerRes) String() string { return proto.CompactTextString(m) } +func (*SubBrokerRes) ProtoMessage() {} +func (*SubBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } + +func init() { + proto.RegisterType((*DataBrokerReq)(nil), "core.DataBrokerReq") + proto.RegisterType((*DataBrokerRes)(nil), "core.DataBrokerRes") + proto.RegisterType((*SubBrokerReq)(nil), "core.SubBrokerReq") + proto.RegisterType((*SubBrokerRes)(nil), "core.SubBrokerRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Broker service + +type BrokerClient interface { + HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) + SubscribePersonalized(ctx context.Context, in *SubBrokerReq, opts ...grpc.CallOption) (*SubBrokerRes, error) +} + +type brokerClient struct { + cc *grpc.ClientConn +} + +func NewBrokerClient(cc *grpc.ClientConn) BrokerClient { + return &brokerClient{cc} +} + +func (c *brokerClient) HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) { + out := new(DataBrokerRes) + err := grpc.Invoke(ctx, "/core.Broker/HandleData", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *SubBrokerReq, opts ...grpc.CallOption) (*SubBrokerRes, error) { + out := new(SubBrokerRes) + err := grpc.Invoke(ctx, "/core.Broker/SubscribePersonalized", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Broker service + +type BrokerServer interface { + HandleData(context.Context, *DataBrokerReq) (*DataBrokerRes, error) + SubscribePersonalized(context.Context, *SubBrokerReq) (*SubBrokerRes, error) +} + +func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { + s.RegisterService(&_Broker_serviceDesc, srv) +} + +func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DataBrokerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerServer).HandleData(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _Broker_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(SubBrokerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerServer).SubscribePersonalized(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Broker_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.Broker", + HandlerType: (*BrokerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "HandleData", + Handler: _Broker_HandleData_Handler, + }, + { + MethodName: "SubscribePersonalized", + Handler: _Broker_SubscribePersonalized_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DataBrokerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataBrokerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(m.Payload.Size())) + n1, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) + n2, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *DataBrokerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataBrokerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(m.Payload.Size())) + n3, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) + n4, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *SubBrokerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SubBrokerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.HandlerNet) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.HandlerNet))) + i += copy(data[i:], m.HandlerNet) + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + return i, nil +} + +func (m *SubBrokerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SubBrokerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64Broker(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Broker(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintBroker(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DataBrokerReq) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DataBrokerRes) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *SubBrokerReq) Size() (n int) { + var l int + _ = l + l = len(m.HandlerNet) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } + return n +} + +func (m *SubBrokerRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovBroker(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozBroker(x uint64) (n int) { + return sovBroker(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DataBrokerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataBrokerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANData{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DataBrokerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataBrokerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANData{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubBrokerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubBrokerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HandlerNet", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HandlerNet = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubBrokerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubBrokerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBroker(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthBroker + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipBroker(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthBroker = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBroker = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorBroker = []byte{ + // 304 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2a, 0xca, 0xcf, + 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, + 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, + 0x0c, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0x44, 0x27, 0xb0, 0xa6, 0xa0, 0xd4, 0x42, 0x21, 0x6d, 0x2e, + 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x41, + 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, + 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, + 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0x55, + 0x70, 0xf1, 0x04, 0x97, 0x26, 0x21, 0xbc, 0x24, 0xc7, 0xc5, 0xe5, 0x91, 0x98, 0x97, 0x92, 0x93, + 0x5a, 0xe4, 0x97, 0x5a, 0x02, 0xb6, 0x8b, 0x33, 0x88, 0x2b, 0x03, 0x2e, 0x22, 0x24, 0xc6, 0xc5, + 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0x36, 0x99, 0x27, 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, + 0xbb, 0xa4, 0x96, 0x81, 0xc4, 0x99, 0x21, 0xe2, 0x29, 0x60, 0x9e, 0x90, 0x04, 0x17, 0xbb, 0x5f, + 0x79, 0x76, 0xb0, 0x77, 0x6a, 0xa5, 0x04, 0x0b, 0x58, 0x82, 0x3d, 0x0f, 0xc2, 0x55, 0xe2, 0x43, + 0xb1, 0xb9, 0xd8, 0xa8, 0x91, 0x91, 0x8b, 0x0d, 0xc2, 0x13, 0x32, 0x83, 0x39, 0x02, 0xe4, 0x2f, + 0x21, 0x61, 0x88, 0xe3, 0x51, 0x82, 0x5e, 0x0a, 0x8b, 0x60, 0xb1, 0x90, 0x3d, 0x97, 0x28, 0xd0, + 0xc8, 0xe2, 0xe4, 0xa2, 0xcc, 0xa4, 0xd4, 0x80, 0xd4, 0xa2, 0xe2, 0xfc, 0xbc, 0xc4, 0x9c, 0xcc, + 0xaa, 0xd4, 0x14, 0x21, 0x21, 0x88, 0x6a, 0x64, 0x9f, 0x4a, 0x61, 0x8a, 0x15, 0x3b, 0x09, 0x9c, + 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, + 0xa8, 0x37, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xba, 0x4b, 0xd2, 0x13, 0x2b, 0x02, 0x00, 0x00, +} diff --git a/core/build_utilities_test.go b/core/build_utilities_test.go deleted file mode 100644 index 3e37af552..000000000 --- a/core/build_utilities_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/brocaar/lorawan" -) - -// Generates a Metadata object with all field completed with relevant values -func genFullMetadata() Metadata { - timeRef := time.Date(2016, 1, 13, 14, 11, 28, 207288421, time.UTC) - return Metadata{ - Chan: pointer.Uint(2), - Codr: pointer.String("4/6"), - Datr: pointer.String("LORA"), - Fdev: pointer.Uint(3), - Freq: pointer.Float64(863.125), - Imme: pointer.Bool(false), - Ipol: pointer.Bool(false), - Lsnr: pointer.Float64(5.2), - Modu: pointer.String("LORA"), - Ncrc: pointer.Bool(true), - Powe: pointer.Uint(3), - Prea: pointer.Uint(8), - Rfch: pointer.Uint(2), - Rssi: pointer.Int(-27), - Size: pointer.Uint(14), - Stat: pointer.Int(0), - Time: pointer.Time(timeRef), - Tmst: pointer.Uint(uint(timeRef.UnixNano())), - } -} - -// Generate a Physical payload representing an uplink or downlink message -func genPHYPayload(uplink bool) lorawan.PHYPayload { - nwkSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - appSKey := [16]byte{16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - macPayload := lorawan.NewMACPayload(uplink) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: 0, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3, 4}}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(uplink) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} diff --git a/core/check_utilities_test.go b/core/check_utilities_test.go deleted file mode 100644 index e7122a03f..000000000 --- a/core/check_utilities_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "fmt" - "reflect" - "regexp" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// Checks that two packets match -func checkPackets(t *testing.T, want Packet, got Packet) { - if want == nil { - if got == nil { - Ok(t, "Check packets") - return - } - Ko(t, "No packet was expected but got %s", got.String()) - return - } - - if got == nil { - Ko(t, "Was expecting %s but got nothing", want.String()) - return - } - - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") - return - } - - Ko(t, "Converted packet does not match expectations. \nWant: \n%s\nGot: \n%s", want.String(), got.String()) -} - -// Checks that obtained json matches expected one -func checkJSON(t *testing.T, want string, got []byte) { - str := string(got) - if str == want { - Ok(t, "check JSON") - return - } - Ko(t, "Marshaled data does not match expectations.\nWant: %s\nGot: %s", want, str) - return -} - -// Checks that obtained metadata matches expected one -func checkMetadata(t *testing.T, want Metadata, got Metadata) { - if reflect.DeepEqual(want, got) { - Ok(t, "check Metadata") - return - } - Ko(t, "Unmarshaled json does not match expectations. \nWant: %s\nGot: %s", want.String(), got.String()) -} - -// Check that obtained json strings contains the required field -func checkFields(t *testing.T, want []string, got []byte) { - for _, field := range want { - ok, err := regexp.Match(fmt.Sprintf("\"%s\":", field), got) - if !ok || err != nil { - Ko(t, "Expected field %s in %s", field, string(got)) - return - } - } - Ok(t, "Check fields") -} diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 051a0fe8f..4d93cac5d 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" diff --git a/core/components/router/router.go b/core/components/router/router.go index 236f5956e..9c826be1f 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 7d940496d..1384fd9ea 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -10,7 +10,7 @@ import ( "sync" "time" - "github.com/KtorZ/rpc/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" ) diff --git a/core/core.go b/core/core.go index 7e391e837..a4f01a071 100644 --- a/core/core.go +++ b/core/core.go @@ -1,41 +1,77 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. +//go:generate sh -c "find protos -name '*.proto' | xargs protoc --gofast_out=plugins=grpc:. -I=protos" package core -type Handler interface { - Register(reg Registration, an AckNacker, s Subscriber) error - HandleUp(p []byte, an AckNacker, upAdapter Adapter) error - HandleDown(p []byte, an AckNacker, down Adapter) error -} +import ( + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) -type Router interface { - Register(reg Registration, an AckNacker) error - HandleUp(p []byte, an AckNacker, up Adapter) error -} +// ValidateLoRaWANData makes sure that the provided lorawanData reference is valid by checking if +// All required parameters are present and conform. +func ValidateLoRaWANData(data *LoRaWANData) (*LoRaWANMACPayload, *LoRaWANMHDR, *LoRaWANFHDR, *LoRaWANFCtrl, error) { + // Validation::0 -> Data isn't nil + if data == nil { + return nil, nil, nil, nil, errors.New(errors.Structural, "No data") + } + + // Validation::1 -> All required fields are there + if data.MHDR == nil || data.MACPayload == nil || data.MACPayload.FHDR == nil || data.MACPayload.FHDR.FCtrl == nil { + return nil, nil, nil, nil, errors.New(errors.Structural, "Invalid Payload Structure") + } + + // Validation::2 -> The MIC is 4-bytes long + if len(data.MIC) != 4 { + return nil, nil, nil, nil, errors.New(errors.Structural, "Invalid MIC") + } -type Broker interface { - Register(reg Registration, an AckNacker) error - HandleUp(p []byte, an AckNacker, up Adapter) error + // Validation::3 -> Device address is 4-bytes long + if len(data.MACPayload.FHDR.DevAddr) != 4 { + return nil, nil, nil, nil, errors.New(errors.Structural, "Invalid Device Address") + } + + return data.MACPayload, data.MHDR, data.MACPayload.FHDR, data.MACPayload.FHDR.FCtrl, nil } -// AckNacker represents an interface that allow adapters to decouple their protocol from the -// behaviour expected by the caller. -type AckNacker interface { - Ack(p Packet) error - Nack(err error) error +// NewLoRaWANData converts a LoRaWANData to a brocaar/lorawan.PHYPayload +func NewLoRaWANData(reqPayload *LoRaWANData, uplink bool) (lorawan.PHYPayload, error) { + mac, mhdr, fhdr, fctrl, err := ValidateLoRaWANData(reqPayload) + if err != nil { + return lorawan.PHYPayload{}, errors.New(errors.Structural, err) + } + + macpayload := lorawan.NewMACPayload(uplink) + macpayload.FPort = uint8(mac.FPort) + copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) + macpayload.FHDR.FCnt = fhdr.FCnt + for _, data := range fhdr.FOpts { + cmd := new(lorawan.MACCommand) + if err := cmd.UnmarshalBinary(data); err == nil { // We ignore invalid commands + macpayload.FHDR.FOpts = append(macpayload.FHDR.FOpts, *cmd) + } + } + macpayload.FHDR.FCtrl.ADR = fctrl.ADR + macpayload.FHDR.FCtrl.ACK = fctrl.Ack + macpayload.FHDR.FCtrl.ADRACKReq = fctrl.ADRAckReq + macpayload.FHDR.FCtrl.FPending = fctrl.FPending + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: mac.FRMPayload, + }} + payload := lorawan.NewPHYPayload(uplink) + payload.MHDR.MType = lorawan.MType(mhdr.MType) + payload.MHDR.Major = lorawan.Major(mhdr.Major) + copy(payload.MIC[:], reqPayload.MIC) + payload.MACPayload = macpayload + + return payload, nil } -// Adapter handles communications between components. They implement a specific communication -// protocol which is completely hidden from the outside. -type Adapter interface { - Send(p Packet, r ...Recipient) ([]byte, error) - //Join(r JoinRequest, r ...Recipient) (JoinResponse, error) - GetRecipient(raw []byte) (Recipient, error) - Next() ([]byte, AckNacker, error) - NextRegistration() (Registration, AckNacker, error) +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (m StatsMetadata) MarshalBinary() ([]byte, error) { + return m.Marshal() } -type Subscriber interface { - Subscribe(reg Registration) error +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (m *StatsMetadata) UnmarshalBinary(data []byte) error { + return m.Unmarshal(data) } diff --git a/core/core.pb.go b/core/core.pb.go new file mode 100644 index 000000000..e52c336f3 --- /dev/null +++ b/core/core.pb.go @@ -0,0 +1,772 @@ +// Code generated by protoc-gen-gogo. +// source: core.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Metadata struct { + DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` + DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` + Frequency float32 `protobuf:"fixed32,3,opt,name=Frequency,json=frequency,proto3" json:"Frequency,omitempty"` + DataRate string `protobuf:"bytes,4,opt,name=DataRate,json=dataRate,proto3" json:"DataRate,omitempty"` + CodingRate string `protobuf:"bytes,5,opt,name=CodingRate,json=codingRate,proto3" json:"CodingRate,omitempty"` + Timestamp uint32 `protobuf:"varint,6,opt,name=Timestamp,json=timestamp,proto3" json:"Timestamp,omitempty"` + Rssi int32 `protobuf:"varint,7,opt,name=Rssi,json=rssi,proto3" json:"Rssi,omitempty"` + Lsnr float32 `protobuf:"fixed32,8,opt,name=Lsnr,json=lsnr,proto3" json:"Lsnr,omitempty"` + PayloadSize uint32 `protobuf:"varint,9,opt,name=PayloadSize,json=payloadSize,proto3" json:"PayloadSize,omitempty"` + Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` + Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` + Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorCore, []int{0} } + +type StatsMetadata struct { + Altitude int32 `protobuf:"varint,1,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` + Longitude float32 `protobuf:"fixed32,2,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` + Latitude float32 `protobuf:"fixed32,3,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` +} + +func (m *StatsMetadata) Reset() { *m = StatsMetadata{} } +func (m *StatsMetadata) String() string { return proto.CompactTextString(m) } +func (*StatsMetadata) ProtoMessage() {} +func (*StatsMetadata) Descriptor() ([]byte, []int) { return fileDescriptorCore, []int{1} } + +func init() { + proto.RegisterType((*Metadata)(nil), "core.Metadata") + proto.RegisterType((*StatsMetadata)(nil), "core.StatsMetadata") +} +func (m *Metadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Metadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DutyRX1 != 0 { + data[i] = 0x8 + i++ + i = encodeVarintCore(data, i, uint64(m.DutyRX1)) + } + if m.DutyRX2 != 0 { + data[i] = 0x10 + i++ + i = encodeVarintCore(data, i, uint64(m.DutyRX2)) + } + if m.Frequency != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Frequency)))) + } + if len(m.DataRate) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintCore(data, i, uint64(len(m.DataRate))) + i += copy(data[i:], m.DataRate) + } + if len(m.CodingRate) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintCore(data, i, uint64(len(m.CodingRate))) + i += copy(data[i:], m.CodingRate) + } + if m.Timestamp != 0 { + data[i] = 0x30 + i++ + i = encodeVarintCore(data, i, uint64(m.Timestamp)) + } + if m.Rssi != 0 { + data[i] = 0x38 + i++ + i = encodeVarintCore(data, i, uint64(m.Rssi)) + } + if m.Lsnr != 0 { + data[i] = 0x45 + i++ + i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Lsnr)))) + } + if m.PayloadSize != 0 { + data[i] = 0x48 + i++ + i = encodeVarintCore(data, i, uint64(m.PayloadSize)) + } + if m.Altitude != 0 { + data[i] = 0xa8 + i++ + data[i] = 0x1 + i++ + i = encodeVarintCore(data, i, uint64(m.Altitude)) + } + if m.Longitude != 0 { + data[i] = 0xb5 + i++ + data[i] = 0x1 + i++ + i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Longitude)))) + } + if m.Latitude != 0 { + data[i] = 0xbd + i++ + data[i] = 0x1 + i++ + i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Latitude)))) + } + return i, nil +} + +func (m *StatsMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatsMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Altitude != 0 { + data[i] = 0x8 + i++ + i = encodeVarintCore(data, i, uint64(m.Altitude)) + } + if m.Longitude != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Longitude)))) + } + if m.Latitude != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Latitude)))) + } + return i, nil +} + +func encodeFixed64Core(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Core(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintCore(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *Metadata) Size() (n int) { + var l int + _ = l + if m.DutyRX1 != 0 { + n += 1 + sovCore(uint64(m.DutyRX1)) + } + if m.DutyRX2 != 0 { + n += 1 + sovCore(uint64(m.DutyRX2)) + } + if m.Frequency != 0 { + n += 5 + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovCore(uint64(l)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovCore(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovCore(uint64(m.Timestamp)) + } + if m.Rssi != 0 { + n += 1 + sovCore(uint64(m.Rssi)) + } + if m.Lsnr != 0 { + n += 5 + } + if m.PayloadSize != 0 { + n += 1 + sovCore(uint64(m.PayloadSize)) + } + if m.Altitude != 0 { + n += 2 + sovCore(uint64(m.Altitude)) + } + if m.Longitude != 0 { + n += 6 + } + if m.Latitude != 0 { + n += 6 + } + return n +} + +func (m *StatsMetadata) Size() (n int) { + var l int + _ = l + if m.Altitude != 0 { + n += 1 + sovCore(uint64(m.Altitude)) + } + if m.Longitude != 0 { + n += 5 + } + if m.Latitude != 0 { + n += 5 + } + return n +} + +func sovCore(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozCore(x uint64) (n int) { + return sovCore(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Metadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DutyRX1", wireType) + } + m.DutyRX1 = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.DutyRX1 |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DutyRX2", wireType) + } + m.DutyRX2 = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.DutyRX2 |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Frequency = float32(math.Float32frombits(v)) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCore + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCore + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rssi", wireType) + } + m.Rssi = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Rssi |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Lsnr", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Lsnr = float32(math.Float32frombits(v)) + case 9: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PayloadSize", wireType) + } + m.PayloadSize = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.PayloadSize |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) + } + m.Altitude = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Altitude |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Longitude = float32(math.Float32frombits(v)) + case 23: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Latitude = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipCore(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCore + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatsMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatsMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatsMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) + } + m.Altitude = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Altitude |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Longitude = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Latitude = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipCore(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCore + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCore(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCore + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCore + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCore + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthCore + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCore + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipCore(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthCore = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCore = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorCore = []byte{ + // 304 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x91, 0xb1, 0x4e, 0xf3, 0x30, + 0x14, 0x85, 0xff, 0xa4, 0x69, 0xe3, 0xdc, 0xaa, 0xd2, 0x2f, 0x4b, 0x80, 0x85, 0x50, 0x54, 0x75, + 0x62, 0x42, 0xa2, 0x3c, 0x01, 0x50, 0x31, 0x05, 0x09, 0xb9, 0x0c, 0xac, 0xa6, 0x36, 0x55, 0xa4, + 0x34, 0x0e, 0xc9, 0xcd, 0x10, 0x9e, 0x84, 0x47, 0x62, 0x64, 0x62, 0x46, 0xf0, 0x22, 0xd8, 0x4e, + 0x42, 0xcb, 0xd2, 0xc1, 0xd2, 0xbd, 0xe7, 0x93, 0xcf, 0x39, 0x89, 0x01, 0x56, 0xba, 0x54, 0x67, + 0x45, 0xa9, 0x51, 0xd3, 0xc0, 0xce, 0xb3, 0x0f, 0x1f, 0xc8, 0xad, 0x42, 0x21, 0x05, 0x0a, 0xca, + 0x20, 0x5c, 0xd4, 0xd8, 0xf0, 0x87, 0x73, 0xe6, 0x4d, 0xbd, 0xd3, 0x09, 0x0f, 0x65, 0xbb, 0x6e, + 0xc9, 0x9c, 0xf9, 0xbb, 0x64, 0x4e, 0x4f, 0x20, 0xba, 0x29, 0xd5, 0x73, 0xad, 0xf2, 0x55, 0xc3, + 0x06, 0x86, 0xf9, 0x3c, 0x7a, 0xea, 0x05, 0x7a, 0x0c, 0x64, 0x61, 0x9c, 0xb9, 0x40, 0xc5, 0x02, + 0x03, 0x23, 0x4e, 0x64, 0xb7, 0xd3, 0x18, 0xe0, 0x5a, 0xcb, 0x34, 0x5f, 0x3b, 0x3a, 0x74, 0xd4, + 0x14, 0xec, 0x15, 0xeb, 0x7c, 0x9f, 0x6e, 0x54, 0x85, 0x62, 0x53, 0xb0, 0x91, 0x4b, 0x8d, 0xb0, + 0x17, 0x28, 0x85, 0x80, 0x57, 0x55, 0xca, 0x42, 0x03, 0x86, 0x3c, 0x28, 0xcd, 0x6c, 0xb5, 0xa4, + 0xca, 0x4b, 0x46, 0x5c, 0x8d, 0x20, 0x33, 0x33, 0x9d, 0xc2, 0xf8, 0x4e, 0x34, 0x99, 0x16, 0x72, + 0x99, 0xbe, 0x28, 0x16, 0x39, 0x9f, 0x71, 0xb1, 0x95, 0x6c, 0xc7, 0xcb, 0x0c, 0x53, 0xac, 0xa5, + 0x62, 0x07, 0xce, 0x8d, 0x88, 0x6e, 0xb7, 0x1d, 0x12, 0x9d, 0xaf, 0x5b, 0x78, 0xd8, 0x7e, 0x5d, + 0xd6, 0x0b, 0xf6, 0x66, 0x22, 0xba, 0x9b, 0x47, 0x0e, 0x92, 0xac, 0xdb, 0x67, 0x0a, 0x26, 0x4b, + 0x14, 0x58, 0xfd, 0xfe, 0xdc, 0xdd, 0x18, 0x6f, 0x5f, 0x8c, 0xbf, 0x2f, 0x66, 0xf0, 0x37, 0xe6, + 0xea, 0xff, 0xdb, 0x57, 0xec, 0xbd, 0x9b, 0xf3, 0x69, 0xce, 0xeb, 0x77, 0xfc, 0xef, 0x71, 0xe4, + 0x9e, 0xf7, 0xe2, 0x27, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x28, 0x9c, 0x1e, 0xec, 0x01, 0x00, 0x00, +} diff --git a/core/handler.pb.go b/core/handler.pb.go new file mode 100644 index 000000000..109c45fcc --- /dev/null +++ b/core/handler.pb.go @@ -0,0 +1,776 @@ +// Code generated by protoc-gen-gogo. +// source: handler.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type DataHandlerReq struct { + Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + AppEUI []byte `protobuf:"bytes,3,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,4,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + FCnt uint32 `protobuf:"varint,5,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` + MType uint32 `protobuf:"varint,6,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` +} + +func (m *DataHandlerReq) Reset() { *m = DataHandlerReq{} } +func (m *DataHandlerReq) String() string { return proto.CompactTextString(m) } +func (*DataHandlerReq) ProtoMessage() {} +func (*DataHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } + +func (m *DataHandlerReq) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type DataHandlerRes struct { + Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *DataHandlerRes) Reset() { *m = DataHandlerRes{} } +func (m *DataHandlerRes) String() string { return proto.CompactTextString(m) } +func (*DataHandlerRes) ProtoMessage() {} +func (*DataHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } + +func (m *DataHandlerRes) GetPayload() *LoRaWANData { + if m != nil { + return m.Payload + } + return nil +} + +func (m *DataHandlerRes) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +func init() { + proto.RegisterType((*DataHandlerReq)(nil), "core.DataHandlerReq") + proto.RegisterType((*DataHandlerRes)(nil), "core.DataHandlerRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Handler service + +type HandlerClient interface { + HandleData(ctx context.Context, in *DataHandlerReq, opts ...grpc.CallOption) (*DataHandlerRes, error) +} + +type handlerClient struct { + cc *grpc.ClientConn +} + +func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { + return &handlerClient{cc} +} + +func (c *handlerClient) HandleData(ctx context.Context, in *DataHandlerReq, opts ...grpc.CallOption) (*DataHandlerRes, error) { + out := new(DataHandlerRes) + err := grpc.Invoke(ctx, "/core.Handler/HandleData", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Handler service + +type HandlerServer interface { + HandleData(context.Context, *DataHandlerReq) (*DataHandlerRes, error) +} + +func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { + s.RegisterService(&_Handler_serviceDesc, srv) +} + +func _Handler_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DataHandlerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerServer).HandleData(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Handler_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.Handler", + HandlerType: (*HandlerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "HandleData", + Handler: _Handler_HandleData_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DataHandlerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataHandlerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) + n1, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.FCnt != 0 { + data[i] = 0x28 + i++ + i = encodeVarintHandler(data, i, uint64(m.FCnt)) + } + if m.MType != 0 { + data[i] = 0x30 + i++ + i = encodeVarintHandler(data, i, uint64(m.MType)) + } + return i, nil +} + +func (m *DataHandlerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataHandlerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(m.Payload.Size())) + n2, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) + n3, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func encodeFixed64Handler(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Handler(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintHandler(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DataHandlerReq) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.FCnt != 0 { + n += 1 + sovHandler(uint64(m.FCnt)) + } + if m.MType != 0 { + n += 1 + sovHandler(uint64(m.MType)) + } + return n +} + +func (m *DataHandlerRes) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func sovHandler(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozHandler(x uint64) (n int) { + return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DataHandlerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataHandlerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) + } + m.MType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.MType |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DataHandlerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataHandlerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANData{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipHandler(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthHandler + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipHandler(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthHandler = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHandler = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorHandler = []byte{ + // 263 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xcd, 0x48, 0xcc, 0x4b, + 0xc9, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, + 0xe2, 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, + 0xa5, 0x0d, 0x8c, 0x5c, 0x7c, 0x2e, 0x89, 0x25, 0x89, 0x1e, 0x10, 0x6d, 0x41, 0xa9, 0x85, 0x42, + 0x12, 0x5c, 0xec, 0x01, 0x89, 0x95, 0x39, 0xf9, 0x89, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, + 0x41, 0xec, 0x05, 0x10, 0xae, 0x90, 0x16, 0x17, 0x87, 0x6f, 0x6a, 0x49, 0x62, 0x0a, 0x50, 0xbd, + 0x04, 0x13, 0x50, 0x8a, 0xdb, 0x88, 0x4f, 0x0f, 0x6c, 0x16, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, + 0x12, 0x12, 0xe3, 0x62, 0x73, 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x06, 0x1b, 0xc2, 0x96, + 0x08, 0xe6, 0x81, 0xc4, 0x5d, 0x52, 0xcb, 0x40, 0xe2, 0x2c, 0x10, 0xf1, 0x14, 0x30, 0x4f, 0x48, + 0x88, 0x8b, 0xc5, 0xcd, 0x39, 0xaf, 0x44, 0x82, 0x15, 0x28, 0xca, 0x1b, 0xc4, 0x92, 0x06, 0x64, + 0x0b, 0x89, 0x70, 0xb1, 0xfa, 0x86, 0x54, 0x16, 0xa4, 0x4a, 0xb0, 0x81, 0x05, 0x59, 0x73, 0x41, + 0x1c, 0xa5, 0x4c, 0x34, 0x17, 0x17, 0x0b, 0x69, 0xa3, 0xba, 0x98, 0xdb, 0x48, 0x10, 0xe2, 0x2c, + 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0x6a, 0xb2, 0x3c, 0x61, 0xe4, 0xcc, 0xc5, 0x0e, + 0xb5, 0x46, 0xc8, 0x82, 0x8b, 0x0b, 0xc2, 0x04, 0x99, 0x26, 0x24, 0x02, 0xd1, 0x82, 0x1a, 0x72, + 0x52, 0xd8, 0x44, 0x8b, 0x9d, 0x04, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0x00, 0xc4, 0x0f, 0x80, 0x78, + 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0x70, 0xd8, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe9, + 0x40, 0x0c, 0x44, 0xad, 0x01, 0x00, 0x00, +} diff --git a/core/lorawan.pb.go b/core/lorawan.pb.go new file mode 100644 index 000000000..1177178c5 --- /dev/null +++ b/core/lorawan.pb.go @@ -0,0 +1,1976 @@ +// Code generated by protoc-gen-gogo. +// source: lorawan.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type LoRaWANData struct { + MHDR *LoRaWANMHDR `protobuf:"bytes,1,opt,name=MHDR,json=mHDR" json:"MHDR,omitempty"` + MACPayload *LoRaWANMACPayload `protobuf:"bytes,2,opt,name=MACPayload,json=mACPayload" json:"MACPayload,omitempty"` + MIC []byte `protobuf:"bytes,3,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` +} + +func (m *LoRaWANData) Reset() { *m = LoRaWANData{} } +func (m *LoRaWANData) String() string { return proto.CompactTextString(m) } +func (*LoRaWANData) ProtoMessage() {} +func (*LoRaWANData) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } + +func (m *LoRaWANData) GetMHDR() *LoRaWANMHDR { + if m != nil { + return m.MHDR + } + return nil +} + +func (m *LoRaWANData) GetMACPayload() *LoRaWANMACPayload { + if m != nil { + return m.MACPayload + } + return nil +} + +type LoRaWANMHDR struct { + MType uint32 `protobuf:"varint,1,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` + Major uint32 `protobuf:"varint,2,opt,name=Major,json=major,proto3" json:"Major,omitempty"` +} + +func (m *LoRaWANMHDR) Reset() { *m = LoRaWANMHDR{} } +func (m *LoRaWANMHDR) String() string { return proto.CompactTextString(m) } +func (*LoRaWANMHDR) ProtoMessage() {} +func (*LoRaWANMHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } + +type LoRaWANMACPayload struct { + FHDR *LoRaWANFHDR `protobuf:"bytes,1,opt,name=FHDR,json=fHDR" json:"FHDR,omitempty"` + FPort uint32 `protobuf:"varint,2,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` + FRMPayload []byte `protobuf:"bytes,3,opt,name=FRMPayload,json=fRMPayload,proto3" json:"FRMPayload,omitempty"` +} + +func (m *LoRaWANMACPayload) Reset() { *m = LoRaWANMACPayload{} } +func (m *LoRaWANMACPayload) String() string { return proto.CompactTextString(m) } +func (*LoRaWANMACPayload) ProtoMessage() {} +func (*LoRaWANMACPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } + +func (m *LoRaWANMACPayload) GetFHDR() *LoRaWANFHDR { + if m != nil { + return m.FHDR + } + return nil +} + +type LoRaWANFHDR struct { + DevAddr []byte `protobuf:"bytes,1,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + FCtrl *LoRaWANFCtrl `protobuf:"bytes,2,opt,name=FCtrl,json=fCtrl" json:"FCtrl,omitempty"` + FCnt uint32 `protobuf:"varint,3,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` + FOpts [][]byte `protobuf:"bytes,4,rep,name=FOpts,json=fOpts" json:"FOpts,omitempty"` +} + +func (m *LoRaWANFHDR) Reset() { *m = LoRaWANFHDR{} } +func (m *LoRaWANFHDR) String() string { return proto.CompactTextString(m) } +func (*LoRaWANFHDR) ProtoMessage() {} +func (*LoRaWANFHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } + +func (m *LoRaWANFHDR) GetFCtrl() *LoRaWANFCtrl { + if m != nil { + return m.FCtrl + } + return nil +} + +type LoRaWANFCtrl struct { + ADR bool `protobuf:"varint,1,opt,name=ADR,json=aDR,proto3" json:"ADR,omitempty"` + ADRAckReq bool `protobuf:"varint,2,opt,name=ADRAckReq,json=aDRAckReq,proto3" json:"ADRAckReq,omitempty"` + Ack bool `protobuf:"varint,3,opt,name=Ack,json=ack,proto3" json:"Ack,omitempty"` + FPending bool `protobuf:"varint,4,opt,name=FPending,json=fPending,proto3" json:"FPending,omitempty"` + FOptsLen []byte `protobuf:"bytes,5,opt,name=FOptsLen,json=fOptsLen,proto3" json:"FOptsLen,omitempty"` +} + +func (m *LoRaWANFCtrl) Reset() { *m = LoRaWANFCtrl{} } +func (m *LoRaWANFCtrl) String() string { return proto.CompactTextString(m) } +func (*LoRaWANFCtrl) ProtoMessage() {} +func (*LoRaWANFCtrl) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{4} } + +type LoRaWANJoinRequest struct { + DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` +} + +func (m *LoRaWANJoinRequest) Reset() { *m = LoRaWANJoinRequest{} } +func (m *LoRaWANJoinRequest) String() string { return proto.CompactTextString(m) } +func (*LoRaWANJoinRequest) ProtoMessage() {} +func (*LoRaWANJoinRequest) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } + +type LoRaWANJoinAccept struct { + AppNonce []byte `protobuf:"bytes,1,opt,name=AppNonce,json=appNonce,proto3" json:"AppNonce,omitempty"` + NetID []byte `protobuf:"bytes,2,opt,name=NetID,json=netID,proto3" json:"NetID,omitempty"` + DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + DLSettings *LoRaWANDLSettings `protobuf:"bytes,4,opt,name=DLSettings,json=dLSettings" json:"DLSettings,omitempty"` + RXDelay uint32 `protobuf:"varint,5,opt,name=RXDelay,json=rXDelay,proto3" json:"RXDelay,omitempty"` +} + +func (m *LoRaWANJoinAccept) Reset() { *m = LoRaWANJoinAccept{} } +func (m *LoRaWANJoinAccept) String() string { return proto.CompactTextString(m) } +func (*LoRaWANJoinAccept) ProtoMessage() {} +func (*LoRaWANJoinAccept) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } + +func (m *LoRaWANJoinAccept) GetDLSettings() *LoRaWANDLSettings { + if m != nil { + return m.DLSettings + } + return nil +} + +type LoRaWANDLSettings struct { + RX1DRoffset uint32 `protobuf:"varint,1,opt,name=RX1DRoffset,json=rX1DRoffset,proto3" json:"RX1DRoffset,omitempty"` + RX2DataRate uint32 `protobuf:"varint,2,opt,name=RX2DataRate,json=rX2DataRate,proto3" json:"RX2DataRate,omitempty"` +} + +func (m *LoRaWANDLSettings) Reset() { *m = LoRaWANDLSettings{} } +func (m *LoRaWANDLSettings) String() string { return proto.CompactTextString(m) } +func (*LoRaWANDLSettings) ProtoMessage() {} +func (*LoRaWANDLSettings) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{7} } + +func init() { + proto.RegisterType((*LoRaWANData)(nil), "core.LoRaWANData") + proto.RegisterType((*LoRaWANMHDR)(nil), "core.LoRaWANMHDR") + proto.RegisterType((*LoRaWANMACPayload)(nil), "core.LoRaWANMACPayload") + proto.RegisterType((*LoRaWANFHDR)(nil), "core.LoRaWANFHDR") + proto.RegisterType((*LoRaWANFCtrl)(nil), "core.LoRaWANFCtrl") + proto.RegisterType((*LoRaWANJoinRequest)(nil), "core.LoRaWANJoinRequest") + proto.RegisterType((*LoRaWANJoinAccept)(nil), "core.LoRaWANJoinAccept") + proto.RegisterType((*LoRaWANDLSettings)(nil), "core.LoRaWANDLSettings") +} +func (m *LoRaWANData) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANData) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.MHDR != nil { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.MHDR.Size())) + n1, err := m.MHDR.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.MACPayload != nil { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) + n2, err := m.MACPayload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.MIC != nil { + if len(m.MIC) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) + } + } + return i, nil +} + +func (m *LoRaWANMHDR) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANMHDR) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.MType != 0 { + data[i] = 0x8 + i++ + i = encodeVarintLorawan(data, i, uint64(m.MType)) + } + if m.Major != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Major)) + } + return i, nil +} + +func (m *LoRaWANMACPayload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANMACPayload) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.FHDR != nil { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.FHDR.Size())) + n3, err := m.FHDR.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.FPort != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FPort)) + } + if m.FRMPayload != nil { + if len(m.FRMPayload) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FRMPayload))) + i += copy(data[i:], m.FRMPayload) + } + } + return i, nil +} + +func (m *LoRaWANFHDR) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANFHDR) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } + if m.FCtrl != nil { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) + n4, err := m.FCtrl.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.FCnt != 0 { + data[i] = 0x18 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, b := range m.FOpts { + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(len(b))) + i += copy(data[i:], b) + } + } + return i, nil +} + +func (m *LoRaWANFCtrl) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANFCtrl) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.ADR { + data[i] = 0x8 + i++ + if m.ADR { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.ADRAckReq { + data[i] = 0x10 + i++ + if m.ADRAckReq { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.Ack { + data[i] = 0x18 + i++ + if m.Ack { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.FPending { + data[i] = 0x20 + i++ + if m.FPending { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.FOptsLen != nil { + if len(m.FOptsLen) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FOptsLen))) + i += copy(data[i:], m.FOptsLen) + } + } + return i, nil +} + +func (m *LoRaWANJoinRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANJoinRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevNonce != nil { + if len(m.DevNonce) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) + } + } + return i, nil +} + +func (m *LoRaWANJoinAccept) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANJoinAccept) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppNonce != nil { + if len(m.AppNonce) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.AppNonce))) + i += copy(data[i:], m.AppNonce) + } + } + if m.NetID != nil { + if len(m.NetID) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.NetID))) + i += copy(data[i:], m.NetID) + } + } + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } + if m.DLSettings != nil { + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(m.DLSettings.Size())) + n5, err := m.DLSettings.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.RXDelay != 0 { + data[i] = 0x28 + i++ + i = encodeVarintLorawan(data, i, uint64(m.RXDelay)) + } + return i, nil +} + +func (m *LoRaWANDLSettings) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LoRaWANDLSettings) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.RX1DRoffset != 0 { + data[i] = 0x8 + i++ + i = encodeVarintLorawan(data, i, uint64(m.RX1DRoffset)) + } + if m.RX2DataRate != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.RX2DataRate)) + } + return i, nil +} + +func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Lorawan(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintLorawan(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *LoRaWANData) Size() (n int) { + var l int + _ = l + if m.MHDR != nil { + l = m.MHDR.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.MACPayload != nil { + l = m.MACPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.MIC != nil { + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *LoRaWANMHDR) Size() (n int) { + var l int + _ = l + if m.MType != 0 { + n += 1 + sovLorawan(uint64(m.MType)) + } + if m.Major != 0 { + n += 1 + sovLorawan(uint64(m.Major)) + } + return n +} + +func (m *LoRaWANMACPayload) Size() (n int) { + var l int + _ = l + if m.FHDR != nil { + l = m.FHDR.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FPort != 0 { + n += 1 + sovLorawan(uint64(m.FPort)) + } + if m.FRMPayload != nil { + l = len(m.FRMPayload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *LoRaWANFHDR) Size() (n int) { + var l int + _ = l + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + if m.FCtrl != nil { + l = m.FCtrl.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, b := range m.FOpts { + l = len(b) + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *LoRaWANFCtrl) Size() (n int) { + var l int + _ = l + if m.ADR { + n += 2 + } + if m.ADRAckReq { + n += 2 + } + if m.Ack { + n += 2 + } + if m.FPending { + n += 2 + } + if m.FOptsLen != nil { + l = len(m.FOptsLen) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *LoRaWANJoinRequest) Size() (n int) { + var l int + _ = l + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + if m.DevNonce != nil { + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *LoRaWANJoinAccept) Size() (n int) { + var l int + _ = l + if m.AppNonce != nil { + l = len(m.AppNonce) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + if m.NetID != nil { + l = len(m.NetID) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + } + if m.DLSettings != nil { + l = m.DLSettings.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.RXDelay != 0 { + n += 1 + sovLorawan(uint64(m.RXDelay)) + } + return n +} + +func (m *LoRaWANDLSettings) Size() (n int) { + var l int + _ = l + if m.RX1DRoffset != 0 { + n += 1 + sovLorawan(uint64(m.RX1DRoffset)) + } + if m.RX2DataRate != 0 { + n += 1 + sovLorawan(uint64(m.RX2DataRate)) + } + return n +} + +func sovLorawan(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozLorawan(x uint64) (n int) { + return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *LoRaWANData) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MHDR", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MHDR == nil { + m.MHDR = &LoRaWANMHDR{} + } + if err := m.MHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MACPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MACPayload == nil { + m.MACPayload = &LoRaWANMACPayload{} + } + if err := m.MACPayload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) + if m.MIC == nil { + m.MIC = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANMHDR) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANMHDR: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANMHDR: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) + } + m.MType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.MType |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) + } + m.Major = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Major |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANMACPayload) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANMACPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANMACPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FHDR", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FHDR == nil { + m.FHDR = &LoRaWANFHDR{} + } + if err := m.FHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) + } + m.FPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FPort |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FRMPayload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FRMPayload = append(m.FRMPayload[:0], data[iNdEx:postIndex]...) + if m.FRMPayload == nil { + m.FRMPayload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANFHDR) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANFHDR: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANFHDR: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FCtrl", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FCtrl == nil { + m.FCtrl = &LoRaWANFCtrl{} + } + if err := m.FCtrl.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FOpts", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FOpts = append(m.FOpts, make([]byte, postIndex-iNdEx)) + copy(m.FOpts[len(m.FOpts)-1], data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANFCtrl) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANFCtrl: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANFCtrl: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ADR", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.ADR = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ADRAckReq", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.ADRAckReq = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Ack = bool(v != 0) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPending", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FPending = bool(v != 0) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FOptsLen", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FOptsLen = append(m.FOptsLen[:0], data[iNdEx:postIndex]...) + if m.FOptsLen == nil { + m.FOptsLen = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANJoinRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANJoinRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANJoinRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) + if m.DevNonce == nil { + m.DevNonce = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANJoinAccept) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANJoinAccept: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANJoinAccept: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppNonce = append(m.AppNonce[:0], data[iNdEx:postIndex]...) + if m.AppNonce == nil { + m.AppNonce = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetID = append(m.NetID[:0], data[iNdEx:postIndex]...) + if m.NetID == nil { + m.NetID = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DLSettings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DLSettings == nil { + m.DLSettings = &LoRaWANDLSettings{} + } + if err := m.DLSettings.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RXDelay", wireType) + } + m.RXDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RXDelay |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *LoRaWANDLSettings) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LoRaWANDLSettings: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LoRaWANDLSettings: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RX1DRoffset", wireType) + } + m.RX1DRoffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RX1DRoffset |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RX2DataRate", wireType) + } + m.RX2DataRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RX2DataRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLorawan(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthLorawan + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipLorawan(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthLorawan = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLorawan = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorLorawan = []byte{ + // 541 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x53, 0xcf, 0x8e, 0xd3, 0x3e, + 0x10, 0xfe, 0x75, 0xd3, 0x3f, 0xd9, 0x69, 0x2b, 0xed, 0x5a, 0x3f, 0x41, 0x84, 0x50, 0x55, 0x45, + 0x42, 0xda, 0x53, 0x25, 0x96, 0x03, 0xe2, 0x18, 0x12, 0x22, 0x8a, 0x9a, 0x52, 0x19, 0xd0, 0xee, + 0x11, 0x93, 0x38, 0x68, 0x69, 0x1b, 0x87, 0xd4, 0x80, 0x2a, 0x0e, 0x5c, 0x79, 0x04, 0x9e, 0x83, + 0xa7, 0xe0, 0xc8, 0x23, 0x20, 0x78, 0x11, 0x66, 0x1c, 0x37, 0xdd, 0x82, 0x38, 0xa4, 0xf5, 0xf7, + 0xcd, 0x78, 0xbe, 0x6f, 0xc6, 0x36, 0x0c, 0x57, 0xaa, 0x12, 0x1f, 0x44, 0x31, 0x29, 0x2b, 0xa5, + 0x15, 0x6b, 0xa7, 0xaa, 0x92, 0xfe, 0x27, 0xe8, 0xcf, 0x14, 0x17, 0x17, 0xc1, 0x3c, 0x12, 0x5a, + 0xb0, 0x3b, 0xd0, 0x4e, 0x1e, 0x47, 0xdc, 0x6b, 0x8d, 0x5b, 0x67, 0xfd, 0xf3, 0xd3, 0x09, 0xe5, + 0x4c, 0x6c, 0x02, 0x05, 0x78, 0x7b, 0x8d, 0xbf, 0xec, 0x3e, 0x40, 0x12, 0x84, 0x0b, 0xb1, 0x5d, + 0x29, 0x91, 0x79, 0x47, 0x26, 0xf9, 0xe6, 0x61, 0x72, 0x13, 0xe6, 0xb0, 0x6e, 0xd6, 0xec, 0x04, + 0x9c, 0x64, 0x1a, 0x7a, 0x0e, 0xee, 0x18, 0x70, 0x67, 0x3d, 0x0d, 0xfd, 0x07, 0x8d, 0x01, 0xaa, + 0xcf, 0xfe, 0x87, 0x4e, 0xf2, 0x7c, 0x5b, 0x4a, 0xe3, 0x60, 0xc8, 0x3b, 0x6b, 0x02, 0x86, 0x15, + 0x6f, 0x54, 0x65, 0xa4, 0x88, 0x25, 0xe0, 0x97, 0x70, 0xfa, 0x97, 0x1a, 0x75, 0x10, 0xff, 0xab, + 0x83, 0xd8, 0x74, 0x90, 0x5b, 0x9d, 0x78, 0xa1, 0x2a, 0xbd, 0xab, 0x98, 0x13, 0x60, 0x23, 0x80, + 0x98, 0x27, 0xbb, 0xbe, 0x6a, 0x97, 0x90, 0x37, 0x8c, 0xff, 0xb1, 0x31, 0x4b, 0xa5, 0x98, 0x07, + 0xbd, 0x48, 0xbe, 0x0f, 0xb2, 0xac, 0x32, 0x72, 0x03, 0xde, 0xcb, 0x6a, 0xc8, 0xce, 0xb0, 0x7c, + 0xa8, 0xab, 0x95, 0x9d, 0x0d, 0x3b, 0xb4, 0x41, 0x11, 0x94, 0xa4, 0x3f, 0xc6, 0xd0, 0x6f, 0x58, + 0x68, 0x23, 0x36, 0x44, 0x73, 0xb8, 0x36, 0xe6, 0x9e, 0x96, 0x7a, 0xe3, 0xb5, 0xc7, 0x0e, 0x56, + 0xed, 0xe4, 0x04, 0xfc, 0xcf, 0x2d, 0x18, 0x5c, 0xaf, 0x40, 0xc3, 0x0c, 0x6c, 0xa7, 0x2e, 0x77, + 0x04, 0x1a, 0xba, 0x0d, 0xc7, 0xc8, 0x04, 0xe9, 0x92, 0xcb, 0xb7, 0x46, 0xda, 0xe5, 0xc7, 0x62, + 0x47, 0x98, 0xfc, 0x74, 0x69, 0x94, 0x28, 0x3f, 0x5d, 0xb2, 0x5b, 0xe0, 0xc6, 0x0b, 0x59, 0x64, + 0x57, 0xc5, 0x6b, 0xd4, 0x22, 0xda, 0xcd, 0x2d, 0x36, 0x31, 0xd2, 0x9d, 0xc9, 0xc2, 0xeb, 0x98, + 0xee, 0xdc, 0xdc, 0x62, 0xff, 0x25, 0x30, 0xeb, 0xe4, 0x89, 0xba, 0x2a, 0xb0, 0xf6, 0x3b, 0xb9, + 0xd1, 0xec, 0x06, 0x74, 0x71, 0x1c, 0x8f, 0x5e, 0x4c, 0xed, 0x34, 0xba, 0x99, 0x41, 0xc4, 0x07, + 0x65, 0x49, 0xfc, 0x51, 0xcd, 0x0b, 0x83, 0x48, 0x01, 0xf3, 0xe7, 0xaa, 0x48, 0xa5, 0x9d, 0xb5, + 0x9b, 0x59, 0xec, 0x7f, 0x6d, 0x35, 0x87, 0x4b, 0x12, 0x41, 0x9a, 0xca, 0x52, 0xd3, 0x0e, 0xac, + 0x54, 0xef, 0xa8, 0x35, 0x5c, 0x61, 0x31, 0x0d, 0x6d, 0x2e, 0xf5, 0x34, 0xb2, 0x22, 0x9d, 0x82, + 0xc0, 0xf5, 0x23, 0x72, 0x0e, 0x8f, 0x08, 0xef, 0x70, 0x34, 0x7b, 0x26, 0xb5, 0xc6, 0x66, 0x37, + 0xa6, 0xfb, 0x3f, 0xef, 0xf0, 0x3e, 0xcc, 0x21, 0x6b, 0xd6, 0x54, 0x92, 0x5f, 0x46, 0x72, 0x25, + 0xb6, 0x66, 0x2e, 0x43, 0xde, 0xab, 0x6a, 0xe8, 0x5f, 0x34, 0x9e, 0xf7, 0x5b, 0xd9, 0x18, 0xfa, + 0xfc, 0xf2, 0x6e, 0xc4, 0x55, 0x9e, 0x6f, 0xa4, 0xb6, 0xf7, 0xba, 0x5f, 0xed, 0xa9, 0x3a, 0xe3, + 0x9c, 0xde, 0x1f, 0x17, 0x5a, 0xda, 0x1b, 0x89, 0x19, 0x0d, 0xf5, 0xf0, 0xe4, 0xdb, 0xcf, 0x51, + 0xeb, 0x3b, 0x7e, 0x3f, 0xf0, 0xfb, 0xf2, 0x6b, 0xf4, 0xdf, 0xab, 0xae, 0x79, 0xc4, 0xf7, 0x7e, + 0x07, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x16, 0x3f, 0xc3, 0xd5, 0x03, 0x00, 0x00, +} diff --git a/core/metadata.go b/core/metadata.go deleted file mode 100644 index cfab0cb51..000000000 --- a/core/metadata.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding/json" - "time" - - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" -) - -// Metadata are carried by any type of packets. They constitute additional informations on the -// packet itself or, about its context (gateway, duty cycle, etc ...) -type Metadata struct { - Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - DutyRX1 *uint `json:"dut1,omitempty"` // DutyCycle state of the gateway on RX1 (dutyManager.State, see core/dutymanager) - DutyRX2 *uint `json:"dut2,omitempty"` // DutyCycle state of the gateway on RX2 (dutyManager.State, see core/dutyManager) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Gtid *string `json:"gtid,omitempty"` // Id of the gateway from which the packet come - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long,omitempty"` // GPS longitude of the gateway in degree (float, E is +) - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) -} - -// TODO -> Metadata should implements a less byte-consuming MarshalBinary method MarshalJSON() - -// metadata allows us to inherit Metadata in metadataProxy but only by extending the exported -// attributes of Metadata such that, they are still parsed by the json.Marshaller / -// json.Unmarshaller though we do not end up with a recursive hellish error. -type metadata Metadata - -// MarshalJSON implements the json.Marshal interface -func (m Metadata) MarshalJSON() ([]byte, error) { - var d *semtech.Datrparser - var t *semtech.Timeparser - - // Handle datr which can be either an uint or string depending of the modulation - if m.Datr != nil { - d = new(semtech.Datrparser) - if m.Modu != nil && *m.Modu == "FSK" { - *d = semtech.Datrparser{Kind: "uint", Value: *m.Datr} - } else { - *d = semtech.Datrparser{Kind: "string", Value: *m.Datr} - } - } - - // Time :'( ... By default, we mashall them as RFC3339Nano and unmarshall them the best we can. - if m.Time != nil { - t = new(semtech.Timeparser) - *t = semtech.Timeparser{Layout: time.RFC3339Nano, Value: m.Time} - } - - data, err := json.Marshal(metadataProxy{ - metadata: metadata(m), - Datr: d, - Time: t, - }) - - if err != nil { - err = errors.New(errors.Structural, err) - } - - return data, err -} - -// UnmarshalJSON implements the json.Unmarshaler interface -func (m *Metadata) UnmarshalJSON(raw []byte) error { - if m == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil Metadata") - } - - proxy := metadataProxy{} - if err := json.Unmarshal(raw, &proxy); err != nil { - return errors.New(errors.Structural, err) - } - *m = Metadata(proxy.metadata) - if proxy.Time != nil { - m.Time = proxy.Time.Value - } - - if proxy.Datr != nil { - m.Datr = &proxy.Datr.Value - } - - return nil -} - -// MarshalBinary implements the binary.Marshaler interface -func (m Metadata) MarshalBinary() ([]byte, error) { - return m.MarshalJSON() -} - -// UnmarshalBinary implements the binary.Unmarshaler interface -func (m *Metadata) UnmarshalBinary(data []byte) error { - return m.UnmarshalJSON(data) -} - -// String implements the io.Stringer interface -func (m Metadata) String() string { - return pointer.DumpPStruct(m, false) -} - -// type metadataProxy is used to conveniently marshal and unmarshal Metadata structure. -// -// Datr field could be either string or uint depending on the Modu field. -// Time field could be parsed in a lot of different way depending of the time format. -// This proxy make sure that everything is marshaled and unmarshaled to the right thing and allow -// the Metadata struct to be user-friendly. -type metadataProxy struct { - metadata - Datr *semtech.Datrparser `json:"datr,omitempty"` - Time *semtech.Timeparser `json:"time,omitempty"` -} diff --git a/core/metadata_test.go b/core/metadata_test.go deleted file mode 100644 index 381a1f955..000000000 --- a/core/metadata_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding/json" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -var commonTests = []struct { - Metadata Metadata - WantError *string - JSON string -}{ - { // Basic attributes, uint, string and float64 - Metadata{Chan: pointer.Uint(2), Codr: pointer.String("4/6"), Freq: pointer.Float64(864.125)}, - nil, - `{"chan":2,"codr":"4/6","freq":864.125}`, - }, - - { // Basic attributes #2, int and bool - Metadata{Imme: pointer.Bool(true), Rssi: pointer.Int(-54)}, - nil, - `{"imme":true,"rssi":-54}`, - }, - - { // Datr attr, FSK type - Metadata{Datr: pointer.String("50000"), Modu: pointer.String("FSK")}, - nil, - `{"modu":"FSK","datr":50000}`, - }, - - { // Datr attr, lora modulation - Metadata{Datr: pointer.String("SF7BW125"), Modu: pointer.String("LORA")}, - nil, - `{"modu":"LORA","datr":"SF7BW125"}`, - }, - - { // Time attr - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC))}, - nil, - `{"time":"2016-01-06T15:11:12.000000142Z"}`, - }, - - { // Mixed - Metadata{ - Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142, time.UTC)), - Modu: pointer.String("FSK"), - Datr: pointer.String("50000"), - Size: pointer.Uint(14), - Lsnr: pointer.Float64(5.7), - }, - nil, - `{"lsnr":5.7,"modu":"FSK","size":14,"datr":50000,"time":"2016-01-06T15:11:12.000000142Z"}`, - }, -} - -var unmarshalTests = []struct { - Metadata Metadata - WantError *string - JSON string -}{ - { // Local time - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 0, time.UTC))}, - nil, - `{"time":"2016-01-06 15:11:12 GMT"}`, - }, - - { // RFC3339 time - Metadata{Time: pointer.Time(time.Date(2016, 1, 6, 15, 11, 12, 142000000, time.UTC))}, - nil, - `{"time":"2016-01-06T15:11:12.142000Z"}`, - }, -} - -func TestMarshaljson(t *testing.T) { - for _, test := range commonTests { - Desc(t, "Marshal medatadata: %s", test.Metadata.String()) - rawJ, errJ := json.Marshal(test.Metadata) - rawB, errB := test.Metadata.MarshalBinary() - - CheckErrors(t, test.WantError, errJ) - CheckErrors(t, test.WantError, errB) - checkJSON(t, test.JSON, rawJ) - checkJSON(t, test.JSON, rawB) - } -} - -func TestUnmarshalJSON(t *testing.T) { - for _, test := range append(commonTests, unmarshalTests...) { - Desc(t, "Unmarshal json: %s", test.JSON) - metadataJ := Metadata{} - errJ := json.Unmarshal([]byte(test.JSON), &metadataJ) - metadataB := Metadata{} - errB := metadataB.UnmarshalBinary([]byte(test.JSON)) - - CheckErrors(t, test.WantError, errJ) - CheckErrors(t, test.WantError, errB) - checkMetadata(t, test.Metadata, metadataJ) - checkMetadata(t, test.Metadata, metadataB) - } -} diff --git a/core/packets.go b/core/packets.go deleted file mode 100644 index d07aef92e..000000000 --- a/core/packets.go +++ /dev/null @@ -1,766 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding" - "fmt" - "math" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" - "github.com/brocaar/lorawan" -) - -const ( - typeRPacket byte = iota - typeBPacket - typeHPacket - typeAPacket - typeJPacket - typeCPacket - typeSPacket -) - -// --------------------------------- -// -// ----- HELPERS ------------------- -// -// --------------------------------- - -// marshalBases is used to marshal in chain several bases which compose a bigger Packet struct -func marshalBases(t byte, bases ...baseMarshaler) ([]byte, error) { - data := []byte{t} - - for _, base := range bases { - dataBase, err := base.Marshal() - if err != nil { - return nil, err - } - data = append(data, dataBase...) - } - return data, nil -} - -// unmarshalBases do the reverse operation of marshalBases -func unmarshalBases(t byte, data []byte, bases ...baseUnmarshaler) error { - if len(data) < 1 || data[0] != t { - return errors.New(errors.Structural, "Not an expected packet") - } - - var rest []byte - var err error - - rest = data[1:] - for _, base := range bases { - if rest, err = base.Unmarshal(rest); err != nil { - return err - } - } - - return err -} - -// UnmarshalPacket takes raw binary data and try to marshal it into a given packet interface: -// -// - RPacket -// - BPacket -// - HPacket -// - APacket -// - JPacket -// - CPacket -// -// It returns an interface so that its easy and handy to perform a type assertion out of it. -// If data are wrong or, if the packet is not unmarshalable, it returns an error. -func UnmarshalPacket(data []byte) (interface{}, error) { - if len(data) < 1 { - return nil, errors.New(errors.Structural, "Cannot unmarshal, not a packet") - } - - var packet interface { - encoding.BinaryUnmarshaler - } - - switch data[0] { - case typeRPacket: - packet = new(rpacket) - case typeBPacket: - packet = new(bpacket) - case typeHPacket: - packet = new(hpacket) - case typeAPacket: - packet = new(apacket) - case typeJPacket: - packet = new(jpacket) - case typeCPacket: - packet = new(cpacket) - case typeSPacket: - packet = new(spacket) - } - - err := packet.UnmarshalBinary(data) - return packet, err -} - -// --------------------------------- -// -// ----- RPACKET ------------------- -// -// --------------------------------- - -// rpacket implements the core.RPacket interface -type rpacket struct { - baserpacket - basempacket - gatewayID baseapacket -} - -// NewRPacket construct a new router packet given a payload and metadata -func NewRPacket(payload lorawan.PHYPayload, gatewayID []byte, metadata Metadata) (RPacket, error) { - if payload.MACPayload == nil { - return nil, errors.New(errors.Structural, "MACPAyload should not be empty") - } - - _, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") - } - - return &rpacket{ - baserpacket: baserpacket{payload: payload}, - basempacket: basempacket{metadata: metadata}, - gatewayID: baseapacket{payload: gatewayID}, - }, nil -} - -// gatewayID implements the core.RPacket interface -func (p rpacket) GatewayID() []byte { - return p.gatewayID.payload -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p rpacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeRPacket, p.baserpacket, p.basempacket, p.gatewayID) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *rpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeRPacket, data, &p.baserpacket, &p.basempacket, &p.gatewayID) -} - -// String implements the Stringer interface -func (p rpacket) String() string { - str := "RPacket {" - str += fmt.Sprintf("\n\t%s}", p.metadata.String()) - str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) - return str -} - -// --------------------------------- -// -// ----- SPACKET ------------------- -// -// --------------------------------- -type spacket struct { - basempacket - gatewayID baseapacket -} - -// NewSPacket constructs a new stats packet from a gateway id and metadata -func NewSPacket(gatewayID []byte, metadata Metadata) (SPacket, error) { - if len(gatewayID) != 8 { - return nil, errors.New(errors.Structural, "Invalid gatewayId. Should be 8 bytes") - } - return &spacket{ - basempacket: basempacket{metadata: metadata}, - gatewayID: baseapacket{payload: gatewayID}, - }, nil -} - -// GatewayID implements the core.SPacket interface -func (p spacket) GatewayID() []byte { - return p.gatewayID.payload -} - -// DevEUI implements the core.SPacket interface -// TODO THIS IS NOT VIABLE. WE NEED TO PROPERLY DEFINE MESSAGES BETWEEN PROCESSES. -func (p spacket) DevEUI() lorawan.EUI64 { - return lorawan.EUI64{} -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p spacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeSPacket, p.basempacket, p.gatewayID) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *spacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeSPacket, data, &p.basempacket, &p.gatewayID) -} - -// String implements the fmt.Stringer interface -func (p spacket) String() string { - str := "SPacket {" - str += fmt.Sprintf("\n\t%s}", p.metadata.String()) - str += fmt.Sprintf("\n\tGatewayID%+v\n}", p.gatewayID.payload) - return str -} - -// --------------------------------- -// -// ----- BPACKET ------------------- -// -// --------------------------------- - -// bpacket implements the core.BPacket interface -type bpacket struct { - baserpacket - basempacket -} - -// NewBPacket constructs a new broker packets given a payload and metadata -func NewBPacket(payload lorawan.PHYPayload, metadata Metadata) (BPacket, error) { - if payload.MACPayload == nil { - return nil, errors.New(errors.Structural, "MACPAyload should not be empty") - } - - macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") - } - - if len(macPayload.FRMPayload) != 1 { - return nil, errors.New(errors.Structural, "Invalid frame payload. Expected exactly 1") - } - - if _, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload); !ok { - return nil, errors.New(errors.Structural, "Invalid frame payload. Expected only data") - } - - return &bpacket{ - baserpacket: baserpacket{payload: payload}, - basempacket: basempacket{metadata: metadata}, - }, nil -} - -// ValidateMIC implements the core.BPacket interface -func (p *bpacket) ValidateMIC(key lorawan.AES128Key) (bool, error) { - return p.baserpacket.payload.ValidateMIC(key) -} - -// SetMIC implements the core.BPacket interface -func (p *bpacket) SetMIC(key lorawan.AES128Key) error { - return p.payload.SetMIC(key) -} - -// Commands implements the core.BPacket interface -func (p bpacket) Commands() []lorawan.MACCommand { - return p.baserpacket.payload.MACPayload.(*lorawan.MACPayload).FHDR.FOpts -} - -// String implements the fmt.Stringer interface -func (p bpacket) String() string { - str := "BPacket {" - str += fmt.Sprintf("\n\t%s}", p.metadata.String()) - str += fmt.Sprintf("\n\tPayload%+v\n}", p.payload) - return str -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p bpacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeBPacket, p.baserpacket, p.basempacket) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *bpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeBPacket, data, &p.baserpacket, &p.basempacket) -} - -// --------------------------------- -// -// ----- HPACKET ------------------- -// -// --------------------------------- - -// hpacket implements the HPacket interface -type hpacket struct { - basehpacket - payload baserpacket - basempacket -} - -// NewHPacket constructs a new Handler packet -func NewHPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload lorawan.PHYPayload, metadata Metadata) (HPacket, error) { - if payload.MACPayload == nil { - return nil, errors.New(errors.Structural, "MACPAyload should not be empty") - } - - macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, errors.New(errors.Structural, "Packet does not carry a MACPayload") - } - - if len(macPayload.FRMPayload) != 1 { - return nil, errors.New(errors.Structural, "Invalid frame payload. Expected exactly 1") - } - - if _, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload); !ok { - return nil, errors.New(errors.Structural, "Invalid frame payload. Expected only data") - } - - return &hpacket{ - basehpacket: basehpacket{ - appEUI: appEUI, - devEUI: devEUI, - }, - payload: baserpacket{ - payload: payload, - }, - basempacket: basempacket{metadata: metadata}, - }, nil -} - -// Payload implements the core.HPacket interface -func (p hpacket) Payload(key lorawan.AES128Key) ([]byte, error) { - macPayload := p.payload.payload.MACPayload.(*lorawan.MACPayload) - if err := macPayload.DecryptFRMPayload(key); err != nil { - return nil, errors.New(errors.Structural, err) - } - if len(macPayload.FRMPayload) != 1 { - return nil, errors.New(errors.Structural, "Unexpected Frame payload") - } - return macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, nil -} - -// FCnt implements the core.HPacket interface -func (p hpacket) FCnt() uint32 { - return p.payload.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p hpacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeHPacket, p.basehpacket, p.payload, p.basempacket) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *hpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeHPacket, data, &p.basehpacket, &p.payload, &p.basempacket) -} - -// String implements the fmt.Stringer interface -func (p hpacket) String() string { - str := "Packet {" - str += fmt.Sprintf("\n\t%s}", p.metadata.String()) - str += fmt.Sprintf("\n\tAppEUI:%+x\n,", p.appEUI) - str += fmt.Sprintf("\n\tDevEUI:%+x\n,", p.devEUI) - str += fmt.Sprintf("\n\tPayload:%v\n}", p.payload) - return str -} - -// --------------------------------- -// -// ----- APACKET ------------------- -// -// --------------------------------- - -// apacket implements the core.APacket interface -type apacket struct { - baseapacket - basehpacket - basegpacket -} - -// NewAPacket constructs a new application packet -func NewAPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, metadata []Metadata) (APacket, error) { - if len(payload) == 0 { - return nil, errors.New(errors.Structural, "Application packet must hold a payload") - } - - return &apacket{ - basehpacket: basehpacket{ - devEUI: devEUI, - appEUI: appEUI, - }, - baseapacket: baseapacket{payload: payload}, - basegpacket: basegpacket{metadata: metadata}, - }, nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p apacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeAPacket, p.basehpacket, p.baseapacket, p.basegpacket) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *apacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeAPacket, data, &p.basehpacket, &p.baseapacket, &p.basegpacket) -} - -// String implements the fmt.Stringer interface -func (p apacket) String() string { - return fmt.Sprintf( - "APacket{AppEUI:%v,DevEUI:%v,Payload:%v,Metadata:%v", - p.AppEUI(), - p.DevEUI(), - p.Payload(), - p.Metadata(), - ) -} - -// --------------------------------- -// -// ----- JPACKET ------------------- -// -// --------------------------------- - -// joinPacket implements the core.JoinPacket interface -type jpacket struct { - baseapacket baseapacket - basehpacket - basempacket -} - -// NewJPacket constructs a new JoinPacket -func NewJPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, devNonce [2]byte, metadata Metadata) JPacket { - return &jpacket{ - basehpacket: basehpacket{ - appEUI: appEUI, - devEUI: devEUI, - }, - baseapacket: baseapacket{payload: devNonce[:]}, - basempacket: basempacket{metadata: metadata}, - } -} - -// DevNonce implements the core.JoinPacket interface -func (p jpacket) DevNonce() [2]byte { - return [2]byte{p.baseapacket.payload[0], p.baseapacket.payload[1]} -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p jpacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeJPacket, p.basehpacket, p.baseapacket, p.basempacket) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *jpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeJPacket, data, &p.basehpacket, &p.baseapacket, &p.basempacket) -} - -// String implements the fmt.Stringer interface -func (p jpacket) String() string { - return fmt.Sprintf( - "JPacket{AppEUI:%v,DevEUI:%v,DevNonce:%v,Metadata:%v", - p.AppEUI(), - p.DevEUI(), - p.DevNonce(), - p.Metadata(), - ) -} - -// acceptpacket implements the core.AcceptPacket interface -type cpacket struct { - basehpacket - baseapacket - nwkSKey baseapacket -} - -// NewCPacket constructs a new CPacket -func NewCPacket(appEUI lorawan.EUI64, devEUI lorawan.EUI64, payload []byte, nwkSKey lorawan.AES128Key) (CPacket, error) { - if len(payload) == 0 { - return nil, errors.New(errors.Structural, "Payload cannot be empty") - } - - return &cpacket{ - basehpacket: basehpacket{ - appEUI: appEUI, - devEUI: devEUI, - }, - baseapacket: baseapacket{payload: payload}, - nwkSKey: baseapacket{payload: nwkSKey[:]}, - }, nil -} - -// NwkSKey implements the core.AcceptPacket interface -func (p cpacket) NwkSKey() lorawan.AES128Key { - var key lorawan.AES128Key - copy(key[:], p.nwkSKey.payload) - return key -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p cpacket) MarshalBinary() ([]byte, error) { - return marshalBases(typeCPacket, p.basehpacket, p.baseapacket, p.nwkSKey) -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (p *cpacket) UnmarshalBinary(data []byte) error { - return unmarshalBases(typeCPacket, data, &p.basehpacket, &p.baseapacket, &p.nwkSKey) -} - -// String implements the fmt.Stringer interface -func (p cpacket) String() string { - return fmt.Sprintf( - "CPacket{AppEUI:%v,DevEUI:%v,Payload:%v,NwkSKey:%v", - p.AppEUI(), - p.DevEUI(), - p.Payload(), - p.NwkSKey(), - ) -} - -// -------------------------------------- -// -------------------------------------- -// -------------------------------------- -// ----- BASE PACKETS ------------------- -// -// All base packet are small components that are used by packets above to define accessors and -// marshaling / unmarshaling methods on a struct. -// All Unmarshal methods return the remaining unconsumed bytes from the input data such that one -// could actually chain calls for different basexxxpacket -// -// -------------------------------------- -// -------------------------------------- -// -------------------------------------- -// -// basempacket -> metadata Metadata -// baserpacket -> payload lorawan.PHYPayload -// baseapacket -> payload []byte -// basehpacket -> appEUI lorawan.EUI64, devEUI lorawan.EUI64 -// (ALWAYS LAST) basegpacket -> metadata []Metadata - -type baseMarshaler interface { - Marshal() ([]byte, error) -} - -type baseUnmarshaler interface { - Unmarshal(data []byte) ([]byte, error) -} - -// basempacket is used to compose other packets -type basempacket struct { - metadata Metadata -} - -func (p basempacket) Metadata() Metadata { - return p.metadata -} - -func (p basempacket) Marshal() ([]byte, error) { - dataMetadata, err := p.metadata.MarshalJSON() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - rw := readwriter.New(nil) - rw.Write(dataMetadata) - return rw.Bytes() -} - -func (p *basempacket) Unmarshal(data []byte) ([]byte, error) { - rw := readwriter.New(data) - - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - - p.metadata = Metadata{} - if err := p.metadata.UnmarshalJSON(dataMetadata); err != nil { - return nil, errors.New(errors.Structural, err) - } - - return rw.Bytes() -} - -// baserpacket is used to compose other packets -type baserpacket struct { - payload lorawan.PHYPayload -} - -func (p baserpacket) Payload() lorawan.PHYPayload { - return p.payload -} - -// DevEUI implements the core.RPacket interface -func (p baserpacket) DevEUI() lorawan.EUI64 { - var devEUI lorawan.EUI64 - copy(devEUI[4:], p.payload.MACPayload.(*lorawan.MACPayload).FHDR.DevAddr[:]) - return devEUI -} - -// ComputeFCnt implements the core.BPacket interface -func (p *baserpacket) ComputeFCnt(wholeCnt uint32) error { - upperSup := uint32(math.Pow(2, 16)) - fcnt := p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt - diff := fcnt - (wholeCnt % upperSup) - var offset uint32 - if diff >= 0 { - offset = diff - } else { - offset = upperSup + diff - } - if offset > upperSup/4 { - return errors.New(errors.Structural, "Gap too big, counter is errored") - } - - p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt + offset - return nil -} - -// FCnt implements the core.BPacket interface -func (p baserpacket) FCnt() uint32 { - return p.payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt -} - -// Marshal transforms the given basepacket to binaries -func (p baserpacket) Marshal() ([]byte, error) { - var mtype byte - switch p.payload.MHDR.MType { - case lorawan.UnconfirmedDataUp: - fallthrough - case lorawan.ConfirmedDataUp: - mtype = 1 // Up - case lorawan.UnconfirmedDataDown: - fallthrough - case lorawan.ConfirmedDataDown: - mtype = 2 // Down - default: - msg := fmt.Sprintf("Unsupported mtype: %s", p.payload.MHDR.MType.String()) - return nil, errors.New(errors.Implementation, msg) - } - - dataPayload, err := p.payload.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - rw := readwriter.New(nil) - rw.Write(mtype) - rw.Write(dataPayload) - return rw.Bytes() -} - -// Unmarshal hydrates the given basepacket from binaries data. -func (p *baserpacket) Unmarshal(data []byte) ([]byte, error) { - if len(data) < 1 { - return nil, errors.New(errors.Structural, "Not a valid packet") - } - - var isUp bool - rw := readwriter.New(data) - rw.Read(func(data []byte) { - if data[0] == 1 { - isUp = true - } - }) - - var dataPayload []byte - rw.Read(func(data []byte) { dataPayload = data }) - - data, err := rw.Bytes() - if err != nil { - return nil, errors.New(errors.Structural, rw.Err()) - } - - p.payload = lorawan.NewPHYPayload(isUp) - if err := p.payload.UnmarshalBinary(dataPayload); err != nil { - return nil, errors.New(errors.Structural, err) - } - - return data, nil -} - -// basehpacket is used to compose other packets -type basehpacket struct { - appEUI lorawan.EUI64 - devEUI lorawan.EUI64 -} - -func (p basehpacket) AppEUI() lorawan.EUI64 { - return p.appEUI -} - -func (p basehpacket) DevEUI() lorawan.EUI64 { - return p.devEUI -} - -func (p basehpacket) Marshal() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(p.appEUI) - rw.Write(p.devEUI) - return rw.Bytes() -} - -func (p *basehpacket) Unmarshal(data []byte) ([]byte, error) { - rw := readwriter.New(data) - rw.Read(func(data []byte) { copy(p.appEUI[:], data) }) - rw.Read(func(data []byte) { copy(p.devEUI[:], data) }) - return rw.Bytes() -} - -// baseapacket is used to compose other packets -type baseapacket struct { - payload []byte -} - -func (p baseapacket) Payload() []byte { - return p.payload -} - -func (p baseapacket) Marshal() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(p.payload) - return rw.Bytes() -} - -func (p *baseapacket) Unmarshal(data []byte) ([]byte, error) { - rw := readwriter.New(data) - rw.Read(func(data []byte) { p.payload = data }) - return rw.Bytes() -} - -// basegpacket is used to compose other packets -type basegpacket struct { - metadata []Metadata -} - -func (p basegpacket) Metadata() []Metadata { - return p.metadata -} - -func (p basegpacket) Marshal() ([]byte, error) { - rw := readwriter.New(nil) - for _, m := range p.metadata { - data, err := m.MarshalJSON() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - rw.Write(data) - } - return rw.Bytes() -} - -func (p *basegpacket) Unmarshal(data []byte) ([]byte, error) { - p.metadata = make([]Metadata, 0) - rw := readwriter.New(data) - - for { - var dataMetadata []byte - rw.Read(func(data []byte) { dataMetadata = data }) - if rw.Err() != nil { - err, ok := rw.Err().(errors.Failure) - if ok && err.Nature == errors.Behavioural { - break - } - return nil, errors.New(errors.Structural, rw.Err()) - } - metadata := new(Metadata) - if err := metadata.UnmarshalJSON(dataMetadata); err != nil { - return nil, errors.New(errors.Structural, err) - } - - p.metadata = append(p.metadata, *metadata) - } - - return nil, nil -} diff --git a/core/packets_interfaces.go b/core/packets_interfaces.go deleted file mode 100644 index 2de0cc816..000000000 --- a/core/packets_interfaces.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding" - "fmt" - - "github.com/brocaar/lorawan" -) - -// Packet represents the core base of a Packet -type Packet interface { - DevEUI() lorawan.EUI64 - encoding.BinaryMarshaler - fmt.Stringer -} - -// RPacket represents packets manipulated by the router that hold Data -type RPacket interface { - Packet - GatewayID() []byte - Metadata() Metadata - Payload() lorawan.PHYPayload -} - -// SPacket represents packets manipulated by the router that hold Stats -type SPacket interface { - Packet - GatewayID() []byte - Metadata() Metadata -} - -// BPacket represents packets manipulated by the broker that hold Data -type BPacket interface { - Packet - Commands() []lorawan.MACCommand - ComputeFCnt(wholeCnt uint32) error - FCnt() uint32 - Metadata() Metadata - Payload() lorawan.PHYPayload - SetMIC(key lorawan.AES128Key) error - ValidateMIC(key lorawan.AES128Key) (bool, error) -} - -// HPacket represents packets manipulated by the handler that hold Data -type HPacket interface { - Packet - AppEUI() lorawan.EUI64 - FCnt() uint32 - Payload(appSKey lorawan.AES128Key) ([]byte, error) // Unencrypted FRMPayload - Metadata() Metadata // TTL on down, DutyCycle + Rssi on Up -} - -// APacket represents packets sent towards an application -type APacket interface { - Packet - AppEUI() lorawan.EUI64 - Payload() []byte - Metadata() []Metadata -} - -// JPacket represents join request packets -type JPacket interface { - Packet - AppEUI() lorawan.EUI64 - DevNonce() [2]byte - Metadata() Metadata // Rssi + DutyCycle -} - -// CPacket represents join accept (confirmation) packets -type CPacket interface { - Packet - AppEUI() lorawan.EUI64 - Payload() []byte - NwkSKey() lorawan.AES128Key -} diff --git a/core/packets_test.go b/core/packets_test.go deleted file mode 100644 index 0636c98e2..000000000 --- a/core/packets_test.go +++ /dev/null @@ -1,556 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "math/rand" - "testing" - - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/brocaar/lorawan" - . "github.com/smartystreets/assertions" -) - -func randBytes(n int) []byte { - bytes := make([]byte, n) - for i := range bytes { - bytes[i] = byte(rand.Intn(255)) - } - return bytes -} - -func newEUI() lorawan.EUI64 { - devEUI := [8]byte{} - copy(devEUI[:], randBytes(8)) - return devEUI -} - -func simplePayload(fcnt uint32, uplink bool) (payload lorawan.PHYPayload, devAddr lorawan.DevAddr, key lorawan.AES128Key) { - copy(devAddr[:], randBytes(4)) - copy(key[:], randBytes(16)) - - payload = newPayload(devAddr, []byte("PLD123"), key, key, fcnt, uplink) - return -} - -func newPayload(devAddr lorawan.DevAddr, data []byte, appSKey lorawan.AES128Key, nwkSKey lorawan.AES128Key, fcnt uint32, uplink bool) lorawan.PHYPayload { - macPayload := lorawan.NewMACPayload(uplink) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: devAddr, - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: fcnt, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: data}} - - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(uplink) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(nwkSKey); err != nil { - panic(err) - } - - return payload -} - -func marshalUnmarshal(t *testing.T, input Packet) interface{} { - a := New(t) - - binary, err := input.MarshalBinary() - a.So(err, ShouldBeNil) - - gOutput, err := UnmarshalPacket(binary) - a.So(err, ShouldBeNil) - - a.So(gOutput, ShouldHaveSameTypeAs, input) - - return gOutput -} - -func TestInvalidMarshalBases(t *testing.T) { - // Only err when Metadata Marshal returns err -} - -func TestBaseMarshalUnmarshal(t *testing.T) { - a := New(t) - - s := uint(123) - mpkt := basempacket{metadata: Metadata{Size: &s}} - - payload, _, _ := simplePayload(1, true) - rpkt := baserpacket{payload: payload} - hpkt := basehpacket{ - appEUI: newEUI(), - devEUI: newEUI(), - } - apkt := baseapacket{ - payload: []byte{0x01, 0x02, 0x03}, - } - gpkt := basegpacket{metadata: []Metadata{ - Metadata{Size: &s}, - }} - - binmpkt, err1 := mpkt.Marshal() - a.So(err1, ShouldBeNil) - binrpkt, err2 := rpkt.Marshal() - a.So(err2, ShouldBeNil) - binhpkt, err3 := hpkt.Marshal() - a.So(err3, ShouldBeNil) - binapkt, err4 := apkt.Marshal() - a.So(err4, ShouldBeNil) - bingpkt, err5 := gpkt.Marshal() - a.So(err5, ShouldBeNil) - - newmpkt := basempacket{} - newrpkt := baserpacket{} - newhpkt := basehpacket{} - newapkt := baseapacket{} - newgpkt := basegpacket{} - - _, err6 := newmpkt.Unmarshal(binmpkt) - a.So(err6, ShouldBeNil) - a.So(*newmpkt.Metadata().Size, ShouldEqual, s) - - _, err7 := newrpkt.Unmarshal(binrpkt) - a.So(err7, ShouldBeNil) - // a.So() - - _, err8 := newhpkt.Unmarshal(binhpkt) - a.So(err8, ShouldBeNil) - - _, err9 := newapkt.Unmarshal(binapkt) - a.So(err9, ShouldBeNil) - - _, erra := newgpkt.Unmarshal(bingpkt) - a.So(erra, ShouldBeNil) - a.So(*newgpkt.Metadata()[0].Size, ShouldEqual, s) - -} - -func TestInvalidUnmarshalBases(t *testing.T) { - a := New(t) - - p := basempacket{} - - err1 := unmarshalBases(0x01, []byte{}, &p) - a.So(err1, ShouldNotBeNil) - - err2 := unmarshalBases(0x01, []byte{0x02}, &p) - a.So(err2, ShouldNotBeNil) - - err3 := unmarshalBases(0x01, []byte{0x01}, &p) - a.So(err3, ShouldNotBeNil) -} - -func TestInvalidUnmarshalPacket(t *testing.T) { - a := New(t) - _, err := UnmarshalPacket([]byte{}) - a.So(err, ShouldNotBeNil) -} - -func TestPacket(t *testing.T) { - a := New(t) - input := basempacket{ - metadata: Metadata{ - Codr: pointer.String("4/6"), - }, - } - - binary, _ := input.Marshal() - - output := basempacket{ - metadata: Metadata{}, - } - - _, err := output.Unmarshal(binary) - a.So(err, ShouldBeNil) -} - -func TestInvalidRPacket(t *testing.T) { - a := New(t) - - // No MACPayload - _, err1 := NewRPacket(lorawan.PHYPayload{}, []byte{}, Metadata{}) - a.So(err1, ShouldNotBeNil) - - // Not a MACPayload - _, err2 := NewRPacket(lorawan.PHYPayload{ - MACPayload: &lorawan.JoinRequestPayload{}, - }, []byte{}, Metadata{}) - a.So(err2, ShouldNotBeNil) -} - -func checkRPacket(t *testing.T, output RPacket, p lorawan.PHYPayload, gid []byte, m Metadata, d lorawan.DevAddr) { - a := New(t) - - a.So(output.Payload(), ShouldResemble, p) - a.So(output.GatewayID(), ShouldResemble, gid) - a.So(output.Metadata(), ShouldResemble, m) - outputDevEUI := output.DevEUI() - a.So(outputDevEUI[4:], ShouldResemble, d[:]) -} - -func TestRPacket(t *testing.T) { - { // UnconfirmedDataUp - payload, devAddr, _ := simplePayload(1, true) - payload.MHDR.MType = lorawan.UnconfirmedDataUp - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - gOutput := marshalUnmarshal(t, input) - - checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) - } - - { // ConfirmedDataUp - payload, devAddr, _ := simplePayload(1, true) - payload.MHDR.MType = lorawan.ConfirmedDataUp - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - gOutput := marshalUnmarshal(t, input) - - checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) - } - - { // UnconfirmedDataDown - payload, devAddr, _ := simplePayload(1, false) - payload.MHDR.MType = lorawan.UnconfirmedDataDown - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - gOutput := marshalUnmarshal(t, input) - - checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) - } - - { // ConfirmedDataDown - payload, devAddr, _ := simplePayload(1, false) - payload.MHDR.MType = lorawan.ConfirmedDataDown - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - gOutput := marshalUnmarshal(t, input) - - checkRPacket(t, gOutput.(RPacket), payload, gwEUI, Metadata{}, devAddr) - } - - { // JoinRequest - payload, _, _ := simplePayload(1, true) - payload.MHDR.MType = lorawan.Proprietary - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - - _, err := input.MarshalBinary() - New(t).So(err, ShouldNotBeNil) - } - - { // JoinAccept - payload, _, _ := simplePayload(1, false) - payload.MHDR.MType = lorawan.Proprietary - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - - _, err := input.MarshalBinary() - New(t).So(err, ShouldNotBeNil) - } - - { // Proprietary - payload, _, _ := simplePayload(1, false) - payload.MHDR.MType = lorawan.Proprietary - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - - _, err := input.MarshalBinary() - New(t).So(err, ShouldNotBeNil) - } -} - -func TestSPacket(t *testing.T) { - // Nope -} - -func TestInvalidBPacket(t *testing.T) { - a := New(t) - - // No MACPayload - _, err1 := NewBPacket(lorawan.PHYPayload{}, Metadata{}) - a.So(err1, ShouldNotBeNil) - - // Not a MACPayload - _, err2 := NewBPacket(lorawan.PHYPayload{ - MACPayload: &lorawan.JoinRequestPayload{}, - }, Metadata{}) - a.So(err2, ShouldNotBeNil) - - // Not enough FRMPayloads - _, err3 := NewBPacket(lorawan.PHYPayload{ - MACPayload: &lorawan.MACPayload{}, - }, Metadata{}) - a.So(err3, ShouldNotBeNil) - - // FRMPayload is not DataPayload - _, err4 := NewBPacket(lorawan.PHYPayload{ - MACPayload: &lorawan.MACPayload{ - FRMPayload: []lorawan.Payload{ - &lorawan.JoinRequestPayload{}, - }, - }, - }, Metadata{}) - a.So(err4, ShouldNotBeNil) - - // FCnt out of bound - var wholeCnt uint32 = 78765436 - payload, _, _ := simplePayload(1, true) - payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65536 + 65536/2 - input, _ := NewBPacket(payload, Metadata{}) - err := input.ComputeFCnt(wholeCnt) - a.So(err, ShouldNotBeNil) -} - -func TestBPacket(t *testing.T) { - a := New(t) - - var wholeCnt uint32 = 78765436 - payload, _, key := simplePayload(wholeCnt+1, true) - payload.MACPayload.(*lorawan.MACPayload).FHDR.FCnt = wholeCnt%65536 + 1 - input, _ := NewBPacket(payload, Metadata{}) - - gOutput := marshalUnmarshal(t, input) - output := gOutput.(BPacket) - - a.So(output.Payload(), ShouldResemble, payload) - a.So(output.Metadata(), ShouldResemble, Metadata{}) - err := output.ComputeFCnt(wholeCnt) - a.So(err, ShouldBeNil) - a.So(output.FCnt(), ShouldEqual, wholeCnt+1) - outputValidateMIC, _ := output.ValidateMIC(key) - a.So(outputValidateMIC, ShouldBeTrue) - a.So(output.Commands(), ShouldBeEmpty) -} - -func TestInvalidHPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - - // No MACPayload - _, err1 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{}, Metadata{}) - a.So(err1, ShouldNotBeNil) - - // Not a MACPayload - _, err2 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{ - MACPayload: &lorawan.JoinRequestPayload{}, - }, Metadata{}) - a.So(err2, ShouldNotBeNil) - - // Not enough FRMPayloads - _, err3 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{ - MACPayload: &lorawan.MACPayload{}, - }, Metadata{}) - a.So(err3, ShouldNotBeNil) - - // FRMPayload is not DataPayload - _, err4 := NewHPacket(appEUI, devEUI, lorawan.PHYPayload{ - MACPayload: &lorawan.MACPayload{ - FRMPayload: []lorawan.Payload{ - &lorawan.JoinRequestPayload{}, - }, - }, - }, Metadata{}) - a.So(err4, ShouldNotBeNil) -} - -func TestHPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - payload, _, key := simplePayload(1, true) - - input, _ := NewHPacket(appEUI, devEUI, payload, Metadata{}) - - gOutput := marshalUnmarshal(t, input) - - output := gOutput.(HPacket) - - a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) - a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) - outPayload, _ := output.Payload(key) - a.So(string(outPayload), ShouldResemble, "PLD123") - a.So(output.Metadata(), ShouldResemble, Metadata{}) - a.So(output.FCnt(), ShouldEqual, 1) -} - -func TestInvalidAPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - - // No Payload - _, err1 := NewAPacket(appEUI, devEUI, []byte{}, []Metadata{}) - a.So(err1, ShouldNotBeNil) -} - -func TestAPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - payload := []byte("PLD123") - - input, _ := NewAPacket(appEUI, devEUI, payload, []Metadata{}) - - gOutput := marshalUnmarshal(t, input) - - output := gOutput.(APacket) - - a.So(output.Payload(), ShouldResemble, payload) - a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) - a.So(output.Payload(), ShouldResemble, payload) - a.So(output.Metadata(), ShouldBeEmpty) -} - -func TestJPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - devNonce := [2]byte{} - copy(devEUI[:], randBytes(2)) - - input := NewJPacket(appEUI, devEUI, devNonce, Metadata{}) - - gOutput := marshalUnmarshal(t, input) - - output := gOutput.(JPacket) - - a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) - a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) - a.So(output.DevNonce(), ShouldEqual, devNonce) - a.So(output.Metadata(), ShouldResemble, Metadata{}) -} - -func TestInvalidCPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - nwkSKey := lorawan.AES128Key{} - - // No Payload - _, err1 := NewCPacket(appEUI, devEUI, []byte{}, nwkSKey) - a.So(err1, ShouldNotBeNil) -} - -func TestCPacket(t *testing.T) { - a := New(t) - - appEUI := newEUI() - devEUI := newEUI() - payload := []byte("PLD123") - - nwkSKey := [16]byte{} - copy(devEUI[:], randBytes(16)) - - input, _ := NewCPacket(appEUI, devEUI, payload, nwkSKey) - - gOutput := marshalUnmarshal(t, input) - - output := gOutput.(CPacket) - - a.So(output.AppEUI().String(), ShouldEqual, appEUI.String()) - a.So(output.DevEUI().String(), ShouldEqual, devEUI.String()) - a.So(output.Payload(), ShouldResemble, payload) - outputNwkSKey := output.NwkSKey() - a.So(outputNwkSKey[:], ShouldResemble, nwkSKey[:]) -} - -func TestString(t *testing.T) { - a := New(t) - - { // RPacket - payload, _, _ := simplePayload(1, true) - gwEUI := []byte{} - copy(gwEUI[:], randBytes(8)) - - input, _ := NewRPacket(payload, gwEUI, Metadata{}) - - a.So(input.String(), ShouldNotEqual, "TODO") - a.So(input.String(), ShouldNotEqual, "") - } - - { // CPacket - appEUI := newEUI() - devEUI := newEUI() - payload := []byte("PLD123") - nwkSKey := [16]byte{} - copy(devEUI[:], randBytes(16)) - - input, _ := NewCPacket(appEUI, devEUI, payload, nwkSKey) - - a.So(input.String(), ShouldNotEqual, "TODO") - a.So(input.String(), ShouldNotEqual, "") - } - - { // JPacket - appEUI := newEUI() - devEUI := newEUI() - devNonce := [2]byte{} - copy(devEUI[:], randBytes(2)) - - input := NewJPacket(appEUI, devEUI, devNonce, Metadata{}) - - a.So(input.String(), ShouldNotEqual, "TODO") - a.So(input.String(), ShouldNotEqual, "") - } - { // APacket - appEUI := newEUI() - devEUI := newEUI() - payload := []byte("PLD123") - - input, _ := NewAPacket(appEUI, devEUI, payload, []Metadata{}) - - a.So(input.String(), ShouldNotEqual, "TODO") - a.So(input.String(), ShouldNotEqual, "") - } - - { // HPacket - - appEUI := newEUI() - devEUI := newEUI() - payload, _, _ := simplePayload(1, true) - - input, _ := NewHPacket(appEUI, devEUI, payload, Metadata{}) - - a.So(input.String(), ShouldNotEqual, "TODO") - a.So(input.String(), ShouldNotEqual, "") - } - - { // BPacket - payload, _, _ := simplePayload(1, true) - - input, _ := NewBPacket(payload, Metadata{}) - - a.So(input.String(), ShouldNotEqual, "TODO") - a.So(input.String(), ShouldNotEqual, "") - } -} diff --git a/core/protos/broker.proto b/core/protos/broker.proto new file mode 100644 index 000000000..e39fd70a7 --- /dev/null +++ b/core/protos/broker.proto @@ -0,0 +1,29 @@ +syntax = "proto3"; +import "lorawan.proto"; +import "core.proto"; + +package core; + +message DataBrokerReq { + LoRaWANData Payload = 1; + Metadata Metadata = 2; +} + +message DataBrokerRes { + LoRaWANData Payload = 1; + Metadata Metadata = 2; +} + +message SubBrokerReq { + string HandlerNet = 1; + bytes AppEUI = 2; + bytes DevEUI = 3; + bytes NwkSKey = 4; +} + +message SubBrokerRes{} + +service Broker { + rpc HandleData (DataBrokerReq) returns (DataBrokerRes); + rpc SubscribePersonalized (SubBrokerReq) returns (SubBrokerRes); +} diff --git a/core/protos/core.proto b/core/protos/core.proto new file mode 100644 index 000000000..808aa5f66 --- /dev/null +++ b/core/protos/core.proto @@ -0,0 +1,27 @@ +syntax = "proto3"; + +package core; + +message Metadata { + uint32 DutyRX1 = 1; + uint32 DutyRX2 = 2; + + float Frequency = 3; + string DataRate = 4; + string CodingRate = 5; + uint32 Timestamp = 6; + int32 Rssi = 7; + float Lsnr = 8; + uint32 PayloadSize = 9; + + int32 Altitude = 21; + float Longitude = 22; + float Latitude = 23; +} + +message StatsMetadata { + int32 Altitude = 1; + float Longitude = 2; + float Latitude = 3; +} + diff --git a/core/protos/handler.proto b/core/protos/handler.proto new file mode 100644 index 000000000..c51f2e074 --- /dev/null +++ b/core/protos/handler.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; +import "lorawan.proto"; +import "core.proto"; + +package core; + +message DataHandlerReq { + bytes Payload = 1; + Metadata Metadata = 2; + bytes AppEUI = 3; + bytes DevEUI = 4; + uint32 FCnt = 5; + uint32 MType = 6; +} + +message DataHandlerRes { + LoRaWANData Payload = 1; + Metadata Metadata = 2; +} + +service Handler { + rpc HandleData (DataHandlerReq) returns (DataHandlerRes); +} diff --git a/core/protos/lorawan.proto b/core/protos/lorawan.proto new file mode 100644 index 000000000..760d5c48e --- /dev/null +++ b/core/protos/lorawan.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +package core; + +message LoRaWANData { + LoRaWANMHDR MHDR = 1; + LoRaWANMACPayload MACPayload = 2; + bytes MIC = 3; + +} + +message LoRaWANMHDR { + uint32 MType = 1; + uint32 Major = 2; +} + + +message LoRaWANMACPayload { + LoRaWANFHDR FHDR = 1; + uint32 FPort = 2; + bytes FRMPayload = 3; +} + +message LoRaWANFHDR { + bytes DevAddr = 1; + LoRaWANFCtrl FCtrl = 2; + uint32 FCnt = 3; + repeated bytes FOpts = 4; +} + +message LoRaWANFCtrl { + bool ADR = 1; + bool ADRAckReq = 2; + bool Ack = 3; + bool FPending = 4; + bytes FOptsLen = 5; +} + +message LoRaWANJoinRequest { + bytes DevEUI = 1; + bytes AppEUI = 2; + bytes DevNonce = 3; +} + +message LoRaWANJoinAccept { + bytes AppNonce = 1; + bytes NetID = 2; + bytes DevAddr = 3; + LoRaWANDLSettings DLSettings = 4; + uint32 RXDelay = 5; + +} + +message LoRaWANDLSettings { + uint32 RX1DRoffset = 1; + uint32 RX2DataRate = 2; +} diff --git a/core/protos/router.proto b/core/protos/router.proto new file mode 100644 index 000000000..b9af02fb0 --- /dev/null +++ b/core/protos/router.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; +import "lorawan.proto"; +import "core.proto"; + +package core; + +message DataRouterReq { + LoRaWANData Payload = 1; + Metadata Metadata = 2; + bytes GatewayID = 3; +} + +message DataRouterRes { + LoRaWANData Payload = 1; + Metadata Metadata = 2; +} + +message StatsReq { + bytes GatewayID = 1; + StatsMetadata Metadata = 2; +} + +message StatsRes{} + +service Router { + rpc HandleData (DataRouterReq) returns (DataRouterRes); + rpc HandleStats (StatsReq) returns (StatsRes); +} diff --git a/core/registrations_interfaces.go b/core/registrations_interfaces.go deleted file mode 100644 index 214778b5d..000000000 --- a/core/registrations_interfaces.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "encoding" - "encoding/json" - - "github.com/brocaar/lorawan" -) - -// Recipient represents the recipient manipulated by adapters -type Recipient interface { - encoding.BinaryMarshaler -} - -// JSONRecipient extends the actual Recipient to also support json marshalin/unmarshaling -type JSONRecipient interface { - Recipient - json.Marshaler -} - -// Registration gives an elementary base for each other registration levels -type Registration interface { - Recipient() Recipient -} - -// RRegistration represents the first-level of registration, used by router and router adapters -type RRegistration interface { - Registration - DevEUI() lorawan.EUI64 -} - -// BRegistration represents the second-level of registrations, used by the broker and broker -// adapters -type BRegistration interface { - RRegistration - AppEUI() lorawan.EUI64 - NwkSKey() lorawan.AES128Key -} - -// ARegistration represents another second-level of registrations, used betwen handler and broker to -// register application before OTAA -type ARegistration interface { - Registration - AppEUI() lorawan.EUI64 -} - -// HRegistration represents the third-level of registrations, used bt the handler and handler -// adapters -type HRegistration interface { - BRegistration - AppSKey() lorawan.AES128Key -} diff --git a/core/router.pb.go b/core/router.pb.go new file mode 100644 index 000000000..8bc93f88d --- /dev/null +++ b/core/router.pb.go @@ -0,0 +1,977 @@ +// Code generated by protoc-gen-gogo. +// source: router.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type DataRouterReq struct { + Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + GatewayID []byte `protobuf:"bytes,3,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` +} + +func (m *DataRouterReq) Reset() { *m = DataRouterReq{} } +func (m *DataRouterReq) String() string { return proto.CompactTextString(m) } +func (*DataRouterReq) ProtoMessage() {} +func (*DataRouterReq) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{0} } + +func (m *DataRouterReq) GetPayload() *LoRaWANData { + if m != nil { + return m.Payload + } + return nil +} + +func (m *DataRouterReq) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type DataRouterRes struct { + Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *DataRouterRes) Reset() { *m = DataRouterRes{} } +func (m *DataRouterRes) String() string { return proto.CompactTextString(m) } +func (*DataRouterRes) ProtoMessage() {} +func (*DataRouterRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{1} } + +func (m *DataRouterRes) GetPayload() *LoRaWANData { + if m != nil { + return m.Payload + } + return nil +} + +func (m *DataRouterRes) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type StatsReq struct { + GatewayID []byte `protobuf:"bytes,1,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` + Metadata *StatsMetadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *StatsReq) Reset() { *m = StatsReq{} } +func (m *StatsReq) String() string { return proto.CompactTextString(m) } +func (*StatsReq) ProtoMessage() {} +func (*StatsReq) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{2} } + +func (m *StatsReq) GetMetadata() *StatsMetadata { + if m != nil { + return m.Metadata + } + return nil +} + +type StatsRes struct { +} + +func (m *StatsRes) Reset() { *m = StatsRes{} } +func (m *StatsRes) String() string { return proto.CompactTextString(m) } +func (*StatsRes) ProtoMessage() {} +func (*StatsRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{3} } + +func init() { + proto.RegisterType((*DataRouterReq)(nil), "core.DataRouterReq") + proto.RegisterType((*DataRouterRes)(nil), "core.DataRouterRes") + proto.RegisterType((*StatsReq)(nil), "core.StatsReq") + proto.RegisterType((*StatsRes)(nil), "core.StatsRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Router service + +type RouterClient interface { + HandleData(ctx context.Context, in *DataRouterReq, opts ...grpc.CallOption) (*DataRouterRes, error) + HandleStats(ctx context.Context, in *StatsReq, opts ...grpc.CallOption) (*StatsRes, error) +} + +type routerClient struct { + cc *grpc.ClientConn +} + +func NewRouterClient(cc *grpc.ClientConn) RouterClient { + return &routerClient{cc} +} + +func (c *routerClient) HandleData(ctx context.Context, in *DataRouterReq, opts ...grpc.CallOption) (*DataRouterRes, error) { + out := new(DataRouterRes) + err := grpc.Invoke(ctx, "/core.Router/HandleData", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerClient) HandleStats(ctx context.Context, in *StatsReq, opts ...grpc.CallOption) (*StatsRes, error) { + out := new(StatsRes) + err := grpc.Invoke(ctx, "/core.Router/HandleStats", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Router service + +type RouterServer interface { + HandleData(context.Context, *DataRouterReq) (*DataRouterRes, error) + HandleStats(context.Context, *StatsReq) (*StatsRes, error) +} + +func RegisterRouterServer(s *grpc.Server, srv RouterServer) { + s.RegisterService(&_Router_serviceDesc, srv) +} + +func _Router_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DataRouterReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterServer).HandleData(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _Router_HandleStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatsReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterServer).HandleStats(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Router_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.Router", + HandlerType: (*RouterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "HandleData", + Handler: _Router_HandleData_Handler, + }, + { + MethodName: "HandleStats", + Handler: _Router_HandleStats_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DataRouterReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataRouterReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) + n1, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) + n2, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.GatewayID != nil { + if len(m.GatewayID) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) + } + } + return i, nil +} + +func (m *DataRouterRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataRouterRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) + n3, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) + n4, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *StatsReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatsReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.GatewayID != nil { + if len(m.GatewayID) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) + } + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) + n5, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + return i, nil +} + +func (m *StatsRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatsRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64Router(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Router(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintRouter(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DataRouterReq) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayID != nil { + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } + return n +} + +func (m *DataRouterRes) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *StatsReq) Size() (n int) { + var l int + _ = l + if m.GatewayID != nil { + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *StatsRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovRouter(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRouter(x uint64) (n int) { + return sovRouter(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DataRouterReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataRouterReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataRouterReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANData{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) + if m.GatewayID == nil { + m.GatewayID = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DataRouterRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataRouterRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataRouterRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANData{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatsReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatsReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatsReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) + if m.GatewayID == nil { + m.GatewayID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &StatsMetadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatsRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatsRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatsRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRouter(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRouter + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRouter(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRouter = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRouter = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorRouter = []byte{ + // 266 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xca, 0x2f, 0x2d, + 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, + 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, + 0x36, 0x46, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0xc4, 0x20, 0xb0, 0xae, 0xa0, 0xd4, 0x42, 0x21, 0x6d, + 0x2e, 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, + 0x41, 0x3d, 0xb0, 0x7a, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0xb0, 0x62, 0xf6, 0x02, 0x88, + 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, + 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x12, 0x92, 0xe1, 0xe2, 0x74, 0x4f, 0x2c, + 0x49, 0x2d, 0x4f, 0xac, 0xf4, 0x74, 0x91, 0x60, 0x06, 0x2a, 0xe6, 0x09, 0xe2, 0x4c, 0x87, 0x09, + 0x28, 0x65, 0xa0, 0xba, 0xa3, 0x98, 0x66, 0xee, 0x50, 0x8a, 0xe4, 0xe2, 0x08, 0x2e, 0x49, 0x2c, + 0x29, 0x06, 0x79, 0x16, 0xc5, 0x4d, 0x8c, 0x68, 0x6e, 0x12, 0xd2, 0xc7, 0x30, 0x55, 0x18, 0x62, + 0x2a, 0x58, 0x3f, 0x16, 0xa3, 0xb9, 0xe0, 0x46, 0x17, 0x1b, 0xe5, 0x73, 0xb1, 0x41, 0x3c, 0x23, + 0x64, 0xc6, 0xc5, 0xe5, 0x91, 0x98, 0x97, 0x92, 0x93, 0x0a, 0x72, 0xb3, 0x10, 0xd4, 0x08, 0x94, + 0x40, 0x97, 0xc2, 0x22, 0x58, 0x2c, 0xa4, 0xcb, 0xc5, 0x0d, 0xd1, 0x07, 0x36, 0x53, 0x88, 0x0f, + 0xc9, 0x6e, 0x90, 0x1e, 0x54, 0x7e, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x80, 0xf8, + 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x8e, 0x63, 0x63, 0x40, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xe4, 0x6d, 0x5d, 0xea, 0x14, 0x02, 0x00, 0x00, +} From 9709bf27f3e4bac4f49457abcaa3e57c6ee1903d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 17:33:09 +0100 Subject: [PATCH 1049/2266] [refactor/grpc] Make dutycycle package okay with new core package --- core/dutycycle/helpers_test.go | 10 +-- core/dutycycle/scoreComputer.go | 12 +-- core/dutycycle/scoreComputer_test.go | 109 +++++++++++---------------- utils/testing/testing.go | 9 +++ 4 files changed, 61 insertions(+), 79 deletions(-) diff --git a/core/dutycycle/helpers_test.go b/core/dutycycle/helpers_test.go index 057a1a34c..fb69be7aa 100644 --- a/core/dutycycle/helpers_test.go +++ b/core/dutycycle/helpers_test.go @@ -6,22 +6,22 @@ package dutycycle import ( "testing" - . "github.com/TheThingsNetwork/ttn/core/mocks" + testutil "github.com/TheThingsNetwork/ttn/utils/testing" ) // ----- CHECK utilities func CheckSubBands(t *testing.T, want subBand, got subBand) { - Check(t, want, got, "SubBands") + testutil.Check(t, want, got, "SubBands") } func CheckUsages(t *testing.T, want map[subBand]uint, got map[subBand]uint) { - Check(t, want, got, "Usages") + testutil.Check(t, want, got, "Usages") } func CheckBestTargets(t *testing.T, want *BestTarget, got *BestTarget) { - Check(t, want, got, "Targets") + testutil.Check(t, want, got, "Targets") } func CheckStates(t *testing.T, want State, got State) { - Check(t, want, got, "States") + testutil.Check(t, want, got, "States") } diff --git a/core/dutycycle/scoreComputer.go b/core/dutycycle/scoreComputer.go index 12b9c12cc..4ede8efa9 100644 --- a/core/dutycycle/scoreComputer.go +++ b/core/dutycycle/scoreComputer.go @@ -56,20 +56,14 @@ func NewScoreComputer(datr *string) (*ScoreComputer, scores, error) { // accordingly whether it is better than the existing one func (c *ScoreComputer) Update(s scores, id int, metadata core.Metadata) scores { dutyRX1, dutyRX2 := metadata.DutyRX1, metadata.DutyRX2 - lsnr, rssi := metadata.Lsnr, metadata.Rssi + lsnr, rssi := float64(metadata.Lsnr), int(metadata.Rssi) - if dutyRX1 == nil || dutyRX2 == nil || lsnr == nil || rssi == nil { - // NOTE We could possibly compute something if we had some of them (but not all). - // However, for now, we expect them all - return s - } - - rx1 := computeScore(State(*dutyRX1), *lsnr, *rssi) + rx1 := computeScore(State(dutyRX1), lsnr, rssi) if rx1 > s.rx1.Score { s.rx1.Score, s.rx1.ID = rx1, id } - rx2 := computeScore(State(*dutyRX2), *lsnr, *rssi) + rx2 := computeScore(State(dutyRX2), lsnr, rssi) if rx2 > s.rx2.Score { s.rx2.Score, s.rx2.ID = rx2, id } diff --git a/core/dutycycle/scoreComputer_test.go b/core/dutycycle/scoreComputer_test.go index 243a0d971..efbaaff01 100644 --- a/core/dutycycle/scoreComputer_test.go +++ b/core/dutycycle/scoreComputer_test.go @@ -75,10 +75,10 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateBlocked)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateBlocked), + Rssi: -25, + Lsnr: 5.0, }) got := c.Get(s) @@ -97,10 +97,10 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateBlocked)), - DutyRX2: pointer.Uint(uint(StateHighlyAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateBlocked), + DutyRX2: uint32(StateHighlyAvailable), + Rssi: -25, + Lsnr: 5.0, }) got := c.Get(s) @@ -119,10 +119,10 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateBlocked)), - DutyRX2: pointer.Uint(uint(StateBlocked)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateBlocked), + DutyRX2: uint32(StateBlocked), + Rssi: -25, + Lsnr: 5.0, }) got := c.Get(s) @@ -141,10 +141,10 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateAvailable), + Rssi: -25, + Lsnr: 5.0, }) got := c.Get(s) @@ -163,16 +163,16 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateAvailable), + Rssi: -25, + Lsnr: 5.0, }) s = c.Update(s, 2, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(3.0), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateAvailable), + Rssi: -25, + Lsnr: 3.0, }) got := c.Get(s) @@ -191,10 +191,10 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateBlocked)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateBlocked), + Rssi: -25, + Lsnr: 5.0, }) got := c.Get(s) @@ -213,16 +213,16 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateWarning)), - DutyRX2: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateWarning), + DutyRX2: uint32(StateAvailable), + Rssi: -25, + Lsnr: 5.0, }) s = c.Update(s, 2, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.0), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateAvailable), + Rssi: -25, + Lsnr: 5.0, }) got := c.Get(s) @@ -241,41 +241,20 @@ func TestUpdateGet(t *testing.T) { // Operate s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - DutyRX2: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.1), + DutyRX1: uint32(StateAvailable), + DutyRX2: uint32(StateAvailable), + Rssi: -25, + Lsnr: 5.1, }) s = c.Update(s, 2, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateHighlyAvailable)), - DutyRX2: pointer.Uint(uint(StateHighlyAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(3.4), + DutyRX1: uint32(StateHighlyAvailable), + DutyRX2: uint32(StateHighlyAvailable), + Rssi: -25, + Lsnr: 3.4, }) got := c.Get(s) // Check CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) } - - // -------------------- - - { - Desc(t, "Missing metadata in update") - - // Build - c, s, err := NewScoreComputer(pointer.String("SF12BW125")) - errutil.CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: pointer.Uint(uint(StateAvailable)), - Rssi: pointer.Int(-25), - Lsnr: pointer.Float64(5.1), - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, nil, got) - } } diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 55ccf3571..8910cb671 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -7,6 +7,7 @@ package testing import ( "fmt" + "reflect" "testing" "github.com/apex/log" @@ -34,3 +35,11 @@ func Ko(t *testing.T, format string, a ...interface{}) { func Desc(t *testing.T, format string, a ...interface{}) { t.Logf("\033[36m%s\033[0m", fmt.Sprintf(format, a...)) } + +// Check serves a for general comparison between two objects +func Check(t *testing.T, want, got interface{}, name string) { + if !reflect.DeepEqual(want, got) { + Ko(t, "%s don't match expectations.\nWant: %+v\nGot: %+v", name, want, got) + } + Ok(t, fmt.Sprintf("Check %s", name)) +} From 9f1ad830ce80edc1e8d4689a4af9a8f0d394acf1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 18:48:06 +0100 Subject: [PATCH 1050/2266] [refactor/grpc] Remove use of a pointer in ScoreComputer initializer --- core/dutycycle/scoreComputer.go | 7 ++----- core/dutycycle/scoreComputer_test.go | 30 ++++++++++------------------ 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/core/dutycycle/scoreComputer.go b/core/dutycycle/scoreComputer.go index 4ede8efa9..c39add121 100644 --- a/core/dutycycle/scoreComputer.go +++ b/core/dutycycle/scoreComputer.go @@ -40,11 +40,8 @@ type scores struct { } // NewScoreComputer constructs a new ScoreComputer and initiate an empty scores table -func NewScoreComputer(datr *string) (*ScoreComputer, scores, error) { - if datr == nil { - return nil, scores{}, errors.New(errors.Structural, "Missing mandatory metadata datr") - } - sf, _, err := ParseDatr(*datr) +func NewScoreComputer(datr string) (*ScoreComputer, scores, error) { + sf, _, err := ParseDatr(datr) if err != nil { return nil, scores{}, errors.New(errors.Structural, err) } diff --git a/core/dutycycle/scoreComputer_test.go b/core/dutycycle/scoreComputer_test.go index efbaaff01..048a34889 100644 --- a/core/dutycycle/scoreComputer_test.go +++ b/core/dutycycle/scoreComputer_test.go @@ -14,17 +14,9 @@ import ( ) func TestNewScoreComputer(t *testing.T) { - { - Desc(t, "Nil datr as argument") - _, _, err := NewScoreComputer(nil) - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - // -------------------- - { Desc(t, "Invalid datr as argument") - _, _, err := NewScoreComputer(pointer.String("TheThingsNetwork")) + _, _, err := NewScoreComputer("TheThingsNetwork") errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) } @@ -32,7 +24,7 @@ func TestNewScoreComputer(t *testing.T) { { Desc(t, "Valid datr") - _, _, err := NewScoreComputer(pointer.String("SF8BW250")) + _, _, err := NewScoreComputer("SF8BW250") errutil.CheckErrors(t, nil, err) } } @@ -54,7 +46,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF7 | ...") // Build - c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + c, s, err := NewScoreComputer("SF7BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -70,7 +62,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF7 | (1, Av, Bl, -25, 5.0)") // Build - c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + c, s, err := NewScoreComputer("SF7BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -92,7 +84,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF7 | (1, Bl, Ha, -25, 5.0)") // Build - c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + c, s, err := NewScoreComputer("SF7BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -114,7 +106,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF7 | (1, Bl, Bl, -25, 5.0)") // Build - c, s, err := NewScoreComputer(pointer.String("SF7BW125")) + c, s, err := NewScoreComputer("SF7BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -136,7 +128,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF9 | (1, Av, Av, -25, 5.0) ") // Build - c, s, err := NewScoreComputer(pointer.String("SF9BW125")) + c, s, err := NewScoreComputer("SF9BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -158,7 +150,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF10 | (1, Av, Av, -25, 5.0) :: (2, Av, Av, -25, 3.0)") // Build - c, s, err := NewScoreComputer(pointer.String("SF10BW125")) + c, s, err := NewScoreComputer("SF10BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -186,7 +178,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF10 | (1, Av, Bl, -25, 5.0)") // Build - c, s, err := NewScoreComputer(pointer.String("SF10BW125")) + c, s, err := NewScoreComputer("SF10BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -208,7 +200,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF8 | (1, Wa, Av, -25, 5.0) :: (2, Av, Av, -25, 5.0)") // Build - c, s, err := NewScoreComputer(pointer.String("SF8BW125")) + c, s, err := NewScoreComputer("SF8BW125") errutil.CheckErrors(t, nil, err) // Operate @@ -236,7 +228,7 @@ func TestUpdateGet(t *testing.T) { Desc(t, "SF12 | (1, Av, Av, -25, 5.1) :: (2, Ha, Ha, -25, 3.4)") // Build - c, s, err := NewScoreComputer(pointer.String("SF12BW125")) + c, s, err := NewScoreComputer("SF12BW125") errutil.CheckErrors(t, nil, err) // Operate From 5f9a5efd811be71e114e93673f8424e1084bf16e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 23:02:47 +0100 Subject: [PATCH 1051/2266] [refactor/grpc] Make handler implements the grpc HandlerServer interface --- core/components/handler/brokerRegistration.go | 57 -- .../handler/brokerRegistration_test.go | 47 - core/components/handler/devStorage.go | 139 ++- core/components/handler/devStorage_test.go | 114 --- core/components/handler/handler.go | 553 +++++----- core/components/handler/handler_test.go | 955 ------------------ core/components/handler/helpers_test.go | 122 --- core/components/handler/mocks_test.go | 115 --- core/components/handler/pktStorage.go | 66 +- core/components/handler/pktStorage_test.go | 153 --- 10 files changed, 381 insertions(+), 1940 deletions(-) delete mode 100644 core/components/handler/brokerRegistration.go delete mode 100644 core/components/handler/brokerRegistration_test.go delete mode 100644 core/components/handler/devStorage_test.go delete mode 100644 core/components/handler/handler_test.go delete mode 100644 core/components/handler/helpers_test.go delete mode 100644 core/components/handler/mocks_test.go delete mode 100644 core/components/handler/pktStorage_test.go diff --git a/core/components/handler/brokerRegistration.go b/core/components/handler/brokerRegistration.go deleted file mode 100644 index c2c5d8883..000000000 --- a/core/components/handler/brokerRegistration.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "encoding/json" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// type brokerRegistration implements the core.BRegistration interface -type brokerRegistration struct { - recipient core.Recipient - appEUI lorawan.EUI64 - nwkSKey lorawan.AES128Key - devEUI lorawan.EUI64 -} - -// Recipient implements the core.BRegistration interface -func (r brokerRegistration) Recipient() core.Recipient { - return r.recipient -} - -// AppEUI implements the core.BRegistration interface -func (r brokerRegistration) AppEUI() lorawan.EUI64 { - return r.appEUI -} - -// DevEUI implements the core.BRegistration interface -func (r brokerRegistration) DevEUI() lorawan.EUI64 { - return r.devEUI -} - -// NwkSKey implements the core.BRegistration interface -func (r brokerRegistration) NwkSKey() lorawan.AES128Key { - return r.nwkSKey -} - -// MarshalJSON implements the encoding/json.Marshaler interface -func (r brokerRegistration) MarshalJSON() ([]byte, error) { - data, err := json.Marshal(struct { - AppEUI lorawan.EUI64 `json:"app_eui"` - NwkSKey lorawan.AES128Key `json:"nwks_key"` - DevEUI lorawan.EUI64 `json:"dev_eui"` - }{ - AppEUI: r.appEUI, - NwkSKey: r.nwkSKey, - DevEUI: r.devEUI, - }) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - return data, nil -} diff --git a/core/components/handler/brokerRegistration_test.go b/core/components/handler/brokerRegistration_test.go deleted file mode 100644 index e594a01ad..000000000 --- a/core/components/handler/brokerRegistration_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "testing" - - mocks "github.com/TheThingsNetwork/ttn/core/mocks" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/brocaar/lorawan" -) - -func TestRegistration(t *testing.T) { - recipient := mocks.NewMockRecipient() - devEUI := lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) - appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) - nwkSKey := lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}) - - reg := brokerRegistration{ - recipient: recipient, - devEUI: devEUI, - appEUI: appEUI, - nwkSKey: nwkSKey, - } - - mocks.Check(t, recipient, reg.Recipient(), "Recipients") - mocks.Check(t, devEUI, reg.DevEUI(), "DevEUIs") - mocks.Check(t, appEUI, reg.AppEUI(), "AppEUIs") - mocks.Check(t, nwkSKey, reg.NwkSKey(), "NwkSKeys") - -} - -func TestRegistrationMarshal(t *testing.T) { - { - reg := brokerRegistration{ - recipient: mocks.NewMockRecipient(), - devEUI: lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - appEUI: lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}), - nwkSKey: lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}), - } - - data, err := reg.MarshalJSON() - t.Log(string(data)) - errutil.CheckErrors(t, nil, err) - } -} diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 6b8fe7b99..39d4ae7ca 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -8,39 +8,36 @@ import ( "encoding/binary" "fmt" "sync" - - . "github.com/TheThingsNetwork/ttn/core" + // + // . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" - "github.com/brocaar/lorawan" ) // DevStorage gives a facade to manipulate the handler devices database type DevStorage interface { - UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error - Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) - StorePersonalized(r HRegistration) error - StoreActivated(r HRegistration) error + UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error + Lookup(appEUI []byte, devEUI []byte) (devEntry, error) + StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error Close() error } +const dbDevices = "devices" + type devEntry struct { - Recipient []byte - DevAddr lorawan.DevAddr - AppSKey lorawan.AES128Key - NwkSKey lorawan.AES128Key - FCntDown uint32 + DevAddr [4]byte + AppSKey [16]byte + NwkSKey [16]byte + FCntDown uint32 } -type appEntry struct { - AppKey lorawan.AES128Key -} +//type appEntry struct { +// AppKey lorawan.AES128Key +//} type devStorage struct { sync.RWMutex - db dbutil.Interface - Name string + db dbutil.Interface } // NewDevStorage creates a new Device Storage for handler @@ -50,21 +47,21 @@ func NewDevStorage(name string) (DevStorage, error) { return nil, errors.New(errors.Operational, err) } - return &devStorage{db: itf, Name: "entry"}, nil + return &devStorage{db: itf}, nil } // Lookup implements the handler.DevStorage interface -func (s *devStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { +func (s *devStorage) Lookup(appEUI []byte, devEUI []byte) (devEntry, error) { return s.lookup(appEUI, devEUI, true) } // lookup allow other method to re-use lookup while holding the lock -func (s *devStorage) lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64, shouldLock bool) (devEntry, error) { +func (s *devStorage) lookup(appEUI []byte, devEUI []byte, shouldLock bool) (devEntry, error) { if shouldLock { s.RLock() defer s.RUnlock() } - itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), &devEntry{}) + itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), &devEntry{}) if err != nil { return devEntry{}, err // Operational || NotFound } @@ -76,31 +73,23 @@ func (s *devStorage) lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64, shouldLo } // StorePersonalized implements the handler.DevStorage interface -func (s *devStorage) StorePersonalized(reg HRegistration) error { - s.Lock() - defer s.Unlock() - appEUI := reg.AppEUI() - devEUI := reg.DevEUI() - devAddr := lorawan.DevAddr{} - copy(devAddr[:], devEUI[4:]) - data, err := reg.Recipient().MarshalBinary() - if err != nil { - return errors.New(errors.Structural, "Cannot marshal recipient") - } - +func (s *devStorage) StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error { + devEUI := make([]byte, 8, 8) + copy(devEUI[4:], devAddr[:]) e := []dbutil.Entry{ &devEntry{ - Recipient: data, - AppSKey: reg.AppSKey(), - NwkSKey: reg.NwkSKey(), - DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, + DevAddr: devAddr, }, } - return s.db.Replace(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), e) + s.Lock() + defer s.Unlock() + return s.db.Replace(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), e) } // UpdateFCnt implements the handler.DevStorage interface -func (s *devStorage) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { +func (s *devStorage) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { s.Lock() defer s.Unlock() devEntry, err := s.lookup(appEUI, devEUI, false) @@ -108,12 +97,7 @@ func (s *devStorage) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt return err } devEntry.FCntDown = fcnt - return s.db.Replace(fmt.Sprintf("%x.%x", appEUI[:], devEUI[:]), []byte(s.Name), []dbutil.Entry{&devEntry}) -} - -// StoreActivated implements the handler.DevStorage interface -func (s *devStorage) StoreActivated(reg HRegistration) error { - return errors.New(errors.Implementation, "Not implemented yet") + return s.db.Replace(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), []dbutil.Entry{&devEntry}) } // Close implements the handler.DevStorage interface @@ -123,45 +107,36 @@ func (s *devStorage) Close() error { // MarshalBinary implements the encoding.BinaryMarshaler interface func (e devEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.Recipient) - rw.Write(e.DevAddr) - rw.Write(e.AppSKey) - rw.Write(e.NwkSKey) - rw.Write(e.FCntDown) - return rw.Bytes() + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, e.DevAddr[:]) // 4 + binary.Write(buf, binary.BigEndian, e.AppSKey[:]) // 16 + binary.Write(buf, binary.BigEndian, e.NwkSKey[:]) // 16 + binary.Write(buf, binary.BigEndian, e.FCntDown) // 4 + if len(buf.Bytes()) != 40 { + return nil, errors.New(errors.Structural, "Unable to marshal devEntry") + } + return buf.Bytes(), nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { e.Recipient = data }) - rw.Read(func(data []byte) { copy(e.DevAddr[:], data) }) - rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) - rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) - rw.TryRead(func(data []byte) error { - buf := new(bytes.Buffer) - buf.Write(data) - fcnt := new(uint32) - if err := binary.Read(buf, binary.BigEndian, fcnt); err != nil { - return err - } - e.FCntDown = *fcnt - return nil - }) - return rw.Err() + buf := bytes.NewBuffer(data) + binary.Read(buf, binary.BigEndian, e.DevAddr) + binary.Read(buf, binary.BigEndian, e.AppSKey) + binary.Read(buf, binary.BigEndian, e.NwkSKey) + return binary.Read(buf, binary.BigEndian, &e.FCntDown) } -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e appEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.AppKey) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *appEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) - return rw.Err() -} +//// MarshalBinary implements the encoding.BinaryMarshaler interface +//func (e appEntry) MarshalBinary() ([]byte, error) { +// rw := readwriter.New(nil) +// rw.Write(e.AppKey) +// return rw.Bytes() +//} +// +//// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +//func (e *appEntry) UnmarshalBinary(data []byte) error { +// rw := readwriter.New(data) +// rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) +// return rw.Err() +//} diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go deleted file mode 100644 index 3043e84fb..000000000 --- a/core/components/handler/devStorage_test.go +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -const devDB = "TestDevStorage.db" - -func TestLookupStore(t *testing.T) { - var db DevStorage - defer func() { - os.Remove(path.Join(os.TempDir(), devDB)) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - var err error - db, err = NewDevStorage(path.Join(os.TempDir(), devDB)) - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Store and Lookup a registration") - - // Build - r := NewMockHRegistration() - - // Operate - err := db.StorePersonalized(r) - CheckErrors(t, nil, err) - entry, err := db.Lookup(r.AppEUI(), r.DevEUI()) - - // Check - CheckErrors(t, nil, err) - CheckEntries(t, r, entry) - } - - // ------------------ - - { - Desc(t, "Lookup a non-existing registration") - - // Build - r := NewMockHRegistration() - r.OutAppEUI = lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) - - // Operate - _, err := db.Lookup(r.AppEUI(), r.DevEUI()) - - // Check - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - } - - // ------------------ - - { - Desc(t, "Store twice the same registration") - - // Build - r := NewMockHRegistration() - r.OutAppEUI = lorawan.EUI64([8]byte{1, 4, 1, 4, 1, 4, 1, 4}) - - // Operate - _ = db.StorePersonalized(r) - err := db.StorePersonalized(r) - CheckErrors(t, nil, err) - entry, err := db.Lookup(r.AppEUI(), r.DevEUI()) - - // Check - CheckErrors(t, nil, err) - CheckEntries(t, r, entry) - } - - // ------------------ - - { - Desc(t, "Store Activated") - - // Build - r := NewMockHRegistration() - r.OutAppEUI = lorawan.EUI64([8]byte{6, 6, 6, 7, 8, 6, 7, 6}) - - // Operate - err := db.StoreActivated(r) - - // Check - CheckErrors(t, pointer.String(string(errors.Implementation)), err) - } - - // ------------------ - - { - Desc(t, "Close the storage") - err := db.Close() - CheckErrors(t, nil, err) - } - -} diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 179efb009..d08d83745 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -6,168 +6,293 @@ package handler import ( "bytes" "encoding/binary" + "net" "reflect" "time" - . "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/brocaar/lorawan" + "golang.org/x/net/context" + "google.golang.org/grpc" ) const bufferDelay time.Duration = time.Millisecond * 300 // component implements the core.Component interface type component struct { - broker JSONRecipient + broker core.BrokerClient ctx log.Interface devices DevStorage packets PktStorage set chan<- bundle + adapter core.AppClient + netAddr string } type bundle struct { - Adapter Adapter - Chresp chan interface{} - Entry devEntry - ID [20]byte - Packet HPacket - Time time.Time + Chresp chan interface{} + Entry devEntry + ID [20]byte + Packet core.DataUpHandlerReq + Time time.Time } // New construct a new Handler -func New(devDb DevStorage, pktDb PktStorage, broker JSONRecipient, ctx log.Interface) Handler { - h := component{ +func New(devDb DevStorage, pktDb PktStorage, broker core.BrokerClient, adapter core.AppClient, ctx log.Interface) core.HandlerServer { + return &component{ ctx: ctx, devices: devDb, packets: pktDb, broker: broker, + adapter: adapter, } +} - set := make(chan bundle) - bundles := make(chan []bundle) +// Start actually runs the component and starts the rpc server +func (h *component) Start(netAddr string) error { + conn, err := net.Listen("tcp", netAddr) + if err != nil { + return errors.New(errors.Operational, err) + } + + server := grpc.NewServer() + core.RegisterHandlerServer(server, h) + set := make(chan bundle) h.set = set + bundles := make(chan []bundle) go h.consumeBundles(bundles) go h.consumeSet(bundles, set) - return h + defer close(bundles) + defer close(set) + + if err := server.Serve(conn); err != nil { + return errors.New(errors.Operational, err) + } + return nil } -// Register implements the core.Component interface -func (h component) Register(reg Registration, an AckNacker, sub Subscriber) (err error) { - h.ctx.WithField("registration", reg).Debug("New registration request") - defer ensureAckNack(an, nil, &err) +// RegisterPersonalized implements the core.HandlerServer interface +func (h component) SubscribePersonalized(bctx context.Context, req *core.SubPersoHandlerReq) (*core.SubPersoHandlerRes, error) { + h.ctx.Debug("New personalized subscription request") stats.MarkMeter("handler.registration.in") - hreg, ok := reg.(HRegistration) - if !ok { + if len(req.AppEUI) != 8 { stats.MarkMeter("handler.registration.invalid") - return errors.New(errors.Structural, "Not a Handler registration") + return nil, errors.New(errors.Structural, "Invalid Application EUI") } - if err = h.devices.StorePersonalized(hreg); err != nil { - return errors.New(errors.Operational, err) + if len(req.DevAddr) != 4 { + stats.MarkMeter("handler.registration.invalid") + return nil, errors.New(errors.Structural, "Invalid Device Address") } + var devAddr [4]byte + copy(devAddr[:], req.DevAddr) - return sub.Subscribe(brokerRegistration{ - recipient: h.broker, - appEUI: hreg.AppEUI(), - devEUI: hreg.DevEUI(), - nwkSKey: hreg.NwkSKey(), + if len(req.NwkSKey) != 16 { + stats.MarkMeter("handler.registration.invalid") + return nil, errors.New(errors.Structural, "Invalid Network Session Key") + } + var nwkSKey [16]byte + copy(nwkSKey[:], req.NwkSKey) + + if len(req.AppSKey) != 16 { + stats.MarkMeter("handler.registration.invalid") + return nil, errors.New(errors.Structural, "Invalid Application Session Key") + } + var appSKey [16]byte + copy(appSKey[:], req.AppSKey) + + if h.netAddr == "" { + return nil, errors.New(errors.Operational, "Illegal call. Start the server first") + } + + if err := h.devices.StorePersonalized(req.AppEUI, devAddr, nwkSKey, appSKey); err != nil { + return nil, errors.New(errors.Operational, err) + } + + _, err := h.broker.SubscribePersonalized(context.Background(), &core.SubPersoBrokerReq{ + HandlerNet: h.netAddr, + AppEUI: req.AppEUI, + DevAddr: req.DevAddr, + NwkSKey: req.NwkSKey, }) + + if err != nil { + return nil, errors.New(errors.Operational, err) + } + return nil, nil } -// HandleUp implements the core.Component interface -func (h component) HandleUp(data []byte, an AckNacker, up Adapter) (err error) { - // Make sure we don't forget the AckNacker - var ack Packet - defer ensureAckNack(an, &ack, &err) +// HandleDataDown implements the core.HandlerServer interface +func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { + stats.MarkMeter("handler.downlink.in") + h.ctx.Debug("Handle downlink message") + + // Unmarshal the given packet and see what gift we get + + if len(req.AppEUI) != 8 { + stats.MarkMeter("handler.downlink.invalid") + return nil, errors.New(errors.Structural, "Invalid Application EUI") + } + + if len(req.DevEUI) != 8 { + stats.MarkMeter("handler.downlink.invalid") + return nil, errors.New(errors.Structural, "Invalid Device EUI") + } + + if len(req.Payload) == 0 { + stats.MarkMeter("handler.downlink.invalid") + return nil, errors.New(errors.Structural, "Invalid payload") + } + + h.ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") + return nil, h.packets.Push(req.AppEUI, req.DevEUI, req.Payload) +} + +// HandleDataUp implements the core.HandlerServer interface +func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq) (*core.DataUpHandlerRes, error) { stats.MarkMeter("handler.uplink.in") - itf, err := UnmarshalPacket(data) - if err != nil { + // 0. Check the packet integrity + if len(req.Payload) == 0 { + stats.MarkMeter("handler.uplink.invalid") + return nil, errors.New(errors.Structural, "Invalid Packet Payload") + } + if len(req.DevEUI) != 8 { + stats.MarkMeter("handler.uplink.invalid") + return nil, errors.New(errors.Structural, "Invalid Device EUI") + } + if len(req.AppEUI) != 8 { stats.MarkMeter("handler.uplink.invalid") - return errors.New(errors.Structural, err) + return nil, errors.New(errors.Structural, "Invalid Application EUI") } + if req.Metadata == nil { + stats.MarkMeter("handler.uplink.invalid") + return nil, errors.New(errors.Structural, "Missing Mandatory Metadata") + } + stats.MarkMeter("handler.uplink.data") - switch itf.(type) { - case HPacket: - stats.MarkMeter("handler.uplink.data") + // 1. Lookup for the associated AppSKey + Application + h.ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") + entry, err := h.devices.Lookup(req.AppEUI, req.DevEUI) + if err != nil { + return nil, err + } - // 0. Retrieve the handler packet - packet := itf.(HPacket) - appEUI := packet.AppEUI() - devEUI := packet.DevEUI() + // 2. Prepare a channel to receive the response from the consumer + chresp := make(chan interface{}) + + // 3. Create a "bundle" which holds info waiting for other related packets + var bundleID [20]byte // AppEUI(8) | DevEUI(8) | FCnt + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, req.AppEUI) + binary.Write(buf, binary.BigEndian, req.DevEUI) + binary.Write(buf, binary.BigEndian, req.FCnt) + data := buf.Bytes() + if len(data) != 20 { + return nil, errors.New(errors.Structural, "Unable to generate bundleID") + } + copy(bundleID[:], data[:]) + + // 4. Send the actual bundle to the consumer + ctx := h.ctx.WithField("BundleID", bundleID) + ctx.Debug("Define new bundle") + h.set <- bundle{ + ID: bundleID, + Packet: *req, + Entry: entry, + Chresp: chresp, + Time: time.Now(), + } - // 1. Lookup for the associated AppSKey + Recipient - h.ctx.WithField("appEUI", appEUI).WithField("devEUI", devEUI).Debug("Perform lookup") - entry, err := h.devices.Lookup(appEUI, devEUI) - if err != nil { - return err - } + // 5. Wait for the response. Could be an error, a packet or nothing. + // We'll respond to a maximum of one node. The handler will use the + // rssi + gateway's duty cycle to select to best fit. + // All other channels will get a nil response. + // If there's an error, all channels get the error. + resp := <-chresp + switch resp.(type) { + case *core.DataUpHandlerRes: + stats.MarkMeter("handler.uplink.ack.with_response") + stats.MarkMeter("handler.downlink.out") + ctx.Debug("Sending downlink packet as response.") + return resp.(*core.DataUpHandlerRes), nil + case error: + stats.MarkMeter("handler.uplink.error") + ctx.WithError(resp.(error)).Warn("Error while processing dowlink.") + return nil, resp.(error) + default: + stats.MarkMeter("handler.uplink.ack.without_response") + ctx.Debug("No response to send.") + return nil, nil + } +} - // 2. Prepare a channel to receive the response from the consumer - chresp := make(chan interface{}) - - // 3. Create a "bundle" which holds info waiting for other related packets - var bundleID [20]byte // AppEUI(8) | DevEUI(8) | FCnt - buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, appEUI[:]) - binary.Write(buf, binary.BigEndian, devEUI[:]) - binary.Write(buf, binary.BigEndian, packet.FCnt()) - data := buf.Bytes() - if len(data) != 20 { - return errors.New(errors.Structural, "Unable to generate bundleID") - } - copy(bundleID[:], data[:]) - - // 4. Send the actual bundle to the consumer - ctx := h.ctx.WithField("BundleID", bundleID) - ctx.Debug("Define new bundle") - h.set <- bundle{ - ID: bundleID, - Packet: packet, - Entry: entry, - Adapter: up, - Chresp: chresp, - Time: time.Now(), - } +// consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) +// It then flushes them once a given delay has passed since the reception of the first bundle. +func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { + ctx := h.ctx.WithField("goroutine", "set consumer") + ctx.Debug("Starting packets buffering") - // 5. Wait for the response. Could be an error, a packet or nothing. - // We'll respond to a maximum of one node. The handler will use the - // rssi + gateway's duty cycle to select to best fit. - // All other channels will get a nil response. - // If there's an error, all channels get the error. - resp := <-chresp - switch resp.(type) { - case BPacket: - stats.MarkMeter("handler.uplink.ack.with_response") - stats.MarkMeter("handler.downlink.out") - ctx.Debug("Received response with packet. Sending Ack") - ack = resp.(Packet) - case error: - stats.MarkMeter("handler.uplink.error") - ctx.WithError(resp.(error)).Warn("Received errored response. Sending Ack") - return resp.(error) - default: - stats.MarkMeter("handler.uplink.ack.without_response") - ctx.Debug("Received empty response. Sending empty Ack") - } + // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture + // with a ttl for each entry. Processed is merely there to avoid late packets from being + // processed again. The TTL could be only of several seconds or minutes. + processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? + buffers := make(map[[20]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles + alarm := make(chan [20]byte) // Communication channel with subsequent alarms - return nil - case JPacket: - stats.MarkMeter("handler.uplink.join_request") - return errors.New(errors.Implementation, "Join Request not yet implemented") - default: - stats.MarkMeter("handler.uplink.unknown") - return errors.New(errors.Implementation, "Unhandled packet type") + for { + select { + case id := <-alarm: + // Get all bundles + bundles := buffers[id] + delete(buffers, id) + + // Register the last processed entry + var pid [16]byte + copy(pid[:], id[:16]) + processed[pid] = id[16:] + + // Actually send the bundle to the be processed + go func(bundles []bundle) { chbundles <- bundles }(bundles) + ctx.WithField("BundleID", id).Debug("Consuming collected bundles") + case b := <-chset: + ctx = ctx.WithField("BundleID", b.ID) + + // Check if bundle has already been processed + var pid [16]byte + copy(pid[:], b.ID[:16]) + if reflect.DeepEqual(processed[pid], b.ID[16:]) { + ctx.Debug("Reject already processed bundle") + go func(b bundle) { + b.Chresp <- errors.New(errors.Behavioural, "Already processed") + }(b) + continue + } + + // Add the bundle to the stack, and set the alarm if its the first + bundles := append(buffers[b.ID], b) + if len(bundles) == 1 { + go setAlarm(alarm, b.ID, bufferDelay) + ctx.Debug("Buffering started -> new alarm set") + } + buffers[b.ID] = bundles + } } } +// setAlarm will trigger a message on the given channel after the given delay +func setAlarm(alarm chan<- [20]byte, id [20]byte, delay time.Duration) { + <-time.After(delay) + alarm <- id +} + // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, // deduplicate them, and send a single enhanced packet to the upadapter for further processing. func (h component) consumeBundles(chbundle <-chan []bundle) { @@ -177,7 +302,7 @@ func (h component) consumeBundles(chbundle <-chan []bundle) { browseBundles: for bundles := range chbundle { ctx.WithField("BundleID", bundles[0].ID).Debug("Consume new bundle") - var metadata []Metadata + var metadata []*core.Metadata var payload []byte var firstTime time.Time @@ -185,9 +310,8 @@ browseBundles: continue browseBundles } b := bundles[0] - h.ctx.WithField("Metadata", b.Packet.Metadata()).Debug("Considering first packet") - computer, scores, err := dutycycle.NewScoreComputer(b.Packet.Metadata().Datr) + computer, scores, err := dutycycle.NewScoreComputer(b.Packet.Metadata.DataRate) // Nil check already done if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -201,7 +325,13 @@ browseBundles: // metadata from other bundle. if i == 0 { var err error - payload, err = bundle.Packet.Payload(bundle.Entry.AppSKey) + payload, err = lorawan.DecryptFRMPayload( + bundle.Packet.Payload, + lorawan.DevAddr(bundle.Entry.DevAddr), + bundle.Packet.FCnt, + true, + bundle.Entry.AppSKey, + ) if err != nil { go h.abortConsume(err, bundles) continue browseBundles @@ -214,59 +344,53 @@ browseBundles: } // Append metadata for each of them - metadata = append(metadata, bundle.Packet.Metadata()) - scores = computer.Update(scores, i, bundle.Packet.Metadata()) + metadata = append(metadata, bundle.Packet.Metadata) + scores = computer.Update(scores, i, *bundle.Packet.Metadata) // Nil check already done } - // Then create an application-level packet - packet, err := NewAPacket(b.Packet.AppEUI(), b.Packet.DevEUI(), payload, metadata) - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } - - // And send it to the wild open + // Then create an application-level packet and send it to the wild open // we don't expect a response from the adapter, end of the chain. - recipient, err := b.Adapter.GetRecipient(b.Entry.Recipient) + _, err = h.adapter.HandleData(context.Background(), &core.DataAppReq{ + DevEUI: b.Packet.DevEUI, + Payload: payload, + Metadata: metadata, + }) if err != nil { go h.abortConsume(err, bundles) continue browseBundles } - _, err = b.Adapter.Send(packet, recipient) - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } stats.MarkMeter("handler.uplink.out") // Now handle the downlink and respond to node h.ctx.Debug("Looking for downlink response") best := computer.Get(scores) h.ctx.WithField("Bundle", best).Debug("Determine best gateway") - var down APacket + var downlink []byte if best != nil { // Avoid pulling when there's no gateway available for an answer - down, err = h.packets.Pull(b.Packet.AppEUI(), b.Packet.DevEUI()) + downlink, err = h.packets.Pull(b.Packet.AppEUI, b.Packet.DevEUI) } if err != nil && err.(errors.Failure).Nature != errors.NotFound { go h.abortConsume(err, bundles) continue browseBundles } - h.ctx.WithField("Packet", down).Debug("Pull downlink from storage") + + // One of those bundle might be available for a response for i, bundle := range bundles { - if best != nil && best.ID == i && down != nil && err == nil { + if best != nil && best.ID == i && downlink != nil && err == nil { stats.MarkMeter("handler.downlink.pull") - bpacket, err := h.buildDownlink(down, bundle.Packet, bundle.Entry, best.IsRX2) + downlink, err := h.buildDownlink(downlink, bundle.Packet, bundle.Entry, best.IsRX2) if err != nil { go h.abortConsume(errors.New(errors.Structural, err), bundles) continue browseBundles } - if err := h.devices.UpdateFCnt(b.Packet.AppEUI(), b.Packet.DevEUI(), bpacket.FCnt()); err != nil { + err = h.devices.UpdateFCnt(b.Packet.AppEUI, b.Packet.DevEUI, downlink.Payload.MACPayload.FHDR.FCnt) + if err != nil { go h.abortConsume(errors.New(errors.Structural, err), bundles) continue browseBundles } - bundle.Chresp <- bpacket + bundle.Chresp <- downlink } else { bundle.Chresp <- nil } @@ -285,19 +409,22 @@ func (h component) abortConsume(err error, bundles []bundle) { // constructs a downlink packet from something we pulled from the gathered downlink, and, the actual // uplink. -func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 bool) (BPacket, error) { - macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ +func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { + macpayload := lorawan.NewMACPayload(false) + macpayload.FHDR = lorawan.FHDR{ FCnt: entry.FCntDown + 1, DevAddr: entry.DevAddr, } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: down.Payload(), - }} + macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} - if err := macPayload.EncryptFRMPayload(entry.AppSKey); err != nil { - return nil, err + if err := macpayload.EncryptFRMPayload(entry.AppSKey); err != nil { + return nil, errors.New(errors.Structural, err) + } + + frmpayload, err := macpayload.FRMPayload[0].MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) } payload := lorawan.NewPHYPayload(false) @@ -305,130 +432,50 @@ func (h component) buildDownlink(down APacket, up HPacket, entry devEntry, isRX2 MType: lorawan.UnconfirmedDataDown, // TODO Handle Confirmed data down Major: lorawan.LoRaWANR1, } - payload.MACPayload = macPayload + payload.MACPayload = macpayload data, err := payload.MarshalBinary() if err != nil { - return nil, err + return nil, errors.New(errors.Structural, err) } - pmetadata := up.Metadata() - if pmetadata.Tmst == nil || pmetadata.Freq == nil || pmetadata.Codr == nil || pmetadata.Datr == nil { - return nil, errors.New(errors.Structural, "Missing mandatory metadata in uplink packet") - } - - metadata := Metadata{ - Freq: pmetadata.Freq, - Codr: pmetadata.Codr, - Datr: pmetadata.Datr, - Size: pointer.Uint(uint(len(data))), - Tmst: pointer.Uint(*pmetadata.Tmst + 1000), + metadata := core.Metadata{ + Frequency: up.Metadata.Frequency, + CodingRate: up.Metadata.CodingRate, + DataRate: up.Metadata.DataRate, + PayloadSize: uint32(len(data)), + Timestamp: up.Metadata.Timestamp + 1000, } if isRX2 { // Should we reply on RX2, metadata aren't the same // TODO Handle different regions with non hard-coded values - metadata.Freq = pointer.Float64(869.5) - metadata.Datr = pointer.String("SF9BW125") - metadata.Tmst = pointer.Uint(*pmetadata.Tmst + 2000) - } - - return NewBPacket(payload, metadata) -} - -// consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) -// It then flushes them once a given delay has passed since the reception of the first bundle. -func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { - ctx := h.ctx.WithField("goroutine", "set consumer") - ctx.Debug("Starting packets buffering") - - // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture - // with a ttl for each entry. Processed is merely there to avoid late packets from being - // processed again. The TTL could be only of several seconds or minutes. - processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? - buffers := make(map[[20]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles - alarm := make(chan [20]byte) // Communication channel with subsequent alarms - - for { - select { - case id := <-alarm: - // Get all bundles - bundles := buffers[id] - delete(buffers, id) - - // Register the last processed entry - var pid [16]byte - copy(pid[:], id[:16]) - processed[pid] = id[16:] - - // Actually send the bundle to the be processed - go func(bundles []bundle) { chbundles <- bundles }(bundles) - ctx.WithField("BundleID", id).Debug("Consuming collected bundles") - case b := <-chset: - ctx = ctx.WithField("BundleID", b.ID) - - // Check if bundle has already been processed - var pid [16]byte - copy(pid[:], b.ID[:16]) - if reflect.DeepEqual(processed[pid], b.ID[16:]) { - ctx.Debug("Reject already processed bundle") - go func(b bundle) { - b.Chresp <- errors.New(errors.Behavioural, "Already processed") - }(b) - continue - } - - // Add the bundle to the stack, and set the alarm if its the first - bundles := append(buffers[b.ID], b) - if len(bundles) == 1 { - go setAlarm(alarm, b.ID, bufferDelay) - ctx.Debug("Buffering started -> new alarm set") - } - buffers[b.ID] = bundles - } - } -} - -// setAlarm will trigger a message on the given channel after the given delay -func setAlarm(alarm chan<- [20]byte, id [20]byte, delay time.Duration) { - <-time.After(delay) - alarm <- id -} - -// HandleDown implements the core.Component interface -func (h component) HandleDown(data []byte, an AckNacker, down Adapter) (err error) { - // Make sure we don't forget the AckNacker - var ack Packet - defer ensureAckNack(an, &ack, &err) - stats.MarkMeter("handler.downlink.in") - - h.ctx.Debug("Handle downlink message") - - // Unmarshal the given packet and see what gift we get - itf, err := UnmarshalPacket(data) - if err != nil { - stats.MarkMeter("handler.downlink.invalid") - return errors.New(errors.Structural, err) + metadata.Frequency = 869.50 + metadata.DataRate = "SF9BW125" + metadata.Timestamp = up.Metadata.Timestamp + 2000 } - switch itf.(type) { - case APacket: - apacket := itf.(APacket) - h.ctx.WithField("DevEUI", apacket.DevEUI()).WithField("AppEUI", apacket.AppEUI()).Debug("Save downlink for later") - return h.packets.Push(apacket) - default: - stats.MarkMeter("handler.downlink.invalid") - return errors.New(errors.Implementation, "Unhandled packet type") - } -} - -func ensureAckNack(an AckNacker, ack *Packet, err *error) { - if err != nil && *err != nil { - an.Nack(*err) - } else { - var p Packet - if ack != nil { - p = *ack - } - an.Ack(p) - } + return &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + }, + }, + FPort: uint32(macpayload.FPort), + FRMPayload: frmpayload, + }, + MIC: payload.MIC[:], + }, + Metadata: &metadata, + }, nil } diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go deleted file mode 100644 index 5cfc80351..000000000 --- a/core/components/handler/handler_test.go +++ /dev/null @@ -1,955 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestRegister(t *testing.T) { - { - Desc(t, "Register valid HRegistration") - - // Build - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - r := NewMockHRegistration() - broker := NewMockJSONRecipient() - br := NewMockBRegistration() - br.OutRecipient = broker - br.OutDevEUI = r.DevEUI() - br.OutAppEUI = r.AppEUI() - br.OutNwkSKey = r.NwkSKey() - sub := NewMockSubscriber() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.Register(r, an, sub) - - // Check - CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, r, devStorage.InStorePersonalized) - CheckSubscriptions(t, br, sub.InSubscribeRegistration) - } - - // -------------------- - - { - Desc(t, "Register invalid HRegistration") - - // Build - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - broker := NewMockJSONRecipient() - sub := NewMockSubscriber() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.Register(nil, an, sub) - - // Checks - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckSubscriptions(t, nil, sub.InSubscribeRegistration) - } - - // -------------------- - - { - Desc(t, "Register valid HRegistration | devStorage fails") - - // Build - devStorage := newMockDevStorage() - devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - r := NewMockHRegistration() - broker := NewMockJSONRecipient() - sub := NewMockSubscriber() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.Register(r, an, sub) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, r, devStorage.InStorePersonalized) - } -} - -func TestHandleDown(t *testing.T) { - { - Desc(t, "Handle downlink APacket") - - // Build - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - adapter := NewMockAdapter() - pkt, _ := NewAPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - []byte("TheThingsNetwork"), - []Metadata{}, - ) - data, _ := pkt.MarshalBinary() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleDown(data, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckPushed(t, pkt, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, true, an.InAck) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle downlink wrong data") - - // Build - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - adapter := NewMockAdapter() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleDown([]byte{1, 2, 3}, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle downlink wrong packet type") - - // Build - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - adapter := NewMockAdapter() - pkt := NewJPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - [2]byte{14, 42}, - Metadata{}, - ) - data, _ := pkt.MarshalBinary() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleDown(data, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Implementation)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } -} - -func TestHandleUp(t *testing.T) { - { - Desc(t, "Handle uplink with 1 packet | No Associated App") - - // Build - devStorage := newMockDevStorage() - devStorage.Failures["Lookup"] = errors.New(errors.NotFound, "Mock: Not Found") - pktStorage := newMockPktStorage() - pktStorage.Failures["Pull"] = errors.New(errors.NotFound, "Mock: Not Found") - an := NewMockAckNacker() - adapter := NewMockAdapter() - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } - - { - Desc(t, "Handle uplink with invalid data") - - // Build - devStorage := newMockDevStorage() - pktStorage := newMockPktStorage() - an := NewMockAckNacker() - adapter := NewMockAdapter() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp([]byte{1, 2, 3}, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | No downlink ready") - - // Build - an := NewMockAckNacker() - adapter := NewMockAdapter() - dataRecipient, _ := adapter.OutGetRecipient.MarshalBinary() - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, true, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{adapter.OutGetRecipient}, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 2 packets in a row | No downlink ready") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - - // First Packet - adapter1 := NewMockAdapter() - adapter1.OutGetRecipient = recipient - an1 := NewMockAckNacker() - inPkt1 := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - DutyRX1: pointer.Uint(uint(dutycycle.StateWarning)), - DutyRX2: pointer.Uint(uint(dutycycle.StateWarning)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn1, _ := inPkt1.MarshalBinary() - - // Second Packet - adapter2 := NewMockAdapter() - adapter2.OutGetRecipient = recipient - an2 := NewMockAckNacker() - inPkt2 := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn2, _ := inPkt2.MarshalBinary() - - // Expected Sent - pktSent, _ := NewAPacket( - inPkt1.AppEUI(), - inPkt1.DevEUI(), - []byte("Payload"), - []Metadata{inPkt1.Metadata(), inPkt2.Metadata()}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - cherr := make(chan bool) - go func() { - var ok bool - defer func(ok *bool) { cherr <- *ok }(&ok) - err := handler.HandleUp(dataIn1, an1, adapter1) - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an1.InAck) - CheckSent(t, pktSent, adapter1.InSendPacket) // We actually transfer to the first bundle - CheckRecipients(t, []Recipient{recipient}, adapter1.InSendRecipients) - ok = true - }() - - go func() { - <-time.After(time.Millisecond * 50) - var ok bool - defer func(ok *bool) { cherr <- *ok }(&ok) - err := handler.HandleUp(dataIn2, an2, adapter2) - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an2.InAck) - CheckSent(t, nil, adapter2.InSendPacket) - CheckRecipients(t, nil, adapter2.InSendRecipients) - ok = true - }() - - // Check - ok1 := <-cherr - ok2 := <-cherr - if !(ok1 && ok2) { - return - } - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | One downlink response") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutGetRecipient = recipient - tmst := time.Now() - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - Freq: pointer.Float64(865.5), - Tmst: pointer.Uint(uint(tmst.Unix() * 1000)), - Codr: pointer.String("4/5"), - DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - brkResp := newBPacket( - [4]byte{2, 2, 2, 2}, - "Downlink", - Metadata{ - Datr: pointer.String("SF7BW125"), - Freq: pointer.Float64(865.5), - Tmst: pointer.Uint(uint(tmst.Add(time.Second).Unix() * 1000)), - Codr: pointer.String("4/5"), - Size: pointer.Uint(21), - }, - 4, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - appResp, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Downlink"), - []Metadata{}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - FCntDown: 3, - } - pktStorage := newMockPktStorage() - pktStorage.OutPull = appResp - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, brkResp, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) - } - - // --------------- - - { - Desc(t, "Handle a late uplink | No downlink ready") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an2 := NewMockAckNacker() - an1 := NewMockAckNacker() - adapter1 := NewMockAdapter() - adapter1.OutGetRecipient = recipient - adapter2 := NewMockAdapter() - adapter2.OutGetRecipient = recipient - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - cherr := make(chan bool) - go func() { - var ok bool - defer func(ok *bool) { cherr <- *ok }(&ok) - err := handler.HandleUp(dataIn, an1, adapter1) - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an1.InAck) - CheckSent(t, pktSent, adapter1.InSendPacket) - ok = true - }() - go func() { - var ok bool - defer func(ok *bool) { cherr <- *ok }(&ok) - <-time.After(2 * bufferDelay) - err := handler.HandleUp(dataIn, an2, adapter2) - // Check - CheckErrors(t, pointer.String(string(errors.Behavioural)), err) - CheckAcks(t, false, an2.InAck) - CheckSent(t, nil, adapter2.InSendPacket) - ok = true - }() - - // Check - if !(<-cherr && <-cherr) { - return - } - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail sending ") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutGetRecipient = recipient - adapter.Failures["Send"] = errors.New(errors.Operational, "Mock Error: Unable to send") - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | No downlink ready | Adapter fail GetRecipient") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutGetRecipient = recipient - adapter.Failures["GetRecipient"] = errors.New(errors.Structural, "Mock Error: Unable to get recipient") - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, nil, adapter.InSendPacket) - CheckRecipients(t, nil, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | No downlink ready | PktStorage fails to pull") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutGetRecipient = recipient - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - Datr: pointer.String("SF7BW125"), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - pktStorage.Failures["Pull"] = errors.New(errors.Operational, "Mock Error: Failed to Pull") - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 2 different packets from same app | No downlink ready") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - - // First Packet - adapter1 := NewMockAdapter() - adapter1.OutGetRecipient = recipient - an1 := NewMockAckNacker() - inPkt1 := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "PayloadPacket1", - Metadata{ - Datr: pointer.String("SF7BW125"), - DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn1, _ := inPkt1.MarshalBinary() - - // Second Packet - adapter2 := NewMockAdapter() - adapter2.OutGetRecipient = recipient - an2 := NewMockAckNacker() - inPkt2 := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "PayloadPacket2", - Metadata{ - Datr: pointer.String("SF7BW125"), - DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 11, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn2, _ := inPkt2.MarshalBinary() - - // Expected responses - pktSent1, _ := NewAPacket( - inPkt1.AppEUI(), - inPkt1.DevEUI(), - []byte("PayloadPacket1"), - []Metadata{inPkt1.Metadata()}, - ) - - pktSent2, _ := NewAPacket( - inPkt1.AppEUI(), - inPkt1.DevEUI(), - []byte("PayloadPacket2"), - []Metadata{inPkt2.Metadata()}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - } - pktStorage := newMockPktStorage() - broker := NewMockJSONRecipient() - - // Operate #1 - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn1, an1, adapter1) - - // Check #1 - CheckErrors(t, nil, err) - CheckAcks(t, true, an1.InAck) - CheckSent(t, pktSent1, adapter1.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter1.InSendRecipients) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - - // Operate #2 - <-time.After(150 * time.Millisecond) - err = handler.HandleUp(dataIn2, an2, adapter2) - - // Check - CheckErrors(t, nil, err) - CheckAcks(t, true, an2.InAck) - CheckSent(t, pktSent2, adapter2.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter2.InSendRecipients) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | One downlink response, mising metadata in uplink") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutGetRecipient = recipient - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - Freq: pointer.Float64(865.5), - DutyRX1: pointer.Uint(uint(dutycycle.StateAvailable)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - appResp, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Downlink"), - []Metadata{}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - FCntDown: 3, - } - pktStorage := newMockPktStorage() - pktStorage.OutPull = appResp - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, false, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) - } - - // -------------------- - - { - Desc(t, "Handle uplink with 1 packet | One downlink response | Only RX2 available") - - // Build - recipient := NewMockJSONRecipient() - dataRecipient, _ := recipient.MarshalBinary() - an := NewMockAckNacker() - adapter := NewMockAdapter() - adapter.OutGetRecipient = recipient - tmst := time.Now() - inPkt := newHPacket( - [8]byte{1, 1, 1, 1, 1, 1, 1, 1}, - [8]byte{2, 2, 2, 2, 2, 2, 2, 2}, - "Payload", - Metadata{ - Datr: pointer.String("SF7BW125"), - Freq: pointer.Float64(865.5), - Tmst: pointer.Uint(uint(tmst.Unix() * 1000)), - Codr: pointer.String("4/5"), - DutyRX1: pointer.Uint(uint(dutycycle.StateBlocked)), - DutyRX2: pointer.Uint(uint(dutycycle.StateAvailable)), - Rssi: pointer.Int(-20), - Lsnr: pointer.Float64(5.0), - }, - 10, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - dataIn, _ := inPkt.MarshalBinary() - pktSent, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Payload"), - []Metadata{inPkt.Metadata()}, - ) - brkResp := newBPacket( - [4]byte{2, 2, 2, 2}, - "Downlink", - Metadata{ - Datr: pointer.String("SF9BW125"), - Freq: pointer.Float64(869.5), - Tmst: pointer.Uint(uint(tmst.Add(2*time.Second).Unix() * 1000)), - Codr: pointer.String("4/5"), - Size: pointer.Uint(21), - }, - 15, - [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - ) - appResp, _ := NewAPacket( - inPkt.AppEUI(), - inPkt.DevEUI(), - []byte("Downlink"), - []Metadata{}, - ) - - devStorage := newMockDevStorage() - devStorage.OutLookup = devEntry{ - Recipient: dataRecipient, - DevAddr: lorawan.DevAddr([4]byte{2, 2, 2, 2}), - AppSKey: [16]byte{1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2}, - NwkSKey: [16]byte{4, 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3}, - FCntDown: 14, - } - pktStorage := newMockPktStorage() - pktStorage.OutPull = appResp - broker := NewMockJSONRecipient() - - // Operate - handler := New(devStorage, pktStorage, broker, GetLogger(t, "Handler")) - err := handler.HandleUp(dataIn, an, adapter) - - // Check - CheckErrors(t, nil, err) - CheckPushed(t, nil, pktStorage.InPush) - CheckPersonalized(t, nil, devStorage.InStorePersonalized) - CheckAcks(t, brkResp, an.InAck) - CheckSent(t, pktSent, adapter.InSendPacket) - CheckRecipients(t, []Recipient{recipient}, adapter.InSendRecipients) - } -} diff --git a/core/components/handler/helpers_test.go b/core/components/handler/helpers_test.go deleted file mode 100644 index cfe377539..000000000 --- a/core/components/handler/helpers_test.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "testing" - - . "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/brocaar/lorawan" -) - -// ----- BUILD utilities -func newHPacket(appEUI [8]byte, devEUI [8]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) HPacket { - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - FCnt: fcnt, - } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - - var key lorawan.AES128Key - copy(key[:], appSKey[:]) - if err := macPayload.EncryptFRMPayload(key); err != nil { - panic(err) - } - - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - - var appEUIp lorawan.EUI64 - var devEUIp lorawan.EUI64 - copy(appEUIp[:], appEUI[:]) - copy(devEUIp[:], devEUI[:]) - - packet, err := NewHPacket(appEUIp, devEUIp, phyPayload, metadata) - if err != nil { - panic(err) - } - return packet -} - -func newBPacket(rawDevAddr [4]byte, payload string, metadata Metadata, fcnt uint32, appSKey [16]byte) BPacket { - var devAddr lorawan.DevAddr - copy(devAddr[:], rawDevAddr[:]) - - macPayload := lorawan.NewMACPayload(false) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: devAddr, - FCnt: fcnt, - } - macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(payload)}} - - var key lorawan.AES128Key - copy(key[:], appSKey[:]) - if err := macPayload.EncryptFRMPayload(key); err != nil { - panic(err) - } - - phyPayload := lorawan.NewPHYPayload(false) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - - packet, err := NewBPacket(phyPayload, metadata) - if err != nil { - panic(err) - } - return packet - -} - -// ----- CHECK utilities -func CheckPushed(t *testing.T, want APacket, got APacket) { - Check(t, want, got, "Pushed") -} - -func CheckPersonalized(t *testing.T, want HRegistration, got HRegistration) { - Check(t, want, got, "Personalized") -} - -func CheckPackets(t *testing.T, want APacket, got APacket) { - Check(t, want, got, "Packets") -} - -func CheckEntries(t *testing.T, want MockHRegistration, got devEntry) { - // NOTE This only works in the case of Personalized devices - var devAddr lorawan.DevAddr - devEUI := want.DevEUI() - copy(devAddr[:], devEUI[4:]) - - wantEntry := devEntry{ - Recipient: want.RawRecipient(), - DevAddr: devAddr, - NwkSKey: want.NwkSKey(), - AppSKey: want.AppSKey(), - } - - Check(t, wantEntry, got, "Entries") -} - -func CheckSubscriptions(t *testing.T, want BRegistration, got Registration) { - var mockGot BRegistration - bgot, ok := got.(BRegistration) - if got != nil && ok { - r := NewMockBRegistration() - r.OutRecipient = bgot.Recipient() - r.OutDevEUI = bgot.DevEUI() - r.OutAppEUI = bgot.AppEUI() - r.OutNwkSKey = bgot.NwkSKey() - mockGot = r - } - Check(t, want, mockGot, "Subscriptions") -} diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go deleted file mode 100644 index 8e46ff8a5..000000000 --- a/core/components/handler/mocks_test.go +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - . "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -// mockDevStorage implements the handler.DevStorage interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type mockDevStorage struct { - Failures map[string]error - OutLookup devEntry - InLookupAppEUI lorawan.EUI64 - InLookupDevEUI lorawan.EUI64 - InStorePersonalized HRegistration - InStoreActivated HRegistration - InUpdateAppEUI lorawan.EUI64 - InUpdateDevEUI lorawan.EUI64 - InUpdateFcnt uint32 -} - -func newMockDevStorage() *mockDevStorage { - return &mockDevStorage{ - Failures: make(map[string]error), - OutLookup: devEntry{ - Recipient: []byte("MockDevStorageRecipient"), - DevAddr: lorawan.DevAddr([4]byte{9, 9, 1, 4}), - AppSKey: lorawan.AES128Key([16]byte{6, 6, 4, 3, 2, 2, 0, 9, 8, 7, 6, 3, 1, 9, 6, 14}), - NwkSKey: lorawan.AES128Key([16]byte{7, 2, 3, 3, 5, 6, 7, 0, 9, 0, 1, 2, 7, 4, 5, 5}), - }, - } -} - -func (s *mockDevStorage) Lookup(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (devEntry, error) { - s.InLookupAppEUI = appEUI - s.InLookupDevEUI = devEUI - if s.Failures["Lookup"] != nil { - return devEntry{}, s.Failures["Lookup"] - } - return s.OutLookup, nil -} - -func (s *mockDevStorage) UpdateFCnt(appEUI lorawan.EUI64, devEUI lorawan.EUI64, fcnt uint32) error { - s.InUpdateAppEUI = appEUI - s.InUpdateDevEUI = devEUI - s.InUpdateFcnt = fcnt - return s.Failures["UpdateFCnt"] -} - -func (s *mockDevStorage) StorePersonalized(r HRegistration) error { - s.InStorePersonalized = r - return s.Failures["StorePersonalized"] -} - -func (s *mockDevStorage) StoreActivated(r HRegistration) error { - s.InStoreActivated = r - return s.Failures["StoreActivated"] -} - -func (s *mockDevStorage) Close() error { - return s.Failures["Close"] -} - -// mockPktStorage implements the handler.PktStorage interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type mockPktStorage struct { - Failures map[string]error - OutPull APacket - InPullAppEUI lorawan.EUI64 - InPullDevEUI lorawan.EUI64 - InPush APacket -} - -func newMockPktStorage() *mockPktStorage { - return &mockPktStorage{ - Failures: make(map[string]error), - } -} - -func (s *mockPktStorage) Push(p APacket) error { - s.InPush = p - return s.Failures["Push"] -} - -func (s *mockPktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { - s.InPullAppEUI = appEUI - s.InPullDevEUI = devEUI - if s.Failures["Pull"] != nil { - return nil, s.Failures["Pull"] - } - return s.OutPull, nil -} - -func (s *mockPktStorage) Close() error { - return s.Failures["Close"] -} diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index bc8df6751..964cad3ee 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -6,27 +6,24 @@ package handler import ( "fmt" - . "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" dbutil "github.com/TheThingsNetwork/ttn/utils/storage" - "github.com/brocaar/lorawan" ) // PktStorage gives a facade to manipulate the handler packets database type PktStorage interface { - Push(p APacket) error - Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) + Push(appEUI []byte, devEUI []byte, payload pktEntry) error + Pull(appEUI []byte, devEUI []byte) (pktEntry, error) Close() error } +const dbPackets = "packets" + type pktStorage struct { - db dbutil.Interface - Name string + db dbutil.Interface } -type pktEntry struct { - APacket -} +type pktEntry []byte // NewPktStorage creates a new PktStorage func NewPktStorage(name string) (PktStorage, error) { @@ -34,55 +31,48 @@ func NewPktStorage(name string) (PktStorage, error) { if err != nil { return nil, errors.New(errors.Operational, err) } - return pktStorage{db: itf, Name: "pktStorage"}, nil -} - -func keyFromEUIs(appEUI lorawan.EUI64, devEUI lorawan.EUI64) []byte { - return append(appEUI[:], devEUI[:]...) + return pktStorage{db: itf}, nil } // Push implements the PktStorage interface -func (s pktStorage) Push(p APacket) error { - err := s.db.Store(s.Name, keyFromEUIs(p.AppEUI(), p.DevEUI()), []dbutil.Entry{&pktEntry{p}}) - if err != nil { - return errors.New(errors.Operational, err) - } - return nil +func (s pktStorage) Push(appEUI, devEUI []byte, payload pktEntry) error { + return s.db.Store(dbPackets, append(appEUI, devEUI...), []dbutil.Entry{&payload}) } // Pull implements the PktStorage interface -func (s pktStorage) Pull(appEUI lorawan.EUI64, devEUI lorawan.EUI64) (APacket, error) { - key := keyFromEUIs(appEUI, devEUI) - - entries, err := s.db.Lookup(s.Name, key, &pktEntry{}) +func (s pktStorage) Pull(appEUI, devEUI []byte) (pktEntry, error) { + key := append(appEUI, devEUI...) + entries, err := s.db.Lookup(dbPackets, key, &pktEntry{}) if err != nil { return nil, err // Operational || NotFound } - packets, ok := entries.([]pktEntry) + payloads, ok := entries.([]pktEntry) if !ok { return nil, errors.New(errors.Operational, "Unable to retrieve data from db") } // NOTE: one day, those entries will be more complicated, with a ttl. // Here's the place where we should check for that. Cheers. - if len(packets) == 0 { + if len(payloads) == 0 { return nil, errors.New(errors.NotFound, fmt.Sprintf("Entry not found for %v", key)) } - pkt := packets[0] + payload := payloads[0] var newEntries []dbutil.Entry - for _, p := range packets[1:] { + for _, p := range payloads[1:] { newEntries = append(newEntries, &p) } - if err := s.db.Replace(s.Name, key, newEntries); err != nil { - // TODO This is critical... we've just lost a packet - return nil, errors.New(errors.Operational, "Unable to restore data in db") + if err := s.db.Replace(dbPackets, key, newEntries); err != nil { + if err := s.db.Replace(dbPackets, key, newEntries); err != nil { + // TODO This is critical... we've just lost a packet + return nil, errors.New(errors.Operational, "Unable to restore data in db") + } } - return pkt.APacket, nil + return payload, nil } // Close implements the PktStorage interface @@ -92,19 +82,11 @@ func (s pktStorage) Close() error { // MarshalBinary implements the encoding.BinaryMarshaler interface func (e pktEntry) MarshalBinary() ([]byte, error) { - return e.APacket.MarshalBinary() + return e, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *pktEntry) UnmarshalBinary(data []byte) error { - itf, err := UnmarshalPacket(data) - if err != nil { - return errors.New(errors.Structural, err) - } - packet, ok := itf.(APacket) - if !ok { - return errors.New(errors.Structural, "Not a Handler packet") - } - e.APacket = packet + *e = data return nil } diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go deleted file mode 100644 index f79b7f744..000000000 --- a/core/components/handler/pktStorage_test.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -const pktDB = "TestPktStorage.db" - -func TestPushPullNormal(t *testing.T) { - var db PktStorage - defer func() { - os.Remove(path.Join(os.TempDir(), pktDB)) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - var err error - db, err = NewPktStorage(path.Join(os.TempDir(), pktDB)) - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Push and Pull a valid APacket") - - // Build - p, _ := NewAPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - []byte("TheThingsNetwork"), - []Metadata{}, - ) - - // Operate - err := db.Push(p) - CheckErrors(t, nil, err) - a, err := db.Pull(p.AppEUI(), p.DevEUI()) - - // Check - CheckErrors(t, nil, err) - CheckPackets(t, p, a) - } - - // ------------------ - - { - Desc(t, "Push two packets") - - // Build - p1, _ := NewAPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - []byte("TheThingsNetwork1"), - []Metadata{}, - ) - p2, _ := NewAPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - []byte("TheThingsNetwork2"), - []Metadata{}, - ) - - // Operate & Check - err := db.Push(p1) - CheckErrors(t, nil, err) - err = db.Push(p2) - CheckErrors(t, nil, err) - - a, err := db.Pull(p1.AppEUI(), p1.DevEUI()) - CheckErrors(t, nil, err) - CheckPackets(t, p1, a) - - a, err = db.Pull(p1.AppEUI(), p1.DevEUI()) - CheckErrors(t, nil, err) - CheckPackets(t, p2, a) - } - - // ------------------ - - { - Desc(t, "Pull a non existing entry") - - // Build - appEUI := lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) - devEUI := lorawan.EUI64([8]byte{2, 3, 4, 2, 3, 4, 2, 3}) - - // Operate - p, err := db.Pull(appEUI, devEUI) - - // Check - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckPackets(t, nil, p) - } - - // ------------------ - - { - Desc(t, "Close the storage") - err := db.Close() - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Push after close") - - // Build - p, _ := NewAPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - []byte("TheThingsNetwork"), - []Metadata{}, - ) - - // Operate - err := db.Push(p) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // ------------------ - - { - Desc(t, "Pull after close") - - // Build - appEUI := lorawan.EUI64([8]byte{1, 2, 1, 2, 1, 2, 1, 2}) - devEUI := lorawan.EUI64([8]byte{2, 3, 4, 2, 3, 4, 2, 3}) - - // Operate - _, err := db.Pull(appEUI, devEUI) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } -} From 6a106fd33b7228282bdd678fb86761e9f2b432fc Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 23:06:09 +0100 Subject: [PATCH 1052/2266] [refactor/grpc] Make broker consistent with new handler --- core/components/broker/broker.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 4d93cac5d..789ab60ec 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -116,7 +116,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } defer conn.Close() handler := core.NewHandlerClient(conn) - resp, err := handler.HandleData(context.Background(), &core.DataHandlerReq{ + resp, err := handler.HandleDataUp(context.Background(), &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, DevEUI: mEntry.DevEUI, AppEUI: mEntry.AppEUI, @@ -156,7 +156,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } // Register implements the core.BrokerServer interface -func (b component) SubscribePersonalized(bctx context.Context, req *core.SubBrokerReq) (*core.SubBrokerRes, error) { +func (b component) SubscribePersonalized(bctx context.Context, req *core.SubPersoBrokerReq) (*core.SubPersoBrokerRes, error) { b.ctx.Debug("Handling personalized subscription") // Ensure the entry is valid @@ -164,9 +164,11 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.SubBrok return nil, errors.New(errors.Structural, "Invalid Application EUI") } - if len(req.DevEUI) != 8 { - return nil, errors.New(errors.Structural, "Invalid Device EUI") + if len(req.DevAddr) != 4 { + return nil, errors.New(errors.Structural, "Invalid Device Address") } + devEUI := make([]byte, 8, 8) + copy(devEUI[4:], req.DevAddr) var nwkSKey [16]byte if len(req.NwkSKey) != 16 { @@ -182,7 +184,7 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.SubBrok return nil, b.StoreDevice(devEntry{ HandlerNet: req.HandlerNet, AppEUI: req.AppEUI, - DevEUI: req.DevEUI, + DevEUI: devEUI, NwkSKey: nwkSKey, FCntUp: 0, }) From fdad038c34799e707793cbc135a11905eb3fd8b2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 15 Mar 2016 23:06:43 +0100 Subject: [PATCH 1053/2266] [refactor/grpc] Add application proto and extend rpc methods for handler --- core/application.pb.go | 561 +++++++++++++++++++++++ core/broker.pb.go | 122 ++--- core/handler.pb.go | 827 +++++++++++++++++++++++++++++++--- core/protos/application.proto | 16 + core/protos/broker.proto | 10 +- core/protos/handler.proto | 25 +- 6 files changed, 1441 insertions(+), 120 deletions(-) create mode 100644 core/application.pb.go create mode 100644 core/protos/application.proto diff --git a/core/application.pb.go b/core/application.pb.go new file mode 100644 index 000000000..271bf1831 --- /dev/null +++ b/core/application.pb.go @@ -0,0 +1,561 @@ +// Code generated by protoc-gen-gogo. +// source: application.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type DataAppReq struct { + Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + Metadata []*Metadata `protobuf:"bytes,3,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *DataAppReq) Reset() { *m = DataAppReq{} } +func (m *DataAppReq) String() string { return proto.CompactTextString(m) } +func (*DataAppReq) ProtoMessage() {} +func (*DataAppReq) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{0} } + +func (m *DataAppReq) GetMetadata() []*Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type DataAppRes struct { +} + +func (m *DataAppRes) Reset() { *m = DataAppRes{} } +func (m *DataAppRes) String() string { return proto.CompactTextString(m) } +func (*DataAppRes) ProtoMessage() {} +func (*DataAppRes) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{1} } + +func init() { + proto.RegisterType((*DataAppReq)(nil), "core.DataAppReq") + proto.RegisterType((*DataAppRes)(nil), "core.DataAppRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for App service + +type AppClient interface { + HandleData(ctx context.Context, in *DataAppReq, opts ...grpc.CallOption) (*DataAppRes, error) +} + +type appClient struct { + cc *grpc.ClientConn +} + +func NewAppClient(cc *grpc.ClientConn) AppClient { + return &appClient{cc} +} + +func (c *appClient) HandleData(ctx context.Context, in *DataAppReq, opts ...grpc.CallOption) (*DataAppRes, error) { + out := new(DataAppRes) + err := grpc.Invoke(ctx, "/core.App/HandleData", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for App service + +type AppServer interface { + HandleData(context.Context, *DataAppReq) (*DataAppRes, error) +} + +func RegisterAppServer(s *grpc.Server, srv AppServer) { + s.RegisterService(&_App_serviceDesc, srv) +} + +func _App_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DataAppReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(AppServer).HandleData(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _App_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.App", + HandlerType: (*AppServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "HandleData", + Handler: _App_HandleData_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DataAppReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataAppReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintApplication(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if len(m.Metadata) > 0 { + for _, msg := range m.Metadata { + data[i] = 0x1a + i++ + i = encodeVarintApplication(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DataAppRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataAppRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64Application(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Application(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintApplication(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DataAppReq) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) + } + } + if len(m.Metadata) > 0 { + for _, e := range m.Metadata { + l = e.Size() + n += 1 + l + sovApplication(uint64(l)) + } + } + return n +} + +func (m *DataAppRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovApplication(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozApplication(x uint64) (n int) { + return sovApplication(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DataAppReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataAppReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataAppReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = append(m.Metadata, &Metadata{}) + if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipApplication(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApplication + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DataAppRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataAppRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataAppRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipApplication(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApplication + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipApplication(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApplication + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApplication + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApplication + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthApplication + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApplication + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipApplication(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthApplication = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowApplication = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorApplication = []byte{ + // 191 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2c, 0x28, 0xc8, + 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, + 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x2c, 0x2e, 0x2e, 0x97, 0xc4, 0x92, + 0x44, 0xc7, 0x82, 0x82, 0xa0, 0xd4, 0x42, 0x21, 0x09, 0x2e, 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, + 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x9e, 0x20, 0xf6, 0x02, 0x08, 0x57, 0x48, 0x8c, 0x8b, + 0xcd, 0x25, 0xb5, 0xcc, 0x35, 0xd4, 0x53, 0x82, 0x09, 0x2c, 0xc1, 0x96, 0x02, 0xe6, 0x09, 0x69, + 0x71, 0x71, 0xf8, 0xa6, 0x96, 0x24, 0xa6, 0x00, 0xcd, 0x90, 0x60, 0x56, 0x60, 0xd6, 0xe0, 0x36, + 0xe2, 0xd3, 0x03, 0x1b, 0x0f, 0x13, 0x0d, 0xe2, 0xc8, 0x85, 0xb2, 0x94, 0x78, 0x90, 0xec, 0x2a, + 0x36, 0x32, 0xe7, 0x62, 0x06, 0xb2, 0x84, 0x0c, 0xb8, 0xb8, 0x3c, 0x12, 0xf3, 0x52, 0x72, 0x52, + 0x41, 0x52, 0x42, 0x02, 0x10, 0xcd, 0x08, 0x27, 0x49, 0xa1, 0x8b, 0x14, 0x3b, 0x09, 0x9c, 0x78, + 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0xbf, + 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8a, 0x75, 0x42, 0xb9, 0xf2, 0x00, 0x00, 0x00, +} diff --git a/core/broker.pb.go b/core/broker.pb.go index c05a0af1b..54771f930 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -8,6 +8,7 @@ It is generated from these files: broker.proto core.proto + application.proto handler.proto router.proto lorawan.proto @@ -15,12 +16,18 @@ It has these top-level messages: DataBrokerReq DataBrokerRes - SubBrokerReq - SubBrokerRes + SubPersoBrokerReq + SubPersoBrokerRes Metadata StatsMetadata - DataHandlerReq - DataHandlerRes + DataAppReq + DataAppRes + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + SubPersoHandlerReq + SubPersoHandlerRes DataRouterReq DataRouterRes StatsReq @@ -104,31 +111,31 @@ func (m *DataBrokerRes) GetMetadata() *Metadata { return nil } -type SubBrokerReq struct { +type SubPersoBrokerReq struct { HandlerNet string `protobuf:"bytes,1,opt,name=HandlerNet,json=handlerNet,proto3" json:"HandlerNet,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` } -func (m *SubBrokerReq) Reset() { *m = SubBrokerReq{} } -func (m *SubBrokerReq) String() string { return proto.CompactTextString(m) } -func (*SubBrokerReq) ProtoMessage() {} -func (*SubBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } +func (m *SubPersoBrokerReq) Reset() { *m = SubPersoBrokerReq{} } +func (m *SubPersoBrokerReq) String() string { return proto.CompactTextString(m) } +func (*SubPersoBrokerReq) ProtoMessage() {} +func (*SubPersoBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } -type SubBrokerRes struct { +type SubPersoBrokerRes struct { } -func (m *SubBrokerRes) Reset() { *m = SubBrokerRes{} } -func (m *SubBrokerRes) String() string { return proto.CompactTextString(m) } -func (*SubBrokerRes) ProtoMessage() {} -func (*SubBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } +func (m *SubPersoBrokerRes) Reset() { *m = SubPersoBrokerRes{} } +func (m *SubPersoBrokerRes) String() string { return proto.CompactTextString(m) } +func (*SubPersoBrokerRes) ProtoMessage() {} +func (*SubPersoBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } func init() { proto.RegisterType((*DataBrokerReq)(nil), "core.DataBrokerReq") proto.RegisterType((*DataBrokerRes)(nil), "core.DataBrokerRes") - proto.RegisterType((*SubBrokerReq)(nil), "core.SubBrokerReq") - proto.RegisterType((*SubBrokerRes)(nil), "core.SubBrokerRes") + proto.RegisterType((*SubPersoBrokerReq)(nil), "core.SubPersoBrokerReq") + proto.RegisterType((*SubPersoBrokerRes)(nil), "core.SubPersoBrokerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -139,7 +146,7 @@ var _ grpc.ClientConn type BrokerClient interface { HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) - SubscribePersonalized(ctx context.Context, in *SubBrokerReq, opts ...grpc.CallOption) (*SubBrokerRes, error) + SubscribePersonalized(ctx context.Context, in *SubPersoBrokerReq, opts ...grpc.CallOption) (*SubPersoBrokerRes, error) } type brokerClient struct { @@ -159,8 +166,8 @@ func (c *brokerClient) HandleData(ctx context.Context, in *DataBrokerReq, opts . return out, nil } -func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *SubBrokerReq, opts ...grpc.CallOption) (*SubBrokerRes, error) { - out := new(SubBrokerRes) +func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *SubPersoBrokerReq, opts ...grpc.CallOption) (*SubPersoBrokerRes, error) { + out := new(SubPersoBrokerRes) err := grpc.Invoke(ctx, "/core.Broker/SubscribePersonalized", in, out, c.cc, opts...) if err != nil { return nil, err @@ -172,7 +179,7 @@ func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *SubBrokerR type BrokerServer interface { HandleData(context.Context, *DataBrokerReq) (*DataBrokerRes, error) - SubscribePersonalized(context.Context, *SubBrokerReq) (*SubBrokerRes, error) + SubscribePersonalized(context.Context, *SubPersoBrokerReq) (*SubPersoBrokerRes, error) } func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { @@ -192,7 +199,7 @@ func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(i } func _Broker_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(SubBrokerReq) + in := new(SubPersoBrokerReq) if err := dec(in); err != nil { return nil, err } @@ -295,7 +302,7 @@ func (m *DataBrokerRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubBrokerReq) Marshal() (data []byte, err error) { +func (m *SubPersoBrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -305,7 +312,7 @@ func (m *SubBrokerReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *SubBrokerReq) MarshalTo(data []byte) (int, error) { +func (m *SubPersoBrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -324,12 +331,12 @@ func (m *SubBrokerReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.AppEUI) } } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { data[i] = 0x1a i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) + i = encodeVarintBroker(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } } if m.NwkSKey != nil { @@ -343,7 +350,7 @@ func (m *SubBrokerReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubBrokerRes) Marshal() (data []byte, err error) { +func (m *SubPersoBrokerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -353,7 +360,7 @@ func (m *SubBrokerRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *SubBrokerRes) MarshalTo(data []byte) (int, error) { +func (m *SubPersoBrokerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -416,7 +423,7 @@ func (m *DataBrokerRes) Size() (n int) { return n } -func (m *SubBrokerReq) Size() (n int) { +func (m *SubPersoBrokerReq) Size() (n int) { var l int _ = l l = len(m.HandlerNet) @@ -429,8 +436,8 @@ func (m *SubBrokerReq) Size() (n int) { n += 1 + l + sovBroker(uint64(l)) } } - if m.DevEUI != nil { - l = len(m.DevEUI) + if m.DevAddr != nil { + l = len(m.DevAddr) if l > 0 { n += 1 + l + sovBroker(uint64(l)) } @@ -444,7 +451,7 @@ func (m *SubBrokerReq) Size() (n int) { return n } -func (m *SubBrokerRes) Size() (n int) { +func (m *SubPersoBrokerRes) Size() (n int) { var l int _ = l return n @@ -695,7 +702,7 @@ func (m *DataBrokerRes) Unmarshal(data []byte) error { } return nil } -func (m *SubBrokerReq) Unmarshal(data []byte) error { +func (m *SubPersoBrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -718,10 +725,10 @@ func (m *SubBrokerReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SubBrokerReq: wiretype end group for non-group") + return fmt.Errorf("proto: SubPersoBrokerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SubBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: SubPersoBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -786,7 +793,7 @@ func (m *SubBrokerReq) Unmarshal(data []byte) error { iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -810,9 +817,9 @@ func (m *SubBrokerReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} } iNdEx = postIndex case 4: @@ -867,7 +874,7 @@ func (m *SubBrokerReq) Unmarshal(data []byte) error { } return nil } -func (m *SubBrokerRes) Unmarshal(data []byte) error { +func (m *SubPersoBrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -890,10 +897,10 @@ func (m *SubBrokerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SubBrokerRes: wiretype end group for non-group") + return fmt.Errorf("proto: SubPersoBrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SubBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: SubPersoBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1023,7 +1030,7 @@ var ( ) var fileDescriptorBroker = []byte{ - // 304 bytes of a gzipped FileDescriptorProto + // 310 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, @@ -1031,16 +1038,17 @@ var fileDescriptorBroker = []byte{ 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x41, 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, - 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0x55, - 0x70, 0xf1, 0x04, 0x97, 0x26, 0x21, 0xbc, 0x24, 0xc7, 0xc5, 0xe5, 0x91, 0x98, 0x97, 0x92, 0x93, - 0x5a, 0xe4, 0x97, 0x5a, 0x02, 0xb6, 0x8b, 0x33, 0x88, 0x2b, 0x03, 0x2e, 0x22, 0x24, 0xc6, 0xc5, - 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0x36, 0x99, 0x27, 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, - 0xbb, 0xa4, 0x96, 0x81, 0xc4, 0x99, 0x21, 0xe2, 0x29, 0x60, 0x9e, 0x90, 0x04, 0x17, 0xbb, 0x5f, - 0x79, 0x76, 0xb0, 0x77, 0x6a, 0xa5, 0x04, 0x0b, 0x58, 0x82, 0x3d, 0x0f, 0xc2, 0x55, 0xe2, 0x43, - 0xb1, 0xb9, 0xd8, 0xa8, 0x91, 0x91, 0x8b, 0x0d, 0xc2, 0x13, 0x32, 0x83, 0x39, 0x02, 0xe4, 0x2f, - 0x21, 0x61, 0x88, 0xe3, 0x51, 0x82, 0x5e, 0x0a, 0x8b, 0x60, 0xb1, 0x90, 0x3d, 0x97, 0x28, 0xd0, - 0xc8, 0xe2, 0xe4, 0xa2, 0xcc, 0xa4, 0xd4, 0x80, 0xd4, 0xa2, 0xe2, 0xfc, 0xbc, 0xc4, 0x9c, 0xcc, - 0xaa, 0xd4, 0x14, 0x21, 0x21, 0x88, 0x6a, 0x64, 0x9f, 0x4a, 0x61, 0x8a, 0x15, 0x3b, 0x09, 0x9c, - 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, - 0xa8, 0x37, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xba, 0x4b, 0xd2, 0x13, 0x2b, 0x02, 0x00, 0x00, + 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xd5, + 0x73, 0x09, 0x06, 0x97, 0x26, 0x05, 0xa4, 0x16, 0x15, 0xe7, 0x23, 0xfc, 0x25, 0xc7, 0xc5, 0xe5, + 0x91, 0x98, 0x97, 0x92, 0x93, 0x5a, 0xe4, 0x97, 0x5a, 0x02, 0xb6, 0x90, 0x33, 0x88, 0x2b, 0x03, + 0x2e, 0x22, 0x24, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0x36, 0x9e, 0x27, 0x88, + 0x2d, 0x11, 0xcc, 0x13, 0x92, 0xe0, 0x62, 0x77, 0x49, 0x2d, 0x73, 0x4c, 0x49, 0x29, 0x92, 0x60, + 0x06, 0x4b, 0xb0, 0xa7, 0x40, 0xb8, 0x20, 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, + 0x16, 0x88, 0x4c, 0x1e, 0x84, 0xab, 0x24, 0x8c, 0xe9, 0x80, 0x62, 0xa3, 0x6e, 0x46, 0x2e, 0x36, + 0x08, 0x4f, 0xc8, 0x0c, 0xe6, 0x16, 0x90, 0x1f, 0x85, 0x84, 0x21, 0x1e, 0x41, 0x89, 0x06, 0x29, + 0x2c, 0x82, 0xc5, 0x42, 0x9e, 0x5c, 0xa2, 0x40, 0x73, 0x8b, 0x93, 0x8b, 0x32, 0x93, 0x52, 0xc1, + 0xa6, 0xe7, 0x25, 0xe6, 0x64, 0x56, 0xa5, 0xa6, 0x08, 0x89, 0x43, 0x54, 0x63, 0xf8, 0x5a, 0x0a, + 0x87, 0x44, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, + 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x4e, 0x10, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9b, 0x59, + 0x69, 0x59, 0x41, 0x02, 0x00, 0x00, } diff --git a/core/handler.pb.go b/core/handler.pb.go index 109c45fcc..67f7bfcfc 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -20,7 +20,7 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -type DataHandlerReq struct { +type DataUpHandlerReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` AppEUI []byte `protobuf:"bytes,3,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` @@ -29,45 +29,88 @@ type DataHandlerReq struct { MType uint32 `protobuf:"varint,6,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` } -func (m *DataHandlerReq) Reset() { *m = DataHandlerReq{} } -func (m *DataHandlerReq) String() string { return proto.CompactTextString(m) } -func (*DataHandlerReq) ProtoMessage() {} -func (*DataHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } +func (m *DataUpHandlerReq) Reset() { *m = DataUpHandlerReq{} } +func (m *DataUpHandlerReq) String() string { return proto.CompactTextString(m) } +func (*DataUpHandlerReq) ProtoMessage() {} +func (*DataUpHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } -func (m *DataHandlerReq) GetMetadata() *Metadata { +func (m *DataUpHandlerReq) GetMetadata() *Metadata { if m != nil { return m.Metadata } return nil } -type DataHandlerRes struct { +type DataUpHandlerRes struct { Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } -func (m *DataHandlerRes) Reset() { *m = DataHandlerRes{} } -func (m *DataHandlerRes) String() string { return proto.CompactTextString(m) } -func (*DataHandlerRes) ProtoMessage() {} -func (*DataHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } +func (m *DataUpHandlerRes) Reset() { *m = DataUpHandlerRes{} } +func (m *DataUpHandlerRes) String() string { return proto.CompactTextString(m) } +func (*DataUpHandlerRes) ProtoMessage() {} +func (*DataUpHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } -func (m *DataHandlerRes) GetPayload() *LoRaWANData { +func (m *DataUpHandlerRes) GetPayload() *LoRaWANData { if m != nil { return m.Payload } return nil } -func (m *DataHandlerRes) GetMetadata() *Metadata { +func (m *DataUpHandlerRes) GetMetadata() *Metadata { if m != nil { return m.Metadata } return nil } +type DataDownHandlerReq struct { + Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` +} + +func (m *DataDownHandlerReq) Reset() { *m = DataDownHandlerReq{} } +func (m *DataDownHandlerReq) String() string { return proto.CompactTextString(m) } +func (*DataDownHandlerReq) ProtoMessage() {} +func (*DataDownHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{2} } + +type DataDownHandlerRes struct { +} + +func (m *DataDownHandlerRes) Reset() { *m = DataDownHandlerRes{} } +func (m *DataDownHandlerRes) String() string { return proto.CompactTextString(m) } +func (*DataDownHandlerRes) ProtoMessage() {} +func (*DataDownHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } + +type SubPersoHandlerReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` +} + +func (m *SubPersoHandlerReq) Reset() { *m = SubPersoHandlerReq{} } +func (m *SubPersoHandlerReq) String() string { return proto.CompactTextString(m) } +func (*SubPersoHandlerReq) ProtoMessage() {} +func (*SubPersoHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } + +type SubPersoHandlerRes struct { +} + +func (m *SubPersoHandlerRes) Reset() { *m = SubPersoHandlerRes{} } +func (m *SubPersoHandlerRes) String() string { return proto.CompactTextString(m) } +func (*SubPersoHandlerRes) ProtoMessage() {} +func (*SubPersoHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } + func init() { - proto.RegisterType((*DataHandlerReq)(nil), "core.DataHandlerReq") - proto.RegisterType((*DataHandlerRes)(nil), "core.DataHandlerRes") + proto.RegisterType((*DataUpHandlerReq)(nil), "core.DataUpHandlerReq") + proto.RegisterType((*DataUpHandlerRes)(nil), "core.DataUpHandlerRes") + proto.RegisterType((*DataDownHandlerReq)(nil), "core.DataDownHandlerReq") + proto.RegisterType((*DataDownHandlerRes)(nil), "core.DataDownHandlerRes") + proto.RegisterType((*SubPersoHandlerReq)(nil), "core.SubPersoHandlerReq") + proto.RegisterType((*SubPersoHandlerRes)(nil), "core.SubPersoHandlerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -77,7 +120,9 @@ var _ grpc.ClientConn // Client API for Handler service type HandlerClient interface { - HandleData(ctx context.Context, in *DataHandlerReq, opts ...grpc.CallOption) (*DataHandlerRes, error) + HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) + HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) + SubscribePersonalized(ctx context.Context, in *SubPersoHandlerReq, opts ...grpc.CallOption) (*SubPersoHandlerRes, error) } type handlerClient struct { @@ -88,9 +133,27 @@ func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { return &handlerClient{cc} } -func (c *handlerClient) HandleData(ctx context.Context, in *DataHandlerReq, opts ...grpc.CallOption) (*DataHandlerRes, error) { - out := new(DataHandlerRes) - err := grpc.Invoke(ctx, "/core.Handler/HandleData", in, out, c.cc, opts...) +func (c *handlerClient) HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) { + out := new(DataUpHandlerRes) + err := grpc.Invoke(ctx, "/core.Handler/HandleDataUp", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerClient) HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) { + out := new(DataDownHandlerRes) + err := grpc.Invoke(ctx, "/core.Handler/HandleDataDown", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *SubPersoHandlerReq, opts ...grpc.CallOption) (*SubPersoHandlerRes, error) { + out := new(SubPersoHandlerRes) + err := grpc.Invoke(ctx, "/core.Handler/SubscribePersonalized", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -100,19 +163,45 @@ func (c *handlerClient) HandleData(ctx context.Context, in *DataHandlerReq, opts // Server API for Handler service type HandlerServer interface { - HandleData(context.Context, *DataHandlerReq) (*DataHandlerRes, error) + HandleDataUp(context.Context, *DataUpHandlerReq) (*DataUpHandlerRes, error) + HandleDataDown(context.Context, *DataDownHandlerReq) (*DataDownHandlerRes, error) + SubscribePersonalized(context.Context, *SubPersoHandlerReq) (*SubPersoHandlerRes, error) } func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { s.RegisterService(&_Handler_serviceDesc, srv) } -func _Handler_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(DataHandlerReq) +func _Handler_HandleDataUp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DataUpHandlerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerServer).HandleDataUp(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _Handler_HandleDataDown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DataDownHandlerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerServer).HandleDataDown(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _Handler_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(SubPersoHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerServer).HandleData(ctx, in) + out, err := srv.(HandlerServer).SubscribePersonalized(ctx, in) if err != nil { return nil, err } @@ -124,14 +213,22 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ HandlerType: (*HandlerServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "HandleData", - Handler: _Handler_HandleData_Handler, + MethodName: "HandleDataUp", + Handler: _Handler_HandleDataUp_Handler, + }, + { + MethodName: "HandleDataDown", + Handler: _Handler_HandleDataDown_Handler, + }, + { + MethodName: "SubscribePersonalized", + Handler: _Handler_SubscribePersonalized_Handler, }, }, Streams: []grpc.StreamDesc{}, } -func (m *DataHandlerReq) Marshal() (data []byte, err error) { +func (m *DataUpHandlerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -141,7 +238,7 @@ func (m *DataHandlerReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *DataHandlerReq) MarshalTo(data []byte) (int, error) { +func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -193,7 +290,7 @@ func (m *DataHandlerReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DataHandlerRes) Marshal() (data []byte, err error) { +func (m *DataUpHandlerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -203,7 +300,7 @@ func (m *DataHandlerRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *DataHandlerRes) MarshalTo(data []byte) (int, error) { +func (m *DataUpHandlerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -231,6 +328,134 @@ func (m *DataHandlerRes) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *DataDownHandlerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + return i, nil +} + +func (m *DataDownHandlerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DataDownHandlerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *SubPersoHandlerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SubPersoHandlerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + if m.AppSKey != nil { + if len(m.AppSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) + } + } + return i, nil +} + +func (m *SubPersoHandlerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SubPersoHandlerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + func encodeFixed64Handler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -258,7 +483,7 @@ func encodeVarintHandler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } -func (m *DataHandlerReq) Size() (n int) { +func (m *DataUpHandlerReq) Size() (n int) { var l int _ = l if m.Payload != nil { @@ -292,7 +517,7 @@ func (m *DataHandlerReq) Size() (n int) { return n } -func (m *DataHandlerRes) Size() (n int) { +func (m *DataUpHandlerRes) Size() (n int) { var l int _ = l if m.Payload != nil { @@ -306,6 +531,72 @@ func (m *DataHandlerRes) Size() (n int) { return n } +func (m *DataDownHandlerReq) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + +func (m *DataDownHandlerRes) Size() (n int) { + var l int + _ = l + return n +} + +func (m *SubPersoHandlerReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.AppSKey != nil { + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + +func (m *SubPersoHandlerRes) Size() (n int) { + var l int + _ = l + return n +} + func sovHandler(x uint64) (n int) { for { n++ @@ -319,7 +610,7 @@ func sovHandler(x uint64) (n int) { func sozHandler(x uint64) (n int) { return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *DataHandlerReq) Unmarshal(data []byte) error { +func (m *DataUpHandlerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -342,10 +633,10 @@ func (m *DataHandlerReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DataHandlerReq: wiretype end group for non-group") + return fmt.Errorf("proto: DataUpHandlerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DataHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DataUpHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -533,7 +824,7 @@ func (m *DataHandlerReq) Unmarshal(data []byte) error { } return nil } -func (m *DataHandlerRes) Unmarshal(data []byte) error { +func (m *DataUpHandlerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -556,10 +847,10 @@ func (m *DataHandlerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DataHandlerRes: wiretype end group for non-group") + return fmt.Errorf("proto: DataUpHandlerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DataHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DataUpHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -649,6 +940,423 @@ func (m *DataHandlerRes) Unmarshal(data []byte) error { } return nil } +func (m *DataDownHandlerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataDownHandlerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataDownHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DataDownHandlerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DataDownHandlerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DataDownHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubPersoHandlerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubPersoHandlerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubPersoHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) + if m.AppSKey == nil { + m.AppSKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubPersoHandlerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubPersoHandlerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubPersoHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipHandler(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -755,22 +1463,31 @@ var ( ) var fileDescriptorHandler = []byte{ - // 263 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xcd, 0x48, 0xcc, 0x4b, - 0xc9, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, - 0xe2, 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, - 0xa5, 0x0d, 0x8c, 0x5c, 0x7c, 0x2e, 0x89, 0x25, 0x89, 0x1e, 0x10, 0x6d, 0x41, 0xa9, 0x85, 0x42, - 0x12, 0x5c, 0xec, 0x01, 0x89, 0x95, 0x39, 0xf9, 0x89, 0x29, 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x3c, - 0x41, 0xec, 0x05, 0x10, 0xae, 0x90, 0x16, 0x17, 0x87, 0x6f, 0x6a, 0x49, 0x62, 0x0a, 0x50, 0xbd, - 0x04, 0x13, 0x50, 0x8a, 0xdb, 0x88, 0x4f, 0x0f, 0x6c, 0x16, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, - 0x12, 0x12, 0xe3, 0x62, 0x73, 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x06, 0x1b, 0xc2, 0x96, - 0x08, 0xe6, 0x81, 0xc4, 0x5d, 0x52, 0xcb, 0x40, 0xe2, 0x2c, 0x10, 0xf1, 0x14, 0x30, 0x4f, 0x48, - 0x88, 0x8b, 0xc5, 0xcd, 0x39, 0xaf, 0x44, 0x82, 0x15, 0x28, 0xca, 0x1b, 0xc4, 0x92, 0x06, 0x64, - 0x0b, 0x89, 0x70, 0xb1, 0xfa, 0x86, 0x54, 0x16, 0xa4, 0x4a, 0xb0, 0x81, 0x05, 0x59, 0x73, 0x41, - 0x1c, 0xa5, 0x4c, 0x34, 0x17, 0x17, 0x0b, 0x69, 0xa3, 0xba, 0x98, 0xdb, 0x48, 0x10, 0xe2, 0x2c, - 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0x6a, 0xb2, 0x3c, 0x61, 0xe4, 0xcc, 0xc5, 0x0e, - 0xb5, 0x46, 0xc8, 0x82, 0x8b, 0x0b, 0xc2, 0x04, 0x99, 0x26, 0x24, 0x02, 0xd1, 0x82, 0x1a, 0x72, - 0x52, 0xd8, 0x44, 0x8b, 0x9d, 0x04, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0x00, 0xc4, 0x0f, 0x80, 0x78, - 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0x70, 0xd8, 0x1b, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xe9, - 0x40, 0x0c, 0x44, 0xad, 0x01, 0x00, 0x00, + // 404 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x4e, 0xea, 0x40, + 0x14, 0xbe, 0x85, 0xd2, 0x92, 0xe1, 0x27, 0xdc, 0x09, 0x97, 0x34, 0x5d, 0x90, 0x9b, 0xae, 0x8c, + 0x26, 0x2c, 0x70, 0x6f, 0x82, 0x56, 0xa3, 0x41, 0x08, 0x29, 0x12, 0x77, 0x26, 0x03, 0x33, 0x46, + 0x42, 0xe9, 0xd4, 0x4e, 0x95, 0xc0, 0x93, 0xf8, 0x1c, 0x3e, 0x85, 0x4b, 0x1f, 0xc0, 0x85, 0xd1, + 0x17, 0x71, 0x7e, 0x4a, 0x0a, 0x02, 0x89, 0x71, 0x31, 0xc9, 0xf9, 0xbe, 0x33, 0xe7, 0x3b, 0xdf, + 0x39, 0xd3, 0x82, 0xd2, 0x1d, 0x0a, 0xb0, 0x4f, 0xa2, 0x46, 0x18, 0xd1, 0x98, 0x42, 0x7d, 0x44, + 0x23, 0x62, 0x97, 0x7c, 0x1a, 0xa1, 0x19, 0x0a, 0x14, 0x69, 0x03, 0x41, 0xaa, 0xd8, 0x79, 0xd6, + 0x40, 0xc5, 0x45, 0x31, 0x1a, 0x84, 0xe7, 0xaa, 0xd0, 0x23, 0xf7, 0xd0, 0x02, 0x66, 0x0f, 0xcd, + 0x7d, 0x8a, 0xb0, 0xa5, 0xfd, 0xd7, 0xf6, 0x8a, 0x9e, 0x19, 0x2a, 0x08, 0xf7, 0x41, 0xbe, 0x43, + 0x62, 0x84, 0x79, 0x85, 0x95, 0xe1, 0xa9, 0x42, 0xb3, 0xdc, 0x90, 0x6a, 0x4b, 0xd6, 0xcb, 0x4f, + 0x93, 0x08, 0xd6, 0x80, 0xd1, 0x0a, 0xc3, 0xd3, 0xc1, 0x85, 0x95, 0x95, 0x22, 0x06, 0x92, 0x48, + 0xf0, 0x2e, 0x79, 0x14, 0xbc, 0xae, 0x78, 0x2c, 0x11, 0x84, 0x40, 0x3f, 0x3b, 0x09, 0x62, 0x2b, + 0xc7, 0xd9, 0x92, 0xa7, 0xdf, 0xf2, 0x18, 0x56, 0x41, 0xae, 0x73, 0x35, 0x0f, 0x89, 0x65, 0x48, + 0x32, 0x37, 0x15, 0xc0, 0x99, 0x6c, 0x78, 0x66, 0xf0, 0x60, 0xdd, 0x73, 0xa1, 0xf9, 0x57, 0x19, + 0xbb, 0xa4, 0x1e, 0xba, 0x6e, 0x75, 0xc5, 0xfd, 0x5f, 0x8d, 0xe1, 0xdc, 0x00, 0x28, 0x8a, 0x5d, + 0x3a, 0x0b, 0x7e, 0xb4, 0xa2, 0x74, 0xec, 0xcc, 0x8e, 0xb1, 0xb3, 0xab, 0x63, 0x3b, 0xd5, 0x2d, + 0xfa, 0xcc, 0x59, 0x00, 0xd8, 0x7f, 0x18, 0xf6, 0x48, 0xc4, 0xe8, 0x4a, 0xd7, 0x54, 0x5b, 0x5b, + 0xd3, 0xe6, 0x6e, 0xb8, 0x76, 0x0b, 0xe3, 0x28, 0x69, 0x6a, 0x62, 0x05, 0x45, 0xa6, 0x3b, 0x9b, + 0xf4, 0xdb, 0x64, 0x9e, 0xb4, 0x35, 0x03, 0x05, 0x45, 0x86, 0x6b, 0xc9, 0x8c, 0x7a, 0x07, 0x13, + 0x29, 0x28, 0x1c, 0x6d, 0xf4, 0x66, 0xcd, 0x37, 0x0d, 0x98, 0x09, 0x84, 0x47, 0xa0, 0xa8, 0x42, + 0xf5, 0x0c, 0xb0, 0xa6, 0xb6, 0xf7, 0xfd, 0x43, 0xb2, 0xb7, 0xf3, 0x0c, 0xba, 0xa0, 0x9c, 0xd6, + 0x8b, 0xc9, 0xa1, 0x95, 0xde, 0x5c, 0xdf, 0xb4, 0xbd, 0x2b, 0xc3, 0x60, 0x1b, 0xfc, 0xe3, 0x3e, + 0xd9, 0x28, 0x1a, 0x0f, 0x89, 0x74, 0x1b, 0x20, 0x7f, 0xbc, 0x20, 0x78, 0x29, 0xb6, 0xb9, 0x40, + 0x7b, 0x57, 0x86, 0x1d, 0x57, 0x5e, 0x3e, 0xea, 0xda, 0x2b, 0x3f, 0xef, 0xfc, 0x3c, 0x7d, 0xd6, + 0xff, 0x0c, 0x0d, 0xf9, 0x87, 0x1c, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xec, 0x22, 0xb9, + 0x53, 0x03, 0x00, 0x00, } diff --git a/core/protos/application.proto b/core/protos/application.proto new file mode 100644 index 000000000..8f9e63694 --- /dev/null +++ b/core/protos/application.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; +import "core.proto"; + +package core; + +message DataAppReq { + bytes Payload = 1; + bytes DevEUI = 2; + repeated Metadata Metadata = 3; +} + +message DataAppRes {} + +service App { + rpc HandleData (DataAppReq) returns (DataAppRes); +} diff --git a/core/protos/broker.proto b/core/protos/broker.proto index e39fd70a7..9076a4900 100644 --- a/core/protos/broker.proto +++ b/core/protos/broker.proto @@ -14,16 +14,16 @@ message DataBrokerRes { Metadata Metadata = 2; } -message SubBrokerReq { +message SubPersoBrokerReq { string HandlerNet = 1; bytes AppEUI = 2; - bytes DevEUI = 3; + bytes DevAddr = 3; bytes NwkSKey = 4; } -message SubBrokerRes{} +message SubPersoBrokerRes{} service Broker { - rpc HandleData (DataBrokerReq) returns (DataBrokerRes); - rpc SubscribePersonalized (SubBrokerReq) returns (SubBrokerRes); + rpc HandleData (DataBrokerReq) returns (DataBrokerRes); + rpc SubscribePersonalized (SubPersoBrokerReq) returns (SubPersoBrokerRes); } diff --git a/core/protos/handler.proto b/core/protos/handler.proto index c51f2e074..f5e71a3fd 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -4,7 +4,7 @@ import "core.proto"; package core; -message DataHandlerReq { +message DataUpHandlerReq { bytes Payload = 1; Metadata Metadata = 2; bytes AppEUI = 3; @@ -13,11 +13,30 @@ message DataHandlerReq { uint32 MType = 6; } -message DataHandlerRes { +message DataUpHandlerRes { LoRaWANData Payload = 1; Metadata Metadata = 2; } +message DataDownHandlerReq { + bytes Payload = 1; + bytes AppEUI = 2; + bytes DevEUI = 3; +} + +message DataDownHandlerRes {} + +message SubPersoHandlerReq { + bytes AppEUI = 1; + bytes DevAddr = 2; + bytes NwkSKey = 3; + bytes AppSKey = 4; +} + +message SubPersoHandlerRes {} + service Handler { - rpc HandleData (DataHandlerReq) returns (DataHandlerRes); + rpc HandleDataUp (DataUpHandlerReq) returns (DataUpHandlerRes); + rpc HandleDataDown (DataDownHandlerReq) returns (DataDownHandlerRes); + rpc SubscribePersonalized (SubPersoHandlerReq) returns (SubPersoHandlerRes); } From 174246a2092944e5cd43572182dbb29af8d20c4e Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 16 Mar 2016 11:03:56 +0100 Subject: [PATCH 1054/2266] [refactor/grpc] Add application wiring with msgpack generator --- core/application.pb.go | 79 ++++++++--- core/applicationPayload.go | 56 ++++++++ core/applicationPayload_gen.go | 196 ++++++++++++++++++++++++++++ core/applicationPayload_gen_test.go | 127 ++++++++++++++++++ core/core.go | 3 + core/protos/application.proto | 5 +- 6 files changed, 448 insertions(+), 18 deletions(-) create mode 100644 core/applicationPayload.go create mode 100644 core/applicationPayload_gen.go create mode 100644 core/applicationPayload_gen_test.go diff --git a/core/application.pb.go b/core/application.pb.go index 271bf1831..971515ee5 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -22,8 +22,9 @@ var _ = math.Inf type DataAppReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - Metadata []*Metadata `protobuf:"bytes,3,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + Metadata []*Metadata `protobuf:"bytes,4,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *DataAppReq) Reset() { *m = DataAppReq{} } @@ -135,9 +136,17 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.Payload) } } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } if m.DevEUI != nil { if len(m.DevEUI) > 0 { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) i += copy(data[i:], m.DevEUI) @@ -145,7 +154,7 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { - data[i] = 0x1a + data[i] = 0x22 i++ i = encodeVarintApplication(data, i, uint64(msg.Size())) n, err := msg.MarshalTo(data[i:]) @@ -212,6 +221,12 @@ func (m *DataAppReq) Size() (n int) { n += 1 + l + sovApplication(uint64(l)) } } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) + } + } if m.DevEUI != nil { l = len(m.DevEUI) if l > 0 { @@ -307,6 +322,37 @@ func (m *DataAppReq) Unmarshal(data []byte) error { } iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } @@ -337,7 +383,7 @@ func (m *DataAppReq) Unmarshal(data []byte) error { m.DevEUI = []byte{} } iNdEx = postIndex - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -545,17 +591,18 @@ var ( ) var fileDescriptorApplication = []byte{ - // 191 bytes of a gzipped FileDescriptorProto + // 203 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, - 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x2c, 0x2e, 0x2e, 0x97, 0xc4, 0x92, - 0x44, 0xc7, 0x82, 0x82, 0xa0, 0xd4, 0x42, 0x21, 0x09, 0x2e, 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, - 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x9e, 0x20, 0xf6, 0x02, 0x08, 0x57, 0x48, 0x8c, 0x8b, - 0xcd, 0x25, 0xb5, 0xcc, 0x35, 0xd4, 0x53, 0x82, 0x09, 0x2c, 0xc1, 0x96, 0x02, 0xe6, 0x09, 0x69, - 0x71, 0x71, 0xf8, 0xa6, 0x96, 0x24, 0xa6, 0x00, 0xcd, 0x90, 0x60, 0x56, 0x60, 0xd6, 0xe0, 0x36, - 0xe2, 0xd3, 0x03, 0x1b, 0x0f, 0x13, 0x0d, 0xe2, 0xc8, 0x85, 0xb2, 0x94, 0x78, 0x90, 0xec, 0x2a, - 0x36, 0x32, 0xe7, 0x62, 0x06, 0xb2, 0x84, 0x0c, 0xb8, 0xb8, 0x3c, 0x12, 0xf3, 0x52, 0x72, 0x52, - 0x41, 0x52, 0x42, 0x02, 0x10, 0xcd, 0x08, 0x27, 0x49, 0xa1, 0x8b, 0x14, 0x3b, 0x09, 0x9c, 0x78, - 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0xbf, - 0x18, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x8a, 0x75, 0x42, 0xb9, 0xf2, 0x00, 0x00, 0x00, + 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x26, 0x46, 0x2e, 0x2e, 0x97, 0xc4, + 0x92, 0x44, 0xc7, 0x82, 0x82, 0xa0, 0xd4, 0x42, 0x21, 0x09, 0x2e, 0xf6, 0x80, 0xc4, 0xca, 0x9c, + 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x9e, 0x20, 0xf6, 0x02, 0x08, 0x57, 0x48, 0x8c, + 0x8b, 0x0d, 0xa8, 0xc6, 0x35, 0xd4, 0x53, 0x82, 0x09, 0x2c, 0xc1, 0x96, 0x08, 0xe6, 0x81, 0xc4, + 0x5d, 0x52, 0xcb, 0x40, 0xe2, 0xcc, 0x10, 0xf1, 0x14, 0x30, 0x4f, 0x48, 0x8b, 0x8b, 0xc3, 0x37, + 0xb5, 0x24, 0x31, 0x05, 0x68, 0xb6, 0x04, 0x8b, 0x02, 0xb3, 0x06, 0xb7, 0x11, 0x9f, 0x1e, 0xd8, + 0x5e, 0x98, 0x68, 0x10, 0x47, 0x2e, 0x94, 0xa5, 0xc4, 0x83, 0xe4, 0x86, 0x62, 0x23, 0x73, 0x2e, + 0x66, 0x20, 0x4b, 0xc8, 0x80, 0x8b, 0xcb, 0x23, 0x31, 0x2f, 0x25, 0x27, 0x15, 0x24, 0x25, 0x24, + 0x00, 0xd1, 0x8c, 0x70, 0xaa, 0x14, 0xba, 0x48, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, + 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0xf6, 0xa4, 0x31, 0x20, 0x00, + 0x00, 0xff, 0xff, 0xf1, 0x20, 0xc5, 0xda, 0x0b, 0x01, 0x00, 0x00, } diff --git a/core/applicationPayload.go b/core/applicationPayload.go new file mode 100644 index 000000000..0dd240052 --- /dev/null +++ b/core/applicationPayload.go @@ -0,0 +1,56 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +//go:generate msgp -io=false + +package core + +import ( + "reflect" +) + +// AppPayload represents the actual payloads sent to application +type AppPayload struct { + Payload []byte `msg:"payload",json:"payload"` + Metadata []AppMetadata `msg:"metadata",json:"metadata"` +} + +// AppMetadata represents gathered metadata that are sent to gateways +type AppMetadata struct { + Frequency float32 `msg:"frequency",json:"frequency"` + DataRate string `msg:"data_rate",json:"data_rate"` + CodingRate string `msg:"coding_rate",json:"coding_rate"` + Timestamp uint32 `msg:"timestamp",json:timestamp"` + Rssi int32 `msg:"rssi",json:"rssi"` + Lsnr float32 `msg:"lsnr",json:"lsnr"` + Altitude int32 `msg:"altitude",json:"altitude"` + Longitude float32 `msg:"longitude",json:"longitude"` + Latitude float32 `msg:"latitude",json:"latitude"` +} + +// ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid +// AppMetadata ready to be marshaled to json +func ProtoMetaToAppMeta(srcs ...*Metadata) []AppMetadata { + var dest []AppMetadata + + for _, src := range srcs { + if src == nil { + continue + } + to := new(AppMetadata) + v := reflect.ValueOf(src).Elem() + t := v.Type() + d := reflect.ValueOf(to).Elem() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i).Name + if d.FieldByName(field).CanSet() { + d.FieldByName(field).Set(v.Field(i)) + } + } + + dest = append(dest, *to) + } + + return dest +} diff --git a/core/applicationPayload_gen.go b/core/applicationPayload_gen.go new file mode 100644 index 000000000..45819702a --- /dev/null +++ b/core/applicationPayload_gen.go @@ -0,0 +1,196 @@ +package core + +// NOTE: THIS FILE WAS PRODUCED BY THE +// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp) +// DO NOT EDIT + +import ( + "github.com/tinylib/msgp/msgp" +) + +// MarshalMsg implements msgp.Marshaler +func (z *AppMetadata) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 9 + // string "frequency" + o = append(o, 0x89, 0xa9, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79) + o = msgp.AppendFloat32(o, z.Frequency) + // string "data_rate" + o = append(o, 0xa9, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x72, 0x61, 0x74, 0x65) + o = msgp.AppendString(o, z.DataRate) + // string "coding_rate" + o = append(o, 0xab, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x61, 0x74, 0x65) + o = msgp.AppendString(o, z.CodingRate) + // string "timestamp" + o = append(o, 0xa9, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70) + o = msgp.AppendUint32(o, z.Timestamp) + // string "rssi" + o = append(o, 0xa4, 0x72, 0x73, 0x73, 0x69) + o = msgp.AppendInt32(o, z.Rssi) + // string "lsnr" + o = append(o, 0xa4, 0x6c, 0x73, 0x6e, 0x72) + o = msgp.AppendFloat32(o, z.Lsnr) + // string "altitude" + o = append(o, 0xa8, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) + o = msgp.AppendInt32(o, z.Altitude) + // string "longitude" + o = append(o, 0xa9, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65) + o = msgp.AppendFloat32(o, z.Longitude) + // string "latitude" + o = append(o, 0xa8, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) + o = msgp.AppendFloat32(o, z.Latitude) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var isz uint32 + isz, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + return + } + for isz > 0 { + isz-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + return + } + switch msgp.UnsafeString(field) { + case "frequency": + z.Frequency, bts, err = msgp.ReadFloat32Bytes(bts) + if err != nil { + return + } + case "data_rate": + z.DataRate, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + return + } + case "coding_rate": + z.CodingRate, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + return + } + case "timestamp": + z.Timestamp, bts, err = msgp.ReadUint32Bytes(bts) + if err != nil { + return + } + case "rssi": + z.Rssi, bts, err = msgp.ReadInt32Bytes(bts) + if err != nil { + return + } + case "lsnr": + z.Lsnr, bts, err = msgp.ReadFloat32Bytes(bts) + if err != nil { + return + } + case "altitude": + z.Altitude, bts, err = msgp.ReadInt32Bytes(bts) + if err != nil { + return + } + case "longitude": + z.Longitude, bts, err = msgp.ReadFloat32Bytes(bts) + if err != nil { + return + } + case "latitude": + z.Latitude, bts, err = msgp.ReadFloat32Bytes(bts) + if err != nil { + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + return + } + } + } + o = bts + return +} + +func (z *AppMetadata) Msgsize() (s int) { + s = 1 + 10 + msgp.Float32Size + 10 + msgp.StringPrefixSize + len(z.DataRate) + 12 + msgp.StringPrefixSize + len(z.CodingRate) + 10 + msgp.Uint32Size + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 9 + msgp.Int32Size + 10 + msgp.Float32Size + 9 + msgp.Float32Size + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *AppPayload) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 2 + // string "payload" + o = append(o, 0x82, 0xa7, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64) + o = msgp.AppendBytes(o, z.Payload) + // string "metadata" + o = append(o, 0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) + o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) + for xvk := range z.Metadata { + o, err = z.Metadata[xvk].MarshalMsg(o) + if err != nil { + return + } + } + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *AppPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var isz uint32 + isz, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + return + } + for isz > 0 { + isz-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + return + } + switch msgp.UnsafeString(field) { + case "payload": + z.Payload, bts, err = msgp.ReadBytesBytes(bts, z.Payload) + if err != nil { + return + } + case "metadata": + var xsz uint32 + xsz, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + return + } + if cap(z.Metadata) >= int(xsz) { + z.Metadata = z.Metadata[:xsz] + } else { + z.Metadata = make([]AppMetadata, xsz) + } + for xvk := range z.Metadata { + bts, err = z.Metadata[xvk].UnmarshalMsg(bts) + if err != nil { + return + } + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + return + } + } + } + o = bts + return +} + +func (z *AppPayload) Msgsize() (s int) { + s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + 9 + msgp.ArrayHeaderSize + for xvk := range z.Metadata { + s += z.Metadata[xvk].Msgsize() + } + return +} diff --git a/core/applicationPayload_gen_test.go b/core/applicationPayload_gen_test.go new file mode 100644 index 000000000..577ce4b84 --- /dev/null +++ b/core/applicationPayload_gen_test.go @@ -0,0 +1,127 @@ +package core + +// NOTE: THIS FILE WAS PRODUCED BY THE +// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp) +// DO NOT EDIT + +import ( + "testing" + + "github.com/tinylib/msgp/msgp" +) + +func TestMarshalUnmarshalAppMetadata(t *testing.T) { + v := AppMetadata{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgAppMetadata(b *testing.B) { + v := AppMetadata{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgAppMetadata(b *testing.B) { + v := AppMetadata{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalAppMetadata(b *testing.B) { + v := AppMetadata{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalAppPayload(t *testing.T) { + v := AppPayload{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgAppPayload(b *testing.B) { + v := AppPayload{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgAppPayload(b *testing.B) { + v := AppPayload{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalAppPayload(b *testing.B) { + v := AppPayload{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/core/core.go b/core/core.go index a4f01a071..a47315013 100644 --- a/core/core.go +++ b/core/core.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + //go:generate sh -c "find protos -name '*.proto' | xargs protoc --gofast_out=plugins=grpc:. -I=protos" package core diff --git a/core/protos/application.proto b/core/protos/application.proto index 8f9e63694..97289daa4 100644 --- a/core/protos/application.proto +++ b/core/protos/application.proto @@ -5,8 +5,9 @@ package core; message DataAppReq { bytes Payload = 1; - bytes DevEUI = 2; - repeated Metadata Metadata = 3; + bytes AppEUI = 2; + bytes DevEUI = 3; + repeated Metadata Metadata = 4; } message DataAppRes {} From 1883fc2740ef1bf1265e1bc2ad16eb3a4498c761 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 16 Mar 2016 11:17:42 +0100 Subject: [PATCH 1055/2266] [refactor/grpc] Rewrite mqtt adapter with new client library + implements grpc app client --- core/adapters/mqtt/handlers/activation.go | 73 --- .../mqtt/handlers/activationRegistration.go | 42 -- .../adapters/mqtt/handlers/activation_test.go | 167 ------ core/adapters/mqtt/handlers/downlink.go | 59 -- core/adapters/mqtt/handlers/downlink_test.go | 143 ----- core/adapters/mqtt/handlers/helpers_test.go | 158 ------ core/adapters/mqtt/handlers/mocks_test.go | 146 ----- core/adapters/mqtt/mocks_test.go | 198 ------- core/adapters/mqtt/mqtt.go | 314 +++-------- core/adapters/mqtt/mqttAckNacker.go | 49 -- core/adapters/mqtt/mqttAckNacker_test.go | 163 ------ core/adapters/mqtt/mqttRecipient.go | 65 --- core/adapters/mqtt/mqtt_test.go | 514 ------------------ core/components/handler/handler.go | 1 + 14 files changed, 88 insertions(+), 2004 deletions(-) delete mode 100644 core/adapters/mqtt/handlers/activation.go delete mode 100644 core/adapters/mqtt/handlers/activationRegistration.go delete mode 100644 core/adapters/mqtt/handlers/activation_test.go delete mode 100644 core/adapters/mqtt/handlers/downlink.go delete mode 100644 core/adapters/mqtt/handlers/downlink_test.go delete mode 100644 core/adapters/mqtt/handlers/helpers_test.go delete mode 100644 core/adapters/mqtt/handlers/mocks_test.go delete mode 100644 core/adapters/mqtt/mocks_test.go delete mode 100644 core/adapters/mqtt/mqttAckNacker.go delete mode 100644 core/adapters/mqtt/mqttAckNacker_test.go delete mode 100644 core/adapters/mqtt/mqttRecipient.go delete mode 100644 core/adapters/mqtt/mqtt_test.go diff --git a/core/adapters/mqtt/handlers/activation.go b/core/adapters/mqtt/handlers/activation.go deleted file mode 100644 index 6fe4e3d0e..000000000 --- a/core/adapters/mqtt/handlers/activation.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "encoding/hex" - "fmt" - "strings" - - MQTT "github.com/KtorZ/paho.mqtt.golang" - . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// Activation handles communication between a handler and an application via MQTT -type Activation struct{} - -// Topic implements the mqtt.Handler interface -func (a Activation) Topic() string { - return "+/devices/+/activations" -} - -// Handle implements the mqtt.Handler interface -func (a Activation) Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { - topicInfos := strings.Split(msg.Topic(), "/") - - if len(topicInfos) != 4 { - return errors.New(errors.Structural, "Invalid given topic") - } - - appEUIStr := topicInfos[0] - devEUIStr := topicInfos[2] - - if devEUIStr != "personalized" { - return errors.New(errors.Implementation, "OTAA not yet supported. Unable to register device") - } - - payload := msg.Payload() - if len(payload) != 36 { - return errors.New(errors.Structural, "Invalid registration payload") - } - - var appEUI lorawan.EUI64 - var devEUI lorawan.EUI64 - var nwkSKey lorawan.AES128Key - var appSKey lorawan.AES128Key - copy(devEUI[4:], msg.Payload()[:4]) - copy(nwkSKey[:], msg.Payload()[4:20]) - copy(appSKey[:], msg.Payload()[20:]) - - data, err := hex.DecodeString(appEUIStr) - if err != nil || len(data) != 8 { - return errors.New(errors.Structural, "Invalid application EUI") - } - copy(appEUI[:], data[:]) - - devEUIStr = hex.EncodeToString(devEUI[:]) - topic := fmt.Sprintf("%s/%s/%s/up", appEUIStr, "devices", devEUIStr) - - chreg <- RegReq{ - Registration: activationRegistration{ - recipient: NewRecipient(topic, ""), - devEUI: devEUI, - appEUI: appEUI, - nwkSKey: nwkSKey, - appSKey: appSKey, - }, - Chresp: nil, - } - return nil -} diff --git a/core/adapters/mqtt/handlers/activationRegistration.go b/core/adapters/mqtt/handlers/activationRegistration.go deleted file mode 100644 index da104cd4b..000000000 --- a/core/adapters/mqtt/handlers/activationRegistration.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -type activationRegistration struct { - recipient core.Recipient - devEUI lorawan.EUI64 - appEUI lorawan.EUI64 - nwkSKey lorawan.AES128Key - appSKey lorawan.AES128Key -} - -// Recipient implements the core.HRegistration interface -func (r activationRegistration) Recipient() core.Recipient { - return r.recipient -} - -// AppEUI implements the core.HRegistration interface -func (r activationRegistration) AppEUI() lorawan.EUI64 { - return r.appEUI -} - -// DevEUI implements the core.HRegistration interface -func (r activationRegistration) DevEUI() lorawan.EUI64 { - return r.devEUI -} - -// AppSKey implements the core.HRegistration interface -func (r activationRegistration) AppSKey() lorawan.AES128Key { - return r.appSKey -} - -// NwkSKey implements the core.HRegistration interface -func (r activationRegistration) NwkSKey() lorawan.AES128Key { - return r.nwkSKey -} diff --git a/core/adapters/mqtt/handlers/activation_test.go b/core/adapters/mqtt/handlers/activation_test.go deleted file mode 100644 index 3b5383ec9..000000000 --- a/core/adapters/mqtt/handlers/activation_test.go +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright © 2016 T//e Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestActivationTopic(t *testing.T) { - wantTopic := "+/devices/+/activations" - - // Describe - Desc(t, "Topic should equal: %s", wantTopic) - - // Build - handler := Activation{} - - // Operate - topic := handler.Topic() - - // Check - checkTopics(t, wantTopic, topic) -} - -func TestActivationHandle(t *testing.T) { - tests := []struct { - Desc string // The test's description - Client *MockClient // An mqtt client to mock (or not) the behavior - Topic string // The topic to which the message is addressed - Payload []byte // The message's payload - - WantError *string // The expected error from the handler - WantSubscription *string // The topic to which a subscription is expected - WantRegistration core.HRegistration // The expected registration towards the adapter - WantPacket core.Packet // The expected packet towards the adapter - }{ - { - Desc: "Ok client | Valid Topic | Valid Payload", - Client: NewMockClient(), - Topic: "0101010101010101/devices/personalized/activations", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - }, - - WantError: nil, - WantSubscription: nil, - WantRegistration: activationRegistration{ - recipient: NewRecipient("0101010101010101/devices/0000000002020202/up", ""), - devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 2, 2, 2, 2}), - appEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - nwkSKey: lorawan.AES128Key([16]byte{3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}), - appSKey: lorawan.AES128Key([16]byte{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}), - }, - WantPacket: nil, - }, - { - Desc: "Ok client | Invalid Topic #1 | Valid Payload", - Client: NewMockClient(), - Topic: "PleaseRegisterMyDevice", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - }, - - WantError: pointer.String(string(errors.Structural)), - WantSubscription: nil, - WantRegistration: nil, - WantPacket: nil, - }, - { - Desc: "Ok client | Invalid Topic #2 | Valid Payload", - Client: NewMockClient(), - Topic: "0101010101010101/devices/0001020304050607/activations", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - }, - - WantError: pointer.String(string(errors.Implementation)), - WantSubscription: nil, - WantRegistration: nil, - WantPacket: nil, - }, - { - Desc: "Ok client | Invalid Topic #3 | Valid Payload", - Client: NewMockClient(), - Topic: "01010101/devices/personalized/activations", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - }, - - WantError: pointer.String(string(errors.Structural)), - WantSubscription: nil, - WantRegistration: nil, - WantPacket: nil, - }, - { - Desc: "Ok client | Valid Topic | Invalid Payload #1", - Client: NewMockClient(), - Topic: "0101010101010101/devices/personalized/activations", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - 01, 02, 03, 04, - }, - - WantError: pointer.String(string(errors.Structural)), - WantSubscription: nil, - WantRegistration: nil, - WantPacket: nil, - }, - { - Desc: "Ok client | Valid Topic | Invalid Payload #2", - Client: NewMockClient(), - Topic: "0101010101010101/devices/personalized/activations", - Payload: []byte{ // DevEUI | NwkSKey | AppSKey - 02, 02, - 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, 03, - 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, 04, - }, - - WantError: pointer.String(string(errors.Structural)), - WantSubscription: nil, - WantRegistration: nil, - WantPacket: nil, - }, - } - - for i, test := range tests { - // Describe - Desc(t, "#%d: %s", i, test.Desc) - - // Build - consumer, chpkt, chreg := newTestConsumer() - handler := Activation{} - - // Operate - err := handler.Handle(test.Client, chpkt, chreg, MockMessage{ - payload: test.Payload, - topic: test.Topic, - }) - <-time.After(time.Millisecond * 100) - - // Check - CheckErrors(t, test.WantError, err) - checkSubscriptions(t, test.WantSubscription, test.Client.InSubscribe) - checkRegistrations(t, test.WantRegistration, consumer.Registration) - checkPackets(t, test.WantPacket, consumer.Packet) - } -} diff --git a/core/adapters/mqtt/handlers/downlink.go b/core/adapters/mqtt/handlers/downlink.go deleted file mode 100644 index c56cde9bd..000000000 --- a/core/adapters/mqtt/handlers/downlink.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "encoding/hex" - "strings" - - MQTT "github.com/KtorZ/paho.mqtt.golang" - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// Downlink handles communication between a handler and an application via MQTT -type Downlink struct{} - -// Topic implements the mqtt.Handler interface -func (a Downlink) Topic() string { - return "+/devices/+/down" -} - -// Handle implements the mqtt.Handler interface -func (a Downlink) Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { - infos := strings.Split(msg.Topic(), "/") - - if len(infos) != 4 { - return errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") - } - - appEUIRaw, erra := hex.DecodeString(infos[0]) - devEUIRaw, errd := hex.DecodeString(infos[2]) - if erra != nil || errd != nil || len(appEUIRaw) != 8 || len(devEUIRaw) != 8 { - return errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI") - } - - var appEUI lorawan.EUI64 - copy(appEUI[:], appEUIRaw) - var devEUI lorawan.EUI64 - copy(devEUI[:], devEUIRaw) - - apacket, err := core.NewAPacket(appEUI, devEUI, msg.Payload(), nil) - if err != nil { - return errors.New(errors.Structural, err) - } - - data, err := apacket.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - - chpkt <- PktReq{ - Packet: data, - Chresp: nil, - } - return nil -} diff --git a/core/adapters/mqtt/handlers/downlink_test.go b/core/adapters/mqtt/handlers/downlink_test.go deleted file mode 100644 index 5f0a6d31e..000000000 --- a/core/adapters/mqtt/handlers/downlink_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestDownlinkTopic(t *testing.T) { - wantTopic := "+/devices/+/down" - - // Describe - Desc(t, "Topic should equal: %s", wantTopic) - - // Build - handler := Downlink{} - - // Operate - topic := handler.Topic() - - // Check - checkTopics(t, wantTopic, topic) -} - -func TestDownlinkHandle(t *testing.T) { - packet, _ := core.NewAPacket( - lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), - []byte{1, 2, 3, 4}, - nil, - ) - - tests := []struct { - Desc string // The test's description - Client *MockClient // An mqtt client to mock (or not) the behavior - Topic string // The topic to which the message is addressed - Payload []byte // The message's payload - - WantError *string // The expected error from the handler - WantSubscription *string // The topic to which a subscription is expected - WantRegistration core.HRegistration // The expected registration towards the adapter - WantPacket core.Packet // The expected packet towards the adapter - }{ - - { - Desc: "Valid Payload | Valid Topic", - Client: NewMockClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/0202020202020202/down", - - WantError: nil, - WantPacket: packet, - WantSubscription: nil, - WantRegistration: nil, - }, - { - Desc: "Valid Payload | Invalid Topic #2", - Client: NewMockClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/0202020202020202/down/again", - - WantError: pointer.String(string(errors.Structural)), - WantPacket: nil, - WantSubscription: nil, - WantRegistration: nil, - }, - { - Desc: "Valid Payload | Invalid Topic", - Client: NewMockClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/0202020202020202", - - WantError: pointer.String(string(errors.Structural)), - WantPacket: nil, - WantSubscription: nil, - WantRegistration: nil, - }, - { - Desc: "Valid Payload | Invalid AppEUI", - Client: NewMockClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "010101/devices/0202020202020202/down", - - WantError: pointer.String(string(errors.Structural)), - WantPacket: nil, - WantSubscription: nil, - WantRegistration: nil, - }, - { - Desc: "Valid Payload | Invalid DevEUI", - Client: NewMockClient(), - Payload: []byte{1, 2, 3, 4}, - Topic: "0101010101010101/devices/020202/down", - - WantError: pointer.String(string(errors.Structural)), - WantPacket: nil, - WantSubscription: nil, - WantRegistration: nil, - }, - { - Desc: "Invalid Payload | Valid Topic", - Client: NewMockClient(), - Payload: []byte{}, - Topic: "0101010101010101/devices/0202020202020202/down", - - WantError: pointer.String(string(errors.Structural)), - WantPacket: nil, - WantSubscription: nil, - WantRegistration: nil, - }, - } - - for i, test := range tests { - // Describe - Desc(t, "#%d: %s", i, test.Desc) - - // Build - consumer, chpkt, chreg := newTestConsumer() - handler := Downlink{} - - // Operate - err := handler.Handle(test.Client, chpkt, chreg, MockMessage{ - payload: test.Payload, - topic: test.Topic, - }) - <-time.After(time.Millisecond * 100) - - // Check - CheckErrors(t, test.WantError, err) - checkSubscriptions(t, test.WantSubscription, test.Client.InSubscribe) - checkRegistrations(t, test.WantRegistration, consumer.Registration) - checkPackets(t, test.WantPacket, consumer.Packet) - } -} diff --git a/core/adapters/mqtt/handlers/helpers_test.go b/core/adapters/mqtt/handlers/helpers_test.go deleted file mode 100644 index 9cf001002..000000000 --- a/core/adapters/mqtt/handlers/helpers_test.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "reflect" - "testing" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// testConsumer generates a component which consumes messages from two channels and make the last -// result available - -type testConsumer struct { - Packet []byte - Registration core.Registration -} - -func newTestConsumer() (*testConsumer, chan PktReq, chan RegReq) { - chpkt := make(chan PktReq) - chreg := make(chan RegReq) - consumer := testConsumer{} - - go func() { - for msg := range chpkt { - consumer.Packet = msg.Packet - } - }() - - go func() { - for msg := range chreg { - consumer.Registration = msg.Registration - } - }() - - return &consumer, chpkt, chreg -} - -// ----- CHECK utilities -func checkTopics(t *testing.T, want string, got string) { - if want == got { - Ok(t, "Check Topics") - return - } - - Ko(t, "Topic does not match expectation.\nWant: %s\nGot: %s", want, got) -} - -func checkRegistrations(t *testing.T, want core.HRegistration, got core.Registration) { - // Check if interfaces are nil - if got == nil { - if want == nil { - Ok(t, "Check Registrations") - return - } - Ko(t, "Expected registration to be %v but got nothing", want) - return - } - if want == nil { - Ko(t, "Expected no registration but got %v", got) - return - } - - // Check recipient topicUp - rWant, ok := want.Recipient().(Recipient) - if !ok { - panic("Expected test to be made with MQTTRecipient") - } - rGot, ok := got.Recipient().(Recipient) - if !ok { - Ko(t, "Recipient isn't MqttRecipient: %v", got.Recipient()) - return - } - if rWant.TopicUp() != rGot.TopicUp() { - Ko(t, "Recipients got different topics up.\nWant: %s\nGot: %s", rWant.TopicUp(), rGot.TopicUp()) - return - } - - rgot, ok := got.(core.HRegistration) - if !ok { - Ko(t, "Expected to receive an HRegistration but got %+v", got) - return - } - - // Check DevEUIs - if !reflect.DeepEqual(want.DevEUI(), rgot.DevEUI()) { - Ko(t, "Registrations' DevEUI are different.\nWant: %v\nGot: %v", want.DevEUI(), rgot.DevEUI()) - return - } - - // Check AppEUIs - if !reflect.DeepEqual(want.AppEUI(), rgot.AppEUI()) { - Ko(t, "Registrations' appEUI are different.\nWant: %v\nGot: %v", want.AppEUI(), rgot.AppEUI()) - return - } - - // Check Network Session Keys - if !reflect.DeepEqual(want.NwkSKey(), rgot.NwkSKey()) { - Ko(t, "Registrations' nwkSKey are different.\nWant: %v\nGot: %v", want.NwkSKey(), rgot.NwkSKey()) - return - } - - // Check Application Session Keys - if !reflect.DeepEqual(want.AppSKey(), rgot.AppSKey()) { - Ko(t, "Registrations' appSKey are different.\nWant: %v\nGot: %v", want.AppSKey(), rgot.AppSKey()) - return - } - - // Pheeew - Ok(t, "Check Registrations") -} - -func checkSubscriptions(t *testing.T, want *string, got *string) { - if got == nil { - if want == nil { - Ok(t, "Check Subscriptions") - return - } - Ko(t, "Expected subscription to be %s but got nothing", *want) - return - } - if want == nil { - Ko(t, "Expected no subscription but got %v", *got) - return - } - - if *want != *got { - Ko(t, "Expected subscription to be %s but got %s", *want, *got) - return - } - - Ok(t, "Check Subscriptions") -} - -func checkPackets(t *testing.T, want core.Packet, got []byte) { - if want == nil { - if got == nil { - Ok(t, "Check Packets") - return - } - Ko(t, "Expected no packet but got %v", got) - return - } - - data, err := want.MarshalBinary() - if err != nil { - panic(err) - } - if reflect.DeepEqual(data, got) { - Ok(t, "Check Packets") - return - } - Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", data, got) -} diff --git a/core/adapters/mqtt/handlers/mocks_test.go b/core/adapters/mqtt/handlers/mocks_test.go deleted file mode 100644 index 61e5d0526..000000000 --- a/core/adapters/mqtt/handlers/mocks_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "fmt" - "time" - - MQTT "github.com/KtorZ/paho.mqtt.golang" - "github.com/TheThingsNetwork/ttn/utils/pointer" -) - -// ----- TYPE utilities - -// testToken gives a fake implementation of MQTT.Token -// -// Provide a failure if you need to simulate an Error() result. -type MockToken struct { - MQTT.Token - Failure *string -} - -func (t MockToken) Wait() bool { - return true -} - -func (t MockToken) WaitTimeout(d time.Duration) bool { - <-time.After(d) - return true -} - -func (t MockToken) Error() error { - if t.Failure == nil { - return nil - } - return fmt.Errorf(*t.Failure) -} - -// MockMessage gives a fake implementation of MQTT.Message -// -// provide payload and topic. Other methods are constants. -type MockMessage struct { - payload interface{} - topic string -} - -func (m MockMessage) Duplicate() bool { - return false -} -func (m MockMessage) Qos() byte { - return 2 -} -func (m MockMessage) Retained() bool { - return false -} -func (m MockMessage) Topic() string { - return m.topic -} -func (m MockMessage) MessageID() uint16 { - return 0 -} -func (m MockMessage) Payload() []byte { - switch m.payload.(type) { - case []byte: - return m.payload.([]byte) - default: - return nil - } -} - -// MockClient gives a fake implementation of a MQTT.ClientInt -// -// It saves the last subscription, unsubscriptions and publication call made -// -// It can also fails on demand (use the newMockClient method to define which methods should fail) -type MockClient struct { - MQTT.Client - InSubscribe *string - InPublish MQTT.Message - InUnsubscribe []string - - Failures map[string]*string - connected bool -} - -func NewMockClient(failures ...string) *MockClient { - client := MockClient{Failures: make(map[string]*string), connected: true, InPublish: MockMessage{}} - - isFailure := func(x string) bool { - for _, f := range failures { - if f == x { - return true - } - } - return false - } - - if isFailure("Connect") { - client.Failures["Connect"] = pointer.String("MockError -> Failed to connect") - } - - if isFailure("Publish") { - client.Failures["Publish"] = pointer.String("MockError -> Failed to publish") - } - - if isFailure("Subscribe") { - client.Failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") - } - - if isFailure("Unsubscribe") { - client.Failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") - } - - return &client -} - -func (c *MockClient) Connect() MQTT.Token { - c.connected = true - return MockToken{Failure: c.Failures["Connect"]} -} - -func (c *MockClient) Disconnect(quiesce uint) { - <-time.After(time.Duration(quiesce)) - c.connected = false - return -} - -func (c MockClient) IsConnected() bool { - return c.connected -} - -func (c *MockClient) Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token { - c.InPublish = MockMessage{payload: payload, topic: topic} - return MockToken{Failure: c.Failures["Publish"]} -} - -func (c *MockClient) Subscribe(topic string, qos byte, callback MQTT.MessageHandler) MQTT.Token { - c.InSubscribe = &topic - return MockToken{Failure: c.Failures["Subscribe"]} -} - -func (c *MockClient) Unsubscribe(topics ...string) MQTT.Token { - c.InUnsubscribe = topics - return MockToken{Failure: c.Failures["Unsubscribe"]} -} diff --git a/core/adapters/mqtt/mocks_test.go b/core/adapters/mqtt/mocks_test.go deleted file mode 100644 index b142a39ec..000000000 --- a/core/adapters/mqtt/mocks_test.go +++ /dev/null @@ -1,198 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - "testing" - "time" - - MQTT "github.com/KtorZ/paho.mqtt.golang" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// ----- TYPE utilities - -// MockHandler provides a fake implementation of a mqtt.Handler -type MockHandler struct { - Failures map[string]error - OutTopic string - InMessage MQTT.Message -} - -func NewMockHandler() *MockHandler { - return &MockHandler{ - Failures: make(map[string]error), - OutTopic: "MockTopic", - } -} - -func (h *MockHandler) Topic() string { - return h.OutTopic -} - -func (h *MockHandler) Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error { - h.InMessage = msg - return h.Failures["Handle"] -} - -// MockToken gives a fake implementation of MQTT.Token -// -// Provide a failure if you need to simulate an Error() result. -type MockToken struct { - MQTT.Token - Failure *string -} - -func (t MockToken) Wait() bool { - return true -} - -func (t MockToken) WaitTimeout(d time.Duration) bool { - <-time.After(d) - return true -} - -func (t MockToken) Error() error { - if t.Failure == nil { - return nil - } - return fmt.Errorf(*t.Failure) -} - -// MockMessage gives a fake implementation of MQTT.Message -// -// provide payload and topic. Other methods are constants. -type MockMessage struct { - payload interface{} - topic string -} - -func (m MockMessage) Duplicate() bool { - return false -} -func (m MockMessage) Qos() byte { - return 2 -} -func (m MockMessage) Retained() bool { - return false -} -func (m MockMessage) Topic() string { - return m.topic -} -func (m MockMessage) MessageID() uint16 { - return 0 -} -func (m MockMessage) Payload() []byte { - switch m.payload.(type) { - case []byte: - return m.payload.([]byte) - default: - return nil - } -} - -// MockClient gives a fake implementation of a MQTT.ClientInt -// -// It saves the last subscription, unsubscriptions and publication call made -// -// It can also fails on demand (use the newMockClient method to define which methods should fail) -type MockClient struct { - MQTT.Client - InSubscribe *string - InPublish MQTT.Message - InUnsubscribe []string - InSubscribeCallBack func(c MQTT.Client, m MQTT.Message) - - Failures map[string]*string - connected bool -} - -func NewMockClient(failures ...string) *MockClient { - client := MockClient{ - Failures: make(map[string]*string), - connected: true, - InPublish: MockMessage{}, - } - - isFailure := func(x string) bool { - for _, f := range failures { - if f == x { - return true - } - } - return false - } - - if isFailure("Connect") { - client.Failures["Connect"] = pointer.String("MockError -> Failed to connect") - } - - if isFailure("Publish") { - client.Failures["Publish"] = pointer.String("MockError -> Failed to publish") - } - - if isFailure("Subscribe") { - client.Failures["Subscribe"] = pointer.String("MockError -> Failed to subscribe") - } - - if isFailure("Unsubscribe") { - client.Failures["Unsubscribe"] = pointer.String("MockError -> Failed to unsubscribe") - } - - return &client -} - -func (c *MockClient) Connect() MQTT.Token { - c.connected = true - return MockToken{Failure: c.Failures["Connect"]} -} - -func (c *MockClient) Disconnect(quiesce uint) { - <-time.After(time.Duration(quiesce)) - c.connected = false - return -} - -func (c MockClient) IsConnected() bool { - return c.connected -} - -func (c *MockClient) Publish(topic string, qos byte, retained bool, payload interface{}) MQTT.Token { - c.InPublish = MockMessage{payload: payload, topic: topic} - return MockToken{Failure: c.Failures["Publish"]} -} - -func (c *MockClient) Subscribe(topic string, qos byte, callback MQTT.MessageHandler) MQTT.Token { - c.InSubscribe = &topic - c.InSubscribeCallBack = callback - return MockToken{Failure: c.Failures["Subscribe"]} -} - -func (c *MockClient) Unsubscribe(topics ...string) MQTT.Token { - c.InUnsubscribe = topics - return MockToken{Failure: c.Failures["Unsubscribe"]} -} - -// ----- CHECK utilities - -func checkSubscriptions(t *testing.T, want *string, got *string) { - if got == nil { - if want == nil { - Ok(t, "Check Subscriptions") - return - } - Ko(t, "Expected subscription to be %s but got nothing", *want) - } - if want == nil { - Ko(t, "Expected no subscription but got %v", *got) - } - - if *want != *got { - Ko(t, "Expected subscription to be %s but got %s", *want, *got) - } - - Ok(t, "Check Subscriptions") -} diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 9ed76f6f5..4c5ca5827 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -4,264 +4,124 @@ package mqtt import ( + "encoding/hex" "fmt" - "sync" "time" - MQTT "github.com/KtorZ/paho.mqtt.golang" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" + "github.com/yosssi/gmq/mqtt" + "github.com/yosssi/gmq/mqtt/client" + "golang.org/x/net/context" + "google.golang.org/grpc" ) -var timeout = time.Second - -// Adapter type materializes an mqtt adapter which implements the basic mqtt protocol -type Adapter struct { - sync.RWMutex - MQTT.Client - ctx log.Interface - packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently - registrations chan RegReq // Incoming registrations -} - -// Handler defines topic-specific handler. -type Handler interface { - Topic() string - Handle(client MQTT.Client, chpkt chan<- PktReq, chreg chan<- RegReq, msg MQTT.Message) error -} - -// MsgRes are sent through the response channel of a pktReq or regReq -type MsgRes []byte // The response content. - -// PktReq are sent through the packets channel when an incoming request arrives -type PktReq struct { - Packet []byte // The actual packet that has been parsed - Chresp chan MsgRes // A response channel waiting for an success or reject confirmation -} - -// RegReq are sent through the registration channel when an incoming registration arrives -type RegReq struct { - Registration core.Registration - Chresp chan MsgRes -} - -// Scheme defines all MQTT communication schemes available -type Scheme string - -// The following constants are used as scheme identifers -const ( - TCP Scheme = "tcp" - TLS Scheme = "tls" - WebSocket Scheme = "ws" -) - -// NewAdapter constructs and allocates a new mqtt adapter -// -// The client is expected to be already connected to the right broker and ready to be used. -func NewAdapter(client MQTT.Client, ctx log.Interface) *Adapter { - adapter := &Adapter{ - Client: client, - ctx: ctx, - packets: make(chan PktReq), - registrations: make(chan RegReq), - } - - return adapter +type adapter struct { + *client.Client + Handler core.HandlerClient + ctx log.Interface } -// NewClient generates a new paho MQTT client from an id and a broker url -// -// The broker url is expected to contain a port if needed such as mybroker.com:87354 -// -// The scheme has to be the same as the one used by the broker: tcp, tls or web socket -func NewClient(id string, broker string, scheme Scheme) (MQTT.Client, <-chan error, error) { - opts := MQTT.NewClientOptions() - cherr := make(chan error) - opts.AddBroker(fmt.Sprintf("%s://%s", scheme, broker)) - opts.SetClientID(id) - opts.SetConnectionLostHandler(func(client MQTT.Client, reason error) { - cherr <- reason - }) - c := MQTT.NewClient(opts) - if token := c.Connect(); token.Wait() && token.Error() != nil { - return nil, nil, errors.New(errors.Operational, token.Error()) +// New constructs an mqtt adapter responsible for making the bridge between the handler and +// application. +func New(handler core.HandlerClient, client *client.Client, ctx log.Interface) core.AppClient { + return adapter{ + Client: client, + Handler: handler, + ctx: ctx, } - return c, cherr, nil } -// Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { - stats.MarkMeter("mqtt_adapter.send") - stats.UpdateHistogram("mqtt_adapter.send_recipients", int64(len(recipients))) - - // Marshal the packet to raw binary data - data, err := p.MarshalBinary() - if err != nil { - a.ctx.WithError(err).Warn("Invalid Packet") - return nil, errors.New(errors.Structural, err) +// NewClient creates and connects a mqtt client with predefined options. +func NewClient(id string, netAddr string, ctx log.Interface) (*client.Client, error) { + var cli *client.Client + var delay time.Duration = 25 * time.Millisecond + + tryConnect := func() error { + ctx.WithField("id", id).WithField("address", netAddr).Debug("(Re)Connecting MQTT Client") + return cli.Connect(&client.ConnectOptions{ + Network: "tcp", + Address: netAddr, + ClientID: []byte(id), + }) } - a.ctx.Debug("Sending Packet") - - // Prepare ground for parrallel mqtt publications - nb := len(recipients) - cherr := make(chan error, nb) - chresp := make(chan []byte, nb) - wg := sync.WaitGroup{} - wg.Add(2 * nb) - a.RLock() - defer a.RUnlock() - - for _, r := range recipients { - // Get the actual recipient - recipient, ok := r.(Recipient) - if !ok { - err := errors.New(errors.Structural, "Unable to interpret recipient as mqttRecipient") - a.ctx.WithField("recipient", r).Warn(err.Error()) - wg.Done() - wg.Done() - cherr <- err - continue + var reconnect func(fault error) + reconnect = func(fault error) { + if cli == nil { + ctx.Fatal("Attempt reconnection on non-existing client") } - - // Subscribe to down channel (before publishing anything) - chdown := make(chan []byte) - if recipient.TopicDown() != "" { - token := a.Subscribe(recipient.TopicDown(), 2, func(client MQTT.Client, msg MQTT.Message) { - chdown <- msg.Payload() - }) - if token.Wait() && token.Error() != nil { - err := errors.New(errors.Operational, "Unable to subscribe to down topic") - a.ctx.WithField("recipient", recipient).Warn(err.Error()) - wg.Done() - wg.Done() - cherr <- err - close(chdown) - continue - } + if delay > 10000*delay { + cli.Terminate() + ctx.WithError(fault).Fatal("Unable to reconnect the mqtt client") } - - // Publish on each topic - go func(recipient Recipient) { - defer wg.Done() - - ctx := a.ctx.WithField("topic", recipient.TopicUp()) - - // Publish packet - ctx.WithField("data", data).Debug("Publish data to mqtt") - token := a.Publish(recipient.TopicUp(), 2, false, data) - if token.Wait() && token.Error() != nil { - ctx.WithError(token.Error()).Error("Unable to publish") - cherr <- errors.New(errors.Operational, token.Error()) - return - } - }(recipient) - - // Avoid waiting for response when there's no topic down - if recipient.TopicDown() == "" { - a.ctx.WithField("recipient", recipient).Debug("No response expected from mqtt recipient") - wg.Done() - continue + <-time.After(delay) + if err := tryConnect(); err != nil { + delay *= 10 + ctx.WithError(err).Debugf("Failed to reconnect MQTT client. Trying again in %s", delay) + reconnect(fault) + return } + delay = 25 * time.Millisecond + } - // Pull responses from each down topic, expecting only one - go func(recipient Recipient, chdown <-chan []byte) { - defer wg.Done() - - ctx := a.ctx.WithField("topic", recipient.TopicDown()) - - ctx.Debug("Wait for mqtt response") - defer func(ctx log.Interface) { - if token := a.Unsubscribe(recipient.TopicDown()); token.Wait() && token.Error() != nil { - ctx.Warn("Unable to unsubscribe topic") - } - }(ctx) + cli = client.New(&client.Options{ErrorHandler: reconnect}) - // Forward the downlink response received if any - select { - case data, ok := <-chdown: - if ok { - chresp <- data - } - case <-time.After(timeout): // Timeout - } - }(recipient, chdown) + if err := tryConnect(); err != nil { + cli.Terminate() + return nil, errors.New(errors.Operational, err) } + return cli, nil +} - // Wait for each request to be done - stats.IncCounter("mqtt_adapter.waiting_for_send") - wg.Wait() - stats.DecCounter("mqtt_adapter.waiting_for_send") - close(cherr) - close(chresp) - - // Collect errors - errored := len(cherr) +// HandleData implements the core.AppClient interface +func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) { + stats.MarkMeter("mqtt_adapter.send") - // Collect response - if len(chresp) > 1 { - return nil, errors.New(errors.Behavioural, "Received too many positive answers") + // Verify the packet integrity + if len(req.Payload) == 0 { + stats.MarkMeter("mqtt_adapter.uplink.invalid") + return nil, errors.New(errors.Structural, "Invalid Packet Payload") } - - if len(chresp) == 0 && errored > 0 { - return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answers") + if len(req.DevEUI) != 8 { + stats.MarkMeter("mqtt_adapter.uplink.invalid") + return nil, errors.New(errors.Structural, "Invalid Device EUI") } - - if len(chresp) == 0 { - return nil, nil + if len(req.AppEUI) != 8 { + stats.MarkMeter("mqtt_adapter.uplink.invalid") + return nil, errors.New(errors.Structural, "Invalid Application EUI") } - return <-chresp, nil -} - -// GetRecipient implements the core.Adapter interface -func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { - recipient := new(recipient) - if err := recipient.UnmarshalBinary(raw); err != nil { - return nil, errors.New(errors.Structural, err) + if req.Metadata == nil { + stats.MarkMeter("mqtt_adapter.uplink.invalid") + return nil, errors.New(errors.Structural, "Missing Mandatory Metadata") } - return recipient, nil -} - -// Next implements the core.Adapter interface -func (a *Adapter) Next() ([]byte, core.AckNacker, error) { - p := <-a.packets - return p.Packet, mqttAckNacker{Chresp: p.Chresp}, nil -} + ctx := a.ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI) -// NextRegistration implements the core.Adapter interface. Not implemented for this adapters. -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - r := <-a.registrations - return r.Registration, mqttAckNacker{Chresp: r.Chresp}, nil -} + // Marshal the packet + appPayload := core.AppPayload{ + Payload: req.Payload, + Metadata: core.ProtoMetaToAppMeta(req.Metadata...), + } + msg, err := appPayload.MarshalMsg(nil) + if err != nil { + return nil, errors.New(errors.Structural, "Unable to marshal the application payload") + } -// Bind registers a handler to a specific endpoint -func (a *Adapter) Bind(h Handler) error { - a.RLock() - defer a.RUnlock() - ctx := a.ctx.WithField("topic", h.Topic()) - ctx.Info("Subscribe new handler") - token := a.Subscribe(h.Topic(), 2, func(client MQTT.Client, msg MQTT.Message) { - ctx.Debug("Handle new mqtt message") - if err := h.Handle(client, a.packets, a.registrations, msg); err != nil { - ctx.WithError(err).Warn("Unable to handle mqtt message") - } + // Actually send it + ctx.Debug("Sending Packet") + deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) + err = a.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: true, + TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)), + Message: msg, }) - if token.Wait() && token.Error() != nil { - ctx.WithError(token.Error()).Error("Unable to Subscribe") - return errors.New(errors.Operational, token.Error()) - } - return nil -} -// Reconnect allows the adapter to be reconnected with a new client -func (a *Adapter) Reconnect() error { - a.Lock() - defer a.Unlock() - a.Disconnect(25) - if token := a.Connect(); token.Wait() && token.Error() != nil { - return errors.New(errors.Operational, token.Error()) + if err != nil { + return nil, errors.New(errors.Operational, err) } - return nil + + return nil, nil } diff --git a/core/adapters/mqtt/mqttAckNacker.go b/core/adapters/mqtt/mqttAckNacker.go deleted file mode 100644 index ae27b3749..000000000 --- a/core/adapters/mqtt/mqttAckNacker.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// mqttAckNacker implements the core.AckNacker interface -type mqttAckNacker struct { - Chresp chan<- MsgRes // A channel dedicated to send back a response -} - -// Ack implements the core.AckNacker interface -func (an mqttAckNacker) Ack(p core.Packet) error { - if an.Chresp == nil || p == nil { - return nil - } - defer close(an.Chresp) - - if p == nil { - return nil - } - - data, err := p.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - - select { - case an.Chresp <- MsgRes(data): - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(errors.Operational, "No response was given to the acknacker") - } -} - -// Nack implements the core.AckNacker interface -func (an mqttAckNacker) Nack(err error) error { - if an.Chresp == nil { - return nil - } - defer close(an.Chresp) - return nil -} diff --git a/core/adapters/mqtt/mqttAckNacker_test.go b/core/adapters/mqtt/mqttAckNacker_test.go deleted file mode 100644 index 67bdd35c1..000000000 --- a/core/adapters/mqtt/mqttAckNacker_test.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestAckNacker(t *testing.T) { - { - Desc(t, "Ack a nil packet") - - // Build - chresp := make(chan MsgRes, 1) - an := mqttAckNacker{Chresp: chresp} - - // Operate - err := an.Ack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, chresp) - } - - // -------------------- - - { - Desc(t, "Ack on a nil chresp") - - // Build - an := mqttAckNacker{Chresp: nil} - - // Operate - err := an.Ack(mocks.NewMockPacket()) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, nil) - } - - // -------------------- - - { - Desc(t, "Ack a valid packet") - - // Build - chresp := make(chan MsgRes, 1) - an := mqttAckNacker{Chresp: chresp} - p := mocks.NewMockPacket() - p.OutMarshalBinary = []byte{14, 14, 14} - - // Operate - err := an.Ack(p) - - // Expectation - want := MsgRes(p.OutMarshalBinary) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, &want, chresp) - } - - // -------------------- - - { - Desc(t, "Ack an invalid packet") - - // Build - chresp := make(chan MsgRes, 1) - an := mqttAckNacker{Chresp: chresp} - p := mocks.NewMockPacket() - p.Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error") - - // Operate - err := an.Ack(p) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckResps(t, nil, chresp) - } - - // -------------------- - - { - Desc(t, "Don't consume chresp on Ack") - - // Build - chresp := make(chan MsgRes) - an := mqttAckNacker{Chresp: chresp} - - // Operate - cherr := make(chan error) - go func() { - cherr <- an.Ack(mocks.NewMockPacket()) - }() - - // Check - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 100): - } - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // -------------------- - - { - Desc(t, "Nack no error") - - // Build - chresp := make(chan MsgRes, 1) - an := mqttAckNacker{Chresp: chresp} - - // Operate - err := an.Nack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, chresp) - } - - // -------------------- - - { - Desc(t, "Nack on a nil chresp") - - // Build - an := mqttAckNacker{Chresp: nil} - - // Operate - err := an.Nack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, nil) - } -} - -func CheckResps(t *testing.T, want *MsgRes, got chan MsgRes) { - if want == nil { - if len(got) == 0 { - Ok(t, "Check Resps") - return - } - Ko(t, "Expected no message response but got one") - } - - if len(got) < 1 { - Ko(t, "Expected one message but got none") - } - - msg := <-got - mocks.Check(t, *want, msg, "Resps") -} diff --git a/core/adapters/mqtt/mqttRecipient.go b/core/adapters/mqtt/mqttRecipient.go deleted file mode 100644 index ae6bfde33..000000000 --- a/core/adapters/mqtt/mqttRecipient.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "encoding" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -// Recipient describes recipient manipulated by the mqtt adapter -type Recipient interface { - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler - TopicUp() string - TopicDown() string -} - -// recipient implements the MqttRecipient interface -type recipient struct { - up string - down string -} - -// NewRecipient creates a new MQTT recipient from two topics -func NewRecipient(up string, down string) Recipient { - return &recipient{up: up, down: down} -} - -// TopicUp implements the Recipient interface -func (r recipient) TopicUp() string { - return r.up -} - -// TopicDown implements the Recipient interface -func (r recipient) TopicDown() string { - return r.down -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (r recipient) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(r.up) - rw.Write(r.down) - - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (r *recipient) UnmarshalBinary(data []byte) error { - if r == nil { - return errors.New(errors.Structural, "Cannot unmarshal nil structure") - } - - rw := readwriter.New(data) - rw.Read(func(data []byte) { r.up = string(data) }) - rw.Read(func(data []byte) { r.down = string(data) }) - - if err := rw.Err(); err != nil { - return errors.New(errors.Structural, err) - } - return nil -} diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go deleted file mode 100644 index 933904c6f..000000000 --- a/core/adapters/mqtt/mqtt_test.go +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - "testing" - "time" - - MQTT "github.com/KtorZ/paho.mqtt.golang" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -const brokerURL = "0.0.0.0:1883" - -func TestMQTTSend(t *testing.T) { - timeout = 100 * time.Millisecond - - tests := []struct { - Desc string // Test Description - Packet []byte // Handy representation of the packet to send - Recipients []testRecipient // List of recipient to send - - WantData []byte // Expected Data on the recipient - WantResponse []byte // Expected Response from the Send method - WantError *string // Expected error nature returned by the Send method - }{ - { - Desc: "1 packet | 1 recipient | No response", - Packet: []byte("TheThingsNetwork"), - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up1", - TopicDown: "down1", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantResponse: nil, - WantError: nil, - }, - { - Desc: "1 packet | 1 recipient | No down topic", - Packet: []byte("TheThingsNetwork"), - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up1", - TopicDown: "", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantResponse: nil, - WantError: nil, - }, - { - Desc: "invalid packet | 1 recipient | No response", - Packet: nil, - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up1", - TopicDown: "down1", - }, - }, - - WantData: nil, - WantResponse: nil, - WantError: pointer.String(string(errors.Structural)), - }, - { - Desc: "1 packet | 2 recipient | No response", - Packet: []byte("TheThingsNetwork"), - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up1", - TopicDown: "down1", - }, - { - Response: nil, - TopicUp: "up2", - TopicDown: "down2", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantResponse: nil, - WantError: nil, - }, - { - Desc: "1 packet | 2 recipients | #1 answer ", - Packet: []byte("TheThingsNetwork"), - Recipients: []testRecipient{ - { - Response: []byte("IoT Rocks"), - TopicUp: "up1", - TopicDown: "down1", - }, - { - Response: nil, - TopicUp: "up2", - TopicDown: "down2", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantResponse: []byte("IoT Rocks"), - WantError: nil, - }, - { - Desc: "1 packet | 2 recipients | both answers ", - Packet: []byte("TheThingsNetwork"), - Recipients: []testRecipient{ - { - Response: []byte("IoT Rocks"), - TopicUp: "up1", - TopicDown: "down1", - }, - { - Response: []byte("IoT Rocks"), - TopicUp: "up2", - TopicDown: "down2", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantResponse: nil, - WantError: pointer.String(string(errors.Behavioural)), - }, - } - - for i, test := range tests { - // Describe - Desc(t, fmt.Sprintf("#%d: %s", i, test.Desc)) - - // Build - aclient, adapter := createAdapter(t) - sclients, chresp := createServers(test.Recipients) - <-time.After(time.Millisecond * 50) - - // Operate - resp, err := trySend(adapter, test.Packet, test.Recipients) - var data []byte - select { - case data = <-chresp: - case <-time.After(time.Millisecond * 100): - } - - // Check - CheckErrors(t, test.WantError, err) - checkData(t, test.WantData, data) - checkResponses(t, test.WantResponse, resp) - - // Clean - aclient.Disconnect(250) - for _, sclient := range sclients { - sclient.Disconnect(250) - } - } -} - -func TestSendErrorCases(t *testing.T) { - tests := []struct { - Desc string // Test Description - Packet []byte // Handy representation of the packet to send - Recipients []testRecipient // List of recipient to send - Client *MockClient // A mocked version of the client - - WantData []byte // Expected Data on the recipient - WantError *string // Expected error nature returned by the Send method - }{ - { - Desc: "1 packet | 1 Recipient | Error on publish", - Packet: []byte("TheThingsNetwork"), - Client: NewMockClient("Publish"), - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up", - TopicDown: "down", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantError: pointer.String(string(errors.Operational)), - }, - { - Desc: "1 packet | 1 Recipient | Error on Subscribe", - Packet: []byte("TheThingsNetwork"), - Client: NewMockClient("Subscribe"), - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up", - TopicDown: "down", - }, - }, - - WantData: nil, - WantError: pointer.String(string(errors.Operational)), - }, - { - Desc: "1 packet | 1 Recipient | Error on Unsubscribe", - Packet: []byte("TheThingsNetwork"), - Client: NewMockClient("Unsubscribe"), - Recipients: []testRecipient{ - { - Response: nil, - TopicUp: "up", - TopicDown: "down", - }, - }, - - WantData: []byte("TheThingsNetwork"), - WantError: nil, - }, - } - - for i, test := range tests { - // Describe - Desc(t, fmt.Sprintf("#%d: %s", i, test.Desc)) - - // Build - adapter := NewAdapter(test.Client, GetLogger(t, "Adapter")) - - // Operate - _, err := trySend(adapter, test.Packet, test.Recipients) - - // Check - CheckErrors(t, test.WantError, err) - checkData(t, test.WantData, test.Client.InPublish.Payload()) - } -} - -func TestOtherMethods(t *testing.T) { - { - // Describe - Desc(t, "Get Recipient | Wrong data") - - // Build - adapter := NewAdapter(NewMockClient(), GetLogger(t, "Adapter")) - - // Operate - _, err := adapter.GetRecipient([]byte{}) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - // -------------------- - - { - // Describe - Desc(t, "Get Recipient | Valid Recipient") - - // Build - adapter := NewAdapter(NewMockClient(), GetLogger(t, "Adapter")) - data, _ := NewRecipient("up", "down").MarshalBinary() - - // Operate - recipient, err := adapter.GetRecipient(data) - - // Check - CheckErrors(t, nil, err) - checkRecipients(t, NewRecipient("up", "down"), recipient) - - } - - // -------------------- - - { - // Describe - Desc(t, "Send invalid recipients") - - // Build - adapter := NewAdapter(NewMockClient(), GetLogger(t, "Adapter")) - - // Operate - _, err := adapter.Send(mocks.NewMockPacket(), mocks.NewMockRecipient()) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // -------------------- - - { - // Describe - Desc(t, "Bind a new handler") - - // Build - client := NewMockClient() - adapter := NewAdapter(client, GetLogger(t, "Adapter")) - handler := NewMockHandler() - msg := MockMessage{ - topic: "MessageTopic", - payload: []byte{1, 2, 3, 4}, - } - - // Operate - err := adapter.Bind(handler) - client.InSubscribeCallBack(client, msg) - - // Check - CheckErrors(t, nil, err) - checkMessages(t, msg, handler.InMessage) - } - - // -------------------- - - { - // Describe - Desc(t, "Bind a new handler | fails to handle") - - // Build - client := NewMockClient() - adapter := NewAdapter(client, GetLogger(t, "Adapter")) - handler := NewMockHandler() - handler.Failures["Handle"] = errors.New(errors.Operational, "Mock Error") - msg := MockMessage{ - topic: "MessageTopic", - payload: []byte{1, 2, 3, 4}, - } - - // Operate - err := adapter.Bind(handler) - client.InSubscribeCallBack(client, msg) - - // Check - CheckErrors(t, nil, err) - checkMessages(t, msg, handler.InMessage) - } - - // -------------------- - - { - // Describe - Desc(t, "Bind a new handler | fails to subscribe") - - // Build - client := NewMockClient("Subscribe") - adapter := NewAdapter(client, GetLogger(t, "Adapter")) - handler := NewMockHandler() - - // Operate - err := adapter.Bind(handler) - - // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } -} - -func TestMQTTRecipient(t *testing.T) { - { - Desc(t, "Marshal / Unmarshal valid recipient") - rm := NewRecipient("topicup", "topicdown") - ru := new(recipient) - data, err := rm.MarshalBinary() - if err == nil { - err = ru.UnmarshalBinary(data) - } - CheckErrors(t, nil, err) - } - - { - Desc(t, "Unmarshal from nil pointer") - rm := NewRecipient("topicup", "topicdown") - var ru *recipient - data, err := rm.MarshalBinary() - if err == nil { - err = ru.UnmarshalBinary(data) - } - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - { - Desc(t, "Unmarshal nil data") - ru := new(recipient) - err := ru.UnmarshalBinary(nil) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - { - Desc(t, "Unmarshal wrong data") - ru := new(recipient) - err := ru.UnmarshalBinary([]byte{1, 2, 3, 4}) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } -} - -// ----- TYPE utilities -type testRecipient struct { - Response []byte - TopicUp string - TopicDown string -} - -type testPacket struct { - payload []byte -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p testPacket) MarshalBinary() ([]byte, error) { - if p.payload == nil { - return nil, errors.New(errors.Structural, "Fake error") - } - - return p.payload, nil -} - -// String implements the core.Packet interface -func (p testPacket) String() string { - return string(p.payload) -} - -// DevEUI implements the core.Packet interface -func (p testPacket) DevEUI() lorawan.EUI64 { - return lorawan.EUI64{} -} - -// ----- BUILD utilities -func createAdapter(t *testing.T) (MQTT.Client, core.Adapter) { - client, _, err := NewClient("testClient", brokerURL, TCP) - if err != nil { - panic(err) - } - - adapter := NewAdapter(client, GetLogger(t, "adapter")) - return client, adapter -} - -func createServers(recipients []testRecipient) ([]MQTT.Client, chan []byte) { - var clients []MQTT.Client - chresp := make(chan []byte, len(recipients)) - for i, r := range recipients { - client, _, err := NewClient(fmt.Sprintf("Client%d%d", i, time.Now().UnixNano()), brokerURL, TCP) - if err != nil { - panic(err) - } - clients = append(clients, client) - - go func(r testRecipient, client MQTT.Client) { - token := client.Subscribe(r.TopicUp, 2, func(client MQTT.Client, msg MQTT.Message) { - if r.Response != nil { - token := client.Publish(r.TopicDown, 2, false, r.Response) - if token.Wait() && token.Error() != nil { - panic(token.Error()) - } - } - chresp <- msg.Payload() - }) - if token.Wait() && token.Error() != nil { - panic(token.Error()) - } - }(r, client) - } - return clients, chresp -} - -// ----- OPERATE utilities -func trySend(adapter core.Adapter, packet []byte, recipients []testRecipient) ([]byte, error) { - // Convert testRecipient to core.Recipient using the mqtt recipient - var coreRecipients []core.Recipient - for _, r := range recipients { - coreRecipients = append(coreRecipients, NewRecipient(r.TopicUp, r.TopicDown)) - } - - // Try send the packet - chresp := make(chan struct { - Data []byte - Error error - }) - go func() { - data, err := adapter.Send(testPacket{packet}, coreRecipients...) - chresp <- struct { - Data []byte - Error error - }{data, err} - }() - - select { - case resp := <-chresp: - return resp.Data, resp.Error - case <-time.After(timeout + time.Millisecond*100): - return nil, nil - } -} - -// ----- CHECK utilities -func checkResponses(t *testing.T, want []byte, got []byte) { - mocks.Check(t, want, got, "Responses") -} - -func checkData(t *testing.T, want []byte, got []byte) { - mocks.Check(t, want, got, "Data") -} - -func checkRecipients(t *testing.T, want core.Recipient, got core.Recipient) { - mocks.Check(t, want, got, "Recipients") -} - -func checkMessages(t *testing.T, want MQTT.Message, got MQTT.Message) { - mocks.Check(t, want, got, "Messages") -} diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index d08d83745..474ff3b1c 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -351,6 +351,7 @@ browseBundles: // Then create an application-level packet and send it to the wild open // we don't expect a response from the adapter, end of the chain. _, err = h.adapter.HandleData(context.Background(), &core.DataAppReq{ + AppEUI: b.Packet.AppEUI, DevEUI: b.Packet.DevEUI, Payload: payload, Metadata: metadata, From 73bc034307af31a22aa9e63d9d4ea77e8a5e9263 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 16 Mar 2016 11:44:23 +0100 Subject: [PATCH 1056/2266] [refactor/grpc] Avoid mutate internal state after instantiation in handler --- core/components/handler/handler.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 474ff3b1c..63e0ab47e 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -42,19 +42,29 @@ type bundle struct { } // New construct a new Handler -func New(devDb DevStorage, pktDb PktStorage, broker core.BrokerClient, adapter core.AppClient, ctx log.Interface) core.HandlerServer { - return &component{ +func New(devDb DevStorage, pktDb PktStorage, broker core.BrokerClient, adapter core.AppClient, netAddr string, ctx log.Interface) core.HandlerServer { + h := &component{ ctx: ctx, devices: devDb, packets: pktDb, broker: broker, adapter: adapter, + netAddr: netAddr, } + + set := make(chan bundle) + bundles := make(chan []bundle) + + h.set = set + go h.consumeBundles(bundles) + go h.consumeSet(bundles, set) + + return h } // Start actually runs the component and starts the rpc server func (h *component) Start(netAddr string) error { - conn, err := net.Listen("tcp", netAddr) + conn, err := net.Listen("tcp", h.netAddr) if err != nil { return errors.New(errors.Operational, err) } @@ -62,15 +72,6 @@ func (h *component) Start(netAddr string) error { server := grpc.NewServer() core.RegisterHandlerServer(server, h) - set := make(chan bundle) - h.set = set - bundles := make(chan []bundle) - go h.consumeBundles(bundles) - go h.consumeSet(bundles, set) - - defer close(bundles) - defer close(set) - if err := server.Serve(conn); err != nil { return errors.New(errors.Operational, err) } From c08db0dfe971ea46bd5063a39c08f112e55cae1f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 16 Mar 2016 13:27:38 +0100 Subject: [PATCH 1057/2266] [refactor/grpc] Reimplement proper downlink and abp with mqtt adapter --- core/adapters/mqtt/mqtt.go | 162 +++++++++++- core/applicationPayload_gen_test.go | 127 --------- core/broker.pb.go | 90 +++---- core/handler.pb.go | 112 ++++---- core/{applicationPayload.go => msgp.go} | 16 +- ...{applicationPayload_gen.go => msgp_gen.go} | 128 ++++++++- core/msgp_gen_test.go | 243 ++++++++++++++++++ core/protos/broker.proto | 6 +- core/protos/handler.proto | 6 +- 9 files changed, 634 insertions(+), 256 deletions(-) delete mode 100644 core/applicationPayload_gen_test.go rename core/{applicationPayload.go => msgp.go} (74%) rename core/{applicationPayload_gen.go => msgp_gen.go} (60%) create mode 100644 core/msgp_gen_test.go diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 4c5ca5827..a33a48ea5 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -5,7 +5,9 @@ package mqtt import ( "encoding/hex" + "encoding/json" "fmt" + "strings" "time" "github.com/TheThingsNetwork/ttn/core" @@ -20,32 +22,84 @@ import ( type adapter struct { *client.Client - Handler core.HandlerClient + handler core.HandlerClient ctx log.Interface } +// Msg are emitted by an MQTT subscriber towards the adapter +type Msg struct { + Topic string + Payload []byte + Type msgType +} + +type msgType byte + +// msgType constants are used in MQTTMsg to characterise the kind of message processed +const ( + Down msgType = iota + ABP + OTAA +) + // New constructs an mqtt adapter responsible for making the bridge between the handler and // application. -func New(handler core.HandlerClient, client *client.Client, ctx log.Interface) core.AppClient { - return adapter{ +func New(handler core.HandlerClient, client *client.Client, chmsg <-chan Msg, ctx log.Interface) core.AppClient { + a := adapter{ Client: client, - Handler: handler, + handler: handler, ctx: ctx, } + + go a.consumeMQTTMsg(chmsg) + + return a } // NewClient creates and connects a mqtt client with predefined options. -func NewClient(id string, netAddr string, ctx log.Interface) (*client.Client, error) { +func newClient(id string, netAddr string, ctx log.Interface) (*client.Client, chan Msg, error) { var cli *client.Client - var delay time.Duration = 25 * time.Millisecond + delay := 25 * time.Millisecond + chmsg := make(chan Msg) tryConnect := func() error { ctx.WithField("id", id).WithField("address", netAddr).Debug("(Re)Connecting MQTT Client") - return cli.Connect(&client.ConnectOptions{ + err := cli.Connect(&client.ConnectOptions{ Network: "tcp", Address: netAddr, ClientID: []byte(id), }) + + if err != nil { + return err + } + + return cli.Subscribe(&client.SubscribeOptions{ + SubReqs: []*client.SubReq{ + &client.SubReq{ + TopicFilter: []byte("+/devices/+/down"), + QoS: mqtt.QoS2, + Handler: func(topic, msg []byte) { + chmsg <- Msg{ + Topic: string(topic), + Payload: msg, + Type: Down, + } + }, + }, + &client.SubReq{ + TopicFilter: []byte("+/devices/personalized/activations"), + QoS: mqtt.QoS2, + Handler: func(topic, msg []byte) { + chmsg <- Msg{ + Topic: string(topic), + Payload: msg, + Type: ABP, + } + }, + }, + }, + }) } var reconnect func(fault error) @@ -71,9 +125,9 @@ func NewClient(id string, netAddr string, ctx log.Interface) (*client.Client, er if err := tryConnect(); err != nil { cli.Terminate() - return nil, errors.New(errors.Operational, err) + return nil, nil, errors.New(errors.Operational, err) } - return cli, nil + return cli, chmsg, nil } // HandleData implements the core.AppClient interface @@ -100,11 +154,11 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp ctx := a.ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI) // Marshal the packet - appPayload := core.AppPayload{ + dataUp := core.DataUpAppReq{ Payload: req.Payload, Metadata: core.ProtoMetaToAppMeta(req.Metadata...), } - msg, err := appPayload.MarshalMsg(nil) + msg, err := dataUp.MarshalMsg(nil) if err != nil { return nil, errors.New(errors.Structural, "Unable to marshal the application payload") } @@ -125,3 +179,89 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp return nil, nil } + +// consumeMQTTMsg processes incoming messages from MQTT broker. +// +// It runs in its own goroutine +func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { + a.ctx.Debug("Start consuming MQTT message") + for msg := range chmsg { + switch msg.Type { + case Down: + req, err := a.handleDataDown(msg) + if err == nil { + _, err = a.handler.HandleDataDown(context.Background(), req) + } + if err != nil { + a.ctx.WithError(err).Debug("Unable to consume data down") + } + case ABP: + req, err := a.handleABP(msg) + if err == nil { + _, err = a.handler.SubscribePersonalized(context.Background(), req) + } + if err != nil { + a.ctx.WithError(err).Debug("Unable to consume ABP") + } + default: + a.ctx.Debug("Unsupported MQTT message's type") + } + } +} + +// handleABP parses and handles Application By Personalization request coming through MQTT +func (a adapter) handleABP(msg Msg) (*core.ABPSubHandlerReq, error) { + // Ensure the query / topic parameters are valid + topicInfos := strings.Split(msg.Topic, "/") + if len(topicInfos) != 4 { + return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") + } + appEUI, err := hex.DecodeString(topicInfos[0]) + if err != nil || len(appEUI) != 8 { + return nil, errors.New(errors.Structural, "Invalid Application EUI") + } + if topicInfos[2] != "personalized" { + return nil, errors.New(errors.Implementation, "OTAA not yet supported. Unable to register device") + } + + // Get the actual message, try messagePack then JSON + var req core.APBSubAppReq + if _, err := req.UnmarshalMsg(msg.Payload); err != nil { + if err = json.Unmarshal(msg.Payload, &req); err != nil { + return nil, errors.New(errors.Structural, err) + } + } + + // Convert it to an handler subscription + return &core.ABPSubHandlerReq{ + AppEUI: appEUI, + DevAddr: req.DevAddr[:], + NwkSKey: req.NwkSKey[:], + AppSKey: req.AppSKey[:], + }, nil +} + +// handleDataDown parses and handles Downlink message coming through MQTT +func (a adapter) handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { + // Ensure the query / topic parameters are valid + topicInfos := strings.Split(msg.Topic, "/") + if len(topicInfos) != 4 { + return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") + } + appEUI, erra := hex.DecodeString(topicInfos[0]) + devEUI, errd := hex.DecodeString(topicInfos[2]) + if erra != nil || errd != nil || len(appEUI) != 8 || len(devEUI) != 8 { + return nil, errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI") + } + + if len(msg.Payload) == 0 { + return nil, errors.New(errors.Structural, "There's no data to handle") + } + + // Convert it to an handler downlink + return &core.DataDownHandlerReq{ + Payload: msg.Payload, + AppEUI: appEUI, + DevEUI: devEUI, + }, nil +} diff --git a/core/applicationPayload_gen_test.go b/core/applicationPayload_gen_test.go deleted file mode 100644 index 577ce4b84..000000000 --- a/core/applicationPayload_gen_test.go +++ /dev/null @@ -1,127 +0,0 @@ -package core - -// NOTE: THIS FILE WAS PRODUCED BY THE -// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp) -// DO NOT EDIT - -import ( - "testing" - - "github.com/tinylib/msgp/msgp" -) - -func TestMarshalUnmarshalAppMetadata(t *testing.T) { - v := AppMetadata{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgAppMetadata(b *testing.B) { - v := AppMetadata{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgAppMetadata(b *testing.B) { - v := AppMetadata{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalAppMetadata(b *testing.B) { - v := AppMetadata{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalAppPayload(t *testing.T) { - v := AppPayload{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgAppPayload(b *testing.B) { - v := AppPayload{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgAppPayload(b *testing.B) { - v := AppPayload{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalAppPayload(b *testing.B) { - v := AppPayload{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/core/broker.pb.go b/core/broker.pb.go index 54771f930..cdc72cb61 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -16,8 +16,8 @@ It has these top-level messages: DataBrokerReq DataBrokerRes - SubPersoBrokerReq - SubPersoBrokerRes + ABPSubBrokerReq + ABPSubBrokerRes Metadata StatsMetadata DataAppReq @@ -26,8 +26,8 @@ DataUpHandlerRes DataDownHandlerReq DataDownHandlerRes - SubPersoHandlerReq - SubPersoHandlerRes + ABPSubHandlerReq + ABPSubHandlerRes DataRouterReq DataRouterRes StatsReq @@ -111,31 +111,31 @@ func (m *DataBrokerRes) GetMetadata() *Metadata { return nil } -type SubPersoBrokerReq struct { +type ABPSubBrokerReq struct { HandlerNet string `protobuf:"bytes,1,opt,name=HandlerNet,json=handlerNet,proto3" json:"HandlerNet,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` } -func (m *SubPersoBrokerReq) Reset() { *m = SubPersoBrokerReq{} } -func (m *SubPersoBrokerReq) String() string { return proto.CompactTextString(m) } -func (*SubPersoBrokerReq) ProtoMessage() {} -func (*SubPersoBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } +func (m *ABPSubBrokerReq) Reset() { *m = ABPSubBrokerReq{} } +func (m *ABPSubBrokerReq) String() string { return proto.CompactTextString(m) } +func (*ABPSubBrokerReq) ProtoMessage() {} +func (*ABPSubBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } -type SubPersoBrokerRes struct { +type ABPSubBrokerRes struct { } -func (m *SubPersoBrokerRes) Reset() { *m = SubPersoBrokerRes{} } -func (m *SubPersoBrokerRes) String() string { return proto.CompactTextString(m) } -func (*SubPersoBrokerRes) ProtoMessage() {} -func (*SubPersoBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } +func (m *ABPSubBrokerRes) Reset() { *m = ABPSubBrokerRes{} } +func (m *ABPSubBrokerRes) String() string { return proto.CompactTextString(m) } +func (*ABPSubBrokerRes) ProtoMessage() {} +func (*ABPSubBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } func init() { proto.RegisterType((*DataBrokerReq)(nil), "core.DataBrokerReq") proto.RegisterType((*DataBrokerRes)(nil), "core.DataBrokerRes") - proto.RegisterType((*SubPersoBrokerReq)(nil), "core.SubPersoBrokerReq") - proto.RegisterType((*SubPersoBrokerRes)(nil), "core.SubPersoBrokerRes") + proto.RegisterType((*ABPSubBrokerReq)(nil), "core.ABPSubBrokerReq") + proto.RegisterType((*ABPSubBrokerRes)(nil), "core.ABPSubBrokerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -146,7 +146,7 @@ var _ grpc.ClientConn type BrokerClient interface { HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) - SubscribePersonalized(ctx context.Context, in *SubPersoBrokerReq, opts ...grpc.CallOption) (*SubPersoBrokerRes, error) + SubscribePersonalized(ctx context.Context, in *ABPSubBrokerReq, opts ...grpc.CallOption) (*ABPSubBrokerRes, error) } type brokerClient struct { @@ -166,8 +166,8 @@ func (c *brokerClient) HandleData(ctx context.Context, in *DataBrokerReq, opts . return out, nil } -func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *SubPersoBrokerReq, opts ...grpc.CallOption) (*SubPersoBrokerRes, error) { - out := new(SubPersoBrokerRes) +func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *ABPSubBrokerReq, opts ...grpc.CallOption) (*ABPSubBrokerRes, error) { + out := new(ABPSubBrokerRes) err := grpc.Invoke(ctx, "/core.Broker/SubscribePersonalized", in, out, c.cc, opts...) if err != nil { return nil, err @@ -179,7 +179,7 @@ func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *SubPersoBr type BrokerServer interface { HandleData(context.Context, *DataBrokerReq) (*DataBrokerRes, error) - SubscribePersonalized(context.Context, *SubPersoBrokerReq) (*SubPersoBrokerRes, error) + SubscribePersonalized(context.Context, *ABPSubBrokerReq) (*ABPSubBrokerRes, error) } func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { @@ -199,7 +199,7 @@ func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(i } func _Broker_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(SubPersoBrokerReq) + in := new(ABPSubBrokerReq) if err := dec(in); err != nil { return nil, err } @@ -302,7 +302,7 @@ func (m *DataBrokerRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubPersoBrokerReq) Marshal() (data []byte, err error) { +func (m *ABPSubBrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -312,7 +312,7 @@ func (m *SubPersoBrokerReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *SubPersoBrokerReq) MarshalTo(data []byte) (int, error) { +func (m *ABPSubBrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -350,7 +350,7 @@ func (m *SubPersoBrokerReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubPersoBrokerRes) Marshal() (data []byte, err error) { +func (m *ABPSubBrokerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -360,7 +360,7 @@ func (m *SubPersoBrokerRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *SubPersoBrokerRes) MarshalTo(data []byte) (int, error) { +func (m *ABPSubBrokerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -423,7 +423,7 @@ func (m *DataBrokerRes) Size() (n int) { return n } -func (m *SubPersoBrokerReq) Size() (n int) { +func (m *ABPSubBrokerReq) Size() (n int) { var l int _ = l l = len(m.HandlerNet) @@ -451,7 +451,7 @@ func (m *SubPersoBrokerReq) Size() (n int) { return n } -func (m *SubPersoBrokerRes) Size() (n int) { +func (m *ABPSubBrokerRes) Size() (n int) { var l int _ = l return n @@ -702,7 +702,7 @@ func (m *DataBrokerRes) Unmarshal(data []byte) error { } return nil } -func (m *SubPersoBrokerReq) Unmarshal(data []byte) error { +func (m *ABPSubBrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -725,10 +725,10 @@ func (m *SubPersoBrokerReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SubPersoBrokerReq: wiretype end group for non-group") + return fmt.Errorf("proto: ABPSubBrokerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SubPersoBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ABPSubBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -874,7 +874,7 @@ func (m *SubPersoBrokerReq) Unmarshal(data []byte) error { } return nil } -func (m *SubPersoBrokerRes) Unmarshal(data []byte) error { +func (m *ABPSubBrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -897,10 +897,10 @@ func (m *SubPersoBrokerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SubPersoBrokerRes: wiretype end group for non-group") + return fmt.Errorf("proto: ABPSubBrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SubPersoBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ABPSubBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1039,16 +1039,16 @@ var fileDescriptorBroker = []byte{ 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xd5, - 0x73, 0x09, 0x06, 0x97, 0x26, 0x05, 0xa4, 0x16, 0x15, 0xe7, 0x23, 0xfc, 0x25, 0xc7, 0xc5, 0xe5, - 0x91, 0x98, 0x97, 0x92, 0x93, 0x5a, 0xe4, 0x97, 0x5a, 0x02, 0xb6, 0x90, 0x33, 0x88, 0x2b, 0x03, - 0x2e, 0x22, 0x24, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0x36, 0x9e, 0x27, 0x88, - 0x2d, 0x11, 0xcc, 0x13, 0x92, 0xe0, 0x62, 0x77, 0x49, 0x2d, 0x73, 0x4c, 0x49, 0x29, 0x92, 0x60, - 0x06, 0x4b, 0xb0, 0xa7, 0x40, 0xb8, 0x20, 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, - 0x16, 0x88, 0x4c, 0x1e, 0x84, 0xab, 0x24, 0x8c, 0xe9, 0x80, 0x62, 0xa3, 0x6e, 0x46, 0x2e, 0x36, - 0x08, 0x4f, 0xc8, 0x0c, 0xe6, 0x16, 0x90, 0x1f, 0x85, 0x84, 0x21, 0x1e, 0x41, 0x89, 0x06, 0x29, - 0x2c, 0x82, 0xc5, 0x42, 0x9e, 0x5c, 0xa2, 0x40, 0x73, 0x8b, 0x93, 0x8b, 0x32, 0x93, 0x52, 0xc1, - 0xa6, 0xe7, 0x25, 0xe6, 0x64, 0x56, 0xa5, 0xa6, 0x08, 0x89, 0x43, 0x54, 0x63, 0xf8, 0x5a, 0x0a, - 0x87, 0x44, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, - 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x4e, 0x10, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9b, 0x59, - 0x69, 0x59, 0x41, 0x02, 0x00, 0x00, + 0x72, 0xf1, 0x3b, 0x3a, 0x05, 0x04, 0x97, 0x26, 0x21, 0x7c, 0x25, 0xc7, 0xc5, 0xe5, 0x91, 0x98, + 0x97, 0x92, 0x93, 0x5a, 0xe4, 0x97, 0x5a, 0x02, 0xb6, 0x8e, 0x33, 0x88, 0x2b, 0x03, 0x2e, 0x22, + 0x24, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0x36, 0x9c, 0x27, 0x88, 0x2d, 0x11, + 0xcc, 0x13, 0x92, 0xe0, 0x62, 0x77, 0x49, 0x2d, 0x73, 0x4c, 0x49, 0x29, 0x92, 0x60, 0x06, 0x4b, + 0xb0, 0xa7, 0x40, 0xb8, 0x20, 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x16, 0x88, + 0x4c, 0x1e, 0x84, 0xab, 0x24, 0x88, 0x6e, 0x7d, 0xb1, 0x51, 0x3b, 0x23, 0x17, 0x1b, 0x84, 0x27, + 0x64, 0x06, 0x73, 0x09, 0xc8, 0x7f, 0x42, 0xc2, 0x10, 0x4f, 0xa0, 0x44, 0x81, 0x14, 0x16, 0xc1, + 0x62, 0x21, 0x57, 0x2e, 0x51, 0xa0, 0x91, 0xc5, 0xc9, 0x45, 0x99, 0x49, 0xa9, 0x01, 0xa9, 0x45, + 0xc5, 0xf9, 0x79, 0x89, 0x39, 0x99, 0x55, 0xa9, 0x29, 0x42, 0xa2, 0x10, 0xd5, 0x68, 0x3e, 0x96, + 0xc2, 0x2a, 0x5c, 0xec, 0x24, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x05, 0x20, 0x7e, 0x00, 0xc4, 0x33, + 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x13, 0x82, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x77, + 0xff, 0xc3, 0x39, 0x02, 0x00, 0x00, } diff --git a/core/handler.pb.go b/core/handler.pb.go index 67f7bfcfc..cf84e602e 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -84,33 +84,33 @@ func (m *DataDownHandlerRes) String() string { return proto.CompactTe func (*DataDownHandlerRes) ProtoMessage() {} func (*DataDownHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } -type SubPersoHandlerReq struct { +type ABPSubHandlerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` } -func (m *SubPersoHandlerReq) Reset() { *m = SubPersoHandlerReq{} } -func (m *SubPersoHandlerReq) String() string { return proto.CompactTextString(m) } -func (*SubPersoHandlerReq) ProtoMessage() {} -func (*SubPersoHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } +func (m *ABPSubHandlerReq) Reset() { *m = ABPSubHandlerReq{} } +func (m *ABPSubHandlerReq) String() string { return proto.CompactTextString(m) } +func (*ABPSubHandlerReq) ProtoMessage() {} +func (*ABPSubHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } -type SubPersoHandlerRes struct { +type ABPSubHandlerRes struct { } -func (m *SubPersoHandlerRes) Reset() { *m = SubPersoHandlerRes{} } -func (m *SubPersoHandlerRes) String() string { return proto.CompactTextString(m) } -func (*SubPersoHandlerRes) ProtoMessage() {} -func (*SubPersoHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } +func (m *ABPSubHandlerRes) Reset() { *m = ABPSubHandlerRes{} } +func (m *ABPSubHandlerRes) String() string { return proto.CompactTextString(m) } +func (*ABPSubHandlerRes) ProtoMessage() {} +func (*ABPSubHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } func init() { proto.RegisterType((*DataUpHandlerReq)(nil), "core.DataUpHandlerReq") proto.RegisterType((*DataUpHandlerRes)(nil), "core.DataUpHandlerRes") proto.RegisterType((*DataDownHandlerReq)(nil), "core.DataDownHandlerReq") proto.RegisterType((*DataDownHandlerRes)(nil), "core.DataDownHandlerRes") - proto.RegisterType((*SubPersoHandlerReq)(nil), "core.SubPersoHandlerReq") - proto.RegisterType((*SubPersoHandlerRes)(nil), "core.SubPersoHandlerRes") + proto.RegisterType((*ABPSubHandlerReq)(nil), "core.ABPSubHandlerReq") + proto.RegisterType((*ABPSubHandlerRes)(nil), "core.ABPSubHandlerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -122,7 +122,7 @@ var _ grpc.ClientConn type HandlerClient interface { HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) - SubscribePersonalized(ctx context.Context, in *SubPersoHandlerReq, opts ...grpc.CallOption) (*SubPersoHandlerRes, error) + SubscribePersonalized(ctx context.Context, in *ABPSubHandlerReq, opts ...grpc.CallOption) (*ABPSubHandlerRes, error) } type handlerClient struct { @@ -151,8 +151,8 @@ func (c *handlerClient) HandleDataDown(ctx context.Context, in *DataDownHandlerR return out, nil } -func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *SubPersoHandlerReq, opts ...grpc.CallOption) (*SubPersoHandlerRes, error) { - out := new(SubPersoHandlerRes) +func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *ABPSubHandlerReq, opts ...grpc.CallOption) (*ABPSubHandlerRes, error) { + out := new(ABPSubHandlerRes) err := grpc.Invoke(ctx, "/core.Handler/SubscribePersonalized", in, out, c.cc, opts...) if err != nil { return nil, err @@ -165,7 +165,7 @@ func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *SubPersoH type HandlerServer interface { HandleDataUp(context.Context, *DataUpHandlerReq) (*DataUpHandlerRes, error) HandleDataDown(context.Context, *DataDownHandlerReq) (*DataDownHandlerRes, error) - SubscribePersonalized(context.Context, *SubPersoHandlerReq) (*SubPersoHandlerRes, error) + SubscribePersonalized(context.Context, *ABPSubHandlerReq) (*ABPSubHandlerRes, error) } func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { @@ -197,7 +197,7 @@ func _Handler_HandleDataDown_Handler(srv interface{}, ctx context.Context, dec f } func _Handler_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(SubPersoHandlerReq) + in := new(ABPSubHandlerReq) if err := dec(in); err != nil { return nil, err } @@ -388,7 +388,7 @@ func (m *DataDownHandlerRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubPersoHandlerReq) Marshal() (data []byte, err error) { +func (m *ABPSubHandlerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -398,7 +398,7 @@ func (m *SubPersoHandlerReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *SubPersoHandlerReq) MarshalTo(data []byte) (int, error) { +func (m *ABPSubHandlerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -438,7 +438,7 @@ func (m *SubPersoHandlerReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubPersoHandlerRes) Marshal() (data []byte, err error) { +func (m *ABPSubHandlerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -448,7 +448,7 @@ func (m *SubPersoHandlerRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *SubPersoHandlerRes) MarshalTo(data []byte) (int, error) { +func (m *ABPSubHandlerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -561,7 +561,7 @@ func (m *DataDownHandlerRes) Size() (n int) { return n } -func (m *SubPersoHandlerReq) Size() (n int) { +func (m *ABPSubHandlerReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -591,7 +591,7 @@ func (m *SubPersoHandlerReq) Size() (n int) { return n } -func (m *SubPersoHandlerRes) Size() (n int) { +func (m *ABPSubHandlerRes) Size() (n int) { var l int _ = l return n @@ -1133,7 +1133,7 @@ func (m *DataDownHandlerRes) Unmarshal(data []byte) error { } return nil } -func (m *SubPersoHandlerReq) Unmarshal(data []byte) error { +func (m *ABPSubHandlerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1156,10 +1156,10 @@ func (m *SubPersoHandlerReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SubPersoHandlerReq: wiretype end group for non-group") + return fmt.Errorf("proto: ABPSubHandlerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SubPersoHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ABPSubHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1307,7 +1307,7 @@ func (m *SubPersoHandlerReq) Unmarshal(data []byte) error { } return nil } -func (m *SubPersoHandlerRes) Unmarshal(data []byte) error { +func (m *ABPSubHandlerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1330,10 +1330,10 @@ func (m *SubPersoHandlerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: SubPersoHandlerRes: wiretype end group for non-group") + return fmt.Errorf("proto: ABPSubHandlerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: SubPersoHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ABPSubHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1463,31 +1463,31 @@ var ( ) var fileDescriptorHandler = []byte{ - // 404 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x4e, 0xea, 0x40, - 0x14, 0xbe, 0x85, 0xd2, 0x92, 0xe1, 0x27, 0xdc, 0x09, 0x97, 0x34, 0x5d, 0x90, 0x9b, 0xae, 0x8c, - 0x26, 0x2c, 0x70, 0x6f, 0x82, 0x56, 0xa3, 0x41, 0x08, 0x29, 0x12, 0x77, 0x26, 0x03, 0x33, 0x46, - 0x42, 0xe9, 0xd4, 0x4e, 0x95, 0xc0, 0x93, 0xf8, 0x1c, 0x3e, 0x85, 0x4b, 0x1f, 0xc0, 0x85, 0xd1, - 0x17, 0x71, 0x7e, 0x4a, 0x0a, 0x02, 0x89, 0x71, 0x31, 0xc9, 0xf9, 0xbe, 0x33, 0xe7, 0x3b, 0xdf, - 0x39, 0xd3, 0x82, 0xd2, 0x1d, 0x0a, 0xb0, 0x4f, 0xa2, 0x46, 0x18, 0xd1, 0x98, 0x42, 0x7d, 0x44, - 0x23, 0x62, 0x97, 0x7c, 0x1a, 0xa1, 0x19, 0x0a, 0x14, 0x69, 0x03, 0x41, 0xaa, 0xd8, 0x79, 0xd6, - 0x40, 0xc5, 0x45, 0x31, 0x1a, 0x84, 0xe7, 0xaa, 0xd0, 0x23, 0xf7, 0xd0, 0x02, 0x66, 0x0f, 0xcd, - 0x7d, 0x8a, 0xb0, 0xa5, 0xfd, 0xd7, 0xf6, 0x8a, 0x9e, 0x19, 0x2a, 0x08, 0xf7, 0x41, 0xbe, 0x43, - 0x62, 0x84, 0x79, 0x85, 0x95, 0xe1, 0xa9, 0x42, 0xb3, 0xdc, 0x90, 0x6a, 0x4b, 0xd6, 0xcb, 0x4f, - 0x93, 0x08, 0xd6, 0x80, 0xd1, 0x0a, 0xc3, 0xd3, 0xc1, 0x85, 0x95, 0x95, 0x22, 0x06, 0x92, 0x48, - 0xf0, 0x2e, 0x79, 0x14, 0xbc, 0xae, 0x78, 0x2c, 0x11, 0x84, 0x40, 0x3f, 0x3b, 0x09, 0x62, 0x2b, - 0xc7, 0xd9, 0x92, 0xa7, 0xdf, 0xf2, 0x18, 0x56, 0x41, 0xae, 0x73, 0x35, 0x0f, 0x89, 0x65, 0x48, - 0x32, 0x37, 0x15, 0xc0, 0x99, 0x6c, 0x78, 0x66, 0xf0, 0x60, 0xdd, 0x73, 0xa1, 0xf9, 0x57, 0x19, - 0xbb, 0xa4, 0x1e, 0xba, 0x6e, 0x75, 0xc5, 0xfd, 0x5f, 0x8d, 0xe1, 0xdc, 0x00, 0x28, 0x8a, 0x5d, - 0x3a, 0x0b, 0x7e, 0xb4, 0xa2, 0x74, 0xec, 0xcc, 0x8e, 0xb1, 0xb3, 0xab, 0x63, 0x3b, 0xd5, 0x2d, - 0xfa, 0xcc, 0x59, 0x00, 0xd8, 0x7f, 0x18, 0xf6, 0x48, 0xc4, 0xe8, 0x4a, 0xd7, 0x54, 0x5b, 0x5b, - 0xd3, 0xe6, 0x6e, 0xb8, 0x76, 0x0b, 0xe3, 0x28, 0x69, 0x6a, 0x62, 0x05, 0x45, 0xa6, 0x3b, 0x9b, - 0xf4, 0xdb, 0x64, 0x9e, 0xb4, 0x35, 0x03, 0x05, 0x45, 0x86, 0x6b, 0xc9, 0x8c, 0x7a, 0x07, 0x13, - 0x29, 0x28, 0x1c, 0x6d, 0xf4, 0x66, 0xcd, 0x37, 0x0d, 0x98, 0x09, 0x84, 0x47, 0xa0, 0xa8, 0x42, - 0xf5, 0x0c, 0xb0, 0xa6, 0xb6, 0xf7, 0xfd, 0x43, 0xb2, 0xb7, 0xf3, 0x0c, 0xba, 0xa0, 0x9c, 0xd6, - 0x8b, 0xc9, 0xa1, 0x95, 0xde, 0x5c, 0xdf, 0xb4, 0xbd, 0x2b, 0xc3, 0x60, 0x1b, 0xfc, 0xe3, 0x3e, - 0xd9, 0x28, 0x1a, 0x0f, 0x89, 0x74, 0x1b, 0x20, 0x7f, 0xbc, 0x20, 0x78, 0x29, 0xb6, 0xb9, 0x40, - 0x7b, 0x57, 0x86, 0x1d, 0x57, 0x5e, 0x3e, 0xea, 0xda, 0x2b, 0x3f, 0xef, 0xfc, 0x3c, 0x7d, 0xd6, - 0xff, 0x0c, 0x0d, 0xf9, 0x87, 0x1c, 0x7e, 0x05, 0x00, 0x00, 0xff, 0xff, 0xb9, 0xec, 0x22, 0xb9, - 0x53, 0x03, 0x00, 0x00, + // 405 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x4a, 0xeb, 0x40, + 0x14, 0xbe, 0x69, 0xd3, 0xa4, 0x4c, 0x7f, 0xe8, 0x1d, 0x7a, 0x4b, 0xc8, 0xa2, 0x5c, 0xb2, 0x12, + 0x85, 0x2e, 0xea, 0x5e, 0x48, 0x8d, 0x7f, 0x68, 0x4b, 0x49, 0x2d, 0xee, 0x84, 0x69, 0x67, 0xc4, + 0xd2, 0x34, 0x13, 0x33, 0xd1, 0x5a, 0x9f, 0xc4, 0xe7, 0xf0, 0x29, 0x5c, 0xba, 0x74, 0x29, 0xfa, + 0x22, 0xce, 0x64, 0x52, 0xd2, 0x5f, 0x10, 0x17, 0x03, 0xe7, 0xfb, 0xce, 0x9c, 0xef, 0x7c, 0xe7, + 0x4c, 0x02, 0x4a, 0xb7, 0xc8, 0xc7, 0x1e, 0x09, 0x1b, 0x41, 0x48, 0x23, 0x0a, 0xd5, 0x21, 0x0d, + 0x89, 0x59, 0xf2, 0x68, 0x88, 0xa6, 0xc8, 0x97, 0xa4, 0x09, 0x04, 0x29, 0x63, 0xeb, 0x45, 0x01, + 0x15, 0x07, 0x45, 0xa8, 0x1f, 0x9c, 0xca, 0x42, 0x97, 0xdc, 0x41, 0x03, 0xe8, 0x5d, 0x34, 0xf3, + 0x28, 0xc2, 0x86, 0xf2, 0x5f, 0xd9, 0x29, 0xba, 0x7a, 0x20, 0x21, 0xdc, 0x05, 0xf9, 0x36, 0x89, + 0x10, 0xe6, 0x15, 0x46, 0x86, 0xa7, 0x0a, 0xcd, 0x72, 0x23, 0x56, 0x9b, 0xb3, 0x6e, 0x7e, 0x92, + 0x44, 0xb0, 0x06, 0x34, 0x3b, 0x08, 0x8e, 0xfa, 0x67, 0x46, 0x36, 0x16, 0xd1, 0x50, 0x8c, 0x04, + 0xef, 0x90, 0x07, 0xc1, 0xab, 0x92, 0xc7, 0x31, 0x82, 0x10, 0xa8, 0xc7, 0x87, 0x7e, 0x64, 0xe4, + 0x38, 0x5b, 0x72, 0xd5, 0x1b, 0x1e, 0xc3, 0x2a, 0xc8, 0xb5, 0x2f, 0x67, 0x01, 0x31, 0xb4, 0x98, + 0xcc, 0x4d, 0x04, 0xb0, 0xc6, 0x6b, 0x9e, 0x19, 0xdc, 0x5b, 0xf6, 0x5c, 0x68, 0xfe, 0x95, 0xc6, + 0x2e, 0xa8, 0x8b, 0xae, 0xec, 0x8e, 0xb8, 0xff, 0xab, 0x31, 0xac, 0x6b, 0x00, 0x45, 0xb1, 0x43, + 0xa7, 0xfe, 0x8f, 0x56, 0x94, 0x8e, 0x9d, 0xd9, 0x32, 0x76, 0x76, 0x71, 0x6c, 0xab, 0xba, 0x41, + 0x9f, 0x59, 0x8f, 0xa0, 0x62, 0xb7, 0xba, 0xbd, 0xfb, 0xc1, 0x42, 0xcf, 0x54, 0x59, 0x59, 0x52, + 0xe6, 0x5e, 0xb8, 0xb2, 0x8d, 0x71, 0x98, 0xb4, 0xd4, 0xb1, 0x84, 0x22, 0xd3, 0x99, 0x8e, 0x7b, + 0xe7, 0x64, 0x96, 0x34, 0xd5, 0x7d, 0x09, 0x45, 0x86, 0x6b, 0xc5, 0x19, 0xf9, 0x0a, 0x3a, 0x92, + 0xd0, 0x82, 0x6b, 0x9d, 0x59, 0xf3, 0x5d, 0x01, 0x7a, 0x02, 0xe1, 0x01, 0x28, 0xca, 0x50, 0x3e, + 0x01, 0xac, 0xc9, 0xcd, 0xad, 0x7e, 0x44, 0xe6, 0x66, 0x9e, 0x41, 0x07, 0x94, 0xd3, 0x7a, 0x31, + 0x35, 0x34, 0xd2, 0x9b, 0xcb, 0x5b, 0x36, 0xb7, 0x65, 0x18, 0x3c, 0x01, 0xff, 0xb8, 0x45, 0x36, + 0x0c, 0x47, 0x03, 0xd2, 0x25, 0x21, 0xa3, 0x3e, 0xf2, 0x46, 0x4f, 0x04, 0xcf, 0xed, 0xac, 0x2e, + 0xcf, 0xdc, 0xcc, 0xb3, 0x56, 0xe5, 0xf5, 0xb3, 0xae, 0xbc, 0xf1, 0xf3, 0xc1, 0xcf, 0xf3, 0x57, + 0xfd, 0xcf, 0x40, 0x8b, 0xff, 0x8c, 0xfd, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9f, 0xcb, 0xb7, + 0xb8, 0x4b, 0x03, 0x00, 0x00, } diff --git a/core/applicationPayload.go b/core/msgp.go similarity index 74% rename from core/applicationPayload.go rename to core/msgp.go index 0dd240052..e573f586b 100644 --- a/core/applicationPayload.go +++ b/core/msgp.go @@ -9,8 +9,8 @@ import ( "reflect" ) -// AppPayload represents the actual payloads sent to application -type AppPayload struct { +// DataUpAppReq represents the actual payloads sent to application on uplink +type DataUpAppReq struct { Payload []byte `msg:"payload",json:"payload"` Metadata []AppMetadata `msg:"metadata",json:"metadata"` } @@ -28,6 +28,18 @@ type AppMetadata struct { Latitude float32 `msg:"latitude",json:"latitude"` } +// DataDownAppReq represents downlink messages sent by applications +type DataDownAppReq struct { + Payload []byte `msg:"payload",json:"payload"` +} + +// APBSubAppReq defines the shape of the request made by an application to the handler +type APBSubAppReq struct { + DevAddr [4]byte `msg:"dev_addr",json:"dev_addr"` + NwkSKey [16]byte `msg:"nwks_key",json:"nwks_key"` + AppSKey [16]byte `msg:"apps_key",json:"apps_key"` +} + // ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid // AppMetadata ready to be marshaled to json func ProtoMetaToAppMeta(srcs ...*Metadata) []AppMetadata { diff --git a/core/applicationPayload_gen.go b/core/msgp_gen.go similarity index 60% rename from core/applicationPayload_gen.go rename to core/msgp_gen.go index 45819702a..7a976cc54 100644 --- a/core/applicationPayload_gen.go +++ b/core/msgp_gen.go @@ -8,6 +8,69 @@ import ( "github.com/tinylib/msgp/msgp" ) +// MarshalMsg implements msgp.Marshaler +func (z *APBSubAppReq) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 3 + // string "dev_addr" + o = append(o, 0x83, 0xa8, 0x64, 0x65, 0x76, 0x5f, 0x61, 0x64, 0x64, 0x72) + o = msgp.AppendBytes(o, z.DevAddr[:]) + // string "nwks_key" + o = append(o, 0xa8, 0x6e, 0x77, 0x6b, 0x73, 0x5f, 0x6b, 0x65, 0x79) + o = msgp.AppendBytes(o, z.NwkSKey[:]) + // string "apps_key" + o = append(o, 0xa8, 0x61, 0x70, 0x70, 0x73, 0x5f, 0x6b, 0x65, 0x79) + o = msgp.AppendBytes(o, z.AppSKey[:]) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *APBSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var isz uint32 + isz, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + return + } + for isz > 0 { + isz-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + return + } + switch msgp.UnsafeString(field) { + case "dev_addr": + bts, err = msgp.ReadExactBytes(bts, z.DevAddr[:]) + if err != nil { + return + } + case "nwks_key": + bts, err = msgp.ReadExactBytes(bts, z.NwkSKey[:]) + if err != nil { + return + } + case "apps_key": + bts, err = msgp.ReadExactBytes(bts, z.AppSKey[:]) + if err != nil { + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + return + } + } + } + o = bts + return +} + +func (z *APBSubAppReq) Msgsize() (s int) { + s = 1 + 9 + msgp.ArrayHeaderSize + (4 * (msgp.ByteSize)) + 9 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + 9 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + return +} + // MarshalMsg implements msgp.Marshaler func (z *AppMetadata) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) @@ -120,7 +183,54 @@ func (z *AppMetadata) Msgsize() (s int) { } // MarshalMsg implements msgp.Marshaler -func (z *AppPayload) MarshalMsg(b []byte) (o []byte, err error) { +func (z *DataDownAppReq) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 1 + // string "payload" + o = append(o, 0x81, 0xa7, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64) + o = msgp.AppendBytes(o, z.Payload) + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *DataDownAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var isz uint32 + isz, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + return + } + for isz > 0 { + isz-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + return + } + switch msgp.UnsafeString(field) { + case "payload": + z.Payload, bts, err = msgp.ReadBytesBytes(bts, z.Payload) + if err != nil { + return + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + return + } + } + } + o = bts + return +} + +func (z *DataDownAppReq) Msgsize() (s int) { + s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + return +} + +// MarshalMsg implements msgp.Marshaler +func (z *DataUpAppReq) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) // map header, size 2 // string "payload" @@ -129,8 +239,8 @@ func (z *AppPayload) MarshalMsg(b []byte) (o []byte, err error) { // string "metadata" o = append(o, 0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) - for xvk := range z.Metadata { - o, err = z.Metadata[xvk].MarshalMsg(o) + for cmr := range z.Metadata { + o, err = z.Metadata[cmr].MarshalMsg(o) if err != nil { return } @@ -139,7 +249,7 @@ func (z *AppPayload) MarshalMsg(b []byte) (o []byte, err error) { } // UnmarshalMsg implements msgp.Unmarshaler -func (z *AppPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *DataUpAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field var isz uint32 @@ -170,8 +280,8 @@ func (z *AppPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { } else { z.Metadata = make([]AppMetadata, xsz) } - for xvk := range z.Metadata { - bts, err = z.Metadata[xvk].UnmarshalMsg(bts) + for cmr := range z.Metadata { + bts, err = z.Metadata[cmr].UnmarshalMsg(bts) if err != nil { return } @@ -187,10 +297,10 @@ func (z *AppPayload) UnmarshalMsg(bts []byte) (o []byte, err error) { return } -func (z *AppPayload) Msgsize() (s int) { +func (z *DataUpAppReq) Msgsize() (s int) { s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + 9 + msgp.ArrayHeaderSize - for xvk := range z.Metadata { - s += z.Metadata[xvk].Msgsize() + for cmr := range z.Metadata { + s += z.Metadata[cmr].Msgsize() } return } diff --git a/core/msgp_gen_test.go b/core/msgp_gen_test.go new file mode 100644 index 000000000..d9edde008 --- /dev/null +++ b/core/msgp_gen_test.go @@ -0,0 +1,243 @@ +package core + +// NOTE: THIS FILE WAS PRODUCED BY THE +// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp) +// DO NOT EDIT + +import ( + "testing" + + "github.com/tinylib/msgp/msgp" +) + +func TestMarshalUnmarshalAPBSubAppReq(t *testing.T) { + v := APBSubAppReq{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgAPBSubAppReq(b *testing.B) { + v := APBSubAppReq{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgAPBSubAppReq(b *testing.B) { + v := APBSubAppReq{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalAPBSubAppReq(b *testing.B) { + v := APBSubAppReq{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalAppMetadata(t *testing.T) { + v := AppMetadata{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgAppMetadata(b *testing.B) { + v := AppMetadata{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgAppMetadata(b *testing.B) { + v := AppMetadata{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalAppMetadata(b *testing.B) { + v := AppMetadata{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalDataDownAppReq(t *testing.T) { + v := DataDownAppReq{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgDataDownAppReq(b *testing.B) { + v := DataDownAppReq{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgDataDownAppReq(b *testing.B) { + v := DataDownAppReq{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalDataDownAppReq(b *testing.B) { + v := DataDownAppReq{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} + +func TestMarshalUnmarshalDataUpAppReq(t *testing.T) { + v := DataUpAppReq{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgDataUpAppReq(b *testing.B) { + v := DataUpAppReq{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgDataUpAppReq(b *testing.B) { + v := DataUpAppReq{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalDataUpAppReq(b *testing.B) { + v := DataUpAppReq{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/core/protos/broker.proto b/core/protos/broker.proto index 9076a4900..f1777276d 100644 --- a/core/protos/broker.proto +++ b/core/protos/broker.proto @@ -14,16 +14,16 @@ message DataBrokerRes { Metadata Metadata = 2; } -message SubPersoBrokerReq { +message ABPSubBrokerReq { string HandlerNet = 1; bytes AppEUI = 2; bytes DevAddr = 3; bytes NwkSKey = 4; } -message SubPersoBrokerRes{} +message ABPSubBrokerRes{} service Broker { rpc HandleData (DataBrokerReq) returns (DataBrokerRes); - rpc SubscribePersonalized (SubPersoBrokerReq) returns (SubPersoBrokerRes); + rpc SubscribePersonalized (ABPSubBrokerReq) returns (ABPSubBrokerRes); } diff --git a/core/protos/handler.proto b/core/protos/handler.proto index f5e71a3fd..1f211fc2a 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -26,17 +26,17 @@ message DataDownHandlerReq { message DataDownHandlerRes {} -message SubPersoHandlerReq { +message ABPSubHandlerReq { bytes AppEUI = 1; bytes DevAddr = 2; bytes NwkSKey = 3; bytes AppSKey = 4; } -message SubPersoHandlerRes {} +message ABPSubHandlerRes {} service Handler { rpc HandleDataUp (DataUpHandlerReq) returns (DataUpHandlerRes); rpc HandleDataDown (DataDownHandlerReq) returns (DataDownHandlerRes); - rpc SubscribePersonalized (SubPersoHandlerReq) returns (SubPersoHandlerRes); + rpc SubscribePersonalized (ABPSubHandlerReq) returns (ABPSubHandlerRes); } From 741c6442e010026d817ceffbd456bf25a68c0497 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 16 Mar 2016 15:28:03 +0100 Subject: [PATCH 1058/2266] [refactor/grpc] Re-implement partially mqtt tests + fixes --- core/adapters/mqtt/mqtt.go | 59 ++-- core/adapters/mqtt/mqtt_test.go | 416 +++++++++++++++++++++++++++++ core/components/broker/broker.go | 2 +- core/components/handler/handler.go | 4 +- core/msgp.go | 34 +-- core/msgp_gen.go | 32 +-- core/msgp_gen_test.go | 16 +- utils/errors/checks/checks.go | 33 --- utils/testing/testing.go | 30 +++ 9 files changed, 530 insertions(+), 96 deletions(-) create mode 100644 core/adapters/mqtt/mqtt_test.go delete mode 100644 utils/errors/checks/checks.go diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index a33a48ea5..a6790060a 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -57,7 +57,7 @@ func New(handler core.HandlerClient, client *client.Client, chmsg <-chan Msg, ct } // NewClient creates and connects a mqtt client with predefined options. -func newClient(id string, netAddr string, ctx log.Interface) (*client.Client, chan Msg, error) { +func NewClient(id string, netAddr string, ctx log.Interface) (*client.Client, chan Msg, error) { var cli *client.Client delay := 25 * time.Millisecond chmsg := make(chan Msg) @@ -188,7 +188,7 @@ func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { for msg := range chmsg { switch msg.Type { case Down: - req, err := a.handleDataDown(msg) + req, err := handleDataDown(msg) if err == nil { _, err = a.handler.HandleDataDown(context.Background(), req) } @@ -196,7 +196,7 @@ func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { a.ctx.WithError(err).Debug("Unable to consume data down") } case ABP: - req, err := a.handleABP(msg) + req, err := handleABP(msg) if err == nil { _, err = a.handler.SubscribePersonalized(context.Background(), req) } @@ -210,39 +210,53 @@ func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { } // handleABP parses and handles Application By Personalization request coming through MQTT -func (a adapter) handleABP(msg Msg) (*core.ABPSubHandlerReq, error) { +func handleABP(msg Msg) (*core.ABPSubHandlerReq, error) { // Ensure the query / topic parameters are valid topicInfos := strings.Split(msg.Topic, "/") if len(topicInfos) != 4 { return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") } - appEUI, err := hex.DecodeString(topicInfos[0]) - if err != nil || len(appEUI) != 8 { - return nil, errors.New(errors.Structural, "Invalid Application EUI") - } - if topicInfos[2] != "personalized" { - return nil, errors.New(errors.Implementation, "OTAA not yet supported. Unable to register device") - } // Get the actual message, try messagePack then JSON - var req core.APBSubAppReq + var req core.ABPSubAppReq if _, err := req.UnmarshalMsg(msg.Payload); err != nil { if err = json.Unmarshal(msg.Payload, &req); err != nil { return nil, errors.New(errors.Structural, err) } } + // Verify each parameter + appEUI, err := hex.DecodeString(topicInfos[0]) + if err != nil || len(appEUI) != 8 { + return nil, errors.New(errors.Structural, "Invalid Application EUI") + } + + devAddr, err := hex.DecodeString(req.DevAddr) + if err != nil || len(devAddr) != 4 { + return nil, errors.New(errors.Structural, "Invalid Device Address") + } + + nwkSKey, err := hex.DecodeString(req.NwkSKey) + if err != nil || len(nwkSKey) != 16 { + return nil, errors.New(errors.Structural, "Invalid Network Session Key") + } + + appSKey, err := hex.DecodeString(req.AppSKey) + if err != nil || len(appSKey) != 16 { + return nil, errors.New(errors.Structural, "Invalid Application Session Key") + } + // Convert it to an handler subscription return &core.ABPSubHandlerReq{ AppEUI: appEUI, - DevAddr: req.DevAddr[:], - NwkSKey: req.NwkSKey[:], - AppSKey: req.AppSKey[:], + DevAddr: devAddr, + NwkSKey: nwkSKey, + AppSKey: appSKey, }, nil } // handleDataDown parses and handles Downlink message coming through MQTT -func (a adapter) handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { +func handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { // Ensure the query / topic parameters are valid topicInfos := strings.Split(msg.Topic, "/") if len(topicInfos) != 4 { @@ -254,13 +268,20 @@ func (a adapter) handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { return nil, errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI") } - if len(msg.Payload) == 0 { - return nil, errors.New(errors.Structural, "There's no data to handle") + // Retrieve the message payload + var req core.DataDownAppReq + if _, err := req.UnmarshalMsg(msg.Payload); err != nil { + if err = json.Unmarshal(msg.Payload, &req); err != nil { + return nil, errors.New(errors.Structural, err) + } + } + if len(req.Payload) == 0 { + return nil, errors.New(errors.Structural, "There's now data to handle") } // Convert it to an handler downlink return &core.DataDownHandlerReq{ - Payload: msg.Payload, + Payload: req.Payload, AppEUI: appEUI, DevEUI: devEUI, }, nil diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go new file mode 100644 index 000000000..c4241df43 --- /dev/null +++ b/core/adapters/mqtt/mqtt_test.go @@ -0,0 +1,416 @@ +// Copyright © 2016 T//e Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestHandleDataDown(t *testing.T) { + { + Desc(t, "Invalid topic :: TTN") + + // Build + msg := Msg{ + Topic: "TTN", + Payload: []byte(`{"payload":"patate"}`), + Type: Down, + } + var want *core.DataDownHandlerReq + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "DataDown Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid topic :: 01/devices/0102030405060708/down") + + // Build + msg := Msg{ + Topic: "01/devices/0102030405060708/down", + Payload: []byte(`{"payload":"patate"}`), + Type: Down, + } + var want *core.DataDownHandlerReq + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "DataDown Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid topic :: 0102030405060708/devices/010203040506/down") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/010203040506/down", + Payload: []byte(`{"payload":"patate"}`), + Type: Down, + } + var want *core.DataDownHandlerReq + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "DataDown Handler Requests") + } + + // -------------------- + + { + Desc(t, "Valid topic, invalid Message Pack payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/0910111213141516/down", + Payload: []byte{129, 167, 112, 97, 121, 108, 111, 97, 100, 150, 112, 97, 116, 97, 116, 101}, + Type: Down, + } + var want *core.DataDownHandlerReq + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "DataDown Handler Requests") + } + + // -------------------- + + { + Desc(t, "Valid topic, invalid json") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/0910111213141516/down", + Payload: []byte(`{"ttn":14}`), + Type: Down, + } + var want *core.DataDownHandlerReq + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "DataDown Handler Requests") + } + + // -------------------- + + { + Desc(t, "Valid topic, valid JSON payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/0910111213141516/down", + Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), + Type: Down, + } + want := &core.DataDownHandlerReq{ + Payload: []byte("patate"), + AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + DevEUI: []byte{0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}, + } + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, nil, err) + Check(t, want, req, "DataDown Handler Requests") + } + + // -------------------- + + { + Desc(t, "Valid topic, valid Message Pack payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/0910111213141516/down", + Payload: []byte{129, 167, 112, 97, 121, 108, 111, 97, 100, 196, 6, 112, 97, 116, 97, 116, 101}, + Type: Down, + } + want := &core.DataDownHandlerReq{ + Payload: []byte("patate"), + AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + DevEUI: []byte{0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}, + } + + // Operate + req, err := handleDataDown(msg) + + // Check + CheckErrors(t, nil, err) + Check(t, want, req, "DataDown Handler Requests") + } +} + +func TestHandleABP(t *testing.T) { + { + Desc(t, "Invalid topic :: TTN") + + // Build + msg := Msg{ + Topic: "TTN", + Payload: []byte(`{ + "dev_addr":"01020304", + "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid topic :: 01/devices/personalized/activations") + + // Build + msg := Msg{ + Topic: "01/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"01020304", + "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid Device Address") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"patate", + "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid Application Session Key Address") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"01020304", + "apps_key":"aabbccdxxxxdeeaabbccddeeaabbccddeeff", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid Network Session Key Address") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"01020304", + "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", + "nwks_key":"014050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid JSON Payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr "01020304", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Incomplete JSON Payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"01020304", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Invalid MsgPack Payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte{}, + Type: ABP, + } + var want *core.ABPSubHandlerReq + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, ErrStructural, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Valid JSON Payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"01020304", + "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", + "nwks_key":"01020304050607080900010203040506" + }`), + Type: ABP, + } + want := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + } + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, nil, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } + + // -------------------- + + { + Desc(t, "Valid MsgPack Payload") + + // Build + msg := Msg{ + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte{131, 168, 100, 101, 118, 95, 97, 100, 100, 114, 168, 48, 49, 48, 50, 48, 51, 48, 52, 168, 110, 119, 107, 115, 95, 107, 101, 121, 217, 32, 48, 49, 48, 50, 48, 51, 48, 52, 48, 53, 48, 54, 48, 55, 48, 56, 48, 57, 48, 48, 48, 49, 48, 50, 48, 51, 48, 52, 48, 53, 48, 54, 168, 97, 112, 112, 115, 95, 107, 101, 121, 217, 32, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102}, + Type: ABP, + } + want := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, + } + + // Operate + req, err := handleABP(msg) + + // Check + CheckErrors(t, nil, err) + Check(t, want, req, "ABP Subscription Handler Requests") + } +} diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 789ab60ec..aefa182e3 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -156,7 +156,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } // Register implements the core.BrokerServer interface -func (b component) SubscribePersonalized(bctx context.Context, req *core.SubPersoBrokerReq) (*core.SubPersoBrokerRes, error) { +func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubBrokerReq) (*core.ABPSubBrokerRes, error) { b.ctx.Debug("Handling personalized subscription") // Ensure the entry is valid diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 63e0ab47e..b06fce147 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -79,7 +79,7 @@ func (h *component) Start(netAddr string) error { } // RegisterPersonalized implements the core.HandlerServer interface -func (h component) SubscribePersonalized(bctx context.Context, req *core.SubPersoHandlerReq) (*core.SubPersoHandlerRes, error) { +func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubHandlerReq) (*core.ABPSubHandlerRes, error) { h.ctx.Debug("New personalized subscription request") stats.MarkMeter("handler.registration.in") @@ -117,7 +117,7 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.SubPers return nil, errors.New(errors.Operational, err) } - _, err := h.broker.SubscribePersonalized(context.Background(), &core.SubPersoBrokerReq{ + _, err := h.broker.SubscribePersonalized(context.Background(), &core.ABPSubBrokerReq{ HandlerNet: h.netAddr, AppEUI: req.AppEUI, DevAddr: req.DevAddr, diff --git a/core/msgp.go b/core/msgp.go index e573f586b..68ca4a484 100644 --- a/core/msgp.go +++ b/core/msgp.go @@ -11,33 +11,33 @@ import ( // DataUpAppReq represents the actual payloads sent to application on uplink type DataUpAppReq struct { - Payload []byte `msg:"payload",json:"payload"` - Metadata []AppMetadata `msg:"metadata",json:"metadata"` + Payload []byte `msg:"payload" json:"payload"` + Metadata []AppMetadata `msg:"metadata" json:"metadata"` } // AppMetadata represents gathered metadata that are sent to gateways type AppMetadata struct { - Frequency float32 `msg:"frequency",json:"frequency"` - DataRate string `msg:"data_rate",json:"data_rate"` - CodingRate string `msg:"coding_rate",json:"coding_rate"` - Timestamp uint32 `msg:"timestamp",json:timestamp"` - Rssi int32 `msg:"rssi",json:"rssi"` - Lsnr float32 `msg:"lsnr",json:"lsnr"` - Altitude int32 `msg:"altitude",json:"altitude"` - Longitude float32 `msg:"longitude",json:"longitude"` - Latitude float32 `msg:"latitude",json:"latitude"` + Frequency float32 `msg:"frequency" json:"frequency"` + DataRate string `msg:"data_rate" json:"data_rate"` + CodingRate string `msg:"coding_rate" json:"coding_rate"` + Timestamp uint32 `msg:"timestamp" json:timestamp"` + Rssi int32 `msg:"rssi" json:"rssi"` + Lsnr float32 `msg:"lsnr" json:"lsnr"` + Altitude int32 `msg:"altitude" json:"altitude"` + Longitude float32 `msg:"longitude" json:"longitude"` + Latitude float32 `msg:"latitude" json:"latitude"` } // DataDownAppReq represents downlink messages sent by applications type DataDownAppReq struct { - Payload []byte `msg:"payload",json:"payload"` + Payload []byte `msg:"payload" json:"payload"` } -// APBSubAppReq defines the shape of the request made by an application to the handler -type APBSubAppReq struct { - DevAddr [4]byte `msg:"dev_addr",json:"dev_addr"` - NwkSKey [16]byte `msg:"nwks_key",json:"nwks_key"` - AppSKey [16]byte `msg:"apps_key",json:"apps_key"` +// ABPSubAppReq defines the shape of the request made by an application to the handler +type ABPSubAppReq struct { + DevAddr string `msg:"dev_addr" json:"dev_addr"` + NwkSKey string `msg:"nwks_key" json:"nwks_key"` + AppSKey string `msg:"apps_key" json:"apps_key"` } // ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid diff --git a/core/msgp_gen.go b/core/msgp_gen.go index 7a976cc54..a5868f99d 100644 --- a/core/msgp_gen.go +++ b/core/msgp_gen.go @@ -9,23 +9,23 @@ import ( ) // MarshalMsg implements msgp.Marshaler -func (z *APBSubAppReq) MarshalMsg(b []byte) (o []byte, err error) { +func (z ABPSubAppReq) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) // map header, size 3 // string "dev_addr" o = append(o, 0x83, 0xa8, 0x64, 0x65, 0x76, 0x5f, 0x61, 0x64, 0x64, 0x72) - o = msgp.AppendBytes(o, z.DevAddr[:]) + o = msgp.AppendString(o, z.DevAddr) // string "nwks_key" o = append(o, 0xa8, 0x6e, 0x77, 0x6b, 0x73, 0x5f, 0x6b, 0x65, 0x79) - o = msgp.AppendBytes(o, z.NwkSKey[:]) + o = msgp.AppendString(o, z.NwkSKey) // string "apps_key" o = append(o, 0xa8, 0x61, 0x70, 0x70, 0x73, 0x5f, 0x6b, 0x65, 0x79) - o = msgp.AppendBytes(o, z.AppSKey[:]) + o = msgp.AppendString(o, z.AppSKey) return } // UnmarshalMsg implements msgp.Unmarshaler -func (z *APBSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { +func (z *ABPSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { var field []byte _ = field var isz uint32 @@ -41,17 +41,17 @@ func (z *APBSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { } switch msgp.UnsafeString(field) { case "dev_addr": - bts, err = msgp.ReadExactBytes(bts, z.DevAddr[:]) + z.DevAddr, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } case "nwks_key": - bts, err = msgp.ReadExactBytes(bts, z.NwkSKey[:]) + z.NwkSKey, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } case "apps_key": - bts, err = msgp.ReadExactBytes(bts, z.AppSKey[:]) + z.AppSKey, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } @@ -66,8 +66,8 @@ func (z *APBSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { return } -func (z *APBSubAppReq) Msgsize() (s int) { - s = 1 + 9 + msgp.ArrayHeaderSize + (4 * (msgp.ByteSize)) + 9 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) + 9 + msgp.ArrayHeaderSize + (16 * (msgp.ByteSize)) +func (z ABPSubAppReq) Msgsize() (s int) { + s = 1 + 9 + msgp.StringPrefixSize + len(z.DevAddr) + 9 + msgp.StringPrefixSize + len(z.NwkSKey) + 9 + msgp.StringPrefixSize + len(z.AppSKey) return } @@ -239,8 +239,8 @@ func (z *DataUpAppReq) MarshalMsg(b []byte) (o []byte, err error) { // string "metadata" o = append(o, 0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) - for cmr := range z.Metadata { - o, err = z.Metadata[cmr].MarshalMsg(o) + for xvk := range z.Metadata { + o, err = z.Metadata[xvk].MarshalMsg(o) if err != nil { return } @@ -280,8 +280,8 @@ func (z *DataUpAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { } else { z.Metadata = make([]AppMetadata, xsz) } - for cmr := range z.Metadata { - bts, err = z.Metadata[cmr].UnmarshalMsg(bts) + for xvk := range z.Metadata { + bts, err = z.Metadata[xvk].UnmarshalMsg(bts) if err != nil { return } @@ -299,8 +299,8 @@ func (z *DataUpAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { func (z *DataUpAppReq) Msgsize() (s int) { s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + 9 + msgp.ArrayHeaderSize - for cmr := range z.Metadata { - s += z.Metadata[cmr].Msgsize() + for xvk := range z.Metadata { + s += z.Metadata[xvk].Msgsize() } return } diff --git a/core/msgp_gen_test.go b/core/msgp_gen_test.go index d9edde008..119e3ce8e 100644 --- a/core/msgp_gen_test.go +++ b/core/msgp_gen_test.go @@ -10,8 +10,8 @@ import ( "github.com/tinylib/msgp/msgp" ) -func TestMarshalUnmarshalAPBSubAppReq(t *testing.T) { - v := APBSubAppReq{} +func TestMarshalUnmarshalABPSubAppReq(t *testing.T) { + v := ABPSubAppReq{} bts, err := v.MarshalMsg(nil) if err != nil { t.Fatal(err) @@ -33,8 +33,8 @@ func TestMarshalUnmarshalAPBSubAppReq(t *testing.T) { } } -func BenchmarkMarshalMsgAPBSubAppReq(b *testing.B) { - v := APBSubAppReq{} +func BenchmarkMarshalMsgABPSubAppReq(b *testing.B) { + v := ABPSubAppReq{} b.ReportAllocs() b.ResetTimer() for i := 0; i < b.N; i++ { @@ -42,8 +42,8 @@ func BenchmarkMarshalMsgAPBSubAppReq(b *testing.B) { } } -func BenchmarkAppendMsgAPBSubAppReq(b *testing.B) { - v := APBSubAppReq{} +func BenchmarkAppendMsgABPSubAppReq(b *testing.B) { + v := ABPSubAppReq{} bts := make([]byte, 0, v.Msgsize()) bts, _ = v.MarshalMsg(bts[0:0]) b.SetBytes(int64(len(bts))) @@ -54,8 +54,8 @@ func BenchmarkAppendMsgAPBSubAppReq(b *testing.B) { } } -func BenchmarkUnmarshalAPBSubAppReq(b *testing.B) { - v := APBSubAppReq{} +func BenchmarkUnmarshalABPSubAppReq(b *testing.B) { + v := ABPSubAppReq{} bts, _ := v.MarshalMsg(nil) b.ReportAllocs() b.SetBytes(int64(len(bts))) diff --git a/utils/errors/checks/checks.go b/utils/errors/checks/checks.go deleted file mode 100644 index b48d9789c..000000000 --- a/utils/errors/checks/checks.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package checks - -import ( - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func CheckErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(Failure).Nature == Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 8910cb671..9bebae03b 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -10,9 +10,16 @@ import ( "reflect" "testing" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" ) +var ErrStructural = pointer.String(string(errors.Structural)) +var ErrOperational = pointer.String(string(errors.Operational)) +var ErrNotFound = pointer.String(string(errors.NotFound)) +var ErrBehavioural = pointer.String(string(errors.Behavioural)) + func GetLogger(t *testing.T, tag string) log.Interface { logger := &log.Logger{ Handler: NewLogHandler(t), @@ -43,3 +50,26 @@ func Check(t *testing.T, want, got interface{}, name string) { } Ok(t, fmt.Sprintf("Check %s", name)) } + +// Check errors verify if a given string corresponds to a known error +func CheckErrors(t *testing.T, want *string, got error) { + if got == nil { + if want == nil { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got nothing", *want) + return + } + + if want == nil { + Ko(t, "Expected no error but got {%v}", got) + return + } + + if got.(errors.Failure).Nature == errors.Nature(*want) { + Ok(t, "Check errors") + return + } + Ko(t, "Expected error to be {%s} but got {%v}", *want, got) +} From 306d82c3f2f08f5fa7392814fa2dac2bf1f9955c Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 16 Mar 2016 19:09:00 +0100 Subject: [PATCH 1059/2266] [refactor/grpc] Test MQTT client and fix auto-reconnection --- core/adapters/mqtt/client.go | 224 +++++++++++++++++ core/adapters/mqtt/client_test.go | 388 ++++++++++++++++++++++++++++++ core/adapters/mqtt/mqtt.go | 79 +----- utils/testing/testing.go | 8 +- 4 files changed, 621 insertions(+), 78 deletions(-) create mode 100644 core/adapters/mqtt/client.go create mode 100644 core/adapters/mqtt/client_test.go diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go new file mode 100644 index 000000000..ed7396d10 --- /dev/null +++ b/core/adapters/mqtt/client.go @@ -0,0 +1,224 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/yosssi/gmq/mqtt" + "github.com/yosssi/gmq/mqtt/client" +) + +const InitialReconnectDelay = 25 * time.Millisecond + +type Client interface { + Subscribe(*client.SubscribeOptions) error + Unsubscribe(*client.UnsubscribeOptions) error + Publish(*client.PublishOptions) error + Connect(*client.ConnectOptions) error + Disconnect() error + Terminate() +} + +type connecter func() error + +type recoverableClient struct { + chcmd chan<- interface{} + cherr <-chan error +} + +type disconnectOptions struct{} +type terminateOptions struct{} + +func (c recoverableClient) Subscribe(o *client.SubscribeOptions) error { + c.chcmd <- o + return <-c.cherr +} + +func (c recoverableClient) Unsubscribe(o *client.UnsubscribeOptions) error { + c.chcmd <- o + return <-c.cherr +} + +func (c recoverableClient) Publish(o *client.PublishOptions) error { + c.chcmd <- o + return <-c.cherr +} + +func (c recoverableClient) Connect(o *client.ConnectOptions) error { + c.chcmd <- o + return <-c.cherr +} + +func (c recoverableClient) Disconnect() error { + c.chcmd <- disconnectOptions{} + return <-c.cherr +} + +func (c recoverableClient) Terminate() { + c.chcmd <- terminateOptions{} +} + +// NewClient creates and connects a mqtt client with predefined options. +func NewClient(id string, netAddr string, ctx log.Interface) (Client, chan Msg, error) { + ctx = ctx.WithField("id", id).WithField("address", netAddr) + chcmd := make(chan interface{}) + cherr := make(chan error) + chmsg := make(chan Msg) + + go monitorClient(id, netAddr, chcmd, cherr, ctx) + + tryConnect := createConnecter(id, netAddr, chmsg, chcmd, ctx) + if err := tryConnect(); err != nil { + close(chcmd) + return nil, nil, errors.New(errors.Operational, err) + } + + return recoverableClient{ + chcmd: chcmd, + cherr: cherr, + }, chmsg, nil +} + +func monitorClient(id string, netAddr string, chcmd <-chan interface{}, cherr chan<- error, ctx log.Interface) { + var cli *client.Client + ctx = ctx.WithField("process", "monitorClient") + ctx.Debug("Start monitoring MQTT client") + + for cmd := range chcmd { + if cli == nil { + init, ok := cmd.(*client.Client) + if !ok { + ctx.Warn("Received cmd whereas client is nil. Ignored") + continue + } + ctx.Debug("Setup initial MQTT client") + cli = init + continue + } + switch cmd.(type) { + case *client.SubscribeOptions: + ctx.Debug("Client received new subscription order") + cherr <- cli.Subscribe(cmd.(*client.SubscribeOptions)) + case *client.UnsubscribeOptions: + ctx.Debug("Client received new unsubscription order") + cherr <- cli.Unsubscribe(cmd.(*client.UnsubscribeOptions)) + case *client.PublishOptions: + ctx.Debug("Client received new publication order") + cherr <- cli.Publish(cmd.(*client.PublishOptions)) + case *client.ConnectOptions: + ctx.Debug("Client received new connection order") + cherr <- cli.Connect(cmd.(*client.ConnectOptions)) + case disconnectOptions: + ctx.Debug("Client received disconnection order") + cherr <- cli.Disconnect() + case terminateOptions: + ctx.Debug("Client received termination order") + cli.Terminate() + cli = nil + case *client.Client: + ctx.Debug("Replacing client with another one") + _ = cli.Disconnect() + cli.Terminate() + cli = cmd.(*client.Client) + default: + ctx.WithField("cmd", cmd).Warn("Received unreckognized command") + } + } + + if cli != nil { + _ = cli.Disconnect() + cli.Terminate() + } + ctx.Debug("Stop monitoring MQTT client") +} + +func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- interface{}, ctx log.Interface) connecter { + ctx.Debug("Create new connecter for MQTT client") + var cli *client.Client + cli = client.New(&client.Options{ + ErrorHandler: func(err error) { + createErrorHandler( + createConnecter(id, netAddr, chmsg, chcmd, ctx), + 10000*InitialReconnectDelay, + ctx, + )(err) + }, + }) + + return func() error { + ctx.Debug("(Re)Connecting MQTT client") + err := cli.Connect(&client.ConnectOptions{ + Network: "tcp", + Address: netAddr, + ClientID: []byte(id), + }) + + if err != nil { + return err + } + + err = cli.Subscribe(&client.SubscribeOptions{ + SubReqs: []*client.SubReq{ + &client.SubReq{ + TopicFilter: []byte("+/devices/+/down"), + QoS: mqtt.QoS2, + Handler: func(topic, msg []byte) { + chmsg <- Msg{ + Topic: string(topic), + Payload: msg, + Type: Down, + } + }, + }, + &client.SubReq{ + TopicFilter: []byte("+/devices/personalized/activations"), + QoS: mqtt.QoS2, + Handler: func(topic, msg []byte) { + chmsg <- Msg{ + Topic: string(topic), + Payload: msg, + Type: ABP, + } + }, + }, + }, + }) + + if err != nil { + return err + } + + select { + case chcmd <- cli: + return nil + case <-time.After(time.Second): + return errors.New(errors.Operational, "Timeout. Unable to set new client") + } + } +} + +// createErrorHandler use the client reference to create an error handler function which will +// attempt to reconnect the client after a failure. +func createErrorHandler(tryReconnect connecter, maxDelay time.Duration, ctx log.Interface) client.ErrorHandler { + delay := InitialReconnectDelay + var reconnect func(fault error) + reconnect = func(fault error) { + if delay > maxDelay { + ctx.WithError(fault).Error("Unable to reconnect the mqtt client") + return + } + ctx.Debugf("Connection lost with MQTT broker. Trying to reconnect in %s", delay) + <-time.After(delay) + if err := tryReconnect(); err != nil { + delay *= 10 + ctx.WithError(err).Debug("Failed to reconnect MQTT client.") + reconnect(fault) + } + } + + return reconnect +} diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go new file mode 100644 index 000000000..01d8a169a --- /dev/null +++ b/core/adapters/mqtt/client_test.go @@ -0,0 +1,388 @@ +// Copyright © 2016 T//e Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/yosssi/gmq/mqtt" + "github.com/yosssi/gmq/mqtt/client" +) + +const BrokerAddr = "0.0.0.0:1883" + +// MockClient implements the Client interface +type MockClient struct { + Failures map[string]error + InUnsubscribe *client.UnsubscribeOptions + InSubscribe *client.SubscribeOptions + InPublish *client.PublishOptions + InConnect *client.ConnectOptions + IsTerminate bool + IsDisconnect bool +} + +// NewMockClient constructs a new MockClient +func NewMockClient() *MockClient { + return &MockClient{ + Failures: make(map[string]error), + } +} + +// Subscribe implements the Client interface +func (m *MockClient) Subscribe(o *client.SubscribeOptions) error { + m.InSubscribe = o + return m.Failures["Subscribe"] +} + +// Unsubscribe implements the Client interface +func (m *MockClient) Unsubscribe(o *client.UnsubscribeOptions) error { + m.InUnsubscribe = o + return m.Failures["Unsubscribe"] +} + +// Publish implements the Client interface +func (m *MockClient) Publish(o *client.PublishOptions) error { + m.InPublish = o + return m.Failures["Publish"] +} + +// Connect implements the Client interface +func (m *MockClient) Connect(o *client.ConnectOptions) error { + m.InConnect = o + return m.Failures["Connect"] +} + +// Terminate implements the Client interface +func (m *MockClient) Terminate() { + m.IsTerminate = true +} + +// Disconnect implements the Client interface +func (m *MockClient) Disconnect() error { + m.IsDisconnect = true + return m.Failures["Disconnect"] +} + +// MockTryConnect simulates a tryConnect function +type MockTryConnect struct { + Func connecter + Attempt int +} + +// NewMockTryConnect instantiates a MockTryConnect structure +func NewMockTryConnect(maxFailures int) *MockTryConnect { + m := new(MockTryConnect) + + m.Func = func() error { + m.Attempt++ + if m.Attempt > maxFailures { + return nil + } + return fmt.Errorf("MockTryConnect: Nope") + } + + return m +} + +func TestCreateErrorHandler(t *testing.T) { + { + Desc(t, "Try reconnect once and fail") + + // Build + tryConnect := NewMockTryConnect(3) + maxDelay := InitialReconnectDelay + handler := createErrorHandler(tryConnect.Func, maxDelay, GetLogger(t, "Test Logger")) + + // Operate + go handler(fmt.Errorf("Mock Failure")) + <-time.After(maxDelay + 50*time.Millisecond) + + // Check + Check(t, 1, tryConnect.Attempt, "Reconnection Attempts") + } + + // -------------------- + + { + Desc(t, "Try reconnect more than once and fail") + + // Build + tryConnect := NewMockTryConnect(3) + maxDelay := InitialReconnectDelay * 10 + handler := createErrorHandler(tryConnect.Func, maxDelay, GetLogger(t, "Test Logger")) + + // Operate + go handler(fmt.Errorf("Mock Failure")) + <-time.After(maxDelay + 50*time.Millisecond) + + // Check + Check(t, 2, tryConnect.Attempt, "Reconnection Attempts") + } + + // -------------------- + + { + Desc(t, "Try reconnect once and succeed") + + // Build + tryConnect := NewMockTryConnect(0) + maxDelay := InitialReconnectDelay + handler := createErrorHandler(tryConnect.Func, maxDelay, GetLogger(t, "Test Logger")) + + // Operate + go handler(fmt.Errorf("Mock Failure")) + <-time.After(maxDelay + 50*time.Millisecond) + + // Check + Check(t, 1, tryConnect.Attempt, "Reconnection Attempts") + } +} + +func TestNewClient(t *testing.T) { + var id int + newID := func() string { + id++ + return fmt.Sprintf("(%d)Client#%d", time.Now().Nanosecond(), id) + } + + testCli := client.New(nil) + if err := testCli.Connect(&client.ConnectOptions{ + Network: "tcp", + Address: BrokerAddr, + ClientID: []byte(newID()), + }); err != nil { + panic(err) + } + + // -------------------- + + { + Desc(t, "Create client with invalid address") + + // Build + _, _, err := NewClient(newID(), "invalidAddress", GetLogger(t, "Test Logger")) + + // Check + CheckErrors(t, ErrOperational, err) + } + + // -------------------- + + { + Desc(t, "Connect a client and receive a down msg") + + // Build + cli, chmsg, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + FatalUnless(t, err) + msg := Msg{ + Type: Down, + Topic: "01020304/devices/01020304/down", + Payload: []byte("message"), + } + + // Operate + testCli.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: false, + TopicName: []byte(msg.Topic), + Message: msg.Payload, + }) + + // Check + var got Msg + select { + case got = <-chmsg: + case <-time.After(75 * time.Millisecond): + } + Check(t, msg, got, "MQTT Messages") + + // Clean + _ = cli.Disconnect() + cli.Terminate() + <-time.After(time.Millisecond * 50) + } + + // -------------------- + + { + Desc(t, "Connect a client and receive a abp msg") + + // Build + cli, chmsg, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + FatalUnless(t, err) + msg := Msg{ + Type: ABP, + Topic: "01020304/devices/personalized/activations", + Payload: []byte("message"), + } + + // Operate + testCli.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: false, + TopicName: []byte(msg.Topic), + Message: msg.Payload, + }) + + // Check + var got Msg + select { + case got = <-chmsg: + case <-time.After(75 * time.Millisecond): + } + Check(t, msg, got, "MQTT Messages") + + // Clean + _ = cli.Disconnect() + cli.Terminate() + <-time.After(time.Millisecond * 50) + } + + // -------------------- + + { + Desc(t, "Connect a client and send on a random topic") + + // Build + cli, chmsg, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + FatalUnless(t, err) + var msg Msg + + // Operate + testCli.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: false, + TopicName: []byte("topic"), + Message: []byte{14, 42}, + }) + + // Check + var got Msg + select { + case got = <-chmsg: + case <-time.After(75 * time.Millisecond): + } + Check(t, msg, got, "MQTT Messages") + + // Clean + _ = cli.Disconnect() + cli.Terminate() + <-time.After(time.Millisecond * 50) + } + + // -------------------- + + { + Desc(t, "Connect the client and simulate a disconnection") + + // Build + id := newID() + cli, chmsg, err := NewClient(id, BrokerAddr, GetLogger(t, "Test Logger")) + FatalUnless(t, err) + msg := Msg{ + Type: ABP, + Topic: "01020304/devices/personalized/activations", + Payload: []byte("message"), + } + + // Operate + usurp := client.New(nil) + err = usurp.Connect(&client.ConnectOptions{ + Network: "tcp", + Address: BrokerAddr, + ClientID: []byte(id), + }) + FatalUnless(t, err) + <-time.After(InitialReconnectDelay * 2) + testCli.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: false, + TopicName: []byte(msg.Topic), + Message: msg.Payload, + }) + + // Check + var got Msg + select { + case got = <-chmsg: + case <-time.After(75 * time.Millisecond): + } + Check(t, msg, got, "MQTT Messages") + + // Clean + _ = cli.Disconnect() + cli.Terminate() + _ = usurp.Disconnect() + usurp.Terminate() + <-time.After(time.Millisecond * 50) + } + + // -------------------- + + { + Desc(t, "Connect a client and publish on a topic") + + // Build + chmsg := make(chan Msg) + msg := Msg{ + Type: Down, + Topic: "topic", + Payload: []byte{14, 42}, + } + cli, _, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + FatalUnless(t, err) + err = testCli.Subscribe(&client.SubscribeOptions{ + []*client.SubReq{ + &client.SubReq{ + TopicFilter: []byte("topic"), + QoS: mqtt.QoS2, + Handler: func(topic, msg []byte) { + chmsg <- Msg{ + Topic: string(topic), + Payload: msg, + Type: Down, + } + }, + }, + }, + }) + FatalUnless(t, err) + + // Operate + err = cli.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: false, + TopicName: []byte(msg.Topic), + Message: msg.Payload, + }) + + // Check + var got Msg + select { + case got = <-chmsg: + case <-time.After(75 * time.Millisecond): + } + + CheckErrors(t, nil, err) + Check(t, msg, got, "MQTT Messages") + + // Clean + _ = cli.Disconnect() + err = testCli.Unsubscribe(&client.UnsubscribeOptions{ + [][]byte{[]byte(msg.Topic)}, + }) + FatalUnless(t, err) + cli.Terminate() + <-time.After(time.Millisecond * 50) + } + + // -------------------- + + _ = testCli.Disconnect() + testCli.Terminate() +} diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index a6790060a..1a25a3609 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "strings" - "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -21,7 +20,7 @@ import ( ) type adapter struct { - *client.Client + Client handler core.HandlerClient ctx log.Interface } @@ -44,7 +43,7 @@ const ( // New constructs an mqtt adapter responsible for making the bridge between the handler and // application. -func New(handler core.HandlerClient, client *client.Client, chmsg <-chan Msg, ctx log.Interface) core.AppClient { +func New(handler core.HandlerClient, client Client, chmsg <-chan Msg, ctx log.Interface) core.AppClient { a := adapter{ Client: client, handler: handler, @@ -56,80 +55,6 @@ func New(handler core.HandlerClient, client *client.Client, chmsg <-chan Msg, ct return a } -// NewClient creates and connects a mqtt client with predefined options. -func NewClient(id string, netAddr string, ctx log.Interface) (*client.Client, chan Msg, error) { - var cli *client.Client - delay := 25 * time.Millisecond - chmsg := make(chan Msg) - - tryConnect := func() error { - ctx.WithField("id", id).WithField("address", netAddr).Debug("(Re)Connecting MQTT Client") - err := cli.Connect(&client.ConnectOptions{ - Network: "tcp", - Address: netAddr, - ClientID: []byte(id), - }) - - if err != nil { - return err - } - - return cli.Subscribe(&client.SubscribeOptions{ - SubReqs: []*client.SubReq{ - &client.SubReq{ - TopicFilter: []byte("+/devices/+/down"), - QoS: mqtt.QoS2, - Handler: func(topic, msg []byte) { - chmsg <- Msg{ - Topic: string(topic), - Payload: msg, - Type: Down, - } - }, - }, - &client.SubReq{ - TopicFilter: []byte("+/devices/personalized/activations"), - QoS: mqtt.QoS2, - Handler: func(topic, msg []byte) { - chmsg <- Msg{ - Topic: string(topic), - Payload: msg, - Type: ABP, - } - }, - }, - }, - }) - } - - var reconnect func(fault error) - reconnect = func(fault error) { - if cli == nil { - ctx.Fatal("Attempt reconnection on non-existing client") - } - if delay > 10000*delay { - cli.Terminate() - ctx.WithError(fault).Fatal("Unable to reconnect the mqtt client") - } - <-time.After(delay) - if err := tryConnect(); err != nil { - delay *= 10 - ctx.WithError(err).Debugf("Failed to reconnect MQTT client. Trying again in %s", delay) - reconnect(fault) - return - } - delay = 25 * time.Millisecond - } - - cli = client.New(&client.Options{ErrorHandler: reconnect}) - - if err := tryConnect(); err != nil { - cli.Terminate() - return nil, nil, errors.New(errors.Operational, err) - } - return cli, chmsg, nil -} - // HandleData implements the core.AppClient interface func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) { stats.MarkMeter("mqtt_adapter.send") diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 9bebae03b..0e6c471c1 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -25,7 +25,7 @@ func GetLogger(t *testing.T, tag string) log.Interface { Handler: NewLogHandler(t), Level: log.DebugLevel, } - return logger.WithField("tag", "Adapter") + return logger.WithField("tag", tag) } // Ok displays a green check symbol @@ -73,3 +73,9 @@ func CheckErrors(t *testing.T, want *string, got error) { } Ko(t, "Expected error to be {%s} but got {%v}", *want, got) } + +func FatalUnless(t *testing.T, err error) { + if err != nil { + Ko(t, "Unexpected error arised: %s", err) + } +} From 4255f0c8bc2dd05447cdb2f935abd8d70e32d4d9 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 00:06:38 +0100 Subject: [PATCH 1060/2266] [refactor/grpc] Finalize tests for mqtt client --- core/adapters/mqtt/client.go | 115 +++++++++++------------------- core/adapters/mqtt/client_test.go | 5 -- core/adapters/mqtt/safeclient.go | 43 +++++++++++ 3 files changed, 83 insertions(+), 80 deletions(-) create mode 100644 core/adapters/mqtt/safeclient.go diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go index ed7396d10..7007633dc 100644 --- a/core/adapters/mqtt/client.go +++ b/core/adapters/mqtt/client.go @@ -12,64 +12,28 @@ import ( "github.com/yosssi/gmq/mqtt/client" ) +// InitialReconnectDelay represents the initial delay of reconnection in case of lose. The client +// will attempt to reconnect several times after a given delay being 10x the previous one. const InitialReconnectDelay = 25 * time.Millisecond +// Client provides an interface for an MQTT client type Client interface { - Subscribe(*client.SubscribeOptions) error - Unsubscribe(*client.UnsubscribeOptions) error + // Publish pushes a message on a given topic Publish(*client.PublishOptions) error - Connect(*client.ConnectOptions) error - Disconnect() error + // Terminate kills internal client goroutine and processes Terminate() } +// connecter is an alias used by methods belows type connecter func() error -type recoverableClient struct { - chcmd chan<- interface{} - cherr <-chan error -} - -type disconnectOptions struct{} -type terminateOptions struct{} - -func (c recoverableClient) Subscribe(o *client.SubscribeOptions) error { - c.chcmd <- o - return <-c.cherr -} - -func (c recoverableClient) Unsubscribe(o *client.UnsubscribeOptions) error { - c.chcmd <- o - return <-c.cherr -} - -func (c recoverableClient) Publish(o *client.PublishOptions) error { - c.chcmd <- o - return <-c.cherr -} - -func (c recoverableClient) Connect(o *client.ConnectOptions) error { - c.chcmd <- o - return <-c.cherr -} - -func (c recoverableClient) Disconnect() error { - c.chcmd <- disconnectOptions{} - return <-c.cherr -} - -func (c recoverableClient) Terminate() { - c.chcmd <- terminateOptions{} -} - // NewClient creates and connects a mqtt client with predefined options. func NewClient(id string, netAddr string, ctx log.Interface) (Client, chan Msg, error) { ctx = ctx.WithField("id", id).WithField("address", netAddr) chcmd := make(chan interface{}) - cherr := make(chan error) chmsg := make(chan Msg) - go monitorClient(id, netAddr, chcmd, cherr, ctx) + go monitorClient(id, netAddr, chcmd, ctx) tryConnect := createConnecter(id, netAddr, chmsg, chcmd, ctx) if err := tryConnect(); err != nil { @@ -77,53 +41,52 @@ func NewClient(id string, netAddr string, ctx log.Interface) (Client, chan Msg, return nil, nil, errors.New(errors.Operational, err) } - return recoverableClient{ + return safeClient{ chcmd: chcmd, - cherr: cherr, }, chmsg, nil } -func monitorClient(id string, netAddr string, chcmd <-chan interface{}, cherr chan<- error, ctx log.Interface) { +// monitorClient is used to keep all accesses to the client completely concurrent-safe. It also +// allows to replace the current client by a new one in case of error. / +// +// When the client loses its connection and isn't able to re-establish it, we need to create a new +// client. However, because that client is likely to be accessed by several goroutines at "the same +// time", we cannot just swap two variables somewhere. The hereby monitor enables a safe client +// swapping and managing. (See safeClient struct as well) +func monitorClient(id string, netAddr string, chcmd <-chan interface{}, ctx log.Interface) { var cli *client.Client ctx = ctx.WithField("process", "monitorClient") ctx.Debug("Start monitoring MQTT client") for cmd := range chcmd { if cli == nil { - init, ok := cmd.(*client.Client) + init, ok := cmd.(cmdClient) if !ok { ctx.Warn("Received cmd whereas client is nil. Ignored") continue } ctx.Debug("Setup initial MQTT client") - cli = init + cli = init.options + init.cherr <- nil continue } switch cmd.(type) { - case *client.SubscribeOptions: - ctx.Debug("Client received new subscription order") - cherr <- cli.Subscribe(cmd.(*client.SubscribeOptions)) - case *client.UnsubscribeOptions: - ctx.Debug("Client received new unsubscription order") - cherr <- cli.Unsubscribe(cmd.(*client.UnsubscribeOptions)) - case *client.PublishOptions: + case cmdPublish: + cmd := cmd.(cmdPublish) ctx.Debug("Client received new publication order") - cherr <- cli.Publish(cmd.(*client.PublishOptions)) - case *client.ConnectOptions: - ctx.Debug("Client received new connection order") - cherr <- cli.Connect(cmd.(*client.ConnectOptions)) - case disconnectOptions: - ctx.Debug("Client received disconnection order") - cherr <- cli.Disconnect() - case terminateOptions: + cmd.cherr <- cli.Publish(cmd.options) + case cmdTerminate: + cmd := cmd.(cmdTerminate) ctx.Debug("Client received termination order") cli.Terminate() cli = nil - case *client.Client: + cmd.cherr <- nil + case cmdClient: + cmd := cmd.(cmdClient) ctx.Debug("Replacing client with another one") - _ = cli.Disconnect() cli.Terminate() - cli = cmd.(*client.Client) + cli = cmd.options + cmd.cherr <- nil default: ctx.WithField("cmd", cmd).Warn("Received unreckognized command") } @@ -136,17 +99,18 @@ func monitorClient(id string, netAddr string, chcmd <-chan interface{}, cherr ch ctx.Debug("Stop monitoring MQTT client") } +// createConnecter is used to start and subscribe a new client. It also make sure that if the +// created client goes down, another one is automatically created such that the client recover +// itself. func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- interface{}, ctx log.Interface) connecter { ctx.Debug("Create new connecter for MQTT client") var cli *client.Client cli = client.New(&client.Options{ - ErrorHandler: func(err error) { - createErrorHandler( - createConnecter(id, netAddr, chmsg, chcmd, ctx), - 10000*InitialReconnectDelay, - ctx, - )(err) - }, + ErrorHandler: createErrorHandler( + func() error { return createConnecter(id, netAddr, chmsg, chcmd, ctx)() }, + 10000*InitialReconnectDelay, + ctx, + ), }) return func() error { @@ -192,9 +156,10 @@ func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- i return err } + cherr := make(chan error) select { - case chcmd <- cli: - return nil + case chcmd <- cmdClient{options: cli, cherr: cherr}: + return <-cherr case <-time.After(time.Second): return errors.New(errors.Operational, "Timeout. Unable to set new client") } diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go index 01d8a169a..87f85038a 100644 --- a/core/adapters/mqtt/client_test.go +++ b/core/adapters/mqtt/client_test.go @@ -202,7 +202,6 @@ func TestNewClient(t *testing.T) { Check(t, msg, got, "MQTT Messages") // Clean - _ = cli.Disconnect() cli.Terminate() <-time.After(time.Millisecond * 50) } @@ -238,7 +237,6 @@ func TestNewClient(t *testing.T) { Check(t, msg, got, "MQTT Messages") // Clean - _ = cli.Disconnect() cli.Terminate() <-time.After(time.Millisecond * 50) } @@ -270,7 +268,6 @@ func TestNewClient(t *testing.T) { Check(t, msg, got, "MQTT Messages") // Clean - _ = cli.Disconnect() cli.Terminate() <-time.After(time.Millisecond * 50) } @@ -315,7 +312,6 @@ func TestNewClient(t *testing.T) { Check(t, msg, got, "MQTT Messages") // Clean - _ = cli.Disconnect() cli.Terminate() _ = usurp.Disconnect() usurp.Terminate() @@ -372,7 +368,6 @@ func TestNewClient(t *testing.T) { Check(t, msg, got, "MQTT Messages") // Clean - _ = cli.Disconnect() err = testCli.Unsubscribe(&client.UnsubscribeOptions{ [][]byte{[]byte(msg.Topic)}, }) diff --git a/core/adapters/mqtt/safeclient.go b/core/adapters/mqtt/safeclient.go new file mode 100644 index 000000000..2a342e3bc --- /dev/null +++ b/core/adapters/mqtt/safeclient.go @@ -0,0 +1,43 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "github.com/yosssi/gmq/mqtt/client" +) + +// NOTE: All that code could be easily generated + +// safeClient implements the mqtt.Client interface. It is completely concurrent-safe +type safeClient struct { + chcmd chan<- interface{} +} + +// Publish implements the Client interface +func (c safeClient) Publish(o *client.PublishOptions) error { + cherr := make(chan error) + c.chcmd <- cmdPublish{options: o, cherr: cherr} + return <-cherr +} + +// Terminate implements the Client interface +func (c safeClient) Terminate() { + cherr := make(chan error) + c.chcmd <- cmdTerminate{cherr: cherr} + <-cherr +} + +type cmdPublish struct { + options *client.PublishOptions + cherr chan<- error +} + +type cmdTerminate struct { + cherr chan<- error +} + +type cmdClient struct { + options *client.Client + cherr chan<- error +} From c14857a177a832640c93b2019d85ab5f28527f04 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 00:38:18 +0100 Subject: [PATCH 1061/2266] [refactor/grpc] Increase a bit coverage in mqtt client --- core/adapters/mqtt/client_test.go | 158 +++++++++++++++++++++++------- 1 file changed, 121 insertions(+), 37 deletions(-) diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go index 87f85038a..379d4dd11 100644 --- a/core/adapters/mqtt/client_test.go +++ b/core/adapters/mqtt/client_test.go @@ -17,13 +17,9 @@ const BrokerAddr = "0.0.0.0:1883" // MockClient implements the Client interface type MockClient struct { - Failures map[string]error - InUnsubscribe *client.UnsubscribeOptions - InSubscribe *client.SubscribeOptions - InPublish *client.PublishOptions - InConnect *client.ConnectOptions - IsTerminate bool - IsDisconnect bool + Failures map[string]error + InPublish *client.PublishOptions + IsTerminate bool } // NewMockClient constructs a new MockClient @@ -33,41 +29,17 @@ func NewMockClient() *MockClient { } } -// Subscribe implements the Client interface -func (m *MockClient) Subscribe(o *client.SubscribeOptions) error { - m.InSubscribe = o - return m.Failures["Subscribe"] -} - -// Unsubscribe implements the Client interface -func (m *MockClient) Unsubscribe(o *client.UnsubscribeOptions) error { - m.InUnsubscribe = o - return m.Failures["Unsubscribe"] -} - // Publish implements the Client interface func (m *MockClient) Publish(o *client.PublishOptions) error { m.InPublish = o return m.Failures["Publish"] } -// Connect implements the Client interface -func (m *MockClient) Connect(o *client.ConnectOptions) error { - m.InConnect = o - return m.Failures["Connect"] -} - // Terminate implements the Client interface func (m *MockClient) Terminate() { m.IsTerminate = true } -// Disconnect implements the Client interface -func (m *MockClient) Disconnect() error { - m.IsDisconnect = true - return m.Failures["Disconnect"] -} - // MockTryConnect simulates a tryConnect function type MockTryConnect struct { Func connecter @@ -143,13 +115,11 @@ func TestCreateErrorHandler(t *testing.T) { } } -func TestNewClient(t *testing.T) { - var id int - newID := func() string { - id++ - return fmt.Sprintf("(%d)Client#%d", time.Now().Nanosecond(), id) - } +func newID() string { + return fmt.Sprintf("(%d)Client", time.Now().Nanosecond()) +} +func TestNewClient(t *testing.T) { testCli := client.New(nil) if err := testCli.Connect(&client.ConnectOptions{ Network: "tcp", @@ -381,3 +351,117 @@ func TestNewClient(t *testing.T) { _ = testCli.Disconnect() testCli.Terminate() } + +func TestMonitorClient(t *testing.T) { + { + Desc(t, "Ensure monitor stops when cmd is closed, without any client") + + // Build + chcmd := make(chan interface{}) + chdone := make(chan bool) + + // Operate + go func() { + monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) + chdone <- true + }() + close(chcmd) + + // Check + var done bool + select { + case done = <-chdone: + case <-time.After(time.Millisecond * 50): + } + Check(t, true, done, "Done signals") + + } + + // -------------------- + + { + Desc(t, "Ensure monitor stops when cmd is closed, with a client") + + // Build + cli := client.New(nil) + chcmd := make(chan interface{}) + chdone := make(chan bool) + cherr := make(chan error) + + // Operate + go func() { + monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) + chdone <- true + }() + chcmd <- cmdClient{cherr: cherr, options: cli} + <-cherr + close(chcmd) + + // Check + var done bool + select { + case done = <-chdone: + case <-time.After(time.Millisecond * 50): + } + Check(t, true, done, "Done signals") + + } + + // -------------------- + + { + Desc(t, "Send an invalid command to monitor, no client") + + // Build + chcmd := make(chan interface{}) + chdone := make(chan bool) + + // Operate + go func() { + monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) + chdone <- true + }() + chcmd <- "Patate" + + // Check + var done bool + select { + case done = <-chdone: + case <-time.After(time.Millisecond * 50): + } + Check(t, false, done, "Done signals") + } + + // -------------------- + + { + Desc(t, "Send an invalid command to monitor, no client") + + // Build + cli := client.New(nil) + chcmd := make(chan interface{}) + chdone := make(chan bool) + cherr := make(chan error) + + // Operate + go func() { + monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) + chdone <- true + }() + chcmd <- cmdClient{cherr: cherr, options: cli} + <-cherr + chcmd <- "Patate" + + // Check + var done bool + select { + case done = <-chdone: + case <-time.After(time.Millisecond * 50): + } + Check(t, false, done, "Done signals") + + // Clean + _ = cli.Disconnect() + cli.Terminate() + } +} From 8d1fa573db1e29f2dec1c7caa6189b19143d40aa Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 11:31:50 +0100 Subject: [PATCH 1062/2266] [refactor/grpc] Write missing tests for mqtt adapter. Now done. --- core/adapters/mqtt/client_test.go | 14 +- core/adapters/mqtt/mqtt.go | 55 ++-- core/adapters/mqtt/mqtt_test.go | 447 +++++++++++++++++++++++++++++ core/mocks/mocks.go | 451 ++++-------------------------- 4 files changed, 540 insertions(+), 427 deletions(-) diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go index 379d4dd11..d2ad378ce 100644 --- a/core/adapters/mqtt/client_test.go +++ b/core/adapters/mqtt/client_test.go @@ -17,9 +17,13 @@ const BrokerAddr = "0.0.0.0:1883" // MockClient implements the Client interface type MockClient struct { - Failures map[string]error - InPublish *client.PublishOptions - IsTerminate bool + Failures map[string]error + InPublish struct { + Options *client.PublishOptions + } + InTerminate struct { + Called bool + } } // NewMockClient constructs a new MockClient @@ -31,13 +35,13 @@ func NewMockClient() *MockClient { // Publish implements the Client interface func (m *MockClient) Publish(o *client.PublishOptions) error { - m.InPublish = o + m.InPublish.Options = o return m.Failures["Publish"] } // Terminate implements the Client interface func (m *MockClient) Terminate() { - m.IsTerminate = true + m.InTerminate.Called = true } // MockTryConnect simulates a tryConnect function diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 1a25a3609..2b58199d4 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -20,9 +20,19 @@ import ( ) type adapter struct { - Client - handler core.HandlerClient - ctx log.Interface + Components +} + +// Components defines a structure to make the instantiation easier to read +type Components struct { + Handler core.HandlerClient + Client Client + Ctx log.Interface +} + +// Options defines a structure to make the instantiation easier to read +type Options struct { + InMsg <-chan Msg } // Msg are emitted by an MQTT subscriber towards the adapter @@ -32,8 +42,6 @@ type Msg struct { Type msgType } -type msgType byte - // msgType constants are used in MQTTMsg to characterise the kind of message processed const ( Down msgType = iota @@ -41,17 +49,13 @@ const ( OTAA ) +type msgType byte + // New constructs an mqtt adapter responsible for making the bridge between the handler and // application. -func New(handler core.HandlerClient, client Client, chmsg <-chan Msg, ctx log.Interface) core.AppClient { - a := adapter{ - Client: client, - handler: handler, - ctx: ctx, - } - - go a.consumeMQTTMsg(chmsg) - +func New(c Components, o Options) core.AppClient { + a := adapter{Components: c} + go a.consumeMQTTMsg(o.InMsg) return a } @@ -60,6 +64,11 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp stats.MarkMeter("mqtt_adapter.send") // Verify the packet integrity + // TODO Move this elsewhere, make it a function call validate() ... + if req == nil { + stats.MarkMeter("mqtt_adapter.uplink.invalid") + return nil, errors.New(errors.Structural, "Received Nil Application Request") + } if len(req.Payload) == 0 { stats.MarkMeter("mqtt_adapter.uplink.invalid") return nil, errors.New(errors.Structural, "Invalid Packet Payload") @@ -76,7 +85,7 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp stats.MarkMeter("mqtt_adapter.uplink.invalid") return nil, errors.New(errors.Structural, "Missing Mandatory Metadata") } - ctx := a.ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI) + ctx := a.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI) // Marshal the packet dataUp := core.DataUpAppReq{ @@ -91,7 +100,7 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp // Actually send it ctx.Debug("Sending Packet") deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) - err = a.Publish(&client.PublishOptions{ + err = a.Client.Publish(&client.PublishOptions{ QoS: mqtt.QoS2, Retain: true, TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)), @@ -101,7 +110,6 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp if err != nil { return nil, errors.New(errors.Operational, err) } - return nil, nil } @@ -109,29 +117,30 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp // // It runs in its own goroutine func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { - a.ctx.Debug("Start consuming MQTT message") + a.Ctx.Debug("Start consuming MQTT messages") for msg := range chmsg { switch msg.Type { case Down: req, err := handleDataDown(msg) if err == nil { - _, err = a.handler.HandleDataDown(context.Background(), req) + _, err = a.Handler.HandleDataDown(context.Background(), req) } if err != nil { - a.ctx.WithError(err).Debug("Unable to consume data down") + a.Ctx.WithError(err).Debug("Unable to consume data down") } case ABP: req, err := handleABP(msg) if err == nil { - _, err = a.handler.SubscribePersonalized(context.Background(), req) + _, err = a.Handler.SubscribePersonalized(context.Background(), req) } if err != nil { - a.ctx.WithError(err).Debug("Unable to consume ABP") + a.Ctx.WithError(err).Debug("Unable to consume ABP") } default: - a.ctx.Debug("Unsupported MQTT message's type") + a.Ctx.Debug("Unsupported MQTT message's type") } } + a.Ctx.Debug("Stop consuming MQTT messages") } // handleABP parses and handles Application By Personalization request coming through MQTT diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index c4241df43..eb301fe22 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -4,10 +4,15 @@ package mqtt import ( + "fmt" "testing" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/yosssi/gmq/mqtt" + "github.com/yosssi/gmq/mqtt/client" + "golang.org/x/net/context" ) func TestHandleDataDown(t *testing.T) { @@ -414,3 +419,445 @@ func TestHandleABP(t *testing.T) { Check(t, want, req, "ABP Subscription Handler Requests") } } + +func TestConsumeMQTTMsg(t *testing.T) { + { + Desc(t, "Consume Valid MsgDown") + + // Build + chmsg := make(chan Msg) + options := Options{InMsg: chmsg} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + _ = New(components, options) + + wantDown := &core.DataDownHandlerReq{ + Payload: []byte("patate"), + DevEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + var wantABP *core.ABPSubHandlerReq + + // Operate + chmsg <- Msg{ + Type: Down, + Topic: "0102030405060708/devices/0807060504030201/down", + Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), + } + + // Checks + Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + + // Clean + close(chmsg) + } + + // -------------------- + + { + Desc(t, "Consume invalid MsgDown") + + // Build + chmsg := make(chan Msg) + options := Options{InMsg: chmsg} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + _ = New(components, options) + + var wantDown *core.DataDownHandlerReq + var wantABP *core.ABPSubHandlerReq + + // Operate + chmsg <- Msg{ + Type: Down, + Topic: "0102030405060708/devices/08070605040/down", + Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), + } + + // Checks + Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + + // Clean + close(chmsg) + } + + // -------------------- + + { + Desc(t, "Consume Valid MsgABP") + + // Build + chmsg := make(chan Msg) + options := Options{InMsg: chmsg} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + _ = New(components, options) + + var wantDown *core.DataDownHandlerReq + wantABP := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + AppSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + + // Operate + chmsg <- Msg{ + Type: ABP, + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{ + "dev_addr":"01020304", + "apps_key":"01020304050607080900010203040506", + "nwks_key":"06050403020100090807060504030201" + }`), + } + + // Checks + Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + + // Clean + close(chmsg) + } + + // -------------------- + + { + Desc(t, "Consume invalid MsgABP") + + // Build + chmsg := make(chan Msg) + options := Options{InMsg: chmsg} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + _ = New(components, options) + + var wantDown *core.DataDownHandlerReq + var wantABP *core.ABPSubHandlerReq + + // Operate + chmsg <- Msg{ + Type: ABP, + Topic: "0102030405060708/devices/personalized/activations", + Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), + } + + // Checks + Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + + // Clean + close(chmsg) + } + + // -------------------- + + { + Desc(t, "Consume Invalid Message Type") + + // Build + chmsg := make(chan Msg) + options := Options{InMsg: chmsg} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + _ = New(components, options) + + var wantDown *core.DataDownHandlerReq + var wantABP *core.ABPSubHandlerReq + + // Operate + chmsg <- Msg{ + Type: 14, + Topic: "0102030405060708/devices/0807060504030201/down", + Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), + } + + // Checks + Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + + // Clean + close(chmsg) + } +} + +func TestHandleData(t *testing.T) { + { + Desc(t, "Handle Invalid AppReq -> Empty payload") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + adapter := New(components, options) + + // Expectations + var wantRes *core.DataAppRes + var wantPub *client.PublishOptions + var wantErr = ErrStructural + + // Operate + res, err := adapter.HandleData( + context.Background(), + &core.DataAppReq{ + Payload: nil, + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + Metadata: []*core.Metadata{}, + }, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } + + // -------------------- + + { + Desc(t, "Handle Invalid AppReq -> Invalid AppEUI") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + adapter := New(components, options) + + // Expectations + var wantRes *core.DataAppRes + var wantPub *client.PublishOptions + var wantErr = ErrStructural + + // Operate + res, err := adapter.HandleData( + context.Background(), + &core.DataAppReq{ + Payload: []byte("patate"), + AppEUI: []byte{1, 2, 3, 4, 5, 6}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + Metadata: []*core.Metadata{}, + }, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } + + // -------------------- + + { + Desc(t, "Handle Invalid AppReq -> Invalid DevEUI") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + adapter := New(components, options) + + // Expectations + var wantRes *core.DataAppRes + var wantPub *client.PublishOptions + var wantErr = ErrStructural + + // Operate + res, err := adapter.HandleData( + context.Background(), + &core.DataAppReq{ + Payload: []byte("patate"), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4, 5, 6}, + Metadata: []*core.Metadata{}, + }, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } + + // -------------------- + + { + Desc(t, "Handle Invalid AppReq -> No Metadata") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + adapter := New(components, options) + + // Expectations + var wantRes *core.DataAppRes + var wantPub *client.PublishOptions + var wantErr = ErrStructural + + // Operate + res, err := adapter.HandleData( + context.Background(), + &core.DataAppReq{ + Payload: []byte("patate"), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + Metadata: nil, + }, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } + + // -------------------- + + { + Desc(t, "Handle Invalid AppReq -> Nil AppReq") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + adapter := New(components, options) + + // Expectations + var wantRes *core.DataAppRes + var wantPub *client.PublishOptions + var wantErr = ErrStructural + + // Operate + res, err := adapter.HandleData( + context.Background(), + nil, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } + + // -------------------- + + { + Desc(t, "Handle Valid AppReq, Fail to Publish") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + components.Client.(*MockClient).Failures["Publish"] = fmt.Errorf("Mock Error") + adapter := New(components, options) + msg := core.DataUpAppReq{Payload: []byte("patate"), Metadata: []core.AppMetadata{}} + data, err := msg.MarshalMsg(nil) + FatalUnless(t, err) + + // Expectations + var wantRes *core.DataAppRes + var wantPub = &client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: true, + TopicName: []byte("0102030405060708/devices/0000000001020304/up"), + Message: data, + } + var wantErr = ErrOperational + + // Operate + res, err := adapter.HandleData( + context.Background(), + &core.DataAppReq{ + Payload: []byte("patate"), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + Metadata: []*core.Metadata{}, + }, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } + + // -------------------- + + { + Desc(t, "Handle Valid AppReq, Publish successful") + + // Build + options := Options{InMsg: nil} + components := Components{ + Client: NewMockClient(), + Handler: mocks.NewHandlerClient(), + Ctx: GetLogger(t, "MQTT Adapter"), + } + adapter := New(components, options) + msg := core.DataUpAppReq{Payload: []byte("patate"), Metadata: []core.AppMetadata{}} + data, err := msg.MarshalMsg(nil) + FatalUnless(t, err) + + // Expectations + var wantRes *core.DataAppRes + var wantPub = &client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: true, + TopicName: []byte("0102030405060708/devices/0000000001020304/up"), + Message: data, + } + var wantErr *string + + // Operate + res, err := adapter.HandleData( + context.Background(), + &core.DataAppReq{ + Payload: []byte("patate"), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + Metadata: []*core.Metadata{}, + }, + ) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Responses") + Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") + } +} diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 852ae50b5..8db5e37bd 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -1,429 +1,82 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. +// Package mocks offers dedicated mocking interface / structures for testing package mocks import ( - "fmt" - "reflect" - "testing" - - . "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" + "github.com/TheThingsNetwork/ttn/core" + "golang.org/x/net/context" + "google.golang.org/grpc" ) -// MockRecipient implements the core.Recipient interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockRecipient struct { - Failures map[string]error - InUnmarshalBinary []byte // Data received by UnmarshalBinary() - OutMarshalBinary []byte // Data spit out by MarshalBinary() -} - -// NewMockRecipient constructs a new mock recipient. -func NewMockRecipient() *MockRecipient { - return &MockRecipient{ - OutMarshalBinary: []byte("MockRecipientData"), - Failures: make(map[string]error), - } -} - -func (r *MockRecipient) MarshalBinary() ([]byte, error) { - if r.Failures["MarshalBinary"] != nil { - return nil, r.Failures["MarshalBinary"] - } - return r.OutMarshalBinary, nil -} - -func (r *MockRecipient) UnmarshalBinary(data []byte) error { - r.InUnmarshalBinary = data - if r.Failures["UnmarshalBinary"] != nil { - return r.Failures["UnmarshalBinary"] - } - return nil -} +// NOTE: All the code below could be generated -type MockJSONRecipient struct { - *MockRecipient - InUnmarshalJSON []byte - OutMarshalJSON []byte -} - -func NewMockJSONRecipient() *MockJSONRecipient { - return &MockJSONRecipient{ - MockRecipient: NewMockRecipient(), - OutMarshalJSON: []byte(`{"out":"MockJSONRecipientData"}`), - } -} - -func (r *MockJSONRecipient) MarshalJSON() ([]byte, error) { - if r.Failures["MarshalJSON"] != nil { - return nil, r.Failures["MarshalJSON"] - } - return r.OutMarshalJSON, nil -} - -func (r *MockJSONRecipient) UnmarshalJSON(data []byte) error { - r.InUnmarshalJSON = data - if r.Failures["UnmarshalJSON"] != nil { - return r.Failures["UnmarshalJSON"] - } - return nil -} - -// MockRegistration implements the core.Registration interface -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockRegistration struct { +// HandlerClient mocks the core.HandlerClient interface +type HandlerClient struct { Failures map[string]error - OutRecipient Recipient - OutMarshalJSON []byte -} - -func NewMockRegistration() MockRegistration { - return MockRegistration{ - Failures: make(map[string]error), - OutRecipient: NewMockRecipient(), - OutMarshalJSON: []byte(`{"out":"MockRegistration"}`), + InHandleDataUp struct { + Ctx context.Context + Req *core.DataUpHandlerReq + Opts []grpc.CallOption } -} - -func (r MockRegistration) RawRecipient() []byte { - data, _ := r.Recipient().MarshalBinary() - return data -} - -func (r MockRegistration) Recipient() Recipient { - return r.OutRecipient -} - -func (r MockRegistration) MarshalJSON() ([]byte, error) { - if r.Failures["MarshalJSON"] != nil { - return nil, r.Failures["MarshalJSON"] + OutHandleDataUp struct { + Res *core.DataUpHandlerRes } - return r.OutMarshalJSON, nil -} - -// MockARegistration implements the core.ARegistration interface -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockARegistration struct { - MockRegistration - OutAppEUI lorawan.EUI64 -} - -func NewMockARegistration() MockARegistration { - return MockARegistration{ - MockRegistration: NewMockRegistration(), - OutAppEUI: lorawan.EUI64([8]byte{9, 0, 9, 2, 2, 2, 3, 4}), + InHandleDataDown struct { + Ctx context.Context + Req *core.DataDownHandlerReq + Opts []grpc.CallOption } -} - -func (r MockARegistration) AppEUI() lorawan.EUI64 { - return r.OutAppEUI -} - -// MockRRegistration implements the core.RRegistration interface -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockRRegistration struct { - MockRegistration - OutDevEUI lorawan.EUI64 -} - -func NewMockRRegistration() MockRRegistration { - return MockRRegistration{ - MockRegistration: NewMockRegistration(), - OutDevEUI: lorawan.EUI64([8]byte{2, 2, 2, 2, 2, 2, 2, 2}), + OutHandleDataDown struct { + Res *core.DataDownHandlerRes } -} - -func (r MockRRegistration) DevEUI() lorawan.EUI64 { - return r.OutDevEUI -} - -// MockBRegistration implements the core.BRegistration interface -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockBRegistration struct { - MockRRegistration - OutAppEUI lorawan.EUI64 - OutNwkSKey lorawan.AES128Key -} - -func NewMockBRegistration() MockBRegistration { - return MockBRegistration{ - MockRRegistration: NewMockRRegistration(), - OutAppEUI: lorawan.EUI64([8]byte{1, 1, 1, 1, 1, 1, 1, 1}), - OutNwkSKey: lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}), + InSubscribePersonalized struct { + Ctx context.Context + Req *core.ABPSubHandlerReq + Opts []grpc.CallOption } -} - -func (r MockBRegistration) AppEUI() lorawan.EUI64 { - return r.OutAppEUI -} - -func (r MockBRegistration) NwkSKey() lorawan.AES128Key { - return r.OutNwkSKey -} - -// MockHRegistration implements the core.HRegistration interface -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockHRegistration struct { - MockBRegistration - OutAppSKey lorawan.AES128Key -} - -func NewMockHRegistration() MockHRegistration { - return MockHRegistration{ - MockBRegistration: NewMockBRegistration(), - OutAppSKey: lorawan.AES128Key([16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}), - } -} - -func (r MockHRegistration) AppSKey() lorawan.AES128Key { - return r.OutAppSKey -} - -// MockAckNacker implements the core.AckNacker interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockAckNacker struct { - InAck struct { - Ack *bool - Packet Packet - Error error - } -} - -func NewMockAckNacker() *MockAckNacker { - return &MockAckNacker{} -} - -func (an *MockAckNacker) Ack(p Packet) error { - an.InAck = struct { - Ack *bool - Packet Packet - Error error - }{ - Ack: pointer.Bool(true), - Packet: p, - } - return nil -} - -func (an *MockAckNacker) Nack(err error) error { - an.InAck = struct { - Ack *bool - Packet Packet - Error error - }{ - Ack: pointer.Bool(false), - Error: err, - } - return nil -} - -// MockAdapter implements the core.Adapter interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockAdapter struct { - Failures map[string]error - InSendPacket Packet - InSendRecipients []Recipient - InGetRecipient []byte - OutSend []byte - OutGetRecipient Recipient - OutNextPacket []byte - OutNextAckNacker AckNacker - OutNextRegReg Registration - OutNextRegAckNacker AckNacker -} - -func NewMockAdapter() *MockAdapter { - return &MockAdapter{ - Failures: make(map[string]error), - OutSend: []byte("MockAdapterSend"), - OutGetRecipient: NewMockRecipient(), - OutNextPacket: []byte("MockAdapterNextPacket"), - OutNextAckNacker: NewMockAckNacker(), - OutNextRegReg: NewMockHRegistration(), - OutNextRegAckNacker: NewMockAckNacker(), - } -} - -func (a *MockAdapter) Send(p Packet, r ...Recipient) ([]byte, error) { - a.InSendPacket = p - a.InSendRecipients = r - if a.Failures["Send"] != nil { - return nil, a.Failures["Send"] - } - return a.OutSend, nil -} - -func (a *MockAdapter) GetRecipient(raw []byte) (Recipient, error) { - a.InGetRecipient = raw - if a.Failures["GetRecipient"] != nil { - return nil, a.Failures["GetRecipient"] - } - return a.OutGetRecipient, nil -} - -func (a *MockAdapter) Next() ([]byte, AckNacker, error) { - if a.Failures["Next"] != nil { - return nil, nil, a.Failures["Next"] + OutSubscribePersonalized struct { + Res *core.ABPSubHandlerRes } - return a.OutNextPacket, a.OutNextAckNacker, nil -} - -func (a *MockAdapter) NextRegistration() (Registration, AckNacker, error) { - if a.Failures["NextRegistration"] != nil { - return nil, nil, a.Failures["NextRegistration"] - } - return a.OutNextRegReg, a.OutNextRegAckNacker, nil -} - -// MockSubscriber implements the core.Subscriber interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockSubscriber struct { - Failures map[string]error - InSubscribeRegistration Registration } -func NewMockSubscriber() *MockSubscriber { - return &MockSubscriber{ +// NewHandlerClient creates a new mock HandlerClient +func NewHandlerClient() *HandlerClient { + return &HandlerClient{ Failures: make(map[string]error), } } -func (s *MockSubscriber) Subscribe(reg Registration) error { - s.InSubscribeRegistration = reg - return s.Failures["Subscribe"] -} - -// MockPacket implements the core.Packet interface -// -// It declares a `Failures` attributes that can be used to -// simulate failures on demand, associating the name of the method -// which needs to fail with the actual failure. -// -// It also stores the last arguments of each function call in appropriated -// attributes. Because there's no computation going on, the expected / wanted -// responses should also be defined. Default values are provided but can be changed -// if needed. -type MockPacket struct { - Failures map[string]error - OutMarshalBinary []byte - OutString string - OutDevEUI lorawan.EUI64 -} - -func NewMockPacket() *MockPacket { - return &MockPacket{ - Failures: make(map[string]error), - OutMarshalBinary: []byte("MockPacketBinary"), - OutString: "MockPacket", - OutDevEUI: lorawan.EUI64([8]byte{1, 2, 1, 2, 4, 4, 5, 5}), - } -} - -func (p *MockPacket) DevEUI() lorawan.EUI64 { - return p.OutDevEUI -} - -func (p *MockPacket) MarshalBinary() ([]byte, error) { - if p.Failures["MarshalBinary"] != nil { - return nil, p.Failures["MarshalBinary"] +// HandleDataUp implements the core.HandlerClient interface +func (m *HandlerClient) HandleDataUp(ctx context.Context, in *core.DataUpHandlerReq, opts ...grpc.CallOption) (*core.DataUpHandlerRes, error) { + m.InHandleDataUp.Ctx = ctx + m.InHandleDataUp.Req = in + m.InHandleDataUp.Opts = opts + if err := m.Failures["HandleDataUp"]; err != nil { + return nil, err } - return p.OutMarshalBinary, nil + return m.OutHandleDataUp.Res, nil } -func (p *MockPacket) String() string { - return p.OutString -} - -// ----- CHECK utilities - -func Check(t *testing.T, want, got interface{}, name string) { - if !reflect.DeepEqual(want, got) { - Ko(t, "%s don't match expectations.\nWant: %+v\nGot: %+v", name, want, got) +// HandleDataDown implements the core.HandlerClient interface +func (m *HandlerClient) HandleDataDown(ctx context.Context, in *core.DataDownHandlerReq, opts ...grpc.CallOption) (*core.DataDownHandlerRes, error) { + m.InHandleDataDown.Ctx = ctx + m.InHandleDataDown.Req = in + m.InHandleDataDown.Opts = opts + if err := m.Failures["HandleDataDown"]; err != nil { + return nil, err } - Ok(t, fmt.Sprintf("Check %s", name)) + return m.OutHandleDataDown.Res, nil } -func CheckAcks(t *testing.T, want interface{}, gotItf interface{}) { - got := gotItf.(struct { - Ack *bool - Packet Packet - Error error - }) - - if got.Ack == nil { - Ko(t, "Invalid ack got: %+v", got) +// SubscribePersonalized implements the core.HandlerClient interface +func (m *HandlerClient) SubscribePersonalized(ctx context.Context, in *core.ABPSubHandlerReq, opts ...grpc.CallOption) (*core.ABPSubHandlerRes, error) { + m.InSubscribePersonalized.Ctx = ctx + m.InSubscribePersonalized.Req = in + m.InSubscribePersonalized.Opts = opts + if err := m.Failures["SubscribePersonalized"]; err != nil { + return nil, err } - - switch want.(type) { - case bool: - Check(t, want.(bool), *(got.Ack), "Acks") - case Packet: - Check(t, want.(Packet), got.Packet, "Acks") - default: - panic("Unexpect ack wanted") - } -} - -func CheckRecipients(t *testing.T, want []Recipient, got []Recipient) { - Check(t, want, got, "Recipients") -} - -func CheckSent(t *testing.T, want Packet, got Packet) { - Check(t, want, got, "Sent") + return m.OutSubscribePersonalized.Res, nil } From fdeb5a2314fd99246f9296556879a21469aec56f Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 12:06:29 +0100 Subject: [PATCH 1063/2266] [refactor/grpc] Add doc to mqtt adapter package --- core/adapters/mqtt/doc.go | 78 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 core/adapters/mqtt/doc.go diff --git a/core/adapters/mqtt/doc.go b/core/adapters/mqtt/doc.go new file mode 100644 index 000000000..2c88b3c3c --- /dev/null +++ b/core/adapters/mqtt/doc.go @@ -0,0 +1,78 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +/* +Package mqtt provides an MQTT adapter between the network and an MQTT broker. The adapter listens on +2 topics: + + +/devices/+/down + +/devices/+/activations + +and may publish on two of them: + + +/devices/+/up + +/devices/+/activations + +Above, the first wildcard refers to an Application Unique Identifier (AppEUI) which is a +hex-encoded string of 16 characters (representing an 8-bytes long AppEUI). The second wildcard +refers to a Device Unique Identifier (DevEUI) which is also an hex-encoded string of 16 +characters with one exception. + +For ABP (Activation By Personalization), the activation should be made to: + + +/devices/personalized/activations + + +Serialization Format + +For each topic, a MessagePack-JSON serialization format is expected, with the following top-level +json structure: + + +ABP :: +/devices/personalized/activations + + {"dev_addr": "% 4 bytes hex-encoded %", "apps_key": "% 16 bytes hex-encoded %", "nwks_key": "% 16 bytes hex-encoded %"} + {"dev_addr":"01020304","apps_key":"01020304050607080900010203040506","nwks_key":"01020304050607080900010203040506"} + +OTAA :: +/devices/+/activations + +... TODO + +Downlink :: +/devices/+/down + + {"payload": % sequence of bytes, decimal format %} + {"payload":[112,97,116,97,116,101]} + +Uplink :: +/devices/+/up + + { + "payload": %sequence of bytes, decimal format%, + "metadata": [{ + "coding_rate": % string %, + "data_rate": % string %, + "frequency": % float %, + "timestamp": % uint %, + "rssi": % int %, + "lsnr": % float %, + "altitude": % int %, + "latitude": % float %, + "longitude": % float % + }, ... ] + } + + { + "payload": [112,97,116,97,116,101], + "metadata": [{ + "coding_rate": "4/6", + "data_rate": "SF8BW125", + "frequency": "866.345", + "timestamp": "123698454", + "rssi": "-37", + "lsnr": "5.3", + "altitude": "56", + "latitude": "-14.678", + "longitude": "33.120182" + }] + } +*/ +package mqtt From bf6e8e48adaf3eeae0e9d8fba3a22a9fcaf7d93b Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 15:43:51 +0100 Subject: [PATCH 1064/2266] [refactor/grpc] Test new metadata conversion methods and fix accordingly --- core/adapters/udp/conversions.go | 56 ++++++--- core/adapters/udp/conversions_test.go | 164 ++++++++++++++++++++++++++ core/adapters/udp/udp.go | 100 +++++++++++----- semtech/decode_test.go | 26 ++-- semtech/encode_test.go | 38 +++--- semtech/semtech.go | 76 ++++++------ semtech/semtech_test.go | 10 +- utils/pointer/pointer.go | 99 ++++++++++++++++ 8 files changed, 446 insertions(+), 123 deletions(-) create mode 100644 core/adapters/udp/conversions_test.go diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 4989feb79..3d5bf8466 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -12,10 +12,11 @@ import ( "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/apex/log" "github.com/brocaar/lorawan" ) -func (a adapter) newDataRouterReq(rxpk semtech.RXPK, gid []byte) (*core.DataRouterReq, error) { +func newDataRouterReq(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (*core.DataRouterReq, error) { // First, we have to get the physical payload which is encoded in the Data field if rxpk.Data == nil { return nil, errors.New(errors.Structural, "There's no data in the packet") @@ -89,7 +90,7 @@ func (a adapter) newDataRouterReq(rxpk semtech.RXPK, gid []byte) (*core.DataRout }, nil } -func (a adapter) newTXPK(resp core.DataRouterRes) (semtech.TXPK, error) { +func newTXPK(resp core.DataRouterRes, ctx log.Interface) (semtech.TXPK, error) { // Step 0: validate the response if resp.Metadata == nil { return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Metadata") @@ -115,29 +116,52 @@ func (a adapter) newTXPK(resp core.DataRouterRes) (semtech.TXPK, error) { return txpk, nil } +// injectMetadata takes metadata from a Struct and inject them into an xpk Struct (rxpk or txpk). +// The xpk is expected to be a pointer to an existing struct. Struct-tag in the rxpk struct are used +// to indicate with field from the struct is bound to which field on the xpk. +// +// All fields in the src struct are expected to be non-pointer values. +// +// It eventually returns the initial xpk argument func injectMetadata(xpk interface{}, src interface{}) interface{} { - v := reflect.ValueOf(src) - t := v.Type() - d := reflect.ValueOf(xpk).Elem() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i).Name - if d.FieldByName(field).CanSet() { - d.FieldByName(field).Set(v.Field(i)) + m := reflect.ValueOf(src) + x := reflect.ValueOf(xpk).Elem() + tx := x.Type() + + for i := 0; i < tx.NumField(); i++ { + t := tx.Field(i).Tag.Get("full") + f := m.FieldByName(t) + if f.IsValid() && f.Interface() != reflect.Zero(f.Type()).Interface() { + p := reflect.New(f.Type()) + p.Elem().Set(f) + if p.Type().AssignableTo(x.Field(i).Type()) { + x.Field(i).Set(p) + } } } + return xpk } +// extractMetadata does the reverse operation than injectMetadata. It takes metadata from an xpk +// structure and inject all compatible field into a given Struct. +// This time, the xpk is expected to be a plain xpk struct (not a pointer) and the target struct +// should be a reference to that struct. +// +// It eventually returns the completed target element. func extractMetadata(xpk interface{}, target interface{}) interface{} { - v := reflect.ValueOf(xpk) - t := v.Type() + x := reflect.ValueOf(xpk) + tx := x.Type() m := reflect.ValueOf(target).Elem() - for i := 0; i < t.NumField(); i++ { - field := t.Field(i).Name - if m.FieldByName(field).CanSet() { - m.FieldByName(field).Set(v.Field(i)) + for i := 0; i < tx.NumField(); i++ { + t := tx.Field(i).Tag.Get("full") + f := m.FieldByName(t) + if f.IsValid() && !x.Field(i).IsNil() { + e := x.Field(i).Elem() + if e.Type().AssignableTo(m.FieldByName(t).Type()) { + m.FieldByName(t).Set(e) + } } } return target diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go new file mode 100644 index 000000000..7a85b07c3 --- /dev/null +++ b/core/adapters/udp/conversions_test.go @@ -0,0 +1,164 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/semtech" + "testing" + // "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestInjectMetadata(t *testing.T) { + { + Desc(t, "Inject full set of Metadata") + + // Build + rxpk := new(semtech.RXPK) + meta := core.Metadata{ + Altitude: -12, + CodingRate: "4/5", + DataRate: "SF7BW125", + DutyRX1: 12, + DutyRX2: 0, + Frequency: 868.34, + Latitude: 12.67, + Longitude: 33.45, + Lsnr: 5.2, + PayloadSize: 14, + Rssi: -12, + Timestamp: 123455, + } + + // Expectation + var want = &semtech.RXPK{ + Codr: pointer.String(meta.CodingRate), + Datr: pointer.String(meta.DataRate), + Freq: pointer.Float32(meta.Frequency), + Lsnr: pointer.Float32(meta.Lsnr), + Size: pointer.Uint32(meta.PayloadSize), + Rssi: pointer.Int32(meta.Rssi), + Tmst: pointer.Uint32(meta.Timestamp), + } + + // Operate + _ = injectMetadata(rxpk, meta) + + // Check + Check(t, want, rxpk, "RXPKs") + } + + // -------------------- + + { + Desc(t, "Partial Set of Metadata") + + // Build + rxpk := new(semtech.RXPK) + meta := core.Metadata{ + Altitude: -12, + CodingRate: "4/5", + Rssi: -12, + Timestamp: 123455, + } + + // Expectation + var want = &semtech.RXPK{ + Codr: pointer.String(meta.CodingRate), + Rssi: pointer.Int32(meta.Rssi), + Tmst: pointer.Uint32(meta.Timestamp), + } + + // Operate + _ = injectMetadata(rxpk, meta) + + // Check + Check(t, want, rxpk, "RXPKs") + } +} + +func TestExtractMetadta(t *testing.T) { + { + Desc(t, "Extract full set of Metadata") + + // Build + txpk := semtech.TXPK{ + Codr: pointer.String("4/5"), + Data: pointer.String("4xvansicvni7bvcxxcvxds=="), + Datr: pointer.String("SF7BW125"), + Fdev: pointer.Uint32(42), + Freq: pointer.Float32(868.45), + Imme: pointer.Bool(true), + Ipol: pointer.Bool(false), + Modu: pointer.String("Lora"), + Ncrc: pointer.Bool(false), + Powe: pointer.Uint32(12000), + Prea: pointer.Uint32(1000), + Rfch: pointer.Uint32(3), + Size: pointer.Uint32(14), + Time: pointer.Time(time.Now()), + Tmst: pointer.Uint32(23456789), + } + meta := new(core.AppMetadata) + + // Expectation + var want = &core.AppMetadata{ + CodingRate: *txpk.Codr, + DataRate: *txpk.Datr, + Frequency: *txpk.Freq, + Timestamp: *txpk.Tmst, + } + + // Operate + _ = extractMetadata(txpk, meta) + + // Check + Check(t, want, meta, "Metadata") + } + + // -------------------- + + { + Desc(t, "Extract partial set of Metadata") + + // Build + txpk := semtech.TXPK{ + Codr: pointer.String("4/5"), + Datr: pointer.String("SF7BW125"), + Fdev: pointer.Uint32(42), + Imme: pointer.Bool(true), + Ipol: pointer.Bool(false), + Ncrc: pointer.Bool(false), + Powe: pointer.Uint32(12000), + Prea: pointer.Uint32(1000), + Size: pointer.Uint32(14), + } + meta := new(core.AppMetadata) + + // Expectation + var want = &core.AppMetadata{ + CodingRate: *txpk.Codr, + DataRate: *txpk.Datr, + } + + // Operate + _ = extractMetadata(txpk, meta) + + // Check + Check(t, want, meta, "Metadata") + } + +} + +func TestNewTXPK(t *testing.T) { + +} + +func TestNewDataRouterReq(t *testing.T) { + +} diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index ede990fa6..0246ac000 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -18,25 +18,39 @@ import ( ) type adapter struct { - ctx log.Interface + Components } +// Components defines a structure to make the instantiation easier to read +type Components struct { + Ctx log.Interface + Router core.RouterServer +} + +// Options defines a structure to make the instantiation easier to read +type Options struct { + // NetAddr refers to the udp address + port the adapter will have to listen + NetAddr string + // MaxReconnectionDelay defines the delay of the longest attempt to reconnect a lost connection + // before giving up. + MaxReconnectionDelay time.Duration +} + +// replier is an alias used by methods herebelow type replier func(data []byte) error // Start constructs and launches a new udp adapter -func Start(bindNet string, router core.RouterServer, ctx log.Interface) error { +func Start(c Components, o Options) (err error) { // Create the udp connection and start listening with a goroutine var udpConn *net.UDPConn - addr, err := net.ResolveUDPAddr("udp", bindNet) - if udpConn, err = net.ListenUDP("udp", addr); err != nil { - ctx.WithError(err).Error("Unable to start server") - return errors.New(errors.Operational, fmt.Sprintf("Invalid bind address %v", bindNet)) + if udpConn, err = tryConnect(o.NetAddr); err != nil { + c.Ctx.WithError(err).Error("Unable to start server") + return errors.New(errors.Operational, fmt.Sprintf("Invalid bind address %v", o.NetAddr)) } - ctx.WithField("bind", bindNet).Info("Starting Server") - - a := adapter{ctx: ctx} - go a.listen(udpConn, router) + c.Ctx.WithField("bind", o.NetAddr).Info("Starting Server") + a := adapter{Components: c} + go a.listen(o.NetAddr, udpConn, o.MaxReconnectionDelay) return nil } @@ -48,41 +62,63 @@ func makeReply(addr *net.UDPAddr, conn *net.UDPConn) replier { } } +// tryConnect attempt to connect to a udp connection +func tryConnect(netAddr string) (*net.UDPConn, error) { + addr, err := net.ResolveUDPAddr("udp", netAddr) + if err != nil { + return nil, err + } + return net.ListenUDP("udp", addr) +} + // listen Handle incoming packets and forward them. // // Runs in its own goroutine. -func (a adapter) listen(conn *net.UDPConn, router core.RouterServer) { - defer conn.Close() - a.ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") - +func (a adapter) listen(netAddr string, conn *net.UDPConn, maxReconnectionDelay time.Duration) { + var err error + a.Ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") for { + // Read on the UDP connection buf := make([]byte, 5000) - n, addr, err := conn.ReadFromUDP(buf) - if err != nil { // Problem with the connection - a.ctx.WithError(err).Error("Connection error") - continue + n, addr, fault := conn.ReadFromUDP(buf) + if fault != nil { // Problem with the connection + for delay := time.Millisecond * 25; delay < maxReconnectionDelay; delay *= 10 { + a.Ctx.Infof("UDP connection lost. Trying to reconnect in %s", delay) + <-time.After(delay) + conn, err = tryConnect(netAddr) + if err == nil { + a.Ctx.Info("UDP connection recovered") + break + } + } + a.Ctx.WithError(fault).Error("Unable to restore UDP connection") + break } - a.ctx.Debug("Incoming datagram") - go func(data []byte, reply replier, router core.RouterServer) { + // Handle the incoming datagram + a.Ctx.Debug("Incoming datagram") + go func(data []byte, reply replier) { pkt := new(semtech.Packet) if err := pkt.UnmarshalBinary(data); err != nil { - a.ctx.WithError(err).Debug("Unable to handle datagram") + a.Ctx.WithError(err).Debug("Unable to handle datagram") } switch pkt.Identifier { case semtech.PULL_DATA: err = a.handlePullData(*pkt, reply) case semtech.PUSH_DATA: - err = a.handlePushData(*pkt, reply, router) + err = a.handlePushData(*pkt, reply) default: err = errors.New(errors.Implementation, "Unhandled packet type") } - if err != nil { - a.ctx.WithError(err).Debug("Unable to handle datagram") + a.Ctx.WithError(err).Debug("Unable to handle datagram") } - }(buf[:n], makeReply(addr, conn), router) + }(buf[:n], makeReply(addr, conn)) + } + + if conn != nil { + _ = conn.Close() } } @@ -106,7 +142,7 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { } // Handle a PUSH_DATA packet coming from a gateway -func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.RouterServer) error { +func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { stats.MarkMeter("semtech_adapter.push_data") stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) @@ -123,7 +159,7 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.R // Process Stat payload if pkt.Payload.Stat != nil { - go router.HandleStats(context.Background(), &core.StatsReq{ + go a.Router.HandleStats(context.Background(), &core.StatsReq{ GatewayID: pkt.GatewayId, Metadata: extractMetadata(*pkt.Payload.Stat, new(core.StatsMetadata)).(*core.StatsMetadata), }) @@ -136,7 +172,7 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.R for _, rxpk := range pkt.Payload.RXPK { go func(rxpk semtech.RXPK) { defer wait.Done() - if err := a.handleDataUp(rxpk, pkt.GatewayId, reply, router); err != nil { + if err := a.handleDataUp(rxpk, pkt.GatewayId, reply); err != nil { cherr <- err } }(rxpk) @@ -148,12 +184,12 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier, router core.R return <-cherr } -func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier, router core.RouterServer) error { - dataRouterReq, err := a.newDataRouterReq(rxpk, gid) +func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier) error { + dataRouterReq, err := newDataRouterReq(rxpk, gid, a.Ctx) if err != nil { return errors.New(errors.Structural, err) } - resp, err := router.HandleData(context.Background(), dataRouterReq) + resp, err := a.Router.HandleData(context.Background(), dataRouterReq) if err != nil { errors.New(errors.Operational, err) } @@ -165,7 +201,7 @@ func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { return nil } - txpk, err := a.newTXPK(*resp) + txpk, err := newTXPK(*resp, a.Ctx) if err != nil { return errors.New(errors.Structural, err) } diff --git a/semtech/decode_test.go b/semtech/decode_test.go index 73a29d948..082ddafa4 100644 --- a/semtech/decode_test.go +++ b/semtech/decode_test.go @@ -50,10 +50,10 @@ func TestUnmarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Chan: pointer.Uint(14), + Chan: pointer.Uint32(14), Codr: pointer.String("4/7"), - Freq: pointer.Float64(873.14), - Rssi: pointer.Int(-42), + Freq: pointer.Float32(873.14), + Rssi: pointer.Int32(-42), }, }, }, @@ -131,10 +131,10 @@ func TestUnmarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Size: pointer.Uint(14), + Size: pointer.Uint32(14), }, RXPK{ - Chan: pointer.Uint(14), + Chan: pointer.Uint32(14), }, }, }, @@ -153,13 +153,13 @@ func TestUnmarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Size: pointer.Uint(14), + Size: pointer.Uint32(14), }, }, Stat: &Stat{ - Ackr: pointer.Float64(0.78), - Alti: pointer.Int(72), - Rxok: pointer.Uint(42), + Ackr: pointer.Float32(0.78), + Alti: pointer.Int32(72), + Rxok: pointer.Uint32(42), }, }, }, @@ -195,12 +195,12 @@ func TestUnmarshalBinary(t *testing.T) { RXPK: []RXPK{ RXPK{ Codr: pointer.String("4/7"), - Rssi: pointer.Int(-42), + Rssi: pointer.Int32(-42), }, }, TXPK: &TXPK{ Ipol: pointer.Bool(true), - Powe: pointer.Uint(12), + Powe: pointer.Uint32(12), }, }, }, @@ -242,7 +242,7 @@ func TestUnmarshalBinary(t *testing.T) { Payload: &Payload{ TXPK: &TXPK{ Ipol: pointer.Bool(true), - Powe: pointer.Uint(12), + Powe: pointer.Uint32(12), }, }, }, @@ -258,7 +258,7 @@ func TestUnmarshalBinary(t *testing.T) { Payload: &Payload{ TXPK: &TXPK{ Ipol: pointer.Bool(true), - Powe: pointer.Uint(12), + Powe: pointer.Uint32(12), Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), }, }, diff --git a/semtech/encode_test.go b/semtech/encode_test.go index 0ff79d7a7..454964651 100644 --- a/semtech/encode_test.go +++ b/semtech/encode_test.go @@ -29,10 +29,10 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Chan: pointer.Uint(14), + Chan: pointer.Uint32(14), Codr: pointer.String("4/7"), - Freq: pointer.Float64(873.14), - Rssi: pointer.Int(-42), + Freq: pointer.Float32(873.14), + Rssi: pointer.Int32(-42), }, }, }, @@ -49,10 +49,10 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Chan: pointer.Uint(14), + Chan: pointer.Uint32(14), Codr: pointer.String("4/7"), - Freq: pointer.Float64(873.14), - Rssi: pointer.Int(-42), + Freq: pointer.Float32(873.14), + Rssi: pointer.Int32(-42), }, }, }, @@ -81,10 +81,10 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Chan: pointer.Uint(14), + Chan: pointer.Uint32(14), Codr: pointer.String("4/7"), - Freq: pointer.Float64(873.14), - Rssi: pointer.Int(-42), + Freq: pointer.Float32(873.14), + Rssi: pointer.Int32(-42), }, }, }, @@ -162,10 +162,10 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Size: pointer.Uint(14), + Size: pointer.Uint32(14), }, RXPK{ - Chan: pointer.Uint(14), + Chan: pointer.Uint32(14), }, }, }, @@ -184,13 +184,13 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ RXPK{ - Size: pointer.Uint(14), + Size: pointer.Uint32(14), }, }, Stat: &Stat{ - Ackr: pointer.Float64(0.78), - Alti: pointer.Int(72), - Rxok: pointer.Uint(42), + Ackr: pointer.Float32(0.78), + Alti: pointer.Int32(72), + Rxok: pointer.Uint32(42), }, }, }, @@ -226,12 +226,12 @@ func TestMarshalBinary(t *testing.T) { RXPK: []RXPK{ RXPK{ Codr: pointer.String("4/7"), - Rssi: pointer.Int(-42), + Rssi: pointer.Int32(-42), }, }, TXPK: &TXPK{ Ipol: pointer.Bool(true), - Powe: pointer.Uint(12), + Powe: pointer.Uint32(12), }, }, }, @@ -294,7 +294,7 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ TXPK: &TXPK{ Ipol: pointer.Bool(true), - Powe: pointer.Uint(12), + Powe: pointer.Uint32(12), }, }, }, @@ -310,7 +310,7 @@ func TestMarshalBinary(t *testing.T) { Payload: &Payload{ TXPK: &TXPK{ Ipol: pointer.Bool(true), - Powe: pointer.Uint(12), + Powe: pointer.Uint32(12), Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), }, }, diff --git a/semtech/semtech.go b/semtech/semtech.go index 718dd4052..0ba15f377 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -17,53 +17,53 @@ type DeviceAddress [4]byte // RXPK represents an uplink json message format sent by the gateway type RXPK struct { - Chan *uint `json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *string `json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float64 `json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float64 `json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int `json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int `json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint `json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) + Chan *uint32 `full:"Channel" json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) + Codr *string `full:"CodingRate" json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `full:"Data" json:"data,omitempty"` // Base64 encoded RF packet payload, padded + Datr *string `full:"DataRate" json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier + Freq *float32 `full:"Frequency" json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) + Lsnr *float32 `full:"Lsnr" json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) + Modu *string `full:"Modulation" json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) + Rssi *int32 `full:"Rssi" json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) + Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Stat *int32 `full:"Status" json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Time *time.Time `full:"Time" json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format + Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } // TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional - Datr *string `json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint `json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float64 `json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint `json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint `json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint `json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint `json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint `json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `full:"CodingRate" json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `full:"Data" json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional + Datr *string `full:"DataRate" json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint32 `full:"FrequencyDev" json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float32 `full:"Frequency" json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `full:"Immediate" json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `full:"PolarizationInv" json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `full:"Modulation" json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `full:"NoCRC" json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint32 `full:"Power" json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint32 `full:"PreambleSize" json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `full:"Time" json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway type Stat struct { - Ackr *float64 `json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged - Alti *int `json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint `json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) - Lati *float64 `json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long *float64 `json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint `json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint `json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) - Rxok *uint `json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *time.Time `json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint `json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) + Ackr *float32 `full:"Acknowledgements" json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged + Alti *int32 `full:"Altitude" json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) + Dwnb *uint32 `full:"NbDownlink" json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) + Lati *float32 `full:"Latitude" json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) + Long *float32 `full:"Longitude" json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) + Rxfw *uint32 `full:"RXForwarded" json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) + Rxnb *uint32 `full:"RXReceived" json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) + Rxok *uint32 `full:"RXValid" json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC + Time *time.Time `full:"Time" json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format + Txnb *uint32 `full:"TXEmitted" json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) } // Packet as seen by the gateway. diff --git a/semtech/semtech_test.go b/semtech/semtech_test.go index e6f22d5bb..053039446 100644 --- a/semtech/semtech_test.go +++ b/semtech/semtech_test.go @@ -43,8 +43,8 @@ func TestString(t *testing.T) { GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, Payload: &Payload{ Stat: &Stat{ - Ackr: pointer.Float64(3.4), - Alti: pointer.Int(14), + Ackr: pointer.Float32(3.4), + Alti: pointer.Int32(14), }, }, } @@ -73,7 +73,7 @@ func TestString(t *testing.T) { GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, Payload: &Payload{ TXPK: &TXPK{ - Freq: pointer.Float64(883.445), + Freq: pointer.Float32(883.445), Codr: pointer.String("4/5"), }, }, @@ -104,11 +104,11 @@ func TestString(t *testing.T) { Payload: &Payload{ RXPK: []RXPK{ { - Freq: pointer.Float64(883.445), + Freq: pointer.Float32(883.445), Codr: pointer.String("4/5"), }, { - Freq: pointer.Float64(883.445), + Freq: pointer.Float32(883.445), Codr: pointer.String("4/5"), }, }, diff --git a/utils/pointer/pointer.go b/utils/pointer/pointer.go index 166a712d0..2817fd594 100644 --- a/utils/pointer/pointer.go +++ b/utils/pointer/pointer.go @@ -25,6 +25,34 @@ func Int(v int) *int { return p } +// Int8 creates a pointer to an int8 from an int8 value +func Int8(v int8) *int8 { + p := new(int8) + *p = v + return p +} + +// Int16 creates a pointer to an int16 from an int16 value +func Int16(v int16) *int16 { + p := new(int16) + *p = v + return p +} + +// Int32 creates a pointer to an int32 from an int32 value +func Int32(v int32) *int32 { + p := new(int32) + *p = v + return p +} + +// Int64 creates a pointer to an int64 from an int64 value +func Int64(v int64) *int64 { + p := new(int64) + *p = v + return p +} + // Uint creates a pointer to an unsigned int from an unsigned int value func Uint(v uint) *uint { p := new(uint) @@ -32,6 +60,41 @@ func Uint(v uint) *uint { return p } +// Uint8 creates a pointer to an unsigned int from an unsigned int8 value +func Uint8(v uint8) *uint8 { + p := new(uint8) + *p = v + return p +} + +// Uint16 creates a pointer to an unsigned int from an unsigned int16 value +func Uint16(v uint16) *uint16 { + p := new(uint16) + *p = v + return p +} + +// Uint32 creates a pointer to an unsigned int from an unsigned int32 value +func Uint32(v uint32) *uint32 { + p := new(uint32) + *p = v + return p +} + +// Uint64 creates a pointer to an unsigned int from an unsigned int64 value +func Uint64(v uint64) *uint64 { + p := new(uint64) + *p = v + return p +} + +// Float32 creates a pointer to a float32 from a float32 value +func Float32(v float32) *float32 { + p := new(float32) + *p = v + return p +} + // Float64 creates a pointer to a float64 from a float64 value func Float64(v float64) *float64 { p := new(float64) @@ -85,14 +148,50 @@ func DumpPStruct(s interface{}, multiline bool) string { if t != nil { str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } + case *int8: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int16: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int32: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *int64: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } case *uint: if t != nil { str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } + case *uint8: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint16: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint32: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } + case *uint64: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } case *string: if t != nil { str += fmt.Sprintf("%s:%+v%s", key, *t, nl) } + case *float32: + if t != nil { + str += fmt.Sprintf("%s:%+v%s", key, *t, nl) + } case *float64: if t != nil { str += fmt.Sprintf("%s:%+v%s", key, *t, nl) From 14e33e4f19be06097fbc45fec6b795f7348cd058 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 17:02:30 +0100 Subject: [PATCH 1065/2266] [refactor/grpc] Finalize conversion tests --- core/adapters/udp/conversions.go | 13 +- core/adapters/udp/conversions_test.go | 282 +++++++++++++++++++++++++- 2 files changed, 280 insertions(+), 15 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 3d5bf8466..e2f3ab331 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -22,15 +22,8 @@ func newDataRouterReq(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (*core.D return nil, errors.New(errors.Structural, "There's no data in the packet") } - // RXPK Data are base64 encoded, yet without the trailing "==" if any..... - encoded := *rxpk.Data - switch len(encoded) % 4 { - case 2: - encoded += "==" - case 3: - encoded += "=" - } - raw, err := base64.StdEncoding.DecodeString(encoded) + // RXPK Data are base64 encoded + raw, err := base64.RawStdEncoding.DecodeString(*rxpk.Data) if err != nil { return nil, errors.New(errors.Structural, err) } @@ -42,7 +35,7 @@ func newDataRouterReq(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (*core.D macpayload, ok := payload.MACPayload.(*lorawan.MACPayload) if !ok { // TODO OTAA join request payloads - return nil, errors.New(errors.Implementation, "Unhandled Physical payload. Expected a MACPayload") + return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a MACPayload") } if len(macpayload.FRMPayload) != 1 { // TODO Handle pure MAC Commands payloads (FType = 0) diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index 7a85b07c3..eea91a6ed 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -4,14 +4,15 @@ package udp import ( + "encoding/base64" + "testing" "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" - "testing" - // "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" ) func TestInjectMetadata(t *testing.T) { @@ -155,10 +156,281 @@ func TestExtractMetadta(t *testing.T) { } -func TestNewTXPK(t *testing.T) { +// func newTXPK(resp core.DataRouterRes, ctx log.Interface) (semtech.TXPK, error) { +func TestNewTXPKAndNewDataRouterReq(t *testing.T) { + { + Desc(t, "Test Valid marshal / unmarshal") -} + // Build + res := core.DataRouterRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: &core.LoRaWANFCtrl{ + ADR: false, + ADRAckReq: false, + Ack: false, + FPending: false, + FOptsLen: nil, + }, + FOpts: nil, + }, + FPort: 1, + FRMPayload: []byte{5, 6, 7, 8}, + }, + MIC: []byte{14, 42, 14, 42}, + }, + Metadata: &core.Metadata{ + CodingRate: "4/5", + DataRate: "SF8BW125", + }, + } + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + + // Expectations + var wantReq = core.DataRouterReq{ + Payload: res.Payload, + Metadata: res.Metadata, + GatewayID: gid, + } + var wantErrTXPK *string + var wantErrReq *string + + // Operate + txpk, errTXPK := newTXPK(res, GetLogger(t, "Convert TXPK")) + req, errReq := newDataRouterReq(semtech.RXPK{ + Codr: txpk.Codr, + Datr: txpk.Datr, + Data: txpk.Data, + }, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErrTXPK, errTXPK) + CheckErrors(t, wantErrReq, errReq) + Check(t, wantReq, *req, "Data Router Requests") + } + + // -------------------- + + { + Desc(t, "New TXPK with no Metadata") + + // Build + res := core.DataRouterRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: &core.LoRaWANFCtrl{ + ADR: false, + ADRAckReq: false, + Ack: false, + FPending: false, + FOptsLen: nil, + }, + FOpts: nil, + }, + FPort: 1, + FRMPayload: []byte{5, 6, 7, 8}, + }, + MIC: []byte{14, 42, 14, 42}, + }, + Metadata: nil, + } + + // Expectations + var wantTXPK semtech.TXPK + var wantErr = ErrStructural + + // Operate + txpk, errTXPK := newTXPK(res, GetLogger(t, "Convert TXPK")) + + // Check + CheckErrors(t, wantErr, errTXPK) + Check(t, wantTXPK, txpk, "TXPKs") + } + + // -------------------- + + { + Desc(t, "New TXPK with an invalid payload") + + // Build + res := core.DataRouterRes{ + Payload: new(core.LoRaWANData), + Metadata: &core.Metadata{ + CodingRate: "4/5", + DataRate: "SF8BW125", + }, + } + + // Expectations + var wantTXPK semtech.TXPK + var wantErr = ErrStructural + + // Operate + txpk, errTXPK := newTXPK(res, GetLogger(t, "Convert TXPK")) + + // Check + CheckErrors(t, wantErr, errTXPK) + Check(t, wantTXPK, txpk, "TXPKs") + } + + // -------------------- + + { + Desc(t, "Test newDataRouter with invalid macpayload") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.UnconfirmedDataUp + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + payload.MACPayload = pointer.Time(time.Now()) // Something marshable + data, err := payload.MarshalBinary() + FatalUnless(t, err) + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + } + + // Expectations + var wantReq *core.DataRouterReq + var wantErr = ErrStructural + + // Operate + req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantReq, req, "Data Router Requests") + } + + // -------------------- + + { + Desc(t, "Test newDataRouter with no data in rxpk") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + } + + // Expectations + var wantReq *core.DataRouterReq + var wantErr = ErrStructural + + // Operate + req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantReq, req, "Data Router Requests") + } + + // -------------------- + + { + Desc(t, "Test newDataRouter with random encoded data in rxpk") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + Data: pointer.String("$#%$^ffg"), + } + + // Expectations + var wantReq *core.DataRouterReq + var wantErr = ErrStructural + + // Operate + req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantReq, req, "Data Router Requests") + } -func TestNewDataRouterReq(t *testing.T) { + // -------------------- + + { + Desc(t, "Test newDataRouter with not base64 data") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + Data: pointer.String("Patate"), + } + // Expectations + var wantReq *core.DataRouterReq + var wantErr = ErrStructural + + // Operate + req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantReq, req, "Data Router Requests") + } + + // -------------------- + + { + Desc(t, "Test newDataRouter with mac commands in fopts") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + payload := lorawan.NewPHYPayload(false) + payload.MHDR.MType = lorawan.UnconfirmedDataDown + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + macpayload := lorawan.NewMACPayload(false) + macpayload.FPort = 1 + macpayload.FHDR.FOpts = []lorawan.MACCommand{ + lorawan.MACCommand{ + CID: lorawan.DutyCycleReq, + Payload: &lorawan.DutyCycleReqPayload{ + MaxDCCycle: 14, + }, + }, + } + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} + payload.MACPayload = macpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + } + + // Expectations + var wantErr *string + + // Operate + _, err = newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + } } From eff3c6566a32e04d59313cef793562a6cbf4fbbc Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 19:12:01 +0100 Subject: [PATCH 1066/2266] [refactor/grpc] Complete test suite for udp/semtech adapter --- core/adapters/udp/udp.go | 16 +- core/adapters/udp/udp_test.go | 888 ++++++++++++++++++++++++++++++++++ core/mocks/mocks.go | 46 ++ 3 files changed, 949 insertions(+), 1 deletion(-) create mode 100644 core/adapters/udp/udp_test.go diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 0246ac000..731ef2270 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -127,6 +127,7 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { stats.MarkMeter("semtech_adapter.pull_data") stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.pull_data", pkt.GatewayId)) stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_pull_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) + a.Ctx.Debug("Handle PULL_DATA") data, err := semtech.Packet{ Version: semtech.VERSION, @@ -146,6 +147,8 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { stats.MarkMeter("semtech_adapter.push_data") stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) + ctx := a.Ctx.WithField("GatewayID", pkt.GatewayId) + ctx.Debug("Handle PUSH_DATA") // AckNowledge with a PUSH_ACK data, err := semtech.Packet{ @@ -154,11 +157,13 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { Identifier: semtech.PUSH_ACK, }.MarshalBinary() if err != nil || reply(data) != nil || pkt.Payload == nil { - return errors.New(errors.Operational, "Unable to process PUSH_DATA packet") + ctx.Debug("Unable to send ACK") + return errors.New(errors.Operational, "Unable to send ACK") } // Process Stat payload if pkt.Payload.Stat != nil { + ctx.Debug("PUSH_DATA contains a stats payload") go a.Router.HandleStats(context.Background(), &core.StatsReq{ GatewayID: pkt.GatewayId, Metadata: extractMetadata(*pkt.Payload.Stat, new(core.StatsMetadata)).(*core.StatsMetadata), @@ -169,10 +174,12 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { cherr := make(chan error, len(pkt.Payload.RXPK)) wait := sync.WaitGroup{} wait.Add(len(pkt.Payload.RXPK)) + ctx.WithField("Nb RXPK", len(pkt.Payload.RXPK)).Debug("Processing RXPK payloads") for _, rxpk := range pkt.Payload.RXPK { go func(rxpk semtech.RXPK) { defer wait.Done() if err := a.handleDataUp(rxpk, pkt.GatewayId, reply); err != nil { + ctx.WithError(err).Debug("Error while processing RXPK") cherr <- err } }(rxpk) @@ -187,31 +194,38 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier) error { dataRouterReq, err := newDataRouterReq(rxpk, gid, a.Ctx) if err != nil { + a.Ctx.WithError(err).Debug("Invalid up RXPK packet") return errors.New(errors.Structural, err) } resp, err := a.Router.HandleData(context.Background(), dataRouterReq) if err != nil { + a.Ctx.WithError(err).Debug("Router failed to process uplink") errors.New(errors.Operational, err) } return a.handleDataDown(resp, reply) } func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { + a.Ctx.Debug("Handle Downlink from router") if resp == nil { // No response + a.Ctx.Debug("No response to send") return nil } txpk, err := newTXPK(*resp, a.Ctx) if err != nil { + a.Ctx.WithError(err).Debug("Unable to interpret downlink") return errors.New(errors.Structural, err) } + a.Ctx.Debug("Creating new downlink response") data, err := semtech.Packet{ Version: semtech.VERSION, Identifier: semtech.PULL_RESP, Payload: &semtech.Payload{TXPK: &txpk}, }.MarshalBinary() if err != nil { + a.Ctx.WithError(err).Debug("Unable to create semtech packet with TXPK") return errors.New(errors.Structural, err) } return reply(data) diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go new file mode 100644 index 000000000..de745ba8d --- /dev/null +++ b/core/adapters/udp/udp_test.go @@ -0,0 +1,888 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "encoding/base64" + "fmt" + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" +) + +func listenPackets(conn *net.UDPConn) chan semtech.Packet { + chresp := make(chan semtech.Packet, 2) + go func() { + for { + buf := make([]byte, 5000) + n, err := conn.Read(buf) + if err == nil { + pkt := new(semtech.Packet) + if err := pkt.UnmarshalBinary(buf[:n]); err == nil { + chresp <- *pkt + } + } + } + }() + return chresp +} + +func TestUDPAdapter(t *testing.T) { + port := 10000 + newAddr := func() string { + port++ + return fmt.Sprintf("0.0.0.0:%d", port) + } + + { + Desc(t, "Send a valid packet through udp, no downlink") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.UnconfirmedDataUp + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + macpayload := lorawan.NewMACPayload(true) + macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} + payload.MACPayload = macpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq = &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + FOptsLen: nil, + }, + FOpts: nil, + }, + FPort: uint32(macpayload.FPort), + FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, + }, + MIC: payload.MIC[:], + }, + Metadata: new(core.Metadata), + GatewayID: packet.GatewayId, + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid packet through udp, with valid downlink") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.UnconfirmedDataUp + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + macpayload := lorawan.NewMACPayload(true) + macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} + payload.MACPayload = macpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + router.OutHandleData.Res = &core.DataRouterRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 2, + FCtrl: new(core.LoRaWANFCtrl), + FOpts: nil, + }, + FPort: 2, + FRMPayload: []byte{1, 2, 3, 4}, + }, + MIC: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + + payloadDown := lorawan.NewPHYPayload(false) + payloadDown.MHDR.MType = lorawan.MType(router.OutHandleData.Res.Payload.MHDR.MType) + payloadDown.MHDR.Major = lorawan.Major(router.OutHandleData.Res.Payload.MHDR.Major) + copy(payloadDown.MIC[:], router.OutHandleData.Res.Payload.MIC) + macpayloadDown := lorawan.NewMACPayload(false) + macpayloadDown.FPort = uint8(router.OutHandleData.Res.Payload.MACPayload.FPort) + macpayloadDown.FHDR.FCnt = router.OutHandleData.Res.Payload.MACPayload.FHDR.FCnt + copy(macpayloadDown.FHDR.DevAddr[:], router.OutHandleData.Res.Payload.MACPayload.FHDR.DevAddr) + macpayloadDown.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ + Bytes: router.OutHandleData.Res.Payload.MACPayload.FRMPayload, + }} + payloadDown.MACPayload = macpayloadDown + dataDown, err := payloadDown.MarshalBinary() + FatalUnless(t, err) + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq = &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + FOptsLen: nil, + }, + FOpts: nil, + }, + FPort: uint32(macpayload.FPort), + FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, + }, + MIC: payload.MIC[:], + }, + Metadata: new(core.Metadata), + GatewayID: packet.GatewayId, + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + { + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{ + TXPK: &semtech.TXPK{ + Data: pointer.String(base64.RawStdEncoding.EncodeToString(dataDown)), + }, + }, + }, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid packet through udp, with invalid downlink") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.UnconfirmedDataUp + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + macpayload := lorawan.NewMACPayload(true) + macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} + payload.MACPayload = macpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + router.OutHandleData.Res = &core.DataRouterRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: nil, + MIC: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq = &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + FOptsLen: nil, + }, + FOpts: nil, + }, + FPort: uint32(macpayload.FPort), + FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, + }, + MIC: payload.MIC[:], + }, + Metadata: new(core.Metadata), + GatewayID: packet.GatewayId, + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a packet through udp, no payload") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: nil, + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a packet through udp, empty payload") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: new(semtech.Payload), + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid packet through udp, no downlink, router fails") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.UnconfirmedDataUp + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + macpayload := lorawan.NewMACPayload(true) + macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} + payload.MACPayload = macpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + router.Failures["HandleData"] = fmt.Errorf("Mock Error") + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq = &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + FOptsLen: nil, + }, + FOpts: nil, + }, + FPort: uint32(macpayload.FPort), + FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, + }, + MIC: payload.MIC[:], + }, + Metadata: new(core.Metadata), + GatewayID: packet.GatewayId, + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid packet through udp with stats, no downlink") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + Stat: &semtech.Stat{ + Alti: pointer.Int32(14), + Long: pointer.Float32(44.7654), + Lati: pointer.Float32(23.56), + }, + }, + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + router.Failures["HandleData"] = fmt.Errorf("Mock Error") + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats = &core.StatsReq{ + GatewayID: packet.GatewayId, + Metadata: &core.StatsMetadata{ + Altitude: *packet.Payload.Stat.Alti, + Longitude: *packet.Payload.Stat.Long, + Latitude: *packet.Payload.Stat.Lati, + }, + } + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a packet through udp, no data in rxpk") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Codr: pointer.String("4/6"), + Datr: pointer.String("SF8BW125"), + }, + }, + }, + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // ------------------- + + { + Desc(t, "Send a PULL_DATA through udp") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PULL_DATA, + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PULL_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // ------------------- + + { + Desc(t, "Invalid options NetAddr") + + // Build + router := mocks.NewRouterServer() + + // Expectations + var wantErrStart = ErrOperational + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + + // Operate + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: "patate", MaxReconnectionDelay: 25 * time.Millisecond}, + ) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + } + + // ------------------- + + { + Desc(t, "Send an invalid semtech as uplink") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + Token: []byte{1, 2}, + Identifier: semtech.PULL_ACK, + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + {}, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } +} diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 8db5e37bd..3c58237d1 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -80,3 +80,49 @@ func (m *HandlerClient) SubscribePersonalized(ctx context.Context, in *core.ABPS } return m.OutSubscribePersonalized.Res, nil } + +// RouterServer mocks the core.RouterServer interface +type RouterServer struct { + Failures map[string]error + InHandleData struct { + Ctx context.Context + Req *core.DataRouterReq + } + OutHandleData struct { + Res *core.DataRouterRes + } + InHandleStats struct { + Ctx context.Context + Req *core.StatsReq + } + OutHandleStats struct { + Res *core.StatsRes + } +} + +// NewRouterServer creates a new mock RouterServer +func NewRouterServer() *RouterServer { + return &RouterServer{ + Failures: make(map[string]error), + } +} + +// HandleData implements the core.RouterServer interface +func (m *RouterServer) HandleData(ctx context.Context, in *core.DataRouterReq) (*core.DataRouterRes, error) { + m.InHandleData.Ctx = ctx + m.InHandleData.Req = in + if err := m.Failures["HandleData"]; err != nil { + return nil, err + } + return m.OutHandleData.Res, nil +} + +// HandleStats implements the core.RouterServer interface +func (m *RouterServer) HandleStats(ctx context.Context, in *core.StatsReq) (*core.StatsRes, error) { + m.InHandleStats.Ctx = ctx + m.InHandleStats.Req = in + if err := m.Failures["HandleStats"]; err != nil { + return nil, err + } + return m.OutHandleStats.Res, nil +} From 023f9aa33e33b53b7374d38061ecd0d39bdfae45 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 20:23:09 +0100 Subject: [PATCH 1067/2266] [refactor/grpc] Re-activate router storage tests --- core/components/router/storage.go | 12 +- core/components/router/storage_test.go | 264 +++++++++++++++++++++++++ 2 files changed, 269 insertions(+), 7 deletions(-) create mode 100644 core/components/router/storage_test.go diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 1384fd9ea..54eda0986 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -133,17 +133,15 @@ func (e entry) MarshalBinary() ([]byte, error) { func (e *entry) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) - // e.until - tdata := new([]byte) - binary.Read(buf, binary.BigEndian, tdata) - if err := e.until.UnmarshalBinary(*tdata); err != nil { - return errors.New(errors.Structural, err) - } - // e.Broker index := new(uint16) binary.Read(buf, binary.BigEndian, index) e.BrokerIndex = int(*index) + // e.until + if err := e.until.UnmarshalBinary(buf.Next(buf.Len())); err != nil { + return errors.New(errors.Structural, err) + } + return nil } diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go new file mode 100644 index 000000000..cfe6c5211 --- /dev/null +++ b/core/components/router/storage_test.go @@ -0,0 +1,264 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "os" + "path" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const storageDB = "TestRouterStorage.db" + +func CheckEntries(t *testing.T, want []entry, got []entry) { + for i, w := range want { + if i >= len(got) { + Ko(t, "Didn't got enough entries: %v", got) + } + tmin := w.until.Add(-time.Second) + tmax := w.until.Add(time.Second) + if !tmin.Before(got[i].until) || !got[i].until.Before(tmax) { + Ko(t, "Unexpected expiry time.\nWant: %s\nGot: %s", w.until, got[i].until) + } + Check(t, w.BrokerIndex, got[i].BrokerIndex, "Brokers") + } +} + +func TestStoreAndLookup(t *testing.T) { + storageDB := path.Join(os.TempDir(), storageDB) + + defer func() { + os.Remove(storageDB) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + db, err := NewStorage(storageDB, time.Hour) + CheckErrors(t, nil, err) + err = db.Close() + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Store then lookup a device") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + devAddr := []byte{0, 0, 0, 1} + + // Operate + err := db.Store(devAddr, 1) + FatalUnless(t, err) + gotEntry, err := db.Lookup(devAddr) + + // Expectations + wantEntry := []entry{ + { + BrokerIndex: 1, + until: time.Now().Add(time.Hour), + }, + } + + // Check + CheckErrors(t, nil, err) + CheckEntries(t, wantEntry, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Lookup non-existing entry") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + devAddr := []byte{0, 0, 0, 2} + + // Operate + gotEntry, err := db.Lookup(devAddr) + + // Checks + CheckErrors(t, ErrNotFound, err) + CheckEntries(t, nil, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Lookup an expired entry") + + // Build + db, _ := NewStorage(storageDB, time.Millisecond*100) + devAddr := []byte{0, 0, 0, 3} + + // Operate + _ = db.Store(devAddr, 1) + <-time.After(time.Millisecond * 200) + gotEntry, err := db.Lookup(devAddr) + + // Checks + CheckErrors(t, ErrNotFound, err) + CheckEntries(t, nil, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Store above an expired entry") + + // Build + db, _ := NewStorage(storageDB, time.Millisecond*100) + devAddr := []byte{0, 0, 0, 4} + + // Operate + _ = db.Store(devAddr, 1) + <-time.After(time.Millisecond * 200) + err := db.Store(devAddr, 2) + FatalUnless(t, err) + gotEntry, err := db.Lookup(devAddr) + + // Expectations + wantEntry := []entry{ + { + BrokerIndex: 2, + until: time.Now().Add(time.Millisecond * 100), + }, + } + + // Checks + CheckErrors(t, nil, err) + CheckEntries(t, wantEntry, gotEntry) + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Store on a closed database") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + _ = db.Close() + devAddr := []byte{0, 0, 0, 5} + + // Operate + err := db.Store(devAddr, 1) + + // Checks + CheckErrors(t, ErrOperational, err) + } + + // ------------------ + + { + Desc(t, "Lookup on a closed database") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + _ = db.Close() + devAddr := []byte{0, 0, 0, 1} + + // Operate + gotEntry, err := db.Lookup(devAddr) + + // Checks + CheckErrors(t, ErrOperational, err) + CheckEntries(t, nil, gotEntry) + } + + // ------------------ + + { + Desc(t, "Store two entries in a row") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + devAddr := []byte{0, 0, 0, 6} + + // Operate + err := db.Store(devAddr, 1) + FatalUnless(t, err) + err = db.Store(devAddr, 2) + FatalUnless(t, err) + gotEntries, err := db.Lookup(devAddr) + FatalUnless(t, err) + + // Expectations + wantEntries := []entry{ + { + BrokerIndex: 1, + until: time.Now().Add(time.Hour), + }, + { + BrokerIndex: 2, + until: time.Now().Add(time.Hour), + }, + } + + // Check + CheckEntries(t, wantEntries, gotEntries) + _ = db.Close() + } +} + +func TestUpdateAndLookup(t *testing.T) { + storageDB := path.Join(os.TempDir(), storageDB) + + defer func() { + os.Remove(storageDB) + }() + + // ------------------ + + { + Desc(t, "Store then lookup stats") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + stats := core.StatsMetadata{ + Altitude: 35, + Longitude: -3.4546, + Latitude: 35.212, + } + gid := []byte{0, 0, 0, 0, 0, 0, 0, 1} + + // Operate + errUpdate := db.UpdateStats(gid, stats) + got, errLookup := db.LookupStats(gid) + + // Check + CheckErrors(t, nil, errUpdate) + CheckErrors(t, nil, errLookup) + Check(t, stats, got, "Metadata") + _ = db.Close() + } + + // ------------------ + + { + Desc(t, "Lookup stats from unknown gateway") + + // Build + db, _ := NewStorage(storageDB, time.Hour) + gid := []byte{0, 0, 0, 0, 0, 0, 0, 2} + + // Operate + got, errLookup := db.LookupStats(gid) + + // Check + CheckErrors(t, ErrNotFound, errLookup) + Check(t, core.StatsMetadata{}, got, "Metadata") + _ = db.Close() + } +} From a191550a3c2680548a9bc30eaa37cadd7d5d99ad Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 20:54:40 +0100 Subject: [PATCH 1068/2266] [refactor/grpc] Update dutymanager to match new 32-bytes types --- core/dutycycle/dutyManager.go | 22 +++++----- core/dutycycle/dutyManager_test.go | 65 ++++++++++++++-------------- core/dutycycle/helpers_test.go | 2 +- core/dutycycle/scoreComputer_test.go | 23 +++++----- 4 files changed, 55 insertions(+), 57 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index e8f811ceb..1bc55ca6d 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -18,19 +18,19 @@ import ( // DutyManager provides an interface to manipulate and compute gateways duty-cycles. type DutyManager interface { - Update(id []byte, freq float64, size uint, datr string, codr string) error + Update(id []byte, freq float32, size uint32, datr string, codr string) error Lookup(id []byte) (Cycles, error) Close() error } -type Cycles map[subBand]uint +type Cycles map[subBand]uint32 type dutyManager struct { sync.RWMutex db dbutil.Interface bucket string CycleLength time.Duration // Duration upon which the duty-cycle is evaluated - MaxDutyCycle map[subBand]float64 // The percentage max duty cycle accepted for each sub-band + MaxDutyCycle map[subBand]float32 // The percentage max duty cycle accepted for each sub-band } // Available sub-bands @@ -63,7 +63,7 @@ const ( type region byte // GetSubBand returns the subband associated to a given frequency -func GetSubBand(freq float64) (subBand, error) { +func GetSubBand(freq float32) (subBand, error) { // g 865.0 – 868.0 MHz 1% or LBT+AFA, 25 mW (=14dBm) if freq >= 865.0 && freq < 868.0 { return EuropeG, nil @@ -94,10 +94,10 @@ func GetSubBand(freq float64) (subBand, error) { // NewManager constructs a new gateway manager from func NewManager(filepath string, cycleLength time.Duration, r region) (DutyManager, error) { - var maxDuty map[subBand]float64 + var maxDuty map[subBand]float32 switch r { case Europe: - maxDuty = map[subBand]float64{ + maxDuty = map[subBand]float32{ EuropeG: 0.01, EuropeG1: 0.01, EuropeG2: 0.001, @@ -131,7 +131,7 @@ func NewManager(filepath string, cycleLength time.Duration, r region) (DutyManag // Datr represents a LoRaWAN data-rate indicator of the form SFxxBWyyy, // where xx C [[7;12]] and yyy C { 125, 250, 500 } // Codr represents a LoRaWAN code rate indicator fo the form 4/x with x C [[5;8]] -func (m *dutyManager) Update(id []byte, freq float64, size uint, datr string, codr string) error { +func (m *dutyManager) Update(id []byte, freq float32, size uint32, datr string, codr string) error { sub, err := GetSubBand(freq) if err != nil { return err @@ -187,13 +187,13 @@ func (m *dutyManager) Lookup(id []byte) (Cycles, error) { entry := itf.([]dutyEntry)[0] // For each sub-band, compute the remaining time-on-air available - cycles := make(map[subBand]uint) + cycles := make(map[subBand]uint32) if entry.Until.After(time.Now()) { for s, toa := range entry.OnAir { // The actual duty cycle - dutyCycle := float64(toa.Nanoseconds()) / float64(m.CycleLength.Nanoseconds()) + dutyCycle := float32(toa.Nanoseconds()) / float32(m.CycleLength.Nanoseconds()) // Now, how full are we comparing to the limitation, in percent - cycles[s] = uint(100 * dutyCycle / m.MaxDutyCycle[s]) + cycles[s] = uint32(100 * dutyCycle / m.MaxDutyCycle[s]) } } @@ -207,7 +207,7 @@ func (m *dutyManager) Close() error { // computeTOA computes the time-on-air given a size in byte, a LoRaWAN datr identifier, an LoRa Codr // identifier. -func computeTOA(size uint, datr string, codr string) (time.Duration, error) { +func computeTOA(size uint32, datr string, codr string) (time.Duration, error) { // Ensure the datr and codr are correct var rc float64 switch codr { diff --git a/core/dutycycle/dutyManager_test.go b/core/dutycycle/dutyManager_test.go index d8b661d4d..36da0140c 100644 --- a/core/dutycycle/dutyManager_test.go +++ b/core/dutycycle/dutyManager_test.go @@ -10,7 +10,6 @@ import ( "time" "github.com/TheThingsNetwork/ttn/utils/errors" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -21,7 +20,7 @@ func TestGetSubBand(t *testing.T) { { Desc(t, "Test EuropeG") sb, err := GetSubBand(867.127) - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckSubBands(t, EuropeG, sb) } @@ -30,7 +29,7 @@ func TestGetSubBand(t *testing.T) { { Desc(t, "Test EuropeG1") sb, err := GetSubBand(868.38) - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckSubBands(t, EuropeG1, sb) } @@ -39,7 +38,7 @@ func TestGetSubBand(t *testing.T) { { Desc(t, "Test EuropeG2") sb, err := GetSubBand(868.865) - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckSubBands(t, EuropeG2, sb) } @@ -48,7 +47,7 @@ func TestGetSubBand(t *testing.T) { { Desc(t, "Test EuropeG3") sb, err := GetSubBand(869.567) - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckSubBands(t, EuropeG3, sb) } @@ -57,7 +56,7 @@ func TestGetSubBand(t *testing.T) { { Desc(t, "Test EuropeG4") sb, err := GetSubBand(869.856) - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckSubBands(t, EuropeG4, sb) } @@ -66,7 +65,7 @@ func TestGetSubBand(t *testing.T) { { Desc(t, "Test Unknown") sb, err := GetSubBand(433.5) - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) CheckSubBands(t, "", sb) } } @@ -79,9 +78,9 @@ func TestNewManager(t *testing.T) { { Desc(t, "Europe with valid cycleLength") m, err := NewManager(dutyManagerDB, time.Minute, Europe) - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) err = m.Close() - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) } // -------------------- @@ -89,7 +88,7 @@ func TestNewManager(t *testing.T) { { Desc(t, "Europe with invalid cycleLength") _, err := NewManager(dutyManagerDB, 0, Europe) - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) } // -------------------- @@ -97,7 +96,7 @@ func TestNewManager(t *testing.T) { { Desc(t, "Not europe with valid cycleLength") _, err := NewManager(dutyManagerDB, time.Minute, China) - errutil.CheckErrors(t, pointer.String(string(errors.Implementation)), err) + CheckErrors(t, pointer.String(string(errors.Implementation)), err) } } @@ -116,7 +115,7 @@ func TestUpdateAndLookup(t *testing.T) { err := m.Update([]byte{1, 2, 3}, 433.65, 100, "SF8BW125", "4/5") // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) // Clean m.Close() @@ -134,7 +133,7 @@ func TestUpdateAndLookup(t *testing.T) { err := m.Update([]byte{1, 2, 3}, 868.1, 100, "SF3BW125", "4/5") // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) // Clean m.Close() @@ -152,7 +151,7 @@ func TestUpdateAndLookup(t *testing.T) { err := m.Update([]byte{1, 2, 3}, 869.5, 100, "SF8BW125", "14") // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) // Clean m.Close() @@ -168,16 +167,16 @@ func TestUpdateAndLookup(t *testing.T) { // Operate err := m.Update([]byte{1, 2, 3}, 868.5, 14, "SF8BW125", "4/5") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) bands, err := m.Lookup([]byte{1, 2, 3}) // Expectation - want := map[subBand]uint{ + want := map[subBand]uint32{ EuropeG1: 10, } // Check - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckUsages(t, want, bands) // Clean @@ -194,21 +193,21 @@ func TestUpdateAndLookup(t *testing.T) { // Operate err := m.Update([]byte{4, 5, 6}, 868.523, 14, "SF8BW125", "4/5") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) err = m.Update([]byte{4, 5, 6}, 868.123, 42, "SF9BW125", "4/5") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) err = m.Update([]byte{4, 5, 6}, 867.785, 42, "SF8BW125", "4/6") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) bands, err := m.Lookup([]byte{4, 5, 6}) // Expectation - want := map[subBand]uint{ + want := map[subBand]uint32{ EuropeG1: 51, EuropeG: 25, } // Check - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckUsages(t, want, bands) // Clean @@ -225,19 +224,19 @@ func TestUpdateAndLookup(t *testing.T) { // Operate err := m.Update([]byte{16, 2, 3}, 868.523, 14, "SF8BW125", "4/7") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) <-time.After(300 * time.Millisecond) err = m.Update([]byte{16, 2, 3}, 868.123, 42, "SF9BW125", "4/5") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) bands, err := m.Lookup([]byte{16, 2, 3}) // Expectation - want := map[subBand]uint{ + want := map[subBand]uint32{ EuropeG1: 9871, } // Check - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckUsages(t, want, bands) // Clean @@ -254,15 +253,15 @@ func TestUpdateAndLookup(t *testing.T) { // Operate err := m.Update([]byte{1, 2, 35}, 868.523, 14, "SF8BW125", "4/8") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) <-time.After(300 * time.Millisecond) bands, err := m.Lookup([]byte{1, 2, 35}) // Expectation - want := map[subBand]uint{} + want := map[subBand]uint32{} // Check - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckUsages(t, want, bands) // Clean @@ -279,18 +278,18 @@ func TestUpdateAndLookup(t *testing.T) { // Operate err := m.Update([]byte{4, 12, 6}, 868.523, 14, "SF11BW125", "4/5") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) err = m.Update([]byte{4, 12, 6}, 868.523, 42, "SF12BW125", "4/5") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) bands, err := m.Lookup([]byte{4, 12, 6}) // Expectation - want := map[subBand]uint{ + want := map[subBand]uint32{ EuropeG1: 384, } // Check - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) CheckUsages(t, want, bands) // Clean diff --git a/core/dutycycle/helpers_test.go b/core/dutycycle/helpers_test.go index fb69be7aa..8af6f2ab8 100644 --- a/core/dutycycle/helpers_test.go +++ b/core/dutycycle/helpers_test.go @@ -14,7 +14,7 @@ func CheckSubBands(t *testing.T, want subBand, got subBand) { testutil.Check(t, want, got, "SubBands") } -func CheckUsages(t *testing.T, want map[subBand]uint, got map[subBand]uint) { +func CheckUsages(t *testing.T, want map[subBand]uint32, got map[subBand]uint32) { testutil.Check(t, want, got, "Usages") } diff --git a/core/dutycycle/scoreComputer_test.go b/core/dutycycle/scoreComputer_test.go index 048a34889..31bb74077 100644 --- a/core/dutycycle/scoreComputer_test.go +++ b/core/dutycycle/scoreComputer_test.go @@ -8,7 +8,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) @@ -17,7 +16,7 @@ func TestNewScoreComputer(t *testing.T) { { Desc(t, "Invalid datr as argument") _, _, err := NewScoreComputer("TheThingsNetwork") - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) + CheckErrors(t, pointer.String(string(errors.Structural)), err) } // -------------------- @@ -25,7 +24,7 @@ func TestNewScoreComputer(t *testing.T) { { Desc(t, "Valid datr") _, _, err := NewScoreComputer("SF8BW250") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) } } @@ -47,7 +46,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF7BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate got := c.Get(s) @@ -63,7 +62,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF7BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -85,7 +84,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF7BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -107,7 +106,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF7BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -129,7 +128,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF9BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -151,7 +150,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF10BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -179,7 +178,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF10BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -201,7 +200,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF8BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ @@ -229,7 +228,7 @@ func TestUpdateGet(t *testing.T) { // Build c, s, err := NewScoreComputer("SF12BW125") - errutil.CheckErrors(t, nil, err) + CheckErrors(t, nil, err) // Operate s = c.Update(s, 1, core.Metadata{ From 18653c612cf5ee27ed3b98f30cde45c00cfc1a6c Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 20:55:01 +0100 Subject: [PATCH 1069/2266] [refactor/grpc] Add mock interfaces for BrokerClient & DutyManager --- core/mocks/mocks.go | 101 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 3c58237d1..f62135fb2 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -6,6 +6,7 @@ package mocks import ( "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/dutycycle" "golang.org/x/net/context" "google.golang.org/grpc" ) @@ -81,6 +82,56 @@ func (m *HandlerClient) SubscribePersonalized(ctx context.Context, in *core.ABPS return m.OutSubscribePersonalized.Res, nil } +// BrokerClient mocks the core.BrokerClient interface +type BrokerClient struct { + Failures map[string]error + InHandleData struct { + Ctx context.Context + Req *core.DataBrokerReq + Opts []grpc.CallOption + } + OutHandleData struct { + Res *core.DataBrokerRes + } + InSubscribePersonalized struct { + Ctx context.Context + Req *core.ABPSubBrokerReq + Opts []grpc.CallOption + } + OutSubscribePersonalized struct { + Res *core.ABPSubBrokerRes + } +} + +// NewBrokerClient creates a new mock BrokerClient +func NewBrokerClient() *BrokerClient { + return &BrokerClient{ + Failures: make(map[string]error), + } +} + +// HandleData implements the core.BrokerClient interface +func (m *BrokerClient) HandleData(ctx context.Context, in *core.DataBrokerReq, opts ...grpc.CallOption) (*core.DataBrokerRes, error) { + m.InHandleData.Ctx = ctx + m.InHandleData.Req = in + m.InHandleData.Opts = opts + if err := m.Failures["HandleData"]; err != nil { + return nil, err + } + return m.OutHandleData.Res, nil +} + +// SubscribePersonalized implements the core.BrokerClient interface +func (m *BrokerClient) SubscribePersonalized(ctx context.Context, in *core.ABPSubBrokerReq, opts ...grpc.CallOption) (*core.ABPSubBrokerRes, error) { + m.InSubscribePersonalized.Ctx = ctx + m.InSubscribePersonalized.Req = in + m.InSubscribePersonalized.Opts = opts + if err := m.Failures["SubscribePersonalized"]; err != nil { + return nil, err + } + return m.OutSubscribePersonalized.Res, nil +} + // RouterServer mocks the core.RouterServer interface type RouterServer struct { Failures map[string]error @@ -126,3 +177,53 @@ func (m *RouterServer) HandleStats(ctx context.Context, in *core.StatsReq) (*cor } return m.OutHandleStats.Res, nil } + +// DutyManager mocks the dutycycle.DutyManager interface +type DutyManager struct { + Failures map[string]error + InUpdate struct { + ID []byte + Freq float32 + Size uint32 + Datr string + Codr string + } + InLookup struct { + ID []byte + } + OutLookup struct { + Cycles dutycycle.Cycles + } + InClose struct { + Called bool + } +} + +// NewDutyManager creates a new mock DutyManager +func NewDutyManager() *DutyManager { + return &DutyManager{ + Failures: make(map[string]error), + } +} + +// Update implements the dutycycle.DutyManager interface +func (m *DutyManager) Update(id []byte, freq float32, size uint32, datr string, codr string) error { + m.InUpdate.ID = id + m.InUpdate.Freq = freq + m.InUpdate.Size = size + m.InUpdate.Datr = datr + m.InUpdate.Codr = codr + return m.Failures["Update"] +} + +// Lookup implements the dutycycle.DutyManager interface +func (m *DutyManager) Lookup(id []byte) (dutycycle.Cycles, error) { + m.InLookup.ID = id + return m.OutLookup.Cycles, m.Failures["Lookup"] +} + +// Close implements the dutycycle.DutyManager interface +func (m *DutyManager) Close() error { + m.InClose.Called = true + return m.Failures["Close"] +} From 9ee4365a03683d81b816dd1f28c2081ab116bad3 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 20:55:33 +0100 Subject: [PATCH 1070/2266] [refactor/grpc] Make router uniform with adapter notation (components & options) --- core/components/router/router.go | 49 +++++++++++++++++++------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 9c826be1f..403caeab0 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -15,16 +15,25 @@ import ( "golang.org/x/net/context" ) +// Components defines a structure to make the instantiation easier to read +type Components struct { + DutyManager dutycycle.DutyManager + Brokers []core.BrokerClient + Ctx log.Interface + Storage Storage +} + +// Options defines a structure to make the instantiation easier to read +type Options struct{} + +// component implements the core.RouterServer interface type component struct { - Storage - manager dutycycle.DutyManager - brokers []core.BrokerClient - ctx log.Interface + Components } // New constructs a new router -func New(db Storage, dm dutycycle.DutyManager, brokers []core.BrokerClient, ctx log.Interface) core.RouterServer { - return component{Storage: db, manager: dm, brokers: brokers, ctx: ctx} +func New(c Components, o Options) core.RouterServer { + return component{Components: c} } // HandleStats implements the core.RouterClient interface @@ -42,13 +51,13 @@ func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.S } stats.MarkMeter("router.stat.in") - return nil, r.UpdateStats(req.GatewayID, *req.Metadata) + return nil, r.Storage.UpdateStats(req.GatewayID, *req.Metadata) } // HandleData implements the core.RouterClient interface func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*core.DataRouterRes, error) { // Get some logs / analytics - r.ctx.Debug("Handling uplink packet") + r.Ctx.Debug("Handling uplink packet") stats.MarkMeter("router.uplink.in") // Validate coming data @@ -64,24 +73,24 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co } // Lookup for an existing broker - entries, err := r.Lookup(fhdr.DevAddr) + entries, err := r.Storage.Lookup(fhdr.DevAddr) if err != nil && err.(errors.Failure).Nature != errors.NotFound { - r.ctx.Warn("Database lookup failed") + r.Ctx.Warn("Database lookup failed") return nil, errors.New(errors.Operational, err) } shouldBroadcast := err != nil // Add Gateway location metadata - if gmeta, err := r.LookupStats(req.GatewayID); err == nil { + if gmeta, err := r.Storage.LookupStats(req.GatewayID); err == nil { req.Metadata.Latitude = gmeta.Latitude req.Metadata.Longitude = gmeta.Longitude req.Metadata.Altitude = gmeta.Altitude } // Add Gateway duty metadata - cycles, err := r.manager.Lookup(req.GatewayID) + cycles, err := r.DutyManager.Lookup(req.GatewayID) if err != nil { - r.ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") + r.Ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") cycles = make(dutycycle.Cycles) } @@ -101,18 +110,18 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co if shouldBroadcast { // No Recipient available -> broadcast stats.MarkMeter("router.broadcast") - response, err = r.send(bpacket, true, r.brokers...) + response, err = r.send(bpacket, true, r.Brokers...) } else { // Recipients are available stats.MarkMeter("router.send") var brokers []core.BrokerClient for _, e := range entries { - brokers = append(brokers, r.brokers[e.BrokerIndex]) + brokers = append(brokers, r.Brokers[e.BrokerIndex]) } response, err = r.send(bpacket, false, brokers...) if err != nil && err.(errors.Failure).Nature == errors.NotFound { // Might be a collision with the dev addr, we better broadcast - response, err = r.send(bpacket, true, r.brokers...) + response, err = r.send(bpacket, true, r.Brokers...) } stats.MarkMeter("router.uplink.out") } @@ -144,7 +153,7 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c datr := req.Metadata.DataRate codr := req.Metadata.CodingRate size := uint(req.Metadata.PayloadSize) - if err := r.manager.Update(gatewayID, freq, size, datr, codr); err != nil { + if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { return nil, errors.New(errors.Operational, err) } @@ -154,7 +163,7 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...core.BrokerClient) (*core.DataBrokerRes, error) { // Define a more helpful context - ctx := r.ctx.WithField("devAddr", req.Payload.MACPayload.FHDR.DevAddr) + ctx := r.Ctx.WithField("devAddr", req.Payload.MACPayload.FHDR.DevAddr) ctx.Debug("Sending Packet") nb := len(brokers) stats.UpdateHistogram("router.send_recipients", int64(nb)) @@ -234,8 +243,8 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co resp := <-chresp // Save the broker for later if it was a broadcast if isBroadcast { - if err := r.Store(req.Payload.MACPayload.FHDR.DevAddr, resp.BrokerIndex); err != nil { - r.ctx.WithError(err).Warn("Failed to store accepted broker") + if err := r.Storage.Store(req.Payload.MACPayload.FHDR.DevAddr, resp.BrokerIndex); err != nil { + r.Ctx.WithError(err).Warn("Failed to store accepted broker") } } return resp.Response, nil From 1a9d4202a0129763dd6409416e5df5e32a687262 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 21:15:53 +0100 Subject: [PATCH 1071/2266] [refactor/grpc] Re-implements router handlestats tests --- core/components/router/mockstorage_test.go | 75 +++++++++ core/components/router/router.go | 8 +- core/components/router/router_test.go | 187 +++++++++++++++++++++ core/dutycycle/dutyManager.go | 2 +- 4 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 core/components/router/mockstorage_test.go create mode 100644 core/components/router/router_test.go diff --git a/core/components/router/mockstorage_test.go b/core/components/router/mockstorage_test.go new file mode 100644 index 000000000..7dd63f2dd --- /dev/null +++ b/core/components/router/mockstorage_test.go @@ -0,0 +1,75 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "github.com/TheThingsNetwork/ttn/core" +) + +// MockStorage mocks the router.Storage interface +type MockStorage struct { + Failures map[string]error + InLookup struct { + DevAddr []byte + } + OutLookup struct { + Entries []entry + } + InStore struct { + DevAddr []byte + BrokerIndex int + } + InLookupStats struct { + GID []byte + } + OutLookupStats struct { + Metadata core.StatsMetadata + } + InUpdateStats struct { + GID []byte + Metadata core.StatsMetadata + } + InClose struct { + Called bool + } +} + +// NewMockStorage creates a new mock storage +func NewMockStorage() *MockStorage { + return &MockStorage{ + Failures: make(map[string]error), + } +} + +// Lookup implements the router.Storage interface +func (m *MockStorage) Lookup(devAddr []byte) ([]entry, error) { + m.InLookup.DevAddr = devAddr + return m.OutLookup.Entries, m.Failures["Lookup"] +} + +// Store implements the router.Storage interface +func (m *MockStorage) Store(devAddr []byte, brokerIndex int) error { + m.InStore.DevAddr = devAddr + m.InStore.BrokerIndex = brokerIndex + return m.Failures["Store"] +} + +// LookupStats implements the router.Storage interface +func (m *MockStorage) LookupStats(gid []byte) (core.StatsMetadata, error) { + m.InLookupStats.GID = gid + return m.OutLookupStats.Metadata, m.Failures["LookupStats"] +} + +// UpdateStats implements the router.Storage interface +func (m *MockStorage) UpdateStats(gid []byte, metadata core.StatsMetadata) error { + m.InUpdateStats.GID = gid + m.InUpdateStats.Metadata = metadata + return m.Failures["UpdateStats"] +} + +// Close implements the router.Storage interface +func (m *MockStorage) Close() error { + m.InClose.Called = true + return m.Failures["Close"] +} diff --git a/core/components/router/router.go b/core/components/router/router.go index 403caeab0..a3d1cbbea 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -94,13 +94,13 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co cycles = make(dutycycle.Cycles) } - sb1, err := dutycycle.GetSubBand(float64(req.Metadata.Frequency)) + sb1, err := dutycycle.GetSubBand(float32(req.Metadata.Frequency)) if err != nil { stats.MarkMeter("router.uplink.not_supported") return nil, errors.New(errors.Structural, "Unhandled uplink signal frequency") } - rx1, rx2 := uint(dutycycle.StateFromDuty(cycles[sb1])), uint(dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3])) + rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) req.Metadata.DutyRX1, req.Metadata.DutyRX2 = uint32(rx1), uint32(rx2) bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} @@ -149,10 +149,10 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c stats.MarkMeter("router.uplink.bad_broker_response") return nil, errors.New(errors.Structural, "Missing mandatory Metadata in response") } - freq := float64(req.Metadata.Frequency) + freq := req.Metadata.Frequency datr := req.Metadata.DataRate codr := req.Metadata.CodingRate - size := uint(req.Metadata.PayloadSize) + size := req.Metadata.PayloadSize if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { return nil, errors.New(errors.Operational, err) } diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go new file mode 100644 index 000000000..207458948 --- /dev/null +++ b/core/components/router/router_test.go @@ -0,0 +1,187 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + //"github.com/TheThingsNetwork/ttn/core/mocks" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "golang.org/x/net/context" +) + +func TestHandleStats(t *testing.T) { + { + Desc(t, "Handle Valid Stats Request") + + // Build + components := Components{ + Ctx: GetLogger(t, "Router"), + Storage: NewMockStorage(), + } + r := New(components, Options{}) + req := &core.StatsReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata: &core.StatsMetadata{ + Altitude: -14, + Longitude: 43.333, + Latitude: -2.342, + }, + } + + // Expect + var wantErr *string + var wantRes *core.StatsRes + var wantID = req.GatewayID + var wantMeta = *req.Metadata + + // Operate + res, err := r.HandleStats(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Stats Responses") + Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") + Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + } + + // -------------------- + + { + Desc(t, "Handle Nil Stats Requests") + + // Build + components := Components{ + Ctx: GetLogger(t, "Router"), + Storage: NewMockStorage(), + } + r := New(components, Options{}) + var req *core.StatsReq + + // Expect + var wantErr = ErrStructural + var wantRes *core.StatsRes + var wantID []byte + var wantMeta core.StatsMetadata + + // Operate + res, err := r.HandleStats(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Stats Responses") + Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") + Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + } + + // -------------------- + + { + Desc(t, "Handle Stats Request | Invalid GatewayID") + + // Build + components := Components{ + Ctx: GetLogger(t, "Router"), + Storage: NewMockStorage(), + } + r := New(components, Options{}) + req := &core.StatsReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, + Metadata: &core.StatsMetadata{ + Altitude: -14, + Longitude: 43.333, + Latitude: -2.342, + }, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.StatsRes + var wantID []byte + var wantMeta core.StatsMetadata + + // Operate + res, err := r.HandleStats(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Stats Responses") + Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") + Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + } + + // -------------------- + + { + Desc(t, "Handle Stats Request | Nil Metadata") + + // Build + components := Components{ + Ctx: GetLogger(t, "Router"), + Storage: NewMockStorage(), + } + r := New(components, Options{}) + req := &core.StatsReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.StatsRes + var wantID []byte + var wantMeta core.StatsMetadata + + // Operate + res, err := r.HandleStats(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Stats Responses") + Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") + Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + } + + // -------------------- + + { + Desc(t, "Handle Stats Request | Storage fails ") + + // Build + components := Components{ + Ctx: GetLogger(t, "Router"), + Storage: NewMockStorage(), + } + components.Storage.(*MockStorage).Failures["UpdateStats"] = errors.New(errors.Operational, "") + r := New(components, Options{}) + req := &core.StatsReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Metadata: &core.StatsMetadata{ + Altitude: -14, + Longitude: 43.333, + Latitude: -2.342, + }, + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.StatsRes + var wantID = req.GatewayID + var wantMeta = *req.Metadata + + // Operate + res, err := r.HandleStats(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Stats Responses") + Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") + Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + } +} + +func TestHandleData(t *testing.T) { + +} diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 1bc55ca6d..0e994ba8a 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -256,7 +256,7 @@ func ParseDatr(datr string) (float64, float64, error) { return sf, bw, nil } -func StateFromDuty(duty uint) State { +func StateFromDuty(duty uint32) State { if duty >= 100 { return StateBlocked } From ae50bc8b2bd10bd9fc92a782881942f8fb4a8329 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 17 Mar 2016 22:29:35 +0100 Subject: [PATCH 1072/2266] [refactor/grpc] Rewrite router tests --- core/components/router/router.go | 20 +- core/components/router/router_test.go | 1164 ++++++++++++++++++++++++- 2 files changed, 1181 insertions(+), 3 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index a3d1cbbea..0e225617c 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -63,12 +63,15 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co // Validate coming data _, _, fhdr, _, err := core.ValidateLoRaWANData(req.Payload) if err != nil { + r.Ctx.WithError(err).Debug("Invalid request payload") return nil, errors.New(errors.Structural, err) } if req.Metadata == nil { + r.Ctx.Debug("Invalid request Metadata") return nil, errors.New(errors.Structural, "Missing mandatory Metadata") } if len(req.GatewayID) != 8 { + r.Ctx.Debug("Invalid request GatewayID") return nil, errors.New(errors.Structural, "Invalid gatewayID") } @@ -79,9 +82,11 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co return nil, errors.New(errors.Operational, err) } shouldBroadcast := err != nil + r.Ctx.WithField("Should Broadcast?", shouldBroadcast).Debug("Storage Lookup done") // Add Gateway location metadata if gmeta, err := r.Storage.LookupStats(req.GatewayID); err == nil { + r.Ctx.Debug("Adding Gateway Metadata to packet") req.Metadata.Latitude = gmeta.Latitude req.Metadata.Longitude = gmeta.Longitude req.Metadata.Altitude = gmeta.Altitude @@ -102,6 +107,7 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) req.Metadata.DutyRX1, req.Metadata.DutyRX2 = uint32(rx1), uint32(rx2) + r.Ctx.WithField("DutyRX1", rx1).WithField("DutyRX2", rx2).Debug("Adding Duty values for RX1 & RX2") bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} @@ -110,16 +116,19 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co if shouldBroadcast { // No Recipient available -> broadcast stats.MarkMeter("router.broadcast") + r.Ctx.Debug("Broadcasting packet to all known brokers") response, err = r.send(bpacket, true, r.Brokers...) } else { // Recipients are available stats.MarkMeter("router.send") var brokers []core.BrokerClient + r.Ctx.Debug("Forwarding packet to known broker(s)") for _, e := range entries { brokers = append(brokers, r.Brokers[e.BrokerIndex]) } response, err = r.send(bpacket, false, brokers...) if err != nil && err.(errors.Failure).Nature == errors.NotFound { + r.Ctx.Debug("No response from known broker(s). Trying again with broadcast") // Might be a collision with the dev addr, we better broadcast response, err = r.send(bpacket, true, r.Brokers...) } @@ -141,12 +150,15 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*core.DataRouterRes, error) { if req == nil { // No response + r.Ctx.Debug("Packet sent. No downlink received.") return nil, nil } + r.Ctx.Debug("Handling downlink response") // Update downlink metadata for the related gateway if req.Metadata == nil { stats.MarkMeter("router.uplink.bad_broker_response") + r.Ctx.Warn("Missing mandatory Metadata in response") return nil, errors.New(errors.Structural, "Missing mandatory Metadata in response") } freq := req.Metadata.Frequency @@ -154,6 +166,7 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c codr := req.Metadata.CodingRate size := req.Metadata.PayloadSize if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { + r.Ctx.WithError(err).Debug("Unable to update DutyManager") return nil, errors.New(errors.Operational, err) } @@ -163,9 +176,9 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...core.BrokerClient) (*core.DataBrokerRes, error) { // Define a more helpful context - ctx := r.Ctx.WithField("devAddr", req.Payload.MACPayload.FHDR.DevAddr) - ctx.Debug("Sending Packet") nb := len(brokers) + ctx := r.Ctx.WithField("devAddr", req.Payload.MACPayload.FHDR.DevAddr) + ctx.WithField("Nb Brokers", nb).Debug("Sending Packet") stats.UpdateHistogram("router.send_recipients", int64(nb)) // Prepare ground for parrallel requests @@ -225,14 +238,17 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co // Collect response if len(chresp) > 1 { + r.Ctx.Warn("Received too many positive answers") return nil, errors.New(errors.Behavioural, "Received too many positive answers") } if len(chresp) == 0 && errored > 0 { + r.Ctx.Debug("No positive response but got errored response(s)") return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answer") } if len(chresp) == 0 && notFound > 0 { + r.Ctx.Debug("No available recipient found") return nil, errors.New(errors.NotFound, "No available recipient found") } diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 207458948..1332e25c0 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -5,11 +5,13 @@ package router import ( "testing" + "time" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/utils/errors" - //"github.com/TheThingsNetwork/ttn/core/mocks" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" "golang.org/x/net/context" ) @@ -183,5 +185,1165 @@ func TestHandleStats(t *testing.T) { } func TestHandleData(t *testing.T) { + { + Desc(t, "Handle invalid uplink | Invalid DevAddr") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4, 5}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: new(core.Metadata), + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore int + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle invalid uplink | Invalid MIC") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3}, + }, + Metadata: new(core.Metadata), + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore int + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle invalid uplink | No Metadata") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: nil, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore int + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle invalid uplink | No Payload") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: nil, + Metadata: new(core.Metadata), + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore int + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle invalid uplink | Invalid GatewayID") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: new(core.Metadata), + GatewayID: []byte{1, 2, 3, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore int + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 1 broker ok | no downlink") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 1, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br, br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 2 brokers unknown | no downlink") + + // Build + dm := mocks.NewDutyManager() + br1 := mocks.NewBrokerClient() + br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") + br2 := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br1, br2}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 1 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") + Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 2 brokers unknown | no downlink | Fail to store") + + // Build + dm := mocks.NewDutyManager() + br1 := mocks.NewBrokerClient() + br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") + br2 := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + st.Failures["Store"] = errors.New(errors.Operational, "Mock Error") + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br1, br2}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 1 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") + Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | Fail Storage Lookup") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.Failures["Lookup"] = errors.New(errors.Operational, "Mock Error") + + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br, br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | Fail DutyManager Lookup") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + br := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 1, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br, br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | Unreckognized frequency") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 1, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br, br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 12.3, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq *core.DataBrokerReq + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 2 brokers unknown | both errored") + + // Build + dm := mocks.NewDutyManager() + br1 := mocks.NewBrokerClient() + br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") + br2 := mocks.NewBrokerClient() + br2.Failures["HandleData"] = errors.New(errors.Operational, "Mock Error") + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br1, br2}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") + Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 2 brokers unknown | both respond positively") + + // Build + dm := mocks.NewDutyManager() + br1 := mocks.NewBrokerClient() + br2 := mocks.NewBrokerClient() + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br1, br2}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrBehavioural + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") + Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 1 broker known, not ok | no downlink") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + br.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 1, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br, br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrNotFound + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 1 broker known ok | valid downlink") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + br.OutHandleData.Res = &core.DataBrokerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{5, 6, 7, 8}, + FCnt: 2, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 4, + FRMPayload: []byte{42, 42, 14, 14}, + }, + MIC: []byte{8, 7, 6, 5}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 0, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantRes = &core.DataRouterRes{ + Payload: br.OutHandleData.Res.Payload, + Metadata: br.OutHandleData.Res.Metadata, + } + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + var wantUpdateGtw = req.GatewayID + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 1 broker known ok | invalid downlink | no metadata") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + br.OutHandleData.Res = &core.DataBrokerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{5, 6, 7, 8}, + FCnt: 2, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 4, + FRMPayload: []byte{42, 42, 14, 14}, + }, + MIC: []byte{8, 7, 6, 5}, + }, + Metadata: nil, + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 0, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle valid uplink | 1 broker known ok | valid downlink | fail update Duty") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br := mocks.NewBrokerClient() + br.OutHandleData.Res = &core.DataBrokerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{5, 6, 7, 8}, + FCnt: 2, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 4, + FRMPayload: []byte{42, 42, 14, 14}, + }, + MIC: []byte{8, 7, 6, 5}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + st.OutLookup.Entries = []entry{ + { + BrokerIndex: 0, + until: time.Now().Add(time.Hour), + }, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.DataRouterRes + var wantBrReq = &core.DataBrokerReq{ + Payload: req.Payload, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 0 + var wantUpdateGtw = req.GatewayID + + // Operate + res, err := r.HandleData(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Data Responses") + Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } } From 1fe8d991fd2c2c7b35f79e2c49e5f4f7d284785c Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 00:55:12 +0100 Subject: [PATCH 1073/2266] [refactor/grpc] Remove old wiring from http adapter. Now ensured by grpc. Kept Status and Healthz though --- core/adapters/http/acknacker_test.go | 477 ------------------ core/adapters/http/doc.go | 6 - core/adapters/http/handlers/applications.go | 117 ----- .../http/handlers/applicationsRegistration.go | 26 - .../http/handlers/applications_test.go | 143 ------ core/adapters/http/handlers/collect.go | 75 --- core/adapters/http/handlers/collect_test.go | 123 ----- core/adapters/http/handlers/helpers_test.go | 215 -------- core/adapters/http/handlers/pubsub.go | 152 ------ .../http/handlers/pubsubRegistration.go | 38 -- core/adapters/http/handlers/pubsub_test.go | 204 -------- .../http/handlers/registrations_test.go | 44 -- core/adapters/http/{handlers => }/healthz.go | 10 +- .../http/{handlers => }/healthz_test.go | 5 +- core/adapters/http/helpers_test.go | 76 --- core/adapters/http/http.go | 321 ++---------- core/adapters/http/httpAckNacker.go | 79 --- core/adapters/http/httpRecipient.go | 72 --- core/adapters/http/httpRegistration.go | 24 - core/adapters/http/http_test.go | 350 ++----------- core/adapters/http/regAckNacker.go | 48 -- .../http/{handlers => }/statuspage.go | 9 +- .../http/{handlers => }/statuspage_test.go | 9 +- core/adapters/http/utils.go | 12 - core/adapters/http/utils_test.go | 19 - 25 files changed, 101 insertions(+), 2553 deletions(-) delete mode 100644 core/adapters/http/acknacker_test.go delete mode 100644 core/adapters/http/handlers/applications.go delete mode 100644 core/adapters/http/handlers/applicationsRegistration.go delete mode 100644 core/adapters/http/handlers/applications_test.go delete mode 100644 core/adapters/http/handlers/collect.go delete mode 100644 core/adapters/http/handlers/collect_test.go delete mode 100644 core/adapters/http/handlers/helpers_test.go delete mode 100644 core/adapters/http/handlers/pubsub.go delete mode 100644 core/adapters/http/handlers/pubsubRegistration.go delete mode 100644 core/adapters/http/handlers/pubsub_test.go delete mode 100644 core/adapters/http/handlers/registrations_test.go rename core/adapters/http/{handlers => }/healthz.go (63%) rename core/adapters/http/{handlers => }/healthz_test.go (83%) delete mode 100644 core/adapters/http/helpers_test.go delete mode 100644 core/adapters/http/httpAckNacker.go delete mode 100644 core/adapters/http/httpRecipient.go delete mode 100644 core/adapters/http/httpRegistration.go delete mode 100644 core/adapters/http/regAckNacker.go rename core/adapters/http/{handlers => }/statuspage.go (88%) rename core/adapters/http/{handlers => }/statuspage_test.go (87%) delete mode 100644 core/adapters/http/utils.go delete mode 100644 core/adapters/http/utils_test.go diff --git a/core/adapters/http/acknacker_test.go b/core/adapters/http/acknacker_test.go deleted file mode 100644 index a8abe65bb..000000000 --- a/core/adapters/http/acknacker_test.go +++ /dev/null @@ -1,477 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" - "testing" - "time" - - // "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - errutil "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestHTTPAckNacker(t *testing.T) { - { - Desc(t, "Ack a nil packet") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - - // Operate - err := an.Ack(nil) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusOK, - Content: nil, - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Ack on a nil chresp") - - // Build - an := httpAckNacker{Chresp: nil} - - // Operate - err := an.Ack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, nil) - } - - // -------------------- - - { - Desc(t, "Ack a valid packet") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - p := mocks.NewMockPacket() - p.OutMarshalBinary = []byte{14, 14, 14} - - // Operate - err := an.Ack(p) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusOK, - Content: p.OutMarshalBinary, - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Ack an invalid packet") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - p := mocks.NewMockPacket() - p.Failures["MarshalBinary"] = errors.New(errors.Structural, "Mock Error") - - // Operate - err := an.Ack(p) - - // Check - errutil.CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckResps(t, nil, chresp) - } - - // -------------------- - - { - Desc(t, "Don't consume chresp on Ack") - - // Build - chresp := make(chan MsgRes) - an := httpAckNacker{Chresp: chresp} - - // Operate - cherr := make(chan error) - go func() { - cherr <- an.Ack(nil) - }() - - // Check - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 100): - } - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // -------------------- - - { - Desc(t, "Nack no error") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - - // Operate - err := an.Nack(nil) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusInternalServerError, - Content: []byte("Unknown Internal Error"), - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Nack NotFound error") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - e := errors.New(errors.NotFound, "Not Found") - - // Operate - err := an.Nack(e) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusNotFound, - Content: []byte(e.Error()), - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Nack Behavioural error") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - e := errors.New(errors.Behavioural, "Behavioural") - - // Operate - err := an.Nack(e) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusNotAcceptable, - Content: []byte(e.Error()), - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Nack Operational error") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - e := errors.New(errors.Operational, "Operational") - - // Operate - err := an.Nack(e) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusInternalServerError, - Content: []byte(e.Error()), - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Nack Implementation error") - - // Build - chresp := make(chan MsgRes, 1) - an := httpAckNacker{Chresp: chresp} - e := errors.New(errors.Implementation, "Implementation") - - // Operate - err := an.Nack(e) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusNotImplemented, - Content: []byte(e.Error()), - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Nack on a nil chresp") - - // Build - an := httpAckNacker{Chresp: nil} - - // Operate - err := an.Nack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, nil) - } - - // -------------------- - - { - Desc(t, "Don't consume chresp on Nack") - - // Build - chresp := make(chan MsgRes) - an := httpAckNacker{Chresp: chresp} - - // Operate - cherr := make(chan error) - go func() { - cherr <- an.Nack(nil) - }() - - // Check - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 100): - } - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - } -} - -func TestRegAckNacker(t *testing.T) { - { - Desc(t, "Ack a nil packet") - - // Build - chresp := make(chan MsgRes, 1) - an := regAckNacker{Chresp: chresp} - - // Operate - err := an.Ack(nil) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusAccepted, - Content: nil, - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Ack on a nil chresp") - - // Build - an := regAckNacker{Chresp: nil} - - // Operate - err := an.Ack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, nil) - } - - // -------------------- - - { - Desc(t, "Ack a valid packet") - - // Build - chresp := make(chan MsgRes, 1) - an := regAckNacker{Chresp: chresp} - p := mocks.NewMockPacket() - p.OutMarshalBinary = []byte{14, 14, 14} - - // Operate - err := an.Ack(p) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusAccepted, - Content: nil, - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Don't consume chresp on Ack") - - // Build - chresp := make(chan MsgRes) - an := regAckNacker{Chresp: chresp} - - // Operate - cherr := make(chan error) - go func() { - cherr <- an.Ack(nil) - }() - - // Check - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 100): - } - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - } - - // -------------------- - - { - Desc(t, "Nack") - - // Build - chresp := make(chan MsgRes, 1) - an := regAckNacker{Chresp: chresp} - - // Operate - err := an.Nack(nil) - - // Expectation - want := &MsgRes{ - StatusCode: http.StatusConflict, - Content: []byte(errors.Structural), - } - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, want, chresp) - } - - // -------------------- - - { - Desc(t, "Nack on a nil chresp") - - // Build - an := regAckNacker{Chresp: nil} - - // Operate - err := an.Nack(nil) - - // Check - errutil.CheckErrors(t, nil, err) - CheckResps(t, nil, nil) - } - - // -------------------- - - { - Desc(t, "Don't consume chresp on Nack") - - // Build - chresp := make(chan MsgRes) - an := regAckNacker{Chresp: chresp} - - // Operate - cherr := make(chan error) - go func() { - cherr <- an.Nack(nil) - }() - - // Check - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 100): - } - errutil.CheckErrors(t, pointer.String(string(errors.Operational)), err) - } -} - -func TestRecipient(t *testing.T) { - - // -------------------- - - { - Desc(t, "Test Marshal / Unmarshal binary") - - // Build - r := NewRecipient("url", "method") - - // Operate - data, err := r.MarshalBinary() - - // Check - errutil.CheckErrors(t, nil, err) - - // Build - r2 := new(recipient) - err = r2.UnmarshalBinary(data) - - // Check - errutil.CheckErrors(t, nil, err) - CheckRecipients(t, r, *r2) - } - - // -------------------- - - { - Desc(t, "Test Marshal JSON") - - // Build - r := NewRecipient("localhost", "PUT") - - // Operate - data, err := r.MarshalJSON() - - // Check - errutil.CheckErrors(t, nil, err) - CheckJSONs(t, []byte(`{"url":"localhost","method":"PUT"}`), data) - } - -} diff --git a/core/adapters/http/doc.go b/core/adapters/http/doc.go index c1dda9a0e..96ffc9720 100644 --- a/core/adapters/http/doc.go +++ b/core/adapters/http/doc.go @@ -2,10 +2,4 @@ // Use of this source code is governed by the MIT license that can be found in the LICENSE file. // Package http provides adapter implementations which run on top of http. -// -// The different protocols and mechanisms used are defined in the following document: -// https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/protocols.md -// -// The basic http adapter module can be used as a brick to build something bigger by registering -// specific endpoints. package http diff --git a/core/adapters/http/handlers/applications.go b/core/adapters/http/handlers/applications.go deleted file mode 100644 index 8787c1f42..000000000 --- a/core/adapters/http/handlers/applications.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "encoding/hex" - "encoding/json" - "io" - "net/http" - "strings" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// Applications defines a handler to handle application registration on a component. -// -// It listens to request of the form: [PUT] /applications -// where appEUI is a 8 bytes hex-encoded address. -// -// It expects a Content-Type = application/json -// -// It also looks for params: -// -// - app_url (http address as string) -// - app_eui (application identifier as 8-bytes hex-encoded string) -// -// It fails with an http 400 Bad Request. if one of the parameter is missing or invalid -// It succeeds with an http 2xx if the request is valid (the response status is under the -// ackNacker responsibility). -// It can possibly fails with another status depending of the AckNacker response. -// -// The PubSub handler generates registration where: -// - AppEUI is available -// - Recipient can be interpreted as an HttpRecipient (Url + Method) -type Applications struct{} - -// URL implements the http.Handler interface -func (p Applications) URL() string { - return "/applications" -} - -// Handle implements the http.Handler interface -func (p Applications) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { - // Check the http method - if req.Method != "PUT" { - err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [PUT] to register a device") - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte(err.Error())) - return err - } - - // Parse body and query params - registration, err := p.parse(req) - if err != nil { - BadRequest(w, err.Error()) - return err - } - - // Send the registration and wait for ack / nack - chresp := make(chan MsgRes) - chreg <- RegReq{Registration: registration, Chresp: chresp} - r, ok := <-chresp - if !ok { - err := errors.New(errors.Operational, "Core server not responding") - BadRequest(w, "Core server not responding") - return err - } - w.WriteHeader(r.StatusCode) - w.Write(r.Content) - return nil -} - -// parse extracts params from the request and fails if the request is invalid. -func (p Applications) parse(req *http.Request) (core.Registration, error) { - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - return applicationsRegistration{}, errors.New(errors.Structural, "Received invalid content-type in request") - } - - // Check configuration in body - body := make([]byte, req.ContentLength) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - return applicationsRegistration{}, errors.New(errors.Structural, err) - } - defer req.Body.Close() - params := new(struct { - URL string `json:"app_url"` - AppEUI string `json:"app_eui"` - }) - if err := json.Unmarshal(body[:n], params); err != nil { - return applicationsRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") - } - - // Verify each request parameter - params.URL = strings.Trim(params.URL, " ") - if len(params.URL) <= 0 { - return applicationsRegistration{}, errors.New(errors.Structural, "Incorrect application url") - } - - appEUI, err := hex.DecodeString(params.AppEUI) - if err != nil || len(appEUI) != 8 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") - } - - // Create actual registration - registration := applicationsRegistration{ - recipient: NewRecipient(params.URL, "PUT"), - appEUI: lorawan.EUI64{}, - } - copy(registration.appEUI[:], appEUI[:]) - return registration, nil -} diff --git a/core/adapters/http/handlers/applicationsRegistration.go b/core/adapters/http/handlers/applicationsRegistration.go deleted file mode 100644 index 421f4a3d6..000000000 --- a/core/adapters/http/handlers/applicationsRegistration.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/brocaar/lorawan" -) - -// applicationsRegistration implements the core.ARegistration interface -type applicationsRegistration struct { - recipient Recipient - appEUI lorawan.EUI64 -} - -// Recipient implements the core.ARegistration interface -func (r applicationsRegistration) Recipient() core.Recipient { - return r.recipient -} - -// AppEUI implements the core.ARegistration interface -func (r applicationsRegistration) AppEUI() lorawan.EUI64 { - return r.appEUI -} diff --git a/core/adapters/http/handlers/applications_test.go b/core/adapters/http/handlers/applications_test.go deleted file mode 100644 index 65cada330..000000000 --- a/core/adapters/http/handlers/applications_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "fmt" - "net/http" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestApplications(t *testing.T) { - tests := []struct { - Desc string - Payload string - ContentType string - Method string - ShouldAck bool - AckPacket core.Packet - - WantContent string - WantStatusCode int - WantRegistration core.Registration - WantError *string - }{ - { - Desc: "Invalid Payload. Valid ContentType. Valid Method. Nack", - Payload: "TheThingsNetwork", - ContentType: "application/json", - Method: "PUT", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Invalid ContentType. Valid Method. Nack", - Payload: `{"app_eui":"0000000011223344","app_url":"url"}`, - ContentType: "text/plain", - Method: "PUT", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Invalid Method. Nack", - Payload: `{"app_eui":"0000000011223344","app_url":"url"}`, - ContentType: "application/json", - Method: "POST", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusMethodNotAllowed, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Nack", - Payload: `{"app_eui":"000011223344","app_url":"url"}`, - ContentType: "application/json", - Method: "PUT", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Nack", - Payload: `{"app_eui":"0000000001020304","app_url":"url"}`, - ContentType: "application/json", - Method: "PUT", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusConflict, - WantRegistration: applicationsRegistration{ - recipient: NewRecipient("url", "PUT"), - appEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - }, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Ack", - Payload: `{"app_eui":"0000000001020304","app_url":"url"}`, - ContentType: "application/json", - Method: "PUT", - ShouldAck: true, - - WantContent: "", - WantStatusCode: http.StatusAccepted, - WantRegistration: applicationsRegistration{ - recipient: NewRecipient("url", "PUT"), - appEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - }, - WantError: nil, - }, - } - - var port uint = 4200 - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - adapter, url := createApplicationsAdapter(t, port) - port++ - client := testClient{} - - // Operate - url = fmt.Sprintf("%s", url) - chresp := client.Send(test.Payload, url, test.Method, test.ContentType) - registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) - var statusCode int - var content []byte - select { - case resp := <-chresp: - statusCode = resp.StatusCode - content = resp.Content - case <-time.After(time.Millisecond * 100): - } - - // Check - CheckErrors(t, test.WantError, err) - checkStatusCode(t, test.WantStatusCode, statusCode) - checkContent(t, test.WantContent, content) - checkRegistration(t, test.WantRegistration, registration) - } -} diff --git a/core/adapters/http/handlers/collect.go b/core/adapters/http/handlers/collect.go deleted file mode 100644 index a0f66936b..000000000 --- a/core/adapters/http/handlers/collect.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "io" - "net/http" - - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// Collect defines a handler for retrieving raw packets sent by a POST request. -// -// It listens to requests of the form: [POST] /packets -// -// It expects an http header Content-Type = application/octet-stream -// -// The body is expected to a binary marshaling of the given packet -// -// This handler does not generate any registration. -type Collect struct{} - -// URL implements the http.Handler interface -func (p Collect) URL() string { - return "/packets" -} - -// Handle implements the http.Handler interface -func (p Collect) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { - // Check the http method - if req.Method != "POST" { - err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [POST] to transfer a packet") - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte(err.Error())) - return err - } - - // Parse body and query params - data, err := p.parse(req) - if err != nil { - BadRequest(w, err.Error()) - return err - } - - // Send the packet and wait for ack / nack - chresp := make(chan MsgRes) - chpkt <- PktReq{Packet: data, Chresp: chresp} - r, ok := <-chresp - if !ok { - err := errors.New(errors.Operational, "Core server not responding") - BadRequest(w, err.Error()) - return err - } - w.WriteHeader(r.StatusCode) - w.Write(r.Content) - return nil -} - -// parse extracts params from the request and fails if the request is invalid. -func (p Collect) parse(req *http.Request) ([]byte, error) { - // Check Content-type - if req.Header.Get("Content-Type") != "application/octet-stream" { - return nil, errors.New(errors.Structural, "Received invalid content-type in request") - } - - // Check configuration in body - body := make([]byte, req.ContentLength) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - return nil, errors.New(errors.Structural, err) - } - return body[:n], nil -} diff --git a/core/adapters/http/handlers/collect_test.go b/core/adapters/http/handlers/collect_test.go deleted file mode 100644 index 33333ae5f..000000000 --- a/core/adapters/http/handlers/collect_test.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "net/http" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestCollect(t *testing.T) { - tests := []struct { - Desc string - Payload string - ContentType string - Method string - ShouldAck bool - AckPacket core.Packet - - WantContent string - WantStatusCode int - WantPacket []byte - WantError *string - }{ - { - Desc: "Valid Payload. Invalid ContentType. Valid Method. Nack.", - Payload: "Patate", - ContentType: "application/patate", - Method: "POST", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantPacket: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Invalid Method. Nack.", - Payload: "Patate", - ContentType: "application/octet-stream", - Method: "PUT", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusMethodNotAllowed, - WantPacket: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Nack.", - Payload: "Patate", - ContentType: "application/octet-stream", - Method: "POST", - ShouldAck: false, - - WantContent: "Unknown", - WantStatusCode: http.StatusInternalServerError, - WantPacket: []byte("Patate"), - WantError: nil, - }, - { - Desc: "Invalid Ack. Valid ContentType. Valid Method.", - Payload: "Patate", - ContentType: "application/octet-stream", - Method: "POST", - ShouldAck: true, - AckPacket: testPacket{payload: ""}, - - WantContent: string(errors.Operational), - WantStatusCode: http.StatusBadRequest, - WantPacket: []byte("Patate"), - WantError: nil, - }, - { - Desc: "Valid Ack. Valid ContentType. Valid Method.", - Payload: "Patate", - ContentType: "application/octet-stream", - Method: "POST", - ShouldAck: true, - AckPacket: testPacket{payload: "Response"}, - - WantContent: "Response", - WantStatusCode: http.StatusOK, - WantPacket: []byte("Patate"), - WantError: nil, - }, - } - - var port uint = 3000 - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - adapter, url := createCollectAdapter(t, port) - client := testClient{} - - // Operate - chresp := client.Send(test.Payload, url, test.Method, test.ContentType) - packet, err := tryNext(adapter, test.ShouldAck, test.AckPacket) - var statusCode int - var content []byte - select { - case resp := <-chresp: - statusCode = resp.StatusCode - content = resp.Content - case <-time.After(time.Millisecond * 100): - } - - // Check - CheckErrors(t, test.WantError, err) - checkStatusCode(t, test.WantStatusCode, statusCode) - checkContent(t, test.WantContent, content) - checkPacket(t, test.WantPacket, packet) - port++ - } -} diff --git a/core/adapters/http/handlers/helpers_test.go b/core/adapters/http/handlers/helpers_test.go deleted file mode 100644 index 81fe19eca..000000000 --- a/core/adapters/http/handlers/helpers_test.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "bytes" - "fmt" - "io" - "net/http" - "reflect" - "strings" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -// ----- TYPES utilities -type testPacket struct { - payload string -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p testPacket) MarshalBinary() ([]byte, error) { - if p.payload == "" { - return nil, errors.New(errors.Structural, "Fake error") - } - - return []byte(p.payload), nil -} - -// String implements the core.Packet interface -func (p testPacket) String() string { - return p.payload -} - -// DevEUI implements the devEUI -func (p testPacket) DevEUI() lorawan.EUI64 { - return lorawan.EUI64{} -} - -// ----- BUILD utilities -func createPubSubAdapter(t *testing.T, port uint) (*Adapter, string) { - net := fmt.Sprintf("0.0.0.0:%d", port) - adapter, err := NewAdapter(net, nil, GetLogger(t, "Adapter")) - if err != nil { - panic(err) - } - <-time.After(time.Millisecond * 250) // Let the connection starts - handler := PubSub{} - adapter.Bind(handler) - return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) -} - -func createApplicationsAdapter(t *testing.T, port uint) (*Adapter, string) { - net := fmt.Sprintf("0.0.0.0:%d", port) - adapter, err := NewAdapter(net, nil, GetLogger(t, "Adapter")) - if err != nil { - panic(err) - } - <-time.After(time.Millisecond * 250) // Let the connection starts - handler := Applications{} - adapter.Bind(handler) - return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) -} - -func createCollectAdapter(t *testing.T, port uint) (*Adapter, string) { - net := fmt.Sprintf("0.0.0.0:%d", port) - adapter, err := NewAdapter(net, nil, GetLogger(t, "Adapter")) - if err != nil { - panic(err) - } - <-time.After(time.Millisecond * 250) // Let the connection starts - handler := Collect{} - adapter.Bind(handler) - return adapter, fmt.Sprintf("http://0.0.0.0:%d%s", port, handler.URL()) -} - -type testClient struct { - http.Client -} - -func (c testClient) Send(payload string, url string, method string, contentType string) chan MsgRes { - buf := new(bytes.Buffer) - if _, err := buf.Write([]byte(payload)); err != nil { - panic(err) - } - - request, err := http.NewRequest(method, url, buf) - if err != nil { - panic(err) - } - request.Header.Set("Content-Type", contentType) - - chresp := make(chan MsgRes) - go func() { - resp, err := c.Do(request) - if err != nil { - panic(err) - } - - data := make([]byte, 2048) - n, err := resp.Body.Read(data) - if err != nil && err != io.EOF { - panic(err) - } - - chresp <- MsgRes{resp.StatusCode, data[:n]} - }() - return chresp -} - -// ----- OPERATE utilities -func tryNext(adapter core.Adapter, shouldAck bool, packet core.Packet) ([]byte, error) { - chresp := make(chan struct { - Packet []byte - Error error - }) - go func() { - pkt, an, err := adapter.Next() - defer func() { - chresp <- struct { - Packet []byte - Error error - }{pkt, err} - }() - if err != nil { - return - } - - if shouldAck { - an.Ack(packet) - } else { - an.Nack(nil) - } - }() - - select { - case resp := <-chresp: - return resp.Packet, resp.Error - case <-time.After(time.Millisecond * 100): - return nil, nil - } -} - -func tryNextRegistration(adapter core.Adapter, shouldAck bool, packet core.Packet) (core.Registration, error) { - chresp := make(chan struct { - Registration core.Registration - Error error - }) - go func() { - reg, an, err := adapter.NextRegistration() - defer func() { - chresp <- struct { - Registration core.Registration - Error error - }{reg, err} - }() - - if err != nil { - return - } - - if shouldAck { - an.Ack(packet) - } else { - an.Nack(nil) - } - }() - - select { - case resp := <-chresp: - return resp.Registration, resp.Error - case <-time.After(time.Millisecond * 100): - return nil, nil - } -} - -// ----- CHECK utilities -func checkStatusCode(t *testing.T, want int, got int) { - if want == got { - Ok(t, "Check status code") - return - } - Ko(t, "Expected status code to be %d but got %d", want, got) -} - -func checkContent(t *testing.T, want string, got []byte) { - if strings.Contains(string(got), want) { - Ok(t, "Check content") - return - } - Ko(t, "Received content does not match expectations.\nWant: %s\nGot: %s", want, string(got)) -} - -func checkPacket(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packet") - return - } - Ko(t, "Received packet does not match expectations.\nWant: %v\nGot: %v", want, got) -} - -func checkRegistration(t *testing.T, want core.Registration, got core.Registration) { - if !reflect.DeepEqual(want, got) { - Ko(t, "Received registration does not match expectations.\nWant: %v\nGot: %v", want, got) - return - } - Ok(t, "check Registrations") -} diff --git a/core/adapters/http/handlers/pubsub.go b/core/adapters/http/handlers/pubsub.go deleted file mode 100644 index 6f5a66481..000000000 --- a/core/adapters/http/handlers/pubsub.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "encoding/hex" - "encoding/json" - "io" - "net/http" - "strings" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -// PubSub defines a handler to handle application | devEUI registration on a component. -// -// It listens to request of the form: [PUT] /end-devices -// where devEUI is a 8 bytes hex-encoded address. -// -// It expects a Content-Type = application/json -// -// It also looks for params: -// -// - dev_eui (8 bytes hex-encoded string) -// - app_eui (8 bytes hex-encoded string) -// - recipient { -// - url (http address as string) -// - method (http verb as string) -// - } -// - nwks_key (16 bytes hex-encoded string) -// -// It fails with an http 400 Bad Request. if one of the parameter is missing or invalid -// It succeeds with an http 2xx if the request is valid (the response status is under the -// ackNacker responsibility. -// It can possibly fails with another status depending of the AckNacker response. -// -// The PubSub handler generates registration where: -// - AppEUI is available -// - DevEUI is available -// - NwkSKey is available -// - Recipient can be interpreted as an HttpRecipient (Url + Method) -type PubSub struct{} - -// URL implements the http.Handler interface -func (p PubSub) URL() string { - return "/end-devices" -} - -// Handle implements the http.Handler interface -func (p PubSub) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { - // Check the http method - if req.Method != "PUT" { - err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [PUT] to register a device") - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte(err.Error())) - return err - } - - // Parse body and query params - registration, err := p.parse(req) - if err != nil { - BadRequest(w, err.Error()) - return err - } - - // Send the registration and wait for ack / nack - chresp := make(chan MsgRes) - chreg <- RegReq{Registration: registration, Chresp: chresp} - r, ok := <-chresp - if !ok { - err := errors.New(errors.Operational, "Core server not responding") - BadRequest(w, err.Error()) - return err - } - w.WriteHeader(r.StatusCode) - w.Write(r.Content) - return nil -} - -// parse extracts params from the request and fails if the request is invalid. -func (p PubSub) parse(req *http.Request) (core.Registration, error) { - // Check Content-type - if req.Header.Get("Content-Type") != "application/json" { - return pubSubRegistration{}, errors.New(errors.Structural, "Received invalid content-type in request") - } - - // Check configuration in body - body := make([]byte, req.ContentLength) - n, err := req.Body.Read(body) - if err != nil && err != io.EOF { - return pubSubRegistration{}, errors.New(errors.Structural, err) - } - defer req.Body.Close() - params := new(struct { - Recipient struct { - URL string `json:"url"` - Method string `json:"method"` - } `json:"recipient"` - Registration struct { - AppEUI string `json:"app_eui"` - DevEUI string `json:"dev_eui"` - NwkSKey string `json:"nwks_key"` - } `json:"registration"` - }) - if err := json.Unmarshal(body[:n], params); err != nil { - return pubSubRegistration{}, errors.New(errors.Structural, "Unable to unmarshal the request body") - } - - // Verify each request parameter - nwkSKey, err := hex.DecodeString(params.Registration.NwkSKey) - if err != nil || len(nwkSKey) != 16 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect network session key") - } - - appEUI, err := hex.DecodeString(params.Registration.AppEUI) - if err != nil || len(appEUI) != 8 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application eui") - } - - devEUI, err := hex.DecodeString(params.Registration.DevEUI) - if err != nil || len(devEUI) != 8 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect device eui") - } - - params.Recipient.URL = strings.Trim(params.Recipient.URL, " ") - if len(params.Recipient.URL) <= 0 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application url") - } - - params.Recipient.Method = strings.Trim(params.Recipient.Method, " ") - if len(params.Recipient.Method) <= 0 { - return pubSubRegistration{}, errors.New(errors.Structural, "Incorrect application method") - } - - // Create actual registration - registration := pubSubRegistration{ - recipient: NewRecipient(params.Recipient.URL, params.Recipient.Method), - appEUI: lorawan.EUI64{}, - devEUI: lorawan.EUI64{}, - nwkSKey: lorawan.AES128Key{}, - } - - copy(registration.appEUI[:], appEUI[:]) - copy(registration.nwkSKey[:], nwkSKey[:]) - copy(registration.devEUI[:], devEUI[:]) - - return registration, nil -} diff --git a/core/adapters/http/handlers/pubsubRegistration.go b/core/adapters/http/handlers/pubsubRegistration.go deleted file mode 100644 index 41f39e380..000000000 --- a/core/adapters/http/handlers/pubsubRegistration.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/brocaar/lorawan" -) - -// type pubSubRegistration implements the core.Registration interface -type pubSubRegistration struct { - recipient Recipient - appEUI lorawan.EUI64 - nwkSKey lorawan.AES128Key - devEUI lorawan.EUI64 -} - -// Recipient implements the core.Registration interface -func (r pubSubRegistration) Recipient() core.Recipient { - return r.recipient -} - -// AppEUI implements the core.Registration interface -func (r pubSubRegistration) AppEUI() lorawan.EUI64 { - return r.appEUI -} - -// DevEUI implements the core.Registration interface -func (r pubSubRegistration) DevEUI() lorawan.EUI64 { - return r.devEUI -} - -// NwkSKey implements the core.Registration interface -func (r pubSubRegistration) NwkSKey() lorawan.AES128Key { - return r.nwkSKey -} diff --git a/core/adapters/http/handlers/pubsub_test.go b/core/adapters/http/handlers/pubsub_test.go deleted file mode 100644 index 5d9078388..000000000 --- a/core/adapters/http/handlers/pubsub_test.go +++ /dev/null @@ -1,204 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "fmt" - "net/http" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestPubSub(t *testing.T) { - tests := []struct { - Desc string - Payload string - ContentType string - Method string - DevEUI string - ShouldAck bool - AckPacket core.Packet - - WantContent string - WantStatusCode int - WantRegistration core.Registration - WantError *string - }{ - { - Desc: "Invalid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", - Payload: "TheThingsNetwork", - ContentType: "application/json", - Method: "PUT", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Invalid ContentType. Valid Method. Valid DevEUI. Nack", - Payload: `{ - "registration":{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304" - }, - "recipient": { - "url": "url", - "method": "PUT" - } - }`, - ContentType: "text/plain", - Method: "PUT", - DevEUI: "", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Invalid Method. Valid DevEUI. Nack", - Payload: `{ - "registration":{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304" - }, - "recipient": { - "url": "url", - "method": "PUT" - } - }`, - ContentType: "application/json", - Method: "POST", - DevEUI: "0000000001020304", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusMethodNotAllowed, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Invalid DevEUI. Nack", - Payload: `{ - "registration":{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000001144" - }, - "recipient": { - "url": "url", - "method": "PUT" - } - }`, - ContentType: "application/json", - Method: "PUT", - DevEUI: "12345678", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusBadRequest, - WantRegistration: nil, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Nack", - Payload: `{ - "registration":{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304" - }, - "recipient": { - "url": "url", - "method": "PUT" - } - }`, - ContentType: "application/json", - Method: "PUT", - DevEUI: "0000000001020304", - ShouldAck: false, - - WantContent: string(errors.Structural), - WantStatusCode: http.StatusConflict, - WantRegistration: pubSubRegistration{ - recipient: NewRecipient("url", "PUT"), - appEUI: lorawan.EUI64([8]byte{0, 1, 2, 3, 4, 5, 6, 7}), - nwkSKey: lorawan.AES128Key([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}), - devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - }, - WantError: nil, - }, - { - Desc: "Valid Payload. Valid ContentType. Valid Method. Valid DevEUI. Ack", - Payload: `{ - "registration":{ - "app_eui":"0001020304050607", - "nwks_key":"00010203040506070809000102030405", - "dev_eui": "0000000001020304" - }, - "recipient": { - "url": "url", - "method": "PUT" - } - }`, - ContentType: "application/json", - Method: "PUT", - DevEUI: "0000000001020304", - ShouldAck: true, - - WantContent: "", - WantStatusCode: http.StatusAccepted, - WantRegistration: pubSubRegistration{ - recipient: NewRecipient("url", "PUT"), - appEUI: lorawan.EUI64([8]byte{0, 1, 2, 3, 4, 5, 6, 7}), - nwkSKey: lorawan.AES128Key([16]byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5}), - devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - }, - WantError: nil, - }, - } - - var port uint = 4000 - for _, test := range tests { - // Describe - Desc(t, test.Desc) - - // Build - adapter, url := createPubSubAdapter(t, port) - port++ - client := testClient{} - - // Operate - url = fmt.Sprintf("%s", url) - chresp := client.Send(test.Payload, url, test.Method, test.ContentType) - registration, err := tryNextRegistration(adapter, test.ShouldAck, test.AckPacket) - var statusCode int - var content []byte - select { - case resp := <-chresp: - statusCode = resp.StatusCode - content = resp.Content - case <-time.After(time.Millisecond * 100): - } - - // Check - t.Log(string(content)) - CheckErrors(t, test.WantError, err) - checkStatusCode(t, test.WantStatusCode, statusCode) - checkContent(t, test.WantContent, content) - checkRegistration(t, test.WantRegistration, registration) - } -} diff --git a/core/adapters/http/handlers/registrations_test.go b/core/adapters/http/handlers/registrations_test.go deleted file mode 100644 index 937f63397..000000000 --- a/core/adapters/http/handlers/registrations_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handlers - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core/adapters/http" - mocks "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/brocaar/lorawan" -) - -func TestPubSubRegistration(t *testing.T) { - recipient := http.NewRecipient("url", "method") - devEUI := lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) - appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) - nwkSKey := lorawan.AES128Key([16]byte{0, 0, 1, 1, 0, 0, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4}) - - reg := pubSubRegistration{ - recipient: recipient, - devEUI: devEUI, - appEUI: appEUI, - nwkSKey: nwkSKey, - } - - mocks.Check(t, recipient, reg.Recipient(), "Recipients") - mocks.Check(t, devEUI, reg.DevEUI(), "DevEUIs") - mocks.Check(t, appEUI, reg.AppEUI(), "AppEUIs") - mocks.Check(t, nwkSKey, reg.NwkSKey(), "NwkSKeys") -} - -func TestApplicationsRegistration(t *testing.T) { - recipient := http.NewRecipient("url", "method") - appEUI := lorawan.EUI64([8]byte{1, 43, 3, 4, 6, 6, 6, 8}) - - reg := applicationsRegistration{ - recipient: recipient, - appEUI: appEUI, - } - - mocks.Check(t, recipient, reg.Recipient(), "Recipients") - mocks.Check(t, appEUI, reg.AppEUI(), "AppEUIs") -} diff --git a/core/adapters/http/handlers/healthz.go b/core/adapters/http/healthz.go similarity index 63% rename from core/adapters/http/handlers/healthz.go rename to core/adapters/http/healthz.go index 18e147eaa..f8aeb0dfe 100644 --- a/core/adapters/http/handlers/healthz.go +++ b/core/adapters/http/healthz.go @@ -1,21 +1,15 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package handlers +package http import ( "net/http" - - . "github.com/TheThingsNetwork/ttn/core/adapters/http" ) // Healthz defines a handler to ping adapters via a GET request. // // It listens to requests of the form: [GET] /healthz -// -// -// This handler does not generate any packet. -// This handler does not generate any registration. type Healthz struct{} // URL implements the http.Handler interface @@ -24,7 +18,7 @@ func (p Healthz) URL() string { } // Handle implements the http.Handler interface -func (p Healthz) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { +func (p Healthz) Handle(w http.ResponseWriter, req *http.Request) error { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) return nil diff --git a/core/adapters/http/handlers/healthz_test.go b/core/adapters/http/healthz_test.go similarity index 83% rename from core/adapters/http/handlers/healthz_test.go rename to core/adapters/http/healthz_test.go index 06b2b4db5..af93fe29a 100644 --- a/core/adapters/http/handlers/healthz_test.go +++ b/core/adapters/http/healthz_test.go @@ -1,13 +1,12 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package handlers +package http import ( "net/http" "testing" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/smartystreets/assertions" ) @@ -29,7 +28,7 @@ func TestHealthzHandle(t *testing.T) { req.RemoteAddr = "127.0.0.1:12345" rw := NewResponseWriter() - h.Handle(&rw, make(chan<- PktReq), make(chan<- RegReq), req) + h.Handle(&rw, req) a.So(rw.TheStatus, assertions.ShouldEqual, 200) a.So(string(rw.TheBody), assertions.ShouldEqual, "ok") } diff --git a/core/adapters/http/helpers_test.go b/core/adapters/http/helpers_test.go deleted file mode 100644 index ee1c07406..000000000 --- a/core/adapters/http/helpers_test.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "reflect" - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// Check utilities -func checkRegistrations(t *testing.T, want []testRegistration, got []core.RRegistration) { - if len(want) != len(got) { - Ko(t, "Expected %d registrations but got %d", len(want), len(got)) - } - -outer: - for _, rw := range want { - for _, rg := range got { - if rg.DevEUI() != rw.DevEUI { - Ko(t, "Expected registration for %v but got for %v", rw.DevEUI, rg.DevEUI()) - } - if reflect.DeepEqual(rw.Recipient.Recipient, rg.Recipient()) { - continue outer - } - } - Ko(t, "Registrations don't match expectation.\nWant: %v\nGot: %v", want, got) - } - Ok(t, "Check registrations") -} - -func checkPayloads(t *testing.T, want string, got []string) { - for _, payload := range got { - if want != payload { - Ko(t, "Paylaod don't match expectation.\nWant: %s\nGot: %s", want, payload) - } - } - Ok(t, "Check payloads") -} - -func CheckResps(t *testing.T, want *MsgRes, got chan MsgRes) { - if want == nil { - if len(got) == 0 { - Ok(t, "Check Resps") - return - } - Ko(t, "Expected no message response but got one") - } - - if len(got) < 1 { - Ko(t, "Expected one message but got none") - } - - msg := <-got - mocks.Check(t, *want, msg, "Resps") -} - -func CheckRecipients(t *testing.T, want Recipient, got Recipient) { - mocks.Check(t, want, got, "Recipients") -} - -func CheckJSONs(t *testing.T, want []byte, got []byte) { - mocks.Check(t, want, got, "JSON") -} - -func CheckMethods(t *testing.T, want string, got string) { - mocks.Check(t, want, got, "Methods") -} - -func CheckContentTypes(t *testing.T, want string, got string) { - mocks.Check(t, want, got, "ContentTypes") -} diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go index ff68bc056..dbb33081d 100644 --- a/core/adapters/http/http.go +++ b/core/adapters/http/http.go @@ -4,319 +4,72 @@ package http import ( - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" "net/http" - "sync" "time" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" ) -// Adapter type materializes an http adapter which implements the basic http protocol -type Adapter struct { - http.Client // Adapter is also an http client - ctx log.Interface // Just a logger, no one really cares about him. - packets chan PktReq // Channel used to "transforms" incoming request to something we can handle concurrently - recipients []core.Recipient // Known recipient used for broadcast if any - registrations chan RegReq // Incoming registrations - serveMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints - net string // Address on which is listening the adapter http server +// Interface defines the public interfaces used by the adapter +type Interface interface { + Bind(Handler) +} + +// adapter type materializes an http adapter +type adapter struct { + Client http.Client // Adapter is also an http client + ServeMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints + Components } // Handler defines endpoint-specific handler. type Handler interface { URL() string - Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error -} - -// MsgRes are sent through the response channel of a pktReq or regReq -type MsgRes struct { - StatusCode int // The http status code to set as an answer - Content []byte // The response content. + Handle(w http.ResponseWriter, req *http.Request) error } -// PktReq are sent through the packets channel when an incoming request arrives -type PktReq struct { - Packet []byte // The actual packet that has been parsed - Chresp chan MsgRes // A response channel waiting for an success or reject confirmation +// Components makes instantiation easier to read +type Components struct { + Ctx log.Interface } -// RegReq are sent through the registration channel when an incoming registration arrives -type RegReq struct { - Registration core.Registration - Chresp chan MsgRes -} - -// NewAdapter constructs and allocates a new http adapter -func NewAdapter(net string, recipients []core.Recipient, ctx log.Interface) (*Adapter, error) { - a := Adapter{ - Client: http.Client{Timeout: 6 * time.Second}, - ctx: ctx, - packets: make(chan PktReq), - recipients: recipients, - registrations: make(chan RegReq), - serveMux: http.NewServeMux(), - net: net, - } - - go a.listenRequests(net) - - return &a, nil +// Options makes instantiation easier to read +type Options struct { + NetAddr string + Timeout time.Duration } -// Register implements the core.Subscriber interface -func (a *Adapter) Subscribe(r core.Registration) error { - // 1. Type assertions and convertions - jsonMarshaler, ok := r.(json.Marshaler) - if !ok { - return errors.New(errors.Structural, "Unable to marshal registration") - } - httpRecipient, ok := r.Recipient().(Recipient) - if !ok { - return errors.New(errors.Structural, "Invalid recipient") - } - - // 2. Marshaling - data, err := json.Marshal(struct { - Recipient struct { - Method string `json:"method"` - URL string `json:"url"` - } `json:"recipient"` - Registration json.Marshaler `json:"registration"` - }{ - Recipient: struct { - Method string `json:"method"` - URL string `json:"url"` - }{ - Method: "POST", - URL: a.net, - }, - Registration: jsonMarshaler, - }) - if err != nil { - return errors.New(errors.Structural, err) - } - buf := new(bytes.Buffer) - buf.Write(data) - - // 3. Send Request - req, err := http.NewRequest(httpRecipient.Method(), fmt.Sprintf("http://%s/end-devices", httpRecipient.URL()), buf) - if err != nil { - return errors.New(errors.Operational, err) - } - req.Header.Add("content-type", "application/json") - resp, err := a.Do(req) - if err != nil { - return errors.New(errors.Operational, err) - } - defer resp.Body.Close() - - // 4. Handle response -> resp body isn't relevant - if resp.StatusCode != http.StatusAccepted && resp.StatusCode != http.StatusOK { - errData := make([]byte, resp.ContentLength) - resp.Body.Read(errData) - return errors.New(errors.Operational, string(errData)) - } - return nil -} - -// Send implements the core.Adapter interface -func (a *Adapter) Send(p core.Packet, recipients ...core.Recipient) ([]byte, error) { - // Marshal the packet to raw binary data - data, err := p.MarshalBinary() - if err != nil { - a.ctx.WithError(err).Warn("Invalid Packet") - return nil, errors.New(errors.Structural, err) - } - - // Try to define a more helpful context - ctx := a.ctx.WithField("devEUI", p.DevEUI()) - ctx.Debug("Sending Packet") - - // Determine whether it's a broadcast or a direct send - nb := len(recipients) - isBroadcast := false - if nb == 0 { - // If no recipient was supplied, try with the known one, otherwise quit. - recipients = a.recipients - nb = len(recipients) - isBroadcast = true - if nb == 0 { - return nil, errors.New(errors.Structural, "No recipient found") - } - } - - if isBroadcast { - stats.MarkMeter("http_adapter.broadcast") - } else { - stats.MarkMeter("http_adapter.send") +// New instantiates a new adapter +func New(c Components, o Options) Interface { + a := adapter{ + Components: c, + ServeMux: http.NewServeMux(), + Client: http.Client{Timeout: o.Timeout}, } - stats.UpdateHistogram("http_adapter.send_recipients", int64(len(recipients))) - - // Prepare ground for parrallel http request - cherr := make(chan error, nb) - chresp := make(chan []byte, nb) - wg := sync.WaitGroup{} - wg.Add(nb) - - // Run each request - for _, recipient := range recipients { - go func(rawRecipient core.Recipient) { - defer wg.Done() - - // Get the actual recipient - recipient, ok := rawRecipient.(Recipient) - if !ok { - ctx.WithField("recipient", rawRecipient).Warn("Unable to interpret recipient as Recipient") - return - } - ctx := ctx.WithField("recipient", recipient.URL()) - - // Send request - ctx.Debugf("%s Request", recipient.Method()) - buf := new(bytes.Buffer) - buf.Write(data) - req, err := http.NewRequest(recipient.Method(), fmt.Sprintf("http://%s/packets", recipient.URL()), buf) - if err != nil { - cherr <- errors.New(errors.Operational, err) - return - } - req.Header.Add("content-type", "application/octet-stream") - resp, err := a.Do(req) - - if err != nil { - cherr <- errors.New(errors.Operational, err) - return - } - defer func() { - // This is needed because the default HTTP client's Transport does not - // attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections unless the Body - // is read to completion and is closed. - io.Copy(ioutil.Discard, resp.Body) - resp.Body.Close() - }() - - // Check response code - switch resp.StatusCode { - case http.StatusOK: - ctx.Debug("Recipient registered for packet") - data, err := ioutil.ReadAll(resp.Body) - if err != nil && err != io.EOF { - cherr <- errors.New(errors.Operational, err) - return - } - if len(data) > 0 { - chresp <- data - } - if isBroadcast { // Generate registration on broadcast - go func() { - a.registrations <- RegReq{ - Registration: httpRegistration{ - recipient: rawRecipient, - devEUI: p.DevEUI(), - }, - Chresp: nil, - } - }() - } - case http.StatusNotFound: - ctx.Debug("Recipient not interested in packet") - cherr <- errors.New(errors.NotFound, "Recipient not interested") - default: - cherr <- errors.New(errors.Operational, fmt.Sprintf("Unexpected response from server: %s (%d)", resp.Status, resp.StatusCode)) - } - }(recipient) - } - - // Wait for each request to be done - stats.IncCounter("http_adapter.waiting_for_send") - wg.Wait() - stats.DecCounter("http_adapter.waiting_for_send") - close(cherr) - close(chresp) - - var errored uint8 - var notFound uint8 - for i := 0; i < len(cherr); i++ { - err := <-cherr - if err.(errors.Failure).Nature != errors.NotFound { - errored++ - ctx.WithError(err).Warn("POST Failed") - } else { - notFound++ - ctx.WithError(err).Debug("Packet destination not found") - } - } - - // Collect response - if len(chresp) > 1 { - return nil, errors.New(errors.Behavioural, "Received too many positive answers") - } - - if len(chresp) == 0 && errored > 0 { - return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answer") - } - - if len(chresp) == 0 && notFound > 0 { - return nil, errors.New(errors.NotFound, "No available recipient found") - } - - if len(chresp) == 0 { - return nil, nil - } - return <-chresp, nil -} - -// GetRecipient implements the core.Adapter interface -func (a *Adapter) GetRecipient(raw []byte) (core.Recipient, error) { - recipient := new(recipient) - if err := recipient.UnmarshalBinary(raw); err != nil { - return nil, errors.New(errors.Structural, err) - } - return *recipient, nil -} - -// Next implements the core.Adapter interface -func (a *Adapter) Next() ([]byte, core.AckNacker, error) { - p := <-a.packets - return p.Packet, httpAckNacker{Chresp: p.Chresp}, nil -} - -// NextRegistration implements the core.Adapter interface. Not implemented for this adapter. -// -// See broadcast and pubsub adapters for mechanisms to handle registrations. -func (a *Adapter) NextRegistration() (core.Registration, core.AckNacker, error) { - r := <-a.registrations - return r.Registration, regAckNacker{Chresp: r.Chresp}, nil + go a.listenRequests(o.NetAddr) + return &a } // Bind registers a handler to a specific endpoint -func (a *Adapter) Bind(h Handler) { - a.ctx.WithField("url", h.URL()).Info("Register new endpoint") - a.serveMux.HandleFunc(h.URL(), func(w http.ResponseWriter, req *http.Request) { - ctx := a.ctx.WithField("url", h.URL()) - ctx.Debug("Handle new request") - err := h.Handle(w, a.packets, a.registrations, req) +func (a *adapter) Bind(h Handler) { + a.Ctx.WithField("url", h.URL()).Info("Register new endpoint") + a.ServeMux.HandleFunc(h.URL(), func(w http.ResponseWriter, req *http.Request) { + Ctx := a.Ctx.WithField("url", h.URL()) + Ctx.Debug("Handle new request") + err := h.Handle(w, req) if err != nil { - ctx.WithError(err).Debug("Failed to handle the request") + Ctx.WithError(err).Debug("Failed to handle the request") } }) } // listenRequests handles incoming registration request sent through http to the adapter -func (a *Adapter) listenRequests(net string) { +func (a *adapter) listenRequests(net string) { server := http.Server{ Addr: net, - Handler: a.serveMux, + Handler: a.ServeMux, } - a.ctx.WithField("bind", net).Info("Starting Server") + a.Ctx.WithField("bind", net).Info("Starting Server") err := server.ListenAndServe() - a.ctx.WithError(err).Warn("HTTP connection lost") + a.Ctx.WithError(err).Warn("HTTP connection lost") } diff --git a/core/adapters/http/httpAckNacker.go b/core/adapters/http/httpAckNacker.go deleted file mode 100644 index 82c199d2f..000000000 --- a/core/adapters/http/httpAckNacker.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// httpAckNacker implements the AckNacker interface -type httpAckNacker struct { - Chresp chan<- MsgRes // A channel dedicated to send back a response -} - -// Ack implements the core.AckNacker interface -func (an httpAckNacker) Ack(p core.Packet) error { - if an.Chresp == nil { - return nil - } - defer close(an.Chresp) - - var data []byte - if p != nil { - var err error - data, err = p.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - } - - select { - case an.Chresp <- MsgRes{ - StatusCode: http.StatusOK, - Content: data, - }: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(errors.Operational, "No response was given to the acknacker") - } -} - -// Nack implements the core.AckNacker interface -func (an httpAckNacker) Nack(err error) error { - if an.Chresp == nil { - return nil - } - defer close(an.Chresp) - - var code int - var content []byte - - if err == nil { - code = http.StatusInternalServerError - content = []byte("Unknown Internal Error") - } else { - switch err.(errors.Failure).Nature { - case errors.NotFound: - code = http.StatusNotFound - case errors.Behavioural: - code = http.StatusNotAcceptable - case errors.Implementation: - code = http.StatusNotImplemented - default: - code = http.StatusInternalServerError - } - content = []byte(err.Error()) - } - - select { - case an.Chresp <- MsgRes{StatusCode: code, Content: content}: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(errors.Operational, "No response was given to the acknacker") - } -} diff --git a/core/adapters/http/httpRecipient.go b/core/adapters/http/httpRecipient.go deleted file mode 100644 index 705a9c729..000000000 --- a/core/adapters/http/httpRecipient.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "encoding" - "encoding/json" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -// Recipient represents the recipient used by the http adapter -type Recipient interface { - json.Marshaler - encoding.BinaryMarshaler - URL() string - Method() string -} - -// NewRecipient constructs a new HttpRecipient -func NewRecipient(url string, method string) Recipient { - return recipient{url: url, method: method} -} - -// Recipient materializes recipients manipulated by the http adapter -type recipient struct { - url string - method string -} - -// Url implements the Recipient interface -func (h recipient) URL() string { - return h.url -} - -// Method implements the Recipient interface -func (h recipient) Method() string { - return h.method -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (h recipient) MarshalBinary() ([]byte, error) { - w := readwriter.New(nil) - w.Write(h.url) - w.Write(h.method) - return w.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (h *recipient) UnmarshalBinary(data []byte) error { - r := readwriter.New(data) - r.Read(func(data []byte) { h.url = string(data) }) - r.Read(func(data []byte) { h.method = string(data) }) - return r.Err() -} - -// MarshalJSON implements the encoding/json.Marshaler interface -func (h recipient) MarshalJSON() ([]byte, error) { - data, err := json.Marshal(struct { - Url string `json:"url"` - Method string `json:"method"` - }{ - Url: h.url, - Method: h.method, - }) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - return data, nil -} diff --git a/core/adapters/http/httpRegistration.go b/core/adapters/http/httpRegistration.go deleted file mode 100644 index 4f9ad55e0..000000000 --- a/core/adapters/http/httpRegistration.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/brocaar/lorawan" -) - -type httpRegistration struct { - recipient core.Recipient - devEUI lorawan.EUI64 -} - -// Recipient implements the core.RRegistration inteface -func (r httpRegistration) Recipient() core.Recipient { - return r.recipient -} - -// DevEUI implements the core.RRegistration interface -func (r httpRegistration) DevEUI() lorawan.EUI64 { - return r.devEUI -} diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index 12a6b723f..fa52a926b 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -5,337 +5,93 @@ package http import ( "fmt" - "io" "net/http" "testing" "time" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" - "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" ) -type testRecipient struct { - Recipient - Behavior string -} - -type testRegistration struct { - Recipient testRecipient - DevEUI lorawan.EUI64 -} - -type testPacket struct { - devEUI lorawan.EUI64 - payload string -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (p testPacket) MarshalBinary() ([]byte, error) { - if p.payload == "" { - return nil, errors.New(errors.Structural, "Fake error") +// MockHandler mocks the http.Handler interface +type MockHandler struct { + Failures map[string]error + InURL struct { + Called bool } - - return []byte(p.payload), nil -} - -// DevEUI implements the core.Addressable interface -func (p testPacket) DevEUI() lorawan.EUI64 { - return p.devEUI -} - -// String implements the core.Packet interface -func (p testPacket) String() string { - return p.payload -} - -func TestSend(t *testing.T) { - recipients := []testRecipient{ - testRecipient{ - Recipient: recipient{ - url: "0.0.0.0:3110", - method: "POST", - }, - Behavior: "AlwaysReject", - }, - testRecipient{ - Recipient: recipient{ - url: "0.0.0.0:3111", - method: "POST", - }, - Behavior: "AlwaysAccept", - }, - testRecipient{ - Recipient: recipient{ - url: "0.0.0.0:3112", - method: "POST", - }, - Behavior: "AlwaysReject", - }, - testRecipient{ - Recipient: recipient{ - url: "0.0.0.0:3113", - method: "POST", - }, - Behavior: "AlwaysReject", - }, + OutURL struct { + URL string } - - tests := []struct { - Recipients []testRecipient - Packet testPacket - WantRegistrations []testRegistration - WantPayload string - WantError *string - }{ - { // Send to recipient a valid packet - Recipients: recipients[1:2], // TODO test with a rejection. Need better error handling - Packet: testPacket{ - devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - payload: "payload", - }, - WantRegistrations: nil, - WantPayload: "payload", - WantError: nil, - }, - { // Broadcast a valid packet - Recipients: nil, - Packet: testPacket{ - devEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - payload: "payload", - }, - WantRegistrations: []testRegistration{ - { - Recipient: recipients[1], - DevEUI: lorawan.EUI64([8]byte{0, 0, 0, 0, 1, 2, 3, 4}), - }, - }, - WantPayload: "payload", - WantError: nil, - }, - { // Send to two recipients an invalid packet - Recipients: recipients[:2], - Packet: testPacket{}, - WantRegistrations: nil, - WantPayload: "", - WantError: pointer.String(string(errors.Structural)), - }, - { // Broadcast an invalid packet - Recipients: nil, - Packet: testPacket{}, - WantRegistrations: nil, - WantPayload: "", - WantError: pointer.String(string(errors.Structural)), - }, + InHandle struct { + Called bool } +} - // Logging - ctx := GetLogger(t, "Adapter") - - // Build - adapter, err := NewAdapter("0.0.0.0:3115", toHTTPRecipient(recipients), ctx) - if err != nil { - panic(err) +// NewMockHandler constructs a new MockHandler object +func NewMockHandler() *MockHandler { + return &MockHandler{ + Failures: make(map[string]error), } - var servers []chan string - for _, r := range recipients { - servers = append(servers, genMockServer(r)) - } - <-time.After(200 * time.Millisecond) - - for _, test := range tests { - // Describe - Desc(t, "Sending packet %v to %v", test.Packet, test.Recipients) +} - // Operate - _, err := adapter.Send(test.Packet, toHTTPRecipient(test.Recipients)...) - registrations := getRegistrations(adapter, test.WantRegistrations) - payloads := getPayloads(servers) +// URL implements the http.Handler interface +func (m *MockHandler) URL() string { + m.InURL.Called = true + return m.OutURL.URL +} - // Check - CheckErrors(t, test.WantError, err) - checkPayloads(t, test.WantPayload, payloads) - checkRegistrations(t, test.WantRegistrations, registrations) - } +// Handle implements the http.Handler interface +func (m *MockHandler) Handle(w http.ResponseWriter, req *http.Request) error { + m.InHandle.Called = true + return m.Failures["Handle"] } -func TestSubscribe(t *testing.T) { +func TestBind(t *testing.T) { { - Desc(t, "Subscribe a valid registration") + Desc(t, "Bind a handler and handle a request") // Build - r := mocks.NewMockRegistration() - r.OutRecipient = NewRecipient("0.0.0.0:4777", "PUT") - a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) - serveMux := http.NewServeMux() - serveMux.HandleFunc("/end-devices", func(w http.ResponseWriter, req *http.Request) { - // Check - CheckContentTypes(t, req.Header.Get("Content-Type"), "application/json") - CheckMethods(t, req.Method, r.OutRecipient.(Recipient).Method()) - - buf := make([]byte, req.ContentLength) - n, err := req.Body.Read(buf) - if err == io.EOF { - err = nil - } - CheckErrors(t, nil, err) - wantJSON := []byte(fmt.Sprintf(`{"recipient":{"method":"POST","url":"0.0.0.0:4776"},"registration":%s}`, r.OutMarshalJSON)) - CheckJSONs(t, wantJSON, buf[:n]) - }) - go http.ListenAndServe(r.OutRecipient.(Recipient).URL(), serveMux) - <-time.After(time.Millisecond * 100) - - // Operate - err := a.Subscribe(r) + options := Options{NetAddr: "0.0.0.0:3001", Timeout: time.Millisecond * 50} + a := New( + Components{Ctx: GetLogger(t, "Adapter")}, + options, + ) + cli := http.Client{} + hdl := NewMockHandler() + hdl.OutURL.URL = "/mock" <-time.After(time.Millisecond * 50) - // Check - CheckErrors(t, nil, err) - } - - // -------------------- - - { - Desc(t, "Subscribe an invalid registration -> Invalid recipient") - - // Build - r := mocks.NewMockRegistration() - r.OutRecipient = NewRecipient("0.0.0.0:4777", "PUT") - r.Failures["MarshalJSON"] = errors.New(errors.Structural, "Mock Error") - a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) - - // Operate - err := a.Subscribe(r) - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - // -------------------- - - { - Desc(t, "Subscribe an invalid registration -> MarshalJSON fails") - - // Build - r := mocks.NewMockRegistration() - r.Failures["MarshalJSON"] = errors.New(errors.Structural, "Mock Error") - a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) - // Operate - err := a.Subscribe(r) + a.Bind(hdl) + _, _ = cli.Get(fmt.Sprintf("http://%s%s", options.NetAddr, hdl.URL())) // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) + Check(t, true, hdl.InUrl.Called, "Url() Calls") + Check(t, true, hdl.InHandle.Called, "Handle() Calls") } // -------------------- { - Desc(t, "Subscribe a valid registration | Refused by server") + Desc(t, "Bind a handler and handle a request | handle fails") // Build - r := mocks.NewMockRegistration() - r.OutRecipient = NewRecipient("0.0.0.0:4778", "PUT") - a, _ := NewAdapter("0.0.0.0:4776", nil, GetLogger(t, "Adapter")) - serveMux := http.NewServeMux() - serveMux.HandleFunc("/end-devices", func(w http.ResponseWriter, req *http.Request) { - w.WriteHeader(http.StatusBadRequest) - w.Write(nil) - }) - go http.ListenAndServe(r.OutRecipient.(Recipient).URL(), serveMux) - <-time.After(time.Millisecond * 100) + options := Options{NetAddr: "0.0.0.0:3002", Timeout: time.Millisecond * 50} + a := New( + Components{Ctx: GetLogger(t, "Adapter")}, + options, + ) + cli := http.Client{} + hdl := NewMockHandler() + hdl.OutURL.URL = "/mock" + hdl.Failures["Handle"] = fmt.Errorf("Mock Error") + <-time.After(time.Millisecond * 50) // Operate - err := a.Subscribe(r) - <-time.After(time.Millisecond * 50) + a.Bind(hdl) + _, _ = cli.Get(fmt.Sprintf("http://%s%s", options.NetAddr, hdl.URL())) // Check - CheckErrors(t, pointer.String(string(errors.Operational)), err) - } -} - -// Convert testRecipient to core.Recipient -func toHTTPRecipient(recipients []testRecipient) []core.Recipient { - var https []core.Recipient - for _, r := range recipients { - https = append(https, r.Recipient) - } - return https -} - -// Operate utilities -func getPayloads(chpayloads []chan string) []string { - var got []string - for _, ch := range chpayloads { - select { - case payload := <-ch: - got = append(got, payload) - case <-time.After(50 * time.Millisecond): - } - } - return got -} - -func getRegistrations(adapter *Adapter, want []testRegistration) []core.RRegistration { - var got []core.RRegistration - for range want { - ch := make(chan core.RRegistration) - go func() { - r, an, err := adapter.NextRegistration() - if err != nil { - return - } - an.Ack(nil) - ch <- r.(core.RRegistration) - }() - select { - case r := <-ch: - got = append(got, r) - case <-time.After(50 * time.Millisecond): - } - } - return got -} - -// Build utilities -func genMockServer(recipient core.Recipient) chan string { - chresp := make(chan string) - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) { - if req.Header.Get("Content-Type") != "application/octet-stream" { - w.WriteHeader(http.StatusBadRequest) - w.Write(nil) - return - } - - buf := make([]byte, req.ContentLength) - n, err := req.Body.Read(buf) - if err != nil && err != io.EOF { - w.WriteHeader(http.StatusBadRequest) - w.Write(nil) - return - } - - switch recipient.(testRecipient).Behavior { - case "AlwaysReject": - w.WriteHeader(http.StatusNotFound) - w.Write(nil) - case "AlwaysAccept": - w.Header().Add("Content-Type", "application/octet-stream") - w.WriteHeader(http.StatusOK) - w.Write(buf[:n]) // TODO, should respond another packet, not the same - } - go func() { chresp <- string(buf[:n]) }() - }) - - server := http.Server{ - Addr: recipient.(Recipient).URL(), - Handler: serveMux, + Check(t, true, hdl.InUrl.Called, "Url() Calls") + Check(t, true, hdl.InHandle.Called, "Handle() Calls") } - go server.ListenAndServe() - return chresp } diff --git a/core/adapters/http/regAckNacker.go b/core/adapters/http/regAckNacker.go deleted file mode 100644 index 6164b7474..000000000 --- a/core/adapters/http/regAckNacker.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// An ackNacker for http registrations -type regAckNacker struct { - Chresp chan<- MsgRes // A channel dedicated to send back a response -} - -// Ack implements the core.Acker interface -func (r regAckNacker) Ack(p core.Packet) error { - if r.Chresp == nil { - return nil - } - defer close(r.Chresp) - - select { - case r.Chresp <- MsgRes{StatusCode: http.StatusAccepted}: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(errors.Operational, "No response was given to the acknacker") - } -} - -// Nack implements the core.Nacker interface -func (r regAckNacker) Nack(err error) error { - if r.Chresp == nil { - return nil - } - select { - case r.Chresp <- MsgRes{ - StatusCode: http.StatusConflict, - Content: []byte(errors.Structural), - }: - return nil - case <-time.After(time.Millisecond * 50): - return errors.New(errors.Operational, "No response was given to the acknacker") - } -} diff --git a/core/adapters/http/handlers/statuspage.go b/core/adapters/http/statuspage.go similarity index 88% rename from core/adapters/http/handlers/statuspage.go rename to core/adapters/http/statuspage.go index 716d42b5e..c601f23bb 100644 --- a/core/adapters/http/handlers/statuspage.go +++ b/core/adapters/http/statuspage.go @@ -1,26 +1,23 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package handlers +package http import ( "encoding/json" "net/http" "strings" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/rcrowley/go-metrics" ) -// StatusPage shows statistic on GEt request +// StatusPage shows statistic on GET request // // It listens to requests of the form: [GET] /status // // No body or query param are expected -// -// This handler does not generate any registration. type StatusPage struct{} // URL implements the http.Handler interface @@ -29,7 +26,7 @@ func (p StatusPage) URL() string { } // Handle implements the http.Handler interface -func (p StatusPage) Handle(w http.ResponseWriter, chpkt chan<- PktReq, chreg chan<- RegReq, req *http.Request) error { +func (p StatusPage) Handle(w http.ResponseWriter, req *http.Request) error { // Check the http method if req.Method != "GET" { err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [GET] to request the status") diff --git a/core/adapters/http/handlers/statuspage_test.go b/core/adapters/http/statuspage_test.go similarity index 87% rename from core/adapters/http/handlers/statuspage_test.go rename to core/adapters/http/statuspage_test.go index e186cb09f..ba94f956e 100644 --- a/core/adapters/http/handlers/statuspage_test.go +++ b/core/adapters/http/statuspage_test.go @@ -1,13 +1,12 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package handlers +package http import ( "net/http" "testing" - . "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/utils/stats" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/smartystreets/assertions" @@ -30,14 +29,14 @@ func TestStatusPageHandler(t *testing.T) { r1, _ := http.NewRequest("POST", "/status", nil) r1.RemoteAddr = "127.0.0.1:12345" rw1 := NewResponseWriter() - s.Handle(&rw1, make(chan<- PktReq), make(chan<- RegReq), r1) + s.Handle(&rw1, r1) a.So(rw1.TheStatus, assertions.ShouldEqual, 405) // Initially Empty r3, _ := http.NewRequest("GET", "/status", nil) r3.RemoteAddr = "127.0.0.1:12345" rw3 := NewResponseWriter() - s.Handle(&rw3, make(chan<- PktReq), make(chan<- RegReq), r3) + s.Handle(&rw3, r3) a.So(rw3.TheStatus, assertions.ShouldEqual, 200) a.So(string(rw3.TheBody), assertions.ShouldEqual, "{}") @@ -51,7 +50,7 @@ func TestStatusPageHandler(t *testing.T) { r4, _ := http.NewRequest("GET", "/status", nil) r4.RemoteAddr = "127.0.0.1:12345" rw4 := NewResponseWriter() - s.Handle(&rw4, make(chan<- PktReq), make(chan<- RegReq), r4) + s.Handle(&rw4, r4) a.So(rw4.TheStatus, assertions.ShouldEqual, 200) a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "\"is-a-counter\"") a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "p_50") diff --git a/core/adapters/http/utils.go b/core/adapters/http/utils.go deleted file mode 100644 index 8a721f350..000000000 --- a/core/adapters/http/utils.go +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import "net/http" - -// BadRequest logs the given failure and sends an appropriate response to the client -func BadRequest(w http.ResponseWriter, msg string) { - w.WriteHeader(http.StatusBadRequest) - w.Write([]byte(msg)) -} diff --git a/core/adapters/http/utils_test.go b/core/adapters/http/utils_test.go deleted file mode 100644 index 73761d023..000000000 --- a/core/adapters/http/utils_test.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/smartystreets/assertions" -) - -func TestBadRequest(t *testing.T) { - rw := NewResponseWriter() - BadRequest(&rw, "Test") - a := assertions.New(t) - a.So(rw.TheStatus, assertions.ShouldEqual, 400) - a.So(string(rw.TheBody), assertions.ShouldEqual, "Test") -} From 160d8c9fe0cd2d8f4837281a21720ce26cd4bd96 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 00:59:45 +0100 Subject: [PATCH 1074/2266] [refactor/grpc] Move storage in core package --- core/components/broker/controller.go | 2 +- core/components/handler/devStorage.go | 2 +- core/components/handler/pktStorage.go | 2 +- core/components/router/storage.go | 2 +- core/dutycycle/dutyManager.go | 2 +- {utils => core}/storage/storage.go | 0 {utils => core}/storage/storage_test.go | 1 - 7 files changed, 5 insertions(+), 6 deletions(-) rename {utils => core}/storage/storage.go (100%) rename {utils => core}/storage/storage_test.go (99%) diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 63bd18ec1..1fbcedf1d 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -10,8 +10,8 @@ import ( "reflect" "sync" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - dbutil "github.com/TheThingsNetwork/ttn/utils/storage" ) // NetworkController gives a facade for manipulating the broker databases and devices diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 39d4ae7ca..58c3e0d1d 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -10,8 +10,8 @@ import ( "sync" // // . "github.com/TheThingsNetwork/ttn/core" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - dbutil "github.com/TheThingsNetwork/ttn/utils/storage" ) // DevStorage gives a facade to manipulate the handler devices database diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 964cad3ee..23d8acc5d 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -6,8 +6,8 @@ package handler import ( "fmt" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - dbutil "github.com/TheThingsNetwork/ttn/utils/storage" ) // PktStorage gives a facade to manipulate the handler packets database diff --git a/core/components/router/storage.go b/core/components/router/storage.go index 54eda0986..fa487998a 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -11,8 +11,8 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - dbutil "github.com/TheThingsNetwork/ttn/utils/storage" ) // Storage gives a facade to manipulate the router database diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 0e994ba8a..9f9f3fd38 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -12,8 +12,8 @@ import ( "sync" "time" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - dbutil "github.com/TheThingsNetwork/ttn/utils/storage" ) // DutyManager provides an interface to manipulate and compute gateways duty-cycles. diff --git a/utils/storage/storage.go b/core/storage/storage.go similarity index 100% rename from utils/storage/storage.go rename to core/storage/storage.go diff --git a/utils/storage/storage_test.go b/core/storage/storage_test.go similarity index 99% rename from utils/storage/storage_test.go rename to core/storage/storage_test.go index 8ced02c45..6e32760f8 100644 --- a/utils/storage/storage_test.go +++ b/core/storage/storage_test.go @@ -10,7 +10,6 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) From db43e91e7e2554b6dff7bda7a5844d3c6020111b Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 02:06:44 +0100 Subject: [PATCH 1075/2266] [refactor/grpc] Cover broker controller with tests --- core/components/broker/broker.go | 4 +- core/components/broker/controller.go | 28 +- core/components/broker/controller_test.go | 357 ++++++++++++++++++++++ 3 files changed, 371 insertions(+), 18 deletions(-) create mode 100644 core/components/broker/controller_test.go diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index aefa182e3..b95070ddd 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -105,7 +105,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // It does matter here to use the DevEUI from the entry and not from the packet. // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check + persistence - if err := b.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, fhdr.FCnt); err != nil { + if err := b.UpdateFCnt(mEntry.AppEUI, devAddr, fhdr.FCnt); err != nil { return nil, err } @@ -181,7 +181,7 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubB return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) } - return nil, b.StoreDevice(devEntry{ + return nil, b.StoreDevice(req.DevAddr, devEntry{ HandlerNet: req.HandlerNet, AppEUI: req.AppEUI, DevEUI: devEUI, diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 1fbcedf1d..065f80743 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -18,7 +18,7 @@ import ( type NetworkController interface { LookupDevices(devAddr []byte) ([]devEntry, error) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) - StoreDevice(entry devEntry) error + StoreDevice(devAddr []byte, entry devEntry) error UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error Close() error } @@ -52,7 +52,7 @@ func NewNetworkController(name string) (NetworkController, error) { func (s *controller) LookupDevices(devAddr []byte) ([]devEntry, error) { s.RLock() defer s.RUnlock() - entries, err := s.db.Lookup(s.Devices, append([]byte{0, 0, 0, 0}, devAddr...), &devEntry{}) + entries, err := s.db.Lookup(s.Devices, devAddr, &devEntry{}) if err != nil { return nil, err } @@ -61,9 +61,9 @@ func (s *controller) LookupDevices(devAddr []byte) ([]devEntry, error) { // WholeCounter implements the broker.NetworkController interface func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { - upperSup := uint32(math.Pow(2, 16)) - diff := devCnt - (entryCnt % upperSup) - var offset uint32 + upperSup := int(math.Pow(2, 16)) + diff := int(devCnt) - (int(entryCnt) % upperSup) + var offset int if diff >= 0 { offset = diff } else { @@ -72,14 +72,14 @@ func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error if offset > upperSup/4 { return 0, errors.New(errors.Structural, "Gap too big, counter is errored") } - return entryCnt + offset, nil + return entryCnt + uint32(offset), nil } // UpdateFCnt implements the broker.NetworkController interface -func (s *controller) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { +func (s *controller) UpdateFCnt(appEUI []byte, devAddr []byte, fcnt uint32) error { s.Lock() defer s.Unlock() - itf, err := s.db.Lookup(s.Devices, devEUI, &devEntry{}) + itf, err := s.db.Lookup(s.Devices, devAddr, &devEntry{}) if err != nil { return err } @@ -95,14 +95,14 @@ func (s *controller) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error newEntries = append(newEntries, entry) } - return s.db.Replace(s.Devices, devEUI, newEntries) + return s.db.Replace(s.Devices, devAddr, newEntries) } // StoreDevice implements the broker.NetworkController interface -func (s *controller) StoreDevice(entry devEntry) error { +func (s *controller) StoreDevice(devAddr []byte, entry devEntry) error { s.Lock() defer s.Unlock() - return s.db.Store(s.Devices, entry.DevEUI, []dbutil.Entry{&entry}) + return s.db.Store(s.Devices, devAddr, []dbutil.Entry{&entry}) } // Close implements the broker.NetworkController interface @@ -136,10 +136,6 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { return errors.New(errors.Structural, err) } - var handler []byte - if err := binary.Read(buf, binary.BigEndian, &handler); err != nil { - return errors.New(errors.Structural, err) - } - e.HandlerNet = string(handler) + e.HandlerNet = string(buf.Next(buf.Len())) return nil } diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go new file mode 100644 index 000000000..f78959fe8 --- /dev/null +++ b/core/components/broker/controller_test.go @@ -0,0 +1,357 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "os" + "path" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const NetworkControllerDB = "TestBrokerNetworkController.db" + +func TestNetworkControllerDevice(t *testing.T) { + NetworkControllerDB := path.Join(os.TempDir(), NetworkControllerDB) + defer func() { + os.Remove(NetworkControllerDB) + }() + + // ------------------- + + { + Desc(t, "Create a new NetworkController") + db, err := NewNetworkController(NetworkControllerDB) + CheckErrors(t, nil, err) + err = db.Close() + CheckErrors(t, nil, err) + } + + // ------------------- + + { + Desc(t, "Store then lookup a registration") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + devAddr := []byte{1, 2, 3, 4} + entry := devEntry{ + HandlerNet: "url", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, + } + + // Operate + err := db.StoreDevice(devAddr, entry) + FatalUnless(t, err) + entries, err := db.LookupDevices(devAddr) + + // Expect + want := []devEntry{entry} + + // Check + CheckErrors(t, nil, err) + Check(t, want, entries, "DevEntries") + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Store entries with same DevAddr") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + devAddr := []byte{1, 2, 3, 5} + entry1 := devEntry{ + HandlerNet: "url", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, + } + entry2 := devEntry{ + HandlerNet: "url", + AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 42, + } + + // Operate + err := db.StoreDevice(devAddr, entry1) + FatalUnless(t, err) + err = db.StoreDevice(devAddr, entry2) + FatalUnless(t, err) + entries, err := db.LookupDevices(devAddr) + + // Expectations + want := []devEntry{entry1, entry2} + + // Check + CheckErrors(t, nil, err) + Check(t, want, entries, "DevEntries") + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Lookup non-existing entry") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + devAddr := []byte{0, 0, 0, 1} + + // Operate + entries, err := db.LookupDevices(devAddr) + + // Expect + var want []devEntry + + // Checks + CheckErrors(t, ErrNotFound, err) + Check(t, want, entries, "DevEntries") + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Store on a closed database") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + _ = db.Close() + devAddr := []byte{1, 0, 0, 2} + entry := devEntry{ + HandlerNet: "url", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, + } + + // Operate + err := db.StoreDevice(devAddr, entry) + + // Checks + CheckErrors(t, ErrOperational, err) + } + + // ------------------- + + { + Desc(t, "Lookup on a closed database") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + _ = db.Close() + devAddr := []byte{1, 2, 3, 4} + + // Operate + entries, err := db.LookupDevices(devAddr) + + // Expect + var want []devEntry + + // Checks + CheckErrors(t, ErrOperational, err) + Check(t, want, entries, "DevEntries") + } + + // ------------------- + + { + Desc(t, "Update counter up of an entry -> one device") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + devAddr := []byte{1, 0, 0, 4} + entry := devEntry{ + HandlerNet: "url", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, + } + + // Operate + err := db.StoreDevice(devAddr, entry) + FatalUnless(t, err) + err1 := db.UpdateFCnt(entry.AppEUI, devAddr, 42) + entries, err2 := db.LookupDevices(devAddr) + + // Expectations + want := []devEntry{entry} + want[0].FCntUp = 42 + + // Check + CheckErrors(t, nil, err1) + CheckErrors(t, nil, err2) + Check(t, want, entries, "DevEntries") + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Update counter -> fail to lookup") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + devAddr := []byte{14, 14, 14, 14} + appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} + + // Operate + err := db.UpdateFCnt(appEUI, devAddr, 14) + + // Checks + CheckErrors(t, ErrNotFound, err) + _ = db.Close() + } + + // ------------------- + + { + Desc(t, "Update counter several entries") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + devAddr := []byte{8, 8, 8, 8} + entry1 := devEntry{ + HandlerNet: "url", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, + } + entry2 := devEntry{ + HandlerNet: "url", + AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 42, + } + + // Operate + err := db.StoreDevice(devAddr, entry1) + FatalUnless(t, err) + err = db.StoreDevice(devAddr, entry2) + FatalUnless(t, err) + err1 := db.UpdateFCnt(entry2.AppEUI, devAddr, 8) + entries, err2 := db.LookupDevices(devAddr) + + // Expectations + want := []devEntry{entry1, entry2} + want[1].FCntUp = 8 + + // Check + CheckErrors(t, nil, err1) + CheckErrors(t, nil, err2) + Check(t, want, entries, "DevEntries") + _ = db.Close() + } + + // -------------------- + + { + Desc(t, "Test counters, both < 65535") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + wholeCnt := uint32(13) + cnt16 := wholeCnt + 1 + + // Operate + cnt32, err := db.WholeCounter(cnt16, wholeCnt) + + // Check + CheckErrors(t, nil, err) + Check(t, wholeCnt+1, cnt32, "Counters") + + _ = db.Close() + } + + // -------------------- + + { + Desc(t, "Test counters, devCnt < wholeCnt, | | > max_gap") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + wholeCnt := uint32(14) + cnt16 := wholeCnt - 1 + + // Operate + _, err := db.WholeCounter(cnt16, wholeCnt) + + // Check + CheckErrors(t, ErrStructural, err) + + _ = db.Close() + } + + // -------------------- + + { + Desc(t, "Test counters, whole > 65535, | | < max_gap") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + wholeCnt := uint32(3824624235) + cnt16 := uint32(wholeCnt%65536 + 2) + + // Operate + cnt32, err := db.WholeCounter(cnt16, wholeCnt) + + // Check + CheckErrors(t, nil, err) + Check(t, wholeCnt+2, cnt32, "Counters") + + _ = db.Close() + } + + // -------------------- + + { + Desc(t, "Test counters, whole > 65535, | | > max_gap") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + wholeCnt := uint32(3824624235) + cnt16 := uint32(wholeCnt%65536 + 45000) + + // Operate + _, err := db.WholeCounter(cnt16, wholeCnt) + + // Check + CheckErrors(t, ErrStructural, err) + + _ = db.Close() + } + + // -------------------- + + { + Desc(t, "Test counters, whole > 65535, | | < max_gap, via inf") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + wholeCnt := uint32(65535) + cnt16 := uint32(2) + + // Operate + cnt32, err := db.WholeCounter(cnt16, wholeCnt) + + // Check + CheckErrors(t, nil, err) + Check(t, wholeCnt+3, cnt32, "Counters") + + _ = db.Close() + } +} From 9014b7524b76bdb22484470bde34338c524aaf43 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 10:34:17 +0100 Subject: [PATCH 1076/2266] [refactor/grpc] Introduce Dialer interface in broker to allow mock testing --- core/components/broker/broker.go | 19 +++--- core/components/broker/controller.go | 15 +++-- core/components/broker/controller_test.go | 70 +++++++++++------------ core/components/broker/dialer.go | 60 +++++++++++++++++++ 4 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 core/components/broker/dialer.go diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index b95070ddd..24e825431 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -7,7 +7,6 @@ import ( "fmt" "regexp" "strings" - "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -15,7 +14,6 @@ import ( "github.com/apex/log" "github.com/brocaar/lorawan" "golang.org/x/net/context" - "google.golang.org/grpc" ) // component implements the core.Component interface @@ -90,7 +88,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c if ok { mEntry = &entry stats.MarkMeter("broker.uplink.handler_lookup.mic_match") - ctx.WithField("handler", entry.HandlerNet).Debug("MIC check succeeded") + ctx.WithField("handler", string(entry.Dialer.MarshalSafely())).Debug("MIC check succeeded") break // We stop at the first valid check ... } } @@ -110,12 +108,11 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } // Then we forward the packet to the handler and wait for the response - conn, err := grpc.Dial(mEntry.HandlerNet, grpc.WithInsecure(), grpc.WithTimeout(time.Second*2)) + handler, closer, err := mEntry.Dialer.Dial() if err != nil { return nil, err } - defer conn.Close() - handler := core.NewHandlerClient(conn) + defer closer.Close() resp, err := handler.HandleDataUp(context.Background(), &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, DevEUI: mEntry.DevEUI, @@ -182,10 +179,10 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubB } return nil, b.StoreDevice(req.DevAddr, devEntry{ - HandlerNet: req.HandlerNet, - AppEUI: req.AppEUI, - DevEUI: devEUI, - NwkSKey: nwkSKey, - FCntUp: 0, + Dialer: NewDialer([]byte(req.HandlerNet)), + AppEUI: req.AppEUI, + DevEUI: devEUI, + NwkSKey: nwkSKey, + FCntUp: 0, }) } diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 065f80743..63414858f 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -24,11 +24,11 @@ type NetworkController interface { } type devEntry struct { - HandlerNet string - AppEUI []byte - DevEUI []byte - NwkSKey [16]byte - FCntUp uint32 + Dialer Dialer + AppEUI []byte + DevEUI []byte + NwkSKey [16]byte + FCntUp uint32 } type controller struct { @@ -120,7 +120,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { if len(buf.Bytes()) != 36 { return nil, errors.New(errors.Structural, "Device entry was invalid. Cannot Marshal") } - binary.Write(buf, binary.BigEndian, []byte(e.HandlerNet)) + binary.Write(buf, binary.BigEndian, e.Dialer.MarshalSafely()) return buf.Bytes(), nil } @@ -135,7 +135,6 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { if err := binary.Read(buf, binary.BigEndian, &e.FCntUp); err != nil { return errors.New(errors.Structural, err) } - - e.HandlerNet = string(buf.Next(buf.Len())) + e.Dialer = NewDialer(buf.Next(buf.Len())) return nil } diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index f78959fe8..34302c593 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -38,11 +38,11 @@ func TestNetworkControllerDevice(t *testing.T) { db, _ := NewNetworkController(NetworkControllerDB) devAddr := []byte{1, 2, 3, 4} entry := devEntry{ - HandlerNet: "url", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, } // Operate @@ -68,18 +68,18 @@ func TestNetworkControllerDevice(t *testing.T) { db, _ := NewNetworkController(NetworkControllerDB) devAddr := []byte{1, 2, 3, 5} entry1 := devEntry{ - HandlerNet: "url", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, } entry2 := devEntry{ - HandlerNet: "url", - AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 42, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 42, } // Operate @@ -129,11 +129,11 @@ func TestNetworkControllerDevice(t *testing.T) { _ = db.Close() devAddr := []byte{1, 0, 0, 2} entry := devEntry{ - HandlerNet: "url", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, } // Operate @@ -173,11 +173,11 @@ func TestNetworkControllerDevice(t *testing.T) { db, _ := NewNetworkController(NetworkControllerDB) devAddr := []byte{1, 0, 0, 4} entry := devEntry{ - HandlerNet: "url", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, } // Operate @@ -224,18 +224,18 @@ func TestNetworkControllerDevice(t *testing.T) { db, _ := NewNetworkController(NetworkControllerDB) devAddr := []byte{8, 8, 8, 8} entry1 := devEntry{ - HandlerNet: "url", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 14, } entry2 := devEntry{ - HandlerNet: "url", - AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 42, + Dialer: NewDialer([]byte("url")), + AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 42, } // Operate diff --git a/core/components/broker/dialer.go b/core/components/broker/dialer.go new file mode 100644 index 000000000..2e5de7253 --- /dev/null +++ b/core/components/broker/dialer.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + "google.golang.org/grpc" +) + +// Dialer abstracts the connection to grpc, or anything else +type Dialer interface { + MarshalSafely() []byte + Dial() (core.HandlerClient, Closer, error) // Dial actually attempts to dial a connection +} + +// Closer is returned by a Dialer to give a hand on closing the dialed connection +type Closer interface { + Close() error +} + +// dialer implements the Dialer interface +type dialer struct { + NetAddr string +} + +// closer implements the Closer interface +type closer struct { + Conn *grpc.ClientConn +} + +// Close implements the Closer interface +func (c closer) Close() error { + if err := c.Conn.Close(); err != nil { + return errors.New(errors.Operational, err) + } + return nil +} + +// NewDialer constructs a new dialer from a given net address +func NewDialer(netAddr []byte) Dialer { + return &dialer{NetAddr: string(netAddr)} +} + +// Dial implements the Dialer interface +func (d dialer) Dial() (core.HandlerClient, Closer, error) { + conn, err := grpc.Dial(d.NetAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*2)) + if err != nil { + return nil, nil, errors.New(errors.Operational, err) + } + return core.NewHandlerClient(conn), closer{Conn: conn}, nil +} + +// MarshalSafely implements the Dialer interface +func (d dialer) MarshalSafely() []byte { + return []byte(d.NetAddr) +} From 77a9e981d7386992c49c4fcc823341800a3a327f Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 13:13:15 +0100 Subject: [PATCH 1077/2266] [refactor/grpc] Add tests for HandleDataUp and fix accordingly --- core/components/broker/broker.go | 64 +- core/components/broker/broker_test.go | 773 ++++++++++++++++++++++ core/components/broker/controller.go | 6 +- core/components/broker/controller_test.go | 7 +- core/components/broker/mocks_test.go | 141 ++++ 5 files changed, 962 insertions(+), 29 deletions(-) create mode 100644 core/components/broker/broker_test.go create mode 100644 core/components/broker/mocks_test.go diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 24e825431..00fd00e38 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -6,7 +6,6 @@ package broker import ( "fmt" "regexp" - "strings" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -16,40 +15,50 @@ import ( "golang.org/x/net/context" ) -// component implements the core.Component interface +// component implements the core.BrokerServer interface type component struct { - NetworkController - ctx log.Interface + Components } +// Components defines a structure to make the instantiation easier to read +type Components struct { + NetworkController NetworkController + Ctx log.Interface +} + +// Options defines a structure to make the instantiation easier to read +type Options struct{} + // New construct a new Broker component -func New(controller NetworkController, ctx log.Interface) core.BrokerServer { - return component{NetworkController: controller, ctx: ctx} +func New(c Components, o Options) core.BrokerServer { + return component{Components: c} } -// HandleData implements the core.RouterClient interface +// HandleData implements the core.BrokerServer interface func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*core.DataBrokerRes, error) { // Get some logs / analytics stats.MarkMeter("broker.uplink.in") - b.ctx.Debug("Handling uplink packet") + b.Ctx.Debug("Handling uplink packet") // Validate incoming data uplinkPayload, err := core.NewLoRaWANData(req.Payload, true) if err != nil { + b.Ctx.WithError(err).Debug("Unable to interpret LoRaWAN payload") return nil, errors.New(errors.Structural, err) } devAddr := req.Payload.MACPayload.FHDR.DevAddr // No nil ref, ensured by NewLoRaWANData() - ctx := b.ctx.WithField("DevAddr", devAddr) + ctx := b.Ctx.WithField("DevAddr", devAddr) // Check whether we should handle it - entries, err := b.LookupDevices(devAddr) + entries, err := b.NetworkController.LookupDevices(devAddr) if err != nil { + ctx = ctx.WithError(err) switch err.(errors.Failure).Nature { case errors.NotFound: stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") ctx.Debug("Uplink device not found") default: - b.ctx.Warn("Database lookup failed") + ctx.Warn("Database lookup failed") } return nil, err } @@ -71,17 +80,17 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // Check with 16-bits counters fhdr.FCnt = fcnt16 - ok, err := uplinkPayload.ValidateMIC(key) + fcnt32, err := b.NetworkController.WholeCounter(fcnt16, entry.FCntUp) if err != nil { continue } - if !ok && entry.FCntUp > 65535 { // Check with 32-bits counter - fcnt32, err := b.WholeCounter(fcnt16, entry.FCntUp) - if err != nil { - continue - } - fhdr.FCnt = fcnt32 + ok, err := uplinkPayload.ValidateMIC(key) + if err != nil { + continue + } + fhdr.FCnt = fcnt32 + if !ok { // Check with 32-bits counter ok, err = uplinkPayload.ValidateMIC(key) } @@ -96,20 +105,22 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c if mEntry == nil { stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") err := errors.New(errors.NotFound, "MIC check returned no matches") - ctx.Debug(err.Error()) + ctx.WithError(err).Debug("Unable to handle uplink") return nil, err } // It does matter here to use the DevEUI from the entry and not from the packet. // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check + persistence - if err := b.UpdateFCnt(mEntry.AppEUI, devAddr, fhdr.FCnt); err != nil { + if err := b.NetworkController.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, devAddr, fhdr.FCnt); err != nil { + ctx.WithError(err).Debug("Unable to update Frame Counter") return nil, err } // Then we forward the packet to the handler and wait for the response handler, closer, err := mEntry.Dialer.Dial() if err != nil { + ctx.WithError(err).Debug("Unable to dial handler") return nil, err } defer closer.Close() @@ -122,8 +133,9 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c Metadata: req.Metadata, }) - if err != nil && !strings.Contains(err.Error(), string(errors.NotFound)) { // FIXME Find better way to analyze error + if err != nil { stats.MarkMeter("broker.uplink.bad_handler_response") + ctx.WithError(err).Debug("Unexpected answer from handler") return nil, errors.New(errors.Operational, err) } stats.MarkMeter("broker.uplink.ok") @@ -131,16 +143,20 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // No response, we stop here and propagate the "no answer". // In case of confirmed data, the handler is in charge of creating the confirmation if resp == nil { + ctx.Debug("Packet successfully sent. There's no downlink.") return nil, nil } // If a response was sent, i.e. a downlink data, we need to compute the right MIC + ctx.Debug("Packet successfully sent. Handling downlink response") downlinkPayload, err := core.NewLoRaWANData(resp.Payload, false) if err != nil { + ctx.WithError(err).Debug("Unable to interpret LoRaWAN downlink datagram") return nil, errors.New(errors.Structural, err) } stats.MarkMeter("broker.downlink.in") if err := downlinkPayload.SetMIC(lorawan.AES128Key(mEntry.NwkSKey)); err != nil { + ctx.WithError(err).Debug("Unable to set MIC") return nil, errors.New(errors.Structural, "Unable to set response MIC") } resp.Payload.MIC = downlinkPayload.MIC[:] @@ -154,7 +170,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // Register implements the core.BrokerServer interface func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubBrokerReq) (*core.ABPSubBrokerRes, error) { - b.ctx.Debug("Handling personalized subscription") + b.Ctx.Debug("Handling personalized subscription") // Ensure the entry is valid if len(req.AppEUI) != 8 { @@ -178,7 +194,9 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubB return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) } - return nil, b.StoreDevice(req.DevAddr, devEntry{ + b.Ctx.Debug("Subscription looks valid") + + return nil, b.NetworkController.StoreDevice(req.DevAddr, devEntry{ Dialer: NewDialer([]byte(req.HandlerNet)), AppEUI: req.AppEUI, DevEUI: devEUI, diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go new file mode 100644 index 000000000..66cc020cc --- /dev/null +++ b/core/components/broker/broker_test.go @@ -0,0 +1,773 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "fmt" + "testing" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + "golang.org/x/net/context" +) + +func TestHandleData(t *testing.T) { + { + Desc(t, "Invalid LoRaWAN payload") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: nil, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrStructural + var wantDataUp *core.DataUpHandlerReq + var wantRes *core.DataBrokerRes + var wantFCnt uint32 + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Fail to lookup device -> Operational") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.Failures["LookupDevices"] = errors.New(errors.Operational, "Mock Error") + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantDataUp *core.DataUpHandlerReq + var wantRes *core.DataBrokerRes + var wantFCnt uint32 + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Fail to lookup device -> Not Found") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.Failures["LookupDevices"] = errors.New(errors.NotFound, "Mock Error") + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{4, 3, 2, 1}, + }, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrNotFound + var wantDataUp *core.DataUpHandlerReq + var wantRes *core.DataBrokerRes + var wantFCnt uint32 + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Valid uplink | Two db entries, second MIC valid") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 2 + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + dl2 := NewMockDialer() + dl2.OutDial.Client = hl + dl2.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl2, + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + FCntUp: 1, + }, + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 1, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[1].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + + // Expect + var wantErr *string + var wantDataUp = &core.DataUpHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + AppEUI: nc.OutLookupDevices.Entries[1].AppEUI, + DevEUI: nc.OutLookupDevices.Entries[1].DevEUI, + FCnt: req.Payload.MACPayload.FHDR.FCnt, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + } + var wantRes *core.DataBrokerRes + var wantFCnt uint32 = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry, FCnt invalid") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.Failures["WholeCounter"] = errors.New(errors.Structural, "Mock Error") + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 1, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 44567, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + + // Expect + var wantErr = ErrNotFound + var wantDataUp *core.DataUpHandlerReq + var wantRes *core.DataBrokerRes + var wantFCnt uint32 + var wantDialer bool + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry, FCnt above 16-bits") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 112534 + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 112500, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + req.Payload.MACPayload.FHDR.FCnt %= 65536 + + // Expect + var wantErr *string + var wantDataUp = &core.DataUpHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, + DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + FCnt: nc.OutWholeCounter.FCnt, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + } + var wantRes *core.DataBrokerRes + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry, Dial failed") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 14 + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 10, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + req.Payload.MACPayload.FHDR.FCnt %= 65536 + + // Expect + var wantErr = ErrOperational + var wantDataUp *core.DataUpHandlerReq + var wantRes *core.DataBrokerRes + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry, HandleDataUp failed") + + // Build + hl := mocks.NewHandlerClient() + hl.Failures["HandleDataUp"] = fmt.Errorf("Mock Error") + + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 14 + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 10, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + req.Payload.MACPayload.FHDR.FCnt %= 65536 + + // Expect + var wantErr = ErrOperational + var wantDataUp = &core.DataUpHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, + DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + FCnt: nc.OutWholeCounter.FCnt, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + } + var wantRes *core.DataBrokerRes + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry | One valid downlink") + + // Build + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 14 + + hl := mocks.NewHandlerClient() + hl.OutHandleDataUp.Res = &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, + }, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 10, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + req.Payload.MACPayload.FHDR.FCnt %= 65536 + + // Expect + var wantErr *string + var wantDataUp = &core.DataUpHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, + DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + FCnt: nc.OutWholeCounter.FCnt, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + } + var wantRes = &core.DataBrokerRes{ + Payload: hl.OutHandleDataUp.Res.Payload, + Metadata: hl.OutHandleDataUp.Res.Metadata, + } + payloadDown, err := core.NewLoRaWANData(req.Payload, false) + FatalUnless(t, err) + err = payloadDown.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + wantRes.Payload.MIC = payloadDown.MIC[:] + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry, UpdateFcnt failed") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 14 + nc.Failures["UpdateFCnt"] = errors.New(errors.Operational, "Mock Error") + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 10, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + req.Payload.MACPayload.FHDR.FCnt %= 65536 + + // Expect + var wantErr = ErrOperational + var wantDataUp *core.DataUpHandlerReq + var wantRes *core.DataBrokerRes + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer bool + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid uplink | One entry | Invalid downlink") + + // Build + nc := NewMockNetworkController() + nc.OutWholeCounter.FCnt = 14 + + hl := mocks.NewHandlerClient() + hl.OutHandleDataUp.Res = &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: nil, + MIC: []byte{0, 0, 0, 0}, + }, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutLookupDevices.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 10, + }, + } + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: nc.OutWholeCounter.FCnt, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + req.Payload.MACPayload.FHDR.FCnt %= 65536 + + // Expect + var wantErr = ErrStructural + var wantDataUp = &core.DataUpHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, + DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + FCnt: nc.OutWholeCounter.FCnt, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + } + var wantRes *core.DataBrokerRes + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + +} + +// SubscribePersonalized(bctx context.Context, req *core.ABPSubBrokerReq) (*core.ABPSubBrokerRes, error) diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 63414858f..b03c411d5 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -19,7 +19,7 @@ type NetworkController interface { LookupDevices(devAddr []byte) ([]devEntry, error) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) StoreDevice(devAddr []byte, entry devEntry) error - UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error + UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error Close() error } @@ -76,7 +76,7 @@ func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error } // UpdateFCnt implements the broker.NetworkController interface -func (s *controller) UpdateFCnt(appEUI []byte, devAddr []byte, fcnt uint32) error { +func (s *controller) UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error { s.Lock() defer s.Unlock() itf, err := s.db.Lookup(s.Devices, devAddr, &devEntry{}) @@ -89,7 +89,7 @@ func (s *controller) UpdateFCnt(appEUI []byte, devAddr []byte, fcnt uint32) erro for _, e := range entries { entry := new(devEntry) *entry = e - if reflect.DeepEqual(entry.AppEUI, appEUI) { + if reflect.DeepEqual(entry.AppEUI, appEUI) && reflect.DeepEqual(entry.DevEUI, devEUI) { entry.FCntUp = fcnt } newEntries = append(newEntries, entry) diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index 34302c593..78215785d 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -183,7 +183,7 @@ func TestNetworkControllerDevice(t *testing.T) { // Operate err := db.StoreDevice(devAddr, entry) FatalUnless(t, err) - err1 := db.UpdateFCnt(entry.AppEUI, devAddr, 42) + err1 := db.UpdateFCnt(entry.AppEUI, entry.DevEUI, devAddr, 42) entries, err2 := db.LookupDevices(devAddr) // Expectations @@ -206,9 +206,10 @@ func TestNetworkControllerDevice(t *testing.T) { db, _ := NewNetworkController(NetworkControllerDB) devAddr := []byte{14, 14, 14, 14} appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} // Operate - err := db.UpdateFCnt(appEUI, devAddr, 14) + err := db.UpdateFCnt(appEUI, devEUI, devAddr, 14) // Checks CheckErrors(t, ErrNotFound, err) @@ -243,7 +244,7 @@ func TestNetworkControllerDevice(t *testing.T) { FatalUnless(t, err) err = db.StoreDevice(devAddr, entry2) FatalUnless(t, err) - err1 := db.UpdateFCnt(entry2.AppEUI, devAddr, 8) + err1 := db.UpdateFCnt(entry2.AppEUI, entry2.DevEUI, devAddr, 8) entries, err2 := db.LookupDevices(devAddr) // Expectations diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go new file mode 100644 index 000000000..45970bfcc --- /dev/null +++ b/core/components/broker/mocks_test.go @@ -0,0 +1,141 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "github.com/TheThingsNetwork/ttn/core" +) + +// NOTE All the code below could be generated + +// MockDialer mocks the Dialer interface +type MockDialer struct { + Failures map[string]error + InMarshalSafely struct { + Called bool + } + OutMarshalSafely struct { + Data []byte + } + InDial struct { + Called bool + } + OutDial struct { + Client core.HandlerClient + Closer Closer + } +} + +// NewMockDialer instantiates a new MockDialer object +func NewMockDialer() *MockDialer { + return &MockDialer{ + Failures: make(map[string]error), + } +} + +// MarshalSafely implements the Dialer interface +func (m *MockDialer) MarshalSafely() []byte { + m.InMarshalSafely.Called = true + return m.OutMarshalSafely.Data +} + +// Dial implements the Dialer interface +func (m *MockDialer) Dial() (core.HandlerClient, Closer, error) { + m.InDial.Called = true + return m.OutDial.Client, m.OutDial.Closer, m.Failures["Dial"] +} + +// MockCloser mocks the Closer interface +type MockCloser struct { + Failures map[string]error + InClose struct { + Called bool + } +} + +// NewMockCloser instantiates a new MockCloser object +func NewMockCloser() *MockCloser { + return &MockCloser{ + Failures: make(map[string]error), + } +} + +// Close implements the Closer interface +func (m *MockCloser) Close() error { + m.InClose.Called = true + return m.Failures["Close"] +} + +// MockNetworkController mocks the NetworkController interface +type MockNetworkController struct { + Failures map[string]error + InLookupDevices struct { + DevAddr []byte + } + OutLookupDevices struct { + Entries []devEntry + } + InWholeCounter struct { + DevCnt uint32 + EntryCnt uint32 + } + OutWholeCounter struct { + FCnt uint32 + } + InStoreDevice struct { + DevAddr []byte + Entry devEntry + } + InUpdateFcnt struct { + AppEUI []byte + DevEUI []byte + DevAddr []byte + FCnt uint32 + } + InClose struct { + Called bool + } +} + +// NewMockNetworkController constructs a new MockNetworkController object +func NewMockNetworkController() *MockNetworkController { + return &MockNetworkController{ + Failures: make(map[string]error), + } +} + +// LookupDevices implements the NetworkController interface +func (m *MockNetworkController) LookupDevices(devAddr []byte) ([]devEntry, error) { + m.InLookupDevices.DevAddr = devAddr + return m.OutLookupDevices.Entries, m.Failures["LookupDevices"] +} + +// WholeCounter implements the NetworkController interface +func (m *MockNetworkController) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { + m.InWholeCounter.DevCnt = devCnt + m.InWholeCounter.EntryCnt = entryCnt + return m.OutWholeCounter.FCnt, m.Failures["WholeCounter"] +} + +// StoreDevice implements the NetworkController interface +func (m *MockNetworkController) StoreDevice(devAddr []byte, entry devEntry) error { + m.InStoreDevice.DevAddr = devAddr + m.InStoreDevice.Entry = entry + return m.Failures["StoreDevice"] +} + +// UpdateFcnt implements the NetworkController interface +func (m *MockNetworkController) UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error { + m.InUpdateFcnt.AppEUI = appEUI + m.InUpdateFcnt.DevEUI = devEUI + m.InUpdateFcnt.DevAddr = devAddr + m.InUpdateFcnt.FCnt = fcnt + return m.Failures["UpdateFCnt"] +} + +// Close implements the NetworkController interface +func (m *MockNetworkController) Close() error { + m.InClose.Called = true + return m.Failures["Close"] +} From 57ab0eaf3d2450ba2da09c5ce3a5f6daf20dc79f Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 13:34:58 +0100 Subject: [PATCH 1078/2266] [refactor/grpc] Finalize broker tests -> SubscribeABP + Dialer --- core/components/broker/broker_test.go | 248 +++++++++++++++++++++++++- 1 file changed, 247 insertions(+), 1 deletion(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 66cc020cc..68f8771bb 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -5,6 +5,7 @@ package broker import ( "fmt" + "net" "testing" "github.com/TheThingsNetwork/ttn/core" @@ -13,6 +14,7 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" "golang.org/x/net/context" + //"google.golang.org/grpc" ) func TestHandleData(t *testing.T) { @@ -767,7 +769,251 @@ func TestHandleData(t *testing.T) { Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } +} + +func TestSubscribePerso(t *testing.T) { + { + Desc(t, "Valid Entry #1") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "87.4352.3:4333", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr *string + var wantRes *core.ABPSubBrokerRes + var wantEntry = devEntry{ + Dialer: NewDialer([]byte(req.HandlerNet)), + AppEUI: req.AppEUI, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } + + // -------------------- + + { + Desc(t, "Valid Entry #2") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "ttn.golang.org:4400", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr *string + var wantRes *core.ABPSubBrokerRes + var wantEntry = devEntry{ + Dialer: NewDialer([]byte(req.HandlerNet)), + AppEUI: req.AppEUI, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } + + // -------------------- + + { + Desc(t, "Valid Entry #1") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "87.4352.3:4333", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr *string + var wantRes *core.ABPSubBrokerRes + var wantEntry = devEntry{ + Dialer: NewDialer([]byte(req.HandlerNet)), + AppEUI: req.AppEUI, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } + + // -------------------- + + { + Desc(t, "Invalid entry -> Bad HandlerNet") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "localhost", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.ABPSubBrokerRes + var wantEntry devEntry + + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } + + // -------------------- + + { + Desc(t, "Invalid entry -> Bad AppEUI") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "87.4352.3:4333", + AppEUI: []byte{1, 2, 3, 4, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.ABPSubBrokerRes + var wantEntry devEntry + + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } + + // -------------------- + + { + Desc(t, "Invalid entry -> Bad DevAddr") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "87.4352.3:4333", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.ABPSubBrokerRes + var wantEntry devEntry + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } + // -------------------- + + { + Desc(t, "Invalid entry -> Bad NwkSKey") + + // Build + nc := NewMockNetworkController() + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.ABPSubBrokerReq{ + HandlerNet: "87.4352.3:4333", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{1, 2, 3, 4}, + NwkSKey: nil, + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.ABPSubBrokerRes + var wantEntry devEntry + + // Operate + res, err := br.SubscribePersonalized(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Broker ABP Responses") + Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") + } } -// SubscribePersonalized(bctx context.Context, req *core.ABPSubBrokerReq) (*core.ABPSubBrokerRes, error) +func TestDialerCloser(t *testing.T) { + { + Desc(t, "Dial on a valid address, server is listening") + + // Build + addr := "0.0.0.0:3300" + conn, err := net.Listen("tcp", addr) + FatalUnless(t, err) + defer conn.Close() + + // Operate & Check + dl := NewDialer([]byte(addr)) + _, cl, errDial := dl.Dial() + CheckErrors(t, nil, errDial) + errClose := cl.Close() + CheckErrors(t, nil, errClose) + } + + // -------------------- + + { + Desc(t, "Dial an invalid address") + + // Build & Operate & Check + dl := NewDialer([]byte("")) + _, _, errDial := dl.Dial() + CheckErrors(t, ErrOperational, errDial) + } +} From 0d882aac50554725a9f1621ecf072192b54a64e2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 14:51:07 +0100 Subject: [PATCH 1079/2266] [refactor/grpc] Add and fix tests for handler storages --- core/components/handler/devStorage.go | 27 +--- core/components/handler/devStorage_test.go | 171 +++++++++++++++++++++ core/components/handler/handler.go | 8 +- core/components/handler/pktStorage.go | 32 ++-- core/components/handler/pktStorage_test.go | 140 +++++++++++++++++ 5 files changed, 338 insertions(+), 40 deletions(-) create mode 100644 core/components/handler/devStorage_test.go create mode 100644 core/components/handler/pktStorage_test.go diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 58c3e0d1d..8ab69ce85 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -8,8 +8,7 @@ import ( "encoding/binary" "fmt" "sync" - // - // . "github.com/TheThingsNetwork/ttn/core" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -31,10 +30,6 @@ type devEntry struct { FCntDown uint32 } -//type appEntry struct { -// AppKey lorawan.AES128Key -//} - type devStorage struct { sync.RWMutex db dbutil.Interface @@ -121,22 +116,8 @@ func (e devEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devEntry) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) - binary.Read(buf, binary.BigEndian, e.DevAddr) - binary.Read(buf, binary.BigEndian, e.AppSKey) - binary.Read(buf, binary.BigEndian, e.NwkSKey) + binary.Read(buf, binary.BigEndian, &e.DevAddr) + binary.Read(buf, binary.BigEndian, &e.AppSKey) + binary.Read(buf, binary.BigEndian, &e.NwkSKey) return binary.Read(buf, binary.BigEndian, &e.FCntDown) } - -//// MarshalBinary implements the encoding.BinaryMarshaler interface -//func (e appEntry) MarshalBinary() ([]byte, error) { -// rw := readwriter.New(nil) -// rw.Write(e.AppKey) -// return rw.Bytes() -//} -// -//// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -//func (e *appEntry) UnmarshalBinary(data []byte) error { -// rw := readwriter.New(data) -// rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) -// return rw.Err() -//} diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go new file mode 100644 index 000000000..8498f9581 --- /dev/null +++ b/core/components/handler/devStorage_test.go @@ -0,0 +1,171 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "os" + "path" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const devDB = "TestDevStorage.db" + +//UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error +//Lookup(appEUI []byte, devEUI []byte) (devEntry, error) +//StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error +//Close() error + +func TestLookupStore(t *testing.T) { + var db DevStorage + defer func() { + os.Remove(path.Join(os.TempDir(), devDB)) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + var err error + db, err = NewDevStorage(path.Join(os.TempDir(), devDB)) + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Store and Lookup a registration") + + // Build + appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} + devAddr := [4]byte{1, 2, 3, 4} + appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + // Expect + var want = devEntry{ + DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, + FCntDown: 0, + } + + // Operate + err := db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + FatalUnless(t, err) + entry, err := db.Lookup(appEUI, devEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, want, entry, "Device Entries") + } + + // ------------------ + + { + Desc(t, "Lookup a non-existing registration") + + // Build + appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} + devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} + + // Operate + _, err := db.Lookup(appEUI, devEUI) + + // Check + CheckErrors(t, ErrNotFound, err) + } + + // ------------------ + + { + Desc(t, "Store twice the same registration") + + // Build + appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 9} + devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} + devAddr := [4]byte{1, 2, 3, 4} + appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} + + // Expect + var want = devEntry{ + DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, + FCntDown: 0, + } + + // Operate + err := db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + FatalUnless(t, err) + err = db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + FatalUnless(t, err) + entry, err := db.Lookup(appEUI, devEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, want, entry, "Device Entries") + } + + // ------------------ + + { + Desc(t, "Update FCnt") + + // Build + appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 14} + devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} + devAddr := [4]byte{1, 2, 3, 4} + appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} + fcnt := uint32(2) + + // Expect + var want = devEntry{ + DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, + FCntDown: fcnt, + } + + // Operate + err := db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + FatalUnless(t, err) + err = db.UpdateFCnt(appEUI, devEUI, fcnt) + entry, errLookup := db.Lookup(appEUI, devEUI) + FatalUnless(t, errLookup) + + // Check + CheckErrors(t, nil, err) + Check(t, want, entry, "Device Entries") + } + + // ------------------ + + { + Desc(t, "Update FCnt, device not found") + + // Build + appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 15} + devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} + fcnt := uint32(2) + + // Operate + err := db.UpdateFCnt(appEUI, devEUI, fcnt) + + // Check + CheckErrors(t, ErrNotFound, err) + } + + // ------------------ + + { + Desc(t, "Close the storage") + err := db.Close() + CheckErrors(t, nil, err) + } +} diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index b06fce147..c37f6098f 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -153,7 +153,7 @@ func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandle } h.ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") - return nil, h.packets.Push(req.AppEUI, req.DevEUI, req.Payload) + return nil, h.packets.Push(req.AppEUI, req.DevEUI, pktEntry{Payload: req.Payload}) } // HandleDataUp implements the core.HandlerServer interface @@ -368,7 +368,7 @@ browseBundles: h.ctx.Debug("Looking for downlink response") best := computer.Get(scores) h.ctx.WithField("Bundle", best).Debug("Determine best gateway") - var downlink []byte + var downlink pktEntry if best != nil { // Avoid pulling when there's no gateway available for an answer downlink, err = h.packets.Pull(b.Packet.AppEUI, b.Packet.DevEUI) } @@ -379,10 +379,10 @@ browseBundles: // One of those bundle might be available for a response for i, bundle := range bundles { - if best != nil && best.ID == i && downlink != nil && err == nil { + if best != nil && best.ID == i && downlink.Payload != nil && err == nil { stats.MarkMeter("handler.downlink.pull") - downlink, err := h.buildDownlink(downlink, bundle.Packet, bundle.Entry, best.IsRX2) + downlink, err := h.buildDownlink(downlink.Payload, bundle.Packet, bundle.Entry, best.IsRX2) if err != nil { go h.abortConsume(errors.New(errors.Structural, err), bundles) continue browseBundles diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 23d8acc5d..1a7ac0a80 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -23,7 +23,9 @@ type pktStorage struct { db dbutil.Interface } -type pktEntry []byte +type pktEntry struct { + Payload []byte +} // NewPktStorage creates a new PktStorage func NewPktStorage(name string) (PktStorage, error) { @@ -44,35 +46,38 @@ func (s pktStorage) Pull(appEUI, devEUI []byte) (pktEntry, error) { key := append(appEUI, devEUI...) entries, err := s.db.Lookup(dbPackets, key, &pktEntry{}) if err != nil { - return nil, err // Operational || NotFound + return pktEntry{}, err // Operational || NotFound } payloads, ok := entries.([]pktEntry) if !ok { - return nil, errors.New(errors.Operational, "Unable to retrieve data from db") + return pktEntry{}, errors.New(errors.Operational, "Unable to retrieve data from db") } // NOTE: one day, those entries will be more complicated, with a ttl. // Here's the place where we should check for that. Cheers. if len(payloads) == 0 { - return nil, errors.New(errors.NotFound, fmt.Sprintf("Entry not found for %v", key)) + return pktEntry{}, errors.New(errors.NotFound, fmt.Sprintf("Entry not found for %v", key)) } - payload := payloads[0] + head := new(pktEntry) + _ = head.UnmarshalBinary(payloads[0].Payload) - var newEntries []dbutil.Entry + var tail []dbutil.Entry for _, p := range payloads[1:] { - newEntries = append(newEntries, &p) + t := new(pktEntry) + *t = p + tail = append(tail, t) } - if err := s.db.Replace(dbPackets, key, newEntries); err != nil { - if err := s.db.Replace(dbPackets, key, newEntries); err != nil { + if err := s.db.Replace(dbPackets, key, tail); err != nil { + if err := s.db.Replace(dbPackets, key, tail); err != nil { // TODO This is critical... we've just lost a packet - return nil, errors.New(errors.Operational, "Unable to restore data in db") + return pktEntry{}, errors.New(errors.Operational, "Unable to restore data in db") } } - return payload, nil + return *head, nil } // Close implements the PktStorage interface @@ -82,11 +87,12 @@ func (s pktStorage) Close() error { // MarshalBinary implements the encoding.BinaryMarshaler interface func (e pktEntry) MarshalBinary() ([]byte, error) { - return e, nil + return e.Payload, nil } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *pktEntry) UnmarshalBinary(data []byte) error { - *e = data + e.Payload = make([]byte, len(data)) + copy(e.Payload, data) return nil } diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go new file mode 100644 index 000000000..79f869991 --- /dev/null +++ b/core/components/handler/pktStorage_test.go @@ -0,0 +1,140 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "os" + "path" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const pktDB = "TestPktStorage.db" + +func TestPushPullNormal(t *testing.T) { + var db PktStorage + defer func() { + os.Remove(path.Join(os.TempDir(), pktDB)) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + var err error + db, err = NewPktStorage(path.Join(os.TempDir(), pktDB)) + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Push and Pull a valid Payload") + + // Build + appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 1} + devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 2} + payload := pktEntry{[]byte("TheThingsNetwork")} + + // Expects + var want = payload + + // Operate + err := db.Push(appEUI, devEUI, payload) + FatalUnless(t, err) + p, err := db.Pull(appEUI, devEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, want, p, "Payloads") + } + + // ------------------ + + { + Desc(t, "Push two payloads -> same device") + + // Build + appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 2} + devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 3} + payload1 := pktEntry{[]byte("TheThingsNetwork1")} + payload2 := pktEntry{[]byte("TheThingsNetwork2")} + + // Expects + var want1 = payload1 + var want2 = payload2 + + // Operate + err := db.Push(appEUI, devEUI, payload1) + FatalUnless(t, err) + err = db.Push(appEUI, devEUI, payload2) + FatalUnless(t, err) + p1, err := db.Pull(appEUI, devEUI) + FatalUnless(t, err) + p2, err := db.Pull(appEUI, devEUI) + FatalUnless(t, err) + + // Check + Check(t, want1, p1, "Payloads") + Check(t, want2, p2, "Payloads") + } + + // ------------------ + + { + Desc(t, "Pull a non existing entry") + + // Build + appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 3} + devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 3} + + // Operate + _, err := db.Pull(appEUI, devEUI) + + // Check + CheckErrors(t, ErrNotFound, err) + } + + // ------------------ + + { + Desc(t, "Close the storage") + err := db.Close() + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Push after close") + + // Build + appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 5} + devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 6} + payload := pktEntry{[]byte("TheThingsNetwork")} + + // Operate + err := db.Push(appEUI, devEUI, payload) + + // Check + CheckErrors(t, ErrOperational, err) + } + + // ------------------ + + { + Desc(t, "Pull after close") + + // Build + appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 1} + devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 2} + + // Operate + _, err := db.Pull(appEUI, devEUI) + + // Check + CheckErrors(t, ErrOperational, err) + } +} From 5fbc7958ced745b30a2d8c4392dddd2eec07d3f6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 15:11:27 +0100 Subject: [PATCH 1080/2266] [refactor/grpc] Prepare testing for handler -> mocks + logs --- core/components/handler/handler.go | 80 ++++++++++-------- core/components/handler/mocks_test.go | 117 ++++++++++++++++++++++++++ core/mocks/mocks.go | 63 ++++++++------ 3 files changed, 196 insertions(+), 64 deletions(-) create mode 100644 core/components/handler/mocks_test.go diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c37f6098f..9d9fcd9b3 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -20,19 +20,31 @@ import ( "google.golang.org/grpc" ) +// bufferDelay defines the timeframe length during which we bufferize packets const bufferDelay time.Duration = time.Millisecond * 300 // component implements the core.Component interface type component struct { - broker core.BrokerClient - ctx log.Interface - devices DevStorage - packets PktStorage - set chan<- bundle - adapter core.AppClient - netAddr string + Components + Set chan<- bundle + NetAddr string } +// Components is used to make handler instantiation easier +type Components struct { + Broker core.BrokerClient + Ctx log.Interface + DevStorage DevStorage + PktStorage PktStorage + AppAdapter core.AppClient +} + +// Options is used to make handler instantiation easier +type Options struct { + NetAddr string +} + +// bundle are used to materialize an incoming request being bufferized, waiting for the others. type bundle struct { Chresp chan interface{} Entry devEntry @@ -42,20 +54,16 @@ type bundle struct { } // New construct a new Handler -func New(devDb DevStorage, pktDb PktStorage, broker core.BrokerClient, adapter core.AppClient, netAddr string, ctx log.Interface) core.HandlerServer { +func New(c Components, o Options) core.HandlerServer { h := &component{ - ctx: ctx, - devices: devDb, - packets: pktDb, - broker: broker, - adapter: adapter, - netAddr: netAddr, + Components: c, + NetAddr: o.NetAddr, } set := make(chan bundle) bundles := make(chan []bundle) - h.set = set + h.Set = set go h.consumeBundles(bundles) go h.consumeSet(bundles, set) @@ -64,7 +72,7 @@ func New(devDb DevStorage, pktDb PktStorage, broker core.BrokerClient, adapter c // Start actually runs the component and starts the rpc server func (h *component) Start(netAddr string) error { - conn, err := net.Listen("tcp", h.netAddr) + conn, err := net.Listen("tcp", h.NetAddr) if err != nil { return errors.New(errors.Operational, err) } @@ -80,7 +88,7 @@ func (h *component) Start(netAddr string) error { // RegisterPersonalized implements the core.HandlerServer interface func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubHandlerReq) (*core.ABPSubHandlerRes, error) { - h.ctx.Debug("New personalized subscription request") + h.Ctx.Debug("New personalized subscription request") stats.MarkMeter("handler.registration.in") if len(req.AppEUI) != 8 { @@ -109,16 +117,16 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH var appSKey [16]byte copy(appSKey[:], req.AppSKey) - if h.netAddr == "" { + if h.NetAddr == "" { return nil, errors.New(errors.Operational, "Illegal call. Start the server first") } - if err := h.devices.StorePersonalized(req.AppEUI, devAddr, nwkSKey, appSKey); err != nil { + if err := h.DevStorage.StorePersonalized(req.AppEUI, devAddr, nwkSKey, appSKey); err != nil { return nil, errors.New(errors.Operational, err) } - _, err := h.broker.SubscribePersonalized(context.Background(), &core.ABPSubBrokerReq{ - HandlerNet: h.netAddr, + _, err := h.Broker.SubscribePersonalized(context.Background(), &core.ABPSubBrokerReq{ + HandlerNet: h.NetAddr, AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, @@ -133,7 +141,7 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH // HandleDataDown implements the core.HandlerServer interface func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { stats.MarkMeter("handler.downlink.in") - h.ctx.Debug("Handle downlink message") + h.Ctx.Debug("Handle downlink message") // Unmarshal the given packet and see what gift we get @@ -152,8 +160,8 @@ func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandle return nil, errors.New(errors.Structural, "Invalid payload") } - h.ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") - return nil, h.packets.Push(req.AppEUI, req.DevEUI, pktEntry{Payload: req.Payload}) + h.Ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") + return nil, h.PktStorage.Push(req.AppEUI, req.DevEUI, pktEntry{Payload: req.Payload}) } // HandleDataUp implements the core.HandlerServer interface @@ -180,8 +188,8 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq stats.MarkMeter("handler.uplink.data") // 1. Lookup for the associated AppSKey + Application - h.ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") - entry, err := h.devices.Lookup(req.AppEUI, req.DevEUI) + h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") + entry, err := h.DevStorage.Lookup(req.AppEUI, req.DevEUI) if err != nil { return nil, err } @@ -202,9 +210,9 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq copy(bundleID[:], data[:]) // 4. Send the actual bundle to the consumer - ctx := h.ctx.WithField("BundleID", bundleID) + ctx := h.Ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") - h.set <- bundle{ + h.Set <- bundle{ ID: bundleID, Packet: *req, Entry: entry, @@ -238,7 +246,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) // It then flushes them once a given delay has passed since the reception of the first bundle. func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { - ctx := h.ctx.WithField("goroutine", "set consumer") + ctx := h.Ctx.WithField("goroutine", "set consumer") ctx.Debug("Starting packets buffering") // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture @@ -297,7 +305,7 @@ func setAlarm(alarm chan<- [20]byte, id [20]byte, delay time.Duration) { // consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, // deduplicate them, and send a single enhanced packet to the upadapter for further processing. func (h component) consumeBundles(chbundle <-chan []bundle) { - ctx := h.ctx.WithField("goroutine", "bundle consumer") + ctx := h.Ctx.WithField("goroutine", "bundle consumer") ctx.Debug("Starting bundle consumer") browseBundles: @@ -351,7 +359,7 @@ browseBundles: // Then create an application-level packet and send it to the wild open // we don't expect a response from the adapter, end of the chain. - _, err = h.adapter.HandleData(context.Background(), &core.DataAppReq{ + _, err = h.AppAdapter.HandleData(context.Background(), &core.DataAppReq{ AppEUI: b.Packet.AppEUI, DevEUI: b.Packet.DevEUI, Payload: payload, @@ -365,12 +373,12 @@ browseBundles: stats.MarkMeter("handler.uplink.out") // Now handle the downlink and respond to node - h.ctx.Debug("Looking for downlink response") + h.Ctx.Debug("Looking for downlink response") best := computer.Get(scores) - h.ctx.WithField("Bundle", best).Debug("Determine best gateway") + h.Ctx.WithField("Bundle", best).Debug("Determine best gateway") var downlink pktEntry if best != nil { // Avoid pulling when there's no gateway available for an answer - downlink, err = h.packets.Pull(b.Packet.AppEUI, b.Packet.DevEUI) + downlink, err = h.PktStorage.Pull(b.Packet.AppEUI, b.Packet.DevEUI) } if err != nil && err.(errors.Failure).Nature != errors.NotFound { go h.abortConsume(err, bundles) @@ -387,7 +395,7 @@ browseBundles: go h.abortConsume(errors.New(errors.Structural, err), bundles) continue browseBundles } - err = h.devices.UpdateFCnt(b.Packet.AppEUI, b.Packet.DevEUI, downlink.Payload.MACPayload.FHDR.FCnt) + err = h.DevStorage.UpdateFCnt(b.Packet.AppEUI, b.Packet.DevEUI, downlink.Payload.MACPayload.FHDR.FCnt) if err != nil { go h.abortConsume(errors.New(errors.Structural, err), bundles) continue browseBundles @@ -403,7 +411,7 @@ browseBundles: // Abort consume forward the given error to all bundle recipients func (h component) abortConsume(err error, bundles []bundle) { stats.MarkMeter("handler.uplink.invalid") - h.ctx.WithError(err).Debug("Unable to consume bundle") + h.Ctx.WithError(err).Debug("Unable to consume bundle") for _, bundle := range bundles { bundle.Chresp <- err } diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go new file mode 100644 index 000000000..76dadb6f2 --- /dev/null +++ b/core/components/handler/mocks_test.go @@ -0,0 +1,117 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +// NOTE: All the code below could be generated + +// MockDevStorage mocks the DevStorage interface +type MockDevStorage struct { + Failures map[string]error + InUpdateFCnt struct { + AppEUI []byte + DevEUI []byte + FCnt uint32 + } + InLookup struct { + AppEUI []byte + DevEUI []byte + } + OutLookup struct { + Entry devEntry + } + InStorePersonalized struct { + AppEUI []byte + DevAddr [4]byte + AppSKey [16]byte + NwkSKey [16]byte + } + InClose struct { + Called bool + } +} + +// NewMockDevStorage creates a new MockDevStorage +func NewMockDevStorage() *MockDevStorage { + return &MockDevStorage{ + Failures: make(map[string]error), + } +} + +// UpdateFCnt implements the DevStorage interface +func (m *MockDevStorage) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { + m.InUpdateFCnt.AppEUI = appEUI + m.InUpdateFCnt.DevEUI = devEUI + m.InUpdateFCnt.FCnt = fcnt + return m.Failures["UpdateFCnt"] +} + +// Lookup implements the DevStorage interface +func (m *MockDevStorage) Lookup(appEUI []byte, devEUI []byte) (devEntry, error) { + m.InLookup.AppEUI = appEUI + m.InLookup.DevEUI = devEUI + return m.OutLookup.Entry, m.Failures["Lookup"] +} + +// StorePersonalized implements the DevStorage interface +func (m *MockDevStorage) StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error { + m.InStorePersonalized.AppEUI = appEUI + m.InStorePersonalized.DevAddr = devAddr + m.InStorePersonalized.AppSKey = appSKey + m.InStorePersonalized.NwkSKey = nwkSKey + return m.Failures["StorePersonalized"] +} + +// Close implements the DevStorage Interface +func (m *MockDevStorage) Close() error { + m.InClose.Called = true + return m.Failures["Close"] +} + +// MockPktStorage mocks the PktStorage interface +type MockPktStorage struct { + Failures map[string]error + InPull struct { + AppEUI []byte + DevEUI []byte + } + OutPull struct { + Entry pktEntry + } + InPush struct { + AppEUI []byte + DevEUI []byte + Payload pktEntry + } + InClose struct { + Called bool + } +} + +// NewMockPktStorage creates a new MockPktStorage +func NewMockPktStorage() *MockPktStorage { + return &MockPktStorage{ + Failures: make(map[string]error), + } +} + +// Close implements the PktStorage Interface +func (m *MockPktStorage) Close() error { + m.InClose.Called = true + return m.Failures["Close"] +} + +// Push implements the PktStorage interface +func (m *MockPktStorage) Push(appEUI []byte, devEUI []byte, payload pktEntry) error { + m.InPush.AppEUI = appEUI + m.InPush.DevEUI = devEUI + m.InPush.Payload = payload + return m.Failures["Push"] +} + +// Push implements the PktStorage interface +func (m *MockPktStorage) Pull(appEUI []byte, devEUI []byte) (pktEntry, error) { + m.InPull.AppEUI = appEUI + m.InPull.DevEUI = devEUI + return m.OutPull.Entry, m.Failures["Pull"] +} diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index f62135fb2..88533f61c 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -13,6 +13,34 @@ import ( // NOTE: All the code below could be generated +// AppClient mocks the core.AppClient interface +type AppClient struct { + Failures map[string]error + InHandleData struct { + Ctx context.Context + Req *core.DataAppReq + Opts []grpc.CallOption + } + OutHandleData struct { + Res *core.DataAppRes + } +} + +// NewAppClient creates a new mock AppClient +func NewAppClient() *AppClient { + return &AppClient{ + Failures: make(map[string]error), + } +} + +// HandleData implements the core.AppClient interface +func (m *AppClient) HandleData(ctx context.Context, in *core.DataAppReq, opts ...grpc.CallOption) (*core.DataAppRes, error) { + m.InHandleData.Ctx = ctx + m.InHandleData.Req = in + m.InHandleData.Opts = opts + return m.OutHandleData.Res, m.Failures["HandleData"] +} + // HandlerClient mocks the core.HandlerClient interface type HandlerClient struct { Failures map[string]error @@ -54,10 +82,7 @@ func (m *HandlerClient) HandleDataUp(ctx context.Context, in *core.DataUpHandler m.InHandleDataUp.Ctx = ctx m.InHandleDataUp.Req = in m.InHandleDataUp.Opts = opts - if err := m.Failures["HandleDataUp"]; err != nil { - return nil, err - } - return m.OutHandleDataUp.Res, nil + return m.OutHandleDataUp.Res, m.Failures["HandleDataUp"] } // HandleDataDown implements the core.HandlerClient interface @@ -65,10 +90,7 @@ func (m *HandlerClient) HandleDataDown(ctx context.Context, in *core.DataDownHan m.InHandleDataDown.Ctx = ctx m.InHandleDataDown.Req = in m.InHandleDataDown.Opts = opts - if err := m.Failures["HandleDataDown"]; err != nil { - return nil, err - } - return m.OutHandleDataDown.Res, nil + return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] } // SubscribePersonalized implements the core.HandlerClient interface @@ -76,10 +98,7 @@ func (m *HandlerClient) SubscribePersonalized(ctx context.Context, in *core.ABPS m.InSubscribePersonalized.Ctx = ctx m.InSubscribePersonalized.Req = in m.InSubscribePersonalized.Opts = opts - if err := m.Failures["SubscribePersonalized"]; err != nil { - return nil, err - } - return m.OutSubscribePersonalized.Res, nil + return m.OutSubscribePersonalized.Res, m.Failures["SubscribePersonalized"] } // BrokerClient mocks the core.BrokerClient interface @@ -115,10 +134,7 @@ func (m *BrokerClient) HandleData(ctx context.Context, in *core.DataBrokerReq, o m.InHandleData.Ctx = ctx m.InHandleData.Req = in m.InHandleData.Opts = opts - if err := m.Failures["HandleData"]; err != nil { - return nil, err - } - return m.OutHandleData.Res, nil + return m.OutHandleData.Res, m.Failures["HandleData"] } // SubscribePersonalized implements the core.BrokerClient interface @@ -126,10 +142,7 @@ func (m *BrokerClient) SubscribePersonalized(ctx context.Context, in *core.ABPSu m.InSubscribePersonalized.Ctx = ctx m.InSubscribePersonalized.Req = in m.InSubscribePersonalized.Opts = opts - if err := m.Failures["SubscribePersonalized"]; err != nil { - return nil, err - } - return m.OutSubscribePersonalized.Res, nil + return m.OutSubscribePersonalized.Res, m.Failures["SubscribePersonalized"] } // RouterServer mocks the core.RouterServer interface @@ -162,20 +175,14 @@ func NewRouterServer() *RouterServer { func (m *RouterServer) HandleData(ctx context.Context, in *core.DataRouterReq) (*core.DataRouterRes, error) { m.InHandleData.Ctx = ctx m.InHandleData.Req = in - if err := m.Failures["HandleData"]; err != nil { - return nil, err - } - return m.OutHandleData.Res, nil + return m.OutHandleData.Res, m.Failures["HandleData"] } // HandleStats implements the core.RouterServer interface func (m *RouterServer) HandleStats(ctx context.Context, in *core.StatsReq) (*core.StatsRes, error) { m.InHandleStats.Ctx = ctx m.InHandleStats.Req = in - if err := m.Failures["HandleStats"]; err != nil { - return nil, err - } - return m.OutHandleStats.Res, nil + return m.OutHandleStats.Res, m.Failures["HandleStats"] } // DutyManager mocks the dutycycle.DutyManager interface From ba3fde5e2e75911a1ba82d1a2be09e21f0d42c09 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 18:00:17 +0100 Subject: [PATCH 1081/2266] [refactor/grpc] Re-add handler tests --- core/components/handler/handler.go | 28 +- core/components/handler/handler_test.go | 1508 +++++++++++++++++++++++ 2 files changed, 1525 insertions(+), 11 deletions(-) create mode 100644 core/components/handler/handler_test.go diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 9d9fcd9b3..a03f93559 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -30,6 +30,12 @@ type component struct { NetAddr string } +// Interface defines the Handler interface +type Interface interface { + core.HandlerServer + Start() error +} + // Components is used to make handler instantiation easier type Components struct { Broker core.BrokerClient @@ -54,7 +60,7 @@ type bundle struct { } // New construct a new Handler -func New(c Components, o Options) core.HandlerServer { +func New(c Components, o Options) Interface { h := &component{ Components: c, NetAddr: o.NetAddr, @@ -71,7 +77,7 @@ func New(c Components, o Options) core.HandlerServer { } // Start actually runs the component and starts the rpc server -func (h *component) Start(netAddr string) error { +func (h *component) Start() error { conn, err := net.Listen("tcp", h.NetAddr) if err != nil { return errors.New(errors.Operational, err) @@ -117,11 +123,10 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH var appSKey [16]byte copy(appSKey[:], req.AppSKey) - if h.NetAddr == "" { - return nil, errors.New(errors.Operational, "Illegal call. Start the server first") - } + h.Ctx.Debug("Registration is valid. Saving and forwarding to broker") if err := h.DevStorage.StorePersonalized(req.AppEUI, devAddr, nwkSKey, appSKey); err != nil { + h.Ctx.WithError(err).Debug("Unable to store registration") return nil, errors.New(errors.Operational, err) } @@ -133,6 +138,7 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH }) if err != nil { + h.Ctx.WithError(err).Debug("Unable to forward registration") return nil, errors.New(errors.Operational, err) } return nil, nil @@ -334,12 +340,12 @@ browseBundles: // metadata from other bundle. if i == 0 { var err error - payload, err = lorawan.DecryptFRMPayload( - bundle.Packet.Payload, + payload, err = lorawan.EncryptFRMPayload( + bundle.Entry.AppSKey, + true, lorawan.DevAddr(bundle.Entry.DevAddr), bundle.Packet.FCnt, - true, - bundle.Entry.AppSKey, + bundle.Packet.Payload, ) if err != nil { go h.abortConsume(err, bundles) @@ -366,7 +372,7 @@ browseBundles: Metadata: metadata, }) if err != nil { - go h.abortConsume(err, bundles) + go h.abortConsume(errors.New(errors.Operational, err), bundles) continue browseBundles } @@ -397,7 +403,7 @@ browseBundles: } err = h.DevStorage.UpdateFCnt(b.Packet.AppEUI, b.Packet.DevEUI, downlink.Payload.MACPayload.FHDR.FCnt) if err != nil { - go h.abortConsume(errors.New(errors.Structural, err), bundles) + go h.abortConsume(err, bundles) continue browseBundles } bundle.Chresp <- downlink diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go new file mode 100644 index 000000000..1ef9a31d4 --- /dev/null +++ b/core/components/handler/handler_test.go @@ -0,0 +1,1508 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/dutycycle" + "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/brocaar/lorawan" + "golang.org/x/net/context" +) + +func TestHandleDataDown(t *testing.T) { + { + Desc(t, "Handle valid downlink") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataDownHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + Payload: []byte("TheThingsNetwork"), + } + + // Expect + var wantError *string + var wantRes *core.DataDownHandlerRes + var wantEntry = pktEntry{Payload: req.Payload} + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataDown(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Data Down Handler Responses") + Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + } + + // -------------------- + + { + Desc(t, "Handle invalid downlink ~> Invalid Payload") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataDownHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + Payload: nil, + } + + // Expect + var wantError = ErrStructural + var wantRes *core.DataDownHandlerRes + var wantEntry pktEntry + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataDown(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Data Down Handler Responses") + Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + } + + // -------------------- + + { + Desc(t, "Handle invalid downlink ~> Invalid AppEUI") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataDownHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + Payload: []byte("TheThingsNetwork"), + } + + // Expect + var wantError = ErrStructural + var wantRes *core.DataDownHandlerRes + var wantEntry pktEntry + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataDown(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Data Down Handler Responses") + Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + } + + // -------------------- + + { + Desc(t, "Handle invalid downlink ~> Invalid DevEUI") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataDownHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, + Payload: []byte("TheThingsNetwork"), + } + + // Expect + var wantError = ErrStructural + var wantRes *core.DataDownHandlerRes + var wantEntry pktEntry + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataDown(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Data Down Handler Responses") + Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + } +} + +func TestHandleDataUp(t *testing.T) { + { + Desc(t, "Handle uplink, 1 packet | Unknown") + + // Build + devStorage := NewMockDevStorage() + devStorage.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataUpHandlerReq{ + Payload: []byte("Payload"), + Metadata: new(core.Metadata), + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: 14, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrNotFound + var wantRes *core.DataUpHandlerRes + var wantData *core.DataAppReq + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, invalid Payload") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataUpHandlerReq{ + Payload: nil, + Metadata: new(core.Metadata), + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: 14, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataUpHandlerRes + var wantData *core.DataAppReq + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, invalid Metadata") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataUpHandlerReq{ + Payload: []byte("Payload"), + Metadata: nil, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: 14, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataUpHandlerRes + var wantData *core.DataAppReq + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, invalid DevEUI") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataUpHandlerReq{ + Payload: []byte("Payload"), + Metadata: new(core.Metadata), + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: nil, + FCnt: 14, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataUpHandlerRes + var wantData *core.DataAppReq + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, invalid AppEUI") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataUpHandlerReq{ + Payload: []byte("Payload"), + Metadata: new(core.Metadata), + AppEUI: []byte{1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: 14, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrStructural + var wantRes *core.DataUpHandlerRes + var wantData *core.DataAppReq + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, 1 packet | No downlink") + + // Build + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr *string + var wantRes *core.DataUpHandlerRes + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "2 packets in a row, same device | No Downlink") + + // Build + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req1 := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + DutyRX1: uint32(dutycycle.StateWarning), + DutyRX2: uint32(dutycycle.StateWarning), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + req2 := &core.DataUpHandlerReq{ + Payload: req1.Payload, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + FCnt: req1.FCnt, + MType: req1.MType, + } + + // Expect + var wantErr1 *string + var wantErr2 *string + var wantRes1 *core.DataUpHandlerRes + var wantRes2 *core.DataUpHandlerRes + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req1.Metadata, req2.Metadata}, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + } + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + + chack := make(chan bool) + go func() { + var ok bool + defer func(ok *bool) { chack <- *ok }(&ok) + res, err := handler.HandleDataUp(context.Background(), req1) + + // Check + CheckErrors(t, wantErr1, err) + Check(t, wantRes1, res, "Data Up Handler Responses") + ok = true + }() + + go func() { + <-time.After(time.Millisecond * 50) + var ok bool + defer func(ok *bool) { chack <- *ok }(&ok) + res, err := handler.HandleDataUp(context.Background(), req2) + + // Check + CheckErrors(t, wantErr2, err) + Check(t, wantRes2, res, "Data Up Handler Responses") + ok = true + }() + + // Check + ok1, ok2 := <-chack, <-chack + Check(t, true, ok1 && ok2, "Acknowledgements") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, 1 packet | one downlink ready") + + // Build + tmst := time.Now() + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + pktStorage.OutPull.Entry.Payload = []byte("Downlink") + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr *string + encodedDown, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + false, + devStorage.OutLookup.Entry.DevAddr, + devStorage.OutLookup.Entry.FCntDown+1, + pktStorage.OutPull.Entry.Payload, + ) + FatalUnless(t, err) + var wantRes = &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: devStorage.OutLookup.Entry.DevAddr[:], + FCnt: devStorage.OutLookup.Entry.FCntDown + 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: uint32(1), + FRMPayload: encodedDown, + }, + MIC: []byte{0, 0, 0, 0}, + }, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000), + PayloadSize: 21, + }, + } + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle late uplink | No Downlink") + + // Build + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req1 := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + DutyRX1: uint32(dutycycle.StateWarning), + DutyRX2: uint32(dutycycle.StateWarning), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + req2 := &core.DataUpHandlerReq{ + Payload: req1.Payload, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + FCnt: req1.FCnt, + MType: req1.MType, + } + + // Expect + var wantErr1 *string + var wantErr2 = ErrBehavioural + var wantRes1 *core.DataUpHandlerRes + var wantRes2 *core.DataUpHandlerRes + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req1.Metadata}, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + } + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + + chack := make(chan bool) + go func() { + var ok bool + defer func(ok *bool) { chack <- *ok }(&ok) + res, err := handler.HandleDataUp(context.Background(), req1) + + // Check + CheckErrors(t, wantErr1, err) + Check(t, wantRes1, res, "Data Up Handler Responses") + ok = true + }() + + go func() { + <-time.After(2 * bufferDelay) + var ok bool + defer func(ok *bool) { chack <- *ok }(&ok) + res, err := handler.HandleDataUp(context.Background(), req2) + + // Check + CheckErrors(t, wantErr2, err) + Check(t, wantRes2, res, "Data Up Handler Responses") + ok = true + }() + + // Check + ok1, ok2 := <-chack, <-chack + Check(t, true, ok1 && ok2, "Acknowledgements") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, 1 packet | AppAdapter fails") + + // Build + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + appAdapter.Failures["HandleData"] = fmt.Errorf("Mock Error") + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.DataUpHandlerRes + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, 1 packet | PktStorage fails") + + // Build + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + pktStorage.Failures["Pull"] = errors.New(errors.Operational, "Mock Error") + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.DataUpHandlerRes + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle two successive uplink | no downlink") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload1, fcnt1 := []byte("Payload1"), uint32(14) + devAddr1, appSKey1 := [4]byte{1, 2, 3, 4}, [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + payload2, fcnt2 := []byte("Payload2"), uint32(35346) + devAddr2, appSKey2 := [4]byte{4, 3, 2, 1}, [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} + encoded1, err := lorawan.EncryptFRMPayload( + appSKey1, + true, + devAddr1, + fcnt1, + payload1, + ) + FatalUnless(t, err) + encoded2, err := lorawan.EncryptFRMPayload( + appSKey2, + true, + devAddr2, + fcnt2, + payload2, + ) + FatalUnless(t, err) + req1 := &core.DataUpHandlerReq{ + Payload: encoded1, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + DutyRX1: uint32(dutycycle.StateWarning), + DutyRX2: uint32(dutycycle.StateWarning), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt1, + MType: uint32(lorawan.UnconfirmedDataUp), + } + req2 := &core.DataUpHandlerReq{ + Payload: encoded2, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + FCnt: fcnt2, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr1 *string + var wantErr2 *string + var wantRes1 *core.DataUpHandlerRes + var wantRes2 *core.DataUpHandlerRes + var wantData1 = &core.DataAppReq{ + Payload: payload1, + Metadata: []*core.Metadata{req1.Metadata}, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + } + var wantData2 = &core.DataAppReq{ + Payload: payload2, + Metadata: []*core.Metadata{req2.Metadata}, + AppEUI: req2.AppEUI, + DevEUI: req2.DevEUI, + } + var wantFCnt1 uint32 + var wantFCnt2 uint32 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + + devStorage.OutLookup.Entry = devEntry{ + DevAddr: devAddr1, + AppSKey: appSKey1, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + res1, err1 := handler.HandleDataUp(context.Background(), req1) + + // Check + CheckErrors(t, wantErr1, err1) + Check(t, wantRes1, res1, "Data Up Handler Responses") + Check(t, wantData1, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt1, devStorage.InUpdateFCnt.FCnt, "Frame counters") + + // Operate + devStorage.OutLookup.Entry = devEntry{ + DevAddr: devAddr2, + AppSKey: appSKey2, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 11, + } + res2, err2 := handler.HandleDataUp(context.Background(), req2) + + // Check + CheckErrors(t, wantErr2, err2) + Check(t, wantRes2, res2, "Data Up Handler Responses") + Check(t, wantData2, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt2, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, 1 packet | one downlink ready | Only RX2 available") + + // Build + tmst := time.Now() + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + pktStorage.OutPull.Entry.Payload = []byte("Downlink") + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateBlocked), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr *string + encodedDown, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + false, + devStorage.OutLookup.Entry.DevAddr, + devStorage.OutLookup.Entry.FCntDown+1, + pktStorage.OutPull.Entry.Payload, + ) + FatalUnless(t, err) + var wantRes = &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: devStorage.OutLookup.Entry.DevAddr[:], + FCnt: devStorage.OutLookup.Entry.FCntDown + 1, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: uint32(1), + FRMPayload: encodedDown, + }, + MIC: []byte{0, 0, 0, 0}, + }, + Metadata: &core.Metadata{ + DataRate: "SF9BW125", + Frequency: 869.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(2*time.Second).Unix() * 1000), + PayloadSize: 21, + }, + } + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle uplink, 1 packet | one downlink ready | Update FCnt fails") + + // Build + tmst := time.Now() + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + DevAddr: [4]byte{3, 4, 2, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + devStorage.Failures["UpdateFCnt"] = errors.New(errors.Operational, "Mock Error") + pktStorage := NewMockPktStorage() + pktStorage.OutPull.Entry.Payload = []byte("Downlink") + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutLookup.Entry.AppSKey, + true, + devStorage.OutLookup.Entry.DevAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateBlocked), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.UnconfirmedDataUp), + } + + // Expect + var wantErr = ErrOperational + var wantRes *core.DataUpHandlerRes + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt = devStorage.OutLookup.Entry.FCntDown + 1 + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + } +} + +func TestSubscribePersonalized(t *testing.T) { + { + Desc(t, "Handle Valid Perso Subscription") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevAddr: []byte{2, 2, 2, 2}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + addr := "localhost" + + // Expect + var wantError *string + var wantRes *core.ABPSubHandlerRes + var wantSub = req.AppEUI + var wantReq = &core.ABPSubBrokerReq{ + HandlerNet: addr, + AppEUI: req.AppEUI, + DevAddr: req.DevAddr, + NwkSKey: req.NwkSKey, + } + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } + + // ---------------------- + + { + Desc(t, "Handle Invalid Perso Subscription -> Invalid AppEUI") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1}, + DevAddr: []byte{2, 2, 2, 2}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + addr := "localhost" + + // Expect + var wantError = ErrStructural + var wantRes *core.ABPSubHandlerRes + var wantSub []byte + var wantReq *core.ABPSubBrokerReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } + + // ---------------------- + + { + Desc(t, "Handle Invalid Perso Subscription -> Invalid DevAddr") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevAddr: nil, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + addr := "localhost" + + // Expect + var wantError = ErrStructural + var wantRes *core.ABPSubHandlerRes + var wantSub []byte + var wantReq *core.ABPSubBrokerReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } + + // ---------------------- + + { + Desc(t, "Handle Invalid Perso Subscription -> Invalid NwkSKey") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevAddr: []byte{2, 2, 2, 2}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 12, 14, 12}, + AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + } + addr := "localhost" + + // Expect + var wantError = ErrStructural + var wantRes *core.ABPSubHandlerRes + var wantSub []byte + var wantReq *core.ABPSubBrokerReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } + + // ---------------------- + + { + Desc(t, "Handle Invalid Perso Subscription -> Invalid AppSKey") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevAddr: []byte{2, 2, 2, 2}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{6, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1}, + } + addr := "localhost" + + // Expect + var wantError = ErrStructural + var wantRes *core.ABPSubHandlerRes + var wantSub []byte + var wantReq *core.ABPSubBrokerReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } + + // ---------------------- + + { + Desc(t, "Handle Invalid Perso Subscription -> Storage fails") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevAddr: []byte{2, 2, 2, 2}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{6, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1, 0, 1, 1, 1, 1}, + } + addr := "localhost" + + // Expect + var wantError = ErrOperational + var wantRes *core.ABPSubHandlerRes + var wantSub = req.AppEUI + var wantReq *core.ABPSubBrokerReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } + + // ---------------------- + + { + Desc(t, "Handle Invalid Perso Subscription -> Brokers fails") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + broker.Failures["SubscribePersonalized"] = fmt.Errorf("Mock Error") + req := &core.ABPSubHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevAddr: []byte{2, 2, 2, 2}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppSKey: []byte{6, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1, 0, 1, 1, 1, 1}, + } + addr := "localhost" + + // Expect + var wantError = ErrOperational + var wantRes *core.ABPSubHandlerRes + var wantSub = req.AppEUI + var wantReq = &core.ABPSubBrokerReq{ + HandlerNet: addr, + AppEUI: req.AppEUI, + DevAddr: req.DevAddr, + NwkSKey: req.NwkSKey, + } + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: addr}) + res, err := handler.SubscribePersonalized(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Subscribe Handler Responses") + Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") + Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + } +} + +func TestStart(t *testing.T) { + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + DevStorage: NewMockDevStorage(), + PktStorage: NewMockPktStorage(), + AppAdapter: mocks.NewAppClient(), + }, Options{NetAddr: "localhost:8888"}) + + cherr := make(chan error) + go func() { + err := handler.Start() + cherr <- err + }() + + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 250): + } + CheckErrors(t, nil, err) +} From 17942c324445aabf349fc0faf34b506232e3eea7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 18:39:21 +0100 Subject: [PATCH 1082/2266] [refactor/grpc] Add Start methods to broker and router --- core/components/broker/broker.go | 33 ++++++++++++++++++++++++--- core/components/broker/broker_test.go | 25 ++++++++++++++++++-- core/components/router/router.go | 33 ++++++++++++++++++++++++--- core/components/router/router_test.go | 22 ++++++++++++++++++ 4 files changed, 105 insertions(+), 8 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 00fd00e38..d34045b13 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -5,6 +5,7 @@ package broker import ( "fmt" + "net" "regexp" "github.com/TheThingsNetwork/ttn/core" @@ -13,11 +14,13 @@ import ( "github.com/apex/log" "github.com/brocaar/lorawan" "golang.org/x/net/context" + "google.golang.org/grpc" ) // component implements the core.BrokerServer interface type component struct { Components + NetAddr string } // Components defines a structure to make the instantiation easier to read @@ -27,11 +30,35 @@ type Components struct { } // Options defines a structure to make the instantiation easier to read -type Options struct{} +type Options struct { + NetAddr string +} + +// Interface defines the Broker interface +type Interface interface { + core.BrokerServer + Start() error +} // New construct a new Broker component -func New(c Components, o Options) core.BrokerServer { - return component{Components: c} +func New(c Components, o Options) Interface { + return component{Components: c, NetAddr: o.NetAddr} +} + +// Start actually runs the component and starts the rpc server +func (b component) Start() error { + conn, err := net.Listen("tcp", b.NetAddr) + if err != nil { + return errors.New(errors.Operational, err) + } + + server := grpc.NewServer() + core.RegisterBrokerServer(server, b) + + if err := server.Serve(conn); err != nil { + return errors.New(errors.Operational, err) + } + return nil } // HandleData implements the core.BrokerServer interface diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 68f8771bb..2a8f5cf01 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -7,6 +7,7 @@ import ( "fmt" "net" "testing" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/mocks" @@ -14,7 +15,6 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" "golang.org/x/net/context" - //"google.golang.org/grpc" ) func TestHandleData(t *testing.T) { @@ -209,7 +209,7 @@ func TestHandleData(t *testing.T) { Metadata: req.Metadata, } var wantRes *core.DataBrokerRes - var wantFCnt uint32 = nc.OutWholeCounter.FCnt + var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer = true // Operate @@ -1017,3 +1017,24 @@ func TestDialerCloser(t *testing.T) { CheckErrors(t, ErrOperational, errDial) } } + +func TestStart(t *testing.T) { + + broker := New(Components{ + Ctx: GetLogger(t, "Broker"), + NetworkController: NewMockNetworkController(), + }, Options{NetAddr: "localhost:8887"}) + + cherr := make(chan error) + go func() { + err := broker.Start() + cherr <- err + }() + + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 250): + } + CheckErrors(t, nil, err) +} diff --git a/core/components/router/router.go b/core/components/router/router.go index 0e225617c..2051b5054 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -4,6 +4,7 @@ package router import ( + "net" "strings" "sync" @@ -13,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "golang.org/x/net/context" + "google.golang.org/grpc" ) // Components defines a structure to make the instantiation easier to read @@ -24,16 +26,41 @@ type Components struct { } // Options defines a structure to make the instantiation easier to read -type Options struct{} +type Options struct { + NetAddr string +} // component implements the core.RouterServer interface type component struct { Components + NetAddr string +} + +// Interface defines the Router interface +type Interface interface { + core.RouterServer + Start() error } // New constructs a new router -func New(c Components, o Options) core.RouterServer { - return component{Components: c} +func New(c Components, o Options) Interface { + return component{Components: c, NetAddr: o.NetAddr} +} + +// Start actually runs the component and starts the rpc server +func (r component) Start() error { + conn, err := net.Listen("tcp", r.NetAddr) + if err != nil { + return errors.New(errors.Operational, err) + } + + server := grpc.NewServer() + core.RegisterRouterServer(server, r) + + if err := server.Serve(conn); err != nil { + return errors.New(errors.Operational, err) + } + return nil } // HandleStats implements the core.RouterClient interface diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 1332e25c0..88b61b948 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -1347,3 +1347,25 @@ func TestHandleData(t *testing.T) { } } + +func TestStart(t *testing.T) { + router := New(Components{ + Ctx: GetLogger(t, "Router"), + DutyManager: mocks.NewDutyManager(), + Brokers: []core.BrokerClient{mocks.NewBrokerClient()}, + Storage: NewMockStorage(), + }, Options{NetAddr: "localhost:8886"}) + + cherr := make(chan error) + go func() { + err := router.Start() + cherr <- err + }() + + var err error + select { + case err = <-cherr: + case <-time.After(time.Millisecond * 250): + } + CheckErrors(t, nil, err) +} From b600cb87f606be7c647649d3e6a3bb13b9b2ce6b Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 18:39:37 +0100 Subject: [PATCH 1083/2266] [refactor/grpc] Fix last issues with tests, linter and vet --- core/adapters/http/http_test.go | 4 ++-- core/components/router/router.go | 4 ++-- core/dutycycle/dutyManager.go | 4 ++++ core/msgp.go | 2 +- utils/errors/errors.go | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go index fa52a926b..91dc74a6d 100644 --- a/core/adapters/http/http_test.go +++ b/core/adapters/http/http_test.go @@ -65,7 +65,7 @@ func TestBind(t *testing.T) { _, _ = cli.Get(fmt.Sprintf("http://%s%s", options.NetAddr, hdl.URL())) // Check - Check(t, true, hdl.InUrl.Called, "Url() Calls") + Check(t, true, hdl.InURL.Called, "Url() Calls") Check(t, true, hdl.InHandle.Called, "Handle() Calls") } @@ -91,7 +91,7 @@ func TestBind(t *testing.T) { _, _ = cli.Get(fmt.Sprintf("http://%s%s", options.NetAddr, hdl.URL())) // Check - Check(t, true, hdl.InUrl.Called, "Url() Calls") + Check(t, true, hdl.InURL.Called, "Url() Calls") Check(t, true, hdl.InHandle.Called, "Handle() Calls") } } diff --git a/core/components/router/router.go b/core/components/router/router.go index 2051b5054..e8ee22de5 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -227,6 +227,7 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co // Handle error if err != nil { + ctx.WithField("index", index).WithError(err).Debug("Error while contacting broker") if strings.Contains(err.Error(), string(errors.NotFound)) { // FIXME Find a better way to analyze the error cherr <- errors.New(errors.NotFound, "Broker not responsible for the node") return @@ -252,8 +253,7 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co var errored uint8 var notFound uint8 - for i := 0; i < len(cherr); i++ { - err := <-cherr + for err := range cherr { if err.(errors.Failure).Nature != errors.NotFound { errored++ ctx.WithError(err).Warn("Failed to contact broker") diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 9f9f3fd38..22dce08b4 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -23,6 +23,7 @@ type DutyManager interface { Close() error } +// Cycles gives a representation of sub-band usages type Cycles map[subBand]uint32 type dutyManager struct { @@ -44,6 +45,7 @@ const ( type subBand string +// State Refers to an actual State of a transmitter type State uint const ( @@ -242,6 +244,7 @@ func computeTOA(size uint32, datr string, codr string) (time.Duration, error) { return time.ParseDuration(fmt.Sprintf("%fms", timeOnAir)) } +// ParseDatr extract the spread factor and the bandwidth from a DataRate identifier func ParseDatr(datr string) (float64, float64, error) { re := regexp.MustCompile("^SF(7|8|9|10|11|12)BW(125|250|500)$") matches := re.FindStringSubmatch(datr) @@ -256,6 +259,7 @@ func ParseDatr(datr string) (float64, float64, error) { return sf, bw, nil } +// StateFromDuty retrieve the associated transmitter state from a duty value func StateFromDuty(duty uint32) State { if duty >= 100 { return StateBlocked diff --git a/core/msgp.go b/core/msgp.go index 68ca4a484..a8ef147ba 100644 --- a/core/msgp.go +++ b/core/msgp.go @@ -20,7 +20,7 @@ type AppMetadata struct { Frequency float32 `msg:"frequency" json:"frequency"` DataRate string `msg:"data_rate" json:"data_rate"` CodingRate string `msg:"coding_rate" json:"coding_rate"` - Timestamp uint32 `msg:"timestamp" json:timestamp"` + Timestamp uint32 `msg:"timestamp" json:"timestamp"` Rssi int32 `msg:"rssi" json:"rssi"` Lsnr float32 `msg:"lsnr" json:"lsnr"` Altitude int32 `msg:"altitude" json:"altitude"` diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 76b2103cc..033989565 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -13,7 +13,7 @@ type Nature string const ( Structural Nature = "Invalid structure" // Requests, parameters or inputs are wrong. Retry won't work. - NotFound Nature = "Unable to found entity" // Failed to lookup something, somewhere + NotFound Nature = "Unable to find entity" // Failed to lookup something, somewhere Behavioural Nature = "Wrong behavior or result" // No error but the result isn't the one expected Operational Nature = "Invalid operation" // An operation failed due to external causes, a retry could work Implementation Nature = "Illegal call" // Method not implemented or unsupported for the given structure From 1762b8a017c3a1020122d12c862b67a01e78b863 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 19:04:14 +0100 Subject: [PATCH 1084/2266] [refactor/grpc] Adjust coverage -> Only core packages --- coverage.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coverage.sh b/coverage.sh index 34ac440ce..fe1cbdceb 100755 --- a/coverage.sh +++ b/coverage.sh @@ -1,7 +1,7 @@ #!/bin/sh mkdir .cover -for pkg in $(go list ./... | grep -vE 'cmd|integration|ttn$') +for pkg in $(go list ./... | grep -E 'core' | grep -vE 'core$|mocks$') do profile=".cover/$(echo $pkg | grep -oE 'ttn/.*' | sed 's/\///g').cover" go test -cover -coverprofile=$profile $pkg From 603d10238b814112eebf843d929064b2328599f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 19:05:20 +0100 Subject: [PATCH 1085/2266] [refactor/grpc] Move api/ into core --- {api => core/api}/topics.go | 0 {api => core/api}/topics_test.go | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename {api => core/api}/topics.go (100%) rename {api => core/api}/topics_test.go (100%) diff --git a/api/topics.go b/core/api/topics.go similarity index 100% rename from api/topics.go rename to core/api/topics.go diff --git a/api/topics_test.go b/core/api/topics_test.go similarity index 100% rename from api/topics_test.go rename to core/api/topics_test.go From a5c24c483b2d5be6f4009a70afa865a14725ebda Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 19:28:32 +0100 Subject: [PATCH 1086/2266] [refactor/grpc] Use of HandlerServer instead of HandlerClient in mqtt adapter --- core/adapters/mqtt/mqtt.go | 2 +- core/adapters/mqtt/mqtt_test.go | 44 +++++++++++++-------------- core/mocks/mocks.go | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 23 deletions(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 2b58199d4..bf8ade65f 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -25,7 +25,7 @@ type adapter struct { // Components defines a structure to make the instantiation easier to read type Components struct { - Handler core.HandlerClient + Handler core.HandlerServer Client Client Ctx log.Interface } diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index eb301fe22..50b351907 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -429,7 +429,7 @@ func TestConsumeMQTTMsg(t *testing.T) { options := Options{InMsg: chmsg} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } _ = New(components, options) @@ -449,8 +449,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -466,7 +466,7 @@ func TestConsumeMQTTMsg(t *testing.T) { options := Options{InMsg: chmsg} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } _ = New(components, options) @@ -482,8 +482,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -499,7 +499,7 @@ func TestConsumeMQTTMsg(t *testing.T) { options := Options{InMsg: chmsg} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } _ = New(components, options) @@ -524,8 +524,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -541,7 +541,7 @@ func TestConsumeMQTTMsg(t *testing.T) { options := Options{InMsg: chmsg} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } _ = New(components, options) @@ -557,8 +557,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -574,7 +574,7 @@ func TestConsumeMQTTMsg(t *testing.T) { options := Options{InMsg: chmsg} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } _ = New(components, options) @@ -590,8 +590,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerClient).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerClient).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -606,7 +606,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -642,7 +642,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -678,7 +678,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -714,7 +714,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -750,7 +750,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -781,7 +781,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } components.Client.(*MockClient).Failures["Publish"] = fmt.Errorf("Mock Error") @@ -826,7 +826,7 @@ func TestHandleData(t *testing.T) { options := Options{InMsg: nil} components := Components{ Client: NewMockClient(), - Handler: mocks.NewHandlerClient(), + Handler: mocks.NewHandlerServer(), Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 88533f61c..42b73cccf 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -234,3 +234,57 @@ func (m *DutyManager) Close() error { m.InClose.Called = true return m.Failures["Close"] } + +// HandlerServer mocks the core.HandlerServer interface +type HandlerServer struct { + Failures map[string]error + InHandleDataUp struct { + Ctx context.Context + Req *core.DataUpHandlerReq + } + OutHandleDataUp struct { + Res *core.DataUpHandlerRes + } + InHandleDataDown struct { + Ctx context.Context + Req *core.DataDownHandlerReq + } + OutHandleDataDown struct { + Res *core.DataDownHandlerRes + } + InSubscribePersonalized struct { + Ctx context.Context + Req *core.ABPSubHandlerReq + } + OutSubscribePersonalized struct { + Res *core.ABPSubHandlerRes + } +} + +// NewHandlerServer creates a new mock HandlerServer +func NewHandlerServer() *HandlerServer { + return &HandlerServer{ + Failures: make(map[string]error), + } +} + +// HandleDataUp implements the core.HandlerServer interface +func (m *HandlerServer) HandleDataUp(ctx context.Context, in *core.DataUpHandlerReq) (*core.DataUpHandlerRes, error) { + m.InHandleDataUp.Ctx = ctx + m.InHandleDataUp.Req = in + return m.OutHandleDataUp.Res, m.Failures["HandleDataUp"] +} + +// HandleDataDown implements the core.HandlerServer interface +func (m *HandlerServer) HandleDataDown(ctx context.Context, in *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { + m.InHandleDataDown.Ctx = ctx + m.InHandleDataDown.Req = in + return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] +} + +// SubscribePersonalized implements the core.HandlerServer interface +func (m *HandlerServer) SubscribePersonalized(ctx context.Context, in *core.ABPSubHandlerReq) (*core.ABPSubHandlerRes, error) { + m.InSubscribePersonalized.Ctx = ctx + m.InSubscribePersonalized.Req = in + return m.OutSubscribePersonalized.Res, m.Failures["SubscribePersonalized"] +} From 5faa3d2e33a01300b5eaf02ed49e6c1c5e124fc0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 19:39:24 +0100 Subject: [PATCH 1087/2266] [refactor/grpc] Start to rewrite cli --- cmd/broker.go | 109 +++++++++------------------------- cmd/handler.go | 154 ++++++++++++++----------------------------------- cmd/router.go | 3 - 3 files changed, 70 insertions(+), 196 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index bb341fcc9..81acd862b 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -7,10 +7,9 @@ import ( "fmt" "path/filepath" "strings" + "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" @@ -30,7 +29,7 @@ and personalized devices (with their network session keys) with the router. PreRun: func(cmd *cobra.Command, args []string) { var statusServer string if viper.GetInt("broker.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) + statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-address"), viper.GetInt("broker.status-port")) stats.Initialize() } else { statusServer = "disabled" @@ -39,41 +38,22 @@ and personalized devices (with their network session keys) with the router. ctx.WithFields(log.Fields{ "database": viper.GetString("broker.database"), "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")), - "downlink": fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")), + "main-server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - // Instantiate all components - rtrNet := fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-bind-address"), viper.GetInt("broker.uplink-port")) - rtrAdapter, err := http.NewAdapter(rtrNet, nil, ctx.WithField("adapter", "router-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Routers Adapter") - } - rtrAdapter.Bind(handlers.Collect{}) - - hdlNet := fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-bind-address"), viper.GetInt("broker.downlink-port")) - hdlAdapter, err := http.NewAdapter(hdlNet, nil, ctx.WithField("adapter", "handler-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Handlers Adapter") - } - hdlAdapter.Bind(handlers.Collect{}) - hdlAdapter.Bind(handlers.PubSub{}) - hdlAdapter.Bind(handlers.Applications{}) - - if viper.GetInt("broker.status-port") > 0 { - statusNet := fmt.Sprintf("%s:%d", viper.GetString("broker.status-bind-address"), viper.GetInt("broker.status-port")) - statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Status Adapter") - } - statusAdapter.Bind(handlers.StatusPage{}) - statusAdapter.Bind(handlers.Healthz{}) - } - // Instantiate Storage + // Status & Health + statusAddr := fmt.Sprintf("%s:%d", viper.GetString("broker.status-address"), viper.GetInt("broker.status-port")) + statusAdapter := http.New( + http.Components{Ctx: ctx.WithField("adapter", "handler-status")}, + http.Options{NetAddr: statusAddr, Timeout: time.Second * 5}, + ) + statusAdapter.Bind(http.Healthz{}) + statusAdapter.Bind(http.StatusPage{}) + // Storage var db broker.NetworkController dbString := viper.GetString("broker.database") @@ -95,45 +75,17 @@ and personalized devices (with their network session keys) with the router. ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } - broker := broker.New(db, ctx) - - // Bring the service to life - - // Listen to uplink - go func() { - for { - packet, an, err := rtrAdapter.Next() - if err != nil { - ctx.WithError(err).Error("Could not retrieve uplink") - continue - } - go func(packet []byte, an core.AckNacker) { - if err := broker.HandleUp(packet, an, hdlAdapter); err != nil { - // We can't do anything with this packet, so we're ignoring it. - ctx.WithError(err).Debug("Could not process uplink") - } - }(packet, an) - } - }() - - // List to handler registrations - go func() { - for { - reg, an, err := hdlAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Error("Could not retrieve registration") - continue - } - go func(reg core.Registration, an core.AckNacker) { - if err := broker.Register(reg, an); err != nil { - // We can't do anything with this registration, so we're ignoring it. - ctx.WithError(err).Debug("Could not process registration") - } - }(reg, an) - } - }() + // Broker + netAddr := fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")) + broker := broker.New( + broker.Components{Ctx: ctx, NetworkController: db}, + broker.Options{NetAddr: netAddr}, + ) - <-make(chan bool) + // Go + if err := broker.Start(); err != nil { + ctx.WithError(err).Fatal("Broker has fallen...") + } }, } @@ -143,18 +95,13 @@ func init() { brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) - brokerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") + brokerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") - viper.BindPFlag("broker.status-bind-address", brokerCmd.Flags().Lookup("status-bind-address")) + viper.BindPFlag("broker.status-address", brokerCmd.Flags().Lookup("status-address")) viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) - brokerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from routers") - brokerCmd.Flags().Int("uplink-port", 1881, "The port for the uplink") - viper.BindPFlag("broker.uplink-bind-address", brokerCmd.Flags().Lookup("uplink-bind-address")) - viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) - - brokerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from brokers") - brokerCmd.Flags().Int("downlink-port", 1781, "The port for the downlink") - viper.BindPFlag("broker.downlink-bind-address", brokerCmd.Flags().Lookup("downlink-bind-address")) - viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) + brokerCmd.Flags().String("server-address", "", "The IP address to listen for uplink and downlink messages") + brokerCmd.Flags().Int("server-port", 1881, "The main communication port") + viper.BindPFlag("broker.server-address", brokerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("broker.server-port", brokerCmd.Flags().Lookup("server-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index f68da29dc..806478d6a 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -11,12 +11,11 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" - httpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - mqttHandlers "github.com/TheThingsNetwork/ttn/core/adapters/mqtt/handlers" "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" + "google.golang.org/grpc" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -49,55 +48,16 @@ The default handler is the bridge between The Things Network and applications. Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - // ----- Start Adapters - brkNet := fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")) - brkAdapter, err := http.NewAdapter(brkNet, nil, ctx.WithField("adapter", "broker-adapter")) - if err != nil { - ctx.WithError(err).Fatal("Could not start broker adapter") - } - brkAdapter.Bind(httpHandlers.Collect{}) - - mqttClient, cherr, err := mqtt.NewClient("handler-client", viper.GetString("handler.mqtt-broker"), mqtt.TCP) - if err != nil { - ctx.WithError(err).Fatal("Could not start MQTT client") - } - - appAdapter := mqtt.NewAdapter(mqttClient, ctx.WithField("adapter", "app-adapter")) - appAdapter.Bind(mqttHandlers.Activation{}) - appAdapter.Bind(mqttHandlers.Downlink{}) - - // Reconnect on failures - go func() { - monitor: - for { - ctx.WithError(<-cherr).Debug("Connection lost on mqtt adapter. Trying to reconnect") - var delay time.Duration = 25 * time.Millisecond - for { - if err := appAdapter.Reconnect(); err != nil { - ctx.WithError(err).Debug("Unable to reconnect") - delay *= 10 - if delay > 10000*delay { - ctx.Fatal("All attempts to reconnect failed") - } - } else { - continue monitor - } - <-time.After(delay * time.Millisecond) - } - } - }() - - if viper.GetInt("handler.status-port") > 0 { - statusNet := fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) - statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Status Adapter") - } - statusAdapter.Bind(httpHandlers.StatusPage{}) - statusAdapter.Bind(httpHandlers.Healthz{}) - } - // Instantiate in-memory devices storage + // Status & Health + statusAddr := fmt.Sprintf("%s:%d", viper.GetString("handler.status-address"), viper.GetInt("handler.status-port")) + statusAdapter := http.New( + http.Components{Ctx: ctx.WithField("adapter", "handler-status")}, + http.Options{NetAddr: statusAddr, Timeout: time.Second * 5}, + ) + statusAdapter.Bind(http.Healthz{}) + statusAdapter.Bind(http.StatusPage{}) + // In-memory devices storage var devicesDB handler.DevStorage devDBString := viper.GetString("handler.dev-database") @@ -119,8 +79,7 @@ The default handler is the bridge between The Things Network and applications. ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local devices storage") } - // Instantiate in-memory packets storage - + // In-memory packets storage var packetsDB handler.PktStorage pktDBString := viper.GetString("handler.pkt-database") @@ -142,70 +101,41 @@ The default handler is the bridge between The Things Network and applications. ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local packets storage") } - // Instantiate the broker to which is bound the handler - broker := http.NewRecipient(viper.GetString("handler.ttn-broker"), "PUT") - // Instantiate the actual handler - handler := handler.New(devicesDB, packetsDB, broker, ctx) + // BrokerClient + brokerConn, err := grpc.Dial(viper.GetString("handler.ttn-broker"), grpc.WithInsecure(), grpc.WithTimeout(time.Second*2)) + if err != nil { + return ctx.WithError(err).Fatal("Could not dial broker") + } + defer brokerConn.Close() + broker := core.NewBrokerClient(brokerConn) + + // Handler + handler := handler.New( + handler.Components{ + Ctx: ctx, + DevStorage: devicesDB, + PktStorage: packetsDB, + Broker: broker, + }, + handler.Options{ + NetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + }, + ) + + // MQTT Client + mqttClient, chmsg, err := mqtt.NewClient( + "handler-client", + viper.GetString("handler-mqtt-broker"), + ctx.WithField("adapter", "app-adapter"), + ) + if err != nil { + ctx.WithError(err).Fatal("Could not start MQTT client") + } + appAdapter := mqtt.New( // Bring the service to life - // Listen uplink - go func() { - for { - packet, an, err := brkAdapter.Next() - if err != nil { - ctx.WithError(err).Warn("Could not get next packet fom brokers") - continue - } - - go func(packet []byte, an core.AckNacker) { - if err := handler.HandleUp(packet, an, appAdapter); err != nil { - // We can't do anything with this packet, so we're ignoring it. - ctx.WithError(err).Debug("Could not process packet from brokers") - } - }(packet, an) - } - }() - - // Listen downlink - go func() { - for { - packet, an, err := appAdapter.Next() - if err != nil { - ctx.WithError(err).Warn("Could not get next packet fom applications") - continue - } - - go func(packet []byte, an core.AckNacker) { - if err := handler.HandleDown(packet, an, brkAdapter); err != nil { - // We can't do anything with this packet, so we're ignoring it. - ctx.WithError(err).Debug("Could not process packet from applications") - } - }(packet, an) - } - }() - - // Listen registrations - go func() { - for { - reg, an, err := appAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Warn("Could not get next registration from applications") - continue - } - - go func(reg core.Registration, an core.AckNacker, s core.Subscriber) { - if err := handler.Register(reg, an, s); err != nil { - // We can't do anything with this registration, so we're ignoring it. - ctx.WithError(err).Warn("Could not process registration from applications") - } - }(reg, an, brkAdapter) - } - }() - - // Wait - <-make(chan bool) }, } diff --git a/cmd/router.go b/cmd/router.go index 1e20821fd..dc4ec3e75 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -10,10 +10,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - httpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/http/handlers" "github.com/TheThingsNetwork/ttn/core/adapters/udp" - udpHandlers "github.com/TheThingsNetwork/ttn/core/adapters/udp/handlers" "github.com/TheThingsNetwork/ttn/core/components/router" "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/utils/stats" From ffd3a68d632c8e9188d3d53b78f585dbf0a00220 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 20:03:05 +0100 Subject: [PATCH 1088/2266] [refactor/grpc] Allow wiring between handler and mqtt adapter --- core/adapters/mqtt/mqtt.go | 29 ++++--- core/adapters/mqtt/mqtt_test.go | 134 ++++++++++++++++---------------- 2 files changed, 84 insertions(+), 79 deletions(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index bf8ade65f..35499ba97 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -19,20 +19,24 @@ import ( "google.golang.org/grpc" ) +// Interface defines a public interface for the mqtt adapter +type Interface interface { + core.AppClient + Start(inMsg <-chan Msg, handler core.HandlerServer) +} + type adapter struct { Components } // Components defines a structure to make the instantiation easier to read type Components struct { - Handler core.HandlerServer - Client Client - Ctx log.Interface + Client Client + Ctx log.Interface } // Options defines a structure to make the instantiation easier to read type Options struct { - InMsg <-chan Msg } // Msg are emitted by an MQTT subscriber towards the adapter @@ -53,10 +57,13 @@ type msgType byte // New constructs an mqtt adapter responsible for making the bridge between the handler and // application. -func New(c Components, o Options) core.AppClient { - a := adapter{Components: c} - go a.consumeMQTTMsg(o.InMsg) - return a +func New(c Components, o Options) Interface { + return adapter{Components: c} +} + +// Start eventually launches the mqtt message internal consumer +func (a adapter) Start(inMsg <-chan Msg, handler core.HandlerServer) { + go a.consumeMQTTMsg(inMsg, handler) } // HandleData implements the core.AppClient interface @@ -116,14 +123,14 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp // consumeMQTTMsg processes incoming messages from MQTT broker. // // It runs in its own goroutine -func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { +func (a adapter) consumeMQTTMsg(chmsg <-chan Msg, handler core.HandlerServer) { a.Ctx.Debug("Start consuming MQTT messages") for msg := range chmsg { switch msg.Type { case Down: req, err := handleDataDown(msg) if err == nil { - _, err = a.Handler.HandleDataDown(context.Background(), req) + _, err = handler.HandleDataDown(context.Background(), req) } if err != nil { a.Ctx.WithError(err).Debug("Unable to consume data down") @@ -131,7 +138,7 @@ func (a adapter) consumeMQTTMsg(chmsg <-chan Msg) { case ABP: req, err := handleABP(msg) if err == nil { - _, err = a.Handler.SubscribePersonalized(context.Background(), req) + _, err = handler.SubscribePersonalized(context.Background(), req) } if err != nil { a.Ctx.WithError(err).Debug("Unable to consume ABP") diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 50b351907..aea7211bf 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -425,14 +425,15 @@ func TestConsumeMQTTMsg(t *testing.T) { Desc(t, "Consume Valid MsgDown") // Build - chmsg := make(chan Msg) - options := Options{InMsg: chmsg} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } - _ = New(components, options) + adapter := New(components, options) + handler := mocks.NewHandlerServer() + chmsg := make(chan Msg) + adapter.Start(chmsg, handler) wantDown := &core.DataDownHandlerReq{ Payload: []byte("patate"), @@ -449,8 +450,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -462,14 +463,15 @@ func TestConsumeMQTTMsg(t *testing.T) { Desc(t, "Consume invalid MsgDown") // Build - chmsg := make(chan Msg) - options := Options{InMsg: chmsg} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } - _ = New(components, options) + adapter := New(components, options) + handler := mocks.NewHandlerServer() + chmsg := make(chan Msg) + adapter.Start(chmsg, handler) var wantDown *core.DataDownHandlerReq var wantABP *core.ABPSubHandlerReq @@ -482,8 +484,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -495,14 +497,15 @@ func TestConsumeMQTTMsg(t *testing.T) { Desc(t, "Consume Valid MsgABP") // Build - chmsg := make(chan Msg) - options := Options{InMsg: chmsg} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } - _ = New(components, options) + adapter := New(components, options) + handler := mocks.NewHandlerServer() + chmsg := make(chan Msg) + adapter.Start(chmsg, handler) var wantDown *core.DataDownHandlerReq wantABP := &core.ABPSubHandlerReq{ @@ -524,8 +527,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -537,14 +540,15 @@ func TestConsumeMQTTMsg(t *testing.T) { Desc(t, "Consume invalid MsgABP") // Build - chmsg := make(chan Msg) - options := Options{InMsg: chmsg} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } - _ = New(components, options) + adapter := New(components, options) + handler := mocks.NewHandlerServer() + chmsg := make(chan Msg) + adapter.Start(chmsg, handler) var wantDown *core.DataDownHandlerReq var wantABP *core.ABPSubHandlerReq @@ -557,8 +561,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -570,14 +574,15 @@ func TestConsumeMQTTMsg(t *testing.T) { Desc(t, "Consume Invalid Message Type") // Build - chmsg := make(chan Msg) - options := Options{InMsg: chmsg} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } - _ = New(components, options) + adapter := New(components, options) + handler := mocks.NewHandlerServer() + chmsg := make(chan Msg) + adapter.Start(chmsg, handler) var wantDown *core.DataDownHandlerReq var wantABP *core.ABPSubHandlerReq @@ -590,8 +595,8 @@ func TestConsumeMQTTMsg(t *testing.T) { } // Checks - Check(t, wantDown, components.Handler.(*mocks.HandlerServer).InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, components.Handler.(*mocks.HandlerServer).InSubscribePersonalized.Req, "Handler Subscriptions") + Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") + Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -603,11 +608,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Invalid AppReq -> Empty payload") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -639,11 +643,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Invalid AppReq -> Invalid AppEUI") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -675,11 +678,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Invalid AppReq -> Invalid DevEUI") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -711,11 +713,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Invalid AppReq -> No Metadata") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -747,11 +748,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Invalid AppReq -> Nil AppReq") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) @@ -778,11 +778,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Valid AppReq, Fail to Publish") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } components.Client.(*MockClient).Failures["Publish"] = fmt.Errorf("Mock Error") adapter := New(components, options) @@ -823,11 +822,10 @@ func TestHandleData(t *testing.T) { Desc(t, "Handle Valid AppReq, Publish successful") // Build - options := Options{InMsg: nil} + options := Options{} components := Components{ - Client: NewMockClient(), - Handler: mocks.NewHandlerServer(), - Ctx: GetLogger(t, "MQTT Adapter"), + Client: NewMockClient(), + Ctx: GetLogger(t, "MQTT Adapter"), } adapter := New(components, options) msg := core.DataUpAppReq{Payload: []byte("patate"), Metadata: []core.AppMetadata{}} From 22d33c4e79c906d60ee447032159f225080823ce Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 20:48:10 +0100 Subject: [PATCH 1089/2266] [refactor/grpc] Make the broker runs two grpc servers --- core/components/broker/broker.go | 27 ++++++++++++++++++++++----- core/components/broker/broker_test.go | 15 ++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index d34045b13..76848bf4d 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -20,7 +20,8 @@ import ( // component implements the core.BrokerServer interface type component struct { Components - NetAddr string + NetAddrUp string + NetAddrDown string } // Components defines a structure to make the instantiation easier to read @@ -31,7 +32,8 @@ type Components struct { // Options defines a structure to make the instantiation easier to read type Options struct { - NetAddr string + NetAddrUp string + NetAddrDown string } // Interface defines the Broker interface @@ -42,12 +44,17 @@ type Interface interface { // New construct a new Broker component func New(c Components, o Options) Interface { - return component{Components: c, NetAddr: o.NetAddr} + return component{Components: c, NetAddrUp: o.NetAddrUp, NetAddrDown: o.NetAddrDown} } // Start actually runs the component and starts the rpc server func (b component) Start() error { - conn, err := net.Listen("tcp", b.NetAddr) + connUp, err := net.Listen("tcp", b.NetAddrUp) + if err != nil { + return errors.New(errors.Operational, err) + } + + connDown, err := net.Listen("tcp", b.NetAddrDown) if err != nil { return errors.New(errors.Operational, err) } @@ -55,7 +62,17 @@ func (b component) Start() error { server := grpc.NewServer() core.RegisterBrokerServer(server, b) - if err := server.Serve(conn); err != nil { + cherr := make(chan error) + + go func() { + cherr <- server.Serve(connUp) + }() + + go func() { + cherr <- server.Serve(connDown) + }() + + if err := <-cherr; err != nil { return errors.New(errors.Operational, err) } return nil diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 2a8f5cf01..bef1f7595 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -1019,11 +1019,16 @@ func TestDialerCloser(t *testing.T) { } func TestStart(t *testing.T) { - - broker := New(Components{ - Ctx: GetLogger(t, "Broker"), - NetworkController: NewMockNetworkController(), - }, Options{NetAddr: "localhost:8887"}) + broker := New( + Components{ + Ctx: GetLogger(t, "Broker"), + NetworkController: NewMockNetworkController(), + }, + Options{ + NetAddrUp: "localhost:8883", + NetAddrDown: "localhost:8884", + }, + ) cherr := make(chan error) go func() { From 530178ed8c99e44bad4bba079608bbd7ff702425 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 20:48:40 +0100 Subject: [PATCH 1090/2266] [refactor/grpc] Finalize rewriting commands --- cmd/broker.go | 19 ++++--- cmd/handler.go | 58 +++++++++++--------- cmd/router.go | 143 +++++++++++++++++++++++-------------------------- 3 files changed, 111 insertions(+), 109 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 81acd862b..f1745bab3 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -76,10 +76,12 @@ and personalized devices (with their network session keys) with the router. } // Broker - netAddr := fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")) broker := broker.New( broker.Components{Ctx: ctx, NetworkController: db}, - broker.Options{NetAddr: netAddr}, + broker.Options{ + NetAddrUp: fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-address"), viper.GetInt("broker.uplink-port")), + NetAddrDown: fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-address"), viper.GetInt("broker.downlink-port")), + }, ) // Go @@ -100,8 +102,13 @@ func init() { viper.BindPFlag("broker.status-address", brokerCmd.Flags().Lookup("status-address")) viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) - brokerCmd.Flags().String("server-address", "", "The IP address to listen for uplink and downlink messages") - brokerCmd.Flags().Int("server-port", 1881, "The main communication port") - viper.BindPFlag("broker.server-address", brokerCmd.Flags().Lookup("server-address")) - viper.BindPFlag("broker.server-port", brokerCmd.Flags().Lookup("server-port")) + brokerCmd.Flags().String("uplink-address", "", "The IP address to listen for uplink messages from routers") + brokerCmd.Flags().Int("uplink-port", 1881, "The main communication port") + viper.BindPFlag("broker.uplink-address", brokerCmd.Flags().Lookup("uplink-address")) + viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) + + brokerCmd.Flags().String("downlink-address", "", "The IP address to listen for downlink messages from handler") + brokerCmd.Flags().Int("downlink-port", 1781, "The main communication port") + viper.BindPFlag("broker.downlink-address", brokerCmd.Flags().Lookup("downlink-address")) + viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index 806478d6a..f67422b07 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -15,9 +15,9 @@ import ( "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" - "google.golang.org/grpc" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc" ) // handlerCmd represents the handler command @@ -30,7 +30,7 @@ The default handler is the bridge between The Things Network and applications. PreRun: func(cmd *cobra.Command, args []string) { var statusServer string if viper.GetInt("handler.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-bind-address"), viper.GetInt("handler.status-port")) + statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-address"), viper.GetInt("handler.status-port")) stats.Initialize() } else { statusServer = "disabled" @@ -40,7 +40,7 @@ The default handler is the bridge between The Things Network and applications. "devicesDatabase": viper.GetString("handler.dev-database"), "packetsDatabase": viper.GetString("handler.pkt-database"), "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("handler.uplink-bind-address"), viper.GetInt("handler.uplink-port")), + "server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), "ttn-broker": viper.GetString("handler.ttn-broker"), "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") @@ -101,41 +101,47 @@ The default handler is the bridge between The Things Network and applications. ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local packets storage") } - // BrokerClient - brokerConn, err := grpc.Dial(viper.GetString("handler.ttn-broker"), grpc.WithInsecure(), grpc.WithTimeout(time.Second*2)) + brokerConn, err := grpc.Dial(viper.GetString("handler.ttn-broker"), grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) if err != nil { - return ctx.WithError(err).Fatal("Could not dial broker") + ctx.WithError(err).Fatal("Could not dial broker") } defer brokerConn.Close() broker := core.NewBrokerClient(brokerConn) + // MQTT Client & adapter + mqttClient, chmsg, err := mqtt.NewClient( + "handler-client", + viper.GetString("handler.mqtt-broker"), + ctx.WithField("adapter", "app-adapter"), + ) + if err != nil { + ctx.WithError(err).Fatal("Could not start MQTT client") + } + appAdapter := mqtt.New( + mqtt.Components{Ctx: ctx.WithField("adapter", "app-adapter"), Client: mqttClient}, + mqtt.Options{}, + ) + // Handler handler := handler.New( handler.Components{ - Ctx: ctx, + Ctx: ctx, DevStorage: devicesDB, PktStorage: packetsDB, - Broker: broker, + Broker: broker, + AppAdapter: appAdapter, }, handler.Options{ NetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), }, ) - // MQTT Client - mqttClient, chmsg, err := mqtt.NewClient( - "handler-client", - viper.GetString("handler-mqtt-broker"), - ctx.WithField("adapter", "app-adapter"), - ) - if err != nil { - ctx.WithError(err).Fatal("Could not start MQTT client") + // Go + appAdapter.Start(chmsg, handler) + if err := handler.Start(); err != nil { + ctx.WithError(err).Fatal("Handler has fallen...") } - appAdapter := mqtt.New( - - // Bring the service to life - }, } @@ -147,15 +153,15 @@ func init() { viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) - handlerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") + handlerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") - viper.BindPFlag("handler.status-bind-address", handlerCmd.Flags().Lookup("status-bind-address")) + viper.BindPFlag("handler.status-address", handlerCmd.Flags().Lookup("status-address")) viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) - handlerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from brokers") - handlerCmd.Flags().Int("uplink-port", 1882, "The port for the uplink") - viper.BindPFlag("handler.uplink-bind-address", handlerCmd.Flags().Lookup("uplink-bind-address")) - viper.BindPFlag("handler.uplink-port", handlerCmd.Flags().Lookup("uplink-port")) + handlerCmd.Flags().String("server-address", "", "The IP address to listen for uplink messages from brokers") + handlerCmd.Flags().Int("server-port", 1882, "The port for the uplink") + viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker (uplink)") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) diff --git a/cmd/router.go b/cmd/router.go index dc4ec3e75..704d3ea3f 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -10,6 +10,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/udp" "github.com/TheThingsNetwork/ttn/core/components/router" "github.com/TheThingsNetwork/ttn/core/dutycycle" @@ -17,6 +18,7 @@ import ( "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc" ) // routerCmd represents the router command @@ -30,7 +32,7 @@ the gateway's duty cycle is (almost) full.`, PreRun: func(cmd *cobra.Command, args []string) { var statusServer string if viper.GetInt("router.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) + statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-address"), viper.GetInt("router.status-port")) stats.Initialize() } else { statusServer = "disabled" @@ -41,46 +43,25 @@ the gateway's duty cycle is (almost) full.`, "db-brokers": viper.GetString("router.db_brokers"), "db-gateways": viper.GetString("router.db_gateways"), "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")), - "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")), + "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-address"), viper.GetInt("router.uplink-port")), + "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), "brokers": viper.GetString("router.brokers"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - gtwNet := fmt.Sprintf("%s:%d", viper.GetString("router.uplink-bind-address"), viper.GetInt("router.uplink-port")) - gtwAdapter, err := udp.NewAdapter(gtwNet, ctx.WithField("adapter", "gateway-semtech")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Gateway Adapter") - } - gtwAdapter.Bind(udpHandlers.Semtech{}) - - var brokers []core.Recipient - brokersStr := strings.Split(viper.GetString("router.brokers"), ",") - for i := range brokersStr { - url := strings.Trim(brokersStr[i], " ") - brokers = append(brokers, http.NewRecipient(url, "POST")) - } - - brkNet := fmt.Sprintf("%s:%d", viper.GetString("router.downlink-bind-address"), viper.GetInt("router.downlink-port")) - brkAdapter, err := http.NewAdapter(brkNet, brokers, ctx.WithField("adapter", "broker-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Broker Adapter") - } - - if viper.GetInt("router.status-port") > 0 { - statusNet := fmt.Sprintf("%s:%d", viper.GetString("router.status-bind-address"), viper.GetInt("router.status-port")) - statusAdapter, err := http.NewAdapter(statusNet, nil, ctx.WithField("adapter", "status-http")) - if err != nil { - ctx.WithError(err).Fatal("Could not start Status Adapter") - } - statusAdapter.Bind(httpHandlers.StatusPage{}) - statusAdapter.Bind(httpHandlers.Healthz{}) - } + // Status & Health + statusAddr := fmt.Sprintf("%s:%d", viper.GetString("router.status-address"), viper.GetInt("router.status-port")) + statusAdapter := http.New( + http.Components{Ctx: ctx.WithField("adapter", "router-status")}, + http.Options{NetAddr: statusAddr, Timeout: time.Second * 5}, + ) + statusAdapter.Bind(http.Healthz{}) + statusAdapter.Bind(http.StatusPage{}) + // In-memory packet storage var db router.Storage - dbString := viper.GetString("router.db_brokers") switch { case strings.HasPrefix(dbString, "boltdb:"): @@ -100,8 +81,8 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } + // Duty Manager var dm dutycycle.DutyManager - dmString := viper.GetString("router.db_gateways") switch { case strings.HasPrefix(dmString, "boltdb:"): @@ -121,45 +102,53 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } - router := router.New(db, dm, ctx) - - // Bring the service to life - - // Listen uplink - go func() { - for { - packet, an, err := gtwAdapter.Next() - if err != nil { - ctx.WithError(err).Warn("Could not get next packet from gateway") - continue - } - go func(packet []byte, an core.AckNacker) { - if err := router.HandleUp(packet, an, brkAdapter); err != nil { - // We can't do anything with this packet, so we're ignoring it. - ctx.WithError(err).Debug("Could not process packet from gateway") - } - }(packet, an) - } - }() - - // Listen broker registrations - go func() { - for { - reg, an, err := brkAdapter.NextRegistration() - if err != nil { - ctx.WithError(err).Warn("Could not get next registration from broker") - continue - } - go func(reg core.Registration, an core.AckNacker) { - if err := router.Register(reg, an); err != nil { - // We can't do anything with this registration, so we're ignoring it. - ctx.WithError(err).Debug("Could not process registration from broker") - } - }(reg, an) + // Broker clients + var brokers []core.BrokerClient + brokersStr := strings.Split(viper.GetString("router.brokers"), ",") + for i := range brokersStr { + url := strings.Trim(brokersStr[i], " ") + brokerConn, err := grpc.Dial(url, grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) + if err != nil { + ctx.WithError(err).Fatal("Could not dial broker") } - }() + defer brokerConn.Close() + broker := core.NewBrokerClient(brokerConn) + brokers = append(brokers, broker) + } - <-make(chan bool) + // Router + router := router.New( + router.Components{ + Ctx: ctx, + DutyManager: dm, + Brokers: brokers, + Storage: db, + }, + router.Options{ + NetAddr: fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), + }, + ) + + // Gateway Adapter + gtwNet := fmt.Sprintf("%s:%d", viper.GetString("router.uplink-address"), viper.GetInt("router.uplink-port")) + err := udp.Start( + udp.Components{ + Ctx: ctx.WithField("adapter", "gateway-semtech"), + Router: router, + }, + udp.Options{ + NetAddr: gtwNet, + MaxReconnectionDelay: 25 * 10000 * time.Millisecond, + }, + ) + if err != nil { + ctx.WithError(err).Fatal("Could not start Gateway Adapter") + } + + // Go + if err := router.Start(); err != nil { + ctx.WithError(err).Fatal("Router has fallen...") + } }, } @@ -172,19 +161,19 @@ func init() { routerCmd.Flags().String("db_gateways", "boltdb:/tmp/ttn_router_gateways.db", "Database connection of managed gateways") viper.BindPFlag("router.db_gateways", routerCmd.Flags().Lookup("db_gateways")) - routerCmd.Flags().String("status-bind-address", "localhost", "The IP address to listen for serving status information") + routerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") - viper.BindPFlag("router.status-bind-address", routerCmd.Flags().Lookup("status-bind-address")) + viper.BindPFlag("router.status-address", routerCmd.Flags().Lookup("status-address")) viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) - routerCmd.Flags().String("uplink-bind-address", "", "The IP address to listen for uplink messages from gateways") + routerCmd.Flags().String("uplink-address", "", "The IP address to listen for uplink messages from gateways") routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") - viper.BindPFlag("router.uplink-bind-address", routerCmd.Flags().Lookup("uplink-bind-address")) + viper.BindPFlag("router.uplink-address", routerCmd.Flags().Lookup("uplink-address")) viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) - routerCmd.Flags().String("downlink-bind-address", "", "The IP address to listen for downlink messages from routers") + routerCmd.Flags().String("downlink-address", "", "The IP address to listen for downlink messages from routers") routerCmd.Flags().Int("downlink-port", 1780, "The port for the downlink") - viper.BindPFlag("router.downlink-bind-address", routerCmd.Flags().Lookup("downlink-bind-address")) + viper.BindPFlag("router.downlink-address", routerCmd.Flags().Lookup("downlink-address")) viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) routerCmd.Flags().String("brokers", ":1881", "Comma-separated list of brokers") From 0c1639fd24f59eda471f92c018ba8c3fc2a43896 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 20:59:29 +0100 Subject: [PATCH 1091/2266] [refactor/grpc] Clean repo and add ttnctl --- cmd/handler.go | 2 +- core/adapters/mqtt/mqtt.go | 2 +- core/adapters/mqtt/mqtt_test.go | 4 +- core/components/broker/broker.go | 3 +- integration/mocker/main.go | 57 ----- integration/scheduler/Dockerfile | 16 -- integration/scheduler/schedule.json | 18 -- integration/scheduler/scheduler.go | 42 --- integration/udp_debugger/Dockerfile | 12 - integration/udp_debugger/udp_debugger.go | 53 ---- simulators/gateway/FORWARDER.md | 89 ------- simulators/gateway/IMITATOR.md | 8 - simulators/gateway/doc.go | 8 - simulators/gateway/forwarder.go | 241 ------------------ simulators/gateway/forwarder_test.go | 309 ----------------------- simulators/gateway/imitator.go | 183 -------------- simulators/gateway/utils.go | 136 ---------- simulators/gateway/utils_test.go | 119 --------- simulators/node/node.go | 120 --------- simulators/node/node_test.go | 35 --- ttnctl/AUTHORS | 11 + ttnctl/LICENSE | 21 ++ ttnctl/cmd/downlink.go | 69 +++++ ttnctl/cmd/register.go | 94 +++++++ ttnctl/cmd/root.go | 68 +++++ ttnctl/cmd/uplink.go | 193 ++++++++++++++ ttnctl/main.go | 21 ++ ttnctl/mqtt/client.go | 51 ++++ ttnctl/util/parse.go | 44 ++++ ttnctl/util/random.go | 89 +++++++ utils/readwriter/readwriter_test.go | 1 - 31 files changed, 667 insertions(+), 1452 deletions(-) delete mode 100644 integration/mocker/main.go delete mode 100644 integration/scheduler/Dockerfile delete mode 100644 integration/scheduler/schedule.json delete mode 100644 integration/scheduler/scheduler.go delete mode 100644 integration/udp_debugger/Dockerfile delete mode 100644 integration/udp_debugger/udp_debugger.go delete mode 100644 simulators/gateway/FORWARDER.md delete mode 100644 simulators/gateway/IMITATOR.md delete mode 100644 simulators/gateway/doc.go delete mode 100644 simulators/gateway/forwarder.go delete mode 100644 simulators/gateway/forwarder_test.go delete mode 100644 simulators/gateway/imitator.go delete mode 100644 simulators/gateway/utils.go delete mode 100644 simulators/gateway/utils_test.go delete mode 100644 simulators/node/node.go delete mode 100644 simulators/node/node_test.go create mode 100644 ttnctl/AUTHORS create mode 100644 ttnctl/LICENSE create mode 100644 ttnctl/cmd/downlink.go create mode 100644 ttnctl/cmd/register.go create mode 100644 ttnctl/cmd/root.go create mode 100644 ttnctl/cmd/uplink.go create mode 100644 ttnctl/main.go create mode 100644 ttnctl/mqtt/client.go create mode 100644 ttnctl/util/parse.go create mode 100644 ttnctl/util/random.go diff --git a/cmd/handler.go b/cmd/handler.go index f67422b07..ae4a5f3cc 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -158,7 +158,7 @@ func init() { viper.BindPFlag("handler.status-address", handlerCmd.Flags().Lookup("status-address")) viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) - handlerCmd.Flags().String("server-address", "", "The IP address to listen for uplink messages from brokers") + handlerCmd.Flags().String("server-address", "localhost", "The IP address to listen for uplink messages from brokers") handlerCmd.Flags().Int("server-port", 1882, "The port for the uplink") viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 35499ba97..9cc714151 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -109,7 +109,7 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) err = a.Client.Publish(&client.PublishOptions{ QoS: mqtt.QoS2, - Retain: true, + Retain: false, TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)), Message: msg, }) diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index aea7211bf..63e31da04 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -793,7 +793,7 @@ func TestHandleData(t *testing.T) { var wantRes *core.DataAppRes var wantPub = &client.PublishOptions{ QoS: mqtt.QoS2, - Retain: true, + Retain: false, TopicName: []byte("0102030405060708/devices/0000000001020304/up"), Message: data, } @@ -836,7 +836,7 @@ func TestHandleData(t *testing.T) { var wantRes *core.DataAppRes var wantPub = &client.PublishOptions{ QoS: mqtt.QoS2, - Retain: true, + Retain: false, TopicName: []byte("0102030405060708/devices/0000000001020304/up"), Message: data, } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 76848bf4d..f0ca450e7 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -233,8 +233,9 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubB } copy(nwkSKey[:], req.NwkSKey) - re := regexp.MustCompile("^[-\\w]+\\.([-\\w]+\\.?)+:\\d+$") + re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") if !re.MatchString(req.HandlerNet) { + b.Ctx.WithField("addr", req.HandlerNet).Debug("Invalid address") return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) } diff --git a/integration/mocker/main.go b/integration/mocker/main.go deleted file mode 100644 index 259fe6925..000000000 --- a/integration/mocker/main.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "os" - "strings" - - "github.com/TheThingsNetwork/ttn/simulators/gateway" - "github.com/TheThingsNetwork/ttn/simulators/node" - "github.com/apex/log" - "github.com/apex/log/handlers/text" -) - -var ( - numNodes int - interval int -) - -func main() { - routers := parseOptions() - - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - - nodeCtx := log.WithFields(log.Fields{"Simulator": "Node"}) - gatewayCtx := log.WithFields(log.Fields{"Simulator": "Gateway"}) - - nodes := []node.LiveNode{} - - for i := 0; i < numNodes; i++ { - nodes = append(nodes, node.New(interval, nodeCtx)) - } - - gateway.MockRandomly(nodes, gatewayCtx, routers...) -} - -func parseOptions() (routers []string) { - var routersFlag string - flag.StringVar(&routersFlag, "routers", "", `Router addresses to which send packets. - For instance: 10.10.3.34:8080,thethingsnetwork.router.com:3000 - `) - - flag.IntVar(&interval, "interval", 500, "Average time (in milliseconds) between node messages") - flag.IntVar(&numNodes, "nodes", 10, "Number of nodes to simulate") - - flag.Parse() - - if routersFlag == "" { - panic("Need to provide at least one router address") - } - - routers = strings.Split(routersFlag, ",") - return -} diff --git a/integration/scheduler/Dockerfile b/integration/scheduler/Dockerfile deleted file mode 100644 index 7c79e4586..000000000 --- a/integration/scheduler/Dockerfile +++ /dev/null @@ -1,16 +0,0 @@ -# The container will assume to have a $ROUTERS env var defined - -# Go, because go is life -FROM golang:latest - -# Dependencies, everything on master -RUN go get "github.com/TheThingsNetwork/ttn/..." - -# Actual files to build -RUN mkdir ~/TheThingsNetwork -ADD . ~/TheThingsNetwork -WORKDIR ~/TheThingsNetwork - -# Build & Launch -RUN go build -o scheduler . -CMD ./scheduler --schedule "./schedule.json" --delay "1s" --routers $ROUTERS diff --git a/integration/scheduler/schedule.json b/integration/scheduler/schedule.json deleted file mode 100644 index 11334e73e..000000000 --- a/integration/scheduler/schedule.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "dev_addr": "14abcd42", - "payload": "The Things Network - #1", - "metadata": { - "rssi": -20 - } - }, - { - "dev_addr": "14abcd42", - "payload": "The Things Network - #2", - "metadata": { - "rssi": -20, - "datr": "SF7BW125", - "modu": "LORA" - } - } -] diff --git a/integration/scheduler/scheduler.go b/integration/scheduler/scheduler.go deleted file mode 100644 index cf3727a28..000000000 --- a/integration/scheduler/scheduler.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "flag" - "strings" - - "github.com/TheThingsNetwork/ttn/simulators/gateway" - "time" -) - -func main() { - routers, delay, schedule := parseOptions() - gateway.MockWithSchedule(schedule, delay, routers...) -} - -func parseOptions() (routers []string, delay time.Duration, schedule string) { - var routersFlag string - var delayFlag string - flag.StringVar(&routersFlag, "routers", "", `Router addresses to which send packets. - For instance: 10.10.3.34:8080,thethingsnetwork.router.com:3000 - `) - flag.StringVar(&delayFlag, "delay", "", `Interval of time between 2 sending. - For instance: 500ms - `) - flag.StringVar(&schedule, "schedule", "", "JSON file defining the packets to schedule") - flag.Parse() - - var err error - if delay, err = time.ParseDuration(delayFlag); err != nil { - panic(err) - } - - if routersFlag == "" { - panic("Need to provide at least one router address") - } - - routers = strings.Split(routersFlag, ",") - return -} diff --git a/integration/udp_debugger/Dockerfile b/integration/udp_debugger/Dockerfile deleted file mode 100644 index 138784c54..000000000 --- a/integration/udp_debugger/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -# Should be placed in root folder til ttn isn't available on master - -FROM golang:latest - -RUN go get github.com/apex/log -RUN go get github.com/brocaar/lorawan -RUN go get github.com/jacobsa/crypto/cmac -RUN mkdir -p /go/src/github.com/TheThingsNetwork/ttn -ADD . /go/src/github.com/TheThingsNetwork/ttn - -RUN go build -o listener github.com/TheThingsNetwork/ttn/integration/udp_debugger -CMD ./listener diff --git a/integration/udp_debugger/udp_debugger.go b/integration/udp_debugger/udp_debugger.go deleted file mode 100644 index 19491b001..000000000 --- a/integration/udp_debugger/udp_debugger.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package main - -import ( - "fmt" - "net" - - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" -) - -func main() { - addr, err := net.ResolveUDPAddr("udp", "0.0.0.0:33000") - if err != nil { - panic(err) - } - - conn, err := net.ListenUDP("udp", addr) - if err != nil { - panic(err) - } - - go func() { - for { - fmt.Printf("\n*******************************\n") - buf := make([]byte, 512) - n, addr, err := conn.ReadFromUDP(buf) - if err != nil { - fmt.Println(err) - continue - } - fmt.Println("Msg from", addr) - pkt := new(semtech.Packet) - if err := pkt.UnmarshalBinary(buf[:n]); err != nil { - fmt.Println(err) - continue - } - - fmt.Printf("Received %x from %x with token %x\n", pkt.Identifier, pkt.GatewayId, pkt.Token) - - if pkt.Payload == nil || len(pkt.Payload.RXPK) < 1 { - fmt.Println("Unexpected packet payload") - continue - } - fmt.Printf(pointer.DumpPStruct(pkt.Payload.RXPK[0], true)) - } - }() - - fmt.Println("Listening on 33000") - <-make(chan bool) -} diff --git a/simulators/gateway/FORWARDER.md b/simulators/gateway/FORWARDER.md deleted file mode 100644 index 24da6a71d..000000000 --- a/simulators/gateway/FORWARDER.md +++ /dev/null @@ -1,89 +0,0 @@ -Forwarder -------- - -### Behavior - -From the [packet -forwarder](https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT) -defined by Semtech we can extract the following behavior for the forwarder: - -- The forwarder is able to send two types of packet: - - `PUSH_DATA` packets with a payload coming from a device - - `PULL_DATA` packets use to maintain an established connection - -- The forwarder is able to receive three types of packet: - - `PUSH_ACK` packets which acknowledge a `PUSH_DATA` - - `PULL_ACK` packets which acknowledge a `PULL_DATA` - - `PULL_RESP` packets used to transfer a response down to the devices - -For the moment, we simply log down any missing acknowledgement. In a second time, we'll -consider re-emitting the corresponding packets. - -A forwarder instance does not presume of any activity, it is sleeping by default and is rather -stimulated by an external agent. - -We want the forwarder to log and store each downlink packet. However, any external agent can -still flush the forwarder to retrieve all stored packet and clear the forwarder internal buffer. -This way, the forwarder is nothing more than a forwarder while the handling logic is under the -control of a separated entity. - -When a downlink datagram is received it is stored unless it does not reflect a valid semtech -Packet (i.e., a `PUSH_ACK`, `PULL_ACK` or `PULL_RESP` with valid data). Any other data received -by the forwarder is ignored. - -### Interfaces - -```go -// New create a forwarder instance bound to a set of routers. -func NewForwarder (id string, routers ...io.ReadWriteCloser) (*Forwarder, error) - -// Flush spits out all downlink packet received by the forwarder since the last flush. -func (fwd *Forwarder) Flush() []semtech.Packet - -// Forward dispatch a packet to all connected routers. -func (fwd *Forwarder) Forward(packet semtech.Packet) error - -// Stats computes and return the forwarder statistics since it was created -func (fwd Forwarder) Stats() semtech.Stat - -// Stop terminate the forwarder activity. Closing all routers connections -func (fwd *Forwarder) Stop() error -``` - -### Stats - - Name | Type | Function ---------|----------|-------------------------------------------------------------- - *time* | *string* | *UTC 'system' time of the gateway, ISO 8601 'expanded' format* - *lati* | *number* | *GPS latitude of the gateway in degree (float, N is +)* - *long* | *number* | *GPS latitude of the gateway in degree (float, E is +)* - *alti* | *number* | *GPS altitude of the gateway in meter RX (integer)* - rxnb | number | Number of radio packets received (unsigned integer) - rxok | number | Number of radio packets received with a valid PHY CRC - rxfw | number | Number of radio packets forwarded (unsigned integer) - ackr | number | Percentage of upstream datagrams that were acknowledged - dwnb | number | Number of downlink datagrams received (unsigned integer) - txnb | number | Number of packets emitted (unsigned integer) - -##### rxnb -Incremented each time a packet is received from a device. In other words, any call to Forward -with a non-nil packet should incremented that stat. - -##### rxok -Incremented each time a packet is received from a device. Because the forwarder only simulate -what a real gateway would do, we do not consider a full device packet with a CRC and a PHY -payload. We only consider the payload and thus, rxok and rxnb should be seemingly the same. - -##### rxfw -This conveys a successful packet forwarding. It should be incremented once per packet received -from devices that has successfully been forwarded to routers (regardless of any ack from them). - -##### ackr -Computed using the number of forwarded packet that has been acknowledged and the total number -of forwarded packet. - -##### dwnb -Incremented each time a packet is received from a router. - -##### txnb -Incremented for each packet forwarded but also for each `PULL_DATA` packets sent. diff --git a/simulators/gateway/IMITATOR.md b/simulators/gateway/IMITATOR.md deleted file mode 100644 index f1f86a2c8..000000000 --- a/simulators/gateway/IMITATOR.md +++ /dev/null @@ -1,8 +0,0 @@ -Imitator --------- - -### Behavior - -The gateway's imitator will behave as is it a set of end-devices sending and receiving data to -and from a gateway. - diff --git a/simulators/gateway/doc.go b/simulators/gateway/doc.go deleted file mode 100644 index a579b6bc5..000000000 --- a/simulators/gateway/doc.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package gateway offers a dummy representation of a gateway. -// -// The package can be used to create a dummy gateway. -// Its former use is to provide a handy simulator for further testing of the whole network chain. -package gateway diff --git a/simulators/gateway/forwarder.go b/simulators/gateway/forwarder.go deleted file mode 100644 index a400518a6..000000000 --- a/simulators/gateway/forwarder.go +++ /dev/null @@ -1,241 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "fmt" - "io" - "time" - - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/apex/log" -) - -type Forwarder struct { - ctx log.Interface - Id [8]byte // Gateway's Identifier - alti int // GPS altitude in RX meters - upnb uint // Number of upstream datagrams sent - ackn uint // Number of upstream datagrams that were acknowledged - dwnb uint // Number of downlink datagrams received - lati float64 // GPS latitude, North is + - long float64 // GPS longitude, East is + - rxfw uint // Number of radio packets forwarded - rxnb uint // Number of radio packets received - adapters []io.ReadWriteCloser // List of downlink adapters - packets []semtech.Packet // Downlink packets received - acks map[[4]byte]uint // adapterIndex | packet.Identifier | packet.Token - commands chan command // Concurrent access on gateway stats - quit chan error // Adapter which loses connection spit here -} - -type commandName string -type command struct { - name commandName - data interface{} -} - -const ( - cmd_ACK commandName = "Acknowledged" - cmd_EMIT commandName = "Emitted" - cmd_RECVUP commandName = "Radio Packet Received" - cmd_RECVDWN commandName = "Dowlink Datagram Received" - cmd_FWD commandName = "Forwarded" - cmd_FLUSH commandName = "Flush" - cmd_STATS commandName = "Stats" -) - -var statTimer <-chan time.Time - -// NewForwarder create a forwarder instance bound to a set of routers. -func NewForwarder(id [8]byte, ctx log.Interface, adapters ...io.ReadWriteCloser) (*Forwarder, error) { - if len(adapters) == 0 { - return nil, fmt.Errorf("At least one adapter must be supplied") - } - - if len(adapters) > 255 { // cf fwd.acks - return nil, fmt.Errorf("Cannot connect more than 255 adapters") - } - - fwd := &Forwarder{ - Id: id, - ctx: ctx, - alti: 120, - lati: 53.3702, - long: 4.8952, - adapters: adapters, - packets: make([]semtech.Packet, 0), - acks: make(map[[4]byte]uint), - commands: make(chan command), - quit: make(chan error, len(adapters)), - } - - go fwd.handleCommands() - - statTimer = time.Tick(5 * time.Second) - - // Star listening to each adapter Read() method - for i, adapter := range fwd.adapters { - go fwd.listenAdapter(adapter, i) - } - - return fwd, nil -} - -// listenAdapter listen to incoming datagrams from an adapter. Non-valid packets are ignored. -func (fwd Forwarder) listenAdapter(adapter io.ReadWriteCloser, index int) { - for { - buf := make([]byte, 1024) - n, err := adapter.Read(buf) - fwd.ctx.WithField("nb bytes", n).Debug("bytes received by adapter") - if err != nil { - fwd.ctx.WithError(err).Error("Connection lost / closed") - fwd.quit <- err - return // Connection lost / closed - } - fwd.ctx.WithField("datagram", buf[:n]).Debug("unmarshalling datagram") - packet := new(semtech.Packet) - if err = packet.UnmarshalBinary(buf[:n]); err != nil { - fwd.ctx.WithError(err).Warn("Unable to unmarshal datagram to packet") - continue - } - - switch packet.Identifier { - case semtech.PUSH_ACK, semtech.PULL_ACK: - fwd.commands <- command{cmd_ACK, ackToken(index, *packet)} - case semtech.PULL_RESP: - fwd.commands <- command{cmd_RECVDWN, packet} - default: - fwd.ctx.WithField("packet", packet).Warn("Ignoring contingent packet") - } - } -} - -// handleCommands acts as a monitor between all goroutines that attempt to modify the forwarder -// attributes. All sensitive operations are done by commands sent through an appropriate channel. -// This method consumes commands from the channel until it's closed. -func (fwd *Forwarder) handleCommands() { - for cmd := range fwd.commands { - fwd.ctx.WithField("command", cmd.name).Debug("executing command") - - switch cmd.name { - case cmd_ACK: - token := cmd.data.([4]byte) - if fwd.acks[token] > 0 { - fwd.acks[token] -= 1 - fwd.ackn += 1 - } - case cmd_FWD: - fwd.rxfw += 1 - case cmd_EMIT: - token := cmd.data.([4]byte) - fwd.acks[token] += 1 - fwd.upnb += 1 - case cmd_RECVUP: - fwd.rxnb += 1 - case cmd_RECVDWN: - fwd.dwnb += 1 - fwd.packets = append(fwd.packets, *cmd.data.(*semtech.Packet)) - case cmd_FLUSH: - cmd.data.(chan []semtech.Packet) <- fwd.packets - fwd.packets = make([]semtech.Packet, 0) - case cmd_STATS: - var ackr float64 - if fwd.upnb > 0 { - ackr = float64(fwd.ackn) / float64(fwd.upnb) - } - cmd.data.(chan semtech.Stat) <- semtech.Stat{ - Ackr: &ackr, - Alti: pointer.Int(fwd.alti), - Dwnb: pointer.Uint(fwd.dwnb), - Lati: pointer.Float64(fwd.lati), - Long: pointer.Float64(fwd.long), - Rxfw: pointer.Uint(fwd.rxfw), - Rxnb: pointer.Uint(fwd.rxnb), - Rxok: pointer.Uint(fwd.rxnb), - Time: pointer.Time(time.Now()), - Txnb: pointer.Uint(0), - } - } - } -} - -// Forward dispatch a packet to all connected routers. -func (fwd Forwarder) Forward(rxpks ...semtech.RXPK) error { - fwd.commands <- command{cmd_RECVUP, nil} - - packet := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PUSH_DATA, - GatewayId: fwd.Id[:], - Token: genToken(), - Payload: &semtech.Payload{RXPK: rxpks}, - } - - select { - case <-statTimer: - stats := fwd.Stats() - packet.Payload.Stat = &stats - default: - } - - raw, err := packet.MarshalBinary() - if err != nil { - return err - } - - for i, adapter := range fwd.adapters { - n, err := adapter.Write(raw) - if err != nil { - return err - } - if n < len(raw) { - return fmt.Errorf("Packet was too long") - } - fwd.commands <- command{cmd_EMIT, ackToken(i, packet)} - } - - fwd.commands <- command{cmd_FWD, nil} - return nil -} - -// Flush spits out all downlink packet received by the forwarder since the last flush. -func (fwd Forwarder) Flush() []semtech.Packet { - chpkt := make(chan []semtech.Packet) - fwd.commands <- command{cmd_FLUSH, chpkt} - return <-chpkt -} - -// Stats computes and return the forwarder statistics since it was created -func (fwd Forwarder) Stats() semtech.Stat { - chstats := make(chan semtech.Stat) - fwd.commands <- command{cmd_STATS, chstats} - return <-chstats -} - -// Stop terminate the forwarder activity. Closing all routers connections -func (fwd Forwarder) Stop() error { - var errors []error - - // Close the uplink adapters - for _, adapter := range fwd.adapters { - err := adapter.Close() - if err != nil { - errors = append(errors, err) - } - } - - // Wait for each adapter to terminate - for range fwd.adapters { - <-fwd.quit - } - - close(fwd.commands) - - if len(errors) > 0 { - return fmt.Errorf("Unable to stop the forwarder: %+v", errors) - } - return nil -} diff --git a/simulators/gateway/forwarder_test.go b/simulators/gateway/forwarder_test.go deleted file mode 100644 index 98eda7f7b..000000000 --- a/simulators/gateway/forwarder_test.go +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "fmt" - "io" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/semtech" - . "github.com/TheThingsNetwork/ttn/utils/testing" - . "github.com/smartystreets/goconvey/convey" -) - -type fakeAdapter struct { - id string - written []byte - Downlink chan []byte -} - -func newFakeAdapter(id string) *fakeAdapter { - return &fakeAdapter{ - id: id, - written: []byte{}, - Downlink: make(chan []byte), - } -} - -// Write implement io.Writer interface -func (a *fakeAdapter) Write(p []byte) (int, error) { - a.written = p - return len(p), nil -} - -// Read implement io.Reader interface -func (a *fakeAdapter) Read(buf []byte) (int, error) { - raw, ok := <-a.Downlink - if !ok { - return 0, fmt.Errorf("Connection has been closed") - } - return copy(buf, raw), nil -} - -// Close implement io.Closer interface -func (a *fakeAdapter) Close() error { - close(a.Downlink) - return nil -} - -// generatePacket provides quick Packet generation for test purpose -func generatePacket(identifier byte, id [8]byte) semtech.Packet { - switch identifier { - case semtech.PUSH_DATA, semtech.PULL_DATA: - return semtech.Packet{ - Version: semtech.VERSION, - Token: genToken(), - Identifier: identifier, - GatewayId: id[:], - } - default: - return semtech.Packet{ - Version: semtech.VERSION, - Identifier: identifier, - Token: genToken(), - } - } -} - -// initForwarder is a little helper used to instance adapters and forwarder for test purpose -func initForwarder(t *testing.T, id [8]byte) (*Forwarder, *fakeAdapter, *fakeAdapter) { - a1, a2 := newFakeAdapter("adapter1"), newFakeAdapter("adapter2") - ctx := GetLogger(t, "Forwarder") - fwd, err := NewForwarder(id, ctx, a1, a2) - if err != nil { - panic(err) - } - return fwd, a1, a2 -} - -func TestForwarder(t *testing.T) { - ctx := GetLogger(t, "Forwarder") - id := [8]byte{0x1, 0x3, 0x3, 0x7, 0x5, 0xA, 0xB, 0x1} - Convey("NewForwarder", t, func() { - Convey("Valid: one adapter", func() { - fwd, err := NewForwarder(id, ctx, newFakeAdapter("1")) - So(err, ShouldBeNil) - defer fwd.Stop() - So(fwd, ShouldNotBeNil) - }) - - Convey("Valid: two adapters", func() { - fwd, err := NewForwarder(id, ctx, newFakeAdapter("1"), newFakeAdapter("2")) - So(err, ShouldBeNil) - defer fwd.Stop() - So(fwd, ShouldNotBeNil) - }) - - Convey("Invalid: no adapter", func() { - fwd, err := NewForwarder(id, ctx) - So(err, ShouldNotBeNil) - So(fwd, ShouldBeNil) - }) - - Convey("Invalid: too many adapters", func() { - var adapters []io.ReadWriteCloser - for i := 0; i < 300; i += 1 { - adapters = append(adapters, newFakeAdapter(fmt.Sprintf("%d", i))) - } - fwd, err := NewForwarder(id, ctx, adapters...) - So(fwd, ShouldBeNil) - So(err, ShouldNotBeNil) - }) - }) - - Convey("Forward", t, func() { - fwd, a1, a2 := initForwarder(t, id) - defer fwd.Stop() - - checkValid := func(identifier byte) func() { - return func() { - rxpk := generateRXPK("MyData", generateDevAddr()) - err := fwd.Forward(rxpk) - So(err, ShouldBeNil) - So(a1.written, ShouldNotBeNil) - So(a2.written, ShouldNotBeNil) - } - } - - Convey("Valid: PUSH_DATA", checkValid(semtech.PUSH_DATA)) - }) - - Convey("Flush", t, func() { - // Make sure we use a complete new forwarder each time - fwd, a1, a2 := initForwarder(t, id) - defer fwd.Stop() - - Convey("Init flush", func() { - So(fwd.Flush(), ShouldResemble, make([]semtech.Packet, 0)) - }) - - Convey("Store incoming valid packet", func() { - // Make sure the connection is established - rxpk := generateRXPK("MyData", generateDevAddr()) - if err := fwd.Forward(rxpk); err != nil { - panic(err) - } - - // Simulate an ack and a valid response - ack := generatePacket(semtech.PUSH_ACK, id) - raw, err := ack.MarshalBinary() - if err != nil { - panic(err) - } - a1.Downlink <- raw - - // Simulate a resp - resp := generatePacket(semtech.PULL_RESP, id) - resp.Token = nil - resp.Payload = &semtech.Payload{RXPK: []semtech.RXPK{rxpk}} - raw, err = resp.MarshalBinary() - if err != nil { - panic(err) - } - a1.Downlink <- raw - - // Flush and check if the response is there - time.Sleep(time.Millisecond * 50) - packets := fwd.Flush() - So(len(packets), ShouldEqual, 1) - So(packets[0], ShouldResemble, resp) - }) - - Convey("Ignore invalid datagrams", func() { - packets := fwd.Flush() - a2.Downlink <- []byte{0x6, 0x8, 0x14} - time.Sleep(time.Millisecond * 50) - So(fwd.Flush(), ShouldResemble, packets) - }) - - Convey("Ignore non relevant packets", func() { - // Simulate a resp - resp := generatePacket(semtech.PULL_DATA, id) - resp.Token = []byte{0x0, 0x0} - raw, err := resp.MarshalBinary() - if err != nil { - panic(err) - } - a1.Downlink <- raw - - // Flush and check wether or not the response has been stored - time.Sleep(time.Millisecond * 50) - packets := fwd.Flush() - So(len(packets), ShouldEqual, 0) - }) - - }) - - Convey("Stats", t, func() { - fwd, a1, a2 := initForwarder(t, id) - defer fwd.Stop() - refStats := fwd.Stats() - - Convey("lati, long, alti, time", func() { - So(refStats.Lati, ShouldNotBeNil) - So(refStats.Long, ShouldNotBeNil) - So(refStats.Alti, ShouldNotBeNil) - So(refStats.Time, ShouldNotBeNil) - So(refStats.Rxnb, ShouldNotBeNil) - So(refStats.Rxok, ShouldNotBeNil) - So(refStats.Ackr, ShouldNotBeNil) - So(refStats.Rxfw, ShouldNotBeNil) - So(refStats.Dwnb, ShouldNotBeNil) - So(refStats.Txnb, ShouldNotBeNil) - - }) - - Convey("rxnb / rxok", func() { - fwd.Forward(generateRXPK("MyData", generateDevAddr())) - stats := fwd.Stats() - So(stats.Rxnb, ShouldNotBeNil) - So(stats.Rxok, ShouldNotBeNil) - So(*stats.Rxnb, ShouldEqual, *refStats.Rxnb+1) - So(*stats.Rxok, ShouldEqual, *refStats.Rxok+1) - }) - - Convey("rxfw", func() { - fwd.Forward(generateRXPK("MyData", generateDevAddr())) - stats := fwd.Stats() - So(stats.Rxfw, ShouldNotBeNil) - So(*stats.Rxfw, ShouldEqual, *refStats.Rxfw+1) - }) - - Convey("ackr", func() { - Convey("ackr: initial", func() { - So(*refStats.Ackr, ShouldEqual, 0) - }) - - sendAndAck := func(a1Ack, a2Ack uint) { - // Send packet + ack - fwd.Forward(generateRXPK("MyData", generateDevAddr())) - ack := generatePacket(semtech.PUSH_ACK, id) - time.Sleep(50 * time.Millisecond) - - pkt := new(semtech.Packet) - if err := pkt.UnmarshalBinary(a1.written); err != nil { - panic(err) - } - ack.Token = pkt.Token - raw, err := ack.MarshalBinary() - if err != nil { - panic(err) - } - for i := uint(0); i < a1Ack; i += 1 { - a1.Downlink <- raw - } - - for i := uint(0); i < a2Ack; i += 1 { - a2.Downlink <- raw - } - time.Sleep(50 * time.Millisecond) - } - - Convey("ackr: valid packet acknowledged", func() { - // Send packet + ack - sendAndAck(1, 1) - - // Check stats - stats := fwd.Stats() - So(*stats.Ackr, ShouldEqual, 1) - }) - - Convey("ackr: valid packet partially acknowledged", func() { - // Send packet + ack - sendAndAck(1, 0) - - // Check stats - stats := fwd.Stats() - So(*stats.Ackr, ShouldEqual, float64(1.0)/float64(2.0)) - }) - - Convey("ackr: valid packet several acks from same", func() { - // Send packet + ack - sendAndAck(2, 0) - - // Check stats - stats := fwd.Stats() - So(*stats.Ackr, ShouldEqual, float64(1.0)/float64(2.0)) - }) - - Convey("ackr: valid packet not ackowledged", func() { - // Send packet + ack - sendAndAck(0, 0) - - // Check stats - stats := fwd.Stats() - So(*stats.Ackr, ShouldEqual, *refStats.Ackr) - }) - }) - - // TODO dwnb - // TODO txnb - }) - - Convey("Stop", t, func() { - //TODO - }) -} diff --git a/simulators/gateway/imitator.go b/simulators/gateway/imitator.go deleted file mode 100644 index 32727a329..000000000 --- a/simulators/gateway/imitator.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "math/rand" - "net" - "os" - "time" - - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/simulators/node" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/apex/log" - "github.com/apex/log/handlers/text" - "github.com/brocaar/lorawan" -) - -// MockWithSchedule will generate fake traffic based on a json file referencing packets -// -// The following shape is expected for the JSON file: -// [ -// { -// "dev_addr": "0102aabb", -// "payload": "My Data Payload", -// "metadata": { -// "rssi": -40, -// "modu": "LORA", -// "datr": "4/7", -// ... -// }, -// }, -// .... -// ] -// -// The imitator will fire udp packet every given interval of time to a set of router. Routers -// addresses are expected to contains the destination port as well. -func MockWithSchedule(filename string, delay time.Duration, routers ...string) { - rxpks, err := rxpkFromConf(filename) - if err != nil { - panic(err) - } - - var adapters []io.ReadWriteCloser - for _, router := range routers { - addr, err := net.ResolveUDPAddr("udp", router) - if err != nil { - panic(err) - } - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - panic(err) - } - adapters = append(adapters, conn) - } - - log.SetHandler(text.New(os.Stdout)) - log.SetLevel(log.DebugLevel) - ctx := log.WithFields(log.Fields{"Simulator": "Gateway"}) - - fwd, err := NewForwarder([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ctx, adapters...) - if err != nil { - panic(err) - } - - for { - for _, rxpk := range rxpks { - <-time.After(delay) - if err := fwd.Forward(rxpk); err != nil { - ctx.WithError(err).WithField("rxpk", rxpk).Warn("failed to forward") - } - } - } -} - -func MockRandomly(nodes []node.LiveNode, ctx log.Interface, routers ...string) { - var adapters []io.ReadWriteCloser - for _, router := range routers { - addr, err := net.ResolveUDPAddr("udp", router) - if err != nil { - panic(err) - } - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - panic(err) - } - adapters = append(adapters, conn) - } - - fwd, err := NewForwarder([8]byte{1, 2, 3, 4, 5, 6, 7, 8}, ctx, adapters...) - if err != nil { - panic(err) - } - - messages := make(chan string) - - for _, n := range nodes { - ctx.Infof("Created node: %s", n.(fmt.Stringer).String()) - go n.Start(messages) - } - - for { - rxpks := [8]semtech.RXPK{} - numPks := rand.Intn(8) + 1 - - for i := 0; i < numPks; i++ { - message := <-messages - - rxpk := semtech.RXPK{ - Rssi: pointer.Int(-20), - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("LORA"), - Data: pointer.String(message), - } - - rxpks[i] = rxpk - } - - err := fwd.Forward(rxpks[:numPks]...) - if err != nil { - ctx.WithError(err).Warn("failed to forward") - } else { - ctx.Debugf("Forwarded %d packets.", numPks) - } - } -} - -// rxpkFromConf read an input json file and parse it into a list of RXPK packets -func rxpkFromConf(filename string) ([]semtech.RXPK, error) { - content, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - var conf []struct { - DevAddr string `json:"dev_addr"` - Metadata metadata `json:"metadata"` - Payload string `json:"payload"` - } - err = json.Unmarshal(content, &conf) - if err != nil { - return nil, err - } - var rxpks []semtech.RXPK - for _, c := range conf { - rxpk := semtech.RXPK(c.Metadata) - rawAddr, err := hex.DecodeString(c.DevAddr) - if err != nil { - return nil, err - } - var devAddr lorawan.DevAddr - copy(devAddr[:], rawAddr) - rxpk.Data = pointer.String(generateData(c.Payload, devAddr)) - rxpks = append(rxpks, rxpk) - } - - return rxpks, nil -} - -type metadata semtech.RXPK // metadata is just an alias used to mislead the UnmarshalJSON - -// UnmarshalJSON implements the json.Unmarshal interface -func (m *metadata) UnmarshalJSON(raw []byte) error { - if m == nil { - return fmt.Errorf("Cannot unmarshal in nil metadata") - } - payload := new(semtech.Payload) - rawPayload := append(append([]byte(`{"rxpk":[`), raw...), []byte(`]}`)...) - err := json.Unmarshal(rawPayload, payload) - if err != nil { - return err - } - if len(payload.RXPK) < 1 { - return fmt.Errorf("Unable to interpret raw bytes as valid metadata") - } - *m = metadata(payload.RXPK[0]) - return nil -} diff --git a/simulators/gateway/utils.go b/simulators/gateway/utils.go deleted file mode 100644 index 9a1b5bdc3..000000000 --- a/simulators/gateway/utils.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "bytes" - "encoding/base64" - "encoding/binary" - "fmt" - "math" - "math/rand" - "strings" - "time" - - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/brocaar/lorawan" -) - -func genToken() []byte { - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, rand.Uint32()) - return b[0:2] -} - -func ackToken(index int, packet semtech.Packet) [4]byte { - buf := new(bytes.Buffer) - binary.Write(buf, binary.LittleEndian, uint16(index)) - id := buf.Bytes()[0] - - var kind byte - switch packet.Identifier { - case semtech.PUSH_ACK, semtech.PUSH_DATA: - kind = 0x1 - case semtech.PULL_ACK, semtech.PULL_DATA, semtech.PULL_RESP: - kind = 0x2 - } - - return [4]byte{id, kind, packet.Token[0], packet.Token[1]} -} - -// Generates RSSI signal between -120 < rssi < 0 -func generateRssi() int { - // Generate RSSI. Tend towards generating great signal strength. - x := float64(rand.Int31()) * float64(2e-9) - return int(-1.6 * math.Exp(x)) -} - -// Generates a frequency between 863.0 and 870.0 Mhz -func generateFreq() float64 { - // EU 863-870MHz - return rand.Float64()*7 + 863.0 -} - -// Generates Datr for instance: SF4BW125 -func generateDatr() string { - // Spread Factor from 12 to 7 - sf := 12 - rand.Intn(7) - var bw int - if sf == 6 { - // DR6 -> SF7@250Khz - sf = 7 - bw = 250 - } else { - bw = 125 - } - return fmt.Sprintf("SF%dBW%d", sf, bw) -} - -// Generates Codr for instance: 4/6 -func generateCodr() string { - d := rand.Intn(4) + 5 - return fmt.Sprintf("4/%d", d) -} - -// Generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise -func generateLsnr() float64 { - x := float64(rand.Int31()) * float64(2e-9) - return math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10 -} - -// Generates fake data from a device -func generateData(frmData string, devAddr lorawan.DevAddr) string { - macPayload := lorawan.NewMACPayload(true) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: devAddr, - FCtrl: lorawan.FCtrl{}, - FCnt: 0, - } - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: []byte(frmData), - }} - macPayload.FPort = 14 - - phyPayload := lorawan.NewPHYPayload(true) - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - phyPayload.SetMIC(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16})) - - raw, err := phyPayload.MarshalBinary() - if err != nil { // Shouldn't be - panic(err) - } - return strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") -} - -// Generate a random device address -func generateDevAddr() lorawan.DevAddr { - devAddr := [4]byte{} - token := new(bytes.Buffer) - binary.Write(token, binary.LittleEndian, time.Now().UnixNano()) - copy(devAddr[:], token.Bytes()[:4]) - return lorawan.DevAddr(devAddr) -} - -func generateRXPK(data string, devAddr lorawan.DevAddr) semtech.RXPK { - now := time.Now().In(time.UTC) - return semtech.RXPK{ - Time: &now, - Tmst: pointer.Uint(uint(now.UnixNano())), - Freq: pointer.Float64(generateFreq()), - Chan: pointer.Uint(0), // Irrelevant - Rfch: pointer.Uint(0), // Irrelevant - Stat: pointer.Int(1), // Assuming CRC was ok - Modu: pointer.String("LORA"), // For now, only consider LORA modulation - Datr: pointer.String(generateDatr()), // Arbitrary - Codr: pointer.String("4/5"), // Arbitrary - Rssi: pointer.Int(generateRssi()), // Arbitrary - Lsnr: pointer.Float64(generateLsnr()), // Arbitrary - Data: pointer.String(generateData(data, devAddr)), // Arbitrary - } -} diff --git a/simulators/gateway/utils_test.go b/simulators/gateway/utils_test.go deleted file mode 100644 index d7e7d6993..000000000 --- a/simulators/gateway/utils_test.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/semtech" - . "github.com/smartystreets/goconvey/convey" -) - -func TestGenToken(t *testing.T) { - Convey("The genToken() method should return randommly generated 2-byte long tokens", t, func() { - Convey("Given 5 generated tokens", func() { - randTokens := [5][]byte{ - genToken(), - genToken(), - genToken(), - genToken(), - genToken(), - } - - Convey("They shouldn't be all identical", func() { - sameTokens := [5][]byte{ - randTokens[0], - randTokens[0], - randTokens[0], - randTokens[0], - randTokens[0], - } - - So(randTokens, ShouldNotResemble, sameTokens) - }) - - Convey("They should all be 2-byte long", func() { - for _, t := range randTokens { - So(len(t), ShouldEqual, 2) - } - }) - }) - }) -} - -func TestAckToken(t *testing.T) { - token := []byte{0x1, 0x4} - generatePacket := func(id byte) semtech.Packet { - return semtech.Packet{ - Token: token, - Identifier: id, - Version: semtech.VERSION, - } - } - - Convey("The ackToken() method should generate appropriate ACK token", t, func() { - Convey("Valid identifier, PULL", func() { - token_data := ackToken(14, generatePacket(semtech.PULL_DATA)) - token_ack := ackToken(14, generatePacket(semtech.PULL_ACK)) - token_resp := ackToken(14, generatePacket(semtech.PULL_RESP)) - So(token_data, ShouldResemble, token_ack) - So(token_ack, ShouldResemble, token_resp) - }) - - Convey("Valid identifier, PUSH", func() { - token_data := ackToken(14, generatePacket(semtech.PUSH_DATA)) - token_ack := ackToken(14, generatePacket(semtech.PUSH_ACK)) - So(token_data, ShouldResemble, token_ack) - }) - - Convey("Valid but different ids", func() { - token_data := ackToken(14, generatePacket(semtech.PULL_DATA)) - token_ack := ackToken(42, generatePacket(semtech.PULL_ACK)) - So(token_data, ShouldNotResemble, token_ack) - }) - - }) -} - -func TestGenerateRssi(t *testing.T) { - Convey("The generateRSSI should generate random RSSI values -120 < val < 0", t, func() { - values := make(map[int]bool) - for i := 0; i < 10; i += 1 { - rssi := generateRssi() - So(rssi, ShouldBeGreaterThanOrEqualTo, -120) - So(rssi, ShouldBeLessThanOrEqualTo, 0) - values[rssi] = true - t.Log(rssi) - } - So(len(values), ShouldBeGreaterThan, 5) - }) -} - -func TestGenerateFreq(t *testing.T) { - Convey("The generateFreq() method should generate random frequence between 863-870MHz", t, func() { - values := make(map[float64]bool) - for i := 0; i < 10; i += 1 { - freq := generateFreq() - So(freq, ShouldBeGreaterThanOrEqualTo, 863.0) - So(freq, ShouldBeLessThanOrEqualTo, 870.0) - values[freq] = true - t.Log(freq) - } - So(len(values), ShouldBeGreaterThan, 5) - }) -} - -func TestGenerateLsnr(t *testing.T) { - Convey("The generateLsnr() function should generate random snr ratio between 5.5 and -2", t, func() { - values := make(map[float64]bool) - for i := 0; i < 10; i += 1 { - lsnr := generateLsnr() - So(lsnr, ShouldBeGreaterThanOrEqualTo, -2) - So(lsnr, ShouldBeLessThanOrEqualTo, 5.5) - values[lsnr] = true - t.Log(lsnr) - } - So(len(values), ShouldBeGreaterThan, 5) - }) -} diff --git a/simulators/node/node.go b/simulators/node/node.go deleted file mode 100644 index 3a51935cb..000000000 --- a/simulators/node/node.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package node - -import ( - "encoding/base64" - "fmt" - "math/rand" - "strings" - "time" - - "github.com/apex/log" - "github.com/brocaar/lorawan" -) - -func randBytes(n int) []byte { - bytes := make([]byte, n) - for i := range bytes { - bytes[i] = byte(rand.Intn(255)) - } - return bytes -} - -type LiveNode interface { - Start(chan string) -} - -type Node struct { - DevAddr lorawan.DevAddr - AppEUI lorawan.EUI64 - NwkSKey lorawan.AES128Key - AppSKey lorawan.AES128Key - FCntUp uint32 - interval int - ctx log.Interface -} - -func New(interval int, ctx log.Interface) *Node { - devAddr := [4]byte{} - copy(devAddr[:], randBytes(4)) - - appEUI := [8]byte{} - copy(appEUI[:], randBytes(8)) - - nwkSKey := [16]byte{} - copy(nwkSKey[:], randBytes(16)) - - appSKey := [16]byte{} - copy(appSKey[:], randBytes(16)) - - node := &Node{ - DevAddr: lorawan.DevAddr(devAddr), - AppEUI: lorawan.EUI64(appEUI), - NwkSKey: lorawan.AES128Key(nwkSKey), - AppSKey: lorawan.AES128Key(appSKey), - interval: interval, - } - - node.ctx = ctx.WithField("devAddr", node.DevAddr) - - return node -} - -func (node *Node) Start(messages chan string) { - for { - <-time.After(time.Duration(rand.ExpFloat64()*float64(node.interval)) * time.Millisecond) - node.NextMessage(messages) - } -} - -func (node *Node) String() string { - return fmt.Sprintf("Node %s:\n AppEUI: %s\n NwkSKey: %s\n AppSKey: %s\n FCntUp: %d", node.DevAddr, node.AppEUI, node.NwkSKey, node.AppSKey, node.FCntUp) -} - -func (node *Node) NextMessage(messages chan string) { - node.FCntUp++ - raw := node.BuildMessage([]byte(fmt.Sprintf("This is message %d.", node.FCntUp))) - node.ctx.WithField("FCnt", node.FCntUp).Debug("Publishing message") - messages <- strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") -} - -func (node *Node) BuildMessage(data []byte) []byte { - uplink := true - - macPayload := lorawan.NewMACPayload(uplink) - macPayload.FHDR = lorawan.FHDR{ - DevAddr: node.DevAddr, - FCtrl: lorawan.FCtrl{ - ADR: false, - ADRACKReq: false, - ACK: false, - }, - FCnt: node.FCntUp, - } - macPayload.FPort = 10 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: data}} - - if err := macPayload.EncryptFRMPayload(node.AppSKey); err != nil { - panic(err) - } - - payload := lorawan.NewPHYPayload(uplink) - payload.MHDR = lorawan.MHDR{ - MType: lorawan.ConfirmedDataUp, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macPayload - - if err := payload.SetMIC(node.NwkSKey); err != nil { - panic(err) - } - - bytes, err := payload.MarshalBinary() - if err != nil { - panic(err) - } - - return bytes -} diff --git a/simulators/node/node_test.go b/simulators/node/node_test.go deleted file mode 100644 index 63f9ea5d5..000000000 --- a/simulators/node/node_test.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package node - -import ( - "math/rand" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" - . "github.com/smartystreets/goconvey/convey" -) - -func TestNewNode(t *testing.T) { - rand.Seed(1234567) // Let's use a static seed for testing - - Convey("Given a Node", t, func() { - ctx := GetLogger(t, "Node") - node := New(100, ctx) - - messages := make(chan string, 3) - - Convey("When sending three messages", func() { - node.NextMessage(messages) - node.NextMessage(messages) - node.NextMessage(messages) - - Convey("They should be published to the channel", func() { - So(len(messages), ShouldEqual, 3) - }) - }) - - }) - -} diff --git a/ttnctl/AUTHORS b/ttnctl/AUTHORS new file mode 100644 index 000000000..ba2e99d67 --- /dev/null +++ b/ttnctl/AUTHORS @@ -0,0 +1,11 @@ +# This is the official list of The Things Network authors for copyright purposes. +# +# The copyright owners listed in this document agree to release their work under +# the MIT license that can be found in the LICENSE file. +# +# Names should be added to this file as +# Firstname Lastname +# +# Please keep the list sorted. + +Hylke Visser diff --git a/ttnctl/LICENSE b/ttnctl/LICENSE new file mode 100644 index 000000000..31c9aa88f --- /dev/null +++ b/ttnctl/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 The Things Network + +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/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go new file mode 100644 index 000000000..b100870b9 --- /dev/null +++ b/ttnctl/cmd/downlink.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "encoding/hex" + "fmt" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/apex/log" + "github.com/htdvisser/ttnctl/mqtt" + "github.com/htdvisser/ttnctl/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// downlinkCmd represents the `downlink` command +var downlinkCmd = &cobra.Command{ + Use: "downlink [DevEUI] [Payload]", + Short: "Send a downlink message to the network", + Long: `Send a downlink message to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + ctx.Fatal("Insufficient arguments") + } + + devEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + + req := core.DataDownAppReq{ + Payload: []byte(args[1]), + } + + payload, err := req.MarshalMsg(nil) + + if err != nil { + ctx.WithError(err).Fatal("Unable to create downlink payload") + } + + mqtt.Setup(viper.GetString("handler.mqtt-broker"), ctx) + mqtt.Connect() + + ctx.WithFields(log.Fields{ + "DevEUI": hex.EncodeToString(devEUI), + "Payload": string(payload), + }).Info("Pushing downlink...") + + token := mqtt.Client.Publish(fmt.Sprintf("%s/devices/%x/down", viper.GetString("handler.app-eui"), devEUI), 2, false, payload) + if token.Wait() && token.Error() != nil { + ctx.WithError(token.Error()).Fatal("Downlink failed.") + } else { + // Although we can't be sure whether it actually succeeded, we can know when the command is published to the MQTT. + ctx.Info("Downlink sent.") + } + }, +} + +func init() { + RootCmd.AddCommand(downlinkCmd) + + downlinkCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + viper.BindPFlag("handler.mqtt-broker", downlinkCmd.Flags().Lookup("mqtt-broker")) + + downlinkCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") + viper.BindPFlag("handler.app-eui", downlinkCmd.Flags().Lookup("app-eui")) +} diff --git a/ttnctl/cmd/register.go b/ttnctl/cmd/register.go new file mode 100644 index 000000000..77d676962 --- /dev/null +++ b/ttnctl/cmd/register.go @@ -0,0 +1,94 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "encoding/hex" + "fmt" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/apex/log" + "github.com/htdvisser/ttnctl/mqtt" + "github.com/htdvisser/ttnctl/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// registerCmd represents the `register` command +var registerCmd = &cobra.Command{ + Use: "register", + Short: "Register an entity with TTN", + Long: `Register a user`, + Run: func(cmd *cobra.Command, args []string) { + ctx.Fatal("register is not implemented.") + }, +} + +// registerPersonalizedDeviceCmd represents the `register personalized-device` command +var registerPersonalizedDeviceCmd = &cobra.Command{ + Use: "personalized-device [DevAddr] [NwkSKey] [AppSKey]", + Short: "Register a new personalized device", + Long: `Register a new personalized device`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 3 { + ctx.Fatal("Insufficient arguments") + } + + devAddr, err := util.Parse32(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevAddr: %s", err) + } + + nwkSKey, err := util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + + appSKey, err := util.Parse128(args[2]) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + + payload, err := core.ABPSubAppReq{ + DevAddr: args[0], + NwkSKey: args[1], + AppSKey: args[2], + }.MarshalMsg(nil) + + if err != nil { + ctx.WithError(err).Fatal("Unable to create a registration") + } + + mqtt.Setup(viper.GetString("handler.mqtt-broker"), ctx) + mqtt.Connect() + + ctx.WithFields(log.Fields{ + "DevAddr": hex.EncodeToString(devAddr), + "NwkSKey": hex.EncodeToString(nwkSKey), + "AppSKey": hex.EncodeToString(appSKey), + }).Info("Registering device...") + + token := mqtt.Client.Publish(fmt.Sprintf("%s/devices/personalized/activations", viper.GetString("handler.app-eui")), 2, false, payload) + if token.Wait() && token.Error() != nil { + ctx.WithError(token.Error()).Fatal("Registration failed.") + } else { + // Although we can't be sure whether it actually succeeded, we can know when the command is published to the MQTT. + ctx.Info("Registration finished.") + } + + }, +} + +func init() { + RootCmd.AddCommand(registerCmd) + + registerCmd.AddCommand(registerPersonalizedDeviceCmd) + + registerPersonalizedDeviceCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + viper.BindPFlag("handler.mqtt-broker", registerPersonalizedDeviceCmd.Flags().Lookup("mqtt-broker")) + + registerPersonalizedDeviceCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") + viper.BindPFlag("handler.app-eui", registerPersonalizedDeviceCmd.Flags().Lookup("app-eui")) + +} diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go new file mode 100644 index 000000000..2df766801 --- /dev/null +++ b/ttnctl/cmd/root.go @@ -0,0 +1,68 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + + "github.com/apex/log" + "github.com/apex/log/handlers/cli" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var cfgFile string + +var ctx log.Interface + +// RootCmd is the entrypoint for handlerctl +var RootCmd = &cobra.Command{ + Use: "ttnctl", + Short: "TTN Control", + Long: `ttnctl is used to control TTN from the command line.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + var logLevel = log.InfoLevel + if viper.GetBool("debug") { + logLevel = log.DebugLevel + } + cli.Colors[log.DebugLevel] = 90 + cli.Colors[log.InfoLevel] = 32 + ctx = &log.Logger{ + Level: logLevel, + Handler: cli.New(os.Stdout), + } + }, +} + +// Execute runs on start +func Execute() { + if err := RootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(-1) + } +} + +// init initializes the configuration and command line flags +func init() { + cobra.OnInitialize(initConfig) + + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") +} + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + viper.SetConfigFile(cfgFile) + } + + viper.SetConfigName(".ttnctl") + viper.AddConfigPath("$HOME") + viper.AutomaticEnv() + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go new file mode 100644 index 000000000..29bb51434 --- /dev/null +++ b/ttnctl/cmd/uplink.go @@ -0,0 +1,193 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "encoding/base64" + "net" + "strconv" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" + "github.com/htdvisser/ttnctl/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// uplinkCmd represents the `uplink` command +var uplinkCmd = &cobra.Command{ + Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [Payload] [FCnt]", + Short: "Send an uplink message to the network", + Long: `Send an uplink message to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 5 { + ctx.Fatalf("Insufficient arguments") + } + + // Parse parameters + devAddrRaw, err := util.Parse32(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevAddr: %s", err) + } + var devAddr lorawan.DevAddr + copy(devAddr[:], devAddrRaw) + + nwkSKeyRaw, err := util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + var nwkSKey lorawan.AES128Key + copy(nwkSKey[:], nwkSKeyRaw[:]) + + appSKeyRaw, err := util.Parse128(args[2]) + if err != nil { + ctx.Fatalf("Invalid appSKey: %s", err) + } + var appSKey lorawan.AES128Key + copy(appSKey[:], appSKeyRaw[:]) + + fcnt, err := strconv.ParseInt(args[4], 10, 64) + if err != nil { + ctx.Fatalf("Invalid FCnt: %s", err) + } + + // Lorawan Payload + macPayload := lorawan.NewMACPayload(true) + macPayload.FHDR = lorawan.FHDR{ + DevAddr: devAddr, + FCnt: uint32(fcnt), + } + macPayload.FPort = 1 + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} + if err := macPayload.EncryptFRMPayload(appSKey); err != nil { + ctx.Fatalf("Unable to encrypt frame payload: %s", err) + } + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = macPayload + if err := phyPayload.SetMIC(nwkSKey); err != nil { + ctx.Fatalf("Unable to set MIC: %s", err) + } + + addr, err := net.ResolveUDPAddr("udp", viper.GetString("router.address")) + if err != nil { + ctx.Fatalf("Couldn't resolve UDP address: %s", err) + } + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + ctx.Fatalf("Couldn't Dial UDP connection: %s", err) + } + + // Handle downlink + chdown := make(chan bool) + go func() { + // Get Ack + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + ctx.Fatalf("Error receiving udp datagram: %s", err) + } + pkt := new(semtech.Packet) + if err := pkt.UnmarshalBinary(buf[:n]); err != nil { + ctx.Fatalf("Invalid udp response: %s", err) + } + ctx.Infof("Received Ack: %s", pkt) + + // Get Downlink, if any + buf = make([]byte, 1024) + n, err = conn.Read(buf) + if err != nil { + ctx.Fatalf("Error receiving udp datagram: %s", err) + } + pkt = new(semtech.Packet) + if err = pkt.UnmarshalBinary(buf[:n]); err != nil { + ctx.Fatalf("Invalid udp response: %s", err) + } + ctx.Infof("Received Downlink: %s", pkt) + defer func() { chdown <- true }() + + if pkt.Payload == nil || pkt.Payload.TXPK == nil || pkt.Payload.TXPK.Data == nil { + ctx.Fatalf("No payload available in downlink response") + } + + data, err := base64.RawStdEncoding.DecodeString(*pkt.Payload.TXPK.Data) + if err != nil { + ctx.Fatalf("Unable to decode data payload: %s", err) + } + + payload := lorawan.NewPHYPayload(false) + if err := payload.UnmarshalBinary(data); err != nil { + ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) + } + + macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok || len(macPayload.FRMPayload) != 1 { + ctx.Fatalf("Unable to retrieve LoRaWAN MACPayload") + } + if err := macPayload.DecryptFRMPayload(appSKey); err != nil { + ctx.Fatalf("Unable to decrypt MACPayload: %s", err) + } + + ctx.Infof("Frame counter: %d", macPayload.FHDR.FCnt) + ctx.Infof("Decrypted Payload: %s", string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes)) + }() + + // Router Packet + data, err := phyPayload.MarshalBinary() + if err != nil { + ctx.Fatalf("Couldn't construct LoRaWAN physical payload: %s", err) + } + encoded := strings.Trim(base64.StdEncoding.EncodeToString(data), "=") + payload := semtech.Packet{ + Identifier: semtech.PUSH_DATA, + Token: util.RandToken(), + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Version: semtech.VERSION, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Rssi: pointer.Int32(util.RandRssi()), + Lsnr: pointer.Float32(util.RandLsnr()), + Freq: pointer.Float32(util.RandFreq()), + Datr: pointer.String(util.RandDatr()), + Codr: pointer.String(util.RandCodr()), + Modu: pointer.String("LoRa"), + Tmst: pointer.Uint32(1), + Data: &encoded, + }, + }, + }, + } + + ctx.Infof("Sending packet: %s", payload.String()) + + data, err = payload.MarshalBinary() + if err != nil { + ctx.Fatalf("Unable to construct framepayload", data) + } + + _, err = conn.Write(data) + if err != nil { + ctx.Fatalf("Unable to send payload") + } + + select { + case <-chdown: + case <-time.After(2 * time.Second): + } + }, +} + +func init() { + RootCmd.AddCommand(uplinkCmd) + + uplinkCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") + viper.BindPFlag("router.address", uplinkCmd.Flags().Lookup("ttn-router")) +} diff --git a/ttnctl/main.go b/ttnctl/main.go new file mode 100644 index 000000000..f52f6df5d --- /dev/null +++ b/ttnctl/main.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "github.com/htdvisser/ttnctl/cmd" + "github.com/spf13/viper" +) + +var ( + gitCommit = "unknown" + buildDate = "unknown" +) + +func main() { + viper.Set("version", "v0") + viper.Set("gitCommit", gitCommit) + viper.Set("buildDate", buildDate) + cmd.Execute() +} diff --git a/ttnctl/mqtt/client.go b/ttnctl/mqtt/client.go new file mode 100644 index 000000000..1650eaa29 --- /dev/null +++ b/ttnctl/mqtt/client.go @@ -0,0 +1,51 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/apex/log" + "github.com/htdvisser/ttnctl/util" +) + +var ( + Client *MQTT.Client + ctx log.Interface +) + +func Setup(broker string, _ctx log.Interface) { + if Client != nil { + _ctx.Fatal("MQTT Client already set up.") + } + ctx = _ctx + + mqttOpts := MQTT.NewClientOptions().AddBroker(fmt.Sprintf("tcp://%s", broker)) + clientID := fmt.Sprintf("ttnctl-%s", util.RandString(16)) + mqttOpts.SetClientID(clientID) + + mqttOpts.SetKeepAlive(20) + + mqttOpts.SetDefaultPublishHandler(func(client *MQTT.Client, msg MQTT.Message) { + ctx.WithField("message", msg).Debug("Received message") + }) + + mqttOpts.SetConnectionLostHandler(func(client *MQTT.Client, err error) { + ctx.WithError(err).Warn("Connection Lost. Reconnecting...") + }) + + Client = MQTT.NewClient(mqttOpts) +} + +func Connect() { + if Client.IsConnected() { + return + } + + ctx.Infof("Connecting to The Things Network...") + if token := Client.Connect(); token.Wait() && token.Error() != nil { + ctx.WithError(token.Error()).Fatal("Could not connect.") + } +} diff --git a/ttnctl/util/parse.go b/ttnctl/util/parse.go new file mode 100644 index 000000000..b54e8151d --- /dev/null +++ b/ttnctl/util/parse.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "encoding/hex" + "fmt" + "regexp" +) + +func parseHEX(input string, length int) ([]byte, error) { + pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length)) + if err != nil { + return nil, fmt.Errorf("Invalid pattern") + } + + valid := pattern.MatchString(input) + if !valid { + return nil, fmt.Errorf("Invalid input") + } + + devAddr, err := hex.DecodeString(input) + if err != nil { + return nil, fmt.Errorf("Could not decode input") + } + + return devAddr, nil +} + +// Parse32 parses a 32-bit hex-encoded string +func Parse32(input string) ([]byte, error) { + return parseHEX(input, 8) +} + +// Parse64 parses a 64-bit hex-encoded string +func Parse64(input string) ([]byte, error) { + return parseHEX(input, 16) +} + +// Parse128 parses a 128-bit hex-encoded string +func Parse128(input string) ([]byte, error) { + return parseHEX(input, 32) +} diff --git a/ttnctl/util/random.go b/ttnctl/util/random.go new file mode 100644 index 000000000..4929fbb89 --- /dev/null +++ b/ttnctl/util/random.go @@ -0,0 +1,89 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "encoding/binary" + "fmt" + "math" + "math/rand" + "time" +) + +// Source: http://stackoverflow.com/a/31832326 + +const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = src.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + + return string(b) +} + +// RandToken generate a random 2-bytes token +func RandToken() []byte { + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, src.Uint32()) + return b[0:2] +} + +// RandRssi generates RSSI signal between -120 < rssi < 0 +func RandRssi() int32 { + // Generate RSSI. Tend towards generating great signal strength. + x := float64(src.Int31()) * float64(2e-9) + return int32(-1.6 * math.Exp(x)) +} + +// RandFreq generates a frequency between 865.0 and 870.0 Mhz +func RandFreq() float32 { + // EU 865-870MHz + return float32(src.Float64()*5 + 865.0) +} + +// RandDatr generates Datr for instance: SF4BW125 +func RandDatr() string { + // Spread Factor from 12 to 7 + sf := 12 - src.Intn(7) + var bw int + if sf == 6 { + // DR6 -> SF7@250Khz + sf = 7 + bw = 250 + } else { + bw = 125 + } + return fmt.Sprintf("SF%dBW%d", sf, bw) +} + +// RandCodr generates Codr for instance: 4/6 +func RandCodr() string { + d := src.Intn(4) + 5 + return fmt.Sprintf("4/%d", d) +} + +// RandLsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise +func RandLsnr() float32 { + x := float64(src.Int31()) * float64(2e-9) + return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) +} diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go index b5d9bbd46..9eb07a26f 100644 --- a/utils/readwriter/readwriter_test.go +++ b/utils/readwriter/readwriter_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/errors/checks" "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" From b3d2e826baf313050bdf058564e6c8bf16cbe9f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 21:44:32 +0100 Subject: [PATCH 1092/2266] [refactor/grpc] Fix panic dur to nil serializations --- core/adapters/udp/udp.go | 2 +- core/components/broker/broker.go | 30 +++++++------- core/components/broker/broker_test.go | 34 ++++++++-------- core/components/handler/handler.go | 38 +++++++++--------- core/components/handler/handler_test.go | 52 ++++++++++++------------- core/components/router/router.go | 28 ++++++------- core/components/router/router_test.go | 42 ++++++++++---------- 7 files changed, 113 insertions(+), 113 deletions(-) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 731ef2270..59a411bbe 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -207,7 +207,7 @@ func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier) erro func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { a.Ctx.Debug("Handle Downlink from router") - if resp == nil { // No response + if resp == nil || resp.Payload == nil { // No response a.Ctx.Debug("No response to send") return nil } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index f0ca450e7..70f864b5a 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -88,7 +88,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c uplinkPayload, err := core.NewLoRaWANData(req.Payload, true) if err != nil { b.Ctx.WithError(err).Debug("Unable to interpret LoRaWAN payload") - return nil, errors.New(errors.Structural, err) + return new(core.DataBrokerRes), errors.New(errors.Structural, err) } devAddr := req.Payload.MACPayload.FHDR.DevAddr // No nil ref, ensured by NewLoRaWANData() ctx := b.Ctx.WithField("DevAddr", devAddr) @@ -104,7 +104,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c default: ctx.Warn("Database lookup failed") } - return nil, err + return new(core.DataBrokerRes), err } stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) @@ -150,7 +150,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") err := errors.New(errors.NotFound, "MIC check returned no matches") ctx.WithError(err).Debug("Unable to handle uplink") - return nil, err + return new(core.DataBrokerRes), err } // It does matter here to use the DevEUI from the entry and not from the packet. @@ -158,14 +158,14 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // to the MIC check + persistence if err := b.NetworkController.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, devAddr, fhdr.FCnt); err != nil { ctx.WithError(err).Debug("Unable to update Frame Counter") - return nil, err + return new(core.DataBrokerRes), err } // Then we forward the packet to the handler and wait for the response handler, closer, err := mEntry.Dialer.Dial() if err != nil { ctx.WithError(err).Debug("Unable to dial handler") - return nil, err + return new(core.DataBrokerRes), err } defer closer.Close() resp, err := handler.HandleDataUp(context.Background(), &core.DataUpHandlerReq{ @@ -180,15 +180,15 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c if err != nil { stats.MarkMeter("broker.uplink.bad_handler_response") ctx.WithError(err).Debug("Unexpected answer from handler") - return nil, errors.New(errors.Operational, err) + return new(core.DataBrokerRes), errors.New(errors.Operational, err) } stats.MarkMeter("broker.uplink.ok") // No response, we stop here and propagate the "no answer". // In case of confirmed data, the handler is in charge of creating the confirmation - if resp == nil { + if resp == nil || resp.Payload == nil { ctx.Debug("Packet successfully sent. There's no downlink.") - return nil, nil + return new(core.DataBrokerRes), nil } // If a response was sent, i.e. a downlink data, we need to compute the right MIC @@ -196,12 +196,12 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c downlinkPayload, err := core.NewLoRaWANData(resp.Payload, false) if err != nil { ctx.WithError(err).Debug("Unable to interpret LoRaWAN downlink datagram") - return nil, errors.New(errors.Structural, err) + return new(core.DataBrokerRes), errors.New(errors.Structural, err) } stats.MarkMeter("broker.downlink.in") if err := downlinkPayload.SetMIC(lorawan.AES128Key(mEntry.NwkSKey)); err != nil { ctx.WithError(err).Debug("Unable to set MIC") - return nil, errors.New(errors.Structural, "Unable to set response MIC") + return new(core.DataBrokerRes), errors.New(errors.Structural, "Unable to set response MIC") } resp.Payload.MIC = downlinkPayload.MIC[:] @@ -218,30 +218,30 @@ func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubB // Ensure the entry is valid if len(req.AppEUI) != 8 { - return nil, errors.New(errors.Structural, "Invalid Application EUI") + return new(core.ABPSubBrokerRes), errors.New(errors.Structural, "Invalid Application EUI") } if len(req.DevAddr) != 4 { - return nil, errors.New(errors.Structural, "Invalid Device Address") + return new(core.ABPSubBrokerRes), errors.New(errors.Structural, "Invalid Device Address") } devEUI := make([]byte, 8, 8) copy(devEUI[4:], req.DevAddr) var nwkSKey [16]byte if len(req.NwkSKey) != 16 { - return nil, errors.New(errors.Structural, "Invalid Network Session Key") + return new(core.ABPSubBrokerRes), errors.New(errors.Structural, "Invalid Network Session Key") } copy(nwkSKey[:], req.NwkSKey) re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") if !re.MatchString(req.HandlerNet) { b.Ctx.WithField("addr", req.HandlerNet).Debug("Invalid address") - return nil, errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) + return new(core.ABPSubBrokerRes), errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) } b.Ctx.Debug("Subscription looks valid") - return nil, b.NetworkController.StoreDevice(req.DevAddr, devEntry{ + return new(core.ABPSubBrokerRes), b.NetworkController.StoreDevice(req.DevAddr, devEntry{ Dialer: NewDialer([]byte(req.HandlerNet)), AppEUI: req.AppEUI, DevEUI: devEUI, diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index bef1f7595..7acaf1502 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -33,7 +33,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural var wantDataUp *core.DataUpHandlerReq - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt uint32 // Operate @@ -79,7 +79,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrOperational var wantDataUp *core.DataUpHandlerReq - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt uint32 // Operate @@ -125,7 +125,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrNotFound var wantDataUp *core.DataUpHandlerReq - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt uint32 // Operate @@ -208,7 +208,7 @@ func TestHandleData(t *testing.T) { MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer = true @@ -275,7 +275,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrNotFound var wantDataUp *core.DataUpHandlerReq - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt uint32 var wantDialer bool @@ -350,7 +350,7 @@ func TestHandleData(t *testing.T) { MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer = true @@ -419,7 +419,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrOperational var wantDataUp *core.DataUpHandlerReq - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer = true @@ -496,7 +496,7 @@ func TestHandleData(t *testing.T) { MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer = true @@ -668,7 +668,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrOperational var wantDataUp *core.DataUpHandlerReq - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer bool @@ -755,7 +755,7 @@ func TestHandleData(t *testing.T) { MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } - var wantRes *core.DataBrokerRes + var wantRes = new(core.DataBrokerRes) var wantFCnt = nc.OutWholeCounter.FCnt var wantDialer = true @@ -787,7 +787,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr *string - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry = devEntry{ Dialer: NewDialer([]byte(req.HandlerNet)), AppEUI: req.AppEUI, @@ -821,7 +821,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr *string - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry = devEntry{ Dialer: NewDialer([]byte(req.HandlerNet)), AppEUI: req.AppEUI, @@ -855,7 +855,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr *string - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry = devEntry{ Dialer: NewDialer([]byte(req.HandlerNet)), AppEUI: req.AppEUI, @@ -889,7 +889,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry devEntry // Operate @@ -918,7 +918,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry devEntry // Operate @@ -947,7 +947,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry devEntry // Operate @@ -975,7 +975,7 @@ func TestSubscribePerso(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.ABPSubBrokerRes + var wantRes = new(core.ABPSubBrokerRes) var wantEntry devEntry // Operate diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index a03f93559..bdaac4cdf 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -99,26 +99,26 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH if len(req.AppEUI) != 8 { stats.MarkMeter("handler.registration.invalid") - return nil, errors.New(errors.Structural, "Invalid Application EUI") + return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Application EUI") } if len(req.DevAddr) != 4 { stats.MarkMeter("handler.registration.invalid") - return nil, errors.New(errors.Structural, "Invalid Device Address") + return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Device Address") } var devAddr [4]byte copy(devAddr[:], req.DevAddr) if len(req.NwkSKey) != 16 { stats.MarkMeter("handler.registration.invalid") - return nil, errors.New(errors.Structural, "Invalid Network Session Key") + return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Network Session Key") } var nwkSKey [16]byte copy(nwkSKey[:], req.NwkSKey) if len(req.AppSKey) != 16 { stats.MarkMeter("handler.registration.invalid") - return nil, errors.New(errors.Structural, "Invalid Application Session Key") + return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Application Session Key") } var appSKey [16]byte copy(appSKey[:], req.AppSKey) @@ -127,7 +127,7 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH if err := h.DevStorage.StorePersonalized(req.AppEUI, devAddr, nwkSKey, appSKey); err != nil { h.Ctx.WithError(err).Debug("Unable to store registration") - return nil, errors.New(errors.Operational, err) + return new(core.ABPSubHandlerRes), errors.New(errors.Operational, err) } _, err := h.Broker.SubscribePersonalized(context.Background(), &core.ABPSubBrokerReq{ @@ -139,9 +139,9 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH if err != nil { h.Ctx.WithError(err).Debug("Unable to forward registration") - return nil, errors.New(errors.Operational, err) + return new(core.ABPSubHandlerRes), errors.New(errors.Operational, err) } - return nil, nil + return new(core.ABPSubHandlerRes), nil } // HandleDataDown implements the core.HandlerServer interface @@ -153,21 +153,21 @@ func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandle if len(req.AppEUI) != 8 { stats.MarkMeter("handler.downlink.invalid") - return nil, errors.New(errors.Structural, "Invalid Application EUI") + return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid Application EUI") } if len(req.DevEUI) != 8 { stats.MarkMeter("handler.downlink.invalid") - return nil, errors.New(errors.Structural, "Invalid Device EUI") + return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid Device EUI") } if len(req.Payload) == 0 { stats.MarkMeter("handler.downlink.invalid") - return nil, errors.New(errors.Structural, "Invalid payload") + return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid payload") } h.Ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") - return nil, h.PktStorage.Push(req.AppEUI, req.DevEUI, pktEntry{Payload: req.Payload}) + return new(core.DataDownHandlerRes), h.PktStorage.Push(req.AppEUI, req.DevEUI, pktEntry{Payload: req.Payload}) } // HandleDataUp implements the core.HandlerServer interface @@ -177,19 +177,19 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // 0. Check the packet integrity if len(req.Payload) == 0 { stats.MarkMeter("handler.uplink.invalid") - return nil, errors.New(errors.Structural, "Invalid Packet Payload") + return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Invalid Packet Payload") } if len(req.DevEUI) != 8 { stats.MarkMeter("handler.uplink.invalid") - return nil, errors.New(errors.Structural, "Invalid Device EUI") + return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Invalid Device EUI") } if len(req.AppEUI) != 8 { stats.MarkMeter("handler.uplink.invalid") - return nil, errors.New(errors.Structural, "Invalid Application EUI") + return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Invalid Application EUI") } if req.Metadata == nil { stats.MarkMeter("handler.uplink.invalid") - return nil, errors.New(errors.Structural, "Missing Mandatory Metadata") + return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Missing Mandatory Metadata") } stats.MarkMeter("handler.uplink.data") @@ -197,7 +197,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") entry, err := h.DevStorage.Lookup(req.AppEUI, req.DevEUI) if err != nil { - return nil, err + return new(core.DataUpHandlerRes), err } // 2. Prepare a channel to receive the response from the consumer @@ -211,7 +211,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq binary.Write(buf, binary.BigEndian, req.FCnt) data := buf.Bytes() if len(data) != 20 { - return nil, errors.New(errors.Structural, "Unable to generate bundleID") + return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Unable to generate bundleID") } copy(bundleID[:], data[:]) @@ -241,11 +241,11 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq case error: stats.MarkMeter("handler.uplink.error") ctx.WithError(resp.(error)).Warn("Error while processing dowlink.") - return nil, resp.(error) + return new(core.DataUpHandlerRes), resp.(error) default: stats.MarkMeter("handler.uplink.ack.without_response") ctx.Debug("No response to send.") - return nil, nil + return new(core.DataUpHandlerRes), nil } } diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 1ef9a31d4..a2c9e614a 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -34,7 +34,7 @@ func TestHandleDataDown(t *testing.T) { // Expect var wantError *string - var wantRes *core.DataDownHandlerRes + var wantRes = new(core.DataDownHandlerRes) var wantEntry = pktEntry{Payload: req.Payload} // Operate @@ -71,7 +71,7 @@ func TestHandleDataDown(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.DataDownHandlerRes + var wantRes = new(core.DataDownHandlerRes) var wantEntry pktEntry // Operate @@ -108,7 +108,7 @@ func TestHandleDataDown(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.DataDownHandlerRes + var wantRes = new(core.DataDownHandlerRes) var wantEntry pktEntry // Operate @@ -145,7 +145,7 @@ func TestHandleDataDown(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.DataDownHandlerRes + var wantRes = new(core.DataDownHandlerRes) var wantEntry pktEntry // Operate @@ -186,7 +186,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrNotFound - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData *core.DataAppReq var wantFCnt uint32 @@ -228,7 +228,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData *core.DataAppReq var wantFCnt uint32 @@ -270,7 +270,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData *core.DataAppReq var wantFCnt uint32 @@ -312,7 +312,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData *core.DataAppReq var wantFCnt uint32 @@ -354,7 +354,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData *core.DataAppReq var wantFCnt uint32 @@ -413,7 +413,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr *string - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData = &core.DataAppReq{ Payload: payload, Metadata: []*core.Metadata{req.Metadata}, @@ -496,8 +496,8 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr1 *string var wantErr2 *string - var wantRes1 *core.DataUpHandlerRes - var wantRes2 *core.DataUpHandlerRes + var wantRes1 = new(core.DataUpHandlerRes) + var wantRes2 = new(core.DataUpHandlerRes) var wantData = &core.DataAppReq{ Payload: payload, Metadata: []*core.Metadata{req1.Metadata, req2.Metadata}, @@ -708,8 +708,8 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr1 *string var wantErr2 = ErrBehavioural - var wantRes1 *core.DataUpHandlerRes - var wantRes2 *core.DataUpHandlerRes + var wantRes1 = new(core.DataUpHandlerRes) + var wantRes2 = new(core.DataUpHandlerRes) var wantData = &core.DataAppReq{ Payload: payload, Metadata: []*core.Metadata{req1.Metadata}, @@ -797,7 +797,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData = &core.DataAppReq{ Payload: payload, Metadata: []*core.Metadata{req.Metadata}, @@ -862,7 +862,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData = &core.DataAppReq{ Payload: payload, Metadata: []*core.Metadata{req.Metadata}, @@ -950,8 +950,8 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr1 *string var wantErr2 *string - var wantRes1 *core.DataUpHandlerRes - var wantRes2 *core.DataUpHandlerRes + var wantRes1 = new(core.DataUpHandlerRes) + var wantRes2 = new(core.DataUpHandlerRes) var wantData1 = &core.DataAppReq{ Payload: payload1, Metadata: []*core.Metadata{req1.Metadata}, @@ -1159,7 +1159,7 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.DataUpHandlerRes + var wantRes = new(core.DataUpHandlerRes) var wantData = &core.DataAppReq{ Payload: payload, Metadata: []*core.Metadata{req.Metadata}, @@ -1205,7 +1205,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError *string - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub = req.AppEUI var wantReq = &core.ABPSubBrokerReq{ HandlerNet: addr, @@ -1251,7 +1251,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub []byte var wantReq *core.ABPSubBrokerReq @@ -1292,7 +1292,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub []byte var wantReq *core.ABPSubBrokerReq @@ -1333,7 +1333,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub []byte var wantReq *core.ABPSubBrokerReq @@ -1374,7 +1374,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError = ErrStructural - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub []byte var wantReq *core.ABPSubBrokerReq @@ -1416,7 +1416,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError = ErrOperational - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub = req.AppEUI var wantReq *core.ABPSubBrokerReq @@ -1458,7 +1458,7 @@ func TestSubscribePersonalized(t *testing.T) { // Expect var wantError = ErrOperational - var wantRes *core.ABPSubHandlerRes + var wantRes = new(core.ABPSubHandlerRes) var wantSub = req.AppEUI var wantReq = &core.ABPSubBrokerReq{ HandlerNet: addr, diff --git a/core/components/router/router.go b/core/components/router/router.go index e8ee22de5..ebca6d361 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -66,19 +66,19 @@ func (r component) Start() error { // HandleStats implements the core.RouterClient interface func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.StatsRes, error) { if req == nil { - return nil, errors.New(errors.Structural, "Invalid nil stats request") + return new(core.StatsRes), errors.New(errors.Structural, "Invalid nil stats request") } if len(req.GatewayID) != 8 { - return nil, errors.New(errors.Structural, "Invalid gateway identifier") + return new(core.StatsRes), errors.New(errors.Structural, "Invalid gateway identifier") } if req.Metadata == nil { - return nil, errors.New(errors.Structural, "Missing mandatory Metadata") + return new(core.StatsRes), errors.New(errors.Structural, "Missing mandatory Metadata") } stats.MarkMeter("router.stat.in") - return nil, r.Storage.UpdateStats(req.GatewayID, *req.Metadata) + return new(core.StatsRes), r.Storage.UpdateStats(req.GatewayID, *req.Metadata) } // HandleData implements the core.RouterClient interface @@ -91,22 +91,22 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co _, _, fhdr, _, err := core.ValidateLoRaWANData(req.Payload) if err != nil { r.Ctx.WithError(err).Debug("Invalid request payload") - return nil, errors.New(errors.Structural, err) + return new(core.DataRouterRes), errors.New(errors.Structural, err) } if req.Metadata == nil { r.Ctx.Debug("Invalid request Metadata") - return nil, errors.New(errors.Structural, "Missing mandatory Metadata") + return new(core.DataRouterRes), errors.New(errors.Structural, "Missing mandatory Metadata") } if len(req.GatewayID) != 8 { r.Ctx.Debug("Invalid request GatewayID") - return nil, errors.New(errors.Structural, "Invalid gatewayID") + return new(core.DataRouterRes), errors.New(errors.Structural, "Invalid gatewayID") } // Lookup for an existing broker entries, err := r.Storage.Lookup(fhdr.DevAddr) if err != nil && err.(errors.Failure).Nature != errors.NotFound { r.Ctx.Warn("Database lookup failed") - return nil, errors.New(errors.Operational, err) + return new(core.DataRouterRes), errors.New(errors.Operational, err) } shouldBroadcast := err != nil r.Ctx.WithField("Should Broadcast?", shouldBroadcast).Debug("Storage Lookup done") @@ -129,7 +129,7 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co sb1, err := dutycycle.GetSubBand(float32(req.Metadata.Frequency)) if err != nil { stats.MarkMeter("router.uplink.not_supported") - return nil, errors.New(errors.Structural, "Unhandled uplink signal frequency") + return new(core.DataRouterRes), errors.New(errors.Structural, "Unhandled uplink signal frequency") } rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) @@ -169,16 +169,16 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co default: stats.MarkMeter("router.uplink.bad_broker_response") } - return nil, err + return new(core.DataRouterRes), err } return r.handleDataDown(response, req.GatewayID) } func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*core.DataRouterRes, error) { - if req == nil { // No response + if req == nil || req.Payload == nil { // No response r.Ctx.Debug("Packet sent. No downlink received.") - return nil, nil + return new(core.DataRouterRes), nil } r.Ctx.Debug("Handling downlink response") @@ -186,7 +186,7 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c if req.Metadata == nil { stats.MarkMeter("router.uplink.bad_broker_response") r.Ctx.Warn("Missing mandatory Metadata in response") - return nil, errors.New(errors.Structural, "Missing mandatory Metadata in response") + return new(core.DataRouterRes), errors.New(errors.Structural, "Missing mandatory Metadata in response") } freq := req.Metadata.Frequency datr := req.Metadata.DataRate @@ -194,7 +194,7 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c size := req.Metadata.PayloadSize if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { r.Ctx.WithError(err).Debug("Unable to update DutyManager") - return nil, errors.New(errors.Operational, err) + return new(core.DataRouterRes), errors.New(errors.Operational, err) } // Send Back the response diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 88b61b948..c359f2b10 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -36,7 +36,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr *string - var wantRes *core.StatsRes + var wantRes = new(core.StatsRes) var wantID = req.GatewayID var wantMeta = *req.Metadata @@ -65,7 +65,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.StatsRes + var wantRes = new(core.StatsRes) var wantID []byte var wantMeta core.StatsMetadata @@ -101,7 +101,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.StatsRes + var wantRes = new(core.StatsRes) var wantID []byte var wantMeta core.StatsMetadata @@ -132,7 +132,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.StatsRes + var wantRes = new(core.StatsRes) var wantID []byte var wantMeta core.StatsMetadata @@ -169,7 +169,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.StatsRes + var wantRes = new(core.StatsRes) var wantID = req.GatewayID var wantMeta = *req.Metadata @@ -221,7 +221,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore int @@ -273,7 +273,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore int @@ -325,7 +325,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore int @@ -362,7 +362,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore int @@ -414,7 +414,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore int @@ -479,7 +479,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr *string - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -549,7 +549,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr *string - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -621,7 +621,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr *string - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -691,7 +691,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore = 0 @@ -757,7 +757,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr *string - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -830,7 +830,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq var wantStore = 0 @@ -893,7 +893,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -963,7 +963,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrBehavioural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -1038,7 +1038,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrNotFound - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -1227,7 +1227,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrStructural - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ @@ -1322,7 +1322,7 @@ func TestHandleData(t *testing.T) { // Expect var wantErr = ErrOperational - var wantRes *core.DataRouterRes + var wantRes = new(core.DataRouterRes) var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ From d7415ee5eb21b835d52c589aeb99c10d274752be Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 21:45:30 +0100 Subject: [PATCH 1093/2266] [refactor/grpc] Update README and remove ROADMAP --- README.md | 6 +----- ROADMAP.md | 30 ------------------------------ 2 files changed, 1 insertion(+), 35 deletions(-) delete mode 100644 ROADMAP.md diff --git a/README.md b/README.md index dd08d8607..9a3da43d7 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ We are working hard on building a working version of the v1.0 backend. Currently - [x] **Router** - [x] **Broker** -- [ ] **Handler**: *work in progress* +- [x] **Handler** ## Contributing @@ -40,10 +40,6 @@ For contributing a feature, please open an issue that explains what you're worki If you want to contribute, but don't know where to start, you could have a look at issues with the label [*help wanted*](https://github.com/TheThingsNetwork/ttn/labels/help%20wanted) or [*difficulty/easy*](https://github.com/TheThingsNetwork/ttn/labels/difficulty%2Feasy). -## Roadmap - -We have a [roadmap](ROADMAP.md) for the coming months. This document will evolve as we further define our long-term vision. - ## License Source code for The Things Network is released under the MIT License, which can be found in the [LICENSE](LICENSE) file. A list of authors can be found in the [AUTHORS](AUTHORS) file. diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 9481c0b78..000000000 --- a/ROADMAP.md +++ /dev/null @@ -1,30 +0,0 @@ -Roadmap -======= - -## Milestone 1 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%201) - -Have a gateway simulator that is able to mock the behavior of a physical gateway. This will be used for testing and ensuring the correctness of other components. - -## Milestone 2 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%202) - -Support for uplink messages (from a node to an application). A gateway can send received messages to a [Router](https://thethingsnetwork.github.io/docs/router/). The Router filters out messages that are part of other networks and routes "our" messages to to a [Broker](https://thethingsnetwork.github.io/docs/broker/). The Broker forwards the messages to a [Handler](https://thethingsnetwork.github.io/docs/handler/), which delivers them to the Application. - -We will not support any MAC commands, nor device or application registration. The system will just forward messages using pre-configured server and end-device addresses. - -## Milestone 3 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%203) - -Support application registration for personalization. Applications provide a list of personalized device addresses along with the network session keys. - -## Milestone 4 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%204) - -Support for downlink messages (from an application to a node). Messages will be shipped as a response to an uplink transmission from a (Class A) node. - -## Milestone 5 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%205) - -Support for Over-the-air Activation (OTAA). Devices with a globally unique end-device identifier (DevEUI), an application identifier (AppEUI) and an application key (AppKey) can send join-requests to the network. - -We still not allow MAC commands from neither the end-device nor a network controller. - -## Milestone 6 - [Issues](https://github.com/TheThingsNetwork/ttn/milestones/Milestone%206) - -Support for LoRaWAN MAC commands. From 45c2b934f71647d5c24bb5a2fd1907959e0d66f1 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 21:55:30 +0100 Subject: [PATCH 1094/2266] [refactor/grpc] Fix import path in ttnctl --- ttnctl/cmd/downlink.go | 4 ++-- ttnctl/cmd/register.go | 4 ++-- ttnctl/cmd/uplink.go | 2 +- ttnctl/main.go | 2 +- ttnctl/mqtt/client.go | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index b100870b9..7e8de40a1 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -8,9 +8,9 @@ import ( "fmt" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/ttnctl/mqtt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" - "github.com/htdvisser/ttnctl/mqtt" - "github.com/htdvisser/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/ttnctl/cmd/register.go b/ttnctl/cmd/register.go index 77d676962..6dc895819 100644 --- a/ttnctl/cmd/register.go +++ b/ttnctl/cmd/register.go @@ -8,9 +8,9 @@ import ( "fmt" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/ttnctl/mqtt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" - "github.com/htdvisser/ttnctl/mqtt" - "github.com/htdvisser/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 29bb51434..a6e225437 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -11,9 +11,9 @@ import ( "time" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/brocaar/lorawan" - "github.com/htdvisser/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/ttnctl/main.go b/ttnctl/main.go index f52f6df5d..579b60a7d 100644 --- a/ttnctl/main.go +++ b/ttnctl/main.go @@ -4,7 +4,7 @@ package main import ( - "github.com/htdvisser/ttnctl/cmd" + "github.com/TheThingsNetwork/ttn/ttnctl/cmd" "github.com/spf13/viper" ) diff --git a/ttnctl/mqtt/client.go b/ttnctl/mqtt/client.go index 1650eaa29..657508191 100644 --- a/ttnctl/mqtt/client.go +++ b/ttnctl/mqtt/client.go @@ -7,8 +7,8 @@ import ( "fmt" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" - "github.com/htdvisser/ttnctl/util" ) var ( From 9e17945d69d4abd056a9f8219c56e26ee18902c2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 18 Mar 2016 22:04:19 +0100 Subject: [PATCH 1095/2266] [refactor/grpc] Last fix with travis and MQTT --- .travis.yml | 6 ++---- core/adapters/mqtt/client.go | 6 ++++++ core/adapters/mqtt/client_test.go | 4 ++-- core/adapters/mqtt/mqtt.go | 2 +- ttnctl/cmd/uplink.go | 4 ++-- 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8837bfa98..d3a02416f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,10 @@ language: go sudo: required go: - - tip + - 1.6 before_install: - - wget http://repo.mosquitto.org/debian/mosquitto-repo.gpg.key - - sudo apt-key add mosquitto-repo.gpg.key - - wget http://repo.mosquitto.org/debian/mosquitto-wheezy.list + - sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa -y - sudo apt-get update install: diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go index 7007633dc..4626e09f5 100644 --- a/core/adapters/mqtt/client.go +++ b/core/adapters/mqtt/client.go @@ -131,6 +131,9 @@ func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- i TopicFilter: []byte("+/devices/+/down"), QoS: mqtt.QoS2, Handler: func(topic, msg []byte) { + if len(msg) == 0 { + return + } chmsg <- Msg{ Topic: string(topic), Payload: msg, @@ -142,6 +145,9 @@ func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- i TopicFilter: []byte("+/devices/personalized/activations"), QoS: mqtt.QoS2, Handler: func(topic, msg []byte) { + if len(msg) == 0 { + return + } chmsg <- Msg{ Topic: string(topic), Payload: msg, diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go index d2ad378ce..2fc6ad91f 100644 --- a/core/adapters/mqtt/client_test.go +++ b/core/adapters/mqtt/client_test.go @@ -307,7 +307,7 @@ func TestNewClient(t *testing.T) { cli, _, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) FatalUnless(t, err) err = testCli.Subscribe(&client.SubscribeOptions{ - []*client.SubReq{ + SubReqs: []*client.SubReq{ &client.SubReq{ TopicFilter: []byte("topic"), QoS: mqtt.QoS2, @@ -343,7 +343,7 @@ func TestNewClient(t *testing.T) { // Clean err = testCli.Unsubscribe(&client.UnsubscribeOptions{ - [][]byte{[]byte(msg.Topic)}, + TopicFilters: [][]byte{[]byte(msg.Topic)}, }) FatalUnless(t, err) cli.Terminate() diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 9cc714151..fddd9a973 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -48,7 +48,7 @@ type Msg struct { // msgType constants are used in MQTTMsg to characterise the kind of message processed const ( - Down msgType = iota + Down msgType = iota + 1 ABP OTAA ) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index a6e225437..a4368d935 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -170,12 +170,12 @@ var uplinkCmd = &cobra.Command{ data, err = payload.MarshalBinary() if err != nil { - ctx.Fatalf("Unable to construct framepayload", data) + ctx.Fatalf("Unable to construct framepayload: %v", data) } _, err = conn.Write(data) if err != nil { - ctx.Fatalf("Unable to send payload") + ctx.Fatal("Unable to send payload") } select { From 5047e956df8c2359160b44e004079a44816c8f0a Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 01:31:09 +0100 Subject: [PATCH 1096/2266] Invert arguments in StorePersonalized in Handler --- core/components/handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index bdaac4cdf..ba99e50a8 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -125,7 +125,7 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH h.Ctx.Debug("Registration is valid. Saving and forwarding to broker") - if err := h.DevStorage.StorePersonalized(req.AppEUI, devAddr, nwkSKey, appSKey); err != nil { + if err := h.DevStorage.StorePersonalized(req.AppEUI, devAddr, appSKey, nwkSKey); err != nil { h.Ctx.WithError(err).Debug("Unable to store registration") return new(core.ABPSubHandlerRes), errors.New(errors.Operational, err) } From 2f75b2334cae3f95fa977f51cb0b1282d7fa3685 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 14:00:13 +0100 Subject: [PATCH 1097/2266] [feature/shield] Add Shield utils to control the number of incoming requests --- utils/shield/shield.go | 41 +++++++++++++++++++++++++++++++++++++ utils/shield/shield_test.go | 36 ++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 utils/shield/shield.go create mode 100644 utils/shield/shield_test.go diff --git a/utils/shield/shield.go b/utils/shield/shield.go new file mode 100644 index 000000000..702b39784 --- /dev/null +++ b/utils/shield/shield.go @@ -0,0 +1,41 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package shield + +import ( + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Interfaces materializes the public API of a shield. +type Interface interface { + ThroughIn() error // Try to pass through, will fail if the shield has been passed too many times + ThroughOut() // Terminate a previous session +} + +type shield struct { + Queue chan struct{} +} + +// New constructs a new shield with the given size +func New(size uint) Interface { + return shield{Queue: make(chan struct{}, size)} +} + +// Through implements the shield Interface +func (s shield) ThroughIn() error { + select { + case s.Queue <- struct{}{}: + return nil + default: + return errors.New(errors.Operational, "Impossible to pass. Too many requests.") + } +} + +// Release implements the shield Interface +func (s shield) ThroughOut() { + select { + case <-s.Queue: + default: + } +} diff --git a/utils/shield/shield_test.go b/utils/shield/shield_test.go new file mode 100644 index 000000000..e71e46bc0 --- /dev/null +++ b/utils/shield/shield_test.go @@ -0,0 +1,36 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package shield + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestShield(t *testing.T) { + { + shield := New(2) + err1 := shield.ThroughIn() + err2 := shield.ThroughIn() + err3 := shield.ThroughIn() + shield.ThroughOut() + err4 := shield.ThroughIn() + CheckErrors(t, nil, err1) + CheckErrors(t, nil, err2) + CheckErrors(t, ErrOperational, err3) + CheckErrors(t, nil, err4) + } + + // ---------- + + { + shield := New(1) + shield.ThroughOut() + err1 := shield.ThroughIn() + err2 := shield.ThroughIn() + CheckErrors(t, nil, err1) + CheckErrors(t, ErrOperational, err2) + } +} From b813c05d7be281a5c86b44e881dcd2ef6cb16f4d Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 14:30:11 +0100 Subject: [PATCH 1098/2266] [fix/ttnctl] Use of right argument index in ttnctl --- ttnctl/cmd/uplink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index a4368d935..5e2d492ef 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -62,7 +62,7 @@ var uplinkCmd = &cobra.Command{ FCnt: uint32(fcnt), } macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[3])}} if err := macPayload.EncryptFRMPayload(appSKey); err != nil { ctx.Fatalf("Unable to encrypt frame payload: %s", err) } From 3a82d3bf4fc12b1e44176b41d3d6858d25f7af68 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 17:58:21 +0100 Subject: [PATCH 1099/2266] [feature/otaa] Write protos and generate new methods accordingly --- core/application.pb.go | 357 +++++++++++++++++- core/broker.pb.go | 551 ++++++++++++++++++++++++++-- core/handler.pb.go | 647 +++++++++++++++++++++++++++++++-- core/lorawan.pb.go | 265 +++----------- core/protos/application.proto | 9 + core/protos/broker.proto | 29 +- core/protos/handler.proto | 15 + core/protos/lorawan.proto | 8 +- core/protos/router.proto | 28 +- core/router.pb.go | 660 +++++++++++++++++++++++++++++++--- 10 files changed, 2217 insertions(+), 352 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 971515ee5..66a1e3616 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -47,9 +47,37 @@ func (m *DataAppRes) String() string { return proto.CompactTextString func (*DataAppRes) ProtoMessage() {} func (*DataAppRes) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{1} } +type JoinAppReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + Metadata []*Metadata `protobuf:"bytes,3,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinAppReq) Reset() { *m = JoinAppReq{} } +func (m *JoinAppReq) String() string { return proto.CompactTextString(m) } +func (*JoinAppReq) ProtoMessage() {} +func (*JoinAppReq) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{2} } + +func (m *JoinAppReq) GetMetadata() []*Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type JoinAppRes struct { +} + +func (m *JoinAppRes) Reset() { *m = JoinAppRes{} } +func (m *JoinAppRes) String() string { return proto.CompactTextString(m) } +func (*JoinAppRes) ProtoMessage() {} +func (*JoinAppRes) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{3} } + func init() { proto.RegisterType((*DataAppReq)(nil), "core.DataAppReq") proto.RegisterType((*DataAppRes)(nil), "core.DataAppRes") + proto.RegisterType((*JoinAppReq)(nil), "core.JoinAppReq") + proto.RegisterType((*JoinAppRes)(nil), "core.JoinAppRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -60,6 +88,7 @@ var _ grpc.ClientConn type AppClient interface { HandleData(ctx context.Context, in *DataAppReq, opts ...grpc.CallOption) (*DataAppRes, error) + HandleJoin(ctx context.Context, in *JoinAppReq, opts ...grpc.CallOption) (*JoinAppRes, error) } type appClient struct { @@ -79,10 +108,20 @@ func (c *appClient) HandleData(ctx context.Context, in *DataAppReq, opts ...grpc return out, nil } +func (c *appClient) HandleJoin(ctx context.Context, in *JoinAppReq, opts ...grpc.CallOption) (*JoinAppRes, error) { + out := new(JoinAppRes) + err := grpc.Invoke(ctx, "/core.App/HandleJoin", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for App service type AppServer interface { HandleData(context.Context, *DataAppReq) (*DataAppRes, error) + HandleJoin(context.Context, *JoinAppReq) (*JoinAppRes, error) } func RegisterAppServer(s *grpc.Server, srv AppServer) { @@ -101,6 +140,18 @@ func _App_HandleData_Handler(srv interface{}, ctx context.Context, dec func(inte return out, nil } +func _App_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(JoinAppReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(AppServer).HandleJoin(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + var _App_serviceDesc = grpc.ServiceDesc{ ServiceName: "core.App", HandlerType: (*AppServer)(nil), @@ -109,6 +160,10 @@ var _App_serviceDesc = grpc.ServiceDesc{ MethodName: "HandleData", Handler: _App_HandleData_Handler, }, + { + MethodName: "HandleJoin", + Handler: _App_HandleJoin_Handler, + }, }, Streams: []grpc.StreamDesc{}, } @@ -185,6 +240,70 @@ func (m *DataAppRes) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *JoinAppReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinAppReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if len(m.Metadata) > 0 { + for _, msg := range m.Metadata { + data[i] = 0x1a + i++ + i = encodeVarintApplication(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *JoinAppRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinAppRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + func encodeFixed64Application(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -248,6 +367,36 @@ func (m *DataAppRes) Size() (n int) { return n } +func (m *JoinAppReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) + } + } + if len(m.Metadata) > 0 { + for _, e := range m.Metadata { + l = e.Size() + n += 1 + l + sovApplication(uint64(l)) + } + } + return n +} + +func (m *JoinAppRes) Size() (n int) { + var l int + _ = l + return n +} + func sovApplication(x uint64) (n int) { for { n++ @@ -485,6 +634,199 @@ func (m *DataAppRes) Unmarshal(data []byte) error { } return nil } +func (m *JoinAppReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinAppReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinAppReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApplication + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = append(m.Metadata, &Metadata{}) + if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipApplication(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApplication + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinAppRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinAppRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinAppRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipApplication(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApplication + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipApplication(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -591,7 +933,7 @@ var ( ) var fileDescriptorApplication = []byte{ - // 203 bytes of a gzipped FileDescriptorProto + // 242 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x26, 0x46, 0x2e, 0x2e, 0x97, 0xc4, @@ -600,9 +942,12 @@ var fileDescriptorApplication = []byte{ 0x8b, 0x0d, 0xa8, 0xc6, 0x35, 0xd4, 0x53, 0x82, 0x09, 0x2c, 0xc1, 0x96, 0x08, 0xe6, 0x81, 0xc4, 0x5d, 0x52, 0xcb, 0x40, 0xe2, 0xcc, 0x10, 0xf1, 0x14, 0x30, 0x4f, 0x48, 0x8b, 0x8b, 0xc3, 0x37, 0xb5, 0x24, 0x31, 0x05, 0x68, 0xb6, 0x04, 0x8b, 0x02, 0xb3, 0x06, 0xb7, 0x11, 0x9f, 0x1e, 0xd8, - 0x5e, 0x98, 0x68, 0x10, 0x47, 0x2e, 0x94, 0xa5, 0xc4, 0x83, 0xe4, 0x86, 0x62, 0x23, 0x73, 0x2e, - 0x66, 0x20, 0x4b, 0xc8, 0x80, 0x8b, 0xcb, 0x23, 0x31, 0x2f, 0x25, 0x27, 0x15, 0x24, 0x25, 0x24, - 0x00, 0xd1, 0x8c, 0x70, 0xaa, 0x14, 0xba, 0x48, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, - 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0xf6, 0xa4, 0x31, 0x20, 0x00, - 0x00, 0xff, 0xff, 0xf1, 0x20, 0xc5, 0xda, 0x0b, 0x01, 0x00, 0x00, + 0x5e, 0x98, 0x68, 0x10, 0x47, 0x2e, 0x94, 0xa5, 0xc4, 0x83, 0xe4, 0x86, 0x62, 0xa5, 0x0c, 0x2e, + 0x2e, 0xaf, 0xfc, 0xcc, 0x3c, 0xa8, 0x8b, 0x10, 0xf6, 0x32, 0xe2, 0xb0, 0x97, 0x09, 0xa7, 0xbd, + 0xcc, 0x84, 0xed, 0x85, 0xdb, 0x54, 0x6c, 0x94, 0xc9, 0xc5, 0x0c, 0x64, 0x09, 0x19, 0x70, 0x71, + 0x79, 0x24, 0xe6, 0xa5, 0xe4, 0xa4, 0x82, 0x9c, 0x24, 0x24, 0x00, 0xd1, 0x8c, 0x08, 0x22, 0x29, + 0x74, 0x91, 0x62, 0x84, 0x0e, 0x90, 0x61, 0x30, 0x1d, 0x08, 0x2f, 0x48, 0xa1, 0x8b, 0x14, 0x3b, + 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, + 0x6c, 0xe0, 0xe8, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x27, 0x4e, 0xb5, 0x01, + 0x00, 0x00, } diff --git a/core/broker.pb.go b/core/broker.pb.go index cdc72cb61..e447502ad 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -18,20 +18,28 @@ DataBrokerRes ABPSubBrokerReq ABPSubBrokerRes + JoinBrokerReq + JoinBrokerRes Metadata StatsMetadata DataAppReq DataAppRes + JoinAppReq + JoinAppRes DataUpHandlerReq DataUpHandlerRes DataDownHandlerReq DataDownHandlerRes ABPSubHandlerReq ABPSubHandlerRes + JoinHandlerReq + JoinHandlerRes DataRouterReq DataRouterRes StatsReq StatsRes + JoinRouterReq + JoinRouterRes LoRaWANData LoRaWANMHDR LoRaWANMACPayload @@ -131,11 +139,56 @@ func (m *ABPSubBrokerRes) String() string { return proto.CompactTextS func (*ABPSubBrokerRes) ProtoMessage() {} func (*ABPSubBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } +type JoinBrokerReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` + Metadata *Metadata `protobuf:"bytes,4,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinBrokerReq) Reset() { *m = JoinBrokerReq{} } +func (m *JoinBrokerReq) String() string { return proto.CompactTextString(m) } +func (*JoinBrokerReq) ProtoMessage() {} +func (*JoinBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{4} } + +func (m *JoinBrokerReq) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type JoinBrokerRes struct { + Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinBrokerRes) Reset() { *m = JoinBrokerRes{} } +func (m *JoinBrokerRes) String() string { return proto.CompactTextString(m) } +func (*JoinBrokerRes) ProtoMessage() {} +func (*JoinBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{5} } + +func (m *JoinBrokerRes) GetPayload() *LoRaWANJoinAccept { + if m != nil { + return m.Payload + } + return nil +} + +func (m *JoinBrokerRes) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + func init() { proto.RegisterType((*DataBrokerReq)(nil), "core.DataBrokerReq") proto.RegisterType((*DataBrokerRes)(nil), "core.DataBrokerRes") proto.RegisterType((*ABPSubBrokerReq)(nil), "core.ABPSubBrokerReq") proto.RegisterType((*ABPSubBrokerRes)(nil), "core.ABPSubBrokerRes") + proto.RegisterType((*JoinBrokerReq)(nil), "core.JoinBrokerReq") + proto.RegisterType((*JoinBrokerRes)(nil), "core.JoinBrokerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -146,6 +199,7 @@ var _ grpc.ClientConn type BrokerClient interface { HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) + HandleJoin(ctx context.Context, in *JoinBrokerReq, opts ...grpc.CallOption) (*JoinBrokerRes, error) SubscribePersonalized(ctx context.Context, in *ABPSubBrokerReq, opts ...grpc.CallOption) (*ABPSubBrokerRes, error) } @@ -166,6 +220,15 @@ func (c *brokerClient) HandleData(ctx context.Context, in *DataBrokerReq, opts . return out, nil } +func (c *brokerClient) HandleJoin(ctx context.Context, in *JoinBrokerReq, opts ...grpc.CallOption) (*JoinBrokerRes, error) { + out := new(JoinBrokerRes) + err := grpc.Invoke(ctx, "/core.Broker/HandleJoin", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *ABPSubBrokerReq, opts ...grpc.CallOption) (*ABPSubBrokerRes, error) { out := new(ABPSubBrokerRes) err := grpc.Invoke(ctx, "/core.Broker/SubscribePersonalized", in, out, c.cc, opts...) @@ -179,6 +242,7 @@ func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *ABPSubBrok type BrokerServer interface { HandleData(context.Context, *DataBrokerReq) (*DataBrokerRes, error) + HandleJoin(context.Context, *JoinBrokerReq) (*JoinBrokerRes, error) SubscribePersonalized(context.Context, *ABPSubBrokerReq) (*ABPSubBrokerRes, error) } @@ -198,6 +262,18 @@ func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(i return out, nil } +func _Broker_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(JoinBrokerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerServer).HandleJoin(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + func _Broker_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { in := new(ABPSubBrokerReq) if err := dec(in); err != nil { @@ -218,6 +294,10 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ MethodName: "HandleData", Handler: _Broker_HandleData_Handler, }, + { + MethodName: "HandleJoin", + Handler: _Broker_HandleJoin_Handler, + }, { MethodName: "SubscribePersonalized", Handler: _Broker_SubscribePersonalized_Handler, @@ -368,6 +448,96 @@ func (m *ABPSubBrokerRes) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *JoinBrokerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinBrokerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.DevNonce != nil { + if len(m.DevNonce) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) + } + } + if m.Metadata != nil { + data[i] = 0x22 + i++ + i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) + n5, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + return i, nil +} + +func (m *JoinBrokerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinBrokerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(m.Payload.Size())) + n6, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) + n7, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } + return i, nil +} + func encodeFixed64Broker(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -457,6 +627,48 @@ func (m *ABPSubBrokerRes) Size() (n int) { return n } +func (m *JoinBrokerReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } + if m.DevNonce != nil { + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *JoinBrokerRes) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + func sovBroker(x uint64) (n int) { for { n++ @@ -924,6 +1136,298 @@ func (m *ABPSubBrokerRes) Unmarshal(data []byte) error { } return nil } +func (m *JoinBrokerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinBrokerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) + if m.DevNonce == nil { + m.DevNonce = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinBrokerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinBrokerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANJoinAccept{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBroker(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -1030,25 +1534,30 @@ var ( ) var fileDescriptorBroker = []byte{ - // 310 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2a, 0xca, 0xcf, - 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, - 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, - 0x0c, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0x44, 0x27, 0xb0, 0xa6, 0xa0, 0xd4, 0x42, 0x21, 0x6d, 0x2e, - 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x41, - 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, - 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, - 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xd5, - 0x72, 0xf1, 0x3b, 0x3a, 0x05, 0x04, 0x97, 0x26, 0x21, 0x7c, 0x25, 0xc7, 0xc5, 0xe5, 0x91, 0x98, - 0x97, 0x92, 0x93, 0x5a, 0xe4, 0x97, 0x5a, 0x02, 0xb6, 0x8e, 0x33, 0x88, 0x2b, 0x03, 0x2e, 0x22, - 0x24, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0x36, 0x9c, 0x27, 0x88, 0x2d, 0x11, - 0xcc, 0x13, 0x92, 0xe0, 0x62, 0x77, 0x49, 0x2d, 0x73, 0x4c, 0x49, 0x29, 0x92, 0x60, 0x06, 0x4b, - 0xb0, 0xa7, 0x40, 0xb8, 0x20, 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x16, 0x88, - 0x4c, 0x1e, 0x84, 0xab, 0x24, 0x88, 0x6e, 0x7d, 0xb1, 0x51, 0x3b, 0x23, 0x17, 0x1b, 0x84, 0x27, - 0x64, 0x06, 0x73, 0x09, 0xc8, 0x7f, 0x42, 0xc2, 0x10, 0x4f, 0xa0, 0x44, 0x81, 0x14, 0x16, 0xc1, - 0x62, 0x21, 0x57, 0x2e, 0x51, 0xa0, 0x91, 0xc5, 0xc9, 0x45, 0x99, 0x49, 0xa9, 0x01, 0xa9, 0x45, - 0xc5, 0xf9, 0x79, 0x89, 0x39, 0x99, 0x55, 0xa9, 0x29, 0x42, 0xa2, 0x10, 0xd5, 0x68, 0x3e, 0x96, - 0xc2, 0x2a, 0x5c, 0xec, 0x24, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x05, 0x20, 0x7e, 0x00, 0xc4, 0x33, - 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x13, 0x82, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x6b, 0x77, - 0xff, 0xc3, 0x39, 0x02, 0x00, 0x00, + // 391 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x53, 0xcd, 0x4a, 0xf3, 0x40, + 0x14, 0xfd, 0xf2, 0x7d, 0x25, 0xc9, 0x77, 0x6d, 0xd5, 0x8e, 0x54, 0x43, 0x16, 0x45, 0xba, 0x12, + 0x85, 0x82, 0x15, 0xdc, 0xa7, 0xb4, 0xe0, 0x6f, 0x28, 0x29, 0xe2, 0x7a, 0x92, 0x19, 0x68, 0x69, + 0xcc, 0xc4, 0x99, 0x68, 0xa9, 0xe0, 0xda, 0x57, 0xf0, 0x6d, 0xdc, 0xba, 0xf4, 0x11, 0x44, 0x5f, + 0xc4, 0x64, 0xa6, 0x35, 0x26, 0x14, 0x04, 0xc1, 0xc5, 0xc0, 0xdc, 0x73, 0xcf, 0xed, 0x39, 0xbd, + 0x67, 0x02, 0x55, 0x9f, 0xb3, 0x09, 0xe5, 0xed, 0x98, 0xb3, 0x84, 0xa1, 0x4a, 0xc0, 0x38, 0xb5, + 0x6b, 0x21, 0xe3, 0x78, 0x8a, 0x23, 0x05, 0xda, 0x90, 0x81, 0xea, 0xde, 0x1a, 0x41, 0xad, 0x87, + 0x13, 0xdc, 0x95, 0x43, 0x1e, 0xbd, 0x46, 0x7b, 0x60, 0x0c, 0xf0, 0x2c, 0x64, 0x98, 0x58, 0xda, + 0xb6, 0xb6, 0xb3, 0xd2, 0xa9, 0xb7, 0x25, 0xfd, 0x8c, 0x79, 0xf8, 0xd2, 0x71, 0x33, 0xb2, 0x67, + 0xc4, 0x8a, 0x81, 0x76, 0xc1, 0x3c, 0xa7, 0x09, 0x26, 0x29, 0x68, 0xfd, 0x95, 0xec, 0x55, 0xc5, + 0x5e, 0xa0, 0x9e, 0x79, 0x35, 0xbf, 0x95, 0x95, 0xc4, 0xef, 0x29, 0xdd, 0xc3, 0x9a, 0xd3, 0x1d, + 0x0c, 0x6f, 0xfc, 0xfc, 0x5f, 0x35, 0x01, 0x8e, 0x70, 0x44, 0x42, 0xca, 0x5d, 0x9a, 0x48, 0xb9, + 0xff, 0x1e, 0x8c, 0x3e, 0x11, 0xb4, 0x09, 0xba, 0x13, 0xc7, 0xfd, 0x8b, 0x63, 0xf9, 0xe3, 0x55, + 0x4f, 0xc7, 0xb2, 0x42, 0x16, 0x18, 0x3d, 0x7a, 0xeb, 0x10, 0xc2, 0xad, 0x7f, 0xb2, 0x61, 0x10, + 0x55, 0x66, 0x1d, 0x77, 0x3a, 0x19, 0x9e, 0xd2, 0x99, 0x55, 0x51, 0x9d, 0x48, 0x95, 0xad, 0x7a, + 0x59, 0x5e, 0xb4, 0x1e, 0x34, 0xa8, 0x9d, 0xb0, 0x71, 0x94, 0x1b, 0xca, 0x05, 0xb5, 0x82, 0x60, + 0x8a, 0xa7, 0x82, 0x5f, 0x8c, 0x10, 0x59, 0x21, 0x1b, 0xcc, 0x14, 0x77, 0x59, 0x14, 0xd0, 0xb9, + 0x13, 0x93, 0xcc, 0xeb, 0xc2, 0x6e, 0x2a, 0xdf, 0xec, 0x26, 0x2a, 0x1a, 0x11, 0x68, 0xbf, 0x9c, + 0xc2, 0x56, 0x21, 0x85, 0x8c, 0xec, 0x04, 0x01, 0x8d, 0x93, 0x1f, 0x65, 0xd1, 0x79, 0xd2, 0x40, + 0x57, 0x62, 0xe8, 0x70, 0x91, 0x41, 0x96, 0x2c, 0xda, 0x50, 0x23, 0x85, 0xc7, 0x67, 0x2f, 0x01, + 0x45, 0x3e, 0x97, 0x79, 0x59, 0xcc, 0x15, 0xb6, 0x69, 0x2f, 0x01, 0x05, 0xea, 0x43, 0x23, 0x0d, + 0x41, 0x04, 0x7c, 0xec, 0xd3, 0x01, 0xe5, 0x82, 0x45, 0x38, 0x1c, 0xdf, 0x51, 0x82, 0x1a, 0x8a, + 0x5d, 0x7a, 0x23, 0xf6, 0x52, 0x58, 0x74, 0xd7, 0x9f, 0xdf, 0x9a, 0xda, 0x4b, 0x7a, 0x5e, 0xd3, + 0xf3, 0xf8, 0xde, 0xfc, 0xe3, 0xeb, 0xf2, 0xd3, 0x39, 0xf8, 0x08, 0x00, 0x00, 0xff, 0xff, 0x25, + 0x06, 0x5b, 0xfc, 0x6b, 0x03, 0x00, 0x00, } diff --git a/core/handler.pb.go b/core/handler.pb.go index cf84e602e..3775e461e 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -104,6 +104,51 @@ func (m *ABPSubHandlerRes) String() string { return proto.CompactText func (*ABPSubHandlerRes) ProtoMessage() {} func (*ABPSubHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } +type JoinHandlerReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` + Metadata *Metadata `protobuf:"bytes,4,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinHandlerReq) Reset() { *m = JoinHandlerReq{} } +func (m *JoinHandlerReq) String() string { return proto.CompactTextString(m) } +func (*JoinHandlerReq) ProtoMessage() {} +func (*JoinHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{6} } + +func (m *JoinHandlerReq) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type JoinHandlerRes struct { + Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + Metadata *Metadata `protobuf:"bytes,4,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinHandlerRes) Reset() { *m = JoinHandlerRes{} } +func (m *JoinHandlerRes) String() string { return proto.CompactTextString(m) } +func (*JoinHandlerRes) ProtoMessage() {} +func (*JoinHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{7} } + +func (m *JoinHandlerRes) GetPayload() *LoRaWANJoinAccept { + if m != nil { + return m.Payload + } + return nil +} + +func (m *JoinHandlerRes) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + func init() { proto.RegisterType((*DataUpHandlerReq)(nil), "core.DataUpHandlerReq") proto.RegisterType((*DataUpHandlerRes)(nil), "core.DataUpHandlerRes") @@ -111,6 +156,8 @@ func init() { proto.RegisterType((*DataDownHandlerRes)(nil), "core.DataDownHandlerRes") proto.RegisterType((*ABPSubHandlerReq)(nil), "core.ABPSubHandlerReq") proto.RegisterType((*ABPSubHandlerRes)(nil), "core.ABPSubHandlerRes") + proto.RegisterType((*JoinHandlerReq)(nil), "core.JoinHandlerReq") + proto.RegisterType((*JoinHandlerRes)(nil), "core.JoinHandlerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -122,6 +169,7 @@ var _ grpc.ClientConn type HandlerClient interface { HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) + HandleJoin(ctx context.Context, in *JoinHandlerReq, opts ...grpc.CallOption) (*JoinHandlerRes, error) SubscribePersonalized(ctx context.Context, in *ABPSubHandlerReq, opts ...grpc.CallOption) (*ABPSubHandlerRes, error) } @@ -151,6 +199,15 @@ func (c *handlerClient) HandleDataDown(ctx context.Context, in *DataDownHandlerR return out, nil } +func (c *handlerClient) HandleJoin(ctx context.Context, in *JoinHandlerReq, opts ...grpc.CallOption) (*JoinHandlerRes, error) { + out := new(JoinHandlerRes) + err := grpc.Invoke(ctx, "/core.Handler/HandleJoin", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *ABPSubHandlerReq, opts ...grpc.CallOption) (*ABPSubHandlerRes, error) { out := new(ABPSubHandlerRes) err := grpc.Invoke(ctx, "/core.Handler/SubscribePersonalized", in, out, c.cc, opts...) @@ -165,6 +222,7 @@ func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *ABPSubHan type HandlerServer interface { HandleDataUp(context.Context, *DataUpHandlerReq) (*DataUpHandlerRes, error) HandleDataDown(context.Context, *DataDownHandlerReq) (*DataDownHandlerRes, error) + HandleJoin(context.Context, *JoinHandlerReq) (*JoinHandlerRes, error) SubscribePersonalized(context.Context, *ABPSubHandlerReq) (*ABPSubHandlerRes, error) } @@ -196,6 +254,18 @@ func _Handler_HandleDataDown_Handler(srv interface{}, ctx context.Context, dec f return out, nil } +func _Handler_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(JoinHandlerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerServer).HandleJoin(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + func _Handler_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { in := new(ABPSubHandlerReq) if err := dec(in); err != nil { @@ -220,6 +290,10 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ MethodName: "HandleDataDown", Handler: _Handler_HandleDataDown_Handler, }, + { + MethodName: "HandleJoin", + Handler: _Handler_HandleJoin_Handler, + }, { MethodName: "SubscribePersonalized", Handler: _Handler_SubscribePersonalized_Handler, @@ -456,6 +530,112 @@ func (m *ABPSubHandlerRes) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *JoinHandlerReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinHandlerReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.DevNonce != nil { + if len(m.DevNonce) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) + } + } + if m.Metadata != nil { + data[i] = 0x22 + i++ + i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) + n4, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *JoinHandlerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinHandlerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(m.Payload.Size())) + n5, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + if m.Metadata != nil { + data[i] = 0x22 + i++ + i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) + n6, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} + func encodeFixed64Handler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -597,6 +777,60 @@ func (m *ABPSubHandlerRes) Size() (n int) { return n } +func (m *JoinHandlerReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.DevNonce != nil { + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *JoinHandlerRes) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + func sovHandler(x uint64) (n int) { for { n++ @@ -1357,6 +1591,360 @@ func (m *ABPSubHandlerRes) Unmarshal(data []byte) error { } return nil } +func (m *JoinHandlerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinHandlerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) + if m.DevNonce == nil { + m.DevNonce = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinHandlerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinHandlerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANJoinAccept{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipHandler(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -1463,31 +2051,36 @@ var ( ) var fileDescriptorHandler = []byte{ - // 405 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x4a, 0xeb, 0x40, - 0x14, 0xbe, 0x69, 0xd3, 0xa4, 0x4c, 0x7f, 0xe8, 0x1d, 0x7a, 0x4b, 0xc8, 0xa2, 0x5c, 0xb2, 0x12, - 0x85, 0x2e, 0xea, 0x5e, 0x48, 0x8d, 0x7f, 0x68, 0x4b, 0x49, 0x2d, 0xee, 0x84, 0x69, 0x67, 0xc4, - 0xd2, 0x34, 0x13, 0x33, 0xd1, 0x5a, 0x9f, 0xc4, 0xe7, 0xf0, 0x29, 0x5c, 0xba, 0x74, 0x29, 0xfa, - 0x22, 0xce, 0x64, 0x52, 0xd2, 0x5f, 0x10, 0x17, 0x03, 0xe7, 0xfb, 0xce, 0x9c, 0xef, 0x7c, 0xe7, - 0x4c, 0x02, 0x4a, 0xb7, 0xc8, 0xc7, 0x1e, 0x09, 0x1b, 0x41, 0x48, 0x23, 0x0a, 0xd5, 0x21, 0x0d, - 0x89, 0x59, 0xf2, 0x68, 0x88, 0xa6, 0xc8, 0x97, 0xa4, 0x09, 0x04, 0x29, 0x63, 0xeb, 0x45, 0x01, - 0x15, 0x07, 0x45, 0xa8, 0x1f, 0x9c, 0xca, 0x42, 0x97, 0xdc, 0x41, 0x03, 0xe8, 0x5d, 0x34, 0xf3, - 0x28, 0xc2, 0x86, 0xf2, 0x5f, 0xd9, 0x29, 0xba, 0x7a, 0x20, 0x21, 0xdc, 0x05, 0xf9, 0x36, 0x89, - 0x10, 0xe6, 0x15, 0x46, 0x86, 0xa7, 0x0a, 0xcd, 0x72, 0x23, 0x56, 0x9b, 0xb3, 0x6e, 0x7e, 0x92, - 0x44, 0xb0, 0x06, 0x34, 0x3b, 0x08, 0x8e, 0xfa, 0x67, 0x46, 0x36, 0x16, 0xd1, 0x50, 0x8c, 0x04, - 0xef, 0x90, 0x07, 0xc1, 0xab, 0x92, 0xc7, 0x31, 0x82, 0x10, 0xa8, 0xc7, 0x87, 0x7e, 0x64, 0xe4, - 0x38, 0x5b, 0x72, 0xd5, 0x1b, 0x1e, 0xc3, 0x2a, 0xc8, 0xb5, 0x2f, 0x67, 0x01, 0x31, 0xb4, 0x98, - 0xcc, 0x4d, 0x04, 0xb0, 0xc6, 0x6b, 0x9e, 0x19, 0xdc, 0x5b, 0xf6, 0x5c, 0x68, 0xfe, 0x95, 0xc6, - 0x2e, 0xa8, 0x8b, 0xae, 0xec, 0x8e, 0xb8, 0xff, 0xab, 0x31, 0xac, 0x6b, 0x00, 0x45, 0xb1, 0x43, - 0xa7, 0xfe, 0x8f, 0x56, 0x94, 0x8e, 0x9d, 0xd9, 0x32, 0x76, 0x76, 0x71, 0x6c, 0xab, 0xba, 0x41, - 0x9f, 0x59, 0x8f, 0xa0, 0x62, 0xb7, 0xba, 0xbd, 0xfb, 0xc1, 0x42, 0xcf, 0x54, 0x59, 0x59, 0x52, - 0xe6, 0x5e, 0xb8, 0xb2, 0x8d, 0x71, 0x98, 0xb4, 0xd4, 0xb1, 0x84, 0x22, 0xd3, 0x99, 0x8e, 0x7b, - 0xe7, 0x64, 0x96, 0x34, 0xd5, 0x7d, 0x09, 0x45, 0x86, 0x6b, 0xc5, 0x19, 0xf9, 0x0a, 0x3a, 0x92, - 0xd0, 0x82, 0x6b, 0x9d, 0x59, 0xf3, 0x5d, 0x01, 0x7a, 0x02, 0xe1, 0x01, 0x28, 0xca, 0x50, 0x3e, - 0x01, 0xac, 0xc9, 0xcd, 0xad, 0x7e, 0x44, 0xe6, 0x66, 0x9e, 0x41, 0x07, 0x94, 0xd3, 0x7a, 0x31, - 0x35, 0x34, 0xd2, 0x9b, 0xcb, 0x5b, 0x36, 0xb7, 0x65, 0x18, 0x3c, 0x01, 0xff, 0xb8, 0x45, 0x36, - 0x0c, 0x47, 0x03, 0xd2, 0x25, 0x21, 0xa3, 0x3e, 0xf2, 0x46, 0x4f, 0x04, 0xcf, 0xed, 0xac, 0x2e, - 0xcf, 0xdc, 0xcc, 0xb3, 0x56, 0xe5, 0xf5, 0xb3, 0xae, 0xbc, 0xf1, 0xf3, 0xc1, 0xcf, 0xf3, 0x57, - 0xfd, 0xcf, 0x40, 0x8b, 0xff, 0x8c, 0xfd, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9f, 0xcb, 0xb7, - 0xb8, 0x4b, 0x03, 0x00, 0x00, + // 487 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0xa9, 0x13, 0x5b, 0xd3, 0x26, 0x0a, 0xa3, 0x50, 0x2c, 0x1f, 0x2a, 0xe4, 0x13, 0x02, + 0xa9, 0x12, 0xe1, 0xc2, 0x09, 0xc9, 0xc5, 0xfc, 0xd3, 0x28, 0x72, 0xa9, 0xb8, 0x21, 0x6d, 0xbc, + 0x8b, 0x88, 0xea, 0xee, 0x1a, 0xaf, 0x21, 0x84, 0x27, 0xe0, 0xcc, 0x89, 0x3b, 0x6f, 0xc0, 0x53, + 0x70, 0xe4, 0x11, 0x10, 0xbc, 0x08, 0xbb, 0x5e, 0x57, 0xae, 0x5d, 0x57, 0x94, 0x1e, 0x2c, 0xed, + 0xf7, 0xcd, 0xce, 0xcc, 0x37, 0xfb, 0x4d, 0x02, 0xc3, 0xb7, 0x84, 0xd3, 0x94, 0xe5, 0xbb, 0x59, + 0x2e, 0x0a, 0x81, 0x76, 0x22, 0x72, 0xe6, 0x0f, 0x53, 0x91, 0x93, 0x15, 0xe1, 0x86, 0xf4, 0x41, + 0x93, 0xe6, 0x1c, 0x7c, 0xb7, 0x60, 0x1c, 0x91, 0x82, 0x1c, 0x66, 0x4f, 0x4c, 0x62, 0xcc, 0xde, + 0xa1, 0x07, 0xce, 0x9c, 0xac, 0x53, 0x41, 0xa8, 0x67, 0xdd, 0xb0, 0x6e, 0x6e, 0xc5, 0x4e, 0x66, + 0x20, 0xde, 0x02, 0x77, 0x9f, 0x15, 0x84, 0xaa, 0x0c, 0xaf, 0xa7, 0x42, 0x9b, 0xd3, 0xd1, 0x6e, + 0x59, 0xed, 0x84, 0x8d, 0xdd, 0xe3, 0xea, 0x84, 0xdb, 0x30, 0x08, 0xb3, 0xec, 0xe1, 0xe1, 0x53, + 0x6f, 0xa3, 0x2c, 0x32, 0x20, 0x25, 0xd2, 0x7c, 0xc4, 0x3e, 0x68, 0xde, 0x36, 0x3c, 0x2d, 0x11, + 0x22, 0xd8, 0x8f, 0x1e, 0xf0, 0xc2, 0xeb, 0x2b, 0x76, 0x18, 0xdb, 0x6f, 0xd4, 0x19, 0x27, 0xd0, + 0xdf, 0x7f, 0xb9, 0xce, 0x98, 0x37, 0x28, 0xc9, 0xfe, 0xb1, 0x06, 0xc1, 0xd1, 0x19, 0xcd, 0x12, + 0x6f, 0x37, 0x35, 0x6f, 0x4e, 0xaf, 0x1a, 0x61, 0x2f, 0x44, 0x4c, 0x5e, 0x85, 0x33, 0x7d, 0xff, + 0x52, 0x63, 0x04, 0xaf, 0x01, 0x75, 0x72, 0x24, 0x56, 0xfc, 0x42, 0x4f, 0x54, 0x8f, 0xdd, 0x3b, + 0x67, 0xec, 0x8d, 0xd3, 0x63, 0x07, 0x93, 0x8e, 0xfa, 0x32, 0xf8, 0x08, 0xe3, 0x70, 0x6f, 0x7e, + 0xf0, 0x7e, 0x71, 0xaa, 0x67, 0x5d, 0xd9, 0x6a, 0x54, 0x56, 0x5a, 0x54, 0xe5, 0x90, 0xd2, 0xbc, + 0x6a, 0xe9, 0x50, 0x03, 0x75, 0x64, 0xb6, 0x3a, 0x3a, 0x78, 0xce, 0xd6, 0x55, 0x53, 0x87, 0x1b, + 0xa8, 0x23, 0xaa, 0x56, 0x19, 0x31, 0x2e, 0x38, 0xc4, 0xc0, 0x00, 0xcf, 0x74, 0x96, 0xc1, 0x67, + 0x0b, 0x46, 0xcf, 0xc4, 0x92, 0x5f, 0x40, 0x4c, 0x3d, 0x66, 0xaf, 0xe1, 0xae, 0x0f, 0xae, 0xe2, + 0x67, 0x82, 0x27, 0xac, 0xd2, 0xe2, 0xd2, 0x0a, 0x37, 0xec, 0xb0, 0xff, 0x61, 0xc7, 0xb7, 0xb6, + 0x14, 0x89, 0x77, 0xda, 0xd6, 0x5f, 0x6f, 0x58, 0xaf, 0x6f, 0x87, 0x49, 0xc2, 0xb2, 0xa2, 0x36, + 0xe9, 0x32, 0x4f, 0xf6, 0x1f, 0x2a, 0xa7, 0x5f, 0x7a, 0xe0, 0x54, 0x0a, 0xf1, 0x3e, 0x6c, 0x99, + 0xa3, 0xd9, 0x59, 0xdc, 0x36, 0x59, 0xed, 0x5f, 0x9d, 0xdf, 0xcd, 0x4b, 0x8c, 0x60, 0x54, 0xe7, + 0xeb, 0x35, 0x41, 0xaf, 0xbe, 0xd9, 0x5c, 0x4b, 0xff, 0xbc, 0x88, 0xc4, 0x7b, 0x00, 0x06, 0xe9, + 0xe7, 0xc0, 0x89, 0xb9, 0xd7, 0xf4, 0xd4, 0xef, 0x62, 0x25, 0x3e, 0x86, 0x6b, 0x6a, 0x1b, 0x64, + 0x92, 0x2f, 0x17, 0x6c, 0xce, 0x72, 0x29, 0x38, 0x49, 0x97, 0x9f, 0x18, 0x3d, 0x19, 0xa4, 0xbd, + 0xa7, 0x7e, 0x37, 0x2f, 0xf7, 0xc6, 0x3f, 0x7e, 0xef, 0x58, 0x3f, 0xd5, 0xf7, 0x4b, 0x7d, 0x5f, + 0xff, 0xec, 0x5c, 0x59, 0x0c, 0xca, 0x3f, 0xa1, 0xbb, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x74, + 0xc7, 0x08, 0x43, 0xb6, 0x04, 0x00, 0x00, } diff --git a/core/lorawan.pb.go b/core/lorawan.pb.go index 1177178c5..7971d7af9 100644 --- a/core/lorawan.pb.go +++ b/core/lorawan.pb.go @@ -15,6 +15,7 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// Uplink & Downlink Data (confirmed or unconfirmed) type LoRaWANData struct { MHDR *LoRaWANMHDR `protobuf:"bytes,1,opt,name=MHDR,json=mHDR" json:"MHDR,omitempty"` MACPayload *LoRaWANMACPayload `protobuf:"bytes,2,opt,name=MACPayload,json=mACPayload" json:"MACPayload,omitempty"` @@ -112,11 +113,7 @@ func (*LoRaWANJoinRequest) ProtoMessage() {} func (*LoRaWANJoinRequest) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } type LoRaWANJoinAccept struct { - AppNonce []byte `protobuf:"bytes,1,opt,name=AppNonce,json=appNonce,proto3" json:"AppNonce,omitempty"` - NetID []byte `protobuf:"bytes,2,opt,name=NetID,json=netID,proto3" json:"NetID,omitempty"` - DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - DLSettings *LoRaWANDLSettings `protobuf:"bytes,4,opt,name=DLSettings,json=dLSettings" json:"DLSettings,omitempty"` - RXDelay uint32 `protobuf:"varint,5,opt,name=RXDelay,json=rXDelay,proto3" json:"RXDelay,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` } func (m *LoRaWANJoinAccept) Reset() { *m = LoRaWANJoinAccept{} } @@ -124,13 +121,6 @@ func (m *LoRaWANJoinAccept) String() string { return proto.CompactTex func (*LoRaWANJoinAccept) ProtoMessage() {} func (*LoRaWANJoinAccept) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } -func (m *LoRaWANJoinAccept) GetDLSettings() *LoRaWANDLSettings { - if m != nil { - return m.DLSettings - } - return nil -} - type LoRaWANDLSettings struct { RX1DRoffset uint32 `protobuf:"varint,1,opt,name=RX1DRoffset,json=rX1DRoffset,proto3" json:"RX1DRoffset,omitempty"` RX2DataRate uint32 `protobuf:"varint,2,opt,name=RX2DataRate,json=rX2DataRate,proto3" json:"RX2DataRate,omitempty"` @@ -438,45 +428,14 @@ func (m *LoRaWANJoinAccept) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppNonce != nil { - if len(m.AppNonce) > 0 { + if m.Payload != nil { + if len(m.Payload) > 0 { data[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(len(m.AppNonce))) - i += copy(data[i:], m.AppNonce) - } - } - if m.NetID != nil { - if len(m.NetID) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.NetID))) - i += copy(data[i:], m.NetID) + i = encodeVarintLorawan(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) } } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - } - if m.DLSettings != nil { - data[i] = 0x22 - i++ - i = encodeVarintLorawan(data, i, uint64(m.DLSettings.Size())) - n5, err := m.DLSettings.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n5 - } - if m.RXDelay != 0 { - data[i] = 0x28 - i++ - i = encodeVarintLorawan(data, i, uint64(m.RXDelay)) - } return i, nil } @@ -662,31 +621,12 @@ func (m *LoRaWANJoinRequest) Size() (n int) { func (m *LoRaWANJoinAccept) Size() (n int) { var l int _ = l - if m.AppNonce != nil { - l = len(m.AppNonce) + if m.Payload != nil { + l = len(m.Payload) if l > 0 { n += 1 + l + sovLorawan(uint64(l)) } } - if m.NetID != nil { - l = len(m.NetID) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - } - if m.DLSettings != nil { - l = m.DLSettings.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.RXDelay != 0 { - n += 1 + sovLorawan(uint64(m.RXDelay)) - } return n } @@ -1580,69 +1520,7 @@ func (m *LoRaWANJoinAccept) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppNonce", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppNonce = append(m.AppNonce[:0], data[iNdEx:postIndex]...) - if m.AppNonce == nil { - m.AppNonce = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NetID = append(m.NetID[:0], data[iNdEx:postIndex]...) - if m.NetID == nil { - m.NetID = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1666,63 +1544,11 @@ func (m *LoRaWANJoinAccept) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DLSettings", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.DLSettings == nil { - m.DLSettings = &LoRaWANDLSettings{} - } - if err := m.DLSettings.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} } iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RXDelay", wireType) - } - m.RXDelay = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.RXDelay |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -1938,39 +1764,36 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 541 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x53, 0xcf, 0x8e, 0xd3, 0x3e, - 0x10, 0xfe, 0x75, 0xd3, 0x3f, 0xd9, 0x69, 0x2b, 0xed, 0x5a, 0x3f, 0x41, 0x84, 0x50, 0x55, 0x45, - 0x42, 0xda, 0x53, 0x25, 0x96, 0x03, 0xe2, 0x18, 0x12, 0x22, 0x8a, 0x9a, 0x52, 0x19, 0xd0, 0xee, - 0x11, 0x93, 0x38, 0x68, 0x69, 0x1b, 0x87, 0xd4, 0x80, 0x2a, 0x0e, 0x5c, 0x79, 0x04, 0x9e, 0x83, - 0xa7, 0xe0, 0xc8, 0x23, 0x20, 0x78, 0x11, 0x66, 0x1c, 0x37, 0xdd, 0x82, 0x38, 0xa4, 0xf5, 0xf7, - 0xcd, 0x78, 0xbe, 0x6f, 0xc6, 0x36, 0x0c, 0x57, 0xaa, 0x12, 0x1f, 0x44, 0x31, 0x29, 0x2b, 0xa5, - 0x15, 0x6b, 0xa7, 0xaa, 0x92, 0xfe, 0x27, 0xe8, 0xcf, 0x14, 0x17, 0x17, 0xc1, 0x3c, 0x12, 0x5a, - 0xb0, 0x3b, 0xd0, 0x4e, 0x1e, 0x47, 0xdc, 0x6b, 0x8d, 0x5b, 0x67, 0xfd, 0xf3, 0xd3, 0x09, 0xe5, - 0x4c, 0x6c, 0x02, 0x05, 0x78, 0x7b, 0x8d, 0xbf, 0xec, 0x3e, 0x40, 0x12, 0x84, 0x0b, 0xb1, 0x5d, - 0x29, 0x91, 0x79, 0x47, 0x26, 0xf9, 0xe6, 0x61, 0x72, 0x13, 0xe6, 0xb0, 0x6e, 0xd6, 0xec, 0x04, - 0x9c, 0x64, 0x1a, 0x7a, 0x0e, 0xee, 0x18, 0x70, 0x67, 0x3d, 0x0d, 0xfd, 0x07, 0x8d, 0x01, 0xaa, - 0xcf, 0xfe, 0x87, 0x4e, 0xf2, 0x7c, 0x5b, 0x4a, 0xe3, 0x60, 0xc8, 0x3b, 0x6b, 0x02, 0x86, 0x15, - 0x6f, 0x54, 0x65, 0xa4, 0x88, 0x25, 0xe0, 0x97, 0x70, 0xfa, 0x97, 0x1a, 0x75, 0x10, 0xff, 0xab, - 0x83, 0xd8, 0x74, 0x90, 0x5b, 0x9d, 0x78, 0xa1, 0x2a, 0xbd, 0xab, 0x98, 0x13, 0x60, 0x23, 0x80, - 0x98, 0x27, 0xbb, 0xbe, 0x6a, 0x97, 0x90, 0x37, 0x8c, 0xff, 0xb1, 0x31, 0x4b, 0xa5, 0x98, 0x07, - 0xbd, 0x48, 0xbe, 0x0f, 0xb2, 0xac, 0x32, 0x72, 0x03, 0xde, 0xcb, 0x6a, 0xc8, 0xce, 0xb0, 0x7c, - 0xa8, 0xab, 0x95, 0x9d, 0x0d, 0x3b, 0xb4, 0x41, 0x11, 0x94, 0xa4, 0x3f, 0xc6, 0xd0, 0x6f, 0x58, - 0x68, 0x23, 0x36, 0x44, 0x73, 0xb8, 0x36, 0xe6, 0x9e, 0x96, 0x7a, 0xe3, 0xb5, 0xc7, 0x0e, 0x56, - 0xed, 0xe4, 0x04, 0xfc, 0xcf, 0x2d, 0x18, 0x5c, 0xaf, 0x40, 0xc3, 0x0c, 0x6c, 0xa7, 0x2e, 0x77, - 0x04, 0x1a, 0xba, 0x0d, 0xc7, 0xc8, 0x04, 0xe9, 0x92, 0xcb, 0xb7, 0x46, 0xda, 0xe5, 0xc7, 0x62, - 0x47, 0x98, 0xfc, 0x74, 0x69, 0x94, 0x28, 0x3f, 0x5d, 0xb2, 0x5b, 0xe0, 0xc6, 0x0b, 0x59, 0x64, - 0x57, 0xc5, 0x6b, 0xd4, 0x22, 0xda, 0xcd, 0x2d, 0x36, 0x31, 0xd2, 0x9d, 0xc9, 0xc2, 0xeb, 0x98, - 0xee, 0xdc, 0xdc, 0x62, 0xff, 0x25, 0x30, 0xeb, 0xe4, 0x89, 0xba, 0x2a, 0xb0, 0xf6, 0x3b, 0xb9, - 0xd1, 0xec, 0x06, 0x74, 0x71, 0x1c, 0x8f, 0x5e, 0x4c, 0xed, 0x34, 0xba, 0x99, 0x41, 0xc4, 0x07, - 0x65, 0x49, 0xfc, 0x51, 0xcd, 0x0b, 0x83, 0x48, 0x01, 0xf3, 0xe7, 0xaa, 0x48, 0xa5, 0x9d, 0xb5, - 0x9b, 0x59, 0xec, 0x7f, 0x6d, 0x35, 0x87, 0x4b, 0x12, 0x41, 0x9a, 0xca, 0x52, 0xd3, 0x0e, 0xac, - 0x54, 0xef, 0xa8, 0x35, 0x5c, 0x61, 0x31, 0x0d, 0x6d, 0x2e, 0xf5, 0x34, 0xb2, 0x22, 0x9d, 0x82, - 0xc0, 0xf5, 0x23, 0x72, 0x0e, 0x8f, 0x08, 0xef, 0x70, 0x34, 0x7b, 0x26, 0xb5, 0xc6, 0x66, 0x37, - 0xa6, 0xfb, 0x3f, 0xef, 0xf0, 0x3e, 0xcc, 0x21, 0x6b, 0xd6, 0x54, 0x92, 0x5f, 0x46, 0x72, 0x25, - 0xb6, 0x66, 0x2e, 0x43, 0xde, 0xab, 0x6a, 0xe8, 0x5f, 0x34, 0x9e, 0xf7, 0x5b, 0xd9, 0x18, 0xfa, - 0xfc, 0xf2, 0x6e, 0xc4, 0x55, 0x9e, 0x6f, 0xa4, 0xb6, 0xf7, 0xba, 0x5f, 0xed, 0xa9, 0x3a, 0xe3, - 0x9c, 0xde, 0x1f, 0x17, 0x5a, 0xda, 0x1b, 0x89, 0x19, 0x0d, 0xf5, 0xf0, 0xe4, 0xdb, 0xcf, 0x51, - 0xeb, 0x3b, 0x7e, 0x3f, 0xf0, 0xfb, 0xf2, 0x6b, 0xf4, 0xdf, 0xab, 0xae, 0x79, 0xc4, 0xf7, 0x7e, - 0x07, 0x00, 0x00, 0xff, 0xff, 0x1e, 0x16, 0x3f, 0xc3, 0xd5, 0x03, 0x00, 0x00, + // 491 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x53, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0x25, 0x75, 0x92, 0xba, 0x93, 0x44, 0x6a, 0x57, 0x08, 0x2c, 0x84, 0xa2, 0xc8, 0x12, 0x52, + 0x2f, 0x44, 0xa2, 0x1c, 0x10, 0x47, 0x63, 0x63, 0x11, 0x14, 0x97, 0x68, 0x01, 0x95, 0x23, 0x8b, + 0xbd, 0x46, 0x25, 0x89, 0xd7, 0x38, 0x0b, 0xa8, 0xe2, 0xc0, 0x95, 0x4f, 0xe0, 0x93, 0x38, 0xf2, + 0x09, 0x08, 0x7e, 0x84, 0x99, 0xc9, 0xc6, 0x50, 0xa1, 0x1e, 0x9c, 0xf8, 0xbd, 0x79, 0x99, 0xf7, + 0x66, 0x76, 0x03, 0xa3, 0x95, 0x69, 0xd4, 0x27, 0x55, 0x4d, 0xeb, 0xc6, 0x58, 0x23, 0xba, 0xb9, + 0x69, 0x74, 0xf8, 0x05, 0x06, 0x73, 0x23, 0xd5, 0x59, 0x74, 0x9a, 0x28, 0xab, 0xc4, 0x1d, 0xe8, + 0x66, 0x4f, 0x12, 0x19, 0x74, 0x26, 0x9d, 0xe3, 0xc1, 0xc9, 0xd1, 0x94, 0x34, 0x53, 0x27, 0xa0, + 0x82, 0xec, 0xae, 0xf1, 0x53, 0x3c, 0x00, 0xc8, 0xa2, 0x78, 0xa1, 0x2e, 0x56, 0x46, 0x15, 0xc1, + 0x1e, 0x8b, 0x6f, 0x5e, 0x16, 0xb7, 0x65, 0x09, 0xeb, 0xf6, 0x5d, 0x1c, 0x82, 0x97, 0xcd, 0xe2, + 0xc0, 0xc3, 0x5f, 0x0c, 0xa5, 0xb7, 0x9e, 0xc5, 0xe1, 0xc3, 0x36, 0x00, 0xf5, 0x17, 0xd7, 0xa1, + 0x97, 0xbd, 0xb8, 0xa8, 0x35, 0x27, 0x18, 0xc9, 0xde, 0x9a, 0x00, 0xb3, 0xea, 0x9d, 0x69, 0xd8, + 0x8a, 0x58, 0x02, 0x61, 0x0d, 0x47, 0xff, 0xb9, 0xd1, 0x04, 0xe9, 0x55, 0x13, 0xa4, 0x3c, 0x41, + 0xe9, 0x7c, 0xd2, 0x85, 0x69, 0xec, 0xae, 0x63, 0x49, 0x40, 0x8c, 0x01, 0x52, 0x99, 0xed, 0xe6, + 0xda, 0xa6, 0x84, 0xb2, 0x65, 0xc2, 0xcf, 0x6d, 0x58, 0x6a, 0x25, 0x02, 0xd8, 0x4f, 0xf4, 0xc7, + 0xa8, 0x28, 0x1a, 0xb6, 0x1b, 0xca, 0xfd, 0x62, 0x0b, 0xc5, 0x31, 0xb6, 0x8f, 0x6d, 0xb3, 0x72, + 0xbb, 0x11, 0x97, 0x63, 0x50, 0x05, 0x2d, 0xe9, 0x4b, 0x08, 0xcc, 0x1b, 0x57, 0x96, 0xcd, 0x46, + 0x18, 0x0e, 0xdf, 0x39, 0xdc, 0xb3, 0xda, 0x6e, 0x82, 0xee, 0xc4, 0xc3, 0xae, 0xbd, 0x92, 0x40, + 0xf8, 0xb5, 0x03, 0xc3, 0x7f, 0x3b, 0xd0, 0x32, 0x23, 0x37, 0xa9, 0x2f, 0x3d, 0x85, 0x81, 0x6e, + 0xc3, 0x01, 0x32, 0x51, 0xbe, 0x94, 0xfa, 0x3d, 0x5b, 0xfb, 0xf2, 0x40, 0xed, 0x08, 0xd6, 0xe7, + 0x4b, 0x76, 0x22, 0x7d, 0xbe, 0x14, 0xb7, 0xc0, 0x4f, 0x17, 0xba, 0x2a, 0xce, 0xab, 0xb7, 0xe8, + 0x45, 0xb4, 0x5f, 0x3a, 0xcc, 0x35, 0xf2, 0x9d, 0xeb, 0x2a, 0xe8, 0xf1, 0x74, 0x7e, 0xe9, 0x70, + 0xf8, 0x1a, 0x84, 0x4b, 0xf2, 0xd4, 0x9c, 0x57, 0xd8, 0xfb, 0x83, 0xde, 0x58, 0x71, 0x03, 0xfa, + 0xb8, 0x8e, 0xc7, 0x2f, 0x67, 0x6e, 0x1b, 0xfd, 0x82, 0x11, 0xf1, 0x51, 0x5d, 0x13, 0xbf, 0xb7, + 0xe5, 0x15, 0x23, 0x72, 0x40, 0xfd, 0xa9, 0xa9, 0x72, 0xed, 0x76, 0xed, 0x17, 0x0e, 0x87, 0x77, + 0xdb, 0xb3, 0x25, 0x87, 0x28, 0xcf, 0x75, 0x6d, 0x69, 0xdf, 0xbb, 0xb3, 0x71, 0xfb, 0xae, 0xdd, + 0xc1, 0x9c, 0xb5, 0xf2, 0x64, 0xfe, 0x5c, 0x5b, 0x8b, 0x03, 0x6c, 0xc4, 0x04, 0x06, 0xf2, 0xd5, + 0xbd, 0x44, 0x9a, 0xb2, 0xdc, 0x68, 0xeb, 0x6e, 0xd4, 0xa0, 0xf9, 0x4b, 0x6d, 0x15, 0x27, 0x74, + 0xf3, 0xa5, 0xb2, 0xda, 0xdd, 0x05, 0x54, 0xb4, 0xd4, 0xa3, 0xc3, 0xef, 0xbf, 0xc6, 0x9d, 0x1f, + 0xf8, 0xfc, 0xc4, 0xe7, 0xdb, 0xef, 0xf1, 0xb5, 0x37, 0x7d, 0xfe, 0xfb, 0xdc, 0xff, 0x13, 0x00, + 0x00, 0xff, 0xff, 0x3a, 0x8f, 0x5b, 0x3a, 0x4f, 0x03, 0x00, 0x00, } diff --git a/core/protos/application.proto b/core/protos/application.proto index 97289daa4..256462b21 100644 --- a/core/protos/application.proto +++ b/core/protos/application.proto @@ -12,6 +12,15 @@ message DataAppReq { message DataAppRes {} +message JoinAppReq { + bytes AppEUI = 1; + bytes DevEUI = 2; + repeated Metadata Metadata = 3; +} + +message JoinAppRes {} + service App { rpc HandleData (DataAppReq) returns (DataAppRes); + rpc HandleJoin (JoinAppReq) returns (JoinAppRes); } diff --git a/core/protos/broker.proto b/core/protos/broker.proto index f1777276d..05257f7cf 100644 --- a/core/protos/broker.proto +++ b/core/protos/broker.proto @@ -5,25 +5,38 @@ import "core.proto"; package core; message DataBrokerReq { - LoRaWANData Payload = 1; - Metadata Metadata = 2; + LoRaWANData Payload = 1; + Metadata Metadata = 2; } message DataBrokerRes { - LoRaWANData Payload = 1; - Metadata Metadata = 2; + LoRaWANData Payload = 1; + Metadata Metadata = 2; } message ABPSubBrokerReq { - string HandlerNet = 1; - bytes AppEUI = 2; - bytes DevAddr = 3; - bytes NwkSKey = 4; + string HandlerNet = 1; + bytes AppEUI = 2; + bytes DevAddr = 3; + bytes NwkSKey = 4; } message ABPSubBrokerRes{} +message JoinBrokerReq { + bytes AppEUI = 1; + bytes DevEUI = 2; + bytes DevNonce = 3; + Metadata Metadata = 4; +} + +message JoinBrokerRes { + LoRaWANJoinAccept Payload = 1; + Metadata Metadata = 2; +} + service Broker { rpc HandleData (DataBrokerReq) returns (DataBrokerRes); + rpc HandleJoin (JoinBrokerReq) returns (JoinBrokerRes); rpc SubscribePersonalized (ABPSubBrokerReq) returns (ABPSubBrokerRes); } diff --git a/core/protos/handler.proto b/core/protos/handler.proto index 1f211fc2a..0348dcb07 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -35,8 +35,23 @@ message ABPSubHandlerReq { message ABPSubHandlerRes {} +message JoinHandlerReq { + bytes AppEUI = 1; + bytes DevEUI = 2; + bytes DevNonce = 3; + Metadata Metadata = 4; +} + +message JoinHandlerRes { + LoRaWANJoinAccept Payload = 1; + bytes DevAddr = 2; + bytes NwkSKey = 3; + Metadata Metadata = 4; +} + service Handler { rpc HandleDataUp (DataUpHandlerReq) returns (DataUpHandlerRes); rpc HandleDataDown (DataDownHandlerReq) returns (DataDownHandlerRes); + rpc HandleJoin (JoinHandlerReq) returns (JoinHandlerRes); rpc SubscribePersonalized (ABPSubHandlerReq) returns (ABPSubHandlerRes); } diff --git a/core/protos/lorawan.proto b/core/protos/lorawan.proto index 760d5c48e..65596253a 100644 --- a/core/protos/lorawan.proto +++ b/core/protos/lorawan.proto @@ -2,6 +2,7 @@ syntax = "proto3"; package core; +// Uplink & Downlink Data (confirmed or unconfirmed) message LoRaWANData { LoRaWANMHDR MHDR = 1; LoRaWANMACPayload MACPayload = 2; @@ -43,12 +44,7 @@ message LoRaWANJoinRequest { } message LoRaWANJoinAccept { - bytes AppNonce = 1; - bytes NetID = 2; - bytes DevAddr = 3; - LoRaWANDLSettings DLSettings = 4; - uint32 RXDelay = 5; - + bytes Payload = 1; } message LoRaWANDLSettings { diff --git a/core/protos/router.proto b/core/protos/router.proto index b9af02fb0..f2ab8f1d8 100644 --- a/core/protos/router.proto +++ b/core/protos/router.proto @@ -5,24 +5,38 @@ import "core.proto"; package core; message DataRouterReq { - LoRaWANData Payload = 1; - Metadata Metadata = 2; - bytes GatewayID = 3; + bytes GatewayID = 1; + LoRaWANData Payload = 2; + Metadata Metadata = 3; } message DataRouterRes { - LoRaWANData Payload = 1; - Metadata Metadata = 2; + LoRaWANData Payload = 1; + Metadata Metadata = 2; } message StatsReq { - bytes GatewayID = 1; - StatsMetadata Metadata = 2; + bytes GatewayID = 1; + StatsMetadata Metadata = 2; } message StatsRes{} +message JoinRouterReq { + bytes GatewayID = 1; + bytes AppEUI = 2; + bytes DevEUI = 3; + bytes DevNonce = 4; + Metadata Metadata = 5; +} + +message JoinRouterRes { + LoRaWANJoinAccept Payload = 1; + Metadata Metadata = 2; +} + service Router { rpc HandleData (DataRouterReq) returns (DataRouterRes); rpc HandleStats (StatsReq) returns (StatsRes); + rpc HandleJoin (JoinRouterReq) returns (JoinRouterRes); } diff --git a/core/router.pb.go b/core/router.pb.go index 8bc93f88d..88bdd66d4 100644 --- a/core/router.pb.go +++ b/core/router.pb.go @@ -21,9 +21,9 @@ var _ = fmt.Errorf var _ = math.Inf type DataRouterReq struct { - Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` - GatewayID []byte `protobuf:"bytes,3,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` + GatewayID []byte `protobuf:"bytes,1,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` + Payload *LoRaWANData `protobuf:"bytes,2,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,3,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *DataRouterReq) Reset() { *m = DataRouterReq{} } @@ -94,11 +94,57 @@ func (m *StatsRes) String() string { return proto.CompactTextString(m func (*StatsRes) ProtoMessage() {} func (*StatsRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{3} } +type JoinRouterReq struct { + GatewayID []byte `protobuf:"bytes,1,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevNonce []byte `protobuf:"bytes,4,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` + Metadata *Metadata `protobuf:"bytes,5,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinRouterReq) Reset() { *m = JoinRouterReq{} } +func (m *JoinRouterReq) String() string { return proto.CompactTextString(m) } +func (*JoinRouterReq) ProtoMessage() {} +func (*JoinRouterReq) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{4} } + +func (m *JoinRouterReq) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type JoinRouterRes struct { + Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` +} + +func (m *JoinRouterRes) Reset() { *m = JoinRouterRes{} } +func (m *JoinRouterRes) String() string { return proto.CompactTextString(m) } +func (*JoinRouterRes) ProtoMessage() {} +func (*JoinRouterRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{5} } + +func (m *JoinRouterRes) GetPayload() *LoRaWANJoinAccept { + if m != nil { + return m.Payload + } + return nil +} + +func (m *JoinRouterRes) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + func init() { proto.RegisterType((*DataRouterReq)(nil), "core.DataRouterReq") proto.RegisterType((*DataRouterRes)(nil), "core.DataRouterRes") proto.RegisterType((*StatsReq)(nil), "core.StatsReq") proto.RegisterType((*StatsRes)(nil), "core.StatsRes") + proto.RegisterType((*JoinRouterReq)(nil), "core.JoinRouterReq") + proto.RegisterType((*JoinRouterRes)(nil), "core.JoinRouterRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -110,6 +156,7 @@ var _ grpc.ClientConn type RouterClient interface { HandleData(ctx context.Context, in *DataRouterReq, opts ...grpc.CallOption) (*DataRouterRes, error) HandleStats(ctx context.Context, in *StatsReq, opts ...grpc.CallOption) (*StatsRes, error) + HandleJoin(ctx context.Context, in *JoinRouterReq, opts ...grpc.CallOption) (*JoinRouterRes, error) } type routerClient struct { @@ -138,11 +185,21 @@ func (c *routerClient) HandleStats(ctx context.Context, in *StatsReq, opts ...gr return out, nil } +func (c *routerClient) HandleJoin(ctx context.Context, in *JoinRouterReq, opts ...grpc.CallOption) (*JoinRouterRes, error) { + out := new(JoinRouterRes) + err := grpc.Invoke(ctx, "/core.Router/HandleJoin", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Router service type RouterServer interface { HandleData(context.Context, *DataRouterReq) (*DataRouterRes, error) HandleStats(context.Context, *StatsReq) (*StatsRes, error) + HandleJoin(context.Context, *JoinRouterReq) (*JoinRouterRes, error) } func RegisterRouterServer(s *grpc.Server, srv RouterServer) { @@ -173,6 +230,18 @@ func _Router_HandleStats_Handler(srv interface{}, ctx context.Context, dec func( return out, nil } +func _Router_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(JoinRouterReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterServer).HandleJoin(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + var _Router_serviceDesc = grpc.ServiceDesc{ ServiceName: "core.Router", HandlerType: (*RouterServer)(nil), @@ -185,6 +254,10 @@ var _Router_serviceDesc = grpc.ServiceDesc{ MethodName: "HandleStats", Handler: _Router_HandleStats_Handler, }, + { + MethodName: "HandleJoin", + Handler: _Router_HandleJoin_Handler, + }, }, Streams: []grpc.StreamDesc{}, } @@ -204,8 +277,16 @@ func (m *DataRouterReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if m.GatewayID != nil { + if len(m.GatewayID) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) + } + } if m.Payload != nil { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) n1, err := m.Payload.MarshalTo(data[i:]) @@ -215,7 +296,7 @@ func (m *DataRouterReq) MarshalTo(data []byte) (int, error) { i += n1 } if m.Metadata != nil { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) n2, err := m.Metadata.MarshalTo(data[i:]) @@ -224,14 +305,6 @@ func (m *DataRouterReq) MarshalTo(data []byte) (int, error) { } i += n2 } - if m.GatewayID != nil { - if len(m.GatewayID) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } - } return i, nil } @@ -327,6 +400,104 @@ func (m *StatsRes) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *JoinRouterReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinRouterReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.GatewayID != nil { + if len(m.GatewayID) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) + } + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintRouter(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.DevNonce != nil { + if len(m.DevNonce) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintRouter(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) + } + } + if m.Metadata != nil { + data[i] = 0x2a + i++ + i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) + n6, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} + +func (m *JoinRouterRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinRouterRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Payload != nil { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) + n7, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.Metadata != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) + n8, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} + func encodeFixed64Router(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -357,6 +528,12 @@ func encodeVarintRouter(data []byte, offset int, v uint64) int { func (m *DataRouterReq) Size() (n int) { var l int _ = l + if m.GatewayID != nil { + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } if m.Payload != nil { l = m.Payload.Size() n += 1 + l + sovRouter(uint64(l)) @@ -365,12 +542,6 @@ func (m *DataRouterReq) Size() (n int) { l = m.Metadata.Size() n += 1 + l + sovRouter(uint64(l)) } - if m.GatewayID != nil { - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - } return n } @@ -410,6 +581,54 @@ func (m *StatsRes) Size() (n int) { return n } +func (m *JoinRouterReq) Size() (n int) { + var l int + _ = l + if m.GatewayID != nil { + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } + if m.DevNonce != nil { + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *JoinRouterRes) Size() (n int) { + var l int + _ = l + if m.Payload != nil { + l = m.Payload.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + func sovRouter(x uint64) (n int) { for { n++ @@ -454,9 +673,9 @@ func (m *DataRouterReq) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -466,28 +685,26 @@ func (m *DataRouterReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - if m.Payload == nil { - m.Payload = &LoRaWANData{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) + if m.GatewayID == nil { + m.GatewayID = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -511,18 +728,18 @@ func (m *DataRouterReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Metadata == nil { - m.Metadata = &Metadata{} + if m.Payload == nil { + m.Payload = &LoRaWANData{} } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -532,21 +749,23 @@ func (m *DataRouterReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) - if m.GatewayID == nil { - m.GatewayID = []byte{} + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex default: @@ -850,6 +1069,329 @@ func (m *StatsRes) Unmarshal(data []byte) error { } return nil } +func (m *JoinRouterReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinRouterReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinRouterReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) + if m.GatewayID == nil { + m.GatewayID = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) + if m.DevNonce == nil { + m.DevNonce = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinRouterRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinRouterRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinRouterRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Payload == nil { + m.Payload = &LoRaWANJoinAccept{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipRouter(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -956,22 +1498,28 @@ var ( ) var fileDescriptorRouter = []byte{ - // 266 bytes of a gzipped FileDescriptorProto + // 368 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xca, 0x2f, 0x2d, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, - 0x36, 0x46, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0xc4, 0x20, 0xb0, 0xae, 0xa0, 0xd4, 0x42, 0x21, 0x6d, - 0x2e, 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, - 0x41, 0x3d, 0xb0, 0x7a, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0xb0, 0x62, 0xf6, 0x02, 0x88, - 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, - 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x12, 0x92, 0xe1, 0xe2, 0x74, 0x4f, 0x2c, - 0x49, 0x2d, 0x4f, 0xac, 0xf4, 0x74, 0x91, 0x60, 0x06, 0x2a, 0xe6, 0x09, 0xe2, 0x4c, 0x87, 0x09, - 0x28, 0x65, 0xa0, 0xba, 0xa3, 0x98, 0x66, 0xee, 0x50, 0x8a, 0xe4, 0xe2, 0x08, 0x2e, 0x49, 0x2c, - 0x29, 0x06, 0x79, 0x16, 0xc5, 0x4d, 0x8c, 0x68, 0x6e, 0x12, 0xd2, 0xc7, 0x30, 0x55, 0x18, 0x62, - 0x2a, 0x58, 0x3f, 0x16, 0xa3, 0xb9, 0xe0, 0x46, 0x17, 0x1b, 0xe5, 0x73, 0xb1, 0x41, 0x3c, 0x23, - 0x64, 0xc6, 0xc5, 0xe5, 0x91, 0x98, 0x97, 0x92, 0x93, 0x0a, 0x72, 0xb3, 0x10, 0xd4, 0x08, 0x94, - 0x40, 0x97, 0xc2, 0x22, 0x58, 0x2c, 0xa4, 0xcb, 0xc5, 0x0d, 0xd1, 0x07, 0x36, 0x53, 0x88, 0x0f, - 0xc9, 0x6e, 0x90, 0x1e, 0x54, 0x7e, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x80, 0xf8, - 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x8e, 0x63, 0x63, 0x40, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xe4, 0x6d, 0x5d, 0xea, 0x14, 0x02, 0x00, 0x00, + 0x36, 0x46, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0xc4, 0x20, 0xb0, 0xae, 0xa0, 0xd4, 0x42, 0x21, 0x19, + 0x2e, 0x4e, 0xf7, 0xc4, 0x92, 0xd4, 0xf2, 0xc4, 0x4a, 0x4f, 0x17, 0x09, 0x46, 0x05, 0x46, 0x0d, + 0x9e, 0x20, 0xce, 0x74, 0x98, 0x80, 0x90, 0x36, 0x17, 0x7b, 0x40, 0x62, 0x65, 0x4e, 0x7e, 0x62, + 0x8a, 0x04, 0x13, 0x50, 0x8e, 0xdb, 0x48, 0x50, 0x0f, 0x6c, 0x9a, 0x4f, 0x7e, 0x50, 0x62, 0xb8, + 0xa3, 0x1f, 0xd8, 0x28, 0xf6, 0x02, 0x88, 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, + 0x14, 0xa0, 0xa0, 0x04, 0x33, 0x58, 0x35, 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, + 0x52, 0xca, 0x40, 0x75, 0x47, 0x31, 0xb2, 0x4d, 0x8c, 0x24, 0xd9, 0xc4, 0x44, 0xc0, 0xa6, 0x48, + 0x2e, 0x8e, 0xe0, 0x92, 0xc4, 0x92, 0x62, 0xc2, 0x9e, 0xd5, 0xc7, 0x30, 0x55, 0x18, 0x62, 0x2a, + 0x58, 0x3f, 0x16, 0xa3, 0xb9, 0xe0, 0x46, 0x17, 0x2b, 0x2d, 0x05, 0x86, 0xac, 0x57, 0x7e, 0x66, + 0x1e, 0xb1, 0x21, 0x2b, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0xb6, 0x8a, 0x27, + 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, 0xbb, 0xa4, 0x96, 0x81, 0xc4, 0x99, 0x21, 0xe2, 0x29, 0x60, + 0x9e, 0x90, 0x14, 0x17, 0x07, 0x50, 0xdc, 0x2f, 0x3f, 0x2f, 0x39, 0x55, 0x82, 0x05, 0x2c, 0xc3, + 0x91, 0x02, 0xe5, 0xa3, 0x04, 0x07, 0x2b, 0x81, 0xe0, 0xc8, 0x43, 0x75, 0x66, 0xb1, 0x90, 0x21, + 0x7a, 0xc0, 0x8b, 0xa3, 0x04, 0x3c, 0x48, 0xb1, 0x63, 0x72, 0x72, 0x6a, 0x41, 0x09, 0x59, 0xc1, + 0x6f, 0xb4, 0x9c, 0x91, 0x8b, 0x0d, 0x62, 0x99, 0x90, 0x19, 0x17, 0x97, 0x47, 0x62, 0x5e, 0x4a, + 0x4e, 0x2a, 0x28, 0x32, 0x85, 0xa0, 0x61, 0x8b, 0x92, 0x1a, 0xa5, 0xb0, 0x08, 0x16, 0x0b, 0xe9, + 0x72, 0x71, 0x43, 0xf4, 0x81, 0x03, 0x5b, 0x88, 0x0f, 0x29, 0x52, 0x40, 0x7a, 0x50, 0xf9, 0xc5, + 0x08, 0x6b, 0x40, 0x4e, 0x87, 0x59, 0x83, 0x12, 0x35, 0x52, 0x58, 0x04, 0x8b, 0x9d, 0x04, 0x4e, + 0x3c, 0x92, 0x63, 0xbc, 0x00, 0xc4, 0x0f, 0x80, 0x78, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0x70, + 0xa6, 0x31, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xed, 0x4d, 0x7e, 0xd8, 0x65, 0x03, 0x00, 0x00, } From 996958c529cd8c1df22b76d3849ad41befc9191a Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 18:43:59 +0100 Subject: [PATCH 1100/2266] [feature/otaa] Handle join request and join accept in udp adapter --- core/adapters/udp/conversions.go | 122 +++++++++++++++----------- core/adapters/udp/conversions_test.go | 72 ++++++--------- core/adapters/udp/udp.go | 66 ++++++++++++-- 3 files changed, 153 insertions(+), 107 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index e2f3ab331..932aa267d 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -4,6 +4,7 @@ package udp import ( + "encoding" "encoding/base64" "reflect" "strings" @@ -16,7 +17,7 @@ import ( "github.com/brocaar/lorawan" ) -func newDataRouterReq(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (*core.DataRouterReq, error) { +func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interface{}, error) { // First, we have to get the physical payload which is encoded in the Data field if rxpk.Data == nil { return nil, errors.New(errors.Structural, "There's no data in the packet") @@ -32,69 +33,83 @@ func newDataRouterReq(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (*core.D return nil, errors.New(errors.Structural, err) } - macpayload, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok { - // TODO OTAA join request payloads - return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a MACPayload") - } - if len(macpayload.FRMPayload) != 1 { - // TODO Handle pure MAC Commands payloads (FType = 0) - return nil, errors.New(errors.Implementation, "Unhandled Physical payload. Expected a Data Payload") - } - frmpayload, err := macpayload.FRMPayload[0].MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } + // Switch over MType + switch payload.MHDR.MType { + case lorawan.ConfirmedDataUp: + fallthrough + case lorawan.UnconfirmedDataUp: + macpayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + // TODO OTAA join request payloads + return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a MACPayload") + } + if len(macpayload.FRMPayload) != 1 { + // TODO Handle pure MAC Commands payloads (FType = 0) + return nil, errors.New(errors.Implementation, "Unhandled Physical payload. Expected a Data Payload") + } + frmpayload, err := macpayload.FRMPayload[0].MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } - var fopts [][]byte - for _, cmd := range macpayload.FHDR.FOpts { - if data, err := cmd.MarshalBinary(); err == nil { // We just ignore invalid MAC Commands - fopts = append(fopts, data) + var fopts [][]byte + for _, cmd := range macpayload.FHDR.FOpts { + if data, err := cmd.MarshalBinary(); err == nil { // We just ignore invalid MAC Commands + fopts = append(fopts, data) + } } - } - // At the end, our converted packet hold the same metadata than the RXPK packet but the Data - // which as been completely transformed into a lorawan Physical Payload. - return &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, + // At the end, our converted packet hold the same metadata than the RXPK packet but the Data + // which as been completely transformed into a lorawan Physical Payload. + return &core.DataRouterReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(payload.MHDR.MType), + Major: uint32(payload.MHDR.Major), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: macpayload.FHDR.DevAddr[:], + FCnt: macpayload.FHDR.FCnt, + FCtrl: &core.LoRaWANFCtrl{ + ADR: macpayload.FHDR.FCtrl.ADR, + ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, + Ack: macpayload.FHDR.FCtrl.ACK, + FPending: macpayload.FHDR.FCtrl.FPending, + }, + FOpts: fopts, }, - FOpts: fopts, + FPort: uint32(macpayload.FPort), + FRMPayload: frmpayload, }, - FPort: uint32(macpayload.FPort), - FRMPayload: frmpayload, + MIC: payload.MIC[:], }, - MIC: payload.MIC[:], - }, - Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), - GatewayID: gid, - }, nil + Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), + GatewayID: gid, + }, nil + case lorawan.JoinRequest: + joinpayload, ok := payload.MACPayload.(*lorawan.JoinRequestPayload) + if !ok { + return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a JoinRequest Payload") + } + return &core.JoinRouterReq{ + GatewayID: gid, + AppEUI: joinpayload.AppEUI[:], + DevEUI: joinpayload.DevEUI[:], + DevNonce: joinpayload.DevNonce[:], + Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), + }, nil + default: + return nil, errors.New(errors.Structural, "Unexpected and unhandled LoRaWAN MHDR Mtype") + } } -func newTXPK(resp core.DataRouterRes, ctx log.Interface) (semtech.TXPK, error) { +func newTXPK(payload encoding.BinaryMarshaler, metadata *core.Metadata, ctx log.Interface) (semtech.TXPK, error) { // Step 0: validate the response - if resp.Metadata == nil { + if metadata == nil { return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Metadata") } - // Step 1: create a new LoRaWAN payload - payload, err := core.NewLoRaWANData(resp.Payload, false) - if err != nil { - return semtech.TXPK{}, errors.New(errors.Structural, err) - } - // Step2: Convert the physical payload to a base64 string (without the padding) raw, err := payload.MarshalBinary() if err != nil { @@ -105,7 +120,7 @@ func newTXPK(resp core.DataRouterRes, ctx log.Interface) (semtech.TXPK, error) { // Step 3, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. - injectMetadata(&txpk, *resp.Metadata) // Validation::2 + injectMetadata(&txpk, *metadata) return txpk, nil } @@ -157,5 +172,6 @@ func extractMetadata(xpk interface{}, target interface{}) interface{} { } } } + return target } diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index eea91a6ed..6e27e228e 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -157,7 +157,7 @@ func TestExtractMetadta(t *testing.T) { } // func newTXPK(resp core.DataRouterRes, ctx log.Interface) (semtech.TXPK, error) { -func TestNewTXPKAndNewDataRouterReq(t *testing.T) { +func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { { Desc(t, "Test Valid marshal / unmarshal") @@ -191,10 +191,12 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { DataRate: "SF8BW125", }, } + payload, err := core.NewLoRaWANData(res.Payload, false) + FatalUnless(t, err) gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} // Expectations - var wantReq = core.DataRouterReq{ + var wantReq = &core.DataRouterReq{ Payload: res.Payload, Metadata: res.Metadata, GatewayID: gid, @@ -203,8 +205,8 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { var wantErrReq *string // Operate - txpk, errTXPK := newTXPK(res, GetLogger(t, "Convert TXPK")) - req, errReq := newDataRouterReq(semtech.RXPK{ + txpk, errTXPK := newTXPK(payload, res.Metadata, GetLogger(t, "Convert TXPK")) + req, errReq := toLoRaWANPayload(semtech.RXPK{ Codr: txpk.Codr, Datr: txpk.Datr, Data: txpk.Data, @@ -213,7 +215,7 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { // Check CheckErrors(t, wantErrTXPK, errTXPK) CheckErrors(t, wantErrReq, errReq) - Check(t, wantReq, *req, "Data Router Requests") + Check(t, wantReq, req, "Data Router Requests") } // -------------------- @@ -248,39 +250,15 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { }, Metadata: nil, } + payload, err := core.NewLoRaWANData(res.Payload, false) + FatalUnless(t, err) // Expectations var wantTXPK semtech.TXPK var wantErr = ErrStructural // Operate - txpk, errTXPK := newTXPK(res, GetLogger(t, "Convert TXPK")) - - // Check - CheckErrors(t, wantErr, errTXPK) - Check(t, wantTXPK, txpk, "TXPKs") - } - - // -------------------- - - { - Desc(t, "New TXPK with an invalid payload") - - // Build - res := core.DataRouterRes{ - Payload: new(core.LoRaWANData), - Metadata: &core.Metadata{ - CodingRate: "4/5", - DataRate: "SF8BW125", - }, - } - - // Expectations - var wantTXPK semtech.TXPK - var wantErr = ErrStructural - - // Operate - txpk, errTXPK := newTXPK(res, GetLogger(t, "Convert TXPK")) + txpk, errTXPK := newTXPK(payload, res.Metadata, GetLogger(t, "Convert TXPK")) // Check CheckErrors(t, wantErr, errTXPK) @@ -290,7 +268,7 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { // -------------------- { - Desc(t, "Test newDataRouter with invalid macpayload") + Desc(t, "Test toLoRaWANPayload with invalid macpayload") // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} @@ -308,11 +286,11 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { } // Expectations - var wantReq *core.DataRouterReq + var wantReq interface{} var wantErr = ErrStructural // Operate - req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) // Check CheckErrors(t, wantErr, err) @@ -322,7 +300,7 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { // -------------------- { - Desc(t, "Test newDataRouter with no data in rxpk") + Desc(t, "Test toLoRaWANPayload with no data in rxpk") // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} @@ -332,11 +310,11 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { } // Expectations - var wantReq *core.DataRouterReq + var wantReq interface{} var wantErr = ErrStructural // Operate - req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) // Check CheckErrors(t, wantErr, err) @@ -346,7 +324,7 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { // -------------------- { - Desc(t, "Test newDataRouter with random encoded data in rxpk") + Desc(t, "Test toLoRaWANPayload with random encoded data in rxpk") // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} @@ -357,11 +335,11 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { } // Expectations - var wantReq *core.DataRouterReq + var wantReq interface{} var wantErr = ErrStructural // Operate - req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) // Check CheckErrors(t, wantErr, err) @@ -371,7 +349,7 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { // -------------------- { - Desc(t, "Test newDataRouter with not base64 data") + Desc(t, "Test toLoRaWANPayload with not base64 data") // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} @@ -382,11 +360,11 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { } // Expectations - var wantReq *core.DataRouterReq + var wantReq interface{} var wantErr = ErrStructural // Operate - req, err := newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) // Check CheckErrors(t, wantErr, err) @@ -396,12 +374,12 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { // -------------------- { - Desc(t, "Test newDataRouter with mac commands in fopts") + Desc(t, "Test toLoRaWANPayload with mac commands in fopts") // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} payload := lorawan.NewPHYPayload(false) - payload.MHDR.MType = lorawan.UnconfirmedDataDown + payload.MHDR.MType = lorawan.UnconfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(false) @@ -428,7 +406,7 @@ func TestNewTXPKAndNewDataRouterReq(t *testing.T) { var wantErr *string // Operate - _, err = newDataRouterReq(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + _, err = toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) // Check CheckErrors(t, wantErr, err) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 59a411bbe..88cf5b78d 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -39,6 +39,14 @@ type Options struct { // replier is an alias used by methods herebelow type replier func(data []byte) error +// straightMarshaler can be used to easily obtain a binary marshaler from a sequence of byte +type straightMarshaler []byte + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (s straightMarshaler) MarshalBinary() ([]byte, error) { + return s, nil +} + // Start constructs and launches a new udp adapter func Start(c Components, o Options) (err error) { // Create the udp connection and start listening with a goroutine @@ -192,17 +200,31 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { } func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier) error { - dataRouterReq, err := newDataRouterReq(rxpk, gid, a.Ctx) + itf, err := toLoRaWANPayload(rxpk, gid, a.Ctx) if err != nil { a.Ctx.WithError(err).Debug("Invalid up RXPK packet") return errors.New(errors.Structural, err) } - resp, err := a.Router.HandleData(context.Background(), dataRouterReq) - if err != nil { - a.Ctx.WithError(err).Debug("Router failed to process uplink") - errors.New(errors.Operational, err) + + switch itf.(type) { + case *core.DataRouterReq: + resp, err := a.Router.HandleData(context.Background(), itf.(*core.DataRouterReq)) + if err != nil { + a.Ctx.WithError(err).Debug("Router failed to process uplink") + return errors.New(errors.Operational, err) + } + return a.handleDataDown(resp, reply) + case *core.JoinRouterReq: + resp, err := a.Router.HandleJoin(context.Background(), itf.(*core.JoinRouterReq)) + if err != nil { + a.Ctx.WithError(err).Debug("Router failed to process join request") + return errors.New(errors.Operational, err) + } + return a.handleJoinAccept(resp, reply) + default: + a.Ctx.Warn("Unhandled LoRaWAN Payload type") + return errors.New(errors.Implementation, "Unhandled LoRaWAN Payload type") } - return a.handleDataDown(resp, reply) } func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { @@ -212,7 +234,12 @@ func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { return nil } - txpk, err := newTXPK(*resp, a.Ctx) + payload, err := core.NewLoRaWANData(resp.Payload, false) + if err != nil { + return errors.New(errors.Structural, err) + } + + txpk, err := newTXPK(payload, resp.Metadata, a.Ctx) if err != nil { a.Ctx.WithError(err).Debug("Unable to interpret downlink") return errors.New(errors.Structural, err) @@ -230,3 +257,28 @@ func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { } return reply(data) } + +func (a adapter) handleJoinAccept(resp *core.JoinRouterRes, reply replier) error { + a.Ctx.Debug("Handle Join-Accept from router") + if resp == nil || resp.Payload == nil { + a.Ctx.Debug("Invalid response") + return errors.New(errors.Structural, "Invalid Join-Accept response. Expected a payload") + } + + txpk, err := newTXPK(straightMarshaler(resp.Payload.Payload), resp.Metadata, a.Ctx) + if err != nil { + a.Ctx.WithError(err).Debug("Unable to interpret Join-Accept") + return errors.New(errors.Structural, err) + } + + a.Ctx.Debug("Creating a new join-accept response") + data, err := semtech.Packet{ + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{TXPK: &txpk}, + }.MarshalBinary() + if err != nil { + a.Ctx.WithError(err).Debug("Unable to create semtech packet with TXPK") + } + return reply(data) +} From f9f8126b0cbbbc2407be0b9ad0b13a3cacb8934b Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 18:50:07 +0100 Subject: [PATCH 1101/2266] [feature/otaa] Add HandleJoin to mock routerserver --- core/mocks/mocks.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 42b73cccf..c79620e16 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -162,6 +162,13 @@ type RouterServer struct { OutHandleStats struct { Res *core.StatsRes } + InHandleJoin struct { + Ctx context.Context + Req *core.JoinRouterReq + } + OutHandleJoin struct { + Res *core.JoinRouterRes + } } // NewRouterServer creates a new mock RouterServer @@ -185,6 +192,13 @@ func (m *RouterServer) HandleStats(ctx context.Context, in *core.StatsReq) (*cor return m.OutHandleStats.Res, m.Failures["HandleStats"] } +// HandleJoin implements the core.RouterServer interface +func (m *RouterServer) HandleJoin(ctx context.Context, in *core.JoinRouterReq) (*core.JoinRouterRes, error) { + m.InHandleJoin.Ctx = ctx + m.InHandleJoin.Req = in + return m.OutHandleJoin.Res, m.Failures["HandleJoin"] +} + // DutyManager mocks the dutycycle.DutyManager interface type DutyManager struct { Failures map[string]error From 3d017bb1e7dacede8cf8405943a956e310ec4dcd Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 19:15:46 +0100 Subject: [PATCH 1102/2266] [feature/otaa] Add tests to udp adapter --- core/adapters/udp/conversions.go | 1 - core/adapters/udp/conversions_test.go | 81 +++++- core/adapters/udp/udp_test.go | 359 ++++++++++++++++++++++++++ 3 files changed, 439 insertions(+), 2 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 932aa267d..8091769cf 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -40,7 +40,6 @@ func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interfa case lorawan.UnconfirmedDataUp: macpayload, ok := payload.MACPayload.(*lorawan.MACPayload) if !ok { - // TODO OTAA join request payloads return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a MACPayload") } if len(macpayload.FRMPayload) != 1 { diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index 6e27e228e..c54a5110f 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -379,7 +379,7 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} payload := lorawan.NewPHYPayload(false) - payload.MHDR.MType = lorawan.UnconfirmedDataUp + payload.MHDR.MType = lorawan.ConfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(false) @@ -411,4 +411,83 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { // Check CheckErrors(t, wantErr, err) } + + // -------------------- + + { + Desc(t, "Test toLoRaWANPayload with unhandled mtype") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + payload := lorawan.NewPHYPayload(false) + payload.MHDR.MType = lorawan.UnconfirmedDataDown + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + macpayload := lorawan.NewMACPayload(false) + macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} + payload.MACPayload = macpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + } + + // Expectations + var wantErr = ErrStructural + + // Operate + _, err = toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + } + + // -------------------- + + { + Desc(t, "Test toLoRaWANPayload with join-request mtype") + + // Build + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + payload := lorawan.NewPHYPayload(false) + payload.MHDR.MType = lorawan.JoinRequest + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + joinpayload := &lorawan.JoinRequestPayload{ + AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevNonce: [2]byte{1, 2}, + } + payload.MACPayload = joinpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + rxpk := semtech.RXPK{ + Codr: pointer.String("4/5"), + Freq: pointer.Float32(867.345), + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + } + + // Expectations + var wantErr *string + var wantReq = &core.JoinRouterReq{ + GatewayID: gid, + AppEUI: joinpayload.AppEUI[:], + DevEUI: joinpayload.DevEUI[:], + DevNonce: joinpayload.DevNonce[:], + Metadata: &core.Metadata{ + CodingRate: *rxpk.Codr, + Frequency: *rxpk.Freq, + }, + } + + // Operate + req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantReq, req, "Join Router Requests") + } } diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index de745ba8d..a151d2721 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -885,4 +885,363 @@ func TestUDPAdapter(t *testing.T) { Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") } + + // -------------------- + + { + Desc(t, "Send a valid join through udp, with valid join-accept") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.JoinRequest + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + joinpayload := &lorawan.JoinRequestPayload{ + AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevNonce: [2]byte{14, 42}, + } + payload.MACPayload = joinpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + dataDown := []byte("accept") + router := mocks.NewRouterServer() + router.OutHandleJoin.Res = &core.JoinRouterRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: dataDown, + }, + Metadata: new(core.Metadata), + } + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantJoinRouterReq = &core.JoinRouterReq{ + GatewayID: packet.GatewayId, + DevEUI: joinpayload.DevEUI[:], + AppEUI: joinpayload.AppEUI[:], + DevNonce: joinpayload.DevNonce[:], + Metadata: new(core.Metadata), + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + { + Version: semtech.VERSION, + Identifier: semtech.PULL_RESP, + Payload: &semtech.Payload{ + TXPK: &semtech.TXPK{ + Data: pointer.String(base64.RawStdEncoding.EncodeToString(dataDown)), + }, + }, + }, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid join through udp, no payload in accept") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.JoinRequest + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + joinpayload := &lorawan.JoinRequestPayload{ + AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevNonce: [2]byte{14, 42}, + } + payload.MACPayload = joinpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + router.OutHandleJoin.Res = &core.JoinRouterRes{ + Payload: nil, + Metadata: new(core.Metadata), + } + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantJoinRouterReq = &core.JoinRouterReq{ + GatewayID: packet.GatewayId, + DevEUI: joinpayload.DevEUI[:], + AppEUI: joinpayload.AppEUI[:], + DevNonce: joinpayload.DevNonce[:], + Metadata: new(core.Metadata), + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid join through udp, no metadata in response") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.JoinRequest + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + joinpayload := &lorawan.JoinRequestPayload{ + AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevNonce: [2]byte{14, 42}, + } + payload.MACPayload = joinpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + dataDown := []byte("accept") + router := mocks.NewRouterServer() + router.OutHandleJoin.Res = &core.JoinRouterRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: dataDown, + }, + Metadata: nil, + } + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantJoinRouterReq = &core.JoinRouterReq{ + GatewayID: packet.GatewayId, + DevEUI: joinpayload.DevEUI[:], + AppEUI: joinpayload.AppEUI[:], + DevNonce: joinpayload.DevNonce[:], + Metadata: new(core.Metadata), + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + + // -------------------- + + { + Desc(t, "Send a valid join through udp, router fails to handle") + + // Build + payload := lorawan.NewPHYPayload(true) + payload.MHDR.MType = lorawan.JoinRequest + payload.MHDR.Major = lorawan.LoRaWANR1 + payload.MIC = [4]byte{1, 2, 3, 4} + joinpayload := &lorawan.JoinRequestPayload{ + AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, + DevNonce: [2]byte{14, 42}, + } + payload.MACPayload = joinpayload + data, err := payload.MarshalBinary() + FatalUnless(t, err) + + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PUSH_DATA, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), + }, + }, + }, + } + data, err = packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + router.Failures["HandleJoin"] = fmt.Errorf("Mock Error") + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantJoinRouterReq = &core.JoinRouterReq{ + GatewayID: packet.GatewayId, + DevEUI: joinpayload.DevEUI[:], + AppEUI: joinpayload.AppEUI[:], + DevNonce: joinpayload.DevNonce[:], + Metadata: new(core.Metadata), + } + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PUSH_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } } From ebe7fc0b1279aa2bd9c75f6b09e39dfbac4129ad Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 19 Mar 2016 19:35:07 +0100 Subject: [PATCH 1103/2266] [feature/otaa] Implements HandleJoin for Router. Need tests --- core/broker.pb.go | 103 ++++++++++++++++++++++--------- core/components/router/router.go | 64 ++++++++++++++++--- core/mocks/mocks.go | 16 +++++ core/protos/broker.proto | 3 +- 4 files changed, 148 insertions(+), 38 deletions(-) diff --git a/core/broker.pb.go b/core/broker.pb.go index e447502ad..0b274b1fb 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -160,7 +160,8 @@ func (m *JoinBrokerReq) GetMetadata() *Metadata { type JoinBrokerRes struct { Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + Metadata *Metadata `protobuf:"bytes,3,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinBrokerRes) Reset() { *m = JoinBrokerRes{} } @@ -525,8 +526,16 @@ func (m *JoinBrokerRes) MarshalTo(data []byte) (int, error) { } i += n6 } + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } if m.Metadata != nil { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) n7, err := m.Metadata.MarshalTo(data[i:]) @@ -662,6 +671,12 @@ func (m *JoinBrokerRes) Size() (n int) { l = m.Payload.Size() n += 1 + l + sovBroker(uint64(l)) } + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } if m.Metadata != nil { l = m.Metadata.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1375,6 +1390,37 @@ func (m *JoinBrokerRes) Unmarshal(data []byte) error { } iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1534,30 +1580,31 @@ var ( ) var fileDescriptorBroker = []byte{ - // 391 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x53, 0xcd, 0x4a, 0xf3, 0x40, - 0x14, 0xfd, 0xf2, 0x7d, 0x25, 0xc9, 0x77, 0x6d, 0xd5, 0x8e, 0x54, 0x43, 0x16, 0x45, 0xba, 0x12, - 0x85, 0x82, 0x15, 0xdc, 0xa7, 0xb4, 0xe0, 0x6f, 0x28, 0x29, 0xe2, 0x7a, 0x92, 0x19, 0x68, 0x69, - 0xcc, 0xc4, 0x99, 0x68, 0xa9, 0xe0, 0xda, 0x57, 0xf0, 0x6d, 0xdc, 0xba, 0xf4, 0x11, 0x44, 0x5f, - 0xc4, 0x64, 0xa6, 0x35, 0x26, 0x14, 0x04, 0xc1, 0xc5, 0xc0, 0xdc, 0x73, 0xcf, 0xed, 0x39, 0xbd, - 0x67, 0x02, 0x55, 0x9f, 0xb3, 0x09, 0xe5, 0xed, 0x98, 0xb3, 0x84, 0xa1, 0x4a, 0xc0, 0x38, 0xb5, - 0x6b, 0x21, 0xe3, 0x78, 0x8a, 0x23, 0x05, 0xda, 0x90, 0x81, 0xea, 0xde, 0x1a, 0x41, 0xad, 0x87, - 0x13, 0xdc, 0x95, 0x43, 0x1e, 0xbd, 0x46, 0x7b, 0x60, 0x0c, 0xf0, 0x2c, 0x64, 0x98, 0x58, 0xda, - 0xb6, 0xb6, 0xb3, 0xd2, 0xa9, 0xb7, 0x25, 0xfd, 0x8c, 0x79, 0xf8, 0xd2, 0x71, 0x33, 0xb2, 0x67, - 0xc4, 0x8a, 0x81, 0x76, 0xc1, 0x3c, 0xa7, 0x09, 0x26, 0x29, 0x68, 0xfd, 0x95, 0xec, 0x55, 0xc5, - 0x5e, 0xa0, 0x9e, 0x79, 0x35, 0xbf, 0x95, 0x95, 0xc4, 0xef, 0x29, 0xdd, 0xc3, 0x9a, 0xd3, 0x1d, - 0x0c, 0x6f, 0xfc, 0xfc, 0x5f, 0x35, 0x01, 0x8e, 0x70, 0x44, 0x42, 0xca, 0x5d, 0x9a, 0x48, 0xb9, - 0xff, 0x1e, 0x8c, 0x3e, 0x11, 0xb4, 0x09, 0xba, 0x13, 0xc7, 0xfd, 0x8b, 0x63, 0xf9, 0xe3, 0x55, - 0x4f, 0xc7, 0xb2, 0x42, 0x16, 0x18, 0x3d, 0x7a, 0xeb, 0x10, 0xc2, 0xad, 0x7f, 0xb2, 0x61, 0x10, - 0x55, 0x66, 0x1d, 0x77, 0x3a, 0x19, 0x9e, 0xd2, 0x99, 0x55, 0x51, 0x9d, 0x48, 0x95, 0xad, 0x7a, - 0x59, 0x5e, 0xb4, 0x1e, 0x34, 0xa8, 0x9d, 0xb0, 0x71, 0x94, 0x1b, 0xca, 0x05, 0xb5, 0x82, 0x60, - 0x8a, 0xa7, 0x82, 0x5f, 0x8c, 0x10, 0x59, 0x21, 0x1b, 0xcc, 0x14, 0x77, 0x59, 0x14, 0xd0, 0xb9, - 0x13, 0x93, 0xcc, 0xeb, 0xc2, 0x6e, 0x2a, 0xdf, 0xec, 0x26, 0x2a, 0x1a, 0x11, 0x68, 0xbf, 0x9c, - 0xc2, 0x56, 0x21, 0x85, 0x8c, 0xec, 0x04, 0x01, 0x8d, 0x93, 0x1f, 0x65, 0xd1, 0x79, 0xd2, 0x40, - 0x57, 0x62, 0xe8, 0x70, 0x91, 0x41, 0x96, 0x2c, 0xda, 0x50, 0x23, 0x85, 0xc7, 0x67, 0x2f, 0x01, - 0x45, 0x3e, 0x97, 0x79, 0x59, 0xcc, 0x15, 0xb6, 0x69, 0x2f, 0x01, 0x05, 0xea, 0x43, 0x23, 0x0d, - 0x41, 0x04, 0x7c, 0xec, 0xd3, 0x01, 0xe5, 0x82, 0x45, 0x38, 0x1c, 0xdf, 0x51, 0x82, 0x1a, 0x8a, - 0x5d, 0x7a, 0x23, 0xf6, 0x52, 0x58, 0x74, 0xd7, 0x9f, 0xdf, 0x9a, 0xda, 0x4b, 0x7a, 0x5e, 0xd3, - 0xf3, 0xf8, 0xde, 0xfc, 0xe3, 0xeb, 0xf2, 0xd3, 0x39, 0xf8, 0x08, 0x00, 0x00, 0xff, 0xff, 0x25, - 0x06, 0x5b, 0xfc, 0x6b, 0x03, 0x00, 0x00, + // 405 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x53, 0x5d, 0x4b, 0xe3, 0x40, + 0x14, 0xdd, 0xb4, 0x25, 0xc9, 0xde, 0x6d, 0x77, 0xb7, 0xb3, 0x74, 0x37, 0xe4, 0xa1, 0x2c, 0x7d, + 0x5a, 0x56, 0x28, 0x58, 0xc1, 0xf7, 0x94, 0x16, 0xfc, 0x0c, 0x25, 0x45, 0x7c, 0x9e, 0x64, 0x06, + 0x5a, 0x1a, 0x33, 0x71, 0x26, 0x5a, 0x2a, 0xf8, 0xac, 0x3f, 0xc1, 0x7f, 0xe3, 0xab, 0x8f, 0xfe, + 0x04, 0xd1, 0x3f, 0x62, 0x32, 0xd3, 0x9a, 0x26, 0x14, 0x7d, 0xf2, 0x61, 0x60, 0xee, 0xb9, 0xe7, + 0xf6, 0x9c, 0xde, 0x33, 0x81, 0xba, 0xcf, 0xd9, 0x8c, 0xf2, 0x6e, 0xcc, 0x59, 0xc2, 0x50, 0x2d, + 0x60, 0x9c, 0xda, 0x8d, 0x90, 0x71, 0x3c, 0xc7, 0x91, 0x02, 0x6d, 0xc8, 0x40, 0x75, 0xef, 0x4c, + 0xa0, 0x31, 0xc0, 0x09, 0xee, 0xcb, 0x21, 0x8f, 0x9e, 0xa3, 0x2d, 0x30, 0x46, 0x78, 0x11, 0x32, + 0x4c, 0x2c, 0xed, 0xaf, 0xf6, 0xef, 0x5b, 0xaf, 0xd9, 0x95, 0xf4, 0x23, 0xe6, 0xe1, 0x53, 0xc7, + 0xcd, 0xc8, 0x9e, 0x11, 0x2b, 0x06, 0xfa, 0x0f, 0xe6, 0x31, 0x4d, 0x30, 0x49, 0x41, 0xab, 0x22, + 0xd9, 0xdf, 0x15, 0x7b, 0x85, 0x7a, 0xe6, 0xd9, 0xf2, 0x56, 0x56, 0x12, 0x9f, 0xa7, 0x74, 0x0d, + 0x3f, 0x9c, 0xfe, 0x68, 0x7c, 0xe1, 0xe7, 0xff, 0xaa, 0x0d, 0xb0, 0x87, 0x23, 0x12, 0x52, 0xee, + 0xd2, 0x44, 0xca, 0x7d, 0xf5, 0x60, 0xf2, 0x86, 0xa0, 0xdf, 0xa0, 0x3b, 0x71, 0x3c, 0x3c, 0xd9, + 0x97, 0x3f, 0x5e, 0xf7, 0x74, 0x2c, 0x2b, 0x64, 0x81, 0x31, 0xa0, 0x97, 0x0e, 0x21, 0xdc, 0xaa, + 0xca, 0x86, 0x41, 0x54, 0x99, 0x75, 0xdc, 0xf9, 0x6c, 0x7c, 0x48, 0x17, 0x56, 0x4d, 0x75, 0x22, + 0x55, 0x76, 0x9a, 0x65, 0x79, 0xd1, 0xb9, 0xd1, 0xa0, 0x71, 0xc0, 0xa6, 0x51, 0x6e, 0x28, 0x17, + 0xd4, 0x0a, 0x82, 0x29, 0x9e, 0x0a, 0xae, 0x19, 0x21, 0xb2, 0x42, 0x36, 0x98, 0x29, 0xee, 0xb2, + 0x28, 0xa0, 0x4b, 0x27, 0x26, 0x59, 0xd6, 0x85, 0xdd, 0xd4, 0x3e, 0xd8, 0xcd, 0x6d, 0xc9, 0x89, + 0x40, 0xdb, 0xe5, 0x18, 0xfe, 0x14, 0x62, 0xc8, 0xc8, 0x4e, 0x10, 0xd0, 0x38, 0xc9, 0xc3, 0x58, + 0xdb, 0x4a, 0xa5, 0xb8, 0x95, 0x75, 0x2b, 0xd5, 0xf7, 0xad, 0xf4, 0xee, 0x35, 0xd0, 0x95, 0x0d, + 0xb4, 0xbb, 0x8a, 0x27, 0x0b, 0x1d, 0xfd, 0x52, 0x23, 0x85, 0x77, 0x69, 0x6f, 0x00, 0x45, 0x3e, + 0x97, 0xb9, 0x5c, 0xcd, 0x15, 0x16, 0x6d, 0x6f, 0x00, 0x05, 0x1a, 0x42, 0x2b, 0xcd, 0x47, 0x04, + 0x7c, 0xea, 0xd3, 0x11, 0xe5, 0x82, 0x45, 0x38, 0x9c, 0x5e, 0x51, 0x82, 0x5a, 0x8a, 0x5d, 0x7a, + 0x3e, 0xf6, 0x46, 0x58, 0xf4, 0x7f, 0x3e, 0x3c, 0xb7, 0xb5, 0xc7, 0xf4, 0x3c, 0xa5, 0xe7, 0xee, + 0xa5, 0xfd, 0xc5, 0xd7, 0xe5, 0x57, 0xb5, 0xf3, 0x1a, 0x00, 0x00, 0xff, 0xff, 0x19, 0xac, 0x93, + 0x41, 0x86, 0x03, 0x00, 0x00, } diff --git a/core/components/router/router.go b/core/components/router/router.go index ebca6d361..645bb80fc 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -81,6 +81,35 @@ func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.S return new(core.StatsRes), r.Storage.UpdateStats(req.GatewayID, *req.Metadata) } +// HandleJoin implements the core.RouterClient interface +func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (*core.JoinRouterRes, error) { + if len(req.GatewayID) != 8 || len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { + r.Ctx.Debug("Invalid request. Parameters are incorrects") + return new(core.JoinRouterRes), errors.New(errors.Structural, "Invalid Request") + } + + // Add Gateway location metadata + if gmeta, err := r.Storage.LookupStats(req.GatewayID); err == nil { + r.Ctx.Debug("Adding Gateway Metadata to packet") + req.Metadata.Latitude = gmeta.Latitude + req.Metadata.Longitude = gmeta.Longitude + req.Metadata.Altitude = gmeta.Altitude + } + + // Broadcast the join request + bpacket := &core.JoinBrokerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + response, err := r.send(bpacket, true, r.Brokers...) + if err != nil { + return new(core.JoinRouterRes), err + } + return response.(*core.JoinRouterRes), err +} + // HandleData implements the core.RouterClient interface func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*core.DataRouterRes, error) { // Get some logs / analytics @@ -139,7 +168,7 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} // Send packet to broker(s) - var response *core.DataBrokerRes + var response interface{} if shouldBroadcast { // No Recipient available -> broadcast stats.MarkMeter("router.broadcast") @@ -172,7 +201,7 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co return new(core.DataRouterRes), err } - return r.handleDataDown(response, req.GatewayID) + return r.handleDataDown(response.(*core.DataBrokerRes), req.GatewayID) } func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*core.DataRouterRes, error) { @@ -201,17 +230,17 @@ func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*c return &core.DataRouterRes{Payload: req.Payload, Metadata: req.Metadata}, nil } -func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...core.BrokerClient) (*core.DataBrokerRes, error) { +func (r component) send(req interface{}, isBroadcast bool, brokers ...core.BrokerClient) (interface{}, error) { // Define a more helpful context nb := len(brokers) - ctx := r.Ctx.WithField("devAddr", req.Payload.MACPayload.FHDR.DevAddr) - ctx.WithField("Nb Brokers", nb).Debug("Sending Packet") + ctx := r.Ctx.WithField("Nb Brokers", nb) + ctx.Debug("Sending Packet") stats.UpdateHistogram("router.send_recipients", int64(nb)) // Prepare ground for parrallel requests cherr := make(chan error, nb) chresp := make(chan struct { - Response *core.DataBrokerRes + Response interface{} BrokerIndex int }, nb) wg := sync.WaitGroup{} @@ -223,7 +252,17 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co defer wg.Done() // Send request - resp, err := broker.HandleData(context.Background(), req) + var resp interface{} + var err error + switch req.(type) { + case *core.DataBrokerReq: + resp, err = broker.HandleData(context.Background(), req.(*core.DataBrokerReq)) + case *core.JoinBrokerReq: + resp, err = broker.HandleJoin(context.Background(), req.(*core.JoinBrokerReq)) + default: + cherr <- errors.New(errors.Structural, "Unknown request type") + return + } // Handle error if err != nil { @@ -238,7 +277,7 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co // Transfer the response chresp <- struct { - Response *core.DataBrokerRes + Response interface{} BrokerIndex int }{resp, index} }(i, broker) @@ -286,7 +325,14 @@ func (r component) send(req *core.DataBrokerReq, isBroadcast bool, brokers ...co resp := <-chresp // Save the broker for later if it was a broadcast if isBroadcast { - if err := r.Storage.Store(req.Payload.MACPayload.FHDR.DevAddr, resp.BrokerIndex); err != nil { + var devAddr []byte + switch req.(type) { + case *core.DataBrokerReq: + devAddr = req.(*core.DataBrokerReq).Payload.MACPayload.FHDR.DevAddr + case *core.JoinBrokerReq: + devAddr = resp.Response.(*core.JoinBrokerRes).DevAddr + } + if err := r.Storage.Store(devAddr, resp.BrokerIndex); err != nil { r.Ctx.WithError(err).Warn("Failed to store accepted broker") } } diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index c79620e16..9f2657aad 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -120,6 +120,14 @@ type BrokerClient struct { OutSubscribePersonalized struct { Res *core.ABPSubBrokerRes } + InHandleJoin struct { + Ctx context.Context + Req *core.JoinBrokerReq + Opts []grpc.CallOption + } + OutHandleJoin struct { + Res *core.JoinBrokerRes + } } // NewBrokerClient creates a new mock BrokerClient @@ -137,6 +145,14 @@ func (m *BrokerClient) HandleData(ctx context.Context, in *core.DataBrokerReq, o return m.OutHandleData.Res, m.Failures["HandleData"] } +// HandleJoin implements the core.BrokerClient interface +func (m *BrokerClient) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts ...grpc.CallOption) (*core.JoinBrokerRes, error) { + m.InHandleJoin.Ctx = ctx + m.InHandleJoin.Req = in + m.InHandleJoin.Opts = opts + return m.OutHandleJoin.Res, m.Failures["HandleJoin"] +} + // SubscribePersonalized implements the core.BrokerClient interface func (m *BrokerClient) SubscribePersonalized(ctx context.Context, in *core.ABPSubBrokerReq, opts ...grpc.CallOption) (*core.ABPSubBrokerRes, error) { m.InSubscribePersonalized.Ctx = ctx diff --git a/core/protos/broker.proto b/core/protos/broker.proto index 05257f7cf..e753a3477 100644 --- a/core/protos/broker.proto +++ b/core/protos/broker.proto @@ -32,7 +32,8 @@ message JoinBrokerReq { message JoinBrokerRes { LoRaWANJoinAccept Payload = 1; - Metadata Metadata = 2; + bytes DevAddr = 2; + Metadata Metadata = 3; } service Broker { From 2f5f076f3d2068f5a1a05f85a722d623d094945e Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 20 Mar 2016 14:57:53 +0100 Subject: [PATCH 1104/2266] [feature/otaa] Add test (and fixes) for HandleJoin in router --- core/components/router/router.go | 116 +++--- core/components/router/router_test.go | 524 ++++++++++++++++++++++++++ 2 files changed, 592 insertions(+), 48 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index 645bb80fc..642313314 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -82,18 +82,16 @@ func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.S } // HandleJoin implements the core.RouterClient interface -func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (*core.JoinRouterRes, error) { +func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (routerRes *core.JoinRouterRes, err error) { if len(req.GatewayID) != 8 || len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { r.Ctx.Debug("Invalid request. Parameters are incorrects") return new(core.JoinRouterRes), errors.New(errors.Structural, "Invalid Request") } - // Add Gateway location metadata - if gmeta, err := r.Storage.LookupStats(req.GatewayID); err == nil { - r.Ctx.Debug("Adding Gateway Metadata to packet") - req.Metadata.Latitude = gmeta.Latitude - req.Metadata.Longitude = gmeta.Longitude - req.Metadata.Altitude = gmeta.Altitude + // Update Metadata with Gateway infos + req.Metadata, err = r.injectMetadata(req.GatewayID, *req.Metadata) + if err != nil { + return new(core.JoinRouterRes), err } // Broadcast the join request @@ -107,7 +105,13 @@ func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (*co if err != nil { return new(core.JoinRouterRes), err } - return response.(*core.JoinRouterRes), err + + // Update Gateway Duty cycle with response metadata + res := response.(*core.JoinBrokerRes) + if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { + return new(core.JoinRouterRes), err + } + return &core.JoinRouterRes{Payload: res.Payload, Metadata: res.Metadata}, nil } // HandleData implements the core.RouterClient interface @@ -131,6 +135,12 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co return new(core.DataRouterRes), errors.New(errors.Structural, "Invalid gatewayID") } + // Update Metadata with Gateway infos + req.Metadata, err = r.injectMetadata(req.GatewayID, *req.Metadata) + if err != nil { + return new(core.DataRouterRes), err + } + // Lookup for an existing broker entries, err := r.Storage.Lookup(fhdr.DevAddr) if err != nil && err.(errors.Failure).Nature != errors.NotFound { @@ -140,31 +150,6 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co shouldBroadcast := err != nil r.Ctx.WithField("Should Broadcast?", shouldBroadcast).Debug("Storage Lookup done") - // Add Gateway location metadata - if gmeta, err := r.Storage.LookupStats(req.GatewayID); err == nil { - r.Ctx.Debug("Adding Gateway Metadata to packet") - req.Metadata.Latitude = gmeta.Latitude - req.Metadata.Longitude = gmeta.Longitude - req.Metadata.Altitude = gmeta.Altitude - } - - // Add Gateway duty metadata - cycles, err := r.DutyManager.Lookup(req.GatewayID) - if err != nil { - r.Ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") - cycles = make(dutycycle.Cycles) - } - - sb1, err := dutycycle.GetSubBand(float32(req.Metadata.Frequency)) - if err != nil { - stats.MarkMeter("router.uplink.not_supported") - return new(core.DataRouterRes), errors.New(errors.Structural, "Unhandled uplink signal frequency") - } - - rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) - req.Metadata.DutyRX1, req.Metadata.DutyRX2 = uint32(rx1), uint32(rx2) - r.Ctx.WithField("DutyRX1", rx1).WithField("DutyRX2", rx2).Debug("Adding Duty values for RX1 & RX2") - bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} // Send packet to broker(s) @@ -201,33 +186,68 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co return new(core.DataRouterRes), err } - return r.handleDataDown(response.(*core.DataBrokerRes), req.GatewayID) -} - -func (r component) handleDataDown(req *core.DataBrokerRes, gatewayID []byte) (*core.DataRouterRes, error) { - if req == nil || req.Payload == nil { // No response + res := response.(*core.DataBrokerRes) + if res == nil || res.Payload == nil { // No response r.Ctx.Debug("Packet sent. No downlink received.") return new(core.DataRouterRes), nil } + // Update Gateway Duty cycle with response metadata + if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { + return new(core.DataRouterRes), err + } + + // Send Back the response + return &core.DataRouterRes{Payload: res.Payload, Metadata: res.Metadata}, nil +} + +func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Metadata, error) { + // Add Gateway location metadata + if gmeta, err := r.Storage.LookupStats(gid); err == nil { + r.Ctx.Debug("Adding Gateway Metadata to packet") + metadata.Latitude = gmeta.Latitude + metadata.Longitude = gmeta.Longitude + metadata.Altitude = gmeta.Altitude + } + + // Add Gateway duty metadata + cycles, err := r.DutyManager.Lookup(gid) + if err != nil { + r.Ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") + cycles = make(dutycycle.Cycles) + } + + sb1, err := dutycycle.GetSubBand(float32(metadata.Frequency)) + if err != nil { + stats.MarkMeter("router.uplink.not_supported") + return nil, errors.New(errors.Structural, "Unhandled uplink signal frequency") + } + + rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) + metadata.DutyRX1, metadata.DutyRX2 = uint32(rx1), uint32(rx2) + r.Ctx.WithField("DutyRX1", rx1).WithField("DutyRX2", rx2).Debug("Adding Duty values for RX1 & RX2") + return &metadata, nil + +} + +func (r component) handleDown(gatewayID []byte, metadata *core.Metadata) error { r.Ctx.Debug("Handling downlink response") + // Update downlink metadata for the related gateway - if req.Metadata == nil { + if metadata == nil { stats.MarkMeter("router.uplink.bad_broker_response") r.Ctx.Warn("Missing mandatory Metadata in response") - return new(core.DataRouterRes), errors.New(errors.Structural, "Missing mandatory Metadata in response") + return errors.New(errors.Structural, "Missing mandatory Metadata in response") } - freq := req.Metadata.Frequency - datr := req.Metadata.DataRate - codr := req.Metadata.CodingRate - size := req.Metadata.PayloadSize + freq := metadata.Frequency + datr := metadata.DataRate + codr := metadata.CodingRate + size := metadata.PayloadSize if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { r.Ctx.WithError(err).Debug("Unable to update DutyManager") - return new(core.DataRouterRes), errors.New(errors.Operational, err) + return errors.New(errors.Operational, err) } - - // Send Back the response - return &core.DataRouterRes{Payload: req.Payload, Metadata: req.Metadata}, nil + return nil } func (r component) send(req interface{}, isBroadcast bool, brokers ...core.BrokerClient) (interface{}, error) { diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index c359f2b10..6afac4bf6 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -1345,6 +1345,530 @@ func TestHandleData(t *testing.T) { Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } +} + +func TestHandleJoin(t *testing.T) { + { + Desc(t, "Handle valid join request | valid join response") + + // Build + dm := mocks.NewDutyManager() + br1 := mocks.NewBrokerClient() + br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") + br2 := mocks.NewBrokerClient() + br2.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: &core.Metadata{}, + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br1, br2}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr *string + var wantRes = &core.JoinRouterRes{ + Payload: br2.OutHandleJoin.Res.Payload, + Metadata: br2.OutHandleJoin.Res.Metadata, + } + var wantBrReq = &core.JoinBrokerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 1 + var wantUpdateGtw = req.GatewayID + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br1.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantBrReq, br2.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle valid join request | invalid join response -> fails to handle down") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br1 := mocks.NewBrokerClient() + br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") + br2 := mocks.NewBrokerClient() + br2.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: &core.Metadata{}, + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br1, br2}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr = ErrOperational + var wantRes = new(core.JoinRouterRes) + var wantBrReq = &core.JoinBrokerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore = 1 + var wantUpdateGtw = req.GatewayID + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br1.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantBrReq, br2.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle invalid join -> No Metadata") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br := mocks.NewBrokerClient() + br.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: nil, + } + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinRouterRes) + var wantBrReq *core.JoinBrokerReq + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle invalid Join Request -> Invalid DevEUI") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br := mocks.NewBrokerClient() + br.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinRouterRes) + var wantBrReq *core.JoinBrokerReq + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle invalid Join Request -> Invalid AppEUI") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br := mocks.NewBrokerClient() + br.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinRouterRes) + var wantBrReq *core.JoinBrokerReq + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle invalid Join Request -> Invalid DevNonce") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br := mocks.NewBrokerClient() + br.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinRouterRes) + var wantBrReq *core.JoinBrokerReq + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle invalid Join Request -> Invalid GatewayID") + + // Build + dm := mocks.NewDutyManager() + dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") + br := mocks.NewBrokerClient() + br.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: new(core.Metadata), + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: nil, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinRouterRes) + var wantBrReq *core.JoinBrokerReq + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle valid join request | fails to send, no broker") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + br.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 868.5, + }, + } + + // Expect + var wantErr = ErrNotFound + var wantRes = new(core.JoinRouterRes) + var wantBrReq = &core.JoinBrokerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: &core.Metadata{ + Altitude: st.OutLookupStats.Metadata.Altitude, + Longitude: st.OutLookupStats.Metadata.Longitude, + Latitude: st.OutLookupStats.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + }, + } + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } + + // -------------------- + + { + Desc(t, "Handle invalid join request -> bad frequency") + + // Build + dm := mocks.NewDutyManager() + br := mocks.NewBrokerClient() + br.OutHandleJoin.Res = &core.JoinBrokerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{1, 2, 3, 4}, + }, + Metadata: &core.Metadata{}, + } + st := NewMockStorage() + st.OutLookupStats.Metadata = core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + } + r := New(Components{ + DutyManager: dm, + Brokers: []core.BrokerClient{br}, + Ctx: GetLogger(t, "Router"), + Storage: st, + }, Options{}) + req := &core.JoinRouterReq{ + GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{3, 3}, + Metadata: &core.Metadata{ + Frequency: 14.42, + }, + } + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinRouterRes) + var wantBrReq *core.JoinBrokerReq + var wantStore int + var wantUpdateGtw []byte + + // Operate + res, err := r.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Router Join Responses") + Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") + Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") + } } From 3e2752fae64c2d6fae097a6595e6b26f11bd4494 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 20 Mar 2016 19:53:17 +0100 Subject: [PATCH 1105/2266] [feature/otaa] Handle join requests in broker --- core/components/broker/broker.go | 84 ++++++++++++++++++++++++++++ core/components/broker/controller.go | 69 ++++++++++++++++++++++- 2 files changed, 151 insertions(+), 2 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 70f864b5a..7419566c8 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -78,6 +78,90 @@ func (b component) Start() error { return nil } +// HandleJoin implements the core.BrokerServer interface +func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*core.JoinBrokerRes, error) { + b.Ctx.Debug("Handling join request") + + // Validate incoming data + if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 { + b.Ctx.Debug("Invalid request. Parameters are incorrect.") + return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid parameters") + } + ctx := b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI) + + // Check if devNonce already referenced + activation, err := b.NetworkController.ReadActivation(req.AppEUI, req.DevEUI) + if err != nil { + ctx.WithError(err).Debug("Unable to lookup activation") + return new(core.JoinBrokerRes), err + } + var found bool + for _, n := range activation.DevNonces { + if n[0] == req.DevNonce[0] && n[1] == req.DevNonce[1] { + found = true + break + } + } + if found { + ctx.Debug("Unable to proceed join request. DevNonce already used by the past.") + return new(core.JoinBrokerRes), errors.New(errors.Structural, "DevNonce used by the past") + } + + // Forward the registration to the handler + handler, closer, err := activation.Dialer.Dial() + if err != nil { + ctx.WithError(err).Debug("Unable to dial handler") + return new(core.JoinBrokerRes), err + } + defer closer.Close() + res, err := handler.HandleJoin(context.Background(), &core.JoinHandlerReq{ + DevEUI: req.DevEUI, + AppEUI: req.AppEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + }) + if err != nil { + ctx.WithError(err).Debug("Error while contacting handler") + return new(core.JoinBrokerRes), errors.New(errors.Operational, err) + } + + // Handle the response + if res == nil || res.Payload == nil || len(res.DevAddr) != 4 || len(res.NwkSKey) != 16 { + ctx.Debug("Invalid response from handler") + return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid response from handler") + } + + // Update the DevNonce + err = b.NetworkController.UpdateActivation(appEntry{ + Dialer: activation.Dialer, + AppEUI: activation.AppEUI, + DevEUI: activation.DevEUI, + DevNonces: append(activation.DevNonces, req.DevNonce), + }) + if err != nil { + ctx.WithError(err).Debug("Unable to update activation entry") + return new(core.JoinBrokerRes), err + } + + var nwkSKey [16]byte + copy(nwkSKey[:], res.NwkSKey) + err = b.NetworkController.StoreDevice(res.DevAddr, devEntry{ // Should be an update + Dialer: activation.Dialer, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + NwkSKey: nwkSKey, + FCntUp: 0, + }) + if err != nil { + ctx.WithError(err).Debug("Unable to store device") + return new(core.JoinBrokerRes), err + } + return &core.JoinBrokerRes{ + Payload: res.Payload, + Metadata: res.Metadata, + }, nil +} + // HandleData implements the core.BrokerServer interface func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*core.DataBrokerRes, error) { // Get some logs / analytics diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index b03c411d5..6dfd2a64e 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -6,6 +6,7 @@ package broker import ( "bytes" "encoding/binary" + "fmt" "math" "reflect" "sync" @@ -19,6 +20,8 @@ type NetworkController interface { LookupDevices(devAddr []byte) ([]devEntry, error) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) StoreDevice(devAddr []byte, entry devEntry) error + ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) + UpdateActivation(entry appEntry) error UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error Close() error } @@ -31,6 +34,13 @@ type devEntry struct { FCntUp uint32 } +type appEntry struct { + Dialer Dialer + AppEUI []byte + DevEUI []byte + DevNonces [][]byte +} + type controller struct { sync.RWMutex db dbutil.Interface @@ -59,6 +69,29 @@ func (s *controller) LookupDevices(devAddr []byte) ([]devEntry, error) { return entries.([]devEntry), nil } +// ReadActivation implements the broker.NetworkController interface +func (s *controller) ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) { + s.RLock() + defer s.RUnlock() + itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte{}, &appEntry{}) + if err != nil { + return appEntry{}, err + } + entries := itf.([]appEntry) + if len(entries) != 1 { + // NOTE should clean up the entry ? + return appEntry{}, errors.New(errors.Structural, "Invalid stored entry") + } + return entries[0], nil +} + +// UpdateAction implements the broker.NetworkController interface +func (s *controller) UpdateActivation(entry appEntry) error { + s.Lock() + defer s.Unlock() + return s.db.Replace(fmt.Sprintf("%x.%x", entry.AppEUI, entry.DevEUI), []byte{}, []dbutil.Entry{&entry}) +} + // WholeCounter implements the broker.NetworkController interface func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { upperSup := int(math.Pow(2, 16)) @@ -132,8 +165,40 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { e.DevEUI = make([]byte, 8, 8) binary.Read(buf, binary.BigEndian, &e.DevEUI) binary.Read(buf, binary.BigEndian, &e.NwkSKey) // fixed-length array - if err := binary.Read(buf, binary.BigEndian, &e.FCntUp); err != nil { - return errors.New(errors.Structural, err) + binary.Read(buf, binary.BigEndian, &e.FCntUp) + e.Dialer = NewDialer(buf.Next(buf.Len())) + return nil +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e appEntry) MarshalBinary() ([]byte, error) { + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, e.AppEUI) + binary.Write(buf, binary.BigEndian, e.DevEUI) + binary.Write(buf, binary.BigEndian, uint16(len(e.DevNonces))) + for _, n := range e.DevNonces { + binary.Write(buf, binary.BigEndian, n) + } + if len(buf.Bytes()) != 8+2+2*len(e.DevNonces) { + return nil, errors.New(errors.Structural, "App entry was invalid. Cannot Marshal") + } + binary.Write(buf, binary.BigEndian, e.Dialer.MarshalSafely()) + return buf.Bytes(), nil +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *appEntry) UnmarshalBinary(data []byte) error { + buf := bytes.NewBuffer(data) + e.AppEUI = make([]byte, 8, 8) + binary.Read(buf, binary.BigEndian, &e.AppEUI) + e.DevEUI = make([]byte, 8, 8) + binary.Read(buf, binary.BigEndian, &e.DevEUI) + var n uint16 + binary.Read(buf, binary.BigEndian, &n) + for i := 0; i < int(n); i++ { + devNonce := make([]byte, 2, 2) + binary.Read(buf, binary.BigEndian, &devNonce) + e.DevNonces = append(e.DevNonces, devNonce) } e.Dialer = NewDialer(buf.Next(buf.Len())) return nil From b326dc181b0026ea0941683ef95fe5c31b0da666 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 20 Mar 2016 19:58:21 +0100 Subject: [PATCH 1106/2266] [feature/otaa] Update mocks to be compatible with new structure --- core/components/broker/mocks_test.go | 23 +++++++++++++++++++++++ core/mocks/mocks.go | 18 +++++++++++++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go index 45970bfcc..52b9c4cec 100644 --- a/core/components/broker/mocks_test.go +++ b/core/components/broker/mocks_test.go @@ -93,6 +93,16 @@ type MockNetworkController struct { DevAddr []byte FCnt uint32 } + InReadActivation struct { + AppEUI []byte + DevEUI []byte + } + OutReadActivation struct { + Entry appEntry + } + InUpdateActivation struct { + Entry appEntry + } InClose struct { Called bool } @@ -134,6 +144,19 @@ func (m *MockNetworkController) UpdateFCnt(appEUI []byte, devEUI []byte, devAddr return m.Failures["UpdateFCnt"] } +// ReadActivation implements the NetworkController interface +func (m *MockNetworkController) ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) { + m.InReadActivation.AppEUI = appEUI + m.InReadActivation.DevEUI = devEUI + return m.OutReadActivation.Entry, m.Failures["ReadActivation"] +} + +// UpdateActivation implements the NetworkController interface +func (m *MockNetworkController) UpdateActivation(entry appEntry) error { + m.InUpdateActivation.Entry = entry + return m.Failures["UpdateActivation"] +} + // Close implements the NetworkController interface func (m *MockNetworkController) Close() error { m.InClose.Called = true diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 9f2657aad..21e3f110c 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -43,7 +43,15 @@ func (m *AppClient) HandleData(ctx context.Context, in *core.DataAppReq, opts .. // HandlerClient mocks the core.HandlerClient interface type HandlerClient struct { - Failures map[string]error + Failures map[string]error + InHandleJoin struct { + Ctx context.Context + Req *core.JoinHandlerReq + Opts []grpc.CallOption + } + OutHandleJoin struct { + Res *core.JoinHandlerRes + } InHandleDataUp struct { Ctx context.Context Req *core.DataUpHandlerReq @@ -77,6 +85,14 @@ func NewHandlerClient() *HandlerClient { } } +// HandleJoin implements the core.HandlerClient interface +func (m *HandlerClient) HandleJoin(ctx context.Context, in *core.JoinHandlerReq, opts ...grpc.CallOption) (*core.JoinHandlerRes, error) { + m.InHandleJoin.Ctx = ctx + m.InHandleJoin.Req = in + m.InHandleJoin.Opts = opts + return m.OutHandleJoin.Res, m.Failures["HandleJoin"] +} + // HandleDataUp implements the core.HandlerClient interface func (m *HandlerClient) HandleDataUp(ctx context.Context, in *core.DataUpHandlerReq, opts ...grpc.CallOption) (*core.DataUpHandlerRes, error) { m.InHandleDataUp.Ctx = ctx From 1a0d3c0f6309c88dd0d18fca90b150c93767b2f6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 20 Mar 2016 23:11:14 +0100 Subject: [PATCH 1107/2266] [feature/otaa] Add tests for HandleJoin and controller in broker --- core/components/broker/broker.go | 4 +- core/components/broker/broker_test.go | 688 ++++++++++++++++++++++ core/components/broker/controller.go | 6 +- core/components/broker/controller_test.go | 49 ++ 4 files changed, 742 insertions(+), 5 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 7419566c8..de2b99281 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -83,7 +83,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c b.Ctx.Debug("Handling join request") // Validate incoming data - if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 { + if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { b.Ctx.Debug("Invalid request. Parameters are incorrect.") return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid parameters") } @@ -128,7 +128,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c // Handle the response if res == nil || res.Payload == nil || len(res.DevAddr) != 4 || len(res.NwkSKey) != 16 { ctx.Debug("Invalid response from handler") - return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid response from handler") + return new(core.JoinBrokerRes), errors.New(errors.Operational, "Invalid response from handler") } // Update the DevNonce diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 7acaf1502..4df8a00a4 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -1018,6 +1018,694 @@ func TestDialerCloser(t *testing.T) { } } +func TestHandleJoin(t *testing.T) { + { + Desc(t, "Valid Join Request | Valid Join Accept") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr *string + var wantJoinReq = &core.JoinHandlerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + var wantRes = &core.JoinBrokerRes{ + Payload: hl.OutHandleJoin.Res.Payload, + Metadata: hl.OutHandleJoin.Res.Metadata, + } + var wantActivation = appEntry{ + Dialer: dl, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonces: [][]byte{req.DevNonce}, + } + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Invalid Join Request -> Invalid AppEUI") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nil, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrStructural + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer bool + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Invalid Join Request -> Invalid DevEUI") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: []byte{1, 2, 3}, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrStructural + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer bool + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Invalid Join Request -> Invalid DevNonce") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14, 15, 16}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrStructural + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer bool + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Invalid Join Request -> Invalid Metadata") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: nil, + } + + // Expect + var wantErr = ErrStructural + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer bool + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request | ReadActivation failed") + + // Build + nc := NewMockNetworkController() + nc.Failures["ReadActivation"] = errors.New(errors.Operational, "Mock Error") + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer bool + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request | DevNonce already exists") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{[]byte{14, 14}}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrStructural + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer bool + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request | Dial fails") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantJoinReq *core.JoinHandlerReq + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + // -------------------- + + { + Desc(t, "Valid Join Request | Handle join fails") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + hl.Failures["HandleJoin"] = fmt.Errorf("Mock Error") + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantJoinReq = &core.JoinHandlerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request | Invalid response from handler") + + // Build + nc := NewMockNetworkController() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: nil, + NwkSKey: nil, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantJoinReq = &core.JoinHandlerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + var wantRes = new(core.JoinBrokerRes) + var wantActivation appEntry + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request | Update Activation fails") + + // Build + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc := NewMockNetworkController() + nc.Failures["UpdateActivation"] = errors.New(errors.Operational, "Mock Error") + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantJoinReq = &core.JoinHandlerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + var wantRes = new(core.JoinBrokerRes) + var wantActivation = appEntry{ + Dialer: dl, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonces: [][]byte{req.DevNonce}, + } + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request | Store device fails") + + // Build + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc := NewMockNetworkController() + nc.Failures["StoreDevice"] = errors.New(errors.Operational, "Mock Error") + nc.OutReadActivation.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonces: [][]byte{}, + } + + br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: nc.OutReadActivation.Entry.AppEUI, + DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr = ErrOperational + var wantJoinReq = &core.JoinHandlerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + var wantRes = new(core.JoinBrokerRes) + var wantActivation = appEntry{ + Dialer: dl, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonces: [][]byte{req.DevNonce}, + } + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } +} + func TestStart(t *testing.T) { broker := New( Components{ diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 6dfd2a64e..50d6cb57b 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -73,7 +73,7 @@ func (s *controller) LookupDevices(devAddr []byte) ([]devEntry, error) { func (s *controller) ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) { s.RLock() defer s.RUnlock() - itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte{}, &appEntry{}) + itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte("entry"), &appEntry{}) if err != nil { return appEntry{}, err } @@ -89,7 +89,7 @@ func (s *controller) ReadActivation(appEUI []byte, devEUI []byte) (appEntry, err func (s *controller) UpdateActivation(entry appEntry) error { s.Lock() defer s.Unlock() - return s.db.Replace(fmt.Sprintf("%x.%x", entry.AppEUI, entry.DevEUI), []byte{}, []dbutil.Entry{&entry}) + return s.db.Replace(fmt.Sprintf("%x.%x", entry.AppEUI, entry.DevEUI), []byte("entry"), []dbutil.Entry{&entry}) } // WholeCounter implements the broker.NetworkController interface @@ -179,7 +179,7 @@ func (e appEntry) MarshalBinary() ([]byte, error) { for _, n := range e.DevNonces { binary.Write(buf, binary.BigEndian, n) } - if len(buf.Bytes()) != 8+2+2*len(e.DevNonces) { + if len(buf.Bytes()) != 16+2+2*len(e.DevNonces) { return nil, errors.New(errors.Structural, "App entry was invalid. Cannot Marshal") } binary.Write(buf, binary.BigEndian, e.Dialer.MarshalSafely()) diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index 78215785d..34f8b71e5 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -355,4 +355,53 @@ func TestNetworkControllerDevice(t *testing.T) { _ = db.Close() } + + // ------------------- + + { + Desc(t, "Test update then read an activation") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + entry := appEntry{ + Dialer: NewDialer([]byte("Dialer")), + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonces: [][]byte{{1, 2}, {3, 4}}, + } + + // Operate + err := db.UpdateActivation(entry) + FatalUnless(t, err) + got, err := db.ReadActivation(entry.AppEUI, entry.DevEUI) + + // Chek + CheckErrors(t, nil, err) + Check(t, entry, got, "Entries") + + _ = db.Close() + + } + + // ------------------- + + { + Desc(t, "Test ReadActivation -> not found") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 2} + devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 3} + var entry appEntry + + // Operate + got, err := db.ReadActivation(appEUI, devEUI) + + // Chek + CheckErrors(t, ErrNotFound, err) + Check(t, entry, got, "Entries") + + _ = db.Close() + + } } From b037cc791b6e110bba41a6ca04476e2c860644f5 Mon Sep 17 00:00:00 2001 From: ktorz Date: Sun, 20 Mar 2016 23:46:41 +0100 Subject: [PATCH 1108/2266] [feature/otaa] Separate consume down packet from consume bundle --- core/components/handler/handler.go | 204 ++++++++++++++++------------- 1 file changed, 114 insertions(+), 90 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index ba99e50a8..c76f07017 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -52,11 +52,12 @@ type Options struct { // bundle are used to materialize an incoming request being bufferized, waiting for the others. type bundle struct { - Chresp chan interface{} - Entry devEntry - ID [20]byte - Packet core.DataUpHandlerReq - Time time.Time + Chresp chan interface{} + Entry devEntry + ID [20]byte + Packet interface{} + DataRate string + Time time.Time } // New construct a new Handler @@ -144,6 +145,12 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH return new(core.ABPSubHandlerRes), nil } +// HandleJoin implements the core.HandlerServer interface +func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (*core.JoinHandlerRes, error) { + // TODO + return nil, nil +} + // HandleDataDown implements the core.HandlerServer interface func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { stats.MarkMeter("handler.downlink.in") @@ -219,11 +226,12 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq ctx := h.Ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") h.Set <- bundle{ - ID: bundleID, - Packet: *req, - Entry: entry, - Chresp: chresp, - Time: time.Now(), + ID: bundleID, + Packet: req, + DataRate: req.Metadata.DataRate, + Entry: entry, + Chresp: chresp, + Time: time.Now(), } // 5. Wait for the response. Could be an error, a packet or nothing. @@ -317,99 +325,115 @@ func (h component) consumeBundles(chbundle <-chan []bundle) { browseBundles: for bundles := range chbundle { ctx.WithField("BundleID", bundles[0].ID).Debug("Consume new bundle") - var metadata []*core.Metadata - var payload []byte - var firstTime time.Time - if len(bundles) < 1 { continue browseBundles } b := bundles[0] - - computer, scores, err := dutycycle.NewScoreComputer(b.Packet.Metadata.DataRate) // Nil check already done - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles + switch b.Packet.(type) { + case *core.DataUpHandlerReq: + pkt := b.Packet.(*core.DataUpHandlerReq) + go h.consumeDown(pkt.AppEUI, pkt.DevEUI, b.DataRate, bundles) + case *core.JoinHandlerReq: + pkt := b.Packet.(*core.JoinHandlerReq) + go h.consumeJoin(pkt.AppEUI, pkt.DevEUI, b.DataRate, bundles) } + } +} - stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) - - for i, bundle := range bundles { - // We only decrypt the payload of the first bundle's packet. - // We assume all the other to be equal and we'll merely collect - // metadata from other bundle. - if i == 0 { - var err error - payload, err = lorawan.EncryptFRMPayload( - bundle.Entry.AppSKey, - true, - lorawan.DevAddr(bundle.Entry.DevAddr), - bundle.Packet.FCnt, - bundle.Packet.Payload, - ) - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } - firstTime = bundle.Time - stats.MarkMeter("handler.uplink.in.unique") - } else { - diff := bundle.Time.Sub(firstTime).Nanoseconds() - stats.UpdateHistogram("handler.uplink.duplicate.delay", diff/1000) - } +// consume Join actually consumes a set of join-request packets +func (h component) consumeJoin(appEUI []byte, devEUI []byte, dataRate string, bundles []bundle) { + // TODO +} - // Append metadata for each of them - metadata = append(metadata, bundle.Packet.Metadata) - scores = computer.Update(scores, i, *bundle.Packet.Metadata) // Nil check already done - } +// consume Down actually consumes a set of downlink packets +func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bundles []bundle) { + stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) + var metadata []*core.Metadata + var payload []byte + var firstTime time.Time - // Then create an application-level packet and send it to the wild open - // we don't expect a response from the adapter, end of the chain. - _, err = h.AppAdapter.HandleData(context.Background(), &core.DataAppReq{ - AppEUI: b.Packet.AppEUI, - DevEUI: b.Packet.DevEUI, - Payload: payload, - Metadata: metadata, - }) - if err != nil { - go h.abortConsume(errors.New(errors.Operational, err), bundles) - continue browseBundles + computer, scores, err := dutycycle.NewScoreComputer(dataRate) + if err != nil { + go h.abortConsume(err, bundles) + return + } + + for i, bundle := range bundles { + // We only decrypt the payload of the first bundle's packet. + // We assume all the other to be equal and we'll merely collect + // metadata from other bundle. + packet := bundle.Packet.(*core.DataUpHandlerReq) + if i == 0 { + var err error + payload, err = lorawan.EncryptFRMPayload( + bundle.Entry.AppSKey, + true, + lorawan.DevAddr(bundle.Entry.DevAddr), + packet.FCnt, + packet.Payload, + ) + if err != nil { + go h.abortConsume(err, bundles) + return + } + firstTime = bundle.Time + stats.MarkMeter("handler.uplink.in.unique") + } else { + diff := bundle.Time.Sub(firstTime).Nanoseconds() + stats.UpdateHistogram("handler.uplink.duplicate.delay", diff/1000) } - stats.MarkMeter("handler.uplink.out") + // Append metadata for each of them + metadata = append(metadata, packet.Metadata) + scores = computer.Update(scores, i, *packet.Metadata) // Nil check already done + } - // Now handle the downlink and respond to node - h.Ctx.Debug("Looking for downlink response") - best := computer.Get(scores) - h.Ctx.WithField("Bundle", best).Debug("Determine best gateway") - var downlink pktEntry - if best != nil { // Avoid pulling when there's no gateway available for an answer - downlink, err = h.PktStorage.Pull(b.Packet.AppEUI, b.Packet.DevEUI) - } - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - go h.abortConsume(err, bundles) - continue browseBundles - } + // Then create an application-level packet and send it to the wild open + // we don't expect a response from the adapter, end of the chain. + _, err = h.AppAdapter.HandleData(context.Background(), &core.DataAppReq{ + AppEUI: appEUI, + DevEUI: devEUI, + Payload: payload, + Metadata: metadata, + }) + if err != nil { + go h.abortConsume(errors.New(errors.Operational, err), bundles) + return + } + + stats.MarkMeter("handler.uplink.out") - // One of those bundle might be available for a response - for i, bundle := range bundles { - if best != nil && best.ID == i && downlink.Payload != nil && err == nil { - stats.MarkMeter("handler.downlink.pull") - - downlink, err := h.buildDownlink(downlink.Payload, bundle.Packet, bundle.Entry, best.IsRX2) - if err != nil { - go h.abortConsume(errors.New(errors.Structural, err), bundles) - continue browseBundles - } - err = h.DevStorage.UpdateFCnt(b.Packet.AppEUI, b.Packet.DevEUI, downlink.Payload.MACPayload.FHDR.FCnt) - if err != nil { - go h.abortConsume(err, bundles) - continue browseBundles - } - bundle.Chresp <- downlink - } else { - bundle.Chresp <- nil + // Now handle the downlink and respond to node + h.Ctx.Debug("Looking for downlink response") + best := computer.Get(scores) + h.Ctx.WithField("Bundle", best).Debug("Determine best gateway") + var downlink pktEntry + if best != nil { // Avoid pulling when there's no gateway available for an answer + downlink, err = h.PktStorage.Pull(appEUI, devEUI) + } + if err != nil && err.(errors.Failure).Nature != errors.NotFound { + go h.abortConsume(err, bundles) + return + } + + // One of those bundle might be available for a response + for i, bundle := range bundles { + if best != nil && best.ID == i && downlink.Payload != nil && err == nil { + stats.MarkMeter("handler.downlink.pull") + + downlink, err := h.buildDownlink(downlink.Payload, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) + if err != nil { + go h.abortConsume(errors.New(errors.Structural, err), bundles) + return + } + err = h.DevStorage.UpdateFCnt(appEUI, devEUI, downlink.Payload.MACPayload.FHDR.FCnt) + if err != nil { + go h.abortConsume(err, bundles) + return } + bundle.Chresp <- downlink + } else { + bundle.Chresp <- nil } } } From 998c7ae7eb4b4c22fda5996dd1eb95ab6078e6b4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 10:59:43 +0100 Subject: [PATCH 1109/2266] [feature/otaa] Fix mocks for handler as well --- core/components/handler/mocks_test.go | 4 ++-- core/mocks/mocks.go | 16 ++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 76dadb6f2..80dd26b78 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -22,7 +22,7 @@ type MockDevStorage struct { } InStorePersonalized struct { AppEUI []byte - DevAddr [4]byte + DevAddr []byte AppSKey [16]byte NwkSKey [16]byte } @@ -54,7 +54,7 @@ func (m *MockDevStorage) Lookup(appEUI []byte, devEUI []byte) (devEntry, error) } // StorePersonalized implements the DevStorage interface -func (m *MockDevStorage) StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error { +func (m *MockDevStorage) StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error { m.InStorePersonalized.AppEUI = appEUI m.InStorePersonalized.DevAddr = devAddr m.InStorePersonalized.AppSKey = appSKey diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 21e3f110c..8cfa4baf2 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -24,6 +24,14 @@ type AppClient struct { OutHandleData struct { Res *core.DataAppRes } + InHandleJoin struct { + Ctx context.Context + Req *core.JoinAppReq + Opts []grpc.CallOption + } + OutHandleJoin struct { + Res *core.JoinAppRes + } } // NewAppClient creates a new mock AppClient @@ -33,6 +41,14 @@ func NewAppClient() *AppClient { } } +// HandleJoin implements the core.AppClient interface +func (m *AppClient) HandleJoin(ctx context.Context, in *core.JoinAppReq, opts ...grpc.CallOption) (*core.JoinAppRes, error) { + m.InHandleJoin.Ctx = ctx + m.InHandleJoin.Req = in + m.InHandleJoin.Opts = opts + return m.OutHandleJoin.Res, m.Failures["HandleJoin"] +} + // HandleData implements the core.AppClient interface func (m *AppClient) HandleData(ctx context.Context, in *core.DataAppReq, opts ...grpc.CallOption) (*core.DataAppRes, error) { m.InHandleData.Ctx = ctx From 86690e8fdcc29020b5878601859e801b04e4b5dd Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 11:00:19 +0100 Subject: [PATCH 1110/2266] [feature/otaa] Add fields to devStorage entry & rewrite marshaler / unmarshaler --- core/components/handler/devStorage.go | 40 +++++--- core/components/handler/devStorage_test.go | 105 ++++++++++++++++++--- 2 files changed, 123 insertions(+), 22 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 8ab69ce85..e025f704d 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -17,17 +17,20 @@ import ( type DevStorage interface { UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error Lookup(appEUI []byte, devEUI []byte) (devEntry, error) - StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error + StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error Close() error } const dbDevices = "devices" type devEntry struct { - DevAddr [4]byte + AppEUI []byte + AppKey [16]byte AppSKey [16]byte - NwkSKey [16]byte + DevAddr []byte + DevEUI []byte FCntDown uint32 + NwkSKey [16]byte } type devStorage struct { @@ -68,9 +71,9 @@ func (s *devStorage) lookup(appEUI []byte, devEUI []byte, shouldLock bool) (devE } // StorePersonalized implements the handler.DevStorage interface -func (s *devStorage) StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error { +func (s *devStorage) StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error { devEUI := make([]byte, 8, 8) - copy(devEUI[4:], devAddr[:]) + copy(devEUI[4:], devAddr) e := []dbutil.Entry{ &devEntry{ AppSKey: appSKey, @@ -102,13 +105,19 @@ func (s *devStorage) Close() error { // MarshalBinary implements the encoding.BinaryMarshaler interface func (e devEntry) MarshalBinary() ([]byte, error) { - buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, e.DevAddr[:]) // 4 + buf, cnt := new(bytes.Buffer), 52 + binary.Write(buf, binary.BigEndian, e.AppKey[:]) // 16 binary.Write(buf, binary.BigEndian, e.AppSKey[:]) // 16 binary.Write(buf, binary.BigEndian, e.NwkSKey[:]) // 16 binary.Write(buf, binary.BigEndian, e.FCntDown) // 4 - if len(buf.Bytes()) != 40 { - return nil, errors.New(errors.Structural, "Unable to marshal devEntry") + in := [][]byte{e.AppEUI, e.DevEUI, e.DevAddr} + for _, d := range in { + binary.Write(buf, binary.BigEndian, uint16(len(d))) + binary.Write(buf, binary.BigEndian, d) + cnt += len(d) + 2 + } + if len(buf.Bytes()) != cnt { + return nil, errors.New(errors.Structural, "DevEntry was invalid, unable to marshal") } return buf.Bytes(), nil } @@ -116,8 +125,17 @@ func (e devEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devEntry) UnmarshalBinary(data []byte) error { buf := bytes.NewBuffer(data) - binary.Read(buf, binary.BigEndian, &e.DevAddr) + binary.Read(buf, binary.BigEndian, &e.AppKey) binary.Read(buf, binary.BigEndian, &e.AppSKey) binary.Read(buf, binary.BigEndian, &e.NwkSKey) - return binary.Read(buf, binary.BigEndian, &e.FCntDown) + binary.Read(buf, binary.BigEndian, &e.FCntDown) + var size *uint16 + in := []*[]byte{&e.AppEUI, &e.DevEUI, &e.DevAddr} + for _, p := range in { + size = new(uint16) + binary.Read(buf, binary.BigEndian, size) + *p = make([]byte, *size, *size) + binary.Read(buf, binary.BigEndian, p) + } + return nil } diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 8498f9581..e87dfc5ac 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -41,16 +41,17 @@ func TestLookupStore(t *testing.T) { // Build appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - devAddr := [4]byte{1, 2, 3, 4} + devAddr := []byte{1, 2, 3, 4} appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} // Expect var want = devEntry{ - DevAddr: devAddr, - AppSKey: appSKey, - NwkSKey: nwkSKey, - FCntDown: 0, + DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, + AppEUI: make([]byte, 0, 0), + DevEUI: make([]byte, 0, 0), } // Operate @@ -87,16 +88,17 @@ func TestLookupStore(t *testing.T) { // Build appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 9} devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - devAddr := [4]byte{1, 2, 3, 4} + devAddr := []byte{1, 2, 3, 4} appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} // Expect var want = devEntry{ - DevAddr: devAddr, - AppSKey: appSKey, - NwkSKey: nwkSKey, - FCntDown: 0, + DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, + AppEUI: make([]byte, 0, 0), + DevEUI: make([]byte, 0, 0), } // Operate @@ -119,7 +121,7 @@ func TestLookupStore(t *testing.T) { // Build appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 14} devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - devAddr := [4]byte{1, 2, 3, 4} + devAddr := []byte{1, 2, 3, 4} appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} fcnt := uint32(2) @@ -130,6 +132,8 @@ func TestLookupStore(t *testing.T) { AppSKey: appSKey, NwkSKey: nwkSKey, FCntDown: fcnt, + AppEUI: make([]byte, 0, 0), + DevEUI: make([]byte, 0, 0), } // Operate @@ -169,3 +173,82 @@ func TestLookupStore(t *testing.T) { CheckErrors(t, nil, err) } } + +func TestMarshalUnmarshalEntries(t *testing.T) { + { + Desc(t, "Complete Entry") + entry := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppSKey: [16]byte{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1}, + DevAddr: []byte{4, 4, 4, 4}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + FCntDown: 42, + NwkSKey: [16]byte{28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13}, + } + + data, err := entry.MarshalBinary() + CheckErrors(t, nil, err) + unmarshaled := new(devEntry) + err = unmarshaled.UnmarshalBinary(data) + CheckErrors(t, nil, err) + Check(t, entry, *unmarshaled, "Entries") + } + + // -------------------- + + { + Desc(t, "Partial Entry") + entry := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + FCntDown: 0, + } + want := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DevAddr: make([]byte, 0, 0), + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + FCntDown: 0, + NwkSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + } + + data, err := entry.MarshalBinary() + CheckErrors(t, nil, err) + unmarshaled := new(devEntry) + err = unmarshaled.UnmarshalBinary(data) + CheckErrors(t, nil, err) + Check(t, want, *unmarshaled, "Entries") + } + + // -------------------- + + { + Desc(t, "Partial Entry bis") + entry := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + DevAddr: []byte{}, + FCntDown: 0, + } + want := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + DevAddr: make([]byte, 0, 0), + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + FCntDown: 0, + NwkSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + } + + data, err := entry.MarshalBinary() + CheckErrors(t, nil, err) + unmarshaled := new(devEntry) + err = unmarshaled.UnmarshalBinary(data) + CheckErrors(t, nil, err) + Check(t, want, *unmarshaled, "Entries") + } +} From dd0530e309338967f8beefd31b175bd816132a5c Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 13:56:03 +0100 Subject: [PATCH 1111/2266] [feature/otaa] Implement OTAA in handler --- core/components/handler/devStorage.go | 8 + core/components/handler/handler.go | 271 +++++++++++++++++++++--- core/components/handler/handler_test.go | 54 +++-- core/components/handler/mocks_test.go | 9 + 4 files changed, 286 insertions(+), 56 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index e025f704d..0c88a9a8f 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -18,6 +18,7 @@ type DevStorage interface { UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error Lookup(appEUI []byte, devEUI []byte) (devEntry, error) StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error + Store(entry devEntry) error Close() error } @@ -86,6 +87,13 @@ func (s *devStorage) StorePersonalized(appEUI []byte, devAddr []byte, appSKey, n return s.db.Replace(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), e) } +// Store implements the handler.DevStorage interface +func (s *devStorage) Store(entry devEntry) error { + s.Lock() + defer s.Unlock() + return s.db.Replace(fmt.Sprintf("%x.%x", entry.AppEUI, entry.DevEUI), []byte(dbDevices), []dbutil.Entry{&entry}) +} + // UpdateFCnt implements the handler.DevStorage interface func (s *devStorage) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { s.Lock() diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c76f07017..b51450eff 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -5,7 +5,9 @@ package handler import ( "bytes" + "crypto/aes" "encoding/binary" + "math/rand" "net" "reflect" "time" @@ -23,11 +25,30 @@ import ( // bufferDelay defines the timeframe length during which we bufferize packets const bufferDelay time.Duration = time.Millisecond * 300 +// dataRates makes correspondance between string datarate identifier and lorawan uint descriptors +var dataRates map[string]uint8 = map[string]uint8{ + "SF12BW125": 0, + "SF11BW125": 1, + "SF10BW125": 2, + "SF9BW125": 3, + "SF8BW125": 4, + "SF7BW125": 5, +} + // component implements the core.Component interface type component struct { Components - Set chan<- bundle - NetAddr string + Set chan<- bundle + NetAddr string + Configuration struct { + CFList [5]uint32 + NetID [3]byte + RX1DROffset uint8 + RX2DataRate string + RX2Freq float32 + RXDelay uint8 + JoinDelay uint8 + } } // Interface defines the Handler interface @@ -67,6 +88,15 @@ func New(c Components, o Options) Interface { NetAddr: o.NetAddr, } + // TODO Make it configurable + h.Configuration.CFList = [5]uint32{867100000, 867300000, 867500000, 867700000, 867900000} + h.Configuration.NetID = [3]byte{14, 14, 14} + h.Configuration.RX1DROffset = 0 + h.Configuration.RX2DataRate = "SF9BW125" + h.Configuration.RX2Freq = 869.525 + h.Configuration.RXDelay = 1 + h.Configuration.JoinDelay = 5 + set := make(chan bundle) bundles := make(chan []bundle) @@ -107,8 +137,6 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH stats.MarkMeter("handler.registration.invalid") return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Device Address") } - var devAddr [4]byte - copy(devAddr[:], req.DevAddr) if len(req.NwkSKey) != 16 { stats.MarkMeter("handler.registration.invalid") @@ -126,7 +154,7 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH h.Ctx.Debug("Registration is valid. Saving and forwarding to broker") - if err := h.DevStorage.StorePersonalized(req.AppEUI, devAddr, appSKey, nwkSKey); err != nil { + if err := h.DevStorage.StorePersonalized(req.AppEUI, req.DevAddr, appSKey, nwkSKey); err != nil { h.Ctx.WithError(err).Debug("Unable to store registration") return new(core.ABPSubHandlerRes), errors.New(errors.Operational, err) } @@ -147,8 +175,63 @@ func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubH // HandleJoin implements the core.HandlerServer interface func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (*core.JoinHandlerRes, error) { - // TODO - return nil, nil + stats.MarkMeter("handler.joinrequest.in") + + // 0. Check the packet integrity + if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { + h.Ctx.Debug("Invalid join request packet") + return new(core.JoinHandlerRes), errors.New(errors.Structural, "Invalid parameters") + } + + // 1. Lookup for the associated AppKey + h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") + entry, err := h.DevStorage.Lookup(req.AppEUI, req.DevEUI) + if err != nil { + return new(core.JoinHandlerRes), err + } + + // 2. Prepare a channel to receive the response from the consumer + chresp := make(chan interface{}) + + // 3. Create a "bundle" which holds info waiting for other related packets + var bundleID [20]byte // AppEUI(8) | DevEUI(8) | DevNonce | [ 0 0 ] + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, req.AppEUI) + binary.Write(buf, binary.BigEndian, req.DevEUI) + binary.Write(buf, binary.BigEndian, req.DevNonce) + data := buf.Bytes() + if len(data) != 18 { + return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to generate bundleID") + } + copy(bundleID[:], data[:]) + + // 4. Send the actual bundle to the consumer + ctx := h.Ctx.WithField("BundleID", bundleID) + ctx.Debug("Define new bundle") + h.Set <- bundle{ + ID: bundleID, + Packet: req, + DataRate: req.Metadata.DataRate, + Entry: entry, + Chresp: chresp, + Time: time.Now(), + } + + // 5. Control the response + resp := <-chresp + switch resp.(type) { + case *core.JoinHandlerRes: + stats.MarkMeter("handler.join.send_accept") + ctx.Debug("Sending Join-Accept") + return resp.(*core.JoinHandlerRes), nil + case error: + stats.MarkMeter("handler.join.error") + ctx.WithError(resp.(error)).Warn("Error while processing join-request.") + return new(core.JoinHandlerRes), resp.(error) + default: + ctx.Debug("No response to send.") + return new(core.JoinHandlerRes), nil + } } // HandleDataDown implements the core.HandlerServer interface @@ -206,6 +289,9 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq if err != nil { return new(core.DataUpHandlerRes), err } + if len(entry.DevAddr) != 4 { // Not Activated + return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Tried to send uplink on non-activated device") + } // 2. Prepare a channel to receive the response from the consumer chresp := make(chan interface{}) @@ -335,14 +421,86 @@ browseBundles: go h.consumeDown(pkt.AppEUI, pkt.DevEUI, b.DataRate, bundles) case *core.JoinHandlerReq: pkt := b.Packet.(*core.JoinHandlerReq) - go h.consumeJoin(pkt.AppEUI, pkt.DevEUI, b.DataRate, bundles) + go h.consumeJoin(pkt.AppEUI, pkt.DevEUI, b.Entry.AppKey, b.DataRate, bundles) } } } // consume Join actually consumes a set of join-request packets -func (h component) consumeJoin(appEUI []byte, devEUI []byte, dataRate string, bundles []bundle) { - // TODO +func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, dataRate string, bundles []bundle) { + // Compute score while gathering metadata + var metadata []*core.Metadata + computer, scores, err := dutycycle.NewScoreComputer(dataRate) + if err != nil { + h.abortConsume(err, bundles) + return + } + + for i, bundle := range bundles { + packet := bundle.Packet.(*core.JoinHandlerReq) + metadata = append(metadata, packet.Metadata) + scores = computer.Update(scores, i, *packet.Metadata) + } + + // Check if at least one is available + best := computer.Get(scores) + if best == nil { + h.abortConsume(errors.New(errors.Operational, "No gateway is available for an answer"), bundles) + return + } + packet := bundles[best.ID].Packet.(*core.JoinHandlerReq) + + // Generate a DevAddr + NwkSKey + AppSKey + rdn := rand.New(rand.NewSource(int64(packet.Metadata.Rssi))) + appNonce, devAddr := make([]byte, 4), [4]byte{} + binary.BigEndian.PutUint32(appNonce, rdn.Uint32()) + binary.BigEndian.PutUint32(devAddr[:], rdn.Uint32()) + + buf := make([]byte, 16) + copy(buf[1:4], appNonce[:3]) + copy(buf[4:7], h.Configuration.NetID[:]) + copy(buf[7:9], packet.DevNonce) + + block, err := aes.NewCipher(appKey[:]) + if err != nil || block.BlockSize() != 16 { + h.abortConsume(errors.New(errors.Structural, "Unable to create cipher to generate keys"), bundles) + return + } + + var nwkSKey, appSKey [16]byte + buf[0] = 0x1 + block.Encrypt(nwkSKey[:], buf) + buf[0] = 0x2 + block.Encrypt(appSKey[:], buf) + + // Update the internal storage entry + err = h.DevStorage.Store(devEntry{ + AppEUI: appEUI, + AppKey: appKey, + AppSKey: appSKey, + DevAddr: devAddr[:], + DevEUI: devEUI, + FCntDown: 0, + NwkSKey: nwkSKey, + }) + if err != nil { + h.abortConsume(err, bundles) + return + } + + // Build join-accept and send it + joinAccept, err := h.buildJoinAccept(packet, appKey, appNonce[:3], devAddr, best.IsRX2) + if err != nil { + h.abortConsume(err, bundles) + return + } + for i, bundle := range bundles { + if i == best.ID { + bundle.Chresp <- joinAccept + } else { + bundle.Chresp <- nil + } + } } // consume Down actually consumes a set of downlink packets @@ -354,7 +512,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu computer, scores, err := dutycycle.NewScoreComputer(dataRate) if err != nil { - go h.abortConsume(err, bundles) + h.abortConsume(err, bundles) return } @@ -365,15 +523,17 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu packet := bundle.Packet.(*core.DataUpHandlerReq) if i == 0 { var err error + var devAddr lorawan.DevAddr + copy(devAddr[:], bundle.Entry.DevAddr) payload, err = lorawan.EncryptFRMPayload( bundle.Entry.AppSKey, true, - lorawan.DevAddr(bundle.Entry.DevAddr), + devAddr, packet.FCnt, packet.Payload, ) if err != nil { - go h.abortConsume(err, bundles) + h.abortConsume(err, bundles) return } firstTime = bundle.Time @@ -397,7 +557,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu Metadata: metadata, }) if err != nil { - go h.abortConsume(errors.New(errors.Operational, err), bundles) + h.abortConsume(errors.New(errors.Operational, err), bundles) return } @@ -412,7 +572,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu downlink, err = h.PktStorage.Pull(appEUI, devEUI) } if err != nil && err.(errors.Failure).Nature != errors.NotFound { - go h.abortConsume(err, bundles) + h.abortConsume(err, bundles) return } @@ -423,12 +583,12 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu downlink, err := h.buildDownlink(downlink.Payload, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) if err != nil { - go h.abortConsume(errors.New(errors.Structural, err), bundles) + h.abortConsume(errors.New(errors.Structural, err), bundles) return } err = h.DevStorage.UpdateFCnt(appEUI, devEUI, downlink.Payload.MACPayload.FHDR.FCnt) if err != nil { - go h.abortConsume(err, bundles) + h.abortConsume(err, bundles) return } bundle.Chresp <- downlink @@ -452,9 +612,9 @@ func (h component) abortConsume(err error, bundles []bundle) { func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { macpayload := lorawan.NewMACPayload(false) macpayload.FHDR = lorawan.FHDR{ - FCnt: entry.FCntDown + 1, - DevAddr: entry.DevAddr, + FCnt: entry.FCntDown + 1, } + copy(macpayload.FHDR.DevAddr[:], entry.DevAddr) macpayload.FPort = 1 macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} @@ -479,20 +639,7 @@ func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry de return nil, errors.New(errors.Structural, err) } - metadata := core.Metadata{ - Frequency: up.Metadata.Frequency, - CodingRate: up.Metadata.CodingRate, - DataRate: up.Metadata.DataRate, - PayloadSize: uint32(len(data)), - Timestamp: up.Metadata.Timestamp + 1000, - } - - if isRX2 { // Should we reply on RX2, metadata aren't the same - // TODO Handle different regions with non hard-coded values - metadata.Frequency = 869.50 - metadata.DataRate = "SF9BW125" - metadata.Timestamp = up.Metadata.Timestamp + 2000 - } + metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000*uint32(h.Configuration.RXDelay), isRX2) return &core.DataUpHandlerRes{ Payload: &core.LoRaWANData{ @@ -519,3 +666,61 @@ func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry de Metadata: &metadata, }, nil } + +func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte, appNonce []byte, devAddr [4]byte, isRX2 bool) (*core.JoinHandlerRes, error) { + payload := lorawan.NewPHYPayload(false) + payload.MHDR = lorawan.MHDR{ + MType: lorawan.JoinAccept, + Major: lorawan.LoRaWANR1, + } + joinAcceptPayload := &lorawan.JoinAcceptPayload{ + NetID: lorawan.NetID(h.Configuration.NetID), + DevAddr: lorawan.DevAddr(devAddr), + DLSettings: lorawan.DLsettings{ + RX1DRoffset: h.Configuration.RX1DROffset, + RX2DataRate: dataRates[h.Configuration.RX2DataRate], + }, + RXDelay: h.Configuration.RXDelay, + } + cflist := lorawan.CFList(h.Configuration.CFList) + joinAcceptPayload.CFList = &cflist + copy(joinAcceptPayload.AppNonce[:], appNonce) + payload.MACPayload = joinAcceptPayload + if err := payload.SetMIC(lorawan.AES128Key(appKey)); err != nil { + return nil, errors.New(errors.Structural, err) + } + if err := payload.EncryptJoinAcceptPayload(lorawan.AES128Key(appKey)); err != nil { + return nil, errors.New(errors.Structural, err) + } + data, err := payload.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + + m := h.buildMetadata(*joinReq.Metadata, uint32(len(data)), 1000*uint32(h.Configuration.JoinDelay), isRX2) + return &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: data, + }, + Metadata: &m, + }, nil +} + +// buildMetadata construct a new Metadata +func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay uint32, isRX2 bool) core.Metadata { + m := core.Metadata{ + Frequency: metadata.Frequency, + CodingRate: metadata.CodingRate, + DataRate: metadata.DataRate, + PayloadSize: size, + Timestamp: metadata.Timestamp + baseDelay, + } + + if isRX2 { // Should we reply on RX2, metadata aren't the same + // TODO Handle different regions with non hard-coded values + m.Frequency = h.Configuration.RX2Freq + m.DataRate = h.Configuration.RX2DataRate + m.Timestamp = metadata.Timestamp + baseDelay + 1000 + } + return m +} diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index a2c9e614a..674c3672c 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -381,9 +381,10 @@ func TestHandleDataUp(t *testing.T) { Desc(t, "Handle uplink, 1 packet | No downlink") // Build + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -395,7 +396,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -445,9 +446,10 @@ func TestHandleDataUp(t *testing.T) { Desc(t, "2 packets in a row, same device | No Downlink") // Build + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -459,7 +461,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -553,9 +555,10 @@ func TestHandleDataUp(t *testing.T) { // Build tmst := time.Now() + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -568,7 +571,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -596,7 +599,7 @@ func TestHandleDataUp(t *testing.T) { encodedDown, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, false, - devStorage.OutLookup.Entry.DevAddr, + devAddr, devStorage.OutLookup.Entry.FCntDown+1, pktStorage.OutPull.Entry.Payload, ) @@ -658,8 +661,9 @@ func TestHandleDataUp(t *testing.T) { // Build devStorage := NewMockDevStorage() + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -671,7 +675,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -764,9 +768,10 @@ func TestHandleDataUp(t *testing.T) { Desc(t, "Handle uplink, 1 packet | AppAdapter fails") // Build + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -779,7 +784,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -829,9 +834,10 @@ func TestHandleDataUp(t *testing.T) { Desc(t, "Handle uplink, 1 packet | PktStorage fails") // Build + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -844,7 +850,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -899,9 +905,9 @@ func TestHandleDataUp(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() payload1, fcnt1 := []byte("Payload1"), uint32(14) - devAddr1, appSKey1 := [4]byte{1, 2, 3, 4}, [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + devAddr1, appSKey1 := lorawan.DevAddr([4]byte{1, 2, 3, 4}), [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} payload2, fcnt2 := []byte("Payload2"), uint32(35346) - devAddr2, appSKey2 := [4]byte{4, 3, 2, 1}, [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} + devAddr2, appSKey2 := lorawan.DevAddr([4]byte{4, 3, 2, 1}), [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} encoded1, err := lorawan.EncryptFRMPayload( appSKey1, true, @@ -977,7 +983,7 @@ func TestHandleDataUp(t *testing.T) { }, Options{NetAddr: "localhost"}) devStorage.OutLookup.Entry = devEntry{ - DevAddr: devAddr1, + DevAddr: devAddr1[:], AppSKey: appSKey1, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -992,7 +998,7 @@ func TestHandleDataUp(t *testing.T) { // Operate devStorage.OutLookup.Entry = devEntry{ - DevAddr: devAddr2, + DevAddr: devAddr2[:], AppSKey: appSKey2, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 11, @@ -1013,9 +1019,10 @@ func TestHandleDataUp(t *testing.T) { // Build tmst := time.Now() + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -1028,7 +1035,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) @@ -1056,7 +1063,7 @@ func TestHandleDataUp(t *testing.T) { encodedDown, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, false, - devStorage.OutLookup.Entry.DevAddr, + devAddr, devStorage.OutLookup.Entry.FCntDown+1, pktStorage.OutPull.Entry.Payload, ) @@ -1080,7 +1087,7 @@ func TestHandleDataUp(t *testing.T) { }, Metadata: &core.Metadata{ DataRate: "SF9BW125", - Frequency: 869.5, + Frequency: 869.525, CodingRate: "4/5", Timestamp: uint32(tmst.Add(2*time.Second).Unix() * 1000), PayloadSize: 21, @@ -1118,9 +1125,10 @@ func TestHandleDataUp(t *testing.T) { // Build tmst := time.Now() + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() devStorage.OutLookup.Entry = devEntry{ - DevAddr: [4]byte{3, 4, 2, 4}, + DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, @@ -1134,7 +1142,7 @@ func TestHandleDataUp(t *testing.T) { encoded, err := lorawan.EncryptFRMPayload( devStorage.OutLookup.Entry.AppSKey, true, - devStorage.OutLookup.Entry.DevAddr, + devAddr, fcnt, payload, ) diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 80dd26b78..45751ffcc 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -26,6 +26,9 @@ type MockDevStorage struct { AppSKey [16]byte NwkSKey [16]byte } + InStore struct { + Entry devEntry + } InClose struct { Called bool } @@ -62,6 +65,12 @@ func (m *MockDevStorage) StorePersonalized(appEUI []byte, devAddr []byte, appSKe return m.Failures["StorePersonalized"] } +// Store implements the DevStorage interface +func (m *MockDevStorage) Store(entry devEntry) error { + m.InStore.Entry = entry + return m.Failures["Store"] +} + // Close implements the DevStorage Interface func (m *MockDevStorage) Close() error { m.InClose.Called = true From 15dc3c02d6b3788e2dfdfde65adb299f391b40f6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 14:02:49 +0100 Subject: [PATCH 1112/2266] [feature/otaa] Allow join-accept to be nil in router and broker --- core/components/broker/broker.go | 7 ++++++- core/components/router/router.go | 4 ++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index de2b99281..a106567c5 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -126,7 +126,12 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c } // Handle the response - if res == nil || res.Payload == nil || len(res.DevAddr) != 4 || len(res.NwkSKey) != 16 { + if res == nil || res.Payload == nil { + ctx.Debug("No Join-Accept") + return new(core.JoinBrokerRes), nil + } + + if len(res.DevAddr) != 4 || len(res.NwkSKey) != 16 { ctx.Debug("Invalid response from handler") return new(core.JoinBrokerRes), errors.New(errors.Operational, "Invalid response from handler") } diff --git a/core/components/router/router.go b/core/components/router/router.go index 642313314..20c6e889f 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -108,6 +108,10 @@ func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (rou // Update Gateway Duty cycle with response metadata res := response.(*core.JoinBrokerRes) + if res == nil || res.Payload == nil { // No response + r.Ctx.Debug("No Join-Accept received") + return new(core.JoinRouterRes), nil + } if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { return new(core.JoinRouterRes), err } From 7e606d6aef9b443b41d200bc3fab3094285e3def Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 14:14:59 +0100 Subject: [PATCH 1113/2266] [feature/otaa] Add HandleJoin to mqtt adapter --- core/adapters/mqtt/mqtt.go | 26 +++++++++++++++ core/mocks/mocks.go | 14 ++++++++ core/msgp.go | 5 +++ core/msgp_gen.go | 68 ++++++++++++++++++++++++++++++++++++++ core/msgp_gen_test.go | 58 ++++++++++++++++++++++++++++++++ 5 files changed, 171 insertions(+) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index fddd9a973..17da491de 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -120,6 +120,32 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp return nil, nil } +// HandleJoin implements the core.AppClient interface +func (a adapter) HandleJoin(bctx context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) { + if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.Metadata) == 0 { + a.Ctx.Debug("Received invalid JoinAppReq") + return nil, errors.New(errors.Structural, "Invalid request parameters") + } + otaa := core.OTAAAppReq{ + Metadata: core.ProtoMetaToAppMeta(req.Metadata...), + } + data, err := otaa.MarshalMsg(nil) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) + err = a.Client.Publish(&client.PublishOptions{ + QoS: mqtt.QoS2, + Retain: false, + TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)), + Message: data, + }) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + return nil, nil +} + // consumeMQTTMsg processes incoming messages from MQTT broker. // // It runs in its own goroutine diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 8cfa4baf2..719db8545 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -314,6 +314,13 @@ type HandlerServer struct { OutHandleDataDown struct { Res *core.DataDownHandlerRes } + InHandleJoin struct { + Ctx context.Context + Req *core.JoinHandlerReq + } + OutHandleJoin struct { + Res *core.JoinHandlerRes + } InSubscribePersonalized struct { Ctx context.Context Req *core.ABPSubHandlerReq @@ -344,6 +351,13 @@ func (m *HandlerServer) HandleDataDown(ctx context.Context, in *core.DataDownHan return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] } +// HandleJoin implements the core.HandlerServer interface +func (m *HandlerServer) HandleJoin(ctx context.Context, in *core.JoinHandlerReq) (*core.JoinHandlerRes, error) { + m.InHandleJoin.Ctx = ctx + m.InHandleJoin.Req = in + return m.OutHandleJoin.Res, m.Failures["HandleJoin"] +} + // SubscribePersonalized implements the core.HandlerServer interface func (m *HandlerServer) SubscribePersonalized(ctx context.Context, in *core.ABPSubHandlerReq) (*core.ABPSubHandlerRes, error) { m.InSubscribePersonalized.Ctx = ctx diff --git a/core/msgp.go b/core/msgp.go index a8ef147ba..0e84ca3ca 100644 --- a/core/msgp.go +++ b/core/msgp.go @@ -15,6 +15,11 @@ type DataUpAppReq struct { Metadata []AppMetadata `msg:"metadata" json:"metadata"` } +// JoinAppReq are used to notify application of an accepted OTAA +type OTAAAppReq struct { + Metadata []AppMetadata `msg:"metadata" json:"metadata"` +} + // AppMetadata represents gathered metadata that are sent to gateways type AppMetadata struct { Frequency float32 `msg:"frequency" json:"frequency"` diff --git a/core/msgp_gen.go b/core/msgp_gen.go index a5868f99d..7a56efc71 100644 --- a/core/msgp_gen.go +++ b/core/msgp_gen.go @@ -304,3 +304,71 @@ func (z *DataUpAppReq) Msgsize() (s int) { } return } + +// MarshalMsg implements msgp.Marshaler +func (z *OTAAAppReq) MarshalMsg(b []byte) (o []byte, err error) { + o = msgp.Require(b, z.Msgsize()) + // map header, size 1 + // string "Metadata" + o = append(o, 0x81, 0xa8, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) + o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) + for bzg := range z.Metadata { + o, err = z.Metadata[bzg].MarshalMsg(o) + if err != nil { + return + } + } + return +} + +// UnmarshalMsg implements msgp.Unmarshaler +func (z *OTAAAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { + var field []byte + _ = field + var isz uint32 + isz, bts, err = msgp.ReadMapHeaderBytes(bts) + if err != nil { + return + } + for isz > 0 { + isz-- + field, bts, err = msgp.ReadMapKeyZC(bts) + if err != nil { + return + } + switch msgp.UnsafeString(field) { + case "Metadata": + var xsz uint32 + xsz, bts, err = msgp.ReadArrayHeaderBytes(bts) + if err != nil { + return + } + if cap(z.Metadata) >= int(xsz) { + z.Metadata = z.Metadata[:xsz] + } else { + z.Metadata = make([]AppMetadata, xsz) + } + for bzg := range z.Metadata { + bts, err = z.Metadata[bzg].UnmarshalMsg(bts) + if err != nil { + return + } + } + default: + bts, err = msgp.Skip(bts) + if err != nil { + return + } + } + } + o = bts + return +} + +func (z *OTAAAppReq) Msgsize() (s int) { + s = 1 + 9 + msgp.ArrayHeaderSize + for bzg := range z.Metadata { + s += z.Metadata[bzg].Msgsize() + } + return +} diff --git a/core/msgp_gen_test.go b/core/msgp_gen_test.go index 119e3ce8e..5040f1237 100644 --- a/core/msgp_gen_test.go +++ b/core/msgp_gen_test.go @@ -241,3 +241,61 @@ func BenchmarkUnmarshalDataUpAppReq(b *testing.B) { } } } + +func TestMarshalUnmarshalOTAAAppReq(t *testing.T) { + v := OTAAAppReq{} + bts, err := v.MarshalMsg(nil) + if err != nil { + t.Fatal(err) + } + left, err := v.UnmarshalMsg(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) + } + + left, err = msgp.Skip(bts) + if err != nil { + t.Fatal(err) + } + if len(left) > 0 { + t.Errorf("%d bytes left over after Skip(): %q", len(left), left) + } +} + +func BenchmarkMarshalMsgOTAAAppReq(b *testing.B) { + v := OTAAAppReq{} + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + v.MarshalMsg(nil) + } +} + +func BenchmarkAppendMsgOTAAAppReq(b *testing.B) { + v := OTAAAppReq{} + bts := make([]byte, 0, v.Msgsize()) + bts, _ = v.MarshalMsg(bts[0:0]) + b.SetBytes(int64(len(bts))) + b.ReportAllocs() + b.ResetTimer() + for i := 0; i < b.N; i++ { + bts, _ = v.MarshalMsg(bts[0:0]) + } +} + +func BenchmarkUnmarshalOTAAAppReq(b *testing.B) { + v := OTAAAppReq{} + bts, _ := v.MarshalMsg(nil) + b.ReportAllocs() + b.SetBytes(int64(len(bts))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + _, err := v.UnmarshalMsg(bts) + if err != nil { + b.Fatal(err) + } + } +} From fc79b1d0bd7392692dde18223f0ae47b66059c50 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 21 Mar 2016 15:08:25 +0100 Subject: [PATCH 1114/2266] Vendor brocaar/lorawan library --- .gitmodules | 3 +++ vendor/github.com/brocaar/lorawan | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 vendor/github.com/brocaar/lorawan diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..52f98dfd2 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "vendor/github.com/brocaar/lorawan"] + path = vendor/github.com/brocaar/lorawan + url = https://github.com/brocaar/lorawan.git diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan new file mode 160000 index 000000000..1d7c07127 --- /dev/null +++ b/vendor/github.com/brocaar/lorawan @@ -0,0 +1 @@ +Subproject commit 1d7c0712758c0762fdda6befae79aacc38f9b4d3 From a613844acbba49251fdbc741031e040493233b18 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 15:10:54 +0100 Subject: [PATCH 1115/2266] [feature/otaa] Add tests to handler for OTAA --- core/components/handler/handler.go | 46 +- core/components/handler/handler_test.go | 681 ++++++++++++++++++++++++ 2 files changed, 711 insertions(+), 16 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index b51450eff..cda909aa6 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -196,14 +196,10 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* // 3. Create a "bundle" which holds info waiting for other related packets var bundleID [20]byte // AppEUI(8) | DevEUI(8) | DevNonce | [ 0 0 ] buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, req.AppEUI) - binary.Write(buf, binary.BigEndian, req.DevEUI) - binary.Write(buf, binary.BigEndian, req.DevNonce) - data := buf.Bytes() - if len(data) != 18 { - return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to generate bundleID") - } - copy(bundleID[:], data[:]) + _ = binary.Write(buf, binary.BigEndian, req.AppEUI) + _ = binary.Write(buf, binary.BigEndian, req.DevEUI) + _ = binary.Write(buf, binary.BigEndian, req.DevNonce) + copy(bundleID[:], buf.Bytes()) // 4. Send the actual bundle to the consumer ctx := h.Ctx.WithField("BundleID", bundleID) @@ -299,14 +295,10 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // 3. Create a "bundle" which holds info waiting for other related packets var bundleID [20]byte // AppEUI(8) | DevEUI(8) | FCnt buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, req.AppEUI) - binary.Write(buf, binary.BigEndian, req.DevEUI) - binary.Write(buf, binary.BigEndian, req.FCnt) - data := buf.Bytes() - if len(data) != 20 { - return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Unable to generate bundleID") - } - copy(bundleID[:], data[:]) + _ = binary.Write(buf, binary.BigEndian, req.AppEUI) + _ = binary.Write(buf, binary.BigEndian, req.DevEUI) + _ = binary.Write(buf, binary.BigEndian, req.FCnt) + copy(bundleID[:], buf.Bytes()) // 4. Send the actual bundle to the consumer ctx := h.Ctx.WithField("BundleID", bundleID) @@ -428,14 +420,19 @@ browseBundles: // consume Join actually consumes a set of join-request packets func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, dataRate string, bundles []bundle) { + ctx := h.Ctx.WithField("AppEUI", appEUI).WithField("DevEUI", devEUI) + ctx.Debug("Consuming join-request") + // Compute score while gathering metadata var metadata []*core.Metadata computer, scores, err := dutycycle.NewScoreComputer(dataRate) if err != nil { + ctx.WithError(err).Debug("Unable to instantiate score computer") h.abortConsume(err, bundles) return } + ctx.Debug("Compute scores for each packet") for i, bundle := range bundles { packet := bundle.Packet.(*core.JoinHandlerReq) metadata = append(metadata, packet.Metadata) @@ -444,6 +441,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da // Check if at least one is available best := computer.Get(scores) + ctx.WithField("Best", best).Debug("Determine best recipient to reply") if best == nil { h.abortConsume(errors.New(errors.Operational, "No gateway is available for an answer"), bundles) return @@ -451,6 +449,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da packet := bundles[best.ID].Packet.(*core.JoinHandlerReq) // Generate a DevAddr + NwkSKey + AppSKey + ctx.Debug("Generate DevAddr, NwkSKey and AppSKey") rdn := rand.New(rand.NewSource(int64(packet.Metadata.Rssi))) appNonce, devAddr := make([]byte, 4), [4]byte{} binary.BigEndian.PutUint32(appNonce, rdn.Uint32()) @@ -484,6 +483,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da NwkSKey: nwkSKey, }) if err != nil { + ctx.WithError(err).Debug("Unable to initialize devEntry with activation") h.abortConsume(err, bundles) return } @@ -491,9 +491,23 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da // Build join-accept and send it joinAccept, err := h.buildJoinAccept(packet, appKey, appNonce[:3], devAddr, best.IsRX2) if err != nil { + ctx.WithError(err).Debug("Unable to build join accept") h.abortConsume(err, bundles) return } + joinAccept.NwkSKey = nwkSKey[:] + joinAccept.DevAddr = devAddr[:] + + // Notify the application + _, err = h.AppAdapter.HandleJoin(context.Background(), &core.JoinAppReq{ + Metadata: metadata, + AppEUI: appEUI, + DevEUI: devEUI, + }) + if err != nil { + ctx.WithError(err).Debug("Fails to notify application") + } + for i, bundle := range bundles { if i == best.ID { bundle.Chresp <- joinAccept diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 674c3672c..288faf953 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1493,6 +1493,687 @@ func TestSubscribePersonalized(t *testing.T) { } } +func TestHandleJoin(t *testing.T) { + { + Desc(t, "Handle valid join-request | get join-accept") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr *string + var wantRes = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding + NwkSKey: nil, // We'll assume it's correct if payload is okay + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000), + PayloadSize: 33, + }, + } + var wantAppReq = &core.JoinAppReq{ + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") + Check(t, 16, len(res.NwkSKey), "Network session keys' length") + Check(t, 4, len(res.DevAddr), "Device addresses' length") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + joinaccept := lorawan.NewPHYPayload(false) + err = joinaccept.UnmarshalBinary(res.Payload.Payload) + CheckErrors(t, nil, err) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InStore.Entry.AppKey)) + CheckErrors(t, nil, err) + Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") + } + + // -------------------- + + { + Desc(t, "Handle valid join-request, fails to notify app.") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + appAdapter.Failures["HandleJoin"] = errors.New(errors.Operational, "Mock Error") + broker := mocks.NewBrokerClient() + + // Expect + var wantErr *string + var wantRes = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding + NwkSKey: nil, // We'll assume it's correct if payload is okay + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000), + PayloadSize: 33, + }, + } + var wantAppReq = &core.JoinAppReq{ + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") + Check(t, 16, len(res.NwkSKey), "Network session keys' length") + Check(t, 4, len(res.DevAddr), "Device addresses' length") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + joinaccept := lorawan.NewPHYPayload(false) + err = joinaccept.UnmarshalBinary(res.Payload.Payload) + CheckErrors(t, nil, err) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InStore.Entry.AppKey)) + CheckErrors(t, nil, err) + Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") + } + + // -------------------- + + { + Desc(t, "Handle valid join-request, fails to store") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + devStorage.Failures["Store"] = errors.New(errors.Operational, "Mock Error") + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrOperational + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle valid join-request, no gateway available") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateBlocked), + DutyRX2: uint32(dutycycle.StateBlocked), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrOperational + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle invalid join request -> Invalid datarate") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "Not A DataRate", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle invalid join-request, lookup fails") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrNotFound + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle invalid join-request -> invalid devEUI") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle invalid join-request -> invalid appEUI") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle invalid join-request -> invalid devNonce") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: nil, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // -------------------- + + { + Desc(t, "Handle invalid join-request -> invalid Metadata") + + // Build + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: nil, + } + + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr = ErrStructural + var wantRes = new(core.JoinHandlerRes) + var wantAppReq *core.JoinAppReq + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Join Handler Responses") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + + // ------------------- + + { + Desc(t, "Handle valid join-request (2 packets)") + + // Build + tmst1 := time.Now() + tmst2 := time.Now().Add(42 * time.Millisecond) + + req1 := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst1.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateWarning), + DutyRX2: uint32(dutycycle.StateWarning), + Rssi: -20, + Lsnr: 5.0, + }, + } + + req2 := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF8BW125", + Frequency: 867.234, + Timestamp: uint32(tmst2.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutLookup.Entry = devEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + + // Expect + var wantErr1 *string + var wantErr2 *string + var wantRes1 = new(core.JoinHandlerRes) + var wantRes2 = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding + NwkSKey: nil, // We'll assume it's correct if payload is okay + Metadata: &core.Metadata{ + DataRate: "SF8BW125", + Frequency: 867.234, + CodingRate: "4/5", + Timestamp: uint32(tmst2.Add(5*time.Second).Unix() * 1000), + PayloadSize: 33, + }, + } + var wantAppReq = &core.JoinAppReq{ + Metadata: []*core.Metadata{req1.Metadata, req2.Metadata}, + AppEUI: req1.AppEUI, + DevEUI: req1.DevEUI, + } + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + + chack := make(chan bool) + go func() { + var ok bool + defer func(ok *bool) { chack <- *ok }(&ok) + res, err := handler.HandleJoin(context.Background(), req1) + + // Check + CheckErrors(t, wantErr1, err) + Check(t, wantRes1, res, "Data Up Handler Responses") + ok = true + }() + + go func() { + <-time.After(bufferDelay / 3) + var ok bool + defer func(ok *bool) { chack <- *ok }(&ok) + res, err := handler.HandleJoin(context.Background(), req2) + + // Check + CheckErrors(t, wantErr2, err) + Check(t, wantRes2.Metadata, res.Metadata, "Join Handler Responses") + Check(t, 16, len(res.NwkSKey), "Network session keys' length") + Check(t, 4, len(res.DevAddr), "Device addresses' length") + joinaccept := lorawan.NewPHYPayload(false) + err = joinaccept.UnmarshalBinary(res.Payload.Payload) + CheckErrors(t, nil, err) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InStore.Entry.AppKey)) + CheckErrors(t, nil, err) + Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") + ok = true + }() + + // Check + ok1, ok2 := <-chack, <-chack + Check(t, true, ok1 && ok2, "Acknowledgements") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + } + +} + func TestStart(t *testing.T) { handler := New(Components{ Ctx: GetLogger(t, "Handler"), From 8d55aa4a94c90f5cb3367880e6a351ec1ffd5700 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 21 Mar 2016 15:13:42 +0100 Subject: [PATCH 1116/2266] Don't test vendored stuff --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d3a02416f..973a9bb01 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,7 +20,7 @@ before_script: - mosquitto -p 1883 1>/dev/null 2>/dev/null & script: - - go list ./... | grep -vE 'integration|ttn$' | xargs go test + - go list ./... | grep -vE 'vendor|integration|ttn$' | xargs go test - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - go vet ./... From 831191868a779ca5ded8979908dc1814d73059af Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 15:19:11 +0100 Subject: [PATCH 1117/2266] [feature/otaa] Fix FPort from lorawan being now a pointer --- core/adapters/udp/conversions.go | 2 +- core/adapters/udp/conversions_test.go | 8 ++++++-- core/adapters/udp/udp_test.go | 27 ++++++++++++++++++--------- core/components/handler/handler.go | 7 ++++--- core/core.go | 3 ++- ttnctl/cmd/uplink.go | 3 ++- 6 files changed, 33 insertions(+), 17 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 8091769cf..cec2a54fb 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -78,7 +78,7 @@ func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interfa }, FOpts: fopts, }, - FPort: uint32(macpayload.FPort), + FPort: uint32(*macpayload.FPort), FRMPayload: frmpayload, }, MIC: payload.MIC[:], diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index c54a5110f..4a986ba4d 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -383,7 +383,9 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(false) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 + macpayload.FHDR.FOpts = []lorawan.MACCommand{ lorawan.MACCommand{ CID: lorawan.DutyCycleReq, @@ -424,7 +426,9 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(false) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} payload.MACPayload = macpayload data, err := payload.MarshalBinary() diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index a151d2721..aa2443323 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -51,7 +51,9 @@ func TestUDPAdapter(t *testing.T) { payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(true) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} payload.MACPayload = macpayload data, err := payload.MarshalBinary() @@ -102,7 +104,7 @@ func TestUDPAdapter(t *testing.T) { }, FOpts: nil, }, - FPort: uint32(macpayload.FPort), + FPort: uint32(*macpayload.FPort), FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, }, MIC: payload.MIC[:], @@ -152,7 +154,9 @@ func TestUDPAdapter(t *testing.T) { payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(true) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} payload.MACPayload = macpayload data, err := payload.MarshalBinary() @@ -201,7 +205,8 @@ func TestUDPAdapter(t *testing.T) { payloadDown.MHDR.Major = lorawan.Major(router.OutHandleData.Res.Payload.MHDR.Major) copy(payloadDown.MIC[:], router.OutHandleData.Res.Payload.MIC) macpayloadDown := lorawan.NewMACPayload(false) - macpayloadDown.FPort = uint8(router.OutHandleData.Res.Payload.MACPayload.FPort) + macpayloadDown.FPort = new(uint8) + *macpayloadDown.FPort = uint8(router.OutHandleData.Res.Payload.MACPayload.FPort) macpayloadDown.FHDR.FCnt = router.OutHandleData.Res.Payload.MACPayload.FHDR.FCnt copy(macpayloadDown.FHDR.DevAddr[:], router.OutHandleData.Res.Payload.MACPayload.FHDR.DevAddr) macpayloadDown.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ @@ -238,7 +243,7 @@ func TestUDPAdapter(t *testing.T) { }, FOpts: nil, }, - FPort: uint32(macpayload.FPort), + FPort: uint32(*macpayload.FPort), FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, }, MIC: payload.MIC[:], @@ -296,7 +301,9 @@ func TestUDPAdapter(t *testing.T) { payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(true) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} payload.MACPayload = macpayload data, err := payload.MarshalBinary() @@ -358,7 +365,7 @@ func TestUDPAdapter(t *testing.T) { }, FOpts: nil, }, - FPort: uint32(macpayload.FPort), + FPort: uint32(*macpayload.FPort), FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, }, MIC: payload.MIC[:], @@ -524,7 +531,9 @@ func TestUDPAdapter(t *testing.T) { payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} macpayload := lorawan.NewMACPayload(true) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} payload.MACPayload = macpayload data, err := payload.MarshalBinary() @@ -576,7 +585,7 @@ func TestUDPAdapter(t *testing.T) { }, FOpts: nil, }, - FPort: uint32(macpayload.FPort), + FPort: uint32(*macpayload.FPort), FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, }, MIC: payload.MIC[:], diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index cda909aa6..af5e4f442 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -26,7 +26,7 @@ import ( const bufferDelay time.Duration = time.Millisecond * 300 // dataRates makes correspondance between string datarate identifier and lorawan uint descriptors -var dataRates map[string]uint8 = map[string]uint8{ +var dataRates = map[string]uint8{ "SF12BW125": 0, "SF11BW125": 1, "SF10BW125": 2, @@ -629,7 +629,8 @@ func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry de FCnt: entry.FCntDown + 1, } copy(macpayload.FHDR.DevAddr[:], entry.DevAddr) - macpayload.FPort = 1 + macpayload.FPort = new(uint8) + *macpayload.FPort = 1 macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} if err := macpayload.EncryptFRMPayload(entry.AppSKey); err != nil { @@ -672,7 +673,7 @@ func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry de FPending: macpayload.FHDR.FCtrl.FPending, }, }, - FPort: uint32(macpayload.FPort), + FPort: uint32(*macpayload.FPort), FRMPayload: frmpayload, }, MIC: payload.MIC[:], diff --git a/core/core.go b/core/core.go index a47315013..718ed2f45 100644 --- a/core/core.go +++ b/core/core.go @@ -44,7 +44,8 @@ func NewLoRaWANData(reqPayload *LoRaWANData, uplink bool) (lorawan.PHYPayload, e } macpayload := lorawan.NewMACPayload(uplink) - macpayload.FPort = uint8(mac.FPort) + macpayload.FPort = new(uint8) + *macpayload.FPort = uint8(mac.FPort) copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) macpayload.FHDR.FCnt = fhdr.FCnt for _, data := range fhdr.FOpts { diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 5e2d492ef..e4888eac0 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -61,7 +61,8 @@ var uplinkCmd = &cobra.Command{ DevAddr: devAddr, FCnt: uint32(fcnt), } - macPayload.FPort = 1 + macPayload.FPort = new(uint8) + *macPayload.FPort = 1 macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[3])}} if err := macPayload.EncryptFRMPayload(appSKey); err != nil { ctx.Fatalf("Unable to encrypt frame payload: %s", err) From 5d8a90417a5db1b2ea4f983a6897b7837166c01f Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 15:51:41 +0100 Subject: [PATCH 1118/2266] Update lorawan submodule --- vendor/github.com/brocaar/lorawan | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan index 1d7c07127..6a90a1f55 160000 --- a/vendor/github.com/brocaar/lorawan +++ b/vendor/github.com/brocaar/lorawan @@ -1 +1 @@ -Subproject commit 1d7c0712758c0762fdda6befae79aacc38f9b4d3 +Subproject commit 6a90a1f556b1081e42474519b5b051187b3c28b6 From 55674388682331a95a3f3f8aadb4ffee4f1f07e6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 17:20:12 +0100 Subject: [PATCH 1119/2266] [refactor/storages] Rework storage package --- core/storage/storage.go | 188 ++++++++++++++++++---------------- core/storage/storage_test.go | 191 ++++++++++++++++++++--------------- 2 files changed, 213 insertions(+), 166 deletions(-) diff --git a/core/storage/storage.go b/core/storage/storage.go index f86851c00..b4c9099d3 100644 --- a/core/storage/storage.go +++ b/core/storage/storage.go @@ -7,7 +7,6 @@ import ( "encoding" "fmt" "reflect" - "strings" "time" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -15,31 +14,35 @@ import ( "github.com/boltdb/bolt" ) -// Entry offers a friendly interface on which the storage will operate. -// Basically, a Entry is nothing more than a binary marshaller/unmarshaller. -type Entry interface { - encoding.BinaryMarshaler - encoding.BinaryUnmarshaler -} - // The storage Interface provides an abstraction on top of the bolt database to store and access // data in a local in-memory database. -// All "table" or "bucket" in a database can be accessed by their name as a string where the dot -// "." is use as a level-separator to select or create nested buckets. +// All methods uses a slice of buckets as bucket selector, each subsequent bucket being nested in +// the previous one. Each time also, the key can be omitted in which case it will fall back to a +// default key. type Interface interface { - Store(name string, key []byte, entries []Entry) error - Replace(name string, key []byte, entries []Entry) error - Lookup(name string, key []byte, shape Entry) (interface{}, error) - Flush(name string, key []byte) error - Reset(name string) error + // Read retrieves a slice of entries from the storage. The output is a slice of the same type as + // of `shape`, possibly of length 0. + // The provided type has to implement a binary.Unmarshaler interface + Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) + // Update replaces a set of entries by the new given set + Update(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error + // Append adds at the end of an existing set the new given set + Append(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error + // Delete complete removes all targeted entries + Delete(key []byte, buckets ...[]byte) error + // Reset empties a bucket + Reset(buckets ...[]byte) error + // Close terminates the communication with the storage Close() error } +var defaultKey = []byte("entry") + type store struct { db *bolt.DB } -// New creates a new storage instance ready-to-use +// New creates a new storage instance ready-to-be-used func New(name string) (Interface, error) { db, err := bolt.Open(name, 0600, &bolt.Options{Timeout: time.Second}) if err != nil { @@ -60,13 +63,12 @@ func ensureErr(err error) error { return err } -// getBucket retrieve a bucket based on a string. The name might present several levels, all -// separated by dot "." which indicates nested buckets. If no bucket is found along the path, they -// are created, otherwise, the existing one is used. -func getBucket(tx *bolt.Tx, name string) (*bolt.Bucket, error) { - path := strings.Split(name, ".") - if len(path) < 1 { - return nil, errors.New(errors.Structural, "Invalid bucket name") +// getBucket retrieves a bucket based on a slice of ordered identifiers. Each following identifier +// targets a nested bucket in the previous one. If no bucket is found along the path, they are +// created if the write rights are granted by Tx, otherwise, the existing one is used. +func getBucket(tx *bolt.Tx, buckets [][]byte) (*bolt.Bucket, error) { + if len(buckets) < 1 { + return nil, errors.New(errors.Structural, "At least one bucket name is required") } var cursor interface { CreateBucketIfNotExists(b []byte) (*bolt.Bucket, error) @@ -75,10 +77,10 @@ func getBucket(tx *bolt.Tx, name string) (*bolt.Bucket, error) { var err error cursor = tx - for _, bname := range path { - next := cursor.Bucket([]byte(bname)) + for _, name := range buckets { + next := cursor.Bucket(name) if next == nil { - if next, err = cursor.CreateBucketIfNotExists([]byte(bname)); err != nil { + if next, err = cursor.CreateBucketIfNotExists(name); err != nil { return nil, errors.New(errors.Operational, err) } } @@ -87,51 +89,21 @@ func getBucket(tx *bolt.Tx, name string) (*bolt.Bucket, error) { return cursor.(*bolt.Bucket), nil } -// Store put a new set of entries in the given bolt database. It adds the entries to an existing set -// or create a new set. -func (itf store) Store(bucketName string, key []byte, entries []Entry) error { - var marshalled [][]byte - - for _, entry := range entries { - m, err := entry.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - marshalled = append(marshalled, m) +// Read implements the storage.Interface interface +func (itf store) Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) { + if key == nil { + key = defaultKey } - err := itf.db.Update(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, bucketName) - if err != nil { - return err - } - w := readwriter.New(bucket.Get(key)) - for _, m := range marshalled { - w.Write(m) - } - data, err := w.Bytes() - if err != nil { - return errors.New(errors.Structural, err) - } - if err := bucket.Put(key, data); err != nil { - return errors.New(errors.Operational, err) - } - return nil - }) - - return ensureErr(err) -} + entryType := reflect.TypeOf(shape) + if entryType.Kind() != reflect.Ptr { + return nil, errors.New(errors.Implementation, "Non-pointer shape not supported") + } -// Lookup retrieves a set of entry from a given bolt database. -// -// The shape is used as a template for retrieving and creating the data. All entries extracted from -// the database will be interpreted as instance of shape and the return result will be a slice of -// the same type of shape. -func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{}, error) { // First, lookup the raw entries var rawEntry []byte err := itf.db.View(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, bucketName) + bucket, err := getBucket(tx, buckets) if err != nil { if err.(errors.Failure).Fault == bolt.ErrTxNotWritable { return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) @@ -151,12 +123,11 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} // Then, interpret them as instance of 'shape' r := readwriter.New(rawEntry) - entryType := reflect.TypeOf(shape).Elem() - entries := reflect.MakeSlice(reflect.SliceOf(entryType), 0, 0) + entries := reflect.MakeSlice(reflect.SliceOf(entryType.Elem()), 0, 0) for { r.Read(func(data []byte) { - entry := reflect.New(entryType).Interface() - entry.(Entry).UnmarshalBinary(data) + entry := reflect.New(entryType.Elem()).Interface() + entry.(encoding.BinaryUnmarshaler).UnmarshalBinary(data) entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) }) if err = r.Err(); err != nil { @@ -170,22 +141,48 @@ func (itf store) Lookup(bucketName string, key []byte, shape Entry) (interface{} return entries.Interface(), nil } -// Flush remove an entry from a bucket -func (itf store) Flush(bucketName string, key []byte) error { - return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, bucketName) +// Append implements the storage.Interface interface +func (itf store) Append(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error { + if key == nil { + key = defaultKey + } + var marshalled [][]byte + + for _, entry := range entries { + m, err := entry.MarshalBinary() + if err != nil { + return errors.New(errors.Structural, err) + } + marshalled = append(marshalled, m) + } + + err := itf.db.Update(func(tx *bolt.Tx) error { + bucket, err := getBucket(tx, buckets) if err != nil { return err } - if err := bucket.Delete(key); err != nil { + w := readwriter.New(bucket.Get(key)) + for _, m := range marshalled { + w.Write(m) + } + data, err := w.Bytes() + if err != nil { + return errors.New(errors.Structural, err) + } + if err := bucket.Put(key, data); err != nil { return errors.New(errors.Operational, err) } return nil - })) + }) + + return ensureErr(err) } -// Replace stores entries in the database by replacing them by a new set -func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { +// Update implements the storage.Interface interface +func (itf store) Update(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error { + if key == nil { + key = defaultKey + } var marshalled [][]byte for _, entry := range entries { @@ -197,7 +194,7 @@ func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { } return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, bucketName) + bucket, err := getBucket(tx, buckets) if err != nil { return err } @@ -219,37 +216,54 @@ func (itf store) Replace(bucketName string, key []byte, entries []Entry) error { })) } -// Reset resets a given bucket from a given bolt database -func (itf store) Reset(bucketName string) error { +// Delete implements the storage.Interface interface +func (itf store) Delete(key []byte, buckets ...[]byte) error { return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { - path := strings.Split(bucketName, ".") + bucket, err := getBucket(tx, buckets) + if err != nil { + return err + } + if err := bucket.Delete(key); err != nil { + return errors.New(errors.Operational, err) + } + return nil + })) +} +// Reset implements the storage.Interface interface +func (itf store) Reset(buckets ...[]byte) (err error) { + if len(buckets) == 0 { + return errors.New(errors.Structural, "Expected at least one bucket") + } + + return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { var cursor interface { DeleteBucket(name []byte) error CreateBucketIfNotExists(name []byte) (*bolt.Bucket, error) } - if len(path) == 1 { + init, last := buckets[:len(buckets)-1], buckets[len(buckets)-1] + + if len(init) == 0 { cursor = tx } else { - var err error - cursor, err = getBucket(tx, strings.Join(path[:len(path)-1], ".")) + cursor, err = getBucket(tx, init) if err != nil { return err } } - if err := cursor.DeleteBucket([]byte(path[len(path)-1])); err != nil { + if err := cursor.DeleteBucket(last); err != nil { return errors.New(errors.Operational, err) } - if _, err := cursor.CreateBucketIfNotExists([]byte(path[len(path)-1])); err != nil { + if _, err := cursor.CreateBucketIfNotExists(last); err != nil { return errors.New(errors.Operational, err) } return nil })) } -// Close terminates the db connection +// Close implements the storage.Interface interface func (itf store) Close() error { if err := itf.db.Close(); err != nil { return errors.New(errors.Operational, err) diff --git a/core/storage/storage_test.go b/core/storage/storage_test.go index 6e32760f8..0c3beccbb 100644 --- a/core/storage/storage_test.go +++ b/core/storage/storage_test.go @@ -4,19 +4,17 @@ package storage import ( + "encoding" "os" "path" - "reflect" "testing" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" . "github.com/TheThingsNetwork/ttn/utils/testing" ) const database = "TestStoreAndLookup.db" -func TestStoreAndLookup(t *testing.T) { +func TestStoreAndRead(t *testing.T) { var itf Interface defer func() { if itf == nil { @@ -42,132 +40,188 @@ func TestStoreAndLookup(t *testing.T) { { Desc(t, "Open database in a forbidden place") _, err := New("/usr/bin") - CheckErrors(t, pointer.String(string(errors.Operational)), err) + CheckErrors(t, ErrOperational, err) } // -------------------- { Desc(t, "Store then lookup in a 1-level bucket") - err := itf.Store("bucket", []byte{1, 2, 3}, []Entry{&testEntry{Data: "TTN"}}) - CheckErrors(t, nil, err) + err := itf.Append([]byte{1, 2, 3}, []encoding.BinaryMarshaler{testEntry{Data: "TTN"}}, []byte("bucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("bucket")) - entries, err := itf.Lookup("bucket", []byte{1, 2, 3}, &testEntry{}) CheckErrors(t, nil, err) - CheckEntries(t, []testEntry{testEntry{Data: "TTN"}}, entries) + Check(t, []testEntry{testEntry{Data: "TTN"}}, entries.([]testEntry), "Entries") } // ------------------- { Desc(t, "Store then lookup in a nested bucket") - err := itf.Store("nested.bucket", []byte{14, 42}, []Entry{&testEntry{Data: "IoT"}}) - CheckErrors(t, nil, err) + err := itf.Append([]byte{14, 42}, []encoding.BinaryMarshaler{&testEntry{Data: "IoT"}}, []byte("nested"), []byte("bucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{14, 42}, &testEntry{}, []byte("nested"), []byte("bucket")) - entries, err := itf.Lookup("nested.bucket", []byte{14, 42}, &testEntry{}) CheckErrors(t, nil, err) - CheckEntries(t, []testEntry{testEntry{Data: "IoT"}}, entries) + Check(t, []testEntry{testEntry{Data: "IoT"}}, entries.([]testEntry), "Entries") } // ------------------- { Desc(t, "Lookup in non-existing bucket") - entries, err := itf.Lookup("DoesntExist", []byte{1, 2, 3}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + entries, err := itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("DoesnExists")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Lookup a non-existing key") - entries, err := itf.Lookup("bucket", []byte{9, 9, 9, 9, 9}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + entries, err := itf.Read([]byte{9, 9, 9, 9, 9}, &testEntry{}, []byte("bucket")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Flush an 1-level bucket entry") - itf.Store("bucket", []byte{1, 1, 1}, []Entry{&testEntry{Data: "TTN"}}) - err := itf.Flush("bucket", []byte{1, 1, 1}) - CheckErrors(t, nil, err) - entries, err := itf.Lookup("bucket", []byte{1, 1, 1}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("bucket")) + err := itf.Delete([]byte{1, 1, 1}, []byte("bucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{1, 1, 1}, &testEntry{}, []byte("bucket")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Flush a nested bucket entry") - itf.Store("nested.bucket", []byte{2, 2, 2}, []Entry{&testEntry{Data: "TTN"}}) - err := itf.Flush("nested.bucket", []byte{2, 2, 2}) - CheckErrors(t, nil, err) - entries, err := itf.Lookup("nested.bucket", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + itf.Append([]byte{2, 2, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("nested"), []byte("bucket")) + err := itf.Delete([]byte{2, 2, 2}, []byte("nested"), []byte("bucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{2, 2, 2}, &testEntry{}, []byte("nested"), []byte("bucket")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Reset a 1-level bucket") - itf.Store("mybucket", []byte{1, 1, 1}, []Entry{&testEntry{Data: "TTN"}}) - err := itf.Reset("mybucket") - CheckErrors(t, nil, err) - entries, err := itf.Lookup("mybucket", []byte{1, 1, 1}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{testEntry{Data: "TTN"}}, []byte("mybucket")) + err := itf.Reset([]byte("mybucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{1, 1, 1}, &testEntry{}, []byte("mybucket")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Reset a nested bucket") - itf.Store("mybucket.nested", []byte{2, 2, 2}, []Entry{&testEntry{Data: "TTN"}}) - err := itf.Reset("mybucket.nested") - CheckErrors(t, nil, err) - entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + itf.Append([]byte{2, 2, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("mybucket"), []byte("nested")) + err := itf.Reset([]byte("mybucket"), []byte("nested")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{2, 2, 2}, &testEntry{}, []byte("mybucket"), []byte("nested")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Reset a nested bucket parent") - itf.Store("mybucket.nested", []byte{2, 2, 2}, []Entry{&testEntry{Data: "TTN"}}) - err := itf.Reset("mybucket") - CheckErrors(t, nil, err) - entries, err := itf.Lookup("mybucket.nested", []byte{2, 2, 2}, &testEntry{}) - CheckErrors(t, pointer.String(string(errors.NotFound)), err) - CheckEntries(t, nil, entries) + itf.Append([]byte{2, 2, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("mybucket"), []byte("nested")) + err := itf.Reset([]byte("mybucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{2, 2, 2}, &testEntry{}, []byte("mybucket"), []byte("nested")) + + CheckErrors(t, ErrNotFound, err) + Check(t, nil, entries, "Entries") } // ------------------- { Desc(t, "Replace an existing entry in a bucket") - itf.Store("anotherbucket", []byte{14, 14, 14}, []Entry{&testEntry{Data: "I don't like IoT"}}) - err := itf.Replace("anotherbucket", []byte{14, 14, 14}, []Entry{&testEntry{Data: "IoT is Awesome"}}) - CheckErrors(t, nil, err) - entries, err := itf.Lookup("anotherbucket", []byte{14, 14, 14}, &testEntry{}) + itf.Append([]byte{14, 14, 14}, []encoding.BinaryMarshaler{&testEntry{Data: "I don't like IoT"}}, []byte("anotherbucket")) + err := itf.Update([]byte{14, 14, 14}, []encoding.BinaryMarshaler{testEntry{Data: "IoT is Awesome"}}, []byte("anotherbucket")) + FatalUnless(t, err) + entries, err := itf.Read([]byte{14, 14, 14}, &testEntry{}, []byte("anotherbucket")) + CheckErrors(t, nil, err) - CheckEntries(t, []testEntry{{Data: "IoT is Awesome"}}, entries) + Check(t, []testEntry{{Data: "IoT is Awesome"}}, entries.([]testEntry), "Entries") } // ------------------- { Desc(t, "Store several entries under the same key") - itf.Store("several", []byte{1, 1, 1}, []Entry{&testEntry{Data: "FirstEntry"}}) - itf.Store("several", []byte{1, 1, 1}, []Entry{&testEntry{Data: "SecondEntry"}, &testEntry{Data: "ThirdEntry"}}) - entries, err := itf.Lookup("several", []byte{1, 1, 1}, &testEntry{}) + itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "FirstEntry"}}, []byte("several")) + itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "SecondEntry"}, &testEntry{Data: "ThirdEntry"}}, []byte("several")) + entries, err := itf.Read([]byte{1, 1, 1}, &testEntry{}, []byte("several")) + CheckErrors(t, nil, err) - CheckEntries(t, []testEntry{{Data: "FirstEntry"}, {Data: "SecondEntry"}, {Data: "ThirdEntry"}}, entries) + Check(t, []testEntry{{Data: "FirstEntry"}, {Data: "SecondEntry"}, {Data: "ThirdEntry"}}, entries.([]testEntry), "Entries") + } + + // -------------------- + + { + Desc(t, "Store, Read, Update and Delete with default key") + err := itf.Append(nil, []encoding.BinaryMarshaler{&testEntry{Data: "Patate"}}, []byte("defaultkeybucket")) + CheckErrors(t, nil, err) + _, err = itf.Read(nil, &testEntry{}, []byte("defaultkeybucket")) + CheckErrors(t, nil, err) + err = itf.Update(nil, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("defaultkeybucket")) + CheckErrors(t, nil, err) + err = itf.Delete(nil, []byte("defaultkeybucket")) + CheckErrors(t, nil, err) + } + + // -------------------- + + { + Desc(t, "Store, Read, Update, Delete & Reset with no bucket names") + err := itf.Append([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "Patate"}}) + CheckErrors(t, ErrStructural, err) + _, err = itf.Read([]byte{1, 2, 3}, &testEntry{}) + CheckErrors(t, ErrStructural, err) + err = itf.Update([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}) + CheckErrors(t, ErrStructural, err) + err = itf.Delete([]byte{1, 2, 3}) + CheckErrors(t, ErrStructural, err) + err = itf.Reset() + CheckErrors(t, ErrStructural, err) + } + + // --------------------- + + { + Desc(t, "Store, Read, Update, Delete & Reset on closed storage") + _ = itf.Close() + err := itf.Append([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "Patate"}}, []byte("closeddb")) + CheckErrors(t, ErrOperational, err) + _, err = itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("closeddb")) + CheckErrors(t, ErrOperational, err) + err = itf.Update([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("closeddb")) + CheckErrors(t, ErrOperational, err) + err = itf.Delete([]byte{1, 2, 3}, []byte("closeddb")) + CheckErrors(t, ErrOperational, err) + err = itf.Reset([]byte("closeddb")) + CheckErrors(t, ErrOperational, err) } } @@ -185,24 +239,3 @@ func (e *testEntry) UnmarshalBinary(data []byte) error { e.Data = string(data) return nil } - -// ----- Check Utilities - -func CheckEntries(t *testing.T, want []testEntry, got interface{}) { - if want == nil && got == nil { - Ok(t, "Check Entries") - return - } - - entries, ok := got.([]testEntry) - if !ok { - Ko(t, "Expected []testEntry but got %+v", got) - return - } - - if !reflect.DeepEqual(want, entries) { - Ko(t, "Retrieved entries don't match expectations.\nWant: %v\nGot: %v", want, entries) - return - } - Ok(t, "Check Entries") -} From bef8c3f54f57e44a7370bac744ff4f8bcbca070a Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 17:24:37 +0100 Subject: [PATCH 1120/2266] [refactor/storages] Make dutycycle compliant with new storage --- core/dutycycle/dutyManager.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go index 22dce08b4..f5941c496 100644 --- a/core/dutycycle/dutyManager.go +++ b/core/dutycycle/dutyManager.go @@ -4,6 +4,7 @@ package dutycycle import ( + "encoding" "encoding/json" "fmt" "math" @@ -64,6 +65,8 @@ const ( type region byte +var bucket = []byte("cycles") + // GetSubBand returns the subband associated to a given frequency func GetSubBand(freq float32) (subBand, error) { // g 865.0 – 868.0 MHz 1% or LBT+AFA, 25 mW (=14dBm) @@ -122,7 +125,6 @@ func NewManager(filepath string, cycleLength time.Duration, r region) (DutyManag return &dutyManager{ db: db, - bucket: "cycles", CycleLength: cycleLength, MaxDutyCycle: maxDuty, }, nil @@ -148,7 +150,7 @@ func (m *dutyManager) Update(id []byte, freq float32, size uint32, datr string, // Lookup and update the entry m.Lock() defer m.Unlock() - itf, err := m.db.Lookup(m.bucket, id, &dutyEntry{}) + itf, err := m.db.Read(id, &dutyEntry{}, bucket) var entry dutyEntry if err == nil { @@ -170,7 +172,7 @@ func (m *dutyManager) Update(id []byte, freq float32, size uint32, datr string, entry.OnAir[sub] += timeOnAir } - return m.db.Replace(m.bucket, id, []dbutil.Entry{&entry}) + return m.db.Update(id, []encoding.BinaryMarshaler{&entry}, bucket) } // Lookup returns the current bandwidth usages for a set of subband @@ -182,7 +184,7 @@ func (m *dutyManager) Lookup(id []byte) (Cycles, error) { defer m.RUnlock() // Lookup the entry - itf, err := m.db.Lookup(m.bucket, id, &dutyEntry{}) + itf, err := m.db.Read(id, &dutyEntry{}, bucket) if err != nil { return nil, err } From e3e13e77d8dcb7aefc766efcdc0c3604461e0d7d Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 18:33:01 +0100 Subject: [PATCH 1121/2266] [refactor/storages] Rework handler storages --- core/components/handler/devStorage.go | 118 ++++++-------------- core/components/handler/pktStorage.go | 148 ++++++++++++++++++-------- 2 files changed, 136 insertions(+), 130 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 0c88a9a8f..a6c95ba78 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -4,22 +4,19 @@ package handler import ( - "bytes" + "encoding" "encoding/binary" - "fmt" - "sync" dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" ) // DevStorage gives a facade to manipulate the handler devices database type DevStorage interface { - UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error - Lookup(appEUI []byte, devEUI []byte) (devEntry, error) - StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error - Store(entry devEntry) error - Close() error + read(appEUI []byte, devEUI []byte) (devEntry, error) + upsert(entry devEntry) error + done() error } const dbDevices = "devices" @@ -35,7 +32,6 @@ type devEntry struct { } type devStorage struct { - sync.RWMutex db dbutil.Interface } @@ -49,20 +45,11 @@ func NewDevStorage(name string) (DevStorage, error) { return &devStorage{db: itf}, nil } -// Lookup implements the handler.DevStorage interface -func (s *devStorage) Lookup(appEUI []byte, devEUI []byte) (devEntry, error) { - return s.lookup(appEUI, devEUI, true) -} - -// lookup allow other method to re-use lookup while holding the lock -func (s *devStorage) lookup(appEUI []byte, devEUI []byte, shouldLock bool) (devEntry, error) { - if shouldLock { - s.RLock() - defer s.RUnlock() - } - itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), &devEntry{}) +// read implements the handler.DevStorage interface +func (s *devStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { + itf, err := s.db.Read(nil, &devEntry{}, appEUI, devEUI) if err != nil { - return devEntry{}, err // Operational || NotFound + return devEntry{}, err } entries, ok := itf.([]devEntry) if !ok || len(entries) != 1 { @@ -71,79 +58,38 @@ func (s *devStorage) lookup(appEUI []byte, devEUI []byte, shouldLock bool) (devE return entries[0], nil } -// StorePersonalized implements the handler.DevStorage interface -func (s *devStorage) StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error { - devEUI := make([]byte, 8, 8) - copy(devEUI[4:], devAddr) - e := []dbutil.Entry{ - &devEntry{ - AppSKey: appSKey, - NwkSKey: nwkSKey, - DevAddr: devAddr, - }, - } - s.Lock() - defer s.Unlock() - return s.db.Replace(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), e) +// upsert implements the handler.DevStorage interface +func (s *devStorage) upsert(entry devEntry) error { + return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) } -// Store implements the handler.DevStorage interface -func (s *devStorage) Store(entry devEntry) error { - s.Lock() - defer s.Unlock() - return s.db.Replace(fmt.Sprintf("%x.%x", entry.AppEUI, entry.DevEUI), []byte(dbDevices), []dbutil.Entry{&entry}) -} - -// UpdateFCnt implements the handler.DevStorage interface -func (s *devStorage) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { - s.Lock() - defer s.Unlock() - devEntry, err := s.lookup(appEUI, devEUI, false) - if err != nil { - return err - } - devEntry.FCntDown = fcnt - return s.db.Replace(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte(dbDevices), []dbutil.Entry{&devEntry}) -} - -// Close implements the handler.DevStorage interface -func (s *devStorage) Close() error { +// done implements the handler.DevStorage interface +func (s *devStorage) done() error { return s.db.Close() } // MarshalBinary implements the encoding.BinaryMarshaler interface func (e devEntry) MarshalBinary() ([]byte, error) { - buf, cnt := new(bytes.Buffer), 52 - binary.Write(buf, binary.BigEndian, e.AppKey[:]) // 16 - binary.Write(buf, binary.BigEndian, e.AppSKey[:]) // 16 - binary.Write(buf, binary.BigEndian, e.NwkSKey[:]) // 16 - binary.Write(buf, binary.BigEndian, e.FCntDown) // 4 - in := [][]byte{e.AppEUI, e.DevEUI, e.DevAddr} - for _, d := range in { - binary.Write(buf, binary.BigEndian, uint16(len(d))) - binary.Write(buf, binary.BigEndian, d) - cnt += len(d) + 2 - } - if len(buf.Bytes()) != cnt { - return nil, errors.New(errors.Structural, "DevEntry was invalid, unable to marshal") - } - return buf.Bytes(), nil + rw := readwriter.New(nil) + rw.Write(e.AppKey[:]) + rw.Write(e.AppSKey[:]) + rw.Write(e.NwkSKey[:]) + rw.Write(e.FCntDown) + rw.Write(e.AppEUI) + rw.Write(e.DevEUI) + rw.Write(e.DevAddr) + return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devEntry) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - binary.Read(buf, binary.BigEndian, &e.AppKey) - binary.Read(buf, binary.BigEndian, &e.AppSKey) - binary.Read(buf, binary.BigEndian, &e.NwkSKey) - binary.Read(buf, binary.BigEndian, &e.FCntDown) - var size *uint16 - in := []*[]byte{&e.AppEUI, &e.DevEUI, &e.DevAddr} - for _, p := range in { - size = new(uint16) - binary.Read(buf, binary.BigEndian, size) - *p = make([]byte, *size, *size) - binary.Read(buf, binary.BigEndian, p) - } - return nil + rw := readwriter.New(data) + rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) + rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) + rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) + rw.Read(func(data []byte) { e.AppEUI = data }) + rw.Read(func(data []byte) { e.DevEUI = data }) + rw.Read(func(data []byte) { e.DevAddr = data }) + return rw.Err() } diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 1a7ac0a80..74c7e4de0 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -4,95 +4,155 @@ package handler import ( - "fmt" + "encoding" + "sync" + "time" dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" ) // PktStorage gives a facade to manipulate the handler packets database type PktStorage interface { - Push(appEUI []byte, devEUI []byte, payload pktEntry) error - Pull(appEUI []byte, devEUI []byte) (pktEntry, error) - Close() error + enqueue(entry pktEntry) error + dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) + peek(appEUI []byte, devEUI []byte) (pktEntry, error) + done() error } const dbPackets = "packets" type pktStorage struct { - db dbutil.Interface + sync.RWMutex + size uint + db dbutil.Interface } type pktEntry struct { + AppEUI []byte + DevEUI []byte Payload []byte + TTL time.Time } // NewPktStorage creates a new PktStorage -func NewPktStorage(name string) (PktStorage, error) { +func NewPktStorage(name string, size uint) (PktStorage, error) { itf, err := dbutil.New(name) if err != nil { return nil, errors.New(errors.Operational, err) } - return pktStorage{db: itf}, nil + return &pktStorage{db: itf, size: size}, nil } -// Push implements the PktStorage interface -func (s pktStorage) Push(appEUI, devEUI []byte, payload pktEntry) error { - return s.db.Store(dbPackets, append(appEUI, devEUI...), []dbutil.Entry{&payload}) +func filterExpired(entries []pktEntry) []pktEntry { + var filtered []pktEntry + now := time.Now() + for _, e := range entries { + if e.TTL.Before(now) { + filtered = append(filtered, e) + } + } + return filtered } -// Pull implements the PktStorage interface -func (s pktStorage) Pull(appEUI, devEUI []byte) (pktEntry, error) { - key := append(appEUI, devEUI...) - entries, err := s.db.Lookup(dbPackets, key, &pktEntry{}) - if err != nil { - return pktEntry{}, err // Operational || NotFound +func pop(entries []pktEntry) (pktEntry, []encoding.BinaryMarshaler) { + head := entries[0] + var tail []encoding.BinaryMarshaler + for _, e := range entries[1:] { + tail = append(tail, e) } + return head, tail +} - payloads, ok := entries.([]pktEntry) - if !ok { - return pktEntry{}, errors.New(errors.Operational, "Unable to retrieve data from db") +// enqueue implements the PktStorage interface +func (s *pktStorage) enqueue(entry pktEntry) error { + s.Lock() + defer s.Unlock() + itf, err := s.db.Read(nil, &pktEntry{}, entry.AppEUI, entry.DevEUI) + if err != nil { + return err } - - // NOTE: one day, those entries will be more complicated, with a ttl. - // Here's the place where we should check for that. Cheers. - if len(payloads) == 0 { - return pktEntry{}, errors.New(errors.NotFound, fmt.Sprintf("Entry not found for %v", key)) + entries := filterExpired(itf.([]pktEntry)) + if len(entries) >= int(s.size) { + _, tail := pop(entries) + return s.db.Update(nil, append(tail, entry), entry.AppEUI, entry.DevEUI) } + // NOTE: We append, even if there're still expired entries, we'll filter them + // during dequeuing + return s.db.Append(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) +} - head := new(pktEntry) - _ = head.UnmarshalBinary(payloads[0].Payload) - - var tail []dbutil.Entry - for _, p := range payloads[1:] { - t := new(pktEntry) - *t = p - tail = append(tail, t) +// dequeue implements the PktStorage interface +func (s *pktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { + s.Lock() + defer s.Unlock() + itf, err := s.db.Read(nil, &pktEntry{}, appEUI, devEUI) + if err != nil { + return pktEntry{}, err } - - if err := s.db.Replace(dbPackets, key, tail); err != nil { - if err := s.db.Replace(dbPackets, key, tail); err != nil { - // TODO This is critical... we've just lost a packet - return pktEntry{}, errors.New(errors.Operational, "Unable to restore data in db") + entries := itf.([]pktEntry) + filtered := filterExpired(entries) + + if len(filtered) < 1 { // No entry left, return NotFound + if len(entries) != len(filtered) { // Get rid of expired entries + var replaces []encoding.BinaryMarshaler + for _, e := range filtered { + replaces = append(replaces, e) + } + _ = s.db.Update(nil, replaces, appEUI, devEUI) } + return pktEntry{}, errors.New(errors.NotFound, "There's no available entry") } - return *head, nil + // Otherwise dequeue the first one and send it + head, tail := pop(filtered) + if err := s.db.Update(nil, tail, appEUI, devEUI); err != nil { + return pktEntry{}, err + } + return head, nil } -// Close implements the PktStorage interface -func (s pktStorage) Close() error { +// peek implements the PktStorage interface +func (s *pktStorage) peek(appEUI []byte, devEUI []byte) (pktEntry, error) { + s.RLock() + defer s.RUnlock() + itf, err := s.db.Read(nil, &pktEntry{}, appEUI, devEUI) + if err != nil { + return pktEntry{}, err + } + entries := filterExpired(itf.([]pktEntry)) + if len(entries) < 1 { + return pktEntry{}, errors.New(errors.NotFound, "There's no available entry") + } + return entries[0], nil +} + +// done implements the PktStorage interface +func (s *pktStorage) done() error { return s.db.Close() } // MarshalBinary implements the encoding.BinaryMarshaler interface func (e pktEntry) MarshalBinary() ([]byte, error) { - return e.Payload, nil + data, err := e.TTL.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + rw := readwriter.New(nil) + rw.Write(e.AppEUI) + rw.Write(e.DevEUI) + rw.Write(e.Payload) + rw.Write(data) + return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *pktEntry) UnmarshalBinary(data []byte) error { - e.Payload = make([]byte, len(data)) - copy(e.Payload, data) - return nil + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.AppEUI = data }) + rw.Read(func(data []byte) { e.DevEUI = data }) + rw.Read(func(data []byte) { e.Payload = data }) + rw.TryRead(func(data []byte) error { return e.TTL.UnmarshalBinary(data) }) + return rw.Err() } From 16fd742cb8d9dc526846b353cdb952a80495c798 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 18:40:07 +0100 Subject: [PATCH 1122/2266] [refactor/storages] Update mocks for handler storages --- core/components/handler/mocks_test.go | 116 +++++++++++--------------- 1 file changed, 49 insertions(+), 67 deletions(-) diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 45751ffcc..7fc71e1f5 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -7,29 +7,18 @@ package handler // MockDevStorage mocks the DevStorage interface type MockDevStorage struct { - Failures map[string]error - InUpdateFCnt struct { - AppEUI []byte - DevEUI []byte - FCnt uint32 - } - InLookup struct { + Failures map[string]error + InRead struct { AppEUI []byte DevEUI []byte } - OutLookup struct { + OutRead struct { Entry devEntry } - InStorePersonalized struct { - AppEUI []byte - DevAddr []byte - AppSKey [16]byte - NwkSKey [16]byte - } - InStore struct { + InUpsert struct { Entry devEntry } - InClose struct { + InDone struct { Called bool } } @@ -41,56 +30,44 @@ func NewMockDevStorage() *MockDevStorage { } } -// UpdateFCnt implements the DevStorage interface -func (m *MockDevStorage) UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error { - m.InUpdateFCnt.AppEUI = appEUI - m.InUpdateFCnt.DevEUI = devEUI - m.InUpdateFCnt.FCnt = fcnt - return m.Failures["UpdateFCnt"] -} - -// Lookup implements the DevStorage interface -func (m *MockDevStorage) Lookup(appEUI []byte, devEUI []byte) (devEntry, error) { - m.InLookup.AppEUI = appEUI - m.InLookup.DevEUI = devEUI - return m.OutLookup.Entry, m.Failures["Lookup"] -} - -// StorePersonalized implements the DevStorage interface -func (m *MockDevStorage) StorePersonalized(appEUI []byte, devAddr []byte, appSKey, nwkSKey [16]byte) error { - m.InStorePersonalized.AppEUI = appEUI - m.InStorePersonalized.DevAddr = devAddr - m.InStorePersonalized.AppSKey = appSKey - m.InStorePersonalized.NwkSKey = nwkSKey - return m.Failures["StorePersonalized"] +// read implements the DevStorage interface +func (m *MockDevStorage) read(appEUI []byte, devEUI []byte) error { + m.InRead.AppEUI = appEUI + m.InRead.DevEUI = devEUI + return m.Failures["read"] } -// Store implements the DevStorage interface -func (m *MockDevStorage) Store(entry devEntry) error { - m.InStore.Entry = entry - return m.Failures["Store"] +// upsert implements the DevStorage interface +func (m *MockDevStorage) upsert(entry devEntry) error { + m.InUpsert.Entry = entry + return m.Failures["upsert"] } -// Close implements the DevStorage Interface -func (m *MockDevStorage) Close() error { - m.InClose.Called = true - return m.Failures["Close"] +// done implements the DevStorage Interface +func (m *MockDevStorage) donw() error { + m.InDone.Called = true + return m.Failures["done"] } // MockPktStorage mocks the PktStorage interface type MockPktStorage struct { - Failures map[string]error - InPull struct { + Failures map[string]error + InDequeue struct { AppEUI []byte DevEUI []byte } - OutPull struct { + OutDequeue struct { + Entry pktEntry + } + InPeek struct { + AppEUI []byte + DevEUI []byte + } + OutPeek struct { Entry pktEntry } InPush struct { - AppEUI []byte - DevEUI []byte - Payload pktEntry + Entry pktEntry } InClose struct { Called bool @@ -104,23 +81,28 @@ func NewMockPktStorage() *MockPktStorage { } } -// Close implements the PktStorage Interface -func (m *MockPktStorage) Close() error { - m.InClose.Called = true - return m.Failures["Close"] +// done implements the PktStorage Interface +func (m *MockPktStorage) done() error { + m.InDonee.Called = true + return m.Failures["done"] +} + +// enqueue implements the PktStorage interface +func (m *MockPktStorage) enqueue(entry pktEntry) error { + m.InEnqueue.Entry = entry + return m.Failures["enqueue"] } -// Push implements the PktStorage interface -func (m *MockPktStorage) Push(appEUI []byte, devEUI []byte, payload pktEntry) error { - m.InPush.AppEUI = appEUI - m.InPush.DevEUI = devEUI - m.InPush.Payload = payload - return m.Failures["Push"] +// dequeue implements the PktStorage interface +func (m *MockPktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { + m.InDequeue.AppEUI = appEUI + m.InDequeue.DevEUI = devEUI + return m.OutDequeue.Entry, m.Failures["dequeue"] } -// Push implements the PktStorage interface -func (m *MockPktStorage) Pull(appEUI []byte, devEUI []byte) (pktEntry, error) { - m.InPull.AppEUI = appEUI - m.InPull.DevEUI = devEUI - return m.OutPull.Entry, m.Failures["Pull"] +// peek implements the PktStorage interface +func (m *MockPktStorage) peek(appEUI []byte, devEUI []byte) (pktEntry, error) { + m.InPeek.AppEUI = appEUI + m.InPeek.DevEUI = devEUI + return m.OutPeek.Entry, m.Failures["peek"] } From c4d99b20f5bde93aa50a59b21722dd2cd0783464 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 18:59:55 +0100 Subject: [PATCH 1123/2266] [refactor/storages] Change related calls in handler --- core/components/handler/handler.go | 76 ++++++++---------------------- 1 file changed, 20 insertions(+), 56 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index af5e4f442..49982ed2e 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/aes" "encoding/binary" + "fmt" "math/rand" "net" "reflect" @@ -123,56 +124,6 @@ func (h *component) Start() error { return nil } -// RegisterPersonalized implements the core.HandlerServer interface -func (h component) SubscribePersonalized(bctx context.Context, req *core.ABPSubHandlerReq) (*core.ABPSubHandlerRes, error) { - h.Ctx.Debug("New personalized subscription request") - stats.MarkMeter("handler.registration.in") - - if len(req.AppEUI) != 8 { - stats.MarkMeter("handler.registration.invalid") - return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Application EUI") - } - - if len(req.DevAddr) != 4 { - stats.MarkMeter("handler.registration.invalid") - return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Device Address") - } - - if len(req.NwkSKey) != 16 { - stats.MarkMeter("handler.registration.invalid") - return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Network Session Key") - } - var nwkSKey [16]byte - copy(nwkSKey[:], req.NwkSKey) - - if len(req.AppSKey) != 16 { - stats.MarkMeter("handler.registration.invalid") - return new(core.ABPSubHandlerRes), errors.New(errors.Structural, "Invalid Application Session Key") - } - var appSKey [16]byte - copy(appSKey[:], req.AppSKey) - - h.Ctx.Debug("Registration is valid. Saving and forwarding to broker") - - if err := h.DevStorage.StorePersonalized(req.AppEUI, req.DevAddr, appSKey, nwkSKey); err != nil { - h.Ctx.WithError(err).Debug("Unable to store registration") - return new(core.ABPSubHandlerRes), errors.New(errors.Operational, err) - } - - _, err := h.Broker.SubscribePersonalized(context.Background(), &core.ABPSubBrokerReq{ - HandlerNet: h.NetAddr, - AppEUI: req.AppEUI, - DevAddr: req.DevAddr, - NwkSKey: req.NwkSKey, - }) - - if err != nil { - h.Ctx.WithError(err).Debug("Unable to forward registration") - return new(core.ABPSubHandlerRes), errors.New(errors.Operational, err) - } - return new(core.ABPSubHandlerRes), nil -} - // HandleJoin implements the core.HandlerServer interface func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (*core.JoinHandlerRes, error) { stats.MarkMeter("handler.joinrequest.in") @@ -185,7 +136,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* // 1. Lookup for the associated AppKey h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") - entry, err := h.DevStorage.Lookup(req.AppEUI, req.DevEUI) + entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) if err != nil { return new(core.JoinHandlerRes), err } @@ -252,8 +203,19 @@ func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandle return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid payload") } + ttl, err := time.ParseDuration(fmt.Sprintf("%dh", req.TTL)) + if req.TTL == 0 || err != nil { + stats.MarkMeter("handler.downlink.invalid") + return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid TTL") + } + h.Ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") - return new(core.DataDownHandlerRes), h.PktStorage.Push(req.AppEUI, req.DevEUI, pktEntry{Payload: req.Payload}) + return new(core.DataDownHandlerRes), h.PktStorage.enqueue(pktEntry{ + Payload: req.Payload, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + TTL: time.Now().Add(ttl), + }) } // HandleDataUp implements the core.HandlerServer interface @@ -281,7 +243,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // 1. Lookup for the associated AppSKey + Application h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") - entry, err := h.DevStorage.Lookup(req.AppEUI, req.DevEUI) + entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) if err != nil { return new(core.DataUpHandlerRes), err } @@ -473,7 +435,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da block.Encrypt(appSKey[:], buf) // Update the internal storage entry - err = h.DevStorage.Store(devEntry{ + err = h.DevStorage.upsert(devEntry{ AppEUI: appEUI, AppKey: appKey, AppSKey: appSKey, @@ -583,7 +545,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu h.Ctx.WithField("Bundle", best).Debug("Determine best gateway") var downlink pktEntry if best != nil { // Avoid pulling when there's no gateway available for an answer - downlink, err = h.PktStorage.Pull(appEUI, devEUI) + downlink, err = h.PktStorage.dequeue(appEUI, devEUI) } if err != nil && err.(errors.Failure).Nature != errors.NotFound { h.abortConsume(err, bundles) @@ -600,7 +562,9 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu h.abortConsume(errors.New(errors.Structural, err), bundles) return } - err = h.DevStorage.UpdateFCnt(appEUI, devEUI, downlink.Payload.MACPayload.FHDR.FCnt) + + bundle.Entry.FCntDown = downlink.Payload.MACPayload.FHDR.FCnt + err = h.DevStorage.upsert(bundle.Entry) if err != nil { h.abortConsume(err, bundles) return From fb8c787a89f95af20b435dd2e2fb55d2bfa4a22f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 21 Mar 2016 20:10:52 +0100 Subject: [PATCH 1124/2266] Add Makefile --- Makefile | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..309463515 --- /dev/null +++ b/Makefile @@ -0,0 +1,86 @@ +SHELL = bash + +GOOS ?= $(shell echo "`go env GOOS`") +GOARCH ?= $(shell echo "`go env GOARCH`") +GOEXE ?= $(shell echo "`go env GOEXE`") + +GOCMD = GOOS=$(GOOS) GOARCH=$(GOARCH) go + +export CGO_ENABLED=0 +GOBUILD = $(GOCMD) build + +GIT_COMMIT = `git rev-parse HEAD 2>/dev/null` +BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` + +LDFLAGS = -ldflags "-w -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" + +DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .Imports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` +TEST_DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .TestImports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` + +select_pkgs = $(GOCMD) list ./... | grep -vE 'vendor' + +RELEASE_DIR ?= release + +ttnpkg = ttn-$(GOOS)-$(GOARCH) +ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) + +ttnbin = $(ttnpkg)$(GOEXE) +ttnctlbin = $(ttnctlpkg)$(GOEXE) + +.PHONY: all clean deps test-deps test fmt vet cover build docker package + +all: clean deps build package + +deps: + $(GOCMD) get -v -u $(DEPS) + +test-deps: + $(GOCMD) get -v -u $(TEST_DEPS) + +test: + $(select_pkgs) | xargs $(GOCMD) test + +fmt: + [[ -z "`$(select_pkgs) | xargs $(GOCMD) fmt | tee -a /dev/stderr`" ]] + +vet: + $(select_pkgs) | xargs $(GOCMD) vet + +cover: + $(error Still have to add this to Makefile) + +clean: + rm -rf $(RELEASE_DIR) + +build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) + +docker: GOOS = linux +docker: GOARCH = amd64 +docker: clean $(RELEASE_DIR)/$(ttnbin) + docker build -t thethingsnetwork/ttn -f Dockerfile.local . + +package: $(RELEASE_DIR)/$(ttnpkg).zip $(RELEASE_DIR)/$(ttnpkg).tar.gz $(RELEASE_DIR)/$(ttnpkg).tar.xz $(RELEASE_DIR)/$(ttnctlpkg).zip $(RELEASE_DIR)/$(ttnctlpkg).tar.gz $(RELEASE_DIR)/$(ttnctlpkg).tar.xz + +$(RELEASE_DIR)/$(ttnbin): + $(GOBUILD) -a -installsuffix cgo ${LDFLAGS} -o $(RELEASE_DIR)/$(ttnbin) ./main.go + +$(RELEASE_DIR)/$(ttnpkg).zip: $(RELEASE_DIR)/$(ttnbin) + cd $(RELEASE_DIR) && zip -q $(ttnpkg).zip $(ttnbin) + +$(RELEASE_DIR)/$(ttnpkg).tar.gz: $(RELEASE_DIR)/$(ttnbin) + cd $(RELEASE_DIR) && tar -czf $(ttnpkg).tar.gz $(ttnbin) + +$(RELEASE_DIR)/$(ttnpkg).tar.xz: $(RELEASE_DIR)/$(ttnbin) + cd $(RELEASE_DIR) && tar -cJf $(ttnpkg).tar.xz $(ttnbin) + +$(RELEASE_DIR)/$(ttnctlbin): + $(GOBUILD) -a -installsuffix cgo ${LDFLAGS} -o $(RELEASE_DIR)/$(ttnctlbin) ./ttnctl/main.go + +$(RELEASE_DIR)/$(ttnctlpkg).zip: $(RELEASE_DIR)/$(ttnctlbin) + cd $(RELEASE_DIR) && zip -q $(ttnctlpkg).zip $(ttnctlbin) + +$(RELEASE_DIR)/$(ttnctlpkg).tar.gz: $(RELEASE_DIR)/$(ttnctlbin) + cd $(RELEASE_DIR) && tar -czf $(ttnctlpkg).tar.gz $(ttnctlbin) + +$(RELEASE_DIR)/$(ttnctlpkg).tar.xz: $(RELEASE_DIR)/$(ttnctlbin) + cd $(RELEASE_DIR) && tar -cJf $(ttnctlpkg).tar.xz $(ttnctlbin) From d17e9a91180df18d1d8fc2bf099a64027ca36ccd Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 23:14:20 +0100 Subject: [PATCH 1125/2266] [refactor/storages] Write tests for devStorage and pktStorage --- core/broker.pb.go | 2 - core/components/handler/devStorage.go | 15 +- core/components/handler/devStorage_test.go | 124 ++---- core/components/handler/mocks_test.go | 12 +- core/components/handler/pktStorage.go | 24 +- core/components/handler/pktStorage_test.go | 193 ++++++--- core/handler.pb.go | 446 +++------------------ core/mocks/mocks.go | 30 -- core/msgp_gen.go | 6 +- core/protos/handler.proto | 11 +- core/storage/storage_test.go | 12 + 11 files changed, 272 insertions(+), 603 deletions(-) diff --git a/core/broker.pb.go b/core/broker.pb.go index 0b274b1fb..74a653837 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -30,8 +30,6 @@ DataUpHandlerRes DataDownHandlerReq DataDownHandlerRes - ABPSubHandlerReq - ABPSubHandlerRes JoinHandlerReq JoinHandlerRes DataRouterReq diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index a6c95ba78..64b6ec995 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -88,8 +88,17 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.AppEUI = data }) - rw.Read(func(data []byte) { e.DevEUI = data }) - rw.Read(func(data []byte) { e.DevAddr = data }) + rw.Read(func(data []byte) { + e.AppEUI = make([]byte, len(data)) + copy(e.AppEUI, data) + }) + rw.Read(func(data []byte) { + e.DevEUI = make([]byte, len(data)) + copy(e.DevEUI, data) + }) + rw.Read(func(data []byte) { + e.DevAddr = make([]byte, len(data)) + copy(e.DevAddr, data) + }) return rw.Err() } diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index e87dfc5ac..fe4dc00a6 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -13,12 +13,7 @@ import ( const devDB = "TestDevStorage.db" -//UpdateFCnt(appEUI []byte, devEUI []byte, fcnt uint32) error -//Lookup(appEUI []byte, devEUI []byte) (devEntry, error) -//StorePersonalized(appEUI []byte, devAddr [4]byte, appSKey, nwkSKey [16]byte) error -//Close() error - -func TestLookupStore(t *testing.T) { +func TestReadStore(t *testing.T) { var db DevStorage defer func() { os.Remove(path.Join(os.TempDir(), devDB)) @@ -36,45 +31,38 @@ func TestLookupStore(t *testing.T) { // ------------------ { - Desc(t, "Store and Lookup a registration") + Desc(t, "Store and read a registration") // Build - appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} - devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - devAddr := []byte{1, 2, 3, 4} - appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} - nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - // Expect - var want = devEntry{ - DevAddr: devAddr, - AppSKey: appSKey, - NwkSKey: nwkSKey, - AppEUI: make([]byte, 0, 0), - DevEUI: make([]byte, 0, 0), + entry := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + DevAddr: []byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, } // Operate - err := db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + err := db.upsert(entry) FatalUnless(t, err) - entry, err := db.Lookup(appEUI, devEUI) + got, err := db.read(entry.AppEUI, entry.DevEUI) // Check CheckErrors(t, nil, err) - Check(t, want, entry, "Device Entries") + Check(t, entry, got, "Device Entries") } // ------------------ { - Desc(t, "Lookup a non-existing registration") + Desc(t, "read a non-existing registration") // Build appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} // Operate - _, err := db.Lookup(appEUI, devEUI) + _, err := db.read(appEUI, devEUI) // Check CheckErrors(t, ErrNotFound, err) @@ -86,31 +74,24 @@ func TestLookupStore(t *testing.T) { Desc(t, "Store twice the same registration") // Build - appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 9} - devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - devAddr := []byte{1, 2, 3, 4} - appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} - nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} - - // Expect - var want = devEntry{ - DevAddr: devAddr, - AppSKey: appSKey, - NwkSKey: nwkSKey, - AppEUI: make([]byte, 0, 0), - DevEUI: make([]byte, 0, 0), + entry := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 9}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + DevAddr: []byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, } // Operate - err := db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + err := db.upsert(entry) FatalUnless(t, err) - err = db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + err = db.upsert(entry) FatalUnless(t, err) - entry, err := db.Lookup(appEUI, devEUI) + got, err := db.read(entry.AppEUI, entry.DevEUI) // Check CheckErrors(t, nil, err) - Check(t, want, entry, "Device Entries") + Check(t, entry, got, "Device Entries") } // ------------------ @@ -119,57 +100,40 @@ func TestLookupStore(t *testing.T) { Desc(t, "Update FCnt") // Build - appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 14} - devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - devAddr := []byte{1, 2, 3, 4} - appSKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} - nwkSKey := [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} - fcnt := uint32(2) - - // Expect - var want = devEntry{ - DevAddr: devAddr, - AppSKey: appSKey, - NwkSKey: nwkSKey, - FCntDown: fcnt, - AppEUI: make([]byte, 0, 0), - DevEUI: make([]byte, 0, 0), + entry := devEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 14}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + DevAddr: []byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 2, + } + update := devEntry{ + AppEUI: entry.AppEUI, + DevEUI: entry.DevEUI, + DevAddr: entry.DevAddr, + AppSKey: entry.AppSKey, + NwkSKey: entry.NwkSKey, + FCntDown: 14, } // Operate - err := db.StorePersonalized(appEUI, devAddr, appSKey, nwkSKey) + err := db.upsert(entry) FatalUnless(t, err) - err = db.UpdateFCnt(appEUI, devEUI, fcnt) - entry, errLookup := db.Lookup(appEUI, devEUI) - FatalUnless(t, errLookup) + err = db.upsert(update) + got, errRead := db.read(entry.AppEUI, entry.DevEUI) + FatalUnless(t, errRead) // Check CheckErrors(t, nil, err) - Check(t, want, entry, "Device Entries") - } - - // ------------------ - - { - Desc(t, "Update FCnt, device not found") - - // Build - appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 15} - devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - fcnt := uint32(2) - - // Operate - err := db.UpdateFCnt(appEUI, devEUI, fcnt) - - // Check - CheckErrors(t, ErrNotFound, err) + Check(t, update, got, "Device Entries") } // ------------------ { Desc(t, "Close the storage") - err := db.Close() + err := db.done() CheckErrors(t, nil, err) } } diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 7fc71e1f5..6b64e0b34 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -31,10 +31,10 @@ func NewMockDevStorage() *MockDevStorage { } // read implements the DevStorage interface -func (m *MockDevStorage) read(appEUI []byte, devEUI []byte) error { +func (m *MockDevStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { m.InRead.AppEUI = appEUI m.InRead.DevEUI = devEUI - return m.Failures["read"] + return m.OutRead.Entry, m.Failures["read"] } // upsert implements the DevStorage interface @@ -44,7 +44,7 @@ func (m *MockDevStorage) upsert(entry devEntry) error { } // done implements the DevStorage Interface -func (m *MockDevStorage) donw() error { +func (m *MockDevStorage) done() error { m.InDone.Called = true return m.Failures["done"] } @@ -66,10 +66,10 @@ type MockPktStorage struct { OutPeek struct { Entry pktEntry } - InPush struct { + InEnqueue struct { Entry pktEntry } - InClose struct { + InDone struct { Called bool } } @@ -83,7 +83,7 @@ func NewMockPktStorage() *MockPktStorage { // done implements the PktStorage Interface func (m *MockPktStorage) done() error { - m.InDonee.Called = true + m.InDone.Called = true return m.Failures["done"] } diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 74c7e4de0..98e7ded69 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -49,7 +49,7 @@ func filterExpired(entries []pktEntry) []pktEntry { var filtered []pktEntry now := time.Now() for _, e := range entries { - if e.TTL.Before(now) { + if e.TTL.After(now) { filtered = append(filtered, e) } } @@ -70,10 +70,13 @@ func (s *pktStorage) enqueue(entry pktEntry) error { s.Lock() defer s.Unlock() itf, err := s.db.Read(nil, &pktEntry{}, entry.AppEUI, entry.DevEUI) - if err != nil { + if err != nil && err.(errors.Failure).Nature != errors.NotFound { return err } - entries := filterExpired(itf.([]pktEntry)) + var entries []pktEntry + if itf != nil { + entries = filterExpired(itf.([]pktEntry)) + } if len(entries) >= int(s.size) { _, tail := pop(entries) return s.db.Update(nil, append(tail, entry), entry.AppEUI, entry.DevEUI) @@ -150,9 +153,18 @@ func (e pktEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *pktEntry) UnmarshalBinary(data []byte) error { rw := readwriter.New(data) - rw.Read(func(data []byte) { e.AppEUI = data }) - rw.Read(func(data []byte) { e.DevEUI = data }) - rw.Read(func(data []byte) { e.Payload = data }) + rw.Read(func(data []byte) { + e.AppEUI = make([]byte, len(data)) + copy(e.AppEUI, data) + }) + rw.Read(func(data []byte) { + e.DevEUI = make([]byte, len(data)) + copy(e.DevEUI, data) + }) + rw.Read(func(data []byte) { + e.Payload = make([]byte, len(data)) + copy(e.Payload, data) + }) rw.TryRead(func(data []byte) error { return e.TTL.UnmarshalBinary(data) }) return rw.Err() } diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go index 79f869991..45a89c611 100644 --- a/core/components/handler/pktStorage_test.go +++ b/core/components/handler/pktStorage_test.go @@ -7,13 +7,14 @@ import ( "os" "path" "testing" + "time" . "github.com/TheThingsNetwork/ttn/utils/testing" ) const pktDB = "TestPktStorage.db" -func TestPushPullNormal(t *testing.T) { +func TestPullSize1(t *testing.T) { var db PktStorage defer func() { os.Remove(path.Join(os.TempDir(), pktDB)) @@ -24,117 +25,181 @@ func TestPushPullNormal(t *testing.T) { { Desc(t, "Create a new storage") var err error - db, err = NewPktStorage(path.Join(os.TempDir(), pktDB)) + db, err = NewPktStorage(path.Join(os.TempDir(), pktDB), 1) CheckErrors(t, nil, err) } // ------------------ { - Desc(t, "Push and Pull a valid Payload") - - // Build - appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 1} - devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 2} - payload := pktEntry{[]byte("TheThingsNetwork")} + Desc(t, "Dequeue an empty area") + got, err := db.dequeue([]byte{1, 2}, []byte{3, 4}) + CheckErrors(t, ErrNotFound, err) + Check(t, pktEntry{}, got, "Packet entries") + } - // Expects - var want = payload + // ------------------ - // Operate - err := db.Push(appEUI, devEUI, payload) - FatalUnless(t, err) - p, err := db.Pull(appEUI, devEUI) + { - // Check - CheckErrors(t, nil, err) - Check(t, want, p, "Payloads") + Desc(t, "Peek an empty area") + got, err := db.peek([]byte{1, 2}, []byte{3, 4}) + CheckErrors(t, ErrNotFound, err) + Check(t, pktEntry{}, got, "Packet entries") } // ------------------ { - Desc(t, "Push two payloads -> same device") - - // Build - appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 2} - devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 3} - payload1 := pktEntry{[]byte("TheThingsNetwork1")} - payload2 := pktEntry{[]byte("TheThingsNetwork2")} + Desc(t, "Queue / Dequeue an entry") + entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14, 42}} + err := db.enqueue(entry) + FatalUnless(t, err) + got, err := db.dequeue(entry.AppEUI, entry.DevEUI) + FatalUnless(t, err) + Check(t, entry, got, "Packet entries") + _, err = db.dequeue(entry.AppEUI, entry.DevEUI) + CheckErrors(t, ErrNotFound, err) + } - // Expects - var want1 = payload1 - var want2 = payload2 + // ------------------ - // Operate - err := db.Push(appEUI, devEUI, payload1) - FatalUnless(t, err) - err = db.Push(appEUI, devEUI, payload2) + { + Desc(t, "Queue / Peek an entry") + entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14, 42}} + err := db.enqueue(entry) FatalUnless(t, err) - p1, err := db.Pull(appEUI, devEUI) + got, err := db.peek(entry.AppEUI, entry.DevEUI) FatalUnless(t, err) - p2, err := db.Pull(appEUI, devEUI) + Check(t, entry, got, "Packet entries") + got, err = db.peek(entry.AppEUI, entry.DevEUI) FatalUnless(t, err) - - // Check - Check(t, want1, p1, "Payloads") - Check(t, want2, p2, "Payloads") + Check(t, entry, got, "Packet entries") } // ------------------ { - Desc(t, "Pull a non existing entry") + Desc(t, "Queue on an existing entry") + entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14, 42}} + entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{1, 2, 3, 4}} + err := db.enqueue(entry1) + FatalUnless(t, err) + err = db.enqueue(entry2) + FatalUnless(t, err) + got, err := db.dequeue(entry1.AppEUI, entry1.DevEUI) + FatalUnless(t, err) + Check(t, entry2, got, "Packet entries") + } + + // ------------------ - // Build - appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 3} - devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 3} + { + Desc(t, "Queue / Wait expiry / Dequeue") + entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now(), Payload: []byte{14, 42}} + err := db.enqueue(entry) + FatalUnless(t, err) + <-time.After(time.Millisecond * 10) + _, err = db.dequeue(entry.AppEUI, entry.DevEUI) + CheckErrors(t, ErrNotFound, err) + } - // Operate - _, err := db.Pull(appEUI, devEUI) + // ------------------ - // Check + { + Desc(t, "Queue / Wait expiry / Peek") + entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now(), Payload: []byte{14, 42}} + err := db.enqueue(entry) + FatalUnless(t, err) + <-time.After(time.Millisecond * 10) + _, err = db.peek(entry.AppEUI, entry.DevEUI) CheckErrors(t, ErrNotFound, err) } // ------------------ { - Desc(t, "Close the storage") - err := db.Close() + Desc(t, "Close") + err := db.done() CheckErrors(t, nil, err) } +} + +func TestPullSize2(t *testing.T) { + var db PktStorage + defer func() { + os.Remove(path.Join(os.TempDir(), pktDB)) + }() // ------------------ { - Desc(t, "Push after close") - - // Build - appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 5} - devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 6} - payload := pktEntry{[]byte("TheThingsNetwork")} + Desc(t, "Create a new storage") + var err error + db, err = NewPktStorage(path.Join(os.TempDir(), pktDB), 2) + CheckErrors(t, nil, err) + } - // Operate - err := db.Push(appEUI, devEUI, payload) + // ------------------ - // Check - CheckErrors(t, ErrOperational, err) + { + Desc(t, "Queue two, then Dequeue two") + entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{42}} + entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14}} + err := db.enqueue(entry1) + FatalUnless(t, err) + err = db.enqueue(entry2) + FatalUnless(t, err) + got1, err := db.dequeue(entry1.AppEUI, entry1.DevEUI) + FatalUnless(t, err) + got2, err := db.dequeue(entry2.AppEUI, entry2.DevEUI) + FatalUnless(t, err) + Check(t, entry1, got1, "Packet Entries") + Check(t, entry2, got2, "Packet Entries") } // ------------------ { - Desc(t, "Pull after close") + Desc(t, "Queue two, wait first expires, then peek") + entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now(), Payload: []byte{42}} + entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14}} + err := db.enqueue(entry1) + FatalUnless(t, err) + err = db.enqueue(entry2) + FatalUnless(t, err) + <-time.After(time.Millisecond * 10) + got, err := db.peek(entry2.AppEUI, entry2.DevEUI) + CheckErrors(t, nil, err) + Check(t, entry2, got, "Packet Entries") + } - // Build - appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 1} - devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 2} + // ------------------ - // Operate - _, err := db.Pull(appEUI, devEUI) + { + Desc(t, "Queue three, dequeue then peek") + entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{42}} + entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14}} + entry3 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{6}} + err := db.enqueue(entry1) + FatalUnless(t, err) + err = db.enqueue(entry2) + FatalUnless(t, err) + err = db.enqueue(entry3) + FatalUnless(t, err) + got1, err := db.dequeue(entry1.AppEUI, entry1.DevEUI) + FatalUnless(t, err) + got2, err := db.peek(entry2.AppEUI, entry2.DevEUI) + FatalUnless(t, err) + Check(t, entry2, got1, "Packet Entries") + Check(t, entry3, got2, "Packet Entries") + } - // Check - CheckErrors(t, ErrOperational, err) + // ------------------ + + { + Desc(t, "Close") + err := db.done() + CheckErrors(t, nil, err) } } diff --git a/core/handler.pb.go b/core/handler.pb.go index 3775e461e..0e0883650 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -69,6 +69,7 @@ type DataDownHandlerReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + TTL uint32 `protobuf:"varint,4,opt,name=TTL,json=tTL,proto3" json:"TTL,omitempty"` } func (m *DataDownHandlerReq) Reset() { *m = DataDownHandlerReq{} } @@ -84,26 +85,6 @@ func (m *DataDownHandlerRes) String() string { return proto.CompactTe func (*DataDownHandlerRes) ProtoMessage() {} func (*DataDownHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } -type ABPSubHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` -} - -func (m *ABPSubHandlerReq) Reset() { *m = ABPSubHandlerReq{} } -func (m *ABPSubHandlerReq) String() string { return proto.CompactTextString(m) } -func (*ABPSubHandlerReq) ProtoMessage() {} -func (*ABPSubHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } - -type ABPSubHandlerRes struct { -} - -func (m *ABPSubHandlerRes) Reset() { *m = ABPSubHandlerRes{} } -func (m *ABPSubHandlerRes) String() string { return proto.CompactTextString(m) } -func (*ABPSubHandlerRes) ProtoMessage() {} -func (*ABPSubHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } - type JoinHandlerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` @@ -114,7 +95,7 @@ type JoinHandlerReq struct { func (m *JoinHandlerReq) Reset() { *m = JoinHandlerReq{} } func (m *JoinHandlerReq) String() string { return proto.CompactTextString(m) } func (*JoinHandlerReq) ProtoMessage() {} -func (*JoinHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{6} } +func (*JoinHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } func (m *JoinHandlerReq) GetMetadata() *Metadata { if m != nil { @@ -133,7 +114,7 @@ type JoinHandlerRes struct { func (m *JoinHandlerRes) Reset() { *m = JoinHandlerRes{} } func (m *JoinHandlerRes) String() string { return proto.CompactTextString(m) } func (*JoinHandlerRes) ProtoMessage() {} -func (*JoinHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{7} } +func (*JoinHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } func (m *JoinHandlerRes) GetPayload() *LoRaWANJoinAccept { if m != nil { @@ -154,8 +135,6 @@ func init() { proto.RegisterType((*DataUpHandlerRes)(nil), "core.DataUpHandlerRes") proto.RegisterType((*DataDownHandlerReq)(nil), "core.DataDownHandlerReq") proto.RegisterType((*DataDownHandlerRes)(nil), "core.DataDownHandlerRes") - proto.RegisterType((*ABPSubHandlerReq)(nil), "core.ABPSubHandlerReq") - proto.RegisterType((*ABPSubHandlerRes)(nil), "core.ABPSubHandlerRes") proto.RegisterType((*JoinHandlerReq)(nil), "core.JoinHandlerReq") proto.RegisterType((*JoinHandlerRes)(nil), "core.JoinHandlerRes") } @@ -170,7 +149,6 @@ type HandlerClient interface { HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) HandleJoin(ctx context.Context, in *JoinHandlerReq, opts ...grpc.CallOption) (*JoinHandlerRes, error) - SubscribePersonalized(ctx context.Context, in *ABPSubHandlerReq, opts ...grpc.CallOption) (*ABPSubHandlerRes, error) } type handlerClient struct { @@ -208,22 +186,12 @@ func (c *handlerClient) HandleJoin(ctx context.Context, in *JoinHandlerReq, opts return out, nil } -func (c *handlerClient) SubscribePersonalized(ctx context.Context, in *ABPSubHandlerReq, opts ...grpc.CallOption) (*ABPSubHandlerRes, error) { - out := new(ABPSubHandlerRes) - err := grpc.Invoke(ctx, "/core.Handler/SubscribePersonalized", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // Server API for Handler service type HandlerServer interface { HandleDataUp(context.Context, *DataUpHandlerReq) (*DataUpHandlerRes, error) HandleDataDown(context.Context, *DataDownHandlerReq) (*DataDownHandlerRes, error) HandleJoin(context.Context, *JoinHandlerReq) (*JoinHandlerRes, error) - SubscribePersonalized(context.Context, *ABPSubHandlerReq) (*ABPSubHandlerRes, error) } func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { @@ -266,18 +234,6 @@ func _Handler_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func( return out, nil } -func _Handler_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(ABPSubHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - out, err := srv.(HandlerServer).SubscribePersonalized(ctx, in) - if err != nil { - return nil, err - } - return out, nil -} - var _Handler_serviceDesc = grpc.ServiceDesc{ ServiceName: "core.Handler", HandlerType: (*HandlerServer)(nil), @@ -294,10 +250,6 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ MethodName: "HandleJoin", Handler: _Handler_HandleJoin_Handler, }, - { - MethodName: "SubscribePersonalized", - Handler: _Handler_SubscribePersonalized_Handler, - }, }, Streams: []grpc.StreamDesc{}, } @@ -441,6 +393,11 @@ func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevEUI) } } + if m.TTL != 0 { + data[i] = 0x20 + i++ + i = encodeVarintHandler(data, i, uint64(m.TTL)) + } return i, nil } @@ -462,74 +419,6 @@ func (m *DataDownHandlerRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ABPSubHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ABPSubHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - } - if m.AppSKey != nil { - if len(m.AppSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } - } - return i, nil -} - -func (m *ABPSubHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ABPSubHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - func (m *JoinHandlerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -732,46 +621,13 @@ func (m *DataDownHandlerReq) Size() (n int) { n += 1 + l + sovHandler(uint64(l)) } } - return n -} - -func (m *DataDownHandlerRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *ABPSubHandlerReq) Size() (n int) { - var l int - _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - } - if m.AppSKey != nil { - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + if m.TTL != 0 { + n += 1 + sovHandler(uint64(m.TTL)) } return n } -func (m *ABPSubHandlerRes) Size() (n int) { +func (m *DataDownHandlerRes) Size() (n int) { var l int _ = l return n @@ -1296,204 +1152,11 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { m.DevEUI = []byte{} } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataDownHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataDownHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataDownHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ABPSubHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ABPSubHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ABPSubHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType) } - var byteLen int + m.TTL = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -1503,23 +1166,11 @@ func (m *ABPSubHandlerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + m.TTL |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) - if m.AppSKey == nil { - m.AppSKey = []byte{} - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -1541,7 +1192,7 @@ func (m *ABPSubHandlerReq) Unmarshal(data []byte) error { } return nil } -func (m *ABPSubHandlerRes) Unmarshal(data []byte) error { +func (m *DataDownHandlerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1564,10 +1215,10 @@ func (m *ABPSubHandlerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ABPSubHandlerRes: wiretype end group for non-group") + return fmt.Errorf("proto: DataDownHandlerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ABPSubHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: DataDownHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -2051,36 +1702,33 @@ var ( ) var fileDescriptorHandler = []byte{ - // 487 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0xa9, 0x13, 0x5b, 0xd3, 0x26, 0x0a, 0xa3, 0x50, 0x2c, 0x1f, 0x2a, 0xe4, 0x13, 0x02, - 0xa9, 0x12, 0xe1, 0xc2, 0x09, 0xc9, 0xc5, 0xfc, 0xd3, 0x28, 0x72, 0xa9, 0xb8, 0x21, 0x6d, 0xbc, - 0x8b, 0x88, 0xea, 0xee, 0x1a, 0xaf, 0x21, 0x84, 0x27, 0xe0, 0xcc, 0x89, 0x3b, 0x6f, 0xc0, 0x53, - 0x70, 0xe4, 0x11, 0x10, 0xbc, 0x08, 0xbb, 0x5e, 0x57, 0xae, 0x5d, 0x57, 0x94, 0x1e, 0x2c, 0xed, - 0xf7, 0xcd, 0xce, 0xcc, 0x37, 0xfb, 0x4d, 0x02, 0xc3, 0xb7, 0x84, 0xd3, 0x94, 0xe5, 0xbb, 0x59, - 0x2e, 0x0a, 0x81, 0x76, 0x22, 0x72, 0xe6, 0x0f, 0x53, 0x91, 0x93, 0x15, 0xe1, 0x86, 0xf4, 0x41, - 0x93, 0xe6, 0x1c, 0x7c, 0xb7, 0x60, 0x1c, 0x91, 0x82, 0x1c, 0x66, 0x4f, 0x4c, 0x62, 0xcc, 0xde, - 0xa1, 0x07, 0xce, 0x9c, 0xac, 0x53, 0x41, 0xa8, 0x67, 0xdd, 0xb0, 0x6e, 0x6e, 0xc5, 0x4e, 0x66, - 0x20, 0xde, 0x02, 0x77, 0x9f, 0x15, 0x84, 0xaa, 0x0c, 0xaf, 0xa7, 0x42, 0x9b, 0xd3, 0xd1, 0x6e, - 0x59, 0xed, 0x84, 0x8d, 0xdd, 0xe3, 0xea, 0x84, 0xdb, 0x30, 0x08, 0xb3, 0xec, 0xe1, 0xe1, 0x53, - 0x6f, 0xa3, 0x2c, 0x32, 0x20, 0x25, 0xd2, 0x7c, 0xc4, 0x3e, 0x68, 0xde, 0x36, 0x3c, 0x2d, 0x11, - 0x22, 0xd8, 0x8f, 0x1e, 0xf0, 0xc2, 0xeb, 0x2b, 0x76, 0x18, 0xdb, 0x6f, 0xd4, 0x19, 0x27, 0xd0, - 0xdf, 0x7f, 0xb9, 0xce, 0x98, 0x37, 0x28, 0xc9, 0xfe, 0xb1, 0x06, 0xc1, 0xd1, 0x19, 0xcd, 0x12, - 0x6f, 0x37, 0x35, 0x6f, 0x4e, 0xaf, 0x1a, 0x61, 0x2f, 0x44, 0x4c, 0x5e, 0x85, 0x33, 0x7d, 0xff, - 0x52, 0x63, 0x04, 0xaf, 0x01, 0x75, 0x72, 0x24, 0x56, 0xfc, 0x42, 0x4f, 0x54, 0x8f, 0xdd, 0x3b, - 0x67, 0xec, 0x8d, 0xd3, 0x63, 0x07, 0x93, 0x8e, 0xfa, 0x32, 0xf8, 0x08, 0xe3, 0x70, 0x6f, 0x7e, - 0xf0, 0x7e, 0x71, 0xaa, 0x67, 0x5d, 0xd9, 0x6a, 0x54, 0x56, 0x5a, 0x54, 0xe5, 0x90, 0xd2, 0xbc, - 0x6a, 0xe9, 0x50, 0x03, 0x75, 0x64, 0xb6, 0x3a, 0x3a, 0x78, 0xce, 0xd6, 0x55, 0x53, 0x87, 0x1b, - 0xa8, 0x23, 0xaa, 0x56, 0x19, 0x31, 0x2e, 0x38, 0xc4, 0xc0, 0x00, 0xcf, 0x74, 0x96, 0xc1, 0x67, - 0x0b, 0x46, 0xcf, 0xc4, 0x92, 0x5f, 0x40, 0x4c, 0x3d, 0x66, 0xaf, 0xe1, 0xae, 0x0f, 0xae, 0xe2, - 0x67, 0x82, 0x27, 0xac, 0xd2, 0xe2, 0xd2, 0x0a, 0x37, 0xec, 0xb0, 0xff, 0x61, 0xc7, 0xb7, 0xb6, - 0x14, 0x89, 0x77, 0xda, 0xd6, 0x5f, 0x6f, 0x58, 0xaf, 0x6f, 0x87, 0x49, 0xc2, 0xb2, 0xa2, 0x36, - 0xe9, 0x32, 0x4f, 0xf6, 0x1f, 0x2a, 0xa7, 0x5f, 0x7a, 0xe0, 0x54, 0x0a, 0xf1, 0x3e, 0x6c, 0x99, - 0xa3, 0xd9, 0x59, 0xdc, 0x36, 0x59, 0xed, 0x5f, 0x9d, 0xdf, 0xcd, 0x4b, 0x8c, 0x60, 0x54, 0xe7, - 0xeb, 0x35, 0x41, 0xaf, 0xbe, 0xd9, 0x5c, 0x4b, 0xff, 0xbc, 0x88, 0xc4, 0x7b, 0x00, 0x06, 0xe9, - 0xe7, 0xc0, 0x89, 0xb9, 0xd7, 0xf4, 0xd4, 0xef, 0x62, 0x25, 0x3e, 0x86, 0x6b, 0x6a, 0x1b, 0x64, - 0x92, 0x2f, 0x17, 0x6c, 0xce, 0x72, 0x29, 0x38, 0x49, 0x97, 0x9f, 0x18, 0x3d, 0x19, 0xa4, 0xbd, - 0xa7, 0x7e, 0x37, 0x2f, 0xf7, 0xc6, 0x3f, 0x7e, 0xef, 0x58, 0x3f, 0xd5, 0xf7, 0x4b, 0x7d, 0x5f, - 0xff, 0xec, 0x5c, 0x59, 0x0c, 0xca, 0x3f, 0xa1, 0xbb, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x74, - 0xc7, 0x08, 0x43, 0xb6, 0x04, 0x00, 0x00, + // 434 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcf, 0xea, 0xd3, 0x40, + 0x10, 0x36, 0x4d, 0x9a, 0x84, 0xf9, 0x35, 0xa5, 0x0e, 0xa5, 0x86, 0x1c, 0x8a, 0xe4, 0x24, 0x0a, + 0x05, 0xeb, 0xc5, 0x93, 0x10, 0x8d, 0xe2, 0x9f, 0xb6, 0x48, 0x4c, 0xf1, 0xbc, 0x66, 0x57, 0x94, + 0xb6, 0xd9, 0x35, 0x09, 0x96, 0xbe, 0x81, 0x8f, 0xe0, 0xdd, 0x37, 0xf0, 0x29, 0xf4, 0xe6, 0x23, + 0x88, 0xbe, 0x88, 0xbb, 0xd9, 0x48, 0x4c, 0x6d, 0xf1, 0xcf, 0x21, 0x30, 0xdf, 0xb7, 0x33, 0x3b, + 0xdf, 0x7c, 0x3b, 0x01, 0xef, 0x15, 0xc9, 0xe9, 0x96, 0x15, 0x33, 0x51, 0xf0, 0x8a, 0xa3, 0x95, + 0xf1, 0x82, 0x05, 0xde, 0x96, 0x17, 0x64, 0x4f, 0x72, 0x4d, 0x06, 0xa0, 0x48, 0x1d, 0x87, 0x1f, + 0x0d, 0x18, 0xc5, 0xa4, 0x22, 0x6b, 0xf1, 0x50, 0x17, 0x26, 0xec, 0x0d, 0xfa, 0xe0, 0x3c, 0x25, + 0x87, 0x2d, 0x27, 0xd4, 0x37, 0xae, 0x1a, 0xd7, 0x06, 0x89, 0x23, 0x34, 0xc4, 0xeb, 0xe0, 0x2e, + 0x59, 0x45, 0xa8, 0xac, 0xf0, 0x7b, 0xf2, 0xe8, 0x62, 0x3e, 0x9c, 0xd5, 0xb7, 0xfd, 0x64, 0x13, + 0x77, 0xd7, 0x44, 0x38, 0x01, 0x3b, 0x12, 0xe2, 0xfe, 0xfa, 0x91, 0x6f, 0xd6, 0x97, 0xd8, 0xa4, + 0x46, 0x8a, 0x8f, 0xd9, 0x5b, 0xc5, 0x5b, 0x9a, 0xa7, 0x35, 0x42, 0x04, 0xeb, 0xc1, 0xbd, 0xbc, + 0xf2, 0xfb, 0x92, 0xf5, 0x12, 0xeb, 0xa5, 0x8c, 0x71, 0x0c, 0xfd, 0x65, 0x7a, 0x10, 0xcc, 0xb7, + 0x6b, 0xb2, 0xbf, 0x53, 0x20, 0xdc, 0xfc, 0xa6, 0xb9, 0xc4, 0x1b, 0x5d, 0xcd, 0x17, 0xf3, 0xcb, + 0x5a, 0xd8, 0x82, 0x27, 0xe4, 0x79, 0xb4, 0x52, 0xf9, 0xff, 0x35, 0x46, 0x28, 0x00, 0x55, 0x71, + 0xcc, 0xf7, 0xf9, 0x5f, 0x59, 0xd4, 0x8e, 0xdd, 0x3b, 0x33, 0xb6, 0xd9, 0x19, 0x7b, 0x04, 0x66, + 0x9a, 0x2e, 0x6a, 0x2f, 0xbc, 0xc4, 0xac, 0xd2, 0x45, 0x38, 0x3e, 0xd1, 0xb1, 0x0c, 0xdf, 0x19, + 0x30, 0x7c, 0xcc, 0x5f, 0xff, 0x2a, 0xa2, 0x6d, 0x65, 0x9c, 0x69, 0xd5, 0xeb, 0xb4, 0x0a, 0xc0, + 0x95, 0xfc, 0x8a, 0xe7, 0x19, 0x6b, 0x44, 0xb8, 0xb4, 0xc1, 0x1d, 0x4b, 0xac, 0x3f, 0x58, 0xf2, + 0xe1, 0x58, 0x4a, 0x89, 0x37, 0x8f, 0xed, 0xbf, 0xd2, 0xb1, 0x5f, 0x65, 0x47, 0x59, 0xc6, 0x44, + 0xd5, 0x1a, 0x25, 0x2d, 0x94, 0x6a, 0x22, 0x4a, 0x8b, 0x46, 0xa6, 0x43, 0x35, 0x54, 0x27, 0xab, + 0xfd, 0xe6, 0xd9, 0x13, 0x76, 0x68, 0x64, 0x3a, 0xb9, 0x86, 0xff, 0xa2, 0x72, 0xfe, 0xd9, 0x00, + 0xa7, 0x51, 0x88, 0x77, 0x60, 0xa0, 0x43, 0xbd, 0x37, 0x38, 0xd1, 0x55, 0xc7, 0x9b, 0x1f, 0x9c, + 0xe6, 0x4b, 0x8c, 0x61, 0xd8, 0xd6, 0xab, 0x87, 0x41, 0xbf, 0xcd, 0xec, 0xae, 0x46, 0x70, 0xee, + 0xa4, 0xc4, 0xdb, 0x00, 0x1a, 0x29, 0x3b, 0x70, 0xac, 0xf3, 0xba, 0x6f, 0x1a, 0x9c, 0x62, 0xcb, + 0xbb, 0xa3, 0x4f, 0xdf, 0xa6, 0xc6, 0x17, 0xf9, 0x7d, 0x95, 0xdf, 0xfb, 0xef, 0xd3, 0x4b, 0x2f, + 0xec, 0xfa, 0xff, 0xbd, 0xf5, 0x23, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x5b, 0xb8, 0x35, 0xf1, 0x03, + 0x00, 0x00, } diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 719db8545..6623aec89 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -84,14 +84,6 @@ type HandlerClient struct { OutHandleDataDown struct { Res *core.DataDownHandlerRes } - InSubscribePersonalized struct { - Ctx context.Context - Req *core.ABPSubHandlerReq - Opts []grpc.CallOption - } - OutSubscribePersonalized struct { - Res *core.ABPSubHandlerRes - } } // NewHandlerClient creates a new mock HandlerClient @@ -125,14 +117,6 @@ func (m *HandlerClient) HandleDataDown(ctx context.Context, in *core.DataDownHan return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] } -// SubscribePersonalized implements the core.HandlerClient interface -func (m *HandlerClient) SubscribePersonalized(ctx context.Context, in *core.ABPSubHandlerReq, opts ...grpc.CallOption) (*core.ABPSubHandlerRes, error) { - m.InSubscribePersonalized.Ctx = ctx - m.InSubscribePersonalized.Req = in - m.InSubscribePersonalized.Opts = opts - return m.OutSubscribePersonalized.Res, m.Failures["SubscribePersonalized"] -} - // BrokerClient mocks the core.BrokerClient interface type BrokerClient struct { Failures map[string]error @@ -321,13 +305,6 @@ type HandlerServer struct { OutHandleJoin struct { Res *core.JoinHandlerRes } - InSubscribePersonalized struct { - Ctx context.Context - Req *core.ABPSubHandlerReq - } - OutSubscribePersonalized struct { - Res *core.ABPSubHandlerRes - } } // NewHandlerServer creates a new mock HandlerServer @@ -357,10 +334,3 @@ func (m *HandlerServer) HandleJoin(ctx context.Context, in *core.JoinHandlerReq) m.InHandleJoin.Req = in return m.OutHandleJoin.Res, m.Failures["HandleJoin"] } - -// SubscribePersonalized implements the core.HandlerServer interface -func (m *HandlerServer) SubscribePersonalized(ctx context.Context, in *core.ABPSubHandlerReq) (*core.ABPSubHandlerRes, error) { - m.InSubscribePersonalized.Ctx = ctx - m.InSubscribePersonalized.Req = in - return m.OutSubscribePersonalized.Res, m.Failures["SubscribePersonalized"] -} diff --git a/core/msgp_gen.go b/core/msgp_gen.go index 7a56efc71..219c3f7bb 100644 --- a/core/msgp_gen.go +++ b/core/msgp_gen.go @@ -309,8 +309,8 @@ func (z *DataUpAppReq) Msgsize() (s int) { func (z *OTAAAppReq) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) // map header, size 1 - // string "Metadata" - o = append(o, 0x81, 0xa8, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) + // string "metadata" + o = append(o, 0x81, 0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) for bzg := range z.Metadata { o, err = z.Metadata[bzg].MarshalMsg(o) @@ -337,7 +337,7 @@ func (z *OTAAAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { return } switch msgp.UnsafeString(field) { - case "Metadata": + case "metadata": var xsz uint32 xsz, bts, err = msgp.ReadArrayHeaderBytes(bts) if err != nil { diff --git a/core/protos/handler.proto b/core/protos/handler.proto index 0348dcb07..5ed0500ad 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -22,19 +22,11 @@ message DataDownHandlerReq { bytes Payload = 1; bytes AppEUI = 2; bytes DevEUI = 3; + uint32 TTL = 4; } message DataDownHandlerRes {} -message ABPSubHandlerReq { - bytes AppEUI = 1; - bytes DevAddr = 2; - bytes NwkSKey = 3; - bytes AppSKey = 4; -} - -message ABPSubHandlerRes {} - message JoinHandlerReq { bytes AppEUI = 1; bytes DevEUI = 2; @@ -53,5 +45,4 @@ service Handler { rpc HandleDataUp (DataUpHandlerReq) returns (DataUpHandlerRes); rpc HandleDataDown (DataDownHandlerReq) returns (DataDownHandlerRes); rpc HandleJoin (JoinHandlerReq) returns (JoinHandlerRes); - rpc SubscribePersonalized (ABPSubHandlerReq) returns (ABPSubHandlerRes); } diff --git a/core/storage/storage_test.go b/core/storage/storage_test.go index 0c3beccbb..22d9e3330 100644 --- a/core/storage/storage_test.go +++ b/core/storage/storage_test.go @@ -69,6 +69,18 @@ func TestStoreAndRead(t *testing.T) { // ------------------- + { + Desc(t, "Store then lookup in a nested bucket with default key") + err := itf.Append(nil, []encoding.BinaryMarshaler{&testEntry{Data: "IoT"}}, []byte("nested"), []byte("buckettt")) + FatalUnless(t, err) + entries, err := itf.Read(nil, &testEntry{}, []byte("nested"), []byte("buckettt")) + + CheckErrors(t, nil, err) + Check(t, []testEntry{testEntry{Data: "IoT"}}, entries.([]testEntry), "Entries") + } + + // ------------------- + { Desc(t, "Lookup in non-existing bucket") entries, err := itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("DoesnExists")) From 4f83c363ab5f702b481e282b200326348da8cf7a Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 23:48:26 +0100 Subject: [PATCH 1126/2266] [refactor/storages] Fix handler tests with new storage interface --- core/components/handler/handler_test.go | 471 ++++++------------------ 1 file changed, 107 insertions(+), 364 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 288faf953..fddb49c04 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -30,6 +30,7 @@ func TestHandleDataDown(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, Payload: []byte("TheThingsNetwork"), + TTL: 2, } // Expect @@ -50,7 +51,7 @@ func TestHandleDataDown(t *testing.T) { // Check CheckErrors(t, wantError, err) Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + Check(t, wantEntry.Payload, pktStorage.InEnqueue.Entry.Payload, "Packet Entries") } // -------------------- @@ -66,6 +67,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + TTL: 2, Payload: nil, } @@ -87,7 +89,7 @@ func TestHandleDataDown(t *testing.T) { // Check CheckErrors(t, wantError, err) Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") } // -------------------- @@ -103,6 +105,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + TTL: 2, Payload: []byte("TheThingsNetwork"), } @@ -124,7 +127,7 @@ func TestHandleDataDown(t *testing.T) { // Check CheckErrors(t, wantError, err) Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") } // -------------------- @@ -140,6 +143,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, + TTL: 2, Payload: []byte("TheThingsNetwork"), } @@ -161,7 +165,45 @@ func TestHandleDataDown(t *testing.T) { // Check CheckErrors(t, wantError, err) Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InPush.Payload, "Packet Entries") + Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") + } + + // -------------------- + + { + Desc(t, "Handle invalid downlink ~> Invalid TTL") + + // Build + devStorage := NewMockDevStorage() + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewBrokerClient() + req := &core.DataDownHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, + TTL: 0, + Payload: []byte("TheThingsNetwork"), + } + + // Expect + var wantError = ErrStructural + var wantRes = new(core.DataDownHandlerRes) + var wantEntry pktEntry + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{NetAddr: "localhost"}) + res, err := handler.HandleDataDown(context.Background(), req) + + // Check + CheckErrors(t, wantError, err) + Check(t, wantRes, res, "Data Down Handler Responses") + Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") } } @@ -171,7 +213,7 @@ func TestHandleDataUp(t *testing.T) { // Build devStorage := NewMockDevStorage() - devStorage.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() @@ -204,7 +246,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -246,7 +288,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -288,7 +330,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -330,7 +372,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -372,7 +414,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -383,7 +425,7 @@ func TestHandleDataUp(t *testing.T) { // Build devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, @@ -394,7 +436,7 @@ func TestHandleDataUp(t *testing.T) { broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -437,7 +479,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -448,7 +490,7 @@ func TestHandleDataUp(t *testing.T) { // Build devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, @@ -459,7 +501,7 @@ func TestHandleDataUp(t *testing.T) { broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -545,7 +587,7 @@ func TestHandleDataUp(t *testing.T) { ok1, ok2 := <-chack, <-chack Check(t, true, ok1 && ok2, "Acknowledgements") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -557,19 +599,19 @@ func TestHandleDataUp(t *testing.T) { tmst := time.Now() devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, } pktStorage := NewMockPktStorage() - pktStorage.OutPull.Entry.Payload = []byte("Downlink") + pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -597,11 +639,11 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr *string encodedDown, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, false, devAddr, - devStorage.OutLookup.Entry.FCntDown+1, - pktStorage.OutPull.Entry.Payload, + devStorage.OutRead.Entry.FCntDown+1, + pktStorage.OutDequeue.Entry.Payload, ) FatalUnless(t, err) var wantRes = &core.DataUpHandlerRes{ @@ -612,8 +654,8 @@ func TestHandleDataUp(t *testing.T) { }, MACPayload: &core.LoRaWANMACPayload{ FHDR: &core.LoRaWANFHDR{ - DevAddr: devStorage.OutLookup.Entry.DevAddr[:], - FCnt: devStorage.OutLookup.Entry.FCntDown + 1, + DevAddr: devStorage.OutRead.Entry.DevAddr[:], + FCnt: devStorage.OutRead.Entry.FCntDown + 1, FCtrl: new(core.LoRaWANFCtrl), }, FPort: uint32(1), @@ -651,7 +693,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -662,7 +704,7 @@ func TestHandleDataUp(t *testing.T) { // Build devStorage := NewMockDevStorage() devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, @@ -673,7 +715,7 @@ func TestHandleDataUp(t *testing.T) { broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -759,7 +801,7 @@ func TestHandleDataUp(t *testing.T) { ok1, ok2 := <-chack, <-chack Check(t, true, ok1 && ok2, "Acknowledgements") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -770,7 +812,7 @@ func TestHandleDataUp(t *testing.T) { // Build devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, @@ -782,7 +824,7 @@ func TestHandleDataUp(t *testing.T) { broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -825,7 +867,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -836,19 +878,19 @@ func TestHandleDataUp(t *testing.T) { // Build devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, } pktStorage := NewMockPktStorage() - pktStorage.Failures["Pull"] = errors.New(errors.Operational, "Mock Error") + pktStorage.Failures["dequeue"] = errors.New(errors.Operational, "Mock Error") appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -891,7 +933,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -982,7 +1024,7 @@ func TestHandleDataUp(t *testing.T) { PktStorage: pktStorage, }, Options{NetAddr: "localhost"}) - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr1[:], AppSKey: appSKey1, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, @@ -994,10 +1036,10 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr1, err1) Check(t, wantRes1, res1, "Data Up Handler Responses") Check(t, wantData1, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt1, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt1, devStorage.InUpsert.Entry.FCntDown, "Frame counters") // Operate - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr2[:], AppSKey: appSKey2, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, @@ -1009,7 +1051,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr2, err2) Check(t, wantRes2, res2, "Data Up Handler Responses") Check(t, wantData2, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt2, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt2, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -1021,19 +1063,19 @@ func TestHandleDataUp(t *testing.T) { tmst := time.Now() devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, } pktStorage := NewMockPktStorage() - pktStorage.OutPull.Entry.Payload = []byte("Downlink") + pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -1061,11 +1103,11 @@ func TestHandleDataUp(t *testing.T) { // Expect var wantErr *string encodedDown, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, false, devAddr, - devStorage.OutLookup.Entry.FCntDown+1, - pktStorage.OutPull.Entry.Payload, + devStorage.OutRead.Entry.FCntDown+1, + pktStorage.OutDequeue.Entry.Payload, ) FatalUnless(t, err) var wantRes = &core.DataUpHandlerRes{ @@ -1076,8 +1118,8 @@ func TestHandleDataUp(t *testing.T) { }, MACPayload: &core.LoRaWANMACPayload{ FHDR: &core.LoRaWANFHDR{ - DevAddr: devStorage.OutLookup.Entry.DevAddr[:], - FCnt: devStorage.OutLookup.Entry.FCntDown + 1, + DevAddr: devStorage.OutRead.Entry.DevAddr[:], + FCnt: devStorage.OutRead.Entry.FCntDown + 1, FCtrl: new(core.LoRaWANFCtrl), }, FPort: uint32(1), @@ -1115,7 +1157,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } // -------------------- @@ -1127,20 +1169,20 @@ func TestHandleDataUp(t *testing.T) { tmst := time.Now() devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr[:], AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, FCntDown: 3, } - devStorage.Failures["UpdateFCnt"] = errors.New(errors.Operational, "Mock Error") + devStorage.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") pktStorage := NewMockPktStorage() - pktStorage.OutPull.Entry.Payload = []byte("Downlink") + pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutLookup.Entry.AppSKey, + devStorage.OutRead.Entry.AppSKey, true, devAddr, fcnt, @@ -1174,7 +1216,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, } - var wantFCnt = devStorage.OutLookup.Entry.FCntDown + 1 + var wantFCnt = devStorage.OutRead.Entry.FCntDown + 1 // Operate handler := New(Components{ @@ -1190,306 +1232,7 @@ func TestHandleDataUp(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Data Up Handler Responses") Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpdateFCnt.FCnt, "Frame counters") - } -} - -func TestSubscribePersonalized(t *testing.T) { - { - Desc(t, "Handle Valid Perso Subscription") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevAddr: []byte{2, 2, 2, 2}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - addr := "localhost" - - // Expect - var wantError *string - var wantRes = new(core.ABPSubHandlerRes) - var wantSub = req.AppEUI - var wantReq = &core.ABPSubBrokerReq{ - HandlerNet: addr, - AppEUI: req.AppEUI, - DevAddr: req.DevAddr, - NwkSKey: req.NwkSKey, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") - } - - // ---------------------- - - { - Desc(t, "Handle Invalid Perso Subscription -> Invalid AppEUI") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1}, - DevAddr: []byte{2, 2, 2, 2}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - addr := "localhost" - - // Expect - var wantError = ErrStructural - var wantRes = new(core.ABPSubHandlerRes) - var wantSub []byte - var wantReq *core.ABPSubBrokerReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") - } - - // ---------------------- - - { - Desc(t, "Handle Invalid Perso Subscription -> Invalid DevAddr") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevAddr: nil, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - addr := "localhost" - - // Expect - var wantError = ErrStructural - var wantRes = new(core.ABPSubHandlerRes) - var wantSub []byte - var wantReq *core.ABPSubBrokerReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") - } - - // ---------------------- - - { - Desc(t, "Handle Invalid Perso Subscription -> Invalid NwkSKey") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevAddr: []byte{2, 2, 2, 2}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 12, 14, 12}, - AppSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - addr := "localhost" - - // Expect - var wantError = ErrStructural - var wantRes = new(core.ABPSubHandlerRes) - var wantSub []byte - var wantReq *core.ABPSubBrokerReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") - } - - // ---------------------- - - { - Desc(t, "Handle Invalid Perso Subscription -> Invalid AppSKey") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevAddr: []byte{2, 2, 2, 2}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{6, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1}, - } - addr := "localhost" - - // Expect - var wantError = ErrStructural - var wantRes = new(core.ABPSubHandlerRes) - var wantSub []byte - var wantReq *core.ABPSubBrokerReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") - } - - // ---------------------- - - { - Desc(t, "Handle Invalid Perso Subscription -> Storage fails") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - devStorage.Failures["StorePersonalized"] = errors.New(errors.Operational, "Mock Error") - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevAddr: []byte{2, 2, 2, 2}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{6, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1, 0, 1, 1, 1, 1}, - } - addr := "localhost" - - // Expect - var wantError = ErrOperational - var wantRes = new(core.ABPSubHandlerRes) - var wantSub = req.AppEUI - var wantReq *core.ABPSubBrokerReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") - } - - // ---------------------- - - { - Desc(t, "Handle Invalid Perso Subscription -> Brokers fails") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() - broker.Failures["SubscribePersonalized"] = fmt.Errorf("Mock Error") - req := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevAddr: []byte{2, 2, 2, 2}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{6, 5, 4, 3, 7, 6, 5, 4, 3, 2, 1, 0, 1, 1, 1, 1}, - } - addr := "localhost" - - // Expect - var wantError = ErrOperational - var wantRes = new(core.ABPSubHandlerRes) - var wantSub = req.AppEUI - var wantReq = &core.ABPSubBrokerReq{ - HandlerNet: addr, - AppEUI: req.AppEUI, - DevAddr: req.DevAddr, - NwkSKey: req.NwkSKey, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{NetAddr: addr}) - res, err := handler.SubscribePersonalized(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Subscribe Handler Responses") - Check(t, wantSub, devStorage.InStorePersonalized.AppEUI, "Subscriptions") - Check(t, wantReq, broker.InSubscribePersonalized.Req, "Subscribe Broker Requests") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } } @@ -1517,7 +1260,7 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, @@ -1564,7 +1307,7 @@ func TestHandleJoin(t *testing.T) { joinaccept := lorawan.NewPHYPayload(false) err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InStore.Entry.AppKey)) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InUpsert.Entry.AppKey)) CheckErrors(t, nil, err) Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") } @@ -1594,7 +1337,7 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, @@ -1642,7 +1385,7 @@ func TestHandleJoin(t *testing.T) { joinaccept := lorawan.NewPHYPayload(false) err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InStore.Entry.AppKey)) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InUpsert.Entry.AppKey)) CheckErrors(t, nil, err) Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") } @@ -1672,12 +1415,12 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, } - devStorage.Failures["Store"] = errors.New(errors.Operational, "Mock Error") + devStorage.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() @@ -1728,7 +1471,7 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, @@ -1783,7 +1526,7 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, @@ -1838,7 +1581,7 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() broker := mocks.NewBrokerClient() @@ -2095,7 +1838,7 @@ func TestHandleJoin(t *testing.T) { } devStorage := NewMockDevStorage() - devStorage.OutLookup.Entry = devEntry{ + devStorage.OutRead.Entry = devEntry{ AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, @@ -2160,7 +1903,7 @@ func TestHandleJoin(t *testing.T) { joinaccept := lorawan.NewPHYPayload(false) err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InStore.Entry.AppKey)) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InUpsert.Entry.AppKey)) CheckErrors(t, nil, err) Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") ok = true From 6fcab9d994b0ad18c8f6b660972ab7833204c261 Mon Sep 17 00:00:00 2001 From: ktorz Date: Mon, 21 Mar 2016 23:54:15 +0100 Subject: [PATCH 1127/2266] [refactor/storages] Remove ABP from mqtt adapter --- core/adapters/mqtt/client.go | 14 -- core/adapters/mqtt/client_test.go | 39 +--- core/adapters/mqtt/mqtt.go | 56 ----- core/adapters/mqtt/mqtt_test.go | 333 ------------------------------ 4 files changed, 2 insertions(+), 440 deletions(-) diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go index 4626e09f5..300f15ada 100644 --- a/core/adapters/mqtt/client.go +++ b/core/adapters/mqtt/client.go @@ -141,20 +141,6 @@ func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- i } }, }, - &client.SubReq{ - TopicFilter: []byte("+/devices/personalized/activations"), - QoS: mqtt.QoS2, - Handler: func(topic, msg []byte) { - if len(msg) == 0 { - return - } - chmsg <- Msg{ - Topic: string(topic), - Payload: msg, - Type: ABP, - } - }, - }, }, }) diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go index 2fc6ad91f..ab403ff19 100644 --- a/core/adapters/mqtt/client_test.go +++ b/core/adapters/mqtt/client_test.go @@ -182,41 +182,6 @@ func TestNewClient(t *testing.T) { // -------------------- - { - Desc(t, "Connect a client and receive a abp msg") - - // Build - cli, chmsg, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) - FatalUnless(t, err) - msg := Msg{ - Type: ABP, - Topic: "01020304/devices/personalized/activations", - Payload: []byte("message"), - } - - // Operate - testCli.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte(msg.Topic), - Message: msg.Payload, - }) - - // Check - var got Msg - select { - case got = <-chmsg: - case <-time.After(75 * time.Millisecond): - } - Check(t, msg, got, "MQTT Messages") - - // Clean - cli.Terminate() - <-time.After(time.Millisecond * 50) - } - - // -------------------- - { Desc(t, "Connect a client and send on a random topic") @@ -256,8 +221,8 @@ func TestNewClient(t *testing.T) { cli, chmsg, err := NewClient(id, BrokerAddr, GetLogger(t, "Test Logger")) FatalUnless(t, err) msg := Msg{ - Type: ABP, - Topic: "01020304/devices/personalized/activations", + Type: Down, + Topic: "0102030405060708/devices/0102030401020304/down", Payload: []byte("message"), } diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 17da491de..120be6952 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -49,8 +49,6 @@ type Msg struct { // msgType constants are used in MQTTMsg to characterise the kind of message processed const ( Down msgType = iota + 1 - ABP - OTAA ) type msgType byte @@ -161,14 +159,6 @@ func (a adapter) consumeMQTTMsg(chmsg <-chan Msg, handler core.HandlerServer) { if err != nil { a.Ctx.WithError(err).Debug("Unable to consume data down") } - case ABP: - req, err := handleABP(msg) - if err == nil { - _, err = handler.SubscribePersonalized(context.Background(), req) - } - if err != nil { - a.Ctx.WithError(err).Debug("Unable to consume ABP") - } default: a.Ctx.Debug("Unsupported MQTT message's type") } @@ -176,52 +166,6 @@ func (a adapter) consumeMQTTMsg(chmsg <-chan Msg, handler core.HandlerServer) { a.Ctx.Debug("Stop consuming MQTT messages") } -// handleABP parses and handles Application By Personalization request coming through MQTT -func handleABP(msg Msg) (*core.ABPSubHandlerReq, error) { - // Ensure the query / topic parameters are valid - topicInfos := strings.Split(msg.Topic, "/") - if len(topicInfos) != 4 { - return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") - } - - // Get the actual message, try messagePack then JSON - var req core.ABPSubAppReq - if _, err := req.UnmarshalMsg(msg.Payload); err != nil { - if err = json.Unmarshal(msg.Payload, &req); err != nil { - return nil, errors.New(errors.Structural, err) - } - } - - // Verify each parameter - appEUI, err := hex.DecodeString(topicInfos[0]) - if err != nil || len(appEUI) != 8 { - return nil, errors.New(errors.Structural, "Invalid Application EUI") - } - - devAddr, err := hex.DecodeString(req.DevAddr) - if err != nil || len(devAddr) != 4 { - return nil, errors.New(errors.Structural, "Invalid Device Address") - } - - nwkSKey, err := hex.DecodeString(req.NwkSKey) - if err != nil || len(nwkSKey) != 16 { - return nil, errors.New(errors.Structural, "Invalid Network Session Key") - } - - appSKey, err := hex.DecodeString(req.AppSKey) - if err != nil || len(appSKey) != 16 { - return nil, errors.New(errors.Structural, "Invalid Application Session Key") - } - - // Convert it to an handler subscription - return &core.ABPSubHandlerReq{ - AppEUI: appEUI, - DevAddr: devAddr, - NwkSKey: nwkSKey, - AppSKey: appSKey, - }, nil -} - // handleDataDown parses and handles Downlink message coming through MQTT func handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { // Ensure the query / topic parameters are valid diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go index 63e31da04..2706cbeac 100644 --- a/core/adapters/mqtt/mqtt_test.go +++ b/core/adapters/mqtt/mqtt_test.go @@ -170,256 +170,6 @@ func TestHandleDataDown(t *testing.T) { } } -func TestHandleABP(t *testing.T) { - { - Desc(t, "Invalid topic :: TTN") - - // Build - msg := Msg{ - Topic: "TTN", - Payload: []byte(`{ - "dev_addr":"01020304", - "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid topic :: 01/devices/personalized/activations") - - // Build - msg := Msg{ - Topic: "01/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"01020304", - "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid Device Address") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"patate", - "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid Application Session Key Address") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"01020304", - "apps_key":"aabbccdxxxxdeeaabbccddeeaabbccddeeff", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid Network Session Key Address") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"01020304", - "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", - "nwks_key":"014050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid JSON Payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr "01020304", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Incomplete JSON Payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"01020304", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid MsgPack Payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte{}, - Type: ABP, - } - var want *core.ABPSubHandlerReq - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Valid JSON Payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"01020304", - "apps_key":"aabbccddeeaabbccddeeaabbccddeeff", - "nwks_key":"01020304050607080900010203040506" - }`), - Type: ABP, - } - want := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, - } - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, nil, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } - - // -------------------- - - { - Desc(t, "Valid MsgPack Payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte{131, 168, 100, 101, 118, 95, 97, 100, 100, 114, 168, 48, 49, 48, 50, 48, 51, 48, 52, 168, 110, 119, 107, 115, 95, 107, 101, 121, 217, 32, 48, 49, 48, 50, 48, 51, 48, 52, 48, 53, 48, 54, 48, 55, 48, 56, 48, 57, 48, 48, 48, 49, 48, 50, 48, 51, 48, 52, 48, 53, 48, 54, 168, 97, 112, 112, 115, 95, 107, 101, 121, 217, 32, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 97, 97, 98, 98, 99, 99, 100, 100, 101, 101, 102, 102}, - Type: ABP, - } - want := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppSKey: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}, - } - - // Operate - req, err := handleABP(msg) - - // Check - CheckErrors(t, nil, err) - Check(t, want, req, "ABP Subscription Handler Requests") - } -} - func TestConsumeMQTTMsg(t *testing.T) { { Desc(t, "Consume Valid MsgDown") @@ -440,7 +190,6 @@ func TestConsumeMQTTMsg(t *testing.T) { DevEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, } - var wantABP *core.ABPSubHandlerReq // Operate chmsg <- Msg{ @@ -451,7 +200,6 @@ func TestConsumeMQTTMsg(t *testing.T) { // Checks Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -474,7 +222,6 @@ func TestConsumeMQTTMsg(t *testing.T) { adapter.Start(chmsg, handler) var wantDown *core.DataDownHandlerReq - var wantABP *core.ABPSubHandlerReq // Operate chmsg <- Msg{ @@ -485,84 +232,6 @@ func TestConsumeMQTTMsg(t *testing.T) { // Checks Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") - - // Clean - close(chmsg) - } - - // -------------------- - - { - Desc(t, "Consume Valid MsgABP") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - handler := mocks.NewHandlerServer() - chmsg := make(chan Msg) - adapter.Start(chmsg, handler) - - var wantDown *core.DataDownHandlerReq - wantABP := &core.ABPSubHandlerReq{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - AppSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: []byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Operate - chmsg <- Msg{ - Type: ABP, - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{ - "dev_addr":"01020304", - "apps_key":"01020304050607080900010203040506", - "nwks_key":"06050403020100090807060504030201" - }`), - } - - // Checks - Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") - - // Clean - close(chmsg) - } - - // -------------------- - - { - Desc(t, "Consume invalid MsgABP") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - handler := mocks.NewHandlerServer() - chmsg := make(chan Msg) - adapter.Start(chmsg, handler) - - var wantDown *core.DataDownHandlerReq - var wantABP *core.ABPSubHandlerReq - - // Operate - chmsg <- Msg{ - Type: ABP, - Topic: "0102030405060708/devices/personalized/activations", - Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), - } - - // Checks - Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) @@ -585,7 +254,6 @@ func TestConsumeMQTTMsg(t *testing.T) { adapter.Start(chmsg, handler) var wantDown *core.DataDownHandlerReq - var wantABP *core.ABPSubHandlerReq // Operate chmsg <- Msg{ @@ -596,7 +264,6 @@ func TestConsumeMQTTMsg(t *testing.T) { // Checks Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - Check(t, wantABP, handler.InSubscribePersonalized.Req, "Handler Subscriptions") // Clean close(chmsg) From 7da0eb3ad64b21873aa422febfd87c6f547f7a88 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 01:14:29 +0100 Subject: [PATCH 1128/2266] [refactor/storages] Decouple network controller from app storage --- core/components/broker/appStorage.go | 117 ++++++++++++++++++ core/components/broker/appStorage_test.go | 141 ++++++++++++++++++++++ 2 files changed, 258 insertions(+) create mode 100644 core/components/broker/appStorage.go create mode 100644 core/components/broker/appStorage_test.go diff --git a/core/components/broker/appStorage.go b/core/components/broker/appStorage.go new file mode 100644 index 000000000..0ded59daf --- /dev/null +++ b/core/components/broker/appStorage.go @@ -0,0 +1,117 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "bytes" + "encoding" + + dbutil "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" +) + +// NOTE: This is a partial duplication of handler.DevStorage + +// AppStorage gives a facade for manipulating the broker applications infos +type AppStorage interface { + read(appEUI []byte, devEUI []byte) (appEntry, error) + upsert(entry appEntry) error + done() error +} + +type appEntry struct { + Dialer Dialer + AppEUI []byte + DevEUI []byte + DevNonces [][]byte + Password []byte + Salt []byte +} + +type appStorage struct { + db dbutil.Interface +} + +// NewAppStorage constructs a new broker controller +func NewAppStorage(name string) (AppStorage, error) { + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + + return &appStorage{db: itf}, nil +} + +// read implements the AppStorage interface +func (s *appStorage) read(appEUI []byte, devEUI []byte) (appEntry, error) { + itf, err := s.db.Read(nil, &appEntry{}, appEUI, devEUI) + if err != nil { + return appEntry{}, err + } + entries, ok := itf.([]appEntry) + if !ok || len(entries) != 1 { + return appEntry{}, errors.New(errors.Structural, "Invalid stored entry") + } + return entries[0], nil +} + +// upsert implements the AppStorage interface +func (s *appStorage) upsert(entry appEntry) error { + return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) +} + +// done implements the AppStorage interface { +func (s *appStorage) done() error { + return s.db.Close() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interfaceA +func (e appEntry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.AppEUI) + rw.Write(e.DevEUI) + rw.Write(e.Password) + rw.Write(e.Salt) + rw.Write(e.Dialer.MarshalSafely()) + buf := new(bytes.Buffer) + for _, n := range e.DevNonces { + _, _ = buf.Write(n) + } + rw.Write(buf.Bytes()) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *appEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { + e.AppEUI = make([]byte, len(data)) + copy(e.AppEUI, data) + }) + rw.Read(func(data []byte) { + e.DevEUI = make([]byte, len(data)) + copy(e.DevEUI, data) + }) + rw.Read(func(data []byte) { + e.Password = make([]byte, len(data)) + copy(e.Password, data) + }) + rw.Read(func(data []byte) { + e.Salt = make([]byte, len(data)) + copy(e.Salt, data) + }) + rw.Read(func(data []byte) { + e.Dialer = NewDialer(data) + }) + rw.Read(func(data []byte) { + n := len(data) / 2 // DevNonce -> 2-bytes + for i := 0; i < int(n); i++ { + devNonce := make([]byte, 2, 2) + copy(devNonce, data[2*i:2*i+2]) + e.DevNonces = append(e.DevNonces, devNonce) + } + }) + return rw.Err() +} diff --git a/core/components/broker/appStorage_test.go b/core/components/broker/appStorage_test.go new file mode 100644 index 000000000..3996aaa55 --- /dev/null +++ b/core/components/broker/appStorage_test.go @@ -0,0 +1,141 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "os" + "path" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const devDB = "TestDevStorage.db" + +func TestReadStore(t *testing.T) { + var db AppStorage + defer func() { + os.Remove(path.Join(os.TempDir(), devDB)) + }() + + // ------------------ + + { + Desc(t, "Create a new storage") + var err error + db, err = NewAppStorage(path.Join(os.TempDir(), devDB)) + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "Store and read a registration") + + // Build + entry := appEntry{ + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 2}, + DevEUI: []byte{0, 2}, + DevNonces: [][]byte{[]byte{1, 2}}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, + } + + // Operate + err := db.upsert(entry) + FatalUnless(t, err) + got, err := db.read(entry.AppEUI, entry.DevEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, entry, got, "Device Entries") + } + + // ------------------ + + { + Desc(t, "read a non-existing registration") + + // Build + appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 2} + devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} + + // Operate + _, err := db.read(appEUI, devEUI) + + // Check + CheckErrors(t, ErrNotFound, err) + } + + // ------------------ + + { + Desc(t, "Store twice the same registration") + + // Build + entry := appEntry{ + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 1}, + DevEUI: []byte{0, 1}, + DevNonces: [][]byte{[]byte{1, 2}}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, + } + + // Operate + err := db.upsert(entry) + FatalUnless(t, err) + err = db.upsert(entry) + FatalUnless(t, err) + got, err := db.read(entry.AppEUI, entry.DevEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, entry, got, "Device Entries") + } + + // ------------------ + + { + Desc(t, "Update an entry") + + // Build + entry := appEntry{ + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 1}, + DevEUI: []byte{0, 1}, + DevNonces: [][]byte{[]byte{1, 2}}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, + } + update := appEntry{ + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 1}, + DevEUI: []byte{0, 1}, + DevNonces: [][]byte{[]byte{1, 2}, []byte{2, 5}}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, + } + + // Operate + err := db.upsert(entry) + FatalUnless(t, err) + err = db.upsert(update) + got, errRead := db.read(entry.AppEUI, entry.DevEUI) + FatalUnless(t, errRead) + + // Check + CheckErrors(t, nil, err) + Check(t, update, got, "Device Entries") + } + + // ------------------ + + { + Desc(t, "Close the storage") + err := db.done() + CheckErrors(t, nil, err) + } +} From 7c3dc2463c96895eaa503bf014e4bc9a0a25d1a7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 02:07:56 +0100 Subject: [PATCH 1129/2266] [refactor/storages] Rewrite network controller methods --- core/components/broker/controller.go | 176 ++++++++------------------- 1 file changed, 53 insertions(+), 123 deletions(-) diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 50d6cb57b..d8267debf 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -4,50 +4,40 @@ package broker import ( - "bytes" - "encoding/binary" - "fmt" + "encoding" "math" "reflect" "sync" dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" ) // NetworkController gives a facade for manipulating the broker databases and devices type NetworkController interface { - LookupDevices(devAddr []byte) ([]devEntry, error) - WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) - StoreDevice(devAddr []byte, entry devEntry) error - ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) - UpdateActivation(entry appEntry) error - UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error - Close() error + read(devAddr []byte) ([]devEntry, error) + upsert(entry devEntry) error + wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) + done() error } type devEntry struct { - Dialer Dialer AppEUI []byte + DevAddr []byte DevEUI []byte - NwkSKey [16]byte + Dialer Dialer FCntUp uint32 -} - -type appEntry struct { - Dialer Dialer - AppEUI []byte - DevEUI []byte - DevNonces [][]byte + NwkSKey [16]byte } type controller struct { sync.RWMutex - db dbutil.Interface - Devices string - Applications string + db dbutil.Interface } +var dbDevices = []byte("devices") + // NewNetworkController constructs a new broker controller func NewNetworkController(name string) (NetworkController, error) { itf, err := dbutil.New(name) @@ -55,45 +45,20 @@ func NewNetworkController(name string) (NetworkController, error) { return nil, errors.New(errors.Operational, err) } - return &controller{db: itf, Devices: "Devices", Applications: "Applications"}, nil + return &controller{db: itf}, nil } -// LookupDevices implements the broker.NetworkController interface -func (s *controller) LookupDevices(devAddr []byte) ([]devEntry, error) { - s.RLock() - defer s.RUnlock() - entries, err := s.db.Lookup(s.Devices, devAddr, &devEntry{}) +// read implements the NetworkController interface +func (s *controller) read(devAddr []byte) ([]devEntry, error) { + entries, err := s.db.Read(devAddr, &devEntry{}, dbDevices) if err != nil { return nil, err } return entries.([]devEntry), nil } -// ReadActivation implements the broker.NetworkController interface -func (s *controller) ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) { - s.RLock() - defer s.RUnlock() - itf, err := s.db.Lookup(fmt.Sprintf("%x.%x", appEUI, devEUI), []byte("entry"), &appEntry{}) - if err != nil { - return appEntry{}, err - } - entries := itf.([]appEntry) - if len(entries) != 1 { - // NOTE should clean up the entry ? - return appEntry{}, errors.New(errors.Structural, "Invalid stored entry") - } - return entries[0], nil -} - -// UpdateAction implements the broker.NetworkController interface -func (s *controller) UpdateActivation(entry appEntry) error { - s.Lock() - defer s.Unlock() - return s.db.Replace(fmt.Sprintf("%x.%x", entry.AppEUI, entry.DevEUI), []byte("entry"), []dbutil.Entry{&entry}) -} - -// WholeCounter implements the broker.NetworkController interface -func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { +// wholeCounter implements the broker.NetworkController interface +func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { upperSup := int(math.Pow(2, 16)) diff := int(devCnt) - (int(entryCnt) % upperSup) var offset int @@ -108,98 +73,63 @@ func (s *controller) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error return entryCnt + uint32(offset), nil } -// UpdateFCnt implements the broker.NetworkController interface -func (s *controller) UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error { +// upsert implements the broker.NetworkController interface +func (s *controller) upsert(update devEntry) error { s.Lock() defer s.Unlock() - itf, err := s.db.Lookup(s.Devices, devAddr, &devEntry{}) + itf, err := s.db.Read(update.DevAddr, &devEntry{}, dbDevices) if err != nil { return err } entries := itf.([]devEntry) - var newEntries []dbutil.Entry + var newEntries []encoding.BinaryMarshaler for _, e := range entries { entry := new(devEntry) *entry = e - if reflect.DeepEqual(entry.AppEUI, appEUI) && reflect.DeepEqual(entry.DevEUI, devEUI) { - entry.FCntUp = fcnt + if reflect.DeepEqual(entry.AppEUI, update.AppEUI) && reflect.DeepEqual(entry.DevEUI, update.DevEUI) { + newEntries = append(newEntries, update) } newEntries = append(newEntries, entry) } - - return s.db.Replace(s.Devices, devAddr, newEntries) -} - -// StoreDevice implements the broker.NetworkController interface -func (s *controller) StoreDevice(devAddr []byte, entry devEntry) error { - s.Lock() - defer s.Unlock() - return s.db.Store(s.Devices, devAddr, []dbutil.Entry{&entry}) + return s.db.Update(update.DevAddr, newEntries, dbDevices) } -// Close implements the broker.NetworkController interface -func (s *controller) Close() error { +// done implements the broker.NetworkController interface +func (s *controller) done() error { return s.db.Close() } // MarshalBinary implements the encoding.BinaryMarshaler interface func (e devEntry) MarshalBinary() ([]byte, error) { - buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, e.AppEUI) // 8 - binary.Write(buf, binary.BigEndian, e.DevEUI) // 8 - binary.Write(buf, binary.BigEndian, e.NwkSKey) // 16 - binary.Write(buf, binary.BigEndian, e.FCntUp) // 4 - if len(buf.Bytes()) != 36 { - return nil, errors.New(errors.Structural, "Device entry was invalid. Cannot Marshal") - } - binary.Write(buf, binary.BigEndian, e.Dialer.MarshalSafely()) - return buf.Bytes(), nil + rw := readwriter.New(nil) + rw.Write(e.AppEUI) + rw.Write(e.DevEUI) + rw.Write(e.DevAddr) + rw.Write(e.NwkSKey[:]) + rw.Write(e.Dialer.MarshalSafely()) + return rw.Bytes() } // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devEntry) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - e.AppEUI = make([]byte, 8, 8) - binary.Read(buf, binary.BigEndian, &e.AppEUI) - e.DevEUI = make([]byte, 8, 8) - binary.Read(buf, binary.BigEndian, &e.DevEUI) - binary.Read(buf, binary.BigEndian, &e.NwkSKey) // fixed-length array - binary.Read(buf, binary.BigEndian, &e.FCntUp) - e.Dialer = NewDialer(buf.Next(buf.Len())) - return nil -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e appEntry) MarshalBinary() ([]byte, error) { - buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, e.AppEUI) - binary.Write(buf, binary.BigEndian, e.DevEUI) - binary.Write(buf, binary.BigEndian, uint16(len(e.DevNonces))) - for _, n := range e.DevNonces { - binary.Write(buf, binary.BigEndian, n) - } - if len(buf.Bytes()) != 16+2+2*len(e.DevNonces) { - return nil, errors.New(errors.Structural, "App entry was invalid. Cannot Marshal") - } - binary.Write(buf, binary.BigEndian, e.Dialer.MarshalSafely()) - return buf.Bytes(), nil -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *appEntry) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - e.AppEUI = make([]byte, 8, 8) - binary.Read(buf, binary.BigEndian, &e.AppEUI) - e.DevEUI = make([]byte, 8, 8) - binary.Read(buf, binary.BigEndian, &e.DevEUI) - var n uint16 - binary.Read(buf, binary.BigEndian, &n) - for i := 0; i < int(n); i++ { - devNonce := make([]byte, 2, 2) - binary.Read(buf, binary.BigEndian, &devNonce) - e.DevNonces = append(e.DevNonces, devNonce) - } - e.Dialer = NewDialer(buf.Next(buf.Len())) - return nil + rw := readwriter.New(data) + rw.Read(func(data []byte) { + e.AppEUI = make([]byte, len(data)) + copy(e.AppEUI, data) + }) + rw.Read(func(data []byte) { + e.DevEUI = make([]byte, len(data)) + copy(e.DevEUI, data) + }) + rw.Read(func(data []byte) { + e.DevAddr = make([]byte, len(data)) + copy(e.DevAddr, data) + }) + + rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + rw.Read(func(data []byte) { + e.Dialer = NewDialer(data) + }) + return rw.Err() } From dbb1ce383cce2ed280154b54f3966943a4a3e6c8 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 10:03:46 +0100 Subject: [PATCH 1130/2266] [refactor/storages] Fix oversight in handler -> dev addr msb are nwkID --- core/components/handler/handler.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 49982ed2e..5e5610a1c 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -416,6 +416,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da appNonce, devAddr := make([]byte, 4), [4]byte{} binary.BigEndian.PutUint32(appNonce, rdn.Uint32()) binary.BigEndian.PutUint32(devAddr[:], rdn.Uint32()) + devAddr[0] = (h.Configuration.NetID[2] << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb buf := make([]byte, 16) copy(buf[1:4], appNonce[:3]) From f7393d6540fec26e6f4a25f7b3571f421bfad19f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 10:41:59 +0100 Subject: [PATCH 1131/2266] [refactor/storages] Regenerate mocks + tests for NetworkController --- core/broker.pb.go | 425 ++-------------------- core/components/broker/controller_test.go | 160 +++----- core/components/broker/mocks_test.go | 134 +++---- core/mocks/mocks.go | 16 - core/protos/broker.proto | 10 - 5 files changed, 139 insertions(+), 606 deletions(-) diff --git a/core/broker.pb.go b/core/broker.pb.go index 74a653837..e4638b305 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -16,8 +16,6 @@ It has these top-level messages: DataBrokerReq DataBrokerRes - ABPSubBrokerReq - ABPSubBrokerRes JoinBrokerReq JoinBrokerRes Metadata @@ -117,26 +115,6 @@ func (m *DataBrokerRes) GetMetadata() *Metadata { return nil } -type ABPSubBrokerReq struct { - HandlerNet string `protobuf:"bytes,1,opt,name=HandlerNet,json=handlerNet,proto3" json:"HandlerNet,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` -} - -func (m *ABPSubBrokerReq) Reset() { *m = ABPSubBrokerReq{} } -func (m *ABPSubBrokerReq) String() string { return proto.CompactTextString(m) } -func (*ABPSubBrokerReq) ProtoMessage() {} -func (*ABPSubBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } - -type ABPSubBrokerRes struct { -} - -func (m *ABPSubBrokerRes) Reset() { *m = ABPSubBrokerRes{} } -func (m *ABPSubBrokerRes) String() string { return proto.CompactTextString(m) } -func (*ABPSubBrokerRes) ProtoMessage() {} -func (*ABPSubBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } - type JoinBrokerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` @@ -147,7 +125,7 @@ type JoinBrokerReq struct { func (m *JoinBrokerReq) Reset() { *m = JoinBrokerReq{} } func (m *JoinBrokerReq) String() string { return proto.CompactTextString(m) } func (*JoinBrokerReq) ProtoMessage() {} -func (*JoinBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{4} } +func (*JoinBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } func (m *JoinBrokerReq) GetMetadata() *Metadata { if m != nil { @@ -165,7 +143,7 @@ type JoinBrokerRes struct { func (m *JoinBrokerRes) Reset() { *m = JoinBrokerRes{} } func (m *JoinBrokerRes) String() string { return proto.CompactTextString(m) } func (*JoinBrokerRes) ProtoMessage() {} -func (*JoinBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{5} } +func (*JoinBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } func (m *JoinBrokerRes) GetPayload() *LoRaWANJoinAccept { if m != nil { @@ -184,8 +162,6 @@ func (m *JoinBrokerRes) GetMetadata() *Metadata { func init() { proto.RegisterType((*DataBrokerReq)(nil), "core.DataBrokerReq") proto.RegisterType((*DataBrokerRes)(nil), "core.DataBrokerRes") - proto.RegisterType((*ABPSubBrokerReq)(nil), "core.ABPSubBrokerReq") - proto.RegisterType((*ABPSubBrokerRes)(nil), "core.ABPSubBrokerRes") proto.RegisterType((*JoinBrokerReq)(nil), "core.JoinBrokerReq") proto.RegisterType((*JoinBrokerRes)(nil), "core.JoinBrokerRes") } @@ -199,7 +175,6 @@ var _ grpc.ClientConn type BrokerClient interface { HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) HandleJoin(ctx context.Context, in *JoinBrokerReq, opts ...grpc.CallOption) (*JoinBrokerRes, error) - SubscribePersonalized(ctx context.Context, in *ABPSubBrokerReq, opts ...grpc.CallOption) (*ABPSubBrokerRes, error) } type brokerClient struct { @@ -228,21 +203,11 @@ func (c *brokerClient) HandleJoin(ctx context.Context, in *JoinBrokerReq, opts . return out, nil } -func (c *brokerClient) SubscribePersonalized(ctx context.Context, in *ABPSubBrokerReq, opts ...grpc.CallOption) (*ABPSubBrokerRes, error) { - out := new(ABPSubBrokerRes) - err := grpc.Invoke(ctx, "/core.Broker/SubscribePersonalized", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - // Server API for Broker service type BrokerServer interface { HandleData(context.Context, *DataBrokerReq) (*DataBrokerRes, error) HandleJoin(context.Context, *JoinBrokerReq) (*JoinBrokerRes, error) - SubscribePersonalized(context.Context, *ABPSubBrokerReq) (*ABPSubBrokerRes, error) } func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { @@ -273,18 +238,6 @@ func _Broker_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(i return out, nil } -func _Broker_SubscribePersonalized_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(ABPSubBrokerReq) - if err := dec(in); err != nil { - return nil, err - } - out, err := srv.(BrokerServer).SubscribePersonalized(ctx, in) - if err != nil { - return nil, err - } - return out, nil -} - var _Broker_serviceDesc = grpc.ServiceDesc{ ServiceName: "core.Broker", HandlerType: (*BrokerServer)(nil), @@ -297,10 +250,6 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ MethodName: "HandleJoin", Handler: _Broker_HandleJoin_Handler, }, - { - MethodName: "SubscribePersonalized", - Handler: _Broker_SubscribePersonalized_Handler, - }, }, Streams: []grpc.StreamDesc{}, } @@ -381,72 +330,6 @@ func (m *DataBrokerRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ABPSubBrokerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ABPSubBrokerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.HandlerNet) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(len(m.HandlerNet))) - i += copy(data[i:], m.HandlerNet) - } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - } - return i, nil -} - -func (m *ABPSubBrokerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ABPSubBrokerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - func (m *JoinBrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -600,40 +483,6 @@ func (m *DataBrokerRes) Size() (n int) { return n } -func (m *ABPSubBrokerReq) Size() (n int) { - var l int - _ = l - l = len(m.HandlerNet) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - } - return n -} - -func (m *ABPSubBrokerRes) Size() (n int) { - var l int - _ = l - return n -} - func (m *JoinBrokerReq) Size() (n int) { var l int _ = l @@ -927,228 +776,6 @@ func (m *DataBrokerRes) Unmarshal(data []byte) error { } return nil } -func (m *ABPSubBrokerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ABPSubBrokerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ABPSubBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field HandlerNet", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.HandlerNet = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ABPSubBrokerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ABPSubBrokerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ABPSubBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *JoinBrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -1578,31 +1205,25 @@ var ( ) var fileDescriptorBroker = []byte{ - // 405 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x53, 0x5d, 0x4b, 0xe3, 0x40, - 0x14, 0xdd, 0xb4, 0x25, 0xc9, 0xde, 0x6d, 0x77, 0xb7, 0xb3, 0x74, 0x37, 0xe4, 0xa1, 0x2c, 0x7d, - 0x5a, 0x56, 0x28, 0x58, 0xc1, 0xf7, 0x94, 0x16, 0xfc, 0x0c, 0x25, 0x45, 0x7c, 0x9e, 0x64, 0x06, - 0x5a, 0x1a, 0x33, 0x71, 0x26, 0x5a, 0x2a, 0xf8, 0xac, 0x3f, 0xc1, 0x7f, 0xe3, 0xab, 0x8f, 0xfe, - 0x04, 0xd1, 0x3f, 0x62, 0x32, 0xd3, 0x9a, 0x26, 0x14, 0x7d, 0xf2, 0x61, 0x60, 0xee, 0xb9, 0xe7, - 0xf6, 0x9c, 0xde, 0x33, 0x81, 0xba, 0xcf, 0xd9, 0x8c, 0xf2, 0x6e, 0xcc, 0x59, 0xc2, 0x50, 0x2d, - 0x60, 0x9c, 0xda, 0x8d, 0x90, 0x71, 0x3c, 0xc7, 0x91, 0x02, 0x6d, 0xc8, 0x40, 0x75, 0xef, 0x4c, - 0xa0, 0x31, 0xc0, 0x09, 0xee, 0xcb, 0x21, 0x8f, 0x9e, 0xa3, 0x2d, 0x30, 0x46, 0x78, 0x11, 0x32, - 0x4c, 0x2c, 0xed, 0xaf, 0xf6, 0xef, 0x5b, 0xaf, 0xd9, 0x95, 0xf4, 0x23, 0xe6, 0xe1, 0x53, 0xc7, - 0xcd, 0xc8, 0x9e, 0x11, 0x2b, 0x06, 0xfa, 0x0f, 0xe6, 0x31, 0x4d, 0x30, 0x49, 0x41, 0xab, 0x22, - 0xd9, 0xdf, 0x15, 0x7b, 0x85, 0x7a, 0xe6, 0xd9, 0xf2, 0x56, 0x56, 0x12, 0x9f, 0xa7, 0x74, 0x0d, - 0x3f, 0x9c, 0xfe, 0x68, 0x7c, 0xe1, 0xe7, 0xff, 0xaa, 0x0d, 0xb0, 0x87, 0x23, 0x12, 0x52, 0xee, - 0xd2, 0x44, 0xca, 0x7d, 0xf5, 0x60, 0xf2, 0x86, 0xa0, 0xdf, 0xa0, 0x3b, 0x71, 0x3c, 0x3c, 0xd9, - 0x97, 0x3f, 0x5e, 0xf7, 0x74, 0x2c, 0x2b, 0x64, 0x81, 0x31, 0xa0, 0x97, 0x0e, 0x21, 0xdc, 0xaa, - 0xca, 0x86, 0x41, 0x54, 0x99, 0x75, 0xdc, 0xf9, 0x6c, 0x7c, 0x48, 0x17, 0x56, 0x4d, 0x75, 0x22, - 0x55, 0x76, 0x9a, 0x65, 0x79, 0xd1, 0xb9, 0xd1, 0xa0, 0x71, 0xc0, 0xa6, 0x51, 0x6e, 0x28, 0x17, - 0xd4, 0x0a, 0x82, 0x29, 0x9e, 0x0a, 0xae, 0x19, 0x21, 0xb2, 0x42, 0x36, 0x98, 0x29, 0xee, 0xb2, - 0x28, 0xa0, 0x4b, 0x27, 0x26, 0x59, 0xd6, 0x85, 0xdd, 0xd4, 0x3e, 0xd8, 0xcd, 0x6d, 0xc9, 0x89, - 0x40, 0xdb, 0xe5, 0x18, 0xfe, 0x14, 0x62, 0xc8, 0xc8, 0x4e, 0x10, 0xd0, 0x38, 0xc9, 0xc3, 0x58, - 0xdb, 0x4a, 0xa5, 0xb8, 0x95, 0x75, 0x2b, 0xd5, 0xf7, 0xad, 0xf4, 0xee, 0x35, 0xd0, 0x95, 0x0d, - 0xb4, 0xbb, 0x8a, 0x27, 0x0b, 0x1d, 0xfd, 0x52, 0x23, 0x85, 0x77, 0x69, 0x6f, 0x00, 0x45, 0x3e, - 0x97, 0xb9, 0x5c, 0xcd, 0x15, 0x16, 0x6d, 0x6f, 0x00, 0x05, 0x1a, 0x42, 0x2b, 0xcd, 0x47, 0x04, - 0x7c, 0xea, 0xd3, 0x11, 0xe5, 0x82, 0x45, 0x38, 0x9c, 0x5e, 0x51, 0x82, 0x5a, 0x8a, 0x5d, 0x7a, - 0x3e, 0xf6, 0x46, 0x58, 0xf4, 0x7f, 0x3e, 0x3c, 0xb7, 0xb5, 0xc7, 0xf4, 0x3c, 0xa5, 0xe7, 0xee, - 0xa5, 0xfd, 0xc5, 0xd7, 0xe5, 0x57, 0xb5, 0xf3, 0x1a, 0x00, 0x00, 0xff, 0xff, 0x19, 0xac, 0x93, - 0x41, 0x86, 0x03, 0x00, 0x00, + // 312 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2a, 0xca, 0xcf, + 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, + 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, + 0x0c, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0x44, 0x27, 0xb0, 0xa6, 0xa0, 0xd4, 0x42, 0x21, 0x6d, 0x2e, + 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x41, + 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, + 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, + 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xb5, + 0x33, 0x72, 0xf1, 0x7a, 0xe5, 0x67, 0xe6, 0x21, 0x3c, 0x25, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, + 0x1a, 0xea, 0x09, 0xb6, 0x89, 0x27, 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, 0xbb, 0xa4, 0x96, 0x81, + 0xc4, 0x99, 0x20, 0xe2, 0x29, 0x60, 0x9e, 0x90, 0x14, 0x17, 0x07, 0x50, 0xdc, 0x2f, 0x3f, 0x2f, + 0x39, 0x55, 0x82, 0x19, 0x2c, 0xc3, 0x91, 0x02, 0xe5, 0xa3, 0xb8, 0x84, 0x85, 0x80, 0x4b, 0x3a, + 0xd0, 0x5c, 0x52, 0x2c, 0x64, 0x88, 0xee, 0x69, 0x71, 0x14, 0x4f, 0x83, 0x14, 0x3b, 0x26, 0x27, + 0xa7, 0x16, 0x94, 0x20, 0xbc, 0x2e, 0xc1, 0xc5, 0x0e, 0x74, 0x8c, 0x63, 0x4a, 0x4a, 0x11, 0xd4, + 0x95, 0xec, 0x29, 0x10, 0x2e, 0x8a, 0x53, 0x98, 0xf1, 0x3b, 0xc5, 0xa8, 0x82, 0x8b, 0x0d, 0xe2, + 0x0a, 0x21, 0x33, 0x2e, 0x2e, 0x8f, 0xc4, 0xbc, 0x94, 0x9c, 0x54, 0x50, 0x08, 0x0b, 0x09, 0x43, + 0x74, 0xa0, 0x24, 0x02, 0x29, 0x2c, 0x82, 0xc5, 0x08, 0x7d, 0x20, 0x47, 0xc2, 0xf4, 0xa1, 0x84, + 0xb3, 0x14, 0x16, 0xc1, 0x62, 0x27, 0x81, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x00, 0xf1, 0x03, 0x20, + 0x9e, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x9c, 0xf6, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, + 0x57, 0x4c, 0xa7, 0xbb, 0xac, 0x02, 0x00, 0x00, } diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index 34f8b71e5..0373538fb 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -25,7 +25,7 @@ func TestNetworkControllerDevice(t *testing.T) { Desc(t, "Create a new NetworkController") db, err := NewNetworkController(NetworkControllerDB) CheckErrors(t, nil, err) - err = db.Close() + err = db.done() CheckErrors(t, nil, err) } @@ -36,8 +36,8 @@ func TestNetworkControllerDevice(t *testing.T) { // Build db, _ := NewNetworkController(NetworkControllerDB) - devAddr := []byte{1, 2, 3, 4} entry := devEntry{ + DevAddr: []byte{1, 2, 3, 4}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, @@ -46,9 +46,9 @@ func TestNetworkControllerDevice(t *testing.T) { } // Operate - err := db.StoreDevice(devAddr, entry) + err := db.upsert(entry) FatalUnless(t, err) - entries, err := db.LookupDevices(devAddr) + entries, err := db.read(entry.DevAddr) // Expect want := []devEntry{entry} @@ -56,7 +56,7 @@ func TestNetworkControllerDevice(t *testing.T) { // Check CheckErrors(t, nil, err) Check(t, want, entries, "DevEntries") - _ = db.Close() + _ = db.done() } // ------------------- @@ -66,8 +66,8 @@ func TestNetworkControllerDevice(t *testing.T) { // Build db, _ := NewNetworkController(NetworkControllerDB) - devAddr := []byte{1, 2, 3, 5} entry1 := devEntry{ + DevAddr: []byte{1, 2, 3, 5}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, @@ -75,6 +75,7 @@ func TestNetworkControllerDevice(t *testing.T) { FCntUp: 14, } entry2 := devEntry{ + DevAddr: []byte{1, 2, 3, 5}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, @@ -83,11 +84,11 @@ func TestNetworkControllerDevice(t *testing.T) { } // Operate - err := db.StoreDevice(devAddr, entry1) + err := db.upsert(entry1) FatalUnless(t, err) - err = db.StoreDevice(devAddr, entry2) + err = db.upsert(entry2) FatalUnless(t, err) - entries, err := db.LookupDevices(devAddr) + entries, err := db.read(entry1.DevAddr) // Expectations want := []devEntry{entry1, entry2} @@ -95,7 +96,7 @@ func TestNetworkControllerDevice(t *testing.T) { // Check CheckErrors(t, nil, err) Check(t, want, entries, "DevEntries") - _ = db.Close() + _ = db.done() } // ------------------- @@ -108,7 +109,7 @@ func TestNetworkControllerDevice(t *testing.T) { devAddr := []byte{0, 0, 0, 1} // Operate - entries, err := db.LookupDevices(devAddr) + entries, err := db.read(devAddr) // Expect var want []devEntry @@ -116,7 +117,7 @@ func TestNetworkControllerDevice(t *testing.T) { // Checks CheckErrors(t, ErrNotFound, err) Check(t, want, entries, "DevEntries") - _ = db.Close() + _ = db.done() } // ------------------- @@ -126,9 +127,9 @@ func TestNetworkControllerDevice(t *testing.T) { // Build db, _ := NewNetworkController(NetworkControllerDB) - _ = db.Close() - devAddr := []byte{1, 0, 0, 2} + _ = db.done() entry := devEntry{ + DevAddr: []byte{1, 0, 0, 2}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, @@ -137,7 +138,7 @@ func TestNetworkControllerDevice(t *testing.T) { } // Operate - err := db.StoreDevice(devAddr, entry) + err := db.upsert(entry) // Checks CheckErrors(t, ErrOperational, err) @@ -150,11 +151,11 @@ func TestNetworkControllerDevice(t *testing.T) { // Build db, _ := NewNetworkController(NetworkControllerDB) - _ = db.Close() + _ = db.done() devAddr := []byte{1, 2, 3, 4} // Operate - entries, err := db.LookupDevices(devAddr) + entries, err := db.read(devAddr) // Expect var want []devEntry @@ -171,8 +172,8 @@ func TestNetworkControllerDevice(t *testing.T) { // Build db, _ := NewNetworkController(NetworkControllerDB) - devAddr := []byte{1, 0, 0, 4} entry := devEntry{ + DevAddr: []byte{1, 0, 0, 4}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, @@ -181,39 +182,20 @@ func TestNetworkControllerDevice(t *testing.T) { } // Operate - err := db.StoreDevice(devAddr, entry) + err := db.upsert(entry) + FatalUnless(t, err) + entry.FCntUp = 42 + err = db.upsert(entry) + FatalUnless(t, err) + entries, err := db.read(entry.DevAddr) FatalUnless(t, err) - err1 := db.UpdateFCnt(entry.AppEUI, entry.DevEUI, devAddr, 42) - entries, err2 := db.LookupDevices(devAddr) - // Expectations + // Expectation want := []devEntry{entry} - want[0].FCntUp = 42 // Check - CheckErrors(t, nil, err1) - CheckErrors(t, nil, err2) Check(t, want, entries, "DevEntries") - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Update counter -> fail to lookup") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - devAddr := []byte{14, 14, 14, 14} - appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} - devEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} - - // Operate - err := db.UpdateFCnt(appEUI, devEUI, devAddr, 14) - - // Checks - CheckErrors(t, ErrNotFound, err) - _ = db.Close() + _ = db.done() } // ------------------- @@ -223,8 +205,8 @@ func TestNetworkControllerDevice(t *testing.T) { // Build db, _ := NewNetworkController(NetworkControllerDB) - devAddr := []byte{8, 8, 8, 8} entry1 := devEntry{ + DevAddr: []byte{8, 8, 8, 8}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, @@ -232,6 +214,7 @@ func TestNetworkControllerDevice(t *testing.T) { FCntUp: 14, } entry2 := devEntry{ + DevAddr: []byte{8, 8, 8, 8}, Dialer: NewDialer([]byte("url")), AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, @@ -240,22 +223,22 @@ func TestNetworkControllerDevice(t *testing.T) { } // Operate - err := db.StoreDevice(devAddr, entry1) + err := db.upsert(entry1) + FatalUnless(t, err) + err = db.upsert(entry2) + FatalUnless(t, err) + entry2.FCntUp = 8 + err = db.upsert(entry2) FatalUnless(t, err) - err = db.StoreDevice(devAddr, entry2) + entries, err := db.read(entry1.DevAddr) FatalUnless(t, err) - err1 := db.UpdateFCnt(entry2.AppEUI, entry2.DevEUI, devAddr, 8) - entries, err2 := db.LookupDevices(devAddr) // Expectations want := []devEntry{entry1, entry2} - want[1].FCntUp = 8 // Check - CheckErrors(t, nil, err1) - CheckErrors(t, nil, err2) Check(t, want, entries, "DevEntries") - _ = db.Close() + _ = db.done() } // -------------------- @@ -269,13 +252,13 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt + 1 // Operate - cnt32, err := db.WholeCounter(cnt16, wholeCnt) + cnt32, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) Check(t, wholeCnt+1, cnt32, "Counters") - _ = db.Close() + _ = db.done() } // -------------------- @@ -289,12 +272,12 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt - 1 // Operate - _, err := db.WholeCounter(cnt16, wholeCnt) + _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, ErrStructural, err) - _ = db.Close() + _ = db.done() } // -------------------- @@ -308,13 +291,13 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 2) // Operate - cnt32, err := db.WholeCounter(cnt16, wholeCnt) + cnt32, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) Check(t, wholeCnt+2, cnt32, "Counters") - _ = db.Close() + _ = db.done() } // -------------------- @@ -328,12 +311,12 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 45000) // Operate - _, err := db.WholeCounter(cnt16, wholeCnt) + _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, ErrStructural, err) - _ = db.Close() + _ = db.done() } // -------------------- @@ -347,61 +330,12 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(2) // Operate - cnt32, err := db.WholeCounter(cnt16, wholeCnt) + cnt32, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) Check(t, wholeCnt+3, cnt32, "Counters") - _ = db.Close() - } - - // ------------------- - - { - Desc(t, "Test update then read an activation") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry := appEntry{ - Dialer: NewDialer([]byte("Dialer")), - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonces: [][]byte{{1, 2}, {3, 4}}, - } - - // Operate - err := db.UpdateActivation(entry) - FatalUnless(t, err) - got, err := db.ReadActivation(entry.AppEUI, entry.DevEUI) - - // Chek - CheckErrors(t, nil, err) - Check(t, entry, got, "Entries") - - _ = db.Close() - - } - - // ------------------- - - { - Desc(t, "Test ReadActivation -> not found") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - appEUI := []byte{1, 1, 1, 1, 1, 1, 1, 2} - devEUI := []byte{2, 2, 2, 2, 2, 2, 2, 3} - var entry appEntry - - // Operate - got, err := db.ReadActivation(appEUI, devEUI) - - // Chek - CheckErrors(t, ErrNotFound, err) - Check(t, entry, got, "Entries") - - _ = db.Close() - + _ = db.done() } } diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go index 52b9c4cec..bd0229ad5 100644 --- a/core/components/broker/mocks_test.go +++ b/core/components/broker/mocks_test.go @@ -67,15 +67,62 @@ func (m *MockCloser) Close() error { return m.Failures["Close"] } +// MockAppStorage mocks the AppStorage interface +type MockAppStorage struct { + Failures map[string]error + InRead struct { + AppEUI []byte + DevEUI []byte + } + OutRead struct { + Entry appEntry + } + InUpsert struct { + Entry appEntry + } + InDone struct { + Called bool + } +} + +// NewMockAppStorage creates a new MockAppStorage +func NewMockAppStorage() *MockAppStorage { + return &MockAppStorage{ + Failures: make(map[string]error), + } +} + +// read implements the AppStorage interface +func (m *MockAppStorage) read(appEUI []byte, devEUI []byte) (appEntry, error) { + m.InRead.AppEUI = appEUI + m.InRead.DevEUI = devEUI + return m.OutRead.Entry, m.Failures["read"] +} + +// upsert implements the AppStorage interface +func (m *MockAppStorage) upsert(entry appEntry) error { + m.InUpsert.Entry = entry + return m.Failures["upsert"] +} + +// done implements the AppStorage Interface +func (m *MockAppStorage) done() error { + m.InDone.Called = true + return m.Failures["done"] +} + // MockNetworkController mocks the NetworkController interface type MockNetworkController struct { - Failures map[string]error - InLookupDevices struct { + Failures map[string]error + InRead struct { DevAddr []byte } - OutLookupDevices struct { + OutRead struct { Entries []devEntry } + InUpsert struct { + Entry devEntry + } InWholeCounter struct { DevCnt uint32 EntryCnt uint32 @@ -83,82 +130,39 @@ type MockNetworkController struct { OutWholeCounter struct { FCnt uint32 } - InStoreDevice struct { - DevAddr []byte - Entry devEntry - } - InUpdateFcnt struct { - AppEUI []byte - DevEUI []byte - DevAddr []byte - FCnt uint32 - } - InReadActivation struct { - AppEUI []byte - DevEUI []byte - } - OutReadActivation struct { - Entry appEntry - } - InUpdateActivation struct { - Entry appEntry - } - InClose struct { + InDone struct { Called bool } } -// NewMockNetworkController constructs a new MockNetworkController object +// NewMockNetworkController creates a new MockNetworkController func NewMockNetworkController() *MockNetworkController { return &MockNetworkController{ Failures: make(map[string]error), } } -// LookupDevices implements the NetworkController interface -func (m *MockNetworkController) LookupDevices(devAddr []byte) ([]devEntry, error) { - m.InLookupDevices.DevAddr = devAddr - return m.OutLookupDevices.Entries, m.Failures["LookupDevices"] +// read implements the NetworkController interface +func (m *MockNetworkController) read(devAddr []byte) ([]devEntry, error) { + m.InRead.DevAddr = devAddr + return m.OutRead.Entries, m.Failures["read"] } -// WholeCounter implements the NetworkController interface -func (m *MockNetworkController) WholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { - m.InWholeCounter.DevCnt = devCnt - m.InWholeCounter.EntryCnt = entryCnt - return m.OutWholeCounter.FCnt, m.Failures["WholeCounter"] -} - -// StoreDevice implements the NetworkController interface -func (m *MockNetworkController) StoreDevice(devAddr []byte, entry devEntry) error { - m.InStoreDevice.DevAddr = devAddr - m.InStoreDevice.Entry = entry - return m.Failures["StoreDevice"] -} - -// UpdateFcnt implements the NetworkController interface -func (m *MockNetworkController) UpdateFCnt(appEUI []byte, devEUI []byte, devAddr []byte, fcnt uint32) error { - m.InUpdateFcnt.AppEUI = appEUI - m.InUpdateFcnt.DevEUI = devEUI - m.InUpdateFcnt.DevAddr = devAddr - m.InUpdateFcnt.FCnt = fcnt - return m.Failures["UpdateFCnt"] +// upsert implements the NetworkController interface +func (m *MockNetworkController) upsert(entry devEntry) error { + m.InUpsert.Entry = entry + return m.Failures["upsert"] } -// ReadActivation implements the NetworkController interface -func (m *MockNetworkController) ReadActivation(appEUI []byte, devEUI []byte) (appEntry, error) { - m.InReadActivation.AppEUI = appEUI - m.InReadActivation.DevEUI = devEUI - return m.OutReadActivation.Entry, m.Failures["ReadActivation"] -} - -// UpdateActivation implements the NetworkController interface -func (m *MockNetworkController) UpdateActivation(entry appEntry) error { - m.InUpdateActivation.Entry = entry - return m.Failures["UpdateActivation"] +// wholeCnt implements the NetworkController interface +func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, error) { + m.InWholeCounter.DevCnt = devCnt + m.InWholeCounter.EntryCnt = entryCnt + return m.OutWholeCounter.FCnt, m.Failures["wholeCounter"] } -// Close implements the NetworkController interface -func (m *MockNetworkController) Close() error { - m.InClose.Called = true - return m.Failures["Close"] +// done implements the NetworkController Interface +func (m *MockNetworkController) done() error { + m.InDone.Called = true + return m.Failures["done"] } diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 6623aec89..112f714fc 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -128,14 +128,6 @@ type BrokerClient struct { OutHandleData struct { Res *core.DataBrokerRes } - InSubscribePersonalized struct { - Ctx context.Context - Req *core.ABPSubBrokerReq - Opts []grpc.CallOption - } - OutSubscribePersonalized struct { - Res *core.ABPSubBrokerRes - } InHandleJoin struct { Ctx context.Context Req *core.JoinBrokerReq @@ -169,14 +161,6 @@ func (m *BrokerClient) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, o return m.OutHandleJoin.Res, m.Failures["HandleJoin"] } -// SubscribePersonalized implements the core.BrokerClient interface -func (m *BrokerClient) SubscribePersonalized(ctx context.Context, in *core.ABPSubBrokerReq, opts ...grpc.CallOption) (*core.ABPSubBrokerRes, error) { - m.InSubscribePersonalized.Ctx = ctx - m.InSubscribePersonalized.Req = in - m.InSubscribePersonalized.Opts = opts - return m.OutSubscribePersonalized.Res, m.Failures["SubscribePersonalized"] -} - // RouterServer mocks the core.RouterServer interface type RouterServer struct { Failures map[string]error diff --git a/core/protos/broker.proto b/core/protos/broker.proto index e753a3477..15686fcd3 100644 --- a/core/protos/broker.proto +++ b/core/protos/broker.proto @@ -14,15 +14,6 @@ message DataBrokerRes { Metadata Metadata = 2; } -message ABPSubBrokerReq { - string HandlerNet = 1; - bytes AppEUI = 2; - bytes DevAddr = 3; - bytes NwkSKey = 4; -} - -message ABPSubBrokerRes{} - message JoinBrokerReq { bytes AppEUI = 1; bytes DevEUI = 2; @@ -39,5 +30,4 @@ message JoinBrokerRes { service Broker { rpc HandleData (DataBrokerReq) returns (DataBrokerRes); rpc HandleJoin (JoinBrokerReq) returns (JoinBrokerRes); - rpc SubscribePersonalized (ABPSubBrokerReq) returns (ABPSubBrokerRes); } From fdf69352d5fb9c2e367dcbb5bc9407da9db110a7 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 10:42:15 +0100 Subject: [PATCH 1132/2266] [refactor/storages] Make broker compliant again with new storage interface --- core/components/broker/broker.go | 77 +++++++++----------------------- 1 file changed, 20 insertions(+), 57 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index a106567c5..362181b44 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -4,9 +4,7 @@ package broker import ( - "fmt" "net" - "regexp" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -20,13 +18,15 @@ import ( // component implements the core.BrokerServer interface type component struct { Components - NetAddrUp string - NetAddrDown string + NetAddrUp string + NetAddrDown string + MaxDevNonces uint } // Components defines a structure to make the instantiation easier to read type Components struct { NetworkController NetworkController + AppStorage AppStorage Ctx log.Interface } @@ -44,7 +44,7 @@ type Interface interface { // New construct a new Broker component func New(c Components, o Options) Interface { - return component{Components: c, NetAddrUp: o.NetAddrUp, NetAddrDown: o.NetAddrDown} + return component{Components: c, NetAddrUp: o.NetAddrUp, NetAddrDown: o.NetAddrDown, MaxDevNonces: 10} } // Start actually runs the component and starts the rpc server @@ -90,13 +90,13 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c ctx := b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI) // Check if devNonce already referenced - activation, err := b.NetworkController.ReadActivation(req.AppEUI, req.DevEUI) + entry, err := b.AppStorage.read(req.AppEUI, req.DevEUI) if err != nil { ctx.WithError(err).Debug("Unable to lookup activation") return new(core.JoinBrokerRes), err } var found bool - for _, n := range activation.DevNonces { + for _, n := range entry.DevNonces { if n[0] == req.DevNonce[0] && n[1] == req.DevNonce[1] { found = true break @@ -108,7 +108,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c } // Forward the registration to the handler - handler, closer, err := activation.Dialer.Dial() + handler, closer, err := entry.Dialer.Dial() if err != nil { ctx.WithError(err).Debug("Unable to dial handler") return new(core.JoinBrokerRes), err @@ -137,12 +137,11 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c } // Update the DevNonce - err = b.NetworkController.UpdateActivation(appEntry{ - Dialer: activation.Dialer, - AppEUI: activation.AppEUI, - DevEUI: activation.DevEUI, - DevNonces: append(activation.DevNonces, req.DevNonce), - }) + entry.DevNonces = append(entry.DevNonces, req.DevNonce) + if uint(len(entry.DevNonces)) > b.MaxDevNonces { + entry.DevNonces = entry.DevNonces[1:] + } + err = b.AppStorage.upsert(entry) if err != nil { ctx.WithError(err).Debug("Unable to update activation entry") return new(core.JoinBrokerRes), err @@ -150,8 +149,9 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c var nwkSKey [16]byte copy(nwkSKey[:], res.NwkSKey) - err = b.NetworkController.StoreDevice(res.DevAddr, devEntry{ // Should be an update - Dialer: activation.Dialer, + err = b.NetworkController.upsert(devEntry{ + Dialer: entry.Dialer, + DevAddr: res.DevAddr, AppEUI: req.AppEUI, DevEUI: req.DevEUI, NwkSKey: nwkSKey, @@ -183,7 +183,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c ctx := b.Ctx.WithField("DevAddr", devAddr) // Check whether we should handle it - entries, err := b.NetworkController.LookupDevices(devAddr) + entries, err := b.NetworkController.read(devAddr) if err != nil { ctx = ctx.WithError(err) switch err.(errors.Failure).Nature { @@ -213,7 +213,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // Check with 16-bits counters fhdr.FCnt = fcnt16 - fcnt32, err := b.NetworkController.WholeCounter(fcnt16, entry.FCntUp) + fcnt32, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) if err != nil { continue } @@ -245,7 +245,8 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // It does matter here to use the DevEUI from the entry and not from the packet. // The packet actually holds a DevAddr and the real DevEUI has been determined thanks // to the MIC check + persistence - if err := b.NetworkController.UpdateFCnt(mEntry.AppEUI, mEntry.DevEUI, devAddr, fhdr.FCnt); err != nil { + mEntry.FCntUp = fhdr.FCnt + if err := b.NetworkController.upsert(*mEntry); err != nil { ctx.WithError(err).Debug("Unable to update Frame Counter") return new(core.DataBrokerRes), err } @@ -300,41 +301,3 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c Metadata: resp.Metadata, }, nil } - -// Register implements the core.BrokerServer interface -func (b component) SubscribePersonalized(bctx context.Context, req *core.ABPSubBrokerReq) (*core.ABPSubBrokerRes, error) { - b.Ctx.Debug("Handling personalized subscription") - - // Ensure the entry is valid - if len(req.AppEUI) != 8 { - return new(core.ABPSubBrokerRes), errors.New(errors.Structural, "Invalid Application EUI") - } - - if len(req.DevAddr) != 4 { - return new(core.ABPSubBrokerRes), errors.New(errors.Structural, "Invalid Device Address") - } - devEUI := make([]byte, 8, 8) - copy(devEUI[4:], req.DevAddr) - - var nwkSKey [16]byte - if len(req.NwkSKey) != 16 { - return new(core.ABPSubBrokerRes), errors.New(errors.Structural, "Invalid Network Session Key") - } - copy(nwkSKey[:], req.NwkSKey) - - re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") - if !re.MatchString(req.HandlerNet) { - b.Ctx.WithField("addr", req.HandlerNet).Debug("Invalid address") - return new(core.ABPSubBrokerRes), errors.New(errors.Structural, fmt.Sprintf("Invalid Handler Net Address. Should match: %s", re)) - } - - b.Ctx.Debug("Subscription looks valid") - - return new(core.ABPSubBrokerRes), b.NetworkController.StoreDevice(req.DevAddr, devEntry{ - Dialer: NewDialer([]byte(req.HandlerNet)), - AppEUI: req.AppEUI, - DevEUI: devEUI, - NwkSKey: nwkSKey, - FCntUp: 0, - }) -} From 0f0b490893032e986fc7c54af34b1a2213dcc8fa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 10:47:15 +0100 Subject: [PATCH 1133/2266] Move drone config to Makefile [Skip CI] --- .drone.yml | 9 ++++++--- Dockerfile.local | 4 ---- Makefile | 4 ++-- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/.drone.yml b/.drone.yml index 76b605ef0..9c09c481d 100644 --- a/.drone.yml +++ b/.drone.yml @@ -14,12 +14,15 @@ compose: build: image: htdvisser/ttnbuild pull: true + environment: + - RELEASE_DIR=release/branch/$$BRANCH commands: # ttnbuild already contains dependencies, so we copy them: - rsync -a /go/src/ /drone/src/ - # and get the ones that we missed in ttnbuild - - GOOS=$$GOOS GOARCH=$$GOARCH go get -d -v ./... - - ./build_binaries.sh $$GOOS $$GOARCH + - make deps + - make build package + - rm release/branch/$$BRANCH/ttn-$$GOOS-$$GOARCH + - rm release/branch/$$BRANCH/ttnctl-$$GOOS-$$GOARCH when: branch: [master, develop] diff --git a/Dockerfile.local b/Dockerfile.local index 9acac189e..03efc6148 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -1,7 +1,3 @@ -# USAGE: -# ./build_binaries.sh linux amd64 -# docker build -t local/ttn -f Dockerfile.local . - FROM alpine RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn diff --git a/Makefile b/Makefile index 309463515..6f41f11a4 100644 --- a/Makefile +++ b/Makefile @@ -32,10 +32,10 @@ ttnctlbin = $(ttnctlpkg)$(GOEXE) all: clean deps build package deps: - $(GOCMD) get -v -u $(DEPS) + $(GOCMD) get -d -v $(DEPS) test-deps: - $(GOCMD) get -v -u $(TEST_DEPS) + $(GOCMD) get -d -v $(TEST_DEPS) test: $(select_pkgs) | xargs $(GOCMD) test From d36ef42a305dde47c2f40a350b467f3834df95f9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 10:51:29 +0100 Subject: [PATCH 1134/2266] GOOS and GOARCH are exported --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6f41f11a4..beb7a3a7c 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ GOOS ?= $(shell echo "`go env GOOS`") GOARCH ?= $(shell echo "`go env GOARCH`") GOEXE ?= $(shell echo "`go env GOEXE`") -GOCMD = GOOS=$(GOOS) GOARCH=$(GOARCH) go +GOCMD = go export CGO_ENABLED=0 GOBUILD = $(GOCMD) build From fa34643ca1442332e035c4dd75e952e8a14ab725 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 10:51:57 +0100 Subject: [PATCH 1135/2266] [refactor/storages] Fix broker's tests --- core/components/broker/broker_test.go | 462 ++++++++------------------ 1 file changed, 134 insertions(+), 328 deletions(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 4df8a00a4..f85f8d1df 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -24,7 +24,8 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + as := NewMockAppStorage() + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: nil, Metadata: new(core.Metadata), @@ -43,7 +44,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") } // -------------------- @@ -54,8 +55,9 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() - nc.Failures["LookupDevices"] = errors.New(errors.Operational, "Mock Error") - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + as := NewMockAppStorage() + nc.Failures["read"] = errors.New(errors.Operational, "Mock Error") + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -89,7 +91,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") } // -------------------- @@ -100,8 +102,9 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() - nc.Failures["LookupDevices"] = errors.New(errors.NotFound, "Mock Error") - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + as := NewMockAppStorage() + nc.Failures["read"] = errors.New(errors.NotFound, "Mock Error") + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -135,7 +138,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") } // -------------------- @@ -146,6 +149,7 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 2 dl := NewMockDialer() @@ -156,7 +160,7 @@ func TestHandleData(t *testing.T) { dl2.OutDial.Client = hl dl2.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl2, AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -172,7 +176,7 @@ func TestHandleData(t *testing.T) { FCntUp: 1, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -194,7 +198,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[1].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[1].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] @@ -202,8 +206,8 @@ func TestHandleData(t *testing.T) { var wantErr *string var wantDataUp = &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutLookupDevices.Entries[1].AppEUI, - DevEUI: nc.OutLookupDevices.Entries[1].DevEUI, + AppEUI: nc.OutRead.Entries[1].AppEUI, + DevEUI: nc.OutRead.Entries[1].DevEUI, FCnt: req.Payload.MACPayload.FHDR.FCnt, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, @@ -219,7 +223,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -231,13 +235,14 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() - nc.Failures["WholeCounter"] = errors.New(errors.Structural, "Mock Error") + as := NewMockAppStorage() + nc.Failures["wholeCounter"] = errors.New(errors.Structural, "Mock Error") dl := NewMockDialer() dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -246,7 +251,7 @@ func TestHandleData(t *testing.T) { FCntUp: 1, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -268,7 +273,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] @@ -286,7 +291,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -298,13 +303,14 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 112534 dl := NewMockDialer() dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -313,7 +319,7 @@ func TestHandleData(t *testing.T) { FCntUp: 112500, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -335,7 +341,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] req.Payload.MACPayload.FHDR.FCnt %= 65536 @@ -344,8 +350,8 @@ func TestHandleData(t *testing.T) { var wantErr *string var wantDataUp = &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, - DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + AppEUI: nc.OutRead.Entries[0].AppEUI, + DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, @@ -361,7 +367,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -373,6 +379,7 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 14 dl := NewMockDialer() @@ -380,7 +387,7 @@ func TestHandleData(t *testing.T) { dl.OutDial.Closer = NewMockCloser() dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -389,7 +396,7 @@ func TestHandleData(t *testing.T) { FCntUp: 10, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -411,7 +418,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] req.Payload.MACPayload.FHDR.FCnt %= 65536 @@ -430,7 +437,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -444,13 +451,14 @@ func TestHandleData(t *testing.T) { hl.Failures["HandleDataUp"] = fmt.Errorf("Mock Error") nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 14 dl := NewMockDialer() dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -459,7 +467,7 @@ func TestHandleData(t *testing.T) { FCntUp: 10, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -481,7 +489,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] req.Payload.MACPayload.FHDR.FCnt %= 65536 @@ -490,8 +498,8 @@ func TestHandleData(t *testing.T) { var wantErr = ErrOperational var wantDataUp = &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, - DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + AppEUI: nc.OutRead.Entries[0].AppEUI, + DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, @@ -507,7 +515,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -518,6 +526,7 @@ func TestHandleData(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 14 hl := mocks.NewHandlerClient() @@ -545,7 +554,7 @@ func TestHandleData(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -554,7 +563,7 @@ func TestHandleData(t *testing.T) { FCntUp: 10, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -576,7 +585,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] req.Payload.MACPayload.FHDR.FCnt %= 65536 @@ -585,8 +594,8 @@ func TestHandleData(t *testing.T) { var wantErr *string var wantDataUp = &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, - DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + AppEUI: nc.OutRead.Entries[0].AppEUI, + DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, @@ -597,7 +606,7 @@ func TestHandleData(t *testing.T) { } payloadDown, err := core.NewLoRaWANData(req.Payload, false) FatalUnless(t, err) - err = payloadDown.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payloadDown.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) wantRes.Payload.MIC = payloadDown.MIC[:] var wantFCnt = nc.OutWholeCounter.FCnt @@ -610,7 +619,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -622,14 +631,15 @@ func TestHandleData(t *testing.T) { // Build hl := mocks.NewHandlerClient() nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 14 - nc.Failures["UpdateFCnt"] = errors.New(errors.Operational, "Mock Error") + nc.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") dl := NewMockDialer() dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -638,7 +648,7 @@ func TestHandleData(t *testing.T) { FCntUp: 10, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -660,7 +670,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] req.Payload.MACPayload.FHDR.FCnt %= 65536 @@ -679,7 +689,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -690,6 +700,7 @@ func TestHandleData(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() nc.OutWholeCounter.FCnt = 14 hl := mocks.NewHandlerClient() @@ -709,7 +720,7 @@ func TestHandleData(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutLookupDevices.Entries = []devEntry{ + nc.OutRead.Entries = []devEntry{ { Dialer: dl, AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, @@ -718,7 +729,7 @@ func TestHandleData(t *testing.T) { FCntUp: 10, }, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.DataBrokerReq{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -740,7 +751,7 @@ func TestHandleData(t *testing.T) { } payload, err := core.NewLoRaWANData(req.Payload, true) FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutLookupDevices.Entries[0].NwkSKey)) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) FatalUnless(t, err) req.Payload.MIC = payload.MIC[:] req.Payload.MACPayload.FHDR.FCnt %= 65536 @@ -749,8 +760,8 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantDataUp = &core.DataUpHandlerReq{ Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutLookupDevices.Entries[0].AppEUI, - DevEUI: nc.OutLookupDevices.Entries[0].DevEUI, + AppEUI: nc.OutRead.Entries[0].AppEUI, + DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, @@ -766,228 +777,11 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpdateFcnt.FCnt, "Frame counters") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } } -func TestSubscribePerso(t *testing.T) { - { - Desc(t, "Valid Entry #1") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "87.4352.3:4333", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr *string - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry = devEntry{ - Dialer: NewDialer([]byte(req.HandlerNet)), - AppEUI: req.AppEUI, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } - - // -------------------- - - { - Desc(t, "Valid Entry #2") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "ttn.golang.org:4400", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr *string - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry = devEntry{ - Dialer: NewDialer([]byte(req.HandlerNet)), - AppEUI: req.AppEUI, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } - - // -------------------- - - { - Desc(t, "Valid Entry #1") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "87.4352.3:4333", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr *string - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry = devEntry{ - Dialer: NewDialer([]byte(req.HandlerNet)), - AppEUI: req.AppEUI, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } - - // -------------------- - - { - Desc(t, "Invalid entry -> Bad HandlerNet") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "localhost", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry devEntry - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } - - // -------------------- - - { - Desc(t, "Invalid entry -> Bad AppEUI") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "87.4352.3:4333", - AppEUI: []byte{1, 2, 3, 4, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry devEntry - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } - - // -------------------- - - { - Desc(t, "Invalid entry -> Bad DevAddr") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "87.4352.3:4333", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry devEntry - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } - // -------------------- - - { - Desc(t, "Invalid entry -> Bad NwkSKey") - - // Build - nc := NewMockNetworkController() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.ABPSubBrokerReq{ - HandlerNet: "87.4352.3:4333", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{1, 2, 3, 4}, - NwkSKey: nil, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.ABPSubBrokerRes) - var wantEntry devEntry - - // Operate - res, err := br.SubscribePersonalized(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Broker ABP Responses") - Check(t, wantEntry, nc.InStoreDevice.Entry, "Device Entries") - } -} - func TestDialerCloser(t *testing.T) { { Desc(t, "Dial on a valid address, server is listening") @@ -1024,6 +818,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1038,17 +833,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1080,7 +875,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1091,6 +886,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1105,17 +901,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ AppEUI: nil, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1134,7 +930,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1145,6 +941,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1159,16 +956,16 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, + AppEUI: as.OutRead.Entry.AppEUI, DevEUI: []byte{1, 2, 3}, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), @@ -1188,7 +985,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1199,6 +996,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1213,17 +1011,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14, 15, 16}, Metadata: new(core.Metadata), } @@ -1242,7 +1040,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1253,6 +1051,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1267,17 +1066,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: nil, } @@ -1296,7 +1095,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1307,7 +1106,8 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() - nc.Failures["ReadActivation"] = errors.New(errors.Operational, "Mock Error") + as := NewMockAppStorage() + as.Failures["read"] = errors.New(errors.Operational, "Mock Error") hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1322,7 +1122,7 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -1344,7 +1144,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1355,6 +1155,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1369,17 +1170,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{[]byte{14, 14}}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1398,7 +1199,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1409,6 +1210,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1424,17 +1226,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1453,7 +1255,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } // -------------------- @@ -1463,6 +1265,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1478,17 +1281,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1512,7 +1315,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1523,6 +1326,7 @@ func TestHandleJoin(t *testing.T) { // Build nc := NewMockNetworkController() + as := NewMockAppStorage() hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1537,17 +1341,17 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Client = hl dl.OutDial.Closer = NewMockCloser() - nc.OutReadActivation.Entry = appEntry{ + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1571,7 +1375,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1596,18 +1400,19 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() nc := NewMockNetworkController() - nc.Failures["UpdateActivation"] = errors.New(errors.Operational, "Mock Error") - nc.OutReadActivation.Entry = appEntry{ + as := NewMockAppStorage() + as.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1636,7 +1441,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1661,18 +1466,19 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() nc := NewMockNetworkController() - nc.Failures["StoreDevice"] = errors.New(errors.Operational, "Mock Error") - nc.OutReadActivation.Entry = appEntry{ + as := NewMockAppStorage() + nc.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") + as.OutRead.Entry = appEntry{ Dialer: dl, AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, } - br := New(Components{NetworkController: nc, Ctx: GetLogger(t, "Broker")}, Options{}) + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadActivation.Entry.AppEUI, - DevEUI: nc.OutReadActivation.Entry.DevEUI, + AppEUI: as.OutRead.Entry.AppEUI, + DevEUI: as.OutRead.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1701,7 +1507,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpdateActivation.Entry, "Activations") + Check(t, wantActivation, as.InUpsert.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } } From 3e90d2e140fd36f45eb72cdfe35bd9f4e9bd079f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 10:52:10 +0100 Subject: [PATCH 1136/2266] [refactor/storages] Make test pass for NetworkController --- core/components/broker/controller.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index d8267debf..2549fb3fb 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -5,6 +5,8 @@ package broker import ( "encoding" + "encoding/binary" + "fmt" "math" "reflect" "sync" @@ -14,6 +16,8 @@ import ( "github.com/TheThingsNetwork/ttn/utils/readwriter" ) +var _ = fmt.Print + // NetworkController gives a facade for manipulating the broker databases and devices type NetworkController interface { read(devAddr []byte) ([]devEntry, error) @@ -79,18 +83,27 @@ func (s *controller) upsert(update devEntry) error { defer s.Unlock() itf, err := s.db.Read(update.DevAddr, &devEntry{}, dbDevices) if err != nil { - return err + if err.(errors.Failure).Nature != errors.NotFound { + return err + } + return s.db.Update(update.DevAddr, []encoding.BinaryMarshaler{update}, dbDevices) } entries := itf.([]devEntry) var newEntries []encoding.BinaryMarshaler + var replaced bool for _, e := range entries { entry := new(devEntry) *entry = e if reflect.DeepEqual(entry.AppEUI, update.AppEUI) && reflect.DeepEqual(entry.DevEUI, update.DevEUI) { newEntries = append(newEntries, update) + replaced = true + } else { + newEntries = append(newEntries, entry) } - newEntries = append(newEntries, entry) + } + if !replaced { + newEntries = append(newEntries, update) } return s.db.Update(update.DevAddr, newEntries, dbDevices) } @@ -107,6 +120,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.DevEUI) rw.Write(e.DevAddr) rw.Write(e.NwkSKey[:]) + rw.Write(e.FCntUp) rw.Write(e.Dialer.MarshalSafely()) return rw.Bytes() } @@ -128,6 +142,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) From 2cbc97fc004c029c926bc28222b53cf0524caba6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 10:53:16 +0100 Subject: [PATCH 1137/2266] Add version command to ttnctl --- ttnctl/cmd/version.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 ttnctl/cmd/version.go diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go new file mode 100644 index 000000000..e3514e7a1 --- /dev/null +++ b/ttnctl/cmd/version.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// versionCmd represents the version command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Get the version", + Long: `Get the version`, + Run: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "commit": viper.GetString("gitCommit"), + "build date": viper.GetString("buildDate"), + }).Infof("You are running %s of ttnctl.", viper.GetString("version")) + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} From 45403403dbdc223f61b1d81a86eb86bcb17cfdcf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 10:58:32 +0100 Subject: [PATCH 1138/2266] Update drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index e80cbdd01..68fce9846 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.sTgJXLRHZ9Rfpv7ZieOv3j08f6ZxqQ8utgbIKkeaBL3SYy8NnFeijZMdVIZveZRJYlDbBS_HYwhx-Dzprm8qTYttrbtdJCus1XgJqwRTBewYaqJt6ayb6rr5MGAZ5RTeMBG-16VSqd1jw8U2K6pf4EbODqNBtBbZsP5s5CXOiNK--CkYAPUjORGNuN07jPfBE4JSQlEAujs3ZsEm9AhJo5Pngx3yfgSYTiIsI_tbMSH8acXJKLzJdkGoJzb_XjNdsQ8sJbWzWvO2bM733qtkzkUfFVjk91kdJDPnE2CsBn11zvLd8NqtrLW57yiRKXGKIdfDduJqFo-zxARUe67I6Q.iW65t1QJ-Mo92VKc.yC9RQpWxKFdySIIx1g7JJ0UBuSz_008VleQu3LQ_tbKGDpeOzrj8qtG5-DT0Bg0vqb4jvugCJ1b1E1X8-b5eFntll78XZ1IF6x-m3UrbXuRxOQ9nhN54gBXvVJf6YWUzsmy0PQtmLBZIuNMEbDiCga_l-HFQ42iv9KCeSySq2b7Z3jkC3J0JJOuIKn_Bjxjcp1YQP41r0NlG8r23HNPuaZr2kotWtDi3V93aVy68g0gk3HuCb3KLSyoTDXdoKHtgM5AncMj7wCA5OTKLdO5hyrL4waqCU5WeDTclXgKSyPuB0UeGV8VXkb3ZGzlRnO4G6hvYUUCPvRHlcFFNjptCAiF-u7bseajXc44afxd_67ixL1iJfkf48OWsYIBu16Dy2oBOpi48Hfys7bKRBI0KUgZVJzGOJpZVeWYWfseK9KL0Hpz2Fy2maIxvaPl_f5E8lX0OK8FVfWjIVggBwmOKBIF7bX4A.NA0JDh9UAYfBNzQQF0iwwQ \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.F75X-X3qcso5npJwdyPoYd-bQTUb_kS8HnQ78vQ3_Kq3N7V58TBRuelIgQF_3OBJWbMKbtXsWtOci_zg8Jl5mU2SkC5rtXieYUn6TYZTRH9vmMLkrClZCIcGYe4GllNv-WfyTWOHbdnSRYbq_7xGK_fZJ3irUoIqlnQma9gyIV7WMPDNTURAF_Iq7K-abrqpC1INbqdIrmkBfe8sGp_CjkSH9LgIVVNOo4tBlZlu8TqnAuUCjBObIqiZfG1M2xWQYdM6H5Xr8VNDPXJisHu4vvTS7kqFEEAHoMuqZZhOhgoUmXPt2qgXkY6xjqHy7M03M4W8BjG7b3FVJmptOhnkxQ.wmpQT8UbzhDObJme.qqroSig9Bs5hhl1nVrArOKXWaL9hoNeIBkXdyqdzx1QEt7ln0OnLhZ8SKYQPk5J33GNCK1pJbftBQiJqwkqFjEQ92h1YTFL9DNd8crTNC7Fu8QMfKqvhsMKOoBbzpb2So77eUZiF_VUafEGM27Epyp3EVj2TnVzisrmcuCsfP-dNXw8jd7DgrJWRLeubJgrm-Ict5qTKtaH-rPgmZ8i8hUFv4A9EM3QKsHySzpOHRUAgVe4ROzYG6lEUMSuUMlR8wQtcNkfQLwYTJPQGHdP5hy6sgFpGxV_qpB4WbmfoIZP4KnHLOa8yo4NlEhzUdVpmwjnezIRkXg82H6UUOum3IgZ8qyN6s78xLLLahghvzJwHOC69qWVeMQygEwnAyJonM_iZBPIFdxwuJSpNZnHLYrtafT6ckotXKPnxW7QG9LUT8a0dxVvSarqbysriLvTVliC8xoEavULegPxMK1Z7_T4ZuUTv.66lQVSU7nWh2cdVWH1KWzw \ No newline at end of file From 62b892925475b914837216863afd78aa1070ab8e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 11:08:34 +0100 Subject: [PATCH 1139/2266] Don't have to remove binaries [Skip CI] --- .drone.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 9c09c481d..9b79ee7c1 100644 --- a/.drone.yml +++ b/.drone.yml @@ -21,8 +21,6 @@ build: - rsync -a /go/src/ /drone/src/ - make deps - make build package - - rm release/branch/$$BRANCH/ttn-$$GOOS-$$GOARCH - - rm release/branch/$$BRANCH/ttnctl-$$GOOS-$$GOARCH when: branch: [master, develop] From 200219607387def14a035ed09f3edd968709e4c7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 11:08:56 +0100 Subject: [PATCH 1140/2266] Update drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index 68fce9846..ef2880db0 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.F75X-X3qcso5npJwdyPoYd-bQTUb_kS8HnQ78vQ3_Kq3N7V58TBRuelIgQF_3OBJWbMKbtXsWtOci_zg8Jl5mU2SkC5rtXieYUn6TYZTRH9vmMLkrClZCIcGYe4GllNv-WfyTWOHbdnSRYbq_7xGK_fZJ3irUoIqlnQma9gyIV7WMPDNTURAF_Iq7K-abrqpC1INbqdIrmkBfe8sGp_CjkSH9LgIVVNOo4tBlZlu8TqnAuUCjBObIqiZfG1M2xWQYdM6H5Xr8VNDPXJisHu4vvTS7kqFEEAHoMuqZZhOhgoUmXPt2qgXkY6xjqHy7M03M4W8BjG7b3FVJmptOhnkxQ.wmpQT8UbzhDObJme.qqroSig9Bs5hhl1nVrArOKXWaL9hoNeIBkXdyqdzx1QEt7ln0OnLhZ8SKYQPk5J33GNCK1pJbftBQiJqwkqFjEQ92h1YTFL9DNd8crTNC7Fu8QMfKqvhsMKOoBbzpb2So77eUZiF_VUafEGM27Epyp3EVj2TnVzisrmcuCsfP-dNXw8jd7DgrJWRLeubJgrm-Ict5qTKtaH-rPgmZ8i8hUFv4A9EM3QKsHySzpOHRUAgVe4ROzYG6lEUMSuUMlR8wQtcNkfQLwYTJPQGHdP5hy6sgFpGxV_qpB4WbmfoIZP4KnHLOa8yo4NlEhzUdVpmwjnezIRkXg82H6UUOum3IgZ8qyN6s78xLLLahghvzJwHOC69qWVeMQygEwnAyJonM_iZBPIFdxwuJSpNZnHLYrtafT6ckotXKPnxW7QG9LUT8a0dxVvSarqbysriLvTVliC8xoEavULegPxMK1Z7_T4ZuUTv.66lQVSU7nWh2cdVWH1KWzw \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.XcDZ8tWDpPNC87Wqu6Mz2Amec2eBjj3yII7GF1EuQ0n28kuogj6shCk7GqmKibCl6tefeov-_0xF7w1HY5K2oEJPN1UPz_pSkll08sSnk6cIn9JpWEDUCtqRhqlnl-70ua5CqegJi_Pjxla3rt7V0HOKHIRocU7wLo5jGs5PYM7Ma6ePUJuBo6Fis3DqlBFXWbTr0wxu9fX_qyDhMCFI5ebSGspdGxy1EJqfX7kR5kERR-4J6gCCFPXrbOh_NEmCRqS9cKxA6bD-AuX3nrkrLK7lMX2jlLqx4bjx2BmcO2sGUdhvY4I0x7-LoHTPPJPbSpbgnET9QMeFok6uemLGfg.vXxzCNCi2SyeY06S.JfDFGp2TUb8UGpDP5Pk3L06VxsiYarf_1X67AUJme3u3PIIp7tSd51DS-YlVArT3zJ07PTp9Z1Nr0kTJkUhwGtM4-yH3x9wdLn7pO0gQyerPIeOxOHt6SvXbgjlvpOjRlXmDdpKSLdxPoKXLFgQsUcTb-N_w7LROTO71GXstK98eEnfmEeuUAeRDWZNbG7EutYLZfoqdZoCVpABSpDDNJskTMAVw6SZSbTKTKT-dzAiPAuex9WiVzkzjU9CoGTvbmFpl0rbD2uWFsou7sTXz3wTehB7meQdWLcoMxJ2QgLBB3LARekiYyhCSjXnOJZ4zXFDQu6QQ417qIC9VdlrVR75sDtUw1Ldhoy-croiICQSpiOin2kllwH2thMtlC4jk_IqN1RkKBxQSPDvQvqnDMse52YW95ZsddGH4XyNZECpRkwK9pZ0BHoiSqSNBN7LvYmplqwoYsh85ZYD8Cb4zgk-gcn_P.-sYy73qvNLMG3VXNHUYldQ \ No newline at end of file From 3fffbab9ea9863fae59e5d88424e9c684308e94e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 11:11:01 +0100 Subject: [PATCH 1141/2266] Remove build_binaries.sh Use Makefile instead [Skip CI] --- build_binaries.sh | 101 ---------------------------------------------- 1 file changed, 101 deletions(-) delete mode 100755 build_binaries.sh diff --git a/build_binaries.sh b/build_binaries.sh deleted file mode 100755 index 7d7b81845..000000000 --- a/build_binaries.sh +++ /dev/null @@ -1,101 +0,0 @@ -#!/bin/bash - -set -e - -TTNROOT=${PWD} -RELEASEPATH=${RELEASEPATH:-$TTNROOT/release} - -mkdir -p $RELEASEPATH - -git_commit=$(git rev-parse HEAD) - -echo "Preparing build..." -echo "CI_COMMIT: $CI_COMMIT" -echo "CI_BRANCH: $CI_BRANCH" -echo "CI_TAG: $CI_TAG" -echo "git_commit: $git_commit" -echo "" - -build_release() -{ - export CGO_ENABLED=0 - export GOOS=$1 - export GOARCH=$2 - - release_name=ttn-$GOOS-$GOARCH - - if [ "$GOOS" == "windows" ] - then - ext=".exe" - else - ext="" - fi - - binary_name=$release_name$ext - - build_date=$(date -u +%Y-%m-%dT%H:%M:%SZ) - - echo "$build_date - Building ttn for $GOOS/$GOARCH..." - - # Build - cd $TTNROOT - - go build -a -installsuffix cgo -ldflags "-w -X main.gitCommit=$git_commit -X main.buildDate=$build_date" -o $RELEASEPATH/$binary_name ./main.go - - # Compress - cd $RELEASEPATH - - echo -n " - Compressing tar.gz... " - tar -czf $release_name.tar.gz $binary_name - echo " Done" - - echo -n " - Compressing tar.xz... " - tar -cJf $release_name.tar.xz $binary_name - echo " Done" - - echo -n " - Compressing zip... " - zip -q $release_name.zip $binary_name > /dev/null - echo " Done" - - # Delete Binary in CI build - if [ "$CI" != "" ] - then - rm $binary_name - fi -} - -if [[ "$1" != "" ]] && [[ "$2" != "" ]] -then - build_release $1 $2 -else - build_release darwin 386 - build_release darwin amd64 - build_release linux 386 - build_release linux amd64 -fi - -# Prepare Releases in CI build -if [ "$CI" != "" ] -then - - cd $RELEASEPATH - - # Branch Release - if [ "$CI_BRANCH" != "" ] - then - echo "Copying files for branch $CI_BRANCH" - mkdir -p branch/$CI_BRANCH - cp ./ttn* branch/$CI_BRANCH/ - fi - - # Tag Release - if [ "$CI_TAG" != "" ] - then - echo "Copying files for tag $CI_TAG" - mkdir -p tag/$CI_TAG - cp ./ttn* tag/$CI_TAG/ - fi - - # Remove Build Files - rm -f ./ttn* -fi From 43de9f36811e2b5f3883bc11fa82bf4082f0f872 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 12:57:17 +0100 Subject: [PATCH 1142/2266] [feature/manager] Add and Register ABP and OTAA registration proto --- core/components/handler/handler.go | 12 + core/handler_manager.pb.go | 1491 ++++++++++++++++++++++++++++ core/protos/handler_manager.proto | 39 + 3 files changed, 1542 insertions(+) create mode 100644 core/handler_manager.pb.go create mode 100644 core/protos/handler_manager.proto diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 5e5610a1c..e738f2db0 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -55,6 +55,7 @@ type component struct { // Interface defines the Handler interface type Interface interface { core.HandlerServer + core.HandlerManagerServer Start() error } @@ -117,6 +118,7 @@ func (h *component) Start() error { server := grpc.NewServer() core.RegisterHandlerServer(server, h) + core.RegisterHandlerManagerServer(server, h) if err := server.Serve(conn); err != nil { return errors.New(errors.Operational, err) @@ -297,6 +299,16 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq } } +// List implements the core.HandlerManagerServer interface +func (h component) List(context.Context, *core.ListDevicesReq) (*core.ListDevicesRes, error) { + return nil, errors.New(errors.Structural, "Not implemented") +} + +// Upsert implements the core.HandlerManagerServer interface +func (h component) Upsert(context.Context, *core.UpsertDeviceReq) (*core.UpsertDeviceRes, error) { + return nil, errors.New(errors.Structural, "Not implemented") +} + // consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) // It then flushes them once a given delay has passed since the reception of the first bundle. func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go new file mode 100644 index 000000000..dbd2f98fe --- /dev/null +++ b/core/handler_manager.pb.go @@ -0,0 +1,1491 @@ +// Code generated by protoc-gen-gogo. +// source: handler_manager.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type Device struct { + DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + // Types that are valid to be assigned to Data: + // *Device_Personalized + // *Device_Activated + Data isDevice_Data `protobuf_oneof:"Data"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} +func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{0} } + +type isDevice_Data interface { + isDevice_Data() + MarshalTo([]byte) (int, error) + Size() int +} + +type Device_Personalized struct { + Personalized *Device_PersonalizedData `protobuf:"bytes,2,opt,name=Personalized,json=personalized,oneof"` +} +type Device_Activated struct { + Activated *Device_ActivatedData `protobuf:"bytes,3,opt,name=Activated,json=activated,oneof"` +} + +func (*Device_Personalized) isDevice_Data() {} +func (*Device_Activated) isDevice_Data() {} + +func (m *Device) GetData() isDevice_Data { + if m != nil { + return m.Data + } + return nil +} + +func (m *Device) GetPersonalized() *Device_PersonalizedData { + if x, ok := m.GetData().(*Device_Personalized); ok { + return x.Personalized + } + return nil +} + +func (m *Device) GetActivated() *Device_ActivatedData { + if x, ok := m.GetData().(*Device_Activated); ok { + return x.Activated + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Device) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Device_OneofMarshaler, _Device_OneofUnmarshaler, _Device_OneofSizer, []interface{}{ + (*Device_Personalized)(nil), + (*Device_Activated)(nil), + } +} + +func _Device_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Device) + // Data + switch x := m.Data.(type) { + case *Device_Personalized: + _ = b.EncodeVarint(2<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Personalized); err != nil { + return err + } + case *Device_Activated: + _ = b.EncodeVarint(3<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Activated); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Device.Data has unexpected type %T", x) + } + return nil +} + +func _Device_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Device) + switch tag { + case 2: // Data.Personalized + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Device_PersonalizedData) + err := b.DecodeMessage(msg) + m.Data = &Device_Personalized{msg} + return true, err + case 3: // Data.Activated + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(Device_ActivatedData) + err := b.DecodeMessage(msg) + m.Data = &Device_Activated{msg} + return true, err + default: + return false, nil + } +} + +func _Device_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Device) + // Data + switch x := m.Data.(type) { + case *Device_Personalized: + s := proto.Size(x.Personalized) + n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Device_Activated: + s := proto.Size(x.Activated) + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type Device_PersonalizedData struct { + NwkSKey []byte `protobuf:"bytes,1,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,2,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` +} + +func (m *Device_PersonalizedData) Reset() { *m = Device_PersonalizedData{} } +func (m *Device_PersonalizedData) String() string { return proto.CompactTextString(m) } +func (*Device_PersonalizedData) ProtoMessage() {} +func (*Device_PersonalizedData) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{0, 0} +} + +type Device_ActivatedData struct { + AppKey []byte `protobuf:"bytes,1,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` +} + +func (m *Device_ActivatedData) Reset() { *m = Device_ActivatedData{} } +func (m *Device_ActivatedData) String() string { return proto.CompactTextString(m) } +func (*Device_ActivatedData) ProtoMessage() {} +func (*Device_ActivatedData) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{0, 1} +} + +type ListDevicesReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` +} + +func (m *ListDevicesReq) Reset() { *m = ListDevicesReq{} } +func (m *ListDevicesReq) String() string { return proto.CompactTextString(m) } +func (*ListDevicesReq) ProtoMessage() {} +func (*ListDevicesReq) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{1} } + +type ListDevicesRes struct { + Devices []*Device `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` +} + +func (m *ListDevicesRes) Reset() { *m = ListDevicesRes{} } +func (m *ListDevicesRes) String() string { return proto.CompactTextString(m) } +func (*ListDevicesRes) ProtoMessage() {} +func (*ListDevicesRes) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{2} } + +func (m *ListDevicesRes) GetDevices() []*Device { + if m != nil { + return m.Devices + } + return nil +} + +type UpsertDeviceReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Device *Device `protobuf:"bytes,2,opt,name=Device,json=device" json:"Device,omitempty"` +} + +func (m *UpsertDeviceReq) Reset() { *m = UpsertDeviceReq{} } +func (m *UpsertDeviceReq) String() string { return proto.CompactTextString(m) } +func (*UpsertDeviceReq) ProtoMessage() {} +func (*UpsertDeviceReq) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{3} } + +func (m *UpsertDeviceReq) GetDevice() *Device { + if m != nil { + return m.Device + } + return nil +} + +type UpsertDeviceRes struct { +} + +func (m *UpsertDeviceRes) Reset() { *m = UpsertDeviceRes{} } +func (m *UpsertDeviceRes) String() string { return proto.CompactTextString(m) } +func (*UpsertDeviceRes) ProtoMessage() {} +func (*UpsertDeviceRes) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{4} } + +func init() { + proto.RegisterType((*Device)(nil), "core.Device") + proto.RegisterType((*Device_PersonalizedData)(nil), "core.Device.PersonalizedData") + proto.RegisterType((*Device_ActivatedData)(nil), "core.Device.ActivatedData") + proto.RegisterType((*ListDevicesReq)(nil), "core.ListDevicesReq") + proto.RegisterType((*ListDevicesRes)(nil), "core.ListDevicesRes") + proto.RegisterType((*UpsertDeviceReq)(nil), "core.UpsertDeviceReq") + proto.RegisterType((*UpsertDeviceRes)(nil), "core.UpsertDeviceRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for HandlerManager service + +type HandlerManagerClient interface { + List(ctx context.Context, in *ListDevicesReq, opts ...grpc.CallOption) (*ListDevicesRes, error) + Upsert(ctx context.Context, in *UpsertDeviceReq, opts ...grpc.CallOption) (*UpsertDeviceRes, error) +} + +type handlerManagerClient struct { + cc *grpc.ClientConn +} + +func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { + return &handlerManagerClient{cc} +} + +func (c *handlerManagerClient) List(ctx context.Context, in *ListDevicesReq, opts ...grpc.CallOption) (*ListDevicesRes, error) { + out := new(ListDevicesRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/List", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerManagerClient) Upsert(ctx context.Context, in *UpsertDeviceReq, opts ...grpc.CallOption) (*UpsertDeviceRes, error) { + out := new(UpsertDeviceRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/Upsert", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for HandlerManager service + +type HandlerManagerServer interface { + List(context.Context, *ListDevicesReq) (*ListDevicesRes, error) + Upsert(context.Context, *UpsertDeviceReq) (*UpsertDeviceRes, error) +} + +func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { + s.RegisterService(&_HandlerManager_serviceDesc, srv) +} + +func _HandlerManager_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ListDevicesReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerManagerServer).List(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _HandlerManager_Upsert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpsertDeviceReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerManagerServer).Upsert(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _HandlerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.HandlerManager", + HandlerType: (*HandlerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "List", + Handler: _HandlerManager_List_Handler, + }, + { + MethodName: "Upsert", + Handler: _HandlerManager_Upsert_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *Device) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Device) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.Data != nil { + nn1, err := m.Data.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *Device_Personalized) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Personalized != nil { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.Personalized.Size())) + n2, err := m.Personalized.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} +func (m *Device_Activated) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Activated != nil { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.Activated.Size())) + n3, err := m.Activated.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} +func (m *Device_PersonalizedData) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Device_PersonalizedData) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + if m.AppSKey != nil { + if len(m.AppSKey) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) + } + } + return i, nil +} + +func (m *Device_ActivatedData) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Device_ActivatedData) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppKey != nil { + if len(m.AppKey) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) + } + } + return i, nil +} + +func (m *ListDevicesReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ListDevicesReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + return i, nil +} + +func (m *ListDevicesRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ListDevicesRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Devices) > 0 { + for _, msg := range m.Devices { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *UpsertDeviceReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UpsertDeviceReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.Device != nil { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.Device.Size())) + n4, err := m.Device.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *UpsertDeviceRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UpsertDeviceRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64HandlerManager(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32HandlerManager(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintHandlerManager(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *Device) Size() (n int) { + var l int + _ = l + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.Data != nil { + n += m.Data.Size() + } + return n +} + +func (m *Device_Personalized) Size() (n int) { + var l int + _ = l + if m.Personalized != nil { + l = m.Personalized.Size() + n += 1 + l + sovHandlerManager(uint64(l)) + } + return n +} +func (m *Device_Activated) Size() (n int) { + var l int + _ = l + if m.Activated != nil { + l = m.Activated.Size() + n += 1 + l + sovHandlerManager(uint64(l)) + } + return n +} +func (m *Device_PersonalizedData) Size() (n int) { + var l int + _ = l + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.AppSKey != nil { + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *Device_ActivatedData) Size() (n int) { + var l int + _ = l + if m.AppKey != nil { + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *ListDevicesReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *ListDevicesRes) Size() (n int) { + var l int + _ = l + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.Size() + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *UpsertDeviceReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.Device != nil { + l = m.Device.Size() + n += 1 + l + sovHandlerManager(uint64(l)) + } + return n +} + +func (m *UpsertDeviceRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovHandlerManager(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozHandlerManager(x uint64) (n int) { + return sovHandlerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Device) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Personalized", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &Device_PersonalizedData{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Device_Personalized{v} + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activated", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &Device_ActivatedData{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Data = &Device_Activated{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device_PersonalizedData) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PersonalizedData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PersonalizedData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) + if m.AppSKey == nil { + m.AppSKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device_ActivatedData) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivatedData: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivatedData: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) + if m.AppKey == nil { + m.AppKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListDevicesReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListDevicesReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListDevicesReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ListDevicesRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ListDevicesRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ListDevicesRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Devices = append(m.Devices, &Device{}) + if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpsertDeviceReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpsertDeviceReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpsertDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Device == nil { + m.Device = &Device{} + } + if err := m.Device.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpsertDeviceRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpsertDeviceRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpsertDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipHandlerManager(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthHandlerManager + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipHandlerManager(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthHandlerManager = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHandlerManager = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorHandlerManager = []byte{ + // 351 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0xcd, 0x4a, 0xc3, 0x40, + 0x10, 0xc7, 0xfb, 0xc5, 0x86, 0x8e, 0xb1, 0xd6, 0xc5, 0x4a, 0x09, 0x58, 0x24, 0x88, 0xf6, 0x94, + 0x43, 0x05, 0x11, 0x6f, 0xad, 0x55, 0x2a, 0x7e, 0x12, 0xe9, 0x59, 0xd6, 0x64, 0xd1, 0x60, 0x4d, + 0x62, 0x36, 0x54, 0x14, 0x1f, 0x44, 0x7c, 0x22, 0x8f, 0x3e, 0x82, 0xe8, 0x8b, 0xb8, 0xd9, 0x49, + 0x35, 0x09, 0xc5, 0xc3, 0x42, 0xfe, 0xf3, 0x9f, 0xf9, 0xed, 0x4c, 0x76, 0xa0, 0x75, 0xcb, 0x7c, + 0x77, 0xc2, 0xa3, 0xab, 0x7b, 0xe6, 0xb3, 0x1b, 0x1e, 0x59, 0x61, 0x14, 0xc4, 0x01, 0xad, 0x39, + 0x41, 0xc4, 0xcd, 0xb7, 0x0a, 0x90, 0x21, 0x9f, 0x7a, 0x0e, 0xa7, 0xab, 0xea, 0xeb, 0x60, 0x7c, + 0xd4, 0x2e, 0xaf, 0x97, 0xbb, 0xba, 0x4d, 0x5c, 0xa5, 0xe8, 0x3e, 0xe8, 0x17, 0x3c, 0x12, 0x81, + 0xcf, 0x26, 0xde, 0x33, 0x77, 0xdb, 0x15, 0xe9, 0x2e, 0xf4, 0xd6, 0xac, 0xa4, 0xde, 0xc2, 0x5a, + 0x2b, 0x9b, 0x30, 0x64, 0x31, 0x1b, 0x95, 0x6c, 0x3d, 0xcc, 0xc4, 0xe8, 0x1e, 0xd4, 0xfb, 0x4e, + 0xec, 0x4d, 0x59, 0x2c, 0x09, 0x55, 0x45, 0x30, 0x72, 0x84, 0x5f, 0x37, 0x2d, 0xaf, 0xb3, 0x59, + 0xc0, 0x38, 0x84, 0x66, 0x91, 0x4f, 0xdb, 0xa0, 0x9d, 0x3d, 0xde, 0x5d, 0x1e, 0xf3, 0xa7, 0xb4, + 0x5b, 0xcd, 0x47, 0x99, 0x38, 0xfd, 0x30, 0x54, 0x4e, 0x05, 0x1d, 0x86, 0xd2, 0xd8, 0x82, 0xc5, + 0xdc, 0x2d, 0xc9, 0xc4, 0x32, 0xf5, 0x8f, 0x41, 0x98, 0x52, 0x03, 0x02, 0xb5, 0xc4, 0x37, 0xbb, + 0xd0, 0x38, 0xf1, 0x44, 0x8c, 0x1d, 0x0a, 0x9b, 0x3f, 0xa4, 0x15, 0x99, 0x7f, 0xc4, 0x94, 0x32, + 0x77, 0x0b, 0x99, 0x82, 0x6e, 0x82, 0x96, 0x2a, 0x99, 0x5a, 0x95, 0xe3, 0xea, 0xd9, 0x71, 0x6d, + 0xcd, 0x45, 0xd3, 0x3c, 0x87, 0xa5, 0x71, 0x28, 0x78, 0x94, 0xd6, 0xfe, 0x73, 0x09, 0xdd, 0x98, + 0x3d, 0x55, 0xfa, 0x04, 0x79, 0x22, 0x41, 0xa2, 0xb9, 0x5c, 0x04, 0x8a, 0xde, 0x0b, 0x34, 0x46, + 0xb8, 0x03, 0xa7, 0xb8, 0x02, 0xb4, 0x07, 0xb5, 0xa4, 0x5f, 0xba, 0x82, 0x88, 0xfc, 0x94, 0xc6, + 0xbc, 0xa8, 0xa0, 0x3b, 0x40, 0x10, 0x4c, 0x5b, 0xe8, 0x17, 0xfa, 0x36, 0xe6, 0x86, 0xc5, 0xa0, + 0xf9, 0xfe, 0xd5, 0x29, 0x7f, 0xc8, 0xf3, 0x29, 0xcf, 0xeb, 0x77, 0xa7, 0x74, 0x4d, 0xd4, 0x06, + 0x6e, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc7, 0x04, 0xfe, 0xa7, 0x9a, 0x02, 0x00, 0x00, +} diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto new file mode 100644 index 000000000..0e7ce3ca3 --- /dev/null +++ b/core/protos/handler_manager.proto @@ -0,0 +1,39 @@ +syntax = "proto3"; + +package core; + +message Device { + bytes DevEUI = 1; + message PersonalizedData { + bytes NwkSKey = 1; + bytes AppSKey = 2; + } + message ActivatedData { + bytes AppKey = 1; + } + oneof Data { + PersonalizedData Personalized = 2; + ActivatedData Activated = 3; + } +} + +message ListDevicesReq { + bytes AppEUI = 1; +} + +message ListDevicesRes { + repeated Device Devices = 1; +} + +message UpsertDeviceReq { + bytes AppEUI = 1; + Device Device = 2; +} + +message UpsertDeviceRes { +} + +service HandlerManager { + rpc List(ListDevicesReq) returns (ListDevicesRes); + rpc Upsert(UpsertDeviceReq) returns (UpsertDeviceRes); +} From d37bc8b8dd71970208e4584b56aa086ad635cb8d Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 13:48:30 +0100 Subject: [PATCH 1143/2266] [refactor/storages] Rewrite router's storages --- core/components/router/gtwStorage.go | 82 +++++++++++++++++ core/components/router/router.go | 32 ++++--- core/components/router/storage.go | 129 +++++++++++---------------- core/storage/storage.go | 5 ++ 4 files changed, 158 insertions(+), 90 deletions(-) create mode 100644 core/components/router/gtwStorage.go diff --git a/core/components/router/gtwStorage.go b/core/components/router/gtwStorage.go new file mode 100644 index 000000000..5f6bef144 --- /dev/null +++ b/core/components/router/gtwStorage.go @@ -0,0 +1,82 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "encoding" + + "github.com/TheThingsNetwork/ttn/core" + dbutil "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" +) + +var dbGateways = []byte("gateways") + +// GtwStorage gives a facade to manipulate the router's gateways data +type GtwStorage interface { + read(gid []byte) (gtwEntry, error) + upsert(entry gtwEntry) error + done() error +} + +type gtwEntry struct { + GatewayID []byte + Metadata core.StatsMetadata +} + +type gtwStorage struct { + db dbutil.Interface +} + +// NewGtwStorage creates a new internal storage for the router +func NewGtwStorage(name string) (GtwStorage, error) { + itf, err := dbutil.New(name) + if err != nil { + return nil, errors.New(errors.Operational, err) + } + return >wStorage{db: itf}, nil +} + +// read implements the router.GtwStorage interface { +func (s *gtwStorage) read(gid []byte) (gtwEntry, error) { + itf, err := s.db.Read(gid, >wEntry{}, dbGateways) + if err != nil { + return gtwEntry{}, err + } + return itf.([]gtwEntry)[0], nil // Storage guarantee at least one entry +} + +// upsert implements the router.GtwStorage interface +func (s *gtwStorage) upsert(entry gtwEntry) error { + return s.db.Update(entry.GatewayID, []encoding.BinaryMarshaler{entry}, dbGateways) +} + +// done implements the router.GtwStorage interface +func (s *gtwStorage) done() error { + return s.db.Close() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e gtwEntry) MarshalBinary() ([]byte, error) { + data, err := e.Metadata.MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + rw := readwriter.New(nil) + rw.Write(e.GatewayID) + rw.Write(data) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *gtwEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { + e.GatewayID = make([]byte, len(data)) + copy(e.GatewayID, data) + }) + rw.TryRead(func(data []byte) error { return e.Metadata.UnmarshalBinary(data) }) + return rw.Err() +} diff --git a/core/components/router/router.go b/core/components/router/router.go index 20c6e889f..4544d8899 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -22,7 +22,8 @@ type Components struct { DutyManager dutycycle.DutyManager Brokers []core.BrokerClient Ctx log.Interface - Storage Storage + BrkStorage BrkStorage + GtwStorage GtwStorage } // Options defines a structure to make the instantiation easier to read @@ -78,7 +79,10 @@ func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.S } stats.MarkMeter("router.stat.in") - return new(core.StatsRes), r.Storage.UpdateStats(req.GatewayID, *req.Metadata) + return new(core.StatsRes), r.GtwStorage.upsert(gtwEntry{ + GatewayID: req.GatewayID, + Metadata: *req.Metadata, + }) } // HandleJoin implements the core.RouterClient interface @@ -146,7 +150,7 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co } // Lookup for an existing broker - entries, err := r.Storage.Lookup(fhdr.DevAddr) + entries, err := r.BrkStorage.read(fhdr.DevAddr) if err != nil && err.(errors.Failure).Nature != errors.NotFound { r.Ctx.Warn("Database lookup failed") return new(core.DataRouterRes), errors.New(errors.Operational, err) @@ -207,11 +211,11 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Metadata, error) { // Add Gateway location metadata - if gmeta, err := r.Storage.LookupStats(gid); err == nil { + if entry, err := r.GtwStorage.read(gid); err == nil { r.Ctx.Debug("Adding Gateway Metadata to packet") - metadata.Latitude = gmeta.Latitude - metadata.Longitude = gmeta.Longitude - metadata.Altitude = gmeta.Altitude + metadata.Latitude = entry.Metadata.Latitude + metadata.Longitude = entry.Metadata.Longitude + metadata.Altitude = entry.Metadata.Altitude } // Add Gateway duty metadata @@ -265,14 +269,14 @@ func (r component) send(req interface{}, isBroadcast bool, brokers ...core.Broke cherr := make(chan error, nb) chresp := make(chan struct { Response interface{} - BrokerIndex int + BrokerIndex uint16 }, nb) wg := sync.WaitGroup{} wg.Add(nb) // Run each request for i, broker := range brokers { - go func(index int, broker core.BrokerClient) { + go func(index uint16, broker core.BrokerClient) { defer wg.Done() // Send request @@ -302,9 +306,9 @@ func (r component) send(req interface{}, isBroadcast bool, brokers ...core.Broke // Transfer the response chresp <- struct { Response interface{} - BrokerIndex int + BrokerIndex uint16 }{resp, index} - }(i, broker) + }(uint16(i), broker) } // Wait for each request to be done @@ -356,7 +360,11 @@ func (r component) send(req interface{}, isBroadcast bool, brokers ...core.Broke case *core.JoinBrokerReq: devAddr = resp.Response.(*core.JoinBrokerRes).DevAddr } - if err := r.Storage.Store(devAddr, resp.BrokerIndex); err != nil { + err := r.BrkStorage.create(brkEntry{ + DevAddr: devAddr, + BrokerIndex: resp.BrokerIndex, + }) + if err != nil { r.Ctx.WithError(err).Warn("Failed to store accepted broker") } } diff --git a/core/components/router/storage.go b/core/components/router/storage.go index fa487998a..d5770b7f8 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -4,92 +4,72 @@ package router import ( - "bytes" + "encoding" "encoding/binary" "fmt" "sync" "time" - "github.com/TheThingsNetwork/ttn/core" dbutil "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/readwriter" ) -// Storage gives a facade to manipulate the router database -type Storage interface { - Lookup(devAddr []byte) ([]entry, error) - Store(devAddr []byte, brokerIndex int) error - LookupStats(gid []byte) (core.StatsMetadata, error) - UpdateStats(gid []byte, metadata core.StatsMetadata) error - Close() error +var dbBrokers = []byte("brokers") + +// BrkStorage gives a facade to manipulate the router's brokers +type BrkStorage interface { + read(devAddr []byte) ([]brkEntry, error) + create(entry brkEntry) error + //remove(entry brkEntry) error + done() error } -type entry struct { - BrokerIndex int +type brkEntry struct { + DevAddr []byte + BrokerIndex uint16 until time.Time } -type storage struct { +type brkStorage struct { sync.Mutex - db dbutil.Interface ExpiryDelay time.Duration + db dbutil.Interface } -const ( - dbBrokers = "brokers" - dbGateway = "gateways" -) - -// NewStorage creates a new internal storage for the router -func NewStorage(name string, delay time.Duration) (Storage, error) { +// NewBrkStorage creates a new internal storage for the router +func NewBrkStorage(name string, delay time.Duration) (BrkStorage, error) { itf, err := dbutil.New(name) if err != nil { return nil, errors.New(errors.Operational, err) } - - return &storage{db: itf, ExpiryDelay: delay}, nil -} - -// UpdateStats implements the router.Storage interface -func (s *storage) UpdateStats(gid []byte, metadata core.StatsMetadata) error { - return s.db.Replace(dbGateway, gid, []dbutil.Entry{&metadata}) + return &brkStorage{db: itf, ExpiryDelay: delay}, nil } -// LookupStats implements the router.Storage interface -func (s *storage) LookupStats(gid []byte) (core.StatsMetadata, error) { - itf, err := s.db.Lookup(dbGateway, gid, &core.StatsMetadata{}) - if err != nil { - return core.StatsMetadata{}, err - } - entries := itf.([]core.StatsMetadata) - if len(entries) == 0 { - return core.StatsMetadata{}, errors.New(errors.NotFound, "Not entry found for given gateway") - } - return entries[0], nil -} - -// Lookup implements the router.Storage interface -func (s *storage) Lookup(devAddr []byte) ([]entry, error) { +// read implements the router.BrkStorage interface +func (s *brkStorage) read(devAddr []byte) ([]brkEntry, error) { s.Lock() defer s.Unlock() - itf, err := s.db.Lookup(dbBrokers, devAddr, &entry{}) + itf, err := s.db.Read(devAddr, &brkEntry{}, dbBrokers) if err != nil { return nil, err } - entries := itf.([]entry) + entries := itf.([]brkEntry) if s.ExpiryDelay != 0 { - var newEntries []dbutil.Entry - var filtered []entry + // Get rid of expired entries + var newEntries []encoding.BinaryMarshaler + var filtered []brkEntry for _, e := range entries { if e.until.After(time.Now()) { - newEntry := new(entry) + newEntry := new(brkEntry) *newEntry = e newEntries = append(newEntries, newEntry) filtered = append(filtered, e) } } - if err := s.db.Replace(dbBrokers, devAddr, newEntries); err != nil { + // Replace filtered entries + if err := s.db.Update(devAddr, newEntries, dbBrokers); err != nil { return nil, errors.New(errors.Operational, err) } entries = filtered @@ -101,47 +81,40 @@ func (s *storage) Lookup(devAddr []byte) ([]entry, error) { return entries, nil } -// Store implements the router.Storage interface -func (s *storage) Store(devAddr []byte, brokerIndex int) error { +// create implements the router.BrkStorage interface +func (s *brkStorage) create(entry brkEntry) error { s.Lock() defer s.Unlock() - return s.db.Store(dbBrokers, devAddr, []dbutil.Entry{&entry{ - BrokerIndex: brokerIndex, - until: time.Now().Add(s.ExpiryDelay), - }}) + entry.until = time.Now().Add(s.ExpiryDelay) + return s.db.Append(entry.DevAddr, []encoding.BinaryMarshaler{entry}, dbBrokers) } -// Close implements the router.Storage interface -func (s *storage) Close() error { +// done implements the router.BrkStorage interface +func (s *brkStorage) done() error { return s.db.Close() } -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e entry) MarshalBinary() ([]byte, error) { +// MarshalBinary implements the encoding.BinaryMarshaler +func (e brkEntry) MarshalBinary() ([]byte, error) { data, err := e.until.MarshalBinary() if err != nil { return nil, errors.New(errors.Structural, err) } - - buf := new(bytes.Buffer) - binary.Write(buf, binary.BigEndian, uint16(e.BrokerIndex)) - binary.Write(buf, binary.BigEndian, data) - return buf.Bytes(), nil + rw := readwriter.New(nil) + rw.Write(e.BrokerIndex) + rw.Write(e.DevAddr) + rw.Write(data) + return rw.Bytes() } -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *entry) UnmarshalBinary(data []byte) error { - buf := bytes.NewBuffer(data) - - // e.Broker - index := new(uint16) - binary.Read(buf, binary.BigEndian, index) - e.BrokerIndex = int(*index) - - // e.until - if err := e.until.UnmarshalBinary(buf.Next(buf.Len())); err != nil { - return errors.New(errors.Structural, err) - } - - return nil +// UnmarshalBinary implements the encoding.BinaryUnmarshaler +func (e *brkEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.BrokerIndex = binary.BigEndian.Uint16(data) }) + rw.Read(func(data []byte) { + e.DevAddr = make([]byte, len(data)) + copy(e.DevAddr, data) + }) + rw.TryRead(func(data []byte) error { return e.until.UnmarshalBinary(data) }) + return rw.Err() } diff --git a/core/storage/storage.go b/core/storage/storage.go index b4c9099d3..f4470d15e 100644 --- a/core/storage/storage.go +++ b/core/storage/storage.go @@ -124,11 +124,13 @@ func (itf store) Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[ // Then, interpret them as instance of 'shape' r := readwriter.New(rawEntry) entries := reflect.MakeSlice(reflect.SliceOf(entryType.Elem()), 0, 0) + var nb uint for { r.Read(func(data []byte) { entry := reflect.New(entryType.Elem()).Interface() entry.(encoding.BinaryUnmarshaler).UnmarshalBinary(data) entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) + nb++ }) if err = r.Err(); err != nil { failure, ok := err.(errors.Failure) @@ -138,6 +140,9 @@ func (itf store) Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[ return nil, errors.New(errors.Operational, err) } } + if nb == 0 { + return nil, errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) + } return entries.Interface(), nil } From 6276513e2ebd976ba67c78a4d9e23246f986b473 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 13:48:42 +0100 Subject: [PATCH 1144/2266] [refactor/storages] Rewrite mocks accordingly --- core/components/router/mockstorage_test.go | 109 ++++++++++++--------- 1 file changed, 61 insertions(+), 48 deletions(-) diff --git a/core/components/router/mockstorage_test.go b/core/components/router/mockstorage_test.go index 7dd63f2dd..d128c3a19 100644 --- a/core/components/router/mockstorage_test.go +++ b/core/components/router/mockstorage_test.go @@ -3,73 +3,86 @@ package router -import ( - "github.com/TheThingsNetwork/ttn/core" -) - -// MockStorage mocks the router.Storage interface -type MockStorage struct { +// MockBrkStorage mocks the router.BrkStorage interface +type MockBrkStorage struct { Failures map[string]error - InLookup struct { + InRead struct { DevAddr []byte } - OutLookup struct { - Entries []entry - } - InStore struct { - DevAddr []byte - BrokerIndex int - } - InLookupStats struct { - GID []byte + OutRead struct { + Entries []brkEntry } - OutLookupStats struct { - Metadata core.StatsMetadata + InCreate struct { + Entry brkEntry } - InUpdateStats struct { - GID []byte - Metadata core.StatsMetadata - } - InClose struct { + InDone struct { Called bool } } -// NewMockStorage creates a new mock storage -func NewMockStorage() *MockStorage { - return &MockStorage{ +// NewMockBrkStorage creates a new mock BrkStorage +func NewMockBrkStorage() *MockBrkStorage { + return &MockBrkStorage{ Failures: make(map[string]error), } } -// Lookup implements the router.Storage interface -func (m *MockStorage) Lookup(devAddr []byte) ([]entry, error) { - m.InLookup.DevAddr = devAddr - return m.OutLookup.Entries, m.Failures["Lookup"] +// read implements the router.BrkStorage interface +func (m *MockBrkStorage) read(devAddr []byte) ([]brkEntry, error) { + m.InRead.DevAddr = devAddr + return m.OutRead.Entries, m.Failures["read"] +} + +// create implements the router.BrkStorage interface +func (m *MockBrkStorage) create(entry brkEntry) error { + m.InCreate.Entry = entry + return m.Failures["create"] +} + +// done implements the router.BrkStorage interface +func (m *MockBrkStorage) done() error { + m.InDone.Called = true + return m.Failures["done"] } -// Store implements the router.Storage interface -func (m *MockStorage) Store(devAddr []byte, brokerIndex int) error { - m.InStore.DevAddr = devAddr - m.InStore.BrokerIndex = brokerIndex - return m.Failures["Store"] +// MockGtwStorage mocks the router.GtwStorage interface +type MockGtwStorage struct { + Failures map[string]error + InRead struct { + DevAddr []byte + } + OutRead struct { + Entry gtwEntry + } + InUpsert struct { + Entry gtwEntry + } + InDone struct { + Called bool + } +} + +// NewMockGtwStorage Upserts a new mock GtwStorage +func NewMockGtwStorage() *MockGtwStorage { + return &MockGtwStorage{ + Failures: make(map[string]error), + } } -// LookupStats implements the router.Storage interface -func (m *MockStorage) LookupStats(gid []byte) (core.StatsMetadata, error) { - m.InLookupStats.GID = gid - return m.OutLookupStats.Metadata, m.Failures["LookupStats"] +// read implements the router.GtwStorage interface +func (m *MockGtwStorage) read(devAddr []byte) (gtwEntry, error) { + m.InRead.DevAddr = devAddr + return m.OutRead.Entry, m.Failures["read"] } -// UpdateStats implements the router.Storage interface -func (m *MockStorage) UpdateStats(gid []byte, metadata core.StatsMetadata) error { - m.InUpdateStats.GID = gid - m.InUpdateStats.Metadata = metadata - return m.Failures["UpdateStats"] +// Upsert implements the router.GtwStorage interface +func (m *MockGtwStorage) upsert(entry gtwEntry) error { + m.InUpsert.Entry = entry + return m.Failures["upsert"] } -// Close implements the router.Storage interface -func (m *MockStorage) Close() error { - m.InClose.Called = true - return m.Failures["Close"] +// done implements the router.GtwStorage interface +func (m *MockGtwStorage) done() error { + m.InDone.Called = true + return m.Failures["done"] } From 597b233ea87f3d139502a85623ba4804332dd0ef Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 13:48:54 +0100 Subject: [PATCH 1145/2266] [refactor/storages] Rewrite tests to accept new interfaces --- core/components/router/router_test.go | 708 +++++++++++++++---------- core/components/router/storage_test.go | 196 +++---- 2 files changed, 523 insertions(+), 381 deletions(-) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 6afac4bf6..1be71804d 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -21,8 +21,9 @@ func TestHandleStats(t *testing.T) { // Build components := Components{ - Ctx: GetLogger(t, "Router"), - Storage: NewMockStorage(), + Ctx: GetLogger(t, "Router"), + BrkStorage: NewMockBrkStorage(), + GtwStorage: NewMockGtwStorage(), } r := New(components, Options{}) req := &core.StatsReq{ @@ -37,8 +38,10 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr *string var wantRes = new(core.StatsRes) - var wantID = req.GatewayID - var wantMeta = *req.Metadata + var wantEntry = gtwEntry{ + GatewayID: req.GatewayID, + Metadata: *req.Metadata, + } // Operate res, err := r.HandleStats(context.Background(), req) @@ -46,8 +49,7 @@ func TestHandleStats(t *testing.T) { // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Stats Responses") - Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") - Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") } // -------------------- @@ -57,8 +59,9 @@ func TestHandleStats(t *testing.T) { // Build components := Components{ - Ctx: GetLogger(t, "Router"), - Storage: NewMockStorage(), + Ctx: GetLogger(t, "Router"), + BrkStorage: NewMockBrkStorage(), + GtwStorage: NewMockGtwStorage(), } r := New(components, Options{}) var req *core.StatsReq @@ -66,8 +69,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrStructural var wantRes = new(core.StatsRes) - var wantID []byte - var wantMeta core.StatsMetadata + var wantEntry gtwEntry // Operate res, err := r.HandleStats(context.Background(), req) @@ -75,8 +77,7 @@ func TestHandleStats(t *testing.T) { // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Stats Responses") - Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") - Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") } // -------------------- @@ -86,8 +87,9 @@ func TestHandleStats(t *testing.T) { // Build components := Components{ - Ctx: GetLogger(t, "Router"), - Storage: NewMockStorage(), + Ctx: GetLogger(t, "Router"), + BrkStorage: NewMockBrkStorage(), + GtwStorage: NewMockGtwStorage(), } r := New(components, Options{}) req := &core.StatsReq{ @@ -102,8 +104,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrStructural var wantRes = new(core.StatsRes) - var wantID []byte - var wantMeta core.StatsMetadata + var wantEntry gtwEntry // Operate res, err := r.HandleStats(context.Background(), req) @@ -111,8 +112,7 @@ func TestHandleStats(t *testing.T) { // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Stats Responses") - Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") - Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") } // -------------------- @@ -122,8 +122,9 @@ func TestHandleStats(t *testing.T) { // Build components := Components{ - Ctx: GetLogger(t, "Router"), - Storage: NewMockStorage(), + Ctx: GetLogger(t, "Router"), + BrkStorage: NewMockBrkStorage(), + GtwStorage: NewMockGtwStorage(), } r := New(components, Options{}) req := &core.StatsReq{ @@ -133,8 +134,7 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrStructural var wantRes = new(core.StatsRes) - var wantID []byte - var wantMeta core.StatsMetadata + var wantEntry gtwEntry // Operate res, err := r.HandleStats(context.Background(), req) @@ -142,8 +142,7 @@ func TestHandleStats(t *testing.T) { // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Stats Responses") - Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") - Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") } // -------------------- @@ -153,10 +152,11 @@ func TestHandleStats(t *testing.T) { // Build components := Components{ - Ctx: GetLogger(t, "Router"), - Storage: NewMockStorage(), + Ctx: GetLogger(t, "Router"), + BrkStorage: NewMockBrkStorage(), + GtwStorage: NewMockGtwStorage(), } - components.Storage.(*MockStorage).Failures["UpdateStats"] = errors.New(errors.Operational, "") + components.GtwStorage.(*MockGtwStorage).Failures["upsert"] = errors.New(errors.Operational, "Mock Error") r := New(components, Options{}) req := &core.StatsReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -170,8 +170,10 @@ func TestHandleStats(t *testing.T) { // Expect var wantErr = ErrOperational var wantRes = new(core.StatsRes) - var wantID = req.GatewayID - var wantMeta = *req.Metadata + var wantEntry = gtwEntry{ + GatewayID: req.GatewayID, + Metadata: *req.Metadata, + } // Operate res, err := r.HandleStats(context.Background(), req) @@ -179,8 +181,7 @@ func TestHandleStats(t *testing.T) { // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Stats Responses") - Check(t, wantID, components.Storage.(*MockStorage).InUpdateStats.GID, "Gateway IDs") - Check(t, wantMeta, components.Storage.(*MockStorage).InUpdateStats.Metadata, "Gateways Metas") + Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") } } @@ -191,12 +192,15 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -223,7 +227,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore int + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -232,7 +236,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -243,12 +247,15 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -275,7 +282,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore int + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -284,7 +291,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -295,12 +302,15 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -327,7 +337,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore int + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -336,7 +346,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -347,12 +357,15 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: nil, @@ -364,7 +377,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore int + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -373,7 +386,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -384,12 +397,15 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -416,7 +432,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore int + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -425,7 +441,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -436,13 +452,19 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 1, until: time.Now().Add(time.Hour), @@ -452,7 +474,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br, br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -483,13 +506,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -498,7 +521,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -511,18 +534,25 @@ func TestHandleData(t *testing.T) { br1 := mocks.NewBrokerClient() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") br2 := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br1, br2}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -553,13 +583,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 1 + var wantStore uint16 = 1 // Operate res, err := r.HandleData(context.Background(), req) @@ -569,7 +599,7 @@ func TestHandleData(t *testing.T) { Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -582,19 +612,26 @@ func TestHandleData(t *testing.T) { br1 := mocks.NewBrokerClient() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") br2 := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") st.Failures["Store"] = errors.New(errors.Operational, "Mock Error") r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br1, br2}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -625,13 +662,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 1 + var wantStore uint16 = 1 // Operate res, err := r.HandleData(context.Background(), req) @@ -641,7 +678,7 @@ func TestHandleData(t *testing.T) { Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -652,19 +689,26 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.Failures["Lookup"] = errors.New(errors.Operational, "Mock Error") + st.Failures["read"] = errors.New(errors.Operational, "Mock Error") r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br, br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -693,7 +737,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrOperational var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -702,7 +746,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -712,15 +756,21 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - dm.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + dm.Failures["read"] = errors.New(errors.NotFound, "Mock Error") br := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 1, until: time.Now().Add(time.Hour), @@ -730,7 +780,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br, br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -761,13 +812,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -776,7 +827,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -787,13 +838,19 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 1, until: time.Now().Add(time.Hour), @@ -803,7 +860,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br, br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -832,7 +890,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -841,7 +899,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -855,18 +913,25 @@ func TestHandleData(t *testing.T) { br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") br2 := mocks.NewBrokerClient() br2.Failures["HandleData"] = errors.New(errors.Operational, "Mock Error") - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br1, br2}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -897,13 +962,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -913,7 +978,7 @@ func TestHandleData(t *testing.T) { Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -925,18 +990,25 @@ func TestHandleData(t *testing.T) { dm := mocks.NewDutyManager() br1 := mocks.NewBrokerClient() br2 := mocks.NewBrokerClient() - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.Failures["Lookup"] = errors.New(errors.NotFound, "Mock Error") + st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br1, br2}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -967,13 +1039,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -983,7 +1055,7 @@ func TestHandleData(t *testing.T) { Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -995,13 +1067,19 @@ func TestHandleData(t *testing.T) { dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() br.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 1, until: time.Now().Add(time.Hour), @@ -1011,7 +1089,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br, br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -1042,13 +1121,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 // Operate res, err := r.HandleData(context.Background(), req) @@ -1057,7 +1136,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") } // -------------------- @@ -1087,13 +1166,19 @@ func TestHandleData(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 0, until: time.Now().Add(time.Hour), @@ -1103,7 +1188,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -1137,13 +1223,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 var wantUpdateGtw = req.GatewayID // Operate @@ -1153,7 +1239,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1184,13 +1270,19 @@ func TestHandleData(t *testing.T) { }, Metadata: nil, } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 0, until: time.Now().Add(time.Hour), @@ -1200,7 +1292,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -1231,13 +1324,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 var wantUpdateGtw []byte // Operate @@ -1247,7 +1340,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1279,13 +1372,19 @@ func TestHandleData(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } - st.OutLookup.Entries = []entry{ + st.OutRead.Entries = []brkEntry{ { BrokerIndex: 0, until: time.Now().Add(time.Hour), @@ -1295,7 +1394,8 @@ func TestHandleData(t *testing.T) { DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.DataRouterReq{ Payload: &core.LoRaWANData{ @@ -1326,13 +1426,13 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 0 + var wantStore uint16 = 0 var wantUpdateGtw = req.GatewayID // Operate @@ -1342,7 +1442,7 @@ func TestHandleData(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } } @@ -1362,17 +1462,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: &core.Metadata{}, } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br1, br2}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1395,13 +1502,13 @@ func TestHandleJoin(t *testing.T) { DevEUI: req.DevEUI, DevNonce: req.DevNonce, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 1 + var wantStore uint16 = 1 var wantUpdateGtw = req.GatewayID // Operate @@ -1412,7 +1519,7 @@ func TestHandleJoin(t *testing.T) { Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br1.InHandleJoin.Req, "Broker Join Requests") Check(t, wantBrReq, br2.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1433,17 +1540,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: &core.Metadata{}, } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br1, br2}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1463,13 +1577,13 @@ func TestHandleJoin(t *testing.T) { DevEUI: req.DevEUI, DevNonce: req.DevNonce, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore = 1 + var wantStore uint16 = 1 var wantUpdateGtw = req.GatewayID // Operate @@ -1480,7 +1594,7 @@ func TestHandleJoin(t *testing.T) { Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br1.InHandleJoin.Req, "Broker Join Requests") Check(t, wantBrReq, br2.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1499,17 +1613,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1523,7 +1644,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.JoinRouterRes) var wantBrReq *core.JoinBrokerReq - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1533,7 +1654,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1552,17 +1673,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1578,7 +1706,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.JoinRouterRes) var wantBrReq *core.JoinBrokerReq - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1588,7 +1716,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1607,17 +1735,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1633,7 +1768,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.JoinRouterRes) var wantBrReq *core.JoinBrokerReq - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1643,7 +1778,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1662,17 +1797,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1688,7 +1830,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.JoinRouterRes) var wantBrReq *core.JoinBrokerReq - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1698,7 +1840,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1717,17 +1859,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: new(core.Metadata), } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: nil, @@ -1743,7 +1892,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.JoinRouterRes) var wantBrReq *core.JoinBrokerReq - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1753,7 +1902,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1766,17 +1915,24 @@ func TestHandleJoin(t *testing.T) { dm := mocks.NewDutyManager() br := mocks.NewBrokerClient() br.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1796,13 +1952,13 @@ func TestHandleJoin(t *testing.T) { DevEUI: req.DevEUI, DevNonce: req.DevNonce, Metadata: &core.Metadata{ - Altitude: st.OutLookupStats.Metadata.Altitude, - Longitude: st.OutLookupStats.Metadata.Longitude, - Latitude: st.OutLookupStats.Metadata.Latitude, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, Frequency: req.Metadata.Frequency, }, } - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1812,7 +1968,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1830,17 +1986,24 @@ func TestHandleJoin(t *testing.T) { }, Metadata: &core.Metadata{}, } - st := NewMockStorage() - st.OutLookupStats.Metadata = core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, + st := NewMockBrkStorage() + gt := NewMockGtwStorage() + + gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} + gt.OutRead.Entry = gtwEntry{ + GatewayID: gid, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: 14.0, + Latitude: -14.0, + }, } r := New(Components{ DutyManager: dm, Brokers: []core.BrokerClient{br}, Ctx: GetLogger(t, "Router"), - Storage: st, + BrkStorage: st, + GtwStorage: gt, }, Options{}) req := &core.JoinRouterReq{ GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, @@ -1856,7 +2019,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.JoinRouterRes) var wantBrReq *core.JoinBrokerReq - var wantStore int + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1866,7 +2029,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InStore.BrokerIndex, "Brokers stored") + Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") } @@ -1877,7 +2040,8 @@ func TestStart(t *testing.T) { Ctx: GetLogger(t, "Router"), DutyManager: mocks.NewDutyManager(), Brokers: []core.BrokerClient{mocks.NewBrokerClient()}, - Storage: NewMockStorage(), + BrkStorage: NewMockBrkStorage(), + GtwStorage: NewMockGtwStorage(), }, Options{NetAddr: "localhost:8886"}) cherr := make(chan error) diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go index cfe6c5211..f0b977f6a 100644 --- a/core/components/router/storage_test.go +++ b/core/components/router/storage_test.go @@ -9,13 +9,12 @@ import ( "testing" "time" - "github.com/TheThingsNetwork/ttn/core" . "github.com/TheThingsNetwork/ttn/utils/testing" ) -const storageDB = "TestRouterStorage.db" +const storageDB = "TestBrkStorage.db" -func CheckEntries(t *testing.T, want []entry, got []entry) { +func CheckEntries(t *testing.T, want []brkEntry, got []brkEntry) { for i, w := range want { if i >= len(got) { Ko(t, "Didn't got enough entries: %v", got) @@ -29,7 +28,7 @@ func CheckEntries(t *testing.T, want []entry, got []entry) { } } -func TestStoreAndLookup(t *testing.T) { +func TestCreateAndRead(t *testing.T) { storageDB := path.Join(os.TempDir(), storageDB) defer func() { @@ -40,120 +39,140 @@ func TestStoreAndLookup(t *testing.T) { { Desc(t, "Create a new storage") - db, err := NewStorage(storageDB, time.Hour) + db, err := NewBrkStorage(storageDB, time.Hour) CheckErrors(t, nil, err) - err = db.Close() + err = db.done() CheckErrors(t, nil, err) } // ------------------ { - Desc(t, "Store then lookup a device") + Desc(t, "create then read a device") // Build - db, _ := NewStorage(storageDB, time.Hour) - devAddr := []byte{0, 0, 0, 1} + db, _ := NewBrkStorage(storageDB, time.Hour) + entry := brkEntry{ + DevAddr: []byte{0, 0, 0, 1}, + BrokerIndex: 1, + } // Operate - err := db.Store(devAddr, 1) + err := db.create(entry) FatalUnless(t, err) - gotEntry, err := db.Lookup(devAddr) + gotbrkEntry, err := db.read(entry.DevAddr) // Expectations - wantEntry := []entry{ + wantbrkEntry := []brkEntry{ { - BrokerIndex: 1, + DevAddr: entry.DevAddr, + BrokerIndex: entry.BrokerIndex, until: time.Now().Add(time.Hour), }, } // Check CheckErrors(t, nil, err) - CheckEntries(t, wantEntry, gotEntry) - _ = db.Close() + CheckEntries(t, wantbrkEntry, gotbrkEntry) + _ = db.done() } // ------------------ { - Desc(t, "Lookup non-existing entry") + Desc(t, "read non-existing brkEntry") // Build - db, _ := NewStorage(storageDB, time.Hour) - devAddr := []byte{0, 0, 0, 2} + db, _ := NewBrkStorage(storageDB, time.Hour) + entry := brkEntry{ + DevAddr: []byte{0, 0, 0, 2}, + BrokerIndex: 1, + } // Operate - gotEntry, err := db.Lookup(devAddr) + gotbrkEntry, err := db.read(entry.DevAddr) // Checks CheckErrors(t, ErrNotFound, err) - CheckEntries(t, nil, gotEntry) - _ = db.Close() + CheckEntries(t, nil, gotbrkEntry) + _ = db.done() } // ------------------ { - Desc(t, "Lookup an expired entry") + Desc(t, "read an expired brkEntry") // Build - db, _ := NewStorage(storageDB, time.Millisecond*100) - devAddr := []byte{0, 0, 0, 3} + db, _ := NewBrkStorage(storageDB, time.Millisecond*100) + entry := brkEntry{ + DevAddr: []byte{0, 0, 0, 3}, + BrokerIndex: 1, + } // Operate - _ = db.Store(devAddr, 1) + _ = db.create(entry) <-time.After(time.Millisecond * 200) - gotEntry, err := db.Lookup(devAddr) + gotbrkEntry, err := db.read(entry.DevAddr) // Checks CheckErrors(t, ErrNotFound, err) - CheckEntries(t, nil, gotEntry) - _ = db.Close() + CheckEntries(t, nil, gotbrkEntry) + _ = db.done() } // ------------------ { - Desc(t, "Store above an expired entry") + Desc(t, "create above an expired brkEntry") // Build - db, _ := NewStorage(storageDB, time.Millisecond*100) - devAddr := []byte{0, 0, 0, 4} + db, _ := NewBrkStorage(storageDB, time.Millisecond*100) + entry := brkEntry{ + DevAddr: []byte{0, 0, 0, 4}, + BrokerIndex: 1, + } + entry2 := brkEntry{ + DevAddr: []byte{0, 0, 0, 4}, + BrokerIndex: 12, + } // Operate - _ = db.Store(devAddr, 1) + _ = db.create(entry) <-time.After(time.Millisecond * 200) - err := db.Store(devAddr, 2) + err := db.create(entry2) FatalUnless(t, err) - gotEntry, err := db.Lookup(devAddr) + gotbrkEntry, err := db.read(entry.DevAddr) // Expectations - wantEntry := []entry{ + wantbrkEntry := []brkEntry{ { - BrokerIndex: 2, + DevAddr: entry2.DevAddr, + BrokerIndex: entry2.BrokerIndex, until: time.Now().Add(time.Millisecond * 100), }, } // Checks CheckErrors(t, nil, err) - CheckEntries(t, wantEntry, gotEntry) - _ = db.Close() + CheckEntries(t, wantbrkEntry, gotbrkEntry) + _ = db.done() } // ------------------ { - Desc(t, "Store on a closed database") + Desc(t, "create on a closed database") // Build - db, _ := NewStorage(storageDB, time.Hour) - _ = db.Close() - devAddr := []byte{0, 0, 0, 5} + db, _ := NewBrkStorage(storageDB, time.Hour) + _ = db.done() + entry := brkEntry{ + DevAddr: []byte{0, 0, 0, 5}, + } // Operate - err := db.Store(devAddr, 1) + err := db.create(entry) // Checks CheckErrors(t, ErrOperational, err) @@ -162,103 +181,62 @@ func TestStoreAndLookup(t *testing.T) { // ------------------ { - Desc(t, "Lookup on a closed database") + Desc(t, "read on a closed database") // Build - db, _ := NewStorage(storageDB, time.Hour) - _ = db.Close() + db, _ := NewBrkStorage(storageDB, time.Hour) + _ = db.done() devAddr := []byte{0, 0, 0, 1} // Operate - gotEntry, err := db.Lookup(devAddr) + gotbrkEntry, err := db.read(devAddr) // Checks CheckErrors(t, ErrOperational, err) - CheckEntries(t, nil, gotEntry) + CheckEntries(t, nil, gotbrkEntry) } // ------------------ { - Desc(t, "Store two entries in a row") + Desc(t, "create two entries in a row") // Build - db, _ := NewStorage(storageDB, time.Hour) - devAddr := []byte{0, 0, 0, 6} + db, _ := NewBrkStorage(storageDB, time.Hour) + entry1 := brkEntry{ + DevAddr: []byte{0, 0, 0, 6}, + BrokerIndex: 14, + } + + entry2 := brkEntry{ + DevAddr: []byte{0, 0, 0, 6}, + BrokerIndex: 20, + } // Operate - err := db.Store(devAddr, 1) + err := db.create(entry1) FatalUnless(t, err) - err = db.Store(devAddr, 2) + err = db.create(entry2) FatalUnless(t, err) - gotEntries, err := db.Lookup(devAddr) + gotEntries, err := db.read(entry1.DevAddr) FatalUnless(t, err) // Expectations - wantEntries := []entry{ + wantEntries := []brkEntry{ { - BrokerIndex: 1, + DevAddr: entry1.DevAddr, + BrokerIndex: entry1.BrokerIndex, until: time.Now().Add(time.Hour), }, { - BrokerIndex: 2, + DevAddr: entry2.DevAddr, + BrokerIndex: entry2.BrokerIndex, until: time.Now().Add(time.Hour), }, } // Check CheckEntries(t, wantEntries, gotEntries) - _ = db.Close() - } -} - -func TestUpdateAndLookup(t *testing.T) { - storageDB := path.Join(os.TempDir(), storageDB) - - defer func() { - os.Remove(storageDB) - }() - - // ------------------ - - { - Desc(t, "Store then lookup stats") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - stats := core.StatsMetadata{ - Altitude: 35, - Longitude: -3.4546, - Latitude: 35.212, - } - gid := []byte{0, 0, 0, 0, 0, 0, 0, 1} - - // Operate - errUpdate := db.UpdateStats(gid, stats) - got, errLookup := db.LookupStats(gid) - - // Check - CheckErrors(t, nil, errUpdate) - CheckErrors(t, nil, errLookup) - Check(t, stats, got, "Metadata") - _ = db.Close() - } - - // ------------------ - - { - Desc(t, "Lookup stats from unknown gateway") - - // Build - db, _ := NewStorage(storageDB, time.Hour) - gid := []byte{0, 0, 0, 0, 0, 0, 0, 2} - - // Operate - got, errLookup := db.LookupStats(gid) - - // Check - CheckErrors(t, ErrNotFound, errLookup) - Check(t, core.StatsMetadata{}, got, "Metadata") - _ = db.Close() + _ = db.done() } } From 027764ec5da9fcd1ccfafd2ce2f38180589ec030 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 13:58:52 +0100 Subject: [PATCH 1146/2266] [refactor/storages] Add test for gateway storage --- core/components/router/gtwStorage_test.go | 172 ++++++++++++++++++++++ 1 file changed, 172 insertions(+) create mode 100644 core/components/router/gtwStorage_test.go diff --git a/core/components/router/gtwStorage_test.go b/core/components/router/gtwStorage_test.go new file mode 100644 index 000000000..68a1d7eea --- /dev/null +++ b/core/components/router/gtwStorage_test.go @@ -0,0 +1,172 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "os" + "path" + "testing" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +const gatewaysDB = "TestGtwStorage.db" + +func TestUpsertAndRead(t *testing.T) { + gatewaysDB := path.Join(os.TempDir(), gatewaysDB) + + defer func() { + os.Remove(gatewaysDB) + }() + + // ------------------ + + { + Desc(t, "Createa new storage") + db, err := NewGtwStorage(gatewaysDB) + CheckErrors(t, nil, err) + err = db.done() + CheckErrors(t, nil, err) + } + + // ------------------ + + { + Desc(t, "upsert then read a device") + + // Build + db, _ := NewGtwStorage(gatewaysDB) + entry := gtwEntry{ + GatewayID: []byte{0, 0, 0, 1}, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: -14, + }, + } + + // Operate + err := db.upsert(entry) + FatalUnless(t, err) + gotGtwEntry, err := db.read(entry.GatewayID) + + // Expectations + wantGtwEntry := gtwEntry{ + GatewayID: entry.GatewayID, + Metadata: entry.Metadata, + } + + // Check + CheckErrors(t, nil, err) + Check(t, wantGtwEntry, gotGtwEntry, "Gateway Entries") + _ = db.done() + } + + // ------------------ + + { + Desc(t, "read non-existing gtwEntry") + + // Build + db, _ := NewGtwStorage(gatewaysDB) + entry := gtwEntry{ + GatewayID: []byte{0, 0, 0, 2}, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: -14, + }, + } + + // Operate + gotGtwEntry, err := db.read(entry.GatewayID) + + // Checks + CheckErrors(t, ErrNotFound, err) + Check(t, gtwEntry{}, gotGtwEntry, "Gateway Entries") + _ = db.done() + } + + // ------------------ + + { + Desc(t, "upsert on a closed database") + + // Build + db, _ := NewGtwStorage(gatewaysDB) + _ = db.done() + entry := gtwEntry{ + GatewayID: []byte{0, 0, 0, 5}, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: -14, + }, + } + + // Operate + err := db.upsert(entry) + + // Checks + CheckErrors(t, ErrOperational, err) + } + + // ------------------ + + { + Desc(t, "read on a closed database") + + // Build + db, _ := NewGtwStorage(gatewaysDB) + _ = db.done() + devAddr := []byte{0, 0, 0, 1} + + // Operate + gotGtwEntry, err := db.read(devAddr) + + // Checks + CheckErrors(t, ErrOperational, err) + Check(t, gtwEntry{}, gotGtwEntry, "Gateway Entries") + } + + // ------------------ + + { + Desc(t, "upsert two entries in a row") + + // Build + db, _ := NewGtwStorage(gatewaysDB) + entry1 := gtwEntry{ + GatewayID: []byte{0, 0, 0, 6}, + Metadata: core.StatsMetadata{ + Altitude: 14, + Longitude: -14, + }, + } + + entry2 := gtwEntry{ + GatewayID: []byte{0, 0, 0, 6}, + Metadata: core.StatsMetadata{ + Altitude: 42, + Longitude: -42, + }, + } + + // Operate + err := db.upsert(entry1) + FatalUnless(t, err) + err = db.upsert(entry2) + FatalUnless(t, err) + gotEntries, err := db.read(entry1.GatewayID) + FatalUnless(t, err) + + // Expectations + wantEntries := gtwEntry{ + GatewayID: entry1.GatewayID, + Metadata: entry2.Metadata, + } + + // Check + Check(t, wantEntries, gotEntries, "Gateway Entries") + _ = db.done() + } +} From 79eb214f50e8e505c7a1a6b53b29aa5c0699ddbe Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 14:45:59 +0100 Subject: [PATCH 1147/2266] [refactor/storages] Remove DevEUI + DevNonces from appStorage -> implement two-step authentication there --- core/components/broker/appStorage.go | 37 +--- core/components/broker/appStorage_test.go | 49 ++--- core/components/broker/broker.go | 28 ++- core/components/broker/broker_test.go | 216 ++++++++++++++++------ core/components/broker/controller.go | 64 ++++++- core/components/broker/controller_test.go | 81 ++++++++ core/components/broker/mocks_test.go | 27 ++- 7 files changed, 367 insertions(+), 135 deletions(-) diff --git a/core/components/broker/appStorage.go b/core/components/broker/appStorage.go index 0ded59daf..d71a4d38e 100644 --- a/core/components/broker/appStorage.go +++ b/core/components/broker/appStorage.go @@ -4,7 +4,6 @@ package broker import ( - "bytes" "encoding" dbutil "github.com/TheThingsNetwork/ttn/core/storage" @@ -16,18 +15,16 @@ import ( // AppStorage gives a facade for manipulating the broker applications infos type AppStorage interface { - read(appEUI []byte, devEUI []byte) (appEntry, error) + read(appEUI []byte) (appEntry, error) upsert(entry appEntry) error done() error } type appEntry struct { - Dialer Dialer - AppEUI []byte - DevEUI []byte - DevNonces [][]byte - Password []byte - Salt []byte + Dialer Dialer + AppEUI []byte + Password []byte + Salt []byte } type appStorage struct { @@ -45,8 +42,8 @@ func NewAppStorage(name string) (AppStorage, error) { } // read implements the AppStorage interface -func (s *appStorage) read(appEUI []byte, devEUI []byte) (appEntry, error) { - itf, err := s.db.Read(nil, &appEntry{}, appEUI, devEUI) +func (s *appStorage) read(appEUI []byte) (appEntry, error) { + itf, err := s.db.Read(nil, &appEntry{}, appEUI) if err != nil { return appEntry{}, err } @@ -59,7 +56,7 @@ func (s *appStorage) read(appEUI []byte, devEUI []byte) (appEntry, error) { // upsert implements the AppStorage interface func (s *appStorage) upsert(entry appEntry) error { - return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) + return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI) } // done implements the AppStorage interface { @@ -71,15 +68,9 @@ func (s *appStorage) done() error { func (e appEntry) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write(e.AppEUI) - rw.Write(e.DevEUI) rw.Write(e.Password) rw.Write(e.Salt) rw.Write(e.Dialer.MarshalSafely()) - buf := new(bytes.Buffer) - for _, n := range e.DevNonces { - _, _ = buf.Write(n) - } - rw.Write(buf.Bytes()) return rw.Bytes() } @@ -90,10 +81,6 @@ func (e *appEntry) UnmarshalBinary(data []byte) error { e.AppEUI = make([]byte, len(data)) copy(e.AppEUI, data) }) - rw.Read(func(data []byte) { - e.DevEUI = make([]byte, len(data)) - copy(e.DevEUI, data) - }) rw.Read(func(data []byte) { e.Password = make([]byte, len(data)) copy(e.Password, data) @@ -105,13 +92,5 @@ func (e *appEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) - rw.Read(func(data []byte) { - n := len(data) / 2 // DevNonce -> 2-bytes - for i := 0; i < int(n); i++ { - devNonce := make([]byte, 2, 2) - copy(devNonce, data[2*i:2*i+2]) - e.DevNonces = append(e.DevNonces, devNonce) - } - }) return rw.Err() } diff --git a/core/components/broker/appStorage_test.go b/core/components/broker/appStorage_test.go index 3996aaa55..ddc37fbd9 100644 --- a/core/components/broker/appStorage_test.go +++ b/core/components/broker/appStorage_test.go @@ -35,18 +35,16 @@ func TestReadStore(t *testing.T) { // Build entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 2}, - DevEUI: []byte{0, 2}, - DevNonces: [][]byte{[]byte{1, 2}}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 2}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, } // Operate err := db.upsert(entry) FatalUnless(t, err) - got, err := db.read(entry.AppEUI, entry.DevEUI) + got, err := db.read(entry.AppEUI) // Check CheckErrors(t, nil, err) @@ -60,10 +58,9 @@ func TestReadStore(t *testing.T) { // Build appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 2} - devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} // Operate - _, err := db.read(appEUI, devEUI) + _, err := db.read(appEUI) // Check CheckErrors(t, ErrNotFound, err) @@ -76,12 +73,10 @@ func TestReadStore(t *testing.T) { // Build entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 1}, - DevEUI: []byte{0, 1}, - DevNonces: [][]byte{[]byte{1, 2}}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 1}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, } // Operate @@ -89,7 +84,7 @@ func TestReadStore(t *testing.T) { FatalUnless(t, err) err = db.upsert(entry) FatalUnless(t, err) - got, err := db.read(entry.AppEUI, entry.DevEUI) + got, err := db.read(entry.AppEUI) // Check CheckErrors(t, nil, err) @@ -103,27 +98,23 @@ func TestReadStore(t *testing.T) { // Build entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 1}, - DevEUI: []byte{0, 1}, - DevNonces: [][]byte{[]byte{1, 2}}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 4}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, } update := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 1}, - DevEUI: []byte{0, 1}, - DevNonces: [][]byte{[]byte{1, 2}, []byte{2, 5}}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 4}, + Password: []byte{3, 4}, + Salt: []byte{5, 6}, } // Operate err := db.upsert(entry) FatalUnless(t, err) err = db.upsert(update) - got, errRead := db.read(entry.AppEUI, entry.DevEUI) + got, errRead := db.read(entry.AppEUI) FatalUnless(t, errRead) // Check diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 362181b44..4614de399 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -90,13 +90,25 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c ctx := b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI) // Check if devNonce already referenced - entry, err := b.AppStorage.read(req.AppEUI, req.DevEUI) + appEntry, err := b.AppStorage.read(req.AppEUI) if err != nil { ctx.WithError(err).Debug("Unable to lookup activation") return new(core.JoinBrokerRes), err } + nonces, err := b.NetworkController.readNonces(req.AppEUI, req.DevEUI) + if err != nil { + if err.(errors.Failure).Nature != errors.NotFound { + ctx.WithError(err).Debug("Unable to retrieve associated devNonces") + return new(core.JoinBrokerRes), err + } + nonces = noncesEntry{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + } + var found bool - for _, n := range entry.DevNonces { + for _, n := range nonces.DevNonces { if n[0] == req.DevNonce[0] && n[1] == req.DevNonce[1] { found = true break @@ -108,7 +120,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c } // Forward the registration to the handler - handler, closer, err := entry.Dialer.Dial() + handler, closer, err := appEntry.Dialer.Dial() if err != nil { ctx.WithError(err).Debug("Unable to dial handler") return new(core.JoinBrokerRes), err @@ -137,11 +149,11 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c } // Update the DevNonce - entry.DevNonces = append(entry.DevNonces, req.DevNonce) - if uint(len(entry.DevNonces)) > b.MaxDevNonces { - entry.DevNonces = entry.DevNonces[1:] + nonces.DevNonces = append(nonces.DevNonces, req.DevNonce) + if uint(len(nonces.DevNonces)) > b.MaxDevNonces { + nonces.DevNonces = nonces.DevNonces[1:] } - err = b.AppStorage.upsert(entry) + err = b.NetworkController.upsertNonces(nonces) if err != nil { ctx.WithError(err).Debug("Unable to update activation entry") return new(core.JoinBrokerRes), err @@ -150,7 +162,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c var nwkSKey [16]byte copy(nwkSKey[:], res.NwkSKey) err = b.NetworkController.upsert(devEntry{ - Dialer: entry.Dialer, + Dialer: appEntry.Dialer, DevAddr: res.DevAddr, AppEUI: req.AppEUI, DevEUI: req.DevEUI, diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index f85f8d1df..b78826942 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -834,7 +834,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -842,8 +845,74 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, + DevNonce: []byte{14, 14}, + Metadata: new(core.Metadata), + } + + // Expect + var wantErr *string + var wantJoinReq = &core.JoinHandlerReq{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonce: req.DevNonce, + Metadata: req.Metadata, + } + var wantRes = &core.JoinBrokerRes{ + Payload: hl.OutHandleJoin.Res.Payload, + Metadata: hl.OutHandleJoin.Res.Metadata, + } + var wantActivation = noncesEntry{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + DevNonces: [][]byte{req.DevNonce}, + } + var wantDialer = true + + // Operate + res, err := br.HandleJoin(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") + Check(t, wantRes, res, "Broker Join Responses") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + + { + Desc(t, "Valid Join Request - very first time, no devNonces | Valid Join Accept") + + // Build + nc := NewMockNetworkController() + nc.Failures["readNonces"] = errors.New(errors.NotFound, "Mock Error") + as := NewMockAppStorage() + hl := mocks.NewHandlerClient() + hl.OutHandleJoin.Res = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{ + Payload: []byte{14, 42}, + }, + DevAddr: []byte{1, 1, 1, 1}, + NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + Metadata: new(core.Metadata), + } + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + as.OutRead.Entry = appEntry{ + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.JoinBrokerReq{ + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -860,8 +929,7 @@ func TestHandleJoin(t *testing.T) { Payload: hl.OutHandleJoin.Res.Payload, Metadata: hl.OutHandleJoin.Res.Metadata, } - var wantActivation = appEntry{ - Dialer: dl, + var wantActivation = noncesEntry{ AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonces: [][]byte{req.DevNonce}, @@ -875,7 +943,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -902,7 +970,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -911,7 +982,7 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ AppEUI: nil, - DevEUI: as.OutRead.Entry.DevEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -920,7 +991,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer bool // Operate @@ -930,7 +1001,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -957,7 +1028,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -975,7 +1049,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer bool // Operate @@ -985,7 +1059,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1012,7 +1086,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1020,8 +1097,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14, 15, 16}, Metadata: new(core.Metadata), } @@ -1030,7 +1107,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer bool // Operate @@ -1040,7 +1117,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1067,7 +1144,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1075,8 +1155,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: nil, } @@ -1085,7 +1165,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer bool // Operate @@ -1095,19 +1175,19 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } // -------------------- { - Desc(t, "Valid Join Request | ReadActivation failed") + Desc(t, "Valid Join Request | ReadNonces failed") // Build nc := NewMockNetworkController() as := NewMockAppStorage() - as.Failures["read"] = errors.New(errors.Operational, "Mock Error") + nc.Failures["readNonces"] = errors.New(errors.Operational, "Mock Error") hl := mocks.NewHandlerClient() hl.OutHandleJoin.Res = &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ @@ -1134,7 +1214,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrOperational var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer bool // Operate @@ -1144,7 +1224,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1171,7 +1251,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{[]byte{14, 14}}, @@ -1179,8 +1262,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1189,7 +1272,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrStructural var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer bool // Operate @@ -1199,7 +1282,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1227,7 +1310,10 @@ func TestHandleJoin(t *testing.T) { dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1235,8 +1321,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1245,7 +1331,7 @@ func TestHandleJoin(t *testing.T) { var wantErr = ErrOperational var wantJoinReq *core.JoinHandlerReq var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer = true // Operate @@ -1255,7 +1341,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } // -------------------- @@ -1282,7 +1368,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1290,8 +1379,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1305,7 +1394,7 @@ func TestHandleJoin(t *testing.T) { Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer = true // Operate @@ -1315,7 +1404,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1342,7 +1431,10 @@ func TestHandleJoin(t *testing.T) { dl.OutDial.Closer = NewMockCloser() as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1350,8 +1442,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1365,7 +1457,7 @@ func TestHandleJoin(t *testing.T) { Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) - var wantActivation appEntry + var wantActivation noncesEntry var wantDialer = true // Operate @@ -1375,7 +1467,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1401,9 +1493,12 @@ func TestHandleJoin(t *testing.T) { nc := NewMockNetworkController() as := NewMockAppStorage() - as.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") + nc.Failures["upsertNonces"] = errors.New(errors.Operational, "Mock Error") as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1411,8 +1506,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1426,8 +1521,7 @@ func TestHandleJoin(t *testing.T) { Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) - var wantActivation = appEntry{ - Dialer: dl, + var wantActivation = noncesEntry{ AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonces: [][]byte{req.DevNonce}, @@ -1441,7 +1535,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } @@ -1469,7 +1563,10 @@ func TestHandleJoin(t *testing.T) { as := NewMockAppStorage() nc.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") as.OutRead.Entry = appEntry{ - Dialer: dl, + Dialer: dl, + AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + } + nc.OutReadNonces.Entry = noncesEntry{ AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonces: [][]byte{}, @@ -1477,8 +1574,8 @@ func TestHandleJoin(t *testing.T) { br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: as.OutRead.Entry.DevEUI, + AppEUI: nc.OutReadNonces.Entry.AppEUI, + DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, Metadata: new(core.Metadata), } @@ -1492,8 +1589,7 @@ func TestHandleJoin(t *testing.T) { Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) - var wantActivation = appEntry{ - Dialer: dl, + var wantActivation = noncesEntry{ AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonces: [][]byte{req.DevNonce}, @@ -1507,7 +1603,7 @@ func TestHandleJoin(t *testing.T) { CheckErrors(t, wantErr, err) Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, as.InUpsert.Entry, "Activations") + Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") Check(t, wantDialer, dl.InDial.Called, "Dialer calls") } } diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 2549fb3fb..84aabaee8 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -4,9 +4,9 @@ package broker import ( + "bytes" "encoding" "encoding/binary" - "fmt" "math" "reflect" "sync" @@ -16,11 +16,11 @@ import ( "github.com/TheThingsNetwork/ttn/utils/readwriter" ) -var _ = fmt.Print - // NetworkController gives a facade for manipulating the broker databases and devices type NetworkController interface { read(devAddr []byte) ([]devEntry, error) + readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) + upsertNonces(entry noncesEntry) error upsert(entry devEntry) error wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) done() error @@ -28,13 +28,19 @@ type NetworkController interface { type devEntry struct { AppEUI []byte - DevAddr []byte DevEUI []byte + DevAddr []byte Dialer Dialer FCntUp uint32 NwkSKey [16]byte } +type noncesEntry struct { + AppEUI []byte + DevEUI []byte + DevNonces [][]byte +} + type controller struct { sync.RWMutex db dbutil.Interface @@ -108,6 +114,20 @@ func (s *controller) upsert(update devEntry) error { return s.db.Update(update.DevAddr, newEntries, dbDevices) } +// readNonces implements the broker.NetworkController interface +func (s *controller) readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) { + itf, err := s.db.Read(nil, &noncesEntry{}, appEUI, devEUI) + if err != nil { + return noncesEntry{}, err + } + return itf.([]noncesEntry)[0], nil // Storage guarantee to have one entry +} + +// upsertNonces implements the broker.NetworkController interface +func (s *controller) upsertNonces(entry noncesEntry) error { + return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) +} + // done implements the broker.NetworkController interface func (s *controller) done() error { return s.db.Close() @@ -140,11 +160,43 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { e.DevAddr = make([]byte, len(data)) copy(e.DevAddr, data) }) - rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) + rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) + return rw.Err() +} + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e noncesEntry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.AppEUI) + rw.Write(e.DevEUI) + buf := new(bytes.Buffer) + for _, n := range e.DevNonces { + _, _ = buf.Write(n) + } + rw.Write(buf.Bytes()) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *noncesEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) rw.Read(func(data []byte) { - e.Dialer = NewDialer(data) + e.AppEUI = make([]byte, len(data)) + copy(e.AppEUI, data) + }) + rw.Read(func(data []byte) { + e.DevEUI = make([]byte, len(data)) + copy(e.DevEUI, data) + }) + rw.Read(func(data []byte) { + n := len(data) / 2 // DevNonce -> 2-bytes + for i := 0; i < int(n); i++ { + devNonce := make([]byte, 2, 2) + copy(devNonce, data[2*i:2*i+2]) + e.DevNonces = append(e.DevNonces, devNonce) + } }) return rw.Err() } diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index 0373538fb..bfd1d9b14 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -339,3 +339,84 @@ func TestNetworkControllerDevice(t *testing.T) { _ = db.done() } } + +func TestNonces(t *testing.T) { + NetworkControllerDB := path.Join(os.TempDir(), NetworkControllerDB) + defer func() { + os.Remove(NetworkControllerDB) + }() + + // ------------------- + + { + Desc(t, "Store then lookup a noncesEntry") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + entry := noncesEntry{ + AppEUI: []byte{1, 2, 3}, + DevEUI: []byte{4, 5, 6}, + DevNonces: [][]byte{[]byte{14, 42}}, + } + + // Operate + err := db.upsertNonces(entry) + FatalUnless(t, err) + got, err := db.readNonces(entry.AppEUI, entry.DevEUI) + FatalUnless(t, err) + + // Check + Check(t, entry, got, "Nonces Entries") + _ = db.done() + } + + // ------------------- + + { + Desc(t, "Update an existing nonce") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + entry := noncesEntry{ + AppEUI: []byte{26, 2, 3}, + DevEUI: []byte{4, 26, 26}, + DevNonces: [][]byte{[]byte{14, 42}}, + } + update := noncesEntry{ + AppEUI: entry.AppEUI, + DevEUI: entry.DevEUI, + DevNonces: [][]byte{[]byte{58, 27}, []byte{12, 11}}, + } + + // Operate + err := db.upsertNonces(entry) + FatalUnless(t, err) + err = db.upsertNonces(update) + FatalUnless(t, err) + got, err := db.readNonces(entry.AppEUI, entry.DevEUI) + FatalUnless(t, err) + + // Check + Check(t, update, got, "Nonces Entries") + _ = db.done() + } + + // ------------------- + + { + + Desc(t, "Lookup an non-existing nonces entry") + + // Build + db, _ := NewNetworkController(NetworkControllerDB) + appEUI := []byte{4, 5, 3, 4} + devEUI := []byte{1, 2, 2, 2} + + // Operate + _, err := db.readNonces(appEUI, devEUI) + + // Check + CheckErrors(t, ErrNotFound, err) + _ = db.done() + } +} diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go index bd0229ad5..46f1a36ed 100644 --- a/core/components/broker/mocks_test.go +++ b/core/components/broker/mocks_test.go @@ -72,7 +72,6 @@ type MockAppStorage struct { Failures map[string]error InRead struct { AppEUI []byte - DevEUI []byte } OutRead struct { Entry appEntry @@ -93,9 +92,8 @@ func NewMockAppStorage() *MockAppStorage { } // read implements the AppStorage interface -func (m *MockAppStorage) read(appEUI []byte, devEUI []byte) (appEntry, error) { +func (m *MockAppStorage) read(appEUI []byte) (appEntry, error) { m.InRead.AppEUI = appEUI - m.InRead.DevEUI = devEUI return m.OutRead.Entry, m.Failures["read"] } @@ -123,6 +121,16 @@ type MockNetworkController struct { InUpsert struct { Entry devEntry } + InReadNonces struct { + AppEUI []byte + DevEUI []byte + } + OutReadNonces struct { + Entry noncesEntry + } + InUpsertNonces struct { + Entry noncesEntry + } InWholeCounter struct { DevCnt uint32 EntryCnt uint32 @@ -154,6 +162,19 @@ func (m *MockNetworkController) upsert(entry devEntry) error { return m.Failures["upsert"] } +// readNonces implements the NetworkController interface +func (m *MockNetworkController) readNonces(appEUI, devEUI []byte) (noncesEntry, error) { + m.InReadNonces.AppEUI = appEUI + m.InReadNonces.DevEUI = devEUI + return m.OutReadNonces.Entry, m.Failures["readNonces"] +} + +// upsertNonces implements the NetworkController interface +func (m *MockNetworkController) upsertNonces(entry noncesEntry) error { + m.InUpsertNonces.Entry = entry + return m.Failures["upsertNonces"] +} + // wholeCnt implements the NetworkController interface func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, error) { m.InWholeCounter.DevCnt = devCnt From 298e8273f41fe7e3aa37996820671ee7717cad8c Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 14:48:13 +0100 Subject: [PATCH 1148/2266] [refactor/storages] Make golint smiles and be happy again --- core/components/router/router_test.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 1be71804d..2400a8afd 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -512,7 +512,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -737,7 +737,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrOperational var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -818,7 +818,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -890,7 +890,7 @@ func TestHandleData(t *testing.T) { var wantErr = ErrStructural var wantRes = new(core.DataRouterRes) var wantBrReq *core.DataBrokerReq - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -968,7 +968,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -1045,7 +1045,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -1127,7 +1127,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 // Operate res, err := r.HandleData(context.Background(), req) @@ -1229,7 +1229,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 var wantUpdateGtw = req.GatewayID // Operate @@ -1330,7 +1330,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 var wantUpdateGtw []byte // Operate @@ -1432,7 +1432,7 @@ func TestHandleData(t *testing.T) { Frequency: req.Metadata.Frequency, }, } - var wantStore uint16 = 0 + var wantStore uint16 var wantUpdateGtw = req.GatewayID // Operate From 64f65b8ae7abaaf818f4d602e9eeee358c5d4626 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 15:01:31 +0100 Subject: [PATCH 1149/2266] [refactor/storages] Update commands --- cmd/broker.go | 55 +++++++++++++++++++++++++++++++++++++------------- cmd/handler.go | 2 +- cmd/router.go | 34 +++++++++++++++++++++++++++---- 3 files changed, 72 insertions(+), 19 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index f1745bab3..71103ec34 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -36,9 +36,10 @@ and personalized devices (with their network session keys) with the router. stats.Enabled = false } ctx.WithFields(log.Fields{ - "database": viper.GetString("broker.database"), - "status-server": statusServer, - "main-server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), + "devices database": viper.GetString("broker.devices_database"), + "applications database": viper.GetString("broker.applications_database"), + "status-server": statusServer, + "main-server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { @@ -54,30 +55,53 @@ and personalized devices (with their network session keys) with the router. statusAdapter.Bind(http.StatusPage{}) // Storage - var db broker.NetworkController + var dbDev broker.NetworkController + devDBString := viper.GetString("broker.devices_database") + switch { + case strings.HasPrefix(devDBString, "boltdb:"): + + dbPath, err := filepath.Abs(devDBString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid devices database path") + } - dbString := viper.GetString("broker.database") + dbDev, err = broker.NewNetworkController(dbPath) + if err != nil { + ctx.WithError(err).Fatal("Could not create local storage") + } + + ctx.WithField("devices database", dbPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid devices database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") + } + + var dbApp broker.AppStorage + appDBString := viper.GetString("broker.applications_database") switch { - case strings.HasPrefix(dbString, "boltdb:"): + case strings.HasPrefix(appDBString, "boltdb:"): - dbPath, err := filepath.Abs(dbString[7:]) + dbPath, err := filepath.Abs(appDBString[7:]) if err != nil { - ctx.WithError(err).Fatal("Invalid database path") + ctx.WithError(err).Fatal("Invalid applications database path") } - db, err = broker.NewNetworkController(dbPath) + dbApp, err = broker.NewAppStorage(dbPath) if err != nil { ctx.WithError(err).Fatal("Could not create local storage") } - ctx.WithField("database", dbPath).Info("Using local storage") + ctx.WithField("applications database", dbPath).Info("Using local storage") default: - ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") + ctx.WithError(fmt.Errorf("Invalid applications database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } // Broker broker := broker.New( - broker.Components{Ctx: ctx, NetworkController: db}, + broker.Components{ + Ctx: ctx, + NetworkController: dbDev, + AppStorage: dbApp, + }, broker.Options{ NetAddrUp: fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-address"), viper.GetInt("broker.uplink-port")), NetAddrDown: fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-address"), viper.GetInt("broker.downlink-port")), @@ -94,8 +118,11 @@ and personalized devices (with their network session keys) with the router. func init() { RootCmd.AddCommand(brokerCmd) - brokerCmd.Flags().String("database", "boltdb:/tmp/ttn_broker.db", "Database connection") - viper.BindPFlag("broker.database", brokerCmd.Flags().Lookup("database")) + brokerCmd.Flags().String("applications_database", "boltdb:/tmp/ttn_apps_broker.db", "Applications Database connection") + viper.BindPFlag("broker.applications_database", brokerCmd.Flags().Lookup("applications_database")) + + brokerCmd.Flags().String("devices_database", "boltdb:/tmp/ttn_devs_broker.db", "Devices Database connection") + viper.BindPFlag("broker.devices_database", brokerCmd.Flags().Lookup("devices_database")) brokerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") diff --git a/cmd/handler.go b/cmd/handler.go index ae4a5f3cc..d508e6ecc 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -91,7 +91,7 @@ The default handler is the bridge between The Things Network and applications. ctx.WithError(err).Fatal("Invalid packets database path") } - packetsDB, err = handler.NewPktStorage(pktDBPath) + packetsDB, err = handler.NewPktStorage(pktDBPath, 1) if err != nil { ctx.WithError(err).Fatal("Could not create local packets storage") } diff --git a/cmd/router.go b/cmd/router.go index 704d3ea3f..548ae4c46 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -42,6 +42,7 @@ the gateway's duty cycle is (almost) full.`, ctx.WithFields(log.Fields{ "db-brokers": viper.GetString("router.db_brokers"), "db-gateways": viper.GetString("router.db_gateways"), + "db-duty": viper.GetString("router.db_duty"), "status-server": statusServer, "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-address"), viper.GetInt("router.uplink-port")), "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), @@ -61,7 +62,7 @@ the gateway's duty cycle is (almost) full.`, statusAdapter.Bind(http.StatusPage{}) // In-memory packet storage - var db router.Storage + var db router.BrkStorage dbString := viper.GetString("router.db_brokers") switch { case strings.HasPrefix(dbString, "boltdb:"): @@ -71,7 +72,7 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(err).Fatal("Invalid database path") } - db, err = router.NewStorage(dbPath, time.Hour*8) + db, err = router.NewBrkStorage(dbPath, time.Hour*8) if err != nil { ctx.WithError(err).Fatal("Could not create a local storage") } @@ -83,7 +84,7 @@ the gateway's duty cycle is (almost) full.`, // Duty Manager var dm dutycycle.DutyManager - dmString := viper.GetString("router.db_gateways") + dmString := viper.GetString("router.db_duty") switch { case strings.HasPrefix(dmString, "boltdb:"): @@ -102,6 +103,27 @@ the gateway's duty cycle is (almost) full.`, ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } + // Gateways + var dg router.GtwStorage + dgString := viper.GetString("router.db_gateways") + switch { + case strings.HasPrefix(dmString, "boltdb:"): + + dgPath, err := filepath.Abs(dgString[7:]) + if err != nil { + ctx.WithError(err).Fatal("Invalid database path") + } + + dg, err = router.NewGtwStorage(dgPath) + if err != nil { + ctx.WithError(err).Fatal("Could not create a local storage") + } + + ctx.WithField("database", dgPath).Info("Using local storage") + default: + ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") + } + // Broker clients var brokers []core.BrokerClient brokersStr := strings.Split(viper.GetString("router.brokers"), ",") @@ -122,7 +144,8 @@ the gateway's duty cycle is (almost) full.`, Ctx: ctx, DutyManager: dm, Brokers: brokers, - Storage: db, + BrkStorage: db, + GtwStorage: dg, }, router.Options{ NetAddr: fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), @@ -161,6 +184,9 @@ func init() { routerCmd.Flags().String("db_gateways", "boltdb:/tmp/ttn_router_gateways.db", "Database connection of managed gateways") viper.BindPFlag("router.db_gateways", routerCmd.Flags().Lookup("db_gateways")) + routerCmd.Flags().String("db_duty", "boltdb:/tmp/ttn_router_duty.db", "Database connection of managed dutycycles") + viper.BindPFlag("router.db_duty", routerCmd.Flags().Lookup("db_duty")) + routerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") viper.BindPFlag("router.status-address", routerCmd.Flags().Lookup("status-address")) From 0ca0aed76f257a89f6f4d145bac770fbddf7cf58 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 15:30:40 +0100 Subject: [PATCH 1150/2266] [feature/manager] Prepend handler protos with Handler --- core/components/handler/handler.go | 4 +- core/handler_manager.pb.go | 335 +++++++++++++++-------------- core/protos/handler_manager.proto | 18 +- 3 files changed, 183 insertions(+), 174 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index e738f2db0..ced3c093f 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -300,12 +300,12 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq } // List implements the core.HandlerManagerServer interface -func (h component) List(context.Context, *core.ListDevicesReq) (*core.ListDevicesRes, error) { +func (h component) List(context.Context, *core.HandlerListDevicesReq) (*core.HandlerListDevicesRes, error) { return nil, errors.New(errors.Structural, "Not implemented") } // Upsert implements the core.HandlerManagerServer interface -func (h component) Upsert(context.Context, *core.UpsertDeviceReq) (*core.UpsertDeviceRes, error) { +func (h component) Upsert(context.Context, *core.HandlerUpsertDeviceReq) (*core.HandlerUpsertDeviceRes, error) { return nil, errors.New(errors.Structural, "Not implemented") } diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index dbd2f98fe..eb0adc606 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -20,119 +20,119 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -type Device struct { +type HandlerDevice struct { DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` // Types that are valid to be assigned to Data: - // *Device_Personalized - // *Device_Activated - Data isDevice_Data `protobuf_oneof:"Data"` + // *HandlerDevice_Personalized + // *HandlerDevice_Activated + Data isHandlerDevice_Data `protobuf_oneof:"Data"` } -func (m *Device) Reset() { *m = Device{} } -func (m *Device) String() string { return proto.CompactTextString(m) } -func (*Device) ProtoMessage() {} -func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{0} } +func (m *HandlerDevice) Reset() { *m = HandlerDevice{} } +func (m *HandlerDevice) String() string { return proto.CompactTextString(m) } +func (*HandlerDevice) ProtoMessage() {} +func (*HandlerDevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{0} } -type isDevice_Data interface { - isDevice_Data() +type isHandlerDevice_Data interface { + isHandlerDevice_Data() MarshalTo([]byte) (int, error) Size() int } -type Device_Personalized struct { - Personalized *Device_PersonalizedData `protobuf:"bytes,2,opt,name=Personalized,json=personalized,oneof"` +type HandlerDevice_Personalized struct { + Personalized *HandlerDevice_PersonalizedData `protobuf:"bytes,2,opt,name=Personalized,json=personalized,oneof"` } -type Device_Activated struct { - Activated *Device_ActivatedData `protobuf:"bytes,3,opt,name=Activated,json=activated,oneof"` +type HandlerDevice_Activated struct { + Activated *HandlerDevice_ActivatedData `protobuf:"bytes,3,opt,name=Activated,json=activated,oneof"` } -func (*Device_Personalized) isDevice_Data() {} -func (*Device_Activated) isDevice_Data() {} +func (*HandlerDevice_Personalized) isHandlerDevice_Data() {} +func (*HandlerDevice_Activated) isHandlerDevice_Data() {} -func (m *Device) GetData() isDevice_Data { +func (m *HandlerDevice) GetData() isHandlerDevice_Data { if m != nil { return m.Data } return nil } -func (m *Device) GetPersonalized() *Device_PersonalizedData { - if x, ok := m.GetData().(*Device_Personalized); ok { +func (m *HandlerDevice) GetPersonalized() *HandlerDevice_PersonalizedData { + if x, ok := m.GetData().(*HandlerDevice_Personalized); ok { return x.Personalized } return nil } -func (m *Device) GetActivated() *Device_ActivatedData { - if x, ok := m.GetData().(*Device_Activated); ok { +func (m *HandlerDevice) GetActivated() *HandlerDevice_ActivatedData { + if x, ok := m.GetData().(*HandlerDevice_Activated); ok { return x.Activated } return nil } // XXX_OneofFuncs is for the internal use of the proto package. -func (*Device) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _Device_OneofMarshaler, _Device_OneofUnmarshaler, _Device_OneofSizer, []interface{}{ - (*Device_Personalized)(nil), - (*Device_Activated)(nil), +func (*HandlerDevice) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _HandlerDevice_OneofMarshaler, _HandlerDevice_OneofUnmarshaler, _HandlerDevice_OneofSizer, []interface{}{ + (*HandlerDevice_Personalized)(nil), + (*HandlerDevice_Activated)(nil), } } -func _Device_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*Device) +func _HandlerDevice_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*HandlerDevice) // Data switch x := m.Data.(type) { - case *Device_Personalized: + case *HandlerDevice_Personalized: _ = b.EncodeVarint(2<<3 | proto.WireBytes) if err := b.EncodeMessage(x.Personalized); err != nil { return err } - case *Device_Activated: + case *HandlerDevice_Activated: _ = b.EncodeVarint(3<<3 | proto.WireBytes) if err := b.EncodeMessage(x.Activated); err != nil { return err } case nil: default: - return fmt.Errorf("Device.Data has unexpected type %T", x) + return fmt.Errorf("HandlerDevice.Data has unexpected type %T", x) } return nil } -func _Device_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*Device) +func _HandlerDevice_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*HandlerDevice) switch tag { case 2: // Data.Personalized if wire != proto.WireBytes { return true, proto.ErrInternalBadWireType } - msg := new(Device_PersonalizedData) + msg := new(HandlerDevice_PersonalizedData) err := b.DecodeMessage(msg) - m.Data = &Device_Personalized{msg} + m.Data = &HandlerDevice_Personalized{msg} return true, err case 3: // Data.Activated if wire != proto.WireBytes { return true, proto.ErrInternalBadWireType } - msg := new(Device_ActivatedData) + msg := new(HandlerDevice_ActivatedData) err := b.DecodeMessage(msg) - m.Data = &Device_Activated{msg} + m.Data = &HandlerDevice_Activated{msg} return true, err default: return false, nil } } -func _Device_OneofSizer(msg proto.Message) (n int) { - m := msg.(*Device) +func _HandlerDevice_OneofSizer(msg proto.Message) (n int) { + m := msg.(*HandlerDevice) // Data switch x := m.Data.(type) { - case *Device_Personalized: + case *HandlerDevice_Personalized: s := proto.Size(x.Personalized) n += proto.SizeVarint(2<<3 | proto.WireBytes) n += proto.SizeVarint(uint64(s)) n += s - case *Device_Activated: + case *HandlerDevice_Activated: s := proto.Size(x.Activated) n += proto.SizeVarint(3<<3 | proto.WireBytes) n += proto.SizeVarint(uint64(s)) @@ -144,87 +144,95 @@ func _Device_OneofSizer(msg proto.Message) (n int) { return n } -type Device_PersonalizedData struct { +type HandlerDevice_PersonalizedData struct { NwkSKey []byte `protobuf:"bytes,1,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` AppSKey []byte `protobuf:"bytes,2,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` } -func (m *Device_PersonalizedData) Reset() { *m = Device_PersonalizedData{} } -func (m *Device_PersonalizedData) String() string { return proto.CompactTextString(m) } -func (*Device_PersonalizedData) ProtoMessage() {} -func (*Device_PersonalizedData) Descriptor() ([]byte, []int) { +func (m *HandlerDevice_PersonalizedData) Reset() { *m = HandlerDevice_PersonalizedData{} } +func (m *HandlerDevice_PersonalizedData) String() string { return proto.CompactTextString(m) } +func (*HandlerDevice_PersonalizedData) ProtoMessage() {} +func (*HandlerDevice_PersonalizedData) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{0, 0} } -type Device_ActivatedData struct { +type HandlerDevice_ActivatedData struct { AppKey []byte `protobuf:"bytes,1,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` } -func (m *Device_ActivatedData) Reset() { *m = Device_ActivatedData{} } -func (m *Device_ActivatedData) String() string { return proto.CompactTextString(m) } -func (*Device_ActivatedData) ProtoMessage() {} -func (*Device_ActivatedData) Descriptor() ([]byte, []int) { +func (m *HandlerDevice_ActivatedData) Reset() { *m = HandlerDevice_ActivatedData{} } +func (m *HandlerDevice_ActivatedData) String() string { return proto.CompactTextString(m) } +func (*HandlerDevice_ActivatedData) ProtoMessage() {} +func (*HandlerDevice_ActivatedData) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{0, 1} } -type ListDevicesReq struct { +type HandlerListDevicesReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` } -func (m *ListDevicesReq) Reset() { *m = ListDevicesReq{} } -func (m *ListDevicesReq) String() string { return proto.CompactTextString(m) } -func (*ListDevicesReq) ProtoMessage() {} -func (*ListDevicesReq) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{1} } +func (m *HandlerListDevicesReq) Reset() { *m = HandlerListDevicesReq{} } +func (m *HandlerListDevicesReq) String() string { return proto.CompactTextString(m) } +func (*HandlerListDevicesReq) ProtoMessage() {} +func (*HandlerListDevicesReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{1} +} -type ListDevicesRes struct { - Devices []*Device `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` +type HandlerListDevicesRes struct { + Devices []*HandlerDevice `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` } -func (m *ListDevicesRes) Reset() { *m = ListDevicesRes{} } -func (m *ListDevicesRes) String() string { return proto.CompactTextString(m) } -func (*ListDevicesRes) ProtoMessage() {} -func (*ListDevicesRes) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{2} } +func (m *HandlerListDevicesRes) Reset() { *m = HandlerListDevicesRes{} } +func (m *HandlerListDevicesRes) String() string { return proto.CompactTextString(m) } +func (*HandlerListDevicesRes) ProtoMessage() {} +func (*HandlerListDevicesRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{2} +} -func (m *ListDevicesRes) GetDevices() []*Device { +func (m *HandlerListDevicesRes) GetDevices() []*HandlerDevice { if m != nil { return m.Devices } return nil } -type UpsertDeviceReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Device *Device `protobuf:"bytes,2,opt,name=Device,json=device" json:"Device,omitempty"` +type HandlerUpsertDeviceReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Device *HandlerDevice `protobuf:"bytes,2,opt,name=Device,json=device" json:"Device,omitempty"` } -func (m *UpsertDeviceReq) Reset() { *m = UpsertDeviceReq{} } -func (m *UpsertDeviceReq) String() string { return proto.CompactTextString(m) } -func (*UpsertDeviceReq) ProtoMessage() {} -func (*UpsertDeviceReq) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{3} } +func (m *HandlerUpsertDeviceReq) Reset() { *m = HandlerUpsertDeviceReq{} } +func (m *HandlerUpsertDeviceReq) String() string { return proto.CompactTextString(m) } +func (*HandlerUpsertDeviceReq) ProtoMessage() {} +func (*HandlerUpsertDeviceReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{3} +} -func (m *UpsertDeviceReq) GetDevice() *Device { +func (m *HandlerUpsertDeviceReq) GetDevice() *HandlerDevice { if m != nil { return m.Device } return nil } -type UpsertDeviceRes struct { +type HandlerUpsertDeviceRes struct { } -func (m *UpsertDeviceRes) Reset() { *m = UpsertDeviceRes{} } -func (m *UpsertDeviceRes) String() string { return proto.CompactTextString(m) } -func (*UpsertDeviceRes) ProtoMessage() {} -func (*UpsertDeviceRes) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{4} } +func (m *HandlerUpsertDeviceRes) Reset() { *m = HandlerUpsertDeviceRes{} } +func (m *HandlerUpsertDeviceRes) String() string { return proto.CompactTextString(m) } +func (*HandlerUpsertDeviceRes) ProtoMessage() {} +func (*HandlerUpsertDeviceRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{4} +} func init() { - proto.RegisterType((*Device)(nil), "core.Device") - proto.RegisterType((*Device_PersonalizedData)(nil), "core.Device.PersonalizedData") - proto.RegisterType((*Device_ActivatedData)(nil), "core.Device.ActivatedData") - proto.RegisterType((*ListDevicesReq)(nil), "core.ListDevicesReq") - proto.RegisterType((*ListDevicesRes)(nil), "core.ListDevicesRes") - proto.RegisterType((*UpsertDeviceReq)(nil), "core.UpsertDeviceReq") - proto.RegisterType((*UpsertDeviceRes)(nil), "core.UpsertDeviceRes") + proto.RegisterType((*HandlerDevice)(nil), "core.HandlerDevice") + proto.RegisterType((*HandlerDevice_PersonalizedData)(nil), "core.HandlerDevice.PersonalizedData") + proto.RegisterType((*HandlerDevice_ActivatedData)(nil), "core.HandlerDevice.ActivatedData") + proto.RegisterType((*HandlerListDevicesReq)(nil), "core.HandlerListDevicesReq") + proto.RegisterType((*HandlerListDevicesRes)(nil), "core.HandlerListDevicesRes") + proto.RegisterType((*HandlerUpsertDeviceReq)(nil), "core.HandlerUpsertDeviceReq") + proto.RegisterType((*HandlerUpsertDeviceRes)(nil), "core.HandlerUpsertDeviceRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -234,8 +242,8 @@ var _ grpc.ClientConn // Client API for HandlerManager service type HandlerManagerClient interface { - List(ctx context.Context, in *ListDevicesReq, opts ...grpc.CallOption) (*ListDevicesRes, error) - Upsert(ctx context.Context, in *UpsertDeviceReq, opts ...grpc.CallOption) (*UpsertDeviceRes, error) + List(ctx context.Context, in *HandlerListDevicesReq, opts ...grpc.CallOption) (*HandlerListDevicesRes, error) + Upsert(ctx context.Context, in *HandlerUpsertDeviceReq, opts ...grpc.CallOption) (*HandlerUpsertDeviceRes, error) } type handlerManagerClient struct { @@ -246,8 +254,8 @@ func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { return &handlerManagerClient{cc} } -func (c *handlerManagerClient) List(ctx context.Context, in *ListDevicesReq, opts ...grpc.CallOption) (*ListDevicesRes, error) { - out := new(ListDevicesRes) +func (c *handlerManagerClient) List(ctx context.Context, in *HandlerListDevicesReq, opts ...grpc.CallOption) (*HandlerListDevicesRes, error) { + out := new(HandlerListDevicesRes) err := grpc.Invoke(ctx, "/core.HandlerManager/List", in, out, c.cc, opts...) if err != nil { return nil, err @@ -255,8 +263,8 @@ func (c *handlerManagerClient) List(ctx context.Context, in *ListDevicesReq, opt return out, nil } -func (c *handlerManagerClient) Upsert(ctx context.Context, in *UpsertDeviceReq, opts ...grpc.CallOption) (*UpsertDeviceRes, error) { - out := new(UpsertDeviceRes) +func (c *handlerManagerClient) Upsert(ctx context.Context, in *HandlerUpsertDeviceReq, opts ...grpc.CallOption) (*HandlerUpsertDeviceRes, error) { + out := new(HandlerUpsertDeviceRes) err := grpc.Invoke(ctx, "/core.HandlerManager/Upsert", in, out, c.cc, opts...) if err != nil { return nil, err @@ -267,8 +275,8 @@ func (c *handlerManagerClient) Upsert(ctx context.Context, in *UpsertDeviceReq, // Server API for HandlerManager service type HandlerManagerServer interface { - List(context.Context, *ListDevicesReq) (*ListDevicesRes, error) - Upsert(context.Context, *UpsertDeviceReq) (*UpsertDeviceRes, error) + List(context.Context, *HandlerListDevicesReq) (*HandlerListDevicesRes, error) + Upsert(context.Context, *HandlerUpsertDeviceReq) (*HandlerUpsertDeviceRes, error) } func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { @@ -276,7 +284,7 @@ func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { } func _HandlerManager_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(ListDevicesReq) + in := new(HandlerListDevicesReq) if err := dec(in); err != nil { return nil, err } @@ -288,7 +296,7 @@ func _HandlerManager_List_Handler(srv interface{}, ctx context.Context, dec func } func _HandlerManager_Upsert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(UpsertDeviceReq) + in := new(HandlerUpsertDeviceReq) if err := dec(in); err != nil { return nil, err } @@ -315,7 +323,7 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, } -func (m *Device) Marshal() (data []byte, err error) { +func (m *HandlerDevice) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -325,7 +333,7 @@ func (m *Device) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *Device) MarshalTo(data []byte) (int, error) { +func (m *HandlerDevice) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -348,7 +356,7 @@ func (m *Device) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Device_Personalized) MarshalTo(data []byte) (int, error) { +func (m *HandlerDevice_Personalized) MarshalTo(data []byte) (int, error) { i := 0 if m.Personalized != nil { data[i] = 0x12 @@ -362,7 +370,7 @@ func (m *Device_Personalized) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *Device_Activated) MarshalTo(data []byte) (int, error) { +func (m *HandlerDevice_Activated) MarshalTo(data []byte) (int, error) { i := 0 if m.Activated != nil { data[i] = 0x1a @@ -376,7 +384,7 @@ func (m *Device_Activated) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *Device_PersonalizedData) Marshal() (data []byte, err error) { +func (m *HandlerDevice_PersonalizedData) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -386,7 +394,7 @@ func (m *Device_PersonalizedData) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *Device_PersonalizedData) MarshalTo(data []byte) (int, error) { +func (m *HandlerDevice_PersonalizedData) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -410,7 +418,7 @@ func (m *Device_PersonalizedData) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Device_ActivatedData) Marshal() (data []byte, err error) { +func (m *HandlerDevice_ActivatedData) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -420,7 +428,7 @@ func (m *Device_ActivatedData) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *Device_ActivatedData) MarshalTo(data []byte) (int, error) { +func (m *HandlerDevice_ActivatedData) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -436,7 +444,7 @@ func (m *Device_ActivatedData) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ListDevicesReq) Marshal() (data []byte, err error) { +func (m *HandlerListDevicesReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -446,7 +454,7 @@ func (m *ListDevicesReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *ListDevicesReq) MarshalTo(data []byte) (int, error) { +func (m *HandlerListDevicesReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -462,7 +470,7 @@ func (m *ListDevicesReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ListDevicesRes) Marshal() (data []byte, err error) { +func (m *HandlerListDevicesRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -472,7 +480,7 @@ func (m *ListDevicesRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *ListDevicesRes) MarshalTo(data []byte) (int, error) { +func (m *HandlerListDevicesRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -492,7 +500,7 @@ func (m *ListDevicesRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *UpsertDeviceReq) Marshal() (data []byte, err error) { +func (m *HandlerUpsertDeviceReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -502,7 +510,7 @@ func (m *UpsertDeviceReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *UpsertDeviceReq) MarshalTo(data []byte) (int, error) { +func (m *HandlerUpsertDeviceReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -528,7 +536,7 @@ func (m *UpsertDeviceReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *UpsertDeviceRes) Marshal() (data []byte, err error) { +func (m *HandlerUpsertDeviceRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -538,7 +546,7 @@ func (m *UpsertDeviceRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *UpsertDeviceRes) MarshalTo(data []byte) (int, error) { +func (m *HandlerUpsertDeviceRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -573,7 +581,7 @@ func encodeVarintHandlerManager(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } -func (m *Device) Size() (n int) { +func (m *HandlerDevice) Size() (n int) { var l int _ = l if m.DevEUI != nil { @@ -588,7 +596,7 @@ func (m *Device) Size() (n int) { return n } -func (m *Device_Personalized) Size() (n int) { +func (m *HandlerDevice_Personalized) Size() (n int) { var l int _ = l if m.Personalized != nil { @@ -597,7 +605,7 @@ func (m *Device_Personalized) Size() (n int) { } return n } -func (m *Device_Activated) Size() (n int) { +func (m *HandlerDevice_Activated) Size() (n int) { var l int _ = l if m.Activated != nil { @@ -606,7 +614,7 @@ func (m *Device_Activated) Size() (n int) { } return n } -func (m *Device_PersonalizedData) Size() (n int) { +func (m *HandlerDevice_PersonalizedData) Size() (n int) { var l int _ = l if m.NwkSKey != nil { @@ -624,7 +632,7 @@ func (m *Device_PersonalizedData) Size() (n int) { return n } -func (m *Device_ActivatedData) Size() (n int) { +func (m *HandlerDevice_ActivatedData) Size() (n int) { var l int _ = l if m.AppKey != nil { @@ -636,7 +644,7 @@ func (m *Device_ActivatedData) Size() (n int) { return n } -func (m *ListDevicesReq) Size() (n int) { +func (m *HandlerListDevicesReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -648,7 +656,7 @@ func (m *ListDevicesReq) Size() (n int) { return n } -func (m *ListDevicesRes) Size() (n int) { +func (m *HandlerListDevicesRes) Size() (n int) { var l int _ = l if len(m.Devices) > 0 { @@ -660,7 +668,7 @@ func (m *ListDevicesRes) Size() (n int) { return n } -func (m *UpsertDeviceReq) Size() (n int) { +func (m *HandlerUpsertDeviceReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -676,7 +684,7 @@ func (m *UpsertDeviceReq) Size() (n int) { return n } -func (m *UpsertDeviceRes) Size() (n int) { +func (m *HandlerUpsertDeviceRes) Size() (n int) { var l int _ = l return n @@ -695,7 +703,7 @@ func sovHandlerManager(x uint64) (n int) { func sozHandlerManager(x uint64) (n int) { return sovHandlerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Device) Unmarshal(data []byte) error { +func (m *HandlerDevice) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -718,10 +726,10 @@ func (m *Device) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Device: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerDevice: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerDevice: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -781,11 +789,11 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &Device_PersonalizedData{} + v := &HandlerDevice_PersonalizedData{} if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } - m.Data = &Device_Personalized{v} + m.Data = &HandlerDevice_Personalized{v} iNdEx = postIndex case 3: if wireType != 2 { @@ -813,11 +821,11 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &Device_ActivatedData{} + v := &HandlerDevice_ActivatedData{} if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } - m.Data = &Device_Activated{v} + m.Data = &HandlerDevice_Activated{v} iNdEx = postIndex default: iNdEx = preIndex @@ -840,7 +848,7 @@ func (m *Device) Unmarshal(data []byte) error { } return nil } -func (m *Device_PersonalizedData) Unmarshal(data []byte) error { +func (m *HandlerDevice_PersonalizedData) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -952,7 +960,7 @@ func (m *Device_PersonalizedData) Unmarshal(data []byte) error { } return nil } -func (m *Device_ActivatedData) Unmarshal(data []byte) error { +func (m *HandlerDevice_ActivatedData) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1033,7 +1041,7 @@ func (m *Device_ActivatedData) Unmarshal(data []byte) error { } return nil } -func (m *ListDevicesReq) Unmarshal(data []byte) error { +func (m *HandlerListDevicesReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1056,10 +1064,10 @@ func (m *ListDevicesReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ListDevicesReq: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerListDevicesReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ListDevicesReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerListDevicesReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1114,7 +1122,7 @@ func (m *ListDevicesReq) Unmarshal(data []byte) error { } return nil } -func (m *ListDevicesRes) Unmarshal(data []byte) error { +func (m *HandlerListDevicesRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1137,10 +1145,10 @@ func (m *ListDevicesRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ListDevicesRes: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerListDevicesRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ListDevicesRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerListDevicesRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1169,7 +1177,7 @@ func (m *ListDevicesRes) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Devices = append(m.Devices, &Device{}) + m.Devices = append(m.Devices, &HandlerDevice{}) if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { return err } @@ -1195,7 +1203,7 @@ func (m *ListDevicesRes) Unmarshal(data []byte) error { } return nil } -func (m *UpsertDeviceReq) Unmarshal(data []byte) error { +func (m *HandlerUpsertDeviceReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1218,10 +1226,10 @@ func (m *UpsertDeviceReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: UpsertDeviceReq: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerUpsertDeviceReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerUpsertDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1282,7 +1290,7 @@ func (m *UpsertDeviceReq) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } if m.Device == nil { - m.Device = &Device{} + m.Device = &HandlerDevice{} } if err := m.Device.Unmarshal(data[iNdEx:postIndex]); err != nil { return err @@ -1309,7 +1317,7 @@ func (m *UpsertDeviceReq) Unmarshal(data []byte) error { } return nil } -func (m *UpsertDeviceRes) Unmarshal(data []byte) error { +func (m *HandlerUpsertDeviceRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1332,10 +1340,10 @@ func (m *UpsertDeviceRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: UpsertDeviceRes: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerUpsertDeviceRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerUpsertDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1465,27 +1473,28 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 351 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0xcd, 0x4a, 0xc3, 0x40, - 0x10, 0xc7, 0xfb, 0xc5, 0x86, 0x8e, 0xb1, 0xd6, 0xc5, 0x4a, 0x09, 0x58, 0x24, 0x88, 0xf6, 0x94, - 0x43, 0x05, 0x11, 0x6f, 0xad, 0x55, 0x2a, 0x7e, 0x12, 0xe9, 0x59, 0xd6, 0x64, 0xd1, 0x60, 0x4d, - 0x62, 0x36, 0x54, 0x14, 0x1f, 0x44, 0x7c, 0x22, 0x8f, 0x3e, 0x82, 0xe8, 0x8b, 0xb8, 0xd9, 0x49, - 0x35, 0x09, 0xc5, 0xc3, 0x42, 0xfe, 0xf3, 0x9f, 0xf9, 0xed, 0x4c, 0x76, 0xa0, 0x75, 0xcb, 0x7c, - 0x77, 0xc2, 0xa3, 0xab, 0x7b, 0xe6, 0xb3, 0x1b, 0x1e, 0x59, 0x61, 0x14, 0xc4, 0x01, 0xad, 0x39, - 0x41, 0xc4, 0xcd, 0xb7, 0x0a, 0x90, 0x21, 0x9f, 0x7a, 0x0e, 0xa7, 0xab, 0xea, 0xeb, 0x60, 0x7c, - 0xd4, 0x2e, 0xaf, 0x97, 0xbb, 0xba, 0x4d, 0x5c, 0xa5, 0xe8, 0x3e, 0xe8, 0x17, 0x3c, 0x12, 0x81, - 0xcf, 0x26, 0xde, 0x33, 0x77, 0xdb, 0x15, 0xe9, 0x2e, 0xf4, 0xd6, 0xac, 0xa4, 0xde, 0xc2, 0x5a, - 0x2b, 0x9b, 0x30, 0x64, 0x31, 0x1b, 0x95, 0x6c, 0x3d, 0xcc, 0xc4, 0xe8, 0x1e, 0xd4, 0xfb, 0x4e, - 0xec, 0x4d, 0x59, 0x2c, 0x09, 0x55, 0x45, 0x30, 0x72, 0x84, 0x5f, 0x37, 0x2d, 0xaf, 0xb3, 0x59, - 0xc0, 0x38, 0x84, 0x66, 0x91, 0x4f, 0xdb, 0xa0, 0x9d, 0x3d, 0xde, 0x5d, 0x1e, 0xf3, 0xa7, 0xb4, - 0x5b, 0xcd, 0x47, 0x99, 0x38, 0xfd, 0x30, 0x54, 0x4e, 0x05, 0x1d, 0x86, 0xd2, 0xd8, 0x82, 0xc5, - 0xdc, 0x2d, 0xc9, 0xc4, 0x32, 0xf5, 0x8f, 0x41, 0x98, 0x52, 0x03, 0x02, 0xb5, 0xc4, 0x37, 0xbb, - 0xd0, 0x38, 0xf1, 0x44, 0x8c, 0x1d, 0x0a, 0x9b, 0x3f, 0xa4, 0x15, 0x99, 0x7f, 0xc4, 0x94, 0x32, - 0x77, 0x0b, 0x99, 0x82, 0x6e, 0x82, 0x96, 0x2a, 0x99, 0x5a, 0x95, 0xe3, 0xea, 0xd9, 0x71, 0x6d, - 0xcd, 0x45, 0xd3, 0x3c, 0x87, 0xa5, 0x71, 0x28, 0x78, 0x94, 0xd6, 0xfe, 0x73, 0x09, 0xdd, 0x98, - 0x3d, 0x55, 0xfa, 0x04, 0x79, 0x22, 0x41, 0xa2, 0xb9, 0x5c, 0x04, 0x8a, 0xde, 0x0b, 0x34, 0x46, - 0xb8, 0x03, 0xa7, 0xb8, 0x02, 0xb4, 0x07, 0xb5, 0xa4, 0x5f, 0xba, 0x82, 0x88, 0xfc, 0x94, 0xc6, - 0xbc, 0xa8, 0xa0, 0x3b, 0x40, 0x10, 0x4c, 0x5b, 0xe8, 0x17, 0xfa, 0x36, 0xe6, 0x86, 0xc5, 0xa0, - 0xf9, 0xfe, 0xd5, 0x29, 0x7f, 0xc8, 0xf3, 0x29, 0xcf, 0xeb, 0x77, 0xa7, 0x74, 0x4d, 0xd4, 0x06, - 0x6e, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xc7, 0x04, 0xfe, 0xa7, 0x9a, 0x02, 0x00, 0x00, + // 362 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0xdf, 0x4a, 0xc3, 0x30, + 0x14, 0xc6, 0xd7, 0x6d, 0xa4, 0xec, 0xb8, 0xc9, 0x88, 0x6c, 0x94, 0x2a, 0x43, 0x8b, 0xa0, 0x20, + 0x56, 0x98, 0x2f, 0xe0, 0xc6, 0x1c, 0xf3, 0x2f, 0x52, 0xd9, 0xa5, 0x48, 0x5c, 0x83, 0x16, 0x67, + 0x5b, 0x93, 0x32, 0xd1, 0x27, 0xd9, 0x5b, 0xf8, 0x1a, 0x5e, 0xfa, 0x08, 0xa2, 0x2f, 0x62, 0x9a, + 0x44, 0x5d, 0x47, 0xdd, 0x45, 0xa1, 0x5f, 0xbe, 0x2f, 0xbf, 0x93, 0x73, 0x12, 0x68, 0xdc, 0x91, + 0xd0, 0x1f, 0x53, 0x76, 0xfd, 0x40, 0x42, 0x72, 0x4b, 0x99, 0x1b, 0xb3, 0x28, 0x89, 0x70, 0x79, + 0x14, 0x31, 0xea, 0xbc, 0x16, 0xa1, 0x36, 0x50, 0x7e, 0x8f, 0x4e, 0x82, 0x11, 0xc5, 0x4d, 0x40, + 0xe2, 0xef, 0x70, 0x78, 0x64, 0x19, 0xeb, 0xc6, 0x76, 0xd5, 0x43, 0xbe, 0x54, 0xf8, 0x18, 0xaa, + 0x17, 0x94, 0xf1, 0x28, 0x24, 0xe3, 0xe0, 0x85, 0xfa, 0x56, 0x51, 0xb8, 0x4b, 0xed, 0x4d, 0x37, + 0xc5, 0xb8, 0x19, 0x84, 0x3b, 0x9b, 0xeb, 0x91, 0x84, 0x0c, 0x0a, 0x5e, 0x35, 0x9e, 0x59, 0xc3, + 0x1d, 0xa8, 0x74, 0x46, 0x49, 0x30, 0x21, 0x89, 0x00, 0x95, 0x24, 0x68, 0x23, 0x0f, 0xf4, 0x1b, + 0xd2, 0x94, 0x0a, 0xf9, 0x59, 0xb0, 0xfb, 0x50, 0x9f, 0x2f, 0x83, 0x2d, 0x30, 0xcf, 0x9f, 0xee, + 0x2f, 0x4f, 0xe8, 0xb3, 0x3e, 0xbb, 0x19, 0x2a, 0x99, 0x3a, 0x9d, 0x38, 0x96, 0x4e, 0x51, 0x39, + 0x44, 0x49, 0x7b, 0x0b, 0x6a, 0x99, 0x2a, 0x69, 0xff, 0x22, 0xfa, 0xc7, 0x40, 0x44, 0xaa, 0x2e, + 0x82, 0x72, 0xea, 0x3b, 0x7b, 0xd0, 0xd0, 0x87, 0x3c, 0x0d, 0x78, 0xa2, 0x0e, 0xca, 0x3d, 0xfa, + 0xa8, 0x37, 0xce, 0x0c, 0x8e, 0x48, 0xe5, 0xf4, 0xf3, 0x37, 0x70, 0xbc, 0x0b, 0xa6, 0x56, 0x62, + 0x47, 0x49, 0xcc, 0x60, 0x25, 0x67, 0x06, 0x9e, 0xe9, 0xab, 0x8c, 0x73, 0x05, 0x4d, 0xed, 0x0c, + 0x63, 0x4e, 0x99, 0x26, 0x2d, 0xa8, 0x8c, 0x77, 0xe4, 0x55, 0x8a, 0x90, 0xbe, 0xac, 0x5c, 0x3e, + 0x52, 0x7c, 0xc7, 0xfa, 0x07, 0xcf, 0xdb, 0x53, 0x03, 0x96, 0xb5, 0x75, 0xa6, 0x9e, 0x10, 0x3e, + 0x80, 0x72, 0xda, 0x0c, 0x5e, 0xcd, 0x10, 0xb3, 0x03, 0xb1, 0x17, 0x98, 0x1c, 0xf7, 0x00, 0xa9, + 0x3a, 0x78, 0x2d, 0x13, 0x9b, 0xeb, 0xcd, 0x5e, 0xe4, 0xf2, 0x6e, 0xfd, 0xed, 0xb3, 0x65, 0xbc, + 0x8b, 0xef, 0x43, 0x7c, 0xd3, 0xaf, 0x56, 0xe1, 0x06, 0xc9, 0xd7, 0xbd, 0xff, 0x1d, 0x00, 0x00, + 0xff, 0xff, 0x08, 0x40, 0xae, 0x74, 0xf6, 0x02, 0x00, 0x00, } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index 0e7ce3ca3..15e208351 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -2,7 +2,7 @@ syntax = "proto3"; package core; -message Device { +message HandlerDevice { bytes DevEUI = 1; message PersonalizedData { bytes NwkSKey = 1; @@ -17,23 +17,23 @@ message Device { } } -message ListDevicesReq { +message HandlerListDevicesReq { bytes AppEUI = 1; } -message ListDevicesRes { - repeated Device Devices = 1; +message HandlerListDevicesRes { + repeated HandlerDevice Devices = 1; } -message UpsertDeviceReq { +message HandlerUpsertDeviceReq { bytes AppEUI = 1; - Device Device = 2; + HandlerDevice Device = 2; } -message UpsertDeviceRes { +message HandlerUpsertDeviceRes { } service HandlerManager { - rpc List(ListDevicesReq) returns (ListDevicesRes); - rpc Upsert(UpsertDeviceReq) returns (UpsertDeviceRes); + rpc List(HandlerListDevicesReq) returns (HandlerListDevicesRes); + rpc Upsert(HandlerUpsertDeviceReq) returns (HandlerUpsertDeviceRes); } From 820d7fb97a0ff0c98e0e719b68e0bdd17e7b97e5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 15:46:18 +0100 Subject: [PATCH 1151/2266] [feature/manager] Add and Register Broker protos - Create App - Update App - Device Registrations --- core/broker_manager.pb.go | 1865 ++++++++++++++++++++++++++++++ core/components/broker/broker.go | 13 + core/protos/broker_manager.proto | 52 + 3 files changed, 1930 insertions(+) create mode 100644 core/broker_manager.pb.go create mode 100644 core/protos/broker_manager.proto diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go new file mode 100644 index 000000000..26a2af48f --- /dev/null +++ b/core/broker_manager.pb.go @@ -0,0 +1,1865 @@ +// Code generated by protoc-gen-gogo. +// source: broker_manager.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type BrokerCreateAppReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + NetAddress string `protobuf:"bytes,2,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` + Password string `protobuf:"bytes,3,opt,name=Password,json=password,proto3" json:"Password,omitempty"` +} + +func (m *BrokerCreateAppReq) Reset() { *m = BrokerCreateAppReq{} } +func (m *BrokerCreateAppReq) String() string { return proto.CompactTextString(m) } +func (*BrokerCreateAppReq) ProtoMessage() {} +func (*BrokerCreateAppReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{0} } + +type BrokerUpdateAppReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + // Types that are valid to be assigned to Update: + // *BrokerUpdateAppReq_NetAddress + // *BrokerUpdateAppReq_Password + Update isBrokerUpdateAppReq_Update `protobuf_oneof:"Update"` +} + +func (m *BrokerUpdateAppReq) Reset() { *m = BrokerUpdateAppReq{} } +func (m *BrokerUpdateAppReq) String() string { return proto.CompactTextString(m) } +func (*BrokerUpdateAppReq) ProtoMessage() {} +func (*BrokerUpdateAppReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{1} } + +type isBrokerUpdateAppReq_Update interface { + isBrokerUpdateAppReq_Update() + MarshalTo([]byte) (int, error) + Size() int +} + +type BrokerUpdateAppReq_NetAddress struct { + NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3,oneof"` +} +type BrokerUpdateAppReq_Password struct { + Password string `protobuf:"bytes,4,opt,name=Password,json=password,proto3,oneof"` +} + +func (*BrokerUpdateAppReq_NetAddress) isBrokerUpdateAppReq_Update() {} +func (*BrokerUpdateAppReq_Password) isBrokerUpdateAppReq_Update() {} + +func (m *BrokerUpdateAppReq) GetUpdate() isBrokerUpdateAppReq_Update { + if m != nil { + return m.Update + } + return nil +} + +func (m *BrokerUpdateAppReq) GetNetAddress() string { + if x, ok := m.GetUpdate().(*BrokerUpdateAppReq_NetAddress); ok { + return x.NetAddress + } + return "" +} + +func (m *BrokerUpdateAppReq) GetPassword() string { + if x, ok := m.GetUpdate().(*BrokerUpdateAppReq_Password); ok { + return x.Password + } + return "" +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*BrokerUpdateAppReq) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _BrokerUpdateAppReq_OneofMarshaler, _BrokerUpdateAppReq_OneofUnmarshaler, _BrokerUpdateAppReq_OneofSizer, []interface{}{ + (*BrokerUpdateAppReq_NetAddress)(nil), + (*BrokerUpdateAppReq_Password)(nil), + } +} + +func _BrokerUpdateAppReq_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*BrokerUpdateAppReq) + // Update + switch x := m.Update.(type) { + case *BrokerUpdateAppReq_NetAddress: + _ = b.EncodeVarint(3<<3 | proto.WireBytes) + _ = b.EncodeStringBytes(x.NetAddress) + case *BrokerUpdateAppReq_Password: + _ = b.EncodeVarint(4<<3 | proto.WireBytes) + _ = b.EncodeStringBytes(x.Password) + case nil: + default: + return fmt.Errorf("BrokerUpdateAppReq.Update has unexpected type %T", x) + } + return nil +} + +func _BrokerUpdateAppReq_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*BrokerUpdateAppReq) + switch tag { + case 3: // Update.NetAddress + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Update = &BrokerUpdateAppReq_NetAddress{x} + return true, err + case 4: // Update.Password + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Update = &BrokerUpdateAppReq_Password{x} + return true, err + default: + return false, nil + } +} + +func _BrokerUpdateAppReq_OneofSizer(msg proto.Message) (n int) { + m := msg.(*BrokerUpdateAppReq) + // Update + switch x := m.Update.(type) { + case *BrokerUpdateAppReq_NetAddress: + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.NetAddress))) + n += len(x.NetAddress) + case *BrokerUpdateAppReq_Password: + n += proto.SizeVarint(4<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.Password))) + n += len(x.Password) + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type BrokerUpsertAppRes struct { + Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` +} + +func (m *BrokerUpsertAppRes) Reset() { *m = BrokerUpsertAppRes{} } +func (m *BrokerUpsertAppRes) String() string { return proto.CompactTextString(m) } +func (*BrokerUpsertAppRes) ProtoMessage() {} +func (*BrokerUpsertAppRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{2} } + +type BrokerDevice struct { + DevAddr []byte `protobuf:"bytes,1,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` +} + +func (m *BrokerDevice) Reset() { *m = BrokerDevice{} } +func (m *BrokerDevice) String() string { return proto.CompactTextString(m) } +func (*BrokerDevice) ProtoMessage() {} +func (*BrokerDevice) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{3} } + +type BrokerListDevicesReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` +} + +func (m *BrokerListDevicesReq) Reset() { *m = BrokerListDevicesReq{} } +func (m *BrokerListDevicesReq) String() string { return proto.CompactTextString(m) } +func (*BrokerListDevicesReq) ProtoMessage() {} +func (*BrokerListDevicesReq) Descriptor() ([]byte, []int) { + return fileDescriptorBrokerManager, []int{4} +} + +type BrokerListDevicesRes struct { + Devices []*BrokerDevice `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` +} + +func (m *BrokerListDevicesRes) Reset() { *m = BrokerListDevicesRes{} } +func (m *BrokerListDevicesRes) String() string { return proto.CompactTextString(m) } +func (*BrokerListDevicesRes) ProtoMessage() {} +func (*BrokerListDevicesRes) Descriptor() ([]byte, []int) { + return fileDescriptorBrokerManager, []int{5} +} + +func (m *BrokerListDevicesRes) GetDevices() []*BrokerDevice { + if m != nil { + return m.Devices + } + return nil +} + +type BrokerUpsertDeviceReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + Device *BrokerDevice `protobuf:"bytes,3,opt,name=Device,json=device" json:"Device,omitempty"` +} + +func (m *BrokerUpsertDeviceReq) Reset() { *m = BrokerUpsertDeviceReq{} } +func (m *BrokerUpsertDeviceReq) String() string { return proto.CompactTextString(m) } +func (*BrokerUpsertDeviceReq) ProtoMessage() {} +func (*BrokerUpsertDeviceReq) Descriptor() ([]byte, []int) { + return fileDescriptorBrokerManager, []int{6} +} + +func (m *BrokerUpsertDeviceReq) GetDevice() *BrokerDevice { + if m != nil { + return m.Device + } + return nil +} + +type BrokerUpsertDeviceRes struct { +} + +func (m *BrokerUpsertDeviceRes) Reset() { *m = BrokerUpsertDeviceRes{} } +func (m *BrokerUpsertDeviceRes) String() string { return proto.CompactTextString(m) } +func (*BrokerUpsertDeviceRes) ProtoMessage() {} +func (*BrokerUpsertDeviceRes) Descriptor() ([]byte, []int) { + return fileDescriptorBrokerManager, []int{7} +} + +func init() { + proto.RegisterType((*BrokerCreateAppReq)(nil), "core.BrokerCreateAppReq") + proto.RegisterType((*BrokerUpdateAppReq)(nil), "core.BrokerUpdateAppReq") + proto.RegisterType((*BrokerUpsertAppRes)(nil), "core.BrokerUpsertAppRes") + proto.RegisterType((*BrokerDevice)(nil), "core.BrokerDevice") + proto.RegisterType((*BrokerListDevicesReq)(nil), "core.BrokerListDevicesReq") + proto.RegisterType((*BrokerListDevicesRes)(nil), "core.BrokerListDevicesRes") + proto.RegisterType((*BrokerUpsertDeviceReq)(nil), "core.BrokerUpsertDeviceReq") + proto.RegisterType((*BrokerUpsertDeviceRes)(nil), "core.BrokerUpsertDeviceRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for BrokerManager service + +type BrokerManagerClient interface { + CreateApp(ctx context.Context, in *BrokerCreateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) + UpdateApp(ctx context.Context, in *BrokerUpdateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) + UpsertDeviceRegistration(ctx context.Context, in *BrokerUpsertDeviceReq, opts ...grpc.CallOption) (*BrokerUpsertDeviceRes, error) +} + +type brokerManagerClient struct { + cc *grpc.ClientConn +} + +func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { + return &brokerManagerClient{cc} +} + +func (c *brokerManagerClient) CreateApp(ctx context.Context, in *BrokerCreateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) { + out := new(BrokerUpsertAppRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/CreateApp", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerManagerClient) UpdateApp(ctx context.Context, in *BrokerUpdateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) { + out := new(BrokerUpsertAppRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/UpdateApp", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerManagerClient) UpsertDeviceRegistration(ctx context.Context, in *BrokerUpsertDeviceReq, opts ...grpc.CallOption) (*BrokerUpsertDeviceRes, error) { + out := new(BrokerUpsertDeviceRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/UpsertDeviceRegistration", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for BrokerManager service + +type BrokerManagerServer interface { + CreateApp(context.Context, *BrokerCreateAppReq) (*BrokerUpsertAppRes, error) + UpdateApp(context.Context, *BrokerUpdateAppReq) (*BrokerUpsertAppRes, error) + UpsertDeviceRegistration(context.Context, *BrokerUpsertDeviceReq) (*BrokerUpsertDeviceRes, error) +} + +func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { + s.RegisterService(&_BrokerManager_serviceDesc, srv) +} + +func _BrokerManager_CreateApp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(BrokerCreateAppReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).CreateApp(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _BrokerManager_UpdateApp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(BrokerUpdateAppReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).UpdateApp(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _BrokerManager_UpsertDeviceRegistration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(BrokerUpsertDeviceReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).UpsertDeviceRegistration(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _BrokerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.BrokerManager", + HandlerType: (*BrokerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "CreateApp", + Handler: _BrokerManager_CreateApp_Handler, + }, + { + MethodName: "UpdateApp", + Handler: _BrokerManager_UpdateApp_Handler, + }, + { + MethodName: "UpsertDeviceRegistration", + Handler: _BrokerManager_UpsertDeviceRegistration_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *BrokerCreateAppReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerCreateAppReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if len(m.NetAddress) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) + i += copy(data[i:], m.NetAddress) + } + if len(m.Password) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Password))) + i += copy(data[i:], m.Password) + } + return i, nil +} + +func (m *BrokerUpdateAppReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerUpdateAppReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if len(m.Token) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if m.Update != nil { + nn1, err := m.Update.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *BrokerUpdateAppReq_NetAddress) MarshalTo(data []byte) (int, error) { + i := 0 + data[i] = 0x1a + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) + i += copy(data[i:], m.NetAddress) + return i, nil +} +func (m *BrokerUpdateAppReq_Password) MarshalTo(data []byte) (int, error) { + i := 0 + data[i] = 0x22 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Password))) + i += copy(data[i:], m.Password) + return i, nil +} +func (m *BrokerUpsertAppRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerUpsertAppRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Token) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + return i, nil +} + +func (m *BrokerDevice) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerDevice) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + return i, nil +} + +func (m *BrokerListDevicesReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerListDevicesReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if len(m.Token) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + return i, nil +} + +func (m *BrokerListDevicesRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerListDevicesRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Devices) > 0 { + for _, msg := range m.Devices { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *BrokerUpsertDeviceReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerUpsertDeviceReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if len(m.Token) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if m.Device != nil { + data[i] = 0x1a + i++ + i = encodeVarintBrokerManager(data, i, uint64(m.Device.Size())) + n2, err := m.Device.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *BrokerUpsertDeviceRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *BrokerUpsertDeviceRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64BrokerManager(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32BrokerManager(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintBrokerManager(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *BrokerCreateAppReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + l = len(m.NetAddress) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + l = len(m.Password) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + return n +} + +func (m *BrokerUpdateAppReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + if m.Update != nil { + n += m.Update.Size() + } + return n +} + +func (m *BrokerUpdateAppReq_NetAddress) Size() (n int) { + var l int + _ = l + l = len(m.NetAddress) + n += 1 + l + sovBrokerManager(uint64(l)) + return n +} +func (m *BrokerUpdateAppReq_Password) Size() (n int) { + var l int + _ = l + l = len(m.Password) + n += 1 + l + sovBrokerManager(uint64(l)) + return n +} +func (m *BrokerUpsertAppRes) Size() (n int) { + var l int + _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + return n +} + +func (m *BrokerDevice) Size() (n int) { + var l int + _ = l + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + return n +} + +func (m *BrokerListDevicesReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + return n +} + +func (m *BrokerListDevicesRes) Size() (n int) { + var l int + _ = l + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.Size() + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + return n +} + +func (m *BrokerUpsertDeviceReq) Size() (n int) { + var l int + _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + } + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } + if m.Device != nil { + l = m.Device.Size() + n += 1 + l + sovBrokerManager(uint64(l)) + } + return n +} + +func (m *BrokerUpsertDeviceRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovBrokerManager(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozBrokerManager(x uint64) (n int) { + return sovBrokerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *BrokerCreateAppReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerCreateAppReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerCreateAppReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetAddress = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Password = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerUpdateAppReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerUpdateAppReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Update = &BrokerUpdateAppReq_NetAddress{string(data[iNdEx:postIndex])} + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Update = &BrokerUpdateAppReq_Password{string(data[iNdEx:postIndex])} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerUpsertAppRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerUpsertAppRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerUpsertAppRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerDevice) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerDevice: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerDevice: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerListDevicesReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerListDevicesReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerListDevicesReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerListDevicesRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerListDevicesRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerListDevicesRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Devices = append(m.Devices, &BrokerDevice{}) + if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerUpsertDeviceReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerUpsertDeviceReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerUpsertDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Device == nil { + m.Device = &BrokerDevice{} + } + if err := m.Device.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BrokerUpsertDeviceRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: BrokerUpsertDeviceRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BrokerUpsertDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBrokerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBrokerManager(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthBrokerManager + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipBrokerManager(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthBrokerManager = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBrokerManager = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorBrokerManager = []byte{ + // 405 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x4e, 0xf2, 0x40, + 0x14, 0xa5, 0xfc, 0x14, 0xb8, 0x1f, 0x5f, 0x62, 0x26, 0xa8, 0x0d, 0x1a, 0x42, 0x66, 0x45, 0x88, + 0x61, 0x81, 0x0f, 0x60, 0x40, 0x4c, 0x34, 0x2a, 0x31, 0x08, 0x1b, 0x37, 0xa6, 0x30, 0x13, 0x24, + 0xc4, 0xb6, 0xcc, 0x4c, 0x24, 0xbe, 0x83, 0x0f, 0xe0, 0x23, 0xb9, 0xf4, 0x11, 0x8c, 0x2e, 0x7c, + 0x0d, 0xe7, 0xa7, 0xd6, 0x56, 0xf1, 0x87, 0xc5, 0x24, 0x73, 0x7b, 0xee, 0x3d, 0xe7, 0xdc, 0x3b, + 0xb7, 0x50, 0x1e, 0x31, 0x7f, 0x46, 0xd9, 0xe5, 0xb5, 0xeb, 0xb9, 0x13, 0xca, 0x9a, 0x01, 0xf3, + 0x85, 0x8f, 0xb2, 0x63, 0x9f, 0x51, 0x7c, 0x05, 0xa8, 0xa3, 0xd1, 0x7d, 0x46, 0x5d, 0x41, 0xdb, + 0x41, 0xd0, 0xa7, 0x73, 0xb4, 0x01, 0xb6, 0xbc, 0x1d, 0x0c, 0x8f, 0x1c, 0xab, 0x66, 0xd5, 0x4b, + 0x7d, 0xdb, 0xd5, 0x11, 0xaa, 0x02, 0xf4, 0xa8, 0x68, 0x13, 0xc2, 0x28, 0xe7, 0x4e, 0x5a, 0x62, + 0xc5, 0x3e, 0x78, 0xd1, 0x17, 0x54, 0x81, 0xc2, 0x99, 0xcb, 0xf9, 0xc2, 0x67, 0xc4, 0xc9, 0x68, + 0xb4, 0x10, 0x84, 0x31, 0xbe, 0xb3, 0xde, 0xa5, 0x86, 0x01, 0xf9, 0x5d, 0xaa, 0x0c, 0xb9, 0x81, + 0x4c, 0xf6, 0x42, 0x95, 0x9c, 0x50, 0x01, 0xaa, 0x25, 0x0c, 0x68, 0x89, 0xc3, 0x54, 0xc2, 0xc2, + 0x76, 0xcc, 0x42, 0x36, 0xc4, 0x23, 0x13, 0x9d, 0x02, 0xd8, 0x46, 0x1d, 0x37, 0x3e, 0xdc, 0x70, + 0xca, 0x84, 0x76, 0xc3, 0x97, 0xab, 0xe2, 0x0b, 0x28, 0x99, 0xdc, 0x2e, 0xbd, 0x99, 0x8e, 0x29, + 0x72, 0x20, 0x2f, 0x6f, 0x4a, 0x31, 0x34, 0x9d, 0x27, 0x26, 0x54, 0xdd, 0x48, 0x44, 0x75, 0x93, + 0x36, 0xdd, 0x10, 0x1d, 0xa9, 0x8a, 0xde, 0x62, 0x76, 0x7e, 0x4c, 0x6f, 0xb5, 0x69, 0x59, 0xe1, + 0x99, 0x10, 0x77, 0xa1, 0x6c, 0xb8, 0x4f, 0xa6, 0x5c, 0x18, 0x7e, 0xbe, 0xf2, 0x5c, 0xbe, 0x61, + 0xe1, 0x68, 0x47, 0x3b, 0x55, 0x91, 0xa4, 0xc9, 0xd4, 0xff, 0xb5, 0x50, 0x53, 0x3d, 0x7b, 0x33, + 0xde, 0x8e, 0x76, 0xaf, 0x52, 0xf0, 0x1c, 0xd6, 0xe3, 0x33, 0x09, 0xe1, 0x95, 0x1f, 0xa9, 0xa1, + 0x87, 0x20, 0x4b, 0x75, 0xaf, 0xcb, 0x35, 0x6d, 0xa3, 0x89, 0x37, 0x97, 0x4b, 0xf2, 0xd6, 0xab, + 0x05, 0xff, 0x0d, 0x72, 0x6a, 0xd6, 0x16, 0xed, 0x41, 0x31, 0x5a, 0x52, 0xe4, 0xc4, 0x39, 0xe3, + 0xbb, 0x5b, 0x49, 0x20, 0x89, 0xc7, 0x95, 0x04, 0xd1, 0xea, 0xa1, 0x4f, 0x69, 0xe4, 0x2f, 0x04, + 0x03, 0x70, 0x92, 0x36, 0x27, 0x72, 0xda, 0xcc, 0x15, 0x53, 0xdf, 0x43, 0x5b, 0x5f, 0xab, 0xa2, + 0xf9, 0x55, 0x7e, 0x00, 0x79, 0x67, 0xed, 0xe1, 0xb9, 0x6a, 0x3d, 0xca, 0xf3, 0x24, 0xcf, 0xfd, + 0x4b, 0x35, 0x35, 0xb2, 0xf5, 0x1f, 0xba, 0xfb, 0x16, 0x00, 0x00, 0xff, 0xff, 0xdd, 0xc6, 0x53, + 0x54, 0xb9, 0x03, 0x00, 0x00, +} diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 4614de399..263701bac 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -39,6 +39,7 @@ type Options struct { // Interface defines the Broker interface type Interface interface { core.BrokerServer + core.BrokerManagerServer Start() error } @@ -313,3 +314,15 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c Metadata: resp.Metadata, }, nil } + +func (b component) CreateApp(context.Context, *core.BrokerCreateAppReq) (*core.BrokerUpsertAppRes, error) { + return nil, errors.New(errors.Structural, "Not implemented") +} + +func (b component) UpdateApp(context.Context, *core.BrokerUpdateAppReq) (*core.BrokerUpsertAppRes, error) { + return nil, errors.New(errors.Structural, "Not implemented") +} + +func (b component) UpsertDeviceRegistration(context.Context, *core.BrokerUpsertDeviceReq) (*core.BrokerUpsertDeviceRes, error) { + return nil, errors.New(errors.Structural, "Not implemented") +} diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto new file mode 100644 index 000000000..8828e9620 --- /dev/null +++ b/core/protos/broker_manager.proto @@ -0,0 +1,52 @@ +syntax = "proto3"; + +package core; + +message BrokerCreateAppReq { + bytes AppEUI = 1; + string NetAddress = 2; + string Password = 3; +} + +message BrokerUpdateAppReq { + bytes AppEUI = 1; + string Token = 2; + oneof Update { + string NetAddress = 3; + string Password = 4; + } +} + +message BrokerUpsertAppRes { + string Token = 2; +} + +message BrokerDevice { + bytes DevAddr = 1; + bytes DevEUI = 2; // prepended with zeros if ABP device + bytes NwkSKey = 3; +} + +message BrokerListDevicesReq { + bytes AppEUI = 1; + string Token = 2; +} + +message BrokerListDevicesRes { + repeated BrokerDevice Devices = 1; +} + +message BrokerUpsertDeviceReq { + bytes AppEUI = 1; + string Token = 2; + BrokerDevice Device = 3; +} + +message BrokerUpsertDeviceRes { +} + +service BrokerManager { + rpc CreateApp(BrokerCreateAppReq) returns (BrokerUpsertAppRes); + rpc UpdateApp(BrokerUpdateAppReq) returns (BrokerUpsertAppRes); + rpc UpsertDeviceRegistration(BrokerUpsertDeviceReq) returns (BrokerUpsertDeviceRes); +} From fd952b02259ade8446414366dfbe710db624d952 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 15:49:57 +0100 Subject: [PATCH 1152/2266] Add coverage to makefile + update travis --- .gitignore | 3 +++ .travis.yml | 17 ++++++++--------- Makefile | 19 +++++++++++++++++-- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/.gitignore b/.gitignore index ded963166..09289adc0 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,6 @@ _testmain.go # Generated databases *.db + +# Generate coverage profile +coverage.out diff --git a/.travis.yml b/.travis.yml index 973a9bb01..2f94e00a3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,19 +11,18 @@ before_install: install: - sudo apt-get install mosquitto mosquitto-clients - - if ! go get github.com/golang/tools/cmd/cover; then go get golang.org/x/tools/cmd/cover; fi - - go get github.com/mattn/goveralls - - go get $(comm -23 <(sort <(go list -f '{{join .Imports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) - - go get $(comm -23 <(sort <(go list -f '{{join .TestImports "\n"}}' ./...) | uniq) <(go list std) | grep -v TheThingsNetwork) + - make deps + - make test-deps + - make cover-deps before_script: - mosquitto -p 1883 1>/dev/null 2>/dev/null & script: - - go list ./... | grep -vE 'vendor|integration|ttn$' | xargs go test - - sh -c 'FMTRES="$(go fmt ./...)"; if [ ! -z "$FMTRES" ]; then echo $FMTRES; exit 255; fi' - - go vet ./... + - make test + - make fmt + - make vet after_success: - - source ./coverage.sh - - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN + - make cover + - make coveralls diff --git a/Makefile b/Makefile index beb7a3a7c..a177042c8 100644 --- a/Makefile +++ b/Makefile @@ -18,8 +18,11 @@ DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .Imports "\n"}}' ./...) | uni TEST_DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .TestImports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` select_pkgs = $(GOCMD) list ./... | grep -vE 'vendor' +coverage_pkgs = $(GOCMD) list ./... | grep -E 'core' | grep -vE 'core$$|mocks$$' RELEASE_DIR ?= release +COVER_FILE = coverage.out +TEMP_COVER_DIR ?= .cover ttnpkg = ttn-$(GOOS)-$(GOARCH) ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) @@ -37,6 +40,10 @@ deps: test-deps: $(GOCMD) get -d -v $(TEST_DEPS) +cover-deps: + if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi + $(GOCMD) get github.com/mattn/goveralls + test: $(select_pkgs) | xargs $(GOCMD) test @@ -47,10 +54,18 @@ vet: $(select_pkgs) | xargs $(GOCMD) vet cover: - $(error Still have to add this to Makefile) + mkdir $(TEMP_COVER_DIR) + for pkg in $$($(coverage_pkgs)); do profile="$(TEMP_COVER_DIR)/$$(echo $$pkg | grep -oE 'ttn/.*' | sed 's/\///g').cover"; $(GOCMD) test -cover -coverprofile=$$profile $$pkg; done + echo "mode: set" > $(COVER_FILE) && cat $(TEMP_COVER_DIR)/*.cover | grep -v mode: | sort -r | awk '{if($$1 != last) {print $$0;last=$$1}}' >> $(COVER_FILE) + rm -r $(TEMP_COVER_DIR) + +coveralls: + $$GOPATH/bin/goveralls -coverprofile=$(COVER_FILE) -service=travis-ci -repotoken $$COVERALLS_TOKEN clean: - rm -rf $(RELEASE_DIR) + [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] + ([ -d $(TEMP_COVER_DIR) ] && rm -rf $(TEMP_COVER_DIR)) || [ ! -d $(TEMP_COVER_DIR) ] + ([ -f $(COVER_FILE) ] && rm $(COVER_FILE)) || [ ! -d $(COVER_FILE) ] build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) From 342bd31790bbc7a84dc30d9bc28ed509c13d3c7a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 15:50:40 +0100 Subject: [PATCH 1153/2266] [feature/manager] Run go generate --- core/application.pb.go | 62 ++++++++++++++++++++++++++++++++++++++++++ core/broker.pb.go | 47 -------------------------------- 2 files changed, 62 insertions(+), 47 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 66a1e3616..1be33b914 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -2,6 +2,64 @@ // source: application.proto // DO NOT EDIT! +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + application.proto + broker.proto + broker_manager.proto + core.proto + handler.proto + handler_manager.proto + lorawan.proto + router.proto + + It has these top-level messages: + DataAppReq + DataAppRes + JoinAppReq + JoinAppRes + DataBrokerReq + DataBrokerRes + JoinBrokerReq + JoinBrokerRes + BrokerCreateAppReq + BrokerUpdateAppReq + BrokerUpsertAppRes + BrokerDevice + BrokerListDevicesReq + BrokerListDevicesRes + BrokerUpsertDeviceReq + BrokerUpsertDeviceRes + Metadata + StatsMetadata + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + JoinHandlerReq + JoinHandlerRes + HandlerDevice + HandlerListDevicesReq + HandlerListDevicesRes + HandlerUpsertDeviceReq + HandlerUpsertDeviceRes + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings + DataRouterReq + DataRouterRes + StatsReq + StatsRes + JoinRouterReq + JoinRouterRes +*/ package core import proto "github.com/golang/protobuf/proto" @@ -20,6 +78,10 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + type DataAppReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` diff --git a/core/broker.pb.go b/core/broker.pb.go index e4638b305..04a9c0935 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -2,49 +2,6 @@ // source: broker.proto // DO NOT EDIT! -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - broker.proto - core.proto - application.proto - handler.proto - router.proto - lorawan.proto - - It has these top-level messages: - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - Metadata - StatsMetadata - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings -*/ package core import proto "github.com/golang/protobuf/proto" @@ -63,10 +20,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - type DataBrokerReq struct { Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` From a1e157f056bab8ee4966574b10ef96d904a11044 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 15:50:53 +0100 Subject: [PATCH 1154/2266] [feature/manager] Remove register from ttnctl --- ttnctl/cmd/register.go | 94 ------------------------------------------ 1 file changed, 94 deletions(-) delete mode 100644 ttnctl/cmd/register.go diff --git a/ttnctl/cmd/register.go b/ttnctl/cmd/register.go deleted file mode 100644 index 6dc895819..000000000 --- a/ttnctl/cmd/register.go +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "encoding/hex" - "fmt" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/ttnctl/mqtt" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/apex/log" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// registerCmd represents the `register` command -var registerCmd = &cobra.Command{ - Use: "register", - Short: "Register an entity with TTN", - Long: `Register a user`, - Run: func(cmd *cobra.Command, args []string) { - ctx.Fatal("register is not implemented.") - }, -} - -// registerPersonalizedDeviceCmd represents the `register personalized-device` command -var registerPersonalizedDeviceCmd = &cobra.Command{ - Use: "personalized-device [DevAddr] [NwkSKey] [AppSKey]", - Short: "Register a new personalized device", - Long: `Register a new personalized device`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 3 { - ctx.Fatal("Insufficient arguments") - } - - devAddr, err := util.Parse32(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevAddr: %s", err) - } - - nwkSKey, err := util.Parse128(args[1]) - if err != nil { - ctx.Fatalf("Invalid NwkSKey: %s", err) - } - - appSKey, err := util.Parse128(args[2]) - if err != nil { - ctx.Fatalf("Invalid AppSKey: %s", err) - } - - payload, err := core.ABPSubAppReq{ - DevAddr: args[0], - NwkSKey: args[1], - AppSKey: args[2], - }.MarshalMsg(nil) - - if err != nil { - ctx.WithError(err).Fatal("Unable to create a registration") - } - - mqtt.Setup(viper.GetString("handler.mqtt-broker"), ctx) - mqtt.Connect() - - ctx.WithFields(log.Fields{ - "DevAddr": hex.EncodeToString(devAddr), - "NwkSKey": hex.EncodeToString(nwkSKey), - "AppSKey": hex.EncodeToString(appSKey), - }).Info("Registering device...") - - token := mqtt.Client.Publish(fmt.Sprintf("%s/devices/personalized/activations", viper.GetString("handler.app-eui")), 2, false, payload) - if token.Wait() && token.Error() != nil { - ctx.WithError(token.Error()).Fatal("Registration failed.") - } else { - // Although we can't be sure whether it actually succeeded, we can know when the command is published to the MQTT. - ctx.Info("Registration finished.") - } - - }, -} - -func init() { - RootCmd.AddCommand(registerCmd) - - registerCmd.AddCommand(registerPersonalizedDeviceCmd) - - registerPersonalizedDeviceCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") - viper.BindPFlag("handler.mqtt-broker", registerPersonalizedDeviceCmd.Flags().Lookup("mqtt-broker")) - - registerPersonalizedDeviceCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") - viper.BindPFlag("handler.app-eui", registerPersonalizedDeviceCmd.Flags().Lookup("app-eui")) - -} From 3d515515dbb50d511570fc65e1e50469bf13bdbf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 15:52:28 +0100 Subject: [PATCH 1155/2266] [feature/manager] Add test for compilation of ttnctl --- ttnctl/cmd/root_test.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 ttnctl/cmd/root_test.go diff --git a/ttnctl/cmd/root_test.go b/ttnctl/cmd/root_test.go new file mode 100644 index 000000000..5f559daa4 --- /dev/null +++ b/ttnctl/cmd/root_test.go @@ -0,0 +1,12 @@ +package cmd + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestRootCmd(t *testing.T) { + a := New(t) + a.So(RootCmd.IsAvailableCommand(), ShouldBeTrue) +} From 3325240022f3ae7040708f180af805e73e06fe14 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Mar 2016 18:23:54 +0100 Subject: [PATCH 1156/2266] [feature/manager] Create ttnctl commands for managing devices TODO: @ktorz should add Token --- ttnctl/cmd/device.go | 199 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 ttnctl/cmd/device.go diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go new file mode 100644 index 000000000..0d7d5735f --- /dev/null +++ b/ttnctl/cmd/device.go @@ -0,0 +1,199 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "golang.org/x/net/context" + + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +const emptyCell = "-" + +func getHandlerManager() (*grpc.ClientConn, core.HandlerManagerClient) { + conn, err := grpc.Dial(viper.GetString("ttn-handler"), grpc.WithInsecure()) + if err != nil { + ctx.Fatalf("Could not connect: %v", err) + } + + return conn, core.NewHandlerManagerClient(conn) +} + +// devicesCmd represents the `devices` command +var devicesCmd = &cobra.Command{ + Use: "devices", + Short: "List registered devices on the Handler", + Long: `List registered devices on the Handler`, + Run: func(cmd *cobra.Command, args []string) { + appEUI, err := util.Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + conn, manager := getHandlerManager() + defer conn.Close() + + res, err := manager.List(context.Background(), &core.HandlerListDevicesReq{ + AppEUI: appEUI, + }) + + if err != nil { + ctx.WithError(err).Fatal("Could not get device list") + } + + ctx.Infof("Found %d devices", len(res.Devices)) + + table := uitable.New() + table.MaxColWidth = 50 + + table.AddRow("DevEUI", "AppKey", "NwkSKey", "AppSKey") + for _, device := range res.Devices { + devEUI := fmt.Sprintf("%X", device.DevEUI) + + appKey := emptyCell + if a := device.GetActivated(); a != nil { + appKey = fmt.Sprintf("%X", a.AppKey) + } + + nwkSKey := emptyCell + appSKey := emptyCell + if p := device.GetPersonalized(); p != nil { + nwkSKey = fmt.Sprintf("%X", p.NwkSKey) + appSKey = fmt.Sprintf("%X", p.AppSKey) + } + + table.AddRow(devEUI, appKey, nwkSKey, appSKey) + } + fmt.Println(table) + + }, +} + +// devicesRegisterCmd represents the `device register` command +var devicesRegisterCmd = &cobra.Command{ + Use: "register [DevEUI] [AppKey]", + Short: "Create or Update OTAA registrations on the Handler", + Long: `Create or Update OTAA registrations on the Handler`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + ctx.Fatal("Insufficient arguments") + } + + appEUI, err := util.Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + devEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + + appKey, err := util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + + conn, manager := getHandlerManager() + defer conn.Close() + + res, err := manager.Upsert(context.Background(), &core.HandlerUpsertDeviceReq{ + AppEUI: appEUI, + Device: &core.HandlerDevice{ + DevEUI: devEUI, + Data: &core.HandlerDevice_Activated{ + Activated: &core.HandlerDevice_ActivatedData{ + AppKey: appKey, + }, + }, + }, + }) + + if err != nil || res == nil { + ctx.WithError(err).Fatal("Could not register device") + } + ctx.Info("Ok") + + }, +} + +// devicesRegisterPersonalizedCmd represents the `device register personalized` command +var devicesRegisterPersonalizedCmd = &cobra.Command{ + Use: "personalized [DevEUI] [NwkSKey] [AppSKey]", + Short: "Create or Update ABP registrations on the Handler", + Long: `Create or Update ABP registrations on the Handler`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 3 { + ctx.Fatal("Insufficient arguments") + } + + appEUI, err := util.Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + devEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + + nwkSKey, err := util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + + appSKey, err := util.Parse128(args[2]) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + + conn, manager := getHandlerManager() + defer conn.Close() + + res, err := manager.Upsert(context.Background(), &core.HandlerUpsertDeviceReq{ + AppEUI: appEUI, + Device: &core.HandlerDevice{ + DevEUI: devEUI, + Data: &core.HandlerDevice_Personalized{ + Personalized: &core.HandlerDevice_PersonalizedData{ + AppSKey: appSKey, + NwkSKey: nwkSKey, + }, + }, + }, + }) + + if err != nil || res == nil { + ctx.WithError(err).Fatal("Could not register device") + } + ctx.Info("Ok") + + }, +} + +func init() { + RootCmd.AddCommand(devicesCmd) + + devicesCmd.AddCommand(devicesRegisterCmd) + + devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) + + devicesCmd.Flags().String("ttn-handler", "0.0.0.0:1882", "The net address of the TTN Handler") + viper.BindPFlag("ttn-handler", devicesCmd.Flags().Lookup("ttn-handler")) + + devicesCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") + viper.BindPFlag("app-eui", devicesCmd.PersistentFlags().Lookup("app-eui")) + + devicesCmd.PersistentFlags().String("app-token", "0102030405060708", "The app Token to use") + viper.BindPFlag("app-token", devicesCmd.PersistentFlags().Lookup("app-token")) + +} From 58123158d4bbf95ec6922f3fca65e94f3bbd9012 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 19:39:26 +0100 Subject: [PATCH 1157/2266] [feature/manager] Rewrite protos -> Simplify definition and declarations --- core/protos/broker_manager.proto | 54 +++++++++++------------------ core/protos/handler_manager.proto | 56 ++++++++++++++++++------------- 2 files changed, 53 insertions(+), 57 deletions(-) diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index 8828e9620..3c76f8013 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -2,51 +2,37 @@ syntax = "proto3"; package core; -message BrokerCreateAppReq { - bytes AppEUI = 1; - string NetAddress = 2; - string Password = 3; +message RegisterOTAABrokerReq { + bytes AppEUI = 1; + string NetAddress = 2; } -message BrokerUpdateAppReq { - bytes AppEUI = 1; - string Token = 2; - oneof Update { - string NetAddress = 3; - string Password = 4; - } -} - -message BrokerUpsertAppRes { - string Token = 2; -} +message RegisterOTAABrokerRes{} -message BrokerDevice { - bytes DevAddr = 1; - bytes DevEUI = 2; // prepended with zeros if ABP device - bytes NwkSKey = 3; +message UpsertABPBrokerReq { + bytes AppEUI = 1; + bytes DevAddr = 2; + bytes NwkSKey = 3; } -message BrokerListDevicesReq { - bytes AppEUI = 1; - string Token = 2; -} +message UpsertABPBrokerRes {} -message BrokerListDevicesRes { - repeated BrokerDevice Devices = 1; +message BrokerDevice { + bytes DevEUI = 1; + bytes DevAddr = 2; + bytes NwkSKey = 3; } -message BrokerUpsertDeviceReq { - bytes AppEUI = 1; - string Token = 2; - BrokerDevice Device = 3; +message ListDevicesBrokerReq { + bytes AppEUI = 1; } -message BrokerUpsertDeviceRes { +message ListDevicesBrokerRes { + repeated BrokerDevice Devices = 1; } service BrokerManager { - rpc CreateApp(BrokerCreateAppReq) returns (BrokerUpsertAppRes); - rpc UpdateApp(BrokerUpdateAppReq) returns (BrokerUpsertAppRes); - rpc UpsertDeviceRegistration(BrokerUpsertDeviceReq) returns (BrokerUpsertDeviceRes); + rpc RegisterOTAA (RegisterOTAABrokerReq) returns (RegisterOTAABrokerRes); + rpc UpsertABP (UpsertABPBrokerReq) returns (UpsertABPBrokerRes); + rpc ListDevices (ListDevicesBrokerReq) returns (ListDevicesBrokerRes); } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index 15e208351..349c76cf8 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -2,38 +2,48 @@ syntax = "proto3"; package core; -message HandlerDevice { - bytes DevEUI = 1; - message PersonalizedData { - bytes NwkSKey = 1; - bytes AppSKey = 2; - } - message ActivatedData { - bytes AppKey = 1; - } - oneof Data { - PersonalizedData Personalized = 2; - ActivatedData Activated = 3; - } +message UpsertOTAAHandlerReq { + bytes AppEUI = 1; + bytes DevEUI = 2; + bytes AppKey = 3; } -message HandlerListDevicesReq { - bytes AppEUI = 1; +message UpsertOTAAHandlerRes{} + +message UpsertABPHandlerReq { + bytes AppEUI = 1; + bytes DevAddr = 2; + bytes NwkSKey = 3; + bytes AppSKey = 4; +} + +message UpsertABPHandlerRes {} + +message ListDevicesHandlerReq { + bytes AppEUI = 1; } -message HandlerListDevicesRes { - repeated HandlerDevice Devices = 1; +message ListDevicesHandlerRes { + repeated HandlerOTAADevice OTAA = 1; + repeated HandlerABPDevice ABP = 2; } -message HandlerUpsertDeviceReq { - bytes AppEUI = 1; - HandlerDevice Device = 2; +message HandlerABPDevice { + bytes DevAddr = 2; + bytes NwkSKey = 3; + bytes AppSKey = 4; } -message HandlerUpsertDeviceRes { +message HandlerOTAADevice { + bytes DevEUI = 1; + bytes DevAddr = 2; + bytes NwkSKey = 3; + bytes AppSKey = 4; + bytes AppKey = 5; } service HandlerManager { - rpc List(HandlerListDevicesReq) returns (HandlerListDevicesRes); - rpc Upsert(HandlerUpsertDeviceReq) returns (HandlerUpsertDeviceRes); + rpc UpsertOTAA (UpsertOTAAHandlerReq) returns (UpsertOTAAHandlerRes); + rpc UpsertABP (UpsertABPHandlerReq) returns (UpsertABPHandlerRes); + rpc ListDevices (ListDevicesHandlerReq) returns (ListDevicesHandlerRes); } From 13994b038e7f49954501d3fc4ef523ad6e982030 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 19:39:36 +0100 Subject: [PATCH 1158/2266] [feature/manager] Re-generate files --- core/application.pb.go | 62 -- core/broker_manager.pb.go | 1104 ++++++++++------------------------ core/handler_manager.pb.go | 1167 +++++++++++++++++++++++------------- 3 files changed, 1085 insertions(+), 1248 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 1be33b914..66a1e3616 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -2,64 +2,6 @@ // source: application.proto // DO NOT EDIT! -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - application.proto - broker.proto - broker_manager.proto - core.proto - handler.proto - handler_manager.proto - lorawan.proto - router.proto - - It has these top-level messages: - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - BrokerCreateAppReq - BrokerUpdateAppReq - BrokerUpsertAppRes - BrokerDevice - BrokerListDevicesReq - BrokerListDevicesRes - BrokerUpsertDeviceReq - BrokerUpsertDeviceRes - Metadata - StatsMetadata - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - HandlerDevice - HandlerListDevicesReq - HandlerListDevicesRes - HandlerUpsertDeviceReq - HandlerUpsertDeviceRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes -*/ package core import proto "github.com/golang/protobuf/proto" @@ -78,10 +20,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - type DataAppReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index 26a2af48f..01f91a56b 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -2,6 +2,66 @@ // source: broker_manager.proto // DO NOT EDIT! +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + broker_manager.proto + handler_manager.proto + broker.proto + core.proto + application.proto + handler.proto + router.proto + lorawan.proto + + It has these top-level messages: + RegisterOTAABrokerReq + RegisterOTAABrokerRes + UpsertABPBrokerReq + UpsertABPBrokerRes + BrokerDevice + ListDevicesBrokerReq + ListDevicesBrokerRes + UpsertOTAAHandlerReq + UpsertOTAAHandlerRes + UpsertABPHandlerReq + UpsertABPHandlerRes + ListDevicesHandlerReq + ListDevicesHandlerRes + HandlerABPDevice + HandlerOTAADevice + DataBrokerReq + DataBrokerRes + JoinBrokerReq + JoinBrokerRes + Metadata + StatsMetadata + DataAppReq + DataAppRes + JoinAppReq + JoinAppRes + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + JoinHandlerReq + JoinHandlerRes + DataRouterReq + DataRouterRes + StatsReq + StatsRes + JoinRouterReq + JoinRouterRes + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings +*/ package core import proto "github.com/golang/protobuf/proto" @@ -20,223 +80,99 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -type BrokerCreateAppReq struct { +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type RegisterOTAABrokerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` NetAddress string `protobuf:"bytes,2,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` - Password string `protobuf:"bytes,3,opt,name=Password,json=password,proto3" json:"Password,omitempty"` -} - -func (m *BrokerCreateAppReq) Reset() { *m = BrokerCreateAppReq{} } -func (m *BrokerCreateAppReq) String() string { return proto.CompactTextString(m) } -func (*BrokerCreateAppReq) ProtoMessage() {} -func (*BrokerCreateAppReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{0} } - -type BrokerUpdateAppReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - // Types that are valid to be assigned to Update: - // *BrokerUpdateAppReq_NetAddress - // *BrokerUpdateAppReq_Password - Update isBrokerUpdateAppReq_Update `protobuf_oneof:"Update"` -} - -func (m *BrokerUpdateAppReq) Reset() { *m = BrokerUpdateAppReq{} } -func (m *BrokerUpdateAppReq) String() string { return proto.CompactTextString(m) } -func (*BrokerUpdateAppReq) ProtoMessage() {} -func (*BrokerUpdateAppReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{1} } - -type isBrokerUpdateAppReq_Update interface { - isBrokerUpdateAppReq_Update() - MarshalTo([]byte) (int, error) - Size() int -} - -type BrokerUpdateAppReq_NetAddress struct { - NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3,oneof"` -} -type BrokerUpdateAppReq_Password struct { - Password string `protobuf:"bytes,4,opt,name=Password,json=password,proto3,oneof"` -} - -func (*BrokerUpdateAppReq_NetAddress) isBrokerUpdateAppReq_Update() {} -func (*BrokerUpdateAppReq_Password) isBrokerUpdateAppReq_Update() {} - -func (m *BrokerUpdateAppReq) GetUpdate() isBrokerUpdateAppReq_Update { - if m != nil { - return m.Update - } - return nil -} - -func (m *BrokerUpdateAppReq) GetNetAddress() string { - if x, ok := m.GetUpdate().(*BrokerUpdateAppReq_NetAddress); ok { - return x.NetAddress - } - return "" } -func (m *BrokerUpdateAppReq) GetPassword() string { - if x, ok := m.GetUpdate().(*BrokerUpdateAppReq_Password); ok { - return x.Password - } - return "" +func (m *RegisterOTAABrokerReq) Reset() { *m = RegisterOTAABrokerReq{} } +func (m *RegisterOTAABrokerReq) String() string { return proto.CompactTextString(m) } +func (*RegisterOTAABrokerReq) ProtoMessage() {} +func (*RegisterOTAABrokerReq) Descriptor() ([]byte, []int) { + return fileDescriptorBrokerManager, []int{0} } -// XXX_OneofFuncs is for the internal use of the proto package. -func (*BrokerUpdateAppReq) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _BrokerUpdateAppReq_OneofMarshaler, _BrokerUpdateAppReq_OneofUnmarshaler, _BrokerUpdateAppReq_OneofSizer, []interface{}{ - (*BrokerUpdateAppReq_NetAddress)(nil), - (*BrokerUpdateAppReq_Password)(nil), - } +type RegisterOTAABrokerRes struct { } -func _BrokerUpdateAppReq_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*BrokerUpdateAppReq) - // Update - switch x := m.Update.(type) { - case *BrokerUpdateAppReq_NetAddress: - _ = b.EncodeVarint(3<<3 | proto.WireBytes) - _ = b.EncodeStringBytes(x.NetAddress) - case *BrokerUpdateAppReq_Password: - _ = b.EncodeVarint(4<<3 | proto.WireBytes) - _ = b.EncodeStringBytes(x.Password) - case nil: - default: - return fmt.Errorf("BrokerUpdateAppReq.Update has unexpected type %T", x) - } - return nil +func (m *RegisterOTAABrokerRes) Reset() { *m = RegisterOTAABrokerRes{} } +func (m *RegisterOTAABrokerRes) String() string { return proto.CompactTextString(m) } +func (*RegisterOTAABrokerRes) ProtoMessage() {} +func (*RegisterOTAABrokerRes) Descriptor() ([]byte, []int) { + return fileDescriptorBrokerManager, []int{1} } -func _BrokerUpdateAppReq_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*BrokerUpdateAppReq) - switch tag { - case 3: // Update.NetAddress - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - x, err := b.DecodeStringBytes() - m.Update = &BrokerUpdateAppReq_NetAddress{x} - return true, err - case 4: // Update.Password - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - x, err := b.DecodeStringBytes() - m.Update = &BrokerUpdateAppReq_Password{x} - return true, err - default: - return false, nil - } +type UpsertABPBrokerReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` } -func _BrokerUpdateAppReq_OneofSizer(msg proto.Message) (n int) { - m := msg.(*BrokerUpdateAppReq) - // Update - switch x := m.Update.(type) { - case *BrokerUpdateAppReq_NetAddress: - n += proto.SizeVarint(3<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(len(x.NetAddress))) - n += len(x.NetAddress) - case *BrokerUpdateAppReq_Password: - n += proto.SizeVarint(4<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(len(x.Password))) - n += len(x.Password) - case nil: - default: - panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) - } - return n -} +func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } +func (m *UpsertABPBrokerReq) String() string { return proto.CompactTextString(m) } +func (*UpsertABPBrokerReq) ProtoMessage() {} +func (*UpsertABPBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{2} } -type BrokerUpsertAppRes struct { - Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` +type UpsertABPBrokerRes struct { } -func (m *BrokerUpsertAppRes) Reset() { *m = BrokerUpsertAppRes{} } -func (m *BrokerUpsertAppRes) String() string { return proto.CompactTextString(m) } -func (*BrokerUpsertAppRes) ProtoMessage() {} -func (*BrokerUpsertAppRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{2} } +func (m *UpsertABPBrokerRes) Reset() { *m = UpsertABPBrokerRes{} } +func (m *UpsertABPBrokerRes) String() string { return proto.CompactTextString(m) } +func (*UpsertABPBrokerRes) ProtoMessage() {} +func (*UpsertABPBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{3} } type BrokerDevice struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` } func (m *BrokerDevice) Reset() { *m = BrokerDevice{} } func (m *BrokerDevice) String() string { return proto.CompactTextString(m) } func (*BrokerDevice) ProtoMessage() {} -func (*BrokerDevice) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{3} } +func (*BrokerDevice) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{4} } -type BrokerListDevicesReq struct { +type ListDevicesBrokerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` -} - -func (m *BrokerListDevicesReq) Reset() { *m = BrokerListDevicesReq{} } -func (m *BrokerListDevicesReq) String() string { return proto.CompactTextString(m) } -func (*BrokerListDevicesReq) ProtoMessage() {} -func (*BrokerListDevicesReq) Descriptor() ([]byte, []int) { - return fileDescriptorBrokerManager, []int{4} -} - -type BrokerListDevicesRes struct { - Devices []*BrokerDevice `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` } -func (m *BrokerListDevicesRes) Reset() { *m = BrokerListDevicesRes{} } -func (m *BrokerListDevicesRes) String() string { return proto.CompactTextString(m) } -func (*BrokerListDevicesRes) ProtoMessage() {} -func (*BrokerListDevicesRes) Descriptor() ([]byte, []int) { +func (m *ListDevicesBrokerReq) Reset() { *m = ListDevicesBrokerReq{} } +func (m *ListDevicesBrokerReq) String() string { return proto.CompactTextString(m) } +func (*ListDevicesBrokerReq) ProtoMessage() {} +func (*ListDevicesBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{5} } -func (m *BrokerListDevicesRes) GetDevices() []*BrokerDevice { - if m != nil { - return m.Devices - } - return nil -} - -type BrokerUpsertDeviceReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Token string `protobuf:"bytes,2,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - Device *BrokerDevice `protobuf:"bytes,3,opt,name=Device,json=device" json:"Device,omitempty"` +type ListDevicesBrokerRes struct { + Devices []*BrokerDevice `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` } -func (m *BrokerUpsertDeviceReq) Reset() { *m = BrokerUpsertDeviceReq{} } -func (m *BrokerUpsertDeviceReq) String() string { return proto.CompactTextString(m) } -func (*BrokerUpsertDeviceReq) ProtoMessage() {} -func (*BrokerUpsertDeviceReq) Descriptor() ([]byte, []int) { +func (m *ListDevicesBrokerRes) Reset() { *m = ListDevicesBrokerRes{} } +func (m *ListDevicesBrokerRes) String() string { return proto.CompactTextString(m) } +func (*ListDevicesBrokerRes) ProtoMessage() {} +func (*ListDevicesBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{6} } -func (m *BrokerUpsertDeviceReq) GetDevice() *BrokerDevice { +func (m *ListDevicesBrokerRes) GetDevices() []*BrokerDevice { if m != nil { - return m.Device + return m.Devices } return nil } -type BrokerUpsertDeviceRes struct { -} - -func (m *BrokerUpsertDeviceRes) Reset() { *m = BrokerUpsertDeviceRes{} } -func (m *BrokerUpsertDeviceRes) String() string { return proto.CompactTextString(m) } -func (*BrokerUpsertDeviceRes) ProtoMessage() {} -func (*BrokerUpsertDeviceRes) Descriptor() ([]byte, []int) { - return fileDescriptorBrokerManager, []int{7} -} - func init() { - proto.RegisterType((*BrokerCreateAppReq)(nil), "core.BrokerCreateAppReq") - proto.RegisterType((*BrokerUpdateAppReq)(nil), "core.BrokerUpdateAppReq") - proto.RegisterType((*BrokerUpsertAppRes)(nil), "core.BrokerUpsertAppRes") + proto.RegisterType((*RegisterOTAABrokerReq)(nil), "core.RegisterOTAABrokerReq") + proto.RegisterType((*RegisterOTAABrokerRes)(nil), "core.RegisterOTAABrokerRes") + proto.RegisterType((*UpsertABPBrokerReq)(nil), "core.UpsertABPBrokerReq") + proto.RegisterType((*UpsertABPBrokerRes)(nil), "core.UpsertABPBrokerRes") proto.RegisterType((*BrokerDevice)(nil), "core.BrokerDevice") - proto.RegisterType((*BrokerListDevicesReq)(nil), "core.BrokerListDevicesReq") - proto.RegisterType((*BrokerListDevicesRes)(nil), "core.BrokerListDevicesRes") - proto.RegisterType((*BrokerUpsertDeviceReq)(nil), "core.BrokerUpsertDeviceReq") - proto.RegisterType((*BrokerUpsertDeviceRes)(nil), "core.BrokerUpsertDeviceRes") + proto.RegisterType((*ListDevicesBrokerReq)(nil), "core.ListDevicesBrokerReq") + proto.RegisterType((*ListDevicesBrokerRes)(nil), "core.ListDevicesBrokerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -246,9 +182,9 @@ var _ grpc.ClientConn // Client API for BrokerManager service type BrokerManagerClient interface { - CreateApp(ctx context.Context, in *BrokerCreateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) - UpdateApp(ctx context.Context, in *BrokerUpdateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) - UpsertDeviceRegistration(ctx context.Context, in *BrokerUpsertDeviceReq, opts ...grpc.CallOption) (*BrokerUpsertDeviceRes, error) + RegisterOTAA(ctx context.Context, in *RegisterOTAABrokerReq, opts ...grpc.CallOption) (*RegisterOTAABrokerRes, error) + UpsertABP(ctx context.Context, in *UpsertABPBrokerReq, opts ...grpc.CallOption) (*UpsertABPBrokerRes, error) + ListDevices(ctx context.Context, in *ListDevicesBrokerReq, opts ...grpc.CallOption) (*ListDevicesBrokerRes, error) } type brokerManagerClient struct { @@ -259,27 +195,27 @@ func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { return &brokerManagerClient{cc} } -func (c *brokerManagerClient) CreateApp(ctx context.Context, in *BrokerCreateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) { - out := new(BrokerUpsertAppRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/CreateApp", in, out, c.cc, opts...) +func (c *brokerManagerClient) RegisterOTAA(ctx context.Context, in *RegisterOTAABrokerReq, opts ...grpc.CallOption) (*RegisterOTAABrokerRes, error) { + out := new(RegisterOTAABrokerRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/RegisterOTAA", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } -func (c *brokerManagerClient) UpdateApp(ctx context.Context, in *BrokerUpdateAppReq, opts ...grpc.CallOption) (*BrokerUpsertAppRes, error) { - out := new(BrokerUpsertAppRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/UpdateApp", in, out, c.cc, opts...) +func (c *brokerManagerClient) UpsertABP(ctx context.Context, in *UpsertABPBrokerReq, opts ...grpc.CallOption) (*UpsertABPBrokerRes, error) { + out := new(UpsertABPBrokerRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/UpsertABP", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } -func (c *brokerManagerClient) UpsertDeviceRegistration(ctx context.Context, in *BrokerUpsertDeviceReq, opts ...grpc.CallOption) (*BrokerUpsertDeviceRes, error) { - out := new(BrokerUpsertDeviceRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/UpsertDeviceRegistration", in, out, c.cc, opts...) +func (c *brokerManagerClient) ListDevices(ctx context.Context, in *ListDevicesBrokerReq, opts ...grpc.CallOption) (*ListDevicesBrokerRes, error) { + out := new(ListDevicesBrokerRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/ListDevices", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -289,45 +225,45 @@ func (c *brokerManagerClient) UpsertDeviceRegistration(ctx context.Context, in * // Server API for BrokerManager service type BrokerManagerServer interface { - CreateApp(context.Context, *BrokerCreateAppReq) (*BrokerUpsertAppRes, error) - UpdateApp(context.Context, *BrokerUpdateAppReq) (*BrokerUpsertAppRes, error) - UpsertDeviceRegistration(context.Context, *BrokerUpsertDeviceReq) (*BrokerUpsertDeviceRes, error) + RegisterOTAA(context.Context, *RegisterOTAABrokerReq) (*RegisterOTAABrokerRes, error) + UpsertABP(context.Context, *UpsertABPBrokerReq) (*UpsertABPBrokerRes, error) + ListDevices(context.Context, *ListDevicesBrokerReq) (*ListDevicesBrokerRes, error) } func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { s.RegisterService(&_BrokerManager_serviceDesc, srv) } -func _BrokerManager_CreateApp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(BrokerCreateAppReq) +func _BrokerManager_RegisterOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(RegisterOTAABrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).CreateApp(ctx, in) + out, err := srv.(BrokerManagerServer).RegisterOTAA(ctx, in) if err != nil { return nil, err } return out, nil } -func _BrokerManager_UpdateApp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(BrokerUpdateAppReq) +func _BrokerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpsertABPBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).UpdateApp(ctx, in) + out, err := srv.(BrokerManagerServer).UpsertABP(ctx, in) if err != nil { return nil, err } return out, nil } -func _BrokerManager_UpsertDeviceRegistration_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(BrokerUpsertDeviceReq) +func _BrokerManager_ListDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ListDevicesBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).UpsertDeviceRegistration(ctx, in) + out, err := srv.(BrokerManagerServer).ListDevices(ctx, in) if err != nil { return nil, err } @@ -339,22 +275,22 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ HandlerType: (*BrokerManagerServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "CreateApp", - Handler: _BrokerManager_CreateApp_Handler, + MethodName: "RegisterOTAA", + Handler: _BrokerManager_RegisterOTAA_Handler, }, { - MethodName: "UpdateApp", - Handler: _BrokerManager_UpdateApp_Handler, + MethodName: "UpsertABP", + Handler: _BrokerManager_UpsertABP_Handler, }, { - MethodName: "UpsertDeviceRegistration", - Handler: _BrokerManager_UpsertDeviceRegistration_Handler, + MethodName: "ListDevices", + Handler: _BrokerManager_ListDevices_Handler, }, }, Streams: []grpc.StreamDesc{}, } -func (m *BrokerCreateAppReq) Marshal() (data []byte, err error) { +func (m *RegisterOTAABrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -364,7 +300,7 @@ func (m *BrokerCreateAppReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *BrokerCreateAppReq) MarshalTo(data []byte) (int, error) { +func (m *RegisterOTAABrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -383,16 +319,28 @@ func (m *BrokerCreateAppReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) i += copy(data[i:], m.NetAddress) } - if len(m.Password) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Password))) - i += copy(data[i:], m.Password) + return i, nil +} + +func (m *RegisterOTAABrokerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err } + return data[:n], nil +} + +func (m *RegisterOTAABrokerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l return i, nil } -func (m *BrokerUpdateAppReq) Marshal() (data []byte, err error) { +func (m *UpsertABPBrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -402,7 +350,7 @@ func (m *BrokerUpdateAppReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *BrokerUpdateAppReq) MarshalTo(data []byte) (int, error) { +func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -415,39 +363,26 @@ func (m *BrokerUpdateAppReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.AppEUI) } } - if len(m.Token) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } } - if m.Update != nil { - nn1, err := m.Update.MarshalTo(data[i:]) - if err != nil { - return 0, err + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } - i += nn1 } return i, nil } -func (m *BrokerUpdateAppReq_NetAddress) MarshalTo(data []byte) (int, error) { - i := 0 - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) - i += copy(data[i:], m.NetAddress) - return i, nil -} -func (m *BrokerUpdateAppReq_Password) MarshalTo(data []byte) (int, error) { - i := 0 - data[i] = 0x22 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Password))) - i += copy(data[i:], m.Password) - return i, nil -} -func (m *BrokerUpsertAppRes) Marshal() (data []byte, err error) { +func (m *UpsertABPBrokerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -457,17 +392,11 @@ func (m *BrokerUpsertAppRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *BrokerUpsertAppRes) MarshalTo(data []byte) (int, error) { +func (m *UpsertABPBrokerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l - if len(m.Token) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } return i, nil } @@ -486,20 +415,20 @@ func (m *BrokerDevice) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { data[i] = 0xa i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { data[i] = 0x12 i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } } if m.NwkSKey != nil { @@ -513,7 +442,7 @@ func (m *BrokerDevice) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *BrokerListDevicesReq) Marshal() (data []byte, err error) { +func (m *ListDevicesBrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -523,7 +452,7 @@ func (m *BrokerListDevicesReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *BrokerListDevicesReq) MarshalTo(data []byte) (int, error) { +func (m *ListDevicesBrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -536,16 +465,10 @@ func (m *BrokerListDevicesReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.AppEUI) } } - if len(m.Token) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } return i, nil } -func (m *BrokerListDevicesRes) Marshal() (data []byte, err error) { +func (m *ListDevicesBrokerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -555,7 +478,7 @@ func (m *BrokerListDevicesRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *BrokerListDevicesRes) MarshalTo(data []byte) (int, error) { +func (m *ListDevicesBrokerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -575,66 +498,6 @@ func (m *BrokerListDevicesRes) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *BrokerUpsertDeviceReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *BrokerUpsertDeviceReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - } - if len(m.Token) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if m.Device != nil { - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(m.Device.Size())) - n2, err := m.Device.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 - } - return i, nil -} - -func (m *BrokerUpsertDeviceRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *BrokerUpsertDeviceRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - func encodeFixed64BrokerManager(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -662,7 +525,7 @@ func encodeVarintBrokerManager(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } -func (m *BrokerCreateAppReq) Size() (n int) { +func (m *RegisterOTAABrokerReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -675,14 +538,16 @@ func (m *BrokerCreateAppReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } - l = len(m.Password) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } return n } -func (m *BrokerUpdateAppReq) Size() (n int) { +func (m *RegisterOTAABrokerRes) Size() (n int) { + var l int + _ = l + return n +} + +func (m *UpsertABPBrokerReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -691,51 +556,38 @@ func (m *BrokerUpdateAppReq) Size() (n int) { n += 1 + l + sovBrokerManager(uint64(l)) } } - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } } - if m.Update != nil { - n += m.Update.Size() + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } } return n } -func (m *BrokerUpdateAppReq_NetAddress) Size() (n int) { - var l int - _ = l - l = len(m.NetAddress) - n += 1 + l + sovBrokerManager(uint64(l)) - return n -} -func (m *BrokerUpdateAppReq_Password) Size() (n int) { +func (m *UpsertABPBrokerRes) Size() (n int) { var l int _ = l - l = len(m.Password) - n += 1 + l + sovBrokerManager(uint64(l)) - return n -} -func (m *BrokerUpsertAppRes) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } return n } func (m *BrokerDevice) Size() (n int) { var l int _ = l - if m.DevAddr != nil { - l = len(m.DevAddr) + if m.DevEUI != nil { + l = len(m.DevEUI) if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } } - if m.DevEUI != nil { - l = len(m.DevEUI) + if m.DevAddr != nil { + l = len(m.DevAddr) if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } @@ -749,7 +601,7 @@ func (m *BrokerDevice) Size() (n int) { return n } -func (m *BrokerListDevicesReq) Size() (n int) { +func (m *ListDevicesBrokerReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -758,14 +610,10 @@ func (m *BrokerListDevicesReq) Size() (n int) { n += 1 + l + sovBrokerManager(uint64(l)) } } - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } return n } -func (m *BrokerListDevicesRes) Size() (n int) { +func (m *ListDevicesBrokerRes) Size() (n int) { var l int _ = l if len(m.Devices) > 0 { @@ -777,32 +625,6 @@ func (m *BrokerListDevicesRes) Size() (n int) { return n } -func (m *BrokerUpsertDeviceReq) Size() (n int) { - var l int - _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - } - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - if m.Device != nil { - l = m.Device.Size() - n += 1 + l + sovBrokerManager(uint64(l)) - } - return n -} - -func (m *BrokerUpsertDeviceRes) Size() (n int) { - var l int - _ = l - return n -} - func sovBrokerManager(x uint64) (n int) { for { n++ @@ -816,7 +638,7 @@ func sovBrokerManager(x uint64) (n int) { func sozBrokerManager(x uint64) (n int) { return sovBrokerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *BrokerCreateAppReq) Unmarshal(data []byte) error { +func (m *RegisterOTAABrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -839,10 +661,10 @@ func (m *BrokerCreateAppReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BrokerCreateAppReq: wiretype end group for non-group") + return fmt.Errorf("proto: RegisterOTAABrokerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerCreateAppReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: RegisterOTAABrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -905,35 +727,6 @@ func (m *BrokerCreateAppReq) Unmarshal(data []byte) error { } m.NetAddress = string(data[iNdEx:postIndex]) iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Password = string(data[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -955,7 +748,7 @@ func (m *BrokerCreateAppReq) Unmarshal(data []byte) error { } return nil } -func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { +func (m *RegisterOTAABrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -978,48 +771,67 @@ func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BrokerUpdateAppReq: wiretype end group for non-group") + return fmt.Errorf("proto: RegisterOTAABrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerUpdateAppReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: RegisterOTAABrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + default: + iNdEx = preIndex + skippy, err := skipBrokerManager(data[iNdEx:]) + if err != nil { + return err } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { + if skippy < 0 { return ErrInvalidLengthBrokerManager } - postIndex := iNdEx + byteLen - if postIndex > l { + if (iNdEx + skippy) > l { return io.ErrUnexpectedEOF } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager } - iNdEx = postIndex - case 2: + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpsertABPBrokerReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpsertABPBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBrokerManager @@ -1029,26 +841,28 @@ func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthBrokerManager } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.Token = string(data[iNdEx:postIndex]) + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } iNdEx = postIndex - case 3: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBrokerManager @@ -1058,26 +872,28 @@ func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthBrokerManager } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.Update = &BrokerUpdateAppReq_NetAddress{string(data[iNdEx:postIndex])} + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } iNdEx = postIndex - case 4: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBrokerManager @@ -1087,20 +903,22 @@ func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthBrokerManager } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.Update = &BrokerUpdateAppReq_Password{string(data[iNdEx:postIndex])} + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } iNdEx = postIndex default: iNdEx = preIndex @@ -1123,7 +941,7 @@ func (m *BrokerUpdateAppReq) Unmarshal(data []byte) error { } return nil } -func (m *BrokerUpsertAppRes) Unmarshal(data []byte) error { +func (m *UpsertABPBrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1146,41 +964,12 @@ func (m *BrokerUpsertAppRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BrokerUpsertAppRes: wiretype end group for non-group") + return fmt.Errorf("proto: UpsertABPBrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerUpsertAppRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: UpsertABPBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -1233,7 +1022,7 @@ func (m *BrokerDevice) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1257,14 +1046,14 @@ func (m *BrokerDevice) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1288,9 +1077,9 @@ func (m *BrokerDevice) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} } iNdEx = postIndex case 3: @@ -1345,7 +1134,7 @@ func (m *BrokerDevice) Unmarshal(data []byte) error { } return nil } -func (m *BrokerListDevicesReq) Unmarshal(data []byte) error { +func (m *ListDevicesBrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1368,10 +1157,10 @@ func (m *BrokerListDevicesReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BrokerListDevicesReq: wiretype end group for non-group") + return fmt.Errorf("proto: ListDevicesBrokerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerListDevicesReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ListDevicesBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1405,35 +1194,6 @@ func (m *BrokerListDevicesReq) Unmarshal(data []byte) error { m.AppEUI = []byte{} } iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -1455,7 +1215,7 @@ func (m *BrokerListDevicesReq) Unmarshal(data []byte) error { } return nil } -func (m *BrokerListDevicesRes) Unmarshal(data []byte) error { +func (m *ListDevicesBrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1478,10 +1238,10 @@ func (m *BrokerListDevicesRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: BrokerListDevicesRes: wiretype end group for non-group") + return fmt.Errorf("proto: ListDevicesBrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerListDevicesRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ListDevicesBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1536,199 +1296,6 @@ func (m *BrokerListDevicesRes) Unmarshal(data []byte) error { } return nil } -func (m *BrokerUpsertDeviceReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BrokerUpsertDeviceReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerUpsertDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Device == nil { - m.Device = &BrokerDevice{} - } - if err := m.Device.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *BrokerUpsertDeviceRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BrokerUpsertDeviceRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerUpsertDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipBrokerManager(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -1835,31 +1402,26 @@ var ( ) var fileDescriptorBrokerManager = []byte{ - // 405 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x4e, 0xf2, 0x40, - 0x14, 0xa5, 0xfc, 0x14, 0xb8, 0x1f, 0x5f, 0x62, 0x26, 0xa8, 0x0d, 0x1a, 0x42, 0x66, 0x45, 0x88, - 0x61, 0x81, 0x0f, 0x60, 0x40, 0x4c, 0x34, 0x2a, 0x31, 0x08, 0x1b, 0x37, 0xa6, 0x30, 0x13, 0x24, - 0xc4, 0xb6, 0xcc, 0x4c, 0x24, 0xbe, 0x83, 0x0f, 0xe0, 0x23, 0xb9, 0xf4, 0x11, 0x8c, 0x2e, 0x7c, - 0x0d, 0xe7, 0xa7, 0xd6, 0x56, 0xf1, 0x87, 0xc5, 0x24, 0x73, 0x7b, 0xee, 0x3d, 0xe7, 0xdc, 0x3b, - 0xb7, 0x50, 0x1e, 0x31, 0x7f, 0x46, 0xd9, 0xe5, 0xb5, 0xeb, 0xb9, 0x13, 0xca, 0x9a, 0x01, 0xf3, - 0x85, 0x8f, 0xb2, 0x63, 0x9f, 0x51, 0x7c, 0x05, 0xa8, 0xa3, 0xd1, 0x7d, 0x46, 0x5d, 0x41, 0xdb, - 0x41, 0xd0, 0xa7, 0x73, 0xb4, 0x01, 0xb6, 0xbc, 0x1d, 0x0c, 0x8f, 0x1c, 0xab, 0x66, 0xd5, 0x4b, - 0x7d, 0xdb, 0xd5, 0x11, 0xaa, 0x02, 0xf4, 0xa8, 0x68, 0x13, 0xc2, 0x28, 0xe7, 0x4e, 0x5a, 0x62, - 0xc5, 0x3e, 0x78, 0xd1, 0x17, 0x54, 0x81, 0xc2, 0x99, 0xcb, 0xf9, 0xc2, 0x67, 0xc4, 0xc9, 0x68, - 0xb4, 0x10, 0x84, 0x31, 0xbe, 0xb3, 0xde, 0xa5, 0x86, 0x01, 0xf9, 0x5d, 0xaa, 0x0c, 0xb9, 0x81, - 0x4c, 0xf6, 0x42, 0x95, 0x9c, 0x50, 0x01, 0xaa, 0x25, 0x0c, 0x68, 0x89, 0xc3, 0x54, 0xc2, 0xc2, - 0x76, 0xcc, 0x42, 0x36, 0xc4, 0x23, 0x13, 0x9d, 0x02, 0xd8, 0x46, 0x1d, 0x37, 0x3e, 0xdc, 0x70, - 0xca, 0x84, 0x76, 0xc3, 0x97, 0xab, 0xe2, 0x0b, 0x28, 0x99, 0xdc, 0x2e, 0xbd, 0x99, 0x8e, 0x29, - 0x72, 0x20, 0x2f, 0x6f, 0x4a, 0x31, 0x34, 0x9d, 0x27, 0x26, 0x54, 0xdd, 0x48, 0x44, 0x75, 0x93, - 0x36, 0xdd, 0x10, 0x1d, 0xa9, 0x8a, 0xde, 0x62, 0x76, 0x7e, 0x4c, 0x6f, 0xb5, 0x69, 0x59, 0xe1, - 0x99, 0x10, 0x77, 0xa1, 0x6c, 0xb8, 0x4f, 0xa6, 0x5c, 0x18, 0x7e, 0xbe, 0xf2, 0x5c, 0xbe, 0x61, - 0xe1, 0x68, 0x47, 0x3b, 0x55, 0x91, 0xa4, 0xc9, 0xd4, 0xff, 0xb5, 0x50, 0x53, 0x3d, 0x7b, 0x33, - 0xde, 0x8e, 0x76, 0xaf, 0x52, 0xf0, 0x1c, 0xd6, 0xe3, 0x33, 0x09, 0xe1, 0x95, 0x1f, 0xa9, 0xa1, - 0x87, 0x20, 0x4b, 0x75, 0xaf, 0xcb, 0x35, 0x6d, 0xa3, 0x89, 0x37, 0x97, 0x4b, 0xf2, 0xd6, 0xab, - 0x05, 0xff, 0x0d, 0x72, 0x6a, 0xd6, 0x16, 0xed, 0x41, 0x31, 0x5a, 0x52, 0xe4, 0xc4, 0x39, 0xe3, - 0xbb, 0x5b, 0x49, 0x20, 0x89, 0xc7, 0x95, 0x04, 0xd1, 0xea, 0xa1, 0x4f, 0x69, 0xe4, 0x2f, 0x04, - 0x03, 0x70, 0x92, 0x36, 0x27, 0x72, 0xda, 0xcc, 0x15, 0x53, 0xdf, 0x43, 0x5b, 0x5f, 0xab, 0xa2, - 0xf9, 0x55, 0x7e, 0x00, 0x79, 0x67, 0xed, 0xe1, 0xb9, 0x6a, 0x3d, 0xca, 0xf3, 0x24, 0xcf, 0xfd, - 0x4b, 0x35, 0x35, 0xb2, 0xf5, 0x1f, 0xba, 0xfb, 0x16, 0x00, 0x00, 0xff, 0xff, 0xdd, 0xc6, 0x53, - 0x54, 0xb9, 0x03, 0x00, 0x00, + // 323 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, + 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, + 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0xf2, 0xe7, 0x12, 0x0d, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, + 0x49, 0x2d, 0xf2, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, + 0x73, 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x62, 0x4b, 0x04, + 0xf3, 0x84, 0xe4, 0xb8, 0xb8, 0xfc, 0x52, 0x4b, 0x1c, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0x25, + 0x98, 0x80, 0x72, 0x9c, 0x41, 0x5c, 0x79, 0x70, 0x11, 0x25, 0x71, 0xec, 0x06, 0x16, 0x2b, 0x25, + 0x70, 0x09, 0x85, 0x16, 0x14, 0xa7, 0x16, 0x95, 0x38, 0x3a, 0x05, 0x10, 0xb6, 0x46, 0x82, 0x8b, + 0xdd, 0x25, 0xb5, 0x0c, 0x64, 0x28, 0xd8, 0x0e, 0x9e, 0x20, 0xf6, 0x14, 0x08, 0x17, 0x24, 0xe3, + 0x57, 0x9e, 0x1d, 0xec, 0x9d, 0x5a, 0x29, 0xc1, 0x0c, 0x91, 0xc9, 0x83, 0x70, 0x95, 0x44, 0xb0, + 0xd8, 0x50, 0xac, 0x14, 0xc5, 0xc5, 0x03, 0xe1, 0x00, 0xcd, 0xcb, 0x4c, 0x4e, 0x05, 0xd9, 0x08, + 0x64, 0x21, 0xd9, 0x98, 0x02, 0xe6, 0x91, 0x65, 0xa3, 0x1e, 0x97, 0x88, 0x0f, 0xd0, 0xab, 0x10, + 0x93, 0x8b, 0x09, 0xfa, 0x4a, 0xc9, 0x05, 0xab, 0xfa, 0x62, 0x21, 0x1d, 0xb0, 0xdd, 0x20, 0x31, + 0xa0, 0x06, 0x66, 0x0d, 0x6e, 0x23, 0x21, 0x3d, 0x50, 0xec, 0xe8, 0x21, 0x3b, 0x1c, 0xec, 0x1e, + 0x90, 0x12, 0xa3, 0x87, 0x8c, 0x5c, 0xbc, 0x10, 0x19, 0x5f, 0x48, 0x8c, 0x0a, 0x79, 0x70, 0xf1, + 0x20, 0x07, 0xba, 0x90, 0x34, 0x44, 0x3b, 0xd6, 0x98, 0x95, 0xc2, 0x23, 0x59, 0x2c, 0x64, 0xcf, + 0xc5, 0x09, 0x0f, 0x43, 0x21, 0x09, 0x88, 0x4a, 0xcc, 0x68, 0x93, 0xc2, 0x25, 0x53, 0x2c, 0xe4, + 0xca, 0xc5, 0x8d, 0xe4, 0x45, 0x21, 0x29, 0x88, 0x42, 0x6c, 0xa1, 0x24, 0x85, 0x5b, 0xae, 0xd8, + 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x40, 0xfc, 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, + 0x62, 0x03, 0x27, 0x5b, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x51, 0x95, 0x62, 0xd4, 0xce, + 0x02, 0x00, 0x00, } diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index eb0adc606..c6365be92 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -20,219 +20,123 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -type HandlerDevice struct { - DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - // Types that are valid to be assigned to Data: - // *HandlerDevice_Personalized - // *HandlerDevice_Activated - Data isHandlerDevice_Data `protobuf_oneof:"Data"` -} - -func (m *HandlerDevice) Reset() { *m = HandlerDevice{} } -func (m *HandlerDevice) String() string { return proto.CompactTextString(m) } -func (*HandlerDevice) ProtoMessage() {} -func (*HandlerDevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{0} } - -type isHandlerDevice_Data interface { - isHandlerDevice_Data() - MarshalTo([]byte) (int, error) - Size() int -} - -type HandlerDevice_Personalized struct { - Personalized *HandlerDevice_PersonalizedData `protobuf:"bytes,2,opt,name=Personalized,json=personalized,oneof"` -} -type HandlerDevice_Activated struct { - Activated *HandlerDevice_ActivatedData `protobuf:"bytes,3,opt,name=Activated,json=activated,oneof"` -} - -func (*HandlerDevice_Personalized) isHandlerDevice_Data() {} -func (*HandlerDevice_Activated) isHandlerDevice_Data() {} - -func (m *HandlerDevice) GetData() isHandlerDevice_Data { - if m != nil { - return m.Data - } - return nil -} - -func (m *HandlerDevice) GetPersonalized() *HandlerDevice_PersonalizedData { - if x, ok := m.GetData().(*HandlerDevice_Personalized); ok { - return x.Personalized - } - return nil -} - -func (m *HandlerDevice) GetActivated() *HandlerDevice_ActivatedData { - if x, ok := m.GetData().(*HandlerDevice_Activated); ok { - return x.Activated - } - return nil +type UpsertOTAAHandlerReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + AppKey []byte `protobuf:"bytes,3,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` } -// XXX_OneofFuncs is for the internal use of the proto package. -func (*HandlerDevice) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _HandlerDevice_OneofMarshaler, _HandlerDevice_OneofUnmarshaler, _HandlerDevice_OneofSizer, []interface{}{ - (*HandlerDevice_Personalized)(nil), - (*HandlerDevice_Activated)(nil), - } +func (m *UpsertOTAAHandlerReq) Reset() { *m = UpsertOTAAHandlerReq{} } +func (m *UpsertOTAAHandlerReq) String() string { return proto.CompactTextString(m) } +func (*UpsertOTAAHandlerReq) ProtoMessage() {} +func (*UpsertOTAAHandlerReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{0} } -func _HandlerDevice_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*HandlerDevice) - // Data - switch x := m.Data.(type) { - case *HandlerDevice_Personalized: - _ = b.EncodeVarint(2<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Personalized); err != nil { - return err - } - case *HandlerDevice_Activated: - _ = b.EncodeVarint(3<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Activated); err != nil { - return err - } - case nil: - default: - return fmt.Errorf("HandlerDevice.Data has unexpected type %T", x) - } - return nil +type UpsertOTAAHandlerRes struct { } -func _HandlerDevice_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*HandlerDevice) - switch tag { - case 2: // Data.Personalized - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(HandlerDevice_PersonalizedData) - err := b.DecodeMessage(msg) - m.Data = &HandlerDevice_Personalized{msg} - return true, err - case 3: // Data.Activated - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(HandlerDevice_ActivatedData) - err := b.DecodeMessage(msg) - m.Data = &HandlerDevice_Activated{msg} - return true, err - default: - return false, nil - } -} - -func _HandlerDevice_OneofSizer(msg proto.Message) (n int) { - m := msg.(*HandlerDevice) - // Data - switch x := m.Data.(type) { - case *HandlerDevice_Personalized: - s := proto.Size(x.Personalized) - n += proto.SizeVarint(2<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case *HandlerDevice_Activated: - s := proto.Size(x.Activated) - n += proto.SizeVarint(3<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case nil: - default: - panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) - } - return n +func (m *UpsertOTAAHandlerRes) Reset() { *m = UpsertOTAAHandlerRes{} } +func (m *UpsertOTAAHandlerRes) String() string { return proto.CompactTextString(m) } +func (*UpsertOTAAHandlerRes) ProtoMessage() {} +func (*UpsertOTAAHandlerRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{1} } -type HandlerDevice_PersonalizedData struct { - NwkSKey []byte `protobuf:"bytes,1,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,2,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` +type UpsertABPHandlerReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` } -func (m *HandlerDevice_PersonalizedData) Reset() { *m = HandlerDevice_PersonalizedData{} } -func (m *HandlerDevice_PersonalizedData) String() string { return proto.CompactTextString(m) } -func (*HandlerDevice_PersonalizedData) ProtoMessage() {} -func (*HandlerDevice_PersonalizedData) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{0, 0} +func (m *UpsertABPHandlerReq) Reset() { *m = UpsertABPHandlerReq{} } +func (m *UpsertABPHandlerReq) String() string { return proto.CompactTextString(m) } +func (*UpsertABPHandlerReq) ProtoMessage() {} +func (*UpsertABPHandlerReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{2} } -type HandlerDevice_ActivatedData struct { - AppKey []byte `protobuf:"bytes,1,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` +type UpsertABPHandlerRes struct { } -func (m *HandlerDevice_ActivatedData) Reset() { *m = HandlerDevice_ActivatedData{} } -func (m *HandlerDevice_ActivatedData) String() string { return proto.CompactTextString(m) } -func (*HandlerDevice_ActivatedData) ProtoMessage() {} -func (*HandlerDevice_ActivatedData) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{0, 1} +func (m *UpsertABPHandlerRes) Reset() { *m = UpsertABPHandlerRes{} } +func (m *UpsertABPHandlerRes) String() string { return proto.CompactTextString(m) } +func (*UpsertABPHandlerRes) ProtoMessage() {} +func (*UpsertABPHandlerRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{3} } -type HandlerListDevicesReq struct { +type ListDevicesHandlerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` } -func (m *HandlerListDevicesReq) Reset() { *m = HandlerListDevicesReq{} } -func (m *HandlerListDevicesReq) String() string { return proto.CompactTextString(m) } -func (*HandlerListDevicesReq) ProtoMessage() {} -func (*HandlerListDevicesReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{1} +func (m *ListDevicesHandlerReq) Reset() { *m = ListDevicesHandlerReq{} } +func (m *ListDevicesHandlerReq) String() string { return proto.CompactTextString(m) } +func (*ListDevicesHandlerReq) ProtoMessage() {} +func (*ListDevicesHandlerReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{4} } -type HandlerListDevicesRes struct { - Devices []*HandlerDevice `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` +type ListDevicesHandlerRes struct { + OTAA []*HandlerOTAADevice `protobuf:"bytes,1,rep,name=OTAA,json=oTAA" json:"OTAA,omitempty"` + ABP []*HandlerABPDevice `protobuf:"bytes,2,rep,name=ABP,json=aBP" json:"ABP,omitempty"` } -func (m *HandlerListDevicesRes) Reset() { *m = HandlerListDevicesRes{} } -func (m *HandlerListDevicesRes) String() string { return proto.CompactTextString(m) } -func (*HandlerListDevicesRes) ProtoMessage() {} -func (*HandlerListDevicesRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{2} +func (m *ListDevicesHandlerRes) Reset() { *m = ListDevicesHandlerRes{} } +func (m *ListDevicesHandlerRes) String() string { return proto.CompactTextString(m) } +func (*ListDevicesHandlerRes) ProtoMessage() {} +func (*ListDevicesHandlerRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{5} } -func (m *HandlerListDevicesRes) GetDevices() []*HandlerDevice { +func (m *ListDevicesHandlerRes) GetOTAA() []*HandlerOTAADevice { if m != nil { - return m.Devices + return m.OTAA } return nil } -type HandlerUpsertDeviceReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Device *HandlerDevice `protobuf:"bytes,2,opt,name=Device,json=device" json:"Device,omitempty"` -} - -func (m *HandlerUpsertDeviceReq) Reset() { *m = HandlerUpsertDeviceReq{} } -func (m *HandlerUpsertDeviceReq) String() string { return proto.CompactTextString(m) } -func (*HandlerUpsertDeviceReq) ProtoMessage() {} -func (*HandlerUpsertDeviceReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{3} -} - -func (m *HandlerUpsertDeviceReq) GetDevice() *HandlerDevice { +func (m *ListDevicesHandlerRes) GetABP() []*HandlerABPDevice { if m != nil { - return m.Device + return m.ABP } return nil } -type HandlerUpsertDeviceRes struct { +type HandlerABPDevice struct { + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` } -func (m *HandlerUpsertDeviceRes) Reset() { *m = HandlerUpsertDeviceRes{} } -func (m *HandlerUpsertDeviceRes) String() string { return proto.CompactTextString(m) } -func (*HandlerUpsertDeviceRes) ProtoMessage() {} -func (*HandlerUpsertDeviceRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{4} +func (m *HandlerABPDevice) Reset() { *m = HandlerABPDevice{} } +func (m *HandlerABPDevice) String() string { return proto.CompactTextString(m) } +func (*HandlerABPDevice) ProtoMessage() {} +func (*HandlerABPDevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{6} } + +type HandlerOTAADevice struct { + DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + AppKey []byte `protobuf:"bytes,5,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` } +func (m *HandlerOTAADevice) Reset() { *m = HandlerOTAADevice{} } +func (m *HandlerOTAADevice) String() string { return proto.CompactTextString(m) } +func (*HandlerOTAADevice) ProtoMessage() {} +func (*HandlerOTAADevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{7} } + func init() { - proto.RegisterType((*HandlerDevice)(nil), "core.HandlerDevice") - proto.RegisterType((*HandlerDevice_PersonalizedData)(nil), "core.HandlerDevice.PersonalizedData") - proto.RegisterType((*HandlerDevice_ActivatedData)(nil), "core.HandlerDevice.ActivatedData") - proto.RegisterType((*HandlerListDevicesReq)(nil), "core.HandlerListDevicesReq") - proto.RegisterType((*HandlerListDevicesRes)(nil), "core.HandlerListDevicesRes") - proto.RegisterType((*HandlerUpsertDeviceReq)(nil), "core.HandlerUpsertDeviceReq") - proto.RegisterType((*HandlerUpsertDeviceRes)(nil), "core.HandlerUpsertDeviceRes") + proto.RegisterType((*UpsertOTAAHandlerReq)(nil), "core.UpsertOTAAHandlerReq") + proto.RegisterType((*UpsertOTAAHandlerRes)(nil), "core.UpsertOTAAHandlerRes") + proto.RegisterType((*UpsertABPHandlerReq)(nil), "core.UpsertABPHandlerReq") + proto.RegisterType((*UpsertABPHandlerRes)(nil), "core.UpsertABPHandlerRes") + proto.RegisterType((*ListDevicesHandlerReq)(nil), "core.ListDevicesHandlerReq") + proto.RegisterType((*ListDevicesHandlerRes)(nil), "core.ListDevicesHandlerRes") + proto.RegisterType((*HandlerABPDevice)(nil), "core.HandlerABPDevice") + proto.RegisterType((*HandlerOTAADevice)(nil), "core.HandlerOTAADevice") } // Reference imports to suppress errors if they are not otherwise used. @@ -242,8 +146,9 @@ var _ grpc.ClientConn // Client API for HandlerManager service type HandlerManagerClient interface { - List(ctx context.Context, in *HandlerListDevicesReq, opts ...grpc.CallOption) (*HandlerListDevicesRes, error) - Upsert(ctx context.Context, in *HandlerUpsertDeviceReq, opts ...grpc.CallOption) (*HandlerUpsertDeviceRes, error) + UpsertOTAA(ctx context.Context, in *UpsertOTAAHandlerReq, opts ...grpc.CallOption) (*UpsertOTAAHandlerRes, error) + UpsertABP(ctx context.Context, in *UpsertABPHandlerReq, opts ...grpc.CallOption) (*UpsertABPHandlerRes, error) + ListDevices(ctx context.Context, in *ListDevicesHandlerReq, opts ...grpc.CallOption) (*ListDevicesHandlerRes, error) } type handlerManagerClient struct { @@ -254,18 +159,27 @@ func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { return &handlerManagerClient{cc} } -func (c *handlerManagerClient) List(ctx context.Context, in *HandlerListDevicesReq, opts ...grpc.CallOption) (*HandlerListDevicesRes, error) { - out := new(HandlerListDevicesRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/List", in, out, c.cc, opts...) +func (c *handlerManagerClient) UpsertOTAA(ctx context.Context, in *UpsertOTAAHandlerReq, opts ...grpc.CallOption) (*UpsertOTAAHandlerRes, error) { + out := new(UpsertOTAAHandlerRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/UpsertOTAA", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerManagerClient) UpsertABP(ctx context.Context, in *UpsertABPHandlerReq, opts ...grpc.CallOption) (*UpsertABPHandlerRes, error) { + out := new(UpsertABPHandlerRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/UpsertABP", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } -func (c *handlerManagerClient) Upsert(ctx context.Context, in *HandlerUpsertDeviceReq, opts ...grpc.CallOption) (*HandlerUpsertDeviceRes, error) { - out := new(HandlerUpsertDeviceRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/Upsert", in, out, c.cc, opts...) +func (c *handlerManagerClient) ListDevices(ctx context.Context, in *ListDevicesHandlerReq, opts ...grpc.CallOption) (*ListDevicesHandlerRes, error) { + out := new(ListDevicesHandlerRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/ListDevices", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -275,32 +189,45 @@ func (c *handlerManagerClient) Upsert(ctx context.Context, in *HandlerUpsertDevi // Server API for HandlerManager service type HandlerManagerServer interface { - List(context.Context, *HandlerListDevicesReq) (*HandlerListDevicesRes, error) - Upsert(context.Context, *HandlerUpsertDeviceReq) (*HandlerUpsertDeviceRes, error) + UpsertOTAA(context.Context, *UpsertOTAAHandlerReq) (*UpsertOTAAHandlerRes, error) + UpsertABP(context.Context, *UpsertABPHandlerReq) (*UpsertABPHandlerRes, error) + ListDevices(context.Context, *ListDevicesHandlerReq) (*ListDevicesHandlerRes, error) } func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { s.RegisterService(&_HandlerManager_serviceDesc, srv) } -func _HandlerManager_List_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(HandlerListDevicesReq) +func _HandlerManager_UpsertOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpsertOTAAHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).List(ctx, in) + out, err := srv.(HandlerManagerServer).UpsertOTAA(ctx, in) if err != nil { return nil, err } return out, nil } -func _HandlerManager_Upsert_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(HandlerUpsertDeviceReq) +func _HandlerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UpsertABPHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).Upsert(ctx, in) + out, err := srv.(HandlerManagerServer).UpsertABP(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _HandlerManager_ListDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ListDevicesHandlerReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerManagerServer).ListDevices(ctx, in) if err != nil { return nil, err } @@ -312,18 +239,22 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ HandlerType: (*HandlerManagerServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "List", - Handler: _HandlerManager_List_Handler, + MethodName: "UpsertOTAA", + Handler: _HandlerManager_UpsertOTAA_Handler, + }, + { + MethodName: "UpsertABP", + Handler: _HandlerManager_UpsertABP_Handler, }, { - MethodName: "Upsert", - Handler: _HandlerManager_Upsert_Handler, + MethodName: "ListDevices", + Handler: _HandlerManager_ListDevices_Handler, }, }, Streams: []grpc.StreamDesc{}, } -func (m *HandlerDevice) Marshal() (data []byte, err error) { +func (m *UpsertOTAAHandlerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -333,58 +264,57 @@ func (m *HandlerDevice) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerDevice) MarshalTo(data []byte) (int, error) { +func (m *UpsertOTAAHandlerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } if m.DevEUI != nil { if len(m.DevEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) i += copy(data[i:], m.DevEUI) } } - if m.Data != nil { - nn1, err := m.Data.MarshalTo(data[i:]) - if err != nil { - return 0, err + if m.AppKey != nil { + if len(m.AppKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) } - i += nn1 } return i, nil } -func (m *HandlerDevice_Personalized) MarshalTo(data []byte) (int, error) { - i := 0 - if m.Personalized != nil { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.Personalized.Size())) - n2, err := m.Personalized.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 +func (m *UpsertOTAAHandlerRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err } - return i, nil + return data[:n], nil } -func (m *HandlerDevice_Activated) MarshalTo(data []byte) (int, error) { - i := 0 - if m.Activated != nil { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.Activated.Size())) - n3, err := m.Activated.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } + +func (m *UpsertOTAAHandlerRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l return i, nil } -func (m *HandlerDevice_PersonalizedData) Marshal() (data []byte, err error) { + +func (m *UpsertABPHandlerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -394,14 +324,30 @@ func (m *HandlerDevice_PersonalizedData) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerDevice_PersonalizedData) MarshalTo(data []byte) (int, error) { +func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } if m.NwkSKey != nil { if len(m.NwkSKey) > 0 { - data[i] = 0xa + data[i] = 0x1a i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) @@ -409,7 +355,7 @@ func (m *HandlerDevice_PersonalizedData) MarshalTo(data []byte) (int, error) { } if m.AppSKey != nil { if len(m.AppSKey) > 0 { - data[i] = 0x12 + data[i] = 0x22 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) i += copy(data[i:], m.AppSKey) @@ -418,7 +364,7 @@ func (m *HandlerDevice_PersonalizedData) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *HandlerDevice_ActivatedData) Marshal() (data []byte, err error) { +func (m *UpsertABPHandlerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -428,23 +374,15 @@ func (m *HandlerDevice_ActivatedData) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerDevice_ActivatedData) MarshalTo(data []byte) (int, error) { +func (m *UpsertABPHandlerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l - if m.AppKey != nil { - if len(m.AppKey) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } - } return i, nil } -func (m *HandlerListDevicesReq) Marshal() (data []byte, err error) { +func (m *ListDevicesHandlerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -454,7 +392,7 @@ func (m *HandlerListDevicesReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerListDevicesReq) MarshalTo(data []byte) (int, error) { +func (m *ListDevicesHandlerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -470,7 +408,7 @@ func (m *HandlerListDevicesReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *HandlerListDevicesRes) Marshal() (data []byte, err error) { +func (m *ListDevicesHandlerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -480,13 +418,13 @@ func (m *HandlerListDevicesRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerListDevicesRes) MarshalTo(data []byte) (int, error) { +func (m *ListDevicesHandlerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l - if len(m.Devices) > 0 { - for _, msg := range m.Devices { + if len(m.OTAA) > 0 { + for _, msg := range m.OTAA { data[i] = 0xa i++ i = encodeVarintHandlerManager(data, i, uint64(msg.Size())) @@ -497,10 +435,22 @@ func (m *HandlerListDevicesRes) MarshalTo(data []byte) (int, error) { i += n } } + if len(m.ABP) > 0 { + for _, msg := range m.ABP { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } -func (m *HandlerUpsertDeviceReq) Marshal() (data []byte, err error) { +func (m *HandlerABPDevice) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -510,33 +460,39 @@ func (m *HandlerUpsertDeviceReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerUpsertDeviceReq) MarshalTo(data []byte) (int, error) { +func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } } - if m.Device != nil { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.Device.Size())) - n4, err := m.Device.MarshalTo(data[i:]) - if err != nil { - return 0, err + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + if m.AppSKey != nil { + if len(m.AppSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) } - i += n4 } return i, nil } -func (m *HandlerUpsertDeviceRes) Marshal() (data []byte, err error) { +func (m *HandlerOTAADevice) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -546,11 +502,51 @@ func (m *HandlerUpsertDeviceRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *HandlerUpsertDeviceRes) MarshalTo(data []byte) (int, error) { +func (m *HandlerOTAADevice) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l + if m.DevEUI != nil { + if len(m.DevEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) + } + } + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } + if m.NwkSKey != nil { + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + } + if m.AppSKey != nil { + if len(m.AppSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) + } + } + if m.AppKey != nil { + if len(m.AppKey) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) + } + } return i, nil } @@ -581,42 +577,51 @@ func encodeVarintHandlerManager(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } -func (m *HandlerDevice) Size() (n int) { +func (m *UpsertOTAAHandlerReq) Size() (n int) { var l int _ = l + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } if m.DevEUI != nil { l = len(m.DevEUI) if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } } - if m.Data != nil { - n += m.Data.Size() + if m.AppKey != nil { + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } } return n } -func (m *HandlerDevice_Personalized) Size() (n int) { +func (m *UpsertOTAAHandlerRes) Size() (n int) { var l int _ = l - if m.Personalized != nil { - l = m.Personalized.Size() - n += 1 + l + sovHandlerManager(uint64(l)) - } return n } -func (m *HandlerDevice_Activated) Size() (n int) { + +func (m *UpsertABPHandlerReq) Size() (n int) { var l int _ = l - if m.Activated != nil { - l = m.Activated.Size() - n += 1 + l + sovHandlerManager(uint64(l)) + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } } - return n -} -func (m *HandlerDevice_PersonalizedData) Size() (n int) { - var l int - _ = l if m.NwkSKey != nil { l = len(m.NwkSKey) if l > 0 { @@ -632,19 +637,13 @@ func (m *HandlerDevice_PersonalizedData) Size() (n int) { return n } -func (m *HandlerDevice_ActivatedData) Size() (n int) { +func (m *UpsertABPHandlerRes) Size() (n int) { var l int _ = l - if m.AppKey != nil { - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - } return n } -func (m *HandlerListDevicesReq) Size() (n int) { +func (m *ListDevicesHandlerReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -656,11 +655,17 @@ func (m *HandlerListDevicesReq) Size() (n int) { return n } -func (m *HandlerListDevicesRes) Size() (n int) { +func (m *ListDevicesHandlerRes) Size() (n int) { var l int _ = l - if len(m.Devices) > 0 { - for _, e := range m.Devices { + if len(m.OTAA) > 0 { + for _, e := range m.OTAA { + l = e.Size() + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if len(m.ABP) > 0 { + for _, e := range m.ABP { l = e.Size() n += 1 + l + sovHandlerManager(uint64(l)) } @@ -668,25 +673,63 @@ func (m *HandlerListDevicesRes) Size() (n int) { return n } -func (m *HandlerUpsertDeviceReq) Size() (n int) { +func (m *HandlerABPDevice) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.NwkSKey != nil { + l = len(m.NwkSKey) if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } } - if m.Device != nil { - l = m.Device.Size() - n += 1 + l + sovHandlerManager(uint64(l)) + if m.AppSKey != nil { + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } } return n } -func (m *HandlerUpsertDeviceRes) Size() (n int) { +func (m *HandlerOTAADevice) Size() (n int) { var l int _ = l + if m.DevEUI != nil { + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.DevAddr != nil { + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.NwkSKey != nil { + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.AppSKey != nil { + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.AppKey != nil { + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } return n } @@ -703,7 +746,7 @@ func sovHandlerManager(x uint64) (n int) { func sozHandlerManager(x uint64) (n int) { return sovHandlerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *HandlerDevice) Unmarshal(data []byte) error { +func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -726,15 +769,15 @@ func (m *HandlerDevice) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HandlerDevice: wiretype end group for non-group") + return fmt.Errorf("proto: UpsertOTAAHandlerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerDevice: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: UpsertOTAAHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -758,16 +801,16 @@ func (m *HandlerDevice) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Personalized", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandlerManager @@ -777,29 +820,28 @@ func (m *HandlerDevice) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthHandlerManager } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - v := &HandlerDevice_PersonalizedData{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} } - m.Data = &HandlerDevice_Personalized{v} iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Activated", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandlerManager @@ -809,23 +851,22 @@ func (m *HandlerDevice) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthHandlerManager } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - v := &HandlerDevice_ActivatedData{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) + if m.AppKey == nil { + m.AppKey = []byte{} } - m.Data = &HandlerDevice_Activated{v} iNdEx = postIndex default: iNdEx = preIndex @@ -848,7 +889,57 @@ func (m *HandlerDevice) Unmarshal(data []byte) error { } return nil } -func (m *HandlerDevice_PersonalizedData) Unmarshal(data []byte) error { +func (m *UpsertOTAAHandlerRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UpsertOTAAHandlerRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UpsertOTAAHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -871,13 +962,75 @@ func (m *HandlerDevice_PersonalizedData) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: PersonalizedData: wiretype end group for non-group") + return fmt.Errorf("proto: UpsertABPHandlerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: PersonalizedData: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: UpsertABPHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -908,7 +1061,7 @@ func (m *HandlerDevice_PersonalizedData) Unmarshal(data []byte) error { m.NwkSKey = []byte{} } iNdEx = postIndex - case 2: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) } @@ -960,7 +1113,7 @@ func (m *HandlerDevice_PersonalizedData) Unmarshal(data []byte) error { } return nil } -func (m *HandlerDevice_ActivatedData) Unmarshal(data []byte) error { +func (m *UpsertABPHandlerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -983,43 +1136,12 @@ func (m *HandlerDevice_ActivatedData) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ActivatedData: wiretype end group for non-group") + return fmt.Errorf("proto: UpsertABPHandlerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ActivatedData: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: UpsertABPHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) - if m.AppKey == nil { - m.AppKey = []byte{} - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1041,7 +1163,7 @@ func (m *HandlerDevice_ActivatedData) Unmarshal(data []byte) error { } return nil } -func (m *HandlerListDevicesReq) Unmarshal(data []byte) error { +func (m *ListDevicesHandlerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1064,10 +1186,10 @@ func (m *HandlerListDevicesReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HandlerListDevicesReq: wiretype end group for non-group") + return fmt.Errorf("proto: ListDevicesHandlerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerListDevicesReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ListDevicesHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1122,7 +1244,7 @@ func (m *HandlerListDevicesReq) Unmarshal(data []byte) error { } return nil } -func (m *HandlerListDevicesRes) Unmarshal(data []byte) error { +func (m *ListDevicesHandlerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1145,15 +1267,15 @@ func (m *HandlerListDevicesRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HandlerListDevicesRes: wiretype end group for non-group") + return fmt.Errorf("proto: ListDevicesHandlerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerListDevicesRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ListDevicesHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field OTAA", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1177,8 +1299,39 @@ func (m *HandlerListDevicesRes) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Devices = append(m.Devices, &HandlerDevice{}) - if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + m.OTAA = append(m.OTAA, &HandlerOTAADevice{}) + if err := m.OTAA[len(m.OTAA)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ABP", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ABP = append(m.ABP, &HandlerABPDevice{}) + if err := m.ABP[len(m.ABP)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1203,7 +1356,7 @@ func (m *HandlerListDevicesRes) Unmarshal(data []byte) error { } return nil } -func (m *HandlerUpsertDeviceReq) Unmarshal(data []byte) error { +func (m *HandlerABPDevice) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1226,15 +1379,15 @@ func (m *HandlerUpsertDeviceReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HandlerUpsertDeviceReq: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerABPDevice: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerUpsertDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerABPDevice: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1258,16 +1411,16 @@ func (m *HandlerUpsertDeviceReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} } iNdEx = postIndex - case 2: + case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Device", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandlerManager @@ -1277,23 +1430,52 @@ func (m *HandlerUpsertDeviceReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthHandlerManager } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - if m.Device == nil { - m.Device = &HandlerDevice{} + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} } - if err := m.Device.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) + if m.AppSKey == nil { + m.AppSKey = []byte{} } iNdEx = postIndex default: @@ -1317,7 +1499,7 @@ func (m *HandlerUpsertDeviceReq) Unmarshal(data []byte) error { } return nil } -func (m *HandlerUpsertDeviceRes) Unmarshal(data []byte) error { +func (m *HandlerOTAADevice) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1340,12 +1522,167 @@ func (m *HandlerUpsertDeviceRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: HandlerUpsertDeviceRes: wiretype end group for non-group") + return fmt.Errorf("proto: HandlerOTAADevice: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerUpsertDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: HandlerOTAADevice: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) + if m.AppSKey == nil { + m.AppSKey = []byte{} + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) + if m.AppKey == nil { + m.AppKey = []byte{} + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1473,28 +1810,28 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 362 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0xdf, 0x4a, 0xc3, 0x30, - 0x14, 0xc6, 0xd7, 0x6d, 0xa4, 0xec, 0xb8, 0xc9, 0x88, 0x6c, 0x94, 0x2a, 0x43, 0x8b, 0xa0, 0x20, - 0x56, 0x98, 0x2f, 0xe0, 0xc6, 0x1c, 0xf3, 0x2f, 0x52, 0xd9, 0xa5, 0x48, 0x5c, 0x83, 0x16, 0x67, - 0x5b, 0x93, 0x32, 0xd1, 0x27, 0xd9, 0x5b, 0xf8, 0x1a, 0x5e, 0xfa, 0x08, 0xa2, 0x2f, 0x62, 0x9a, - 0x44, 0x5d, 0x47, 0xdd, 0x45, 0xa1, 0x5f, 0xbe, 0x2f, 0xbf, 0x93, 0x73, 0x12, 0x68, 0xdc, 0x91, - 0xd0, 0x1f, 0x53, 0x76, 0xfd, 0x40, 0x42, 0x72, 0x4b, 0x99, 0x1b, 0xb3, 0x28, 0x89, 0x70, 0x79, - 0x14, 0x31, 0xea, 0xbc, 0x16, 0xa1, 0x36, 0x50, 0x7e, 0x8f, 0x4e, 0x82, 0x11, 0xc5, 0x4d, 0x40, - 0xe2, 0xef, 0x70, 0x78, 0x64, 0x19, 0xeb, 0xc6, 0x76, 0xd5, 0x43, 0xbe, 0x54, 0xf8, 0x18, 0xaa, - 0x17, 0x94, 0xf1, 0x28, 0x24, 0xe3, 0xe0, 0x85, 0xfa, 0x56, 0x51, 0xb8, 0x4b, 0xed, 0x4d, 0x37, - 0xc5, 0xb8, 0x19, 0x84, 0x3b, 0x9b, 0xeb, 0x91, 0x84, 0x0c, 0x0a, 0x5e, 0x35, 0x9e, 0x59, 0xc3, - 0x1d, 0xa8, 0x74, 0x46, 0x49, 0x30, 0x21, 0x89, 0x00, 0x95, 0x24, 0x68, 0x23, 0x0f, 0xf4, 0x1b, - 0xd2, 0x94, 0x0a, 0xf9, 0x59, 0xb0, 0xfb, 0x50, 0x9f, 0x2f, 0x83, 0x2d, 0x30, 0xcf, 0x9f, 0xee, - 0x2f, 0x4f, 0xe8, 0xb3, 0x3e, 0xbb, 0x19, 0x2a, 0x99, 0x3a, 0x9d, 0x38, 0x96, 0x4e, 0x51, 0x39, - 0x44, 0x49, 0x7b, 0x0b, 0x6a, 0x99, 0x2a, 0x69, 0xff, 0x22, 0xfa, 0xc7, 0x40, 0x44, 0xaa, 0x2e, - 0x82, 0x72, 0xea, 0x3b, 0x7b, 0xd0, 0xd0, 0x87, 0x3c, 0x0d, 0x78, 0xa2, 0x0e, 0xca, 0x3d, 0xfa, - 0xa8, 0x37, 0xce, 0x0c, 0x8e, 0x48, 0xe5, 0xf4, 0xf3, 0x37, 0x70, 0xbc, 0x0b, 0xa6, 0x56, 0x62, - 0x47, 0x49, 0xcc, 0x60, 0x25, 0x67, 0x06, 0x9e, 0xe9, 0xab, 0x8c, 0x73, 0x05, 0x4d, 0xed, 0x0c, - 0x63, 0x4e, 0x99, 0x26, 0x2d, 0xa8, 0x8c, 0x77, 0xe4, 0x55, 0x8a, 0x90, 0xbe, 0xac, 0x5c, 0x3e, - 0x52, 0x7c, 0xc7, 0xfa, 0x07, 0xcf, 0xdb, 0x53, 0x03, 0x96, 0xb5, 0x75, 0xa6, 0x9e, 0x10, 0x3e, - 0x80, 0x72, 0xda, 0x0c, 0x5e, 0xcd, 0x10, 0xb3, 0x03, 0xb1, 0x17, 0x98, 0x1c, 0xf7, 0x00, 0xa9, - 0x3a, 0x78, 0x2d, 0x13, 0x9b, 0xeb, 0xcd, 0x5e, 0xe4, 0xf2, 0x6e, 0xfd, 0xed, 0xb3, 0x65, 0xbc, - 0x8b, 0xef, 0x43, 0x7c, 0xd3, 0xaf, 0x56, 0xe1, 0x06, 0xc9, 0xd7, 0xbd, 0xff, 0x1d, 0x00, 0x00, - 0xff, 0xff, 0x08, 0x40, 0xae, 0x74, 0xf6, 0x02, 0x00, 0x00, + // 364 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcd, 0x48, 0xcc, 0x4b, + 0xc9, 0x49, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, + 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x8a, 0xe3, 0x12, 0x09, 0x2d, 0x28, 0x4e, 0x2d, + 0x2a, 0xf1, 0x0f, 0x71, 0x74, 0xf4, 0x80, 0x28, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, 0x73, + 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x62, 0x4b, 0x04, 0xf3, + 0x40, 0xe2, 0x2e, 0xa9, 0x65, 0x20, 0x71, 0x26, 0x88, 0x78, 0x0a, 0x98, 0x07, 0x55, 0xef, 0x9d, + 0x5a, 0x29, 0xc1, 0x0c, 0x57, 0x0f, 0xe4, 0x29, 0x89, 0x61, 0x35, 0xbf, 0x58, 0xa9, 0x9a, 0x4b, + 0x18, 0x22, 0xee, 0xe8, 0x14, 0x40, 0x84, 0xb5, 0x12, 0x5c, 0xec, 0x40, 0x6b, 0x1d, 0x53, 0x52, + 0x8a, 0xa0, 0xf6, 0xb2, 0xa7, 0x40, 0xb8, 0x20, 0x19, 0xbf, 0xf2, 0xec, 0x60, 0x84, 0xcd, 0xec, + 0x79, 0x10, 0x2e, 0x48, 0x06, 0x68, 0x16, 0x58, 0x86, 0x05, 0x22, 0x93, 0x08, 0xe1, 0x2a, 0x89, + 0x62, 0xb3, 0xbc, 0x58, 0x49, 0x9f, 0x4b, 0xd4, 0x27, 0xb3, 0xb8, 0x04, 0x68, 0x51, 0x66, 0x72, + 0x6a, 0x31, 0x61, 0x57, 0x29, 0xe5, 0x61, 0xd7, 0x50, 0x2c, 0xa4, 0xcd, 0xc5, 0x02, 0xf2, 0x2f, + 0x50, 0x39, 0xb3, 0x06, 0xb7, 0x91, 0xb8, 0x1e, 0x28, 0xa8, 0xf5, 0xa0, 0xf2, 0x20, 0x09, 0x88, + 0x8e, 0x20, 0x96, 0x7c, 0x20, 0x5b, 0x48, 0x83, 0x8b, 0x19, 0xe8, 0x0e, 0xa0, 0xbf, 0x40, 0x6a, + 0xc5, 0x50, 0xd4, 0x02, 0xc5, 0xa1, 0x4a, 0x99, 0x13, 0x9d, 0x02, 0x94, 0x12, 0xb8, 0x04, 0xd0, + 0x25, 0xa8, 0x1c, 0x32, 0x13, 0x19, 0xb9, 0x04, 0x31, 0xdc, 0x89, 0x14, 0xe9, 0x8c, 0x28, 0x91, + 0x4e, 0x55, 0xbb, 0x91, 0x92, 0x10, 0x2b, 0x72, 0x12, 0x32, 0x7a, 0xc4, 0xc8, 0xc5, 0x07, 0x75, + 0x93, 0x2f, 0x24, 0x05, 0x0b, 0xb9, 0x70, 0x71, 0x21, 0x52, 0x95, 0x90, 0x14, 0x24, 0xcc, 0xb0, + 0xa5, 0x63, 0x29, 0xdc, 0x72, 0xc5, 0x42, 0x8e, 0x5c, 0x9c, 0xf0, 0x64, 0x20, 0x24, 0x89, 0xac, + 0x10, 0x25, 0x51, 0x4a, 0xe1, 0x94, 0x2a, 0x16, 0x72, 0xe7, 0xe2, 0x46, 0x4a, 0x01, 0x42, 0xd2, + 0x10, 0x95, 0x58, 0x53, 0x91, 0x14, 0x1e, 0xc9, 0x62, 0x27, 0x81, 0x13, 0x8f, 0xe4, 0x18, 0x2f, + 0x00, 0xf1, 0x03, 0x20, 0x9e, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x9c, 0x4d, 0x8d, 0x01, 0x01, + 0x00, 0x00, 0xff, 0xff, 0x2e, 0xd8, 0x9b, 0x23, 0xbf, 0x03, 0x00, 0x00, } From 71065eac786e42a7b47300f256e62d032075f7ce Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 20:28:40 +0100 Subject: [PATCH 1159/2266] [feature/manager] Use of pointer for AppKey in handler storage --- core/broker_manager.pb.go | 94 +++++++++++----------- core/components/handler/devStorage.go | 15 +++- core/components/handler/devStorage_test.go | 10 +-- core/components/handler/handler.go | 20 ++--- core/interfaces.go | 20 +++++ core/protos/broker_manager.proto | 6 +- 6 files changed, 94 insertions(+), 71 deletions(-) create mode 100644 core/interfaces.go diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index 01f91a56b..1df14dfd0 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -16,8 +16,8 @@ lorawan.proto It has these top-level messages: - RegisterOTAABrokerReq - RegisterOTAABrokerRes + ValidateOTAABrokerReq + ValidateOTAABrokerRes UpsertABPBrokerReq UpsertABPBrokerRes BrokerDevice @@ -84,25 +84,25 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. const _ = proto.ProtoPackageIsVersion1 -type RegisterOTAABrokerReq struct { +type ValidateOTAABrokerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` NetAddress string `protobuf:"bytes,2,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` } -func (m *RegisterOTAABrokerReq) Reset() { *m = RegisterOTAABrokerReq{} } -func (m *RegisterOTAABrokerReq) String() string { return proto.CompactTextString(m) } -func (*RegisterOTAABrokerReq) ProtoMessage() {} -func (*RegisterOTAABrokerReq) Descriptor() ([]byte, []int) { +func (m *ValidateOTAABrokerReq) Reset() { *m = ValidateOTAABrokerReq{} } +func (m *ValidateOTAABrokerReq) String() string { return proto.CompactTextString(m) } +func (*ValidateOTAABrokerReq) ProtoMessage() {} +func (*ValidateOTAABrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{0} } -type RegisterOTAABrokerRes struct { +type ValidateOTAABrokerRes struct { } -func (m *RegisterOTAABrokerRes) Reset() { *m = RegisterOTAABrokerRes{} } -func (m *RegisterOTAABrokerRes) String() string { return proto.CompactTextString(m) } -func (*RegisterOTAABrokerRes) ProtoMessage() {} -func (*RegisterOTAABrokerRes) Descriptor() ([]byte, []int) { +func (m *ValidateOTAABrokerRes) Reset() { *m = ValidateOTAABrokerRes{} } +func (m *ValidateOTAABrokerRes) String() string { return proto.CompactTextString(m) } +func (*ValidateOTAABrokerRes) ProtoMessage() {} +func (*ValidateOTAABrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{1} } @@ -166,8 +166,8 @@ func (m *ListDevicesBrokerRes) GetDevices() []*BrokerDevice { } func init() { - proto.RegisterType((*RegisterOTAABrokerReq)(nil), "core.RegisterOTAABrokerReq") - proto.RegisterType((*RegisterOTAABrokerRes)(nil), "core.RegisterOTAABrokerRes") + proto.RegisterType((*ValidateOTAABrokerReq)(nil), "core.ValidateOTAABrokerReq") + proto.RegisterType((*ValidateOTAABrokerRes)(nil), "core.ValidateOTAABrokerRes") proto.RegisterType((*UpsertABPBrokerReq)(nil), "core.UpsertABPBrokerReq") proto.RegisterType((*UpsertABPBrokerRes)(nil), "core.UpsertABPBrokerRes") proto.RegisterType((*BrokerDevice)(nil), "core.BrokerDevice") @@ -182,7 +182,7 @@ var _ grpc.ClientConn // Client API for BrokerManager service type BrokerManagerClient interface { - RegisterOTAA(ctx context.Context, in *RegisterOTAABrokerReq, opts ...grpc.CallOption) (*RegisterOTAABrokerRes, error) + ValidateOTAA(ctx context.Context, in *ValidateOTAABrokerReq, opts ...grpc.CallOption) (*ValidateOTAABrokerRes, error) UpsertABP(ctx context.Context, in *UpsertABPBrokerReq, opts ...grpc.CallOption) (*UpsertABPBrokerRes, error) ListDevices(ctx context.Context, in *ListDevicesBrokerReq, opts ...grpc.CallOption) (*ListDevicesBrokerRes, error) } @@ -195,9 +195,9 @@ func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { return &brokerManagerClient{cc} } -func (c *brokerManagerClient) RegisterOTAA(ctx context.Context, in *RegisterOTAABrokerReq, opts ...grpc.CallOption) (*RegisterOTAABrokerRes, error) { - out := new(RegisterOTAABrokerRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/RegisterOTAA", in, out, c.cc, opts...) +func (c *brokerManagerClient) ValidateOTAA(ctx context.Context, in *ValidateOTAABrokerReq, opts ...grpc.CallOption) (*ValidateOTAABrokerRes, error) { + out := new(ValidateOTAABrokerRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/ValidateOTAA", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -225,7 +225,7 @@ func (c *brokerManagerClient) ListDevices(ctx context.Context, in *ListDevicesBr // Server API for BrokerManager service type BrokerManagerServer interface { - RegisterOTAA(context.Context, *RegisterOTAABrokerReq) (*RegisterOTAABrokerRes, error) + ValidateOTAA(context.Context, *ValidateOTAABrokerReq) (*ValidateOTAABrokerRes, error) UpsertABP(context.Context, *UpsertABPBrokerReq) (*UpsertABPBrokerRes, error) ListDevices(context.Context, *ListDevicesBrokerReq) (*ListDevicesBrokerRes, error) } @@ -234,12 +234,12 @@ func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { s.RegisterService(&_BrokerManager_serviceDesc, srv) } -func _BrokerManager_RegisterOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(RegisterOTAABrokerReq) +func _BrokerManager_ValidateOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ValidateOTAABrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).RegisterOTAA(ctx, in) + out, err := srv.(BrokerManagerServer).ValidateOTAA(ctx, in) if err != nil { return nil, err } @@ -275,8 +275,8 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ HandlerType: (*BrokerManagerServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "RegisterOTAA", - Handler: _BrokerManager_RegisterOTAA_Handler, + MethodName: "ValidateOTAA", + Handler: _BrokerManager_ValidateOTAA_Handler, }, { MethodName: "UpsertABP", @@ -290,7 +290,7 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, } -func (m *RegisterOTAABrokerReq) Marshal() (data []byte, err error) { +func (m *ValidateOTAABrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -300,7 +300,7 @@ func (m *RegisterOTAABrokerReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *RegisterOTAABrokerReq) MarshalTo(data []byte) (int, error) { +func (m *ValidateOTAABrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -322,7 +322,7 @@ func (m *RegisterOTAABrokerReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *RegisterOTAABrokerRes) Marshal() (data []byte, err error) { +func (m *ValidateOTAABrokerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -332,7 +332,7 @@ func (m *RegisterOTAABrokerRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *RegisterOTAABrokerRes) MarshalTo(data []byte) (int, error) { +func (m *ValidateOTAABrokerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -525,7 +525,7 @@ func encodeVarintBrokerManager(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } -func (m *RegisterOTAABrokerReq) Size() (n int) { +func (m *ValidateOTAABrokerReq) Size() (n int) { var l int _ = l if m.AppEUI != nil { @@ -541,7 +541,7 @@ func (m *RegisterOTAABrokerReq) Size() (n int) { return n } -func (m *RegisterOTAABrokerRes) Size() (n int) { +func (m *ValidateOTAABrokerRes) Size() (n int) { var l int _ = l return n @@ -638,7 +638,7 @@ func sovBrokerManager(x uint64) (n int) { func sozBrokerManager(x uint64) (n int) { return sovBrokerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *RegisterOTAABrokerReq) Unmarshal(data []byte) error { +func (m *ValidateOTAABrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -661,10 +661,10 @@ func (m *RegisterOTAABrokerReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: RegisterOTAABrokerReq: wiretype end group for non-group") + return fmt.Errorf("proto: ValidateOTAABrokerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: RegisterOTAABrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ValidateOTAABrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -748,7 +748,7 @@ func (m *RegisterOTAABrokerReq) Unmarshal(data []byte) error { } return nil } -func (m *RegisterOTAABrokerRes) Unmarshal(data []byte) error { +func (m *ValidateOTAABrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -771,10 +771,10 @@ func (m *RegisterOTAABrokerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: RegisterOTAABrokerRes: wiretype end group for non-group") + return fmt.Errorf("proto: ValidateOTAABrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: RegisterOTAABrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ValidateOTAABrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -1405,8 +1405,8 @@ var fileDescriptorBrokerManager = []byte{ // 323 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0xf2, 0xe7, 0x12, 0x0d, 0x4a, 0x4d, 0xcf, 0x2c, 0x2e, - 0x49, 0x2d, 0xf2, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, + 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0xf2, 0xe7, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, + 0x2c, 0x49, 0xf5, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, 0x73, 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x62, 0x4b, 0x04, 0xf3, 0x84, 0xe4, 0xb8, 0xb8, 0xfc, 0x52, 0x4b, 0x1c, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0x25, 0x98, 0x80, 0x72, 0x9c, 0x41, 0x5c, 0x79, 0x70, 0x11, 0x25, 0x71, 0xec, 0x06, 0x16, 0x2b, 0x25, @@ -1414,14 +1414,14 @@ var fileDescriptorBrokerManager = []byte{ 0xdd, 0x25, 0xb5, 0x0c, 0x64, 0x28, 0xd8, 0x0e, 0x9e, 0x20, 0xf6, 0x14, 0x08, 0x17, 0x24, 0xe3, 0x57, 0x9e, 0x1d, 0xec, 0x9d, 0x5a, 0x29, 0xc1, 0x0c, 0x91, 0xc9, 0x83, 0x70, 0x95, 0x44, 0xb0, 0xd8, 0x50, 0xac, 0x14, 0xc5, 0xc5, 0x03, 0xe1, 0x00, 0xcd, 0xcb, 0x4c, 0x4e, 0x05, 0xd9, 0x08, - 0x64, 0x21, 0xd9, 0x98, 0x02, 0xe6, 0x91, 0x65, 0xa3, 0x1e, 0x97, 0x88, 0x0f, 0xd0, 0xab, 0x10, - 0x93, 0x8b, 0x09, 0xfa, 0x4a, 0xc9, 0x05, 0xab, 0xfa, 0x62, 0x21, 0x1d, 0xb0, 0xdd, 0x20, 0x31, - 0xa0, 0x06, 0x66, 0x0d, 0x6e, 0x23, 0x21, 0x3d, 0x50, 0xec, 0xe8, 0x21, 0x3b, 0x1c, 0xec, 0x1e, - 0x90, 0x12, 0xa3, 0x87, 0x8c, 0x5c, 0xbc, 0x10, 0x19, 0x5f, 0x48, 0x8c, 0x0a, 0x79, 0x70, 0xf1, - 0x20, 0x07, 0xba, 0x90, 0x34, 0x44, 0x3b, 0xd6, 0x98, 0x95, 0xc2, 0x23, 0x59, 0x2c, 0x64, 0xcf, - 0xc5, 0x09, 0x0f, 0x43, 0x21, 0x09, 0x88, 0x4a, 0xcc, 0x68, 0x93, 0xc2, 0x25, 0x53, 0x2c, 0xe4, - 0xca, 0xc5, 0x8d, 0xe4, 0x45, 0x21, 0x29, 0x88, 0x42, 0x6c, 0xa1, 0x24, 0x85, 0x5b, 0xae, 0xd8, - 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x40, 0xfc, 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, - 0x62, 0x03, 0x27, 0x5b, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x51, 0x95, 0x62, 0xd4, 0xce, + 0x64, 0x21, 0xd9, 0x98, 0x02, 0xe6, 0x91, 0x65, 0xa3, 0x1e, 0x97, 0x88, 0x4f, 0x66, 0x71, 0x09, + 0xc4, 0xe4, 0x62, 0x82, 0xbe, 0x52, 0x72, 0xc1, 0xaa, 0xbe, 0x58, 0x48, 0x07, 0x6c, 0x37, 0x48, + 0x0c, 0xa8, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x48, 0x0f, 0x14, 0x3b, 0x7a, 0xc8, 0x0e, 0x07, 0xbb, + 0x07, 0xa4, 0xc4, 0xe8, 0x21, 0x23, 0x17, 0x2f, 0x44, 0xc6, 0x17, 0x12, 0xa3, 0x42, 0x1e, 0x5c, + 0x3c, 0xc8, 0x81, 0x2e, 0x24, 0x0d, 0xd1, 0x8e, 0x35, 0x66, 0xa5, 0xf0, 0x48, 0x16, 0x0b, 0xd9, + 0x73, 0x71, 0xc2, 0xc3, 0x50, 0x48, 0x02, 0xa2, 0x12, 0x33, 0xda, 0xa4, 0x70, 0xc9, 0x14, 0x0b, + 0xb9, 0x72, 0x71, 0x23, 0x79, 0x51, 0x48, 0x0a, 0xa2, 0x10, 0x5b, 0x28, 0x49, 0xe1, 0x96, 0x2b, + 0x76, 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0x02, 0x10, 0x3f, 0x00, 0xe2, 0x19, 0x8f, 0xe5, 0x18, + 0x92, 0xd8, 0xc0, 0xc9, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xa5, 0x46, 0xfb, 0x03, 0xce, 0x02, 0x00, 0x00, } diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 64b6ec995..6b1b6cffd 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -23,7 +23,7 @@ const dbDevices = "devices" type devEntry struct { AppEUI []byte - AppKey [16]byte + AppKey *[16]byte AppSKey [16]byte DevAddr []byte DevEUI []byte @@ -71,7 +71,11 @@ func (s *devStorage) done() error { // MarshalBinary implements the encoding.BinaryMarshaler interface func (e devEntry) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) - rw.Write(e.AppKey[:]) + if e.AppKey != nil { + rw.Write(e.AppKey[:]) + } else { + rw.Write([]byte{}) + } rw.Write(e.AppSKey[:]) rw.Write(e.NwkSKey[:]) rw.Write(e.FCntDown) @@ -84,7 +88,12 @@ func (e devEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devEntry) UnmarshalBinary(data []byte) error { rw := readwriter.New(data) - rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) + rw.Read(func(data []byte) { + if len(data) == 16 { + e.AppKey = new([16]byte) + copy(e.AppKey[:], data) + } + }) rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index fe4dc00a6..30413e80f 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -143,7 +143,7 @@ func TestMarshalUnmarshalEntries(t *testing.T) { Desc(t, "Complete Entry") entry := devEntry{ AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, AppSKey: [16]byte{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1}, DevAddr: []byte{4, 4, 4, 4}, DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, @@ -165,13 +165,13 @@ func TestMarshalUnmarshalEntries(t *testing.T) { Desc(t, "Partial Entry") entry := devEntry{ AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, FCntDown: 0, } want := devEntry{ AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, AppSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, DevAddr: make([]byte, 0, 0), DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, @@ -193,14 +193,14 @@ func TestMarshalUnmarshalEntries(t *testing.T) { Desc(t, "Partial Entry bis") entry := devEntry{ AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, DevAddr: []byte{}, FCntDown: 0, } want := devEntry{ AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, AppSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, DevAddr: make([]byte, 0, 0), DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index ced3c093f..5a009b015 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -61,7 +61,7 @@ type Interface interface { // Components is used to make handler instantiation easier type Components struct { - Broker core.BrokerClient + Broker core.Broker Ctx log.Interface DevStorage DevStorage PktStorage PktStorage @@ -142,6 +142,9 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* if err != nil { return new(core.JoinHandlerRes), err } + if entry.AppKey == nil { // Trying to activate an ABP device + return new(core.JoinHandlerRes), errors.New(errors.Behavioural, "Trying to activate a personalized device") + } // 2. Prepare a channel to receive the response from the consumer chresp := make(chan interface{}) @@ -299,16 +302,6 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq } } -// List implements the core.HandlerManagerServer interface -func (h component) List(context.Context, *core.HandlerListDevicesReq) (*core.HandlerListDevicesRes, error) { - return nil, errors.New(errors.Structural, "Not implemented") -} - -// Upsert implements the core.HandlerManagerServer interface -func (h component) Upsert(context.Context, *core.HandlerUpsertDeviceReq) (*core.HandlerUpsertDeviceRes, error) { - return nil, errors.New(errors.Structural, "Not implemented") -} - // consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) // It then flushes them once a given delay has passed since the reception of the first bundle. func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { @@ -387,7 +380,8 @@ browseBundles: go h.consumeDown(pkt.AppEUI, pkt.DevEUI, b.DataRate, bundles) case *core.JoinHandlerReq: pkt := b.Packet.(*core.JoinHandlerReq) - go h.consumeJoin(pkt.AppEUI, pkt.DevEUI, b.Entry.AppKey, b.DataRate, bundles) + // Entry.AppKey not nil, checked before creating any bundles + go h.consumeJoin(pkt.AppEUI, pkt.DevEUI, *b.Entry.AppKey, b.DataRate, bundles) } } } @@ -450,7 +444,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da // Update the internal storage entry err = h.DevStorage.upsert(devEntry{ AppEUI: appEUI, - AppKey: appKey, + AppKey: &appKey, AppSKey: appSKey, DevAddr: devAddr[:], DevEUI: devEUI, diff --git a/core/interfaces.go b/core/interfaces.go new file mode 100644 index 000000000..814359ff2 --- /dev/null +++ b/core/interfaces.go @@ -0,0 +1,20 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package core + +type Router interface { + RouterClient +} + +type Broker interface { + BrokerClient + BrokerManagerClient + BeginToken(token string) Broker + EndToken() +} + +type Handler interface { + HandlerClient + HandlerManagerClient +} diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index 3c76f8013..37a5d8710 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -2,12 +2,12 @@ syntax = "proto3"; package core; -message RegisterOTAABrokerReq { +message ValidateOTAABrokerReq { bytes AppEUI = 1; string NetAddress = 2; } -message RegisterOTAABrokerRes{} +message ValidateOTAABrokerRes{} message UpsertABPBrokerReq { bytes AppEUI = 1; @@ -32,7 +32,7 @@ message ListDevicesBrokerRes { } service BrokerManager { - rpc RegisterOTAA (RegisterOTAABrokerReq) returns (RegisterOTAABrokerRes); + rpc ValidateOTAA (ValidateOTAABrokerReq) returns (ValidateOTAABrokerRes); rpc UpsertABP (UpsertABPBrokerReq) returns (UpsertABPBrokerRes); rpc ListDevices (ListDevicesBrokerReq) returns (ListDevicesBrokerRes); } From 5e71824704bbbdfa1c166f22261f8758006e5136 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 20:29:00 +0100 Subject: [PATCH 1160/2266] [feature/manager] Implements HandlerManager (ListDevices still missing) --- core/components/handler/handlerManager.go | 95 +++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 core/components/handler/handlerManager.go diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go new file mode 100644 index 000000000..5cb435538 --- /dev/null +++ b/core/components/handler/handlerManager.go @@ -0,0 +1,95 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" +) + +// ListDevices implements the core.HandlerManagerServer interface +func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandlerReq) (*core.ListDevicesHandlerRes, error) { + return new(core.ListDevicesHandlerRes), errors.New(errors.Implementation, "Not implemented") +} + +// UpsertABP implements the core.HandlerManager interface +func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq) (*core.UpsertABPHandlerRes, error) { + h.Ctx.Debug("Handle Upsert ABP Request") + + // 1. Validate the request + if len(req.AppEUI) != 8 || len(req.DevAddr) != 4 || len(req.NwkSKey) != 16 || len(req.AppSKey) != 16 { + err := errors.New(errors.Structural, "Invalid request parameters") + h.Ctx.WithError(err).Debug("Unable to handle ABP request") + return new(core.UpsertABPHandlerRes), err + } + + // 2. Forward to the broker firt -> The Broker also does the token verification + _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ + AppEUI: req.AppEUI, + DevAddr: req.DevAddr, + NwkSKey: req.NwkSKey, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected ABP") + return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) + } + + // 3. Insert the request in our own storage + h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering Device.") + entry := devEntry{ + AppEUI: req.AppEUI, + DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), + } + copy(entry.NwkSKey[:], req.NwkSKey) + copy(entry.AppSKey[:], req.AppSKey) + if err = h.DevStorage.upsert(entry); err != nil { + h.Ctx.WithError(err).Debug("Error while trying to save valid request") + return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) + } + + // Done. + return new(core.UpsertABPHandlerRes), nil +} + +// UpsertOTAA implements the core.HandlerManager interface +func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerReq) (*core.UpsertOTAAHandlerRes, error) { + h.Ctx.Debug("Handle Upsert OTAA Request") + + // 1. Validate the request + if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.AppKey) != 16 { + err := errors.New(errors.Structural, "Invalid request parameters") + h.Ctx.WithError(err).Debug("Unable to handle OTAA request") + return new(core.UpsertOTAAHandlerRes), err + } + + // 2. Notify the broker -> The Broker also does the token verification + // TODO Extract token from metadata and add it to request metadata + _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ + NetAddress: h.NetAddr, + AppEUI: req.AppEUI, + }) + + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected OTAA") + return new(core.UpsertOTAAHandlerRes), errors.New(errors.Operational, err) + } + + // 3. Insert the request in our own storage + h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI).Debug("Request accepted by broker. Registering Device.") + var appKey [16]byte + copy(appKey[:], req.AppKey) + err = h.DevStorage.upsert(devEntry{ + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + AppKey: &appKey, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Error while trying to save valid request") + return new(core.UpsertOTAAHandlerRes), err + } + + // 4. Done. + return new(core.UpsertOTAAHandlerRes), nil +} From 590d3facdafb930c39057d4795d4dd889bea1a59 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 20:40:34 +0100 Subject: [PATCH 1161/2266] [feature/manager] Fix tests and mocks --- core/components/handler/handler_test.go | 78 +++++++++++------------ core/mocks/mocks.go | 83 ++++++++++++++++++++++--- 2 files changed, 113 insertions(+), 48 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index fddb49c04..4fa139744 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -25,7 +25,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -63,7 +63,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -101,7 +101,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -139,7 +139,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, @@ -177,7 +177,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, @@ -216,7 +216,7 @@ func TestHandleDataUp(t *testing.T) { devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: new(core.Metadata), @@ -258,7 +258,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataUpHandlerReq{ Payload: nil, Metadata: new(core.Metadata), @@ -300,7 +300,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: nil, @@ -342,7 +342,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: new(core.Metadata), @@ -384,7 +384,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: new(core.Metadata), @@ -433,7 +433,7 @@ func TestHandleDataUp(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -498,7 +498,7 @@ func TestHandleDataUp(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -608,7 +608,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -712,7 +712,7 @@ func TestHandleDataUp(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -821,7 +821,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() appAdapter.Failures["HandleData"] = fmt.Errorf("Mock Error") - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -887,7 +887,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.Failures["dequeue"] = errors.New(errors.Operational, "Mock Error") appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -945,7 +945,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload1, fcnt1 := []byte("Payload1"), uint32(14) devAddr1, appSKey1 := lorawan.DevAddr([4]byte{1, 2, 3, 4}), [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} payload2, fcnt2 := []byte("Payload2"), uint32(35346) @@ -1072,7 +1072,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -1179,7 +1179,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -1261,13 +1261,13 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() devStorage.OutRead.Entry = devEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr *string @@ -1307,7 +1307,7 @@ func TestHandleJoin(t *testing.T) { joinaccept := lorawan.NewPHYPayload(false) err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InUpsert.Entry.AppKey)) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) CheckErrors(t, nil, err) Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") } @@ -1338,14 +1338,14 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() devStorage.OutRead.Entry = devEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() appAdapter.Failures["HandleJoin"] = errors.New(errors.Operational, "Mock Error") - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr *string @@ -1385,7 +1385,7 @@ func TestHandleJoin(t *testing.T) { joinaccept := lorawan.NewPHYPayload(false) err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InUpsert.Entry.AppKey)) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) CheckErrors(t, nil, err) Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") } @@ -1416,14 +1416,14 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() devStorage.OutRead.Entry = devEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, } devStorage.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrOperational @@ -1472,13 +1472,13 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() devStorage.OutRead.Entry = devEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrOperational @@ -1527,13 +1527,13 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() devStorage.OutRead.Entry = devEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrStructural @@ -1584,7 +1584,7 @@ func TestHandleJoin(t *testing.T) { devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrNotFound @@ -1634,7 +1634,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrStructural @@ -1684,7 +1684,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrStructural @@ -1734,7 +1734,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrStructural @@ -1773,7 +1773,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr = ErrStructural @@ -1839,13 +1839,13 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() devStorage.OutRead.Entry = devEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBrokerClient() + broker := mocks.NewBroker() // Expect var wantErr1 *string @@ -1903,7 +1903,7 @@ func TestHandleJoin(t *testing.T) { joinaccept := lorawan.NewPHYPayload(false) err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(devStorage.InUpsert.Entry.AppKey)) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) CheckErrors(t, nil, err) Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") ok = true diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 112f714fc..0bd0b7ee4 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -117,8 +117,8 @@ func (m *HandlerClient) HandleDataDown(ctx context.Context, in *core.DataDownHan return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] } -// BrokerClient mocks the core.BrokerClient interface -type BrokerClient struct { +// Broker mocks the core.Broker interface +type Broker struct { Failures map[string]error InHandleData struct { Ctx context.Context @@ -136,31 +136,96 @@ type BrokerClient struct { OutHandleJoin struct { Res *core.JoinBrokerRes } + InUpsertABP struct { + Ctx context.Context + Req *core.UpsertABPBrokerReq + Opts []grpc.CallOption + } + OutUpsertABP struct { + Res *core.UpsertABPBrokerRes + } + InValidateOTAA struct { + Ctx context.Context + Req *core.ValidateOTAABrokerReq + Opts []grpc.CallOption + } + OutValidateOTAA struct { + Res *core.ValidateOTAABrokerRes + } + InListDevices struct { + Ctx context.Context + Req *core.ListDevicesBrokerReq + Opts []grpc.CallOption + } + OutListDevices struct { + Res *core.ListDevicesBrokerRes + } + InBeginToken struct { + Token string + } + InEndToken struct { + Called bool + } } -// NewBrokerClient creates a new mock BrokerClient -func NewBrokerClient() *BrokerClient { - return &BrokerClient{ +// NewBroker creates a new mock Broker +func NewBroker() *Broker { + return &Broker{ Failures: make(map[string]error), } } -// HandleData implements the core.BrokerClient interface -func (m *BrokerClient) HandleData(ctx context.Context, in *core.DataBrokerReq, opts ...grpc.CallOption) (*core.DataBrokerRes, error) { +// HandleData implements the core.Broker interface +func (m *Broker) HandleData(ctx context.Context, in *core.DataBrokerReq, opts ...grpc.CallOption) (*core.DataBrokerRes, error) { m.InHandleData.Ctx = ctx m.InHandleData.Req = in m.InHandleData.Opts = opts return m.OutHandleData.Res, m.Failures["HandleData"] } -// HandleJoin implements the core.BrokerClient interface -func (m *BrokerClient) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts ...grpc.CallOption) (*core.JoinBrokerRes, error) { +// HandleJoin implements the core.Broker interface +func (m *Broker) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts ...grpc.CallOption) (*core.JoinBrokerRes, error) { m.InHandleJoin.Ctx = ctx m.InHandleJoin.Req = in m.InHandleJoin.Opts = opts return m.OutHandleJoin.Res, m.Failures["HandleJoin"] } +// UpsertABP implements the core.Broker interface +func (m *Broker) UpsertABP(ctx context.Context, in *core.UpsertABPBrokerReq, opts ...grpc.CallOption) (*core.UpsertABPBrokerRes, error) { + m.InUpsertABP.Ctx = ctx + m.InUpsertABP.Req = in + m.InUpsertABP.Opts = opts + return m.OutUpsertABP.Res, m.Failures["UpsertABP"] +} + +// ValidateOTAA implements the core.Broker interface +func (m *Broker) ValidateOTAA(ctx context.Context, in *core.ValidateOTAABrokerReq, opts ...grpc.CallOption) (*core.ValidateOTAABrokerRes, error) { + m.InValidateOTAA.Ctx = ctx + m.InValidateOTAA.Req = in + m.InValidateOTAA.Opts = opts + return m.OutValidateOTAA.Res, m.Failures["ValidateOTAA"] +} + +// ListDevices implements the core.Broker interface +func (m *Broker) ListDevices(ctx context.Context, in *core.ListDevicesBrokerReq, opts ...grpc.CallOption) (*core.ListDevicesBrokerRes, error) { + m.InListDevices.Ctx = ctx + m.InListDevices.Req = in + m.InListDevices.Opts = opts + return m.OutListDevices.Res, m.Failures["ListDevices"] +} + +// BeginToken implements the core.Broker interface +func (m *Broker) BeginToken(token string) core.Broker { + m.InBeginToken.Token = token + return m +} + +// EndToken implements the core.Broker interface +func (m *Broker) EndToken() { + m.InEndToken.Called = true +} + // RouterServer mocks the core.RouterServer interface type RouterServer struct { Failures map[string]error From 86e360c1fc4e16239d7d22b664235ff3d919553e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 21:15:51 +0100 Subject: [PATCH 1162/2266] [feature/manager] Implement BrokerManagerServer in Broker (Still need to implement ListDevices) --- core/broker_manager.pb.go | 84 +++++++++++++++++------ core/components/broker/appStorage.go | 16 +---- core/components/broker/appStorage_test.go | 24 +++---- core/components/broker/broker.go | 12 ---- core/components/broker/brokerManager.go | 83 ++++++++++++++++++++++ core/components/broker/brokerToken.go | 15 ++++ core/components/handler/handlerManager.go | 7 +- core/protos/broker_manager.proto | 5 +- 8 files changed, 177 insertions(+), 69 deletions(-) create mode 100644 core/components/broker/brokerManager.go create mode 100644 core/components/broker/brokerToken.go diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index 1df14dfd0..e99df5575 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -107,9 +107,10 @@ func (*ValidateOTAABrokerRes) Descriptor() ([]byte, []int) { } type UpsertABPBrokerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + NetAddress string `protobuf:"bytes,2,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` + DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` } func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } @@ -363,9 +364,15 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.AppEUI) } } + if len(m.NetAddress) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) + i += copy(data[i:], m.NetAddress) + } if m.DevAddr != nil { if len(m.DevAddr) > 0 { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) i += copy(data[i:], m.DevAddr) @@ -373,7 +380,7 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { } if m.NwkSKey != nil { if len(m.NwkSKey) > 0 { - data[i] = 0x1a + data[i] = 0x22 i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) @@ -556,6 +563,10 @@ func (m *UpsertABPBrokerReq) Size() (n int) { n += 1 + l + sovBrokerManager(uint64(l)) } } + l = len(m.NetAddress) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } if m.DevAddr != nil { l = len(m.DevAddr) if l > 0 { @@ -859,6 +870,35 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { } iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetAddress = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } @@ -889,7 +929,7 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { m.DevAddr = []byte{} } iNdEx = postIndex - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -1402,26 +1442,26 @@ var ( ) var fileDescriptorBrokerManager = []byte{ - // 323 bytes of a gzipped FileDescriptorProto + // 332 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0xf2, 0xe7, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, 0x2c, 0x49, 0xf5, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, 0x73, 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x62, 0x4b, 0x04, 0xf3, 0x84, 0xe4, 0xb8, 0xb8, 0xfc, 0x52, 0x4b, 0x1c, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0x25, - 0x98, 0x80, 0x72, 0x9c, 0x41, 0x5c, 0x79, 0x70, 0x11, 0x25, 0x71, 0xec, 0x06, 0x16, 0x2b, 0x25, - 0x70, 0x09, 0x85, 0x16, 0x14, 0xa7, 0x16, 0x95, 0x38, 0x3a, 0x05, 0x10, 0xb6, 0x46, 0x82, 0x8b, - 0xdd, 0x25, 0xb5, 0x0c, 0x64, 0x28, 0xd8, 0x0e, 0x9e, 0x20, 0xf6, 0x14, 0x08, 0x17, 0x24, 0xe3, - 0x57, 0x9e, 0x1d, 0xec, 0x9d, 0x5a, 0x29, 0xc1, 0x0c, 0x91, 0xc9, 0x83, 0x70, 0x95, 0x44, 0xb0, - 0xd8, 0x50, 0xac, 0x14, 0xc5, 0xc5, 0x03, 0xe1, 0x00, 0xcd, 0xcb, 0x4c, 0x4e, 0x05, 0xd9, 0x08, - 0x64, 0x21, 0xd9, 0x98, 0x02, 0xe6, 0x91, 0x65, 0xa3, 0x1e, 0x97, 0x88, 0x4f, 0x66, 0x71, 0x09, - 0xc4, 0xe4, 0x62, 0x82, 0xbe, 0x52, 0x72, 0xc1, 0xaa, 0xbe, 0x58, 0x48, 0x07, 0x6c, 0x37, 0x48, - 0x0c, 0xa8, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x48, 0x0f, 0x14, 0x3b, 0x7a, 0xc8, 0x0e, 0x07, 0xbb, - 0x07, 0xa4, 0xc4, 0xe8, 0x21, 0x23, 0x17, 0x2f, 0x44, 0xc6, 0x17, 0x12, 0xa3, 0x42, 0x1e, 0x5c, - 0x3c, 0xc8, 0x81, 0x2e, 0x24, 0x0d, 0xd1, 0x8e, 0x35, 0x66, 0xa5, 0xf0, 0x48, 0x16, 0x0b, 0xd9, - 0x73, 0x71, 0xc2, 0xc3, 0x50, 0x48, 0x02, 0xa2, 0x12, 0x33, 0xda, 0xa4, 0x70, 0xc9, 0x14, 0x0b, - 0xb9, 0x72, 0x71, 0x23, 0x79, 0x51, 0x48, 0x0a, 0xa2, 0x10, 0x5b, 0x28, 0x49, 0xe1, 0x96, 0x2b, - 0x76, 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0x02, 0x10, 0x3f, 0x00, 0xe2, 0x19, 0x8f, 0xe5, 0x18, - 0x92, 0xd8, 0xc0, 0xc9, 0xd6, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0xa5, 0x46, 0xfb, 0x03, 0xce, - 0x02, 0x00, 0x00, + 0x98, 0x80, 0x72, 0x9c, 0x41, 0x5c, 0x79, 0x70, 0x11, 0x25, 0x71, 0xec, 0x06, 0x16, 0x2b, 0x35, + 0x30, 0x72, 0x09, 0x85, 0x16, 0x14, 0xa7, 0x16, 0x95, 0x38, 0x3a, 0x05, 0x50, 0x6c, 0x8f, 0x90, + 0x04, 0x17, 0xbb, 0x4b, 0x6a, 0x19, 0x88, 0x27, 0xc1, 0x0c, 0xd6, 0xc8, 0x9e, 0x02, 0xe1, 0x82, + 0x64, 0xfc, 0xca, 0xb3, 0x83, 0xbd, 0x53, 0x2b, 0x25, 0x58, 0x20, 0x32, 0x79, 0x10, 0xae, 0x92, + 0x08, 0x16, 0x17, 0x14, 0x2b, 0x45, 0x71, 0xf1, 0x40, 0x38, 0x40, 0xf3, 0x32, 0x93, 0x53, 0x41, + 0x2e, 0x02, 0xb2, 0x90, 0x5c, 0x94, 0x02, 0xe6, 0x21, 0xdb, 0xc8, 0x84, 0xd3, 0x46, 0x66, 0x54, + 0x1b, 0xf5, 0xb8, 0x44, 0x7c, 0x32, 0x8b, 0x4b, 0x20, 0x26, 0x17, 0x13, 0xf4, 0xb5, 0x92, 0x0b, + 0x56, 0xf5, 0xc5, 0x42, 0x3a, 0x60, 0xbb, 0x41, 0x62, 0x40, 0x0d, 0xcc, 0x1a, 0xdc, 0x46, 0x42, + 0x7a, 0xa0, 0xe8, 0xd3, 0x43, 0x76, 0x38, 0xd8, 0x3d, 0x20, 0x25, 0x46, 0x0f, 0x19, 0xb9, 0x78, + 0x21, 0x32, 0xbe, 0x90, 0x28, 0x17, 0xf2, 0xe0, 0xe2, 0x41, 0x8e, 0x15, 0x21, 0x69, 0x88, 0x76, + 0xac, 0x51, 0x2f, 0x85, 0x47, 0xb2, 0x58, 0xc8, 0x9e, 0x8b, 0x13, 0x1e, 0x86, 0x42, 0x12, 0x10, + 0x95, 0x98, 0xd1, 0x2a, 0x85, 0x4b, 0xa6, 0x58, 0xc8, 0x95, 0x8b, 0x1b, 0xc9, 0x8b, 0x42, 0x52, + 0x10, 0x85, 0xd8, 0x42, 0x49, 0x0a, 0xb7, 0x5c, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, + 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x4e, 0xd7, 0xc6, 0x80, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x39, 0x26, 0x0a, 0x66, 0xef, 0x02, 0x00, 0x00, } diff --git a/core/components/broker/appStorage.go b/core/components/broker/appStorage.go index d71a4d38e..a91ad7fd8 100644 --- a/core/components/broker/appStorage.go +++ b/core/components/broker/appStorage.go @@ -21,10 +21,8 @@ type AppStorage interface { } type appEntry struct { - Dialer Dialer - AppEUI []byte - Password []byte - Salt []byte + Dialer Dialer + AppEUI []byte } type appStorage struct { @@ -68,8 +66,6 @@ func (s *appStorage) done() error { func (e appEntry) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) rw.Write(e.AppEUI) - rw.Write(e.Password) - rw.Write(e.Salt) rw.Write(e.Dialer.MarshalSafely()) return rw.Bytes() } @@ -81,14 +77,6 @@ func (e *appEntry) UnmarshalBinary(data []byte) error { e.AppEUI = make([]byte, len(data)) copy(e.AppEUI, data) }) - rw.Read(func(data []byte) { - e.Password = make([]byte, len(data)) - copy(e.Password, data) - }) - rw.Read(func(data []byte) { - e.Salt = make([]byte, len(data)) - copy(e.Salt, data) - }) rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) diff --git a/core/components/broker/appStorage_test.go b/core/components/broker/appStorage_test.go index ddc37fbd9..d6aa10654 100644 --- a/core/components/broker/appStorage_test.go +++ b/core/components/broker/appStorage_test.go @@ -35,10 +35,8 @@ func TestReadStore(t *testing.T) { // Build entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 2}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 2}, } // Operate @@ -73,10 +71,8 @@ func TestReadStore(t *testing.T) { // Build entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 1}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 1}, } // Operate @@ -98,16 +94,12 @@ func TestReadStore(t *testing.T) { // Build entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 4}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 4}, } update := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 4}, - Password: []byte{3, 4}, - Salt: []byte{5, 6}, + Dialer: NewDialer([]byte("dialer")), + AppEUI: []byte{0, 4}, } // Operate diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 263701bac..3f1cbf4e2 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -314,15 +314,3 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c Metadata: resp.Metadata, }, nil } - -func (b component) CreateApp(context.Context, *core.BrokerCreateAppReq) (*core.BrokerUpsertAppRes, error) { - return nil, errors.New(errors.Structural, "Not implemented") -} - -func (b component) UpdateApp(context.Context, *core.BrokerUpdateAppReq) (*core.BrokerUpsertAppRes, error) { - return nil, errors.New(errors.Structural, "Not implemented") -} - -func (b component) UpsertDeviceRegistration(context.Context, *core.BrokerUpsertDeviceReq) (*core.BrokerUpsertDeviceRes, error) { - return nil, errors.New(errors.Structural, "Not implemented") -} diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go new file mode 100644 index 000000000..6a4510145 --- /dev/null +++ b/core/components/broker/brokerManager.go @@ -0,0 +1,83 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "regexp" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" +) + +// ListDevices implements the core.BrokerManagerServer interface +func (b component) ListDevices(bctx context.Context, req *core.ListDevicesBrokerReq) (*core.ListDevicesBrokerRes, error) { + return new(core.ListDevicesBrokerRes), errors.New(errors.Implementation, "Not implemented") +} + +// ValidateOTAA implements the core.BrokerManager interface +func (b component) ValidateOTAA(bctx context.Context, req *core.ValidateOTAABrokerReq) (*core.ValidateOTAABrokerRes, error) { + b.Ctx.Debug("Handle ValidateOTAA request") + + // 1. Validate the request + re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") + if len(req.AppEUI) != 8 || !re.Match([]byte(req.NetAddress)) { + err := errors.New(errors.Structural, "Invalid request parameters") + b.Ctx.WithError(err).Debug("Unable to validate OTAA request") + return new(core.ValidateOTAABrokerRes), err + } + + // 2. Verify and validate the token + // TODO + + // 3. Update the internal storage + b.Ctx.WithField("AppEUI", req.AppEUI).Debug("Request accepted by broker. Registering / Updating App.") + err := b.AppStorage.upsert(appEntry{ + Dialer: NewDialer([]byte(req.NetAddress)), + AppEUI: req.AppEUI, + }) + if err != nil { + b.Ctx.WithError(err).Debug("Error while trying to save valid request") + return new(core.ValidateOTAABrokerRes), errors.New(errors.Operational, err) + } + + // 4. Done. + return new(core.ValidateOTAABrokerRes), nil +} + +// UpsertABP implements the core.BrokerManager interface +func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) (*core.UpsertABPBrokerRes, error) { + b.Ctx.Debug("Handle ValidateOTAA request") + + // 1. Validate the request + re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") + if len(req.AppEUI) != 8 || !re.Match([]byte(req.NetAddress)) || len(req.DevAddr) != 4 || len(req.NwkSKey) != 16 { + err := errors.New(errors.Structural, "Invalid request parameters") + b.Ctx.WithError(err).Debug("Unable to proceed Upsert ABP request") + return new(core.UpsertABPBrokerRes), err + } + + // 2. Verify and validate the token + // TODO + + // 3. Update the internal storage + b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering device.") + var nwkSKey [16]byte + copy(nwkSKey[:], req.NwkSKey) + err := b.NetworkController.upsert(devEntry{ + Dialer: NewDialer([]byte(req.NetAddress)), + AppEUI: req.AppEUI, + DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), + DevAddr: req.DevAddr, + NwkSKey: nwkSKey, + FCntUp: 0, + }) + if err != nil { + b.Ctx.WithError(err).Debug("Error while trying to save valid request") + return new(core.UpsertABPBrokerRes), errors.New(errors.Operational, err) + } + + // 4. Done. + return new(core.UpsertABPBrokerRes), nil +} diff --git a/core/components/broker/brokerToken.go b/core/components/broker/brokerToken.go new file mode 100644 index 000000000..bb9ab07a4 --- /dev/null +++ b/core/components/broker/brokerToken.go @@ -0,0 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +// BeginToken implements the core.Broker interface +func (b *component) BeginToken(token string) Interface { + // TODO - Acquire Mutex, Mutate Token + return b +} + +// EndToken implements the core.Broker interface +func (b *component) EndToken() { + // TODO - Release Mutex +} diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 5cb435538..549c1862e 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -27,9 +27,10 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq // 2. Forward to the broker firt -> The Broker also does the token verification _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ - AppEUI: req.AppEUI, - DevAddr: req.DevAddr, - NwkSKey: req.NwkSKey, + AppEUI: req.AppEUI, + DevAddr: req.DevAddr, + NwkSKey: req.NwkSKey, + NetAddress: h.NetAddr, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index 37a5d8710..53188d609 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -11,8 +11,9 @@ message ValidateOTAABrokerRes{} message UpsertABPBrokerReq { bytes AppEUI = 1; - bytes DevAddr = 2; - bytes NwkSKey = 3; + string NetAddress = 2; + bytes DevAddr = 3; + bytes NwkSKey = 4; } message UpsertABPBrokerRes {} From 3d8b95e19ae149da7cd065ca1f488b00dea638bc Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 21:28:50 +0100 Subject: [PATCH 1163/2266] [feature/manager] Add incomplete but working brokerClient --- cmd/handler.go | 9 +- core/components/broker/brokerToken.go | 55 +- coverage.out | 882 ++++++++++++++++++++++++++ 3 files changed, 936 insertions(+), 10 deletions(-) create mode 100644 coverage.out diff --git a/cmd/handler.go b/cmd/handler.go index d508e6ecc..2c2cc651c 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -9,15 +9,14 @@ import ( "strings" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" - "google.golang.org/grpc" ) // handlerCmd represents the handler command @@ -102,12 +101,10 @@ The default handler is the bridge between The Things Network and applications. } // BrokerClient - brokerConn, err := grpc.Dial(viper.GetString("handler.ttn-broker"), grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) + brokerClient, err := broker.NewClient(viper.GetString("handler.ttn-broker")) if err != nil { ctx.WithError(err).Fatal("Could not dial broker") } - defer brokerConn.Close() - broker := core.NewBrokerClient(brokerConn) // MQTT Client & adapter mqttClient, chmsg, err := mqtt.NewClient( @@ -129,7 +126,7 @@ The default handler is the bridge between The Things Network and applications. Ctx: ctx, DevStorage: devicesDB, PktStorage: packetsDB, - Broker: broker, + Broker: brokerClient, AppAdapter: appAdapter, }, handler.Options{ diff --git a/core/components/broker/brokerToken.go b/core/components/broker/brokerToken.go index bb9ab07a4..3d9d9e5fb 100644 --- a/core/components/broker/brokerToken.go +++ b/core/components/broker/brokerToken.go @@ -3,13 +3,60 @@ package broker +import ( + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +type brokerClient struct { + sync.Mutex + *tokenCredentials + core.BrokerClient + core.BrokerManagerClient +} + +// NewClient instantiates a new core.Broker client +func NewClient(netAddr string) (core.Broker, error) { + brokerConn, err := grpc.Dial(netAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) // Add Credentials Token + if err != nil { + return nil, err + } + broker := core.NewBrokerClient(brokerConn) + brokerManager := core.NewBrokerManagerClient(brokerConn) + return &brokerClient{ + BrokerClient: broker, + BrokerManagerClient: brokerManager, + }, nil +} + // BeginToken implements the core.Broker interface -func (b *component) BeginToken(token string) Interface { - // TODO - Acquire Mutex, Mutate Token +func (b *brokerClient) BeginToken(token string) core.Broker { + b.Lock() + b.token = token return b } // EndToken implements the core.Broker interface -func (b *component) EndToken() { - // TODO - Release Mutex +func (b *brokerClient) EndToken() { + b.Unlock() +} + +type tokenCredentials struct { + token string +} + +// GetRequestMetadata implements the grpc/credentials.Credentials interface +func (t tokenCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "token": t.token, + }, nil +} + +// RequireTransportSecurity implements the grpc/credentials.Credentials interface +func (t tokenCredentials) RequireTransportSecurity() bool { + return false // TODO -> True, Need use of TLS to communicate the token } diff --git a/coverage.out b/coverage.out new file mode 100644 index 000000000..14a436231 --- /dev/null +++ b/coverage.out @@ -0,0 +1,882 @@ +mode: set +github.com/TheThingsNetwork/ttn/core/storage/storage.go:99.37,101.3 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:98.2,99.37 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:94.16,96.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:93.109,94.16 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:89.2,89.35 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:87.3,87.16 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:83.68,85.5 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:82.18,83.68 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:80.31,82.18 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:73.2,80.31 4 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:70.22,72.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:69.69,70.22 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:63.2,63.12 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:60.9,62.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:59.2,60.9 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:56.16,58.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:55.33,56.16 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:51.2,51.23 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:48.16,50.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:46.42,48.16 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:276.2,276.12 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:273.39,275.3 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:272.32,273.39 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:267.3,267.13 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:264.65,266.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:264.3,264.65 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:261.51,263.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:261.3,261.51 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:256.18,258.5 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:254.4,256.18 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:252.21,254.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:244.57,252.21 3 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:244.2,244.57 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:240.23,242.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:239.55,240.23 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:234.3,234.13 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:231.44,233.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:231.3,231.44 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:228.17,230.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:226.57,228.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:225.62,226.57 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:220.3,220.13 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:217.47,219.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:217.3,217.47 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:214.17,216.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:213.3,214.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:210.32,212.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:209.3,210.32 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:206.44,208.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:206.3,206.44 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:203.17,205.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:201.57,203.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:201.2,201.57 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:198.3,198.37 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:195.17,197.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:193.32,195.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:191.2,193.32 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:188.16,190.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:187.98,188.16 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:183.2,183.23 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:180.3,180.13 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:177.47,179.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:177.3,177.47 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:174.17,176.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:173.3,174.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:170.32,172.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:169.3,170.32 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:166.17,168.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:164.47,166.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:164.2,164.47 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:161.3,161.37 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:158.17,160.4 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:156.32,158.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:154.2,156.32 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:151.16,153.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:150.98,151.16 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:146.2,146.33 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:143.2,143.13 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:143.13,145.3 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:140.4,140.51 1 0 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:137.50,138.10 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:135.32,137.50 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:135.3,135.32 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:129.28,134.4 4 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:128.6,129.28 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:125.2,128.6 4 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:120.2,120.16 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:120.16,122.3 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:117.3,117.13 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:114.22,116.4 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:113.3,114.22 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:111.4,111.14 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:108.59,110.5 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:107.17,108.59 1 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:105.45,107.17 2 1 +github.com/TheThingsNetwork/ttn/core/storage/storage.go:104.2,105.45 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:99.2,99.16 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:99.16,101.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:94.2,95.11 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:92.2,93.16 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:90.2,91.16 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:88.2,89.16 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:83.59,87.14 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:80.2,80.12 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:77.2,77.21 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:77.21,79.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:74.49,76.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:73.51,74.49 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:68.2,68.10 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:64.23,66.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:63.2,64.23 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:59.23,61.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:54.81,59.23 4 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:49.2,49.52 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:45.16,47.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:43.68,45.16 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:110.2,112.14 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:103.3,103.23 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:103.23,105.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:101.3,101.23 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:101.23,103.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:97.2,97.63 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:93.33,95.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:93.2,93.33 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:88.36,90.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:88.2,88.36 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:83.35,85.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:83.2,83.35 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:78.35,80.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:78.2,78.35 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:73.35,75.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:71.48,73.35 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:301.2,301.12 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:298.48,300.3 1 0 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:297.56,298.48 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:293.2,293.18 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:290.16,292.3 1 0 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:288.52,290.16 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:278.2,278.29 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:274.2,274.15 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:274.15,276.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:270.2,270.15 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:270.15,272.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:266.17,268.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:265.39,266.17 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:258.2,261.20 3 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:254.23,256.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:250.55,254.23 3 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:243.2,246.59 3 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:238.41,240.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:236.2,238.41 3 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:231.16,233.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:230.2,231.16 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:226.2,227.58 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:224.2,225.17 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:222.2,223.17 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:220.2,221.17 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:218.2,219.17 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:214.79,217.14 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:208.37,210.2 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:204.2,204.20 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:196.35,201.4 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:195.35,196.35 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:191.2,195.35 3 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:188.16,190.3 1 0 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:182.57,188.16 4 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:175.2,175.68 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:171.3,173.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:168.36,171.3 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:168.2,168.36 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:163.3,165.3 1 0 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:158.59,163.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:158.3,158.59 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:156.16,158.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:151.2,156.16 5 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:146.16,148.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:145.2,146.16 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:140.16,142.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:138.100,140.16 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:126.2,130.8 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:122.16,124.3 1 0 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:121.2,122.16 2 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:116.22,118.3 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:116.2,116.22 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:112.2,113.72 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:104.2,111.4 1 1 +github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:101.92,103.11 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:98.51,100.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:93.35,95.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:85.51,90.2 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:81.2,81.21 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:78.2,78.23 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:78.23,80.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:75.3,75.21 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:72.69,74.4 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:72.3,72.69 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:64.33,69.5 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:63.29,64.33 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:59.24,63.29 3 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:57.2,59.24 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:54.16,56.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:50.63,54.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:46.2,46.54 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:43.16,45.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:41.74,43.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:119.2,119.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:118.37,118.77 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:118.2,118.37 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:114.28,117.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:114.2,114.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:113.28,113.77 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:111.55,113.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:103.2,107.19 5 1 +github.com/TheThingsNetwork/ttn/core/components/router/storage.go:100.16,102.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:97.16,99.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:96.2,97.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:90.126,93.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:89.120,90.126 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:81.2,85.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:77.2,77.25 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:77.25,79.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:73.29,75.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:73.2,73.29 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:69.16,71.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:68.97,69.16 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:64.2,64.12 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:61.43,63.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:58.2,61.43 3 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:54.16,56.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:52.34,54.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:47.45,49.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:371.2,371.27 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:367.17,369.4 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:363.3,367.17 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:360.3,361.57 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:358.3,359.71 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:355.17,357.21 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:353.2,355.17 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:349.2,349.22 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:349.22,351.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:344.38,347.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:344.2,344.38 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:339.37,342.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:339.2,339.37 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:334.2,334.21 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:334.21,337.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:327.4,330.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:324.53,327.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:323.25,324.53 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:315.2,323.25 8 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:307.4,310.18 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:302.5,303.11 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:298.63,301.6 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:296.4,296.18 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:296.18,298.63 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:290.4,292.11 2 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:288.4,289.83 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:286.4,287.83 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:279.51,285.22 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:278.33,279.51 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:261.111,278.33 9 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:258.2,258.12 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:254.80,257.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:250.2,254.80 5 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:245.21,249.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:241.80,245.21 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:234.2,237.23 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:229.16,232.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:228.2,229.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:223.16,226.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:222.2,223.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:214.54,219.3 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:212.95,214.54 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:209.2,209.79 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:204.66,206.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:204.2,204.66 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:198.38,201.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:197.2,198.38 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:194.3,194.38 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:191.3,192.56 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:189.3,190.61 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:187.2,187.16 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:187.16,188.38 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:184.3,184.39 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:179.67,183.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:178.3,179.67 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:175.29,177.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:170.3,175.29 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:165.21,170.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:158.2,165.21 5 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:154.66,157.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:153.2,154.66 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:148.16,150.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:147.2,148.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:141.29,144.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:141.2,141.29 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:137.25,140.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:137.2,137.25 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:133.16,136.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:126.106,133.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:122.2,122.79 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:119.66,121.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:119.2,119.66 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:115.38,118.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:114.2,115.38 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:109.16,111.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/router.go:102.2,109.16 3 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:81.2,81.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:80.37,80.80 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:80.2,80.37 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:76.28,79.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:74.55,76.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:67.2,70.19 4 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:64.16,66.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:62.51,64.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:57.35,59.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:52.51,54.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:48.2,48.33 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:45.16,47.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:43.57,45.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:39.2,39.34 1 1 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:36.16,38.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:34.53,36.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:97.2,100.23 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:94.16,96.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:90.78,94.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:86.2,86.88 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:80.33,83.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:80.2,80.33 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:77.16,79.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:76.2,77.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:73.66,75.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:69.52,73.66 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:65.2,65.19 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:62.32,64.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:59.69,62.32 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:56.2,56.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:52.23,54.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:51.28,52.23 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:48.51,51.28 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:45.2,45.46 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:42.16,44.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:40.64,42.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:169.2,169.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:168.37,168.75 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:168.2,168.37 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:164.28,167.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:164.2,164.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:160.28,163.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:160.2,160.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:156.28,159.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:154.55,156.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:145.2,150.19 6 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:142.16,144.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:140.51,142.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:135.35,137.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:131.2,131.24 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:128.22,130.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:127.2,128.22 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:124.16,126.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:120.75,124.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:116.2,116.18 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:113.63,115.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:112.2,113.63 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:108.3,108.79 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:106.4,106.50 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:103.31,105.5 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:101.36,103.31 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:100.23,101.36 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:86.45,109.2 14 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:705.2,705.10 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:699.11,704.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:690.115,699.11 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:680.2,686.8 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:676.16,678.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:675.2,676.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:672.84,674.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:672.2,672.84 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:669.66,671.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:650.159,669.66 8 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:622.2,647.8 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:618.16,620.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:610.2,618.16 5 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:606.16,608.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:605.2,606.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:601.68,603.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:591.133,601.68 7 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:584.33,586.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:581.62,584.33 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:574.4,576.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:573.4,573.29 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:569.18,572.5 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:567.4,569.18 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:562.18,565.5 2 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:558.75,562.18 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:557.33,558.75 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:557.2,557.33 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:551.66,554.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:551.2,551.66 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:548.17,550.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:541.2,548.17 6 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:536.16,539.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:530.2,536.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:524.3,525.56 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:518.4,521.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:516.4,517.47 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:512.18,515.5 2 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:501.13,512.18 5 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:496.33,501.13 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:496.2,496.33 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:491.16,494.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:484.97,491.16 6 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:477.4,479.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:475.19,477.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:474.33,475.19 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:474.2,474.33 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:470.16,472.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:461.2,470.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:456.16,460.3 3 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:455.2,456.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:448.16,452.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:432.2,448.16 7 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:427.43,430.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:411.2,427.43 13 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:407.17,410.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:405.2,407.17 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:398.33,402.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:397.2,398.33 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:391.16,395.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:384.114,391.16 5 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:376.3,378.81 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:373.3,375.65 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:371.3,372.26 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:368.23,369.26 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:366.32,368.23 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:361.61,366.32 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:354.72,357.2 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:348.4,348.27 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:344.25,347.5 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:343.4,344.25 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:339.5,339.13 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:336.23,338.6 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:334.52,336.23 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:328.3,334.52 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:327.4,327.70 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:326.30,326.54 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:315.3,326.30 6 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:313.6,314.10 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:302.79,313.6 6 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:293.2,296.41 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:289.2,292.50 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:284.2,288.44 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:255.2,283.21 12 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:250.29,252.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:250.2,250.29 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:247.16,249.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:242.2,247.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:238.25,241.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:238.2,238.25 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:234.26,237.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:234.2,234.26 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:230.26,233.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:230.2,230.26 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:226.27,229.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:222.115,226.27 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:212.2,218.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:207.32,210.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:206.2,207.32 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:201.27,204.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:201.2,201.27 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:196.26,199.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:196.2,196.26 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:191.26,194.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:185.121,191.26 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:178.2,180.39 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:174.2,177.48 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:170.2,173.42 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:145.2,169.21 12 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:140.16,142.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:138.2,140.16 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:132.113,135.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:128.109,132.113 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:124.2,124.12 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:121.43,123.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:118.2,121.43 3 1 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:114.16,116.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:112.35,114.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:99.2,99.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:99.28,102.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:95.2,95.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:95.28,98.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:91.2,91.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:91.28,94.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:90.2,90.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:90.28,90.74 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:89.2,89.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:89.28,89.56 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:88.28,88.56 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:88.2,88.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:87.28,87.55 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:85.55,87.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:72.51,82.2 9 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:67.35,69.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:62.51,64.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:58.2,58.24 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:55.30,57.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:54.2,55.30 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:51.16,53.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:49.75,51.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:45.2,45.34 1 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:41.16,43.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:39.53,41.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:103.2,103.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:58.40,60.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:54.2,54.61 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:51.16,53.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:49.60,51.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:44.39,46.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:40.2,40.12 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:37.39,39.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:36.31,37.39 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:97.2,101.28 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:95.3,95.84 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:92.53,94.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:91.16,92.53 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:87.52,91.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:83.2,83.39 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:80.2,80.25 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:80.25,82.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:77.3,79.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:75.15,77.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:71.83,75.15 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:67.2,67.34 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:64.16,66.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:62.63,64.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:58.2,58.34 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:54.16,56.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:52.67,54.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:201.2,201.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:195.31,199.4 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:193.28,195.31 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:193.2,193.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:189.28,192.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:189.2,189.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:185.28,188.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:183.58,185.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:178.2,179.19 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:175.32,177.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:170.54,175.32 5 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:166.2,166.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:165.28,165.58 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:165.2,165.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:164.28,164.72 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:164.2,164.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:163.28,163.56 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:163.2,163.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:159.28,162.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:159.2,159.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:155.28,158.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:155.2,155.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:151.28,154.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:149.55,151.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:137.51,146.2 8 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:132.35,134.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:127.60,129.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:123.2,123.36 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:120.16,122.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:118.84,120.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:114.2,114.59 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:111.2,111.15 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:111.15,113.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:107.4,109.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:104.103,107.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:101.28,104.103 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:99.16,100.53 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:98.2,99.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:94.16,97.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:90.2,94.16 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:86.113,89.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:82.107,86.113 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:78.2,78.12 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:75.32,77.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:75.2,75.32 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:71.2,71.12 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:71.12,73.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:67.12,69.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:62.2,67.12 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:58.16,60.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:57.2,58.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:53.16,55.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:51.34,53.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:46.45,48.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:308.2,314.8 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:304.82,307.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:303.2,304.82 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:299.16,302.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:297.2,299.16 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:291.40,294.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:287.2,291.40 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:282.16,286.3 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:272.2,282.16 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:268.16,271.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:267.2,268.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:261.60,264.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:260.2,261.60 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:250.2,250.19 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:250.19,255.3 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:242.9,246.9 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:242.3,242.9 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:238.10,240.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:237.3,238.10 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:234.17,235.12 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:233.3,234.17 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:229.17,230.12 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:222.32,229.17 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:210.2,222.32 5 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:208.3,208.38 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:205.3,206.38 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:202.3,204.40 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:199.16,201.38 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:194.2,199.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:190.16,193.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:183.107,190.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:176.2,179.8 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:172.16,175.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:162.2,172.16 4 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:157.16,160.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:156.2,157.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:153.50,155.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:152.2,153.50 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:146.53,149.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:146.2,146.53 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:141.38,144.3 2 0 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:141.2,141.38 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:135.16,138.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:128.2,135.16 3 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:124.16,127.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:123.2,124.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:117.2,117.11 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:117.11,120.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:112.57,114.9 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:111.37,112.57 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:110.2,111.37 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:104.3,107.4 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:100.53,103.4 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:95.2,95.17 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:92.2,92.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:92.28,94.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:88.28,91.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:88.2,88.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:84.28,87.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:84.2,84.28 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:80.28,83.3 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:78.55,80.28 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:68.51,75.2 6 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:63.35,65.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:58.51,60.2 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:54.2,54.24 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:51.30,53.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:50.2,51.30 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:47.16,49.3 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:45.60,47.16 2 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:41.2,41.34 1 1 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:37.16,39.3 1 0 +github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:35.53,37.16 2 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:62.48,64.2 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:58.2,58.73 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:54.36,56.3 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:54.2,54.36 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:51.20,53.3 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:49.60,51.20 2 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:45.2,45.33 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:42.20,44.3 1 1 +github.com/TheThingsNetwork/ttn/core/api/topics.go:40.52,42.20 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:97.19,99.11 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:93.82,97.19 4 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:92.19,93.82 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:88.6,92.19 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:85.96,88.6 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:79.2,79.35 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:76.16,78.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:74.55,76.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:67.33,70.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:66.62,67.33 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:59.2,62.12 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:54.54,57.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:51.49,54.54 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:46.60,48.2 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:283.2,283.20 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:280.16,282.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:274.2,280.16 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:269.16,272.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:268.2,269.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:263.40,266.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:261.82,263.40 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:258.2,258.20 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:254.16,257.3 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:248.2,254.16 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:243.16,246.3 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:242.2,243.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:238.16,240.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:237.2,238.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:232.40,235.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:230.80,232.40 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:224.2,226.77 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:223.3,223.41 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:219.17,222.4 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:217.2,219.17 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:216.3,216.39 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:212.17,215.4 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:210.2,212.17 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:209.2,209.20 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:204.16,207.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:202.83,204.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:197.2,199.16 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:189.69,192.5 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:187.30,189.69 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:186.40,187.30 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:182.2,186.40 5 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:173.29,179.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:173.2,173.29 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:167.60,170.3 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:154.74,167.60 7 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:150.2,150.20 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:146.16,148.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:134.74,146.16 6 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:128.2,128.17 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:128.17,130.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:122.4,122.18 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:122.18,124.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:119.4,120.69 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:117.4,118.40 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:115.4,116.40 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:114.4,114.26 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:110.52,112.5 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:108.39,110.52 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:107.3,108.39 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:102.4,103.9 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:94.3,100.9 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:91.10,93.4 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:89.2,91.10 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:63.3,88.9 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:56.52,58.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:55.45,56.52 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:54.3,55.45 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:50.17,52.4 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:49.3,50.17 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:45.38,48.4 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:45.3,45.38 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:42.10,44.4 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:40.2,42.10 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:38.2,39.14 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:37.2,37.28 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:32.52,34.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:31.2,32.52 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:28.16,30.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:27.2,28.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:22.22,24.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:20.94,22.22 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:175.2,175.15 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:169.54,171.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:167.41,169.54 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:164.37,167.41 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:159.71,164.37 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:150.2,150.12 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:144.48,146.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:141.73,144.48 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:138.37,141.73 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:133.67,138.37 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:117.2,123.18 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:114.16,116.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:113.2,114.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:108.21,110.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:106.114,108.21 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:101.2,102.91 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/safeclient.go:25.33,29.2 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/safeclient.go:18.61,22.2 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:93.2,101.16 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:89.2,89.25 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:89.25,92.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:85.2,85.26 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:85.26,88.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:81.2,81.26 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:81.26,84.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:77.27,80.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:77.2,77.27 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:73.16,76.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:68.121,73.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:63.70,65.2 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:58.45,60.2 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:194.2,198.8 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:189.27,191.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:189.2,189.27 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:185.58,187.4 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:184.57,185.58 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:183.2,184.57 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:178.72,180.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:176.2,178.72 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:173.26,175.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:170.64,173.26 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:166.2,166.45 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:162.3,163.50 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:159.4,159.18 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:159.18,161.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:156.18,158.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:154.3,156.18 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:152.25,153.19 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:150.79,152.25 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:144.2,144.17 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:141.16,143.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:134.2,141.16 3 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:131.16,133.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:127.2,131.16 3 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:123.76,126.3 2 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:122.121,123.76 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:118.2,118.17 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:115.16,117.3 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:106.2,115.16 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:101.16,103.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:99.2,99.42 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:95.2,95.16 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:95.16,98.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:90.3,91.68 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:84.3,89.20 5 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:78.3,83.20 5 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:74.3,77.41 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:73.3,73.21 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:68.4,71.12 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:64.11,66.13 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:62.17,64.11 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:61.25,62.17 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:56.92,61.25 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:44.2,46.15 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:39.37,42.3 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:31.88,39.37 6 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:180.2,180.18 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:173.40,177.4 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:171.3,173.40 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:167.23,170.4 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:166.32,167.23 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:163.112,166.32 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:155.3,156.78 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:153.3,154.18 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:151.3,152.10 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:147.3,147.17 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:147.17,149.4 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:137.7,141.8 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:134.24,136.8 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:133.39,134.24 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:128.3,133.39 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:124.17,126.4 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:116.22,124.17 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:116.2,116.22 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:110.17,110.77 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:105.122,110.17 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:92.2,95.12 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:88.16,90.3 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:87.2,88.16 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:81.30,83.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:79.3,81.30 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:71.3,77.33 5 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:60.3,69.28 8 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:56.3,58.33 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:54.3,54.29 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:49.5,51.5 1 0 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:47.60,49.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:47.4,47.60 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:44.29,46.5 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:43.49,44.29 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:40.55,43.49 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:38.2,40.55 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:31.25,36.3 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:29.76,31.25 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:24.34,26.2 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:67.46,75.2 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:60.17,62.4 1 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:56.80,60.17 4 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:54.35,56.80 2 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:43.45,51.2 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/healthz.go:21.73,25.2 3 1 +github.com/TheThingsNetwork/ttn/core/adapters/http/healthz.go:16.31,18.2 1 1 From ecae88a5d4b6df2a5b15ecd0ef19f6da12debef6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 21:47:34 +0100 Subject: [PATCH 1164/2266] [feature/manager] Update ttnctl with reworked protos --- ttnctl/cmd/device.go | 79 ++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 47 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 0d7d5735f..9c3aad31b 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -42,7 +42,7 @@ var devicesCmd = &cobra.Command{ conn, manager := getHandlerManager() defer conn.Close() - res, err := manager.List(context.Background(), &core.HandlerListDevicesReq{ + res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ AppEUI: appEUI, }) @@ -50,31 +50,31 @@ var devicesCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not get device list") } - ctx.Infof("Found %d devices", len(res.Devices)) - + ctx.Infof("Found %d personalized devices (ABP)", len(res.ABP)) table := uitable.New() - table.MaxColWidth = 50 + table.MaxColWidth = 70 + table.AddRow("DevAddr", "NwkSKey", "AppSKey") + for _, device := range res.ABP { + devAddr := fmt.Sprintf("%X", device.DevAddr) + nwkSKey := fmt.Sprintf("%X", device.NwkSKey) + appSKey := fmt.Sprintf("%X", device.AppSKey) + table.AddRow(devAddr, nwkSKey, appSKey) + } + fmt.Println(table) - table.AddRow("DevEUI", "AppKey", "NwkSKey", "AppSKey") - for _, device := range res.Devices { + ctx.Infof("Found %d dynamic devices (OTAA)", len(res.OTAA)) + table = uitable.New() + table.MaxColWidth = 40 + table.AddRow("DevEUI", "DevAddr", "NwkSKey", "AppSKey", "AppKey") + for _, device := range res.OTAA { devEUI := fmt.Sprintf("%X", device.DevEUI) - - appKey := emptyCell - if a := device.GetActivated(); a != nil { - appKey = fmt.Sprintf("%X", a.AppKey) - } - - nwkSKey := emptyCell - appSKey := emptyCell - if p := device.GetPersonalized(); p != nil { - nwkSKey = fmt.Sprintf("%X", p.NwkSKey) - appSKey = fmt.Sprintf("%X", p.AppSKey) - } - - table.AddRow(devEUI, appKey, nwkSKey, appSKey) + devAddr := fmt.Sprintf("%X", device.DevAddr) + nwkSKey := fmt.Sprintf("%X", device.NwkSKey) + appSKey := fmt.Sprintf("%X", device.AppSKey) + appKey := fmt.Sprintf("%X", device.AppKey) + table.AddRow(devEUI, devAddr, nwkSKey, appSKey, appKey) } fmt.Println(table) - }, } @@ -106,29 +106,22 @@ var devicesRegisterCmd = &cobra.Command{ conn, manager := getHandlerManager() defer conn.Close() - res, err := manager.Upsert(context.Background(), &core.HandlerUpsertDeviceReq{ + res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ AppEUI: appEUI, - Device: &core.HandlerDevice{ - DevEUI: devEUI, - Data: &core.HandlerDevice_Activated{ - Activated: &core.HandlerDevice_ActivatedData{ - AppKey: appKey, - }, - }, - }, + DevEUI: devEUI, + AppKey: appKey, }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } ctx.Info("Ok") - }, } // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ - Use: "personalized [DevEUI] [NwkSKey] [AppSKey]", + Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", Short: "Create or Update ABP registrations on the Handler", Long: `Create or Update ABP registrations on the Handler`, Run: func(cmd *cobra.Command, args []string) { @@ -141,9 +134,9 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ ctx.Fatalf("Invalid AppEUI: %s", err) } - devEUI, err := util.Parse64(args[0]) + devAddr, err := util.Parse32(args[0]) if err != nil { - ctx.Fatalf("Invalid DevEUI: %s", err) + ctx.Fatalf("Invalid DevAddr: %s", err) } nwkSKey, err := util.Parse128(args[1]) @@ -159,24 +152,17 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ conn, manager := getHandlerManager() defer conn.Close() - res, err := manager.Upsert(context.Background(), &core.HandlerUpsertDeviceReq{ - AppEUI: appEUI, - Device: &core.HandlerDevice{ - DevEUI: devEUI, - Data: &core.HandlerDevice_Personalized{ - Personalized: &core.HandlerDevice_PersonalizedData{ - AppSKey: appSKey, - NwkSKey: nwkSKey, - }, - }, - }, + res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ + AppEUI: appEUI, + DevAddr: devAddr, + AppSKey: appSKey, + NwkSKey: nwkSKey, }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } ctx.Info("Ok") - }, } @@ -195,5 +181,4 @@ func init() { devicesCmd.PersistentFlags().String("app-token", "0102030405060708", "The app Token to use") viper.BindPFlag("app-token", devicesCmd.PersistentFlags().Lookup("app-token")) - } From d4bcfefdd8bca48de8f99a9a7f4c58eb8d79e68e Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 21:49:00 +0100 Subject: [PATCH 1165/2266] [feature/manager] Fix old reference to BrokerClient in router. We have to something about the naming .... --- core/components/router/router_test.go | 66 +++++++++++++-------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 2400a8afd..4d8e8f8b5 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -191,7 +191,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -246,7 +246,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -301,7 +301,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -356,7 +356,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -396,7 +396,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -451,7 +451,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -531,9 +531,9 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBrokerClient() + br1 := mocks.NewBroker() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBrokerClient() + br2 := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -609,9 +609,9 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBrokerClient() + br1 := mocks.NewBroker() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBrokerClient() + br2 := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -688,7 +688,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -757,7 +757,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -837,7 +837,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -909,9 +909,9 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBrokerClient() + br1 := mocks.NewBroker() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBrokerClient() + br2 := mocks.NewBroker() br2.Failures["HandleData"] = errors.New(errors.Operational, "Mock Error") st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -988,8 +988,8 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBrokerClient() - br2 := mocks.NewBrokerClient() + br1 := mocks.NewBroker() + br2 := mocks.NewBroker() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -1065,7 +1065,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -1146,7 +1146,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleData.Res = &core.DataBrokerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -1250,7 +1250,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleData.Res = &core.DataBrokerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -1352,7 +1352,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleData.Res = &core.DataBrokerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -1453,9 +1453,9 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBrokerClient() + br1 := mocks.NewBroker() br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBrokerClient() + br2 := mocks.NewBroker() br2.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1531,9 +1531,9 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br1 := mocks.NewBrokerClient() + br1 := mocks.NewBroker() br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBrokerClient() + br2 := mocks.NewBroker() br2.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1606,7 +1606,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1666,7 +1666,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1728,7 +1728,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1790,7 +1790,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1852,7 +1852,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1913,7 +1913,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -1979,7 +1979,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBrokerClient() + br := mocks.NewBroker() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -2039,7 +2039,7 @@ func TestStart(t *testing.T) { router := New(Components{ Ctx: GetLogger(t, "Router"), DutyManager: mocks.NewDutyManager(), - Brokers: []core.BrokerClient{mocks.NewBrokerClient()}, + Brokers: []core.BrokerClient{mocks.NewBroker()}, BrkStorage: NewMockBrkStorage(), GtwStorage: NewMockGtwStorage(), }, Options{NetAddr: "localhost:8886"}) From 2cad418119ccd7606766b5a4dca9d1aebeef0f8f Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 22:10:54 +0100 Subject: [PATCH 1166/2266] [feature/manager] Fix issues with TTL, BrokerManagerServer not registered, DevAddr missing in ABP --- core/adapters/mqtt/mqtt.go | 1 + core/components/broker/broker.go | 1 + core/components/handler/handler.go | 6 +- core/components/handler/handlerManager.go | 6 +- core/components/handler/handler_test.go | 10 +-- core/handler.pb.go | 86 +++++++++++++---------- core/msgp.go | 1 + core/msgp_gen.go | 14 +++- core/protos/handler.proto | 2 +- ttnctl/cmd/downlink.go | 3 +- 10 files changed, 78 insertions(+), 52 deletions(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 120be6952..6e968b3b7 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -193,6 +193,7 @@ func handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { // Convert it to an handler downlink return &core.DataDownHandlerReq{ Payload: req.Payload, + TTL: req.TTL, AppEUI: appEUI, DevEUI: devEUI, }, nil diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 3f1cbf4e2..ab4068009 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -62,6 +62,7 @@ func (b component) Start() error { server := grpc.NewServer() core.RegisterBrokerServer(server, b) + core.RegisterBrokerManagerServer(server, b) cherr := make(chan error) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 5a009b015..4f5c9ebb4 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -7,7 +7,6 @@ import ( "bytes" "crypto/aes" "encoding/binary" - "fmt" "math/rand" "net" "reflect" @@ -208,8 +207,8 @@ func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandle return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid payload") } - ttl, err := time.ParseDuration(fmt.Sprintf("%dh", req.TTL)) - if req.TTL == 0 || err != nil { + ttl, err := time.ParseDuration(req.TTL) + if err != nil || ttl == 0 { stats.MarkMeter("handler.downlink.invalid") return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid TTL") } @@ -252,6 +251,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq if err != nil { return new(core.DataUpHandlerRes), err } + h.Ctx.Debugf("%+v", entry) if len(entry.DevAddr) != 4 { // Not Activated return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Tried to send uplink on non-activated device") } diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 549c1862e..e5162ff2d 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -40,8 +40,10 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq // 3. Insert the request in our own storage h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering Device.") entry := devEntry{ - AppEUI: req.AppEUI, - DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), + AppEUI: req.AppEUI, + DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), + DevAddr: req.DevAddr, + FCntDown: 0, } copy(entry.NwkSKey[:], req.NwkSKey) copy(entry.AppSKey[:], req.AppSKey) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 4fa139744..e4fcdd65e 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -30,7 +30,7 @@ func TestHandleDataDown(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, Payload: []byte("TheThingsNetwork"), - TTL: 2, + TTL: "2h", } // Expect @@ -67,7 +67,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - TTL: 2, + TTL: "2h", Payload: nil, } @@ -105,7 +105,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - TTL: 2, + TTL: "2h", Payload: []byte("TheThingsNetwork"), } @@ -143,7 +143,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, - TTL: 2, + TTL: "2h", Payload: []byte("TheThingsNetwork"), } @@ -181,7 +181,7 @@ func TestHandleDataDown(t *testing.T) { req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, - TTL: 0, + TTL: "0s", Payload: []byte("TheThingsNetwork"), } diff --git a/core/handler.pb.go b/core/handler.pb.go index 0e0883650..761148687 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -69,7 +69,7 @@ type DataDownHandlerReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - TTL uint32 `protobuf:"varint,4,opt,name=TTL,json=tTL,proto3" json:"TTL,omitempty"` + TTL string `protobuf:"bytes,4,opt,name=TTL,json=tTL,proto3" json:"TTL,omitempty"` } func (m *DataDownHandlerReq) Reset() { *m = DataDownHandlerReq{} } @@ -393,10 +393,11 @@ func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevEUI) } } - if m.TTL != 0 { - data[i] = 0x20 + if len(m.TTL) > 0 { + data[i] = 0x22 i++ - i = encodeVarintHandler(data, i, uint64(m.TTL)) + i = encodeVarintHandler(data, i, uint64(len(m.TTL))) + i += copy(data[i:], m.TTL) } return i, nil } @@ -621,8 +622,9 @@ func (m *DataDownHandlerReq) Size() (n int) { n += 1 + l + sovHandler(uint64(l)) } } - if m.TTL != 0 { - n += 1 + sovHandler(uint64(m.TTL)) + l = len(m.TTL) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } return n } @@ -1153,10 +1155,10 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { } iNdEx = postIndex case 4: - if wireType != 0 { + if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType) } - m.TTL = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -1166,11 +1168,21 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.TTL |= (uint32(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.TTL = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -1702,33 +1714,33 @@ var ( ) var fileDescriptorHandler = []byte{ - // 434 bytes of a gzipped FileDescriptorProto + // 435 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcf, 0xea, 0xd3, 0x40, - 0x10, 0x36, 0x4d, 0x9a, 0x84, 0xf9, 0x35, 0xa5, 0x0e, 0xa5, 0x86, 0x1c, 0x8a, 0xe4, 0x24, 0x0a, - 0x05, 0xeb, 0xc5, 0x93, 0x10, 0x8d, 0xe2, 0x9f, 0xb6, 0x48, 0x4c, 0xf1, 0xbc, 0x66, 0x57, 0x94, - 0xb6, 0xd9, 0x35, 0x09, 0x96, 0xbe, 0x81, 0x8f, 0xe0, 0xdd, 0x37, 0xf0, 0x29, 0xf4, 0xe6, 0x23, - 0x88, 0xbe, 0x88, 0xbb, 0xd9, 0x48, 0x4c, 0x6d, 0xf1, 0xcf, 0x21, 0x30, 0xdf, 0xb7, 0x33, 0x3b, - 0xdf, 0x7c, 0x3b, 0x01, 0xef, 0x15, 0xc9, 0xe9, 0x96, 0x15, 0x33, 0x51, 0xf0, 0x8a, 0xa3, 0x95, - 0xf1, 0x82, 0x05, 0xde, 0x96, 0x17, 0x64, 0x4f, 0x72, 0x4d, 0x06, 0xa0, 0x48, 0x1d, 0x87, 0x1f, - 0x0d, 0x18, 0xc5, 0xa4, 0x22, 0x6b, 0xf1, 0x50, 0x17, 0x26, 0xec, 0x0d, 0xfa, 0xe0, 0x3c, 0x25, - 0x87, 0x2d, 0x27, 0xd4, 0x37, 0xae, 0x1a, 0xd7, 0x06, 0x89, 0x23, 0x34, 0xc4, 0xeb, 0xe0, 0x2e, - 0x59, 0x45, 0xa8, 0xac, 0xf0, 0x7b, 0xf2, 0xe8, 0x62, 0x3e, 0x9c, 0xd5, 0xb7, 0xfd, 0x64, 0x13, - 0x77, 0xd7, 0x44, 0x38, 0x01, 0x3b, 0x12, 0xe2, 0xfe, 0xfa, 0x91, 0x6f, 0xd6, 0x97, 0xd8, 0xa4, - 0x46, 0x8a, 0x8f, 0xd9, 0x5b, 0xc5, 0x5b, 0x9a, 0xa7, 0x35, 0x42, 0x04, 0xeb, 0xc1, 0xbd, 0xbc, - 0xf2, 0xfb, 0x92, 0xf5, 0x12, 0xeb, 0xa5, 0x8c, 0x71, 0x0c, 0xfd, 0x65, 0x7a, 0x10, 0xcc, 0xb7, - 0x6b, 0xb2, 0xbf, 0x53, 0x20, 0xdc, 0xfc, 0xa6, 0xb9, 0xc4, 0x1b, 0x5d, 0xcd, 0x17, 0xf3, 0xcb, - 0x5a, 0xd8, 0x82, 0x27, 0xe4, 0x79, 0xb4, 0x52, 0xf9, 0xff, 0x35, 0x46, 0x28, 0x00, 0x55, 0x71, - 0xcc, 0xf7, 0xf9, 0x5f, 0x59, 0xd4, 0x8e, 0xdd, 0x3b, 0x33, 0xb6, 0xd9, 0x19, 0x7b, 0x04, 0x66, - 0x9a, 0x2e, 0x6a, 0x2f, 0xbc, 0xc4, 0xac, 0xd2, 0x45, 0x38, 0x3e, 0xd1, 0xb1, 0x0c, 0xdf, 0x19, - 0x30, 0x7c, 0xcc, 0x5f, 0xff, 0x2a, 0xa2, 0x6d, 0x65, 0x9c, 0x69, 0xd5, 0xeb, 0xb4, 0x0a, 0xc0, - 0x95, 0xfc, 0x8a, 0xe7, 0x19, 0x6b, 0x44, 0xb8, 0xb4, 0xc1, 0x1d, 0x4b, 0xac, 0x3f, 0x58, 0xf2, - 0xe1, 0x58, 0x4a, 0x89, 0x37, 0x8f, 0xed, 0xbf, 0xd2, 0xb1, 0x5f, 0x65, 0x47, 0x59, 0xc6, 0x44, - 0xd5, 0x1a, 0x25, 0x2d, 0x94, 0x6a, 0x22, 0x4a, 0x8b, 0x46, 0xa6, 0x43, 0x35, 0x54, 0x27, 0xab, - 0xfd, 0xe6, 0xd9, 0x13, 0x76, 0x68, 0x64, 0x3a, 0xb9, 0x86, 0xff, 0xa2, 0x72, 0xfe, 0xd9, 0x00, - 0xa7, 0x51, 0x88, 0x77, 0x60, 0xa0, 0x43, 0xbd, 0x37, 0x38, 0xd1, 0x55, 0xc7, 0x9b, 0x1f, 0x9c, - 0xe6, 0x4b, 0x8c, 0x61, 0xd8, 0xd6, 0xab, 0x87, 0x41, 0xbf, 0xcd, 0xec, 0xae, 0x46, 0x70, 0xee, - 0xa4, 0xc4, 0xdb, 0x00, 0x1a, 0x29, 0x3b, 0x70, 0xac, 0xf3, 0xba, 0x6f, 0x1a, 0x9c, 0x62, 0xcb, - 0xbb, 0xa3, 0x4f, 0xdf, 0xa6, 0xc6, 0x17, 0xf9, 0x7d, 0x95, 0xdf, 0xfb, 0xef, 0xd3, 0x4b, 0x2f, - 0xec, 0xfa, 0xff, 0xbd, 0xf5, 0x23, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x5b, 0xb8, 0x35, 0xf1, 0x03, - 0x00, 0x00, + 0x10, 0x36, 0x4d, 0x9a, 0xc4, 0xe9, 0x1f, 0xea, 0x50, 0x6a, 0xc8, 0xa1, 0x48, 0x4e, 0xa2, 0x50, + 0xb0, 0x5e, 0x3c, 0x09, 0xd1, 0x28, 0xfe, 0x69, 0x8b, 0xc4, 0x14, 0xcf, 0x6b, 0x76, 0x45, 0x69, + 0x9b, 0x5d, 0x93, 0x60, 0xe9, 0x1b, 0xf8, 0x08, 0xde, 0x7d, 0x03, 0x9f, 0x42, 0x6f, 0x3e, 0x82, + 0xe8, 0x8b, 0xb8, 0x9b, 0x8d, 0xc4, 0xd4, 0x16, 0xf5, 0x77, 0x08, 0xcc, 0xf7, 0xed, 0xcc, 0xce, + 0x37, 0xdf, 0x4e, 0x60, 0xf0, 0x9a, 0x64, 0x74, 0xcb, 0xf2, 0x99, 0xc8, 0x79, 0xc9, 0xd1, 0x4a, + 0x79, 0xce, 0xfc, 0xc1, 0x96, 0xe7, 0x64, 0x4f, 0x32, 0x4d, 0xfa, 0xa0, 0x48, 0x1d, 0x07, 0x9f, + 0x0c, 0x18, 0x45, 0xa4, 0x24, 0x6b, 0xf1, 0x48, 0x17, 0xc6, 0xec, 0x2d, 0x7a, 0xe0, 0x3c, 0x23, + 0x87, 0x2d, 0x27, 0xd4, 0x33, 0xae, 0x19, 0xd7, 0xfb, 0xb1, 0x23, 0x34, 0xc4, 0x1b, 0xe0, 0x2e, + 0x59, 0x49, 0xa8, 0xac, 0xf0, 0x3a, 0xf2, 0xa8, 0x37, 0x1f, 0xce, 0xaa, 0xdb, 0x7e, 0xb1, 0xb1, + 0xbb, 0xab, 0x23, 0x9c, 0x80, 0x1d, 0x0a, 0xf1, 0x60, 0xfd, 0xd8, 0x33, 0xab, 0x4b, 0x6c, 0x52, + 0x21, 0xc5, 0x47, 0xec, 0x9d, 0xe2, 0x2d, 0xcd, 0xd3, 0x0a, 0x21, 0x82, 0xf5, 0xf0, 0x7e, 0x56, + 0x7a, 0x5d, 0xc9, 0x0e, 0x62, 0xeb, 0x95, 0x8c, 0x71, 0x0c, 0xdd, 0x65, 0x72, 0x10, 0xcc, 0xb3, + 0x2b, 0xb2, 0xbb, 0x53, 0x20, 0xd8, 0xfc, 0xa1, 0xb9, 0xc0, 0x9b, 0x6d, 0xcd, 0xbd, 0xf9, 0x15, + 0x2d, 0x6c, 0xc1, 0x63, 0xf2, 0x22, 0x5c, 0xa9, 0xfc, 0x0b, 0x8d, 0x11, 0x08, 0x40, 0x55, 0x1c, + 0xf1, 0x7d, 0xf6, 0x4f, 0x16, 0x35, 0x63, 0x77, 0xce, 0x8c, 0x6d, 0xb6, 0xc6, 0x1e, 0x81, 0x99, + 0x24, 0x8b, 0xca, 0x8b, 0xcb, 0xb1, 0x59, 0x26, 0x8b, 0x60, 0x7c, 0xa2, 0x63, 0x11, 0xbc, 0x37, + 0x60, 0xf8, 0x84, 0xbf, 0xf9, 0x5d, 0x44, 0xd3, 0xca, 0x38, 0xd3, 0xaa, 0xd3, 0x6a, 0xe5, 0x83, + 0x2b, 0xf9, 0x15, 0xcf, 0x52, 0x56, 0x8b, 0x70, 0x69, 0x8d, 0x5b, 0x96, 0x58, 0x7f, 0xb1, 0xe4, + 0xe3, 0xb1, 0x94, 0x02, 0x6f, 0x1d, 0xdb, 0x7f, 0xb5, 0x65, 0xbf, 0xca, 0x0e, 0xd3, 0x94, 0x89, + 0xb2, 0x31, 0x4a, 0x5a, 0x28, 0xd5, 0x84, 0x94, 0xe6, 0xb5, 0x4c, 0x87, 0x6a, 0xa8, 0x4e, 0x56, + 0xfb, 0xcd, 0xf3, 0xa7, 0xec, 0x50, 0xcb, 0x74, 0x32, 0x0d, 0xff, 0x47, 0xe5, 0xfc, 0x8b, 0x01, + 0x4e, 0xad, 0x10, 0xef, 0x42, 0x5f, 0x87, 0x7a, 0x6f, 0x70, 0xa2, 0xab, 0x8e, 0x37, 0xdf, 0x3f, + 0xcd, 0x17, 0x18, 0xc1, 0xb0, 0xa9, 0x57, 0x0f, 0x83, 0x5e, 0x93, 0xd9, 0x5e, 0x0d, 0xff, 0xdc, + 0x49, 0x81, 0x77, 0x00, 0x34, 0x52, 0x76, 0xe0, 0x58, 0xe7, 0xb5, 0xdf, 0xd4, 0x3f, 0xc5, 0x16, + 0xf7, 0x46, 0x9f, 0xbf, 0x4f, 0x8d, 0xaf, 0xf2, 0xfb, 0x26, 0xbf, 0x0f, 0x3f, 0xa6, 0x97, 0x5e, + 0xda, 0xd5, 0xff, 0x7b, 0xfb, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x35, 0x1b, 0x79, 0xf1, + 0x03, 0x00, 0x00, } diff --git a/core/msgp.go b/core/msgp.go index 0e84ca3ca..520969f95 100644 --- a/core/msgp.go +++ b/core/msgp.go @@ -36,6 +36,7 @@ type AppMetadata struct { // DataDownAppReq represents downlink messages sent by applications type DataDownAppReq struct { Payload []byte `msg:"payload" json:"payload"` + TTL string `msg:"ttl" json:"ttl"` } // ABPSubAppReq defines the shape of the request made by an application to the handler diff --git a/core/msgp_gen.go b/core/msgp_gen.go index 219c3f7bb..a40464c1a 100644 --- a/core/msgp_gen.go +++ b/core/msgp_gen.go @@ -185,10 +185,13 @@ func (z *AppMetadata) Msgsize() (s int) { // MarshalMsg implements msgp.Marshaler func (z *DataDownAppReq) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) - // map header, size 1 + // map header, size 2 // string "payload" - o = append(o, 0x81, 0xa7, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64) + o = append(o, 0x82, 0xa7, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64) o = msgp.AppendBytes(o, z.Payload) + // string "ttl" + o = append(o, 0xa3, 0x74, 0x74, 0x6c) + o = msgp.AppendString(o, z.TTL) return } @@ -213,6 +216,11 @@ func (z *DataDownAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { if err != nil { return } + case "ttl": + z.TTL, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + return + } default: bts, err = msgp.Skip(bts) if err != nil { @@ -225,7 +233,7 @@ func (z *DataDownAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { } func (z *DataDownAppReq) Msgsize() (s int) { - s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + 4 + msgp.StringPrefixSize + len(z.TTL) return } diff --git a/core/protos/handler.proto b/core/protos/handler.proto index 5ed0500ad..d428ee6ef 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -22,7 +22,7 @@ message DataDownHandlerReq { bytes Payload = 1; bytes AppEUI = 2; bytes DevEUI = 3; - uint32 TTL = 4; + string TTL = 4; } message DataDownHandlerRes {} diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 7e8de40a1..563cf18fb 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -17,7 +17,7 @@ import ( // downlinkCmd represents the `downlink` command var downlinkCmd = &cobra.Command{ - Use: "downlink [DevEUI] [Payload]", + Use: "downlink [DevEUI] [Payload] [TTL]", Short: "Send a downlink message to the network", Long: `Send a downlink message to the network`, Run: func(cmd *cobra.Command, args []string) { @@ -32,6 +32,7 @@ var downlinkCmd = &cobra.Command{ req := core.DataDownAppReq{ Payload: []byte(args[1]), + TTL: args[2], } payload, err := req.MarshalMsg(nil) From 8769a2246347b90882226eca02690f5df713fe71 Mon Sep 17 00:00:00 2001 From: ktorz Date: Tue, 22 Mar 2016 23:12:39 +0100 Subject: [PATCH 1167/2266] [feature/manager] Add MIC check to join-request --- core/adapters/udp/conversions.go | 1 + core/adapters/udp/conversions_test.go | 1 + core/adapters/udp/udp_test.go | 4 + core/broker.pb.go | 79 +++++++-- core/components/broker/broker.go | 3 +- core/components/broker/broker_test.go | 30 ++++ core/components/handler/handler.go | 14 ++ core/components/handler/handler_test.go | 68 ++++++++ core/components/router/router.go | 3 +- core/components/router/router_test.go | 12 ++ core/handler.pb.go | 109 ++++++++---- core/protos/broker.proto | 3 +- core/protos/handler.proto | 3 +- core/protos/router.proto | 3 +- core/router.pb.go | 71 ++++++-- ttnctl/cmd/join.go | 210 ++++++++++++++++++++++++ 16 files changed, 550 insertions(+), 64 deletions(-) create mode 100644 ttnctl/cmd/join.go diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index cec2a54fb..324d01697 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -96,6 +96,7 @@ func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interfa AppEUI: joinpayload.AppEUI[:], DevEUI: joinpayload.DevEUI[:], DevNonce: joinpayload.DevNonce[:], + MIC: payload.MIC[:], Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), }, nil default: diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index 4a986ba4d..6eae9ba5d 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -481,6 +481,7 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { AppEUI: joinpayload.AppEUI[:], DevEUI: joinpayload.DevEUI[:], DevNonce: joinpayload.DevNonce[:], + MIC: payload.MIC[:], Metadata: &core.Metadata{ CodingRate: *rxpk.Codr, Frequency: *rxpk.Freq, diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index aa2443323..24d89c259 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -952,6 +952,7 @@ func TestUDPAdapter(t *testing.T) { DevEUI: joinpayload.DevEUI[:], AppEUI: joinpayload.AppEUI[:], DevNonce: joinpayload.DevNonce[:], + MIC: payload.MIC[:], Metadata: new(core.Metadata), } var wantStats *core.StatsReq @@ -1047,6 +1048,7 @@ func TestUDPAdapter(t *testing.T) { DevEUI: joinpayload.DevEUI[:], AppEUI: joinpayload.AppEUI[:], DevNonce: joinpayload.DevNonce[:], + MIC: payload.MIC[:], Metadata: new(core.Metadata), } var wantStats *core.StatsReq @@ -1137,6 +1139,7 @@ func TestUDPAdapter(t *testing.T) { DevEUI: joinpayload.DevEUI[:], AppEUI: joinpayload.AppEUI[:], DevNonce: joinpayload.DevNonce[:], + MIC: payload.MIC[:], Metadata: new(core.Metadata), } var wantStats *core.StatsReq @@ -1221,6 +1224,7 @@ func TestUDPAdapter(t *testing.T) { DevEUI: joinpayload.DevEUI[:], AppEUI: joinpayload.AppEUI[:], DevNonce: joinpayload.DevNonce[:], + MIC: payload.MIC[:], Metadata: new(core.Metadata), } var wantStats *core.StatsReq diff --git a/core/broker.pb.go b/core/broker.pb.go index 04a9c0935..7afc3fa8b 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -72,7 +72,8 @@ type JoinBrokerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - Metadata *Metadata `protobuf:"bytes,4,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + MIC []byte `protobuf:"bytes,4,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` + Metadata *Metadata `protobuf:"bytes,5,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinBrokerReq) Reset() { *m = JoinBrokerReq{} } @@ -322,8 +323,16 @@ func (m *JoinBrokerReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevNonce) } } + if m.MIC != nil { + if len(m.MIC) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) + } + } if m.Metadata != nil { - data[i] = 0x22 + data[i] = 0x2a i++ i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) n5, err := m.Metadata.MarshalTo(data[i:]) @@ -457,6 +466,12 @@ func (m *JoinBrokerReq) Size() (n int) { n += 1 + l + sovBroker(uint64(l)) } } + if m.MIC != nil { + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + } if m.Metadata != nil { l = m.Metadata.Size() n += 1 + l + sovBroker(uint64(l)) @@ -852,6 +867,37 @@ func (m *JoinBrokerReq) Unmarshal(data []byte) error { } iNdEx = postIndex case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) + if m.MIC == nil { + m.MIC = []byte{} + } + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1158,7 +1204,7 @@ var ( ) var fileDescriptorBroker = []byte{ - // 312 bytes of a gzipped FileDescriptorProto + // 327 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, @@ -1166,17 +1212,18 @@ var fileDescriptorBroker = []byte{ 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x41, 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, - 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xb5, - 0x33, 0x72, 0xf1, 0x7a, 0xe5, 0x67, 0xe6, 0x21, 0x3c, 0x25, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, - 0x1a, 0xea, 0x09, 0xb6, 0x89, 0x27, 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, 0xbb, 0xa4, 0x96, 0x81, - 0xc4, 0x99, 0x20, 0xe2, 0x29, 0x60, 0x9e, 0x90, 0x14, 0x17, 0x07, 0x50, 0xdc, 0x2f, 0x3f, 0x2f, - 0x39, 0x55, 0x82, 0x19, 0x2c, 0xc3, 0x91, 0x02, 0xe5, 0xa3, 0xb8, 0x84, 0x85, 0x80, 0x4b, 0x3a, - 0xd0, 0x5c, 0x52, 0x2c, 0x64, 0x88, 0xee, 0x69, 0x71, 0x14, 0x4f, 0x83, 0x14, 0x3b, 0x26, 0x27, - 0xa7, 0x16, 0x94, 0x20, 0xbc, 0x2e, 0xc1, 0xc5, 0x0e, 0x74, 0x8c, 0x63, 0x4a, 0x4a, 0x11, 0xd4, - 0x95, 0xec, 0x29, 0x10, 0x2e, 0x8a, 0x53, 0x98, 0xf1, 0x3b, 0xc5, 0xa8, 0x82, 0x8b, 0x0d, 0xe2, - 0x0a, 0x21, 0x33, 0x2e, 0x2e, 0x8f, 0xc4, 0xbc, 0x94, 0x9c, 0x54, 0x50, 0x08, 0x0b, 0x09, 0x43, - 0x74, 0xa0, 0x24, 0x02, 0x29, 0x2c, 0x82, 0xc5, 0x08, 0x7d, 0x20, 0x47, 0xc2, 0xf4, 0xa1, 0x84, - 0xb3, 0x14, 0x16, 0xc1, 0x62, 0x27, 0x81, 0x13, 0x8f, 0xe4, 0x18, 0x2f, 0x00, 0xf1, 0x03, 0x20, - 0x9e, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x9c, 0xf6, 0x8c, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, - 0x57, 0x4c, 0xa7, 0xbb, 0xac, 0x02, 0x00, 0x00, + 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xcd, + 0x64, 0xe4, 0xe2, 0xf5, 0xca, 0xcf, 0xcc, 0x43, 0x78, 0x4a, 0x8c, 0x8b, 0xcd, 0xb1, 0xa0, 0xc0, + 0x35, 0xd4, 0x13, 0x6c, 0x13, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x07, 0x12, 0x77, 0x49, 0x2d, 0x03, + 0x89, 0x33, 0x41, 0xc4, 0x53, 0xc0, 0x3c, 0x21, 0x29, 0x2e, 0x0e, 0xa0, 0xb8, 0x5f, 0x7e, 0x5e, + 0x72, 0xaa, 0x04, 0x33, 0x58, 0x86, 0x23, 0x05, 0xca, 0x17, 0x12, 0xe0, 0x62, 0xf6, 0xf5, 0x74, + 0x96, 0x60, 0x01, 0x0b, 0x33, 0xe7, 0x7a, 0x3a, 0xa3, 0xb8, 0x8d, 0x95, 0x80, 0xdb, 0x3a, 0xd0, + 0xdc, 0x56, 0x2c, 0x64, 0x88, 0x1e, 0x0c, 0xe2, 0x28, 0xc1, 0x00, 0x52, 0xec, 0x98, 0x9c, 0x9c, + 0x5a, 0x50, 0x82, 0x08, 0x0c, 0x09, 0x2e, 0x76, 0xa0, 0xf3, 0x1c, 0x53, 0x52, 0x8a, 0xa0, 0xee, + 0x66, 0x4f, 0x81, 0x70, 0x51, 0x9c, 0xc2, 0x8c, 0xdf, 0x29, 0x46, 0x15, 0x5c, 0x6c, 0x10, 0x57, + 0x08, 0x99, 0x71, 0x71, 0x79, 0x24, 0xe6, 0xa5, 0xe4, 0xa4, 0x82, 0xc2, 0x5c, 0x48, 0x18, 0xa2, + 0x03, 0x25, 0x59, 0x48, 0x61, 0x11, 0x2c, 0x46, 0xe8, 0x03, 0x39, 0x12, 0xa6, 0x0f, 0x25, 0xe4, + 0xa5, 0xb0, 0x08, 0x16, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, + 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0xd4, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x48, + 0xfb, 0x4c, 0x62, 0xbe, 0x02, 0x00, 0x00, } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index ab4068009..40afc12b0 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -85,7 +85,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c b.Ctx.Debug("Handling join request") // Validate incoming data - if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { + if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || len(req.MIC) != 4 || req.Metadata == nil { b.Ctx.Debug("Invalid request. Parameters are incorrect.") return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid parameters") } @@ -132,6 +132,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c DevEUI: req.DevEUI, AppEUI: req.AppEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, }) if err != nil { diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index b78826942..c4922d8c8 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -848,6 +848,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -857,6 +859,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } var wantRes = &core.JoinBrokerRes{ @@ -914,6 +917,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -923,6 +928,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } var wantRes = &core.JoinBrokerRes{ @@ -984,6 +990,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nil, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1042,6 +1050,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: as.OutRead.Entry.AppEUI, DevEUI: []byte{1, 2, 3}, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1158,6 +1168,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: nil, } @@ -1207,6 +1219,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1265,6 +1279,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1324,6 +1340,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1382,6 +1400,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1391,6 +1411,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) @@ -1445,6 +1466,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1454,6 +1477,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) @@ -1509,6 +1533,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1518,6 +1544,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) @@ -1577,6 +1604,8 @@ func TestHandleJoin(t *testing.T) { AppEUI: nc.OutReadNonces.Entry.AppEUI, DevEUI: nc.OutReadNonces.Entry.DevEUI, DevNonce: []byte{14, 14}, + MIC: []byte{14, 14, 14, 14}, + Metadata: new(core.Metadata), } @@ -1586,6 +1615,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } var wantRes = new(core.JoinBrokerRes) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 4f5c9ebb4..dbfd0afcc 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -145,6 +145,20 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* return new(core.JoinHandlerRes), errors.New(errors.Behavioural, "Trying to activate a personalized device") } + // 1.5. (Yep, indexed comments sucks) Verify MIC + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{Major: lorawan.LoRaWANR1, MType: lorawan.JoinRequest} + joinPayload := lorawan.JoinRequestPayload{} + copy(payload.MIC[:], req.MIC) + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + if ok, err := payload.ValidateMIC(lorawan.AES128Key(*entry.AppKey)); err != nil || !ok { + h.Ctx.WithError(err).Debug("Unable to validate MIC from incoming join-request") + return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to validate MIC") + } + // 2. Prepare a channel to receive the response from the consumer chresp := make(chan interface{}) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index e4fcdd65e..f1a8a4ecb 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1269,6 +1269,17 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewBroker() + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + // Expect var wantErr *string var wantRes = &core.JoinHandlerRes{ @@ -1347,6 +1358,17 @@ func TestHandleJoin(t *testing.T) { appAdapter.Failures["HandleJoin"] = errors.New(errors.Operational, "Mock Error") broker := mocks.NewBroker() + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + // Expect var wantErr *string var wantRes = &core.JoinHandlerRes{ @@ -1425,6 +1447,17 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewBroker() + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + // Expect var wantErr = ErrOperational var wantRes = new(core.JoinHandlerRes) @@ -1480,6 +1513,17 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewBroker() + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + // Expect var wantErr = ErrOperational var wantRes = new(core.JoinHandlerRes) @@ -1535,6 +1579,17 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewBroker() + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + // Expect var wantErr = ErrStructural var wantRes = new(core.JoinHandlerRes) @@ -1568,6 +1623,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{14, 42}, + MIC: []byte{1, 2, 3, 4}, Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, @@ -1847,6 +1903,18 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewBroker() + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req1.AppEUI) + copy(joinPayload.DevEUI[:], req1.DevEUI) + copy(joinPayload.DevNonce[:], req1.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req1.MIC = payload.MIC[:] + req2.MIC = payload.MIC[:] + // Expect var wantErr1 *string var wantErr2 *string diff --git a/core/components/router/router.go b/core/components/router/router.go index 4544d8899..6ddbb397a 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -87,7 +87,7 @@ func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.S // HandleJoin implements the core.RouterClient interface func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (routerRes *core.JoinRouterRes, err error) { - if len(req.GatewayID) != 8 || len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { + if len(req.GatewayID) != 8 || len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.DevNonce) != 2 || len(req.MIC) != 4 || req.Metadata == nil { r.Ctx.Debug("Invalid request. Parameters are incorrects") return new(core.JoinRouterRes), errors.New(errors.Structural, "Invalid Request") } @@ -103,6 +103,7 @@ func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (rou AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: req.Metadata, } response, err := r.send(bpacket, true, r.Brokers...) diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 4d8e8f8b5..9f14ce833 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -1486,6 +1486,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1501,6 +1502,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: &core.Metadata{ Altitude: gt.OutRead.Entry.Metadata.Altitude, Longitude: gt.OutRead.Entry.Metadata.Longitude, @@ -1564,6 +1566,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1576,6 +1579,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: &core.Metadata{ Altitude: gt.OutRead.Entry.Metadata.Altitude, Longitude: gt.OutRead.Entry.Metadata.Longitude, @@ -1637,6 +1641,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: nil, } @@ -1697,6 +1702,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1759,6 +1765,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1821,6 +1828,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1883,6 +1891,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1939,6 +1948,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 868.5, }, @@ -1951,6 +1961,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, DevNonce: req.DevNonce, + MIC: req.MIC, Metadata: &core.Metadata{ Altitude: gt.OutRead.Entry.Metadata.Altitude, Longitude: gt.OutRead.Entry.Metadata.Longitude, @@ -2010,6 +2021,7 @@ func TestHandleJoin(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, DevNonce: []byte{3, 3}, + MIC: []byte{14, 14, 14, 14}, Metadata: &core.Metadata{ Frequency: 14.42, }, diff --git a/core/handler.pb.go b/core/handler.pb.go index 761148687..21eee48bd 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -89,7 +89,8 @@ type JoinHandlerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - Metadata *Metadata `protobuf:"bytes,4,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + MIC []byte `protobuf:"bytes,4,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` + Metadata *Metadata `protobuf:"bytes,5,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinHandlerReq) Reset() { *m = JoinHandlerReq{} } @@ -459,8 +460,16 @@ func (m *JoinHandlerReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevNonce) } } + if m.MIC != nil { + if len(m.MIC) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) + } + } if m.Metadata != nil { - data[i] = 0x22 + data[i] = 0x2a i++ i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) n4, err := m.Metadata.MarshalTo(data[i:]) @@ -656,6 +665,12 @@ func (m *JoinHandlerReq) Size() (n int) { n += 1 + l + sovHandler(uint64(l)) } } + if m.MIC != nil { + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + } if m.Metadata != nil { l = m.Metadata.Size() n += 1 + l + sovHandler(uint64(l)) @@ -1377,6 +1392,37 @@ func (m *JoinHandlerReq) Unmarshal(data []byte) error { } iNdEx = postIndex case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) + if m.MIC == nil { + m.MIC = []byte{} + } + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1714,33 +1760,34 @@ var ( ) var fileDescriptorHandler = []byte{ - // 435 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcf, 0xea, 0xd3, 0x40, - 0x10, 0x36, 0x4d, 0x9a, 0xc4, 0xe9, 0x1f, 0xea, 0x50, 0x6a, 0xc8, 0xa1, 0x48, 0x4e, 0xa2, 0x50, - 0xb0, 0x5e, 0x3c, 0x09, 0xd1, 0x28, 0xfe, 0x69, 0x8b, 0xc4, 0x14, 0xcf, 0x6b, 0x76, 0x45, 0x69, - 0x9b, 0x5d, 0x93, 0x60, 0xe9, 0x1b, 0xf8, 0x08, 0xde, 0x7d, 0x03, 0x9f, 0x42, 0x6f, 0x3e, 0x82, - 0xe8, 0x8b, 0xb8, 0x9b, 0x8d, 0xc4, 0xd4, 0x16, 0xf5, 0x77, 0x08, 0xcc, 0xf7, 0xed, 0xcc, 0xce, - 0x37, 0xdf, 0x4e, 0x60, 0xf0, 0x9a, 0x64, 0x74, 0xcb, 0xf2, 0x99, 0xc8, 0x79, 0xc9, 0xd1, 0x4a, - 0x79, 0xce, 0xfc, 0xc1, 0x96, 0xe7, 0x64, 0x4f, 0x32, 0x4d, 0xfa, 0xa0, 0x48, 0x1d, 0x07, 0x9f, - 0x0c, 0x18, 0x45, 0xa4, 0x24, 0x6b, 0xf1, 0x48, 0x17, 0xc6, 0xec, 0x2d, 0x7a, 0xe0, 0x3c, 0x23, - 0x87, 0x2d, 0x27, 0xd4, 0x33, 0xae, 0x19, 0xd7, 0xfb, 0xb1, 0x23, 0x34, 0xc4, 0x1b, 0xe0, 0x2e, - 0x59, 0x49, 0xa8, 0xac, 0xf0, 0x3a, 0xf2, 0xa8, 0x37, 0x1f, 0xce, 0xaa, 0xdb, 0x7e, 0xb1, 0xb1, - 0xbb, 0xab, 0x23, 0x9c, 0x80, 0x1d, 0x0a, 0xf1, 0x60, 0xfd, 0xd8, 0x33, 0xab, 0x4b, 0x6c, 0x52, - 0x21, 0xc5, 0x47, 0xec, 0x9d, 0xe2, 0x2d, 0xcd, 0xd3, 0x0a, 0x21, 0x82, 0xf5, 0xf0, 0x7e, 0x56, - 0x7a, 0x5d, 0xc9, 0x0e, 0x62, 0xeb, 0x95, 0x8c, 0x71, 0x0c, 0xdd, 0x65, 0x72, 0x10, 0xcc, 0xb3, - 0x2b, 0xb2, 0xbb, 0x53, 0x20, 0xd8, 0xfc, 0xa1, 0xb9, 0xc0, 0x9b, 0x6d, 0xcd, 0xbd, 0xf9, 0x15, - 0x2d, 0x6c, 0xc1, 0x63, 0xf2, 0x22, 0x5c, 0xa9, 0xfc, 0x0b, 0x8d, 0x11, 0x08, 0x40, 0x55, 0x1c, - 0xf1, 0x7d, 0xf6, 0x4f, 0x16, 0x35, 0x63, 0x77, 0xce, 0x8c, 0x6d, 0xb6, 0xc6, 0x1e, 0x81, 0x99, - 0x24, 0x8b, 0xca, 0x8b, 0xcb, 0xb1, 0x59, 0x26, 0x8b, 0x60, 0x7c, 0xa2, 0x63, 0x11, 0xbc, 0x37, - 0x60, 0xf8, 0x84, 0xbf, 0xf9, 0x5d, 0x44, 0xd3, 0xca, 0x38, 0xd3, 0xaa, 0xd3, 0x6a, 0xe5, 0x83, - 0x2b, 0xf9, 0x15, 0xcf, 0x52, 0x56, 0x8b, 0x70, 0x69, 0x8d, 0x5b, 0x96, 0x58, 0x7f, 0xb1, 0xe4, - 0xe3, 0xb1, 0x94, 0x02, 0x6f, 0x1d, 0xdb, 0x7f, 0xb5, 0x65, 0xbf, 0xca, 0x0e, 0xd3, 0x94, 0x89, - 0xb2, 0x31, 0x4a, 0x5a, 0x28, 0xd5, 0x84, 0x94, 0xe6, 0xb5, 0x4c, 0x87, 0x6a, 0xa8, 0x4e, 0x56, - 0xfb, 0xcd, 0xf3, 0xa7, 0xec, 0x50, 0xcb, 0x74, 0x32, 0x0d, 0xff, 0x47, 0xe5, 0xfc, 0x8b, 0x01, - 0x4e, 0xad, 0x10, 0xef, 0x42, 0x5f, 0x87, 0x7a, 0x6f, 0x70, 0xa2, 0xab, 0x8e, 0x37, 0xdf, 0x3f, - 0xcd, 0x17, 0x18, 0xc1, 0xb0, 0xa9, 0x57, 0x0f, 0x83, 0x5e, 0x93, 0xd9, 0x5e, 0x0d, 0xff, 0xdc, - 0x49, 0x81, 0x77, 0x00, 0x34, 0x52, 0x76, 0xe0, 0x58, 0xe7, 0xb5, 0xdf, 0xd4, 0x3f, 0xc5, 0x16, - 0xf7, 0x46, 0x9f, 0xbf, 0x4f, 0x8d, 0xaf, 0xf2, 0xfb, 0x26, 0xbf, 0x0f, 0x3f, 0xa6, 0x97, 0x5e, - 0xda, 0xd5, 0xff, 0x7b, 0xfb, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x35, 0x1b, 0x79, 0xf1, - 0x03, 0x00, 0x00, + // 449 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xc1, 0x8e, 0xd3, 0x30, + 0x10, 0x25, 0x4d, 0x9a, 0x84, 0xd9, 0x6d, 0x55, 0x46, 0xd5, 0x12, 0xe5, 0xb0, 0x42, 0x39, 0x21, + 0x90, 0x56, 0xa2, 0x5c, 0x38, 0x21, 0x85, 0x06, 0x44, 0xa1, 0xad, 0x50, 0xc8, 0x8a, 0xb3, 0x89, + 0x8d, 0x40, 0xdb, 0xc6, 0x26, 0x89, 0xa8, 0xfa, 0x27, 0x88, 0x2b, 0x7f, 0xc0, 0x57, 0xc0, 0x8d, + 0x4f, 0x40, 0xf0, 0x23, 0xd8, 0x71, 0x50, 0x70, 0x69, 0x01, 0xed, 0x21, 0xd2, 0xbc, 0xe7, 0x99, + 0xcc, 0x9b, 0x37, 0x36, 0x0c, 0x5e, 0x93, 0x82, 0xae, 0x58, 0x79, 0x26, 0x4a, 0x5e, 0x73, 0x74, + 0x72, 0x5e, 0xb2, 0x70, 0xb0, 0xe2, 0x25, 0xd9, 0x90, 0x42, 0x93, 0x21, 0x28, 0x52, 0xc7, 0xd1, + 0x27, 0x0b, 0x46, 0x09, 0xa9, 0xc9, 0xb9, 0x78, 0xac, 0x0b, 0x53, 0xf6, 0x16, 0x03, 0xf0, 0x9e, + 0x91, 0xed, 0x8a, 0x13, 0x1a, 0x58, 0x37, 0xac, 0x9b, 0xc7, 0xa9, 0x27, 0x34, 0xc4, 0x5b, 0xe0, + 0x2f, 0x58, 0x4d, 0xa8, 0xac, 0x08, 0x7a, 0xf2, 0xe8, 0x68, 0x32, 0x3c, 0x6b, 0xfe, 0xf6, 0x8b, + 0x4d, 0xfd, 0x75, 0x1b, 0xe1, 0x09, 0xb8, 0xb1, 0x10, 0x0f, 0xcf, 0x67, 0x81, 0xdd, 0xfc, 0xc4, + 0x25, 0x0d, 0x52, 0x7c, 0xc2, 0xde, 0x29, 0xde, 0xd1, 0x3c, 0x6d, 0x10, 0x22, 0x38, 0x8f, 0xa6, + 0x45, 0x1d, 0xf4, 0x25, 0x3b, 0x48, 0x9d, 0x57, 0x32, 0xc6, 0x31, 0xf4, 0x17, 0xd9, 0x56, 0xb0, + 0xc0, 0x6d, 0xc8, 0xfe, 0x5a, 0x81, 0xe8, 0xe2, 0x0f, 0xcd, 0x15, 0xde, 0x36, 0x35, 0x1f, 0x4d, + 0xae, 0x69, 0x61, 0x73, 0x9e, 0x92, 0x17, 0xf1, 0x52, 0xe5, 0x5f, 0x6a, 0x8c, 0x48, 0x00, 0xaa, + 0xe2, 0x84, 0x6f, 0x8a, 0xff, 0xb2, 0xa8, 0x1b, 0xbb, 0x77, 0x60, 0x6c, 0xdb, 0x18, 0x7b, 0x04, + 0x76, 0x96, 0xcd, 0x1b, 0x2f, 0xae, 0xa6, 0x76, 0x9d, 0xcd, 0xa3, 0xf1, 0x9e, 0x8e, 0x55, 0xf4, + 0xc1, 0x82, 0xe1, 0x13, 0xfe, 0xe6, 0x77, 0x11, 0x5d, 0x2b, 0xeb, 0x40, 0xab, 0x9e, 0xd1, 0x2a, + 0x04, 0x5f, 0xf2, 0x4b, 0x5e, 0xe4, 0xac, 0x15, 0xe1, 0xd3, 0x16, 0x2b, 0x19, 0x8b, 0xd9, 0xb4, + 0x5d, 0x89, 0xbd, 0x9e, 0x4d, 0x0d, 0x93, 0xfa, 0xff, 0x30, 0xe9, 0xe3, 0xae, 0xb8, 0x0a, 0xef, + 0xec, 0x2e, 0xe4, 0xba, 0xb1, 0x10, 0x95, 0x1d, 0xe7, 0x39, 0x13, 0x75, 0x67, 0x9d, 0x34, 0x55, + 0xea, 0x8b, 0x29, 0x2d, 0x5b, 0xe1, 0x1e, 0xd5, 0x50, 0x9d, 0x2c, 0x37, 0x17, 0xcf, 0x9f, 0xb2, + 0x6d, 0x2b, 0xdc, 0x2b, 0x34, 0x34, 0x54, 0x3a, 0x7f, 0x57, 0x39, 0xf9, 0x62, 0x81, 0xd7, 0x2a, + 0xc4, 0xfb, 0x70, 0xac, 0x43, 0x7d, 0x93, 0xf0, 0x44, 0x57, 0xed, 0xbe, 0x85, 0x70, 0x3f, 0x5f, + 0x61, 0x02, 0xc3, 0xae, 0x5e, 0xad, 0x0a, 0x83, 0x2e, 0xd3, 0xbc, 0x2c, 0xe1, 0xa1, 0x93, 0x0a, + 0xef, 0x01, 0x68, 0xa4, 0xec, 0xc0, 0xb1, 0xce, 0x33, 0xb7, 0x1c, 0xee, 0x63, 0xab, 0x07, 0xa3, + 0xcf, 0xdf, 0x4f, 0xad, 0xaf, 0xf2, 0xfb, 0x26, 0xbf, 0xf7, 0x3f, 0x4e, 0xaf, 0xbc, 0x74, 0x9b, + 0x17, 0x7d, 0xf7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x1e, 0xc9, 0x1d, 0x03, 0x04, 0x00, + 0x00, } diff --git a/core/protos/broker.proto b/core/protos/broker.proto index 15686fcd3..30bcb27e0 100644 --- a/core/protos/broker.proto +++ b/core/protos/broker.proto @@ -18,7 +18,8 @@ message JoinBrokerReq { bytes AppEUI = 1; bytes DevEUI = 2; bytes DevNonce = 3; - Metadata Metadata = 4; + bytes MIC = 4; + Metadata Metadata = 5; } message JoinBrokerRes { diff --git a/core/protos/handler.proto b/core/protos/handler.proto index d428ee6ef..d265a97be 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -31,7 +31,8 @@ message JoinHandlerReq { bytes AppEUI = 1; bytes DevEUI = 2; bytes DevNonce = 3; - Metadata Metadata = 4; + bytes MIC = 4; + Metadata Metadata = 5; } message JoinHandlerRes { diff --git a/core/protos/router.proto b/core/protos/router.proto index f2ab8f1d8..5edea63ff 100644 --- a/core/protos/router.proto +++ b/core/protos/router.proto @@ -27,7 +27,8 @@ message JoinRouterReq { bytes AppEUI = 2; bytes DevEUI = 3; bytes DevNonce = 4; - Metadata Metadata = 5; + bytes MIC = 5; + Metadata Metadata = 6; } message JoinRouterRes { diff --git a/core/router.pb.go b/core/router.pb.go index 88bdd66d4..987d36be3 100644 --- a/core/router.pb.go +++ b/core/router.pb.go @@ -99,7 +99,8 @@ type JoinRouterReq struct { AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` DevNonce []byte `protobuf:"bytes,4,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - Metadata *Metadata `protobuf:"bytes,5,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + MIC []byte `protobuf:"bytes,5,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` + Metadata *Metadata `protobuf:"bytes,6,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinRouterReq) Reset() { *m = JoinRouterReq{} } @@ -447,8 +448,16 @@ func (m *JoinRouterReq) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevNonce) } } + if m.MIC != nil { + if len(m.MIC) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintRouter(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) + } + } if m.Metadata != nil { - data[i] = 0x2a + data[i] = 0x32 i++ i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) n6, err := m.Metadata.MarshalTo(data[i:]) @@ -608,6 +617,12 @@ func (m *JoinRouterReq) Size() (n int) { n += 1 + l + sovRouter(uint64(l)) } } + if m.MIC != nil { + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + } if m.Metadata != nil { l = m.Metadata.Size() n += 1 + l + sovRouter(uint64(l)) @@ -1223,6 +1238,37 @@ func (m *JoinRouterReq) Unmarshal(data []byte) error { } iNdEx = postIndex case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) + if m.MIC == nil { + m.MIC = []byte{} + } + iNdEx = postIndex + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1498,7 +1544,7 @@ var ( ) var fileDescriptorRouter = []byte{ - // 368 bytes of a gzipped FileDescriptorProto + // 383 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xca, 0x2f, 0x2d, 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, @@ -1510,16 +1556,17 @@ var fileDescriptorRouter = []byte{ 0x14, 0xa0, 0xa0, 0x04, 0x33, 0x58, 0x35, 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x52, 0xca, 0x40, 0x75, 0x47, 0x31, 0xb2, 0x4d, 0x8c, 0x24, 0xd9, 0xc4, 0x44, 0xc0, 0xa6, 0x48, 0x2e, 0x8e, 0xe0, 0x92, 0xc4, 0x92, 0x62, 0xc2, 0x9e, 0xd5, 0xc7, 0x30, 0x55, 0x18, 0x62, 0x2a, - 0x58, 0x3f, 0x16, 0xa3, 0xb9, 0xe0, 0x46, 0x17, 0x2b, 0x2d, 0x05, 0x86, 0xac, 0x57, 0x7e, 0x66, + 0x58, 0x3f, 0x16, 0xa3, 0xb9, 0xe0, 0x46, 0x17, 0x2b, 0x6d, 0x07, 0x86, 0xac, 0x57, 0x7e, 0x66, 0x1e, 0xb1, 0x21, 0x2b, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0xb6, 0x8a, 0x27, 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, 0xbb, 0xa4, 0x96, 0x81, 0xc4, 0x99, 0x21, 0xe2, 0x29, 0x60, 0x9e, 0x90, 0x14, 0x17, 0x07, 0x50, 0xdc, 0x2f, 0x3f, 0x2f, 0x39, 0x55, 0x82, 0x05, 0x2c, 0xc3, - 0x91, 0x02, 0xe5, 0xa3, 0x04, 0x07, 0x2b, 0x81, 0xe0, 0xc8, 0x43, 0x75, 0x66, 0xb1, 0x90, 0x21, - 0x7a, 0xc0, 0x8b, 0xa3, 0x04, 0x3c, 0x48, 0xb1, 0x63, 0x72, 0x72, 0x6a, 0x41, 0x09, 0x59, 0xc1, - 0x6f, 0xb4, 0x9c, 0x91, 0x8b, 0x0d, 0x62, 0x99, 0x90, 0x19, 0x17, 0x97, 0x47, 0x62, 0x5e, 0x4a, - 0x4e, 0x2a, 0x28, 0x32, 0x85, 0xa0, 0x61, 0x8b, 0x92, 0x1a, 0xa5, 0xb0, 0x08, 0x16, 0x0b, 0xe9, - 0x72, 0x71, 0x43, 0xf4, 0x81, 0x03, 0x5b, 0x88, 0x0f, 0x29, 0x52, 0x40, 0x7a, 0x50, 0xf9, 0xc5, - 0x08, 0x6b, 0x40, 0x4e, 0x87, 0x59, 0x83, 0x12, 0x35, 0x52, 0x58, 0x04, 0x8b, 0x9d, 0x04, 0x4e, - 0x3c, 0x92, 0x63, 0xbc, 0x00, 0xc4, 0x0f, 0x80, 0x78, 0xc6, 0x63, 0x39, 0x86, 0x24, 0x36, 0x70, - 0xa6, 0x31, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xed, 0x4d, 0x7e, 0xd8, 0x65, 0x03, 0x00, 0x00, + 0x91, 0x02, 0xe5, 0x0b, 0x09, 0x70, 0x31, 0xfb, 0x7a, 0x3a, 0x4b, 0xb0, 0x82, 0x85, 0x99, 0x73, + 0x3d, 0x9d, 0x51, 0x02, 0x88, 0x8d, 0x40, 0x00, 0xe5, 0xa1, 0x3a, 0xbc, 0x58, 0xc8, 0x10, 0x3d, + 0x2a, 0xc4, 0x51, 0xa2, 0x02, 0xa4, 0xd8, 0x31, 0x39, 0x39, 0xb5, 0xa0, 0x84, 0xac, 0x08, 0x31, + 0x5a, 0xce, 0xc8, 0xc5, 0x06, 0xb1, 0x4c, 0xc8, 0x8c, 0x8b, 0xcb, 0x23, 0x31, 0x2f, 0x25, 0x27, + 0x15, 0x14, 0xbd, 0x42, 0xd0, 0xd0, 0x46, 0x49, 0x9f, 0x52, 0x58, 0x04, 0x8b, 0x85, 0x74, 0xb9, + 0xb8, 0x21, 0xfa, 0xc0, 0xc1, 0x2f, 0xc4, 0x87, 0x14, 0x4d, 0x20, 0x3d, 0xa8, 0xfc, 0x62, 0x84, + 0x35, 0x20, 0xa7, 0xc3, 0xac, 0x41, 0x89, 0x2c, 0x29, 0x2c, 0x82, 0xc5, 0x4e, 0x02, 0x27, 0x1e, + 0xc9, 0x31, 0x5e, 0x00, 0xe2, 0x07, 0x40, 0x3c, 0xe3, 0xb1, 0x1c, 0x43, 0x12, 0x1b, 0x38, 0x1b, + 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf4, 0xb5, 0x24, 0xc6, 0x77, 0x03, 0x00, 0x00, } diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go new file mode 100644 index 000000000..c84f1b77b --- /dev/null +++ b/ttnctl/cmd/join.go @@ -0,0 +1,210 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "crypto/aes" + "encoding/base64" + "net" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// joinCmd represents the `join-request` command +var joinCmd = &cobra.Command{ + Use: "join [DevEUI] [AppKey]", + Short: "Send an join request message to the network", + Long: `Send an join request to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + ctx.Fatalf("Insufficient arguments") + } + + // Parse parameters + devEUIRaw, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + var devEUI lorawan.EUI64 + copy(devEUI[:], devEUIRaw) + + appKeyRaw, err := util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid appKey: %s", err) + } + var appKey lorawan.AES128Key + copy(appKey[:], appKeyRaw) + + appEUIRaw, err := util.Parse64(viper.GetString("join.app-eui")) + if err != nil { + ctx.Fatalf("Invalid appEUI: %s", err) + } + var appEUI lorawan.EUI64 + copy(appEUI[:], appEUIRaw) + + // Generate a DevNonce + var devNonce [2]byte + copy(devNonce[:], util.RandToken()) + + // Lorawan Payload + joinPayload := lorawan.JoinRequestPayload{ + AppEUI: appEUI, + DevEUI: devEUI, + DevNonce: devNonce, + } + phyPayload := lorawan.NewPHYPayload(true) + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.JoinRequest, + Major: lorawan.LoRaWANR1, + } + phyPayload.MACPayload = &joinPayload + if err := phyPayload.SetMIC(appKey); err != nil { + ctx.Fatalf("Unable to set MIC: %s", err) + } + + addr, err := net.ResolveUDPAddr("udp", viper.GetString("router.address")) + if err != nil { + ctx.Fatalf("Couldn't resolve UDP address: %s", err) + } + conn, err := net.DialUDP("udp", nil, addr) + if err != nil { + ctx.Fatalf("Couldn't Dial UDP connection: %s", err) + } + + // Handle downlink + chdown := make(chan bool) + go func() { + // Get Ack + buf := make([]byte, 1024) + n, err := conn.Read(buf) + if err != nil { + ctx.Fatalf("Error receiving udp datagram: %s", err) + } + pkt := new(semtech.Packet) + if err := pkt.UnmarshalBinary(buf[:n]); err != nil { + ctx.Fatalf("Invalid udp response: %s", err) + } + ctx.Infof("Received Ack: %s", pkt) + + // Get Downlink, if any + buf = make([]byte, 1024) + n, err = conn.Read(buf) + if err != nil { + ctx.Fatalf("Error receiving udp datagram: %s", err) + } + pkt = new(semtech.Packet) + if err = pkt.UnmarshalBinary(buf[:n]); err != nil { + ctx.Fatalf("Invalid udp response: %s", err) + } + ctx.Infof("Received Downlink: %s", pkt) + defer func() { chdown <- true }() + + if pkt.Payload == nil || pkt.Payload.TXPK == nil || pkt.Payload.TXPK.Data == nil { + ctx.Fatalf("No payload available in downlink response") + } + + data, err := base64.RawStdEncoding.DecodeString(*pkt.Payload.TXPK.Data) + if err != nil { + ctx.Fatalf("Unable to decode data payload: %s", err) + } + + payload := lorawan.NewPHYPayload(false) + if err := payload.UnmarshalBinary(data); err != nil { + ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) + } + + if err := payload.DecryptJoinAcceptPayload(appKey); err != nil { + ctx.Fatalf("Unable to decrypt MACPayload: %s", err) + } + + joinAccept, ok := payload.MACPayload.(*lorawan.JoinAcceptPayload) + if !ok { + ctx.Fatalf("Unable to retrieve LoRaWAN Join-Accept Payload") + } + + buf = make([]byte, 16) + copy(buf[1:4], joinAccept.AppNonce[:]) + copy(buf[4:7], joinAccept.NetID[:]) + copy(buf[7:9], devNonce[:]) + + block, err := aes.NewCipher(appKey[:]) + if err != nil || block.BlockSize() != 16 { + ctx.Fatalf("Unable to create cipher to generate keys: %s", err) + } + + var nwkSKey, appSKey [16]byte + buf[0] = 0x1 + block.Encrypt(nwkSKey[:], buf) + buf[0] = 0x2 + block.Encrypt(appSKey[:], buf) + + ctx.Info("Network Joined.") + ctx.Infof("Device Address: %X", joinAccept.DevAddr[:]) + ctx.Infof("Network Session Key: %X", nwkSKey) + ctx.Infof("Application Session Key: %X", appSKey) + ctx.Infof("Available Frequencies: %v", joinAccept.CFList) + }() + + // Router Packet + data, err := phyPayload.MarshalBinary() + if err != nil { + ctx.Fatalf("Couldn't construct LoRaWAN physical payload: %s", err) + } + encoded := strings.Trim(base64.StdEncoding.EncodeToString(data), "=") + payload := semtech.Packet{ + Identifier: semtech.PUSH_DATA, + Token: util.RandToken(), + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Version: semtech.VERSION, + Payload: &semtech.Payload{ + RXPK: []semtech.RXPK{ + { + Rssi: pointer.Int32(util.RandRssi()), + Lsnr: pointer.Float32(util.RandLsnr()), + Freq: pointer.Float32(util.RandFreq()), + Datr: pointer.String(util.RandDatr()), + Codr: pointer.String(util.RandCodr()), + Modu: pointer.String("LoRa"), + Tmst: pointer.Uint32(1), + Data: &encoded, + }, + }, + }, + } + + ctx.Infof("Sending packet: %s", payload.String()) + + data, err = payload.MarshalBinary() + if err != nil { + ctx.Fatalf("Unable to construct framepayload: %v", data) + } + + _, err = conn.Write(data) + if err != nil { + ctx.Fatal("Unable to send payload") + } + + select { + case <-chdown: + case <-time.After(2 * time.Second): + } + }, +} + +func init() { + RootCmd.AddCommand(joinCmd) + + joinCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") + viper.BindPFlag("router.address", joinCmd.Flags().Lookup("ttn-router")) + + joinCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") + viper.BindPFlag("join.app-eui", joinCmd.Flags().Lookup("app-eui")) +} From 546739624372b4c341fc738c0be6372413091a11 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 00:45:29 +0100 Subject: [PATCH 1168/2266] [feature/manager] Harmonize name for AuthBrokerClient --- core/components/broker/brokerToken.go | 4 +- core/components/handler/handler.go | 2 +- core/components/handler/handler_test.go | 60 ++++++++--------- core/components/router/router.go | 6 +- core/components/router/router_test.go | 66 +++++++++---------- core/core.go | 29 ++++++++ core/{msgp.go => definitions.go} | 36 ++-------- core/{msgp_gen.go => definitions_gen.go} | 0 ...gp_gen_test.go => definitions_gen_test.go} | 0 core/interfaces.go | 20 ------ core/mocks/mocks.go | 24 +++---- 11 files changed, 117 insertions(+), 130 deletions(-) rename core/{msgp.go => definitions.go} (71%) rename core/{msgp_gen.go => definitions_gen.go} (100%) rename core/{msgp_gen_test.go => definitions_gen_test.go} (100%) delete mode 100644 core/interfaces.go diff --git a/core/components/broker/brokerToken.go b/core/components/broker/brokerToken.go index 3d9d9e5fb..18702f3c2 100644 --- a/core/components/broker/brokerToken.go +++ b/core/components/broker/brokerToken.go @@ -20,7 +20,7 @@ type brokerClient struct { } // NewClient instantiates a new core.Broker client -func NewClient(netAddr string) (core.Broker, error) { +func NewClient(netAddr string) (core.AuthBrokerClient, error) { brokerConn, err := grpc.Dial(netAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) // Add Credentials Token if err != nil { return nil, err @@ -34,7 +34,7 @@ func NewClient(netAddr string) (core.Broker, error) { } // BeginToken implements the core.Broker interface -func (b *brokerClient) BeginToken(token string) core.Broker { +func (b *brokerClient) BeginToken(token string) core.AuthBrokerClient { b.Lock() b.token = token return b diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index dbfd0afcc..58d0ebe1c 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -60,7 +60,7 @@ type Interface interface { // Components is used to make handler instantiation easier type Components struct { - Broker core.Broker + Broker core.AuthBrokerClient Ctx log.Interface DevStorage DevStorage PktStorage PktStorage diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index f1a8a4ecb..22749373b 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -25,7 +25,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -63,7 +63,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -101,7 +101,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, @@ -139,7 +139,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, @@ -177,7 +177,7 @@ func TestHandleDataDown(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataDownHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, @@ -216,7 +216,7 @@ func TestHandleDataUp(t *testing.T) { devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: new(core.Metadata), @@ -258,7 +258,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataUpHandlerReq{ Payload: nil, Metadata: new(core.Metadata), @@ -300,7 +300,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: nil, @@ -342,7 +342,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: new(core.Metadata), @@ -384,7 +384,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() req := &core.DataUpHandlerReq{ Payload: []byte("Payload"), Metadata: new(core.Metadata), @@ -433,7 +433,7 @@ func TestHandleDataUp(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -498,7 +498,7 @@ func TestHandleDataUp(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -608,7 +608,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -712,7 +712,7 @@ func TestHandleDataUp(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -821,7 +821,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() appAdapter.Failures["HandleData"] = fmt.Errorf("Mock Error") - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -887,7 +887,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.Failures["dequeue"] = errors.New(errors.Operational, "Mock Error") appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -945,7 +945,7 @@ func TestHandleDataUp(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload1, fcnt1 := []byte("Payload1"), uint32(14) devAddr1, appSKey1 := lorawan.DevAddr([4]byte{1, 2, 3, 4}), [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} payload2, fcnt2 := []byte("Payload2"), uint32(35346) @@ -1072,7 +1072,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -1179,7 +1179,7 @@ func TestHandleDataUp(t *testing.T) { pktStorage := NewMockPktStorage() pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload, fcnt := []byte("Payload"), uint32(14) encoded, err := lorawan.EncryptFRMPayload( devStorage.OutRead.Entry.AppSKey, @@ -1267,7 +1267,7 @@ func TestHandleJoin(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} @@ -1356,7 +1356,7 @@ func TestHandleJoin(t *testing.T) { pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() appAdapter.Failures["HandleJoin"] = errors.New(errors.Operational, "Mock Error") - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} @@ -1445,7 +1445,7 @@ func TestHandleJoin(t *testing.T) { devStorage.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} @@ -1511,7 +1511,7 @@ func TestHandleJoin(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} @@ -1577,7 +1577,7 @@ func TestHandleJoin(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} @@ -1640,7 +1640,7 @@ func TestHandleJoin(t *testing.T) { devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() // Expect var wantErr = ErrNotFound @@ -1690,7 +1690,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() // Expect var wantErr = ErrStructural @@ -1740,7 +1740,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() // Expect var wantErr = ErrStructural @@ -1790,7 +1790,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() // Expect var wantErr = ErrStructural @@ -1829,7 +1829,7 @@ func TestHandleJoin(t *testing.T) { devStorage := NewMockDevStorage() pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() // Expect var wantErr = ErrStructural @@ -1901,7 +1901,7 @@ func TestHandleJoin(t *testing.T) { } pktStorage := NewMockPktStorage() appAdapter := mocks.NewAppClient() - broker := mocks.NewBroker() + broker := mocks.NewAuthBrokerClient() payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} diff --git a/core/components/router/router.go b/core/components/router/router.go index 6ddbb397a..224c56bb6 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -37,14 +37,14 @@ type component struct { NetAddr string } -// Interface defines the Router interface -type Interface interface { +// Server defines the Router Server interface +type Server interface { core.RouterServer Start() error } // New constructs a new router -func New(c Components, o Options) Interface { +func New(c Components, o Options) Server { return component{Components: c, NetAddr: o.NetAddr} } diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 9f14ce833..cf4b4270f 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -191,7 +191,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -246,7 +246,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -301,7 +301,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -356,7 +356,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -396,7 +396,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -451,7 +451,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -531,9 +531,9 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBroker() + br1 := mocks.NewAuthBrokerClient() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBroker() + br2 := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -609,9 +609,9 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBroker() + br1 := mocks.NewAuthBrokerClient() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBroker() + br2 := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -688,7 +688,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -757,7 +757,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -837,7 +837,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -909,9 +909,9 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBroker() + br1 := mocks.NewAuthBrokerClient() br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBroker() + br2 := mocks.NewAuthBrokerClient() br2.Failures["HandleData"] = errors.New(errors.Operational, "Mock Error") st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -988,8 +988,8 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBroker() - br2 := mocks.NewBroker() + br1 := mocks.NewAuthBrokerClient() + br2 := mocks.NewAuthBrokerClient() st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -1065,7 +1065,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -1146,7 +1146,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleData.Res = &core.DataBrokerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -1250,7 +1250,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleData.Res = &core.DataBrokerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -1352,7 +1352,7 @@ func TestHandleData(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleData.Res = &core.DataBrokerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ @@ -1453,9 +1453,9 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() - br1 := mocks.NewBroker() + br1 := mocks.NewAuthBrokerClient() br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBroker() + br2 := mocks.NewAuthBrokerClient() br2.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1533,9 +1533,9 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br1 := mocks.NewBroker() + br1 := mocks.NewAuthBrokerClient() br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewBroker() + br2 := mocks.NewAuthBrokerClient() br2.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1610,7 +1610,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1671,7 +1671,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1734,7 +1734,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1797,7 +1797,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1860,7 +1860,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -1922,7 +1922,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") st := NewMockBrkStorage() gt := NewMockGtwStorage() @@ -1990,7 +1990,7 @@ func TestHandleJoin(t *testing.T) { // Build dm := mocks.NewDutyManager() - br := mocks.NewBroker() + br := mocks.NewAuthBrokerClient() br.OutHandleJoin.Res = &core.JoinBrokerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: []byte{1, 2, 3, 4}, @@ -2051,7 +2051,7 @@ func TestStart(t *testing.T) { router := New(Components{ Ctx: GetLogger(t, "Router"), DutyManager: mocks.NewDutyManager(), - Brokers: []core.BrokerClient{mocks.NewBroker()}, + Brokers: []core.BrokerClient{mocks.NewAuthBrokerClient()}, BrkStorage: NewMockBrkStorage(), GtwStorage: NewMockGtwStorage(), }, Options{NetAddr: "localhost:8886"}) diff --git a/core/core.go b/core/core.go index 718ed2f45..e72e4c9f3 100644 --- a/core/core.go +++ b/core/core.go @@ -6,6 +6,8 @@ package core import ( + "reflect" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" ) @@ -70,6 +72,33 @@ func NewLoRaWANData(reqPayload *LoRaWANData, uplink bool) (lorawan.PHYPayload, e return payload, nil } +// ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid +// AppMetadata ready to be marshaled to json +func ProtoMetaToAppMeta(srcs ...*Metadata) []AppMetadata { + var dest []AppMetadata + + for _, src := range srcs { + if src == nil { + continue + } + to := new(AppMetadata) + v := reflect.ValueOf(src).Elem() + t := v.Type() + d := reflect.ValueOf(to).Elem() + + for i := 0; i < t.NumField(); i++ { + field := t.Field(i).Name + if d.FieldByName(field).CanSet() { + d.FieldByName(field).Set(v.Field(i)) + } + } + + dest = append(dest, *to) + } + + return dest +} + // MarshalBinary implements the encoding.BinaryMarshaler interface func (m StatsMetadata) MarshalBinary() ([]byte, error) { return m.Marshal() diff --git a/core/msgp.go b/core/definitions.go similarity index 71% rename from core/msgp.go rename to core/definitions.go index 520969f95..52553ab64 100644 --- a/core/msgp.go +++ b/core/definitions.go @@ -5,10 +5,6 @@ package core -import ( - "reflect" -) - // DataUpAppReq represents the actual payloads sent to application on uplink type DataUpAppReq struct { Payload []byte `msg:"payload" json:"payload"` @@ -46,29 +42,11 @@ type ABPSubAppReq struct { AppSKey string `msg:"apps_key" json:"apps_key"` } -// ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid -// AppMetadata ready to be marshaled to json -func ProtoMetaToAppMeta(srcs ...*Metadata) []AppMetadata { - var dest []AppMetadata - - for _, src := range srcs { - if src == nil { - continue - } - to := new(AppMetadata) - v := reflect.ValueOf(src).Elem() - t := v.Type() - d := reflect.ValueOf(to).Elem() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i).Name - if d.FieldByName(field).CanSet() { - d.FieldByName(field).Set(v.Field(i)) - } - } - - dest = append(dest, *to) - } - - return dest +// AuthBrokerClient gathers both BrokerClient & BrokerManagerClient interfaces with additional +// method to add a custom token to each rpc requests's metadata +type AuthBrokerClient interface { + BrokerClient + BrokerManagerClient + BeginToken(token string) AuthBrokerClient + EndToken() } diff --git a/core/msgp_gen.go b/core/definitions_gen.go similarity index 100% rename from core/msgp_gen.go rename to core/definitions_gen.go diff --git a/core/msgp_gen_test.go b/core/definitions_gen_test.go similarity index 100% rename from core/msgp_gen_test.go rename to core/definitions_gen_test.go diff --git a/core/interfaces.go b/core/interfaces.go deleted file mode 100644 index 814359ff2..000000000 --- a/core/interfaces.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -type Router interface { - RouterClient -} - -type Broker interface { - BrokerClient - BrokerManagerClient - BeginToken(token string) Broker - EndToken() -} - -type Handler interface { - HandlerClient - HandlerManagerClient -} diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 0bd0b7ee4..450e3164e 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -117,8 +117,8 @@ func (m *HandlerClient) HandleDataDown(ctx context.Context, in *core.DataDownHan return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] } -// Broker mocks the core.Broker interface -type Broker struct { +// AuthBrokerClient mocks the core.AuthBrokerClient interface +type AuthBrokerClient struct { Failures map[string]error InHandleData struct { Ctx context.Context @@ -168,15 +168,15 @@ type Broker struct { } } -// NewBroker creates a new mock Broker -func NewBroker() *Broker { - return &Broker{ +// NewAuthBrokerClient creates a new mock AuthBrokerClient +func NewAuthBrokerClient() *AuthBrokerClient { + return &AuthBrokerClient{ Failures: make(map[string]error), } } // HandleData implements the core.Broker interface -func (m *Broker) HandleData(ctx context.Context, in *core.DataBrokerReq, opts ...grpc.CallOption) (*core.DataBrokerRes, error) { +func (m *AuthBrokerClient) HandleData(ctx context.Context, in *core.DataBrokerReq, opts ...grpc.CallOption) (*core.DataBrokerRes, error) { m.InHandleData.Ctx = ctx m.InHandleData.Req = in m.InHandleData.Opts = opts @@ -184,7 +184,7 @@ func (m *Broker) HandleData(ctx context.Context, in *core.DataBrokerReq, opts .. } // HandleJoin implements the core.Broker interface -func (m *Broker) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts ...grpc.CallOption) (*core.JoinBrokerRes, error) { +func (m *AuthBrokerClient) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts ...grpc.CallOption) (*core.JoinBrokerRes, error) { m.InHandleJoin.Ctx = ctx m.InHandleJoin.Req = in m.InHandleJoin.Opts = opts @@ -192,7 +192,7 @@ func (m *Broker) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts .. } // UpsertABP implements the core.Broker interface -func (m *Broker) UpsertABP(ctx context.Context, in *core.UpsertABPBrokerReq, opts ...grpc.CallOption) (*core.UpsertABPBrokerRes, error) { +func (m *AuthBrokerClient) UpsertABP(ctx context.Context, in *core.UpsertABPBrokerReq, opts ...grpc.CallOption) (*core.UpsertABPBrokerRes, error) { m.InUpsertABP.Ctx = ctx m.InUpsertABP.Req = in m.InUpsertABP.Opts = opts @@ -200,7 +200,7 @@ func (m *Broker) UpsertABP(ctx context.Context, in *core.UpsertABPBrokerReq, opt } // ValidateOTAA implements the core.Broker interface -func (m *Broker) ValidateOTAA(ctx context.Context, in *core.ValidateOTAABrokerReq, opts ...grpc.CallOption) (*core.ValidateOTAABrokerRes, error) { +func (m *AuthBrokerClient) ValidateOTAA(ctx context.Context, in *core.ValidateOTAABrokerReq, opts ...grpc.CallOption) (*core.ValidateOTAABrokerRes, error) { m.InValidateOTAA.Ctx = ctx m.InValidateOTAA.Req = in m.InValidateOTAA.Opts = opts @@ -208,7 +208,7 @@ func (m *Broker) ValidateOTAA(ctx context.Context, in *core.ValidateOTAABrokerRe } // ListDevices implements the core.Broker interface -func (m *Broker) ListDevices(ctx context.Context, in *core.ListDevicesBrokerReq, opts ...grpc.CallOption) (*core.ListDevicesBrokerRes, error) { +func (m *AuthBrokerClient) ListDevices(ctx context.Context, in *core.ListDevicesBrokerReq, opts ...grpc.CallOption) (*core.ListDevicesBrokerRes, error) { m.InListDevices.Ctx = ctx m.InListDevices.Req = in m.InListDevices.Opts = opts @@ -216,13 +216,13 @@ func (m *Broker) ListDevices(ctx context.Context, in *core.ListDevicesBrokerReq, } // BeginToken implements the core.Broker interface -func (m *Broker) BeginToken(token string) core.Broker { +func (m *AuthBrokerClient) BeginToken(token string) core.AuthBrokerClient { m.InBeginToken.Token = token return m } // EndToken implements the core.Broker interface -func (m *Broker) EndToken() { +func (m *AuthBrokerClient) EndToken() { m.InEndToken.Called = true } From 63ccd20f6acbd5ab57c63b13ba648d5a42bf0b5d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 03:01:02 +0100 Subject: [PATCH 1169/2266] [feature/manager] Implement Token verification --- core/components/broker/broker.go | 10 +++- core/components/broker/brokerManager.go | 30 +++++++++- core/components/broker/brokerToken.go | 9 ++- core/components/handler/handlerManager.go | 18 ++++-- core/components/handler/handlerToken.go | 69 +++++++++++++++++++++++ core/definitions.go | 9 +++ ttnctl/cmd/device.go | 38 +++++-------- 7 files changed, 152 insertions(+), 31 deletions(-) create mode 100644 core/components/handler/handlerToken.go diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 40afc12b0..afed80e76 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -18,6 +18,7 @@ import ( // component implements the core.BrokerServer interface type component struct { Components + SecretKey [32]byte NetAddrUp string NetAddrDown string MaxDevNonces uint @@ -34,6 +35,7 @@ type Components struct { type Options struct { NetAddrUp string NetAddrDown string + SecretKey [32]byte } // Interface defines the Broker interface @@ -45,7 +47,13 @@ type Interface interface { // New construct a new Broker component func New(c Components, o Options) Interface { - return component{Components: c, NetAddrUp: o.NetAddrUp, NetAddrDown: o.NetAddrDown, MaxDevNonces: 10} + return component{ + Components: c, + NetAddrUp: o.NetAddrUp, + NetAddrDown: o.NetAddrDown, + MaxDevNonces: 10, + SecretKey: [32]byte{14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 42, 42, 42, 42, 42, 42}, // TODO Use options & ENV var + } } // Start actually runs the component and starts the rpc server diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 6a4510145..099938faf 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -4,11 +4,14 @@ package broker import ( + "fmt" "regexp" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" + jwt "github.com/dgrijalva/jwt-go" "golang.org/x/net/context" + "google.golang.org/grpc/metadata" ) // ListDevices implements the core.BrokerManagerServer interface @@ -29,7 +32,9 @@ func (b component) ValidateOTAA(bctx context.Context, req *core.ValidateOTAABrok } // 2. Verify and validate the token - // TODO + if err := b.validateToken(bctx, req.AppEUI); err != nil { + return new(core.ValidateOTAABrokerRes), err + } // 3. Update the internal storage b.Ctx.WithField("AppEUI", req.AppEUI).Debug("Request accepted by broker. Registering / Updating App.") @@ -59,7 +64,9 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) } // 2. Verify and validate the token - // TODO + if err := b.validateToken(bctx, req.AppEUI); err != nil { + return new(core.UpsertABPBrokerRes), err + } // 3. Update the internal storage b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering device.") @@ -81,3 +88,22 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) // 4. Done. return new(core.UpsertABPBrokerRes), nil } + +// validateToken verify an OAuth Bearer token pass through metadata during RPC +func (b component) validateToken(ctx context.Context, appEUI []byte) error { + re := regexp.MustCompile("[[:alnum:]=/+]+\\.[[:alnum:]=/+]+\\.[[:alnum:]=/+]+") + meta, ok := metadata.FromContext(ctx) + if !ok || len(meta["token"]) < 1 || !re.MatchString(meta["token"][0]) { + return errors.New(errors.Structural, "Unable to retrieve token from metadata") + } + token, err := jwt.Parse(meta["token"][0], func(token *jwt.Token) (interface{}, error) { + return b.SecretKey[:], nil + }) + if err != nil { + return errors.New(errors.Structural, "Unable to parse token") + } + if !token.Valid || token.Claims["sub"] != fmt.Sprintf("%X", appEUI) { + return errors.New(errors.Structural, "Invalid token.") + } + return nil +} diff --git a/core/components/broker/brokerToken.go b/core/components/broker/brokerToken.go index 18702f3c2..786e094af 100644 --- a/core/components/broker/brokerToken.go +++ b/core/components/broker/brokerToken.go @@ -21,13 +21,20 @@ type brokerClient struct { // NewClient instantiates a new core.Broker client func NewClient(netAddr string) (core.AuthBrokerClient, error) { - brokerConn, err := grpc.Dial(netAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) // Add Credentials Token + tokener := tokenCredentials{} + brokerConn, err := grpc.Dial( + netAddr, + grpc.WithInsecure(), // TODO Use of TLS + grpc.WithPerRPCCredentials(&tokener), + grpc.WithTimeout(time.Second*15), + ) if err != nil { return nil, err } broker := core.NewBrokerClient(brokerConn) brokerManager := core.NewBrokerManagerClient(brokerConn) return &brokerClient{ + tokenCredentials: &tokener, BrokerClient: broker, BrokerManagerClient: brokerManager, }, nil diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index e5162ff2d..7a7ba9a42 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -7,6 +7,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" + "google.golang.org/grpc/metadata" ) // ListDevices implements the core.HandlerManagerServer interface @@ -26,12 +27,17 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq } // 2. Forward to the broker firt -> The Broker also does the token verification - _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ + var token string + if meta, ok := metadata.FromContext(bctx); ok && len(meta["token"]) > 0 { + token = meta["token"][0] + } + _, err := h.Broker.BeginToken(token).UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, NetAddress: h.NetAddr, }) + h.Broker.EndToken() if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) @@ -68,12 +74,16 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR } // 2. Notify the broker -> The Broker also does the token verification - // TODO Extract token from metadata and add it to request metadata - _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ + var token string + h.Ctx.WithField("meta", bctx).Debug("Trying to get Meta") + if meta, ok := metadata.FromContext(bctx); ok && len(meta["token"]) > 0 { + token = meta["token"][0] + } + _, err := h.Broker.BeginToken(token).ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ NetAddress: h.NetAddr, AppEUI: req.AppEUI, }) - + h.Broker.EndToken() if err != nil { h.Ctx.WithError(err).Debug("Broker rejected OTAA") return new(core.UpsertOTAAHandlerRes), errors.New(errors.Operational, err) diff --git a/core/components/handler/handlerToken.go b/core/components/handler/handlerToken.go new file mode 100644 index 000000000..d7f760dbc --- /dev/null +++ b/core/components/handler/handlerToken.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +type handlerClient struct { + sync.Mutex + *tokenCredentials + core.HandlerClient + core.HandlerManagerClient +} + +// NewClient instantiates a new core.handler client +func NewClient(netAddr string) (core.AuthHandlerClient, error) { + tokener := tokenCredentials{} + handlerConn, err := grpc.Dial( + netAddr, + grpc.WithInsecure(), // TODO Use of TLS + grpc.WithPerRPCCredentials(&tokener), + grpc.WithTimeout(time.Second*15), + ) + if err != nil { + return nil, err + } + handler := core.NewHandlerClient(handlerConn) + handlerManager := core.NewHandlerManagerClient(handlerConn) + return &handlerClient{ + tokenCredentials: &tokener, + HandlerClient: handler, + HandlerManagerClient: handlerManager, + }, nil +} + +// BeginToken implements the core.handler interface +func (b *handlerClient) BeginToken(token string) core.AuthHandlerClient { + b.Lock() + b.token = token + return b +} + +// EndToken implements the core.handler interface +func (b *handlerClient) EndToken() { + b.Unlock() +} + +type tokenCredentials struct { + token string +} + +// GetRequestMetadata implements the grpc/credentials.Credentials interface +func (t tokenCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { + return map[string]string{ + "token": t.token, + }, nil +} + +// RequireTransportSecurity implements the grpc/credentials.Credentials interface +func (t tokenCredentials) RequireTransportSecurity() bool { + return false // TODO -> True, Need use of TLS to communicate the token +} diff --git a/core/definitions.go b/core/definitions.go index 52553ab64..21bdac658 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -50,3 +50,12 @@ type AuthBrokerClient interface { BeginToken(token string) AuthBrokerClient EndToken() } + +// AuthHandlerClient gathers both HandlerClient & HandlerManagerClient interfaces with additional +// method to add a custom token to each rpc requests's metadata +type AuthHandlerClient interface { + HandlerClient + HandlerManagerClient + BeginToken(token string) AuthHandlerClient + EndToken() +} diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 9c3aad31b..cb921b715 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -6,26 +6,23 @@ package cmd import ( "fmt" - "golang.org/x/net/context" - - "google.golang.org/grpc" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/gosuri/uitable" "github.com/spf13/cobra" "github.com/spf13/viper" + "golang.org/x/net/context" ) const emptyCell = "-" -func getHandlerManager() (*grpc.ClientConn, core.HandlerManagerClient) { - conn, err := grpc.Dial(viper.GetString("ttn-handler"), grpc.WithInsecure()) +func getHandlerManager() core.AuthHandlerClient { + cli, err := handler.NewClient(viper.GetString("ttn-handler")) if err != nil { ctx.Fatalf("Could not connect: %v", err) } - - return conn, core.NewHandlerManagerClient(conn) + return cli } // devicesCmd represents the `devices` command @@ -39,13 +36,12 @@ var devicesCmd = &cobra.Command{ ctx.Fatalf("Invalid AppEUI: %s", err) } - conn, manager := getHandlerManager() - defer conn.Close() + manager := getHandlerManager() - res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ + res, err := manager.BeginToken(viper.GetString("app-token")).ListDevices(context.Background(), &core.ListDevicesHandlerReq{ AppEUI: appEUI, }) - + manager.EndToken() if err != nil { ctx.WithError(err).Fatal("Could not get device list") } @@ -103,15 +99,13 @@ var devicesRegisterCmd = &cobra.Command{ ctx.Fatalf("Invalid AppKey: %s", err) } - conn, manager := getHandlerManager() - defer conn.Close() - - res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ + manager := getHandlerManager() + res, err := manager.BeginToken(viper.GetString("app-token")).UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, }) - + manager.EndToken() if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } @@ -149,16 +143,14 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ ctx.Fatalf("Invalid AppSKey: %s", err) } - conn, manager := getHandlerManager() - defer conn.Close() - - res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ + manager := getHandlerManager() + res, err := manager.BeginToken(viper.GetString("app-token")).UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ AppEUI: appEUI, DevAddr: devAddr, AppSKey: appSKey, NwkSKey: nwkSKey, }) - + manager.EndToken() if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } @@ -179,6 +171,6 @@ func init() { devicesCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") viper.BindPFlag("app-eui", devicesCmd.PersistentFlags().Lookup("app-eui")) - devicesCmd.PersistentFlags().String("app-token", "0102030405060708", "The app Token to use") + devicesCmd.PersistentFlags().String("app-token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJUVE4tSEFORExFUi0xIiwiaXNzIjoiVGhlVGhpbmdzVGhlTmV0d29yayIsInN1YiI6IjAxMDIwMzA0MDUwNjA3MDgifQ.zMHNXAVgQj672lwwDVmfYshpMvPwm6A8oNWJ7teGS2A", "The app Token to use") viper.BindPFlag("app-token", devicesCmd.PersistentFlags().Lookup("app-token")) } From 96e7ade88a82863f63ac678ffba7bce9cb8ea4bc Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 03:48:38 +0100 Subject: [PATCH 1170/2266] [feature/manager] Implement ListDevices --- core/components/handler/devStorage.go | 18 ++++++---- core/components/handler/handlerManager.go | 43 +++++++++++++++++++++-- core/components/handler/pktStorage.go | 14 ++++---- core/storage/storage.go | 38 ++++++++++++++++++++ core/storage/storage_test.go | 19 ++++++++++ 5 files changed, 117 insertions(+), 15 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 6b1b6cffd..b28050a0d 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -15,6 +15,7 @@ import ( // DevStorage gives a facade to manipulate the handler devices database type DevStorage interface { read(appEUI []byte, devEUI []byte) (devEntry, error) + readAll(appEUI []byte) ([]devEntry, error) upsert(entry devEntry) error done() error } @@ -47,20 +48,25 @@ func NewDevStorage(name string) (DevStorage, error) { // read implements the handler.DevStorage interface func (s *devStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { - itf, err := s.db.Read(nil, &devEntry{}, appEUI, devEUI) + itf, err := s.db.Read(devEUI, &devEntry{}, appEUI) if err != nil { return devEntry{}, err } - entries, ok := itf.([]devEntry) - if !ok || len(entries) != 1 { - return devEntry{}, errors.New(errors.Structural, "Invalid stored entry") + return itf.([]devEntry)[0], nil // Type and dimensio guaranteed by db.Read() +} + +// readAll implements the handler.DevStorage interface +func (s *devStorage) readAll(appEUI []byte) ([]devEntry, error) { + itf, err := s.db.ReadAll(&devEntry{}, appEUI) + if err != nil { + return nil, err } - return entries[0], nil + return itf.([]devEntry), nil } // upsert implements the handler.DevStorage interface func (s *devStorage) upsert(entry devEntry) error { - return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) + return s.db.Update(entry.DevEUI, []encoding.BinaryMarshaler{entry}, entry.AppEUI) } // done implements the handler.DevStorage interface diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 7a7ba9a42..f761fc070 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -12,7 +12,47 @@ import ( // ListDevices implements the core.HandlerManagerServer interface func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandlerReq) (*core.ListDevicesHandlerRes, error) { - return new(core.ListDevicesHandlerRes), errors.New(errors.Implementation, "Not implemented") + h.Ctx.Debug("Handle ListDevices Request") + + // 1. Validate the request + if len(req.AppEUI) != 8 { + err := errors.New(errors.Structural, "Invalid request parameters") + h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") + return new(core.ListDevicesHandlerRes), err + } + + // 2. Validate token & retrieve devices from db + entries, err := h.DevStorage.readAll(req.AppEUI) + if err != nil { + h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") + return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) + } + + // 3. Build the reply, separate OTAA from ABP + var abp []*core.HandlerABPDevice + var otaa []*core.HandlerOTAADevice + for _, dev := range entries { + d := new(devEntry) + *d = dev + if dev.AppKey == nil { + abp = append(abp, &core.HandlerABPDevice{ + DevAddr: d.DevAddr, + NwkSKey: d.NwkSKey[:], + AppSKey: d.AppSKey[:], + }) + } else { + otaa = append(otaa, &core.HandlerOTAADevice{ + DevEUI: d.DevEUI, + DevAddr: d.DevAddr, + NwkSKey: d.NwkSKey[:], + AppSKey: d.AppSKey[:], + AppKey: d.AppKey[:], + }) + } + } + + // 4. Done + return &core.ListDevicesHandlerRes{ABP: abp, OTAA: otaa}, nil } // UpsertABP implements the core.HandlerManager interface @@ -75,7 +115,6 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR // 2. Notify the broker -> The Broker also does the token verification var token string - h.Ctx.WithField("meta", bctx).Debug("Trying to get Meta") if meta, ok := metadata.FromContext(bctx); ok && len(meta["token"]) > 0 { token = meta["token"][0] } diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go index 98e7ded69..9ad39bd48 100644 --- a/core/components/handler/pktStorage.go +++ b/core/components/handler/pktStorage.go @@ -69,7 +69,7 @@ func pop(entries []pktEntry) (pktEntry, []encoding.BinaryMarshaler) { func (s *pktStorage) enqueue(entry pktEntry) error { s.Lock() defer s.Unlock() - itf, err := s.db.Read(nil, &pktEntry{}, entry.AppEUI, entry.DevEUI) + itf, err := s.db.Read(entry.DevEUI, &pktEntry{}, entry.AppEUI) if err != nil && err.(errors.Failure).Nature != errors.NotFound { return err } @@ -79,18 +79,18 @@ func (s *pktStorage) enqueue(entry pktEntry) error { } if len(entries) >= int(s.size) { _, tail := pop(entries) - return s.db.Update(nil, append(tail, entry), entry.AppEUI, entry.DevEUI) + return s.db.Update(entry.DevEUI, append(tail, entry), entry.AppEUI) } // NOTE: We append, even if there're still expired entries, we'll filter them // during dequeuing - return s.db.Append(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) + return s.db.Append(entry.DevEUI, []encoding.BinaryMarshaler{entry}, entry.AppEUI) } // dequeue implements the PktStorage interface func (s *pktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { s.Lock() defer s.Unlock() - itf, err := s.db.Read(nil, &pktEntry{}, appEUI, devEUI) + itf, err := s.db.Read(devEUI, &pktEntry{}, appEUI) if err != nil { return pktEntry{}, err } @@ -103,14 +103,14 @@ func (s *pktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { for _, e := range filtered { replaces = append(replaces, e) } - _ = s.db.Update(nil, replaces, appEUI, devEUI) + _ = s.db.Update(devEUI, replaces, appEUI) } return pktEntry{}, errors.New(errors.NotFound, "There's no available entry") } // Otherwise dequeue the first one and send it head, tail := pop(filtered) - if err := s.db.Update(nil, tail, appEUI, devEUI); err != nil { + if err := s.db.Update(devEUI, tail, appEUI); err != nil { return pktEntry{}, err } return head, nil @@ -120,7 +120,7 @@ func (s *pktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { func (s *pktStorage) peek(appEUI []byte, devEUI []byte) (pktEntry, error) { s.RLock() defer s.RUnlock() - itf, err := s.db.Read(nil, &pktEntry{}, appEUI, devEUI) + itf, err := s.db.Read(devEUI, &pktEntry{}, appEUI) if err != nil { return pktEntry{}, err } diff --git a/core/storage/storage.go b/core/storage/storage.go index f4470d15e..c1ac5751f 100644 --- a/core/storage/storage.go +++ b/core/storage/storage.go @@ -24,6 +24,9 @@ type Interface interface { // of `shape`, possibly of length 0. // The provided type has to implement a binary.Unmarshaler interface Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) + // ReadAll goes through each key of a bucket and return a slice of all values. This implies + // therefore that all values in each key are of the same type (the shape provided) + ReadAll(shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) // Update replaces a set of entries by the new given set Update(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error // Append adds at the end of an existing set the new given set @@ -89,6 +92,41 @@ func getBucket(tx *bolt.Tx, buckets [][]byte) (*bolt.Bucket, error) { return cursor.(*bolt.Bucket), nil } +// ReadAll implements the storage.Interface interface +func (itf store) ReadAll(shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) { + entryType := reflect.TypeOf(shape) + if entryType.Kind() != reflect.Ptr { + return nil, errors.New(errors.Implementation, "Non-pointer shape not supported") + } + + entries := reflect.MakeSlice(reflect.SliceOf(entryType.Elem()), 0, 0) + err := itf.db.View(func(tx *bolt.Tx) error { + bucket, err := getBucket(tx, buckets) + if err != nil { + if err.(errors.Failure).Fault == bolt.ErrTxNotWritable { + return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", buckets)) + } + return err + } + return bucket.ForEach(func(_ []byte, v []byte) error { + r := readwriter.New(v) + r.Read(func(data []byte) { + entry := reflect.New(entryType.Elem()).Interface() + entry.(encoding.BinaryUnmarshaler).UnmarshalBinary(data) + entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) + }) + return r.Err() + }) + }) + if err != nil { + return nil, ensureErr(err) + } + if entries.Len() == 0 { + return nil, errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", buckets)) + } + return entries.Interface(), nil +} + // Read implements the storage.Interface interface func (itf store) Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) { if key == nil { diff --git a/core/storage/storage_test.go b/core/storage/storage_test.go index 22d9e3330..420d14079 100644 --- a/core/storage/storage_test.go +++ b/core/storage/storage_test.go @@ -221,6 +221,22 @@ func TestStoreAndRead(t *testing.T) { // --------------------- + { + Desc(t, "Read All entry of a bucket") + err := itf.Update([]byte{0, 0, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "The"}}, []byte("level1")) + FatalUnless(t, err) + err = itf.Update([]byte{0, 0, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "Things"}}, []byte("level1")) + FatalUnless(t, err) + err = itf.Update([]byte{0, 0, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "Network"}}, []byte("level1")) + FatalUnless(t, err) + entries, err := itf.ReadAll(&testEntry{}, []byte("level1")) + want := []testEntry{{Data: "The"}, {Data: "Things"}, {Data: "Network"}} + CheckErrors(t, nil, err) + Check(t, want, entries, "Entries") + } + + // --------------------- + { Desc(t, "Store, Read, Update, Delete & Reset on closed storage") _ = itf.Close() @@ -228,6 +244,8 @@ func TestStoreAndRead(t *testing.T) { CheckErrors(t, ErrOperational, err) _, err = itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("closeddb")) CheckErrors(t, ErrOperational, err) + _, err = itf.ReadAll(&testEntry{}, []byte("closeddb")) + CheckErrors(t, ErrOperational, err) err = itf.Update([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("closeddb")) CheckErrors(t, ErrOperational, err) err = itf.Delete([]byte{1, 2, 3}, []byte("closeddb")) @@ -235,6 +253,7 @@ func TestStoreAndRead(t *testing.T) { err = itf.Reset([]byte("closeddb")) CheckErrors(t, ErrOperational, err) } + } // ----- Type Utilities From ded671355f4acd4538cc520b98417b93d621f8fc Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 04:14:06 +0100 Subject: [PATCH 1171/2266] [feature/manager] Eventually switch for plain token in message -> Clearer, Cleaner, Better. --- core/broker_manager.pb.go | 325 +++++++++++++--------- core/components/broker/brokerClient.go | 34 +++ core/components/broker/brokerManager.go | 32 ++- core/components/broker/brokerToken.go | 69 ----- core/components/handler/handlerClient.go | 34 +++ core/components/handler/handlerManager.go | 21 +- core/components/handler/handlerToken.go | 69 ----- core/components/handler/mocks_test.go | 12 + core/definitions.go | 10 +- core/handler_manager.pb.go | 212 +++++++++++--- core/mocks/mocks.go | 37 +-- core/protos/broker_manager.proto | 25 +- core/protos/handler_manager.proto | 19 +- ttnctl/cmd/device.go | 12 +- 14 files changed, 509 insertions(+), 402 deletions(-) create mode 100644 core/components/broker/brokerClient.go delete mode 100644 core/components/broker/brokerToken.go create mode 100644 core/components/handler/handlerClient.go delete mode 100644 core/components/handler/handlerToken.go diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index e99df5575..1c7f21bb9 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -21,8 +21,8 @@ UpsertABPBrokerReq UpsertABPBrokerRes BrokerDevice - ListDevicesBrokerReq - ListDevicesBrokerRes + ValidateTokenBrokerReq + ValidateTokenBrokerRes UpsertOTAAHandlerReq UpsertOTAAHandlerRes UpsertABPHandlerReq @@ -85,8 +85,9 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 type ValidateOTAABrokerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - NetAddress string `protobuf:"bytes,2,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` } func (m *ValidateOTAABrokerReq) Reset() { *m = ValidateOTAABrokerReq{} } @@ -107,10 +108,11 @@ func (*ValidateOTAABrokerRes) Descriptor() ([]byte, []int) { } type UpsertABPBrokerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - NetAddress string `protobuf:"bytes,2,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` - DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` + DevAddr []byte `protobuf:"bytes,4,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,5,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` } func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } @@ -137,43 +139,36 @@ func (m *BrokerDevice) String() string { return proto.CompactTextStri func (*BrokerDevice) ProtoMessage() {} func (*BrokerDevice) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{4} } -type ListDevicesBrokerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` +type ValidateTokenBrokerReq struct { + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` } -func (m *ListDevicesBrokerReq) Reset() { *m = ListDevicesBrokerReq{} } -func (m *ListDevicesBrokerReq) String() string { return proto.CompactTextString(m) } -func (*ListDevicesBrokerReq) ProtoMessage() {} -func (*ListDevicesBrokerReq) Descriptor() ([]byte, []int) { +func (m *ValidateTokenBrokerReq) Reset() { *m = ValidateTokenBrokerReq{} } +func (m *ValidateTokenBrokerReq) String() string { return proto.CompactTextString(m) } +func (*ValidateTokenBrokerReq) ProtoMessage() {} +func (*ValidateTokenBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{5} } -type ListDevicesBrokerRes struct { - Devices []*BrokerDevice `protobuf:"bytes,1,rep,name=Devices,json=devices" json:"Devices,omitempty"` +type ValidateTokenBrokerRes struct { } -func (m *ListDevicesBrokerRes) Reset() { *m = ListDevicesBrokerRes{} } -func (m *ListDevicesBrokerRes) String() string { return proto.CompactTextString(m) } -func (*ListDevicesBrokerRes) ProtoMessage() {} -func (*ListDevicesBrokerRes) Descriptor() ([]byte, []int) { +func (m *ValidateTokenBrokerRes) Reset() { *m = ValidateTokenBrokerRes{} } +func (m *ValidateTokenBrokerRes) String() string { return proto.CompactTextString(m) } +func (*ValidateTokenBrokerRes) ProtoMessage() {} +func (*ValidateTokenBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{6} } -func (m *ListDevicesBrokerRes) GetDevices() []*BrokerDevice { - if m != nil { - return m.Devices - } - return nil -} - func init() { proto.RegisterType((*ValidateOTAABrokerReq)(nil), "core.ValidateOTAABrokerReq") proto.RegisterType((*ValidateOTAABrokerRes)(nil), "core.ValidateOTAABrokerRes") proto.RegisterType((*UpsertABPBrokerReq)(nil), "core.UpsertABPBrokerReq") proto.RegisterType((*UpsertABPBrokerRes)(nil), "core.UpsertABPBrokerRes") proto.RegisterType((*BrokerDevice)(nil), "core.BrokerDevice") - proto.RegisterType((*ListDevicesBrokerReq)(nil), "core.ListDevicesBrokerReq") - proto.RegisterType((*ListDevicesBrokerRes)(nil), "core.ListDevicesBrokerRes") + proto.RegisterType((*ValidateTokenBrokerReq)(nil), "core.ValidateTokenBrokerReq") + proto.RegisterType((*ValidateTokenBrokerRes)(nil), "core.ValidateTokenBrokerRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -185,7 +180,7 @@ var _ grpc.ClientConn type BrokerManagerClient interface { ValidateOTAA(ctx context.Context, in *ValidateOTAABrokerReq, opts ...grpc.CallOption) (*ValidateOTAABrokerRes, error) UpsertABP(ctx context.Context, in *UpsertABPBrokerReq, opts ...grpc.CallOption) (*UpsertABPBrokerRes, error) - ListDevices(ctx context.Context, in *ListDevicesBrokerReq, opts ...grpc.CallOption) (*ListDevicesBrokerRes, error) + ValidateToken(ctx context.Context, in *ValidateTokenBrokerReq, opts ...grpc.CallOption) (*ValidateTokenBrokerRes, error) } type brokerManagerClient struct { @@ -214,9 +209,9 @@ func (c *brokerManagerClient) UpsertABP(ctx context.Context, in *UpsertABPBroker return out, nil } -func (c *brokerManagerClient) ListDevices(ctx context.Context, in *ListDevicesBrokerReq, opts ...grpc.CallOption) (*ListDevicesBrokerRes, error) { - out := new(ListDevicesBrokerRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/ListDevices", in, out, c.cc, opts...) +func (c *brokerManagerClient) ValidateToken(ctx context.Context, in *ValidateTokenBrokerReq, opts ...grpc.CallOption) (*ValidateTokenBrokerRes, error) { + out := new(ValidateTokenBrokerRes) + err := grpc.Invoke(ctx, "/core.BrokerManager/ValidateToken", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -228,7 +223,7 @@ func (c *brokerManagerClient) ListDevices(ctx context.Context, in *ListDevicesBr type BrokerManagerServer interface { ValidateOTAA(context.Context, *ValidateOTAABrokerReq) (*ValidateOTAABrokerRes, error) UpsertABP(context.Context, *UpsertABPBrokerReq) (*UpsertABPBrokerRes, error) - ListDevices(context.Context, *ListDevicesBrokerReq) (*ListDevicesBrokerRes, error) + ValidateToken(context.Context, *ValidateTokenBrokerReq) (*ValidateTokenBrokerRes, error) } func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { @@ -259,12 +254,12 @@ func _BrokerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec return out, nil } -func _BrokerManager_ListDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { - in := new(ListDevicesBrokerReq) +func _BrokerManager_ValidateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ValidateTokenBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).ListDevices(ctx, in) + out, err := srv.(BrokerManagerServer).ValidateToken(ctx, in) if err != nil { return nil, err } @@ -284,8 +279,8 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ Handler: _BrokerManager_UpsertABP_Handler, }, { - MethodName: "ListDevices", - Handler: _BrokerManager_ListDevices_Handler, + MethodName: "ValidateToken", + Handler: _BrokerManager_ValidateToken_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -306,16 +301,22 @@ func (m *ValidateOTAABrokerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) } } if len(m.NetAddress) > 0 { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) i += copy(data[i:], m.NetAddress) @@ -356,23 +357,29 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) } } if len(m.NetAddress) > 0 { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) i += copy(data[i:], m.NetAddress) } if m.DevAddr != nil { if len(m.DevAddr) > 0 { - data[i] = 0x1a + data[i] = 0x22 i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) i += copy(data[i:], m.DevAddr) @@ -380,7 +387,7 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { } if m.NwkSKey != nil { if len(m.NwkSKey) > 0 { - data[i] = 0x22 + data[i] = 0x2a i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) @@ -449,7 +456,7 @@ func (m *BrokerDevice) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ListDevicesBrokerReq) Marshal() (data []byte, err error) { +func (m *ValidateTokenBrokerReq) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -459,14 +466,20 @@ func (m *ListDevicesBrokerReq) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *ListDevicesBrokerReq) MarshalTo(data []byte) (int, error) { +func (m *ValidateTokenBrokerReq) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -475,7 +488,7 @@ func (m *ListDevicesBrokerReq) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ListDevicesBrokerRes) Marshal() (data []byte, err error) { +func (m *ValidateTokenBrokerRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -485,23 +498,11 @@ func (m *ListDevicesBrokerRes) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *ListDevicesBrokerRes) MarshalTo(data []byte) (int, error) { +func (m *ValidateTokenBrokerRes) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l - if len(m.Devices) > 0 { - for _, msg := range m.Devices { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } return i, nil } @@ -535,6 +536,10 @@ func encodeVarintBrokerManager(data []byte, offset int, v uint64) int { func (m *ValidateOTAABrokerReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -557,6 +562,10 @@ func (m *ValidateOTAABrokerRes) Size() (n int) { func (m *UpsertABPBrokerReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -612,9 +621,13 @@ func (m *BrokerDevice) Size() (n int) { return n } -func (m *ListDevicesBrokerReq) Size() (n int) { +func (m *ValidateTokenBrokerReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) + } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -624,15 +637,9 @@ func (m *ListDevicesBrokerReq) Size() (n int) { return n } -func (m *ListDevicesBrokerRes) Size() (n int) { +func (m *ValidateTokenBrokerRes) Size() (n int) { var l int _ = l - if len(m.Devices) > 0 { - for _, e := range m.Devices { - l = e.Size() - n += 1 + l + sovBrokerManager(uint64(l)) - } - } return n } @@ -679,6 +686,35 @@ func (m *ValidateOTAABrokerReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -709,7 +745,7 @@ func (m *ValidateOTAABrokerReq) Unmarshal(data []byte) error { m.AppEUI = []byte{} } iNdEx = postIndex - case 2: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) } @@ -839,6 +875,35 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -869,7 +934,7 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { m.AppEUI = []byte{} } iNdEx = postIndex - case 2: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) } @@ -898,7 +963,7 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { } m.NetAddress = string(data[iNdEx:postIndex]) iNdEx = postIndex - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } @@ -929,7 +994,7 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { m.DevAddr = []byte{} } iNdEx = postIndex - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -1174,7 +1239,7 @@ func (m *BrokerDevice) Unmarshal(data []byte) error { } return nil } -func (m *ListDevicesBrokerReq) Unmarshal(data []byte) error { +func (m *ValidateTokenBrokerReq) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1197,13 +1262,42 @@ func (m *ListDevicesBrokerReq) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ListDevicesBrokerReq: wiretype end group for non-group") + return fmt.Errorf("proto: ValidateTokenBrokerReq: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ListDevicesBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ValidateTokenBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBrokerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -1255,7 +1349,7 @@ func (m *ListDevicesBrokerReq) Unmarshal(data []byte) error { } return nil } -func (m *ListDevicesBrokerRes) Unmarshal(data []byte) error { +func (m *ValidateTokenBrokerRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1278,43 +1372,12 @@ func (m *ListDevicesBrokerRes) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ListDevicesBrokerRes: wiretype end group for non-group") + return fmt.Errorf("proto: ValidateTokenBrokerRes: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ListDevicesBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ValidateTokenBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Devices = append(m.Devices, &BrokerDevice{}) - if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -1442,26 +1505,26 @@ var ( ) var fileDescriptorBrokerManager = []byte{ - // 332 bytes of a gzipped FileDescriptorProto + // 327 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0xf2, 0xe7, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, - 0x2c, 0x49, 0xf5, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, - 0x73, 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x62, 0x4b, 0x04, - 0xf3, 0x84, 0xe4, 0xb8, 0xb8, 0xfc, 0x52, 0x4b, 0x1c, 0x53, 0x52, 0x8a, 0x52, 0x8b, 0x8b, 0x25, - 0x98, 0x80, 0x72, 0x9c, 0x41, 0x5c, 0x79, 0x70, 0x11, 0x25, 0x71, 0xec, 0x06, 0x16, 0x2b, 0x35, - 0x30, 0x72, 0x09, 0x85, 0x16, 0x14, 0xa7, 0x16, 0x95, 0x38, 0x3a, 0x05, 0x50, 0x6c, 0x8f, 0x90, - 0x04, 0x17, 0xbb, 0x4b, 0x6a, 0x19, 0x88, 0x27, 0xc1, 0x0c, 0xd6, 0xc8, 0x9e, 0x02, 0xe1, 0x82, - 0x64, 0xfc, 0xca, 0xb3, 0x83, 0xbd, 0x53, 0x2b, 0x25, 0x58, 0x20, 0x32, 0x79, 0x10, 0xae, 0x92, - 0x08, 0x16, 0x17, 0x14, 0x2b, 0x45, 0x71, 0xf1, 0x40, 0x38, 0x40, 0xf3, 0x32, 0x93, 0x53, 0x41, - 0x2e, 0x02, 0xb2, 0x90, 0x5c, 0x94, 0x02, 0xe6, 0x21, 0xdb, 0xc8, 0x84, 0xd3, 0x46, 0x66, 0x54, - 0x1b, 0xf5, 0xb8, 0x44, 0x7c, 0x32, 0x8b, 0x4b, 0x20, 0x26, 0x17, 0x13, 0xf4, 0xb5, 0x92, 0x0b, - 0x56, 0xf5, 0xc5, 0x42, 0x3a, 0x60, 0xbb, 0x41, 0x62, 0x40, 0x0d, 0xcc, 0x1a, 0xdc, 0x46, 0x42, - 0x7a, 0xa0, 0xe8, 0xd3, 0x43, 0x76, 0x38, 0xd8, 0x3d, 0x20, 0x25, 0x46, 0x0f, 0x19, 0xb9, 0x78, - 0x21, 0x32, 0xbe, 0x90, 0x28, 0x17, 0xf2, 0xe0, 0xe2, 0x41, 0x8e, 0x15, 0x21, 0x69, 0x88, 0x76, - 0xac, 0x51, 0x2f, 0x85, 0x47, 0xb2, 0x58, 0xc8, 0x9e, 0x8b, 0x13, 0x1e, 0x86, 0x42, 0x12, 0x10, - 0x95, 0x98, 0xd1, 0x2a, 0x85, 0x4b, 0xa6, 0x58, 0xc8, 0x95, 0x8b, 0x1b, 0xc9, 0x8b, 0x42, 0x52, - 0x10, 0x85, 0xd8, 0x42, 0x49, 0x0a, 0xb7, 0x5c, 0xb1, 0x93, 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, - 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, 0x4e, 0xd7, 0xc6, 0x80, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x39, 0x26, 0x0a, 0x66, 0xef, 0x02, 0x00, 0x00, + 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x4a, 0xe5, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, + 0x2c, 0x49, 0xf5, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe1, 0x62, + 0x0d, 0x01, 0xb2, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x58, 0x4b, 0x40, 0x1c, 0x21, + 0x31, 0x2e, 0x36, 0xc7, 0x82, 0x02, 0xd7, 0x50, 0x4f, 0x09, 0x26, 0xa0, 0x30, 0x4f, 0x10, 0x5b, + 0x22, 0x98, 0x27, 0x24, 0xc7, 0xc5, 0xe5, 0x97, 0x5a, 0xe2, 0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, + 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x34, + 0x46, 0x2e, 0xa1, 0xd0, 0x82, 0xe2, 0xd4, 0xa2, 0x12, 0x47, 0xa7, 0x00, 0x1a, 0xd9, 0x2e, 0x24, + 0xc1, 0xc5, 0xee, 0x92, 0x5a, 0x06, 0xe2, 0x49, 0xb0, 0x80, 0x35, 0xb2, 0xa7, 0x40, 0xb8, 0x20, + 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0xab, 0x24, + 0x82, 0xc5, 0x5d, 0xc5, 0x4a, 0x51, 0x5c, 0x3c, 0x10, 0x0e, 0xd0, 0xbc, 0xcc, 0xe4, 0x54, 0x90, + 0x8b, 0x80, 0x2c, 0x90, 0x8b, 0x18, 0x21, 0x2e, 0x4a, 0x01, 0xf3, 0x90, 0x6d, 0x64, 0xc2, 0x69, + 0x23, 0x33, 0xaa, 0x8d, 0x6e, 0x5c, 0x62, 0xb0, 0x30, 0x02, 0xfb, 0x9d, 0xcc, 0xd0, 0x50, 0x92, + 0xc0, 0x61, 0x4e, 0xb1, 0xd1, 0x73, 0x46, 0x2e, 0x5e, 0x08, 0xcf, 0x17, 0x92, 0x14, 0x84, 0x3c, + 0xb8, 0x78, 0x90, 0xe3, 0x45, 0x48, 0x5a, 0x0f, 0x94, 0x2a, 0xf4, 0xb0, 0x26, 0x09, 0x29, 0x3c, + 0x92, 0xc5, 0x42, 0xf6, 0x5c, 0x9c, 0xf0, 0xf0, 0x12, 0x92, 0x80, 0xa8, 0xc4, 0x8c, 0x58, 0x29, + 0x5c, 0x32, 0xc5, 0x42, 0xde, 0x5c, 0xbc, 0x28, 0xce, 0x16, 0x92, 0x41, 0xb5, 0x0e, 0x35, 0x4c, + 0xa4, 0xf0, 0xc9, 0x16, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, + 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0x54, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x06, + 0x4b, 0xeb, 0xf5, 0x0d, 0x03, 0x00, 0x00, } diff --git a/core/components/broker/brokerClient.go b/core/components/broker/brokerClient.go new file mode 100644 index 000000000..a1ba08fa2 --- /dev/null +++ b/core/components/broker/brokerClient.go @@ -0,0 +1,34 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core" + "google.golang.org/grpc" +) + +type brokerClient struct { + core.BrokerClient + core.BrokerManagerClient +} + +// NewClient instantiates a new core.Broker client +func NewClient(netAddr string) (core.AuthBrokerClient, error) { + brokerConn, err := grpc.Dial( + netAddr, + grpc.WithInsecure(), // TODO Use of TLS + grpc.WithTimeout(time.Second*15), + ) + if err != nil { + return nil, err + } + broker := core.NewBrokerClient(brokerConn) + brokerManager := core.NewBrokerManagerClient(brokerConn) + return &brokerClient{ + BrokerClient: broker, + BrokerManagerClient: brokerManager, + }, nil +} diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 099938faf..810d9a9dd 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -11,12 +11,21 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" jwt "github.com/dgrijalva/jwt-go" "golang.org/x/net/context" - "google.golang.org/grpc/metadata" ) -// ListDevices implements the core.BrokerManagerServer interface -func (b component) ListDevices(bctx context.Context, req *core.ListDevicesBrokerReq) (*core.ListDevicesBrokerRes, error) { - return new(core.ListDevicesBrokerRes), errors.New(errors.Implementation, "Not implemented") +// ValidateToken implements the core.BrokerManagerServer interface +func (b component) ValidateToken(bctx context.Context, req *core.ValidateTokenBrokerReq) (*core.ValidateTokenBrokerRes, error) { + b.Ctx.Debug("Handle ValidateToken request") + if len(req.AppEUI) != 8 { + err := errors.New(errors.Structural, "Invalid request parameters") + b.Ctx.WithError(err).Debug("Unable to handle ValidateToken request") + return new(core.ValidateTokenBrokerRes), err + } + if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { + b.Ctx.WithError(err).Debug("Unable to handle ValidateToken request") + return new(core.ValidateTokenBrokerRes), err + } + return new(core.ValidateTokenBrokerRes), nil } // ValidateOTAA implements the core.BrokerManager interface @@ -32,7 +41,7 @@ func (b component) ValidateOTAA(bctx context.Context, req *core.ValidateOTAABrok } // 2. Verify and validate the token - if err := b.validateToken(bctx, req.AppEUI); err != nil { + if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { return new(core.ValidateOTAABrokerRes), err } @@ -64,7 +73,7 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) } // 2. Verify and validate the token - if err := b.validateToken(bctx, req.AppEUI); err != nil { + if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { return new(core.UpsertABPBrokerRes), err } @@ -90,19 +99,14 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) } // validateToken verify an OAuth Bearer token pass through metadata during RPC -func (b component) validateToken(ctx context.Context, appEUI []byte) error { - re := regexp.MustCompile("[[:alnum:]=/+]+\\.[[:alnum:]=/+]+\\.[[:alnum:]=/+]+") - meta, ok := metadata.FromContext(ctx) - if !ok || len(meta["token"]) < 1 || !re.MatchString(meta["token"][0]) { - return errors.New(errors.Structural, "Unable to retrieve token from metadata") - } - token, err := jwt.Parse(meta["token"][0], func(token *jwt.Token) (interface{}, error) { +func (b component) validateToken(ctx context.Context, token string, appEUI []byte) error { + parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { return b.SecretKey[:], nil }) if err != nil { return errors.New(errors.Structural, "Unable to parse token") } - if !token.Valid || token.Claims["sub"] != fmt.Sprintf("%X", appEUI) { + if !parsed.Valid || parsed.Claims["sub"] != fmt.Sprintf("%X", appEUI) { return errors.New(errors.Structural, "Invalid token.") } return nil diff --git a/core/components/broker/brokerToken.go b/core/components/broker/brokerToken.go deleted file mode 100644 index 786e094af..000000000 --- a/core/components/broker/brokerToken.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -type brokerClient struct { - sync.Mutex - *tokenCredentials - core.BrokerClient - core.BrokerManagerClient -} - -// NewClient instantiates a new core.Broker client -func NewClient(netAddr string) (core.AuthBrokerClient, error) { - tokener := tokenCredentials{} - brokerConn, err := grpc.Dial( - netAddr, - grpc.WithInsecure(), // TODO Use of TLS - grpc.WithPerRPCCredentials(&tokener), - grpc.WithTimeout(time.Second*15), - ) - if err != nil { - return nil, err - } - broker := core.NewBrokerClient(brokerConn) - brokerManager := core.NewBrokerManagerClient(brokerConn) - return &brokerClient{ - tokenCredentials: &tokener, - BrokerClient: broker, - BrokerManagerClient: brokerManager, - }, nil -} - -// BeginToken implements the core.Broker interface -func (b *brokerClient) BeginToken(token string) core.AuthBrokerClient { - b.Lock() - b.token = token - return b -} - -// EndToken implements the core.Broker interface -func (b *brokerClient) EndToken() { - b.Unlock() -} - -type tokenCredentials struct { - token string -} - -// GetRequestMetadata implements the grpc/credentials.Credentials interface -func (t tokenCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { - return map[string]string{ - "token": t.token, - }, nil -} - -// RequireTransportSecurity implements the grpc/credentials.Credentials interface -func (t tokenCredentials) RequireTransportSecurity() bool { - return false // TODO -> True, Need use of TLS to communicate the token -} diff --git a/core/components/handler/handlerClient.go b/core/components/handler/handlerClient.go new file mode 100644 index 000000000..0672b0594 --- /dev/null +++ b/core/components/handler/handlerClient.go @@ -0,0 +1,34 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core" + "google.golang.org/grpc" +) + +type handlerClient struct { + core.HandlerClient + core.HandlerManagerClient +} + +// NewClient instantiates a new core.handler client +func NewClient(netAddr string) (core.AuthHandlerClient, error) { + handlerConn, err := grpc.Dial( + netAddr, + grpc.WithInsecure(), // TODO Use of TLS + grpc.WithTimeout(time.Second*15), + ) + if err != nil { + return nil, err + } + handler := core.NewHandlerClient(handlerConn) + handlerManager := core.NewHandlerManagerClient(handlerConn) + return &handlerClient{ + HandlerClient: handler, + HandlerManagerClient: handlerManager, + }, nil +} diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index f761fc070..6756e6b13 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -7,7 +7,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" - "google.golang.org/grpc/metadata" ) // ListDevices implements the core.HandlerManagerServer interface @@ -22,6 +21,10 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle } // 2. Validate token & retrieve devices from db + if _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { + h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") + return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) + } entries, err := h.DevStorage.readAll(req.AppEUI) if err != nil { h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") @@ -67,17 +70,13 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq } // 2. Forward to the broker firt -> The Broker also does the token verification - var token string - if meta, ok := metadata.FromContext(bctx); ok && len(meta["token"]) > 0 { - token = meta["token"][0] - } - _, err := h.Broker.BeginToken(token).UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ + _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ + Token: req.Token, AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, NetAddress: h.NetAddr, }) - h.Broker.EndToken() if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) @@ -114,15 +113,11 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR } // 2. Notify the broker -> The Broker also does the token verification - var token string - if meta, ok := metadata.FromContext(bctx); ok && len(meta["token"]) > 0 { - token = meta["token"][0] - } - _, err := h.Broker.BeginToken(token).ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ + _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ + Token: req.Token, NetAddress: h.NetAddr, AppEUI: req.AppEUI, }) - h.Broker.EndToken() if err != nil { h.Ctx.WithError(err).Debug("Broker rejected OTAA") return new(core.UpsertOTAAHandlerRes), errors.New(errors.Operational, err) diff --git a/core/components/handler/handlerToken.go b/core/components/handler/handlerToken.go deleted file mode 100644 index d7f760dbc..000000000 --- a/core/components/handler/handlerToken.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -type handlerClient struct { - sync.Mutex - *tokenCredentials - core.HandlerClient - core.HandlerManagerClient -} - -// NewClient instantiates a new core.handler client -func NewClient(netAddr string) (core.AuthHandlerClient, error) { - tokener := tokenCredentials{} - handlerConn, err := grpc.Dial( - netAddr, - grpc.WithInsecure(), // TODO Use of TLS - grpc.WithPerRPCCredentials(&tokener), - grpc.WithTimeout(time.Second*15), - ) - if err != nil { - return nil, err - } - handler := core.NewHandlerClient(handlerConn) - handlerManager := core.NewHandlerManagerClient(handlerConn) - return &handlerClient{ - tokenCredentials: &tokener, - HandlerClient: handler, - HandlerManagerClient: handlerManager, - }, nil -} - -// BeginToken implements the core.handler interface -func (b *handlerClient) BeginToken(token string) core.AuthHandlerClient { - b.Lock() - b.token = token - return b -} - -// EndToken implements the core.handler interface -func (b *handlerClient) EndToken() { - b.Unlock() -} - -type tokenCredentials struct { - token string -} - -// GetRequestMetadata implements the grpc/credentials.Credentials interface -func (t tokenCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { - return map[string]string{ - "token": t.token, - }, nil -} - -// RequireTransportSecurity implements the grpc/credentials.Credentials interface -func (t tokenCredentials) RequireTransportSecurity() bool { - return false // TODO -> True, Need use of TLS to communicate the token -} diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 6b64e0b34..cc6a0fc70 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -15,6 +15,12 @@ type MockDevStorage struct { OutRead struct { Entry devEntry } + InReadAll struct { + AppEUI []byte + } + OutReadAll struct { + Entries []devEntry + } InUpsert struct { Entry devEntry } @@ -37,6 +43,12 @@ func (m *MockDevStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { return m.OutRead.Entry, m.Failures["read"] } +// readAll implements the DevStorage interface +func (m *MockDevStorage) readAll(appEUI []byte) ([]devEntry, error) { + m.InReadAll.AppEUI = appEUI + return m.OutReadAll.Entries, m.Failures["readAll"] +} + // upsert implements the DevStorage interface func (m *MockDevStorage) upsert(entry devEntry) error { m.InUpsert.Entry = entry diff --git a/core/definitions.go b/core/definitions.go index 21bdac658..28e4c6746 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -42,20 +42,14 @@ type ABPSubAppReq struct { AppSKey string `msg:"apps_key" json:"apps_key"` } -// AuthBrokerClient gathers both BrokerClient & BrokerManagerClient interfaces with additional -// method to add a custom token to each rpc requests's metadata +// AuthBrokerClient gathers both BrokerClient & BrokerManagerClient interfaces type AuthBrokerClient interface { BrokerClient BrokerManagerClient - BeginToken(token string) AuthBrokerClient - EndToken() } -// AuthHandlerClient gathers both HandlerClient & HandlerManagerClient interfaces with additional -// method to add a custom token to each rpc requests's metadata +// AuthHandlerClient gathers both HandlerClient & HandlerManagerClient interfaces type AuthHandlerClient interface { HandlerClient HandlerManagerClient - BeginToken(token string) AuthHandlerClient - EndToken() } diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index c6365be92..447d0b123 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -21,9 +21,10 @@ var _ = fmt.Errorf var _ = math.Inf type UpsertOTAAHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - AppKey []byte `protobuf:"bytes,3,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + AppKey []byte `protobuf:"bytes,4,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` } func (m *UpsertOTAAHandlerReq) Reset() { *m = UpsertOTAAHandlerReq{} } @@ -44,10 +45,11 @@ func (*UpsertOTAAHandlerRes) Descriptor() ([]byte, []int) { } type UpsertABPHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,5,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` } func (m *UpsertABPHandlerReq) Reset() { *m = UpsertABPHandlerReq{} } @@ -68,7 +70,8 @@ func (*UpsertABPHandlerRes) Descriptor() ([]byte, []int) { } type ListDevicesHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` } func (m *ListDevicesHandlerReq) Reset() { *m = ListDevicesHandlerReq{} } @@ -269,9 +272,15 @@ func (m *UpsertOTAAHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -279,7 +288,7 @@ func (m *UpsertOTAAHandlerReq) MarshalTo(data []byte) (int, error) { } if m.DevEUI != nil { if len(m.DevEUI) > 0 { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) i += copy(data[i:], m.DevEUI) @@ -287,7 +296,7 @@ func (m *UpsertOTAAHandlerReq) MarshalTo(data []byte) (int, error) { } if m.AppKey != nil { if len(m.AppKey) > 0 { - data[i] = 0x1a + data[i] = 0x22 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) i += copy(data[i:], m.AppKey) @@ -329,9 +338,15 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -339,7 +354,7 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { } if m.DevAddr != nil { if len(m.DevAddr) > 0 { - data[i] = 0x12 + data[i] = 0x1a i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) i += copy(data[i:], m.DevAddr) @@ -347,7 +362,7 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { } if m.NwkSKey != nil { if len(m.NwkSKey) > 0 { - data[i] = 0x1a + data[i] = 0x22 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) @@ -355,7 +370,7 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { } if m.AppSKey != nil { if len(m.AppSKey) > 0 { - data[i] = 0x22 + data[i] = 0x2a i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) i += copy(data[i:], m.AppSKey) @@ -397,9 +412,15 @@ func (m *ListDevicesHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0xa + data[i] = 0x12 i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -580,6 +601,10 @@ func encodeVarintHandlerManager(data []byte, offset int, v uint64) int { func (m *UpsertOTAAHandlerReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -610,6 +635,10 @@ func (m *UpsertOTAAHandlerRes) Size() (n int) { func (m *UpsertABPHandlerReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -646,6 +675,10 @@ func (m *UpsertABPHandlerRes) Size() (n int) { func (m *ListDevicesHandlerReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -776,6 +809,35 @@ func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -806,7 +868,7 @@ func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { m.AppEUI = []byte{} } iNdEx = postIndex - case 2: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } @@ -837,7 +899,7 @@ func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { m.DevEUI = []byte{} } iNdEx = postIndex - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) } @@ -969,6 +1031,35 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -999,7 +1090,7 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { m.AppEUI = []byte{} } iNdEx = postIndex - case 2: + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } @@ -1030,7 +1121,7 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { m.DevAddr = []byte{} } iNdEx = postIndex - case 3: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -1061,7 +1152,7 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { m.NwkSKey = []byte{} } iNdEx = postIndex - case 4: + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) } @@ -1193,6 +1284,35 @@ func (m *ListDevicesHandlerReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -1810,28 +1930,30 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 364 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0xcd, 0x48, 0xcc, 0x4b, - 0xc9, 0x49, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, - 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x8a, 0xe3, 0x12, 0x09, 0x2d, 0x28, 0x4e, 0x2d, - 0x2a, 0xf1, 0x0f, 0x71, 0x74, 0xf4, 0x80, 0x28, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe3, 0x62, 0x73, - 0x2c, 0x28, 0x70, 0x0d, 0xf5, 0x94, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x09, 0x62, 0x4b, 0x04, 0xf3, - 0x40, 0xe2, 0x2e, 0xa9, 0x65, 0x20, 0x71, 0x26, 0x88, 0x78, 0x0a, 0x98, 0x07, 0x55, 0xef, 0x9d, - 0x5a, 0x29, 0xc1, 0x0c, 0x57, 0x0f, 0xe4, 0x29, 0x89, 0x61, 0x35, 0xbf, 0x58, 0xa9, 0x9a, 0x4b, - 0x18, 0x22, 0xee, 0xe8, 0x14, 0x40, 0x84, 0xb5, 0x12, 0x5c, 0xec, 0x40, 0x6b, 0x1d, 0x53, 0x52, - 0x8a, 0xa0, 0xf6, 0xb2, 0xa7, 0x40, 0xb8, 0x20, 0x19, 0xbf, 0xf2, 0xec, 0x60, 0x84, 0xcd, 0xec, - 0x79, 0x10, 0x2e, 0x48, 0x06, 0x68, 0x16, 0x58, 0x86, 0x05, 0x22, 0x93, 0x08, 0xe1, 0x2a, 0x89, - 0x62, 0xb3, 0xbc, 0x58, 0x49, 0x9f, 0x4b, 0xd4, 0x27, 0xb3, 0xb8, 0x04, 0x68, 0x51, 0x66, 0x72, - 0x6a, 0x31, 0x61, 0x57, 0x29, 0xe5, 0x61, 0xd7, 0x50, 0x2c, 0xa4, 0xcd, 0xc5, 0x02, 0xf2, 0x2f, - 0x50, 0x39, 0xb3, 0x06, 0xb7, 0x91, 0xb8, 0x1e, 0x28, 0xa8, 0xf5, 0xa0, 0xf2, 0x20, 0x09, 0x88, - 0x8e, 0x20, 0x96, 0x7c, 0x20, 0x5b, 0x48, 0x83, 0x8b, 0x19, 0xe8, 0x0e, 0xa0, 0xbf, 0x40, 0x6a, - 0xc5, 0x50, 0xd4, 0x02, 0xc5, 0xa1, 0x4a, 0x99, 0x13, 0x9d, 0x02, 0x94, 0x12, 0xb8, 0x04, 0xd0, - 0x25, 0xa8, 0x1c, 0x32, 0x13, 0x19, 0xb9, 0x04, 0x31, 0xdc, 0x89, 0x14, 0xe9, 0x8c, 0x28, 0x91, - 0x4e, 0x55, 0xbb, 0x91, 0x92, 0x10, 0x2b, 0x72, 0x12, 0x32, 0x7a, 0xc4, 0xc8, 0xc5, 0x07, 0x75, - 0x93, 0x2f, 0x24, 0x05, 0x0b, 0xb9, 0x70, 0x71, 0x21, 0x52, 0x95, 0x90, 0x14, 0x24, 0xcc, 0xb0, - 0xa5, 0x63, 0x29, 0xdc, 0x72, 0xc5, 0x42, 0x8e, 0x5c, 0x9c, 0xf0, 0x64, 0x20, 0x24, 0x89, 0xac, - 0x10, 0x25, 0x51, 0x4a, 0xe1, 0x94, 0x2a, 0x16, 0x72, 0xe7, 0xe2, 0x46, 0x4a, 0x01, 0x42, 0xd2, - 0x10, 0x95, 0x58, 0x53, 0x91, 0x14, 0x1e, 0xc9, 0x62, 0x27, 0x81, 0x13, 0x8f, 0xe4, 0x18, 0x2f, - 0x00, 0xf1, 0x03, 0x20, 0x9e, 0xf1, 0x58, 0x8e, 0x21, 0x89, 0x0d, 0x9c, 0x4d, 0x8d, 0x01, 0x01, - 0x00, 0x00, 0xff, 0xff, 0x2e, 0xd8, 0x9b, 0x23, 0xbf, 0x03, 0x00, 0x00, + // 391 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x53, 0xcd, 0x4e, 0xf2, 0x40, + 0x14, 0xfd, 0x4a, 0x5b, 0x08, 0x97, 0x2f, 0x5f, 0xf8, 0x46, 0xc0, 0x5a, 0x13, 0x62, 0x66, 0x45, + 0x62, 0xc2, 0x02, 0x9f, 0xa0, 0x04, 0xa2, 0xc6, 0x3f, 0x52, 0x61, 0xad, 0x95, 0x4e, 0x94, 0xa0, + 0x6d, 0xed, 0x34, 0x1a, 0x1f, 0xc3, 0x9d, 0x8f, 0xe4, 0xd2, 0x47, 0x30, 0xf8, 0x22, 0xde, 0xe9, + 0x34, 0xd0, 0x6a, 0xeb, 0x82, 0xb8, 0x98, 0xa4, 0xe7, 0x9e, 0xd3, 0x3b, 0x27, 0x77, 0xce, 0x85, + 0xe6, 0x8d, 0xe3, 0xb9, 0xb7, 0x2c, 0xbc, 0xb8, 0x73, 0x3c, 0xe7, 0x9a, 0x85, 0xdd, 0x20, 0xf4, + 0x23, 0x9f, 0x68, 0x53, 0x3f, 0x64, 0x34, 0x82, 0xc6, 0x24, 0xe0, 0x2c, 0x8c, 0xce, 0xc6, 0x96, + 0x75, 0x20, 0x85, 0x36, 0xbb, 0x27, 0x0d, 0xd0, 0xc7, 0xfe, 0x9c, 0x79, 0x86, 0xb2, 0xa3, 0x74, + 0xaa, 0xb6, 0x1e, 0x09, 0x40, 0x5a, 0x50, 0xb6, 0x82, 0x60, 0x38, 0x39, 0x34, 0x4a, 0x58, 0xfe, + 0x6b, 0x97, 0x9d, 0x18, 0x89, 0xfa, 0x80, 0x3d, 0x88, 0xba, 0x2a, 0xeb, 0x6e, 0x8c, 0x12, 0xfd, + 0x11, 0x7b, 0x32, 0xb4, 0xa5, 0x1e, 0x11, 0x6d, 0xe5, 0xde, 0xca, 0xe9, 0xb3, 0x02, 0x1b, 0x92, + 0xb0, 0xfa, 0xa3, 0xb5, 0xdd, 0x18, 0x50, 0x41, 0x37, 0x96, 0xeb, 0x86, 0x89, 0x9d, 0x8a, 0x2b, + 0xa1, 0x60, 0x4e, 0x1f, 0xe7, 0xe7, 0x2b, 0x43, 0x15, 0x4f, 0x42, 0xc1, 0x60, 0xaf, 0x98, 0xd1, + 0x25, 0xe3, 0x48, 0x48, 0x9b, 0x79, 0x96, 0x38, 0x1d, 0x42, 0xf3, 0x78, 0xc6, 0x23, 0xbc, 0x68, + 0x36, 0x65, 0x7c, 0x5d, 0xaf, 0xd4, 0xcb, 0x6f, 0xc3, 0xc9, 0x2e, 0x68, 0x62, 0x38, 0xd8, 0x45, + 0xed, 0xd4, 0x7a, 0x9b, 0x5d, 0xf1, 0x5a, 0xdd, 0x84, 0x17, 0x84, 0xfc, 0xc3, 0xd6, 0x7c, 0xfc, + 0x26, 0x1d, 0x50, 0xd1, 0x1d, 0xb6, 0x16, 0xda, 0x56, 0x46, 0x8b, 0xf5, 0x44, 0xaa, 0x3a, 0xfd, + 0x11, 0xbd, 0x84, 0xfa, 0x57, 0x22, 0x3d, 0xaf, 0x52, 0xe1, 0xbc, 0xd4, 0xc2, 0x79, 0x69, 0xd9, + 0x79, 0xe1, 0x1b, 0xfe, 0xff, 0xe6, 0x33, 0x95, 0x10, 0x25, 0x93, 0x90, 0x5f, 0xbd, 0x3b, 0x95, + 0x37, 0x3d, 0x9d, 0xb7, 0xde, 0x42, 0x81, 0x7f, 0x89, 0xa7, 0x13, 0xb9, 0x04, 0x64, 0x00, 0xb0, + 0x8a, 0x20, 0x31, 0xe5, 0xcc, 0xf2, 0x56, 0xc1, 0x2c, 0xe6, 0x38, 0xb1, 0xa0, 0xba, 0x0c, 0x07, + 0xd9, 0x4a, 0x0b, 0x33, 0x01, 0x36, 0x0b, 0x29, 0x4e, 0xf6, 0xa1, 0x96, 0x4a, 0x00, 0xd9, 0x96, + 0xca, 0xdc, 0x6c, 0x99, 0x3f, 0x90, 0xbc, 0x5f, 0x7f, 0x5d, 0xb4, 0x95, 0x37, 0x3c, 0xef, 0x78, + 0x5e, 0x3e, 0xda, 0x7f, 0xae, 0xca, 0xf1, 0xa6, 0xef, 0x7d, 0x06, 0x00, 0x00, 0xff, 0xff, 0xc2, + 0x64, 0x7b, 0xb7, 0x02, 0x04, 0x00, 0x00, } diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go index 450e3164e..2dc986f03 100644 --- a/core/mocks/mocks.go +++ b/core/mocks/mocks.go @@ -152,19 +152,13 @@ type AuthBrokerClient struct { OutValidateOTAA struct { Res *core.ValidateOTAABrokerRes } - InListDevices struct { + InValidateToken struct { Ctx context.Context - Req *core.ListDevicesBrokerReq + Req *core.ValidateTokenBrokerReq Opts []grpc.CallOption } - OutListDevices struct { - Res *core.ListDevicesBrokerRes - } - InBeginToken struct { - Token string - } - InEndToken struct { - Called bool + OutValidateToken struct { + Res *core.ValidateTokenBrokerRes } } @@ -207,23 +201,12 @@ func (m *AuthBrokerClient) ValidateOTAA(ctx context.Context, in *core.ValidateOT return m.OutValidateOTAA.Res, m.Failures["ValidateOTAA"] } -// ListDevices implements the core.Broker interface -func (m *AuthBrokerClient) ListDevices(ctx context.Context, in *core.ListDevicesBrokerReq, opts ...grpc.CallOption) (*core.ListDevicesBrokerRes, error) { - m.InListDevices.Ctx = ctx - m.InListDevices.Req = in - m.InListDevices.Opts = opts - return m.OutListDevices.Res, m.Failures["ListDevices"] -} - -// BeginToken implements the core.Broker interface -func (m *AuthBrokerClient) BeginToken(token string) core.AuthBrokerClient { - m.InBeginToken.Token = token - return m -} - -// EndToken implements the core.Broker interface -func (m *AuthBrokerClient) EndToken() { - m.InEndToken.Called = true +// ValidateToken implements the core.Broker interface +func (m *AuthBrokerClient) ValidateToken(ctx context.Context, in *core.ValidateTokenBrokerReq, opts ...grpc.CallOption) (*core.ValidateTokenBrokerRes, error) { + m.InValidateToken.Ctx = ctx + m.InValidateToken.Req = in + m.InValidateToken.Opts = opts + return m.OutValidateToken.Res, m.Failures["ValidateToken"] } // RouterServer mocks the core.RouterServer interface diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index 53188d609..9df7903e7 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -3,17 +3,19 @@ syntax = "proto3"; package core; message ValidateOTAABrokerReq { - bytes AppEUI = 1; - string NetAddress = 2; + string Token = 1; + bytes AppEUI = 2; + string NetAddress = 3; } message ValidateOTAABrokerRes{} message UpsertABPBrokerReq { - bytes AppEUI = 1; - string NetAddress = 2; - bytes DevAddr = 3; - bytes NwkSKey = 4; + string Token = 1; + bytes AppEUI = 2; + string NetAddress = 3; + bytes DevAddr = 4; + bytes NwkSKey = 5; } message UpsertABPBrokerRes {} @@ -24,16 +26,15 @@ message BrokerDevice { bytes NwkSKey = 3; } -message ListDevicesBrokerReq { - bytes AppEUI = 1; +message ValidateTokenBrokerReq { + string Token = 1; + bytes AppEUI = 2; } -message ListDevicesBrokerRes { - repeated BrokerDevice Devices = 1; -} +message ValidateTokenBrokerRes {} service BrokerManager { rpc ValidateOTAA (ValidateOTAABrokerReq) returns (ValidateOTAABrokerRes); rpc UpsertABP (UpsertABPBrokerReq) returns (UpsertABPBrokerRes); - rpc ListDevices (ListDevicesBrokerReq) returns (ListDevicesBrokerRes); + rpc ValidateToken (ValidateTokenBrokerReq) returns (ValidateTokenBrokerRes); } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index 349c76cf8..8c3a27535 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -3,24 +3,27 @@ syntax = "proto3"; package core; message UpsertOTAAHandlerReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - bytes AppKey = 3; + string Token = 1; + bytes AppEUI = 2; + bytes DevEUI = 3; + bytes AppKey = 4; } message UpsertOTAAHandlerRes{} message UpsertABPHandlerReq { - bytes AppEUI = 1; - bytes DevAddr = 2; - bytes NwkSKey = 3; - bytes AppSKey = 4; + string Token = 1; + bytes AppEUI = 2; + bytes DevAddr = 3; + bytes NwkSKey = 4; + bytes AppSKey = 5; } message UpsertABPHandlerRes {} message ListDevicesHandlerReq { - bytes AppEUI = 1; + string Token = 1; + bytes AppEUI = 2; } message ListDevicesHandlerRes { diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index cb921b715..300cbc697 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -38,10 +38,10 @@ var devicesCmd = &cobra.Command{ manager := getHandlerManager() - res, err := manager.BeginToken(viper.GetString("app-token")).ListDevices(context.Background(), &core.ListDevicesHandlerReq{ + res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ + Token: viper.GetString("app-token"), AppEUI: appEUI, }) - manager.EndToken() if err != nil { ctx.WithError(err).Fatal("Could not get device list") } @@ -100,12 +100,12 @@ var devicesRegisterCmd = &cobra.Command{ } manager := getHandlerManager() - res, err := manager.BeginToken(viper.GetString("app-token")).UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ + res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ + Token: viper.GetString("app-token"), AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, }) - manager.EndToken() if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } @@ -144,13 +144,13 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ } manager := getHandlerManager() - res, err := manager.BeginToken(viper.GetString("app-token")).UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ + res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ + Token: viper.GetString("app-token"), AppEUI: appEUI, DevAddr: devAddr, AppSKey: appSKey, NwkSKey: nwkSKey, }) - manager.EndToken() if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } From 5f46155513c504e1a40eb8a079fee1d6be0c8413 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 05:14:33 +0100 Subject: [PATCH 1172/2266] [feature/manager] Add handler_manager tests --- core/components/handler/devStorage_test.go | 45 + core/components/handler/handlerClient_test.go | 727 +++++++++++++++ coverage.out | 882 ------------------ 3 files changed, 772 insertions(+), 882 deletions(-) create mode 100644 core/components/handler/handlerClient_test.go delete mode 100644 coverage.out diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 30413e80f..7abbc5e7e 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -131,6 +131,51 @@ func TestReadStore(t *testing.T) { // ------------------ + { + Desc(t, "Store several, then readAll") + + // Build + entry1 := devEntry{ + AppEUI: []byte{1, 2, 3, 44, 54, 6, 7, 14}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, + DevAddr: []byte{1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 2, + } + entry2 := devEntry{ + AppEUI: []byte{1, 2, 3, 44, 54, 6, 7, 14}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + DevAddr: []byte{2, 2, 3, 4}, + AppSKey: [16]byte{2, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{7, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + entry3 := devEntry{ + AppEUI: []byte{1, 8, 9, 44, 54, 6, 7, 14}, + DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, + DevAddr: []byte{2, 2, 3, 4}, + AppSKey: [16]byte{2, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{7, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + + // Operate + err := db.upsert(entry1) + FatalUnless(t, err) + err = db.upsert(entry2) + FatalUnless(t, err) + err = db.upsert(entry3) + FatalUnless(t, err) + entries, err := db.readAll(entry1.AppEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, []devEntry{entry1, entry2}, entries, "Devices Entries") + } + + // ------------------ + { Desc(t, "Close the storage") err := db.done() diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go new file mode 100644 index 000000000..22994672d --- /dev/null +++ b/core/components/handler/handlerClient_test.go @@ -0,0 +1,727 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "golang.org/x/net/context" +) + +func TestNewClient(t *testing.T) { + _, err := NewClient("0.0.0.0:12345") + CheckErrors(t, nil, err) +} + +func TestListDevices(t *testing.T) { + { + Desc(t, "Valid request, no issue") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.OutReadAll.Entries = []devEntry{ + { + DevEUI: []byte{1, 2, 1, 2, 1, 2, 1, 2}, + DevAddr: []byte{14, 14, 14, 14}, + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: [16]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + FCntDown: 14, + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + }, + { + DevAddr: []byte{42, 42, 42, 42}, + AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, + NwkSKey: [16]byte{1, 2, 3, 6, 1, 2, 3, 6, 1, 2, 3, 6, 1, 2, 3, 6}, + AppSKey: [16]byte{48, 2, 48, 2, 48, 2, 48, 2, 48, 2, 48, 2, 48, 2, 48, 2}, + FCntDown: 5, + }, + } + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.ListDevicesHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = &core.ListDevicesHandlerRes{ + OTAA: []*core.HandlerOTAADevice{ + &core.HandlerOTAADevice{ + DevEUI: st.OutReadAll.Entries[0].DevEUI, + DevAddr: st.OutReadAll.Entries[0].DevAddr, + NwkSKey: st.OutReadAll.Entries[0].NwkSKey[:], + AppSKey: st.OutReadAll.Entries[0].AppSKey[:], + AppKey: st.OutReadAll.Entries[0].AppKey[:], + }, + }, + ABP: []*core.HandlerABPDevice{ + &core.HandlerABPDevice{ + DevAddr: st.OutReadAll.Entries[1].DevAddr, + NwkSKey: st.OutReadAll.Entries[1].NwkSKey[:], + AppSKey: st.OutReadAll.Entries[1].AppSKey[:], + }, + }, + } + + // Operate + res, err := h.ListDevices(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | readAll fails") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.Failures["readAll"] = errors.New(errors.Operational, "Mock Error") + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.ListDevicesHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.ListDevicesHandlerRes) + + // Operate + res, err := h.ListDevices(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | broker fails") + + // Build + br := mocks.NewAuthBrokerClient() + br.Failures["ValidateToken"] = errors.New(errors.Operational, "Mock Error") + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.ListDevicesHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.ListDevicesHandlerRes) + + // Operate + res, err := h.ListDevices(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid AppEUI") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.ListDevicesHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateTokenBrokerReq + var wantRes = new(core.ListDevicesHandlerRes) + + // Operate + res, err := h.ListDevices(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } +} + +func TestUpsertABP(t *testing.T) { + { + Desc(t, "Valid request, no issue") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr *string + var wantBrkCall = &core.UpsertABPBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + NetAddress: h.(*component).NetAddr, + } + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | storage fails") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.UpsertABPBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + NetAddress: h.(*component).NetAddr, + } + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | broker fails") + + // Build + br := mocks.NewAuthBrokerClient() + br.Failures["UpsertABP"] = errors.New(errors.Operational, "Mock Error") + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.UpsertABPBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + NetAddress: h.(*component).NetAddr, + } + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | DevAddr invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{14, 14, 14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.UpsertABPBrokerReq + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | AppEUI invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6}, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.UpsertABPBrokerReq + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | NwkSKey invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: nil, + AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.UpsertABPBrokerReq + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | AppSKey invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertABPHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: []byte{14, 14, 14, 14}, + NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, + AppSKey: []byte{1, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.UpsertABPBrokerReq + var wantRes = new(core.UpsertABPHandlerRes) + + // Operate + res, err := h.UpsertABP(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } +} + +func TestUpsertOTAA(t *testing.T) { + { + Desc(t, "Valid request, no issue") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertOTAAHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr *string + var wantBrkCall = &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: h.(*component).NetAddr, + } + var wantRes = new(core.UpsertOTAAHandlerRes) + + // Operate + res, err := h.UpsertOTAA(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | storage fails") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertOTAAHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: h.(*component).NetAddr, + } + var wantRes = new(core.UpsertOTAAHandlerRes) + + // Operate + res, err := h.UpsertOTAA(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | broker fails") + + // Build + br := mocks.NewAuthBrokerClient() + br.Failures["ValidateOTAA"] = errors.New(errors.Operational, "Mock Error") + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertOTAAHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: h.(*component).NetAddr, + } + var wantRes = new(core.UpsertOTAAHandlerRes) + + // Operate + res, err := h.UpsertOTAA(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | DevEUI invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertOTAAHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{14, 14, 14, 14, 14, 14}, + AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateOTAABrokerReq + var wantRes = new(core.UpsertOTAAHandlerRes) + + // Operate + res, err := h.UpsertOTAA(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | AppEUI invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertOTAAHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateOTAABrokerReq + var wantRes = new(core.UpsertOTAAHandlerRes) + + // Operate + res, err := h.UpsertOTAA(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | AppKey invalid") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + NetAddr: "NetAddr", + }) + req := &core.UpsertOTAAHandlerReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, + AppKey: []byte{1, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateOTAABrokerReq + var wantRes = new(core.UpsertOTAAHandlerRes) + + // Operate + res, err := h.UpsertOTAA(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } +} diff --git a/coverage.out b/coverage.out deleted file mode 100644 index 14a436231..000000000 --- a/coverage.out +++ /dev/null @@ -1,882 +0,0 @@ -mode: set -github.com/TheThingsNetwork/ttn/core/storage/storage.go:99.37,101.3 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:98.2,99.37 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:94.16,96.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:93.109,94.16 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:89.2,89.35 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:87.3,87.16 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:83.68,85.5 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:82.18,83.68 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:80.31,82.18 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:73.2,80.31 4 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:70.22,72.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:69.69,70.22 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:63.2,63.12 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:60.9,62.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:59.2,60.9 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:56.16,58.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:55.33,56.16 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:51.2,51.23 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:48.16,50.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:46.42,48.16 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:276.2,276.12 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:273.39,275.3 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:272.32,273.39 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:267.3,267.13 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:264.65,266.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:264.3,264.65 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:261.51,263.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:261.3,261.51 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:256.18,258.5 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:254.4,256.18 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:252.21,254.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:244.57,252.21 3 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:244.2,244.57 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:240.23,242.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:239.55,240.23 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:234.3,234.13 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:231.44,233.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:231.3,231.44 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:228.17,230.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:226.57,228.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:225.62,226.57 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:220.3,220.13 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:217.47,219.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:217.3,217.47 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:214.17,216.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:213.3,214.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:210.32,212.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:209.3,210.32 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:206.44,208.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:206.3,206.44 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:203.17,205.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:201.57,203.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:201.2,201.57 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:198.3,198.37 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:195.17,197.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:193.32,195.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:191.2,193.32 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:188.16,190.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:187.98,188.16 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:183.2,183.23 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:180.3,180.13 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:177.47,179.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:177.3,177.47 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:174.17,176.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:173.3,174.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:170.32,172.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:169.3,170.32 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:166.17,168.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:164.47,166.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:164.2,164.47 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:161.3,161.37 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:158.17,160.4 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:156.32,158.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:154.2,156.32 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:151.16,153.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:150.98,151.16 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:146.2,146.33 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:143.2,143.13 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:143.13,145.3 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:140.4,140.51 1 0 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:137.50,138.10 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:135.32,137.50 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:135.3,135.32 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:129.28,134.4 4 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:128.6,129.28 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:125.2,128.6 4 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:120.2,120.16 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:120.16,122.3 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:117.3,117.13 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:114.22,116.4 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:113.3,114.22 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:111.4,111.14 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:108.59,110.5 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:107.17,108.59 1 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:105.45,107.17 2 1 -github.com/TheThingsNetwork/ttn/core/storage/storage.go:104.2,105.45 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:99.2,99.16 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:99.16,101.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:94.2,95.11 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:92.2,93.16 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:90.2,91.16 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:88.2,89.16 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:83.59,87.14 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:80.2,80.12 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:77.2,77.21 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:77.21,79.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:74.49,76.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:73.51,74.49 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:68.2,68.10 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:64.23,66.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:63.2,64.23 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:59.23,61.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:54.81,59.23 4 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:49.2,49.52 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:45.16,47.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:43.68,45.16 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:110.2,112.14 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:103.3,103.23 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:103.23,105.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:101.3,101.23 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/scoreComputer.go:101.23,103.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:97.2,97.63 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:93.33,95.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:93.2,93.33 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:88.36,90.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:88.2,88.36 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:83.35,85.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:83.2,83.35 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:78.35,80.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:78.2,78.35 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:73.35,75.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:71.48,73.35 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:301.2,301.12 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:298.48,300.3 1 0 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:297.56,298.48 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:293.2,293.18 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:290.16,292.3 1 0 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:288.52,290.16 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:278.2,278.29 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:274.2,274.15 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:274.15,276.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:270.2,270.15 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:270.15,272.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:266.17,268.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:265.39,266.17 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:258.2,261.20 3 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:254.23,256.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:250.55,254.23 3 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:243.2,246.59 3 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:238.41,240.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:236.2,238.41 3 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:231.16,233.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:230.2,231.16 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:226.2,227.58 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:224.2,225.17 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:222.2,223.17 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:220.2,221.17 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:218.2,219.17 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:214.79,217.14 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:208.37,210.2 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:204.2,204.20 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:196.35,201.4 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:195.35,196.35 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:191.2,195.35 3 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:188.16,190.3 1 0 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:182.57,188.16 4 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:175.2,175.68 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:171.3,173.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:168.36,171.3 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:168.2,168.36 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:163.3,165.3 1 0 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:158.59,163.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:158.3,158.59 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:156.16,158.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:151.2,156.16 5 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:146.16,148.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:145.2,146.16 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:140.16,142.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:138.100,140.16 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:126.2,130.8 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:122.16,124.3 1 0 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:121.2,122.16 2 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:116.22,118.3 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:116.2,116.22 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:112.2,113.72 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:104.2,111.4 1 1 -github.com/TheThingsNetwork/ttn/core/dutycycle/dutyManager.go:101.92,103.11 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:98.51,100.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:93.35,95.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:85.51,90.2 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:81.2,81.21 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:78.2,78.23 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:78.23,80.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:75.3,75.21 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:72.69,74.4 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:72.3,72.69 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:64.33,69.5 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:63.29,64.33 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:59.24,63.29 3 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:57.2,59.24 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:54.16,56.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:50.63,54.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:46.2,46.54 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:43.16,45.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:41.74,43.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:119.2,119.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:118.37,118.77 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:118.2,118.37 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:114.28,117.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:114.2,114.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:113.28,113.77 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:111.55,113.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:103.2,107.19 5 1 -github.com/TheThingsNetwork/ttn/core/components/router/storage.go:100.16,102.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:97.16,99.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:96.2,97.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:90.126,93.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:89.120,90.126 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:81.2,85.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:77.2,77.25 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:77.25,79.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:73.29,75.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:73.2,73.29 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:69.16,71.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:68.97,69.16 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:64.2,64.12 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:61.43,63.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:58.2,61.43 3 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:54.16,56.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:52.34,54.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:47.45,49.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:371.2,371.27 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:367.17,369.4 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:363.3,367.17 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:360.3,361.57 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:358.3,359.71 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:355.17,357.21 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:353.2,355.17 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:349.2,349.22 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:349.22,351.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:344.38,347.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:344.2,344.38 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:339.37,342.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:339.2,339.37 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:334.2,334.21 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:334.21,337.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:327.4,330.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:324.53,327.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:323.25,324.53 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:315.2,323.25 8 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:307.4,310.18 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:302.5,303.11 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:298.63,301.6 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:296.4,296.18 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:296.18,298.63 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:290.4,292.11 2 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:288.4,289.83 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:286.4,287.83 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:279.51,285.22 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:278.33,279.51 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:261.111,278.33 9 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:258.2,258.12 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:254.80,257.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:250.2,254.80 5 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:245.21,249.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:241.80,245.21 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:234.2,237.23 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:229.16,232.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:228.2,229.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:223.16,226.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:222.2,223.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:214.54,219.3 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:212.95,214.54 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:209.2,209.79 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:204.66,206.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:204.2,204.66 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:198.38,201.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:197.2,198.38 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:194.3,194.38 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:191.3,192.56 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:189.3,190.61 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:187.2,187.16 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:187.16,188.38 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:184.3,184.39 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:179.67,183.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:178.3,179.67 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:175.29,177.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:170.3,175.29 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:165.21,170.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:158.2,165.21 5 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:154.66,157.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:153.2,154.66 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:148.16,150.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:147.2,148.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:141.29,144.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:141.2,141.29 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:137.25,140.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:137.2,137.25 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:133.16,136.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:126.106,133.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:122.2,122.79 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:119.66,121.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:119.2,119.66 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:115.38,118.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:114.2,115.38 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:109.16,111.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/router.go:102.2,109.16 3 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:81.2,81.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:80.37,80.80 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:80.2,80.37 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:76.28,79.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:74.55,76.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:67.2,70.19 4 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:64.16,66.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:62.51,64.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:57.35,59.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:52.51,54.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:48.2,48.33 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:45.16,47.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:43.57,45.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:39.2,39.34 1 1 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:36.16,38.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/router/gtwStorage.go:34.53,36.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:97.2,100.23 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:94.16,96.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:90.78,94.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:86.2,86.88 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:80.33,83.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:80.2,80.33 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:77.16,79.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:76.2,77.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:73.66,75.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:69.52,73.66 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:65.2,65.19 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:62.32,64.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:59.69,62.32 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:56.2,56.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:52.23,54.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:51.28,52.23 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:48.51,51.28 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:45.2,45.46 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:42.16,44.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:40.64,42.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:169.2,169.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:168.37,168.75 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:168.2,168.37 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:164.28,167.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:164.2,164.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:160.28,163.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:160.2,160.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:156.28,159.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:154.55,156.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:145.2,150.19 6 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:142.16,144.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:140.51,142.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:135.35,137.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:131.2,131.24 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:128.22,130.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:127.2,128.22 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:124.16,126.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:120.75,124.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:116.2,116.18 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:113.63,115.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:112.2,113.63 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:108.3,108.79 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:106.4,106.50 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:103.31,105.5 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:101.36,103.31 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/pktStorage.go:100.23,101.36 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:86.45,109.2 14 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:705.2,705.10 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:699.11,704.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:690.115,699.11 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:680.2,686.8 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:676.16,678.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:675.2,676.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:672.84,674.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:672.2,672.84 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:669.66,671.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:650.159,669.66 8 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:622.2,647.8 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:618.16,620.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:610.2,618.16 5 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:606.16,608.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:605.2,606.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:601.68,603.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:591.133,601.68 7 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:584.33,586.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:581.62,584.33 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:574.4,576.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:573.4,573.29 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:569.18,572.5 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:567.4,569.18 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:562.18,565.5 2 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:558.75,562.18 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:557.33,558.75 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:557.2,557.33 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:551.66,554.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:551.2,551.66 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:548.17,550.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:541.2,548.17 6 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:536.16,539.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:530.2,536.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:524.3,525.56 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:518.4,521.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:516.4,517.47 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:512.18,515.5 2 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:501.13,512.18 5 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:496.33,501.13 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:496.2,496.33 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:491.16,494.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:484.97,491.16 6 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:477.4,479.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:475.19,477.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:474.33,475.19 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:474.2,474.33 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:470.16,472.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:461.2,470.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:456.16,460.3 3 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:455.2,456.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:448.16,452.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:432.2,448.16 7 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:427.43,430.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:411.2,427.43 13 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:407.17,410.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:405.2,407.17 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:398.33,402.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:397.2,398.33 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:391.16,395.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:384.114,391.16 5 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:376.3,378.81 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:373.3,375.65 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:371.3,372.26 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:368.23,369.26 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:366.32,368.23 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:361.61,366.32 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:354.72,357.2 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:348.4,348.27 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:344.25,347.5 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:343.4,344.25 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:339.5,339.13 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:336.23,338.6 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:334.52,336.23 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:328.3,334.52 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:327.4,327.70 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:326.30,326.54 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:315.3,326.30 6 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:313.6,314.10 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:302.79,313.6 6 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:293.2,296.41 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:289.2,292.50 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:284.2,288.44 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:255.2,283.21 12 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:250.29,252.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:250.2,250.29 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:247.16,249.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:242.2,247.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:238.25,241.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:238.2,238.25 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:234.26,237.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:234.2,234.26 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:230.26,233.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:230.2,230.26 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:226.27,229.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:222.115,226.27 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:212.2,218.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:207.32,210.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:206.2,207.32 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:201.27,204.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:201.2,201.27 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:196.26,199.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:196.2,196.26 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:191.26,194.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:185.121,191.26 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:178.2,180.39 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:174.2,177.48 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:170.2,173.42 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:145.2,169.21 12 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:140.16,142.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:138.2,140.16 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:132.113,135.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:128.109,132.113 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:124.2,124.12 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:121.43,123.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:118.2,121.43 3 1 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:114.16,116.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/handler.go:112.35,114.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:99.2,99.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:99.28,102.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:95.2,95.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:95.28,98.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:91.2,91.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:91.28,94.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:90.2,90.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:90.28,90.74 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:89.2,89.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:89.28,89.56 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:88.28,88.56 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:88.2,88.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:87.28,87.55 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:85.55,87.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:72.51,82.2 9 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:67.35,69.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:62.51,64.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:58.2,58.24 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:55.30,57.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:54.2,55.30 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:51.16,53.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:49.75,51.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:45.2,45.34 1 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:41.16,43.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:39.53,41.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/handler/devStorage.go:103.2,103.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:58.40,60.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:54.2,54.61 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:51.16,53.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:49.60,51.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:44.39,46.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:40.2,40.12 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:37.39,39.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/dialer.go:36.31,37.39 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:97.2,101.28 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:95.3,95.84 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:92.53,94.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:91.16,92.53 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:87.52,91.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:83.2,83.39 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:80.2,80.25 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:80.25,82.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:77.3,79.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:75.15,77.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:71.83,75.15 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:67.2,67.34 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:64.16,66.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:62.63,64.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:58.2,58.34 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:54.16,56.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:52.67,54.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:201.2,201.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:195.31,199.4 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:193.28,195.31 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:193.2,193.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:189.28,192.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:189.2,189.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:185.28,188.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:183.58,185.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:178.2,179.19 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:175.32,177.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:170.54,175.32 5 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:166.2,166.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:165.28,165.58 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:165.2,165.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:164.28,164.72 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:164.2,164.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:163.28,163.56 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:163.2,163.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:159.28,162.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:159.2,159.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:155.28,158.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:155.2,155.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:151.28,154.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:149.55,151.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:137.51,146.2 8 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:132.35,134.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:127.60,129.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:123.2,123.36 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:120.16,122.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:118.84,120.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:114.2,114.59 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:111.2,111.15 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:111.15,113.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:107.4,109.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:104.103,107.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/controller.go:101.28,104.103 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:99.16,100.53 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:98.2,99.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:94.16,97.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:90.2,94.16 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:86.113,89.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:82.107,86.113 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:78.2,78.12 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:75.32,77.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:75.2,75.32 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:71.2,71.12 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:71.12,73.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:67.12,69.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:62.2,67.12 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:58.16,60.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:57.2,58.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:53.16,55.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:51.34,53.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:46.45,48.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:308.2,314.8 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:304.82,307.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:303.2,304.82 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:299.16,302.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:297.2,299.16 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:291.40,294.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:287.2,291.40 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:282.16,286.3 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:272.2,282.16 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:268.16,271.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:267.2,268.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:261.60,264.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:260.2,261.60 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:250.2,250.19 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:250.19,255.3 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:242.9,246.9 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:242.3,242.9 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:238.10,240.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:237.3,238.10 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:234.17,235.12 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:233.3,234.17 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:229.17,230.12 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:222.32,229.17 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:210.2,222.32 5 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:208.3,208.38 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:205.3,206.38 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:202.3,204.40 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:199.16,201.38 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:194.2,199.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:190.16,193.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:183.107,190.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:176.2,179.8 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:172.16,175.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:162.2,172.16 4 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:157.16,160.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:156.2,157.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:153.50,155.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:152.2,153.50 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:146.53,149.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:146.2,146.53 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:141.38,144.3 2 0 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:141.2,141.38 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:135.16,138.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:128.2,135.16 3 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:124.16,127.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:123.2,124.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:117.2,117.11 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:117.11,120.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:112.57,114.9 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:111.37,112.57 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:110.2,111.37 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:104.3,107.4 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/broker.go:100.53,103.4 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:95.2,95.17 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:92.2,92.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:92.28,94.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:88.28,91.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:88.2,88.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:84.28,87.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:84.2,84.28 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:80.28,83.3 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:78.55,80.28 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:68.51,75.2 6 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:63.35,65.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:58.51,60.2 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:54.2,54.24 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:51.30,53.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:50.2,51.30 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:47.16,49.3 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:45.60,47.16 2 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:41.2,41.34 1 1 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:37.16,39.3 1 0 -github.com/TheThingsNetwork/ttn/core/components/broker/appStorage.go:35.53,37.16 2 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:62.48,64.2 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:58.2,58.73 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:54.36,56.3 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:54.2,54.36 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:51.20,53.3 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:49.60,51.20 2 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:45.2,45.33 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:42.20,44.3 1 1 -github.com/TheThingsNetwork/ttn/core/api/topics.go:40.52,42.20 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:97.19,99.11 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:93.82,97.19 4 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:92.19,93.82 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:88.6,92.19 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:85.96,88.6 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:79.2,79.35 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:76.16,78.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:74.55,76.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:67.33,70.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:66.62,67.33 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:59.2,62.12 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:54.54,57.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:51.49,54.54 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:46.60,48.2 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:283.2,283.20 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:280.16,282.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:274.2,280.16 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:269.16,272.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:268.2,269.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:263.40,266.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:261.82,263.40 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:258.2,258.20 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:254.16,257.3 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:248.2,254.16 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:243.16,246.3 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:242.2,243.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:238.16,240.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:237.2,238.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:232.40,235.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:230.80,232.40 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:224.2,226.77 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:223.3,223.41 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:219.17,222.4 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:217.2,219.17 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:216.3,216.39 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:212.17,215.4 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:210.2,212.17 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:209.2,209.20 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:204.16,207.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:202.83,204.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:197.2,199.16 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:189.69,192.5 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:187.30,189.69 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:186.40,187.30 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:182.2,186.40 5 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:173.29,179.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:173.2,173.29 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:167.60,170.3 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:154.74,167.60 7 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:150.2,150.20 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:146.16,148.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:134.74,146.16 6 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:128.2,128.17 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:128.17,130.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:122.4,122.18 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:122.18,124.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:119.4,120.69 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:117.4,118.40 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:115.4,116.40 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:114.4,114.26 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:110.52,112.5 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:108.39,110.52 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:107.3,108.39 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/udp.go:102.4,103.9 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:94.3,100.9 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:91.10,93.4 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:89.2,91.10 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:63.3,88.9 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:56.52,58.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:55.45,56.52 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:54.3,55.45 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:50.17,52.4 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:49.3,50.17 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:45.38,48.4 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:45.3,45.38 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:42.10,44.4 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:40.2,42.10 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:38.2,39.14 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:37.2,37.28 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:32.52,34.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:31.2,32.52 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:28.16,30.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:27.2,28.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:22.22,24.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:20.94,22.22 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:175.2,175.15 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:169.54,171.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:167.41,169.54 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:164.37,167.41 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:159.71,164.37 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:150.2,150.12 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:144.48,146.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:141.73,144.48 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:138.37,141.73 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:133.67,138.37 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:117.2,123.18 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:114.16,116.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:113.2,114.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:108.21,110.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:106.114,108.21 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/udp/conversions.go:101.2,102.91 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/safeclient.go:25.33,29.2 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/safeclient.go:18.61,22.2 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:93.2,101.16 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:89.2,89.25 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:89.25,92.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:85.2,85.26 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:85.26,88.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:81.2,81.26 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:81.26,84.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:77.27,80.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:77.2,77.27 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:73.16,76.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:68.121,73.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:63.70,65.2 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:58.45,60.2 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:194.2,198.8 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:189.27,191.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:189.2,189.27 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:185.58,187.4 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:184.57,185.58 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:183.2,184.57 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:178.72,180.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:176.2,178.72 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:173.26,175.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:170.64,173.26 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:166.2,166.45 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:162.3,163.50 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:159.4,159.18 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:159.18,161.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:156.18,158.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:154.3,156.18 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:152.25,153.19 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:150.79,152.25 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:144.2,144.17 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:141.16,143.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:134.2,141.16 3 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:131.16,133.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:127.2,131.16 3 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:123.76,126.3 2 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:122.121,123.76 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:118.2,118.17 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:115.16,117.3 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:106.2,115.16 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/mqtt.go:101.16,103.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:99.2,99.42 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:95.2,95.16 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:95.16,98.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:90.3,91.68 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:84.3,89.20 5 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:78.3,83.20 5 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:74.3,77.41 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:73.3,73.21 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:68.4,71.12 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:64.11,66.13 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:62.17,64.11 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:61.25,62.17 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:56.92,61.25 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:44.2,46.15 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:39.37,42.3 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:31.88,39.37 6 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:180.2,180.18 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:173.40,177.4 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:171.3,173.40 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:167.23,170.4 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:166.32,167.23 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:163.112,166.32 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:155.3,156.78 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:153.3,154.18 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:151.3,152.10 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:147.3,147.17 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:147.17,149.4 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:137.7,141.8 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:134.24,136.8 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:133.39,134.24 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:128.3,133.39 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:124.17,126.4 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:116.22,124.17 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:116.2,116.22 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:110.17,110.77 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/mqtt/client.go:105.122,110.17 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:92.2,95.12 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:88.16,90.3 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:87.2,88.16 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:81.30,83.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:79.3,81.30 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:71.3,77.33 5 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:60.3,69.28 8 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:56.3,58.33 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:54.3,54.29 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:49.5,51.5 1 0 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:47.60,49.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:47.4,47.60 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:44.29,46.5 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:43.49,44.29 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:40.55,43.49 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:38.2,40.55 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:31.25,36.3 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:29.76,31.25 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/statuspage.go:24.34,26.2 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:67.46,75.2 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:60.17,62.4 1 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:56.80,60.17 4 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:54.35,56.80 2 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/http.go:43.45,51.2 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/healthz.go:21.73,25.2 3 1 -github.com/TheThingsNetwork/ttn/core/adapters/http/healthz.go:16.31,18.2 1 1 From e6a81c68fc6d9ace6fe09c4f41077ad85e735768 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Mar 2016 09:33:32 +0100 Subject: [PATCH 1173/2266] [cli] Update CLI doc and version info --- cmd/broker.go | 19 ++++++++++--------- cmd/handler.go | 11 ++++++----- cmd/handler_test.go | 12 ------------ cmd/root.go | 4 ++-- cmd/{broker_test.go => root_test.go} | 4 ++-- cmd/router.go | 14 ++++++++------ cmd/router_test.go | 12 ------------ cmd/version.go | 6 +++--- cmd/version_test.go | 28 ---------------------------- main.go | 2 +- ttnctl/cmd/device.go | 10 +++++----- ttnctl/cmd/downlink.go | 4 ++-- ttnctl/cmd/join.go | 4 ++-- ttnctl/cmd/root.go | 4 ++-- ttnctl/cmd/uplink.go | 4 ++-- ttnctl/cmd/version.go | 4 ++-- ttnctl/main.go | 2 +- 17 files changed, 48 insertions(+), 96 deletions(-) delete mode 100644 cmd/handler_test.go rename cmd/{broker_test.go => root_test.go} (51%) delete mode 100644 cmd/router_test.go delete mode 100644 cmd/version_test.go diff --git a/cmd/broker.go b/cmd/broker.go index 71103ec34..e5dc7502f 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -21,10 +21,11 @@ import ( var brokerCmd = &cobra.Command{ Use: "broker", Short: "The Things Network broker", - Long: ` -The broker is responsible for finding the right handler for uplink packets it -receives from routers. This means that handlers have to register applications -and personalized devices (with their network session keys) with the router. + Long: `ttn broker starts the Broker component of The Things Network. + +The Broker is responsible for finding the right handler for uplink packets it +receives from Routers. Handlers have register Applications and personalized +devices (with their network session keys) with the Broker. `, PreRun: func(cmd *cobra.Command, args []string) { var statusServer string @@ -124,18 +125,18 @@ func init() { brokerCmd.Flags().String("devices_database", "boltdb:/tmp/ttn_devs_broker.db", "Devices Database connection") viper.BindPFlag("broker.devices_database", brokerCmd.Flags().Lookup("devices_database")) - brokerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") + brokerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") viper.BindPFlag("broker.status-address", brokerCmd.Flags().Lookup("status-address")) viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) - brokerCmd.Flags().String("uplink-address", "", "The IP address to listen for uplink messages from routers") - brokerCmd.Flags().Int("uplink-port", 1881, "The main communication port") + brokerCmd.Flags().String("uplink-address", "0.0.0.0", "The IP address to listen for uplink communication") + brokerCmd.Flags().Int("uplink-port", 1881, "The port for uplink communication") viper.BindPFlag("broker.uplink-address", brokerCmd.Flags().Lookup("uplink-address")) viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) - brokerCmd.Flags().String("downlink-address", "", "The IP address to listen for downlink messages from handler") - brokerCmd.Flags().Int("downlink-port", 1781, "The main communication port") + brokerCmd.Flags().String("downlink-address", "0.0.0.0", "The IP address to listen for downlink communication") + brokerCmd.Flags().Int("downlink-port", 1781, "The port for downlink communication") viper.BindPFlag("broker.downlink-address", brokerCmd.Flags().Lookup("downlink-address")) viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index 2c2cc651c..22d387657 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -23,8 +23,9 @@ import ( var handlerCmd = &cobra.Command{ Use: "handler", Short: "The Things Network handler", - Long: ` -The default handler is the bridge between The Things Network and applications. + Long: `ttn handler starts a default Handler for The Things Network + +The Handler is the bridge between The Things Network and applications. `, PreRun: func(cmd *cobra.Command, args []string) { var statusServer string @@ -150,13 +151,13 @@ func init() { viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) - handlerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") + handlerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") viper.BindPFlag("handler.status-address", handlerCmd.Flags().Lookup("status-address")) viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) - handlerCmd.Flags().String("server-address", "localhost", "The IP address to listen for uplink messages from brokers") - handlerCmd.Flags().Int("server-port", 1882, "The port for the uplink") + handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication from other components") + handlerCmd.Flags().Int("server-port", 1882, "The port for communication from other components") viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) diff --git a/cmd/handler_test.go b/cmd/handler_test.go deleted file mode 100644 index a7867336e..000000000 --- a/cmd/handler_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package cmd - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestHandlerCmd(t *testing.T) { - a := New(t) - a.So(handlerCmd.IsAvailableCommand(), ShouldBeTrue) -} diff --git a/cmd/root.go b/cmd/root.go index 50b92c74e..d22ab0586 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -21,8 +21,8 @@ var ctx log.Interface // RootCmd is executed when ttn is executed without a subcommand var RootCmd = &cobra.Command{ Use: "ttn", - Short: "The Things Network", - Long: `The Things Network's backend server`, + Short: "The Things Network's backend servers", + Long: `ttn launches The Things Network's backend servers`, PersistentPreRun: func(cmd *cobra.Command, args []string) { var logLevel = log.InfoLevel if viper.GetBool("debug") { diff --git a/cmd/broker_test.go b/cmd/root_test.go similarity index 51% rename from cmd/broker_test.go rename to cmd/root_test.go index 13e3df43b..5f559daa4 100644 --- a/cmd/broker_test.go +++ b/cmd/root_test.go @@ -6,7 +6,7 @@ import ( . "github.com/smartystreets/assertions" ) -func TestBrokerCmd(t *testing.T) { +func TestRootCmd(t *testing.T) { a := New(t) - a.So(brokerCmd.IsAvailableCommand(), ShouldBeTrue) + a.So(RootCmd.IsAvailableCommand(), ShouldBeTrue) } diff --git a/cmd/router.go b/cmd/router.go index 548ae4c46..4050d03ef 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -25,7 +25,9 @@ import ( var routerCmd = &cobra.Command{ Use: "router", Short: "The Things Network router", - Long: `The router accepts connections from gateways and forwards uplink packets to one + Long: `ttn router starts the Router component of The Things Network. + +The Router accepts connections from gateways and forwards uplink packets to one or more brokers. The router is also responsible for monitoring gateways, collecting statistics from gateways and for enforcing TTN's fair use policy when the gateway's duty cycle is (almost) full.`, @@ -187,18 +189,18 @@ func init() { routerCmd.Flags().String("db_duty", "boltdb:/tmp/ttn_router_duty.db", "Database connection of managed dutycycles") viper.BindPFlag("router.db_duty", routerCmd.Flags().Lookup("db_duty")) - routerCmd.Flags().String("status-address", "localhost", "The IP address to listen for serving status information") + routerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") viper.BindPFlag("router.status-address", routerCmd.Flags().Lookup("status-address")) viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) - routerCmd.Flags().String("uplink-address", "", "The IP address to listen for uplink messages from gateways") - routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for the uplink") + routerCmd.Flags().String("uplink-address", "0.0.0.0", "The IP address to listen for uplink communication from gateways") + routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for uplink communication from gateways") viper.BindPFlag("router.uplink-address", routerCmd.Flags().Lookup("uplink-address")) viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) - routerCmd.Flags().String("downlink-address", "", "The IP address to listen for downlink messages from routers") - routerCmd.Flags().Int("downlink-port", 1780, "The port for the downlink") + routerCmd.Flags().String("downlink-address", "0.0.0.0", "The IP address to listen for downlink communication") + routerCmd.Flags().Int("downlink-port", 1780, "The port for downlink communication") viper.BindPFlag("router.downlink-address", routerCmd.Flags().Lookup("downlink-address")) viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) diff --git a/cmd/router_test.go b/cmd/router_test.go deleted file mode 100644 index 2553af4de..000000000 --- a/cmd/router_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package cmd - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestRouterCmd(t *testing.T) { - a := New(t) - a.So(routerCmd.IsAvailableCommand(), ShouldBeTrue) -} diff --git a/cmd/version.go b/cmd/version.go index 2afa08df5..9a35129a5 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -12,13 +12,13 @@ import ( // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", - Short: "Get the version", - Long: `Get the version`, + Short: "Get build and version information", + Long: `ttn version gets the build and version information of ttn`, Run: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ "commit": viper.GetString("gitCommit"), "build date": viper.GetString("buildDate"), - }).Infof("You are running %s of The Things Network.", viper.GetString("version")) + }).Infof("You are running %s of ttn.", viper.GetString("version")) }, } diff --git a/cmd/version_test.go b/cmd/version_test.go deleted file mode 100644 index 3c2b07014..000000000 --- a/cmd/version_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package cmd - -import ( - "testing" - - "github.com/apex/log" - "github.com/apex/log/handlers/memory" - . "github.com/smartystreets/assertions" - "github.com/spf13/viper" -) - -func TestVersionCmd(t *testing.T) { - a := New(t) - - h := memory.New() - - ctx = &log.Logger{ - Level: log.DebugLevel, - Handler: h, - } - - viper.Set("version", "v-test") - versionCmd.Run(versionCmd, []string{}) - - a.So(h.Entries, ShouldHaveLength, 1) - a.So(h.Entries[0].Message, ShouldContainSubstring, "The Things Network") - a.So(h.Entries[0].Message, ShouldContainSubstring, "v-test") -} diff --git a/main.go b/main.go index 8cdb2c9e8..263751043 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ var ( ) func main() { - viper.Set("version", "v0") + viper.Set("version", "v1-staging") viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 300cbc697..a104cd929 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -28,8 +28,8 @@ func getHandlerManager() core.AuthHandlerClient { // devicesCmd represents the `devices` command var devicesCmd = &cobra.Command{ Use: "devices", - Short: "List registered devices on the Handler", - Long: `List registered devices on the Handler`, + Short: "Manage devices on the Handler", + Long: `ttnctl devices retrieves a list of devices that your application registered on the Handler.`, Run: func(cmd *cobra.Command, args []string) { appEUI, err := util.Parse64(viper.GetString("app-eui")) if err != nil { @@ -77,8 +77,8 @@ var devicesCmd = &cobra.Command{ // devicesRegisterCmd represents the `device register` command var devicesRegisterCmd = &cobra.Command{ Use: "register [DevEUI] [AppKey]", - Short: "Create or Update OTAA registrations on the Handler", - Long: `Create or Update OTAA registrations on the Handler`, + Short: "Create or Update registrations on the Handler", + Long: `ttnctl device register creates or updates an OTAA registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { ctx.Fatal("Insufficient arguments") @@ -117,7 +117,7 @@ var devicesRegisterCmd = &cobra.Command{ var devicesRegisterPersonalizedCmd = &cobra.Command{ Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", Short: "Create or Update ABP registrations on the Handler", - Long: `Create or Update ABP registrations on the Handler`, + Long: `ttnctl device register creates or updates an ABP registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 3 { ctx.Fatal("Insufficient arguments") diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 563cf18fb..7093d3769 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -18,8 +18,8 @@ import ( // downlinkCmd represents the `downlink` command var downlinkCmd = &cobra.Command{ Use: "downlink [DevEUI] [Payload] [TTL]", - Short: "Send a downlink message to the network", - Long: `Send a downlink message to the network`, + Short: "Send downlink messages to the network", + Long: `ttnctl downlink sends a downlink message to the network`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { ctx.Fatal("Insufficient arguments") diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index c84f1b77b..c9aafaa21 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -21,8 +21,8 @@ import ( // joinCmd represents the `join-request` command var joinCmd = &cobra.Command{ Use: "join [DevEUI] [AppKey]", - Short: "Send an join request message to the network", - Long: `Send an join request to the network`, + Short: "Send a join requests to the network", + Long: `ttnctl join sends a join request to the network`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { ctx.Fatalf("Insufficient arguments") diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 2df766801..0af22b1c8 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -20,8 +20,8 @@ var ctx log.Interface // RootCmd is the entrypoint for handlerctl var RootCmd = &cobra.Command{ Use: "ttnctl", - Short: "TTN Control", - Long: `ttnctl is used to control TTN from the command line.`, + Short: "Control The Things Network from the command line", + Long: `ttnctl controls The Things Network from the command line.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { var logLevel = log.InfoLevel if viper.GetBool("debug") { diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index e4888eac0..cc8bb4366 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -21,8 +21,8 @@ import ( // uplinkCmd represents the `uplink` command var uplinkCmd = &cobra.Command{ Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [Payload] [FCnt]", - Short: "Send an uplink message to the network", - Long: `Send an uplink message to the network`, + Short: "Send uplink messages to the network", + Long: `ttnctl uplink sends an uplink message to the network`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 5 { ctx.Fatalf("Insufficient arguments") diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index e3514e7a1..178527026 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -12,8 +12,8 @@ import ( // versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", - Short: "Get the version", - Long: `Get the version`, + Short: "Get build and version information", + Long: `ttnctl version gets the build and version information of ttnctl`, Run: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ "commit": viper.GetString("gitCommit"), diff --git a/ttnctl/main.go b/ttnctl/main.go index 579b60a7d..c9be40b96 100644 --- a/ttnctl/main.go +++ b/ttnctl/main.go @@ -14,7 +14,7 @@ var ( ) func main() { - viper.Set("version", "v0") + viper.Set("version", "v1-staging") viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() From ecce6ad8529f98bbc33e5a533fdb4eb6a650ceb0 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 14:22:09 +0100 Subject: [PATCH 1174/2266] Add missing check for extra TTL parameter --- ttnctl/cmd/downlink.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 7093d3769..98c35cda9 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -19,9 +19,12 @@ import ( var downlinkCmd = &cobra.Command{ Use: "downlink [DevEUI] [Payload] [TTL]", Short: "Send downlink messages to the network", - Long: `ttnctl downlink sends a downlink message to the network`, + Long: `ttnctl downlink sends a downlink message to the network + +The DevEUI should be an 8-byte long hex-encoded string (16 chars), whereas the TTL is +expected to define a Time To Live in a handy format, for instance: "1h" for one hour.`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 2 { + if len(args) < 3 { ctx.Fatal("Insufficient arguments") } From 4132e8ddf93deedead9c6666313ee06ea4a85924 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Mar 2016 10:25:55 +0100 Subject: [PATCH 1175/2266] [hotfix] Fix stat registry - Use RWMutex instead of regular Mutex - Check if defaultTTL is set in Renew method --- utils/stats/registry.go | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/utils/stats/registry.go b/utils/stats/registry.go index c2c927413..09b5d39ce 100644 --- a/utils/stats/registry.go +++ b/utils/stats/registry.go @@ -28,7 +28,7 @@ type TTNRegistry struct { metrics map[string]interface{} timeouts map[string]uint defaultTTL uint - mutex sync.Mutex + mutex sync.RWMutex } // NewRegistry reates a new registry and starts a goroutine for the TTL. @@ -49,8 +49,8 @@ func (r *TTNRegistry) Each(f func(string, interface{})) { // Get the metric by the given name or nil if none is registered. func (r *TTNRegistry) Get(name string) interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() + r.mutex.RLock() + defer r.mutex.RUnlock() return r.metrics[name] } @@ -59,14 +59,14 @@ func (r *TTNRegistry) Get(name string) interface{} { // The interface can be the metric to register if not found in registry, // or a function returning the metric for lazy instantiation. func (r *TTNRegistry) GetOrRegister(name string, i interface{}) interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() + r.mutex.RLock() if metric, ok := r.metrics[name]; ok { return metric } if v := reflect.ValueOf(i); v.Kind() == reflect.Func { i = v.Call(nil)[0].Interface() } + r.mutex.RUnlock() r.register(name, i) return i } @@ -81,8 +81,8 @@ func (r *TTNRegistry) Register(name string, i interface{}) error { // RunHealthchecks runs all registered healthchecks. func (r *TTNRegistry) RunHealthchecks() { - r.mutex.Lock() - defer r.mutex.Unlock() + r.mutex.RLock() + defer r.mutex.RUnlock() for _, i := range r.metrics { if h, ok := i.(metrics.Healthcheck); ok { h.Check() @@ -140,7 +140,20 @@ func (r *TTNRegistry) SetTTL(name string, ticks uint) { // Renew sets the TTL of a metric to the default value func (r *TTNRegistry) Renew(name string) { - r.SetTTL(name, r.defaultTTL) + if r.defaultTTL == 0 { + return + } + + r.mutex.RLock() + _, ok := r.metrics[name] + ttl, _ := r.timeouts[name] + r.mutex.RUnlock() + + if ok && r.defaultTTL > ttl { + r.mutex.Lock() + defer r.mutex.Unlock() + r.timeouts[name] = r.defaultTTL + } } func (r *TTNRegistry) register(name string, i interface{}) error { @@ -158,8 +171,8 @@ func (r *TTNRegistry) register(name string, i interface{}) error { } func (r *TTNRegistry) registered() map[string]interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() + r.mutex.RLock() + defer r.mutex.RUnlock() metrics := make(map[string]interface{}, len(r.metrics)) for name, i := range r.metrics { metrics[name] = i From c794244e3d670e6008df307d59d745fd411e4307 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 14:37:10 +0100 Subject: [PATCH 1176/2266] [debt/metadata] Add missing mandatory metadata to uplink and downlink definitions --- core/definitions.go | 18 +++++++++++------- core/protos/core.proto | 35 ++++++++++++++++++++--------------- semtech/semtech.go | 32 ++++++++++++++++---------------- 3 files changed, 47 insertions(+), 38 deletions(-) diff --git a/core/definitions.go b/core/definitions.go index 28e4c6746..4d93234bf 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -18,15 +18,19 @@ type OTAAAppReq struct { // AppMetadata represents gathered metadata that are sent to gateways type AppMetadata struct { - Frequency float32 `msg:"frequency" json:"frequency"` - DataRate string `msg:"data_rate" json:"data_rate"` - CodingRate string `msg:"coding_rate" json:"coding_rate"` - Timestamp uint32 `msg:"timestamp" json:"timestamp"` + Frequency float32 `msg:"freq" json:"freq"` + DataRate string `msg:"datr" json:"datr"` + CodingRate string `msg:"codr" json:"codr"` + Timestamp uint32 `msg:"tmst" json:"tmst"` + Time string `msg:"time" json:"time"` Rssi int32 `msg:"rssi" json:"rssi"` Lsnr float32 `msg:"lsnr" json:"lsnr"` - Altitude int32 `msg:"altitude" json:"altitude"` - Longitude float32 `msg:"longitude" json:"longitude"` - Latitude float32 `msg:"latitude" json:"latitude"` + RFChain uint32 `msg:"rfch" json:"rfch"` + CRCStatus int32 `msg:"stat" json:"stat"` + Modulation string `msg:"modu" json:"modu"` + Altitude int32 `msg:"alti" json:"alti"` + Longitude float32 `msg:"long" json:"long"` + Latitude float32 `msg:"lati" json:"lati"` } // DataDownAppReq represents downlink messages sent by applications diff --git a/core/protos/core.proto b/core/protos/core.proto index 808aa5f66..75d96b8c7 100644 --- a/core/protos/core.proto +++ b/core/protos/core.proto @@ -3,25 +3,30 @@ syntax = "proto3"; package core; message Metadata { - uint32 DutyRX1 = 1; - uint32 DutyRX2 = 2; + uint32 DutyRX1 = 1; + uint32 DutyRX2 = 2; - float Frequency = 3; - string DataRate = 4; - string CodingRate = 5; - uint32 Timestamp = 6; - int32 Rssi = 7; - float Lsnr = 8; - uint32 PayloadSize = 9; + float Frequency = 3; + string DataRate = 4; + string CodingRate = 5; + uint32 Timestamp = 6; + int32 Rssi = 7; + float Lsnr = 8; + uint32 PayloadSize = 9; + string Time = 10; + uint32 RadioFreqChan = 11; + int32 CRCStatus = 12; + string Modulation = 13; + bool InvPolarity = 14; - int32 Altitude = 21; - float Longitude = 22; - float Latitude = 23; + int32 Altitude = 21; + float Longitude = 22; + float Latitude = 23; } message StatsMetadata { - int32 Altitude = 1; - float Longitude = 2; - float Latitude = 3; + int32 Altitude = 1; + float Longitude = 2; + float Latitude = 3; } diff --git a/semtech/semtech.go b/semtech/semtech.go index 0ba15f377..f978adbdb 100644 --- a/semtech/semtech.go +++ b/semtech/semtech.go @@ -27,7 +27,7 @@ type RXPK struct { Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) Rssi *int32 `full:"Rssi" json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int32 `full:"Status" json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC + Stat *int32 `full:"CRCStatus" json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC Time *time.Time `full:"Time" json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) } @@ -35,21 +35,21 @@ type RXPK struct { // TXPK represents a downlink j,omitemptyson message format received by the gateway. // Most field are optional. type TXPK struct { - Codr *string `full:"CodingRate" json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `full:"Data" json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional - Datr *string `full:"DataRate" json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint32 `full:"FrequencyDev" json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float32 `full:"Frequency" json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `full:"Immediate" json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `full:"PolarizationInv" json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `full:"Modulation" json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `full:"NoCRC" json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint32 `full:"Power" json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint32 `full:"PreambleSize" json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `full:"Time" json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) + Codr *string `full:"CodingRate" json:"codr,omitempty"` // LoRa ECC coding rate identifier + Data *string `full:"Data" json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional + Datr *string `full:"DataRate" json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) + Fdev *uint32 `full:"FrequencyDev" json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) + Freq *float32 `full:"Frequency" json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) + Imme *bool `full:"Immediate" json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) + Ipol *bool `full:"InvPolarity" json:"ipol,omitempty"` // Lora modulation polarization inversion + Modu *string `full:"Modulation" json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" + Ncrc *bool `full:"NoCRC" json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) + Powe *uint32 `full:"Power" json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) + Prea *uint32 `full:"PreambleSize" json:"prea,omitempty"` // RF preamble size (unsigned integer) + Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) + Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) + Time *time.Time `full:"Time" json:"-"` // Send packet at a certain time (GPS synchronization required) + Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) } // Stat represents a status json message format sent by the gateway From a2badb69ecd039fecf96553209c3eee3b171e93b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:03:09 +0100 Subject: [PATCH 1177/2266] [debt/metadata] Add server time to metadata --- core/components/handler/handler.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 58d0ebe1c..f642f0c51 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -173,6 +173,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* // 4. Send the actual bundle to the consumer ctx := h.Ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") + req.Metadata.Time = time.Now().Format(time.RFC3339Nano) h.Set <- bundle{ ID: bundleID, Packet: req, @@ -284,6 +285,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // 4. Send the actual bundle to the consumer ctx := h.Ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") + req.Metadata.Time = time.Now().Format(time.RFC3339Nano) h.Set <- bundle{ ID: bundleID, Packet: req, From c83295b027a40f5867e29e279aebc07f0f65cb75 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:03:26 +0100 Subject: [PATCH 1178/2266] [debt/metadata] Fix wrong topic being used by mqtt adapter --- core/adapters/mqtt/mqtt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index 6e968b3b7..e70365ba6 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -135,7 +135,7 @@ func (a adapter) HandleJoin(bctx context.Context, req *core.JoinAppReq, _ ...grp err = a.Client.Publish(&client.PublishOptions{ QoS: mqtt.QoS2, Retain: false, - TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)), + TopicName: []byte(fmt.Sprintf("%s/devices/%s/activations", aeui, deui)), Message: data, }) if err != nil { From fb83ba4505b806c6efa08dfb42d32773e1d40b77 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:03:44 +0100 Subject: [PATCH 1179/2266] [debt/metadata] Regenerate protos & msgp --- core/core.pb.go | 239 ++++++++++++++++++++++++++++++++++------ core/definitions_gen.go | 78 +++++++++---- 2 files changed, 262 insertions(+), 55 deletions(-) diff --git a/core/core.pb.go b/core/core.pb.go index e52c336f3..e8c3aebe0 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -16,18 +16,23 @@ var _ = fmt.Errorf var _ = math.Inf type Metadata struct { - DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` - DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` - Frequency float32 `protobuf:"fixed32,3,opt,name=Frequency,json=frequency,proto3" json:"Frequency,omitempty"` - DataRate string `protobuf:"bytes,4,opt,name=DataRate,json=dataRate,proto3" json:"DataRate,omitempty"` - CodingRate string `protobuf:"bytes,5,opt,name=CodingRate,json=codingRate,proto3" json:"CodingRate,omitempty"` - Timestamp uint32 `protobuf:"varint,6,opt,name=Timestamp,json=timestamp,proto3" json:"Timestamp,omitempty"` - Rssi int32 `protobuf:"varint,7,opt,name=Rssi,json=rssi,proto3" json:"Rssi,omitempty"` - Lsnr float32 `protobuf:"fixed32,8,opt,name=Lsnr,json=lsnr,proto3" json:"Lsnr,omitempty"` - PayloadSize uint32 `protobuf:"varint,9,opt,name=PayloadSize,json=payloadSize,proto3" json:"PayloadSize,omitempty"` - Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` - Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` - Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` + DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` + DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` + Frequency float32 `protobuf:"fixed32,3,opt,name=Frequency,json=frequency,proto3" json:"Frequency,omitempty"` + DataRate string `protobuf:"bytes,4,opt,name=DataRate,json=dataRate,proto3" json:"DataRate,omitempty"` + CodingRate string `protobuf:"bytes,5,opt,name=CodingRate,json=codingRate,proto3" json:"CodingRate,omitempty"` + Timestamp uint32 `protobuf:"varint,6,opt,name=Timestamp,json=timestamp,proto3" json:"Timestamp,omitempty"` + Rssi int32 `protobuf:"varint,7,opt,name=Rssi,json=rssi,proto3" json:"Rssi,omitempty"` + Lsnr float32 `protobuf:"fixed32,8,opt,name=Lsnr,json=lsnr,proto3" json:"Lsnr,omitempty"` + PayloadSize uint32 `protobuf:"varint,9,opt,name=PayloadSize,json=payloadSize,proto3" json:"PayloadSize,omitempty"` + Time string `protobuf:"bytes,10,opt,name=Time,json=time,proto3" json:"Time,omitempty"` + RadioFreqChan uint32 `protobuf:"varint,11,opt,name=RadioFreqChan,json=radioFreqChan,proto3" json:"RadioFreqChan,omitempty"` + CRCStatus int32 `protobuf:"varint,12,opt,name=CRCStatus,json=cRCStatus,proto3" json:"CRCStatus,omitempty"` + Modulation string `protobuf:"bytes,13,opt,name=Modulation,json=modulation,proto3" json:"Modulation,omitempty"` + InvPolarity bool `protobuf:"varint,14,opt,name=InvPolarity,json=invPolarity,proto3" json:"InvPolarity,omitempty"` + Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` + Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` + Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -112,6 +117,38 @@ func (m *Metadata) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintCore(data, i, uint64(m.PayloadSize)) } + if len(m.Time) > 0 { + data[i] = 0x52 + i++ + i = encodeVarintCore(data, i, uint64(len(m.Time))) + i += copy(data[i:], m.Time) + } + if m.RadioFreqChan != 0 { + data[i] = 0x58 + i++ + i = encodeVarintCore(data, i, uint64(m.RadioFreqChan)) + } + if m.CRCStatus != 0 { + data[i] = 0x60 + i++ + i = encodeVarintCore(data, i, uint64(m.CRCStatus)) + } + if len(m.Modulation) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintCore(data, i, uint64(len(m.Modulation))) + i += copy(data[i:], m.Modulation) + } + if m.InvPolarity { + data[i] = 0x70 + i++ + if m.InvPolarity { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } if m.Altitude != 0 { data[i] = 0xa8 i++ @@ -228,6 +265,23 @@ func (m *Metadata) Size() (n int) { if m.PayloadSize != 0 { n += 1 + sovCore(uint64(m.PayloadSize)) } + l = len(m.Time) + if l > 0 { + n += 1 + l + sovCore(uint64(l)) + } + if m.RadioFreqChan != 0 { + n += 1 + sovCore(uint64(m.RadioFreqChan)) + } + if m.CRCStatus != 0 { + n += 1 + sovCore(uint64(m.CRCStatus)) + } + l = len(m.Modulation) + if l > 0 { + n += 1 + l + sovCore(uint64(l)) + } + if m.InvPolarity { + n += 2 + } if m.Altitude != 0 { n += 2 + sovCore(uint64(m.Altitude)) } @@ -478,6 +532,122 @@ func (m *Metadata) Unmarshal(data []byte) error { break } } + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCore + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Time = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RadioFreqChan", wireType) + } + m.RadioFreqChan = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RadioFreqChan |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CRCStatus", wireType) + } + m.CRCStatus = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.CRCStatus |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCore + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Modulation = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field InvPolarity", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.InvPolarity = bool(v != 0) case 21: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) @@ -749,24 +919,29 @@ var ( ) var fileDescriptorCore = []byte{ - // 304 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x91, 0xb1, 0x4e, 0xf3, 0x30, - 0x14, 0x85, 0xff, 0xa4, 0x69, 0xe3, 0xdc, 0xaa, 0xd2, 0x2f, 0x4b, 0x80, 0x85, 0x50, 0x54, 0x75, - 0x62, 0x42, 0xa2, 0x3c, 0x01, 0x50, 0x31, 0x05, 0x09, 0xb9, 0x0c, 0xac, 0xa6, 0x36, 0x55, 0xa4, - 0x34, 0x0e, 0xc9, 0xcd, 0x10, 0x9e, 0x84, 0x47, 0x62, 0x64, 0x62, 0x46, 0xf0, 0x22, 0xd8, 0x4e, - 0x42, 0xcb, 0xd2, 0xc1, 0xd2, 0xbd, 0xe7, 0x93, 0xcf, 0x39, 0x89, 0x01, 0x56, 0xba, 0x54, 0x67, - 0x45, 0xa9, 0x51, 0xd3, 0xc0, 0xce, 0xb3, 0x0f, 0x1f, 0xc8, 0xad, 0x42, 0x21, 0x05, 0x0a, 0xca, - 0x20, 0x5c, 0xd4, 0xd8, 0xf0, 0x87, 0x73, 0xe6, 0x4d, 0xbd, 0xd3, 0x09, 0x0f, 0x65, 0xbb, 0x6e, - 0xc9, 0x9c, 0xf9, 0xbb, 0x64, 0x4e, 0x4f, 0x20, 0xba, 0x29, 0xd5, 0x73, 0xad, 0xf2, 0x55, 0xc3, - 0x06, 0x86, 0xf9, 0x3c, 0x7a, 0xea, 0x05, 0x7a, 0x0c, 0x64, 0x61, 0x9c, 0xb9, 0x40, 0xc5, 0x02, - 0x03, 0x23, 0x4e, 0x64, 0xb7, 0xd3, 0x18, 0xe0, 0x5a, 0xcb, 0x34, 0x5f, 0x3b, 0x3a, 0x74, 0xd4, - 0x14, 0xec, 0x15, 0xeb, 0x7c, 0x9f, 0x6e, 0x54, 0x85, 0x62, 0x53, 0xb0, 0x91, 0x4b, 0x8d, 0xb0, - 0x17, 0x28, 0x85, 0x80, 0x57, 0x55, 0xca, 0x42, 0x03, 0x86, 0x3c, 0x28, 0xcd, 0x6c, 0xb5, 0xa4, - 0xca, 0x4b, 0x46, 0x5c, 0x8d, 0x20, 0x33, 0x33, 0x9d, 0xc2, 0xf8, 0x4e, 0x34, 0x99, 0x16, 0x72, - 0x99, 0xbe, 0x28, 0x16, 0x39, 0x9f, 0x71, 0xb1, 0x95, 0x6c, 0xc7, 0xcb, 0x0c, 0x53, 0xac, 0xa5, - 0x62, 0x07, 0xce, 0x8d, 0x88, 0x6e, 0xb7, 0x1d, 0x12, 0x9d, 0xaf, 0x5b, 0x78, 0xd8, 0x7e, 0x5d, - 0xd6, 0x0b, 0xf6, 0x66, 0x22, 0xba, 0x9b, 0x47, 0x0e, 0x92, 0xac, 0xdb, 0x67, 0x0a, 0x26, 0x4b, - 0x14, 0x58, 0xfd, 0xfe, 0xdc, 0xdd, 0x18, 0x6f, 0x5f, 0x8c, 0xbf, 0x2f, 0x66, 0xf0, 0x37, 0xe6, - 0xea, 0xff, 0xdb, 0x57, 0xec, 0xbd, 0x9b, 0xf3, 0x69, 0xce, 0xeb, 0x77, 0xfc, 0xef, 0x71, 0xe4, - 0x9e, 0xf7, 0xe2, 0x27, 0x00, 0x00, 0xff, 0xff, 0xd2, 0x28, 0x9c, 0x1e, 0xec, 0x01, 0x00, 0x00, + // 384 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0x41, 0x4e, 0xe3, 0x30, + 0x14, 0x86, 0x27, 0x6d, 0xda, 0x26, 0xce, 0x64, 0x34, 0xb2, 0x34, 0xc3, 0x13, 0x42, 0x55, 0x55, + 0xb1, 0x60, 0x85, 0x44, 0x39, 0x01, 0xa4, 0x42, 0x42, 0x6a, 0xa5, 0xca, 0x65, 0xc1, 0xd6, 0xd4, + 0xa6, 0x58, 0x4a, 0xe3, 0xe2, 0x38, 0x48, 0xe1, 0x24, 0x1c, 0x89, 0x25, 0x47, 0x40, 0x70, 0x01, + 0x8e, 0x80, 0xed, 0x24, 0x6d, 0xd9, 0x74, 0x61, 0xc9, 0xef, 0xfb, 0xfb, 0xde, 0xff, 0x3f, 0x37, + 0x08, 0x2d, 0xa4, 0xe2, 0xa7, 0x6b, 0x25, 0xb5, 0xc4, 0xbe, 0xbd, 0x0f, 0xbf, 0xda, 0x28, 0x98, + 0x72, 0x4d, 0x19, 0xd5, 0x14, 0x03, 0xea, 0x8d, 0x0b, 0x5d, 0x92, 0xdb, 0x33, 0xf0, 0x06, 0xde, + 0x49, 0x4c, 0x7a, 0xac, 0x2a, 0xb7, 0xca, 0x08, 0x5a, 0xbb, 0xca, 0x08, 0x1f, 0xa1, 0xf0, 0x4a, + 0xf1, 0xc7, 0x82, 0x67, 0x8b, 0x12, 0xda, 0x46, 0x6b, 0x91, 0xf0, 0xbe, 0x01, 0xf8, 0x10, 0x05, + 0x63, 0x33, 0x99, 0x50, 0xcd, 0xc1, 0x37, 0x62, 0x48, 0x02, 0x56, 0xd7, 0xb8, 0x8f, 0x50, 0x22, + 0x99, 0xc8, 0x96, 0x4e, 0xed, 0x38, 0xd5, 0x04, 0x6c, 0x88, 0x9d, 0x7c, 0x23, 0x56, 0x3c, 0xd7, + 0x74, 0xb5, 0x86, 0xae, 0x73, 0x0d, 0x75, 0x03, 0x30, 0x46, 0x3e, 0xc9, 0x73, 0x01, 0x3d, 0x23, + 0x74, 0x88, 0xaf, 0xcc, 0xdd, 0xb2, 0x49, 0x9e, 0x29, 0x08, 0x5c, 0x0c, 0x3f, 0x35, 0x77, 0x3c, + 0x40, 0xd1, 0x8c, 0x96, 0xa9, 0xa4, 0x6c, 0x2e, 0x9e, 0x39, 0x84, 0x6e, 0x4e, 0xb4, 0xde, 0x22, + 0xdb, 0x65, 0x7d, 0x00, 0xb9, 0x04, 0xbe, 0xb5, 0xc0, 0xc7, 0x28, 0x26, 0x94, 0x09, 0x69, 0x57, + 0x4b, 0x1e, 0x68, 0x06, 0x91, 0xeb, 0x8b, 0xd5, 0x2e, 0xb4, 0x09, 0x13, 0x92, 0xcc, 0x35, 0xd5, + 0x45, 0x0e, 0xbf, 0x5d, 0x90, 0x70, 0xd1, 0x00, 0xbb, 0xdf, 0x54, 0xb2, 0x22, 0xa5, 0x5a, 0xc8, + 0x0c, 0xe2, 0x6a, 0xbf, 0xd5, 0x86, 0xd8, 0x64, 0xd7, 0xd9, 0xd3, 0x4c, 0xa6, 0x54, 0x09, 0x5d, + 0xc2, 0x1f, 0xf3, 0x83, 0x80, 0x44, 0x62, 0x8b, 0xec, 0xeb, 0x5d, 0xa4, 0x5a, 0xe8, 0x82, 0x71, + 0xf8, 0xe7, 0xc6, 0x07, 0xb4, 0xae, 0xad, 0xf7, 0x44, 0x66, 0xcb, 0x4a, 0xfc, 0x5f, 0xbd, 0x7b, + 0xda, 0x00, 0xdb, 0x39, 0xa1, 0x75, 0xe7, 0x81, 0x13, 0x83, 0xb4, 0xae, 0x87, 0x1c, 0xc5, 0x36, + 0x61, 0xbe, 0xf9, 0xdb, 0x77, 0x6d, 0xbc, 0x7d, 0x36, 0xad, 0x7d, 0x36, 0xed, 0x9f, 0x36, 0x97, + 0x7f, 0x5f, 0x3f, 0xfa, 0xde, 0x9b, 0x39, 0xef, 0xe6, 0xbc, 0x7c, 0xf6, 0x7f, 0xdd, 0x75, 0xdd, + 0x87, 0x77, 0xfe, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x53, 0xf7, 0xf8, 0xc9, 0x86, 0x02, 0x00, 0x00, } diff --git a/core/definitions_gen.go b/core/definitions_gen.go index a40464c1a..654ab2c38 100644 --- a/core/definitions_gen.go +++ b/core/definitions_gen.go @@ -74,33 +74,45 @@ func (z ABPSubAppReq) Msgsize() (s int) { // MarshalMsg implements msgp.Marshaler func (z *AppMetadata) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) - // map header, size 9 - // string "frequency" - o = append(o, 0x89, 0xa9, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79) + // map header, size 13 + // string "freq" + o = append(o, 0x8d, 0xa4, 0x66, 0x72, 0x65, 0x71) o = msgp.AppendFloat32(o, z.Frequency) - // string "data_rate" - o = append(o, 0xa9, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x72, 0x61, 0x74, 0x65) + // string "datr" + o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x72) o = msgp.AppendString(o, z.DataRate) - // string "coding_rate" - o = append(o, 0xab, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x72, 0x61, 0x74, 0x65) + // string "codr" + o = append(o, 0xa4, 0x63, 0x6f, 0x64, 0x72) o = msgp.AppendString(o, z.CodingRate) - // string "timestamp" - o = append(o, 0xa9, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70) + // string "tmst" + o = append(o, 0xa4, 0x74, 0x6d, 0x73, 0x74) o = msgp.AppendUint32(o, z.Timestamp) + // string "time" + o = append(o, 0xa4, 0x74, 0x69, 0x6d, 0x65) + o = msgp.AppendString(o, z.Time) // string "rssi" o = append(o, 0xa4, 0x72, 0x73, 0x73, 0x69) o = msgp.AppendInt32(o, z.Rssi) // string "lsnr" o = append(o, 0xa4, 0x6c, 0x73, 0x6e, 0x72) o = msgp.AppendFloat32(o, z.Lsnr) - // string "altitude" - o = append(o, 0xa8, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) + // string "rfch" + o = append(o, 0xa4, 0x72, 0x66, 0x63, 0x68) + o = msgp.AppendUint32(o, z.RFChain) + // string "stat" + o = append(o, 0xa4, 0x73, 0x74, 0x61, 0x74) + o = msgp.AppendInt32(o, z.CRCStatus) + // string "modu" + o = append(o, 0xa4, 0x6d, 0x6f, 0x64, 0x75) + o = msgp.AppendString(o, z.Modulation) + // string "alti" + o = append(o, 0xa4, 0x61, 0x6c, 0x74, 0x69) o = msgp.AppendInt32(o, z.Altitude) - // string "longitude" - o = append(o, 0xa9, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65) + // string "long" + o = append(o, 0xa4, 0x6c, 0x6f, 0x6e, 0x67) o = msgp.AppendFloat32(o, z.Longitude) - // string "latitude" - o = append(o, 0xa8, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) + // string "lati" + o = append(o, 0xa4, 0x6c, 0x61, 0x74, 0x69) o = msgp.AppendFloat32(o, z.Latitude) return } @@ -121,26 +133,31 @@ func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { return } switch msgp.UnsafeString(field) { - case "frequency": + case "freq": z.Frequency, bts, err = msgp.ReadFloat32Bytes(bts) if err != nil { return } - case "data_rate": + case "datr": z.DataRate, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "coding_rate": + case "codr": z.CodingRate, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "timestamp": + case "tmst": z.Timestamp, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { return } + case "time": + z.Time, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + return + } case "rssi": z.Rssi, bts, err = msgp.ReadInt32Bytes(bts) if err != nil { @@ -151,17 +168,32 @@ func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { if err != nil { return } - case "altitude": + case "rfch": + z.RFChain, bts, err = msgp.ReadUint32Bytes(bts) + if err != nil { + return + } + case "stat": + z.CRCStatus, bts, err = msgp.ReadInt32Bytes(bts) + if err != nil { + return + } + case "modu": + z.Modulation, bts, err = msgp.ReadStringBytes(bts) + if err != nil { + return + } + case "alti": z.Altitude, bts, err = msgp.ReadInt32Bytes(bts) if err != nil { return } - case "longitude": + case "long": z.Longitude, bts, err = msgp.ReadFloat32Bytes(bts) if err != nil { return } - case "latitude": + case "lati": z.Latitude, bts, err = msgp.ReadFloat32Bytes(bts) if err != nil { return @@ -178,7 +210,7 @@ func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { } func (z *AppMetadata) Msgsize() (s int) { - s = 1 + 10 + msgp.Float32Size + 10 + msgp.StringPrefixSize + len(z.DataRate) + 12 + msgp.StringPrefixSize + len(z.CodingRate) + 10 + msgp.Uint32Size + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 9 + msgp.Int32Size + 10 + msgp.Float32Size + 9 + msgp.Float32Size + s = 1 + 5 + msgp.Float32Size + 5 + msgp.StringPrefixSize + len(z.DataRate) + 5 + msgp.StringPrefixSize + len(z.CodingRate) + 5 + msgp.Uint32Size + 5 + msgp.StringPrefixSize + len(z.Time) + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 5 + msgp.Uint32Size + 5 + msgp.Int32Size + 5 + msgp.StringPrefixSize + len(z.Modulation) + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 5 + msgp.Float32Size return } From dbbbc47f57f2c438281d0e9eddfa16c42f977b61 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:15:46 +0100 Subject: [PATCH 1180/2266] [feature/splitHandler] Allow handler to operate over two different net addresses --- core/components/handler/handler.go | 31 ++++++--- core/components/handler/handlerClient_test.go | 63 ++++++++++++------- core/components/handler/handlerManager.go | 4 +- core/components/handler/handler_test.go | 62 +++++++++--------- 4 files changed, 94 insertions(+), 66 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 58d0ebe1c..f860c1c4a 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -7,6 +7,7 @@ import ( "bytes" "crypto/aes" "encoding/binary" + "fmt" "math/rand" "net" "reflect" @@ -38,9 +39,10 @@ var dataRates = map[string]uint8{ // component implements the core.Component interface type component struct { Components - Set chan<- bundle - NetAddr string - Configuration struct { + Set chan<- bundle + PublicNetAddr string + PrivateNetAddr string + Configuration struct { CFList [5]uint32 NetID [3]byte RX1DROffset uint8 @@ -69,7 +71,8 @@ type Components struct { // Options is used to make handler instantiation easier type Options struct { - NetAddr string + PublicNetAddr string // Net Address used to communicate with the handler from the outside + PrivateNetAddr string // Net Address the handler provides to brokers for internal communications } // bundle are used to materialize an incoming request being bufferized, waiting for the others. @@ -85,8 +88,9 @@ type bundle struct { // New construct a new Handler func New(c Components, o Options) Interface { h := &component{ - Components: c, - NetAddr: o.NetAddr, + Components: c, + PublicNetAddr: o.PublicNetAddr, + PrivateNetAddr: o.PrivateNetAddr, } // TODO Make it configurable @@ -110,16 +114,23 @@ func New(c Components, o Options) Interface { // Start actually runs the component and starts the rpc server func (h *component) Start() error { - conn, err := net.Listen("tcp", h.NetAddr) - if err != nil { - return errors.New(errors.Operational, err) + pubConn, errPub := net.Listen("tcp", h.PublicNetAddr) + priConn, errPri := net.Listen("tcp", h.PrivateNetAddr) + + if errPub != nil || errPri != nil { + return errors.New(errors.Operational, fmt.Sprintf("Unable to open connections: %s | %s", errPub, errPri)) } server := grpc.NewServer() core.RegisterHandlerServer(server, h) core.RegisterHandlerManagerServer(server, h) - if err := server.Serve(conn); err != nil { + cherr := make(chan error) + + go func() { cherr <- server.Serve(pubConn) }() + go func() { cherr <- server.Serve(priConn) }() + + if err := <-cherr; err != nil { return errors.New(errors.Operational, err) } return nil diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go index 22994672d..e5e727492 100644 --- a/core/components/handler/handlerClient_test.go +++ b/core/components/handler/handlerClient_test.go @@ -49,7 +49,8 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -105,7 +106,8 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -144,7 +146,8 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -182,7 +185,8 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -217,7 +221,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -234,7 +239,7 @@ func TestUpsertABP(t *testing.T) { AppEUI: req.AppEUI, DevAddr: []byte{14, 14, 14, 14}, NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).NetAddr, + NetAddress: h.(*component).PrivateNetAddr, } var wantRes = new(core.UpsertABPHandlerRes) @@ -262,7 +267,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -279,7 +285,7 @@ func TestUpsertABP(t *testing.T) { AppEUI: req.AppEUI, DevAddr: []byte{14, 14, 14, 14}, NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).NetAddr, + NetAddress: h.(*component).PrivateNetAddr, } var wantRes = new(core.UpsertABPHandlerRes) @@ -307,7 +313,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -324,7 +331,7 @@ func TestUpsertABP(t *testing.T) { AppEUI: req.AppEUI, DevAddr: []byte{14, 14, 14, 14}, NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).NetAddr, + NetAddress: h.(*component).PrivateNetAddr, } var wantRes = new(core.UpsertABPHandlerRes) @@ -351,7 +358,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -389,7 +397,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -427,7 +436,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -465,7 +475,8 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -503,7 +514,8 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -517,7 +529,7 @@ func TestUpsertOTAA(t *testing.T) { var wantBrkCall = &core.ValidateOTAABrokerReq{ Token: req.Token, AppEUI: req.AppEUI, - NetAddress: h.(*component).NetAddr, + NetAddress: h.(*component).PrivateNetAddr, } var wantRes = new(core.UpsertOTAAHandlerRes) @@ -545,7 +557,8 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -559,7 +572,7 @@ func TestUpsertOTAA(t *testing.T) { var wantBrkCall = &core.ValidateOTAABrokerReq{ Token: req.Token, AppEUI: req.AppEUI, - NetAddress: h.(*component).NetAddr, + NetAddress: h.(*component).PrivateNetAddr, } var wantRes = new(core.UpsertOTAAHandlerRes) @@ -587,7 +600,8 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -601,7 +615,7 @@ func TestUpsertOTAA(t *testing.T) { var wantBrkCall = &core.ValidateOTAABrokerReq{ Token: req.Token, AppEUI: req.AppEUI, - NetAddress: h.(*component).NetAddr, + NetAddress: h.(*component).PrivateNetAddr, } var wantRes = new(core.UpsertOTAAHandlerRes) @@ -628,7 +642,8 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -665,7 +680,8 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -702,7 +718,8 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - NetAddr: "NetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 6756e6b13..34a05fbee 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -75,7 +75,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, - NetAddress: h.NetAddr, + NetAddress: h.PrivateNetAddr, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") @@ -115,7 +115,7 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR // 2. Notify the broker -> The Broker also does the token verification _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ Token: req.Token, - NetAddress: h.NetAddr, + NetAddress: h.PrivateNetAddr, AppEUI: req.AppEUI, }) if err != nil { diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 22749373b..14669be6c 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -45,7 +45,7 @@ func TestHandleDataDown(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataDown(context.Background(), req) // Check @@ -83,7 +83,7 @@ func TestHandleDataDown(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataDown(context.Background(), req) // Check @@ -121,7 +121,7 @@ func TestHandleDataDown(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataDown(context.Background(), req) // Check @@ -159,7 +159,7 @@ func TestHandleDataDown(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataDown(context.Background(), req) // Check @@ -197,7 +197,7 @@ func TestHandleDataDown(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataDown(context.Background(), req) // Check @@ -239,7 +239,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -281,7 +281,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -323,7 +323,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -365,7 +365,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -407,7 +407,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -472,7 +472,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -557,7 +557,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) chack := make(chan bool) go func() { @@ -686,7 +686,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -771,7 +771,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) chack := make(chan bool) go func() { @@ -860,7 +860,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -926,7 +926,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -1022,7 +1022,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) devStorage.OutRead.Entry = devEntry{ DevAddr: devAddr1[:], @@ -1150,7 +1150,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -1225,7 +1225,7 @@ func TestHandleDataUp(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleDataUp(context.Background(), req) // Check @@ -1306,7 +1306,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1395,7 +1395,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1470,7 +1470,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1536,7 +1536,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1602,7 +1602,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1654,7 +1654,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1704,7 +1704,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1754,7 +1754,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1804,7 +1804,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1843,7 +1843,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) res, err := handler.HandleJoin(context.Background(), req) // Check @@ -1943,7 +1943,7 @@ func TestHandleJoin(t *testing.T) { AppAdapter: appAdapter, DevStorage: devStorage, PktStorage: pktStorage, - }, Options{NetAddr: "localhost"}) + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) chack := make(chan bool) go func() { @@ -1991,7 +1991,7 @@ func TestStart(t *testing.T) { DevStorage: NewMockDevStorage(), PktStorage: NewMockPktStorage(), AppAdapter: mocks.NewAppClient(), - }, Options{NetAddr: "localhost:8888"}) + }, Options{PublicNetAddr: "localhost:8888", PrivateNetAddr: "localhost:8889"}) cherr := make(chan error) go func() { From 5da774a2ce575d1216d6f0d6dd0c913d25f73989 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:19:58 +0100 Subject: [PATCH 1181/2266] [feature/splitHandler] Modify CMD cli to allow two different ports & addresses for the handler --- cmd/handler.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index 22d387657..afd252477 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -40,7 +40,8 @@ The Handler is the bridge between The Things Network and applications. "devicesDatabase": viper.GetString("handler.dev-database"), "packetsDatabase": viper.GetString("handler.pkt-database"), "status-server": statusServer, - "server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + "internal server": fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), + "public server": fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), "ttn-broker": viper.GetString("handler.ttn-broker"), "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") @@ -131,7 +132,8 @@ The Handler is the bridge between The Things Network and applications. AppAdapter: appAdapter, }, handler.Options{ - NetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + PublicNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), + PrivateNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), }, ) @@ -156,10 +158,15 @@ func init() { viper.BindPFlag("handler.status-address", handlerCmd.Flags().Lookup("status-address")) viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) - handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication from other components") - handlerCmd.Flags().Int("server-port", 1882, "The port for communication from other components") - viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) - viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) + handlerCmd.Flags().String("internal-address", "0.0.0.0", "The IP address to listen for communication from other components") + handlerCmd.Flags().Int("internal-port", 1882, "The port for communication from other components") + viper.BindPFlag("handler.internal-address", handlerCmd.Flags().Lookup("internal-address")) + viper.BindPFlag("handler.internal-port", handlerCmd.Flags().Lookup("internal-port")) + + handlerCmd.Flags().String("public-address", "0.0.0.0", "The IP address to listen for communication with the wild open") + handlerCmd.Flags().Int("public-port", 1885, "The port for communication with the wild open") + viper.BindPFlag("handler.public-address", handlerCmd.Flags().Lookup("public-address")) + viper.BindPFlag("handler.public-port", handlerCmd.Flags().Lookup("public-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker (uplink)") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) From 45485cc2b05682eeb751c37d304b062f1056f23d Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:34:57 +0100 Subject: [PATCH 1182/2266] [feature/splitHandler] Change port in ttnctl --- cmd/handler.go | 2 +- ttnctl/cmd/device.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index afd252477..8f8dcb550 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -164,7 +164,7 @@ func init() { viper.BindPFlag("handler.internal-port", handlerCmd.Flags().Lookup("internal-port")) handlerCmd.Flags().String("public-address", "0.0.0.0", "The IP address to listen for communication with the wild open") - handlerCmd.Flags().Int("public-port", 1885, "The port for communication with the wild open") + handlerCmd.Flags().Int("public-port", 1782, "The port for communication with the wild open") viper.BindPFlag("handler.public-address", handlerCmd.Flags().Lookup("public-address")) viper.BindPFlag("handler.public-port", handlerCmd.Flags().Lookup("public-port")) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index a104cd929..40405d8bc 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -165,7 +165,7 @@ func init() { devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) - devicesCmd.Flags().String("ttn-handler", "0.0.0.0:1882", "The net address of the TTN Handler") + devicesCmd.Flags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler") viper.BindPFlag("ttn-handler", devicesCmd.Flags().Lookup("ttn-handler")) devicesCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") From f145f130425a3db8f0e15d3b932fd5dd5f5f024b Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 15:54:51 +0100 Subject: [PATCH 1183/2266] Fix metadata time conversion issue --- core/adapters/udp/conversions.go | 3 +++ core/adapters/udp/conversions_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 324d01697..0c7dd02ef 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -8,6 +8,7 @@ import ( "encoding/base64" "reflect" "strings" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" @@ -169,6 +170,8 @@ func extractMetadata(xpk interface{}, target interface{}) interface{} { e := x.Field(i).Elem() if e.Type().AssignableTo(m.FieldByName(t).Type()) { m.FieldByName(t).Set(e) + } else if e.Type().AssignableTo(reflect.TypeOf(time.Time{})) { + m.FieldByName(t).Set(reflect.ValueOf(e.Interface().(time.Time).Format(time.RFC3339Nano))) } } } diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index 6eae9ba5d..808f74bff 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -113,6 +113,9 @@ func TestExtractMetadta(t *testing.T) { DataRate: *txpk.Datr, Frequency: *txpk.Freq, Timestamp: *txpk.Tmst, + Modulation: *txpk.Modu, + RFChain: *txpk.Rfch, + Time: txpk.Time.Format(time.RFC3339Nano), } // Operate From da9b5ee1b107c6150b9e1b22903c95ba309a7953 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Mar 2016 17:27:29 +0100 Subject: [PATCH 1184/2266] [hotfix] stats mutex Was introduced in 4132e8dd --- utils/stats/registry.go | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/utils/stats/registry.go b/utils/stats/registry.go index 09b5d39ce..7ff38044c 100644 --- a/utils/stats/registry.go +++ b/utils/stats/registry.go @@ -28,7 +28,7 @@ type TTNRegistry struct { metrics map[string]interface{} timeouts map[string]uint defaultTTL uint - mutex sync.RWMutex + mutex sync.Mutex } // NewRegistry reates a new registry and starts a goroutine for the TTL. @@ -49,8 +49,8 @@ func (r *TTNRegistry) Each(f func(string, interface{})) { // Get the metric by the given name or nil if none is registered. func (r *TTNRegistry) Get(name string) interface{} { - r.mutex.RLock() - defer r.mutex.RUnlock() + r.mutex.Lock() + defer r.mutex.Unlock() return r.metrics[name] } @@ -59,14 +59,14 @@ func (r *TTNRegistry) Get(name string) interface{} { // The interface can be the metric to register if not found in registry, // or a function returning the metric for lazy instantiation. func (r *TTNRegistry) GetOrRegister(name string, i interface{}) interface{} { - r.mutex.RLock() + r.mutex.Lock() + defer r.mutex.Unlock() if metric, ok := r.metrics[name]; ok { return metric } if v := reflect.ValueOf(i); v.Kind() == reflect.Func { i = v.Call(nil)[0].Interface() } - r.mutex.RUnlock() r.register(name, i) return i } @@ -81,8 +81,8 @@ func (r *TTNRegistry) Register(name string, i interface{}) error { // RunHealthchecks runs all registered healthchecks. func (r *TTNRegistry) RunHealthchecks() { - r.mutex.RLock() - defer r.mutex.RUnlock() + r.mutex.Lock() + defer r.mutex.Unlock() for _, i := range r.metrics { if h, ok := i.(metrics.Healthcheck); ok { h.Check() @@ -144,14 +144,13 @@ func (r *TTNRegistry) Renew(name string) { return } - r.mutex.RLock() + r.mutex.Lock() + defer r.mutex.Unlock() + _, ok := r.metrics[name] ttl, _ := r.timeouts[name] - r.mutex.RUnlock() if ok && r.defaultTTL > ttl { - r.mutex.Lock() - defer r.mutex.Unlock() r.timeouts[name] = r.defaultTTL } } @@ -171,8 +170,8 @@ func (r *TTNRegistry) register(name string, i interface{}) error { } func (r *TTNRegistry) registered() map[string]interface{} { - r.mutex.RLock() - defer r.mutex.RUnlock() + r.mutex.Lock() + defer r.mutex.Unlock() metrics := make(map[string]interface{}, len(r.metrics)) for name, i := range r.metrics { metrics[name] = i From 4cd939766b13f763ecdbf41bde9c162a6b49c3f4 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 17:36:38 +0100 Subject: [PATCH 1185/2266] Fix commands using prefixed config variables --- ttnctl/cmd/device.go | 3 +-- ttnctl/cmd/downlink.go | 8 ++++---- ttnctl/cmd/join.go | 8 ++++---- ttnctl/cmd/uplink.go | 4 ++-- 4 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 40405d8bc..3ac1021bc 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -160,9 +160,7 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ func init() { RootCmd.AddCommand(devicesCmd) - devicesCmd.AddCommand(devicesRegisterCmd) - devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) devicesCmd.Flags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler") @@ -173,4 +171,5 @@ func init() { devicesCmd.PersistentFlags().String("app-token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJUVE4tSEFORExFUi0xIiwiaXNzIjoiVGhlVGhpbmdzVGhlTmV0d29yayIsInN1YiI6IjAxMDIwMzA0MDUwNjA3MDgifQ.zMHNXAVgQj672lwwDVmfYshpMvPwm6A8oNWJ7teGS2A", "The app Token to use") viper.BindPFlag("app-token", devicesCmd.PersistentFlags().Lookup("app-token")) + } diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 98c35cda9..61fee2e70 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -44,7 +44,7 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.WithError(err).Fatal("Unable to create downlink payload") } - mqtt.Setup(viper.GetString("handler.mqtt-broker"), ctx) + mqtt.Setup(viper.GetString("mqtt-broker"), ctx) mqtt.Connect() ctx.WithFields(log.Fields{ @@ -52,7 +52,7 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one "Payload": string(payload), }).Info("Pushing downlink...") - token := mqtt.Client.Publish(fmt.Sprintf("%s/devices/%x/down", viper.GetString("handler.app-eui"), devEUI), 2, false, payload) + token := mqtt.Client.Publish(fmt.Sprintf("%s/devices/%x/down", viper.GetString("app-eui"), devEUI), 2, false, payload) if token.Wait() && token.Error() != nil { ctx.WithError(token.Error()).Fatal("Downlink failed.") } else { @@ -66,8 +66,8 @@ func init() { RootCmd.AddCommand(downlinkCmd) downlinkCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") - viper.BindPFlag("handler.mqtt-broker", downlinkCmd.Flags().Lookup("mqtt-broker")) + viper.BindPFlag("mqtt-broker", downlinkCmd.Flags().Lookup("mqtt-broker")) downlinkCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") - viper.BindPFlag("handler.app-eui", downlinkCmd.Flags().Lookup("app-eui")) + viper.BindPFlag("app-eui", downlinkCmd.Flags().Lookup("app-eui")) } diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index c9aafaa21..b0ecef494 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -43,7 +43,7 @@ var joinCmd = &cobra.Command{ var appKey lorawan.AES128Key copy(appKey[:], appKeyRaw) - appEUIRaw, err := util.Parse64(viper.GetString("join.app-eui")) + appEUIRaw, err := util.Parse64(viper.GetString("app-eui")) if err != nil { ctx.Fatalf("Invalid appEUI: %s", err) } @@ -70,7 +70,7 @@ var joinCmd = &cobra.Command{ ctx.Fatalf("Unable to set MIC: %s", err) } - addr, err := net.ResolveUDPAddr("udp", viper.GetString("router.address")) + addr, err := net.ResolveUDPAddr("udp", viper.GetString("ttn-router")) if err != nil { ctx.Fatalf("Couldn't resolve UDP address: %s", err) } @@ -203,8 +203,8 @@ func init() { RootCmd.AddCommand(joinCmd) joinCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") - viper.BindPFlag("router.address", joinCmd.Flags().Lookup("ttn-router")) + viper.BindPFlag("ttn-router", joinCmd.Flags().Lookup("ttn-router")) joinCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") - viper.BindPFlag("join.app-eui", joinCmd.Flags().Lookup("app-eui")) + viper.BindPFlag("app-eui", joinCmd.Flags().Lookup("app-eui")) } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index cc8bb4366..712719e75 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -77,7 +77,7 @@ var uplinkCmd = &cobra.Command{ ctx.Fatalf("Unable to set MIC: %s", err) } - addr, err := net.ResolveUDPAddr("udp", viper.GetString("router.address")) + addr, err := net.ResolveUDPAddr("udp", viper.GetString("ttn-router")) if err != nil { ctx.Fatalf("Couldn't resolve UDP address: %s", err) } @@ -190,5 +190,5 @@ func init() { RootCmd.AddCommand(uplinkCmd) uplinkCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") - viper.BindPFlag("router.address", uplinkCmd.Flags().Lookup("ttn-router")) + viper.BindPFlag("ttn-router", uplinkCmd.Flags().Lookup("ttn-router")) } From a0f396e069b4cdb7f197f55ef0576f09d645b5ce Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 18:02:25 +0100 Subject: [PATCH 1186/2266] Add support for ConfirmedData in handler --- core/components/handler/handler.go | 43 +++-- core/components/handler/handler_test.go | 209 ++++++++++++++++++++++++ 2 files changed, 238 insertions(+), 14 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 292dd40ca..c78cd9140 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -588,10 +588,20 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu // One of those bundle might be available for a response for i, bundle := range bundles { - if best != nil && best.ID == i && downlink.Payload != nil && err == nil { + upType := lorawan.MType(bundle.Packet.(*core.DataUpHandlerReq).MType) + if best != nil && best.ID == i && (downlink.Payload != nil && err == nil || upType == lorawan.ConfirmedDataUp) { stats.MarkMeter("handler.downlink.pull") - - downlink, err := h.buildDownlink(downlink.Payload, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) + var downType lorawan.MType + switch upType { + case lorawan.UnconfirmedDataUp: + downType = lorawan.UnconfirmedDataDown + case lorawan.ConfirmedDataUp: + downType = lorawan.ConfirmedDataDown + default: + h.abortConsume(errors.New(errors.Implementation, "Unreckognized uplink MType"), bundles) + return + } + downlink, err := h.buildDownlink(downlink.Payload, downType, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) if err != nil { h.abortConsume(errors.New(errors.Structural, err), bundles) return @@ -621,7 +631,7 @@ func (h component) abortConsume(err error, bundles []bundle) { // constructs a downlink packet from something we pulled from the gathered downlink, and, the actual // uplink. -func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { +func (h component) buildDownlink(down []byte, mtype lorawan.MType, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { macpayload := lorawan.NewMACPayload(false) macpayload.FHDR = lorawan.FHDR{ FCnt: entry.FCntDown + 1, @@ -629,20 +639,25 @@ func (h component) buildDownlink(down []byte, up core.DataUpHandlerReq, entry de copy(macpayload.FHDR.DevAddr[:], entry.DevAddr) macpayload.FPort = new(uint8) *macpayload.FPort = 1 - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} - - if err := macpayload.EncryptFRMPayload(entry.AppSKey); err != nil { - return nil, errors.New(errors.Structural, err) - } - - frmpayload, err := macpayload.FRMPayload[0].MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) + if mtype == lorawan.ConfirmedDataDown { + macpayload.FHDR.FCtrl.ACK = true + } + var frmpayload []byte + if down != nil { + macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} + err := macpayload.EncryptFRMPayload(entry.AppSKey) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + frmpayload, err = macpayload.FRMPayload[0].MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } } payload := lorawan.NewPHYPayload(false) payload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, // TODO Handle Confirmed data down + MType: mtype, Major: lorawan.LoRaWANR1, } payload.MACPayload = macpayload diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 14669be6c..d474b7381 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1234,6 +1234,215 @@ func TestHandleDataUp(t *testing.T) { Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") } + + // -------------------- + + { + Desc(t, "Handle confirmed uplink, 1 packet | one downlink ready") + + // Build + tmst := time.Now() + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) + devStorage := NewMockDevStorage() + devStorage.OutRead.Entry = devEntry{ + DevAddr: devAddr[:], + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") + appAdapter := mocks.NewAppClient() + broker := mocks.NewAuthBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutRead.Entry.AppSKey, + true, + devAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.ConfirmedDataUp), + } + + // Expect + var wantErr *string + encodedDown, err := lorawan.EncryptFRMPayload( + devStorage.OutRead.Entry.AppSKey, + false, + devAddr, + devStorage.OutRead.Entry.FCntDown+1, + pktStorage.OutDequeue.Entry.Payload, + ) + FatalUnless(t, err) + var wantRes = &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.ConfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: devStorage.OutRead.Entry.DevAddr[:], + FCnt: devStorage.OutRead.Entry.FCntDown + 1, + FCtrl: &core.LoRaWANFCtrl{ + Ack: true, + }, + }, + FPort: uint32(1), + FRMPayload: encodedDown, + }, + MIC: []byte{0, 0, 0, 0}, + }, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000), + PayloadSize: 21, + }, + } + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") + } + + // -------------------- + + { + Desc(t, "Handle confirmed uplink, 1 packet | no downlink") + + // Build + tmst := time.Now() + devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) + devStorage := NewMockDevStorage() + devStorage.OutRead.Entry = devEntry{ + DevAddr: devAddr[:], + AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntDown: 3, + } + pktStorage := NewMockPktStorage() + pktStorage.Failures["dequeue"] = errors.New(errors.NotFound, "Mock Error") + appAdapter := mocks.NewAppClient() + broker := mocks.NewAuthBrokerClient() + payload, fcnt := []byte("Payload"), uint32(14) + encoded, err := lorawan.EncryptFRMPayload( + devStorage.OutRead.Entry.AppSKey, + true, + devAddr, + fcnt, + payload, + ) + FatalUnless(t, err) + req := &core.DataUpHandlerReq{ + Payload: encoded, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + FCnt: fcnt, + MType: uint32(lorawan.ConfirmedDataUp), + } + + // Expect + var wantErr *string + var wantRes = &core.DataUpHandlerRes{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.ConfirmedDataDown), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: devStorage.OutRead.Entry.DevAddr[:], + FCnt: devStorage.OutRead.Entry.FCntDown + 1, + FCtrl: &core.LoRaWANFCtrl{ + Ack: true, + }, + }, + FPort: uint32(1), + FRMPayload: nil, + }, + MIC: []byte{0, 0, 0, 0}, + }, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000), + PayloadSize: 13, + }, + } + var wantData = &core.DataAppReq{ + Payload: payload, + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) + res, err := handler.HandleDataUp(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes, res, "Data Up Handler Responses") + Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") + } + } func TestHandleJoin(t *testing.T) { From caf77ca83b945138e82b02bbbb84a6cadf10bbbe Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Mar 2016 18:09:08 +0100 Subject: [PATCH 1187/2266] [cli] Make db flags and locations more consistent --- cmd/broker.go | 16 ++++++++-------- cmd/handler.go | 16 ++++++++-------- cmd/router.go | 24 ++++++++++++------------ 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index e5dc7502f..8959af6e6 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -37,8 +37,8 @@ devices (with their network session keys) with the Broker. stats.Enabled = false } ctx.WithFields(log.Fields{ - "devices database": viper.GetString("broker.devices_database"), - "applications database": viper.GetString("broker.applications_database"), + "devices database": viper.GetString("broker.db-devices"), + "applications database": viper.GetString("broker.db-apps"), "status-server": statusServer, "main-server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), }).Info("Using Configuration") @@ -57,7 +57,7 @@ devices (with their network session keys) with the Broker. // Storage var dbDev broker.NetworkController - devDBString := viper.GetString("broker.devices_database") + devDBString := viper.GetString("broker.db-devices") switch { case strings.HasPrefix(devDBString, "boltdb:"): @@ -77,7 +77,7 @@ devices (with their network session keys) with the Broker. } var dbApp broker.AppStorage - appDBString := viper.GetString("broker.applications_database") + appDBString := viper.GetString("broker.db-apps") switch { case strings.HasPrefix(appDBString, "boltdb:"): @@ -119,11 +119,11 @@ devices (with their network session keys) with the Broker. func init() { RootCmd.AddCommand(brokerCmd) - brokerCmd.Flags().String("applications_database", "boltdb:/tmp/ttn_apps_broker.db", "Applications Database connection") - viper.BindPFlag("broker.applications_database", brokerCmd.Flags().Lookup("applications_database")) + brokerCmd.Flags().String("db-apps", "boltdb:/tmp/ttn_broker_apps.db", "Applications Database connection") + viper.BindPFlag("broker.db-apps", brokerCmd.Flags().Lookup("db-apps")) - brokerCmd.Flags().String("devices_database", "boltdb:/tmp/ttn_devs_broker.db", "Devices Database connection") - viper.BindPFlag("broker.devices_database", brokerCmd.Flags().Lookup("devices_database")) + brokerCmd.Flags().String("db-devices", "boltdb:/tmp/ttn_broker_devices.db", "Devices Database connection") + viper.BindPFlag("broker.db-devices", brokerCmd.Flags().Lookup("db-devices")) brokerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") diff --git a/cmd/handler.go b/cmd/handler.go index 8f8dcb550..ac9c0aa32 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -37,8 +37,8 @@ The Handler is the bridge between The Things Network and applications. stats.Enabled = false } ctx.WithFields(log.Fields{ - "devicesDatabase": viper.GetString("handler.dev-database"), - "packetsDatabase": viper.GetString("handler.pkt-database"), + "devicesDatabase": viper.GetString("handler.db-devices"), + "packetsDatabase": viper.GetString("handler.db-packets"), "status-server": statusServer, "internal server": fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), "public server": fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), @@ -61,7 +61,7 @@ The Handler is the bridge between The Things Network and applications. // In-memory devices storage var devicesDB handler.DevStorage - devDBString := viper.GetString("handler.dev-database") + devDBString := viper.GetString("handler.db-devices") switch { case strings.HasPrefix(devDBString, "boltdb:"): @@ -83,7 +83,7 @@ The Handler is the bridge between The Things Network and applications. // In-memory packets storage var packetsDB handler.PktStorage - pktDBString := viper.GetString("handler.pkt-database") + pktDBString := viper.GetString("handler.db-packets") switch { case strings.HasPrefix(pktDBString, "boltdb:"): @@ -148,10 +148,10 @@ The Handler is the bridge between The Things Network and applications. func init() { RootCmd.AddCommand(handlerCmd) - handlerCmd.Flags().String("dev-database", "boltdb:/tmp/ttn_handler_devices.db", "Devices Database connection") - handlerCmd.Flags().String("pkt-database", "boltdb:/tmp/ttn_handler_packets.db", "Packets Database connection") - viper.BindPFlag("handler.dev-database", handlerCmd.Flags().Lookup("dev-database")) - viper.BindPFlag("handler.pkt-database", handlerCmd.Flags().Lookup("pkt-database")) + handlerCmd.Flags().String("db-devices", "boltdb:/tmp/ttn_handler_devices.db", "Devices Database connection") + handlerCmd.Flags().String("db-packets", "boltdb:/tmp/ttn_handler_packets.db", "Packets Database connection") + viper.BindPFlag("handler.db-devices", handlerCmd.Flags().Lookup("db-devices")) + viper.BindPFlag("handler.db-packets", handlerCmd.Flags().Lookup("db-packets")) handlerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") diff --git a/cmd/router.go b/cmd/router.go index 4050d03ef..bbd0e0472 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -42,9 +42,9 @@ the gateway's duty cycle is (almost) full.`, } ctx.WithFields(log.Fields{ - "db-brokers": viper.GetString("router.db_brokers"), - "db-gateways": viper.GetString("router.db_gateways"), - "db-duty": viper.GetString("router.db_duty"), + "db-brokers": viper.GetString("router.db-brokers"), + "db-gateways": viper.GetString("router.db-gateways"), + "db-duty": viper.GetString("router.db-duty"), "status-server": statusServer, "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-address"), viper.GetInt("router.uplink-port")), "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), @@ -65,7 +65,7 @@ the gateway's duty cycle is (almost) full.`, // In-memory packet storage var db router.BrkStorage - dbString := viper.GetString("router.db_brokers") + dbString := viper.GetString("router.db-brokers") switch { case strings.HasPrefix(dbString, "boltdb:"): @@ -86,7 +86,7 @@ the gateway's duty cycle is (almost) full.`, // Duty Manager var dm dutycycle.DutyManager - dmString := viper.GetString("router.db_duty") + dmString := viper.GetString("router.db-duty") switch { case strings.HasPrefix(dmString, "boltdb:"): @@ -107,7 +107,7 @@ the gateway's duty cycle is (almost) full.`, // Gateways var dg router.GtwStorage - dgString := viper.GetString("router.db_gateways") + dgString := viper.GetString("router.db-gateways") switch { case strings.HasPrefix(dmString, "boltdb:"): @@ -180,14 +180,14 @@ the gateway's duty cycle is (almost) full.`, func init() { RootCmd.AddCommand(routerCmd) - routerCmd.Flags().String("db_brokers", "boltdb:/tmp/ttn_router_brokers.db", "Database connection of known brokers") - viper.BindPFlag("router.db_brokers", routerCmd.Flags().Lookup("db_brokers")) + routerCmd.Flags().String("db-brokers", "boltdb:/tmp/ttn_router_brokers.db", "Database connection of known brokers") + viper.BindPFlag("router.db-brokers", routerCmd.Flags().Lookup("db-brokers")) - routerCmd.Flags().String("db_gateways", "boltdb:/tmp/ttn_router_gateways.db", "Database connection of managed gateways") - viper.BindPFlag("router.db_gateways", routerCmd.Flags().Lookup("db_gateways")) + routerCmd.Flags().String("db-gateways", "boltdb:/tmp/ttn_router_gateways.db", "Database connection of managed gateways") + viper.BindPFlag("router.db-gateways", routerCmd.Flags().Lookup("db-gateways")) - routerCmd.Flags().String("db_duty", "boltdb:/tmp/ttn_router_duty.db", "Database connection of managed dutycycles") - viper.BindPFlag("router.db_duty", routerCmd.Flags().Lookup("db_duty")) + routerCmd.Flags().String("db-duty", "boltdb:/tmp/ttn_router_duty.db", "Database connection of managed dutycycles") + viper.BindPFlag("router.db-duty", routerCmd.Flags().Lookup("db-duty")) routerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") From 8600ba96264297e4791e13d88e2ea08bfa110480 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 18:16:46 +0100 Subject: [PATCH 1188/2266] Add support for confirmed data in ttnctl uplink --- ttnctl/cmd/uplink.go | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index cc8bb4366..1c9344a1f 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -20,37 +20,47 @@ import ( // uplinkCmd represents the `uplink` command var uplinkCmd = &cobra.Command{ - Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [Payload] [FCnt]", + Use: "uplink [ShouldConfirm] [DevAddr] [NwkSKey] [AppSKey] [Payload] [FCnt]", Short: "Send uplink messages to the network", Long: `ttnctl uplink sends an uplink message to the network`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 5 { + if len(args) < 6 { ctx.Fatalf("Insufficient arguments") } // Parse parameters - devAddrRaw, err := util.Parse32(args[0]) + var mtype lorawan.MType + switch args[0] { + case "yes": + fallthrough + case "true": + mtype = lorawan.ConfirmedDataUp + default: + mtype = lorawan.UnconfirmedDataUp + } + + devAddrRaw, err := util.Parse32(args[1]) if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } var devAddr lorawan.DevAddr copy(devAddr[:], devAddrRaw) - nwkSKeyRaw, err := util.Parse128(args[1]) + nwkSKeyRaw, err := util.Parse128(args[2]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) } var nwkSKey lorawan.AES128Key copy(nwkSKey[:], nwkSKeyRaw[:]) - appSKeyRaw, err := util.Parse128(args[2]) + appSKeyRaw, err := util.Parse128(args[3]) if err != nil { ctx.Fatalf("Invalid appSKey: %s", err) } var appSKey lorawan.AES128Key copy(appSKey[:], appSKeyRaw[:]) - fcnt, err := strconv.ParseInt(args[4], 10, 64) + fcnt, err := strconv.ParseInt(args[5], 10, 64) if err != nil { ctx.Fatalf("Invalid FCnt: %s", err) } @@ -63,13 +73,13 @@ var uplinkCmd = &cobra.Command{ } macPayload.FPort = new(uint8) *macPayload.FPort = 1 - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[3])}} + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} if err := macPayload.EncryptFRMPayload(appSKey); err != nil { ctx.Fatalf("Unable to encrypt frame payload: %s", err) } phyPayload := lorawan.NewPHYPayload(true) phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, + MType: mtype, Major: lorawan.LoRaWANR1, } phyPayload.MACPayload = macPayload @@ -129,15 +139,18 @@ var uplinkCmd = &cobra.Command{ } macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok || len(macPayload.FRMPayload) != 1 { + if !ok || len(macPayload.FRMPayload) > 1 { ctx.Fatalf("Unable to retrieve LoRaWAN MACPayload") } - if err := macPayload.DecryptFRMPayload(appSKey); err != nil { - ctx.Fatalf("Unable to decrypt MACPayload: %s", err) - } - ctx.Infof("Frame counter: %d", macPayload.FHDR.FCnt) - ctx.Infof("Decrypted Payload: %s", string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes)) + if len(macPayload.FRMPayload) > 0 { + if err := macPayload.DecryptFRMPayload(appSKey); err != nil { + ctx.Fatalf("Unable to decrypt MACPayload: %s", err) + } + ctx.Infof("Decrypted Payload: %s", string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes)) + } else { + ctx.Infof("The frame payload was empty.") + } }() // Router Packet From 0a894cd6a5b850d98c6b7b2d531f216464905e1f Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 18:48:04 +0100 Subject: [PATCH 1189/2266] [fix/fcnt_encoding] Fix encoding issues -> Fallback to standard decoder with padding if raw doesn't succeed --- core/adapters/udp/conversions.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 0c7dd02ef..865804133 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -7,7 +7,6 @@ import ( "encoding" "encoding/base64" "reflect" - "strings" "time" "github.com/TheThingsNetwork/ttn/core" @@ -27,7 +26,9 @@ func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interfa // RXPK Data are base64 encoded raw, err := base64.RawStdEncoding.DecodeString(*rxpk.Data) if err != nil { - return nil, errors.New(errors.Structural, err) + if raw, err = base64.StdEncoding.DecodeString(*rxpk.Data); err != nil { + return nil, errors.New(errors.Structural, err) + } } payload := lorawan.NewPHYPayload(true) if err = payload.UnmarshalBinary(raw); err != nil { @@ -116,7 +117,7 @@ func newTXPK(payload encoding.BinaryMarshaler, metadata *core.Metadata, ctx log. if err != nil { return semtech.TXPK{}, errors.New(errors.Structural, err) } - data := strings.Trim(base64.StdEncoding.EncodeToString(raw), "=") + data := base64.RawStdEncoding.EncodeToString(raw) txpk := semtech.TXPK{Data: pointer.String(data)} // Step 3, copy every compatible metadata from the packet to the TXPK packet. From adf09f6ad997fef380a009208a0b20dd4a536086 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 19:13:16 +0100 Subject: [PATCH 1190/2266] [fix/fcnt_encoding] Store FCntUp in handler as well, just for stats --- core/components/handler/devStorage.go | 3 +++ core/components/handler/handler.go | 16 +++++++++++++--- core/components/handler/handler_test.go | 10 +++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index b28050a0d..b1317de66 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -29,6 +29,7 @@ type devEntry struct { DevAddr []byte DevEUI []byte FCntDown uint32 + FCntUp uint32 NwkSKey [16]byte } @@ -84,6 +85,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { } rw.Write(e.AppSKey[:]) rw.Write(e.NwkSKey[:]) + rw.Write(e.FCntUp) rw.Write(e.FCntDown) rw.Write(e.AppEUI) rw.Write(e.DevEUI) @@ -102,6 +104,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { }) rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) + rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.AppEUI = make([]byte, len(data)) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c78cd9140..2026b22b7 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -476,6 +476,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da DevAddr: devAddr[:], DevEUI: devEUI, FCntDown: 0, + FCntUp: 0, NwkSKey: nwkSKey, }) if err != nil { @@ -587,9 +588,9 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu } // One of those bundle might be available for a response + upType := lorawan.MType(bundles[0].Packet.(*core.DataUpHandlerReq).MType) for i, bundle := range bundles { - upType := lorawan.MType(bundle.Packet.(*core.DataUpHandlerReq).MType) - if best != nil && best.ID == i && (downlink.Payload != nil && err == nil || upType == lorawan.ConfirmedDataUp) { + if best != nil && best.ID == i && (downlink.Payload != nil || upType == lorawan.ConfirmedDataUp) { stats.MarkMeter("handler.downlink.pull") var downType lorawan.MType switch upType { @@ -598,7 +599,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu case lorawan.ConfirmedDataUp: downType = lorawan.ConfirmedDataDown default: - h.abortConsume(errors.New(errors.Implementation, "Unreckognized uplink MType"), bundles) + h.abortConsume(errors.New(errors.Implementation, "Unrecognized uplink MType"), bundles) return } downlink, err := h.buildDownlink(downlink.Payload, downType, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) @@ -608,6 +609,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu } bundle.Entry.FCntDown = downlink.Payload.MACPayload.FHDR.FCnt + bundle.Entry.FCntUp = bundle.Packet.(*core.DataUpHandlerReq).FCnt err = h.DevStorage.upsert(bundle.Entry) if err != nil { h.abortConsume(err, bundles) @@ -618,6 +620,14 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu bundle.Chresp <- nil } } + + // Then, if there was no downlink, we still update the Frame Counter Up in the storage + if best == nil || downlink.Payload == nil && upType != lorawan.ConfirmedDataUp { + bundles[0].Entry.FCntUp = bundles[0].Packet.(*core.DataUpHandlerReq).FCnt + if err := h.DevStorage.upsert(bundles[0].Entry); err != nil { + h.Ctx.WithError(err).Debug("Unable to update Frame Counter Up") + } + } } // Abort consume forward the given error to all bundle recipients diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index d474b7381..33efef748 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -463,7 +463,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, } - var wantFCnt uint32 + var wantFCnt = devStorage.OutRead.Entry.FCntDown // Operate handler := New(Components{ @@ -548,7 +548,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, } - var wantFCnt uint32 + var wantFCnt = devStorage.OutRead.Entry.FCntDown // Operate handler := New(Components{ @@ -762,7 +762,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, } - var wantFCnt uint32 + var wantFCnt = devStorage.OutRead.Entry.FCntDown // Operate handler := New(Components{ @@ -1012,8 +1012,8 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req2.AppEUI, DevEUI: req2.DevEUI, } - var wantFCnt1 uint32 - var wantFCnt2 uint32 + var wantFCnt1 uint32 = 3 + var wantFCnt2 uint32 = 11 // Operate handler := New(Components{ From 1c1a66648674e06075c586d56b829439285a6fe2 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 19:14:58 +0100 Subject: [PATCH 1191/2266] [fix/fcnt_encoding] Add Frame counter to listdevices response --- core/components/handler/handlerManager.go | 20 ++++++++++++-------- core/protos/handler_manager.proto | 4 ++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 34a05fbee..d12d4367c 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -39,17 +39,21 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle *d = dev if dev.AppKey == nil { abp = append(abp, &core.HandlerABPDevice{ - DevAddr: d.DevAddr, - NwkSKey: d.NwkSKey[:], - AppSKey: d.AppSKey[:], + DevAddr: d.DevAddr, + NwkSKey: d.NwkSKey[:], + AppSKey: d.AppSKey[:], + FCntUp: d.FCntUp, + FCntDown: d.FCntDown, }) } else { otaa = append(otaa, &core.HandlerOTAADevice{ - DevEUI: d.DevEUI, - DevAddr: d.DevAddr, - NwkSKey: d.NwkSKey[:], - AppSKey: d.AppSKey[:], - AppKey: d.AppKey[:], + DevEUI: d.DevEUI, + DevAddr: d.DevAddr, + NwkSKey: d.NwkSKey[:], + AppSKey: d.AppSKey[:], + AppKey: d.AppKey[:], + FCntUp: d.FCntUp, + FCntDown: d.FCntDown, }) } } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index 8c3a27535..b4a4a869b 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -35,6 +35,8 @@ message HandlerABPDevice { bytes DevAddr = 2; bytes NwkSKey = 3; bytes AppSKey = 4; + uint32 FCntUp = 5; + uint32 FCntDown = 6; } message HandlerOTAADevice { @@ -43,6 +45,8 @@ message HandlerOTAADevice { bytes NwkSKey = 3; bytes AppSKey = 4; bytes AppKey = 5; + uint32 FCntUp = 7; + uint32 FCntDown = 8; } service HandlerManager { From ae59b1f02ace2b5b000e8f9b2b25c5bc9ac8fcc6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 19:15:29 +0100 Subject: [PATCH 1192/2266] [fix/fcnt_encoding] Regenerate new protos --- core/handler_manager.pb.go | 183 ++++++++++++++++++++++++++++++------- 1 file changed, 149 insertions(+), 34 deletions(-) diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index 447d0b123..3f523a775 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -108,9 +108,11 @@ func (m *ListDevicesHandlerRes) GetABP() []*HandlerABPDevice { } type HandlerABPDevice struct { - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + FCntUp uint32 `protobuf:"varint,5,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` + FCntDown uint32 `protobuf:"varint,6,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` } func (m *HandlerABPDevice) Reset() { *m = HandlerABPDevice{} } @@ -119,11 +121,13 @@ func (*HandlerABPDevice) ProtoMessage() {} func (*HandlerABPDevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{6} } type HandlerOTAADevice struct { - DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` - AppKey []byte `protobuf:"bytes,5,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` + DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + AppKey []byte `protobuf:"bytes,5,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` + FCntUp uint32 `protobuf:"varint,7,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` + FCntDown uint32 `protobuf:"varint,8,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` } func (m *HandlerOTAADevice) Reset() { *m = HandlerOTAADevice{} } @@ -510,6 +514,16 @@ func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.AppSKey) } } + if m.FCntUp != 0 { + data[i] = 0x28 + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + data[i] = 0x30 + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) + } return i, nil } @@ -568,6 +582,16 @@ func (m *HandlerOTAADevice) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.AppKey) } } + if m.FCntUp != 0 { + data[i] = 0x38 + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + data[i] = 0x40 + i++ + i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) + } return i, nil } @@ -727,6 +751,12 @@ func (m *HandlerABPDevice) Size() (n int) { n += 1 + l + sovHandlerManager(uint64(l)) } } + if m.FCntUp != 0 { + n += 1 + sovHandlerManager(uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + n += 1 + sovHandlerManager(uint64(m.FCntDown)) + } return n } @@ -763,6 +793,12 @@ func (m *HandlerOTAADevice) Size() (n int) { n += 1 + l + sovHandlerManager(uint64(l)) } } + if m.FCntUp != 0 { + n += 1 + sovHandlerManager(uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + n += 1 + sovHandlerManager(uint64(m.FCntDown)) + } return n } @@ -1598,6 +1634,44 @@ func (m *HandlerABPDevice) Unmarshal(data []byte) error { m.AppSKey = []byte{} } iNdEx = postIndex + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) + } + m.FCntUp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCntUp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) + } + m.FCntDown = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCntDown |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1803,6 +1877,44 @@ func (m *HandlerOTAADevice) Unmarshal(data []byte) error { m.AppKey = []byte{} } iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) + } + m.FCntUp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCntUp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 8: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) + } + m.FCntDown = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCntDown |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1930,30 +2042,33 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 391 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x53, 0xcd, 0x4e, 0xf2, 0x40, - 0x14, 0xfd, 0x4a, 0x5b, 0x08, 0x97, 0x2f, 0x5f, 0xf8, 0x46, 0xc0, 0x5a, 0x13, 0x62, 0x66, 0x45, - 0x62, 0xc2, 0x02, 0x9f, 0xa0, 0x04, 0xa2, 0xc6, 0x3f, 0x52, 0x61, 0xad, 0x95, 0x4e, 0x94, 0xa0, - 0x6d, 0xed, 0x34, 0x1a, 0x1f, 0xc3, 0x9d, 0x8f, 0xe4, 0xd2, 0x47, 0x30, 0xf8, 0x22, 0xde, 0xe9, - 0x34, 0xd0, 0x6a, 0xeb, 0x82, 0xb8, 0x98, 0xa4, 0xe7, 0x9e, 0xd3, 0x3b, 0x27, 0x77, 0xce, 0x85, - 0xe6, 0x8d, 0xe3, 0xb9, 0xb7, 0x2c, 0xbc, 0xb8, 0x73, 0x3c, 0xe7, 0x9a, 0x85, 0xdd, 0x20, 0xf4, - 0x23, 0x9f, 0x68, 0x53, 0x3f, 0x64, 0x34, 0x82, 0xc6, 0x24, 0xe0, 0x2c, 0x8c, 0xce, 0xc6, 0x96, - 0x75, 0x20, 0x85, 0x36, 0xbb, 0x27, 0x0d, 0xd0, 0xc7, 0xfe, 0x9c, 0x79, 0x86, 0xb2, 0xa3, 0x74, - 0xaa, 0xb6, 0x1e, 0x09, 0x40, 0x5a, 0x50, 0xb6, 0x82, 0x60, 0x38, 0x39, 0x34, 0x4a, 0x58, 0xfe, - 0x6b, 0x97, 0x9d, 0x18, 0x89, 0xfa, 0x80, 0x3d, 0x88, 0xba, 0x2a, 0xeb, 0x6e, 0x8c, 0x12, 0xfd, - 0x11, 0x7b, 0x32, 0xb4, 0xa5, 0x1e, 0x11, 0x6d, 0xe5, 0xde, 0xca, 0xe9, 0xb3, 0x02, 0x1b, 0x92, - 0xb0, 0xfa, 0xa3, 0xb5, 0xdd, 0x18, 0x50, 0x41, 0x37, 0x96, 0xeb, 0x86, 0x89, 0x9d, 0x8a, 0x2b, - 0xa1, 0x60, 0x4e, 0x1f, 0xe7, 0xe7, 0x2b, 0x43, 0x15, 0x4f, 0x42, 0xc1, 0x60, 0xaf, 0x98, 0xd1, - 0x25, 0xe3, 0x48, 0x48, 0x9b, 0x79, 0x96, 0x38, 0x1d, 0x42, 0xf3, 0x78, 0xc6, 0x23, 0xbc, 0x68, - 0x36, 0x65, 0x7c, 0x5d, 0xaf, 0xd4, 0xcb, 0x6f, 0xc3, 0xc9, 0x2e, 0x68, 0x62, 0x38, 0xd8, 0x45, - 0xed, 0xd4, 0x7a, 0x9b, 0x5d, 0xf1, 0x5a, 0xdd, 0x84, 0x17, 0x84, 0xfc, 0xc3, 0xd6, 0x7c, 0xfc, - 0x26, 0x1d, 0x50, 0xd1, 0x1d, 0xb6, 0x16, 0xda, 0x56, 0x46, 0x8b, 0xf5, 0x44, 0xaa, 0x3a, 0xfd, - 0x11, 0xbd, 0x84, 0xfa, 0x57, 0x22, 0x3d, 0xaf, 0x52, 0xe1, 0xbc, 0xd4, 0xc2, 0x79, 0x69, 0xd9, - 0x79, 0xe1, 0x1b, 0xfe, 0xff, 0xe6, 0x33, 0x95, 0x10, 0x25, 0x93, 0x90, 0x5f, 0xbd, 0x3b, 0x95, - 0x37, 0x3d, 0x9d, 0xb7, 0xde, 0x42, 0x81, 0x7f, 0x89, 0xa7, 0x13, 0xb9, 0x04, 0x64, 0x00, 0xb0, - 0x8a, 0x20, 0x31, 0xe5, 0xcc, 0xf2, 0x56, 0xc1, 0x2c, 0xe6, 0x38, 0xb1, 0xa0, 0xba, 0x0c, 0x07, - 0xd9, 0x4a, 0x0b, 0x33, 0x01, 0x36, 0x0b, 0x29, 0x4e, 0xf6, 0xa1, 0x96, 0x4a, 0x00, 0xd9, 0x96, - 0xca, 0xdc, 0x6c, 0x99, 0x3f, 0x90, 0xbc, 0x5f, 0x7f, 0x5d, 0xb4, 0x95, 0x37, 0x3c, 0xef, 0x78, - 0x5e, 0x3e, 0xda, 0x7f, 0xae, 0xca, 0xf1, 0xa6, 0xef, 0x7d, 0x06, 0x00, 0x00, 0xff, 0xff, 0xc2, - 0x64, 0x7b, 0xb7, 0x02, 0x04, 0x00, 0x00, + // 436 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xcb, 0x6e, 0xda, 0x40, + 0x14, 0xed, 0xe0, 0x17, 0x5c, 0xda, 0x8a, 0x4e, 0x81, 0xba, 0xae, 0x84, 0xaa, 0x59, 0x21, 0x55, + 0x62, 0x41, 0xbf, 0xc0, 0x14, 0xfa, 0x50, 0x5f, 0xc8, 0x81, 0x75, 0xe4, 0xe0, 0x21, 0x41, 0x24, + 0xb6, 0xe3, 0xb1, 0x82, 0xf2, 0x19, 0x91, 0xb2, 0xc8, 0x0f, 0x45, 0xca, 0x32, 0x9f, 0x10, 0x91, + 0x1f, 0xc9, 0x8c, 0xc7, 0x02, 0x3b, 0xb1, 0xb3, 0x40, 0x59, 0x58, 0xe2, 0xdc, 0x73, 0xb8, 0x3e, + 0xf7, 0xce, 0x19, 0x43, 0xeb, 0xc8, 0xf5, 0xbd, 0x63, 0x1a, 0xed, 0x9f, 0xb8, 0xbe, 0x7b, 0x48, + 0xa3, 0x5e, 0x18, 0x05, 0x71, 0x80, 0xd5, 0x59, 0x10, 0x51, 0x12, 0x43, 0x73, 0x1a, 0x32, 0x1a, + 0xc5, 0xff, 0x27, 0xb6, 0xfd, 0x53, 0x0a, 0x1d, 0x7a, 0x8a, 0x9b, 0xa0, 0x4d, 0x82, 0x25, 0xf5, + 0x4d, 0xf4, 0x19, 0x75, 0x6b, 0x8e, 0x16, 0x0b, 0x80, 0xdb, 0xa0, 0xdb, 0x61, 0x38, 0x9a, 0xfe, + 0x32, 0x2b, 0xbc, 0xfc, 0xda, 0xd1, 0xdd, 0x04, 0x89, 0xfa, 0x90, 0x9e, 0x89, 0xba, 0x22, 0xeb, + 0x5e, 0x82, 0x52, 0xfd, 0x6f, 0x7a, 0x6e, 0xaa, 0x1b, 0x3d, 0x47, 0xa4, 0x5d, 0xf8, 0x56, 0x46, + 0x2e, 0x10, 0xbc, 0x97, 0x84, 0x3d, 0x18, 0xef, 0xec, 0xc6, 0x04, 0x83, 0xbb, 0xb1, 0x3d, 0x2f, + 0x4a, 0xed, 0x18, 0x9e, 0x84, 0x82, 0xf9, 0xb7, 0x5a, 0xee, 0x6d, 0x0d, 0x19, 0xbe, 0x84, 0x82, + 0xe1, 0xbd, 0x12, 0x46, 0x93, 0x8c, 0x2b, 0x21, 0x69, 0x15, 0x59, 0x62, 0x64, 0x04, 0xad, 0x3f, + 0x0b, 0x16, 0xf3, 0x17, 0x2d, 0x66, 0x94, 0xed, 0xea, 0x95, 0xf8, 0xc5, 0x6d, 0x18, 0xfe, 0x02, + 0xaa, 0x58, 0x0e, 0xef, 0xa2, 0x74, 0xeb, 0xfd, 0x0f, 0x3d, 0x71, 0x5a, 0xbd, 0x94, 0x17, 0x84, + 0xfc, 0x87, 0xa3, 0x06, 0xfc, 0x37, 0xee, 0x82, 0xc2, 0xdd, 0xf1, 0xd6, 0x42, 0xdb, 0xce, 0x69, + 0x79, 0x3d, 0x95, 0x2a, 0xee, 0x60, 0x4c, 0x2e, 0x11, 0x34, 0x1e, 0x33, 0xd9, 0x85, 0x55, 0x4a, + 0x17, 0xa6, 0x94, 0x2e, 0x4c, 0xcd, 0x2d, 0x4c, 0x8c, 0xfa, 0xfd, 0x9b, 0x1f, 0x4f, 0xc3, 0x64, + 0x93, 0x6f, 0x1c, 0x7d, 0x9e, 0x20, 0x6c, 0x41, 0x55, 0xd4, 0x87, 0xc1, 0xca, 0x37, 0xf5, 0x84, + 0xa9, 0xce, 0x53, 0x4c, 0xae, 0x11, 0xbc, 0x7b, 0x32, 0x5c, 0x26, 0x56, 0x28, 0x17, 0xab, 0x17, + 0xf7, 0x9b, 0x86, 0x54, 0xcb, 0x86, 0x34, 0x33, 0x87, 0x51, 0x3a, 0x47, 0x35, 0x3f, 0x47, 0x7f, + 0x8d, 0xe0, 0x6d, 0x3a, 0xc7, 0x5f, 0x79, 0xdb, 0xf0, 0x10, 0x60, 0x9b, 0x75, 0x6c, 0xc9, 0xc3, + 0x29, 0xba, 0x73, 0x56, 0x39, 0xc7, 0xb0, 0x0d, 0xb5, 0x4d, 0x0a, 0xf1, 0xc7, 0xac, 0x30, 0x77, + 0x53, 0xac, 0x52, 0x8a, 0xe1, 0x1f, 0x50, 0xcf, 0x44, 0x0d, 0x7f, 0x92, 0xca, 0xc2, 0x10, 0x5b, + 0xcf, 0x90, 0x6c, 0xd0, 0xb8, 0x59, 0x77, 0xd0, 0x2d, 0x7f, 0xee, 0xf8, 0x73, 0x75, 0xdf, 0x79, + 0x75, 0xa0, 0x27, 0x9f, 0x94, 0xaf, 0x0f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x92, 0x19, 0x70, + 0x6b, 0x04, 0x00, 0x00, } From 0b0b64769ebe644cd4b3a6f5bbde7af01672da15 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 19:17:02 +0100 Subject: [PATCH 1193/2266] [fix/fcnt_encoding] Add frame counters to list devices in ttnctl --- ttnctl/cmd/device.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 3ac1021bc..f294ccea1 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -49,26 +49,26 @@ var devicesCmd = &cobra.Command{ ctx.Infof("Found %d personalized devices (ABP)", len(res.ABP)) table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevAddr", "NwkSKey", "AppSKey") + table.AddRow("DevAddr", "NwkSKey", "AppSKey", "FCntUp", "FCntDown") for _, device := range res.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) nwkSKey := fmt.Sprintf("%X", device.NwkSKey) appSKey := fmt.Sprintf("%X", device.AppSKey) - table.AddRow(devAddr, nwkSKey, appSKey) + table.AddRow(devAddr, nwkSKey, appSKey, device.FCntUp, device.FCntDown) } fmt.Println(table) ctx.Infof("Found %d dynamic devices (OTAA)", len(res.OTAA)) table = uitable.New() table.MaxColWidth = 40 - table.AddRow("DevEUI", "DevAddr", "NwkSKey", "AppSKey", "AppKey") + table.AddRow("DevEUI", "DevAddr", "NwkSKey", "AppSKey", "AppKey", "FCntUp", "FCntDown") for _, device := range res.OTAA { devEUI := fmt.Sprintf("%X", device.DevEUI) devAddr := fmt.Sprintf("%X", device.DevAddr) nwkSKey := fmt.Sprintf("%X", device.NwkSKey) appSKey := fmt.Sprintf("%X", device.AppSKey) appKey := fmt.Sprintf("%X", device.AppKey) - table.AddRow(devEUI, devAddr, nwkSKey, appSKey, appKey) + table.AddRow(devEUI, devAddr, nwkSKey, appSKey, appKey, device.FCntUp, device.FCntDown) } fmt.Println(table) }, From 4cfa0f20604bc5d6af0fffd1f5f54bb86d8810f6 Mon Sep 17 00:00:00 2001 From: ktorz Date: Wed, 23 Mar 2016 19:25:40 +0100 Subject: [PATCH 1194/2266] Fix oversight in handlerManager's tests --- core/components/handler/handlerClient_test.go | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go index e5e727492..7f2f34f44 100644 --- a/core/components/handler/handlerClient_test.go +++ b/core/components/handler/handlerClient_test.go @@ -66,18 +66,22 @@ func TestListDevices(t *testing.T) { var wantRes = &core.ListDevicesHandlerRes{ OTAA: []*core.HandlerOTAADevice{ &core.HandlerOTAADevice{ - DevEUI: st.OutReadAll.Entries[0].DevEUI, - DevAddr: st.OutReadAll.Entries[0].DevAddr, - NwkSKey: st.OutReadAll.Entries[0].NwkSKey[:], - AppSKey: st.OutReadAll.Entries[0].AppSKey[:], - AppKey: st.OutReadAll.Entries[0].AppKey[:], + DevEUI: st.OutReadAll.Entries[0].DevEUI, + DevAddr: st.OutReadAll.Entries[0].DevAddr, + NwkSKey: st.OutReadAll.Entries[0].NwkSKey[:], + AppSKey: st.OutReadAll.Entries[0].AppSKey[:], + AppKey: st.OutReadAll.Entries[0].AppKey[:], + FCntDown: 14, + FCntUp: 0, }, }, ABP: []*core.HandlerABPDevice{ &core.HandlerABPDevice{ - DevAddr: st.OutReadAll.Entries[1].DevAddr, - NwkSKey: st.OutReadAll.Entries[1].NwkSKey[:], - AppSKey: st.OutReadAll.Entries[1].AppSKey[:], + DevAddr: st.OutReadAll.Entries[1].DevAddr, + NwkSKey: st.OutReadAll.Entries[1].NwkSKey[:], + AppSKey: st.OutReadAll.Entries[1].AppSKey[:], + FCntDown: 5, + FCntUp: 0, }, }, } From 49cab193f8705bcf9bfb7f6358287c1ce324647e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 09:42:43 +0100 Subject: [PATCH 1195/2266] [ttnctl] Fix env parsing --- ttnctl/cmd/root.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 0af22b1c8..1e437740d 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" "os" + "strings" "github.com/apex/log" "github.com/apex/log/handlers/cli" @@ -59,6 +60,7 @@ func initConfig() { viper.SetConfigName(".ttnctl") viper.AddConfigPath("$HOME") + viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() // If a config file is found, read it in. From 309925dd2b6b7fa48cfdb7871a7568ac417891d7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 11:28:55 +0100 Subject: [PATCH 1196/2266] [handler] Announce public address instead of listen address A handler listening on 0.0.0.0:1882 should not send that address to the broker.. That's not correct. --- cmd/handler.go | 7 +- core/components/handler/handler.go | 21 ++-- core/components/handler/handlerClient_test.go | 97 +++++++++++-------- core/components/handler/handlerManager.go | 4 +- 4 files changed, 76 insertions(+), 53 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index ac9c0aa32..ee0d1149a 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -132,8 +132,9 @@ The Handler is the bridge between The Things Network and applications. AppAdapter: appAdapter, }, handler.Options{ - PublicNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), - PrivateNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), + PublicNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), + PrivateNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), + PrivateNetAddrAnnounce: fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address-announce"), viper.GetInt("handler.internal-port")), }, ) @@ -159,8 +160,10 @@ func init() { viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) handlerCmd.Flags().String("internal-address", "0.0.0.0", "The IP address to listen for communication from other components") + handlerCmd.Flags().String("internal-address-announce", "localhost", "The hostname to announce for communication from other components") handlerCmd.Flags().Int("internal-port", 1882, "The port for communication from other components") viper.BindPFlag("handler.internal-address", handlerCmd.Flags().Lookup("internal-address")) + viper.BindPFlag("handler.internal-address-announce", handlerCmd.Flags().Lookup("internal-address-announce")) viper.BindPFlag("handler.internal-port", handlerCmd.Flags().Lookup("internal-port")) handlerCmd.Flags().String("public-address", "0.0.0.0", "The IP address to listen for communication with the wild open") diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 2026b22b7..5153f085f 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -39,10 +39,11 @@ var dataRates = map[string]uint8{ // component implements the core.Component interface type component struct { Components - Set chan<- bundle - PublicNetAddr string - PrivateNetAddr string - Configuration struct { + Set chan<- bundle + PublicNetAddr string + PrivateNetAddr string + PrivateNetAddrAnnounce string + Configuration struct { CFList [5]uint32 NetID [3]byte RX1DROffset uint8 @@ -71,8 +72,9 @@ type Components struct { // Options is used to make handler instantiation easier type Options struct { - PublicNetAddr string // Net Address used to communicate with the handler from the outside - PrivateNetAddr string // Net Address the handler provides to brokers for internal communications + PublicNetAddr string // Net Address used to communicate with the handler from the outside + PrivateNetAddr string // Net Address the handler listens on for internal communications + PrivateNetAddrAnnounce string // Net Address the handler announces to brokers for internal communications } // bundle are used to materialize an incoming request being bufferized, waiting for the others. @@ -88,9 +90,10 @@ type bundle struct { // New construct a new Handler func New(c Components, o Options) Interface { h := &component{ - Components: c, - PublicNetAddr: o.PublicNetAddr, - PrivateNetAddr: o.PrivateNetAddr, + Components: c, + PublicNetAddr: o.PublicNetAddr, + PrivateNetAddr: o.PrivateNetAddr, + PrivateNetAddrAnnounce: o.PrivateNetAddrAnnounce, } // TODO Make it configurable diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go index 7f2f34f44..c9423b46f 100644 --- a/core/components/handler/handlerClient_test.go +++ b/core/components/handler/handlerClient_test.go @@ -49,8 +49,9 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -110,8 +111,9 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -150,8 +152,9 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -189,8 +192,9 @@ func TestListDevices(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.ListDevicesHandlerReq{ Token: "==OAuth==Token==", @@ -225,8 +229,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -243,7 +248,7 @@ func TestUpsertABP(t *testing.T) { AppEUI: req.AppEUI, DevAddr: []byte{14, 14, 14, 14}, NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).PrivateNetAddr, + NetAddress: h.(*component).PrivateNetAddrAnnounce, } var wantRes = new(core.UpsertABPHandlerRes) @@ -271,8 +276,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -289,7 +295,7 @@ func TestUpsertABP(t *testing.T) { AppEUI: req.AppEUI, DevAddr: []byte{14, 14, 14, 14}, NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).PrivateNetAddr, + NetAddress: h.(*component).PrivateNetAddrAnnounce, } var wantRes = new(core.UpsertABPHandlerRes) @@ -317,8 +323,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -335,7 +342,7 @@ func TestUpsertABP(t *testing.T) { AppEUI: req.AppEUI, DevAddr: []byte{14, 14, 14, 14}, NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).PrivateNetAddr, + NetAddress: h.(*component).PrivateNetAddrAnnounce, } var wantRes = new(core.UpsertABPHandlerRes) @@ -362,8 +369,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -401,8 +409,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -440,8 +449,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -479,8 +489,9 @@ func TestUpsertABP(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertABPHandlerReq{ Token: "==OAuth==Token==", @@ -518,8 +529,9 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -533,7 +545,7 @@ func TestUpsertOTAA(t *testing.T) { var wantBrkCall = &core.ValidateOTAABrokerReq{ Token: req.Token, AppEUI: req.AppEUI, - NetAddress: h.(*component).PrivateNetAddr, + NetAddress: h.(*component).PrivateNetAddrAnnounce, } var wantRes = new(core.UpsertOTAAHandlerRes) @@ -561,8 +573,9 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -576,7 +589,7 @@ func TestUpsertOTAA(t *testing.T) { var wantBrkCall = &core.ValidateOTAABrokerReq{ Token: req.Token, AppEUI: req.AppEUI, - NetAddress: h.(*component).PrivateNetAddr, + NetAddress: h.(*component).PrivateNetAddrAnnounce, } var wantRes = new(core.UpsertOTAAHandlerRes) @@ -604,8 +617,9 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -619,7 +633,7 @@ func TestUpsertOTAA(t *testing.T) { var wantBrkCall = &core.ValidateOTAABrokerReq{ Token: req.Token, AppEUI: req.AppEUI, - NetAddress: h.(*component).PrivateNetAddr, + NetAddress: h.(*component).PrivateNetAddrAnnounce, } var wantRes = new(core.UpsertOTAAHandlerRes) @@ -646,8 +660,9 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -684,8 +699,9 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", @@ -722,8 +738,9 @@ func TestUpsertOTAA(t *testing.T) { Broker: br, DevStorage: st, }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", }) req := &core.UpsertOTAAHandlerReq{ Token: "==OAuth==Token==", diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index d12d4367c..5f626f4a5 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -79,7 +79,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, - NetAddress: h.PrivateNetAddr, + NetAddress: h.PrivateNetAddrAnnounce, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") @@ -119,7 +119,7 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR // 2. Notify the broker -> The Broker also does the token verification _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ Token: req.Token, - NetAddress: h.PrivateNetAddr, + NetAddress: h.PrivateNetAddrAnnounce, AppEUI: req.AppEUI, }) if err != nil { From 7dcc723aa596ba28f4baf5756b6b62604ac00106 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 24 Mar 2016 12:34:13 +0100 Subject: [PATCH 1197/2266] Use microseconds timestamps + add Power and InvPolarity to down metadata --- core/adapters/udp/conversions.go | 2 + core/components/handler/handler.go | 17 +++- core/components/handler/handler_test.go | 60 ++++++----- core/core.pb.go | 127 +++++++++++++++--------- core/protos/core.proto | 3 +- 5 files changed, 133 insertions(+), 76 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 865804133..8ea6c18c8 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -120,6 +120,8 @@ func newTXPK(payload encoding.BinaryMarshaler, metadata *core.Metadata, ctx log. data := base64.RawStdEncoding.EncodeToString(raw) txpk := semtech.TXPK{Data: pointer.String(data)} + ctx.WithField("Metadata", *metadata).Debug("Injecting metadata") + // Step 3, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. injectMetadata(&txpk, *metadata) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 5153f085f..1e71838f3 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -50,6 +50,9 @@ type component struct { RX2DataRate string RX2Freq float32 RXDelay uint8 + PowerRX1 uint32 + PowerRX2 uint32 + InvPolarity bool JoinDelay uint8 } } @@ -104,6 +107,9 @@ func New(c Components, o Options) Interface { h.Configuration.RX2Freq = 869.525 h.Configuration.RXDelay = 1 h.Configuration.JoinDelay = 5 + h.Configuration.PowerRX1 = 14 + h.Configuration.PowerRX2 = 27 + h.Configuration.InvPolarity = true set := make(chan bundle) bundles := make(chan []bundle) @@ -680,7 +686,7 @@ func (h component) buildDownlink(down []byte, mtype lorawan.MType, up core.DataU return nil, errors.New(errors.Structural, err) } - metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000*uint32(h.Configuration.RXDelay), isRX2) + metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.RXDelay), isRX2) return &core.DataUpHandlerRes{ Payload: &core.LoRaWANData{ @@ -738,7 +744,7 @@ func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte return nil, errors.New(errors.Structural, err) } - m := h.buildMetadata(*joinReq.Metadata, uint32(len(data)), 1000*uint32(h.Configuration.JoinDelay), isRX2) + m := h.buildMetadata(*joinReq.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.JoinDelay), isRX2) return &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: data, @@ -753,6 +759,10 @@ func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay Frequency: metadata.Frequency, CodingRate: metadata.CodingRate, DataRate: metadata.DataRate, + Modulation: metadata.Modulation, + RFChain: metadata.RFChain, + InvPolarity: h.Configuration.InvPolarity, + Power: h.Configuration.PowerRX1, PayloadSize: size, Timestamp: metadata.Timestamp + baseDelay, } @@ -761,7 +771,8 @@ func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay // TODO Handle different regions with non hard-coded values m.Frequency = h.Configuration.RX2Freq m.DataRate = h.Configuration.RX2DataRate - m.Timestamp = metadata.Timestamp + baseDelay + 1000 + m.Power = h.Configuration.PowerRX2 + m.Timestamp = metadata.Timestamp + baseDelay + 1000000 } return m } diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 33efef748..852996bef 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -623,7 +623,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -667,8 +667,10 @@ func TestHandleDataUp(t *testing.T) { DataRate: "SF7BW125", Frequency: 865.5, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000), + Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000000), PayloadSize: 21, + Power: 14, + InvPolarity: true, }, } var wantData = &core.DataAppReq{ @@ -1087,7 +1089,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateBlocked), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1131,8 +1133,10 @@ func TestHandleDataUp(t *testing.T) { DataRate: "SF9BW125", Frequency: 869.525, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(2*time.Second).Unix() * 1000), + Timestamp: uint32(tmst.Add(2*time.Second).Unix() * 1000000), PayloadSize: 21, + Power: 27, + InvPolarity: true, }, } var wantData = &core.DataAppReq{ @@ -1194,7 +1198,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateBlocked), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1268,7 +1272,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1314,8 +1318,10 @@ func TestHandleDataUp(t *testing.T) { DataRate: "SF7BW125", Frequency: 865.5, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000), + Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000000), PayloadSize: 21, + Power: 14, + InvPolarity: true, }, } var wantData = &core.DataAppReq{ @@ -1376,7 +1382,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1414,8 +1420,10 @@ func TestHandleDataUp(t *testing.T) { DataRate: "SF7BW125", Frequency: 865.5, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000), + Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000000), PayloadSize: 13, + Power: 14, + InvPolarity: true, }, } var wantData = &core.DataAppReq{ @@ -1459,7 +1467,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1498,8 +1506,10 @@ func TestHandleJoin(t *testing.T) { DataRate: "SF7BW125", Frequency: 865.5, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000), + Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000), PayloadSize: 33, + Power: 14, + InvPolarity: true, }, } var wantAppReq = &core.JoinAppReq{ @@ -1547,7 +1557,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1587,8 +1597,10 @@ func TestHandleJoin(t *testing.T) { DataRate: "SF7BW125", Frequency: 865.5, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000), + Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000), PayloadSize: 33, + Power: 14, + InvPolarity: true, }, } var wantAppReq = &core.JoinAppReq{ @@ -1636,7 +1648,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1703,7 +1715,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateBlocked), DutyRX2: uint32(dutycycle.StateBlocked), @@ -1769,7 +1781,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "Not A DataRate", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1836,7 +1848,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1887,7 +1899,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1937,7 +1949,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -1987,7 +1999,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000), + Timestamp: uint32(tmst.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -2077,7 +2089,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF7BW125", Frequency: 865.5, - Timestamp: uint32(tmst1.Unix() * 1000), + Timestamp: uint32(tmst1.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateWarning), DutyRX2: uint32(dutycycle.StateWarning), @@ -2093,7 +2105,7 @@ func TestHandleJoin(t *testing.T) { Metadata: &core.Metadata{ DataRate: "SF8BW125", Frequency: 867.234, - Timestamp: uint32(tmst2.Unix() * 1000), + Timestamp: uint32(tmst2.Unix() * 1000000), CodingRate: "4/5", DutyRX1: uint32(dutycycle.StateAvailable), DutyRX2: uint32(dutycycle.StateAvailable), @@ -2135,8 +2147,10 @@ func TestHandleJoin(t *testing.T) { DataRate: "SF8BW125", Frequency: 867.234, CodingRate: "4/5", - Timestamp: uint32(tmst2.Add(5*time.Second).Unix() * 1000), + Timestamp: uint32(tmst2.Add(5*time.Second).Unix() * 1000000), PayloadSize: 33, + Power: 14, + InvPolarity: true, }, } var wantAppReq = &core.JoinAppReq{ diff --git a/core/core.pb.go b/core/core.pb.go index e8c3aebe0..df476b2cc 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -16,23 +16,24 @@ var _ = fmt.Errorf var _ = math.Inf type Metadata struct { - DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` - DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` - Frequency float32 `protobuf:"fixed32,3,opt,name=Frequency,json=frequency,proto3" json:"Frequency,omitempty"` - DataRate string `protobuf:"bytes,4,opt,name=DataRate,json=dataRate,proto3" json:"DataRate,omitempty"` - CodingRate string `protobuf:"bytes,5,opt,name=CodingRate,json=codingRate,proto3" json:"CodingRate,omitempty"` - Timestamp uint32 `protobuf:"varint,6,opt,name=Timestamp,json=timestamp,proto3" json:"Timestamp,omitempty"` - Rssi int32 `protobuf:"varint,7,opt,name=Rssi,json=rssi,proto3" json:"Rssi,omitempty"` - Lsnr float32 `protobuf:"fixed32,8,opt,name=Lsnr,json=lsnr,proto3" json:"Lsnr,omitempty"` - PayloadSize uint32 `protobuf:"varint,9,opt,name=PayloadSize,json=payloadSize,proto3" json:"PayloadSize,omitempty"` - Time string `protobuf:"bytes,10,opt,name=Time,json=time,proto3" json:"Time,omitempty"` - RadioFreqChan uint32 `protobuf:"varint,11,opt,name=RadioFreqChan,json=radioFreqChan,proto3" json:"RadioFreqChan,omitempty"` - CRCStatus int32 `protobuf:"varint,12,opt,name=CRCStatus,json=cRCStatus,proto3" json:"CRCStatus,omitempty"` - Modulation string `protobuf:"bytes,13,opt,name=Modulation,json=modulation,proto3" json:"Modulation,omitempty"` - InvPolarity bool `protobuf:"varint,14,opt,name=InvPolarity,json=invPolarity,proto3" json:"InvPolarity,omitempty"` - Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` - Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` - Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` + DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` + DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` + Frequency float32 `protobuf:"fixed32,3,opt,name=Frequency,json=frequency,proto3" json:"Frequency,omitempty"` + DataRate string `protobuf:"bytes,4,opt,name=DataRate,json=dataRate,proto3" json:"DataRate,omitempty"` + CodingRate string `protobuf:"bytes,5,opt,name=CodingRate,json=codingRate,proto3" json:"CodingRate,omitempty"` + Timestamp uint32 `protobuf:"varint,6,opt,name=Timestamp,json=timestamp,proto3" json:"Timestamp,omitempty"` + Rssi int32 `protobuf:"varint,7,opt,name=Rssi,json=rssi,proto3" json:"Rssi,omitempty"` + Lsnr float32 `protobuf:"fixed32,8,opt,name=Lsnr,json=lsnr,proto3" json:"Lsnr,omitempty"` + PayloadSize uint32 `protobuf:"varint,9,opt,name=PayloadSize,json=payloadSize,proto3" json:"PayloadSize,omitempty"` + Time string `protobuf:"bytes,10,opt,name=Time,json=time,proto3" json:"Time,omitempty"` + RFChain uint32 `protobuf:"varint,11,opt,name=RFChain,json=rFChain,proto3" json:"RFChain,omitempty"` + CRCStatus int32 `protobuf:"varint,12,opt,name=CRCStatus,json=cRCStatus,proto3" json:"CRCStatus,omitempty"` + Modulation string `protobuf:"bytes,13,opt,name=Modulation,json=modulation,proto3" json:"Modulation,omitempty"` + InvPolarity bool `protobuf:"varint,14,opt,name=InvPolarity,json=invPolarity,proto3" json:"InvPolarity,omitempty"` + Power uint32 `protobuf:"varint,15,opt,name=Power,json=power,proto3" json:"Power,omitempty"` + Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` + Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` + Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -123,10 +124,10 @@ func (m *Metadata) MarshalTo(data []byte) (int, error) { i = encodeVarintCore(data, i, uint64(len(m.Time))) i += copy(data[i:], m.Time) } - if m.RadioFreqChan != 0 { + if m.RFChain != 0 { data[i] = 0x58 i++ - i = encodeVarintCore(data, i, uint64(m.RadioFreqChan)) + i = encodeVarintCore(data, i, uint64(m.RFChain)) } if m.CRCStatus != 0 { data[i] = 0x60 @@ -149,6 +150,11 @@ func (m *Metadata) MarshalTo(data []byte) (int, error) { } i++ } + if m.Power != 0 { + data[i] = 0x78 + i++ + i = encodeVarintCore(data, i, uint64(m.Power)) + } if m.Altitude != 0 { data[i] = 0xa8 i++ @@ -269,8 +275,8 @@ func (m *Metadata) Size() (n int) { if l > 0 { n += 1 + l + sovCore(uint64(l)) } - if m.RadioFreqChan != 0 { - n += 1 + sovCore(uint64(m.RadioFreqChan)) + if m.RFChain != 0 { + n += 1 + sovCore(uint64(m.RFChain)) } if m.CRCStatus != 0 { n += 1 + sovCore(uint64(m.CRCStatus)) @@ -282,6 +288,9 @@ func (m *Metadata) Size() (n int) { if m.InvPolarity { n += 2 } + if m.Power != 0 { + n += 1 + sovCore(uint64(m.Power)) + } if m.Altitude != 0 { n += 2 + sovCore(uint64(m.Altitude)) } @@ -563,9 +572,9 @@ func (m *Metadata) Unmarshal(data []byte) error { iNdEx = postIndex case 11: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RadioFreqChan", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RFChain", wireType) } - m.RadioFreqChan = 0 + m.RFChain = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowCore @@ -575,7 +584,7 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.RadioFreqChan |= (uint32(b) & 0x7F) << shift + m.RFChain |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } @@ -648,6 +657,25 @@ func (m *Metadata) Unmarshal(data []byte) error { } } m.InvPolarity = bool(v != 0) + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) + } + m.Power = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Power |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 21: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) @@ -919,29 +947,30 @@ var ( ) var fileDescriptorCore = []byte{ - // 384 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0x41, 0x4e, 0xe3, 0x30, - 0x14, 0x86, 0x27, 0x6d, 0xda, 0x26, 0xce, 0x64, 0x34, 0xb2, 0x34, 0xc3, 0x13, 0x42, 0x55, 0x55, - 0xb1, 0x60, 0x85, 0x44, 0x39, 0x01, 0xa4, 0x42, 0x42, 0x6a, 0xa5, 0xca, 0x65, 0xc1, 0xd6, 0xd4, - 0xa6, 0x58, 0x4a, 0xe3, 0xe2, 0x38, 0x48, 0xe1, 0x24, 0x1c, 0x89, 0x25, 0x47, 0x40, 0x70, 0x01, - 0x8e, 0x80, 0xed, 0x24, 0x6d, 0xd9, 0x74, 0x61, 0xc9, 0xef, 0xfb, 0xfb, 0xde, 0xff, 0x3f, 0x37, - 0x08, 0x2d, 0xa4, 0xe2, 0xa7, 0x6b, 0x25, 0xb5, 0xc4, 0xbe, 0xbd, 0x0f, 0xbf, 0xda, 0x28, 0x98, - 0x72, 0x4d, 0x19, 0xd5, 0x14, 0x03, 0xea, 0x8d, 0x0b, 0x5d, 0x92, 0xdb, 0x33, 0xf0, 0x06, 0xde, - 0x49, 0x4c, 0x7a, 0xac, 0x2a, 0xb7, 0xca, 0x08, 0x5a, 0xbb, 0xca, 0x08, 0x1f, 0xa1, 0xf0, 0x4a, - 0xf1, 0xc7, 0x82, 0x67, 0x8b, 0x12, 0xda, 0x46, 0x6b, 0x91, 0xf0, 0xbe, 0x01, 0xf8, 0x10, 0x05, - 0x63, 0x33, 0x99, 0x50, 0xcd, 0xc1, 0x37, 0x62, 0x48, 0x02, 0x56, 0xd7, 0xb8, 0x8f, 0x50, 0x22, - 0x99, 0xc8, 0x96, 0x4e, 0xed, 0x38, 0xd5, 0x04, 0x6c, 0x88, 0x9d, 0x7c, 0x23, 0x56, 0x3c, 0xd7, - 0x74, 0xb5, 0x86, 0xae, 0x73, 0x0d, 0x75, 0x03, 0x30, 0x46, 0x3e, 0xc9, 0x73, 0x01, 0x3d, 0x23, - 0x74, 0x88, 0xaf, 0xcc, 0xdd, 0xb2, 0x49, 0x9e, 0x29, 0x08, 0x5c, 0x0c, 0x3f, 0x35, 0x77, 0x3c, - 0x40, 0xd1, 0x8c, 0x96, 0xa9, 0xa4, 0x6c, 0x2e, 0x9e, 0x39, 0x84, 0x6e, 0x4e, 0xb4, 0xde, 0x22, - 0xdb, 0x65, 0x7d, 0x00, 0xb9, 0x04, 0xbe, 0xb5, 0xc0, 0xc7, 0x28, 0x26, 0x94, 0x09, 0x69, 0x57, - 0x4b, 0x1e, 0x68, 0x06, 0x91, 0xeb, 0x8b, 0xd5, 0x2e, 0xb4, 0x09, 0x13, 0x92, 0xcc, 0x35, 0xd5, - 0x45, 0x0e, 0xbf, 0x5d, 0x90, 0x70, 0xd1, 0x00, 0xbb, 0xdf, 0x54, 0xb2, 0x22, 0xa5, 0x5a, 0xc8, - 0x0c, 0xe2, 0x6a, 0xbf, 0xd5, 0x86, 0xd8, 0x64, 0xd7, 0xd9, 0xd3, 0x4c, 0xa6, 0x54, 0x09, 0x5d, - 0xc2, 0x1f, 0xf3, 0x83, 0x80, 0x44, 0x62, 0x8b, 0xec, 0xeb, 0x5d, 0xa4, 0x5a, 0xe8, 0x82, 0x71, - 0xf8, 0xe7, 0xc6, 0x07, 0xb4, 0xae, 0xad, 0xf7, 0x44, 0x66, 0xcb, 0x4a, 0xfc, 0x5f, 0xbd, 0x7b, - 0xda, 0x00, 0xdb, 0x39, 0xa1, 0x75, 0xe7, 0x81, 0x13, 0x83, 0xb4, 0xae, 0x87, 0x1c, 0xc5, 0x36, - 0x61, 0xbe, 0xf9, 0xdb, 0x77, 0x6d, 0xbc, 0x7d, 0x36, 0xad, 0x7d, 0x36, 0xed, 0x9f, 0x36, 0x97, - 0x7f, 0x5f, 0x3f, 0xfa, 0xde, 0x9b, 0x39, 0xef, 0xe6, 0xbc, 0x7c, 0xf6, 0x7f, 0xdd, 0x75, 0xdd, - 0x87, 0x77, 0xfe, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x53, 0xf7, 0xf8, 0xc9, 0x86, 0x02, 0x00, 0x00, + // 391 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x52, 0x41, 0x4e, 0xe3, 0x30, + 0x14, 0x9d, 0xb4, 0x49, 0x9b, 0x38, 0xd3, 0x99, 0x91, 0x35, 0x33, 0x7c, 0x21, 0x54, 0x55, 0x5d, + 0xb1, 0x42, 0xa2, 0x9c, 0x00, 0x52, 0x55, 0x42, 0x6a, 0xa5, 0xca, 0x65, 0xc1, 0xd6, 0x34, 0xa6, + 0x58, 0x4a, 0xe3, 0xe0, 0x38, 0xa0, 0x70, 0x12, 0x8e, 0xc4, 0x92, 0x23, 0x20, 0xb8, 0x05, 0x2b, + 0x6c, 0x27, 0x69, 0xcb, 0xa6, 0x0b, 0x4b, 0xff, 0xbd, 0x97, 0xff, 0xdf, 0xfb, 0xb1, 0x11, 0x5a, + 0x0a, 0xc9, 0x4e, 0x32, 0x29, 0x94, 0xc0, 0xae, 0xa9, 0x87, 0x9f, 0x6d, 0xe4, 0xcf, 0x98, 0xa2, + 0x31, 0x55, 0x14, 0x03, 0xea, 0x8e, 0x0b, 0x55, 0x92, 0xeb, 0x53, 0x70, 0x06, 0xce, 0x71, 0x8f, + 0x74, 0xe3, 0x0a, 0x6e, 0x95, 0x11, 0xb4, 0x76, 0x95, 0x11, 0x3e, 0x42, 0xc1, 0x44, 0xb2, 0xfb, + 0x82, 0xa5, 0xcb, 0x12, 0xda, 0x5a, 0x6b, 0x91, 0xe0, 0xb6, 0x21, 0xf0, 0x21, 0xf2, 0xc7, 0x7a, + 0x32, 0xa1, 0x8a, 0x81, 0xab, 0xc5, 0x80, 0xf8, 0x71, 0x8d, 0x71, 0x1f, 0xa1, 0x48, 0xc4, 0x3c, + 0x5d, 0x59, 0xd5, 0xb3, 0xaa, 0x0e, 0xd8, 0x30, 0x66, 0xf2, 0x15, 0x5f, 0xb3, 0x5c, 0xd1, 0x75, + 0x06, 0x1d, 0xeb, 0x1a, 0xa8, 0x86, 0xc0, 0x18, 0xb9, 0x24, 0xcf, 0x39, 0x74, 0xb5, 0xe0, 0x11, + 0x57, 0xea, 0xda, 0x70, 0xd3, 0x3c, 0x95, 0xe0, 0xdb, 0x18, 0x6e, 0xa2, 0x6b, 0x3c, 0x40, 0xe1, + 0x9c, 0x96, 0x89, 0xa0, 0xf1, 0x82, 0x3f, 0x31, 0x08, 0xec, 0x9c, 0x30, 0xdb, 0x52, 0xa6, 0xcb, + 0xf8, 0x00, 0xb2, 0x09, 0x5c, 0x63, 0x61, 0xf6, 0x25, 0x93, 0xe8, 0x8e, 0xf2, 0x14, 0xc2, 0x6a, + 0x5f, 0x59, 0x41, 0x93, 0x2a, 0x22, 0xd1, 0x42, 0x51, 0x55, 0xe4, 0xf0, 0xd3, 0x9a, 0x07, 0xcb, + 0x86, 0x30, 0x3b, 0xcd, 0x44, 0x5c, 0x24, 0x54, 0x71, 0x91, 0x42, 0xaf, 0xda, 0x69, 0xbd, 0x61, + 0x4c, 0x9a, 0xcb, 0xf4, 0x61, 0x2e, 0x12, 0x2a, 0xb9, 0x2a, 0xe1, 0x97, 0xfe, 0xc0, 0x27, 0x21, + 0xdf, 0x52, 0xf8, 0x2f, 0xf2, 0xe6, 0xe2, 0x91, 0x49, 0xf8, 0x6d, 0x7d, 0xbd, 0xcc, 0x00, 0xf3, + 0x1f, 0xcf, 0x13, 0xc5, 0x55, 0x11, 0x33, 0xf8, 0x67, 0x4d, 0x7d, 0x5a, 0x63, 0x93, 0x68, 0x2a, + 0xd2, 0x55, 0x25, 0xfe, 0xaf, 0x6e, 0x20, 0x69, 0x08, 0xd3, 0x39, 0xa5, 0x75, 0xe7, 0x81, 0x15, + 0xfd, 0xa4, 0xc6, 0x43, 0x86, 0x7a, 0x26, 0x77, 0xbe, 0x79, 0x00, 0xbb, 0x36, 0xce, 0x3e, 0x9b, + 0xd6, 0x3e, 0x9b, 0xf6, 0x77, 0x9b, 0x8b, 0x3f, 0x2f, 0xef, 0x7d, 0xe7, 0x55, 0x9f, 0x37, 0x7d, + 0x9e, 0x3f, 0xfa, 0x3f, 0x6e, 0x3a, 0xf6, 0x09, 0x9e, 0x7d, 0x05, 0x00, 0x00, 0xff, 0xff, 0x26, + 0xf4, 0x86, 0xcc, 0x90, 0x02, 0x00, 0x00, } diff --git a/core/protos/core.proto b/core/protos/core.proto index 75d96b8c7..38d385b83 100644 --- a/core/protos/core.proto +++ b/core/protos/core.proto @@ -14,10 +14,11 @@ message Metadata { float Lsnr = 8; uint32 PayloadSize = 9; string Time = 10; - uint32 RadioFreqChan = 11; + uint32 RFChain = 11; int32 CRCStatus = 12; string Modulation = 13; bool InvPolarity = 14; + uint32 Power = 15; int32 Altitude = 21; float Longitude = 22; From 00b9778f02a36c89a23f470045d79326bd2e276f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 13:00:57 +0100 Subject: [PATCH 1198/2266] [ttnctl] Move duplicate flags to RootCmd to avoid overwrite --- ttnctl/cmd/device.go | 10 ---------- ttnctl/cmd/downlink.go | 6 ------ ttnctl/cmd/join.go | 6 ------ ttnctl/cmd/root.go | 16 ++++++++++++++++ ttnctl/cmd/uplink.go | 3 --- 5 files changed, 16 insertions(+), 25 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index f294ccea1..58409f5bf 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -162,14 +162,4 @@ func init() { RootCmd.AddCommand(devicesCmd) devicesCmd.AddCommand(devicesRegisterCmd) devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) - - devicesCmd.Flags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler") - viper.BindPFlag("ttn-handler", devicesCmd.Flags().Lookup("ttn-handler")) - - devicesCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") - viper.BindPFlag("app-eui", devicesCmd.PersistentFlags().Lookup("app-eui")) - - devicesCmd.PersistentFlags().String("app-token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJUVE4tSEFORExFUi0xIiwiaXNzIjoiVGhlVGhpbmdzVGhlTmV0d29yayIsInN1YiI6IjAxMDIwMzA0MDUwNjA3MDgifQ.zMHNXAVgQj672lwwDVmfYshpMvPwm6A8oNWJ7teGS2A", "The app Token to use") - viper.BindPFlag("app-token", devicesCmd.PersistentFlags().Lookup("app-token")) - } diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 61fee2e70..fe78adf1b 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -64,10 +64,4 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one func init() { RootCmd.AddCommand(downlinkCmd) - - downlinkCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") - viper.BindPFlag("mqtt-broker", downlinkCmd.Flags().Lookup("mqtt-broker")) - - downlinkCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") - viper.BindPFlag("app-eui", downlinkCmd.Flags().Lookup("app-eui")) } diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index b0ecef494..181790b4e 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -201,10 +201,4 @@ var joinCmd = &cobra.Command{ func init() { RootCmd.AddCommand(joinCmd) - - joinCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") - viper.BindPFlag("ttn-router", joinCmd.Flags().Lookup("ttn-router")) - - joinCmd.Flags().String("app-eui", "0102030405060708", "The app EUI to use") - viper.BindPFlag("app-eui", joinCmd.Flags().Lookup("app-eui")) } diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 1e437740d..9571e09c5 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -50,6 +50,22 @@ func init() { cobra.OnInitialize(initConfig) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") + + RootCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") + viper.BindPFlag("ttn-router", RootCmd.Flags().Lookup("ttn-router")) + + RootCmd.PersistentFlags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler") + viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) + + RootCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + viper.BindPFlag("mqtt-broker", RootCmd.Flags().Lookup("mqtt-broker")) + + RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") + viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) + + RootCmd.PersistentFlags().String("app-token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJUVE4tSEFORExFUi0xIiwiaXNzIjoiVGhlVGhpbmdzVGhlTmV0d29yayIsInN1YiI6IjAxMDIwMzA0MDUwNjA3MDgifQ.zMHNXAVgQj672lwwDVmfYshpMvPwm6A8oNWJ7teGS2A", "The app Token to use") + viper.BindPFlag("app-token", RootCmd.PersistentFlags().Lookup("app-token")) + } // initConfig reads in config file and ENV variables if set. diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index f5b66d083..66abdcfd6 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -201,7 +201,4 @@ var uplinkCmd = &cobra.Command{ func init() { RootCmd.AddCommand(uplinkCmd) - - uplinkCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") - viper.BindPFlag("ttn-router", uplinkCmd.Flags().Lookup("ttn-router")) } From 315984292ebef4b3c7a47cba1f95d13ff8ed36fb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 13:41:28 +0100 Subject: [PATCH 1199/2266] Temporarily Drop replay-attack check See Issue #87 --- core/components/handler/handler.go | 23 +-- core/components/handler/handler_test.go | 211 ++++++++++++------------ 2 files changed, 119 insertions(+), 115 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 1e71838f3..b41067c4e 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -10,7 +10,6 @@ import ( "fmt" "math/rand" "net" - "reflect" "time" "github.com/TheThingsNetwork/ttn/core" @@ -347,7 +346,9 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture // with a ttl for each entry. Processed is merely there to avoid late packets from being // processed again. The TTL could be only of several seconds or minutes. - processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? + // processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? + // See Issue #87 + buffers := make(map[[20]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles alarm := make(chan [20]byte) // Communication channel with subsequent alarms @@ -361,7 +362,7 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { // Register the last processed entry var pid [16]byte copy(pid[:], id[:16]) - processed[pid] = id[16:] + // processed[pid] = id[16:] // See Issue #87 // Actually send the bundle to the be processed go func(bundles []bundle) { chbundles <- bundles }(bundles) @@ -372,13 +373,15 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { // Check if bundle has already been processed var pid [16]byte copy(pid[:], b.ID[:16]) - if reflect.DeepEqual(processed[pid], b.ID[16:]) { - ctx.Debug("Reject already processed bundle") - go func(b bundle) { - b.Chresp <- errors.New(errors.Behavioural, "Already processed") - }(b) - continue - } + + // See Issue #87 + // if reflect.DeepEqual(processed[pid], b.ID[16:]) { + // ctx.Debug("Reject already processed bundle") + // go func(b bundle) { + // b.Chresp <- errors.New(errors.Behavioural, "Already processed") + // }(b) + // continue + // } // Add the bundle to the stack, and set the alarm if its the first bundles := append(buffers[b.ID], b) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 852996bef..04ecac016 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -700,111 +700,112 @@ func TestHandleDataUp(t *testing.T) { // -------------------- - { - Desc(t, "Handle late uplink | No Downlink") - - // Build - devStorage := NewMockDevStorage() - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req1 := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - DutyRX1: uint32(dutycycle.StateWarning), - DutyRX2: uint32(dutycycle.StateWarning), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - MType: uint32(lorawan.UnconfirmedDataUp), - } - req2 := &core.DataUpHandlerReq{ - Payload: req1.Payload, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - FCnt: req1.FCnt, - MType: req1.MType, - } - - // Expect - var wantErr1 *string - var wantErr2 = ErrBehavioural - var wantRes1 = new(core.DataUpHandlerRes) - var wantRes2 = new(core.DataUpHandlerRes) - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req1.Metadata}, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - } - var wantFCnt = devStorage.OutRead.Entry.FCntDown - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - - chack := make(chan bool) - go func() { - var ok bool - defer func(ok *bool) { chack <- *ok }(&ok) - res, err := handler.HandleDataUp(context.Background(), req1) - - // Check - CheckErrors(t, wantErr1, err) - Check(t, wantRes1, res, "Data Up Handler Responses") - ok = true - }() - - go func() { - <-time.After(2 * bufferDelay) - var ok bool - defer func(ok *bool) { chack <- *ok }(&ok) - res, err := handler.HandleDataUp(context.Background(), req2) - - // Check - CheckErrors(t, wantErr2, err) - Check(t, wantRes2, res, "Data Up Handler Responses") - ok = true - }() - - // Check - ok1, ok2 := <-chack, <-chack - Check(t, true, ok1 && ok2, "Acknowledgements") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } + // See Issue #87 + // { + // Desc(t, "Handle late uplink | No Downlink") + // + // // Build + // devStorage := NewMockDevStorage() + // devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) + // devStorage.OutRead.Entry = devEntry{ + // DevAddr: devAddr[:], + // AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + // NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + // FCntDown: 3, + // } + // pktStorage := NewMockPktStorage() + // appAdapter := mocks.NewAppClient() + // broker := mocks.NewAuthBrokerClient() + // payload, fcnt := []byte("Payload"), uint32(14) + // encoded, err := lorawan.EncryptFRMPayload( + // devStorage.OutRead.Entry.AppSKey, + // true, + // devAddr, + // fcnt, + // payload, + // ) + // FatalUnless(t, err) + // req1 := &core.DataUpHandlerReq{ + // Payload: encoded, + // Metadata: &core.Metadata{ + // DataRate: "SF7BW125", + // DutyRX1: uint32(dutycycle.StateWarning), + // DutyRX2: uint32(dutycycle.StateWarning), + // Rssi: -20, + // Lsnr: 5.0, + // }, + // AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + // DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + // FCnt: fcnt, + // MType: uint32(lorawan.UnconfirmedDataUp), + // } + // req2 := &core.DataUpHandlerReq{ + // Payload: req1.Payload, + // Metadata: &core.Metadata{ + // DataRate: "SF7BW125", + // DutyRX1: uint32(dutycycle.StateAvailable), + // DutyRX2: uint32(dutycycle.StateAvailable), + // Rssi: -20, + // Lsnr: 5.0, + // }, + // AppEUI: req1.AppEUI, + // DevEUI: req1.DevEUI, + // FCnt: req1.FCnt, + // MType: req1.MType, + // } + // + // // Expect + // var wantErr1 *string + // var wantErr2 = ErrBehavioural + // var wantRes1 = new(core.DataUpHandlerRes) + // var wantRes2 = new(core.DataUpHandlerRes) + // var wantData = &core.DataAppReq{ + // Payload: payload, + // Metadata: []*core.Metadata{req1.Metadata}, + // AppEUI: req1.AppEUI, + // DevEUI: req1.DevEUI, + // } + // var wantFCnt = devStorage.OutRead.Entry.FCntDown + // + // // Operate + // handler := New(Components{ + // Ctx: GetLogger(t, "Handler"), + // Broker: broker, + // AppAdapter: appAdapter, + // DevStorage: devStorage, + // PktStorage: pktStorage, + // }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) + // + // chack := make(chan bool) + // go func() { + // var ok bool + // defer func(ok *bool) { chack <- *ok }(&ok) + // res, err := handler.HandleDataUp(context.Background(), req1) + // + // // Check + // CheckErrors(t, wantErr1, err) + // Check(t, wantRes1, res, "Data Up Handler Responses") + // ok = true + // }() + // + // go func() { + // <-time.After(2 * bufferDelay) + // var ok bool + // defer func(ok *bool) { chack <- *ok }(&ok) + // res, err := handler.HandleDataUp(context.Background(), req2) + // + // // Check + // CheckErrors(t, wantErr2, err) + // Check(t, wantRes2, res, "Data Up Handler Responses") + // ok = true + // }() + // + // // Check + // ok1, ok2 := <-chack, <-chack + // Check(t, true, ok1 && ok2, "Acknowledgements") + // Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") + // Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") + // } // -------------------- From fac211d47deeeeeb44b167c3e7db16231701da28 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 14:16:25 +0100 Subject: [PATCH 1200/2266] [hotfix] Response RFChain should always be 0 --- core/components/handler/handler.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index b41067c4e..bb351a0b0 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -51,6 +51,7 @@ type component struct { RXDelay uint8 PowerRX1 uint32 PowerRX2 uint32 + RFChain uint32 InvPolarity bool JoinDelay uint8 } @@ -108,6 +109,7 @@ func New(c Components, o Options) Interface { h.Configuration.JoinDelay = 5 h.Configuration.PowerRX1 = 14 h.Configuration.PowerRX2 = 27 + h.Configuration.RFChain = 0 h.Configuration.InvPolarity = true set := make(chan bundle) @@ -763,7 +765,7 @@ func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay CodingRate: metadata.CodingRate, DataRate: metadata.DataRate, Modulation: metadata.Modulation, - RFChain: metadata.RFChain, + RFChain: h.Configuration.RFChain, InvPolarity: h.Configuration.InvPolarity, Power: h.Configuration.PowerRX1, PayloadSize: size, From 17f08287b8441a5cbe3eca9f7d0f768a4d72b927 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 15:45:08 +0100 Subject: [PATCH 1201/2266] [hotfix] Make sure rfch is passed to gateway --- core/adapters/udp/conversions.go | 6 ++++++ core/adapters/udp/udp_test.go | 2 ++ 2 files changed, 8 insertions(+) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 8ea6c18c8..892756d5f 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -125,6 +125,12 @@ func newTXPK(payload encoding.BinaryMarshaler, metadata *core.Metadata, ctx log. // Step 3, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. injectMetadata(&txpk, *metadata) + + // Step 4, set required Rfch to its default value. // See issue # + if txpk.Rfch == nil { + txpk.Rfch = pointer.Uint32(0) + } + return txpk, nil } diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index 24d89c259..063918841 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -264,6 +264,7 @@ func TestUDPAdapter(t *testing.T) { Payload: &semtech.Payload{ TXPK: &semtech.TXPK{ Data: pointer.String(base64.RawStdEncoding.EncodeToString(dataDown)), + Rfch: pointer.Uint32(0), }, }, }, @@ -968,6 +969,7 @@ func TestUDPAdapter(t *testing.T) { Payload: &semtech.Payload{ TXPK: &semtech.TXPK{ Data: pointer.String(base64.RawStdEncoding.EncodeToString(dataDown)), + Rfch: pointer.Uint32(0), }, }, }, From 0d9c126b9143d810371bd09950fb8109ddcc470f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 16:34:43 +0100 Subject: [PATCH 1202/2266] [hotfix] Fix ttn-router and mqtt-broker flags in ttnctl --- ttnctl/cmd/root.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 9571e09c5..8641fa42f 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -51,14 +51,14 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") - RootCmd.Flags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") - viper.BindPFlag("ttn-router", RootCmd.Flags().Lookup("ttn-router")) + RootCmd.PersistentFlags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") + viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router")) RootCmd.PersistentFlags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler") viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) - RootCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") - viper.BindPFlag("mqtt-broker", RootCmd.Flags().Lookup("mqtt-broker")) + RootCmd.PersistentFlags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) From 263307b4978b1496584aad0e523330237e194d1d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Mar 2016 20:03:37 +0100 Subject: [PATCH 1203/2266] [udp.semtech] send PULL_RESP over correct socket --- core/adapters/udp/connection.go | 84 +++++++++++++ core/adapters/udp/connection_test.go | 39 ++++++ core/adapters/udp/udp.go | 35 +++--- core/adapters/udp/udp_test.go | 173 ++++++++++++++++----------- ttnctl/cmd/join.go | 32 ++++- ttnctl/cmd/uplink.go | 32 ++++- 6 files changed, 304 insertions(+), 91 deletions(-) create mode 100644 core/adapters/udp/connection.go create mode 100644 core/adapters/udp/connection_test.go diff --git a/core/adapters/udp/connection.go b/core/adapters/udp/connection.go new file mode 100644 index 000000000..eed7bee0a --- /dev/null +++ b/core/adapters/udp/connection.go @@ -0,0 +1,84 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "fmt" + "net" + "sync" +) + +type replier interface { + WriteToUplink(data []byte) error + WriteToDownlink(data []byte) error +} + +type gatewayConn struct { + sync.RWMutex + conn *net.UDPConn + uplinkAddr *net.UDPAddr + downlinkAddr *net.UDPAddr +} + +func (c *gatewayConn) SetConn(conn *net.UDPConn) { + c.Lock() + defer c.Unlock() + c.conn = conn +} + +func (c *gatewayConn) SetUplinkAddr(addr *net.UDPAddr) { + c.Lock() + defer c.Unlock() + c.uplinkAddr = addr +} + +func (c *gatewayConn) SetDownlinkAddr(addr *net.UDPAddr) { + c.Lock() + defer c.Unlock() + c.downlinkAddr = addr +} + +func (c *gatewayConn) WriteToUplink(data []byte) error { + c.RLock() + defer c.RUnlock() + if c.conn == nil || c.uplinkAddr == nil { + return fmt.Errorf("Uplink connection unavailable.") + } + + _, err := c.conn.WriteToUDP(data, c.uplinkAddr) + return err +} + +func (c *gatewayConn) WriteToDownlink(data []byte) error { + c.RLock() + defer c.RUnlock() + if c.conn == nil || c.downlinkAddr == nil { + return fmt.Errorf("Downlink connection unavailable.") + } + + _, err := c.conn.WriteToUDP(data, c.downlinkAddr) + return err +} + +type gatewayPool struct { + sync.Mutex + connections map[[8]byte]*gatewayConn +} + +func newPool() *gatewayPool { + return &gatewayPool{ + connections: make(map[[8]byte]*gatewayConn), + } +} + +func (p *gatewayPool) GetOrCreate(gatewayID []byte) *gatewayConn { + p.Lock() + defer p.Unlock() + var id [8]byte + copy(id[:], gatewayID) + if _, ok := p.connections[id]; !ok { + p.connections[id] = &gatewayConn{} + } + return p.connections[id] +} diff --git a/core/adapters/udp/connection_test.go b/core/adapters/udp/connection_test.go new file mode 100644 index 000000000..0867b75cf --- /dev/null +++ b/core/adapters/udp/connection_test.go @@ -0,0 +1,39 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package udp + +import ( + "net" + "testing" + + . "github.com/smartystreets/assertions" +) + +func mkConn() *gatewayConn { + return &gatewayConn{} +} + +func TestSetConn(t *testing.T) { + a := New(t) + exp := &net.UDPConn{} + c := mkConn() + c.SetConn(exp) + a.So(c.conn, ShouldEqual, exp) +} + +func TestSetUplinkAddr(t *testing.T) { + a := New(t) + exp := &net.UDPAddr{} + c := mkConn() + c.SetUplinkAddr(exp) + a.So(c.uplinkAddr, ShouldEqual, exp) +} + +func TestSetDownlinkAddr(t *testing.T) { + a := New(t) + exp := &net.UDPAddr{} + c := mkConn() + c.SetDownlinkAddr(exp) + a.So(c.downlinkAddr, ShouldEqual, exp) +} diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index 88cf5b78d..c59b948fd 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -19,6 +19,7 @@ import ( type adapter struct { Components + pool *gatewayPool } // Components defines a structure to make the instantiation easier to read @@ -36,9 +37,6 @@ type Options struct { MaxReconnectionDelay time.Duration } -// replier is an alias used by methods herebelow -type replier func(data []byte) error - // straightMarshaler can be used to easily obtain a binary marshaler from a sequence of byte type straightMarshaler []byte @@ -57,19 +55,11 @@ func Start(c Components, o Options) (err error) { } c.Ctx.WithField("bind", o.NetAddr).Info("Starting Server") - a := adapter{Components: c} + a := adapter{Components: c, pool: newPool()} go a.listen(o.NetAddr, udpConn, o.MaxReconnectionDelay) return nil } -// makeReply curryfies a writing to udp connection by binding the address and connection -func makeReply(addr *net.UDPAddr, conn *net.UDPConn) replier { - return func(data []byte) error { - _, err := conn.WriteToUDP(data, addr) - return err - } -} - // tryConnect attempt to connect to a udp connection func tryConnect(netAddr string) (*net.UDPConn, error) { addr, err := net.ResolveUDPAddr("udp", netAddr) @@ -105,24 +95,29 @@ func (a adapter) listen(netAddr string, conn *net.UDPConn, maxReconnectionDelay // Handle the incoming datagram a.Ctx.Debug("Incoming datagram") - go func(data []byte, reply replier) { + go func(data []byte, conn *net.UDPConn) { pkt := new(semtech.Packet) if err := pkt.UnmarshalBinary(data); err != nil { a.Ctx.WithError(err).Debug("Unable to handle datagram") } + gtwConn := a.pool.GetOrCreate(pkt.GatewayId) + gtwConn.SetConn(conn) + switch pkt.Identifier { case semtech.PULL_DATA: - err = a.handlePullData(*pkt, reply) + gtwConn.SetDownlinkAddr(addr) + err = a.handlePullData(*pkt, gtwConn) case semtech.PUSH_DATA: - err = a.handlePushData(*pkt, reply) + gtwConn.SetUplinkAddr(addr) + err = a.handlePushData(*pkt, gtwConn) default: err = errors.New(errors.Implementation, "Unhandled packet type") } if err != nil { a.Ctx.WithError(err).Debug("Unable to handle datagram") } - }(buf[:n], makeReply(addr, conn)) + }(buf[:n], conn) } if conn != nil { @@ -147,7 +142,7 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { return errors.New(errors.Structural, err) } - return reply(data) + return reply.WriteToDownlink(data) } // Handle a PUSH_DATA packet coming from a gateway @@ -164,7 +159,7 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { Token: pkt.Token, Identifier: semtech.PUSH_ACK, }.MarshalBinary() - if err != nil || reply(data) != nil || pkt.Payload == nil { + if err != nil || reply.WriteToUplink(data) != nil || pkt.Payload == nil { ctx.Debug("Unable to send ACK") return errors.New(errors.Operational, "Unable to send ACK") } @@ -255,7 +250,7 @@ func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { a.Ctx.WithError(err).Debug("Unable to create semtech packet with TXPK") return errors.New(errors.Structural, err) } - return reply(data) + return reply.WriteToDownlink(data) } func (a adapter) handleJoinAccept(resp *core.JoinRouterRes, reply replier) error { @@ -280,5 +275,5 @@ func (a adapter) handleJoinAccept(resp *core.JoinRouterRes, reply replier) error if err != nil { a.Ctx.WithError(err).Debug("Unable to create semtech packet with TXPK") } - return reply(data) + return reply.WriteToDownlink(data) } diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index 063918841..088fd31e4 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -42,6 +42,63 @@ func TestUDPAdapter(t *testing.T) { return fmt.Sprintf("0.0.0.0:%d", port) } + // ------------------- + + { + Desc(t, "Send a PULL_DATA through udp") + + // Build + packet := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PULL_DATA, + } + data, err := packet.MarshalBinary() + FatalUnless(t, err) + + router := mocks.NewRouterServer() + + netAddr := newAddr() + addr, err := net.ResolveUDPAddr("udp", netAddr) + FatalUnless(t, err) + conn, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + + // Expectations + var wantErrStart *string + var wantDataRouterReq *core.DataRouterReq + var wantStats *core.StatsReq + var wantSemtechResp = []semtech.Packet{ + { + Version: semtech.VERSION, + Token: packet.Token, + Identifier: semtech.PULL_ACK, + }, + {}, + } + + // Operate + chpkt := listenPackets(conn) + errStart := Start( + Components{Router: router, Ctx: GetLogger(t, "Adapter")}, + Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, + ) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + _, err = conn.Write(data) + FatalUnless(t, err) + <-time.After(time.Millisecond * 50) + close(chpkt) + + // Check + CheckErrors(t, wantErrStart, errStart) + Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") + Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") + Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + } + { Desc(t, "Send a valid packet through udp, no downlink") @@ -148,6 +205,16 @@ func TestUDPAdapter(t *testing.T) { { Desc(t, "Send a valid packet through udp, with valid downlink") + // Build pull packet + pullPacket := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PULL_DATA, + } + pullData, err := pullPacket.MarshalBinary() + FatalUnless(t, err) + // Build payload := lorawan.NewPHYPayload(true) payload.MHDR.MType = lorawan.UnconfirmedDataUp @@ -219,7 +286,9 @@ func TestUDPAdapter(t *testing.T) { netAddr := newAddr() addr, err := net.ResolveUDPAddr("udp", netAddr) FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) + conn1, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + conn2, err := net.DialUDP("udp", nil, addr) FatalUnless(t, err) // Expectations @@ -271,24 +340,30 @@ func TestUDPAdapter(t *testing.T) { } // Operate - chpkt := listenPackets(conn) + chpkt1 := listenPackets(conn1) + chpkt2 := listenPackets(conn2) errStart := Start( Components{Router: router, Ctx: GetLogger(t, "Adapter")}, Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, ) FatalUnless(t, err) + <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) + _, err = conn1.Write(pullData) + <-time.After(time.Millisecond * 50) + _, err = conn2.Write(data) FatalUnless(t, err) <-time.After(time.Millisecond * 50) - close(chpkt) + close(chpkt1) + close(chpkt2) // Check CheckErrors(t, wantErrStart, errStart) Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + Check(t, wantSemtechResp[0], <-chpkt2, "Acknowledgements") + <-chpkt1 + Check(t, wantSemtechResp[1], <-chpkt1, "Downlinks") } // -------------------- @@ -764,63 +839,6 @@ func TestUDPAdapter(t *testing.T) { // ------------------- - { - Desc(t, "Send a PULL_DATA through udp") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PULL_DATA, - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PULL_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // ------------------- - { Desc(t, "Invalid options NetAddr") @@ -901,6 +919,16 @@ func TestUDPAdapter(t *testing.T) { { Desc(t, "Send a valid join through udp, with valid join-accept") + // Build pull packet + pullPacket := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PULL_DATA, + } + pullData, err := pullPacket.MarshalBinary() + FatalUnless(t, err) + // Build payload := lorawan.NewPHYPayload(true) payload.MHDR.MType = lorawan.JoinRequest @@ -943,7 +971,9 @@ func TestUDPAdapter(t *testing.T) { netAddr := newAddr() addr, err := net.ResolveUDPAddr("udp", netAddr) FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) + conn1, err := net.DialUDP("udp", nil, addr) + FatalUnless(t, err) + conn2, err := net.DialUDP("udp", nil, addr) FatalUnless(t, err) // Expectations @@ -976,24 +1006,29 @@ func TestUDPAdapter(t *testing.T) { } // Operate - chpkt := listenPackets(conn) + chpkt1 := listenPackets(conn1) + chpkt2 := listenPackets(conn2) errStart := Start( Components{Router: router, Ctx: GetLogger(t, "Adapter")}, Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, ) FatalUnless(t, err) <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) + _, err = conn1.Write(pullData) + <-time.After(time.Millisecond * 50) + _, err = conn2.Write(data) FatalUnless(t, err) <-time.After(time.Millisecond * 50) - close(chpkt) + close(chpkt1) + close(chpkt2) // Check CheckErrors(t, wantErrStart, errStart) Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") + <-chpkt1 // get the PULL_RESP + Check(t, wantSemtechResp[0], <-chpkt2, "Acknowledgements") + Check(t, wantSemtechResp[1], <-chpkt1, "Downlinks") } // -------------------- diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 181790b4e..5a28b7a34 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -82,7 +82,7 @@ var joinCmd = &cobra.Command{ // Handle downlink chdown := make(chan bool) go func() { - // Get Ack + // Get PullAck buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { @@ -92,6 +92,18 @@ var joinCmd = &cobra.Command{ if err := pkt.UnmarshalBinary(buf[:n]); err != nil { ctx.Fatalf("Invalid udp response: %s", err) } + ctx.Infof("Received PullAck: %s", pkt) + + // Get Ack + buf = make([]byte, 1024) + n, err = conn.Read(buf) + if err != nil { + ctx.Fatalf("Error receiving udp datagram: %s", err) + } + pkt = new(semtech.Packet) + if err := pkt.UnmarshalBinary(buf[:n]); err != nil { + ctx.Fatalf("Invalid udp response: %s", err) + } ctx.Infof("Received Ack: %s", pkt) // Get Downlink, if any @@ -153,6 +165,19 @@ var joinCmd = &cobra.Command{ ctx.Infof("Available Frequencies: %v", joinAccept.CFList) }() + // PULL_DATA Packet + + pullPacket := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PULL_DATA, + } + pullData, err := pullPacket.MarshalBinary() + if err != nil { + ctx.Fatal("Unable to construct pull_data") + } + // Router Packet data, err := phyPayload.MarshalBinary() if err != nil { @@ -187,6 +212,11 @@ var joinCmd = &cobra.Command{ ctx.Fatalf("Unable to construct framepayload: %v", data) } + _, err = conn.Write(pullData) + if err != nil { + ctx.Fatal("Unable to send pull_data") + } + _, err = conn.Write(data) if err != nil { ctx.Fatal("Unable to send payload") diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 66abdcfd6..ae5ae47d4 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -99,7 +99,7 @@ var uplinkCmd = &cobra.Command{ // Handle downlink chdown := make(chan bool) go func() { - // Get Ack + // Get PullAck buf := make([]byte, 1024) n, err := conn.Read(buf) if err != nil { @@ -109,6 +109,18 @@ var uplinkCmd = &cobra.Command{ if err := pkt.UnmarshalBinary(buf[:n]); err != nil { ctx.Fatalf("Invalid udp response: %s", err) } + ctx.Infof("Received PullAck: %s", pkt) + + // Get Ack + buf = make([]byte, 1024) + n, err = conn.Read(buf) + if err != nil { + ctx.Fatalf("Error receiving udp datagram: %s", err) + } + pkt = new(semtech.Packet) + if err := pkt.UnmarshalBinary(buf[:n]); err != nil { + ctx.Fatalf("Invalid udp response: %s", err) + } ctx.Infof("Received Ack: %s", pkt) // Get Downlink, if any @@ -153,6 +165,19 @@ var uplinkCmd = &cobra.Command{ } }() + // PULL_DATA Packet + + pullPacket := semtech.Packet{ + Version: semtech.VERSION, + GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + Token: []byte{1, 2}, + Identifier: semtech.PULL_DATA, + } + pullData, err := pullPacket.MarshalBinary() + if err != nil { + ctx.Fatal("Unable to construct pull_data") + } + // Router Packet data, err := phyPayload.MarshalBinary() if err != nil { @@ -187,6 +212,11 @@ var uplinkCmd = &cobra.Command{ ctx.Fatalf("Unable to construct framepayload: %v", data) } + _, err = conn.Write(pullData) + if err != nil { + ctx.Fatal("Unable to send pull_data") + } + _, err = conn.Write(data) if err != nil { ctx.Fatal("Unable to send payload") From d43ff777c46c6f7e762a2fc04f038e093e1e4a77 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 24 Mar 2016 17:14:50 +0100 Subject: [PATCH 1204/2266] [debt/fcnt-reset] Create a processed queue to manage processed packets --- core/components/handler/queue.go | 83 ++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 core/components/handler/queue.go diff --git a/core/components/handler/queue.go b/core/components/handler/queue.go new file mode 100644 index 000000000..3412939f7 --- /dev/null +++ b/core/components/handler/queue.go @@ -0,0 +1,83 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "reflect" + "sync" +) + +type pQueue interface { + Put([]byte) + Contains([]byte) bool + Remove([]byte) +} + +type pq struct { + sync.RWMutex // Guars ids & values + ids []pQueueEntry + values map[[17]byte]pQueueEntry + size int + ticket int64 +} + +type pQueueEntry struct { + Ticket int64 + ID [17]byte + Value []byte +} + +func newPQueue(size uint) pQueue { + return &pq{ + size: int(size), + values: make(map[[17]byte]pQueueEntry), + } +} + +// Put Insert a new entry in the queue. If the queue is full, then the oldest entry is discarded +func (q *pq) Put(pid []byte) { + q.Lock() + id, v := q.fromPid(pid) + q.ticket++ + entry := pQueueEntry{Ticket: q.ticket, Value: v, ID: id} + q.values[id] = entry + if len(q.values) > q.size { + var i = 0 + for _, e := range q.ids { + if v, ok := q.values[e.ID]; ok && v.ID == e.ID && v.Ticket == e.Ticket { + delete(q.values, e.ID) + q.ids = q.ids[i+1:] + break + } + i++ + } + } + q.ids = append(q.ids, entry) + q.Unlock() +} + +// Remove discard an entry from the map +func (q *pq) Remove(pid []byte) { + q.Lock() + id, _ := q.fromPid(pid) + delete(q.values, id) + q.Unlock() +} + +// Contains check whether a value exist for the given id +func (q pq) Contains(pid []byte) bool { + q.RLock() + defer q.RUnlock() + id, v := q.fromPid(pid) + if e, ok := q.values[id]; ok { + return reflect.DeepEqual(e.Value[:], v) + } + return false +} + +func (q pq) fromPid(pid []byte) ([17]byte, []byte) { + var id [17]byte + copy(id[:], pid[:17]) + return id, pid[17:] +} From cea556a2419dfbada4651f3dcbf4d1b7d0d87265 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 24 Mar 2016 17:15:00 +0100 Subject: [PATCH 1205/2266] [debt/fcnt-reset] Make use of the processed queue --- core/components/handler/handler.go | 58 ++++++++++++++---------------- 1 file changed, 27 insertions(+), 31 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index bb351a0b0..4a939939d 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -38,7 +38,8 @@ var dataRates = map[string]uint8{ // component implements the core.Component interface type component struct { Components - Set chan<- bundle + ChBundles chan<- bundle + Processed pQueue PublicNetAddr string PrivateNetAddr string PrivateNetAddrAnnounce string @@ -78,13 +79,14 @@ type Options struct { PublicNetAddr string // Net Address used to communicate with the handler from the outside PrivateNetAddr string // Net Address the handler listens on for internal communications PrivateNetAddrAnnounce string // Net Address the handler announces to brokers for internal communications + ProcessedQueueSize uint // The maximum number of appEUI + devEUI the handler can process at the same time } // bundle are used to materialize an incoming request being bufferized, waiting for the others. type bundle struct { Chresp chan interface{} Entry devEntry - ID [20]byte + ID [21]byte Packet interface{} DataRate string Time time.Time @@ -92,11 +94,16 @@ type bundle struct { // New construct a new Handler func New(c Components, o Options) Interface { + if o.ProcessedQueueSize == 0 { + o.ProcessedQueueSize = 5000 + } + h := &component{ Components: c, PublicNetAddr: o.PublicNetAddr, PrivateNetAddr: o.PrivateNetAddr, PrivateNetAddrAnnounce: o.PrivateNetAddrAnnounce, + Processed: newPQueue(o.ProcessedQueueSize), } // TODO Make it configurable @@ -115,7 +122,7 @@ func New(c Components, o Options) Interface { set := make(chan bundle) bundles := make(chan []bundle) - h.Set = set + h.ChBundles = set go h.consumeBundles(bundles) go h.consumeSet(bundles, set) @@ -184,8 +191,8 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* chresp := make(chan interface{}) // 3. Create a "bundle" which holds info waiting for other related packets - var bundleID [20]byte // AppEUI(8) | DevEUI(8) | DevNonce | [ 0 0 ] - buf := new(bytes.Buffer) + var bundleID [21]byte // Type | AppEUI(8) | DevEUI(8) | DevNonce | [ 0 0 ] + buf := bytes.NewBuffer([]byte{0}) // 0 for join _ = binary.Write(buf, binary.BigEndian, req.AppEUI) _ = binary.Write(buf, binary.BigEndian, req.DevEUI) _ = binary.Write(buf, binary.BigEndian, req.DevNonce) @@ -195,7 +202,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* ctx := h.Ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") req.Metadata.Time = time.Now().Format(time.RFC3339Nano) - h.Set <- bundle{ + h.ChBundles <- bundle{ ID: bundleID, Packet: req, DataRate: req.Metadata.DataRate, @@ -296,8 +303,8 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq chresp := make(chan interface{}) // 3. Create a "bundle" which holds info waiting for other related packets - var bundleID [20]byte // AppEUI(8) | DevEUI(8) | FCnt - buf := new(bytes.Buffer) + var bundleID [21]byte // Type | AppEUI(8) | DevEUI(8) | FCnt + buf := bytes.NewBuffer([]byte{1}) // 1 for uplink _ = binary.Write(buf, binary.BigEndian, req.AppEUI) _ = binary.Write(buf, binary.BigEndian, req.DevEUI) _ = binary.Write(buf, binary.BigEndian, req.FCnt) @@ -307,7 +314,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq ctx := h.Ctx.WithField("BundleID", bundleID) ctx.Debug("Define new bundle") req.Metadata.Time = time.Now().Format(time.RFC3339Nano) - h.Set <- bundle{ + h.ChBundles <- bundle{ ID: bundleID, Packet: req, DataRate: req.Metadata.DataRate, @@ -348,11 +355,8 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture // with a ttl for each entry. Processed is merely there to avoid late packets from being // processed again. The TTL could be only of several seconds or minutes. - // processed := make(map[[16]byte][]byte) // AppEUI | DevEUI | FCnt -> hasBeenProcessed ? - // See Issue #87 - - buffers := make(map[[20]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles - alarm := make(chan [20]byte) // Communication channel with subsequent alarms + buffers := make(map[[21]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles + alarm := make(chan [21]byte) // Communication channel with subsequent alarms for { select { @@ -360,11 +364,7 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { // Get all bundles bundles := buffers[id] delete(buffers, id) - - // Register the last processed entry - var pid [16]byte - copy(pid[:], id[:16]) - // processed[pid] = id[16:] // See Issue #87 + h.Processed.Put(id[:]) // Register the last processed entry // Actually send the bundle to the be processed go func(bundles []bundle) { chbundles <- bundles }(bundles) @@ -373,17 +373,13 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { ctx = ctx.WithField("BundleID", b.ID) // Check if bundle has already been processed - var pid [16]byte - copy(pid[:], b.ID[:16]) - - // See Issue #87 - // if reflect.DeepEqual(processed[pid], b.ID[16:]) { - // ctx.Debug("Reject already processed bundle") - // go func(b bundle) { - // b.Chresp <- errors.New(errors.Behavioural, "Already processed") - // }(b) - // continue - // } + if h.Processed.Contains(b.ID[:]) { + ctx.Debug("Reject already processed bundle") + go func(b bundle) { + b.Chresp <- errors.New(errors.Behavioural, "Already processed") + }(b) + continue + } // Add the bundle to the stack, and set the alarm if its the first bundles := append(buffers[b.ID], b) @@ -397,7 +393,7 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { } // setAlarm will trigger a message on the given channel after the given delay -func setAlarm(alarm chan<- [20]byte, id [20]byte, delay time.Duration) { +func setAlarm(alarm chan<- [21]byte, id [21]byte, delay time.Duration) { <-time.After(delay) alarm <- id } From bdb1e0ba1620e890d722f0bee2c4a3b224191f97 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 24 Mar 2016 17:19:38 +0100 Subject: [PATCH 1206/2266] [debt/fcnt-reset] Reset Processed counters on ABP and OTAA --- core/components/handler/handler.go | 2 ++ core/components/handler/handlerManager.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 4a939939d..e93ddf9d5 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -517,6 +517,8 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da for i, bundle := range bundles { if i == best.ID { + // Reset processed packets for that appEUI + devEUi + h.Processed.Remove(append([]byte{1}, bundle.ID[1:]...)) bundle.Chresp <- joinAccept } else { bundle.Chresp <- nil diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 5f626f4a5..b7dd53b70 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -93,6 +93,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), DevAddr: req.DevAddr, FCntDown: 0, + FCntUp: 0, } copy(entry.NwkSKey[:], req.NwkSKey) copy(entry.AppSKey[:], req.AppSKey) @@ -100,6 +101,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq h.Ctx.WithError(err).Debug("Error while trying to save valid request") return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) } + h.Processed.Remove(append([]byte{1}, append(entry.AppEUI, entry.DevEUI...)...)) // Done. return new(core.UpsertABPHandlerRes), nil From a5400607d669c979c191718aa70e01fb1def2f62 Mon Sep 17 00:00:00 2001 From: ktorz Date: Thu, 24 Mar 2016 23:34:00 +0100 Subject: [PATCH 1207/2266] [debt/fcnt-reset] Add tests for processed queue --- core/components/handler/queue.go | 2 + core/components/handler/queue_test.go | 85 +++++++++++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 core/components/handler/queue_test.go diff --git a/core/components/handler/queue.go b/core/components/handler/queue.go index 3412939f7..4a73cd74b 100644 --- a/core/components/handler/queue.go +++ b/core/components/handler/queue.go @@ -4,6 +4,7 @@ package handler import ( + "fmt" "reflect" "sync" ) @@ -60,6 +61,7 @@ func (q *pq) Put(pid []byte) { // Remove discard an entry from the map func (q *pq) Remove(pid []byte) { q.Lock() + fmt.Println(pid) id, _ := q.fromPid(pid) delete(q.values, id) q.Unlock() diff --git a/core/components/handler/queue_test.go b/core/components/handler/queue_test.go new file mode 100644 index 000000000..1798dae62 --- /dev/null +++ b/core/components/handler/queue_test.go @@ -0,0 +1,85 @@ +// Copyright © 2015 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func TestQueue(t *testing.T) { + { + Desc(t, "Put then Contain") + id := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} + q := newPQueue(1) + q.Put(id) + Check(t, true, q.Contains(id), "Contains") + } + + // ---------- + + { + Desc(t, "Put three on size 2") + id1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} + id2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 99} + id3 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 99} + q := newPQueue(2) + q.Put(id1) + q.Put(id2) + q.Put(id3) + Check(t, false, q.Contains(id1), "Contains") + Check(t, true, q.Contains(id2), "Contains") + Check(t, true, q.Contains(id3), "Contains") + } + + // ---------- + + { + Desc(t, "Put -> Remove -> Contain") + id := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} + q := newPQueue(2) + q.Put(id) + q.Remove(id[:17]) + Check(t, false, q.Contains(id), "Contains") + } + + // ---------- + + { + Desc(t, "Size 2, Put 2, update first, put 1") + id1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} + id2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 99} + id3 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 99} + q := newPQueue(2) + q.Put(id1) + q.Put(id2) + q.Put(id1) + q.Put(id3) + Check(t, true, q.Contains(id1), "Contains") + Check(t, false, q.Contains(id2), "Contains") + Check(t, true, q.Contains(id3), "Contains") + } + + // ---------- + + { + Desc(t, "size 2 | Put 1 and update 1 several times, remove, then put 2") + id1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} + id2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 99} + id3 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 99} + q := newPQueue(1) + q.Put(id1) + q.Put(id1) + q.Put(id1) + q.Put(id1) + q.Put(id1) + q.Remove(id1) + q.Put(id2) + q.Put(id3) + Check(t, false, q.Contains(id1), "Contains") + Check(t, false, q.Contains(id2), "Contains") + Check(t, true, q.Contains(id3), "Contains") + } +} From 30bfd5baff5342e9f4742c144a79fc7802432d69 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 25 Mar 2016 01:02:35 +0100 Subject: [PATCH 1208/2266] [hotfix] Use of pointer receiver on missed one in Processed Queue --- core/components/handler/queue.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/components/handler/queue.go b/core/components/handler/queue.go index 4a73cd74b..d54409824 100644 --- a/core/components/handler/queue.go +++ b/core/components/handler/queue.go @@ -68,7 +68,7 @@ func (q *pq) Remove(pid []byte) { } // Contains check whether a value exist for the given id -func (q pq) Contains(pid []byte) bool { +func (q *pq) Contains(pid []byte) bool { q.RLock() defer q.RUnlock() id, v := q.fromPid(pid) @@ -78,7 +78,7 @@ func (q pq) Contains(pid []byte) bool { return false } -func (q pq) fromPid(pid []byte) ([17]byte, []byte) { +func (q *pq) fromPid(pid []byte) ([17]byte, []byte) { var id [17]byte copy(id[:], pid[:17]) return id, pid[17:] From c28943f52f06f6f594d478b0632c90712409cd4b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 11:28:01 +0100 Subject: [PATCH 1209/2266] Improve logging in broker --- core/components/broker/broker.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index afed80e76..ee1ccdec9 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -248,6 +248,9 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c fhdr.FCnt = fcnt32 if !ok { // Check with 32-bits counter ok, err = uplinkPayload.ValidateMIC(key) + if err != nil { + continue + } } if ok { @@ -260,7 +263,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c if mEntry == nil { stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") - err := errors.New(errors.NotFound, "MIC check returned no matches") + err := errors.New(errors.NotFound, "FCntUp or MIC check did not match") ctx.WithError(err).Debug("Unable to handle uplink") return new(core.DataBrokerRes), err } From b31d31bea47c542254f88e16544bcdb2d44bd37b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 12:21:30 +0100 Subject: [PATCH 1210/2266] [cli] Use custom cli handler --- cmd/root.go | 2 +- utils/cli/cli.go | 101 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 utils/cli/cli.go diff --git a/cmd/root.go b/cmd/root.go index d22ab0586..17d298e07 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,8 +8,8 @@ import ( "os" "strings" + "github.com/TheThingsNetwork/ttn/utils/cli" "github.com/apex/log" - "github.com/apex/log/handlers/cli" "github.com/spf13/cobra" "github.com/spf13/viper" ) diff --git a/utils/cli/cli.go b/utils/cli/cli.go new file mode 100644 index 000000000..2c706d18d --- /dev/null +++ b/utils/cli/cli.go @@ -0,0 +1,101 @@ +package cli + +import ( + "fmt" + "io" + "sort" + "sync" + + "github.com/apex/log" +) + +// colors. +const ( + none = 0 + red = 31 + green = 32 + yellow = 33 + blue = 34 + gray = 90 +) + +// Colors mapping. +var Colors = [...]int{ + log.DebugLevel: gray, + log.InfoLevel: blue, + log.WarnLevel: yellow, + log.ErrorLevel: red, + log.FatalLevel: red, +} + +// Strings mapping. +var Strings = [...]string{ + log.DebugLevel: "DEBUG", + log.InfoLevel: "INFO", + log.WarnLevel: "WARN", + log.ErrorLevel: "ERROR", + log.FatalLevel: "FATAL", +} + +// field used for sorting. +type field struct { + Name string + Value interface{} +} + +// by sorts projects by call count. +type byName []field + +func (a byName) Len() int { return len(a) } +func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } + +// Handler implementation. +type Handler struct { + mu sync.Mutex + Writer io.Writer +} + +// New handler. +func New(w io.Writer) *Handler { + return &Handler{ + Writer: w, + } +} + +// HandleLog implements log.Handler. +func (h *Handler) HandleLog(e *log.Entry) error { + color := Colors[e.Level] + level := Strings[e.Level] + + var fields []field + + for k, v := range e.Fields { + fields = append(fields, field{k, v}) + } + + sort.Sort(byName(fields)) + + h.mu.Lock() + defer h.mu.Unlock() + + fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-25s", color, level, e.Message) + + for _, f := range fields { + var value interface{} + switch t := f.Value.(type) { + case []byte: // addresses and EUIs are []byte + value = fmt.Sprintf("%X", t) + case [21]byte: // bundle IDs [21]byte + value = fmt.Sprintf("%X-%X-%X-%X", t[0], t[1:9], t[9:17], t[17:]) + default: + value = f.Value + } + + fmt.Fprintf(h.Writer, " \033[%dm%s\033[0m=%v", color, f.Name, value) + } + + fmt.Fprintln(h.Writer) + + return nil +} From 47382c9d6d50948162e5057faa92e4aa86957128 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 15:11:49 +0100 Subject: [PATCH 1211/2266] Store gateway id in UDP connection --- core/adapters/udp/connection.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/adapters/udp/connection.go b/core/adapters/udp/connection.go index eed7bee0a..7fca8f633 100644 --- a/core/adapters/udp/connection.go +++ b/core/adapters/udp/connection.go @@ -10,12 +10,14 @@ import ( ) type replier interface { + DestinationID() []byte WriteToUplink(data []byte) error WriteToDownlink(data []byte) error } type gatewayConn struct { sync.RWMutex + gatewayID []byte conn *net.UDPConn uplinkAddr *net.UDPAddr downlinkAddr *net.UDPAddr @@ -39,6 +41,10 @@ func (c *gatewayConn) SetDownlinkAddr(addr *net.UDPAddr) { c.downlinkAddr = addr } +func (c *gatewayConn) DestinationID() []byte { + return c.gatewayID +} + func (c *gatewayConn) WriteToUplink(data []byte) error { c.RLock() defer c.RUnlock() @@ -78,7 +84,7 @@ func (p *gatewayPool) GetOrCreate(gatewayID []byte) *gatewayConn { var id [8]byte copy(id[:], gatewayID) if _, ok := p.connections[id]; !ok { - p.connections[id] = &gatewayConn{} + p.connections[id] = &gatewayConn{gatewayID: gatewayID} } return p.connections[id] } From b0bf473cb98b4388796eb0bba73d9c67643f53b3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 15:12:27 +0100 Subject: [PATCH 1212/2266] Logging in Router --- core/adapters/udp/conversions.go | 2 - core/adapters/udp/udp.go | 86 ++++++++++++++++++-------------- core/components/router/router.go | 77 ++++++++++++++++------------ 3 files changed, 94 insertions(+), 71 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 892756d5f..2379309c1 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -120,8 +120,6 @@ func newTXPK(payload encoding.BinaryMarshaler, metadata *core.Metadata, ctx log. data := base64.RawStdEncoding.EncodeToString(raw) txpk := semtech.TXPK{Data: pointer.String(data)} - ctx.WithField("Metadata", *metadata).Debug("Injecting metadata") - // Step 3, copy every compatible metadata from the packet to the TXPK packet. // We are possibly loosing information here. injectMetadata(&txpk, *metadata) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index c59b948fd..aadb47c10 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -93,12 +93,13 @@ func (a adapter) listen(netAddr string, conn *net.UDPConn, maxReconnectionDelay break } + ctx := a.Ctx.WithField("Source", addr.IP.String()) + // Handle the incoming datagram - a.Ctx.Debug("Incoming datagram") go func(data []byte, conn *net.UDPConn) { pkt := new(semtech.Packet) if err := pkt.UnmarshalBinary(data); err != nil { - a.Ctx.WithError(err).Debug("Unable to handle datagram") + ctx.WithError(err).Debug("Invalid datagram") } gtwConn := a.pool.GetOrCreate(pkt.GatewayId) @@ -115,7 +116,7 @@ func (a adapter) listen(netAddr string, conn *net.UDPConn, maxReconnectionDelay err = errors.New(errors.Implementation, "Unhandled packet type") } if err != nil { - a.Ctx.WithError(err).Debug("Unable to handle datagram") + ctx.WithError(err).Debug("Unable to handle datagram") } }(buf[:n], conn) } @@ -130,7 +131,8 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { stats.MarkMeter("semtech_adapter.pull_data") stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.pull_data", pkt.GatewayId)) stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_pull_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) - a.Ctx.Debug("Handle PULL_DATA") + ctx := a.Ctx.WithField("GatewayID", pkt.GatewayId) + ctx.Debug("Handle PULL_DATA") data, err := semtech.Packet{ Version: semtech.VERSION, @@ -138,8 +140,9 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { Identifier: semtech.PULL_ACK, }.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) + if err != nil || reply.WriteToDownlink(data) != nil { + ctx.Debug("Unable to send PULL_ACK") + return errors.New(errors.Operational, "Unable to send PUSH_ACK") } return reply.WriteToDownlink(data) @@ -159,14 +162,15 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { Token: pkt.Token, Identifier: semtech.PUSH_ACK, }.MarshalBinary() + if err != nil || reply.WriteToUplink(data) != nil || pkt.Payload == nil { - ctx.Debug("Unable to send ACK") - return errors.New(errors.Operational, "Unable to send ACK") + ctx.Debug("Unable to send PUSH_ACK") + return errors.New(errors.Operational, "Unable to send PUSH_ACK") } // Process Stat payload if pkt.Payload.Stat != nil { - ctx.Debug("PUSH_DATA contains a stats payload") + ctx.Debug("Handle stat") go a.Router.HandleStats(context.Background(), &core.StatsReq{ GatewayID: pkt.GatewayId, Metadata: extractMetadata(*pkt.Payload.Stat, new(core.StatsMetadata)).(*core.StatsMetadata), @@ -174,58 +178,61 @@ func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { } // Process rxpks payloads - cherr := make(chan error, len(pkt.Payload.RXPK)) wait := sync.WaitGroup{} - wait.Add(len(pkt.Payload.RXPK)) - ctx.WithField("Nb RXPK", len(pkt.Payload.RXPK)).Debug("Processing RXPK payloads") - for _, rxpk := range pkt.Payload.RXPK { - go func(rxpk semtech.RXPK) { - defer wait.Done() - if err := a.handleDataUp(rxpk, pkt.GatewayId, reply); err != nil { - ctx.WithError(err).Debug("Error while processing RXPK") - cherr <- err - } - }(rxpk) + + if len(pkt.Payload.RXPK) > 0 { + wait.Add(len(pkt.Payload.RXPK)) + ctx.Debug("Handle rxpk") + for _, rxpk := range pkt.Payload.RXPK { + go func(rxpk semtech.RXPK) { + defer wait.Done() + if err := a.handleDataUp(rxpk, pkt.GatewayId, reply); err != nil { + ctx.WithError(err).Debug("rxpk not processed") + } + }(rxpk) + } } // Retrieve any errors wait.Wait() - close(cherr) - return <-cherr + return nil } func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier) error { - itf, err := toLoRaWANPayload(rxpk, gid, a.Ctx) + ctx := a.Ctx.WithField("GatewayID", gid) + + itf, err := toLoRaWANPayload(rxpk, gid, ctx) if err != nil { - a.Ctx.WithError(err).Debug("Invalid up RXPK packet") + ctx.WithError(err).Debug("Invalid up RXPK packet") return errors.New(errors.Structural, err) } switch itf.(type) { case *core.DataRouterReq: + ctx.Debug("Handle uplink data") resp, err := a.Router.HandleData(context.Background(), itf.(*core.DataRouterReq)) if err != nil { - a.Ctx.WithError(err).Debug("Router failed to process uplink") return errors.New(errors.Operational, err) } return a.handleDataDown(resp, reply) case *core.JoinRouterReq: + ctx.Debug("Handle join request") resp, err := a.Router.HandleJoin(context.Background(), itf.(*core.JoinRouterReq)) if err != nil { - a.Ctx.WithError(err).Debug("Router failed to process join request") return errors.New(errors.Operational, err) } return a.handleJoinAccept(resp, reply) default: - a.Ctx.Warn("Unhandled LoRaWAN Payload type") + ctx.Warn("Unhandled LoRaWAN Payload type") return errors.New(errors.Implementation, "Unhandled LoRaWAN Payload type") } } func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { - a.Ctx.Debug("Handle Downlink from router") + ctx := a.Ctx.WithField("GatewayID", reply.DestinationID()) + if resp == nil || resp.Payload == nil { // No response - a.Ctx.Debug("No response to send") + ctx.Debug("No response to send") return nil } @@ -234,46 +241,49 @@ func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { return errors.New(errors.Structural, err) } - txpk, err := newTXPK(payload, resp.Metadata, a.Ctx) + txpk, err := newTXPK(payload, resp.Metadata, ctx) if err != nil { - a.Ctx.WithError(err).Debug("Unable to interpret downlink") return errors.New(errors.Structural, err) } - a.Ctx.Debug("Creating new downlink response") + ctx.WithFields(log.Fields{ + "Metadata": resp.Metadata, + "DevAddr": resp.Payload.MACPayload.FHDR.DevAddr, + }).Debug("Send txpk") + data, err := semtech.Packet{ Version: semtech.VERSION, Identifier: semtech.PULL_RESP, Payload: &semtech.Payload{TXPK: &txpk}, }.MarshalBinary() if err != nil { - a.Ctx.WithError(err).Debug("Unable to create semtech packet with TXPK") return errors.New(errors.Structural, err) } + return reply.WriteToDownlink(data) } func (a adapter) handleJoinAccept(resp *core.JoinRouterRes, reply replier) error { - a.Ctx.Debug("Handle Join-Accept from router") + ctx := a.Ctx.WithField("GatewayID", reply.DestinationID()) + if resp == nil || resp.Payload == nil { - a.Ctx.Debug("Invalid response") return errors.New(errors.Structural, "Invalid Join-Accept response. Expected a payload") } txpk, err := newTXPK(straightMarshaler(resp.Payload.Payload), resp.Metadata, a.Ctx) if err != nil { - a.Ctx.WithError(err).Debug("Unable to interpret Join-Accept") return errors.New(errors.Structural, err) } - a.Ctx.Debug("Creating a new join-accept response") + ctx.WithField("Metadata", resp.Metadata).Debug("Send join-accept") + data, err := semtech.Packet{ Version: semtech.VERSION, Identifier: semtech.PULL_RESP, Payload: &semtech.Payload{TXPK: &txpk}, }.MarshalBinary() if err != nil { - a.Ctx.WithError(err).Debug("Unable to create semtech packet with TXPK") + return errors.New(errors.Structural, err) } return reply.WriteToDownlink(data) } diff --git a/core/components/router/router.go b/core/components/router/router.go index 224c56bb6..9e1cda778 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -86,9 +86,12 @@ func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.S } // HandleJoin implements the core.RouterClient interface -func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (routerRes *core.JoinRouterRes, err error) { +func (r component) HandleJoin(_ context.Context, req *core.JoinRouterReq) (routerRes *core.JoinRouterRes, err error) { + ctx := r.Ctx.WithField("GatewayID", req.GatewayID) + stats.MarkMeter("router.join.in") + if len(req.GatewayID) != 8 || len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.DevNonce) != 2 || len(req.MIC) != 4 || req.Metadata == nil { - r.Ctx.Debug("Invalid request. Parameters are incorrects") + ctx.Debug("Invalid request. Parameters are incorrects") return new(core.JoinRouterRes), errors.New(errors.Structural, "Invalid Request") } @@ -98,6 +101,13 @@ func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (rou return new(core.JoinRouterRes), err } + ctx = ctx.WithFields(log.Fields{ + "AppEUI": req.AppEUI, + "DevEUI": req.DevEUI, + }) + + ctx.WithField("Metadata", req.Metadata).Debug("Inject Metadata") + // Broadcast the join request bpacket := &core.JoinBrokerReq{ AppEUI: req.AppEUI, @@ -114,9 +124,11 @@ func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (rou // Update Gateway Duty cycle with response metadata res := response.(*core.JoinBrokerRes) if res == nil || res.Payload == nil { // No response - r.Ctx.Debug("No Join-Accept received") + ctx.Debug("No join-accept received") return new(core.JoinRouterRes), nil } + ctx.Debug("Handle join-accept") + if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { return new(core.JoinRouterRes), err } @@ -124,23 +136,23 @@ func (r component) HandleJoin(ctx context.Context, req *core.JoinRouterReq) (rou } // HandleData implements the core.RouterClient interface -func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*core.DataRouterRes, error) { +func (r component) HandleData(_ context.Context, req *core.DataRouterReq) (*core.DataRouterRes, error) { // Get some logs / analytics - r.Ctx.Debug("Handling uplink packet") + ctx := r.Ctx.WithField("GatewayID", req.GatewayID) stats.MarkMeter("router.uplink.in") // Validate coming data _, _, fhdr, _, err := core.ValidateLoRaWANData(req.Payload) if err != nil { - r.Ctx.WithError(err).Debug("Invalid request payload") + ctx.WithError(err).Debug("Invalid request payload") return new(core.DataRouterRes), errors.New(errors.Structural, err) } if req.Metadata == nil { - r.Ctx.Debug("Invalid request Metadata") + ctx.Debug("Invalid request Metadata") return new(core.DataRouterRes), errors.New(errors.Structural, "Missing mandatory Metadata") } if len(req.GatewayID) != 8 { - r.Ctx.Debug("Invalid request GatewayID") + ctx.Debug("Invalid request GatewayID") return new(core.DataRouterRes), errors.New(errors.Structural, "Invalid gatewayID") } @@ -149,15 +161,20 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co if err != nil { return new(core.DataRouterRes), err } + ctx.WithFields(log.Fields{ + "DevAddr": fhdr.DevAddr, + "Metadata": req.Metadata, + }).Debug("Inject Metadata") + + ctx = r.Ctx.WithField("DevAddr", fhdr.DevAddr) // Lookup for an existing broker entries, err := r.BrkStorage.read(fhdr.DevAddr) if err != nil && err.(errors.Failure).Nature != errors.NotFound { - r.Ctx.Warn("Database lookup failed") + ctx.Warn("Database lookup failed") return new(core.DataRouterRes), errors.New(errors.Operational, err) } shouldBroadcast := err != nil - r.Ctx.WithField("Should Broadcast?", shouldBroadcast).Debug("Storage Lookup done") bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} @@ -166,19 +183,19 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co if shouldBroadcast { // No Recipient available -> broadcast stats.MarkMeter("router.broadcast") - r.Ctx.Debug("Broadcasting packet to all known brokers") + ctx.Debug("Broadcast to brokers") response, err = r.send(bpacket, true, r.Brokers...) } else { // Recipients are available stats.MarkMeter("router.send") var brokers []core.BrokerClient - r.Ctx.Debug("Forwarding packet to known broker(s)") + ctx.Debug("Send to known brokers") for _, e := range entries { brokers = append(brokers, r.Brokers[e.BrokerIndex]) } response, err = r.send(bpacket, false, brokers...) if err != nil && err.(errors.Failure).Nature == errors.NotFound { - r.Ctx.Debug("No response from known broker(s). Trying again with broadcast") + ctx.Debug("Retry with broadcast") // Might be a collision with the dev addr, we better broadcast response, err = r.send(bpacket, true, r.Brokers...) } @@ -188,8 +205,10 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co if err != nil { switch err.(errors.Failure).Nature { case errors.NotFound: + ctx.Debug("All brokers rejected") stats.MarkMeter("router.uplink.negative_broker_response") default: + ctx.WithError(err).Warn("Failed forward to broker") stats.MarkMeter("router.uplink.bad_broker_response") } return new(core.DataRouterRes), err @@ -197,9 +216,10 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co res := response.(*core.DataBrokerRes) if res == nil || res.Payload == nil { // No response - r.Ctx.Debug("Packet sent. No downlink received.") + ctx.Debug("No downlink response") return new(core.DataRouterRes), nil } + ctx.WithField("GatewayID", req.GatewayID).Debug("Handle downlink response") // Update Gateway Duty cycle with response metadata if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { @@ -211,9 +231,10 @@ func (r component) HandleData(ctx context.Context, req *core.DataRouterReq) (*co } func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Metadata, error) { + ctx := r.Ctx.WithField("GatewayID", gid) + // Add Gateway location metadata if entry, err := r.GtwStorage.read(gid); err == nil { - r.Ctx.Debug("Adding Gateway Metadata to packet") metadata.Latitude = entry.Metadata.Latitude metadata.Longitude = entry.Metadata.Longitude metadata.Altitude = entry.Metadata.Altitude @@ -222,30 +243,31 @@ func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Met // Add Gateway duty metadata cycles, err := r.DutyManager.Lookup(gid) if err != nil { - r.Ctx.WithError(err).Debug("Unable to get any metadata about duty-cycles") + ctx.WithError(err).Debug("No duty-cycle metadata available") cycles = make(dutycycle.Cycles) } sb1, err := dutycycle.GetSubBand(float32(metadata.Frequency)) if err != nil { stats.MarkMeter("router.uplink.not_supported") - return nil, errors.New(errors.Structural, "Unhandled uplink signal frequency") + ctx.WithField("Frequency", metadata.Frequency).Debug("Unsupported frequency") + return nil, errors.New(errors.Structural, "Unsupported frequency") } rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) metadata.DutyRX1, metadata.DutyRX2 = uint32(rx1), uint32(rx2) - r.Ctx.WithField("DutyRX1", rx1).WithField("DutyRX2", rx2).Debug("Adding Duty values for RX1 & RX2") + ctx.WithField("Frequency", metadata.Frequency).WithField("Rx1", rx1).WithField("Rx2", rx2).Debug("Set duty cycles") return &metadata, nil } func (r component) handleDown(gatewayID []byte, metadata *core.Metadata) error { - r.Ctx.Debug("Handling downlink response") + ctx := r.Ctx.WithField("GatewayID", gatewayID) // Update downlink metadata for the related gateway if metadata == nil { stats.MarkMeter("router.uplink.bad_broker_response") - r.Ctx.Warn("Missing mandatory Metadata in response") + ctx.Warn("Missing mandatory Metadata in response") return errors.New(errors.Structural, "Missing mandatory Metadata in response") } freq := metadata.Frequency @@ -253,7 +275,7 @@ func (r component) handleDown(gatewayID []byte, metadata *core.Metadata) error { codr := metadata.CodingRate size := metadata.PayloadSize if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { - r.Ctx.WithError(err).Debug("Unable to update DutyManager") + ctx.WithError(err).Debug("Unable to update DutyManager") return errors.New(errors.Operational, err) } return nil @@ -262,8 +284,6 @@ func (r component) handleDown(gatewayID []byte, metadata *core.Metadata) error { func (r component) send(req interface{}, isBroadcast bool, brokers ...core.BrokerClient) (interface{}, error) { // Define a more helpful context nb := len(brokers) - ctx := r.Ctx.WithField("Nb Brokers", nb) - ctx.Debug("Sending Packet") stats.UpdateHistogram("router.send_recipients", int64(nb)) // Prepare ground for parrallel requests @@ -295,7 +315,6 @@ func (r component) send(req interface{}, isBroadcast bool, brokers ...core.Broke // Handle error if err != nil { - ctx.WithField("index", index).WithError(err).Debug("Error while contacting broker") if strings.Contains(err.Error(), string(errors.NotFound)) { // FIXME Find a better way to analyze the error cherr <- errors.New(errors.NotFound, "Broker not responsible for the node") return @@ -324,26 +343,22 @@ func (r component) send(req interface{}, isBroadcast bool, brokers ...core.Broke for err := range cherr { if err.(errors.Failure).Nature != errors.NotFound { errored++ - ctx.WithError(err).Warn("Failed to contact broker") + r.Ctx.WithError(err).Warn("Unexpected response") } else { notFound++ - ctx.WithError(err).Debug("Packet destination not found") } } // Collect response if len(chresp) > 1 { - r.Ctx.Warn("Received too many positive answers") - return nil, errors.New(errors.Behavioural, "Received too many positive answers") + return nil, errors.New(errors.Behavioural, "Too many positive answers") } if len(chresp) == 0 && errored > 0 { - r.Ctx.Debug("No positive response but got errored response(s)") - return nil, errors.New(errors.Operational, "No positive response from recipients but got unexpected answer") + return nil, errors.New(errors.Operational, "Unexpected response") } if len(chresp) == 0 && notFound > 0 { - r.Ctx.Debug("No available recipient found") return nil, errors.New(errors.NotFound, "No available recipient found") } From 997a9b2aae425e5c25e9e86e940cac433b3a2082 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 16:51:04 +0100 Subject: [PATCH 1213/2266] More Logging improvements --- core/adapters/mqtt/client.go | 3 -- core/components/broker/broker.go | 31 ++++++++++------ core/components/handler/handler.go | 58 ++++++++++++++++-------------- utils/cli/cli.go | 2 +- 4 files changed, 54 insertions(+), 40 deletions(-) diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go index 300f15ada..89ce660a8 100644 --- a/core/adapters/mqtt/client.go +++ b/core/adapters/mqtt/client.go @@ -73,17 +73,14 @@ func monitorClient(id string, netAddr string, chcmd <-chan interface{}, ctx log. switch cmd.(type) { case cmdPublish: cmd := cmd.(cmdPublish) - ctx.Debug("Client received new publication order") cmd.cherr <- cli.Publish(cmd.options) case cmdTerminate: cmd := cmd.(cmdTerminate) - ctx.Debug("Client received termination order") cli.Terminate() cli = nil cmd.cherr <- nil case cmdClient: cmd := cmd.(cmdClient) - ctx.Debug("Replacing client with another one") cli.Terminate() cli = cmd.options cmd.cherr <- nil diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index ee1ccdec9..d33c02d39 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -90,14 +90,18 @@ func (b component) Start() error { // HandleJoin implements the core.BrokerServer interface func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*core.JoinBrokerRes, error) { - b.Ctx.Debug("Handling join request") - // Validate incoming data if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || len(req.MIC) != 4 || req.Metadata == nil { - b.Ctx.Debug("Invalid request. Parameters are incorrect.") + b.Ctx.Debug("Invalid join request. Parameters are incorrect.") return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid parameters") } - ctx := b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI) + + ctx := b.Ctx.WithFields(log.Fields{ + "AppEUI": req.AppEUI, + "DevEUI": req.DevEUI, + }) + + ctx.Debug("Handle join request") // Check if devNonce already referenced appEntry, err := b.AppStorage.read(req.AppEUI) @@ -125,7 +129,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c } } if found { - ctx.Debug("Unable to proceed join request. DevNonce already used by the past.") + ctx.Debug("DevNonce already used in the past") return new(core.JoinBrokerRes), errors.New(errors.Structural, "DevNonce used by the past") } @@ -150,7 +154,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c // Handle the response if res == nil || res.Payload == nil { - ctx.Debug("No Join-Accept") + ctx.Debug("No join-accept received") return new(core.JoinBrokerRes), nil } @@ -159,6 +163,8 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c return new(core.JoinBrokerRes), errors.New(errors.Operational, "Invalid response from handler") } + ctx.WithField("DevAddr", res.DevAddr).Debug("Handle join-accept") + // Update the DevNonce nonces.DevNonces = append(nonces.DevNonces, req.DevNonce) if uint(len(nonces.DevNonces)) > b.MaxDevNonces { @@ -194,7 +200,6 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*core.DataBrokerRes, error) { // Get some logs / analytics stats.MarkMeter("broker.uplink.in") - b.Ctx.Debug("Handling uplink packet") // Validate incoming data uplinkPayload, err := core.NewLoRaWANData(req.Payload, true) @@ -203,7 +208,9 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c return new(core.DataBrokerRes), errors.New(errors.Structural, err) } devAddr := req.Payload.MACPayload.FHDR.DevAddr // No nil ref, ensured by NewLoRaWANData() + ctx := b.Ctx.WithField("DevAddr", devAddr) + ctx.Debug("Handle uplink") // Check whether we should handle it entries, err := b.NetworkController.read(devAddr) @@ -256,7 +263,11 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c if ok { mEntry = &entry stats.MarkMeter("broker.uplink.handler_lookup.mic_match") - ctx.WithField("handler", string(entry.Dialer.MarshalSafely())).Debug("MIC check succeeded") + ctx = ctx.WithFields(log.Fields{ + "DevEUI": mEntry.DevEUI, + "Handler": string(entry.Dialer.MarshalSafely()), + }) + ctx.Debug("MIC check succeeded") break // We stop at the first valid check ... } } @@ -303,12 +314,12 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // No response, we stop here and propagate the "no answer". // In case of confirmed data, the handler is in charge of creating the confirmation if resp == nil || resp.Payload == nil { - ctx.Debug("Packet successfully sent. There's no downlink.") + ctx.Debug("No downlink response") return new(core.DataBrokerRes), nil } + ctx.Debug("Handle downlink response") // If a response was sent, i.e. a downlink data, we need to compute the right MIC - ctx.Debug("Packet successfully sent. Handling downlink response") downlinkPayload, err := core.NewLoRaWANData(resp.Payload, false) if err != nil { ctx.WithError(err).Debug("Unable to interpret LoRaWAN downlink datagram") diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index e93ddf9d5..23cb7e2f4 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -163,13 +163,21 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* return new(core.JoinHandlerRes), errors.New(errors.Structural, "Invalid parameters") } + ctx := h.Ctx.WithFields(log.Fields{ + "AppEUI": req.AppEUI, + "DevEUI": req.DevEUI, + }) + + ctx.Debug("Handle join request") + // 1. Lookup for the associated AppKey - h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) if err != nil { + ctx.Debug("Trying to activate unknown device") return new(core.JoinHandlerRes), err } if entry.AppKey == nil { // Trying to activate an ABP device + ctx.Debug("Trying to activate personalized device") return new(core.JoinHandlerRes), errors.New(errors.Behavioural, "Trying to activate a personalized device") } @@ -183,7 +191,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* copy(joinPayload.DevNonce[:], req.DevNonce) payload.MACPayload = &joinPayload if ok, err := payload.ValidateMIC(lorawan.AES128Key(*entry.AppKey)); err != nil || !ok { - h.Ctx.WithError(err).Debug("Unable to validate MIC from incoming join-request") + ctx.WithError(err).Debug("Invalid join-request MIC") return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to validate MIC") } @@ -199,8 +207,8 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* copy(bundleID[:], buf.Bytes()) // 4. Send the actual bundle to the consumer - ctx := h.Ctx.WithField("BundleID", bundleID) - ctx.Debug("Define new bundle") + ctx.WithField("BundleID", bundleID).Debug("Define new bundle") + req.Metadata.Time = time.Now().Format(time.RFC3339Nano) h.ChBundles <- bundle{ ID: bundleID, @@ -216,7 +224,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* switch resp.(type) { case *core.JoinHandlerRes: stats.MarkMeter("handler.join.send_accept") - ctx.Debug("Sending Join-Accept") + ctx.Debug("Sending join-accept") return resp.(*core.JoinHandlerRes), nil case error: stats.MarkMeter("handler.join.error") @@ -231,7 +239,6 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* // HandleDataDown implements the core.HandlerServer interface func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { stats.MarkMeter("handler.downlink.in") - h.Ctx.Debug("Handle downlink message") // Unmarshal the given packet and see what gift we get @@ -256,7 +263,8 @@ func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandle return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid TTL") } - h.Ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Save downlink for later") + h.Ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Handle downlink - enqueue") + return new(core.DataDownHandlerRes), h.PktStorage.enqueue(pktEntry{ Payload: req.Payload, AppEUI: req.AppEUI, @@ -288,13 +296,18 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq } stats.MarkMeter("handler.uplink.data") + ctx := h.Ctx.WithFields(log.Fields{ + "AppEUI": req.AppEUI, + "DevEUI": req.DevEUI, + }) + + ctx.Debug("Handle Uplink") + // 1. Lookup for the associated AppSKey + Application - h.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI).Debug("Perform lookup") entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) if err != nil { return new(core.DataUpHandlerRes), err } - h.Ctx.Debugf("%+v", entry) if len(entry.DevAddr) != 4 { // Not Activated return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Tried to send uplink on non-activated device") } @@ -311,8 +324,7 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq copy(bundleID[:], buf.Bytes()) // 4. Send the actual bundle to the consumer - ctx := h.Ctx.WithField("BundleID", bundleID) - ctx.Debug("Define new bundle") + ctx.WithField("BundleID", bundleID).Debug("Define new bundle") req.Metadata.Time = time.Now().Format(time.RFC3339Nano) h.ChBundles <- bundle{ ID: bundleID, @@ -333,15 +345,15 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq case *core.DataUpHandlerRes: stats.MarkMeter("handler.uplink.ack.with_response") stats.MarkMeter("handler.downlink.out") - ctx.Debug("Sending downlink packet as response.") + ctx.Debug("Sending downlink packet as response") return resp.(*core.DataUpHandlerRes), nil case error: stats.MarkMeter("handler.uplink.error") - ctx.WithError(resp.(error)).Warn("Error while processing dowlink.") + ctx.WithError(resp.(error)).Warn("Error while processing dowlink") return new(core.DataUpHandlerRes), resp.(error) default: stats.MarkMeter("handler.uplink.ack.without_response") - ctx.Debug("No response to send.") + ctx.Debug("No response to send") return new(core.DataUpHandlerRes), nil } } @@ -350,11 +362,8 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // It then flushes them once a given delay has passed since the reception of the first bundle. func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { ctx := h.Ctx.WithField("goroutine", "set consumer") - ctx.Debug("Starting packets buffering") + ctx.Debug("Starting set consumer") - // NOTE Processed is likely to grow quickly. One has to define a more efficient data stucture - // with a ttl for each entry. Processed is merely there to avoid late packets from being - // processed again. The TTL could be only of several seconds or minutes. buffers := make(map[[21]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles alarm := make(chan [21]byte) // Communication channel with subsequent alarms @@ -367,14 +376,14 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { h.Processed.Put(id[:]) // Register the last processed entry // Actually send the bundle to the be processed + ctx.WithField("BundleID", id).Debug("End buffering") go func(bundles []bundle) { chbundles <- bundles }(bundles) - ctx.WithField("BundleID", id).Debug("Consuming collected bundles") case b := <-chset: ctx = ctx.WithField("BundleID", b.ID) // Check if bundle has already been processed if h.Processed.Contains(b.ID[:]) { - ctx.Debug("Reject already processed bundle") + ctx.Debug("Already processed - Reject") go func(b bundle) { b.Chresp <- errors.New(errors.Behavioural, "Already processed") }(b) @@ -384,8 +393,8 @@ func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { // Add the bundle to the stack, and set the alarm if its the first bundles := append(buffers[b.ID], b) if len(bundles) == 1 { + ctx.Debug("Start buffering") go setAlarm(alarm, b.ID, bufferDelay) - ctx.Debug("Buffering started -> new alarm set") } buffers[b.ID] = bundles } @@ -406,7 +415,7 @@ func (h component) consumeBundles(chbundle <-chan []bundle) { browseBundles: for bundles := range chbundle { - ctx.WithField("BundleID", bundles[0].ID).Debug("Consume new bundle") + ctx.WithField("BundleID", bundles[0].ID).Debug("Consume bundle") if len(bundles) < 1 { continue browseBundles } @@ -437,7 +446,6 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da return } - ctx.Debug("Compute scores for each packet") for i, bundle := range bundles { packet := bundle.Packet.(*core.JoinHandlerReq) metadata = append(metadata, packet.Metadata) @@ -446,7 +454,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da // Check if at least one is available best := computer.Get(scores) - ctx.WithField("Best", best).Debug("Determine best recipient to reply") + ctx.WithField("Best", best).Debug("Determine best response gateway") if best == nil { h.abortConsume(errors.New(errors.Operational, "No gateway is available for an answer"), bundles) return @@ -587,9 +595,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu stats.MarkMeter("handler.uplink.out") // Now handle the downlink and respond to node - h.Ctx.Debug("Looking for downlink response") best := computer.Get(scores) - h.Ctx.WithField("Bundle", best).Debug("Determine best gateway") var downlink pktEntry if best != nil { // Avoid pulling when there's no gateway available for an answer downlink, err = h.PktStorage.dequeue(appEUI, devEUI) diff --git a/utils/cli/cli.go b/utils/cli/cli.go index 2c706d18d..acca9c68e 100644 --- a/utils/cli/cli.go +++ b/utils/cli/cli.go @@ -79,7 +79,7 @@ func (h *Handler) HandleLog(e *log.Entry) error { h.mu.Lock() defer h.mu.Unlock() - fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-25s", color, level, e.Message) + fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-40s", color, level, e.Message) for _, f := range fields { var value interface{} From 3af26cbc9aafad3c7c3d3782373938a3baeb0b49 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 17:03:10 +0100 Subject: [PATCH 1214/2266] Fix UDP adapter --- core/adapters/udp/udp.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go index aadb47c10..af1b82260 100644 --- a/core/adapters/udp/udp.go +++ b/core/adapters/udp/udp.go @@ -142,10 +142,10 @@ func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { if err != nil || reply.WriteToDownlink(data) != nil { ctx.Debug("Unable to send PULL_ACK") - return errors.New(errors.Operational, "Unable to send PUSH_ACK") + return errors.New(errors.Operational, "Unable to send PULL_ACK") } - return reply.WriteToDownlink(data) + return nil } // Handle a PUSH_DATA packet coming from a gateway From ef9a9f3bb49e7441e80d21343a3fa4e19e5b4f6d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 17:13:53 +0100 Subject: [PATCH 1215/2266] Remove Println --- core/components/handler/queue.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/components/handler/queue.go b/core/components/handler/queue.go index d54409824..2dfb692c4 100644 --- a/core/components/handler/queue.go +++ b/core/components/handler/queue.go @@ -4,7 +4,6 @@ package handler import ( - "fmt" "reflect" "sync" ) @@ -61,7 +60,6 @@ func (q *pq) Put(pid []byte) { // Remove discard an entry from the map func (q *pq) Remove(pid []byte) { q.Lock() - fmt.Println(pid) id, _ := q.fromPid(pid) delete(q.values, id) q.Unlock() From 763f7e78590142adb2a38654b69a5d31023f4782 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 17:14:58 +0100 Subject: [PATCH 1216/2266] Don't log the err when we know it's "Not Found" --- core/components/broker/broker.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index d33c02d39..52fb2e766 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -215,13 +215,12 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // Check whether we should handle it entries, err := b.NetworkController.read(devAddr) if err != nil { - ctx = ctx.WithError(err) switch err.(errors.Failure).Nature { case errors.NotFound: stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") ctx.Debug("Uplink device not found") default: - ctx.Warn("Database lookup failed") + ctx.WithError(err).Warn("Database lookup failed") } return new(core.DataBrokerRes), err } From c9b53eac9c8e065f3fbe917d26b2a1ad84ef555f Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 18:15:41 +0100 Subject: [PATCH 1217/2266] Aligned instruction with command --- ttnctl/cmd/device.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 58409f5bf..152d00321 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -78,7 +78,7 @@ var devicesCmd = &cobra.Command{ var devicesRegisterCmd = &cobra.Command{ Use: "register [DevEUI] [AppKey]", Short: "Create or Update registrations on the Handler", - Long: `ttnctl device register creates or updates an OTAA registration on the Handler`, + Long: `ttnctl devices register creates or updates an OTAA registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { ctx.Fatal("Insufficient arguments") @@ -117,7 +117,7 @@ var devicesRegisterCmd = &cobra.Command{ var devicesRegisterPersonalizedCmd = &cobra.Command{ Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", Short: "Create or Update ABP registrations on the Handler", - Long: `ttnctl device register creates or updates an ABP registration on the Handler`, + Long: `ttnctl devices register creates or updates an ABP registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 3 { ctx.Fatal("Insufficient arguments") From 0d763dc509f7cacbc3572197706b8e6e64534b16 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 25 Mar 2016 17:25:36 +0100 Subject: [PATCH 1218/2266] [log] Fix copy/paste mistake --- core/components/broker/brokerManager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 810d9a9dd..5cda64e89 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -62,7 +62,7 @@ func (b component) ValidateOTAA(bctx context.Context, req *core.ValidateOTAABrok // UpsertABP implements the core.BrokerManager interface func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) (*core.UpsertABPBrokerRes, error) { - b.Ctx.Debug("Handle ValidateOTAA request") + b.Ctx.Debug("Handle UpsertABP request") // 1. Validate the request re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") From c0885ce778584c09eadb1a677ff533fe8252353f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Mar 2016 12:02:12 +0200 Subject: [PATCH 1219/2266] Add TTNCTL prefix for ttnctl environment --- ttnctl/cmd/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 8641fa42f..1603abc46 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -76,6 +76,7 @@ func initConfig() { viper.SetConfigName(".ttnctl") viper.AddConfigPath("$HOME") + viper.SetEnvPrefix("ttnctl") // set environment prefix viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() From baabb3639101a53b381261915be8c25655dc7fe5 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 25 Mar 2016 22:13:58 +0100 Subject: [PATCH 1220/2266] [ttnctl-user] Register user from ttnctl, references #91 --- ttnctl/cmd/users.go | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 ttnctl/cmd/users.go diff --git a/ttnctl/cmd/users.go b/ttnctl/cmd/users.go new file mode 100644 index 000000000..e317fc60c --- /dev/null +++ b/ttnctl/cmd/users.go @@ -0,0 +1,67 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net/http" + "net/url" + + "github.com/howeyc/gopass" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// usersCmd represents the users command +var usersCmd = &cobra.Command{ + Use: "users", + Short: "Manage users on the account server", + Long: `ttnctl users allows you to manage users`, + Run: func(cmd *cobra.Command, args []string) { + cmd.Help() + }, +} + +var usersCreateCmd = &cobra.Command{ + Use: "create [e-mail]", + Short: "Create a new user", + Long: `ttnctl users create allows you to create a new user`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + ctx.Fatal("Insufficient arguments") + } + + email := args[0] + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal("Invalid password") + } + + uri := fmt.Sprintf("http://%s/register", viper.GetString("ttn-account-server")) + values := url.Values{ + "email": {email}, + "password": {string(password)}, + } + res, err := http.PostForm(uri, values) + if err != nil { + ctx.WithError(err).Fatal("Registration failed") + } + defer res.Body.Close() + + if res.StatusCode != http.StatusCreated { + ctx.Fatalf("Registration failed: %d %s", res.StatusCode, res.Status) + } + + ctx.Info("User created") + }, +} + +func init() { + RootCmd.AddCommand(usersCmd) + usersCmd.AddCommand(usersCreateCmd) + + usersCmd.PersistentFlags().String("ttn-account-server", "account.thethings.network", "The Things Network OAuth 2.0 account server") + viper.BindPFlag("ttn-account-server", usersCmd.PersistentFlags().Lookup("ttn-account-server")) +} From 08baa6211e2616fd8ac7823b0c1bc57bd6335a99 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 17:58:42 +0100 Subject: [PATCH 1221/2266] [ttnctl-user] Login and store token in file --- ttnctl/cmd/users.go | 95 ++++++++++++++++++++++++++++++++++++++++---- ttnctl/util/auth.go | 97 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 ttnctl/util/auth.go diff --git a/ttnctl/cmd/users.go b/ttnctl/cmd/users.go index e317fc60c..992b2f3d2 100644 --- a/ttnctl/cmd/users.go +++ b/ttnctl/cmd/users.go @@ -4,29 +4,50 @@ package cmd import ( + "encoding/json" "fmt" "net/http" "net/url" + "strings" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" "github.com/spf13/cobra" "github.com/spf13/viper" ) +type token struct { + AccessToken string `json:"access_token"` + Error string `json:"error"` + ErrorDescription string `json:"error_description"` +} + // usersCmd represents the users command var usersCmd = &cobra.Command{ - Use: "users", - Short: "Manage users on the account server", - Long: `ttnctl users allows you to manage users`, + Use: "user", + Short: "Show the current user", + Long: `ttnctl user shows the current logged on user`, Run: func(cmd *cobra.Command, args []string) { - cmd.Help() + t, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + + if t == nil { + ctx.Warn("No login found") + return + } + + // TODO: Validate token + + ctx.Infof("Logged on as %s", t.Email) }, } var usersCreateCmd = &cobra.Command{ Use: "create [e-mail]", Short: "Create a new user", - Long: `ttnctl users create allows you to create a new user`, + Long: `ttnctl user create allows you to create a new user`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { ctx.Fatal("Insufficient arguments") @@ -39,7 +60,7 @@ var usersCreateCmd = &cobra.Command{ ctx.Fatal("Invalid password") } - uri := fmt.Sprintf("http://%s/register", viper.GetString("ttn-account-server")) + uri := fmt.Sprintf("%s/register", viper.GetString("ttn-account-server")) values := url.Values{ "email": {email}, "password": {string(password)}, @@ -58,10 +79,70 @@ var usersCreateCmd = &cobra.Command{ }, } +var usersLoginCmd = &cobra.Command{ + Use: "login [e-mail]", + Short: "Login", + Long: `ttnctl user login allows you to login`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + ctx.Fatal("Insufficient arguments") + } + + email := args[0] + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal("Invalid password") + } + + server := viper.GetString("ttn-account-server") + uri := fmt.Sprintf("%s/token", server) + values := url.Values{ + "grant_type": {"password"}, + "username": {email}, + "password": {string(password)}, + } + req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) + if err != nil { + ctx.WithError(err).Fatal("Create request failed") + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth("ttnctl", "") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Request failed") + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + var t token + if err := decoder.Decode(&t); err != nil { + ctx.WithError(err).Fatal("Failed to parse response") + } + + if resp.StatusCode != http.StatusOK { + if t.Error != "" { + ctx.Fatalf("Request failed: %s", t.ErrorDescription) + } else { + ctx.Fatalf("Request failed: %s", resp.Status) + } + } + + ctx.Infof("Logged in as %s", email) + + if err := util.SaveAuth(server, email, t.AccessToken); err != nil { + ctx.WithError(err).Error("Failed to save login") + } + }, +} + func init() { RootCmd.AddCommand(usersCmd) usersCmd.AddCommand(usersCreateCmd) + usersCmd.AddCommand(usersLoginCmd) - usersCmd.PersistentFlags().String("ttn-account-server", "account.thethings.network", "The Things Network OAuth 2.0 account server") + usersCmd.PersistentFlags().String("ttn-account-server", "https://account.thethings.network", "The Things Network OAuth 2.0 account server") viper.BindPFlag("ttn-account-server", usersCmd.PersistentFlags().Lookup("ttn-account-server")) } diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go new file mode 100644 index 000000000..7f15dc860 --- /dev/null +++ b/ttnctl/util/auth.go @@ -0,0 +1,97 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "encoding/json" + "io/ioutil" + "os" + "os/user" + "path" +) + +const ( + authsFileName = ".ttnctl/auths.json" + authsFilePerm = 0600 +) + +// Auth represents an authentication token +type Auth struct { + Token string `json:"token"` + Email string `json:"email"` +} + +type auths struct { + Auths map[string]*Auth `json:"auths"` +} + +// LoadAuth loads the authentication token for the specified server +func LoadAuth(server string) (*Auth, error) { + a, err := loadAuths() + if err != nil { + return nil, err + } + return a.Auths[server], nil +} + +// SaveAuth saves the authentication token for the specified server and e-mail +func SaveAuth(server, email, token string) error { + a, err := loadAuths() + // Ignore error - just create new structure + if err != nil || a == nil { + a = &auths{} + } + + // Initialize the map if not exists and add the token + if a.Auths == nil { + a.Auths = make(map[string]*Auth) + } + a.Auths[server] = &Auth{token, email} + + // Marshal and write to disk + buff, err := json.Marshal(&a) + if err != nil { + return err + } + filename, err := getAuthsFilename() + if err != nil { + return err + } + if err := os.MkdirAll(path.Dir(filename), 0755); err != nil { + return err + } + if err := ioutil.WriteFile(filename, buff, authsFilePerm); err != nil { + return err + } + return nil +} + +// loadAuths loads the authentication tokens. This function always returns an +// empty structure if the file does not exist. +func loadAuths() (*auths, error) { + filename, err := getAuthsFilename() + if err != nil { + return nil, err + } + if _, err := os.Stat(filename); os.IsNotExist(err) { + return &auths{}, nil + } + buff, err := ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + var a auths + if err := json.Unmarshal(buff, &a); err != nil { + return nil, err + } + return &a, nil +} + +func getAuthsFilename() (string, error) { + u, err := user.Current() + if err != nil { + return "", err + } + return path.Join(u.HomeDir, authsFileName), nil +} From 814df2abaaa58f2120b0ae99accc38598641b840 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 18:25:13 +0100 Subject: [PATCH 1222/2266] [ttnctl-user] Integrated login functionality and got rid of app-token flag --- ttnctl/cmd/device.go | 31 +++++++++++++++++++++++++++---- ttnctl/cmd/root.go | 5 ++--- ttnctl/cmd/users.go | 3 --- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 152d00321..4fd23b310 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -36,10 +36,17 @@ var devicesCmd = &cobra.Command{ ctx.Fatalf("Invalid AppEUI: %s", err) } - manager := getHandlerManager() + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + manager := getHandlerManager() res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ - Token: viper.GetString("app-token"), + Token: auth.Token, AppEUI: appEUI, }) if err != nil { @@ -99,9 +106,17 @@ var devicesRegisterCmd = &cobra.Command{ ctx.Fatalf("Invalid AppKey: %s", err) } + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + manager := getHandlerManager() res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ - Token: viper.GetString("app-token"), + Token: auth.Token, AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, @@ -143,9 +158,17 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ ctx.Fatalf("Invalid AppSKey: %s", err) } + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + manager := getHandlerManager() res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ - Token: viper.GetString("app-token"), + Token: auth.Token, AppEUI: appEUI, DevAddr: devAddr, AppSKey: appSKey, diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 1603abc46..e1b17c0a1 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -63,9 +63,8 @@ func init() { RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) - RootCmd.PersistentFlags().String("app-token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJUVE4tSEFORExFUi0xIiwiaXNzIjoiVGhlVGhpbmdzVGhlTmV0d29yayIsInN1YiI6IjAxMDIwMzA0MDUwNjA3MDgifQ.zMHNXAVgQj672lwwDVmfYshpMvPwm6A8oNWJ7teGS2A", "The app Token to use") - viper.BindPFlag("app-token", RootCmd.PersistentFlags().Lookup("app-token")) - + RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethings.network", "The address of the OAuth 2.0 server") + viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) } // initConfig reads in config file and ENV variables if set. diff --git a/ttnctl/cmd/users.go b/ttnctl/cmd/users.go index 992b2f3d2..73a911c22 100644 --- a/ttnctl/cmd/users.go +++ b/ttnctl/cmd/users.go @@ -142,7 +142,4 @@ func init() { RootCmd.AddCommand(usersCmd) usersCmd.AddCommand(usersCreateCmd) usersCmd.AddCommand(usersLoginCmd) - - usersCmd.PersistentFlags().String("ttn-account-server", "https://account.thethings.network", "The Things Network OAuth 2.0 account server") - viper.BindPFlag("ttn-account-server", usersCmd.PersistentFlags().Lookup("ttn-account-server")) } From 014f455de5912dae3711543c35db82406fb30810 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 18:56:40 +0100 Subject: [PATCH 1223/2266] [ttnctl-user] Consistent naming and improved error reporting --- ttnctl/cmd/{users.go => user.go} | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) rename ttnctl/cmd/{users.go => user.go} (87%) diff --git a/ttnctl/cmd/users.go b/ttnctl/cmd/user.go similarity index 87% rename from ttnctl/cmd/users.go rename to ttnctl/cmd/user.go index 73a911c22..73848a35b 100644 --- a/ttnctl/cmd/users.go +++ b/ttnctl/cmd/user.go @@ -22,8 +22,8 @@ type token struct { ErrorDescription string `json:"error_description"` } -// usersCmd represents the users command -var usersCmd = &cobra.Command{ +// userCmd represents the users command +var userCmd = &cobra.Command{ Use: "user", Short: "Show the current user", Long: `ttnctl user shows the current logged on user`, @@ -44,7 +44,7 @@ var usersCmd = &cobra.Command{ }, } -var usersCreateCmd = &cobra.Command{ +var userCreateCmd = &cobra.Command{ Use: "create [e-mail]", Short: "Create a new user", Long: `ttnctl user create allows you to create a new user`, @@ -57,7 +57,7 @@ var usersCreateCmd = &cobra.Command{ fmt.Print("Password: ") password, err := gopass.GetPasswd() if err != nil { - ctx.Fatal("Invalid password") + ctx.Fatal(err.Error()) } uri := fmt.Sprintf("%s/register", viper.GetString("ttn-account-server")) @@ -69,7 +69,6 @@ var usersCreateCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Registration failed") } - defer res.Body.Close() if res.StatusCode != http.StatusCreated { ctx.Fatalf("Registration failed: %d %s", res.StatusCode, res.Status) @@ -79,7 +78,7 @@ var usersCreateCmd = &cobra.Command{ }, } -var usersLoginCmd = &cobra.Command{ +var userLoginCmd = &cobra.Command{ Use: "login [e-mail]", Short: "Login", Long: `ttnctl user login allows you to login`, @@ -92,7 +91,7 @@ var usersLoginCmd = &cobra.Command{ fmt.Print("Password: ") password, err := gopass.GetPasswd() if err != nil { - ctx.Fatal("Invalid password") + ctx.Fatal(err.Error()) } server := viper.GetString("ttn-account-server") @@ -114,8 +113,8 @@ var usersLoginCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Request failed") } - defer resp.Body.Close() + defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) var t token if err := decoder.Decode(&t); err != nil { @@ -130,16 +129,16 @@ var usersLoginCmd = &cobra.Command{ } } - ctx.Infof("Logged in as %s", email) - if err := util.SaveAuth(server, email, t.AccessToken); err != nil { - ctx.WithError(err).Error("Failed to save login") + ctx.WithError(err).Fatal("Failed to save login") } + + ctx.Infof("Logged in as %s and persisted token in $HOME/.ttnctl/auths.json", email) }, } func init() { - RootCmd.AddCommand(usersCmd) - usersCmd.AddCommand(usersCreateCmd) - usersCmd.AddCommand(usersLoginCmd) + RootCmd.AddCommand(userCmd) + userCmd.AddCommand(userCreateCmd) + userCmd.AddCommand(userLoginCmd) } From cb0cd4d09d15a85446bb124ec4413d16c9b106da Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 18:56:56 +0100 Subject: [PATCH 1224/2266] [ttnctl-user] Set default account server to account.thethingsnetwork.org --- ttnctl/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index e1b17c0a1..89ea8aef7 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -63,7 +63,7 @@ func init() { RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) - RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethings.network", "The address of the OAuth 2.0 server") + RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) } From 2433c6f3b6c2db829b2c887bd2597e2b7aff3cb9 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 19:12:20 +0100 Subject: [PATCH 1225/2266] [ttnctl-user] Token expiration check and variable util.AuthsFileName --- ttnctl/cmd/user.go | 13 ++++++---- ttnctl/util/auth.go | 58 ++++++++++++++++++++++----------------------- 2 files changed, 37 insertions(+), 34 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 73848a35b..81fa73ac0 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -9,6 +9,7 @@ import ( "net/http" "net/url" "strings" + "time" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" @@ -20,6 +21,7 @@ type token struct { AccessToken string `json:"access_token"` Error string `json:"error"` ErrorDescription string `json:"error_description"` + ExpiresIn int `json:"expires_in"` } // userCmd represents the users command @@ -30,15 +32,15 @@ var userCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { t, err := util.LoadAuth(viper.GetString("ttn-account-server")) if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") + ctx.WithError(err).Fatal("Failed to load authentication token") } if t == nil { - ctx.Warn("No login found") + ctx.Warn("No login found or token expired") return } - // TODO: Validate token + // TODO: Validate token online ctx.Infof("Logged on as %s", t.Email) }, @@ -129,11 +131,12 @@ var userLoginCmd = &cobra.Command{ } } - if err := util.SaveAuth(server, email, t.AccessToken); err != nil { + expires := time.Now().Add(time.Duration(t.ExpiresIn) * time.Second) + if err := util.SaveAuth(server, email, t.AccessToken, expires); err != nil { ctx.WithError(err).Fatal("Failed to save login") } - ctx.Infof("Logged in as %s and persisted token in $HOME/.ttnctl/auths.json", email) + ctx.Infof("Logged in as %s and persisted token in %s", email, util.AuthsFileName) }, } diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 7f15dc860..0e1300f07 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -9,34 +9,50 @@ import ( "os" "os/user" "path" + "time" ) -const ( - authsFileName = ".ttnctl/auths.json" - authsFilePerm = 0600 -) +const authsFilePerm = 0600 + +// AuthsFileName is where the authentication tokens are stored. Defaults to +// $HOME/.ttnctl/auths.json +var AuthsFileName string // Auth represents an authentication token type Auth struct { - Token string `json:"token"` - Email string `json:"email"` + Token string `json:"token"` + Email string `json:"email"` + Expires time.Time `json:"expires"` } type auths struct { Auths map[string]*Auth `json:"auths"` } +func init() { + u, err := user.Current() + if err != nil { + // TODO: Should we support an alternative? + panic(err) + } + AuthsFileName = path.Join(u.HomeDir, ".ttnctl/auths.json") +} + // LoadAuth loads the authentication token for the specified server func LoadAuth(server string) (*Auth, error) { a, err := loadAuths() if err != nil { return nil, err } - return a.Auths[server], nil + t, ok := a.Auths[server] + if !ok || time.Now().After(t.Expires) { + return nil, nil + } + return t, nil } // SaveAuth saves the authentication token for the specified server and e-mail -func SaveAuth(server, email, token string) error { +func SaveAuth(server, email, token string, expires time.Time) error { a, err := loadAuths() // Ignore error - just create new structure if err != nil || a == nil { @@ -47,21 +63,17 @@ func SaveAuth(server, email, token string) error { if a.Auths == nil { a.Auths = make(map[string]*Auth) } - a.Auths[server] = &Auth{token, email} + a.Auths[server] = &Auth{token, email, expires} // Marshal and write to disk buff, err := json.Marshal(&a) if err != nil { return err } - filename, err := getAuthsFilename() - if err != nil { + if err := os.MkdirAll(path.Dir(AuthsFileName), 0755); err != nil { return err } - if err := os.MkdirAll(path.Dir(filename), 0755); err != nil { - return err - } - if err := ioutil.WriteFile(filename, buff, authsFilePerm); err != nil { + if err := ioutil.WriteFile(AuthsFileName, buff, authsFilePerm); err != nil { return err } return nil @@ -70,14 +82,10 @@ func SaveAuth(server, email, token string) error { // loadAuths loads the authentication tokens. This function always returns an // empty structure if the file does not exist. func loadAuths() (*auths, error) { - filename, err := getAuthsFilename() - if err != nil { - return nil, err - } - if _, err := os.Stat(filename); os.IsNotExist(err) { + if _, err := os.Stat(AuthsFileName); os.IsNotExist(err) { return &auths{}, nil } - buff, err := ioutil.ReadFile(filename) + buff, err := ioutil.ReadFile(AuthsFileName) if err != nil { return nil, err } @@ -87,11 +95,3 @@ func loadAuths() (*auths, error) { } return &a, nil } - -func getAuthsFilename() (string, error) { - u, err := user.Current() - if err != nil { - return "", err - } - return path.Join(u.HomeDir, authsFileName), nil -} From 6f4f00a11d238588bbbb6b9ef798b49a5c1ea0dd Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 19:25:24 +0100 Subject: [PATCH 1226/2266] [ttnctl-user] Switched to go-homedir because of compatibility issue with os/user --- ttnctl/util/auth.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 0e1300f07..3ee3b4067 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -7,9 +7,10 @@ import ( "encoding/json" "io/ioutil" "os" - "os/user" "path" "time" + + homedir "github.com/mitchellh/go-homedir" ) const authsFilePerm = 0600 @@ -30,12 +31,15 @@ type auths struct { } func init() { - u, err := user.Current() + dir, err := homedir.Dir() + if err != nil { + panic(err) + } + expanded, err := homedir.Expand(dir) if err != nil { - // TODO: Should we support an alternative? panic(err) } - AuthsFileName = path.Join(u.HomeDir, ".ttnctl/auths.json") + AuthsFileName = path.Join(expanded, ".ttnctl/auths.json") } // LoadAuth loads the authentication token for the specified server From 4b0c4144c15b4f4ef4c240b1901ac00e9c2e3fbf Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 28 Mar 2016 00:13:54 +0200 Subject: [PATCH 1227/2266] [ttnctl-user] Save refresh token --- ttnctl/util/auth.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 3ee3b4067..8177be208 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -21,9 +21,10 @@ var AuthsFileName string // Auth represents an authentication token type Auth struct { - Token string `json:"token"` - Email string `json:"email"` - Expires time.Time `json:"expires"` + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + Email string `json:"email"` + Expires time.Time `json:"expires"` } type auths struct { @@ -56,7 +57,7 @@ func LoadAuth(server string) (*Auth, error) { } // SaveAuth saves the authentication token for the specified server and e-mail -func SaveAuth(server, email, token string, expires time.Time) error { +func SaveAuth(server, email, accessToken, refreshToken string, expires time.Time) error { a, err := loadAuths() // Ignore error - just create new structure if err != nil || a == nil { @@ -67,7 +68,7 @@ func SaveAuth(server, email, token string, expires time.Time) error { if a.Auths == nil { a.Auths = make(map[string]*Auth) } - a.Auths[server] = &Auth{token, email, expires} + a.Auths[server] = &Auth{accessToken, refreshToken, email, expires} // Marshal and write to disk buff, err := json.Marshal(&a) From 907a66b96ec528edfb58a9ca2413fa9914e86019 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 28 Mar 2016 00:42:18 +0200 Subject: [PATCH 1228/2266] [ttnctl-user] Moved login functionality to util --- ttnctl/cmd/device.go | 6 ++-- ttnctl/cmd/user.go | 49 ++--------------------------- ttnctl/util/auth.go | 73 +++++++++++++++++++++++++++++++++++++++----- 3 files changed, 71 insertions(+), 57 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 4fd23b310..4298a57f6 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -46,7 +46,7 @@ var devicesCmd = &cobra.Command{ manager := getHandlerManager() res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ - Token: auth.Token, + Token: auth.AccessToken, AppEUI: appEUI, }) if err != nil { @@ -116,7 +116,7 @@ var devicesRegisterCmd = &cobra.Command{ manager := getHandlerManager() res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ - Token: auth.Token, + Token: auth.AccessToken, AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, @@ -168,7 +168,7 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ manager := getHandlerManager() res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ - Token: auth.Token, + Token: auth.AccessToken, AppEUI: appEUI, DevAddr: devAddr, AppSKey: appSKey, diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 81fa73ac0..9bdbaf8a1 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -4,12 +4,9 @@ package cmd import ( - "encoding/json" "fmt" "net/http" "net/url" - "strings" - "time" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" @@ -17,13 +14,6 @@ import ( "github.com/spf13/viper" ) -type token struct { - AccessToken string `json:"access_token"` - Error string `json:"error"` - ErrorDescription string `json:"error_description"` - ExpiresIn int `json:"expires_in"` -} - // userCmd represents the users command var userCmd = &cobra.Command{ Use: "user", @@ -96,44 +86,9 @@ var userLoginCmd = &cobra.Command{ ctx.Fatal(err.Error()) } - server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/token", server) - values := url.Values{ - "grant_type": {"password"}, - "username": {email}, - "password": {string(password)}, - } - req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) - if err != nil { - ctx.WithError(err).Fatal("Create request failed") - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth("ttnctl", "") - - client := &http.Client{} - resp, err := client.Do(req) + _, err = util.Login(viper.GetString("ttn-account-server"), email, string(password)) if err != nil { - ctx.WithError(err).Fatal("Request failed") - } - - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - var t token - if err := decoder.Decode(&t); err != nil { - ctx.WithError(err).Fatal("Failed to parse response") - } - - if resp.StatusCode != http.StatusOK { - if t.Error != "" { - ctx.Fatalf("Request failed: %s", t.ErrorDescription) - } else { - ctx.Fatalf("Request failed: %s", resp.Status) - } - } - - expires := time.Now().Add(time.Duration(t.ExpiresIn) * time.Second) - if err := util.SaveAuth(server, email, t.AccessToken, expires); err != nil { - ctx.WithError(err).Fatal("Failed to save login") + ctx.WithError(err).Fatal("Failed to login") } ctx.Infof("Logged in as %s and persisted token in %s", email, util.AuthsFileName) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 8177be208..557f43bc7 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -5,9 +5,14 @@ package util import ( "encoding/json" + "errors" + "fmt" "io/ioutil" + "net/http" + "net/url" "os" "path" + "strings" "time" homedir "github.com/mitchellh/go-homedir" @@ -31,6 +36,14 @@ type auths struct { Auths map[string]*Auth `json:"auths"` } +type token struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` + Error string `json:"error"` + ErrorDescription string `json:"error_description"` + ExpiresIn int `json:"expires_in"` +} + func init() { dir, err := homedir.Dir() if err != nil { @@ -43,6 +56,50 @@ func init() { AuthsFileName = path.Join(expanded, ".ttnctl/auths.json") } +// Login attemps to login using the specified credentials on the server +func Login(server, email, password string) (*Auth, error) { + uri := fmt.Sprintf("%s/token", server) + values := url.Values{ + "grant_type": {"password"}, + "username": {email}, + "password": {password}, + } + req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.SetBasicAuth("ttnctl", "") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + var t token + if err := decoder.Decode(&t); err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + if t.Error != "" { + return nil, errors.New(t.ErrorDescription) + } + return nil, errors.New(resp.Status) + } + + expires := time.Now().Add(time.Duration(t.ExpiresIn) * time.Second) + auth, err := saveAuth(server, email, t.AccessToken, t.RefreshToken, expires) + if err != nil { + return nil, err + } + + return auth, nil +} + // LoadAuth loads the authentication token for the specified server func LoadAuth(server string) (*Auth, error) { a, err := loadAuths() @@ -51,13 +108,14 @@ func LoadAuth(server string) (*Auth, error) { } t, ok := a.Auths[server] if !ok || time.Now().After(t.Expires) { + // TODO: Refresh the token return nil, nil } return t, nil } -// SaveAuth saves the authentication token for the specified server and e-mail -func SaveAuth(server, email, accessToken, refreshToken string, expires time.Time) error { +// saveAuth saves the authentication token for the specified server and e-mail +func saveAuth(server, email, accessToken, refreshToken string, expires time.Time) (*Auth, error) { a, err := loadAuths() // Ignore error - just create new structure if err != nil || a == nil { @@ -68,20 +126,21 @@ func SaveAuth(server, email, accessToken, refreshToken string, expires time.Time if a.Auths == nil { a.Auths = make(map[string]*Auth) } - a.Auths[server] = &Auth{accessToken, refreshToken, email, expires} + auth := &Auth{accessToken, refreshToken, email, expires} + a.Auths[server] = auth // Marshal and write to disk buff, err := json.Marshal(&a) if err != nil { - return err + return nil, err } if err := os.MkdirAll(path.Dir(AuthsFileName), 0755); err != nil { - return err + return nil, err } if err := ioutil.WriteFile(AuthsFileName, buff, authsFilePerm); err != nil { - return err + return nil, err } - return nil + return auth, nil } // loadAuths loads the authentication tokens. This function always returns an From 1e7ff0e7cf148714c8fcc2fe810530bb2398545e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 28 Mar 2016 01:04:05 +0200 Subject: [PATCH 1229/2266] [ttnctl-user] Refresh the token --- ttnctl/cmd/user.go | 2 -- ttnctl/util/auth.go | 45 ++++++++++++++++++++++++++++++--------------- 2 files changed, 30 insertions(+), 17 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 9bdbaf8a1..8ea2f073d 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -30,8 +30,6 @@ var userCmd = &cobra.Command{ return } - // TODO: Validate token online - ctx.Infof("Logged on as %s", t.Email) }, } diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 557f43bc7..237ec20c9 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -58,12 +58,41 @@ func init() { // Login attemps to login using the specified credentials on the server func Login(server, email, password string) (*Auth, error) { - uri := fmt.Sprintf("%s/token", server) values := url.Values{ "grant_type": {"password"}, "username": {email}, "password": {password}, } + return newToken(server, email, values) +} + +// LoadAuth loads the authentication token for the specified server and attempts +// to refresh the token if it has been expired +func LoadAuth(server string) (*Auth, error) { + a, err := loadAuths() + if err != nil { + return nil, err + } + auth, ok := a.Auths[server] + if !ok { + return nil, nil + } + if time.Now().After(auth.Expires) { + return refreshToken(server, auth) + } + return auth, nil +} + +func refreshToken(server string, auth *Auth) (*Auth, error) { + values := url.Values{ + "grant_type": {"refresh_token"}, + "refresh_token": {auth.RefreshToken}, + } + return newToken(server, auth.Email, values) +} + +func newToken(server, email string, values url.Values) (*Auth, error) { + uri := fmt.Sprintf("%s/token", server) req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) if err != nil { return nil, err @@ -100,20 +129,6 @@ func Login(server, email, password string) (*Auth, error) { return auth, nil } -// LoadAuth loads the authentication token for the specified server -func LoadAuth(server string) (*Auth, error) { - a, err := loadAuths() - if err != nil { - return nil, err - } - t, ok := a.Auths[server] - if !ok || time.Now().After(t.Expires) { - // TODO: Refresh the token - return nil, nil - } - return t, nil -} - // saveAuth saves the authentication token for the specified server and e-mail func saveAuth(server, email, accessToken, refreshToken string, expires time.Time) (*Auth, error) { a, err := loadAuths() From 460b3b03c350bc549b9dfbff0b988a4b838ab3dd Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 28 Mar 2016 01:13:07 +0200 Subject: [PATCH 1230/2266] [ttnctl-user] Added command to log the current user out --- ttnctl/cmd/user.go | 16 ++++++++++++++- ttnctl/util/auth.go | 48 ++++++++++++++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 15 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 8ea2f073d..7cca65fe4 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -26,7 +26,7 @@ var userCmd = &cobra.Command{ } if t == nil { - ctx.Warn("No login found or token expired") + ctx.Warn("No login found. Please login with ttnctl user login [e-mail]") return } @@ -93,8 +93,22 @@ var userLoginCmd = &cobra.Command{ }, } +var userLogoutCmd = &cobra.Command{ + Use: "logout", + Short: "Logout the current user", + Long: `ttnctl user logout logs out the current user`, + Run: func(cmd *cobra.Command, args []string) { + if err := util.Logout(viper.GetString("ttn-account-server")); err != nil { + ctx.WithError(err).Fatal("Failed to log out") + } + + ctx.Info("Logged out") + }, +} + func init() { RootCmd.AddCommand(userCmd) userCmd.AddCommand(userCreateCmd) userCmd.AddCommand(userLoginCmd) + userCmd.AddCommand(userLogoutCmd) } diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 237ec20c9..78b36efd7 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -66,6 +66,19 @@ func Login(server, email, password string) (*Auth, error) { return newToken(server, email, values) } +// Logout deletes the token for the specified server +func Logout(server string) error { + a, err := loadAuths() + if err != nil { + return err + } + delete(a.Auths, server) + if err := saveAuths(a); err != nil { + return err + } + return nil +} + // LoadAuth loads the authentication token for the specified server and attempts // to refresh the token if it has been expired func LoadAuth(server string) (*Auth, error) { @@ -138,23 +151,12 @@ func saveAuth(server, email, accessToken, refreshToken string, expires time.Time } // Initialize the map if not exists and add the token - if a.Auths == nil { - a.Auths = make(map[string]*Auth) - } auth := &Auth{accessToken, refreshToken, email, expires} a.Auths[server] = auth - - // Marshal and write to disk - buff, err := json.Marshal(&a) - if err != nil { - return nil, err - } - if err := os.MkdirAll(path.Dir(AuthsFileName), 0755); err != nil { - return nil, err - } - if err := ioutil.WriteFile(AuthsFileName, buff, authsFilePerm); err != nil { + if err := saveAuths(a); err != nil { return nil, err } + return auth, nil } @@ -162,7 +164,7 @@ func saveAuth(server, email, accessToken, refreshToken string, expires time.Time // empty structure if the file does not exist. func loadAuths() (*auths, error) { if _, err := os.Stat(AuthsFileName); os.IsNotExist(err) { - return &auths{}, nil + return &auths{make(map[string]*Auth)}, nil } buff, err := ioutil.ReadFile(AuthsFileName) if err != nil { @@ -172,5 +174,23 @@ func loadAuths() (*auths, error) { if err := json.Unmarshal(buff, &a); err != nil { return nil, err } + if a.Auths == nil { + a.Auths = make(map[string]*Auth) + } return &a, nil } + +func saveAuths(a *auths) error { + // Marshal and write to disk + buff, err := json.Marshal(&a) + if err != nil { + return err + } + if err := os.MkdirAll(path.Dir(AuthsFileName), 0755); err != nil { + return err + } + if err := ioutil.WriteFile(AuthsFileName, buff, authsFilePerm); err != nil { + return err + } + return nil +} From ec6bda35a3b454804f2108485fbb9b540f7fdbdd Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 00:04:37 +0200 Subject: [PATCH 1231/2266] [ttnctl-user] Added tests for util.auth --- ttnctl/util/auth.go | 16 ++--- ttnctl/util/auth_test.go | 147 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 8 deletions(-) create mode 100644 ttnctl/util/auth_test.go diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 78b36efd7..b06f7e589 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -15,7 +15,7 @@ import ( "strings" "time" - homedir "github.com/mitchellh/go-homedir" + "github.com/mitchellh/go-homedir" ) const authsFilePerm = 0600 @@ -37,11 +37,11 @@ type auths struct { } type token struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - Error string `json:"error"` - ErrorDescription string `json:"error_description"` - ExpiresIn int `json:"expires_in"` + AccessToken string `json:"access_token,omitempty"` + RefreshToken string `json:"refresh_token,omitempty"` + Error string `json:"error,omitempty"` + ErrorDescription string `json:"error_description,omitempty"` + ExpiresIn int `json:"expires_in,omitempty"` } func init() { @@ -160,8 +160,8 @@ func saveAuth(server, email, accessToken, refreshToken string, expires time.Time return auth, nil } -// loadAuths loads the authentication tokens. This function always returns an -// empty structure if the file does not exist. +// loadAuths loads the authentication tokens. This function returns an empty +// structure if the file does not exist. func loadAuths() (*auths, error) { if _, err := os.Stat(AuthsFileName); os.IsNotExist(err) { return &auths{make(map[string]*Auth)}, nil diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go new file mode 100644 index 000000000..45a45af82 --- /dev/null +++ b/ttnctl/util/auth_test.go @@ -0,0 +1,147 @@ +package util + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func newTokenServer(a *Assertion) *httptest.Server { + return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.So(r.RequestURI, ShouldEqual, "/token") + a.So(r.Method, ShouldEqual, "POST") + + username, password, ok := r.BasicAuth() + a.So(ok, ShouldBeTrue) + a.So(username, ShouldEqual, "ttnctl") + a.So(password, ShouldEqual, "") + + grantType := r.FormValue("grant_type") + if grantType == "password" { + handleNewToken(a, w, r) + } else if grantType == "refresh_token" { + handleRefreshToken(a, w, r) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) +} + +func handleNewToken(a *Assertion, w http.ResponseWriter, r *http.Request) { + var resp token + if r.FormValue("username") == "jantje@test.org" && r.FormValue("password") == "secret" { + resp = token{ + AccessToken: "123", + RefreshToken: "ABC", + ExpiresIn: 3600, + } + w.WriteHeader(http.StatusOK) + } else { + resp = token{ + Error: "invalid_credentials", + ErrorDescription: "Invalid credentials", + } + w.WriteHeader(http.StatusForbidden) + } + + encoder := json.NewEncoder(w) + err := encoder.Encode(&resp) + a.So(err, ShouldBeNil) +} + +func handleRefreshToken(a *Assertion, w http.ResponseWriter, r *http.Request) { + var resp token + if r.FormValue("refresh_token") == "ABC" { + resp = token{ + AccessToken: "456", + RefreshToken: "DEF", + ExpiresIn: 3600, + } + w.WriteHeader(http.StatusOK) + } else { + resp = token{ + Error: "invalid_grant", + ErrorDescription: "Refresh token not found", + } + w.WriteHeader(http.StatusBadRequest) + } + + encoder := json.NewEncoder(w) + err := encoder.Encode(&resp) + a.So(err, ShouldBeNil) +} + +func TestLogin(t *testing.T) { + a := New(t) + server := newTokenServer(a) + defer server.Close() + + _, err := Login(server.URL, "pietje@test.org", "secret") + a.So(err, ShouldNotBeNil) + a.So(err.Error(), ShouldEqual, "Invalid credentials") + + loginAuth, err := Login(server.URL, "jantje@test.org", "secret") + a.So(err, ShouldBeNil) + a.So(loginAuth, ShouldNotBeNil) + a.So(loginAuth.AccessToken, ShouldEqual, "123") + a.So(loginAuth.RefreshToken, ShouldEqual, "ABC") + a.So(loginAuth.Email, ShouldEqual, "jantje@test.org") + + loadedAuth, err := LoadAuth(server.URL) + a.So(err, ShouldBeNil) + a.So(loadedAuth, ShouldNotBeNil) + a.So(loginAuth, ShouldResemble, loadedAuth) +} + +func TestLogout(t *testing.T) { + a := New(t) + server := newTokenServer(a) + defer server.Close() + + // Make sure we're not logged on + err := Logout(server.URL) + a.So(err, ShouldBeNil) + loadedAuth, err := LoadAuth(server.URL) + a.So(err, ShouldBeNil) + a.So(loadedAuth, ShouldBeNil) + + // Login + loginAuth, err := Login(server.URL, "jantje@test.org", "secret") + a.So(err, ShouldBeNil) + a.So(loginAuth, ShouldNotBeNil) + + // Logout + err = Logout(server.URL) + a.So(err, ShouldBeNil) + loadedAuth, err = LoadAuth(server.URL) + a.So(err, ShouldBeNil) + a.So(loadedAuth, ShouldBeNil) +} + +func TestLoadWithRefresh(t *testing.T) { + a := New(t) + server := newTokenServer(a) + defer server.Close() + + // Make sure we're not logged on + err := Logout(server.URL) + a.So(err, ShouldBeNil) + + // Save an expired token + expires := time.Now().Add(time.Duration(-1) * time.Hour) + savedAuth, err := saveAuth(server.URL, "jantje@test.org", "123", "ABC", expires) + a.So(err, ShouldBeNil) + + // Refresh the token + loadedAuth, err := LoadAuth(server.URL) + a.So(err, ShouldBeNil) + a.So(loadedAuth, ShouldNotBeNil) + a.So(savedAuth, ShouldNotResemble, loadedAuth) + a.So(loadedAuth.AccessToken, ShouldEqual, "456") + a.So(loadedAuth.RefreshToken, ShouldEqual, "DEF") + a.So(loadedAuth.Email, ShouldEqual, "jantje@test.org") +} From bf1ff3ec41f1da3e97dd403afbe99e7dedec9e3d Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 00:08:25 +0200 Subject: [PATCH 1232/2266] [ttnctl-user] Added test for invalid refresh token --- ttnctl/util/auth_test.go | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go index 45a45af82..9e632d6db 100644 --- a/ttnctl/util/auth_test.go +++ b/ttnctl/util/auth_test.go @@ -145,3 +145,24 @@ func TestLoadWithRefresh(t *testing.T) { a.So(loadedAuth.RefreshToken, ShouldEqual, "DEF") a.So(loadedAuth.Email, ShouldEqual, "jantje@test.org") } + +func TestLoadWithInvalidRefresh(t *testing.T) { + a := New(t) + server := newTokenServer(a) + defer server.Close() + + // Make sure we're not logged on + err := Logout(server.URL) + a.So(err, ShouldBeNil) + + // Save an expired token + expires := time.Now().Add(time.Duration(-1) * time.Hour) + _, err = saveAuth(server.URL, "pietje@test.org", "987", "ZYX", expires) + a.So(err, ShouldBeNil) + + // Refresh the token + loadedAuth, err := LoadAuth(server.URL) + a.So(err, ShouldNotBeNil) + a.So(err.Error(), ShouldEqual, "Refresh token not found") + a.So(loadedAuth, ShouldBeNil) +} From 17bb9526b2b780bbc3a5d95b307ba279f4a83a33 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 01:09:05 +0200 Subject: [PATCH 1233/2266] [ttnctl-user] Cleanup of tokens after testing --- ttnctl/util/auth_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go index 9e632d6db..fcfcb193c 100644 --- a/ttnctl/util/auth_test.go +++ b/ttnctl/util/auth_test.go @@ -95,6 +95,8 @@ func TestLogin(t *testing.T) { a.So(err, ShouldBeNil) a.So(loadedAuth, ShouldNotBeNil) a.So(loginAuth, ShouldResemble, loadedAuth) + + Logout(server.URL) } func TestLogout(t *testing.T) { @@ -144,6 +146,8 @@ func TestLoadWithRefresh(t *testing.T) { a.So(loadedAuth.AccessToken, ShouldEqual, "456") a.So(loadedAuth.RefreshToken, ShouldEqual, "DEF") a.So(loadedAuth.Email, ShouldEqual, "jantje@test.org") + + Logout(server.URL) } func TestLoadWithInvalidRefresh(t *testing.T) { @@ -165,4 +169,6 @@ func TestLoadWithInvalidRefresh(t *testing.T) { a.So(err, ShouldNotBeNil) a.So(err.Error(), ShouldEqual, "Refresh token not found") a.So(loadedAuth, ShouldBeNil) + + Logout(server.URL) } From 0b63b0c9a5aa24eea01fb5ed0440e74557a9ca62 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 01:26:27 +0200 Subject: [PATCH 1234/2266] [ttnctl-user] Add NewRequestWithAuth --- ttnctl/util/auth.go | 19 +++++++++++++++++++ ttnctl/util/auth_test.go | 11 +++++++++++ 2 files changed, 30 insertions(+) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index b06f7e589..858bb0ca3 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "net/http" "net/url" @@ -96,6 +97,24 @@ func LoadAuth(server string) (*Auth, error) { return auth, nil } +// NewRequestWithAuth creates a new HTTP request and adds the access token of +// the authenticated user as bearer token +func NewRequestWithAuth(server, method, urlStr string, body io.Reader) (*http.Request, error) { + auth, err := LoadAuth(server) + if err != nil { + return nil, err + } + if auth == nil { + return nil, errors.New("No authentication token found. Please login") + } + req, err := http.NewRequest(method, urlStr, body) + if err != nil { + return nil, err + } + req.Header.Add("Authorization", fmt.Sprintf("bearer %s", auth.AccessToken)) + return req, nil +} + func refreshToken(server string, auth *Auth) (*Auth, error) { values := url.Values{ "grant_type": {"refresh_token"}, diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go index fcfcb193c..c97b1c0db 100644 --- a/ttnctl/util/auth_test.go +++ b/ttnctl/util/auth_test.go @@ -2,6 +2,7 @@ package util import ( "encoding/json" + "fmt" "net/http" "net/http/httptest" "testing" @@ -96,6 +97,12 @@ func TestLogin(t *testing.T) { a.So(loadedAuth, ShouldNotBeNil) a.So(loginAuth, ShouldResemble, loadedAuth) + // Check if we get this token on the HTTP request + req, err := NewRequestWithAuth(server.URL, "GET", "http://external", nil) + a.So(err, ShouldBeNil) + a.So(req, ShouldNotBeNil) + a.So(req.Header.Get("Authorization"), ShouldEqual, fmt.Sprintf("bearer %s", loadedAuth.AccessToken)) + Logout(server.URL) } @@ -122,6 +129,10 @@ func TestLogout(t *testing.T) { loadedAuth, err = LoadAuth(server.URL) a.So(err, ShouldBeNil) a.So(loadedAuth, ShouldBeNil) + + // Make sure that we can't make an HTTP request + _, err = NewRequestWithAuth(server.URL, "GET", "http://external", nil) + a.So(err, ShouldNotBeNil) } func TestLoadWithRefresh(t *testing.T) { From 55ed07b9400a1d0e320acc1622ffa3a1a97c88bc Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 18:28:54 +0800 Subject: [PATCH 1235/2266] [ttnctl-user] Use UTC time for expiration --- ttnctl/util/auth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 858bb0ca3..723a0e312 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -91,7 +91,7 @@ func LoadAuth(server string) (*Auth, error) { if !ok { return nil, nil } - if time.Now().After(auth.Expires) { + if time.Now().UTC().After(auth.Expires) { return refreshToken(server, auth) } return auth, nil @@ -152,7 +152,7 @@ func newToken(server, email string, values url.Values) (*Auth, error) { return nil, errors.New(resp.Status) } - expires := time.Now().Add(time.Duration(t.ExpiresIn) * time.Second) + expires := time.Now().UTC().Add(time.Duration(t.ExpiresIn) * time.Second) auth, err := saveAuth(server, email, t.AccessToken, t.RefreshToken, expires) if err != nil { return nil, err From 8d5944e4157d0ca5a2a2e3c7eaa558411010caab Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 26 Mar 2016 18:45:25 +0100 Subject: [PATCH 1236/2266] [ttnctl-applications] Applications command stub --- ttnctl/cmd/applications.go | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 ttnctl/cmd/applications.go diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go new file mode 100644 index 000000000..9f3504a3d --- /dev/null +++ b/ttnctl/cmd/applications.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +// applicationsCmd represents the applications command +var applicationsCmd = &cobra.Command{ + Use: "applications", + Short: "Show applications", + Long: `ttnctl applications retrieves your applications of the logged on user.`, + Run: func(cmd *cobra.Command, args []string) { + // TODO: Work your own magic here + fmt.Println("applications called") + }, +} + +func init() { + RootCmd.AddCommand(applicationsCmd) +} From ab15ca7776b26154de1e252460606577c0aa8eaf Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 28 Mar 2016 00:11:06 +0200 Subject: [PATCH 1237/2266] [ttnctl-applications] Get login info --- ttnctl/cmd/applications.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 9f3504a3d..227784176 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -4,19 +4,25 @@ package cmd import ( - "fmt" - + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // applicationsCmd represents the applications command var applicationsCmd = &cobra.Command{ Use: "applications", Short: "Show applications", - Long: `ttnctl applications retrieves your applications of the logged on user.`, + Long: `ttnctl applications retrieves the applications of the logged on user.`, Run: func(cmd *cobra.Command, args []string) { - // TODO: Work your own magic here - fmt.Println("applications called") + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + }, } From 7658254c15e22d75517dc99dff8b7268895b33ea Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 28 Mar 2016 00:11:16 +0200 Subject: [PATCH 1238/2266] [ttnctl-user] Show token in debug mode --- ttnctl/cmd/user.go | 1 - 1 file changed, 1 deletion(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 7cca65fe4..396433826 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -24,7 +24,6 @@ var userCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Failed to load authentication token") } - if t == nil { ctx.Warn("No login found. Please login with ttnctl user login [e-mail]") return From 6e76993c932693eb5a510f449ead134f23464c26 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 01:30:05 +0200 Subject: [PATCH 1239/2266] [ttnctl-applications] Prepare request --- ttnctl/cmd/applications.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 227784176..c6c6f23f3 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -4,6 +4,8 @@ package cmd import ( + "fmt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -15,14 +17,11 @@ var applicationsCmd = &cobra.Command{ Short: "Show applications", Long: `ttnctl applications retrieves the applications of the logged on user.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + server := viper.GetString("ttn-account-server") + _, err := util.NewRequestWithAuth(server, "GET", fmt.Sprintf("%s/applications", server), nil) if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") + ctx.WithError(err).Fatal("Create request failed") } - }, } From 77e13c0ff7589161553adddc065df769230158b4 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 02:16:37 +0200 Subject: [PATCH 1240/2266] [ttnctl-applications] Show applications --- ttnctl/cmd/applications.go | 39 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index c6c6f23f3..e09701800 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -4,13 +4,22 @@ package cmd import ( + "encoding/json" "fmt" + "net/http" "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" "github.com/spf13/cobra" "github.com/spf13/viper" ) +type app struct { + EUI string `json:"eui"` + Name string `json:"name"` + Owner string `json:"owner"` +} + // applicationsCmd represents the applications command var applicationsCmd = &cobra.Command{ Use: "applications", @@ -18,10 +27,36 @@ var applicationsCmd = &cobra.Command{ Long: `ttnctl applications retrieves the applications of the logged on user.`, Run: func(cmd *cobra.Command, args []string) { server := viper.GetString("ttn-account-server") - _, err := util.NewRequestWithAuth(server, "GET", fmt.Sprintf("%s/applications", server), nil) + req, err := util.NewRequestWithAuth(server, "GET", fmt.Sprintf("%s/applications", server), nil) + if err != nil { + ctx.WithError(err).Fatal("Failed to created authenticated request") + } + + client := &http.Client{} + resp, err := client.Do(req) if err != nil { - ctx.WithError(err).Fatal("Create request failed") + ctx.WithError(err).Fatal("Failed to get applications") + } + if resp.StatusCode != http.StatusOK { + ctx.Fatalf("Failed to get applications: %s", resp.Status) + } + + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + var apps []*app + err = decoder.Decode(&apps) + if err != nil { + ctx.WithError(err).Fatal("Failed to read applications") + } + + ctx.Infof("Found %d application(s)", len(apps)) + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("EUI", "Name", "Owner") + for _, app := range apps { + table.AddRow(app.EUI, app.Name, app.Owner) } + fmt.Println(table) }, } From 1c52668750d5497810d8d2f1addacb8eb7bf848b Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 02:21:30 +0200 Subject: [PATCH 1241/2266] [ttnctl] Show help when command is invalid --- ttnctl/cmd/device.go | 6 ++++-- ttnctl/cmd/user.go | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 4298a57f6..1d4493229 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -88,7 +88,8 @@ var devicesRegisterCmd = &cobra.Command{ Long: `ttnctl devices register creates or updates an OTAA registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { - ctx.Fatal("Insufficient arguments") + cmd.Help() + return } appEUI, err := util.Parse64(viper.GetString("app-eui")) @@ -135,7 +136,8 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ Long: `ttnctl devices register creates or updates an ABP registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 3 { - ctx.Fatal("Insufficient arguments") + cmd.Help() + return } appEUI, err := util.Parse64(viper.GetString("app-eui")) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 396433826..9540655ab 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -39,7 +39,8 @@ var userCreateCmd = &cobra.Command{ Long: `ttnctl user create allows you to create a new user`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { - ctx.Fatal("Insufficient arguments") + cmd.Help() + return } email := args[0] @@ -73,7 +74,8 @@ var userLoginCmd = &cobra.Command{ Long: `ttnctl user login allows you to login`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { - ctx.Fatal("Insufficient arguments") + cmd.Help() + return } email := args[0] From 032e5d4ddd1576beb874d549abffaf05c5f97925 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 02:31:33 +0200 Subject: [PATCH 1242/2266] [ttnctl-applications] Create an application --- ttnctl/cmd/applications.go | 48 +++++++++++++++++++++++++++++++++++++- ttnctl/util/auth.go | 9 +++++++ 2 files changed, 56 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index e09701800..1ddf294ba 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -7,8 +7,11 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" + "strings" "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" "github.com/gosuri/uitable" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -29,7 +32,7 @@ var applicationsCmd = &cobra.Command{ server := viper.GetString("ttn-account-server") req, err := util.NewRequestWithAuth(server, "GET", fmt.Sprintf("%s/applications", server), nil) if err != nil { - ctx.WithError(err).Fatal("Failed to created authenticated request") + ctx.WithError(err).Fatal("Failed to create authenticated request") } client := &http.Client{} @@ -60,6 +63,49 @@ var applicationsCmd = &cobra.Command{ }, } +var applicationsCreateCmd = &cobra.Command{ + Use: "create [eui] [name]", + Short: "Create a new application", + Long: `ttnctl applications create creates a new application.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + cmd.Help() + return + } + + server := viper.GetString("ttn-account-server") + values := url.Values{ + "eui": {args[0]}, + "name": {args[1]}, + } + req, err := util.NewRequestWithAuth(server, "POST", fmt.Sprintf("%s/applications", server), strings.NewReader(values.Encode())) + if err != nil { + ctx.WithError(err).Fatal("Failed to create authenticated request") + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Failed to create application") + } + if resp.StatusCode != http.StatusCreated { + ctx.Fatalf("Failed to create application: %s", resp.Status) + } + + ctx.Info("Application created successfully") + + // We need to refresh the token to add the new application to the set of + // claims + _, err = util.RefreshToken(server) + if err != nil { + log.WithError(err).Warn("Failed to refresh token. Please login") + } + }, +} + func init() { RootCmd.AddCommand(applicationsCmd) + applicationsCmd.AddCommand(applicationsCreateCmd) } diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 723a0e312..f3da790a0 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -115,6 +115,15 @@ func NewRequestWithAuth(server, method, urlStr string, body io.Reader) (*http.Re return req, nil } +// RefreshToken refreshes the current token +func RefreshToken(server string) (*Auth, error) { + auth, err := LoadAuth(server) + if err != nil { + return nil, err + } + return refreshToken(server, auth) +} + func refreshToken(server string, auth *Auth) (*Auth, error) { values := url.Values{ "grant_type": {"refresh_token"}, From c6beb8708a1dbe8da27b44b9e9c80bcbb1b82389 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 29 Mar 2016 19:17:36 +0800 Subject: [PATCH 1243/2266] Updated AUTHORS --- ttnctl/AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/ttnctl/AUTHORS b/ttnctl/AUTHORS index ba2e99d67..6177a203c 100644 --- a/ttnctl/AUTHORS +++ b/ttnctl/AUTHORS @@ -9,3 +9,4 @@ # Please keep the list sorted. Hylke Visser +Johan Stokking From fe5e28b3773c211e093d2bb4719d8836358a97d6 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 30 Mar 2016 00:09:30 +0800 Subject: [PATCH 1244/2266] [ttnctl-applications] Parse AppEUI from user input --- ttnctl/cmd/applications.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 1ddf294ba..ffe91cd07 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -73,9 +73,14 @@ var applicationsCreateCmd = &cobra.Command{ return } + appEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + server := viper.GetString("ttn-account-server") values := url.Values{ - "eui": {args[0]}, + "eui": {fmt.Sprintf("%X", appEUI)}, "name": {args[1]}, } req, err := util.NewRequestWithAuth(server, "POST", fmt.Sprintf("%s/applications", server), strings.NewReader(values.Encode())) From 6d5092dba99f8e87e38aeba820774c7c7b8776d9 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 30 Mar 2016 00:07:29 +0800 Subject: [PATCH 1245/2266] Load OAuth 2.0 token from file and use claims based app authorization --- cmd/broker.go | 22 ++++++++++++++++++++++ core/components/broker/broker.go | 6 +++--- core/components/broker/brokerManager.go | 23 +++++++++++++++++++---- 3 files changed, 44 insertions(+), 7 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 8959af6e6..fc4fdfdf8 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -5,6 +5,8 @@ package cmd import ( "fmt" + "io/ioutil" + "path" "path/filepath" "strings" "time" @@ -13,6 +15,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -96,6 +99,12 @@ devices (with their network session keys) with the Broker. ctx.WithError(fmt.Errorf("Invalid applications database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } + // Security + tokenKey, err := ioutil.ReadFile(viper.GetString("broker.oauth2-keyfile")) + if err != nil { + ctx.WithError(err).Fatal("Unable to load OAuth 2.0 key") + } + // Broker broker := broker.New( broker.Components{ @@ -106,6 +115,7 @@ devices (with their network session keys) with the Broker. broker.Options{ NetAddrUp: fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-address"), viper.GetInt("broker.uplink-port")), NetAddrDown: fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-address"), viper.GetInt("broker.downlink-port")), + TokenKey: tokenKey, }, ) @@ -139,4 +149,16 @@ func init() { brokerCmd.Flags().Int("downlink-port", 1781, "The port for downlink communication") viper.BindPFlag("broker.downlink-address", brokerCmd.Flags().Lookup("downlink-address")) viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) + + defaultOAuth2KeyFile := "" + dir, err := homedir.Dir() + if err == nil { + expanded, err := homedir.Expand(dir) + if err == nil { + defaultOAuth2KeyFile = path.Join(expanded, ".ttn/oauth2-token.pub") + } + } + + brokerCmd.Flags().String("oauth2-keyfile", defaultOAuth2KeyFile, "The OAuth 2.0 public key") + viper.BindPFlag("broker.oauth2-keyfile", brokerCmd.Flags().Lookup("oauth2-keyfile")) } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 52fb2e766..308711c91 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -18,7 +18,7 @@ import ( // component implements the core.BrokerServer interface type component struct { Components - SecretKey [32]byte + TokenKey []byte NetAddrUp string NetAddrDown string MaxDevNonces uint @@ -35,7 +35,7 @@ type Components struct { type Options struct { NetAddrUp string NetAddrDown string - SecretKey [32]byte + TokenKey []byte } // Interface defines the Broker interface @@ -52,7 +52,7 @@ func New(c Components, o Options) Interface { NetAddrUp: o.NetAddrUp, NetAddrDown: o.NetAddrDown, MaxDevNonces: 10, - SecretKey: [32]byte{14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 42, 42, 42, 42, 42, 42}, // TODO Use options & ENV var + TokenKey: o.TokenKey, } } diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 5cda64e89..e6741fea3 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -101,13 +101,28 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) // validateToken verify an OAuth Bearer token pass through metadata during RPC func (b component) validateToken(ctx context.Context, token string, appEUI []byte) error { parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return b.SecretKey[:], nil + if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return b.TokenKey, nil }) if err != nil { return errors.New(errors.Structural, "Unable to parse token") } - if !parsed.Valid || parsed.Claims["sub"] != fmt.Sprintf("%X", appEUI) { - return errors.New(errors.Structural, "Invalid token.") + if !parsed.Valid { + return errors.New(errors.Operational, "The token is not valid or is expired.") } - return nil + + apps, ok := parsed.Claims["apps"].([]interface{}) + if !ok { + return fmt.Errorf("Invalid type of apps claim: %T", parsed.Claims["apps"]) + } + + for _, a := range apps { + if s, ok := a.(string); ok && s == fmt.Sprintf("%X", appEUI) { + return nil + } + } + + return errors.New(errors.Operational, "Unauthorized") } From 4467c1be8c3db398f8b02cb741f47157c9851e84 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 30 Mar 2016 18:46:16 +0800 Subject: [PATCH 1246/2266] Added handler MQTT credentials --- cmd/handler.go | 6 ++++++ core/adapters/mqtt/client.go | 12 +++++++----- core/adapters/mqtt/client_test.go | 10 +++++----- core/adapters/mqtt/mqtt.go | 2 +- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index ee0d1149a..d321fa835 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -112,6 +112,8 @@ The Handler is the bridge between The Things Network and applications. mqttClient, chmsg, err := mqtt.NewClient( "handler-client", viper.GetString("handler.mqtt-broker"), + viper.GetString("handler.mqtt-username"), + viper.GetString("handler.mqtt-password"), ctx.WithField("adapter", "app-adapter"), ) if err != nil { @@ -172,7 +174,11 @@ func init() { viper.BindPFlag("handler.public-port", handlerCmd.Flags().Lookup("public-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker (uplink)") + handlerCmd.Flags().String("mqtt-username", "", "The username for the MQTT broker (uplink)") + handlerCmd.Flags().String("mqtt-password", "", "The password for the MQTT broker (uplink)") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) + viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) + viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) handlerCmd.Flags().String("ttn-broker", "localhost:1781", "The address of the TTN broker (downlink)") viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go index 89ce660a8..9ef5da1c9 100644 --- a/core/adapters/mqtt/client.go +++ b/core/adapters/mqtt/client.go @@ -28,14 +28,14 @@ type Client interface { type connecter func() error // NewClient creates and connects a mqtt client with predefined options. -func NewClient(id string, netAddr string, ctx log.Interface) (Client, chan Msg, error) { +func NewClient(id, netAddr, username, password string, ctx log.Interface) (Client, chan Msg, error) { ctx = ctx.WithField("id", id).WithField("address", netAddr) chcmd := make(chan interface{}) chmsg := make(chan Msg) go monitorClient(id, netAddr, chcmd, ctx) - tryConnect := createConnecter(id, netAddr, chmsg, chcmd, ctx) + tryConnect := createConnecter(id, netAddr, username, password, chmsg, chcmd, ctx) if err := tryConnect(); err != nil { close(chcmd) return nil, nil, errors.New(errors.Operational, err) @@ -85,7 +85,7 @@ func monitorClient(id string, netAddr string, chcmd <-chan interface{}, ctx log. cli = cmd.options cmd.cherr <- nil default: - ctx.WithField("cmd", cmd).Warn("Received unreckognized command") + ctx.WithField("cmd", cmd).Warn("Received unrecognized command") } } @@ -99,12 +99,12 @@ func monitorClient(id string, netAddr string, chcmd <-chan interface{}, ctx log. // createConnecter is used to start and subscribe a new client. It also make sure that if the // created client goes down, another one is automatically created such that the client recover // itself. -func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- interface{}, ctx log.Interface) connecter { +func createConnecter(id, netAddr, username, password string, chmsg chan<- Msg, chcmd chan<- interface{}, ctx log.Interface) connecter { ctx.Debug("Create new connecter for MQTT client") var cli *client.Client cli = client.New(&client.Options{ ErrorHandler: createErrorHandler( - func() error { return createConnecter(id, netAddr, chmsg, chcmd, ctx)() }, + func() error { return createConnecter(id, netAddr, username, password, chmsg, chcmd, ctx)() }, 10000*InitialReconnectDelay, ctx, ), @@ -116,6 +116,8 @@ func createConnecter(id string, netAddr string, chmsg chan<- Msg, chcmd chan<- i Network: "tcp", Address: netAddr, ClientID: []byte(id), + UserName: []byte(username), + Password: []byte(password), }) if err != nil { diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go index ab403ff19..2cf91f9c3 100644 --- a/core/adapters/mqtt/client_test.go +++ b/core/adapters/mqtt/client_test.go @@ -139,7 +139,7 @@ func TestNewClient(t *testing.T) { Desc(t, "Create client with invalid address") // Build - _, _, err := NewClient(newID(), "invalidAddress", GetLogger(t, "Test Logger")) + _, _, err := NewClient(newID(), "invalidAddress", "", "", GetLogger(t, "Test Logger")) // Check CheckErrors(t, ErrOperational, err) @@ -151,7 +151,7 @@ func TestNewClient(t *testing.T) { Desc(t, "Connect a client and receive a down msg") // Build - cli, chmsg, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + cli, chmsg, err := NewClient(newID(), BrokerAddr, "", "", GetLogger(t, "Test Logger")) FatalUnless(t, err) msg := Msg{ Type: Down, @@ -186,7 +186,7 @@ func TestNewClient(t *testing.T) { Desc(t, "Connect a client and send on a random topic") // Build - cli, chmsg, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + cli, chmsg, err := NewClient(newID(), BrokerAddr, "", "", GetLogger(t, "Test Logger")) FatalUnless(t, err) var msg Msg @@ -218,7 +218,7 @@ func TestNewClient(t *testing.T) { // Build id := newID() - cli, chmsg, err := NewClient(id, BrokerAddr, GetLogger(t, "Test Logger")) + cli, chmsg, err := NewClient(id, BrokerAddr, "", "", GetLogger(t, "Test Logger")) FatalUnless(t, err) msg := Msg{ Type: Down, @@ -269,7 +269,7 @@ func TestNewClient(t *testing.T) { Topic: "topic", Payload: []byte{14, 42}, } - cli, _, err := NewClient(newID(), BrokerAddr, GetLogger(t, "Test Logger")) + cli, _, err := NewClient(newID(), BrokerAddr, "", "", GetLogger(t, "Test Logger")) FatalUnless(t, err) err = testCli.Subscribe(&client.SubscribeOptions{ SubReqs: []*client.SubReq{ diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go index e70365ba6..4153ff19d 100644 --- a/core/adapters/mqtt/mqtt.go +++ b/core/adapters/mqtt/mqtt.go @@ -103,7 +103,7 @@ func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grp } // Actually send it - ctx.Debug("Sending Packet") + ctx.Debug("Sending packet") deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) err = a.Client.Publish(&client.PublishOptions{ QoS: mqtt.QoS2, From 7cba2e75ce5add2f8530ae49136f6eafcb74cc40 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Mar 2016 14:29:48 +0200 Subject: [PATCH 1247/2266] [cli] Move log handler --- cmd/root.go | 4 ++-- cmd/router.go | 2 +- ttnctl/cmd/root.go | 6 ++---- utils/cli/{ => handler}/cli.go | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) rename utils/cli/{ => handler}/cli.go (99%) diff --git a/cmd/root.go b/cmd/root.go index 17d298e07..3adf61614 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,7 +8,7 @@ import ( "os" "strings" - "github.com/TheThingsNetwork/ttn/utils/cli" + cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -30,7 +30,7 @@ var RootCmd = &cobra.Command{ } ctx = &log.Logger{ Level: logLevel, - Handler: cli.New(os.Stdout), + Handler: cliHandler.New(os.Stdout), } }, } diff --git a/cmd/router.go b/cmd/router.go index bbd0e0472..0d37d1c01 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -204,6 +204,6 @@ func init() { viper.BindPFlag("router.downlink-address", routerCmd.Flags().Lookup("downlink-address")) viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) - routerCmd.Flags().String("brokers", ":1881", "Comma-separated list of brokers") + routerCmd.Flags().String("brokers", "localhost:1881", "Comma-separated list of brokers") viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) } diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 89ea8aef7..13d6271be 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -8,8 +8,8 @@ import ( "os" "strings" + cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" - "github.com/apex/log/handlers/cli" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -28,11 +28,9 @@ var RootCmd = &cobra.Command{ if viper.GetBool("debug") { logLevel = log.DebugLevel } - cli.Colors[log.DebugLevel] = 90 - cli.Colors[log.InfoLevel] = 32 ctx = &log.Logger{ Level: logLevel, - Handler: cli.New(os.Stdout), + Handler: cliHandler.New(os.Stdout), } }, } diff --git a/utils/cli/cli.go b/utils/cli/handler/cli.go similarity index 99% rename from utils/cli/cli.go rename to utils/cli/handler/cli.go index acca9c68e..f16c289ec 100644 --- a/utils/cli/cli.go +++ b/utils/cli/handler/cli.go @@ -1,4 +1,4 @@ -package cli +package handler import ( "fmt" From 1095033dbfe44f01009a6a57265c5c9e0de77263 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Mar 2016 13:38:09 +0200 Subject: [PATCH 1248/2266] Move random util to separate package --- ttnctl/cmd/join.go | 15 +++++++------ ttnctl/cmd/uplink.go | 13 ++++++----- ttnctl/mqtt/client.go | 4 ++-- {ttnctl/util => utils/random}/random.go | 30 ++++++++++++------------- 4 files changed, 32 insertions(+), 30 deletions(-) rename {ttnctl/util => utils/random}/random.go (73%) diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 5a28b7a34..3c751fe61 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -52,7 +53,7 @@ var joinCmd = &cobra.Command{ // Generate a DevNonce var devNonce [2]byte - copy(devNonce[:], util.RandToken()) + copy(devNonce[:], random.Token()) // Lorawan Payload joinPayload := lorawan.JoinRequestPayload{ @@ -186,17 +187,17 @@ var joinCmd = &cobra.Command{ encoded := strings.Trim(base64.StdEncoding.EncodeToString(data), "=") payload := semtech.Packet{ Identifier: semtech.PUSH_DATA, - Token: util.RandToken(), + Token: random.Token(), GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, Version: semtech.VERSION, Payload: &semtech.Payload{ RXPK: []semtech.RXPK{ { - Rssi: pointer.Int32(util.RandRssi()), - Lsnr: pointer.Float32(util.RandLsnr()), - Freq: pointer.Float32(util.RandFreq()), - Datr: pointer.String(util.RandDatr()), - Codr: pointer.String(util.RandCodr()), + Rssi: pointer.Int32(random.Rssi()), + Lsnr: pointer.Float32(random.Lsnr()), + Freq: pointer.Float32(random.Freq()), + Datr: pointer.String(random.Datr()), + Codr: pointer.String(random.Codr()), Modu: pointer.String("LoRa"), Tmst: pointer.Uint32(1), Data: &encoded, diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index ae5ae47d4..0d07a0a4a 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -186,17 +187,17 @@ var uplinkCmd = &cobra.Command{ encoded := strings.Trim(base64.StdEncoding.EncodeToString(data), "=") payload := semtech.Packet{ Identifier: semtech.PUSH_DATA, - Token: util.RandToken(), + Token: random.Token(), GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, Version: semtech.VERSION, Payload: &semtech.Payload{ RXPK: []semtech.RXPK{ { - Rssi: pointer.Int32(util.RandRssi()), - Lsnr: pointer.Float32(util.RandLsnr()), - Freq: pointer.Float32(util.RandFreq()), - Datr: pointer.String(util.RandDatr()), - Codr: pointer.String(util.RandCodr()), + Rssi: pointer.Int32(random.Rssi()), + Lsnr: pointer.Float32(random.Lsnr()), + Freq: pointer.Float32(random.Freq()), + Datr: pointer.String(random.Datr()), + Codr: pointer.String(random.Codr()), Modu: pointer.String("LoRa"), Tmst: pointer.Uint32(1), Data: &encoded, diff --git a/ttnctl/mqtt/client.go b/ttnctl/mqtt/client.go index 657508191..e772af280 100644 --- a/ttnctl/mqtt/client.go +++ b/ttnctl/mqtt/client.go @@ -7,7 +7,7 @@ import ( "fmt" MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" ) @@ -23,7 +23,7 @@ func Setup(broker string, _ctx log.Interface) { ctx = _ctx mqttOpts := MQTT.NewClientOptions().AddBroker(fmt.Sprintf("tcp://%s", broker)) - clientID := fmt.Sprintf("ttnctl-%s", util.RandString(16)) + clientID := fmt.Sprintf("ttnctl-%s", random.String(16)) mqttOpts.SetClientID(clientID) mqttOpts.SetKeepAlive(20) diff --git a/ttnctl/util/random.go b/utils/random/random.go similarity index 73% rename from ttnctl/util/random.go rename to utils/random/random.go index 4929fbb89..48d276233 100644 --- a/ttnctl/util/random.go +++ b/utils/random/random.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package util +package random import ( "encoding/binary" @@ -22,8 +22,8 @@ const ( var src = rand.New(rand.NewSource(time.Now().UnixNano())) -// RandString returns random string of length n -func RandString(n int) string { +// String returns random string of length n +func String(n int) string { b := make([]byte, n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { @@ -41,28 +41,28 @@ func RandString(n int) string { return string(b) } -// RandToken generate a random 2-bytes token -func RandToken() []byte { +// Token generate a random 2-bytes token +func Token() []byte { b := make([]byte, 4) binary.BigEndian.PutUint32(b, src.Uint32()) return b[0:2] } -// RandRssi generates RSSI signal between -120 < rssi < 0 -func RandRssi() int32 { +// Rssi generates RSSI signal between -120 < rssi < 0 +func Rssi() int32 { // Generate RSSI. Tend towards generating great signal strength. x := float64(src.Int31()) * float64(2e-9) return int32(-1.6 * math.Exp(x)) } -// RandFreq generates a frequency between 865.0 and 870.0 Mhz -func RandFreq() float32 { +// Freq generates a frequency between 865.0 and 870.0 Mhz +func Freq() float32 { // EU 865-870MHz return float32(src.Float64()*5 + 865.0) } -// RandDatr generates Datr for instance: SF4BW125 -func RandDatr() string { +// Datr generates Datr for instance: SF4BW125 +func Datr() string { // Spread Factor from 12 to 7 sf := 12 - src.Intn(7) var bw int @@ -76,14 +76,14 @@ func RandDatr() string { return fmt.Sprintf("SF%dBW%d", sf, bw) } -// RandCodr generates Codr for instance: 4/6 -func RandCodr() string { +// Codr generates Codr for instance: 4/6 +func Codr() string { d := src.Intn(4) + 5 return fmt.Sprintf("4/%d", d) } -// RandLsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise -func RandLsnr() float32 { +// Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise +func Lsnr() float32 { x := float64(src.Int31()) * float64(2e-9) return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) } From 6dab78b1ac241dcee583b38673e0b2c4e34de320 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Mar 2016 13:38:48 +0200 Subject: [PATCH 1249/2266] [handler] Use random MQTT ID Resolves #105 --- cmd/handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/handler.go b/cmd/handler.go index d321fa835..a318c4433 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/TheThingsNetwork/ttn/core/components/handler" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/spf13/cobra" @@ -110,7 +111,7 @@ The Handler is the bridge between The Things Network and applications. // MQTT Client & adapter mqttClient, chmsg, err := mqtt.NewClient( - "handler-client", + fmt.Sprintf("handler-%s", random.String(15)), viper.GetString("handler.mqtt-broker"), viper.GetString("handler.mqtt-username"), viper.GetString("handler.mqtt-password"), From e6695c5215d96cdcaf036c81bf295551c30b750a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Mar 2016 18:27:24 +0200 Subject: [PATCH 1250/2266] [ttnctl] Add subscribe command --- ttnctl/cmd/subscribe.go | 134 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 ttnctl/cmd/subscribe.go diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go new file mode 100644 index 000000000..dd31912f6 --- /dev/null +++ b/ttnctl/cmd/subscribe.go @@ -0,0 +1,134 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "regexp" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/howeyc/gopass" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var subscribeCmd = &cobra.Command{ + Use: "subscribe [DevEUI]", + Short: "Subscribe to uplink messages from a device", + Long: `ttnctl subscribe prints out uplink messages from a device as they arrive. + +The optional DevEUI argument can be used to only receive messages from a +specific device. By default you will receive messages from all devices of your +application.`, + Run: func(cmd *cobra.Command, args []string) { + appEUI, err := util.Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + var devEUI = "+" + if len(args) > 0 { + eui, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + devEUI = fmt.Sprintf("%X", eui) + ctx.Infof("Subscribing uplink messages from device %s", devEUI) + } else { + ctx.Infof("Subscribing to uplink messages from all devices in application %x", appEUI) + } + + t, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication token") + } + if t == nil { + ctx.Fatal("No login found. Please login with ttnctl user login [e-mail]") + } + + // NOTE: until the MQTT server supports access tokens, we'll have to ask for a password. + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal(err.Error()) + } + + broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) + opts := MQTT.NewClientOptions().AddBroker(broker) + + clientID := fmt.Sprintf("ttntool-%s", util.RandString(15)) + opts.SetClientID(clientID) + + opts.SetUsername(t.Email) + opts.SetPassword(string(password)) + + opts.SetKeepAlive(20) + + opts.SetOnConnectHandler(func(client *MQTT.Client) { + ctx.Info("Connected to The Things Network.") + }) + + opts.SetDefaultPublishHandler(func(client *MQTT.Client, msg MQTT.Message) { + t, err := api.DecodeDeviceTopic(msg.Topic()) + if err != nil { + ctx.WithError(err).Warn("There's something wrong with the MQTT topic.") + } + + ctx := ctx.WithField("DevEUI", t.DevEUI) + + dataUp := &core.DataUpAppReq{} + _, err = dataUp.UnmarshalMsg(msg.Payload()) + if err != nil { + ctx.WithError(err).Warn("Could not unmarshal uplink.") + } + + // TODO: Find out what Metadata people want to see here + + unprintable, _ := regexp.Compile(`[^[:print:]]`) + if unprintable.Match(dataUp.Payload) { + ctx.Infof("%X", dataUp.Payload) + } else { + ctx.Infof("%s", dataUp.Payload) + ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") + } + + if l := len(dataUp.Payload); l > 12 { + ctx.Warnf("Your payload has a size of %d bytes. We recommend to send no more than 12 bytes.", l) + } + + // TODO: Add warnings for airtime / duty-cycle / fair-use + + }) + + opts.SetConnectionLostHandler(func(client *MQTT.Client, err error) { + ctx.WithError(err).Error("Connection Lost. Reconnecting...") + }) + + mqttClient := MQTT.NewClient(opts) + + ctx.WithField("mqtt-broker", broker).Info("Connecting to The Things Network...") + if token := mqttClient.Connect(); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + + topic := fmt.Sprintf("%X/devices/%s/up", appEUI, devEUI) + ctx.WithField("topic", topic).Debug("Subscribing...") + + if token := mqttClient.Subscribe(topic, 2, nil); token.Wait() && token.Error() != nil { + ctx.WithField("topic", topic).WithError(token.Error()).Fatal("Could not subscribe.") + } + + ctx.WithField("topic", topic).Debug("Subscribed.") + + <-make(chan bool) + + }, +} + +func init() { + RootCmd.AddCommand(subscribeCmd) +} From fa4dadc85b56055bf99bb6bed790351876484889 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Mar 2016 14:51:17 +0200 Subject: [PATCH 1251/2266] [ttnctl-subscribe] Use correct random string --- ttnctl/cmd/subscribe.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index dd31912f6..0029a2c24 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/api" "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/howeyc/gopass" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -60,7 +61,7 @@ application.`, broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) opts := MQTT.NewClientOptions().AddBroker(broker) - clientID := fmt.Sprintf("ttntool-%s", util.RandString(15)) + clientID := fmt.Sprintf("ttntool-%s", random.String(15)) opts.SetClientID(clientID) opts.SetUsername(t.Email) From 566ef7c5bfce2f250fb7cbb17c1fb44c3fec2aa3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Mar 2016 19:27:43 +0200 Subject: [PATCH 1252/2266] Don't start Handler when MQTT user/pass are unset --- cmd/handler.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmd/handler.go b/cmd/handler.go index a318c4433..a221bb506 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -109,6 +109,10 @@ The Handler is the bridge between The Things Network and applications. ctx.WithError(err).Fatal("Could not dial broker") } + if viper.GetString("handler.mqtt-username") == "" || viper.GetString("handler.mqtt-password") == "" { + ctx.WithError(fmt.Errorf("MQTT username or password not set")).Fatal("Could not connect to MQTT") + } + // MQTT Client & adapter mqttClient, chmsg, err := mqtt.NewClient( fmt.Sprintf("handler-%s", random.String(15)), @@ -118,7 +122,7 @@ The Handler is the bridge between The Things Network and applications. ctx.WithField("adapter", "app-adapter"), ) if err != nil { - ctx.WithError(err).Fatal("Could not start MQTT client") + ctx.WithError(err).Fatal("Could not connect to MQTT") } appAdapter := mqtt.New( mqtt.Components{Ctx: ctx.WithField("adapter", "app-adapter"), Client: mqttClient}, From 8670578d0a03c17bb6f5271f69855213ee4272f0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 31 Mar 2016 11:00:19 +0200 Subject: [PATCH 1253/2266] [otaa] Calculate Session Keys --- core/otaa/session_keys.go | 42 ++++++++++++++++++++++++++++++++++ core/otaa/session_keys_test.go | 30 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 core/otaa/session_keys.go create mode 100644 core/otaa/session_keys_test.go diff --git a/core/otaa/session_keys.go b/core/otaa/session_keys.go new file mode 100644 index 000000000..d54ee3bc4 --- /dev/null +++ b/core/otaa/session_keys.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package otaa + +import ( + "crypto/aes" + + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// CalculateSessionKeys calculates the AppSKey and NwkSKey +// All arguments are MSB-first +func CalculateSessionKeys(appKey [16]byte, appNonce [3]byte, netID [3]byte, devNonce [2]byte) (appSKey, nwkSKey [16]byte, err error) { + + buf := make([]byte, 16) + copy(buf[1:4], reverse(appNonce[:])) + copy(buf[4:7], reverse(netID[:])) + copy(buf[7:9], reverse(devNonce[:])) + + block, err := aes.NewCipher(appKey[:]) + + if err != nil || block.BlockSize() != 16 { + err = errors.New(errors.Structural, "Unable to create cipher to generate keys") + return + } + + buf[0] = 0x1 + block.Encrypt(nwkSKey[:], buf) + buf[0] = 0x2 + block.Encrypt(appSKey[:], buf) + + return +} + +// reverse is used to convert between MSB-first and LSB-first +func reverse(in []byte) (out []byte) { + for i := len(in) - 1; i >= 0; i-- { + out = append(out, in[i]) + } + return +} diff --git a/core/otaa/session_keys_test.go b/core/otaa/session_keys_test.go new file mode 100644 index 000000000..a58baec47 --- /dev/null +++ b/core/otaa/session_keys_test.go @@ -0,0 +1,30 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package otaa + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestCalculateSessionKeys(t *testing.T) { + a := New(t) + + // MSB first + appKey := [16]byte{0xBE, 0xC4, 0x99, 0xC6, 0x9E, 0x9C, 0x93, 0x9E, 0x41, 0x3B, 0x66, 0x39, 0x61, 0x63, 0x6C, 0x61} + devNonce := [2]byte{0x73, 0x69} + netID := [3]byte{0x00, 0x00, 0x00} + appNonce := [3]byte{0xAE, 0x3B, 0x1C} + + appSKey, nwkSKey, err := CalculateSessionKeys(appKey, appNonce, netID, devNonce) + a.So(err, ShouldBeNil) + + // MSB first + expectedNwkSKey := [16]byte{0x33, 0xD5, 0xF3, 0x74, 0x29, 0xDA, 0x60, 0xF0, 0xA5, 0x7A, 0xB5, 0xAA, 0x06, 0x95, 0xE4, 0x98} + expectedAppSKey := [16]byte{0x71, 0x4F, 0xA5, 0x53, 0x03, 0x07, 0xD6, 0x03, 0xE8, 0x7C, 0x78, 0x65, 0xDF, 0x86, 0x2A, 0x85} + + a.So(appSKey, ShouldResemble, expectedAppSKey) + a.So(nwkSKey, ShouldResemble, expectedNwkSKey) +} From bec6f174f938b742cd5fa1604b5e1e4bfa037195 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 31 Mar 2016 11:01:40 +0200 Subject: [PATCH 1254/2266] [otaa] Use CalculateSessionKeys in Handler and ttnctl --- core/components/handler/handler.go | 33 ++++++++++++++---------------- ttnctl/cmd/join.go | 20 +++++------------- 2 files changed, 20 insertions(+), 33 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 23cb7e2f4..e9036d8de 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -5,7 +5,6 @@ package handler import ( "bytes" - "crypto/aes" "encoding/binary" "fmt" "math/rand" @@ -14,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/dutycycle" + "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" @@ -461,31 +461,28 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da } packet := bundles[best.ID].Packet.(*core.JoinHandlerReq) - // Generate a DevAddr + NwkSKey + AppSKey - ctx.Debug("Generate DevAddr, NwkSKey and AppSKey") + // Generate a DevAddr - Note: this should be done by the Broker (issue #90). Random generation should be moved to the random package rdn := rand.New(rand.NewSource(int64(packet.Metadata.Rssi))) - appNonce, devAddr := make([]byte, 4), [4]byte{} - binary.BigEndian.PutUint32(appNonce, rdn.Uint32()) + var devAddr [4]byte binary.BigEndian.PutUint32(devAddr[:], rdn.Uint32()) devAddr[0] = (h.Configuration.NetID[2] << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb - buf := make([]byte, 16) - copy(buf[1:4], appNonce[:3]) - copy(buf[4:7], h.Configuration.NetID[:]) - copy(buf[7:9], packet.DevNonce) + // Generate appNonce - Note: this should be moved to the random package + b := make([]byte, 4) + binary.BigEndian.PutUint32(b, rdn.Uint32()) + var appNonce [3]byte + copy(appNonce[:], b[:3]) - block, err := aes.NewCipher(appKey[:]) - if err != nil || block.BlockSize() != 16 { - h.abortConsume(errors.New(errors.Structural, "Unable to create cipher to generate keys"), bundles) + var devNonce [2]byte + copy(devNonce[:], packet.DevNonce) + + // Generate Session keys + appSKey, nwkSKey, err := otaa.CalculateSessionKeys(appKey, appNonce, h.Configuration.NetID, devNonce) + if err != nil { + h.abortConsume(errors.New(errors.Structural, "Unable to generate session keys"), bundles) return } - var nwkSKey, appSKey [16]byte - buf[0] = 0x1 - block.Encrypt(nwkSKey[:], buf) - buf[0] = 0x2 - block.Encrypt(appSKey[:], buf) - // Update the internal storage entry err = h.DevStorage.upsert(devEntry{ AppEUI: appEUI, diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 3c751fe61..744398fac 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -4,12 +4,12 @@ package cmd import ( - "crypto/aes" "encoding/base64" "net" "strings" "time" + "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -143,22 +143,12 @@ var joinCmd = &cobra.Command{ ctx.Fatalf("Unable to retrieve LoRaWAN Join-Accept Payload") } - buf = make([]byte, 16) - copy(buf[1:4], joinAccept.AppNonce[:]) - copy(buf[4:7], joinAccept.NetID[:]) - copy(buf[7:9], devNonce[:]) - - block, err := aes.NewCipher(appKey[:]) - if err != nil || block.BlockSize() != 16 { - ctx.Fatalf("Unable to create cipher to generate keys: %s", err) + // Generate Session keys + appSKey, nwkSKey, err := otaa.CalculateSessionKeys(appKey, joinAccept.AppNonce, joinAccept.NetID, devNonce) + if err != nil { + ctx.Fatal("Unable to compute session keys") } - var nwkSKey, appSKey [16]byte - buf[0] = 0x1 - block.Encrypt(nwkSKey[:], buf) - buf[0] = 0x2 - block.Encrypt(appSKey[:], buf) - ctx.Info("Network Joined.") ctx.Infof("Device Address: %X", joinAccept.DevAddr[:]) ctx.Infof("Network Session Key: %X", nwkSKey) From 9e711bcfa75742928166880ce55f4c58655aeb2c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 31 Mar 2016 14:23:49 +0200 Subject: [PATCH 1255/2266] [handler] Send ACK as Unconfirmed Downlink ACK should be sent as unconfirmed downlink message. Confirmed downlink depends on #116 and should be requested by the application. --- core/components/handler/handler.go | 18 +++++------------- core/components/handler/handler_test.go | 4 ++-- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index e9036d8de..df900818d 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -607,17 +607,9 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu for i, bundle := range bundles { if best != nil && best.ID == i && (downlink.Payload != nil || upType == lorawan.ConfirmedDataUp) { stats.MarkMeter("handler.downlink.pull") - var downType lorawan.MType - switch upType { - case lorawan.UnconfirmedDataUp: - downType = lorawan.UnconfirmedDataDown - case lorawan.ConfirmedDataUp: - downType = lorawan.ConfirmedDataDown - default: - h.abortConsume(errors.New(errors.Implementation, "Unrecognized uplink MType"), bundles) - return - } - downlink, err := h.buildDownlink(downlink.Payload, downType, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) + downType := lorawan.UnconfirmedDataDown + ack := (upType == lorawan.ConfirmedDataUp) + downlink, err := h.buildDownlink(downlink.Payload, downType, ack, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) if err != nil { h.abortConsume(errors.New(errors.Structural, err), bundles) return @@ -656,7 +648,7 @@ func (h component) abortConsume(err error, bundles []bundle) { // constructs a downlink packet from something we pulled from the gathered downlink, and, the actual // uplink. -func (h component) buildDownlink(down []byte, mtype lorawan.MType, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { +func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { macpayload := lorawan.NewMACPayload(false) macpayload.FHDR = lorawan.FHDR{ FCnt: entry.FCntDown + 1, @@ -664,7 +656,7 @@ func (h component) buildDownlink(down []byte, mtype lorawan.MType, up core.DataU copy(macpayload.FHDR.DevAddr[:], entry.DevAddr) macpayload.FPort = new(uint8) *macpayload.FPort = 1 - if mtype == lorawan.ConfirmedDataDown { + if ack { macpayload.FHDR.FCtrl.ACK = true } var frmpayload []byte diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 04ecac016..7382962d2 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1299,7 +1299,7 @@ func TestHandleDataUp(t *testing.T) { var wantRes = &core.DataUpHandlerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.ConfirmedDataDown), + MType: uint32(lorawan.UnconfirmedDataDown), Major: uint32(lorawan.LoRaWANR1), }, MACPayload: &core.LoRaWANMACPayload{ @@ -1401,7 +1401,7 @@ func TestHandleDataUp(t *testing.T) { var wantRes = &core.DataUpHandlerRes{ Payload: &core.LoRaWANData{ MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.ConfirmedDataDown), + MType: uint32(lorawan.UnconfirmedDataDown), Major: uint32(lorawan.LoRaWANR1), }, MACPayload: &core.LoRaWANMACPayload{ From df21eb68a5cf71740c9a00a370d77a7d57177839 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 31 Mar 2016 16:36:44 +0800 Subject: [PATCH 1256/2266] Get public OAuth key from account server, closes #106 --- cmd/broker.go | 17 ++--- core/components/broker/broker.go | 32 +++++---- core/components/broker/brokerManager.go | 17 +++-- utils/tokenkey/tokenkey.go | 86 +++++++++++++++++++++++++ 4 files changed, 125 insertions(+), 27 deletions(-) create mode 100644 utils/tokenkey/tokenkey.go diff --git a/cmd/broker.go b/cmd/broker.go index fc4fdfdf8..dbdd5971f 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -5,7 +5,6 @@ package cmd import ( "fmt" - "io/ioutil" "path" "path/filepath" "strings" @@ -14,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/adapters/http" "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" @@ -99,12 +99,6 @@ devices (with their network session keys) with the Broker. ctx.WithError(fmt.Errorf("Invalid applications database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") } - // Security - tokenKey, err := ioutil.ReadFile(viper.GetString("broker.oauth2-keyfile")) - if err != nil { - ctx.WithError(err).Fatal("Unable to load OAuth 2.0 key") - } - // Broker broker := broker.New( broker.Components{ @@ -113,9 +107,9 @@ devices (with their network session keys) with the Broker. AppStorage: dbApp, }, broker.Options{ - NetAddrUp: fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-address"), viper.GetInt("broker.uplink-port")), - NetAddrDown: fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-address"), viper.GetInt("broker.downlink-port")), - TokenKey: tokenKey, + NetAddrUp: fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-address"), viper.GetInt("broker.uplink-port")), + NetAddrDown: fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-address"), viper.GetInt("broker.downlink-port")), + TokenKeyProvider: tokenkey.NewHTTPProvider(fmt.Sprintf("%s/key", viper.GetString("broker.account-server")), viper.GetString("broker.oauth2-keyfile")), }, ) @@ -150,6 +144,9 @@ func init() { viper.BindPFlag("broker.downlink-address", brokerCmd.Flags().Lookup("downlink-address")) viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) + brokerCmd.Flags().String("account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") + viper.BindPFlag("broker.account-server", brokerCmd.Flags().Lookup("account-server")) + defaultOAuth2KeyFile := "" dir, err := homedir.Dir() if err == nil { diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 308711c91..409da8a59 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -4,11 +4,13 @@ package broker import ( + "fmt" "net" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/brocaar/lorawan" "golang.org/x/net/context" @@ -18,10 +20,10 @@ import ( // component implements the core.BrokerServer interface type component struct { Components - TokenKey []byte - NetAddrUp string - NetAddrDown string - MaxDevNonces uint + NetAddrUp string + NetAddrDown string + TokenKeyProvider tokenkey.Provider + MaxDevNonces uint } // Components defines a structure to make the instantiation easier to read @@ -33,9 +35,9 @@ type Components struct { // Options defines a structure to make the instantiation easier to read type Options struct { - NetAddrUp string - NetAddrDown string - TokenKey []byte + NetAddrUp string + NetAddrDown string + TokenKeyProvider tokenkey.Provider } // Interface defines the Broker interface @@ -48,11 +50,11 @@ type Interface interface { // New construct a new Broker component func New(c Components, o Options) Interface { return component{ - Components: c, - NetAddrUp: o.NetAddrUp, - NetAddrDown: o.NetAddrDown, - MaxDevNonces: 10, - TokenKey: o.TokenKey, + Components: c, + NetAddrUp: o.NetAddrUp, + NetAddrDown: o.NetAddrDown, + TokenKeyProvider: o.TokenKeyProvider, + MaxDevNonces: 10, } } @@ -68,6 +70,12 @@ func (b component) Start() error { return errors.New(errors.Operational, err) } + tokenKey, err := b.TokenKeyProvider.Refresh() + if err != nil { + return errors.New(errors.Operational, fmt.Sprintf("Failed to refresh token key: %s", err.Error())) + } + b.Ctx.WithField("provider", b.TokenKeyProvider).Infof("Got token key for algorithm %v", tokenKey.Algorithm) + server := grpc.NewServer() core.RegisterBrokerServer(server, b) core.RegisterBrokerManagerServer(server, b) diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index e6741fea3..900cf83bd 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -101,16 +101,23 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) // validateToken verify an OAuth Bearer token pass through metadata during RPC func (b component) validateToken(ctx context.Context, token string, appEUI []byte) error { parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + if b.TokenKeyProvider == nil { + return nil, errors.New(errors.Structural, "No token provider configured") } - return b.TokenKey, nil + t, err := b.TokenKeyProvider.Get() + if err != nil { + return nil, err + } + if t.Algorithm != token.Header["alg"] { + return nil, errors.New(errors.Structural, fmt.Sprintf("Expected algorithm %v but got %v", t.Algorithm, token.Header["alg"])) + } + return []byte(t.Key), nil }) if err != nil { - return errors.New(errors.Structural, "Unable to parse token") + return errors.New(errors.Structural, fmt.Sprintf("Unable to parse token: %s", err.Error())) } if !parsed.Valid { - return errors.New(errors.Operational, "The token is not valid or is expired.") + return errors.New(errors.Operational, "The token is not valid or is expired") } apps, ok := parsed.Claims["apps"].([]interface{}) diff --git a/utils/tokenkey/tokenkey.go b/utils/tokenkey/tokenkey.go new file mode 100644 index 000000000..9c396d3a2 --- /dev/null +++ b/utils/tokenkey/tokenkey.go @@ -0,0 +1,86 @@ +package tokenkey + +import ( + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "net/http" + "os" +) + +// T is the data returned by the token key provider +type T struct { + Algorithm string `json:"algorithm"` + Key string `json:"key"` +} + +// Provider represents a provider of the token key +type Provider interface { + fmt.Stringer + Get() (*T, error) + Refresh() (*T, error) +} + +type httpProvider struct { + url string + cacheFile string +} + +// NewHTTPProvider returns a new Provider that fetches the key from a HTTP +// resource +func NewHTTPProvider(url, cacheFile string) Provider { + return &httpProvider{url, cacheFile} +} + +func (p *httpProvider) String() string { + return p.url +} + +func (p *httpProvider) Get() (*T, error) { + var data []byte + + // Try to read the data from cache + d, err := ioutil.ReadFile(p.cacheFile) + if err == nil { + data = d + } + + // If the file doesn't exist or if there's a read error, get it from the + // server + if data == nil { + resp, err := http.Get(p.url) + if err != nil { + return nil, err + } + if resp.StatusCode != http.StatusOK { + return nil, errors.New(resp.Status) + } + defer resp.Body.Close() + data, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + } + + var token T + if err := json.Unmarshal(data, &token); err != nil { + return nil, err + } + + // Don't care about errors here. It's better to retrieve keys all the time + // because they can't be cached than not to be able to verify a token + ioutil.WriteFile(p.cacheFile, data, 0644) + + return &token, nil +} + +func (p *httpProvider) Refresh() (*T, error) { + // Just delete the cached file... + if err := os.Remove(p.cacheFile); err != nil && !os.IsNotExist(err) { + return nil, err + } + + // ...so that Get always gets a new file + return p.Get() +} From 8a640f9b66ac47d3fca1067ba656055a1af344e1 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 31 Mar 2016 16:43:51 +0800 Subject: [PATCH 1257/2266] Fix for testing; provider can be null --- core/components/broker/broker.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 409da8a59..0524a581a 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -70,11 +70,13 @@ func (b component) Start() error { return errors.New(errors.Operational, err) } - tokenKey, err := b.TokenKeyProvider.Refresh() - if err != nil { - return errors.New(errors.Operational, fmt.Sprintf("Failed to refresh token key: %s", err.Error())) + if b.TokenKeyProvider != nil { + tokenKey, err := b.TokenKeyProvider.Refresh() + if err != nil { + return errors.New(errors.Operational, fmt.Sprintf("Failed to refresh token key: %s", err.Error())) + } + b.Ctx.WithField("provider", b.TokenKeyProvider).Infof("Got token key for algorithm %v", tokenKey.Algorithm) } - b.Ctx.WithField("provider", b.TokenKeyProvider).Infof("Got token key for algorithm %v", tokenKey.Algorithm) server := grpc.NewServer() core.RegisterBrokerServer(server, b) From 287ab0392c2e82c17aa29b6e0ccb2acd6d35e3f4 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 31 Mar 2016 16:52:47 +0800 Subject: [PATCH 1258/2266] Renamed T to K 'cause it's a key --- utils/tokenkey/tokenkey.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/utils/tokenkey/tokenkey.go b/utils/tokenkey/tokenkey.go index 9c396d3a2..742b5672a 100644 --- a/utils/tokenkey/tokenkey.go +++ b/utils/tokenkey/tokenkey.go @@ -9,8 +9,8 @@ import ( "os" ) -// T is the data returned by the token key provider -type T struct { +// K is the data returned by the token key provider +type K struct { Algorithm string `json:"algorithm"` Key string `json:"key"` } @@ -18,8 +18,8 @@ type T struct { // Provider represents a provider of the token key type Provider interface { fmt.Stringer - Get() (*T, error) - Refresh() (*T, error) + Get() (*K, error) + Refresh() (*K, error) } type httpProvider struct { @@ -37,7 +37,7 @@ func (p *httpProvider) String() string { return p.url } -func (p *httpProvider) Get() (*T, error) { +func (p *httpProvider) Get() (*K, error) { var data []byte // Try to read the data from cache @@ -63,8 +63,8 @@ func (p *httpProvider) Get() (*T, error) { } } - var token T - if err := json.Unmarshal(data, &token); err != nil { + var key K + if err := json.Unmarshal(data, &key); err != nil { return nil, err } @@ -72,10 +72,10 @@ func (p *httpProvider) Get() (*T, error) { // because they can't be cached than not to be able to verify a token ioutil.WriteFile(p.cacheFile, data, 0644) - return &token, nil + return &key, nil } -func (p *httpProvider) Refresh() (*T, error) { +func (p *httpProvider) Refresh() (*K, error) { // Just delete the cached file... if err := os.Remove(p.cacheFile); err != nil && !os.IsNotExist(err) { return nil, err From 4074373506b3f59c1bac7c2654fcd7890d1ef40d Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 31 Mar 2016 21:48:57 +0800 Subject: [PATCH 1259/2266] Do not require fetching the token key online on start-up --- core/components/broker/broker.go | 2 +- core/components/broker/brokerManager.go | 8 ++-- utils/tokenkey/tokenkey.go | 58 ++++++++++++------------- 3 files changed, 32 insertions(+), 36 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 0524a581a..3f8accf04 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -71,7 +71,7 @@ func (b component) Start() error { } if b.TokenKeyProvider != nil { - tokenKey, err := b.TokenKeyProvider.Refresh() + tokenKey, err := b.TokenKeyProvider.Get(true) if err != nil { return errors.New(errors.Operational, fmt.Sprintf("Failed to refresh token key: %s", err.Error())) } diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 900cf83bd..5c27ff512 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -104,14 +104,14 @@ func (b component) validateToken(ctx context.Context, token string, appEUI []byt if b.TokenKeyProvider == nil { return nil, errors.New(errors.Structural, "No token provider configured") } - t, err := b.TokenKeyProvider.Get() + k, err := b.TokenKeyProvider.Get(false) if err != nil { return nil, err } - if t.Algorithm != token.Header["alg"] { - return nil, errors.New(errors.Structural, fmt.Sprintf("Expected algorithm %v but got %v", t.Algorithm, token.Header["alg"])) + if k.Algorithm != token.Header["alg"] { + return nil, errors.New(errors.Structural, fmt.Sprintf("Expected algorithm %v but got %v", k.Algorithm, token.Header["alg"])) } - return []byte(t.Key), nil + return []byte(k.Key), nil }) if err != nil { return errors.New(errors.Structural, fmt.Sprintf("Unable to parse token: %s", err.Error())) diff --git a/utils/tokenkey/tokenkey.go b/utils/tokenkey/tokenkey.go index 742b5672a..61f511bbb 100644 --- a/utils/tokenkey/tokenkey.go +++ b/utils/tokenkey/tokenkey.go @@ -6,7 +6,6 @@ import ( "fmt" "io/ioutil" "net/http" - "os" ) // K is the data returned by the token key provider @@ -18,8 +17,7 @@ type K struct { // Provider represents a provider of the token key type Provider interface { fmt.Stringer - Get() (*K, error) - Refresh() (*K, error) + Get(renew bool) (*K, error) } type httpProvider struct { @@ -37,29 +35,25 @@ func (p *httpProvider) String() string { return p.url } -func (p *httpProvider) Get() (*K, error) { +func (p *httpProvider) Get(renew bool) (*K, error) { var data []byte - // Try to read the data from cache - d, err := ioutil.ReadFile(p.cacheFile) + // Try to read from cache + cached, err := ioutil.ReadFile(p.cacheFile) if err == nil { - data = d + data = cached } - // If the file doesn't exist or if there's a read error, get it from the - // server - if data == nil { - resp, err := http.Get(p.url) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - return nil, errors.New(resp.Status) - } - defer resp.Body.Close() - data, err = ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err + // Fetch token if there's a renew or if there's no key cached + if renew || data == nil { + fetched, err := p.fetch() + if err == nil { + data = fetched + // Don't care about errors here. It's better to retrieve keys all the time + // because they can't be cached than not to be able to verify a token + ioutil.WriteFile(p.cacheFile, data, 0644) + } else if data == nil { + return nil, err // We don't have a key here } } @@ -68,19 +62,21 @@ func (p *httpProvider) Get() (*K, error) { return nil, err } - // Don't care about errors here. It's better to retrieve keys all the time - // because they can't be cached than not to be able to verify a token - ioutil.WriteFile(p.cacheFile, data, 0644) - return &key, nil } -func (p *httpProvider) Refresh() (*K, error) { - // Just delete the cached file... - if err := os.Remove(p.cacheFile); err != nil && !os.IsNotExist(err) { +func (p *httpProvider) fetch() ([]byte, error) { + resp, err := http.Get(p.url) + if err != nil { return nil, err } - - // ...so that Get always gets a new file - return p.Get() + if resp.StatusCode != http.StatusOK { + return nil, errors.New(resp.Status) + } + defer resp.Body.Close() + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + return data, nil } From 074cb69a856b7bfcd44ef5b3cc33d20412ebda05 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 14:11:34 +0200 Subject: [PATCH 1260/2266] [mqtt] Add TTN MQTT client --- mqtt/client.go | 233 ++++++++++++++++++++++ mqtt/client_test.go | 461 ++++++++++++++++++++++++++++++++++++++++++++ mqtt/topics.go | 60 ++++++ mqtt/topics_test.go | 101 ++++++++++ 4 files changed, 855 insertions(+) create mode 100644 mqtt/client.go create mode 100644 mqtt/client_test.go create mode 100644 mqtt/topics.go create mode 100644 mqtt/topics_test.go diff --git a/mqtt/client.go b/mqtt/client.go new file mode 100644 index 000000000..2e66c126c --- /dev/null +++ b/mqtt/client.go @@ -0,0 +1,233 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "time" + + MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" +) + +const QoS = 0x02 + +// Client connects to the MQTT server and can publish/subscribe on uplink, downlink and activations from devices +type Client interface { + Connect() error + Disconnect() + + IsConnected() bool + + // Uplink pub/sub + PublishUplink(appEUI []byte, devEUI []byte, payload core.DataUpAppReq) Token + SubscribeUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token + SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token + + // Downlink pub/sub + PublishDownlink(appEUI []byte, devEUI []byte, payload core.DataDownAppReq) Token + SubscribeDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token + SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token + + // Activation pub/sub + PublishActivation(appEUI []byte, devEUI []byte, payload core.OTAAAppReq) Token + SubscribeActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token + SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token +} + +type Token interface { + Wait() bool + Error() error +} + +type simpleToken struct { + err error +} + +func (t *simpleToken) Wait() bool { + return (t.err == nil) +} + +func (t *simpleToken) Error() error { + return t.err +} + +type UplinkHandler func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) +type DownlinkHandler func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) +type ActivationHandler func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) + +type defaultClient struct { + mqtt *MQTT.Client + ctx log.Interface +} + +func NewClient(ctx log.Interface, id, username, password string, brokers ...string) Client { + mqttOpts := MQTT.NewClientOptions() + + for _, broker := range brokers { + mqttOpts.AddBroker(broker) + } + + mqttOpts.SetClientID(fmt.Sprintf("%s-%s", id, random.String(16))) + mqttOpts.SetUsername(username) + mqttOpts.SetPassword(password) + + // TODO: Some tuning of these values probably won't hurt: + mqttOpts.SetKeepAlive(30 * time.Second) + mqttOpts.SetPingTimeout(10 * time.Second) + + mqttOpts.SetDefaultPublishHandler(func(client *MQTT.Client, msg MQTT.Message) { + if ctx != nil { + ctx.WithField("message", msg).Debug("Received unhandled message") + } + }) + + mqttOpts.SetConnectionLostHandler(func(client *MQTT.Client, err error) { + if ctx != nil { + ctx.WithError(err).Debug("Disconnected, reconnecting...") + } + }) + + mqttOpts.SetOnConnectHandler(func(client *MQTT.Client) { + if ctx != nil { + ctx.Debug("Connected") + } + }) + + return &defaultClient{ + mqtt: MQTT.NewClient(mqttOpts), + ctx: ctx, + } +} + +func (c *defaultClient) Connect() error { + if c.mqtt.IsConnected() { + return nil + } + if token := c.mqtt.Connect(); token.Wait() && token.Error() != nil { + return fmt.Errorf("Could not connect: %s", token.Error()) + } + return nil +} + +func (c *defaultClient) Disconnect() { + if !c.mqtt.IsConnected() { + return + } + c.mqtt.Disconnect(25) +} + +func (c *defaultClient) IsConnected() bool { + return c.mqtt.IsConnected() +} + +func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) Token { + topic := Topic{appEUI, devEUI, Uplink} + msg, err := dataUp.MarshalMsg(nil) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.mqtt.Publish(topic.String(), QoS, false, msg) +} + +func (c *defaultClient) SubscribeUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token { + topic := Topic{appEUI, devEUI, Uplink} + return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseTopic(msg.Topic()) + if err != nil && c.ctx != nil { + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") + return + } + + // Unmarshal the payload + dataUp := &core.DataUpAppReq{} + _, err = dataUp.UnmarshalMsg(msg.Payload()) + if err != nil && c.ctx != nil { + c.ctx.WithError(err).Warn("Could not unmarshal uplink") + return + } + + // Call the uplink handler + handler(c, topic.AppEUI, topic.DevEUI, *dataUp) + }) +} + +func (c *defaultClient) SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token { + return c.SubscribeUplink(appEUI, []byte{}, handler) +} + +func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown core.DataDownAppReq) Token { + topic := Topic{appEUI, devEUI, Downlink} + msg, err := dataDown.MarshalMsg(nil) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.mqtt.Publish(topic.String(), QoS, false, msg) +} + +func (c *defaultClient) SubscribeDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token { + topic := Topic{appEUI, devEUI, Downlink} + return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseTopic(msg.Topic()) + if err != nil && c.ctx != nil { + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") + return + } + + // Unmarshal the payload + dataDown := &core.DataDownAppReq{} + _, err = dataDown.UnmarshalMsg(msg.Payload()) + if err != nil && c.ctx != nil { + c.ctx.WithError(err).Warn("Could not unmarshal Downlink") + return + } + + // Call the Downlink handler + handler(c, topic.AppEUI, topic.DevEUI, *dataDown) + }) +} + +func (c *defaultClient) SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token { + return c.SubscribeDownlink(appEUI, []byte{}, handler) +} + +func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activation core.OTAAAppReq) Token { + topic := Topic{appEUI, devEUI, Activations} + msg, err := activation.MarshalMsg(nil) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.mqtt.Publish(topic.String(), QoS, false, msg) +} + +func (c *defaultClient) SubscribeActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token { + topic := Topic{appEUI, devEUI, Activations} + return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseTopic(msg.Topic()) + if err != nil && c.ctx != nil { + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") + return + } + + // Unmarshal the payload + activation := &core.OTAAAppReq{} + _, err = activation.UnmarshalMsg(msg.Payload()) + if err != nil && c.ctx != nil { + c.ctx.WithError(err).Warn("Could not unmarshal Activation") + return + } + + // Call the Activation handler + handler(c, topic.AppEUI, topic.DevEUI, *activation) + }) +} + +func (c *defaultClient) SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token { + return c.SubscribeActivations(appEUI, []byte{}, handler) +} diff --git a/mqtt/client_test.go b/mqtt/client_test.go new file mode 100644 index 000000000..91f46e36c --- /dev/null +++ b/mqtt/client_test.go @@ -0,0 +1,461 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "sync" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestToken(t *testing.T) { + a := New(t) + + okToken := simpleToken{} + a.So(okToken.Wait(), ShouldBeTrue) + a.So(okToken.Error(), ShouldBeNil) + + failToken := simpleToken{fmt.Errorf("Err")} + a.So(failToken.Wait(), ShouldBeFalse) + a.So(failToken.Error(), ShouldNotBeNil) +} + +func TestNewClient(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + a.So(c.(*defaultClient).mqtt, ShouldNotBeNil) +} + +func TestConnect(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + err := c.Connect() + a.So(err, ShouldBeNil) + + // Connecting while already connected should not change anything + err = c.Connect() + a.So(err, ShouldBeNil) +} + +func TestConnectInvalidAddress(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 + err := c.Connect() + a.So(err, ShouldNotBeNil) +} + +func TestConnectInvalidCredentials(t *testing.T) { + t.Skipf("Need authenticated MQTT for TestConnectInvalidCredentials - Skipping") +} + +func TestIsConnected(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + + a.So(c.IsConnected(), ShouldBeTrue) +} + +func TestDisconnect(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + + // Disconnecting when not connected should not change anything + c.Disconnect() + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + c.Disconnect() + + a.So(c.IsConnected(), ShouldBeFalse) +} + +func TestRandomTopicPublish(t *testing.T) { + ctx := GetLogger(t, "TestInvalidUplink") + + c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") + c.Connect() + + c.(*defaultClient).mqtt.Subscribe("randomtopic", QoS, nil).Wait() + c.(*defaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}).Wait() + + <-time.After(25 * time.Millisecond) + + ctx.Info("This test should have printed one message.") +} + +// Uplink pub/sub + +func TestPublishUplink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + dataUp := core.DataUpAppReq{ + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + } + + token := c.PublishUplink(eui, eui, dataUp) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeUplink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + token := c.SubscribeUplink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppUplink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + token := c.SubscribeAppUplink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubUplink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + appEUI := []byte{0x04, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + + var wg sync.WaitGroup + + wg.Add(1) + + c.SubscribeUplink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + a.So(appEUI, ShouldResemble, appEUI) + a.So(devEUI, ShouldResemble, devEUI) + + wg.Done() + }).Wait() + + c.PublishUplink(appEUI, devEUI, core.DataUpAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + + wg.Wait() +} + +func TestPubSubAppUplink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + appEUI := []byte{0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI1 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + devEUI2 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} + + var wg sync.WaitGroup + + wg.Add(2) + + c.SubscribeAppUplink(appEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + a.So(appEUI, ShouldResemble, appEUI) + a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + wg.Done() + }).Wait() + + c.PublishUplink(appEUI, devEUI1, core.DataUpAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink(appEUI, devEUI2, core.DataUpAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + + wg.Wait() +} + +func TestInvalidUplink(t *testing.T) { + ctx := GetLogger(t, "TestInvalidUplink") + + c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x06, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + c.SubscribeAppUplink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + Ko(t, "Did not expect any message") + }).Wait() + + // Invalid Topic + c.(*defaultClient).mqtt.Publish("0602030405060708/devices/x/up", QoS, false, []byte{0x00}).Wait() + + // Invalid Payload + c.(*defaultClient).mqtt.Publish("0602030405060708/devices/0602030405060708/up", QoS, false, []byte{0x00}).Wait() + + <-time.After(25 * time.Millisecond) + + ctx.Info("This test should have printed two warnings.") +} + +// Downlink pub/sub + +func TestPublishDownlink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x01, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + dataDown := core.DataDownAppReq{ + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + } + + token := c.PublishDownlink(eui, eui, dataDown) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + token := c.SubscribeDownlink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppDownlink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x03, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + token := c.SubscribeAppDownlink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubDownlink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + appEUI := []byte{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + + var wg sync.WaitGroup + + wg.Add(1) + + c.SubscribeDownlink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + a.So(appEUI, ShouldResemble, appEUI) + a.So(devEUI, ShouldResemble, devEUI) + + wg.Done() + }).Wait() + + c.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + + wg.Wait() +} + +func TestPubSubAppDownlink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + appEUI := []byte{0x05, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI1 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + devEUI2 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} + + var wg sync.WaitGroup + + wg.Add(2) + + c.SubscribeAppDownlink(appEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + a.So(appEUI, ShouldResemble, appEUI) + a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + wg.Done() + }).Wait() + + c.PublishDownlink(appEUI, devEUI1, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink(appEUI, devEUI2, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + + wg.Wait() +} + +func TestInvalidDownlink(t *testing.T) { + ctx := GetLogger(t, "TestInvalidDownlink") + + c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x06, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + c.SubscribeAppDownlink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + Ko(t, "Did not expect any message") + }).Wait() + + // Invalid Topic + c.(*defaultClient).mqtt.Publish("0603030405060708/devices/x/down", QoS, false, []byte{0x00}).Wait() + + // Invalid Payload + c.(*defaultClient).mqtt.Publish("0603030405060708/devices/0602030405060708/down", QoS, false, []byte{0x00}).Wait() + + <-time.After(25 * time.Millisecond) + + ctx.Info("This test should have printed two warnings.") +} + +// Activations pub/sub + +func TestPublishActivations(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x01, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + dataActivations := core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}} + + token := c.PublishActivation(eui, eui, dataActivations) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeActivations(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + token := c.SubscribeActivations(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppActivations(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x03, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + token := c.SubscribeAppActivations(eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubActivations(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + appEUI := []byte{0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + + var wg sync.WaitGroup + + wg.Add(1) + + c.SubscribeActivations(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + a.So(appEUI, ShouldResemble, appEUI) + a.So(devEUI, ShouldResemble, devEUI) + + wg.Done() + }).Wait() + + c.PublishActivation(appEUI, devEUI, core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}}).Wait() + + wg.Wait() +} + +func TestPubSubAppActivations(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + appEUI := []byte{0x05, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI1 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + devEUI2 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} + + var wg sync.WaitGroup + + wg.Add(2) + + c.SubscribeAppActivations(appEUI, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + a.So(appEUI, ShouldResemble, appEUI) + a.So(req.Metadata[0].DataRate, ShouldEqual, "SF7BW125") + wg.Done() + }).Wait() + + c.PublishActivation(appEUI, devEUI1, core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation(appEUI, devEUI2, core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}}).Wait() + + wg.Wait() +} + +func TestInvalidActivations(t *testing.T) { + ctx := GetLogger(t, "TestInvalidActivations") + + c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") + c.Connect() + + eui := []byte{0x06, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + c.SubscribeAppActivations(eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + Ko(t, "Did not expect any message") + }).Wait() + + // Invalid Topic + c.(*defaultClient).mqtt.Publish("0604030405060708/devices/x/activations", QoS, false, []byte{0x00}).Wait() + + // Invalid Payload + c.(*defaultClient).mqtt.Publish("0604030405060708/devices/0602030405060708/activations", QoS, false, []byte{0x00}).Wait() + + <-time.After(25 * time.Millisecond) + + ctx.Info("This test should have printed two warnings.") +} diff --git a/mqtt/topics.go b/mqtt/topics.go new file mode 100644 index 000000000..63131aba3 --- /dev/null +++ b/mqtt/topics.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/hex" + "fmt" + "regexp" +) + +// TopicType represents the type of a device topic +type TopicType string + +const ( + // Activations of devices + Activations TopicType = "activations" + // Uplink data from devices + Uplink TopicType = "up" + // Downlink data to devices + Downlink TopicType = "down" +) + +// Topic represents an MQTT topic for application devices +// If the DevEUI is an empty []byte{}, it is considered to be a wildcard +type Topic struct { + AppEUI []byte + DevEUI []byte + Type TopicType +} + +// ParseTopic parses an MQTT topic string to a Topic struct +func ParseTopic(topic string) (*Topic, error) { + pattern := regexp.MustCompile("([0-9A-F]{16})/(devices)/([0-9A-F]{16}|\\+)/(activations|up|down)") + matches := pattern.FindStringSubmatch(topic) + + if len(matches) < 4 { + return nil, fmt.Errorf("Invalid topic format") + } + + appEUI, _ := hex.DecodeString(matches[1]) // validity asserted by our regex pattern + + devEUI := []byte{} + if matches[3] != "+" { + devEUI, _ = hex.DecodeString(matches[3]) // validity asserted by our regex pattern + } + + topicType := TopicType(matches[4]) + + return &Topic{appEUI, devEUI, topicType}, nil +} + +// String implements the Stringer interface +func (t Topic) String() string { + devEUI := "+" + if len(t.DevEUI) > 0 { + devEUI = fmt.Sprintf("%X", t.DevEUI) + } + return fmt.Sprintf("%X/%s/%s/%s", t.AppEUI, "devices", devEUI, t.Type) +} diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go new file mode 100644 index 000000000..b758a475d --- /dev/null +++ b/mqtt/topics_test.go @@ -0,0 +1,101 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestParseTopic(t *testing.T) { + a := New(t) + + topic := "0102030405060708/devices/0807060504030201/up" + + expected := &Topic{ + AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + DevEUI: []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, + Type: Uplink, + } + + got, err := ParseTopic(topic) + + a.So(err, ShouldBeNil) + a.So(got, ShouldResemble, expected) +} + +func TestParseTopicInvalid(t *testing.T) { + a := New(t) + + _, err := ParseTopic("000000000000000a/devices/0807060504030201/up") // AppEUI contains lowercase hex chars + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("01020304050607/devices/0807060504030201/up") // AppEUI too short + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("abcdefghijklmnop/devices/08070605040302/up") // AppEUI contains invalid characters + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("0102030405060708/devices/000000000000000a/up") // DevEUI contains lowercase hex chars + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("0102030405060708/devices/08070605040302/up") // DevEUI too short + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("0102030405060708/devices/abcdefghijklmnop/up") // DevEUI contains invalid characters + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("0102030405060708/fridges/0102030405060708/up") // We don't support fridges (at least, not specifically fridges) + a.So(err, ShouldNotBeNil) + + _, err = ParseTopic("0102030405060708/devices/0102030405060708/emotions") // Devices usually don't publish emotions + a.So(err, ShouldNotBeNil) +} + +func TestTopicString(t *testing.T) { + a := New(t) + + topic := &Topic{ + AppEUI: []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, + DevEUI: []byte{0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21}, + Type: Downlink, + } + + expected := "123456789ABCDEF0/devices/2827262524232221/down" + + got := topic.String() + + a.So(got, ShouldResemble, expected) +} + +func TestTopicParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + // Uppercase (not lowercase) + "0102030405060708/devices/ABCDABCD12345678/up", + "0102030405060708/devices/ABCDABCD12345678/down", + "0102030405060708/devices/ABCDABCD12345678/activations", + // Numbers + "0102030405060708/devices/0000000012345678/up", + "0102030405060708/devices/0000000012345678/down", + "0102030405060708/devices/0000000012345678/activations", + // Wildcard + "0102030405060708/devices/+/up", + "0102030405060708/devices/+/down", + "0102030405060708/devices/+/activations", + // Not Wildcard + "0102030405060708/devices/0000000000000000/up", + "0102030405060708/devices/0000000000000000/down", + "0102030405060708/devices/0000000000000000/activations", + } + + for _, expected := range expectedList { + topic, err := ParseTopic(expected) + a.So(err, ShouldBeNil) + a.So(topic.String(), ShouldEqual, expected) + } + +} From 5f3ea56be4e3b69e4a8f5f16b189114d2f19ec51 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 15:09:45 +0200 Subject: [PATCH 1261/2266] [mqtt] Use MQTT client in ttnctl --- ttnctl/cmd/downlink.go | 31 ++++++----------- ttnctl/cmd/subscribe.go | 76 +++++------------------------------------ ttnctl/mqtt/client.go | 51 --------------------------- ttnctl/util/mqtt.go | 40 ++++++++++++++++++++++ 4 files changed, 59 insertions(+), 139 deletions(-) delete mode 100644 ttnctl/mqtt/client.go create mode 100644 ttnctl/util/mqtt.go diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index fe78adf1b..afe67408d 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -4,13 +4,8 @@ package cmd import ( - "encoding/hex" - "fmt" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/ttnctl/mqtt" "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -28,37 +23,33 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.Fatal("Insufficient arguments") } + appEUI, err := util.Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + devEUI, err := util.Parse64(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } - req := core.DataDownAppReq{ + dataDown := core.DataDownAppReq{ Payload: []byte(args[1]), TTL: args[2], } - payload, err := req.MarshalMsg(nil) - if err != nil { ctx.WithError(err).Fatal("Unable to create downlink payload") } - mqtt.Setup(viper.GetString("mqtt-broker"), ctx) - mqtt.Connect() + client := util.GetMQTTClient(ctx) - ctx.WithFields(log.Fields{ - "DevEUI": hex.EncodeToString(devEUI), - "Payload": string(payload), - }).Info("Pushing downlink...") + token := client.PublishDownlink(appEUI, devEUI, dataDown) - token := mqtt.Client.Publish(fmt.Sprintf("%s/devices/%x/down", viper.GetString("app-eui"), devEUI), 2, false, payload) - if token.Wait() && token.Error() != nil { - ctx.WithError(token.Error()).Fatal("Downlink failed.") - } else { - // Although we can't be sure whether it actually succeeded, we can know when the command is published to the MQTT. - ctx.Info("Downlink sent.") + if ok := token.Wait(); !ok { + ctx.WithError(token.Error()).Fatal("Could not subscribe") } + }, } diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 0029a2c24..8c28f98b7 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -4,15 +4,11 @@ package cmd import ( - "fmt" "regexp" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/api" + "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/howeyc/gopass" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -31,61 +27,21 @@ application.`, ctx.Fatalf("Invalid AppEUI: %s", err) } - var devEUI = "+" + var devEUI []byte if len(args) > 0 { - eui, err := util.Parse64(args[0]) + devEUI, err = util.Parse64(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } - devEUI = fmt.Sprintf("%X", eui) ctx.Infof("Subscribing uplink messages from device %s", devEUI) } else { ctx.Infof("Subscribing to uplink messages from all devices in application %x", appEUI) } - t, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication token") - } - if t == nil { - ctx.Fatal("No login found. Please login with ttnctl user login [e-mail]") - } - - // NOTE: until the MQTT server supports access tokens, we'll have to ask for a password. - fmt.Print("Password: ") - password, err := gopass.GetPasswd() - if err != nil { - ctx.Fatal(err.Error()) - } - - broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) - opts := MQTT.NewClientOptions().AddBroker(broker) + client := util.GetMQTTClient(ctx) - clientID := fmt.Sprintf("ttntool-%s", random.String(15)) - opts.SetClientID(clientID) - - opts.SetUsername(t.Email) - opts.SetPassword(string(password)) - - opts.SetKeepAlive(20) - - opts.SetOnConnectHandler(func(client *MQTT.Client) { - ctx.Info("Connected to The Things Network.") - }) - - opts.SetDefaultPublishHandler(func(client *MQTT.Client, msg MQTT.Message) { - t, err := api.DecodeDeviceTopic(msg.Topic()) - if err != nil { - ctx.WithError(err).Warn("There's something wrong with the MQTT topic.") - } - - ctx := ctx.WithField("DevEUI", t.DevEUI) - - dataUp := &core.DataUpAppReq{} - _, err = dataUp.UnmarshalMsg(msg.Payload()) - if err != nil { - ctx.WithError(err).Warn("Could not unmarshal uplink.") - } + token := client.SubscribeUplink(appEUI, devEUI, func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { + ctx := ctx.WithField("DevEUI", devEUI) // TODO: Find out what Metadata people want to see here @@ -105,26 +61,10 @@ application.`, }) - opts.SetConnectionLostHandler(func(client *MQTT.Client, err error) { - ctx.WithError(err).Error("Connection Lost. Reconnecting...") - }) - - mqttClient := MQTT.NewClient(opts) - - ctx.WithField("mqtt-broker", broker).Info("Connecting to The Things Network...") - if token := mqttClient.Connect(); token.Wait() && token.Error() != nil { - panic(token.Error()) - } - - topic := fmt.Sprintf("%X/devices/%s/up", appEUI, devEUI) - ctx.WithField("topic", topic).Debug("Subscribing...") - - if token := mqttClient.Subscribe(topic, 2, nil); token.Wait() && token.Error() != nil { - ctx.WithField("topic", topic).WithError(token.Error()).Fatal("Could not subscribe.") + if ok := token.Wait(); !ok { + ctx.WithError(token.Error()).Fatal("Could not subscribe") } - ctx.WithField("topic", topic).Debug("Subscribed.") - <-make(chan bool) }, diff --git a/ttnctl/mqtt/client.go b/ttnctl/mqtt/client.go deleted file mode 100644 index e772af280..000000000 --- a/ttnctl/mqtt/client.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/apex/log" -) - -var ( - Client *MQTT.Client - ctx log.Interface -) - -func Setup(broker string, _ctx log.Interface) { - if Client != nil { - _ctx.Fatal("MQTT Client already set up.") - } - ctx = _ctx - - mqttOpts := MQTT.NewClientOptions().AddBroker(fmt.Sprintf("tcp://%s", broker)) - clientID := fmt.Sprintf("ttnctl-%s", random.String(16)) - mqttOpts.SetClientID(clientID) - - mqttOpts.SetKeepAlive(20) - - mqttOpts.SetDefaultPublishHandler(func(client *MQTT.Client, msg MQTT.Message) { - ctx.WithField("message", msg).Debug("Received message") - }) - - mqttOpts.SetConnectionLostHandler(func(client *MQTT.Client, err error) { - ctx.WithError(err).Warn("Connection Lost. Reconnecting...") - }) - - Client = MQTT.NewClient(mqttOpts) -} - -func Connect() { - if Client.IsConnected() { - return - } - - ctx.Infof("Connecting to The Things Network...") - if token := Client.Connect(); token.Wait() && token.Error() != nil { - ctx.WithError(token.Error()).Fatal("Could not connect.") - } -} diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go new file mode 100644 index 000000000..1b60f6c36 --- /dev/null +++ b/ttnctl/util/mqtt.go @@ -0,0 +1,40 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" + "github.com/howeyc/gopass" + "github.com/spf13/viper" +) + +func GetMQTTClient(ctx log.Interface) mqtt.Client { + user, err := LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication token") + } + if user == nil { + ctx.Fatal("No login found. Please login with ttnctl user login [e-mail]") + } + + // NOTE: until the MQTT server supports access tokens, we'll have to ask for a password. + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal(err.Error()) + } + + broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) + client := mqtt.NewClient(ctx, "ttnctl", user.Email, string(password), broker) + + err = client.Connect() + if err != nil { + ctx.WithError(err).Fatal("Could not connect") + } + + return client +} From 28a395558937172e8fe0dcc29bf46b4f46fe18bf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 15:43:18 +0200 Subject: [PATCH 1262/2266] [mqtt] Allow application wildcards --- mqtt/topics.go | 14 +++++++++++--- mqtt/topics_test.go | 8 ++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/mqtt/topics.go b/mqtt/topics.go index 63131aba3..fae380f19 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -31,14 +31,17 @@ type Topic struct { // ParseTopic parses an MQTT topic string to a Topic struct func ParseTopic(topic string) (*Topic, error) { - pattern := regexp.MustCompile("([0-9A-F]{16})/(devices)/([0-9A-F]{16}|\\+)/(activations|up|down)") + pattern := regexp.MustCompile("([0-9A-F]{16}|\\+)/(devices)/([0-9A-F]{16}|\\+)/(activations|up|down)") matches := pattern.FindStringSubmatch(topic) if len(matches) < 4 { return nil, fmt.Errorf("Invalid topic format") } - appEUI, _ := hex.DecodeString(matches[1]) // validity asserted by our regex pattern + appEUI := []byte{} + if matches[3] != "+" { + appEUI, _ = hex.DecodeString(matches[1]) // validity asserted by our regex pattern + } devEUI := []byte{} if matches[3] != "+" { @@ -52,9 +55,14 @@ func ParseTopic(topic string) (*Topic, error) { // String implements the Stringer interface func (t Topic) String() string { + appEUI := "+" + if len(t.AppEUI) > 0 { + appEUI = fmt.Sprintf("%X", t.AppEUI) + } + devEUI := "+" if len(t.DevEUI) > 0 { devEUI = fmt.Sprintf("%X", t.DevEUI) } - return fmt.Sprintf("%X/%s/%s/%s", t.AppEUI, "devices", devEUI, t.Type) + return fmt.Sprintf("%s/%s/%s/%s", appEUI, "devices", devEUI, t.Type) } diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index b758a475d..24a005c52 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -82,10 +82,10 @@ func TestTopicParseAndString(t *testing.T) { "0102030405060708/devices/0000000012345678/up", "0102030405060708/devices/0000000012345678/down", "0102030405060708/devices/0000000012345678/activations", - // Wildcard - "0102030405060708/devices/+/up", - "0102030405060708/devices/+/down", - "0102030405060708/devices/+/activations", + // Wildcards + "+/devices/+/up", + "+/devices/+/down", + "+/devices/+/activations", // Not Wildcard "0102030405060708/devices/0000000000000000/up", "0102030405060708/devices/0000000000000000/down", From 8bb4d86bf340222b9631b8f74986110f348a72ab Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 15:45:12 +0200 Subject: [PATCH 1263/2266] [mqtt] Rename device subscriptions --- mqtt/client.go | 18 +++++++++--------- mqtt/client_test.go | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 2e66c126c..894a84229 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -24,17 +24,17 @@ type Client interface { // Uplink pub/sub PublishUplink(appEUI []byte, devEUI []byte, payload core.DataUpAppReq) Token - SubscribeUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token + SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token // Downlink pub/sub PublishDownlink(appEUI []byte, devEUI []byte, payload core.DataDownAppReq) Token - SubscribeDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token + SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token // Activation pub/sub PublishActivation(appEUI []byte, devEUI []byte, payload core.OTAAAppReq) Token - SubscribeActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token + SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token } @@ -133,7 +133,7 @@ func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core. return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token { +func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token { topic := Topic{appEUI, devEUI, Uplink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -157,7 +157,7 @@ func (c *defaultClient) SubscribeUplink(appEUI []byte, devEUI []byte, handler Up } func (c *defaultClient) SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token { - return c.SubscribeUplink(appEUI, []byte{}, handler) + return c.SubscribeDeviceUplink(appEUI, []byte{}, handler) } func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown core.DataDownAppReq) Token { @@ -169,7 +169,7 @@ func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown c return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token { +func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token { topic := Topic{appEUI, devEUI, Downlink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -193,7 +193,7 @@ func (c *defaultClient) SubscribeDownlink(appEUI []byte, devEUI []byte, handler } func (c *defaultClient) SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token { - return c.SubscribeDownlink(appEUI, []byte{}, handler) + return c.SubscribeDeviceDownlink(appEUI, []byte{}, handler) } func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activation core.OTAAAppReq) Token { @@ -205,7 +205,7 @@ func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activati return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token { +func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token { topic := Topic{appEUI, devEUI, Activations} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -229,5 +229,5 @@ func (c *defaultClient) SubscribeActivations(appEUI []byte, devEUI []byte, handl } func (c *defaultClient) SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token { - return c.SubscribeActivations(appEUI, []byte{}, handler) + return c.SubscribeDeviceActivations(appEUI, []byte{}, handler) } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 91f46e36c..1e08d877f 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -112,14 +112,14 @@ func TestPublishUplink(t *testing.T) { a.So(token.Error(), ShouldBeNil) } -func TestSubscribeUplink(t *testing.T) { +func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() eui := []byte{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeUplink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + token := c.SubscribeDeviceUplink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { }) ok := token.Wait() @@ -156,7 +156,7 @@ func TestPubSubUplink(t *testing.T) { wg.Add(1) - c.SubscribeUplink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + c.SubscribeDeviceUplink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) @@ -235,14 +235,14 @@ func TestPublishDownlink(t *testing.T) { a.So(token.Error(), ShouldBeNil) } -func TestSubscribeDownlink(t *testing.T) { +func TestSubscribeDeviceDownlink(t *testing.T) { a := New(t) c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() eui := []byte{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDownlink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + token := c.SubscribeDeviceDownlink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { }) ok := token.Wait() @@ -279,7 +279,7 @@ func TestPubSubDownlink(t *testing.T) { wg.Add(1) - c.SubscribeDownlink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + c.SubscribeDeviceDownlink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) @@ -356,14 +356,14 @@ func TestPublishActivations(t *testing.T) { a.So(token.Error(), ShouldBeNil) } -func TestSubscribeActivations(t *testing.T) { +func TestSubscribeDeviceActivations(t *testing.T) { a := New(t) c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() eui := []byte{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeActivations(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + token := c.SubscribeDeviceActivations(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { }) ok := token.Wait() @@ -400,7 +400,7 @@ func TestPubSubActivations(t *testing.T) { wg.Add(1) - c.SubscribeActivations(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + c.SubscribeDeviceActivations(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) From 50ad0a94b47a6391089dd92c60862680422dc2ce Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 15:57:08 +0200 Subject: [PATCH 1264/2266] [mqtt] Correct error handling when ctx==nil --- mqtt/client.go | 36 ++++++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 894a84229..9eed30f61 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -138,16 +138,20 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, hand return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseTopic(msg.Topic()) - if err != nil && c.ctx != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") + if err != nil { + if c.ctx != nil { + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") + } return } // Unmarshal the payload dataUp := &core.DataUpAppReq{} _, err = dataUp.UnmarshalMsg(msg.Payload()) - if err != nil && c.ctx != nil { - c.ctx.WithError(err).Warn("Could not unmarshal uplink") + if err != nil { + if c.ctx != nil { + c.ctx.WithError(err).Warn("Could not unmarshal uplink") + } return } @@ -174,16 +178,20 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, ha return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseTopic(msg.Topic()) - if err != nil && c.ctx != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") + if err != nil { + if c.ctx != nil { + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") + } return } // Unmarshal the payload dataDown := &core.DataDownAppReq{} _, err = dataDown.UnmarshalMsg(msg.Payload()) - if err != nil && c.ctx != nil { - c.ctx.WithError(err).Warn("Could not unmarshal Downlink") + if err != nil { + if c.ctx != nil { + c.ctx.WithError(err).Warn("Could not unmarshal Downlink") + } return } @@ -210,16 +218,20 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseTopic(msg.Topic()) - if err != nil && c.ctx != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") + if err != nil { + if c.ctx != nil { + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") + } return } // Unmarshal the payload activation := &core.OTAAAppReq{} _, err = activation.UnmarshalMsg(msg.Payload()) - if err != nil && c.ctx != nil { - c.ctx.WithError(err).Warn("Could not unmarshal Activation") + if err != nil { + if c.ctx != nil { + c.ctx.WithError(err).Warn("Could not unmarshal Activation") + } return } From 9b986c8a6c0590a611c67e645d31cc0717aa779b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 15:57:26 +0200 Subject: [PATCH 1265/2266] [mqtt] Allow subscribing to wildcard application --- mqtt/client.go | 15 +++++++++++++++ mqtt/client_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/mqtt/client.go b/mqtt/client.go index 9eed30f61..653222e1a 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -26,16 +26,19 @@ type Client interface { PublishUplink(appEUI []byte, devEUI []byte, payload core.DataUpAppReq) Token SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token + SubscribeUplink(handler UplinkHandler) Token // Downlink pub/sub PublishDownlink(appEUI []byte, devEUI []byte, payload core.DataDownAppReq) Token SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token + SubscribeDownlink(handler DownlinkHandler) Token // Activation pub/sub PublishActivation(appEUI []byte, devEUI []byte, payload core.OTAAAppReq) Token SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token + SubscribeActivations(handler ActivationHandler) Token } type Token interface { @@ -164,6 +167,10 @@ func (c *defaultClient) SubscribeAppUplink(appEUI []byte, handler UplinkHandler) return c.SubscribeDeviceUplink(appEUI, []byte{}, handler) } +func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { + return c.SubscribeDeviceUplink([]byte{}, []byte{}, handler) +} + func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown core.DataDownAppReq) Token { topic := Topic{appEUI, devEUI, Downlink} msg, err := dataDown.MarshalMsg(nil) @@ -204,6 +211,10 @@ func (c *defaultClient) SubscribeAppDownlink(appEUI []byte, handler DownlinkHand return c.SubscribeDeviceDownlink(appEUI, []byte{}, handler) } +func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink([]byte{}, []byte{}, handler) +} + func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activation core.OTAAAppReq) Token { topic := Topic{appEUI, devEUI, Activations} msg, err := activation.MarshalMsg(nil) @@ -243,3 +254,7 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, func (c *defaultClient) SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token { return c.SubscribeDeviceActivations(appEUI, []byte{}, handler) } + +func (c *defaultClient) SubscribeActivations(handler ActivationHandler) Token { + return c.SubscribeDeviceActivations([]byte{}, []byte{}, handler) +} diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 1e08d877f..1cac11021 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -144,6 +144,20 @@ func TestSubscribeAppUplink(t *testing.T) { a.So(token.Error(), ShouldBeNil) } +func TestSubscribeUplink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + token := c.SubscribeUplink(func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + func TestPubSubUplink(t *testing.T) { a := New(t) c := NewClient(nil, "test", "", "", "tcp://localhost:1883") @@ -267,6 +281,20 @@ func TestSubscribeAppDownlink(t *testing.T) { a.So(token.Error(), ShouldBeNil) } +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + token := c.SubscribeDownlink(func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + func TestPubSubDownlink(t *testing.T) { a := New(t) c := NewClient(nil, "test", "", "", "tcp://localhost:1883") @@ -388,6 +416,20 @@ func TestSubscribeAppActivations(t *testing.T) { a.So(token.Error(), ShouldBeNil) } +func TestSubscribeActivations(t *testing.T) { + a := New(t) + c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c.Connect() + + token := c.SubscribeActivations(func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + + }) + ok := token.Wait() + + a.So(ok, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + func TestPubSubActivations(t *testing.T) { a := New(t) c := NewClient(nil, "test", "", "", "tcp://localhost:1883") From 3e40bcbdbda42ea28107bb375087959809293634 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 17:55:56 +0200 Subject: [PATCH 1266/2266] [ttnctl] Update for 8bb4d86 --- ttnctl/cmd/subscribe.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 8c28f98b7..a1909a7f7 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -40,7 +40,7 @@ application.`, client := util.GetMQTTClient(ctx) - token := client.SubscribeUplink(appEUI, devEUI, func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { + token := client.SubscribeDeviceUplink(appEUI, devEUI, func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { ctx := ctx.WithField("DevEUI", devEUI) // TODO: Find out what Metadata people want to see here From 059ee5ca2e81897b6a27b827c042ca9ecec9c86f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 17:56:11 +0200 Subject: [PATCH 1267/2266] [mqtt] Use new Paho --- mqtt/client.go | 20 +++++++++++--------- mqtt/client_test.go | 42 +++++++++++++++--------------------------- 2 files changed, 26 insertions(+), 36 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 653222e1a..c9c301eac 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -7,10 +7,10 @@ import ( "fmt" "time" - MQTT "git.eclipse.org/gitroot/paho/org.eclipse.paho.mqtt.golang.git" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" + MQTT "github.com/eclipse/paho.mqtt.golang" ) const QoS = 0x02 @@ -50,10 +50,12 @@ type simpleToken struct { err error } +// Wait always returns true func (t *simpleToken) Wait() bool { - return (t.err == nil) + return true } +// Error contains the error if present func (t *simpleToken) Error() error { return t.err } @@ -63,7 +65,7 @@ type DownlinkHandler func(client Client, appEUI []byte, devEUI []byte, req core. type ActivationHandler func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) type defaultClient struct { - mqtt *MQTT.Client + mqtt MQTT.Client ctx log.Interface } @@ -82,19 +84,19 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri mqttOpts.SetKeepAlive(30 * time.Second) mqttOpts.SetPingTimeout(10 * time.Second) - mqttOpts.SetDefaultPublishHandler(func(client *MQTT.Client, msg MQTT.Message) { + mqttOpts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) { if ctx != nil { ctx.WithField("message", msg).Debug("Received unhandled message") } }) - mqttOpts.SetConnectionLostHandler(func(client *MQTT.Client, err error) { + mqttOpts.SetConnectionLostHandler(func(client MQTT.Client, err error) { if ctx != nil { ctx.WithError(err).Debug("Disconnected, reconnecting...") } }) - mqttOpts.SetOnConnectHandler(func(client *MQTT.Client) { + mqttOpts.SetOnConnectHandler(func(client MQTT.Client) { if ctx != nil { ctx.Debug("Connected") } @@ -138,7 +140,7 @@ func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core. func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token { topic := Topic{appEUI, devEUI, Uplink} - return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { + return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseTopic(msg.Topic()) if err != nil { @@ -182,7 +184,7 @@ func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown c func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token { topic := Topic{appEUI, devEUI, Downlink} - return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { + return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseTopic(msg.Topic()) if err != nil { @@ -226,7 +228,7 @@ func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activati func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token { topic := Topic{appEUI, devEUI, Activations} - return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt *MQTT.Client, msg MQTT.Message) { + return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseTopic(msg.Topic()) if err != nil { diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 1cac11021..0769ab2d1 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -18,11 +18,11 @@ func TestToken(t *testing.T) { a := New(t) okToken := simpleToken{} - a.So(okToken.Wait(), ShouldBeTrue) + okToken.Wait() a.So(okToken.Error(), ShouldBeNil) failToken := simpleToken{fmt.Errorf("Err")} - a.So(failToken.Wait(), ShouldBeFalse) + failToken.Wait() a.So(failToken.Error(), ShouldNotBeNil) } @@ -80,7 +80,7 @@ func TestDisconnect(t *testing.T) { } func TestRandomTopicPublish(t *testing.T) { - ctx := GetLogger(t, "TestInvalidUplink") + ctx := GetLogger(t, "TestRandomTopicPublish") c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") c.Connect() @@ -106,9 +106,8 @@ func TestPublishUplink(t *testing.T) { } token := c.PublishUplink(eui, eui, dataUp) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -122,9 +121,8 @@ func TestSubscribeDeviceUplink(t *testing.T) { token := c.SubscribeDeviceUplink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -138,9 +136,8 @@ func TestSubscribeAppUplink(t *testing.T) { token := c.SubscribeAppUplink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -152,9 +149,8 @@ func TestSubscribeUplink(t *testing.T) { token := c.SubscribeUplink(func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -243,9 +239,8 @@ func TestPublishDownlink(t *testing.T) { } token := c.PublishDownlink(eui, eui, dataDown) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -259,9 +254,8 @@ func TestSubscribeDeviceDownlink(t *testing.T) { token := c.SubscribeDeviceDownlink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -275,9 +269,8 @@ func TestSubscribeAppDownlink(t *testing.T) { token := c.SubscribeAppDownlink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -289,9 +282,8 @@ func TestSubscribeDownlink(t *testing.T) { token := c.SubscribeDownlink(func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -378,9 +370,8 @@ func TestPublishActivations(t *testing.T) { dataActivations := core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}} token := c.PublishActivation(eui, eui, dataActivations) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -394,9 +385,8 @@ func TestSubscribeDeviceActivations(t *testing.T) { token := c.SubscribeDeviceActivations(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -410,9 +400,8 @@ func TestSubscribeAppActivations(t *testing.T) { token := c.SubscribeAppActivations(eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } @@ -424,9 +413,8 @@ func TestSubscribeActivations(t *testing.T) { token := c.SubscribeActivations(func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { }) - ok := token.Wait() + token.Wait() - a.So(ok, ShouldBeTrue) a.So(token.Error(), ShouldBeNil) } From 40d44e31e56d617cded08314bf2c0170764ea0ca Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 18:51:20 +0200 Subject: [PATCH 1268/2266] [mqtt] Replace gmq with Paho in Handler --- cmd/handler.go | 24 +- core/adapters/mqtt/adapter.go | 152 +++++++++ core/adapters/mqtt/adapter_test.go | 232 +++++++++++++ core/adapters/mqtt/client.go | 180 ---------- core/adapters/mqtt/client_test.go | 436 ------------------------ core/adapters/mqtt/doc.go | 78 ----- core/adapters/mqtt/mqtt.go | 200 ----------- core/adapters/mqtt/mqtt_test.go | 528 ----------------------------- core/adapters/mqtt/safeclient.go | 43 --- 9 files changed, 397 insertions(+), 1476 deletions(-) create mode 100644 core/adapters/mqtt/adapter.go create mode 100644 core/adapters/mqtt/adapter_test.go delete mode 100644 core/adapters/mqtt/client.go delete mode 100644 core/adapters/mqtt/client_test.go delete mode 100644 core/adapters/mqtt/doc.go delete mode 100644 core/adapters/mqtt/mqtt.go delete mode 100644 core/adapters/mqtt/mqtt_test.go delete mode 100644 core/adapters/mqtt/safeclient.go diff --git a/cmd/handler.go b/cmd/handler.go index a221bb506..ef64d9478 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -10,10 +10,10 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + handlerMQTT "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/core/components/broker" "github.com/TheThingsNetwork/ttn/core/components/handler" - "github.com/TheThingsNetwork/ttn/utils/random" + ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/spf13/cobra" @@ -114,19 +114,21 @@ The Handler is the bridge between The Things Network and applications. } // MQTT Client & adapter - mqttClient, chmsg, err := mqtt.NewClient( - fmt.Sprintf("handler-%s", random.String(15)), - viper.GetString("handler.mqtt-broker"), + mqttClient := ttnMQTT.NewClient( + ctx.WithField("adapter", "handler-mqtt"), + "ttnhdl", viper.GetString("handler.mqtt-username"), viper.GetString("handler.mqtt-password"), - ctx.WithField("adapter", "app-adapter"), + fmt.Sprintf("tcp://%s", viper.GetString("handler.mqtt-broker")), ) + err = mqttClient.Connect() if err != nil { ctx.WithError(err).Fatal("Could not connect to MQTT") } - appAdapter := mqtt.New( - mqtt.Components{Ctx: ctx.WithField("adapter", "app-adapter"), Client: mqttClient}, - mqtt.Options{}, + + appAdapter := handlerMQTT.NewAdapter( + ctx.WithField("adapter", "app-adapter"), + mqttClient, ) // Handler @@ -145,8 +147,8 @@ The Handler is the bridge between The Things Network and applications. }, ) - // Go - appAdapter.Start(chmsg, handler) + appAdapter.SubscribeDownlink(handler) + if err := handler.Start(); err != nil { ctx.WithError(err).Fatal("Handler has fallen...") } diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go new file mode 100644 index 000000000..7e455a29f --- /dev/null +++ b/core/adapters/mqtt/adapter.go @@ -0,0 +1,152 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "github.com/TheThingsNetwork/ttn/core" + ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/apex/log" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// Adapter defines a public interface for the mqtt adapter +type Adapter interface { + core.AppClient + SubscribeDownlink(handler core.HandlerServer) error +} + +type defaultAdapter struct { + ctx log.Interface + client ttnMQTT.Client +} + +// HandleData implements the core.AppClient interface +func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) { + if err := validateData(req); err != nil { + return nil, errors.New(errors.Structural, err) + } + + dataUp := core.DataUpAppReq{ + Payload: req.Payload, + Metadata: core.ProtoMetaToAppMeta(req.Metadata...), + } + + if a.ctx != nil { + a.ctx.WithFields(log.Fields{ + "AppEUI": req.AppEUI, + "DevEUI": req.DevEUI, + }).Debug("Publishing Uplink") + } + + token := a.client.PublishUplink(req.AppEUI, req.DevEUI, dataUp) + if token.Wait(); token.Error() != nil { + return nil, errors.New(errors.Structural, token.Error()) + } + return nil, nil +} + +func validateData(req *core.DataAppReq) error { + var err error + switch { + case req == nil: + err = errors.New(errors.Structural, "Received Nil Application Request") + case len(req.Payload) == 0: + err = errors.New(errors.Structural, "Invalid Packet Payload") + case len(req.DevEUI) != 8: + err = errors.New(errors.Structural, "Invalid Device EUI") + case len(req.AppEUI) != 8: + err = errors.New(errors.Structural, "Invalid Application EUI") + case req.Metadata == nil: + err = errors.New(errors.Structural, "Missing Mandatory Metadata") + } + + if err != nil { + stats.MarkMeter("mqtt_adapter.uplink.invalid") + return err + } + + return nil +} + +// HandleJoin implements the core.AppClient interface +func (a *defaultAdapter) HandleJoin(_ context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) { + if err := validateJoin(req); err != nil { + return nil, errors.New(errors.Structural, err) + } + + otaa := core.OTAAAppReq{ + Metadata: core.ProtoMetaToAppMeta(req.Metadata...), + } + + if a.ctx != nil { + a.ctx.WithFields(log.Fields{ + "AppEUI": req.AppEUI, + "DevEUI": req.DevEUI, + }).Debug("Publishing Activation") + } + + token := a.client.PublishActivation(req.AppEUI, req.DevEUI, otaa) + if token.Wait(); token.Error() != nil { + return nil, errors.New(errors.Structural, token.Error()) + } + return nil, nil +} + +func validateJoin(req *core.JoinAppReq) error { + var err error + switch { + case req == nil: + err = errors.New(errors.Structural, "Received Nil Application Request") + case len(req.DevEUI) != 8: + err = errors.New(errors.Structural, "Invalid Device EUI") + case len(req.AppEUI) != 8: + err = errors.New(errors.Structural, "Invalid Application EUI") + case req.Metadata == nil: + err = errors.New(errors.Structural, "Missing Mandatory Metadata") + } + + if err != nil { + stats.MarkMeter("mqtt_adapter.join.invalid") + return err + } + + return nil +} + +func (a *defaultAdapter) SubscribeDownlink(handler core.HandlerServer) error { + token := a.client.SubscribeDownlink(func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + if len(req.Payload) == 0 { + if a.ctx != nil { + a.ctx.Debug("Skipping empty downlink") + } + return + } + + if a.ctx != nil { + a.ctx.WithFields(log.Fields{ + "AppEUI": appEUI, + "DevEUI": devEUI, + }).Debug("Receiving Downlink") + } + + // Convert it to an handler downlink + handler.HandleDataDown(context.Background(), &core.DataDownHandlerReq{ + Payload: req.Payload, + TTL: req.TTL, + AppEUI: appEUI, + DevEUI: devEUI, + }) + }) + + token.Wait() + return token.Error() +} + +// NewAdapter returns a new MQTT handler adapter +func NewAdapter(ctx log.Interface, client ttnMQTT.Client) Adapter { + return &defaultAdapter{ctx, client} +} diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go new file mode 100644 index 000000000..db04af772 --- /dev/null +++ b/core/adapters/mqtt/adapter_test.go @@ -0,0 +1,232 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "sync" + "testing" + "time" + + "golang.org/x/net/context" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/mocks" + ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestNewAdapter(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "TestNewAdapter") + client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") + adapter := NewAdapter(ctx, client) + + a.So(adapter.(*defaultAdapter).client, ShouldEqual, client) +} + +func TestHandleData(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "TestHandleData") + client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") + client.Connect() + + adapter := NewAdapter(ctx, client) + + eui := []byte{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + req := core.DataAppReq{ + Payload: []byte{0x01, 0x02}, + Metadata: []*core.Metadata{ + &core.Metadata{DataRate: "SF7BW125"}, + }, + AppEUI: eui, + DevEUI: eui, + } + + var wg sync.WaitGroup + wg.Add(1) + + client.SubscribeDeviceUplink(eui, eui, func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { + a.So(appEUI, ShouldResemble, eui) + a.So(devEUI, ShouldResemble, eui) + a.So(dataUp.Payload, ShouldResemble, []byte{0x01, 0x02}) + a.So(dataUp.Metadata[0].DataRate, ShouldEqual, "SF7BW125") + wg.Done() + }).Wait() + + res, err := adapter.HandleData(context.Background(), &req) + a.So(err, ShouldBeNil) + a.So(res, ShouldBeNil) + + wg.Wait() + +} + +func TestHandleInvalidData(t *testing.T) { + a := New(t) + client := ttnMQTT.NewClient(nil, "test", "", "", "tcp://localhost:1883") + adapter := NewAdapter(nil, client) + + // nil Request + _, err := adapter.HandleData(context.Background(), nil) + a.So(err, ShouldNotBeNil) + + // Invalid Payload + _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ + Payload: []byte{}, + }) + a.So(err, ShouldNotBeNil) + + // Invalid DevEUI + _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ + Payload: []byte{0x00}, + DevEUI: []byte{}, + }) + a.So(err, ShouldNotBeNil) + + // Invalid AppEUI + _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ + Payload: []byte{0x00}, + DevEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + AppEUI: []byte{}, + }) + a.So(err, ShouldNotBeNil) + + // Missing Metadata + _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ + Payload: []byte{0x00}, + DevEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + AppEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + }) + a.So(err, ShouldNotBeNil) + + // Not Connected + _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ + Payload: []byte{0x00}, + DevEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + AppEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + Metadata: []*core.Metadata{}, + }) + a.So(err, ShouldNotBeNil) +} + +func TestHandleJoin(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "TestHandleJoin") + client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") + client.Connect() + + adapter := NewAdapter(ctx, client) + + eui := []byte{0x0a, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + + req := core.JoinAppReq{ + AppEUI: eui, + DevEUI: eui, + Metadata: []*core.Metadata{ + &core.Metadata{DataRate: "SF7BW125"}, + }, + } + + var wg sync.WaitGroup + wg.Add(1) + + client.SubscribeDeviceActivations(eui, eui, func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, activation core.OTAAAppReq) { + a.So(appEUI, ShouldResemble, eui) + a.So(devEUI, ShouldResemble, eui) + a.So(activation.Metadata[0].DataRate, ShouldEqual, "SF7BW125") + wg.Done() + }).Wait() + + res, err := adapter.HandleJoin(context.Background(), &req) + a.So(err, ShouldBeNil) + a.So(res, ShouldBeNil) + + wg.Wait() +} + +func TestHandleInvalidJoin(t *testing.T) { + a := New(t) + client := ttnMQTT.NewClient(nil, "test", "", "", "tcp://localhost:1883") + adapter := NewAdapter(nil, client) + + // nil Request + _, err := adapter.HandleJoin(context.Background(), nil) + a.So(err, ShouldNotBeNil) + + // Invalid DevEUI + _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ + DevEUI: []byte{}, + }) + a.So(err, ShouldNotBeNil) + + // Invalid AppEUI + _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ + DevEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + AppEUI: []byte{}, + }) + a.So(err, ShouldNotBeNil) + + // Missing Metadata + _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ + DevEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + AppEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + }) + a.So(err, ShouldNotBeNil) + + // Not Connected + _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ + DevEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + AppEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + Metadata: []*core.Metadata{}, + }) + a.So(err, ShouldNotBeNil) +} + +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "TestHandleJoin") + client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") + client.Connect() + + adapter := NewAdapter(ctx, client) + handler := mocks.NewHandlerServer() + + adapter.SubscribeDownlink(handler) + + appEUI := []byte{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + + <-time.After(20 * time.Millisecond) + + expected := &core.DataDownHandlerReq{ + AppEUI: appEUI, + DevEUI: devEUI, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + } + + a.So(handler.InHandleDataDown.Req, ShouldResemble, expected) +} + +func TestSubscribeInvalidDownlink(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "TestHandleJoin") + client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") + client.Connect() + + adapter := NewAdapter(ctx, client) + handler := mocks.NewHandlerServer() + + adapter.SubscribeDownlink(handler) + + appEUI := []byte{0x04, 0x03, 0x03, 0x09, 0x05, 0x06, 0x07, 0x08} + devEUI := []byte{0x08, 0x07, 0x06, 0x09, 0x04, 0x03, 0x02, 0x01} + client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{}}).Wait() + + <-time.After(20 * time.Millisecond) + + a.So(handler.InHandleDataDown.Req, ShouldBeNil) +} diff --git a/core/adapters/mqtt/client.go b/core/adapters/mqtt/client.go deleted file mode 100644 index 9ef5da1c9..000000000 --- a/core/adapters/mqtt/client.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/apex/log" - "github.com/yosssi/gmq/mqtt" - "github.com/yosssi/gmq/mqtt/client" -) - -// InitialReconnectDelay represents the initial delay of reconnection in case of lose. The client -// will attempt to reconnect several times after a given delay being 10x the previous one. -const InitialReconnectDelay = 25 * time.Millisecond - -// Client provides an interface for an MQTT client -type Client interface { - // Publish pushes a message on a given topic - Publish(*client.PublishOptions) error - // Terminate kills internal client goroutine and processes - Terminate() -} - -// connecter is an alias used by methods belows -type connecter func() error - -// NewClient creates and connects a mqtt client with predefined options. -func NewClient(id, netAddr, username, password string, ctx log.Interface) (Client, chan Msg, error) { - ctx = ctx.WithField("id", id).WithField("address", netAddr) - chcmd := make(chan interface{}) - chmsg := make(chan Msg) - - go monitorClient(id, netAddr, chcmd, ctx) - - tryConnect := createConnecter(id, netAddr, username, password, chmsg, chcmd, ctx) - if err := tryConnect(); err != nil { - close(chcmd) - return nil, nil, errors.New(errors.Operational, err) - } - - return safeClient{ - chcmd: chcmd, - }, chmsg, nil -} - -// monitorClient is used to keep all accesses to the client completely concurrent-safe. It also -// allows to replace the current client by a new one in case of error. / -// -// When the client loses its connection and isn't able to re-establish it, we need to create a new -// client. However, because that client is likely to be accessed by several goroutines at "the same -// time", we cannot just swap two variables somewhere. The hereby monitor enables a safe client -// swapping and managing. (See safeClient struct as well) -func monitorClient(id string, netAddr string, chcmd <-chan interface{}, ctx log.Interface) { - var cli *client.Client - ctx = ctx.WithField("process", "monitorClient") - ctx.Debug("Start monitoring MQTT client") - - for cmd := range chcmd { - if cli == nil { - init, ok := cmd.(cmdClient) - if !ok { - ctx.Warn("Received cmd whereas client is nil. Ignored") - continue - } - ctx.Debug("Setup initial MQTT client") - cli = init.options - init.cherr <- nil - continue - } - switch cmd.(type) { - case cmdPublish: - cmd := cmd.(cmdPublish) - cmd.cherr <- cli.Publish(cmd.options) - case cmdTerminate: - cmd := cmd.(cmdTerminate) - cli.Terminate() - cli = nil - cmd.cherr <- nil - case cmdClient: - cmd := cmd.(cmdClient) - cli.Terminate() - cli = cmd.options - cmd.cherr <- nil - default: - ctx.WithField("cmd", cmd).Warn("Received unrecognized command") - } - } - - if cli != nil { - _ = cli.Disconnect() - cli.Terminate() - } - ctx.Debug("Stop monitoring MQTT client") -} - -// createConnecter is used to start and subscribe a new client. It also make sure that if the -// created client goes down, another one is automatically created such that the client recover -// itself. -func createConnecter(id, netAddr, username, password string, chmsg chan<- Msg, chcmd chan<- interface{}, ctx log.Interface) connecter { - ctx.Debug("Create new connecter for MQTT client") - var cli *client.Client - cli = client.New(&client.Options{ - ErrorHandler: createErrorHandler( - func() error { return createConnecter(id, netAddr, username, password, chmsg, chcmd, ctx)() }, - 10000*InitialReconnectDelay, - ctx, - ), - }) - - return func() error { - ctx.Debug("(Re)Connecting MQTT client") - err := cli.Connect(&client.ConnectOptions{ - Network: "tcp", - Address: netAddr, - ClientID: []byte(id), - UserName: []byte(username), - Password: []byte(password), - }) - - if err != nil { - return err - } - - err = cli.Subscribe(&client.SubscribeOptions{ - SubReqs: []*client.SubReq{ - &client.SubReq{ - TopicFilter: []byte("+/devices/+/down"), - QoS: mqtt.QoS2, - Handler: func(topic, msg []byte) { - if len(msg) == 0 { - return - } - chmsg <- Msg{ - Topic: string(topic), - Payload: msg, - Type: Down, - } - }, - }, - }, - }) - - if err != nil { - return err - } - - cherr := make(chan error) - select { - case chcmd <- cmdClient{options: cli, cherr: cherr}: - return <-cherr - case <-time.After(time.Second): - return errors.New(errors.Operational, "Timeout. Unable to set new client") - } - } -} - -// createErrorHandler use the client reference to create an error handler function which will -// attempt to reconnect the client after a failure. -func createErrorHandler(tryReconnect connecter, maxDelay time.Duration, ctx log.Interface) client.ErrorHandler { - delay := InitialReconnectDelay - var reconnect func(fault error) - reconnect = func(fault error) { - if delay > maxDelay { - ctx.WithError(fault).Error("Unable to reconnect the mqtt client") - return - } - ctx.Debugf("Connection lost with MQTT broker. Trying to reconnect in %s", delay) - <-time.After(delay) - if err := tryReconnect(); err != nil { - delay *= 10 - ctx.WithError(err).Debug("Failed to reconnect MQTT client.") - reconnect(fault) - } - } - - return reconnect -} diff --git a/core/adapters/mqtt/client_test.go b/core/adapters/mqtt/client_test.go deleted file mode 100644 index 2cf91f9c3..000000000 --- a/core/adapters/mqtt/client_test.go +++ /dev/null @@ -1,436 +0,0 @@ -// Copyright © 2016 T//e Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/yosssi/gmq/mqtt" - "github.com/yosssi/gmq/mqtt/client" -) - -const BrokerAddr = "0.0.0.0:1883" - -// MockClient implements the Client interface -type MockClient struct { - Failures map[string]error - InPublish struct { - Options *client.PublishOptions - } - InTerminate struct { - Called bool - } -} - -// NewMockClient constructs a new MockClient -func NewMockClient() *MockClient { - return &MockClient{ - Failures: make(map[string]error), - } -} - -// Publish implements the Client interface -func (m *MockClient) Publish(o *client.PublishOptions) error { - m.InPublish.Options = o - return m.Failures["Publish"] -} - -// Terminate implements the Client interface -func (m *MockClient) Terminate() { - m.InTerminate.Called = true -} - -// MockTryConnect simulates a tryConnect function -type MockTryConnect struct { - Func connecter - Attempt int -} - -// NewMockTryConnect instantiates a MockTryConnect structure -func NewMockTryConnect(maxFailures int) *MockTryConnect { - m := new(MockTryConnect) - - m.Func = func() error { - m.Attempt++ - if m.Attempt > maxFailures { - return nil - } - return fmt.Errorf("MockTryConnect: Nope") - } - - return m -} - -func TestCreateErrorHandler(t *testing.T) { - { - Desc(t, "Try reconnect once and fail") - - // Build - tryConnect := NewMockTryConnect(3) - maxDelay := InitialReconnectDelay - handler := createErrorHandler(tryConnect.Func, maxDelay, GetLogger(t, "Test Logger")) - - // Operate - go handler(fmt.Errorf("Mock Failure")) - <-time.After(maxDelay + 50*time.Millisecond) - - // Check - Check(t, 1, tryConnect.Attempt, "Reconnection Attempts") - } - - // -------------------- - - { - Desc(t, "Try reconnect more than once and fail") - - // Build - tryConnect := NewMockTryConnect(3) - maxDelay := InitialReconnectDelay * 10 - handler := createErrorHandler(tryConnect.Func, maxDelay, GetLogger(t, "Test Logger")) - - // Operate - go handler(fmt.Errorf("Mock Failure")) - <-time.After(maxDelay + 50*time.Millisecond) - - // Check - Check(t, 2, tryConnect.Attempt, "Reconnection Attempts") - } - - // -------------------- - - { - Desc(t, "Try reconnect once and succeed") - - // Build - tryConnect := NewMockTryConnect(0) - maxDelay := InitialReconnectDelay - handler := createErrorHandler(tryConnect.Func, maxDelay, GetLogger(t, "Test Logger")) - - // Operate - go handler(fmt.Errorf("Mock Failure")) - <-time.After(maxDelay + 50*time.Millisecond) - - // Check - Check(t, 1, tryConnect.Attempt, "Reconnection Attempts") - } -} - -func newID() string { - return fmt.Sprintf("(%d)Client", time.Now().Nanosecond()) -} - -func TestNewClient(t *testing.T) { - testCli := client.New(nil) - if err := testCli.Connect(&client.ConnectOptions{ - Network: "tcp", - Address: BrokerAddr, - ClientID: []byte(newID()), - }); err != nil { - panic(err) - } - - // -------------------- - - { - Desc(t, "Create client with invalid address") - - // Build - _, _, err := NewClient(newID(), "invalidAddress", "", "", GetLogger(t, "Test Logger")) - - // Check - CheckErrors(t, ErrOperational, err) - } - - // -------------------- - - { - Desc(t, "Connect a client and receive a down msg") - - // Build - cli, chmsg, err := NewClient(newID(), BrokerAddr, "", "", GetLogger(t, "Test Logger")) - FatalUnless(t, err) - msg := Msg{ - Type: Down, - Topic: "01020304/devices/01020304/down", - Payload: []byte("message"), - } - - // Operate - testCli.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte(msg.Topic), - Message: msg.Payload, - }) - - // Check - var got Msg - select { - case got = <-chmsg: - case <-time.After(75 * time.Millisecond): - } - Check(t, msg, got, "MQTT Messages") - - // Clean - cli.Terminate() - <-time.After(time.Millisecond * 50) - } - - // -------------------- - - { - Desc(t, "Connect a client and send on a random topic") - - // Build - cli, chmsg, err := NewClient(newID(), BrokerAddr, "", "", GetLogger(t, "Test Logger")) - FatalUnless(t, err) - var msg Msg - - // Operate - testCli.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte("topic"), - Message: []byte{14, 42}, - }) - - // Check - var got Msg - select { - case got = <-chmsg: - case <-time.After(75 * time.Millisecond): - } - Check(t, msg, got, "MQTT Messages") - - // Clean - cli.Terminate() - <-time.After(time.Millisecond * 50) - } - - // -------------------- - - { - Desc(t, "Connect the client and simulate a disconnection") - - // Build - id := newID() - cli, chmsg, err := NewClient(id, BrokerAddr, "", "", GetLogger(t, "Test Logger")) - FatalUnless(t, err) - msg := Msg{ - Type: Down, - Topic: "0102030405060708/devices/0102030401020304/down", - Payload: []byte("message"), - } - - // Operate - usurp := client.New(nil) - err = usurp.Connect(&client.ConnectOptions{ - Network: "tcp", - Address: BrokerAddr, - ClientID: []byte(id), - }) - FatalUnless(t, err) - <-time.After(InitialReconnectDelay * 2) - testCli.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte(msg.Topic), - Message: msg.Payload, - }) - - // Check - var got Msg - select { - case got = <-chmsg: - case <-time.After(75 * time.Millisecond): - } - Check(t, msg, got, "MQTT Messages") - - // Clean - cli.Terminate() - _ = usurp.Disconnect() - usurp.Terminate() - <-time.After(time.Millisecond * 50) - } - - // -------------------- - - { - Desc(t, "Connect a client and publish on a topic") - - // Build - chmsg := make(chan Msg) - msg := Msg{ - Type: Down, - Topic: "topic", - Payload: []byte{14, 42}, - } - cli, _, err := NewClient(newID(), BrokerAddr, "", "", GetLogger(t, "Test Logger")) - FatalUnless(t, err) - err = testCli.Subscribe(&client.SubscribeOptions{ - SubReqs: []*client.SubReq{ - &client.SubReq{ - TopicFilter: []byte("topic"), - QoS: mqtt.QoS2, - Handler: func(topic, msg []byte) { - chmsg <- Msg{ - Topic: string(topic), - Payload: msg, - Type: Down, - } - }, - }, - }, - }) - FatalUnless(t, err) - - // Operate - err = cli.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte(msg.Topic), - Message: msg.Payload, - }) - - // Check - var got Msg - select { - case got = <-chmsg: - case <-time.After(75 * time.Millisecond): - } - - CheckErrors(t, nil, err) - Check(t, msg, got, "MQTT Messages") - - // Clean - err = testCli.Unsubscribe(&client.UnsubscribeOptions{ - TopicFilters: [][]byte{[]byte(msg.Topic)}, - }) - FatalUnless(t, err) - cli.Terminate() - <-time.After(time.Millisecond * 50) - } - - // -------------------- - - _ = testCli.Disconnect() - testCli.Terminate() -} - -func TestMonitorClient(t *testing.T) { - { - Desc(t, "Ensure monitor stops when cmd is closed, without any client") - - // Build - chcmd := make(chan interface{}) - chdone := make(chan bool) - - // Operate - go func() { - monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) - chdone <- true - }() - close(chcmd) - - // Check - var done bool - select { - case done = <-chdone: - case <-time.After(time.Millisecond * 50): - } - Check(t, true, done, "Done signals") - - } - - // -------------------- - - { - Desc(t, "Ensure monitor stops when cmd is closed, with a client") - - // Build - cli := client.New(nil) - chcmd := make(chan interface{}) - chdone := make(chan bool) - cherr := make(chan error) - - // Operate - go func() { - monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) - chdone <- true - }() - chcmd <- cmdClient{cherr: cherr, options: cli} - <-cherr - close(chcmd) - - // Check - var done bool - select { - case done = <-chdone: - case <-time.After(time.Millisecond * 50): - } - Check(t, true, done, "Done signals") - - } - - // -------------------- - - { - Desc(t, "Send an invalid command to monitor, no client") - - // Build - chcmd := make(chan interface{}) - chdone := make(chan bool) - - // Operate - go func() { - monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) - chdone <- true - }() - chcmd <- "Patate" - - // Check - var done bool - select { - case done = <-chdone: - case <-time.After(time.Millisecond * 50): - } - Check(t, false, done, "Done signals") - } - - // -------------------- - - { - Desc(t, "Send an invalid command to monitor, no client") - - // Build - cli := client.New(nil) - chcmd := make(chan interface{}) - chdone := make(chan bool) - cherr := make(chan error) - - // Operate - go func() { - monitorClient(newID(), BrokerAddr, chcmd, GetLogger(t, "Test Client")) - chdone <- true - }() - chcmd <- cmdClient{cherr: cherr, options: cli} - <-cherr - chcmd <- "Patate" - - // Check - var done bool - select { - case done = <-chdone: - case <-time.After(time.Millisecond * 50): - } - Check(t, false, done, "Done signals") - - // Clean - _ = cli.Disconnect() - cli.Terminate() - } -} diff --git a/core/adapters/mqtt/doc.go b/core/adapters/mqtt/doc.go deleted file mode 100644 index 2c88b3c3c..000000000 --- a/core/adapters/mqtt/doc.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -/* -Package mqtt provides an MQTT adapter between the network and an MQTT broker. The adapter listens on -2 topics: - - +/devices/+/down - +/devices/+/activations - -and may publish on two of them: - - +/devices/+/up - +/devices/+/activations - -Above, the first wildcard refers to an Application Unique Identifier (AppEUI) which is a -hex-encoded string of 16 characters (representing an 8-bytes long AppEUI). The second wildcard -refers to a Device Unique Identifier (DevEUI) which is also an hex-encoded string of 16 -characters with one exception. - -For ABP (Activation By Personalization), the activation should be made to: - - +/devices/personalized/activations - - -Serialization Format - -For each topic, a MessagePack-JSON serialization format is expected, with the following top-level -json structure: - - -ABP :: +/devices/personalized/activations - - {"dev_addr": "% 4 bytes hex-encoded %", "apps_key": "% 16 bytes hex-encoded %", "nwks_key": "% 16 bytes hex-encoded %"} - {"dev_addr":"01020304","apps_key":"01020304050607080900010203040506","nwks_key":"01020304050607080900010203040506"} - -OTAA :: +/devices/+/activations - -... TODO - -Downlink :: +/devices/+/down - - {"payload": % sequence of bytes, decimal format %} - {"payload":[112,97,116,97,116,101]} - -Uplink :: +/devices/+/up - - { - "payload": %sequence of bytes, decimal format%, - "metadata": [{ - "coding_rate": % string %, - "data_rate": % string %, - "frequency": % float %, - "timestamp": % uint %, - "rssi": % int %, - "lsnr": % float %, - "altitude": % int %, - "latitude": % float %, - "longitude": % float % - }, ... ] - } - - { - "payload": [112,97,116,97,116,101], - "metadata": [{ - "coding_rate": "4/6", - "data_rate": "SF8BW125", - "frequency": "866.345", - "timestamp": "123698454", - "rssi": "-37", - "lsnr": "5.3", - "altitude": "56", - "latitude": "-14.678", - "longitude": "33.120182" - }] - } -*/ -package mqtt diff --git a/core/adapters/mqtt/mqtt.go b/core/adapters/mqtt/mqtt.go deleted file mode 100644 index 4153ff19d..000000000 --- a/core/adapters/mqtt/mqtt.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "encoding/hex" - "encoding/json" - "fmt" - "strings" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" - "github.com/yosssi/gmq/mqtt" - "github.com/yosssi/gmq/mqtt/client" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -// Interface defines a public interface for the mqtt adapter -type Interface interface { - core.AppClient - Start(inMsg <-chan Msg, handler core.HandlerServer) -} - -type adapter struct { - Components -} - -// Components defines a structure to make the instantiation easier to read -type Components struct { - Client Client - Ctx log.Interface -} - -// Options defines a structure to make the instantiation easier to read -type Options struct { -} - -// Msg are emitted by an MQTT subscriber towards the adapter -type Msg struct { - Topic string - Payload []byte - Type msgType -} - -// msgType constants are used in MQTTMsg to characterise the kind of message processed -const ( - Down msgType = iota + 1 -) - -type msgType byte - -// New constructs an mqtt adapter responsible for making the bridge between the handler and -// application. -func New(c Components, o Options) Interface { - return adapter{Components: c} -} - -// Start eventually launches the mqtt message internal consumer -func (a adapter) Start(inMsg <-chan Msg, handler core.HandlerServer) { - go a.consumeMQTTMsg(inMsg, handler) -} - -// HandleData implements the core.AppClient interface -func (a adapter) HandleData(bctx context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) { - stats.MarkMeter("mqtt_adapter.send") - - // Verify the packet integrity - // TODO Move this elsewhere, make it a function call validate() ... - if req == nil { - stats.MarkMeter("mqtt_adapter.uplink.invalid") - return nil, errors.New(errors.Structural, "Received Nil Application Request") - } - if len(req.Payload) == 0 { - stats.MarkMeter("mqtt_adapter.uplink.invalid") - return nil, errors.New(errors.Structural, "Invalid Packet Payload") - } - if len(req.DevEUI) != 8 { - stats.MarkMeter("mqtt_adapter.uplink.invalid") - return nil, errors.New(errors.Structural, "Invalid Device EUI") - } - if len(req.AppEUI) != 8 { - stats.MarkMeter("mqtt_adapter.uplink.invalid") - return nil, errors.New(errors.Structural, "Invalid Application EUI") - } - if req.Metadata == nil { - stats.MarkMeter("mqtt_adapter.uplink.invalid") - return nil, errors.New(errors.Structural, "Missing Mandatory Metadata") - } - ctx := a.Ctx.WithField("appEUI", req.AppEUI).WithField("devEUI", req.DevEUI) - - // Marshal the packet - dataUp := core.DataUpAppReq{ - Payload: req.Payload, - Metadata: core.ProtoMetaToAppMeta(req.Metadata...), - } - msg, err := dataUp.MarshalMsg(nil) - if err != nil { - return nil, errors.New(errors.Structural, "Unable to marshal the application payload") - } - - // Actually send it - ctx.Debug("Sending packet") - deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) - err = a.Client.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte(fmt.Sprintf("%s/devices/%s/up", aeui, deui)), - Message: msg, - }) - - if err != nil { - return nil, errors.New(errors.Operational, err) - } - return nil, nil -} - -// HandleJoin implements the core.AppClient interface -func (a adapter) HandleJoin(bctx context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) { - if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.Metadata) == 0 { - a.Ctx.Debug("Received invalid JoinAppReq") - return nil, errors.New(errors.Structural, "Invalid request parameters") - } - otaa := core.OTAAAppReq{ - Metadata: core.ProtoMetaToAppMeta(req.Metadata...), - } - data, err := otaa.MarshalMsg(nil) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - deui, aeui := hex.EncodeToString(req.DevEUI), hex.EncodeToString(req.AppEUI) - err = a.Client.Publish(&client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte(fmt.Sprintf("%s/devices/%s/activations", aeui, deui)), - Message: data, - }) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - return nil, nil -} - -// consumeMQTTMsg processes incoming messages from MQTT broker. -// -// It runs in its own goroutine -func (a adapter) consumeMQTTMsg(chmsg <-chan Msg, handler core.HandlerServer) { - a.Ctx.Debug("Start consuming MQTT messages") - for msg := range chmsg { - switch msg.Type { - case Down: - req, err := handleDataDown(msg) - if err == nil { - _, err = handler.HandleDataDown(context.Background(), req) - } - if err != nil { - a.Ctx.WithError(err).Debug("Unable to consume data down") - } - default: - a.Ctx.Debug("Unsupported MQTT message's type") - } - } - a.Ctx.Debug("Stop consuming MQTT messages") -} - -// handleDataDown parses and handles Downlink message coming through MQTT -func handleDataDown(msg Msg) (*core.DataDownHandlerReq, error) { - // Ensure the query / topic parameters are valid - topicInfos := strings.Split(msg.Topic, "/") - if len(topicInfos) != 4 { - return nil, errors.New(errors.Structural, "Unexpect (and invalid) mqtt topic") - } - appEUI, erra := hex.DecodeString(topicInfos[0]) - devEUI, errd := hex.DecodeString(topicInfos[2]) - if erra != nil || errd != nil || len(appEUI) != 8 || len(devEUI) != 8 { - return nil, errors.New(errors.Structural, "Topic constituted of invalid AppEUI or DevEUI") - } - - // Retrieve the message payload - var req core.DataDownAppReq - if _, err := req.UnmarshalMsg(msg.Payload); err != nil { - if err = json.Unmarshal(msg.Payload, &req); err != nil { - return nil, errors.New(errors.Structural, err) - } - } - if len(req.Payload) == 0 { - return nil, errors.New(errors.Structural, "There's now data to handle") - } - - // Convert it to an handler downlink - return &core.DataDownHandlerReq{ - Payload: req.Payload, - TTL: req.TTL, - AppEUI: appEUI, - DevEUI: devEUI, - }, nil -} diff --git a/core/adapters/mqtt/mqtt_test.go b/core/adapters/mqtt/mqtt_test.go deleted file mode 100644 index 2706cbeac..000000000 --- a/core/adapters/mqtt/mqtt_test.go +++ /dev/null @@ -1,528 +0,0 @@ -// Copyright © 2016 T//e Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "fmt" - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/yosssi/gmq/mqtt" - "github.com/yosssi/gmq/mqtt/client" - "golang.org/x/net/context" -) - -func TestHandleDataDown(t *testing.T) { - { - Desc(t, "Invalid topic :: TTN") - - // Build - msg := Msg{ - Topic: "TTN", - Payload: []byte(`{"payload":"patate"}`), - Type: Down, - } - var want *core.DataDownHandlerReq - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "DataDown Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid topic :: 01/devices/0102030405060708/down") - - // Build - msg := Msg{ - Topic: "01/devices/0102030405060708/down", - Payload: []byte(`{"payload":"patate"}`), - Type: Down, - } - var want *core.DataDownHandlerReq - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "DataDown Handler Requests") - } - - // -------------------- - - { - Desc(t, "Invalid topic :: 0102030405060708/devices/010203040506/down") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/010203040506/down", - Payload: []byte(`{"payload":"patate"}`), - Type: Down, - } - var want *core.DataDownHandlerReq - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "DataDown Handler Requests") - } - - // -------------------- - - { - Desc(t, "Valid topic, invalid Message Pack payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/0910111213141516/down", - Payload: []byte{129, 167, 112, 97, 121, 108, 111, 97, 100, 150, 112, 97, 116, 97, 116, 101}, - Type: Down, - } - var want *core.DataDownHandlerReq - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "DataDown Handler Requests") - } - - // -------------------- - - { - Desc(t, "Valid topic, invalid json") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/0910111213141516/down", - Payload: []byte(`{"ttn":14}`), - Type: Down, - } - var want *core.DataDownHandlerReq - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, ErrStructural, err) - Check(t, want, req, "DataDown Handler Requests") - } - - // -------------------- - - { - Desc(t, "Valid topic, valid JSON payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/0910111213141516/down", - Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), - Type: Down, - } - want := &core.DataDownHandlerReq{ - Payload: []byte("patate"), - AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - DevEUI: []byte{0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}, - } - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, nil, err) - Check(t, want, req, "DataDown Handler Requests") - } - - // -------------------- - - { - Desc(t, "Valid topic, valid Message Pack payload") - - // Build - msg := Msg{ - Topic: "0102030405060708/devices/0910111213141516/down", - Payload: []byte{129, 167, 112, 97, 121, 108, 111, 97, 100, 196, 6, 112, 97, 116, 97, 116, 101}, - Type: Down, - } - want := &core.DataDownHandlerReq{ - Payload: []byte("patate"), - AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - DevEUI: []byte{0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16}, - } - - // Operate - req, err := handleDataDown(msg) - - // Check - CheckErrors(t, nil, err) - Check(t, want, req, "DataDown Handler Requests") - } -} - -func TestConsumeMQTTMsg(t *testing.T) { - { - Desc(t, "Consume Valid MsgDown") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - handler := mocks.NewHandlerServer() - chmsg := make(chan Msg) - adapter.Start(chmsg, handler) - - wantDown := &core.DataDownHandlerReq{ - Payload: []byte("patate"), - DevEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Operate - chmsg <- Msg{ - Type: Down, - Topic: "0102030405060708/devices/0807060504030201/down", - Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), - } - - // Checks - Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - - // Clean - close(chmsg) - } - - // -------------------- - - { - Desc(t, "Consume invalid MsgDown") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - handler := mocks.NewHandlerServer() - chmsg := make(chan Msg) - adapter.Start(chmsg, handler) - - var wantDown *core.DataDownHandlerReq - - // Operate - chmsg <- Msg{ - Type: Down, - Topic: "0102030405060708/devices/08070605040/down", - Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), - } - - // Checks - Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - - // Clean - close(chmsg) - } - - // -------------------- - - { - Desc(t, "Consume Invalid Message Type") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - handler := mocks.NewHandlerServer() - chmsg := make(chan Msg) - adapter.Start(chmsg, handler) - - var wantDown *core.DataDownHandlerReq - - // Operate - chmsg <- Msg{ - Type: 14, - Topic: "0102030405060708/devices/0807060504030201/down", - Payload: []byte(`{"payload":[112,97,116,97,116,101]}`), - } - - // Checks - Check(t, wantDown, handler.InHandleDataDown.Req, "Handler Down Requests") - - // Clean - close(chmsg) - } -} - -func TestHandleData(t *testing.T) { - { - Desc(t, "Handle Invalid AppReq -> Empty payload") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - - // Expectations - var wantRes *core.DataAppRes - var wantPub *client.PublishOptions - var wantErr = ErrStructural - - // Operate - res, err := adapter.HandleData( - context.Background(), - &core.DataAppReq{ - Payload: nil, - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - Metadata: []*core.Metadata{}, - }, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } - - // -------------------- - - { - Desc(t, "Handle Invalid AppReq -> Invalid AppEUI") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - - // Expectations - var wantRes *core.DataAppRes - var wantPub *client.PublishOptions - var wantErr = ErrStructural - - // Operate - res, err := adapter.HandleData( - context.Background(), - &core.DataAppReq{ - Payload: []byte("patate"), - AppEUI: []byte{1, 2, 3, 4, 5, 6}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - Metadata: []*core.Metadata{}, - }, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } - - // -------------------- - - { - Desc(t, "Handle Invalid AppReq -> Invalid DevEUI") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - - // Expectations - var wantRes *core.DataAppRes - var wantPub *client.PublishOptions - var wantErr = ErrStructural - - // Operate - res, err := adapter.HandleData( - context.Background(), - &core.DataAppReq{ - Payload: []byte("patate"), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4, 5, 6}, - Metadata: []*core.Metadata{}, - }, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } - - // -------------------- - - { - Desc(t, "Handle Invalid AppReq -> No Metadata") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - - // Expectations - var wantRes *core.DataAppRes - var wantPub *client.PublishOptions - var wantErr = ErrStructural - - // Operate - res, err := adapter.HandleData( - context.Background(), - &core.DataAppReq{ - Payload: []byte("patate"), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - Metadata: nil, - }, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } - - // -------------------- - - { - Desc(t, "Handle Invalid AppReq -> Nil AppReq") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - - // Expectations - var wantRes *core.DataAppRes - var wantPub *client.PublishOptions - var wantErr = ErrStructural - - // Operate - res, err := adapter.HandleData( - context.Background(), - nil, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } - - // -------------------- - - { - Desc(t, "Handle Valid AppReq, Fail to Publish") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - components.Client.(*MockClient).Failures["Publish"] = fmt.Errorf("Mock Error") - adapter := New(components, options) - msg := core.DataUpAppReq{Payload: []byte("patate"), Metadata: []core.AppMetadata{}} - data, err := msg.MarshalMsg(nil) - FatalUnless(t, err) - - // Expectations - var wantRes *core.DataAppRes - var wantPub = &client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte("0102030405060708/devices/0000000001020304/up"), - Message: data, - } - var wantErr = ErrOperational - - // Operate - res, err := adapter.HandleData( - context.Background(), - &core.DataAppReq{ - Payload: []byte("patate"), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - Metadata: []*core.Metadata{}, - }, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } - - // -------------------- - - { - Desc(t, "Handle Valid AppReq, Publish successful") - - // Build - options := Options{} - components := Components{ - Client: NewMockClient(), - Ctx: GetLogger(t, "MQTT Adapter"), - } - adapter := New(components, options) - msg := core.DataUpAppReq{Payload: []byte("patate"), Metadata: []core.AppMetadata{}} - data, err := msg.MarshalMsg(nil) - FatalUnless(t, err) - - // Expectations - var wantRes *core.DataAppRes - var wantPub = &client.PublishOptions{ - QoS: mqtt.QoS2, - Retain: false, - TopicName: []byte("0102030405060708/devices/0000000001020304/up"), - Message: data, - } - var wantErr *string - - // Operate - res, err := adapter.HandleData( - context.Background(), - &core.DataAppReq{ - Payload: []byte("patate"), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - Metadata: []*core.Metadata{}, - }, - ) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Responses") - Check(t, wantPub, components.Client.(*MockClient).InPublish.Options, "Publications") - } -} diff --git a/core/adapters/mqtt/safeclient.go b/core/adapters/mqtt/safeclient.go deleted file mode 100644 index 2a342e3bc..000000000 --- a/core/adapters/mqtt/safeclient.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "github.com/yosssi/gmq/mqtt/client" -) - -// NOTE: All that code could be easily generated - -// safeClient implements the mqtt.Client interface. It is completely concurrent-safe -type safeClient struct { - chcmd chan<- interface{} -} - -// Publish implements the Client interface -func (c safeClient) Publish(o *client.PublishOptions) error { - cherr := make(chan error) - c.chcmd <- cmdPublish{options: o, cherr: cherr} - return <-cherr -} - -// Terminate implements the Client interface -func (c safeClient) Terminate() { - cherr := make(chan error) - c.chcmd <- cmdTerminate{cherr: cherr} - <-cherr -} - -type cmdPublish struct { - options *client.PublishOptions - cherr chan<- error -} - -type cmdTerminate struct { - cherr chan<- error -} - -type cmdClient struct { - options *client.Client - cherr chan<- error -} From dfa51815197df17e9ee1555994c4c996a44b2e40 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 19:11:04 +0200 Subject: [PATCH 1269/2266] [ttnctl] Update cli messages --- core/adapters/mqtt/adapter_test.go | 4 ++-- ttnctl/cmd/subscribe.go | 1 + ttnctl/util/mqtt.go | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index db04af772..3f88eff74 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -200,7 +200,7 @@ func TestSubscribeDownlink(t *testing.T) { devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - <-time.After(20 * time.Millisecond) + <-time.After(25 * time.Millisecond) expected := &core.DataDownHandlerReq{ AppEUI: appEUI, @@ -226,7 +226,7 @@ func TestSubscribeInvalidDownlink(t *testing.T) { devEUI := []byte{0x08, 0x07, 0x06, 0x09, 0x04, 0x03, 0x02, 0x01} client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{}}).Wait() - <-time.After(20 * time.Millisecond) + <-time.After(25 * time.Millisecond) a.So(handler.InHandleDataDown.Req, ShouldBeNil) } diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index a1909a7f7..8e384d43d 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -64,6 +64,7 @@ application.`, if ok := token.Wait(); !ok { ctx.WithError(token.Error()).Fatal("Could not subscribe") } + ctx.Info("Subscribed. Waiting for messages...") <-make(chan bool) diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 1b60f6c36..62b5bb690 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -22,7 +22,7 @@ func GetMQTTClient(ctx log.Interface) mqtt.Client { } // NOTE: until the MQTT server supports access tokens, we'll have to ask for a password. - fmt.Print("Password: ") + fmt.Printf("Password for account %s: ", user.Email) password, err := gopass.GetPasswd() if err != nil { ctx.Fatal(err.Error()) From 1b2e614b461619ee169a8fd522002062bc3b78fe Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 19:15:19 +0200 Subject: [PATCH 1270/2266] [mqtt-adapter] copy-paste --- core/adapters/mqtt/adapter_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index 3f88eff74..6741c4c8d 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -187,7 +187,7 @@ func TestHandleInvalidJoin(t *testing.T) { func TestSubscribeDownlink(t *testing.T) { a := New(t) - ctx := GetLogger(t, "TestHandleJoin") + ctx := GetLogger(t, "TestSubscribeDownlink") client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") client.Connect() @@ -213,7 +213,7 @@ func TestSubscribeDownlink(t *testing.T) { func TestSubscribeInvalidDownlink(t *testing.T) { a := New(t) - ctx := GetLogger(t, "TestHandleJoin") + ctx := GetLogger(t, "TestSubscribeInvalidDownlink") client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") client.Connect() From d27e658f96e458c0dd6d368824921b489a91739b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 1 Apr 2016 19:45:13 +0200 Subject: [PATCH 1271/2266] [mqtt-test] Increase wait time --- core/adapters/mqtt/adapter_test.go | 10 ++++++---- mqtt/client_test.go | 8 ++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index 6741c4c8d..a5636c5c8 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -194,13 +194,14 @@ func TestSubscribeDownlink(t *testing.T) { adapter := NewAdapter(ctx, client) handler := mocks.NewHandlerServer() - adapter.SubscribeDownlink(handler) + err := adapter.SubscribeDownlink(handler) + a.So(err, ShouldBeNil) appEUI := []byte{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - <-time.After(25 * time.Millisecond) + <-time.After(50 * time.Millisecond) expected := &core.DataDownHandlerReq{ AppEUI: appEUI, @@ -220,13 +221,14 @@ func TestSubscribeInvalidDownlink(t *testing.T) { adapter := NewAdapter(ctx, client) handler := mocks.NewHandlerServer() - adapter.SubscribeDownlink(handler) + err := adapter.SubscribeDownlink(handler) + a.So(err, ShouldBeNil) appEUI := []byte{0x04, 0x03, 0x03, 0x09, 0x05, 0x06, 0x07, 0x08} devEUI := []byte{0x08, 0x07, 0x06, 0x09, 0x04, 0x03, 0x02, 0x01} client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{}}).Wait() - <-time.After(25 * time.Millisecond) + <-time.After(50 * time.Millisecond) a.So(handler.InHandleDataDown.Req, ShouldBeNil) } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 0769ab2d1..2cb45c24c 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -88,7 +88,7 @@ func TestRandomTopicPublish(t *testing.T) { c.(*defaultClient).mqtt.Subscribe("randomtopic", QoS, nil).Wait() c.(*defaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}).Wait() - <-time.After(25 * time.Millisecond) + <-time.After(50 * time.Millisecond) ctx.Info("This test should have printed one message.") } @@ -221,7 +221,7 @@ func TestInvalidUplink(t *testing.T) { // Invalid Payload c.(*defaultClient).mqtt.Publish("0602030405060708/devices/0602030405060708/up", QoS, false, []byte{0x00}).Wait() - <-time.After(25 * time.Millisecond) + <-time.After(50 * time.Millisecond) ctx.Info("This test should have printed two warnings.") } @@ -354,7 +354,7 @@ func TestInvalidDownlink(t *testing.T) { // Invalid Payload c.(*defaultClient).mqtt.Publish("0603030405060708/devices/0602030405060708/down", QoS, false, []byte{0x00}).Wait() - <-time.After(25 * time.Millisecond) + <-time.After(50 * time.Millisecond) ctx.Info("This test should have printed two warnings.") } @@ -485,7 +485,7 @@ func TestInvalidActivations(t *testing.T) { // Invalid Payload c.(*defaultClient).mqtt.Publish("0604030405060708/devices/0602030405060708/activations", QoS, false, []byte{0x00}).Wait() - <-time.After(25 * time.Millisecond) + <-time.After(50 * time.Millisecond) ctx.Info("This test should have printed two warnings.") } From 2f14b7bdd5d12b46ea2baee38692250bb9ef4a8e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 2 Apr 2016 16:30:10 +0200 Subject: [PATCH 1272/2266] [ttnctl] Clean disconnect from MQTT --- ttnctl/cmd/downlink.go | 6 ++++-- ttnctl/cmd/subscribe.go | 12 ++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index afe67408d..59572a9be 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -46,10 +46,12 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one token := client.PublishDownlink(appEUI, devEUI, dataDown) - if ok := token.Wait(); !ok { - ctx.WithError(token.Error()).Fatal("Could not subscribe") + if token.Wait(); token.Error() != nil { + ctx.WithError(token.Error()).Fatal("Could not publish downlink") } + client.Disconnect() + }, } diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 8e384d43d..27fbe8a1e 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -4,6 +4,8 @@ package cmd import ( + "os" + "os/signal" "regexp" "github.com/TheThingsNetwork/ttn/core" @@ -61,12 +63,18 @@ application.`, }) - if ok := token.Wait(); !ok { + if token.Wait(); token.Error() != nil { ctx.WithError(token.Error()).Fatal("Could not subscribe") } ctx.Info("Subscribed. Waiting for messages...") - <-make(chan bool) + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + + // Block until a signal is received. + <-c + + client.Disconnect() }, } From b77ff78a10c2d6b70773246c29dacc3beacc049e Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Apr 2016 16:38:30 +0200 Subject: [PATCH 1273/2266] Avoid return nil when dealing with protos --- core/adapters/mqtt/adapter.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index 7e455a29f..70c580113 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -27,7 +27,7 @@ type defaultAdapter struct { // HandleData implements the core.AppClient interface func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) { if err := validateData(req); err != nil { - return nil, errors.New(errors.Structural, err) + return new(core.DataAppRes), errors.New(errors.Structural, err) } dataUp := core.DataUpAppReq{ @@ -44,9 +44,9 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . token := a.client.PublishUplink(req.AppEUI, req.DevEUI, dataUp) if token.Wait(); token.Error() != nil { - return nil, errors.New(errors.Structural, token.Error()) + return new(core.DataAppRes), errors.New(errors.Structural, token.Error()) } - return nil, nil + return new(core.DataAppRes), nil } func validateData(req *core.DataAppReq) error { @@ -75,7 +75,7 @@ func validateData(req *core.DataAppReq) error { // HandleJoin implements the core.AppClient interface func (a *defaultAdapter) HandleJoin(_ context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) { if err := validateJoin(req); err != nil { - return nil, errors.New(errors.Structural, err) + return new(core.JoinAppRes), errors.New(errors.Structural, err) } otaa := core.OTAAAppReq{ @@ -91,9 +91,9 @@ func (a *defaultAdapter) HandleJoin(_ context.Context, req *core.JoinAppReq, _ . token := a.client.PublishActivation(req.AppEUI, req.DevEUI, otaa) if token.Wait(); token.Error() != nil { - return nil, errors.New(errors.Structural, token.Error()) + return new(core.JoinAppRes), errors.New(errors.Structural, token.Error()) } - return nil, nil + return new(core.JoinAppRes), nil } func validateJoin(req *core.JoinAppReq) error { From d70c1bfb0dd41af096037daa004ec048c56dd86c Mon Sep 17 00:00:00 2001 From: ktorz Date: Sat, 2 Apr 2016 16:39:04 +0200 Subject: [PATCH 1274/2266] Remove username and password empty check in cmd --- cmd/handler.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index ef64d9478..cc1074494 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -109,10 +109,6 @@ The Handler is the bridge between The Things Network and applications. ctx.WithError(err).Fatal("Could not dial broker") } - if viper.GetString("handler.mqtt-username") == "" || viper.GetString("handler.mqtt-password") == "" { - ctx.WithError(fmt.Errorf("MQTT username or password not set")).Fatal("Could not connect to MQTT") - } - // MQTT Client & adapter mqttClient := ttnMQTT.NewClient( ctx.WithField("adapter", "handler-mqtt"), From 36e66351e8f12fe12d5b3c4635190e340cb021ee Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 15:13:23 +0200 Subject: [PATCH 1275/2266] Use users token --- ttnctl/util/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index f3da790a0..1bbd8a49c 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -133,7 +133,7 @@ func refreshToken(server string, auth *Auth) (*Auth, error) { } func newToken(server, email string, values url.Values) (*Auth, error) { - uri := fmt.Sprintf("%s/token", server) + uri := fmt.Sprintf("%s/users/token", server) req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) if err != nil { return nil, err From d5ae1208143585af5297515f4248b72968f222c6 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 15:32:47 +0200 Subject: [PATCH 1276/2266] Show access keys and delete application --- ttnctl/cmd/applications.go | 55 ++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index ffe91cd07..054db072a 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -18,9 +18,11 @@ import ( ) type app struct { - EUI string `json:"eui"` - Name string `json:"name"` - Owner string `json:"owner"` + EUI string `json:"eui"` + Name string `json:"name"` + Owner string `json:"owner"` + AccessKeys []string `json:"accessKeys"` + Valid bool `json:"valid"` } // applicationsCmd represents the applications command @@ -55,10 +57,11 @@ var applicationsCmd = &cobra.Command{ ctx.Infof("Found %d application(s)", len(apps)) table := uitable.New() table.MaxColWidth = 70 - table.AddRow("EUI", "Name", "Owner") + table.AddRow("EUI", "Name", "Owner", "Access Keys", "Valid") for _, app := range apps { - table.AddRow(app.EUI, app.Name, app.Owner) + table.AddRow(app.EUI, app.Name, app.Owner, strings.Join(app.AccessKeys, ", "), app.Valid) } + fmt.Println(table) }, } @@ -110,7 +113,49 @@ var applicationsCreateCmd = &cobra.Command{ }, } +var applicationsDeleteCmd = &cobra.Command{ + Use: "delete [eui]", + Short: "Delete an application", + Long: `ttnctl application delete deletes an existing application.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.Help() + return + } + + appEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + server := viper.GetString("ttn-account-server") + req, err := util.NewRequestWithAuth(server, "DELETE", fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)), nil) + if err != nil { + ctx.WithError(err).Fatal("Failed to create authenticated request") + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Failed to delete application") + } + if resp.StatusCode != http.StatusOK { + ctx.Fatalf("Failed to delete application: %s", resp.Status) + } + + ctx.Info("Application deleted successfully") + + // We need to refresh the token to remove the application from the set of + // claims + _, err = util.RefreshToken(server) + if err != nil { + log.WithError(err).Warn("Failed to refresh token. Please login") + } + }, +} + func init() { RootCmd.AddCommand(applicationsCmd) applicationsCmd.AddCommand(applicationsCreateCmd) + applicationsCmd.AddCommand(applicationsDeleteCmd) } From 54bf8a26aad718b4c5efa3e8aa7794f26a3c4a52 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 15:43:56 +0200 Subject: [PATCH 1277/2266] Authorize other user for application --- ttnctl/cmd/applications.go | 50 +++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 054db072a..b4089b7ac 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -32,7 +32,8 @@ var applicationsCmd = &cobra.Command{ Long: `ttnctl applications retrieves the applications of the logged on user.`, Run: func(cmd *cobra.Command, args []string) { server := viper.GetString("ttn-account-server") - req, err := util.NewRequestWithAuth(server, "GET", fmt.Sprintf("%s/applications", server), nil) + uri := fmt.Sprintf("%s/applications", server) + req, err := util.NewRequestWithAuth(server, "GET", uri, nil) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") } @@ -82,11 +83,12 @@ var applicationsCreateCmd = &cobra.Command{ } server := viper.GetString("ttn-account-server") + uri := fmt.Sprintf("%s/applications", server) values := url.Values{ "eui": {fmt.Sprintf("%X", appEUI)}, "name": {args[1]}, } - req, err := util.NewRequestWithAuth(server, "POST", fmt.Sprintf("%s/applications", server), strings.NewReader(values.Encode())) + req, err := util.NewRequestWithAuth(server, "POST", uri, strings.NewReader(values.Encode())) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") } @@ -129,7 +131,8 @@ var applicationsDeleteCmd = &cobra.Command{ } server := viper.GetString("ttn-account-server") - req, err := util.NewRequestWithAuth(server, "DELETE", fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)), nil) + uri := fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)) + req, err := util.NewRequestWithAuth(server, "DELETE", uri, nil) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") } @@ -154,8 +157,49 @@ var applicationsDeleteCmd = &cobra.Command{ }, } +var applicationsAuthorizeCmd = &cobra.Command{ + Use: "authorize [eui] [e-mail]", + Short: "Authorize a user for the application", + Long: `ttnctl applications authorize lets you authorize a user for an application.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + cmd.Help() + return + } + + appEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + server := viper.GetString("ttn-account-server") + uri := fmt.Sprintf("%s/applications/%s/authorize", server, fmt.Sprintf("%X", appEUI)) + values := url.Values{ + "email": {args[1]}, + } + req, err := util.NewRequestWithAuth(server, "PUT", uri, strings.NewReader(values.Encode())) + if err != nil { + ctx.WithError(err).Fatal("Failed to create authenticated request") + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Failed to authorize user") + } + if resp.StatusCode != http.StatusOK { + ctx.Fatalf("Failed to authorize user: %s", resp.Status) + } + + ctx.Info("User authorized successfully") + }, +} + func init() { RootCmd.AddCommand(applicationsCmd) applicationsCmd.AddCommand(applicationsCreateCmd) applicationsCmd.AddCommand(applicationsDeleteCmd) + applicationsCmd.AddCommand(applicationsAuthorizeCmd) } From d448bc69d14fa50f633a03b86b0a7308b4e2cab0 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 16:05:13 +0200 Subject: [PATCH 1278/2266] Uppercase parsed AppEUI and DevEUI --- core/api/topics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/api/topics.go b/core/api/topics.go index b22f36e20..ed4e742d0 100644 --- a/core/api/topics.go +++ b/core/api/topics.go @@ -55,7 +55,7 @@ func DecodeDeviceTopic(topic string) (*DeviceTopic, error) { return nil, errors.New("Not a device topic") } - return &DeviceTopic{parts[0], parts[2], DeviceTopicType(parts[3])}, nil + return &DeviceTopic{strings.ToUpper(parts[0]), strings.ToUpper(parts[2]), DeviceTopicType(parts[3])}, nil } // Encode encodes the DeviceTopic to a topic From 7e2320842e3caca41cd1a3f8dea920ef60915fc5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 4 Apr 2016 13:12:06 +0200 Subject: [PATCH 1279/2266] [hotfix] Fix MQTT test --- core/adapters/mqtt/adapter_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index a5636c5c8..0d291c4d3 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -58,7 +58,7 @@ func TestHandleData(t *testing.T) { res, err := adapter.HandleData(context.Background(), &req) a.So(err, ShouldBeNil) - a.So(res, ShouldBeNil) + a.So(res, ShouldResemble, new(core.DataAppRes)) wg.Wait() @@ -142,7 +142,7 @@ func TestHandleJoin(t *testing.T) { res, err := adapter.HandleJoin(context.Background(), &req) a.So(err, ShouldBeNil) - a.So(res, ShouldBeNil) + a.So(res, ShouldResemble, new(core.JoinAppRes)) wg.Wait() } From 20e3fa9cb37fdca9df94a242f7f73d9fd746d561 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 12:17:07 -0700 Subject: [PATCH 1280/2266] Fixed test --- ttnctl/util/auth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go index c97b1c0db..58a34bf88 100644 --- a/ttnctl/util/auth_test.go +++ b/ttnctl/util/auth_test.go @@ -13,7 +13,7 @@ import ( func newTokenServer(a *Assertion) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, "/token") + a.So(r.RequestURI, ShouldEqual, "/users/token") a.So(r.Method, ShouldEqual, "POST") username, password, ok := r.BasicAuth() From e337eedd9f9ebfb3e9bd4b440758b94ea9dee586 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 15:13:23 +0200 Subject: [PATCH 1281/2266] Use users token --- ttnctl/util/auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index f3da790a0..1bbd8a49c 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -133,7 +133,7 @@ func refreshToken(server string, auth *Auth) (*Auth, error) { } func newToken(server, email string, values url.Values) (*Auth, error) { - uri := fmt.Sprintf("%s/token", server) + uri := fmt.Sprintf("%s/users/token", server) req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) if err != nil { return nil, err From ffdf902562079dd1b9d64fcce9a8c2d9756eaba8 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 15:32:47 +0200 Subject: [PATCH 1282/2266] Show access keys and delete application --- ttnctl/cmd/applications.go | 55 ++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index ffe91cd07..054db072a 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -18,9 +18,11 @@ import ( ) type app struct { - EUI string `json:"eui"` - Name string `json:"name"` - Owner string `json:"owner"` + EUI string `json:"eui"` + Name string `json:"name"` + Owner string `json:"owner"` + AccessKeys []string `json:"accessKeys"` + Valid bool `json:"valid"` } // applicationsCmd represents the applications command @@ -55,10 +57,11 @@ var applicationsCmd = &cobra.Command{ ctx.Infof("Found %d application(s)", len(apps)) table := uitable.New() table.MaxColWidth = 70 - table.AddRow("EUI", "Name", "Owner") + table.AddRow("EUI", "Name", "Owner", "Access Keys", "Valid") for _, app := range apps { - table.AddRow(app.EUI, app.Name, app.Owner) + table.AddRow(app.EUI, app.Name, app.Owner, strings.Join(app.AccessKeys, ", "), app.Valid) } + fmt.Println(table) }, } @@ -110,7 +113,49 @@ var applicationsCreateCmd = &cobra.Command{ }, } +var applicationsDeleteCmd = &cobra.Command{ + Use: "delete [eui]", + Short: "Delete an application", + Long: `ttnctl application delete deletes an existing application.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.Help() + return + } + + appEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + server := viper.GetString("ttn-account-server") + req, err := util.NewRequestWithAuth(server, "DELETE", fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)), nil) + if err != nil { + ctx.WithError(err).Fatal("Failed to create authenticated request") + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Failed to delete application") + } + if resp.StatusCode != http.StatusOK { + ctx.Fatalf("Failed to delete application: %s", resp.Status) + } + + ctx.Info("Application deleted successfully") + + // We need to refresh the token to remove the application from the set of + // claims + _, err = util.RefreshToken(server) + if err != nil { + log.WithError(err).Warn("Failed to refresh token. Please login") + } + }, +} + func init() { RootCmd.AddCommand(applicationsCmd) applicationsCmd.AddCommand(applicationsCreateCmd) + applicationsCmd.AddCommand(applicationsDeleteCmd) } From 4db601c8fa6fd8738aa018e2791a64ba69280b91 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 15:43:56 +0200 Subject: [PATCH 1283/2266] Authorize other user for application --- ttnctl/cmd/applications.go | 50 +++++++++++++++++++++++++++++++++++--- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 054db072a..b4089b7ac 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -32,7 +32,8 @@ var applicationsCmd = &cobra.Command{ Long: `ttnctl applications retrieves the applications of the logged on user.`, Run: func(cmd *cobra.Command, args []string) { server := viper.GetString("ttn-account-server") - req, err := util.NewRequestWithAuth(server, "GET", fmt.Sprintf("%s/applications", server), nil) + uri := fmt.Sprintf("%s/applications", server) + req, err := util.NewRequestWithAuth(server, "GET", uri, nil) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") } @@ -82,11 +83,12 @@ var applicationsCreateCmd = &cobra.Command{ } server := viper.GetString("ttn-account-server") + uri := fmt.Sprintf("%s/applications", server) values := url.Values{ "eui": {fmt.Sprintf("%X", appEUI)}, "name": {args[1]}, } - req, err := util.NewRequestWithAuth(server, "POST", fmt.Sprintf("%s/applications", server), strings.NewReader(values.Encode())) + req, err := util.NewRequestWithAuth(server, "POST", uri, strings.NewReader(values.Encode())) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") } @@ -129,7 +131,8 @@ var applicationsDeleteCmd = &cobra.Command{ } server := viper.GetString("ttn-account-server") - req, err := util.NewRequestWithAuth(server, "DELETE", fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)), nil) + uri := fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)) + req, err := util.NewRequestWithAuth(server, "DELETE", uri, nil) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") } @@ -154,8 +157,49 @@ var applicationsDeleteCmd = &cobra.Command{ }, } +var applicationsAuthorizeCmd = &cobra.Command{ + Use: "authorize [eui] [e-mail]", + Short: "Authorize a user for the application", + Long: `ttnctl applications authorize lets you authorize a user for an application.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + cmd.Help() + return + } + + appEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + server := viper.GetString("ttn-account-server") + uri := fmt.Sprintf("%s/applications/%s/authorize", server, fmt.Sprintf("%X", appEUI)) + values := url.Values{ + "email": {args[1]}, + } + req, err := util.NewRequestWithAuth(server, "PUT", uri, strings.NewReader(values.Encode())) + if err != nil { + ctx.WithError(err).Fatal("Failed to create authenticated request") + } + + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Failed to authorize user") + } + if resp.StatusCode != http.StatusOK { + ctx.Fatalf("Failed to authorize user: %s", resp.Status) + } + + ctx.Info("User authorized successfully") + }, +} + func init() { RootCmd.AddCommand(applicationsCmd) applicationsCmd.AddCommand(applicationsCreateCmd) applicationsCmd.AddCommand(applicationsDeleteCmd) + applicationsCmd.AddCommand(applicationsAuthorizeCmd) } From 31e2b4c2e9270d1491c016a0ef5eda24cd588404 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 3 Apr 2016 16:05:13 +0200 Subject: [PATCH 1284/2266] Uppercase parsed AppEUI and DevEUI --- core/api/topics.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/api/topics.go b/core/api/topics.go index b22f36e20..ed4e742d0 100644 --- a/core/api/topics.go +++ b/core/api/topics.go @@ -55,7 +55,7 @@ func DecodeDeviceTopic(topic string) (*DeviceTopic, error) { return nil, errors.New("Not a device topic") } - return &DeviceTopic{parts[0], parts[2], DeviceTopicType(parts[3])}, nil + return &DeviceTopic{strings.ToUpper(parts[0]), strings.ToUpper(parts[2]), DeviceTopicType(parts[3])}, nil } // Encode encodes the DeviceTopic to a topic From 62d2f59ee55fcf85b437e750caba28f587d3d1eb Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 12:17:07 -0700 Subject: [PATCH 1285/2266] Fixed test --- ttnctl/util/auth_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go index c97b1c0db..58a34bf88 100644 --- a/ttnctl/util/auth_test.go +++ b/ttnctl/util/auth_test.go @@ -13,7 +13,7 @@ import ( func newTokenServer(a *Assertion) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, "/token") + a.So(r.RequestURI, ShouldEqual, "/users/token") a.So(r.Method, ShouldEqual, "POST") username, password, ok := r.BasicAuth() From 16e0beb939892958583b6ffc74062d5dfc907ee9 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 12:29:29 -0700 Subject: [PATCH 1286/2266] Removed obsolete api package --- core/api/topics.go | 64 ------------------------ core/api/topics_test.go | 107 ---------------------------------------- 2 files changed, 171 deletions(-) delete mode 100644 core/api/topics.go delete mode 100644 core/api/topics_test.go diff --git a/core/api/topics.go b/core/api/topics.go deleted file mode 100644 index ed4e742d0..000000000 --- a/core/api/topics.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package api - -import ( - "errors" - "fmt" - "strings" -) - -// TopicType represents the type of a topic -type TopicType string - -const ( - // Devices indicates a topic for devices - Devices TopicType = "devices" -) - -// DeviceTopicType represents the type of a device topic -type DeviceTopicType string - -const ( - // Activations of devices - Activations DeviceTopicType = "activations" - // Uplink data from devices - Uplink DeviceTopicType = "up" - // Downlink data to devices - Downlink DeviceTopicType = "down" -) - -// DeviceTopic represents a publish/subscribe topic for application devices -type DeviceTopic struct { - AppEUI string - DevEUI string - Type DeviceTopicType -} - -// GetTopicType returns the type of the specified topic -func GetTopicType(topic string) (TopicType, error) { - parts := strings.Split(topic, "/") - if len(parts) < 2 { - return "", errors.New("Invalid format") - } - return TopicType(parts[1]), nil -} - -// DecodeDeviceTopic decodes the specified topic in a DeviceTopic structure -func DecodeDeviceTopic(topic string) (*DeviceTopic, error) { - parts := strings.Split(topic, "/") - if len(parts) < 4 { - return nil, errors.New("Invalid format") - } - if TopicType(parts[1]) != Devices { - return nil, errors.New("Not a device topic") - } - - return &DeviceTopic{strings.ToUpper(parts[0]), strings.ToUpper(parts[2]), DeviceTopicType(parts[3])}, nil -} - -// Encode encodes the DeviceTopic to a topic -func (t *DeviceTopic) Encode() (string, error) { - return fmt.Sprintf("%s/%s/%s/%s", t.AppEUI, Devices, t.DevEUI, t.Type), nil -} diff --git a/core/api/topics_test.go b/core/api/topics_test.go deleted file mode 100644 index 19cf088cd..000000000 --- a/core/api/topics_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package api - -import ( - "testing" - - . "github.com/smartystreets/goconvey/convey" -) - -func TestGetType(t *testing.T) { - Convey("Given the topic 0101/somevalue", t, func() { - topic := "0101/somevalue" - Convey("Then the type is somevalue", func() { - tp, err := GetTopicType(topic) - So(err, ShouldBeNil) - So(tp, ShouldEqual, "somevalue") - }) - }) - - Convey("Given the topic 0101/devices/AA/up", t, func() { - topic := "0101/devices/AA/up" - Convey("Then the type is devices", func() { - tp, err := GetTopicType(topic) - So(err, ShouldBeNil) - So(tp, ShouldEqual, Devices) - }) - }) - - Convey("Given the topic 0101", t, func() { - topic := "0101" - Convey("Then the type cannot be determined", func() { - _, err := GetTopicType(topic) - So(err, ShouldNotBeNil) - }) - }) -} - -func TestDecodeDeviceTopic(t *testing.T) { - Convey("Given the topic 0101/devices/AA/up", t, func() { - topic := "0101/devices/AA/up" - Convey("Then the type is devices", func() { - tp, err := GetTopicType(topic) - So(err, ShouldBeNil) - So(tp, ShouldEqual, Devices) - }) - - Convey("Then the device topic can be decoded", func() { - tp, err := DecodeDeviceTopic(topic) - So(err, ShouldBeNil) - - Convey("And appEui is 0101, devEui is AA and type is Uplink", func() { - So(tp.AppEUI, ShouldEqual, "0101") - So(tp.DevEUI, ShouldEqual, "AA") - So(tp.Type, ShouldEqual, Uplink) - }) - }) - }) - - Convey("Given the topic 0101/devices", t, func() { - topic := "0101/devices" - Convey("Then the device topic cannot be decoded", func() { - _, err := DecodeDeviceTopic(topic) - So(err, ShouldNotBeNil) - }) - }) - - Convey("Given the topic 0101/aa/bb/cc", t, func() { - topic := "0101/aa/bb/cc" - Convey("Then the device topic cannot be decoded", func() { - _, err := DecodeDeviceTopic(topic) - So(err, ShouldNotBeNil) - }) - }) -} - -func TestEncodeDeviceTopic(t *testing.T) { - Convey("Given the appEui 0202, devEui BB and type Downlink", t, func() { - tp := &DeviceTopic{ - AppEUI: "0202", - DevEUI: "BB", - Type: Downlink, - } - - Convey("Then the topic is 0202/devices/BB/down", func() { - topic, err := tp.Encode() - So(err, ShouldBeNil) - So(topic, ShouldEqual, "0202/devices/BB/down") - }) - }) -} - -func TestRoundtrip(t *testing.T) { - Convey("Given the input 0303/devices/CC/activations", t, func() { - input := "0303/devices/CC/activations" - - Convey("Then the encoded output is the same as the decoded input", func() { - tp, err := DecodeDeviceTopic(input) - So(err, ShouldBeNil) - - output, err := tp.Encode() - So(err, ShouldBeNil) - So(output, ShouldEqual, input) - }) - }) -} From 8766b16d7a038c895cf72a6438520df4947cdc3f Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 15:32:51 -0700 Subject: [PATCH 1287/2266] Consistent field names --- cmd/broker.go | 4 ++-- cmd/handler.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index dbdd5971f..606441c1c 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -40,8 +40,8 @@ devices (with their network session keys) with the Broker. stats.Enabled = false } ctx.WithFields(log.Fields{ - "devices database": viper.GetString("broker.db-devices"), - "applications database": viper.GetString("broker.db-apps"), + "devices-database": viper.GetString("broker.db-devices"), + "applications-database": viper.GetString("broker.db-apps"), "status-server": statusServer, "main-server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), }).Info("Using Configuration") diff --git a/cmd/handler.go b/cmd/handler.go index cc1074494..a29a0be1b 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -38,13 +38,13 @@ The Handler is the bridge between The Things Network and applications. stats.Enabled = false } ctx.WithFields(log.Fields{ - "devicesDatabase": viper.GetString("handler.db-devices"), - "packetsDatabase": viper.GetString("handler.db-packets"), - "status-server": statusServer, - "internal server": fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), - "public server": fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), - "ttn-broker": viper.GetString("handler.ttn-broker"), - "mqtt-broker": viper.GetString("handler.mqtt-broker"), + "devices-database": viper.GetString("handler.db-devices"), + "packets-database": viper.GetString("handler.db-packets"), + "status-server": statusServer, + "internal-server": fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), + "public-server": fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), + "ttn-broker": viper.GetString("handler.ttn-broker"), + "mqtt-broker": viper.GetString("handler.mqtt-broker"), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { From e39a0c85191c9ce7d4cd48a52cf2d803493afd3e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 15:35:06 -0700 Subject: [PATCH 1288/2266] Default handler username for MQTT --- cmd/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/handler.go b/cmd/handler.go index a29a0be1b..867e23572 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -177,7 +177,7 @@ func init() { viper.BindPFlag("handler.public-port", handlerCmd.Flags().Lookup("public-port")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker (uplink)") - handlerCmd.Flags().String("mqtt-username", "", "The username for the MQTT broker (uplink)") + handlerCmd.Flags().String("mqtt-username", "handler", "The username for the MQTT broker (uplink)") handlerCmd.Flags().String("mqtt-password", "", "The password for the MQTT broker (uplink)") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) From 6d881f7d9fd5f5b67084fab6f4a85f751cad15ea Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 14:47:04 -0700 Subject: [PATCH 1289/2266] Fixed generic naming for MQTT topic --- mqtt/client.go | 18 +++++++++--------- mqtt/topics.go | 26 +++++++++++++------------- mqtt/topics_test.go | 28 ++++++++++++++-------------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index c9c301eac..2971aaddc 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -130,7 +130,7 @@ func (c *defaultClient) IsConnected() bool { } func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) Token { - topic := Topic{appEUI, devEUI, Uplink} + topic := DeviceTopic{appEUI, devEUI, Uplink} msg, err := dataUp.MarshalMsg(nil) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -139,10 +139,10 @@ func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core. } func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token { - topic := Topic{appEUI, devEUI, Uplink} + topic := DeviceTopic{appEUI, devEUI, Uplink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic - topic, err := ParseTopic(msg.Topic()) + topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { if c.ctx != nil { c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") @@ -174,7 +174,7 @@ func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { } func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown core.DataDownAppReq) Token { - topic := Topic{appEUI, devEUI, Downlink} + topic := DeviceTopic{appEUI, devEUI, Downlink} msg, err := dataDown.MarshalMsg(nil) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -183,10 +183,10 @@ func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown c } func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token { - topic := Topic{appEUI, devEUI, Downlink} + topic := DeviceTopic{appEUI, devEUI, Downlink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic - topic, err := ParseTopic(msg.Topic()) + topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { if c.ctx != nil { c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") @@ -218,7 +218,7 @@ func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { } func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activation core.OTAAAppReq) Token { - topic := Topic{appEUI, devEUI, Activations} + topic := DeviceTopic{appEUI, devEUI, Activations} msg, err := activation.MarshalMsg(nil) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -227,10 +227,10 @@ func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activati } func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token { - topic := Topic{appEUI, devEUI, Activations} + topic := DeviceTopic{appEUI, devEUI, Activations} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic - topic, err := ParseTopic(msg.Topic()) + topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { if c.ctx != nil { c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") diff --git a/mqtt/topics.go b/mqtt/topics.go index fae380f19..9711abf86 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -9,28 +9,28 @@ import ( "regexp" ) -// TopicType represents the type of a device topic -type TopicType string +// DeviceTopicType represents the type of a device topic +type DeviceTopicType string const ( // Activations of devices - Activations TopicType = "activations" + Activations DeviceTopicType = "activations" // Uplink data from devices - Uplink TopicType = "up" + Uplink DeviceTopicType = "up" // Downlink data to devices - Downlink TopicType = "down" + Downlink DeviceTopicType = "down" ) -// Topic represents an MQTT topic for application devices +// DeviceTopic represents an MQTT topic for application devices // If the DevEUI is an empty []byte{}, it is considered to be a wildcard -type Topic struct { +type DeviceTopic struct { AppEUI []byte DevEUI []byte - Type TopicType + Type DeviceTopicType } -// ParseTopic parses an MQTT topic string to a Topic struct -func ParseTopic(topic string) (*Topic, error) { +// ParseDeviceTopic parses an MQTT device topic string to a DeviceTopic struct +func ParseDeviceTopic(topic string) (*DeviceTopic, error) { pattern := regexp.MustCompile("([0-9A-F]{16}|\\+)/(devices)/([0-9A-F]{16}|\\+)/(activations|up|down)") matches := pattern.FindStringSubmatch(topic) @@ -48,13 +48,13 @@ func ParseTopic(topic string) (*Topic, error) { devEUI, _ = hex.DecodeString(matches[3]) // validity asserted by our regex pattern } - topicType := TopicType(matches[4]) + topicType := DeviceTopicType(matches[4]) - return &Topic{appEUI, devEUI, topicType}, nil + return &DeviceTopic{appEUI, devEUI, topicType}, nil } // String implements the Stringer interface -func (t Topic) String() string { +func (t DeviceTopic) String() string { appEUI := "+" if len(t.AppEUI) > 0 { appEUI = fmt.Sprintf("%X", t.AppEUI) diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index 24a005c52..360c61574 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -9,55 +9,55 @@ import ( . "github.com/smartystreets/assertions" ) -func TestParseTopic(t *testing.T) { +func TestParseDeviceTopic(t *testing.T) { a := New(t) topic := "0102030405060708/devices/0807060504030201/up" - expected := &Topic{ + expected := &DeviceTopic{ AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, DevEUI: []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, Type: Uplink, } - got, err := ParseTopic(topic) + got, err := ParseDeviceTopic(topic) a.So(err, ShouldBeNil) a.So(got, ShouldResemble, expected) } -func TestParseTopicInvalid(t *testing.T) { +func TestParseDeviceTopicInvalid(t *testing.T) { a := New(t) - _, err := ParseTopic("000000000000000a/devices/0807060504030201/up") // AppEUI contains lowercase hex chars + _, err := ParseDeviceTopic("000000000000000a/devices/0807060504030201/up") // AppEUI contains lowercase hex chars a.So(err, ShouldNotBeNil) - _, err = ParseTopic("01020304050607/devices/0807060504030201/up") // AppEUI too short + _, err = ParseDeviceTopic("01020304050607/devices/0807060504030201/up") // AppEUI too short a.So(err, ShouldNotBeNil) - _, err = ParseTopic("abcdefghijklmnop/devices/08070605040302/up") // AppEUI contains invalid characters + _, err = ParseDeviceTopic("abcdefghijklmnop/devices/08070605040302/up") // AppEUI contains invalid characters a.So(err, ShouldNotBeNil) - _, err = ParseTopic("0102030405060708/devices/000000000000000a/up") // DevEUI contains lowercase hex chars + _, err = ParseDeviceTopic("0102030405060708/devices/000000000000000a/up") // DevEUI contains lowercase hex chars a.So(err, ShouldNotBeNil) - _, err = ParseTopic("0102030405060708/devices/08070605040302/up") // DevEUI too short + _, err = ParseDeviceTopic("0102030405060708/devices/08070605040302/up") // DevEUI too short a.So(err, ShouldNotBeNil) - _, err = ParseTopic("0102030405060708/devices/abcdefghijklmnop/up") // DevEUI contains invalid characters + _, err = ParseDeviceTopic("0102030405060708/devices/abcdefghijklmnop/up") // DevEUI contains invalid characters a.So(err, ShouldNotBeNil) - _, err = ParseTopic("0102030405060708/fridges/0102030405060708/up") // We don't support fridges (at least, not specifically fridges) + _, err = ParseDeviceTopic("0102030405060708/fridges/0102030405060708/up") // We don't support fridges (at least, not specifically fridges) a.So(err, ShouldNotBeNil) - _, err = ParseTopic("0102030405060708/devices/0102030405060708/emotions") // Devices usually don't publish emotions + _, err = ParseDeviceTopic("0102030405060708/devices/0102030405060708/emotions") // Devices usually don't publish emotions a.So(err, ShouldNotBeNil) } func TestTopicString(t *testing.T) { a := New(t) - topic := &Topic{ + topic := &DeviceTopic{ AppEUI: []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, DevEUI: []byte{0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21}, Type: Downlink, @@ -93,7 +93,7 @@ func TestTopicParseAndString(t *testing.T) { } for _, expected := range expectedList { - topic, err := ParseTopic(expected) + topic, err := ParseDeviceTopic(expected) a.So(err, ShouldBeNil) a.So(topic.String(), ShouldEqual, expected) } From b778fddd345056940509005b020c7b18af96d951 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 14:55:26 -0700 Subject: [PATCH 1290/2266] Human readable definition field names --- core/application.pb.go | 64 ++++++++++++++++++++++++++++++ core/broker_manager.pb.go | 64 ------------------------------ core/definitions.go | 28 ++++++------- core/definitions_gen.go | 82 +++++++++++++++++++-------------------- 4 files changed, 119 insertions(+), 119 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 66a1e3616..d85d42a4b 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -2,6 +2,66 @@ // source: application.proto // DO NOT EDIT! +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + application.proto + broker.proto + broker_manager.proto + core.proto + handler.proto + handler_manager.proto + lorawan.proto + router.proto + + It has these top-level messages: + DataAppReq + DataAppRes + JoinAppReq + JoinAppRes + DataBrokerReq + DataBrokerRes + JoinBrokerReq + JoinBrokerRes + ValidateOTAABrokerReq + ValidateOTAABrokerRes + UpsertABPBrokerReq + UpsertABPBrokerRes + BrokerDevice + ValidateTokenBrokerReq + ValidateTokenBrokerRes + Metadata + StatsMetadata + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + JoinHandlerReq + JoinHandlerRes + UpsertOTAAHandlerReq + UpsertOTAAHandlerRes + UpsertABPHandlerReq + UpsertABPHandlerRes + ListDevicesHandlerReq + ListDevicesHandlerRes + HandlerABPDevice + HandlerOTAADevice + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings + DataRouterReq + DataRouterRes + StatsReq + StatsRes + JoinRouterReq + JoinRouterRes +*/ package core import proto "github.com/golang/protobuf/proto" @@ -20,6 +80,10 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + type DataAppReq struct { Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index 1c7f21bb9..30d6f4c1a 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -2,66 +2,6 @@ // source: broker_manager.proto // DO NOT EDIT! -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - broker_manager.proto - handler_manager.proto - broker.proto - core.proto - application.proto - handler.proto - router.proto - lorawan.proto - - It has these top-level messages: - ValidateOTAABrokerReq - ValidateOTAABrokerRes - UpsertABPBrokerReq - UpsertABPBrokerRes - BrokerDevice - ValidateTokenBrokerReq - ValidateTokenBrokerRes - UpsertOTAAHandlerReq - UpsertOTAAHandlerRes - UpsertABPHandlerReq - UpsertABPHandlerRes - ListDevicesHandlerReq - ListDevicesHandlerRes - HandlerABPDevice - HandlerOTAADevice - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - Metadata - StatsMetadata - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings -*/ package core import proto "github.com/golang/protobuf/proto" @@ -80,10 +20,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - type ValidateOTAABrokerReq struct { Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` diff --git a/core/definitions.go b/core/definitions.go index 4d93234bf..99b966466 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -11,26 +11,26 @@ type DataUpAppReq struct { Metadata []AppMetadata `msg:"metadata" json:"metadata"` } -// JoinAppReq are used to notify application of an accepted OTAA +// OTAAAppReq are used to notify application of an accepted OTAA type OTAAAppReq struct { Metadata []AppMetadata `msg:"metadata" json:"metadata"` } // AppMetadata represents gathered metadata that are sent to gateways type AppMetadata struct { - Frequency float32 `msg:"freq" json:"freq"` - DataRate string `msg:"datr" json:"datr"` - CodingRate string `msg:"codr" json:"codr"` - Timestamp uint32 `msg:"tmst" json:"tmst"` + Frequency float32 `msg:"frequency" json:"frequency"` + DataRate string `msg:"datarate" json:"datarate"` + CodingRate string `msg:"coderate" json:"coderate"` + Timestamp uint32 `msg:"timestamp" json:"timestamp"` Time string `msg:"time" json:"time"` Rssi int32 `msg:"rssi" json:"rssi"` Lsnr float32 `msg:"lsnr" json:"lsnr"` - RFChain uint32 `msg:"rfch" json:"rfch"` - CRCStatus int32 `msg:"stat" json:"stat"` - Modulation string `msg:"modu" json:"modu"` - Altitude int32 `msg:"alti" json:"alti"` - Longitude float32 `msg:"long" json:"long"` - Latitude float32 `msg:"lati" json:"lati"` + RFChain uint32 `msg:"rfchain" json:"rfchain"` + CRCStatus int32 `msg:"crc" json:"crc"` + Modulation string `msg:"modulation" json:"modulation"` + Altitude int32 `msg:"altitude" json:"altitude"` + Longitude float32 `msg:"longitude" json:"longitude"` + Latitude float32 `msg:"latitude" json:"latitude"` } // DataDownAppReq represents downlink messages sent by applications @@ -41,9 +41,9 @@ type DataDownAppReq struct { // ABPSubAppReq defines the shape of the request made by an application to the handler type ABPSubAppReq struct { - DevAddr string `msg:"dev_addr" json:"dev_addr"` - NwkSKey string `msg:"nwks_key" json:"nwks_key"` - AppSKey string `msg:"apps_key" json:"apps_key"` + DevAddr string `msg:"devaddr" json:"devaddr"` + NwkSKey string `msg:"nwkskey" json:"nwkskey"` + AppSKey string `msg:"appskey" json:"appskey"` } // AuthBrokerClient gathers both BrokerClient & BrokerManagerClient interfaces diff --git a/core/definitions_gen.go b/core/definitions_gen.go index 654ab2c38..ee57d0f7f 100644 --- a/core/definitions_gen.go +++ b/core/definitions_gen.go @@ -12,14 +12,14 @@ import ( func (z ABPSubAppReq) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) // map header, size 3 - // string "dev_addr" - o = append(o, 0x83, 0xa8, 0x64, 0x65, 0x76, 0x5f, 0x61, 0x64, 0x64, 0x72) + // string "devaddr" + o = append(o, 0x83, 0xa7, 0x64, 0x65, 0x76, 0x61, 0x64, 0x64, 0x72) o = msgp.AppendString(o, z.DevAddr) - // string "nwks_key" - o = append(o, 0xa8, 0x6e, 0x77, 0x6b, 0x73, 0x5f, 0x6b, 0x65, 0x79) + // string "nwkskey" + o = append(o, 0xa7, 0x6e, 0x77, 0x6b, 0x73, 0x6b, 0x65, 0x79) o = msgp.AppendString(o, z.NwkSKey) - // string "apps_key" - o = append(o, 0xa8, 0x61, 0x70, 0x70, 0x73, 0x5f, 0x6b, 0x65, 0x79) + // string "appskey" + o = append(o, 0xa7, 0x61, 0x70, 0x70, 0x73, 0x6b, 0x65, 0x79) o = msgp.AppendString(o, z.AppSKey) return } @@ -40,17 +40,17 @@ func (z *ABPSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { return } switch msgp.UnsafeString(field) { - case "dev_addr": + case "devaddr": z.DevAddr, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "nwks_key": + case "nwkskey": z.NwkSKey, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "apps_key": + case "appskey": z.AppSKey, bts, err = msgp.ReadStringBytes(bts) if err != nil { return @@ -67,7 +67,7 @@ func (z *ABPSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { } func (z ABPSubAppReq) Msgsize() (s int) { - s = 1 + 9 + msgp.StringPrefixSize + len(z.DevAddr) + 9 + msgp.StringPrefixSize + len(z.NwkSKey) + 9 + msgp.StringPrefixSize + len(z.AppSKey) + s = 1 + 8 + msgp.StringPrefixSize + len(z.DevAddr) + 8 + msgp.StringPrefixSize + len(z.NwkSKey) + 8 + msgp.StringPrefixSize + len(z.AppSKey) return } @@ -75,17 +75,17 @@ func (z ABPSubAppReq) Msgsize() (s int) { func (z *AppMetadata) MarshalMsg(b []byte) (o []byte, err error) { o = msgp.Require(b, z.Msgsize()) // map header, size 13 - // string "freq" - o = append(o, 0x8d, 0xa4, 0x66, 0x72, 0x65, 0x71) + // string "frequency" + o = append(o, 0x8d, 0xa9, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79) o = msgp.AppendFloat32(o, z.Frequency) - // string "datr" - o = append(o, 0xa4, 0x64, 0x61, 0x74, 0x72) + // string "datarate" + o = append(o, 0xa8, 0x64, 0x61, 0x74, 0x61, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendString(o, z.DataRate) - // string "codr" - o = append(o, 0xa4, 0x63, 0x6f, 0x64, 0x72) + // string "coderate" + o = append(o, 0xa8, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65) o = msgp.AppendString(o, z.CodingRate) - // string "tmst" - o = append(o, 0xa4, 0x74, 0x6d, 0x73, 0x74) + // string "timestamp" + o = append(o, 0xa9, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70) o = msgp.AppendUint32(o, z.Timestamp) // string "time" o = append(o, 0xa4, 0x74, 0x69, 0x6d, 0x65) @@ -96,23 +96,23 @@ func (z *AppMetadata) MarshalMsg(b []byte) (o []byte, err error) { // string "lsnr" o = append(o, 0xa4, 0x6c, 0x73, 0x6e, 0x72) o = msgp.AppendFloat32(o, z.Lsnr) - // string "rfch" - o = append(o, 0xa4, 0x72, 0x66, 0x63, 0x68) + // string "rfchain" + o = append(o, 0xa7, 0x72, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e) o = msgp.AppendUint32(o, z.RFChain) - // string "stat" - o = append(o, 0xa4, 0x73, 0x74, 0x61, 0x74) + // string "crc" + o = append(o, 0xa3, 0x63, 0x72, 0x63) o = msgp.AppendInt32(o, z.CRCStatus) - // string "modu" - o = append(o, 0xa4, 0x6d, 0x6f, 0x64, 0x75) + // string "modulation" + o = append(o, 0xaa, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e) o = msgp.AppendString(o, z.Modulation) - // string "alti" - o = append(o, 0xa4, 0x61, 0x6c, 0x74, 0x69) + // string "altitude" + o = append(o, 0xa8, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) o = msgp.AppendInt32(o, z.Altitude) - // string "long" - o = append(o, 0xa4, 0x6c, 0x6f, 0x6e, 0x67) + // string "longitude" + o = append(o, 0xa9, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65) o = msgp.AppendFloat32(o, z.Longitude) - // string "lati" - o = append(o, 0xa4, 0x6c, 0x61, 0x74, 0x69) + // string "latitude" + o = append(o, 0xa8, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) o = msgp.AppendFloat32(o, z.Latitude) return } @@ -133,22 +133,22 @@ func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { return } switch msgp.UnsafeString(field) { - case "freq": + case "frequency": z.Frequency, bts, err = msgp.ReadFloat32Bytes(bts) if err != nil { return } - case "datr": + case "datarate": z.DataRate, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "codr": + case "coderate": z.CodingRate, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "tmst": + case "timestamp": z.Timestamp, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { return @@ -168,32 +168,32 @@ func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { if err != nil { return } - case "rfch": + case "rfchain": z.RFChain, bts, err = msgp.ReadUint32Bytes(bts) if err != nil { return } - case "stat": + case "crc": z.CRCStatus, bts, err = msgp.ReadInt32Bytes(bts) if err != nil { return } - case "modu": + case "modulation": z.Modulation, bts, err = msgp.ReadStringBytes(bts) if err != nil { return } - case "alti": + case "altitude": z.Altitude, bts, err = msgp.ReadInt32Bytes(bts) if err != nil { return } - case "long": + case "longitude": z.Longitude, bts, err = msgp.ReadFloat32Bytes(bts) if err != nil { return } - case "lati": + case "latitude": z.Latitude, bts, err = msgp.ReadFloat32Bytes(bts) if err != nil { return @@ -210,7 +210,7 @@ func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { } func (z *AppMetadata) Msgsize() (s int) { - s = 1 + 5 + msgp.Float32Size + 5 + msgp.StringPrefixSize + len(z.DataRate) + 5 + msgp.StringPrefixSize + len(z.CodingRate) + 5 + msgp.Uint32Size + 5 + msgp.StringPrefixSize + len(z.Time) + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 5 + msgp.Uint32Size + 5 + msgp.Int32Size + 5 + msgp.StringPrefixSize + len(z.Modulation) + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 5 + msgp.Float32Size + s = 1 + 10 + msgp.Float32Size + 9 + msgp.StringPrefixSize + len(z.DataRate) + 9 + msgp.StringPrefixSize + len(z.CodingRate) + 10 + msgp.Uint32Size + 5 + msgp.StringPrefixSize + len(z.Time) + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 8 + msgp.Uint32Size + 4 + msgp.Int32Size + 11 + msgp.StringPrefixSize + len(z.Modulation) + 9 + msgp.Int32Size + 10 + msgp.Float32Size + 9 + msgp.Float32Size return } From 01426800de7b4fda4c9f2cfe71f172d8977b3e6d Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 14:58:50 -0700 Subject: [PATCH 1291/2266] Renamed coderate to codingrate --- core/definitions.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/definitions.go b/core/definitions.go index 99b966466..83f24800e 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -20,7 +20,7 @@ type OTAAAppReq struct { type AppMetadata struct { Frequency float32 `msg:"frequency" json:"frequency"` DataRate string `msg:"datarate" json:"datarate"` - CodingRate string `msg:"coderate" json:"coderate"` + CodingRate string `msg:"codingrate" json:"codingrate"` Timestamp uint32 `msg:"timestamp" json:"timestamp"` Time string `msg:"time" json:"time"` Rssi int32 `msg:"rssi" json:"rssi"` From 1c1e4080ba4f2defa715cc2aa8bb0d1035d4d196 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 18:17:54 -0700 Subject: [PATCH 1292/2266] Subscribe with application credentials to MQTT broker --- ttnctl/cmd/applications.go | 30 +----------------------- ttnctl/cmd/downlink.go | 20 +++++++++++++++- ttnctl/cmd/subscribe.go | 19 ++++++++++++++- ttnctl/util/applications.go | 46 +++++++++++++++++++++++++++++++++++++ ttnctl/util/mqtt.go | 24 +++++-------------- 5 files changed, 90 insertions(+), 49 deletions(-) create mode 100644 ttnctl/util/applications.go diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index b4089b7ac..3877a9321 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -4,7 +4,6 @@ package cmd import ( - "encoding/json" "fmt" "net/http" "net/url" @@ -17,43 +16,16 @@ import ( "github.com/spf13/viper" ) -type app struct { - EUI string `json:"eui"` - Name string `json:"name"` - Owner string `json:"owner"` - AccessKeys []string `json:"accessKeys"` - Valid bool `json:"valid"` -} - // applicationsCmd represents the applications command var applicationsCmd = &cobra.Command{ Use: "applications", Short: "Show applications", Long: `ttnctl applications retrieves the applications of the logged on user.`, Run: func(cmd *cobra.Command, args []string) { - server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications", server) - req, err := util.NewRequestWithAuth(server, "GET", uri, nil) - if err != nil { - ctx.WithError(err).Fatal("Failed to create authenticated request") - } - - client := &http.Client{} - resp, err := client.Do(req) + apps, err := util.GetApplications(ctx) if err != nil { ctx.WithError(err).Fatal("Failed to get applications") } - if resp.StatusCode != http.StatusOK { - ctx.Fatalf("Failed to get applications: %s", resp.Status) - } - - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - var apps []*app - err = decoder.Decode(&apps) - if err != nil { - ctx.WithError(err).Fatal("Failed to read applications") - } ctx.Infof("Found %d application(s)", len(apps)) table := uitable.New() diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 59572a9be..74ffed2d9 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -4,6 +4,8 @@ package cmd import ( + "fmt" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" @@ -28,6 +30,22 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.Fatalf("Invalid AppEUI: %s", err) } + apps, err := util.GetApplications(ctx) + if err != nil { + ctx.WithError(err).Fatal("Failed to get applications") + } + + var appAccessKey string + for _, a := range apps { + if a.EUI == fmt.Sprintf("%X", appEUI) { + // Don't care about which access key in this cli + appAccessKey = a.AccessKeys[0] + } + } + if appAccessKey == "" { + ctx.Fatal("Application not found") + } + devEUI, err := util.Parse64(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) @@ -42,7 +60,7 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.WithError(err).Fatal("Unable to create downlink payload") } - client := util.GetMQTTClient(ctx) + client := util.ConnectMQTTClient(ctx, appEUI, appAccessKey) token := client.PublishDownlink(appEUI, devEUI, dataDown) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 27fbe8a1e..4ca4c8c0d 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -4,6 +4,7 @@ package cmd import ( + "fmt" "os" "os/signal" "regexp" @@ -29,6 +30,22 @@ application.`, ctx.Fatalf("Invalid AppEUI: %s", err) } + apps, err := util.GetApplications(ctx) + if err != nil { + ctx.WithError(err).Fatal("Failed to get applications") + } + + var appAccessKey string + for _, a := range apps { + if a.EUI == fmt.Sprintf("%X", appEUI) { + // Don't care about which access key in this cli + appAccessKey = a.AccessKeys[0] + } + } + if appAccessKey == "" { + ctx.Fatal("Application not found") + } + var devEUI []byte if len(args) > 0 { devEUI, err = util.Parse64(args[0]) @@ -40,7 +57,7 @@ application.`, ctx.Infof("Subscribing to uplink messages from all devices in application %x", appEUI) } - client := util.GetMQTTClient(ctx) + client := util.ConnectMQTTClient(ctx, appEUI, appAccessKey) token := client.SubscribeDeviceUplink(appEUI, devEUI, func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { ctx := ctx.WithField("DevEUI", devEUI) diff --git a/ttnctl/util/applications.go b/ttnctl/util/applications.go new file mode 100644 index 000000000..20b35604b --- /dev/null +++ b/ttnctl/util/applications.go @@ -0,0 +1,46 @@ +package util + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/apex/log" + "github.com/spf13/viper" +) + +type App struct { + EUI string `json:"eui"` // TODO: Change to []string + Name string `json:"name"` + Owner string `json:"owner"` + AccessKeys []string `json:"accessKeys"` + Valid bool `json:"valid"` +} + +func GetApplications(ctx log.Interface) ([]*App, error) { + server := viper.GetString("ttn-account-server") + uri := fmt.Sprintf("%s/applications", server) + req, err := NewRequestWithAuth(server, "GET", uri, nil) + if err != nil { + ctx.WithError(err).Fatal("Failed to create authenticated request") + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + ctx.WithError(err).Fatal("Failed to get applications") + } + if resp.StatusCode != http.StatusOK { + ctx.Fatalf("Failed to get applications: %s", resp.Status) + } + + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + var apps []*App + err = decoder.Decode(&apps) + if err != nil { + ctx.WithError(err).Fatal("Failed to read applications") + } + + return apps, nil +} diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 62b5bb690..a961bb1a2 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -8,30 +8,18 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" - "github.com/howeyc/gopass" "github.com/spf13/viper" ) -func GetMQTTClient(ctx log.Interface) mqtt.Client { - user, err := LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication token") - } - if user == nil { - ctx.Fatal("No login found. Please login with ttnctl user login [e-mail]") - } - - // NOTE: until the MQTT server supports access tokens, we'll have to ask for a password. - fmt.Printf("Password for account %s: ", user.Email) - password, err := gopass.GetPasswd() - if err != nil { - ctx.Fatal(err.Error()) - } +// ConnectMQTTClient connects a new MQTT clients with the specified credentials +func ConnectMQTTClient(ctx log.Interface, appEui []byte, accessKey string) mqtt.Client { + username := fmt.Sprintf("%X", appEui) + password := accessKey broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) - client := mqtt.NewClient(ctx, "ttnctl", user.Email, string(password), broker) + client := mqtt.NewClient(ctx, "ttnctl", username, password, broker) - err = client.Connect() + err := client.Connect() if err != nil { ctx.WithError(err).Fatal("Could not connect") } From 3f6840a77e5f48bff6e3969fbeef89148d90c453 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 18:34:27 -0700 Subject: [PATCH 1293/2266] Moved duplicate code to ConnectMQTTClient --- ttnctl/cmd/downlink.go | 20 +------------------- ttnctl/cmd/subscribe.go | 21 ++------------------- ttnctl/util/mqtt.go | 29 +++++++++++++++++++++++------ 3 files changed, 26 insertions(+), 44 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 74ffed2d9..2e73e1b18 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -4,8 +4,6 @@ package cmd import ( - "fmt" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" @@ -30,22 +28,6 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.Fatalf("Invalid AppEUI: %s", err) } - apps, err := util.GetApplications(ctx) - if err != nil { - ctx.WithError(err).Fatal("Failed to get applications") - } - - var appAccessKey string - for _, a := range apps { - if a.EUI == fmt.Sprintf("%X", appEUI) { - // Don't care about which access key in this cli - appAccessKey = a.AccessKeys[0] - } - } - if appAccessKey == "" { - ctx.Fatal("Application not found") - } - devEUI, err := util.Parse64(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) @@ -60,7 +42,7 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.WithError(err).Fatal("Unable to create downlink payload") } - client := util.ConnectMQTTClient(ctx, appEUI, appAccessKey) + client := util.ConnectMQTTClient(ctx) token := client.PublishDownlink(appEUI, devEUI, dataDown) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 4ca4c8c0d..527370f64 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -4,7 +4,6 @@ package cmd import ( - "fmt" "os" "os/signal" "regexp" @@ -25,27 +24,12 @@ The optional DevEUI argument can be used to only receive messages from a specific device. By default you will receive messages from all devices of your application.`, Run: func(cmd *cobra.Command, args []string) { + appEUI, err := util.Parse64(viper.GetString("app-eui")) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } - apps, err := util.GetApplications(ctx) - if err != nil { - ctx.WithError(err).Fatal("Failed to get applications") - } - - var appAccessKey string - for _, a := range apps { - if a.EUI == fmt.Sprintf("%X", appEUI) { - // Don't care about which access key in this cli - appAccessKey = a.AccessKeys[0] - } - } - if appAccessKey == "" { - ctx.Fatal("Application not found") - } - var devEUI []byte if len(args) > 0 { devEUI, err = util.Parse64(args[0]) @@ -57,7 +41,7 @@ application.`, ctx.Infof("Subscribing to uplink messages from all devices in application %x", appEUI) } - client := util.ConnectMQTTClient(ctx, appEUI, appAccessKey) + client := util.ConnectMQTTClient(ctx) token := client.SubscribeDeviceUplink(appEUI, devEUI, func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { ctx := ctx.WithField("DevEUI", devEUI) @@ -77,7 +61,6 @@ application.`, } // TODO: Add warnings for airtime / duty-cycle / fair-use - }) if token.Wait(); token.Error() != nil { diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index a961bb1a2..baa0edf57 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -12,15 +12,32 @@ import ( ) // ConnectMQTTClient connects a new MQTT clients with the specified credentials -func ConnectMQTTClient(ctx log.Interface, appEui []byte, accessKey string) mqtt.Client { - username := fmt.Sprintf("%X", appEui) - password := accessKey +func ConnectMQTTClient(ctx log.Interface) mqtt.Client { + appEUI, err := Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + apps, err := GetApplications(ctx) + if err != nil { + ctx.WithError(err).Fatal("Failed to get applications") + } + + var app *App + for _, a := range apps { + if a.EUI == fmt.Sprintf("%X", appEUI) { + app = a + } + } + if app == nil { + ctx.Fatal("Application not found") + } broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) - client := mqtt.NewClient(ctx, "ttnctl", username, password, broker) + // Don't care about which access key here + client := mqtt.NewClient(ctx, "ttnctl", app.EUI, app.AccessKeys[0], broker) - err := client.Connect() - if err != nil { + if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } From e2ac9b39328fed16525d6e6060901a3d6176e857 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 4 Apr 2016 18:35:01 -0700 Subject: [PATCH 1294/2266] Loosened payload size warning and added note about binary warning --- ttnctl/cmd/subscribe.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 527370f64..340f5f8b6 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -48,6 +48,7 @@ application.`, // TODO: Find out what Metadata people want to see here + // NOTE: This is a race condition; binary values may be printable unprintable, _ := regexp.Compile(`[^[:print:]]`) if unprintable.Match(dataUp.Payload) { ctx.Infof("%X", dataUp.Payload) @@ -56,8 +57,8 @@ application.`, ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") } - if l := len(dataUp.Payload); l > 12 { - ctx.Warnf("Your payload has a size of %d bytes. We recommend to send no more than 12 bytes.", l) + if l := len(dataUp.Payload); l > 20 { + ctx.Warnf("Your payload has a size of %d bytes. We recommend to send no more than 20 bytes.", l) } // TODO: Add warnings for airtime / duty-cycle / fair-use From 83721b7b1cbd3b43aa010856bccb3419f13e86c1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 5 Apr 2016 09:32:26 +0200 Subject: [PATCH 1295/2266] Migrate MQTT client to JSON --- mqtt/client.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 2971aaddc..ec0443dca 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -4,6 +4,7 @@ package mqtt import ( + "encoding/json" "fmt" "time" @@ -131,7 +132,7 @@ func (c *defaultClient) IsConnected() bool { func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) Token { topic := DeviceTopic{appEUI, devEUI, Uplink} - msg, err := dataUp.MarshalMsg(nil) + msg, err := json.Marshal(dataUp) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} } @@ -152,7 +153,8 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, hand // Unmarshal the payload dataUp := &core.DataUpAppReq{} - _, err = dataUp.UnmarshalMsg(msg.Payload()) + err = json.Unmarshal(msg.Payload(), dataUp) + if err != nil { if c.ctx != nil { c.ctx.WithError(err).Warn("Could not unmarshal uplink") @@ -175,7 +177,7 @@ func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown core.DataDownAppReq) Token { topic := DeviceTopic{appEUI, devEUI, Downlink} - msg, err := dataDown.MarshalMsg(nil) + msg, err := json.Marshal(dataDown) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} } @@ -196,7 +198,7 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, ha // Unmarshal the payload dataDown := &core.DataDownAppReq{} - _, err = dataDown.UnmarshalMsg(msg.Payload()) + err = json.Unmarshal(msg.Payload(), dataDown) if err != nil { if c.ctx != nil { c.ctx.WithError(err).Warn("Could not unmarshal Downlink") @@ -219,7 +221,7 @@ func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activation core.OTAAAppReq) Token { topic := DeviceTopic{appEUI, devEUI, Activations} - msg, err := activation.MarshalMsg(nil) + msg, err := json.Marshal(activation) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} } @@ -240,7 +242,7 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, // Unmarshal the payload activation := &core.OTAAAppReq{} - _, err = activation.UnmarshalMsg(msg.Payload()) + err = json.Unmarshal(msg.Payload(), activation) if err != nil { if c.ctx != nil { c.ctx.WithError(err).Warn("Could not unmarshal Activation") From b9846e0a9bbcc85587b41b0616b9ff57abd0db39 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 5 Apr 2016 12:52:44 +0200 Subject: [PATCH 1296/2266] Point ttnctl to staging --- ttnctl/cmd/root.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 13d6271be..1e8249a85 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -49,13 +49,13 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") - RootCmd.PersistentFlags().String("ttn-router", "0.0.0.0:1700", "The net address of the TTN Router") + RootCmd.PersistentFlags().String("ttn-router", "staging.thethingsnetwork.org:1700", "The net address of the TTN Router") viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router")) - RootCmd.PersistentFlags().String("ttn-handler", "0.0.0.0:1782", "The net address of the TTN Handler") + RootCmd.PersistentFlags().String("ttn-handler", "staging.thethingsnetwork.org:1782", "The net address of the TTN Handler") viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) - RootCmd.PersistentFlags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + RootCmd.PersistentFlags().String("mqtt-broker", "staging.thethingsnetwork.org:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") From 415c0c6112448dd0df4f2d35e7dae6e92fdb0e30 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 5 Apr 2016 22:25:22 +0200 Subject: [PATCH 1297/2266] [hotfix] Fix panic when join MIC fails `err` can be nil --- core/components/handler/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index df900818d..6868dfc7f 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -191,7 +191,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* copy(joinPayload.DevNonce[:], req.DevNonce) payload.MACPayload = &joinPayload if ok, err := payload.ValidateMIC(lorawan.AES128Key(*entry.AppKey)); err != nil || !ok { - ctx.WithError(err).Debug("Invalid join-request MIC") + ctx.Debug("Invalid join-request MIC") return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to validate MIC") } From c318239b7be53ef04e11063a79268ee72142fdf4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 09:14:56 +0200 Subject: [PATCH 1298/2266] Remove msgpack support We recently migrated the MQTT messages back to JSON, so we no longer need msgpack support. --- core/definitions.go | 44 ++-- core/definitions_gen.go | 414 ----------------------------------- core/definitions_gen_test.go | 301 ------------------------- 3 files changed, 21 insertions(+), 738 deletions(-) delete mode 100644 core/definitions_gen.go delete mode 100644 core/definitions_gen_test.go diff --git a/core/definitions.go b/core/definitions.go index 83f24800e..ed2ec773b 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -1,49 +1,47 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -//go:generate msgp -io=false - package core // DataUpAppReq represents the actual payloads sent to application on uplink type DataUpAppReq struct { - Payload []byte `msg:"payload" json:"payload"` - Metadata []AppMetadata `msg:"metadata" json:"metadata"` + Payload []byte `json:"payload"` + Metadata []AppMetadata `json:"metadata"` } // OTAAAppReq are used to notify application of an accepted OTAA type OTAAAppReq struct { - Metadata []AppMetadata `msg:"metadata" json:"metadata"` + Metadata []AppMetadata `json:"metadata"` } // AppMetadata represents gathered metadata that are sent to gateways type AppMetadata struct { - Frequency float32 `msg:"frequency" json:"frequency"` - DataRate string `msg:"datarate" json:"datarate"` - CodingRate string `msg:"codingrate" json:"codingrate"` - Timestamp uint32 `msg:"timestamp" json:"timestamp"` - Time string `msg:"time" json:"time"` - Rssi int32 `msg:"rssi" json:"rssi"` - Lsnr float32 `msg:"lsnr" json:"lsnr"` - RFChain uint32 `msg:"rfchain" json:"rfchain"` - CRCStatus int32 `msg:"crc" json:"crc"` - Modulation string `msg:"modulation" json:"modulation"` - Altitude int32 `msg:"altitude" json:"altitude"` - Longitude float32 `msg:"longitude" json:"longitude"` - Latitude float32 `msg:"latitude" json:"latitude"` + Frequency float32 `json:"frequency"` + DataRate string `json:"datarate"` + CodingRate string `json:"codingrate"` + Timestamp uint32 `json:"timestamp"` + Time string `json:"time"` + Rssi int32 `json:"rssi"` + Lsnr float32 `json:"lsnr"` + RFChain uint32 `json:"rfchain"` + CRCStatus int32 `json:"crc"` + Modulation string `json:"modulation"` + Altitude int32 `json:"altitude"` + Longitude float32 `json:"longitude"` + Latitude float32 `json:"latitude"` } // DataDownAppReq represents downlink messages sent by applications type DataDownAppReq struct { - Payload []byte `msg:"payload" json:"payload"` - TTL string `msg:"ttl" json:"ttl"` + Payload []byte `json:"payload"` + TTL string `json:"ttl"` } // ABPSubAppReq defines the shape of the request made by an application to the handler type ABPSubAppReq struct { - DevAddr string `msg:"devaddr" json:"devaddr"` - NwkSKey string `msg:"nwkskey" json:"nwkskey"` - AppSKey string `msg:"appskey" json:"appskey"` + DevAddr string `json:"devaddr"` + NwkSKey string `json:"nwkskey"` + AppSKey string `json:"appskey"` } // AuthBrokerClient gathers both BrokerClient & BrokerManagerClient interfaces diff --git a/core/definitions_gen.go b/core/definitions_gen.go deleted file mode 100644 index ee57d0f7f..000000000 --- a/core/definitions_gen.go +++ /dev/null @@ -1,414 +0,0 @@ -package core - -// NOTE: THIS FILE WAS PRODUCED BY THE -// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp) -// DO NOT EDIT - -import ( - "github.com/tinylib/msgp/msgp" -) - -// MarshalMsg implements msgp.Marshaler -func (z ABPSubAppReq) MarshalMsg(b []byte) (o []byte, err error) { - o = msgp.Require(b, z.Msgsize()) - // map header, size 3 - // string "devaddr" - o = append(o, 0x83, 0xa7, 0x64, 0x65, 0x76, 0x61, 0x64, 0x64, 0x72) - o = msgp.AppendString(o, z.DevAddr) - // string "nwkskey" - o = append(o, 0xa7, 0x6e, 0x77, 0x6b, 0x73, 0x6b, 0x65, 0x79) - o = msgp.AppendString(o, z.NwkSKey) - // string "appskey" - o = append(o, 0xa7, 0x61, 0x70, 0x70, 0x73, 0x6b, 0x65, 0x79) - o = msgp.AppendString(o, z.AppSKey) - return -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *ABPSubAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var isz uint32 - isz, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - return - } - for isz > 0 { - isz-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - return - } - switch msgp.UnsafeString(field) { - case "devaddr": - z.DevAddr, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - case "nwkskey": - z.NwkSKey, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - case "appskey": - z.AppSKey, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - default: - bts, err = msgp.Skip(bts) - if err != nil { - return - } - } - } - o = bts - return -} - -func (z ABPSubAppReq) Msgsize() (s int) { - s = 1 + 8 + msgp.StringPrefixSize + len(z.DevAddr) + 8 + msgp.StringPrefixSize + len(z.NwkSKey) + 8 + msgp.StringPrefixSize + len(z.AppSKey) - return -} - -// MarshalMsg implements msgp.Marshaler -func (z *AppMetadata) MarshalMsg(b []byte) (o []byte, err error) { - o = msgp.Require(b, z.Msgsize()) - // map header, size 13 - // string "frequency" - o = append(o, 0x8d, 0xa9, 0x66, 0x72, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x63, 0x79) - o = msgp.AppendFloat32(o, z.Frequency) - // string "datarate" - o = append(o, 0xa8, 0x64, 0x61, 0x74, 0x61, 0x72, 0x61, 0x74, 0x65) - o = msgp.AppendString(o, z.DataRate) - // string "coderate" - o = append(o, 0xa8, 0x63, 0x6f, 0x64, 0x65, 0x72, 0x61, 0x74, 0x65) - o = msgp.AppendString(o, z.CodingRate) - // string "timestamp" - o = append(o, 0xa9, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70) - o = msgp.AppendUint32(o, z.Timestamp) - // string "time" - o = append(o, 0xa4, 0x74, 0x69, 0x6d, 0x65) - o = msgp.AppendString(o, z.Time) - // string "rssi" - o = append(o, 0xa4, 0x72, 0x73, 0x73, 0x69) - o = msgp.AppendInt32(o, z.Rssi) - // string "lsnr" - o = append(o, 0xa4, 0x6c, 0x73, 0x6e, 0x72) - o = msgp.AppendFloat32(o, z.Lsnr) - // string "rfchain" - o = append(o, 0xa7, 0x72, 0x66, 0x63, 0x68, 0x61, 0x69, 0x6e) - o = msgp.AppendUint32(o, z.RFChain) - // string "crc" - o = append(o, 0xa3, 0x63, 0x72, 0x63) - o = msgp.AppendInt32(o, z.CRCStatus) - // string "modulation" - o = append(o, 0xaa, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e) - o = msgp.AppendString(o, z.Modulation) - // string "altitude" - o = append(o, 0xa8, 0x61, 0x6c, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) - o = msgp.AppendInt32(o, z.Altitude) - // string "longitude" - o = append(o, 0xa9, 0x6c, 0x6f, 0x6e, 0x67, 0x69, 0x74, 0x75, 0x64, 0x65) - o = msgp.AppendFloat32(o, z.Longitude) - // string "latitude" - o = append(o, 0xa8, 0x6c, 0x61, 0x74, 0x69, 0x74, 0x75, 0x64, 0x65) - o = msgp.AppendFloat32(o, z.Latitude) - return -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *AppMetadata) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var isz uint32 - isz, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - return - } - for isz > 0 { - isz-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - return - } - switch msgp.UnsafeString(field) { - case "frequency": - z.Frequency, bts, err = msgp.ReadFloat32Bytes(bts) - if err != nil { - return - } - case "datarate": - z.DataRate, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - case "coderate": - z.CodingRate, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - case "timestamp": - z.Timestamp, bts, err = msgp.ReadUint32Bytes(bts) - if err != nil { - return - } - case "time": - z.Time, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - case "rssi": - z.Rssi, bts, err = msgp.ReadInt32Bytes(bts) - if err != nil { - return - } - case "lsnr": - z.Lsnr, bts, err = msgp.ReadFloat32Bytes(bts) - if err != nil { - return - } - case "rfchain": - z.RFChain, bts, err = msgp.ReadUint32Bytes(bts) - if err != nil { - return - } - case "crc": - z.CRCStatus, bts, err = msgp.ReadInt32Bytes(bts) - if err != nil { - return - } - case "modulation": - z.Modulation, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - case "altitude": - z.Altitude, bts, err = msgp.ReadInt32Bytes(bts) - if err != nil { - return - } - case "longitude": - z.Longitude, bts, err = msgp.ReadFloat32Bytes(bts) - if err != nil { - return - } - case "latitude": - z.Latitude, bts, err = msgp.ReadFloat32Bytes(bts) - if err != nil { - return - } - default: - bts, err = msgp.Skip(bts) - if err != nil { - return - } - } - } - o = bts - return -} - -func (z *AppMetadata) Msgsize() (s int) { - s = 1 + 10 + msgp.Float32Size + 9 + msgp.StringPrefixSize + len(z.DataRate) + 9 + msgp.StringPrefixSize + len(z.CodingRate) + 10 + msgp.Uint32Size + 5 + msgp.StringPrefixSize + len(z.Time) + 5 + msgp.Int32Size + 5 + msgp.Float32Size + 8 + msgp.Uint32Size + 4 + msgp.Int32Size + 11 + msgp.StringPrefixSize + len(z.Modulation) + 9 + msgp.Int32Size + 10 + msgp.Float32Size + 9 + msgp.Float32Size - return -} - -// MarshalMsg implements msgp.Marshaler -func (z *DataDownAppReq) MarshalMsg(b []byte) (o []byte, err error) { - o = msgp.Require(b, z.Msgsize()) - // map header, size 2 - // string "payload" - o = append(o, 0x82, 0xa7, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64) - o = msgp.AppendBytes(o, z.Payload) - // string "ttl" - o = append(o, 0xa3, 0x74, 0x74, 0x6c) - o = msgp.AppendString(o, z.TTL) - return -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *DataDownAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var isz uint32 - isz, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - return - } - for isz > 0 { - isz-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - return - } - switch msgp.UnsafeString(field) { - case "payload": - z.Payload, bts, err = msgp.ReadBytesBytes(bts, z.Payload) - if err != nil { - return - } - case "ttl": - z.TTL, bts, err = msgp.ReadStringBytes(bts) - if err != nil { - return - } - default: - bts, err = msgp.Skip(bts) - if err != nil { - return - } - } - } - o = bts - return -} - -func (z *DataDownAppReq) Msgsize() (s int) { - s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + 4 + msgp.StringPrefixSize + len(z.TTL) - return -} - -// MarshalMsg implements msgp.Marshaler -func (z *DataUpAppReq) MarshalMsg(b []byte) (o []byte, err error) { - o = msgp.Require(b, z.Msgsize()) - // map header, size 2 - // string "payload" - o = append(o, 0x82, 0xa7, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64) - o = msgp.AppendBytes(o, z.Payload) - // string "metadata" - o = append(o, 0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) - o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) - for xvk := range z.Metadata { - o, err = z.Metadata[xvk].MarshalMsg(o) - if err != nil { - return - } - } - return -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *DataUpAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var isz uint32 - isz, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - return - } - for isz > 0 { - isz-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - return - } - switch msgp.UnsafeString(field) { - case "payload": - z.Payload, bts, err = msgp.ReadBytesBytes(bts, z.Payload) - if err != nil { - return - } - case "metadata": - var xsz uint32 - xsz, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - return - } - if cap(z.Metadata) >= int(xsz) { - z.Metadata = z.Metadata[:xsz] - } else { - z.Metadata = make([]AppMetadata, xsz) - } - for xvk := range z.Metadata { - bts, err = z.Metadata[xvk].UnmarshalMsg(bts) - if err != nil { - return - } - } - default: - bts, err = msgp.Skip(bts) - if err != nil { - return - } - } - } - o = bts - return -} - -func (z *DataUpAppReq) Msgsize() (s int) { - s = 1 + 8 + msgp.BytesPrefixSize + len(z.Payload) + 9 + msgp.ArrayHeaderSize - for xvk := range z.Metadata { - s += z.Metadata[xvk].Msgsize() - } - return -} - -// MarshalMsg implements msgp.Marshaler -func (z *OTAAAppReq) MarshalMsg(b []byte) (o []byte, err error) { - o = msgp.Require(b, z.Msgsize()) - // map header, size 1 - // string "metadata" - o = append(o, 0x81, 0xa8, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61) - o = msgp.AppendArrayHeader(o, uint32(len(z.Metadata))) - for bzg := range z.Metadata { - o, err = z.Metadata[bzg].MarshalMsg(o) - if err != nil { - return - } - } - return -} - -// UnmarshalMsg implements msgp.Unmarshaler -func (z *OTAAAppReq) UnmarshalMsg(bts []byte) (o []byte, err error) { - var field []byte - _ = field - var isz uint32 - isz, bts, err = msgp.ReadMapHeaderBytes(bts) - if err != nil { - return - } - for isz > 0 { - isz-- - field, bts, err = msgp.ReadMapKeyZC(bts) - if err != nil { - return - } - switch msgp.UnsafeString(field) { - case "metadata": - var xsz uint32 - xsz, bts, err = msgp.ReadArrayHeaderBytes(bts) - if err != nil { - return - } - if cap(z.Metadata) >= int(xsz) { - z.Metadata = z.Metadata[:xsz] - } else { - z.Metadata = make([]AppMetadata, xsz) - } - for bzg := range z.Metadata { - bts, err = z.Metadata[bzg].UnmarshalMsg(bts) - if err != nil { - return - } - } - default: - bts, err = msgp.Skip(bts) - if err != nil { - return - } - } - } - o = bts - return -} - -func (z *OTAAAppReq) Msgsize() (s int) { - s = 1 + 9 + msgp.ArrayHeaderSize - for bzg := range z.Metadata { - s += z.Metadata[bzg].Msgsize() - } - return -} diff --git a/core/definitions_gen_test.go b/core/definitions_gen_test.go deleted file mode 100644 index 5040f1237..000000000 --- a/core/definitions_gen_test.go +++ /dev/null @@ -1,301 +0,0 @@ -package core - -// NOTE: THIS FILE WAS PRODUCED BY THE -// MSGP CODE GENERATION TOOL (github.com/tinylib/msgp) -// DO NOT EDIT - -import ( - "testing" - - "github.com/tinylib/msgp/msgp" -) - -func TestMarshalUnmarshalABPSubAppReq(t *testing.T) { - v := ABPSubAppReq{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgABPSubAppReq(b *testing.B) { - v := ABPSubAppReq{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgABPSubAppReq(b *testing.B) { - v := ABPSubAppReq{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalABPSubAppReq(b *testing.B) { - v := ABPSubAppReq{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalAppMetadata(t *testing.T) { - v := AppMetadata{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgAppMetadata(b *testing.B) { - v := AppMetadata{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgAppMetadata(b *testing.B) { - v := AppMetadata{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalAppMetadata(b *testing.B) { - v := AppMetadata{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalDataDownAppReq(t *testing.T) { - v := DataDownAppReq{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgDataDownAppReq(b *testing.B) { - v := DataDownAppReq{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgDataDownAppReq(b *testing.B) { - v := DataDownAppReq{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalDataDownAppReq(b *testing.B) { - v := DataDownAppReq{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalDataUpAppReq(t *testing.T) { - v := DataUpAppReq{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgDataUpAppReq(b *testing.B) { - v := DataUpAppReq{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgDataUpAppReq(b *testing.B) { - v := DataUpAppReq{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalDataUpAppReq(b *testing.B) { - v := DataUpAppReq{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} - -func TestMarshalUnmarshalOTAAAppReq(t *testing.T) { - v := OTAAAppReq{} - bts, err := v.MarshalMsg(nil) - if err != nil { - t.Fatal(err) - } - left, err := v.UnmarshalMsg(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after UnmarshalMsg(): %q", len(left), left) - } - - left, err = msgp.Skip(bts) - if err != nil { - t.Fatal(err) - } - if len(left) > 0 { - t.Errorf("%d bytes left over after Skip(): %q", len(left), left) - } -} - -func BenchmarkMarshalMsgOTAAAppReq(b *testing.B) { - v := OTAAAppReq{} - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - v.MarshalMsg(nil) - } -} - -func BenchmarkAppendMsgOTAAAppReq(b *testing.B) { - v := OTAAAppReq{} - bts := make([]byte, 0, v.Msgsize()) - bts, _ = v.MarshalMsg(bts[0:0]) - b.SetBytes(int64(len(bts))) - b.ReportAllocs() - b.ResetTimer() - for i := 0; i < b.N; i++ { - bts, _ = v.MarshalMsg(bts[0:0]) - } -} - -func BenchmarkUnmarshalOTAAAppReq(b *testing.B) { - v := OTAAAppReq{} - bts, _ := v.MarshalMsg(nil) - b.ReportAllocs() - b.SetBytes(int64(len(bts))) - b.ResetTimer() - for i := 0; i < b.N; i++ { - _, err := v.UnmarshalMsg(bts) - if err != nil { - b.Fatal(err) - } - } -} From 9f7c81209da3ff89a6c7d14bf8c2c9bc58979a41 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 11:23:55 +0200 Subject: [PATCH 1299/2266] Add ServerTime to Metadata Changes: - Time is no longer set in Handler - Time is taken from Gateway metadata (if set) - ServerTime is added by Router - ttnctl adds fake gateway time Resolves #128 --- core/components/handler/handler.go | 2 - core/components/router/router.go | 3 + core/components/router/router_test.go | 45 +++++++++++++ core/core.pb.go | 95 +++++++++++++++++++-------- core/definitions.go | 5 +- core/protos/core.proto | 3 +- ttnctl/cmd/uplink.go | 1 + 7 files changed, 123 insertions(+), 31 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 6868dfc7f..606aa63ec 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -209,7 +209,6 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* // 4. Send the actual bundle to the consumer ctx.WithField("BundleID", bundleID).Debug("Define new bundle") - req.Metadata.Time = time.Now().Format(time.RFC3339Nano) h.ChBundles <- bundle{ ID: bundleID, Packet: req, @@ -325,7 +324,6 @@ func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq // 4. Send the actual bundle to the consumer ctx.WithField("BundleID", bundleID).Debug("Define new bundle") - req.Metadata.Time = time.Now().Format(time.RFC3339Nano) h.ChBundles <- bundle{ ID: bundleID, Packet: req, diff --git a/core/components/router/router.go b/core/components/router/router.go index 9e1cda778..d26b2a3c4 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -7,6 +7,7 @@ import ( "net" "strings" "sync" + "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/dutycycle" @@ -233,6 +234,8 @@ func (r component) HandleData(_ context.Context, req *core.DataRouterReq) (*core func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Metadata, error) { ctx := r.Ctx.WithField("GatewayID", gid) + metadata.ServerTime = time.Now().UTC().Format(time.RFC3339Nano) + // Add Gateway location metadata if entry, err := r.GtwStorage.read(gid); err == nil { metadata.Latitude = entry.Metadata.Latitude diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index cf4b4270f..0dc107d92 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -517,6 +517,9 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -594,6 +597,10 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br1.InHandleData.Req.Metadata.ServerTime = "" + br2.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -673,6 +680,10 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br1.InHandleData.Req.Metadata.ServerTime = "" + br2.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -823,6 +834,9 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -973,6 +987,10 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br1.InHandleData.Req.Metadata.ServerTime = "" + br2.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -1050,6 +1068,10 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br1.InHandleData.Req.Metadata.ServerTime = "" + br2.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -1132,6 +1154,9 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -1235,6 +1260,9 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -1336,6 +1364,9 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -1438,6 +1469,9 @@ func TestHandleData(t *testing.T) { // Operate res, err := r.HandleData(context.Background(), req) + // Ignore ServerTime + br.InHandleData.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Data Responses") @@ -1516,6 +1550,10 @@ func TestHandleJoin(t *testing.T) { // Operate res, err := r.HandleJoin(context.Background(), req) + // Ignore ServerTime + br1.InHandleJoin.Req.Metadata.ServerTime = "" + br2.InHandleJoin.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") @@ -1593,6 +1631,10 @@ func TestHandleJoin(t *testing.T) { // Operate res, err := r.HandleJoin(context.Background(), req) + // Ignore ServerTime + br1.InHandleJoin.Req.Metadata.ServerTime = "" + br2.InHandleJoin.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") @@ -1975,6 +2017,9 @@ func TestHandleJoin(t *testing.T) { // Operate res, err := r.HandleJoin(context.Background(), req) + // Ignore ServerTime + br.InHandleJoin.Req.Metadata.ServerTime = "" + // Check CheckErrors(t, wantErr, err) Check(t, wantRes, res, "Router Join Responses") diff --git a/core/core.pb.go b/core/core.pb.go index df476b2cc..16c0407cc 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -34,6 +34,7 @@ type Metadata struct { Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` + ServerTime string `protobuf:"bytes,31,opt,name=ServerTime,json=serverTime,proto3" json:"ServerTime,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -176,6 +177,14 @@ func (m *Metadata) MarshalTo(data []byte) (int, error) { i++ i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Latitude)))) } + if len(m.ServerTime) > 0 { + data[i] = 0xfa + i++ + data[i] = 0x1 + i++ + i = encodeVarintCore(data, i, uint64(len(m.ServerTime))) + i += copy(data[i:], m.ServerTime) + } return i, nil } @@ -300,6 +309,10 @@ func (m *Metadata) Size() (n int) { if m.Latitude != 0 { n += 6 } + l = len(m.ServerTime) + if l > 0 { + n += 2 + l + sovCore(uint64(l)) + } return n } @@ -723,6 +736,35 @@ func (m *Metadata) Unmarshal(data []byte) error { v |= uint32(data[iNdEx-2]) << 16 v |= uint32(data[iNdEx-1]) << 24 m.Latitude = float32(math.Float32frombits(v)) + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerTime", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCore + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServerTime = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCore(data[iNdEx:]) @@ -947,30 +989,31 @@ var ( ) var fileDescriptorCore = []byte{ - // 391 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x52, 0x41, 0x4e, 0xe3, 0x30, - 0x14, 0x9d, 0xb4, 0x49, 0x9b, 0x38, 0xd3, 0x99, 0x91, 0x35, 0x33, 0x7c, 0x21, 0x54, 0x55, 0x5d, - 0xb1, 0x42, 0xa2, 0x9c, 0x00, 0x52, 0x55, 0x42, 0x6a, 0xa5, 0xca, 0x65, 0xc1, 0xd6, 0x34, 0xa6, - 0x58, 0x4a, 0xe3, 0xe0, 0x38, 0xa0, 0x70, 0x12, 0x8e, 0xc4, 0x92, 0x23, 0x20, 0xb8, 0x05, 0x2b, - 0x6c, 0x27, 0x69, 0xcb, 0xa6, 0x0b, 0x4b, 0xff, 0xbd, 0x97, 0xff, 0xdf, 0xfb, 0xb1, 0x11, 0x5a, - 0x0a, 0xc9, 0x4e, 0x32, 0x29, 0x94, 0xc0, 0xae, 0xa9, 0x87, 0x9f, 0x6d, 0xe4, 0xcf, 0x98, 0xa2, - 0x31, 0x55, 0x14, 0x03, 0xea, 0x8e, 0x0b, 0x55, 0x92, 0xeb, 0x53, 0x70, 0x06, 0xce, 0x71, 0x8f, - 0x74, 0xe3, 0x0a, 0x6e, 0x95, 0x11, 0xb4, 0x76, 0x95, 0x11, 0x3e, 0x42, 0xc1, 0x44, 0xb2, 0xfb, - 0x82, 0xa5, 0xcb, 0x12, 0xda, 0x5a, 0x6b, 0x91, 0xe0, 0xb6, 0x21, 0xf0, 0x21, 0xf2, 0xc7, 0x7a, - 0x32, 0xa1, 0x8a, 0x81, 0xab, 0xc5, 0x80, 0xf8, 0x71, 0x8d, 0x71, 0x1f, 0xa1, 0x48, 0xc4, 0x3c, - 0x5d, 0x59, 0xd5, 0xb3, 0xaa, 0x0e, 0xd8, 0x30, 0x66, 0xf2, 0x15, 0x5f, 0xb3, 0x5c, 0xd1, 0x75, - 0x06, 0x1d, 0xeb, 0x1a, 0xa8, 0x86, 0xc0, 0x18, 0xb9, 0x24, 0xcf, 0x39, 0x74, 0xb5, 0xe0, 0x11, - 0x57, 0xea, 0xda, 0x70, 0xd3, 0x3c, 0x95, 0xe0, 0xdb, 0x18, 0x6e, 0xa2, 0x6b, 0x3c, 0x40, 0xe1, - 0x9c, 0x96, 0x89, 0xa0, 0xf1, 0x82, 0x3f, 0x31, 0x08, 0xec, 0x9c, 0x30, 0xdb, 0x52, 0xa6, 0xcb, - 0xf8, 0x00, 0xb2, 0x09, 0x5c, 0x63, 0x61, 0xf6, 0x25, 0x93, 0xe8, 0x8e, 0xf2, 0x14, 0xc2, 0x6a, - 0x5f, 0x59, 0x41, 0x93, 0x2a, 0x22, 0xd1, 0x42, 0x51, 0x55, 0xe4, 0xf0, 0xd3, 0x9a, 0x07, 0xcb, - 0x86, 0x30, 0x3b, 0xcd, 0x44, 0x5c, 0x24, 0x54, 0x71, 0x91, 0x42, 0xaf, 0xda, 0x69, 0xbd, 0x61, - 0x4c, 0x9a, 0xcb, 0xf4, 0x61, 0x2e, 0x12, 0x2a, 0xb9, 0x2a, 0xe1, 0x97, 0xfe, 0xc0, 0x27, 0x21, - 0xdf, 0x52, 0xf8, 0x2f, 0xf2, 0xe6, 0xe2, 0x91, 0x49, 0xf8, 0x6d, 0x7d, 0xbd, 0xcc, 0x00, 0xf3, - 0x1f, 0xcf, 0x13, 0xc5, 0x55, 0x11, 0x33, 0xf8, 0x67, 0x4d, 0x7d, 0x5a, 0x63, 0x93, 0x68, 0x2a, - 0xd2, 0x55, 0x25, 0xfe, 0xaf, 0x6e, 0x20, 0x69, 0x08, 0xd3, 0x39, 0xa5, 0x75, 0xe7, 0x81, 0x15, - 0xfd, 0xa4, 0xc6, 0x43, 0x86, 0x7a, 0x26, 0x77, 0xbe, 0x79, 0x00, 0xbb, 0x36, 0xce, 0x3e, 0x9b, - 0xd6, 0x3e, 0x9b, 0xf6, 0x77, 0x9b, 0x8b, 0x3f, 0x2f, 0xef, 0x7d, 0xe7, 0x55, 0x9f, 0x37, 0x7d, - 0x9e, 0x3f, 0xfa, 0x3f, 0x6e, 0x3a, 0xf6, 0x09, 0x9e, 0x7d, 0x05, 0x00, 0x00, 0xff, 0xff, 0x26, - 0xf4, 0x86, 0xcc, 0x90, 0x02, 0x00, 0x00, + // 406 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0x4f, 0x8e, 0xd3, 0x30, + 0x18, 0xc5, 0x49, 0x9b, 0xb4, 0x89, 0x43, 0x01, 0x59, 0xfc, 0xf9, 0x84, 0x50, 0xa9, 0xba, 0x62, + 0x85, 0x44, 0x39, 0x01, 0xa4, 0xaa, 0x84, 0xd4, 0x4a, 0x95, 0xcb, 0x82, 0xad, 0x69, 0x4c, 0xb1, + 0x94, 0xc6, 0xc1, 0x71, 0x8a, 0xc2, 0x49, 0xd0, 0x9c, 0x68, 0x96, 0x73, 0x84, 0xd1, 0xcc, 0x45, + 0xc6, 0x9f, 0x93, 0xb4, 0x9d, 0x4d, 0x17, 0x96, 0xfc, 0xbd, 0x57, 0xfb, 0xfd, 0x5e, 0x63, 0x42, + 0xb6, 0x4a, 0x8b, 0x8f, 0x85, 0x56, 0x46, 0x51, 0x1f, 0xf7, 0xd3, 0x2b, 0x9f, 0x84, 0x2b, 0x61, + 0x78, 0xca, 0x0d, 0xa7, 0x40, 0x86, 0xf3, 0xca, 0xd4, 0xec, 0xc7, 0x27, 0xf0, 0x26, 0xde, 0x87, + 0x11, 0x1b, 0xa6, 0xcd, 0x78, 0x72, 0x66, 0xd0, 0x3b, 0x77, 0x66, 0xf4, 0x1d, 0x89, 0x16, 0x5a, + 0xfc, 0xa9, 0x44, 0xbe, 0xad, 0xa1, 0x6f, 0xbd, 0x1e, 0x8b, 0x7e, 0x75, 0x02, 0x7d, 0x4b, 0xc2, + 0xb9, 0xbd, 0x99, 0x71, 0x23, 0xc0, 0xb7, 0x66, 0xc4, 0xc2, 0xb4, 0x9d, 0xe9, 0x98, 0x90, 0x44, + 0xa5, 0x32, 0xdf, 0x39, 0x37, 0x70, 0xae, 0x05, 0xec, 0x14, 0xbc, 0xf9, 0xbb, 0xdc, 0x8b, 0xd2, + 0xf0, 0x7d, 0x01, 0x03, 0x97, 0x1a, 0x99, 0x4e, 0xa0, 0x94, 0xf8, 0xac, 0x2c, 0x25, 0x0c, 0xad, + 0x11, 0x30, 0x5f, 0xdb, 0x3d, 0x6a, 0xcb, 0x32, 0xd7, 0x10, 0x3a, 0x0c, 0x3f, 0xb3, 0x7b, 0x3a, + 0x21, 0xf1, 0x9a, 0xd7, 0x99, 0xe2, 0xe9, 0x46, 0xfe, 0x13, 0x10, 0xb9, 0x7b, 0xe2, 0xe2, 0x24, + 0xe1, 0x29, 0xcc, 0x01, 0xe2, 0x08, 0x7c, 0x8c, 0xc0, 0xbe, 0x6c, 0x91, 0xfc, 0xe6, 0x32, 0x87, + 0xb8, 0xe9, 0xab, 0x9b, 0x11, 0xa9, 0x12, 0x96, 0x6c, 0x0c, 0x37, 0x55, 0x09, 0x4f, 0x5d, 0x78, + 0xb4, 0xed, 0x04, 0xec, 0xb4, 0x52, 0x69, 0x95, 0x71, 0x23, 0x55, 0x0e, 0xa3, 0xa6, 0xd3, 0xfe, + 0xa8, 0x20, 0xcd, 0xb7, 0xfc, 0xb0, 0x56, 0x19, 0xd7, 0xd2, 0xd4, 0xf0, 0xcc, 0xfe, 0x20, 0x64, + 0xb1, 0x3c, 0x49, 0xf4, 0x25, 0x09, 0xd6, 0xea, 0xaf, 0xd0, 0xf0, 0xdc, 0xe5, 0x06, 0x05, 0x0e, + 0xf8, 0x3f, 0x7e, 0xc9, 0x8c, 0x34, 0x55, 0x2a, 0xe0, 0x95, 0x0b, 0x0d, 0x79, 0x3b, 0x23, 0xd1, + 0x52, 0xe5, 0xbb, 0xc6, 0x7c, 0xdd, 0x7c, 0x81, 0xac, 0x13, 0xf0, 0xe4, 0x92, 0xb7, 0x27, 0xdf, + 0x38, 0x33, 0xcc, 0xda, 0x19, 0x69, 0x37, 0x42, 0x1f, 0x84, 0x76, 0xfd, 0xdf, 0x37, 0xb4, 0xe5, + 0x51, 0x99, 0x0a, 0x32, 0xc2, 0x5e, 0xe5, 0xf1, 0x81, 0x9c, 0x63, 0x78, 0x97, 0x30, 0x7a, 0x97, + 0x30, 0xfa, 0x8f, 0x31, 0xbe, 0xbe, 0xb8, 0xbe, 0x1b, 0x7b, 0x37, 0x76, 0xdd, 0xda, 0xf5, 0xff, + 0x7e, 0xfc, 0xe4, 0xe7, 0xc0, 0x3d, 0xd1, 0xcf, 0x0f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xbb, + 0x81, 0x38, 0xb0, 0x02, 0x00, 0x00, } diff --git a/core/definitions.go b/core/definitions.go index ed2ec773b..e194035a3 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -19,8 +19,9 @@ type AppMetadata struct { Frequency float32 `json:"frequency"` DataRate string `json:"datarate"` CodingRate string `json:"codingrate"` - Timestamp uint32 `json:"timestamp"` - Time string `json:"time"` + Timestamp uint32 `json:"gateway_timestamp"` + Time string `json:"gateway_time,omitempty"` + ServerTime string `json:"server_time"` Rssi int32 `json:"rssi"` Lsnr float32 `json:"lsnr"` RFChain uint32 `json:"rfchain"` diff --git a/core/protos/core.proto b/core/protos/core.proto index 38d385b83..0107476fb 100644 --- a/core/protos/core.proto +++ b/core/protos/core.proto @@ -23,6 +23,8 @@ message Metadata { int32 Altitude = 21; float Longitude = 22; float Latitude = 23; + + string ServerTime = 31; } message StatsMetadata { @@ -30,4 +32,3 @@ message StatsMetadata { float Longitude = 2; float Latitude = 3; } - diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 0d07a0a4a..e854b9912 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -200,6 +200,7 @@ var uplinkCmd = &cobra.Command{ Codr: pointer.String(random.Codr()), Modu: pointer.String("LoRa"), Tmst: pointer.Uint32(1), + Time: pointer.Time(time.Now().UTC()), Data: &encoded, }, }, From 8eeb9061bf220eba0ba4b172b57fa7f6c112aa08 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 13:05:39 +0200 Subject: [PATCH 1300/2266] Add FPort to uplink datastructures Related to #126 --- core/application.pb.go | 144 ++++++---- core/definitions.go | 2 + core/handler.pb.go | 485 +++++++++++++++++++--------------- core/protos/application.proto | 11 +- core/protos/handler.proto | 39 +-- 5 files changed, 383 insertions(+), 298 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index d85d42a4b..11c14d1c6 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -85,10 +85,11 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 type DataAppReq struct { - Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - Metadata []*Metadata `protobuf:"bytes,4,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + FPort uint32 `protobuf:"varint,10,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` + Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + Metadata []*Metadata `protobuf:"bytes,30,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *DataAppReq) Reset() { *m = DataAppReq{} } @@ -114,7 +115,7 @@ func (*DataAppRes) Descriptor() ([]byte, []int) { return fileDescriptorApplicati type JoinAppReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - Metadata []*Metadata `protobuf:"bytes,3,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` + Metadata []*Metadata `protobuf:"bytes,30,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinAppReq) Reset() { *m = JoinAppReq{} } @@ -247,17 +248,9 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa - i++ - i = encodeVarintApplication(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0x12 + data[i] = 0xa i++ i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -265,15 +258,32 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { } if m.DevEUI != nil { if len(m.DevEUI) > 0 { - data[i] = 0x1a + data[i] = 0x12 i++ i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) i += copy(data[i:], m.DevEUI) } } + if m.FPort != 0 { + data[i] = 0x50 + i++ + i = encodeVarintApplication(data, i, uint64(m.FPort)) + } + if m.Payload != nil { + if len(m.Payload) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { - data[i] = 0x22 + data[i] = 0xf2 + i++ + data[i] = 0x1 i++ i = encodeVarintApplication(data, i, uint64(msg.Size())) n, err := msg.MarshalTo(data[i:]) @@ -337,7 +347,9 @@ func (m *JoinAppReq) MarshalTo(data []byte) (int, error) { } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { - data[i] = 0x1a + data[i] = 0xf2 + i++ + data[i] = 0x1 i++ i = encodeVarintApplication(data, i, uint64(msg.Size())) n, err := msg.MarshalTo(data[i:]) @@ -398,12 +410,6 @@ func encodeVarintApplication(data []byte, offset int, v uint64) int { func (m *DataAppReq) Size() (n int) { var l int _ = l - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } - } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -416,10 +422,19 @@ func (m *DataAppReq) Size() (n int) { n += 1 + l + sovApplication(uint64(l)) } } + if m.FPort != 0 { + n += 1 + sovApplication(uint64(m.FPort)) + } + if m.Payload != nil { + l = len(m.Payload) + if l > 0 { + n += 2 + l + sovApplication(uint64(l)) + } + } if len(m.Metadata) > 0 { for _, e := range m.Metadata { l = e.Size() - n += 1 + l + sovApplication(uint64(l)) + n += 2 + l + sovApplication(uint64(l)) } } return n @@ -449,7 +464,7 @@ func (m *JoinAppReq) Size() (n int) { if len(m.Metadata) > 0 { for _, e := range m.Metadata { l = e.Size() - n += 1 + l + sovApplication(uint64(l)) + n += 2 + l + sovApplication(uint64(l)) } } return n @@ -505,7 +520,7 @@ func (m *DataAppReq) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -529,14 +544,14 @@ func (m *DataAppReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -560,14 +575,33 @@ func (m *DataAppReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} } iNdEx = postIndex - case 3: + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) + } + m.FPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FPort |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 20: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -591,12 +625,12 @@ func (m *DataAppReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} } iNdEx = postIndex - case 4: + case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -789,7 +823,7 @@ func (m *JoinAppReq) Unmarshal(data []byte) error { m.DevEUI = []byte{} } iNdEx = postIndex - case 3: + case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -997,21 +1031,21 @@ var ( ) var fileDescriptorApplication = []byte{ - // 242 bytes of a gzipped FileDescriptorProto + // 251 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, - 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x26, 0x46, 0x2e, 0x2e, 0x97, 0xc4, - 0x92, 0x44, 0xc7, 0x82, 0x82, 0xa0, 0xd4, 0x42, 0x21, 0x09, 0x2e, 0xf6, 0x80, 0xc4, 0xca, 0x9c, - 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x9e, 0x20, 0xf6, 0x02, 0x08, 0x57, 0x48, 0x8c, - 0x8b, 0x0d, 0xa8, 0xc6, 0x35, 0xd4, 0x53, 0x82, 0x09, 0x2c, 0xc1, 0x96, 0x08, 0xe6, 0x81, 0xc4, - 0x5d, 0x52, 0xcb, 0x40, 0xe2, 0xcc, 0x10, 0xf1, 0x14, 0x30, 0x4f, 0x48, 0x8b, 0x8b, 0xc3, 0x37, - 0xb5, 0x24, 0x31, 0x05, 0x68, 0xb6, 0x04, 0x8b, 0x02, 0xb3, 0x06, 0xb7, 0x11, 0x9f, 0x1e, 0xd8, - 0x5e, 0x98, 0x68, 0x10, 0x47, 0x2e, 0x94, 0xa5, 0xc4, 0x83, 0xe4, 0x86, 0x62, 0xa5, 0x0c, 0x2e, - 0x2e, 0xaf, 0xfc, 0xcc, 0x3c, 0xa8, 0x8b, 0x10, 0xf6, 0x32, 0xe2, 0xb0, 0x97, 0x09, 0xa7, 0xbd, - 0xcc, 0x84, 0xed, 0x85, 0xdb, 0x54, 0x6c, 0x94, 0xc9, 0xc5, 0x0c, 0x64, 0x09, 0x19, 0x70, 0x71, - 0x79, 0x24, 0xe6, 0xa5, 0xe4, 0xa4, 0x82, 0x9c, 0x24, 0x24, 0x00, 0xd1, 0x8c, 0x08, 0x22, 0x29, - 0x74, 0x91, 0x62, 0x84, 0x0e, 0x90, 0x61, 0x30, 0x1d, 0x08, 0x2f, 0x48, 0xa1, 0x8b, 0x14, 0x3b, - 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, - 0x6c, 0xe0, 0xe8, 0x30, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x04, 0x04, 0x27, 0x4e, 0xb5, 0x01, - 0x00, 0x00, + 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x19, 0x8c, 0x5c, 0x5c, 0x2e, 0x89, + 0x25, 0x89, 0x8e, 0x05, 0x05, 0x41, 0xa9, 0x85, 0x42, 0x62, 0x5c, 0x6c, 0x40, 0x96, 0x6b, 0xa8, + 0xa7, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x07, 0x12, 0x77, 0x49, 0x2d, + 0x03, 0x89, 0x33, 0x41, 0xc4, 0x53, 0xc0, 0x3c, 0x21, 0x11, 0x2e, 0x56, 0xb7, 0x80, 0xfc, 0xa2, + 0x12, 0x09, 0x2e, 0xa0, 0x30, 0x6f, 0x10, 0x6b, 0x1a, 0x88, 0x23, 0x24, 0xc1, 0xc5, 0x1e, 0x90, + 0x58, 0x99, 0x93, 0x9f, 0x98, 0x22, 0x21, 0x02, 0x56, 0xce, 0x5e, 0x00, 0xe1, 0x0a, 0x69, 0x71, + 0x71, 0xf8, 0xa6, 0x96, 0x24, 0xa6, 0x00, 0x6d, 0x94, 0x90, 0x53, 0x60, 0xd6, 0xe0, 0x36, 0xe2, + 0xd3, 0x03, 0xbb, 0x06, 0x26, 0x1a, 0xc4, 0x91, 0x0b, 0x65, 0x29, 0xf1, 0x20, 0xb9, 0xac, 0x58, + 0x29, 0x83, 0x8b, 0xcb, 0x2b, 0x3f, 0x33, 0x8f, 0x4c, 0x77, 0x92, 0x68, 0x2f, 0xdc, 0xa6, 0x62, + 0xa3, 0x4c, 0x2e, 0x66, 0x20, 0x4b, 0xc8, 0x80, 0x8b, 0xcb, 0x23, 0x31, 0x2f, 0x25, 0x27, 0x15, + 0xe4, 0x24, 0x21, 0x01, 0x88, 0x66, 0x44, 0xc0, 0x49, 0xa1, 0x8b, 0x14, 0x23, 0x74, 0x80, 0x0c, + 0x83, 0xe9, 0x40, 0x78, 0x41, 0x0a, 0x5d, 0xa4, 0xd8, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, + 0x40, 0xfc, 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0x47, 0x92, 0x31, 0x20, 0x00, + 0x00, 0xff, 0xff, 0x79, 0x69, 0x22, 0xd8, 0xcb, 0x01, 0x00, 0x00, } diff --git a/core/definitions.go b/core/definitions.go index e194035a3..4f6eced51 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -6,6 +6,7 @@ package core // DataUpAppReq represents the actual payloads sent to application on uplink type DataUpAppReq struct { Payload []byte `json:"payload"` + FPort uint8 `json:"port,omitempty"` Metadata []AppMetadata `json:"metadata"` } @@ -35,6 +36,7 @@ type AppMetadata struct { // DataDownAppReq represents downlink messages sent by applications type DataDownAppReq struct { Payload []byte `json:"payload"` + FPort uint8 `json:"port,omitempty"` TTL string `json:"ttl"` } diff --git a/core/handler.pb.go b/core/handler.pb.go index 21eee48bd..0dc03ce82 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -21,12 +21,13 @@ var _ = fmt.Errorf var _ = math.Inf type DataUpHandlerReq struct { - Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` - AppEUI []byte `protobuf:"bytes,3,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,4,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - FCnt uint32 `protobuf:"varint,5,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` - MType uint32 `protobuf:"varint,6,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + FCnt uint32 `protobuf:"varint,10,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` + FPort uint32 `protobuf:"varint,11,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` + MType uint32 `protobuf:"varint,12,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` + Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *DataUpHandlerReq) Reset() { *m = DataUpHandlerReq{} } @@ -42,8 +43,8 @@ func (m *DataUpHandlerReq) GetMetadata() *Metadata { } type DataUpHandlerRes struct { - Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + Payload *LoRaWANData `protobuf:"bytes,20,opt,name=Payload,json=payload" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *DataUpHandlerRes) Reset() { *m = DataUpHandlerRes{} } @@ -66,10 +67,10 @@ func (m *DataUpHandlerRes) GetMetadata() *Metadata { } type DataDownHandlerReq struct { - Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - TTL string `protobuf:"bytes,4,opt,name=TTL,json=tTL,proto3" json:"TTL,omitempty"` + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + TTL string `protobuf:"bytes,30,opt,name=TTL,json=tTL,proto3" json:"TTL,omitempty"` } func (m *DataDownHandlerReq) Reset() { *m = DataDownHandlerReq{} } @@ -88,9 +89,9 @@ func (*DataDownHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorH type JoinHandlerReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - MIC []byte `protobuf:"bytes,4,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` - Metadata *Metadata `protobuf:"bytes,5,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + DevNonce []byte `protobuf:"bytes,10,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` + MIC []byte `protobuf:"bytes,11,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` + Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinHandlerReq) Reset() { *m = JoinHandlerReq{} } @@ -106,10 +107,10 @@ func (m *JoinHandlerReq) GetMetadata() *Metadata { } type JoinHandlerRes struct { - Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - Metadata *Metadata `protobuf:"bytes,4,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + DevAddr []byte `protobuf:"bytes,1,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` + Payload *LoRaWANJoinAccept `protobuf:"bytes,20,opt,name=Payload,json=payload" json:"Payload,omitempty"` + NwkSKey []byte `protobuf:"bytes,21,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *JoinHandlerRes) Reset() { *m = JoinHandlerRes{} } @@ -270,27 +271,9 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - } - if m.Metadata != nil { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) - n1, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 - } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0x1a + data[i] = 0xa i++ i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -298,22 +281,49 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { } if m.DevEUI != nil { if len(m.DevEUI) > 0 { - data[i] = 0x22 + data[i] = 0x12 i++ i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) i += copy(data[i:], m.DevEUI) } } if m.FCnt != 0 { - data[i] = 0x28 + data[i] = 0x50 i++ i = encodeVarintHandler(data, i, uint64(m.FCnt)) } + if m.FPort != 0 { + data[i] = 0x58 + i++ + i = encodeVarintHandler(data, i, uint64(m.FPort)) + } if m.MType != 0 { - data[i] = 0x30 + data[i] = 0x60 i++ i = encodeVarintHandler(data, i, uint64(m.MType)) } + if m.Payload != nil { + if len(m.Payload) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + } + if m.Metadata != nil { + data[i] = 0xf2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) + n1, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } return i, nil } @@ -333,7 +343,9 @@ func (m *DataUpHandlerRes) MarshalTo(data []byte) (int, error) { var l int _ = l if m.Payload != nil { - data[i] = 0xa + data[i] = 0xa2 + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(m.Payload.Size())) n2, err := m.Payload.MarshalTo(data[i:]) @@ -343,7 +355,9 @@ func (m *DataUpHandlerRes) MarshalTo(data []byte) (int, error) { i += n2 } if m.Metadata != nil { - data[i] = 0x12 + data[i] = 0xf2 + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) n3, err := m.Metadata.MarshalTo(data[i:]) @@ -370,17 +384,9 @@ func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - } if m.AppEUI != nil { if len(m.AppEUI) > 0 { - data[i] = 0x12 + data[i] = 0xa i++ i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) @@ -388,14 +394,26 @@ func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { } if m.DevEUI != nil { if len(m.DevEUI) > 0 { - data[i] = 0x1a + data[i] = 0x12 i++ i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) i += copy(data[i:], m.DevEUI) } } + if m.Payload != nil { + if len(m.Payload) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + } if len(m.TTL) > 0 { - data[i] = 0x22 + data[i] = 0xf2 + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(len(m.TTL))) i += copy(data[i:], m.TTL) @@ -454,7 +472,7 @@ func (m *JoinHandlerReq) MarshalTo(data []byte) (int, error) { } if m.DevNonce != nil { if len(m.DevNonce) > 0 { - data[i] = 0x1a + data[i] = 0x52 i++ i = encodeVarintHandler(data, i, uint64(len(m.DevNonce))) i += copy(data[i:], m.DevNonce) @@ -462,14 +480,16 @@ func (m *JoinHandlerReq) MarshalTo(data []byte) (int, error) { } if m.MIC != nil { if len(m.MIC) > 0 { - data[i] = 0x22 + data[i] = 0x5a i++ i = encodeVarintHandler(data, i, uint64(len(m.MIC))) i += copy(data[i:], m.MIC) } } if m.Metadata != nil { - data[i] = 0x2a + data[i] = 0xf2 + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) n4, err := m.Metadata.MarshalTo(data[i:]) @@ -496,8 +516,18 @@ func (m *JoinHandlerRes) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if m.DevAddr != nil { + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + } if m.Payload != nil { - data[i] = 0xa + data[i] = 0xa2 + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(m.Payload.Size())) n5, err := m.Payload.MarshalTo(data[i:]) @@ -506,24 +536,20 @@ func (m *JoinHandlerRes) MarshalTo(data []byte) (int, error) { } i += n5 } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - } if m.NwkSKey != nil { if len(m.NwkSKey) > 0 { - data[i] = 0x1a + data[i] = 0xaa + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) } } if m.Metadata != nil { - data[i] = 0x22 + data[i] = 0xf2 + i++ + data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) n6, err := m.Metadata.MarshalTo(data[i:]) @@ -565,16 +591,6 @@ func encodeVarintHandler(data []byte, offset int, v uint64) int { func (m *DataUpHandlerReq) Size() (n int) { var l int _ = l - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovHandler(uint64(l)) - } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -590,9 +606,22 @@ func (m *DataUpHandlerReq) Size() (n int) { if m.FCnt != 0 { n += 1 + sovHandler(uint64(m.FCnt)) } + if m.FPort != 0 { + n += 1 + sovHandler(uint64(m.FPort)) + } if m.MType != 0 { n += 1 + sovHandler(uint64(m.MType)) } + if m.Payload != nil { + l = len(m.Payload) + if l > 0 { + n += 2 + l + sovHandler(uint64(l)) + } + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 2 + l + sovHandler(uint64(l)) + } return n } @@ -601,11 +630,11 @@ func (m *DataUpHandlerRes) Size() (n int) { _ = l if m.Payload != nil { l = m.Payload.Size() - n += 1 + l + sovHandler(uint64(l)) + n += 2 + l + sovHandler(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() - n += 1 + l + sovHandler(uint64(l)) + n += 2 + l + sovHandler(uint64(l)) } return n } @@ -613,12 +642,6 @@ func (m *DataUpHandlerRes) Size() (n int) { func (m *DataDownHandlerReq) Size() (n int) { var l int _ = l - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - } if m.AppEUI != nil { l = len(m.AppEUI) if l > 0 { @@ -631,9 +654,15 @@ func (m *DataDownHandlerReq) Size() (n int) { n += 1 + l + sovHandler(uint64(l)) } } + if m.Payload != nil { + l = len(m.Payload) + if l > 0 { + n += 2 + l + sovHandler(uint64(l)) + } + } l = len(m.TTL) if l > 0 { - n += 1 + l + sovHandler(uint64(l)) + n += 2 + l + sovHandler(uint64(l)) } return n } @@ -673,7 +702,7 @@ func (m *JoinHandlerReq) Size() (n int) { } if m.Metadata != nil { l = m.Metadata.Size() - n += 1 + l + sovHandler(uint64(l)) + n += 2 + l + sovHandler(uint64(l)) } return n } @@ -681,25 +710,25 @@ func (m *JoinHandlerReq) Size() (n int) { func (m *JoinHandlerRes) Size() (n int) { var l int _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovHandler(uint64(l)) - } if m.DevAddr != nil { l = len(m.DevAddr) if l > 0 { n += 1 + l + sovHandler(uint64(l)) } } + if m.Payload != nil { + l = m.Payload.Size() + n += 2 + l + sovHandler(uint64(l)) + } if m.NwkSKey != nil { l = len(m.NwkSKey) if l > 0 { - n += 1 + l + sovHandler(uint64(l)) + n += 2 + l + sovHandler(uint64(l)) } } if m.Metadata != nil { l = m.Metadata.Size() - n += 1 + l + sovHandler(uint64(l)) + n += 2 + l + sovHandler(uint64(l)) } return n } @@ -748,7 +777,7 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -772,16 +801,16 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -791,30 +820,28 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthHandler } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} } iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + case 10: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) } - var byteLen int + m.FCnt = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -824,26 +851,52 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + m.FCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { - return ErrInvalidLengthHandler + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF + m.FPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FPort |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) } - iNdEx = postIndex - case 4: + m.MType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.MType |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 20: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -867,16 +920,16 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} } iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + case 30: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } - m.FCnt = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -886,30 +939,25 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) + if msglen < 0 { + return ErrInvalidLengthHandler } - m.MType = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.MType |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -960,7 +1008,7 @@ func (m *DataUpHandlerRes) Unmarshal(data []byte) error { return fmt.Errorf("proto: DataUpHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: + case 20: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } @@ -993,7 +1041,7 @@ func (m *DataUpHandlerRes) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 2: + case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1078,7 +1126,7 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1102,14 +1150,14 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1133,14 +1181,14 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} + m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) + if m.DevEUI == nil { + m.DevEUI = []byte{} } iNdEx = postIndex - case 3: + case 20: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1164,12 +1212,12 @@ func (m *DataDownHandlerReq) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} } iNdEx = postIndex - case 4: + case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType) } @@ -1360,7 +1408,7 @@ func (m *JoinHandlerReq) Unmarshal(data []byte) error { m.DevEUI = []byte{} } iNdEx = postIndex - case 3: + case 10: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) } @@ -1391,7 +1439,7 @@ func (m *JoinHandlerReq) Unmarshal(data []byte) error { m.DevNonce = []byte{} } iNdEx = postIndex - case 4: + case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) } @@ -1422,7 +1470,7 @@ func (m *JoinHandlerReq) Unmarshal(data []byte) error { m.MIC = []byte{} } iNdEx = postIndex - case 5: + case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1507,9 +1555,9 @@ func (m *JoinHandlerRes) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } - var msglen int + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -1519,30 +1567,28 @@ func (m *JoinHandlerRes) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + if byteLen < 0 { return ErrInvalidLengthHandler } - postIndex := iNdEx + msglen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - if m.Payload == nil { - m.Payload = &LoRaWANJoinAccept{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} } iNdEx = postIndex - case 2: + case 20: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } - var byteLen int + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -1552,24 +1598,26 @@ func (m *JoinHandlerRes) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + if msglen < 0 { return ErrInvalidLengthHandler } - postIndex := iNdEx + byteLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} + if m.Payload == nil { + m.Payload = &LoRaWANJoinAccept{} + } + if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex - case 3: + case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -1600,7 +1648,7 @@ func (m *JoinHandlerRes) Unmarshal(data []byte) error { m.NwkSKey = []byte{} } iNdEx = postIndex - case 4: + case 30: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1760,34 +1808,33 @@ var ( ) var fileDescriptorHandler = []byte{ - // 449 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xc1, 0x8e, 0xd3, 0x30, - 0x10, 0x25, 0x4d, 0x9a, 0x84, 0xd9, 0x6d, 0x55, 0x46, 0xd5, 0x12, 0xe5, 0xb0, 0x42, 0x39, 0x21, - 0x90, 0x56, 0xa2, 0x5c, 0x38, 0x21, 0x85, 0x06, 0x44, 0xa1, 0xad, 0x50, 0xc8, 0x8a, 0xb3, 0x89, - 0x8d, 0x40, 0xdb, 0xc6, 0x26, 0x89, 0xa8, 0xfa, 0x27, 0x88, 0x2b, 0x7f, 0xc0, 0x57, 0xc0, 0x8d, - 0x4f, 0x40, 0xf0, 0x23, 0xd8, 0x71, 0x50, 0x70, 0x69, 0x01, 0xed, 0x21, 0xd2, 0xbc, 0xe7, 0x99, - 0xcc, 0x9b, 0x37, 0x36, 0x0c, 0x5e, 0x93, 0x82, 0xae, 0x58, 0x79, 0x26, 0x4a, 0x5e, 0x73, 0x74, - 0x72, 0x5e, 0xb2, 0x70, 0xb0, 0xe2, 0x25, 0xd9, 0x90, 0x42, 0x93, 0x21, 0x28, 0x52, 0xc7, 0xd1, - 0x27, 0x0b, 0x46, 0x09, 0xa9, 0xc9, 0xb9, 0x78, 0xac, 0x0b, 0x53, 0xf6, 0x16, 0x03, 0xf0, 0x9e, - 0x91, 0xed, 0x8a, 0x13, 0x1a, 0x58, 0x37, 0xac, 0x9b, 0xc7, 0xa9, 0x27, 0x34, 0xc4, 0x5b, 0xe0, - 0x2f, 0x58, 0x4d, 0xa8, 0xac, 0x08, 0x7a, 0xf2, 0xe8, 0x68, 0x32, 0x3c, 0x6b, 0xfe, 0xf6, 0x8b, - 0x4d, 0xfd, 0x75, 0x1b, 0xe1, 0x09, 0xb8, 0xb1, 0x10, 0x0f, 0xcf, 0x67, 0x81, 0xdd, 0xfc, 0xc4, - 0x25, 0x0d, 0x52, 0x7c, 0xc2, 0xde, 0x29, 0xde, 0xd1, 0x3c, 0x6d, 0x10, 0x22, 0x38, 0x8f, 0xa6, - 0x45, 0x1d, 0xf4, 0x25, 0x3b, 0x48, 0x9d, 0x57, 0x32, 0xc6, 0x31, 0xf4, 0x17, 0xd9, 0x56, 0xb0, - 0xc0, 0x6d, 0xc8, 0xfe, 0x5a, 0x81, 0xe8, 0xe2, 0x0f, 0xcd, 0x15, 0xde, 0x36, 0x35, 0x1f, 0x4d, - 0xae, 0x69, 0x61, 0x73, 0x9e, 0x92, 0x17, 0xf1, 0x52, 0xe5, 0x5f, 0x6a, 0x8c, 0x48, 0x00, 0xaa, - 0xe2, 0x84, 0x6f, 0x8a, 0xff, 0xb2, 0xa8, 0x1b, 0xbb, 0x77, 0x60, 0x6c, 0xdb, 0x18, 0x7b, 0x04, - 0x76, 0x96, 0xcd, 0x1b, 0x2f, 0xae, 0xa6, 0x76, 0x9d, 0xcd, 0xa3, 0xf1, 0x9e, 0x8e, 0x55, 0xf4, - 0xc1, 0x82, 0xe1, 0x13, 0xfe, 0xe6, 0x77, 0x11, 0x5d, 0x2b, 0xeb, 0x40, 0xab, 0x9e, 0xd1, 0x2a, - 0x04, 0x5f, 0xf2, 0x4b, 0x5e, 0xe4, 0xac, 0x15, 0xe1, 0xd3, 0x16, 0x2b, 0x19, 0x8b, 0xd9, 0xb4, - 0x5d, 0x89, 0xbd, 0x9e, 0x4d, 0x0d, 0x93, 0xfa, 0xff, 0x30, 0xe9, 0xe3, 0xae, 0xb8, 0x0a, 0xef, - 0xec, 0x2e, 0xe4, 0xba, 0xb1, 0x10, 0x95, 0x1d, 0xe7, 0x39, 0x13, 0x75, 0x67, 0x9d, 0x34, 0x55, - 0xea, 0x8b, 0x29, 0x2d, 0x5b, 0xe1, 0x1e, 0xd5, 0x50, 0x9d, 0x2c, 0x37, 0x17, 0xcf, 0x9f, 0xb2, - 0x6d, 0x2b, 0xdc, 0x2b, 0x34, 0x34, 0x54, 0x3a, 0x7f, 0x57, 0x39, 0xf9, 0x62, 0x81, 0xd7, 0x2a, - 0xc4, 0xfb, 0x70, 0xac, 0x43, 0x7d, 0x93, 0xf0, 0x44, 0x57, 0xed, 0xbe, 0x85, 0x70, 0x3f, 0x5f, - 0x61, 0x02, 0xc3, 0xae, 0x5e, 0xad, 0x0a, 0x83, 0x2e, 0xd3, 0xbc, 0x2c, 0xe1, 0xa1, 0x93, 0x0a, - 0xef, 0x01, 0x68, 0xa4, 0xec, 0xc0, 0xb1, 0xce, 0x33, 0xb7, 0x1c, 0xee, 0x63, 0xab, 0x07, 0xa3, - 0xcf, 0xdf, 0x4f, 0xad, 0xaf, 0xf2, 0xfb, 0x26, 0xbf, 0xf7, 0x3f, 0x4e, 0xaf, 0xbc, 0x74, 0x9b, - 0x17, 0x7d, 0xf7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf4, 0x1e, 0xc9, 0x1d, 0x03, 0x04, 0x00, - 0x00, + // 443 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x93, 0xcf, 0x8e, 0xd3, 0x30, + 0x10, 0xc6, 0x09, 0xed, 0xb6, 0x65, 0x9a, 0x56, 0x65, 0x54, 0x16, 0x2b, 0x87, 0x0a, 0xe5, 0x84, + 0x40, 0x5a, 0x89, 0x72, 0xe1, 0x84, 0x14, 0x1a, 0x56, 0x14, 0xda, 0x6a, 0x65, 0xb2, 0xe2, 0x6c, + 0x62, 0xaf, 0x40, 0xdb, 0xc6, 0x26, 0x89, 0xa8, 0xfa, 0x26, 0x88, 0x2b, 0x2f, 0xb3, 0xdc, 0x78, + 0x04, 0x04, 0x2f, 0x82, 0xff, 0x04, 0x55, 0x29, 0xdd, 0xc3, 0xd2, 0x43, 0xa4, 0xf9, 0x7e, 0xf1, + 0x8c, 0xbf, 0xf1, 0xd8, 0xd0, 0xfb, 0xc0, 0x32, 0xbe, 0x14, 0xf9, 0x89, 0xca, 0x65, 0x29, 0xb1, + 0x99, 0xca, 0x5c, 0x04, 0xbd, 0xa5, 0xcc, 0xd9, 0x9a, 0x65, 0x0e, 0x06, 0x60, 0xa0, 0x8b, 0xc3, + 0x2b, 0x0f, 0x06, 0x31, 0x2b, 0xd9, 0xb9, 0x7a, 0xe5, 0x12, 0xa9, 0xf8, 0x84, 0xc7, 0xd0, 0x8a, + 0x94, 0x7a, 0x79, 0x3e, 0x25, 0xde, 0x03, 0xef, 0xa1, 0x4f, 0x5b, 0xcc, 0x2a, 0xc3, 0x63, 0xf1, + 0xd9, 0xf0, 0xdb, 0x8e, 0x73, 0xab, 0x10, 0xa1, 0x79, 0x3a, 0xc9, 0x4a, 0x02, 0x9a, 0xf6, 0x68, + 0xf3, 0x42, 0xc7, 0x38, 0x84, 0xa3, 0xd3, 0x33, 0x99, 0x97, 0xa4, 0x6b, 0xe1, 0xd1, 0x85, 0x11, + 0x86, 0xce, 0x93, 0x8d, 0x12, 0xc4, 0x77, 0x74, 0x65, 0x04, 0x12, 0x68, 0x9f, 0xb1, 0xcd, 0x52, + 0x32, 0x4e, 0x86, 0xb6, 0x70, 0x5b, 0x39, 0x89, 0x8f, 0xa0, 0x33, 0x17, 0x25, 0xe3, 0xda, 0x21, + 0x19, 0xe9, 0x5f, 0xdd, 0x71, 0xff, 0xc4, 0xba, 0xff, 0x4b, 0x69, 0x67, 0x55, 0x45, 0xe1, 0xe5, + 0x3f, 0x9d, 0x14, 0xf8, 0xb8, 0x5e, 0xb9, 0x3b, 0xbe, 0xeb, 0xd2, 0x67, 0x92, 0xb2, 0x77, 0xd1, + 0xc2, 0xac, 0xff, 0xbf, 0xcd, 0x14, 0xa0, 0x49, 0x8e, 0xe5, 0x3a, 0x3b, 0xe0, 0xe0, 0xae, 0x6f, + 0x7c, 0x00, 0x8d, 0x24, 0x99, 0x59, 0x1b, 0x77, 0x68, 0xa3, 0x4c, 0x66, 0xe1, 0x70, 0xcf, 0x8e, + 0x45, 0xf8, 0xd5, 0x83, 0xfe, 0x6b, 0xf9, 0xf1, 0x10, 0x13, 0x01, 0x74, 0x34, 0x5f, 0xc8, 0x2c, + 0x15, 0x76, 0x82, 0x3e, 0xed, 0xf0, 0x4a, 0x1b, 0x1b, 0xf3, 0xe9, 0xc4, 0xce, 0xd0, 0xa7, 0x8d, + 0xd5, 0x74, 0x72, 0xa3, 0x43, 0xfa, 0xb6, 0x6b, 0xae, 0x30, 0x1d, 0xeb, 0xcd, 0x22, 0xce, 0xf3, + 0xca, 0x5d, 0x9b, 0x3b, 0x89, 0x4f, 0x76, 0x47, 0x75, 0xbf, 0x36, 0x2a, 0x53, 0x27, 0x4a, 0x53, + 0xa1, 0xca, 0xed, 0x21, 0xe9, 0x62, 0x8b, 0xf5, 0xe5, 0xdb, 0x37, 0x62, 0x43, 0xee, 0xb9, 0x62, + 0x99, 0x93, 0x37, 0x71, 0x39, 0xfe, 0xee, 0x41, 0xbb, 0x72, 0x88, 0xcf, 0xc1, 0x77, 0xa1, 0xbb, + 0x49, 0x78, 0xec, 0xb2, 0x76, 0x5f, 0x48, 0xb0, 0x9f, 0x17, 0x18, 0x43, 0x7f, 0x9b, 0x6f, 0x46, + 0x85, 0x64, 0xbb, 0xb2, 0x7e, 0x59, 0x82, 0xeb, 0xfe, 0x14, 0xf8, 0x0c, 0xc0, 0x29, 0xd3, 0x34, + 0x0e, 0xdd, 0xba, 0xfa, 0x94, 0x83, 0x7d, 0xb4, 0x78, 0x31, 0xb8, 0xfa, 0x35, 0xf2, 0x7e, 0xe8, + 0xef, 0xa7, 0xfe, 0xbe, 0xfc, 0x1e, 0xdd, 0x7a, 0xdf, 0xb2, 0xef, 0xfc, 0xe9, 0x9f, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x4a, 0x5b, 0xdc, 0x30, 0x19, 0x04, 0x00, 0x00, } diff --git a/core/protos/application.proto b/core/protos/application.proto index 256462b21..75be069b6 100644 --- a/core/protos/application.proto +++ b/core/protos/application.proto @@ -4,10 +4,11 @@ import "core.proto"; package core; message DataAppReq { - bytes Payload = 1; - bytes AppEUI = 2; - bytes DevEUI = 3; - repeated Metadata Metadata = 4; + bytes AppEUI = 1; + bytes DevEUI = 2; + uint32 FPort = 10; + bytes Payload = 20; + repeated Metadata Metadata = 30; } message DataAppRes {} @@ -15,7 +16,7 @@ message DataAppRes {} message JoinAppReq { bytes AppEUI = 1; bytes DevEUI = 2; - repeated Metadata Metadata = 3; + repeated Metadata Metadata = 30; } message JoinAppRes {} diff --git a/core/protos/handler.proto b/core/protos/handler.proto index d265a97be..7d862adaf 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -5,24 +5,25 @@ import "core.proto"; package core; message DataUpHandlerReq { - bytes Payload = 1; - Metadata Metadata = 2; - bytes AppEUI = 3; - bytes DevEUI = 4; - uint32 FCnt = 5; - uint32 MType = 6; + bytes AppEUI = 1; + bytes DevEUI = 2; + uint32 FCnt = 10; + uint32 FPort = 11; + uint32 MType = 12; + bytes Payload = 20; + Metadata Metadata = 30; } message DataUpHandlerRes { - LoRaWANData Payload = 1; - Metadata Metadata = 2; + LoRaWANData Payload = 20; + Metadata Metadata = 30; } message DataDownHandlerReq { - bytes Payload = 1; - bytes AppEUI = 2; - bytes DevEUI = 3; - string TTL = 4; + bytes AppEUI = 1; + bytes DevEUI = 2; + bytes Payload = 20; + string TTL = 30; } message DataDownHandlerRes {} @@ -30,16 +31,16 @@ message DataDownHandlerRes {} message JoinHandlerReq { bytes AppEUI = 1; bytes DevEUI = 2; - bytes DevNonce = 3; - bytes MIC = 4; - Metadata Metadata = 5; + bytes DevNonce = 10; + bytes MIC = 11; + Metadata Metadata = 30; } message JoinHandlerRes { - LoRaWANJoinAccept Payload = 1; - bytes DevAddr = 2; - bytes NwkSKey = 3; - Metadata Metadata = 4; + bytes DevAddr = 1; + LoRaWANJoinAccept Payload = 20; + bytes NwkSKey = 21; + Metadata Metadata = 30; } service Handler { From 0cc5ffe72b4c1e87665107ec16ec67434edb630e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 13:12:33 +0200 Subject: [PATCH 1301/2266] Forward FPort up to MQTT Related to #126 --- core/adapters/mqtt/adapter.go | 1 + core/components/broker/broker.go | 1 + core/components/handler/handler.go | 3 +++ core/components/handler/handler_test.go | 28 +++++++++++++++++++++++++ ttnctl/cmd/uplink.go | 3 +-- 5 files changed, 34 insertions(+), 2 deletions(-) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index 70c580113..fa7f3f75e 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -33,6 +33,7 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . dataUp := core.DataUpAppReq{ Payload: req.Payload, Metadata: core.ProtoMetaToAppMeta(req.Metadata...), + FPort: uint8(req.FPort), } if a.ctx != nil { diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 3f8accf04..7d69dcf2e 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -309,6 +309,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c DevEUI: mEntry.DevEUI, AppEUI: mEntry.AppEUI, FCnt: fhdr.FCnt, + FPort: req.Payload.MACPayload.FPort, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, }) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 606aa63ec..1842da561 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -533,6 +533,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bundles []bundle) { stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) var metadata []*core.Metadata + var fPort uint32 var payload []byte var firstTime time.Time @@ -563,6 +564,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu return } firstTime = bundle.Time + fPort = packet.FPort stats.MarkMeter("handler.uplink.in.unique") } else { diff := bundle.Time.Sub(firstTime).Nanoseconds() @@ -579,6 +581,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu _, err = h.AppAdapter.HandleData(context.Background(), &core.DataAppReq{ AppEUI: appEUI, DevEUI: devEUI, + FPort: fPort, Payload: payload, Metadata: metadata, }) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 7382962d2..a9e76fb7e 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -223,6 +223,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: 14, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -265,6 +266,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: 14, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -307,6 +309,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: 14, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -349,6 +352,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: nil, FCnt: 14, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -391,6 +395,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: 14, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -451,6 +456,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -462,6 +468,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt = devStorage.OutRead.Entry.FCntDown @@ -520,6 +527,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 10, MType: uint32(lorawan.UnconfirmedDataUp), } req2 := &core.DataUpHandlerReq{ @@ -534,6 +542,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, FCnt: req1.FCnt, + FPort: req1.FPort, MType: req1.MType, } @@ -547,6 +556,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req1.Metadata, req2.Metadata}, AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, + FPort: 10, } var wantFCnt = devStorage.OutRead.Entry.FCntDown @@ -633,6 +643,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -678,6 +689,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt @@ -842,6 +854,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -853,6 +866,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt uint32 @@ -908,6 +922,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -919,6 +934,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt uint32 @@ -981,6 +997,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt1, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } req2 := &core.DataUpHandlerReq{ @@ -995,6 +1012,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, DevEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, FCnt: fcnt2, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -1008,12 +1026,14 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req1.Metadata}, AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, + FPort: 1, } var wantData2 = &core.DataAppReq{ Payload: payload2, Metadata: []*core.Metadata{req2.Metadata}, AppEUI: req2.AppEUI, DevEUI: req2.DevEUI, + FPort: 1, } var wantFCnt1 uint32 = 3 var wantFCnt2 uint32 = 11 @@ -1100,6 +1120,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -1145,6 +1166,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt @@ -1209,6 +1231,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.UnconfirmedDataUp), } @@ -1220,6 +1243,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt = devStorage.OutRead.Entry.FCntDown + 1 @@ -1283,6 +1307,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.ConfirmedDataUp), } @@ -1330,6 +1355,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt @@ -1393,6 +1419,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, FCnt: fcnt, + FPort: 1, MType: uint32(lorawan.ConfirmedDataUp), } @@ -1432,6 +1459,7 @@ func TestHandleDataUp(t *testing.T) { Metadata: []*core.Metadata{req.Metadata}, AppEUI: req.AppEUI, DevEUI: req.DevEUI, + FPort: 1, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index e854b9912..7f1a05c91 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -72,8 +72,7 @@ var uplinkCmd = &cobra.Command{ DevAddr: devAddr, FCnt: uint32(fcnt), } - macPayload.FPort = new(uint8) - *macPayload.FPort = 1 + macPayload.FPort = pointer.Uint8(1) macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} if err := macPayload.EncryptFRMPayload(appSKey); err != nil { ctx.Fatalf("Unable to encrypt frame payload: %s", err) From 03f786fee16d3991c07bfd6814392df0a6dd33ab Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 13:37:33 +0200 Subject: [PATCH 1302/2266] Add FCnt to uplink datastructures Related to #126 --- core/application.pb.go | 53 +++++++++++++++++++++------ core/components/broker/broker_test.go | 5 +++ core/definitions.go | 1 + core/protos/application.proto | 1 + 4 files changed, 48 insertions(+), 12 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 11c14d1c6..170ea2ddc 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -88,6 +88,7 @@ type DataAppReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` FPort uint32 `protobuf:"varint,10,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` + FCnt uint32 `protobuf:"varint,11,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` Metadata []*Metadata `protobuf:"bytes,30,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` } @@ -269,6 +270,11 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintApplication(data, i, uint64(m.FPort)) } + if m.FCnt != 0 { + data[i] = 0x58 + i++ + i = encodeVarintApplication(data, i, uint64(m.FCnt)) + } if m.Payload != nil { if len(m.Payload) > 0 { data[i] = 0xa2 @@ -425,6 +431,9 @@ func (m *DataAppReq) Size() (n int) { if m.FPort != 0 { n += 1 + sovApplication(uint64(m.FPort)) } + if m.FCnt != 0 { + n += 1 + sovApplication(uint64(m.FCnt)) + } if m.Payload != nil { l = len(m.Payload) if l > 0 { @@ -599,6 +608,25 @@ func (m *DataAppReq) Unmarshal(data []byte) error { break } } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApplication + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 20: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) @@ -1031,21 +1059,22 @@ var ( ) var fileDescriptorApplication = []byte{ - // 251 bytes of a gzipped FileDescriptorProto + // 267 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, - 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x19, 0x8c, 0x5c, 0x5c, 0x2e, 0x89, + 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x35, 0x8c, 0x5c, 0x5c, 0x2e, 0x89, 0x25, 0x89, 0x8e, 0x05, 0x05, 0x41, 0xa9, 0x85, 0x42, 0x62, 0x5c, 0x6c, 0x40, 0x96, 0x6b, 0xa8, 0xa7, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x07, 0x12, 0x77, 0x49, 0x2d, 0x03, 0x89, 0x33, 0x41, 0xc4, 0x53, 0xc0, 0x3c, 0x21, 0x11, 0x2e, 0x56, 0xb7, 0x80, 0xfc, 0xa2, - 0x12, 0x09, 0x2e, 0xa0, 0x30, 0x6f, 0x10, 0x6b, 0x1a, 0x88, 0x23, 0x24, 0xc1, 0xc5, 0x1e, 0x90, - 0x58, 0x99, 0x93, 0x9f, 0x98, 0x22, 0x21, 0x02, 0x56, 0xce, 0x5e, 0x00, 0xe1, 0x0a, 0x69, 0x71, - 0x71, 0xf8, 0xa6, 0x96, 0x24, 0xa6, 0x00, 0x6d, 0x94, 0x90, 0x53, 0x60, 0xd6, 0xe0, 0x36, 0xe2, - 0xd3, 0x03, 0xbb, 0x06, 0x26, 0x1a, 0xc4, 0x91, 0x0b, 0x65, 0x29, 0xf1, 0x20, 0xb9, 0xac, 0x58, - 0x29, 0x83, 0x8b, 0xcb, 0x2b, 0x3f, 0x33, 0x8f, 0x4c, 0x77, 0x92, 0x68, 0x2f, 0xdc, 0xa6, 0x62, - 0xa3, 0x4c, 0x2e, 0x66, 0x20, 0x4b, 0xc8, 0x80, 0x8b, 0xcb, 0x23, 0x31, 0x2f, 0x25, 0x27, 0x15, - 0xe4, 0x24, 0x21, 0x01, 0x88, 0x66, 0x44, 0xc0, 0x49, 0xa1, 0x8b, 0x14, 0x23, 0x74, 0x80, 0x0c, - 0x83, 0xe9, 0x40, 0x78, 0x41, 0x0a, 0x5d, 0xa4, 0xd8, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, - 0x40, 0xfc, 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0x47, 0x92, 0x31, 0x20, 0x00, - 0x00, 0xff, 0xff, 0x79, 0x69, 0x22, 0xd8, 0xcb, 0x01, 0x00, 0x00, + 0x12, 0x09, 0x2e, 0xa0, 0x30, 0x6f, 0x10, 0x6b, 0x1a, 0x88, 0x23, 0x24, 0xc4, 0xc5, 0xe2, 0xe6, + 0x9c, 0x57, 0x22, 0xc1, 0x0d, 0x16, 0x64, 0x49, 0x03, 0xb2, 0x85, 0x24, 0xb8, 0xd8, 0x03, 0x12, + 0x2b, 0x73, 0xf2, 0x13, 0x53, 0x24, 0x44, 0xc0, 0x46, 0xb0, 0x17, 0x40, 0xb8, 0x42, 0x5a, 0x5c, + 0x1c, 0xbe, 0xa9, 0x25, 0x89, 0x29, 0x40, 0x57, 0x48, 0xc8, 0x29, 0x30, 0x6b, 0x70, 0x1b, 0xf1, + 0xe9, 0x81, 0x5d, 0x08, 0x13, 0x0d, 0xe2, 0xc8, 0x85, 0xb2, 0x94, 0x78, 0x90, 0x5c, 0x5b, 0xac, + 0x94, 0xc1, 0xc5, 0xe5, 0x95, 0x9f, 0x99, 0x47, 0xa6, 0xdb, 0x49, 0xb4, 0x17, 0x6e, 0x53, 0xb1, + 0x51, 0x26, 0x17, 0x33, 0x90, 0x25, 0x64, 0xc0, 0xc5, 0xe5, 0x91, 0x98, 0x97, 0x92, 0x93, 0x0a, + 0x72, 0x92, 0x90, 0x00, 0x44, 0x33, 0x22, 0x30, 0xa5, 0xd0, 0x45, 0x8a, 0x11, 0x3a, 0x40, 0x86, + 0xc1, 0x74, 0x20, 0xbc, 0x20, 0x85, 0x2e, 0x52, 0xec, 0x24, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x05, + 0x20, 0x7e, 0x00, 0xc4, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x23, 0xce, 0x18, 0x10, 0x00, + 0x00, 0xff, 0xff, 0x31, 0xc2, 0xe7, 0xa9, 0xdf, 0x01, 0x00, 0x00, } diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index c4922d8c8..69425651a 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -209,6 +209,7 @@ func TestHandleData(t *testing.T) { AppEUI: nc.OutRead.Entries[1].AppEUI, DevEUI: nc.OutRead.Entries[1].DevEUI, FCnt: req.Payload.MACPayload.FHDR.FCnt, + FPort: 1, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } @@ -353,6 +354,7 @@ func TestHandleData(t *testing.T) { AppEUI: nc.OutRead.Entries[0].AppEUI, DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, + FPort: 1, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } @@ -501,6 +503,7 @@ func TestHandleData(t *testing.T) { AppEUI: nc.OutRead.Entries[0].AppEUI, DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, + FPort: 1, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } @@ -597,6 +600,7 @@ func TestHandleData(t *testing.T) { AppEUI: nc.OutRead.Entries[0].AppEUI, DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, + FPort: 1, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } @@ -763,6 +767,7 @@ func TestHandleData(t *testing.T) { AppEUI: nc.OutRead.Entries[0].AppEUI, DevEUI: nc.OutRead.Entries[0].DevEUI, FCnt: nc.OutWholeCounter.FCnt, + FPort: 1, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, } diff --git a/core/definitions.go b/core/definitions.go index 4f6eced51..162bcca2c 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -7,6 +7,7 @@ package core type DataUpAppReq struct { Payload []byte `json:"payload"` FPort uint8 `json:"port,omitempty"` + FCnt uint32 `json:"counter"` Metadata []AppMetadata `json:"metadata"` } diff --git a/core/protos/application.proto b/core/protos/application.proto index 75be069b6..551db23e8 100644 --- a/core/protos/application.proto +++ b/core/protos/application.proto @@ -7,6 +7,7 @@ message DataAppReq { bytes AppEUI = 1; bytes DevEUI = 2; uint32 FPort = 10; + uint32 FCnt = 11; bytes Payload = 20; repeated Metadata Metadata = 30; } From 2f0ab5fb931ebcd2a31ea2025f2977731656b43b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 13:37:53 +0200 Subject: [PATCH 1303/2266] Forward FCnt up to MQTT Related to #126 --- core/adapters/mqtt/adapter.go | 1 + core/adapters/mqtt/adapter_test.go | 4 ++++ core/components/handler/handler.go | 3 +++ core/components/handler/handler_test.go | 11 +++++++++++ 4 files changed, 19 insertions(+) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index fa7f3f75e..c872abb44 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -34,6 +34,7 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . Payload: req.Payload, Metadata: core.ProtoMetaToAppMeta(req.Metadata...), FPort: uint8(req.FPort), + FCnt: req.FCnt, } if a.ctx != nil { diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index 0d291c4d3..ffced1de1 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -43,6 +43,8 @@ func TestHandleData(t *testing.T) { }, AppEUI: eui, DevEUI: eui, + FPort: 14, + FCnt: 200, } var wg sync.WaitGroup @@ -51,6 +53,8 @@ func TestHandleData(t *testing.T) { client.SubscribeDeviceUplink(eui, eui, func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { a.So(appEUI, ShouldResemble, eui) a.So(devEUI, ShouldResemble, eui) + a.So(dataUp.FPort, ShouldEqual, 14) + a.So(dataUp.FCnt, ShouldEqual, 200) a.So(dataUp.Payload, ShouldResemble, []byte{0x01, 0x02}) a.So(dataUp.Metadata[0].DataRate, ShouldEqual, "SF7BW125") wg.Done() diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 1842da561..020c8d5ad 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -534,6 +534,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) var metadata []*core.Metadata var fPort uint32 + var fCnt uint32 var payload []byte var firstTime time.Time @@ -565,6 +566,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu } firstTime = bundle.Time fPort = packet.FPort + fCnt = packet.FCnt stats.MarkMeter("handler.uplink.in.unique") } else { diff := bundle.Time.Sub(firstTime).Nanoseconds() @@ -582,6 +584,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu AppEUI: appEUI, DevEUI: devEUI, FPort: fPort, + FCnt: fCnt, Payload: payload, Metadata: metadata, }) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index a9e76fb7e..cec312a6a 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -469,6 +469,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt = devStorage.OutRead.Entry.FCntDown @@ -557,6 +558,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, FPort: 10, + FCnt: 14, } var wantFCnt = devStorage.OutRead.Entry.FCntDown @@ -690,6 +692,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt @@ -867,6 +870,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt uint32 @@ -935,6 +939,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt uint32 @@ -1027,6 +1032,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req1.AppEUI, DevEUI: req1.DevEUI, FPort: 1, + FCnt: 14, } var wantData2 = &core.DataAppReq{ Payload: payload2, @@ -1034,6 +1040,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req2.AppEUI, DevEUI: req2.DevEUI, FPort: 1, + FCnt: 35346, } var wantFCnt1 uint32 = 3 var wantFCnt2 uint32 = 11 @@ -1167,6 +1174,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt @@ -1244,6 +1252,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt = devStorage.OutRead.Entry.FCntDown + 1 @@ -1356,6 +1365,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt @@ -1460,6 +1470,7 @@ func TestHandleDataUp(t *testing.T) { AppEUI: req.AppEUI, DevEUI: req.DevEUI, FPort: 1, + FCnt: 14, } var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt From 15298272035b7ffa61086fff8ddc5c792b7873dd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 14:43:43 +0200 Subject: [PATCH 1304/2266] [ttnctl] Add ttnctl devices info command Also cleaned up tables in 'ttnctl devices' Resolves #131 --- ttnctl/cmd/device.go | 139 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 130 insertions(+), 9 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 1d4493229..a8106fb03 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "reflect" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/components/handler" @@ -54,33 +55,152 @@ var devicesCmd = &cobra.Command{ } ctx.Infof("Found %d personalized devices (ABP)", len(res.ABP)) + table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevAddr", "NwkSKey", "AppSKey", "FCntUp", "FCntDown") + table.AddRow("DevAddr", "FCntUp", "FCntDown") for _, device := range res.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) - nwkSKey := fmt.Sprintf("%X", device.NwkSKey) - appSKey := fmt.Sprintf("%X", device.AppSKey) - table.AddRow(devAddr, nwkSKey, appSKey, device.FCntUp, device.FCntDown) + table.AddRow(devAddr, device.FCntUp, device.FCntDown) } + + fmt.Println() fmt.Println(table) + fmt.Println() ctx.Infof("Found %d dynamic devices (OTAA)", len(res.OTAA)) table = uitable.New() table.MaxColWidth = 40 - table.AddRow("DevEUI", "DevAddr", "NwkSKey", "AppSKey", "AppKey", "FCntUp", "FCntDown") + table.AddRow("DevEUI", "DevAddr", "FCntUp", "FCntDown") for _, device := range res.OTAA { devEUI := fmt.Sprintf("%X", device.DevEUI) devAddr := fmt.Sprintf("%X", device.DevAddr) - nwkSKey := fmt.Sprintf("%X", device.NwkSKey) - appSKey := fmt.Sprintf("%X", device.AppSKey) - appKey := fmt.Sprintf("%X", device.AppKey) - table.AddRow(devEUI, devAddr, nwkSKey, appSKey, appKey, device.FCntUp, device.FCntDown) + table.AddRow(devEUI, devAddr, device.FCntUp, device.FCntDown) } + + fmt.Println() fmt.Println(table) + fmt.Println() + + ctx.Info("Run 'ttnctl devices info [DevAddr|DevEUI]' for more information about a specific device") + + }, +} + +// devicesInfoCmd represents the `devices info` command +var devicesInfoCmd = &cobra.Command{ + Use: "info [DevAddr|DevEUI]", + Short: "Show device information", + Long: `ttnctl devices info shows information about a specific device.`, + Run: func(cmd *cobra.Command, args []string) { + appEUI, err := util.Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + if len(args) != 1 { + ctx.Fatal("Missing DevAddr or DevEUI") + } + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + + manager := getHandlerManager() + res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ + Token: auth.AccessToken, + AppEUI: appEUI, + }) + if err != nil { + ctx.WithError(err).Fatal("Could not get device list") + } + + if devEUI, err := util.Parse64(args[0]); err == nil { + for _, device := range res.OTAA { + if reflect.DeepEqual(device.DevEUI, devEUI) { + fmt.Println("Dynamic device:") + + fmt.Println() + fmt.Printf(" DevEUI: %X\n", device.DevEUI) + fmt.Printf(" {%s}\n", cStyle(device.DevEUI)) + + fmt.Println() + fmt.Printf(" AppKey: %X\n", device.AppKey) + fmt.Printf(" {%s}\n", cStyle(device.AppKey)) + + if len(device.DevAddr) != 0 { + fmt.Println() + fmt.Println(" Activated with the following parameters:") + + fmt.Println() + fmt.Printf(" DevAddr: %X\n", device.DevAddr) + fmt.Printf(" {%s}\n", cStyle(device.DevAddr)) + + fmt.Println() + fmt.Printf(" NwkSKey: %X\n", device.NwkSKey) + fmt.Printf(" {%s}\n", cStyle(device.NwkSKey)) + + fmt.Println() + fmt.Printf(" AppSKey: %X\n", device.AppSKey) + fmt.Printf(" {%s}\n", cStyle(device.AppSKey)) + + fmt.Println() + fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) + } else { + fmt.Println() + fmt.Println(" Not yet activated") + } + + return + } + } + } + + if devAddr, err := util.Parse32(args[0]); err == nil { + for _, device := range res.ABP { + if reflect.DeepEqual(device.DevAddr, devAddr) { + fmt.Println("Personalized device:") + + fmt.Println() + fmt.Printf(" DevAddr: %X\n", device.DevAddr) + fmt.Printf(" {%s}\n", cStyle(device.DevAddr)) + + fmt.Println() + fmt.Printf(" NwkSKey: %X\n", device.NwkSKey) + fmt.Printf(" {%s}\n", cStyle(device.NwkSKey)) + + fmt.Println() + fmt.Printf(" AppSKey: %X\n", device.AppSKey) + fmt.Printf(" {%s}\n", cStyle(device.AppSKey)) + + fmt.Println() + fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) + return + } + } + } else { + ctx.Fatal("Invalid DevAddr or DevEUI") + } + + ctx.Info("Device not found") + }, } +func cStyle(bytes []byte) (output string) { + for i, b := range bytes { + if i != 0 { + output += ", " + } + output += fmt.Sprintf("0x%02X", b) + } + return +} + // devicesRegisterCmd represents the `device register` command var devicesRegisterCmd = &cobra.Command{ Use: "register [DevEUI] [AppKey]", @@ -186,5 +306,6 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ func init() { RootCmd.AddCommand(devicesCmd) devicesCmd.AddCommand(devicesRegisterCmd) + devicesCmd.AddCommand(devicesInfoCmd) devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) } From 3d63225618d658f74bbd1600fed0b5bda19e7f92 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 15:40:11 +0200 Subject: [PATCH 1305/2266] [ttnctl] Add applications use command Resolves #130 --- ttnctl/cmd/applications.go | 64 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 3877a9321..1b22f21e1 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -5,13 +5,18 @@ package cmd import ( "fmt" + "io/ioutil" "net/http" "net/url" + "path" "strings" + "gopkg.in/yaml.v2" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/gosuri/uitable" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -169,9 +174,68 @@ var applicationsAuthorizeCmd = &cobra.Command{ }, } +var applicationsUseCmd = &cobra.Command{ + Use: "use [eui]", + Short: "Set an application as active", + Long: `ttnctl applications use marks an application as the currently active application in ttnctl.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.Help() + return + } + + appEUI, err := util.Parse64(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + // Determine config file + cFile := viper.ConfigFileUsed() + if cFile == "" { + dir, err := homedir.Dir() + if err != nil { + ctx.WithError(err).Fatal("Could not get homedir") + } + expanded, err := homedir.Expand(dir) + if err != nil { + ctx.WithError(err).Fatal("Could not get homedir") + } + cFile = path.Join(expanded, ".ttnctl.yaml") + } + + c := make(map[string]interface{}) + + // Read config file + bytes, err := ioutil.ReadFile(cFile) + if err == nil { + err = yaml.Unmarshal(bytes, &c) + } + if err != nil { + ctx.Warnf("Could not read configuration file, will just create a new one") + } + + // Update app + c["app-eui"] = fmt.Sprintf("%X", appEUI) + + // Write config file + d, err := yaml.Marshal(&c) + if err != nil { + ctx.Fatal("Could not generate configiguration file contents") + } + err = ioutil.WriteFile(cFile, d, 0644) + if err != nil { + ctx.WithError(err).Fatal("Could not write configiguration file") + } + + ctx.Infof("You are now using application %X.", appEUI) + + }, +} + func init() { RootCmd.AddCommand(applicationsCmd) applicationsCmd.AddCommand(applicationsCreateCmd) applicationsCmd.AddCommand(applicationsDeleteCmd) applicationsCmd.AddCommand(applicationsAuthorizeCmd) + applicationsCmd.AddCommand(applicationsUseCmd) } From 07d8bc22aea12f6d6a99f0a027ad579ac39e9f86 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 15:40:37 +0200 Subject: [PATCH 1306/2266] [ttnctl] Only print config file info when DEBUG=true --- ttnctl/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 1e8249a85..575e75853 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -78,7 +78,7 @@ func initConfig() { viper.AutomaticEnv() // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { + if err := viper.ReadInConfig(); err == nil && viper.GetBool("debug") { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } From 045c3f2a9c5c2f827081f2fa5c966d1a278e9628 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 6 Apr 2016 15:50:41 +0200 Subject: [PATCH 1307/2266] [ttnctl] Move app-eui logic to func util.GetAppEUI --- ttnctl/cmd/device.go | 21 +++++---------------- ttnctl/cmd/downlink.go | 6 +----- ttnctl/cmd/join.go | 5 +---- ttnctl/cmd/root.go | 2 +- ttnctl/cmd/subscribe.go | 8 ++------ ttnctl/util/applications.go | 13 +++++++++++++ ttnctl/util/mqtt.go | 5 +---- 7 files changed, 24 insertions(+), 36 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index a8106fb03..f1741648f 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -32,10 +32,8 @@ var devicesCmd = &cobra.Command{ Short: "Manage devices on the Handler", Long: `ttnctl devices retrieves a list of devices that your application registered on the Handler.`, Run: func(cmd *cobra.Command, args []string) { - appEUI, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + + appEUI := util.GetAppEUI(ctx) auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) if err != nil { @@ -93,10 +91,7 @@ var devicesInfoCmd = &cobra.Command{ Short: "Show device information", Long: `ttnctl devices info shows information about a specific device.`, Run: func(cmd *cobra.Command, args []string) { - appEUI, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + appEUI := util.GetAppEUI(ctx) if len(args) != 1 { ctx.Fatal("Missing DevAddr or DevEUI") @@ -212,10 +207,7 @@ var devicesRegisterCmd = &cobra.Command{ return } - appEUI, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + appEUI := util.GetAppEUI(ctx) devEUI, err := util.Parse64(args[0]) if err != nil { @@ -260,10 +252,7 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ return } - appEUI, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + appEUI := util.GetAppEUI(ctx) devAddr, err := util.Parse32(args[0]) if err != nil { diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 2e73e1b18..b04fa476a 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -7,7 +7,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // downlinkCmd represents the `downlink` command @@ -23,10 +22,7 @@ expected to define a Time To Live in a handy format, for instance: "1h" for one ctx.Fatal("Insufficient arguments") } - appEUI, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + appEUI := util.GetAppEUI(ctx) devEUI, err := util.Parse64(args[0]) if err != nil { diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 744398fac..e688bf416 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -44,10 +44,7 @@ var joinCmd = &cobra.Command{ var appKey lorawan.AES128Key copy(appKey[:], appKeyRaw) - appEUIRaw, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid appEUI: %s", err) - } + appEUIRaw := util.GetAppEUI(ctx) var appEUI lorawan.EUI64 copy(appEUI[:], appEUIRaw) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 575e75853..57daf2f47 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -58,7 +58,7 @@ func init() { RootCmd.PersistentFlags().String("mqtt-broker", "staging.thethingsnetwork.org:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) - RootCmd.PersistentFlags().String("app-eui", "0102030405060708", "The app EUI to use") + RootCmd.PersistentFlags().String("app-eui", "", "The app EUI to use") viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 340f5f8b6..732fc190c 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -12,7 +12,6 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var subscribeCmd = &cobra.Command{ @@ -25,14 +24,11 @@ specific device. By default you will receive messages from all devices of your application.`, Run: func(cmd *cobra.Command, args []string) { - appEUI, err := util.Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + appEUI := util.GetAppEUI(ctx) var devEUI []byte if len(args) > 0 { - devEUI, err = util.Parse64(args[0]) + devEUI, err := util.Parse64(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } diff --git a/ttnctl/util/applications.go b/ttnctl/util/applications.go index 20b35604b..248e9a417 100644 --- a/ttnctl/util/applications.go +++ b/ttnctl/util/applications.go @@ -17,6 +17,19 @@ type App struct { Valid bool `json:"valid"` } +func GetAppEUI(ctx log.Interface) []byte { + if viper.GetString("app-eui") == "" { + ctx.Fatal("AppEUI not set. You probably want to run 'ttnctl applications use [appEUI]' to do this.") + } + + appEUI, err := Parse64(viper.GetString("app-eui")) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + + return appEUI +} + func GetApplications(ctx log.Interface) ([]*App, error) { server := viper.GetString("ttn-account-server") uri := fmt.Sprintf("%s/applications", server) diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index baa0edf57..316e01d67 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -13,10 +13,7 @@ import ( // ConnectMQTTClient connects a new MQTT clients with the specified credentials func ConnectMQTTClient(ctx log.Interface) mqtt.Client { - appEUI, err := Parse64(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } + appEUI := GetAppEUI(ctx) apps, err := GetApplications(ctx) if err != nil { From 66ffc3d50b0a58c4c365789d9e83d169b8a606a2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 7 Apr 2016 17:14:05 +0200 Subject: [PATCH 1308/2266] Add Channel and GatewayEUI to Metadata --- core/components/router/router.go | 2 + core/components/router/router_test.go | 117 ++++++++++++----------- core/core.pb.go | 128 ++++++++++++++++++++------ core/definitions.go | 2 + core/protos/core.proto | 2 + 5 files changed, 172 insertions(+), 79 deletions(-) diff --git a/core/components/router/router.go b/core/components/router/router.go index d26b2a3c4..ec9b07fb8 100644 --- a/core/components/router/router.go +++ b/core/components/router/router.go @@ -4,6 +4,7 @@ package router import ( + "fmt" "net" "strings" "sync" @@ -234,6 +235,7 @@ func (r component) HandleData(_ context.Context, req *core.DataRouterReq) (*core func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Metadata, error) { ctx := r.Ctx.WithField("GatewayID", gid) + metadata.GatewayEUI = fmt.Sprintf("%X", gid) metadata.ServerTime = time.Now().UTC().Format(time.RFC3339Nano) // Add Gateway location metadata diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go index 0dc107d92..7fb4d4332 100644 --- a/core/components/router/router_test.go +++ b/core/components/router/router_test.go @@ -506,10 +506,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -586,10 +587,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 = 1 @@ -669,10 +671,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 = 1 @@ -823,10 +826,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -976,10 +980,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -1057,10 +1062,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -1143,10 +1149,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -1248,10 +1255,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -1352,10 +1360,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -1457,10 +1466,11 @@ func TestHandleData(t *testing.T) { var wantBrReq = &core.DataBrokerReq{ Payload: req.Payload, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 @@ -1538,10 +1548,11 @@ func TestHandleJoin(t *testing.T) { DevNonce: req.DevNonce, MIC: req.MIC, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 = 1 @@ -1619,10 +1630,11 @@ func TestHandleJoin(t *testing.T) { DevNonce: req.DevNonce, MIC: req.MIC, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 = 1 @@ -2005,10 +2017,11 @@ func TestHandleJoin(t *testing.T) { DevNonce: req.DevNonce, MIC: req.MIC, Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, + Altitude: gt.OutRead.Entry.Metadata.Altitude, + Longitude: gt.OutRead.Entry.Metadata.Longitude, + Latitude: gt.OutRead.Entry.Metadata.Latitude, + Frequency: req.Metadata.Frequency, + GatewayEUI: "0102030405060708", }, } var wantStore uint16 diff --git a/core/core.pb.go b/core/core.pb.go index 16c0407cc..0cf07130f 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -31,6 +31,8 @@ type Metadata struct { Modulation string `protobuf:"bytes,13,opt,name=Modulation,json=modulation,proto3" json:"Modulation,omitempty"` InvPolarity bool `protobuf:"varint,14,opt,name=InvPolarity,json=invPolarity,proto3" json:"InvPolarity,omitempty"` Power uint32 `protobuf:"varint,15,opt,name=Power,json=power,proto3" json:"Power,omitempty"` + Channel uint32 `protobuf:"varint,16,opt,name=Channel,json=channel,proto3" json:"Channel,omitempty"` + GatewayEUI string `protobuf:"bytes,20,opt,name=GatewayEUI,json=gatewayEUI,proto3" json:"GatewayEUI,omitempty"` Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` @@ -156,6 +158,21 @@ func (m *Metadata) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintCore(data, i, uint64(m.Power)) } + if m.Channel != 0 { + data[i] = 0x80 + i++ + data[i] = 0x1 + i++ + i = encodeVarintCore(data, i, uint64(m.Channel)) + } + if len(m.GatewayEUI) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintCore(data, i, uint64(len(m.GatewayEUI))) + i += copy(data[i:], m.GatewayEUI) + } if m.Altitude != 0 { data[i] = 0xa8 i++ @@ -300,6 +317,13 @@ func (m *Metadata) Size() (n int) { if m.Power != 0 { n += 1 + sovCore(uint64(m.Power)) } + if m.Channel != 0 { + n += 2 + sovCore(uint64(m.Channel)) + } + l = len(m.GatewayEUI) + if l > 0 { + n += 2 + l + sovCore(uint64(l)) + } if m.Altitude != 0 { n += 2 + sovCore(uint64(m.Altitude)) } @@ -689,6 +713,54 @@ func (m *Metadata) Unmarshal(data []byte) error { break } } + case 16: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + m.Channel = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Channel |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 20: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEUI", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCore + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCore + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayEUI = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) @@ -989,31 +1061,33 @@ var ( ) var fileDescriptorCore = []byte{ - // 406 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x92, 0x4f, 0x8e, 0xd3, 0x30, - 0x18, 0xc5, 0x49, 0x9b, 0xb4, 0x89, 0x43, 0x01, 0x59, 0xfc, 0xf9, 0x84, 0x50, 0xa9, 0xba, 0x62, - 0x85, 0x44, 0x39, 0x01, 0xa4, 0xaa, 0x84, 0xd4, 0x4a, 0x95, 0xcb, 0x82, 0xad, 0x69, 0x4c, 0xb1, - 0x94, 0xc6, 0xc1, 0x71, 0x8a, 0xc2, 0x49, 0xd0, 0x9c, 0x68, 0x96, 0x73, 0x84, 0xd1, 0xcc, 0x45, - 0xc6, 0x9f, 0x93, 0xb4, 0x9d, 0x4d, 0x17, 0x96, 0xfc, 0xbd, 0x57, 0xfb, 0xfd, 0x5e, 0x63, 0x42, - 0xb6, 0x4a, 0x8b, 0x8f, 0x85, 0x56, 0x46, 0x51, 0x1f, 0xf7, 0xd3, 0x2b, 0x9f, 0x84, 0x2b, 0x61, - 0x78, 0xca, 0x0d, 0xa7, 0x40, 0x86, 0xf3, 0xca, 0xd4, 0xec, 0xc7, 0x27, 0xf0, 0x26, 0xde, 0x87, - 0x11, 0x1b, 0xa6, 0xcd, 0x78, 0x72, 0x66, 0xd0, 0x3b, 0x77, 0x66, 0xf4, 0x1d, 0x89, 0x16, 0x5a, - 0xfc, 0xa9, 0x44, 0xbe, 0xad, 0xa1, 0x6f, 0xbd, 0x1e, 0x8b, 0x7e, 0x75, 0x02, 0x7d, 0x4b, 0xc2, - 0xb9, 0xbd, 0x99, 0x71, 0x23, 0xc0, 0xb7, 0x66, 0xc4, 0xc2, 0xb4, 0x9d, 0xe9, 0x98, 0x90, 0x44, - 0xa5, 0x32, 0xdf, 0x39, 0x37, 0x70, 0xae, 0x05, 0xec, 0x14, 0xbc, 0xf9, 0xbb, 0xdc, 0x8b, 0xd2, - 0xf0, 0x7d, 0x01, 0x03, 0x97, 0x1a, 0x99, 0x4e, 0xa0, 0x94, 0xf8, 0xac, 0x2c, 0x25, 0x0c, 0xad, - 0x11, 0x30, 0x5f, 0xdb, 0x3d, 0x6a, 0xcb, 0x32, 0xd7, 0x10, 0x3a, 0x0c, 0x3f, 0xb3, 0x7b, 0x3a, - 0x21, 0xf1, 0x9a, 0xd7, 0x99, 0xe2, 0xe9, 0x46, 0xfe, 0x13, 0x10, 0xb9, 0x7b, 0xe2, 0xe2, 0x24, - 0xe1, 0x29, 0xcc, 0x01, 0xe2, 0x08, 0x7c, 0x8c, 0xc0, 0xbe, 0x6c, 0x91, 0xfc, 0xe6, 0x32, 0x87, - 0xb8, 0xe9, 0xab, 0x9b, 0x11, 0xa9, 0x12, 0x96, 0x6c, 0x0c, 0x37, 0x55, 0x09, 0x4f, 0x5d, 0x78, - 0xb4, 0xed, 0x04, 0xec, 0xb4, 0x52, 0x69, 0x95, 0x71, 0x23, 0x55, 0x0e, 0xa3, 0xa6, 0xd3, 0xfe, - 0xa8, 0x20, 0xcd, 0xb7, 0xfc, 0xb0, 0x56, 0x19, 0xd7, 0xd2, 0xd4, 0xf0, 0xcc, 0xfe, 0x20, 0x64, - 0xb1, 0x3c, 0x49, 0xf4, 0x25, 0x09, 0xd6, 0xea, 0xaf, 0xd0, 0xf0, 0xdc, 0xe5, 0x06, 0x05, 0x0e, - 0xf8, 0x3f, 0x7e, 0xc9, 0x8c, 0x34, 0x55, 0x2a, 0xe0, 0x95, 0x0b, 0x0d, 0x79, 0x3b, 0x23, 0xd1, - 0x52, 0xe5, 0xbb, 0xc6, 0x7c, 0xdd, 0x7c, 0x81, 0xac, 0x13, 0xf0, 0xe4, 0x92, 0xb7, 0x27, 0xdf, - 0x38, 0x33, 0xcc, 0xda, 0x19, 0x69, 0x37, 0x42, 0x1f, 0x84, 0x76, 0xfd, 0xdf, 0x37, 0xb4, 0xe5, - 0x51, 0x99, 0x0a, 0x32, 0xc2, 0x5e, 0xe5, 0xf1, 0x81, 0x9c, 0x63, 0x78, 0x97, 0x30, 0x7a, 0x97, - 0x30, 0xfa, 0x8f, 0x31, 0xbe, 0xbe, 0xb8, 0xbe, 0x1b, 0x7b, 0x37, 0x76, 0xdd, 0xda, 0xf5, 0xff, - 0x7e, 0xfc, 0xe4, 0xe7, 0xc0, 0x3d, 0xd1, 0xcf, 0x0f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8e, 0xbb, - 0x81, 0x38, 0xb0, 0x02, 0x00, 0x00, + // 434 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x52, 0xd1, 0x6e, 0xd3, 0x30, + 0x14, 0xa5, 0x5d, 0xba, 0x26, 0x2e, 0x85, 0xc9, 0x1a, 0x70, 0x85, 0x50, 0x99, 0xf6, 0xc4, 0x13, + 0x12, 0xe3, 0x0b, 0x20, 0x63, 0x68, 0x52, 0x27, 0x55, 0x2e, 0x48, 0xbc, 0x9a, 0xc4, 0x14, 0x4b, + 0xae, 0x1d, 0x1c, 0x67, 0x53, 0xf8, 0x12, 0x3e, 0x89, 0x47, 0x1e, 0xf8, 0x00, 0x04, 0x3f, 0x82, + 0xaf, 0x9d, 0xa4, 0xdb, 0x4b, 0x1f, 0x22, 0xf9, 0x9c, 0x93, 0x7b, 0xcf, 0xb9, 0xd7, 0x26, 0xa4, + 0x30, 0x56, 0xbc, 0xac, 0xac, 0x71, 0x86, 0x26, 0x78, 0x3e, 0xfd, 0x9d, 0x90, 0xf4, 0x4a, 0x38, + 0x5e, 0x72, 0xc7, 0x29, 0x90, 0xe9, 0x79, 0xe3, 0x5a, 0xf6, 0xe9, 0x15, 0x8c, 0x4e, 0x46, 0x2f, + 0xe6, 0x6c, 0x5a, 0x46, 0xb8, 0x53, 0xce, 0x60, 0x7c, 0x5b, 0x39, 0xa3, 0xcf, 0x48, 0x76, 0x61, + 0xc5, 0xb7, 0x46, 0xe8, 0xa2, 0x85, 0x03, 0xaf, 0x8d, 0x59, 0xf6, 0xa5, 0x27, 0xe8, 0x53, 0x92, + 0x9e, 0xfb, 0xce, 0x8c, 0x3b, 0x01, 0x89, 0x17, 0x33, 0x96, 0x96, 0x1d, 0xa6, 0x0b, 0x42, 0x72, + 0x53, 0x4a, 0xbd, 0x09, 0xea, 0x24, 0xa8, 0x3e, 0x60, 0xcf, 0x60, 0xe7, 0x0f, 0x72, 0x2b, 0x6a, + 0xc7, 0xb7, 0x15, 0x1c, 0x06, 0xd7, 0xcc, 0xf5, 0x04, 0xa5, 0x24, 0x61, 0x75, 0x2d, 0x61, 0xea, + 0x85, 0x09, 0x4b, 0xac, 0x3f, 0x23, 0xb7, 0xac, 0xb5, 0x85, 0x34, 0xc4, 0x48, 0x94, 0x3f, 0xd3, + 0x13, 0x32, 0x5b, 0xf1, 0x56, 0x19, 0x5e, 0xae, 0xe5, 0x77, 0x01, 0x59, 0xe8, 0x33, 0xab, 0x76, + 0x14, 0x56, 0xa1, 0x0f, 0x90, 0x90, 0x20, 0x41, 0x0b, 0x9c, 0x97, 0x5d, 0xe4, 0x5f, 0xb9, 0xd4, + 0x30, 0x8b, 0xf3, 0xda, 0x08, 0x31, 0x55, 0xce, 0xf2, 0xb5, 0xe3, 0xae, 0xa9, 0xe1, 0x7e, 0x30, + 0xcf, 0x8a, 0x9e, 0xc0, 0x99, 0xae, 0x4c, 0xd9, 0x28, 0xee, 0xa4, 0xd1, 0x30, 0x8f, 0x33, 0x6d, + 0x07, 0x06, 0xd3, 0x5c, 0xea, 0xeb, 0x95, 0x51, 0xdc, 0x4a, 0xd7, 0xc2, 0x03, 0xff, 0x43, 0xca, + 0x66, 0x72, 0x47, 0xd1, 0x63, 0x32, 0x59, 0x99, 0x1b, 0x61, 0xe1, 0x61, 0xf0, 0x9d, 0x54, 0x08, + 0x30, 0x8f, 0xb7, 0xd7, 0x5a, 0x28, 0x38, 0x8a, 0x79, 0x8a, 0x08, 0xd1, 0xf1, 0xbd, 0xdf, 0xd6, + 0x0d, 0x6f, 0xdf, 0x7d, 0xbc, 0x84, 0xe3, 0xe8, 0xb8, 0x19, 0x18, 0xbc, 0x81, 0x37, 0xca, 0x49, + 0xd7, 0x94, 0x02, 0x1e, 0x85, 0xb8, 0x29, 0xef, 0x30, 0xce, 0xb2, 0x34, 0x7a, 0x13, 0xc5, 0xc7, + 0xf1, 0xee, 0x54, 0x4f, 0x60, 0xe5, 0x92, 0x77, 0x95, 0x4f, 0x82, 0x98, 0xaa, 0x0e, 0xa3, 0xeb, + 0x5a, 0xd8, 0x6b, 0x61, 0xc3, 0xe6, 0x9e, 0x47, 0xd7, 0x7a, 0x60, 0x4e, 0x05, 0x99, 0xe3, 0x46, + 0xea, 0xe1, 0x69, 0xdd, 0x8e, 0x31, 0xda, 0x17, 0x63, 0xbc, 0x2f, 0xc6, 0xc1, 0xdd, 0x18, 0x6f, + 0x8f, 0x7e, 0xfe, 0x5d, 0x8c, 0x7e, 0xf9, 0xef, 0x8f, 0xff, 0x7e, 0xfc, 0x5b, 0xdc, 0xfb, 0x7c, + 0x18, 0x1e, 0xf7, 0xeb, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x23, 0x2c, 0xd8, 0xcc, 0xea, 0x02, + 0x00, 0x00, } diff --git a/core/definitions.go b/core/definitions.go index 162bcca2c..4e0328ea5 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -23,12 +23,14 @@ type AppMetadata struct { CodingRate string `json:"codingrate"` Timestamp uint32 `json:"gateway_timestamp"` Time string `json:"gateway_time,omitempty"` + Channel uint32 `json:"channel"` ServerTime string `json:"server_time"` Rssi int32 `json:"rssi"` Lsnr float32 `json:"lsnr"` RFChain uint32 `json:"rfchain"` CRCStatus int32 `json:"crc"` Modulation string `json:"modulation"` + GatewayEUI string `json:"gateway_eui"` Altitude int32 `json:"altitude"` Longitude float32 `json:"longitude"` Latitude float32 `json:"latitude"` diff --git a/core/protos/core.proto b/core/protos/core.proto index 0107476fb..01bef9f8b 100644 --- a/core/protos/core.proto +++ b/core/protos/core.proto @@ -19,7 +19,9 @@ message Metadata { string Modulation = 13; bool InvPolarity = 14; uint32 Power = 15; + uint32 Channel = 16; + string GatewayEUI = 20; int32 Altitude = 21; float Longitude = 22; float Latitude = 23; From 2c0af623ceb3b2d3242a5a29b5b939d2aa3ee999 Mon Sep 17 00:00:00 2001 From: ktorz Date: Fri, 8 Apr 2016 20:02:59 +0200 Subject: [PATCH 1309/2266] Fix concurrent access to the router storage, causing a same broker to be stored twiced in some cases --- core/components/router/storage.go | 35 ++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/core/components/router/storage.go b/core/components/router/storage.go index d5770b7f8..8d872573b 100644 --- a/core/components/router/storage.go +++ b/core/components/router/storage.go @@ -48,8 +48,15 @@ func NewBrkStorage(name string, delay time.Duration) (BrkStorage, error) { // read implements the router.BrkStorage interface func (s *brkStorage) read(devAddr []byte) ([]brkEntry, error) { - s.Lock() - defer s.Unlock() + return s._read(devAddr, true) +} + +// _read implements the router.BrkStorage interface logic but gives control over mutex to the caller +func (s *brkStorage) _read(devAddr []byte, shouldLock bool) ([]brkEntry, error) { + if shouldLock { + s.Lock() + defer s.Unlock() + } itf, err := s.db.Read(devAddr, &brkEntry{}, dbBrokers) if err != nil { return nil, err @@ -85,7 +92,29 @@ func (s *brkStorage) read(devAddr []byte) ([]brkEntry, error) { func (s *brkStorage) create(entry brkEntry) error { s.Lock() defer s.Unlock() - entry.until = time.Now().Add(s.ExpiryDelay) + + entries, err := s._read(entry.DevAddr, false) + if err != nil && err.(errors.Failure).Nature != errors.NotFound { + return err + } + + var updates []encoding.BinaryMarshaler + until, found := time.Now().Add(s.ExpiryDelay), false + for i, e := range entries { + if entry.BrokerIndex == e.BrokerIndex { + // Entry already there, just update the TTL + entries[i].until = until + found = true + } + updates = append(updates, e) + } + + if found { // The entry was already existing + return s.db.Update(entry.DevAddr, updates, dbBrokers) + } + + // Otherwise, we just happend it + entry.until = until return s.db.Append(entry.DevAddr, []encoding.BinaryMarshaler{entry}, dbBrokers) } From 64c578aaf4fe6a44d2b556c9526b14c9f54bf876 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Apr 2016 11:28:46 +0200 Subject: [PATCH 1310/2266] [proto] Regenerate proto code --- Makefile | 8 +- core/application.pb.go | 104 +++++------- core/broker.pb.go | 100 +++++------- core/broker_manager.pb.go | 160 ++++++++----------- core/handler.pb.go | 252 ++++++++++++----------------- core/handler_manager.pb.go | 320 +++++++++++++++---------------------- core/lorawan.pb.go | 160 ++++++++----------- core/router.pb.go | 140 +++++++--------- 8 files changed, 503 insertions(+), 741 deletions(-) diff --git a/Makefile b/Makefile index a177042c8..87ecc62e5 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps test-deps test fmt vet cover build docker package +.PHONY: all clean deps test-deps build-deps proto test fmt vet cover build docker package all: clean deps build package @@ -40,6 +40,12 @@ deps: test-deps: $(GOCMD) get -d -v $(TEST_DEPS) +proto-deps: + $(GOCMD) get -v github.com/gogo/protobuf/protoc-gen-gofast + +proto: + find core/protos -name '*.proto' | xargs protoc --gofast_out=plugins=grpc:./core -I=core/protos + cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi $(GOCMD) get github.com/mattn/goveralls diff --git a/core/application.pb.go b/core/application.pb.go index 170ea2ddc..254990ab6 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -249,21 +249,17 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } if m.FPort != 0 { data[i] = 0x50 @@ -275,15 +271,13 @@ func (m *DataAppReq) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintApplication(data, i, uint64(m.FCnt)) } - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintApplication(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } + if len(m.Payload) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { @@ -335,21 +329,17 @@ func (m *JoinAppReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { @@ -416,17 +406,13 @@ func encodeVarintApplication(data []byte, offset int, v uint64) int { func (m *DataAppReq) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) } if m.FPort != 0 { n += 1 + sovApplication(uint64(m.FPort)) @@ -434,11 +420,9 @@ func (m *DataAppReq) Size() (n int) { if m.FCnt != 0 { n += 1 + sovApplication(uint64(m.FCnt)) } - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 2 + l + sovApplication(uint64(l)) - } + l = len(m.Payload) + if l > 0 { + n += 2 + l + sovApplication(uint64(l)) } if len(m.Metadata) > 0 { for _, e := range m.Metadata { @@ -458,17 +442,13 @@ func (m *DataAppRes) Size() (n int) { func (m *JoinAppReq) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovApplication(uint64(l)) } if len(m.Metadata) > 0 { for _, e := range m.Metadata { diff --git a/core/broker.pb.go b/core/broker.pb.go index 7afc3fa8b..7af5a9a60 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -299,37 +299,29 @@ func (m *JoinBrokerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.DevNonce != nil { - if len(m.DevNonce) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } + if len(m.DevNonce) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) } - if m.MIC != nil { - if len(m.MIC) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } + if len(m.MIC) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) } if m.Metadata != nil { data[i] = 0x2a @@ -369,13 +361,11 @@ func (m *JoinBrokerRes) MarshalTo(data []byte) (int, error) { } i += n6 } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } if m.Metadata != nil { data[i] = 0x1a @@ -448,29 +438,21 @@ func (m *DataBrokerRes) Size() (n int) { func (m *JoinBrokerReq) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) } - if m.DevNonce != nil { - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) } - if m.MIC != nil { - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() @@ -486,11 +468,9 @@ func (m *JoinBrokerRes) Size() (n int) { l = m.Payload.Size() n += 1 + l + sovBroker(uint64(l)) } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index 30d6f4c1a..a4908b3a7 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -243,13 +243,11 @@ func (m *ValidateOTAABrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } if len(m.NetAddress) > 0 { data[i] = 0x1a @@ -299,13 +297,11 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } if len(m.NetAddress) > 0 { data[i] = 0x1a @@ -313,21 +309,17 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) i += copy(data[i:], m.NetAddress) } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } + if len(m.NwkSKey) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } return i, nil } @@ -365,29 +357,23 @@ func (m *BrokerDevice) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } return i, nil } @@ -413,13 +399,11 @@ func (m *ValidateTokenBrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } return i, nil } @@ -476,11 +460,9 @@ func (m *ValidateOTAABrokerReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } l = len(m.NetAddress) if l > 0 { @@ -502,27 +484,21 @@ func (m *UpsertABPBrokerReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } l = len(m.NetAddress) if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } return n } @@ -536,23 +512,17 @@ func (m *UpsertABPBrokerRes) Size() (n int) { func (m *BrokerDevice) Size() (n int) { var l int _ = l - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } return n } @@ -564,11 +534,9 @@ func (m *ValidateTokenBrokerReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovBrokerManager(uint64(l)) } return n } diff --git a/core/handler.pb.go b/core/handler.pb.go index 0dc03ce82..52569d9b4 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -271,21 +271,17 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } if m.FCnt != 0 { data[i] = 0x50 @@ -302,15 +298,13 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandler(data, i, uint64(m.MType)) } - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } + if len(m.Payload) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) } if m.Metadata != nil { data[i] = 0xf2 @@ -384,31 +378,25 @@ func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } + if len(m.Payload) > 0 { + data[i] = 0xa2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) } if len(m.TTL) > 0 { data[i] = 0xf2 @@ -454,37 +442,29 @@ func (m *JoinHandlerReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.DevNonce != nil { - if len(m.DevNonce) > 0 { - data[i] = 0x52 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } + if len(m.DevNonce) > 0 { + data[i] = 0x52 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) } - if m.MIC != nil { - if len(m.MIC) > 0 { - data[i] = 0x5a - i++ - i = encodeVarintHandler(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } + if len(m.MIC) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) } if m.Metadata != nil { data[i] = 0xf2 @@ -516,13 +496,11 @@ func (m *JoinHandlerRes) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } if m.Payload != nil { data[i] = 0xa2 @@ -536,15 +514,13 @@ func (m *JoinHandlerRes) MarshalTo(data []byte) (int, error) { } i += n5 } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0xaa - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } + if len(m.NwkSKey) > 0 { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } if m.Metadata != nil { data[i] = 0xf2 @@ -591,17 +567,13 @@ func encodeVarintHandler(data []byte, offset int, v uint64) int { func (m *DataUpHandlerReq) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } if m.FCnt != 0 { n += 1 + sovHandler(uint64(m.FCnt)) @@ -612,11 +584,9 @@ func (m *DataUpHandlerReq) Size() (n int) { if m.MType != 0 { n += 1 + sovHandler(uint64(m.MType)) } - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } + l = len(m.Payload) + if l > 0 { + n += 2 + l + sovHandler(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() @@ -642,23 +612,17 @@ func (m *DataUpHandlerRes) Size() (n int) { func (m *DataDownHandlerReq) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } + l = len(m.Payload) + if l > 0 { + n += 2 + l + sovHandler(uint64(l)) } l = len(m.TTL) if l > 0 { @@ -676,29 +640,21 @@ func (m *DataDownHandlerRes) Size() (n int) { func (m *JoinHandlerReq) Size() (n int) { var l int _ = l - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } - if m.DevNonce != nil { - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } - if m.MIC != nil { - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() @@ -710,21 +666,17 @@ func (m *JoinHandlerReq) Size() (n int) { func (m *JoinHandlerRes) Size() (n int) { var l int _ = l - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) } if m.Payload != nil { l = m.Payload.Size() n += 2 + l + sovHandler(uint64(l)) } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } + l = len(m.NwkSKey) + if l > 0 { + n += 2 + l + sovHandler(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index 3f523a775..de1404b4e 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -282,29 +282,23 @@ func (m *UpsertOTAAHandlerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.AppKey != nil { - if len(m.AppKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } + if len(m.AppKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) } return i, nil } @@ -348,37 +342,29 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } + if len(m.NwkSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } - if m.AppSKey != nil { - if len(m.AppSKey) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } + if len(m.AppSKey) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) } return i, nil } @@ -422,13 +408,11 @@ func (m *ListDevicesHandlerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } return i, nil } @@ -490,29 +474,23 @@ func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } - if m.AppSKey != nil { - if len(m.AppSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } + if len(m.AppSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) } if m.FCntUp != 0 { data[i] = 0x28 @@ -542,45 +520,35 @@ func (m *HandlerOTAADevice) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } - if m.NwkSKey != nil { - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) } - if m.AppSKey != nil { - if len(m.AppSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } + if len(m.AppSKey) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) + i += copy(data[i:], m.AppSKey) } - if m.AppKey != nil { - if len(m.AppKey) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } + if len(m.AppKey) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) } if m.FCntUp != 0 { data[i] = 0x38 @@ -629,23 +597,17 @@ func (m *UpsertOTAAHandlerReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppKey != nil { - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } @@ -663,29 +625,21 @@ func (m *UpsertABPHandlerReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppSKey != nil { - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } @@ -703,11 +657,9 @@ func (m *ListDevicesHandlerReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } @@ -733,23 +685,17 @@ func (m *ListDevicesHandlerRes) Size() (n int) { func (m *HandlerABPDevice) Size() (n int) { var l int _ = l - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppSKey != nil { - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } if m.FCntUp != 0 { n += 1 + sovHandlerManager(uint64(m.FCntUp)) @@ -763,35 +709,25 @@ func (m *HandlerABPDevice) Size() (n int) { func (m *HandlerOTAADevice) Size() (n int) { var l int _ = l - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.NwkSKey != nil { - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppSKey != nil { - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppSKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppKey != nil { - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } if m.FCntUp != 0 { n += 1 + sovHandlerManager(uint64(m.FCntUp)) diff --git a/core/lorawan.pb.go b/core/lorawan.pb.go index 7971d7af9..ea6236aa0 100644 --- a/core/lorawan.pb.go +++ b/core/lorawan.pb.go @@ -176,13 +176,11 @@ func (m *LoRaWANData) MarshalTo(data []byte) (int, error) { } i += n2 } - if m.MIC != nil { - if len(m.MIC) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } + if len(m.MIC) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) } return i, nil } @@ -245,13 +243,11 @@ func (m *LoRaWANMACPayload) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintLorawan(data, i, uint64(m.FPort)) } - if m.FRMPayload != nil { - if len(m.FRMPayload) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FRMPayload))) - i += copy(data[i:], m.FRMPayload) - } + if len(m.FRMPayload) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FRMPayload))) + i += copy(data[i:], m.FRMPayload) } return i, nil } @@ -271,13 +267,11 @@ func (m *LoRaWANFHDR) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevAddr != nil { - if len(m.DevAddr) > 0 { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) } if m.FCtrl != nil { data[i] = 0x12 @@ -360,13 +354,11 @@ func (m *LoRaWANFCtrl) MarshalTo(data []byte) (int, error) { } i++ } - if m.FOptsLen != nil { - if len(m.FOptsLen) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FOptsLen))) - i += copy(data[i:], m.FOptsLen) - } + if len(m.FOptsLen) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FOptsLen))) + i += copy(data[i:], m.FOptsLen) } return i, nil } @@ -386,29 +378,23 @@ func (m *LoRaWANJoinRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevNonce != nil { - if len(m.DevNonce) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } + if len(m.DevNonce) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) } return i, nil } @@ -428,13 +414,11 @@ func (m *LoRaWANJoinAccept) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.Payload != nil { - if len(m.Payload) > 0 { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) } return i, nil } @@ -505,11 +489,9 @@ func (m *LoRaWANData) Size() (n int) { l = m.MACPayload.Size() n += 1 + l + sovLorawan(uint64(l)) } - if m.MIC != nil { - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } return n } @@ -536,11 +518,9 @@ func (m *LoRaWANMACPayload) Size() (n int) { if m.FPort != 0 { n += 1 + sovLorawan(uint64(m.FPort)) } - if m.FRMPayload != nil { - l = len(m.FRMPayload) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.FRMPayload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } return n } @@ -548,11 +528,9 @@ func (m *LoRaWANMACPayload) Size() (n int) { func (m *LoRaWANFHDR) Size() (n int) { var l int _ = l - if m.DevAddr != nil { - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } if m.FCtrl != nil { l = m.FCtrl.Size() @@ -585,11 +563,9 @@ func (m *LoRaWANFCtrl) Size() (n int) { if m.FPending { n += 2 } - if m.FOptsLen != nil { - l = len(m.FOptsLen) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.FOptsLen) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } return n } @@ -597,23 +573,17 @@ func (m *LoRaWANFCtrl) Size() (n int) { func (m *LoRaWANJoinRequest) Size() (n int) { var l int _ = l - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } - if m.DevNonce != nil { - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } return n } @@ -621,11 +591,9 @@ func (m *LoRaWANJoinRequest) Size() (n int) { func (m *LoRaWANJoinAccept) Size() (n int) { var l int _ = l - if m.Payload != nil { - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) } return n } diff --git a/core/router.pb.go b/core/router.pb.go index 987d36be3..35a92da6f 100644 --- a/core/router.pb.go +++ b/core/router.pb.go @@ -278,13 +278,11 @@ func (m *DataRouterReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayID != nil { - if len(m.GatewayID) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } + if len(m.GatewayID) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) } if m.Payload != nil { data[i] = 0x12 @@ -362,13 +360,11 @@ func (m *StatsReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayID != nil { - if len(m.GatewayID) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } + if len(m.GatewayID) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) } if m.Metadata != nil { data[i] = 0x12 @@ -416,45 +412,35 @@ func (m *JoinRouterReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayID != nil { - if len(m.GatewayID) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } + if len(m.GatewayID) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) + i += copy(data[i:], m.GatewayID) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintRouter(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.DevEUI != nil { - if len(m.DevEUI) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintRouter(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } + if len(m.DevEUI) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintRouter(data, i, uint64(len(m.DevEUI))) + i += copy(data[i:], m.DevEUI) } - if m.DevNonce != nil { - if len(m.DevNonce) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintRouter(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } + if len(m.DevNonce) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintRouter(data, i, uint64(len(m.DevNonce))) + i += copy(data[i:], m.DevNonce) } - if m.MIC != nil { - if len(m.MIC) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintRouter(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } + if len(m.MIC) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintRouter(data, i, uint64(len(m.MIC))) + i += copy(data[i:], m.MIC) } if m.Metadata != nil { data[i] = 0x32 @@ -537,11 +523,9 @@ func encodeVarintRouter(data []byte, offset int, v uint64) int { func (m *DataRouterReq) Size() (n int) { var l int _ = l - if m.GatewayID != nil { - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } if m.Payload != nil { l = m.Payload.Size() @@ -571,11 +555,9 @@ func (m *DataRouterRes) Size() (n int) { func (m *StatsReq) Size() (n int) { var l int _ = l - if m.GatewayID != nil { - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() @@ -593,35 +575,25 @@ func (m *StatsRes) Size() (n int) { func (m *JoinRouterReq) Size() (n int) { var l int _ = l - if m.GatewayID != nil { - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.GatewayID) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } - if m.DevEUI != nil { - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.DevEUI) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } - if m.DevNonce != nil { - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.DevNonce) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } - if m.MIC != nil { - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } + l = len(m.MIC) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) } if m.Metadata != nil { l = m.Metadata.Size() From 9edde3a3bab860596f94280fa0eeb8078b221b97 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Apr 2016 11:55:31 +0200 Subject: [PATCH 1311/2266] [ttnctl] Generate Keys if not supplied --- ttnctl/cmd/device.go | 51 +++++++++++++++++++++++++++++------------- utils/random/random.go | 7 ++++++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index f1741648f..3bee720e7 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -10,6 +10,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" "github.com/gosuri/uitable" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -202,7 +204,7 @@ var devicesRegisterCmd = &cobra.Command{ Short: "Create or Update registrations on the Handler", Long: `ttnctl devices register creates or updates an OTAA registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 2 { + if len(args) == 0 { cmd.Help() return } @@ -214,9 +216,15 @@ var devicesRegisterCmd = &cobra.Command{ ctx.Fatalf("Invalid DevEUI: %s", err) } - appKey, err := util.Parse128(args[1]) - if err != nil { - ctx.Fatalf("Invalid AppKey: %s", err) + var appKey []byte + if len(args) >= 2 { + appKey, err = util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + } else { + ctx.Info("Generating random AppKey...") + appKey = random.Bytes(16) } auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) @@ -237,7 +245,10 @@ var devicesRegisterCmd = &cobra.Command{ if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } - ctx.Info("Ok") + ctx.WithFields(log.Fields{ + "DevEUI": devEUI, + "AppKey": appKey, + }).Info("Registered device") }, } @@ -247,7 +258,7 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ Short: "Create or Update ABP registrations on the Handler", Long: `ttnctl devices register creates or updates an ABP registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 3 { + if len(args) == 0 { cmd.Help() return } @@ -259,14 +270,20 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ ctx.Fatalf("Invalid DevAddr: %s", err) } - nwkSKey, err := util.Parse128(args[1]) - if err != nil { - ctx.Fatalf("Invalid NwkSKey: %s", err) - } - - appSKey, err := util.Parse128(args[2]) - if err != nil { - ctx.Fatalf("Invalid AppSKey: %s", err) + var nwkSKey, appSKey []byte + if len(args) >= 3 { + nwkSKey, err = util.Parse128(args[1]) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + appSKey, err = util.Parse128(args[2]) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + } else { + ctx.Info("Generating random NwkSKey and AppSKey...") + nwkSKey = random.Bytes(16) + appSKey = random.Bytes(16) } auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) @@ -288,7 +305,11 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") } - ctx.Info("Ok") + ctx.WithFields(log.Fields{ + "DevAddr": devAddr, + "NwkSKey": nwkSKey, + "AppSKey": appSKey, + }).Info("Registered personalized device") }, } diff --git a/utils/random/random.go b/utils/random/random.go index 48d276233..57e8bbf93 100644 --- a/utils/random/random.go +++ b/utils/random/random.go @@ -87,3 +87,10 @@ func Lsnr() float32 { x := float64(src.Int31()) * float64(2e-9) return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) } + +// Bytes generates a random byte slice of length n +func Bytes(n int) []byte { + p := make([]byte, n) + src.Read(p) + return p +} From 3f61e88c96c4223014c94408f3f54e769f36bb6e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 17:48:52 -0500 Subject: [PATCH 1312/2266] Introduced default device entry for default AppKey --- core/components/handler/devStorage.go | 40 ++++++++++++-- core/components/handler/devStorage_test.go | 62 ++++++++++++++++++++-- core/components/handler/handlerManager.go | 40 ++++++++++++-- core/components/handler/mocks_test.go | 21 ++++++++ 4 files changed, 151 insertions(+), 12 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index b1317de66..b2ffad836 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -17,6 +17,8 @@ type DevStorage interface { read(appEUI []byte, devEUI []byte) (devEntry, error) readAll(appEUI []byte) ([]devEntry, error) upsert(entry devEntry) error + setDefault(entry devDefaultEntry) error + getDefault(appEUI []byte) (devDefaultEntry, error) done() error } @@ -33,6 +35,11 @@ type devEntry struct { NwkSKey [16]byte } +type devDefaultEntry struct { + AppEUI []byte + AppKey [16]byte +} + type devStorage struct { db dbutil.Interface } @@ -47,7 +54,6 @@ func NewDevStorage(name string) (DevStorage, error) { return &devStorage{db: itf}, nil } -// read implements the handler.DevStorage interface func (s *devStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { itf, err := s.db.Read(devEUI, &devEntry{}, appEUI) if err != nil { @@ -56,7 +62,6 @@ func (s *devStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { return itf.([]devEntry)[0], nil // Type and dimensio guaranteed by db.Read() } -// readAll implements the handler.DevStorage interface func (s *devStorage) readAll(appEUI []byte) ([]devEntry, error) { itf, err := s.db.ReadAll(&devEntry{}, appEUI) if err != nil { @@ -65,11 +70,22 @@ func (s *devStorage) readAll(appEUI []byte) ([]devEntry, error) { return itf.([]devEntry), nil } -// upsert implements the handler.DevStorage interface func (s *devStorage) upsert(entry devEntry) error { return s.db.Update(entry.DevEUI, []encoding.BinaryMarshaler{entry}, entry.AppEUI) } +func (s *devStorage) setDefault(entry devDefaultEntry) error { + return s.db.Update([]byte("default"), []encoding.BinaryMarshaler{entry}, entry.AppEUI) +} + +func (s *devStorage) getDefault(appEUI []byte) (devDefaultEntry, error) { + itf, err := s.db.Read([]byte("default"), &devDefaultEntry{}, appEUI) + if err != nil { + return devDefaultEntry{}, err + } + return itf.([]devDefaultEntry)[0], nil +} + // done implements the handler.DevStorage interface func (s *devStorage) done() error { return s.db.Close() @@ -100,6 +116,8 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { if len(data) == 16 { e.AppKey = new([16]byte) copy(e.AppKey[:], data) + } else { + e.AppKey = nil } }) rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) @@ -120,3 +138,19 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { }) return rw.Err() } + +// MarshalBinary implements the encoding.BinaryMarshaler interface +func (e devDefaultEntry) MarshalBinary() ([]byte, error) { + rw := readwriter.New(nil) + rw.Write(e.AppEUI) + rw.Write(e.AppKey[:]) + return rw.Bytes() +} + +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface +func (e *devDefaultEntry) UnmarshalBinary(data []byte) error { + rw := readwriter.New(data) + rw.Read(func(data []byte) { e.AppEUI = data }) + rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) + return rw.Err() +} diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 7abbc5e7e..8acebcbd1 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -13,7 +13,7 @@ import ( const devDB = "TestDevStorage.db" -func TestReadStore(t *testing.T) { +func TestStore(t *testing.T) { var db DevStorage defer func() { os.Remove(path.Join(os.TempDir(), devDB)) @@ -55,7 +55,7 @@ func TestReadStore(t *testing.T) { // ------------------ { - Desc(t, "read a non-existing registration") + Desc(t, "Read a non-existing registration") // Build appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} @@ -171,7 +171,44 @@ func TestReadStore(t *testing.T) { // Check CheckErrors(t, nil, err) - Check(t, []devEntry{entry1, entry2}, entries, "Devices Entries") + Check(t, []devEntry{entry1, entry2}, entries, "Devices entries") + } + + // ------------------ + + { + Desc(t, "Read a non-existing default device entry") + + // Build + appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} + + // Operate + _, err := db.getDefault(appEUI) + + // Check + CheckErrors(t, ErrNotFound, err) + } + + // ------------------ + + { + Desc(t, "Set and get default device entry") + + // Build + appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} + entry := devDefaultEntry{ + AppEUI: appEUI, + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Operate + err := db.setDefault(entry) + FatalUnless(t, err) + want, err := db.getDefault(appEUI) + + // Check + CheckErrors(t, nil, err) + Check(t, want, entry, "Default entry") } // ------------------ @@ -183,7 +220,7 @@ func TestReadStore(t *testing.T) { } } -func TestMarshalUnmarshalEntries(t *testing.T) { +func TestMarshalUnmarshalDevEntries(t *testing.T) { { Desc(t, "Complete Entry") entry := devEntry{ @@ -261,3 +298,20 @@ func TestMarshalUnmarshalEntries(t *testing.T) { Check(t, want, *unmarshaled, "Entries") } } + +func TestMarshalUnmarshalDevDefaultEntries(t *testing.T) { + { + Desc(t, "Entry") + entry := devDefaultEntry{ + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, + } + + data, err := entry.MarshalBinary() + CheckErrors(t, nil, err) + unmarshaled := new(devDefaultEntry) + err = unmarshaled.UnmarshalBinary(data) + CheckErrors(t, nil, err) + Check(t, entry, *unmarshaled, "Entries") + } +} diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index b7dd53b70..0931ed9dc 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -58,7 +58,6 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle } } - // 4. Done return &core.ListDevicesHandlerRes{ABP: abp, OTAA: otaa}, nil } @@ -86,7 +85,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) } - // 3. Insert the request in our own storage + // 3. Save the device in the local storage h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering Device.") entry := devEntry{ AppEUI: req.AppEUI, @@ -103,7 +102,6 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq } h.Processed.Remove(append([]byte{1}, append(entry.AppEUI, entry.DevEUI...)...)) - // Done. return new(core.UpsertABPHandlerRes), nil } @@ -129,7 +127,7 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR return new(core.UpsertOTAAHandlerRes), errors.New(errors.Operational, err) } - // 3. Insert the request in our own storage + // 3. Save the device in the local storage h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI).Debug("Request accepted by broker. Registering Device.") var appKey [16]byte copy(appKey[:], req.AppKey) @@ -143,6 +141,38 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR return new(core.UpsertOTAAHandlerRes), err } - // 4. Done. return new(core.UpsertOTAAHandlerRes), nil } + +// SetDefaultAppKey implements the core.HandlerManager interface +func (h component) SetDefaultAppKey(bctx context.Context, req *core.SetDefaultAppKeyReq) (*core.SetDefaultAppKeyRes, error) { + h.Ctx.Debug("Handle Set Default AppKey Request") + + // 1. Validate the request + if len(req.AppEUI) != 8 || len(req.AppKey) != 16 { + err := errors.New(errors.Structural, "Invalid request parameters") + h.Ctx.WithError(err).Debug("Unable to handle set default AppKey request") + return new(core.SetDefaultAppKeyRes), err + } + + // 2. Validate the token + _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected token") + return new(core.SetDefaultAppKeyRes), errors.New(errors.Operational, err) + } + + // 3. Set the key in the local storage + h.Ctx.WithField("AppEUI", req.AppEUI).Debug("Valid token. Registering default AppKey.") + var appKey [16]byte + copy(appKey[:], req.AppKey) + err = h.DevStorage.setDefault(devDefaultEntry{ + AppEUI: req.AppEUI, + AppKey: appKey, + }) + + return new(core.SetDefaultAppKeyRes), nil +} diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index cc6a0fc70..361a8dcb8 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -24,6 +24,15 @@ type MockDevStorage struct { InUpsert struct { Entry devEntry } + InGetDefault struct { + AppEUI []byte + } + OutGetDefault struct { + Entry devDefaultEntry + } + InSetDefault struct { + Entry devDefaultEntry + } InDone struct { Called bool } @@ -55,6 +64,18 @@ func (m *MockDevStorage) upsert(entry devEntry) error { return m.Failures["upsert"] } +// getDefault implements the DevStorage interface +func (m *MockDevStorage) getDefault(appEUI []byte) (devDefaultEntry, error) { + m.InGetDefault.AppEUI = appEUI + return m.OutGetDefault.Entry, m.Failures["getDefault"] +} + +// setDefault implements the DevStorage Interface +func (m *MockDevStorage) setDefault(e devDefaultEntry) error { + m.InSetDefault.Entry = e + return m.Failures["setDefault"] +} + // done implements the DevStorage Interface func (m *MockDevStorage) done() error { m.InDone.Called = true From baa79d7043a56ad3f69b09baa9d22b3441c6d4d5 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 17:49:44 -0500 Subject: [PATCH 1313/2266] Clarified token invalidation error message --- core/components/broker/brokerManager.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 5c27ff512..c043e4f8f 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -22,7 +22,7 @@ func (b component) ValidateToken(bctx context.Context, req *core.ValidateTokenBr return new(core.ValidateTokenBrokerRes), err } if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { - b.Ctx.WithError(err).Debug("Unable to handle ValidateToken request") + b.Ctx.WithError(err).Debug("The token is invalid") return new(core.ValidateTokenBrokerRes), err } return new(core.ValidateTokenBrokerRes), nil From 5a950e762d11ec46630c1f4a60eb5bf6bdfce49a Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 18:17:27 -0500 Subject: [PATCH 1314/2266] Implemented getter and setter of default device --- core/application.pb.go | 4 + core/components/handler/handlerManager.go | 73 ++- core/handler_manager.pb.go | 720 +++++++++++++++++++++- core/protos/handler_manager.proto | 25 +- 4 files changed, 770 insertions(+), 52 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 254990ab6..3a459dfc9 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -47,6 +47,10 @@ ListDevicesHandlerRes HandlerABPDevice HandlerOTAADevice + GetDefaultDeviceReq + GetDefaultDeviceRes + SetDefaultDeviceReq + SetDefaultDeviceRes LoRaWANData LoRaWANMHDR LoRaWANMACPayload diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 0931ed9dc..6eadb87be 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -11,23 +11,24 @@ import ( // ListDevices implements the core.HandlerManagerServer interface func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandlerReq) (*core.ListDevicesHandlerRes, error) { - h.Ctx.Debug("Handle ListDevices Request") + h.Ctx.Debug("Handle list devices request") // 1. Validate the request if len(req.AppEUI) != 8 { err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") + h.Ctx.WithError(err).Debug("Unable to handle list devices request") return new(core.ListDevicesHandlerRes), err } - // 2. Validate token & retrieve devices from db + // 2. Validate token and retrieve devices from the storage + // TODO: On not found, report gracefully back to caller instead of failing with not found if _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { - h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") + h.Ctx.WithError(err).Debug("Unable to handle list devices request") return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) } entries, err := h.DevStorage.readAll(req.AppEUI) if err != nil { - h.Ctx.WithError(err).Debug("Unable to handle ListDevices request") + h.Ctx.WithError(err).Debug("Unable to handle list devices request") return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) } @@ -63,7 +64,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle // UpsertABP implements the core.HandlerManager interface func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq) (*core.UpsertABPHandlerRes, error) { - h.Ctx.Debug("Handle Upsert ABP Request") + h.Ctx.Debug("Handle upsert ABP request") // 1. Validate the request if len(req.AppEUI) != 8 || len(req.DevAddr) != 4 || len(req.NwkSKey) != 16 || len(req.AppSKey) != 16 { @@ -85,8 +86,8 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) } - // 3. Save the device in the local storage - h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering Device.") + // 3. Save the device in the storage + h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering device") entry := devEntry{ AppEUI: req.AppEUI, DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), @@ -107,7 +108,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq // UpsertOTAA implements the core.HandlerManager interface func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerReq) (*core.UpsertOTAAHandlerRes, error) { - h.Ctx.Debug("Handle Upsert OTAA Request") + h.Ctx.Debug("Handle upsert OTAA request") // 1. Validate the request if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.AppKey) != 16 { @@ -127,8 +128,8 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR return new(core.UpsertOTAAHandlerRes), errors.New(errors.Operational, err) } - // 3. Save the device in the local storage - h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI).Debug("Request accepted by broker. Registering Device.") + // 3. Save the device in the storage + h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI).Debug("Request accepted by broker. Registering device") var appKey [16]byte copy(appKey[:], req.AppKey) err = h.DevStorage.upsert(devEntry{ @@ -144,15 +145,47 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR return new(core.UpsertOTAAHandlerRes), nil } -// SetDefaultAppKey implements the core.HandlerManager interface -func (h component) SetDefaultAppKey(bctx context.Context, req *core.SetDefaultAppKeyReq) (*core.SetDefaultAppKeyRes, error) { - h.Ctx.Debug("Handle Set Default AppKey Request") +// GetDefaultDevice implements the core.HandlerManager Interface +func (h component) GetDefaultDevice(bctx context.Context, req *core.GetDefaultDeviceReq) (*core.GetDefaultDeviceRes, error) { + h.Ctx.Debug("Handle get default device request") + + // 1. Validate the request + if len(req.AppEUI) != 8 { + err := errors.New(errors.Structural, "Invalid request parameters") + h.Ctx.WithError(err).Debug("Unable to handle set default device request") + return new(core.GetDefaultDeviceRes), err + } + + // 2. Validate the token + _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected token") + return new(core.GetDefaultDeviceRes), errors.New(errors.Operational, err) + } + + // 3. Get default device entry from storage + // TODO: On not found, report gracefully back to caller instead of failing with not found + entry, err := h.DevStorage.getDefault(req.AppEUI) + if err != nil { + h.Ctx.WithError(err).Debug("Error while trying to retrieve default device") + return new(core.GetDefaultDeviceRes), err + } + + return &core.GetDefaultDeviceRes{AppKey: entry.AppKey[:]}, nil +} + +// SetDefaultDevice implements the core.HandlerManager interface +func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDeviceReq) (*core.SetDefaultDeviceRes, error) { + h.Ctx.Debug("Handle set default device request") // 1. Validate the request if len(req.AppEUI) != 8 || len(req.AppKey) != 16 { err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle set default AppKey request") - return new(core.SetDefaultAppKeyRes), err + h.Ctx.WithError(err).Debug("Unable to handle set default device request") + return new(core.SetDefaultDeviceRes), err } // 2. Validate the token @@ -162,11 +195,11 @@ func (h component) SetDefaultAppKey(bctx context.Context, req *core.SetDefaultAp }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected token") - return new(core.SetDefaultAppKeyRes), errors.New(errors.Operational, err) + return new(core.SetDefaultDeviceRes), errors.New(errors.Operational, err) } - // 3. Set the key in the local storage - h.Ctx.WithField("AppEUI", req.AppEUI).Debug("Valid token. Registering default AppKey.") + // 3. Set the default device in the storage + h.Ctx.WithField("AppEUI", req.AppEUI).Debug("Valid token. Registering default device") var appKey [16]byte copy(appKey[:], req.AppKey) err = h.DevStorage.setDefault(devDefaultEntry{ @@ -174,5 +207,5 @@ func (h component) SetDefaultAppKey(bctx context.Context, req *core.SetDefaultAp AppKey: appKey, }) - return new(core.SetDefaultAppKeyRes), nil + return new(core.SetDefaultDeviceRes), nil } diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index de1404b4e..74a97806b 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -135,6 +135,52 @@ func (m *HandlerOTAADevice) String() string { return proto.CompactTex func (*HandlerOTAADevice) ProtoMessage() {} func (*HandlerOTAADevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{7} } +type GetDefaultDeviceReq struct { + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` +} + +func (m *GetDefaultDeviceReq) Reset() { *m = GetDefaultDeviceReq{} } +func (m *GetDefaultDeviceReq) String() string { return proto.CompactTextString(m) } +func (*GetDefaultDeviceReq) ProtoMessage() {} +func (*GetDefaultDeviceReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{8} +} + +type GetDefaultDeviceRes struct { + AppKey []byte `protobuf:"bytes,1,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` +} + +func (m *GetDefaultDeviceRes) Reset() { *m = GetDefaultDeviceRes{} } +func (m *GetDefaultDeviceRes) String() string { return proto.CompactTextString(m) } +func (*GetDefaultDeviceRes) ProtoMessage() {} +func (*GetDefaultDeviceRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{9} +} + +type SetDefaultDeviceReq struct { + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + AppKey []byte `protobuf:"bytes,3,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` +} + +func (m *SetDefaultDeviceReq) Reset() { *m = SetDefaultDeviceReq{} } +func (m *SetDefaultDeviceReq) String() string { return proto.CompactTextString(m) } +func (*SetDefaultDeviceReq) ProtoMessage() {} +func (*SetDefaultDeviceReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{10} +} + +type SetDefaultDeviceRes struct { +} + +func (m *SetDefaultDeviceRes) Reset() { *m = SetDefaultDeviceRes{} } +func (m *SetDefaultDeviceRes) String() string { return proto.CompactTextString(m) } +func (*SetDefaultDeviceRes) ProtoMessage() {} +func (*SetDefaultDeviceRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{11} +} + func init() { proto.RegisterType((*UpsertOTAAHandlerReq)(nil), "core.UpsertOTAAHandlerReq") proto.RegisterType((*UpsertOTAAHandlerRes)(nil), "core.UpsertOTAAHandlerRes") @@ -144,6 +190,10 @@ func init() { proto.RegisterType((*ListDevicesHandlerRes)(nil), "core.ListDevicesHandlerRes") proto.RegisterType((*HandlerABPDevice)(nil), "core.HandlerABPDevice") proto.RegisterType((*HandlerOTAADevice)(nil), "core.HandlerOTAADevice") + proto.RegisterType((*GetDefaultDeviceReq)(nil), "core.GetDefaultDeviceReq") + proto.RegisterType((*GetDefaultDeviceRes)(nil), "core.GetDefaultDeviceRes") + proto.RegisterType((*SetDefaultDeviceReq)(nil), "core.SetDefaultDeviceReq") + proto.RegisterType((*SetDefaultDeviceRes)(nil), "core.SetDefaultDeviceRes") } // Reference imports to suppress errors if they are not otherwise used. @@ -156,6 +206,8 @@ type HandlerManagerClient interface { UpsertOTAA(ctx context.Context, in *UpsertOTAAHandlerReq, opts ...grpc.CallOption) (*UpsertOTAAHandlerRes, error) UpsertABP(ctx context.Context, in *UpsertABPHandlerReq, opts ...grpc.CallOption) (*UpsertABPHandlerRes, error) ListDevices(ctx context.Context, in *ListDevicesHandlerReq, opts ...grpc.CallOption) (*ListDevicesHandlerRes, error) + GetDefaultDevice(ctx context.Context, in *GetDefaultDeviceReq, opts ...grpc.CallOption) (*GetDefaultDeviceRes, error) + SetDefaultDevice(ctx context.Context, in *SetDefaultDeviceReq, opts ...grpc.CallOption) (*SetDefaultDeviceRes, error) } type handlerManagerClient struct { @@ -193,12 +245,32 @@ func (c *handlerManagerClient) ListDevices(ctx context.Context, in *ListDevicesH return out, nil } +func (c *handlerManagerClient) GetDefaultDevice(ctx context.Context, in *GetDefaultDeviceReq, opts ...grpc.CallOption) (*GetDefaultDeviceRes, error) { + out := new(GetDefaultDeviceRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/GetDefaultDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerManagerClient) SetDefaultDevice(ctx context.Context, in *SetDefaultDeviceReq, opts ...grpc.CallOption) (*SetDefaultDeviceRes, error) { + out := new(SetDefaultDeviceRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/SetDefaultDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for HandlerManager service type HandlerManagerServer interface { UpsertOTAA(context.Context, *UpsertOTAAHandlerReq) (*UpsertOTAAHandlerRes, error) UpsertABP(context.Context, *UpsertABPHandlerReq) (*UpsertABPHandlerRes, error) ListDevices(context.Context, *ListDevicesHandlerReq) (*ListDevicesHandlerRes, error) + GetDefaultDevice(context.Context, *GetDefaultDeviceReq) (*GetDefaultDeviceRes, error) + SetDefaultDevice(context.Context, *SetDefaultDeviceReq) (*SetDefaultDeviceRes, error) } func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { @@ -241,6 +313,30 @@ func _HandlerManager_ListDevices_Handler(srv interface{}, ctx context.Context, d return out, nil } +func _HandlerManager_GetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(GetDefaultDeviceReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerManagerServer).GetDefaultDevice(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _HandlerManager_SetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(SetDefaultDeviceReq) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerManagerServer).SetDefaultDevice(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + var _HandlerManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "core.HandlerManager", HandlerType: (*HandlerManagerServer)(nil), @@ -257,6 +353,14 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ MethodName: "ListDevices", Handler: _HandlerManager_ListDevices_Handler, }, + { + MethodName: "GetDefaultDevice", + Handler: _HandlerManager_GetDefaultDevice_Handler, + }, + { + MethodName: "SetDefaultDevice", + Handler: _HandlerManager_SetDefaultDevice_Handler, + }, }, Streams: []grpc.StreamDesc{}, } @@ -563,6 +667,122 @@ func (m *HandlerOTAADevice) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *GetDefaultDeviceReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + return i, nil +} + +func (m *GetDefaultDeviceRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetDefaultDeviceRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppKey != nil { + if len(m.AppKey) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) + } + } + return i, nil +} + +func (m *SetDefaultDeviceReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Token) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if m.AppEUI != nil { + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + } + if m.AppKey != nil { + if len(m.AppKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) + } + } + return i, nil +} + +func (m *SetDefaultDeviceRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SetDefaultDeviceRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + func encodeFixed64HandlerManager(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -738,6 +958,62 @@ func (m *HandlerOTAADevice) Size() (n int) { return n } +func (m *GetDefaultDeviceReq) Size() (n int) { + var l int + _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *GetDefaultDeviceRes) Size() (n int) { + var l int + _ = l + if m.AppKey != nil { + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *SetDefaultDeviceReq) Size() (n int) { + var l int + _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + if m.AppEUI != nil { + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + if m.AppKey != nil { + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + } + return n +} + +func (m *SetDefaultDeviceRes) Size() (n int) { + var l int + _ = l + return n +} + func sovHandlerManager(x uint64) (n int) { for { n++ @@ -1872,6 +2148,388 @@ func (m *HandlerOTAADevice) Unmarshal(data []byte) error { } return nil } +func (m *GetDefaultDeviceReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetDefaultDeviceReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetDefaultDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetDefaultDeviceRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetDefaultDeviceRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetDefaultDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) + if m.AppKey == nil { + m.AppKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SetDefaultDeviceReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SetDefaultDeviceReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SetDefaultDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) + if m.AppKey == nil { + m.AppKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SetDefaultDeviceRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SetDefaultDeviceRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SetDefaultDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipHandlerManager(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -1978,33 +2636,37 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 436 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xcb, 0x6e, 0xda, 0x40, - 0x14, 0xed, 0xe0, 0x17, 0x5c, 0xda, 0x8a, 0x4e, 0x81, 0xba, 0xae, 0x84, 0xaa, 0x59, 0x21, 0x55, - 0x62, 0x41, 0xbf, 0xc0, 0x14, 0xfa, 0x50, 0x5f, 0xc8, 0x81, 0x75, 0xe4, 0xe0, 0x21, 0x41, 0x24, - 0xb6, 0xe3, 0xb1, 0x82, 0xf2, 0x19, 0x91, 0xb2, 0xc8, 0x0f, 0x45, 0xca, 0x32, 0x9f, 0x10, 0x91, - 0x1f, 0xc9, 0x8c, 0xc7, 0x02, 0x3b, 0xb1, 0xb3, 0x40, 0x59, 0x58, 0xe2, 0xdc, 0x73, 0xb8, 0x3e, - 0xf7, 0xce, 0x19, 0x43, 0xeb, 0xc8, 0xf5, 0xbd, 0x63, 0x1a, 0xed, 0x9f, 0xb8, 0xbe, 0x7b, 0x48, - 0xa3, 0x5e, 0x18, 0x05, 0x71, 0x80, 0xd5, 0x59, 0x10, 0x51, 0x12, 0x43, 0x73, 0x1a, 0x32, 0x1a, - 0xc5, 0xff, 0x27, 0xb6, 0xfd, 0x53, 0x0a, 0x1d, 0x7a, 0x8a, 0x9b, 0xa0, 0x4d, 0x82, 0x25, 0xf5, - 0x4d, 0xf4, 0x19, 0x75, 0x6b, 0x8e, 0x16, 0x0b, 0x80, 0xdb, 0xa0, 0xdb, 0x61, 0x38, 0x9a, 0xfe, - 0x32, 0x2b, 0xbc, 0xfc, 0xda, 0xd1, 0xdd, 0x04, 0x89, 0xfa, 0x90, 0x9e, 0x89, 0xba, 0x22, 0xeb, - 0x5e, 0x82, 0x52, 0xfd, 0x6f, 0x7a, 0x6e, 0xaa, 0x1b, 0x3d, 0x47, 0xa4, 0x5d, 0xf8, 0x56, 0x46, - 0x2e, 0x10, 0xbc, 0x97, 0x84, 0x3d, 0x18, 0xef, 0xec, 0xc6, 0x04, 0x83, 0xbb, 0xb1, 0x3d, 0x2f, - 0x4a, 0xed, 0x18, 0x9e, 0x84, 0x82, 0xf9, 0xb7, 0x5a, 0xee, 0x6d, 0x0d, 0x19, 0xbe, 0x84, 0x82, - 0xe1, 0xbd, 0x12, 0x46, 0x93, 0x8c, 0x2b, 0x21, 0x69, 0x15, 0x59, 0x62, 0x64, 0x04, 0xad, 0x3f, - 0x0b, 0x16, 0xf3, 0x17, 0x2d, 0x66, 0x94, 0xed, 0xea, 0x95, 0xf8, 0xc5, 0x6d, 0x18, 0xfe, 0x02, - 0xaa, 0x58, 0x0e, 0xef, 0xa2, 0x74, 0xeb, 0xfd, 0x0f, 0x3d, 0x71, 0x5a, 0xbd, 0x94, 0x17, 0x84, - 0xfc, 0x87, 0xa3, 0x06, 0xfc, 0x37, 0xee, 0x82, 0xc2, 0xdd, 0xf1, 0xd6, 0x42, 0xdb, 0xce, 0x69, - 0x79, 0x3d, 0x95, 0x2a, 0xee, 0x60, 0x4c, 0x2e, 0x11, 0x34, 0x1e, 0x33, 0xd9, 0x85, 0x55, 0x4a, - 0x17, 0xa6, 0x94, 0x2e, 0x4c, 0xcd, 0x2d, 0x4c, 0x8c, 0xfa, 0xfd, 0x9b, 0x1f, 0x4f, 0xc3, 0x64, - 0x93, 0x6f, 0x1c, 0x7d, 0x9e, 0x20, 0x6c, 0x41, 0x55, 0xd4, 0x87, 0xc1, 0xca, 0x37, 0xf5, 0x84, - 0xa9, 0xce, 0x53, 0x4c, 0xae, 0x11, 0xbc, 0x7b, 0x32, 0x5c, 0x26, 0x56, 0x28, 0x17, 0xab, 0x17, - 0xf7, 0x9b, 0x86, 0x54, 0xcb, 0x86, 0x34, 0x33, 0x87, 0x51, 0x3a, 0x47, 0x35, 0x3f, 0x47, 0x7f, - 0x8d, 0xe0, 0x6d, 0x3a, 0xc7, 0x5f, 0x79, 0xdb, 0xf0, 0x10, 0x60, 0x9b, 0x75, 0x6c, 0xc9, 0xc3, - 0x29, 0xba, 0x73, 0x56, 0x39, 0xc7, 0xb0, 0x0d, 0xb5, 0x4d, 0x0a, 0xf1, 0xc7, 0xac, 0x30, 0x77, - 0x53, 0xac, 0x52, 0x8a, 0xe1, 0x1f, 0x50, 0xcf, 0x44, 0x0d, 0x7f, 0x92, 0xca, 0xc2, 0x10, 0x5b, - 0xcf, 0x90, 0x6c, 0xd0, 0xb8, 0x59, 0x77, 0xd0, 0x2d, 0x7f, 0xee, 0xf8, 0x73, 0x75, 0xdf, 0x79, - 0x75, 0xa0, 0x27, 0x9f, 0x94, 0xaf, 0x0f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x92, 0x19, 0x70, - 0x6b, 0x04, 0x00, 0x00, + // 501 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xcb, 0x6e, 0xd3, 0x40, + 0x14, 0x65, 0x62, 0xc7, 0x49, 0x6f, 0x01, 0x85, 0x49, 0x13, 0x8c, 0x91, 0x2a, 0x34, 0xab, 0x48, + 0x88, 0x2c, 0xca, 0x17, 0x38, 0x4d, 0x69, 0x11, 0xaf, 0xca, 0x6e, 0x56, 0x2c, 0x90, 0xa9, 0x27, + 0x50, 0xb5, 0xd8, 0xc6, 0x63, 0xa8, 0xf8, 0x0c, 0x24, 0x16, 0xfc, 0x10, 0x12, 0x4b, 0x3e, 0x01, + 0xc1, 0x3f, 0xb0, 0x66, 0x5e, 0xb4, 0xe3, 0x30, 0x83, 0x44, 0x61, 0x61, 0x29, 0xe7, 0x9e, 0x9b, + 0x33, 0x67, 0xee, 0x63, 0x60, 0xf4, 0x32, 0x2b, 0xf2, 0x13, 0x5a, 0x3f, 0x7b, 0x95, 0x15, 0xd9, + 0x0b, 0x5a, 0x4f, 0xab, 0xba, 0x6c, 0x4a, 0xec, 0x1f, 0x96, 0x35, 0x25, 0x0d, 0x6c, 0x2c, 0x2a, + 0x46, 0xeb, 0xe6, 0xc9, 0x41, 0x1c, 0xef, 0xa9, 0xc4, 0x84, 0xbe, 0xc6, 0x1b, 0xd0, 0x3d, 0x28, + 0x8f, 0x69, 0x11, 0xa2, 0x5b, 0x68, 0xb2, 0x96, 0x74, 0x1b, 0x01, 0xf0, 0x18, 0x82, 0xb8, 0xaa, + 0x76, 0x16, 0xf7, 0xc3, 0x0e, 0x0f, 0x5f, 0x4e, 0x82, 0x4c, 0x22, 0x11, 0x9f, 0xd3, 0xb7, 0x22, + 0xee, 0xa9, 0x78, 0x2e, 0x91, 0xce, 0x7f, 0x40, 0xdf, 0x85, 0xfe, 0x59, 0x3e, 0x47, 0x64, 0x6c, + 0x3d, 0x95, 0x91, 0xf7, 0x08, 0x86, 0x8a, 0x88, 0x67, 0xfb, 0x17, 0x76, 0x13, 0x42, 0x8f, 0xbb, + 0x89, 0xf3, 0xbc, 0xd6, 0x76, 0x7a, 0xb9, 0x82, 0x82, 0x79, 0x7c, 0x7a, 0x9c, 0x9e, 0x1b, 0xea, + 0x15, 0x0a, 0x0a, 0x86, 0x6b, 0x49, 0xa6, 0xab, 0x98, 0x4c, 0x41, 0x32, 0xb2, 0x59, 0x62, 0x64, + 0x07, 0x46, 0x0f, 0x8f, 0x58, 0xc3, 0x0f, 0x3a, 0x3a, 0xa4, 0xec, 0xa2, 0x5e, 0x49, 0x61, 0x97, + 0x61, 0xf8, 0x36, 0xf8, 0xa2, 0x38, 0x5c, 0xc5, 0x9b, 0xac, 0x6f, 0x5d, 0x9f, 0x8a, 0x6e, 0x4d, + 0x35, 0x2f, 0x08, 0xf5, 0x8f, 0xc4, 0x2f, 0xf9, 0x6f, 0x3c, 0x01, 0x8f, 0xbb, 0xe3, 0xd2, 0x22, + 0x77, 0xdc, 0xca, 0xe5, 0x71, 0x9d, 0xea, 0x65, 0xb3, 0x7d, 0xf2, 0x01, 0xc1, 0x60, 0x95, 0x31, + 0x0b, 0xd6, 0x71, 0x16, 0xcc, 0x73, 0x16, 0xcc, 0x6f, 0x15, 0x4c, 0x5c, 0xf5, 0xde, 0x76, 0xd1, + 0x2c, 0x2a, 0x59, 0xc9, 0x2b, 0x49, 0xb0, 0x94, 0x08, 0x47, 0xd0, 0x17, 0xf1, 0x79, 0x79, 0x5a, + 0x84, 0x81, 0x64, 0xfa, 0x4b, 0x8d, 0xc9, 0x27, 0x04, 0xd7, 0x7e, 0xbb, 0x9c, 0x31, 0x56, 0xa8, + 0x35, 0x56, 0xff, 0xdd, 0xaf, 0x1e, 0xd2, 0xae, 0x39, 0xa4, 0xc6, 0x3d, 0x7a, 0xce, 0x7b, 0xf4, + 0x57, 0xee, 0xb1, 0x0d, 0xc3, 0x5d, 0xca, 0xbb, 0xb9, 0xcc, 0xde, 0x9c, 0xe8, 0xa6, 0xfe, 0xfd, + 0x4c, 0xdc, 0xb1, 0x89, 0x30, 0xc3, 0x27, 0x6a, 0x2d, 0xd3, 0x53, 0x18, 0xa6, 0xff, 0x7a, 0xa6, + 0x21, 0xee, 0xb5, 0xc4, 0x47, 0x36, 0x71, 0xb6, 0xf5, 0xa3, 0x03, 0x57, 0x75, 0xbf, 0x1e, 0xa9, + 0x57, 0x05, 0xcf, 0x01, 0xce, 0x77, 0x1a, 0x47, 0x6a, 0x08, 0x6d, 0x6f, 0x4b, 0xe4, 0xe6, 0x18, + 0x8e, 0x61, 0xed, 0x6c, 0xdb, 0xf0, 0x0d, 0x33, 0xb1, 0xf5, 0x22, 0x44, 0x4e, 0x8a, 0xe1, 0x5d, + 0x58, 0x37, 0x56, 0x0a, 0xdf, 0x54, 0x99, 0xd6, 0x65, 0x8d, 0xfe, 0x40, 0x32, 0xbc, 0x07, 0x83, + 0xd5, 0x3e, 0xfc, 0xb2, 0x64, 0x69, 0x72, 0xe4, 0xa4, 0xa4, 0x52, 0xea, 0x50, 0x4a, 0xdd, 0x4a, + 0x96, 0xc2, 0xcf, 0x06, 0x9f, 0xbf, 0x6d, 0xa2, 0x2f, 0xfc, 0xfb, 0xca, 0xbf, 0x8f, 0xdf, 0x37, + 0x2f, 0x3d, 0x0f, 0xe4, 0x73, 0x7e, 0xf7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x2c, 0xbd, + 0x68, 0xe7, 0x05, 0x00, 0x00, } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index b4a4a869b..a9401dea8 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -49,8 +49,27 @@ message HandlerOTAADevice { uint32 FCntDown = 8; } +message GetDefaultDeviceReq { + string Token = 1; + bytes AppEUI = 2; +} + +message GetDefaultDeviceRes { + bytes AppKey = 1; +} + +message SetDefaultDeviceReq { + string Token = 1; + bytes AppEUI = 2; + bytes AppKey = 3; +} + +message SetDefaultDeviceRes {} + service HandlerManager { - rpc UpsertOTAA (UpsertOTAAHandlerReq) returns (UpsertOTAAHandlerRes); - rpc UpsertABP (UpsertABPHandlerReq) returns (UpsertABPHandlerRes); - rpc ListDevices (ListDevicesHandlerReq) returns (ListDevicesHandlerRes); + rpc UpsertOTAA (UpsertOTAAHandlerReq) returns (UpsertOTAAHandlerRes); + rpc UpsertABP (UpsertABPHandlerReq) returns (UpsertABPHandlerRes); + rpc ListDevices (ListDevicesHandlerReq) returns (ListDevicesHandlerRes); + rpc GetDefaultDevice (GetDefaultDeviceReq) returns (GetDefaultDeviceRes); + rpc SetDefaultDevice (SetDefaultDeviceReq) returns (SetDefaultDeviceRes); } From 491a3906fc2f5582a03a1c5234c1587dfa2eb4e6 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 21:24:57 -0500 Subject: [PATCH 1315/2266] Create new device when it doesn't exist and default device settings exist --- core/components/handler/devStorage.go | 20 +++--- core/components/handler/devStorage_test.go | 18 +++--- core/components/handler/handler.go | 49 ++++++++++----- core/components/handler/handlerManager.go | 7 +-- core/components/handler/handler_test.go | 72 ++++++++++++++++++++-- core/components/handler/mocks_test.go | 9 +-- 6 files changed, 129 insertions(+), 46 deletions(-) diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index b2ffad836..32b0f5a10 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -17,8 +17,8 @@ type DevStorage interface { read(appEUI []byte, devEUI []byte) (devEntry, error) readAll(appEUI []byte) ([]devEntry, error) upsert(entry devEntry) error - setDefault(entry devDefaultEntry) error - getDefault(appEUI []byte) (devDefaultEntry, error) + setDefault(appEUI []byte, entry *devDefaultEntry) error + getDefault(appEUI []byte) (*devDefaultEntry, error) done() error } @@ -36,7 +36,6 @@ type devEntry struct { } type devDefaultEntry struct { - AppEUI []byte AppKey [16]byte } @@ -74,16 +73,19 @@ func (s *devStorage) upsert(entry devEntry) error { return s.db.Update(entry.DevEUI, []encoding.BinaryMarshaler{entry}, entry.AppEUI) } -func (s *devStorage) setDefault(entry devDefaultEntry) error { - return s.db.Update([]byte("default"), []encoding.BinaryMarshaler{entry}, entry.AppEUI) +func (s *devStorage) setDefault(appEUI []byte, entry *devDefaultEntry) error { + return s.db.Update([]byte("default"), []encoding.BinaryMarshaler{entry}, appEUI) } -func (s *devStorage) getDefault(appEUI []byte) (devDefaultEntry, error) { +func (s *devStorage) getDefault(appEUI []byte) (*devDefaultEntry, error) { itf, err := s.db.Read([]byte("default"), &devDefaultEntry{}, appEUI) if err != nil { - return devDefaultEntry{}, err + if ferr, ok := err.(errors.Failure); ok && ferr.Nature == errors.NotFound { + return nil, nil + } + return nil, err } - return itf.([]devDefaultEntry)[0], nil + return &itf.([]devDefaultEntry)[0], nil } // done implements the handler.DevStorage interface @@ -142,7 +144,6 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { // MarshalBinary implements the encoding.BinaryMarshaler interface func (e devDefaultEntry) MarshalBinary() ([]byte, error) { rw := readwriter.New(nil) - rw.Write(e.AppEUI) rw.Write(e.AppKey[:]) return rw.Bytes() } @@ -150,7 +151,6 @@ func (e devDefaultEntry) MarshalBinary() ([]byte, error) { // UnmarshalBinary implements the encoding.BinaryUnmarshaler interface func (e *devDefaultEntry) UnmarshalBinary(data []byte) error { rw := readwriter.New(data) - rw.Read(func(data []byte) { e.AppEUI = data }) rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) return rw.Err() } diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go index 8acebcbd1..a39ddf9a8 100644 --- a/core/components/handler/devStorage_test.go +++ b/core/components/handler/devStorage_test.go @@ -171,7 +171,7 @@ func TestStore(t *testing.T) { // Check CheckErrors(t, nil, err) - Check(t, []devEntry{entry1, entry2}, entries, "Devices entries") + Check(t, []devEntry{entry1, entry2}, entries, "Devices Entries") } // ------------------ @@ -183,10 +183,14 @@ func TestStore(t *testing.T) { appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} // Operate - _, err := db.getDefault(appEUI) + entry, err := db.getDefault(appEUI) + + // Expect + var want *devDefaultEntry // Check - CheckErrors(t, ErrNotFound, err) + CheckErrors(t, nil, err) + Check(t, want, entry, "Default Entry") } // ------------------ @@ -197,18 +201,17 @@ func TestStore(t *testing.T) { // Build appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} entry := devDefaultEntry{ - AppEUI: appEUI, AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, } // Operate - err := db.setDefault(entry) + err := db.setDefault(appEUI, &entry) FatalUnless(t, err) want, err := db.getDefault(appEUI) // Check - CheckErrors(t, nil, err) - Check(t, want, entry, "Default entry") + FatalUnless(t, err) + Check(t, *want, entry, "Default Entry") } // ------------------ @@ -303,7 +306,6 @@ func TestMarshalUnmarshalDevDefaultEntries(t *testing.T) { { Desc(t, "Entry") entry := devDefaultEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, } diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 020c8d5ad..715771df2 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -170,18 +170,37 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* ctx.Debug("Handle join request") - // 1. Lookup for the associated AppKey + // 1. Lookup the associated entry or create new entry based on default entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) - if err != nil { - ctx.Debug("Trying to activate unknown device") + if ferr, ok := err.(errors.Failure); ok && ferr.Nature == errors.NotFound { // The device is unknown, check if there are default settings + defaultEntry, err := h.DevStorage.getDefault(req.AppEUI) + if err != nil { + ctx.WithError(err).Debug("Failed to retrieve default device settings") + return new(core.JoinHandlerRes), err + } else if defaultEntry == nil { + ctx.Debug("Device unknown and no default device settings configured") + return new(core.JoinHandlerRes), errors.New(errors.NotFound, "Device unknown and no default device settings configured") + } + // Register a new OTAA device based on default + ctx.Debug("Registering a new OTAA device based on default settings") + entry = devEntry{ + AppEUI: req.AppEUI, + AppKey: &defaultEntry.AppKey, + DevEUI: req.DevEUI, + } + if err = h.DevStorage.upsert(entry); err != nil { + ctx.WithError(err).Debug("Failed to store new device based on default settings") + return new(core.JoinHandlerRes), err + } + } else if err != nil { // General error + ctx.WithError(err).Debug("Failed to retrieve device entry from storage") return new(core.JoinHandlerRes), err - } - if entry.AppKey == nil { // Trying to activate an ABP device - ctx.Debug("Trying to activate personalized device") - return new(core.JoinHandlerRes), errors.New(errors.Behavioural, "Trying to activate a personalized device") + } else if entry.AppKey == nil { // Trying to activate an ABP device + ctx.Debug("Cannot activate a personalized device over the air") + return new(core.JoinHandlerRes), errors.New(errors.Behavioural, "Cannot activate a personalized device over the air") } - // 1.5. (Yep, indexed comments sucks) Verify MIC + // 2. Verify MIC payload := lorawan.NewPHYPayload(true) payload.MHDR = lorawan.MHDR{Major: lorawan.LoRaWANR1, MType: lorawan.JoinRequest} joinPayload := lorawan.JoinRequestPayload{} @@ -195,18 +214,18 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to validate MIC") } - // 2. Prepare a channel to receive the response from the consumer + // 3. Prepare a channel to receive the response from the consumer chresp := make(chan interface{}) - // 3. Create a "bundle" which holds info waiting for other related packets + // 4. Create a "bundle" which holds info waiting for other related packets var bundleID [21]byte // Type | AppEUI(8) | DevEUI(8) | DevNonce | [ 0 0 ] buf := bytes.NewBuffer([]byte{0}) // 0 for join - _ = binary.Write(buf, binary.BigEndian, req.AppEUI) - _ = binary.Write(buf, binary.BigEndian, req.DevEUI) - _ = binary.Write(buf, binary.BigEndian, req.DevNonce) + binary.Write(buf, binary.BigEndian, req.AppEUI) + binary.Write(buf, binary.BigEndian, req.DevEUI) + binary.Write(buf, binary.BigEndian, req.DevNonce) copy(bundleID[:], buf.Bytes()) - // 4. Send the actual bundle to the consumer + // 5. Send the actual bundle to the consumer ctx.WithField("BundleID", bundleID).Debug("Define new bundle") h.ChBundles <- bundle{ @@ -218,7 +237,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* Time: time.Now(), } - // 5. Control the response + // 6. Control the response resp := <-chresp switch resp.(type) { case *core.JoinHandlerRes: diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 6eadb87be..1b8651a95 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -98,7 +98,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq copy(entry.NwkSKey[:], req.NwkSKey) copy(entry.AppSKey[:], req.AppSKey) if err = h.DevStorage.upsert(entry); err != nil { - h.Ctx.WithError(err).Debug("Error while trying to save valid request") + h.Ctx.WithError(err).Debug("Error while trying to handle valid request") return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) } h.Processed.Remove(append([]byte{1}, append(entry.AppEUI, entry.DevEUI...)...)) @@ -138,7 +138,7 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR AppKey: &appKey, }) if err != nil { - h.Ctx.WithError(err).Debug("Error while trying to save valid request") + h.Ctx.WithError(err).Debug("Error while trying to handle valid request") return new(core.UpsertOTAAHandlerRes), err } @@ -202,8 +202,7 @@ func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDe h.Ctx.WithField("AppEUI", req.AppEUI).Debug("Valid token. Registering default device") var appKey [16]byte copy(appKey[:], req.AppKey) - err = h.DevStorage.setDefault(devDefaultEntry{ - AppEUI: req.AppEUI, + err = h.DevStorage.setDefault(req.AppEUI, &devDefaultEntry{ AppKey: appKey, }) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index cec312a6a..931427ab8 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1809,7 +1809,7 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join request -> Invalid datarate") + Desc(t, "Handle invalid join request: Invalid datarate") // Build tmst := time.Now() @@ -1927,7 +1927,69 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join-request -> invalid devEUI") + Desc(t, "Handle valid join-request, recover unknown device with default device") + + // Build + tmst := time.Now() + + appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") + devStorage.OutGetDefault.Entry = &devDefaultEntry{ + AppKey: appKey, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewAuthBrokerClient() + + payload := lorawan.NewPHYPayload(true) + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(appKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) + _, err = handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, nil, err) + Check(t, req.AppEUI, devStorage.InUpsert.Entry.AppEUI, "New Device's AppEUI") + Check(t, appKey, *devStorage.InUpsert.Entry.AppKey, "New Device's AppKey") + Check(t, req.DevEUI, devStorage.InUpsert.Entry.DevEUI, "New Device's DevEUI") + } + + // -------------------- + + { + Desc(t, "Handle invalid join-request: invalid devEUI") // Build tmst := time.Now() @@ -1977,7 +2039,7 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join-request -> invalid appEUI") + Desc(t, "Handle invalid join-request: invalid appEUI") // Build tmst := time.Now() @@ -2027,7 +2089,7 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join-request -> invalid devNonce") + Desc(t, "Handle invalid join-request: invalid devNonce") // Build tmst := time.Now() @@ -2077,7 +2139,7 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join-request -> invalid Metadata") + Desc(t, "Handle invalid join-request: invalid Metadata") // Build req := &core.JoinHandlerReq{ diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go index 361a8dcb8..22d4585c2 100644 --- a/core/components/handler/mocks_test.go +++ b/core/components/handler/mocks_test.go @@ -28,10 +28,11 @@ type MockDevStorage struct { AppEUI []byte } OutGetDefault struct { - Entry devDefaultEntry + Entry *devDefaultEntry } InSetDefault struct { - Entry devDefaultEntry + AppEUI []byte + Entry *devDefaultEntry } InDone struct { Called bool @@ -65,13 +66,13 @@ func (m *MockDevStorage) upsert(entry devEntry) error { } // getDefault implements the DevStorage interface -func (m *MockDevStorage) getDefault(appEUI []byte) (devDefaultEntry, error) { +func (m *MockDevStorage) getDefault(appEUI []byte) (*devDefaultEntry, error) { m.InGetDefault.AppEUI = appEUI return m.OutGetDefault.Entry, m.Failures["getDefault"] } // setDefault implements the DevStorage Interface -func (m *MockDevStorage) setDefault(e devDefaultEntry) error { +func (m *MockDevStorage) setDefault(appEUI []byte, e *devDefaultEntry) error { m.InSetDefault.Entry = e return m.Failures["setDefault"] } From 31bf59e0dd331b25d38edb40c93400b9026dd28b Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 22:45:34 -0500 Subject: [PATCH 1316/2266] Full test of new device activated with default AppKey --- core/components/handler/handler_test.go | 41 ++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 931427ab8..6f12e49b3 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1809,7 +1809,7 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join request: Invalid datarate") + Desc(t, "Handle invalid join request: invalid datarate") // Build tmst := time.Now() @@ -1875,7 +1875,7 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle invalid join-request, lookup fails") + Desc(t, "Handle invalid join-request: lookup fails") // Build tmst := time.Now() @@ -1927,12 +1927,12 @@ func TestHandleJoin(t *testing.T) { // -------------------- { - Desc(t, "Handle valid join-request, recover unknown device with default device") + Desc(t, "Handle valid join-request | create new device with default AppKey") // Build tmst := time.Now() - appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} + req := &core.JoinHandlerReq{ AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, @@ -1969,6 +1969,27 @@ func TestHandleJoin(t *testing.T) { FatalUnless(t, err) req.MIC = payload.MIC[:] + // Expect + var wantErr *string + var wantRes = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding + NwkSKey: nil, // We'll assume it's correct if payload is okay + Metadata: &core.Metadata{ + DataRate: "SF7BW125", + Frequency: 865.5, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000), + PayloadSize: 33, + Power: 14, + InvPolarity: true, + }, + } + var wantAppReq = &core.JoinAppReq{ + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + // Operate handler := New(Components{ Ctx: GetLogger(t, "Handler"), @@ -1977,10 +1998,20 @@ func TestHandleJoin(t *testing.T) { DevStorage: devStorage, PktStorage: pktStorage, }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - _, err = handler.HandleJoin(context.Background(), req) + res, err := handler.HandleJoin(context.Background(), req) // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") + Check(t, 16, len(res.NwkSKey), "Network session keys' length") + Check(t, 4, len(res.DevAddr), "Device addresses' length") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + joinaccept := lorawan.NewPHYPayload(false) + err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) + CheckErrors(t, nil, err) + Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") Check(t, req.AppEUI, devStorage.InUpsert.Entry.AppEUI, "New Device's AppEUI") Check(t, appKey, *devStorage.InUpsert.Entry.AppKey, "New Device's AppKey") Check(t, req.DevEUI, devStorage.InUpsert.Entry.DevEUI, "New Device's DevEUI") From 82330de75208458e3e107420db522d782e280881 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 23:26:54 -0500 Subject: [PATCH 1317/2266] Added tests for broker manager --- core/components/handler/handlerClient_test.go | 373 ++++++++++++++++++ core/components/handler/handlerManager.go | 6 +- 2 files changed, 377 insertions(+), 2 deletions(-) diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go index c9423b46f..e32ab65d6 100644 --- a/core/components/handler/handlerClient_test.go +++ b/core/components/handler/handlerClient_test.go @@ -763,3 +763,376 @@ func TestUpsertOTAA(t *testing.T) { Check(t, wantRes, res, "Handler responses") } } + +func TestGetDefault(t *testing.T) { + { + Desc(t, "Valid request, no issue") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.OutGetDefault.Entry = &devDefaultEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.GetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr *string + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = &core.GetDefaultDeviceRes{ + AppKey: st.OutGetDefault.Entry.AppKey[:], + } + + // Operate + res, err := h.GetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | Invalid AppEUI") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.OutGetDefault.Entry = &devDefaultEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.GetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateTokenBrokerReq + var wantRes = new(core.GetDefaultDeviceRes) + + // Operate + res, err := h.GetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Invalid request | Invalid token") + + // Build + br := mocks.NewAuthBrokerClient() + br.Failures["ValidateToken"] = errors.New(errors.Operational, "Mock Error") + st := NewMockDevStorage() + st.OutGetDefault.Entry = &devDefaultEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.GetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.GetDefaultDeviceRes) + + // Operate + res, err := h.GetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + { + Desc(t, "Invalid request | Default not found") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.Failures["getDefault"] = errors.New(errors.NotFound, "Mock Error") + st.OutGetDefault.Entry = &devDefaultEntry{ + AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.GetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + } + + // Expect + var wantErr = ErrNotFound + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.GetDefaultDeviceRes) + + // Operate + res, err := h.GetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } +} + +func TestSetDefault(t *testing.T) { + { + Desc(t, "Valid request, no issue") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.SetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + + // Expect + var wantErr *string + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.SetDefaultDeviceRes) + + // Operate + res, err := h.SetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | Invalid AppEUI") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.SetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{5, 6, 7, 8}, + AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateTokenBrokerReq + var wantRes = new(core.SetDefaultDeviceRes) + + // Operate + res, err := h.SetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | Invalid AppKey") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.SetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: []byte{1, 2, 3, 4}, + } + + // Expect + var wantErr = ErrStructural + var wantBrkCall *core.ValidateTokenBrokerReq + var wantRes = new(core.SetDefaultDeviceRes) + + // Operate + res, err := h.SetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | Invalid token") + + // Build + br := mocks.NewAuthBrokerClient() + br.Failures["ValidateToken"] = errors.New(errors.Operational, "Mock Error") + st := NewMockDevStorage() + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.SetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.SetDefaultDeviceRes) + + // Operate + res, err := h.SetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } + + // -------------------- + + { + Desc(t, "Valid request | Storage error") + + // Build + br := mocks.NewAuthBrokerClient() + st := NewMockDevStorage() + st.Failures["setDefault"] = errors.New(errors.Operational, "Mock Error") + h := New( + Components{ + Ctx: GetLogger(t, "Handler"), + Broker: br, + DevStorage: st, + }, Options{ + PublicNetAddr: "NetAddr", + PrivateNetAddr: "PrivNetAddr", + PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", + }) + req := &core.SetDefaultDeviceReq{ + Token: "==OAuth==Token==", + AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, + AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, + } + + // Expect + var wantErr = ErrOperational + var wantBrkCall = &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + } + var wantRes = new(core.SetDefaultDeviceRes) + + // Operate + res, err := h.SetDefaultDevice(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantRes, res, "Handler responses") + } +} diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 1b8651a95..ce94d59cd 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -21,7 +21,6 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle } // 2. Validate token and retrieve devices from the storage - // TODO: On not found, report gracefully back to caller instead of failing with not found if _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { h.Ctx.WithError(err).Debug("Unable to handle list devices request") return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) @@ -167,7 +166,6 @@ func (h component) GetDefaultDevice(bctx context.Context, req *core.GetDefaultDe } // 3. Get default device entry from storage - // TODO: On not found, report gracefully back to caller instead of failing with not found entry, err := h.DevStorage.getDefault(req.AppEUI) if err != nil { h.Ctx.WithError(err).Debug("Error while trying to retrieve default device") @@ -205,6 +203,10 @@ func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDe err = h.DevStorage.setDefault(req.AppEUI, &devDefaultEntry{ AppKey: appKey, }) + if err != nil { + h.Ctx.WithError(err).Debug("Storage error") + return new(core.SetDefaultDeviceRes), errors.New(errors.Operational, err) + } return new(core.SetDefaultDeviceRes), nil } From c97a96d35b9f93a9f6a7c3aad7e86d4fd1822fa9 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 23:29:01 -0500 Subject: [PATCH 1318/2266] Show default device AppKey if present --- ttnctl/cmd/device.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 3bee720e7..9d46792d1 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -46,7 +46,22 @@ var devicesCmd = &cobra.Command{ } manager := getHandlerManager() - res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ + defaultDevice, err := manager.GetDefaultDevice(context.Background(), &core.GetDefaultDeviceReq{ + Token: auth.AccessToken, + AppEUI: appEUI, + }) + if err != nil { + ctx.WithError(err).Fatal("Could not get default device settings") + } + if defaultDevice != nil { + ctx.Warn("Application activates new devices with default AppKey") + fmt.Printf("Default AppKey: %X\n", defaultDevice.AppKey) + fmt.Printf(" {%s}\n", cStyle(defaultDevice.AppKey)) + } else { + ctx.Info("Application does not activate new devices with default AppKey") + } + + devices, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ Token: auth.AccessToken, AppEUI: appEUI, }) @@ -54,12 +69,12 @@ var devicesCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not get device list") } - ctx.Infof("Found %d personalized devices (ABP)", len(res.ABP)) + ctx.Infof("Found %d personalized devices (ABP)", len(devices.ABP)) table := uitable.New() table.MaxColWidth = 70 table.AddRow("DevAddr", "FCntUp", "FCntDown") - for _, device := range res.ABP { + for _, device := range devices.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) table.AddRow(devAddr, device.FCntUp, device.FCntDown) } @@ -68,11 +83,11 @@ var devicesCmd = &cobra.Command{ fmt.Println(table) fmt.Println() - ctx.Infof("Found %d dynamic devices (OTAA)", len(res.OTAA)) + ctx.Infof("Found %d dynamic devices (OTAA)", len(devices.OTAA)) table = uitable.New() table.MaxColWidth = 40 table.AddRow("DevEUI", "DevAddr", "FCntUp", "FCntDown") - for _, device := range res.OTAA { + for _, device := range devices.OTAA { devEUI := fmt.Sprintf("%X", device.DevEUI) devAddr := fmt.Sprintf("%X", device.DevAddr) table.AddRow(devEUI, devAddr, device.FCntUp, device.FCntDown) @@ -83,7 +98,6 @@ var devicesCmd = &cobra.Command{ fmt.Println() ctx.Info("Run 'ttnctl devices info [DevAddr|DevEUI]' for more information about a specific device") - }, } From dd7fc4aa2fc2683902ad4b1506c679fe9938c9d7 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 23:49:09 -0500 Subject: [PATCH 1319/2266] Added command to set default device settings --- ttnctl/cmd/device.go | 53 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 9d46792d1..4c26702b3 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -32,7 +32,8 @@ func getHandlerManager() core.AuthHandlerClient { var devicesCmd = &cobra.Command{ Use: "devices", Short: "Manage devices on the Handler", - Long: `ttnctl devices retrieves a list of devices that your application registered on the Handler.`, + Long: `ttnctl devices retrieves a list of devices that your application +registered on the Handler.`, Run: func(cmd *cobra.Command, args []string) { appEUI := util.GetAppEUI(ctx) @@ -216,7 +217,8 @@ func cStyle(bytes []byte) (output string) { var devicesRegisterCmd = &cobra.Command{ Use: "register [DevEUI] [AppKey]", Short: "Create or Update registrations on the Handler", - Long: `ttnctl devices register creates or updates an OTAA registration on the Handler`, + Long: `ttnctl devices register creates or updates an OTAA registration on +the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { cmd.Help() @@ -269,8 +271,9 @@ var devicesRegisterCmd = &cobra.Command{ // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", - Short: "Create or Update ABP registrations on the Handler", - Long: `ttnctl devices register creates or updates an ABP registration on the Handler`, + Short: "Create or update ABP registrations on the Handler", + Long: `ttnctl devices register personalized creates or updates an ABP +registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { cmd.Help() @@ -327,9 +330,51 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ }, } +// devicesRegisterDefaultCmd represents the `device register` command +var devicesRegisterDefaultCmd = &cobra.Command{ + Use: "default [AppKey]", + Short: "Create or update default OTAA registrations on the Handler", + Long: `ttnctl devices register default creates or updates OTAA registrations +on the Handler that have not been explicitly registered using ttnctl devices +register [DevEUI] [AppKey]`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.Help() + return + } + + appEUI := util.GetAppEUI(ctx) + + appKey, err := util.Parse128(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + + manager := getHandlerManager() + res, err := manager.SetDefaultDevice(context.Background(), &core.SetDefaultDeviceReq{ + Token: auth.AccessToken, + AppEUI: appEUI, + AppKey: appKey, + }) + if err != nil || res == nil { + ctx.WithError(err).Fatal("Could not set default device settings") + } + ctx.Info("Ok") + }, +} + func init() { RootCmd.AddCommand(devicesCmd) devicesCmd.AddCommand(devicesRegisterCmd) devicesCmd.AddCommand(devicesInfoCmd) devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) + devicesRegisterCmd.AddCommand(devicesRegisterDefaultCmd) } From 344d220055e111ed14721bafb44ff439f77bdaae Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 9 Apr 2016 23:49:22 -0500 Subject: [PATCH 1320/2266] Consistent line endings of long command descriptions --- ttnctl/cmd/downlink.go | 5 +++-- ttnctl/cmd/subscribe.go | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index b04fa476a..633b7e2ce 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -15,8 +15,9 @@ var downlinkCmd = &cobra.Command{ Short: "Send downlink messages to the network", Long: `ttnctl downlink sends a downlink message to the network -The DevEUI should be an 8-byte long hex-encoded string (16 chars), whereas the TTL is -expected to define a Time To Live in a handy format, for instance: "1h" for one hour.`, +The DevEUI should be an 8-byte long hex-encoded string (16 chars), whereas the +TTL is expected to define a Time To Live in a handy format, for instance: "1h" +for one hour.`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 3 { ctx.Fatal("Insufficient arguments") diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 732fc190c..94b240822 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -17,7 +17,8 @@ import ( var subscribeCmd = &cobra.Command{ Use: "subscribe [DevEUI]", Short: "Subscribe to uplink messages from a device", - Long: `ttnctl subscribe prints out uplink messages from a device as they arrive. + Long: `ttnctl subscribe prints out uplink messages from a device as they +arrive. The optional DevEUI argument can be used to only receive messages from a specific device. By default you will receive messages from all devices of your From 09a8de1b00ba4ef2d99f120754d0c2fe43f496dc Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 13 Apr 2016 11:51:24 +0200 Subject: [PATCH 1321/2266] Call ValidateOTAA to upsert the application --- core/components/handler/handlerManager.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index ce94d59cd..2df34b525 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -21,7 +21,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle } // 2. Validate token and retrieve devices from the storage - if _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { + if _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { h.Ctx.WithError(err).Debug("Unable to handle list devices request") return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) } @@ -187,9 +187,10 @@ func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDe } // 2. Validate the token - _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, + _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: h.PrivateNetAddrAnnounce, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected token") From c6f673d917b8482ca9aae0c1839e2ff90096ee35 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 13 Apr 2016 12:16:33 +0200 Subject: [PATCH 1322/2266] Fixed tests --- core/components/handler/handlerClient_test.go | 37 ++++++++++--------- core/components/handler/handlerManager.go | 2 +- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go index e32ab65d6..6858f129a 100644 --- a/core/components/handler/handlerClient_test.go +++ b/core/components/handler/handlerClient_test.go @@ -961,9 +961,10 @@ func TestSetDefault(t *testing.T) { // Expect var wantErr *string - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, + var wantBrkCall = &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: "PrivateNetAddrAnnounce", } var wantRes = new(core.SetDefaultDeviceRes) @@ -972,7 +973,7 @@ func TestSetDefault(t *testing.T) { // Check CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") Check(t, wantRes, res, "Handler responses") } @@ -1002,7 +1003,7 @@ func TestSetDefault(t *testing.T) { // Expect var wantErr = ErrStructural - var wantBrkCall *core.ValidateTokenBrokerReq + var wantBrkCall *core.ValidateOTAABrokerReq var wantRes = new(core.SetDefaultDeviceRes) // Operate @@ -1010,7 +1011,7 @@ func TestSetDefault(t *testing.T) { // Check CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") Check(t, wantRes, res, "Handler responses") } @@ -1040,7 +1041,7 @@ func TestSetDefault(t *testing.T) { // Expect var wantErr = ErrStructural - var wantBrkCall *core.ValidateTokenBrokerReq + var wantBrkCall *core.ValidateOTAABrokerReq var wantRes = new(core.SetDefaultDeviceRes) // Operate @@ -1048,7 +1049,7 @@ func TestSetDefault(t *testing.T) { // Check CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") Check(t, wantRes, res, "Handler responses") } @@ -1059,7 +1060,7 @@ func TestSetDefault(t *testing.T) { // Build br := mocks.NewAuthBrokerClient() - br.Failures["ValidateToken"] = errors.New(errors.Operational, "Mock Error") + br.Failures["ValidateOTAA"] = errors.New(errors.Operational, "Mock Error") st := NewMockDevStorage() h := New( Components{ @@ -1079,9 +1080,10 @@ func TestSetDefault(t *testing.T) { // Expect var wantErr = ErrOperational - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, + var wantBrkCall = &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: "PrivateNetAddrAnnounce", } var wantRes = new(core.SetDefaultDeviceRes) @@ -1090,7 +1092,7 @@ func TestSetDefault(t *testing.T) { // Check CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") Check(t, wantRes, res, "Handler responses") } @@ -1121,9 +1123,10 @@ func TestSetDefault(t *testing.T) { // Expect var wantErr = ErrOperational - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, + var wantBrkCall = &core.ValidateOTAABrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + NetAddress: "PrivateNetAddrAnnounce", } var wantRes = new(core.SetDefaultDeviceRes) @@ -1132,7 +1135,7 @@ func TestSetDefault(t *testing.T) { // Check CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") + Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") Check(t, wantRes, res, "Handler responses") } } diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 2df34b525..ee895406b 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -21,7 +21,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle } // 2. Validate token and retrieve devices from the storage - if _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { + if _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { h.Ctx.WithError(err).Debug("Unable to handle list devices request") return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) } From 7c4ca9d423eff5f62a4423b6c598709e016614b7 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 13 Apr 2016 12:23:06 +0200 Subject: [PATCH 1323/2266] Remove obsolete statement --- core/components/handler/handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 715771df2..c96be0df1 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -177,7 +177,8 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* if err != nil { ctx.WithError(err).Debug("Failed to retrieve default device settings") return new(core.JoinHandlerRes), err - } else if defaultEntry == nil { + } + if defaultEntry == nil { ctx.Debug("Device unknown and no default device settings configured") return new(core.JoinHandlerRes), errors.New(errors.NotFound, "Device unknown and no default device settings configured") } From c11c974baf225a38d34df780fd3f8606820546d6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Apr 2016 12:51:01 +0200 Subject: [PATCH 1324/2266] [ttnctl] Generate random default AppKey See #104 --- ttnctl/cmd/device.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 4c26702b3..d3e502b9f 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -345,9 +345,16 @@ register [DevEUI] [AppKey]`, appEUI := util.GetAppEUI(ctx) - appKey, err := util.Parse128(args[0]) - if err != nil { - ctx.Fatalf("Invalid AppKey: %s", err) + var appKey []byte + var err error + if len(args) >= 2 { + appKey, err = util.Parse128(args[0]) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + } else { + ctx.Info("Generating random AppKey...") + appKey = random.Bytes(16) } auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) From 0ca51d1ff474f5b3c333902fd065612d17e458be Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 13 Apr 2016 17:14:07 +0200 Subject: [PATCH 1325/2266] Fix for nil pointer dereference when there's no default device configured --- core/components/handler/handlerManager.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index ee895406b..12990bd42 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -171,6 +171,9 @@ func (h component) GetDefaultDevice(bctx context.Context, req *core.GetDefaultDe h.Ctx.WithError(err).Debug("Error while trying to retrieve default device") return new(core.GetDefaultDeviceRes), err } + if entry == nil { + return new(core.GetDefaultDeviceRes), errors.New(errors.NotFound, "No default device found") + } return &core.GetDefaultDeviceRes{AppKey: entry.AppKey[:]}, nil } From aed8254ae5bf63771812041a69cbf4af0cfb5e44 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 13 Apr 2016 17:51:54 +0200 Subject: [PATCH 1326/2266] Got rid of fatal when there's no default device --- ttnctl/cmd/device.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index d3e502b9f..64a2ee5c5 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -52,7 +52,8 @@ registered on the Handler.`, AppEUI: appEUI, }) if err != nil { - ctx.WithError(err).Fatal("Could not get default device settings") + // TODO: Check reason + defaultDevice = nil } if defaultDevice != nil { ctx.Warn("Application activates new devices with default AppKey") From 0bee8b7daaf5a841bcf24ba92786caa737fb7a5f Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 13 Apr 2016 23:48:21 +0200 Subject: [PATCH 1327/2266] Got rid of manual entry of AppEUI; it is now generated by the account server --- ttnctl/cmd/applications.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 1b22f21e1..f026e58b6 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -45,25 +45,19 @@ var applicationsCmd = &cobra.Command{ } var applicationsCreateCmd = &cobra.Command{ - Use: "create [eui] [name]", + Use: "create [name]", Short: "Create a new application", Long: `ttnctl applications create creates a new application.`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 2 { + if len(args) < 1 { cmd.Help() return } - appEUI, err := util.Parse64(args[0]) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } - server := viper.GetString("ttn-account-server") uri := fmt.Sprintf("%s/applications", server) values := url.Values{ - "eui": {fmt.Sprintf("%X", appEUI)}, - "name": {args[1]}, + "name": {args[0]}, } req, err := util.NewRequestWithAuth(server, "POST", uri, strings.NewReader(values.Encode())) if err != nil { From 7bdb22e2cdc60e3fa84bf5bc7ed8459dca3f5902 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Apr 2016 09:17:51 +0200 Subject: [PATCH 1328/2266] [drone] Fix cloning --- .drone.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.drone.yml b/.drone.yml index 9b79ee7c1..acde4a3ca 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,3 +1,6 @@ +clone: + recursive: true + matrix: GOOS: - linux From 1cf052feefbe044c338dfdc3b2fd9d5ad70c3784 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Apr 2016 09:18:33 +0200 Subject: [PATCH 1329/2266] [drone] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index ef2880db0..03a194495 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.XcDZ8tWDpPNC87Wqu6Mz2Amec2eBjj3yII7GF1EuQ0n28kuogj6shCk7GqmKibCl6tefeov-_0xF7w1HY5K2oEJPN1UPz_pSkll08sSnk6cIn9JpWEDUCtqRhqlnl-70ua5CqegJi_Pjxla3rt7V0HOKHIRocU7wLo5jGs5PYM7Ma6ePUJuBo6Fis3DqlBFXWbTr0wxu9fX_qyDhMCFI5ebSGspdGxy1EJqfX7kR5kERR-4J6gCCFPXrbOh_NEmCRqS9cKxA6bD-AuX3nrkrLK7lMX2jlLqx4bjx2BmcO2sGUdhvY4I0x7-LoHTPPJPbSpbgnET9QMeFok6uemLGfg.vXxzCNCi2SyeY06S.JfDFGp2TUb8UGpDP5Pk3L06VxsiYarf_1X67AUJme3u3PIIp7tSd51DS-YlVArT3zJ07PTp9Z1Nr0kTJkUhwGtM4-yH3x9wdLn7pO0gQyerPIeOxOHt6SvXbgjlvpOjRlXmDdpKSLdxPoKXLFgQsUcTb-N_w7LROTO71GXstK98eEnfmEeuUAeRDWZNbG7EutYLZfoqdZoCVpABSpDDNJskTMAVw6SZSbTKTKT-dzAiPAuex9WiVzkzjU9CoGTvbmFpl0rbD2uWFsou7sTXz3wTehB7meQdWLcoMxJ2QgLBB3LARekiYyhCSjXnOJZ4zXFDQu6QQ417qIC9VdlrVR75sDtUw1Ldhoy-croiICQSpiOin2kllwH2thMtlC4jk_IqN1RkKBxQSPDvQvqnDMse52YW95ZsddGH4XyNZECpRkwK9pZ0BHoiSqSNBN7LvYmplqwoYsh85ZYD8Cb4zgk-gcn_P.-sYy73qvNLMG3VXNHUYldQ \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.JMQYGyVPuM2EI2Xd1J64gBOn2rMth9a0D7DVf5K_5HQR3zv8gqnCvFwk2erJkKrFoxfeV5yQzmaHOrblmXrik1L4ccQ6VGFKRcUsWwHDDTbzy9wk3wHyXTl8AvXob27zQMJrDfdG6U_2mmUfVpfdmsJbYvFGBQK77_9OLEQE8cbLv9rX1pV_2D12fGVmMY6w7e-kGATwrqqwdYY-e4gRVqmyI5gwN6SeZbHmEVqlk-umsaYrGyPa5tMldPPmi27h7c0SCsOOdpju13epOVnyBIz4O6HVvXP5k6kvroKdeozxG_7PGUjDYyBWrKgeUbOl6iriG-bSPs0f2NFcvt37bA.q7uQR6u-HmVfuKYO.Sz8Mnh9hib1Snu0MHF_3WCn1uQOg4OfFv__KAyAi4CkddiysKOumOODSVpAsWBMPWd0XFHlo32_13HLRekjhoBbowdo40LHmHGrgXXIEqt544lhviTYR6FupVB6xCe89CIRE8-yXMT81I_pT7y0cuHEh4CmHTQsDD8TGmfphk-NQFYpNYf6yUsNO0iNj4o-HkCPtW5MtvkHw9gseuJtgDzuvJQ89lFzESSqfgxjJDT04DF9SWgZFPIHoWRsXQhKQ4ieuzuE5QGHAIL7eHV6QDfppx2yUnC4CnfoN-DSlrDkYoABmjIjOd3AKRZm1BM4u-zJp3znz7vHLScUVPDmz45lxULOlcNZvZTxDqte0zvBYOu2b1FxM_EqQkKV4BZfBpNwppkGJDHoGofLE_GFUbnJtpXznYIyQi29yOVNbVJXfv9wPP-w8uiglZKcjUCU-fOSzx5qyeNo--sEYe_e8L-kXvE7H.TNNZVpTq27vwqNu1P-SY0w \ No newline at end of file From 3b00360a6d10e09c914488e05199c831972c8078 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Sat, 16 Apr 2016 01:18:41 +0200 Subject: [PATCH 1330/2266] Ask user to retype password to prevent typing errors when creating user --- ttnctl/cmd/user.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 9540655ab..524ba0a08 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "net/url" + "reflect" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" @@ -49,6 +50,14 @@ var userCreateCmd = &cobra.Command{ if err != nil { ctx.Fatal(err.Error()) } + fmt.Print("Retype password: ") + password2, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal(err.Error()) + } + if ! reflect.DeepEqual(password,password2) { + ctx.Fatal("Passwords do not match") + } uri := fmt.Sprintf("%s/register", viper.GetString("ttn-account-server")) values := url.Values{ From 54d41452e44ce23350b3f3925f80e66dcd6e0075 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Sat, 16 Apr 2016 01:32:34 +0200 Subject: [PATCH 1331/2266] Fix formatting --- ttnctl/cmd/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 524ba0a08..4ba5ef3de 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -55,7 +55,7 @@ var userCreateCmd = &cobra.Command{ if err != nil { ctx.Fatal(err.Error()) } - if ! reflect.DeepEqual(password,password2) { + if !reflect.DeepEqual(password, password2) { ctx.Fatal("Passwords do not match") } From 3ad637706b58e339a8d925e5306315f99460038b Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Sun, 17 Apr 2016 18:15:05 +0200 Subject: [PATCH 1332/2266] Changed prompt for second password query and use bytes.Equal() --- ttnctl/cmd/user.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 4ba5ef3de..1f8064e90 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -7,7 +7,7 @@ import ( "fmt" "net/http" "net/url" - "reflect" + "bytes" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" @@ -50,12 +50,12 @@ var userCreateCmd = &cobra.Command{ if err != nil { ctx.Fatal(err.Error()) } - fmt.Print("Retype password: ") + fmt.Print("Confirm password: ") password2, err := gopass.GetPasswd() if err != nil { ctx.Fatal(err.Error()) } - if !reflect.DeepEqual(password, password2) { + if !bytes.Equal(password, password2) { ctx.Fatal("Passwords do not match") } From 5385199a1fb4e79e44f24620b4c2e75576a60311 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Sun, 17 Apr 2016 18:20:41 +0200 Subject: [PATCH 1333/2266] Fix formatting. --- ttnctl/cmd/user.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 1f8064e90..6b1d18a8f 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -4,10 +4,10 @@ package cmd import ( + "bytes" "fmt" "net/http" "net/url" - "bytes" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" From b1e4d987f0a933c0bd2031a7d80ea6d4adbc238d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 18 Apr 2016 14:21:40 +0200 Subject: [PATCH 1334/2266] Don't wait too long for MQTT publish --- core/adapters/mqtt/adapter.go | 38 +++++++++++++++++++++++++++++++---- mqtt/client.go | 6 ++++++ 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index c872abb44..4d3f70ed9 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -4,6 +4,8 @@ package mqtt import ( + "time" + "github.com/TheThingsNetwork/ttn/core" ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -13,6 +15,8 @@ import ( "google.golang.org/grpc" ) +const mqttTimeout = 20 * time.Millisecond + // Adapter defines a public interface for the mqtt adapter type Adapter interface { core.AppClient @@ -45,8 +49,21 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . } token := a.client.PublishUplink(req.AppEUI, req.DevEUI, dataUp) - if token.Wait(); token.Error() != nil { - return new(core.DataAppRes), errors.New(errors.Structural, token.Error()) + if token.WaitTimeout(mqttTimeout) { + // token did not timeout: just return + if token.Error() != nil { + return new(core.DataAppRes), errors.New(errors.Structural, token.Error()) + } + } else { + // token did timeout: wait for it in background and just return + go func() { + token.Wait() + if token.Error() != nil { + if a.ctx != nil { + a.ctx.WithError(token.Error()).Warn("Could not publish uplink") + } + } + }() } return new(core.DataAppRes), nil } @@ -92,8 +109,21 @@ func (a *defaultAdapter) HandleJoin(_ context.Context, req *core.JoinAppReq, _ . } token := a.client.PublishActivation(req.AppEUI, req.DevEUI, otaa) - if token.Wait(); token.Error() != nil { - return new(core.JoinAppRes), errors.New(errors.Structural, token.Error()) + if token.WaitTimeout(mqttTimeout) { + // token did not timeout: just return + if token.Error() != nil { + return new(core.JoinAppRes), errors.New(errors.Structural, token.Error()) + } + } else { + // token did timeout: wait for it in background and just return + go func() { + token.Wait() + if token.Error() != nil { + if a.ctx != nil { + a.ctx.WithError(token.Error()).Warn("Could not publish activation") + } + } + }() } return new(core.JoinAppRes), nil } diff --git a/mqtt/client.go b/mqtt/client.go index ec0443dca..591a44dd5 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -44,6 +44,7 @@ type Client interface { type Token interface { Wait() bool + WaitTimeout(time.Duration) bool Error() error } @@ -56,6 +57,11 @@ func (t *simpleToken) Wait() bool { return true } +// WaitTimeout always returns true +func (t *simpleToken) WaitTimeout(_ time.Duration) bool { + return true +} + // Error contains the error if present func (t *simpleToken) Error() error { return t.err From 02ca93e30097d1707736b79ed6ac20589855c12e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 18 Apr 2016 21:07:19 +0200 Subject: [PATCH 1335/2266] [ttnctl] Print AppEUI for OTAA devices --- ttnctl/cmd/device.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 64a2ee5c5..427359cb7 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -137,6 +137,10 @@ var devicesInfoCmd = &cobra.Command{ if reflect.DeepEqual(device.DevEUI, devEUI) { fmt.Println("Dynamic device:") + fmt.Println() + fmt.Printf(" AppEUI: %X\n", appEUI) + fmt.Printf(" {%s}\n", cStyle(appEUI)) + fmt.Println() fmt.Printf(" DevEUI: %X\n", device.DevEUI) fmt.Printf(" {%s}\n", cStyle(device.DevEUI)) From 4596cfd0bf16a6aad65c8960c8fdeeac8bd69681 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 19 Apr 2016 09:42:47 +0200 Subject: [PATCH 1336/2266] [hotfix/mqtt] Keep subscriptions on Broker --- mqtt/client.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 591a44dd5..5ce0d48eb 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -91,15 +91,19 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri mqttOpts.SetKeepAlive(30 * time.Second) mqttOpts.SetPingTimeout(10 * time.Second) + // Usually this setting should not be used together with random ClientIDs, but + // we configured The Things Network's MQTT servers to handle this correctly. + mqttOpts.SetCleanSession(false) + mqttOpts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) { if ctx != nil { - ctx.WithField("message", msg).Debug("Received unhandled message") + ctx.WithField("message", msg).Warn("Received unhandled message") } }) mqttOpts.SetConnectionLostHandler(func(client MQTT.Client, err error) { if ctx != nil { - ctx.WithError(err).Debug("Disconnected, reconnecting...") + ctx.WithError(err).Warn("Disconnected, reconnecting...") } }) From 6ae226b967465218cf254cd225ca976ad7726b49 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 19 Apr 2016 10:58:24 +0200 Subject: [PATCH 1337/2266] [hotfix] Fix gRPC dependency --- .gitmodules | 3 ++ core/handler_manager.pb.go | 80 ++++++++++++++--------------------- vendor/google.golang.org/grpc | 1 + 3 files changed, 36 insertions(+), 48 deletions(-) create mode 160000 vendor/google.golang.org/grpc diff --git a/.gitmodules b/.gitmodules index 52f98dfd2..dfc3e431c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "vendor/github.com/brocaar/lorawan"] path = vendor/github.com/brocaar/lorawan url = https://github.com/brocaar/lorawan.git +[submodule "vendor/google.golang.org/grpc"] + path = vendor/google.golang.org/grpc + url = git@github.com:grpc/grpc-go.git diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index 74a97806b..edb1cdc7f 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -688,13 +688,11 @@ func (m *GetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } return i, nil } @@ -714,13 +712,11 @@ func (m *GetDefaultDeviceRes) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.AppKey != nil { - if len(m.AppKey) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } + if len(m.AppKey) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) } return i, nil } @@ -746,21 +742,17 @@ func (m *SetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) i += copy(data[i:], m.Token) } - if m.AppEUI != nil { - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) } - if m.AppKey != nil { - if len(m.AppKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } + if len(m.AppKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) + i += copy(data[i:], m.AppKey) } return i, nil } @@ -965,11 +957,9 @@ func (m *GetDefaultDeviceReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } @@ -977,11 +967,9 @@ func (m *GetDefaultDeviceReq) Size() (n int) { func (m *GetDefaultDeviceRes) Size() (n int) { var l int _ = l - if m.AppKey != nil { - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } @@ -993,17 +981,13 @@ func (m *SetDefaultDeviceReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppEUI != nil { - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } - if m.AppKey != nil { - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } + l = len(m.AppKey) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } diff --git a/vendor/google.golang.org/grpc b/vendor/google.golang.org/grpc new file mode 160000 index 000000000..dec33edc3 --- /dev/null +++ b/vendor/google.golang.org/grpc @@ -0,0 +1 @@ +Subproject commit dec33edc378cf4971a2741cfd86ed70a644d6ba3 From d32169ab2e51dc40cca2e90472a63ff53daf51b4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 19 Apr 2016 11:07:28 +0200 Subject: [PATCH 1338/2266] [hotfix/grpc] Use HTTPS version of gRPC repo --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index dfc3e431c..9b3eea699 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = https://github.com/brocaar/lorawan.git [submodule "vendor/google.golang.org/grpc"] path = vendor/google.golang.org/grpc - url = git@github.com:grpc/grpc-go.git + url = https://github.com/grpc/grpc-go.git From d56f1032fe6ac34e80f2db7f6173f76facc1be6b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 21 Apr 2016 09:21:36 +0200 Subject: [PATCH 1339/2266] Fix not-so-random DevAddrs and AppNonces in OTAA --- core/components/handler/handler.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c96be0df1..2fbe699c4 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -7,7 +7,6 @@ import ( "bytes" "encoding/binary" "fmt" - "math/rand" "net" "time" @@ -15,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/dutycycle" "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -479,17 +479,14 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da } packet := bundles[best.ID].Packet.(*core.JoinHandlerReq) - // Generate a DevAddr - Note: this should be done by the Broker (issue #90). Random generation should be moved to the random package - rdn := rand.New(rand.NewSource(int64(packet.Metadata.Rssi))) + // Generate a DevAddr - Note: this should be done by the Broker (issue #90). var devAddr [4]byte - binary.BigEndian.PutUint32(devAddr[:], rdn.Uint32()) + copy(devAddr[:], random.Bytes(4)) devAddr[0] = (h.Configuration.NetID[2] << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb - // Generate appNonce - Note: this should be moved to the random package - b := make([]byte, 4) - binary.BigEndian.PutUint32(b, rdn.Uint32()) + // Generate appNonce var appNonce [3]byte - copy(appNonce[:], b[:3]) + copy(appNonce[:], random.Bytes(3)) var devNonce [2]byte copy(devNonce[:], packet.DevNonce) From 1c752bcb89613188483211b75b2f3488ea76324b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 21 Apr 2016 13:12:53 +0200 Subject: [PATCH 1340/2266] Update brocaar/lorawan --- core/adapters/udp/conversions.go | 2 +- core/adapters/udp/conversions_test.go | 42 +++---------------------- core/adapters/udp/udp_test.go | 28 ++++++++--------- core/components/handler/handler.go | 30 ++++++++++-------- core/components/handler/handler_test.go | 22 ++++++------- core/core.go | 12 +++---- ttnctl/cmd/join.go | 4 +-- ttnctl/cmd/uplink.go | 14 ++++----- vendor/github.com/brocaar/lorawan | 2 +- 9 files changed, 64 insertions(+), 92 deletions(-) diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go index 2379309c1..975c2a64c 100644 --- a/core/adapters/udp/conversions.go +++ b/core/adapters/udp/conversions.go @@ -30,7 +30,7 @@ func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interfa return nil, errors.New(errors.Structural, err) } } - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} if err = payload.UnmarshalBinary(raw); err != nil { return nil, errors.New(errors.Structural, err) } diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go index 808f74bff..944082211 100644 --- a/core/adapters/udp/conversions_test.go +++ b/core/adapters/udp/conversions_test.go @@ -270,38 +270,6 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { // -------------------- - { - Desc(t, "Test toLoRaWANPayload with invalid macpayload") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := lorawan.NewPHYPayload(true) - payload.MHDR.MType = lorawan.UnconfirmedDataUp - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - payload.MACPayload = pointer.Time(time.Now()) // Something marshable - data, err := payload.MarshalBinary() - FatalUnless(t, err) - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - } - - // Expectations - var wantReq interface{} - var wantErr = ErrStructural - - // Operate - req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantReq, req, "Data Router Requests") - } - - // -------------------- - { Desc(t, "Test toLoRaWANPayload with no data in rxpk") @@ -381,11 +349,11 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.ConfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := lorawan.NewMACPayload(false) + macpayload := &lorawan.MACPayload{} macpayload.FPort = new(uint8) *macpayload.FPort = 1 @@ -424,11 +392,11 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.UnconfirmedDataDown payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := lorawan.NewMACPayload(false) + macpayload := &lorawan.MACPayload{} macpayload.FPort = new(uint8) *macpayload.FPort = 1 @@ -459,7 +427,7 @@ func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { // Build gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.JoinRequest payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go index 088fd31e4..62ff97254 100644 --- a/core/adapters/udp/udp_test.go +++ b/core/adapters/udp/udp_test.go @@ -103,11 +103,11 @@ func TestUDPAdapter(t *testing.T) { Desc(t, "Send a valid packet through udp, no downlink") // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.UnconfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := lorawan.NewMACPayload(true) + macpayload := &lorawan.MACPayload{} macpayload.FPort = new(uint8) *macpayload.FPort = 1 @@ -216,11 +216,11 @@ func TestUDPAdapter(t *testing.T) { FatalUnless(t, err) // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.UnconfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := lorawan.NewMACPayload(true) + macpayload := &lorawan.MACPayload{} macpayload.FPort = new(uint8) *macpayload.FPort = 1 @@ -267,11 +267,11 @@ func TestUDPAdapter(t *testing.T) { Metadata: new(core.Metadata), } - payloadDown := lorawan.NewPHYPayload(false) + payloadDown := &lorawan.PHYPayload{} payloadDown.MHDR.MType = lorawan.MType(router.OutHandleData.Res.Payload.MHDR.MType) payloadDown.MHDR.Major = lorawan.Major(router.OutHandleData.Res.Payload.MHDR.Major) copy(payloadDown.MIC[:], router.OutHandleData.Res.Payload.MIC) - macpayloadDown := lorawan.NewMACPayload(false) + macpayloadDown := &lorawan.MACPayload{} macpayloadDown.FPort = new(uint8) *macpayloadDown.FPort = uint8(router.OutHandleData.Res.Payload.MACPayload.FPort) macpayloadDown.FHDR.FCnt = router.OutHandleData.Res.Payload.MACPayload.FHDR.FCnt @@ -372,11 +372,11 @@ func TestUDPAdapter(t *testing.T) { Desc(t, "Send a valid packet through udp, with invalid downlink") // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.UnconfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := lorawan.NewMACPayload(true) + macpayload := &lorawan.MACPayload{} macpayload.FPort = new(uint8) *macpayload.FPort = 1 @@ -602,11 +602,11 @@ func TestUDPAdapter(t *testing.T) { Desc(t, "Send a valid packet through udp, no downlink, router fails") // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.UnconfirmedDataUp payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := lorawan.NewMACPayload(true) + macpayload := &lorawan.MACPayload{} macpayload.FPort = new(uint8) *macpayload.FPort = 1 @@ -930,7 +930,7 @@ func TestUDPAdapter(t *testing.T) { FatalUnless(t, err) // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.JoinRequest payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} @@ -1037,7 +1037,7 @@ func TestUDPAdapter(t *testing.T) { Desc(t, "Send a valid join through udp, no payload in accept") // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.JoinRequest payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} @@ -1125,7 +1125,7 @@ func TestUDPAdapter(t *testing.T) { Desc(t, "Send a valid join through udp, no metadata in response") // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.JoinRequest payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} @@ -1216,7 +1216,7 @@ func TestUDPAdapter(t *testing.T) { Desc(t, "Send a valid join through udp, router fails to handle") // Build - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.JoinRequest payload.MHDR.Major = lorawan.LoRaWANR1 payload.MIC = [4]byte{1, 2, 3, 4} diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 2fbe699c4..4bea83890 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -202,7 +202,7 @@ func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (* } // 2. Verify MIC - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{Major: lorawan.LoRaWANR1, MType: lorawan.JoinRequest} joinPayload := lorawan.JoinRequestPayload{} copy(payload.MIC[:], req.MIC) @@ -670,7 +670,7 @@ func (h component) abortConsume(err error, bundles []bundle) { // constructs a downlink packet from something we pulled from the gathered downlink, and, the actual // uplink. func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { - macpayload := lorawan.NewMACPayload(false) + macpayload := &lorawan.MACPayload{} macpayload.FHDR = lorawan.FHDR{ FCnt: entry.FCntDown + 1, } @@ -680,20 +680,12 @@ func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up if ack { macpayload.FHDR.FCtrl.ACK = true } - var frmpayload []byte + if down != nil { macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} - err := macpayload.EncryptFRMPayload(entry.AppSKey) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - frmpayload, err = macpayload.FRMPayload[0].MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } } - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{ MType: mtype, Major: lorawan.LoRaWANR1, @@ -705,6 +697,18 @@ func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up return nil, errors.New(errors.Structural, err) } + var frmpayload []byte + err = payload.EncryptFRMPayload(entry.AppSKey) + if err != nil { + return nil, errors.New(errors.Structural, err) + } + if down != nil { + frmpayload, err = macpayload.FRMPayload[0].MarshalBinary() + if err != nil { + return nil, errors.New(errors.Structural, err) + } + } + metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.RXDelay), isRX2) return &core.DataUpHandlerRes{ @@ -734,7 +738,7 @@ func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up } func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte, appNonce []byte, devAddr [4]byte, isRX2 bool) (*core.JoinHandlerRes, error) { - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{ MType: lorawan.JoinAccept, Major: lorawan.LoRaWANR1, diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 6f12e49b3..12dfeb0c9 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1526,7 +1526,7 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req.AppEUI) @@ -1574,7 +1574,7 @@ func TestHandleJoin(t *testing.T) { Check(t, 16, len(res.NwkSKey), "Network session keys' length") Check(t, 4, len(res.DevAddr), "Device addresses' length") Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := lorawan.NewPHYPayload(false) + joinaccept := &lorawan.PHYPayload{} err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) @@ -1617,7 +1617,7 @@ func TestHandleJoin(t *testing.T) { appAdapter.Failures["HandleJoin"] = errors.New(errors.Operational, "Mock Error") broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req.AppEUI) @@ -1665,7 +1665,7 @@ func TestHandleJoin(t *testing.T) { Check(t, 16, len(res.NwkSKey), "Network session keys' length") Check(t, 4, len(res.DevAddr), "Device addresses' length") Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := lorawan.NewPHYPayload(false) + joinaccept := &lorawan.PHYPayload{} err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) @@ -1708,7 +1708,7 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req.AppEUI) @@ -1774,7 +1774,7 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req.AppEUI) @@ -1840,7 +1840,7 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req.AppEUI) @@ -1958,7 +1958,7 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req.AppEUI) @@ -2006,7 +2006,7 @@ func TestHandleJoin(t *testing.T) { Check(t, 16, len(res.NwkSKey), "Network session keys' length") Check(t, 4, len(res.DevAddr), "Device addresses' length") Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := lorawan.NewPHYPayload(false) + joinaccept := &lorawan.PHYPayload{} err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) @@ -2257,7 +2257,7 @@ func TestHandleJoin(t *testing.T) { appAdapter := mocks.NewAppClient() broker := mocks.NewAuthBrokerClient() - payload := lorawan.NewPHYPayload(true) + payload := &lorawan.PHYPayload{} payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} joinPayload := lorawan.JoinRequestPayload{} copy(joinPayload.AppEUI[:], req1.AppEUI) @@ -2324,7 +2324,7 @@ func TestHandleJoin(t *testing.T) { Check(t, wantRes2.Metadata, res.Metadata, "Join Handler Responses") Check(t, 16, len(res.NwkSKey), "Network session keys' length") Check(t, 4, len(res.DevAddr), "Device addresses' length") - joinaccept := lorawan.NewPHYPayload(false) + joinaccept := &lorawan.PHYPayload{} err = joinaccept.UnmarshalBinary(res.Payload.Payload) CheckErrors(t, nil, err) err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) diff --git a/core/core.go b/core/core.go index e72e4c9f3..3e4c21ac0 100644 --- a/core/core.go +++ b/core/core.go @@ -9,6 +9,7 @@ import ( "reflect" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/brocaar/lorawan" ) @@ -45,14 +46,13 @@ func NewLoRaWANData(reqPayload *LoRaWANData, uplink bool) (lorawan.PHYPayload, e return lorawan.PHYPayload{}, errors.New(errors.Structural, err) } - macpayload := lorawan.NewMACPayload(uplink) - macpayload.FPort = new(uint8) - *macpayload.FPort = uint8(mac.FPort) + macpayload := &lorawan.MACPayload{} + macpayload.FPort = pointer.Uint8(uint8(mac.FPort)) copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) macpayload.FHDR.FCnt = fhdr.FCnt for _, data := range fhdr.FOpts { cmd := new(lorawan.MACCommand) - if err := cmd.UnmarshalBinary(data); err == nil { // We ignore invalid commands + if err := cmd.UnmarshalBinary(uplink, data); err == nil { // We ignore invalid commands macpayload.FHDR.FOpts = append(macpayload.FHDR.FOpts, *cmd) } } @@ -63,13 +63,13 @@ func NewLoRaWANData(reqPayload *LoRaWANData, uplink bool) (lorawan.PHYPayload, e macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ Bytes: mac.FRMPayload, }} - payload := lorawan.NewPHYPayload(uplink) + payload := &lorawan.PHYPayload{} payload.MHDR.MType = lorawan.MType(mhdr.MType) payload.MHDR.Major = lorawan.Major(mhdr.Major) copy(payload.MIC[:], reqPayload.MIC) payload.MACPayload = macpayload - return payload, nil + return *payload, nil } // ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index e688bf416..584b5b2ab 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -58,7 +58,7 @@ var joinCmd = &cobra.Command{ DevEUI: devEUI, DevNonce: devNonce, } - phyPayload := lorawan.NewPHYPayload(true) + phyPayload := &lorawan.PHYPayload{} phyPayload.MHDR = lorawan.MHDR{ MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1, @@ -126,7 +126,7 @@ var joinCmd = &cobra.Command{ ctx.Fatalf("Unable to decode data payload: %s", err) } - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} if err := payload.UnmarshalBinary(data); err != nil { ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 7f1a05c91..19b7ed9df 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -67,22 +67,22 @@ var uplinkCmd = &cobra.Command{ } // Lorawan Payload - macPayload := lorawan.NewMACPayload(true) + macPayload := &lorawan.MACPayload{} macPayload.FHDR = lorawan.FHDR{ DevAddr: devAddr, FCnt: uint32(fcnt), } macPayload.FPort = pointer.Uint8(1) macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} - if err := macPayload.EncryptFRMPayload(appSKey); err != nil { - ctx.Fatalf("Unable to encrypt frame payload: %s", err) - } - phyPayload := lorawan.NewPHYPayload(true) + phyPayload := &lorawan.PHYPayload{} phyPayload.MHDR = lorawan.MHDR{ MType: mtype, Major: lorawan.LoRaWANR1, } phyPayload.MACPayload = macPayload + if err := phyPayload.EncryptFRMPayload(appSKey); err != nil { + ctx.Fatalf("Unable to encrypt frame payload: %s", err) + } if err := phyPayload.SetMIC(nwkSKey); err != nil { ctx.Fatalf("Unable to set MIC: %s", err) } @@ -145,7 +145,7 @@ var uplinkCmd = &cobra.Command{ ctx.Fatalf("Unable to decode data payload: %s", err) } - payload := lorawan.NewPHYPayload(false) + payload := &lorawan.PHYPayload{} if err := payload.UnmarshalBinary(data); err != nil { ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) } @@ -156,7 +156,7 @@ var uplinkCmd = &cobra.Command{ } ctx.Infof("Frame counter: %d", macPayload.FHDR.FCnt) if len(macPayload.FRMPayload) > 0 { - if err := macPayload.DecryptFRMPayload(appSKey); err != nil { + if err := phyPayload.DecryptFRMPayload(appSKey); err != nil { ctx.Fatalf("Unable to decrypt MACPayload: %s", err) } ctx.Infof("Decrypted Payload: %s", string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes)) diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan index 6a90a1f55..a932a7086 160000 --- a/vendor/github.com/brocaar/lorawan +++ b/vendor/github.com/brocaar/lorawan @@ -1 +1 @@ -Subproject commit 6a90a1f556b1081e42474519b5b051187b3c28b6 +Subproject commit a932a70865e10d377ff5863ed86fa9f8b35c6f22 From 2c53d0d35822cc5acdc693f6b595da73e17b9216 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 21 Apr 2016 20:12:40 +0200 Subject: [PATCH 1341/2266] Remove documents from this repo --- documents/protocols/protocols.md | 116 ------------------------------ documents/protocols/semtech.pdf | Bin 854079 -> 0 bytes documents/reports/01_feb_2016.pdf | Bin 114971 -> 0 bytes 3 files changed, 116 deletions(-) delete mode 100644 documents/protocols/protocols.md delete mode 100644 documents/protocols/semtech.pdf delete mode 100644 documents/reports/01_feb_2016.pdf diff --git a/documents/protocols/protocols.md b/documents/protocols/protocols.md deleted file mode 100644 index 762bcbc98..000000000 --- a/documents/protocols/protocols.md +++ /dev/null @@ -1,116 +0,0 @@ -semtech ~ udp -============= - -Have a look at [this document](https://github.com/TheThingsNetwork/ttn/blob/develop/documents/protocols/semtech.pdf) - -basic ~ http -============ - -The basic http protocol relies seemingly on `http`. - -An adapter which implements this protocol should provide at least one end-point: - -- `[POST] /packets` - - -#### Request - -Packets are sent as a json payload of the following shape: - -```js - { - "payload": , - "metadata": { - "chan": ..., // Concentrator "IF" channel used for RX (unsigned integer) - "codr": ..., // LoRa ECC coding rate identifier - "datr": ..., // LoRa datarate identifier - "fdev": ..., // FSK frequency deviation (unsigned integer, in Hz) - "freq": ..., // RX Central frequency in MHx (unsigned float, Hz precision) - "imme": ..., // Send packet immediately (will ignore tmst & time) - "ipol": ..., // Lora modulation polarization inversion - "lsnr": ..., // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - "modu": ..., // Modulation identifier "LORA" - "ncrc": ..., // If true, disable the CRC of the physical layer (optional) - "powe": ..., // TX output power in dBm (unsigned integer, dBm precision) - "prea": ..., // RF preamble size (unsigned integer) - "rfch": ..., // Concentrator "RF chain" used for RX (unsigned integer) - "rssi": ..., // RSSI in dBm (signed integer, 1 dB precision) - "size": ..., // RF packet payload size in bytes (unsigned integer) - "stat": ..., // CRC status: 1 - OK, -1 = fail, 0 = no CRC - "time": ..., // UTC time of pkt RX, us precision, ISO 8601 'compact' format - "tmst": ... // Internal timestamp of "RX finished" event (32b unsigned) - } - } -``` - -All fields in metadata are optional, so is the metadata field itself. The payload should be a -base64 encoded binary representation of a Physical Payload as defined by the -[lorawan](https://github.com/brocaar/lorawan) go package - -#### Response - -The adapter may provide two answers to the demander. - -- An `HTTP 200 Ok.` means that the packet has been accepted and is handled by the server. - -- An `HTTP 404 Not Found.` means that the server doesn't take care of packet coming from the - end-device related to the packet. - -Another type of response could be misinterpreted by the sender. An `404` response doesn't -contain any body payload. However, a `200` might. In such a case, the response has the same -shape as the one described above: a plain `json` with an encoded physical payload and some -possible metadata. - -basic+pubsub ~ http -=================== - -The `pubsub` http adapter is an extension of the `basic` http adapter. In addition of the -behavior defined in the corresponding section, the `pubsub` adapter also provide the following -end-point: - -- `[PUT] /end-devices/:devAddr` - -where `:devAddr` identify a device address encoded as an hexadecimal string of 8 characters (2 -characters for a single byte), for instance: "09a3bc52". - -This end-point is used to register a handler for a given end-device such that every packet of -the network coming from that device will be forwarded via http to the handler. - -#### Request - -Requests are expected to come along with a `json` payload of the following shape: - -```js - { - "app_id": ..., // Application identifier (string) - "app_url": ..., // Webhook to which forward incoming data (string) - "nws_key": ... // The network session key associated to the device (string, 32 characters) - } -``` - -The network session key `nws_key` is supposed to be an hexadecimal encoded version of the -associated network session key. - -#### Response - -As a response, the emitter might consider three situations: - -- `HTTP 202 Accepted.` as a confirmation of the registration - -- `HTTP 400 Bad Request.` if the request or the parameters aren't valid - -- `HTTP 409 Conflict.` if for some reason, the end-device cannot be registered - -All those requests have empty payloads. - -basic+broadcast ~ http -====================== - -The `broadcast` http adapter is an extension of the `basic` http adapter. This adapter enables -network discovery through a simple convention. When no recipient is provided to the adapter for -a send request, it will seemly broadcast the request to every accessible recipient reachable. - -Thus, because it relies on the basic http protocol, it will ignore `404 Not Found` responses -from servers but, will generate a new registration demand for a `200 Ok` received. So far, a -maximum of only one positive anwer among all is expected. Positive acknowledgement for -different servers will lead to an error. diff --git a/documents/protocols/semtech.pdf b/documents/protocols/semtech.pdf deleted file mode 100644 index 1295523a4edb9338aee928f5f95b829582eacb86..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 854079 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Ð#%z3DUv3A*LGg7 zFM-N^X3nV>OYVa4j_jfqn O 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
%$iYn3nm7AuI$&W3?Z)Vt^1nD!tja+< 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%0A6?8qE5-5D&|C1897^AJ?VJ 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 z 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∾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 z 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 diff --git a/documents/reports/01_feb_2016.pdf b/documents/reports/01_feb_2016.pdf deleted file mode 100644 index 9d68dc08ac9355b1d93a5a6c36e952991d727efa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 114971 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 z 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 Date: Mon, 25 Apr 2016 00:03:29 +0200 Subject: [PATCH 1342/2266] Added developer mode for ADR devices --- core/application.pb.go | 68 ----------- core/broker_manager.pb.go | 57 +++++++-- core/components/broker/broker.go | 18 ++- core/components/broker/brokerManager.go | 1 + core/components/broker/controller.go | 14 ++- core/components/broker/controller_test.go | 14 ++- core/components/broker/mocks_test.go | 4 +- core/components/handler/devStorage.go | 3 + core/components/handler/handler.go | 4 + core/components/handler/handlerManager.go | 5 +- core/core.pb.go | 68 +++++++++++ core/handler.pb.go | 93 ++++++++++----- core/handler_manager.pb.go | 135 ++++++++++++++++------ core/protos/broker_manager.proto | 1 + core/protos/handler.proto | 1 + core/protos/handler_manager.proto | 2 + ttnctl/cmd/device.go | 15 ++- utils/readwriter/readwriter.go | 9 ++ 18 files changed, 354 insertions(+), 158 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 3a459dfc9..4a5872d17 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -2,70 +2,6 @@ // source: application.proto // DO NOT EDIT! -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - application.proto - broker.proto - broker_manager.proto - core.proto - handler.proto - handler_manager.proto - lorawan.proto - router.proto - - It has these top-level messages: - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - ValidateOTAABrokerReq - ValidateOTAABrokerRes - UpsertABPBrokerReq - UpsertABPBrokerRes - BrokerDevice - ValidateTokenBrokerReq - ValidateTokenBrokerRes - Metadata - StatsMetadata - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - UpsertOTAAHandlerReq - UpsertOTAAHandlerRes - UpsertABPHandlerReq - UpsertABPHandlerRes - ListDevicesHandlerReq - ListDevicesHandlerRes - HandlerABPDevice - HandlerOTAADevice - GetDefaultDeviceReq - GetDefaultDeviceRes - SetDefaultDeviceReq - SetDefaultDeviceRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes -*/ package core import proto "github.com/golang/protobuf/proto" @@ -84,10 +20,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - type DataAppReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index a4908b3a7..3ad8392ff 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -49,6 +49,7 @@ type UpsertABPBrokerReq struct { NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` DevAddr []byte `protobuf:"bytes,4,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,5,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + DevMode bool `protobuf:"varint,6,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` } func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } @@ -321,6 +322,16 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) } + if m.DevMode { + data[i] = 0x30 + i++ + if m.DevMode { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -500,6 +511,9 @@ func (m *UpsertABPBrokerReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } + if m.DevMode { + n += 2 + } return n } @@ -929,6 +943,26 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { m.NwkSKey = []byte{} } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -1409,7 +1443,7 @@ var ( ) var fileDescriptorBrokerManager = []byte{ - // 327 bytes of a gzipped FileDescriptorProto + // 343 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x4a, 0xe5, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, @@ -1417,18 +1451,19 @@ var fileDescriptorBrokerManager = []byte{ 0x0d, 0x01, 0xb2, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x58, 0x4b, 0x40, 0x1c, 0x21, 0x31, 0x2e, 0x36, 0xc7, 0x82, 0x02, 0xd7, 0x50, 0x4f, 0x09, 0x26, 0xa0, 0x30, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x27, 0x24, 0xc7, 0xc5, 0xe5, 0x97, 0x5a, 0xe2, 0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, - 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x34, + 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x06, 0x46, 0x2e, 0xa1, 0xd0, 0x82, 0xe2, 0xd4, 0xa2, 0x12, 0x47, 0xa7, 0x00, 0x1a, 0xd9, 0x2e, 0x24, 0xc1, 0xc5, 0xee, 0x92, 0x5a, 0x06, 0xe2, 0x49, 0xb0, 0x80, 0x35, 0xb2, 0xa7, 0x40, 0xb8, 0x20, - 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0xab, 0x24, - 0x82, 0xc5, 0x5d, 0xc5, 0x4a, 0x51, 0x5c, 0x3c, 0x10, 0x0e, 0xd0, 0xbc, 0xcc, 0xe4, 0x54, 0x90, - 0x8b, 0x80, 0x2c, 0x90, 0x8b, 0x18, 0x21, 0x2e, 0x4a, 0x01, 0xf3, 0x90, 0x6d, 0x64, 0xc2, 0x69, - 0x23, 0x33, 0xaa, 0x8d, 0x6e, 0x5c, 0x62, 0xb0, 0x30, 0x02, 0xfb, 0x9d, 0xcc, 0xd0, 0x50, 0x92, - 0xc0, 0x61, 0x4e, 0xb1, 0xd1, 0x73, 0x46, 0x2e, 0x5e, 0x08, 0xcf, 0x17, 0x92, 0x14, 0x84, 0x3c, - 0xb8, 0x78, 0x90, 0xe3, 0x45, 0x48, 0x5a, 0x0f, 0x94, 0x2a, 0xf4, 0xb0, 0x26, 0x09, 0x29, 0x3c, - 0x92, 0xc5, 0x42, 0xf6, 0x5c, 0x9c, 0xf0, 0xf0, 0x12, 0x92, 0x80, 0xa8, 0xc4, 0x8c, 0x58, 0x29, + 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0x0b, 0xd5, + 0xe3, 0x9b, 0x9f, 0x92, 0x2a, 0xc1, 0x06, 0x94, 0xe1, 0x00, 0xeb, 0x01, 0x71, 0x95, 0x44, 0xb0, + 0xb8, 0xb8, 0x58, 0x29, 0x8a, 0x8b, 0x07, 0xc2, 0x01, 0xea, 0xca, 0x4c, 0x4e, 0x05, 0xb9, 0x15, + 0xc8, 0x02, 0xb9, 0x95, 0x11, 0xe2, 0xd6, 0x14, 0x30, 0x0f, 0xd9, 0x2d, 0x4c, 0x38, 0xdd, 0xc2, + 0x8c, 0xe2, 0x16, 0x25, 0x37, 0x2e, 0x31, 0x58, 0xe8, 0x81, 0x43, 0x85, 0xcc, 0x70, 0x52, 0x92, + 0xc0, 0x61, 0x4e, 0xb1, 0xd1, 0x73, 0x46, 0x2e, 0x5e, 0x08, 0xcf, 0x17, 0x92, 0x48, 0x84, 0x3c, + 0xb8, 0x78, 0x90, 0x63, 0x4c, 0x48, 0x5a, 0x0f, 0x94, 0x5e, 0xf4, 0xb0, 0x26, 0x16, 0x29, 0x3c, + 0x92, 0xc5, 0x42, 0xf6, 0x5c, 0x9c, 0xf0, 0xf0, 0x12, 0x92, 0x80, 0xa8, 0xc4, 0x8c, 0x72, 0x29, 0x5c, 0x32, 0xc5, 0x42, 0xde, 0x5c, 0xbc, 0x28, 0xce, 0x16, 0x92, 0x41, 0xb5, 0x0e, 0x35, 0x4c, 0xa4, 0xf0, 0xc9, 0x16, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, - 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0x54, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x06, - 0x4b, 0xeb, 0xf5, 0x0d, 0x03, 0x00, 0x00, + 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0xfc, 0x60, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xc6, + 0x07, 0x41, 0x5b, 0x27, 0x03, 0x00, 0x00, } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 7d69dcf2e..e1fa637e7 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -194,6 +194,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c AppEUI: req.AppEUI, DevEUI: req.DevEUI, NwkSKey: nwkSKey, + DevMode: false, FCntUp: 0, }) if err != nil { @@ -245,6 +246,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c fhdr := &uplinkPayload.MACPayload.(*lorawan.MACPayload).FHDR // No nil ref, ensured by NewLoRaWANData() fcnt16 := fhdr.FCnt // Keep a reference to the original counter + fcntReset := false var mEntry *devEntry for _, entry := range entries { // retrieve the network session key @@ -252,11 +254,18 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // Check with 16-bits counters fhdr.FCnt = fcnt16 - fcnt32, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) + fcnt32, counterReset, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) if err != nil { continue } + if counterReset { + dtx := ctx.WithFields(log.Fields{ + "DevMode": entry.DevMode, + }) + dtx.Debug("Counter reset detected") + } + ok, err := uplinkPayload.ValidateMIC(key) if err != nil { continue @@ -270,6 +279,12 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } if ok { + if counterReset { + if !entry.DevMode { + continue + } + fcntReset = true + } mEntry = &entry stats.MarkMeter("broker.uplink.handler_lookup.mic_match") ctx = ctx.WithFields(log.Fields{ @@ -312,6 +327,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c FPort: req.Payload.MACPayload.FPort, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, + FCUPRst: fcntReset, }) if err != nil { diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index c043e4f8f..7e54b4b83 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -87,6 +87,7 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), DevAddr: req.DevAddr, NwkSKey: nwkSKey, + DevMode: req.DevMode, FCntUp: 0, }) if err != nil { diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 84aabaee8..7f1b78ac2 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -22,7 +22,7 @@ type NetworkController interface { readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) upsertNonces(entry noncesEntry) error upsert(entry devEntry) error - wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) + wholeCounter(devCnt uint32, entryCnt uint32) (uint32, bool, error) done() error } @@ -33,6 +33,7 @@ type devEntry struct { Dialer Dialer FCntUp uint32 NwkSKey [16]byte + DevMode bool } type noncesEntry struct { @@ -68,19 +69,22 @@ func (s *controller) read(devAddr []byte) ([]devEntry, error) { } // wholeCounter implements the broker.NetworkController interface -func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { +func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, bool, error) { upperSup := int(math.Pow(2, 16)) diff := int(devCnt) - (int(entryCnt) % upperSup) var offset int if diff >= 0 { offset = diff } else { + if entryCnt < (uint32(upperSup) - 10) { + return devCnt, true, nil + } offset = upperSup + diff } if offset > upperSup/4 { - return 0, errors.New(errors.Structural, "Gap too big, counter is errored") + return 0, false, errors.New(errors.Structural, "Gap too big, counter is errored") } - return entryCnt + uint32(offset), nil + return entryCnt + uint32(offset), false, nil } // upsert implements the broker.NetworkController interface @@ -141,6 +145,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.DevAddr) rw.Write(e.NwkSKey[:]) rw.Write(e.FCntUp) + rw.Write(e.DevMode) rw.Write(e.Dialer.MarshalSafely()) return rw.Bytes() } @@ -162,6 +167,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) + rw.Read(func(data []byte) { e.DevMode = (data[0] != 0) }) rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) return rw.Err() } diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index bfd1d9b14..8d5253f1f 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -252,7 +252,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt + 1 // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) @@ -272,10 +272,12 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt - 1 // Operate - _, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, countReset, err := db.wholeCounter(cnt16, wholeCnt) // Check - CheckErrors(t, ErrStructural, err) + CheckErrors(t, nil, err) + Check(t, cnt16, cnt32, "Counters") + Check(t, true, countReset, "Counter reset") _ = db.done() } @@ -291,7 +293,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 2) // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) @@ -311,7 +313,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 45000) // Operate - _, err := db.wholeCounter(cnt16, wholeCnt) + _, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, ErrStructural, err) @@ -330,7 +332,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(2) // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go index 46f1a36ed..803eaca37 100644 --- a/core/components/broker/mocks_test.go +++ b/core/components/broker/mocks_test.go @@ -176,10 +176,10 @@ func (m *MockNetworkController) upsertNonces(entry noncesEntry) error { } // wholeCnt implements the NetworkController interface -func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, error) { +func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, bool, error) { m.InWholeCounter.DevCnt = devCnt m.InWholeCounter.EntryCnt = entryCnt - return m.OutWholeCounter.FCnt, m.Failures["wholeCounter"] + return m.OutWholeCounter.FCnt, false, m.Failures["wholeCounter"] } // done implements the NetworkController Interface diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 32b0f5a10..821424d8f 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -33,6 +33,7 @@ type devEntry struct { FCntDown uint32 FCntUp uint32 NwkSKey [16]byte + DevMode bool } type devDefaultEntry struct { @@ -105,6 +106,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.NwkSKey[:]) rw.Write(e.FCntUp) rw.Write(e.FCntDown) + rw.Write(e.DevMode) rw.Write(e.AppEUI) rw.Write(e.DevEUI) rw.Write(e.DevAddr) @@ -126,6 +128,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) + rw.Read(func(data []byte) { e.DevMode = (data[0] != 0) }) rw.Read(func(data []byte) { e.AppEUI = make([]byte, len(data)) copy(e.AppEUI, data) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c96be0df1..1abb001bc 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -511,6 +511,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da FCntDown: 0, FCntUp: 0, NwkSKey: nwkSKey, + DevMode: false, }) if err != nil { ctx.WithError(err).Debug("Unable to initialize devEntry with activation") @@ -633,6 +634,9 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu stats.MarkMeter("handler.downlink.pull") downType := lorawan.UnconfirmedDataDown ack := (upType == lorawan.ConfirmedDataUp) + if bundle.Packet.(*core.DataUpHandlerReq).FCUPRst { + bundle.Entry.FCntDown = 0 + } downlink, err := h.buildDownlink(downlink.Payload, downType, ack, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) if err != nil { h.abortConsume(errors.New(errors.Structural, err), bundles) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 12990bd42..61281eb91 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -44,6 +44,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle AppSKey: d.AppSKey[:], FCntUp: d.FCntUp, FCntDown: d.FCntDown, + DevMode: d.DevMode, }) } else { otaa = append(otaa, &core.HandlerOTAADevice{ @@ -72,13 +73,14 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq return new(core.UpsertABPHandlerRes), err } - // 2. Forward to the broker firt -> The Broker also does the token verification + // 2. Forward to the broker first -> The Broker also does the token verification _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ Token: req.Token, AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, NetAddress: h.PrivateNetAddrAnnounce, + DevMode: req.DevMode, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") @@ -93,6 +95,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq DevAddr: req.DevAddr, FCntDown: 0, FCntUp: 0, + DevMode: req.DevMode, } copy(entry.NwkSKey[:], req.NwkSKey) copy(entry.AppSKey[:], req.AppSKey) diff --git a/core/core.pb.go b/core/core.pb.go index 0cf07130f..6a9885c56 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -2,6 +2,70 @@ // source: core.proto // DO NOT EDIT! +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + core.proto + broker.proto + broker_manager.proto + handler_manager.proto + lorawan.proto + handler.proto + application.proto + router.proto + + It has these top-level messages: + Metadata + StatsMetadata + DataBrokerReq + DataBrokerRes + JoinBrokerReq + JoinBrokerRes + ValidateOTAABrokerReq + ValidateOTAABrokerRes + UpsertABPBrokerReq + UpsertABPBrokerRes + BrokerDevice + ValidateTokenBrokerReq + ValidateTokenBrokerRes + UpsertOTAAHandlerReq + UpsertOTAAHandlerRes + UpsertABPHandlerReq + UpsertABPHandlerRes + ListDevicesHandlerReq + ListDevicesHandlerRes + HandlerABPDevice + HandlerOTAADevice + GetDefaultDeviceReq + GetDefaultDeviceRes + SetDefaultDeviceReq + SetDefaultDeviceRes + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + JoinHandlerReq + JoinHandlerRes + DataAppReq + DataAppRes + JoinAppReq + JoinAppRes + DataRouterReq + DataRouterRes + StatsReq + StatsRes + JoinRouterReq + JoinRouterRes +*/ package core import proto "github.com/golang/protobuf/proto" @@ -15,6 +79,10 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + type Metadata struct { DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` diff --git a/core/handler.pb.go b/core/handler.pb.go index 52569d9b4..41e869e87 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -26,6 +26,7 @@ type DataUpHandlerReq struct { FCnt uint32 `protobuf:"varint,10,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` FPort uint32 `protobuf:"varint,11,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` MType uint32 `protobuf:"varint,12,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` + FCUPRst bool `protobuf:"varint,13,opt,name=FCUPRst,json=fCUPRst,proto3" json:"FCUPRst,omitempty"` Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } @@ -298,6 +299,16 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandler(data, i, uint64(m.MType)) } + if m.FCUPRst { + data[i] = 0x68 + i++ + if m.FCUPRst { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } if len(m.Payload) > 0 { data[i] = 0xa2 i++ @@ -584,6 +595,9 @@ func (m *DataUpHandlerReq) Size() (n int) { if m.MType != 0 { n += 1 + sovHandler(uint64(m.MType)) } + if m.FCUPRst { + n += 2 + } l = len(m.Payload) if l > 0 { n += 2 + l + sovHandler(uint64(l)) @@ -846,6 +860,26 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { break } } + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCUPRst", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FCUPRst = bool(v != 0) case 20: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) @@ -1760,33 +1794,34 @@ var ( ) var fileDescriptorHandler = []byte{ - // 443 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x93, 0xcf, 0x8e, 0xd3, 0x30, - 0x10, 0xc6, 0x09, 0xed, 0xb6, 0x65, 0x9a, 0x56, 0x65, 0x54, 0x16, 0x2b, 0x87, 0x0a, 0xe5, 0x84, - 0x40, 0x5a, 0x89, 0x72, 0xe1, 0x84, 0x14, 0x1a, 0x56, 0x14, 0xda, 0x6a, 0x65, 0xb2, 0xe2, 0x6c, - 0x62, 0xaf, 0x40, 0xdb, 0xc6, 0x26, 0x89, 0xa8, 0xfa, 0x26, 0x88, 0x2b, 0x2f, 0xb3, 0xdc, 0x78, - 0x04, 0x04, 0x2f, 0x82, 0xff, 0x04, 0x55, 0x29, 0xdd, 0xc3, 0xd2, 0x43, 0xa4, 0xf9, 0x7e, 0xf1, - 0x8c, 0xbf, 0xf1, 0xd8, 0xd0, 0xfb, 0xc0, 0x32, 0xbe, 0x14, 0xf9, 0x89, 0xca, 0x65, 0x29, 0xb1, - 0x99, 0xca, 0x5c, 0x04, 0xbd, 0xa5, 0xcc, 0xd9, 0x9a, 0x65, 0x0e, 0x06, 0x60, 0xa0, 0x8b, 0xc3, - 0x2b, 0x0f, 0x06, 0x31, 0x2b, 0xd9, 0xb9, 0x7a, 0xe5, 0x12, 0xa9, 0xf8, 0x84, 0xc7, 0xd0, 0x8a, - 0x94, 0x7a, 0x79, 0x3e, 0x25, 0xde, 0x03, 0xef, 0xa1, 0x4f, 0x5b, 0xcc, 0x2a, 0xc3, 0x63, 0xf1, - 0xd9, 0xf0, 0xdb, 0x8e, 0x73, 0xab, 0x10, 0xa1, 0x79, 0x3a, 0xc9, 0x4a, 0x02, 0x9a, 0xf6, 0x68, - 0xf3, 0x42, 0xc7, 0x38, 0x84, 0xa3, 0xd3, 0x33, 0x99, 0x97, 0xa4, 0x6b, 0xe1, 0xd1, 0x85, 0x11, - 0x86, 0xce, 0x93, 0x8d, 0x12, 0xc4, 0x77, 0x74, 0x65, 0x04, 0x12, 0x68, 0x9f, 0xb1, 0xcd, 0x52, - 0x32, 0x4e, 0x86, 0xb6, 0x70, 0x5b, 0x39, 0x89, 0x8f, 0xa0, 0x33, 0x17, 0x25, 0xe3, 0xda, 0x21, - 0x19, 0xe9, 0x5f, 0xdd, 0x71, 0xff, 0xc4, 0xba, 0xff, 0x4b, 0x69, 0x67, 0x55, 0x45, 0xe1, 0xe5, - 0x3f, 0x9d, 0x14, 0xf8, 0xb8, 0x5e, 0xb9, 0x3b, 0xbe, 0xeb, 0xd2, 0x67, 0x92, 0xb2, 0x77, 0xd1, - 0xc2, 0xac, 0xff, 0xbf, 0xcd, 0x14, 0xa0, 0x49, 0x8e, 0xe5, 0x3a, 0x3b, 0xe0, 0xe0, 0xae, 0x6f, - 0x7c, 0x00, 0x8d, 0x24, 0x99, 0x59, 0x1b, 0x77, 0x68, 0xa3, 0x4c, 0x66, 0xe1, 0x70, 0xcf, 0x8e, - 0x45, 0xf8, 0xd5, 0x83, 0xfe, 0x6b, 0xf9, 0xf1, 0x10, 0x13, 0x01, 0x74, 0x34, 0x5f, 0xc8, 0x2c, - 0x15, 0x76, 0x82, 0x3e, 0xed, 0xf0, 0x4a, 0x1b, 0x1b, 0xf3, 0xe9, 0xc4, 0xce, 0xd0, 0xa7, 0x8d, - 0xd5, 0x74, 0x72, 0xa3, 0x43, 0xfa, 0xb6, 0x6b, 0xae, 0x30, 0x1d, 0xeb, 0xcd, 0x22, 0xce, 0xf3, - 0xca, 0x5d, 0x9b, 0x3b, 0x89, 0x4f, 0x76, 0x47, 0x75, 0xbf, 0x36, 0x2a, 0x53, 0x27, 0x4a, 0x53, - 0xa1, 0xca, 0xed, 0x21, 0xe9, 0x62, 0x8b, 0xf5, 0xe5, 0xdb, 0x37, 0x62, 0x43, 0xee, 0xb9, 0x62, - 0x99, 0x93, 0x37, 0x71, 0x39, 0xfe, 0xee, 0x41, 0xbb, 0x72, 0x88, 0xcf, 0xc1, 0x77, 0xa1, 0xbb, - 0x49, 0x78, 0xec, 0xb2, 0x76, 0x5f, 0x48, 0xb0, 0x9f, 0x17, 0x18, 0x43, 0x7f, 0x9b, 0x6f, 0x46, - 0x85, 0x64, 0xbb, 0xb2, 0x7e, 0x59, 0x82, 0xeb, 0xfe, 0x14, 0xf8, 0x0c, 0xc0, 0x29, 0xd3, 0x34, - 0x0e, 0xdd, 0xba, 0xfa, 0x94, 0x83, 0x7d, 0xb4, 0x78, 0x31, 0xb8, 0xfa, 0x35, 0xf2, 0x7e, 0xe8, - 0xef, 0xa7, 0xfe, 0xbe, 0xfc, 0x1e, 0xdd, 0x7a, 0xdf, 0xb2, 0xef, 0xfc, 0xe9, 0x9f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x4a, 0x5b, 0xdc, 0x30, 0x19, 0x04, 0x00, 0x00, + // 456 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x94, 0x41, 0xaf, 0x12, 0x31, + 0x10, 0xc7, 0x5d, 0xe1, 0xb1, 0xeb, 0xb0, 0x10, 0x9c, 0xe0, 0xb3, 0xd9, 0x03, 0x31, 0x9c, 0x8c, + 0x26, 0x2f, 0x11, 0x2f, 0x9e, 0x4c, 0x10, 0x24, 0xa2, 0x40, 0x48, 0x85, 0x78, 0xae, 0x6c, 0x89, + 0xe6, 0xc1, 0x76, 0xdd, 0x6d, 0x24, 0x7c, 0x13, 0xe3, 0xd5, 0x2f, 0xa3, 0x37, 0x3f, 0x82, 0x79, + 0x7e, 0x11, 0xdb, 0x0e, 0x86, 0x2c, 0xf2, 0x0e, 0x4f, 0x0e, 0x9b, 0xf4, 0xff, 0x6b, 0x67, 0xe6, + 0x3f, 0x9d, 0x02, 0xd4, 0x3e, 0x88, 0x24, 0x5e, 0xc9, 0xec, 0x22, 0xcd, 0x94, 0x56, 0x58, 0x5e, + 0xa8, 0x4c, 0x46, 0xb5, 0x95, 0xca, 0xc4, 0x46, 0x24, 0x04, 0x23, 0xb0, 0x90, 0xd6, 0xed, 0x2b, + 0x0f, 0x1a, 0x7d, 0xa1, 0xc5, 0x3c, 0x7d, 0x45, 0x81, 0x5c, 0x7e, 0xc2, 0x73, 0xa8, 0x74, 0xd3, + 0xf4, 0xe5, 0x7c, 0xc8, 0xbc, 0x07, 0xde, 0xc3, 0x90, 0x57, 0x84, 0x53, 0x96, 0xf7, 0xe5, 0x67, + 0xcb, 0x6f, 0x13, 0x8f, 0x9d, 0x42, 0x84, 0xf2, 0xa0, 0x97, 0x68, 0x06, 0x86, 0xd6, 0x78, 0x79, + 0x69, 0xd6, 0xd8, 0x84, 0xb3, 0xc1, 0x54, 0x65, 0x9a, 0x55, 0x1d, 0x3c, 0x5b, 0x5a, 0x61, 0xe9, + 0x78, 0xb6, 0x4d, 0x25, 0x0b, 0x89, 0xae, 0xad, 0x40, 0x06, 0xfe, 0xa0, 0x37, 0x9f, 0xf2, 0x5c, + 0xb3, 0x9a, 0xe1, 0x01, 0xf7, 0x97, 0x24, 0xed, 0xce, 0x54, 0x6c, 0x57, 0x4a, 0xc4, 0xac, 0xe9, + 0x4a, 0xfa, 0x29, 0x49, 0x7c, 0x04, 0xc1, 0x58, 0x6a, 0x11, 0x1b, 0xef, 0xac, 0x65, 0xb6, 0xaa, + 0x9d, 0xfa, 0x85, 0xeb, 0xeb, 0x2f, 0xe5, 0xc1, 0x7a, 0xb7, 0x6a, 0x5f, 0xfe, 0xd3, 0x63, 0x8e, + 0x8f, 0x8b, 0x99, 0xab, 0x9d, 0xbb, 0x14, 0x3e, 0x52, 0x5c, 0xbc, 0xeb, 0x4e, 0xec, 0xf9, 0xff, + 0x2b, 0x96, 0x02, 0xda, 0xe0, 0xbe, 0xda, 0x24, 0x27, 0x5c, 0xe9, 0xf5, 0x8d, 0x37, 0xa0, 0x34, + 0x9b, 0x8d, 0x9c, 0x8d, 0x3b, 0xbc, 0xa4, 0x67, 0xa3, 0x76, 0xf3, 0x48, 0xc5, 0xbc, 0xfd, 0xd5, + 0x83, 0xfa, 0x6b, 0xf5, 0xf1, 0x14, 0x13, 0x11, 0x04, 0x86, 0x4f, 0x54, 0xb2, 0x90, 0x6e, 0xb6, + 0x21, 0x0f, 0xe2, 0x9d, 0xb6, 0x36, 0xc6, 0xc3, 0x9e, 0x9b, 0x6e, 0xc8, 0x4b, 0xeb, 0x61, 0xef, + 0x46, 0x97, 0xf4, 0xed, 0xd0, 0x5c, 0x6e, 0x3b, 0x36, 0xc5, 0xba, 0x71, 0x9c, 0xed, 0xdc, 0xf9, + 0x31, 0x49, 0x7c, 0x72, 0x38, 0xaa, 0xfb, 0x85, 0x51, 0xd9, 0x3c, 0xdd, 0xc5, 0x42, 0xa6, 0x7a, + 0x7f, 0x49, 0x26, 0xd9, 0x64, 0x73, 0xf9, 0xf6, 0x8d, 0xdc, 0xb2, 0x7b, 0x94, 0x2c, 0x21, 0x79, + 0x13, 0x97, 0x9d, 0x1f, 0x1e, 0xf8, 0x3b, 0x87, 0xf8, 0x1c, 0x42, 0x5a, 0xd2, 0x4b, 0xc2, 0x73, + 0x8a, 0x3a, 0xfc, 0xed, 0x44, 0xc7, 0x79, 0x8e, 0x7d, 0xa8, 0xef, 0xe3, 0xed, 0xa8, 0x90, 0xed, + 0x4f, 0x16, 0x1f, 0x4b, 0x74, 0xdd, 0x4e, 0x8e, 0xcf, 0x00, 0x48, 0xd9, 0xa6, 0xb1, 0x49, 0xe7, + 0x8a, 0x53, 0x8e, 0x8e, 0xd1, 0xfc, 0x45, 0xe3, 0xfb, 0x55, 0xcb, 0xfb, 0x69, 0xbe, 0x5f, 0xe6, + 0xfb, 0xf2, 0xbb, 0x75, 0xeb, 0x7d, 0xc5, 0xfd, 0x03, 0x3c, 0xfd, 0x13, 0x00, 0x00, 0xff, 0xff, + 0x33, 0x19, 0x96, 0x2c, 0x33, 0x04, 0x00, 0x00, } diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index edb1cdc7f..598f92380 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -50,6 +50,7 @@ type UpsertABPHandlerReq struct { DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` AppSKey []byte `protobuf:"bytes,5,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + DevMode bool `protobuf:"varint,6,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` } func (m *UpsertABPHandlerReq) Reset() { *m = UpsertABPHandlerReq{} } @@ -113,6 +114,7 @@ type HandlerABPDevice struct { AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` FCntUp uint32 `protobuf:"varint,5,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` FCntDown uint32 `protobuf:"varint,6,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` + DevMode bool `protobuf:"varint,7,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` } func (m *HandlerABPDevice) Reset() { *m = HandlerABPDevice{} } @@ -470,6 +472,16 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) i += copy(data[i:], m.AppSKey) } + if m.DevMode { + data[i] = 0x30 + i++ + if m.DevMode { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -606,6 +618,16 @@ func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) } + if m.DevMode { + data[i] = 0x38 + i++ + if m.DevMode { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -853,6 +875,9 @@ func (m *UpsertABPHandlerReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } + if m.DevMode { + n += 2 + } return n } @@ -915,6 +940,9 @@ func (m *HandlerABPDevice) Size() (n int) { if m.FCntDown != 0 { n += 1 + sovHandlerManager(uint64(m.FCntDown)) } + if m.DevMode { + n += 2 + } return n } @@ -1415,6 +1443,26 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { m.AppSKey = []byte{} } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1868,6 +1916,26 @@ func (m *HandlerABPDevice) Unmarshal(data []byte) error { break } } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -2620,37 +2688,38 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 501 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xcb, 0x6e, 0xd3, 0x40, - 0x14, 0x65, 0x62, 0xc7, 0x49, 0x6f, 0x01, 0x85, 0x49, 0x13, 0x8c, 0x91, 0x2a, 0x34, 0xab, 0x48, - 0x88, 0x2c, 0xca, 0x17, 0x38, 0x4d, 0x69, 0x11, 0xaf, 0xca, 0x6e, 0x56, 0x2c, 0x90, 0xa9, 0x27, - 0x50, 0xb5, 0xd8, 0xc6, 0x63, 0xa8, 0xf8, 0x0c, 0x24, 0x16, 0xfc, 0x10, 0x12, 0x4b, 0x3e, 0x01, - 0xc1, 0x3f, 0xb0, 0x66, 0x5e, 0xb4, 0xe3, 0x30, 0x83, 0x44, 0x61, 0x61, 0x29, 0xe7, 0x9e, 0x9b, - 0x33, 0x67, 0xee, 0x63, 0x60, 0xf4, 0x32, 0x2b, 0xf2, 0x13, 0x5a, 0x3f, 0x7b, 0x95, 0x15, 0xd9, - 0x0b, 0x5a, 0x4f, 0xab, 0xba, 0x6c, 0x4a, 0xec, 0x1f, 0x96, 0x35, 0x25, 0x0d, 0x6c, 0x2c, 0x2a, - 0x46, 0xeb, 0xe6, 0xc9, 0x41, 0x1c, 0xef, 0xa9, 0xc4, 0x84, 0xbe, 0xc6, 0x1b, 0xd0, 0x3d, 0x28, - 0x8f, 0x69, 0x11, 0xa2, 0x5b, 0x68, 0xb2, 0x96, 0x74, 0x1b, 0x01, 0xf0, 0x18, 0x82, 0xb8, 0xaa, - 0x76, 0x16, 0xf7, 0xc3, 0x0e, 0x0f, 0x5f, 0x4e, 0x82, 0x4c, 0x22, 0x11, 0x9f, 0xd3, 0xb7, 0x22, - 0xee, 0xa9, 0x78, 0x2e, 0x91, 0xce, 0x7f, 0x40, 0xdf, 0x85, 0xfe, 0x59, 0x3e, 0x47, 0x64, 0x6c, - 0x3d, 0x95, 0x91, 0xf7, 0x08, 0x86, 0x8a, 0x88, 0x67, 0xfb, 0x17, 0x76, 0x13, 0x42, 0x8f, 0xbb, - 0x89, 0xf3, 0xbc, 0xd6, 0x76, 0x7a, 0xb9, 0x82, 0x82, 0x79, 0x7c, 0x7a, 0x9c, 0x9e, 0x1b, 0xea, - 0x15, 0x0a, 0x0a, 0x86, 0x6b, 0x49, 0xa6, 0xab, 0x98, 0x4c, 0x41, 0x32, 0xb2, 0x59, 0x62, 0x64, - 0x07, 0x46, 0x0f, 0x8f, 0x58, 0xc3, 0x0f, 0x3a, 0x3a, 0xa4, 0xec, 0xa2, 0x5e, 0x49, 0x61, 0x97, - 0x61, 0xf8, 0x36, 0xf8, 0xa2, 0x38, 0x5c, 0xc5, 0x9b, 0xac, 0x6f, 0x5d, 0x9f, 0x8a, 0x6e, 0x4d, - 0x35, 0x2f, 0x08, 0xf5, 0x8f, 0xc4, 0x2f, 0xf9, 0x6f, 0x3c, 0x01, 0x8f, 0xbb, 0xe3, 0xd2, 0x22, - 0x77, 0xdc, 0xca, 0xe5, 0x71, 0x9d, 0xea, 0x65, 0xb3, 0x7d, 0xf2, 0x01, 0xc1, 0x60, 0x95, 0x31, - 0x0b, 0xd6, 0x71, 0x16, 0xcc, 0x73, 0x16, 0xcc, 0x6f, 0x15, 0x4c, 0x5c, 0xf5, 0xde, 0x76, 0xd1, - 0x2c, 0x2a, 0x59, 0xc9, 0x2b, 0x49, 0xb0, 0x94, 0x08, 0x47, 0xd0, 0x17, 0xf1, 0x79, 0x79, 0x5a, - 0x84, 0x81, 0x64, 0xfa, 0x4b, 0x8d, 0xc9, 0x27, 0x04, 0xd7, 0x7e, 0xbb, 0x9c, 0x31, 0x56, 0xa8, - 0x35, 0x56, 0xff, 0xdd, 0xaf, 0x1e, 0xd2, 0xae, 0x39, 0xa4, 0xc6, 0x3d, 0x7a, 0xce, 0x7b, 0xf4, - 0x57, 0xee, 0xb1, 0x0d, 0xc3, 0x5d, 0xca, 0xbb, 0xb9, 0xcc, 0xde, 0x9c, 0xe8, 0xa6, 0xfe, 0xfd, - 0x4c, 0xdc, 0xb1, 0x89, 0x30, 0xc3, 0x27, 0x6a, 0x2d, 0xd3, 0x53, 0x18, 0xa6, 0xff, 0x7a, 0xa6, - 0x21, 0xee, 0xb5, 0xc4, 0x47, 0x36, 0x71, 0xb6, 0xf5, 0xa3, 0x03, 0x57, 0x75, 0xbf, 0x1e, 0xa9, - 0x57, 0x05, 0xcf, 0x01, 0xce, 0x77, 0x1a, 0x47, 0x6a, 0x08, 0x6d, 0x6f, 0x4b, 0xe4, 0xe6, 0x18, - 0x8e, 0x61, 0xed, 0x6c, 0xdb, 0xf0, 0x0d, 0x33, 0xb1, 0xf5, 0x22, 0x44, 0x4e, 0x8a, 0xe1, 0x5d, - 0x58, 0x37, 0x56, 0x0a, 0xdf, 0x54, 0x99, 0xd6, 0x65, 0x8d, 0xfe, 0x40, 0x32, 0xbc, 0x07, 0x83, - 0xd5, 0x3e, 0xfc, 0xb2, 0x64, 0x69, 0x72, 0xe4, 0xa4, 0xa4, 0x52, 0xea, 0x50, 0x4a, 0xdd, 0x4a, - 0x96, 0xc2, 0xcf, 0x06, 0x9f, 0xbf, 0x6d, 0xa2, 0x2f, 0xfc, 0xfb, 0xca, 0xbf, 0x8f, 0xdf, 0x37, - 0x2f, 0x3d, 0x0f, 0xe4, 0x73, 0x7e, 0xf7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x2c, 0xbd, - 0x68, 0xe7, 0x05, 0x00, 0x00, + // 520 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0xb1, 0x63, 0xa7, 0x53, 0x8a, 0xc2, 0xa6, 0x09, 0xc6, 0x48, 0x15, 0xda, 0x53, 0x24, + 0x44, 0x0e, 0xe5, 0x09, 0x9c, 0xa6, 0xb4, 0x08, 0x0a, 0x95, 0xd3, 0x9c, 0x38, 0x20, 0x53, 0x6f, + 0xa0, 0x6a, 0xb1, 0x8d, 0xd7, 0x50, 0xf1, 0x26, 0xbc, 0x03, 0x12, 0x6f, 0x81, 0xc4, 0x91, 0x47, + 0x40, 0xf0, 0x0e, 0x9c, 0xd9, 0x3f, 0xda, 0xdd, 0xb2, 0x8b, 0x44, 0xe1, 0x60, 0x29, 0xdf, 0x7c, + 0xe3, 0xcf, 0x33, 0xdf, 0xcc, 0x6e, 0x60, 0xf8, 0x32, 0x2f, 0x8b, 0x13, 0xd2, 0x3c, 0x7b, 0x95, + 0x97, 0xf9, 0x0b, 0xd2, 0x4c, 0xea, 0xa6, 0x6a, 0x2b, 0x14, 0x1c, 0x56, 0x0d, 0xc1, 0x2d, 0xac, + 0x2f, 0x6a, 0x4a, 0x9a, 0xf6, 0xc9, 0x41, 0x9a, 0xee, 0xca, 0xc4, 0x8c, 0xbc, 0x46, 0xeb, 0xd0, + 0x3d, 0xa8, 0x8e, 0x49, 0x19, 0x7b, 0xb7, 0xbd, 0xf1, 0x4a, 0xd6, 0x6d, 0x39, 0x40, 0x23, 0x08, + 0xd3, 0xba, 0xde, 0x5e, 0x3c, 0x88, 0x3b, 0x2c, 0x7c, 0x35, 0x0b, 0x73, 0x81, 0x78, 0x7c, 0x46, + 0xde, 0xf2, 0xb8, 0x2f, 0xe3, 0x85, 0x40, 0x2a, 0xff, 0x21, 0x79, 0x17, 0x07, 0x67, 0xf9, 0x0c, + 0xe1, 0x91, 0xf5, 0xab, 0x14, 0x7f, 0xf0, 0x60, 0x20, 0x89, 0x74, 0xba, 0x7f, 0xe9, 0x6a, 0x62, + 0x88, 0x58, 0x35, 0x69, 0x51, 0x34, 0xaa, 0x9c, 0xa8, 0x90, 0x90, 0x33, 0x8f, 0x4f, 0x8f, 0xe7, + 0xe7, 0x05, 0x45, 0xa5, 0x84, 0x9c, 0x61, 0x5a, 0x82, 0xe9, 0x4a, 0x26, 0x97, 0x50, 0xa9, 0xed, + 0x55, 0x05, 0x89, 0x43, 0xc6, 0xf4, 0x84, 0x1a, 0x87, 0x78, 0x68, 0x2b, 0x96, 0xe2, 0x6d, 0x18, + 0x3e, 0x3a, 0xa2, 0x2d, 0x7b, 0xe9, 0xe8, 0x90, 0xd0, 0xcb, 0x76, 0x81, 0x4b, 0xbb, 0x0c, 0x45, + 0x77, 0x20, 0xe0, 0xb6, 0x31, 0x15, 0x7f, 0xbc, 0xba, 0x79, 0x63, 0xc2, 0xe7, 0x38, 0x51, 0x3c, + 0x27, 0xe4, 0x1b, 0x59, 0x50, 0xb1, 0xdf, 0x68, 0x0c, 0x3e, 0xab, 0x8e, 0x49, 0xf3, 0xdc, 0x91, + 0x91, 0xcb, 0xe2, 0x2a, 0xd5, 0xcf, 0xa7, 0xfb, 0xf8, 0xa3, 0x07, 0xfd, 0x8b, 0x8c, 0x6e, 0x65, + 0xc7, 0x69, 0xa5, 0xef, 0xb4, 0x32, 0x30, 0xad, 0x64, 0xad, 0xde, 0xdf, 0x2a, 0xdb, 0x45, 0x2d, + 0x3c, 0x5e, 0xcb, 0xc2, 0xa5, 0x40, 0x28, 0x81, 0x1e, 0x8f, 0xcf, 0xaa, 0xd3, 0x52, 0x78, 0xbc, + 0x96, 0xf5, 0x96, 0x0a, 0xeb, 0xf6, 0x47, 0xa6, 0xfd, 0x9f, 0x3c, 0xb8, 0xfe, 0x5b, 0xdb, 0xda, + 0x2a, 0x7a, 0xc6, 0x2a, 0xfe, 0xf7, 0x4e, 0xd4, 0x62, 0x77, 0xf5, 0xc5, 0xd6, 0x3a, 0x8c, 0x9c, + 0x1d, 0xf6, 0xcc, 0x0e, 0xf1, 0x16, 0x0c, 0x76, 0x08, 0x9b, 0xf3, 0x32, 0x7f, 0x73, 0xa2, 0xc6, + 0xfd, 0xf7, 0xdb, 0x72, 0xd7, 0x26, 0x42, 0xb5, 0x3a, 0x3d, 0xe3, 0x00, 0x3e, 0x85, 0xc1, 0xfc, + 0x5f, 0xbf, 0xa9, 0x89, 0xfb, 0x86, 0xf8, 0xd0, 0x26, 0x4e, 0x37, 0x7f, 0x74, 0xe0, 0x9a, 0x9a, + 0xd7, 0x9e, 0xbc, 0x89, 0xd0, 0x0c, 0xe0, 0xfc, 0x1e, 0x40, 0x89, 0x5c, 0x4f, 0xdb, 0x7d, 0x94, + 0xb8, 0x39, 0x8a, 0x52, 0x58, 0x39, 0x3b, 0x87, 0xe8, 0xa6, 0x9e, 0x68, 0xdc, 0x22, 0x89, 0x93, + 0xa2, 0x68, 0x07, 0x56, 0xb5, 0xc3, 0x86, 0x6e, 0xc9, 0x4c, 0xeb, 0x31, 0x4e, 0xfe, 0x40, 0x52, + 0xb4, 0x0b, 0xfd, 0x8b, 0x73, 0xf8, 0x55, 0x92, 0x65, 0xc8, 0x89, 0x93, 0x12, 0x4a, 0x73, 0x87, + 0xd2, 0xdc, 0xad, 0x64, 0x31, 0x7e, 0xda, 0xff, 0xfc, 0x6d, 0xc3, 0xfb, 0xc2, 0x9e, 0xaf, 0xec, + 0x79, 0xff, 0x7d, 0xe3, 0xca, 0xf3, 0x50, 0xfc, 0x05, 0xdc, 0xfb, 0x19, 0x00, 0x00, 0xff, 0xff, + 0xef, 0x7e, 0x53, 0x39, 0x1b, 0x06, 0x00, 0x00, } diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index 9df7903e7..c42063cac 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -16,6 +16,7 @@ message UpsertABPBrokerReq { string NetAddress = 3; bytes DevAddr = 4; bytes NwkSKey = 5; + bool DevMode = 6; } message UpsertABPBrokerRes {} diff --git a/core/protos/handler.proto b/core/protos/handler.proto index 7d862adaf..a84d4ddd0 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -10,6 +10,7 @@ message DataUpHandlerReq { uint32 FCnt = 10; uint32 FPort = 11; uint32 MType = 12; + bool FCUPRst = 13; bytes Payload = 20; Metadata Metadata = 30; } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index a9401dea8..75a6e11ec 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -17,6 +17,7 @@ message UpsertABPHandlerReq { bytes DevAddr = 3; bytes NwkSKey = 4; bytes AppSKey = 5; + bool DevMode = 6; } message UpsertABPHandlerRes {} @@ -37,6 +38,7 @@ message HandlerABPDevice { bytes AppSKey = 4; uint32 FCntUp = 5; uint32 FCntDown = 6; + bool DevMode = 7; } message HandlerOTAADevice { diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 427359cb7..e17456f07 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" "reflect" + "strings" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/components/handler" @@ -75,10 +76,10 @@ registered on the Handler.`, table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevAddr", "FCntUp", "FCntDown") + table.AddRow("DevAddr", "FCntUp", "FCntDown", "DeveloperMode") for _, device := range devices.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) - table.AddRow(devAddr, device.FCntUp, device.FCntDown) + table.AddRow(devAddr, device.FCntUp, device.FCntDown, device.DevMode) } fmt.Println() @@ -196,6 +197,8 @@ var devicesInfoCmd = &cobra.Command{ fmt.Println() fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) + fmt.Println() + fmt.Printf(" Developer Mode: %t\n", device.DevMode) return } } @@ -275,7 +278,7 @@ the Handler`, // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ - Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", + Use: "personalized [DevAddr] [NwkSKey] [AppSKey] [DeveloperMode]", Short: "Create or update ABP registrations on the Handler", Long: `ttnctl devices register personalized creates or updates an ABP registration on the Handler`, @@ -293,6 +296,7 @@ registration on the Handler`, } var nwkSKey, appSKey []byte + devMode := false if len(args) >= 3 { nwkSKey, err = util.Parse128(args[1]) if err != nil { @@ -302,6 +306,9 @@ registration on the Handler`, if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } + if (len(args) >= 4) && strings.EqualFold(args[3], "true") { + devMode = true + } } else { ctx.Info("Generating random NwkSKey and AppSKey...") nwkSKey = random.Bytes(16) @@ -323,6 +330,7 @@ registration on the Handler`, DevAddr: devAddr, AppSKey: appSKey, NwkSKey: nwkSKey, + DevMode: devMode, }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") @@ -331,6 +339,7 @@ registration on the Handler`, "DevAddr": devAddr, "NwkSKey": nwkSKey, "AppSKey": appSKey, + "DevMode": devMode, }).Info("Registered personalized device") }, } diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 606502d2f..90648b247 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -49,6 +49,15 @@ func New(buf []byte) Interface { func (w *rw) Write(data interface{}) { var dataLen uint16 switch data.(type) { + case bool: + if data.(bool) { + w.directWrite(uint16(1)) + w.directWrite(byte(1)) + } else { + w.directWrite(uint16(1)) + w.directWrite(byte(0)) + } + return case uint8: dataLen = 1 case uint16: From 917974585f4f773dd0dbce38c7554cd0d3e8a806 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Mon, 25 Apr 2016 00:03:29 +0200 Subject: [PATCH 1343/2266] Added developer mode for ABP devices --- core/application.pb.go | 68 ----------- core/broker_manager.pb.go | 57 +++++++-- core/components/broker/broker.go | 18 ++- core/components/broker/brokerManager.go | 1 + core/components/broker/controller.go | 14 ++- core/components/broker/controller_test.go | 14 ++- core/components/broker/mocks_test.go | 4 +- core/components/handler/devStorage.go | 3 + core/components/handler/handler.go | 4 + core/components/handler/handlerManager.go | 5 +- core/core.pb.go | 68 +++++++++++ core/handler.pb.go | 93 ++++++++++----- core/handler_manager.pb.go | 135 ++++++++++++++++------ core/protos/broker_manager.proto | 1 + core/protos/handler.proto | 1 + core/protos/handler_manager.proto | 2 + ttnctl/cmd/device.go | 15 ++- utils/readwriter/readwriter.go | 9 ++ 18 files changed, 354 insertions(+), 158 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 3a459dfc9..4a5872d17 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -2,70 +2,6 @@ // source: application.proto // DO NOT EDIT! -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - application.proto - broker.proto - broker_manager.proto - core.proto - handler.proto - handler_manager.proto - lorawan.proto - router.proto - - It has these top-level messages: - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - ValidateOTAABrokerReq - ValidateOTAABrokerRes - UpsertABPBrokerReq - UpsertABPBrokerRes - BrokerDevice - ValidateTokenBrokerReq - ValidateTokenBrokerRes - Metadata - StatsMetadata - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - UpsertOTAAHandlerReq - UpsertOTAAHandlerRes - UpsertABPHandlerReq - UpsertABPHandlerRes - ListDevicesHandlerReq - ListDevicesHandlerRes - HandlerABPDevice - HandlerOTAADevice - GetDefaultDeviceReq - GetDefaultDeviceRes - SetDefaultDeviceReq - SetDefaultDeviceRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes -*/ package core import proto "github.com/golang/protobuf/proto" @@ -84,10 +20,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - type DataAppReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index a4908b3a7..3ad8392ff 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -49,6 +49,7 @@ type UpsertABPBrokerReq struct { NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` DevAddr []byte `protobuf:"bytes,4,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,5,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` + DevMode bool `protobuf:"varint,6,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` } func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } @@ -321,6 +322,16 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) } + if m.DevMode { + data[i] = 0x30 + i++ + if m.DevMode { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -500,6 +511,9 @@ func (m *UpsertABPBrokerReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } + if m.DevMode { + n += 2 + } return n } @@ -929,6 +943,26 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { m.NwkSKey = []byte{} } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBrokerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -1409,7 +1443,7 @@ var ( ) var fileDescriptorBrokerManager = []byte{ - // 327 bytes of a gzipped FileDescriptorProto + // 343 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x4a, 0xe5, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, @@ -1417,18 +1451,19 @@ var fileDescriptorBrokerManager = []byte{ 0x0d, 0x01, 0xb2, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x58, 0x4b, 0x40, 0x1c, 0x21, 0x31, 0x2e, 0x36, 0xc7, 0x82, 0x02, 0xd7, 0x50, 0x4f, 0x09, 0x26, 0xa0, 0x30, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x27, 0x24, 0xc7, 0xc5, 0xe5, 0x97, 0x5a, 0xe2, 0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, - 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x34, + 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x06, 0x46, 0x2e, 0xa1, 0xd0, 0x82, 0xe2, 0xd4, 0xa2, 0x12, 0x47, 0xa7, 0x00, 0x1a, 0xd9, 0x2e, 0x24, 0xc1, 0xc5, 0xee, 0x92, 0x5a, 0x06, 0xe2, 0x49, 0xb0, 0x80, 0x35, 0xb2, 0xa7, 0x40, 0xb8, 0x20, - 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0xab, 0x24, - 0x82, 0xc5, 0x5d, 0xc5, 0x4a, 0x51, 0x5c, 0x3c, 0x10, 0x0e, 0xd0, 0xbc, 0xcc, 0xe4, 0x54, 0x90, - 0x8b, 0x80, 0x2c, 0x90, 0x8b, 0x18, 0x21, 0x2e, 0x4a, 0x01, 0xf3, 0x90, 0x6d, 0x64, 0xc2, 0x69, - 0x23, 0x33, 0xaa, 0x8d, 0x6e, 0x5c, 0x62, 0xb0, 0x30, 0x02, 0xfb, 0x9d, 0xcc, 0xd0, 0x50, 0x92, - 0xc0, 0x61, 0x4e, 0xb1, 0xd1, 0x73, 0x46, 0x2e, 0x5e, 0x08, 0xcf, 0x17, 0x92, 0x14, 0x84, 0x3c, - 0xb8, 0x78, 0x90, 0xe3, 0x45, 0x48, 0x5a, 0x0f, 0x94, 0x2a, 0xf4, 0xb0, 0x26, 0x09, 0x29, 0x3c, - 0x92, 0xc5, 0x42, 0xf6, 0x5c, 0x9c, 0xf0, 0xf0, 0x12, 0x92, 0x80, 0xa8, 0xc4, 0x8c, 0x58, 0x29, + 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0x0b, 0xd5, + 0xe3, 0x9b, 0x9f, 0x92, 0x2a, 0xc1, 0x06, 0x94, 0xe1, 0x00, 0xeb, 0x01, 0x71, 0x95, 0x44, 0xb0, + 0xb8, 0xb8, 0x58, 0x29, 0x8a, 0x8b, 0x07, 0xc2, 0x01, 0xea, 0xca, 0x4c, 0x4e, 0x05, 0xb9, 0x15, + 0xc8, 0x02, 0xb9, 0x95, 0x11, 0xe2, 0xd6, 0x14, 0x30, 0x0f, 0xd9, 0x2d, 0x4c, 0x38, 0xdd, 0xc2, + 0x8c, 0xe2, 0x16, 0x25, 0x37, 0x2e, 0x31, 0x58, 0xe8, 0x81, 0x43, 0x85, 0xcc, 0x70, 0x52, 0x92, + 0xc0, 0x61, 0x4e, 0xb1, 0xd1, 0x73, 0x46, 0x2e, 0x5e, 0x08, 0xcf, 0x17, 0x92, 0x48, 0x84, 0x3c, + 0xb8, 0x78, 0x90, 0x63, 0x4c, 0x48, 0x5a, 0x0f, 0x94, 0x5e, 0xf4, 0xb0, 0x26, 0x16, 0x29, 0x3c, + 0x92, 0xc5, 0x42, 0xf6, 0x5c, 0x9c, 0xf0, 0xf0, 0x12, 0x92, 0x80, 0xa8, 0xc4, 0x8c, 0x72, 0x29, 0x5c, 0x32, 0xc5, 0x42, 0xde, 0x5c, 0xbc, 0x28, 0xce, 0x16, 0x92, 0x41, 0xb5, 0x0e, 0x35, 0x4c, 0xa4, 0xf0, 0xc9, 0x16, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, - 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0x54, 0x6f, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x06, - 0x4b, 0xeb, 0xf5, 0x0d, 0x03, 0x00, 0x00, + 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0xfc, 0x60, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xc6, + 0x07, 0x41, 0x5b, 0x27, 0x03, 0x00, 0x00, } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 7d69dcf2e..e1fa637e7 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -194,6 +194,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c AppEUI: req.AppEUI, DevEUI: req.DevEUI, NwkSKey: nwkSKey, + DevMode: false, FCntUp: 0, }) if err != nil { @@ -245,6 +246,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c fhdr := &uplinkPayload.MACPayload.(*lorawan.MACPayload).FHDR // No nil ref, ensured by NewLoRaWANData() fcnt16 := fhdr.FCnt // Keep a reference to the original counter + fcntReset := false var mEntry *devEntry for _, entry := range entries { // retrieve the network session key @@ -252,11 +254,18 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c // Check with 16-bits counters fhdr.FCnt = fcnt16 - fcnt32, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) + fcnt32, counterReset, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) if err != nil { continue } + if counterReset { + dtx := ctx.WithFields(log.Fields{ + "DevMode": entry.DevMode, + }) + dtx.Debug("Counter reset detected") + } + ok, err := uplinkPayload.ValidateMIC(key) if err != nil { continue @@ -270,6 +279,12 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } if ok { + if counterReset { + if !entry.DevMode { + continue + } + fcntReset = true + } mEntry = &entry stats.MarkMeter("broker.uplink.handler_lookup.mic_match") ctx = ctx.WithFields(log.Fields{ @@ -312,6 +327,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c FPort: req.Payload.MACPayload.FPort, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, + FCUPRst: fcntReset, }) if err != nil { diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index c043e4f8f..7e54b4b83 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -87,6 +87,7 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), DevAddr: req.DevAddr, NwkSKey: nwkSKey, + DevMode: req.DevMode, FCntUp: 0, }) if err != nil { diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 84aabaee8..7f1b78ac2 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -22,7 +22,7 @@ type NetworkController interface { readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) upsertNonces(entry noncesEntry) error upsert(entry devEntry) error - wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) + wholeCounter(devCnt uint32, entryCnt uint32) (uint32, bool, error) done() error } @@ -33,6 +33,7 @@ type devEntry struct { Dialer Dialer FCntUp uint32 NwkSKey [16]byte + DevMode bool } type noncesEntry struct { @@ -68,19 +69,22 @@ func (s *controller) read(devAddr []byte) ([]devEntry, error) { } // wholeCounter implements the broker.NetworkController interface -func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { +func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, bool, error) { upperSup := int(math.Pow(2, 16)) diff := int(devCnt) - (int(entryCnt) % upperSup) var offset int if diff >= 0 { offset = diff } else { + if entryCnt < (uint32(upperSup) - 10) { + return devCnt, true, nil + } offset = upperSup + diff } if offset > upperSup/4 { - return 0, errors.New(errors.Structural, "Gap too big, counter is errored") + return 0, false, errors.New(errors.Structural, "Gap too big, counter is errored") } - return entryCnt + uint32(offset), nil + return entryCnt + uint32(offset), false, nil } // upsert implements the broker.NetworkController interface @@ -141,6 +145,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.DevAddr) rw.Write(e.NwkSKey[:]) rw.Write(e.FCntUp) + rw.Write(e.DevMode) rw.Write(e.Dialer.MarshalSafely()) return rw.Bytes() } @@ -162,6 +167,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) + rw.Read(func(data []byte) { e.DevMode = (data[0] != 0) }) rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) return rw.Err() } diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index bfd1d9b14..8d5253f1f 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -252,7 +252,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt + 1 // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) @@ -272,10 +272,12 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt - 1 // Operate - _, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, countReset, err := db.wholeCounter(cnt16, wholeCnt) // Check - CheckErrors(t, ErrStructural, err) + CheckErrors(t, nil, err) + Check(t, cnt16, cnt32, "Counters") + Check(t, true, countReset, "Counter reset") _ = db.done() } @@ -291,7 +293,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 2) // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) @@ -311,7 +313,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 45000) // Operate - _, err := db.wholeCounter(cnt16, wholeCnt) + _, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, ErrStructural, err) @@ -330,7 +332,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(2) // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go index 46f1a36ed..803eaca37 100644 --- a/core/components/broker/mocks_test.go +++ b/core/components/broker/mocks_test.go @@ -176,10 +176,10 @@ func (m *MockNetworkController) upsertNonces(entry noncesEntry) error { } // wholeCnt implements the NetworkController interface -func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, error) { +func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, bool, error) { m.InWholeCounter.DevCnt = devCnt m.InWholeCounter.EntryCnt = entryCnt - return m.OutWholeCounter.FCnt, m.Failures["wholeCounter"] + return m.OutWholeCounter.FCnt, false, m.Failures["wholeCounter"] } // done implements the NetworkController Interface diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 32b0f5a10..821424d8f 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -33,6 +33,7 @@ type devEntry struct { FCntDown uint32 FCntUp uint32 NwkSKey [16]byte + DevMode bool } type devDefaultEntry struct { @@ -105,6 +106,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.NwkSKey[:]) rw.Write(e.FCntUp) rw.Write(e.FCntDown) + rw.Write(e.DevMode) rw.Write(e.AppEUI) rw.Write(e.DevEUI) rw.Write(e.DevAddr) @@ -126,6 +128,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) + rw.Read(func(data []byte) { e.DevMode = (data[0] != 0) }) rw.Read(func(data []byte) { e.AppEUI = make([]byte, len(data)) copy(e.AppEUI, data) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c96be0df1..1abb001bc 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -511,6 +511,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da FCntDown: 0, FCntUp: 0, NwkSKey: nwkSKey, + DevMode: false, }) if err != nil { ctx.WithError(err).Debug("Unable to initialize devEntry with activation") @@ -633,6 +634,9 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu stats.MarkMeter("handler.downlink.pull") downType := lorawan.UnconfirmedDataDown ack := (upType == lorawan.ConfirmedDataUp) + if bundle.Packet.(*core.DataUpHandlerReq).FCUPRst { + bundle.Entry.FCntDown = 0 + } downlink, err := h.buildDownlink(downlink.Payload, downType, ack, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) if err != nil { h.abortConsume(errors.New(errors.Structural, err), bundles) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 12990bd42..61281eb91 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -44,6 +44,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle AppSKey: d.AppSKey[:], FCntUp: d.FCntUp, FCntDown: d.FCntDown, + DevMode: d.DevMode, }) } else { otaa = append(otaa, &core.HandlerOTAADevice{ @@ -72,13 +73,14 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq return new(core.UpsertABPHandlerRes), err } - // 2. Forward to the broker firt -> The Broker also does the token verification + // 2. Forward to the broker first -> The Broker also does the token verification _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ Token: req.Token, AppEUI: req.AppEUI, DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, NetAddress: h.PrivateNetAddrAnnounce, + DevMode: req.DevMode, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") @@ -93,6 +95,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq DevAddr: req.DevAddr, FCntDown: 0, FCntUp: 0, + DevMode: req.DevMode, } copy(entry.NwkSKey[:], req.NwkSKey) copy(entry.AppSKey[:], req.AppSKey) diff --git a/core/core.pb.go b/core/core.pb.go index 0cf07130f..6a9885c56 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -2,6 +2,70 @@ // source: core.proto // DO NOT EDIT! +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + core.proto + broker.proto + broker_manager.proto + handler_manager.proto + lorawan.proto + handler.proto + application.proto + router.proto + + It has these top-level messages: + Metadata + StatsMetadata + DataBrokerReq + DataBrokerRes + JoinBrokerReq + JoinBrokerRes + ValidateOTAABrokerReq + ValidateOTAABrokerRes + UpsertABPBrokerReq + UpsertABPBrokerRes + BrokerDevice + ValidateTokenBrokerReq + ValidateTokenBrokerRes + UpsertOTAAHandlerReq + UpsertOTAAHandlerRes + UpsertABPHandlerReq + UpsertABPHandlerRes + ListDevicesHandlerReq + ListDevicesHandlerRes + HandlerABPDevice + HandlerOTAADevice + GetDefaultDeviceReq + GetDefaultDeviceRes + SetDefaultDeviceReq + SetDefaultDeviceRes + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + JoinHandlerReq + JoinHandlerRes + DataAppReq + DataAppRes + JoinAppReq + JoinAppRes + DataRouterReq + DataRouterRes + StatsReq + StatsRes + JoinRouterReq + JoinRouterRes +*/ package core import proto "github.com/golang/protobuf/proto" @@ -15,6 +79,10 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + type Metadata struct { DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` diff --git a/core/handler.pb.go b/core/handler.pb.go index 52569d9b4..41e869e87 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -26,6 +26,7 @@ type DataUpHandlerReq struct { FCnt uint32 `protobuf:"varint,10,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` FPort uint32 `protobuf:"varint,11,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` MType uint32 `protobuf:"varint,12,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` + FCUPRst bool `protobuf:"varint,13,opt,name=FCUPRst,json=fCUPRst,proto3" json:"FCUPRst,omitempty"` Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } @@ -298,6 +299,16 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandler(data, i, uint64(m.MType)) } + if m.FCUPRst { + data[i] = 0x68 + i++ + if m.FCUPRst { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } if len(m.Payload) > 0 { data[i] = 0xa2 i++ @@ -584,6 +595,9 @@ func (m *DataUpHandlerReq) Size() (n int) { if m.MType != 0 { n += 1 + sovHandler(uint64(m.MType)) } + if m.FCUPRst { + n += 2 + } l = len(m.Payload) if l > 0 { n += 2 + l + sovHandler(uint64(l)) @@ -846,6 +860,26 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { break } } + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCUPRst", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FCUPRst = bool(v != 0) case 20: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) @@ -1760,33 +1794,34 @@ var ( ) var fileDescriptorHandler = []byte{ - // 443 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x93, 0xcf, 0x8e, 0xd3, 0x30, - 0x10, 0xc6, 0x09, 0xed, 0xb6, 0x65, 0x9a, 0x56, 0x65, 0x54, 0x16, 0x2b, 0x87, 0x0a, 0xe5, 0x84, - 0x40, 0x5a, 0x89, 0x72, 0xe1, 0x84, 0x14, 0x1a, 0x56, 0x14, 0xda, 0x6a, 0x65, 0xb2, 0xe2, 0x6c, - 0x62, 0xaf, 0x40, 0xdb, 0xc6, 0x26, 0x89, 0xa8, 0xfa, 0x26, 0x88, 0x2b, 0x2f, 0xb3, 0xdc, 0x78, - 0x04, 0x04, 0x2f, 0x82, 0xff, 0x04, 0x55, 0x29, 0xdd, 0xc3, 0xd2, 0x43, 0xa4, 0xf9, 0x7e, 0xf1, - 0x8c, 0xbf, 0xf1, 0xd8, 0xd0, 0xfb, 0xc0, 0x32, 0xbe, 0x14, 0xf9, 0x89, 0xca, 0x65, 0x29, 0xb1, - 0x99, 0xca, 0x5c, 0x04, 0xbd, 0xa5, 0xcc, 0xd9, 0x9a, 0x65, 0x0e, 0x06, 0x60, 0xa0, 0x8b, 0xc3, - 0x2b, 0x0f, 0x06, 0x31, 0x2b, 0xd9, 0xb9, 0x7a, 0xe5, 0x12, 0xa9, 0xf8, 0x84, 0xc7, 0xd0, 0x8a, - 0x94, 0x7a, 0x79, 0x3e, 0x25, 0xde, 0x03, 0xef, 0xa1, 0x4f, 0x5b, 0xcc, 0x2a, 0xc3, 0x63, 0xf1, - 0xd9, 0xf0, 0xdb, 0x8e, 0x73, 0xab, 0x10, 0xa1, 0x79, 0x3a, 0xc9, 0x4a, 0x02, 0x9a, 0xf6, 0x68, - 0xf3, 0x42, 0xc7, 0x38, 0x84, 0xa3, 0xd3, 0x33, 0x99, 0x97, 0xa4, 0x6b, 0xe1, 0xd1, 0x85, 0x11, - 0x86, 0xce, 0x93, 0x8d, 0x12, 0xc4, 0x77, 0x74, 0x65, 0x04, 0x12, 0x68, 0x9f, 0xb1, 0xcd, 0x52, - 0x32, 0x4e, 0x86, 0xb6, 0x70, 0x5b, 0x39, 0x89, 0x8f, 0xa0, 0x33, 0x17, 0x25, 0xe3, 0xda, 0x21, - 0x19, 0xe9, 0x5f, 0xdd, 0x71, 0xff, 0xc4, 0xba, 0xff, 0x4b, 0x69, 0x67, 0x55, 0x45, 0xe1, 0xe5, - 0x3f, 0x9d, 0x14, 0xf8, 0xb8, 0x5e, 0xb9, 0x3b, 0xbe, 0xeb, 0xd2, 0x67, 0x92, 0xb2, 0x77, 0xd1, - 0xc2, 0xac, 0xff, 0xbf, 0xcd, 0x14, 0xa0, 0x49, 0x8e, 0xe5, 0x3a, 0x3b, 0xe0, 0xe0, 0xae, 0x6f, - 0x7c, 0x00, 0x8d, 0x24, 0x99, 0x59, 0x1b, 0x77, 0x68, 0xa3, 0x4c, 0x66, 0xe1, 0x70, 0xcf, 0x8e, - 0x45, 0xf8, 0xd5, 0x83, 0xfe, 0x6b, 0xf9, 0xf1, 0x10, 0x13, 0x01, 0x74, 0x34, 0x5f, 0xc8, 0x2c, - 0x15, 0x76, 0x82, 0x3e, 0xed, 0xf0, 0x4a, 0x1b, 0x1b, 0xf3, 0xe9, 0xc4, 0xce, 0xd0, 0xa7, 0x8d, - 0xd5, 0x74, 0x72, 0xa3, 0x43, 0xfa, 0xb6, 0x6b, 0xae, 0x30, 0x1d, 0xeb, 0xcd, 0x22, 0xce, 0xf3, - 0xca, 0x5d, 0x9b, 0x3b, 0x89, 0x4f, 0x76, 0x47, 0x75, 0xbf, 0x36, 0x2a, 0x53, 0x27, 0x4a, 0x53, - 0xa1, 0xca, 0xed, 0x21, 0xe9, 0x62, 0x8b, 0xf5, 0xe5, 0xdb, 0x37, 0x62, 0x43, 0xee, 0xb9, 0x62, - 0x99, 0x93, 0x37, 0x71, 0x39, 0xfe, 0xee, 0x41, 0xbb, 0x72, 0x88, 0xcf, 0xc1, 0x77, 0xa1, 0xbb, - 0x49, 0x78, 0xec, 0xb2, 0x76, 0x5f, 0x48, 0xb0, 0x9f, 0x17, 0x18, 0x43, 0x7f, 0x9b, 0x6f, 0x46, - 0x85, 0x64, 0xbb, 0xb2, 0x7e, 0x59, 0x82, 0xeb, 0xfe, 0x14, 0xf8, 0x0c, 0xc0, 0x29, 0xd3, 0x34, - 0x0e, 0xdd, 0xba, 0xfa, 0x94, 0x83, 0x7d, 0xb4, 0x78, 0x31, 0xb8, 0xfa, 0x35, 0xf2, 0x7e, 0xe8, - 0xef, 0xa7, 0xfe, 0xbe, 0xfc, 0x1e, 0xdd, 0x7a, 0xdf, 0xb2, 0xef, 0xfc, 0xe9, 0x9f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x4a, 0x5b, 0xdc, 0x30, 0x19, 0x04, 0x00, 0x00, + // 456 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x94, 0x41, 0xaf, 0x12, 0x31, + 0x10, 0xc7, 0x5d, 0xe1, 0xb1, 0xeb, 0xb0, 0x10, 0x9c, 0xe0, 0xb3, 0xd9, 0x03, 0x31, 0x9c, 0x8c, + 0x26, 0x2f, 0x11, 0x2f, 0x9e, 0x4c, 0x10, 0x24, 0xa2, 0x40, 0x48, 0x85, 0x78, 0xae, 0x6c, 0x89, + 0xe6, 0xc1, 0x76, 0xdd, 0x6d, 0x24, 0x7c, 0x13, 0xe3, 0xd5, 0x2f, 0xa3, 0x37, 0x3f, 0x82, 0x79, + 0x7e, 0x11, 0xdb, 0x0e, 0x86, 0x2c, 0xf2, 0x0e, 0x4f, 0x0e, 0x9b, 0xf4, 0xff, 0x6b, 0x67, 0xe6, + 0x3f, 0x9d, 0x02, 0xd4, 0x3e, 0x88, 0x24, 0x5e, 0xc9, 0xec, 0x22, 0xcd, 0x94, 0x56, 0x58, 0x5e, + 0xa8, 0x4c, 0x46, 0xb5, 0x95, 0xca, 0xc4, 0x46, 0x24, 0x04, 0x23, 0xb0, 0x90, 0xd6, 0xed, 0x2b, + 0x0f, 0x1a, 0x7d, 0xa1, 0xc5, 0x3c, 0x7d, 0x45, 0x81, 0x5c, 0x7e, 0xc2, 0x73, 0xa8, 0x74, 0xd3, + 0xf4, 0xe5, 0x7c, 0xc8, 0xbc, 0x07, 0xde, 0xc3, 0x90, 0x57, 0x84, 0x53, 0x96, 0xf7, 0xe5, 0x67, + 0xcb, 0x6f, 0x13, 0x8f, 0x9d, 0x42, 0x84, 0xf2, 0xa0, 0x97, 0x68, 0x06, 0x86, 0xd6, 0x78, 0x79, + 0x69, 0xd6, 0xd8, 0x84, 0xb3, 0xc1, 0x54, 0x65, 0x9a, 0x55, 0x1d, 0x3c, 0x5b, 0x5a, 0x61, 0xe9, + 0x78, 0xb6, 0x4d, 0x25, 0x0b, 0x89, 0xae, 0xad, 0x40, 0x06, 0xfe, 0xa0, 0x37, 0x9f, 0xf2, 0x5c, + 0xb3, 0x9a, 0xe1, 0x01, 0xf7, 0x97, 0x24, 0xed, 0xce, 0x54, 0x6c, 0x57, 0x4a, 0xc4, 0xac, 0xe9, + 0x4a, 0xfa, 0x29, 0x49, 0x7c, 0x04, 0xc1, 0x58, 0x6a, 0x11, 0x1b, 0xef, 0xac, 0x65, 0xb6, 0xaa, + 0x9d, 0xfa, 0x85, 0xeb, 0xeb, 0x2f, 0xe5, 0xc1, 0x7a, 0xb7, 0x6a, 0x5f, 0xfe, 0xd3, 0x63, 0x8e, + 0x8f, 0x8b, 0x99, 0xab, 0x9d, 0xbb, 0x14, 0x3e, 0x52, 0x5c, 0xbc, 0xeb, 0x4e, 0xec, 0xf9, 0xff, + 0x2b, 0x96, 0x02, 0xda, 0xe0, 0xbe, 0xda, 0x24, 0x27, 0x5c, 0xe9, 0xf5, 0x8d, 0x37, 0xa0, 0x34, + 0x9b, 0x8d, 0x9c, 0x8d, 0x3b, 0xbc, 0xa4, 0x67, 0xa3, 0x76, 0xf3, 0x48, 0xc5, 0xbc, 0xfd, 0xd5, + 0x83, 0xfa, 0x6b, 0xf5, 0xf1, 0x14, 0x13, 0x11, 0x04, 0x86, 0x4f, 0x54, 0xb2, 0x90, 0x6e, 0xb6, + 0x21, 0x0f, 0xe2, 0x9d, 0xb6, 0x36, 0xc6, 0xc3, 0x9e, 0x9b, 0x6e, 0xc8, 0x4b, 0xeb, 0x61, 0xef, + 0x46, 0x97, 0xf4, 0xed, 0xd0, 0x5c, 0x6e, 0x3b, 0x36, 0xc5, 0xba, 0x71, 0x9c, 0xed, 0xdc, 0xf9, + 0x31, 0x49, 0x7c, 0x72, 0x38, 0xaa, 0xfb, 0x85, 0x51, 0xd9, 0x3c, 0xdd, 0xc5, 0x42, 0xa6, 0x7a, + 0x7f, 0x49, 0x26, 0xd9, 0x64, 0x73, 0xf9, 0xf6, 0x8d, 0xdc, 0xb2, 0x7b, 0x94, 0x2c, 0x21, 0x79, + 0x13, 0x97, 0x9d, 0x1f, 0x1e, 0xf8, 0x3b, 0x87, 0xf8, 0x1c, 0x42, 0x5a, 0xd2, 0x4b, 0xc2, 0x73, + 0x8a, 0x3a, 0xfc, 0xed, 0x44, 0xc7, 0x79, 0x8e, 0x7d, 0xa8, 0xef, 0xe3, 0xed, 0xa8, 0x90, 0xed, + 0x4f, 0x16, 0x1f, 0x4b, 0x74, 0xdd, 0x4e, 0x8e, 0xcf, 0x00, 0x48, 0xd9, 0xa6, 0xb1, 0x49, 0xe7, + 0x8a, 0x53, 0x8e, 0x8e, 0xd1, 0xfc, 0x45, 0xe3, 0xfb, 0x55, 0xcb, 0xfb, 0x69, 0xbe, 0x5f, 0xe6, + 0xfb, 0xf2, 0xbb, 0x75, 0xeb, 0x7d, 0xc5, 0xfd, 0x03, 0x3c, 0xfd, 0x13, 0x00, 0x00, 0xff, 0xff, + 0x33, 0x19, 0x96, 0x2c, 0x33, 0x04, 0x00, 0x00, } diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index edb1cdc7f..598f92380 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -50,6 +50,7 @@ type UpsertABPHandlerReq struct { DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` AppSKey []byte `protobuf:"bytes,5,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` + DevMode bool `protobuf:"varint,6,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` } func (m *UpsertABPHandlerReq) Reset() { *m = UpsertABPHandlerReq{} } @@ -113,6 +114,7 @@ type HandlerABPDevice struct { AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` FCntUp uint32 `protobuf:"varint,5,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` FCntDown uint32 `protobuf:"varint,6,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` + DevMode bool `protobuf:"varint,7,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` } func (m *HandlerABPDevice) Reset() { *m = HandlerABPDevice{} } @@ -470,6 +472,16 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) i += copy(data[i:], m.AppSKey) } + if m.DevMode { + data[i] = 0x30 + i++ + if m.DevMode { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -606,6 +618,16 @@ func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) } + if m.DevMode { + data[i] = 0x38 + i++ + if m.DevMode { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -853,6 +875,9 @@ func (m *UpsertABPHandlerReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } + if m.DevMode { + n += 2 + } return n } @@ -915,6 +940,9 @@ func (m *HandlerABPDevice) Size() (n int) { if m.FCntDown != 0 { n += 1 + sovHandlerManager(uint64(m.FCntDown)) } + if m.DevMode { + n += 2 + } return n } @@ -1415,6 +1443,26 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { m.AppSKey = []byte{} } iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1868,6 +1916,26 @@ func (m *HandlerABPDevice) Unmarshal(data []byte) error { break } } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -2620,37 +2688,38 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 501 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xcb, 0x6e, 0xd3, 0x40, - 0x14, 0x65, 0x62, 0xc7, 0x49, 0x6f, 0x01, 0x85, 0x49, 0x13, 0x8c, 0x91, 0x2a, 0x34, 0xab, 0x48, - 0x88, 0x2c, 0xca, 0x17, 0x38, 0x4d, 0x69, 0x11, 0xaf, 0xca, 0x6e, 0x56, 0x2c, 0x90, 0xa9, 0x27, - 0x50, 0xb5, 0xd8, 0xc6, 0x63, 0xa8, 0xf8, 0x0c, 0x24, 0x16, 0xfc, 0x10, 0x12, 0x4b, 0x3e, 0x01, - 0xc1, 0x3f, 0xb0, 0x66, 0x5e, 0xb4, 0xe3, 0x30, 0x83, 0x44, 0x61, 0x61, 0x29, 0xe7, 0x9e, 0x9b, - 0x33, 0x67, 0xee, 0x63, 0x60, 0xf4, 0x32, 0x2b, 0xf2, 0x13, 0x5a, 0x3f, 0x7b, 0x95, 0x15, 0xd9, - 0x0b, 0x5a, 0x4f, 0xab, 0xba, 0x6c, 0x4a, 0xec, 0x1f, 0x96, 0x35, 0x25, 0x0d, 0x6c, 0x2c, 0x2a, - 0x46, 0xeb, 0xe6, 0xc9, 0x41, 0x1c, 0xef, 0xa9, 0xc4, 0x84, 0xbe, 0xc6, 0x1b, 0xd0, 0x3d, 0x28, - 0x8f, 0x69, 0x11, 0xa2, 0x5b, 0x68, 0xb2, 0x96, 0x74, 0x1b, 0x01, 0xf0, 0x18, 0x82, 0xb8, 0xaa, - 0x76, 0x16, 0xf7, 0xc3, 0x0e, 0x0f, 0x5f, 0x4e, 0x82, 0x4c, 0x22, 0x11, 0x9f, 0xd3, 0xb7, 0x22, - 0xee, 0xa9, 0x78, 0x2e, 0x91, 0xce, 0x7f, 0x40, 0xdf, 0x85, 0xfe, 0x59, 0x3e, 0x47, 0x64, 0x6c, - 0x3d, 0x95, 0x91, 0xf7, 0x08, 0x86, 0x8a, 0x88, 0x67, 0xfb, 0x17, 0x76, 0x13, 0x42, 0x8f, 0xbb, - 0x89, 0xf3, 0xbc, 0xd6, 0x76, 0x7a, 0xb9, 0x82, 0x82, 0x79, 0x7c, 0x7a, 0x9c, 0x9e, 0x1b, 0xea, - 0x15, 0x0a, 0x0a, 0x86, 0x6b, 0x49, 0xa6, 0xab, 0x98, 0x4c, 0x41, 0x32, 0xb2, 0x59, 0x62, 0x64, - 0x07, 0x46, 0x0f, 0x8f, 0x58, 0xc3, 0x0f, 0x3a, 0x3a, 0xa4, 0xec, 0xa2, 0x5e, 0x49, 0x61, 0x97, - 0x61, 0xf8, 0x36, 0xf8, 0xa2, 0x38, 0x5c, 0xc5, 0x9b, 0xac, 0x6f, 0x5d, 0x9f, 0x8a, 0x6e, 0x4d, - 0x35, 0x2f, 0x08, 0xf5, 0x8f, 0xc4, 0x2f, 0xf9, 0x6f, 0x3c, 0x01, 0x8f, 0xbb, 0xe3, 0xd2, 0x22, - 0x77, 0xdc, 0xca, 0xe5, 0x71, 0x9d, 0xea, 0x65, 0xb3, 0x7d, 0xf2, 0x01, 0xc1, 0x60, 0x95, 0x31, - 0x0b, 0xd6, 0x71, 0x16, 0xcc, 0x73, 0x16, 0xcc, 0x6f, 0x15, 0x4c, 0x5c, 0xf5, 0xde, 0x76, 0xd1, - 0x2c, 0x2a, 0x59, 0xc9, 0x2b, 0x49, 0xb0, 0x94, 0x08, 0x47, 0xd0, 0x17, 0xf1, 0x79, 0x79, 0x5a, - 0x84, 0x81, 0x64, 0xfa, 0x4b, 0x8d, 0xc9, 0x27, 0x04, 0xd7, 0x7e, 0xbb, 0x9c, 0x31, 0x56, 0xa8, - 0x35, 0x56, 0xff, 0xdd, 0xaf, 0x1e, 0xd2, 0xae, 0x39, 0xa4, 0xc6, 0x3d, 0x7a, 0xce, 0x7b, 0xf4, - 0x57, 0xee, 0xb1, 0x0d, 0xc3, 0x5d, 0xca, 0xbb, 0xb9, 0xcc, 0xde, 0x9c, 0xe8, 0xa6, 0xfe, 0xfd, - 0x4c, 0xdc, 0xb1, 0x89, 0x30, 0xc3, 0x27, 0x6a, 0x2d, 0xd3, 0x53, 0x18, 0xa6, 0xff, 0x7a, 0xa6, - 0x21, 0xee, 0xb5, 0xc4, 0x47, 0x36, 0x71, 0xb6, 0xf5, 0xa3, 0x03, 0x57, 0x75, 0xbf, 0x1e, 0xa9, - 0x57, 0x05, 0xcf, 0x01, 0xce, 0x77, 0x1a, 0x47, 0x6a, 0x08, 0x6d, 0x6f, 0x4b, 0xe4, 0xe6, 0x18, - 0x8e, 0x61, 0xed, 0x6c, 0xdb, 0xf0, 0x0d, 0x33, 0xb1, 0xf5, 0x22, 0x44, 0x4e, 0x8a, 0xe1, 0x5d, - 0x58, 0x37, 0x56, 0x0a, 0xdf, 0x54, 0x99, 0xd6, 0x65, 0x8d, 0xfe, 0x40, 0x32, 0xbc, 0x07, 0x83, - 0xd5, 0x3e, 0xfc, 0xb2, 0x64, 0x69, 0x72, 0xe4, 0xa4, 0xa4, 0x52, 0xea, 0x50, 0x4a, 0xdd, 0x4a, - 0x96, 0xc2, 0xcf, 0x06, 0x9f, 0xbf, 0x6d, 0xa2, 0x2f, 0xfc, 0xfb, 0xca, 0xbf, 0x8f, 0xdf, 0x37, - 0x2f, 0x3d, 0x0f, 0xe4, 0x73, 0x7e, 0xf7, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x2c, 0xbd, - 0x68, 0xe7, 0x05, 0x00, 0x00, + // 520 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0xb1, 0x63, 0xa7, 0x53, 0x8a, 0xc2, 0xa6, 0x09, 0xc6, 0x48, 0x15, 0xda, 0x53, 0x24, + 0x44, 0x0e, 0xe5, 0x09, 0x9c, 0xa6, 0xb4, 0x08, 0x0a, 0x95, 0xd3, 0x9c, 0x38, 0x20, 0x53, 0x6f, + 0xa0, 0x6a, 0xb1, 0x8d, 0xd7, 0x50, 0xf1, 0x26, 0xbc, 0x03, 0x12, 0x6f, 0x81, 0xc4, 0x91, 0x47, + 0x40, 0xf0, 0x0e, 0x9c, 0xd9, 0x3f, 0xda, 0xdd, 0xb2, 0x8b, 0x44, 0xe1, 0x60, 0x29, 0xdf, 0x7c, + 0xe3, 0xcf, 0x33, 0xdf, 0xcc, 0x6e, 0x60, 0xf8, 0x32, 0x2f, 0x8b, 0x13, 0xd2, 0x3c, 0x7b, 0x95, + 0x97, 0xf9, 0x0b, 0xd2, 0x4c, 0xea, 0xa6, 0x6a, 0x2b, 0x14, 0x1c, 0x56, 0x0d, 0xc1, 0x2d, 0xac, + 0x2f, 0x6a, 0x4a, 0x9a, 0xf6, 0xc9, 0x41, 0x9a, 0xee, 0xca, 0xc4, 0x8c, 0xbc, 0x46, 0xeb, 0xd0, + 0x3d, 0xa8, 0x8e, 0x49, 0x19, 0x7b, 0xb7, 0xbd, 0xf1, 0x4a, 0xd6, 0x6d, 0x39, 0x40, 0x23, 0x08, + 0xd3, 0xba, 0xde, 0x5e, 0x3c, 0x88, 0x3b, 0x2c, 0x7c, 0x35, 0x0b, 0x73, 0x81, 0x78, 0x7c, 0x46, + 0xde, 0xf2, 0xb8, 0x2f, 0xe3, 0x85, 0x40, 0x2a, 0xff, 0x21, 0x79, 0x17, 0x07, 0x67, 0xf9, 0x0c, + 0xe1, 0x91, 0xf5, 0xab, 0x14, 0x7f, 0xf0, 0x60, 0x20, 0x89, 0x74, 0xba, 0x7f, 0xe9, 0x6a, 0x62, + 0x88, 0x58, 0x35, 0x69, 0x51, 0x34, 0xaa, 0x9c, 0xa8, 0x90, 0x90, 0x33, 0x8f, 0x4f, 0x8f, 0xe7, + 0xe7, 0x05, 0x45, 0xa5, 0x84, 0x9c, 0x61, 0x5a, 0x82, 0xe9, 0x4a, 0x26, 0x97, 0x50, 0xa9, 0xed, + 0x55, 0x05, 0x89, 0x43, 0xc6, 0xf4, 0x84, 0x1a, 0x87, 0x78, 0x68, 0x2b, 0x96, 0xe2, 0x6d, 0x18, + 0x3e, 0x3a, 0xa2, 0x2d, 0x7b, 0xe9, 0xe8, 0x90, 0xd0, 0xcb, 0x76, 0x81, 0x4b, 0xbb, 0x0c, 0x45, + 0x77, 0x20, 0xe0, 0xb6, 0x31, 0x15, 0x7f, 0xbc, 0xba, 0x79, 0x63, 0xc2, 0xe7, 0x38, 0x51, 0x3c, + 0x27, 0xe4, 0x1b, 0x59, 0x50, 0xb1, 0xdf, 0x68, 0x0c, 0x3e, 0xab, 0x8e, 0x49, 0xf3, 0xdc, 0x91, + 0x91, 0xcb, 0xe2, 0x2a, 0xd5, 0xcf, 0xa7, 0xfb, 0xf8, 0xa3, 0x07, 0xfd, 0x8b, 0x8c, 0x6e, 0x65, + 0xc7, 0x69, 0xa5, 0xef, 0xb4, 0x32, 0x30, 0xad, 0x64, 0xad, 0xde, 0xdf, 0x2a, 0xdb, 0x45, 0x2d, + 0x3c, 0x5e, 0xcb, 0xc2, 0xa5, 0x40, 0x28, 0x81, 0x1e, 0x8f, 0xcf, 0xaa, 0xd3, 0x52, 0x78, 0xbc, + 0x96, 0xf5, 0x96, 0x0a, 0xeb, 0xf6, 0x47, 0xa6, 0xfd, 0x9f, 0x3c, 0xb8, 0xfe, 0x5b, 0xdb, 0xda, + 0x2a, 0x7a, 0xc6, 0x2a, 0xfe, 0xf7, 0x4e, 0xd4, 0x62, 0x77, 0xf5, 0xc5, 0xd6, 0x3a, 0x8c, 0x9c, + 0x1d, 0xf6, 0xcc, 0x0e, 0xf1, 0x16, 0x0c, 0x76, 0x08, 0x9b, 0xf3, 0x32, 0x7f, 0x73, 0xa2, 0xc6, + 0xfd, 0xf7, 0xdb, 0x72, 0xd7, 0x26, 0x42, 0xb5, 0x3a, 0x3d, 0xe3, 0x00, 0x3e, 0x85, 0xc1, 0xfc, + 0x5f, 0xbf, 0xa9, 0x89, 0xfb, 0x86, 0xf8, 0xd0, 0x26, 0x4e, 0x37, 0x7f, 0x74, 0xe0, 0x9a, 0x9a, + 0xd7, 0x9e, 0xbc, 0x89, 0xd0, 0x0c, 0xe0, 0xfc, 0x1e, 0x40, 0x89, 0x5c, 0x4f, 0xdb, 0x7d, 0x94, + 0xb8, 0x39, 0x8a, 0x52, 0x58, 0x39, 0x3b, 0x87, 0xe8, 0xa6, 0x9e, 0x68, 0xdc, 0x22, 0x89, 0x93, + 0xa2, 0x68, 0x07, 0x56, 0xb5, 0xc3, 0x86, 0x6e, 0xc9, 0x4c, 0xeb, 0x31, 0x4e, 0xfe, 0x40, 0x52, + 0xb4, 0x0b, 0xfd, 0x8b, 0x73, 0xf8, 0x55, 0x92, 0x65, 0xc8, 0x89, 0x93, 0x12, 0x4a, 0x73, 0x87, + 0xd2, 0xdc, 0xad, 0x64, 0x31, 0x7e, 0xda, 0xff, 0xfc, 0x6d, 0xc3, 0xfb, 0xc2, 0x9e, 0xaf, 0xec, + 0x79, 0xff, 0x7d, 0xe3, 0xca, 0xf3, 0x50, 0xfc, 0x05, 0xdc, 0xfb, 0x19, 0x00, 0x00, 0xff, 0xff, + 0xef, 0x7e, 0x53, 0x39, 0x1b, 0x06, 0x00, 0x00, } diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index 9df7903e7..c42063cac 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -16,6 +16,7 @@ message UpsertABPBrokerReq { string NetAddress = 3; bytes DevAddr = 4; bytes NwkSKey = 5; + bool DevMode = 6; } message UpsertABPBrokerRes {} diff --git a/core/protos/handler.proto b/core/protos/handler.proto index 7d862adaf..a84d4ddd0 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -10,6 +10,7 @@ message DataUpHandlerReq { uint32 FCnt = 10; uint32 FPort = 11; uint32 MType = 12; + bool FCUPRst = 13; bytes Payload = 20; Metadata Metadata = 30; } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index a9401dea8..75a6e11ec 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -17,6 +17,7 @@ message UpsertABPHandlerReq { bytes DevAddr = 3; bytes NwkSKey = 4; bytes AppSKey = 5; + bool DevMode = 6; } message UpsertABPHandlerRes {} @@ -37,6 +38,7 @@ message HandlerABPDevice { bytes AppSKey = 4; uint32 FCntUp = 5; uint32 FCntDown = 6; + bool DevMode = 7; } message HandlerOTAADevice { diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 427359cb7..e17456f07 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" "reflect" + "strings" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/components/handler" @@ -75,10 +76,10 @@ registered on the Handler.`, table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevAddr", "FCntUp", "FCntDown") + table.AddRow("DevAddr", "FCntUp", "FCntDown", "DeveloperMode") for _, device := range devices.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) - table.AddRow(devAddr, device.FCntUp, device.FCntDown) + table.AddRow(devAddr, device.FCntUp, device.FCntDown, device.DevMode) } fmt.Println() @@ -196,6 +197,8 @@ var devicesInfoCmd = &cobra.Command{ fmt.Println() fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) + fmt.Println() + fmt.Printf(" Developer Mode: %t\n", device.DevMode) return } } @@ -275,7 +278,7 @@ the Handler`, // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ - Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", + Use: "personalized [DevAddr] [NwkSKey] [AppSKey] [DeveloperMode]", Short: "Create or update ABP registrations on the Handler", Long: `ttnctl devices register personalized creates or updates an ABP registration on the Handler`, @@ -293,6 +296,7 @@ registration on the Handler`, } var nwkSKey, appSKey []byte + devMode := false if len(args) >= 3 { nwkSKey, err = util.Parse128(args[1]) if err != nil { @@ -302,6 +306,9 @@ registration on the Handler`, if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } + if (len(args) >= 4) && strings.EqualFold(args[3], "true") { + devMode = true + } } else { ctx.Info("Generating random NwkSKey and AppSKey...") nwkSKey = random.Bytes(16) @@ -323,6 +330,7 @@ registration on the Handler`, DevAddr: devAddr, AppSKey: appSKey, NwkSKey: nwkSKey, + DevMode: devMode, }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") @@ -331,6 +339,7 @@ registration on the Handler`, "DevAddr": devAddr, "NwkSKey": nwkSKey, "AppSKey": appSKey, + "DevMode": devMode, }).Info("Registered personalized device") }, } diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go index 606502d2f..90648b247 100644 --- a/utils/readwriter/readwriter.go +++ b/utils/readwriter/readwriter.go @@ -49,6 +49,15 @@ func New(buf []byte) Interface { func (w *rw) Write(data interface{}) { var dataLen uint16 switch data.(type) { + case bool: + if data.(bool) { + w.directWrite(uint16(1)) + w.directWrite(byte(1)) + } else { + w.directWrite(uint16(1)) + w.directWrite(byte(0)) + } + return case uint8: dataLen = 1 case uint16: From 66f178426f0f668ddfe09c858f3c2fd84d5fb223 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Mon, 25 Apr 2016 00:25:44 +0200 Subject: [PATCH 1344/2266] Update field name to FCntUpReset --- core/components/broker/broker.go | 2 +- core/components/handler/handler.go | 2 +- core/handler.pb.go | 86 +++++++++++++++--------------- core/protos/handler.proto | 2 +- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index e1fa637e7..92c8e8940 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -327,7 +327,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c FPort: req.Payload.MACPayload.FPort, MType: req.Payload.MHDR.MType, Metadata: req.Metadata, - FCUPRst: fcntReset, + FCntUpReset: fcntReset, }) if err != nil { diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 1abb001bc..de6830a81 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -634,7 +634,7 @@ func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bu stats.MarkMeter("handler.downlink.pull") downType := lorawan.UnconfirmedDataDown ack := (upType == lorawan.ConfirmedDataUp) - if bundle.Packet.(*core.DataUpHandlerReq).FCUPRst { + if bundle.Packet.(*core.DataUpHandlerReq).FCntUpReset { bundle.Entry.FCntDown = 0 } downlink, err := h.buildDownlink(downlink.Payload, downType, ack, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) diff --git a/core/handler.pb.go b/core/handler.pb.go index 41e869e87..d8fadaaca 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -21,14 +21,14 @@ var _ = fmt.Errorf var _ = math.Inf type DataUpHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - FCnt uint32 `protobuf:"varint,10,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` - FPort uint32 `protobuf:"varint,11,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` - MType uint32 `protobuf:"varint,12,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` - FCUPRst bool `protobuf:"varint,13,opt,name=FCUPRst,json=fCUPRst,proto3" json:"FCUPRst,omitempty"` - Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` + FCnt uint32 `protobuf:"varint,10,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` + FPort uint32 `protobuf:"varint,11,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` + MType uint32 `protobuf:"varint,12,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` + FCntUpReset bool `protobuf:"varint,13,opt,name=FCntUpReset,json=fCntUpReset,proto3" json:"FCntUpReset,omitempty"` + Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` } func (m *DataUpHandlerReq) Reset() { *m = DataUpHandlerReq{} } @@ -299,10 +299,10 @@ func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandler(data, i, uint64(m.MType)) } - if m.FCUPRst { + if m.FCntUpReset { data[i] = 0x68 i++ - if m.FCUPRst { + if m.FCntUpReset { data[i] = 1 } else { data[i] = 0 @@ -595,7 +595,7 @@ func (m *DataUpHandlerReq) Size() (n int) { if m.MType != 0 { n += 1 + sovHandler(uint64(m.MType)) } - if m.FCUPRst { + if m.FCntUpReset { n += 2 } l = len(m.Payload) @@ -862,7 +862,7 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { } case 13: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCUPRst", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FCntUpReset", wireType) } var v int for shift := uint(0); ; shift += 7 { @@ -879,7 +879,7 @@ func (m *DataUpHandlerReq) Unmarshal(data []byte) error { break } } - m.FCUPRst = bool(v != 0) + m.FCntUpReset = bool(v != 0) case 20: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) @@ -1794,34 +1794,34 @@ var ( ) var fileDescriptorHandler = []byte{ - // 456 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x94, 0x41, 0xaf, 0x12, 0x31, - 0x10, 0xc7, 0x5d, 0xe1, 0xb1, 0xeb, 0xb0, 0x10, 0x9c, 0xe0, 0xb3, 0xd9, 0x03, 0x31, 0x9c, 0x8c, - 0x26, 0x2f, 0x11, 0x2f, 0x9e, 0x4c, 0x10, 0x24, 0xa2, 0x40, 0x48, 0x85, 0x78, 0xae, 0x6c, 0x89, - 0xe6, 0xc1, 0x76, 0xdd, 0x6d, 0x24, 0x7c, 0x13, 0xe3, 0xd5, 0x2f, 0xa3, 0x37, 0x3f, 0x82, 0x79, - 0x7e, 0x11, 0xdb, 0x0e, 0x86, 0x2c, 0xf2, 0x0e, 0x4f, 0x0e, 0x9b, 0xf4, 0xff, 0x6b, 0x67, 0xe6, - 0x3f, 0x9d, 0x02, 0xd4, 0x3e, 0x88, 0x24, 0x5e, 0xc9, 0xec, 0x22, 0xcd, 0x94, 0x56, 0x58, 0x5e, - 0xa8, 0x4c, 0x46, 0xb5, 0x95, 0xca, 0xc4, 0x46, 0x24, 0x04, 0x23, 0xb0, 0x90, 0xd6, 0xed, 0x2b, - 0x0f, 0x1a, 0x7d, 0xa1, 0xc5, 0x3c, 0x7d, 0x45, 0x81, 0x5c, 0x7e, 0xc2, 0x73, 0xa8, 0x74, 0xd3, - 0xf4, 0xe5, 0x7c, 0xc8, 0xbc, 0x07, 0xde, 0xc3, 0x90, 0x57, 0x84, 0x53, 0x96, 0xf7, 0xe5, 0x67, - 0xcb, 0x6f, 0x13, 0x8f, 0x9d, 0x42, 0x84, 0xf2, 0xa0, 0x97, 0x68, 0x06, 0x86, 0xd6, 0x78, 0x79, - 0x69, 0xd6, 0xd8, 0x84, 0xb3, 0xc1, 0x54, 0x65, 0x9a, 0x55, 0x1d, 0x3c, 0x5b, 0x5a, 0x61, 0xe9, - 0x78, 0xb6, 0x4d, 0x25, 0x0b, 0x89, 0xae, 0xad, 0x40, 0x06, 0xfe, 0xa0, 0x37, 0x9f, 0xf2, 0x5c, - 0xb3, 0x9a, 0xe1, 0x01, 0xf7, 0x97, 0x24, 0xed, 0xce, 0x54, 0x6c, 0x57, 0x4a, 0xc4, 0xac, 0xe9, - 0x4a, 0xfa, 0x29, 0x49, 0x7c, 0x04, 0xc1, 0x58, 0x6a, 0x11, 0x1b, 0xef, 0xac, 0x65, 0xb6, 0xaa, - 0x9d, 0xfa, 0x85, 0xeb, 0xeb, 0x2f, 0xe5, 0xc1, 0x7a, 0xb7, 0x6a, 0x5f, 0xfe, 0xd3, 0x63, 0x8e, - 0x8f, 0x8b, 0x99, 0xab, 0x9d, 0xbb, 0x14, 0x3e, 0x52, 0x5c, 0xbc, 0xeb, 0x4e, 0xec, 0xf9, 0xff, - 0x2b, 0x96, 0x02, 0xda, 0xe0, 0xbe, 0xda, 0x24, 0x27, 0x5c, 0xe9, 0xf5, 0x8d, 0x37, 0xa0, 0x34, - 0x9b, 0x8d, 0x9c, 0x8d, 0x3b, 0xbc, 0xa4, 0x67, 0xa3, 0x76, 0xf3, 0x48, 0xc5, 0xbc, 0xfd, 0xd5, - 0x83, 0xfa, 0x6b, 0xf5, 0xf1, 0x14, 0x13, 0x11, 0x04, 0x86, 0x4f, 0x54, 0xb2, 0x90, 0x6e, 0xb6, - 0x21, 0x0f, 0xe2, 0x9d, 0xb6, 0x36, 0xc6, 0xc3, 0x9e, 0x9b, 0x6e, 0xc8, 0x4b, 0xeb, 0x61, 0xef, - 0x46, 0x97, 0xf4, 0xed, 0xd0, 0x5c, 0x6e, 0x3b, 0x36, 0xc5, 0xba, 0x71, 0x9c, 0xed, 0xdc, 0xf9, - 0x31, 0x49, 0x7c, 0x72, 0x38, 0xaa, 0xfb, 0x85, 0x51, 0xd9, 0x3c, 0xdd, 0xc5, 0x42, 0xa6, 0x7a, - 0x7f, 0x49, 0x26, 0xd9, 0x64, 0x73, 0xf9, 0xf6, 0x8d, 0xdc, 0xb2, 0x7b, 0x94, 0x2c, 0x21, 0x79, - 0x13, 0x97, 0x9d, 0x1f, 0x1e, 0xf8, 0x3b, 0x87, 0xf8, 0x1c, 0x42, 0x5a, 0xd2, 0x4b, 0xc2, 0x73, - 0x8a, 0x3a, 0xfc, 0xed, 0x44, 0xc7, 0x79, 0x8e, 0x7d, 0xa8, 0xef, 0xe3, 0xed, 0xa8, 0x90, 0xed, - 0x4f, 0x16, 0x1f, 0x4b, 0x74, 0xdd, 0x4e, 0x8e, 0xcf, 0x00, 0x48, 0xd9, 0xa6, 0xb1, 0x49, 0xe7, - 0x8a, 0x53, 0x8e, 0x8e, 0xd1, 0xfc, 0x45, 0xe3, 0xfb, 0x55, 0xcb, 0xfb, 0x69, 0xbe, 0x5f, 0xe6, - 0xfb, 0xf2, 0xbb, 0x75, 0xeb, 0x7d, 0xc5, 0xfd, 0x03, 0x3c, 0xfd, 0x13, 0x00, 0x00, 0xff, 0xff, - 0x33, 0x19, 0x96, 0x2c, 0x33, 0x04, 0x00, 0x00, + // 461 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x94, 0x4d, 0x8f, 0x12, 0x31, + 0x18, 0xc7, 0x1d, 0x61, 0x61, 0x7c, 0x66, 0x20, 0xf8, 0x04, 0xd7, 0x66, 0x0e, 0x64, 0xc3, 0xc9, + 0x68, 0xb2, 0x89, 0x78, 0xf1, 0x64, 0x82, 0xe0, 0x46, 0x14, 0xc8, 0xa6, 0x42, 0x3c, 0x57, 0x5a, + 0xa2, 0x59, 0x98, 0xd6, 0x99, 0x46, 0xc2, 0x37, 0x31, 0x5e, 0xfd, 0x32, 0x7a, 0xf3, 0x23, 0x18, + 0xbd, 0xf9, 0x29, 0xec, 0x0b, 0x66, 0x32, 0xc8, 0x1e, 0x76, 0xf7, 0xd0, 0xe4, 0xf9, 0xff, 0xda, + 0xe7, 0xad, 0x4f, 0x67, 0xa0, 0xf1, 0x9e, 0xa5, 0x7c, 0x25, 0xb2, 0x53, 0x95, 0x49, 0x2d, 0xb1, + 0xba, 0x90, 0x99, 0x48, 0x1a, 0x2b, 0x99, 0xb1, 0x0d, 0x4b, 0x3d, 0x4c, 0xc0, 0x42, 0x6f, 0x77, + 0xff, 0x04, 0xd0, 0x1a, 0x32, 0xcd, 0xe6, 0xea, 0xa5, 0x77, 0xa4, 0xe2, 0x23, 0x1e, 0x43, 0xad, + 0xaf, 0xd4, 0x8b, 0xf9, 0x88, 0x04, 0x27, 0xc1, 0x83, 0x98, 0xd6, 0x98, 0x53, 0x96, 0x0f, 0xc5, + 0x27, 0xcb, 0x6f, 0x7b, 0xce, 0x9d, 0x42, 0x84, 0xea, 0xd9, 0x20, 0xd5, 0x04, 0x0c, 0x6d, 0xd0, + 0xea, 0xd2, 0xd8, 0xd8, 0x86, 0xa3, 0xb3, 0x73, 0x99, 0x69, 0x12, 0x39, 0x78, 0xb4, 0xb4, 0xc2, + 0xd2, 0xc9, 0x6c, 0xab, 0x04, 0x89, 0x3d, 0x5d, 0x5b, 0x81, 0x27, 0x10, 0x59, 0xff, 0xb9, 0xa2, + 0x22, 0x17, 0x9a, 0x34, 0xcc, 0x5e, 0x48, 0xa3, 0x65, 0x81, 0x90, 0x40, 0xfd, 0x9c, 0x6d, 0x57, + 0x92, 0x71, 0xd2, 0x76, 0xa9, 0xeb, 0xca, 0x4b, 0x7c, 0x08, 0xe1, 0x44, 0x68, 0xc6, 0x4d, 0x0f, + 0xa4, 0x63, 0xb6, 0xa2, 0x5e, 0xf3, 0xd4, 0xf5, 0xf7, 0x8f, 0xd2, 0x70, 0xbd, 0xb3, 0xba, 0x17, + 0xff, 0xf5, 0x9a, 0xe3, 0xa3, 0x72, 0xe4, 0xa8, 0x77, 0xd7, 0xbb, 0x8f, 0x25, 0x65, 0x6f, 0xfb, + 0x53, 0x7b, 0xfe, 0x7a, 0xc9, 0x14, 0xa0, 0x75, 0x1e, 0xca, 0x4d, 0x7a, 0x83, 0xab, 0xbd, 0xbc, + 0xf1, 0x16, 0x54, 0x66, 0xb3, 0xb1, 0x2b, 0xe3, 0x0e, 0xad, 0xe8, 0xd9, 0xb8, 0xdb, 0x3e, 0x90, + 0x31, 0xef, 0x7e, 0x09, 0xa0, 0xf9, 0x4a, 0x7e, 0xb8, 0x49, 0x11, 0x09, 0x84, 0x86, 0x4f, 0x65, + 0xba, 0x10, 0x6e, 0xc6, 0x31, 0x0d, 0xf9, 0x4e, 0xdb, 0x32, 0x26, 0xa3, 0x81, 0x9b, 0x72, 0x4c, + 0x2b, 0xeb, 0xd1, 0xe0, 0x4a, 0x97, 0xf4, 0x75, 0xbf, 0xb8, 0xdc, 0x76, 0x6c, 0x92, 0xf5, 0x39, + 0xcf, 0x76, 0xd5, 0xd5, 0xb9, 0x97, 0xf8, 0x78, 0x7f, 0x54, 0xf7, 0x4b, 0xa3, 0xb2, 0x71, 0xfa, + 0x8b, 0x85, 0x50, 0xba, 0xb8, 0x24, 0x13, 0x6c, 0xba, 0xb9, 0x78, 0xf3, 0x5a, 0x6c, 0xc9, 0x3d, + 0x1f, 0x2c, 0xf5, 0xf2, 0x2a, 0x55, 0xf6, 0xbe, 0x07, 0x50, 0xdf, 0x55, 0x88, 0xcf, 0x20, 0xf6, + 0xa6, 0x7f, 0x49, 0x78, 0xec, 0xbd, 0xf6, 0xbf, 0xa1, 0xe4, 0x30, 0xcf, 0x71, 0x08, 0xcd, 0xc2, + 0xdf, 0x8e, 0x0a, 0x49, 0x71, 0xb2, 0xfc, 0x58, 0x92, 0xcb, 0x76, 0x72, 0x7c, 0x0a, 0xe0, 0x95, + 0x6d, 0x1a, 0xdb, 0xfe, 0x5c, 0x79, 0xca, 0xc9, 0x21, 0x9a, 0x3f, 0x6f, 0x7d, 0xfb, 0xd5, 0x09, + 0x7e, 0x98, 0xf5, 0xd3, 0xac, 0xcf, 0xbf, 0x3b, 0xb7, 0xde, 0xd5, 0xdc, 0x9f, 0xe0, 0xc9, 0xdf, + 0x00, 0x00, 0x00, 0xff, 0xff, 0xae, 0x68, 0x70, 0x32, 0x3b, 0x04, 0x00, 0x00, } diff --git a/core/protos/handler.proto b/core/protos/handler.proto index a84d4ddd0..4ee9b12e7 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -10,7 +10,7 @@ message DataUpHandlerReq { uint32 FCnt = 10; uint32 FPort = 11; uint32 MType = 12; - bool FCUPRst = 13; + bool FCntUpReset = 13; bytes Payload = 20; Metadata Metadata = 30; } From b4d07eea32d5ae533446e4054ea1633c2e4620c6 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Mon, 25 Apr 2016 00:37:01 +0200 Subject: [PATCH 1345/2266] Fix formatting errors from merge --- core/components/broker/broker.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 92c8e8940..ca3a5e13f 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -320,14 +320,14 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } defer closer.Close() resp, err := handler.HandleDataUp(context.Background(), &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - DevEUI: mEntry.DevEUI, - AppEUI: mEntry.AppEUI, - FCnt: fhdr.FCnt, - FPort: req.Payload.MACPayload.FPort, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - FCntUpReset: fcntReset, + Payload: req.Payload.MACPayload.FRMPayload, + DevEUI: mEntry.DevEUI, + AppEUI: mEntry.AppEUI, + FCnt: fhdr.FCnt, + FPort: req.Payload.MACPayload.FPort, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + FCntUpReset: fcntReset, }) if err != nil { From 3672f6e9e6b3a00ec916d3ec68eab0d1a5e5776d Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Mon, 25 Apr 2016 22:33:26 +0200 Subject: [PATCH 1346/2266] Redesign developer mode checks based on pull request review comments. --- core/components/broker/broker.go | 31 ++++++++++------------- core/components/broker/controller.go | 11 +++----- core/components/broker/controller_test.go | 14 +++++----- core/components/broker/mocks_test.go | 4 +-- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index ca3a5e13f..895580383 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -246,26 +246,27 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c fhdr := &uplinkPayload.MACPayload.(*lorawan.MACPayload).FHDR // No nil ref, ensured by NewLoRaWANData() fcnt16 := fhdr.FCnt // Keep a reference to the original counter - fcntReset := false + var fcntReset bool var mEntry *devEntry for _, entry := range entries { + fcntReset = false // retrieve the network session key key := lorawan.AES128Key(entry.NwkSKey) - // Check with 16-bits counters - fhdr.FCnt = fcnt16 - fcnt32, counterReset, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) + // Check frame counter is in valid range + fcnt32, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) if err != nil { - continue - } - - if counterReset { - dtx := ctx.WithFields(log.Fields{ - "DevMode": entry.DevMode, - }) - dtx.Debug("Counter reset detected") + // invalid, is device in developer mode + if entry.DevMode { + fcnt32 = fcnt16 + fcntReset = true + } else { + continue + } } + // Check with 16-bits counters + fhdr.FCnt = fcnt16 ok, err := uplinkPayload.ValidateMIC(key) if err != nil { continue @@ -279,12 +280,6 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c } if ok { - if counterReset { - if !entry.DevMode { - continue - } - fcntReset = true - } mEntry = &entry stats.MarkMeter("broker.uplink.handler_lookup.mic_match") ctx = ctx.WithFields(log.Fields{ diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 7f1b78ac2..280e7e753 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -22,7 +22,7 @@ type NetworkController interface { readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) upsertNonces(entry noncesEntry) error upsert(entry devEntry) error - wholeCounter(devCnt uint32, entryCnt uint32) (uint32, bool, error) + wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) done() error } @@ -69,22 +69,19 @@ func (s *controller) read(devAddr []byte) ([]devEntry, error) { } // wholeCounter implements the broker.NetworkController interface -func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, bool, error) { +func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { upperSup := int(math.Pow(2, 16)) diff := int(devCnt) - (int(entryCnt) % upperSup) var offset int if diff >= 0 { offset = diff } else { - if entryCnt < (uint32(upperSup) - 10) { - return devCnt, true, nil - } offset = upperSup + diff } if offset > upperSup/4 { - return 0, false, errors.New(errors.Structural, "Gap too big, counter is errored") + return 0, errors.New(errors.Structural, "Gap too big, counter is errored") } - return entryCnt + uint32(offset), false, nil + return entryCnt + uint32(offset), nil } // upsert implements the broker.NetworkController interface diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go index 8d5253f1f..bfd1d9b14 100644 --- a/core/components/broker/controller_test.go +++ b/core/components/broker/controller_test.go @@ -252,7 +252,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt + 1 // Operate - cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) @@ -272,12 +272,10 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := wholeCnt - 1 // Operate - cnt32, countReset, err := db.wholeCounter(cnt16, wholeCnt) + _, err := db.wholeCounter(cnt16, wholeCnt) // Check - CheckErrors(t, nil, err) - Check(t, cnt16, cnt32, "Counters") - Check(t, true, countReset, "Counter reset") + CheckErrors(t, ErrStructural, err) _ = db.done() } @@ -293,7 +291,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 2) // Operate - cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) @@ -313,7 +311,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(wholeCnt%65536 + 45000) // Operate - _, _, err := db.wholeCounter(cnt16, wholeCnt) + _, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, ErrStructural, err) @@ -332,7 +330,7 @@ func TestNetworkControllerDevice(t *testing.T) { cnt16 := uint32(2) // Operate - cnt32, _, err := db.wholeCounter(cnt16, wholeCnt) + cnt32, err := db.wholeCounter(cnt16, wholeCnt) // Check CheckErrors(t, nil, err) diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go index 803eaca37..46f1a36ed 100644 --- a/core/components/broker/mocks_test.go +++ b/core/components/broker/mocks_test.go @@ -176,10 +176,10 @@ func (m *MockNetworkController) upsertNonces(entry noncesEntry) error { } // wholeCnt implements the NetworkController interface -func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, bool, error) { +func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, error) { m.InWholeCounter.DevCnt = devCnt m.InWholeCounter.EntryCnt = entryCnt - return m.OutWholeCounter.FCnt, false, m.Failures["wholeCounter"] + return m.OutWholeCounter.FCnt, m.Failures["wholeCounter"] } // done implements the NetworkController Interface From 24c05ee352309745cd9ca3aef68469a7ffe862db Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Tue, 26 Apr 2016 08:00:34 +0200 Subject: [PATCH 1347/2266] Replace DevMode bool with bit flags --- core/broker_manager.pb.go | 50 +++++----- core/components/broker/broker.go | 4 +- core/components/broker/brokerManager.go | 2 +- core/components/broker/controller.go | 6 +- core/components/handler/devStorage.go | 6 +- core/components/handler/handler.go | 2 +- core/components/handler/handlerManager.go | 6 +- core/flags.go | 8 ++ core/handler_manager.pb.go | 110 ++++++++++------------ core/protos/broker_manager.proto | 2 +- core/protos/handler_manager.proto | 4 +- ttnctl/cmd/device.go | 16 ++-- 12 files changed, 103 insertions(+), 113 deletions(-) create mode 100644 core/flags.go diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index 3ad8392ff..ba3aaced9 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -49,7 +49,7 @@ type UpsertABPBrokerReq struct { NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` DevAddr []byte `protobuf:"bytes,4,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,5,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - DevMode bool `protobuf:"varint,6,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` + Flags uint32 `protobuf:"varint,6,opt,name=Flags,json=flags,proto3" json:"Flags,omitempty"` } func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } @@ -322,15 +322,10 @@ func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) i += copy(data[i:], m.NwkSKey) } - if m.DevMode { + if m.Flags != 0 { data[i] = 0x30 i++ - if m.DevMode { - data[i] = 1 - } else { - data[i] = 0 - } - i++ + i = encodeVarintBrokerManager(data, i, uint64(m.Flags)) } return i, nil } @@ -511,8 +506,8 @@ func (m *UpsertABPBrokerReq) Size() (n int) { if l > 0 { n += 1 + l + sovBrokerManager(uint64(l)) } - if m.DevMode { - n += 2 + if m.Flags != 0 { + n += 1 + sovBrokerManager(uint64(m.Flags)) } return n } @@ -945,9 +940,9 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { iNdEx = postIndex case 6: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) } - var v int + m.Flags = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBrokerManager @@ -957,12 +952,11 @@ func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - v |= (int(b) & 0x7F) << shift + m.Flags |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBrokerManager(data[iNdEx:]) @@ -1443,7 +1437,7 @@ var ( ) var fileDescriptorBrokerManager = []byte{ - // 343 bytes of a gzipped FileDescriptorProto + // 346 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x4a, 0xe5, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, @@ -1451,19 +1445,19 @@ var fileDescriptorBrokerManager = []byte{ 0x0d, 0x01, 0xb2, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x58, 0x4b, 0x40, 0x1c, 0x21, 0x31, 0x2e, 0x36, 0xc7, 0x82, 0x02, 0xd7, 0x50, 0x4f, 0x09, 0x26, 0xa0, 0x30, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x27, 0x24, 0xc7, 0xc5, 0xe5, 0x97, 0x5a, 0xe2, 0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, - 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x06, + 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x1a, 0x46, 0x2e, 0xa1, 0xd0, 0x82, 0xe2, 0xd4, 0xa2, 0x12, 0x47, 0xa7, 0x00, 0x1a, 0xd9, 0x2e, 0x24, 0xc1, 0xc5, 0xee, 0x92, 0x5a, 0x06, 0xe2, 0x49, 0xb0, 0x80, 0x35, 0xb2, 0xa7, 0x40, 0xb8, 0x20, - 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0x0b, 0xd5, - 0xe3, 0x9b, 0x9f, 0x92, 0x2a, 0xc1, 0x06, 0x94, 0xe1, 0x00, 0xeb, 0x01, 0x71, 0x95, 0x44, 0xb0, - 0xb8, 0xb8, 0x58, 0x29, 0x8a, 0x8b, 0x07, 0xc2, 0x01, 0xea, 0xca, 0x4c, 0x4e, 0x05, 0xb9, 0x15, - 0xc8, 0x02, 0xb9, 0x95, 0x11, 0xe2, 0xd6, 0x14, 0x30, 0x0f, 0xd9, 0x2d, 0x4c, 0x38, 0xdd, 0xc2, - 0x8c, 0xe2, 0x16, 0x25, 0x37, 0x2e, 0x31, 0x58, 0xe8, 0x81, 0x43, 0x85, 0xcc, 0x70, 0x52, 0x92, - 0xc0, 0x61, 0x4e, 0xb1, 0xd1, 0x73, 0x46, 0x2e, 0x5e, 0x08, 0xcf, 0x17, 0x92, 0x48, 0x84, 0x3c, - 0xb8, 0x78, 0x90, 0x63, 0x4c, 0x48, 0x5a, 0x0f, 0x94, 0x5e, 0xf4, 0xb0, 0x26, 0x16, 0x29, 0x3c, - 0x92, 0xc5, 0x42, 0xf6, 0x5c, 0x9c, 0xf0, 0xf0, 0x12, 0x92, 0x80, 0xa8, 0xc4, 0x8c, 0x72, 0x29, - 0x5c, 0x32, 0xc5, 0x42, 0xde, 0x5c, 0xbc, 0x28, 0xce, 0x16, 0x92, 0x41, 0xb5, 0x0e, 0x35, 0x4c, - 0xa4, 0xf0, 0xc9, 0x16, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, - 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0xfc, 0x60, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xc6, - 0x07, 0x41, 0x5b, 0x27, 0x03, 0x00, 0x00, + 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0x0b, 0x72, + 0x81, 0x5b, 0x4e, 0x62, 0x7a, 0xb1, 0x04, 0x1b, 0x50, 0x9c, 0x37, 0x88, 0x35, 0x0d, 0xc4, 0x51, + 0x12, 0xc1, 0xe2, 0xda, 0x62, 0xa5, 0x28, 0x2e, 0x1e, 0x08, 0x07, 0x68, 0x4b, 0x66, 0x72, 0x2a, + 0xc8, 0x9d, 0x40, 0x16, 0xc8, 0x9d, 0x8c, 0x10, 0x77, 0xa6, 0x80, 0x79, 0xc8, 0xee, 0x60, 0xc2, + 0xe9, 0x0e, 0x66, 0x14, 0x77, 0x28, 0xb9, 0x71, 0x89, 0xc1, 0x42, 0x0e, 0x1c, 0x22, 0x64, 0x86, + 0x91, 0x92, 0x04, 0x0e, 0x73, 0x8a, 0x8d, 0x9e, 0x33, 0x72, 0xf1, 0x42, 0x78, 0xbe, 0x90, 0x04, + 0x22, 0xe4, 0xc1, 0xc5, 0x83, 0x1c, 0x5b, 0x42, 0xd2, 0x7a, 0xa0, 0xb4, 0xa2, 0x87, 0x35, 0xa1, + 0x48, 0xe1, 0x91, 0x2c, 0x16, 0xb2, 0xe7, 0xe2, 0x84, 0x87, 0x97, 0x90, 0x04, 0x44, 0x25, 0x66, + 0x74, 0x4b, 0xe1, 0x92, 0x29, 0x16, 0xf2, 0xe6, 0xe2, 0x45, 0x71, 0xb6, 0x90, 0x0c, 0xaa, 0x75, + 0xa8, 0x61, 0x22, 0x85, 0x4f, 0xb6, 0xd8, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x40, 0xfc, + 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xe7, 0x05, 0x63, 0x40, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xc4, 0x86, 0x11, 0x98, 0x23, 0x03, 0x00, 0x00, } diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go index 895580383..162d1c448 100644 --- a/core/components/broker/broker.go +++ b/core/components/broker/broker.go @@ -194,7 +194,7 @@ func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*c AppEUI: req.AppEUI, DevEUI: req.DevEUI, NwkSKey: nwkSKey, - DevMode: false, + Flags: 0, FCntUp: 0, }) if err != nil { @@ -257,7 +257,7 @@ func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*c fcnt32, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) if err != nil { // invalid, is device in developer mode - if entry.DevMode { + if (entry.Flags & core.RelaxFcntCheck) != 0 { fcnt32 = fcnt16 fcntReset = true } else { diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go index 7e54b4b83..7fb7a7be8 100644 --- a/core/components/broker/brokerManager.go +++ b/core/components/broker/brokerManager.go @@ -87,7 +87,7 @@ func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), DevAddr: req.DevAddr, NwkSKey: nwkSKey, - DevMode: req.DevMode, + Flags: req.Flags, FCntUp: 0, }) if err != nil { diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go index 280e7e753..610f7cd86 100644 --- a/core/components/broker/controller.go +++ b/core/components/broker/controller.go @@ -33,7 +33,7 @@ type devEntry struct { Dialer Dialer FCntUp uint32 NwkSKey [16]byte - DevMode bool + Flags uint32 } type noncesEntry struct { @@ -142,7 +142,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.DevAddr) rw.Write(e.NwkSKey[:]) rw.Write(e.FCntUp) - rw.Write(e.DevMode) + rw.Write(e.Flags) rw.Write(e.Dialer.MarshalSafely()) return rw.Bytes() } @@ -164,7 +164,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { }) rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.DevMode = (data[0] != 0) }) + rw.Read(func(data []byte) { e.Flags = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) return rw.Err() } diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go index 821424d8f..667ee2b91 100644 --- a/core/components/handler/devStorage.go +++ b/core/components/handler/devStorage.go @@ -33,7 +33,7 @@ type devEntry struct { FCntDown uint32 FCntUp uint32 NwkSKey [16]byte - DevMode bool + Flags uint32 } type devDefaultEntry struct { @@ -106,7 +106,7 @@ func (e devEntry) MarshalBinary() ([]byte, error) { rw.Write(e.NwkSKey[:]) rw.Write(e.FCntUp) rw.Write(e.FCntDown) - rw.Write(e.DevMode) + rw.Write(e.Flags) rw.Write(e.AppEUI) rw.Write(e.DevEUI) rw.Write(e.DevAddr) @@ -128,7 +128,7 @@ func (e *devEntry) UnmarshalBinary(data []byte) error { rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.DevMode = (data[0] != 0) }) + rw.Read(func(data []byte) { e.Flags = binary.BigEndian.Uint32(data) }) rw.Read(func(data []byte) { e.AppEUI = make([]byte, len(data)) copy(e.AppEUI, data) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 7a5e68b6b..8963566ca 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -508,7 +508,7 @@ func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, da FCntDown: 0, FCntUp: 0, NwkSKey: nwkSKey, - DevMode: false, + Flags: 0, }) if err != nil { ctx.WithError(err).Debug("Unable to initialize devEntry with activation") diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 61281eb91..4476f492e 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -44,7 +44,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle AppSKey: d.AppSKey[:], FCntUp: d.FCntUp, FCntDown: d.FCntDown, - DevMode: d.DevMode, + Flags: d.Flags, }) } else { otaa = append(otaa, &core.HandlerOTAADevice{ @@ -80,7 +80,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq DevAddr: req.DevAddr, NwkSKey: req.NwkSKey, NetAddress: h.PrivateNetAddrAnnounce, - DevMode: req.DevMode, + Flags: req.Flags, }) if err != nil { h.Ctx.WithError(err).Debug("Broker rejected ABP") @@ -95,7 +95,7 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq DevAddr: req.DevAddr, FCntDown: 0, FCntUp: 0, - DevMode: req.DevMode, + Flags: req.Flags, } copy(entry.NwkSKey[:], req.NwkSKey) copy(entry.AppSKey[:], req.AppSKey) diff --git a/core/flags.go b/core/flags.go new file mode 100644 index 000000000..37a2fcf36 --- /dev/null +++ b/core/flags.go @@ -0,0 +1,8 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package core + +const ( + RelaxFcntCheck uint32 = 1 << iota +) diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index 598f92380..60b036c4e 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -50,7 +50,7 @@ type UpsertABPHandlerReq struct { DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` AppSKey []byte `protobuf:"bytes,5,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` - DevMode bool `protobuf:"varint,6,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` + Flags uint32 `protobuf:"varint,6,opt,name=Flags,json=flags,proto3" json:"Flags,omitempty"` } func (m *UpsertABPHandlerReq) Reset() { *m = UpsertABPHandlerReq{} } @@ -114,7 +114,7 @@ type HandlerABPDevice struct { AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` FCntUp uint32 `protobuf:"varint,5,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` FCntDown uint32 `protobuf:"varint,6,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` - DevMode bool `protobuf:"varint,7,opt,name=DevMode,json=devMode,proto3" json:"DevMode,omitempty"` + Flags uint32 `protobuf:"varint,7,opt,name=Flags,json=flags,proto3" json:"Flags,omitempty"` } func (m *HandlerABPDevice) Reset() { *m = HandlerABPDevice{} } @@ -472,15 +472,10 @@ func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) i += copy(data[i:], m.AppSKey) } - if m.DevMode { + if m.Flags != 0 { data[i] = 0x30 i++ - if m.DevMode { - data[i] = 1 - } else { - data[i] = 0 - } - i++ + i = encodeVarintHandlerManager(data, i, uint64(m.Flags)) } return i, nil } @@ -618,15 +613,10 @@ func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) } - if m.DevMode { + if m.Flags != 0 { data[i] = 0x38 i++ - if m.DevMode { - data[i] = 1 - } else { - data[i] = 0 - } - i++ + i = encodeVarintHandlerManager(data, i, uint64(m.Flags)) } return i, nil } @@ -875,8 +865,8 @@ func (m *UpsertABPHandlerReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } - if m.DevMode { - n += 2 + if m.Flags != 0 { + n += 1 + sovHandlerManager(uint64(m.Flags)) } return n } @@ -940,8 +930,8 @@ func (m *HandlerABPDevice) Size() (n int) { if m.FCntDown != 0 { n += 1 + sovHandlerManager(uint64(m.FCntDown)) } - if m.DevMode { - n += 2 + if m.Flags != 0 { + n += 1 + sovHandlerManager(uint64(m.Flags)) } return n } @@ -1445,9 +1435,9 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { iNdEx = postIndex case 6: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) } - var v int + m.Flags = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandlerManager @@ -1457,12 +1447,11 @@ func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - v |= (int(b) & 0x7F) << shift + m.Flags |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -1918,9 +1907,9 @@ func (m *HandlerABPDevice) Unmarshal(data []byte) error { } case 7: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DevMode", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) } - var v int + m.Flags = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandlerManager @@ -1930,12 +1919,11 @@ func (m *HandlerABPDevice) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - v |= (int(b) & 0x7F) << shift + m.Flags |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - m.DevMode = bool(v != 0) default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -2689,37 +2677,37 @@ var ( var fileDescriptorHandlerManager = []byte{ // 520 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0xb1, 0x63, 0xa7, 0x53, 0x8a, 0xc2, 0xa6, 0x09, 0xc6, 0x48, 0x15, 0xda, 0x53, 0x24, - 0x44, 0x0e, 0xe5, 0x09, 0x9c, 0xa6, 0xb4, 0x08, 0x0a, 0x95, 0xd3, 0x9c, 0x38, 0x20, 0x53, 0x6f, - 0xa0, 0x6a, 0xb1, 0x8d, 0xd7, 0x50, 0xf1, 0x26, 0xbc, 0x03, 0x12, 0x6f, 0x81, 0xc4, 0x91, 0x47, - 0x40, 0xf0, 0x0e, 0x9c, 0xd9, 0x3f, 0xda, 0xdd, 0xb2, 0x8b, 0x44, 0xe1, 0x60, 0x29, 0xdf, 0x7c, - 0xe3, 0xcf, 0x33, 0xdf, 0xcc, 0x6e, 0x60, 0xf8, 0x32, 0x2f, 0x8b, 0x13, 0xd2, 0x3c, 0x7b, 0x95, - 0x97, 0xf9, 0x0b, 0xd2, 0x4c, 0xea, 0xa6, 0x6a, 0x2b, 0x14, 0x1c, 0x56, 0x0d, 0xc1, 0x2d, 0xac, - 0x2f, 0x6a, 0x4a, 0x9a, 0xf6, 0xc9, 0x41, 0x9a, 0xee, 0xca, 0xc4, 0x8c, 0xbc, 0x46, 0xeb, 0xd0, - 0x3d, 0xa8, 0x8e, 0x49, 0x19, 0x7b, 0xb7, 0xbd, 0xf1, 0x4a, 0xd6, 0x6d, 0x39, 0x40, 0x23, 0x08, - 0xd3, 0xba, 0xde, 0x5e, 0x3c, 0x88, 0x3b, 0x2c, 0x7c, 0x35, 0x0b, 0x73, 0x81, 0x78, 0x7c, 0x46, - 0xde, 0xf2, 0xb8, 0x2f, 0xe3, 0x85, 0x40, 0x2a, 0xff, 0x21, 0x79, 0x17, 0x07, 0x67, 0xf9, 0x0c, - 0xe1, 0x91, 0xf5, 0xab, 0x14, 0x7f, 0xf0, 0x60, 0x20, 0x89, 0x74, 0xba, 0x7f, 0xe9, 0x6a, 0x62, - 0x88, 0x58, 0x35, 0x69, 0x51, 0x34, 0xaa, 0x9c, 0xa8, 0x90, 0x90, 0x33, 0x8f, 0x4f, 0x8f, 0xe7, - 0xe7, 0x05, 0x45, 0xa5, 0x84, 0x9c, 0x61, 0x5a, 0x82, 0xe9, 0x4a, 0x26, 0x97, 0x50, 0xa9, 0xed, - 0x55, 0x05, 0x89, 0x43, 0xc6, 0xf4, 0x84, 0x1a, 0x87, 0x78, 0x68, 0x2b, 0x96, 0xe2, 0x6d, 0x18, - 0x3e, 0x3a, 0xa2, 0x2d, 0x7b, 0xe9, 0xe8, 0x90, 0xd0, 0xcb, 0x76, 0x81, 0x4b, 0xbb, 0x0c, 0x45, - 0x77, 0x20, 0xe0, 0xb6, 0x31, 0x15, 0x7f, 0xbc, 0xba, 0x79, 0x63, 0xc2, 0xe7, 0x38, 0x51, 0x3c, - 0x27, 0xe4, 0x1b, 0x59, 0x50, 0xb1, 0xdf, 0x68, 0x0c, 0x3e, 0xab, 0x8e, 0x49, 0xf3, 0xdc, 0x91, - 0x91, 0xcb, 0xe2, 0x2a, 0xd5, 0xcf, 0xa7, 0xfb, 0xf8, 0xa3, 0x07, 0xfd, 0x8b, 0x8c, 0x6e, 0x65, - 0xc7, 0x69, 0xa5, 0xef, 0xb4, 0x32, 0x30, 0xad, 0x64, 0xad, 0xde, 0xdf, 0x2a, 0xdb, 0x45, 0x2d, - 0x3c, 0x5e, 0xcb, 0xc2, 0xa5, 0x40, 0x28, 0x81, 0x1e, 0x8f, 0xcf, 0xaa, 0xd3, 0x52, 0x78, 0xbc, - 0x96, 0xf5, 0x96, 0x0a, 0xeb, 0xf6, 0x47, 0xa6, 0xfd, 0x9f, 0x3c, 0xb8, 0xfe, 0x5b, 0xdb, 0xda, - 0x2a, 0x7a, 0xc6, 0x2a, 0xfe, 0xf7, 0x4e, 0xd4, 0x62, 0x77, 0xf5, 0xc5, 0xd6, 0x3a, 0x8c, 0x9c, - 0x1d, 0xf6, 0xcc, 0x0e, 0xf1, 0x16, 0x0c, 0x76, 0x08, 0x9b, 0xf3, 0x32, 0x7f, 0x73, 0xa2, 0xc6, - 0xfd, 0xf7, 0xdb, 0x72, 0xd7, 0x26, 0x42, 0xb5, 0x3a, 0x3d, 0xe3, 0x00, 0x3e, 0x85, 0xc1, 0xfc, - 0x5f, 0xbf, 0xa9, 0x89, 0xfb, 0x86, 0xf8, 0xd0, 0x26, 0x4e, 0x37, 0x7f, 0x74, 0xe0, 0x9a, 0x9a, - 0xd7, 0x9e, 0xbc, 0x89, 0xd0, 0x0c, 0xe0, 0xfc, 0x1e, 0x40, 0x89, 0x5c, 0x4f, 0xdb, 0x7d, 0x94, - 0xb8, 0x39, 0x8a, 0x52, 0x58, 0x39, 0x3b, 0x87, 0xe8, 0xa6, 0x9e, 0x68, 0xdc, 0x22, 0x89, 0x93, - 0xa2, 0x68, 0x07, 0x56, 0xb5, 0xc3, 0x86, 0x6e, 0xc9, 0x4c, 0xeb, 0x31, 0x4e, 0xfe, 0x40, 0x52, - 0xb4, 0x0b, 0xfd, 0x8b, 0x73, 0xf8, 0x55, 0x92, 0x65, 0xc8, 0x89, 0x93, 0x12, 0x4a, 0x73, 0x87, - 0xd2, 0xdc, 0xad, 0x64, 0x31, 0x7e, 0xda, 0xff, 0xfc, 0x6d, 0xc3, 0xfb, 0xc2, 0x9e, 0xaf, 0xec, - 0x79, 0xff, 0x7d, 0xe3, 0xca, 0xf3, 0x50, 0xfc, 0x05, 0xdc, 0xfb, 0x19, 0x00, 0x00, 0xff, 0xff, - 0xef, 0x7e, 0x53, 0x39, 0x1b, 0x06, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0x26, 0xcd, 0x5f, 0x77, 0xc6, 0x50, 0x71, 0xd7, 0x12, 0x82, 0x34, 0xa1, 0x5c, 0x55, 0x42, + 0xf4, 0x62, 0x3c, 0x41, 0xba, 0xee, 0x07, 0xf1, 0x37, 0xa5, 0xeb, 0x15, 0x17, 0xc8, 0x2c, 0xce, + 0x98, 0x56, 0x92, 0x10, 0x07, 0x26, 0xde, 0x84, 0x37, 0x40, 0xe2, 0x3d, 0x90, 0xb8, 0xe4, 0x11, + 0x10, 0xbc, 0x03, 0xd7, 0xd8, 0xb1, 0x59, 0x9d, 0x61, 0x23, 0x51, 0x76, 0x11, 0xa9, 0xdf, 0xf9, + 0x4e, 0x3f, 0x9f, 0xf3, 0x9d, 0x63, 0xc3, 0xe0, 0x15, 0xce, 0xd3, 0x05, 0xa9, 0x5e, 0xbc, 0xc6, + 0x39, 0x3e, 0x21, 0xd5, 0xb8, 0xac, 0x8a, 0xba, 0x40, 0xce, 0x71, 0x51, 0x91, 0xa8, 0x86, 0xcd, + 0x79, 0x49, 0x49, 0x55, 0x3f, 0x3b, 0x8a, 0xe3, 0x03, 0x91, 0x98, 0x90, 0x37, 0x68, 0x13, 0xdc, + 0xa3, 0xe2, 0x8c, 0xe4, 0x81, 0x75, 0xd7, 0x1a, 0xad, 0x25, 0x6e, 0xcd, 0x01, 0x1a, 0x82, 0x17, + 0x97, 0xe5, 0xee, 0xfc, 0x61, 0xd0, 0x61, 0xe1, 0xeb, 0x89, 0x87, 0x1b, 0xc4, 0xe3, 0x53, 0xf2, + 0x8e, 0xc7, 0x6d, 0x11, 0x4f, 0x1b, 0x24, 0xf3, 0x1f, 0x91, 0xf7, 0x81, 0x73, 0x91, 0xcf, 0x50, + 0x34, 0xd4, 0x9e, 0x4a, 0xa3, 0x8f, 0x16, 0xf4, 0x05, 0x11, 0x4f, 0x0e, 0x57, 0xae, 0x26, 0x00, + 0x9f, 0x55, 0x13, 0xa7, 0x69, 0x25, 0xcb, 0xf1, 0x53, 0x01, 0x39, 0xf3, 0xf4, 0xfc, 0x6c, 0xb6, + 0x2c, 0xc8, 0xcf, 0x05, 0xe4, 0x0c, 0xd3, 0x6a, 0x18, 0x57, 0x30, 0x58, 0x40, 0x7e, 0xf6, 0xde, + 0x02, 0x9f, 0xd0, 0xc0, 0x63, 0xf1, 0x8d, 0xc4, 0xcd, 0x38, 0x88, 0x06, 0xba, 0x42, 0x69, 0xb4, + 0x0b, 0x83, 0xc7, 0xa7, 0xb4, 0x66, 0xc7, 0x9f, 0x1e, 0x13, 0xba, 0x6a, 0x07, 0x51, 0xae, 0x97, + 0xa1, 0xe8, 0x1e, 0x38, 0xdc, 0x32, 0xa6, 0x62, 0x8f, 0xd6, 0xb7, 0x6f, 0x8d, 0xf9, 0x0c, 0xc7, + 0x92, 0xe7, 0x84, 0xf8, 0x47, 0xe2, 0x14, 0xec, 0x37, 0x1a, 0x81, 0xcd, 0xaa, 0x63, 0xd2, 0x3c, + 0x77, 0xd8, 0xca, 0x65, 0x71, 0x99, 0x6a, 0xe3, 0xc9, 0x61, 0xf4, 0xc9, 0x82, 0xde, 0x65, 0x46, + 0xb5, 0xb1, 0x63, 0xb4, 0xd1, 0x36, 0xda, 0xe8, 0xb4, 0x6d, 0x64, 0xad, 0xee, 0xed, 0xe4, 0xf5, + 0xbc, 0x6c, 0xfc, 0xdd, 0x48, 0xbc, 0xac, 0x41, 0x28, 0x84, 0x2e, 0x8f, 0x4f, 0x8b, 0xf3, 0x5c, + 0x3a, 0xdc, 0xcd, 0x24, 0x5e, 0x5a, 0xef, 0xab, 0xd6, 0x7f, 0xb6, 0xe0, 0xe6, 0x1f, 0x2d, 0x2b, + 0x2b, 0x68, 0xb5, 0x56, 0xf0, 0xca, 0xbb, 0x90, 0x0b, 0xed, 0xaa, 0x0b, 0xad, 0x74, 0xe7, 0x1b, + 0xbb, 0xeb, 0xb6, 0xbb, 0x8b, 0x76, 0xa0, 0xbf, 0x4f, 0xd8, 0x8c, 0x33, 0xfc, 0x76, 0x21, 0x47, + 0xfd, 0xef, 0x9b, 0x72, 0x5f, 0x27, 0x42, 0x95, 0x3a, 0xad, 0xd6, 0xc5, 0x7b, 0x0e, 0xfd, 0xd9, + 0xff, 0x9e, 0xa9, 0x88, 0xdb, 0x2d, 0xf1, 0x81, 0x4e, 0x9c, 0x6e, 0xff, 0xec, 0xc0, 0x0d, 0x39, + 0xaf, 0x27, 0xe2, 0x05, 0x42, 0x53, 0x80, 0xe5, 0xfd, 0x47, 0xa1, 0x58, 0x4d, 0xdd, 0x3b, 0x14, + 0x9a, 0x39, 0x8a, 0x62, 0x58, 0xbb, 0xb8, 0x83, 0xe8, 0xb6, 0x9a, 0xd8, 0x7a, 0x3d, 0x42, 0x23, + 0x45, 0xd1, 0x3e, 0xac, 0x2b, 0x17, 0x0d, 0xdd, 0x11, 0x99, 0xda, 0x2b, 0x1c, 0xfe, 0x85, 0xa4, + 0xe8, 0x00, 0x7a, 0x97, 0xe7, 0xf0, 0xbb, 0x24, 0xcd, 0x90, 0x43, 0x23, 0xd5, 0x28, 0xcd, 0x0c, + 0x4a, 0x33, 0xb3, 0x92, 0xc6, 0xf8, 0x49, 0xef, 0xcb, 0xf7, 0x2d, 0xeb, 0x2b, 0xfb, 0xbe, 0xb1, + 0xef, 0xc3, 0x8f, 0xad, 0x6b, 0x2f, 0xbd, 0xe6, 0xe9, 0x7f, 0xf0, 0x2b, 0x00, 0x00, 0xff, 0xff, + 0x3b, 0xea, 0x11, 0x02, 0x13, 0x06, 0x00, 0x00, } diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto index c42063cac..e4a93e53a 100644 --- a/core/protos/broker_manager.proto +++ b/core/protos/broker_manager.proto @@ -16,7 +16,7 @@ message UpsertABPBrokerReq { string NetAddress = 3; bytes DevAddr = 4; bytes NwkSKey = 5; - bool DevMode = 6; + uint32 Flags = 6; } message UpsertABPBrokerRes {} diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index 75a6e11ec..bbb92ee4e 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -17,7 +17,7 @@ message UpsertABPHandlerReq { bytes DevAddr = 3; bytes NwkSKey = 4; bytes AppSKey = 5; - bool DevMode = 6; + uint32 Flags = 6; } message UpsertABPHandlerRes {} @@ -38,7 +38,7 @@ message HandlerABPDevice { bytes AppSKey = 4; uint32 FCntUp = 5; uint32 FCntDown = 6; - bool DevMode = 7; + uint32 Flags = 7; } message HandlerOTAADevice { diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index e17456f07..5c9c85041 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -76,10 +76,10 @@ registered on the Handler.`, table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevAddr", "FCntUp", "FCntDown", "DeveloperMode") + table.AddRow("DevAddr", "FCntUp", "FCntDown", "RelaxFcntCheck") for _, device := range devices.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) - table.AddRow(devAddr, device.FCntUp, device.FCntDown, device.DevMode) + table.AddRow(devAddr, device.FCntUp, device.FCntDown, (device.Flags&core.RelaxFcntCheck) != 0) } fmt.Println() @@ -198,7 +198,7 @@ var devicesInfoCmd = &cobra.Command{ fmt.Println() fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) fmt.Println() - fmt.Printf(" Developer Mode: %t\n", device.DevMode) + fmt.Printf(" Relax Counter Checks: %t\n", device.Flags&core.RelaxFcntCheck) return } } @@ -278,7 +278,7 @@ the Handler`, // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ - Use: "personalized [DevAddr] [NwkSKey] [AppSKey] [DeveloperMode]", + Use: "personalized [DevAddr] [NwkSKey] [AppSKey] [RelaxFcntCheck]", Short: "Create or update ABP registrations on the Handler", Long: `ttnctl devices register personalized creates or updates an ABP registration on the Handler`, @@ -296,7 +296,7 @@ registration on the Handler`, } var nwkSKey, appSKey []byte - devMode := false + var flags uint32 = 0 if len(args) >= 3 { nwkSKey, err = util.Parse128(args[1]) if err != nil { @@ -307,7 +307,7 @@ registration on the Handler`, ctx.Fatalf("Invalid AppSKey: %s", err) } if (len(args) >= 4) && strings.EqualFold(args[3], "true") { - devMode = true + flags = core.RelaxFcntCheck } } else { ctx.Info("Generating random NwkSKey and AppSKey...") @@ -330,7 +330,7 @@ registration on the Handler`, DevAddr: devAddr, AppSKey: appSKey, NwkSKey: nwkSKey, - DevMode: devMode, + Flags: flags, }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") @@ -339,7 +339,7 @@ registration on the Handler`, "DevAddr": devAddr, "NwkSKey": nwkSKey, "AppSKey": appSKey, - "DevMode": devMode, + "Flags": flags, }).Info("Registered personalized device") }, } From b2d91f119169debf11ce392f90823ec893c27de0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 09:29:17 +0200 Subject: [PATCH 1348/2266] [ttnctl] Warn people when using default keys --- ttnctl/cmd/device.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 427359cb7..caafd141e 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -20,6 +20,8 @@ import ( const emptyCell = "-" +var defaultKey = []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} + func getHandlerManager() core.AuthHandlerClient { cli, err := handler.NewClient(viper.GetString("ttn-handler")) if err != nil { @@ -302,6 +304,9 @@ registration on the Handler`, if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } + if reflect.DeepEqual(nwkSKey, defaultKey) || reflect.DeepEqual(appSKey, defaultKey) { + ctx.Warn("You are using default keys, any attacker can read your data or attack your device's connectivity.") + } } else { ctx.Info("Generating random NwkSKey and AppSKey...") nwkSKey = random.Bytes(16) From 51f6ffba64f47700672a1cb84a366ce3368f6e5d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 10:49:11 +0200 Subject: [PATCH 1349/2266] [ttnctl] Update downlink command - Make TTL optional (default: 1h) - Payload is HEX (unless --plain flag) --- ttnctl/cmd/downlink.go | 30 +++++++++++++++++++++--------- ttnctl/util/parse.go | 9 +++++---- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 633b7e2ce..aef91b078 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -15,13 +15,16 @@ var downlinkCmd = &cobra.Command{ Short: "Send downlink messages to the network", Long: `ttnctl downlink sends a downlink message to the network -The DevEUI should be an 8-byte long hex-encoded string (16 chars), whereas the -TTL is expected to define a Time To Live in a handy format, for instance: "1h" -for one hour.`, +The DevEUI should be an 8-byte long hex-encoded string (16 chars), the Payload +is a hex-encoded string and the TTL defines the time-to-live of this downlink, +formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 3 { + if len(args) < 2 { ctx.Fatal("Insufficient arguments") } + if len(args) < 3 { + args = append(args, "1h") + } appEUI := util.GetAppEUI(ctx) @@ -30,8 +33,18 @@ for one hour.`, ctx.Fatalf("Invalid DevEUI: %s", err) } + var payload []byte + if plain, _ := cmd.Flags().GetBool("plain"); plain { + payload = []byte(args[1]) + } else { + payload, err = util.ParseHEX(args[1], len(args[1])) + if err != nil { + ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") + } + } + dataDown := core.DataDownAppReq{ - Payload: []byte(args[1]), + Payload: payload, TTL: args[2], } @@ -40,18 +53,17 @@ for one hour.`, } client := util.ConnectMQTTClient(ctx) + defer client.Disconnect() token := client.PublishDownlink(appEUI, devEUI, dataDown) - if token.Wait(); token.Error() != nil { ctx.WithError(token.Error()).Fatal("Could not publish downlink") } - - client.Disconnect() - + ctx.Info("Scheduled downlink") }, } func init() { RootCmd.AddCommand(downlinkCmd) + downlinkCmd.Flags().Bool("plain", false, "send payload as plain-text") } diff --git a/ttnctl/util/parse.go b/ttnctl/util/parse.go index b54e8151d..336b2e7cd 100644 --- a/ttnctl/util/parse.go +++ b/ttnctl/util/parse.go @@ -9,7 +9,8 @@ import ( "regexp" ) -func parseHEX(input string, length int) ([]byte, error) { +// ParseHEX parses a hexidecimal string to a byte slice +func ParseHEX(input string, length int) ([]byte, error) { pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length)) if err != nil { return nil, fmt.Errorf("Invalid pattern") @@ -30,15 +31,15 @@ func parseHEX(input string, length int) ([]byte, error) { // Parse32 parses a 32-bit hex-encoded string func Parse32(input string) ([]byte, error) { - return parseHEX(input, 8) + return ParseHEX(input, 8) } // Parse64 parses a 64-bit hex-encoded string func Parse64(input string) ([]byte, error) { - return parseHEX(input, 16) + return ParseHEX(input, 16) } // Parse128 parses a 128-bit hex-encoded string func Parse128(input string) ([]byte, error) { - return parseHEX(input, 32) + return ParseHEX(input, 32) } From f9d7850eb714e898760fe49c752d45bbfa06a306 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 10:52:13 +0200 Subject: [PATCH 1350/2266] [ttnctl] Add Quick Start Guide to RootCmd --- ttnctl/cmd/root.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 57daf2f47..5dafe6225 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -22,7 +22,30 @@ var ctx log.Interface var RootCmd = &cobra.Command{ Use: "ttnctl", Short: "Control The Things Network from the command line", - Long: `ttnctl controls The Things Network from the command line.`, + Long: `ttnctl controls The Things Network from the command line. + +Quick start guide: + 1. Create an account: + $ ttnctl user create [Your Email] + 2. Sign in: + $ ttnctl user login [Your Email] + 3. Create an application: + $ ttnctl applications create [Application Name] + 4. List your applications: + $ ttnctl applications + 5. Choose an application to use from now on: + $ ttnctl applications use [EUI] + 6. Create a new device: + $ ttnctl devices register [DevEUI] [AppKey] + 7. List your devices: + $ ttnctl devices + 8. Get info about a specific device: + $ ttnctl devices info [DevEUI] + 9. Subscribe to incoming messages from this device: + $ ttnctl subscribe [DevEUI] + 10. Schedule downlink to this device: + $ ttnctl downlink [DevEUI] [Hex-encoded Payload] + `, PersistentPreRun: func(cmd *cobra.Command, args []string) { var logLevel = log.InfoLevel if viper.GetBool("debug") { From 2f5a59b9c0e75f6b583a7b64952949c8f776bf1f Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Fri, 29 Apr 2016 12:33:24 +0200 Subject: [PATCH 1351/2266] Updated based on feedback from pull-request --- core/components/broker/broker_test.go | 78 +++++++++++++++++++++++++++ core/protos/handler.proto | 2 +- ttnctl/cmd/device.go | 32 ++++++++--- 3 files changed, 103 insertions(+), 9 deletions(-) diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go index 69425651a..3031dd9c4 100644 --- a/core/components/broker/broker_test.go +++ b/core/components/broker/broker_test.go @@ -524,6 +524,84 @@ func TestHandleData(t *testing.T) { // -------------------- + { + Desc(t, "Valid uplink | One entry, FCnt invalid, RelaxFcntCheck set") + + // Build + hl := mocks.NewHandlerClient() + nc := NewMockNetworkController() + as := NewMockAppStorage() + nc.Failures["wholeCounter"] = errors.New(errors.Structural, "Mock Error") + + dl := NewMockDialer() + dl.OutDial.Client = hl + dl.OutDial.Closer = NewMockCloser() + + nc.OutRead.Entries = []devEntry{ + { + Dialer: dl, + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, + FCntUp: 40, + Flags: core.RelaxFcntCheck, + }, + } + br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) + req := &core.DataBrokerReq{ + Payload: &core.LoRaWANData{ + MHDR: &core.LoRaWANMHDR{ + MType: uint32(lorawan.UnconfirmedDataUp), + Major: uint32(lorawan.LoRaWANR1), + }, + MACPayload: &core.LoRaWANMACPayload{ + FHDR: &core.LoRaWANFHDR{ + DevAddr: []byte{1, 2, 3, 4}, + FCnt: 0, + FCtrl: new(core.LoRaWANFCtrl), + }, + FPort: 1, + FRMPayload: []byte{14, 14, 42, 42}, + }, + MIC: []byte{0, 0, 0, 0}, // Temporary, computed below + }, + Metadata: new(core.Metadata), + } + payload, err := core.NewLoRaWANData(req.Payload, true) + FatalUnless(t, err) + err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) + FatalUnless(t, err) + req.Payload.MIC = payload.MIC[:] + + // Expect + var wantErr *string + var wantDataUp = &core.DataUpHandlerReq{ + Payload: req.Payload.MACPayload.FRMPayload, + AppEUI: nc.OutRead.Entries[0].AppEUI, + DevEUI: nc.OutRead.Entries[0].DevEUI, + FCnt: 0, + FPort: 1, + MType: req.Payload.MHDR.MType, + Metadata: req.Metadata, + FCntUpReset: true, + } + var wantRes = new(core.DataBrokerRes) + var wantFCnt = nc.OutWholeCounter.FCnt + var wantDialer = true + + // Operate + res, err := br.HandleData(context.Background(), req) + + // Checks + CheckErrors(t, wantErr, err) + Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") + Check(t, wantRes, res, "Broker Data Responses") + Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") + Check(t, wantDialer, dl.InDial.Called, "Dialer calls") + } + + // -------------------- + { Desc(t, "Valid uplink | One entry | One valid downlink") diff --git a/core/protos/handler.proto b/core/protos/handler.proto index 4ee9b12e7..d75539f64 100644 --- a/core/protos/handler.proto +++ b/core/protos/handler.proto @@ -10,7 +10,7 @@ message DataUpHandlerReq { uint32 FCnt = 10; uint32 FPort = 11; uint32 MType = 12; - bool FCntUpReset = 13; + bool FCntUpReset = 13; bytes Payload = 20; Metadata Metadata = 30; } diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 5c9c85041..8525307d2 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -76,10 +76,17 @@ registered on the Handler.`, table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevAddr", "FCntUp", "FCntDown", "RelaxFcntCheck") + table.AddRow("DevAddr", "FCntUp", "FCntDown", "Flags") for _, device := range devices.ABP { devAddr := fmt.Sprintf("%X", device.DevAddr) - table.AddRow(devAddr, device.FCntUp, device.FCntDown, (device.Flags&core.RelaxFcntCheck) != 0) + var flags string + if (device.Flags & core.RelaxFcntCheck) != 0 { + flags = "relax-fcnt" + } + if flags == "" { + flags = "-" + } + table.AddRow(devAddr, device.FCntUp, device.FCntDown, strings.TrimLeft(flags, ",")) } fmt.Println() @@ -198,7 +205,14 @@ var devicesInfoCmd = &cobra.Command{ fmt.Println() fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) fmt.Println() - fmt.Printf(" Relax Counter Checks: %t\n", device.Flags&core.RelaxFcntCheck) + var flags string + if (device.Flags & core.RelaxFcntCheck) != 0 { + flags = "relax-fcnt" + } + if flags == "" { + flags = "-" + } + fmt.Printf(" Flags: %s\n", strings.TrimLeft(flags, ",")) return } } @@ -278,7 +292,7 @@ the Handler`, // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ - Use: "personalized [DevAddr] [NwkSKey] [AppSKey] [RelaxFcntCheck]", + Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", Short: "Create or update ABP registrations on the Handler", Long: `ttnctl devices register personalized creates or updates an ABP registration on the Handler`, @@ -296,7 +310,6 @@ registration on the Handler`, } var nwkSKey, appSKey []byte - var flags uint32 = 0 if len(args) >= 3 { nwkSKey, err = util.Parse128(args[1]) if err != nil { @@ -306,15 +319,17 @@ registration on the Handler`, if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } - if (len(args) >= 4) && strings.EqualFold(args[3], "true") { - flags = core.RelaxFcntCheck - } } else { ctx.Info("Generating random NwkSKey and AppSKey...") nwkSKey = random.Bytes(16) appSKey = random.Bytes(16) } + var flags uint32 + if value, _ := cmd.Flags().GetBool("relax-fcnt"); value { + flags |= core.RelaxFcntCheck + } + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) if err != nil { ctx.WithError(err).Fatal("Failed to load authentication") @@ -398,4 +413,5 @@ func init() { devicesCmd.AddCommand(devicesInfoCmd) devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) devicesRegisterCmd.AddCommand(devicesRegisterDefaultCmd) + devicesRegisterPersonalizedCmd.Flags().Bool("relax-fcnt", false, "Allow frame counter to reset (insecure)") } From fbba1e1309b2bdbad6b52bac763e42aef15f6356 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 14:49:51 +0200 Subject: [PATCH 1352/2266] [vendor] Update gRPC --- core/application.pb.go | 36 ++++++++++----- core/broker.pb.go | 36 ++++++++++----- core/broker_manager.pb.go | 52 +++++++++++++++------- core/handler.pb.go | 52 +++++++++++++++------- core/handler_manager.pb.go | 84 ++++++++++++++++++++++++----------- core/router.pb.go | 52 +++++++++++++++------- vendor/google.golang.org/grpc | 2 +- 7 files changed, 223 insertions(+), 91 deletions(-) diff --git a/core/application.pb.go b/core/application.pb.go index 3a459dfc9..611096269 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -154,6 +154,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for App service type AppClient interface { @@ -198,28 +202,40 @@ func RegisterAppServer(s *grpc.Server, srv AppServer) { s.RegisterService(&_App_serviceDesc, srv) } -func _App_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _App_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DataAppReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(AppServer).HandleData(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(AppServer).HandleData(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.App/HandleData", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AppServer).HandleData(ctx, req.(*DataAppReq)) + } + return interceptor(ctx, in, info, handler) } -func _App_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _App_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(JoinAppReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(AppServer).HandleJoin(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(AppServer).HandleJoin(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.App/HandleJoin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AppServer).HandleJoin(ctx, req.(*JoinAppReq)) + } + return interceptor(ctx, in, info, handler) } var _App_serviceDesc = grpc.ServiceDesc{ diff --git a/core/broker.pb.go b/core/broker.pb.go index 7af5a9a60..5452b4e01 100644 --- a/core/broker.pb.go +++ b/core/broker.pb.go @@ -124,6 +124,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Broker service type BrokerClient interface { @@ -168,28 +172,40 @@ func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { s.RegisterService(&_Broker_serviceDesc, srv) } -func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DataBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerServer).HandleData(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerServer).HandleData(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Broker/HandleData", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerServer).HandleData(ctx, req.(*DataBrokerReq)) + } + return interceptor(ctx, in, info, handler) } -func _Broker_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Broker_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(JoinBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerServer).HandleJoin(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerServer).HandleJoin(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Broker/HandleJoin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerServer).HandleJoin(ctx, req.(*JoinBrokerReq)) + } + return interceptor(ctx, in, info, handler) } var _Broker_serviceDesc = grpc.ServiceDesc{ diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go index a4908b3a7..733dcee21 100644 --- a/core/broker_manager.pb.go +++ b/core/broker_manager.pb.go @@ -111,6 +111,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for BrokerManager service type BrokerManagerClient interface { @@ -166,40 +170,58 @@ func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { s.RegisterService(&_BrokerManager_serviceDesc, srv) } -func _BrokerManager_ValidateOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_ValidateOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ValidateOTAABrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).ValidateOTAA(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).ValidateOTAA(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.BrokerManager/ValidateOTAA", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).ValidateOTAA(ctx, req.(*ValidateOTAABrokerReq)) + } + return interceptor(ctx, in, info, handler) } -func _BrokerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpsertABPBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).UpsertABP(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).UpsertABP(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.BrokerManager/UpsertABP", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).UpsertABP(ctx, req.(*UpsertABPBrokerReq)) + } + return interceptor(ctx, in, info, handler) } -func _BrokerManager_ValidateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_ValidateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ValidateTokenBrokerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).ValidateToken(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).ValidateToken(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.BrokerManager/ValidateToken", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).ValidateToken(ctx, req.(*ValidateTokenBrokerReq)) + } + return interceptor(ctx, in, info, handler) } var _BrokerManager_serviceDesc = grpc.ServiceDesc{ diff --git a/core/handler.pb.go b/core/handler.pb.go index 52569d9b4..278ca6928 100644 --- a/core/handler.pb.go +++ b/core/handler.pb.go @@ -145,6 +145,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Handler service type HandlerClient interface { @@ -200,40 +204,58 @@ func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { s.RegisterService(&_Handler_serviceDesc, srv) } -func _Handler_HandleDataUp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Handler_HandleDataUp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DataUpHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerServer).HandleDataUp(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerServer).HandleDataUp(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Handler/HandleDataUp", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).HandleDataUp(ctx, req.(*DataUpHandlerReq)) + } + return interceptor(ctx, in, info, handler) } -func _Handler_HandleDataDown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Handler_HandleDataDown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DataDownHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerServer).HandleDataDown(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerServer).HandleDataDown(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Handler/HandleDataDown", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).HandleDataDown(ctx, req.(*DataDownHandlerReq)) + } + return interceptor(ctx, in, info, handler) } -func _Handler_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Handler_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(JoinHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerServer).HandleJoin(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerServer).HandleJoin(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Handler/HandleJoin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).HandleJoin(ctx, req.(*JoinHandlerReq)) + } + return interceptor(ctx, in, info, handler) } var _Handler_serviceDesc = grpc.ServiceDesc{ diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index edb1cdc7f..d6bcb3540 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -200,6 +200,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for HandlerManager service type HandlerManagerClient interface { @@ -277,64 +281,94 @@ func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { s.RegisterService(&_HandlerManager_serviceDesc, srv) } -func _HandlerManager_UpsertOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _HandlerManager_UpsertOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpsertOTAAHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).UpsertOTAA(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerManagerServer).UpsertOTAA(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/UpsertOTAA", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).UpsertOTAA(ctx, req.(*UpsertOTAAHandlerReq)) + } + return interceptor(ctx, in, info, handler) } -func _HandlerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _HandlerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UpsertABPHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).UpsertABP(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerManagerServer).UpsertABP(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/UpsertABP", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).UpsertABP(ctx, req.(*UpsertABPHandlerReq)) + } + return interceptor(ctx, in, info, handler) } -func _HandlerManager_ListDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _HandlerManager_ListDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListDevicesHandlerReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).ListDevices(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerManagerServer).ListDevices(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/ListDevices", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).ListDevices(ctx, req.(*ListDevicesHandlerReq)) + } + return interceptor(ctx, in, info, handler) } -func _HandlerManager_GetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _HandlerManager_GetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetDefaultDeviceReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).GetDefaultDevice(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerManagerServer).GetDefaultDevice(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/GetDefaultDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).GetDefaultDevice(ctx, req.(*GetDefaultDeviceReq)) + } + return interceptor(ctx, in, info, handler) } -func _HandlerManager_SetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _HandlerManager_SetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SetDefaultDeviceReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).SetDefaultDevice(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerManagerServer).SetDefaultDevice(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/SetDefaultDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).SetDefaultDevice(ctx, req.(*SetDefaultDeviceReq)) + } + return interceptor(ctx, in, info, handler) } var _HandlerManager_serviceDesc = grpc.ServiceDesc{ diff --git a/core/router.pb.go b/core/router.pb.go index 35a92da6f..7fe956394 100644 --- a/core/router.pb.go +++ b/core/router.pb.go @@ -152,6 +152,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Router service type RouterClient interface { @@ -207,40 +211,58 @@ func RegisterRouterServer(s *grpc.Server, srv RouterServer) { s.RegisterService(&_Router_serviceDesc, srv) } -func _Router_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Router_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DataRouterReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterServer).HandleData(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterServer).HandleData(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Router/HandleData", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).HandleData(ctx, req.(*DataRouterReq)) + } + return interceptor(ctx, in, info, handler) } -func _Router_HandleStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Router_HandleStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatsReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterServer).HandleStats(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterServer).HandleStats(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Router/HandleStats", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).HandleStats(ctx, req.(*StatsReq)) + } + return interceptor(ctx, in, info, handler) } -func _Router_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Router_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(JoinRouterReq) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterServer).HandleJoin(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterServer).HandleJoin(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.Router/HandleJoin", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).HandleJoin(ctx, req.(*JoinRouterReq)) + } + return interceptor(ctx, in, info, handler) } var _Router_serviceDesc = grpc.ServiceDesc{ diff --git a/vendor/google.golang.org/grpc b/vendor/google.golang.org/grpc index dec33edc3..b062a3c00 160000 --- a/vendor/google.golang.org/grpc +++ b/vendor/google.golang.org/grpc @@ -1 +1 @@ -Subproject commit dec33edc378cf4971a2741cfd86ed70a644d6ba3 +Subproject commit b062a3c003c22bfef58fa99d689e6a892b408f9d From c847488d045074df2995ca5cade9b7df2b810cea Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Fri, 29 Apr 2016 15:25:49 +0200 Subject: [PATCH 1353/2266] Add DevEUI to MQTT data as seperate item --- core/adapters/mqtt/adapter.go | 3 +++ core/definitions.go | 1 + 2 files changed, 4 insertions(+) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index 4d3f70ed9..219518efa 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -4,6 +4,8 @@ package mqtt import ( + "encoding/hex" + "strings" "time" "github.com/TheThingsNetwork/ttn/core" @@ -39,6 +41,7 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . Metadata: core.ProtoMetaToAppMeta(req.Metadata...), FPort: uint8(req.FPort), FCnt: req.FCnt, + DevEUI: strings.ToUpper(hex.EncodeToString(req.DevEUI)), } if a.ctx != nil { diff --git a/core/definitions.go b/core/definitions.go index 4e0328ea5..125922f04 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -8,6 +8,7 @@ type DataUpAppReq struct { Payload []byte `json:"payload"` FPort uint8 `json:"port,omitempty"` FCnt uint32 `json:"counter"` + DevEUI string `json:"deveui"` Metadata []AppMetadata `json:"metadata"` } From 8051281a44271c9f4ed8f0b5c7c63fc054bc5af0 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Fri, 29 Apr 2016 15:52:24 +0200 Subject: [PATCH 1354/2266] Update field name and formatting command as requested --- core/adapters/mqtt/adapter.go | 5 ++--- core/definitions.go | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index 219518efa..894a62593 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -4,8 +4,7 @@ package mqtt import ( - "encoding/hex" - "strings" + "fmt" "time" "github.com/TheThingsNetwork/ttn/core" @@ -41,7 +40,7 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . Metadata: core.ProtoMetaToAppMeta(req.Metadata...), FPort: uint8(req.FPort), FCnt: req.FCnt, - DevEUI: strings.ToUpper(hex.EncodeToString(req.DevEUI)), + DevEUI: fmt.Sprintf("%X", req.DevEUI), } if a.ctx != nil { diff --git a/core/definitions.go b/core/definitions.go index 125922f04..853702b12 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -8,7 +8,7 @@ type DataUpAppReq struct { Payload []byte `json:"payload"` FPort uint8 `json:"port,omitempty"` FCnt uint32 `json:"counter"` - DevEUI string `json:"deveui"` + DevEUI string `json:"dev_eui"` Metadata []AppMetadata `json:"metadata"` } From a8e9a72269e3cc0b803a6c82a98d788b6132f8a9 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Mon, 2 May 2016 07:54:22 +0200 Subject: [PATCH 1355/2266] Check if EUI in 'ttnctl applications use' is owned by user (#152) * Add check for valid application EUI for 'applications use' command Resolves #151 --- ttnctl/cmd/applications.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index f026e58b6..a88ec6938 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -183,6 +183,25 @@ var applicationsUseCmd = &cobra.Command{ ctx.Fatalf("Invalid AppEUI: %s", err) } + // check AppEUI provided is owned by user + apps, err := util.GetApplications(ctx) + if err != nil { + ctx.WithError(err).Fatal("Failed to get applications") + } + + var found bool + newEUI := fmt.Sprintf("%X", appEUI) + for _, app := range apps { + if app.EUI == newEUI { + found = true + break + } + } + + if !found { + ctx.Fatalf("%X not found in registered applications", appEUI) + } + // Determine config file cFile := viper.ConfigFileUsed() if cFile == "" { @@ -209,7 +228,7 @@ var applicationsUseCmd = &cobra.Command{ } // Update app - c["app-eui"] = fmt.Sprintf("%X", appEUI) + c["app-eui"] = newEUI // Write config file d, err := yaml.Marshal(&c) From 0f63dd9a3e6afe55edc7a3a6b39ae9d18da00c87 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 08:02:40 +0200 Subject: [PATCH 1356/2266] [ttnctl] Add warning to relax-fcnt --- ttnctl/cmd/device.go | 1 + ttnctl/cmd/subscribe.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index f4d1bc340..1849f2c9a 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -333,6 +333,7 @@ registration on the Handler`, var flags uint32 if value, _ := cmd.Flags().GetBool("relax-fcnt"); value { flags |= core.RelaxFcntCheck + ctx.Warn("You are disabling frame counter checks. Your device is not protected against replay-attacks.") } auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 94b240822..151486f22 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -33,9 +33,9 @@ application.`, if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } - ctx.Infof("Subscribing uplink messages from device %s", devEUI) + ctx.Infof("Subscribing uplink messages from device %X", devEUI) } else { - ctx.Infof("Subscribing to uplink messages from all devices in application %x", appEUI) + ctx.Infof("Subscribing to uplink messages from all devices in application %X", appEUI) } client := util.ConnectMQTTClient(ctx) From 93f2f5befd4f0a5a715a02f4ce46271f82ad340a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 19:39:21 +0200 Subject: [PATCH 1357/2266] [ttnctl] Windows doesn't like colors :( --- utils/cli/handler/cli.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/utils/cli/handler/cli.go b/utils/cli/handler/cli.go index f16c289ec..8d3326a59 100644 --- a/utils/cli/handler/cli.go +++ b/utils/cli/handler/cli.go @@ -3,6 +3,7 @@ package handler import ( "fmt" "io" + "runtime" "sort" "sync" @@ -79,7 +80,11 @@ func (h *Handler) HandleLog(e *log.Entry) error { h.mu.Lock() defer h.mu.Unlock() - fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-40s", color, level, e.Message) + if runtime.GOOS == "windows" { + fmt.Fprintf(h.Writer, "%6s %-40s", level, e.Message) + } else { + fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-40s", color, level, e.Message) + } for _, f := range fields { var value interface{} @@ -92,7 +97,12 @@ func (h *Handler) HandleLog(e *log.Entry) error { value = f.Value } - fmt.Fprintf(h.Writer, " \033[%dm%s\033[0m=%v", color, f.Name, value) + if runtime.GOOS == "windows" { + fmt.Fprintf(h.Writer, " %s=%v", f.Name, value) + } else { + fmt.Fprintf(h.Writer, " \033[%dm%s\033[0m=%v", color, f.Name, value) + } + } fmt.Fprintln(h.Writer) From 330ef9c4a29ed65b36e51d000c7e515395aa957c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 4 May 2016 10:31:57 +0200 Subject: [PATCH 1358/2266] [ttnctl] Make subscribe print hex data by default --- ttnctl/cmd/downlink.go | 1 + ttnctl/cmd/subscribe.go | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index aef91b078..7b7bde438 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -35,6 +35,7 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, var payload []byte if plain, _ := cmd.Flags().GetBool("plain"); plain { + ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") payload = []byte(args[1]) } else { payload, err = util.ParseHEX(args[1], len(args[1])) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 151486f22..ce45dbad9 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -45,13 +45,15 @@ application.`, // TODO: Find out what Metadata people want to see here - // NOTE: This is a race condition; binary values may be printable - unprintable, _ := regexp.Compile(`[^[:print:]]`) - if unprintable.Match(dataUp.Payload) { - ctx.Infof("%X", dataUp.Payload) + if plain, _ := cmd.Flags().GetBool("plain"); plain { + unprintable, _ := regexp.Compile(`[^[:print:]]`) + if unprintable.Match(dataUp.Payload) { + ctx.WithField("warning", "payload contains unprintable characters").Infof("%X", dataUp.Payload) + } else { + ctx.Infof("%s", dataUp.Payload) + } } else { - ctx.Infof("%s", dataUp.Payload) - ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") + ctx.Infof("%X", dataUp.Payload) } if l := len(dataUp.Payload); l > 20 { @@ -66,6 +68,10 @@ application.`, } ctx.Info("Subscribed. Waiting for messages...") + if plain, _ := cmd.Flags().GetBool("plain"); plain { + ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") + } + c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) @@ -79,4 +85,5 @@ application.`, func init() { RootCmd.AddCommand(subscribeCmd) + subscribeCmd.Flags().Bool("plain", false, "parse payload as plain-text") } From 1d5f59da4130e1609c117dc64e4d7dfe3f1b4589 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 4 May 2016 11:35:09 +0200 Subject: [PATCH 1359/2266] [ttnctl] Add info for LMiC --- ttnctl/cmd/device.go | 76 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 15 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 1849f2c9a..a28f988f2 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -61,7 +61,7 @@ registered on the Handler.`, if defaultDevice != nil { ctx.Warn("Application activates new devices with default AppKey") fmt.Printf("Default AppKey: %X\n", defaultDevice.AppKey) - fmt.Printf(" {%s}\n", cStyle(defaultDevice.AppKey)) + fmt.Printf(" {%s}\n", cStyle(defaultDevice.AppKey, msbf)) } else { ctx.Info("Application does not activate new devices with default AppKey") } @@ -113,6 +113,11 @@ registered on the Handler.`, }, } +const ( + msbf = true + lsbf = false +) + // devicesInfoCmd represents the `devices info` command var devicesInfoCmd = &cobra.Command{ Use: "info [DevAddr|DevEUI]", @@ -142,22 +147,41 @@ var devicesInfoCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not get device list") } + lmic, _ := cmd.Flags().GetBool("lmic") + if devEUI, err := util.Parse64(args[0]); err == nil { for _, device := range res.OTAA { if reflect.DeepEqual(device.DevEUI, devEUI) { fmt.Println("Dynamic device:") fmt.Println() - fmt.Printf(" AppEUI: %X\n", appEUI) - fmt.Printf(" {%s}\n", cStyle(appEUI)) + + // LMiC decided to use LSBF for AppEUI and call it ArtEUI + if lmic { + fmt.Printf(" AppEUI: %X (sometimes called ArtEUI)\n", appEUI) + fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(appEUI, lsbf)) + } else { + fmt.Printf(" AppEUI: %X\n", appEUI) + fmt.Printf(" {%s}\n", cStyle(appEUI, msbf)) + } fmt.Println() fmt.Printf(" DevEUI: %X\n", device.DevEUI) - fmt.Printf(" {%s}\n", cStyle(device.DevEUI)) + // LMiC decided to use LSBF for DevEUI + if lmic { + fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(device.DevEUI, lsbf)) + } else { + fmt.Printf(" {%s}\n", cStyle(device.DevEUI, msbf)) + } fmt.Println() - fmt.Printf(" AppKey: %X\n", device.AppKey) - fmt.Printf(" {%s}\n", cStyle(device.AppKey)) + // LMiC decided to rename AppKey to DevKey + if lmic { + fmt.Printf(" AppKey: %X (sometimes called DevKey)\n", device.AppKey) + } else { + fmt.Printf(" AppKey: %X\n", device.AppKey) + } + fmt.Printf(" {%s}\n", cStyle(device.AppKey, msbf)) if len(device.DevAddr) != 0 { fmt.Println() @@ -165,15 +189,20 @@ var devicesInfoCmd = &cobra.Command{ fmt.Println() fmt.Printf(" DevAddr: %X\n", device.DevAddr) - fmt.Printf(" {%s}\n", cStyle(device.DevAddr)) + fmt.Printf(" {%s}\n", cStyle(device.DevAddr, msbf)) fmt.Println() fmt.Printf(" NwkSKey: %X\n", device.NwkSKey) - fmt.Printf(" {%s}\n", cStyle(device.NwkSKey)) + fmt.Printf(" {%s}\n", cStyle(device.NwkSKey, msbf)) fmt.Println() - fmt.Printf(" AppSKey: %X\n", device.AppSKey) - fmt.Printf(" {%s}\n", cStyle(device.AppSKey)) + // LMiC decided to rename AppSKey to ArtSKey + if lmic { + fmt.Printf(" AppSKey: %X (sometimes called ArtSKey)\n", device.AppSKey) + } else { + fmt.Printf(" AppSKey: %X\n", device.AppSKey) + } + fmt.Printf(" {%s}\n", cStyle(device.AppSKey, msbf)) fmt.Println() fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) @@ -194,15 +223,20 @@ var devicesInfoCmd = &cobra.Command{ fmt.Println() fmt.Printf(" DevAddr: %X\n", device.DevAddr) - fmt.Printf(" {%s}\n", cStyle(device.DevAddr)) + fmt.Printf(" {%s}\n", cStyle(device.DevAddr, msbf)) fmt.Println() fmt.Printf(" NwkSKey: %X\n", device.NwkSKey) - fmt.Printf(" {%s}\n", cStyle(device.NwkSKey)) + fmt.Printf(" {%s}\n", cStyle(device.NwkSKey, msbf)) fmt.Println() - fmt.Printf(" AppSKey: %X\n", device.AppSKey) - fmt.Printf(" {%s}\n", cStyle(device.AppSKey)) + // LMiC decided to rename AppSKey to ArtSKey + if lmic { + fmt.Printf(" AppSKey: %X (sometimes called ArtSKey)\n", device.AppSKey) + } else { + fmt.Printf(" AppSKey: %X\n", device.AppSKey) + } + fmt.Printf(" {%s}\n", cStyle(device.AppSKey, msbf)) fmt.Println() fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) @@ -227,7 +261,10 @@ var devicesInfoCmd = &cobra.Command{ }, } -func cStyle(bytes []byte) (output string) { +func cStyle(bytes []byte, msbf bool) (output string) { + if !msbf { + bytes = reverse(bytes) + } for i, b := range bytes { if i != 0 { output += ", " @@ -237,6 +274,14 @@ func cStyle(bytes []byte) (output string) { return } +// reverse is used to convert between MSB-first and LSB-first +func reverse(in []byte) (out []byte) { + for i := len(in) - 1; i >= 0; i-- { + out = append(out, in[i]) + } + return +} + // devicesRegisterCmd represents the `device register` command var devicesRegisterCmd = &cobra.Command{ Use: "register [DevEUI] [AppKey]", @@ -417,6 +462,7 @@ func init() { RootCmd.AddCommand(devicesCmd) devicesCmd.AddCommand(devicesRegisterCmd) devicesCmd.AddCommand(devicesInfoCmd) + devicesInfoCmd.Flags().Bool("lmic", false, "Print info for LMiC") devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) devicesRegisterCmd.AddCommand(devicesRegisterDefaultCmd) devicesRegisterPersonalizedCmd.Flags().Bool("relax-fcnt", false, "Allow frame counter to reset (insecure)") From 1a84e95a230e44f87ec8f4cfdb71c04e94aa6e51 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 6 May 2016 20:22:56 +0200 Subject: [PATCH 1360/2266] [ttnctl] Fix uplink encrypt/decrypt --- ttnctl/cmd/uplink.go | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 19b7ed9df..2ac6c55c8 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -6,6 +6,7 @@ package cmd import ( "encoding/base64" "net" + "regexp" "strconv" "strings" "time" @@ -73,7 +74,16 @@ var uplinkCmd = &cobra.Command{ FCnt: uint32(fcnt), } macPayload.FPort = pointer.Uint8(1) - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} + if plain, _ := cmd.Flags().GetBool("plain"); plain { + ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} + } else { + payload, err := util.ParseHEX(args[4], len(args[4])) + if err != nil { + ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") + } + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: payload}} + } phyPayload := &lorawan.PHYPayload{} phyPayload.MHDR = lorawan.MHDR{ MType: mtype, @@ -156,10 +166,26 @@ var uplinkCmd = &cobra.Command{ } ctx.Infof("Frame counter: %d", macPayload.FHDR.FCnt) if len(macPayload.FRMPayload) > 0 { - if err := phyPayload.DecryptFRMPayload(appSKey); err != nil { + decrypted, err := lorawan.EncryptFRMPayload( + appSKey, + false, + devAddr, + macPayload.FHDR.FCnt, + macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, + ) + if err != nil { ctx.Fatalf("Unable to decrypt MACPayload: %s", err) } - ctx.Infof("Decrypted Payload: %s", string(macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes)) + if plain, _ := cmd.Flags().GetBool("plain"); plain { + unprintable, _ := regexp.Compile(`[^[:print:]]`) + if unprintable.Match(decrypted) { + ctx.WithField("warning", "payload contains unprintable characters").Infof("Decrypted Payload: %X", decrypted) + } else { + ctx.Infof("%s", decrypted) + } + } else { + ctx.Infof("Decrypted Payload: %X", decrypted) + } } else { ctx.Infof("The frame payload was empty.") } @@ -232,4 +258,5 @@ var uplinkCmd = &cobra.Command{ func init() { RootCmd.AddCommand(uplinkCmd) + uplinkCmd.Flags().Bool("plain", false, "send payload as plain-text") } From f6f0f8c17ce711ec8d59318ad596a27907d1016f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 22 Apr 2016 11:32:00 +0200 Subject: [PATCH 1361/2266] Squashed commit of the following: commit 0c537b1a73baef26bf5f4eaf3030e86181cb0fc0 Author: Romeo Van Snick Date: Mon Apr 18 16:04:22 2016 +0200 Println -> Sprintf (duh) commit 146cfbf7702b2df4f73b98e651d265c0041a2ea7 Author: Romeo Van Snick Date: Mon Apr 18 16:00:29 2016 +0200 Implement Stringer for all types commit 52b46060521b1113ee3d7c4e7e8874beb73cda69 Author: Romeo Van Snick Date: Mon Apr 18 15:49:46 2016 +0200 use (Un)MarshalText instead of (Un)MarshalJSON commit 6b37d47be2cc010d72bbc2de42d5e49630f5fa41 Author: Romeo Van Snick Date: Mon Apr 18 15:17:22 2016 +0200 update comments to reflect change of names commit 5f8a47320b6caed325f8db6c2c29a67792c0459f Author: Romeo Van Snick Date: Mon Apr 18 15:16:23 2016 +0200 remove util/parse and replace all code instances with core.parse commit 877b9345092faf05dada830938857056484dbba4 Author: Romeo Van Snick Date: Mon Apr 18 14:05:58 2016 +0200 add core types (App|Dev)EUI, (App|Nwk)Skey, AppKey and some utilities --- core/types.go | 190 ++++++++++++++++++++++++++++++++++++ ttnctl/cmd/applications.go | 7 +- ttnctl/cmd/device.go | 16 +-- ttnctl/cmd/downlink.go | 2 +- ttnctl/cmd/join.go | 5 +- ttnctl/cmd/subscribe.go | 2 +- ttnctl/cmd/uplink.go | 8 +- ttnctl/util/applications.go | 3 +- ttnctl/util/parse.go | 15 --- 9 files changed, 213 insertions(+), 35 deletions(-) create mode 100644 core/types.go diff --git a/core/types.go b/core/types.go new file mode 100644 index 000000000..bb9b256bf --- /dev/null +++ b/core/types.go @@ -0,0 +1,190 @@ +package core + +import ( + "encoding/hex" + "fmt" + "regexp" +) + +func parseHEX(input string, length int) ([]byte, error) { + pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length)) + if err != nil { + return nil, fmt.Errorf("Invalid pattern") + } + + valid := pattern.MatchString(input) + if !valid { + return nil, fmt.Errorf("Invalid input: %s", input) + } + + devAddr, err := hex.DecodeString(input) + if err != nil { + return nil, fmt.Errorf("Could not decode input: %s", input) + } + + return devAddr, nil +} + +// ParseAddr parses a 32-bit hex-encoded string +func ParseAddr(input string) ([]byte, error) { + return parseHEX(input, 8) +} + +// ParseEUI parses a 64-bit hex-encoded string +func ParseEUI(input string) ([]byte, error) { + return parseHEX(input, 16) +} + +// ParseKey parses a 128-bit hex-encoded string +func ParseKey(input string) ([]byte, error) { + return parseHEX(input, 32) +} + +type AppEUI []byte +type DevEUI []byte +type DevAddr []byte + +type AppKey []byte +type AppSKey []byte +type NwkSKey []byte + +type DeviceType int + +const ( + ABP DeviceType = iota + OTAA +) + +func (appEUI AppEUI) String() string { + return fmt.Sprintf("%X", []byte(appEUI)) +} + +func (devEUI DevEUI) String() string { + return fmt.Sprintf("%X", []byte(devEUI)) +} + +func (devAddr DevAddr) String() string { + return fmt.Sprintf("%X", []byte(devAddr)) +} + +func (appKey AppKey) String() string { + return fmt.Sprintf("%X", []byte(appKey)) +} + +func (appSKey AppSKey) String() string { + return fmt.Sprintf("%X", []byte(appSKey)) +} + +func (nwkSKey NwkSKey) String() string { + return fmt.Sprintf("%X", []byte(nwkSKey)) +} + +func (deviceType *DeviceType) UnmarshalText(b []byte) error { + str := string(b) + switch { + case str == "ABP": + *deviceType = ABP + case str == "OTAA": + *deviceType = OTAA + default: + return fmt.Errorf("Unknown device type %s", str) + } + return nil +} + +func (deviceType DeviceType) MarshalText() ([]byte, error) { + switch deviceType { + case OTAA: + return []byte("OTAA"), nil + + case ABP: + return []byte("ABP"), nil + + default: + return nil, fmt.Errorf("Invalid device type value") + } +} + +func (appEUI AppEUI) MarshalText() ([]byte, error) { + return []byte(appEUI.String()), nil +} + +func (appEUI *AppEUI) UnmarshalText(data []byte) error { + parsed, err := ParseEUI(string(data)) + if err != nil { + return err + } + + *appEUI = AppEUI(parsed) + return nil +} + +func (devEUI DevEUI) MarshalText() ([]byte, error) { + return []byte(devEUI.String()), nil +} + +func (devEUI *DevEUI) UnmarshalText(data []byte) error { + parsed, err := ParseEUI(string(data)) + if err != nil { + return err + } + + *devEUI = DevEUI(parsed) + return nil +} + +func (devAddr DevAddr) MarshalText() ([]byte, error) { + return []byte(devAddr.String()), nil +} + +func (devAddr *DevAddr) UnmarshalText(data []byte) error { + parsed, err := ParseAddr(string(data)) + if err != nil { + return err + } + + *devAddr = DevAddr(parsed) + return nil +} + +func (appKey *AppKey) MarshalText() ([]byte, error) { + return []byte(appKey.String()), nil +} + +func (appKey *AppKey) UnmarshalText(data []byte) error { + parsed, err := ParseKey(string(data)) + if err != nil { + return err + } + + *appKey = AppKey(parsed) + return nil +} + +func (appSKey *AppSKey) MarshalText() ([]byte, error) { + return []byte(appSKey.String()), nil +} + +func (appSKey *AppSKey) UnmarshalText(data []byte) error { + parsed, err := ParseKey(string(data)) + if err != nil { + return err + } + + *appSKey = AppSKey(parsed) + return nil +} + +func (nwkSKey *NwkSKey) MarshalText() ([]byte, error) { + return []byte(nwkSKey.String()), nil +} + +func (nwkSKey *NwkSKey) UnmarshalText(data []byte) error { + parsed, err := ParseKey(string(data)) + if err != nil { + return err + } + + *nwkSKey = NwkSKey(parsed) + return nil +} diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index a88ec6938..64ec6c2d9 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -13,6 +13,7 @@ import ( "gopkg.in/yaml.v2" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/gosuri/uitable" @@ -96,7 +97,7 @@ var applicationsDeleteCmd = &cobra.Command{ return } - appEUI, err := util.Parse64(args[0]) + appEUI, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } @@ -138,7 +139,7 @@ var applicationsAuthorizeCmd = &cobra.Command{ return } - appEUI, err := util.Parse64(args[0]) + appEUI, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } @@ -178,7 +179,7 @@ var applicationsUseCmd = &cobra.Command{ return } - appEUI, err := util.Parse64(args[0]) + appEUI, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index a28f988f2..8ee3ce708 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -149,7 +149,7 @@ var devicesInfoCmd = &cobra.Command{ lmic, _ := cmd.Flags().GetBool("lmic") - if devEUI, err := util.Parse64(args[0]); err == nil { + if devEUI, err := core.ParseEUI(args[0]); err == nil { for _, device := range res.OTAA { if reflect.DeepEqual(device.DevEUI, devEUI) { fmt.Println("Dynamic device:") @@ -216,7 +216,7 @@ var devicesInfoCmd = &cobra.Command{ } } - if devAddr, err := util.Parse32(args[0]); err == nil { + if devAddr, err := core.ParseAddr(args[0]); err == nil { for _, device := range res.ABP { if reflect.DeepEqual(device.DevAddr, devAddr) { fmt.Println("Personalized device:") @@ -296,14 +296,14 @@ the Handler`, appEUI := util.GetAppEUI(ctx) - devEUI, err := util.Parse64(args[0]) + devEUI, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } var appKey []byte if len(args) >= 2 { - appKey, err = util.Parse128(args[1]) + appKey, err = core.ParseKey(args[1]) if err != nil { ctx.Fatalf("Invalid AppKey: %s", err) } @@ -351,18 +351,18 @@ registration on the Handler`, appEUI := util.GetAppEUI(ctx) - devAddr, err := util.Parse32(args[0]) + devAddr, err := core.ParseAddr(args[0]) if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } var nwkSKey, appSKey []byte if len(args) >= 3 { - nwkSKey, err = util.Parse128(args[1]) + nwkSKey, err = core.ParseKey(args[1]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) } - appSKey, err = util.Parse128(args[2]) + appSKey, err = core.ParseKey(args[2]) if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } @@ -428,7 +428,7 @@ register [DevEUI] [AppKey]`, var appKey []byte var err error if len(args) >= 2 { - appKey, err = util.Parse128(args[0]) + appKey, err = core.ParseKey(args[0]) if err != nil { ctx.Fatalf("Invalid AppKey: %s", err) } diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 7b7bde438..2f6461f77 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -28,7 +28,7 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, appEUI := util.GetAppEUI(ctx) - devEUI, err := util.Parse64(args[0]) + devEUI, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 584b5b2ab..291f3d14d 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/ttnctl/util" @@ -30,14 +31,14 @@ var joinCmd = &cobra.Command{ } // Parse parameters - devEUIRaw, err := util.Parse64(args[0]) + devEUIRaw, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } var devEUI lorawan.EUI64 copy(devEUI[:], devEUIRaw) - appKeyRaw, err := util.Parse128(args[1]) + appKeyRaw, err := core.ParseKey(args[1]) if err != nil { ctx.Fatalf("Invalid appKey: %s", err) } diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index ce45dbad9..ff6b363af 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -29,7 +29,7 @@ application.`, var devEUI []byte if len(args) > 0 { - devEUI, err := util.Parse64(args[0]) + devEUI, err := core.ParseEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 2ac6c55c8..4d8b65049 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -11,8 +11,8 @@ import ( "strings" "time" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" @@ -41,21 +41,21 @@ var uplinkCmd = &cobra.Command{ mtype = lorawan.UnconfirmedDataUp } - devAddrRaw, err := util.Parse32(args[1]) + devAddrRaw, err := core.ParseAddr(args[1]) if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } var devAddr lorawan.DevAddr copy(devAddr[:], devAddrRaw) - nwkSKeyRaw, err := util.Parse128(args[2]) + nwkSKeyRaw, err := core.ParseKey(args[2]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) } var nwkSKey lorawan.AES128Key copy(nwkSKey[:], nwkSKeyRaw[:]) - appSKeyRaw, err := util.Parse128(args[3]) + appSKeyRaw, err := core.ParseKey(args[3]) if err != nil { ctx.Fatalf("Invalid appSKey: %s", err) } diff --git a/ttnctl/util/applications.go b/ttnctl/util/applications.go index 248e9a417..8fcf514ed 100644 --- a/ttnctl/util/applications.go +++ b/ttnctl/util/applications.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" + "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" "github.com/spf13/viper" ) @@ -22,7 +23,7 @@ func GetAppEUI(ctx log.Interface) []byte { ctx.Fatal("AppEUI not set. You probably want to run 'ttnctl applications use [appEUI]' to do this.") } - appEUI, err := Parse64(viper.GetString("app-eui")) + appEUI, err := core.ParseEUI(viper.GetString("app-eui")) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } diff --git a/ttnctl/util/parse.go b/ttnctl/util/parse.go index 336b2e7cd..32389cd37 100644 --- a/ttnctl/util/parse.go +++ b/ttnctl/util/parse.go @@ -28,18 +28,3 @@ func ParseHEX(input string, length int) ([]byte, error) { return devAddr, nil } - -// Parse32 parses a 32-bit hex-encoded string -func Parse32(input string) ([]byte, error) { - return ParseHEX(input, 8) -} - -// Parse64 parses a 64-bit hex-encoded string -func Parse64(input string) ([]byte, error) { - return ParseHEX(input, 16) -} - -// Parse128 parses a 128-bit hex-encoded string -func Parse128(input string) ([]byte, error) { - return ParseHEX(input, 32) -} From 47d50b1c0bec6b2553fa42f7af8b195b768abdc1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 22 Apr 2016 15:17:27 +0200 Subject: [PATCH 1362/2266] [refactor] Core Types Refactor e6e0f1de --- core/types.go | 190 ----------------------- core/types/dev_addr.go | 70 +++++++++ core/types/dev_addr_test.go | 60 ++++++++ core/types/device_type.go | 115 ++++++++++++++ core/types/device_type_test.go | 82 ++++++++++ core/types/eui.go | 267 +++++++++++++++++++++++++++++++++ core/types/eui_test.go | 219 +++++++++++++++++++++++++++ core/types/keys.go | 266 ++++++++++++++++++++++++++++++++ core/types/keys_test.go | 219 +++++++++++++++++++++++++++ ttnctl/cmd/applications.go | 12 +- ttnctl/cmd/device.go | 60 ++++---- ttnctl/cmd/downlink.go | 6 +- ttnctl/cmd/join.go | 22 +-- ttnctl/cmd/subscribe.go | 7 +- ttnctl/cmd/uplink.go | 25 ++- ttnctl/util/applications.go | 6 +- 16 files changed, 1364 insertions(+), 262 deletions(-) delete mode 100644 core/types.go create mode 100644 core/types/dev_addr.go create mode 100644 core/types/dev_addr_test.go create mode 100644 core/types/device_type.go create mode 100644 core/types/device_type_test.go create mode 100644 core/types/eui.go create mode 100644 core/types/eui_test.go create mode 100644 core/types/keys.go create mode 100644 core/types/keys_test.go diff --git a/core/types.go b/core/types.go deleted file mode 100644 index bb9b256bf..000000000 --- a/core/types.go +++ /dev/null @@ -1,190 +0,0 @@ -package core - -import ( - "encoding/hex" - "fmt" - "regexp" -) - -func parseHEX(input string, length int) ([]byte, error) { - pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length)) - if err != nil { - return nil, fmt.Errorf("Invalid pattern") - } - - valid := pattern.MatchString(input) - if !valid { - return nil, fmt.Errorf("Invalid input: %s", input) - } - - devAddr, err := hex.DecodeString(input) - if err != nil { - return nil, fmt.Errorf("Could not decode input: %s", input) - } - - return devAddr, nil -} - -// ParseAddr parses a 32-bit hex-encoded string -func ParseAddr(input string) ([]byte, error) { - return parseHEX(input, 8) -} - -// ParseEUI parses a 64-bit hex-encoded string -func ParseEUI(input string) ([]byte, error) { - return parseHEX(input, 16) -} - -// ParseKey parses a 128-bit hex-encoded string -func ParseKey(input string) ([]byte, error) { - return parseHEX(input, 32) -} - -type AppEUI []byte -type DevEUI []byte -type DevAddr []byte - -type AppKey []byte -type AppSKey []byte -type NwkSKey []byte - -type DeviceType int - -const ( - ABP DeviceType = iota - OTAA -) - -func (appEUI AppEUI) String() string { - return fmt.Sprintf("%X", []byte(appEUI)) -} - -func (devEUI DevEUI) String() string { - return fmt.Sprintf("%X", []byte(devEUI)) -} - -func (devAddr DevAddr) String() string { - return fmt.Sprintf("%X", []byte(devAddr)) -} - -func (appKey AppKey) String() string { - return fmt.Sprintf("%X", []byte(appKey)) -} - -func (appSKey AppSKey) String() string { - return fmt.Sprintf("%X", []byte(appSKey)) -} - -func (nwkSKey NwkSKey) String() string { - return fmt.Sprintf("%X", []byte(nwkSKey)) -} - -func (deviceType *DeviceType) UnmarshalText(b []byte) error { - str := string(b) - switch { - case str == "ABP": - *deviceType = ABP - case str == "OTAA": - *deviceType = OTAA - default: - return fmt.Errorf("Unknown device type %s", str) - } - return nil -} - -func (deviceType DeviceType) MarshalText() ([]byte, error) { - switch deviceType { - case OTAA: - return []byte("OTAA"), nil - - case ABP: - return []byte("ABP"), nil - - default: - return nil, fmt.Errorf("Invalid device type value") - } -} - -func (appEUI AppEUI) MarshalText() ([]byte, error) { - return []byte(appEUI.String()), nil -} - -func (appEUI *AppEUI) UnmarshalText(data []byte) error { - parsed, err := ParseEUI(string(data)) - if err != nil { - return err - } - - *appEUI = AppEUI(parsed) - return nil -} - -func (devEUI DevEUI) MarshalText() ([]byte, error) { - return []byte(devEUI.String()), nil -} - -func (devEUI *DevEUI) UnmarshalText(data []byte) error { - parsed, err := ParseEUI(string(data)) - if err != nil { - return err - } - - *devEUI = DevEUI(parsed) - return nil -} - -func (devAddr DevAddr) MarshalText() ([]byte, error) { - return []byte(devAddr.String()), nil -} - -func (devAddr *DevAddr) UnmarshalText(data []byte) error { - parsed, err := ParseAddr(string(data)) - if err != nil { - return err - } - - *devAddr = DevAddr(parsed) - return nil -} - -func (appKey *AppKey) MarshalText() ([]byte, error) { - return []byte(appKey.String()), nil -} - -func (appKey *AppKey) UnmarshalText(data []byte) error { - parsed, err := ParseKey(string(data)) - if err != nil { - return err - } - - *appKey = AppKey(parsed) - return nil -} - -func (appSKey *AppSKey) MarshalText() ([]byte, error) { - return []byte(appSKey.String()), nil -} - -func (appSKey *AppSKey) UnmarshalText(data []byte) error { - parsed, err := ParseKey(string(data)) - if err != nil { - return err - } - - *appSKey = AppSKey(parsed) - return nil -} - -func (nwkSKey *NwkSKey) MarshalText() ([]byte, error) { - return []byte(nwkSKey.String()), nil -} - -func (nwkSKey *NwkSKey) UnmarshalText(data []byte) error { - parsed, err := ParseKey(string(data)) - if err != nil { - return err - } - - *nwkSKey = NwkSKey(parsed) - return nil -} diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go new file mode 100644 index 000000000..de098715a --- /dev/null +++ b/core/types/dev_addr.go @@ -0,0 +1,70 @@ +package types + +import ( + "encoding/hex" + "errors" + "strings" +) + +// DevAddr is a non-unique address for LoRaWAN devices. +type DevAddr [4]byte + +// ParseDevAddr parses a 32-bit hex-encoded string to a DevAddr +func ParseDevAddr(input string) (addr DevAddr, err error) { + bytes, err := parseHEX(input, 4) + if err != nil { + return + } + copy(addr[:], bytes) + return +} + +// Bytes returns the DevAddr as a byte slice +func (addr DevAddr) Bytes() []byte { + return addr[:] +} + +// String implements the Stringer interface. +func (addr DevAddr) String() string { + return strings.ToUpper(hex.EncodeToString(addr.Bytes())) +} + +// MarshalText implements the TextMarshaler interface. +func (addr DevAddr) MarshalText() ([]byte, error) { + return []byte(addr.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (addr *DevAddr) UnmarshalText(data []byte) error { + parsed, err := ParseDevAddr(string(data)) + if err != nil { + return err + } + *addr = DevAddr(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (addr DevAddr) MarshalBinary() ([]byte, error) { + return addr.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (addr *DevAddr) UnmarshalBinary(data []byte) error { + if len(data) != 4 { + return errors.New("ttn/core: Invalid length for DevAddr") + } + copy(addr[:], data) + return nil +} + +// Marshal implements the Marshaler interface. +func (addr DevAddr) Marshal() ([]byte, error) { + return addr.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (addr *DevAddr) Unmarshal(data []byte) error { + *addr = [4]byte{} // Reset the receiver + return addr.UnmarshalBinary(data) +} diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go new file mode 100644 index 000000000..ec6dcf689 --- /dev/null +++ b/core/types/dev_addr_test.go @@ -0,0 +1,60 @@ +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDevAddr(t *testing.T) { + a := New(t) + + // Setup + addr := DevAddr{1, 2, 254, 255} + str := "0102FEFF" + bin := []byte{0x01, 0x02, 0xfe, 0xff} + + // Bytes + a.So(addr.Bytes(), ShouldResemble, bin) + + // String + a.So(addr.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := addr.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := addr.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := addr.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseDevAddr(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, addr) + + // UnmarshalText + utOut := &DevAddr{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, addr) + + // UnmarshalBinary + ubOut := &DevAddr{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, addr) + + // Unmarshal + uOut := &DevAddr{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, addr) +} diff --git a/core/types/device_type.go b/core/types/device_type.go new file mode 100644 index 000000000..15b525de2 --- /dev/null +++ b/core/types/device_type.go @@ -0,0 +1,115 @@ +package types + +import ( + "encoding/hex" + "errors" + "fmt" + "regexp" +) + +// DeviceType is the type of a LoRaWAN device. +type DeviceType byte + +const ( + // ABP is a LoRaWAN device that is activated by personalization. + ABP DeviceType = iota + // OTAA is an over-the-air activated LoRaWAN device. + OTAA +) + +// ParseDeviceType parses a string to a DeviceType. +func ParseDeviceType(input string) (devType DeviceType, err error) { + switch input { + case "ABP": + devType = ABP + case "OTAA": + devType = OTAA + default: + err = errors.New("ttn/core: Invalid DeviceType") + } + return +} + +// String implements the Stringer interface. +func (devType DeviceType) String() string { + switch devType { + case ABP: + return "ABP" + case OTAA: + return "OTAA" + } + return "" +} + +// MarshalText implements the TextMarshaler interface. +func (devType DeviceType) MarshalText() ([]byte, error) { + str := devType.String() + if str == "" { + return nil, errors.New("ttn/core: Invalid DeviceType") + } + return []byte(devType.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (devType *DeviceType) UnmarshalText(data []byte) error { + parsed, err := ParseDeviceType(string(data)) + if err != nil { + return err + } + *devType = DeviceType(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (devType DeviceType) MarshalBinary() ([]byte, error) { + switch devType { + case ABP, OTAA: + return []byte{byte(devType)}, nil + default: + return nil, errors.New("ttn/core: Invalid DeviceType") + } +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (devType *DeviceType) UnmarshalBinary(data []byte) error { + if len(data) != 1 { + return errors.New("ttn/core: Invalid length for DeviceType") + } + switch data[0] { + case byte(ABP), byte(OTAA): + *devType = DeviceType(data[0]) + default: + return errors.New("ttn/core: Invalid DeviceType") + } + return nil +} + +// Marshal implements the Marshaler interface. +func (devType DeviceType) Marshal() ([]byte, error) { + return devType.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (devType *DeviceType) Unmarshal(data []byte) error { + return devType.UnmarshalBinary(data) +} + +// parseHEX parses a string "input" to a byteslice with length "length". +func parseHEX(input string, length int) ([]byte, error) { + pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) + if err != nil { + return nil, fmt.Errorf("Invalid pattern") + } + + valid := pattern.MatchString(input) + if !valid { + return nil, fmt.Errorf("Invalid input: %s", input) + } + + slice, err := hex.DecodeString(input) + if err != nil { + return nil, fmt.Errorf("Could not decode input: %s", input) + } + + return slice, nil +} diff --git a/core/types/device_type_test.go b/core/types/device_type_test.go new file mode 100644 index 000000000..206b98e0b --- /dev/null +++ b/core/types/device_type_test.go @@ -0,0 +1,82 @@ +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDeviceType(t *testing.T) { + a := New(t) + + // Setup + abp := ABP + otaa := OTAA + abpStr := "ABP" + otaaStr := "OTAA" + abpBin := []byte{0x00} + otaaBin := []byte{0x01} + + // String + a.So(abp.String(), ShouldEqual, abpStr) + a.So(otaa.String(), ShouldEqual, otaaStr) + + // MarshalText + mtOut, err := abp.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(abpStr)) + mtOut, err = otaa.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(otaaStr)) + + // MarshalBinary + mbOut, err := abp.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, abpBin) + mbOut, err = otaa.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, otaaBin) + + // Marshal + mOut, err := abp.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, abpBin) + mOut, err = otaa.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, otaaBin) + + // Parse + pOut, err := ParseDeviceType(abpStr) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, abp) + pOut, err = ParseDeviceType(otaaStr) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, otaa) + + // UnmarshalText + utOut := ABP + err = utOut.UnmarshalText([]byte(otaaStr)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldEqual, otaa) + err = utOut.UnmarshalText([]byte(abpStr)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldEqual, abp) + + // UnmarshalBinary + ubOut := ABP + err = ubOut.UnmarshalBinary(otaaBin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldEqual, otaa) + err = ubOut.UnmarshalBinary(abpBin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldEqual, abp) + + // Unmarshal + uOut := ABP + err = uOut.Unmarshal(otaaBin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldEqual, otaa) + err = uOut.Unmarshal(abpBin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldEqual, abp) +} diff --git a/core/types/eui.go b/core/types/eui.go new file mode 100644 index 000000000..2abea5f14 --- /dev/null +++ b/core/types/eui.go @@ -0,0 +1,267 @@ +package types + +import ( + "encoding/hex" + "errors" + "strings" +) + +// EUI64 is used for AppEUIs and DevEUIs. +type EUI64 [8]byte + +// AppEUI is a unique identifier for applications. +type AppEUI EUI64 + +// DevEUI is a unique identifier for devices. +type DevEUI EUI64 + +// GatewayEUI is a unique identifier for devices. +type GatewayEUI EUI64 + +// ParseEUI64 parses a 64-bit hex-encoded string to an EUI64. +func ParseEUI64(input string) (eui EUI64, err error) { + bytes, err := parseHEX(input, 8) + if err != nil { + return + } + copy(eui[:], bytes) + return +} + +// Bytes returns the EUI64 as a byte slice +func (eui EUI64) Bytes() []byte { + return eui[:] +} + +func (eui EUI64) String() string { + return strings.ToUpper(hex.EncodeToString(eui.Bytes())) +} + +// MarshalText implements the TextMarshaler interface. +func (eui EUI64) MarshalText() ([]byte, error) { + return []byte(eui.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *EUI64) UnmarshalText(data []byte) error { + parsed, err := ParseEUI64(string(data)) + if err != nil { + return err + } + *eui = EUI64(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui EUI64) MarshalBinary() ([]byte, error) { + return eui.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *EUI64) UnmarshalBinary(data []byte) error { + if len(data) != 8 { + return errors.New("ttn/core: Invalid length for EUI64") + } + copy(eui[:], data) + return nil +} + +// Marshal implements the Marshaler interface. +func (eui EUI64) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *EUI64) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} + +// ParseAppEUI parses a 64-bit hex-encoded string to an AppEUI +func ParseAppEUI(input string) (eui AppEUI, err error) { + eui64, err := ParseEUI64(input) + if err != nil { + return + } + eui = AppEUI(eui64) + return +} + +// Bytes returns the AppEUI as a byte slice +func (eui AppEUI) Bytes() []byte { + return EUI64(eui).Bytes() +} + +// String implements the Stringer interface. +func (eui AppEUI) String() string { + return EUI64(eui).String() +} + +// MarshalText implements the TextMarshaler interface. +func (eui AppEUI) MarshalText() ([]byte, error) { + return EUI64(eui).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *AppEUI) UnmarshalText(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *eui = AppEUI(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui AppEUI) MarshalBinary() ([]byte, error) { + return EUI64(eui).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *AppEUI) UnmarshalBinary(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *eui = AppEUI(e) + return nil +} + +// Marshal implements the Marshaler interface. +func (eui AppEUI) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *AppEUI) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} + +// ParseDevEUI parses a 64-bit hex-encoded string to an DevEUI +func ParseDevEUI(input string) (eui DevEUI, err error) { + eui64, err := ParseEUI64(input) + if err != nil { + return + } + eui = DevEUI(eui64) + return +} + +// Bytes returns the DevEUI as a byte slice +func (eui DevEUI) Bytes() []byte { + return EUI64(eui).Bytes() +} + +// String implements the Stringer interface. +func (eui DevEUI) String() string { + return EUI64(eui).String() +} + +// MarshalText implements the TextMarshaler interface. +func (eui DevEUI) MarshalText() ([]byte, error) { + return EUI64(eui).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *DevEUI) UnmarshalText(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *eui = DevEUI(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui DevEUI) MarshalBinary() ([]byte, error) { + return EUI64(eui).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *DevEUI) UnmarshalBinary(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *eui = DevEUI(e) + return nil +} + +// Marshal implements the Marshaler interface. +func (eui DevEUI) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *DevEUI) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} + +// ParseGatewayEUI parses a 64-bit hex-encoded string to an GatewayEUI +func ParseGatewayEUI(input string) (eui GatewayEUI, err error) { + eui64, err := ParseEUI64(input) + if err != nil { + return + } + eui = GatewayEUI(eui64) + return +} + +// Bytes returns the GatewayEUI as a byte slice +func (eui GatewayEUI) Bytes() []byte { + return EUI64(eui).Bytes() +} + +// String implements the Stringer interface. +func (eui GatewayEUI) String() string { + return EUI64(eui).String() +} + +// MarshalText implements the TextMarshaler interface. +func (eui GatewayEUI) MarshalText() ([]byte, error) { + return EUI64(eui).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (eui *GatewayEUI) UnmarshalText(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *eui = GatewayEUI(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (eui GatewayEUI) MarshalBinary() ([]byte, error) { + return EUI64(eui).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (eui *GatewayEUI) UnmarshalBinary(data []byte) error { + e := EUI64(*eui) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *eui = GatewayEUI(e) + return nil +} + +// Marshal implements the Marshaler interface. +func (eui GatewayEUI) Marshal() ([]byte, error) { + return eui.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (eui *GatewayEUI) Unmarshal(data []byte) error { + *eui = [8]byte{} // Reset the receiver + return eui.UnmarshalBinary(data) +} diff --git a/core/types/eui_test.go b/core/types/eui_test.go new file mode 100644 index 000000000..ee04b0895 --- /dev/null +++ b/core/types/eui_test.go @@ -0,0 +1,219 @@ +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestEUI64(t *testing.T) { + a := New(t) + + // Setup + eui := EUI64{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseEUI64(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldResemble, eui) + + // UnmarshalText + utOut := &EUI64{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &eui) + + // UnmarshalBinary + ubOut := &EUI64{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &eui) + + // Unmarshal + uOut := &EUI64{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &eui) +} + +func TestAppEUI(t *testing.T) { + a := New(t) + + // Setup + eui := AppEUI{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseAppEUI(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, eui) + + // UnmarshalText + utOut := &AppEUI{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, eui) + + // UnmarshalBinary + ubOut := &AppEUI{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, eui) + + // Unmarshal + uOut := &AppEUI{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, eui) +} + +func TestDevEUI(t *testing.T) { + a := New(t) + + // Setup + eui := DevEUI{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseDevEUI(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, eui) + + // UnmarshalText + utOut := &DevEUI{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, eui) + + // UnmarshalBinary + ubOut := &DevEUI{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, eui) + + // Unmarshal + uOut := &DevEUI{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, eui) +} + +func TestGatewayEUI(t *testing.T) { + a := New(t) + + // Setup + eui := GatewayEUI{1, 2, 3, 4, 252, 253, 254, 255} + str := "01020304FCFDFEFF" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} + + // Bytes + a.So(eui.Bytes(), ShouldResemble, bin) + + // String + a.So(eui.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := eui.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := eui.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := eui.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseGatewayEUI(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, eui) + + // UnmarshalText + utOut := &GatewayEUI{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, eui) + + // UnmarshalBinary + ubOut := &GatewayEUI{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, eui) + + // Unmarshal + uOut := &GatewayEUI{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, eui) +} diff --git a/core/types/keys.go b/core/types/keys.go new file mode 100644 index 000000000..e07d214a9 --- /dev/null +++ b/core/types/keys.go @@ -0,0 +1,266 @@ +package types + +import ( + "encoding/hex" + "errors" + "strings" +) + +// AES128Key is an 128 bit AES key. +type AES128Key [16]byte + +// AppKey (Application Key) is used for LoRaWAN OTAA. +type AppKey AES128Key + +// NwkSKey (Network Session Key) is used for LoRaWAN MIC calculation. +type NwkSKey AES128Key + +// AppSKey (Application Session Key) is used for LoRaWAN payload encryption. +type AppSKey AES128Key + +// ParseAES128Key parses a 128-bit hex-encoded string to an AES128Key +func ParseAES128Key(input string) (key AES128Key, err error) { + bytes, err := parseHEX(input, 16) + if err != nil { + return + } + copy(key[:], bytes) + return +} + +// Bytes returns the AES128Key as a byte slice +func (key AES128Key) Bytes() []byte { + return key[:] +} + +// String implements the Stringer interface. +func (key AES128Key) String() string { + return strings.ToUpper(hex.EncodeToString(key.Bytes())) +} + +// MarshalText implements the TextMarshaler interface. +func (key AES128Key) MarshalText() ([]byte, error) { + return []byte(key.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *AES128Key) UnmarshalText(data []byte) error { + parsed, err := ParseAES128Key(string(data)) + if err != nil { + return err + } + *key = AES128Key(parsed) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key AES128Key) MarshalBinary() ([]byte, error) { + return key.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *AES128Key) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return errors.New("ttn/core: Invalid length for AES128Key") + } + copy(key[:], data) + return nil +} + +// Marshal implements the Marshaler interface. +func (key AES128Key) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *AES128Key) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +// ParseAppKey parses a 64-bit hex-encoded string to an AppKey +func ParseAppKey(input string) (key AppKey, err error) { + aes128key, err := ParseAES128Key(input) + if err != nil { + return + } + key = AppKey(aes128key) + return +} + +// Bytes returns the AppKey as a byte slice +func (key AppKey) Bytes() []byte { + return AES128Key(key).Bytes() +} + +func (key AppKey) String() string { + return AES128Key(key).String() +} + +// MarshalText implements the TextMarshaler interface. +func (key AppKey) MarshalText() ([]byte, error) { + return AES128Key(key).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *AppKey) UnmarshalText(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *key = AppKey(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key AppKey) MarshalBinary() ([]byte, error) { + return AES128Key(key).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *AppKey) UnmarshalBinary(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *key = AppKey(e) + return nil +} + +// Marshal implements the Marshaler interface. +func (key AppKey) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *AppKey) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +// ParseAppSKey parses a 64-bit hex-encoded string to an AppSKey +func ParseAppSKey(input string) (key AppSKey, err error) { + aes128key, err := ParseAES128Key(input) + if err != nil { + return + } + key = AppSKey(aes128key) + return +} + +// Bytes returns the AppSKey as a byte slice +func (key AppSKey) Bytes() []byte { + return AES128Key(key).Bytes() +} + +func (key AppSKey) String() string { + return AES128Key(key).String() +} + +// MarshalText implements the TextMarshaler interface. +func (key AppSKey) MarshalText() ([]byte, error) { + return AES128Key(key).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *AppSKey) UnmarshalText(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *key = AppSKey(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key AppSKey) MarshalBinary() ([]byte, error) { + return AES128Key(key).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *AppSKey) UnmarshalBinary(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *key = AppSKey(e) + return nil +} + +// Marshal implements the Marshaler interface. +func (key AppSKey) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *AppSKey) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} + +// ParseNwkSKey parses a 64-bit hex-encoded string to an NwkSKey +func ParseNwkSKey(input string) (key NwkSKey, err error) { + aes128key, err := ParseAES128Key(input) + if err != nil { + return + } + key = NwkSKey(aes128key) + return +} + +// Bytes returns the NwkSKey as a byte slice +func (key NwkSKey) Bytes() []byte { + return AES128Key(key).Bytes() +} + +// String implements the Stringer interface. +func (key NwkSKey) String() string { + return AES128Key(key).String() +} + +// MarshalText implements the TextMarshaler interface. +func (key NwkSKey) MarshalText() ([]byte, error) { + return AES128Key(key).MarshalText() +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (key *NwkSKey) UnmarshalText(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalText(data) + if err != nil { + return err + } + *key = NwkSKey(e) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (key NwkSKey) MarshalBinary() ([]byte, error) { + return AES128Key(key).MarshalBinary() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (key *NwkSKey) UnmarshalBinary(data []byte) error { + e := AES128Key(*key) + err := e.UnmarshalBinary(data) + if err != nil { + return err + } + *key = NwkSKey(e) + return nil +} + +// Marshal implements the Marshaler interface. +func (key NwkSKey) Marshal() ([]byte, error) { + return key.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (key *NwkSKey) Unmarshal(data []byte) error { + *key = [16]byte{} // Reset the receiver + return key.UnmarshalBinary(data) +} diff --git a/core/types/keys_test.go b/core/types/keys_test.go new file mode 100644 index 000000000..dbf58df85 --- /dev/null +++ b/core/types/keys_test.go @@ -0,0 +1,219 @@ +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestAES128Key(t *testing.T) { + a := New(t) + + // Setup + key := AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseAES128Key(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &AES128Key{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &AES128Key{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &AES128Key{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) +} + +func TestAppKey(t *testing.T) { + a := New(t) + + // Setup + key := AppKey{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseAppKey(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &AppKey{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &AppKey{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &AppKey{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) +} + +func TestNwkSKey(t *testing.T) { + a := New(t) + + // Setup + key := NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseNwkSKey(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &NwkSKey{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &NwkSKey{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &NwkSKey{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) +} + +func TestAppSKey(t *testing.T) { + a := New(t) + + // Setup + key := AppSKey{1, 2, 3, 4, 5, 6, 7, 8, 249, 250, 251, 252, 253, 254, 255, 0} + str := "0102030405060708F9FAFBFCFDFEFF00" + bin := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, 0x00} + + // Bytes + a.So(key.Bytes(), ShouldResemble, bin) + + // String + a.So(key.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := key.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := key.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := key.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseAppSKey(str) + a.So(err, ShouldBeNil) + a.So(pOut, ShouldEqual, key) + + // UnmarshalText + utOut := &AppSKey{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldEqual, key) + + // UnmarshalBinary + ubOut := &AppSKey{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldEqual, key) + + // Unmarshal + uOut := &AppSKey{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldEqual, key) +} diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 64ec6c2d9..7a5febe7c 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -13,7 +13,7 @@ import ( "gopkg.in/yaml.v2" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/gosuri/uitable" @@ -97,13 +97,13 @@ var applicationsDeleteCmd = &cobra.Command{ return } - appEUI, err := core.ParseEUI(args[0]) + appEUI, err := types.ParseAppEUI(args[0]) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications/%s", server, fmt.Sprintf("%X", appEUI)) + uri := fmt.Sprintf("%s/applications/%s", server, appEUI) req, err := util.NewRequestWithAuth(server, "DELETE", uri, nil) if err != nil { ctx.WithError(err).Fatal("Failed to create authenticated request") @@ -139,7 +139,7 @@ var applicationsAuthorizeCmd = &cobra.Command{ return } - appEUI, err := core.ParseEUI(args[0]) + appEUI, err := types.ParseAppEUI(args[0]) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } @@ -179,7 +179,7 @@ var applicationsUseCmd = &cobra.Command{ return } - appEUI, err := core.ParseEUI(args[0]) + appEUI, err := types.ParseAppEUI(args[0]) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } @@ -241,7 +241,7 @@ var applicationsUseCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not write configiguration file") } - ctx.Infof("You are now using application %X.", appEUI) + ctx.Infof("You are now using application %s.", appEUI) }, } diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 8ee3ce708..db9590630 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -10,6 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/components/handler" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" @@ -52,7 +53,7 @@ registered on the Handler.`, manager := getHandlerManager() defaultDevice, err := manager.GetDefaultDevice(context.Background(), &core.GetDefaultDeviceReq{ Token: auth.AccessToken, - AppEUI: appEUI, + AppEUI: appEUI.Bytes(), }) if err != nil { // TODO: Check reason @@ -68,7 +69,7 @@ registered on the Handler.`, devices, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ Token: auth.AccessToken, - AppEUI: appEUI, + AppEUI: appEUI.Bytes(), }) if err != nil { ctx.WithError(err).Fatal("Could not get device list") @@ -141,7 +142,7 @@ var devicesInfoCmd = &cobra.Command{ manager := getHandlerManager() res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ Token: auth.AccessToken, - AppEUI: appEUI, + AppEUI: appEUI.Bytes(), }) if err != nil { ctx.WithError(err).Fatal("Could not get device list") @@ -149,7 +150,7 @@ var devicesInfoCmd = &cobra.Command{ lmic, _ := cmd.Flags().GetBool("lmic") - if devEUI, err := core.ParseEUI(args[0]); err == nil { + if devEUI, err := types.ParseDevEUI(args[0]); err == nil { for _, device := range res.OTAA { if reflect.DeepEqual(device.DevEUI, devEUI) { fmt.Println("Dynamic device:") @@ -159,10 +160,10 @@ var devicesInfoCmd = &cobra.Command{ // LMiC decided to use LSBF for AppEUI and call it ArtEUI if lmic { fmt.Printf(" AppEUI: %X (sometimes called ArtEUI)\n", appEUI) - fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(appEUI, lsbf)) + fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(appEUI.Bytes(), lsbf)) } else { fmt.Printf(" AppEUI: %X\n", appEUI) - fmt.Printf(" {%s}\n", cStyle(appEUI, msbf)) + fmt.Printf(" {%s}\n", cStyle(appEUI.Bytes(), msbf)) } fmt.Println() @@ -216,7 +217,7 @@ var devicesInfoCmd = &cobra.Command{ } } - if devAddr, err := core.ParseAddr(args[0]); err == nil { + if devAddr, err := types.ParseDevAddr(args[0]); err == nil { for _, device := range res.ABP { if reflect.DeepEqual(device.DevAddr, devAddr) { fmt.Println("Personalized device:") @@ -296,20 +297,20 @@ the Handler`, appEUI := util.GetAppEUI(ctx) - devEUI, err := core.ParseEUI(args[0]) + devEUI, err := types.ParseDevEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } - var appKey []byte + var appKey types.AppKey if len(args) >= 2 { - appKey, err = core.ParseKey(args[1]) + appKey, err = types.ParseAppKey(args[1]) if err != nil { ctx.Fatalf("Invalid AppKey: %s", err) } } else { ctx.Info("Generating random AppKey...") - appKey = random.Bytes(16) + copy(appKey[:], random.Bytes(16)) } auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) @@ -323,9 +324,9 @@ the Handler`, manager := getHandlerManager() res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ Token: auth.AccessToken, - AppEUI: appEUI, - DevEUI: devEUI, - AppKey: appKey, + AppEUI: appEUI.Bytes(), + DevEUI: devEUI.Bytes(), + AppKey: appKey.Bytes(), }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not register device") @@ -351,18 +352,19 @@ registration on the Handler`, appEUI := util.GetAppEUI(ctx) - devAddr, err := core.ParseAddr(args[0]) + devAddr, err := types.ParseDevAddr(args[0]) if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } - var nwkSKey, appSKey []byte + var nwkSKey types.NwkSKey + var appSKey types.AppSKey if len(args) >= 3 { - nwkSKey, err = core.ParseKey(args[1]) + nwkSKey, err = types.ParseNwkSKey(args[1]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) } - appSKey, err = core.ParseKey(args[2]) + appSKey, err = types.ParseAppSKey(args[2]) if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } @@ -371,8 +373,8 @@ registration on the Handler`, } } else { ctx.Info("Generating random NwkSKey and AppSKey...") - nwkSKey = random.Bytes(16) - appSKey = random.Bytes(16) + copy(nwkSKey[:], random.Bytes(16)) + copy(appSKey[:], random.Bytes(16)) } var flags uint32 @@ -392,10 +394,10 @@ registration on the Handler`, manager := getHandlerManager() res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ Token: auth.AccessToken, - AppEUI: appEUI, - DevAddr: devAddr, - AppSKey: appSKey, - NwkSKey: nwkSKey, + AppEUI: appEUI.Bytes(), + DevAddr: devAddr.Bytes(), + AppSKey: appSKey.Bytes(), + NwkSKey: nwkSKey.Bytes(), Flags: flags, }) if err != nil || res == nil { @@ -425,16 +427,16 @@ register [DevEUI] [AppKey]`, appEUI := util.GetAppEUI(ctx) - var appKey []byte + var appKey types.AppKey var err error if len(args) >= 2 { - appKey, err = core.ParseKey(args[0]) + appKey, err = types.ParseAppKey(args[0]) if err != nil { ctx.Fatalf("Invalid AppKey: %s", err) } } else { ctx.Info("Generating random AppKey...") - appKey = random.Bytes(16) + copy(appKey[:], random.Bytes(16)) } auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) @@ -448,8 +450,8 @@ register [DevEUI] [AppKey]`, manager := getHandlerManager() res, err := manager.SetDefaultDevice(context.Background(), &core.SetDefaultDeviceReq{ Token: auth.AccessToken, - AppEUI: appEUI, - AppKey: appKey, + AppEUI: appEUI.Bytes(), + AppKey: appKey.Bytes(), }) if err != nil || res == nil { ctx.WithError(err).Fatal("Could not set default device settings") diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 2f6461f77..9d00c0336 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -5,6 +5,7 @@ package cmd import ( "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) @@ -28,7 +29,7 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, appEUI := util.GetAppEUI(ctx) - devEUI, err := core.ParseEUI(args[0]) + devEUI, err := types.ParseDevEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } @@ -56,7 +57,8 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, client := util.ConnectMQTTClient(ctx) defer client.Disconnect() - token := client.PublishDownlink(appEUI, devEUI, dataDown) + token := client.PublishDownlink(appEUI.Bytes(), devEUI.Bytes(), dataDown) + if token.Wait(); token.Error() != nil { ctx.WithError(token.Error()).Fatal("Could not publish downlink") } diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 291f3d14d..10ebd0fc6 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/otaa" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/semtech" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" @@ -31,23 +31,17 @@ var joinCmd = &cobra.Command{ } // Parse parameters - devEUIRaw, err := core.ParseEUI(args[0]) + devEUI, err := types.ParseDevEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } - var devEUI lorawan.EUI64 - copy(devEUI[:], devEUIRaw) - appKeyRaw, err := core.ParseKey(args[1]) + appKey, err := types.ParseAppKey(args[1]) if err != nil { ctx.Fatalf("Invalid appKey: %s", err) } - var appKey lorawan.AES128Key - copy(appKey[:], appKeyRaw) - appEUIRaw := util.GetAppEUI(ctx) - var appEUI lorawan.EUI64 - copy(appEUI[:], appEUIRaw) + appEUI := util.GetAppEUI(ctx) // Generate a DevNonce var devNonce [2]byte @@ -55,8 +49,8 @@ var joinCmd = &cobra.Command{ // Lorawan Payload joinPayload := lorawan.JoinRequestPayload{ - AppEUI: appEUI, - DevEUI: devEUI, + AppEUI: lorawan.EUI64(appEUI), + DevEUI: lorawan.EUI64(devEUI), DevNonce: devNonce, } phyPayload := &lorawan.PHYPayload{} @@ -65,7 +59,7 @@ var joinCmd = &cobra.Command{ Major: lorawan.LoRaWANR1, } phyPayload.MACPayload = &joinPayload - if err := phyPayload.SetMIC(appKey); err != nil { + if err := phyPayload.SetMIC(lorawan.AES128Key(appKey)); err != nil { ctx.Fatalf("Unable to set MIC: %s", err) } @@ -132,7 +126,7 @@ var joinCmd = &cobra.Command{ ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) } - if err := payload.DecryptJoinAcceptPayload(appKey); err != nil { + if err := payload.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)); err != nil { ctx.Fatalf("Unable to decrypt MACPayload: %s", err) } diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index ff6b363af..cd5ed893e 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -9,6 +9,7 @@ import ( "regexp" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" @@ -27,9 +28,9 @@ application.`, appEUI := util.GetAppEUI(ctx) - var devEUI []byte + var devEUI types.DevEUI if len(args) > 0 { - devEUI, err := core.ParseEUI(args[0]) + devEUI, err := types.ParseDevEUI(args[0]) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } @@ -40,7 +41,7 @@ application.`, client := util.ConnectMQTTClient(ctx) - token := client.SubscribeDeviceUplink(appEUI, devEUI, func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { + token := client.SubscribeDeviceUplink(appEUI.Bytes(), devEUI.Bytes(), func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { ctx := ctx.WithField("DevEUI", devEUI) // TODO: Find out what Metadata people want to see here diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 4d8b65049..1e94b8f90 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -11,8 +11,9 @@ import ( "strings" "time" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/semtech" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" @@ -41,26 +42,20 @@ var uplinkCmd = &cobra.Command{ mtype = lorawan.UnconfirmedDataUp } - devAddrRaw, err := core.ParseAddr(args[1]) + devAddr, err := types.ParseDevAddr(args[1]) if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } - var devAddr lorawan.DevAddr - copy(devAddr[:], devAddrRaw) - nwkSKeyRaw, err := core.ParseKey(args[2]) + nwkSKey, err := types.ParseNwkSKey(args[2]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) } - var nwkSKey lorawan.AES128Key - copy(nwkSKey[:], nwkSKeyRaw[:]) - appSKeyRaw, err := core.ParseKey(args[3]) + appSKey, err := types.ParseAppSKey(args[3]) if err != nil { ctx.Fatalf("Invalid appSKey: %s", err) } - var appSKey lorawan.AES128Key - copy(appSKey[:], appSKeyRaw[:]) fcnt, err := strconv.ParseInt(args[5], 10, 64) if err != nil { @@ -70,7 +65,7 @@ var uplinkCmd = &cobra.Command{ // Lorawan Payload macPayload := &lorawan.MACPayload{} macPayload.FHDR = lorawan.FHDR{ - DevAddr: devAddr, + DevAddr: lorawan.DevAddr(devAddr), FCnt: uint32(fcnt), } macPayload.FPort = pointer.Uint8(1) @@ -90,10 +85,10 @@ var uplinkCmd = &cobra.Command{ Major: lorawan.LoRaWANR1, } phyPayload.MACPayload = macPayload - if err := phyPayload.EncryptFRMPayload(appSKey); err != nil { + if err := phyPayload.EncryptFRMPayload(lorawan.AES128Key(appSKey)); err != nil { ctx.Fatalf("Unable to encrypt frame payload: %s", err) } - if err := phyPayload.SetMIC(nwkSKey); err != nil { + if err := phyPayload.SetMIC(lorawan.AES128Key(nwkSKey)); err != nil { ctx.Fatalf("Unable to set MIC: %s", err) } @@ -167,9 +162,9 @@ var uplinkCmd = &cobra.Command{ ctx.Infof("Frame counter: %d", macPayload.FHDR.FCnt) if len(macPayload.FRMPayload) > 0 { decrypted, err := lorawan.EncryptFRMPayload( - appSKey, + lorawan.AES128Key(appSKey), false, - devAddr, + lorawan.DevAddr(devAddr), macPayload.FHDR.FCnt, macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, ) diff --git a/ttnctl/util/applications.go b/ttnctl/util/applications.go index 8fcf514ed..cecd0c9a2 100644 --- a/ttnctl/util/applications.go +++ b/ttnctl/util/applications.go @@ -5,7 +5,7 @@ import ( "fmt" "net/http" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/spf13/viper" ) @@ -18,12 +18,12 @@ type App struct { Valid bool `json:"valid"` } -func GetAppEUI(ctx log.Interface) []byte { +func GetAppEUI(ctx log.Interface) types.AppEUI { if viper.GetString("app-eui") == "" { ctx.Fatal("AppEUI not set. You probably want to run 'ttnctl applications use [appEUI]' to do this.") } - appEUI, err := core.ParseEUI(viper.GetString("app-eui")) + appEUI, err := types.ParseAppEUI(viper.GetString("app-eui")) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) } From 37e4e40a8f3803ee8f90d96be567f1ad71c18cd3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 22 Apr 2016 15:59:08 +0200 Subject: [PATCH 1363/2266] [core/types] Implement GoStringer --- core/types/dev_addr.go | 5 +++++ core/types/device_type.go | 5 +++++ core/types/eui.go | 20 ++++++++++++++++++++ core/types/keys.go | 20 ++++++++++++++++++++ 4 files changed, 50 insertions(+) diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index de098715a..14bf4adc6 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -29,6 +29,11 @@ func (addr DevAddr) String() string { return strings.ToUpper(hex.EncodeToString(addr.Bytes())) } +// GoString implements the GoStringer interface. +func (addr DevAddr) GoString() string { + return addr.String() +} + // MarshalText implements the TextMarshaler interface. func (addr DevAddr) MarshalText() ([]byte, error) { return []byte(addr.String()), nil diff --git a/core/types/device_type.go b/core/types/device_type.go index 15b525de2..6465797bd 100644 --- a/core/types/device_type.go +++ b/core/types/device_type.go @@ -41,6 +41,11 @@ func (devType DeviceType) String() string { return "" } +// GoString implements the GoStringer interface. +func (devType DeviceType) GoString() string { + return devType.String() +} + // MarshalText implements the TextMarshaler interface. func (devType DeviceType) MarshalText() ([]byte, error) { str := devType.String() diff --git a/core/types/eui.go b/core/types/eui.go index 2abea5f14..a7f495a49 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -37,6 +37,11 @@ func (eui EUI64) String() string { return strings.ToUpper(hex.EncodeToString(eui.Bytes())) } +// GoString implements the GoStringer interface. +func (eui EUI64) GoString() string { + return eui.String() +} + // MarshalText implements the TextMarshaler interface. func (eui EUI64) MarshalText() ([]byte, error) { return []byte(eui.String()), nil @@ -97,6 +102,11 @@ func (eui AppEUI) String() string { return EUI64(eui).String() } +// GoString implements the GoStringer interface. +func (eui AppEUI) GoString() string { + return eui.String() +} + // MarshalText implements the TextMarshaler interface. func (eui AppEUI) MarshalText() ([]byte, error) { return EUI64(eui).MarshalText() @@ -160,6 +170,11 @@ func (eui DevEUI) String() string { return EUI64(eui).String() } +// GoString implements the GoStringer interface. +func (eui DevEUI) GoString() string { + return eui.String() +} + // MarshalText implements the TextMarshaler interface. func (eui DevEUI) MarshalText() ([]byte, error) { return EUI64(eui).MarshalText() @@ -223,6 +238,11 @@ func (eui GatewayEUI) String() string { return EUI64(eui).String() } +// GoString implements the GoStringer interface. +func (eui GatewayEUI) GoString() string { + return eui.String() +} + // MarshalText implements the TextMarshaler interface. func (eui GatewayEUI) MarshalText() ([]byte, error) { return EUI64(eui).MarshalText() diff --git a/core/types/keys.go b/core/types/keys.go index e07d214a9..018cf58a9 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -38,6 +38,11 @@ func (key AES128Key) String() string { return strings.ToUpper(hex.EncodeToString(key.Bytes())) } +// GoString implements the GoStringer interface. +func (key AES128Key) GoString() string { + return key.String() +} + // MarshalText implements the TextMarshaler interface. func (key AES128Key) MarshalText() ([]byte, error) { return []byte(key.String()), nil @@ -97,6 +102,11 @@ func (key AppKey) String() string { return AES128Key(key).String() } +// GoString implements the GoStringer interface. +func (key AppKey) GoString() string { + return key.String() +} + // MarshalText implements the TextMarshaler interface. func (key AppKey) MarshalText() ([]byte, error) { return AES128Key(key).MarshalText() @@ -159,6 +169,11 @@ func (key AppSKey) String() string { return AES128Key(key).String() } +// GoString implements the GoStringer interface. +func (key AppSKey) GoString() string { + return key.String() +} + // MarshalText implements the TextMarshaler interface. func (key AppSKey) MarshalText() ([]byte, error) { return AES128Key(key).MarshalText() @@ -222,6 +237,11 @@ func (key NwkSKey) String() string { return AES128Key(key).String() } +// GoString implements the GoStringer interface. +func (key NwkSKey) GoString() string { + return key.String() +} + // MarshalText implements the TextMarshaler interface. func (key NwkSKey) MarshalText() ([]byte, error) { return AES128Key(key).MarshalText() From eb17f2e440924dae180ff2bb7f57e63dde566b13 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 22 Apr 2016 16:00:17 +0200 Subject: [PATCH 1364/2266] [refactor] Use core types in MQTT --- core/adapters/mqtt/adapter.go | 23 +++++-- core/adapters/mqtt/adapter_test.go | 37 ++++++------ mqtt/client.go | 55 ++++++++--------- mqtt/client_test.go | 97 +++++++++++++++--------------- mqtt/topics.go | 47 ++++++++------- mqtt/topics_test.go | 15 ++--- ttnctl/cmd/downlink.go | 2 +- ttnctl/cmd/subscribe.go | 2 +- 8 files changed, 150 insertions(+), 128 deletions(-) diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index 894a62593..0ea67094b 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -8,6 +8,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/stats" @@ -50,7 +51,13 @@ func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ . }).Debug("Publishing Uplink") } - token := a.client.PublishUplink(req.AppEUI, req.DevEUI, dataUp) + // Type conversions for MQTT (we'll refactor the adapter later) + var appEUI types.AppEUI + appEUI.Unmarshal(req.AppEUI) + var devEUI types.DevEUI + devEUI.Unmarshal(req.DevEUI) + + token := a.client.PublishUplink(appEUI, devEUI, dataUp) if token.WaitTimeout(mqttTimeout) { // token did not timeout: just return if token.Error() != nil { @@ -110,7 +117,13 @@ func (a *defaultAdapter) HandleJoin(_ context.Context, req *core.JoinAppReq, _ . }).Debug("Publishing Activation") } - token := a.client.PublishActivation(req.AppEUI, req.DevEUI, otaa) + // Type conversions for MQTT (we'll refactor the adapter later) + var appEUI types.AppEUI + appEUI.Unmarshal(req.AppEUI) + var devEUI types.DevEUI + devEUI.Unmarshal(req.DevEUI) + + token := a.client.PublishActivation(appEUI, devEUI, otaa) if token.WaitTimeout(mqttTimeout) { // token did not timeout: just return if token.Error() != nil { @@ -152,7 +165,7 @@ func validateJoin(req *core.JoinAppReq) error { } func (a *defaultAdapter) SubscribeDownlink(handler core.HandlerServer) error { - token := a.client.SubscribeDownlink(func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + token := a.client.SubscribeDownlink(func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { if len(req.Payload) == 0 { if a.ctx != nil { a.ctx.Debug("Skipping empty downlink") @@ -171,8 +184,8 @@ func (a *defaultAdapter) SubscribeDownlink(handler core.HandlerServer) error { handler.HandleDataDown(context.Background(), &core.DataDownHandlerReq{ Payload: req.Payload, TTL: req.TTL, - AppEUI: appEUI, - DevEUI: devEUI, + AppEUI: appEUI.Bytes(), + DevEUI: devEUI.Bytes(), }) }) diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index ffced1de1..bd746d984 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/mocks" + "github.com/TheThingsNetwork/ttn/core/types" ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -34,15 +35,15 @@ func TestHandleData(t *testing.T) { adapter := NewAdapter(ctx, client) - eui := []byte{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} req := core.DataAppReq{ Payload: []byte{0x01, 0x02}, Metadata: []*core.Metadata{ &core.Metadata{DataRate: "SF7BW125"}, }, - AppEUI: eui, - DevEUI: eui, + AppEUI: eui.Bytes(), + DevEUI: eui.Bytes(), FPort: 14, FCnt: 200, } @@ -50,9 +51,9 @@ func TestHandleData(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - client.SubscribeDeviceUplink(eui, eui, func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { - a.So(appEUI, ShouldResemble, eui) - a.So(devEUI, ShouldResemble, eui) + client.SubscribeDeviceUplink(types.AppEUI(eui), types.DevEUI(eui), func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, dataUp core.DataUpAppReq) { + a.So(types.EUI64(appEUI), ShouldEqual, eui) + a.So(types.EUI64(devEUI), ShouldEqual, eui) a.So(dataUp.FPort, ShouldEqual, 14) a.So(dataUp.FCnt, ShouldEqual, 200) a.So(dataUp.Payload, ShouldResemble, []byte{0x01, 0x02}) @@ -124,11 +125,11 @@ func TestHandleJoin(t *testing.T) { adapter := NewAdapter(ctx, client) - eui := []byte{0x0a, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x0a, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} req := core.JoinAppReq{ - AppEUI: eui, - DevEUI: eui, + AppEUI: eui.Bytes(), + DevEUI: eui.Bytes(), Metadata: []*core.Metadata{ &core.Metadata{DataRate: "SF7BW125"}, }, @@ -137,9 +138,9 @@ func TestHandleJoin(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - client.SubscribeDeviceActivations(eui, eui, func(client ttnMQTT.Client, appEUI []byte, devEUI []byte, activation core.OTAAAppReq) { - a.So(appEUI, ShouldResemble, eui) - a.So(devEUI, ShouldResemble, eui) + client.SubscribeDeviceActivations(types.AppEUI(eui), types.DevEUI(eui), func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, activation core.OTAAAppReq) { + a.So(types.EUI64(appEUI), ShouldResemble, eui) + a.So(types.EUI64(devEUI), ShouldResemble, eui) a.So(activation.Metadata[0].DataRate, ShouldEqual, "SF7BW125") wg.Done() }).Wait() @@ -201,15 +202,15 @@ func TestSubscribeDownlink(t *testing.T) { err := adapter.SubscribeDownlink(handler) a.So(err, ShouldBeNil) - appEUI := []byte{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() <-time.After(50 * time.Millisecond) expected := &core.DataDownHandlerReq{ - AppEUI: appEUI, - DevEUI: devEUI, + AppEUI: appEUI.Bytes(), + DevEUI: devEUI.Bytes(), Payload: []byte{0x01, 0x02, 0x03, 0x04}, } @@ -228,8 +229,8 @@ func TestSubscribeInvalidDownlink(t *testing.T) { err := adapter.SubscribeDownlink(handler) a.So(err, ShouldBeNil) - appEUI := []byte{0x04, 0x03, 0x03, 0x09, 0x05, 0x06, 0x07, 0x08} - devEUI := []byte{0x08, 0x07, 0x06, 0x09, 0x04, 0x03, 0x02, 0x01} + appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x09, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x09, 0x04, 0x03, 0x02, 0x01} client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{}}).Wait() <-time.After(50 * time.Millisecond) diff --git a/mqtt/client.go b/mqtt/client.go index 5ce0d48eb..c518ea260 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -9,6 +9,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" MQTT "github.com/eclipse/paho.mqtt.golang" @@ -24,21 +25,21 @@ type Client interface { IsConnected() bool // Uplink pub/sub - PublishUplink(appEUI []byte, devEUI []byte, payload core.DataUpAppReq) Token - SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token - SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token + PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, payload core.DataUpAppReq) Token + SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types.DevEUI, handler UplinkHandler) Token + SubscribeAppUplink(appEUI types.AppEUI, handler UplinkHandler) Token SubscribeUplink(handler UplinkHandler) Token // Downlink pub/sub - PublishDownlink(appEUI []byte, devEUI []byte, payload core.DataDownAppReq) Token - SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token - SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token + PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, payload core.DataDownAppReq) Token + SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI types.DevEUI, handler DownlinkHandler) Token + SubscribeAppDownlink(appEUI types.AppEUI, handler DownlinkHandler) Token SubscribeDownlink(handler DownlinkHandler) Token // Activation pub/sub - PublishActivation(appEUI []byte, devEUI []byte, payload core.OTAAAppReq) Token - SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token - SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token + PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, payload core.OTAAAppReq) Token + SubscribeDeviceActivations(appEUI types.AppEUI, devEUI types.DevEUI, handler ActivationHandler) Token + SubscribeAppActivations(appEUI types.AppEUI, handler ActivationHandler) Token SubscribeActivations(handler ActivationHandler) Token } @@ -67,9 +68,9 @@ func (t *simpleToken) Error() error { return t.err } -type UplinkHandler func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) -type DownlinkHandler func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) -type ActivationHandler func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) +type UplinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) +type DownlinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) +type ActivationHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) type defaultClient struct { mqtt MQTT.Client @@ -140,7 +141,7 @@ func (c *defaultClient) IsConnected() bool { return c.mqtt.IsConnected() } -func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) Token { +func (c *defaultClient) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, dataUp core.DataUpAppReq) Token { topic := DeviceTopic{appEUI, devEUI, Uplink} msg, err := json.Marshal(dataUp) if err != nil { @@ -149,7 +150,7 @@ func (c *defaultClient) PublishUplink(appEUI []byte, devEUI []byte, dataUp core. return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, handler UplinkHandler) Token { +func (c *defaultClient) SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types.DevEUI, handler UplinkHandler) Token { topic := DeviceTopic{appEUI, devEUI, Uplink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -177,15 +178,15 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI []byte, devEUI []byte, hand }) } -func (c *defaultClient) SubscribeAppUplink(appEUI []byte, handler UplinkHandler) Token { - return c.SubscribeDeviceUplink(appEUI, []byte{}, handler) +func (c *defaultClient) SubscribeAppUplink(appEUI types.AppEUI, handler UplinkHandler) Token { + return c.SubscribeDeviceUplink(appEUI, types.DevEUI{}, handler) } func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { - return c.SubscribeDeviceUplink([]byte{}, []byte{}, handler) + return c.SubscribeDeviceUplink(types.AppEUI{}, types.DevEUI{}, handler) } -func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown core.DataDownAppReq) Token { +func (c *defaultClient) PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, dataDown core.DataDownAppReq) Token { topic := DeviceTopic{appEUI, devEUI, Downlink} msg, err := json.Marshal(dataDown) if err != nil { @@ -194,7 +195,7 @@ func (c *defaultClient) PublishDownlink(appEUI []byte, devEUI []byte, dataDown c return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, handler DownlinkHandler) Token { +func (c *defaultClient) SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI types.DevEUI, handler DownlinkHandler) Token { topic := DeviceTopic{appEUI, devEUI, Downlink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -221,15 +222,15 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI []byte, devEUI []byte, ha }) } -func (c *defaultClient) SubscribeAppDownlink(appEUI []byte, handler DownlinkHandler) Token { - return c.SubscribeDeviceDownlink(appEUI, []byte{}, handler) +func (c *defaultClient) SubscribeAppDownlink(appEUI types.AppEUI, handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink(appEUI, types.DevEUI{}, handler) } func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { - return c.SubscribeDeviceDownlink([]byte{}, []byte{}, handler) + return c.SubscribeDeviceDownlink(types.AppEUI{}, types.DevEUI{}, handler) } -func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activation core.OTAAAppReq) Token { +func (c *defaultClient) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, activation core.OTAAAppReq) Token { topic := DeviceTopic{appEUI, devEUI, Activations} msg, err := json.Marshal(activation) if err != nil { @@ -238,7 +239,7 @@ func (c *defaultClient) PublishActivation(appEUI []byte, devEUI []byte, activati return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, handler ActivationHandler) Token { +func (c *defaultClient) SubscribeDeviceActivations(appEUI types.AppEUI, devEUI types.DevEUI, handler ActivationHandler) Token { topic := DeviceTopic{appEUI, devEUI, Activations} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -265,10 +266,10 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI []byte, devEUI []byte, }) } -func (c *defaultClient) SubscribeAppActivations(appEUI []byte, handler ActivationHandler) Token { - return c.SubscribeDeviceActivations(appEUI, []byte{}, handler) +func (c *defaultClient) SubscribeAppActivations(appEUI types.AppEUI, handler ActivationHandler) Token { + return c.SubscribeDeviceActivations(appEUI, types.DevEUI{}, handler) } func (c *defaultClient) SubscribeActivations(handler ActivationHandler) Token { - return c.SubscribeDeviceActivations([]byte{}, []byte{}, handler) + return c.SubscribeDeviceActivations(types.AppEUI{}, types.DevEUI{}, handler) } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 2cb45c24c..a6e942e38 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -100,12 +101,12 @@ func TestPublishUplink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} dataUp := core.DataUpAppReq{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, } - token := c.PublishUplink(eui, eui, dataUp) + token := c.PublishUplink(types.AppEUI(eui), types.DevEUI(eui), dataUp) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -116,9 +117,9 @@ func TestSubscribeDeviceUplink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDeviceUplink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + token := c.SubscribeDeviceUplink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { }) token.Wait() @@ -131,9 +132,9 @@ func TestSubscribeAppUplink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.AppEUI{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeAppUplink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + token := c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { }) token.Wait() @@ -146,7 +147,7 @@ func TestSubscribeUplink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeUplink(func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + token := c.SubscribeUplink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { }) token.Wait() @@ -159,14 +160,14 @@ func TestPubSubUplink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := []byte{0x04, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + appEUI := types.AppEUI{0x04, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} var wg sync.WaitGroup wg.Add(1) - c.SubscribeDeviceUplink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + c.SubscribeDeviceUplink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) @@ -183,15 +184,15 @@ func TestPubSubAppUplink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := []byte{0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI1 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - devEUI2 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} + appEUI := types.AppEUI{0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI1 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + devEUI2 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} var wg sync.WaitGroup wg.Add(2) - c.SubscribeAppUplink(appEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + c.SubscribeAppUplink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() @@ -209,9 +210,9 @@ func TestInvalidUplink(t *testing.T) { c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x06, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.AppEUI{0x06, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - c.SubscribeAppUplink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataUpAppReq) { + c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { Ko(t, "Did not expect any message") }).Wait() @@ -233,12 +234,12 @@ func TestPublishDownlink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x01, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x01, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} dataDown := core.DataDownAppReq{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, } - token := c.PublishDownlink(eui, eui, dataDown) + token := c.PublishDownlink(types.AppEUI(eui), types.DevEUI(eui), dataDown) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -249,9 +250,9 @@ func TestSubscribeDeviceDownlink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDeviceDownlink(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + token := c.SubscribeDeviceDownlink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { }) token.Wait() @@ -264,9 +265,9 @@ func TestSubscribeAppDownlink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x03, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.AppEUI{0x03, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeAppDownlink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + token := c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { }) token.Wait() @@ -279,7 +280,7 @@ func TestSubscribeDownlink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeDownlink(func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + token := c.SubscribeDownlink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { }) token.Wait() @@ -292,14 +293,14 @@ func TestPubSubDownlink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := []byte{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} var wg sync.WaitGroup wg.Add(1) - c.SubscribeDeviceDownlink(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + c.SubscribeDeviceDownlink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) @@ -316,15 +317,15 @@ func TestPubSubAppDownlink(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := []byte{0x05, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI1 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - devEUI2 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} + appEUI := types.AppEUI{0x05, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI1 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + devEUI2 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} var wg sync.WaitGroup wg.Add(2) - c.SubscribeAppDownlink(appEUI, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + c.SubscribeAppDownlink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() @@ -342,9 +343,9 @@ func TestInvalidDownlink(t *testing.T) { c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x06, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.AppEUI{0x06, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - c.SubscribeAppDownlink(eui, func(client Client, appEUI []byte, devEUI []byte, req core.DataDownAppReq) { + c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { Ko(t, "Did not expect any message") }).Wait() @@ -366,10 +367,10 @@ func TestPublishActivations(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x01, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x01, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} dataActivations := core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}} - token := c.PublishActivation(eui, eui, dataActivations) + token := c.PublishActivation(types.AppEUI(eui), types.DevEUI(eui), dataActivations) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -380,9 +381,9 @@ func TestSubscribeDeviceActivations(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.EUI64{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDeviceActivations(eui, eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + token := c.SubscribeDeviceActivations(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { }) token.Wait() @@ -395,9 +396,9 @@ func TestSubscribeAppActivations(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x03, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.AppEUI{0x03, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeAppActivations(eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + token := c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { }) token.Wait() @@ -410,7 +411,7 @@ func TestSubscribeActivations(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeActivations(func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + token := c.SubscribeActivations(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { }) token.Wait() @@ -423,14 +424,14 @@ func TestPubSubActivations(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := []byte{0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + appEUI := types.AppEUI{0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} var wg sync.WaitGroup wg.Add(1) - c.SubscribeDeviceActivations(appEUI, devEUI, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + c.SubscribeDeviceActivations(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) @@ -447,15 +448,15 @@ func TestPubSubAppActivations(t *testing.T) { c := NewClient(nil, "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := []byte{0x05, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI1 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - devEUI2 := []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} + appEUI := types.AppEUI{0x05, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI1 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} + devEUI2 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} var wg sync.WaitGroup wg.Add(2) - c.SubscribeAppActivations(appEUI, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + c.SubscribeAppActivations(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { a.So(appEUI, ShouldResemble, appEUI) a.So(req.Metadata[0].DataRate, ShouldEqual, "SF7BW125") wg.Done() @@ -473,9 +474,9 @@ func TestInvalidActivations(t *testing.T) { c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") c.Connect() - eui := []byte{0x06, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + eui := types.AppEUI{0x06, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - c.SubscribeAppActivations(eui, func(client Client, appEUI []byte, devEUI []byte, req core.OTAAAppReq) { + c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { Ko(t, "Did not expect any message") }).Wait() diff --git a/mqtt/topics.go b/mqtt/topics.go index 9711abf86..296355da6 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -4,9 +4,11 @@ package mqtt import ( - "encoding/hex" "fmt" + "reflect" "regexp" + + "github.com/TheThingsNetwork/ttn/core/types" ) // DeviceTopicType represents the type of a device topic @@ -21,48 +23,51 @@ const ( Downlink DeviceTopicType = "down" ) +const wildcard = "+" + // DeviceTopic represents an MQTT topic for application devices // If the DevEUI is an empty []byte{}, it is considered to be a wildcard type DeviceTopic struct { - AppEUI []byte - DevEUI []byte + AppEUI types.AppEUI + DevEUI types.DevEUI Type DeviceTopicType } // ParseDeviceTopic parses an MQTT device topic string to a DeviceTopic struct func ParseDeviceTopic(topic string) (*DeviceTopic, error) { + var err error pattern := regexp.MustCompile("([0-9A-F]{16}|\\+)/(devices)/([0-9A-F]{16}|\\+)/(activations|up|down)") matches := pattern.FindStringSubmatch(topic) - if len(matches) < 4 { return nil, fmt.Errorf("Invalid topic format") } - - appEUI := []byte{} - if matches[3] != "+" { - appEUI, _ = hex.DecodeString(matches[1]) // validity asserted by our regex pattern + var appEUI types.AppEUI + if matches[1] != wildcard { + appEUI, err = types.ParseAppEUI(matches[1]) + if err != nil { + return nil, err + } } - - devEUI := []byte{} - if matches[3] != "+" { - devEUI, _ = hex.DecodeString(matches[3]) // validity asserted by our regex pattern + var devEUI types.DevEUI + if matches[1] != wildcard { + devEUI, err = types.ParseDevEUI(matches[3]) + if err != nil { + return nil, err + } } - topicType := DeviceTopicType(matches[4]) - return &DeviceTopic{appEUI, devEUI, topicType}, nil } // String implements the Stringer interface func (t DeviceTopic) String() string { - appEUI := "+" - if len(t.AppEUI) > 0 { - appEUI = fmt.Sprintf("%X", t.AppEUI) + appEUI := wildcard + if !reflect.DeepEqual(t.AppEUI, types.AppEUI{}) { + appEUI = t.AppEUI.String() } - - devEUI := "+" - if len(t.DevEUI) > 0 { - devEUI = fmt.Sprintf("%X", t.DevEUI) + devEUI := wildcard + if !reflect.DeepEqual(t.DevEUI, types.DevEUI{}) { + devEUI = t.DevEUI.String() } return fmt.Sprintf("%s/%s/%s/%s", appEUI, "devices", devEUI, t.Type) } diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index 360c61574..f6c913ff3 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -6,6 +6,7 @@ package mqtt import ( "testing" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) @@ -15,8 +16,8 @@ func TestParseDeviceTopic(t *testing.T) { topic := "0102030405060708/devices/0807060504030201/up" expected := &DeviceTopic{ - AppEUI: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - DevEUI: []byte{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, + AppEUI: types.AppEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, + DevEUI: types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, Type: Uplink, } @@ -58,8 +59,8 @@ func TestTopicString(t *testing.T) { a := New(t) topic := &DeviceTopic{ - AppEUI: []byte{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - DevEUI: []byte{0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21}, + AppEUI: types.AppEUI{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, + DevEUI: types.DevEUI{0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21}, Type: Downlink, } @@ -87,9 +88,9 @@ func TestTopicParseAndString(t *testing.T) { "+/devices/+/down", "+/devices/+/activations", // Not Wildcard - "0102030405060708/devices/0000000000000000/up", - "0102030405060708/devices/0000000000000000/down", - "0102030405060708/devices/0000000000000000/activations", + "0102030405060708/devices/0100000000000000/up", + "0102030405060708/devices/0100000000000000/down", + "0102030405060708/devices/0100000000000000/activations", } for _, expected := range expectedList { diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 9d00c0336..f1ae89675 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -57,7 +57,7 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, client := util.ConnectMQTTClient(ctx) defer client.Disconnect() - token := client.PublishDownlink(appEUI.Bytes(), devEUI.Bytes(), dataDown) + token := client.PublishDownlink(appEUI, devEUI, dataDown) if token.Wait(); token.Error() != nil { ctx.WithError(token.Error()).Fatal("Could not publish downlink") diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index cd5ed893e..9f53d367b 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -41,7 +41,7 @@ application.`, client := util.ConnectMQTTClient(ctx) - token := client.SubscribeDeviceUplink(appEUI.Bytes(), devEUI.Bytes(), func(client mqtt.Client, appEUI []byte, devEUI []byte, dataUp core.DataUpAppReq) { + token := client.SubscribeDeviceUplink(appEUI, devEUI, func(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, dataUp core.DataUpAppReq) { ctx := ctx.WithField("DevEUI", devEUI) // TODO: Find out what Metadata people want to see here From 6791be426bc33444ca01d2f8aea0e6c9ba60c42b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 6 May 2016 11:20:01 +0200 Subject: [PATCH 1365/2266] [ttnctl] Correct use of core/types --- ttnctl/cmd/applications.go | 7 +++---- ttnctl/cmd/device.go | 6 +++--- ttnctl/cmd/subscribe.go | 4 ++-- ttnctl/cmd/uplink.go | 5 +++++ ttnctl/util/applications.go | 10 +++++----- ttnctl/util/mqtt.go | 4 ++-- 6 files changed, 20 insertions(+), 16 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 7a5febe7c..ee6bbeacf 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -191,16 +191,15 @@ var applicationsUseCmd = &cobra.Command{ } var found bool - newEUI := fmt.Sprintf("%X", appEUI) for _, app := range apps { - if app.EUI == newEUI { + if app.EUI == appEUI { found = true break } } if !found { - ctx.Fatalf("%X not found in registered applications", appEUI) + ctx.Fatalf("%s not found in registered applications", appEUI) } // Determine config file @@ -229,7 +228,7 @@ var applicationsUseCmd = &cobra.Command{ } // Update app - c["app-eui"] = newEUI + c["app-eui"] = appEUI // Write config file d, err := yaml.Marshal(&c) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index db9590630..46b798fcd 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -152,7 +152,7 @@ var devicesInfoCmd = &cobra.Command{ if devEUI, err := types.ParseDevEUI(args[0]); err == nil { for _, device := range res.OTAA { - if reflect.DeepEqual(device.DevEUI, devEUI) { + if reflect.DeepEqual(device.DevEUI, devEUI.Bytes()) { fmt.Println("Dynamic device:") fmt.Println() @@ -219,7 +219,7 @@ var devicesInfoCmd = &cobra.Command{ if devAddr, err := types.ParseDevAddr(args[0]); err == nil { for _, device := range res.ABP { - if reflect.DeepEqual(device.DevAddr, devAddr) { + if reflect.DeepEqual(device.DevAddr, devAddr.Bytes()) { fmt.Println("Personalized device:") fmt.Println() @@ -368,7 +368,7 @@ registration on the Handler`, if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } - if reflect.DeepEqual(nwkSKey, defaultKey) || reflect.DeepEqual(appSKey, defaultKey) { + if reflect.DeepEqual(nwkSKey.Bytes(), defaultKey) || reflect.DeepEqual(appSKey.Bytes(), defaultKey) { ctx.Warn("You are using default keys, any attacker can read your data or attack your device's connectivity.") } } else { diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 9f53d367b..59efe1947 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -34,9 +34,9 @@ application.`, if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) } - ctx.Infof("Subscribing uplink messages from device %X", devEUI) + ctx.Infof("Subscribing uplink messages from device %s", devEUI) } else { - ctx.Infof("Subscribing to uplink messages from all devices in application %X", appEUI) + ctx.Infof("Subscribing to uplink messages from all devices in application %s", appEUI) } client := util.ConnectMQTTClient(ctx) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 1e94b8f90..e03114489 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -155,6 +155,11 @@ var uplinkCmd = &cobra.Command{ ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) } + micOK, _ := payload.ValidateMIC(lorawan.AES128Key(nwkSKey)) + if !micOK { + ctx.Warn("MIC check failed.") + } + macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) if !ok || len(macPayload.FRMPayload) > 1 { ctx.Fatalf("Unable to retrieve LoRaWAN MACPayload") diff --git a/ttnctl/util/applications.go b/ttnctl/util/applications.go index cecd0c9a2..c756ab0f5 100644 --- a/ttnctl/util/applications.go +++ b/ttnctl/util/applications.go @@ -11,11 +11,11 @@ import ( ) type App struct { - EUI string `json:"eui"` // TODO: Change to []string - Name string `json:"name"` - Owner string `json:"owner"` - AccessKeys []string `json:"accessKeys"` - Valid bool `json:"valid"` + EUI types.AppEUI `json:"eui"` // TODO: Change to []string + Name string `json:"name"` + Owner string `json:"owner"` + AccessKeys []string `json:"accessKeys"` + Valid bool `json:"valid"` } func GetAppEUI(ctx log.Interface) types.AppEUI { diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 316e01d67..3e4ae0fc1 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -22,7 +22,7 @@ func ConnectMQTTClient(ctx log.Interface) mqtt.Client { var app *App for _, a := range apps { - if a.EUI == fmt.Sprintf("%X", appEUI) { + if a.EUI == appEUI { app = a } } @@ -32,7 +32,7 @@ func ConnectMQTTClient(ctx log.Interface) mqtt.Client { broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) // Don't care about which access key here - client := mqtt.NewClient(ctx, "ttnctl", app.EUI, app.AccessKeys[0], broker) + client := mqtt.NewClient(ctx, "ttnctl", app.EUI.String(), app.AccessKeys[0], broker) if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") From 9f47c127600d0e65fa35b8ef0ed42ae2ca4caaaa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 13 May 2016 12:50:55 +0200 Subject: [PATCH 1366/2266] Fix CI build for ARM [Skip CI] --- .drone.yml | 17 ++++++++--------- Makefile | 9 ++++----- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/.drone.yml b/.drone.yml index acde4a3ca..82f3bd339 100644 --- a/.drone.yml +++ b/.drone.yml @@ -2,13 +2,13 @@ clone: recursive: true matrix: - GOOS: - - linux - - darwin - - windows - GOARCH: - - "386" - - amd64 + TARGET_PLATFORM: + - linux-386 + - linux-amd64 + - linux-arm + - darwin-amd64 + - windows-386 + - windows-amd64 compose: mqtt: @@ -43,5 +43,4 @@ publish: when: branch: master matrix: - GOOS: linux - GOARCH: amd64 + TARGET_PLATFORM: linux-amd64 diff --git a/Makefile b/Makefile index 87ecc62e5..ea4f53bd5 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ SHELL = bash -GOOS ?= $(shell echo "`go env GOOS`") -GOARCH ?= $(shell echo "`go env GOARCH`") -GOEXE ?= $(shell echo "`go env GOEXE`") +GOOS ?= $(or $(word 1,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOOS`")) +GOARCH ?= $(or $(word 2,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOARCH`")) +GOEXE ?= $(shell echo "`GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE`") GOCMD = go @@ -75,8 +75,7 @@ clean: build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) -docker: GOOS = linux -docker: GOARCH = amd64 +docker: TARGET_PLATFORM = linux-amd64 docker: clean $(RELEASE_DIR)/$(ttnbin) docker build -t thethingsnetwork/ttn -f Dockerfile.local . From 1e12a49076a7e851282c3216620419d8c1fa12fc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 13 May 2016 12:52:08 +0200 Subject: [PATCH 1367/2266] [ci] Update .drone.sec --- .drone.sec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.sec b/.drone.sec index 03a194495..7a6f90106 100644 --- a/.drone.sec +++ b/.drone.sec @@ -1 +1 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.JMQYGyVPuM2EI2Xd1J64gBOn2rMth9a0D7DVf5K_5HQR3zv8gqnCvFwk2erJkKrFoxfeV5yQzmaHOrblmXrik1L4ccQ6VGFKRcUsWwHDDTbzy9wk3wHyXTl8AvXob27zQMJrDfdG6U_2mmUfVpfdmsJbYvFGBQK77_9OLEQE8cbLv9rX1pV_2D12fGVmMY6w7e-kGATwrqqwdYY-e4gRVqmyI5gwN6SeZbHmEVqlk-umsaYrGyPa5tMldPPmi27h7c0SCsOOdpju13epOVnyBIz4O6HVvXP5k6kvroKdeozxG_7PGUjDYyBWrKgeUbOl6iriG-bSPs0f2NFcvt37bA.q7uQR6u-HmVfuKYO.Sz8Mnh9hib1Snu0MHF_3WCn1uQOg4OfFv__KAyAi4CkddiysKOumOODSVpAsWBMPWd0XFHlo32_13HLRekjhoBbowdo40LHmHGrgXXIEqt544lhviTYR6FupVB6xCe89CIRE8-yXMT81I_pT7y0cuHEh4CmHTQsDD8TGmfphk-NQFYpNYf6yUsNO0iNj4o-HkCPtW5MtvkHw9gseuJtgDzuvJQ89lFzESSqfgxjJDT04DF9SWgZFPIHoWRsXQhKQ4ieuzuE5QGHAIL7eHV6QDfppx2yUnC4CnfoN-DSlrDkYoABmjIjOd3AKRZm1BM4u-zJp3znz7vHLScUVPDmz45lxULOlcNZvZTxDqte0zvBYOu2b1FxM_EqQkKV4BZfBpNwppkGJDHoGofLE_GFUbnJtpXznYIyQi29yOVNbVJXfv9wPP-w8uiglZKcjUCU-fOSzx5qyeNo--sEYe_e8L-kXvE7H.TNNZVpTq27vwqNu1P-SY0w \ No newline at end of file +eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.n9QUGATHW2RFFSgvadl1JZ80jbdqiCM7AyrulN1R7MOlB1c8S7Z29Ioc3f3HGx3eBiFokb0uCKDOnSt3J2eUGvhYHXNxn7C18gHOIhA_vS4fMistcPgX68a-Rietp0DkdA5S1WLQvOLDEpkmt_wltDVkApQ8ROLQQKHPsQCltt16C5jou6QrDjCadiiY8zHLInAvVVwi7jIpL98nhz50YK_CZfB7H4pvWVSfFqZiSt-XoN6mCfzn44-hoB-WDz4UdHmgEnxa_CktpKPXc9PtMod73yhpBJf87vUHounfcJJJwK1fSjNhJlc7bHli8Sfp0dDSJZSOe5m4iZc9bJC9Bg.xQj0AkcaSwPQkO3K.FC5xexkTbNUW89TFveVqLhrhZ3Uh9HKUgawzp3Ogypx0f5Glw7ELFuTUcRrNDArWZuLxChLGcKoKFNke4Jlc4T39zKmNaV_hgqS1Dt5Uv9MT_j8JZi1OTLa1UwxLOBJqAbK9azcwhV9FPb2X46ts-yZsuCyMqe2gBCN_zFYVw4UCGFjwR81QwiKR-7jFm4sklpIxcrztBNeNhmpmHs8VE25Ya1dy5wyvWwhFlI9FTm01DPDsG2xOZcbvvOO9N9lAAkuvnGJyM-JZIbKnf-Fxs9MORLoqcngumfAKxjwUI4dDBKFmWLxakIDsvBdUBp1JtIzI2vwg71m1oBgvN0DXzfA7_Z55u7fJpkShOct74ewlytmWwAZ_1BSaP45CPJ7hAkr5XQM1Vn-Jozudmw24VBbC2kOm2RrlPZRvni35eOc4PJ4njoTutLLMTs3XcvdIhTeKdEwZp1-_3Ex059Im6t8PPYcx.0rqytqBzBFPMRKkkGrubGg \ No newline at end of file From 0115d3c7de66914b37a9f6911752c015453144e5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 13 May 2016 14:13:32 +0200 Subject: [PATCH 1368/2266] [ci] Fix Makefile --- Makefile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index ea4f53bd5..338ab15de 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,11 @@ SHELL = bash -GOOS ?= $(or $(word 1,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOOS`")) -GOARCH ?= $(or $(word 2,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOARCH`")) -GOEXE ?= $(shell echo "`GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE`") +export GOOS=$(or $(word 1,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOOS`")) +export GOARCH=$(or $(word 2,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOARCH`")) +export GOEXE=$(shell echo "`GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE`") +export CGO_ENABLED=0 GOCMD = go - -export CGO_ENABLED=0 GOBUILD = $(GOCMD) build GIT_COMMIT = `git rev-parse HEAD 2>/dev/null` From a6bcb971159270ef5ee283c9b75c08c00571490e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 14 May 2016 19:27:45 +0200 Subject: [PATCH 1369/2266] Add Procfile for development --- Makefile | 5 ++++- Procfile | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Procfile diff --git a/Makefile b/Makefile index 338ab15de..69c02b7b7 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps test-deps build-deps proto test fmt vet cover build docker package +.PHONY: all clean deps test-deps dev-deps proto test fmt vet cover build docker package all: clean deps build package @@ -39,6 +39,9 @@ deps: test-deps: $(GOCMD) get -d -v $(TEST_DEPS) +dev-deps: + $(GOCMD) go get -v github.com/ddollar/forego + proto-deps: $(GOCMD) get -v github.com/gogo/protobuf/protoc-gen-gofast diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..0ba7b1431 --- /dev/null +++ b/Procfile @@ -0,0 +1,3 @@ +ro: go run main.go router +br: go run main.go broker +ha: go run main.go handler From a27f9231d9ab91418f3be657d88654b34e6161e4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 15 May 2016 14:21:46 +0200 Subject: [PATCH 1370/2266] Fix mistake in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 69c02b7b7..e0e1b78db 100644 --- a/Makefile +++ b/Makefile @@ -40,7 +40,7 @@ test-deps: $(GOCMD) get -d -v $(TEST_DEPS) dev-deps: - $(GOCMD) go get -v github.com/ddollar/forego + $(GOCMD) get -v github.com/ddollar/forego proto-deps: $(GOCMD) get -v github.com/gogo/protobuf/protoc-gen-gofast From 5923008dfa3d873157f977c9e003c2d6ccbc6f2e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 00:46:08 +0200 Subject: [PATCH 1371/2266] Decode, convert and validate payload using JavaScript functions --- core/payload/functions.go | 86 ++++++++++++++++++++++++++++++++++ core/payload/functions_test.go | 61 ++++++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 core/payload/functions.go create mode 100644 core/payload/functions_test.go diff --git a/core/payload/functions.go b/core/payload/functions.go new file mode 100644 index 000000000..a9ca01eff --- /dev/null +++ b/core/payload/functions.go @@ -0,0 +1,86 @@ +package payload + +import ( + "errors" + "fmt" + + "github.com/robertkrimen/otto" +) + +// Functions decodes, converts and validates payload using JavaScript functions +type Functions struct { + // Decoder is a JavaScript function that accepts the payload as byte array and + // returns an object containing the decoded values + Decoder string + // Converter is a JavaScript function that accepts the data as decoded by + // Decoder and returns an object containing the converted values + Converter string + // Validator is a JavaScript function that validates the data is converted by + // Converter and returns a boolean value indicating the validity of the data + Validator string +} + +// Decode decodes the payload using the Decoder function into a map +func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { + if f.Decoder == "" { + return nil, errors.New("Decoder function not set") + } + + vm := otto.New() + vm.Set("payload", payload) + value, err := vm.Run(fmt.Sprintf("(%s)(payload)", f.Decoder)) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Decoder does not return an object") + } + + v, _ := value.Export() + return v.(map[string]interface{}), nil +} + +// Convert converts the values in the specified map to a another map using the +// Converter function. If the Converter function is not set, this function +// returns the data as-is +func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{}, error) { + if f.Converter == "" { + return data, nil + } + + vm := otto.New() + vm.Set("data", data) + value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Converter)) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Converter does not return an object") + } + + v, _ := value.Export() + return v.(map[string]interface{}), nil +} + +// Validate validates the values in the specified map using the Validator +// function. If the Validator function is not set, this function returns true +func (f *Functions) Validate(data map[string]interface{}) (bool, error) { + if f.Validator == "" { + return true, nil + } + + vm := otto.New() + vm.Set("data", data) + value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Validator)) + if err != nil { + return false, err + } + + if !value.IsBoolean() { + return false, errors.New("Validator does not return a boolean") + } + + return value.ToBoolean() +} diff --git a/core/payload/functions_test.go b/core/payload/functions_test.go new file mode 100644 index 000000000..253fda102 --- /dev/null +++ b/core/payload/functions_test.go @@ -0,0 +1,61 @@ +package payload + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDecode(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `function(payload) { + return { + value: (payload[0] << 8) | payload[1] + }; + }`, + } + payload := []byte{0x48, 0x65} + + m, err := functions.Decode(payload) + a.So(err, ShouldBeNil) + + size, ok := m["value"] + a.So(ok, ShouldBeTrue) + a.So(size, ShouldEqual, 18533) +} + +func TestConvert(t *testing.T) { + a := New(t) + + functions := &Functions{ + Converter: `function(data) { + return { + size: data.temperature * 2 + }; + }`, + } + + data, err := functions.Convert(map[string]interface{}{"temperature": 11}) + a.So(err, ShouldBeNil) + a.So(data["size"], ShouldEqual, 22) +} + +func TestValidate(t *testing.T) { + a := New(t) + + functions := &Functions{ + Validator: `function(data) { + return data.temperature < 20; + }`, + } + + valid, err := functions.Validate(map[string]interface{}{"temperature": 10}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) + + valid, err = functions.Validate(map[string]interface{}{"temperature": 30}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) +} From 9c9d1a3b8da90dcc7d26a70fd233374253cc0a4b Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 01:00:51 +0200 Subject: [PATCH 1372/2266] Added Process function --- core/payload/functions.go | 19 +++++++++++++++++++ core/payload/functions_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/core/payload/functions.go b/core/payload/functions.go index a9ca01eff..c5629915a 100644 --- a/core/payload/functions.go +++ b/core/payload/functions.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package payload import ( @@ -84,3 +87,19 @@ func (f *Functions) Validate(data map[string]interface{}) (bool, error) { return value.ToBoolean() } + +// Process decodes the specified payload, converts it and test the validity +func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error) { + decoded, err := f.Decode(payload) + if err != nil { + return nil, false, err + } + + converted, err := f.Convert(decoded) + if err != nil { + return nil, false, err + } + + valid, err := f.Validate(converted) + return converted, valid, err +} diff --git a/core/payload/functions_test.go b/core/payload/functions_test.go index 253fda102..1dadd28fe 100644 --- a/core/payload/functions_test.go +++ b/core/payload/functions_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package payload import ( @@ -59,3 +62,29 @@ func TestValidate(t *testing.T) { a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) } + +func TestProcess(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `function(payload) { + return { + temperature: payload[0], + humidity: payload[1] + } + }`, + Converter: `function(data) { + data.temperature /= 2; + return data; + }`, + Validator: `function(data) { + return data.humidity >= 0 && data.temperature <= 100; + }`, + } + + data, valid, err := functions.Process([]byte{40, 15}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) + a.So(data["temperature"], ShouldEqual, 20) + a.So(data["humidity"], ShouldEqual, 15) +} From b50ef01d59339e76881e7c9130f099158da5a556 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 01:02:52 +0200 Subject: [PATCH 1373/2266] Fixed test --- core/payload/functions_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/payload/functions_test.go b/core/payload/functions_test.go index 1dadd28fe..3aa6dfc36 100644 --- a/core/payload/functions_test.go +++ b/core/payload/functions_test.go @@ -78,13 +78,13 @@ func TestProcess(t *testing.T) { return data; }`, Validator: `function(data) { - return data.humidity >= 0 && data.temperature <= 100; + return data.humidity >= 0 && data.humidity <= 100; }`, } - data, valid, err := functions.Process([]byte{40, 15}) + data, valid, err := functions.Process([]byte{40, 110}) a.So(err, ShouldBeNil) - a.So(valid, ShouldBeTrue) + a.So(valid, ShouldBeFalse) a.So(data["temperature"], ShouldEqual, 20) - a.So(data["humidity"], ShouldEqual, 15) + a.So(data["humidity"], ShouldEqual, 110) } From 64084fbf9fd5f2edf0fa87793359202d214aff7e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 01:14:32 +0200 Subject: [PATCH 1374/2266] Test no functions --- core/payload/functions_test.go | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/core/payload/functions_test.go b/core/payload/functions_test.go index 3aa6dfc36..e44818e21 100644 --- a/core/payload/functions_test.go +++ b/core/payload/functions_test.go @@ -32,35 +32,42 @@ func TestDecode(t *testing.T) { func TestConvert(t *testing.T) { a := New(t) - functions := &Functions{ + withFunction := &Functions{ Converter: `function(data) { return { - size: data.temperature * 2 + celcius: data.temperature * 2 }; }`, } + data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) + a.So(err, ShouldBeNil) + a.So(data["celcius"], ShouldEqual, 22) - data, err := functions.Convert(map[string]interface{}{"temperature": 11}) + withoutFunction := &Functions{} + data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) a.So(err, ShouldBeNil) - a.So(data["size"], ShouldEqual, 22) + a.So(data["temperature"], ShouldEqual, 11) } func TestValidate(t *testing.T) { a := New(t) - functions := &Functions{ + withFunction := &Functions{ Validator: `function(data) { return data.temperature < 20; }`, } - - valid, err := functions.Validate(map[string]interface{}{"temperature": 10}) + valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}) a.So(err, ShouldBeNil) a.So(valid, ShouldBeTrue) - - valid, err = functions.Validate(map[string]interface{}{"temperature": 30}) + valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}) a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) + + withoutFunction := &Functions{} + valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) } func TestProcess(t *testing.T) { From 666dbfe112c16b426a7c47e32bb8b3c377eddbe2 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 23:20:20 +0200 Subject: [PATCH 1375/2266] Using new types formatting --- ttnctl/cmd/device.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 46b798fcd..f06c9e8e7 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -4,6 +4,7 @@ package cmd import ( + "bytes" "fmt" "reflect" "strings" @@ -56,7 +57,7 @@ registered on the Handler.`, AppEUI: appEUI.Bytes(), }) if err != nil { - // TODO: Check reason + // TODO: Check reason; not found is OK here defaultDevice = nil } if defaultDevice != nil { @@ -152,7 +153,7 @@ var devicesInfoCmd = &cobra.Command{ if devEUI, err := types.ParseDevEUI(args[0]); err == nil { for _, device := range res.OTAA { - if reflect.DeepEqual(device.DevEUI, devEUI.Bytes()) { + if bytes.Equal(device.DevEUI, devEUI.Bytes()) { fmt.Println("Dynamic device:") fmt.Println() @@ -219,7 +220,7 @@ var devicesInfoCmd = &cobra.Command{ if devAddr, err := types.ParseDevAddr(args[0]); err == nil { for _, device := range res.ABP { - if reflect.DeepEqual(device.DevAddr, devAddr.Bytes()) { + if bytes.Equal(device.DevAddr, devAddr.Bytes()) { fmt.Println("Personalized device:") fmt.Println() From 35735d3aa5aa4ff60578fef9b0692540a846dd2e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 23:22:25 +0200 Subject: [PATCH 1376/2266] Collection package using decoder, converter and validator functions --- core/collection/appCollector.go | 85 +++++++++++++++++++++ core/collection/appCollector_test.go | 107 +++++++++++++++++++++++++++ core/collection/dataStorage.go | 16 ++++ core/collection/functions.go | 105 ++++++++++++++++++++++++++ core/collection/functions_test.go | 97 ++++++++++++++++++++++++ 5 files changed, 410 insertions(+) create mode 100644 core/collection/appCollector.go create mode 100644 core/collection/appCollector_test.go create mode 100644 core/collection/dataStorage.go create mode 100644 core/collection/functions.go create mode 100644 core/collection/functions_test.go diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go new file mode 100644 index 000000000..6254e7e7c --- /dev/null +++ b/core/collection/appCollector.go @@ -0,0 +1,85 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collection + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +// AppCollector represents a collector for application data +type AppCollector interface { + Start() error + Stop() +} + +type appCollector struct { + ctx log.Interface + eui types.AppEUI + functions *Functions + broker string + client mqtt.Client + storage DataStorage +} + +// NewMqttAppCollector instantiates a new AppCollector instance using MQTT +func NewMqttAppCollector(ctx log.Interface, broker string, eui types.AppEUI, key string, functions *Functions, storage DataStorage) AppCollector { + return &appCollector{ + ctx: ctx, + eui: eui, + functions: functions, + broker: broker, + client: mqtt.NewClient(ctx, "collector", eui.String(), key, fmt.Sprintf("tcp://%s", broker)), + storage: storage, + } +} + +func (c *appCollector) Start() error { + err := c.client.Connect() + if err != nil { + c.ctx.WithError(err).Error("Connect failed") + return err + } + if token := c.client.SubscribeAppUplink(c.eui, c.handleUplink); token.Wait() && token.Error() != nil { + c.ctx.WithError(token.Error()).Error("Failed to subscribe") + return token.Error() + } + c.ctx.WithField("broker", c.broker).Info("Subscribed to app uplink packets") + return nil +} + +func (c *appCollector) Stop() { + c.client.Disconnect() +} + +func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + ctx := c.ctx.WithField("devEUI", devEUI) + + fields, valid, err := c.functions.Process(req.Payload) + if err != nil { + ctx.WithError(err).Error("Failed to process payload") + return + } + if !valid { + ctx.Warn("The payload is not valid") + return + } + + t, err := time.Parse(time.RFC3339, req.Metadata[0].ServerTime) + if err != nil { + ctx.WithError(err).Warnf("Invalid time: %v", req.Metadata[0].ServerTime) + return + } + + err = c.storage.Save(appEUI, devEUI, t, fields) + if err != nil { + ctx.WithError(err).Error("Failed to save data") + } + ctx.Debug("Saved uplink packet in store") +} diff --git a/core/collection/appCollector_test.go b/core/collection/appCollector_test.go new file mode 100644 index 000000000..de127c526 --- /dev/null +++ b/core/collection/appCollector_test.go @@ -0,0 +1,107 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collection + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/apex/log" + + . "github.com/smartystreets/assertions" +) + +type mockStorage struct { + entry chan *mockStorageEntry + entries []*mockStorageEntry +} + +type mockStorageEntry struct { + devEUI types.DevEUI + fields map[string]interface{} +} + +func createTestCollector(ctx log.Interface, storage DataStorage) AppCollector { + eui, _ := types.ParseAppEUI("8000000000000001") + functions := &Functions{ + Decoder: `function(payload) { return { size: payload.length } }`, + Converter: `function(data) { return data; }`, + Validator: `function(data) { return data.size > 0; }`, + } + return NewMqttAppCollector(ctx, "localhost:1883", eui, "", functions, storage) +} + +func TestStart(t *testing.T) { + a := New(t) + + ctx := ttntesting.GetLogger(t, "Collection") + storage := &mockStorage{} + collector := createTestCollector(ctx, storage) + a.So(collector, ShouldNotBeNil) + + err := collector.Start() + defer collector.Stop() + a.So(err, ShouldBeNil) +} + +func TestCollect(t *testing.T) { + a := New(t) + + ctx := ttntesting.GetLogger(t, "Collection") + storage := &mockStorage{ + entry: make(chan *mockStorageEntry), + } + collector := createTestCollector(ctx, storage) + + err := collector.Start() + defer collector.Stop() + a.So(err, ShouldBeNil) + + appEUI, _ := types.ParseAppEUI("8000000000000001") + devEUI, _ := types.ParseDevEUI("1000000000000001") + + client := mqtt.NewClient(ctx, "collector", "", "", "tcp://localhost:1883") + err = client.Connect() + So(err, ShouldBeNil) + defer client.Disconnect() + + req := core.DataUpAppReq{ + DevEUI: devEUI.String(), + FCnt: 0, + FPort: 1, + Metadata: []core.AppMetadata{core.AppMetadata{ServerTime: time.Now().Format(time.RFC3339)}}, + Payload: []byte{0x1, 0x2, 0x3}, + } + if token := client.PublishUplink(appEUI, devEUI, req); token.Wait() && token.Error() != nil { + panic(token.Error()) + } + + var entry *mockStorageEntry + select { + case entry = <-storage.entry: + break + case <-time.After(time.Second): + panic("Timeout") + } + a.So(entry, ShouldNotBeNil) + a.So(entry.devEUI, ShouldResemble, devEUI) + a.So(entry.fields, ShouldHaveLength, 1) + a.So(entry.fields["size"], ShouldEqual, 3) +} + +func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { + entry := &mockStorageEntry{devEUI, fields} + s.entries = append(s.entries, entry) + s.entry <- entry + return nil +} + +func (s *mockStorage) Close() error { + s.entry <- nil + return nil +} diff --git a/core/collection/dataStorage.go b/core/collection/dataStorage.go new file mode 100644 index 000000000..9cd990815 --- /dev/null +++ b/core/collection/dataStorage.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collection + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// DataStorage provides methods to select and save data +type DataStorage interface { + Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error + Close() error +} diff --git a/core/collection/functions.go b/core/collection/functions.go new file mode 100644 index 000000000..f5299cca5 --- /dev/null +++ b/core/collection/functions.go @@ -0,0 +1,105 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collection + +import ( + "errors" + "fmt" + + "github.com/robertkrimen/otto" +) + +// Functions decodes, converts and validates payload using JavaScript functions +type Functions struct { + // Decoder is a JavaScript function that accepts the payload as byte array and + // returns an object containing the decoded values + Decoder string + // Converter is a JavaScript function that accepts the data as decoded by + // Decoder and returns an object containing the converted values + Converter string + // Validator is a JavaScript function that validates the data is converted by + // Converter and returns a boolean value indicating the validity of the data + Validator string +} + +// Decode decodes the payload using the Decoder function into a map +func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { + if f.Decoder == "" { + return nil, errors.New("Decoder function not set") + } + + vm := otto.New() + vm.Set("payload", payload) + value, err := vm.Run(fmt.Sprintf("(%s)(payload)", f.Decoder)) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Decoder does not return an object") + } + + v, _ := value.Export() + return v.(map[string]interface{}), nil +} + +// Convert converts the values in the specified map to a another map using the +// Converter function. If the Converter function is not set, this function +// returns the data as-is +func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{}, error) { + if f.Converter == "" { + return data, nil + } + + vm := otto.New() + vm.Set("data", data) + value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Converter)) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Converter does not return an object") + } + + v, _ := value.Export() + return v.(map[string]interface{}), nil +} + +// Validate validates the values in the specified map using the Validator +// function. If the Validator function is not set, this function returns true +func (f *Functions) Validate(data map[string]interface{}) (bool, error) { + if f.Validator == "" { + return true, nil + } + + vm := otto.New() + vm.Set("data", data) + value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Validator)) + if err != nil { + return false, err + } + + if !value.IsBoolean() { + return false, errors.New("Validator does not return a boolean") + } + + return value.ToBoolean() +} + +// Process decodes the specified payload, converts it and test the validity +func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error) { + decoded, err := f.Decode(payload) + if err != nil { + return nil, false, err + } + + converted, err := f.Convert(decoded) + if err != nil { + return nil, false, err + } + + valid, err := f.Validate(converted) + return converted, valid, err +} diff --git a/core/collection/functions_test.go b/core/collection/functions_test.go new file mode 100644 index 000000000..41a12262f --- /dev/null +++ b/core/collection/functions_test.go @@ -0,0 +1,97 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collection + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDecode(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `function(payload) { + return { + value: (payload[0] << 8) | payload[1] + }; + }`, + } + payload := []byte{0x48, 0x65} + + m, err := functions.Decode(payload) + a.So(err, ShouldBeNil) + + size, ok := m["value"] + a.So(ok, ShouldBeTrue) + a.So(size, ShouldEqual, 18533) +} + +func TestConvert(t *testing.T) { + a := New(t) + + withFunction := &Functions{ + Converter: `function(data) { + return { + celcius: data.temperature * 2 + }; + }`, + } + data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) + a.So(err, ShouldBeNil) + a.So(data["celcius"], ShouldEqual, 22) + + withoutFunction := &Functions{} + data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) + a.So(err, ShouldBeNil) + a.So(data["temperature"], ShouldEqual, 11) +} + +func TestValidate(t *testing.T) { + a := New(t) + + withFunction := &Functions{ + Validator: `function(data) { + return data.temperature < 20; + }`, + } + valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) + valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) + + withoutFunction := &Functions{} + valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) +} + +func TestProcess(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `function(payload) { + return { + temperature: payload[0], + humidity: payload[1] + } + }`, + Converter: `function(data) { + data.temperature /= 2; + return data; + }`, + Validator: `function(data) { + return data.humidity >= 0 && data.humidity <= 100; + }`, + } + + data, valid, err := functions.Process([]byte{40, 110}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) + a.So(data["temperature"], ShouldEqual, 20) + a.So(data["humidity"], ShouldEqual, 110) +} From 3753769be3b40ea3470dad46a292faa403259f93 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 23:22:54 +0200 Subject: [PATCH 1377/2266] Collector mode with InfluxDB back-end --- cmd/collector.go | 78 +++++++++++++++++++++ core/collector/appStorage.go | 102 ++++++++++++++++++++++++++++ core/collector/appStorage_test.go | 97 ++++++++++++++++++++++++++ core/collector/collector.go | 83 ++++++++++++++++++++++ core/collector/collector_test.go | 52 ++++++++++++++ core/collector/influxdb/influxdb.go | 54 +++++++++++++++ 6 files changed, 466 insertions(+) create mode 100644 cmd/collector.go create mode 100644 core/collector/appStorage.go create mode 100644 core/collector/appStorage_test.go create mode 100644 core/collector/collector.go create mode 100644 core/collector/collector_test.go create mode 100644 core/collector/influxdb/influxdb.go diff --git a/cmd/collector.go b/cmd/collector.go new file mode 100644 index 000000000..058cdbc73 --- /dev/null +++ b/cmd/collector.go @@ -0,0 +1,78 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "os" + "os/signal" + + "github.com/TheThingsNetwork/ttn/core/collector" + "github.com/TheThingsNetwork/ttn/core/collector/influxdb" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "gopkg.in/redis.v3" +) + +// collectorCmd represents the collector command +var collectorCmd = &cobra.Command{ + Use: "collector", + Short: "The Things Network collector", + Long: `ttn collector starts the Collector component of The Things Network. + +The Collector is responsible for storing uplink packets from the handler for +configured applications. +`, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + appStorage, err := collector.ConnectRedisAppStorage(&redis.Options{ + Addr: viper.GetString("collector.redis-addr"), + DB: int64(viper.GetInt("collector.redis-db")), + }) + if err != nil { + ctx.WithError(err).Fatal("Failed to connect to Redis") + } + defer appStorage.Close() + + dataStorage, err := influxdb.NewInfluxDBStorage(viper.GetString("collector.influxdb-addr"), + viper.GetString("collector.influxdb-username"), viper.GetString("collector.influxdb-password")) + if err != nil { + ctx.WithError(err).Fatal("Failed to connect to InfluxDB") + } + + col := collector.NewCollector(ctx, appStorage, viper.GetString("collector.mqtt-broker"), dataStorage) + collectors, err := col.Start() + if startError, ok := err.(collector.StartError); ok { + ctx.WithError(startError).Warn("Could not start collecting all applications") + } else if err != nil { + ctx.WithError(err).Fatal("Could not start collector") + } + defer col.Stop() + + ctx.Infof("Started %d collectors", len(collectors)) + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + <-c + }, +} + +func init() { + RootCmd.AddCommand(collectorCmd) + + collectorCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") + viper.BindPFlag("collector.mqtt-broker", collectorCmd.Flags().Lookup("mqtt-broker")) + + collectorCmd.Flags().String("influxdb-addr", "http://localhost:8086", "The address of InfluxDB") + collectorCmd.Flags().String("influxdb-username", "", "The username for InfluxDB") + collectorCmd.Flags().String("influxdb-password", "", "The password for InfluxDB") + viper.BindPFlag("collector.influxdb-addr", collectorCmd.Flags().Lookup("influxdb-addr")) + viper.BindPFlag("collector.influxdb-username", collectorCmd.Flags().Lookup("influxdb-username")) + viper.BindPFlag("collector.influxdb-password", collectorCmd.Flags().Lookup("influxdb-password")) + + collectorCmd.Flags().String("redis-addr", "localhost:6379", "The address of Redis") + collectorCmd.Flags().Int("redis-db", 0, "The database of Redis") + viper.BindPFlag("collector.redis-addr", collectorCmd.Flags().Lookup("redis-addr")) + viper.BindPFlag("collector.redis-db", collectorCmd.Flags().Lookup("redis-db")) +} diff --git a/core/collector/appStorage.go b/core/collector/appStorage.go new file mode 100644 index 000000000..4ff8d6cab --- /dev/null +++ b/core/collector/appStorage.go @@ -0,0 +1,102 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" +) + +// App represents a stored application +type App struct { + EUI types.AppEUI + Key string + collection.Functions +} + +// AppStorage provides storage for applications +type AppStorage interface { + SetKey(eui types.AppEUI, key string) error + SetFunctions(eui types.AppEUI, functions *collection.Functions) error + Get(eui types.AppEUI) (*App, error) + GetAll() ([]*App, error) + Reset() error + Close() error +} + +type redisAppStorage struct { + client *redis.Client +} + +// ConnectRedisAppStorage connects to Redis using the specified options +func ConnectRedisAppStorage(opt *redis.Options) (AppStorage, error) { + client := redis.NewClient(opt) + _, err := client.Ping().Result() + if err != nil { + client.Close() + return nil, err + } + return &redisAppStorage{client}, nil +} + +func (s *redisAppStorage) SetKey(eui types.AppEUI, key string) error { + return s.client.HSet(eui.String(), "key", key).Err() +} + +func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.Functions) error { + return s.client.HMSetMap(eui.String(), map[string]string{ + "decoder": functions.Decoder, + "converter": functions.Converter, + "validator": functions.Validator, + }).Err() +} + +func (s *redisAppStorage) Get(eui types.AppEUI) (*App, error) { + m, err := s.client.HGetAllMap(eui.String()).Result() + if err == redis.Nil { + return nil, nil + } else if err != nil { + return nil, err + } + app := &App{ + EUI: eui, + Key: m["key"], + Functions: collection.Functions{ + Decoder: m["decoder"], + Converter: m["converter"], + Validator: m["validator"], + }, + } + return app, nil +} + +func (s *redisAppStorage) GetAll() ([]*App, error) { + euis, err := s.client.Keys("*").Result() + if err != nil { + return nil, err + } + apps := make([]*App, len(euis)) + for i, k := range euis { + eui, err := types.ParseAppEUI(k) + if err != nil { + return nil, err + } + app, err := s.Get(eui) + if err != nil { + return nil, err + } + apps[i] = app + } + return apps, nil +} + +func (s *redisAppStorage) Reset() error { + return s.client.FlushDb().Err() +} + +func (s *redisAppStorage) Close() error { + return s.client.Close() +} diff --git a/core/collector/appStorage_test.go b/core/collector/appStorage_test.go new file mode 100644 index 000000000..dcdc9168d --- /dev/null +++ b/core/collector/appStorage_test.go @@ -0,0 +1,97 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "testing" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func createStorage() AppStorage { + storage, err := ConnectRedisAppStorage(&redis.Options{Addr: "localhost:6379"}) + if err != nil { + panic(err) + } + return storage +} + +func TestConnect(t *testing.T) { + a := New(t) + + c, err := ConnectRedisAppStorage(&redis.Options{Addr: "localhost:6379"}) + a.So(err, ShouldBeNil) + defer c.Close() + + _, err = ConnectRedisAppStorage(&redis.Options{Addr: ""}) + a.So(err, ShouldNotBeNil) +} + +func TestSetKey(t *testing.T) { + a := New(t) + + eui, _ := types.ParseAppEUI("8000000000000001") + key := "key" + + storage := createStorage() + defer storage.Close() + defer storage.Reset() + + err := storage.SetKey(eui, key) + a.So(err, ShouldBeNil) + + app, err := storage.Get(eui) + a.So(err, ShouldBeNil) + a.So(app.EUI, ShouldEqual, eui) + a.So(app.Key, ShouldEqual, key) +} + +func TestGetAll(t *testing.T) { + a := New(t) + + eui1, _ := types.ParseAppEUI("8000000000000001") + eui2, _ := types.ParseAppEUI("8000000000000002") + + storage := createStorage() + defer storage.Close() + defer storage.Reset() + + err := storage.SetKey(eui1, "key1") + a.So(err, ShouldBeNil) + err = storage.SetKey(eui2, "key2") + a.So(err, ShouldBeNil) + + apps, err := storage.GetAll() + a.So(err, ShouldBeNil) + a.So(apps, ShouldHaveLength, 2) +} + +func TestSetFunctions(t *testing.T) { + a := New(t) + + eui, _ := types.ParseAppEUI("8000000000000001") + functions := &collection.Functions{ + Decoder: `function (payload) { return { size: payload.length; } }`, + Converter: `function (data) { return data; }`, + Validator: `function (data) { return data.size % 2 == 0; }`, + } + + storage := createStorage() + defer storage.Close() + defer storage.Reset() + + err := storage.SetFunctions(eui, functions) + a.So(err, ShouldBeNil) + + app, err := storage.Get(eui) + a.So(err, ShouldBeNil) + a.So(app.EUI, ShouldEqual, eui) + a.So(app.Decoder, ShouldEqual, functions.Decoder) + a.So(app.Converter, ShouldEqual, functions.Converter) + a.So(app.Validator, ShouldEqual, functions.Validator) +} diff --git a/core/collector/collector.go b/core/collector/collector.go new file mode 100644 index 000000000..cd42bd78b --- /dev/null +++ b/core/collector/collector.go @@ -0,0 +1,83 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "fmt" + "strings" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// Collector collects data from the Handler and stores it +type Collector interface { + Start() ([]collection.AppCollector, error) + Stop() +} + +type collector struct { + ctx log.Interface + appStorage AppStorage + broker string + apps []collection.AppCollector + dataStorage collection.DataStorage +} + +// NewCollector creates a new collector +func NewCollector(ctx log.Interface, appStorage AppStorage, broker string, dataStorage collection.DataStorage) Collector { + return &collector{ + ctx: ctx, + appStorage: appStorage, + broker: broker, + dataStorage: dataStorage, + } +} + +// StartError contains errors of starting applications +type StartError struct { + errors map[types.AppEUI]error +} + +func (e StartError) Error() string { + var s string + for eui, err := range e.errors { + s += fmt.Sprintf("%v: %v\n", eui, err) + } + return strings.TrimRight(s, "\n") +} + +func (c *collector) Start() ([]collection.AppCollector, error) { + apps, err := c.appStorage.GetAll() + if err != nil { + c.ctx.WithError(err).Error("Failed to get applications") + return nil, err + } + + startErrors := make(map[types.AppEUI]error) + for _, app := range apps { + ctx := c.ctx.WithField("appEUI", app.EUI) + ac := collection.NewMqttAppCollector(ctx, c.broker, app.EUI, app.Key, &app.Functions, c.dataStorage) + ctx.Info("Starting app collector") + if err := ac.Start(); err != nil { + ctx.WithError(err).Warn("Failed to start app collector; ignoring app") + startErrors[app.EUI] = err + continue + } + c.apps = append(c.apps, ac) + } + + if len(startErrors) > 0 { + return c.apps, StartError{startErrors} + } + return c.apps, nil +} + +func (c *collector) Stop() { + for _, app := range c.apps { + app.Stop() + } + c.apps = nil +} diff --git a/core/collector/collector_test.go b/core/collector/collector_test.go new file mode 100644 index 000000000..19ef003a6 --- /dev/null +++ b/core/collector/collector_test.go @@ -0,0 +1,52 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" + "gopkg.in/redis.v3" + + . "github.com/smartystreets/assertions" +) + +type mockStorage struct { +} + +func TestCollection(t *testing.T) { + a := New(t) + + appStorage, err := ConnectRedisAppStorage(&redis.Options{Addr: "localhost:6379"}) + if err != nil { + panic(err) + } + defer appStorage.Close() + defer appStorage.Reset() + + eui, _ := types.ParseAppEUI("8000000000000001") + err = appStorage.SetFunctions(eui, &collection.Functions{ + Decoder: `function (payload) { return { size: payload.length; } }`, + Converter: `function (data) { return data; }`, + Validator: `function (data) { return data.size % 2 == 0; }`, + }) + a.So(err, ShouldBeNil) + + collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}) + collectors, err := collector.Start() + defer collector.Stop() + a.So(err, ShouldBeNil) + a.So(collectors, ShouldHaveLength, 1) +} + +func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { + return nil +} + +func (s *mockStorage) Close() error { + return nil +} diff --git a/core/collector/influxdb/influxdb.go b/core/collector/influxdb/influxdb.go new file mode 100644 index 000000000..c0e815402 --- /dev/null +++ b/core/collector/influxdb/influxdb.go @@ -0,0 +1,54 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package influxdb + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/influxdata/influxdb/client/v2" +) + +type influxDBStorage struct { + client client.Client +} + +// NewInfluxDBStorage instantiates a new DataStorage for InfluxDB +func NewInfluxDBStorage(addr, username, password string) (collection.DataStorage, error) { + c, err := client.NewHTTPClient(client.HTTPConfig{ + Addr: addr, + Username: username, + Password: password, + }) + if err != nil { + return nil, err + } + return &influxDBStorage{c}, nil +} + +func (i *influxDBStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { + bp, err := client.NewBatchPoints(client.BatchPointsConfig{ + Database: "packets", + Precision: "us", + }) + if err != nil { + return err + } + + tags := map[string]string{ + "devEUI": devEUI.String(), + } + p, err := client.NewPoint(appEUI.String(), tags, fields, t) + if err != nil { + return err + } + + bp.AddPoint(p) + return i.client.Write(bp) +} + +func (i *influxDBStorage) Close() error { + return i.client.Close() +} From 5c67ef13b8dbdbd060b77c53c6b4671d7a3f7c5b Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 23:30:44 +0200 Subject: [PATCH 1378/2266] Removed obsolete package payload --- core/payload/functions.go | 105 --------------------------------- core/payload/functions_test.go | 97 ------------------------------ 2 files changed, 202 deletions(-) delete mode 100644 core/payload/functions.go delete mode 100644 core/payload/functions_test.go diff --git a/core/payload/functions.go b/core/payload/functions.go deleted file mode 100644 index c5629915a..000000000 --- a/core/payload/functions.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package payload - -import ( - "errors" - "fmt" - - "github.com/robertkrimen/otto" -) - -// Functions decodes, converts and validates payload using JavaScript functions -type Functions struct { - // Decoder is a JavaScript function that accepts the payload as byte array and - // returns an object containing the decoded values - Decoder string - // Converter is a JavaScript function that accepts the data as decoded by - // Decoder and returns an object containing the converted values - Converter string - // Validator is a JavaScript function that validates the data is converted by - // Converter and returns a boolean value indicating the validity of the data - Validator string -} - -// Decode decodes the payload using the Decoder function into a map -func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { - if f.Decoder == "" { - return nil, errors.New("Decoder function not set") - } - - vm := otto.New() - vm.Set("payload", payload) - value, err := vm.Run(fmt.Sprintf("(%s)(payload)", f.Decoder)) - if err != nil { - return nil, err - } - - if !value.IsObject() { - return nil, errors.New("Decoder does not return an object") - } - - v, _ := value.Export() - return v.(map[string]interface{}), nil -} - -// Convert converts the values in the specified map to a another map using the -// Converter function. If the Converter function is not set, this function -// returns the data as-is -func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{}, error) { - if f.Converter == "" { - return data, nil - } - - vm := otto.New() - vm.Set("data", data) - value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Converter)) - if err != nil { - return nil, err - } - - if !value.IsObject() { - return nil, errors.New("Converter does not return an object") - } - - v, _ := value.Export() - return v.(map[string]interface{}), nil -} - -// Validate validates the values in the specified map using the Validator -// function. If the Validator function is not set, this function returns true -func (f *Functions) Validate(data map[string]interface{}) (bool, error) { - if f.Validator == "" { - return true, nil - } - - vm := otto.New() - vm.Set("data", data) - value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Validator)) - if err != nil { - return false, err - } - - if !value.IsBoolean() { - return false, errors.New("Validator does not return a boolean") - } - - return value.ToBoolean() -} - -// Process decodes the specified payload, converts it and test the validity -func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error) { - decoded, err := f.Decode(payload) - if err != nil { - return nil, false, err - } - - converted, err := f.Convert(decoded) - if err != nil { - return nil, false, err - } - - valid, err := f.Validate(converted) - return converted, valid, err -} diff --git a/core/payload/functions_test.go b/core/payload/functions_test.go deleted file mode 100644 index e44818e21..000000000 --- a/core/payload/functions_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package payload - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestDecode(t *testing.T) { - a := New(t) - - functions := &Functions{ - Decoder: `function(payload) { - return { - value: (payload[0] << 8) | payload[1] - }; - }`, - } - payload := []byte{0x48, 0x65} - - m, err := functions.Decode(payload) - a.So(err, ShouldBeNil) - - size, ok := m["value"] - a.So(ok, ShouldBeTrue) - a.So(size, ShouldEqual, 18533) -} - -func TestConvert(t *testing.T) { - a := New(t) - - withFunction := &Functions{ - Converter: `function(data) { - return { - celcius: data.temperature * 2 - }; - }`, - } - data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) - a.So(err, ShouldBeNil) - a.So(data["celcius"], ShouldEqual, 22) - - withoutFunction := &Functions{} - data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) - a.So(err, ShouldBeNil) - a.So(data["temperature"], ShouldEqual, 11) -} - -func TestValidate(t *testing.T) { - a := New(t) - - withFunction := &Functions{ - Validator: `function(data) { - return data.temperature < 20; - }`, - } - valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeTrue) - valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeFalse) - - withoutFunction := &Functions{} - valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeTrue) -} - -func TestProcess(t *testing.T) { - a := New(t) - - functions := &Functions{ - Decoder: `function(payload) { - return { - temperature: payload[0], - humidity: payload[1] - } - }`, - Converter: `function(data) { - data.temperature /= 2; - return data; - }`, - Validator: `function(data) { - return data.humidity >= 0 && data.humidity <= 100; - }`, - } - - data, valid, err := functions.Process([]byte{40, 110}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeFalse) - a.So(data["temperature"], ShouldEqual, 20) - a.So(data["humidity"], ShouldEqual, 110) -} From dd606ca16dd3b598d28f147e6f86f8e6bef53c4c Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 2 May 2016 23:30:55 +0200 Subject: [PATCH 1379/2266] Consistent indenting --- core/collection/functions_test.go | 36 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/core/collection/functions_test.go b/core/collection/functions_test.go index 41a12262f..81db4b481 100644 --- a/core/collection/functions_test.go +++ b/core/collection/functions_test.go @@ -14,10 +14,10 @@ func TestDecode(t *testing.T) { functions := &Functions{ Decoder: `function(payload) { - return { - value: (payload[0] << 8) | payload[1] - }; - }`, + return { + value: (payload[0] << 8) | payload[1] + }; +}`, } payload := []byte{0x48, 0x65} @@ -34,10 +34,10 @@ func TestConvert(t *testing.T) { withFunction := &Functions{ Converter: `function(data) { - return { - celcius: data.temperature * 2 - }; - }`, + return { + celcius: data.temperature * 2 + }; +}`, } data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) a.So(err, ShouldBeNil) @@ -75,18 +75,18 @@ func TestProcess(t *testing.T) { functions := &Functions{ Decoder: `function(payload) { - return { - temperature: payload[0], - humidity: payload[1] - } - }`, + return { + temperature: payload[0], + humidity: payload[1] + } +}`, Converter: `function(data) { - data.temperature /= 2; - return data; - }`, + data.temperature /= 2; + return data; +}`, Validator: `function(data) { - return data.humidity >= 0 && data.humidity <= 100; - }`, + return data.humidity >= 0 && data.humidity <= 100; +}`, } data, valid, err := functions.Process([]byte{40, 110}) From 2eabddf1dacd1cb53762efd27f8542a502cf97ae Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 3 May 2016 17:24:06 +0200 Subject: [PATCH 1380/2266] Return after error reporting --- core/collection/appCollector.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go index 6254e7e7c..10658468a 100644 --- a/core/collection/appCollector.go +++ b/core/collection/appCollector.go @@ -80,6 +80,7 @@ func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, dev err = c.storage.Save(appEUI, devEUI, t, fields) if err != nil { ctx.WithError(err).Error("Failed to save data") + return } ctx.Debug("Saved uplink packet in store") } From 8fc4bdbbda5203b57aad448d703bebf3a734a37e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 7 May 2016 00:11:47 +0200 Subject: [PATCH 1381/2266] Added update-deps --- Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index e0e1b78db..587acbcdc 100644 --- a/Makefile +++ b/Makefile @@ -29,13 +29,16 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps test-deps dev-deps proto test fmt vet cover build docker package +.PHONY: all clean deps update-deps test-deps dev-deps proto test fmt vet cover build docker package all: clean deps build package deps: $(GOCMD) get -d -v $(DEPS) +update-deps: + $(GOCMD) get -u -d -v $(DEPS) + test-deps: $(GOCMD) get -d -v $(TEST_DEPS) From c18d0f75f34966e64cf9bacb6476bf9f4787ddc8 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 7 May 2016 00:13:44 +0200 Subject: [PATCH 1382/2266] Handler processes payload using fields adapter --- cmd/collector.go | 6 +- cmd/handler.go | 25 +++- core/adapters/fields/adapter.go | 93 ++++++++++++++ core/adapters/fields/adapter_test.go | 100 +++++++++++++++ core/adapters/fields/appStorage.go | 73 +++++++++++ core/adapters/fields/appStorage_test.go | 55 +++++++++ core/adapters/mqtt/adapter.go | 157 +++++------------------- core/adapters/mqtt/adapter_test.go | 134 ++++---------------- core/application.pb.go | 68 ++++++++++ core/collection/appCollector.go | 36 ++---- core/collection/appCollector_test.go | 15 +-- core/collector/appStorage.go | 25 +--- core/collector/appStorage_test.go | 34 +---- core/collector/collector.go | 2 +- core/collector/collector_test.go | 52 -------- core/core.pb.go | 68 ---------- core/definitions.go | 11 +- mqtt/client.go | 36 ++---- mqtt/client_test.go | 46 +++---- 19 files changed, 529 insertions(+), 507 deletions(-) create mode 100644 core/adapters/fields/adapter.go create mode 100644 core/adapters/fields/adapter_test.go create mode 100644 core/adapters/fields/appStorage.go create mode 100644 core/adapters/fields/appStorage_test.go delete mode 100644 core/collector/collector_test.go diff --git a/cmd/collector.go b/cmd/collector.go index 058cdbc73..7aa6d4342 100644 --- a/cmd/collector.go +++ b/cmd/collector.go @@ -11,7 +11,6 @@ import ( "github.com/TheThingsNetwork/ttn/core/collector/influxdb" "github.com/spf13/cobra" "github.com/spf13/viper" - "gopkg.in/redis.v3" ) // collectorCmd represents the collector command @@ -26,10 +25,7 @@ configured applications. Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - appStorage, err := collector.ConnectRedisAppStorage(&redis.Options{ - Addr: viper.GetString("collector.redis-addr"), - DB: int64(viper.GetInt("collector.redis-db")), - }) + appStorage, err := collector.ConnectRedis(viper.GetString("collector.redis-addr"), int64(viper.GetInt("collector.redis-db"))) if err != nil { ctx.WithError(err).Fatal("Failed to connect to Redis") } diff --git a/cmd/handler.go b/cmd/handler.go index 867e23572..4cfd2b70c 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/TheThingsNetwork/ttn/core/adapters/fields" "github.com/TheThingsNetwork/ttn/core/adapters/http" handlerMQTT "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" "github.com/TheThingsNetwork/ttn/core/components/broker" @@ -122,9 +123,18 @@ The Handler is the bridge between The Things Network and applications. ctx.WithError(err).Fatal("Could not connect to MQTT") } - appAdapter := handlerMQTT.NewAdapter( - ctx.WithField("adapter", "app-adapter"), - mqttClient, + storage, err := fields.ConnectRedis(viper.GetString("handler.redis-addr"), int64(viper.GetInt("handler.redis-db"))) + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Redis") + } + + fieldsAdapter := fields.NewAdapter( + ctx.WithField("adapter", "fields-adapter"), + storage, + handlerMQTT.NewAdapter( + ctx.WithField("adapter", "mqtt-adapter"), + mqttClient, + ), ) // Handler @@ -134,7 +144,7 @@ The Handler is the bridge between The Things Network and applications. DevStorage: devicesDB, PktStorage: packetsDB, Broker: brokerClient, - AppAdapter: appAdapter, + AppAdapter: fieldsAdapter, }, handler.Options{ PublicNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), @@ -143,7 +153,7 @@ The Handler is the bridge between The Things Network and applications. }, ) - appAdapter.SubscribeDownlink(handler) + fieldsAdapter.SubscribeDownlink(handler) if err := handler.Start(); err != nil { ctx.WithError(err).Fatal("Handler has fallen...") @@ -183,6 +193,11 @@ func init() { viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) + handlerCmd.Flags().String("redis-addr", "localhost:6379", "The address of Redis") + handlerCmd.Flags().Int64("redis-db", 0, "The database of Redis") + viper.BindPFlag("handler.redis-addr", handlerCmd.Flags().Lookup("redis-addr")) + viper.BindPFlag("handler.redis-db", handlerCmd.Flags().Lookup("redis-db")) + handlerCmd.Flags().String("ttn-broker", "localhost:1781", "The address of the TTN broker (downlink)") viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) } diff --git a/core/adapters/fields/adapter.go b/core/adapters/fields/adapter.go new file mode 100644 index 000000000..dd66c7e99 --- /dev/null +++ b/core/adapters/fields/adapter.go @@ -0,0 +1,93 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package fields + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +// Adapter represents the interface of an application +type Adapter interface { + core.AppClient + SubscribeDownlink(handler core.HandlerServer) error +} + +type adapter struct { + ctx log.Interface + storage AppStorage + mqtt mqtt.Adapter +} + +// NewAdapter returns a new adapter that processes binary payload to fields +func NewAdapter(ctx log.Interface, storage AppStorage, next mqtt.Adapter) Adapter { + return &adapter{ctx, storage, next} +} + +func (s *adapter) HandleData(context context.Context, in *core.DataAppReq, opt ...grpc.CallOption) (*core.DataAppRes, error) { + var appEUI types.AppEUI + appEUI.Unmarshal(in.AppEUI) + var devEUI types.DevEUI + devEUI.Unmarshal(in.DevEUI) + ctx := s.ctx.WithFields(&log.Fields{"appEUI": appEUI, "devEUI": devEUI}) + + req := core.DataUpAppReq{ + Payload: in.Payload, + Metadata: core.ProtoMetaToAppMeta(in.Metadata...), + FPort: uint8(in.FPort), + FCnt: in.FCnt, + DevEUI: devEUI.String(), + } + + functions, err := s.storage.GetFunctions(appEUI) + if err != nil { + ctx.WithError(err).Warn("Failed to get functions") + // If we can't get the functions here, just publish it anyway without fields + return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) + } + + fields, valid, err := functions.Process(in.Payload) + if err != nil { + // If there were errors processing the payload, just publish it anyway + // without fields + ctx.WithError(err).Warn("Failed to process payload") + return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) + } + + if !valid { + // If the payload has been processed successfully but is not valid, it should + // not be published + ctx.Info("The processed payload is not valid") + return new(core.DataAppRes), errors.New(errors.Operational, "The processed payload is not valid") + } + + req.Fields = fields + if err := s.mqtt.PublishUplink(appEUI, devEUI, req); err != nil { + return new(core.DataAppRes), errors.New(errors.Operational, "Failed to publish data") + } + + return new(core.DataAppRes), nil +} + +func (s *adapter) HandleJoin(context context.Context, in *core.JoinAppReq, opt ...grpc.CallOption) (*core.JoinAppRes, error) { + var appEUI types.AppEUI + appEUI.Unmarshal(in.AppEUI) + var devEUI types.DevEUI + devEUI.Unmarshal(in.DevEUI) + + req := core.OTAAAppReq{ + Metadata: core.ProtoMetaToAppMeta(in.Metadata...), + } + + return new(core.JoinAppRes), s.mqtt.PublishActivation(appEUI, devEUI, req) +} + +func (s *adapter) SubscribeDownlink(handler core.HandlerServer) error { + return s.mqtt.SubscribeDownlink(handler) +} diff --git a/core/adapters/fields/adapter_test.go b/core/adapters/fields/adapter_test.go new file mode 100644 index 000000000..996fcbf1e --- /dev/null +++ b/core/adapters/fields/adapter_test.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package fields + +import ( + "testing" + + "golang.org/x/net/context" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +type mockPublisher struct { + appEUI *types.AppEUI + devEUI *types.DevEUI + uplinkReq *core.DataUpAppReq + activationReq *core.OTAAAppReq +} + +func TestHandleData(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestHandleData") + storage, err := ConnectRedis("localhost:6379", 0) + a.So(err, ShouldBeNil) + defer storage.Close() + defer storage.Reset() + + publisher := &mockPublisher{} + adapter := NewAdapter(ctx, storage, publisher) + + appEUI, _ := types.ParseAppEUI("0102030405060708") + devEUI, _ := types.ParseDevEUI("00000000AABBCCDD") + + req := &core.DataAppReq{ + AppEUI: appEUI.Bytes(), + DevEUI: devEUI.Bytes(), + FPort: 1, + Payload: []byte{0x08, 0x70}, + } + + // Normal flow + functions := &collection.Functions{ + Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, + } + err = storage.SetFunctions(appEUI, functions) + a.So(err, ShouldBeNil) + res, err := adapter.HandleData(context.Background(), req) + a.So(res, ShouldEqual, new(core.DataAppRes)) + a.So(err, ShouldBeNil) + a.So(publisher.appEUI, ShouldResemble, &appEUI) + a.So(publisher.devEUI, ShouldResemble, &devEUI) + a.So(publisher.uplinkReq, ShouldNotBeNil) + a.So(publisher.uplinkReq.Fields, ShouldResemble, map[string]interface{}{ + "temperature": 21.6, + }) + + // Invalidate data + functions.Validator = `function(data) { return false; }` + err = storage.SetFunctions(appEUI, functions) + a.So(err, ShouldBeNil) + res, err = adapter.HandleData(context.Background(), req) + a.So(res, ShouldEqual, new(core.DataAppRes)) + a.So(err, ShouldNotBeNil) + + // Function error + functions.Converter = `function(data) { throw "expected"; }` + err = storage.SetFunctions(appEUI, functions) + a.So(err, ShouldBeNil) + res, err = adapter.HandleData(context.Background(), req) + a.So(res, ShouldEqual, new(core.DataAppRes)) + a.So(err, ShouldBeNil) + a.So(publisher.appEUI, ShouldResemble, &appEUI) + a.So(publisher.devEUI, ShouldResemble, &devEUI) + a.So(publisher.uplinkReq, ShouldNotBeNil) + a.So(publisher.uplinkReq.Fields, ShouldResemble, *new(map[string]interface{})) +} + +func (p *mockPublisher) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error { + p.appEUI = &appEUI + p.devEUI = &devEUI + p.uplinkReq = &req + return nil +} + +func (p *mockPublisher) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) error { + p.appEUI = &appEUI + p.devEUI = &devEUI + p.activationReq = &req + return nil +} + +func (p *mockPublisher) SubscribeDownlink(handler core.HandlerServer) error { + return nil +} diff --git a/core/adapters/fields/appStorage.go b/core/adapters/fields/appStorage.go new file mode 100644 index 000000000..0ad050d65 --- /dev/null +++ b/core/adapters/fields/appStorage.go @@ -0,0 +1,73 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package fields + +import ( + "fmt" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" +) + +// AppStorage provides storage for applications +type AppStorage interface { + SetFunctions(eui types.AppEUI, functions *collection.Functions) error + GetFunctions(eui types.AppEUI) (*collection.Functions, error) + Reset() error + Close() error +} + +type redisAppStorage struct { + client *redis.Client +} + +// ConnectRedis connects to Redis using the specified options +func ConnectRedis(addr string, db int64) (AppStorage, error) { + client := redis.NewClient(&redis.Options{ + Addr: addr, + DB: db, + }) + _, err := client.Ping().Result() + if err != nil { + client.Close() + return nil, err + } + return &redisAppStorage{client}, nil +} + +func (s *redisAppStorage) makeKey(eui types.AppEUI) string { + return fmt.Sprintf("app:%s", eui.String()) +} + +func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.Functions) error { + return s.client.HMSetMap(s.makeKey(eui), map[string]string{ + "decoder": functions.Decoder, + "converter": functions.Converter, + "validator": functions.Validator, + }).Err() +} + +func (s *redisAppStorage) GetFunctions(eui types.AppEUI) (*collection.Functions, error) { + m, err := s.client.HGetAllMap(s.makeKey(eui)).Result() + if err == redis.Nil { + return nil, nil + } else if err != nil { + return nil, err + } + return &collection.Functions{ + Decoder: m["decoder"], + Converter: m["converter"], + Validator: m["validator"], + }, nil +} + +func (s *redisAppStorage) Reset() error { + return s.client.FlushDb().Err() +} + +func (s *redisAppStorage) Close() error { + return s.client.Close() +} diff --git a/core/adapters/fields/appStorage_test.go b/core/adapters/fields/appStorage_test.go new file mode 100644 index 000000000..1781cdba6 --- /dev/null +++ b/core/adapters/fields/appStorage_test.go @@ -0,0 +1,55 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package fields + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func createStorage() AppStorage { + storage, err := ConnectRedis("localhost:6379", 0) + if err != nil { + panic(err) + } + return storage +} + +func TestConnect(t *testing.T) { + a := New(t) + + c, err := ConnectRedis("localhost:6379", 0) + a.So(err, ShouldBeNil) + defer c.Close() + + _, err = ConnectRedis("", 0) + a.So(err, ShouldNotBeNil) +} + +func TestSetFunctions(t *testing.T) { + a := New(t) + + eui, _ := types.ParseAppEUI("8000000000000001") + functions := &collection.Functions{ + Decoder: `function (payload) { return { size: payload.length; } }`, + Converter: `function (data) { return data; }`, + Validator: `function (data) { return data.size % 2 == 0; }`, + } + + storage := createStorage() + defer storage.Close() + defer storage.Reset() + + err := storage.SetFunctions(eui, functions) + a.So(err, ShouldBeNil) + + fetchedFunctions, err := storage.GetFunctions(eui) + a.So(err, ShouldBeNil) + a.So(fetchedFunctions.Decoder, ShouldEqual, functions.Decoder) + a.So(fetchedFunctions.Converter, ShouldEqual, functions.Converter) + a.So(fetchedFunctions.Validator, ShouldEqual, functions.Validator) +} diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go index 0ea67094b..1b4782468 100644 --- a/core/adapters/mqtt/adapter.go +++ b/core/adapters/mqtt/adapter.go @@ -4,24 +4,22 @@ package mqtt import ( - "fmt" "time" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" "github.com/apex/log" "golang.org/x/net/context" - "google.golang.org/grpc" ) -const mqttTimeout = 20 * time.Millisecond +const publishTimeout = 20 * time.Millisecond -// Adapter defines a public interface for the mqtt adapter +// Adapter represents the interface of an application type Adapter interface { - core.AppClient + PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error + PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) error SubscribeDownlink(handler core.HandlerServer) error } @@ -30,155 +28,73 @@ type defaultAdapter struct { client ttnMQTT.Client } -// HandleData implements the core.AppClient interface -func (a *defaultAdapter) HandleData(_ context.Context, req *core.DataAppReq, _ ...grpc.CallOption) (*core.DataAppRes, error) { - if err := validateData(req); err != nil { - return new(core.DataAppRes), errors.New(errors.Structural, err) - } - - dataUp := core.DataUpAppReq{ - Payload: req.Payload, - Metadata: core.ProtoMetaToAppMeta(req.Metadata...), - FPort: uint8(req.FPort), - FCnt: req.FCnt, - DevEUI: fmt.Sprintf("%X", req.DevEUI), - } - - if a.ctx != nil { - a.ctx.WithFields(log.Fields{ - "AppEUI": req.AppEUI, - "DevEUI": req.DevEUI, - }).Debug("Publishing Uplink") - } +// NewAdapter returns a new MQTT handler adapter +func NewAdapter(ctx log.Interface, client ttnMQTT.Client) Adapter { + return &defaultAdapter{ctx, client} +} - // Type conversions for MQTT (we'll refactor the adapter later) - var appEUI types.AppEUI - appEUI.Unmarshal(req.AppEUI) - var devEUI types.DevEUI - devEUI.Unmarshal(req.DevEUI) +// HandleData implements the core.AppClient interface +func (a *defaultAdapter) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error { + ctx := a.ctx.WithFields(log.Fields{ + "AppEUI": appEUI, + "DevEUI": devEUI, + }) + ctx.Debug("Publishing Uplink") - token := a.client.PublishUplink(appEUI, devEUI, dataUp) - if token.WaitTimeout(mqttTimeout) { + token := a.client.PublishUplink(appEUI, devEUI, req) + if token.WaitTimeout(publishTimeout) { // token did not timeout: just return if token.Error() != nil { - return new(core.DataAppRes), errors.New(errors.Structural, token.Error()) + return errors.New(errors.Structural, token.Error()) } } else { // token did timeout: wait for it in background and just return go func() { token.Wait() if token.Error() != nil { - if a.ctx != nil { - a.ctx.WithError(token.Error()).Warn("Could not publish uplink") - } + ctx.WithError(token.Error()).Warn("Could not publish uplink") } }() } - return new(core.DataAppRes), nil -} - -func validateData(req *core.DataAppReq) error { - var err error - switch { - case req == nil: - err = errors.New(errors.Structural, "Received Nil Application Request") - case len(req.Payload) == 0: - err = errors.New(errors.Structural, "Invalid Packet Payload") - case len(req.DevEUI) != 8: - err = errors.New(errors.Structural, "Invalid Device EUI") - case len(req.AppEUI) != 8: - err = errors.New(errors.Structural, "Invalid Application EUI") - case req.Metadata == nil: - err = errors.New(errors.Structural, "Missing Mandatory Metadata") - } - - if err != nil { - stats.MarkMeter("mqtt_adapter.uplink.invalid") - return err - } - return nil } -// HandleJoin implements the core.AppClient interface -func (a *defaultAdapter) HandleJoin(_ context.Context, req *core.JoinAppReq, _ ...grpc.CallOption) (*core.JoinAppRes, error) { - if err := validateJoin(req); err != nil { - return new(core.JoinAppRes), errors.New(errors.Structural, err) - } - - otaa := core.OTAAAppReq{ - Metadata: core.ProtoMetaToAppMeta(req.Metadata...), - } - - if a.ctx != nil { - a.ctx.WithFields(log.Fields{ - "AppEUI": req.AppEUI, - "DevEUI": req.DevEUI, - }).Debug("Publishing Activation") - } - - // Type conversions for MQTT (we'll refactor the adapter later) - var appEUI types.AppEUI - appEUI.Unmarshal(req.AppEUI) - var devEUI types.DevEUI - devEUI.Unmarshal(req.DevEUI) +func (a *defaultAdapter) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) error { + ctx := a.ctx.WithFields(log.Fields{ + "AppEUI": appEUI, + "DevEUI": devEUI, + }) + ctx.Debug("Publishing Activation") - token := a.client.PublishActivation(appEUI, devEUI, otaa) - if token.WaitTimeout(mqttTimeout) { + token := a.client.PublishActivation(appEUI, devEUI, req) + if token.WaitTimeout(publishTimeout) { // token did not timeout: just return if token.Error() != nil { - return new(core.JoinAppRes), errors.New(errors.Structural, token.Error()) + return errors.New(errors.Structural, token.Error()) } } else { // token did timeout: wait for it in background and just return go func() { token.Wait() if token.Error() != nil { - if a.ctx != nil { - a.ctx.WithError(token.Error()).Warn("Could not publish activation") - } + ctx.WithError(token.Error()).Warn("Could not publish activation") } }() } - return new(core.JoinAppRes), nil -} - -func validateJoin(req *core.JoinAppReq) error { - var err error - switch { - case req == nil: - err = errors.New(errors.Structural, "Received Nil Application Request") - case len(req.DevEUI) != 8: - err = errors.New(errors.Structural, "Invalid Device EUI") - case len(req.AppEUI) != 8: - err = errors.New(errors.Structural, "Invalid Application EUI") - case req.Metadata == nil: - err = errors.New(errors.Structural, "Missing Mandatory Metadata") - } - - if err != nil { - stats.MarkMeter("mqtt_adapter.join.invalid") - return err - } - return nil } func (a *defaultAdapter) SubscribeDownlink(handler core.HandlerServer) error { token := a.client.SubscribeDownlink(func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { if len(req.Payload) == 0 { - if a.ctx != nil { - a.ctx.Debug("Skipping empty downlink") - } + a.ctx.Debug("Skipping empty downlink") return } - if a.ctx != nil { - a.ctx.WithFields(log.Fields{ - "AppEUI": appEUI, - "DevEUI": devEUI, - }).Debug("Receiving Downlink") - } + a.ctx.WithFields(log.Fields{ + "AppEUI": appEUI, + "DevEUI": devEUI, + }).Debug("Receiving Downlink") // Convert it to an handler downlink handler.HandleDataDown(context.Background(), &core.DataDownHandlerReq{ @@ -192,8 +108,3 @@ func (a *defaultAdapter) SubscribeDownlink(handler core.HandlerServer) error { token.Wait() return token.Error() } - -// NewAdapter returns a new MQTT handler adapter -func NewAdapter(ctx log.Interface, client ttnMQTT.Client) Adapter { - return &defaultAdapter{ctx, client} -} diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go index bd746d984..c53369bab 100644 --- a/core/adapters/mqtt/adapter_test.go +++ b/core/adapters/mqtt/adapter_test.go @@ -8,8 +8,6 @@ import ( "testing" "time" - "golang.org/x/net/context" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/mocks" "github.com/TheThingsNetwork/ttn/core/types" @@ -27,23 +25,23 @@ func TestNewAdapter(t *testing.T) { a.So(adapter.(*defaultAdapter).client, ShouldEqual, client) } -func TestHandleData(t *testing.T) { +func TestPublishUplink(t *testing.T) { a := New(t) - ctx := GetLogger(t, "TestHandleData") + ctx := GetLogger(t, "TestPublishUplink") client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") client.Connect() adapter := NewAdapter(ctx, client) - eui := types.EUI64{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + appEUI := types.AppEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - req := core.DataAppReq{ + req := core.DataUpAppReq{ Payload: []byte{0x01, 0x02}, - Metadata: []*core.Metadata{ - &core.Metadata{DataRate: "SF7BW125"}, + Metadata: []core.AppMetadata{ + core.AppMetadata{DataRate: "SF7BW125"}, }, - AppEUI: eui.Bytes(), - DevEUI: eui.Bytes(), + DevEUI: devEUI.String(), FPort: 14, FCnt: 200, } @@ -51,9 +49,9 @@ func TestHandleData(t *testing.T) { var wg sync.WaitGroup wg.Add(1) - client.SubscribeDeviceUplink(types.AppEUI(eui), types.DevEUI(eui), func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, dataUp core.DataUpAppReq) { - a.So(types.EUI64(appEUI), ShouldEqual, eui) - a.So(types.EUI64(devEUI), ShouldEqual, eui) + client.SubscribeDeviceUplink(appEUI, devEUI, func(client ttnMQTT.Client, rappEUI types.AppEUI, rdevEUI types.DevEUI, dataUp core.DataUpAppReq) { + a.So(rappEUI, ShouldEqual, appEUI) + a.So(rdevEUI, ShouldEqual, devEUI) a.So(dataUp.FPort, ShouldEqual, 14) a.So(dataUp.FCnt, ShouldEqual, 200) a.So(dataUp.Payload, ShouldResemble, []byte{0x01, 0x02}) @@ -61,60 +59,10 @@ func TestHandleData(t *testing.T) { wg.Done() }).Wait() - res, err := adapter.HandleData(context.Background(), &req) + err := adapter.PublishUplink(appEUI, devEUI, req) a.So(err, ShouldBeNil) - a.So(res, ShouldResemble, new(core.DataAppRes)) wg.Wait() - -} - -func TestHandleInvalidData(t *testing.T) { - a := New(t) - client := ttnMQTT.NewClient(nil, "test", "", "", "tcp://localhost:1883") - adapter := NewAdapter(nil, client) - - // nil Request - _, err := adapter.HandleData(context.Background(), nil) - a.So(err, ShouldNotBeNil) - - // Invalid Payload - _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ - Payload: []byte{}, - }) - a.So(err, ShouldNotBeNil) - - // Invalid DevEUI - _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ - Payload: []byte{0x00}, - DevEUI: []byte{}, - }) - a.So(err, ShouldNotBeNil) - - // Invalid AppEUI - _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ - Payload: []byte{0x00}, - DevEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - AppEUI: []byte{}, - }) - a.So(err, ShouldNotBeNil) - - // Missing Metadata - _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ - Payload: []byte{0x00}, - DevEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - AppEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - }) - a.So(err, ShouldNotBeNil) - - // Not Connected - _, err = adapter.HandleData(context.Background(), &core.DataAppReq{ - Payload: []byte{0x00}, - DevEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - AppEUI: []byte{0x0b, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - Metadata: []*core.Metadata{}, - }) - a.So(err, ShouldNotBeNil) } func TestHandleJoin(t *testing.T) { @@ -125,71 +73,31 @@ func TestHandleJoin(t *testing.T) { adapter := NewAdapter(ctx, client) - eui := types.EUI64{0x0a, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + appEUI := types.AppEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} + devEUI := types.DevEUI{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - req := core.JoinAppReq{ - AppEUI: eui.Bytes(), - DevEUI: eui.Bytes(), - Metadata: []*core.Metadata{ - &core.Metadata{DataRate: "SF7BW125"}, + req := core.OTAAAppReq{ + Metadata: []core.AppMetadata{ + core.AppMetadata{DataRate: "SF7BW125"}, }, } var wg sync.WaitGroup wg.Add(1) - client.SubscribeDeviceActivations(types.AppEUI(eui), types.DevEUI(eui), func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, activation core.OTAAAppReq) { - a.So(types.EUI64(appEUI), ShouldResemble, eui) - a.So(types.EUI64(devEUI), ShouldResemble, eui) + client.SubscribeDeviceActivations(appEUI, devEUI, func(client ttnMQTT.Client, rappEUI types.AppEUI, rdevEUI types.DevEUI, activation core.OTAAAppReq) { + a.So(rappEUI, ShouldResemble, appEUI) + a.So(rdevEUI, ShouldResemble, devEUI) a.So(activation.Metadata[0].DataRate, ShouldEqual, "SF7BW125") wg.Done() }).Wait() - res, err := adapter.HandleJoin(context.Background(), &req) + err := adapter.PublishActivation(appEUI, devEUI, req) a.So(err, ShouldBeNil) - a.So(res, ShouldResemble, new(core.JoinAppRes)) wg.Wait() } -func TestHandleInvalidJoin(t *testing.T) { - a := New(t) - client := ttnMQTT.NewClient(nil, "test", "", "", "tcp://localhost:1883") - adapter := NewAdapter(nil, client) - - // nil Request - _, err := adapter.HandleJoin(context.Background(), nil) - a.So(err, ShouldNotBeNil) - - // Invalid DevEUI - _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ - DevEUI: []byte{}, - }) - a.So(err, ShouldNotBeNil) - - // Invalid AppEUI - _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ - DevEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - AppEUI: []byte{}, - }) - a.So(err, ShouldNotBeNil) - - // Missing Metadata - _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ - DevEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - AppEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - }) - a.So(err, ShouldNotBeNil) - - // Not Connected - _, err = adapter.HandleJoin(context.Background(), &core.JoinAppReq{ - DevEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - AppEUI: []byte{0x0c, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - Metadata: []*core.Metadata{}, - }) - a.So(err, ShouldNotBeNil) -} - func TestSubscribeDownlink(t *testing.T) { a := New(t) ctx := GetLogger(t, "TestSubscribeDownlink") diff --git a/core/application.pb.go b/core/application.pb.go index 54caaaa57..611096269 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -2,6 +2,70 @@ // source: application.proto // DO NOT EDIT! +/* + Package core is a generated protocol buffer package. + + It is generated from these files: + application.proto + broker.proto + broker_manager.proto + core.proto + handler.proto + handler_manager.proto + lorawan.proto + router.proto + + It has these top-level messages: + DataAppReq + DataAppRes + JoinAppReq + JoinAppRes + DataBrokerReq + DataBrokerRes + JoinBrokerReq + JoinBrokerRes + ValidateOTAABrokerReq + ValidateOTAABrokerRes + UpsertABPBrokerReq + UpsertABPBrokerRes + BrokerDevice + ValidateTokenBrokerReq + ValidateTokenBrokerRes + Metadata + StatsMetadata + DataUpHandlerReq + DataUpHandlerRes + DataDownHandlerReq + DataDownHandlerRes + JoinHandlerReq + JoinHandlerRes + UpsertOTAAHandlerReq + UpsertOTAAHandlerRes + UpsertABPHandlerReq + UpsertABPHandlerRes + ListDevicesHandlerReq + ListDevicesHandlerRes + HandlerABPDevice + HandlerOTAADevice + GetDefaultDeviceReq + GetDefaultDeviceRes + SetDefaultDeviceReq + SetDefaultDeviceRes + LoRaWANData + LoRaWANMHDR + LoRaWANMACPayload + LoRaWANFHDR + LoRaWANFCtrl + LoRaWANJoinRequest + LoRaWANJoinAccept + LoRaWANDLSettings + DataRouterReq + DataRouterRes + StatsReq + StatsRes + JoinRouterReq + JoinRouterRes +*/ package core import proto "github.com/golang/protobuf/proto" @@ -20,6 +84,10 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + type DataAppReq struct { AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go index 10658468a..846c7bdd0 100644 --- a/core/collection/appCollector.go +++ b/core/collection/appCollector.go @@ -20,23 +20,21 @@ type AppCollector interface { } type appCollector struct { - ctx log.Interface - eui types.AppEUI - functions *Functions - broker string - client mqtt.Client - storage DataStorage + ctx log.Interface + eui types.AppEUI + broker string + client mqtt.Client + storage DataStorage } // NewMqttAppCollector instantiates a new AppCollector instance using MQTT -func NewMqttAppCollector(ctx log.Interface, broker string, eui types.AppEUI, key string, functions *Functions, storage DataStorage) AppCollector { +func NewMqttAppCollector(ctx log.Interface, broker string, eui types.AppEUI, key string, storage DataStorage) AppCollector { return &appCollector{ - ctx: ctx, - eui: eui, - functions: functions, - broker: broker, - client: mqtt.NewClient(ctx, "collector", eui.String(), key, fmt.Sprintf("tcp://%s", broker)), - storage: storage, + ctx: ctx, + eui: eui, + broker: broker, + client: mqtt.NewClient(ctx, "collector", eui.String(), key, fmt.Sprintf("tcp://%s", broker)), + storage: storage, } } @@ -61,23 +59,13 @@ func (c *appCollector) Stop() { func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { ctx := c.ctx.WithField("devEUI", devEUI) - fields, valid, err := c.functions.Process(req.Payload) - if err != nil { - ctx.WithError(err).Error("Failed to process payload") - return - } - if !valid { - ctx.Warn("The payload is not valid") - return - } - t, err := time.Parse(time.RFC3339, req.Metadata[0].ServerTime) if err != nil { ctx.WithError(err).Warnf("Invalid time: %v", req.Metadata[0].ServerTime) return } - err = c.storage.Save(appEUI, devEUI, t, fields) + err = c.storage.Save(appEUI, devEUI, t, req.Fields) if err != nil { ctx.WithError(err).Error("Failed to save data") return diff --git a/core/collection/appCollector_test.go b/core/collection/appCollector_test.go index de127c526..624c88ad9 100644 --- a/core/collection/appCollector_test.go +++ b/core/collection/appCollector_test.go @@ -17,8 +17,7 @@ import ( ) type mockStorage struct { - entry chan *mockStorageEntry - entries []*mockStorageEntry + entry chan *mockStorageEntry } type mockStorageEntry struct { @@ -28,12 +27,7 @@ type mockStorageEntry struct { func createTestCollector(ctx log.Interface, storage DataStorage) AppCollector { eui, _ := types.ParseAppEUI("8000000000000001") - functions := &Functions{ - Decoder: `function(payload) { return { size: payload.length } }`, - Converter: `function(data) { return data; }`, - Validator: `function(data) { return data.size > 0; }`, - } - return NewMqttAppCollector(ctx, "localhost:1883", eui, "", functions, storage) + return NewMqttAppCollector(ctx, "localhost:1883", eui, "", storage) } func TestStart(t *testing.T) { @@ -76,6 +70,7 @@ func TestCollect(t *testing.T) { FPort: 1, Metadata: []core.AppMetadata{core.AppMetadata{ServerTime: time.Now().Format(time.RFC3339)}}, Payload: []byte{0x1, 0x2, 0x3}, + Fields: map[string]interface{}{"size": 3}, } if token := client.PublishUplink(appEUI, devEUI, req); token.Wait() && token.Error() != nil { panic(token.Error()) @@ -95,9 +90,7 @@ func TestCollect(t *testing.T) { } func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { - entry := &mockStorageEntry{devEUI, fields} - s.entries = append(s.entries, entry) - s.entry <- entry + s.entry <- &mockStorageEntry{devEUI, fields} return nil } diff --git a/core/collector/appStorage.go b/core/collector/appStorage.go index 4ff8d6cab..c3cd96542 100644 --- a/core/collector/appStorage.go +++ b/core/collector/appStorage.go @@ -6,7 +6,6 @@ package collector import ( "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/core/collection" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -14,13 +13,11 @@ import ( type App struct { EUI types.AppEUI Key string - collection.Functions } // AppStorage provides storage for applications type AppStorage interface { SetKey(eui types.AppEUI, key string) error - SetFunctions(eui types.AppEUI, functions *collection.Functions) error Get(eui types.AppEUI) (*App, error) GetAll() ([]*App, error) Reset() error @@ -31,9 +28,12 @@ type redisAppStorage struct { client *redis.Client } -// ConnectRedisAppStorage connects to Redis using the specified options -func ConnectRedisAppStorage(opt *redis.Options) (AppStorage, error) { - client := redis.NewClient(opt) +// ConnectRedis connects to Redis using the specified options +func ConnectRedis(addr string, db int64) (AppStorage, error) { + client := redis.NewClient(&redis.Options{ + Addr: addr, + DB: db, + }) _, err := client.Ping().Result() if err != nil { client.Close() @@ -46,14 +46,6 @@ func (s *redisAppStorage) SetKey(eui types.AppEUI, key string) error { return s.client.HSet(eui.String(), "key", key).Err() } -func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.Functions) error { - return s.client.HMSetMap(eui.String(), map[string]string{ - "decoder": functions.Decoder, - "converter": functions.Converter, - "validator": functions.Validator, - }).Err() -} - func (s *redisAppStorage) Get(eui types.AppEUI) (*App, error) { m, err := s.client.HGetAllMap(eui.String()).Result() if err == redis.Nil { @@ -64,11 +56,6 @@ func (s *redisAppStorage) Get(eui types.AppEUI) (*App, error) { app := &App{ EUI: eui, Key: m["key"], - Functions: collection.Functions{ - Decoder: m["decoder"], - Converter: m["converter"], - Validator: m["validator"], - }, } return app, nil } diff --git a/core/collector/appStorage_test.go b/core/collector/appStorage_test.go index dcdc9168d..3b016821d 100644 --- a/core/collector/appStorage_test.go +++ b/core/collector/appStorage_test.go @@ -6,15 +6,12 @@ package collector import ( "testing" - "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/collection" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) func createStorage() AppStorage { - storage, err := ConnectRedisAppStorage(&redis.Options{Addr: "localhost:6379"}) + storage, err := ConnectRedis("localhost:6379", 0) if err != nil { panic(err) } @@ -24,11 +21,11 @@ func createStorage() AppStorage { func TestConnect(t *testing.T) { a := New(t) - c, err := ConnectRedisAppStorage(&redis.Options{Addr: "localhost:6379"}) + c, err := ConnectRedis("localhost:6379", 0) a.So(err, ShouldBeNil) defer c.Close() - _, err = ConnectRedisAppStorage(&redis.Options{Addr: ""}) + _, err = ConnectRedis("", 0) a.So(err, ShouldNotBeNil) } @@ -70,28 +67,3 @@ func TestGetAll(t *testing.T) { a.So(err, ShouldBeNil) a.So(apps, ShouldHaveLength, 2) } - -func TestSetFunctions(t *testing.T) { - a := New(t) - - eui, _ := types.ParseAppEUI("8000000000000001") - functions := &collection.Functions{ - Decoder: `function (payload) { return { size: payload.length; } }`, - Converter: `function (data) { return data; }`, - Validator: `function (data) { return data.size % 2 == 0; }`, - } - - storage := createStorage() - defer storage.Close() - defer storage.Reset() - - err := storage.SetFunctions(eui, functions) - a.So(err, ShouldBeNil) - - app, err := storage.Get(eui) - a.So(err, ShouldBeNil) - a.So(app.EUI, ShouldEqual, eui) - a.So(app.Decoder, ShouldEqual, functions.Decoder) - a.So(app.Converter, ShouldEqual, functions.Converter) - a.So(app.Validator, ShouldEqual, functions.Validator) -} diff --git a/core/collector/collector.go b/core/collector/collector.go index cd42bd78b..e69cc8017 100644 --- a/core/collector/collector.go +++ b/core/collector/collector.go @@ -59,7 +59,7 @@ func (c *collector) Start() ([]collection.AppCollector, error) { startErrors := make(map[types.AppEUI]error) for _, app := range apps { ctx := c.ctx.WithField("appEUI", app.EUI) - ac := collection.NewMqttAppCollector(ctx, c.broker, app.EUI, app.Key, &app.Functions, c.dataStorage) + ac := collection.NewMqttAppCollector(ctx, c.broker, app.EUI, app.Key, c.dataStorage) ctx.Info("Starting app collector") if err := ac.Start(); err != nil { ctx.WithError(err).Warn("Failed to start app collector; ignoring app") diff --git a/core/collector/collector_test.go b/core/collector/collector_test.go deleted file mode 100644 index 19ef003a6..000000000 --- a/core/collector/collector_test.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core/collection" - "github.com/TheThingsNetwork/ttn/core/types" - ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" - "gopkg.in/redis.v3" - - . "github.com/smartystreets/assertions" -) - -type mockStorage struct { -} - -func TestCollection(t *testing.T) { - a := New(t) - - appStorage, err := ConnectRedisAppStorage(&redis.Options{Addr: "localhost:6379"}) - if err != nil { - panic(err) - } - defer appStorage.Close() - defer appStorage.Reset() - - eui, _ := types.ParseAppEUI("8000000000000001") - err = appStorage.SetFunctions(eui, &collection.Functions{ - Decoder: `function (payload) { return { size: payload.length; } }`, - Converter: `function (data) { return data; }`, - Validator: `function (data) { return data.size % 2 == 0; }`, - }) - a.So(err, ShouldBeNil) - - collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}) - collectors, err := collector.Start() - defer collector.Stop() - a.So(err, ShouldBeNil) - a.So(collectors, ShouldHaveLength, 1) -} - -func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { - return nil -} - -func (s *mockStorage) Close() error { - return nil -} diff --git a/core/core.pb.go b/core/core.pb.go index 6a9885c56..0cf07130f 100644 --- a/core/core.pb.go +++ b/core/core.pb.go @@ -2,70 +2,6 @@ // source: core.proto // DO NOT EDIT! -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - core.proto - broker.proto - broker_manager.proto - handler_manager.proto - lorawan.proto - handler.proto - application.proto - router.proto - - It has these top-level messages: - Metadata - StatsMetadata - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - ValidateOTAABrokerReq - ValidateOTAABrokerRes - UpsertABPBrokerReq - UpsertABPBrokerRes - BrokerDevice - ValidateTokenBrokerReq - ValidateTokenBrokerRes - UpsertOTAAHandlerReq - UpsertOTAAHandlerRes - UpsertABPHandlerReq - UpsertABPHandlerRes - ListDevicesHandlerReq - ListDevicesHandlerRes - HandlerABPDevice - HandlerOTAADevice - GetDefaultDeviceReq - GetDefaultDeviceRes - SetDefaultDeviceReq - SetDefaultDeviceRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes -*/ package core import proto "github.com/golang/protobuf/proto" @@ -79,10 +15,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - type Metadata struct { DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` diff --git a/core/definitions.go b/core/definitions.go index 853702b12..441a24f46 100644 --- a/core/definitions.go +++ b/core/definitions.go @@ -5,11 +5,12 @@ package core // DataUpAppReq represents the actual payloads sent to application on uplink type DataUpAppReq struct { - Payload []byte `json:"payload"` - FPort uint8 `json:"port,omitempty"` - FCnt uint32 `json:"counter"` - DevEUI string `json:"dev_eui"` - Metadata []AppMetadata `json:"metadata"` + Payload []byte `json:"payload"` + Fields map[string]interface{} `json:"fields,omitempty"` + FPort uint8 `json:"port,omitempty"` + FCnt uint32 `json:"counter"` + DevEUI string `json:"dev_eui"` + Metadata []AppMetadata `json:"metadata"` } // OTAAAppReq are used to notify application of an accepted OTAA diff --git a/mqtt/client.go b/mqtt/client.go index c518ea260..cdddae670 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -97,21 +97,15 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri mqttOpts.SetCleanSession(false) mqttOpts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) { - if ctx != nil { - ctx.WithField("message", msg).Warn("Received unhandled message") - } + ctx.WithField("message", msg).Warn("Received unhandled message") }) mqttOpts.SetConnectionLostHandler(func(client MQTT.Client, err error) { - if ctx != nil { - ctx.WithError(err).Warn("Disconnected, reconnecting...") - } + ctx.WithError(err).Warn("Disconnected, reconnecting...") }) mqttOpts.SetOnConnectHandler(func(client MQTT.Client) { - if ctx != nil { - ctx.Debug("Connected") - } + ctx.Debug("Connected") }) return &defaultClient{ @@ -156,9 +150,7 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types. // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { - if c.ctx != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") - } + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") return } @@ -167,9 +159,7 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types. err = json.Unmarshal(msg.Payload(), dataUp) if err != nil { - if c.ctx != nil { - c.ctx.WithError(err).Warn("Could not unmarshal uplink") - } + c.ctx.WithError(err).Warn("Could not unmarshal uplink") return } @@ -201,9 +191,7 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI type // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { - if c.ctx != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") - } + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") return } @@ -211,9 +199,7 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI type dataDown := &core.DataDownAppReq{} err = json.Unmarshal(msg.Payload(), dataDown) if err != nil { - if c.ctx != nil { - c.ctx.WithError(err).Warn("Could not unmarshal Downlink") - } + c.ctx.WithError(err).Warn("Could not unmarshal Downlink") return } @@ -245,9 +231,7 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI types.AppEUI, devEUI t // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { - if c.ctx != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") - } + c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") return } @@ -255,9 +239,7 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI types.AppEUI, devEUI t activation := &core.OTAAAppReq{} err = json.Unmarshal(msg.Payload(), activation) if err != nil { - if c.ctx != nil { - c.ctx.WithError(err).Warn("Could not unmarshal Activation") - } + c.ctx.WithError(err).Warn("Could not unmarshal Activation") return } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index a6e942e38..ab9f65831 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -29,13 +29,13 @@ func TestToken(t *testing.T) { func TestNewClient(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") a.So(c.(*defaultClient).mqtt, ShouldNotBeNil) } func TestConnect(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") err := c.Connect() a.So(err, ShouldBeNil) @@ -46,7 +46,7 @@ func TestConnect(t *testing.T) { func TestConnectInvalidAddress(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 err := c.Connect() a.So(err, ShouldNotBeNil) } @@ -57,7 +57,7 @@ func TestConnectInvalidCredentials(t *testing.T) { func TestIsConnected(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") a.So(c.IsConnected(), ShouldBeFalse) @@ -68,7 +68,7 @@ func TestIsConnected(t *testing.T) { func TestDisconnect(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") // Disconnecting when not connected should not change anything c.Disconnect() @@ -98,7 +98,7 @@ func TestRandomTopicPublish(t *testing.T) { func TestPublishUplink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.EUI64{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -114,7 +114,7 @@ func TestPublishUplink(t *testing.T) { func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.EUI64{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -129,7 +129,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { func TestSubscribeAppUplink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.AppEUI{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -144,7 +144,7 @@ func TestSubscribeAppUplink(t *testing.T) { func TestSubscribeUplink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() token := c.SubscribeUplink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { @@ -157,7 +157,7 @@ func TestSubscribeUplink(t *testing.T) { func TestPubSubUplink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() appEUI := types.AppEUI{0x04, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -181,7 +181,7 @@ func TestPubSubUplink(t *testing.T) { func TestPubSubAppUplink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() appEUI := types.AppEUI{0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -231,7 +231,7 @@ func TestInvalidUplink(t *testing.T) { func TestPublishDownlink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.EUI64{0x01, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -247,7 +247,7 @@ func TestPublishDownlink(t *testing.T) { func TestSubscribeDeviceDownlink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.EUI64{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -262,7 +262,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { func TestSubscribeAppDownlink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.AppEUI{0x03, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -277,7 +277,7 @@ func TestSubscribeAppDownlink(t *testing.T) { func TestSubscribeDownlink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() token := c.SubscribeDownlink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { @@ -290,7 +290,7 @@ func TestSubscribeDownlink(t *testing.T) { func TestPubSubDownlink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -314,7 +314,7 @@ func TestPubSubDownlink(t *testing.T) { func TestPubSubAppDownlink(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() appEUI := types.AppEUI{0x05, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -364,7 +364,7 @@ func TestInvalidDownlink(t *testing.T) { func TestPublishActivations(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.EUI64{0x01, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -378,7 +378,7 @@ func TestPublishActivations(t *testing.T) { func TestSubscribeDeviceActivations(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.EUI64{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -393,7 +393,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { func TestSubscribeAppActivations(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() eui := types.AppEUI{0x03, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -408,7 +408,7 @@ func TestSubscribeAppActivations(t *testing.T) { func TestSubscribeActivations(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() token := c.SubscribeActivations(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { @@ -421,7 +421,7 @@ func TestSubscribeActivations(t *testing.T) { func TestPubSubActivations(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() appEUI := types.AppEUI{0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} @@ -445,7 +445,7 @@ func TestPubSubActivations(t *testing.T) { func TestPubSubAppActivations(t *testing.T) { a := New(t) - c := NewClient(nil, "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() appEUI := types.AppEUI{0x05, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} From 4e3c486330522ff16e786ccfa203ed907b05f150 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sat, 7 May 2016 15:30:16 +0200 Subject: [PATCH 1383/2266] Fixed tests --- core/collector/collector_test.go | 46 ++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 core/collector/collector_test.go diff --git a/core/collector/collector_test.go b/core/collector/collector_test.go new file mode 100644 index 000000000..bc1540f33 --- /dev/null +++ b/core/collector/collector_test.go @@ -0,0 +1,46 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" + + . "github.com/smartystreets/assertions" +) + +type mockStorage struct { +} + +func TestCollection(t *testing.T) { + a := New(t) + + appStorage, err := ConnectRedis("localhost:6379", 0) + if err != nil { + panic(err) + } + defer appStorage.Close() + defer appStorage.Reset() + + eui, _ := types.ParseAppEUI("8000000000000001") + err = appStorage.SetKey(eui, "secret") + a.So(err, ShouldBeNil) + + collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}) + collectors, err := collector.Start() + defer collector.Stop() + a.So(err, ShouldBeNil) + a.So(collectors, ShouldHaveLength, 1) +} + +func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { + return nil +} + +func (s *mockStorage) Close() error { + return nil +} From 76772a90ebf8479a175a5deebaf9a7923ba3e9e0 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 9 May 2016 14:21:46 +0200 Subject: [PATCH 1384/2266] Added Redis to Travis config --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 2f94e00a3..409c1eb65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,9 @@ install: - make test-deps - make cover-deps +services: + - redis-server + before_script: - mosquitto -p 1883 1>/dev/null 2>/dev/null & From 8f08d485666ced794ce4ea047f5953cea05f46e6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 17:53:48 +0200 Subject: [PATCH 1385/2266] Add Redis to Travis --- .travis.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.travis.yml b/.travis.yml index 409c1eb65..65f5201f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,9 @@ services: before_script: - mosquitto -p 1883 1>/dev/null 2>/dev/null & +services: + - redis-server + script: - make test - make fmt From d86dc0379fccfa0a92d16796c8376616b2661a92 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 9 May 2016 14:28:05 +0200 Subject: [PATCH 1386/2266] Cherry picked Redis to Travis --- .travis.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 65f5201f1..a1c34d62f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,9 +15,6 @@ install: - make test-deps - make cover-deps -services: - - redis-server - before_script: - mosquitto -p 1883 1>/dev/null 2>/dev/null & From 12057a786295e26c57f6bfb2dca70d1fde1085a7 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 9 May 2016 15:31:24 +0200 Subject: [PATCH 1387/2266] Naming convention --- cmd/collector.go | 2 +- core/collector/influxdb/influxdb.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/collector.go b/cmd/collector.go index 7aa6d4342..180f0b365 100644 --- a/cmd/collector.go +++ b/cmd/collector.go @@ -31,7 +31,7 @@ configured applications. } defer appStorage.Close() - dataStorage, err := influxdb.NewInfluxDBStorage(viper.GetString("collector.influxdb-addr"), + dataStorage, err := influxdb.NewDataStorage(viper.GetString("collector.influxdb-addr"), viper.GetString("collector.influxdb-username"), viper.GetString("collector.influxdb-password")) if err != nil { ctx.WithError(err).Fatal("Failed to connect to InfluxDB") diff --git a/core/collector/influxdb/influxdb.go b/core/collector/influxdb/influxdb.go index c0e815402..5dd9322a4 100644 --- a/core/collector/influxdb/influxdb.go +++ b/core/collector/influxdb/influxdb.go @@ -15,8 +15,8 @@ type influxDBStorage struct { client client.Client } -// NewInfluxDBStorage instantiates a new DataStorage for InfluxDB -func NewInfluxDBStorage(addr, username, password string) (collection.DataStorage, error) { +// NewDataStorage instantiates a new DataStorage for InfluxDB +func NewDataStorage(addr, username, password string) (collection.DataStorage, error) { c, err := client.NewHTTPClient(client.HTTPConfig{ Addr: addr, Username: username, From d88172e8ec13ee4c452097c9e8825ae3ed4f8c8a Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 9 May 2016 15:31:36 +0200 Subject: [PATCH 1388/2266] Using Redis keys and sets --- core/adapters/fields/appStorage.go | 10 ++++++---- core/collector/appStorage.go | 32 +++++++++++++++++++++++++++--- core/collector/appStorage_test.go | 11 +++++++--- core/collector/collector_test.go | 2 ++ 4 files changed, 45 insertions(+), 10 deletions(-) diff --git a/core/adapters/fields/appStorage.go b/core/adapters/fields/appStorage.go index 0ad050d65..f33e2c497 100644 --- a/core/adapters/fields/appStorage.go +++ b/core/adapters/fields/appStorage.go @@ -12,6 +12,8 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) +const appKey = "fields:app:%s" + // AppStorage provides storage for applications type AppStorage interface { SetFunctions(eui types.AppEUI, functions *collection.Functions) error @@ -38,12 +40,12 @@ func ConnectRedis(addr string, db int64) (AppStorage, error) { return &redisAppStorage{client}, nil } -func (s *redisAppStorage) makeKey(eui types.AppEUI) string { - return fmt.Sprintf("app:%s", eui.String()) +func makeKey(eui types.AppEUI) string { + return fmt.Sprintf(appKey, eui.String()) } func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.Functions) error { - return s.client.HMSetMap(s.makeKey(eui), map[string]string{ + return s.client.HMSetMap(makeKey(eui), map[string]string{ "decoder": functions.Decoder, "converter": functions.Converter, "validator": functions.Validator, @@ -51,7 +53,7 @@ func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.F } func (s *redisAppStorage) GetFunctions(eui types.AppEUI) (*collection.Functions, error) { - m, err := s.client.HGetAllMap(s.makeKey(eui)).Result() + m, err := s.client.HGetAllMap(makeKey(eui)).Result() if err == redis.Nil { return nil, nil } else if err != nil { diff --git a/core/collector/appStorage.go b/core/collector/appStorage.go index c3cd96542..9b0bd23f6 100644 --- a/core/collector/appStorage.go +++ b/core/collector/appStorage.go @@ -4,11 +4,18 @@ package collector import ( + "fmt" + "gopkg.in/redis.v3" "github.com/TheThingsNetwork/ttn/core/types" ) +const ( + appsKey = "collector:apps" + appKey = "collector:app:%s" +) + // App represents a stored application type App struct { EUI types.AppEUI @@ -17,6 +24,8 @@ type App struct { // AppStorage provides storage for applications type AppStorage interface { + Add(eui types.AppEUI) error + Remove(eui types.AppEUI) error SetKey(eui types.AppEUI, key string) error Get(eui types.AppEUI) (*App, error) GetAll() ([]*App, error) @@ -42,12 +51,29 @@ func ConnectRedis(addr string, db int64) (AppStorage, error) { return &redisAppStorage{client}, nil } +func makeKey(eui types.AppEUI) string { + return fmt.Sprintf(appKey, eui.String()) +} + +func (s *redisAppStorage) Add(eui types.AppEUI) error { + return s.client.SAdd(appsKey, eui.String()).Err() +} + +func (s *redisAppStorage) Remove(eui types.AppEUI) error { + err := s.client.SRem(appsKey, eui.String()).Err() + if err != nil { + return err + } + s.client.Del(makeKey(eui)) + return nil +} + func (s *redisAppStorage) SetKey(eui types.AppEUI, key string) error { - return s.client.HSet(eui.String(), "key", key).Err() + return s.client.HSet(makeKey(eui), "key", key).Err() } func (s *redisAppStorage) Get(eui types.AppEUI) (*App, error) { - m, err := s.client.HGetAllMap(eui.String()).Result() + m, err := s.client.HGetAllMap(makeKey(eui)).Result() if err == redis.Nil { return nil, nil } else if err != nil { @@ -61,7 +87,7 @@ func (s *redisAppStorage) Get(eui types.AppEUI) (*App, error) { } func (s *redisAppStorage) GetAll() ([]*App, error) { - euis, err := s.client.Keys("*").Result() + euis, err := s.client.SMembers(appsKey).Result() if err != nil { return nil, err } diff --git a/core/collector/appStorage_test.go b/core/collector/appStorage_test.go index 3b016821d..09443af80 100644 --- a/core/collector/appStorage_test.go +++ b/core/collector/appStorage_test.go @@ -58,12 +58,17 @@ func TestGetAll(t *testing.T) { defer storage.Close() defer storage.Reset() - err := storage.SetKey(eui1, "key1") + err := storage.Add(eui1) a.So(err, ShouldBeNil) - err = storage.SetKey(eui2, "key2") + err = storage.Add(eui2) a.So(err, ShouldBeNil) - apps, err := storage.GetAll() a.So(err, ShouldBeNil) a.So(apps, ShouldHaveLength, 2) + + err = storage.Remove(eui1) + a.So(err, ShouldBeNil) + apps, err = storage.GetAll() + a.So(err, ShouldBeNil) + a.So(apps, ShouldHaveLength, 1) } diff --git a/core/collector/collector_test.go b/core/collector/collector_test.go index bc1540f33..cd7f97ae7 100644 --- a/core/collector/collector_test.go +++ b/core/collector/collector_test.go @@ -27,6 +27,8 @@ func TestCollection(t *testing.T) { defer appStorage.Reset() eui, _ := types.ParseAppEUI("8000000000000001") + err = appStorage.Add(eui) + a.So(err, ShouldBeNil) err = appStorage.SetKey(eui, "secret") a.So(err, ShouldBeNil) From 132e699331491f5a2f981b3bfdbd86686504d2cf Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 13 May 2016 12:55:22 +0100 Subject: [PATCH 1389/2266] Fields adapter in Handler --- cmd/collector.go | 18 +- core/adapters/fields/adapter.go | 7 +- core/adapters/fields/adapter_test.go | 3 +- core/adapters/fields/appStorage.go | 17 +- core/adapters/fields/appStorage_test.go | 3 +- .../fields}/functions.go | 2 +- .../fields}/functions_test.go | 2 +- core/application.pb.go | 14 + core/collection/appCollector.go | 2 +- core/collector.pb.go | 1144 ++++++++++++++++ core/collector/collector.go | 83 -- core/{ => components}/collector/appStorage.go | 38 +- .../collector/appStorage_test.go | 11 +- core/components/collector/collector.go | 127 ++ core/components/collector/collectorManager.go | 75 + .../collector/collector_test.go | 2 +- .../collector/influxdb/influxdb.go | 0 core/components/handler/handlerManager.go | 89 +- core/handler_manager.pb.go | 1202 ++++++++++++++++- core/protos/collector.proto | 35 + core/protos/handler_manager.proto | 49 +- ttnctl/cmd/applications.go | 1 - ttnctl/cmd/applicationsPayloadFunctions.go | 149 ++ ttnctl/cmd/device.go | 9 - ttnctl/cmd/root.go | 10 + 25 files changed, 2884 insertions(+), 208 deletions(-) rename core/{collection => adapters/fields}/functions.go (99%) rename core/{collection => adapters/fields}/functions_test.go (99%) create mode 100644 core/collector.pb.go delete mode 100644 core/collector/collector.go rename core/{ => components}/collector/appStorage.go (75%) rename core/{ => components}/collector/appStorage_test.go (87%) create mode 100644 core/components/collector/collector.go create mode 100644 core/components/collector/collectorManager.go rename core/{ => components}/collector/collector_test.go (94%) rename core/{ => components}/collector/influxdb/influxdb.go (100%) create mode 100644 core/protos/collector.proto create mode 100644 ttnctl/cmd/applicationsPayloadFunctions.go diff --git a/cmd/collector.go b/cmd/collector.go index 180f0b365..f0d4e22f1 100644 --- a/cmd/collector.go +++ b/cmd/collector.go @@ -4,11 +4,12 @@ package cmd import ( + "fmt" "os" "os/signal" - "github.com/TheThingsNetwork/ttn/core/collector" - "github.com/TheThingsNetwork/ttn/core/collector/influxdb" + "github.com/TheThingsNetwork/ttn/core/components/collector" + "github.com/TheThingsNetwork/ttn/core/components/collector/influxdb" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -37,7 +38,11 @@ configured applications. ctx.WithError(err).Fatal("Failed to connect to InfluxDB") } - col := collector.NewCollector(ctx, appStorage, viper.GetString("collector.mqtt-broker"), dataStorage) + col := collector.NewCollector(ctx, + appStorage, + viper.GetString("collector.mqtt-broker"), + dataStorage, + fmt.Sprintf("%s:%d", viper.GetString("collector.address"), viper.GetInt("collector.port"))) collectors, err := col.Start() if startError, ok := err.(collector.StartError); ok { ctx.WithError(startError).Warn("Could not start collecting all applications") @@ -46,7 +51,7 @@ configured applications. } defer col.Stop() - ctx.Infof("Started %d collectors", len(collectors)) + ctx.Infof("Started %d app collectors", len(collectors)) c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) @@ -57,6 +62,11 @@ configured applications. func init() { RootCmd.AddCommand(collectorCmd) + collectorCmd.Flags().String("address", "0.0.0.0", "The IP address to listen for management") + collectorCmd.Flags().Int("port", 1783, "The port to listen for management") + viper.BindPFlag("collector.address", collectorCmd.Flags().Lookup("address")) + viper.BindPFlag("collector.port", collectorCmd.Flags().Lookup("port")) + collectorCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") viper.BindPFlag("collector.mqtt-broker", collectorCmd.Flags().Lookup("mqtt-broker")) diff --git a/core/adapters/fields/adapter.go b/core/adapters/fields/adapter.go index dd66c7e99..252caad4b 100644 --- a/core/adapters/fields/adapter.go +++ b/core/adapters/fields/adapter.go @@ -16,6 +16,7 @@ import ( // Adapter represents the interface of an application type Adapter interface { core.AppClient + Storage() AppStorage SubscribeDownlink(handler core.HandlerServer) error } @@ -35,7 +36,7 @@ func (s *adapter) HandleData(context context.Context, in *core.DataAppReq, opt . appEUI.Unmarshal(in.AppEUI) var devEUI types.DevEUI devEUI.Unmarshal(in.DevEUI) - ctx := s.ctx.WithFields(&log.Fields{"appEUI": appEUI, "devEUI": devEUI}) + ctx := s.ctx.WithFields(&log.Fields{"AppEUI": appEUI, "DevEUI": devEUI}) req := core.DataUpAppReq{ Payload: in.Payload, @@ -91,3 +92,7 @@ func (s *adapter) HandleJoin(context context.Context, in *core.JoinAppReq, opt . func (s *adapter) SubscribeDownlink(handler core.HandlerServer) error { return s.mqtt.SubscribeDownlink(handler) } + +func (s *adapter) Storage() AppStorage { + return s.storage +} diff --git a/core/adapters/fields/adapter_test.go b/core/adapters/fields/adapter_test.go index 996fcbf1e..5128f24fc 100644 --- a/core/adapters/fields/adapter_test.go +++ b/core/adapters/fields/adapter_test.go @@ -9,7 +9,6 @@ import ( "golang.org/x/net/context" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/collection" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -45,7 +44,7 @@ func TestHandleData(t *testing.T) { } // Normal flow - functions := &collection.Functions{ + functions := &Functions{ Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, } err = storage.SetFunctions(appEUI, functions) diff --git a/core/adapters/fields/appStorage.go b/core/adapters/fields/appStorage.go index f33e2c497..cca0565a0 100644 --- a/core/adapters/fields/appStorage.go +++ b/core/adapters/fields/appStorage.go @@ -8,7 +8,6 @@ import ( "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/core/collection" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -16,8 +15,8 @@ const appKey = "fields:app:%s" // AppStorage provides storage for applications type AppStorage interface { - SetFunctions(eui types.AppEUI, functions *collection.Functions) error - GetFunctions(eui types.AppEUI) (*collection.Functions, error) + SetFunctions(eui types.AppEUI, functions *Functions) error + GetFunctions(eui types.AppEUI) (*Functions, error) Reset() error Close() error } @@ -44,7 +43,7 @@ func makeKey(eui types.AppEUI) string { return fmt.Sprintf(appKey, eui.String()) } -func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.Functions) error { +func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *Functions) error { return s.client.HMSetMap(makeKey(eui), map[string]string{ "decoder": functions.Decoder, "converter": functions.Converter, @@ -52,15 +51,19 @@ func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *collection.F }).Err() } -func (s *redisAppStorage) GetFunctions(eui types.AppEUI) (*collection.Functions, error) { +func (s *redisAppStorage) GetFunctions(eui types.AppEUI) (*Functions, error) { m, err := s.client.HGetAllMap(makeKey(eui)).Result() if err == redis.Nil { return nil, nil } else if err != nil { return nil, err } - return &collection.Functions{ - Decoder: m["decoder"], + decoder, ok := m["decoder"] + if !ok { + return nil, nil + } + return &Functions{ + Decoder: decoder, Converter: m["converter"], Validator: m["validator"], }, nil diff --git a/core/adapters/fields/appStorage_test.go b/core/adapters/fields/appStorage_test.go index 1781cdba6..897ded33f 100644 --- a/core/adapters/fields/appStorage_test.go +++ b/core/adapters/fields/appStorage_test.go @@ -6,7 +6,6 @@ package fields import ( "testing" - "github.com/TheThingsNetwork/ttn/core/collection" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) @@ -34,7 +33,7 @@ func TestSetFunctions(t *testing.T) { a := New(t) eui, _ := types.ParseAppEUI("8000000000000001") - functions := &collection.Functions{ + functions := &Functions{ Decoder: `function (payload) { return { size: payload.length; } }`, Converter: `function (data) { return data; }`, Validator: `function (data) { return data.size % 2 == 0; }`, diff --git a/core/collection/functions.go b/core/adapters/fields/functions.go similarity index 99% rename from core/collection/functions.go rename to core/adapters/fields/functions.go index f5299cca5..626448c40 100644 --- a/core/collection/functions.go +++ b/core/adapters/fields/functions.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package collection +package fields import ( "errors" diff --git a/core/collection/functions_test.go b/core/adapters/fields/functions_test.go similarity index 99% rename from core/collection/functions_test.go rename to core/adapters/fields/functions_test.go index 81db4b481..3b0c4de61 100644 --- a/core/collection/functions_test.go +++ b/core/adapters/fields/functions_test.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package collection +package fields import ( "testing" diff --git a/core/application.pb.go b/core/application.pb.go index 611096269..a4daa8500 100644 --- a/core/application.pb.go +++ b/core/application.pb.go @@ -9,6 +9,7 @@ application.proto broker.proto broker_manager.proto + collector.proto core.proto handler.proto handler_manager.proto @@ -31,6 +32,13 @@ BrokerDevice ValidateTokenBrokerReq ValidateTokenBrokerRes + GetApplicationsCollectorReq + GetApplicationsCollectorRes + CollectorApplication + AddApplicationCollectorReq + AddApplicationCollectorRes + RemoveApplicationCollectorReq + RemoveApplicationCollectorRes Metadata StatsMetadata DataUpHandlerReq @@ -50,6 +58,12 @@ GetDefaultDeviceReq GetDefaultDeviceRes SetDefaultDeviceReq + GetPayloadFunctionsReq + GetPayloadFunctionsRes + SetPayloadFunctionsReq + SetPayloadFunctionsRes + TestPayloadFunctionsReq + TestPayloadFunctionsRes SetDefaultDeviceRes LoRaWANData LoRaWANMHDR diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go index 846c7bdd0..60ab24088 100644 --- a/core/collection/appCollector.go +++ b/core/collection/appCollector.go @@ -57,7 +57,7 @@ func (c *appCollector) Stop() { } func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { - ctx := c.ctx.WithField("devEUI", devEUI) + ctx := c.ctx.WithField("DevEUI", devEUI) t, err := time.Parse(time.RFC3339, req.Metadata[0].ServerTime) if err != nil { diff --git a/core/collector.pb.go b/core/collector.pb.go new file mode 100644 index 000000000..e9fce881b --- /dev/null +++ b/core/collector.pb.go @@ -0,0 +1,1144 @@ +// Code generated by protoc-gen-gogo. +// source: collector.proto +// DO NOT EDIT! + +package core + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type GetApplicationsCollectorReq struct { +} + +func (m *GetApplicationsCollectorReq) Reset() { *m = GetApplicationsCollectorReq{} } +func (m *GetApplicationsCollectorReq) String() string { return proto.CompactTextString(m) } +func (*GetApplicationsCollectorReq) ProtoMessage() {} +func (*GetApplicationsCollectorReq) Descriptor() ([]byte, []int) { + return fileDescriptorCollector, []int{0} +} + +type GetApplicationsCollectorRes struct { + Applications []*CollectorApplication `protobuf:"bytes,1,rep,name=Applications,json=applications" json:"Applications,omitempty"` +} + +func (m *GetApplicationsCollectorRes) Reset() { *m = GetApplicationsCollectorRes{} } +func (m *GetApplicationsCollectorRes) String() string { return proto.CompactTextString(m) } +func (*GetApplicationsCollectorRes) ProtoMessage() {} +func (*GetApplicationsCollectorRes) Descriptor() ([]byte, []int) { + return fileDescriptorCollector, []int{1} +} + +func (m *GetApplicationsCollectorRes) GetApplications() []*CollectorApplication { + if m != nil { + return m.Applications + } + return nil +} + +type CollectorApplication struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` +} + +func (m *CollectorApplication) Reset() { *m = CollectorApplication{} } +func (m *CollectorApplication) String() string { return proto.CompactTextString(m) } +func (*CollectorApplication) ProtoMessage() {} +func (*CollectorApplication) Descriptor() ([]byte, []int) { return fileDescriptorCollector, []int{2} } + +type AddApplicationCollectorReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + AppAccessKey string `protobuf:"bytes,2,opt,name=AppAccessKey,json=appAccessKey,proto3" json:"AppAccessKey,omitempty"` +} + +func (m *AddApplicationCollectorReq) Reset() { *m = AddApplicationCollectorReq{} } +func (m *AddApplicationCollectorReq) String() string { return proto.CompactTextString(m) } +func (*AddApplicationCollectorReq) ProtoMessage() {} +func (*AddApplicationCollectorReq) Descriptor() ([]byte, []int) { + return fileDescriptorCollector, []int{3} +} + +type AddApplicationCollectorRes struct { +} + +func (m *AddApplicationCollectorRes) Reset() { *m = AddApplicationCollectorRes{} } +func (m *AddApplicationCollectorRes) String() string { return proto.CompactTextString(m) } +func (*AddApplicationCollectorRes) ProtoMessage() {} +func (*AddApplicationCollectorRes) Descriptor() ([]byte, []int) { + return fileDescriptorCollector, []int{4} +} + +type RemoveApplicationCollectorReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` +} + +func (m *RemoveApplicationCollectorReq) Reset() { *m = RemoveApplicationCollectorReq{} } +func (m *RemoveApplicationCollectorReq) String() string { return proto.CompactTextString(m) } +func (*RemoveApplicationCollectorReq) ProtoMessage() {} +func (*RemoveApplicationCollectorReq) Descriptor() ([]byte, []int) { + return fileDescriptorCollector, []int{5} +} + +type RemoveApplicationCollectorRes struct { +} + +func (m *RemoveApplicationCollectorRes) Reset() { *m = RemoveApplicationCollectorRes{} } +func (m *RemoveApplicationCollectorRes) String() string { return proto.CompactTextString(m) } +func (*RemoveApplicationCollectorRes) ProtoMessage() {} +func (*RemoveApplicationCollectorRes) Descriptor() ([]byte, []int) { + return fileDescriptorCollector, []int{6} +} + +func init() { + proto.RegisterType((*GetApplicationsCollectorReq)(nil), "core.GetApplicationsCollectorReq") + proto.RegisterType((*GetApplicationsCollectorRes)(nil), "core.GetApplicationsCollectorRes") + proto.RegisterType((*CollectorApplication)(nil), "core.CollectorApplication") + proto.RegisterType((*AddApplicationCollectorReq)(nil), "core.AddApplicationCollectorReq") + proto.RegisterType((*AddApplicationCollectorRes)(nil), "core.AddApplicationCollectorRes") + proto.RegisterType((*RemoveApplicationCollectorReq)(nil), "core.RemoveApplicationCollectorReq") + proto.RegisterType((*RemoveApplicationCollectorRes)(nil), "core.RemoveApplicationCollectorRes") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + +// Client API for CollectorManager service + +type CollectorManagerClient interface { + GetApplications(ctx context.Context, in *GetApplicationsCollectorReq, opts ...grpc.CallOption) (*GetApplicationsCollectorRes, error) + AddApplication(ctx context.Context, in *AddApplicationCollectorReq, opts ...grpc.CallOption) (*AddApplicationCollectorRes, error) + RemoveApplication(ctx context.Context, in *RemoveApplicationCollectorReq, opts ...grpc.CallOption) (*RemoveApplicationCollectorRes, error) +} + +type collectorManagerClient struct { + cc *grpc.ClientConn +} + +func NewCollectorManagerClient(cc *grpc.ClientConn) CollectorManagerClient { + return &collectorManagerClient{cc} +} + +func (c *collectorManagerClient) GetApplications(ctx context.Context, in *GetApplicationsCollectorReq, opts ...grpc.CallOption) (*GetApplicationsCollectorRes, error) { + out := new(GetApplicationsCollectorRes) + err := grpc.Invoke(ctx, "/core.CollectorManager/GetApplications", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *collectorManagerClient) AddApplication(ctx context.Context, in *AddApplicationCollectorReq, opts ...grpc.CallOption) (*AddApplicationCollectorRes, error) { + out := new(AddApplicationCollectorRes) + err := grpc.Invoke(ctx, "/core.CollectorManager/AddApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *collectorManagerClient) RemoveApplication(ctx context.Context, in *RemoveApplicationCollectorReq, opts ...grpc.CallOption) (*RemoveApplicationCollectorRes, error) { + out := new(RemoveApplicationCollectorRes) + err := grpc.Invoke(ctx, "/core.CollectorManager/RemoveApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for CollectorManager service + +type CollectorManagerServer interface { + GetApplications(context.Context, *GetApplicationsCollectorReq) (*GetApplicationsCollectorRes, error) + AddApplication(context.Context, *AddApplicationCollectorReq) (*AddApplicationCollectorRes, error) + RemoveApplication(context.Context, *RemoveApplicationCollectorReq) (*RemoveApplicationCollectorRes, error) +} + +func RegisterCollectorManagerServer(s *grpc.Server, srv CollectorManagerServer) { + s.RegisterService(&_CollectorManager_serviceDesc, srv) +} + +func _CollectorManager_GetApplications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetApplicationsCollectorReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorManagerServer).GetApplications(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.CollectorManager/GetApplications", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorManagerServer).GetApplications(ctx, req.(*GetApplicationsCollectorReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _CollectorManager_AddApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(AddApplicationCollectorReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorManagerServer).AddApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.CollectorManager/AddApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorManagerServer).AddApplication(ctx, req.(*AddApplicationCollectorReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _CollectorManager_RemoveApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RemoveApplicationCollectorReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CollectorManagerServer).RemoveApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.CollectorManager/RemoveApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CollectorManagerServer).RemoveApplication(ctx, req.(*RemoveApplicationCollectorReq)) + } + return interceptor(ctx, in, info, handler) +} + +var _CollectorManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "core.CollectorManager", + HandlerType: (*CollectorManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetApplications", + Handler: _CollectorManager_GetApplications_Handler, + }, + { + MethodName: "AddApplication", + Handler: _CollectorManager_AddApplication_Handler, + }, + { + MethodName: "RemoveApplication", + Handler: _CollectorManager_RemoveApplication_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *GetApplicationsCollectorReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetApplicationsCollectorReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *GetApplicationsCollectorRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetApplicationsCollectorRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Applications) > 0 { + for _, msg := range m.Applications { + data[i] = 0xa + i++ + i = encodeVarintCollector(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *CollectorApplication) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *CollectorApplication) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintCollector(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + return i, nil +} + +func (m *AddApplicationCollectorReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *AddApplicationCollectorReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintCollector(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + if len(m.AppAccessKey) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintCollector(data, i, uint64(len(m.AppAccessKey))) + i += copy(data[i:], m.AppAccessKey) + } + return i, nil +} + +func (m *AddApplicationCollectorRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *AddApplicationCollectorRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *RemoveApplicationCollectorReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RemoveApplicationCollectorReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintCollector(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + return i, nil +} + +func (m *RemoveApplicationCollectorRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RemoveApplicationCollectorRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64Collector(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Collector(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintCollector(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *GetApplicationsCollectorReq) Size() (n int) { + var l int + _ = l + return n +} + +func (m *GetApplicationsCollectorRes) Size() (n int) { + var l int + _ = l + if len(m.Applications) > 0 { + for _, e := range m.Applications { + l = e.Size() + n += 1 + l + sovCollector(uint64(l)) + } + } + return n +} + +func (m *CollectorApplication) Size() (n int) { + var l int + _ = l + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovCollector(uint64(l)) + } + return n +} + +func (m *AddApplicationCollectorReq) Size() (n int) { + var l int + _ = l + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovCollector(uint64(l)) + } + l = len(m.AppAccessKey) + if l > 0 { + n += 1 + l + sovCollector(uint64(l)) + } + return n +} + +func (m *AddApplicationCollectorRes) Size() (n int) { + var l int + _ = l + return n +} + +func (m *RemoveApplicationCollectorReq) Size() (n int) { + var l int + _ = l + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovCollector(uint64(l)) + } + return n +} + +func (m *RemoveApplicationCollectorRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovCollector(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozCollector(x uint64) (n int) { + return sovCollector(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GetApplicationsCollectorReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetApplicationsCollectorReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetApplicationsCollectorReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetApplicationsCollectorRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetApplicationsCollectorRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetApplicationsCollectorRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Applications", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthCollector + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Applications = append(m.Applications, &CollectorApplication{}) + if err := m.Applications[len(m.Applications)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CollectorApplication) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CollectorApplication: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CollectorApplication: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCollector + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AddApplicationCollectorReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AddApplicationCollectorReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AddApplicationCollectorReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCollector + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppAccessKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCollector + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppAccessKey = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AddApplicationCollectorRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AddApplicationCollectorRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AddApplicationCollectorRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RemoveApplicationCollectorReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RemoveApplicationCollectorReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RemoveApplicationCollectorReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCollector + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RemoveApplicationCollectorRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCollector + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RemoveApplicationCollectorRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RemoveApplicationCollectorRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipCollector(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthCollector + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipCollector(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCollector + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCollector + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCollector + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthCollector + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowCollector + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipCollector(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthCollector = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowCollector = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorCollector = []byte{ + // 276 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0xce, 0xcf, 0xc9, + 0x49, 0x4d, 0x2e, 0xc9, 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, + 0x4a, 0x55, 0x92, 0xe5, 0x92, 0x76, 0x4f, 0x2d, 0x71, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, + 0xc9, 0xcc, 0xcf, 0x2b, 0x76, 0x86, 0xa9, 0x0b, 0x4a, 0x2d, 0x54, 0x8a, 0xc5, 0x27, 0x5d, 0x2c, + 0x64, 0xc7, 0xc5, 0x83, 0x2c, 0x27, 0xc1, 0xa8, 0xc0, 0xac, 0xc1, 0x6d, 0x24, 0xa5, 0x07, 0x32, + 0x5a, 0x0f, 0xae, 0x12, 0x49, 0x49, 0x10, 0x4f, 0x22, 0x92, 0x7a, 0x25, 0x3d, 0x2e, 0x11, 0x6c, + 0xaa, 0x84, 0xc4, 0xb8, 0xd8, 0x80, 0x5c, 0xd7, 0x50, 0x4f, 0xa0, 0x89, 0x8c, 0x1a, 0x3c, 0x41, + 0x6c, 0x89, 0x60, 0x9e, 0x52, 0x04, 0x97, 0x94, 0x63, 0x4a, 0x0a, 0x92, 0x4a, 0x64, 0xc7, 0xe2, + 0xd2, 0x25, 0xa4, 0x04, 0x76, 0xa5, 0x63, 0x72, 0x72, 0x6a, 0x71, 0xb1, 0x77, 0x6a, 0xa5, 0x04, + 0x13, 0x50, 0x96, 0x13, 0xec, 0x12, 0xb8, 0x98, 0x92, 0x0c, 0x1e, 0x93, 0x8b, 0x95, 0xcc, 0xb9, + 0x64, 0x83, 0x52, 0x73, 0xf3, 0xcb, 0x52, 0x49, 0xb4, 0x5a, 0x49, 0x1e, 0xbf, 0xc6, 0x62, 0xa3, + 0x05, 0x4c, 0x5c, 0x02, 0x70, 0x01, 0xdf, 0xc4, 0xbc, 0xc4, 0xf4, 0xd4, 0x22, 0xa1, 0x70, 0x2e, + 0x7e, 0xb4, 0x50, 0x17, 0x52, 0x84, 0x84, 0x29, 0x9e, 0xb8, 0x92, 0x22, 0xa8, 0xa4, 0x58, 0x28, + 0x84, 0x8b, 0x0f, 0xd5, 0x97, 0x42, 0x0a, 0x10, 0x4d, 0xb8, 0x43, 0x55, 0x8a, 0x90, 0x8a, 0x62, + 0xa1, 0x58, 0x2e, 0x41, 0x0c, 0x4f, 0x0a, 0x29, 0x43, 0xb4, 0xe1, 0x0d, 0x36, 0x29, 0x22, 0x14, + 0x15, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, + 0x0c, 0x49, 0x6c, 0xe0, 0x14, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x8f, 0x82, 0xfe, + 0xd4, 0x02, 0x00, 0x00, +} diff --git a/core/collector/collector.go b/core/collector/collector.go deleted file mode 100644 index e69cc8017..000000000 --- a/core/collector/collector.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "fmt" - "strings" - - "github.com/TheThingsNetwork/ttn/core/collection" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/apex/log" -) - -// Collector collects data from the Handler and stores it -type Collector interface { - Start() ([]collection.AppCollector, error) - Stop() -} - -type collector struct { - ctx log.Interface - appStorage AppStorage - broker string - apps []collection.AppCollector - dataStorage collection.DataStorage -} - -// NewCollector creates a new collector -func NewCollector(ctx log.Interface, appStorage AppStorage, broker string, dataStorage collection.DataStorage) Collector { - return &collector{ - ctx: ctx, - appStorage: appStorage, - broker: broker, - dataStorage: dataStorage, - } -} - -// StartError contains errors of starting applications -type StartError struct { - errors map[types.AppEUI]error -} - -func (e StartError) Error() string { - var s string - for eui, err := range e.errors { - s += fmt.Sprintf("%v: %v\n", eui, err) - } - return strings.TrimRight(s, "\n") -} - -func (c *collector) Start() ([]collection.AppCollector, error) { - apps, err := c.appStorage.GetAll() - if err != nil { - c.ctx.WithError(err).Error("Failed to get applications") - return nil, err - } - - startErrors := make(map[types.AppEUI]error) - for _, app := range apps { - ctx := c.ctx.WithField("appEUI", app.EUI) - ac := collection.NewMqttAppCollector(ctx, c.broker, app.EUI, app.Key, c.dataStorage) - ctx.Info("Starting app collector") - if err := ac.Start(); err != nil { - ctx.WithError(err).Warn("Failed to start app collector; ignoring app") - startErrors[app.EUI] = err - continue - } - c.apps = append(c.apps, ac) - } - - if len(startErrors) > 0 { - return c.apps, StartError{startErrors} - } - return c.apps, nil -} - -func (c *collector) Stop() { - for _, app := range c.apps { - app.Stop() - } - c.apps = nil -} diff --git a/core/collector/appStorage.go b/core/components/collector/appStorage.go similarity index 75% rename from core/collector/appStorage.go rename to core/components/collector/appStorage.go index 9b0bd23f6..ff1a20fb0 100644 --- a/core/collector/appStorage.go +++ b/core/components/collector/appStorage.go @@ -16,19 +16,13 @@ const ( appKey = "collector:app:%s" ) -// App represents a stored application -type App struct { - EUI types.AppEUI - Key string -} - // AppStorage provides storage for applications type AppStorage interface { Add(eui types.AppEUI) error Remove(eui types.AppEUI) error SetKey(eui types.AppEUI, key string) error - Get(eui types.AppEUI) (*App, error) - GetAll() ([]*App, error) + GetKey(eui types.AppEUI) (string, error) + List() ([]types.AppEUI, error) Reset() error Close() error } @@ -72,38 +66,30 @@ func (s *redisAppStorage) SetKey(eui types.AppEUI, key string) error { return s.client.HSet(makeKey(eui), "key", key).Err() } -func (s *redisAppStorage) Get(eui types.AppEUI) (*App, error) { +func (s *redisAppStorage) GetKey(eui types.AppEUI) (string, error) { m, err := s.client.HGetAllMap(makeKey(eui)).Result() if err == redis.Nil { - return nil, nil + return "", nil } else if err != nil { - return nil, err - } - app := &App{ - EUI: eui, - Key: m["key"], + return "", err } - return app, nil + return m["key"], nil } -func (s *redisAppStorage) GetAll() ([]*App, error) { - euis, err := s.client.SMembers(appsKey).Result() +func (s *redisAppStorage) List() ([]types.AppEUI, error) { + members, err := s.client.SMembers(appsKey).Result() if err != nil { return nil, err } - apps := make([]*App, len(euis)) - for i, k := range euis { + euis := make([]types.AppEUI, len(members)) + for i, k := range members { eui, err := types.ParseAppEUI(k) if err != nil { return nil, err } - app, err := s.Get(eui) - if err != nil { - return nil, err - } - apps[i] = app + euis[i] = eui } - return apps, nil + return euis, nil } func (s *redisAppStorage) Reset() error { diff --git a/core/collector/appStorage_test.go b/core/components/collector/appStorage_test.go similarity index 87% rename from core/collector/appStorage_test.go rename to core/components/collector/appStorage_test.go index 09443af80..36527dd7a 100644 --- a/core/collector/appStorage_test.go +++ b/core/components/collector/appStorage_test.go @@ -42,13 +42,12 @@ func TestSetKey(t *testing.T) { err := storage.SetKey(eui, key) a.So(err, ShouldBeNil) - app, err := storage.Get(eui) + fetchedKey, err := storage.GetKey(eui) a.So(err, ShouldBeNil) - a.So(app.EUI, ShouldEqual, eui) - a.So(app.Key, ShouldEqual, key) + a.So(fetchedKey, ShouldEqual, key) } -func TestGetAll(t *testing.T) { +func TestList(t *testing.T) { a := New(t) eui1, _ := types.ParseAppEUI("8000000000000001") @@ -62,13 +61,13 @@ func TestGetAll(t *testing.T) { a.So(err, ShouldBeNil) err = storage.Add(eui2) a.So(err, ShouldBeNil) - apps, err := storage.GetAll() + apps, err := storage.List() a.So(err, ShouldBeNil) a.So(apps, ShouldHaveLength, 2) err = storage.Remove(eui1) a.So(err, ShouldBeNil) - apps, err = storage.GetAll() + apps, err = storage.List() a.So(err, ShouldBeNil) a.So(apps, ShouldHaveLength, 1) } diff --git a/core/components/collector/collector.go b/core/components/collector/collector.go new file mode 100644 index 000000000..590ec040f --- /dev/null +++ b/core/components/collector/collector.go @@ -0,0 +1,127 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "errors" + "fmt" + "net" + "strings" + + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/core/collection" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// Collector collects data from the Handler and stores it +type Collector interface { + Start() (map[types.AppEUI]collection.AppCollector, error) + Stop() +} + +type collector struct { + ctx log.Interface + appStorage AppStorage + broker string + apps map[types.AppEUI]collection.AppCollector + dataStorage collection.DataStorage + netAddr string + server *grpc.Server +} + +// NewCollector creates a new collector +func NewCollector(ctx log.Interface, appStorage AppStorage, broker string, dataStorage collection.DataStorage, netAddr string) Collector { + c := &collector{ + ctx: ctx, + appStorage: appStorage, + broker: broker, + apps: map[types.AppEUI]collection.AppCollector{}, + dataStorage: dataStorage, + netAddr: netAddr, + server: grpc.NewServer(), + } + c.RegisterServer(c.server) + return c +} + +// StartError contains errors of starting applications +type StartError struct { + errors map[types.AppEUI]error +} + +func (e StartError) Error() string { + var s string + for eui, err := range e.errors { + s += fmt.Sprintf("%v: %v\n", eui, err) + } + return strings.TrimRight(s, "\n") +} + +func (c *collector) Start() (map[types.AppEUI]collection.AppCollector, error) { + // Start management service + lis, err := net.Listen("tcp", c.netAddr) + if err != nil { + c.ctx.WithError(err).Fatalf("Listen on %s failed", c.netAddr) + } + go c.server.Serve(lis) + + // Get applications + apps, err := c.appStorage.List() + if err != nil { + c.ctx.WithError(err).Error("Failed to get applications") + return nil, err + } + + // Start app collectors + startErrors := make(map[types.AppEUI]error) + for _, appEUI := range apps { + if err := c.startApp(appEUI); err != nil { + c.ctx.WithField("AppEUI", appEUI).WithError(err).Warn("Failed to start app collector; ignoring app") + startErrors[appEUI] = err + } + } + if len(startErrors) > 0 { + return c.apps, StartError{startErrors} + } + return c.apps, nil +} + +func (c *collector) Stop() { + c.server.Stop() + + for _, app := range c.apps { + app.Stop() + } + c.apps = nil +} + +func (c *collector) startApp(eui types.AppEUI) error { + ctx := c.ctx.WithField("AppEUI", eui) + + key, err := c.appStorage.GetKey(eui) + if err != nil { + return err + } else if key == "" { + return errors.New("Not found") + } + + ac := collection.NewMqttAppCollector(ctx, c.broker, eui, key, c.dataStorage) + ctx.Info("Starting app collector") + if err := ac.Start(); err != nil { + return err + } + c.apps[eui] = ac + return nil +} + +func (c *collector) stopApp(eui types.AppEUI) error { + app, ok := c.apps[eui] + if !ok { + return errors.New("Not found") + } + app.Stop() + return nil +} diff --git a/core/components/collector/collectorManager.go b/core/components/collector/collectorManager.go new file mode 100644 index 000000000..c04eb5e62 --- /dev/null +++ b/core/components/collector/collectorManager.go @@ -0,0 +1,75 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package collector + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +type collectorManagerServer struct { + collector *collector +} + +func (s *collectorManagerServer) GetApplications(ctx context.Context, req *core.GetApplicationsCollectorReq) (*core.GetApplicationsCollectorRes, error) { + res := new(core.GetApplicationsCollectorRes) + + apps, err := s.collector.appStorage.List() + if err != nil { + return res, err + } + + res.Applications = make([]*core.CollectorApplication, 0, len(apps)) + for _, eui := range apps { + res.Applications = append(res.Applications, &core.CollectorApplication{ + AppEUI: eui.Bytes(), + }) + } + + return res, nil +} + +func (s *collectorManagerServer) AddApplication(ctx context.Context, req *core.AddApplicationCollectorReq) (*core.AddApplicationCollectorRes, error) { + res := new(core.AddApplicationCollectorRes) + + var appEUI types.AppEUI + if err := appEUI.Unmarshal(req.AppEUI); err != nil { + return res, err + } + + if err := s.collector.appStorage.Add(appEUI); err != nil { + return res, err + } + if err := s.collector.appStorage.SetKey(appEUI, req.AppAccessKey); err != nil { + return res, err + } + + s.collector.startApp(appEUI) + + return res, nil +} + +func (s *collectorManagerServer) RemoveApplication(ctx context.Context, req *core.RemoveApplicationCollectorReq) (*core.RemoveApplicationCollectorRes, error) { + res := new(core.RemoveApplicationCollectorRes) + + var appEUI types.AppEUI + if err := appEUI.Unmarshal(req.AppEUI); err != nil { + return res, err + } + + s.collector.stopApp(appEUI) + + if err := s.collector.appStorage.Remove(appEUI); err != nil { + return res, err + } + + return res, nil +} + +func (c *collector) RegisterServer(s *grpc.Server) { + srv := &collectorManagerServer{c} + core.RegisterCollectorManagerServer(s, srv) +} diff --git a/core/collector/collector_test.go b/core/components/collector/collector_test.go similarity index 94% rename from core/collector/collector_test.go rename to core/components/collector/collector_test.go index cd7f97ae7..0cdd03acb 100644 --- a/core/collector/collector_test.go +++ b/core/components/collector/collector_test.go @@ -32,7 +32,7 @@ func TestCollection(t *testing.T) { err = appStorage.SetKey(eui, "secret") a.So(err, ShouldBeNil) - collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}) + collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}, "localhost:1783") collectors, err := collector.Start() defer collector.Stop() a.So(err, ShouldBeNil) diff --git a/core/collector/influxdb/influxdb.go b/core/components/collector/influxdb/influxdb.go similarity index 100% rename from core/collector/influxdb/influxdb.go rename to core/components/collector/influxdb/influxdb.go diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 4476f492e..b319f46bb 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -4,12 +4,15 @@ package handler import ( + "encoding/json" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/adapters/fields" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" ) -// ListDevices implements the core.HandlerManagerServer interface func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandlerReq) (*core.ListDevicesHandlerRes, error) { h.Ctx.Debug("Handle list devices request") @@ -35,6 +38,7 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle var abp []*core.HandlerABPDevice var otaa []*core.HandlerOTAADevice for _, dev := range entries { + // WTF? d := new(devEntry) *d = dev if dev.AppKey == nil { @@ -62,7 +66,6 @@ func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandle return &core.ListDevicesHandlerRes{ABP: abp, OTAA: otaa}, nil } -// UpsertABP implements the core.HandlerManager interface func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq) (*core.UpsertABPHandlerRes, error) { h.Ctx.Debug("Handle upsert ABP request") @@ -108,7 +111,6 @@ func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq return new(core.UpsertABPHandlerRes), nil } -// UpsertOTAA implements the core.HandlerManager interface func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerReq) (*core.UpsertOTAAHandlerRes, error) { h.Ctx.Debug("Handle upsert OTAA request") @@ -147,7 +149,6 @@ func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerR return new(core.UpsertOTAAHandlerRes), nil } -// GetDefaultDevice implements the core.HandlerManager Interface func (h component) GetDefaultDevice(bctx context.Context, req *core.GetDefaultDeviceReq) (*core.GetDefaultDeviceRes, error) { h.Ctx.Debug("Handle get default device request") @@ -181,7 +182,6 @@ func (h component) GetDefaultDevice(bctx context.Context, req *core.GetDefaultDe return &core.GetDefaultDeviceRes{AppKey: entry.AppKey[:]}, nil } -// SetDefaultDevice implements the core.HandlerManager interface func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDeviceReq) (*core.SetDefaultDeviceRes, error) { h.Ctx.Debug("Handle set default device request") @@ -217,3 +217,82 @@ func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDe return new(core.SetDefaultDeviceRes), nil } + +func (h component) GetPayloadFunctions(ctx context.Context, req *core.GetPayloadFunctionsReq) (*core.GetPayloadFunctionsRes, error) { + res := new(core.GetPayloadFunctionsRes) + + adapter, ok := h.AppAdapter.(fields.Adapter) + if !ok { + return res, errors.New(errors.Structural, "Invalid adapter") + } + + var appEUI types.AppEUI + appEUI.Unmarshal(req.AppEUI) + functions, err := adapter.Storage().GetFunctions(appEUI) + if err != nil { + return res, err + } + if functions == nil { + return res, errors.New(errors.Operational, "Not found") + } + + res.Decoder = functions.Decoder + res.Converter = functions.Converter + res.Validator = functions.Validator + return res, nil +} + +func (h component) SetPayloadFunctions(ctx context.Context, req *core.SetPayloadFunctionsReq) (*core.SetPayloadFunctionsRes, error) { + res := new(core.SetPayloadFunctionsRes) + + adapter, ok := h.AppAdapter.(fields.Adapter) + if !ok { + return res, errors.New(errors.Structural, "Invalid adapter") + } + + var appEUI types.AppEUI + appEUI.Unmarshal(req.AppEUI) + err := adapter.Storage().SetFunctions(appEUI, &fields.Functions{ + Decoder: req.Decoder, + Converter: req.Converter, + Validator: req.Validator, + }) + if err != nil { + return res, err + } + + return res, nil +} + +func (h component) TestPayloadFunctions(ctx context.Context, req *core.TestPayloadFunctionsReq) (*core.TestPayloadFunctionsRes, error) { + res := new(core.TestPayloadFunctionsRes) + + adapter, ok := h.AppAdapter.(fields.Adapter) + if !ok { + return res, errors.New(errors.Structural, "Invalid adapter") + } + + var appEUI types.AppEUI + appEUI.Unmarshal(req.AppEUI) + functions, err := adapter.Storage().GetFunctions(appEUI) + if err != nil { + return res, err + } + if functions == nil { + return res, errors.New(errors.Operational, "Not found") + } + + fields, valid, err := functions.Process(req.Payload) + if err != nil { + return res, err + } + + buf, err := json.Marshal(fields) + if err != nil { + return res, err + } + + res.Valid = valid + res.Fields = string(buf) + return res, nil +} diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index b444ebe54..248c51384 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -173,6 +173,78 @@ func (*SetDefaultDeviceReq) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{10} } +type GetPayloadFunctionsReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` +} + +func (m *GetPayloadFunctionsReq) Reset() { *m = GetPayloadFunctionsReq{} } +func (m *GetPayloadFunctionsReq) String() string { return proto.CompactTextString(m) } +func (*GetPayloadFunctionsReq) ProtoMessage() {} +func (*GetPayloadFunctionsReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{11} +} + +type GetPayloadFunctionsRes struct { + Decoder string `protobuf:"bytes,1,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` + Converter string `protobuf:"bytes,2,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` + Validator string `protobuf:"bytes,3,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` +} + +func (m *GetPayloadFunctionsRes) Reset() { *m = GetPayloadFunctionsRes{} } +func (m *GetPayloadFunctionsRes) String() string { return proto.CompactTextString(m) } +func (*GetPayloadFunctionsRes) ProtoMessage() {} +func (*GetPayloadFunctionsRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{12} +} + +type SetPayloadFunctionsReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Decoder string `protobuf:"bytes,11,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` + Converter string `protobuf:"bytes,12,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` + Validator string `protobuf:"bytes,13,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` +} + +func (m *SetPayloadFunctionsReq) Reset() { *m = SetPayloadFunctionsReq{} } +func (m *SetPayloadFunctionsReq) String() string { return proto.CompactTextString(m) } +func (*SetPayloadFunctionsReq) ProtoMessage() {} +func (*SetPayloadFunctionsReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{13} +} + +type SetPayloadFunctionsRes struct { +} + +func (m *SetPayloadFunctionsRes) Reset() { *m = SetPayloadFunctionsRes{} } +func (m *SetPayloadFunctionsRes) String() string { return proto.CompactTextString(m) } +func (*SetPayloadFunctionsRes) ProtoMessage() {} +func (*SetPayloadFunctionsRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{14} +} + +type TestPayloadFunctionsReq struct { + AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Payload []byte `protobuf:"bytes,11,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` +} + +func (m *TestPayloadFunctionsReq) Reset() { *m = TestPayloadFunctionsReq{} } +func (m *TestPayloadFunctionsReq) String() string { return proto.CompactTextString(m) } +func (*TestPayloadFunctionsReq) ProtoMessage() {} +func (*TestPayloadFunctionsReq) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{15} +} + +type TestPayloadFunctionsRes struct { + Fields string `protobuf:"bytes,1,opt,name=Fields,json=fields,proto3" json:"Fields,omitempty"` + Valid bool `protobuf:"varint,2,opt,name=Valid,json=valid,proto3" json:"Valid,omitempty"` +} + +func (m *TestPayloadFunctionsRes) Reset() { *m = TestPayloadFunctionsRes{} } +func (m *TestPayloadFunctionsRes) String() string { return proto.CompactTextString(m) } +func (*TestPayloadFunctionsRes) ProtoMessage() {} +func (*TestPayloadFunctionsRes) Descriptor() ([]byte, []int) { + return fileDescriptorHandlerManager, []int{16} +} + type SetDefaultDeviceRes struct { } @@ -180,7 +252,7 @@ func (m *SetDefaultDeviceRes) Reset() { *m = SetDefaultDeviceRes{} } func (m *SetDefaultDeviceRes) String() string { return proto.CompactTextString(m) } func (*SetDefaultDeviceRes) ProtoMessage() {} func (*SetDefaultDeviceRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{11} + return fileDescriptorHandlerManager, []int{17} } func init() { @@ -195,6 +267,12 @@ func init() { proto.RegisterType((*GetDefaultDeviceReq)(nil), "core.GetDefaultDeviceReq") proto.RegisterType((*GetDefaultDeviceRes)(nil), "core.GetDefaultDeviceRes") proto.RegisterType((*SetDefaultDeviceReq)(nil), "core.SetDefaultDeviceReq") + proto.RegisterType((*GetPayloadFunctionsReq)(nil), "core.GetPayloadFunctionsReq") + proto.RegisterType((*GetPayloadFunctionsRes)(nil), "core.GetPayloadFunctionsRes") + proto.RegisterType((*SetPayloadFunctionsReq)(nil), "core.SetPayloadFunctionsReq") + proto.RegisterType((*SetPayloadFunctionsRes)(nil), "core.SetPayloadFunctionsRes") + proto.RegisterType((*TestPayloadFunctionsReq)(nil), "core.TestPayloadFunctionsReq") + proto.RegisterType((*TestPayloadFunctionsRes)(nil), "core.TestPayloadFunctionsRes") proto.RegisterType((*SetDefaultDeviceRes)(nil), "core.SetDefaultDeviceRes") } @@ -214,6 +292,9 @@ type HandlerManagerClient interface { ListDevices(ctx context.Context, in *ListDevicesHandlerReq, opts ...grpc.CallOption) (*ListDevicesHandlerRes, error) GetDefaultDevice(ctx context.Context, in *GetDefaultDeviceReq, opts ...grpc.CallOption) (*GetDefaultDeviceRes, error) SetDefaultDevice(ctx context.Context, in *SetDefaultDeviceReq, opts ...grpc.CallOption) (*SetDefaultDeviceRes, error) + GetPayloadFunctions(ctx context.Context, in *GetPayloadFunctionsReq, opts ...grpc.CallOption) (*GetPayloadFunctionsRes, error) + SetPayloadFunctions(ctx context.Context, in *SetPayloadFunctionsReq, opts ...grpc.CallOption) (*SetPayloadFunctionsRes, error) + TestPayloadFunctions(ctx context.Context, in *TestPayloadFunctionsReq, opts ...grpc.CallOption) (*TestPayloadFunctionsRes, error) } type handlerManagerClient struct { @@ -269,6 +350,33 @@ func (c *handlerManagerClient) SetDefaultDevice(ctx context.Context, in *SetDefa return out, nil } +func (c *handlerManagerClient) GetPayloadFunctions(ctx context.Context, in *GetPayloadFunctionsReq, opts ...grpc.CallOption) (*GetPayloadFunctionsRes, error) { + out := new(GetPayloadFunctionsRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/GetPayloadFunctions", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerManagerClient) SetPayloadFunctions(ctx context.Context, in *SetPayloadFunctionsReq, opts ...grpc.CallOption) (*SetPayloadFunctionsRes, error) { + out := new(SetPayloadFunctionsRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/SetPayloadFunctions", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *handlerManagerClient) TestPayloadFunctions(ctx context.Context, in *TestPayloadFunctionsReq, opts ...grpc.CallOption) (*TestPayloadFunctionsRes, error) { + out := new(TestPayloadFunctionsRes) + err := grpc.Invoke(ctx, "/core.HandlerManager/TestPayloadFunctions", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for HandlerManager service type HandlerManagerServer interface { @@ -277,6 +385,9 @@ type HandlerManagerServer interface { ListDevices(context.Context, *ListDevicesHandlerReq) (*ListDevicesHandlerRes, error) GetDefaultDevice(context.Context, *GetDefaultDeviceReq) (*GetDefaultDeviceRes, error) SetDefaultDevice(context.Context, *SetDefaultDeviceReq) (*SetDefaultDeviceRes, error) + GetPayloadFunctions(context.Context, *GetPayloadFunctionsReq) (*GetPayloadFunctionsRes, error) + SetPayloadFunctions(context.Context, *SetPayloadFunctionsReq) (*SetPayloadFunctionsRes, error) + TestPayloadFunctions(context.Context, *TestPayloadFunctionsReq) (*TestPayloadFunctionsRes, error) } func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { @@ -373,6 +484,60 @@ func _HandlerManager_SetDefaultDevice_Handler(srv interface{}, ctx context.Conte return interceptor(ctx, in, info, handler) } +func _HandlerManager_GetPayloadFunctions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetPayloadFunctionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerManagerServer).GetPayloadFunctions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/GetPayloadFunctions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).GetPayloadFunctions(ctx, req.(*GetPayloadFunctionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _HandlerManager_SetPayloadFunctions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(SetPayloadFunctionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerManagerServer).SetPayloadFunctions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/SetPayloadFunctions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).SetPayloadFunctions(ctx, req.(*SetPayloadFunctionsReq)) + } + return interceptor(ctx, in, info, handler) +} + +func _HandlerManager_TestPayloadFunctions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(TestPayloadFunctionsReq) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerManagerServer).TestPayloadFunctions(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/core.HandlerManager/TestPayloadFunctions", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).TestPayloadFunctions(ctx, req.(*TestPayloadFunctionsReq)) + } + return interceptor(ctx, in, info, handler) +} + var _HandlerManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "core.HandlerManager", HandlerType: (*HandlerManagerServer)(nil), @@ -397,6 +562,18 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ MethodName: "SetDefaultDevice", Handler: _HandlerManager_SetDefaultDevice_Handler, }, + { + MethodName: "GetPayloadFunctions", + Handler: _HandlerManager_GetPayloadFunctions_Handler, + }, + { + MethodName: "SetPayloadFunctions", + Handler: _HandlerManager_SetPayloadFunctions_Handler, + }, + { + MethodName: "TestPayloadFunctions", + Handler: _HandlerManager_TestPayloadFunctions_Handler, + }, }, Streams: []grpc.StreamDesc{}, } @@ -803,6 +980,190 @@ func (m *SetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *GetPayloadFunctionsReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + return i, nil +} + +func (m *GetPayloadFunctionsRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetPayloadFunctionsRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Decoder) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Decoder))) + i += copy(data[i:], m.Decoder) + } + if len(m.Converter) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Converter))) + i += copy(data[i:], m.Converter) + } + if len(m.Validator) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Validator))) + i += copy(data[i:], m.Validator) + } + return i, nil +} + +func (m *SetPayloadFunctionsReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SetPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + if len(m.Decoder) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Decoder))) + i += copy(data[i:], m.Decoder) + } + if len(m.Converter) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Converter))) + i += copy(data[i:], m.Converter) + } + if len(m.Validator) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Validator))) + i += copy(data[i:], m.Validator) + } + return i, nil +} + +func (m *SetPayloadFunctionsRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SetPayloadFunctionsRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *TestPayloadFunctionsReq) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TestPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEUI) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) + i += copy(data[i:], m.AppEUI) + } + if len(m.Payload) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + return i, nil +} + +func (m *TestPayloadFunctionsRes) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TestPayloadFunctionsRes) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Fields) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Fields))) + i += copy(data[i:], m.Fields) + } + if m.Valid { + data[i] = 0x10 + i++ + if m.Valid { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + return i, nil +} + func (m *SetDefaultDeviceRes) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -1044,29 +1405,112 @@ func (m *SetDefaultDeviceReq) Size() (n int) { return n } -func (m *SetDefaultDeviceRes) Size() (n int) { +func (m *GetPayloadFunctionsReq) Size() (n int) { var l int _ = l + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } return n } -func sovHandlerManager(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } +func (m *GetPayloadFunctionsRes) Size() (n int) { + var l int + _ = l + l = len(m.Decoder) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Converter) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) } return n } -func sozHandlerManager(x uint64) (n int) { - return sovHandlerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { + +func (m *SetPayloadFunctionsReq) Size() (n int) { + var l int + _ = l + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Decoder) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Converter) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + return n +} + +func (m *SetPayloadFunctionsRes) Size() (n int) { + var l int + _ = l + return n +} + +func (m *TestPayloadFunctionsReq) Size() (n int) { + var l int + _ = l + l = len(m.AppEUI) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + return n +} + +func (m *TestPayloadFunctionsRes) Size() (n int) { + var l int + _ = l + l = len(m.Fields) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } + if m.Valid { + n += 2 + } + return n +} + +func (m *SetDefaultDeviceRes) Size() (n int) { + var l int + _ = l + return n +} + +func sovHandlerManager(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozHandlerManager(x uint64) (n int) { + return sovHandlerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { preIndex := iNdEx var wire uint64 for shift := uint(0); ; shift += 7 { @@ -2554,6 +2998,653 @@ func (m *SetDefaultDeviceReq) Unmarshal(data []byte) error { } return nil } +func (m *GetPayloadFunctionsReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetPayloadFunctionsReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetPayloadFunctionsReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetPayloadFunctionsRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetPayloadFunctionsRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetPayloadFunctionsRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Decoder = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Converter = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SetPayloadFunctionsReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SetPayloadFunctionsReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SetPayloadFunctionsReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Decoder = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Converter = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SetPayloadFunctionsRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SetPayloadFunctionsRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SetPayloadFunctionsRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TestPayloadFunctionsReq) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TestPayloadFunctionsReq: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TestPayloadFunctionsReq: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) + if m.AppEUI == nil { + m.AppEUI = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TestPayloadFunctionsRes) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TestPayloadFunctionsRes: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TestPayloadFunctionsRes: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Valid", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Valid = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipHandlerManager(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandlerManager + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *SetDefaultDeviceRes) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -2710,38 +3801,49 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 520 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xdd, 0x6e, 0xd3, 0x30, - 0x14, 0x26, 0xcd, 0x5f, 0x77, 0xc6, 0x50, 0x71, 0xd7, 0x12, 0x82, 0x34, 0xa1, 0x5c, 0x55, 0x42, - 0xf4, 0x62, 0x3c, 0x41, 0xba, 0xee, 0x07, 0xf1, 0x37, 0xa5, 0xeb, 0x15, 0x17, 0xc8, 0x2c, 0xce, - 0x98, 0x56, 0x92, 0x10, 0x07, 0x26, 0xde, 0x84, 0x37, 0x40, 0xe2, 0x3d, 0x90, 0xb8, 0xe4, 0x11, - 0x10, 0xbc, 0x03, 0xd7, 0xd8, 0xb1, 0x59, 0x9d, 0x61, 0x23, 0x51, 0x76, 0x11, 0xa9, 0xdf, 0xf9, - 0x4e, 0x3f, 0x9f, 0xf3, 0x9d, 0x63, 0xc3, 0xe0, 0x15, 0xce, 0xd3, 0x05, 0xa9, 0x5e, 0xbc, 0xc6, - 0x39, 0x3e, 0x21, 0xd5, 0xb8, 0xac, 0x8a, 0xba, 0x40, 0xce, 0x71, 0x51, 0x91, 0xa8, 0x86, 0xcd, - 0x79, 0x49, 0x49, 0x55, 0x3f, 0x3b, 0x8a, 0xe3, 0x03, 0x91, 0x98, 0x90, 0x37, 0x68, 0x13, 0xdc, - 0xa3, 0xe2, 0x8c, 0xe4, 0x81, 0x75, 0xd7, 0x1a, 0xad, 0x25, 0x6e, 0xcd, 0x01, 0x1a, 0x82, 0x17, - 0x97, 0xe5, 0xee, 0xfc, 0x61, 0xd0, 0x61, 0xe1, 0xeb, 0x89, 0x87, 0x1b, 0xc4, 0xe3, 0x53, 0xf2, - 0x8e, 0xc7, 0x6d, 0x11, 0x4f, 0x1b, 0x24, 0xf3, 0x1f, 0x91, 0xf7, 0x81, 0x73, 0x91, 0xcf, 0x50, - 0x34, 0xd4, 0x9e, 0x4a, 0xa3, 0x8f, 0x16, 0xf4, 0x05, 0x11, 0x4f, 0x0e, 0x57, 0xae, 0x26, 0x00, - 0x9f, 0x55, 0x13, 0xa7, 0x69, 0x25, 0xcb, 0xf1, 0x53, 0x01, 0x39, 0xf3, 0xf4, 0xfc, 0x6c, 0xb6, - 0x2c, 0xc8, 0xcf, 0x05, 0xe4, 0x0c, 0xd3, 0x6a, 0x18, 0x57, 0x30, 0x58, 0x40, 0x7e, 0xf6, 0xde, - 0x02, 0x9f, 0xd0, 0xc0, 0x63, 0xf1, 0x8d, 0xc4, 0xcd, 0x38, 0x88, 0x06, 0xba, 0x42, 0x69, 0xb4, - 0x0b, 0x83, 0xc7, 0xa7, 0xb4, 0x66, 0xc7, 0x9f, 0x1e, 0x13, 0xba, 0x6a, 0x07, 0x51, 0xae, 0x97, - 0xa1, 0xe8, 0x1e, 0x38, 0xdc, 0x32, 0xa6, 0x62, 0x8f, 0xd6, 0xb7, 0x6f, 0x8d, 0xf9, 0x0c, 0xc7, - 0x92, 0xe7, 0x84, 0xf8, 0x47, 0xe2, 0x14, 0xec, 0x37, 0x1a, 0x81, 0xcd, 0xaa, 0x63, 0xd2, 0x3c, - 0x77, 0xd8, 0xca, 0x65, 0x71, 0x99, 0x6a, 0xe3, 0xc9, 0x61, 0xf4, 0xc9, 0x82, 0xde, 0x65, 0x46, - 0xb5, 0xb1, 0x63, 0xb4, 0xd1, 0x36, 0xda, 0xe8, 0xb4, 0x6d, 0x64, 0xad, 0xee, 0xed, 0xe4, 0xf5, - 0xbc, 0x6c, 0xfc, 0xdd, 0x48, 0xbc, 0xac, 0x41, 0x28, 0x84, 0x2e, 0x8f, 0x4f, 0x8b, 0xf3, 0x5c, - 0x3a, 0xdc, 0xcd, 0x24, 0x5e, 0x5a, 0xef, 0xab, 0xd6, 0x7f, 0xb6, 0xe0, 0xe6, 0x1f, 0x2d, 0x2b, - 0x2b, 0x68, 0xb5, 0x56, 0xf0, 0xca, 0xbb, 0x90, 0x0b, 0xed, 0xaa, 0x0b, 0xad, 0x74, 0xe7, 0x1b, - 0xbb, 0xeb, 0xb6, 0xbb, 0x8b, 0x76, 0xa0, 0xbf, 0x4f, 0xd8, 0x8c, 0x33, 0xfc, 0x76, 0x21, 0x47, - 0xfd, 0xef, 0x9b, 0x72, 0x5f, 0x27, 0x42, 0x95, 0x3a, 0xad, 0xd6, 0xc5, 0x7b, 0x0e, 0xfd, 0xd9, - 0xff, 0x9e, 0xa9, 0x88, 0xdb, 0x2d, 0xf1, 0x81, 0x4e, 0x9c, 0x6e, 0xff, 0xec, 0xc0, 0x0d, 0x39, - 0xaf, 0x27, 0xe2, 0x05, 0x42, 0x53, 0x80, 0xe5, 0xfd, 0x47, 0xa1, 0x58, 0x4d, 0xdd, 0x3b, 0x14, - 0x9a, 0x39, 0x8a, 0x62, 0x58, 0xbb, 0xb8, 0x83, 0xe8, 0xb6, 0x9a, 0xd8, 0x7a, 0x3d, 0x42, 0x23, - 0x45, 0xd1, 0x3e, 0xac, 0x2b, 0x17, 0x0d, 0xdd, 0x11, 0x99, 0xda, 0x2b, 0x1c, 0xfe, 0x85, 0xa4, - 0xe8, 0x00, 0x7a, 0x97, 0xe7, 0xf0, 0xbb, 0x24, 0xcd, 0x90, 0x43, 0x23, 0xd5, 0x28, 0xcd, 0x0c, - 0x4a, 0x33, 0xb3, 0x92, 0xc6, 0xf8, 0x49, 0xef, 0xcb, 0xf7, 0x2d, 0xeb, 0x2b, 0xfb, 0xbe, 0xb1, - 0xef, 0xc3, 0x8f, 0xad, 0x6b, 0x2f, 0xbd, 0xe6, 0xe9, 0x7f, 0xf0, 0x2b, 0x00, 0x00, 0xff, 0xff, - 0x3b, 0xea, 0x11, 0x02, 0x13, 0x06, 0x00, 0x00, + // 701 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x4e, 0xd4, 0x40, + 0x14, 0xb6, 0xec, 0xff, 0x01, 0x0c, 0x0e, 0xb0, 0xd4, 0x8a, 0xc4, 0xf4, 0x8a, 0xc4, 0x48, 0x0c, + 0x3e, 0x41, 0xf9, 0x59, 0x30, 0xf8, 0x83, 0xed, 0xe2, 0x8d, 0x17, 0x66, 0xdc, 0xce, 0xe2, 0x86, + 0xda, 0xd6, 0x4e, 0x81, 0xf0, 0x02, 0x3e, 0x83, 0x6f, 0x60, 0xe2, 0x7b, 0x98, 0x78, 0xe9, 0x23, + 0x18, 0x7d, 0x03, 0x9f, 0xc0, 0xf9, 0x83, 0x6d, 0x97, 0x99, 0x26, 0x8b, 0x5e, 0x34, 0xd9, 0x73, + 0xbe, 0xd3, 0x6f, 0xbe, 0x39, 0xf3, 0x9d, 0xe9, 0xc2, 0xf2, 0x7b, 0x1c, 0x87, 0x11, 0xc9, 0xde, + 0x7e, 0xc0, 0x31, 0x3e, 0x26, 0xd9, 0x46, 0x9a, 0x25, 0x79, 0x82, 0xea, 0x83, 0x24, 0x23, 0x6e, + 0x0e, 0x4b, 0x47, 0x29, 0x25, 0x59, 0xfe, 0xb2, 0xef, 0x79, 0xfb, 0xb2, 0xd0, 0x27, 0x1f, 0xd1, + 0x12, 0x34, 0xfa, 0xc9, 0x09, 0x89, 0x6d, 0xeb, 0x81, 0xb5, 0xde, 0xf1, 0x1b, 0x39, 0x0f, 0x50, + 0x17, 0x9a, 0x5e, 0x9a, 0xee, 0x1e, 0x3d, 0xb5, 0x67, 0x58, 0x7a, 0xce, 0x6f, 0x62, 0x11, 0xf1, + 0xfc, 0x0e, 0x39, 0xe3, 0xf9, 0x9a, 0xcc, 0x87, 0x22, 0x52, 0xf5, 0x07, 0xe4, 0xc2, 0xae, 0x5f, + 0xd5, 0xb3, 0xc8, 0xed, 0x6a, 0x57, 0xa5, 0xee, 0x17, 0x0b, 0x16, 0x25, 0xe0, 0x6d, 0x1d, 0xde, + 0x58, 0x8d, 0x0d, 0x2d, 0xa6, 0xc6, 0x0b, 0xc3, 0x4c, 0xc9, 0x69, 0x85, 0x32, 0xe4, 0xc8, 0x8b, + 0xf3, 0x93, 0x60, 0x2c, 0xa8, 0x15, 0xcb, 0x90, 0x23, 0x8c, 0x4b, 0x20, 0x0d, 0x89, 0x60, 0x19, + 0xf2, 0xb5, 0x7b, 0x11, 0x3e, 0xa6, 0x76, 0x93, 0xe5, 0xe7, 0xfd, 0xc6, 0x90, 0x07, 0xee, 0xb2, + 0x4e, 0x28, 0x75, 0x77, 0x61, 0xf9, 0xd9, 0x88, 0xe6, 0x6c, 0xf9, 0xd1, 0x80, 0xd0, 0x9b, 0xee, + 0xc0, 0x8d, 0xf5, 0x34, 0x14, 0x3d, 0x84, 0x3a, 0x6f, 0x19, 0x63, 0xa9, 0xad, 0xcf, 0x6e, 0xae, + 0x6c, 0xf0, 0x33, 0xdc, 0x50, 0x38, 0x07, 0xe4, 0x1b, 0x7e, 0x3d, 0x61, 0xbf, 0xd1, 0x3a, 0xd4, + 0x98, 0x3a, 0x46, 0xcd, 0x6b, 0xbb, 0xa5, 0x5a, 0x96, 0x57, 0xa5, 0x35, 0xbc, 0x75, 0xe8, 0x7e, + 0xb5, 0x60, 0x61, 0x12, 0x29, 0xb6, 0x71, 0xc6, 0xd8, 0xc6, 0x9a, 0xb1, 0x8d, 0xf5, 0x72, 0x1b, + 0xd9, 0x56, 0x7b, 0xdb, 0x71, 0x7e, 0x94, 0x8a, 0xfe, 0xce, 0xfb, 0xcd, 0xa1, 0x88, 0x90, 0x03, + 0x6d, 0x9e, 0xdf, 0x49, 0xce, 0x63, 0xd5, 0xe1, 0xf6, 0x50, 0xc5, 0xe3, 0xd6, 0xb7, 0x8a, 0xad, + 0xff, 0x66, 0xc1, 0x9d, 0x6b, 0x5b, 0x2e, 0x58, 0xd0, 0x2a, 0x59, 0xf0, 0xbf, 0xef, 0x42, 0x19, + 0xba, 0x51, 0x34, 0x74, 0x61, 0x77, 0x2d, 0xe3, 0xee, 0xda, 0xe5, 0xdd, 0xb9, 0xdb, 0xb0, 0xb8, + 0x47, 0xd8, 0x19, 0x0f, 0xf1, 0x69, 0xa4, 0x8e, 0x7a, 0x7a, 0xa7, 0x3c, 0xd2, 0x91, 0xd0, 0x82, + 0x4e, 0xab, 0x34, 0x78, 0x6f, 0x60, 0x31, 0xf8, 0xd7, 0x35, 0x0b, 0xe4, 0xb5, 0x12, 0xf9, 0x63, + 0xe8, 0x32, 0x2d, 0x87, 0xf8, 0x22, 0x4a, 0x70, 0xd8, 0x3b, 0x8d, 0x07, 0xf9, 0x28, 0x89, 0x29, + 0xe7, 0x1f, 0x33, 0x59, 0x13, 0x3e, 0xd7, 0xbf, 0x41, 0xe5, 0xb1, 0x0d, 0x92, 0x90, 0x64, 0x4a, + 0x13, 0x3b, 0x36, 0x11, 0xa2, 0x55, 0xe8, 0x6c, 0x27, 0xf1, 0x19, 0x1b, 0x3d, 0x22, 0x8f, 0xb4, + 0xe3, 0x77, 0x06, 0x97, 0x09, 0x8e, 0xbe, 0xc6, 0xd1, 0x28, 0xc4, 0x79, 0x22, 0xa7, 0x9f, 0xa1, + 0x67, 0x97, 0x09, 0xf7, 0x93, 0x05, 0xdd, 0x60, 0x2a, 0x89, 0x45, 0x21, 0xb3, 0x15, 0x42, 0xe6, + 0x2a, 0x85, 0xcc, 0x4f, 0x0a, 0xb1, 0x0d, 0x3a, 0xa8, 0x7b, 0x00, 0x2b, 0x7d, 0x42, 0xa7, 0x95, + 0xa8, 0xca, 0x85, 0x44, 0x66, 0xd7, 0x54, 0x86, 0xee, 0x9e, 0x89, 0x4c, 0x38, 0xa4, 0x37, 0x22, + 0x51, 0x48, 0x55, 0x7f, 0x9b, 0x43, 0x11, 0x71, 0x2b, 0x08, 0xdd, 0xa2, 0xb5, 0x6d, 0xbf, 0x21, + 0x34, 0xf3, 0xeb, 0xee, 0xba, 0x6f, 0xe8, 0xe6, 0x9f, 0x3a, 0xdc, 0x56, 0xa3, 0xf8, 0x5c, 0x7e, + 0x5c, 0xd0, 0x0e, 0xc0, 0xf8, 0x6a, 0x47, 0x8e, 0xbc, 0x75, 0x74, 0x9f, 0x18, 0xc7, 0x8c, 0x51, + 0xe4, 0x41, 0xe7, 0xea, 0x7a, 0x45, 0x77, 0x8b, 0x85, 0xa5, 0x0f, 0x83, 0x63, 0x84, 0x28, 0xda, + 0x83, 0xd9, 0xc2, 0x1d, 0x8a, 0xee, 0xc9, 0x4a, 0xed, 0xed, 0xec, 0x54, 0x80, 0x14, 0xed, 0xc3, + 0xc2, 0xe4, 0x88, 0x5d, 0x4a, 0xd2, 0xcc, 0xaf, 0x63, 0x84, 0x04, 0x53, 0x60, 0x60, 0x0a, 0xcc, + 0x4c, 0x9a, 0xc6, 0xa3, 0x57, 0x62, 0xec, 0x27, 0xcf, 0x15, 0xad, 0x5e, 0xad, 0xad, 0xf1, 0x8f, + 0x53, 0x85, 0x0a, 0xca, 0xc0, 0x4c, 0x19, 0x54, 0x52, 0xea, 0xbd, 0x8c, 0xfa, 0xb0, 0xa4, 0xb3, + 0x1f, 0xba, 0x2f, 0xdf, 0x32, 0xf8, 0xdc, 0xa9, 0x84, 0xe9, 0xd6, 0xc2, 0xf7, 0x5f, 0x6b, 0xd6, + 0x0f, 0xf6, 0xfc, 0x64, 0xcf, 0xe7, 0xdf, 0x6b, 0xb7, 0xde, 0x35, 0xc5, 0x3f, 0x9a, 0x27, 0x7f, + 0x03, 0x00, 0x00, 0xff, 0xff, 0xae, 0x14, 0xff, 0x9e, 0xea, 0x08, 0x00, 0x00, } diff --git a/core/protos/collector.proto b/core/protos/collector.proto new file mode 100644 index 000000000..bb2f1f456 --- /dev/null +++ b/core/protos/collector.proto @@ -0,0 +1,35 @@ +syntax = "proto3"; + +package core; + +message GetApplicationsCollectorReq { +} + +message GetApplicationsCollectorRes { + repeated CollectorApplication Applications = 1; +} + +message CollectorApplication { + bytes AppEUI = 1; +} + +message AddApplicationCollectorReq { + bytes AppEUI = 1; + string AppAccessKey = 2; +} + +message AddApplicationCollectorRes { +} + +message RemoveApplicationCollectorReq { + bytes AppEUI = 1; +} + +message RemoveApplicationCollectorRes { +} + +service CollectorManager { + rpc GetApplications (GetApplicationsCollectorReq) returns (GetApplicationsCollectorRes); + rpc AddApplication (AddApplicationCollectorReq) returns (AddApplicationCollectorRes); + rpc RemoveApplication (RemoveApplicationCollectorReq) returns (RemoveApplicationCollectorRes); +} diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index bbb92ee4e..ebc403f95 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -52,12 +52,12 @@ message HandlerOTAADevice { } message GetDefaultDeviceReq { - string Token = 1; - bytes AppEUI = 2; + string Token = 1; + bytes AppEUI = 2; } message GetDefaultDeviceRes { - bytes AppKey = 1; + bytes AppKey = 1; } message SetDefaultDeviceReq { @@ -66,12 +66,45 @@ message SetDefaultDeviceReq { bytes AppKey = 3; } +message GetPayloadFunctionsReq { + bytes AppEUI = 1; +} + +message GetPayloadFunctionsRes { + string Decoder = 1; + string Converter = 2; + string Validator = 3; +} + +message SetPayloadFunctionsReq { + bytes AppEUI = 1; + string Decoder = 11; + string Converter = 12; + string Validator = 13; +} + +message SetPayloadFunctionsRes { +} + +message TestPayloadFunctionsReq { + bytes AppEUI = 1; + bytes Payload = 11; +} + +message TestPayloadFunctionsRes { + string Fields = 1; + bool Valid = 2; +} + message SetDefaultDeviceRes {} service HandlerManager { - rpc UpsertOTAA (UpsertOTAAHandlerReq) returns (UpsertOTAAHandlerRes); - rpc UpsertABP (UpsertABPHandlerReq) returns (UpsertABPHandlerRes); - rpc ListDevices (ListDevicesHandlerReq) returns (ListDevicesHandlerRes); - rpc GetDefaultDevice (GetDefaultDeviceReq) returns (GetDefaultDeviceRes); - rpc SetDefaultDevice (SetDefaultDeviceReq) returns (SetDefaultDeviceRes); + rpc UpsertOTAA (UpsertOTAAHandlerReq) returns (UpsertOTAAHandlerRes); + rpc UpsertABP (UpsertABPHandlerReq) returns (UpsertABPHandlerRes); + rpc ListDevices (ListDevicesHandlerReq) returns (ListDevicesHandlerRes); + rpc GetDefaultDevice (GetDefaultDeviceReq) returns (GetDefaultDeviceRes); + rpc SetDefaultDevice (SetDefaultDeviceReq) returns (SetDefaultDeviceRes); + rpc GetPayloadFunctions (GetPayloadFunctionsReq) returns (GetPayloadFunctionsRes); + rpc SetPayloadFunctions (SetPayloadFunctionsReq) returns (SetPayloadFunctionsRes); + rpc TestPayloadFunctions (TestPayloadFunctionsReq) returns (TestPayloadFunctionsRes); } diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index ee6bbeacf..736c2b6d0 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -241,7 +241,6 @@ var applicationsUseCmd = &cobra.Command{ } ctx.Infof("You are now using application %s.", appEUI) - }, } diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go new file mode 100644 index 000000000..8ad5dbfe0 --- /dev/null +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -0,0 +1,149 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "io/ioutil" + + "golang.org/x/net/context" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +// applicationsPayloadFunctionsCmd represents the applicationsPayloadFunctions command +var applicationsPayloadFunctionsCmd = &cobra.Command{ + Use: "pf", + Short: "Show the payload functions", + Long: `ttnctl applications pf shows the payload functions for decoding, +converting and validating binary payload. +`, + Run: func(cmd *cobra.Command, args []string) { + appEUI := util.GetAppEUI(ctx) + + manager := getHandlerManager() + res, err := manager.GetPayloadFunctions(context.Background(), &core.GetPayloadFunctionsReq{ + AppEUI: appEUI.Bytes(), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not get payload functions") + } + + ctx.Info("Has decoder function") + fmt.Println(res.Decoder) + + if res.Converter != "" { + ctx.Info("Has converter function") + fmt.Println(res.Converter) + } + + if res.Validator != "" { + ctx.Info("Has validator function") + fmt.Println(res.Validator) + } else { + ctx.Warn("Does not have validator function") + } + }, +} + +// applicationsSetPayloadFunctionsCmd represents the applicationsSetPayloadFunctions command +var applicationsSetPayloadFunctionsCmd = &cobra.Command{ + Use: "set [decoder.js]", + Short: "Set payload functions", + Long: `ttnctl applications pf set sets the decoder, converter and validator +function by loading the specified files containing JavaScript code. +`, + Run: func(cmd *cobra.Command, args []string) { + appEUI := util.GetAppEUI(ctx) + + if len(args) == 0 { + cmd.Help() + return + } + + decoder, err := ioutil.ReadFile(args[0]) + if err != nil { + ctx.WithError(err).Fatal("Read decoder file failed") + } + + var converter []byte + converterFile, err := cmd.Flags().GetString("converter") + if converterFile != "" { + converter, err = ioutil.ReadFile(converterFile) + if err != nil { + ctx.WithError(err).Fatal("Read converter file failed") + } + } + + var validator []byte + validatorFile, err := cmd.Flags().GetString("validator") + if validatorFile != "" { + validator, err = ioutil.ReadFile(validatorFile) + if err != nil { + ctx.WithError(err).Fatal("Read validator file failed") + } + } + + manager := getHandlerManager() + _, err = manager.SetPayloadFunctions(context.Background(), &core.SetPayloadFunctionsReq{ + AppEUI: appEUI.Bytes(), + Decoder: string(decoder), + Converter: string(converter), + Validator: string(validator), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not set payload functions") + } + ctx.Info("Successfully set payload functions") + }, +} + +// applicationsTestPayloadFunctionsCmd represents the applicationsTestPayloadFunctions command +var applicationsTestPayloadFunctionsCmd = &cobra.Command{ + Use: "test [payload]", + Short: "Test the payload functions", + Long: `ttnctl applications pf test sends the specified binary data to the +Handler and returns the fields and validation result. +`, + Run: func(cmd *cobra.Command, args []string) { + appEUI := util.GetAppEUI(ctx) + + if len(args) == 0 { + cmd.Help() + return + } + + payload, err := util.ParseHEX(args[0], len(args[0])) + if err != nil { + ctx.WithError(err).Fatal("Invalid payload") + } + + manager := getHandlerManager() + res, err := manager.TestPayloadFunctions(context.Background(), &core.TestPayloadFunctionsReq{ + AppEUI: appEUI.Bytes(), + Payload: payload, + }) + if err != nil { + ctx.WithError(err).Fatal("Test payload functions failed") + } + + if res.Valid { + ctx.Info("Valid payload") + } else { + ctx.Warn("Invalid payload") + } + fmt.Printf("JSON: %s\n", res.Fields) + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsPayloadFunctionsCmd) + applicationsPayloadFunctionsCmd.AddCommand(applicationsSetPayloadFunctionsCmd) + applicationsPayloadFunctionsCmd.AddCommand(applicationsTestPayloadFunctionsCmd) + + applicationsSetPayloadFunctionsCmd.Flags().StringP("converter", "c", "", "Converter function") + applicationsSetPayloadFunctionsCmd.Flags().StringP("validator", "v", "", "Validator function") +} diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index f06c9e8e7..18c4b1250 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -10,7 +10,6 @@ import ( "strings" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/components/handler" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/random" @@ -25,14 +24,6 @@ const emptyCell = "-" var defaultKey = []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} -func getHandlerManager() core.AuthHandlerClient { - cli, err := handler.NewClient(viper.GetString("ttn-handler")) - if err != nil { - ctx.Fatalf("Could not connect: %v", err) - } - return cli -} - // devicesCmd represents the `devices` command var devicesCmd = &cobra.Command{ Use: "devices", diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 5dafe6225..6d2976a58 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -8,6 +8,8 @@ import ( "os" "strings" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/components/handler" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" "github.com/spf13/cobra" @@ -18,6 +20,14 @@ var cfgFile string var ctx log.Interface +func getHandlerManager() core.AuthHandlerClient { + cli, err := handler.NewClient(viper.GetString("ttn-handler")) + if err != nil { + ctx.Fatalf("Could not connect: %v", err) + } + return cli +} + // RootCmd is the entrypoint for handlerctl var RootCmd = &cobra.Command{ Use: "ttnctl", From 4ebc4dcbe414239711744106e1edef5711eb95c1 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 13 May 2016 19:39:25 +0100 Subject: [PATCH 1390/2266] Promoted getHandlerManager to util --- ttnctl/cmd/applicationsPayloadFunctions.go | 6 +++--- ttnctl/cmd/device.go | 10 +++++----- ttnctl/cmd/root.go | 10 ---------- ttnctl/util/managers.go | 16 ++++++++++++++++ 4 files changed, 24 insertions(+), 18 deletions(-) create mode 100644 ttnctl/util/managers.go diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go index 8ad5dbfe0..c148d49e7 100644 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -24,7 +24,7 @@ converting and validating binary payload. Run: func(cmd *cobra.Command, args []string) { appEUI := util.GetAppEUI(ctx) - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) res, err := manager.GetPayloadFunctions(context.Background(), &core.GetPayloadFunctionsReq{ AppEUI: appEUI.Bytes(), }) @@ -87,7 +87,7 @@ function by loading the specified files containing JavaScript code. } } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) _, err = manager.SetPayloadFunctions(context.Background(), &core.SetPayloadFunctionsReq{ AppEUI: appEUI.Bytes(), Decoder: string(decoder), @@ -121,7 +121,7 @@ Handler and returns the fields and validation result. ctx.WithError(err).Fatal("Invalid payload") } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) res, err := manager.TestPayloadFunctions(context.Background(), &core.TestPayloadFunctionsReq{ AppEUI: appEUI.Bytes(), Payload: payload, diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 18c4b1250..635b46984 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -42,7 +42,7 @@ registered on the Handler.`, ctx.Fatal("No authentication found. Please login") } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) defaultDevice, err := manager.GetDefaultDevice(context.Background(), &core.GetDefaultDeviceReq{ Token: auth.AccessToken, AppEUI: appEUI.Bytes(), @@ -131,7 +131,7 @@ var devicesInfoCmd = &cobra.Command{ ctx.Fatal("No authentication found. Please login") } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ Token: auth.AccessToken, AppEUI: appEUI.Bytes(), @@ -313,7 +313,7 @@ the Handler`, ctx.Fatal("No authentication found. Please login") } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ Token: auth.AccessToken, AppEUI: appEUI.Bytes(), @@ -383,7 +383,7 @@ registration on the Handler`, ctx.Fatal("No authentication found. Please login") } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ Token: auth.AccessToken, AppEUI: appEUI.Bytes(), @@ -439,7 +439,7 @@ register [DevEUI] [AppKey]`, ctx.Fatal("No authentication found. Please login") } - manager := getHandlerManager() + manager := util.GetHandlerManager(ctx) res, err := manager.SetDefaultDevice(context.Background(), &core.SetDefaultDeviceReq{ Token: auth.AccessToken, AppEUI: appEUI.Bytes(), diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 6d2976a58..5dafe6225 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -8,8 +8,6 @@ import ( "os" "strings" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/components/handler" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" "github.com/spf13/cobra" @@ -20,14 +18,6 @@ var cfgFile string var ctx log.Interface -func getHandlerManager() core.AuthHandlerClient { - cli, err := handler.NewClient(viper.GetString("ttn-handler")) - if err != nil { - ctx.Fatalf("Could not connect: %v", err) - } - return cli -} - // RootCmd is the entrypoint for handlerctl var RootCmd = &cobra.Command{ Use: "ttnctl", diff --git a/ttnctl/util/managers.go b/ttnctl/util/managers.go new file mode 100644 index 000000000..ee2e1d302 --- /dev/null +++ b/ttnctl/util/managers.go @@ -0,0 +1,16 @@ +package util + +import ( + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/components/handler" + "github.com/apex/log" + "github.com/spf13/viper" +) + +func GetHandlerManager(ctx log.Interface) core.AuthHandlerClient { + cli, err := handler.NewClient(viper.GetString("ttn-handler")) + if err != nil { + ctx.Fatalf("Could not connect: %v", err) + } + return cli +} From e8e655d41178af09e543a4ffb417ecafd1cab1f9 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 13 May 2016 19:39:58 +0100 Subject: [PATCH 1391/2266] Check if payload functions are null --- core/adapters/fields/adapter.go | 5 +++++ core/adapters/fields/appStorage_test.go | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/adapters/fields/adapter.go b/core/adapters/fields/adapter.go index 252caad4b..d7690742f 100644 --- a/core/adapters/fields/adapter.go +++ b/core/adapters/fields/adapter.go @@ -53,6 +53,11 @@ func (s *adapter) HandleData(context context.Context, in *core.DataAppReq, opt . return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) } + if functions == nil { + // Publish when there are no payload functions set + return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) + } + fields, valid, err := functions.Process(in.Payload) if err != nil { // If there were errors processing the payload, just publish it anyway diff --git a/core/adapters/fields/appStorage_test.go b/core/adapters/fields/appStorage_test.go index 897ded33f..f4df7cfb3 100644 --- a/core/adapters/fields/appStorage_test.go +++ b/core/adapters/fields/appStorage_test.go @@ -29,6 +29,19 @@ func TestConnect(t *testing.T) { a.So(err, ShouldNotBeNil) } +func TestGetFunctions(t *testing.T) { + a := New(t) + + eui, _ := types.ParseAppEUI("8000000000000001") + + storage := createStorage() + defer storage.Close() + + fetchedFunctions, err := storage.GetFunctions(eui) + a.So(err, ShouldBeNil) + a.So(fetchedFunctions, ShouldBeNil) +} + func TestSetFunctions(t *testing.T) { a := New(t) From 28149b2d78a0c80985a45c99aecab82c7b530608 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 13 May 2016 19:40:34 +0100 Subject: [PATCH 1392/2266] Clarified names --- core/collection/appCollector.go | 28 +++++++++++-------- core/components/collector/appStorage.go | 8 +++--- core/components/collector/appStorage_test.go | 6 ++-- core/components/collector/collector.go | 6 ++-- core/components/collector/collectorManager.go | 4 +-- core/components/collector/collector_test.go | 8 +++++- 6 files changed, 36 insertions(+), 24 deletions(-) diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go index 60ab24088..6ad1071f4 100644 --- a/core/collection/appCollector.go +++ b/core/collection/appCollector.go @@ -20,21 +20,21 @@ type AppCollector interface { } type appCollector struct { - ctx log.Interface - eui types.AppEUI - broker string - client mqtt.Client - storage DataStorage + ctx log.Interface + eui types.AppEUI + mqttBroker string + client mqtt.Client + storage DataStorage } // NewMqttAppCollector instantiates a new AppCollector instance using MQTT -func NewMqttAppCollector(ctx log.Interface, broker string, eui types.AppEUI, key string, storage DataStorage) AppCollector { +func NewMqttAppCollector(ctx log.Interface, mqttBroker string, eui types.AppEUI, key string, storage DataStorage) AppCollector { return &appCollector{ - ctx: ctx, - eui: eui, - broker: broker, - client: mqtt.NewClient(ctx, "collector", eui.String(), key, fmt.Sprintf("tcp://%s", broker)), - storage: storage, + ctx: ctx, + eui: eui, + mqttBroker: mqttBroker, + client: mqtt.NewClient(ctx, "collector", eui.String(), key, fmt.Sprintf("tcp://%s", mqttBroker)), + storage: storage, } } @@ -48,7 +48,7 @@ func (c *appCollector) Start() error { c.ctx.WithError(token.Error()).Error("Failed to subscribe") return token.Error() } - c.ctx.WithField("broker", c.broker).Info("Subscribed to app uplink packets") + c.ctx.WithField("Broker", c.mqttBroker).Info("Subscribed to app uplink packets") return nil } @@ -57,6 +57,10 @@ func (c *appCollector) Stop() { } func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + if req.Fields == nil || len(req.Fields) == 0 { + return + } + ctx := c.ctx.WithField("DevEUI", devEUI) t, err := time.Parse(time.RFC3339, req.Metadata[0].ServerTime) diff --git a/core/components/collector/appStorage.go b/core/components/collector/appStorage.go index ff1a20fb0..4d7cd8f3d 100644 --- a/core/components/collector/appStorage.go +++ b/core/components/collector/appStorage.go @@ -20,8 +20,8 @@ const ( type AppStorage interface { Add(eui types.AppEUI) error Remove(eui types.AppEUI) error - SetKey(eui types.AppEUI, key string) error - GetKey(eui types.AppEUI) (string, error) + SetAccessKey(eui types.AppEUI, key string) error + GetAccessKey(eui types.AppEUI) (string, error) List() ([]types.AppEUI, error) Reset() error Close() error @@ -62,11 +62,11 @@ func (s *redisAppStorage) Remove(eui types.AppEUI) error { return nil } -func (s *redisAppStorage) SetKey(eui types.AppEUI, key string) error { +func (s *redisAppStorage) SetAccessKey(eui types.AppEUI, key string) error { return s.client.HSet(makeKey(eui), "key", key).Err() } -func (s *redisAppStorage) GetKey(eui types.AppEUI) (string, error) { +func (s *redisAppStorage) GetAccessKey(eui types.AppEUI) (string, error) { m, err := s.client.HGetAllMap(makeKey(eui)).Result() if err == redis.Nil { return "", nil diff --git a/core/components/collector/appStorage_test.go b/core/components/collector/appStorage_test.go index 36527dd7a..f7020b994 100644 --- a/core/components/collector/appStorage_test.go +++ b/core/components/collector/appStorage_test.go @@ -29,7 +29,7 @@ func TestConnect(t *testing.T) { a.So(err, ShouldNotBeNil) } -func TestSetKey(t *testing.T) { +func TestAccessSetKey(t *testing.T) { a := New(t) eui, _ := types.ParseAppEUI("8000000000000001") @@ -39,10 +39,10 @@ func TestSetKey(t *testing.T) { defer storage.Close() defer storage.Reset() - err := storage.SetKey(eui, key) + err := storage.SetAccessKey(eui, key) a.So(err, ShouldBeNil) - fetchedKey, err := storage.GetKey(eui) + fetchedKey, err := storage.GetAccessKey(eui) a.So(err, ShouldBeNil) a.So(fetchedKey, ShouldEqual, key) } diff --git a/core/components/collector/collector.go b/core/components/collector/collector.go index 590ec040f..12d3d3a9c 100644 --- a/core/components/collector/collector.go +++ b/core/components/collector/collector.go @@ -20,6 +20,7 @@ import ( type Collector interface { Start() (map[types.AppEUI]collection.AppCollector, error) Stop() + StopApp(eui types.AppEUI) error } type collector struct { @@ -101,7 +102,7 @@ func (c *collector) Stop() { func (c *collector) startApp(eui types.AppEUI) error { ctx := c.ctx.WithField("AppEUI", eui) - key, err := c.appStorage.GetKey(eui) + key, err := c.appStorage.GetAccessKey(eui) if err != nil { return err } else if key == "" { @@ -117,11 +118,12 @@ func (c *collector) startApp(eui types.AppEUI) error { return nil } -func (c *collector) stopApp(eui types.AppEUI) error { +func (c *collector) StopApp(eui types.AppEUI) error { app, ok := c.apps[eui] if !ok { return errors.New("Not found") } app.Stop() + delete(c.apps, eui) return nil } diff --git a/core/components/collector/collectorManager.go b/core/components/collector/collectorManager.go index c04eb5e62..4aeb0ef36 100644 --- a/core/components/collector/collectorManager.go +++ b/core/components/collector/collectorManager.go @@ -43,7 +43,7 @@ func (s *collectorManagerServer) AddApplication(ctx context.Context, req *core.A if err := s.collector.appStorage.Add(appEUI); err != nil { return res, err } - if err := s.collector.appStorage.SetKey(appEUI, req.AppAccessKey); err != nil { + if err := s.collector.appStorage.SetAccessKey(appEUI, req.AppAccessKey); err != nil { return res, err } @@ -60,7 +60,7 @@ func (s *collectorManagerServer) RemoveApplication(ctx context.Context, req *cor return res, err } - s.collector.stopApp(appEUI) + s.collector.StopApp(appEUI) if err := s.collector.appStorage.Remove(appEUI); err != nil { return res, err diff --git a/core/components/collector/collector_test.go b/core/components/collector/collector_test.go index 0cdd03acb..b706dad8a 100644 --- a/core/components/collector/collector_test.go +++ b/core/components/collector/collector_test.go @@ -29,7 +29,7 @@ func TestCollection(t *testing.T) { eui, _ := types.ParseAppEUI("8000000000000001") err = appStorage.Add(eui) a.So(err, ShouldBeNil) - err = appStorage.SetKey(eui, "secret") + err = appStorage.SetAccessKey(eui, "secret") a.So(err, ShouldBeNil) collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}, "localhost:1783") @@ -37,6 +37,12 @@ func TestCollection(t *testing.T) { defer collector.Stop() a.So(err, ShouldBeNil) a.So(collectors, ShouldHaveLength, 1) + + err = collector.StopApp(eui) + a.So(err, ShouldBeNil) + + err = collector.StopApp(eui) + a.So(err, ShouldNotBeNil) // Not found } func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { From c427d757c02276426dbada672978bf852d722d87 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 13 May 2016 19:57:14 +0100 Subject: [PATCH 1393/2266] Test invalid JavaScript input --- core/adapters/fields/functions_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/core/adapters/fields/functions_test.go b/core/adapters/fields/functions_test.go index 3b0c4de61..acf298f0f 100644 --- a/core/adapters/fields/functions_test.go +++ b/core/adapters/fields/functions_test.go @@ -95,3 +95,27 @@ func TestProcess(t *testing.T) { a.So(data["temperature"], ShouldEqual, 20) a.So(data["humidity"], ShouldEqual, 110) } + +func TestProcessInvalidFunction(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `this is not valid JavaScript`, + } + _, _, err := functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Converter: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Validator: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) +} From 5379100053a65ffd347e5f5d13e90120c897314c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 17 May 2016 09:32:34 +0200 Subject: [PATCH 1394/2266] Add some fields adapter tests --- core/adapters/fields/adapter_test.go | 40 +++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/core/adapters/fields/adapter_test.go b/core/adapters/fields/adapter_test.go index 5128f24fc..f6848e60a 100644 --- a/core/adapters/fields/adapter_test.go +++ b/core/adapters/fields/adapter_test.go @@ -43,13 +43,22 @@ func TestHandleData(t *testing.T) { Payload: []byte{0x08, 0x70}, } + // No functions + res, err := adapter.HandleData(context.Background(), req) + a.So(res, ShouldEqual, new(core.DataAppRes)) + a.So(err, ShouldBeNil) + a.So(publisher.appEUI, ShouldResemble, &appEUI) + a.So(publisher.devEUI, ShouldResemble, &devEUI) + a.So(publisher.uplinkReq, ShouldNotBeNil) + a.So(publisher.uplinkReq.Fields, ShouldBeEmpty) + // Normal flow functions := &Functions{ Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, } err = storage.SetFunctions(appEUI, functions) a.So(err, ShouldBeNil) - res, err := adapter.HandleData(context.Background(), req) + res, err = adapter.HandleData(context.Background(), req) a.So(res, ShouldEqual, new(core.DataAppRes)) a.So(err, ShouldBeNil) a.So(publisher.appEUI, ShouldResemble, &appEUI) @@ -80,6 +89,35 @@ func TestHandleData(t *testing.T) { a.So(publisher.uplinkReq.Fields, ShouldResemble, *new(map[string]interface{})) } +func TestHandleJoin(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestHandleJoin") + storage, err := ConnectRedis("localhost:6379", 0) + a.So(err, ShouldBeNil) + defer storage.Close() + defer storage.Reset() + + publisher := &mockPublisher{} + adapter := NewAdapter(ctx, storage, publisher) + + appEUI, _ := types.ParseAppEUI("0102030405060708") + devEUI, _ := types.ParseDevEUI("00000000AABBCCDD") + + req := &core.JoinAppReq{ + AppEUI: appEUI.Bytes(), + DevEUI: devEUI.Bytes(), + } + + res, err := adapter.HandleJoin(context.Background(), req) + a.So(res, ShouldEqual, new(core.JoinAppRes)) + a.So(err, ShouldBeNil) + + a.So(publisher.appEUI, ShouldResemble, &appEUI) + a.So(publisher.devEUI, ShouldResemble, &devEUI) + a.So(publisher.activationReq, ShouldNotBeNil) +} + func (p *mockPublisher) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error { p.appEUI = &appEUI p.devEUI = &devEUI From 48480890bea2a0cd6a63feca6d73768a2d9c7b63 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 17 May 2016 09:40:26 +0200 Subject: [PATCH 1395/2266] Add some fields functions tests --- core/adapters/fields/functions_test.go | 35 +++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/core/adapters/fields/functions_test.go b/core/adapters/fields/functions_test.go index acf298f0f..f94ba5ee5 100644 --- a/core/adapters/fields/functions_test.go +++ b/core/adapters/fields/functions_test.go @@ -99,12 +99,28 @@ func TestProcess(t *testing.T) { func TestProcessInvalidFunction(t *testing.T) { a := New(t) + // Empty Function functions := &Functions{ - Decoder: `this is not valid JavaScript`, + Decoder: ``, } _, _, err := functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) + // Invalid Function + functions = &Functions{ + Decoder: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &Functions{ + Decoder: `function(payload) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Function functions = &Functions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `this is not valid JavaScript`, @@ -112,10 +128,27 @@ func TestProcessInvalidFunction(t *testing.T) { _, _, err = functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) + // Invalid Return + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Converter: `function(data) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Function functions = &Functions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `this is not valid JavaScript`, } _, _, err = functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) + + // Invalid Return + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Validator: `function(data) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) } From d66337b5b1b4a0b0fd5cac7bf82e4c9accba7cd6 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 17 May 2016 14:39:50 +0200 Subject: [PATCH 1396/2266] add IsEmpty to core types --- core/types/dev_addr.go | 6 ++++++ core/types/dev_addr_test.go | 5 +++++ core/types/eui.go | 18 ++++++++++++++++++ core/types/eui_test.go | 20 ++++++++++++++++++++ core/types/keys.go | 18 ++++++++++++++++++ core/types/keys_test.go | 20 ++++++++++++++++++++ 6 files changed, 87 insertions(+) diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 14bf4adc6..afeb9c7ba 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -73,3 +73,9 @@ func (addr *DevAddr) Unmarshal(data []byte) error { *addr = [4]byte{} // Reset the receiver return addr.UnmarshalBinary(data) } + +var empty DevAddr + +func (addr DevAddr) IsEmpty() bool { + return addr == empty +} diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index ec6dcf689..9962f01fa 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -57,4 +57,9 @@ func TestDevAddr(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, addr) + + // IsEmpty + var empty DevAddr + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(addr.IsEmpty(), ShouldEqual, false) } diff --git a/core/types/eui.go b/core/types/eui.go index a7f495a49..ecbe457ec 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -285,3 +285,21 @@ func (eui *GatewayEUI) Unmarshal(data []byte) error { *eui = [8]byte{} // Reset the receiver return eui.UnmarshalBinary(data) } + +var emptyEUI64 EUI64 + +func (eui EUI64) IsEmpty() bool { + return eui == emptyEUI64 +} + +func (eui DevEUI) IsEmpty() bool { + return EUI64(eui).IsEmpty() +} + +func (eui AppEUI) IsEmpty() bool { + return EUI64(eui).IsEmpty() +} + +func (eui GatewayEUI) IsEmpty() bool { + return EUI64(eui).IsEmpty() +} diff --git a/core/types/eui_test.go b/core/types/eui_test.go index ee04b0895..3f6862175 100644 --- a/core/types/eui_test.go +++ b/core/types/eui_test.go @@ -57,6 +57,11 @@ func TestEUI64(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(uOut, ShouldResemble, &eui) + + // IsEmpty + var empty EUI64 + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) } func TestAppEUI(t *testing.T) { @@ -110,6 +115,11 @@ func TestAppEUI(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, eui) + + // IsEmpty + var empty AppEUI + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) } func TestDevEUI(t *testing.T) { @@ -163,6 +173,11 @@ func TestDevEUI(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, eui) + + // IsEmpty + var empty DevEUI + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) } func TestGatewayEUI(t *testing.T) { @@ -216,4 +231,9 @@ func TestGatewayEUI(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, eui) + + // IsEmpty + var empty GatewayEUI + a.So(empty.IsEmpty(), ShouldEqual, true) + a.So(eui.IsEmpty(), ShouldEqual, false) } diff --git a/core/types/keys.go b/core/types/keys.go index 018cf58a9..48817d96a 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -284,3 +284,21 @@ func (key *NwkSKey) Unmarshal(data []byte) error { *key = [16]byte{} // Reset the receiver return key.UnmarshalBinary(data) } + +var emptyAES AES128Key + +func (key AES128Key) IsEmpty() bool { + return key == emptyAES +} + +func (key AppKey) IsEmpty() bool { + return AES128Key(key).IsEmpty() +} + +func (key AppSKey) IsEmpty() bool { + return AES128Key(key).IsEmpty() +} + +func (key NwkSKey) IsEmpty() bool { + return AES128Key(key).IsEmpty() +} diff --git a/core/types/keys_test.go b/core/types/keys_test.go index dbf58df85..ffb433f83 100644 --- a/core/types/keys_test.go +++ b/core/types/keys_test.go @@ -57,6 +57,11 @@ func TestAES128Key(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty AES128Key + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) } func TestAppKey(t *testing.T) { @@ -110,6 +115,11 @@ func TestAppKey(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty AppKey + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) } func TestNwkSKey(t *testing.T) { @@ -163,6 +173,11 @@ func TestNwkSKey(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty NwkSKey + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) } func TestAppSKey(t *testing.T) { @@ -216,4 +231,9 @@ func TestAppSKey(t *testing.T) { err = uOut.Unmarshal(bin) a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, key) + + // IsEmpty + var empty AppSKey + a.So(empty.IsEmpty(), ShouldBeTrue) + a.So(key.IsEmpty(), ShouldBeFalse) } From c41f9c0eb2c595dcb5d45ab1281251fa9af8b839 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 18 May 2016 17:05:20 +0200 Subject: [PATCH 1397/2266] Token validation for payload functions --- core/components/handler/handlerManager.go | 29 ++- core/handler_manager.pb.go | 220 +++++++++++++++++----- core/protos/handler_manager.proto | 9 +- 3 files changed, 204 insertions(+), 54 deletions(-) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index b319f46bb..4b3e40a61 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -221,6 +221,15 @@ func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDe func (h component) GetPayloadFunctions(ctx context.Context, req *core.GetPayloadFunctionsReq) (*core.GetPayloadFunctionsRes, error) { res := new(core.GetPayloadFunctionsRes) + _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected token") + return res, errors.New(errors.Operational, err) + } + adapter, ok := h.AppAdapter.(fields.Adapter) if !ok { return res, errors.New(errors.Structural, "Invalid adapter") @@ -245,6 +254,15 @@ func (h component) GetPayloadFunctions(ctx context.Context, req *core.GetPayload func (h component) SetPayloadFunctions(ctx context.Context, req *core.SetPayloadFunctionsReq) (*core.SetPayloadFunctionsRes, error) { res := new(core.SetPayloadFunctionsRes) + _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected token") + return res, errors.New(errors.Operational, err) + } + adapter, ok := h.AppAdapter.(fields.Adapter) if !ok { return res, errors.New(errors.Structural, "Invalid adapter") @@ -252,7 +270,7 @@ func (h component) SetPayloadFunctions(ctx context.Context, req *core.SetPayload var appEUI types.AppEUI appEUI.Unmarshal(req.AppEUI) - err := adapter.Storage().SetFunctions(appEUI, &fields.Functions{ + err = adapter.Storage().SetFunctions(appEUI, &fields.Functions{ Decoder: req.Decoder, Converter: req.Converter, Validator: req.Validator, @@ -267,6 +285,15 @@ func (h component) SetPayloadFunctions(ctx context.Context, req *core.SetPayload func (h component) TestPayloadFunctions(ctx context.Context, req *core.TestPayloadFunctionsReq) (*core.TestPayloadFunctionsRes, error) { res := new(core.TestPayloadFunctionsRes) + _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ + Token: req.Token, + AppEUI: req.AppEUI, + }) + if err != nil { + h.Ctx.WithError(err).Debug("Broker rejected token") + return res, errors.New(errors.Operational, err) + } + adapter, ok := h.AppAdapter.(fields.Adapter) if !ok { return res, errors.New(errors.Structural, "Invalid adapter") diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index 248c51384..4d86d11e5 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -174,7 +174,8 @@ func (*SetDefaultDeviceReq) Descriptor() ([]byte, []int) { } type GetPayloadFunctionsReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` } func (m *GetPayloadFunctionsReq) Reset() { *m = GetPayloadFunctionsReq{} } @@ -198,7 +199,8 @@ func (*GetPayloadFunctionsRes) Descriptor() ([]byte, []int) { } type SetPayloadFunctionsReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` Decoder string `protobuf:"bytes,11,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` Converter string `protobuf:"bytes,12,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` Validator string `protobuf:"bytes,13,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` @@ -222,7 +224,8 @@ func (*SetPayloadFunctionsRes) Descriptor() ([]byte, []int) { } type TestPayloadFunctionsReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` Payload []byte `protobuf:"bytes,11,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` } @@ -995,9 +998,15 @@ func (m *GetPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.AppEUI) > 0 { + if len(m.Token) > 0 { data[i] = 0xa i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) } @@ -1055,9 +1064,15 @@ func (m *SetPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.AppEUI) > 0 { + if len(m.Token) > 0 { data[i] = 0xa i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) } @@ -1115,9 +1130,15 @@ func (m *TestPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.AppEUI) > 0 { + if len(m.Token) > 0 { data[i] = 0xa i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if len(m.AppEUI) > 0 { + data[i] = 0x12 + i++ i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) i += copy(data[i:], m.AppEUI) } @@ -1408,6 +1429,10 @@ func (m *SetDefaultDeviceReq) Size() (n int) { func (m *GetPayloadFunctionsReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } l = len(m.AppEUI) if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) @@ -1436,6 +1461,10 @@ func (m *GetPayloadFunctionsRes) Size() (n int) { func (m *SetPayloadFunctionsReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } l = len(m.AppEUI) if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) @@ -1464,6 +1493,10 @@ func (m *SetPayloadFunctionsRes) Size() (n int) { func (m *TestPayloadFunctionsReq) Size() (n int) { var l int _ = l + l = len(m.Token) + if l > 0 { + n += 1 + l + sovHandlerManager(uint64(l)) + } l = len(m.AppEUI) if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) @@ -3028,6 +3061,35 @@ func (m *GetPayloadFunctionsReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -3246,6 +3308,35 @@ func (m *SetPayloadFunctionsReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -3464,6 +3555,35 @@ func (m *TestPayloadFunctionsReq) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Token = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) } @@ -3801,49 +3921,49 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 701 bytes of a gzipped FileDescriptorProto + // 696 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x4e, 0xd4, 0x40, - 0x14, 0xb6, 0xec, 0xff, 0x01, 0x0c, 0x0e, 0xb0, 0xd4, 0x8a, 0xc4, 0xf4, 0x8a, 0xc4, 0x48, 0x0c, - 0x3e, 0x41, 0xf9, 0x59, 0x30, 0xf8, 0x83, 0xed, 0xe2, 0x8d, 0x17, 0x66, 0xdc, 0xce, 0xe2, 0x86, - 0xda, 0xd6, 0x4e, 0x81, 0xf0, 0x02, 0x3e, 0x83, 0x6f, 0x60, 0xe2, 0x7b, 0x98, 0x78, 0xe9, 0x23, - 0x18, 0x7d, 0x03, 0x9f, 0xc0, 0xf9, 0x83, 0x6d, 0x97, 0x99, 0x26, 0x8b, 0x5e, 0x34, 0xd9, 0x73, - 0xbe, 0xd3, 0x6f, 0xbe, 0x39, 0xf3, 0x9d, 0xe9, 0xc2, 0xf2, 0x7b, 0x1c, 0x87, 0x11, 0xc9, 0xde, - 0x7e, 0xc0, 0x31, 0x3e, 0x26, 0xd9, 0x46, 0x9a, 0x25, 0x79, 0x82, 0xea, 0x83, 0x24, 0x23, 0x6e, - 0x0e, 0x4b, 0x47, 0x29, 0x25, 0x59, 0xfe, 0xb2, 0xef, 0x79, 0xfb, 0xb2, 0xd0, 0x27, 0x1f, 0xd1, - 0x12, 0x34, 0xfa, 0xc9, 0x09, 0x89, 0x6d, 0xeb, 0x81, 0xb5, 0xde, 0xf1, 0x1b, 0x39, 0x0f, 0x50, - 0x17, 0x9a, 0x5e, 0x9a, 0xee, 0x1e, 0x3d, 0xb5, 0x67, 0x58, 0x7a, 0xce, 0x6f, 0x62, 0x11, 0xf1, - 0xfc, 0x0e, 0x39, 0xe3, 0xf9, 0x9a, 0xcc, 0x87, 0x22, 0x52, 0xf5, 0x07, 0xe4, 0xc2, 0xae, 0x5f, - 0xd5, 0xb3, 0xc8, 0xed, 0x6a, 0x57, 0xa5, 0xee, 0x17, 0x0b, 0x16, 0x25, 0xe0, 0x6d, 0x1d, 0xde, - 0x58, 0x8d, 0x0d, 0x2d, 0xa6, 0xc6, 0x0b, 0xc3, 0x4c, 0xc9, 0x69, 0x85, 0x32, 0xe4, 0xc8, 0x8b, - 0xf3, 0x93, 0x60, 0x2c, 0xa8, 0x15, 0xcb, 0x90, 0x23, 0x8c, 0x4b, 0x20, 0x0d, 0x89, 0x60, 0x19, - 0xf2, 0xb5, 0x7b, 0x11, 0x3e, 0xa6, 0x76, 0x93, 0xe5, 0xe7, 0xfd, 0xc6, 0x90, 0x07, 0xee, 0xb2, - 0x4e, 0x28, 0x75, 0x77, 0x61, 0xf9, 0xd9, 0x88, 0xe6, 0x6c, 0xf9, 0xd1, 0x80, 0xd0, 0x9b, 0xee, - 0xc0, 0x8d, 0xf5, 0x34, 0x14, 0x3d, 0x84, 0x3a, 0x6f, 0x19, 0x63, 0xa9, 0xad, 0xcf, 0x6e, 0xae, - 0x6c, 0xf0, 0x33, 0xdc, 0x50, 0x38, 0x07, 0xe4, 0x1b, 0x7e, 0x3d, 0x61, 0xbf, 0xd1, 0x3a, 0xd4, - 0x98, 0x3a, 0x46, 0xcd, 0x6b, 0xbb, 0xa5, 0x5a, 0x96, 0x57, 0xa5, 0x35, 0xbc, 0x75, 0xe8, 0x7e, - 0xb5, 0x60, 0x61, 0x12, 0x29, 0xb6, 0x71, 0xc6, 0xd8, 0xc6, 0x9a, 0xb1, 0x8d, 0xf5, 0x72, 0x1b, - 0xd9, 0x56, 0x7b, 0xdb, 0x71, 0x7e, 0x94, 0x8a, 0xfe, 0xce, 0xfb, 0xcd, 0xa1, 0x88, 0x90, 0x03, - 0x6d, 0x9e, 0xdf, 0x49, 0xce, 0x63, 0xd5, 0xe1, 0xf6, 0x50, 0xc5, 0xe3, 0xd6, 0xb7, 0x8a, 0xad, - 0xff, 0x66, 0xc1, 0x9d, 0x6b, 0x5b, 0x2e, 0x58, 0xd0, 0x2a, 0x59, 0xf0, 0xbf, 0xef, 0x42, 0x19, - 0xba, 0x51, 0x34, 0x74, 0x61, 0x77, 0x2d, 0xe3, 0xee, 0xda, 0xe5, 0xdd, 0xb9, 0xdb, 0xb0, 0xb8, - 0x47, 0xd8, 0x19, 0x0f, 0xf1, 0x69, 0xa4, 0x8e, 0x7a, 0x7a, 0xa7, 0x3c, 0xd2, 0x91, 0xd0, 0x82, - 0x4e, 0xab, 0x34, 0x78, 0x6f, 0x60, 0x31, 0xf8, 0xd7, 0x35, 0x0b, 0xe4, 0xb5, 0x12, 0xf9, 0x63, - 0xe8, 0x32, 0x2d, 0x87, 0xf8, 0x22, 0x4a, 0x70, 0xd8, 0x3b, 0x8d, 0x07, 0xf9, 0x28, 0x89, 0x29, - 0xe7, 0x1f, 0x33, 0x59, 0x13, 0x3e, 0xd7, 0xbf, 0x41, 0xe5, 0xb1, 0x0d, 0x92, 0x90, 0x64, 0x4a, - 0x13, 0x3b, 0x36, 0x11, 0xa2, 0x55, 0xe8, 0x6c, 0x27, 0xf1, 0x19, 0x1b, 0x3d, 0x22, 0x8f, 0xb4, - 0xe3, 0x77, 0x06, 0x97, 0x09, 0x8e, 0xbe, 0xc6, 0xd1, 0x28, 0xc4, 0x79, 0x22, 0xa7, 0x9f, 0xa1, - 0x67, 0x97, 0x09, 0xf7, 0x93, 0x05, 0xdd, 0x60, 0x2a, 0x89, 0x45, 0x21, 0xb3, 0x15, 0x42, 0xe6, - 0x2a, 0x85, 0xcc, 0x4f, 0x0a, 0xb1, 0x0d, 0x3a, 0xa8, 0x7b, 0x00, 0x2b, 0x7d, 0x42, 0xa7, 0x95, - 0xa8, 0xca, 0x85, 0x44, 0x66, 0xd7, 0x54, 0x86, 0xee, 0x9e, 0x89, 0x4c, 0x38, 0xa4, 0x37, 0x22, - 0x51, 0x48, 0x55, 0x7f, 0x9b, 0x43, 0x11, 0x71, 0x2b, 0x08, 0xdd, 0xa2, 0xb5, 0x6d, 0xbf, 0x21, - 0x34, 0xf3, 0xeb, 0xee, 0xba, 0x6f, 0xe8, 0xe6, 0x9f, 0x3a, 0xdc, 0x56, 0xa3, 0xf8, 0x5c, 0x7e, - 0x5c, 0xd0, 0x0e, 0xc0, 0xf8, 0x6a, 0x47, 0x8e, 0xbc, 0x75, 0x74, 0x9f, 0x18, 0xc7, 0x8c, 0x51, - 0xe4, 0x41, 0xe7, 0xea, 0x7a, 0x45, 0x77, 0x8b, 0x85, 0xa5, 0x0f, 0x83, 0x63, 0x84, 0x28, 0xda, - 0x83, 0xd9, 0xc2, 0x1d, 0x8a, 0xee, 0xc9, 0x4a, 0xed, 0xed, 0xec, 0x54, 0x80, 0x14, 0xed, 0xc3, - 0xc2, 0xe4, 0x88, 0x5d, 0x4a, 0xd2, 0xcc, 0xaf, 0x63, 0x84, 0x04, 0x53, 0x60, 0x60, 0x0a, 0xcc, - 0x4c, 0x9a, 0xc6, 0xa3, 0x57, 0x62, 0xec, 0x27, 0xcf, 0x15, 0xad, 0x5e, 0xad, 0xad, 0xf1, 0x8f, - 0x53, 0x85, 0x0a, 0xca, 0xc0, 0x4c, 0x19, 0x54, 0x52, 0xea, 0xbd, 0x8c, 0xfa, 0xb0, 0xa4, 0xb3, - 0x1f, 0xba, 0x2f, 0xdf, 0x32, 0xf8, 0xdc, 0xa9, 0x84, 0xe9, 0xd6, 0xc2, 0xf7, 0x5f, 0x6b, 0xd6, - 0x0f, 0xf6, 0xfc, 0x64, 0xcf, 0xe7, 0xdf, 0x6b, 0xb7, 0xde, 0x35, 0xc5, 0x3f, 0x9a, 0x27, 0x7f, - 0x03, 0x00, 0x00, 0xff, 0xff, 0xae, 0x14, 0xff, 0x9e, 0xea, 0x08, 0x00, 0x00, + 0x14, 0xb6, 0xec, 0x6e, 0x77, 0xf7, 0x00, 0x06, 0x07, 0x58, 0x6a, 0x45, 0x62, 0x7a, 0x45, 0x62, + 0xe4, 0x02, 0x9f, 0xa0, 0xfc, 0x2c, 0x18, 0xff, 0xb0, 0x5d, 0xbc, 0xf1, 0xc2, 0x8c, 0xdb, 0x59, + 0xdc, 0x50, 0xdb, 0xda, 0x29, 0x10, 0xde, 0xc4, 0x0b, 0xef, 0x4d, 0x7c, 0x0f, 0x13, 0x2f, 0x7d, + 0x04, 0xa3, 0x6f, 0xe0, 0x13, 0x38, 0x7f, 0x40, 0xbb, 0xcc, 0x34, 0x11, 0xf7, 0x62, 0x13, 0xce, + 0xf9, 0x4e, 0xbf, 0xf3, 0x9d, 0x33, 0x67, 0xce, 0x00, 0xcb, 0xef, 0x71, 0x12, 0xc5, 0x24, 0x7f, + 0xfb, 0x01, 0x27, 0xf8, 0x88, 0xe4, 0x1b, 0x59, 0x9e, 0x16, 0x29, 0x6a, 0x0e, 0xd3, 0x9c, 0x78, + 0x05, 0x2c, 0x1d, 0x66, 0x94, 0xe4, 0xc5, 0xcb, 0x81, 0xef, 0xef, 0xcb, 0xc0, 0x80, 0x7c, 0x44, + 0x4b, 0xd0, 0x1a, 0xa4, 0xc7, 0x24, 0x71, 0xac, 0x07, 0xd6, 0x7a, 0x37, 0x68, 0x15, 0xdc, 0x40, + 0x3d, 0xb0, 0xfd, 0x2c, 0xdb, 0x3d, 0x7c, 0xe2, 0xcc, 0x30, 0xf7, 0x5c, 0x60, 0x63, 0x61, 0x71, + 0xff, 0x0e, 0x39, 0xe5, 0xfe, 0x86, 0xf4, 0x47, 0xc2, 0x52, 0xf1, 0x4f, 0xc9, 0xb9, 0xd3, 0xbc, + 0x8c, 0x67, 0x96, 0xd7, 0xd3, 0x66, 0xa5, 0xde, 0x17, 0x0b, 0x16, 0x25, 0xe0, 0x6f, 0x1d, 0xdc, + 0x58, 0x8d, 0x03, 0x6d, 0xa6, 0xc6, 0x8f, 0xa2, 0x5c, 0xc9, 0x69, 0x47, 0xd2, 0xe4, 0xc8, 0x8b, + 0xb3, 0xe3, 0xf0, 0x4a, 0x50, 0x3b, 0x91, 0x26, 0x47, 0x18, 0x97, 0x40, 0x5a, 0x12, 0xc1, 0xd2, + 0xe4, 0xb9, 0xfb, 0x31, 0x3e, 0xa2, 0x8e, 0xcd, 0xfc, 0xf3, 0x41, 0x6b, 0xc4, 0x0d, 0x6f, 0x59, + 0x27, 0x94, 0x7a, 0xbb, 0xb0, 0xfc, 0x6c, 0x4c, 0x0b, 0x96, 0x7e, 0x3c, 0x24, 0xf4, 0xa6, 0x15, + 0x78, 0x89, 0x9e, 0x86, 0xa2, 0x87, 0xd0, 0xe4, 0x2d, 0x63, 0x2c, 0x8d, 0xf5, 0xd9, 0xcd, 0x95, + 0x0d, 0x7e, 0x86, 0x1b, 0x0a, 0xe7, 0x80, 0xfc, 0x22, 0x68, 0xa6, 0xec, 0x6f, 0xb4, 0x0e, 0x0d, + 0xa6, 0x8e, 0x51, 0xf3, 0xd8, 0x5e, 0x25, 0x96, 0xf9, 0x55, 0x68, 0x03, 0x6f, 0x1d, 0x78, 0x5f, + 0x2d, 0x58, 0x98, 0x44, 0xca, 0x6d, 0x9c, 0x31, 0xb6, 0xb1, 0x61, 0x6c, 0x63, 0xb3, 0xda, 0x46, + 0x56, 0x6a, 0x7f, 0x3b, 0x29, 0x0e, 0x33, 0xd1, 0xdf, 0xf9, 0xc0, 0x1e, 0x09, 0x0b, 0xb9, 0xd0, + 0xe1, 0xfe, 0x9d, 0xf4, 0x2c, 0x51, 0x1d, 0xee, 0x8c, 0x94, 0x7d, 0xd5, 0xfa, 0x76, 0xb9, 0xf5, + 0xdf, 0x2c, 0xb8, 0x73, 0xad, 0xe4, 0xd2, 0x08, 0x5a, 0x95, 0x11, 0x9c, 0x7a, 0x15, 0x6a, 0xa0, + 0x5b, 0xe5, 0x81, 0x2e, 0x55, 0xd7, 0x36, 0x56, 0xd7, 0xa9, 0x56, 0xe7, 0x6d, 0xc3, 0xe2, 0x1e, + 0x61, 0x67, 0x3c, 0xc2, 0x27, 0xb1, 0x3a, 0xea, 0x7f, 0x9f, 0x94, 0x47, 0x3a, 0x12, 0x5a, 0xd2, + 0x69, 0x55, 0x2e, 0xde, 0x1b, 0x58, 0x0c, 0xff, 0x37, 0x67, 0x89, 0xbc, 0x51, 0x21, 0xef, 0x43, + 0x8f, 0x69, 0x39, 0xc0, 0xe7, 0x71, 0x8a, 0xa3, 0xfe, 0x49, 0x32, 0x2c, 0xc6, 0x69, 0x42, 0x6f, + 0x32, 0xfd, 0x7a, 0x1e, 0x2a, 0x0f, 0x73, 0x98, 0x46, 0x24, 0x57, 0x4c, 0xec, 0x30, 0x85, 0x89, + 0x56, 0xa1, 0xbb, 0x9d, 0x26, 0xa7, 0xec, 0x42, 0x12, 0x79, 0xd0, 0xdd, 0xa0, 0x3b, 0xbc, 0x70, + 0x70, 0xf4, 0x35, 0x8e, 0xc7, 0x11, 0x2e, 0x52, 0xb9, 0x13, 0x18, 0x7a, 0x7a, 0xe1, 0xf0, 0x3e, + 0x5b, 0xd0, 0x0b, 0xa7, 0x20, 0xbc, 0x2c, 0x6f, 0xb6, 0x46, 0xde, 0x5c, 0xad, 0xbc, 0xf9, 0x49, + 0x79, 0x8e, 0x41, 0x1d, 0xf5, 0x30, 0xac, 0x0c, 0x08, 0x9d, 0x8e, 0x70, 0x45, 0x22, 0x84, 0xb3, + 0x81, 0xcf, 0xa4, 0xe9, 0xed, 0x99, 0x52, 0x88, 0x19, 0xeb, 0x8f, 0x49, 0x1c, 0x51, 0x95, 0xc3, + 0x1e, 0x09, 0x8b, 0xa7, 0x16, 0xd5, 0x88, 0x1c, 0x9d, 0xa0, 0x25, 0x2a, 0xe1, 0x0b, 0xf3, 0xfa, + 0xe4, 0xd1, 0xcd, 0x3f, 0x4d, 0xb8, 0xad, 0x2e, 0xf3, 0x73, 0xf9, 0x3c, 0xa1, 0x1d, 0x80, 0xab, + 0xc7, 0x01, 0xb9, 0x72, 0x6f, 0xe9, 0x1e, 0x29, 0xd7, 0x8c, 0x51, 0xe4, 0x43, 0xf7, 0x72, 0x41, + 0xa3, 0xbb, 0xe5, 0xc0, 0xca, 0xd3, 0xe2, 0x1a, 0x21, 0x8a, 0xf6, 0x60, 0xb6, 0xb4, 0x85, 0xd1, + 0x3d, 0x19, 0xa9, 0xdd, 0xef, 0x6e, 0x0d, 0x48, 0xd1, 0x3e, 0x2c, 0x4c, 0x5e, 0xd2, 0x0b, 0x49, + 0x9a, 0x0d, 0xe0, 0x1a, 0x21, 0xc1, 0x14, 0x1a, 0x98, 0x42, 0x33, 0x93, 0xa6, 0xf1, 0xe8, 0x95, + 0x58, 0x1c, 0x93, 0xe7, 0x8a, 0x56, 0x2f, 0x73, 0x6b, 0xa6, 0xca, 0xad, 0x43, 0x05, 0x65, 0x68, + 0xa6, 0x0c, 0x6b, 0x29, 0xf5, 0x13, 0x8e, 0x06, 0xb0, 0xa4, 0x1b, 0x3f, 0x74, 0x5f, 0x7e, 0x65, + 0x98, 0x7e, 0xb7, 0x16, 0xa6, 0x5b, 0x0b, 0xdf, 0x7f, 0xad, 0x59, 0x3f, 0xd8, 0xef, 0x27, 0xfb, + 0x7d, 0xfa, 0xbd, 0x76, 0xeb, 0x9d, 0x2d, 0xfe, 0x27, 0x7a, 0xfc, 0x37, 0x00, 0x00, 0xff, 0xff, + 0x1d, 0xe5, 0x48, 0xaa, 0x2c, 0x09, 0x00, 0x00, } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index ebc403f95..bebc7f29c 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -67,7 +67,8 @@ message SetDefaultDeviceReq { } message GetPayloadFunctionsReq { - bytes AppEUI = 1; + string Token = 1; + bytes AppEUI = 2; } message GetPayloadFunctionsRes { @@ -77,7 +78,8 @@ message GetPayloadFunctionsRes { } message SetPayloadFunctionsReq { - bytes AppEUI = 1; + string Token = 1; + bytes AppEUI = 2; string Decoder = 11; string Converter = 12; string Validator = 13; @@ -87,7 +89,8 @@ message SetPayloadFunctionsRes { } message TestPayloadFunctionsReq { - bytes AppEUI = 1; + string Token = 1; + bytes AppEUI = 2; bytes Payload = 11; } From e60e7b24ef32498af984d2ca1fb568f15b17a9b7 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 18 May 2016 17:08:16 +0200 Subject: [PATCH 1398/2266] Token validation for payload functions --- ttnctl/cmd/applicationsPayloadFunctions.go | 28 ++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go index c148d49e7..9be08786d 100644 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // applicationsPayloadFunctionsCmd represents the applicationsPayloadFunctions command @@ -24,8 +25,17 @@ converting and validating binary payload. Run: func(cmd *cobra.Command, args []string) { appEUI := util.GetAppEUI(ctx) + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + manager := util.GetHandlerManager(ctx) res, err := manager.GetPayloadFunctions(context.Background(), &core.GetPayloadFunctionsReq{ + Token: auth.AccessToken, AppEUI: appEUI.Bytes(), }) if err != nil { @@ -64,6 +74,14 @@ function by loading the specified files containing JavaScript code. return } + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + decoder, err := ioutil.ReadFile(args[0]) if err != nil { ctx.WithError(err).Fatal("Read decoder file failed") @@ -89,6 +107,7 @@ function by loading the specified files containing JavaScript code. manager := util.GetHandlerManager(ctx) _, err = manager.SetPayloadFunctions(context.Background(), &core.SetPayloadFunctionsReq{ + Token: auth.AccessToken, AppEUI: appEUI.Bytes(), Decoder: string(decoder), Converter: string(converter), @@ -116,6 +135,14 @@ Handler and returns the fields and validation result. return } + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found. Please login") + } + payload, err := util.ParseHEX(args[0], len(args[0])) if err != nil { ctx.WithError(err).Fatal("Invalid payload") @@ -123,6 +150,7 @@ Handler and returns the fields and validation result. manager := util.GetHandlerManager(ctx) res, err := manager.TestPayloadFunctions(context.Background(), &core.TestPayloadFunctionsReq{ + Token: auth.AccessToken, AppEUI: appEUI.Bytes(), Payload: payload, }) From 5745f2fd533a736f618d20bf02ddd44e297112e8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 19 May 2016 00:45:16 +0200 Subject: [PATCH 1399/2266] add Functions to TestPayloadFunctions so they can be tested offline --- core/components/handler/handlerManager.go | 16 +- core/handler_manager.pb.go | 223 +++++++++++++++++----- core/protos/handler_manager.proto | 3 + 3 files changed, 183 insertions(+), 59 deletions(-) diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go index 4b3e40a61..1777e1c82 100644 --- a/core/components/handler/handlerManager.go +++ b/core/components/handler/handlerManager.go @@ -294,19 +294,13 @@ func (h component) TestPayloadFunctions(ctx context.Context, req *core.TestPaylo return res, errors.New(errors.Operational, err) } - adapter, ok := h.AppAdapter.(fields.Adapter) - if !ok { - return res, errors.New(errors.Structural, "Invalid adapter") - } - var appEUI types.AppEUI appEUI.Unmarshal(req.AppEUI) - functions, err := adapter.Storage().GetFunctions(appEUI) - if err != nil { - return res, err - } - if functions == nil { - return res, errors.New(errors.Operational, "Not found") + + functions := fields.Functions{ + Decoder: req.Decoder, + Converter: req.Converter, + Validator: req.Validator, } fields, valid, err := functions.Process(req.Payload) diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go index 4d86d11e5..48120bad6 100644 --- a/core/handler_manager.pb.go +++ b/core/handler_manager.pb.go @@ -224,9 +224,12 @@ func (*SetPayloadFunctionsRes) Descriptor() ([]byte, []int) { } type TestPayloadFunctionsReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Payload []byte `protobuf:"bytes,11,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` + AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` + Payload []byte `protobuf:"bytes,11,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` + Decoder string `protobuf:"bytes,21,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` + Converter string `protobuf:"bytes,22,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` + Validator string `protobuf:"bytes,23,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` } func (m *TestPayloadFunctionsReq) Reset() { *m = TestPayloadFunctionsReq{} } @@ -1148,6 +1151,30 @@ func (m *TestPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { i = encodeVarintHandlerManager(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if len(m.Decoder) > 0 { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Decoder))) + i += copy(data[i:], m.Decoder) + } + if len(m.Converter) > 0 { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Converter))) + i += copy(data[i:], m.Converter) + } + if len(m.Validator) > 0 { + data[i] = 0xba + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandlerManager(data, i, uint64(len(m.Validator))) + i += copy(data[i:], m.Validator) + } return i, nil } @@ -1505,6 +1532,18 @@ func (m *TestPayloadFunctionsReq) Size() (n int) { if l > 0 { n += 1 + l + sovHandlerManager(uint64(l)) } + l = len(m.Decoder) + if l > 0 { + n += 2 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Converter) + if l > 0 { + n += 2 + l + sovHandlerManager(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 2 + l + sovHandlerManager(uint64(l)) + } return n } @@ -3645,6 +3684,93 @@ func (m *TestPayloadFunctionsReq) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Decoder = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Converter = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandlerManager + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandlerManager + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandlerManager(data[iNdEx:]) @@ -3921,49 +4047,50 @@ var ( ) var fileDescriptorHandlerManager = []byte{ - // 696 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x4e, 0xd4, 0x40, - 0x14, 0xb6, 0xec, 0x6e, 0x77, 0xf7, 0x00, 0x06, 0x07, 0x58, 0x6a, 0x45, 0x62, 0x7a, 0x45, 0x62, - 0xe4, 0x02, 0x9f, 0xa0, 0xfc, 0x2c, 0x18, 0xff, 0xb0, 0x5d, 0xbc, 0xf1, 0xc2, 0x8c, 0xdb, 0x59, - 0xdc, 0x50, 0xdb, 0xda, 0x29, 0x10, 0xde, 0xc4, 0x0b, 0xef, 0x4d, 0x7c, 0x0f, 0x13, 0x2f, 0x7d, - 0x04, 0xa3, 0x6f, 0xe0, 0x13, 0x38, 0x7f, 0x40, 0xbb, 0xcc, 0x34, 0x11, 0xf7, 0x62, 0x13, 0xce, - 0xf9, 0x4e, 0xbf, 0xf3, 0x9d, 0x33, 0x67, 0xce, 0x00, 0xcb, 0xef, 0x71, 0x12, 0xc5, 0x24, 0x7f, - 0xfb, 0x01, 0x27, 0xf8, 0x88, 0xe4, 0x1b, 0x59, 0x9e, 0x16, 0x29, 0x6a, 0x0e, 0xd3, 0x9c, 0x78, - 0x05, 0x2c, 0x1d, 0x66, 0x94, 0xe4, 0xc5, 0xcb, 0x81, 0xef, 0xef, 0xcb, 0xc0, 0x80, 0x7c, 0x44, - 0x4b, 0xd0, 0x1a, 0xa4, 0xc7, 0x24, 0x71, 0xac, 0x07, 0xd6, 0x7a, 0x37, 0x68, 0x15, 0xdc, 0x40, - 0x3d, 0xb0, 0xfd, 0x2c, 0xdb, 0x3d, 0x7c, 0xe2, 0xcc, 0x30, 0xf7, 0x5c, 0x60, 0x63, 0x61, 0x71, - 0xff, 0x0e, 0x39, 0xe5, 0xfe, 0x86, 0xf4, 0x47, 0xc2, 0x52, 0xf1, 0x4f, 0xc9, 0xb9, 0xd3, 0xbc, - 0x8c, 0x67, 0x96, 0xd7, 0xd3, 0x66, 0xa5, 0xde, 0x17, 0x0b, 0x16, 0x25, 0xe0, 0x6f, 0x1d, 0xdc, - 0x58, 0x8d, 0x03, 0x6d, 0xa6, 0xc6, 0x8f, 0xa2, 0x5c, 0xc9, 0x69, 0x47, 0xd2, 0xe4, 0xc8, 0x8b, - 0xb3, 0xe3, 0xf0, 0x4a, 0x50, 0x3b, 0x91, 0x26, 0x47, 0x18, 0x97, 0x40, 0x5a, 0x12, 0xc1, 0xd2, - 0xe4, 0xb9, 0xfb, 0x31, 0x3e, 0xa2, 0x8e, 0xcd, 0xfc, 0xf3, 0x41, 0x6b, 0xc4, 0x0d, 0x6f, 0x59, - 0x27, 0x94, 0x7a, 0xbb, 0xb0, 0xfc, 0x6c, 0x4c, 0x0b, 0x96, 0x7e, 0x3c, 0x24, 0xf4, 0xa6, 0x15, - 0x78, 0x89, 0x9e, 0x86, 0xa2, 0x87, 0xd0, 0xe4, 0x2d, 0x63, 0x2c, 0x8d, 0xf5, 0xd9, 0xcd, 0x95, - 0x0d, 0x7e, 0x86, 0x1b, 0x0a, 0xe7, 0x80, 0xfc, 0x22, 0x68, 0xa6, 0xec, 0x6f, 0xb4, 0x0e, 0x0d, - 0xa6, 0x8e, 0x51, 0xf3, 0xd8, 0x5e, 0x25, 0x96, 0xf9, 0x55, 0x68, 0x03, 0x6f, 0x1d, 0x78, 0x5f, - 0x2d, 0x58, 0x98, 0x44, 0xca, 0x6d, 0x9c, 0x31, 0xb6, 0xb1, 0x61, 0x6c, 0x63, 0xb3, 0xda, 0x46, - 0x56, 0x6a, 0x7f, 0x3b, 0x29, 0x0e, 0x33, 0xd1, 0xdf, 0xf9, 0xc0, 0x1e, 0x09, 0x0b, 0xb9, 0xd0, - 0xe1, 0xfe, 0x9d, 0xf4, 0x2c, 0x51, 0x1d, 0xee, 0x8c, 0x94, 0x7d, 0xd5, 0xfa, 0x76, 0xb9, 0xf5, - 0xdf, 0x2c, 0xb8, 0x73, 0xad, 0xe4, 0xd2, 0x08, 0x5a, 0x95, 0x11, 0x9c, 0x7a, 0x15, 0x6a, 0xa0, - 0x5b, 0xe5, 0x81, 0x2e, 0x55, 0xd7, 0x36, 0x56, 0xd7, 0xa9, 0x56, 0xe7, 0x6d, 0xc3, 0xe2, 0x1e, - 0x61, 0x67, 0x3c, 0xc2, 0x27, 0xb1, 0x3a, 0xea, 0x7f, 0x9f, 0x94, 0x47, 0x3a, 0x12, 0x5a, 0xd2, - 0x69, 0x55, 0x2e, 0xde, 0x1b, 0x58, 0x0c, 0xff, 0x37, 0x67, 0x89, 0xbc, 0x51, 0x21, 0xef, 0x43, - 0x8f, 0x69, 0x39, 0xc0, 0xe7, 0x71, 0x8a, 0xa3, 0xfe, 0x49, 0x32, 0x2c, 0xc6, 0x69, 0x42, 0x6f, - 0x32, 0xfd, 0x7a, 0x1e, 0x2a, 0x0f, 0x73, 0x98, 0x46, 0x24, 0x57, 0x4c, 0xec, 0x30, 0x85, 0x89, - 0x56, 0xa1, 0xbb, 0x9d, 0x26, 0xa7, 0xec, 0x42, 0x12, 0x79, 0xd0, 0xdd, 0xa0, 0x3b, 0xbc, 0x70, - 0x70, 0xf4, 0x35, 0x8e, 0xc7, 0x11, 0x2e, 0x52, 0xb9, 0x13, 0x18, 0x7a, 0x7a, 0xe1, 0xf0, 0x3e, - 0x5b, 0xd0, 0x0b, 0xa7, 0x20, 0xbc, 0x2c, 0x6f, 0xb6, 0x46, 0xde, 0x5c, 0xad, 0xbc, 0xf9, 0x49, - 0x79, 0x8e, 0x41, 0x1d, 0xf5, 0x30, 0xac, 0x0c, 0x08, 0x9d, 0x8e, 0x70, 0x45, 0x22, 0x84, 0xb3, - 0x81, 0xcf, 0xa4, 0xe9, 0xed, 0x99, 0x52, 0x88, 0x19, 0xeb, 0x8f, 0x49, 0x1c, 0x51, 0x95, 0xc3, - 0x1e, 0x09, 0x8b, 0xa7, 0x16, 0xd5, 0x88, 0x1c, 0x9d, 0xa0, 0x25, 0x2a, 0xe1, 0x0b, 0xf3, 0xfa, - 0xe4, 0xd1, 0xcd, 0x3f, 0x4d, 0xb8, 0xad, 0x2e, 0xf3, 0x73, 0xf9, 0x3c, 0xa1, 0x1d, 0x80, 0xab, - 0xc7, 0x01, 0xb9, 0x72, 0x6f, 0xe9, 0x1e, 0x29, 0xd7, 0x8c, 0x51, 0xe4, 0x43, 0xf7, 0x72, 0x41, - 0xa3, 0xbb, 0xe5, 0xc0, 0xca, 0xd3, 0xe2, 0x1a, 0x21, 0x8a, 0xf6, 0x60, 0xb6, 0xb4, 0x85, 0xd1, - 0x3d, 0x19, 0xa9, 0xdd, 0xef, 0x6e, 0x0d, 0x48, 0xd1, 0x3e, 0x2c, 0x4c, 0x5e, 0xd2, 0x0b, 0x49, - 0x9a, 0x0d, 0xe0, 0x1a, 0x21, 0xc1, 0x14, 0x1a, 0x98, 0x42, 0x33, 0x93, 0xa6, 0xf1, 0xe8, 0x95, - 0x58, 0x1c, 0x93, 0xe7, 0x8a, 0x56, 0x2f, 0x73, 0x6b, 0xa6, 0xca, 0xad, 0x43, 0x05, 0x65, 0x68, - 0xa6, 0x0c, 0x6b, 0x29, 0xf5, 0x13, 0x8e, 0x06, 0xb0, 0xa4, 0x1b, 0x3f, 0x74, 0x5f, 0x7e, 0x65, - 0x98, 0x7e, 0xb7, 0x16, 0xa6, 0x5b, 0x0b, 0xdf, 0x7f, 0xad, 0x59, 0x3f, 0xd8, 0xef, 0x27, 0xfb, - 0x7d, 0xfa, 0xbd, 0x76, 0xeb, 0x9d, 0x2d, 0xfe, 0x27, 0x7a, 0xfc, 0x37, 0x00, 0x00, 0xff, 0xff, - 0x1d, 0xe5, 0x48, 0xaa, 0x2c, 0x09, 0x00, 0x00, + // 709 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdb, 0x4e, 0xd5, 0x4c, + 0x14, 0xfe, 0xcb, 0x3e, 0x2f, 0xe0, 0x0f, 0x0e, 0xec, 0x52, 0x2b, 0x12, 0xd3, 0x2b, 0x12, 0x23, + 0x17, 0xf8, 0x04, 0xe5, 0xb0, 0xc1, 0x78, 0xc2, 0x76, 0xe3, 0x8d, 0x17, 0x66, 0xdc, 0x9d, 0x8d, + 0x3b, 0xd4, 0xb6, 0x76, 0x0a, 0x84, 0x37, 0xf1, 0xc2, 0x7b, 0x13, 0x5f, 0xc0, 0x27, 0x30, 0xf1, + 0xd2, 0x47, 0x30, 0xfa, 0x06, 0x3e, 0x81, 0x73, 0x02, 0xda, 0xcd, 0x4c, 0x13, 0x91, 0x8b, 0x9d, + 0xb0, 0xd6, 0xb7, 0xfa, 0xcd, 0xb7, 0x0e, 0xb3, 0x06, 0xe8, 0xbf, 0xc5, 0x49, 0x14, 0x93, 0xfc, + 0xf5, 0x3b, 0x9c, 0xe0, 0x43, 0x92, 0xaf, 0x67, 0x79, 0x5a, 0xa4, 0xa8, 0x39, 0x4a, 0x73, 0xe2, + 0x15, 0xb0, 0x74, 0x90, 0x51, 0x92, 0x17, 0xcf, 0x87, 0xbe, 0xbf, 0x27, 0x03, 0x03, 0xf2, 0x1e, + 0x2d, 0x41, 0x6b, 0x98, 0x1e, 0x91, 0xc4, 0xb1, 0xee, 0x59, 0x6b, 0xbd, 0xa0, 0x55, 0x70, 0x03, + 0xd9, 0xd0, 0xf6, 0xb3, 0x6c, 0xe7, 0xe0, 0x91, 0x33, 0xc3, 0xdc, 0x73, 0x41, 0x1b, 0x0b, 0x8b, + 0xfb, 0xb7, 0xc9, 0x09, 0xf7, 0x37, 0xa4, 0x3f, 0x12, 0x96, 0x8a, 0x7f, 0x4c, 0xce, 0x9c, 0xe6, + 0x45, 0x3c, 0xb3, 0x3c, 0x5b, 0x7b, 0x2a, 0xf5, 0x3e, 0x59, 0xb0, 0x28, 0x01, 0x7f, 0x73, 0xff, + 0xda, 0x6a, 0x1c, 0xe8, 0x30, 0x35, 0x7e, 0x14, 0xe5, 0x4a, 0x4e, 0x27, 0x92, 0x26, 0x47, 0x9e, + 0x9d, 0x1e, 0x85, 0x97, 0x82, 0x3a, 0x89, 0x34, 0x39, 0xc2, 0xb8, 0x04, 0xd2, 0x92, 0x08, 0x96, + 0x26, 0x3f, 0x7b, 0x10, 0xe3, 0x43, 0xea, 0xb4, 0x99, 0x7f, 0x3e, 0x68, 0x8d, 0xb9, 0xe1, 0xf5, + 0x75, 0x42, 0xa9, 0xb7, 0x03, 0xfd, 0x27, 0x13, 0x5a, 0xb0, 0xe3, 0x27, 0x23, 0x42, 0xaf, 0x9b, + 0x81, 0x97, 0xe8, 0x69, 0x28, 0xba, 0x0f, 0x4d, 0x5e, 0x32, 0xc6, 0xd2, 0x58, 0x9b, 0xdd, 0x58, + 0x5e, 0xe7, 0x3d, 0x5c, 0x57, 0x38, 0x07, 0xe4, 0x17, 0x41, 0x33, 0x65, 0x7f, 0xa3, 0x35, 0x68, + 0x30, 0x75, 0x8c, 0x9a, 0xc7, 0xda, 0x95, 0x58, 0xe6, 0x57, 0xa1, 0x0d, 0xbc, 0xb9, 0xef, 0x7d, + 0xb6, 0x60, 0x61, 0x1a, 0x29, 0x97, 0x71, 0xc6, 0x58, 0xc6, 0x86, 0xb1, 0x8c, 0xcd, 0x6a, 0x19, + 0x59, 0xaa, 0x83, 0xad, 0xa4, 0x38, 0xc8, 0x44, 0x7d, 0xe7, 0x83, 0xf6, 0x58, 0x58, 0xc8, 0x85, + 0x2e, 0xf7, 0x6f, 0xa7, 0xa7, 0x89, 0xaa, 0x70, 0x77, 0xac, 0xec, 0xcb, 0xd2, 0x77, 0xca, 0xa5, + 0xff, 0x6a, 0xc1, 0xad, 0x2b, 0x29, 0x97, 0x46, 0xd0, 0xaa, 0x8c, 0xe0, 0x8d, 0x67, 0xa1, 0x06, + 0xba, 0x55, 0x1e, 0xe8, 0x52, 0x76, 0x1d, 0x63, 0x76, 0xdd, 0x6a, 0x76, 0xde, 0x16, 0x2c, 0xee, + 0x12, 0xd6, 0xe3, 0x31, 0x3e, 0x8e, 0x55, 0xab, 0xff, 0x7e, 0x52, 0x1e, 0xe8, 0x48, 0x68, 0x49, + 0xa7, 0x55, 0xb9, 0x78, 0xaf, 0x60, 0x31, 0xfc, 0xd7, 0x33, 0x4b, 0xe4, 0x8d, 0x0a, 0xf9, 0x00, + 0x6c, 0xa6, 0x65, 0x1f, 0x9f, 0xc5, 0x29, 0x8e, 0x06, 0xc7, 0xc9, 0xa8, 0x98, 0xa4, 0x09, 0xbd, + 0xce, 0xf4, 0xeb, 0x79, 0xa8, 0x6c, 0xe6, 0x28, 0x8d, 0x48, 0xae, 0x98, 0x58, 0x33, 0x85, 0x89, + 0x56, 0xa0, 0xb7, 0x95, 0x26, 0x27, 0xec, 0x42, 0x12, 0xd9, 0xe8, 0x5e, 0xd0, 0x1b, 0x9d, 0x3b, + 0x38, 0xfa, 0x12, 0xc7, 0x93, 0x08, 0x17, 0xa9, 0xdc, 0x09, 0x0c, 0x3d, 0x39, 0x77, 0x78, 0x1f, + 0x2d, 0xb0, 0xc3, 0x1b, 0x10, 0x5e, 0x96, 0x37, 0x5b, 0x23, 0x6f, 0xae, 0x56, 0xde, 0xfc, 0xb4, + 0x3c, 0xc7, 0xa0, 0x8e, 0x7a, 0x5f, 0x2c, 0x58, 0x1e, 0x12, 0x7a, 0x33, 0xca, 0x15, 0x89, 0x50, + 0xce, 0x26, 0x3e, 0x93, 0x66, 0x39, 0xa7, 0x7e, 0x4d, 0x4e, 0x76, 0x6d, 0x4e, 0xcb, 0xd3, 0x39, + 0xed, 0x9a, 0x84, 0x8b, 0xd1, 0x1d, 0x4c, 0x48, 0x1c, 0x51, 0xa5, 0xbc, 0x3d, 0x16, 0x16, 0x4f, + 0x48, 0x10, 0x0a, 0xe5, 0xdd, 0xa0, 0x25, 0xc8, 0xf8, 0x1e, 0xbe, 0x3a, 0xd0, 0x74, 0xe3, 0x77, + 0x13, 0xfe, 0x57, 0x3b, 0xe2, 0xa9, 0x7c, 0xf5, 0xd0, 0x36, 0xc0, 0xe5, 0x9b, 0x83, 0x5c, 0xb9, + 0x0e, 0x75, 0x6f, 0x9f, 0x6b, 0xc6, 0x28, 0xf2, 0xa1, 0x77, 0xb1, 0xf7, 0xd1, 0xed, 0x72, 0x60, + 0xe5, 0xc5, 0x72, 0x8d, 0x10, 0x45, 0xbb, 0x30, 0x5b, 0x5a, 0xee, 0xe8, 0x8e, 0x8c, 0xd4, 0x3e, + 0x1b, 0x6e, 0x0d, 0x48, 0xd1, 0x1e, 0x2c, 0x4c, 0xdf, 0xfd, 0x73, 0x49, 0x9a, 0xc5, 0xe2, 0x1a, + 0x21, 0xc1, 0x14, 0x1a, 0x98, 0x42, 0x33, 0x93, 0xa6, 0xf0, 0xe8, 0x85, 0xd8, 0x47, 0xd3, 0x7d, + 0x45, 0x2b, 0x17, 0x67, 0x6b, 0x66, 0xd5, 0xad, 0x43, 0x05, 0x65, 0x68, 0xa6, 0x0c, 0x6b, 0x29, + 0xf5, 0x17, 0x07, 0x0d, 0x61, 0x49, 0x37, 0x7e, 0xe8, 0xae, 0xfc, 0xca, 0x70, 0xa7, 0xdc, 0x5a, + 0x98, 0x6e, 0x2e, 0x7c, 0xfb, 0xb9, 0x6a, 0x7d, 0x67, 0xbf, 0x1f, 0xec, 0xf7, 0xe1, 0xd7, 0xea, + 0x7f, 0x6f, 0xda, 0xe2, 0x5f, 0xad, 0x87, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x74, 0xe9, 0xf9, + 0xf9, 0x83, 0x09, 0x00, 0x00, } diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto index bebc7f29c..32cc6a2d2 100644 --- a/core/protos/handler_manager.proto +++ b/core/protos/handler_manager.proto @@ -92,6 +92,9 @@ message TestPayloadFunctionsReq { string Token = 1; bytes AppEUI = 2; bytes Payload = 11; + string Decoder = 21; + string Converter = 22; + string Validator = 23; } message TestPayloadFunctionsRes { From b8602d85795259554c3a8021cc99edbee2aabd38 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 19 May 2016 00:57:15 +0200 Subject: [PATCH 1400/2266] implement payload function testing in ttnctl --- ttnctl/cmd/applicationsPayloadFunctions.go | 41 ++++++++++++++++++---- 1 file changed, 35 insertions(+), 6 deletions(-) diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go index 9be08786d..42e216c29 100644 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -122,10 +122,10 @@ function by loading the specified files containing JavaScript code. // applicationsTestPayloadFunctionsCmd represents the applicationsTestPayloadFunctions command var applicationsTestPayloadFunctionsCmd = &cobra.Command{ - Use: "test [payload]", + Use: "test [payload] [decoder.js]", Short: "Test the payload functions", - Long: `ttnctl applications pf test sends the specified binary data to the -Handler and returns the fields and validation result. + Long: `ttnctl applications pf test sends the specified payload functions to +the Handler, as well as a payload to test them on and returns the fields and validation result. `, Run: func(cmd *cobra.Command, args []string) { appEUI := util.GetAppEUI(ctx) @@ -148,11 +148,37 @@ Handler and returns the fields and validation result. ctx.WithError(err).Fatal("Invalid payload") } + decoder, err := ioutil.ReadFile(args[0]) + if err != nil { + ctx.WithError(err).Fatal("Read decoder file failed") + } + + var converter []byte + converterFile, err := cmd.Flags().GetString("converter") + if converterFile != "" { + converter, err = ioutil.ReadFile(converterFile) + if err != nil { + ctx.WithError(err).Fatal("Read converter file failed") + } + } + + var validator []byte + validatorFile, err := cmd.Flags().GetString("validator") + if validatorFile != "" { + validator, err = ioutil.ReadFile(validatorFile) + if err != nil { + ctx.WithError(err).Fatal("Read validator file failed") + } + } + manager := util.GetHandlerManager(ctx) res, err := manager.TestPayloadFunctions(context.Background(), &core.TestPayloadFunctionsReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - Payload: payload, + Token: auth.AccessToken, + AppEUI: appEUI.Bytes(), + Payload: payload, + Decoder: string(decoder), + Converter: string(converter), + Validator: string(validator), }) if err != nil { ctx.WithError(err).Fatal("Test payload functions failed") @@ -174,4 +200,7 @@ func init() { applicationsSetPayloadFunctionsCmd.Flags().StringP("converter", "c", "", "Converter function") applicationsSetPayloadFunctionsCmd.Flags().StringP("validator", "v", "", "Validator function") + + applicationsTestPayloadFunctionsCmd.Flags().StringP("converter", "c", "", "Converter function") + applicationsTestPayloadFunctionsCmd.Flags().StringP("validator", "v", "", "Validator function") } From 950bc9f6ef7ddcffe3b4bbb526c71fc0a6a73e5d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 19 May 2016 11:12:55 +0200 Subject: [PATCH 1401/2266] [ci] Use Ubuntu mosquitto in Travis --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index a1c34d62f..daa41f286 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,10 @@ go: - 1.6 before_install: - - sudo apt-add-repository ppa:mosquitto-dev/mosquitto-ppa -y - sudo apt-get update install: - - sudo apt-get install mosquitto mosquitto-clients + - sudo apt-get install mosquitto - make deps - make test-deps - make cover-deps @@ -19,7 +18,7 @@ before_script: - mosquitto -p 1883 1>/dev/null 2>/dev/null & services: - - redis-server + - redis-server script: - make test From dba8426ce169928d5789b73396a7a2e20e18e110 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 19 May 2016 11:30:23 +0200 Subject: [PATCH 1402/2266] Fix for providing default AppKey --- ttnctl/cmd/device.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 635b46984..898135506 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -421,7 +421,7 @@ register [DevEUI] [AppKey]`, var appKey types.AppKey var err error - if len(args) >= 2 { + if len(args) >= 1 { appKey, err = types.ParseAppKey(args[0]) if err != nil { ctx.Fatalf("Invalid AppKey: %s", err) From 38da846a49012c70262116a5256434b5ab318315 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 19 May 2016 11:47:24 +0200 Subject: [PATCH 1403/2266] Fix for providing default AppKey --- ttnctl/cmd/device.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index 898135506..d4670c2f1 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -412,11 +412,6 @@ var devicesRegisterDefaultCmd = &cobra.Command{ on the Handler that have not been explicitly registered using ttnctl devices register [DevEUI] [AppKey]`, Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.Help() - return - } - appEUI := util.GetAppEUI(ctx) var appKey types.AppKey From 3cf58d0816d8feb61c2512eab6800e9bb691bf94 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 19 May 2016 12:01:35 +0200 Subject: [PATCH 1404/2266] Fixed formatting of AppEUI in device info --- ttnctl/cmd/device.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index d4670c2f1..a64341c40 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -151,10 +151,10 @@ var devicesInfoCmd = &cobra.Command{ // LMiC decided to use LSBF for AppEUI and call it ArtEUI if lmic { - fmt.Printf(" AppEUI: %X (sometimes called ArtEUI)\n", appEUI) + fmt.Printf(" AppEUI: %s (sometimes called ArtEUI)\n", appEUI) fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(appEUI.Bytes(), lsbf)) } else { - fmt.Printf(" AppEUI: %X\n", appEUI) + fmt.Printf(" AppEUI: %s\n", appEUI) fmt.Printf(" {%s}\n", cStyle(appEUI.Bytes(), msbf)) } From 34ccbb5b8180139e6d64ecef562de23d3bb66fc3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 19 May 2016 16:14:27 +0200 Subject: [PATCH 1405/2266] [mqtt] Retry Connect --- mqtt/client.go | 22 +++++++++++++++++++--- mqtt/client_test.go | 2 ++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index cdddae670..25b3f6e90 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -114,12 +114,28 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri } } -func (c *defaultClient) Connect() error { +var ( + // ConnectRetries says how many times the client should retry a failed connection + ConnectRetries = 5 + // ConnectRetryDelay says how long the client should wait between retries + ConnectRetryDelay = time.Second +) + +func (c *defaultClient) Connect() (err error) { if c.mqtt.IsConnected() { return nil } - if token := c.mqtt.Connect(); token.Wait() && token.Error() != nil { - return fmt.Errorf("Could not connect: %s", token.Error()) + for retries := 0; retries < ConnectRetries; retries++ { + token := c.mqtt.Connect() + token.Wait() + err = token.Error() + if err == nil { + break + } + <-time.After(ConnectRetryDelay) + } + if err != nil { + return fmt.Errorf("Could not connect: %s", err) } return nil } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index ab9f65831..589a3ad71 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -46,6 +46,8 @@ func TestConnect(t *testing.T) { func TestConnectInvalidAddress(t *testing.T) { a := New(t) + ConnectRetries = 2 + ConnectRetryDelay = 50 * time.Millisecond c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 err := c.Connect() a.So(err, ShouldNotBeNil) From 68e32488d1773a6d268c835b5d220d7ecc101750 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 20 May 2016 19:30:47 +0200 Subject: [PATCH 1406/2266] Added connect retries for Redis --- core/components/collector/appStorage.go | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/core/components/collector/appStorage.go b/core/components/collector/appStorage.go index 4d7cd8f3d..4105f9915 100644 --- a/core/components/collector/appStorage.go +++ b/core/components/collector/appStorage.go @@ -5,6 +5,7 @@ package collector import ( "fmt" + "time" "gopkg.in/redis.v3" @@ -16,6 +17,13 @@ const ( appKey = "collector:app:%s" ) +var ( + // ConnectRetries says how many times the client should retry a failed connection + ConnectRetries = 5 + // ConnectRetryDelay says how long the client should wait between retries + ConnectRetryDelay = time.Second +) + // AppStorage provides storage for applications type AppStorage interface { Add(eui types.AppEUI) error @@ -37,7 +45,14 @@ func ConnectRedis(addr string, db int64) (AppStorage, error) { Addr: addr, DB: db, }) - _, err := client.Ping().Result() + var err error + for retries := 0; retries < ConnectRetries; retries++ { + _, err = client.Ping().Result() + if err == nil { + break + } + <-time.After(ConnectRetryDelay) + } if err != nil { client.Close() return nil, err From f8f286989aefd64badcc0e1288dde38432f72ee7 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 20 May 2016 22:12:11 +0200 Subject: [PATCH 1407/2266] Set retry count to 10 --- mqtt/client.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 25b3f6e90..da51812e8 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -116,15 +116,16 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri var ( // ConnectRetries says how many times the client should retry a failed connection - ConnectRetries = 5 + ConnectRetries = 10 // ConnectRetryDelay says how long the client should wait between retries ConnectRetryDelay = time.Second ) -func (c *defaultClient) Connect() (err error) { +func (c *defaultClient) Connect() error { if c.mqtt.IsConnected() { return nil } + var err error for retries := 0; retries < ConnectRetries; retries++ { token := c.mqtt.Connect() token.Wait() From 1f3cc9de1f5c0464249b181b1d007c33ba2ecfc7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 21 May 2016 13:27:15 +0200 Subject: [PATCH 1408/2266] [ttnctl] Generate random ABP DevAddr --- ttnctl/cmd/device.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go index a64341c40..a83fc9219 100644 --- a/ttnctl/cmd/device.go +++ b/ttnctl/cmd/device.go @@ -330,6 +330,8 @@ the Handler`, }, } +const netID = 0x13 + // devicesRegisterPersonalizedCmd represents the `device register personalized` command var devicesRegisterPersonalizedCmd = &cobra.Command{ Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", @@ -337,18 +339,22 @@ var devicesRegisterPersonalizedCmd = &cobra.Command{ Long: `ttnctl devices register personalized creates or updates an ABP registration on the Handler`, Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.Help() - return + var devAddr types.DevAddr + var err error + + if len(args) >= 1 { + devAddr, err = types.ParseDevAddr(args[0]) + if err != nil { + ctx.Fatalf("Invalid DevAddr: %s", err) + } + } else { + ctx.Info("Generating random DevAddr...") + copy(devAddr[:], random.Bytes(4)) + devAddr[0] = (netID << 1) | (devAddr[0] & 1) } appEUI := util.GetAppEUI(ctx) - devAddr, err := types.ParseDevAddr(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevAddr: %s", err) - } - var nwkSKey types.NwkSKey var appSKey types.AppSKey if len(args) >= 3 { From 294996b60230891069c2c3d401065b6c43e24bbe Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Sat, 21 May 2016 14:45:55 +0200 Subject: [PATCH 1409/2266] fix doubly hex encoded app EUI in ttnctl applications authorize --- ttnctl/cmd/applications.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 736c2b6d0..64a45109d 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -145,7 +145,7 @@ var applicationsAuthorizeCmd = &cobra.Command{ } server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications/%s/authorize", server, fmt.Sprintf("%X", appEUI)) + uri := fmt.Sprintf("%s/applications/%s/authorize", server, appEUI) values := url.Values{ "email": {args[1]}, } From f4a72bdf2260892dba332a3e6a43a0077f5458aa Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 23 May 2016 09:40:26 +0200 Subject: [PATCH 1410/2266] fix arguments parsing for ttnctl applications pf test --- ttnctl/cmd/applicationsPayloadFunctions.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go index 42e216c29..a598cb6a6 100644 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -130,7 +130,7 @@ the Handler, as well as a payload to test them on and returns the fields and val Run: func(cmd *cobra.Command, args []string) { appEUI := util.GetAppEUI(ctx) - if len(args) == 0 { + if len(args) <= 1 { cmd.Help() return } @@ -148,7 +148,7 @@ the Handler, as well as a payload to test them on and returns the fields and val ctx.WithError(err).Fatal("Invalid payload") } - decoder, err := ioutil.ReadFile(args[0]) + decoder, err := ioutil.ReadFile(args[1]) if err != nil { ctx.WithError(err).Fatal("Read decoder file failed") } From 4f9306b63f56f777d88d8ca69693ca553de37274 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 23 May 2016 18:19:41 +0200 Subject: [PATCH 1411/2266] add message pointing to account server on login error --- ttnctl/cmd/user.go | 4 +++- ttnctl/util/auth.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 6b1d18a8f..cac297d55 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -94,8 +94,10 @@ var userLoginCmd = &cobra.Command{ ctx.Fatal(err.Error()) } - _, err = util.Login(viper.GetString("ttn-account-server"), email, string(password)) + server := viper.GetString("ttn-account-server") + _, err = util.Login(server, email, string(password)) if err != nil { + ctx.Info(fmt.Sprintf("Visit %s to register or to retrieve your account credentials.", server)) ctx.WithError(err).Fatal("Failed to login") } diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 1bbd8a49c..604932656 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -154,6 +154,10 @@ func newToken(server, email string, values url.Values) (*Auth, error) { return nil, err } + if resp.StatusCode == http.StatusUnauthorized { + return nil, errors.New("Incorrect username or password.") + } + if resp.StatusCode != http.StatusOK { if t.Error != "" { return nil, errors.New(t.ErrorDescription) From e5cd7bb2a4cc089d622dd676926c889aac1dcd25 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 25 May 2016 12:00:34 +0200 Subject: [PATCH 1412/2266] Set Accept header for user create --- ttnctl/cmd/user.go | 16 ++++++++++++++-- ttnctl/util/auth.go | 3 ++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index cac297d55..5fe36957f 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -6,8 +6,10 @@ package cmd import ( "bytes" "fmt" + "io/ioutil" "net/http" "net/url" + "strings" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" @@ -64,13 +66,23 @@ var userCreateCmd = &cobra.Command{ "email": {email}, "password": {string(password)}, } - res, err := http.PostForm(uri, values) + + req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) + if err != nil { + ctx.WithError(err).Fatalf("Failed to create request") + } + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json") + + client := &http.Client{} + res, err := client.Do(req) if err != nil { ctx.WithError(err).Fatal("Registration failed") } if res.StatusCode != http.StatusCreated { - ctx.Fatalf("Registration failed: %d %s", res.StatusCode, res.Status) + buf, _ := ioutil.ReadAll(res.Body) + ctx.Fatalf("Registration failed: %s (%v)", res.Status, string(buf)) } ctx.Info("User created") diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go index 604932656..c4aa5c7b3 100644 --- a/ttnctl/util/auth.go +++ b/ttnctl/util/auth.go @@ -111,7 +111,8 @@ func NewRequestWithAuth(server, method, urlStr string, body io.Reader) (*http.Re if err != nil { return nil, err } - req.Header.Add("Authorization", fmt.Sprintf("bearer %s", auth.AccessToken)) + req.Header.Set("Authorization", fmt.Sprintf("bearer %s", auth.AccessToken)) + req.Header.Set("Accept", "application/json") return req, nil } From 915bb5e22dd1e78859b35f00b16edcf528cfec58 Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Sun, 5 Jun 2016 23:30:24 +0200 Subject: [PATCH 1413/2266] Fix for join issues mentioned in "EU RX2 parameters #155", use SF12 in RX2 for join accept. --- core/components/handler/handler.go | 15 +++-- core/components/handler/handler_test.go | 90 +++++++++++++++++++++++++ 2 files changed, 101 insertions(+), 4 deletions(-) diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index 8963566ca..c41f1742b 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -48,6 +48,7 @@ type component struct { NetID [3]byte RX1DROffset uint8 RX2DataRate string + RX2JoinRate string RX2Freq float32 RXDelay uint8 PowerRX1 uint32 @@ -111,6 +112,7 @@ func New(c Components, o Options) Interface { h.Configuration.NetID = [3]byte{14, 14, 14} h.Configuration.RX1DROffset = 0 h.Configuration.RX2DataRate = "SF9BW125" + h.Configuration.RX2JoinRate = "SF12BW125" h.Configuration.RX2Freq = 869.525 h.Configuration.RXDelay = 1 h.Configuration.JoinDelay = 5 @@ -713,7 +715,7 @@ func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up } } - metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.RXDelay), isRX2) + metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.RXDelay), isRX2, false) return &core.DataUpHandlerRes{ Payload: &core.LoRaWANData{ @@ -771,7 +773,8 @@ func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte return nil, errors.New(errors.Structural, err) } - m := h.buildMetadata(*joinReq.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.JoinDelay), isRX2) + // force RX2 for testing + m := h.buildMetadata(*joinReq.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.JoinDelay), isRX2, true) return &core.JoinHandlerRes{ Payload: &core.LoRaWANJoinAccept{ Payload: data, @@ -781,7 +784,7 @@ func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte } // buildMetadata construct a new Metadata -func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay uint32, isRX2 bool) core.Metadata { +func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay uint32, isRX2 bool, isJoin bool) core.Metadata { m := core.Metadata{ Frequency: metadata.Frequency, CodingRate: metadata.CodingRate, @@ -797,7 +800,11 @@ func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay if isRX2 { // Should we reply on RX2, metadata aren't the same // TODO Handle different regions with non hard-coded values m.Frequency = h.Configuration.RX2Freq - m.DataRate = h.Configuration.RX2DataRate + if isJoin { + m.DataRate = h.Configuration.RX2JoinRate + } else { + m.DataRate = h.Configuration.RX2DataRate + } m.Power = h.Configuration.PowerRX2 m.Timestamp = metadata.Timestamp + baseDelay + 1000000 } diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 12dfeb0c9..4ebc84249 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1584,6 +1584,96 @@ func TestHandleJoin(t *testing.T) { // -------------------- + { + Desc(t, "Handle valid join-request | get join-accept for RX2") + + // Build + tmst := time.Now() + + req := &core.JoinHandlerReq{ + AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, + DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, + DevNonce: []byte{14, 42}, + Metadata: &core.Metadata{ + DataRate: "SF9BW125", + Frequency: 865.5, + Timestamp: uint32(tmst.Unix() * 1000000), + CodingRate: "4/5", + DutyRX1: uint32(dutycycle.StateAvailable), + DutyRX2: uint32(dutycycle.StateAvailable), + Rssi: -20, + Lsnr: 5.0, + }, + } + + devStorage := NewMockDevStorage() + devStorage.OutRead.Entry = devEntry{ + AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + pktStorage := NewMockPktStorage() + appAdapter := mocks.NewAppClient() + broker := mocks.NewAuthBrokerClient() + + payload := &lorawan.PHYPayload{} + payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} + joinPayload := lorawan.JoinRequestPayload{} + copy(joinPayload.AppEUI[:], req.AppEUI) + copy(joinPayload.DevEUI[:], req.DevEUI) + copy(joinPayload.DevNonce[:], req.DevNonce) + payload.MACPayload = &joinPayload + err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) + FatalUnless(t, err) + req.MIC = payload.MIC[:] + + // Expect + var wantErr *string + var wantRes = &core.JoinHandlerRes{ + Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding + NwkSKey: nil, // We'll assume it's correct if payload is okay + Metadata: &core.Metadata{ + DataRate: "SF12BW125", + Frequency: 869.525, + CodingRate: "4/5", + Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000 + 1000000), + PayloadSize: 33, + Power: 27, + InvPolarity: true, + }, + } + var wantAppReq = &core.JoinAppReq{ + Metadata: []*core.Metadata{req.Metadata}, + AppEUI: req.AppEUI, + DevEUI: req.DevEUI, + } + + // Operate + handler := New(Components{ + Ctx: GetLogger(t, "Handler"), + Broker: broker, + AppAdapter: appAdapter, + DevStorage: devStorage, + PktStorage: pktStorage, + }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) + res, err := handler.HandleJoin(context.Background(), req) + + // Check + CheckErrors(t, wantErr, err) + Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") + Check(t, 16, len(res.NwkSKey), "Network session keys' length") + Check(t, 4, len(res.DevAddr), "Device addresses' length") + Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") + joinaccept := &lorawan.PHYPayload{} + err = joinaccept.UnmarshalBinary(res.Payload.Payload) + CheckErrors(t, nil, err) + err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) + CheckErrors(t, nil, err) + Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") + } + + // -------------------- + { Desc(t, "Handle valid join-request, fails to notify app.") From f651f73c70cdf101ac968d2a013272c49864032d Mon Sep 17 00:00:00 2001 From: Jac Kersing Date: Mon, 6 Jun 2016 00:21:50 +0200 Subject: [PATCH 1414/2266] Fix formatting --- core/components/handler/handler_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go index 4ebc84249..44e1a630e 100644 --- a/core/components/handler/handler_test.go +++ b/core/components/handler/handler_test.go @@ -1636,7 +1636,7 @@ func TestHandleJoin(t *testing.T) { DataRate: "SF12BW125", Frequency: 869.525, CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000 + 1000000), + Timestamp: uint32(tmst.Add(5*time.Second).Unix()*1000000 + 1000000), PayloadSize: 33, Power: 27, InvPolarity: true, From 2bd3390d413b0d19c2c66cb6a190ed6ecc024605 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Apr 2016 11:58:36 +0200 Subject: [PATCH 1415/2266] [refactor] Create proto files for refactored backend --- Makefile | 13 + api/api.pb.go | 809 +++++ api/api.proto | 28 + api/broker/broker.pb.go | 3951 +++++++++++++++++++++++++ api/broker/broker.proto | 142 + api/discovery/discovery.pb.go | 874 ++++++ api/discovery/discovery.proto | 31 + api/gateway/gateway.pb.go | 1666 +++++++++++ api/gateway/gateway.proto | 54 + api/gateway/semtech/semtech.pb.go | 686 +++++ api/gateway/semtech/semtech.proto | 22 + api/handler/handler.pb.go | 490 +++ api/handler/handler.proto | 23 + api/networkserver/networkserver.pb.go | 1393 +++++++++ api/networkserver/networkserver.proto | 60 + api/protocol/lorawan/lorawan.pb.go | 1891 ++++++++++++ api/protocol/lorawan/lorawan.proto | 64 + api/protocol/protocol.pb.go | 1171 ++++++++ api/protocol/protocol.proto | 30 + api/router/router.pb.go | 3117 +++++++++++++++++++ api/router/router.proto | 124 + 21 files changed, 16639 insertions(+) create mode 100644 api/api.pb.go create mode 100644 api/api.proto create mode 100644 api/broker/broker.pb.go create mode 100644 api/broker/broker.proto create mode 100644 api/discovery/discovery.pb.go create mode 100644 api/discovery/discovery.proto create mode 100644 api/gateway/gateway.pb.go create mode 100644 api/gateway/gateway.proto create mode 100644 api/gateway/semtech/semtech.pb.go create mode 100644 api/gateway/semtech/semtech.proto create mode 100644 api/handler/handler.pb.go create mode 100644 api/handler/handler.proto create mode 100644 api/networkserver/networkserver.pb.go create mode 100644 api/networkserver/networkserver.proto create mode 100644 api/protocol/lorawan/lorawan.pb.go create mode 100644 api/protocol/lorawan/lorawan.proto create mode 100644 api/protocol/protocol.pb.go create mode 100644 api/protocol/protocol.proto create mode 100644 api/router/router.pb.go create mode 100644 api/router/router.proto diff --git a/Makefile b/Makefile index 587acbcdc..09ac0cef7 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,8 @@ export CGO_ENABLED=0 GOCMD = go GOBUILD = $(GOCMD) build +PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn + GIT_COMMIT = `git rev-parse HEAD 2>/dev/null` BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` @@ -50,6 +52,16 @@ proto-deps: proto: find core/protos -name '*.proto' | xargs protoc --gofast_out=plugins=grpc:./core -I=core/protos + @$(PROTOC)/api/*.proto + @$(PROTOC)/api/protocol/protocol.proto + @$(PROTOC)/api/protocol/**/*.proto + @$(PROTOC)/api/gateway/gateway.proto + @$(PROTOC)/api/gateway/**/*.proto + @$(PROTOC)/api/router/router.proto + @$(PROTOC)/api/broker/broker.proto + @$(PROTOC)/api/handler/handler.proto + @$(PROTOC)/api/networkserver/networkserver.proto + @$(PROTOC)/api/discovery/discovery.proto cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi @@ -77,6 +89,7 @@ clean: [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] ([ -d $(TEMP_COVER_DIR) ] && rm -rf $(TEMP_COVER_DIR)) || [ ! -d $(TEMP_COVER_DIR) ] ([ -f $(COVER_FILE) ] && rm $(COVER_FILE)) || [ ! -d $(COVER_FILE) ] + find ./api -name '*.pb.go' | xargs rm -f build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) diff --git a/api/api.pb.go b/api/api.pb.go new file mode 100644 index 000000000..1bcd89580 --- /dev/null +++ b/api/api.pb.go @@ -0,0 +1,809 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/api.proto +// DO NOT EDIT! + +/* + Package api is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/api.proto + + It has these top-level messages: + Ack + Percentiles + Rates + ServerMetadata +*/ +package api + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +// message Ack is used to acknowledge a request, without giving a response. +type Ack struct { +} + +func (m *Ack) Reset() { *m = Ack{} } +func (m *Ack) String() string { return proto.CompactTextString(m) } +func (*Ack) ProtoMessage() {} +func (*Ack) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{0} } + +type Percentiles struct { + Percentile1 float32 `protobuf:"fixed32,1,opt,name=percentile1,proto3" json:"percentile1,omitempty"` + Percentile5 float32 `protobuf:"fixed32,2,opt,name=percentile5,proto3" json:"percentile5,omitempty"` + Percentile10 float32 `protobuf:"fixed32,3,opt,name=percentile10,proto3" json:"percentile10,omitempty"` + Percentile25 float32 `protobuf:"fixed32,4,opt,name=percentile25,proto3" json:"percentile25,omitempty"` + Percentile50 float32 `protobuf:"fixed32,5,opt,name=percentile50,proto3" json:"percentile50,omitempty"` + Percentile75 float32 `protobuf:"fixed32,6,opt,name=percentile75,proto3" json:"percentile75,omitempty"` + Percentile90 float32 `protobuf:"fixed32,7,opt,name=percentile90,proto3" json:"percentile90,omitempty"` + Percentile95 float32 `protobuf:"fixed32,8,opt,name=percentile95,proto3" json:"percentile95,omitempty"` + Percentile99 float32 `protobuf:"fixed32,9,opt,name=percentile99,proto3" json:"percentile99,omitempty"` +} + +func (m *Percentiles) Reset() { *m = Percentiles{} } +func (m *Percentiles) String() string { return proto.CompactTextString(m) } +func (*Percentiles) ProtoMessage() {} +func (*Percentiles) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} } + +type Rates struct { + Rate1 float32 `protobuf:"fixed32,1,opt,name=rate1,proto3" json:"rate1,omitempty"` + Rate5 float32 `protobuf:"fixed32,2,opt,name=rate5,proto3" json:"rate5,omitempty"` + Rate15 float32 `protobuf:"fixed32,3,opt,name=rate15,proto3" json:"rate15,omitempty"` +} + +func (m *Rates) Reset() { *m = Rates{} } +func (m *Rates) String() string { return proto.CompactTextString(m) } +func (*Rates) ProtoMessage() {} +func (*Rates) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} } + +type ServerMetadata struct { +} + +func (m *ServerMetadata) Reset() { *m = ServerMetadata{} } +func (m *ServerMetadata) String() string { return proto.CompactTextString(m) } +func (*ServerMetadata) ProtoMessage() {} +func (*ServerMetadata) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3} } + +func init() { + proto.RegisterType((*Ack)(nil), "api.Ack") + proto.RegisterType((*Percentiles)(nil), "api.Percentiles") + proto.RegisterType((*Rates)(nil), "api.Rates") + proto.RegisterType((*ServerMetadata)(nil), "api.ServerMetadata") +} +func (m *Ack) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Ack) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *Percentiles) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Percentiles) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Percentile1 != 0 { + data[i] = 0xd + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile1)))) + } + if m.Percentile5 != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile5)))) + } + if m.Percentile10 != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile10)))) + } + if m.Percentile25 != 0 { + data[i] = 0x25 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile25)))) + } + if m.Percentile50 != 0 { + data[i] = 0x2d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile50)))) + } + if m.Percentile75 != 0 { + data[i] = 0x35 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile75)))) + } + if m.Percentile90 != 0 { + data[i] = 0x3d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile90)))) + } + if m.Percentile95 != 0 { + data[i] = 0x45 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile95)))) + } + if m.Percentile99 != 0 { + data[i] = 0x4d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile99)))) + } + return i, nil +} + +func (m *Rates) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Rates) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Rate1 != 0 { + data[i] = 0xd + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Rate1)))) + } + if m.Rate5 != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Rate5)))) + } + if m.Rate15 != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Rate15)))) + } + return i, nil +} + +func (m *ServerMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ServerMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64Api(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Api(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintApi(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *Ack) Size() (n int) { + var l int + _ = l + return n +} + +func (m *Percentiles) Size() (n int) { + var l int + _ = l + if m.Percentile1 != 0 { + n += 5 + } + if m.Percentile5 != 0 { + n += 5 + } + if m.Percentile10 != 0 { + n += 5 + } + if m.Percentile25 != 0 { + n += 5 + } + if m.Percentile50 != 0 { + n += 5 + } + if m.Percentile75 != 0 { + n += 5 + } + if m.Percentile90 != 0 { + n += 5 + } + if m.Percentile95 != 0 { + n += 5 + } + if m.Percentile99 != 0 { + n += 5 + } + return n +} + +func (m *Rates) Size() (n int) { + var l int + _ = l + if m.Rate1 != 0 { + n += 5 + } + if m.Rate5 != 0 { + n += 5 + } + if m.Rate15 != 0 { + n += 5 + } + return n +} + +func (m *ServerMetadata) Size() (n int) { + var l int + _ = l + return n +} + +func sovApi(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozApi(x uint64) (n int) { + return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Ack) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Ack: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Ack: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Percentiles) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Percentiles: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Percentiles: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile10", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile10 = float32(math.Float32frombits(v)) + case 4: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile25", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile25 = float32(math.Float32frombits(v)) + case 5: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile50", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile50 = float32(math.Float32frombits(v)) + case 6: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile75", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile75 = float32(math.Float32frombits(v)) + case 7: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile90", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile90 = float32(math.Float32frombits(v)) + case 8: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile95", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile95 = float32(math.Float32frombits(v)) + case 9: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Percentile99", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Percentile99 = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Rates) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Rates: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Rates: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Rate1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Rate5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rate15", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Rate15 = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ServerMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ServerMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ServerMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipApi(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthApi + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowApi + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipApi(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthApi = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowApi = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorApi = []byte{ + // 263 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, + 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0x04, + 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x66, 0x20, 0x53, 0x89, 0x95, 0x8b, 0xd9, 0x31, + 0x39, 0x5b, 0xe9, 0x2c, 0x13, 0x17, 0x77, 0x40, 0x6a, 0x51, 0x72, 0x6a, 0x5e, 0x49, 0x66, 0x4e, + 0x6a, 0xb1, 0x90, 0x02, 0x17, 0x77, 0x01, 0x9c, 0x6b, 0x28, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x14, + 0x84, 0x2c, 0x84, 0xaa, 0xc2, 0x54, 0x82, 0x09, 0x5d, 0x85, 0xa9, 0x90, 0x12, 0x17, 0x0f, 0x92, + 0x06, 0x03, 0x09, 0x66, 0xb0, 0x12, 0x14, 0x31, 0x54, 0x35, 0x46, 0xa6, 0x12, 0x2c, 0xe8, 0x6a, + 0x8c, 0xd0, 0xcc, 0x31, 0x35, 0x90, 0x60, 0x45, 0x57, 0x63, 0x8a, 0x66, 0x8e, 0xb9, 0xa9, 0x04, + 0x1b, 0xba, 0x1a, 0x73, 0x34, 0x73, 0x2c, 0x0d, 0x24, 0xd8, 0xd1, 0xd5, 0x58, 0xa2, 0x99, 0x63, + 0x69, 0x2a, 0xc1, 0x81, 0xa1, 0x06, 0xdd, 0x1c, 0x4b, 0x09, 0x4e, 0x0c, 0x35, 0x96, 0x4a, 0xde, + 0x5c, 0xac, 0x41, 0x89, 0x25, 0xc0, 0x80, 0x14, 0xe1, 0x62, 0x2d, 0x02, 0x32, 0x60, 0x41, 0x08, + 0xe1, 0xc0, 0x44, 0x61, 0xc1, 0x06, 0xe1, 0x08, 0x89, 0x71, 0xb1, 0x81, 0xa5, 0x4d, 0xa1, 0x41, + 0x05, 0xe5, 0x29, 0x09, 0x70, 0xf1, 0x05, 0xa7, 0x16, 0x95, 0xa5, 0x16, 0xf9, 0xa6, 0x96, 0x24, + 0xa6, 0x24, 0x96, 0x24, 0x3a, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, + 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0x38, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd1, + 0xa3, 0x30, 0x03, 0x04, 0x02, 0x00, 0x00, +} diff --git a/api/api.proto b/api/api.proto new file mode 100644 index 000000000..a77d8ba6e --- /dev/null +++ b/api/api.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package api; + +// message Ack is used to acknowledge a request, without giving a response. +message Ack {} + +message Percentiles { + float percentile1 = 1; + float percentile5 = 2; + float percentile10 = 3; + float percentile25 = 4; + float percentile50 = 5; + float percentile75 = 6; + float percentile90 = 7; + float percentile95 = 8; + float percentile99 = 9; +} + +message Rates { + float rate1 = 1; + float rate5 = 2; + float rate15 = 3; +} + +message ServerMetadata { + +} diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go new file mode 100644 index 000000000..952943954 --- /dev/null +++ b/api/broker/broker.pb.go @@ -0,0 +1,3951 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/broker/broker.proto +// DO NOT EDIT! + +/* + Package broker is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/broker/broker.proto + + It has these top-level messages: + DownlinkOption + UplinkMessage + DownlinkMessage + DeviceActivationResponse + DeduplicatedUplinkMessage + DeviceActivationRequest + DeduplicatedDeviceActivationRequest + SubscribeRequest + ApplicationsRequest + ApplicationsResponse + RegisterApplicationRequest + UnregisterApplicationRequest + StatusRequest + StatusResponse +*/ +package broker + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import api "github.com/TheThingsNetwork/ttn/api" +import protocol "github.com/TheThingsNetwork/ttn/api/protocol" +import gateway "github.com/TheThingsNetwork/ttn/api/gateway" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type DownlinkOption struct { + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + Score uint32 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` + Deadline int64 `protobuf:"varint,3,opt,name=deadline,proto3" json:"deadline,omitempty"` +} + +func (m *DownlinkOption) Reset() { *m = DownlinkOption{} } +func (m *DownlinkOption) String() string { return proto.CompactTextString(m) } +func (*DownlinkOption) ProtoMessage() {} +func (*DownlinkOption) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{0} } + +// received from the Router +type UplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,11,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,12,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + DownlinkOptions []*DownlinkOption `protobuf:"bytes,21,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` +} + +func (m *UplinkMessage) Reset() { *m = UplinkMessage{} } +func (m *UplinkMessage) String() string { return proto.CompactTextString(m) } +func (*UplinkMessage) ProtoMessage() {} +func (*UplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{1} } + +func (m *UplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *UplinkMessage) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *UplinkMessage) GetDownlinkOptions() []*DownlinkOption { + if m != nil { + return m.DownlinkOptions + } + return nil +} + +// received from the Handler, sent to the Router, used as Template +type DownlinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DownlinkOption *DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` +} + +func (m *DownlinkMessage) Reset() { *m = DownlinkMessage{} } +func (m *DownlinkMessage) String() string { return proto.CompactTextString(m) } +func (*DownlinkMessage) ProtoMessage() {} +func (*DownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } + +func (m *DownlinkMessage) GetDownlinkOption() *DownlinkOption { + if m != nil { + return m.DownlinkOption + } + return nil +} + +// sent to the Router, used as Template +type DeviceActivationResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DownlinkOption *DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` +} + +func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } +func (m *DeviceActivationResponse) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationResponse) ProtoMessage() {} +func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } + +func (m *DeviceActivationResponse) GetDownlinkOption() *DownlinkOption { + if m != nil { + return m.DownlinkOption + } + return nil +} + +// sent to the Handler +type DeduplicatedUplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` + AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` +} + +func (m *DeduplicatedUplinkMessage) Reset() { *m = DeduplicatedUplinkMessage{} } +func (m *DeduplicatedUplinkMessage) String() string { return proto.CompactTextString(m) } +func (*DeduplicatedUplinkMessage) ProtoMessage() {} +func (*DeduplicatedUplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{4} } + +func (m *DeduplicatedUplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeduplicatedUplinkMessage) GetGatewayMetadata() []*gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeduplicatedUplinkMessage) GetResponseTemplate() *DownlinkMessage { + if m != nil { + return m.ResponseTemplate + } + return nil +} + +// received from the Router +type DeviceActivationRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` + AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` +} + +func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } +func (m *DeviceActivationRequest) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationRequest) ProtoMessage() {} +func (*DeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{5} } + +func (m *DeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetDownlinkOptions() []*DownlinkOption { + if m != nil { + return m.DownlinkOptions + } + return nil +} + +// sent to the Handler +type DeduplicatedDeviceActivationRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` + AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + ResponseTemplate *DeviceActivationResponse `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` +} + +func (m *DeduplicatedDeviceActivationRequest) Reset() { *m = DeduplicatedDeviceActivationRequest{} } +func (m *DeduplicatedDeviceActivationRequest) String() string { return proto.CompactTextString(m) } +func (*DeduplicatedDeviceActivationRequest) ProtoMessage() {} +func (*DeduplicatedDeviceActivationRequest) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{6} +} + +func (m *DeduplicatedDeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + +func (m *DeduplicatedDeviceActivationRequest) GetResponseTemplate() *DeviceActivationResponse { + if m != nil { + return m.ResponseTemplate + } + return nil +} + +// message SubscribeRequest is used by a Handler to subscribe to uplink messages +// for a certain application. +type SubscribeRequest struct { +} + +func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} } +func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) } +func (*SubscribeRequest) ProtoMessage() {} +func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{7} } + +type ApplicationsRequest struct { +} + +func (m *ApplicationsRequest) Reset() { *m = ApplicationsRequest{} } +func (m *ApplicationsRequest) String() string { return proto.CompactTextString(m) } +func (*ApplicationsRequest) ProtoMessage() {} +func (*ApplicationsRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{8} } + +type ApplicationsResponse struct { + AppEuis []string `protobuf:"bytes,1,rep,name=app_euis,json=appEuis" json:"app_euis,omitempty"` +} + +func (m *ApplicationsResponse) Reset() { *m = ApplicationsResponse{} } +func (m *ApplicationsResponse) String() string { return proto.CompactTextString(m) } +func (*ApplicationsResponse) ProtoMessage() {} +func (*ApplicationsResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{9} } + +// message RegisterApplicationRequest is used to register an application at this +// Broker +type RegisterApplicationRequest struct { + AppEui string `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` +} + +func (m *RegisterApplicationRequest) Reset() { *m = RegisterApplicationRequest{} } +func (m *RegisterApplicationRequest) String() string { return proto.CompactTextString(m) } +func (*RegisterApplicationRequest) ProtoMessage() {} +func (*RegisterApplicationRequest) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{10} +} + +// message UnregisterApplicationRequest is used to unregister an application at +// this Broker +type UnregisterApplicationRequest struct { + AppEui string `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` +} + +func (m *UnregisterApplicationRequest) Reset() { *m = UnregisterApplicationRequest{} } +func (m *UnregisterApplicationRequest) String() string { return proto.CompactTextString(m) } +func (*UnregisterApplicationRequest) ProtoMessage() {} +func (*UnregisterApplicationRequest) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{11} +} + +// message StatusRequest is used to request the status of this Broker +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{12} } + +// message StatusResponse is the response to the StatusRequest +type StatusResponse struct { + // Uplink + Uplink *api.Rates `protobuf:"bytes,1,opt,name=uplink" json:"uplink,omitempty"` + UplinkUnique *api.Rates `protobuf:"bytes,2,opt,name=uplink_unique,json=uplinkUnique" json:"uplink_unique,omitempty"` + // Downlink + Downlink *api.Rates `protobuf:"bytes,11,opt,name=downlink" json:"downlink,omitempty"` + // Activations + Activations *api.Rates `protobuf:"bytes,21,opt,name=activations" json:"activations,omitempty"` + ActivationsUnique *api.Rates `protobuf:"bytes,22,opt,name=activations_unique,json=activationsUnique" json:"activations_unique,omitempty"` + ActivationsAccepted *api.Rates `protobuf:"bytes,23,opt,name=activations_accepted,json=activationsAccepted" json:"activations_accepted,omitempty"` + // Deduplication histogram percentiles + Deduplication *api.Percentiles `protobuf:"bytes,31,opt,name=deduplication" json:"deduplication,omitempty"` + // Connections + ConnectedRouters uint32 `protobuf:"varint,41,opt,name=connected_routers,json=connectedRouters,proto3" json:"connected_routers,omitempty"` + ConnectedHandlers uint32 `protobuf:"varint,42,opt,name=connected_handlers,json=connectedHandlers,proto3" json:"connected_handlers,omitempty"` +} + +func (m *StatusResponse) Reset() { *m = StatusResponse{} } +func (m *StatusResponse) String() string { return proto.CompactTextString(m) } +func (*StatusResponse) ProtoMessage() {} +func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{13} } + +func (m *StatusResponse) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *StatusResponse) GetUplinkUnique() *api.Rates { + if m != nil { + return m.UplinkUnique + } + return nil +} + +func (m *StatusResponse) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *StatusResponse) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + +func (m *StatusResponse) GetActivationsUnique() *api.Rates { + if m != nil { + return m.ActivationsUnique + } + return nil +} + +func (m *StatusResponse) GetActivationsAccepted() *api.Rates { + if m != nil { + return m.ActivationsAccepted + } + return nil +} + +func (m *StatusResponse) GetDeduplication() *api.Percentiles { + if m != nil { + return m.Deduplication + } + return nil +} + +func init() { + proto.RegisterType((*DownlinkOption)(nil), "broker.DownlinkOption") + proto.RegisterType((*UplinkMessage)(nil), "broker.UplinkMessage") + proto.RegisterType((*DownlinkMessage)(nil), "broker.DownlinkMessage") + proto.RegisterType((*DeviceActivationResponse)(nil), "broker.DeviceActivationResponse") + proto.RegisterType((*DeduplicatedUplinkMessage)(nil), "broker.DeduplicatedUplinkMessage") + proto.RegisterType((*DeviceActivationRequest)(nil), "broker.DeviceActivationRequest") + proto.RegisterType((*DeduplicatedDeviceActivationRequest)(nil), "broker.DeduplicatedDeviceActivationRequest") + proto.RegisterType((*SubscribeRequest)(nil), "broker.SubscribeRequest") + proto.RegisterType((*ApplicationsRequest)(nil), "broker.ApplicationsRequest") + proto.RegisterType((*ApplicationsResponse)(nil), "broker.ApplicationsResponse") + proto.RegisterType((*RegisterApplicationRequest)(nil), "broker.RegisterApplicationRequest") + proto.RegisterType((*UnregisterApplicationRequest)(nil), "broker.UnregisterApplicationRequest") + proto.RegisterType((*StatusRequest)(nil), "broker.StatusRequest") + proto.RegisterType((*StatusResponse)(nil), "broker.StatusResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Broker service + +type BrokerClient interface { + // Router initiates an Association with the Broker. + Associate(ctx context.Context, opts ...grpc.CallOption) (Broker_AssociateClient, error) + // Handler subscribes to uplink stream. + Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Broker_SubscribeClient, error) + // Handler initiates downlink stream. + Publish(ctx context.Context, opts ...grpc.CallOption) (Broker_PublishClient, error) + // Router requests device activation + Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) +} + +type brokerClient struct { + cc *grpc.ClientConn +} + +func NewBrokerClient(cc *grpc.ClientConn) BrokerClient { + return &brokerClient{cc} +} + +func (c *brokerClient) Associate(ctx context.Context, opts ...grpc.CallOption) (Broker_AssociateClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Broker_serviceDesc.Streams[0], c.cc, "/broker.Broker/Associate", opts...) + if err != nil { + return nil, err + } + x := &brokerAssociateClient{stream} + return x, nil +} + +type Broker_AssociateClient interface { + Send(*UplinkMessage) error + Recv() (*DownlinkMessage, error) + grpc.ClientStream +} + +type brokerAssociateClient struct { + grpc.ClientStream +} + +func (x *brokerAssociateClient) Send(m *UplinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *brokerAssociateClient) Recv() (*DownlinkMessage, error) { + m := new(DownlinkMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *brokerClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Broker_SubscribeClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Broker_serviceDesc.Streams[1], c.cc, "/broker.Broker/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &brokerSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Broker_SubscribeClient interface { + Recv() (*DeduplicatedUplinkMessage, error) + grpc.ClientStream +} + +type brokerSubscribeClient struct { + grpc.ClientStream +} + +func (x *brokerSubscribeClient) Recv() (*DeduplicatedUplinkMessage, error) { + m := new(DeduplicatedUplinkMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *brokerClient) Publish(ctx context.Context, opts ...grpc.CallOption) (Broker_PublishClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Broker_serviceDesc.Streams[2], c.cc, "/broker.Broker/Publish", opts...) + if err != nil { + return nil, err + } + x := &brokerPublishClient{stream} + return x, nil +} + +type Broker_PublishClient interface { + Send(*DownlinkMessage) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type brokerPublishClient struct { + grpc.ClientStream +} + +func (x *brokerPublishClient) Send(m *DownlinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *brokerPublishClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *brokerClient) Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { + out := new(DeviceActivationResponse) + err := grpc.Invoke(ctx, "/broker.Broker/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Broker service + +type BrokerServer interface { + // Router initiates an Association with the Broker. + Associate(Broker_AssociateServer) error + // Handler subscribes to uplink stream. + Subscribe(*SubscribeRequest, Broker_SubscribeServer) error + // Handler initiates downlink stream. + Publish(Broker_PublishServer) error + // Router requests device activation + Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) +} + +func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { + s.RegisterService(&_Broker_serviceDesc, srv) +} + +func _Broker_Associate_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(BrokerServer).Associate(&brokerAssociateServer{stream}) +} + +type Broker_AssociateServer interface { + Send(*DownlinkMessage) error + Recv() (*UplinkMessage, error) + grpc.ServerStream +} + +type brokerAssociateServer struct { + grpc.ServerStream +} + +func (x *brokerAssociateServer) Send(m *DownlinkMessage) error { + return x.ServerStream.SendMsg(m) +} + +func (x *brokerAssociateServer) Recv() (*UplinkMessage, error) { + m := new(UplinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Broker_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(BrokerServer).Subscribe(m, &brokerSubscribeServer{stream}) +} + +type Broker_SubscribeServer interface { + Send(*DeduplicatedUplinkMessage) error + grpc.ServerStream +} + +type brokerSubscribeServer struct { + grpc.ServerStream +} + +func (x *brokerSubscribeServer) Send(m *DeduplicatedUplinkMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _Broker_Publish_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(BrokerServer).Publish(&brokerPublishServer{stream}) +} + +type Broker_PublishServer interface { + SendAndClose(*api.Ack) error + Recv() (*DownlinkMessage, error) + grpc.ServerStream +} + +type brokerPublishServer struct { + grpc.ServerStream +} + +func (x *brokerPublishServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *brokerPublishServer) Recv() (*DownlinkMessage, error) { + m := new(DownlinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Broker_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerServer).Activate(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Broker_serviceDesc = grpc.ServiceDesc{ + ServiceName: "broker.Broker", + HandlerType: (*BrokerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Activate", + Handler: _Broker_Activate_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "Associate", + Handler: _Broker_Associate_Handler, + ServerStreams: true, + ClientStreams: true, + }, + { + StreamName: "Subscribe", + Handler: _Broker_Subscribe_Handler, + ServerStreams: true, + }, + { + StreamName: "Publish", + Handler: _Broker_Publish_Handler, + ClientStreams: true, + }, + }, +} + +// Client API for BrokerManager service + +type BrokerManagerClient interface { + // Network operator lists all Applications on this Broker + Applications(ctx context.Context, in *ApplicationsRequest, opts ...grpc.CallOption) (*ApplicationsResponse, error) + // Application owner registers Application with Broker Manager + RegisterApplication(ctx context.Context, in *RegisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) + // Application owner unregisters Application with Broker Manager + UnregisterApplication(ctx context.Context, in *UnregisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) + // Network operator requests Broker status + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) +} + +type brokerManagerClient struct { + cc *grpc.ClientConn +} + +func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { + return &brokerManagerClient{cc} +} + +func (c *brokerManagerClient) Applications(ctx context.Context, in *ApplicationsRequest, opts ...grpc.CallOption) (*ApplicationsResponse, error) { + out := new(ApplicationsResponse) + err := grpc.Invoke(ctx, "/broker.BrokerManager/Applications", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerManagerClient) RegisterApplication(ctx context.Context, in *RegisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/broker.BrokerManager/RegisterApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerManagerClient) UnregisterApplication(ctx context.Context, in *UnregisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/broker.BrokerManager/UnregisterApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *brokerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := grpc.Invoke(ctx, "/broker.BrokerManager/Status", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for BrokerManager service + +type BrokerManagerServer interface { + // Network operator lists all Applications on this Broker + Applications(context.Context, *ApplicationsRequest) (*ApplicationsResponse, error) + // Application owner registers Application with Broker Manager + RegisterApplication(context.Context, *RegisterApplicationRequest) (*api.Ack, error) + // Application owner unregisters Application with Broker Manager + UnregisterApplication(context.Context, *UnregisterApplicationRequest) (*api.Ack, error) + // Network operator requests Broker status + Status(context.Context, *StatusRequest) (*StatusResponse, error) +} + +func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { + s.RegisterService(&_BrokerManager_serviceDesc, srv) +} + +func _BrokerManager_Applications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(ApplicationsRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).Applications(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _BrokerManager_RegisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(RegisterApplicationRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).RegisterApplication(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _BrokerManager_UnregisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UnregisterApplicationRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).UnregisterApplication(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _BrokerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(BrokerManagerServer).Status(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _BrokerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "broker.BrokerManager", + HandlerType: (*BrokerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Applications", + Handler: _BrokerManager_Applications_Handler, + }, + { + MethodName: "RegisterApplication", + Handler: _BrokerManager_RegisterApplication_Handler, + }, + { + MethodName: "UnregisterApplication", + Handler: _BrokerManager_UnregisterApplication_Handler, + }, + { + MethodName: "Status", + Handler: _BrokerManager_Status_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DownlinkOption) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Identifier) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Identifier))) + i += copy(data[i:], m.Identifier) + } + if m.Score != 0 { + data[i] = 0x10 + i++ + i = encodeVarintBroker(data, i, uint64(m.Score)) + } + if m.Deadline != 0 { + data[i] = 0x18 + i++ + i = encodeVarintBroker(data, i, uint64(m.Deadline)) + } + return i, nil +} + +func (m *UplinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.ProtocolMetadata != nil { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) + n1, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.GatewayMetadata != nil { + data[i] = 0x62 + i++ + i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) + n2, err := m.GatewayMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if len(m.DownlinkOptions) > 0 { + for _, msg := range m.DownlinkOptions { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DownlinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.DownlinkOption != nil { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) + n3, err := m.DownlinkOption.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.DownlinkOption != nil { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) + n4, err := m.DownlinkOption.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *DeduplicatedUplinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if len(m.DevEui) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevEui))) + i += copy(data[i:], m.DevEui) + } + if len(m.AppEui) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + if m.ProtocolMetadata != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) + n5, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if len(m.GatewayMetadata) > 0 { + for _, msg := range m.GatewayMetadata { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.ResponseTemplate != nil { + data[i] = 0xfa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) + n6, err := m.ResponseTemplate.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} + +func (m *DeviceActivationRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if len(m.DevEui) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevEui))) + i += copy(data[i:], m.DevEui) + } + if len(m.AppEui) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + if m.ProtocolMetadata != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) + n7, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.GatewayMetadata != nil { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) + n8, err := m.GatewayMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } + if m.ActivationMetadata != nil { + data[i] = 0xba + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) + n9, err := m.ActivationMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n9 + } + if len(m.DownlinkOptions) > 0 { + for _, msg := range m.DownlinkOptions { + data[i] = 0xfa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DeduplicatedDeviceActivationRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if len(m.DevEui) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevEui))) + i += copy(data[i:], m.DevEui) + } + if len(m.AppEui) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + if m.ProtocolMetadata != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) + n10, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n10 + } + if m.GatewayMetadata != nil { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) + n11, err := m.GatewayMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n11 + } + if m.ActivationMetadata != nil { + data[i] = 0xba + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) + n12, err := m.ActivationMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n12 + } + if m.ResponseTemplate != nil { + data[i] = 0xfa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) + n13, err := m.ResponseTemplate.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n13 + } + return i, nil +} + +func (m *SubscribeRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *ApplicationsRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ApplicationsRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *ApplicationsResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ApplicationsResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEuis) > 0 { + for _, s := range m.AppEuis { + data[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + return i, nil +} + +func (m *RegisterApplicationRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RegisterApplicationRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEui) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + return i, nil +} + +func (m *UnregisterApplicationRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UnregisterApplicationRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEui) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + return i, nil +} + +func (m *StatusRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *StatusResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Uplink != nil { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) + n14, err := m.Uplink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n14 + } + if m.UplinkUnique != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) + n15, err := m.UplinkUnique.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n15 + } + if m.Downlink != nil { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) + n16, err := m.Downlink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n16 + } + if m.Activations != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) + n17, err := m.Activations.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n17 + } + if m.ActivationsUnique != nil { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) + n18, err := m.ActivationsUnique.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n18 + } + if m.ActivationsAccepted != nil { + data[i] = 0xba + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) + n19, err := m.ActivationsAccepted.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n19 + } + if m.Deduplication != nil { + data[i] = 0xfa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) + n20, err := m.Deduplication.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n20 + } + if m.ConnectedRouters != 0 { + data[i] = 0xc8 + i++ + data[i] = 0x2 + i++ + i = encodeVarintBroker(data, i, uint64(m.ConnectedRouters)) + } + if m.ConnectedHandlers != 0 { + data[i] = 0xd0 + i++ + data[i] = 0x2 + i++ + i = encodeVarintBroker(data, i, uint64(m.ConnectedHandlers)) + } + return i, nil +} + +func encodeFixed64Broker(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Broker(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintBroker(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DownlinkOption) Size() (n int) { + var l int + _ = l + l = len(m.Identifier) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.Score != 0 { + n += 1 + sovBroker(uint64(m.Score)) + } + if m.Deadline != 0 { + n += 1 + sovBroker(uint64(m.Deadline)) + } + return n +} + +func (m *UplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if len(m.DownlinkOptions) > 0 { + for _, e := range m.DownlinkOptions { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + return n +} + +func (m *DownlinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.DownlinkOption != nil { + l = m.DownlinkOption.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DeviceActivationResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.DownlinkOption != nil { + l = m.DownlinkOption.Size() + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DeduplicatedUplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if len(m.GatewayMetadata) > 0 { + for _, e := range m.GatewayMetadata { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + if m.ResponseTemplate != nil { + l = m.ResponseTemplate.Size() + n += 2 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *DeviceActivationRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if len(m.DownlinkOptions) > 0 { + for _, e := range m.DownlinkOptions { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } + } + return n +} + +func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ResponseTemplate != nil { + l = m.ResponseTemplate.Size() + n += 2 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *SubscribeRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *ApplicationsRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *ApplicationsResponse) Size() (n int) { + var l int + _ = l + if len(m.AppEuis) > 0 { + for _, s := range m.AppEuis { + l = len(s) + n += 1 + l + sovBroker(uint64(l)) + } + } + return n +} + +func (m *RegisterApplicationRequest) Size() (n int) { + var l int + _ = l + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *UnregisterApplicationRequest) Size() (n int) { + var l int + _ = l + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *StatusResponse) Size() (n int) { + var l int + _ = l + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.UplinkUnique != nil { + l = m.UplinkUnique.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ActivationsUnique != nil { + l = m.ActivationsUnique.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ActivationsAccepted != nil { + l = m.ActivationsAccepted.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.Deduplication != nil { + l = m.Deduplication.Size() + n += 2 + l + sovBroker(uint64(l)) + } + if m.ConnectedRouters != 0 { + n += 2 + sovBroker(uint64(m.ConnectedRouters)) + } + if m.ConnectedHandlers != 0 { + n += 2 + sovBroker(uint64(m.ConnectedHandlers)) + } + return n +} + +func sovBroker(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozBroker(x uint64) (n int) { + return sovBroker(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DownlinkOption) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DownlinkOption: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DownlinkOption: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Identifier", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Identifier = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Score", wireType) + } + m.Score = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Score |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Deadline", wireType) + } + m.Deadline = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Deadline |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UplinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOptions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DownlinkOptions = append(m.DownlinkOptions, &DownlinkOption{}) + if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DownlinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DownlinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DownlinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeduplicatedUplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeduplicatedUplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) + if m.DevEui == nil { + m.DevEui = []byte{} + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) + if m.AppEui == nil { + m.AppEui = []byte{} + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayMetadata = append(m.GatewayMetadata, &gateway.RxMetadata{}) + if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseTemplate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ResponseTemplate == nil { + m.ResponseTemplate = &DownlinkMessage{} + } + if err := m.ResponseTemplate.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) + if m.DevEui == nil { + m.DevEui = []byte{} + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) + if m.AppEui == nil { + m.AppEui = []byte{} + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOptions", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DownlinkOptions = append(m.DownlinkOptions, &DownlinkOption{}) + if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeduplicatedDeviceActivationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeduplicatedDeviceActivationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) + if m.DevEui == nil { + m.DevEui = []byte{} + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) + if m.AppEui == nil { + m.AppEui = []byte{} + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResponseTemplate", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ResponseTemplate == nil { + m.ResponseTemplate = &DeviceActivationResponse{} + } + if err := m.ResponseTemplate.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SubscribeRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubscribeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubscribeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ApplicationsRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationsRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ApplicationsResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationsResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEuis", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEuis = append(m.AppEuis, string(data[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RegisterApplicationRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RegisterApplicationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RegisterApplicationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UnregisterApplicationRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UnregisterApplicationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UnregisterApplicationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field UplinkUnique", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.UplinkUnique == nil { + m.UplinkUnique = &api.Rates{} + } + if err := m.UplinkUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationsUnique", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationsUnique == nil { + m.ActivationsUnique = &api.Rates{} + } + if err := m.ActivationsUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationsAccepted", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationsAccepted == nil { + m.ActivationsAccepted = &api.Rates{} + } + if err := m.ActivationsAccepted.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Deduplication", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Deduplication == nil { + m.Deduplication = &api.Percentiles{} + } + if err := m.Deduplication.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 41: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedRouters", wireType) + } + m.ConnectedRouters = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ConnectedRouters |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 42: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedHandlers", wireType) + } + m.ConnectedHandlers = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ConnectedHandlers |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipBroker(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthBroker + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowBroker + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipBroker(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthBroker = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowBroker = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorBroker = []byte{ + // 953 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0x26, 0x89, 0x48, 0xdb, 0xd3, 0xa4, 0x49, 0xa7, 0x4d, 0xeb, 0x35, 0x55, 0x5b, 0x0c, 0x42, + 0xe5, 0x67, 0x13, 0x28, 0x82, 0x6a, 0x85, 0x00, 0x65, 0x29, 0xe2, 0x47, 0x0a, 0xac, 0xbc, 0xed, + 0x75, 0x34, 0xb1, 0x87, 0x64, 0xd4, 0xd4, 0x36, 0x9e, 0x71, 0xcb, 0xbe, 0x06, 0xe2, 0x82, 0x5b, + 0xde, 0x80, 0xc7, 0x80, 0x3b, 0x1e, 0x01, 0xc1, 0x03, 0x70, 0xcf, 0x15, 0xe3, 0xf9, 0xf1, 0x4f, + 0x1b, 0xb3, 0xa1, 0x88, 0x0b, 0xa4, 0xbd, 0xb0, 0x92, 0x39, 0xbf, 0x73, 0xce, 0x77, 0x3e, 0x1f, + 0xc3, 0xc9, 0x94, 0xf2, 0x59, 0x32, 0xe9, 0x7b, 0xe1, 0xe5, 0xe0, 0x6c, 0x46, 0xce, 0x66, 0x34, + 0x98, 0xb2, 0x2f, 0x08, 0xbf, 0x0e, 0xe3, 0x8b, 0x01, 0xe7, 0xc1, 0x00, 0x47, 0x74, 0x30, 0x89, + 0xc3, 0x0b, 0x12, 0xeb, 0x9f, 0x7e, 0x14, 0x87, 0x3c, 0x44, 0x4d, 0x75, 0xb2, 0xef, 0x2f, 0x13, + 0x40, 0x3c, 0xca, 0xcd, 0x7e, 0x6f, 0x19, 0x73, 0x69, 0xea, 0x85, 0xf3, 0xec, 0x8f, 0x76, 0x7e, + 0xb0, 0x8c, 0xf3, 0x14, 0x73, 0x72, 0x8d, 0x9f, 0x98, 0x5f, 0xe5, 0xea, 0x4c, 0x60, 0xe3, 0x34, + 0xbc, 0x0e, 0xe6, 0x34, 0xb8, 0xf8, 0x32, 0xe2, 0x34, 0x0c, 0xd0, 0x3e, 0x00, 0xf5, 0x49, 0xc0, + 0xe9, 0x57, 0x94, 0xc4, 0x56, 0xed, 0xb0, 0x76, 0xb4, 0xe6, 0x16, 0x24, 0x68, 0x1b, 0x9e, 0x67, + 0x5e, 0x18, 0x13, 0xab, 0x2e, 0x54, 0x6d, 0x57, 0x1d, 0x90, 0x0d, 0xab, 0x3e, 0xc1, 0xbe, 0x88, + 0x43, 0xac, 0x86, 0x50, 0x34, 0xdc, 0xec, 0xec, 0xfc, 0x51, 0x83, 0xf6, 0x79, 0x94, 0xa6, 0x18, + 0x11, 0xc6, 0xf0, 0x94, 0x20, 0x0b, 0x56, 0x22, 0xfc, 0x64, 0x1e, 0x62, 0x5f, 0x26, 0x68, 0xb9, + 0xe6, 0x88, 0x86, 0xb0, 0x69, 0x8a, 0x1b, 0x5f, 0x12, 0x8e, 0x7d, 0xcc, 0xb1, 0xb5, 0x2e, 0x6c, + 0xd6, 0x8f, 0xb7, 0xfb, 0x59, 0xd9, 0xee, 0x37, 0x23, 0xad, 0x73, 0xbb, 0x46, 0x68, 0x24, 0xe8, + 0x03, 0xe8, 0xea, 0x1a, 0xf3, 0x08, 0x2d, 0x19, 0x61, 0xab, 0x6f, 0x8a, 0x2f, 0x04, 0xe8, 0x68, + 0x59, 0xe6, 0x3f, 0x84, 0xae, 0xaf, 0x5b, 0x32, 0x0e, 0x65, 0x4f, 0x98, 0xd5, 0x3b, 0x6c, 0x08, + 0xff, 0x9d, 0xbe, 0x86, 0xba, 0xdc, 0x32, 0xb7, 0xe3, 0x97, 0xce, 0xcc, 0x99, 0x43, 0xc7, 0x98, + 0x3c, 0xbd, 0xe4, 0x0f, 0xa1, 0x73, 0x23, 0x9f, 0x2e, 0xb8, 0x2a, 0xdd, 0x46, 0x39, 0x9d, 0x93, + 0x80, 0x75, 0x4a, 0xae, 0xa8, 0x47, 0x86, 0x1e, 0xa7, 0x57, 0x58, 0xda, 0x10, 0x16, 0x89, 0x8b, + 0xfc, 0xa7, 0x69, 0x7f, 0xac, 0xc3, 0xbd, 0x53, 0xe2, 0x27, 0x02, 0x59, 0x4f, 0xb4, 0xd0, 0x5f, + 0x16, 0xe2, 0x5d, 0x58, 0xf1, 0xc9, 0xd5, 0x98, 0x24, 0x54, 0x26, 0x6c, 0xb9, 0x4d, 0x71, 0xfc, + 0x38, 0xa1, 0xa9, 0x02, 0x47, 0x91, 0x54, 0xb4, 0x94, 0x42, 0x1c, 0x53, 0xc5, 0xc2, 0xa1, 0xe8, + 0xfd, 0xeb, 0xa1, 0xd8, 0x91, 0xa0, 0x2e, 0x37, 0x14, 0xa7, 0xb0, 0x19, 0xeb, 0x9e, 0x8e, 0x39, + 0xb9, 0x8c, 0xe6, 0x42, 0x6f, 0x1d, 0xc8, 0x2b, 0xec, 0xde, 0xec, 0x97, 0x6e, 0x81, 0xdb, 0x35, + 0x1e, 0x67, 0xda, 0xc1, 0xf9, 0xb3, 0x0e, 0xbb, 0xb7, 0xa1, 0xfa, 0x3a, 0x21, 0x8c, 0xff, 0x3f, + 0x1a, 0xb6, 0x3c, 0x8b, 0x46, 0xb0, 0x85, 0xb3, 0x1a, 0xf3, 0x10, 0xbb, 0x32, 0xc4, 0x5e, 0x7e, + 0x89, 0xbc, 0x11, 0x59, 0x2c, 0x84, 0x6f, 0xc9, 0x16, 0x92, 0xf2, 0xe0, 0x9f, 0x91, 0xf2, 0xdb, + 0x06, 0xbc, 0x54, 0x9c, 0xd7, 0x67, 0x40, 0xdc, 0x05, 0x88, 0x51, 0x35, 0x11, 0x0e, 0x33, 0x24, + 0x2a, 0xde, 0x46, 0x0b, 0x18, 0x81, 0xa0, 0xfb, 0x38, 0x99, 0x30, 0x2f, 0xa6, 0x13, 0xa2, 0x01, + 0x70, 0x7a, 0xb0, 0x35, 0x8c, 0x14, 0x4a, 0x29, 0x70, 0x46, 0xfc, 0x16, 0x6c, 0x97, 0xc5, 0xfa, + 0x15, 0x77, 0x0f, 0x56, 0x75, 0xf3, 0x99, 0x00, 0xac, 0x21, 0xd6, 0xd5, 0x8a, 0xea, 0x3e, 0x73, + 0xde, 0x01, 0xdb, 0x25, 0x53, 0xca, 0x38, 0x89, 0x0b, 0xae, 0x06, 0xe8, 0x02, 0x6a, 0x6a, 0xcd, + 0x69, 0xd4, 0x9c, 0x13, 0xd8, 0x3b, 0x0f, 0xe2, 0x3b, 0x38, 0x76, 0xa0, 0xfd, 0x98, 0x63, 0x9e, + 0x64, 0x77, 0xfe, 0xb9, 0x01, 0x1b, 0x46, 0xa2, 0xaf, 0xeb, 0x40, 0x33, 0x91, 0x6f, 0x4a, 0xe9, + 0xbb, 0x7e, 0x0c, 0xfd, 0xf4, 0x2b, 0xc0, 0x15, 0xcd, 0x60, 0xae, 0xd6, 0xa0, 0x01, 0xb4, 0xd5, + 0xbf, 0x71, 0x12, 0x50, 0x11, 0x49, 0xee, 0xda, 0xb2, 0x69, 0x4b, 0x19, 0x9c, 0x4b, 0x3d, 0x7a, + 0x45, 0xac, 0x5f, 0x3d, 0xee, 0xfa, 0x2d, 0x5e, 0xb4, 0xcd, 0x74, 0xe8, 0x0d, 0x58, 0xcf, 0x31, + 0x65, 0x7a, 0x12, 0x8b, 0xa6, 0x45, 0x35, 0x7a, 0x00, 0x85, 0x09, 0x60, 0xe6, 0x2e, 0x3b, 0xb7, + 0x9c, 0x36, 0x0b, 0x56, 0xfa, 0x42, 0xef, 0xc3, 0x76, 0xd1, 0x15, 0x7b, 0x1e, 0x89, 0x04, 0xe7, + 0xf4, 0xd8, 0x15, 0x9d, 0x0b, 0xd3, 0xc9, 0x86, 0xda, 0x0c, 0xbd, 0x0b, 0x6d, 0x3f, 0xa3, 0x6a, + 0xba, 0x9a, 0xd4, 0x84, 0x75, 0xa5, 0xdf, 0x23, 0x12, 0x7b, 0xe9, 0xe7, 0xc8, 0x5c, 0x78, 0x97, + 0xcd, 0xd0, 0xeb, 0xb0, 0xe9, 0x85, 0x41, 0x40, 0x3c, 0x11, 0x64, 0x1c, 0x87, 0x89, 0xc0, 0x8f, + 0x59, 0xaf, 0xca, 0x0f, 0x95, 0x6e, 0xa6, 0x70, 0x95, 0x1c, 0xdd, 0x07, 0x94, 0x1b, 0xcf, 0x70, + 0xe0, 0xcf, 0x53, 0xeb, 0xd7, 0xa4, 0x75, 0x1e, 0xe6, 0x53, 0xad, 0x38, 0xfe, 0xae, 0x0e, 0xcd, + 0x87, 0x72, 0xc0, 0xc5, 0xee, 0x5c, 0x1b, 0x32, 0x16, 0x7a, 0x54, 0x54, 0x80, 0x7a, 0x66, 0xec, + 0x4b, 0x0b, 0xd0, 0xae, 0x5a, 0x0b, 0x47, 0xb5, 0x37, 0x6b, 0xe8, 0x73, 0x58, 0xcb, 0xc6, 0x1e, + 0x59, 0xc6, 0xf2, 0x26, 0x13, 0xec, 0x17, 0x73, 0x46, 0x55, 0xec, 0x59, 0x11, 0xab, 0x0f, 0x2b, + 0x8f, 0x92, 0xc9, 0x9c, 0xb2, 0x19, 0xaa, 0xca, 0x69, 0xaf, 0xca, 0xc6, 0x0d, 0xbd, 0x8b, 0xa3, + 0x9a, 0x60, 0xf0, 0xaa, 0xa6, 0x26, 0x41, 0x07, 0xd5, 0x94, 0x55, 0x37, 0x78, 0x2a, 0xa7, 0x8f, + 0x7f, 0xa8, 0x43, 0x5b, 0xb5, 0x65, 0x84, 0x03, 0x91, 0x2b, 0x46, 0x9f, 0x41, 0xab, 0x48, 0x54, + 0xf4, 0x82, 0x89, 0xb1, 0x80, 0xd5, 0xf6, 0xde, 0x62, 0xa5, 0x26, 0xcb, 0x47, 0xb0, 0xb5, 0x80, + 0xc0, 0xc8, 0x31, 0x4e, 0xd5, 0xec, 0xce, 0x4b, 0x46, 0x9f, 0x40, 0x6f, 0x21, 0x9d, 0xd1, 0xcb, + 0x19, 0x72, 0x7f, 0xc3, 0xf6, 0x42, 0xa0, 0x13, 0x68, 0x2a, 0x32, 0xe7, 0x98, 0x97, 0xe8, 0x6e, + 0xef, 0xdc, 0x14, 0xab, 0x32, 0x1e, 0x76, 0x7f, 0xfa, 0x6d, 0xbf, 0xf6, 0x8b, 0x78, 0x7e, 0x15, + 0xcf, 0xf7, 0xbf, 0xef, 0x3f, 0x37, 0x69, 0xca, 0xf7, 0xee, 0xdb, 0x7f, 0x05, 0x00, 0x00, 0xff, + 0xff, 0xb2, 0x6a, 0xc3, 0xc1, 0x68, 0x0c, 0x00, 0x00, +} diff --git a/api/broker/broker.proto b/api/broker/broker.proto new file mode 100644 index 000000000..aa86b0614 --- /dev/null +++ b/api/broker/broker.proto @@ -0,0 +1,142 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; +import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; + +package broker; + +message DownlinkOption { + string identifier = 1; + uint32 score = 2; // lower is better, 0 is best + int64 deadline = 3; // deadline time at server represented as the number of nanoseconds elapsed since January 1, 1970 UTC +} + +// received from the Router +message UplinkMessage { + bytes payload = 1; + protocol.RxMetadata protocol_metadata = 11; + gateway.RxMetadata gateway_metadata = 12; + repeated DownlinkOption downlink_options = 21; +} + +// received from the Handler, sent to the Router, used as Template +message DownlinkMessage { + bytes payload = 1; + DownlinkOption downlink_option = 11; +} + +//sent to the Router, used as Template +message DeviceActivationResponse { + bytes payload = 1; + DownlinkOption downlink_option = 11; +} + +// sent to the Handler +message DeduplicatedUplinkMessage { + bytes payload = 1; + bytes dev_eui = 11; + bytes app_eui = 12; + protocol.RxMetadata protocol_metadata = 21; + repeated gateway.RxMetadata gateway_metadata = 22; + DownlinkMessage response_template = 31; +} + +// received from the Router +message DeviceActivationRequest { + bytes payload = 1; + bytes dev_eui = 11; + bytes app_eui = 12; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + protocol.ActivationMetadata activation_metadata = 23; + repeated DownlinkOption downlink_options = 31; +} + +// sent to the Handler +message DeduplicatedDeviceActivationRequest { + bytes payload = 1; + bytes dev_eui = 11; + bytes app_eui = 12; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + protocol.ActivationMetadata activation_metadata = 23; + DeviceActivationResponse response_template = 31; +} + +// message SubscribeRequest is used by a Handler to subscribe to uplink messages +// for a certain application. +message SubscribeRequest {} + +// The Broker service provides pure network functionality +service Broker { + // Router initiates an Association with the Broker. + rpc Associate(stream UplinkMessage) returns (stream DownlinkMessage); + + // Handler subscribes to uplink stream. + rpc Subscribe(SubscribeRequest) returns (stream DeduplicatedUplinkMessage); + + // Handler initiates downlink stream. + rpc Publish(stream DownlinkMessage) returns (api.Ack); + + // Router requests device activation + rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); +} + +message ApplicationsRequest {} + +message ApplicationsResponse { + repeated string app_euis = 1; +} + +// message RegisterApplicationRequest is used to register an application at this +// Broker +message RegisterApplicationRequest { + string app_eui = 1; +} + +// message UnregisterApplicationRequest is used to unregister an application at +// this Broker +message UnregisterApplicationRequest { + string app_eui = 1; +} + +// message StatusRequest is used to request the status of this Broker +message StatusRequest {} + +// message StatusResponse is the response to the StatusRequest +message StatusResponse { + // Uplink + api.Rates uplink = 1; + api.Rates uplink_unique = 2; + + // Downlink + api.Rates downlink = 11; + + // Activations + api.Rates activations = 21; + api.Rates activations_unique = 22; + api.Rates activations_accepted = 23; + + // Deduplication histogram percentiles + api.Percentiles deduplication = 31; + + // Connections + uint32 connected_routers = 41; + uint32 connected_handlers = 42; +} + +// The BrokerManager service provides configuration and monitoring functionality +service BrokerManager { + // Network operator lists all Applications on this Broker + rpc Applications(ApplicationsRequest) returns (ApplicationsResponse); + + // Application owner registers Application with Broker Manager + rpc RegisterApplication(RegisterApplicationRequest) returns (api.Ack); + + // Application owner unregisters Application with Broker Manager + rpc UnregisterApplication(UnregisterApplicationRequest) returns (api.Ack); + + // Network operator requests Broker status + rpc Status(StatusRequest) returns (StatusResponse); +} diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go new file mode 100644 index 000000000..a383578c6 --- /dev/null +++ b/api/discovery/discovery.pb.go @@ -0,0 +1,874 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto +// DO NOT EDIT! + +/* + Package discovery is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto + + It has these top-level messages: + Announcement + DiscoverRequest + DiscoverResponse +*/ +package discovery + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import api "github.com/TheThingsNetwork/ttn/api" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type Announcement struct { + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + ServiceVersion string `protobuf:"bytes,2,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + NetAddress string `protobuf:"bytes,3,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` + Description string `protobuf:"bytes,11,opt,name=description,proto3" json:"description,omitempty"` + Fingerprint string `protobuf:"bytes,12,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` +} + +func (m *Announcement) Reset() { *m = Announcement{} } +func (m *Announcement) String() string { return proto.CompactTextString(m) } +func (*Announcement) ProtoMessage() {} +func (*Announcement) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } + +type DiscoverRequest struct { + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` +} + +func (m *DiscoverRequest) Reset() { *m = DiscoverRequest{} } +func (m *DiscoverRequest) String() string { return proto.CompactTextString(m) } +func (*DiscoverRequest) ProtoMessage() {} +func (*DiscoverRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{1} } + +type DiscoverResponse struct { + Services []*Announcement `protobuf:"bytes,1,rep,name=services" json:"services,omitempty"` +} + +func (m *DiscoverResponse) Reset() { *m = DiscoverResponse{} } +func (m *DiscoverResponse) String() string { return proto.CompactTextString(m) } +func (*DiscoverResponse) ProtoMessage() {} +func (*DiscoverResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } + +func (m *DiscoverResponse) GetServices() []*Announcement { + if m != nil { + return m.Services + } + return nil +} + +func init() { + proto.RegisterType((*Announcement)(nil), "discovery.Announcement") + proto.RegisterType((*DiscoverRequest)(nil), "discovery.DiscoverRequest") + proto.RegisterType((*DiscoverResponse)(nil), "discovery.DiscoverResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Discovery service + +type DiscoveryClient interface { + Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*api.Ack, error) + Discover(ctx context.Context, in *DiscoverRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) +} + +type discoveryClient struct { + cc *grpc.ClientConn +} + +func NewDiscoveryClient(cc *grpc.ClientConn) DiscoveryClient { + return &discoveryClient{cc} +} + +func (c *discoveryClient) Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/discovery.Discovery/Announce", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discoveryClient) Discover(ctx context.Context, in *DiscoverRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) { + out := new(DiscoverResponse) + err := grpc.Invoke(ctx, "/discovery.Discovery/Discover", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Discovery service + +type DiscoveryServer interface { + Announce(context.Context, *Announcement) (*api.Ack, error) + Discover(context.Context, *DiscoverRequest) (*DiscoverResponse, error) +} + +func RegisterDiscoveryServer(s *grpc.Server, srv DiscoveryServer) { + s.RegisterService(&_Discovery_serviceDesc, srv) +} + +func _Discovery_Announce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(Announcement) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(DiscoveryServer).Announce(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _Discovery_Discover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DiscoverRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(DiscoveryServer).Discover(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Discovery_serviceDesc = grpc.ServiceDesc{ + ServiceName: "discovery.Discovery", + HandlerType: (*DiscoveryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Announce", + Handler: _Discovery_Announce_Handler, + }, + { + MethodName: "Discover", + Handler: _Discovery_Discover_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +// Client API for DiscoveryManager service + +type DiscoveryManagerClient interface { +} + +type discoveryManagerClient struct { + cc *grpc.ClientConn +} + +func NewDiscoveryManagerClient(cc *grpc.ClientConn) DiscoveryManagerClient { + return &discoveryManagerClient{cc} +} + +// Server API for DiscoveryManager service + +type DiscoveryManagerServer interface { +} + +func RegisterDiscoveryManagerServer(s *grpc.Server, srv DiscoveryManagerServer) { + s.RegisterService(&_DiscoveryManager_serviceDesc, srv) +} + +var _DiscoveryManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "discovery.DiscoveryManager", + HandlerType: (*DiscoveryManagerServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{}, +} + +func (m *Announcement) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Announcement) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ServiceName) > 0 { + data[i] = 0xa + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) + i += copy(data[i:], m.ServiceName) + } + if len(m.ServiceVersion) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceVersion))) + i += copy(data[i:], m.ServiceVersion) + } + if len(m.NetAddress) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.NetAddress))) + i += copy(data[i:], m.NetAddress) + } + if len(m.Description) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) + i += copy(data[i:], m.Description) + } + if len(m.Fingerprint) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Fingerprint))) + i += copy(data[i:], m.Fingerprint) + } + return i, nil +} + +func (m *DiscoverRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DiscoverRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ServiceName) > 0 { + data[i] = 0xa + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) + i += copy(data[i:], m.ServiceName) + } + return i, nil +} + +func (m *DiscoverResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DiscoverResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Services) > 0 { + for _, msg := range m.Services { + data[i] = 0xa + i++ + i = encodeVarintDiscovery(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeFixed64Discovery(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Discovery(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintDiscovery(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *Announcement) Size() (n int) { + var l int + _ = l + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ServiceVersion) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.NetAddress) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.Fingerprint) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + return n +} + +func (m *DiscoverRequest) Size() (n int) { + var l int + _ = l + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + return n +} + +func (m *DiscoverResponse) Size() (n int) { + var l int + _ = l + if len(m.Services) > 0 { + for _, e := range m.Services { + l = e.Size() + n += 1 + l + sovDiscovery(uint64(l)) + } + } + return n +} + +func sovDiscovery(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozDiscovery(x uint64) (n int) { + return sovDiscovery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Announcement) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Announcement: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Announcement: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceVersion", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceVersion = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetAddress = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fingerprint", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fingerprint = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DiscoverRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DiscoverRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DiscoverRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DiscoverResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DiscoverResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DiscoverResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Services", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Services = append(m.Services, &Announcement{}) + if err := m.Services[len(m.Services)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDiscovery(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthDiscovery + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDiscovery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipDiscovery(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthDiscovery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDiscovery = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorDiscovery = []byte{ + // 340 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x91, 0xb1, 0x4e, 0xc2, 0x40, + 0x18, 0xc7, 0xad, 0x24, 0xa6, 0x7c, 0x25, 0x42, 0x6e, 0xb1, 0xa9, 0x09, 0x22, 0x8b, 0x2c, 0xb4, + 0x09, 0xb8, 0x3a, 0xa0, 0x26, 0x4e, 0x32, 0x10, 0xe2, 0x4a, 0x4a, 0xfb, 0x09, 0x17, 0xc2, 0x5d, + 0xbd, 0x3b, 0x30, 0x4c, 0xbe, 0x86, 0x6f, 0xe3, 0xea, 0xe8, 0x23, 0x18, 0x7d, 0x11, 0x8f, 0x5e, + 0x4b, 0x1b, 0xa3, 0x09, 0xc3, 0x25, 0xcd, 0xaf, 0xbf, 0xff, 0x77, 0xfd, 0x7f, 0x85, 0xab, 0x19, + 0x55, 0xf3, 0xd5, 0xd4, 0x8f, 0xf8, 0x32, 0x18, 0xcf, 0x71, 0x3c, 0xa7, 0x6c, 0x26, 0x87, 0xa8, + 0x9e, 0xb9, 0x58, 0x04, 0x4a, 0xb1, 0x20, 0x4c, 0x68, 0x10, 0x53, 0x19, 0xf1, 0x35, 0x8a, 0x4d, + 0xf1, 0xe4, 0x27, 0x82, 0x2b, 0x4e, 0xaa, 0x3b, 0xe0, 0x75, 0xf7, 0x99, 0xa4, 0x8f, 0x49, 0xb6, + 0xdf, 0x2c, 0xa8, 0x0d, 0x18, 0xe3, 0x2b, 0x16, 0xe1, 0x12, 0x99, 0x22, 0xe7, 0x50, 0x93, 0x28, + 0xd6, 0x34, 0xc2, 0x09, 0x0b, 0x97, 0xe8, 0x5a, 0x2d, 0xab, 0x53, 0x1d, 0x39, 0x19, 0x1b, 0x6a, + 0x44, 0x2e, 0xa0, 0x9e, 0x2b, 0xfa, 0x4a, 0x49, 0x39, 0x73, 0x0f, 0x53, 0xeb, 0x38, 0xc3, 0x0f, + 0x86, 0x92, 0x33, 0x70, 0x18, 0xaa, 0x49, 0x18, 0xc7, 0x02, 0xa5, 0x74, 0x2b, 0xa9, 0x04, 0x1a, + 0x0d, 0x0c, 0x21, 0x2d, 0x70, 0x62, 0x94, 0x91, 0xa0, 0x89, 0xda, 0x4e, 0x71, 0xcc, 0x5d, 0x25, + 0xb4, 0x35, 0x1e, 0x75, 0x03, 0x14, 0x89, 0xa0, 0x4c, 0xb9, 0x35, 0x63, 0x94, 0x50, 0xfb, 0x12, + 0xea, 0xb7, 0x59, 0xfb, 0x11, 0x3e, 0xad, 0x50, 0xee, 0xd3, 0xa1, 0x7d, 0x07, 0x8d, 0x22, 0x25, + 0x13, 0xce, 0x24, 0x92, 0x3e, 0xd8, 0x99, 0x22, 0x75, 0xa4, 0xd2, 0x71, 0x7a, 0x27, 0x7e, 0xb1, + 0xe9, 0xf2, 0x96, 0x46, 0x3b, 0xb1, 0xf7, 0x02, 0xd5, 0x7c, 0xd0, 0x86, 0x74, 0xc1, 0xce, 0x35, + 0xf2, 0x5f, 0xd6, 0xb3, 0xfd, 0xed, 0xfa, 0x07, 0xd1, 0x82, 0xdc, 0x80, 0x9d, 0x67, 0x89, 0x57, + 0xd2, 0x7f, 0xf5, 0xf1, 0x4e, 0xff, 0x7c, 0x67, 0xbe, 0xba, 0x47, 0x8a, 0x26, 0x9b, 0xfb, 0x90, + 0x85, 0x7a, 0x31, 0xd7, 0x8d, 0xf7, 0xaf, 0xa6, 0xf5, 0xa1, 0xcf, 0xa7, 0x3e, 0xaf, 0xdf, 0xcd, + 0x83, 0xe9, 0x51, 0xfa, 0xbb, 0xfb, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x46, 0xee, 0xf4, 0xf2, + 0x69, 0x02, 0x00, 0x00, +} diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto new file mode 100644 index 000000000..8ec68b0a9 --- /dev/null +++ b/api/discovery/discovery.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/api.proto"; + +package discovery; + +message Announcement { + string service_name = 1; + string service_version = 2; + string net_address = 3; + string description = 11; + string fingerprint = 12; +} + +message DiscoverRequest { + string service_name = 1; +} + +message DiscoverResponse { + repeated Announcement services = 1; +} + +service Discovery { + rpc Announce(Announcement) returns (api.Ack); + rpc Discover(DiscoverRequest) returns (DiscoverResponse); +} + +// The DiscoveryManager service provides configuration and monitoring functionality +service DiscoveryManager { + +} diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go new file mode 100644 index 000000000..0ca1327e9 --- /dev/null +++ b/api/gateway/gateway.pb.go @@ -0,0 +1,1666 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto +// DO NOT EDIT! + +/* + Package gateway is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto + + It has these top-level messages: + GPSMetadata + RxMetadata + TxConfiguration + StatusMessage +*/ +package gateway + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type GPSMetadata struct { + Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` + Latitude float32 `protobuf:"fixed32,2,opt,name=latitude,proto3" json:"latitude,omitempty"` + Longitude float32 `protobuf:"fixed32,3,opt,name=longitude,proto3" json:"longitude,omitempty"` + Altitude int32 `protobuf:"varint,4,opt,name=altitude,proto3" json:"altitude,omitempty"` +} + +func (m *GPSMetadata) Reset() { *m = GPSMetadata{} } +func (m *GPSMetadata) String() string { return proto.CompactTextString(m) } +func (*GPSMetadata) ProtoMessage() {} +func (*GPSMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{0} } + +type RxMetadata struct { + Frequency uint64 `protobuf:"varint,1,opt,name=frequency,proto3" json:"frequency,omitempty"` + Rssi float32 `protobuf:"fixed32,2,opt,name=rssi,proto3" json:"rssi,omitempty"` + Snr float32 `protobuf:"fixed32,3,opt,name=snr,proto3" json:"snr,omitempty"` + // Types that are valid to be assigned to Gateway: + // *RxMetadata_Semtech + Gateway isRxMetadata_Gateway `protobuf_oneof:"gateway"` +} + +func (m *RxMetadata) Reset() { *m = RxMetadata{} } +func (m *RxMetadata) String() string { return proto.CompactTextString(m) } +func (*RxMetadata) ProtoMessage() {} +func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{1} } + +type isRxMetadata_Gateway interface { + isRxMetadata_Gateway() + MarshalTo([]byte) (int, error) + Size() int +} + +type RxMetadata_Semtech struct { + Semtech *semtech.RxMetadata `protobuf:"bytes,11,opt,name=semtech,oneof"` +} + +func (*RxMetadata_Semtech) isRxMetadata_Gateway() {} + +func (m *RxMetadata) GetGateway() isRxMetadata_Gateway { + if m != nil { + return m.Gateway + } + return nil +} + +func (m *RxMetadata) GetSemtech() *semtech.RxMetadata { + if x, ok := m.GetGateway().(*RxMetadata_Semtech); ok { + return x.Semtech + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*RxMetadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _RxMetadata_OneofMarshaler, _RxMetadata_OneofUnmarshaler, _RxMetadata_OneofSizer, []interface{}{ + (*RxMetadata_Semtech)(nil), + } +} + +func _RxMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*RxMetadata) + // gateway + switch x := m.Gateway.(type) { + case *RxMetadata_Semtech: + _ = b.EncodeVarint(11<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Semtech); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("RxMetadata.Gateway has unexpected type %T", x) + } + return nil +} + +func _RxMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*RxMetadata) + switch tag { + case 11: // gateway.semtech + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(semtech.RxMetadata) + err := b.DecodeMessage(msg) + m.Gateway = &RxMetadata_Semtech{msg} + return true, err + default: + return false, nil + } +} + +func _RxMetadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*RxMetadata) + // gateway + switch x := m.Gateway.(type) { + case *RxMetadata_Semtech: + s := proto.Size(x.Semtech) + n += proto.SizeVarint(11<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type TxConfiguration struct { + Frequency uint64 `protobuf:"varint,1,opt,name=frequency,proto3" json:"frequency,omitempty"` + Power int32 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` + // Types that are valid to be assigned to Gateway: + // *TxConfiguration_Semtech + Gateway isTxConfiguration_Gateway `protobuf_oneof:"gateway"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{2} } + +type isTxConfiguration_Gateway interface { + isTxConfiguration_Gateway() + MarshalTo([]byte) (int, error) + Size() int +} + +type TxConfiguration_Semtech struct { + Semtech *semtech.TxConfiguration `protobuf:"bytes,11,opt,name=semtech,oneof"` +} + +func (*TxConfiguration_Semtech) isTxConfiguration_Gateway() {} + +func (m *TxConfiguration) GetGateway() isTxConfiguration_Gateway { + if m != nil { + return m.Gateway + } + return nil +} + +func (m *TxConfiguration) GetSemtech() *semtech.TxConfiguration { + if x, ok := m.GetGateway().(*TxConfiguration_Semtech); ok { + return x.Semtech + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*TxConfiguration) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _TxConfiguration_OneofMarshaler, _TxConfiguration_OneofUnmarshaler, _TxConfiguration_OneofSizer, []interface{}{ + (*TxConfiguration_Semtech)(nil), + } +} + +func _TxConfiguration_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*TxConfiguration) + // gateway + switch x := m.Gateway.(type) { + case *TxConfiguration_Semtech: + _ = b.EncodeVarint(11<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Semtech); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("TxConfiguration.Gateway has unexpected type %T", x) + } + return nil +} + +func _TxConfiguration_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*TxConfiguration) + switch tag { + case 11: // gateway.semtech + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(semtech.TxConfiguration) + err := b.DecodeMessage(msg) + m.Gateway = &TxConfiguration_Semtech{msg} + return true, err + default: + return false, nil + } +} + +func _TxConfiguration_OneofSizer(msg proto.Message) (n int) { + m := msg.(*TxConfiguration) + // gateway + switch x := m.Gateway.(type) { + case *TxConfiguration_Semtech: + s := proto.Size(x.Semtech) + n += proto.SizeVarint(11<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +// message StatusMessage represents a status update from a Gateway. +// See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat +type StatusMessage struct { + Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Ip []string `protobuf:"bytes,11,rep,name=ip" json:"ip,omitempty"` + Platform string `protobuf:"bytes,12,opt,name=platform,proto3" json:"platform,omitempty"` + ContactEmail string `protobuf:"bytes,13,opt,name=contact_email,json=contactEmail,proto3" json:"contact_email,omitempty"` + Description string `protobuf:"bytes,14,opt,name=description,proto3" json:"description,omitempty"` + Gps *GPSMetadata `protobuf:"bytes,21,opt,name=gps" json:"gps,omitempty"` + Rtt uint32 `protobuf:"varint,31,opt,name=rtt,proto3" json:"rtt,omitempty"` + RxIn uint32 `protobuf:"varint,41,opt,name=rx_in,json=rxIn,proto3" json:"rx_in,omitempty"` + RxOk uint32 `protobuf:"varint,42,opt,name=rx_ok,json=rxOk,proto3" json:"rx_ok,omitempty"` + TxIn uint32 `protobuf:"varint,43,opt,name=tx_in,json=txIn,proto3" json:"tx_in,omitempty"` + TxOk uint32 `protobuf:"varint,44,opt,name=tx_ok,json=txOk,proto3" json:"tx_ok,omitempty"` + // Types that are valid to be assigned to Gateway: + // *StatusMessage_Semtech + Gateway isStatusMessage_Gateway `protobuf_oneof:"gateway"` +} + +func (m *StatusMessage) Reset() { *m = StatusMessage{} } +func (m *StatusMessage) String() string { return proto.CompactTextString(m) } +func (*StatusMessage) ProtoMessage() {} +func (*StatusMessage) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3} } + +type isStatusMessage_Gateway interface { + isStatusMessage_Gateway() + MarshalTo([]byte) (int, error) + Size() int +} + +type StatusMessage_Semtech struct { + Semtech *semtech.StatusMessage `protobuf:"bytes,51,opt,name=semtech,oneof"` +} + +func (*StatusMessage_Semtech) isStatusMessage_Gateway() {} + +func (m *StatusMessage) GetGateway() isStatusMessage_Gateway { + if m != nil { + return m.Gateway + } + return nil +} + +func (m *StatusMessage) GetGps() *GPSMetadata { + if m != nil { + return m.Gps + } + return nil +} + +func (m *StatusMessage) GetSemtech() *semtech.StatusMessage { + if x, ok := m.GetGateway().(*StatusMessage_Semtech); ok { + return x.Semtech + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*StatusMessage) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _StatusMessage_OneofMarshaler, _StatusMessage_OneofUnmarshaler, _StatusMessage_OneofSizer, []interface{}{ + (*StatusMessage_Semtech)(nil), + } +} + +func _StatusMessage_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*StatusMessage) + // gateway + switch x := m.Gateway.(type) { + case *StatusMessage_Semtech: + _ = b.EncodeVarint(51<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Semtech); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("StatusMessage.Gateway has unexpected type %T", x) + } + return nil +} + +func _StatusMessage_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*StatusMessage) + switch tag { + case 51: // gateway.semtech + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(semtech.StatusMessage) + err := b.DecodeMessage(msg) + m.Gateway = &StatusMessage_Semtech{msg} + return true, err + default: + return false, nil + } +} + +func _StatusMessage_OneofSizer(msg proto.Message) (n int) { + m := msg.(*StatusMessage) + // gateway + switch x := m.Gateway.(type) { + case *StatusMessage_Semtech: + s := proto.Size(x.Semtech) + n += proto.SizeVarint(51<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +func init() { + proto.RegisterType((*GPSMetadata)(nil), "gateway.GPSMetadata") + proto.RegisterType((*RxMetadata)(nil), "gateway.RxMetadata") + proto.RegisterType((*TxConfiguration)(nil), "gateway.TxConfiguration") + proto.RegisterType((*StatusMessage)(nil), "gateway.StatusMessage") +} +func (m *GPSMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GPSMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Time != 0 { + data[i] = 0x8 + i++ + i = encodeVarintGateway(data, i, uint64(m.Time)) + } + if m.Latitude != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Latitude)))) + } + if m.Longitude != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Longitude)))) + } + if m.Altitude != 0 { + data[i] = 0x20 + i++ + i = encodeVarintGateway(data, i, uint64(m.Altitude)) + } + return i, nil +} + +func (m *RxMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RxMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Frequency != 0 { + data[i] = 0x8 + i++ + i = encodeVarintGateway(data, i, uint64(m.Frequency)) + } + if m.Rssi != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Rssi)))) + } + if m.Snr != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Snr)))) + } + if m.Gateway != nil { + nn1, err := m.Gateway.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *RxMetadata_Semtech) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Semtech != nil { + data[i] = 0x5a + i++ + i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) + n2, err := m.Semtech.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} +func (m *TxConfiguration) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Frequency != 0 { + data[i] = 0x8 + i++ + i = encodeVarintGateway(data, i, uint64(m.Frequency)) + } + if m.Power != 0 { + data[i] = 0x10 + i++ + i = encodeVarintGateway(data, i, uint64(m.Power)) + } + if m.Gateway != nil { + nn3, err := m.Gateway.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn3 + } + return i, nil +} + +func (m *TxConfiguration_Semtech) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Semtech != nil { + data[i] = 0x5a + i++ + i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) + n4, err := m.Semtech.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} +func (m *StatusMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Timestamp != 0 { + data[i] = 0x8 + i++ + i = encodeVarintGateway(data, i, uint64(m.Timestamp)) + } + if m.Time != 0 { + data[i] = 0x10 + i++ + i = encodeVarintGateway(data, i, uint64(m.Time)) + } + if len(m.Ip) > 0 { + for _, s := range m.Ip { + data[i] = 0x5a + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + if len(m.Platform) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintGateway(data, i, uint64(len(m.Platform))) + i += copy(data[i:], m.Platform) + } + if len(m.ContactEmail) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintGateway(data, i, uint64(len(m.ContactEmail))) + i += copy(data[i:], m.ContactEmail) + } + if len(m.Description) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintGateway(data, i, uint64(len(m.Description))) + i += copy(data[i:], m.Description) + } + if m.Gps != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) + n5, err := m.Gps.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Rtt != 0 { + data[i] = 0xf8 + i++ + data[i] = 0x1 + i++ + i = encodeVarintGateway(data, i, uint64(m.Rtt)) + } + if m.RxIn != 0 { + data[i] = 0xc8 + i++ + data[i] = 0x2 + i++ + i = encodeVarintGateway(data, i, uint64(m.RxIn)) + } + if m.RxOk != 0 { + data[i] = 0xd0 + i++ + data[i] = 0x2 + i++ + i = encodeVarintGateway(data, i, uint64(m.RxOk)) + } + if m.TxIn != 0 { + data[i] = 0xd8 + i++ + data[i] = 0x2 + i++ + i = encodeVarintGateway(data, i, uint64(m.TxIn)) + } + if m.TxOk != 0 { + data[i] = 0xe0 + i++ + data[i] = 0x2 + i++ + i = encodeVarintGateway(data, i, uint64(m.TxOk)) + } + if m.Gateway != nil { + nn6, err := m.Gateway.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn6 + } + return i, nil +} + +func (m *StatusMessage_Semtech) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Semtech != nil { + data[i] = 0x9a + i++ + data[i] = 0x3 + i++ + i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) + n7, err := m.Semtech.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } + return i, nil +} +func encodeFixed64Gateway(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Gateway(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintGateway(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *GPSMetadata) Size() (n int) { + var l int + _ = l + if m.Time != 0 { + n += 1 + sovGateway(uint64(m.Time)) + } + if m.Latitude != 0 { + n += 5 + } + if m.Longitude != 0 { + n += 5 + } + if m.Altitude != 0 { + n += 1 + sovGateway(uint64(m.Altitude)) + } + return n +} + +func (m *RxMetadata) Size() (n int) { + var l int + _ = l + if m.Frequency != 0 { + n += 1 + sovGateway(uint64(m.Frequency)) + } + if m.Rssi != 0 { + n += 5 + } + if m.Snr != 0 { + n += 5 + } + if m.Gateway != nil { + n += m.Gateway.Size() + } + return n +} + +func (m *RxMetadata_Semtech) Size() (n int) { + var l int + _ = l + if m.Semtech != nil { + l = m.Semtech.Size() + n += 1 + l + sovGateway(uint64(l)) + } + return n +} +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Frequency != 0 { + n += 1 + sovGateway(uint64(m.Frequency)) + } + if m.Power != 0 { + n += 1 + sovGateway(uint64(m.Power)) + } + if m.Gateway != nil { + n += m.Gateway.Size() + } + return n +} + +func (m *TxConfiguration_Semtech) Size() (n int) { + var l int + _ = l + if m.Semtech != nil { + l = m.Semtech.Size() + n += 1 + l + sovGateway(uint64(l)) + } + return n +} +func (m *StatusMessage) Size() (n int) { + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovGateway(uint64(m.Timestamp)) + } + if m.Time != 0 { + n += 1 + sovGateway(uint64(m.Time)) + } + if len(m.Ip) > 0 { + for _, s := range m.Ip { + l = len(s) + n += 1 + l + sovGateway(uint64(l)) + } + } + l = len(m.Platform) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + l = len(m.ContactEmail) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + l = len(m.Description) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } + if m.Gps != nil { + l = m.Gps.Size() + n += 2 + l + sovGateway(uint64(l)) + } + if m.Rtt != 0 { + n += 2 + sovGateway(uint64(m.Rtt)) + } + if m.RxIn != 0 { + n += 2 + sovGateway(uint64(m.RxIn)) + } + if m.RxOk != 0 { + n += 2 + sovGateway(uint64(m.RxOk)) + } + if m.TxIn != 0 { + n += 2 + sovGateway(uint64(m.TxIn)) + } + if m.TxOk != 0 { + n += 2 + sovGateway(uint64(m.TxOk)) + } + if m.Gateway != nil { + n += m.Gateway.Size() + } + return n +} + +func (m *StatusMessage_Semtech) Size() (n int) { + var l int + _ = l + if m.Semtech != nil { + l = m.Semtech.Size() + n += 2 + l + sovGateway(uint64(l)) + } + return n +} + +func sovGateway(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozGateway(x uint64) (n int) { + return sovGateway(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GPSMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GPSMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GPSMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Time |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Latitude = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Longitude = float32(math.Float32frombits(v)) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) + } + m.Altitude = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Altitude |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipGateway(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RxMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RxMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RxMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) + } + m.Frequency = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Frequency |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Rssi", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Rssi = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Snr", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Snr = float32(math.Float32frombits(v)) + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &semtech.RxMetadata{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Gateway = &RxMetadata_Semtech{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGateway(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) + } + m.Frequency = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Frequency |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) + } + m.Power = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Power |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &semtech.TxConfiguration{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Gateway = &TxConfiguration_Semtech{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGateway(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Time |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Ip", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Ip = append(m.Ip, string(data[iNdEx:postIndex])) + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Platform", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Platform = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ContactEmail", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ContactEmail = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Description = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Gps", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + if err := m.Gps.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rtt", wireType) + } + m.Rtt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Rtt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 41: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxIn", wireType) + } + m.RxIn = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RxIn |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 42: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxOk", wireType) + } + m.RxOk = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RxOk |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 43: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TxIn", wireType) + } + m.TxIn = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.TxIn |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 44: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TxOk", wireType) + } + m.TxOk = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.TxOk |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 51: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &semtech.StatusMessage{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Gateway = &StatusMessage_Semtech{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGateway(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGateway(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthGateway + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGateway + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipGateway(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthGateway = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGateway = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorGateway = []byte{ + // 499 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6e, 0x13, 0x31, + 0x10, 0x6e, 0xb2, 0x09, 0x25, 0x93, 0xa6, 0x54, 0x6e, 0x41, 0x56, 0x85, 0x4a, 0x15, 0x24, 0x54, + 0x7e, 0x94, 0x48, 0x2d, 0x17, 0x8e, 0x14, 0x21, 0xe0, 0x50, 0x40, 0x6e, 0xee, 0x95, 0xbb, 0x71, + 0x36, 0x56, 0xb2, 0xf6, 0x62, 0xcf, 0x2a, 0xe9, 0x91, 0x0b, 0x27, 0x1e, 0x80, 0x47, 0xe2, 0xc8, + 0x23, 0x20, 0x78, 0x11, 0x6c, 0x67, 0x7f, 0x42, 0x05, 0x42, 0x1c, 0x56, 0x9e, 0xf9, 0xfc, 0x79, + 0xe6, 0x1b, 0x7f, 0x5e, 0x78, 0x96, 0x48, 0x9c, 0xe6, 0x97, 0x83, 0x58, 0xa7, 0xc3, 0xd1, 0x54, + 0x8c, 0xa6, 0x52, 0x25, 0xf6, 0xad, 0xc0, 0x85, 0x36, 0xb3, 0x21, 0xa2, 0x1a, 0xf2, 0x4c, 0x0e, + 0x13, 0x8e, 0x62, 0xc1, 0xaf, 0xca, 0x75, 0x90, 0x19, 0x8d, 0x9a, 0x6c, 0x16, 0xe9, 0xfe, 0xf3, + 0xff, 0xa9, 0x61, 0x45, 0x8a, 0x22, 0x9e, 0x96, 0xeb, 0xaa, 0x56, 0x7f, 0x01, 0xdd, 0x57, 0xef, + 0xcf, 0xcf, 0x04, 0xf2, 0x31, 0x47, 0x4e, 0x08, 0xb4, 0x50, 0xa6, 0x82, 0x36, 0x0e, 0x1b, 0x47, + 0x11, 0x0b, 0x31, 0xd9, 0x87, 0x9b, 0x73, 0x8e, 0x12, 0xf3, 0xb1, 0xa0, 0x4d, 0x87, 0x37, 0x59, + 0x95, 0x93, 0xbb, 0xd0, 0x99, 0x6b, 0x95, 0xac, 0x36, 0xa3, 0xb0, 0x59, 0x03, 0xfe, 0x24, 0x9f, + 0x17, 0x27, 0x5b, 0x6e, 0xb3, 0xcd, 0xaa, 0xbc, 0xff, 0xb9, 0x01, 0xc0, 0x96, 0x55, 0x63, 0x57, + 0x68, 0x62, 0xc4, 0x87, 0x5c, 0xa8, 0xf8, 0x2a, 0x74, 0x6f, 0xb1, 0x1a, 0xf0, 0xb2, 0x8c, 0xb5, + 0xb2, 0x68, 0x1f, 0x62, 0xb2, 0x03, 0x91, 0x55, 0xa6, 0x68, 0xea, 0x43, 0x32, 0x84, 0xcd, 0x62, + 0x38, 0xda, 0x75, 0x68, 0xf7, 0x78, 0x77, 0x50, 0x0e, 0x5b, 0x77, 0x7a, 0xbd, 0xc1, 0x4a, 0xd6, + 0x69, 0x07, 0xca, 0xab, 0xec, 0x7f, 0x6a, 0xc0, 0xad, 0xd1, 0xf2, 0x85, 0x56, 0x13, 0x99, 0xe4, + 0xc6, 0x8d, 0xa7, 0xd5, 0x3f, 0x34, 0xed, 0x41, 0x3b, 0xd3, 0x0b, 0x61, 0x82, 0xa8, 0x36, 0x5b, + 0x25, 0xe4, 0xe9, 0x75, 0x0d, 0xb4, 0xd2, 0x70, 0xad, 0xfc, 0x5f, 0x84, 0x7c, 0x8c, 0xa0, 0x77, + 0x8e, 0x1c, 0x73, 0x7b, 0x26, 0xac, 0xe5, 0x49, 0xb8, 0x63, 0xef, 0x83, 0x45, 0x9e, 0x66, 0x41, + 0x46, 0x8f, 0xd5, 0x40, 0xe5, 0x58, 0x73, 0xcd, 0xb1, 0x6d, 0x68, 0xca, 0xcc, 0xf5, 0x8f, 0x8e, + 0x3a, 0xcc, 0x45, 0xde, 0x87, 0xcc, 0x59, 0x36, 0xd1, 0x26, 0xa5, 0x5b, 0x8e, 0xd7, 0x61, 0x55, + 0x4e, 0xee, 0x43, 0x2f, 0xd6, 0x0a, 0x79, 0x8c, 0x17, 0x22, 0xe5, 0x72, 0x4e, 0x7b, 0x81, 0xb0, + 0x55, 0x80, 0x2f, 0x3d, 0x46, 0x0e, 0xa1, 0x3b, 0x16, 0x36, 0x36, 0x32, 0xf3, 0xca, 0xe9, 0x76, + 0xa0, 0xac, 0x43, 0xe4, 0x01, 0x44, 0x49, 0x66, 0xe9, 0xed, 0x30, 0xf3, 0xde, 0xa0, 0x7c, 0xb0, + 0x6b, 0x6f, 0x8b, 0x79, 0x82, 0x77, 0xcd, 0x20, 0xd2, 0x7b, 0x61, 0x0c, 0x1f, 0x92, 0x5d, 0x68, + 0x9b, 0xe5, 0x85, 0x54, 0xf4, 0x61, 0xc0, 0x5a, 0x66, 0xf9, 0x46, 0x15, 0xa0, 0x9e, 0xd1, 0x47, + 0x25, 0xf8, 0x6e, 0xe6, 0x41, 0x0c, 0xcc, 0xc7, 0x2b, 0x10, 0x0b, 0x26, 0x06, 0xe6, 0x93, 0x12, + 0x74, 0xcc, 0xe3, 0xda, 0x85, 0x93, 0xa0, 0xe8, 0x4e, 0xe5, 0xc2, 0x6f, 0x77, 0xfb, 0x67, 0x0f, + 0x4e, 0x77, 0xbe, 0xfe, 0x38, 0x68, 0x7c, 0x73, 0xdf, 0x77, 0xf7, 0x7d, 0xf9, 0x79, 0xb0, 0x71, + 0x79, 0x23, 0xfc, 0x2d, 0x27, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x72, 0x60, 0x00, 0xb6, + 0x03, 0x00, 0x00, +} diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto new file mode 100644 index 000000000..4c827d201 --- /dev/null +++ b/api/gateway/gateway.proto @@ -0,0 +1,54 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto"; + +package gateway; + +message GPSMetadata { + int64 time = 1; + float latitude = 2; + float longitude = 3; + int32 altitude = 4; +} + +message RxMetadata { + uint64 frequency = 1; // frequency in Hz + float rssi = 2; // received signal strength in dBm + float snr = 3; // signal-to-noise-ratio in dB + oneof gateway { + semtech.RxMetadata semtech = 11; + } +} + +message TxConfiguration { + uint64 frequency = 1; // frequency in Hz + int32 power = 2; // transmit power in dBm + oneof gateway { + semtech.TxConfiguration semtech = 11; + } +} + +// message StatusMessage represents a status update from a Gateway. +// See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat +message StatusMessage { + uint32 timestamp = 1; + int64 time = 2; + + repeated string ip = 11; + string platform = 12; + string contact_email = 13; + string description = 14; + + GPSMetadata gps = 21; + + uint32 rtt = 31; + + uint32 rx_in = 41; + uint32 rx_ok = 42; + uint32 tx_in = 43; + uint32 tx_ok = 44; + + oneof gateway { + semtech.StatusMessage semtech = 51; + } +} diff --git a/api/gateway/semtech/semtech.pb.go b/api/gateway/semtech/semtech.pb.go new file mode 100644 index 000000000..66edca998 --- /dev/null +++ b/api/gateway/semtech/semtech.pb.go @@ -0,0 +1,686 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto +// DO NOT EDIT! + +/* + Package semtech is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto + + It has these top-level messages: + RxMetadata + TxConfiguration + StatusMessage +*/ +package semtech + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type RxMetadata struct { + Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RfChain uint32 `protobuf:"varint,11,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Channel uint32 `protobuf:"varint,12,opt,name=channel,proto3" json:"channel,omitempty"` +} + +func (m *RxMetadata) Reset() { *m = RxMetadata{} } +func (m *RxMetadata) String() string { return proto.CompactTextString(m) } +func (*RxMetadata) ProtoMessage() {} +func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{0} } + +type TxConfiguration struct { + Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RfChain uint32 `protobuf:"varint,11,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + PolarizationInversion bool `protobuf:"varint,21,opt,name=polarization_inversion,json=polarizationInversion,proto3" json:"polarization_inversion,omitempty"` + FrequencyDeviation uint32 `protobuf:"varint,22,opt,name=frequency_deviation,json=frequencyDeviation,proto3" json:"frequency_deviation,omitempty"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{1} } + +type StatusMessage struct { + Region lorawan.Region `protobuf:"varint,13,opt,name=region,proto3,enum=lorawan.Region" json:"region,omitempty"` +} + +func (m *StatusMessage) Reset() { *m = StatusMessage{} } +func (m *StatusMessage) String() string { return proto.CompactTextString(m) } +func (*StatusMessage) ProtoMessage() {} +func (*StatusMessage) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{2} } + +func init() { + proto.RegisterType((*RxMetadata)(nil), "semtech.RxMetadata") + proto.RegisterType((*TxConfiguration)(nil), "semtech.TxConfiguration") + proto.RegisterType((*StatusMessage)(nil), "semtech.StatusMessage") +} +func (m *RxMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RxMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Timestamp != 0 { + data[i] = 0x8 + i++ + i = encodeVarintSemtech(data, i, uint64(m.Timestamp)) + } + if m.RfChain != 0 { + data[i] = 0x58 + i++ + i = encodeVarintSemtech(data, i, uint64(m.RfChain)) + } + if m.Channel != 0 { + data[i] = 0x60 + i++ + i = encodeVarintSemtech(data, i, uint64(m.Channel)) + } + return i, nil +} + +func (m *TxConfiguration) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Timestamp != 0 { + data[i] = 0x8 + i++ + i = encodeVarintSemtech(data, i, uint64(m.Timestamp)) + } + if m.RfChain != 0 { + data[i] = 0x58 + i++ + i = encodeVarintSemtech(data, i, uint64(m.RfChain)) + } + if m.PolarizationInversion { + data[i] = 0xa8 + i++ + data[i] = 0x1 + i++ + if m.PolarizationInversion { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.FrequencyDeviation != 0 { + data[i] = 0xb0 + i++ + data[i] = 0x1 + i++ + i = encodeVarintSemtech(data, i, uint64(m.FrequencyDeviation)) + } + return i, nil +} + +func (m *StatusMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Region != 0 { + data[i] = 0x68 + i++ + i = encodeVarintSemtech(data, i, uint64(m.Region)) + } + return i, nil +} + +func encodeFixed64Semtech(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Semtech(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintSemtech(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *RxMetadata) Size() (n int) { + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovSemtech(uint64(m.Timestamp)) + } + if m.RfChain != 0 { + n += 1 + sovSemtech(uint64(m.RfChain)) + } + if m.Channel != 0 { + n += 1 + sovSemtech(uint64(m.Channel)) + } + return n +} + +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Timestamp != 0 { + n += 1 + sovSemtech(uint64(m.Timestamp)) + } + if m.RfChain != 0 { + n += 1 + sovSemtech(uint64(m.RfChain)) + } + if m.PolarizationInversion { + n += 3 + } + if m.FrequencyDeviation != 0 { + n += 2 + sovSemtech(uint64(m.FrequencyDeviation)) + } + return n +} + +func (m *StatusMessage) Size() (n int) { + var l int + _ = l + if m.Region != 0 { + n += 1 + sovSemtech(uint64(m.Region)) + } + return n +} + +func sovSemtech(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozSemtech(x uint64) (n int) { + return sovSemtech(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RxMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RxMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RxMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) + } + m.RfChain = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RfChain |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + m.Channel = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Channel |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSemtech(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthSemtech + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) + } + m.RfChain = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RfChain |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PolarizationInversion", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.PolarizationInversion = bool(v != 0) + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FrequencyDeviation", wireType) + } + m.FrequencyDeviation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FrequencyDeviation |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSemtech(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthSemtech + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Region", wireType) + } + m.Region = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowSemtech + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Region |= (lorawan.Region(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipSemtech(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthSemtech + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipSemtech(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSemtech + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSemtech + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSemtech + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthSemtech + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowSemtech + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipSemtech(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthSemtech = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowSemtech = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorSemtech = []byte{ + // 318 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x90, 0x3d, 0x4e, 0xc3, 0x40, + 0x10, 0x85, 0x71, 0x93, 0x84, 0x85, 0x10, 0x64, 0x94, 0xc8, 0x20, 0x14, 0xa1, 0x34, 0x50, 0x65, + 0x25, 0x10, 0x12, 0x2d, 0x09, 0x0d, 0x45, 0x28, 0x4c, 0x7a, 0x6b, 0xe2, 0x8c, 0xed, 0x15, 0xf6, + 0xae, 0xd9, 0x1d, 0xe7, 0x87, 0x93, 0x70, 0x0e, 0x4e, 0x41, 0xc9, 0x11, 0x10, 0x5c, 0x04, 0x67, + 0x13, 0x03, 0x25, 0xa2, 0x18, 0x8d, 0xdf, 0xfb, 0xfc, 0xde, 0x4a, 0xc3, 0xae, 0x63, 0x41, 0x49, + 0x31, 0xe9, 0x87, 0x2a, 0xe3, 0xe3, 0x04, 0xc7, 0x89, 0x90, 0xb1, 0xb9, 0x43, 0x9a, 0x2b, 0xfd, + 0xc0, 0x89, 0x24, 0x87, 0x5c, 0xf0, 0x18, 0x08, 0xe7, 0xb0, 0xe4, 0x06, 0x33, 0xc2, 0x30, 0xa9, + 0x76, 0x3f, 0xd7, 0x8a, 0x94, 0x5b, 0xdf, 0xc8, 0xa3, 0xc1, 0x5f, 0xba, 0x6c, 0x26, 0x54, 0x29, + 0x4f, 0x95, 0x86, 0x39, 0xc8, 0x6a, 0xaf, 0xcb, 0x7a, 0x01, 0x63, 0xfe, 0x62, 0x84, 0x04, 0x53, + 0x20, 0x70, 0x8f, 0xd9, 0x36, 0x89, 0x0c, 0x0d, 0x41, 0x96, 0x7b, 0xce, 0x89, 0x73, 0xd6, 0xf4, + 0x7f, 0x0c, 0xf7, 0x90, 0x35, 0x74, 0x14, 0x84, 0x09, 0x08, 0xe9, 0xed, 0x58, 0x58, 0xd7, 0xd1, + 0x70, 0x25, 0x5d, 0x8f, 0xd5, 0x4b, 0x5f, 0x4a, 0x4c, 0xbd, 0xdd, 0x35, 0xd9, 0xc8, 0xde, 0x8b, + 0xc3, 0x5a, 0xe3, 0xc5, 0x50, 0xc9, 0x48, 0xc4, 0x85, 0x06, 0x12, 0x4a, 0xfe, 0xff, 0x99, 0x4b, + 0xd6, 0xc9, 0x55, 0x0a, 0x5a, 0x3c, 0xd9, 0xa2, 0x40, 0xc8, 0x19, 0x6a, 0x53, 0x7e, 0x79, 0xed, + 0xf2, 0xc7, 0x86, 0xdf, 0xfe, 0x4d, 0x6f, 0x2b, 0xe8, 0x72, 0x76, 0x10, 0x69, 0x7c, 0x2c, 0x50, + 0x86, 0xcb, 0x60, 0x8a, 0x33, 0x61, 0xb9, 0xd7, 0xb1, 0xe5, 0xee, 0x37, 0xba, 0xa9, 0x48, 0xef, + 0x8a, 0x35, 0xef, 0x09, 0xa8, 0x30, 0x23, 0x34, 0x06, 0x62, 0x74, 0x4f, 0x59, 0x4d, 0x63, 0xbc, + 0x0a, 0x35, 0xcb, 0xd0, 0xde, 0x79, 0xab, 0x5f, 0x9d, 0xd1, 0xb7, 0xb6, 0xbf, 0xc1, 0x83, 0xfd, + 0xd7, 0x8f, 0xae, 0xf3, 0x56, 0xce, 0x7b, 0x39, 0xcf, 0x9f, 0xdd, 0xad, 0x49, 0xcd, 0x1e, 0xfa, + 0xe2, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x2e, 0xca, 0xef, 0x4e, 0xfa, 0x01, 0x00, 0x00, +} diff --git a/api/gateway/semtech/semtech.proto b/api/gateway/semtech/semtech.proto new file mode 100644 index 000000000..93acbd83d --- /dev/null +++ b/api/gateway/semtech/semtech.proto @@ -0,0 +1,22 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto"; + +package semtech; + +message RxMetadata { + uint32 timestamp = 1; + uint32 rf_chain = 11; + uint32 channel = 12; +} + +message TxConfiguration { + uint32 timestamp = 1; + uint32 rf_chain = 11; + bool polarization_inversion = 21; // LoRa polarization inversion (basically always true) + uint32 frequency_deviation = 22; // FSK frequency deviation in Hz +} + +message StatusMessage { + lorawan.Region region = 13; +} diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go new file mode 100644 index 000000000..cac1ac300 --- /dev/null +++ b/api/handler/handler.pb.go @@ -0,0 +1,490 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/handler/handler.proto +// DO NOT EDIT! + +/* + Package handler is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/handler/handler.proto + + It has these top-level messages: + StatusRequest + StatusResponse +*/ +package handler + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/TheThingsNetwork/ttn/api" +import broker "github.com/TheThingsNetwork/ttn/api/broker" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +// message StatusRequest is used to request the status of this Handler +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } + +// message StatusResponse is the response to the StatusRequest +type StatusResponse struct { +} + +func (m *StatusResponse) Reset() { *m = StatusResponse{} } +func (m *StatusResponse) String() string { return proto.CompactTextString(m) } +func (*StatusResponse) ProtoMessage() {} +func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } + +func init() { + proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") + proto.RegisterType((*StatusResponse)(nil), "handler.StatusResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Handler service + +type HandlerClient interface { + Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) +} + +type handlerClient struct { + cc *grpc.ClientConn +} + +func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { + return &handlerClient{cc} +} + +func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) { + out := new(broker.DeviceActivationResponse) + err := grpc.Invoke(ctx, "/handler.Handler/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Handler service + +type HandlerServer interface { + Activate(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*broker.DeviceActivationResponse, error) +} + +func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { + s.RegisterService(&_Handler_serviceDesc, srv) +} + +func _Handler_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(broker.DeduplicatedDeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerServer).Activate(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Handler_serviceDesc = grpc.ServiceDesc{ + ServiceName: "handler.Handler", + HandlerType: (*HandlerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Activate", + Handler: _Handler_Activate_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +// Client API for HandlerManager service + +type HandlerManagerClient interface { + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) +} + +type handlerManagerClient struct { + cc *grpc.ClientConn +} + +func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { + return &handlerManagerClient{cc} +} + +func (c *handlerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := grpc.Invoke(ctx, "/handler.HandlerManager/Status", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for HandlerManager service + +type HandlerManagerServer interface { + Status(context.Context, *StatusRequest) (*StatusResponse, error) +} + +func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { + s.RegisterService(&_HandlerManager_serviceDesc, srv) +} + +func _HandlerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(HandlerManagerServer).Status(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _HandlerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "handler.HandlerManager", + HandlerType: (*HandlerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Status", + Handler: _HandlerManager_Status_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *StatusRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *StatusResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func encodeFixed64Handler(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Handler(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintHandler(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *StatusResponse) Size() (n int) { + var l int + _ = l + return n +} + +func sovHandler(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozHandler(x uint64) (n int) { + return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *StatusRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipHandler(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthHandler + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowHandler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipHandler(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthHandler = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowHandler = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorHandler = []byte{ + // 240 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4c, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, + 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, + 0xcf, 0x48, 0xcc, 0x4b, 0xc9, 0x49, 0x2d, 0x82, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, + 0xec, 0x50, 0xae, 0x94, 0x2e, 0x31, 0x66, 0x00, 0x31, 0x44, 0x9f, 0x94, 0x39, 0x31, 0xca, 0x93, + 0x8a, 0xf2, 0xb3, 0x81, 0x36, 0x42, 0x28, 0x88, 0x46, 0x25, 0x7e, 0x2e, 0xde, 0xe0, 0x92, 0xc4, + 0x92, 0xd2, 0xe2, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x01, 0x2e, 0x3e, 0x98, 0x40, + 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x51, 0x0a, 0x17, 0xbb, 0x07, 0xc4, 0x55, 0x42, 0x91, 0x5c, + 0x1c, 0x8e, 0xc9, 0x25, 0x99, 0x65, 0x89, 0x25, 0xa9, 0x42, 0xda, 0x7a, 0x50, 0x83, 0x5c, 0x52, + 0x53, 0x4a, 0x0b, 0x72, 0x32, 0x93, 0x81, 0x82, 0x29, 0x2e, 0xa9, 0x65, 0x99, 0xc9, 0xa9, 0x50, + 0x35, 0x99, 0xf9, 0x79, 0x50, 0x53, 0xa5, 0x14, 0x10, 0x8a, 0xd1, 0x15, 0x40, 0x6d, 0xf1, 0xe6, + 0xe2, 0x83, 0xda, 0xe2, 0x9b, 0x98, 0x97, 0x98, 0x0e, 0xb4, 0xcc, 0x92, 0x8b, 0x0d, 0xe2, 0x12, + 0x21, 0x31, 0x3d, 0x58, 0x28, 0xa1, 0xb8, 0x55, 0x4a, 0x1c, 0x43, 0x1c, 0x62, 0x98, 0x93, 0xc0, + 0x89, 0x47, 0x72, 0x8c, 0x17, 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, + 0xf6, 0xae, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xdc, 0xb9, 0xd2, 0x6f, 0x9c, 0x01, 0x00, 0x00, +} diff --git a/api/handler/handler.proto b/api/handler/handler.proto new file mode 100644 index 000000000..f1e2d3d43 --- /dev/null +++ b/api/handler/handler.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; + +package handler; + +// The Handler service provides pure network functionality +service Handler { + rpc Activate(broker.DeduplicatedDeviceActivationRequest) returns (broker.DeviceActivationResponse); +} + +// message StatusRequest is used to request the status of this Handler +message StatusRequest {} + +// message StatusResponse is the response to the StatusRequest +message StatusResponse {} + +// The HandlerManager service provides configuration and monitoring +// functionality +service HandlerManager { + rpc Status(StatusRequest) returns (StatusResponse); +} diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go new file mode 100644 index 000000000..3c48756e9 --- /dev/null +++ b/api/networkserver/networkserver.pb.go @@ -0,0 +1,1393 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto +// DO NOT EDIT! + +/* + Package networkserver is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto + + It has these top-level messages: + DevicesRequest + DevicesResponse + UplinkResponse + RegisterDeviceRequest + StatusRequest + StatusResponse +*/ +package networkserver + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import api "github.com/TheThingsNetwork/ttn/api" +import broker "github.com/TheThingsNetwork/ttn/api/broker" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type DevicesRequest struct { + DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` + FCnt uint32 `protobuf:"varint,2,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` +} + +func (m *DevicesRequest) Reset() { *m = DevicesRequest{} } +func (m *DevicesRequest) String() string { return proto.CompactTextString(m) } +func (*DevicesRequest) ProtoMessage() {} +func (*DevicesRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{0} } + +type DevicesResponse struct { + Results []*DevicesResponse_Device `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` +} + +func (m *DevicesResponse) Reset() { *m = DevicesResponse{} } +func (m *DevicesResponse) String() string { return proto.CompactTextString(m) } +func (*DevicesResponse) ProtoMessage() {} +func (*DevicesResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1} } + +func (m *DevicesResponse) GetResults() []*DevicesResponse_Device { + if m != nil { + return m.Results + } + return nil +} + +type DevicesResponse_Device struct { + AppEui []byte `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` + DevEui []byte `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` + NwkSKey []byte `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3" json:"nwk_s_key,omitempty"` +} + +func (m *DevicesResponse_Device) Reset() { *m = DevicesResponse_Device{} } +func (m *DevicesResponse_Device) String() string { return proto.CompactTextString(m) } +func (*DevicesResponse_Device) ProtoMessage() {} +func (*DevicesResponse_Device) Descriptor() ([]byte, []int) { + return fileDescriptorNetworkserver, []int{1, 0} +} + +type UplinkResponse struct { + NeedDownlink bool `protobuf:"varint,1,opt,name=need_downlink,json=needDownlink,proto3" json:"need_downlink,omitempty"` +} + +func (m *UplinkResponse) Reset() { *m = UplinkResponse{} } +func (m *UplinkResponse) String() string { return proto.CompactTextString(m) } +func (*UplinkResponse) ProtoMessage() {} +func (*UplinkResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{2} } + +// message RegisterDeviceRequest is used to register a device in the +// NetworkServer +type RegisterDeviceRequest struct { +} + +func (m *RegisterDeviceRequest) Reset() { *m = RegisterDeviceRequest{} } +func (m *RegisterDeviceRequest) String() string { return proto.CompactTextString(m) } +func (*RegisterDeviceRequest) ProtoMessage() {} +func (*RegisterDeviceRequest) Descriptor() ([]byte, []int) { + return fileDescriptorNetworkserver, []int{3} +} + +// message StatusRequest is used to request the status of this NetworkServer +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } + +// message StatusResponse is the response to the StatusRequest +type StatusResponse struct { + // GetDevices histogram percentiles + DevicesPerAddress *api.Percentiles `protobuf:"bytes,1,opt,name=devices_per_address,json=devicesPerAddress" json:"devices_per_address,omitempty"` +} + +func (m *StatusResponse) Reset() { *m = StatusResponse{} } +func (m *StatusResponse) String() string { return proto.CompactTextString(m) } +func (*StatusResponse) ProtoMessage() {} +func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{5} } + +func (m *StatusResponse) GetDevicesPerAddress() *api.Percentiles { + if m != nil { + return m.DevicesPerAddress + } + return nil +} + +func init() { + proto.RegisterType((*DevicesRequest)(nil), "networkserver.DevicesRequest") + proto.RegisterType((*DevicesResponse)(nil), "networkserver.DevicesResponse") + proto.RegisterType((*DevicesResponse_Device)(nil), "networkserver.DevicesResponse.Device") + proto.RegisterType((*UplinkResponse)(nil), "networkserver.UplinkResponse") + proto.RegisterType((*RegisterDeviceRequest)(nil), "networkserver.RegisterDeviceRequest") + proto.RegisterType((*StatusRequest)(nil), "networkserver.StatusRequest") + proto.RegisterType((*StatusResponse)(nil), "networkserver.StatusResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for NetworkServer service + +type NetworkServerClient interface { + // Broker requests devices with DevAddr for MIC check + GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) + // Broker requests device activation "template" from Network Server + Activate(ctx context.Context, in *broker.DeviceActivationResponse, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) + // Broker informs Network Server about Uplink + Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*UplinkResponse, error) + // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... + Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) +} + +type networkServerClient struct { + cc *grpc.ClientConn +} + +func NewNetworkServerClient(cc *grpc.ClientConn) NetworkServerClient { + return &networkServerClient{cc} +} + +func (c *networkServerClient) GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) { + out := new(DevicesResponse) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/GetDevices", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Activate(ctx context.Context, in *broker.DeviceActivationResponse, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) { + out := new(broker.DeviceActivationResponse) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*UplinkResponse, error) { + out := new(UplinkResponse) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Uplink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) { + out := new(broker.DownlinkMessage) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Downlink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for NetworkServer service + +type NetworkServerServer interface { + // Broker requests devices with DevAddr for MIC check + GetDevices(context.Context, *DevicesRequest) (*DevicesResponse, error) + // Broker requests device activation "template" from Network Server + Activate(context.Context, *broker.DeviceActivationResponse) (*broker.DeviceActivationResponse, error) + // Broker informs Network Server about Uplink + Uplink(context.Context, *broker.DeduplicatedUplinkMessage) (*UplinkResponse, error) + // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... + Downlink(context.Context, *broker.DownlinkMessage) (*broker.DownlinkMessage, error) +} + +func RegisterNetworkServerServer(s *grpc.Server, srv NetworkServerServer) { + s.RegisterService(&_NetworkServer_serviceDesc, srv) +} + +func _NetworkServer_GetDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DevicesRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(NetworkServerServer).GetDevices(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _NetworkServer_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(broker.DeviceActivationResponse) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(NetworkServerServer).Activate(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _NetworkServer_Uplink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(broker.DeduplicatedUplinkMessage) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(NetworkServerServer).Uplink(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _NetworkServer_Downlink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(broker.DownlinkMessage) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(NetworkServerServer).Downlink(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _NetworkServer_serviceDesc = grpc.ServiceDesc{ + ServiceName: "networkserver.NetworkServer", + HandlerType: (*NetworkServerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetDevices", + Handler: _NetworkServer_GetDevices_Handler, + }, + { + MethodName: "Activate", + Handler: _NetworkServer_Activate_Handler, + }, + { + MethodName: "Uplink", + Handler: _NetworkServer_Uplink_Handler, + }, + { + MethodName: "Downlink", + Handler: _NetworkServer_Downlink_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +// Client API for NetworkServerManager service + +type NetworkServerManagerClient interface { + RegisterDevice(ctx context.Context, in *RegisterDeviceRequest, opts ...grpc.CallOption) (*api.Ack, error) + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) +} + +type networkServerManagerClient struct { + cc *grpc.ClientConn +} + +func NewNetworkServerManagerClient(cc *grpc.ClientConn) NetworkServerManagerClient { + return &networkServerManagerClient{cc} +} + +func (c *networkServerManagerClient) RegisterDevice(ctx context.Context, in *RegisterDeviceRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/RegisterDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/Status", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for NetworkServerManager service + +type NetworkServerManagerServer interface { + RegisterDevice(context.Context, *RegisterDeviceRequest) (*api.Ack, error) + Status(context.Context, *StatusRequest) (*StatusResponse, error) +} + +func RegisterNetworkServerManagerServer(s *grpc.Server, srv NetworkServerManagerServer) { + s.RegisterService(&_NetworkServerManager_serviceDesc, srv) +} + +func _NetworkServerManager_RegisterDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(RegisterDeviceRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(NetworkServerManagerServer).RegisterDevice(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _NetworkServerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(NetworkServerManagerServer).Status(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "networkserver.NetworkServerManager", + HandlerType: (*NetworkServerManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterDevice", + Handler: _NetworkServerManager_RegisterDevice_Handler, + }, + { + MethodName: "Status", + Handler: _NetworkServerManager_Status_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DevicesRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DevicesRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintNetworkserver(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + if m.FCnt != 0 { + data[i] = 0x10 + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.FCnt)) + } + return i, nil +} + +func (m *DevicesResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DevicesResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Results) > 0 { + for _, msg := range m.Results { + data[i] = 0xa + i++ + i = encodeVarintNetworkserver(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *DevicesResponse_Device) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppEui) > 0 { + data[i] = 0xa + i++ + i = encodeVarintNetworkserver(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + if len(m.DevEui) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintNetworkserver(data, i, uint64(len(m.DevEui))) + i += copy(data[i:], m.DevEui) + } + if len(m.NwkSKey) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintNetworkserver(data, i, uint64(len(m.NwkSKey))) + i += copy(data[i:], m.NwkSKey) + } + return i, nil +} + +func (m *UplinkResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UplinkResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.NeedDownlink { + data[i] = 0x8 + i++ + if m.NeedDownlink { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + return i, nil +} + +func (m *RegisterDeviceRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RegisterDeviceRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *StatusRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *StatusResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevicesPerAddress != nil { + data[i] = 0xa + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.DevicesPerAddress.Size())) + n1, err := m.DevicesPerAddress.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + return i, nil +} + +func encodeFixed64Networkserver(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Networkserver(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintNetworkserver(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DevicesRequest) Size() (n int) { + var l int + _ = l + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovNetworkserver(uint64(m.FCnt)) + } + return n +} + +func (m *DevicesResponse) Size() (n int) { + var l int + _ = l + if len(m.Results) > 0 { + for _, e := range m.Results { + l = e.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + } + return n +} + +func (m *DevicesResponse_Device) Size() (n int) { + var l int + _ = l + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovNetworkserver(uint64(l)) + } + l = len(m.DevEui) + if l > 0 { + n += 1 + l + sovNetworkserver(uint64(l)) + } + l = len(m.NwkSKey) + if l > 0 { + n += 1 + l + sovNetworkserver(uint64(l)) + } + return n +} + +func (m *UplinkResponse) Size() (n int) { + var l int + _ = l + if m.NeedDownlink { + n += 2 + } + return n +} + +func (m *RegisterDeviceRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *StatusResponse) Size() (n int) { + var l int + _ = l + if m.DevicesPerAddress != nil { + l = m.DevicesPerAddress.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + return n +} + +func sovNetworkserver(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozNetworkserver(x uint64) (n int) { + return sovNetworkserver(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DevicesRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevicesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevicesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevicesResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevicesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevicesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Results = append(m.Results, &DevicesResponse_Device{}) + if err := m.Results[len(m.Results)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevicesResponse_Device) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) + if m.AppEui == nil { + m.AppEui = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) + if m.DevEui == nil { + m.DevEui = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) + if m.NwkSKey == nil { + m.NwkSKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UplinkResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UplinkResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UplinkResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NeedDownlink", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.NeedDownlink = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RegisterDeviceRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RegisterDeviceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RegisterDeviceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevicesPerAddress", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DevicesPerAddress == nil { + m.DevicesPerAddress = &api.Percentiles{} + } + if err := m.DevicesPerAddress.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipNetworkserver(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthNetworkserver + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipNetworkserver(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthNetworkserver = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowNetworkserver = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorNetworkserver = []byte{ + // 533 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x6e, 0xd3, 0x40, + 0x14, 0x26, 0x29, 0x24, 0xe1, 0xb5, 0x49, 0xcb, 0x14, 0x94, 0x60, 0x41, 0x54, 0x0c, 0x48, 0xdd, + 0xe0, 0x48, 0x41, 0x88, 0x0d, 0x12, 0x0d, 0x34, 0x42, 0xa2, 0x6a, 0x85, 0x9c, 0xc2, 0xd6, 0x72, + 0xec, 0xd7, 0x64, 0xe4, 0x30, 0x36, 0x33, 0xe3, 0x44, 0xbd, 0x09, 0x3b, 0xf6, 0x1c, 0x80, 0x33, + 0xb0, 0xe4, 0x08, 0x08, 0x2e, 0xc2, 0xd8, 0x33, 0x0e, 0xb2, 0xa1, 0xd0, 0xc5, 0xc8, 0x7a, 0xdf, + 0xfb, 0xff, 0xbe, 0x67, 0x18, 0xcf, 0xa8, 0x9c, 0xa7, 0x53, 0x27, 0x88, 0xdf, 0x0f, 0x4e, 0xe7, + 0x78, 0x3a, 0xa7, 0x6c, 0x26, 0x4e, 0x50, 0xae, 0x62, 0x1e, 0x0d, 0xa4, 0x64, 0x03, 0x3f, 0xa1, + 0x03, 0xa6, 0x6d, 0x81, 0x7c, 0x89, 0xbc, 0x6c, 0x39, 0x09, 0x8f, 0x65, 0x4c, 0xda, 0x25, 0xd0, + 0x7a, 0x74, 0x99, 0xaa, 0xea, 0xe9, 0x6c, 0xeb, 0xe9, 0x65, 0xc2, 0xa7, 0x3c, 0x8e, 0x54, 0x77, + 0xfd, 0xd1, 0x89, 0xf6, 0x01, 0x74, 0x0e, 0x71, 0x49, 0x03, 0x14, 0x2e, 0x7e, 0x48, 0x51, 0x48, + 0x72, 0x1b, 0x5a, 0x21, 0x2e, 0x3d, 0x3f, 0x0c, 0x79, 0xaf, 0xb6, 0x57, 0xdb, 0xdf, 0x72, 0x9b, + 0xca, 0x1e, 0x29, 0x93, 0xec, 0xc2, 0xb5, 0x33, 0x2f, 0x60, 0xb2, 0x57, 0x57, 0x78, 0xdb, 0xbd, + 0x7a, 0xf6, 0x92, 0x49, 0xfb, 0x73, 0x0d, 0xb6, 0xd7, 0x25, 0x44, 0x12, 0x33, 0x81, 0xe4, 0x39, + 0x34, 0x39, 0x8a, 0x74, 0x21, 0x85, 0x2a, 0xb1, 0xb1, 0xbf, 0x39, 0x7c, 0xe8, 0x94, 0x77, 0xae, + 0x24, 0x18, 0xdb, 0x2d, 0xb2, 0xac, 0x77, 0xd0, 0xd0, 0x10, 0xe9, 0x42, 0xd3, 0x4f, 0x12, 0x0f, + 0x53, 0x6a, 0xa6, 0x69, 0x28, 0x73, 0x9c, 0xd2, 0xcc, 0x91, 0xcd, 0x99, 0x39, 0xea, 0xda, 0xa1, + 0xcc, 0xcc, 0x61, 0xc1, 0x75, 0xb6, 0x8a, 0x3c, 0xe1, 0x45, 0x78, 0xde, 0xdb, 0xd0, 0x1b, 0x28, + 0x60, 0x72, 0x84, 0xe7, 0xf6, 0x13, 0xe8, 0xbc, 0x4d, 0x16, 0x94, 0x45, 0xeb, 0x51, 0xef, 0x83, + 0x62, 0x1e, 0x43, 0x2f, 0x8c, 0x57, 0x2c, 0x73, 0xe4, 0x5d, 0x5a, 0xee, 0x56, 0x06, 0x1e, 0x1a, + 0xcc, 0xee, 0xc2, 0x2d, 0x17, 0x67, 0x54, 0x48, 0xe4, 0x66, 0x52, 0x4d, 0x96, 0xbd, 0x0d, 0xed, + 0x89, 0xf4, 0x65, 0x5a, 0xb0, 0x67, 0xbb, 0xd0, 0x29, 0x00, 0xd3, 0xe0, 0x00, 0x76, 0x43, 0xbd, + 0xad, 0x97, 0x20, 0xcf, 0x79, 0x45, 0x21, 0xf2, 0x36, 0x9b, 0xc3, 0x1d, 0x27, 0xd3, 0xf0, 0x0d, + 0xf2, 0x00, 0x99, 0xa4, 0x0b, 0xc5, 0xc8, 0x0d, 0x13, 0xac, 0xb0, 0x91, 0x0e, 0x1d, 0x7e, 0xa9, + 0x43, 0xdb, 0x68, 0x39, 0xc9, 0xe9, 0x23, 0x47, 0x00, 0xaf, 0x50, 0x1a, 0x12, 0xc9, 0xdd, 0x8b, + 0xc8, 0xcd, 0x47, 0xb2, 0xfa, 0xff, 0xe6, 0x9e, 0x9c, 0x40, 0x6b, 0x14, 0x48, 0xba, 0xf4, 0x25, + 0x92, 0x3d, 0xc7, 0x5c, 0x87, 0x0e, 0x32, 0x38, 0x8d, 0x59, 0x11, 0x6d, 0xfd, 0x37, 0x82, 0xbc, + 0x86, 0x86, 0xe6, 0x98, 0xdc, 0xfb, 0x1d, 0x1b, 0xa6, 0x0a, 0x0a, 0x54, 0x8b, 0x50, 0xfb, 0x8e, + 0xd5, 0x62, 0xfe, 0x0c, 0xad, 0xea, 0xec, 0x15, 0x75, 0x9e, 0x41, 0xab, 0x10, 0x81, 0x74, 0xd7, + 0xd5, 0x0c, 0x52, 0xd4, 0xb8, 0xc8, 0x31, 0xfc, 0x54, 0x83, 0x9b, 0x25, 0xe2, 0x8e, 0x7d, 0xa6, + 0x70, 0xae, 0x34, 0xe9, 0x94, 0xf5, 0x24, 0x0f, 0x2a, 0x73, 0xfc, 0x55, 0x6e, 0xab, 0x95, 0xcb, + 0x35, 0x0a, 0x22, 0x32, 0x86, 0x86, 0xd6, 0x99, 0xdc, 0xa9, 0x64, 0x96, 0xee, 0xe1, 0x8f, 0xfd, + 0xca, 0xc7, 0xf1, 0x62, 0xe7, 0xeb, 0x8f, 0x7e, 0xed, 0x9b, 0x7a, 0xdf, 0xd5, 0xfb, 0xf8, 0xb3, + 0x7f, 0x65, 0xda, 0xc8, 0xff, 0xcb, 0xc7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x97, 0x8c, 0x25, + 0x20, 0x57, 0x04, 0x00, 0x00, +} diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto new file mode 100644 index 000000000..67ec6dc95 --- /dev/null +++ b/api/networkserver/networkserver.proto @@ -0,0 +1,60 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; + +package networkserver; + +message DevicesRequest { + bytes dev_addr = 1; + uint32 f_cnt = 2; +} + +message DevicesResponse { + message Device { + bytes app_eui = 1; + bytes dev_eui = 2; + bytes nwk_s_key = 3; + } + repeated Device results = 1; +} + +message UplinkResponse { + bool need_downlink = 1; +} + +service NetworkServer { + // Broker requests devices with DevAddr for MIC check + rpc GetDevices(DevicesRequest) returns (DevicesResponse); + + // Broker requests device activation "template" from Network Server + rpc Activate(broker.DeviceActivationResponse) returns (broker.DeviceActivationResponse); + + // Broker informs Network Server about Uplink + rpc Uplink(broker.DeduplicatedUplinkMessage) returns (UplinkResponse); + + // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... + rpc Downlink(broker.DownlinkMessage) returns (broker.DownlinkMessage); +} + +// message RegisterDeviceRequest is used to register a device in the +// NetworkServer +message RegisterDeviceRequest { + // TODO +} + +// message StatusRequest is used to request the status of this NetworkServer +message StatusRequest {} + +// message StatusResponse is the response to the StatusRequest +message StatusResponse { + // GetDevices histogram percentiles + api.Percentiles devices_per_address = 1; +} + +// The NetworkServerManager service provides configuration and monitoring +// functionality +service NetworkServerManager { + rpc RegisterDevice(RegisterDeviceRequest) returns (api.Ack); + rpc Status(StatusRequest) returns (StatusResponse); +} diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go new file mode 100644 index 000000000..c3417da08 --- /dev/null +++ b/api/protocol/lorawan/lorawan.pb.go @@ -0,0 +1,1891 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto +// DO NOT EDIT! + +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto + + It has these top-level messages: + Metadata + TxConfiguration + ActivationMetadata + PHYPayload + MHdr + MACPayload + FHdr + FCtrl +*/ +package lorawan + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type Region int32 + +const ( + Region_EU863_870 Region = 0 + Region_US902_928 Region = 1 + Region_CN779_787 Region = 2 + Region_EU433 Region = 3 + Region_AU915_928 Region = 4 + Region_CN470_510 Region = 5 +) + +var Region_name = map[int32]string{ + 0: "EU863_870", + 1: "US902_928", + 2: "CN779_787", + 3: "EU433", + 4: "AU915_928", + 5: "CN470_510", +} +var Region_value = map[string]int32{ + "EU863_870": 0, + "US902_928": 1, + "CN779_787": 2, + "EU433": 3, + "AU915_928": 4, + "CN470_510": 5, +} + +func (x Region) String() string { + return proto.EnumName(Region_name, int32(x)) +} +func (Region) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } + +type Metadata_Modulation int32 + +const ( + Metadata_LORA Metadata_Modulation = 0 + Metadata_FSK Metadata_Modulation = 1 +) + +var Metadata_Modulation_name = map[int32]string{ + 0: "LORA", + 1: "FSK", +} +var Metadata_Modulation_value = map[string]int32{ + "LORA": 0, + "FSK": 1, +} + +func (x Metadata_Modulation) String() string { + return proto.EnumName(Metadata_Modulation_name, int32(x)) +} +func (Metadata_Modulation) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0, 0} } + +type Metadata struct { + Modulation Metadata_Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Metadata_Modulation" json:"modulation,omitempty"` + DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` + BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` + CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } + +type TxConfiguration struct { + DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } + +type ActivationMetadata struct { + DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` + NetworkSessionKey []byte `protobuf:"bytes,2,opt,name=network_session_key,json=networkSessionKey,proto3" json:"network_session_key,omitempty"` +} + +func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } +func (m *ActivationMetadata) String() string { return proto.CompactTextString(m) } +func (*ActivationMetadata) ProtoMessage() {} +func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } + +type PHYPayload struct { + Mhdr *MHdr `protobuf:"bytes,1,opt,name=mhdr" json:"mhdr,omitempty"` + MACPayload *MACPayload `protobuf:"bytes,2,opt,name=MAC_payload,json=mACPayload" json:"MAC_payload,omitempty"` + Mic []byte `protobuf:"bytes,3,opt,name=mic,proto3" json:"mic,omitempty"` +} + +func (m *PHYPayload) Reset() { *m = PHYPayload{} } +func (m *PHYPayload) String() string { return proto.CompactTextString(m) } +func (*PHYPayload) ProtoMessage() {} +func (*PHYPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } + +func (m *PHYPayload) GetMhdr() *MHdr { + if m != nil { + return m.Mhdr + } + return nil +} + +func (m *PHYPayload) GetMACPayload() *MACPayload { + if m != nil { + return m.MACPayload + } + return nil +} + +type MHdr struct { + MType uint32 `protobuf:"varint,1,opt,name=m_type,json=mType,proto3" json:"m_type,omitempty"` + Major uint32 `protobuf:"varint,2,opt,name=major,proto3" json:"major,omitempty"` +} + +func (m *MHdr) Reset() { *m = MHdr{} } +func (m *MHdr) String() string { return proto.CompactTextString(m) } +func (*MHdr) ProtoMessage() {} +func (*MHdr) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{4} } + +type MACPayload struct { + FHdr *FHdr `protobuf:"bytes,1,opt,name=f_hdr,json=fHdr" json:"f_hdr,omitempty"` + FPort uint32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"` + FRMPayload []byte `protobuf:"bytes,3,opt,name=FRM_payload,json=fRMPayload,proto3" json:"FRM_payload,omitempty"` +} + +func (m *MACPayload) Reset() { *m = MACPayload{} } +func (m *MACPayload) String() string { return proto.CompactTextString(m) } +func (*MACPayload) ProtoMessage() {} +func (*MACPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } + +func (m *MACPayload) GetFHdr() *FHdr { + if m != nil { + return m.FHdr + } + return nil +} + +type FHdr struct { + DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` + FCtrl *FCtrl `protobuf:"bytes,2,opt,name=f_ctrl,json=fCtrl" json:"f_ctrl,omitempty"` + FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + FOpts [][]byte `protobuf:"bytes,4,rep,name=f_opts,json=fOpts" json:"f_opts,omitempty"` +} + +func (m *FHdr) Reset() { *m = FHdr{} } +func (m *FHdr) String() string { return proto.CompactTextString(m) } +func (*FHdr) ProtoMessage() {} +func (*FHdr) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } + +func (m *FHdr) GetFCtrl() *FCtrl { + if m != nil { + return m.FCtrl + } + return nil +} + +type FCtrl struct { + Adr bool `protobuf:"varint,1,opt,name=adr,proto3" json:"adr,omitempty"` + AdrAckReq bool `protobuf:"varint,2,opt,name=adr_ack_req,json=adrAckReq,proto3" json:"adr_ack_req,omitempty"` + Ack bool `protobuf:"varint,3,opt,name=ack,proto3" json:"ack,omitempty"` + FPending bool `protobuf:"varint,4,opt,name=f_pending,json=fPending,proto3" json:"f_pending,omitempty"` + FOptsLen []byte `protobuf:"bytes,5,opt,name=f_opts_len,json=fOptsLen,proto3" json:"f_opts_len,omitempty"` +} + +func (m *FCtrl) Reset() { *m = FCtrl{} } +func (m *FCtrl) String() string { return proto.CompactTextString(m) } +func (*FCtrl) ProtoMessage() {} +func (*FCtrl) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{7} } + +func init() { + proto.RegisterType((*Metadata)(nil), "lorawan.Metadata") + proto.RegisterType((*TxConfiguration)(nil), "lorawan.TxConfiguration") + proto.RegisterType((*ActivationMetadata)(nil), "lorawan.ActivationMetadata") + proto.RegisterType((*PHYPayload)(nil), "lorawan.PHYPayload") + proto.RegisterType((*MHdr)(nil), "lorawan.MHdr") + proto.RegisterType((*MACPayload)(nil), "lorawan.MACPayload") + proto.RegisterType((*FHdr)(nil), "lorawan.FHdr") + proto.RegisterType((*FCtrl)(nil), "lorawan.FCtrl") + proto.RegisterEnum("lorawan.Region", Region_name, Region_value) + proto.RegisterEnum("lorawan.Metadata_Modulation", Metadata_Modulation_name, Metadata_Modulation_value) +} +func (m *Metadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Metadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Modulation != 0 { + data[i] = 0x58 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Modulation)) + } + if len(m.DataRate) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DataRate))) + i += copy(data[i:], m.DataRate) + } + if m.BitRate != 0 { + data[i] = 0x68 + i++ + i = encodeVarintLorawan(data, i, uint64(m.BitRate)) + } + if len(m.CodingRate) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.CodingRate))) + i += copy(data[i:], m.CodingRate) + } + return i, nil +} + +func (m *TxConfiguration) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + return i, nil +} + +func (m *ActivationMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + if len(m.NetworkSessionKey) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.NetworkSessionKey))) + i += copy(data[i:], m.NetworkSessionKey) + } + return i, nil +} + +func (m *PHYPayload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *PHYPayload) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Mhdr != nil { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.Mhdr.Size())) + n1, err := m.Mhdr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.MACPayload != nil { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) + n2, err := m.MACPayload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if len(m.Mic) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.Mic))) + i += copy(data[i:], m.Mic) + } + return i, nil +} + +func (m *MHdr) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *MHdr) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.MType != 0 { + data[i] = 0x8 + i++ + i = encodeVarintLorawan(data, i, uint64(m.MType)) + } + if m.Major != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Major)) + } + return i, nil +} + +func (m *MACPayload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *MACPayload) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.FHdr != nil { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.FHdr.Size())) + n3, err := m.FHdr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.FPort != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FPort)) + } + if len(m.FRMPayload) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FRMPayload))) + i += copy(data[i:], m.FRMPayload) + } + return i, nil +} + +func (m *FHdr) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *FHdr) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.DevAddr) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) + i += copy(data[i:], m.DevAddr) + } + if m.FCtrl != nil { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) + n4, err := m.FCtrl.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.FCnt != 0 { + data[i] = 0x18 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, b := range m.FOpts { + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(len(b))) + i += copy(data[i:], b) + } + } + return i, nil +} + +func (m *FCtrl) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *FCtrl) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Adr { + data[i] = 0x8 + i++ + if m.Adr { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.AdrAckReq { + data[i] = 0x10 + i++ + if m.AdrAckReq { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.Ack { + data[i] = 0x18 + i++ + if m.Ack { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.FPending { + data[i] = 0x20 + i++ + if m.FPending { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if len(m.FOptsLen) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FOptsLen))) + i += copy(data[i:], m.FOptsLen) + } + return i, nil +} + +func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Lorawan(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintLorawan(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *Metadata) Size() (n int) { + var l int + _ = l + if m.Modulation != 0 { + n += 1 + sovLorawan(uint64(m.Modulation)) + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.BitRate != 0 { + n += 1 + sovLorawan(uint64(m.BitRate)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *ActivationMetadata) Size() (n int) { + var l int + _ = l + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + l = len(m.NetworkSessionKey) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *PHYPayload) Size() (n int) { + var l int + _ = l + if m.Mhdr != nil { + l = m.Mhdr.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.MACPayload != nil { + l = m.MACPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + l = len(m.Mic) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *MHdr) Size() (n int) { + var l int + _ = l + if m.MType != 0 { + n += 1 + sovLorawan(uint64(m.MType)) + } + if m.Major != 0 { + n += 1 + sovLorawan(uint64(m.Major)) + } + return n +} + +func (m *MACPayload) Size() (n int) { + var l int + _ = l + if m.FHdr != nil { + l = m.FHdr.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FPort != 0 { + n += 1 + sovLorawan(uint64(m.FPort)) + } + l = len(m.FRMPayload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *FHdr) Size() (n int) { + var l int + _ = l + l = len(m.DevAddr) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCtrl != nil { + l = m.FCtrl.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, b := range m.FOpts { + l = len(b) + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *FCtrl) Size() (n int) { + var l int + _ = l + if m.Adr { + n += 2 + } + if m.AdrAckReq { + n += 2 + } + if m.Ack { + n += 2 + } + if m.FPending { + n += 2 + } + l = len(m.FOptsLen) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func sovLorawan(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozLorawan(x uint64) (n int) { + return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Metadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + m.Modulation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Modulation |= (Metadata_Modulation(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + } + m.BitRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.BitRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetworkSessionKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetworkSessionKey = append(m.NetworkSessionKey[:0], data[iNdEx:postIndex]...) + if m.NetworkSessionKey == nil { + m.NetworkSessionKey = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PHYPayload) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PHYPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PHYPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Mhdr", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Mhdr == nil { + m.Mhdr = &MHdr{} + } + if err := m.Mhdr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MACPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.MACPayload == nil { + m.MACPayload = &MACPayload{} + } + if err := m.MACPayload.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Mic", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Mic = append(m.Mic[:0], data[iNdEx:postIndex]...) + if m.Mic == nil { + m.Mic = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MHdr) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MHdr: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MHdr: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) + } + m.MType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.MType |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) + } + m.Major = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Major |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MACPayload) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MACPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MACPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FHdr", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FHdr == nil { + m.FHdr = &FHdr{} + } + if err := m.FHdr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) + } + m.FPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FPort |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FRMPayload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FRMPayload = append(m.FRMPayload[:0], data[iNdEx:postIndex]...) + if m.FRMPayload == nil { + m.FRMPayload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FHdr) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FHdr: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FHdr: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) + if m.DevAddr == nil { + m.DevAddr = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FCtrl", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.FCtrl == nil { + m.FCtrl = &FCtrl{} + } + if err := m.FCtrl.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FOpts", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FOpts = append(m.FOpts, make([]byte, postIndex-iNdEx)) + copy(m.FOpts[len(m.FOpts)-1], data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FCtrl) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FCtrl: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FCtrl: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Adr", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Adr = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AdrAckReq", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.AdrAckReq = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Ack = bool(v != 0) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPending", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.FPending = bool(v != 0) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FOptsLen", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FOptsLen = append(m.FOptsLen[:0], data[iNdEx:postIndex]...) + if m.FOptsLen == nil { + m.FOptsLen = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipLorawan(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthLorawan + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowLorawan + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipLorawan(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthLorawan = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowLorawan = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorLorawan = []byte{ + // 661 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x53, 0xd1, 0x6e, 0x12, 0x41, + 0x14, 0x2d, 0x85, 0x2d, 0xcb, 0xa5, 0xd4, 0x75, 0xaa, 0x09, 0xc6, 0xa6, 0xad, 0x9b, 0x98, 0x18, + 0x63, 0x80, 0x42, 0x2b, 0x90, 0xf8, 0xb2, 0x25, 0x6d, 0x9a, 0xb4, 0xb4, 0x64, 0x0a, 0x0f, 0xbe, + 0x38, 0x99, 0xee, 0xce, 0xc2, 0x0a, 0xbb, 0x43, 0x97, 0x69, 0x2b, 0x3f, 0xe0, 0x37, 0xf8, 0x3d, + 0x3e, 0xf9, 0xe8, 0x27, 0x18, 0xfd, 0x11, 0x67, 0x66, 0x17, 0xf0, 0x41, 0x7d, 0xd8, 0xcc, 0x9c, + 0x7b, 0xcf, 0x3d, 0xe7, 0xde, 0xcb, 0x00, 0xc7, 0xc3, 0x40, 0x8c, 0xee, 0x6e, 0x2a, 0x2e, 0x0f, + 0xab, 0xfd, 0x11, 0xeb, 0x8f, 0x82, 0x68, 0x38, 0xbb, 0x64, 0xe2, 0x81, 0xc7, 0xe3, 0xaa, 0x10, + 0x51, 0x95, 0x4e, 0x83, 0xea, 0x34, 0xe6, 0x82, 0xbb, 0x7c, 0x52, 0x9d, 0xf0, 0x98, 0x3e, 0xd0, + 0x68, 0x71, 0x56, 0x74, 0x02, 0xe5, 0x53, 0x68, 0x7f, 0xcd, 0x80, 0xd9, 0x65, 0x82, 0x7a, 0x54, + 0x50, 0xf4, 0x0e, 0x20, 0xe4, 0xde, 0xdd, 0x84, 0x8a, 0x80, 0x47, 0xe5, 0xe2, 0x7e, 0xe6, 0xd5, + 0x56, 0x7d, 0xa7, 0xb2, 0xa8, 0x5c, 0xd0, 0x2a, 0xdd, 0x25, 0x07, 0xff, 0xc1, 0x47, 0xcf, 0xa1, + 0xa0, 0xd2, 0x24, 0xa6, 0x82, 0x95, 0x37, 0x65, 0x71, 0x01, 0x9b, 0x2a, 0x80, 0x25, 0x46, 0xcf, + 0xc0, 0xbc, 0x09, 0x44, 0x92, 0x2b, 0xc9, 0x5c, 0x09, 0xe7, 0x25, 0xd6, 0xa9, 0x3d, 0x28, 0xba, + 0xdc, 0x93, 0x43, 0x24, 0xd9, 0x2d, 0x5d, 0x09, 0x49, 0x48, 0x11, 0xec, 0x3d, 0x80, 0x95, 0x25, + 0x32, 0x21, 0x77, 0x71, 0x85, 0x1d, 0x6b, 0x0d, 0xe5, 0x21, 0x7b, 0x7a, 0x7d, 0x6e, 0x65, 0xec, + 0x37, 0xf0, 0xa8, 0xff, 0xa9, 0xc3, 0x23, 0x3f, 0x18, 0xde, 0xc5, 0x09, 0x4b, 0xfa, 0x79, 0xec, + 0x9e, 0x50, 0xcf, 0x8b, 0xcb, 0x19, 0xa9, 0xb8, 0x89, 0xf3, 0x12, 0x3b, 0x12, 0xda, 0x04, 0x90, + 0xe3, 0x8a, 0xe0, 0x5e, 0x13, 0x97, 0xb3, 0xff, 0xbb, 0x00, 0x55, 0x60, 0x3b, 0x4a, 0xd6, 0x4b, + 0x66, 0x6c, 0x36, 0x93, 0x55, 0x64, 0xcc, 0xe6, 0xe5, 0x75, 0xcd, 0x7a, 0x9c, 0xa6, 0xae, 0x93, + 0xcc, 0x39, 0x9b, 0xdb, 0x0f, 0x00, 0xbd, 0xb3, 0xf7, 0x3d, 0x3a, 0x9f, 0x70, 0xea, 0xa1, 0x17, + 0x90, 0x0b, 0x47, 0xa9, 0x68, 0xb1, 0x5e, 0x5a, 0xad, 0xf3, 0xcc, 0x8b, 0xb1, 0x4e, 0xa1, 0x43, + 0x28, 0x76, 0x9d, 0x0e, 0x99, 0x26, 0x15, 0x5a, 0xb8, 0x58, 0xdf, 0x5e, 0x31, 0x9d, 0x4e, 0x2a, + 0x26, 0xf7, 0xbd, 0xbc, 0x23, 0x0b, 0xb2, 0x61, 0xe0, 0x96, 0xb3, 0xba, 0x0d, 0x75, 0xb5, 0x1b, + 0x90, 0x53, 0xaa, 0xe8, 0x29, 0x6c, 0x84, 0x44, 0xcc, 0xa7, 0x4c, 0x9b, 0x96, 0xb0, 0x11, 0xf6, + 0x25, 0x40, 0x4f, 0xc0, 0x08, 0xe9, 0x47, 0x1e, 0x6b, 0x03, 0x15, 0x55, 0xc0, 0x1e, 0xc9, 0xed, + 0xae, 0x44, 0x6d, 0x30, 0x7c, 0xf2, 0xb7, 0x76, 0x4f, 0x75, 0xbb, 0x7e, 0x2a, 0xef, 0x93, 0x29, + 0x8f, 0xc5, 0x42, 0xc8, 0xef, 0x49, 0xa0, 0x7e, 0xc7, 0x53, 0xdc, 0x5d, 0x4e, 0x91, 0xf4, 0x05, + 0x3e, 0xee, 0xa6, 0xda, 0xb6, 0x80, 0x9c, 0x52, 0xf9, 0xdf, 0xaa, 0x5f, 0x2a, 0x69, 0x57, 0xc4, + 0x93, 0x74, 0x09, 0x5b, 0x2b, 0xff, 0x8e, 0x8c, 0x4a, 0x2b, 0x75, 0xa0, 0x6d, 0xd5, 0xa5, 0x1b, + 0x09, 0x6d, 0x52, 0x92, 0x6d, 0x75, 0x22, 0x91, 0xb4, 0xc5, 0xa7, 0x62, 0x56, 0xce, 0xed, 0x67, + 0xa5, 0xa8, 0xe1, 0x5f, 0x49, 0x60, 0x7f, 0xce, 0x80, 0xa1, 0x8b, 0xd5, 0xc2, 0x68, 0x6a, 0x69, + 0x62, 0x75, 0x45, 0xbb, 0x50, 0x94, 0x07, 0xa1, 0xee, 0x98, 0xc4, 0xec, 0x56, 0x7b, 0x9a, 0xb8, + 0x20, 0x43, 0x8e, 0x3b, 0xc6, 0xec, 0x56, 0x57, 0xb8, 0x63, 0xed, 0xa2, 0x2a, 0xdc, 0xb1, 0x7a, + 0xe4, 0x72, 0x76, 0x16, 0xa9, 0xc7, 0x29, 0x7d, 0x54, 0xdc, 0xf4, 0x7b, 0x09, 0x46, 0x3b, 0x00, + 0x49, 0x07, 0x64, 0xc2, 0xa2, 0xb2, 0xa1, 0x47, 0x33, 0x75, 0x17, 0x17, 0x2c, 0x7a, 0xfd, 0x01, + 0x36, 0x30, 0x1b, 0xaa, 0xc7, 0x59, 0x82, 0xc2, 0xc9, 0xa0, 0xf5, 0xb6, 0x41, 0x5a, 0xcd, 0x9a, + 0x7c, 0xc7, 0x12, 0x0e, 0xae, 0xdb, 0xb5, 0x3a, 0x69, 0xd7, 0x5b, 0x56, 0x46, 0xc1, 0xce, 0x65, + 0xb3, 0xd9, 0x26, 0xcd, 0x56, 0xd3, 0x5a, 0x47, 0x05, 0x30, 0x4e, 0x06, 0x87, 0x8d, 0x86, 0x95, + 0x55, 0x19, 0x67, 0xd0, 0x3e, 0x38, 0xd2, 0xc4, 0x5c, 0x42, 0x3c, 0x6c, 0xd6, 0xc8, 0xd1, 0x41, + 0xcd, 0x32, 0x8e, 0xad, 0x6f, 0x3f, 0x77, 0x33, 0xdf, 0xe5, 0xf7, 0x43, 0x7e, 0x5f, 0x7e, 0xed, + 0xae, 0xdd, 0x6c, 0xe8, 0x3f, 0x7b, 0xe3, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x39, 0xdb, 0x21, + 0xc6, 0x32, 0x04, 0x00, 0x00, +} diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto new file mode 100644 index 000000000..6333afc9d --- /dev/null +++ b/api/protocol/lorawan/lorawan.proto @@ -0,0 +1,64 @@ +syntax = "proto3"; + +package lorawan; + +message Metadata { + enum Modulation { + LORA = 0; + FSK = 1; + } + Modulation modulation = 11; + string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} + uint32 bit_rate = 13; // FSK bit rate in bit/s + string coding_rate = 14; // LoRa coding rate +} + +message TxConfiguration { + bytes dev_addr = 1; +} + +message ActivationMetadata { + bytes dev_addr = 1; + bytes network_session_key = 2; +} + +message PHYPayload { + MHdr mhdr = 1; + MACPayload MAC_payload = 2; + bytes mic = 3; +} + +message MHdr { + uint32 m_type = 1; + uint32 major = 2; +} + +message MACPayload { + FHdr f_hdr = 1; + uint32 f_port = 2; + bytes FRM_payload = 3; +} + +message FHdr { + bytes dev_addr = 1; + FCtrl f_ctrl = 2; + uint32 f_cnt = 3; + repeated bytes f_opts = 4; +} + +message FCtrl { + bool adr = 1; + bool adr_ack_req = 2; + bool ack = 3; + bool f_pending = 4; + bytes f_opts_len = 5; +} + +enum Region { + EU863_870 = 0; + US902_928 = 1; + CN779_787 = 2; + EU433 = 3; + AU915_928 = 4; + CN470_510 = 5; +} diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go new file mode 100644 index 000000000..012c1c75c --- /dev/null +++ b/api/protocol/protocol.pb.go @@ -0,0 +1,1171 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto +// DO NOT EDIT! + +/* + Package protocol is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto + + It has these top-level messages: + RxMetadata + TxConfiguration + ActivationMetadata + Payload +*/ +package protocol + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type RxMetadata struct { + // Types that are valid to be assigned to Protocol: + // *RxMetadata_Lorawan + Protocol isRxMetadata_Protocol `protobuf_oneof:"protocol"` +} + +func (m *RxMetadata) Reset() { *m = RxMetadata{} } +func (m *RxMetadata) String() string { return proto.CompactTextString(m) } +func (*RxMetadata) ProtoMessage() {} +func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{0} } + +type isRxMetadata_Protocol interface { + isRxMetadata_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type RxMetadata_Lorawan struct { + Lorawan *lorawan.Metadata `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*RxMetadata_Lorawan) isRxMetadata_Protocol() {} + +func (m *RxMetadata) GetProtocol() isRxMetadata_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *RxMetadata) GetLorawan() *lorawan.Metadata { + if x, ok := m.GetProtocol().(*RxMetadata_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*RxMetadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _RxMetadata_OneofMarshaler, _RxMetadata_OneofUnmarshaler, _RxMetadata_OneofSizer, []interface{}{ + (*RxMetadata_Lorawan)(nil), + } +} + +func _RxMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*RxMetadata) + // protocol + switch x := m.Protocol.(type) { + case *RxMetadata_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("RxMetadata.Protocol has unexpected type %T", x) + } + return nil +} + +func _RxMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*RxMetadata) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.Metadata) + err := b.DecodeMessage(msg) + m.Protocol = &RxMetadata_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _RxMetadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*RxMetadata) + // protocol + switch x := m.Protocol.(type) { + case *RxMetadata_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type TxConfiguration struct { + // Types that are valid to be assigned to Protocol: + // *TxConfiguration_Lorawan + Protocol isTxConfiguration_Protocol `protobuf_oneof:"protocol"` +} + +func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } +func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } +func (*TxConfiguration) ProtoMessage() {} +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{1} } + +type isTxConfiguration_Protocol interface { + isTxConfiguration_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type TxConfiguration_Lorawan struct { + Lorawan *lorawan.TxConfiguration `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*TxConfiguration_Lorawan) isTxConfiguration_Protocol() {} + +func (m *TxConfiguration) GetProtocol() isTxConfiguration_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *TxConfiguration) GetLorawan() *lorawan.TxConfiguration { + if x, ok := m.GetProtocol().(*TxConfiguration_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*TxConfiguration) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _TxConfiguration_OneofMarshaler, _TxConfiguration_OneofUnmarshaler, _TxConfiguration_OneofSizer, []interface{}{ + (*TxConfiguration_Lorawan)(nil), + } +} + +func _TxConfiguration_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*TxConfiguration) + // protocol + switch x := m.Protocol.(type) { + case *TxConfiguration_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("TxConfiguration.Protocol has unexpected type %T", x) + } + return nil +} + +func _TxConfiguration_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*TxConfiguration) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.TxConfiguration) + err := b.DecodeMessage(msg) + m.Protocol = &TxConfiguration_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _TxConfiguration_OneofSizer(msg proto.Message) (n int) { + m := msg.(*TxConfiguration) + // protocol + switch x := m.Protocol.(type) { + case *TxConfiguration_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type ActivationMetadata struct { + // Types that are valid to be assigned to Protocol: + // *ActivationMetadata_Lorawan + Protocol isActivationMetadata_Protocol `protobuf_oneof:"protocol"` +} + +func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } +func (m *ActivationMetadata) String() string { return proto.CompactTextString(m) } +func (*ActivationMetadata) ProtoMessage() {} +func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{2} } + +type isActivationMetadata_Protocol interface { + isActivationMetadata_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type ActivationMetadata_Lorawan struct { + Lorawan *lorawan.ActivationMetadata `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*ActivationMetadata_Lorawan) isActivationMetadata_Protocol() {} + +func (m *ActivationMetadata) GetProtocol() isActivationMetadata_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *ActivationMetadata) GetLorawan() *lorawan.ActivationMetadata { + if x, ok := m.GetProtocol().(*ActivationMetadata_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*ActivationMetadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _ActivationMetadata_OneofMarshaler, _ActivationMetadata_OneofUnmarshaler, _ActivationMetadata_OneofSizer, []interface{}{ + (*ActivationMetadata_Lorawan)(nil), + } +} + +func _ActivationMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*ActivationMetadata) + // protocol + switch x := m.Protocol.(type) { + case *ActivationMetadata_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("ActivationMetadata.Protocol has unexpected type %T", x) + } + return nil +} + +func _ActivationMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*ActivationMetadata) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.ActivationMetadata) + err := b.DecodeMessage(msg) + m.Protocol = &ActivationMetadata_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _ActivationMetadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*ActivationMetadata) + // protocol + switch x := m.Protocol.(type) { + case *ActivationMetadata_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type Payload struct { + Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` + // Types that are valid to be assigned to Protocol: + // *Payload_Lorawan + Protocol isPayload_Protocol `protobuf_oneof:"protocol"` +} + +func (m *Payload) Reset() { *m = Payload{} } +func (m *Payload) String() string { return proto.CompactTextString(m) } +func (*Payload) ProtoMessage() {} +func (*Payload) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{3} } + +type isPayload_Protocol interface { + isPayload_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type Payload_Lorawan struct { + Lorawan *lorawan.PHYPayload `protobuf:"bytes,2,opt,name=lorawan,oneof"` +} + +func (*Payload_Lorawan) isPayload_Protocol() {} + +func (m *Payload) GetProtocol() isPayload_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *Payload) GetLorawan() *lorawan.PHYPayload { + if x, ok := m.GetProtocol().(*Payload_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Payload) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Payload_OneofMarshaler, _Payload_OneofUnmarshaler, _Payload_OneofSizer, []interface{}{ + (*Payload_Lorawan)(nil), + } +} + +func _Payload_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Payload) + // protocol + switch x := m.Protocol.(type) { + case *Payload_Lorawan: + _ = b.EncodeVarint(2<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Payload.Protocol has unexpected type %T", x) + } + return nil +} + +func _Payload_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Payload) + switch tag { + case 2: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.PHYPayload) + err := b.DecodeMessage(msg) + m.Protocol = &Payload_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _Payload_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Payload) + // protocol + switch x := m.Protocol.(type) { + case *Payload_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(2<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +func init() { + proto.RegisterType((*RxMetadata)(nil), "protocol.RxMetadata") + proto.RegisterType((*TxConfiguration)(nil), "protocol.TxConfiguration") + proto.RegisterType((*ActivationMetadata)(nil), "protocol.ActivationMetadata") + proto.RegisterType((*Payload)(nil), "protocol.Payload") +} +func (m *RxMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RxMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn1, err := m.Protocol.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *RxMetadata_Lorawan) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + data[i] = 0xa + i++ + i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) + n2, err := m.Lorawan.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} +func (m *TxConfiguration) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn3, err := m.Protocol.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn3 + } + return i, nil +} + +func (m *TxConfiguration_Lorawan) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + data[i] = 0xa + i++ + i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) + n4, err := m.Lorawan.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} +func (m *ActivationMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn5, err := m.Protocol.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn5 + } + return i, nil +} + +func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + data[i] = 0xa + i++ + i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) + n6, err := m.Lorawan.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} +func (m *Payload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Payload) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Data) > 0 { + data[i] = 0xa + i++ + i = encodeVarintProtocol(data, i, uint64(len(m.Data))) + i += copy(data[i:], m.Data) + } + if m.Protocol != nil { + nn7, err := m.Protocol.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn7 + } + return i, nil +} + +func (m *Payload_Lorawan) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + data[i] = 0x12 + i++ + i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) + n8, err := m.Lorawan.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} +func encodeFixed64Protocol(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Protocol(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintProtocol(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *RxMetadata) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *RxMetadata_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *TxConfiguration_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} +func (m *ActivationMetadata) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *ActivationMetadata_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} +func (m *Payload) Size() (n int) { + var l int + _ = l + l = len(m.Data) + if l > 0 { + n += 1 + l + sovProtocol(uint64(l)) + } + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *Payload_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} + +func sovProtocol(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozProtocol(x uint64) (n int) { + return sovProtocol(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *RxMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RxMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RxMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.Metadata{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &RxMetadata_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.TxConfiguration{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &TxConfiguration_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.ActivationMetadata{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &ActivationMetadata_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Payload) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Payload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Payload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Data = append(m.Data[:0], data[iNdEx:postIndex]...) + if m.Data == nil { + m.Data = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.PHYPayload{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &Payload_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipProtocol(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthProtocol + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowProtocol + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipProtocol(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthProtocol = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowProtocol = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorProtocol = []byte{ + // 251 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, + 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, + 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0x81, 0x33, 0xf4, 0xc0, 0x0c, 0x21, 0x0e, 0x18, + 0x5f, 0xca, 0x89, 0x24, 0x63, 0x72, 0xf2, 0x8b, 0x12, 0xcb, 0x13, 0xf3, 0x60, 0x34, 0xc4, 0x34, + 0x25, 0x77, 0x2e, 0xae, 0xa0, 0x0a, 0xdf, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, 0x44, 0x21, 0x5d, + 0x2e, 0x76, 0xa8, 0xb4, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xa0, 0x1e, 0x4c, 0x39, 0x4c, + 0x8d, 0x07, 0x43, 0x10, 0x4c, 0x8d, 0x13, 0x17, 0x17, 0xdc, 0x31, 0x4a, 0xc1, 0x5c, 0xfc, 0x21, + 0x15, 0xce, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0xa5, 0x45, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x42, 0x26, + 0xe8, 0xa6, 0x49, 0xc0, 0x4d, 0x43, 0x53, 0x8a, 0xcb, 0xd0, 0x48, 0x2e, 0x21, 0xc7, 0xe4, 0x92, + 0xcc, 0x32, 0xb0, 0x22, 0xb8, 0x2b, 0xcd, 0xd1, 0xcd, 0x95, 0x86, 0x9b, 0x8b, 0xa9, 0x1a, 0x97, + 0xd1, 0x51, 0x5c, 0xec, 0x01, 0x89, 0x95, 0x39, 0xf9, 0x89, 0x29, 0x42, 0x42, 0x5c, 0x2c, 0x20, + 0x95, 0x60, 0xc3, 0x78, 0x82, 0xc0, 0x6c, 0x21, 0x7d, 0x84, 0x1d, 0x4c, 0x60, 0x3b, 0x84, 0xe1, + 0x76, 0x04, 0x78, 0x44, 0x42, 0x75, 0xe2, 0x30, 0xdb, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, + 0x40, 0xfc, 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xcb, 0x19, 0x03, 0x02, 0x00, + 0x00, 0xff, 0xff, 0xca, 0x90, 0x37, 0x29, 0xfa, 0x01, 0x00, 0x00, +} diff --git a/api/protocol/protocol.proto b/api/protocol/protocol.proto new file mode 100644 index 000000000..3bece744d --- /dev/null +++ b/api/protocol/protocol.proto @@ -0,0 +1,30 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto"; + +package protocol; + +message RxMetadata { + oneof protocol { + lorawan.Metadata lorawan = 1; + } +} + +message TxConfiguration { + oneof protocol { + lorawan.TxConfiguration lorawan = 1; + } +} + +message ActivationMetadata { + oneof protocol { + lorawan.ActivationMetadata lorawan = 1; + } +} + +message Payload { + bytes data = 1; + oneof protocol { + lorawan.PHYPayload lorawan = 2; + } +} diff --git a/api/router/router.pb.go b/api/router/router.pb.go new file mode 100644 index 000000000..e38aa778d --- /dev/null +++ b/api/router/router.pb.go @@ -0,0 +1,3117 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/router/router.proto +// DO NOT EDIT! + +/* + Package router is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/router/router.proto + + It has these top-level messages: + SubscribeRequest + UplinkMessage + DownlinkMessage + DeviceActivationRequest + DeviceActivationResponse + GatewaysRequest + GatewaysResponse + RegisterGatewayRequest + UnregisterGatewayRequest + GatewayStatusRequest + GatewayStatusResponse + StatusRequest + StatusResponse +*/ +package router + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import api "github.com/TheThingsNetwork/ttn/api" +import protocol "github.com/TheThingsNetwork/ttn/api/protocol" +import gateway "github.com/TheThingsNetwork/ttn/api/gateway" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +type SubscribeRequest struct { + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` +} + +func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} } +func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) } +func (*SubscribeRequest) ProtoMessage() {} +func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{0} } + +type UplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,11,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,12,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` +} + +func (m *UplinkMessage) Reset() { *m = UplinkMessage{} } +func (m *UplinkMessage) String() string { return proto.CompactTextString(m) } +func (*UplinkMessage) ProtoMessage() {} +func (*UplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{1} } + +func (m *UplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *UplinkMessage) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +type DownlinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + ProtocolConfiguration *protocol.TxConfiguration `protobuf:"bytes,11,opt,name=protocol_configuration,json=protocolConfiguration" json:"protocol_configuration,omitempty"` + GatewayConfiguration *gateway.TxConfiguration `protobuf:"bytes,12,opt,name=gateway_configuration,json=gatewayConfiguration" json:"gateway_configuration,omitempty"` +} + +func (m *DownlinkMessage) Reset() { *m = DownlinkMessage{} } +func (m *DownlinkMessage) String() string { return proto.CompactTextString(m) } +func (*DownlinkMessage) ProtoMessage() {} +func (*DownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{2} } + +func (m *DownlinkMessage) GetProtocolConfiguration() *protocol.TxConfiguration { + if m != nil { + return m.ProtocolConfiguration + } + return nil +} + +func (m *DownlinkMessage) GetGatewayConfiguration() *gateway.TxConfiguration { + if m != nil { + return m.GatewayConfiguration + } + return nil +} + +type DeviceActivationRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` + AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` +} + +func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } +func (m *DeviceActivationRequest) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationRequest) ProtoMessage() {} +func (*DeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{3} } + +func (m *DeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { + if m != nil { + return m.ProtocolMetadata + } + return nil +} + +func (m *DeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { + if m != nil { + return m.GatewayMetadata + } + return nil +} + +type DeviceActivationResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + ProtocolConfiguration *protocol.TxConfiguration `protobuf:"bytes,11,opt,name=protocol_configuration,json=protocolConfiguration" json:"protocol_configuration,omitempty"` + GatewayConfiguration *gateway.TxConfiguration `protobuf:"bytes,12,opt,name=gateway_configuration,json=gatewayConfiguration" json:"gateway_configuration,omitempty"` +} + +func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } +func (m *DeviceActivationResponse) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationResponse) ProtoMessage() {} +func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{4} } + +func (m *DeviceActivationResponse) GetProtocolConfiguration() *protocol.TxConfiguration { + if m != nil { + return m.ProtocolConfiguration + } + return nil +} + +func (m *DeviceActivationResponse) GetGatewayConfiguration() *gateway.TxConfiguration { + if m != nil { + return m.GatewayConfiguration + } + return nil +} + +// message GatewaysRequest is used to list all Gateways on this Router +type GatewaysRequest struct { +} + +func (m *GatewaysRequest) Reset() { *m = GatewaysRequest{} } +func (m *GatewaysRequest) String() string { return proto.CompactTextString(m) } +func (*GatewaysRequest) ProtoMessage() {} +func (*GatewaysRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{5} } + +// message GatewaysResponse is the response to the GatewaysRequest +type GatewaysResponse struct { + GatewayIds []string `protobuf:"bytes,1,rep,name=gateway_ids,json=gatewayIds" json:"gateway_ids,omitempty"` +} + +func (m *GatewaysResponse) Reset() { *m = GatewaysResponse{} } +func (m *GatewaysResponse) String() string { return proto.CompactTextString(m) } +func (*GatewaysResponse) ProtoMessage() {} +func (*GatewaysResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{6} } + +// message RegisterGatewayRequest is used to register a Gateway with this Router +type RegisterGatewayRequest struct { + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` +} + +func (m *RegisterGatewayRequest) Reset() { *m = RegisterGatewayRequest{} } +func (m *RegisterGatewayRequest) String() string { return proto.CompactTextString(m) } +func (*RegisterGatewayRequest) ProtoMessage() {} +func (*RegisterGatewayRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{7} } + +// message UnregisterGatewayRequest is used to unregister a Gateway from this +// Router +type UnregisterGatewayRequest struct { + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` +} + +func (m *UnregisterGatewayRequest) Reset() { *m = UnregisterGatewayRequest{} } +func (m *UnregisterGatewayRequest) String() string { return proto.CompactTextString(m) } +func (*UnregisterGatewayRequest) ProtoMessage() {} +func (*UnregisterGatewayRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{8} } + +// message GatewayStatusRequest is used to request the status of a gateway from +// this Router +type GatewayStatusRequest struct { + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` +} + +func (m *GatewayStatusRequest) Reset() { *m = GatewayStatusRequest{} } +func (m *GatewayStatusRequest) String() string { return proto.CompactTextString(m) } +func (*GatewayStatusRequest) ProtoMessage() {} +func (*GatewayStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{9} } + +// message GatewayStatusResponse is the response to the GatewayStatusRequest +type GatewayStatusResponse struct { + LastStatus *gateway.StatusMessage `protobuf:"bytes,1,opt,name=last_status,json=lastStatus" json:"last_status,omitempty"` +} + +func (m *GatewayStatusResponse) Reset() { *m = GatewayStatusResponse{} } +func (m *GatewayStatusResponse) String() string { return proto.CompactTextString(m) } +func (*GatewayStatusResponse) ProtoMessage() {} +func (*GatewayStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{10} } + +func (m *GatewayStatusResponse) GetLastStatus() *gateway.StatusMessage { + if m != nil { + return m.LastStatus + } + return nil +} + +// message StatusRequest is used to request the status of this Router +type StatusRequest struct { +} + +func (m *StatusRequest) Reset() { *m = StatusRequest{} } +func (m *StatusRequest) String() string { return proto.CompactTextString(m) } +func (*StatusRequest) ProtoMessage() {} +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{11} } + +// message StatusResponse is the response to the StatusRequest +type StatusResponse struct { + // Gateways + GatewayStatus *api.Rates `protobuf:"bytes,1,opt,name=gateway_status,json=gatewayStatus" json:"gateway_status,omitempty"` + ActiveGateways uint32 `protobuf:"varint,4,opt,name=active_gateways,json=activeGateways,proto3" json:"active_gateways,omitempty"` + // Uplink + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + // Downlink + Downlink *api.Rates `protobuf:"bytes,21,opt,name=downlink" json:"downlink,omitempty"` + // Activations + Activations *api.Rates `protobuf:"bytes,31,opt,name=activations" json:"activations,omitempty"` + ActivationsAccepted *api.Rates `protobuf:"bytes,32,opt,name=activations_accepted,json=activationsAccepted" json:"activations_accepted,omitempty"` + // Connections + ConnectedGateways uint32 `protobuf:"varint,41,opt,name=connected_gateways,json=connectedGateways,proto3" json:"connected_gateways,omitempty"` + ConnectedBrokers uint32 `protobuf:"varint,42,opt,name=connected_brokers,json=connectedBrokers,proto3" json:"connected_brokers,omitempty"` +} + +func (m *StatusResponse) Reset() { *m = StatusResponse{} } +func (m *StatusResponse) String() string { return proto.CompactTextString(m) } +func (*StatusResponse) ProtoMessage() {} +func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{12} } + +func (m *StatusResponse) GetGatewayStatus() *api.Rates { + if m != nil { + return m.GatewayStatus + } + return nil +} + +func (m *StatusResponse) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *StatusResponse) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *StatusResponse) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + +func (m *StatusResponse) GetActivationsAccepted() *api.Rates { + if m != nil { + return m.ActivationsAccepted + } + return nil +} + +func init() { + proto.RegisterType((*SubscribeRequest)(nil), "router.SubscribeRequest") + proto.RegisterType((*UplinkMessage)(nil), "router.UplinkMessage") + proto.RegisterType((*DownlinkMessage)(nil), "router.DownlinkMessage") + proto.RegisterType((*DeviceActivationRequest)(nil), "router.DeviceActivationRequest") + proto.RegisterType((*DeviceActivationResponse)(nil), "router.DeviceActivationResponse") + proto.RegisterType((*GatewaysRequest)(nil), "router.GatewaysRequest") + proto.RegisterType((*GatewaysResponse)(nil), "router.GatewaysResponse") + proto.RegisterType((*RegisterGatewayRequest)(nil), "router.RegisterGatewayRequest") + proto.RegisterType((*UnregisterGatewayRequest)(nil), "router.UnregisterGatewayRequest") + proto.RegisterType((*GatewayStatusRequest)(nil), "router.GatewayStatusRequest") + proto.RegisterType((*GatewayStatusResponse)(nil), "router.GatewayStatusResponse") + proto.RegisterType((*StatusRequest)(nil), "router.StatusRequest") + proto.RegisterType((*StatusResponse)(nil), "router.StatusResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// Client API for Router service + +type RouterClient interface { + // Gateway streams status messages to Router + GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Router_GatewayStatusClient, error) + // Gateway streams uplink messages to Router + Uplink(ctx context.Context, opts ...grpc.CallOption) (Router_UplinkClient, error) + // Gateway subscribes to downlink messages from Router + Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Router_SubscribeClient, error) + // Gateway requests device activation + Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) +} + +type routerClient struct { + cc *grpc.ClientConn +} + +func NewRouterClient(cc *grpc.ClientConn) RouterClient { + return &routerClient{cc} +} + +func (c *routerClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Router_GatewayStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Router_serviceDesc.Streams[0], c.cc, "/router.Router/GatewayStatus", opts...) + if err != nil { + return nil, err + } + x := &routerGatewayStatusClient{stream} + return x, nil +} + +type Router_GatewayStatusClient interface { + Send(*gateway.StatusMessage) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type routerGatewayStatusClient struct { + grpc.ClientStream +} + +func (x *routerGatewayStatusClient) Send(m *gateway.StatusMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *routerGatewayStatusClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Uplink(ctx context.Context, opts ...grpc.CallOption) (Router_UplinkClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Router_serviceDesc.Streams[1], c.cc, "/router.Router/Uplink", opts...) + if err != nil { + return nil, err + } + x := &routerUplinkClient{stream} + return x, nil +} + +type Router_UplinkClient interface { + Send(*UplinkMessage) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type routerUplinkClient struct { + grpc.ClientStream +} + +func (x *routerUplinkClient) Send(m *UplinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *routerUplinkClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Router_SubscribeClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Router_serviceDesc.Streams[2], c.cc, "/router.Router/Subscribe", opts...) + if err != nil { + return nil, err + } + x := &routerSubscribeClient{stream} + if err := x.ClientStream.SendMsg(in); err != nil { + return nil, err + } + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + return x, nil +} + +type Router_SubscribeClient interface { + Recv() (*DownlinkMessage, error) + grpc.ClientStream +} + +type routerSubscribeClient struct { + grpc.ClientStream +} + +func (x *routerSubscribeClient) Recv() (*DownlinkMessage, error) { + m := new(DownlinkMessage) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *routerClient) Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { + out := new(DeviceActivationResponse) + err := grpc.Invoke(ctx, "/router.Router/Activate", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for Router service + +type RouterServer interface { + // Gateway streams status messages to Router + GatewayStatus(Router_GatewayStatusServer) error + // Gateway streams uplink messages to Router + Uplink(Router_UplinkServer) error + // Gateway subscribes to downlink messages from Router + Subscribe(*SubscribeRequest, Router_SubscribeServer) error + // Gateway requests device activation + Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) +} + +func RegisterRouterServer(s *grpc.Server, srv RouterServer) { + s.RegisterService(&_Router_serviceDesc, srv) +} + +func _Router_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(RouterServer).GatewayStatus(&routerGatewayStatusServer{stream}) +} + +type Router_GatewayStatusServer interface { + SendAndClose(*api.Ack) error + Recv() (*gateway.StatusMessage, error) + grpc.ServerStream +} + +type routerGatewayStatusServer struct { + grpc.ServerStream +} + +func (x *routerGatewayStatusServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *routerGatewayStatusServer) Recv() (*gateway.StatusMessage, error) { + m := new(gateway.StatusMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Router_Uplink_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(RouterServer).Uplink(&routerUplinkServer{stream}) +} + +type Router_UplinkServer interface { + SendAndClose(*api.Ack) error + Recv() (*UplinkMessage, error) + grpc.ServerStream +} + +type routerUplinkServer struct { + grpc.ServerStream +} + +func (x *routerUplinkServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *routerUplinkServer) Recv() (*UplinkMessage, error) { + m := new(UplinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Router_Subscribe_Handler(srv interface{}, stream grpc.ServerStream) error { + m := new(SubscribeRequest) + if err := stream.RecvMsg(m); err != nil { + return err + } + return srv.(RouterServer).Subscribe(m, &routerSubscribeServer{stream}) +} + +type Router_SubscribeServer interface { + Send(*DownlinkMessage) error + grpc.ServerStream +} + +type routerSubscribeServer struct { + grpc.ServerStream +} + +func (x *routerSubscribeServer) Send(m *DownlinkMessage) error { + return x.ServerStream.SendMsg(m) +} + +func _Router_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(DeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterServer).Activate(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _Router_serviceDesc = grpc.ServiceDesc{ + ServiceName: "router.Router", + HandlerType: (*RouterServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Activate", + Handler: _Router_Activate_Handler, + }, + }, + Streams: []grpc.StreamDesc{ + { + StreamName: "GatewayStatus", + Handler: _Router_GatewayStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "Uplink", + Handler: _Router_Uplink_Handler, + ClientStreams: true, + }, + { + StreamName: "Subscribe", + Handler: _Router_Subscribe_Handler, + ServerStreams: true, + }, + }, +} + +// Client API for RouterManager service + +type RouterManagerClient interface { + // Network operator requests list of Gateways from Router Manager + Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error) + // Gateway owner or network operator registers Gateway with Router Manager + RegisterGateway(ctx context.Context, in *RegisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) + // Gateway owner or network operator unregisters Gateway with Router Manager + UnregisterGateway(ctx context.Context, in *UnregisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) + // Gateway owner or network operator requests Gateway status from Router Manager + GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) + // Network operator requests Router status + Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) +} + +type routerManagerClient struct { + cc *grpc.ClientConn +} + +func NewRouterManagerClient(cc *grpc.ClientConn) RouterManagerClient { + return &routerManagerClient{cc} +} + +func (c *routerManagerClient) Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error) { + out := new(GatewaysResponse) + err := grpc.Invoke(ctx, "/router.RouterManager/Gateways", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerManagerClient) RegisterGateway(ctx context.Context, in *RegisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/router.RouterManager/RegisterGateway", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerManagerClient) UnregisterGateway(ctx context.Context, in *UnregisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/router.RouterManager/UnregisterGateway", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerManagerClient) GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) { + out := new(GatewayStatusResponse) + err := grpc.Invoke(ctx, "/router.RouterManager/GatewayStatus", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *routerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { + out := new(StatusResponse) + err := grpc.Invoke(ctx, "/router.RouterManager/Status", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for RouterManager service + +type RouterManagerServer interface { + // Network operator requests list of Gateways from Router Manager + Gateways(context.Context, *GatewaysRequest) (*GatewaysResponse, error) + // Gateway owner or network operator registers Gateway with Router Manager + RegisterGateway(context.Context, *RegisterGatewayRequest) (*api.Ack, error) + // Gateway owner or network operator unregisters Gateway with Router Manager + UnregisterGateway(context.Context, *UnregisterGatewayRequest) (*api.Ack, error) + // Gateway owner or network operator requests Gateway status from Router Manager + GatewayStatus(context.Context, *GatewayStatusRequest) (*GatewayStatusResponse, error) + // Network operator requests Router status + Status(context.Context, *StatusRequest) (*StatusResponse, error) +} + +func RegisterRouterManagerServer(s *grpc.Server, srv RouterManagerServer) { + s.RegisterService(&_RouterManager_serviceDesc, srv) +} + +func _RouterManager_Gateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(GatewaysRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterManagerServer).Gateways(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _RouterManager_RegisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(RegisterGatewayRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterManagerServer).RegisterGateway(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _RouterManager_UnregisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(UnregisterGatewayRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterManagerServer).UnregisterGateway(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _RouterManager_GatewayStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(GatewayStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterManagerServer).GatewayStatus(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +func _RouterManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { + in := new(StatusRequest) + if err := dec(in); err != nil { + return nil, err + } + out, err := srv.(RouterManagerServer).Status(ctx, in) + if err != nil { + return nil, err + } + return out, nil +} + +var _RouterManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "router.RouterManager", + HandlerType: (*RouterManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Gateways", + Handler: _RouterManager_Gateways_Handler, + }, + { + MethodName: "RegisterGateway", + Handler: _RouterManager_RegisterGateway_Handler, + }, + { + MethodName: "UnregisterGateway", + Handler: _RouterManager_UnregisterGateway_Handler, + }, + { + MethodName: "GatewayStatus", + Handler: _RouterManager_GatewayStatus_Handler, + }, + { + MethodName: "Status", + Handler: _RouterManager_Status_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *SubscribeRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) + } + return i, nil +} + +func (m *UplinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.ProtocolMetadata != nil { + data[i] = 0x5a + i++ + i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) + n1, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.GatewayMetadata != nil { + data[i] = 0x62 + i++ + i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) + n2, err := m.GatewayMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + +func (m *DownlinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.ProtocolConfiguration != nil { + data[i] = 0x5a + i++ + i = encodeVarintRouter(data, i, uint64(m.ProtocolConfiguration.Size())) + n3, err := m.ProtocolConfiguration.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.GatewayConfiguration != nil { + data[i] = 0x62 + i++ + i = encodeVarintRouter(data, i, uint64(m.GatewayConfiguration.Size())) + n4, err := m.GatewayConfiguration.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} + +func (m *DeviceActivationRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if len(m.DevEui) > 0 { + data[i] = 0x5a + i++ + i = encodeVarintRouter(data, i, uint64(len(m.DevEui))) + i += copy(data[i:], m.DevEui) + } + if len(m.AppEui) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintRouter(data, i, uint64(len(m.AppEui))) + i += copy(data[i:], m.AppEui) + } + if m.ProtocolMetadata != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) + n5, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.GatewayMetadata != nil { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) + n6, err := m.GatewayMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} + +func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.ProtocolConfiguration != nil { + data[i] = 0x5a + i++ + i = encodeVarintRouter(data, i, uint64(m.ProtocolConfiguration.Size())) + n7, err := m.ProtocolConfiguration.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.GatewayConfiguration != nil { + data[i] = 0x62 + i++ + i = encodeVarintRouter(data, i, uint64(m.GatewayConfiguration.Size())) + n8, err := m.GatewayConfiguration.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} + +func (m *GatewaysRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GatewaysRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *GatewaysResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GatewaysResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayIds) > 0 { + for _, s := range m.GatewayIds { + data[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + return i, nil +} + +func (m *RegisterGatewayRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *RegisterGatewayRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) + } + return i, nil +} + +func (m *UnregisterGatewayRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *UnregisterGatewayRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) + } + return i, nil +} + +func (m *GatewayStatusRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GatewayStatusRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.GatewayId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) + } + return i, nil +} + +func (m *GatewayStatusResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.LastStatus != nil { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(m.LastStatus.Size())) + n9, err := m.LastStatus.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n9 + } + return i, nil +} + +func (m *StatusRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *StatusResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *StatusResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.GatewayStatus != nil { + data[i] = 0xa + i++ + i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) + n10, err := m.GatewayStatus.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n10 + } + if m.ActiveGateways != 0 { + data[i] = 0x20 + i++ + i = encodeVarintRouter(data, i, uint64(m.ActiveGateways)) + } + if m.Uplink != nil { + data[i] = 0x5a + i++ + i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) + n11, err := m.Uplink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n11 + } + if m.Downlink != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) + n12, err := m.Downlink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n12 + } + if m.Activations != nil { + data[i] = 0xfa + i++ + data[i] = 0x1 + i++ + i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) + n13, err := m.Activations.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n13 + } + if m.ActivationsAccepted != nil { + data[i] = 0x82 + i++ + data[i] = 0x2 + i++ + i = encodeVarintRouter(data, i, uint64(m.ActivationsAccepted.Size())) + n14, err := m.ActivationsAccepted.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n14 + } + if m.ConnectedGateways != 0 { + data[i] = 0xc8 + i++ + data[i] = 0x2 + i++ + i = encodeVarintRouter(data, i, uint64(m.ConnectedGateways)) + } + if m.ConnectedBrokers != 0 { + data[i] = 0xd0 + i++ + data[i] = 0x2 + i++ + i = encodeVarintRouter(data, i, uint64(m.ConnectedBrokers)) + } + return i, nil +} + +func encodeFixed64Router(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Router(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintRouter(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *SubscribeRequest) Size() (n int) { + var l int + _ = l + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *UplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *DownlinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolConfiguration != nil { + l = m.ProtocolConfiguration.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayConfiguration != nil { + l = m.GatewayConfiguration.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *DeviceActivationRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + l = len(m.DevEui) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + l = len(m.AppEui) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolMetadata != nil { + l = m.ProtocolMetadata.Size() + n += 2 + l + sovRouter(uint64(l)) + } + if m.GatewayMetadata != nil { + l = m.GatewayMetadata.Size() + n += 2 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *DeviceActivationResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + if m.ProtocolConfiguration != nil { + l = m.ProtocolConfiguration.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.GatewayConfiguration != nil { + l = m.GatewayConfiguration.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *GatewaysRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *GatewaysResponse) Size() (n int) { + var l int + _ = l + if len(m.GatewayIds) > 0 { + for _, s := range m.GatewayIds { + l = len(s) + n += 1 + l + sovRouter(uint64(l)) + } + } + return n +} + +func (m *RegisterGatewayRequest) Size() (n int) { + var l int + _ = l + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *UnregisterGatewayRequest) Size() (n int) { + var l int + _ = l + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *GatewayStatusRequest) Size() (n int) { + var l int + _ = l + l = len(m.GatewayId) + if l > 0 { + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *GatewayStatusResponse) Size() (n int) { + var l int + _ = l + if m.LastStatus != nil { + l = m.LastStatus.Size() + n += 1 + l + sovRouter(uint64(l)) + } + return n +} + +func (m *StatusRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *StatusResponse) Size() (n int) { + var l int + _ = l + if m.GatewayStatus != nil { + l = m.GatewayStatus.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.ActiveGateways != 0 { + n += 1 + sovRouter(uint64(m.ActiveGateways)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 2 + l + sovRouter(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 2 + l + sovRouter(uint64(l)) + } + if m.ActivationsAccepted != nil { + l = m.ActivationsAccepted.Size() + n += 2 + l + sovRouter(uint64(l)) + } + if m.ConnectedGateways != 0 { + n += 2 + sovRouter(uint64(m.ConnectedGateways)) + } + if m.ConnectedBrokers != 0 { + n += 2 + sovRouter(uint64(m.ConnectedBrokers)) + } + return n +} + +func sovRouter(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRouter(x uint64) (n int) { + return sovRouter(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *SubscribeRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: SubscribeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: SubscribeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UplinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DownlinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DownlinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DownlinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolConfiguration == nil { + m.ProtocolConfiguration = &protocol.TxConfiguration{} + } + if err := m.ProtocolConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayConfiguration == nil { + m.GatewayConfiguration = &gateway.TxConfiguration{} + } + if err := m.GatewayConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) + if m.DevEui == nil { + m.DevEui = []byte{} + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) + if m.AppEui == nil { + m.AppEui = []byte{} + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolMetadata == nil { + m.ProtocolMetadata = &protocol.RxMetadata{} + } + if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 22: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayMetadata == nil { + m.GatewayMetadata = &gateway.RxMetadata{} + } + if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceActivationResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolConfiguration == nil { + m.ProtocolConfiguration = &protocol.TxConfiguration{} + } + if err := m.ProtocolConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayConfiguration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayConfiguration == nil { + m.GatewayConfiguration = &gateway.TxConfiguration{} + } + if err := m.GatewayConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GatewaysRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GatewaysRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GatewaysRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GatewaysResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GatewaysResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GatewaysResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayIds", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayIds = append(m.GatewayIds, string(data[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RegisterGatewayRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RegisterGatewayRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RegisterGatewayRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *UnregisterGatewayRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: UnregisterGatewayRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: UnregisterGatewayRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GatewayStatusRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GatewayStatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GatewayStatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.GatewayId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GatewayStatusResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GatewayStatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GatewayStatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastStatus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.LastStatus == nil { + m.LastStatus = &gateway.StatusMessage{} + } + if err := m.LastStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *StatusResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayStatus", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.GatewayStatus == nil { + m.GatewayStatus = &api.Rates{} + } + if err := m.GatewayStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ActiveGateways", wireType) + } + m.ActiveGateways = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ActiveGateways |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 31: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 32: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationsAccepted", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationsAccepted == nil { + m.ActivationsAccepted = &api.Rates{} + } + if err := m.ActivationsAccepted.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 41: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedGateways", wireType) + } + m.ConnectedGateways = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ConnectedGateways |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 42: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ConnectedBrokers", wireType) + } + m.ConnectedBrokers = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ConnectedBrokers |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipRouter(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRouter + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRouter(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthRouter + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRouter + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRouter(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRouter = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRouter = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorRouter = []byte{ + // 818 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x55, 0x4d, 0x6f, 0xda, 0x48, + 0x18, 0x5e, 0x6f, 0x56, 0x84, 0xbc, 0x40, 0x80, 0x09, 0x10, 0x2f, 0xda, 0x7c, 0xc8, 0x87, 0xdd, + 0xec, 0x47, 0x60, 0x43, 0xb4, 0x8a, 0xa2, 0x68, 0xdb, 0x92, 0xa6, 0xaa, 0x2a, 0x95, 0x2a, 0x72, + 0x92, 0x33, 0x1a, 0xcc, 0x94, 0x58, 0x10, 0xdb, 0xf5, 0x8c, 0xf3, 0x71, 0xec, 0xbf, 0xe8, 0x8f, + 0xe8, 0x0f, 0xe9, 0xa1, 0x87, 0x4a, 0x55, 0xef, 0x55, 0x7b, 0xe9, 0x7f, 0xe8, 0xa5, 0x83, 0x67, + 0x3c, 0xd8, 0x26, 0x48, 0xb4, 0x3d, 0xf5, 0x60, 0xc1, 0x3c, 0xcf, 0xf3, 0xbe, 0x7e, 0x5f, 0xbf, + 0xf3, 0xcc, 0xc0, 0xde, 0xc0, 0x66, 0xe7, 0x41, 0xaf, 0x61, 0xb9, 0x17, 0xcd, 0xd3, 0x73, 0x72, + 0x7a, 0x6e, 0x3b, 0x03, 0xfa, 0x84, 0xb0, 0x2b, 0xd7, 0x1f, 0x36, 0x19, 0x73, 0x9a, 0xd8, 0xb3, + 0x9b, 0xbe, 0x1b, 0x30, 0xe2, 0xcb, 0x9f, 0x86, 0xe7, 0xbb, 0xcc, 0x45, 0x19, 0xb1, 0xaa, 0x6f, + 0xcf, 0x93, 0x80, 0x3f, 0x22, 0xac, 0x7e, 0x30, 0x8f, 0x3c, 0x94, 0x5a, 0xee, 0x48, 0xfd, 0x91, + 0xc1, 0xfb, 0xf3, 0x04, 0x0f, 0x30, 0x23, 0x57, 0xf8, 0x26, 0xfa, 0x15, 0xa1, 0xc6, 0x0e, 0x94, + 0x4e, 0x82, 0x1e, 0xb5, 0x7c, 0xbb, 0x47, 0x4c, 0xf2, 0x2c, 0x20, 0x94, 0xa1, 0x35, 0x00, 0x29, + 0xea, 0xda, 0x7d, 0x5d, 0xdb, 0xd4, 0xb6, 0x96, 0xcc, 0x25, 0x89, 0x3c, 0xea, 0x1b, 0x2f, 0x35, + 0x28, 0x9c, 0x79, 0x23, 0xdb, 0x19, 0x76, 0x08, 0xa5, 0x78, 0x40, 0x90, 0x0e, 0x8b, 0x1e, 0xbe, + 0x19, 0xb9, 0x58, 0xa8, 0xf3, 0x66, 0xb4, 0x44, 0x6d, 0x28, 0x47, 0xb5, 0x76, 0x2f, 0x08, 0xc3, + 0x7d, 0xcc, 0xb0, 0x9e, 0xe3, 0x9a, 0x5c, 0xab, 0xd2, 0x50, 0x5d, 0x98, 0xd7, 0x1d, 0xc9, 0x99, + 0xa5, 0x08, 0x8c, 0x10, 0x74, 0x07, 0x4a, 0x51, 0x35, 0x2a, 0x43, 0x3e, 0xcc, 0xb0, 0xd2, 0x88, + 0x7a, 0x89, 0x25, 0x28, 0x4a, 0x2c, 0x02, 0x8c, 0xd7, 0x1a, 0x14, 0x8f, 0xdc, 0x2b, 0x67, 0xbe, + 0x82, 0x8f, 0xa1, 0xa6, 0x0a, 0xb6, 0x5c, 0xe7, 0xa9, 0x3d, 0x08, 0x7c, 0xcc, 0x6c, 0xd7, 0x91, + 0x55, 0xff, 0x3a, 0xa9, 0xfa, 0xf4, 0xfa, 0x7e, 0x5c, 0x60, 0x56, 0x23, 0x26, 0x01, 0xa3, 0x0e, + 0x54, 0xa3, 0xfa, 0x93, 0x09, 0x45, 0x13, 0xba, 0x6a, 0x22, 0x9d, 0xaf, 0x22, 0x89, 0x04, 0x6a, + 0x7c, 0xd2, 0x60, 0xf5, 0x88, 0x5c, 0xda, 0x16, 0x69, 0x5b, 0xcc, 0xbe, 0x14, 0x52, 0x39, 0xb8, + 0xd9, 0x6d, 0xad, 0xc2, 0x62, 0x9f, 0x5c, 0x76, 0x49, 0x60, 0x87, 0x7d, 0xe4, 0xcd, 0x0c, 0x5f, + 0x3e, 0x08, 0xec, 0x31, 0x81, 0x3d, 0x2f, 0x24, 0xf2, 0x82, 0xe0, 0xcb, 0x31, 0x71, 0xeb, 0xe4, + 0xaa, 0xdf, 0x3d, 0xb9, 0xda, 0x57, 0x4c, 0xee, 0x9d, 0x06, 0xfa, 0x74, 0xab, 0xd4, 0x73, 0x1d, + 0xfa, 0x43, 0x8f, 0xb0, 0x0c, 0xc5, 0x87, 0x02, 0xa7, 0x72, 0x72, 0xc6, 0x2e, 0x94, 0x26, 0x90, + 0xec, 0x70, 0x03, 0x72, 0x13, 0x1b, 0x52, 0xde, 0xe5, 0x02, 0xf7, 0x21, 0x28, 0x1f, 0x52, 0x63, + 0x0f, 0x6a, 0x26, 0x19, 0xd8, 0x94, 0x1f, 0x37, 0x32, 0x78, 0x4e, 0x07, 0xef, 0x83, 0x7e, 0xe6, + 0xf8, 0xdf, 0x14, 0xfa, 0x1f, 0x54, 0x64, 0xc0, 0x09, 0xc3, 0x2c, 0xa0, 0x73, 0x86, 0x1d, 0x43, + 0x35, 0x15, 0x26, 0x9b, 0xdc, 0x83, 0xdc, 0x08, 0x53, 0xd6, 0xa5, 0x21, 0x1c, 0x06, 0xe6, 0x5a, + 0x35, 0xf5, 0x41, 0x85, 0x5a, 0xda, 0xd6, 0x84, 0xb1, 0x54, 0x40, 0x46, 0x11, 0x0a, 0x89, 0x0a, + 0x8c, 0xe7, 0x0b, 0xb0, 0x9c, 0x4a, 0xbe, 0x03, 0xcb, 0x51, 0x51, 0x89, 0xfc, 0xd0, 0x18, 0x1f, + 0xbc, 0x26, 0xa7, 0xa8, 0x59, 0x18, 0xc4, 0xeb, 0x42, 0x7f, 0x40, 0x11, 0x8f, 0x37, 0x1b, 0xe9, + 0x4a, 0x9c, 0xea, 0xbf, 0xf0, 0x98, 0x82, 0xb9, 0x2c, 0xe0, 0x68, 0x4a, 0xc8, 0x80, 0x4c, 0x10, + 0x1e, 0x82, 0x72, 0x57, 0xc5, 0x73, 0x4a, 0x06, 0xfd, 0x0e, 0xd9, 0xbe, 0x3c, 0x79, 0xa4, 0x75, + 0xe2, 0x2a, 0xc5, 0xa1, 0x7f, 0x20, 0x87, 0xd5, 0x0e, 0xa7, 0xfa, 0xc6, 0x94, 0x34, 0x4e, 0xa3, + 0xff, 0xa1, 0x12, 0x5b, 0x76, 0xb1, 0x65, 0x11, 0x8f, 0x91, 0xbe, 0xbe, 0x39, 0x15, 0xb6, 0x12, + 0xd3, 0xb5, 0xa5, 0x0c, 0x6d, 0x03, 0xe2, 0x9b, 0xd8, 0x21, 0x16, 0x5f, 0x4c, 0x9a, 0xfc, 0x33, + 0x6c, 0xb2, 0xac, 0x18, 0xd5, 0xe7, 0xdf, 0x30, 0x01, 0xbb, 0x3d, 0xdf, 0x1d, 0x12, 0x9f, 0xea, + 0x7f, 0x85, 0xea, 0x92, 0x22, 0x0e, 0x05, 0xde, 0xfa, 0xac, 0x41, 0xc6, 0x0c, 0xef, 0x3f, 0xb4, + 0x0b, 0x85, 0xc4, 0xc4, 0xd1, 0x8c, 0xa1, 0xd6, 0xb3, 0x61, 0xc1, 0x6d, 0x6b, 0xb8, 0xa5, 0xf1, + 0x97, 0x65, 0xc4, 0xcd, 0x82, 0xaa, 0x0d, 0x79, 0xab, 0x26, 0x6e, 0x9a, 0x84, 0xf8, 0x1e, 0x2c, + 0xa9, 0xab, 0x0b, 0xe9, 0x91, 0x3e, 0x7d, 0x9b, 0xd5, 0x57, 0x23, 0x26, 0x75, 0x09, 0xfc, 0xab, + 0x71, 0x5f, 0x67, 0xe5, 0xc9, 0xc2, 0xdd, 0xa6, 0x64, 0xb7, 0x1f, 0xae, 0xf5, 0xcd, 0xd9, 0x02, + 0xb1, 0xdd, 0x5a, 0x6f, 0x7f, 0x86, 0x82, 0xe8, 0xbe, 0x83, 0x1d, 0xfe, 0x06, 0x9f, 0x8f, 0x2a, + 0xab, 0x3e, 0xa4, 0xaa, 0x23, 0xe5, 0xfd, 0xba, 0x3e, 0x4d, 0xc8, 0xfd, 0x7b, 0x00, 0xc5, 0x94, + 0xc1, 0xd1, 0x7a, 0x24, 0xbe, 0xdd, 0xf9, 0x93, 0x0f, 0x84, 0xee, 0x42, 0x79, 0xca, 0xe4, 0x48, + 0x35, 0x31, 0xcb, 0xff, 0xb1, 0x04, 0x8f, 0xd3, 0x13, 0xfc, 0x2d, 0x55, 0x68, 0xc2, 0x7f, 0xf5, + 0xb5, 0x19, 0xac, 0x32, 0x7a, 0x46, 0xa6, 0x51, 0xa3, 0x4d, 0xc6, 0xd7, 0xd2, 0xb0, 0x08, 0x3c, + 0x2c, 0xbd, 0xfa, 0xb0, 0xae, 0xbd, 0xe1, 0xcf, 0x7b, 0xfe, 0xbc, 0xf8, 0xb8, 0xfe, 0x53, 0x2f, + 0x13, 0x9e, 0xd2, 0xbb, 0x5f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x79, 0xa9, 0x26, 0x5e, 0xa4, 0x09, + 0x00, 0x00, +} diff --git a/api/router/router.proto b/api/router/router.proto new file mode 100644 index 000000000..c78ad8db4 --- /dev/null +++ b/api/router/router.proto @@ -0,0 +1,124 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; +import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; + +package router; + +message SubscribeRequest { + string gateway_id = 1; +} + +message UplinkMessage { + bytes payload = 1; + protocol.RxMetadata protocol_metadata = 11; + gateway.RxMetadata gateway_metadata = 12; +} + +message DownlinkMessage { + bytes payload = 1; + protocol.TxConfiguration protocol_configuration = 11; + gateway.TxConfiguration gateway_configuration = 12; +} + +message DeviceActivationRequest { + bytes payload = 1; + bytes dev_eui = 11; + bytes app_eui = 12; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; +} + +message DeviceActivationResponse { + bytes payload = 1; + protocol.TxConfiguration protocol_configuration = 11; + gateway.TxConfiguration gateway_configuration = 12; +} + +// The Router service provides pure network functionality +service Router { + // Gateway streams status messages to Router + rpc GatewayStatus(stream gateway.StatusMessage) returns (api.Ack); + + // Gateway streams uplink messages to Router + rpc Uplink(stream UplinkMessage) returns (api.Ack); + + // Gateway subscribes to downlink messages from Router + rpc Subscribe(SubscribeRequest) returns (stream DownlinkMessage); + + // Gateway requests device activation + rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); +} + +// message GatewaysRequest is used to list all Gateways on this Router +message GatewaysRequest {} + +// message GatewaysResponse is the response to the GatewaysRequest +message GatewaysResponse { + repeated string gateway_ids = 1; +} + +// message RegisterGatewayRequest is used to register a Gateway with this Router +message RegisterGatewayRequest { + string gateway_id = 1; +} + +// message UnregisterGatewayRequest is used to unregister a Gateway from this +// Router +message UnregisterGatewayRequest { + string gateway_id = 1; +} + +// message GatewayStatusRequest is used to request the status of a gateway from +// this Router +message GatewayStatusRequest { + string gateway_id = 1; +} + +// message GatewayStatusResponse is the response to the GatewayStatusRequest +message GatewayStatusResponse { + gateway.StatusMessage last_status = 1; +} + +// message StatusRequest is used to request the status of this Router +message StatusRequest {} + +// message StatusResponse is the response to the StatusRequest +message StatusResponse { + // Gateways + api.Rates gateway_status = 1; + uint32 active_gateways = 4; + + // Uplink + api.Rates uplink = 11; + + // Downlink + api.Rates downlink = 21; + + // Activations + api.Rates activations = 31; + api.Rates activations_accepted = 32; + + // Connections + uint32 connected_gateways = 41; + uint32 connected_brokers = 42; +} + +// The RouterManager service provides configuration and monitoring functionality +service RouterManager { + // Network operator requests list of Gateways from Router Manager + rpc Gateways(GatewaysRequest) returns (GatewaysResponse); + + // Gateway owner or network operator registers Gateway with Router Manager + rpc RegisterGateway(RegisterGatewayRequest) returns (api.Ack); + + // Gateway owner or network operator unregisters Gateway with Router Manager + rpc UnregisterGateway(UnregisterGatewayRequest) returns (api.Ack); + + // Gateway owner or network operator requests Gateway status from Router Manager + rpc GatewayStatus(GatewayStatusRequest) returns (GatewayStatusResponse); + + // Network operator requests Router status + rpc Status(StatusRequest) returns (StatusResponse); +} From de01c703c929ab1b07810f5c4a6eb07c981159b8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Apr 2016 22:50:47 +0200 Subject: [PATCH 1416/2266] [refactor] Add need_downlink to Broker proto --- api/broker/broker.pb.go | 159 +++++++++++++++++++++++++--------------- api/broker/broker.proto | 1 + 2 files changed, 99 insertions(+), 61 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 952943954..0adb74697 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -138,6 +138,7 @@ type DeduplicatedUplinkMessage struct { ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` + NeedDownlink bool `protobuf:"varint,32,opt,name=need_downlink,json=needDownlink,proto3" json:"need_downlink,omitempty"` } func (m *DeduplicatedUplinkMessage) Reset() { *m = DeduplicatedUplinkMessage{} } @@ -1046,6 +1047,18 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { } i += n6 } + if m.NeedDownlink { + data[i] = 0x80 + i++ + data[i] = 0x2 + i++ + if m.NeedDownlink { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } return i, nil } @@ -1588,6 +1601,9 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { l = m.ResponseTemplate.Size() n += 2 + l + sovBroker(uint64(l)) } + if m.NeedDownlink { + n += 3 + } return n } @@ -2506,6 +2522,26 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 32: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field NeedDownlink", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.NeedDownlink = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBroker(data[iNdEx:]) @@ -3887,65 +3923,66 @@ var ( ) var fileDescriptorBroker = []byte{ - // 953 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0x26, 0x89, 0x48, 0xdb, 0xd3, 0xa4, 0x49, 0xa7, 0x4d, 0xeb, 0x35, 0x55, 0x5b, 0x0c, 0x42, - 0xe5, 0x67, 0x13, 0x28, 0x82, 0x6a, 0x85, 0x00, 0x65, 0x29, 0xe2, 0x47, 0x0a, 0xac, 0xbc, 0xed, - 0x75, 0x34, 0xb1, 0x87, 0x64, 0xd4, 0xd4, 0x36, 0x9e, 0x71, 0xcb, 0xbe, 0x06, 0xe2, 0x82, 0x5b, - 0xde, 0x80, 0xc7, 0x80, 0x3b, 0x1e, 0x01, 0xc1, 0x03, 0x70, 0xcf, 0x15, 0xe3, 0xf9, 0xf1, 0x4f, - 0x1b, 0xb3, 0xa1, 0x88, 0x0b, 0xa4, 0xbd, 0xb0, 0x92, 0x39, 0xbf, 0x73, 0xce, 0x77, 0x3e, 0x1f, - 0xc3, 0xc9, 0x94, 0xf2, 0x59, 0x32, 0xe9, 0x7b, 0xe1, 0xe5, 0xe0, 0x6c, 0x46, 0xce, 0x66, 0x34, - 0x98, 0xb2, 0x2f, 0x08, 0xbf, 0x0e, 0xe3, 0x8b, 0x01, 0xe7, 0xc1, 0x00, 0x47, 0x74, 0x30, 0x89, - 0xc3, 0x0b, 0x12, 0xeb, 0x9f, 0x7e, 0x14, 0x87, 0x3c, 0x44, 0x4d, 0x75, 0xb2, 0xef, 0x2f, 0x13, - 0x40, 0x3c, 0xca, 0xcd, 0x7e, 0x6f, 0x19, 0x73, 0x69, 0xea, 0x85, 0xf3, 0xec, 0x8f, 0x76, 0x7e, - 0xb0, 0x8c, 0xf3, 0x14, 0x73, 0x72, 0x8d, 0x9f, 0x98, 0x5f, 0xe5, 0xea, 0x4c, 0x60, 0xe3, 0x34, - 0xbc, 0x0e, 0xe6, 0x34, 0xb8, 0xf8, 0x32, 0xe2, 0x34, 0x0c, 0xd0, 0x3e, 0x00, 0xf5, 0x49, 0xc0, - 0xe9, 0x57, 0x94, 0xc4, 0x56, 0xed, 0xb0, 0x76, 0xb4, 0xe6, 0x16, 0x24, 0x68, 0x1b, 0x9e, 0x67, - 0x5e, 0x18, 0x13, 0xab, 0x2e, 0x54, 0x6d, 0x57, 0x1d, 0x90, 0x0d, 0xab, 0x3e, 0xc1, 0xbe, 0x88, - 0x43, 0xac, 0x86, 0x50, 0x34, 0xdc, 0xec, 0xec, 0xfc, 0x51, 0x83, 0xf6, 0x79, 0x94, 0xa6, 0x18, - 0x11, 0xc6, 0xf0, 0x94, 0x20, 0x0b, 0x56, 0x22, 0xfc, 0x64, 0x1e, 0x62, 0x5f, 0x26, 0x68, 0xb9, - 0xe6, 0x88, 0x86, 0xb0, 0x69, 0x8a, 0x1b, 0x5f, 0x12, 0x8e, 0x7d, 0xcc, 0xb1, 0xb5, 0x2e, 0x6c, - 0xd6, 0x8f, 0xb7, 0xfb, 0x59, 0xd9, 0xee, 0x37, 0x23, 0xad, 0x73, 0xbb, 0x46, 0x68, 0x24, 0xe8, - 0x03, 0xe8, 0xea, 0x1a, 0xf3, 0x08, 0x2d, 0x19, 0x61, 0xab, 0x6f, 0x8a, 0x2f, 0x04, 0xe8, 0x68, - 0x59, 0xe6, 0x3f, 0x84, 0xae, 0xaf, 0x5b, 0x32, 0x0e, 0x65, 0x4f, 0x98, 0xd5, 0x3b, 0x6c, 0x08, - 0xff, 0x9d, 0xbe, 0x86, 0xba, 0xdc, 0x32, 0xb7, 0xe3, 0x97, 0xce, 0xcc, 0x99, 0x43, 0xc7, 0x98, - 0x3c, 0xbd, 0xe4, 0x0f, 0xa1, 0x73, 0x23, 0x9f, 0x2e, 0xb8, 0x2a, 0xdd, 0x46, 0x39, 0x9d, 0x93, - 0x80, 0x75, 0x4a, 0xae, 0xa8, 0x47, 0x86, 0x1e, 0xa7, 0x57, 0x58, 0xda, 0x10, 0x16, 0x89, 0x8b, - 0xfc, 0xa7, 0x69, 0x7f, 0xac, 0xc3, 0xbd, 0x53, 0xe2, 0x27, 0x02, 0x59, 0x4f, 0xb4, 0xd0, 0x5f, - 0x16, 0xe2, 0x5d, 0x58, 0xf1, 0xc9, 0xd5, 0x98, 0x24, 0x54, 0x26, 0x6c, 0xb9, 0x4d, 0x71, 0xfc, - 0x38, 0xa1, 0xa9, 0x02, 0x47, 0x91, 0x54, 0xb4, 0x94, 0x42, 0x1c, 0x53, 0xc5, 0xc2, 0xa1, 0xe8, - 0xfd, 0xeb, 0xa1, 0xd8, 0x91, 0xa0, 0x2e, 0x37, 0x14, 0xa7, 0xb0, 0x19, 0xeb, 0x9e, 0x8e, 0x39, - 0xb9, 0x8c, 0xe6, 0x42, 0x6f, 0x1d, 0xc8, 0x2b, 0xec, 0xde, 0xec, 0x97, 0x6e, 0x81, 0xdb, 0x35, - 0x1e, 0x67, 0xda, 0xc1, 0xf9, 0xb3, 0x0e, 0xbb, 0xb7, 0xa1, 0xfa, 0x3a, 0x21, 0x8c, 0xff, 0x3f, - 0x1a, 0xb6, 0x3c, 0x8b, 0x46, 0xb0, 0x85, 0xb3, 0x1a, 0xf3, 0x10, 0xbb, 0x32, 0xc4, 0x5e, 0x7e, - 0x89, 0xbc, 0x11, 0x59, 0x2c, 0x84, 0x6f, 0xc9, 0x16, 0x92, 0xf2, 0xe0, 0x9f, 0x91, 0xf2, 0xdb, - 0x06, 0xbc, 0x54, 0x9c, 0xd7, 0x67, 0x40, 0xdc, 0x05, 0x88, 0x51, 0x35, 0x11, 0x0e, 0x33, 0x24, - 0x2a, 0xde, 0x46, 0x0b, 0x18, 0x81, 0xa0, 0xfb, 0x38, 0x99, 0x30, 0x2f, 0xa6, 0x13, 0xa2, 0x01, - 0x70, 0x7a, 0xb0, 0x35, 0x8c, 0x14, 0x4a, 0x29, 0x70, 0x46, 0xfc, 0x16, 0x6c, 0x97, 0xc5, 0xfa, - 0x15, 0x77, 0x0f, 0x56, 0x75, 0xf3, 0x99, 0x00, 0xac, 0x21, 0xd6, 0xd5, 0x8a, 0xea, 0x3e, 0x73, - 0xde, 0x01, 0xdb, 0x25, 0x53, 0xca, 0x38, 0x89, 0x0b, 0xae, 0x06, 0xe8, 0x02, 0x6a, 0x6a, 0xcd, - 0x69, 0xd4, 0x9c, 0x13, 0xd8, 0x3b, 0x0f, 0xe2, 0x3b, 0x38, 0x76, 0xa0, 0xfd, 0x98, 0x63, 0x9e, - 0x64, 0x77, 0xfe, 0xb9, 0x01, 0x1b, 0x46, 0xa2, 0xaf, 0xeb, 0x40, 0x33, 0x91, 0x6f, 0x4a, 0xe9, - 0xbb, 0x7e, 0x0c, 0xfd, 0xf4, 0x2b, 0xc0, 0x15, 0xcd, 0x60, 0xae, 0xd6, 0xa0, 0x01, 0xb4, 0xd5, - 0xbf, 0x71, 0x12, 0x50, 0x11, 0x49, 0xee, 0xda, 0xb2, 0x69, 0x4b, 0x19, 0x9c, 0x4b, 0x3d, 0x7a, - 0x45, 0xac, 0x5f, 0x3d, 0xee, 0xfa, 0x2d, 0x5e, 0xb4, 0xcd, 0x74, 0xe8, 0x0d, 0x58, 0xcf, 0x31, - 0x65, 0x7a, 0x12, 0x8b, 0xa6, 0x45, 0x35, 0x7a, 0x00, 0x85, 0x09, 0x60, 0xe6, 0x2e, 0x3b, 0xb7, - 0x9c, 0x36, 0x0b, 0x56, 0xfa, 0x42, 0xef, 0xc3, 0x76, 0xd1, 0x15, 0x7b, 0x1e, 0x89, 0x04, 0xe7, - 0xf4, 0xd8, 0x15, 0x9d, 0x0b, 0xd3, 0xc9, 0x86, 0xda, 0x0c, 0xbd, 0x0b, 0x6d, 0x3f, 0xa3, 0x6a, - 0xba, 0x9a, 0xd4, 0x84, 0x75, 0xa5, 0xdf, 0x23, 0x12, 0x7b, 0xe9, 0xe7, 0xc8, 0x5c, 0x78, 0x97, - 0xcd, 0xd0, 0xeb, 0xb0, 0xe9, 0x85, 0x41, 0x40, 0x3c, 0x11, 0x64, 0x1c, 0x87, 0x89, 0xc0, 0x8f, - 0x59, 0xaf, 0xca, 0x0f, 0x95, 0x6e, 0xa6, 0x70, 0x95, 0x1c, 0xdd, 0x07, 0x94, 0x1b, 0xcf, 0x70, - 0xe0, 0xcf, 0x53, 0xeb, 0xd7, 0xa4, 0x75, 0x1e, 0xe6, 0x53, 0xad, 0x38, 0xfe, 0xae, 0x0e, 0xcd, - 0x87, 0x72, 0xc0, 0xc5, 0xee, 0x5c, 0x1b, 0x32, 0x16, 0x7a, 0x54, 0x54, 0x80, 0x7a, 0x66, 0xec, - 0x4b, 0x0b, 0xd0, 0xae, 0x5a, 0x0b, 0x47, 0xb5, 0x37, 0x6b, 0xe8, 0x73, 0x58, 0xcb, 0xc6, 0x1e, - 0x59, 0xc6, 0xf2, 0x26, 0x13, 0xec, 0x17, 0x73, 0x46, 0x55, 0xec, 0x59, 0x11, 0xab, 0x0f, 0x2b, - 0x8f, 0x92, 0xc9, 0x9c, 0xb2, 0x19, 0xaa, 0xca, 0x69, 0xaf, 0xca, 0xc6, 0x0d, 0xbd, 0x8b, 0xa3, - 0x9a, 0x60, 0xf0, 0xaa, 0xa6, 0x26, 0x41, 0x07, 0xd5, 0x94, 0x55, 0x37, 0x78, 0x2a, 0xa7, 0x8f, - 0x7f, 0xa8, 0x43, 0x5b, 0xb5, 0x65, 0x84, 0x03, 0x91, 0x2b, 0x46, 0x9f, 0x41, 0xab, 0x48, 0x54, - 0xf4, 0x82, 0x89, 0xb1, 0x80, 0xd5, 0xf6, 0xde, 0x62, 0xa5, 0x26, 0xcb, 0x47, 0xb0, 0xb5, 0x80, - 0xc0, 0xc8, 0x31, 0x4e, 0xd5, 0xec, 0xce, 0x4b, 0x46, 0x9f, 0x40, 0x6f, 0x21, 0x9d, 0xd1, 0xcb, - 0x19, 0x72, 0x7f, 0xc3, 0xf6, 0x42, 0xa0, 0x13, 0x68, 0x2a, 0x32, 0xe7, 0x98, 0x97, 0xe8, 0x6e, - 0xef, 0xdc, 0x14, 0xab, 0x32, 0x1e, 0x76, 0x7f, 0xfa, 0x6d, 0xbf, 0xf6, 0x8b, 0x78, 0x7e, 0x15, - 0xcf, 0xf7, 0xbf, 0xef, 0x3f, 0x37, 0x69, 0xca, 0xf7, 0xee, 0xdb, 0x7f, 0x05, 0x00, 0x00, 0xff, - 0xff, 0xb2, 0x6a, 0xc3, 0xc1, 0x68, 0x0c, 0x00, 0x00, + // 971 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4b, 0x6f, 0xeb, 0x44, + 0x14, 0x26, 0x89, 0x48, 0xd3, 0x93, 0xa4, 0x49, 0xa7, 0x4d, 0xeb, 0x6b, 0xaa, 0xb6, 0xf8, 0x22, + 0x54, 0x1e, 0x37, 0x81, 0x22, 0xa8, 0xae, 0x10, 0xa0, 0x5c, 0x8a, 0x78, 0x48, 0x81, 0x2b, 0xdf, + 0x76, 0x1d, 0x4d, 0xec, 0x21, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x72, 0xff, 0x06, 0x62, 0xc1, + 0x96, 0x7f, 0x03, 0x3b, 0x36, 0xec, 0x11, 0xfc, 0x00, 0xf6, 0xac, 0x18, 0xcf, 0xc3, 0x76, 0x5e, + 0xdc, 0x50, 0xc4, 0x02, 0x89, 0x85, 0x65, 0xcf, 0x39, 0xe7, 0x3b, 0x33, 0x73, 0xbe, 0xf3, 0xcd, + 0x18, 0xce, 0xc6, 0x94, 0x4f, 0x92, 0x51, 0xd7, 0x0b, 0xaf, 0x7b, 0x17, 0x13, 0x72, 0x31, 0xa1, + 0xc1, 0x98, 0x7d, 0x4e, 0xf8, 0x6d, 0x18, 0x5f, 0xf5, 0x38, 0x0f, 0x7a, 0x38, 0xa2, 0xbd, 0x51, + 0x1c, 0x5e, 0x91, 0x58, 0xbf, 0xba, 0x51, 0x1c, 0xf2, 0x10, 0x55, 0xd5, 0xc8, 0x7e, 0xb0, 0x4e, + 0x02, 0xf1, 0x28, 0x98, 0xfd, 0xee, 0x3a, 0xe1, 0x32, 0xd4, 0x0b, 0xa7, 0xd9, 0x87, 0x06, 0x3f, + 0x5c, 0x07, 0x3c, 0xc6, 0x9c, 0xdc, 0xe2, 0xa7, 0xe6, 0xad, 0xa0, 0xce, 0x08, 0xb6, 0xce, 0xc3, + 0xdb, 0x60, 0x4a, 0x83, 0xab, 0x2f, 0x22, 0x4e, 0xc3, 0x00, 0x1d, 0x02, 0x50, 0x9f, 0x04, 0x9c, + 0x7e, 0x49, 0x49, 0x6c, 0x95, 0x8e, 0x4b, 0x27, 0x9b, 0x6e, 0xc1, 0x82, 0x76, 0xe1, 0x79, 0xe6, + 0x85, 0x31, 0xb1, 0xca, 0xc2, 0xd5, 0x74, 0xd5, 0x00, 0xd9, 0x50, 0xf3, 0x09, 0xf6, 0x45, 0x1e, + 0x62, 0x55, 0x84, 0xa3, 0xe2, 0x66, 0x63, 0xe7, 0xf7, 0x12, 0x34, 0x2f, 0xa3, 0x74, 0x8a, 0x01, + 0x61, 0x0c, 0x8f, 0x09, 0xb2, 0x60, 0x23, 0xc2, 0x4f, 0xa7, 0x21, 0xf6, 0xe5, 0x04, 0x0d, 0xd7, + 0x0c, 0x51, 0x1f, 0xb6, 0xcd, 0xe6, 0x86, 0xd7, 0x84, 0x63, 0x1f, 0x73, 0x6c, 0xd5, 0x45, 0x4c, + 0xfd, 0x74, 0xb7, 0x9b, 0x6d, 0xdb, 0xfd, 0x7a, 0xa0, 0x7d, 0x6e, 0xdb, 0x18, 0x8d, 0x05, 0xbd, + 0x0f, 0x6d, 0xbd, 0xc7, 0x3c, 0x43, 0x43, 0x66, 0xd8, 0xe9, 0x9a, 0xcd, 0x17, 0x12, 0xb4, 0xb4, + 0x2d, 0xc3, 0xf7, 0xa1, 0xed, 0xeb, 0x92, 0x0c, 0x43, 0x59, 0x13, 0x66, 0x75, 0x8e, 0x2b, 0x02, + 0xbf, 0xd7, 0xd5, 0x54, 0xcf, 0x96, 0xcc, 0x6d, 0xf9, 0x33, 0x63, 0xe6, 0x4c, 0xa1, 0x65, 0x42, + 0x9e, 0xbd, 0xe5, 0x0f, 0xa0, 0x35, 0x37, 0x9f, 0xde, 0xf0, 0xaa, 0xe9, 0xb6, 0x66, 0xa7, 0x73, + 0x12, 0xb0, 0xce, 0xc9, 0x0d, 0xf5, 0x48, 0xdf, 0xe3, 0xf4, 0x06, 0xcb, 0x18, 0xc2, 0x22, 0xb1, + 0x90, 0x7f, 0x75, 0xda, 0x9f, 0xcb, 0x70, 0xef, 0x9c, 0xf8, 0x89, 0x60, 0xd6, 0x13, 0x25, 0xf4, + 0xd7, 0xa5, 0x78, 0x1f, 0x36, 0x7c, 0x72, 0x33, 0x24, 0x09, 0x95, 0x13, 0x36, 0xdc, 0xaa, 0x18, + 0x7e, 0x94, 0xd0, 0xd4, 0x81, 0xa3, 0x48, 0x3a, 0x1a, 0xca, 0x21, 0x86, 0xa9, 0x63, 0x69, 0x53, + 0x74, 0xfe, 0x71, 0x53, 0xec, 0x49, 0x52, 0xd7, 0x6b, 0x8a, 0x73, 0xd8, 0x8e, 0x75, 0x4d, 0x87, + 0x9c, 0x5c, 0x47, 0x53, 0xe1, 0xb7, 0x8e, 0xe4, 0x12, 0xf6, 0xe7, 0xeb, 0xa5, 0x4b, 0xe0, 0xb6, + 0x0d, 0xe2, 0x42, 0x03, 0xd0, 0x7d, 0x68, 0x06, 0x84, 0xf8, 0x43, 0x53, 0x49, 0xeb, 0x58, 0x64, + 0xa8, 0xb9, 0x8d, 0xd4, 0x68, 0xd0, 0xce, 0x1f, 0x65, 0xd8, 0x5f, 0xe4, 0xf3, 0xab, 0x84, 0x30, + 0xfe, 0xdf, 0xa8, 0xea, 0xfa, 0x52, 0x1b, 0xc0, 0x0e, 0xce, 0xf6, 0x98, 0xa7, 0xd8, 0x97, 0x29, + 0x0e, 0xf2, 0x45, 0xe4, 0x85, 0xc8, 0x72, 0x21, 0xbc, 0x60, 0x5b, 0xaa, 0xdc, 0xa3, 0xbf, 0xa7, + 0xdc, 0x6f, 0x2a, 0x70, 0xbf, 0xd8, 0xd4, 0xff, 0x13, 0x71, 0x17, 0x22, 0x06, 0xab, 0xd5, 0x72, + 0x9c, 0x31, 0xb1, 0xe2, 0xc8, 0x5a, 0x94, 0x8d, 0x83, 0xa0, 0xfd, 0x24, 0x19, 0x31, 0x2f, 0xa6, + 0x23, 0xa2, 0x09, 0x70, 0x3a, 0xb0, 0xd3, 0x8f, 0x14, 0x4b, 0x29, 0x71, 0xc6, 0xfc, 0x26, 0xec, + 0xce, 0x9a, 0xf5, 0x39, 0x78, 0x0f, 0x6a, 0xba, 0xf8, 0x4c, 0x10, 0x56, 0x11, 0x77, 0xda, 0x86, + 0xaa, 0x3e, 0x73, 0xde, 0x06, 0xdb, 0x25, 0x63, 0xca, 0x38, 0x89, 0x0b, 0x50, 0x43, 0x74, 0x81, + 0x35, 0x75, 0x17, 0x6a, 0xd6, 0x9c, 0x33, 0x38, 0xb8, 0x0c, 0xe2, 0x3b, 0x00, 0x5b, 0xd0, 0x7c, + 0xc2, 0x31, 0x4f, 0xb2, 0x35, 0xff, 0x58, 0x81, 0x2d, 0x63, 0xd1, 0xcb, 0x75, 0xa0, 0x9a, 0xc8, + 0xe3, 0x54, 0x62, 0xeb, 0xa7, 0xd0, 0x4d, 0x7f, 0x15, 0x5c, 0x51, 0x0c, 0xe6, 0x6a, 0x0f, 0xea, + 0x41, 0x53, 0x7d, 0x0d, 0x93, 0x80, 0x8a, 0x4c, 0xf2, 0x42, 0x9e, 0x0d, 0x6d, 0xa8, 0x80, 0x4b, + 0xe9, 0x47, 0x2f, 0x8b, 0x3b, 0xda, 0x1c, 0x3c, 0xf5, 0x85, 0xd8, 0xcc, 0x87, 0x5e, 0x87, 0x7a, + 0xce, 0x29, 0xd3, 0x9d, 0x58, 0x0c, 0x2d, 0xba, 0xd1, 0x43, 0x28, 0x74, 0x00, 0x33, 0x6b, 0xd9, + 0x5b, 0x00, 0x6d, 0x17, 0xa2, 0xf4, 0x82, 0xde, 0x83, 0xdd, 0x22, 0x14, 0x7b, 0x1e, 0x89, 0x84, + 0xe6, 0x74, 0xdb, 0x15, 0xc1, 0x85, 0xee, 0x64, 0x7d, 0x1d, 0x86, 0xde, 0x81, 0xa6, 0x9f, 0x49, + 0x35, 0xbd, 0xbf, 0x54, 0x87, 0xb5, 0x25, 0xee, 0x31, 0x89, 0xbd, 0xf4, 0x9f, 0x65, 0x2a, 0xd0, + 0xb3, 0x61, 0xe8, 0x35, 0xd8, 0xf6, 0xc2, 0x20, 0x20, 0x9e, 0x48, 0x32, 0x8c, 0xc3, 0x44, 0xf0, + 0xc7, 0xac, 0x57, 0xe4, 0xdf, 0x4c, 0x3b, 0x73, 0xb8, 0xca, 0x8e, 0x1e, 0x00, 0xca, 0x83, 0x27, + 0x38, 0xf0, 0xa7, 0x69, 0xf4, 0xab, 0x32, 0x3a, 0x4f, 0xf3, 0x89, 0x76, 0x9c, 0x7e, 0x5b, 0x86, + 0xea, 0x23, 0xd9, 0xe0, 0xe2, 0x82, 0xdd, 0xec, 0x33, 0x16, 0x7a, 0x34, 0x3d, 0xf9, 0x3b, 0xa6, + 0xed, 0x67, 0x6e, 0x49, 0x7b, 0xd5, 0xdd, 0x71, 0x52, 0x7a, 0xa3, 0x84, 0x3e, 0x83, 0xcd, 0xac, + 0xed, 0x91, 0x65, 0x22, 0xe7, 0x95, 0x60, 0xbf, 0x98, 0x2b, 0x6a, 0xc5, 0x65, 0x2c, 0x72, 0x75, + 0x61, 0xe3, 0x71, 0x32, 0x9a, 0x52, 0x36, 0x41, 0xab, 0xe6, 0xb4, 0x6b, 0xb2, 0x70, 0x7d, 0xef, + 0xea, 0xa4, 0x24, 0x14, 0x5c, 0xd3, 0xd2, 0x24, 0xe8, 0x68, 0xb5, 0x64, 0xd5, 0x0a, 0x9e, 0xa9, + 0xe9, 0xd3, 0xef, 0xcb, 0xd0, 0x54, 0x65, 0x19, 0xe0, 0x40, 0xcc, 0x15, 0xa3, 0x4f, 0xa1, 0x51, + 0x14, 0x2a, 0x7a, 0xc1, 0xe4, 0x58, 0xa2, 0x6a, 0xfb, 0x60, 0xb9, 0x53, 0x8b, 0xe5, 0x43, 0xd8, + 0x59, 0x22, 0x60, 0xe4, 0x18, 0xd0, 0x6a, 0x75, 0xe7, 0x5b, 0x46, 0x1f, 0x43, 0x67, 0xa9, 0x9c, + 0xd1, 0x4b, 0x19, 0x73, 0x7f, 0xa1, 0xf6, 0x42, 0xa2, 0x33, 0xa8, 0x2a, 0x31, 0xe7, 0x9c, 0xcf, + 0xc8, 0xdd, 0xde, 0x9b, 0x37, 0xab, 0x6d, 0x3c, 0x6a, 0xff, 0xf0, 0xeb, 0x61, 0xe9, 0x27, 0xf1, + 0xfc, 0x22, 0x9e, 0xef, 0x7e, 0x3b, 0x7c, 0x6e, 0x54, 0x95, 0xe7, 0xee, 0x5b, 0x7f, 0x06, 0x00, + 0x00, 0xff, 0xff, 0xcd, 0xe7, 0xb5, 0x29, 0x8d, 0x0c, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index aa86b0614..c4f8987e4 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -40,6 +40,7 @@ message DeduplicatedUplinkMessage { protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; DownlinkMessage response_template = 31; + bool need_downlink = 32; } // received from the Router From 20341b48d63ca8dc392ed91fa3d708c17d309807 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 14:57:52 +0200 Subject: [PATCH 1417/2266] [refactor] use ParseHEX from core/types --- core/types/dev_addr.go | 2 +- core/types/device_type.go | 27 +-------------------------- core/types/eui.go | 2 +- core/types/keys.go | 2 +- core/types/parse_hex.go | 27 +++++++++++++++++++++++++++ ttnctl/cmd/downlink.go | 2 +- ttnctl/cmd/uplink.go | 3 +-- ttnctl/util/parse.go | 30 ------------------------------ 8 files changed, 33 insertions(+), 62 deletions(-) create mode 100644 core/types/parse_hex.go delete mode 100644 ttnctl/util/parse.go diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index afeb9c7ba..53e183b09 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -11,7 +11,7 @@ type DevAddr [4]byte // ParseDevAddr parses a 32-bit hex-encoded string to a DevAddr func ParseDevAddr(input string) (addr DevAddr, err error) { - bytes, err := parseHEX(input, 4) + bytes, err := ParseHEX(input, 4) if err != nil { return } diff --git a/core/types/device_type.go b/core/types/device_type.go index 6465797bd..aa1567ea6 100644 --- a/core/types/device_type.go +++ b/core/types/device_type.go @@ -1,11 +1,6 @@ package types -import ( - "encoding/hex" - "errors" - "fmt" - "regexp" -) +import "errors" // DeviceType is the type of a LoRaWAN device. type DeviceType byte @@ -98,23 +93,3 @@ func (devType DeviceType) Marshal() ([]byte, error) { func (devType *DeviceType) Unmarshal(data []byte) error { return devType.UnmarshalBinary(data) } - -// parseHEX parses a string "input" to a byteslice with length "length". -func parseHEX(input string, length int) ([]byte, error) { - pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) - if err != nil { - return nil, fmt.Errorf("Invalid pattern") - } - - valid := pattern.MatchString(input) - if !valid { - return nil, fmt.Errorf("Invalid input: %s", input) - } - - slice, err := hex.DecodeString(input) - if err != nil { - return nil, fmt.Errorf("Could not decode input: %s", input) - } - - return slice, nil -} diff --git a/core/types/eui.go b/core/types/eui.go index ecbe457ec..e94544e61 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -20,7 +20,7 @@ type GatewayEUI EUI64 // ParseEUI64 parses a 64-bit hex-encoded string to an EUI64. func ParseEUI64(input string) (eui EUI64, err error) { - bytes, err := parseHEX(input, 8) + bytes, err := ParseHEX(input, 8) if err != nil { return } diff --git a/core/types/keys.go b/core/types/keys.go index 48817d96a..bdaa8df54 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -20,7 +20,7 @@ type AppSKey AES128Key // ParseAES128Key parses a 128-bit hex-encoded string to an AES128Key func ParseAES128Key(input string) (key AES128Key, err error) { - bytes, err := parseHEX(input, 16) + bytes, err := ParseHEX(input, 16) if err != nil { return } diff --git a/core/types/parse_hex.go b/core/types/parse_hex.go new file mode 100644 index 000000000..2fe945afe --- /dev/null +++ b/core/types/parse_hex.go @@ -0,0 +1,27 @@ +package types + +import ( + "encoding/hex" + "fmt" + "regexp" +) + +// ParseHEX parses a string "input" to a byteslice with length "length". +func ParseHEX(input string, length int) ([]byte, error) { + pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) + if err != nil { + return nil, fmt.Errorf("Invalid pattern") + } + + valid := pattern.MatchString(input) + if !valid { + return nil, fmt.Errorf("Invalid input: %s", input) + } + + slice, err := hex.DecodeString(input) + if err != nil { + return nil, fmt.Errorf("Could not decode input: %s", input) + } + + return slice, nil +} diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index f1ae89675..f08cbc144 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -39,7 +39,7 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") payload = []byte(args[1]) } else { - payload, err = util.ParseHEX(args[1], len(args[1])) + payload, err = types.ParseHEX(args[1], len(args[1])) if err != nil { ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index e03114489..b2669a767 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -13,7 +13,6 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" @@ -73,7 +72,7 @@ var uplinkCmd = &cobra.Command{ ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} } else { - payload, err := util.ParseHEX(args[4], len(args[4])) + payload, err := types.ParseHEX(args[4], len(args[4])) if err != nil { ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") } diff --git a/ttnctl/util/parse.go b/ttnctl/util/parse.go deleted file mode 100644 index 32389cd37..000000000 --- a/ttnctl/util/parse.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "encoding/hex" - "fmt" - "regexp" -) - -// ParseHEX parses a hexidecimal string to a byte slice -func ParseHEX(input string, length int) ([]byte, error) { - pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length)) - if err != nil { - return nil, fmt.Errorf("Invalid pattern") - } - - valid := pattern.MatchString(input) - if !valid { - return nil, fmt.Errorf("Invalid input") - } - - devAddr, err := hex.DecodeString(input) - if err != nil { - return nil, fmt.Errorf("Could not decode input") - } - - return devAddr, nil -} From e8c03625e7527f1bb5e10abb58fe6c9ea87dbed0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 14:58:51 +0200 Subject: [PATCH 1418/2266] Update protos --- api/broker/broker.pb.go | 84 +++++++++++++++------- api/discovery/discovery.pb.go | 36 +++++++--- api/handler/handler.pb.go | 36 +++++++--- api/networkserver/networkserver.pb.go | 100 ++++++++++++++++++-------- api/router/router.pb.go | 100 ++++++++++++++++++-------- 5 files changed, 251 insertions(+), 105 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 0adb74697..c166b82c6 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -412,6 +412,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Broker service type BrokerClient interface { @@ -629,16 +633,22 @@ func (x *brokerPublishServer) Recv() (*DownlinkMessage, error) { return m, nil } -func _Broker_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Broker_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeviceActivationRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerServer).Activate(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerServer).Activate(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.Broker/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerServer).Activate(ctx, req.(*DeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) } var _Broker_serviceDesc = grpc.ServiceDesc{ @@ -744,52 +754,76 @@ func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { s.RegisterService(&_BrokerManager_serviceDesc, srv) } -func _BrokerManager_Applications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_Applications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ApplicationsRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).Applications(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).Applications(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/Applications", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).Applications(ctx, req.(*ApplicationsRequest)) + } + return interceptor(ctx, in, info, handler) } -func _BrokerManager_RegisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_RegisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RegisterApplicationRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).RegisterApplication(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).RegisterApplication(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/RegisterApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).RegisterApplication(ctx, req.(*RegisterApplicationRequest)) + } + return interceptor(ctx, in, info, handler) } -func _BrokerManager_UnregisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_UnregisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UnregisterApplicationRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).UnregisterApplication(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).UnregisterApplication(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/UnregisterApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).UnregisterApplication(ctx, req.(*UnregisterApplicationRequest)) + } + return interceptor(ctx, in, info, handler) } -func _BrokerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _BrokerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(BrokerManagerServer).Status(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(BrokerManagerServer).Status(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) } var _BrokerManager_serviceDesc = grpc.ServiceDesc{ diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index a383578c6..f5c26d339 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -84,6 +84,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Discovery service type DiscoveryClient interface { @@ -128,28 +132,40 @@ func RegisterDiscoveryServer(s *grpc.Server, srv DiscoveryServer) { s.RegisterService(&_Discovery_serviceDesc, srv) } -func _Discovery_Announce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Discovery_Announce_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(Announcement) if err := dec(in); err != nil { return nil, err } - out, err := srv.(DiscoveryServer).Announce(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(DiscoveryServer).Announce(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/Announce", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).Announce(ctx, req.(*Announcement)) + } + return interceptor(ctx, in, info, handler) } -func _Discovery_Discover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Discovery_Discover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DiscoverRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(DiscoveryServer).Discover(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(DiscoveryServer).Discover(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/Discover", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).Discover(ctx, req.(*DiscoverRequest)) + } + return interceptor(ctx, in, info, handler) } var _Discovery_serviceDesc = grpc.ServiceDesc{ diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index cac1ac300..19a95ee11 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -63,6 +63,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Handler service type HandlerClient interface { @@ -96,16 +100,22 @@ func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { s.RegisterService(&_Handler_serviceDesc, srv) } -func _Handler_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Handler_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(broker.DeduplicatedDeviceActivationRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerServer).Activate(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerServer).Activate(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.Handler/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).Activate(ctx, req.(*broker.DeduplicatedDeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) } var _Handler_serviceDesc = grpc.ServiceDesc{ @@ -153,16 +163,22 @@ func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { s.RegisterService(&_HandlerManager_serviceDesc, srv) } -func _HandlerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _HandlerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(HandlerManagerServer).Status(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(HandlerManagerServer).Status(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.HandlerManager/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerManagerServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) } var _HandlerManager_serviceDesc = grpc.ServiceDesc{ diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 3c48756e9..a618e69a4 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -141,6 +141,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for NetworkServer service type NetworkServerClient interface { @@ -215,52 +219,76 @@ func RegisterNetworkServerServer(s *grpc.Server, srv NetworkServerServer) { s.RegisterService(&_NetworkServer_serviceDesc, srv) } -func _NetworkServer_GetDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _NetworkServer_GetDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DevicesRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(NetworkServerServer).GetDevices(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(NetworkServerServer).GetDevices(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/GetDevices", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).GetDevices(ctx, req.(*DevicesRequest)) + } + return interceptor(ctx, in, info, handler) } -func _NetworkServer_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _NetworkServer_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(broker.DeviceActivationResponse) if err := dec(in); err != nil { return nil, err } - out, err := srv.(NetworkServerServer).Activate(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(NetworkServerServer).Activate(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).Activate(ctx, req.(*broker.DeviceActivationResponse)) + } + return interceptor(ctx, in, info, handler) } -func _NetworkServer_Uplink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _NetworkServer_Uplink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(broker.DeduplicatedUplinkMessage) if err := dec(in); err != nil { return nil, err } - out, err := srv.(NetworkServerServer).Uplink(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(NetworkServerServer).Uplink(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/Uplink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).Uplink(ctx, req.(*broker.DeduplicatedUplinkMessage)) + } + return interceptor(ctx, in, info, handler) } -func _NetworkServer_Downlink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _NetworkServer_Downlink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(broker.DownlinkMessage) if err := dec(in); err != nil { return nil, err } - out, err := srv.(NetworkServerServer).Downlink(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(NetworkServerServer).Downlink(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/Downlink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).Downlink(ctx, req.(*broker.DownlinkMessage)) + } + return interceptor(ctx, in, info, handler) } var _NetworkServer_serviceDesc = grpc.ServiceDesc{ @@ -331,28 +359,40 @@ func RegisterNetworkServerManagerServer(s *grpc.Server, srv NetworkServerManager s.RegisterService(&_NetworkServerManager_serviceDesc, srv) } -func _NetworkServerManager_RegisterDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _NetworkServerManager_RegisterDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RegisterDeviceRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(NetworkServerManagerServer).RegisterDevice(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(NetworkServerManagerServer).RegisterDevice(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServerManager/RegisterDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerManagerServer).RegisterDevice(ctx, req.(*RegisterDeviceRequest)) + } + return interceptor(ctx, in, info, handler) } -func _NetworkServerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _NetworkServerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(NetworkServerManagerServer).Status(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(NetworkServerManagerServer).Status(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServerManager/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerManagerServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) } var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ diff --git a/api/router/router.pb.go b/api/router/router.pb.go index e38aa778d..6f6f62463 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -313,6 +313,10 @@ func init() { var _ context.Context var _ grpc.ClientConn +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + // Client API for Router service type RouterClient interface { @@ -533,16 +537,22 @@ func (x *routerSubscribeServer) Send(m *DownlinkMessage) error { return x.ServerStream.SendMsg(m) } -func _Router_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _Router_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(DeviceActivationRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterServer).Activate(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterServer).Activate(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.Router/Activate", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterServer).Activate(ctx, req.(*DeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) } var _Router_serviceDesc = grpc.ServiceDesc{ @@ -660,64 +670,94 @@ func RegisterRouterManagerServer(s *grpc.Server, srv RouterManagerServer) { s.RegisterService(&_RouterManager_serviceDesc, srv) } -func _RouterManager_Gateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _RouterManager_Gateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GatewaysRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterManagerServer).Gateways(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterManagerServer).Gateways(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/Gateways", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).Gateways(ctx, req.(*GatewaysRequest)) + } + return interceptor(ctx, in, info, handler) } -func _RouterManager_RegisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _RouterManager_RegisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(RegisterGatewayRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterManagerServer).RegisterGateway(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterManagerServer).RegisterGateway(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/RegisterGateway", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).RegisterGateway(ctx, req.(*RegisterGatewayRequest)) + } + return interceptor(ctx, in, info, handler) } -func _RouterManager_UnregisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _RouterManager_UnregisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(UnregisterGatewayRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterManagerServer).UnregisterGateway(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterManagerServer).UnregisterGateway(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/UnregisterGateway", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).UnregisterGateway(ctx, req.(*UnregisterGatewayRequest)) + } + return interceptor(ctx, in, info, handler) } -func _RouterManager_GatewayStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _RouterManager_GatewayStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GatewayStatusRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterManagerServer).GatewayStatus(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterManagerServer).GatewayStatus(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/GatewayStatus", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).GatewayStatus(ctx, req.(*GatewayStatusRequest)) + } + return interceptor(ctx, in, info, handler) } -func _RouterManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) { +func _RouterManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } - out, err := srv.(RouterManagerServer).Status(ctx, in) - if err != nil { - return nil, err + if interceptor == nil { + return srv.(RouterManagerServer).Status(ctx, in) } - return out, nil + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/router.RouterManager/Status", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RouterManagerServer).Status(ctx, req.(*StatusRequest)) + } + return interceptor(ctx, in, info, handler) } var _RouterManager_serviceDesc = grpc.ServiceDesc{ From f01fb4cb02b6d76bf172567f57737d888f4a34c6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 16:48:20 +0200 Subject: [PATCH 1419/2266] [refactor] Remove gateway-specific status --- api/gateway/gateway.pb.go | 217 +++++------------------------- api/gateway/gateway.proto | 4 - api/gateway/semtech/semtech.pb.go | 150 +++------------------ api/gateway/semtech/semtech.proto | 6 - 4 files changed, 49 insertions(+), 328 deletions(-) diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 0ca1327e9..ea51aad90 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -248,9 +248,6 @@ type StatusMessage struct { RxOk uint32 `protobuf:"varint,42,opt,name=rx_ok,json=rxOk,proto3" json:"rx_ok,omitempty"` TxIn uint32 `protobuf:"varint,43,opt,name=tx_in,json=txIn,proto3" json:"tx_in,omitempty"` TxOk uint32 `protobuf:"varint,44,opt,name=tx_ok,json=txOk,proto3" json:"tx_ok,omitempty"` - // Types that are valid to be assigned to Gateway: - // *StatusMessage_Semtech - Gateway isStatusMessage_Gateway `protobuf_oneof:"gateway"` } func (m *StatusMessage) Reset() { *m = StatusMessage{} } @@ -258,25 +255,6 @@ func (m *StatusMessage) String() string { return proto.CompactTextStr func (*StatusMessage) ProtoMessage() {} func (*StatusMessage) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3} } -type isStatusMessage_Gateway interface { - isStatusMessage_Gateway() - MarshalTo([]byte) (int, error) - Size() int -} - -type StatusMessage_Semtech struct { - Semtech *semtech.StatusMessage `protobuf:"bytes,51,opt,name=semtech,oneof"` -} - -func (*StatusMessage_Semtech) isStatusMessage_Gateway() {} - -func (m *StatusMessage) GetGateway() isStatusMessage_Gateway { - if m != nil { - return m.Gateway - } - return nil -} - func (m *StatusMessage) GetGps() *GPSMetadata { if m != nil { return m.Gps @@ -284,68 +262,6 @@ func (m *StatusMessage) GetGps() *GPSMetadata { return nil } -func (m *StatusMessage) GetSemtech() *semtech.StatusMessage { - if x, ok := m.GetGateway().(*StatusMessage_Semtech); ok { - return x.Semtech - } - return nil -} - -// XXX_OneofFuncs is for the internal use of the proto package. -func (*StatusMessage) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _StatusMessage_OneofMarshaler, _StatusMessage_OneofUnmarshaler, _StatusMessage_OneofSizer, []interface{}{ - (*StatusMessage_Semtech)(nil), - } -} - -func _StatusMessage_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*StatusMessage) - // gateway - switch x := m.Gateway.(type) { - case *StatusMessage_Semtech: - _ = b.EncodeVarint(51<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Semtech); err != nil { - return err - } - case nil: - default: - return fmt.Errorf("StatusMessage.Gateway has unexpected type %T", x) - } - return nil -} - -func _StatusMessage_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*StatusMessage) - switch tag { - case 51: // gateway.semtech - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(semtech.StatusMessage) - err := b.DecodeMessage(msg) - m.Gateway = &StatusMessage_Semtech{msg} - return true, err - default: - return false, nil - } -} - -func _StatusMessage_OneofSizer(msg proto.Message) (n int) { - m := msg.(*StatusMessage) - // gateway - switch x := m.Gateway.(type) { - case *StatusMessage_Semtech: - s := proto.Size(x.Semtech) - n += proto.SizeVarint(51<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case nil: - default: - panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) - } - return n -} - func init() { proto.RegisterType((*GPSMetadata)(nil), "gateway.GPSMetadata") proto.RegisterType((*RxMetadata)(nil), "gateway.RxMetadata") @@ -598,32 +514,9 @@ func (m *StatusMessage) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintGateway(data, i, uint64(m.TxOk)) } - if m.Gateway != nil { - nn6, err := m.Gateway.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += nn6 - } return i, nil } -func (m *StatusMessage_Semtech) MarshalTo(data []byte) (int, error) { - i := 0 - if m.Semtech != nil { - data[i] = 0x9a - i++ - data[i] = 0x3 - i++ - i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) - n7, err := m.Semtech.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n7 - } - return i, nil -} func encodeFixed64Gateway(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -766,19 +659,6 @@ func (m *StatusMessage) Size() (n int) { if m.TxOk != 0 { n += 2 + sovGateway(uint64(m.TxOk)) } - if m.Gateway != nil { - n += m.Gateway.Size() - } - return n -} - -func (m *StatusMessage_Semtech) Size() (n int) { - var l int - _ = l - if m.Semtech != nil { - l = m.Semtech.Size() - n += 2 + l + sovGateway(uint64(l)) - } return n } @@ -1471,38 +1351,6 @@ func (m *StatusMessage) Unmarshal(data []byte) error { break } } - case 51: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGateway - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthGateway - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &semtech.StatusMessage{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - m.Gateway = &StatusMessage_Semtech{v} - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGateway(data[iNdEx:]) @@ -1630,37 +1478,36 @@ var ( ) var fileDescriptorGateway = []byte{ - // 499 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6e, 0x13, 0x31, - 0x10, 0x6e, 0xb2, 0x09, 0x25, 0x93, 0xa6, 0x54, 0x6e, 0x41, 0x56, 0x85, 0x4a, 0x15, 0x24, 0x54, - 0x7e, 0x94, 0x48, 0x2d, 0x17, 0x8e, 0x14, 0x21, 0xe0, 0x50, 0x40, 0x6e, 0xee, 0x95, 0xbb, 0x71, - 0x36, 0x56, 0xb2, 0xf6, 0x62, 0xcf, 0x2a, 0xe9, 0x91, 0x0b, 0x27, 0x1e, 0x80, 0x47, 0xe2, 0xc8, - 0x23, 0x20, 0x78, 0x11, 0x6c, 0x67, 0x7f, 0x42, 0x05, 0x42, 0x1c, 0x56, 0x9e, 0xf9, 0xfc, 0x79, - 0xe6, 0x1b, 0x7f, 0x5e, 0x78, 0x96, 0x48, 0x9c, 0xe6, 0x97, 0x83, 0x58, 0xa7, 0xc3, 0xd1, 0x54, - 0x8c, 0xa6, 0x52, 0x25, 0xf6, 0xad, 0xc0, 0x85, 0x36, 0xb3, 0x21, 0xa2, 0x1a, 0xf2, 0x4c, 0x0e, - 0x13, 0x8e, 0x62, 0xc1, 0xaf, 0xca, 0x75, 0x90, 0x19, 0x8d, 0x9a, 0x6c, 0x16, 0xe9, 0xfe, 0xf3, - 0xff, 0xa9, 0x61, 0x45, 0x8a, 0x22, 0x9e, 0x96, 0xeb, 0xaa, 0x56, 0x7f, 0x01, 0xdd, 0x57, 0xef, - 0xcf, 0xcf, 0x04, 0xf2, 0x31, 0x47, 0x4e, 0x08, 0xb4, 0x50, 0xa6, 0x82, 0x36, 0x0e, 0x1b, 0x47, - 0x11, 0x0b, 0x31, 0xd9, 0x87, 0x9b, 0x73, 0x8e, 0x12, 0xf3, 0xb1, 0xa0, 0x4d, 0x87, 0x37, 0x59, - 0x95, 0x93, 0xbb, 0xd0, 0x99, 0x6b, 0x95, 0xac, 0x36, 0xa3, 0xb0, 0x59, 0x03, 0xfe, 0x24, 0x9f, - 0x17, 0x27, 0x5b, 0x6e, 0xb3, 0xcd, 0xaa, 0xbc, 0xff, 0xb9, 0x01, 0xc0, 0x96, 0x55, 0x63, 0x57, - 0x68, 0x62, 0xc4, 0x87, 0x5c, 0xa8, 0xf8, 0x2a, 0x74, 0x6f, 0xb1, 0x1a, 0xf0, 0xb2, 0x8c, 0xb5, - 0xb2, 0x68, 0x1f, 0x62, 0xb2, 0x03, 0x91, 0x55, 0xa6, 0x68, 0xea, 0x43, 0x32, 0x84, 0xcd, 0x62, - 0x38, 0xda, 0x75, 0x68, 0xf7, 0x78, 0x77, 0x50, 0x0e, 0x5b, 0x77, 0x7a, 0xbd, 0xc1, 0x4a, 0xd6, - 0x69, 0x07, 0xca, 0xab, 0xec, 0x7f, 0x6a, 0xc0, 0xad, 0xd1, 0xf2, 0x85, 0x56, 0x13, 0x99, 0xe4, - 0xc6, 0x8d, 0xa7, 0xd5, 0x3f, 0x34, 0xed, 0x41, 0x3b, 0xd3, 0x0b, 0x61, 0x82, 0xa8, 0x36, 0x5b, - 0x25, 0xe4, 0xe9, 0x75, 0x0d, 0xb4, 0xd2, 0x70, 0xad, 0xfc, 0x5f, 0x84, 0x7c, 0x8c, 0xa0, 0x77, - 0x8e, 0x1c, 0x73, 0x7b, 0x26, 0xac, 0xe5, 0x49, 0xb8, 0x63, 0xef, 0x83, 0x45, 0x9e, 0x66, 0x41, - 0x46, 0x8f, 0xd5, 0x40, 0xe5, 0x58, 0x73, 0xcd, 0xb1, 0x6d, 0x68, 0xca, 0xcc, 0xf5, 0x8f, 0x8e, - 0x3a, 0xcc, 0x45, 0xde, 0x87, 0xcc, 0x59, 0x36, 0xd1, 0x26, 0xa5, 0x5b, 0x8e, 0xd7, 0x61, 0x55, - 0x4e, 0xee, 0x43, 0x2f, 0xd6, 0x0a, 0x79, 0x8c, 0x17, 0x22, 0xe5, 0x72, 0x4e, 0x7b, 0x81, 0xb0, - 0x55, 0x80, 0x2f, 0x3d, 0x46, 0x0e, 0xa1, 0x3b, 0x16, 0x36, 0x36, 0x32, 0xf3, 0xca, 0xe9, 0x76, - 0xa0, 0xac, 0x43, 0xe4, 0x01, 0x44, 0x49, 0x66, 0xe9, 0xed, 0x30, 0xf3, 0xde, 0xa0, 0x7c, 0xb0, - 0x6b, 0x6f, 0x8b, 0x79, 0x82, 0x77, 0xcd, 0x20, 0xd2, 0x7b, 0x61, 0x0c, 0x1f, 0x92, 0x5d, 0x68, - 0x9b, 0xe5, 0x85, 0x54, 0xf4, 0x61, 0xc0, 0x5a, 0x66, 0xf9, 0x46, 0x15, 0xa0, 0x9e, 0xd1, 0x47, - 0x25, 0xf8, 0x6e, 0xe6, 0x41, 0x0c, 0xcc, 0xc7, 0x2b, 0x10, 0x0b, 0x26, 0x06, 0xe6, 0x93, 0x12, - 0x74, 0xcc, 0xe3, 0xda, 0x85, 0x93, 0xa0, 0xe8, 0x4e, 0xe5, 0xc2, 0x6f, 0x77, 0xfb, 0x67, 0x0f, - 0x4e, 0x77, 0xbe, 0xfe, 0x38, 0x68, 0x7c, 0x73, 0xdf, 0x77, 0xf7, 0x7d, 0xf9, 0x79, 0xb0, 0x71, - 0x79, 0x23, 0xfc, 0x2d, 0x27, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0xaa, 0x72, 0x60, 0x00, 0xb6, - 0x03, 0x00, 0x00, + // 486 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x1b, 0x3b, 0xa1, 0x64, 0xd2, 0x94, 0x6a, 0x5b, 0xa4, 0x55, 0x85, 0x4a, 0x15, 0x24, + 0x54, 0xfe, 0x28, 0x96, 0x80, 0x0b, 0x47, 0x8a, 0x10, 0x70, 0x28, 0x45, 0xdb, 0xdc, 0xab, 0xad, + 0xb3, 0x71, 0x56, 0x89, 0x77, 0xcd, 0xee, 0x58, 0x49, 0x5f, 0x80, 0x13, 0x0f, 0xc0, 0xf3, 0x70, + 0xe2, 0xc8, 0x23, 0x20, 0x78, 0x11, 0x76, 0x37, 0xb6, 0x13, 0x55, 0x42, 0x88, 0x83, 0xe5, 0x99, + 0xdf, 0x7e, 0x9e, 0xf9, 0xc6, 0xb3, 0xf0, 0x32, 0x93, 0x38, 0x2d, 0xaf, 0x86, 0xa9, 0xce, 0x93, + 0xd1, 0x54, 0x8c, 0xa6, 0x52, 0x65, 0xf6, 0x83, 0xc0, 0x85, 0x36, 0xb3, 0x04, 0x51, 0x25, 0xbc, + 0x90, 0x49, 0xc6, 0x51, 0x2c, 0xf8, 0x75, 0xfd, 0x1e, 0x16, 0x46, 0xa3, 0x26, 0xdb, 0x55, 0x7a, + 0xf8, 0xea, 0x7f, 0x6a, 0x58, 0x91, 0xa3, 0x48, 0xa7, 0xf5, 0x7b, 0x55, 0x6b, 0xb0, 0x80, 0xde, + 0xdb, 0x8f, 0x17, 0x67, 0x02, 0xf9, 0x98, 0x23, 0x27, 0x04, 0xda, 0x28, 0x73, 0x41, 0x5b, 0xc7, + 0xad, 0x93, 0x98, 0x85, 0x98, 0x1c, 0xc2, 0xed, 0x39, 0x47, 0x89, 0xe5, 0x58, 0xd0, 0xc8, 0xf1, + 0x88, 0x35, 0x39, 0xb9, 0x07, 0xdd, 0xb9, 0x56, 0xd9, 0xea, 0x30, 0x0e, 0x87, 0x6b, 0xe0, 0xbf, + 0xe4, 0xf3, 0xea, 0xcb, 0xb6, 0x3b, 0xec, 0xb0, 0x26, 0x1f, 0x7c, 0x69, 0x01, 0xb0, 0x65, 0xd3, + 0xd8, 0x15, 0x9a, 0x18, 0xf1, 0xa9, 0x14, 0x2a, 0xbd, 0x0e, 0xdd, 0xdb, 0x6c, 0x0d, 0xbc, 0x2d, + 0x63, 0xad, 0xac, 0xda, 0x87, 0x98, 0xec, 0x41, 0x6c, 0x95, 0xa9, 0x9a, 0xfa, 0x90, 0x24, 0xb0, + 0x5d, 0x0d, 0x47, 0x7b, 0x8e, 0xf6, 0x9e, 0xed, 0x0f, 0xeb, 0x61, 0xd7, 0x9d, 0xde, 0x6d, 0xb1, + 0x5a, 0x75, 0xda, 0x85, 0xfa, 0x57, 0x0e, 0x3e, 0xb7, 0xe0, 0xce, 0x68, 0xf9, 0x5a, 0xab, 0x89, + 0xcc, 0x4a, 0xe3, 0xc6, 0xd3, 0xea, 0x1f, 0x9e, 0x0e, 0xa0, 0x53, 0xe8, 0x85, 0x30, 0xc1, 0x54, + 0x87, 0xad, 0x12, 0xf2, 0xe2, 0xa6, 0x07, 0xda, 0x78, 0xb8, 0x51, 0xfe, 0x2f, 0x46, 0xbe, 0x45, + 0xd0, 0xbf, 0x40, 0x8e, 0xa5, 0x3d, 0x13, 0xd6, 0xf2, 0x2c, 0xfc, 0x63, 0xbf, 0x07, 0x8b, 0x3c, + 0x2f, 0x82, 0x8d, 0x3e, 0x5b, 0x83, 0x66, 0x63, 0xd1, 0xc6, 0xc6, 0x76, 0x21, 0x92, 0x85, 0xeb, + 0x1f, 0x9f, 0x74, 0x99, 0x8b, 0xfc, 0x1e, 0x0a, 0xb7, 0xb2, 0x89, 0x36, 0x39, 0xdd, 0x71, 0xba, + 0x2e, 0x6b, 0x72, 0xf2, 0x00, 0xfa, 0xa9, 0x56, 0xc8, 0x53, 0xbc, 0x14, 0x39, 0x97, 0x73, 0xda, + 0x0f, 0x82, 0x9d, 0x0a, 0xbe, 0xf1, 0x8c, 0x1c, 0x43, 0x6f, 0x2c, 0x6c, 0x6a, 0x64, 0xe1, 0x9d, + 0xd3, 0xdd, 0x20, 0xd9, 0x44, 0xe4, 0x21, 0xc4, 0x59, 0x61, 0xe9, 0xdd, 0x30, 0xf3, 0xc1, 0xb0, + 0xbe, 0xb0, 0x1b, 0x77, 0x8b, 0x79, 0x81, 0xdf, 0x9a, 0x41, 0xa4, 0xf7, 0xc3, 0x18, 0x3e, 0x24, + 0xfb, 0xd0, 0x31, 0xcb, 0x4b, 0xa9, 0xe8, 0xa3, 0xc0, 0xda, 0x66, 0xf9, 0x5e, 0x55, 0x50, 0xcf, + 0xe8, 0xe3, 0x1a, 0x9e, 0xcf, 0x3c, 0xc4, 0xa0, 0x7c, 0xb2, 0x82, 0x58, 0x29, 0x31, 0x28, 0x9f, + 0xd6, 0xf0, 0x7c, 0x76, 0xba, 0xf7, 0xfd, 0xd7, 0x51, 0xeb, 0x87, 0x7b, 0x7e, 0xba, 0xe7, 0xeb, + 0xef, 0xa3, 0xad, 0xab, 0x5b, 0xe1, 0xba, 0x3f, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0x8a, 0xe6, + 0xe3, 0x4e, 0x77, 0x03, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 4c827d201..0926e0650 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -47,8 +47,4 @@ message StatusMessage { uint32 rx_ok = 42; uint32 tx_in = 43; uint32 tx_ok = 44; - - oneof gateway { - semtech.StatusMessage semtech = 51; - } } diff --git a/api/gateway/semtech/semtech.pb.go b/api/gateway/semtech/semtech.pb.go index 66edca998..7a1bddef1 100644 --- a/api/gateway/semtech/semtech.pb.go +++ b/api/gateway/semtech/semtech.pb.go @@ -11,14 +11,12 @@ It has these top-level messages: RxMetadata TxConfiguration - StatusMessage */ package semtech import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" import io "io" @@ -54,19 +52,9 @@ func (m *TxConfiguration) String() string { return proto.CompactTextS func (*TxConfiguration) ProtoMessage() {} func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{1} } -type StatusMessage struct { - Region lorawan.Region `protobuf:"varint,13,opt,name=region,proto3,enum=lorawan.Region" json:"region,omitempty"` -} - -func (m *StatusMessage) Reset() { *m = StatusMessage{} } -func (m *StatusMessage) String() string { return proto.CompactTextString(m) } -func (*StatusMessage) ProtoMessage() {} -func (*StatusMessage) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{2} } - func init() { proto.RegisterType((*RxMetadata)(nil), "semtech.RxMetadata") proto.RegisterType((*TxConfiguration)(nil), "semtech.TxConfiguration") - proto.RegisterType((*StatusMessage)(nil), "semtech.StatusMessage") } func (m *RxMetadata) Marshal() (data []byte, err error) { size := m.Size() @@ -148,29 +136,6 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusMessage) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *StatusMessage) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Region != 0 { - data[i] = 0x68 - i++ - i = encodeVarintSemtech(data, i, uint64(m.Region)) - } - return i, nil -} - func encodeFixed64Semtech(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -231,15 +196,6 @@ func (m *TxConfiguration) Size() (n int) { return n } -func (m *StatusMessage) Size() (n int) { - var l int - _ = l - if m.Region != 0 { - n += 1 + sovSemtech(uint64(m.Region)) - } - return n -} - func sovSemtech(x uint64) (n int) { for { n++ @@ -487,75 +443,6 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } return nil } -func (m *StatusMessage) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StatusMessage: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StatusMessage: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 13: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Region", wireType) - } - m.Region = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Region |= (lorawan.Region(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipSemtech(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthSemtech - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipSemtech(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -662,25 +549,22 @@ var ( ) var fileDescriptorSemtech = []byte{ - // 318 bytes of a gzipped FileDescriptorProto + // 267 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x90, 0x3d, 0x4e, 0xc3, 0x40, - 0x10, 0x85, 0x71, 0x93, 0x84, 0x85, 0x10, 0x64, 0x94, 0xc8, 0x20, 0x14, 0xa1, 0x34, 0x50, 0x65, - 0x25, 0x10, 0x12, 0x2d, 0x09, 0x0d, 0x45, 0x28, 0x4c, 0x7a, 0x6b, 0xe2, 0x8c, 0xed, 0x15, 0xf6, - 0xae, 0xd9, 0x1d, 0xe7, 0x87, 0x93, 0x70, 0x0e, 0x4e, 0x41, 0xc9, 0x11, 0x10, 0x5c, 0x04, 0x67, - 0x13, 0x03, 0x25, 0xa2, 0x18, 0x8d, 0xdf, 0xfb, 0xfc, 0xde, 0x4a, 0xc3, 0xae, 0x63, 0x41, 0x49, - 0x31, 0xe9, 0x87, 0x2a, 0xe3, 0xe3, 0x04, 0xc7, 0x89, 0x90, 0xb1, 0xb9, 0x43, 0x9a, 0x2b, 0xfd, - 0xc0, 0x89, 0x24, 0x87, 0x5c, 0xf0, 0x18, 0x08, 0xe7, 0xb0, 0xe4, 0x06, 0x33, 0xc2, 0x30, 0xa9, - 0x76, 0x3f, 0xd7, 0x8a, 0x94, 0x5b, 0xdf, 0xc8, 0xa3, 0xc1, 0x5f, 0xba, 0x6c, 0x26, 0x54, 0x29, - 0x4f, 0x95, 0x86, 0x39, 0xc8, 0x6a, 0xaf, 0xcb, 0x7a, 0x01, 0x63, 0xfe, 0x62, 0x84, 0x04, 0x53, - 0x20, 0x70, 0x8f, 0xd9, 0x36, 0x89, 0x0c, 0x0d, 0x41, 0x96, 0x7b, 0xce, 0x89, 0x73, 0xd6, 0xf4, - 0x7f, 0x0c, 0xf7, 0x90, 0x35, 0x74, 0x14, 0x84, 0x09, 0x08, 0xe9, 0xed, 0x58, 0x58, 0xd7, 0xd1, - 0x70, 0x25, 0x5d, 0x8f, 0xd5, 0x4b, 0x5f, 0x4a, 0x4c, 0xbd, 0xdd, 0x35, 0xd9, 0xc8, 0xde, 0x8b, - 0xc3, 0x5a, 0xe3, 0xc5, 0x50, 0xc9, 0x48, 0xc4, 0x85, 0x06, 0x12, 0x4a, 0xfe, 0xff, 0x99, 0x4b, - 0xd6, 0xc9, 0x55, 0x0a, 0x5a, 0x3c, 0xd9, 0xa2, 0x40, 0xc8, 0x19, 0x6a, 0x53, 0x7e, 0x79, 0xed, - 0xf2, 0xc7, 0x86, 0xdf, 0xfe, 0x4d, 0x6f, 0x2b, 0xe8, 0x72, 0x76, 0x10, 0x69, 0x7c, 0x2c, 0x50, - 0x86, 0xcb, 0x60, 0x8a, 0x33, 0x61, 0xb9, 0xd7, 0xb1, 0xe5, 0xee, 0x37, 0xba, 0xa9, 0x48, 0xef, - 0x8a, 0x35, 0xef, 0x09, 0xa8, 0x30, 0x23, 0x34, 0x06, 0x62, 0x74, 0x4f, 0x59, 0x4d, 0x63, 0xbc, - 0x0a, 0x35, 0xcb, 0xd0, 0xde, 0x79, 0xab, 0x5f, 0x9d, 0xd1, 0xb7, 0xb6, 0xbf, 0xc1, 0x83, 0xfd, - 0xd7, 0x8f, 0xae, 0xf3, 0x56, 0xce, 0x7b, 0x39, 0xcf, 0x9f, 0xdd, 0xad, 0x49, 0xcd, 0x1e, 0xfa, - 0xe2, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x2e, 0xca, 0xef, 0x4e, 0xfa, 0x01, 0x00, 0x00, + 0x10, 0x85, 0x71, 0x83, 0xc3, 0x02, 0x02, 0x2d, 0x4a, 0xb4, 0x48, 0x28, 0x42, 0xa9, 0xa8, 0xb2, + 0x05, 0xe2, 0x00, 0x10, 0x1a, 0x0a, 0x28, 0x2c, 0xf7, 0xd6, 0xc4, 0x19, 0xdb, 0x23, 0xe2, 0x5d, + 0xb3, 0x1e, 0xe7, 0x87, 0x93, 0x70, 0x0e, 0x4e, 0x41, 0xc9, 0x11, 0x10, 0x5c, 0x04, 0xb3, 0xc4, + 0x40, 0x4d, 0xf1, 0xb4, 0xfb, 0xde, 0x37, 0x7a, 0x23, 0x8d, 0xb8, 0xcc, 0x89, 0x8b, 0x66, 0x3a, + 0x4e, 0x6d, 0xa9, 0xe3, 0x02, 0xe3, 0x82, 0x4c, 0x5e, 0xdf, 0x21, 0x2f, 0xad, 0xbb, 0xd7, 0xcc, + 0x46, 0x43, 0x45, 0x3a, 0x07, 0xc6, 0x25, 0xac, 0x75, 0x8d, 0x25, 0x63, 0x5a, 0x74, 0xef, 0xb8, + 0x72, 0x96, 0xad, 0x0c, 0x37, 0x76, 0x94, 0x08, 0x11, 0xad, 0x6e, 0x91, 0x61, 0x06, 0x0c, 0xf2, + 0x44, 0xec, 0x30, 0x95, 0x58, 0x33, 0x94, 0x95, 0x0a, 0x4e, 0x83, 0xb3, 0xfd, 0xe8, 0x37, 0x90, + 0xc7, 0xa2, 0xe7, 0xb2, 0x24, 0x2d, 0x80, 0x8c, 0xda, 0xf5, 0x30, 0x74, 0xd9, 0xe4, 0xcb, 0x4a, + 0x25, 0xc2, 0x36, 0x37, 0x06, 0xe7, 0x6a, 0xef, 0x9b, 0x6c, 0xec, 0xe8, 0x39, 0x10, 0x07, 0xf1, + 0x6a, 0x62, 0x4d, 0x46, 0x79, 0xe3, 0x80, 0xc9, 0x9a, 0xff, 0xaf, 0xb9, 0x10, 0x83, 0xca, 0xce, + 0xc1, 0xd1, 0xa3, 0x2f, 0x4a, 0xc8, 0x2c, 0xd0, 0xd5, 0xed, 0x4f, 0xf5, 0xdb, 0xc1, 0x5e, 0xd4, + 0xff, 0x4b, 0x6f, 0x3a, 0x28, 0xb5, 0x38, 0xca, 0x1c, 0x3e, 0x34, 0x68, 0xd2, 0x75, 0x32, 0xc3, + 0x05, 0x79, 0xae, 0x06, 0xbe, 0x5c, 0xfe, 0xa0, 0xeb, 0x8e, 0x5c, 0x1d, 0xbe, 0xbc, 0x0f, 0x83, + 0xd7, 0x56, 0x6f, 0xad, 0x9e, 0x3e, 0x86, 0x5b, 0xd3, 0x6d, 0x7f, 0xb7, 0xf3, 0xcf, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x0e, 0x9b, 0x08, 0xee, 0x7c, 0x01, 0x00, 0x00, } diff --git a/api/gateway/semtech/semtech.proto b/api/gateway/semtech/semtech.proto index 93acbd83d..7a68b5d3c 100644 --- a/api/gateway/semtech/semtech.proto +++ b/api/gateway/semtech/semtech.proto @@ -1,7 +1,5 @@ syntax = "proto3"; -import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto"; - package semtech; message RxMetadata { @@ -16,7 +14,3 @@ message TxConfiguration { bool polarization_inversion = 21; // LoRa polarization inversion (basically always true) uint32 frequency_deviation = 22; // FSK frequency deviation in Hz } - -message StatusMessage { - lorawan.Region region = 13; -} From 7edf0352cef64ebcbd74670c4ed3a72d86281c12 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Apr 2016 19:00:29 +0200 Subject: [PATCH 1420/2266] [storage] Add HSlice for gateway stat --- api/gateway/status_message.go | 165 +++++++++++++++++++++++++++++++++ core/storage/hslice.go | 168 ++++++++++++++++++++++++++++++++++ core/storage/hslice_test.go | 130 ++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 api/gateway/status_message.go create mode 100644 core/storage/hslice.go create mode 100644 core/storage/hslice_test.go diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go new file mode 100644 index 000000000..13adae7a2 --- /dev/null +++ b/api/gateway/status_message.go @@ -0,0 +1,165 @@ +package gateway + +import ( + "strings" + + "github.com/TheThingsNetwork/ttn/core/storage" +) + +func (m *StatusMessage) ToHSlice() *storage.HSlice { + slice := storage.NewHSlice() + if m.Timestamp != 0 { + slice.SetUint32("timestamp", m.Timestamp) + } + if m.Time != 0 { + slice.SetInt64("time", m.Time) + } + if len(m.Ip) > 0 { + slice.SetString("ip", strings.Join(m.Ip, ",")) + } + if m.Platform != "" { + slice.SetString("platform", m.Platform) + } + if m.ContactEmail != "" { + slice.SetString("contact_email", m.ContactEmail) + } + if m.Description != "" { + slice.SetString("description", m.Description) + } + if m.Gps != nil { + if m.Gps.Time != 0 { + slice.SetInt64("gps_time", m.Gps.Time) + } + if m.Gps.Latitude != 0 { + slice.SetFloat32("latitude", m.Gps.Latitude) + } + if m.Gps.Longitude != 0 { + slice.SetFloat32("longitude", m.Gps.Longitude) + } + if m.Gps.Altitude != 0 { + slice.SetInt32("altitude", m.Gps.Altitude) + } + } + if m.Rtt != 0 { + slice.SetUint32("rtt", m.Rtt) + } + if m.RxIn != 0 { + slice.SetUint32("rx_in", m.RxIn) + } + if m.RxOk != 0 { + slice.SetUint32("rx_ok", m.RxOk) + } + if m.TxIn != 0 { + slice.SetUint32("tx_in", m.TxIn) + } + if m.TxOk != 0 { + slice.SetUint32("tx_ok", m.TxOk) + } + return slice +} + +func (m *StatusMessage) FromHSlice(slice *storage.HSlice) error { + timestamp, err := slice.GetUint32("timestamp") + if err == nil { + m.Timestamp = timestamp + } else if err != storage.ErrDoesNotExist { + return err + } + time, err := slice.GetInt64("time") + if err == nil { + m.Time = time + } else if err != storage.ErrDoesNotExist { + return err + } + ip, err := slice.GetString("ip") + if err == nil { + m.Ip = strings.Split(ip, ",") + } else if err != storage.ErrDoesNotExist { + return err + } + platform, err := slice.GetString("platform") + if err == nil { + m.Platform = platform + } else if err != storage.ErrDoesNotExist { + return err + } + contactEmail, err := slice.GetString("contact_email") + if err == nil { + m.ContactEmail = contactEmail + } else if err != storage.ErrDoesNotExist { + return err + } + description, err := slice.GetString("description") + if err == nil { + m.Description = description + } else if err != storage.ErrDoesNotExist { + return err + } + gpsTime, err := slice.GetInt64("gps_time") + if err == nil { + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + m.Gps.Time = gpsTime + } else if err != storage.ErrDoesNotExist { + return err + } + latitude, err := slice.GetFloat32("latitude") + if err == nil { + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + m.Gps.Latitude = latitude + } else if err != storage.ErrDoesNotExist { + return err + } + longitude, err := slice.GetFloat32("longitude") + if err == nil { + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + m.Gps.Longitude = longitude + } else if err != storage.ErrDoesNotExist { + return err + } + altitude, err := slice.GetInt32("altitude") + if err == nil { + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + m.Gps.Altitude = altitude + } else if err != storage.ErrDoesNotExist { + return err + } + rtt, err := slice.GetUint32("rtt") + if err == nil { + m.Rtt = rtt + } else if err != storage.ErrDoesNotExist { + return err + } + rxIn, err := slice.GetUint32("rx_in") + if err == nil { + m.RxIn = rxIn + } else if err != storage.ErrDoesNotExist { + return err + } + rxOk, err := slice.GetUint32("rx_ok") + if err == nil { + m.RxOk = rxOk + } else if err != storage.ErrDoesNotExist { + return err + } + txIn, err := slice.GetUint32("tx_in") + if err == nil { + m.TxIn = txIn + } else if err != storage.ErrDoesNotExist { + return err + } + txOk, err := slice.GetUint32("tx_ok") + if err == nil { + m.TxOk = txOk + } else if err != storage.ErrDoesNotExist { + return err + } + return nil +} diff --git a/core/storage/hslice.go b/core/storage/hslice.go new file mode 100644 index 000000000..83ccfe6f4 --- /dev/null +++ b/core/storage/hslice.go @@ -0,0 +1,168 @@ +package storage + +import ( + "encoding/hex" + "errors" + "strconv" +) + +type HSlice struct { + Data map[string]string +} + +var ( + // ErrDoesNotExist indicates that a key does not exist + ErrDoesNotExist = errors.New("HSlice: key does not exist") + // ErrInvalidLength indicates that a slice had an invalid length + ErrInvalidLength = errors.New("HSsice: invalid length") +) + +func NewHSlice() *HSlice { + return &HSlice{map[string]string{}} +} + +// MarshalHSlice returns a slice with the data +func (s *HSlice) MarshalHSlice() (out []string) { + for k, v := range s.Data { + if k != "" && v != "" { + out = append(out, k, v) + } + } + return +} + +// UnmarshalHSlice imports data from slice +func (s *HSlice) UnmarshalHSlice(slice []string) error { + if len(slice)%2 != 0 { + return ErrInvalidLength + } + for i := 0; i < len(slice); i = i + 2 { + s.Data[slice[i]] = slice[i+1] + } + return nil +} + +// SetFloat32 does what its name suggests +func (s *HSlice) SetFloat32(key string, value float32) { + s.Data[key] = strconv.FormatFloat(float64(value), 'E', -1, 32) +} + +// SetFloat64 does what its name suggests +func (s *HSlice) SetFloat64(key string, value float64) { + s.Data[key] = strconv.FormatFloat(value, 'E', -1, 64) +} + +// SetInt32 does what its name suggests +func (s *HSlice) SetInt32(key string, value int32) { + s.SetInt64(key, int64(value)) +} + +// SetInt64 does what its name suggests +func (s *HSlice) SetInt64(key string, value int64) { + s.Data[key] = strconv.FormatInt(value, 10) +} + +// SetUint32 does what its name suggests +func (s *HSlice) SetUint32(key string, value uint32) { + s.SetUint64(key, uint64(value)) +} + +// SetUint64 does what its name suggests +func (s *HSlice) SetUint64(key string, value uint64) { + s.Data[key] = strconv.FormatUint(value, 10) +} + +// SetBool does what its name suggests +func (s *HSlice) SetBool(key string, value bool) { + s.Data[key] = strconv.FormatBool(value) +} + +// SetString does what its name suggests +func (s *HSlice) SetString(key string, value string) { + if value != "" { + s.Data[key] = value + } +} + +// SetBytes does what its name suggests +func (s *HSlice) SetBytes(key string, value []byte) { + if len(value) > 0 { + s.Data[key] = hex.EncodeToString(value) + } +} + +// GetFloat32 does what its name suggests +func (s *HSlice) GetFloat32(key string) (value float32, err error) { + if val, ok := s.Data[key]; ok { + res, err := strconv.ParseFloat(val, 32) + return float32(res), err + } + return 0, ErrDoesNotExist +} + +// GetFloat64 does what its name suggests +func (s *HSlice) GetFloat64(key string) (value float64, err error) { + if val, ok := s.Data[key]; ok { + return strconv.ParseFloat(val, 64) + } + return 0, ErrDoesNotExist +} + +// GetInt32 does what its name suggests +func (s *HSlice) GetInt32(key string) (value int32, err error) { + if val, ok := s.Data[key]; ok { + res, err := strconv.ParseInt(val, 10, 32) + return int32(res), err + } + return 0, ErrDoesNotExist +} + +// GetInt64 does what its name suggests +func (s *HSlice) GetInt64(key string) (value int64, err error) { + if val, ok := s.Data[key]; ok { + res, err := strconv.ParseInt(val, 10, 64) + return res, err + } + return 0, ErrDoesNotExist +} + +// GetUint32 does what its name suggests +func (s *HSlice) GetUint32(key string) (value uint32, err error) { + if val, ok := s.Data[key]; ok { + res, err := strconv.ParseUint(val, 10, 32) + return uint32(res), err + } + return 0, ErrDoesNotExist +} + +// GetUint64 does what its name suggests +func (s *HSlice) GetUint64(key string) (value uint64, err error) { + if val, ok := s.Data[key]; ok { + return strconv.ParseUint(val, 10, 64) + } + return 0, ErrDoesNotExist +} + +// GetBool does what its name suggests +func (s *HSlice) GetBool(key string) (value bool, err error) { + if val, ok := s.Data[key]; ok { + return strconv.ParseBool(val) + } + return false, ErrDoesNotExist +} + +// GetString does what its name suggests +func (s *HSlice) GetString(key string) (value string, err error) { + if val, ok := s.Data[key]; ok { + return val, nil + } + return "", ErrDoesNotExist +} + +// GetBytes does what its name suggests +func (s *HSlice) GetBytes(key string) (value []byte, err error) { + if val, ok := s.Data[key]; ok { + return hex.DecodeString(val) + } + return []byte{}, ErrDoesNotExist +} diff --git a/core/storage/hslice_test.go b/core/storage/hslice_test.go new file mode 100644 index 000000000..26aa78e1d --- /dev/null +++ b/core/storage/hslice_test.go @@ -0,0 +1,130 @@ +package storage + +import ( + "testing" + + "github.com/smartystreets/assertions" +) + +func TestHSliceFloat32(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetFloat32("Float32") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetFloat32("Float32", 123.456) + f32, err := s.GetFloat32("Float32") + a.So(err, assertions.ShouldBeNil) + a.So(f32, assertions.ShouldEqual, 123.456) +} + +func TestHSliceFloat64(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetFloat64("Float64") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetFloat64("Float64", 123.456) + f32, err := s.GetFloat64("Float64") + a.So(err, assertions.ShouldBeNil) + a.So(f32, assertions.ShouldEqual, 123.456) +} + +func TestHSliceInt32(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetInt32("Int32") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetInt32("Int32", -123456) + res, err := s.GetInt32("Int32") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, -123456) +} + +func TestHSliceInt64(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetInt64("Int64") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetInt64("Int64", -123456) + res, err := s.GetInt64("Int64") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, -123456) +} + +func TestHSliceUint32(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetUint32("Uint32") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetUint32("Uint32", 123456) + res, err := s.GetUint32("Uint32") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, 123456) +} + +func TestHSliceUint64(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetUint64("Uint64") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetUint64("Uint64", 123456) + res, err := s.GetUint64("Uint64") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, 123456) +} + +func TestHSliceBool(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetBool("Bool") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetBool("Bool", true) + res, err := s.GetBool("Bool") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, true) + s.SetBool("Bool", false) + res, err = s.GetBool("Bool") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, false) +} + +func TestHSliceString(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetString("String") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetString("String", "the string") + res, err := s.GetString("String") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, "the string") +} + +func TestHSliceBytes(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + _, err := s.GetBytes("Bytes") + a.So(err, assertions.ShouldEqual, ErrDoesNotExist) + s.SetBytes("Bytes", []byte{0x12, 0x34, 0xcd, 0xef}) + res, err := s.GetBytes("Bytes") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldResemble, []byte{0x12, 0x34, 0xcd, 0xef}) +} + +func TestHSliceSlice(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + s.SetString("String", "the string") + slice := s.MarshalHSlice() + a.So(slice, assertions.ShouldResemble, []string{"String", "the string"}) +} + +func TestHSliceFrom(t *testing.T) { + a := assertions.New(t) + s := NewHSlice() + err := s.UnmarshalHSlice([]string{"String", "the string"}) + a.So(err, assertions.ShouldBeNil) + res, err := s.GetString("String") + a.So(err, assertions.ShouldBeNil) + a.So(res, assertions.ShouldEqual, "the string") + err = s.UnmarshalHSlice([]string{"String", "the string", "another string"}) + a.So(err, assertions.ShouldEqual, ErrInvalidLength) +} From 16ae3af525960d757c38dcbd0f40da6426a21889 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 14:27:07 +0200 Subject: [PATCH 1421/2266] Add toa package for Time-on-Air calculation --- core/types/data_rate.go | 81 ++++++++++++++++++++++++++++++++++++ core/types/data_rate_test.go | 60 ++++++++++++++++++++++++++ utils/toa/toa.go | 52 +++++++++++++++++++++++ utils/toa/toa_test.go | 72 ++++++++++++++++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 core/types/data_rate.go create mode 100644 core/types/data_rate_test.go create mode 100644 utils/toa/toa.go create mode 100644 utils/toa/toa_test.go diff --git a/core/types/data_rate.go b/core/types/data_rate.go new file mode 100644 index 000000000..5e82d42e5 --- /dev/null +++ b/core/types/data_rate.go @@ -0,0 +1,81 @@ +package types + +import ( + "errors" + "fmt" + "regexp" + "strconv" +) + +type DataRate struct { + SpreadingFactor uint `json:"spreading_factor,omitempty"` + Bandwidth uint `json:"bandwidth,omitempty"` +} + +// ParseDataRate parses a 32-bit hex-encoded string to a Devdatr +func ParseDataRate(input string) (datr *DataRate, err error) { + re := regexp.MustCompile("SF(7|8|9|10|11|12)BW(125|250|500)") + matches := re.FindStringSubmatch(input) + if len(matches) != 3 { + return nil, errors.New("ttn/core: Invalid DataRate") + } + + sf, _ := strconv.ParseUint(matches[1], 10, 64) + bw, _ := strconv.ParseUint(matches[2], 10, 64) + + return &DataRate{ + SpreadingFactor: uint(sf), + Bandwidth: uint(bw), + }, nil +} + +// Bytes returns the DataRate as a byte slice +func (datr DataRate) Bytes() []byte { + return []byte(datr.String()) +} + +// String implements the Stringer interface. +func (datr DataRate) String() string { + return fmt.Sprintf("SF%dBW%d", datr.SpreadingFactor, datr.Bandwidth) +} + +// GoString implements the GoStringer interface. +func (datr DataRate) GoString() string { + return datr.String() +} + +// MarshalText implements the TextMarshaler interface. +func (datr DataRate) MarshalText() ([]byte, error) { + return []byte(datr.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (datr *DataRate) UnmarshalText(data []byte) error { + parsed, err := ParseDataRate(string(data)) + if err != nil { + return err + } + *datr = *parsed + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (datr DataRate) MarshalBinary() ([]byte, error) { + return datr.MarshalText() +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (datr *DataRate) UnmarshalBinary(data []byte) error { + return datr.UnmarshalText(data) +} + +// Marshal implements the Marshaler interface. +func (datr DataRate) Marshal() ([]byte, error) { + return datr.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (datr *DataRate) Unmarshal(data []byte) error { + *datr = DataRate{} // Reset the receiver + return datr.UnmarshalBinary(data) +} diff --git a/core/types/data_rate_test.go b/core/types/data_rate_test.go new file mode 100644 index 000000000..b60858f12 --- /dev/null +++ b/core/types/data_rate_test.go @@ -0,0 +1,60 @@ +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDataRate(t *testing.T) { + a := New(t) + + // Setup + datr := DataRate{7, 125} + str := "SF7BW125" + bin := []byte("SF7BW125") + + // Bytes + a.So(datr.Bytes(), ShouldResemble, bin) + + // String + a.So(datr.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := datr.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := datr.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := datr.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // Parse + pOut, err := ParseDataRate(str) + a.So(err, ShouldBeNil) + a.So(*pOut, ShouldResemble, datr) + + // UnmarshalText + utOut := &DataRate{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(*utOut, ShouldResemble, datr) + + // UnmarshalBinary + ubOut := &DataRate{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(*ubOut, ShouldResemble, datr) + + // Unmarshal + uOut := &DataRate{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(*uOut, ShouldResemble, datr) +} diff --git a/utils/toa/toa.go b/utils/toa/toa.go new file mode 100644 index 000000000..e85267cfc --- /dev/null +++ b/utils/toa/toa.go @@ -0,0 +1,52 @@ +package toa + +import ( + "math" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Compute the time-on-air given a PHY payload size in bytes, a datr identifier, +// an LoRa coding rate identifier. Note that this function operates on the PHY +// payload size and does not add the LoRaWAN header. +// +// See http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf, page 7 +func Compute(payloadSize uint, datr string, codr string) (time.Duration, error) { + // Determine CR + var cr float64 + switch codr { + case "4/5": + cr = 1 + case "4/6": + cr = 2 + case "4/7": + cr = 3 + case "4/8": + cr = 4 + default: + return 0, errors.New(errors.Structural, "Invalid Codr") + } + // Determine DR + dr, err := types.ParseDataRate(datr) + if err != nil { + return 0, err + } + // Determine DE + var de float64 + if dr.Bandwidth == 125 && (dr.SpreadingFactor == 11 || dr.SpreadingFactor == 12) { + de = 1.0 + } + pl := float64(payloadSize) + sf := float64(dr.SpreadingFactor) + bw := float64(dr.Bandwidth) + h := 0.0 // 0 means header is enabled + + tSym := math.Pow(2, float64(dr.SpreadingFactor)) / bw + + payloadNb := 8.0 + math.Max(0.0, math.Ceil((8.0*pl-4.0*sf+28.0+16.0-20.0*h)/(4.0*(sf-2.0*de)))*(cr+4.0)) + timeOnAir := (payloadNb + 12.25) * tSym * 1000000 // in nanoseconds + + return time.Duration(timeOnAir), nil +} diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go new file mode 100644 index 000000000..6249476eb --- /dev/null +++ b/utils/toa/toa_test.go @@ -0,0 +1,72 @@ +package toa + +import ( + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestCompute(t *testing.T) { + a := New(t) + + var toa time.Duration + var err error + + // Test different SFs + sfTests := map[string]uint{ + "SF7BW125": 41216, + "SF8BW125": 72192, + "SF9BW125": 144384, + "SF10BW125": 288768, + "SF11BW125": 577536, + "SF12BW125": 991232, + } + for dr, us := range sfTests { + toa, err = Compute(10, dr, "4/5") + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + + // Test different BWs + bwTests := map[string]uint{ + "SF7BW125": 41216, + "SF7BW250": 20608, + "SF7BW500": 10304, + } + for dr, us := range bwTests { + toa, err = Compute(10, dr, "4/5") + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + + // Test different CRs + crTests := map[string]uint{ + "4/5": 41216, + "4/6": 45312, + "4/7": 49408, + "4/8": 53504, + } + for cr, us := range crTests { + toa, err = Compute(10, "SF7BW125", cr) + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + + // Test different payload sizes + plTests := map[uint]uint{ + 13: 46336, + 14: 46336, + 15: 46336, + 16: 51456, + 17: 51456, + 18: 51456, + 19: 51456, + } + for size, us := range plTests { + toa, err = Compute(size, "SF7BW125", "4/5") + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) + } + +} From 1fc94c2ba90180f158d19b6f035d97ece7d0f730 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 15:13:30 +0200 Subject: [PATCH 1422/2266] [refactor] Use core/types in Protos --- api/broker/broker.pb.go | 363 ++++++++++++++------------ api/broker/broker.proto | 14 +- api/gateway/status_message.go | 165 ------------ api/networkserver/networkserver.pb.go | 175 +++++++------ api/networkserver/networkserver.proto | 10 +- api/protocol/lorawan/lorawan.pb.go | 208 ++++++++------- api/protocol/lorawan/lorawan.proto | 10 +- api/router/router.pb.go | 339 +++++++++++++----------- api/router/router.proto | 14 +- 9 files changed, 635 insertions(+), 663 deletions(-) delete mode 100644 api/gateway/status_message.go diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index c166b82c6..3b731ec8e 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -29,10 +29,13 @@ package broker import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" import gateway "github.com/TheThingsNetwork/ttn/api/gateway" +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" @@ -132,13 +135,13 @@ func (m *DeviceActivationResponse) GetDownlinkOption() *DownlinkOption { // sent to the Handler type DeduplicatedUplinkMessage struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` - AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` - ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` - ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` - NeedDownlink bool `protobuf:"varint,32,opt,name=need_downlink,json=needDownlink,proto3" json:"need_downlink,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` + NeedDownlink bool `protobuf:"varint,32,opt,name=need_downlink,json=needDownlink,proto3" json:"need_downlink,omitempty"` } func (m *DeduplicatedUplinkMessage) Reset() { *m = DeduplicatedUplinkMessage{} } @@ -169,13 +172,13 @@ func (m *DeduplicatedUplinkMessage) GetResponseTemplate() *DownlinkMessage { // received from the Router type DeviceActivationRequest struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` - AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` - ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` - ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` - DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` } func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } @@ -213,13 +216,13 @@ func (m *DeviceActivationRequest) GetDownlinkOptions() []*DownlinkOption { // sent to the Handler type DeduplicatedDeviceActivationRequest struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` - AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` - ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` - ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` - ResponseTemplate *DeviceActivationResponse `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + ResponseTemplate *DeviceActivationResponse `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` } func (m *DeduplicatedDeviceActivationRequest) Reset() { *m = DeduplicatedDeviceActivationRequest{} } @@ -1031,17 +1034,25 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if len(m.DevEui) > 0 { + if m.DevEui != nil { data[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevEui))) - i += copy(data[i:], m.DevEui) + i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) + n5, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 } - if len(m.AppEui) > 0 { + if m.AppEui != nil { data[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) + i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) + n6, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1049,11 +1060,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n5, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n7, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n7 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1075,11 +1086,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n6, err := m.ResponseTemplate.MarshalTo(data[i:]) + n8, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n8 } if m.NeedDownlink { data[i] = 0x80 @@ -1117,17 +1128,25 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if len(m.DevEui) > 0 { + if m.DevEui != nil { data[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevEui))) - i += copy(data[i:], m.DevEui) + i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) + n9, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n9 } - if len(m.AppEui) > 0 { + if m.AppEui != nil { data[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) + i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) + n10, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n10 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1135,11 +1154,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n7, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n11, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n11 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1147,11 +1166,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n8, err := m.GatewayMetadata.MarshalTo(data[i:]) + n12, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n12 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1159,11 +1178,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n9, err := m.ActivationMetadata.MarshalTo(data[i:]) + n13, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n13 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -1203,17 +1222,25 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if len(m.DevEui) > 0 { + if m.DevEui != nil { data[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevEui))) - i += copy(data[i:], m.DevEui) + i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) + n14, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n14 } - if len(m.AppEui) > 0 { + if m.AppEui != nil { data[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) + i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) + n15, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n15 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1221,11 +1248,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n10, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n16, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n16 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1233,11 +1260,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n11, err := m.GatewayMetadata.MarshalTo(data[i:]) + n17, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n17 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1245,11 +1272,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n12, err := m.ActivationMetadata.MarshalTo(data[i:]) + n18, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n18 } if m.ResponseTemplate != nil { data[i] = 0xfa @@ -1257,11 +1284,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n13, err := m.ResponseTemplate.MarshalTo(data[i:]) + n19, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n19 } return i, nil } @@ -1420,31 +1447,31 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n14, err := m.Uplink.MarshalTo(data[i:]) + n20, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n20 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n15, err := m.UplinkUnique.MarshalTo(data[i:]) + n21, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n21 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n16, err := m.Downlink.MarshalTo(data[i:]) + n22, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n22 } if m.Activations != nil { data[i] = 0xaa @@ -1452,11 +1479,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n17, err := m.Activations.MarshalTo(data[i:]) + n23, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n23 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1464,11 +1491,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n18, err := m.ActivationsUnique.MarshalTo(data[i:]) + n24, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n24 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1476,11 +1503,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n19, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n25, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n25 } if m.Deduplication != nil { data[i] = 0xfa @@ -1488,11 +1515,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n20, err := m.Deduplication.MarshalTo(data[i:]) + n26, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n20 + i += n26 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1613,12 +1640,12 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } - l = len(m.DevEui) - if l > 0 { + if m.DevEui != nil { + l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) } - l = len(m.AppEui) - if l > 0 { + if m.AppEui != nil { + l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } if m.ProtocolMetadata != nil { @@ -1648,12 +1675,12 @@ func (m *DeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } - l = len(m.DevEui) - if l > 0 { + if m.DevEui != nil { + l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) } - l = len(m.AppEui) - if l > 0 { + if m.AppEui != nil { + l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } if m.ProtocolMetadata != nil { @@ -1684,12 +1711,12 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } - l = len(m.DevEui) - if l > 0 { + if m.DevEui != nil { + l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) } - l = len(m.AppEui) - if l > 0 { + if m.AppEui != nil { + l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } if m.ProtocolMetadata != nil { @@ -2423,9 +2450,10 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) - if m.DevEui == nil { - m.DevEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 12: @@ -2454,9 +2482,10 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) - if m.AppEui == nil { - m.AppEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 21: @@ -2683,9 +2712,10 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) - if m.DevEui == nil { - m.DevEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 12: @@ -2714,9 +2744,10 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) - if m.AppEui == nil { - m.AppEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 21: @@ -2956,9 +2987,10 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) - if m.DevEui == nil { - m.DevEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 12: @@ -2987,9 +3019,10 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) - if m.AppEui == nil { - m.AppEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 21: @@ -3957,66 +3990,70 @@ var ( ) var fileDescriptorBroker = []byte{ - // 971 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4b, 0x6f, 0xeb, 0x44, - 0x14, 0x26, 0x89, 0x48, 0xd3, 0x93, 0xa4, 0x49, 0xa7, 0x4d, 0xeb, 0x6b, 0xaa, 0xb6, 0xf8, 0x22, - 0x54, 0x1e, 0x37, 0x81, 0x22, 0xa8, 0xae, 0x10, 0xa0, 0x5c, 0x8a, 0x78, 0x48, 0x81, 0x2b, 0xdf, - 0x76, 0x1d, 0x4d, 0xec, 0x21, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x72, 0xff, 0x06, 0x62, 0xc1, - 0x96, 0x7f, 0x03, 0x3b, 0x36, 0xec, 0x11, 0xfc, 0x00, 0xf6, 0xac, 0x18, 0xcf, 0xc3, 0x76, 0x5e, - 0xdc, 0x50, 0xc4, 0x02, 0x89, 0x85, 0x65, 0xcf, 0x39, 0xe7, 0x3b, 0x33, 0x73, 0xbe, 0xf3, 0xcd, - 0x18, 0xce, 0xc6, 0x94, 0x4f, 0x92, 0x51, 0xd7, 0x0b, 0xaf, 0x7b, 0x17, 0x13, 0x72, 0x31, 0xa1, - 0xc1, 0x98, 0x7d, 0x4e, 0xf8, 0x6d, 0x18, 0x5f, 0xf5, 0x38, 0x0f, 0x7a, 0x38, 0xa2, 0xbd, 0x51, - 0x1c, 0x5e, 0x91, 0x58, 0xbf, 0xba, 0x51, 0x1c, 0xf2, 0x10, 0x55, 0xd5, 0xc8, 0x7e, 0xb0, 0x4e, - 0x02, 0xf1, 0x28, 0x98, 0xfd, 0xee, 0x3a, 0xe1, 0x32, 0xd4, 0x0b, 0xa7, 0xd9, 0x87, 0x06, 0x3f, - 0x5c, 0x07, 0x3c, 0xc6, 0x9c, 0xdc, 0xe2, 0xa7, 0xe6, 0xad, 0xa0, 0xce, 0x08, 0xb6, 0xce, 0xc3, - 0xdb, 0x60, 0x4a, 0x83, 0xab, 0x2f, 0x22, 0x4e, 0xc3, 0x00, 0x1d, 0x02, 0x50, 0x9f, 0x04, 0x9c, - 0x7e, 0x49, 0x49, 0x6c, 0x95, 0x8e, 0x4b, 0x27, 0x9b, 0x6e, 0xc1, 0x82, 0x76, 0xe1, 0x79, 0xe6, - 0x85, 0x31, 0xb1, 0xca, 0xc2, 0xd5, 0x74, 0xd5, 0x00, 0xd9, 0x50, 0xf3, 0x09, 0xf6, 0x45, 0x1e, - 0x62, 0x55, 0x84, 0xa3, 0xe2, 0x66, 0x63, 0xe7, 0xf7, 0x12, 0x34, 0x2f, 0xa3, 0x74, 0x8a, 0x01, - 0x61, 0x0c, 0x8f, 0x09, 0xb2, 0x60, 0x23, 0xc2, 0x4f, 0xa7, 0x21, 0xf6, 0xe5, 0x04, 0x0d, 0xd7, - 0x0c, 0x51, 0x1f, 0xb6, 0xcd, 0xe6, 0x86, 0xd7, 0x84, 0x63, 0x1f, 0x73, 0x6c, 0xd5, 0x45, 0x4c, - 0xfd, 0x74, 0xb7, 0x9b, 0x6d, 0xdb, 0xfd, 0x7a, 0xa0, 0x7d, 0x6e, 0xdb, 0x18, 0x8d, 0x05, 0xbd, - 0x0f, 0x6d, 0xbd, 0xc7, 0x3c, 0x43, 0x43, 0x66, 0xd8, 0xe9, 0x9a, 0xcd, 0x17, 0x12, 0xb4, 0xb4, - 0x2d, 0xc3, 0xf7, 0xa1, 0xed, 0xeb, 0x92, 0x0c, 0x43, 0x59, 0x13, 0x66, 0x75, 0x8e, 0x2b, 0x02, - 0xbf, 0xd7, 0xd5, 0x54, 0xcf, 0x96, 0xcc, 0x6d, 0xf9, 0x33, 0x63, 0xe6, 0x4c, 0xa1, 0x65, 0x42, - 0x9e, 0xbd, 0xe5, 0x0f, 0xa0, 0x35, 0x37, 0x9f, 0xde, 0xf0, 0xaa, 0xe9, 0xb6, 0x66, 0xa7, 0x73, - 0x12, 0xb0, 0xce, 0xc9, 0x0d, 0xf5, 0x48, 0xdf, 0xe3, 0xf4, 0x06, 0xcb, 0x18, 0xc2, 0x22, 0xb1, - 0x90, 0x7f, 0x75, 0xda, 0x9f, 0xcb, 0x70, 0xef, 0x9c, 0xf8, 0x89, 0x60, 0xd6, 0x13, 0x25, 0xf4, - 0xd7, 0xa5, 0x78, 0x1f, 0x36, 0x7c, 0x72, 0x33, 0x24, 0x09, 0x95, 0x13, 0x36, 0xdc, 0xaa, 0x18, - 0x7e, 0x94, 0xd0, 0xd4, 0x81, 0xa3, 0x48, 0x3a, 0x1a, 0xca, 0x21, 0x86, 0xa9, 0x63, 0x69, 0x53, - 0x74, 0xfe, 0x71, 0x53, 0xec, 0x49, 0x52, 0xd7, 0x6b, 0x8a, 0x73, 0xd8, 0x8e, 0x75, 0x4d, 0x87, - 0x9c, 0x5c, 0x47, 0x53, 0xe1, 0xb7, 0x8e, 0xe4, 0x12, 0xf6, 0xe7, 0xeb, 0xa5, 0x4b, 0xe0, 0xb6, - 0x0d, 0xe2, 0x42, 0x03, 0xd0, 0x7d, 0x68, 0x06, 0x84, 0xf8, 0x43, 0x53, 0x49, 0xeb, 0x58, 0x64, - 0xa8, 0xb9, 0x8d, 0xd4, 0x68, 0xd0, 0xce, 0x1f, 0x65, 0xd8, 0x5f, 0xe4, 0xf3, 0xab, 0x84, 0x30, - 0xfe, 0xdf, 0xa8, 0xea, 0xfa, 0x52, 0x1b, 0xc0, 0x0e, 0xce, 0xf6, 0x98, 0xa7, 0xd8, 0x97, 0x29, - 0x0e, 0xf2, 0x45, 0xe4, 0x85, 0xc8, 0x72, 0x21, 0xbc, 0x60, 0x5b, 0xaa, 0xdc, 0xa3, 0xbf, 0xa7, - 0xdc, 0x6f, 0x2a, 0x70, 0xbf, 0xd8, 0xd4, 0xff, 0x13, 0x71, 0x17, 0x22, 0x06, 0xab, 0xd5, 0x72, - 0x9c, 0x31, 0xb1, 0xe2, 0xc8, 0x5a, 0x94, 0x8d, 0x83, 0xa0, 0xfd, 0x24, 0x19, 0x31, 0x2f, 0xa6, - 0x23, 0xa2, 0x09, 0x70, 0x3a, 0xb0, 0xd3, 0x8f, 0x14, 0x4b, 0x29, 0x71, 0xc6, 0xfc, 0x26, 0xec, - 0xce, 0x9a, 0xf5, 0x39, 0x78, 0x0f, 0x6a, 0xba, 0xf8, 0x4c, 0x10, 0x56, 0x11, 0x77, 0xda, 0x86, - 0xaa, 0x3e, 0x73, 0xde, 0x06, 0xdb, 0x25, 0x63, 0xca, 0x38, 0x89, 0x0b, 0x50, 0x43, 0x74, 0x81, - 0x35, 0x75, 0x17, 0x6a, 0xd6, 0x9c, 0x33, 0x38, 0xb8, 0x0c, 0xe2, 0x3b, 0x00, 0x5b, 0xd0, 0x7c, - 0xc2, 0x31, 0x4f, 0xb2, 0x35, 0xff, 0x58, 0x81, 0x2d, 0x63, 0xd1, 0xcb, 0x75, 0xa0, 0x9a, 0xc8, - 0xe3, 0x54, 0x62, 0xeb, 0xa7, 0xd0, 0x4d, 0x7f, 0x15, 0x5c, 0x51, 0x0c, 0xe6, 0x6a, 0x0f, 0xea, - 0x41, 0x53, 0x7d, 0x0d, 0x93, 0x80, 0x8a, 0x4c, 0xf2, 0x42, 0x9e, 0x0d, 0x6d, 0xa8, 0x80, 0x4b, - 0xe9, 0x47, 0x2f, 0x8b, 0x3b, 0xda, 0x1c, 0x3c, 0xf5, 0x85, 0xd8, 0xcc, 0x87, 0x5e, 0x87, 0x7a, - 0xce, 0x29, 0xd3, 0x9d, 0x58, 0x0c, 0x2d, 0xba, 0xd1, 0x43, 0x28, 0x74, 0x00, 0x33, 0x6b, 0xd9, - 0x5b, 0x00, 0x6d, 0x17, 0xa2, 0xf4, 0x82, 0xde, 0x83, 0xdd, 0x22, 0x14, 0x7b, 0x1e, 0x89, 0x84, - 0xe6, 0x74, 0xdb, 0x15, 0xc1, 0x85, 0xee, 0x64, 0x7d, 0x1d, 0x86, 0xde, 0x81, 0xa6, 0x9f, 0x49, - 0x35, 0xbd, 0xbf, 0x54, 0x87, 0xb5, 0x25, 0xee, 0x31, 0x89, 0xbd, 0xf4, 0x9f, 0x65, 0x2a, 0xd0, - 0xb3, 0x61, 0xe8, 0x35, 0xd8, 0xf6, 0xc2, 0x20, 0x20, 0x9e, 0x48, 0x32, 0x8c, 0xc3, 0x44, 0xf0, - 0xc7, 0xac, 0x57, 0xe4, 0xdf, 0x4c, 0x3b, 0x73, 0xb8, 0xca, 0x8e, 0x1e, 0x00, 0xca, 0x83, 0x27, - 0x38, 0xf0, 0xa7, 0x69, 0xf4, 0xab, 0x32, 0x3a, 0x4f, 0xf3, 0x89, 0x76, 0x9c, 0x7e, 0x5b, 0x86, - 0xea, 0x23, 0xd9, 0xe0, 0xe2, 0x82, 0xdd, 0xec, 0x33, 0x16, 0x7a, 0x34, 0x3d, 0xf9, 0x3b, 0xa6, - 0xed, 0x67, 0x6e, 0x49, 0x7b, 0xd5, 0xdd, 0x71, 0x52, 0x7a, 0xa3, 0x84, 0x3e, 0x83, 0xcd, 0xac, - 0xed, 0x91, 0x65, 0x22, 0xe7, 0x95, 0x60, 0xbf, 0x98, 0x2b, 0x6a, 0xc5, 0x65, 0x2c, 0x72, 0x75, - 0x61, 0xe3, 0x71, 0x32, 0x9a, 0x52, 0x36, 0x41, 0xab, 0xe6, 0xb4, 0x6b, 0xb2, 0x70, 0x7d, 0xef, - 0xea, 0xa4, 0x24, 0x14, 0x5c, 0xd3, 0xd2, 0x24, 0xe8, 0x68, 0xb5, 0x64, 0xd5, 0x0a, 0x9e, 0xa9, - 0xe9, 0xd3, 0xef, 0xcb, 0xd0, 0x54, 0x65, 0x19, 0xe0, 0x40, 0xcc, 0x15, 0xa3, 0x4f, 0xa1, 0x51, - 0x14, 0x2a, 0x7a, 0xc1, 0xe4, 0x58, 0xa2, 0x6a, 0xfb, 0x60, 0xb9, 0x53, 0x8b, 0xe5, 0x43, 0xd8, - 0x59, 0x22, 0x60, 0xe4, 0x18, 0xd0, 0x6a, 0x75, 0xe7, 0x5b, 0x46, 0x1f, 0x43, 0x67, 0xa9, 0x9c, - 0xd1, 0x4b, 0x19, 0x73, 0x7f, 0xa1, 0xf6, 0x42, 0xa2, 0x33, 0xa8, 0x2a, 0x31, 0xe7, 0x9c, 0xcf, - 0xc8, 0xdd, 0xde, 0x9b, 0x37, 0xab, 0x6d, 0x3c, 0x6a, 0xff, 0xf0, 0xeb, 0x61, 0xe9, 0x27, 0xf1, - 0xfc, 0x22, 0x9e, 0xef, 0x7e, 0x3b, 0x7c, 0x6e, 0x54, 0x95, 0xe7, 0xee, 0x5b, 0x7f, 0x06, 0x00, - 0x00, 0xff, 0xff, 0xcd, 0xe7, 0xb5, 0x29, 0x8d, 0x0c, 0x00, 0x00, + // 1028 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0x27, 0x0d, 0xa4, 0xe9, 0x4b, 0xd2, 0xa4, 0xd3, 0xa6, 0xf5, 0x86, 0xaa, 0x2d, 0x5e, 0x84, + 0xca, 0x9f, 0x4d, 0xd8, 0xa2, 0xa5, 0x5a, 0x21, 0x40, 0x29, 0x5d, 0xc1, 0x22, 0x65, 0x59, 0x79, + 0xdb, 0x73, 0x34, 0xb1, 0x67, 0x93, 0x51, 0x53, 0xdb, 0x78, 0xc6, 0x2d, 0xbd, 0xf1, 0x21, 0x38, + 0x70, 0xe5, 0xb3, 0x70, 0x81, 0x1b, 0x67, 0x0e, 0x08, 0xc1, 0x85, 0x1b, 0x5f, 0x81, 0xf1, 0x78, + 0xc6, 0x7f, 0x9a, 0x98, 0x86, 0x45, 0x48, 0x1c, 0x7a, 0xb0, 0xec, 0x79, 0x7f, 0x7e, 0x6f, 0xe6, + 0xbd, 0xdf, 0x1b, 0x3f, 0x38, 0x1c, 0x53, 0x3e, 0x09, 0x47, 0x5d, 0xdb, 0x3b, 0xef, 0x9d, 0x4c, + 0xc8, 0xc9, 0x84, 0xba, 0x63, 0xf6, 0x84, 0xf0, 0x4b, 0x2f, 0x38, 0xeb, 0x71, 0xee, 0xf6, 0xb0, + 0x4f, 0x7b, 0xa3, 0xc0, 0x3b, 0x23, 0x81, 0x7a, 0x75, 0xfd, 0xc0, 0xe3, 0x1e, 0xaa, 0xc4, 0xab, + 0xce, 0xbd, 0x0c, 0xc0, 0xd8, 0x1b, 0x7b, 0x3d, 0xa9, 0x1e, 0x85, 0xcf, 0xe5, 0x4a, 0x2e, 0xe4, + 0x57, 0xec, 0x96, 0x33, 0x2f, 0x8c, 0x27, 0x1e, 0x65, 0xfe, 0xc1, 0x22, 0xe6, 0xd2, 0xd4, 0xf6, + 0xa6, 0xc9, 0x87, 0x72, 0x7e, 0xb8, 0x88, 0xf3, 0x18, 0x73, 0x72, 0x89, 0xaf, 0xf4, 0x3b, 0x76, + 0x35, 0x47, 0xb0, 0x7a, 0xec, 0x5d, 0xba, 0x53, 0xea, 0x9e, 0x7d, 0xe1, 0x73, 0xea, 0xb9, 0x68, + 0x07, 0x80, 0x3a, 0xc4, 0xe5, 0xf4, 0x39, 0x25, 0x81, 0x51, 0xda, 0x2b, 0xed, 0xaf, 0x58, 0x19, + 0x09, 0xda, 0x80, 0x57, 0x98, 0xed, 0x05, 0xc4, 0x58, 0x12, 0xaa, 0x86, 0x15, 0x2f, 0x50, 0x07, + 0xaa, 0x0e, 0xc1, 0x8e, 0xc0, 0x21, 0x46, 0x59, 0x28, 0xca, 0x56, 0xb2, 0x36, 0xff, 0x2c, 0x41, + 0xe3, 0xd4, 0x8f, 0x42, 0x0c, 0x08, 0x63, 0x78, 0x4c, 0x90, 0x01, 0xcb, 0x3e, 0xbe, 0x9a, 0x7a, + 0xd8, 0x91, 0x01, 0xea, 0x96, 0x5e, 0xa2, 0x3e, 0xac, 0xe9, 0xc3, 0x0d, 0xcf, 0x09, 0xc7, 0x0e, + 0xe6, 0xd8, 0xa8, 0x09, 0x9b, 0xda, 0xc1, 0x46, 0x37, 0x39, 0xb6, 0xf5, 0xd5, 0x40, 0xe9, 0xac, + 0x96, 0x16, 0x6a, 0x09, 0xfa, 0x08, 0x5a, 0xea, 0x8c, 0x29, 0x42, 0x5d, 0x22, 0xac, 0x77, 0xf5, + 0xe1, 0x33, 0x00, 0x4d, 0x25, 0x4b, 0xfc, 0xfb, 0xd0, 0x72, 0x54, 0x4a, 0x86, 0x9e, 0xcc, 0x09, + 0x33, 0xda, 0x7b, 0x65, 0xe1, 0xbf, 0xd9, 0x55, 0xcc, 0xc8, 0xa7, 0xcc, 0x6a, 0x3a, 0xb9, 0x35, + 0x33, 0xa7, 0xd0, 0xd4, 0x26, 0x37, 0x1f, 0xf9, 0x63, 0x68, 0x5e, 0x8b, 0xa7, 0x0e, 0x5c, 0x14, + 0x6e, 0x35, 0x1f, 0xce, 0x0c, 0xc1, 0x38, 0x26, 0x17, 0xd4, 0x26, 0x7d, 0x9b, 0xd3, 0x0b, 0x2c, + 0x6d, 0x08, 0xf3, 0xc5, 0x46, 0xfe, 0xd3, 0xb0, 0xdf, 0x97, 0xe1, 0xce, 0x31, 0x71, 0x42, 0x51, + 0x59, 0x5b, 0xa4, 0xd0, 0x59, 0xb4, 0xc4, 0x4f, 0x60, 0xd9, 0x21, 0x17, 0x43, 0x12, 0x52, 0x19, + 0xb0, 0x7e, 0xf4, 0xe0, 0xe7, 0x5f, 0x76, 0xef, 0xdf, 0x44, 0xe1, 0x88, 0x65, 0x3d, 0x7e, 0xe5, + 0x13, 0xd6, 0x15, 0x87, 0x7d, 0x74, 0xfa, 0xd8, 0xaa, 0x08, 0x94, 0x47, 0x21, 0x8d, 0xf0, 0xb0, + 0xef, 0x4b, 0xbc, 0xfa, 0x0b, 0xe1, 0xf5, 0x7d, 0x5f, 0xe2, 0x09, 0x94, 0x08, 0x6f, 0x2e, 0x05, + 0xdb, 0xff, 0x9a, 0x82, 0x9b, 0x92, 0x42, 0x8b, 0x51, 0xf0, 0x18, 0xd6, 0x02, 0x55, 0xc1, 0x21, + 0x27, 0xe7, 0xfe, 0x54, 0xe8, 0x8d, 0x5d, 0xb9, 0x85, 0xad, 0xeb, 0xd5, 0x51, 0x09, 0xb7, 0x5a, + 0xda, 0xe3, 0x44, 0x39, 0xa0, 0xbb, 0xd0, 0x70, 0x09, 0x71, 0x86, 0xba, 0x6e, 0xc6, 0x9e, 0x40, + 0xa8, 0x5a, 0xf5, 0x48, 0xa8, 0xbd, 0xcd, 0x3f, 0xca, 0xb0, 0x35, 0xcb, 0x9e, 0x2f, 0x43, 0xc2, + 0xf8, 0x6d, 0x0d, 0x67, 0x6b, 0xb8, 0xf8, 0x35, 0x32, 0x80, 0x75, 0x9c, 0x64, 0x34, 0x85, 0xd8, + 0x92, 0x10, 0xdb, 0xe9, 0x26, 0xd2, 0xb4, 0x27, 0x58, 0x08, 0xcf, 0xc8, 0xe6, 0xde, 0x4a, 0xbb, + 0xff, 0xec, 0x56, 0xfa, 0xfa, 0x65, 0xb8, 0x9b, 0x6d, 0xd8, 0xdb, 0xb2, 0xff, 0xff, 0xcb, 0x3e, + 0x28, 0xbe, 0x09, 0xf6, 0x92, 0xba, 0x17, 0x5c, 0xfe, 0xb3, 0x57, 0x82, 0x89, 0xa0, 0xf5, 0x2c, + 0x1c, 0x31, 0x3b, 0xa0, 0x23, 0xa2, 0xca, 0x6d, 0xb6, 0x61, 0x5d, 0xa4, 0x51, 0x72, 0x22, 0xa2, + 0x89, 0x16, 0xdf, 0x87, 0x8d, 0xbc, 0x58, 0xfd, 0x51, 0xee, 0x40, 0x55, 0xd5, 0x8c, 0x09, 0x7a, + 0x94, 0xc5, 0x74, 0xb0, 0x1c, 0x67, 0x9f, 0x99, 0x0f, 0xa0, 0x63, 0x91, 0x31, 0x65, 0x9c, 0x04, + 0x19, 0x57, 0x4d, 0xab, 0xad, 0xb4, 0xd8, 0xf1, 0x54, 0xa1, 0xaa, 0x66, 0x1e, 0xc2, 0xf6, 0xa9, + 0x1b, 0xbc, 0x80, 0x63, 0x13, 0x1a, 0xcf, 0x38, 0xe6, 0x61, 0xb2, 0xe7, 0x1f, 0xcb, 0xb0, 0xaa, + 0x25, 0x6a, 0xbb, 0x26, 0x54, 0x42, 0xf9, 0x63, 0x92, 0xbe, 0xb5, 0x03, 0xe8, 0x46, 0x43, 0x97, + 0x25, 0x92, 0xc1, 0x2c, 0xa5, 0x41, 0x3d, 0x68, 0xc4, 0x5f, 0xc3, 0xd0, 0xa5, 0x02, 0x49, 0x8e, + 0x36, 0x79, 0xd3, 0x7a, 0x6c, 0x70, 0x2a, 0xf5, 0xe8, 0x0d, 0x31, 0xed, 0xe8, 0x4b, 0xb5, 0x36, + 0x63, 0x9b, 0xe8, 0xd0, 0x3b, 0x50, 0x4b, 0x6b, 0xca, 0x14, 0x13, 0xb3, 0xa6, 0x59, 0x35, 0x7a, + 0x08, 0x19, 0x06, 0x30, 0xbd, 0x97, 0xcd, 0x19, 0xa7, 0xb5, 0x8c, 0x95, 0xda, 0xd0, 0x87, 0xb0, + 0x91, 0x75, 0xc5, 0xb6, 0x4d, 0x7c, 0xd1, 0xe1, 0x8a, 0x76, 0x59, 0xe7, 0x0c, 0x3b, 0x59, 0x5f, + 0x99, 0xa1, 0xf7, 0xa1, 0xe1, 0x24, 0x17, 0x43, 0x34, 0x09, 0xc4, 0x0c, 0x6b, 0x49, 0xbf, 0xa7, + 0x24, 0xb0, 0xa3, 0xe9, 0x6f, 0x2a, 0xbc, 0xf3, 0x66, 0xe8, 0x6d, 0x58, 0xb3, 0x3d, 0xd7, 0x25, + 0xb6, 0x00, 0x19, 0x06, 0x5e, 0x28, 0xea, 0xc7, 0x8c, 0x37, 0xe5, 0x5c, 0xd8, 0x4a, 0x14, 0x56, + 0x2c, 0x47, 0xf7, 0x00, 0xa5, 0xc6, 0x13, 0xec, 0x3a, 0xd3, 0xc8, 0xfa, 0x2d, 0x69, 0x9d, 0xc2, + 0x7c, 0xa6, 0x14, 0x07, 0xdf, 0x2c, 0x41, 0xe5, 0x48, 0x12, 0x5c, 0x8c, 0x2a, 0x2b, 0x7d, 0xc6, + 0x3c, 0x9b, 0x46, 0x7f, 0xb5, 0xb6, 0xa6, 0x7d, 0x6e, 0xde, 0xe8, 0x14, 0xfd, 0x17, 0xf7, 0x4b, + 0xef, 0x96, 0xd0, 0xe7, 0xb0, 0x92, 0xd0, 0x1e, 0x19, 0xda, 0xf2, 0x7a, 0x27, 0x74, 0x5e, 0x4b, + 0x3b, 0xaa, 0x60, 0xac, 0x11, 0x58, 0x5d, 0x58, 0x7e, 0x1a, 0x8e, 0xa6, 0x94, 0x4d, 0x50, 0x51, + 0xcc, 0x4e, 0x55, 0x26, 0xae, 0x6f, 0x9f, 0xed, 0x97, 0x44, 0x07, 0x57, 0x55, 0x6b, 0x12, 0xb4, + 0x5b, 0xdc, 0xb2, 0xf1, 0x0e, 0x6e, 0xec, 0xe9, 0x83, 0xef, 0x96, 0xa0, 0x11, 0xa7, 0x65, 0x80, + 0x5d, 0x11, 0x2b, 0x40, 0x8f, 0xa1, 0x9e, 0x6d, 0x54, 0xf4, 0xaa, 0xc6, 0x98, 0xd3, 0xd5, 0x9d, + 0xed, 0xf9, 0x4a, 0xd5, 0x2c, 0x9f, 0xc0, 0xfa, 0x9c, 0x06, 0x46, 0xa6, 0x76, 0x2a, 0xee, 0xee, + 0xf4, 0xc8, 0xe8, 0x53, 0x68, 0xcf, 0x6d, 0x67, 0xf4, 0x7a, 0x52, 0xb9, 0xbf, 0xe9, 0xf6, 0x0c, + 0xd0, 0x21, 0x54, 0xe2, 0x66, 0x4e, 0x6b, 0x9e, 0x6b, 0xf7, 0xce, 0xe6, 0x75, 0x71, 0x7c, 0x8c, + 0xa3, 0xd6, 0x0f, 0xbf, 0xed, 0x94, 0x7e, 0x12, 0xcf, 0xaf, 0xe2, 0xf9, 0xf6, 0xf7, 0x9d, 0x97, + 0x46, 0x15, 0x79, 0xef, 0xbe, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x06, 0x33, 0xf9, 0xe4, + 0x06, 0x0e, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index c4f8987e4..5996a75a2 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; @@ -35,8 +37,8 @@ message DeviceActivationResponse { // sent to the Handler message DeduplicatedUplinkMessage { bytes payload = 1; - bytes dev_eui = 11; - bytes app_eui = 12; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; DownlinkMessage response_template = 31; @@ -46,8 +48,8 @@ message DeduplicatedUplinkMessage { // received from the Router message DeviceActivationRequest { bytes payload = 1; - bytes dev_eui = 11; - bytes app_eui = 12; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; gateway.RxMetadata gateway_metadata = 22; protocol.ActivationMetadata activation_metadata = 23; @@ -57,8 +59,8 @@ message DeviceActivationRequest { // sent to the Handler message DeduplicatedDeviceActivationRequest { bytes payload = 1; - bytes dev_eui = 11; - bytes app_eui = 12; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; gateway.RxMetadata gateway_metadata = 22; protocol.ActivationMetadata activation_metadata = 23; diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go deleted file mode 100644 index 13adae7a2..000000000 --- a/api/gateway/status_message.go +++ /dev/null @@ -1,165 +0,0 @@ -package gateway - -import ( - "strings" - - "github.com/TheThingsNetwork/ttn/core/storage" -) - -func (m *StatusMessage) ToHSlice() *storage.HSlice { - slice := storage.NewHSlice() - if m.Timestamp != 0 { - slice.SetUint32("timestamp", m.Timestamp) - } - if m.Time != 0 { - slice.SetInt64("time", m.Time) - } - if len(m.Ip) > 0 { - slice.SetString("ip", strings.Join(m.Ip, ",")) - } - if m.Platform != "" { - slice.SetString("platform", m.Platform) - } - if m.ContactEmail != "" { - slice.SetString("contact_email", m.ContactEmail) - } - if m.Description != "" { - slice.SetString("description", m.Description) - } - if m.Gps != nil { - if m.Gps.Time != 0 { - slice.SetInt64("gps_time", m.Gps.Time) - } - if m.Gps.Latitude != 0 { - slice.SetFloat32("latitude", m.Gps.Latitude) - } - if m.Gps.Longitude != 0 { - slice.SetFloat32("longitude", m.Gps.Longitude) - } - if m.Gps.Altitude != 0 { - slice.SetInt32("altitude", m.Gps.Altitude) - } - } - if m.Rtt != 0 { - slice.SetUint32("rtt", m.Rtt) - } - if m.RxIn != 0 { - slice.SetUint32("rx_in", m.RxIn) - } - if m.RxOk != 0 { - slice.SetUint32("rx_ok", m.RxOk) - } - if m.TxIn != 0 { - slice.SetUint32("tx_in", m.TxIn) - } - if m.TxOk != 0 { - slice.SetUint32("tx_ok", m.TxOk) - } - return slice -} - -func (m *StatusMessage) FromHSlice(slice *storage.HSlice) error { - timestamp, err := slice.GetUint32("timestamp") - if err == nil { - m.Timestamp = timestamp - } else if err != storage.ErrDoesNotExist { - return err - } - time, err := slice.GetInt64("time") - if err == nil { - m.Time = time - } else if err != storage.ErrDoesNotExist { - return err - } - ip, err := slice.GetString("ip") - if err == nil { - m.Ip = strings.Split(ip, ",") - } else if err != storage.ErrDoesNotExist { - return err - } - platform, err := slice.GetString("platform") - if err == nil { - m.Platform = platform - } else if err != storage.ErrDoesNotExist { - return err - } - contactEmail, err := slice.GetString("contact_email") - if err == nil { - m.ContactEmail = contactEmail - } else if err != storage.ErrDoesNotExist { - return err - } - description, err := slice.GetString("description") - if err == nil { - m.Description = description - } else if err != storage.ErrDoesNotExist { - return err - } - gpsTime, err := slice.GetInt64("gps_time") - if err == nil { - if m.Gps == nil { - m.Gps = &GPSMetadata{} - } - m.Gps.Time = gpsTime - } else if err != storage.ErrDoesNotExist { - return err - } - latitude, err := slice.GetFloat32("latitude") - if err == nil { - if m.Gps == nil { - m.Gps = &GPSMetadata{} - } - m.Gps.Latitude = latitude - } else if err != storage.ErrDoesNotExist { - return err - } - longitude, err := slice.GetFloat32("longitude") - if err == nil { - if m.Gps == nil { - m.Gps = &GPSMetadata{} - } - m.Gps.Longitude = longitude - } else if err != storage.ErrDoesNotExist { - return err - } - altitude, err := slice.GetInt32("altitude") - if err == nil { - if m.Gps == nil { - m.Gps = &GPSMetadata{} - } - m.Gps.Altitude = altitude - } else if err != storage.ErrDoesNotExist { - return err - } - rtt, err := slice.GetUint32("rtt") - if err == nil { - m.Rtt = rtt - } else if err != storage.ErrDoesNotExist { - return err - } - rxIn, err := slice.GetUint32("rx_in") - if err == nil { - m.RxIn = rxIn - } else if err != storage.ErrDoesNotExist { - return err - } - rxOk, err := slice.GetUint32("rx_ok") - if err == nil { - m.RxOk = rxOk - } else if err != storage.ErrDoesNotExist { - return err - } - txIn, err := slice.GetUint32("tx_in") - if err == nil { - m.TxIn = txIn - } else if err != storage.ErrDoesNotExist { - return err - } - txOk, err := slice.GetUint32("tx_ok") - if err == nil { - m.TxOk = txOk - } else if err != storage.ErrDoesNotExist { - return err - } - return nil -} diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index a618e69a4..a2e9e3828 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -21,9 +21,12 @@ package networkserver import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" @@ -41,8 +44,8 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 type DevicesRequest struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` - FCnt uint32 `protobuf:"varint,2,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + FCnt uint32 `protobuf:"varint,2,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` } func (m *DevicesRequest) Reset() { *m = DevicesRequest{} } @@ -67,9 +70,9 @@ func (m *DevicesResponse) GetResults() []*DevicesResponse_Device { } type DevicesResponse_Device struct { - AppEui []byte `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` - DevEui []byte `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3" json:"nwk_s_key,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` } func (m *DevicesResponse_Device) Reset() { *m = DevicesResponse_Device{} } @@ -426,11 +429,15 @@ func (m *DevicesRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.DevAddr) > 0 { + if m.DevAddr != nil { data[i] = 0xa i++ - i = encodeVarintNetworkserver(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) + i = encodeVarintNetworkserver(data, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 } if m.FCnt != 0 { data[i] = 0x10 @@ -485,23 +492,35 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.AppEui) > 0 { + if m.AppEui != nil { data[i] = 0xa i++ - i = encodeVarintNetworkserver(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) + i = encodeVarintNetworkserver(data, i, uint64(m.AppEui.Size())) + n2, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 } - if len(m.DevEui) > 0 { + if m.DevEui != nil { data[i] = 0x12 i++ - i = encodeVarintNetworkserver(data, i, uint64(len(m.DevEui))) - i += copy(data[i:], m.DevEui) + i = encodeVarintNetworkserver(data, i, uint64(m.DevEui.Size())) + n3, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 } - if len(m.NwkSKey) > 0 { + if m.NwkSKey != nil { data[i] = 0x1a i++ - i = encodeVarintNetworkserver(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) + i = encodeVarintNetworkserver(data, i, uint64(m.NwkSKey.Size())) + n4, err := m.NwkSKey.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 } return i, nil } @@ -589,11 +608,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintNetworkserver(data, i, uint64(m.DevicesPerAddress.Size())) - n1, err := m.DevicesPerAddress.MarshalTo(data[i:]) + n5, err := m.DevicesPerAddress.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n1 + i += n5 } return i, nil } @@ -628,8 +647,8 @@ func encodeVarintNetworkserver(data []byte, offset int, v uint64) int { func (m *DevicesRequest) Size() (n int) { var l int _ = l - l = len(m.DevAddr) - if l > 0 { + if m.DevAddr != nil { + l = m.DevAddr.Size() n += 1 + l + sovNetworkserver(uint64(l)) } if m.FCnt != 0 { @@ -653,16 +672,16 @@ func (m *DevicesResponse) Size() (n int) { func (m *DevicesResponse_Device) Size() (n int) { var l int _ = l - l = len(m.AppEui) - if l > 0 { + if m.AppEui != nil { + l = m.AppEui.Size() n += 1 + l + sovNetworkserver(uint64(l)) } - l = len(m.DevEui) - if l > 0 { + if m.DevEui != nil { + l = m.DevEui.Size() n += 1 + l + sovNetworkserver(uint64(l)) } - l = len(m.NwkSKey) - if l > 0 { + if m.NwkSKey != nil { + l = m.NwkSKey.Size() n += 1 + l + sovNetworkserver(uint64(l)) } return n @@ -767,9 +786,10 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 2: @@ -948,9 +968,10 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) - if m.AppEui == nil { - m.AppEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 2: @@ -979,9 +1000,10 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) - if m.DevEui == nil { - m.DevEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 3: @@ -1010,9 +1032,10 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey + m.NwkSKey = &v + if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex default: @@ -1395,39 +1418,43 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 533 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x53, 0x4d, 0x6e, 0xd3, 0x40, - 0x14, 0x26, 0x29, 0x24, 0xe1, 0xb5, 0x49, 0xcb, 0x14, 0x94, 0x60, 0x41, 0x54, 0x0c, 0x48, 0xdd, - 0xe0, 0x48, 0x41, 0x88, 0x0d, 0x12, 0x0d, 0x34, 0x42, 0xa2, 0x6a, 0x85, 0x9c, 0xc2, 0xd6, 0x72, - 0xec, 0xd7, 0x64, 0xe4, 0x30, 0x36, 0x33, 0xe3, 0x44, 0xbd, 0x09, 0x3b, 0xf6, 0x1c, 0x80, 0x33, - 0xb0, 0xe4, 0x08, 0x08, 0x2e, 0xc2, 0xd8, 0x33, 0x0e, 0xb2, 0xa1, 0xd0, 0xc5, 0xc8, 0x7a, 0xdf, - 0xfb, 0xff, 0xbe, 0x67, 0x18, 0xcf, 0xa8, 0x9c, 0xa7, 0x53, 0x27, 0x88, 0xdf, 0x0f, 0x4e, 0xe7, - 0x78, 0x3a, 0xa7, 0x6c, 0x26, 0x4e, 0x50, 0xae, 0x62, 0x1e, 0x0d, 0xa4, 0x64, 0x03, 0x3f, 0xa1, - 0x03, 0xa6, 0x6d, 0x81, 0x7c, 0x89, 0xbc, 0x6c, 0x39, 0x09, 0x8f, 0x65, 0x4c, 0xda, 0x25, 0xd0, - 0x7a, 0x74, 0x99, 0xaa, 0xea, 0xe9, 0x6c, 0xeb, 0xe9, 0x65, 0xc2, 0xa7, 0x3c, 0x8e, 0x54, 0x77, - 0xfd, 0xd1, 0x89, 0xf6, 0x01, 0x74, 0x0e, 0x71, 0x49, 0x03, 0x14, 0x2e, 0x7e, 0x48, 0x51, 0x48, - 0x72, 0x1b, 0x5a, 0x21, 0x2e, 0x3d, 0x3f, 0x0c, 0x79, 0xaf, 0xb6, 0x57, 0xdb, 0xdf, 0x72, 0x9b, - 0xca, 0x1e, 0x29, 0x93, 0xec, 0xc2, 0xb5, 0x33, 0x2f, 0x60, 0xb2, 0x57, 0x57, 0x78, 0xdb, 0xbd, - 0x7a, 0xf6, 0x92, 0x49, 0xfb, 0x73, 0x0d, 0xb6, 0xd7, 0x25, 0x44, 0x12, 0x33, 0x81, 0xe4, 0x39, - 0x34, 0x39, 0x8a, 0x74, 0x21, 0x85, 0x2a, 0xb1, 0xb1, 0xbf, 0x39, 0x7c, 0xe8, 0x94, 0x77, 0xae, - 0x24, 0x18, 0xdb, 0x2d, 0xb2, 0xac, 0x77, 0xd0, 0xd0, 0x10, 0xe9, 0x42, 0xd3, 0x4f, 0x12, 0x0f, - 0x53, 0x6a, 0xa6, 0x69, 0x28, 0x73, 0x9c, 0xd2, 0xcc, 0x91, 0xcd, 0x99, 0x39, 0xea, 0xda, 0xa1, - 0xcc, 0xcc, 0x61, 0xc1, 0x75, 0xb6, 0x8a, 0x3c, 0xe1, 0x45, 0x78, 0xde, 0xdb, 0xd0, 0x1b, 0x28, - 0x60, 0x72, 0x84, 0xe7, 0xf6, 0x13, 0xe8, 0xbc, 0x4d, 0x16, 0x94, 0x45, 0xeb, 0x51, 0xef, 0x83, - 0x62, 0x1e, 0x43, 0x2f, 0x8c, 0x57, 0x2c, 0x73, 0xe4, 0x5d, 0x5a, 0xee, 0x56, 0x06, 0x1e, 0x1a, - 0xcc, 0xee, 0xc2, 0x2d, 0x17, 0x67, 0x54, 0x48, 0xe4, 0x66, 0x52, 0x4d, 0x96, 0xbd, 0x0d, 0xed, - 0x89, 0xf4, 0x65, 0x5a, 0xb0, 0x67, 0xbb, 0xd0, 0x29, 0x00, 0xd3, 0xe0, 0x00, 0x76, 0x43, 0xbd, - 0xad, 0x97, 0x20, 0xcf, 0x79, 0x45, 0x21, 0xf2, 0x36, 0x9b, 0xc3, 0x1d, 0x27, 0xd3, 0xf0, 0x0d, - 0xf2, 0x00, 0x99, 0xa4, 0x0b, 0xc5, 0xc8, 0x0d, 0x13, 0xac, 0xb0, 0x91, 0x0e, 0x1d, 0x7e, 0xa9, - 0x43, 0xdb, 0x68, 0x39, 0xc9, 0xe9, 0x23, 0x47, 0x00, 0xaf, 0x50, 0x1a, 0x12, 0xc9, 0xdd, 0x8b, - 0xc8, 0xcd, 0x47, 0xb2, 0xfa, 0xff, 0xe6, 0x9e, 0x9c, 0x40, 0x6b, 0x14, 0x48, 0xba, 0xf4, 0x25, - 0x92, 0x3d, 0xc7, 0x5c, 0x87, 0x0e, 0x32, 0x38, 0x8d, 0x59, 0x11, 0x6d, 0xfd, 0x37, 0x82, 0xbc, - 0x86, 0x86, 0xe6, 0x98, 0xdc, 0xfb, 0x1d, 0x1b, 0xa6, 0x0a, 0x0a, 0x54, 0x8b, 0x50, 0xfb, 0x8e, - 0xd5, 0x62, 0xfe, 0x0c, 0xad, 0xea, 0xec, 0x15, 0x75, 0x9e, 0x41, 0xab, 0x10, 0x81, 0x74, 0xd7, - 0xd5, 0x0c, 0x52, 0xd4, 0xb8, 0xc8, 0x31, 0xfc, 0x54, 0x83, 0x9b, 0x25, 0xe2, 0x8e, 0x7d, 0xa6, - 0x70, 0xae, 0x34, 0xe9, 0x94, 0xf5, 0x24, 0x0f, 0x2a, 0x73, 0xfc, 0x55, 0x6e, 0xab, 0x95, 0xcb, - 0x35, 0x0a, 0x22, 0x32, 0x86, 0x86, 0xd6, 0x99, 0xdc, 0xa9, 0x64, 0x96, 0xee, 0xe1, 0x8f, 0xfd, - 0xca, 0xc7, 0xf1, 0x62, 0xe7, 0xeb, 0x8f, 0x7e, 0xed, 0x9b, 0x7a, 0xdf, 0xd5, 0xfb, 0xf8, 0xb3, - 0x7f, 0x65, 0xda, 0xc8, 0xff, 0xcb, 0xc7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x97, 0x8c, 0x25, - 0x20, 0x57, 0x04, 0x00, 0x00, + // 605 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0xfd, 0x92, 0x7e, 0x24, 0x61, 0xda, 0xa4, 0x65, 0x0a, 0x6a, 0x65, 0x41, 0x29, 0x06, 0xa4, + 0x6e, 0xb0, 0x45, 0x50, 0x61, 0x83, 0x04, 0x29, 0x8d, 0x10, 0x54, 0xad, 0xc0, 0x69, 0xd7, 0x96, + 0x63, 0xdf, 0x38, 0x96, 0xc3, 0xd8, 0xcc, 0x8c, 0x13, 0xf2, 0x26, 0xec, 0x78, 0x0b, 0x9e, 0x81, + 0x25, 0x6c, 0x59, 0x20, 0x04, 0xaf, 0xc1, 0x82, 0xf1, 0xcc, 0x38, 0xe0, 0x40, 0x28, 0x74, 0x61, + 0x65, 0xe6, 0xce, 0xb9, 0xe7, 0xfe, 0x9c, 0xa3, 0xa0, 0x6e, 0x18, 0xf1, 0x61, 0xd6, 0xb7, 0xfc, + 0xe4, 0x85, 0x7d, 0x3c, 0x84, 0xe3, 0x61, 0x44, 0x42, 0x76, 0x04, 0x7c, 0x92, 0xd0, 0xd8, 0xe6, + 0x9c, 0xd8, 0x5e, 0x1a, 0xd9, 0x44, 0xdd, 0x19, 0xd0, 0x31, 0xd0, 0xf2, 0xcd, 0x4a, 0x69, 0xc2, + 0x13, 0xdc, 0x2c, 0x05, 0x8d, 0x5b, 0x3f, 0xb1, 0x86, 0x49, 0x98, 0xd8, 0x12, 0xd5, 0xcf, 0x06, + 0xf2, 0x26, 0x2f, 0xf2, 0xa4, 0xb2, 0x4b, 0xf0, 0x85, 0x4d, 0x88, 0x4f, 0xc3, 0xef, 0xfd, 0x0d, + 0xbc, 0x4f, 0x93, 0x58, 0x34, 0xab, 0x7e, 0x54, 0xa2, 0xf9, 0x0a, 0xb5, 0xf6, 0x61, 0x1c, 0xf9, + 0xc0, 0x1c, 0x78, 0x99, 0x01, 0xe3, 0xf8, 0x39, 0x6a, 0x04, 0x30, 0x76, 0xbd, 0x20, 0xa0, 0x9b, + 0x95, 0xed, 0xca, 0xce, 0xca, 0xde, 0xdd, 0x8f, 0x9f, 0xae, 0xb6, 0x4f, 0x2b, 0xe0, 0x27, 0x14, + 0x6c, 0x3e, 0x4d, 0x81, 0x59, 0x82, 0xb0, 0x23, 0xb2, 0x9d, 0x7a, 0xa0, 0x0e, 0x78, 0x1d, 0x9d, + 0x1b, 0xb8, 0x3e, 0xe1, 0x9b, 0x55, 0xc1, 0xd7, 0x74, 0xfe, 0x1f, 0x3c, 0x22, 0xdc, 0xfc, 0x50, + 0x45, 0xab, 0xb3, 0xd2, 0x2c, 0x4d, 0x08, 0x03, 0xfc, 0x00, 0xd5, 0x29, 0xb0, 0x6c, 0xc4, 0x99, + 0x28, 0xbd, 0xb4, 0xb3, 0xdc, 0xbe, 0x69, 0x95, 0x57, 0x3b, 0x97, 0xa0, 0xef, 0x4e, 0x91, 0x65, + 0x7c, 0xab, 0xa0, 0x9a, 0x8a, 0xe1, 0x23, 0x54, 0xf7, 0xd2, 0xd4, 0x85, 0x2c, 0xd2, 0x63, 0xec, + 0x8a, 0x31, 0x6e, 0xff, 0xc3, 0x18, 0x9d, 0x34, 0xed, 0x9e, 0x3c, 0x71, 0x6a, 0x82, 0xa5, 0x9b, + 0x45, 0x39, 0x5f, 0xbe, 0x97, 0x9c, 0xaf, 0x7a, 0x26, 0x3e, 0xd1, 0x97, 0xe4, 0x13, 0x2c, 0x39, + 0x9f, 0x83, 0xce, 0x93, 0x49, 0xec, 0x32, 0x37, 0x86, 0xe9, 0xe6, 0xd2, 0x99, 0x16, 0x7d, 0x34, + 0x89, 0x7b, 0x07, 0x30, 0x75, 0xea, 0x44, 0x1d, 0xcc, 0x5d, 0xd4, 0x3a, 0x49, 0x47, 0x11, 0x89, + 0x67, 0x1b, 0xbd, 0x8e, 0x84, 0x0f, 0x21, 0x70, 0x83, 0x64, 0x42, 0xf2, 0x07, 0xb9, 0x8b, 0x86, + 0xb3, 0x92, 0x07, 0xf7, 0x75, 0xcc, 0xdc, 0x40, 0x97, 0x1c, 0x08, 0x23, 0xc6, 0x81, 0xea, 0x85, + 0x2a, 0x2f, 0x98, 0xab, 0xa8, 0xd9, 0xe3, 0x1e, 0xcf, 0x0a, 0x73, 0x98, 0x0e, 0x6a, 0x15, 0x01, + 0x5d, 0xe0, 0x21, 0x5a, 0x0f, 0x94, 0x28, 0x6e, 0x0a, 0x54, 0xda, 0x06, 0x18, 0x93, 0x65, 0x96, + 0xdb, 0x6b, 0x56, 0x6e, 0xd1, 0x67, 0x40, 0x7d, 0x20, 0x3c, 0x1a, 0x09, 0xe1, 0x2e, 0x68, 0xb0, + 0x88, 0x75, 0x14, 0xb4, 0xfd, 0xb6, 0x8a, 0x9a, 0x7a, 0xc0, 0x9e, 0x54, 0x19, 0x1f, 0x20, 0xf4, + 0x18, 0xb8, 0xd6, 0x1a, 0x5f, 0x59, 0xe4, 0x01, 0xd9, 0x92, 0xb1, 0xf5, 0x67, 0x8b, 0x08, 0xdd, + 0x1a, 0x1d, 0x9f, 0x47, 0x63, 0x8f, 0x03, 0xde, 0xb6, 0xb4, 0xf9, 0x15, 0x48, 0xc7, 0xa3, 0x84, + 0x14, 0x68, 0xe3, 0x54, 0x04, 0x7e, 0x8a, 0x6a, 0x6a, 0xc7, 0xf8, 0xda, 0x0f, 0x6c, 0x90, 0x89, + 0x90, 0x2f, 0x4a, 0x04, 0xea, 0xed, 0x50, 0x0c, 0xe6, 0x85, 0x60, 0xcc, 0xf7, 0x3e, 0xa7, 0xce, + 0x7d, 0xd4, 0x28, 0x44, 0xc0, 0x1b, 0x33, 0x36, 0x1d, 0x29, 0x38, 0x16, 0x3d, 0xb4, 0xdf, 0x54, + 0xd0, 0xc5, 0xd2, 0xe2, 0x0e, 0x3d, 0x22, 0xe2, 0x54, 0x68, 0xd2, 0x2a, 0xeb, 0x89, 0x6f, 0xcc, + 0xf5, 0xf1, 0x5b, 0xb9, 0x8d, 0x86, 0x94, 0xab, 0xe3, 0xc7, 0xb8, 0x8b, 0x6a, 0x4a, 0x67, 0x7c, + 0x79, 0x2e, 0xb3, 0xe4, 0x87, 0x5f, 0xe6, 0x2b, 0x9b, 0x63, 0x6f, 0xed, 0xdd, 0x97, 0xad, 0xca, + 0x7b, 0xf1, 0x7d, 0x16, 0xdf, 0xeb, 0xaf, 0x5b, 0xff, 0xf5, 0x6b, 0xf2, 0x6f, 0xe7, 0xce, 0xf7, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x46, 0xc8, 0x19, 0x0c, 0x65, 0x05, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 67ec6dc95..315b7688f 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -1,20 +1,22 @@ syntax = "proto3"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; package networkserver; message DevicesRequest { - bytes dev_addr = 1; + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; uint32 f_cnt = 2; } message DevicesResponse { message Device { - bytes app_eui = 1; - bytes dev_eui = 2; - bytes nwk_s_key = 3; + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; } repeated Device results = 1; } diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index c3417da08..1138a47ce 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -23,6 +23,9 @@ package lorawan import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import _ "github.com/gogo/protobuf/gogoproto" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" import io "io" @@ -102,7 +105,7 @@ func (*Metadata) ProtoMessage() {} func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } type TxConfiguration struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` } func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } @@ -111,8 +114,8 @@ func (*TxConfiguration) ProtoMessage() {} func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } type ActivationMetadata struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` - NetworkSessionKey []byte `protobuf:"bytes,2,opt,name=network_session_key,json=networkSessionKey,proto3" json:"network_session_key,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,2,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` } func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } @@ -174,10 +177,10 @@ func (m *MACPayload) GetFHdr() *FHdr { } type FHdr struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3" json:"dev_addr,omitempty"` - FCtrl *FCtrl `protobuf:"bytes,2,opt,name=f_ctrl,json=fCtrl" json:"f_ctrl,omitempty"` - FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` - FOpts [][]byte `protobuf:"bytes,4,rep,name=f_opts,json=fOpts" json:"f_opts,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + FCtrl *FCtrl `protobuf:"bytes,2,opt,name=f_ctrl,json=fCtrl" json:"f_ctrl,omitempty"` + FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + FOpts [][]byte `protobuf:"bytes,4,rep,name=f_opts,json=fOpts" json:"f_opts,omitempty"` } func (m *FHdr) Reset() { *m = FHdr{} } @@ -272,11 +275,15 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.DevAddr) > 0 { + if m.DevAddr != nil { data[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) + i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 } return i, nil } @@ -296,17 +303,25 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.DevAddr) > 0 { + if m.DevAddr != nil { data[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) + i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) + n2, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 } - if len(m.NetworkSessionKey) > 0 { + if m.NwkSKey != nil { data[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.NetworkSessionKey))) - i += copy(data[i:], m.NetworkSessionKey) + i = encodeVarintLorawan(data, i, uint64(m.NwkSKey.Size())) + n3, err := m.NwkSKey.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 } return i, nil } @@ -330,21 +345,21 @@ func (m *PHYPayload) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.Mhdr.Size())) - n1, err := m.Mhdr.MarshalTo(data[i:]) + n4, err := m.Mhdr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n1 + i += n4 } if m.MACPayload != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) - n2, err := m.MACPayload.MarshalTo(data[i:]) + n5, err := m.MACPayload.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n5 } if len(m.Mic) > 0 { data[i] = 0x1a @@ -402,11 +417,11 @@ func (m *MACPayload) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.FHdr.Size())) - n3, err := m.FHdr.MarshalTo(data[i:]) + n6, err := m.FHdr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n6 } if m.FPort != 0 { data[i] = 0x10 @@ -437,21 +452,25 @@ func (m *FHdr) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.DevAddr) > 0 { + if m.DevAddr != nil { data[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) + i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) + n7, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 } if m.FCtrl != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) - n4, err := m.FCtrl.MarshalTo(data[i:]) + n8, err := m.FCtrl.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n8 } if m.FCnt != 0 { data[i] = 0x18 @@ -583,8 +602,8 @@ func (m *Metadata) Size() (n int) { func (m *TxConfiguration) Size() (n int) { var l int _ = l - l = len(m.DevAddr) - if l > 0 { + if m.DevAddr != nil { + l = m.DevAddr.Size() n += 1 + l + sovLorawan(uint64(l)) } return n @@ -593,12 +612,12 @@ func (m *TxConfiguration) Size() (n int) { func (m *ActivationMetadata) Size() (n int) { var l int _ = l - l = len(m.DevAddr) - if l > 0 { + if m.DevAddr != nil { + l = m.DevAddr.Size() n += 1 + l + sovLorawan(uint64(l)) } - l = len(m.NetworkSessionKey) - if l > 0 { + if m.NwkSKey != nil { + l = m.NwkSKey.Size() n += 1 + l + sovLorawan(uint64(l)) } return n @@ -654,8 +673,8 @@ func (m *MACPayload) Size() (n int) { func (m *FHdr) Size() (n int) { var l int _ = l - l = len(m.DevAddr) - if l > 0 { + if m.DevAddr != nil { + l = m.DevAddr.Size() n += 1 + l + sovLorawan(uint64(l)) } if m.FCtrl != nil { @@ -910,9 +929,10 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex default: @@ -991,14 +1011,15 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetworkSessionKey", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -1022,9 +1043,10 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NetworkSessionKey = append(m.NetworkSessionKey[:0], data[iNdEx:postIndex]...) - if m.NetworkSessionKey == nil { - m.NetworkSessionKey = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey + m.NwkSKey = &v + if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex default: @@ -1471,9 +1493,10 @@ func (m *FHdr) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 2: @@ -1845,47 +1868,50 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 661 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x53, 0xd1, 0x6e, 0x12, 0x41, - 0x14, 0x2d, 0x85, 0x2d, 0xcb, 0xa5, 0xd4, 0x75, 0xaa, 0x09, 0xc6, 0xa6, 0xad, 0x9b, 0x98, 0x18, - 0x63, 0x80, 0x42, 0x2b, 0x90, 0xf8, 0xb2, 0x25, 0x6d, 0x9a, 0xb4, 0xb4, 0x64, 0x0a, 0x0f, 0xbe, - 0x38, 0x99, 0xee, 0xce, 0xc2, 0x0a, 0xbb, 0x43, 0x97, 0x69, 0x2b, 0x3f, 0xe0, 0x37, 0xf8, 0x3d, - 0x3e, 0xf9, 0xe8, 0x27, 0x18, 0xfd, 0x11, 0x67, 0x66, 0x17, 0xf0, 0x41, 0x7d, 0xd8, 0xcc, 0x9c, - 0x7b, 0xcf, 0x3d, 0xe7, 0xde, 0xcb, 0x00, 0xc7, 0xc3, 0x40, 0x8c, 0xee, 0x6e, 0x2a, 0x2e, 0x0f, - 0xab, 0xfd, 0x11, 0xeb, 0x8f, 0x82, 0x68, 0x38, 0xbb, 0x64, 0xe2, 0x81, 0xc7, 0xe3, 0xaa, 0x10, - 0x51, 0x95, 0x4e, 0x83, 0xea, 0x34, 0xe6, 0x82, 0xbb, 0x7c, 0x52, 0x9d, 0xf0, 0x98, 0x3e, 0xd0, - 0x68, 0x71, 0x56, 0x74, 0x02, 0xe5, 0x53, 0x68, 0x7f, 0xcd, 0x80, 0xd9, 0x65, 0x82, 0x7a, 0x54, - 0x50, 0xf4, 0x0e, 0x20, 0xe4, 0xde, 0xdd, 0x84, 0x8a, 0x80, 0x47, 0xe5, 0xe2, 0x7e, 0xe6, 0xd5, - 0x56, 0x7d, 0xa7, 0xb2, 0xa8, 0x5c, 0xd0, 0x2a, 0xdd, 0x25, 0x07, 0xff, 0xc1, 0x47, 0xcf, 0xa1, - 0xa0, 0xd2, 0x24, 0xa6, 0x82, 0x95, 0x37, 0x65, 0x71, 0x01, 0x9b, 0x2a, 0x80, 0x25, 0x46, 0xcf, - 0xc0, 0xbc, 0x09, 0x44, 0x92, 0x2b, 0xc9, 0x5c, 0x09, 0xe7, 0x25, 0xd6, 0xa9, 0x3d, 0x28, 0xba, - 0xdc, 0x93, 0x43, 0x24, 0xd9, 0x2d, 0x5d, 0x09, 0x49, 0x48, 0x11, 0xec, 0x3d, 0x80, 0x95, 0x25, - 0x32, 0x21, 0x77, 0x71, 0x85, 0x1d, 0x6b, 0x0d, 0xe5, 0x21, 0x7b, 0x7a, 0x7d, 0x6e, 0x65, 0xec, - 0x37, 0xf0, 0xa8, 0xff, 0xa9, 0xc3, 0x23, 0x3f, 0x18, 0xde, 0xc5, 0x09, 0x4b, 0xfa, 0x79, 0xec, - 0x9e, 0x50, 0xcf, 0x8b, 0xcb, 0x19, 0xa9, 0xb8, 0x89, 0xf3, 0x12, 0x3b, 0x12, 0xda, 0x04, 0x90, - 0xe3, 0x8a, 0xe0, 0x5e, 0x13, 0x97, 0xb3, 0xff, 0xbb, 0x00, 0x55, 0x60, 0x3b, 0x4a, 0xd6, 0x4b, - 0x66, 0x6c, 0x36, 0x93, 0x55, 0x64, 0xcc, 0xe6, 0xe5, 0x75, 0xcd, 0x7a, 0x9c, 0xa6, 0xae, 0x93, - 0xcc, 0x39, 0x9b, 0xdb, 0x0f, 0x00, 0xbd, 0xb3, 0xf7, 0x3d, 0x3a, 0x9f, 0x70, 0xea, 0xa1, 0x17, - 0x90, 0x0b, 0x47, 0xa9, 0x68, 0xb1, 0x5e, 0x5a, 0xad, 0xf3, 0xcc, 0x8b, 0xb1, 0x4e, 0xa1, 0x43, - 0x28, 0x76, 0x9d, 0x0e, 0x99, 0x26, 0x15, 0x5a, 0xb8, 0x58, 0xdf, 0x5e, 0x31, 0x9d, 0x4e, 0x2a, - 0x26, 0xf7, 0xbd, 0xbc, 0x23, 0x0b, 0xb2, 0x61, 0xe0, 0x96, 0xb3, 0xba, 0x0d, 0x75, 0xb5, 0x1b, - 0x90, 0x53, 0xaa, 0xe8, 0x29, 0x6c, 0x84, 0x44, 0xcc, 0xa7, 0x4c, 0x9b, 0x96, 0xb0, 0x11, 0xf6, - 0x25, 0x40, 0x4f, 0xc0, 0x08, 0xe9, 0x47, 0x1e, 0x6b, 0x03, 0x15, 0x55, 0xc0, 0x1e, 0xc9, 0xed, - 0xae, 0x44, 0x6d, 0x30, 0x7c, 0xf2, 0xb7, 0x76, 0x4f, 0x75, 0xbb, 0x7e, 0x2a, 0xef, 0x93, 0x29, - 0x8f, 0xc5, 0x42, 0xc8, 0xef, 0x49, 0xa0, 0x7e, 0xc7, 0x53, 0xdc, 0x5d, 0x4e, 0x91, 0xf4, 0x05, - 0x3e, 0xee, 0xa6, 0xda, 0xb6, 0x80, 0x9c, 0x52, 0xf9, 0xdf, 0xaa, 0x5f, 0x2a, 0x69, 0x57, 0xc4, - 0x93, 0x74, 0x09, 0x5b, 0x2b, 0xff, 0x8e, 0x8c, 0x4a, 0x2b, 0x75, 0xa0, 0x6d, 0xd5, 0xa5, 0x1b, - 0x09, 0x6d, 0x52, 0x92, 0x6d, 0x75, 0x22, 0x91, 0xb4, 0xc5, 0xa7, 0x62, 0x56, 0xce, 0xed, 0x67, - 0xa5, 0xa8, 0xe1, 0x5f, 0x49, 0x60, 0x7f, 0xce, 0x80, 0xa1, 0x8b, 0xd5, 0xc2, 0x68, 0x6a, 0x69, - 0x62, 0x75, 0x45, 0xbb, 0x50, 0x94, 0x07, 0xa1, 0xee, 0x98, 0xc4, 0xec, 0x56, 0x7b, 0x9a, 0xb8, - 0x20, 0x43, 0x8e, 0x3b, 0xc6, 0xec, 0x56, 0x57, 0xb8, 0x63, 0xed, 0xa2, 0x2a, 0xdc, 0xb1, 0x7a, - 0xe4, 0x72, 0x76, 0x16, 0xa9, 0xc7, 0x29, 0x7d, 0x54, 0xdc, 0xf4, 0x7b, 0x09, 0x46, 0x3b, 0x00, - 0x49, 0x07, 0x64, 0xc2, 0xa2, 0xb2, 0xa1, 0x47, 0x33, 0x75, 0x17, 0x17, 0x2c, 0x7a, 0xfd, 0x01, - 0x36, 0x30, 0x1b, 0xaa, 0xc7, 0x59, 0x82, 0xc2, 0xc9, 0xa0, 0xf5, 0xb6, 0x41, 0x5a, 0xcd, 0x9a, - 0x7c, 0xc7, 0x12, 0x0e, 0xae, 0xdb, 0xb5, 0x3a, 0x69, 0xd7, 0x5b, 0x56, 0x46, 0xc1, 0xce, 0x65, - 0xb3, 0xd9, 0x26, 0xcd, 0x56, 0xd3, 0x5a, 0x47, 0x05, 0x30, 0x4e, 0x06, 0x87, 0x8d, 0x86, 0x95, - 0x55, 0x19, 0x67, 0xd0, 0x3e, 0x38, 0xd2, 0xc4, 0x5c, 0x42, 0x3c, 0x6c, 0xd6, 0xc8, 0xd1, 0x41, - 0xcd, 0x32, 0x8e, 0xad, 0x6f, 0x3f, 0x77, 0x33, 0xdf, 0xe5, 0xf7, 0x43, 0x7e, 0x5f, 0x7e, 0xed, - 0xae, 0xdd, 0x6c, 0xe8, 0x3f, 0x7b, 0xe3, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x39, 0xdb, 0x21, - 0xc6, 0x32, 0x04, 0x00, 0x00, + // 716 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x54, 0xcd, 0x6e, 0xda, 0x4a, + 0x14, 0x8e, 0x03, 0x0e, 0xe6, 0x10, 0x72, 0xad, 0xc9, 0xbd, 0x12, 0xf7, 0xde, 0x28, 0xc9, 0xb5, + 0x74, 0xa5, 0xaa, 0x52, 0x81, 0x40, 0x12, 0x40, 0xea, 0x86, 0xd0, 0x44, 0x91, 0x12, 0x12, 0x3a, + 0x81, 0x45, 0x37, 0x1d, 0x19, 0x7b, 0x0c, 0xae, 0xc1, 0x43, 0xcc, 0x10, 0xca, 0x0b, 0xf4, 0x19, + 0xfa, 0x10, 0x7d, 0x83, 0xee, 0xba, 0xea, 0xb2, 0xeb, 0x2e, 0xaa, 0xaa, 0x7d, 0x91, 0xce, 0x8c, + 0xf9, 0xc9, 0xa2, 0x52, 0xa5, 0xaa, 0x5d, 0xa0, 0x39, 0xdf, 0xf9, 0xf9, 0xce, 0x77, 0xce, 0x78, + 0x80, 0x93, 0x9e, 0xcf, 0xfb, 0x93, 0x6e, 0xde, 0x61, 0xc3, 0x42, 0xbb, 0x4f, 0xdb, 0x7d, 0x3f, + 0xec, 0x8d, 0xaf, 0x28, 0x9f, 0xb2, 0x28, 0x28, 0x70, 0x1e, 0x16, 0xec, 0x91, 0x5f, 0x18, 0x45, + 0x8c, 0x33, 0x87, 0x0d, 0x0a, 0x03, 0x16, 0xd9, 0x53, 0x3b, 0x5c, 0x9c, 0x79, 0x15, 0x40, 0xa9, + 0x39, 0xfc, 0xe7, 0xd1, 0x3d, 0xb2, 0x1e, 0xeb, 0xb1, 0xb8, 0xb0, 0x3b, 0xf1, 0x14, 0x52, 0x40, + 0x59, 0x71, 0x9d, 0xf5, 0x4e, 0x03, 0xa3, 0x49, 0xb9, 0xed, 0xda, 0xdc, 0x46, 0x8f, 0x01, 0x86, + 0xcc, 0x9d, 0x0c, 0x6c, 0xee, 0xb3, 0x30, 0x97, 0xd9, 0xd7, 0x1e, 0x6c, 0x95, 0x76, 0xf2, 0x8b, + 0x46, 0x8b, 0xb4, 0x7c, 0x73, 0x99, 0x83, 0xef, 0xe5, 0xa3, 0x7f, 0x21, 0x2d, 0xc3, 0x24, 0xb2, + 0x39, 0xcd, 0x6d, 0x8a, 0xe2, 0x34, 0x36, 0xa4, 0x03, 0x0b, 0x8c, 0xfe, 0x06, 0xa3, 0xeb, 0xf3, + 0x38, 0x96, 0x15, 0xb1, 0x2c, 0x4e, 0x09, 0xac, 0x42, 0x7b, 0x90, 0x71, 0x98, 0x2b, 0x66, 0x8e, + 0xa3, 0x5b, 0xaa, 0x12, 0x62, 0x97, 0x4c, 0xb0, 0xf6, 0x00, 0x56, 0x2d, 0x91, 0x01, 0xc9, 0xcb, + 0x6b, 0x5c, 0x37, 0xd7, 0x50, 0x0a, 0x12, 0x67, 0x37, 0x17, 0xa6, 0x66, 0xb9, 0xf0, 0x47, 0xfb, + 0x65, 0x83, 0x85, 0x9e, 0xdf, 0x9b, 0x44, 0x71, 0xd6, 0x53, 0x30, 0x5c, 0x7a, 0x47, 0x6c, 0xd7, + 0x8d, 0x72, 0x9a, 0x60, 0xdc, 0x3c, 0x39, 0xfe, 0xf8, 0x69, 0xaf, 0xf4, 0xa3, 0x4d, 0x3b, 0x2c, + 0xa2, 0x05, 0x3e, 0x1b, 0xd1, 0x71, 0xfe, 0x09, 0xbd, 0xab, 0x8b, 0x6a, 0x9c, 0x72, 0x63, 0xc3, + 0x7a, 0xab, 0x01, 0xaa, 0x3b, 0xdc, 0xbf, 0x53, 0x1d, 0x96, 0x4b, 0xfb, 0xf5, 0x9d, 0x10, 0x86, + 0x74, 0x38, 0x0d, 0xc8, 0x98, 0x04, 0x74, 0x96, 0x5b, 0xff, 0x29, 0xce, 0xab, 0x69, 0x70, 0x73, + 0x41, 0x67, 0x38, 0x15, 0xc6, 0x86, 0x35, 0x05, 0x68, 0x9d, 0x3f, 0x6b, 0xd9, 0xb3, 0x01, 0xb3, + 0x5d, 0xf4, 0x1f, 0x24, 0x87, 0xfd, 0xb9, 0xe0, 0x4c, 0x29, 0xbb, 0xba, 0xe3, 0x73, 0xa1, 0x43, + 0x85, 0xd0, 0x21, 0x64, 0x9a, 0xf5, 0x06, 0x19, 0xc5, 0x15, 0x4a, 0x46, 0xa6, 0xb4, 0xbd, 0xca, + 0xac, 0x37, 0xe6, 0x64, 0xe2, 0x23, 0x58, 0xda, 0xc8, 0x84, 0xc4, 0xd0, 0x77, 0x72, 0x09, 0x29, + 0x1a, 0x4b, 0xd3, 0x2a, 0x43, 0x52, 0xb2, 0xa2, 0xbf, 0x60, 0x63, 0x48, 0xa4, 0x38, 0xd5, 0x34, + 0x8b, 0xf5, 0x61, 0x5b, 0x00, 0xf4, 0x27, 0xe8, 0x43, 0xfb, 0x05, 0x8b, 0x54, 0x03, 0xe9, 0x95, + 0xc0, 0xea, 0x8b, 0x2b, 0x5f, 0x91, 0x5a, 0xa0, 0x7b, 0xe4, 0x7b, 0x72, 0xcf, 0x94, 0x5c, 0x6f, + 0x4e, 0xef, 0x91, 0x11, 0x8b, 0xf8, 0x82, 0xc8, 0x6b, 0x09, 0x20, 0x3f, 0xae, 0x33, 0xdc, 0x5c, + 0x4e, 0x11, 0xeb, 0x02, 0x0f, 0x37, 0xe7, 0xdc, 0xd6, 0x1b, 0x0d, 0x92, 0x92, 0xe6, 0x77, 0xdc, + 0xe3, 0xff, 0x52, 0x93, 0xc3, 0xa3, 0xc1, 0x7c, 0x7b, 0x5b, 0x2b, 0xe1, 0x0d, 0xe1, 0x15, 0x1a, + 0xe5, 0x81, 0xb6, 0xe5, 0x78, 0x4e, 0xc8, 0x95, 0xba, 0xac, 0x98, 0xa7, 0x11, 0xf2, 0x78, 0x1e, + 0x36, 0xe2, 0xe3, 0x5c, 0x72, 0x3f, 0x21, 0x34, 0xeb, 0xde, 0xb5, 0x00, 0xd6, 0x2b, 0x0d, 0x74, + 0x55, 0x2c, 0x37, 0x6d, 0xcf, 0xa5, 0x1a, 0x58, 0x9a, 0x68, 0x17, 0x32, 0xe2, 0x20, 0xb6, 0x13, + 0x90, 0x88, 0xde, 0xaa, 0x9e, 0x06, 0x4e, 0x0b, 0x57, 0xdd, 0x09, 0x30, 0xbd, 0x55, 0x15, 0x4e, + 0xa0, 0xba, 0xc8, 0x0a, 0x27, 0x90, 0x4f, 0x56, 0x2c, 0x8d, 0x86, 0xf2, 0xa9, 0x89, 0x3e, 0xd2, + 0x6f, 0x78, 0xad, 0x18, 0xa3, 0x1d, 0x80, 0x58, 0x01, 0x19, 0xd0, 0x30, 0xa7, 0xab, 0xcd, 0x19, + 0x4a, 0xc5, 0x25, 0x0d, 0x1f, 0x3e, 0x87, 0x0d, 0x4c, 0x7b, 0xf2, 0xa9, 0x65, 0x21, 0x7d, 0xda, + 0xa9, 0x1e, 0x97, 0x49, 0xb5, 0x52, 0x14, 0xaf, 0x52, 0xc0, 0xce, 0x4d, 0xad, 0x58, 0x22, 0xb5, + 0x52, 0xd5, 0xd4, 0x24, 0x6c, 0x5c, 0x55, 0x2a, 0x35, 0x52, 0xa9, 0x56, 0xcc, 0x75, 0x94, 0x06, + 0xfd, 0xb4, 0x73, 0x58, 0x2e, 0x9b, 0x09, 0x19, 0xa9, 0x77, 0x6a, 0x07, 0x47, 0x2a, 0x31, 0x19, + 0x27, 0x1e, 0x56, 0x8a, 0xe4, 0xe8, 0xa0, 0x68, 0xea, 0x27, 0xe6, 0xfb, 0x2f, 0xbb, 0xda, 0x07, + 0xf1, 0xfb, 0x2c, 0x7e, 0xaf, 0xbf, 0xee, 0xae, 0x75, 0x37, 0xd4, 0x3f, 0x56, 0xf9, 0x5b, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x10, 0xf6, 0x16, 0x7f, 0x2f, 0x05, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 6333afc9d..4fd1dfd99 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + package lorawan; message Metadata { @@ -14,12 +16,12 @@ message Metadata { } message TxConfiguration { - bytes dev_addr = 1; + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; } message ActivationMetadata { - bytes dev_addr = 1; - bytes network_session_key = 2; + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + bytes nwk_s_key = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; } message PHYPayload { @@ -40,7 +42,7 @@ message MACPayload { } message FHdr { - bytes dev_addr = 1; + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; FCtrl f_ctrl = 2; uint32 f_cnt = 3; repeated bytes f_opts = 4; diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 6f6f62463..4eaa1202b 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -28,10 +28,13 @@ package router import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" import gateway "github.com/TheThingsNetwork/ttn/api/gateway" +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + import ( context "golang.org/x/net/context" grpc "google.golang.org/grpc" @@ -108,11 +111,11 @@ func (m *DownlinkMessage) GetGatewayConfiguration() *gateway.TxConfiguration { } type DeviceActivationRequest struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DevEui []byte `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3" json:"dev_eui,omitempty"` - AppEui []byte `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` - ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` } func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } @@ -170,7 +173,7 @@ func (*GatewaysRequest) Descriptor() ([]byte, []int) { return fileDescriptorRout // message GatewaysResponse is the response to the GatewaysRequest type GatewaysResponse struct { - GatewayIds []string `protobuf:"bytes,1,rep,name=gateway_ids,json=gatewayIds" json:"gateway_ids,omitempty"` + GatewayIds []github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,rep,name=gateway_ids,json=gatewayIds,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_ids,omitempty"` } func (m *GatewaysResponse) Reset() { *m = GatewaysResponse{} } @@ -180,7 +183,7 @@ func (*GatewaysResponse) Descriptor() ([]byte, []int) { return fileDescriptorRou // message RegisterGatewayRequest is used to register a Gateway with this Router type RegisterGatewayRequest struct { - GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` } func (m *RegisterGatewayRequest) Reset() { *m = RegisterGatewayRequest{} } @@ -191,7 +194,7 @@ func (*RegisterGatewayRequest) Descriptor() ([]byte, []int) { return fileDescrip // message UnregisterGatewayRequest is used to unregister a Gateway from this // Router type UnregisterGatewayRequest struct { - GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` } func (m *UnregisterGatewayRequest) Reset() { *m = UnregisterGatewayRequest{} } @@ -202,7 +205,7 @@ func (*UnregisterGatewayRequest) Descriptor() ([]byte, []int) { return fileDescr // message GatewayStatusRequest is used to request the status of a gateway from // this Router type GatewayStatusRequest struct { - GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` } func (m *GatewayStatusRequest) Reset() { *m = GatewayStatusRequest{} } @@ -921,17 +924,25 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { i = encodeVarintRouter(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if len(m.DevEui) > 0 { + if m.DevEui != nil { data[i] = 0x5a i++ - i = encodeVarintRouter(data, i, uint64(len(m.DevEui))) - i += copy(data[i:], m.DevEui) + i = encodeVarintRouter(data, i, uint64(m.DevEui.Size())) + n5, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 } - if len(m.AppEui) > 0 { + if m.AppEui != nil { data[i] = 0x62 i++ - i = encodeVarintRouter(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) + i = encodeVarintRouter(data, i, uint64(m.AppEui.Size())) + n6, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -939,11 +950,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) - n5, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n7, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n7 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -951,11 +962,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) - n6, err := m.GatewayMetadata.MarshalTo(data[i:]) + n8, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n8 } return i, nil } @@ -985,21 +996,21 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.ProtocolConfiguration.Size())) - n7, err := m.ProtocolConfiguration.MarshalTo(data[i:]) + n9, err := m.ProtocolConfiguration.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n9 } if m.GatewayConfiguration != nil { data[i] = 0x62 i++ i = encodeVarintRouter(data, i, uint64(m.GatewayConfiguration.Size())) - n8, err := m.GatewayConfiguration.MarshalTo(data[i:]) + n10, err := m.GatewayConfiguration.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n10 } return i, nil } @@ -1038,18 +1049,15 @@ func (m *GatewaysResponse) MarshalTo(data []byte) (int, error) { var l int _ = l if len(m.GatewayIds) > 0 { - for _, s := range m.GatewayIds { + for _, msg := range m.GatewayIds { data[i] = 0xa i++ - l = len(s) - for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ + i = encodeVarintRouter(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err } - data[i] = uint8(l) - i++ - i += copy(data[i:], s) + i += n } } return i, nil @@ -1070,11 +1078,15 @@ func (m *RegisterGatewayRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.GatewayId) > 0 { + if m.GatewayEui != nil { data[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) + i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) + n11, err := m.GatewayEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n11 } return i, nil } @@ -1094,11 +1106,15 @@ func (m *UnregisterGatewayRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.GatewayId) > 0 { + if m.GatewayEui != nil { data[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) + i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) + n12, err := m.GatewayEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n12 } return i, nil } @@ -1118,11 +1134,15 @@ func (m *GatewayStatusRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.GatewayId) > 0 { + if m.GatewayEui != nil { data[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) + i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) + n13, err := m.GatewayEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n13 } return i, nil } @@ -1146,11 +1166,11 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.LastStatus.Size())) - n9, err := m.LastStatus.MarshalTo(data[i:]) + n14, err := m.LastStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n14 } return i, nil } @@ -1192,11 +1212,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n10, err := m.GatewayStatus.MarshalTo(data[i:]) + n15, err := m.GatewayStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n15 } if m.ActiveGateways != 0 { data[i] = 0x20 @@ -1207,11 +1227,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n11, err := m.Uplink.MarshalTo(data[i:]) + n16, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n16 } if m.Downlink != nil { data[i] = 0xaa @@ -1219,11 +1239,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n12, err := m.Downlink.MarshalTo(data[i:]) + n17, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n17 } if m.Activations != nil { data[i] = 0xfa @@ -1231,11 +1251,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n13, err := m.Activations.MarshalTo(data[i:]) + n18, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n18 } if m.ActivationsAccepted != nil { data[i] = 0x82 @@ -1243,11 +1263,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x2 i++ i = encodeVarintRouter(data, i, uint64(m.ActivationsAccepted.Size())) - n14, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n19, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n19 } if m.ConnectedGateways != 0 { data[i] = 0xc8 @@ -1346,12 +1366,12 @@ func (m *DeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovRouter(uint64(l)) } - l = len(m.DevEui) - if l > 0 { + if m.DevEui != nil { + l = m.DevEui.Size() n += 1 + l + sovRouter(uint64(l)) } - l = len(m.AppEui) - if l > 0 { + if m.AppEui != nil { + l = m.AppEui.Size() n += 1 + l + sovRouter(uint64(l)) } if m.ProtocolMetadata != nil { @@ -1393,8 +1413,8 @@ func (m *GatewaysResponse) Size() (n int) { var l int _ = l if len(m.GatewayIds) > 0 { - for _, s := range m.GatewayIds { - l = len(s) + for _, e := range m.GatewayIds { + l = e.Size() n += 1 + l + sovRouter(uint64(l)) } } @@ -1404,8 +1424,8 @@ func (m *GatewaysResponse) Size() (n int) { func (m *RegisterGatewayRequest) Size() (n int) { var l int _ = l - l = len(m.GatewayId) - if l > 0 { + if m.GatewayEui != nil { + l = m.GatewayEui.Size() n += 1 + l + sovRouter(uint64(l)) } return n @@ -1414,8 +1434,8 @@ func (m *RegisterGatewayRequest) Size() (n int) { func (m *UnregisterGatewayRequest) Size() (n int) { var l int _ = l - l = len(m.GatewayId) - if l > 0 { + if m.GatewayEui != nil { + l = m.GatewayEui.Size() n += 1 + l + sovRouter(uint64(l)) } return n @@ -1424,8 +1444,8 @@ func (m *UnregisterGatewayRequest) Size() (n int) { func (m *GatewayStatusRequest) Size() (n int) { var l int _ = l - l = len(m.GatewayId) - if l > 0 { + if m.GatewayEui != nil { + l = m.GatewayEui.Size() n += 1 + l + sovRouter(uint64(l)) } return n @@ -1954,9 +1974,10 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevEui = append(m.DevEui[:0], data[iNdEx:postIndex]...) - if m.DevEui == nil { - m.DevEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 12: @@ -1985,9 +2006,10 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppEui = append(m.AppEui[:0], data[iNdEx:postIndex]...) - if m.AppEui == nil { - m.AppEui = []byte{} + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err } iNdEx = postIndex case 21: @@ -2307,7 +2329,7 @@ func (m *GatewaysResponse) Unmarshal(data []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field GatewayIds", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -2317,20 +2339,23 @@ func (m *GatewaysResponse) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayIds = append(m.GatewayIds, string(data[iNdEx:postIndex])) + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayIds = append(m.GatewayIds, v) + if err := m.GatewayIds[len(m.GatewayIds)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -2384,9 +2409,9 @@ func (m *RegisterGatewayRequest) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -2396,20 +2421,23 @@ func (m *RegisterGatewayRequest) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayId = string(data[iNdEx:postIndex]) + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayEui = &v + if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -2463,9 +2491,9 @@ func (m *UnregisterGatewayRequest) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -2475,20 +2503,23 @@ func (m *UnregisterGatewayRequest) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayId = string(data[iNdEx:postIndex]) + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayEui = &v + if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -2542,9 +2573,9 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -2554,20 +2585,23 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayId = string(data[iNdEx:postIndex]) + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayEui = &v + if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -3101,57 +3135,62 @@ var ( ) var fileDescriptorRouter = []byte{ - // 818 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x55, 0x4d, 0x6f, 0xda, 0x48, - 0x18, 0x5e, 0x6f, 0x56, 0x84, 0xbc, 0x40, 0x80, 0x09, 0x10, 0x2f, 0xda, 0x7c, 0xc8, 0x87, 0xdd, - 0xec, 0x47, 0x60, 0x43, 0xb4, 0x8a, 0xa2, 0x68, 0xdb, 0x92, 0xa6, 0xaa, 0x2a, 0x95, 0x2a, 0x72, - 0x92, 0x33, 0x1a, 0xcc, 0x94, 0x58, 0x10, 0xdb, 0xf5, 0x8c, 0xf3, 0x71, 0xec, 0xbf, 0xe8, 0x8f, - 0xe8, 0x0f, 0xe9, 0xa1, 0x87, 0x4a, 0x55, 0xef, 0x55, 0x7b, 0xe9, 0x7f, 0xe8, 0xa5, 0x83, 0x67, - 0x3c, 0xd8, 0x26, 0x48, 0xb4, 0x3d, 0xf5, 0x60, 0xc1, 0x3c, 0xcf, 0xf3, 0xbe, 0x7e, 0x5f, 0xbf, - 0xf3, 0xcc, 0xc0, 0xde, 0xc0, 0x66, 0xe7, 0x41, 0xaf, 0x61, 0xb9, 0x17, 0xcd, 0xd3, 0x73, 0x72, - 0x7a, 0x6e, 0x3b, 0x03, 0xfa, 0x84, 0xb0, 0x2b, 0xd7, 0x1f, 0x36, 0x19, 0x73, 0x9a, 0xd8, 0xb3, - 0x9b, 0xbe, 0x1b, 0x30, 0xe2, 0xcb, 0x9f, 0x86, 0xe7, 0xbb, 0xcc, 0x45, 0x19, 0xb1, 0xaa, 0x6f, - 0xcf, 0x93, 0x80, 0x3f, 0x22, 0xac, 0x7e, 0x30, 0x8f, 0x3c, 0x94, 0x5a, 0xee, 0x48, 0xfd, 0x91, - 0xc1, 0xfb, 0xf3, 0x04, 0x0f, 0x30, 0x23, 0x57, 0xf8, 0x26, 0xfa, 0x15, 0xa1, 0xc6, 0x0e, 0x94, - 0x4e, 0x82, 0x1e, 0xb5, 0x7c, 0xbb, 0x47, 0x4c, 0xf2, 0x2c, 0x20, 0x94, 0xa1, 0x35, 0x00, 0x29, - 0xea, 0xda, 0x7d, 0x5d, 0xdb, 0xd4, 0xb6, 0x96, 0xcc, 0x25, 0x89, 0x3c, 0xea, 0x1b, 0x2f, 0x35, - 0x28, 0x9c, 0x79, 0x23, 0xdb, 0x19, 0x76, 0x08, 0xa5, 0x78, 0x40, 0x90, 0x0e, 0x8b, 0x1e, 0xbe, - 0x19, 0xb9, 0x58, 0xa8, 0xf3, 0x66, 0xb4, 0x44, 0x6d, 0x28, 0x47, 0xb5, 0x76, 0x2f, 0x08, 0xc3, - 0x7d, 0xcc, 0xb0, 0x9e, 0xe3, 0x9a, 0x5c, 0xab, 0xd2, 0x50, 0x5d, 0x98, 0xd7, 0x1d, 0xc9, 0x99, - 0xa5, 0x08, 0x8c, 0x10, 0x74, 0x07, 0x4a, 0x51, 0x35, 0x2a, 0x43, 0x3e, 0xcc, 0xb0, 0xd2, 0x88, - 0x7a, 0x89, 0x25, 0x28, 0x4a, 0x2c, 0x02, 0x8c, 0xd7, 0x1a, 0x14, 0x8f, 0xdc, 0x2b, 0x67, 0xbe, - 0x82, 0x8f, 0xa1, 0xa6, 0x0a, 0xb6, 0x5c, 0xe7, 0xa9, 0x3d, 0x08, 0x7c, 0xcc, 0x6c, 0xd7, 0x91, - 0x55, 0xff, 0x3a, 0xa9, 0xfa, 0xf4, 0xfa, 0x7e, 0x5c, 0x60, 0x56, 0x23, 0x26, 0x01, 0xa3, 0x0e, - 0x54, 0xa3, 0xfa, 0x93, 0x09, 0x45, 0x13, 0xba, 0x6a, 0x22, 0x9d, 0xaf, 0x22, 0x89, 0x04, 0x6a, - 0x7c, 0xd2, 0x60, 0xf5, 0x88, 0x5c, 0xda, 0x16, 0x69, 0x5b, 0xcc, 0xbe, 0x14, 0x52, 0x39, 0xb8, - 0xd9, 0x6d, 0xad, 0xc2, 0x62, 0x9f, 0x5c, 0x76, 0x49, 0x60, 0x87, 0x7d, 0xe4, 0xcd, 0x0c, 0x5f, - 0x3e, 0x08, 0xec, 0x31, 0x81, 0x3d, 0x2f, 0x24, 0xf2, 0x82, 0xe0, 0xcb, 0x31, 0x71, 0xeb, 0xe4, - 0xaa, 0xdf, 0x3d, 0xb9, 0xda, 0x57, 0x4c, 0xee, 0x9d, 0x06, 0xfa, 0x74, 0xab, 0xd4, 0x73, 0x1d, - 0xfa, 0x43, 0x8f, 0xb0, 0x0c, 0xc5, 0x87, 0x02, 0xa7, 0x72, 0x72, 0xc6, 0x2e, 0x94, 0x26, 0x90, - 0xec, 0x70, 0x03, 0x72, 0x13, 0x1b, 0x52, 0xde, 0xe5, 0x02, 0xf7, 0x21, 0x28, 0x1f, 0x52, 0x63, - 0x0f, 0x6a, 0x26, 0x19, 0xd8, 0x94, 0x1f, 0x37, 0x32, 0x78, 0x4e, 0x07, 0xef, 0x83, 0x7e, 0xe6, - 0xf8, 0xdf, 0x14, 0xfa, 0x1f, 0x54, 0x64, 0xc0, 0x09, 0xc3, 0x2c, 0xa0, 0x73, 0x86, 0x1d, 0x43, - 0x35, 0x15, 0x26, 0x9b, 0xdc, 0x83, 0xdc, 0x08, 0x53, 0xd6, 0xa5, 0x21, 0x1c, 0x06, 0xe6, 0x5a, - 0x35, 0xf5, 0x41, 0x85, 0x5a, 0xda, 0xd6, 0x84, 0xb1, 0x54, 0x40, 0x46, 0x11, 0x0a, 0x89, 0x0a, - 0x8c, 0xe7, 0x0b, 0xb0, 0x9c, 0x4a, 0xbe, 0x03, 0xcb, 0x51, 0x51, 0x89, 0xfc, 0xd0, 0x18, 0x1f, - 0xbc, 0x26, 0xa7, 0xa8, 0x59, 0x18, 0xc4, 0xeb, 0x42, 0x7f, 0x40, 0x11, 0x8f, 0x37, 0x1b, 0xe9, - 0x4a, 0x9c, 0xea, 0xbf, 0xf0, 0x98, 0x82, 0xb9, 0x2c, 0xe0, 0x68, 0x4a, 0xc8, 0x80, 0x4c, 0x10, - 0x1e, 0x82, 0x72, 0x57, 0xc5, 0x73, 0x4a, 0x06, 0xfd, 0x0e, 0xd9, 0xbe, 0x3c, 0x79, 0xa4, 0x75, - 0xe2, 0x2a, 0xc5, 0xa1, 0x7f, 0x20, 0x87, 0xd5, 0x0e, 0xa7, 0xfa, 0xc6, 0x94, 0x34, 0x4e, 0xa3, - 0xff, 0xa1, 0x12, 0x5b, 0x76, 0xb1, 0x65, 0x11, 0x8f, 0x91, 0xbe, 0xbe, 0x39, 0x15, 0xb6, 0x12, - 0xd3, 0xb5, 0xa5, 0x0c, 0x6d, 0x03, 0xe2, 0x9b, 0xd8, 0x21, 0x16, 0x5f, 0x4c, 0x9a, 0xfc, 0x33, - 0x6c, 0xb2, 0xac, 0x18, 0xd5, 0xe7, 0xdf, 0x30, 0x01, 0xbb, 0x3d, 0xdf, 0x1d, 0x12, 0x9f, 0xea, - 0x7f, 0x85, 0xea, 0x92, 0x22, 0x0e, 0x05, 0xde, 0xfa, 0xac, 0x41, 0xc6, 0x0c, 0xef, 0x3f, 0xb4, - 0x0b, 0x85, 0xc4, 0xc4, 0xd1, 0x8c, 0xa1, 0xd6, 0xb3, 0x61, 0xc1, 0x6d, 0x6b, 0xb8, 0xa5, 0xf1, - 0x97, 0x65, 0xc4, 0xcd, 0x82, 0xaa, 0x0d, 0x79, 0xab, 0x26, 0x6e, 0x9a, 0x84, 0xf8, 0x1e, 0x2c, - 0xa9, 0xab, 0x0b, 0xe9, 0x91, 0x3e, 0x7d, 0x9b, 0xd5, 0x57, 0x23, 0x26, 0x75, 0x09, 0xfc, 0xab, - 0x71, 0x5f, 0x67, 0xe5, 0xc9, 0xc2, 0xdd, 0xa6, 0x64, 0xb7, 0x1f, 0xae, 0xf5, 0xcd, 0xd9, 0x02, - 0xb1, 0xdd, 0x5a, 0x6f, 0x7f, 0x86, 0x82, 0xe8, 0xbe, 0x83, 0x1d, 0xfe, 0x06, 0x9f, 0x8f, 0x2a, - 0xab, 0x3e, 0xa4, 0xaa, 0x23, 0xe5, 0xfd, 0xba, 0x3e, 0x4d, 0xc8, 0xfd, 0x7b, 0x00, 0xc5, 0x94, - 0xc1, 0xd1, 0x7a, 0x24, 0xbe, 0xdd, 0xf9, 0x93, 0x0f, 0x84, 0xee, 0x42, 0x79, 0xca, 0xe4, 0x48, - 0x35, 0x31, 0xcb, 0xff, 0xb1, 0x04, 0x8f, 0xd3, 0x13, 0xfc, 0x2d, 0x55, 0x68, 0xc2, 0x7f, 0xf5, - 0xb5, 0x19, 0xac, 0x32, 0x7a, 0x46, 0xa6, 0x51, 0xa3, 0x4d, 0xc6, 0xd7, 0xd2, 0xb0, 0x08, 0x3c, - 0x2c, 0xbd, 0xfa, 0xb0, 0xae, 0xbd, 0xe1, 0xcf, 0x7b, 0xfe, 0xbc, 0xf8, 0xb8, 0xfe, 0x53, 0x2f, - 0x13, 0x9e, 0xd2, 0xbb, 0x5f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x79, 0xa9, 0x26, 0x5e, 0xa4, 0x09, + // 898 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0xc7, 0xbb, 0x28, 0x9b, 0xbe, 0x24, 0x4d, 0x32, 0x9b, 0x64, 0x4d, 0xc4, 0xb6, 0x95, 0x0f, + 0xb0, 0xfc, 0xd9, 0x84, 0x66, 0xb5, 0x5a, 0xad, 0x56, 0xfc, 0x49, 0x69, 0x85, 0x2a, 0x91, 0xaa, + 0x72, 0xdb, 0x0b, 0x97, 0x68, 0xe2, 0x4c, 0x5d, 0x2b, 0xa9, 0x6d, 0x3c, 0xe3, 0xb4, 0x3d, 0xf2, + 0x2d, 0xf8, 0x10, 0x7c, 0x10, 0x0e, 0x1c, 0x90, 0x10, 0x1c, 0x38, 0x20, 0x04, 0x1f, 0x83, 0x0b, + 0x93, 0xf1, 0xcc, 0xc4, 0x76, 0x1a, 0xd1, 0x02, 0xaa, 0xb4, 0x07, 0x2b, 0x99, 0xdf, 0xfb, 0xcd, + 0xef, 0xbd, 0xe7, 0xf7, 0xe6, 0x8d, 0xe1, 0x85, 0xeb, 0xb1, 0xb3, 0x78, 0xd4, 0x71, 0x82, 0xf3, + 0xee, 0xf1, 0x19, 0x39, 0x3e, 0xf3, 0x7c, 0x97, 0x1e, 0x10, 0x76, 0x11, 0x44, 0x93, 0x2e, 0x63, + 0x7e, 0x17, 0x87, 0x5e, 0x37, 0x0a, 0x62, 0x46, 0x22, 0xf9, 0xd3, 0x09, 0xa3, 0x80, 0x05, 0xa8, + 0x90, 0xac, 0xda, 0x4f, 0x53, 0x02, 0x6e, 0xe0, 0x06, 0x5d, 0x61, 0x1e, 0xc5, 0xa7, 0x62, 0x25, + 0x16, 0xe2, 0x5f, 0xb2, 0x2d, 0x43, 0x5f, 0xe9, 0x8f, 0x3f, 0x92, 0xfe, 0xea, 0x26, 0x74, 0x41, + 0x75, 0x82, 0xa9, 0xfe, 0x23, 0x37, 0xbf, 0xbc, 0xc9, 0x66, 0x17, 0x33, 0x72, 0x81, 0xaf, 0xd4, + 0x6f, 0xb2, 0xd5, 0xda, 0x86, 0xda, 0x51, 0x3c, 0xa2, 0x4e, 0xe4, 0x8d, 0x88, 0x4d, 0xbe, 0x8e, + 0x09, 0x65, 0xe8, 0x31, 0x80, 0x24, 0x0d, 0xbd, 0xb1, 0x69, 0x6c, 0x19, 0x4f, 0xd6, 0xec, 0x35, + 0x89, 0xec, 0x8f, 0xad, 0xef, 0x0c, 0xa8, 0x9c, 0x84, 0x53, 0xcf, 0x9f, 0x0c, 0x08, 0xa5, 0xd8, + 0x25, 0xc8, 0x84, 0x07, 0x21, 0xbe, 0x9a, 0x06, 0x38, 0x61, 0x97, 0x6d, 0xb5, 0x44, 0x7d, 0xa8, + 0xab, 0x58, 0x87, 0xe7, 0x84, 0xe1, 0x31, 0x66, 0xd8, 0x2c, 0x71, 0x4e, 0xa9, 0xd7, 0xe8, 0xe8, + 0x2c, 0xec, 0xcb, 0x81, 0xb4, 0xd9, 0x35, 0x05, 0x2a, 0x04, 0x7d, 0x02, 0x35, 0x15, 0x8d, 0x56, + 0x28, 0x0b, 0x85, 0x87, 0x1d, 0x95, 0x4b, 0x4a, 0xa0, 0x2a, 0x31, 0x05, 0x58, 0x3f, 0x18, 0x50, + 0xdd, 0x0d, 0x2e, 0xfc, 0x9b, 0x05, 0x7c, 0x08, 0x2d, 0x1d, 0xb0, 0x13, 0xf8, 0xa7, 0x9e, 0x1b, + 0x47, 0x98, 0x79, 0x81, 0x2f, 0xa3, 0x7e, 0x6b, 0x11, 0xf5, 0xf1, 0xe5, 0xe7, 0x69, 0x82, 0xdd, + 0x54, 0x96, 0x0c, 0x8c, 0x06, 0xd0, 0x54, 0xf1, 0x67, 0x05, 0x93, 0x24, 0x4c, 0x9d, 0x44, 0x5e, + 0xaf, 0x21, 0x0d, 0x19, 0xd4, 0xfa, 0xe5, 0x1e, 0x3c, 0xda, 0x25, 0x33, 0xcf, 0x21, 0x7d, 0x87, + 0x79, 0xb3, 0x84, 0x2a, 0x0b, 0xb7, 0x3a, 0xad, 0x03, 0x78, 0x30, 0x26, 0xb3, 0x21, 0x89, 0x3d, + 0x91, 0x47, 0x79, 0xe7, 0xf9, 0xaf, 0xbf, 0x6d, 0x6e, 0xff, 0x53, 0xdb, 0x38, 0x41, 0x44, 0xba, + 0xec, 0x2a, 0x24, 0xb4, 0xc3, 0x5d, 0xee, 0x9d, 0xec, 0xdb, 0x05, 0xae, 0xb2, 0x17, 0x7b, 0x73, + 0x3d, 0x1c, 0x86, 0x42, 0xaf, 0xfc, 0xaf, 0xf4, 0xfa, 0x61, 0x28, 0xf4, 0xb8, 0xca, 0x5c, 0xef, + 0xda, 0x3e, 0x69, 0xfe, 0xe7, 0x3e, 0x69, 0xdd, 0xa2, 0x4f, 0x7e, 0x36, 0xc0, 0x5c, 0x7e, 0xb1, + 0x34, 0x0c, 0x7c, 0xfa, 0x5a, 0x37, 0x4c, 0x1d, 0xaa, 0x5f, 0x24, 0x38, 0x95, 0x7d, 0x62, 0xf9, + 0x50, 0x5b, 0x40, 0x32, 0xc3, 0xaf, 0xa0, 0xb4, 0x38, 0xf4, 0x94, 0x67, 0x79, 0x9f, 0x57, 0xf5, + 0x25, 0xaf, 0xea, 0xf3, 0x5b, 0x54, 0x55, 0xaa, 0xce, 0x2b, 0x0b, 0x7a, 0x60, 0x50, 0x8b, 0x41, + 0xcb, 0x26, 0xae, 0x47, 0xf9, 0x18, 0x95, 0x0c, 0xd5, 0xb1, 0x29, 0xaf, 0xf3, 0x5e, 0x12, 0xef, + 0xf6, 0xff, 0xf0, 0xca, 0x7b, 0xca, 0x9a, 0x81, 0x79, 0xe2, 0x47, 0x77, 0xef, 0x37, 0x82, 0x86, + 0xb4, 0x1c, 0x31, 0xcc, 0x62, 0x7a, 0x17, 0x3e, 0x0f, 0xa1, 0x99, 0xf3, 0x29, 0xcb, 0xfa, 0x02, + 0x4a, 0x53, 0x4c, 0xd9, 0x90, 0x0a, 0x58, 0x38, 0x2d, 0xf5, 0x5a, 0xba, 0x85, 0x12, 0xb6, 0x1c, + 0x8b, 0x36, 0xcc, 0xa9, 0x09, 0x64, 0x55, 0xa1, 0x92, 0x09, 0xdf, 0xfa, 0xe6, 0x3e, 0xac, 0xe7, + 0xc4, 0xb7, 0x61, 0x5d, 0x65, 0x94, 0xd1, 0x87, 0xce, 0xfc, 0x62, 0xb3, 0xb9, 0x89, 0xda, 0x15, + 0x37, 0x1d, 0x17, 0x7a, 0x17, 0xaa, 0x78, 0x7e, 0xbc, 0xc8, 0x50, 0xe2, 0xd4, 0x7c, 0x93, 0xef, + 0xa9, 0xd8, 0xeb, 0x09, 0xac, 0xfa, 0x12, 0x59, 0x50, 0x88, 0xc5, 0x25, 0x23, 0xcf, 0x51, 0x5a, + 0x53, 0x5a, 0xd0, 0x3b, 0x50, 0x1c, 0xcb, 0xc9, 0x2e, 0x87, 0x45, 0x9a, 0xa5, 0x6d, 0xe8, 0x43, + 0x28, 0x61, 0x7d, 0xa6, 0xa9, 0xb9, 0xb9, 0x44, 0x4d, 0x9b, 0xd1, 0xc7, 0xd0, 0x48, 0x2d, 0x87, + 0xd8, 0x71, 0x48, 0xc8, 0xc8, 0xd8, 0xdc, 0x5a, 0xda, 0xf6, 0x30, 0xc5, 0xeb, 0x4b, 0x1a, 0x7a, + 0x0a, 0x88, 0x1f, 0x5b, 0x9f, 0x38, 0x7c, 0xb1, 0x48, 0xf2, 0x3d, 0x91, 0x64, 0x5d, 0x5b, 0x74, + 0x9e, 0x1f, 0xc0, 0x02, 0x1c, 0x8e, 0xa2, 0x60, 0x42, 0x22, 0x6a, 0xbe, 0x2f, 0xd8, 0x35, 0x6d, + 0xd8, 0x49, 0xf0, 0xde, 0x5f, 0x06, 0x14, 0x6c, 0xf1, 0x39, 0x82, 0x9e, 0x41, 0x25, 0x53, 0x71, + 0xb4, 0xa2, 0xa8, 0xed, 0xa2, 0x08, 0xb8, 0xef, 0x4c, 0x9e, 0x18, 0xdc, 0x59, 0x21, 0xb9, 0xb9, + 0x51, 0xb3, 0x23, 0x3f, 0x72, 0x32, 0x37, 0x79, 0x86, 0xfc, 0x19, 0xac, 0xe9, 0x4f, 0x03, 0x64, + 0x2a, 0x7e, 0xfe, 0x6b, 0xa1, 0xfd, 0x48, 0x59, 0x72, 0x97, 0xec, 0x47, 0x06, 0x9f, 0x64, 0x45, + 0x39, 0x4b, 0x09, 0xda, 0xd4, 0xb4, 0xeb, 0x2f, 0xaf, 0xf6, 0xd6, 0x6a, 0x42, 0xd2, 0x6e, 0xbd, + 0x9f, 0xee, 0x41, 0x25, 0xc9, 0x7e, 0x80, 0x7d, 0xee, 0x21, 0xe2, 0xa5, 0x2a, 0xea, 0x17, 0xa9, + 0xe3, 0xc8, 0x4d, 0xbb, 0xb6, 0xb9, 0x6c, 0x90, 0xfd, 0xfb, 0x0a, 0xaa, 0xb9, 0xb9, 0x84, 0x36, + 0x14, 0xf9, 0xfa, 0x81, 0xb5, 0x78, 0x41, 0xe8, 0x53, 0xa8, 0x2f, 0x8d, 0x17, 0xa4, 0x93, 0x58, + 0x35, 0x79, 0x52, 0x02, 0x5f, 0xe6, 0x2b, 0xf8, 0x76, 0x2e, 0xd0, 0xcc, 0xf9, 0x6b, 0x3f, 0x5e, + 0x61, 0xd5, 0x07, 0xbd, 0x20, 0x65, 0x74, 0x69, 0xb3, 0xfb, 0x5b, 0x79, 0x38, 0xd9, 0xb8, 0x53, + 0xfb, 0xfe, 0x8f, 0x0d, 0xe3, 0x47, 0xfe, 0xfc, 0xce, 0x9f, 0x6f, 0xff, 0xdc, 0x78, 0x63, 0x54, + 0x10, 0xf7, 0xd2, 0xb3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xcf, 0xbb, 0xfa, 0x29, 0x33, 0x0b, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index c78ad8db4..20b7ba043 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; @@ -24,8 +26,8 @@ message DownlinkMessage { message DeviceActivationRequest { bytes payload = 1; - bytes dev_eui = 11; - bytes app_eui = 12; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; gateway.RxMetadata gateway_metadata = 22; } @@ -56,24 +58,24 @@ message GatewaysRequest {} // message GatewaysResponse is the response to the GatewaysRequest message GatewaysResponse { - repeated string gateway_ids = 1; + repeated bytes gateway_ids = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; } // message RegisterGatewayRequest is used to register a Gateway with this Router message RegisterGatewayRequest { - string gateway_id = 1; + bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; } // message UnregisterGatewayRequest is used to unregister a Gateway from this // Router message UnregisterGatewayRequest { - string gateway_id = 1; + bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; } // message GatewayStatusRequest is used to request the status of a gateway from // this Router message GatewayStatusRequest { - string gateway_id = 1; + bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; } // message GatewayStatusResponse is the response to the GatewayStatusRequest From b7990eee5cce3d10822d8f4fe210ec4b788fcead Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 15:28:00 +0200 Subject: [PATCH 1423/2266] Update development setup --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9a3da43d7..8f165160a 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,15 @@ When you get started with The Things Network, you'll probably have some question - Read background information on the [wiki](http://thethingsnetwork.org/wiki) - Send an email to @johanstokking (johan@thethingsnetwork.org) -## Running the Network - -We are working hard on building a working version of the v1.0 backend. Currently, we have the following components working: - -- [x] **Router** -- [x] **Broker** -- [x] **Handler** +## Development Setup + +1. Make sure you have [Go](https://golang.org), [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) installed. +2. Fork this repository +3. Clone the fork: `git clone --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` +4. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` +5. Get some Go dependencies: `make deps test-deps` +6. _Optional:_ install [protocol compiler](https://github.com/google/protobuf) and `make proto-deps` +7. Run the tests: `make test` ## Contributing From 6654495c32b2fe3d9e11921c2d7d04fa65d422a2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 16:02:40 +0200 Subject: [PATCH 1424/2266] Add proto marshalTo to core/types --- core/types/data_rate.go | 11 ++++++++++ core/types/dev_addr.go | 11 ++++++++++ core/types/device_type.go | 11 ++++++++++ core/types/eui.go | 44 +++++++++++++++++++++++++++++++++++++++ core/types/keys.go | 44 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 121 insertions(+) diff --git a/core/types/data_rate.go b/core/types/data_rate.go index 5e82d42e5..02bdeef68 100644 --- a/core/types/data_rate.go +++ b/core/types/data_rate.go @@ -69,6 +69,17 @@ func (datr *DataRate) UnmarshalBinary(data []byte) error { return datr.UnmarshalText(data) } +// MarshalTo is used by Protobuf +func (datr DataRate) MarshalTo(b []byte) (int, error) { + copy(b, datr.Bytes()) + return len(datr.Bytes()), nil +} + +// Size is used by Protobuf +func (datr DataRate) Size() int { + return len(datr.Bytes()) +} + // Marshal implements the Marshaler interface. func (datr DataRate) Marshal() ([]byte, error) { return datr.MarshalBinary() diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 53e183b09..f95c2de06 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -63,6 +63,17 @@ func (addr *DevAddr) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (addr DevAddr) MarshalTo(b []byte) (int, error) { + copy(b, addr.Bytes()) + return 4, nil +} + +// Size is used by Protobuf +func (addr DevAddr) Size() int { + return 4 +} + // Marshal implements the Marshaler interface. func (addr DevAddr) Marshal() ([]byte, error) { return addr.MarshalBinary() diff --git a/core/types/device_type.go b/core/types/device_type.go index aa1567ea6..a30e9b6a9 100644 --- a/core/types/device_type.go +++ b/core/types/device_type.go @@ -84,6 +84,17 @@ func (devType *DeviceType) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (devType *DeviceType) MarshalTo(b []byte) (int, error) { + copy(b, []byte(devType.String())) + return len(devType.String()), nil +} + +// Size is used by Protobuf +func (devType *DeviceType) Size() int { + return len(devType.String()) +} + // Marshal implements the Marshaler interface. func (devType DeviceType) Marshal() ([]byte, error) { return devType.MarshalBinary() diff --git a/core/types/eui.go b/core/types/eui.go index e94544e61..7fed07c22 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -71,6 +71,17 @@ func (eui *EUI64) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (eui *EUI64) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *EUI64) Size() int { + return 8 +} + // Marshal implements the Marshaler interface. func (eui EUI64) Marshal() ([]byte, error) { return eui.MarshalBinary() @@ -139,6 +150,17 @@ func (eui *AppEUI) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (eui *AppEUI) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *AppEUI) Size() int { + return 8 +} + // Marshal implements the Marshaler interface. func (eui AppEUI) Marshal() ([]byte, error) { return eui.MarshalBinary() @@ -207,6 +229,17 @@ func (eui *DevEUI) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (eui *DevEUI) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *DevEUI) Size() int { + return 8 +} + // Marshal implements the Marshaler interface. func (eui DevEUI) Marshal() ([]byte, error) { return eui.MarshalBinary() @@ -275,6 +308,17 @@ func (eui *GatewayEUI) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (eui *GatewayEUI) MarshalTo(b []byte) (int, error) { + copy(b, eui.Bytes()) + return 8, nil +} + +// Size is used by Protobuf +func (eui *GatewayEUI) Size() int { + return 8 +} + // Marshal implements the Marshaler interface. func (eui GatewayEUI) Marshal() ([]byte, error) { return eui.MarshalBinary() diff --git a/core/types/keys.go b/core/types/keys.go index bdaa8df54..bbdb05dfd 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -72,6 +72,17 @@ func (key *AES128Key) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (key *AES128Key) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *AES128Key) Size() int { + return 16 +} + // Marshal implements the Marshaler interface. func (key AES128Key) Marshal() ([]byte, error) { return key.MarshalBinary() @@ -139,6 +150,17 @@ func (key *AppKey) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (key *AppKey) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *AppKey) Size() int { + return 16 +} + // Marshal implements the Marshaler interface. func (key AppKey) Marshal() ([]byte, error) { return key.MarshalBinary() @@ -206,6 +228,17 @@ func (key *AppSKey) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (key *AppSKey) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *AppSKey) Size() int { + return 16 +} + // Marshal implements the Marshaler interface. func (key AppSKey) Marshal() ([]byte, error) { return key.MarshalBinary() @@ -274,6 +307,17 @@ func (key *NwkSKey) UnmarshalBinary(data []byte) error { return nil } +// MarshalTo is used by Protobuf +func (key *NwkSKey) MarshalTo(b []byte) (int, error) { + copy(b, key.Bytes()) + return 16, nil +} + +// Size is used by Protobuf +func (key *NwkSKey) Size() int { + return 16 +} + // Marshal implements the Marshaler interface. func (key NwkSKey) Marshal() ([]byte, error) { return key.MarshalBinary() From 9caa6b93c2a10dc278b82c5910685e2a5f5f7a2a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 2 May 2016 17:18:48 +0200 Subject: [PATCH 1425/2266] [refactor/router] Implement GatewayStatus Handling --- api/gateway/status_message.go | 194 +++++++++++++++++++++++++++++ api/gateway/status_message_test.go | 65 ++++++++++ core/router/gateway_status.go | 57 +++++++++ core/router/gateway_status_test.go | 77 ++++++++++++ core/router/router.go | 17 +++ core/router/router_test.go | 1 + core/storage/hslice.go | 168 ------------------------- core/storage/hslice_test.go | 130 ------------------- core/storage/redis.go | 90 +++++++++++++ core/storage/redis_test.go | 75 +++++++++++ 10 files changed, 576 insertions(+), 298 deletions(-) create mode 100644 api/gateway/status_message.go create mode 100644 api/gateway/status_message_test.go create mode 100644 core/router/gateway_status.go create mode 100644 core/router/gateway_status_test.go create mode 100644 core/router/router.go create mode 100644 core/router/router_test.go delete mode 100644 core/storage/hslice.go delete mode 100644 core/storage/hslice_test.go create mode 100644 core/storage/redis.go create mode 100644 core/storage/redis_test.go diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go new file mode 100644 index 000000000..6372e1441 --- /dev/null +++ b/api/gateway/status_message.go @@ -0,0 +1,194 @@ +package gateway + +import ( + "fmt" + "strings" + + "github.com/TheThingsNetwork/ttn/core/storage" +) + +// StatusMessageProperties contains all properties of a StatusMessage that can +// be stored in Redis. +var StatusMessageProperties = []string{ + "timestamp", + "time", + "ip", + "platform", + "contact_email", + "description", + "gps.time", + "gps.latitude", + "gps.longitude", + "gps.altitude", + "rtt", + "rx_in", + "rx_ok", + "tx_in", + "tx_ok", +} + +// ToStringStringMap converts the given properties of StatusMessage to a +// map[string]string for storage in Redis. +func (status *StatusMessage) ToStringStringMap(properties ...string) (map[string]string, error) { + output := make(map[string]string) + for _, p := range properties { + property, err := status.formatProperty(p) + if err != nil { + return output, err + } + if property != "" { + output[p] = property + } + } + return output, nil +} + +// FromStringStringMap imports known values from the input to a StatusMessage. +func (status *StatusMessage) FromStringStringMap(input map[string]string) error { + for k, v := range input { + status.parseProperty(k, v) + } + return nil +} + +func (status *StatusMessage) formatProperty(property string) (formatted string, err error) { + switch property { + case "timestamp": + formatted = storage.FormatUint32(status.Timestamp) + case "time": + formatted = storage.FormatInt64(status.Time) + case "ip": + formatted = strings.Join(status.Ip, ",") + case "platform": + formatted = status.Platform + case "contact_email": + formatted = status.ContactEmail + case "description": + formatted = status.Description + case "gps.time": + if status.Gps != nil { + formatted = storage.FormatInt64(status.Gps.Time) + } + case "gps.latitude": + if status.Gps != nil { + formatted = storage.FormatFloat32(status.Gps.Latitude) + } + case "gps.longitude": + if status.Gps != nil { + formatted = storage.FormatFloat32(status.Gps.Longitude) + } + case "gps.altitude": + if status.Gps != nil { + formatted = storage.FormatInt32(status.Gps.Altitude) + } + case "rtt": + formatted = storage.FormatUint32(status.Rtt) + case "rx_in": + formatted = storage.FormatUint32(status.RxIn) + case "rx_ok": + formatted = storage.FormatUint32(status.RxOk) + case "tx_in": + formatted = storage.FormatUint32(status.TxIn) + case "tx_ok": + formatted = storage.FormatUint32(status.TxOk) + default: + err = fmt.Errorf("Property %s does not exist in StatusMessage", property) + } + return +} + +func (status *StatusMessage) parseProperty(property string, value string) error { + if value == "" { + return nil + } + switch property { + case "timestamp": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + status.Timestamp = val + case "time": + val, err := storage.ParseInt64(value) + if err != nil { + return err + } + status.Time = val + case "ip": + val := strings.Split(value, ",") + status.Ip = val + case "platform": + status.Platform = value + case "contact_email": + status.ContactEmail = value + case "description": + status.Description = value + case "gps.time": + if status.Gps == nil { + status.Gps = &GPSMetadata{} + } + val, err := storage.ParseInt64(value) + if err != nil { + return err + } + status.Gps.Time = val + case "gps.latitude": + if status.Gps == nil { + status.Gps = &GPSMetadata{} + } + val, err := storage.ParseFloat32(value) + if err != nil { + return err + } + status.Gps.Latitude = val + case "gps.longitude": + if status.Gps == nil { + status.Gps = &GPSMetadata{} + } + val, err := storage.ParseFloat32(value) + if err != nil { + return err + } + status.Gps.Longitude = val + case "gps.altitude": + if status.Gps == nil { + status.Gps = &GPSMetadata{} + } + val, err := storage.ParseInt32(value) + if err != nil { + return err + } + status.Gps.Altitude = val + case "rtt": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + status.Rtt = val + case "rx_in": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + status.RxIn = val + case "rx_ok": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + status.RxOk = val + case "tx_in": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + status.TxIn = val + case "tx_ok": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + status.TxOk = val + } + return nil +} diff --git a/api/gateway/status_message_test.go b/api/gateway/status_message_test.go new file mode 100644 index 000000000..9b2fd5b5c --- /dev/null +++ b/api/gateway/status_message_test.go @@ -0,0 +1,65 @@ +package gateway + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func getStatusMessage() (status *StatusMessage, smap map[string]string) { + t := int64(1462201853428843766) + return &StatusMessage{ + Timestamp: 12345, + Time: t, + Ip: []string{"169.50.131.24", "2a03:8180:1401:14f::2"}, + Platform: "The Things Gateway", + ContactEmail: "contact@email.net", + Description: "Description", + Gps: &GPSMetadata{ + Time: t, + Latitude: 52.3737171, + Longitude: 4.884567, + Altitude: 9, + }, + Rtt: 12, + RxIn: 42, + RxOk: 41, + TxIn: 52, + TxOk: 51, + }, map[string]string{ + "timestamp": "12345", + "time": "1462201853428843766", + "ip": "169.50.131.24,2a03:8180:1401:14f::2", + "platform": "The Things Gateway", + "contact_email": "contact@email.net", + "description": "Description", + "gps.time": "1462201853428843766", + "gps.latitude": "52.37372", + "gps.longitude": "4.884567", + "gps.altitude": "9", + "rtt": "12", + "rx_in": "42", + "rx_ok": "41", + "tx_in": "52", + "tx_ok": "51", + } +} + +func TestToStringMap(t *testing.T) { + a := New(t) + status, expected := getStatusMessage() + smap, err := status.ToStringStringMap(StatusMessageProperties...) + a.So(err, ShouldBeNil) + a.So(smap, ShouldResemble, expected) +} + +func TestFromStringMap(t *testing.T) { + a := New(t) + status := &StatusMessage{} + expected, smap := getStatusMessage() + err := status.FromStringStringMap(smap) + a.So(err, ShouldBeNil) + a.So(status, ShouldResemble, expected) +} + +// TODO: Test error cases diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go new file mode 100644 index 000000000..a3c9b5b16 --- /dev/null +++ b/core/router/gateway_status.go @@ -0,0 +1,57 @@ +package router + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + "gopkg.in/redis.v3" +) + +type GatewayStatusStore interface { + Upsert(id types.GatewayEUI, status *gateway.StatusMessage) error + Get(id types.GatewayEUI) (*gateway.StatusMessage, error) +} + +type redisGatewayStore struct { + client *redis.Client +} + +func NewGatewayStatusStore(client *redis.Client) GatewayStatusStore { + return &redisGatewayStore{ + client: client, + } +} + +func (s *redisGatewayStore) key(gatewayEUI types.GatewayEUI) string { + return fmt.Sprintf("gateway:%s", gatewayEUI) +} + +func (s *redisGatewayStore) Upsert(gatewayEUI types.GatewayEUI, status *gateway.StatusMessage) error { + m, err := status.ToStringStringMap(gateway.StatusMessageProperties...) + if err != nil { + return err + } + return s.client.HMSetMap(s.key(gatewayEUI), m).Err() +} + +func (s *redisGatewayStore) Get(gatewayEUI types.GatewayEUI) (*gateway.StatusMessage, error) { + status := &gateway.StatusMessage{} + res, err := s.client.HGetAllMap(s.key(gatewayEUI)).Result() + if err != nil { + return nil, err + } + err = status.FromStringStringMap(res) + if err != nil { + return nil, err + } + return status, nil +} + +func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *gateway.StatusMessage) error { + err := r.gatewayStatusStore.Upsert(gatewayEUI, status) + if err != nil { + return err + } + return nil +} diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go new file mode 100644 index 000000000..61770d66f --- /dev/null +++ b/core/router/gateway_status_test.go @@ -0,0 +1,77 @@ +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" + + "gopkg.in/redis.v3" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) +} + +func TestNewGatewayStatusStore(t *testing.T) { + a := New(t) + client := getRedisClient() + store := NewGatewayStatusStore(client) + a.So(store, ShouldNotBeNil) +} + +func TestGatewayGetUpsert(t *testing.T) { + a := New(t) + eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 1} + client := getRedisClient() + store := NewGatewayStatusStore(client) + + // Cleanup before and after + client.Del(store.(*redisGatewayStore).key(eui)) + // defer client.Del(store.(*redisGatewayStore).key(eui)) + + // Get non-existing gateway status -> expect empty + status, err := store.Get(eui) + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, gateway.StatusMessage{}) + + // Upsert -> expect no error + statusMessage := &gateway.StatusMessage{Description: "Fake Gateway"} + err = store.Upsert(eui, statusMessage) + a.So(err, ShouldBeNil) + + // Get existing gateway status -> expect status + status, err = store.Get(eui) + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} + +func TestHandleGatewayStatus(t *testing.T) { + a := New(t) + eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 2} + client := getRedisClient() + store := NewGatewayStatusStore(client) + router := &router{ + gatewayStatusStore: store, + } + + // Handle + statusMessage := &gateway.StatusMessage{Description: "Fake Gateway"} + err := router.HandleGatewayStatus(eui, statusMessage) + a.So(err, ShouldBeNil) + + // Check storage + status, err := store.Get(eui) + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} + +// TODO: Test error cases diff --git a/core/router/router.go b/core/router/router.go new file mode 100644 index 000000000..1f03d3828 --- /dev/null +++ b/core/router/router.go @@ -0,0 +1,17 @@ +package router + +import ( + "github.com/TheThingsNetwork/ttn/api/gateway" + pb "github.com/TheThingsNetwork/ttn/api/router" +) + +type Router interface { + HandleGatewayStatus(status *gateway.StatusMessage) error + HandleUplink(uplink *pb.UplinkMessage) error + HandleDownlink() error + HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) +} + +type router struct { + gatewayStatusStore GatewayStatusStore +} diff --git a/core/router/router_test.go b/core/router/router_test.go new file mode 100644 index 000000000..7ef135b39 --- /dev/null +++ b/core/router/router_test.go @@ -0,0 +1 @@ +package router diff --git a/core/storage/hslice.go b/core/storage/hslice.go deleted file mode 100644 index 83ccfe6f4..000000000 --- a/core/storage/hslice.go +++ /dev/null @@ -1,168 +0,0 @@ -package storage - -import ( - "encoding/hex" - "errors" - "strconv" -) - -type HSlice struct { - Data map[string]string -} - -var ( - // ErrDoesNotExist indicates that a key does not exist - ErrDoesNotExist = errors.New("HSlice: key does not exist") - // ErrInvalidLength indicates that a slice had an invalid length - ErrInvalidLength = errors.New("HSsice: invalid length") -) - -func NewHSlice() *HSlice { - return &HSlice{map[string]string{}} -} - -// MarshalHSlice returns a slice with the data -func (s *HSlice) MarshalHSlice() (out []string) { - for k, v := range s.Data { - if k != "" && v != "" { - out = append(out, k, v) - } - } - return -} - -// UnmarshalHSlice imports data from slice -func (s *HSlice) UnmarshalHSlice(slice []string) error { - if len(slice)%2 != 0 { - return ErrInvalidLength - } - for i := 0; i < len(slice); i = i + 2 { - s.Data[slice[i]] = slice[i+1] - } - return nil -} - -// SetFloat32 does what its name suggests -func (s *HSlice) SetFloat32(key string, value float32) { - s.Data[key] = strconv.FormatFloat(float64(value), 'E', -1, 32) -} - -// SetFloat64 does what its name suggests -func (s *HSlice) SetFloat64(key string, value float64) { - s.Data[key] = strconv.FormatFloat(value, 'E', -1, 64) -} - -// SetInt32 does what its name suggests -func (s *HSlice) SetInt32(key string, value int32) { - s.SetInt64(key, int64(value)) -} - -// SetInt64 does what its name suggests -func (s *HSlice) SetInt64(key string, value int64) { - s.Data[key] = strconv.FormatInt(value, 10) -} - -// SetUint32 does what its name suggests -func (s *HSlice) SetUint32(key string, value uint32) { - s.SetUint64(key, uint64(value)) -} - -// SetUint64 does what its name suggests -func (s *HSlice) SetUint64(key string, value uint64) { - s.Data[key] = strconv.FormatUint(value, 10) -} - -// SetBool does what its name suggests -func (s *HSlice) SetBool(key string, value bool) { - s.Data[key] = strconv.FormatBool(value) -} - -// SetString does what its name suggests -func (s *HSlice) SetString(key string, value string) { - if value != "" { - s.Data[key] = value - } -} - -// SetBytes does what its name suggests -func (s *HSlice) SetBytes(key string, value []byte) { - if len(value) > 0 { - s.Data[key] = hex.EncodeToString(value) - } -} - -// GetFloat32 does what its name suggests -func (s *HSlice) GetFloat32(key string) (value float32, err error) { - if val, ok := s.Data[key]; ok { - res, err := strconv.ParseFloat(val, 32) - return float32(res), err - } - return 0, ErrDoesNotExist -} - -// GetFloat64 does what its name suggests -func (s *HSlice) GetFloat64(key string) (value float64, err error) { - if val, ok := s.Data[key]; ok { - return strconv.ParseFloat(val, 64) - } - return 0, ErrDoesNotExist -} - -// GetInt32 does what its name suggests -func (s *HSlice) GetInt32(key string) (value int32, err error) { - if val, ok := s.Data[key]; ok { - res, err := strconv.ParseInt(val, 10, 32) - return int32(res), err - } - return 0, ErrDoesNotExist -} - -// GetInt64 does what its name suggests -func (s *HSlice) GetInt64(key string) (value int64, err error) { - if val, ok := s.Data[key]; ok { - res, err := strconv.ParseInt(val, 10, 64) - return res, err - } - return 0, ErrDoesNotExist -} - -// GetUint32 does what its name suggests -func (s *HSlice) GetUint32(key string) (value uint32, err error) { - if val, ok := s.Data[key]; ok { - res, err := strconv.ParseUint(val, 10, 32) - return uint32(res), err - } - return 0, ErrDoesNotExist -} - -// GetUint64 does what its name suggests -func (s *HSlice) GetUint64(key string) (value uint64, err error) { - if val, ok := s.Data[key]; ok { - return strconv.ParseUint(val, 10, 64) - } - return 0, ErrDoesNotExist -} - -// GetBool does what its name suggests -func (s *HSlice) GetBool(key string) (value bool, err error) { - if val, ok := s.Data[key]; ok { - return strconv.ParseBool(val) - } - return false, ErrDoesNotExist -} - -// GetString does what its name suggests -func (s *HSlice) GetString(key string) (value string, err error) { - if val, ok := s.Data[key]; ok { - return val, nil - } - return "", ErrDoesNotExist -} - -// GetBytes does what its name suggests -func (s *HSlice) GetBytes(key string) (value []byte, err error) { - if val, ok := s.Data[key]; ok { - return hex.DecodeString(val) - } - return []byte{}, ErrDoesNotExist -} diff --git a/core/storage/hslice_test.go b/core/storage/hslice_test.go deleted file mode 100644 index 26aa78e1d..000000000 --- a/core/storage/hslice_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package storage - -import ( - "testing" - - "github.com/smartystreets/assertions" -) - -func TestHSliceFloat32(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetFloat32("Float32") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetFloat32("Float32", 123.456) - f32, err := s.GetFloat32("Float32") - a.So(err, assertions.ShouldBeNil) - a.So(f32, assertions.ShouldEqual, 123.456) -} - -func TestHSliceFloat64(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetFloat64("Float64") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetFloat64("Float64", 123.456) - f32, err := s.GetFloat64("Float64") - a.So(err, assertions.ShouldBeNil) - a.So(f32, assertions.ShouldEqual, 123.456) -} - -func TestHSliceInt32(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetInt32("Int32") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetInt32("Int32", -123456) - res, err := s.GetInt32("Int32") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, -123456) -} - -func TestHSliceInt64(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetInt64("Int64") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetInt64("Int64", -123456) - res, err := s.GetInt64("Int64") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, -123456) -} - -func TestHSliceUint32(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetUint32("Uint32") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetUint32("Uint32", 123456) - res, err := s.GetUint32("Uint32") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, 123456) -} - -func TestHSliceUint64(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetUint64("Uint64") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetUint64("Uint64", 123456) - res, err := s.GetUint64("Uint64") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, 123456) -} - -func TestHSliceBool(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetBool("Bool") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetBool("Bool", true) - res, err := s.GetBool("Bool") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, true) - s.SetBool("Bool", false) - res, err = s.GetBool("Bool") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, false) -} - -func TestHSliceString(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetString("String") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetString("String", "the string") - res, err := s.GetString("String") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, "the string") -} - -func TestHSliceBytes(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - _, err := s.GetBytes("Bytes") - a.So(err, assertions.ShouldEqual, ErrDoesNotExist) - s.SetBytes("Bytes", []byte{0x12, 0x34, 0xcd, 0xef}) - res, err := s.GetBytes("Bytes") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldResemble, []byte{0x12, 0x34, 0xcd, 0xef}) -} - -func TestHSliceSlice(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - s.SetString("String", "the string") - slice := s.MarshalHSlice() - a.So(slice, assertions.ShouldResemble, []string{"String", "the string"}) -} - -func TestHSliceFrom(t *testing.T) { - a := assertions.New(t) - s := NewHSlice() - err := s.UnmarshalHSlice([]string{"String", "the string"}) - a.So(err, assertions.ShouldBeNil) - res, err := s.GetString("String") - a.So(err, assertions.ShouldBeNil) - a.So(res, assertions.ShouldEqual, "the string") - err = s.UnmarshalHSlice([]string{"String", "the string", "another string"}) - a.So(err, assertions.ShouldEqual, ErrInvalidLength) -} diff --git a/core/storage/redis.go b/core/storage/redis.go new file mode 100644 index 000000000..a11b7811c --- /dev/null +++ b/core/storage/redis.go @@ -0,0 +1,90 @@ +package storage + +import ( + "encoding/hex" + "fmt" + "strconv" +) + +// FormatFloat32 does what its name suggests +func FormatFloat32(value float32) string { + return strconv.FormatFloat(float64(value), 'f', -1, 32) +} + +// FormatFloat64 does what its name suggests +func FormatFloat64(value float64) string { + return strconv.FormatFloat(value, 'f', -1, 64) +} + +// FormatInt32 does what its name suggests +func FormatInt32(value int32) string { + return FormatInt64(int64(value)) +} + +// FormatInt64 does what its name suggests +func FormatInt64(value int64) string { + return strconv.FormatInt(value, 10) +} + +// FormatUint32 does what its name suggests +func FormatUint32(value uint32) string { + return FormatUint64(uint64(value)) +} + +// FormatUint64 does what its name suggests +func FormatUint64(value uint64) string { + return strconv.FormatUint(value, 10) +} + +// FormatBool does what its name suggests +func FormatBool(value bool) string { + return strconv.FormatBool(value) +} + +// FormatBytes does what its name suggests +func FormatBytes(value []byte) string { + return fmt.Sprintf("%X", value) +} + +// ParseFloat32 does what its name suggests +func ParseFloat32(val string) (float32, error) { + res, err := strconv.ParseFloat(val, 32) + return float32(res), err +} + +// ParseFloat64 does what its name suggests +func ParseFloat64(val string) (float64, error) { + return strconv.ParseFloat(val, 64) +} + +// ParseInt32 does what its name suggests +func ParseInt32(val string) (int32, error) { + res, err := strconv.ParseInt(val, 10, 32) + return int32(res), err +} + +// ParseInt64 does what its name suggests +func ParseInt64(val string) (int64, error) { + return strconv.ParseInt(val, 10, 64) +} + +// ParseUint32 does what its name suggests +func ParseUint32(val string) (uint32, error) { + res, err := strconv.ParseUint(val, 10, 32) + return uint32(res), err +} + +// ParseUint64 does what its name suggests +func ParseUint64(val string) (uint64, error) { + return strconv.ParseUint(val, 10, 64) +} + +// ParseBool does what its name suggests +func ParseBool(val string) (bool, error) { + return strconv.ParseBool(val) +} + +// ParseBytes does what its name suggests +func ParseBytes(val string) ([]byte, error) { + return hex.DecodeString(val) +} diff --git a/core/storage/redis_test.go b/core/storage/redis_test.go new file mode 100644 index 000000000..b1e5c5f24 --- /dev/null +++ b/core/storage/redis_test.go @@ -0,0 +1,75 @@ +package storage + +import ( + "testing" + + "github.com/smartystreets/assertions" +) + +func TestFloat32(t *testing.T) { + a := assertions.New(t) + a.So(FormatFloat32(123.456), assertions.ShouldEqual, "123.456") + f32, err := ParseFloat32("123.456") + a.So(err, assertions.ShouldBeNil) + a.So(f32, assertions.ShouldEqual, 123.456) +} + +func TestFloat64(t *testing.T) { + a := assertions.New(t) + a.So(FormatFloat64(123.456), assertions.ShouldEqual, "123.456") + f64, err := ParseFloat64("123.456") + a.So(err, assertions.ShouldBeNil) + a.So(f64, assertions.ShouldEqual, 123.456) +} + +func TestInt32(t *testing.T) { + a := assertions.New(t) + a.So(FormatInt32(-123456), assertions.ShouldEqual, "-123456") + i32, err := ParseInt32("-123456") + a.So(err, assertions.ShouldBeNil) + a.So(i32, assertions.ShouldEqual, -123456) +} + +func TestInt64(t *testing.T) { + a := assertions.New(t) + a.So(FormatInt64(-123456), assertions.ShouldEqual, "-123456") + i64, err := ParseInt64("-123456") + a.So(err, assertions.ShouldBeNil) + a.So(i64, assertions.ShouldEqual, -123456) +} + +func TestUint32(t *testing.T) { + a := assertions.New(t) + a.So(FormatUint32(123456), assertions.ShouldEqual, "123456") + i32, err := ParseUint32("123456") + a.So(err, assertions.ShouldBeNil) + a.So(i32, assertions.ShouldEqual, 123456) +} + +func TestUint64(t *testing.T) { + a := assertions.New(t) + a.So(FormatUint64(123456), assertions.ShouldEqual, "123456") + i64, err := ParseUint64("123456") + a.So(err, assertions.ShouldBeNil) + a.So(i64, assertions.ShouldEqual, 123456) +} + +func TestBool(t *testing.T) { + a := assertions.New(t) + a.So(FormatBool(true), assertions.ShouldEqual, "true") + b, err := ParseBool("true") + a.So(err, assertions.ShouldBeNil) + a.So(b, assertions.ShouldEqual, true) + a.So(FormatBool(false), assertions.ShouldEqual, "false") + b, err = ParseBool("false") + a.So(err, assertions.ShouldBeNil) + a.So(b, assertions.ShouldEqual, false) +} + +func TestBytes(t *testing.T) { + a := assertions.New(t) + a.So(FormatBytes([]byte{0x12, 0x34, 0xcd, 0xef}), assertions.ShouldEqual, "1234CDEF") + i64, err := ParseBytes("1234CDEF") + a.So(err, assertions.ShouldBeNil) + a.So(i64, assertions.ShouldResemble, []byte{0x12, 0x34, 0xcd, 0xef}) +} From 67b3d41cab3986cd60bfbba55e69c87b0828466f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 4 May 2016 13:21:30 +0200 Subject: [PATCH 1426/2266] Add Metadata to Discovery Announcement --- api/discovery/discovery.pb.go | 299 ++++++++++++++++++++++++++++++---- api/discovery/discovery.proto | 9 + 2 files changed, 277 insertions(+), 31 deletions(-) diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index f5c26d339..0c2c87c5b 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -9,6 +9,7 @@ github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto It has these top-level messages: + Metadata Announcement DiscoverRequest DiscoverResponse @@ -36,18 +37,54 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. const _ = proto.ProtoPackageIsVersion1 +type Metadata_Key int32 + +const ( + Metadata_PREFIX Metadata_Key = 0 +) + +var Metadata_Key_name = map[int32]string{ + 0: "PREFIX", +} +var Metadata_Key_value = map[string]int32{ + "PREFIX": 0, +} + +func (x Metadata_Key) String() string { + return proto.EnumName(Metadata_Key_name, int32(x)) +} +func (Metadata_Key) EnumDescriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0, 0} } + +type Metadata struct { + Key Metadata_Key `protobuf:"varint,1,opt,name=key,proto3,enum=discovery.Metadata_Key" json:"key,omitempty"` + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } + type Announcement struct { - ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - ServiceVersion string `protobuf:"bytes,2,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` - NetAddress string `protobuf:"bytes,3,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` - Description string `protobuf:"bytes,11,opt,name=description,proto3" json:"description,omitempty"` - Fingerprint string `protobuf:"bytes,12,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + ServiceVersion string `protobuf:"bytes,2,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + NetAddress string `protobuf:"bytes,3,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` + Description string `protobuf:"bytes,11,opt,name=description,proto3" json:"description,omitempty"` + Fingerprint string `protobuf:"bytes,12,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` + Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } func (m *Announcement) Reset() { *m = Announcement{} } func (m *Announcement) String() string { return proto.CompactTextString(m) } func (*Announcement) ProtoMessage() {} -func (*Announcement) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } +func (*Announcement) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{1} } + +func (m *Announcement) GetMetadata() []*Metadata { + if m != nil { + return m.Metadata + } + return nil +} type DiscoverRequest struct { ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` @@ -56,7 +93,7 @@ type DiscoverRequest struct { func (m *DiscoverRequest) Reset() { *m = DiscoverRequest{} } func (m *DiscoverRequest) String() string { return proto.CompactTextString(m) } func (*DiscoverRequest) ProtoMessage() {} -func (*DiscoverRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{1} } +func (*DiscoverRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } type DiscoverResponse struct { Services []*Announcement `protobuf:"bytes,1,rep,name=services" json:"services,omitempty"` @@ -65,7 +102,7 @@ type DiscoverResponse struct { func (m *DiscoverResponse) Reset() { *m = DiscoverResponse{} } func (m *DiscoverResponse) String() string { return proto.CompactTextString(m) } func (*DiscoverResponse) ProtoMessage() {} -func (*DiscoverResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } +func (*DiscoverResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{3} } func (m *DiscoverResponse) GetServices() []*Announcement { if m != nil { @@ -75,9 +112,11 @@ func (m *DiscoverResponse) GetServices() []*Announcement { } func init() { + proto.RegisterType((*Metadata)(nil), "discovery.Metadata") proto.RegisterType((*Announcement)(nil), "discovery.Announcement") proto.RegisterType((*DiscoverRequest)(nil), "discovery.DiscoverRequest") proto.RegisterType((*DiscoverResponse)(nil), "discovery.DiscoverResponse") + proto.RegisterEnum("discovery.Metadata_Key", Metadata_Key_name, Metadata_Key_value) } // Reference imports to suppress errors if they are not otherwise used. @@ -213,6 +252,35 @@ var _DiscoveryManager_serviceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, } +func (m *Metadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Metadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Key != 0 { + data[i] = 0x8 + i++ + i = encodeVarintDiscovery(data, i, uint64(m.Key)) + } + if len(m.Value) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Value))) + i += copy(data[i:], m.Value) + } + return i, nil +} + func (m *Announcement) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -258,6 +326,20 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { i = encodeVarintDiscovery(data, i, uint64(len(m.Fingerprint))) i += copy(data[i:], m.Fingerprint) } + if len(m.Metadata) > 0 { + for _, msg := range m.Metadata { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintDiscovery(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -342,6 +424,19 @@ func encodeVarintDiscovery(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } +func (m *Metadata) Size() (n int) { + var l int + _ = l + if m.Key != 0 { + n += 1 + sovDiscovery(uint64(m.Key)) + } + l = len(m.Value) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + return n +} + func (m *Announcement) Size() (n int) { var l int _ = l @@ -365,6 +460,12 @@ func (m *Announcement) Size() (n int) { if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } + if len(m.Metadata) > 0 { + for _, e := range m.Metadata { + l = e.Size() + n += 2 + l + sovDiscovery(uint64(l)) + } + } return n } @@ -403,6 +504,106 @@ func sovDiscovery(x uint64) (n int) { func sozDiscovery(x uint64) (n int) { return sovDiscovery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *Metadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + } + m.Key = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Key |= (Metadata_Key(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Value = append(m.Value[:0], data[iNdEx:postIndex]...) + if m.Value == nil { + m.Value = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Announcement) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -577,6 +778,37 @@ func (m *Announcement) Unmarshal(data []byte) error { } m.Fingerprint = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = append(m.Metadata, &Metadata{}) + if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipDiscovery(data[iNdEx:]) @@ -864,27 +1096,32 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 340 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x91, 0xb1, 0x4e, 0xc2, 0x40, - 0x18, 0xc7, 0xad, 0x24, 0xa6, 0x7c, 0x25, 0x42, 0x6e, 0xb1, 0xa9, 0x09, 0x22, 0x8b, 0x2c, 0xb4, - 0x09, 0xb8, 0x3a, 0xa0, 0x26, 0x4e, 0x32, 0x10, 0xe2, 0x4a, 0x4a, 0xfb, 0x09, 0x17, 0xc2, 0x5d, - 0xbd, 0x3b, 0x30, 0x4c, 0xbe, 0x86, 0x6f, 0xe3, 0xea, 0xe8, 0x23, 0x18, 0x7d, 0x11, 0x8f, 0x5e, - 0x4b, 0x1b, 0xa3, 0x09, 0xc3, 0x25, 0xcd, 0xaf, 0xbf, 0xff, 0x77, 0xfd, 0x7f, 0x85, 0xab, 0x19, - 0x55, 0xf3, 0xd5, 0xd4, 0x8f, 0xf8, 0x32, 0x18, 0xcf, 0x71, 0x3c, 0xa7, 0x6c, 0x26, 0x87, 0xa8, - 0x9e, 0xb9, 0x58, 0x04, 0x4a, 0xb1, 0x20, 0x4c, 0x68, 0x10, 0x53, 0x19, 0xf1, 0x35, 0x8a, 0x4d, - 0xf1, 0xe4, 0x27, 0x82, 0x2b, 0x4e, 0xaa, 0x3b, 0xe0, 0x75, 0xf7, 0x99, 0xa4, 0x8f, 0x49, 0xb6, - 0xdf, 0x2c, 0xa8, 0x0d, 0x18, 0xe3, 0x2b, 0x16, 0xe1, 0x12, 0x99, 0x22, 0xe7, 0x50, 0x93, 0x28, - 0xd6, 0x34, 0xc2, 0x09, 0x0b, 0x97, 0xe8, 0x5a, 0x2d, 0xab, 0x53, 0x1d, 0x39, 0x19, 0x1b, 0x6a, - 0x44, 0x2e, 0xa0, 0x9e, 0x2b, 0xfa, 0x4a, 0x49, 0x39, 0x73, 0x0f, 0x53, 0xeb, 0x38, 0xc3, 0x0f, - 0x86, 0x92, 0x33, 0x70, 0x18, 0xaa, 0x49, 0x18, 0xc7, 0x02, 0xa5, 0x74, 0x2b, 0xa9, 0x04, 0x1a, - 0x0d, 0x0c, 0x21, 0x2d, 0x70, 0x62, 0x94, 0x91, 0xa0, 0x89, 0xda, 0x4e, 0x71, 0xcc, 0x5d, 0x25, - 0xb4, 0x35, 0x1e, 0x75, 0x03, 0x14, 0x89, 0xa0, 0x4c, 0xb9, 0x35, 0x63, 0x94, 0x50, 0xfb, 0x12, - 0xea, 0xb7, 0x59, 0xfb, 0x11, 0x3e, 0xad, 0x50, 0xee, 0xd3, 0xa1, 0x7d, 0x07, 0x8d, 0x22, 0x25, - 0x13, 0xce, 0x24, 0x92, 0x3e, 0xd8, 0x99, 0x22, 0x75, 0xa4, 0xd2, 0x71, 0x7a, 0x27, 0x7e, 0xb1, - 0xe9, 0xf2, 0x96, 0x46, 0x3b, 0xb1, 0xf7, 0x02, 0xd5, 0x7c, 0xd0, 0x86, 0x74, 0xc1, 0xce, 0x35, - 0xf2, 0x5f, 0xd6, 0xb3, 0xfd, 0xed, 0xfa, 0x07, 0xd1, 0x82, 0xdc, 0x80, 0x9d, 0x67, 0x89, 0x57, - 0xd2, 0x7f, 0xf5, 0xf1, 0x4e, 0xff, 0x7c, 0x67, 0xbe, 0xba, 0x47, 0x8a, 0x26, 0x9b, 0xfb, 0x90, - 0x85, 0x7a, 0x31, 0xd7, 0x8d, 0xf7, 0xaf, 0xa6, 0xf5, 0xa1, 0xcf, 0xa7, 0x3e, 0xaf, 0xdf, 0xcd, - 0x83, 0xe9, 0x51, 0xfa, 0xbb, 0xfb, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x46, 0xee, 0xf4, 0xf2, - 0x69, 0x02, 0x00, 0x00, + // 422 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x6a, 0xd4, 0x40, + 0x14, 0xc6, 0xbb, 0x2e, 0x96, 0xec, 0xc9, 0xd2, 0xae, 0xa3, 0x62, 0x88, 0x50, 0xd7, 0xdc, 0x58, + 0x2f, 0x9a, 0x40, 0xea, 0xad, 0x17, 0xeb, 0x5f, 0x44, 0x5a, 0x64, 0x28, 0xe2, 0x95, 0x65, 0x9a, + 0x1c, 0xbb, 0xc3, 0x9a, 0x99, 0x38, 0x33, 0x59, 0xc9, 0x95, 0xaf, 0xe1, 0x23, 0x79, 0xe9, 0x23, + 0x88, 0xbe, 0x80, 0x8f, 0xe0, 0xe4, 0xdf, 0x26, 0x48, 0x0b, 0x7b, 0x71, 0x20, 0xf9, 0xe6, 0xf7, + 0x9d, 0x2f, 0xe7, 0x64, 0xe0, 0xe9, 0x25, 0x37, 0xcb, 0xe2, 0x22, 0x4c, 0x64, 0x16, 0x9d, 0x2d, + 0xf1, 0x6c, 0xc9, 0xc5, 0xa5, 0x3e, 0x45, 0xf3, 0x55, 0xaa, 0x55, 0x64, 0x8c, 0x88, 0x58, 0xce, + 0xa3, 0x94, 0xeb, 0x44, 0xae, 0x51, 0x95, 0xfd, 0x53, 0x98, 0x2b, 0x69, 0x24, 0x99, 0x6c, 0x04, + 0xff, 0x68, 0x9b, 0x4e, 0xb6, 0x1a, 0x67, 0xf0, 0x11, 0x9c, 0x13, 0x34, 0x2c, 0x65, 0x86, 0x91, + 0xc7, 0x30, 0x5e, 0x61, 0xe9, 0x8d, 0xe6, 0xa3, 0xc3, 0xbd, 0xf8, 0x5e, 0xd8, 0x87, 0x74, 0x44, + 0xf8, 0x16, 0x4b, 0x5a, 0x31, 0xe4, 0x0e, 0xdc, 0x5c, 0xb3, 0xcf, 0x05, 0x7a, 0x37, 0x2c, 0x3c, + 0xa5, 0xcd, 0x4b, 0x70, 0x0b, 0xc6, 0x96, 0x20, 0x00, 0xbb, 0xef, 0xe8, 0xcb, 0x57, 0x6f, 0x3e, + 0xcc, 0x76, 0x82, 0xbf, 0x23, 0x98, 0x2e, 0x84, 0x90, 0x85, 0x48, 0x30, 0x43, 0x61, 0xc8, 0x43, + 0x98, 0x6a, 0x54, 0x6b, 0x9e, 0xe0, 0xb9, 0x60, 0x19, 0xd6, 0x69, 0x13, 0xea, 0xb6, 0xda, 0xa9, + 0x95, 0xc8, 0x23, 0xd8, 0xef, 0x10, 0x1b, 0xaf, 0xb9, 0x14, 0x75, 0xcc, 0x84, 0xee, 0xb5, 0xf2, + 0xfb, 0x46, 0x25, 0x0f, 0xc0, 0x15, 0x68, 0xce, 0x59, 0x9a, 0x2a, 0xd4, 0xda, 0x1b, 0xd7, 0x10, + 0x58, 0x69, 0xd1, 0x28, 0x64, 0x0e, 0x6e, 0x8a, 0x3a, 0x51, 0x3c, 0x37, 0x55, 0x17, 0xb7, 0xc9, + 0x1a, 0x48, 0x15, 0xf1, 0xc9, 0x6e, 0x08, 0x55, 0xae, 0xb8, 0x30, 0xde, 0xb4, 0x21, 0x06, 0x12, + 0x89, 0xc0, 0xc9, 0xda, 0xf9, 0xbd, 0xbb, 0xf3, 0xf1, 0xa1, 0x1b, 0xdf, 0xbe, 0x62, 0x35, 0x74, + 0x03, 0x05, 0x4f, 0x60, 0xff, 0x45, 0x7b, 0x4e, 0xf1, 0x4b, 0x81, 0x7a, 0x9b, 0xa1, 0x83, 0xd7, + 0x30, 0xeb, 0x5d, 0x3a, 0x97, 0x42, 0x23, 0x39, 0x06, 0xa7, 0x45, 0xb4, 0xb5, 0x54, 0xd1, 0xc3, + 0xbf, 0x32, 0x5c, 0x2b, 0xdd, 0x80, 0xf1, 0x37, 0x98, 0x74, 0x8d, 0x4a, 0x72, 0x04, 0x4e, 0x87, + 0x91, 0xeb, 0xbc, 0xbe, 0x13, 0x56, 0xf7, 0x61, 0x91, 0xac, 0xc8, 0x73, 0x70, 0x3a, 0x2f, 0xf1, + 0x07, 0xf8, 0x7f, 0xf3, 0xf8, 0xf7, 0xaf, 0x3c, 0x6b, 0xbe, 0x3a, 0x26, 0xfd, 0x24, 0xe5, 0x09, + 0x13, 0xcc, 0x6e, 0xf2, 0xd9, 0xec, 0xc7, 0xef, 0x83, 0xd1, 0x4f, 0x5b, 0xbf, 0x6c, 0x7d, 0xff, + 0x73, 0xb0, 0x73, 0xb1, 0x5b, 0xdf, 0xbf, 0xe3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x67, 0x53, + 0x9f, 0x27, 0xfa, 0x02, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 8ec68b0a9..696575856 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -4,12 +4,21 @@ import "github.com/TheThingsNetwork/ttn/api/api.proto"; package discovery; +message Metadata { + enum Key { + PREFIX = 0; + } + Key key = 1; + bytes value = 2; +} + message Announcement { string service_name = 1; string service_version = 2; string net_address = 3; string description = 11; string fingerprint = 12; + repeated Metadata metadata = 21; } message DiscoverRequest { From 80d4cccdce738bf4d8dac17abddd3650ff70b148 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 4 May 2016 16:50:14 +0200 Subject: [PATCH 1427/2266] [refactor] Reference implementation for Discovery server --- core/discovery/broker_discovery.go | 74 +++++++++++++ core/discovery/broker_discovery_test.go | 105 +++++++++++++++++++ core/discovery/discovery.go | 69 ++++++++++++ core/discovery/discovery_integration_test.go | 38 +++++++ core/discovery/discovery_test.go | 78 ++++++++++++++ core/discovery/server.go | 35 +++++++ core/discovery/server_test.go | 62 +++++++++++ 7 files changed, 461 insertions(+) create mode 100644 core/discovery/broker_discovery.go create mode 100644 core/discovery/broker_discovery_test.go create mode 100644 core/discovery/discovery.go create mode 100644 core/discovery/discovery_integration_test.go create mode 100644 core/discovery/discovery_test.go create mode 100644 core/discovery/server.go create mode 100644 core/discovery/server_test.go diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go new file mode 100644 index 000000000..187a5835f --- /dev/null +++ b/core/discovery/broker_discovery.go @@ -0,0 +1,74 @@ +package discovery + +import ( + "bytes" + "sync" + "time" + + "google.golang.org/grpc" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "golang.org/x/net/context" +) + +// BrokerCacheTime indicates how long the BrokerDiscovery should cache the services +var BrokerCacheTime = 30 * time.Minute + +// BrokerDiscovery is used as a client to discover Brokers +type BrokerDiscovery interface { + Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) +} + +type brokerDiscovery struct { + serverAddress string + cache []*pb.Announcement + cacheLock sync.RWMutex + cacheValidUntil time.Time +} + +// NewBrokerDiscovery returns a new BrokerDiscovery on top of the given gRPC connection +func NewBrokerDiscovery(serverAddress string) BrokerDiscovery { + return &brokerDiscovery{serverAddress: serverAddress} +} + +func (d *brokerDiscovery) refreshCache() error { + // Connect to the server + conn, err := grpc.Dial(d.serverAddress, DialOptions...) + if err != nil { + return err + } + defer conn.Close() + client := pb.NewDiscoveryClient(conn) + res, err := client.Discover(context.Background(), &pb.DiscoverRequest{ServiceName: "broker"}) + if err != nil { + return err + } + // TODO: validate response + d.cacheLock.Lock() + defer d.cacheLock.Unlock() + d.cacheValidUntil = time.Now().Add(BrokerCacheTime) + d.cache = res.Services + return nil +} + +func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) { + d.cacheLock.Lock() + if time.Now().After(d.cacheValidUntil) { + d.cacheValidUntil = time.Now().Add(10 * time.Second) + go d.refreshCache() + } + d.cacheLock.Unlock() + d.cacheLock.RLock() + defer d.cacheLock.RUnlock() + matches := []*pb.Announcement{} + for _, service := range d.cache { + for _, meta := range service.Metadata { + if meta.Key == pb.Metadata_PREFIX && bytes.HasPrefix(devAddr.Bytes(), meta.Value) { + matches = append(matches, service) + break + } + } + } + return matches, nil +} diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go new file mode 100644 index 000000000..f82d906c6 --- /dev/null +++ b/core/discovery/broker_discovery_test.go @@ -0,0 +1,105 @@ +package discovery + +import ( + "fmt" + "testing" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func buildTestBrokerDiscoveryClient(port uint) *brokerDiscovery { + discovery := NewBrokerDiscovery(fmt.Sprintf("localhost:%d", port)).(*brokerDiscovery) + discovery.refreshCache() + return discovery +} + +func TestBrokerDiscoveryDiscover(t *testing.T) { + a := New(t) + + // Broker1 has a prefix with all DevAddrs + broker1 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker1", NetAddress: "localhost1:1881", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{}}, + }, + } + + // Broker2 has one DevAddr prefix + broker2 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker2", NetAddress: "localhost2:1881", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x01}}, + }, + } + + // Broker3 has multiple DevAddr prefixes + broker3 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker3", NetAddress: "localhost3:1881", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x02, 0x03}}, + }, + } + + d := &brokerDiscovery{ + cacheValidUntil: time.Now().Add(10 * time.Minute), + cache: []*pb.Announcement{broker1, broker2, broker3}, + } + + results, err := d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldContain, broker1) + a.So(results, ShouldContain, broker2) + a.So(results, ShouldNotContain, broker3) + + results, err = d.Discover(types.DevAddr{0x02, 0x03, 0x04, 0x05}) + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldContain, broker1) + a.So(results, ShouldNotContain, broker2) + a.So(results, ShouldContain, broker3) + + results, err = d.Discover(types.DevAddr{0x04, 0x05, 0x06, 0x07}) + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldContain, broker1) + a.So(results, ShouldNotContain, broker2) + a.So(results, ShouldNotContain, broker3) +} + +func TestBrokerDiscoveryCache(t *testing.T) { + a := New(t) + + port := randomPort() + + discoveryServer, _ := buildMockDiscoveryServer(port) + + broker := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker", NetAddress: "localhost1:1881", + Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{}}}, + } + + d := &brokerDiscovery{ + serverAddress: fmt.Sprintf("localhost:%d", port), + cacheValidUntil: time.Now().Add(-1 * time.Minute), + cache: []*pb.Announcement{broker}, + } + + // It should return the cached broker initially + results, err := d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) + a.So(err, ShouldBeNil) + a.So(results, ShouldContain, broker) + + // It should still return the cached broker + results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) + a.So(err, ShouldBeNil) + a.So(results, ShouldContain, broker) + + <-time.After(10 * time.Millisecond) + + // It should return the refreshed (empty) broker list + results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) + a.So(err, ShouldBeNil) + a.So(results, ShouldBeEmpty) + + a.So(discoveryServer.discover, ShouldEqual, 1) +} diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go new file mode 100644 index 000000000..9afa82550 --- /dev/null +++ b/core/discovery/discovery.go @@ -0,0 +1,69 @@ +// Package discovery implements TTN Service Discovery. +package discovery + +import ( + "fmt" + "sync" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "google.golang.org/grpc" +) + +// DialOptions are the gRPC dial options for discovery calls +// TODO: disable insecure connections +var DialOptions = []grpc.DialOption{ + grpc.WithInsecure(), +} + +// Discovery specifies the interface for the TTN Service Discovery component +type Discovery interface { + Announce(announcement *pb.Announcement) error + Discover(serviceName string) ([]*pb.Announcement, error) +} + +// discovery is a reference implementation for a TTN Service Discovery component. +// TODO: Implement one with a real database +type discovery struct { + services map[string]map[string]*pb.Announcement + sync.RWMutex +} + +func (d *discovery) Announce(announcement *pb.Announcement) error { + d.Lock() + defer d.Unlock() + + // Get the list + services, ok := d.services[announcement.ServiceName] + if !ok { + services = map[string]*pb.Announcement{} + d.services[announcement.ServiceName] = services + } + + // Find an existing announcement + service, ok := services[announcement.Fingerprint] + if ok { + *service = *announcement + } else { + services[announcement.Fingerprint] = announcement + } + + return nil +} + +func (d *discovery) Discover(serviceName string) ([]*pb.Announcement, error) { + d.RLock() + defer d.RUnlock() + + // Get the list + services, ok := d.services[serviceName] + if !ok { + return nil, fmt.Errorf("Service %s does not exist", serviceName) + } + + // Traverse the list + announcements := make([]*pb.Announcement, 0, len(services)) + for _, service := range services { + announcements = append(announcements, service) + } + return announcements, nil +} diff --git a/core/discovery/discovery_integration_test.go b/core/discovery/discovery_integration_test.go new file mode 100644 index 000000000..988fd9e84 --- /dev/null +++ b/core/discovery/discovery_integration_test.go @@ -0,0 +1,38 @@ +package discovery + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestIntegrationBrokerDiscovery(t *testing.T) { + a := New(t) + + port := randomPort() + + discoveryServer, s := buildTestDiscoveryServer(port) + defer s.Stop() + + discoveryServer.services = map[string]map[string]*pb.Announcement{ + "broker": map[string]*pb.Announcement{ + "broker1": &pb.Announcement{Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x01}}, + }}, + "broker2": &pb.Announcement{Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x02}}, + }}, + }, + "other": map[string]*pb.Announcement{ + "other": &pb.Announcement{}, + }, + } + + discoveryClient := buildTestBrokerDiscoveryClient(port) + + brokers, err := discoveryClient.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) + a.So(err, ShouldBeNil) + a.So(brokers, ShouldHaveLength, 1) +} diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go new file mode 100644 index 000000000..41868fc76 --- /dev/null +++ b/core/discovery/discovery_test.go @@ -0,0 +1,78 @@ +package discovery + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + . "github.com/smartystreets/assertions" +) + +func TestDiscoveryDiscover(t *testing.T) { + a := New(t) + + router := &pb.Announcement{Fingerprint: "router"} + broker1 := &pb.Announcement{Fingerprint: "broker1"} + broker2 := &pb.Announcement{Fingerprint: "broker2"} + + d := &discovery{ + services: map[string]map[string]*pb.Announcement{ + "router": map[string]*pb.Announcement{ + "router": router, + }, + "broker": map[string]*pb.Announcement{ + "broker1": broker1, + "broker2": broker2, + }, + }, + } + + _, err := d.Discover("random") + a.So(err, ShouldNotBeNil) + + services, err := d.Discover("router") + a.So(err, ShouldBeNil) + a.So(services, ShouldContain, router) + a.So(services, ShouldNotContain, broker1) + + services, err = d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldContain, broker1) + a.So(services, ShouldContain, broker2) + a.So(services, ShouldNotContain, router) +} + +func TestDiscoveryAnnounce(t *testing.T) { + a := New(t) + + broker1a := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker1", NetAddress: "old address"} + broker1b := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker1", NetAddress: "new address"} + broker2 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker2", NetAddress: "other address"} + + d := &discovery{ + services: map[string]map[string]*pb.Announcement{}, + } + + err := d.Announce(broker1a) + a.So(err, ShouldBeNil) + + services, err := d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "old address") + + err = d.Announce(broker1b) + a.So(err, ShouldBeNil) + + services, err = d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "new address") + + err = d.Announce(broker2) + a.So(err, ShouldBeNil) + + services, err = d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) + +} diff --git a/core/discovery/server.go b/core/discovery/server.go new file mode 100644 index 000000000..1288106f9 --- /dev/null +++ b/core/discovery/server.go @@ -0,0 +1,35 @@ +package discovery + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +type discoveryServer struct { + discovery Discovery +} + +func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { + err := d.discovery.Announce(announcement) + if err != nil { + return nil, err + } + return &api.Ack{}, nil +} + +func (d *discoveryServer) Discover(ctx context.Context, req *pb.DiscoverRequest) (*pb.DiscoverResponse, error) { + services, err := d.discovery.Discover(req.ServiceName) + if err != nil { + return nil, err + } + return &pb.DiscoverResponse{ + Services: services, + }, nil +} + +func (d *discovery) RegisterDiscoveryServer(s *grpc.Server) { + server := &discoveryServer{d} + pb.RegisterDiscoveryServer(s, server) +} diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go new file mode 100644 index 000000000..a86b10d30 --- /dev/null +++ b/core/discovery/server_test.go @@ -0,0 +1,62 @@ +package discovery + +import ( + "fmt" + "math/rand" + "net" + "time" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +func randomPort() uint { + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(5000) + 5000 + return uint(port) +} + +func buildTestDiscoveryServer(port uint) (*discovery, *grpc.Server) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + d := &discovery{} + s := grpc.NewServer() + d.RegisterDiscoveryServer(s) + go s.Serve(lis) + + return d, s +} + +func buildMockDiscoveryServer(port uint) (*mockDiscoveryServer, *grpc.Server) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + d := &mockDiscoveryServer{} + s := grpc.NewServer() + pb.RegisterDiscoveryServer(s, d) + go s.Serve(lis) + return d, s +} + +type mockDiscoveryServer struct { + announce uint + discover uint +} + +func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { + d.announce++ + <-time.After(5 * time.Millisecond) + return &api.Ack{}, nil +} +func (d *mockDiscoveryServer) Discover(ctx context.Context, req *pb.DiscoverRequest) (*pb.DiscoverResponse, error) { + d.discover++ + <-time.After(5 * time.Millisecond) + return &pb.DiscoverResponse{ + Services: []*pb.Announcement{}, + }, nil +} From f7a98a10a70a3af06cb149b62743dba2187e40fc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 6 May 2016 15:02:17 +0200 Subject: [PATCH 1428/2266] [refactor] router/gateway package --- api/protocol/lorawan/lorawan.pb.go | 286 +++++++++++++++++------- api/protocol/lorawan/lorawan.proto | 13 +- core/router/gateway/gateway.go | 19 ++ core/router/gateway/status.go | 72 ++++++ core/router/gateway/status_test.go | 81 +++++++ core/router/gateway/utilization.go | 123 ++++++++++ core/router/gateway/utilization_test.go | 92 ++++++++ core/router/gateway_status.go | 54 +---- core/router/gateway_status_test.go | 59 +---- core/router/router.go | 68 +++++- 10 files changed, 679 insertions(+), 188 deletions(-) create mode 100644 core/router/gateway/gateway.go create mode 100644 core/router/gateway/status.go create mode 100644 core/router/gateway/status_test.go create mode 100644 core/router/gateway/utilization.go create mode 100644 core/router/gateway/utilization_test.go diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 1138a47ce..172f583af 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -38,6 +38,27 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. const _ = proto.ProtoPackageIsVersion1 +type Modulation int32 + +const ( + Modulation_LORA Modulation = 0 + Modulation_FSK Modulation = 1 +) + +var Modulation_name = map[int32]string{ + 0: "LORA", + 1: "FSK", +} +var Modulation_value = map[string]int32{ + "LORA": 0, + "FSK": 1, +} + +func (x Modulation) String() string { + return proto.EnumName(Modulation_name, int32(x)) +} +func (Modulation) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } + type Region int32 const ( @@ -69,34 +90,13 @@ var Region_value = map[string]int32{ func (x Region) String() string { return proto.EnumName(Region_name, int32(x)) } -func (Region) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } - -type Metadata_Modulation int32 - -const ( - Metadata_LORA Metadata_Modulation = 0 - Metadata_FSK Metadata_Modulation = 1 -) - -var Metadata_Modulation_name = map[int32]string{ - 0: "LORA", - 1: "FSK", -} -var Metadata_Modulation_value = map[string]int32{ - "LORA": 0, - "FSK": 1, -} - -func (x Metadata_Modulation) String() string { - return proto.EnumName(Metadata_Modulation_name, int32(x)) -} -func (Metadata_Modulation) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0, 0} } +func (Region) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } type Metadata struct { - Modulation Metadata_Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Metadata_Modulation" json:"modulation,omitempty"` - DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` - BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` - CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` + Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` + DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` + BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` + CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -105,7 +105,11 @@ func (*Metadata) ProtoMessage() {} func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } type TxConfiguration struct { - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` + DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` + BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` + CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` } func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } @@ -217,8 +221,8 @@ func init() { proto.RegisterType((*MACPayload)(nil), "lorawan.MACPayload") proto.RegisterType((*FHdr)(nil), "lorawan.FHdr") proto.RegisterType((*FCtrl)(nil), "lorawan.FCtrl") + proto.RegisterEnum("lorawan.Modulation", Modulation_name, Modulation_value) proto.RegisterEnum("lorawan.Region", Region_name, Region_value) - proto.RegisterEnum("lorawan.Metadata_Modulation", Metadata_Modulation_name, Metadata_Modulation_value) } func (m *Metadata) Marshal() (data []byte, err error) { size := m.Size() @@ -285,6 +289,28 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { } i += n1 } + if m.Modulation != 0 { + data[i] = 0x58 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Modulation)) + } + if len(m.DataRate) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.DataRate))) + i += copy(data[i:], m.DataRate) + } + if m.BitRate != 0 { + data[i] = 0x68 + i++ + i = encodeVarintLorawan(data, i, uint64(m.BitRate)) + } + if len(m.CodingRate) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.CodingRate))) + i += copy(data[i:], m.CodingRate) + } return i, nil } @@ -606,6 +632,20 @@ func (m *TxConfiguration) Size() (n int) { l = m.DevAddr.Size() n += 1 + l + sovLorawan(uint64(l)) } + if m.Modulation != 0 { + n += 1 + sovLorawan(uint64(m.Modulation)) + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.BitRate != 0 { + n += 1 + sovLorawan(uint64(m.BitRate)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } return n } @@ -771,7 +811,7 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.Modulation |= (Metadata_Modulation(b) & 0x7F) << shift + m.Modulation |= (Modulation(b) & 0x7F) << shift if b < 0x80 { break } @@ -935,6 +975,102 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + m.Modulation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Modulation |= (Modulation(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + } + m.BitRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.BitRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -1868,50 +2004,50 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 716 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x54, 0xcd, 0x6e, 0xda, 0x4a, - 0x14, 0x8e, 0x03, 0x0e, 0xe6, 0x10, 0x72, 0xad, 0xc9, 0xbd, 0x12, 0xf7, 0xde, 0x28, 0xc9, 0xb5, - 0x74, 0xa5, 0xaa, 0x52, 0x81, 0x40, 0x12, 0x40, 0xea, 0x86, 0xd0, 0x44, 0x91, 0x12, 0x12, 0x3a, - 0x81, 0x45, 0x37, 0x1d, 0x19, 0x7b, 0x0c, 0xae, 0xc1, 0x43, 0xcc, 0x10, 0xca, 0x0b, 0xf4, 0x19, - 0xfa, 0x10, 0x7d, 0x83, 0xee, 0xba, 0xea, 0xb2, 0xeb, 0x2e, 0xaa, 0xaa, 0x7d, 0x91, 0xce, 0x8c, - 0xf9, 0xc9, 0xa2, 0x52, 0xa5, 0xaa, 0x5d, 0xa0, 0x39, 0xdf, 0xf9, 0xf9, 0xce, 0x77, 0xce, 0x78, - 0x80, 0x93, 0x9e, 0xcf, 0xfb, 0x93, 0x6e, 0xde, 0x61, 0xc3, 0x42, 0xbb, 0x4f, 0xdb, 0x7d, 0x3f, - 0xec, 0x8d, 0xaf, 0x28, 0x9f, 0xb2, 0x28, 0x28, 0x70, 0x1e, 0x16, 0xec, 0x91, 0x5f, 0x18, 0x45, - 0x8c, 0x33, 0x87, 0x0d, 0x0a, 0x03, 0x16, 0xd9, 0x53, 0x3b, 0x5c, 0x9c, 0x79, 0x15, 0x40, 0xa9, - 0x39, 0xfc, 0xe7, 0xd1, 0x3d, 0xb2, 0x1e, 0xeb, 0xb1, 0xb8, 0xb0, 0x3b, 0xf1, 0x14, 0x52, 0x40, - 0x59, 0x71, 0x9d, 0xf5, 0x4e, 0x03, 0xa3, 0x49, 0xb9, 0xed, 0xda, 0xdc, 0x46, 0x8f, 0x01, 0x86, - 0xcc, 0x9d, 0x0c, 0x6c, 0xee, 0xb3, 0x30, 0x97, 0xd9, 0xd7, 0x1e, 0x6c, 0x95, 0x76, 0xf2, 0x8b, - 0x46, 0x8b, 0xb4, 0x7c, 0x73, 0x99, 0x83, 0xef, 0xe5, 0xa3, 0x7f, 0x21, 0x2d, 0xc3, 0x24, 0xb2, - 0x39, 0xcd, 0x6d, 0x8a, 0xe2, 0x34, 0x36, 0xa4, 0x03, 0x0b, 0x8c, 0xfe, 0x06, 0xa3, 0xeb, 0xf3, - 0x38, 0x96, 0x15, 0xb1, 0x2c, 0x4e, 0x09, 0xac, 0x42, 0x7b, 0x90, 0x71, 0x98, 0x2b, 0x66, 0x8e, - 0xa3, 0x5b, 0xaa, 0x12, 0x62, 0x97, 0x4c, 0xb0, 0xf6, 0x00, 0x56, 0x2d, 0x91, 0x01, 0xc9, 0xcb, - 0x6b, 0x5c, 0x37, 0xd7, 0x50, 0x0a, 0x12, 0x67, 0x37, 0x17, 0xa6, 0x66, 0xb9, 0xf0, 0x47, 0xfb, - 0x65, 0x83, 0x85, 0x9e, 0xdf, 0x9b, 0x44, 0x71, 0xd6, 0x53, 0x30, 0x5c, 0x7a, 0x47, 0x6c, 0xd7, - 0x8d, 0x72, 0x9a, 0x60, 0xdc, 0x3c, 0x39, 0xfe, 0xf8, 0x69, 0xaf, 0xf4, 0xa3, 0x4d, 0x3b, 0x2c, - 0xa2, 0x05, 0x3e, 0x1b, 0xd1, 0x71, 0xfe, 0x09, 0xbd, 0xab, 0x8b, 0x6a, 0x9c, 0x72, 0x63, 0xc3, - 0x7a, 0xab, 0x01, 0xaa, 0x3b, 0xdc, 0xbf, 0x53, 0x1d, 0x96, 0x4b, 0xfb, 0xf5, 0x9d, 0x10, 0x86, - 0x74, 0x38, 0x0d, 0xc8, 0x98, 0x04, 0x74, 0x96, 0x5b, 0xff, 0x29, 0xce, 0xab, 0x69, 0x70, 0x73, - 0x41, 0x67, 0x38, 0x15, 0xc6, 0x86, 0x35, 0x05, 0x68, 0x9d, 0x3f, 0x6b, 0xd9, 0xb3, 0x01, 0xb3, - 0x5d, 0xf4, 0x1f, 0x24, 0x87, 0xfd, 0xb9, 0xe0, 0x4c, 0x29, 0xbb, 0xba, 0xe3, 0x73, 0xa1, 0x43, - 0x85, 0xd0, 0x21, 0x64, 0x9a, 0xf5, 0x06, 0x19, 0xc5, 0x15, 0x4a, 0x46, 0xa6, 0xb4, 0xbd, 0xca, - 0xac, 0x37, 0xe6, 0x64, 0xe2, 0x23, 0x58, 0xda, 0xc8, 0x84, 0xc4, 0xd0, 0x77, 0x72, 0x09, 0x29, - 0x1a, 0x4b, 0xd3, 0x2a, 0x43, 0x52, 0xb2, 0xa2, 0xbf, 0x60, 0x63, 0x48, 0xa4, 0x38, 0xd5, 0x34, - 0x8b, 0xf5, 0x61, 0x5b, 0x00, 0xf4, 0x27, 0xe8, 0x43, 0xfb, 0x05, 0x8b, 0x54, 0x03, 0xe9, 0x95, - 0xc0, 0xea, 0x8b, 0x2b, 0x5f, 0x91, 0x5a, 0xa0, 0x7b, 0xe4, 0x7b, 0x72, 0xcf, 0x94, 0x5c, 0x6f, - 0x4e, 0xef, 0x91, 0x11, 0x8b, 0xf8, 0x82, 0xc8, 0x6b, 0x09, 0x20, 0x3f, 0xae, 0x33, 0xdc, 0x5c, - 0x4e, 0x11, 0xeb, 0x02, 0x0f, 0x37, 0xe7, 0xdc, 0xd6, 0x1b, 0x0d, 0x92, 0x92, 0xe6, 0x77, 0xdc, - 0xe3, 0xff, 0x52, 0x93, 0xc3, 0xa3, 0xc1, 0x7c, 0x7b, 0x5b, 0x2b, 0xe1, 0x0d, 0xe1, 0x15, 0x1a, - 0xe5, 0x81, 0xb6, 0xe5, 0x78, 0x4e, 0xc8, 0x95, 0xba, 0xac, 0x98, 0xa7, 0x11, 0xf2, 0x78, 0x1e, - 0x36, 0xe2, 0xe3, 0x5c, 0x72, 0x3f, 0x21, 0x34, 0xeb, 0xde, 0xb5, 0x00, 0xd6, 0x2b, 0x0d, 0x74, - 0x55, 0x2c, 0x37, 0x6d, 0xcf, 0xa5, 0x1a, 0x58, 0x9a, 0x68, 0x17, 0x32, 0xe2, 0x20, 0xb6, 0x13, - 0x90, 0x88, 0xde, 0xaa, 0x9e, 0x06, 0x4e, 0x0b, 0x57, 0xdd, 0x09, 0x30, 0xbd, 0x55, 0x15, 0x4e, - 0xa0, 0xba, 0xc8, 0x0a, 0x27, 0x90, 0x4f, 0x56, 0x2c, 0x8d, 0x86, 0xf2, 0xa9, 0x89, 0x3e, 0xd2, - 0x6f, 0x78, 0xad, 0x18, 0xa3, 0x1d, 0x80, 0x58, 0x01, 0x19, 0xd0, 0x30, 0xa7, 0xab, 0xcd, 0x19, - 0x4a, 0xc5, 0x25, 0x0d, 0x1f, 0x3e, 0x87, 0x0d, 0x4c, 0x7b, 0xf2, 0xa9, 0x65, 0x21, 0x7d, 0xda, - 0xa9, 0x1e, 0x97, 0x49, 0xb5, 0x52, 0x14, 0xaf, 0x52, 0xc0, 0xce, 0x4d, 0xad, 0x58, 0x22, 0xb5, - 0x52, 0xd5, 0xd4, 0x24, 0x6c, 0x5c, 0x55, 0x2a, 0x35, 0x52, 0xa9, 0x56, 0xcc, 0x75, 0x94, 0x06, - 0xfd, 0xb4, 0x73, 0x58, 0x2e, 0x9b, 0x09, 0x19, 0xa9, 0x77, 0x6a, 0x07, 0x47, 0x2a, 0x31, 0x19, - 0x27, 0x1e, 0x56, 0x8a, 0xe4, 0xe8, 0xa0, 0x68, 0xea, 0x27, 0xe6, 0xfb, 0x2f, 0xbb, 0xda, 0x07, - 0xf1, 0xfb, 0x2c, 0x7e, 0xaf, 0xbf, 0xee, 0xae, 0x75, 0x37, 0xd4, 0x3f, 0x56, 0xf9, 0x5b, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x10, 0xf6, 0x16, 0x7f, 0x2f, 0x05, 0x00, 0x00, + // 713 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xd1, 0x6e, 0xda, 0x4a, + 0x10, 0x8d, 0x03, 0x0e, 0x66, 0x08, 0xb9, 0xd6, 0xe6, 0x5e, 0x89, 0x7b, 0x6f, 0x95, 0xa4, 0x96, + 0x2a, 0x55, 0x91, 0x0a, 0x04, 0x92, 0x00, 0x8f, 0x84, 0x26, 0x8a, 0x94, 0x90, 0xd0, 0x0d, 0x3c, + 0xf4, 0xa5, 0x2b, 0x63, 0xaf, 0xc1, 0x35, 0x78, 0x89, 0x59, 0x42, 0xf9, 0x81, 0x7e, 0x43, 0x3e, + 0xa2, 0x7f, 0xd0, 0x1f, 0xe8, 0x63, 0x9f, 0xfb, 0x50, 0x55, 0xed, 0x1f, 0xf4, 0x0b, 0xba, 0xbb, + 0x26, 0x90, 0x87, 0x4a, 0x95, 0x2a, 0x45, 0xea, 0x83, 0xb5, 0x73, 0x66, 0xe6, 0xcc, 0x9c, 0x99, + 0x65, 0x81, 0xa3, 0x9e, 0xcf, 0xfb, 0x93, 0x6e, 0xde, 0x61, 0xc3, 0x42, 0xbb, 0x4f, 0xdb, 0x7d, + 0x3f, 0xec, 0x8d, 0x2f, 0x28, 0x9f, 0xb2, 0x28, 0x28, 0x70, 0x1e, 0x16, 0xec, 0x91, 0x5f, 0x18, + 0x45, 0x8c, 0x33, 0x87, 0x0d, 0x0a, 0x03, 0x16, 0xd9, 0x53, 0x3b, 0xbc, 0x3b, 0xf3, 0x2a, 0x80, + 0x52, 0x73, 0xf8, 0xdf, 0xb3, 0x7b, 0xc5, 0x7a, 0xac, 0xc7, 0x62, 0x62, 0x77, 0xe2, 0x29, 0xa4, + 0x80, 0xb2, 0x62, 0x9e, 0x75, 0xab, 0x81, 0xd1, 0xa4, 0xdc, 0x76, 0x6d, 0x6e, 0xa3, 0x32, 0xc0, + 0x90, 0xb9, 0x93, 0x81, 0xcd, 0x7d, 0x16, 0xe6, 0x32, 0x3b, 0xda, 0xd3, 0x8d, 0xd2, 0x66, 0xfe, + 0xae, 0x51, 0x73, 0x11, 0xc2, 0xf7, 0xd2, 0xd0, 0xff, 0x90, 0x96, 0x64, 0x12, 0xd9, 0x9c, 0xe6, + 0xd6, 0x05, 0x27, 0x8d, 0x0d, 0xe9, 0xc0, 0x02, 0xa3, 0x7f, 0xc1, 0xe8, 0xfa, 0x3c, 0x8e, 0x65, + 0x45, 0x2c, 0x8b, 0x53, 0x02, 0xab, 0xd0, 0x36, 0x64, 0x1c, 0xe6, 0x8a, 0x51, 0xe3, 0xe8, 0x86, + 0x62, 0x42, 0xec, 0x92, 0x09, 0xd6, 0x77, 0x0d, 0xfe, 0x6a, 0xbf, 0x69, 0xb0, 0xd0, 0xf3, 0x7b, + 0x93, 0x28, 0x6e, 0xf6, 0x02, 0x0c, 0x97, 0xde, 0x10, 0xdb, 0x75, 0xa3, 0x9c, 0x26, 0x18, 0xeb, + 0x47, 0x87, 0x9f, 0x3e, 0x6f, 0x97, 0x7e, 0xb5, 0x40, 0x87, 0x45, 0xb4, 0xc0, 0x67, 0x23, 0x3a, + 0xce, 0x3f, 0xa7, 0x37, 0x75, 0xc1, 0xc6, 0x29, 0x37, 0x36, 0xfe, 0xb0, 0xa1, 0xdf, 0x6b, 0x80, + 0xea, 0x0e, 0xf7, 0x6f, 0x54, 0x9f, 0xc5, 0xcd, 0x3c, 0xc0, 0xdc, 0x18, 0xd2, 0xe1, 0x34, 0x20, + 0x63, 0x12, 0xd0, 0x59, 0x6e, 0xf5, 0xb7, 0x6a, 0x5e, 0x4c, 0x83, 0xab, 0x33, 0x3a, 0xc3, 0xa9, + 0x30, 0x36, 0xac, 0x29, 0x40, 0xeb, 0xf4, 0x65, 0xcb, 0x9e, 0x0d, 0x98, 0xed, 0xa2, 0xc7, 0x90, + 0x1c, 0xf6, 0xe7, 0x82, 0x33, 0xa5, 0xec, 0x72, 0xa7, 0xa7, 0x42, 0x87, 0x0a, 0xa1, 0x7d, 0xc8, + 0x34, 0xeb, 0x0d, 0x32, 0x8a, 0x19, 0x4a, 0x46, 0xe6, 0xfe, 0xf6, 0xeb, 0x8d, 0x79, 0x31, 0xb1, + 0xfd, 0x85, 0x8d, 0x4c, 0x48, 0x0c, 0x7d, 0x27, 0x97, 0x90, 0xa2, 0xb1, 0x34, 0xad, 0x32, 0x24, + 0x65, 0x55, 0xf4, 0x0f, 0xac, 0x0d, 0x89, 0x14, 0xa7, 0x9a, 0x66, 0xb1, 0x3e, 0x6c, 0x0b, 0x80, + 0xfe, 0x06, 0x7d, 0x68, 0xbf, 0x66, 0x91, 0x6a, 0x20, 0xbd, 0x12, 0x58, 0x7d, 0x80, 0x65, 0x03, + 0x64, 0x81, 0xee, 0x91, 0x9f, 0xc9, 0x3d, 0x51, 0x72, 0xbd, 0x79, 0x79, 0x8f, 0x8c, 0x58, 0xc4, + 0xef, 0x0a, 0x79, 0x2d, 0x01, 0xe4, 0xad, 0x9e, 0xe0, 0xe6, 0x62, 0x8a, 0x58, 0x17, 0x78, 0xb8, + 0x39, 0xaf, 0x6d, 0xbd, 0xd3, 0x20, 0x29, 0xcb, 0x3c, 0xc4, 0x3d, 0x3e, 0x91, 0x9a, 0x1c, 0x1e, + 0x0d, 0xe6, 0xdb, 0xdb, 0x58, 0x0a, 0x6f, 0x08, 0xaf, 0xd0, 0x28, 0x0f, 0xb4, 0x29, 0xc7, 0x73, + 0x42, 0xae, 0xd4, 0x65, 0xc5, 0x3c, 0x8d, 0x90, 0xc7, 0xf3, 0xb0, 0x11, 0x1f, 0xe7, 0x92, 0x3b, + 0x09, 0xa1, 0x59, 0xf7, 0x2e, 0x05, 0xb0, 0xde, 0x6a, 0xa0, 0x2b, 0xb2, 0xdc, 0xb4, 0x3d, 0x97, + 0x6a, 0x60, 0x69, 0xa2, 0x2d, 0xc8, 0x88, 0x83, 0xd8, 0x4e, 0x40, 0x22, 0x7a, 0xad, 0x7a, 0x1a, + 0x38, 0x2d, 0x5c, 0x75, 0x27, 0xc0, 0xf4, 0x5a, 0x31, 0x9c, 0x40, 0x75, 0x91, 0x0c, 0x27, 0x90, + 0x6f, 0x45, 0x2c, 0x8d, 0x86, 0xf2, 0x37, 0x2e, 0xfa, 0x48, 0xbf, 0xe1, 0xb5, 0x62, 0x8c, 0x1e, + 0x01, 0xc4, 0x0a, 0xc8, 0x80, 0x86, 0x39, 0x5d, 0x6d, 0xce, 0x50, 0x2a, 0xce, 0x69, 0xb8, 0xbb, + 0x2d, 0x6e, 0x68, 0xf9, 0xe8, 0x0c, 0x48, 0x9e, 0x5f, 0xe2, 0xba, 0xb9, 0x82, 0x52, 0x90, 0x38, + 0xb9, 0x3a, 0x33, 0xb5, 0xdd, 0x57, 0xb0, 0x86, 0x69, 0x4f, 0x06, 0xb3, 0x90, 0x3e, 0xee, 0x54, + 0x0f, 0xcb, 0xa4, 0x5a, 0x29, 0x8a, 0x0c, 0x01, 0x3b, 0x57, 0xb5, 0x62, 0x89, 0xd4, 0x4a, 0x55, + 0x53, 0x93, 0xb0, 0x71, 0x51, 0xa9, 0xd4, 0x48, 0xa5, 0x5a, 0x31, 0x57, 0x51, 0x1a, 0xf4, 0xe3, + 0xce, 0x7e, 0xb9, 0x6c, 0x26, 0x64, 0xa4, 0xde, 0xa9, 0xed, 0x1d, 0xa8, 0xc4, 0x64, 0x9c, 0xb8, + 0x5f, 0x29, 0x92, 0x83, 0xbd, 0xa2, 0xa9, 0x1f, 0x99, 0x1f, 0xbe, 0x6e, 0x69, 0x1f, 0xc5, 0xf7, + 0x45, 0x7c, 0xb7, 0xdf, 0xb6, 0x56, 0xba, 0x6b, 0xea, 0x7f, 0xb3, 0xfc, 0x23, 0x00, 0x00, 0xff, + 0xff, 0x1e, 0x91, 0x6f, 0x01, 0xb5, 0x05, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 4fd1dfd99..2faf90c6b 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -4,11 +4,12 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; package lorawan; +enum Modulation { + LORA = 0; + FSK = 1; +} + message Metadata { - enum Modulation { - LORA = 0; - FSK = 1; - } Modulation modulation = 11; string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} uint32 bit_rate = 13; // FSK bit rate in bit/s @@ -17,6 +18,10 @@ message Metadata { message TxConfiguration { bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + Modulation modulation = 11; + string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} + uint32 bit_rate = 13; // FSK bit rate in bit/s + string coding_rate = 14; // LoRa coding rate } message ActivationMetadata { diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go new file mode 100644 index 000000000..5e3fc02f3 --- /dev/null +++ b/core/router/gateway/gateway.go @@ -0,0 +1,19 @@ +package gateway + +import "github.com/TheThingsNetwork/ttn/core/types" + +func NewGateway(eui types.GatewayEUI) *Gateway { + return &Gateway{ + EUI: eui, + Status: NewStatusStore(), + Utilization: NewUtilization(), + Schedule: NewSchedule(), + } +} + +type Gateway struct { + EUI types.GatewayEUI + Status StatusStore + Utilization Utilization + Schedule Schedule +} diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go new file mode 100644 index 000000000..e00c7b738 --- /dev/null +++ b/core/router/gateway/status.go @@ -0,0 +1,72 @@ +package gateway + +import ( + "fmt" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + "gopkg.in/redis.v3" +) + +// StatusStore is a database for setting and retrieving the latest gateway status +type StatusStore interface { + // Insert or Update the status + Update(status *pb_gateway.StatusMessage) error + // Get the last status + Get() (*pb_gateway.StatusMessage, error) +} + +// NewStatusStore creates a new in-memory status store +func NewStatusStore() StatusStore { + return &statusStore{} +} + +type statusStore struct { + lastStatus *pb_gateway.StatusMessage +} + +func (s *statusStore) Update(status *pb_gateway.StatusMessage) error { + s.lastStatus = status + return nil +} + +func (s *statusStore) Get() (*pb_gateway.StatusMessage, error) { + if s.lastStatus != nil { + return s.lastStatus, nil + } + return &pb_gateway.StatusMessage{}, nil +} + +// NewRedisStatusStore creates a new Redis-based status store +func NewRedisStatusStore(client *redis.Client, eui types.GatewayEUI) StatusStore { + return &redisStatusStore{ + client: client, + key: fmt.Sprintf("gateway:%s", eui), + } +} + +type redisStatusStore struct { + client *redis.Client + key string +} + +func (s *redisStatusStore) Update(status *pb_gateway.StatusMessage) error { + m, err := status.ToStringStringMap(pb_gateway.StatusMessageProperties...) + if err != nil { + return err + } + return s.client.HMSetMap(s.key, m).Err() +} + +func (s *redisStatusStore) Get() (*pb_gateway.StatusMessage, error) { + status := &pb_gateway.StatusMessage{} + res, err := s.client.HGetAllMap(s.key).Result() + if err != nil { + return nil, err + } + err = status.FromStringStringMap(res) + if err != nil { + return nil, err + } + return status, nil +} diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go new file mode 100644 index 000000000..202666722 --- /dev/null +++ b/core/router/gateway/status_test.go @@ -0,0 +1,81 @@ +package gateway + +import ( + "testing" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" + + "gopkg.in/redis.v3" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) +} + +func TestNewGatewayStatusStore(t *testing.T) { + a := New(t) + client := getRedisClient() + eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 0} + store := NewRedisStatusStore(client, eui) + a.So(store, ShouldNotBeNil) + store = NewStatusStore() + a.So(store, ShouldNotBeNil) +} + +func TestStatusGetUpsert(t *testing.T) { + a := New(t) + store := NewStatusStore() + + // Get non-existing gateway status -> expect empty + status, err := store.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, pb_gateway.StatusMessage{}) + + // Update -> expect no error + statusMessage := &pb_gateway.StatusMessage{Description: "Fake Gateway"} + err = store.Update(statusMessage) + a.So(err, ShouldBeNil) + + // Get existing gateway status -> expect status + status, err = store.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} + +func TestRedisStatusGetUpsert(t *testing.T) { + a := New(t) + eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 1} + client := getRedisClient() + store := NewRedisStatusStore(client, eui) + + // Cleanup before and after + client.Del(store.(*redisStatusStore).key) + defer client.Del(store.(*redisStatusStore).key) + + // Get non-existing gateway status -> expect empty + status, err := store.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, pb_gateway.StatusMessage{}) + + // Update -> expect no error + statusMessage := &pb_gateway.StatusMessage{Description: "Fake Gateway"} + err = store.Update(statusMessage) + a.So(err, ShouldBeNil) + + // Get existing gateway status -> expect status + status, err = store.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} + +// TODO: Test error cases diff --git a/core/router/gateway/utilization.go b/core/router/gateway/utilization.go new file mode 100644 index 000000000..07268dfc5 --- /dev/null +++ b/core/router/gateway/utilization.go @@ -0,0 +1,123 @@ +package gateway + +import ( + "sync" + "time" + + pb_router "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/rcrowley/go-metrics" +) + +// Utilization manages the utilization of a gateway and its channels +// It is based on an exponentially weighted moving average over one minute +type Utilization interface { + // AddRx updates the utilization for receiving an uplink message + AddRx(uplink *pb_router.UplinkMessage) error + // AddRx updates the utilization for transmitting a downlink message + AddTx(downlink *pb_router.DownlinkMessage) error + // Get returns the overall rx and tx utilization for the gateway. If the gateway has multiple channels, the values will be 0 <= value < numChannels + Get() (rx float64, tx float64) + // GetChannel returns the rx and tx utilization for the given channel. The values will be 0 <= value < 1 + GetChannel(frequency uint64) (rx float64, tx float64) + // Tick the clock to update the moving average. It should be called every 5 seconds + Tick() +} + +// NewUtilization creates a new Utilization +func NewUtilization() Utilization { + return &utilization{ + overallRx: metrics.NewEWMA1(), + channelRx: map[uint64]metrics.EWMA{}, + overallTx: metrics.NewEWMA1(), + channelTx: map[uint64]metrics.EWMA{}, + } +} + +type utilization struct { + overallRx metrics.EWMA + channelRx map[uint64]metrics.EWMA + channelRxLock sync.RWMutex + overallTx metrics.EWMA + channelTx map[uint64]metrics.EWMA + channelTxLock sync.RWMutex +} + +func (u *utilization) AddRx(uplink *pb_router.UplinkMessage) error { + var t time.Duration + var err error + if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { + t, err = toa.Compute(uint(len(uplink.Payload)), lorawan.DataRate, lorawan.CodingRate) + if err != nil { + return err + } + } + if t == 0 { + return nil + } + u.overallRx.Update(int64(t) / 1000) + frequency := uplink.GatewayMetadata.Frequency + u.channelRxLock.Lock() + defer u.channelRxLock.Unlock() + if _, ok := u.channelRx[frequency]; !ok { + u.channelRx[frequency] = metrics.NewEWMA1() + } + u.channelRx[frequency].Update(int64(t) / 1000) + return nil +} + +func (u *utilization) AddTx(downlink *pb_router.DownlinkMessage) error { + var t time.Duration + var err error + if lorawan := downlink.ProtocolConfiguration.GetLorawan(); lorawan != nil { + t, err = toa.Compute(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) + if err != nil { + return err + } + } + if t == 0 { + return nil + } + u.overallTx.Update(int64(t) / 1000) + frequency := downlink.GatewayConfiguration.Frequency + u.channelTxLock.Lock() + defer u.channelTxLock.Unlock() + if _, ok := u.channelTx[frequency]; !ok { + u.channelTx[frequency] = metrics.NewEWMA1() + } + u.channelTx[frequency].Update(int64(t) / 1000) + return nil +} + +func (u *utilization) Tick() { + u.overallRx.Tick() + u.channelRxLock.RLock() + for _, ch := range u.channelRx { + ch.Tick() + } + u.channelRxLock.RUnlock() + u.overallTx.Tick() + u.channelTxLock.RLock() + for _, ch := range u.channelTx { + ch.Tick() + } + u.channelTxLock.RUnlock() +} + +func (u *utilization) Get() (float64, float64) { + return u.overallRx.Snapshot().Rate() * 1000.0 / float64(time.Second), u.overallTx.Snapshot().Rate() * 1000.0 / float64(time.Second) +} + +func (u *utilization) GetChannel(frequency uint64) (rx float64, tx float64) { + u.channelRxLock.RLock() + if channel, ok := u.channelRx[frequency]; ok { + rx = channel.Snapshot().Rate() * 1000.0 / float64(time.Second) + } + u.channelRxLock.RUnlock() + u.channelTxLock.RLock() + if channel, ok := u.channelTx[frequency]; ok { + tx = channel.Snapshot().Rate() * 1000.0 / float64(time.Second) + } + u.channelTxLock.RUnlock() + return +} diff --git a/core/router/gateway/utilization_test.go b/core/router/gateway/utilization_test.go new file mode 100644 index 000000000..581554061 --- /dev/null +++ b/core/router/gateway/utilization_test.go @@ -0,0 +1,92 @@ +package gateway + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/smartystreets/assertions" +) + +func buildUplink(freq uint64) *pb.UplinkMessage { + return &pb.UplinkMessage{Payload: make([]byte, 10), ProtocolMetadata: &pb_protocol.RxMetadata{ + Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{ + DataRate: "SF7BW125", + CodingRate: "4/5", + }}, + }, GatewayMetadata: &gateway.RxMetadata{ + Frequency: freq, + }} +} + +func buildDownlink(freq uint64) *pb.DownlinkMessage { + return &pb.DownlinkMessage{Payload: make([]byte, 10), ProtocolConfiguration: &pb_protocol.TxConfiguration{ + Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + DataRate: "SF7BW125", + CodingRate: "4/5", + }}, + }, GatewayConfiguration: &gateway.TxConfiguration{ + Frequency: freq, + }} +} + +func TestRxUtilization(t *testing.T) { + a := New(t) + u := NewUtilization() + err := u.AddRx(buildUplink(8680000000)) + a.So(err, ShouldBeNil) + err = u.AddRx(buildUplink(8682000000)) + a.So(err, ShouldBeNil) + + rx, tx := u.Get() + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0) + + u.Tick() // 5 seconds later + + rx, tx = u.GetChannel(8680000000) + a.So(rx, ShouldAlmostEqual, 0.041216/5.0) // 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) + + rx, tx = u.Get() + a.So(rx, ShouldAlmostEqual, 0.082432/5.0) // two times 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) + + u.AddRx(buildUplink(8680000000)) + u.AddRx(buildUplink(8682000000)) + + u.Tick() // 5 seconds later + + rx, tx = u.GetChannel(8680000000) + a.So(rx, ShouldAlmostEqual, 0.041216/5.0) // still 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) + + rx, tx = u.Get() + a.So(rx, ShouldAlmostEqual, 0.082432/5.0) // still two times 41 ms per second + a.So(tx, ShouldAlmostEqual, 0) +} + +func TestTxUtilization(t *testing.T) { + a := New(t) + u := NewUtilization() + err := u.AddTx(buildDownlink(8680000000)) + a.So(err, ShouldBeNil) + err = u.AddTx(buildDownlink(8682000000)) + a.So(err, ShouldBeNil) + + rx, tx := u.Get() + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0) + + u.Tick() // 5 seconds later + + rx, tx = u.GetChannel(8680000000) + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0.041216/5.0) // 41 ms per second + + rx, tx = u.Get() + a.So(rx, ShouldAlmostEqual, 0) + a.So(tx, ShouldAlmostEqual, 0.082432/5.0) // two times 41 ms per second +} diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index a3c9b5b16..076b5b01b 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -1,57 +1,11 @@ package router import ( - "fmt" - - "github.com/TheThingsNetwork/ttn/api/gateway" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/core/types" - "gopkg.in/redis.v3" ) -type GatewayStatusStore interface { - Upsert(id types.GatewayEUI, status *gateway.StatusMessage) error - Get(id types.GatewayEUI) (*gateway.StatusMessage, error) -} - -type redisGatewayStore struct { - client *redis.Client -} - -func NewGatewayStatusStore(client *redis.Client) GatewayStatusStore { - return &redisGatewayStore{ - client: client, - } -} - -func (s *redisGatewayStore) key(gatewayEUI types.GatewayEUI) string { - return fmt.Sprintf("gateway:%s", gatewayEUI) -} - -func (s *redisGatewayStore) Upsert(gatewayEUI types.GatewayEUI, status *gateway.StatusMessage) error { - m, err := status.ToStringStringMap(gateway.StatusMessageProperties...) - if err != nil { - return err - } - return s.client.HMSetMap(s.key(gatewayEUI), m).Err() -} - -func (s *redisGatewayStore) Get(gatewayEUI types.GatewayEUI) (*gateway.StatusMessage, error) { - status := &gateway.StatusMessage{} - res, err := s.client.HGetAllMap(s.key(gatewayEUI)).Result() - if err != nil { - return nil, err - } - err = status.FromStringStringMap(res) - if err != nil { - return nil, err - } - return status, nil -} - -func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *gateway.StatusMessage) error { - err := r.gatewayStatusStore.Upsert(gatewayEUI, status) - if err != nil { - return err - } - return nil +func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.StatusMessage) error { + gateway := r.getGateway(gatewayEUI) + return gateway.Status.Update(status) } diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index 61770d66f..fa111696c 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -3,75 +3,28 @@ package router import ( "testing" - "github.com/TheThingsNetwork/ttn/api/gateway" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" - - "gopkg.in/redis.v3" ) -func getRedisClient() *redis.Client { - return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Password: "", // no password set - DB: 0, // use default DB - }) -} - -func TestNewGatewayStatusStore(t *testing.T) { - a := New(t) - client := getRedisClient() - store := NewGatewayStatusStore(client) - a.So(store, ShouldNotBeNil) -} - -func TestGatewayGetUpsert(t *testing.T) { - a := New(t) - eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 1} - client := getRedisClient() - store := NewGatewayStatusStore(client) - - // Cleanup before and after - client.Del(store.(*redisGatewayStore).key(eui)) - // defer client.Del(store.(*redisGatewayStore).key(eui)) - - // Get non-existing gateway status -> expect empty - status, err := store.Get(eui) - a.So(err, ShouldBeNil) - a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, gateway.StatusMessage{}) - - // Upsert -> expect no error - statusMessage := &gateway.StatusMessage{Description: "Fake Gateway"} - err = store.Upsert(eui, statusMessage) - a.So(err, ShouldBeNil) - - // Get existing gateway status -> expect status - status, err = store.Get(eui) - a.So(err, ShouldBeNil) - a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, *statusMessage) -} - func TestHandleGatewayStatus(t *testing.T) { a := New(t) eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 2} - client := getRedisClient() - store := NewGatewayStatusStore(client) + router := &router{ - gatewayStatusStore: store, + gateways: map[types.GatewayEUI]*gateway.Gateway{}, } // Handle - statusMessage := &gateway.StatusMessage{Description: "Fake Gateway"} + statusMessage := &pb_gateway.StatusMessage{Description: "Fake Gateway"} err := router.HandleGatewayStatus(eui, statusMessage) a.So(err, ShouldBeNil) // Check storage - status, err := store.Get(eui) + status, err := router.getGateway(eui).Status.Get() a.So(err, ShouldBeNil) a.So(status, ShouldNotBeNil) a.So(*status, ShouldResemble, *statusMessage) } - -// TODO: Test error cases diff --git a/core/router/router.go b/core/router/router.go index 1f03d3828..ec7a32977 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -1,17 +1,73 @@ package router import ( - "github.com/TheThingsNetwork/ttn/api/gateway" + "sync" + + "golang.org/x/net/context" + + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/api" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/discovery" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" ) +// Router component type Router interface { - HandleGatewayStatus(status *gateway.StatusMessage) error - HandleUplink(uplink *pb.UplinkMessage) error - HandleDownlink() error - HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) + // Handle a status message from a gateway + HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.StatusMessage) error + // Handle an uplink message from a gateway + HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error + // Handle a downlink message + HandleDownlink(message *pb.DownlinkMessage) error + // Subscribe to downlink messages + SubscribeDownlink(gatewayEUI types.GatewayEUI) (chan pb.DownlinkMessage, error) + // Unsubscribe from downlink messages + UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error + // Handle a device activation + HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) } type router struct { - gatewayStatusStore GatewayStatusStore + identity *pb_discovery.Announcement + gateways map[types.GatewayEUI]*gateway.Gateway + gatewaysLock sync.RWMutex + brokerDiscovery discovery.BrokerDiscovery + brokers map[string]pb_broker.Broker_AssociateClient + brokersLock sync.RWMutex +} + +// getGateway gets or creates a Gateway +func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { + r.gatewaysLock.Lock() + defer r.gatewaysLock.Unlock() + if _, ok := r.gateways[eui]; !ok { + r.gateways[eui] = gateway.NewGateway(eui) + } + return r.gateways[eui] +} + +// getBroker gets or creates a broker association +func (r *router) getBroker(broker *pb_discovery.Announcement) (pb_broker.Broker_AssociateClient, error) { + r.gatewaysLock.Lock() + defer r.gatewaysLock.Unlock() + if _, ok := r.brokers[broker.NetAddress]; !ok { + // Connect to the server + conn, err := grpc.Dial(broker.NetAddress, api.DialOptions...) + if err != nil { + return nil, err + } + client := pb_broker.NewBrokerClient(conn) + association, err := client.Associate(context.Background()) + if err != nil { + return nil, err + } + r.brokers[broker.NetAddress] = association + } + return r.brokers[broker.NetAddress], nil } From e94478772fcab6e173d49a890fe503710c9b7749 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 8 May 2016 16:13:41 +0200 Subject: [PATCH 1429/2266] [refactor] Add NOC protos --- Makefile | 1 + api/broker/broker.pb.go | 362 +++++++++++++++----------- api/broker/broker.proto | 7 +- api/gateway/gateway.pb.go | 94 +++---- api/gateway/gateway.proto | 4 +- api/gateway/status_message.go | 14 +- api/gateway/status_message_test.go | 6 +- api/handler/handler.pb.go | 68 ++--- api/handler/handler.proto | 6 +- api/networkserver/networkserver.pb.go | 130 ++++----- api/networkserver/networkserver.proto | 6 +- api/noc/noc.pb.go | 361 +++++++++++++++++++++++++ api/noc/noc.proto | 16 ++ api/router/router.pb.go | 189 +++++++------- api/router/router.proto | 10 +- core/router/gateway/status.go | 18 +- core/router/gateway/status_test.go | 8 +- core/router/gateway_status.go | 2 +- core/router/gateway_status_test.go | 2 +- core/router/router.go | 2 +- 20 files changed, 870 insertions(+), 436 deletions(-) create mode 100644 api/noc/noc.pb.go create mode 100644 api/noc/noc.proto diff --git a/Makefile b/Makefile index 09ac0cef7..68521f4ba 100644 --- a/Makefile +++ b/Makefile @@ -62,6 +62,7 @@ proto: @$(PROTOC)/api/handler/handler.proto @$(PROTOC)/api/networkserver/networkserver.proto @$(PROTOC)/api/discovery/discovery.proto + @$(PROTOC)/api/noc/noc.proto cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 3b731ec8e..317647411 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -22,7 +22,7 @@ RegisterApplicationRequest UnregisterApplicationRequest StatusRequest - StatusResponse + Status */ package broker @@ -53,9 +53,10 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 type DownlinkOption struct { - Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` - Score uint32 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` - Deadline int64 `protobuf:"varint,3,opt,name=deadline,proto3" json:"deadline,omitempty"` + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + Score uint32 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` + Deadline int64 `protobuf:"varint,3,opt,name=deadline,proto3" json:"deadline,omitempty"` + Configuration *gateway.TxConfiguration `protobuf:"bytes,4,opt,name=configuration" json:"configuration,omitempty"` } func (m *DownlinkOption) Reset() { *m = DownlinkOption{} } @@ -63,6 +64,13 @@ func (m *DownlinkOption) String() string { return proto.CompactTextSt func (*DownlinkOption) ProtoMessage() {} func (*DownlinkOption) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{0} } +func (m *DownlinkOption) GetConfiguration() *gateway.TxConfiguration { + if m != nil { + return m.Configuration + } + return nil +} + // received from the Router type UplinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` @@ -322,8 +330,8 @@ func (m *StatusRequest) String() string { return proto.CompactTextStr func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{12} } -// message StatusResponse is the response to the StatusRequest -type StatusResponse struct { +// message Status is the response to the StatusRequest +type Status struct { // Uplink Uplink *api.Rates `protobuf:"bytes,1,opt,name=uplink" json:"uplink,omitempty"` UplinkUnique *api.Rates `protobuf:"bytes,2,opt,name=uplink_unique,json=uplinkUnique" json:"uplink_unique,omitempty"` @@ -340,54 +348,54 @@ type StatusResponse struct { ConnectedHandlers uint32 `protobuf:"varint,42,opt,name=connected_handlers,json=connectedHandlers,proto3" json:"connected_handlers,omitempty"` } -func (m *StatusResponse) Reset() { *m = StatusResponse{} } -func (m *StatusResponse) String() string { return proto.CompactTextString(m) } -func (*StatusResponse) ProtoMessage() {} -func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{13} } +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{13} } -func (m *StatusResponse) GetUplink() *api.Rates { +func (m *Status) GetUplink() *api.Rates { if m != nil { return m.Uplink } return nil } -func (m *StatusResponse) GetUplinkUnique() *api.Rates { +func (m *Status) GetUplinkUnique() *api.Rates { if m != nil { return m.UplinkUnique } return nil } -func (m *StatusResponse) GetDownlink() *api.Rates { +func (m *Status) GetDownlink() *api.Rates { if m != nil { return m.Downlink } return nil } -func (m *StatusResponse) GetActivations() *api.Rates { +func (m *Status) GetActivations() *api.Rates { if m != nil { return m.Activations } return nil } -func (m *StatusResponse) GetActivationsUnique() *api.Rates { +func (m *Status) GetActivationsUnique() *api.Rates { if m != nil { return m.ActivationsUnique } return nil } -func (m *StatusResponse) GetActivationsAccepted() *api.Rates { +func (m *Status) GetActivationsAccepted() *api.Rates { if m != nil { return m.ActivationsAccepted } return nil } -func (m *StatusResponse) GetDeduplication() *api.Percentiles { +func (m *Status) GetDeduplication() *api.Percentiles { if m != nil { return m.Deduplication } @@ -408,7 +416,7 @@ func init() { proto.RegisterType((*RegisterApplicationRequest)(nil), "broker.RegisterApplicationRequest") proto.RegisterType((*UnregisterApplicationRequest)(nil), "broker.UnregisterApplicationRequest") proto.RegisterType((*StatusRequest)(nil), "broker.StatusRequest") - proto.RegisterType((*StatusResponse)(nil), "broker.StatusResponse") + proto.RegisterType((*Status)(nil), "broker.Status") } // Reference imports to suppress errors if they are not otherwise used. @@ -693,7 +701,7 @@ type BrokerManagerClient interface { // Application owner unregisters Application with Broker Manager UnregisterApplication(ctx context.Context, in *UnregisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) // Network operator requests Broker status - Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } type brokerManagerClient struct { @@ -731,9 +739,9 @@ func (c *brokerManagerClient) UnregisterApplication(ctx context.Context, in *Unr return out, nil } -func (c *brokerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { - out := new(StatusResponse) - err := grpc.Invoke(ctx, "/broker.BrokerManager/Status", in, out, c.cc, opts...) +func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/broker.BrokerManager/GetStatus", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -750,7 +758,7 @@ type BrokerManagerServer interface { // Application owner unregisters Application with Broker Manager UnregisterApplication(context.Context, *UnregisterApplicationRequest) (*api.Ack, error) // Network operator requests Broker status - Status(context.Context, *StatusRequest) (*StatusResponse, error) + GetStatus(context.Context, *StatusRequest) (*Status, error) } func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { @@ -811,20 +819,20 @@ func _BrokerManager_UnregisterApplication_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } -func _BrokerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _BrokerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(BrokerManagerServer).Status(ctx, in) + return srv.(BrokerManagerServer).GetStatus(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/broker.BrokerManager/Status", + FullMethod: "/broker.BrokerManager/GetStatus", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).Status(ctx, req.(*StatusRequest)) + return srv.(BrokerManagerServer).GetStatus(ctx, req.(*StatusRequest)) } return interceptor(ctx, in, info, handler) } @@ -846,8 +854,8 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ Handler: _BrokerManager_UnregisterApplication_Handler, }, { - MethodName: "Status", - Handler: _BrokerManager_Status_Handler, + MethodName: "GetStatus", + Handler: _BrokerManager_GetStatus_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -884,6 +892,16 @@ func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintBroker(data, i, uint64(m.Deadline)) } + if m.Configuration != nil { + data[i] = 0x22 + i++ + i = encodeVarintBroker(data, i, uint64(m.Configuration.Size())) + n1, err := m.Configuration.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } return i, nil } @@ -912,21 +930,21 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n1, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n2, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n1 + i += n2 } if m.GatewayMetadata != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n2, err := m.GatewayMetadata.MarshalTo(data[i:]) + n3, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n3 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -970,11 +988,11 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n3, err := m.DownlinkOption.MarshalTo(data[i:]) + n4, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n4 } return i, nil } @@ -1004,11 +1022,11 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n4, err := m.DownlinkOption.MarshalTo(data[i:]) + n5, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n5 } return i, nil } @@ -1038,21 +1056,21 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n5, err := m.DevEui.MarshalTo(data[i:]) + n6, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n6 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n6, err := m.AppEui.MarshalTo(data[i:]) + n7, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n7 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1060,11 +1078,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n7, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n8, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n8 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1086,11 +1104,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n8, err := m.ResponseTemplate.MarshalTo(data[i:]) + n9, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n9 } if m.NeedDownlink { data[i] = 0x80 @@ -1132,21 +1150,21 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n9, err := m.DevEui.MarshalTo(data[i:]) + n10, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n10 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n10, err := m.AppEui.MarshalTo(data[i:]) + n11, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n11 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1154,11 +1172,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n11, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n12, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n12 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1166,11 +1184,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n12, err := m.GatewayMetadata.MarshalTo(data[i:]) + n13, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n13 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1178,11 +1196,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n13, err := m.ActivationMetadata.MarshalTo(data[i:]) + n14, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n14 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -1226,21 +1244,21 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n14, err := m.DevEui.MarshalTo(data[i:]) + n15, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n15 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n15, err := m.AppEui.MarshalTo(data[i:]) + n16, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n16 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1248,11 +1266,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n16, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n17, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n17 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1260,11 +1278,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n17, err := m.GatewayMetadata.MarshalTo(data[i:]) + n18, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n18 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1272,11 +1290,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n18, err := m.ActivationMetadata.MarshalTo(data[i:]) + n19, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n19 } if m.ResponseTemplate != nil { data[i] = 0xfa @@ -1284,11 +1302,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n19, err := m.ResponseTemplate.MarshalTo(data[i:]) + n20, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n20 } return i, nil } @@ -1428,7 +1446,7 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusResponse) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -1438,7 +1456,7 @@ func (m *StatusResponse) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *StatusResponse) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -1447,31 +1465,31 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n20, err := m.Uplink.MarshalTo(data[i:]) + n21, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n20 + i += n21 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n21, err := m.UplinkUnique.MarshalTo(data[i:]) + n22, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n21 + i += n22 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n22, err := m.Downlink.MarshalTo(data[i:]) + n23, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n22 + i += n23 } if m.Activations != nil { data[i] = 0xaa @@ -1479,11 +1497,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n23, err := m.Activations.MarshalTo(data[i:]) + n24, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n23 + i += n24 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1491,11 +1509,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n24, err := m.ActivationsUnique.MarshalTo(data[i:]) + n25, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n24 + i += n25 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1503,11 +1521,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n25, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n26, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n25 + i += n26 } if m.Deduplication != nil { data[i] = 0xfa @@ -1515,11 +1533,11 @@ func (m *StatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n26, err := m.Deduplication.MarshalTo(data[i:]) + n27, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n27 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1578,6 +1596,10 @@ func (m *DownlinkOption) Size() (n int) { if m.Deadline != 0 { n += 1 + sovBroker(uint64(m.Deadline)) } + if m.Configuration != nil { + l = m.Configuration.Size() + n += 1 + l + sovBroker(uint64(l)) + } return n } @@ -1788,7 +1810,7 @@ func (m *StatusRequest) Size() (n int) { return n } -func (m *StatusResponse) Size() (n int) { +func (m *Status) Size() (n int) { var l int _ = l if m.Uplink != nil { @@ -1937,6 +1959,39 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { break } } + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Configuration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Configuration == nil { + m.Configuration = &gateway.TxConfiguration{} + } + if err := m.Configuration.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBroker(data[iNdEx:]) @@ -3565,7 +3620,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *StatusResponse) Unmarshal(data []byte) error { +func (m *Status) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -3588,10 +3643,10 @@ func (m *StatusResponse) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + return fmt.Errorf("proto: Status: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3990,70 +4045,71 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1028 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0x27, 0x0d, 0xa4, 0xe9, 0x4b, 0xd2, 0xa4, 0xd3, 0xa6, 0xf5, 0x86, 0xaa, 0x2d, 0x5e, 0x84, - 0xca, 0x9f, 0x4d, 0xd8, 0xa2, 0xa5, 0x5a, 0x21, 0x40, 0x29, 0x5d, 0xc1, 0x22, 0x65, 0x59, 0x79, - 0xdb, 0x73, 0x34, 0xb1, 0x67, 0x93, 0x51, 0x53, 0xdb, 0x78, 0xc6, 0x2d, 0xbd, 0xf1, 0x21, 0x38, - 0x70, 0xe5, 0xb3, 0x70, 0x81, 0x1b, 0x67, 0x0e, 0x08, 0xc1, 0x85, 0x1b, 0x5f, 0x81, 0xf1, 0x78, - 0xc6, 0x7f, 0x9a, 0x98, 0x86, 0x45, 0x48, 0x1c, 0x7a, 0xb0, 0xec, 0x79, 0x7f, 0x7e, 0x6f, 0xe6, - 0xbd, 0xdf, 0x1b, 0x3f, 0x38, 0x1c, 0x53, 0x3e, 0x09, 0x47, 0x5d, 0xdb, 0x3b, 0xef, 0x9d, 0x4c, - 0xc8, 0xc9, 0x84, 0xba, 0x63, 0xf6, 0x84, 0xf0, 0x4b, 0x2f, 0x38, 0xeb, 0x71, 0xee, 0xf6, 0xb0, - 0x4f, 0x7b, 0xa3, 0xc0, 0x3b, 0x23, 0x81, 0x7a, 0x75, 0xfd, 0xc0, 0xe3, 0x1e, 0xaa, 0xc4, 0xab, - 0xce, 0xbd, 0x0c, 0xc0, 0xd8, 0x1b, 0x7b, 0x3d, 0xa9, 0x1e, 0x85, 0xcf, 0xe5, 0x4a, 0x2e, 0xe4, - 0x57, 0xec, 0x96, 0x33, 0x2f, 0x8c, 0x27, 0x1e, 0x65, 0xfe, 0xc1, 0x22, 0xe6, 0xd2, 0xd4, 0xf6, - 0xa6, 0xc9, 0x87, 0x72, 0x7e, 0xb8, 0x88, 0xf3, 0x18, 0x73, 0x72, 0x89, 0xaf, 0xf4, 0x3b, 0x76, - 0x35, 0x47, 0xb0, 0x7a, 0xec, 0x5d, 0xba, 0x53, 0xea, 0x9e, 0x7d, 0xe1, 0x73, 0xea, 0xb9, 0x68, - 0x07, 0x80, 0x3a, 0xc4, 0xe5, 0xf4, 0x39, 0x25, 0x81, 0x51, 0xda, 0x2b, 0xed, 0xaf, 0x58, 0x19, - 0x09, 0xda, 0x80, 0x57, 0x98, 0xed, 0x05, 0xc4, 0x58, 0x12, 0xaa, 0x86, 0x15, 0x2f, 0x50, 0x07, - 0xaa, 0x0e, 0xc1, 0x8e, 0xc0, 0x21, 0x46, 0x59, 0x28, 0xca, 0x56, 0xb2, 0x36, 0xff, 0x2c, 0x41, - 0xe3, 0xd4, 0x8f, 0x42, 0x0c, 0x08, 0x63, 0x78, 0x4c, 0x90, 0x01, 0xcb, 0x3e, 0xbe, 0x9a, 0x7a, - 0xd8, 0x91, 0x01, 0xea, 0x96, 0x5e, 0xa2, 0x3e, 0xac, 0xe9, 0xc3, 0x0d, 0xcf, 0x09, 0xc7, 0x0e, - 0xe6, 0xd8, 0xa8, 0x09, 0x9b, 0xda, 0xc1, 0x46, 0x37, 0x39, 0xb6, 0xf5, 0xd5, 0x40, 0xe9, 0xac, - 0x96, 0x16, 0x6a, 0x09, 0xfa, 0x08, 0x5a, 0xea, 0x8c, 0x29, 0x42, 0x5d, 0x22, 0xac, 0x77, 0xf5, - 0xe1, 0x33, 0x00, 0x4d, 0x25, 0x4b, 0xfc, 0xfb, 0xd0, 0x72, 0x54, 0x4a, 0x86, 0x9e, 0xcc, 0x09, - 0x33, 0xda, 0x7b, 0x65, 0xe1, 0xbf, 0xd9, 0x55, 0xcc, 0xc8, 0xa7, 0xcc, 0x6a, 0x3a, 0xb9, 0x35, - 0x33, 0xa7, 0xd0, 0xd4, 0x26, 0x37, 0x1f, 0xf9, 0x63, 0x68, 0x5e, 0x8b, 0xa7, 0x0e, 0x5c, 0x14, - 0x6e, 0x35, 0x1f, 0xce, 0x0c, 0xc1, 0x38, 0x26, 0x17, 0xd4, 0x26, 0x7d, 0x9b, 0xd3, 0x0b, 0x2c, - 0x6d, 0x08, 0xf3, 0xc5, 0x46, 0xfe, 0xd3, 0xb0, 0xdf, 0x97, 0xe1, 0xce, 0x31, 0x71, 0x42, 0x51, - 0x59, 0x5b, 0xa4, 0xd0, 0x59, 0xb4, 0xc4, 0x4f, 0x60, 0xd9, 0x21, 0x17, 0x43, 0x12, 0x52, 0x19, - 0xb0, 0x7e, 0xf4, 0xe0, 0xe7, 0x5f, 0x76, 0xef, 0xdf, 0x44, 0xe1, 0x88, 0x65, 0x3d, 0x7e, 0xe5, - 0x13, 0xd6, 0x15, 0x87, 0x7d, 0x74, 0xfa, 0xd8, 0xaa, 0x08, 0x94, 0x47, 0x21, 0x8d, 0xf0, 0xb0, - 0xef, 0x4b, 0xbc, 0xfa, 0x0b, 0xe1, 0xf5, 0x7d, 0x5f, 0xe2, 0x09, 0x94, 0x08, 0x6f, 0x2e, 0x05, - 0xdb, 0xff, 0x9a, 0x82, 0x9b, 0x92, 0x42, 0x8b, 0x51, 0xf0, 0x18, 0xd6, 0x02, 0x55, 0xc1, 0x21, - 0x27, 0xe7, 0xfe, 0x54, 0xe8, 0x8d, 0x5d, 0xb9, 0x85, 0xad, 0xeb, 0xd5, 0x51, 0x09, 0xb7, 0x5a, - 0xda, 0xe3, 0x44, 0x39, 0xa0, 0xbb, 0xd0, 0x70, 0x09, 0x71, 0x86, 0xba, 0x6e, 0xc6, 0x9e, 0x40, - 0xa8, 0x5a, 0xf5, 0x48, 0xa8, 0xbd, 0xcd, 0x3f, 0xca, 0xb0, 0x35, 0xcb, 0x9e, 0x2f, 0x43, 0xc2, - 0xf8, 0x6d, 0x0d, 0x67, 0x6b, 0xb8, 0xf8, 0x35, 0x32, 0x80, 0x75, 0x9c, 0x64, 0x34, 0x85, 0xd8, - 0x92, 0x10, 0xdb, 0xe9, 0x26, 0xd2, 0xb4, 0x27, 0x58, 0x08, 0xcf, 0xc8, 0xe6, 0xde, 0x4a, 0xbb, - 0xff, 0xec, 0x56, 0xfa, 0xfa, 0x65, 0xb8, 0x9b, 0x6d, 0xd8, 0xdb, 0xb2, 0xff, 0xff, 0xcb, 0x3e, - 0x28, 0xbe, 0x09, 0xf6, 0x92, 0xba, 0x17, 0x5c, 0xfe, 0xb3, 0x57, 0x82, 0x89, 0xa0, 0xf5, 0x2c, - 0x1c, 0x31, 0x3b, 0xa0, 0x23, 0xa2, 0xca, 0x6d, 0xb6, 0x61, 0x5d, 0xa4, 0x51, 0x72, 0x22, 0xa2, - 0x89, 0x16, 0xdf, 0x87, 0x8d, 0xbc, 0x58, 0xfd, 0x51, 0xee, 0x40, 0x55, 0xd5, 0x8c, 0x09, 0x7a, - 0x94, 0xc5, 0x74, 0xb0, 0x1c, 0x67, 0x9f, 0x99, 0x0f, 0xa0, 0x63, 0x91, 0x31, 0x65, 0x9c, 0x04, - 0x19, 0x57, 0x4d, 0xab, 0xad, 0xb4, 0xd8, 0xf1, 0x54, 0xa1, 0xaa, 0x66, 0x1e, 0xc2, 0xf6, 0xa9, - 0x1b, 0xbc, 0x80, 0x63, 0x13, 0x1a, 0xcf, 0x38, 0xe6, 0x61, 0xb2, 0xe7, 0x1f, 0xcb, 0xb0, 0xaa, - 0x25, 0x6a, 0xbb, 0x26, 0x54, 0x42, 0xf9, 0x63, 0x92, 0xbe, 0xb5, 0x03, 0xe8, 0x46, 0x43, 0x97, - 0x25, 0x92, 0xc1, 0x2c, 0xa5, 0x41, 0x3d, 0x68, 0xc4, 0x5f, 0xc3, 0xd0, 0xa5, 0x02, 0x49, 0x8e, - 0x36, 0x79, 0xd3, 0x7a, 0x6c, 0x70, 0x2a, 0xf5, 0xe8, 0x0d, 0x31, 0xed, 0xe8, 0x4b, 0xb5, 0x36, - 0x63, 0x9b, 0xe8, 0xd0, 0x3b, 0x50, 0x4b, 0x6b, 0xca, 0x14, 0x13, 0xb3, 0xa6, 0x59, 0x35, 0x7a, - 0x08, 0x19, 0x06, 0x30, 0xbd, 0x97, 0xcd, 0x19, 0xa7, 0xb5, 0x8c, 0x95, 0xda, 0xd0, 0x87, 0xb0, - 0x91, 0x75, 0xc5, 0xb6, 0x4d, 0x7c, 0xd1, 0xe1, 0x8a, 0x76, 0x59, 0xe7, 0x0c, 0x3b, 0x59, 0x5f, - 0x99, 0xa1, 0xf7, 0xa1, 0xe1, 0x24, 0x17, 0x43, 0x34, 0x09, 0xc4, 0x0c, 0x6b, 0x49, 0xbf, 0xa7, - 0x24, 0xb0, 0xa3, 0xe9, 0x6f, 0x2a, 0xbc, 0xf3, 0x66, 0xe8, 0x6d, 0x58, 0xb3, 0x3d, 0xd7, 0x25, - 0xb6, 0x00, 0x19, 0x06, 0x5e, 0x28, 0xea, 0xc7, 0x8c, 0x37, 0xe5, 0x5c, 0xd8, 0x4a, 0x14, 0x56, - 0x2c, 0x47, 0xf7, 0x00, 0xa5, 0xc6, 0x13, 0xec, 0x3a, 0xd3, 0xc8, 0xfa, 0x2d, 0x69, 0x9d, 0xc2, - 0x7c, 0xa6, 0x14, 0x07, 0xdf, 0x2c, 0x41, 0xe5, 0x48, 0x12, 0x5c, 0x8c, 0x2a, 0x2b, 0x7d, 0xc6, - 0x3c, 0x9b, 0x46, 0x7f, 0xb5, 0xb6, 0xa6, 0x7d, 0x6e, 0xde, 0xe8, 0x14, 0xfd, 0x17, 0xf7, 0x4b, - 0xef, 0x96, 0xd0, 0xe7, 0xb0, 0x92, 0xd0, 0x1e, 0x19, 0xda, 0xf2, 0x7a, 0x27, 0x74, 0x5e, 0x4b, - 0x3b, 0xaa, 0x60, 0xac, 0x11, 0x58, 0x5d, 0x58, 0x7e, 0x1a, 0x8e, 0xa6, 0x94, 0x4d, 0x50, 0x51, - 0xcc, 0x4e, 0x55, 0x26, 0xae, 0x6f, 0x9f, 0xed, 0x97, 0x44, 0x07, 0x57, 0x55, 0x6b, 0x12, 0xb4, - 0x5b, 0xdc, 0xb2, 0xf1, 0x0e, 0x6e, 0xec, 0xe9, 0x83, 0xef, 0x96, 0xa0, 0x11, 0xa7, 0x65, 0x80, - 0x5d, 0x11, 0x2b, 0x40, 0x8f, 0xa1, 0x9e, 0x6d, 0x54, 0xf4, 0xaa, 0xc6, 0x98, 0xd3, 0xd5, 0x9d, - 0xed, 0xf9, 0x4a, 0xd5, 0x2c, 0x9f, 0xc0, 0xfa, 0x9c, 0x06, 0x46, 0xa6, 0x76, 0x2a, 0xee, 0xee, - 0xf4, 0xc8, 0xe8, 0x53, 0x68, 0xcf, 0x6d, 0x67, 0xf4, 0x7a, 0x52, 0xb9, 0xbf, 0xe9, 0xf6, 0x0c, - 0xd0, 0x21, 0x54, 0xe2, 0x66, 0x4e, 0x6b, 0x9e, 0x6b, 0xf7, 0xce, 0xe6, 0x75, 0x71, 0x7c, 0x8c, - 0xa3, 0xd6, 0x0f, 0xbf, 0xed, 0x94, 0x7e, 0x12, 0xcf, 0xaf, 0xe2, 0xf9, 0xf6, 0xf7, 0x9d, 0x97, - 0x46, 0x15, 0x79, 0xef, 0xbe, 0xf7, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x06, 0x33, 0xf9, 0xe4, - 0x06, 0x0e, 0x00, 0x00, + // 1056 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0x71, 0x71, 0x9c, 0x67, 0x3b, 0x76, 0x26, 0x71, 0xb2, 0x35, 0x51, 0x12, 0xb6, 0x08, + 0x85, 0x3f, 0xb5, 0xa9, 0x51, 0xa9, 0x2a, 0x04, 0xc8, 0x69, 0xaa, 0x52, 0x24, 0x97, 0x6a, 0x9b, + 0x9c, 0xad, 0xf1, 0xee, 0xc4, 0x1e, 0xc5, 0xd9, 0x5d, 0x76, 0x66, 0x93, 0xe6, 0xc6, 0x87, 0xe0, + 0xc0, 0x81, 0x13, 0xdf, 0x04, 0x71, 0xe1, 0xc8, 0x99, 0x03, 0x42, 0x70, 0xe1, 0xc6, 0x57, 0x60, + 0x76, 0x76, 0x66, 0xff, 0xc4, 0x36, 0x31, 0x45, 0x48, 0x3d, 0xf4, 0x60, 0x79, 0xe7, 0xfd, 0xf9, + 0xbd, 0xb7, 0xef, 0xf7, 0xde, 0xec, 0x83, 0x7b, 0x23, 0xca, 0xc7, 0xe1, 0xb0, 0x6d, 0x7b, 0x67, + 0x9d, 0xa3, 0x31, 0x39, 0x1a, 0x53, 0x77, 0xc4, 0x9e, 0x10, 0x7e, 0xe1, 0x05, 0xa7, 0x1d, 0xce, + 0xdd, 0x0e, 0xf6, 0x69, 0x67, 0x18, 0x78, 0xa7, 0x24, 0x50, 0x7f, 0x6d, 0x3f, 0xf0, 0xb8, 0x87, + 0x4a, 0xf1, 0xa9, 0x75, 0x3b, 0x03, 0x30, 0xf2, 0x46, 0x5e, 0x47, 0xaa, 0x87, 0xe1, 0x89, 0x3c, + 0xc9, 0x83, 0x7c, 0x8a, 0xdd, 0x72, 0xe6, 0x73, 0xe3, 0x89, 0x9f, 0x32, 0xff, 0x78, 0x11, 0x73, + 0x69, 0x6a, 0x7b, 0x93, 0xe4, 0x41, 0x39, 0xdf, 0x5f, 0xc4, 0x79, 0x84, 0x39, 0xb9, 0xc0, 0x97, + 0xfa, 0x3f, 0x76, 0x35, 0xbf, 0x2f, 0xc0, 0xea, 0xa1, 0x77, 0xe1, 0x4e, 0xa8, 0x7b, 0xfa, 0xa5, + 0xcf, 0xa9, 0xe7, 0xa2, 0x1d, 0x00, 0xea, 0x10, 0x97, 0xd3, 0x13, 0x4a, 0x02, 0xa3, 0xb0, 0x57, + 0xd8, 0x5f, 0xb1, 0x32, 0x12, 0xb4, 0x01, 0xaf, 0x33, 0xdb, 0x0b, 0x88, 0xb1, 0x24, 0x54, 0x35, + 0x2b, 0x3e, 0xa0, 0x16, 0x94, 0x1d, 0x82, 0x1d, 0x81, 0x43, 0x8c, 0xa2, 0x50, 0x14, 0xad, 0xe4, + 0x8c, 0x3e, 0x85, 0x9a, 0xed, 0xb9, 0x27, 0x74, 0x14, 0x06, 0x38, 0x0a, 0x61, 0xdc, 0x10, 0x06, + 0x95, 0xae, 0xd1, 0xd6, 0xb9, 0x1c, 0x3d, 0x7f, 0x90, 0xd5, 0x5b, 0x79, 0x73, 0xf3, 0xaf, 0x02, + 0xd4, 0x8e, 0xfd, 0x28, 0xc5, 0x3e, 0x61, 0x0c, 0x8f, 0x08, 0x32, 0x60, 0xd9, 0xc7, 0x97, 0x13, + 0x0f, 0x3b, 0x32, 0xc1, 0xaa, 0xa5, 0x8f, 0xa8, 0x07, 0x6b, 0xba, 0x3a, 0x83, 0x33, 0xc2, 0xb1, + 0x83, 0x39, 0x36, 0x2a, 0x32, 0xde, 0x46, 0x3b, 0xa9, 0x9b, 0xf5, 0xbc, 0xaf, 0x74, 0x56, 0x43, + 0x0b, 0xb5, 0x44, 0xa4, 0xdb, 0x50, 0x89, 0xa5, 0x08, 0x55, 0x89, 0xb0, 0x9e, 0x64, 0x9c, 0x01, + 0xa8, 0x2b, 0x59, 0xe2, 0xdf, 0x83, 0x86, 0xa3, 0x4a, 0x3a, 0xf0, 0x64, 0x4d, 0x99, 0xd1, 0xdc, + 0x2b, 0x0a, 0xff, 0xcd, 0xb6, 0x6a, 0xad, 0x7c, 0xc9, 0xad, 0xba, 0x93, 0x3b, 0x33, 0x73, 0x02, + 0x75, 0x6d, 0x72, 0xfd, 0x2b, 0x7f, 0x06, 0xf5, 0x2b, 0xf1, 0xd4, 0x0b, 0xcf, 0x0b, 0xb7, 0x9a, + 0x0f, 0x67, 0x86, 0x60, 0x1c, 0x92, 0x73, 0x6a, 0x93, 0x9e, 0xcd, 0xe9, 0x79, 0x4c, 0x01, 0x61, + 0xbe, 0x48, 0xe4, 0x7f, 0x0d, 0xfb, 0x63, 0x11, 0x6e, 0x1e, 0x12, 0x27, 0x14, 0xcc, 0xda, 0xa2, + 0x84, 0xce, 0xa2, 0x14, 0x3f, 0x81, 0x65, 0x87, 0x9c, 0x0f, 0x48, 0x48, 0x65, 0xc0, 0xea, 0xc1, + 0xdd, 0x5f, 0x7e, 0xdd, 0xbd, 0x73, 0xdd, 0x0c, 0x44, 0x5d, 0xda, 0xe1, 0x97, 0x3e, 0x61, 0x6d, + 0xf1, 0xb2, 0x0f, 0x8f, 0x1f, 0x5b, 0x25, 0x81, 0xf2, 0x30, 0xa4, 0x11, 0x1e, 0xf6, 0x7d, 0x89, + 0x57, 0x7d, 0x21, 0xbc, 0x9e, 0xef, 0x4b, 0x3c, 0x81, 0x12, 0xe1, 0xcd, 0x6c, 0xc1, 0xe6, 0x7f, + 0x6e, 0xc1, 0x4d, 0xd9, 0x42, 0x8b, 0xb5, 0xe0, 0x21, 0xac, 0x05, 0x8a, 0xc1, 0x01, 0x27, 0x67, + 0xfe, 0x44, 0xe8, 0x8d, 0x5d, 0x99, 0xc2, 0xd6, 0x55, 0x76, 0x54, 0xc1, 0xad, 0x86, 0xf6, 0x38, + 0x52, 0x0e, 0xe8, 0x16, 0xd4, 0x5c, 0x42, 0x9c, 0x81, 0xe6, 0xcd, 0xd8, 0x13, 0x08, 0x65, 0xab, + 0x1a, 0x09, 0xb5, 0xb7, 0xf9, 0x67, 0x11, 0xb6, 0xa6, 0xbb, 0xe7, 0xab, 0x90, 0x30, 0xfe, 0x8a, + 0xc3, 0x69, 0x0e, 0x17, 0xbf, 0x46, 0xfa, 0xb0, 0x8e, 0x93, 0x8a, 0xa6, 0x10, 0x5b, 0x12, 0x62, + 0x3b, 0x4d, 0x22, 0x2d, 0x7b, 0x82, 0x85, 0xf0, 0x94, 0x6c, 0xe6, 0xad, 0xb4, 0xfb, 0xef, 0x6e, + 0xa5, 0xaf, 0x6f, 0xc0, 0xad, 0xec, 0xc0, 0xbe, 0xa2, 0xfd, 0xe5, 0xa7, 0xbd, 0x3f, 0xff, 0x26, + 0xd8, 0x4b, 0x78, 0x9f, 0x73, 0xf9, 0x4f, 0x5f, 0x09, 0x26, 0x82, 0xc6, 0xb3, 0x70, 0xc8, 0xec, + 0x80, 0x0e, 0x89, 0xa2, 0xdb, 0x6c, 0xc2, 0xba, 0x28, 0xa3, 0xec, 0x89, 0xa8, 0x4d, 0xb4, 0xf8, + 0x0e, 0x6c, 0xe4, 0xc5, 0xea, 0x8b, 0x72, 0x13, 0xca, 0x8a, 0x33, 0x26, 0xda, 0xa3, 0x28, 0xb6, + 0x8b, 0xe5, 0xb8, 0xfa, 0xcc, 0xbc, 0x0b, 0x2d, 0x8b, 0x8c, 0x28, 0xe3, 0x24, 0xc8, 0xb8, 0xea, + 0xb6, 0xda, 0x4a, 0xc9, 0x8e, 0xb7, 0x12, 0xc5, 0x9a, 0x79, 0x0f, 0xb6, 0x8f, 0xdd, 0xe0, 0x05, + 0x1c, 0xeb, 0x50, 0x7b, 0xc6, 0x31, 0x0f, 0x93, 0x9c, 0x7f, 0x28, 0x42, 0x29, 0x96, 0x20, 0x13, + 0x4a, 0xa1, 0xfc, 0x20, 0x49, 0x9f, 0x4a, 0x17, 0xda, 0xd1, 0xb6, 0x66, 0x89, 0x22, 0x30, 0x4b, + 0x69, 0x50, 0x07, 0x6a, 0xf1, 0xd3, 0x20, 0x74, 0xa9, 0x40, 0x90, 0x2b, 0x51, 0xde, 0xb4, 0x1a, + 0x1b, 0x1c, 0x4b, 0x3d, 0x7a, 0x5b, 0x6c, 0x49, 0xfa, 0x32, 0xad, 0x4c, 0xd9, 0x26, 0x3a, 0xf4, + 0x3e, 0x54, 0x52, 0x2e, 0x99, 0xea, 0xc0, 0xac, 0x69, 0x56, 0x8d, 0xee, 0x43, 0x86, 0x79, 0xa6, + 0x73, 0xd9, 0x9c, 0x72, 0x5a, 0xcb, 0x58, 0xa9, 0x84, 0x3e, 0x81, 0x8d, 0xac, 0x2b, 0xb6, 0x6d, + 0xe2, 0x8b, 0xc9, 0x56, 0xed, 0x96, 0x75, 0xce, 0x74, 0x25, 0xeb, 0x29, 0x33, 0xf4, 0x11, 0xd4, + 0x9c, 0xe4, 0x42, 0x88, 0x36, 0x80, 0xb8, 0xb3, 0x1a, 0xd2, 0xef, 0x29, 0x09, 0xec, 0x68, 0x6b, + 0x9c, 0x08, 0xef, 0xbc, 0x19, 0x7a, 0x0f, 0xd6, 0xc4, 0x8a, 0xe7, 0x12, 0x5b, 0x80, 0x0c, 0x02, + 0x2f, 0x14, 0xbc, 0x31, 0xe3, 0x1d, 0xb9, 0x4f, 0x36, 0x12, 0x85, 0x15, 0xcb, 0xd1, 0x6d, 0x40, + 0xa9, 0xf1, 0x18, 0xbb, 0xce, 0x24, 0xb2, 0x7e, 0x57, 0x5a, 0xa7, 0x30, 0x9f, 0x2b, 0x45, 0xf7, + 0x9b, 0x25, 0x28, 0x1d, 0xc8, 0xc6, 0x16, 0x2b, 0xca, 0x4a, 0x8f, 0x31, 0xcf, 0xa6, 0xd1, 0xd7, + 0xac, 0xa9, 0xdb, 0x3d, 0xb7, 0x67, 0xb4, 0xe6, 0x7d, 0x0f, 0xf7, 0x0b, 0x1f, 0x14, 0xd0, 0x17, + 0xb0, 0x92, 0xb4, 0x3b, 0x32, 0xb4, 0xe5, 0xd5, 0x09, 0x68, 0xbd, 0x99, 0x4e, 0xd2, 0x9c, 0x75, + 0x46, 0x60, 0xb5, 0x61, 0xf9, 0x69, 0x38, 0x9c, 0x50, 0x36, 0x46, 0xf3, 0x62, 0xb6, 0xca, 0xb2, + 0x70, 0x3d, 0xfb, 0x74, 0xbf, 0x20, 0x26, 0xb7, 0xac, 0x46, 0x92, 0xa0, 0xdd, 0xf9, 0xa3, 0x1a, + 0x67, 0x70, 0xed, 0x2c, 0x77, 0xbf, 0x5b, 0x82, 0x5a, 0x5c, 0x96, 0x3e, 0x76, 0x45, 0xac, 0x00, + 0x3d, 0x86, 0x6a, 0x76, 0x40, 0xd1, 0x1b, 0x1a, 0x63, 0xc6, 0x34, 0xb7, 0xb6, 0x67, 0x2b, 0xd5, + 0x4c, 0x3f, 0x80, 0xf5, 0x19, 0x83, 0x8b, 0x4c, 0xed, 0x34, 0x7f, 0xaa, 0xd3, 0x57, 0x46, 0x8f, + 0xa0, 0x39, 0x73, 0x8c, 0xd1, 0x5b, 0x09, 0x73, 0xff, 0x30, 0xe5, 0x19, 0xa0, 0x2e, 0xac, 0x3c, + 0x22, 0x5c, 0xcd, 0x71, 0x42, 0x7b, 0x6e, 0xd2, 0x5b, 0xab, 0x79, 0xf1, 0x41, 0xe3, 0xa7, 0xdf, + 0x77, 0x0a, 0x3f, 0x8b, 0xdf, 0x6f, 0xe2, 0xf7, 0xed, 0x1f, 0x3b, 0xaf, 0x0d, 0x4b, 0xf2, 0xaa, + 0xfd, 0xf0, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x69, 0xae, 0xa8, 0x3a, 0x0e, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 5996a75a2..00b4f7086 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -12,6 +12,7 @@ message DownlinkOption { string identifier = 1; uint32 score = 2; // lower is better, 0 is best int64 deadline = 3; // deadline time at server represented as the number of nanoseconds elapsed since January 1, 1970 UTC + gateway.TxConfiguration configuration = 4; } // received from the Router @@ -107,8 +108,8 @@ message UnregisterApplicationRequest { // message StatusRequest is used to request the status of this Broker message StatusRequest {} -// message StatusResponse is the response to the StatusRequest -message StatusResponse { +// message Status is the response to the StatusRequest +message Status { // Uplink api.Rates uplink = 1; api.Rates uplink_unique = 2; @@ -141,5 +142,5 @@ service BrokerManager { rpc UnregisterApplication(UnregisterApplicationRequest) returns (api.Ack); // Network operator requests Broker status - rpc Status(StatusRequest) returns (StatusResponse); + rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index ea51aad90..ccbc04fba 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -12,7 +12,7 @@ GPSMetadata RxMetadata TxConfiguration - StatusMessage + Status */ package gateway @@ -233,9 +233,9 @@ func _TxConfiguration_OneofSizer(msg proto.Message) (n int) { return n } -// message StatusMessage represents a status update from a Gateway. +// message Status represents a status update from a Gateway. // See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat -type StatusMessage struct { +type Status struct { Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` Ip []string `protobuf:"bytes,11,rep,name=ip" json:"ip,omitempty"` @@ -250,12 +250,12 @@ type StatusMessage struct { TxOk uint32 `protobuf:"varint,44,opt,name=tx_ok,json=txOk,proto3" json:"tx_ok,omitempty"` } -func (m *StatusMessage) Reset() { *m = StatusMessage{} } -func (m *StatusMessage) String() string { return proto.CompactTextString(m) } -func (*StatusMessage) ProtoMessage() {} -func (*StatusMessage) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3} } +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3} } -func (m *StatusMessage) GetGps() *GPSMetadata { +func (m *Status) GetGps() *GPSMetadata { if m != nil { return m.Gps } @@ -266,7 +266,7 @@ func init() { proto.RegisterType((*GPSMetadata)(nil), "gateway.GPSMetadata") proto.RegisterType((*RxMetadata)(nil), "gateway.RxMetadata") proto.RegisterType((*TxConfiguration)(nil), "gateway.TxConfiguration") - proto.RegisterType((*StatusMessage)(nil), "gateway.StatusMessage") + proto.RegisterType((*Status)(nil), "gateway.Status") } func (m *GPSMetadata) Marshal() (data []byte, err error) { size := m.Size() @@ -409,7 +409,7 @@ func (m *TxConfiguration_Semtech) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *StatusMessage) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -419,7 +419,7 @@ func (m *StatusMessage) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *StatusMessage) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -613,7 +613,7 @@ func (m *TxConfiguration_Semtech) Size() (n int) { } return n } -func (m *StatusMessage) Size() (n int) { +func (m *Status) Size() (n int) { var l int _ = l if m.Timestamp != 0 { @@ -1040,7 +1040,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } return nil } -func (m *StatusMessage) Unmarshal(data []byte) error { +func (m *Status) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1063,10 +1063,10 @@ func (m *StatusMessage) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: StatusMessage: wiretype end group for non-group") + return fmt.Errorf("proto: Status: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: StatusMessage: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1478,36 +1478,36 @@ var ( ) var fileDescriptorGateway = []byte{ - // 486 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x92, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x1b, 0x3b, 0xa1, 0x64, 0xd2, 0x94, 0x6a, 0x5b, 0xa4, 0x55, 0x85, 0x4a, 0x15, 0x24, - 0x54, 0xfe, 0x28, 0x96, 0x80, 0x0b, 0x47, 0x8a, 0x10, 0x70, 0x28, 0x45, 0xdb, 0xdc, 0xab, 0xad, - 0xb3, 0x71, 0x56, 0x89, 0x77, 0xcd, 0xee, 0x58, 0x49, 0x5f, 0x80, 0x13, 0x0f, 0xc0, 0xf3, 0x70, - 0xe2, 0xc8, 0x23, 0x20, 0x78, 0x11, 0x76, 0x37, 0xb6, 0x13, 0x55, 0x42, 0x88, 0x83, 0xe5, 0x99, - 0xdf, 0x7e, 0x9e, 0xf9, 0xc6, 0xb3, 0xf0, 0x32, 0x93, 0x38, 0x2d, 0xaf, 0x86, 0xa9, 0xce, 0x93, - 0xd1, 0x54, 0x8c, 0xa6, 0x52, 0x65, 0xf6, 0x83, 0xc0, 0x85, 0x36, 0xb3, 0x04, 0x51, 0x25, 0xbc, - 0x90, 0x49, 0xc6, 0x51, 0x2c, 0xf8, 0x75, 0xfd, 0x1e, 0x16, 0x46, 0xa3, 0x26, 0xdb, 0x55, 0x7a, - 0xf8, 0xea, 0x7f, 0x6a, 0x58, 0x91, 0xa3, 0x48, 0xa7, 0xf5, 0x7b, 0x55, 0x6b, 0xb0, 0x80, 0xde, - 0xdb, 0x8f, 0x17, 0x67, 0x02, 0xf9, 0x98, 0x23, 0x27, 0x04, 0xda, 0x28, 0x73, 0x41, 0x5b, 0xc7, - 0xad, 0x93, 0x98, 0x85, 0x98, 0x1c, 0xc2, 0xed, 0x39, 0x47, 0x89, 0xe5, 0x58, 0xd0, 0xc8, 0xf1, - 0x88, 0x35, 0x39, 0xb9, 0x07, 0xdd, 0xb9, 0x56, 0xd9, 0xea, 0x30, 0x0e, 0x87, 0x6b, 0xe0, 0xbf, - 0xe4, 0xf3, 0xea, 0xcb, 0xb6, 0x3b, 0xec, 0xb0, 0x26, 0x1f, 0x7c, 0x69, 0x01, 0xb0, 0x65, 0xd3, - 0xd8, 0x15, 0x9a, 0x18, 0xf1, 0xa9, 0x14, 0x2a, 0xbd, 0x0e, 0xdd, 0xdb, 0x6c, 0x0d, 0xbc, 0x2d, - 0x63, 0xad, 0xac, 0xda, 0x87, 0x98, 0xec, 0x41, 0x6c, 0x95, 0xa9, 0x9a, 0xfa, 0x90, 0x24, 0xb0, - 0x5d, 0x0d, 0x47, 0x7b, 0x8e, 0xf6, 0x9e, 0xed, 0x0f, 0xeb, 0x61, 0xd7, 0x9d, 0xde, 0x6d, 0xb1, - 0x5a, 0x75, 0xda, 0x85, 0xfa, 0x57, 0x0e, 0x3e, 0xb7, 0xe0, 0xce, 0x68, 0xf9, 0x5a, 0xab, 0x89, - 0xcc, 0x4a, 0xe3, 0xc6, 0xd3, 0xea, 0x1f, 0x9e, 0x0e, 0xa0, 0x53, 0xe8, 0x85, 0x30, 0xc1, 0x54, - 0x87, 0xad, 0x12, 0xf2, 0xe2, 0xa6, 0x07, 0xda, 0x78, 0xb8, 0x51, 0xfe, 0x2f, 0x46, 0xbe, 0x45, - 0xd0, 0xbf, 0x40, 0x8e, 0xa5, 0x3d, 0x13, 0xd6, 0xf2, 0x2c, 0xfc, 0x63, 0xbf, 0x07, 0x8b, 0x3c, - 0x2f, 0x82, 0x8d, 0x3e, 0x5b, 0x83, 0x66, 0x63, 0xd1, 0xc6, 0xc6, 0x76, 0x21, 0x92, 0x85, 0xeb, - 0x1f, 0x9f, 0x74, 0x99, 0x8b, 0xfc, 0x1e, 0x0a, 0xb7, 0xb2, 0x89, 0x36, 0x39, 0xdd, 0x71, 0xba, - 0x2e, 0x6b, 0x72, 0xf2, 0x00, 0xfa, 0xa9, 0x56, 0xc8, 0x53, 0xbc, 0x14, 0x39, 0x97, 0x73, 0xda, - 0x0f, 0x82, 0x9d, 0x0a, 0xbe, 0xf1, 0x8c, 0x1c, 0x43, 0x6f, 0x2c, 0x6c, 0x6a, 0x64, 0xe1, 0x9d, - 0xd3, 0xdd, 0x20, 0xd9, 0x44, 0xe4, 0x21, 0xc4, 0x59, 0x61, 0xe9, 0xdd, 0x30, 0xf3, 0xc1, 0xb0, - 0xbe, 0xb0, 0x1b, 0x77, 0x8b, 0x79, 0x81, 0xdf, 0x9a, 0x41, 0xa4, 0xf7, 0xc3, 0x18, 0x3e, 0x24, - 0xfb, 0xd0, 0x31, 0xcb, 0x4b, 0xa9, 0xe8, 0xa3, 0xc0, 0xda, 0x66, 0xf9, 0x5e, 0x55, 0x50, 0xcf, - 0xe8, 0xe3, 0x1a, 0x9e, 0xcf, 0x3c, 0xc4, 0xa0, 0x7c, 0xb2, 0x82, 0x58, 0x29, 0x31, 0x28, 0x9f, - 0xd6, 0xf0, 0x7c, 0x76, 0xba, 0xf7, 0xfd, 0xd7, 0x51, 0xeb, 0x87, 0x7b, 0x7e, 0xba, 0xe7, 0xeb, - 0xef, 0xa3, 0xad, 0xab, 0x5b, 0xe1, 0xba, 0x3f, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0x8a, 0xe6, - 0xe3, 0x4e, 0x77, 0x03, 0x00, 0x00, + // 482 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x92, 0xdf, 0x6e, 0xd3, 0x30, + 0x14, 0xc6, 0xd7, 0xa4, 0xdd, 0xe8, 0xe9, 0x3a, 0x26, 0x6f, 0x48, 0xd6, 0x84, 0xc6, 0x54, 0x24, + 0x34, 0xfe, 0xa8, 0x91, 0x80, 0x1b, 0x2e, 0x19, 0x42, 0xc0, 0x05, 0x0c, 0x79, 0xbd, 0x9f, 0xbc, + 0xd4, 0x4d, 0xad, 0x36, 0x76, 0xb0, 0x4f, 0xd4, 0xee, 0x05, 0xb8, 0xe2, 0x01, 0x78, 0x17, 0x5e, + 0x80, 0x4b, 0x1e, 0x01, 0xc1, 0x8b, 0x60, 0xbb, 0x49, 0x5a, 0x4d, 0x42, 0x88, 0x8b, 0x28, 0xe7, + 0xfc, 0xfc, 0xe5, 0x7c, 0xe7, 0xe4, 0x18, 0x5e, 0x64, 0x12, 0xa7, 0xe5, 0xd5, 0x30, 0xd5, 0x79, + 0x32, 0x9a, 0x8a, 0xd1, 0x54, 0xaa, 0xcc, 0x7e, 0x10, 0xb8, 0xd0, 0x66, 0x96, 0x20, 0xaa, 0x84, + 0x17, 0x32, 0xc9, 0x38, 0x8a, 0x05, 0xbf, 0xae, 0xdf, 0xc3, 0xc2, 0x68, 0xd4, 0x64, 0xa7, 0x4a, + 0x8f, 0x5e, 0xfe, 0x4f, 0x0d, 0x2b, 0x72, 0x14, 0xe9, 0xb4, 0x7e, 0xaf, 0x6a, 0x0d, 0x16, 0xd0, + 0x7b, 0xf3, 0xf1, 0xe2, 0xbd, 0x40, 0x3e, 0xe6, 0xc8, 0x09, 0x81, 0x36, 0xca, 0x5c, 0xd0, 0xd6, + 0x49, 0xeb, 0x34, 0x66, 0x21, 0x26, 0x47, 0x70, 0x6b, 0xce, 0x51, 0x62, 0x39, 0x16, 0x34, 0x72, + 0x3c, 0x62, 0x4d, 0x4e, 0xee, 0x42, 0x77, 0xae, 0x55, 0xb6, 0x3a, 0x8c, 0xc3, 0xe1, 0x1a, 0xf8, + 0x2f, 0xf9, 0xbc, 0xfa, 0xb2, 0xed, 0x0e, 0x3b, 0xac, 0xc9, 0x07, 0x5f, 0x5a, 0x00, 0x6c, 0xd9, + 0x18, 0xbb, 0x42, 0x13, 0x23, 0x3e, 0x95, 0x42, 0xa5, 0xd7, 0xc1, 0xbd, 0xcd, 0xd6, 0xc0, 0xb7, + 0x65, 0xac, 0x95, 0x95, 0x7d, 0x88, 0xc9, 0x3e, 0xc4, 0x56, 0x99, 0xca, 0xd4, 0x87, 0x24, 0x81, + 0x9d, 0x6a, 0x38, 0xda, 0x73, 0xb4, 0xf7, 0xf4, 0x60, 0x58, 0x0f, 0xbb, 0x76, 0x7a, 0xbb, 0xc5, + 0x6a, 0xd5, 0x59, 0x17, 0xea, 0x5f, 0x39, 0xf8, 0xdc, 0x82, 0xdb, 0xa3, 0xe5, 0x2b, 0xad, 0x26, + 0x32, 0x2b, 0x8d, 0x1b, 0x4f, 0xab, 0x7f, 0xf4, 0x74, 0x08, 0x9d, 0x42, 0x2f, 0x84, 0x09, 0x4d, + 0x75, 0xd8, 0x2a, 0x21, 0xcf, 0x6f, 0xf6, 0x40, 0x9b, 0x1e, 0x6e, 0x94, 0xff, 0x4b, 0x23, 0xdf, + 0x22, 0xd8, 0xbe, 0x40, 0x8e, 0xa5, 0xf5, 0xfe, 0x7e, 0x01, 0x16, 0x79, 0x5e, 0x04, 0xff, 0x3e, + 0x5b, 0x83, 0x66, 0x55, 0xd1, 0xc6, 0xaa, 0xf6, 0x20, 0x92, 0x85, 0x33, 0x8e, 0x4f, 0xbb, 0xcc, + 0x45, 0x7e, 0x01, 0x85, 0xdb, 0xd5, 0x44, 0x9b, 0x9c, 0xee, 0x3a, 0x5d, 0x97, 0x35, 0x39, 0xb9, + 0x0f, 0xfd, 0x54, 0x2b, 0xe4, 0x29, 0x5e, 0x8a, 0x9c, 0xcb, 0x39, 0xed, 0x07, 0xc1, 0x6e, 0x05, + 0x5f, 0x7b, 0x46, 0x4e, 0xa0, 0x37, 0x16, 0x36, 0x35, 0xb2, 0xf0, 0x2d, 0xd3, 0xbd, 0x20, 0xd9, + 0x44, 0xe4, 0x01, 0xc4, 0x59, 0x61, 0xe9, 0x9d, 0x30, 0xec, 0xe1, 0xb0, 0xbe, 0xa9, 0x1b, 0x97, + 0x8a, 0x79, 0x81, 0x5f, 0x97, 0x41, 0xa4, 0xf7, 0xc2, 0x18, 0x3e, 0x24, 0x07, 0xd0, 0x31, 0xcb, + 0x4b, 0xa9, 0xe8, 0xc3, 0xc0, 0xda, 0x66, 0xf9, 0x4e, 0x55, 0x50, 0xcf, 0xe8, 0xa3, 0x1a, 0x9e, + 0xcf, 0x3c, 0xc4, 0xa0, 0x7c, 0xbc, 0x82, 0x58, 0x29, 0x31, 0x28, 0x9f, 0xd4, 0xf0, 0x7c, 0x76, + 0xb6, 0xff, 0xfd, 0xd7, 0x71, 0xeb, 0x87, 0x7b, 0x7e, 0xba, 0xe7, 0xeb, 0xef, 0xe3, 0xad, 0xab, + 0xed, 0x70, 0xcf, 0x9f, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x35, 0x0c, 0x13, 0x0c, 0x70, 0x03, + 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 0926e0650..2271b114f 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -28,9 +28,9 @@ message TxConfiguration { } } -// message StatusMessage represents a status update from a Gateway. +// message Status represents a status update from a Gateway. // See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat -message StatusMessage { +message Status { uint32 timestamp = 1; int64 time = 2; diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go index 6372e1441..7ece5f2a1 100644 --- a/api/gateway/status_message.go +++ b/api/gateway/status_message.go @@ -27,9 +27,9 @@ var StatusMessageProperties = []string{ "tx_ok", } -// ToStringStringMap converts the given properties of StatusMessage to a +// ToStringStringMap converts the given properties of Status to a // map[string]string for storage in Redis. -func (status *StatusMessage) ToStringStringMap(properties ...string) (map[string]string, error) { +func (status *Status) ToStringStringMap(properties ...string) (map[string]string, error) { output := make(map[string]string) for _, p := range properties { property, err := status.formatProperty(p) @@ -43,15 +43,15 @@ func (status *StatusMessage) ToStringStringMap(properties ...string) (map[string return output, nil } -// FromStringStringMap imports known values from the input to a StatusMessage. -func (status *StatusMessage) FromStringStringMap(input map[string]string) error { +// FromStringStringMap imports known values from the input to a Status. +func (status *Status) FromStringStringMap(input map[string]string) error { for k, v := range input { status.parseProperty(k, v) } return nil } -func (status *StatusMessage) formatProperty(property string) (formatted string, err error) { +func (status *Status) formatProperty(property string) (formatted string, err error) { switch property { case "timestamp": formatted = storage.FormatUint32(status.Timestamp) @@ -92,12 +92,12 @@ func (status *StatusMessage) formatProperty(property string) (formatted string, case "tx_ok": formatted = storage.FormatUint32(status.TxOk) default: - err = fmt.Errorf("Property %s does not exist in StatusMessage", property) + err = fmt.Errorf("Property %s does not exist in Status", property) } return } -func (status *StatusMessage) parseProperty(property string, value string) error { +func (status *Status) parseProperty(property string, value string) error { if value == "" { return nil } diff --git a/api/gateway/status_message_test.go b/api/gateway/status_message_test.go index 9b2fd5b5c..3cad8169e 100644 --- a/api/gateway/status_message_test.go +++ b/api/gateway/status_message_test.go @@ -6,9 +6,9 @@ import ( . "github.com/smartystreets/assertions" ) -func getStatusMessage() (status *StatusMessage, smap map[string]string) { +func getStatusMessage() (status *Status, smap map[string]string) { t := int64(1462201853428843766) - return &StatusMessage{ + return &Status{ Timestamp: 12345, Time: t, Ip: []string{"169.50.131.24", "2a03:8180:1401:14f::2"}, @@ -55,7 +55,7 @@ func TestToStringMap(t *testing.T) { func TestFromStringMap(t *testing.T) { a := New(t) - status := &StatusMessage{} + status := &Status{} expected, smap := getStatusMessage() err := status.FromStringStringMap(smap) a.So(err, ShouldBeNil) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 19a95ee11..abdce47fe 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -10,7 +10,7 @@ It has these top-level messages: StatusRequest - StatusResponse + Status */ package handler @@ -45,18 +45,18 @@ func (m *StatusRequest) String() string { return proto.CompactTextStr func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } -// message StatusResponse is the response to the StatusRequest -type StatusResponse struct { +// message Status is the response to the StatusRequest +type Status struct { } -func (m *StatusResponse) Reset() { *m = StatusResponse{} } -func (m *StatusResponse) String() string { return proto.CompactTextString(m) } -func (*StatusResponse) ProtoMessage() {} -func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } func init() { proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") - proto.RegisterType((*StatusResponse)(nil), "handler.StatusResponse") + proto.RegisterType((*Status)(nil), "handler.Status") } // Reference imports to suppress errors if they are not otherwise used. @@ -133,7 +133,7 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ // Client API for HandlerManager service type HandlerManagerClient interface { - Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } type handlerManagerClient struct { @@ -144,9 +144,9 @@ func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { return &handlerManagerClient{cc} } -func (c *handlerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { - out := new(StatusResponse) - err := grpc.Invoke(ctx, "/handler.HandlerManager/Status", in, out, c.cc, opts...) +func (c *handlerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/handler.HandlerManager/GetStatus", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -156,27 +156,27 @@ func (c *handlerManagerClient) Status(ctx context.Context, in *StatusRequest, op // Server API for HandlerManager service type HandlerManagerServer interface { - Status(context.Context, *StatusRequest) (*StatusResponse, error) + GetStatus(context.Context, *StatusRequest) (*Status, error) } func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { s.RegisterService(&_HandlerManager_serviceDesc, srv) } -func _HandlerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _HandlerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(HandlerManagerServer).Status(ctx, in) + return srv.(HandlerManagerServer).GetStatus(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/handler.HandlerManager/Status", + FullMethod: "/handler.HandlerManager/GetStatus", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).Status(ctx, req.(*StatusRequest)) + return srv.(HandlerManagerServer).GetStatus(ctx, req.(*StatusRequest)) } return interceptor(ctx, in, info, handler) } @@ -186,8 +186,8 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ HandlerType: (*HandlerManagerServer)(nil), Methods: []grpc.MethodDesc{ { - MethodName: "Status", - Handler: _HandlerManager_Status_Handler, + MethodName: "GetStatus", + Handler: _HandlerManager_GetStatus_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -211,7 +211,7 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusResponse) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -221,7 +221,7 @@ func (m *StatusResponse) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *StatusResponse) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -262,7 +262,7 @@ func (m *StatusRequest) Size() (n int) { return n } -func (m *StatusResponse) Size() (n int) { +func (m *Status) Size() (n int) { var l int _ = l return n @@ -331,7 +331,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *StatusResponse) Unmarshal(data []byte) error { +func (m *Status) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -354,10 +354,10 @@ func (m *StatusResponse) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + return fmt.Errorf("proto: Status: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { default: @@ -494,13 +494,13 @@ var fileDescriptorHandler = []byte{ 0xcf, 0x48, 0xcc, 0x4b, 0xc9, 0x49, 0x2d, 0x82, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, 0xec, 0x50, 0xae, 0x94, 0x2e, 0x31, 0x66, 0x00, 0x31, 0x44, 0x9f, 0x94, 0x39, 0x31, 0xca, 0x93, 0x8a, 0xf2, 0xb3, 0x81, 0x36, 0x42, 0x28, 0x88, 0x46, 0x25, 0x7e, 0x2e, 0xde, 0xe0, 0x92, 0xc4, - 0x92, 0xd2, 0xe2, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x01, 0x2e, 0x3e, 0x98, 0x40, - 0x71, 0x41, 0x7e, 0x5e, 0x71, 0xaa, 0x51, 0x0a, 0x17, 0xbb, 0x07, 0xc4, 0x55, 0x42, 0x91, 0x5c, - 0x1c, 0x8e, 0xc9, 0x25, 0x99, 0x65, 0x89, 0x25, 0xa9, 0x42, 0xda, 0x7a, 0x50, 0x83, 0x5c, 0x52, - 0x53, 0x4a, 0x0b, 0x72, 0x32, 0x93, 0x81, 0x82, 0x29, 0x2e, 0xa9, 0x65, 0x99, 0xc9, 0xa9, 0x50, - 0x35, 0x99, 0xf9, 0x79, 0x50, 0x53, 0xa5, 0x14, 0x10, 0x8a, 0xd1, 0x15, 0x40, 0x6d, 0xf1, 0xe6, - 0xe2, 0x83, 0xda, 0xe2, 0x9b, 0x98, 0x97, 0x98, 0x0e, 0xb4, 0xcc, 0x92, 0x8b, 0x0d, 0xe2, 0x12, - 0x21, 0x31, 0x3d, 0x58, 0x28, 0xa1, 0xb8, 0x55, 0x4a, 0x1c, 0x43, 0x1c, 0x62, 0x98, 0x93, 0xc0, - 0x89, 0x47, 0x72, 0x8c, 0x17, 0x80, 0xf8, 0x01, 0x10, 0xcf, 0x78, 0x2c, 0xc7, 0x90, 0xc4, 0x06, - 0xf6, 0xae, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xdc, 0xb9, 0xd2, 0x6f, 0x9c, 0x01, 0x00, 0x00, + 0x92, 0xd2, 0xe2, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x0e, 0x2e, 0x36, 0x88, 0x80, + 0x51, 0x0a, 0x17, 0xbb, 0x07, 0xc4, 0x35, 0x42, 0x91, 0x5c, 0x1c, 0x8e, 0xc9, 0x25, 0x99, 0x65, + 0x89, 0x25, 0xa9, 0x42, 0xda, 0x7a, 0x50, 0x03, 0x5c, 0x52, 0x53, 0x4a, 0x0b, 0x72, 0x32, 0x93, + 0x81, 0x82, 0x29, 0x2e, 0xa9, 0x65, 0x99, 0xc9, 0xa9, 0x50, 0x35, 0x99, 0xf9, 0x79, 0x50, 0xd3, + 0xa4, 0x14, 0x10, 0x8a, 0xd1, 0x15, 0x14, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x1a, 0xb9, 0x71, 0xf1, + 0x41, 0x6d, 0xf1, 0x4d, 0xcc, 0x4b, 0x4c, 0x07, 0x5a, 0x66, 0xc2, 0xc5, 0xe9, 0x9e, 0x5a, 0x02, + 0x71, 0x84, 0x90, 0x98, 0x1e, 0x2c, 0x80, 0x50, 0x9c, 0x29, 0xc5, 0x8f, 0x26, 0xee, 0x24, 0x70, + 0xe2, 0x91, 0x1c, 0xe3, 0x05, 0x20, 0x7e, 0x00, 0xc4, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, + 0x7d, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x70, 0x1c, 0x54, 0x57, 0x8f, 0x01, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index f1e2d3d43..99574c7e3 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -13,11 +13,11 @@ service Handler { // message StatusRequest is used to request the status of this Handler message StatusRequest {} -// message StatusResponse is the response to the StatusRequest -message StatusResponse {} +// message Status is the response to the StatusRequest +message Status {} // The HandlerManager service provides configuration and monitoring // functionality service HandlerManager { - rpc Status(StatusRequest) returns (StatusResponse); + rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index a2e9e3828..57e5793a7 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -14,7 +14,7 @@ UplinkResponse RegisterDeviceRequest StatusRequest - StatusResponse + Status */ package networkserver @@ -112,18 +112,18 @@ func (m *StatusRequest) String() string { return proto.CompactTextStr func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } -// message StatusResponse is the response to the StatusRequest -type StatusResponse struct { +// message Status is the response to the StatusRequest +type Status struct { // GetDevices histogram percentiles DevicesPerAddress *api.Percentiles `protobuf:"bytes,1,opt,name=devices_per_address,json=devicesPerAddress" json:"devices_per_address,omitempty"` } -func (m *StatusResponse) Reset() { *m = StatusResponse{} } -func (m *StatusResponse) String() string { return proto.CompactTextString(m) } -func (*StatusResponse) ProtoMessage() {} -func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{5} } +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{5} } -func (m *StatusResponse) GetDevicesPerAddress() *api.Percentiles { +func (m *Status) GetDevicesPerAddress() *api.Percentiles { if m != nil { return m.DevicesPerAddress } @@ -137,7 +137,7 @@ func init() { proto.RegisterType((*UplinkResponse)(nil), "networkserver.UplinkResponse") proto.RegisterType((*RegisterDeviceRequest)(nil), "networkserver.RegisterDeviceRequest") proto.RegisterType((*StatusRequest)(nil), "networkserver.StatusRequest") - proto.RegisterType((*StatusResponse)(nil), "networkserver.StatusResponse") + proto.RegisterType((*Status)(nil), "networkserver.Status") } // Reference imports to suppress errors if they are not otherwise used. @@ -322,7 +322,7 @@ var _NetworkServer_serviceDesc = grpc.ServiceDesc{ type NetworkServerManagerClient interface { RegisterDevice(ctx context.Context, in *RegisterDeviceRequest, opts ...grpc.CallOption) (*api.Ack, error) - Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } type networkServerManagerClient struct { @@ -342,9 +342,9 @@ func (c *networkServerManagerClient) RegisterDevice(ctx context.Context, in *Reg return out, nil } -func (c *networkServerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { - out := new(StatusResponse) - err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/Status", in, out, c.cc, opts...) +func (c *networkServerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/GetStatus", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -355,7 +355,7 @@ func (c *networkServerManagerClient) Status(ctx context.Context, in *StatusReque type NetworkServerManagerServer interface { RegisterDevice(context.Context, *RegisterDeviceRequest) (*api.Ack, error) - Status(context.Context, *StatusRequest) (*StatusResponse, error) + GetStatus(context.Context, *StatusRequest) (*Status, error) } func RegisterNetworkServerManagerServer(s *grpc.Server, srv NetworkServerManagerServer) { @@ -380,20 +380,20 @@ func _NetworkServerManager_RegisterDevice_Handler(srv interface{}, ctx context.C return interceptor(ctx, in, info, handler) } -func _NetworkServerManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _NetworkServerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(NetworkServerManagerServer).Status(ctx, in) + return srv.(NetworkServerManagerServer).GetStatus(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/networkserver.NetworkServerManager/Status", + FullMethod: "/networkserver.NetworkServerManager/GetStatus", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServerManagerServer).Status(ctx, req.(*StatusRequest)) + return srv.(NetworkServerManagerServer).GetStatus(ctx, req.(*StatusRequest)) } return interceptor(ctx, in, info, handler) } @@ -407,8 +407,8 @@ var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ Handler: _NetworkServerManager_RegisterDevice_Handler, }, { - MethodName: "Status", - Handler: _NetworkServerManager_Status_Handler, + MethodName: "GetStatus", + Handler: _NetworkServerManager_GetStatus_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -589,7 +589,7 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusResponse) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -599,7 +599,7 @@ func (m *StatusResponse) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *StatusResponse) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -708,7 +708,7 @@ func (m *StatusRequest) Size() (n int) { return n } -func (m *StatusResponse) Size() (n int) { +func (m *Status) Size() (n int) { var l int _ = l if m.DevicesPerAddress != nil { @@ -1229,7 +1229,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *StatusResponse) Unmarshal(data []byte) error { +func (m *Status) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1252,10 +1252,10 @@ func (m *StatusResponse) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + return fmt.Errorf("proto: Status: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1418,43 +1418,43 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 605 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0xfd, 0x92, 0x7e, 0x24, 0x61, 0xda, 0xa4, 0x65, 0x0a, 0x6a, 0x65, 0x41, 0x29, 0x06, 0xa4, - 0x6e, 0xb0, 0x45, 0x50, 0x61, 0x83, 0x04, 0x29, 0x8d, 0x10, 0x54, 0xad, 0xc0, 0x69, 0xd7, 0x96, - 0x63, 0xdf, 0x38, 0x96, 0xc3, 0xd8, 0xcc, 0x8c, 0x13, 0xf2, 0x26, 0xec, 0x78, 0x0b, 0x9e, 0x81, - 0x25, 0x6c, 0x59, 0x20, 0x04, 0xaf, 0xc1, 0x82, 0xf1, 0xcc, 0x38, 0xe0, 0x40, 0x28, 0x74, 0x61, - 0x65, 0xe6, 0xce, 0xb9, 0xe7, 0xfe, 0x9c, 0xa3, 0xa0, 0x6e, 0x18, 0xf1, 0x61, 0xd6, 0xb7, 0xfc, - 0xe4, 0x85, 0x7d, 0x3c, 0x84, 0xe3, 0x61, 0x44, 0x42, 0x76, 0x04, 0x7c, 0x92, 0xd0, 0xd8, 0xe6, - 0x9c, 0xd8, 0x5e, 0x1a, 0xd9, 0x44, 0xdd, 0x19, 0xd0, 0x31, 0xd0, 0xf2, 0xcd, 0x4a, 0x69, 0xc2, - 0x13, 0xdc, 0x2c, 0x05, 0x8d, 0x5b, 0x3f, 0xb1, 0x86, 0x49, 0x98, 0xd8, 0x12, 0xd5, 0xcf, 0x06, - 0xf2, 0x26, 0x2f, 0xf2, 0xa4, 0xb2, 0x4b, 0xf0, 0x85, 0x4d, 0x88, 0x4f, 0xc3, 0xef, 0xfd, 0x0d, - 0xbc, 0x4f, 0x93, 0x58, 0x34, 0xab, 0x7e, 0x54, 0xa2, 0xf9, 0x0a, 0xb5, 0xf6, 0x61, 0x1c, 0xf9, - 0xc0, 0x1c, 0x78, 0x99, 0x01, 0xe3, 0xf8, 0x39, 0x6a, 0x04, 0x30, 0x76, 0xbd, 0x20, 0xa0, 0x9b, - 0x95, 0xed, 0xca, 0xce, 0xca, 0xde, 0xdd, 0x8f, 0x9f, 0xae, 0xb6, 0x4f, 0x2b, 0xe0, 0x27, 0x14, - 0x6c, 0x3e, 0x4d, 0x81, 0x59, 0x82, 0xb0, 0x23, 0xb2, 0x9d, 0x7a, 0xa0, 0x0e, 0x78, 0x1d, 0x9d, - 0x1b, 0xb8, 0x3e, 0xe1, 0x9b, 0x55, 0xc1, 0xd7, 0x74, 0xfe, 0x1f, 0x3c, 0x22, 0xdc, 0xfc, 0x50, - 0x45, 0xab, 0xb3, 0xd2, 0x2c, 0x4d, 0x08, 0x03, 0xfc, 0x00, 0xd5, 0x29, 0xb0, 0x6c, 0xc4, 0x99, - 0x28, 0xbd, 0xb4, 0xb3, 0xdc, 0xbe, 0x69, 0x95, 0x57, 0x3b, 0x97, 0xa0, 0xef, 0x4e, 0x91, 0x65, - 0x7c, 0xab, 0xa0, 0x9a, 0x8a, 0xe1, 0x23, 0x54, 0xf7, 0xd2, 0xd4, 0x85, 0x2c, 0xd2, 0x63, 0xec, - 0x8a, 0x31, 0x6e, 0xff, 0xc3, 0x18, 0x9d, 0x34, 0xed, 0x9e, 0x3c, 0x71, 0x6a, 0x82, 0xa5, 0x9b, - 0x45, 0x39, 0x5f, 0xbe, 0x97, 0x9c, 0xaf, 0x7a, 0x26, 0x3e, 0xd1, 0x97, 0xe4, 0x13, 0x2c, 0x39, - 0x9f, 0x83, 0xce, 0x93, 0x49, 0xec, 0x32, 0x37, 0x86, 0xe9, 0xe6, 0xd2, 0x99, 0x16, 0x7d, 0x34, - 0x89, 0x7b, 0x07, 0x30, 0x75, 0xea, 0x44, 0x1d, 0xcc, 0x5d, 0xd4, 0x3a, 0x49, 0x47, 0x11, 0x89, - 0x67, 0x1b, 0xbd, 0x8e, 0x84, 0x0f, 0x21, 0x70, 0x83, 0x64, 0x42, 0xf2, 0x07, 0xb9, 0x8b, 0x86, - 0xb3, 0x92, 0x07, 0xf7, 0x75, 0xcc, 0xdc, 0x40, 0x97, 0x1c, 0x08, 0x23, 0xc6, 0x81, 0xea, 0x85, - 0x2a, 0x2f, 0x98, 0xab, 0xa8, 0xd9, 0xe3, 0x1e, 0xcf, 0x0a, 0x73, 0x98, 0x0e, 0x6a, 0x15, 0x01, - 0x5d, 0xe0, 0x21, 0x5a, 0x0f, 0x94, 0x28, 0x6e, 0x0a, 0x54, 0xda, 0x06, 0x18, 0x93, 0x65, 0x96, - 0xdb, 0x6b, 0x56, 0x6e, 0xd1, 0x67, 0x40, 0x7d, 0x20, 0x3c, 0x1a, 0x09, 0xe1, 0x2e, 0x68, 0xb0, - 0x88, 0x75, 0x14, 0xb4, 0xfd, 0xb6, 0x8a, 0x9a, 0x7a, 0xc0, 0x9e, 0x54, 0x19, 0x1f, 0x20, 0xf4, - 0x18, 0xb8, 0xd6, 0x1a, 0x5f, 0x59, 0xe4, 0x01, 0xd9, 0x92, 0xb1, 0xf5, 0x67, 0x8b, 0x08, 0xdd, - 0x1a, 0x1d, 0x9f, 0x47, 0x63, 0x8f, 0x03, 0xde, 0xb6, 0xb4, 0xf9, 0x15, 0x48, 0xc7, 0xa3, 0x84, - 0x14, 0x68, 0xe3, 0x54, 0x04, 0x7e, 0x8a, 0x6a, 0x6a, 0xc7, 0xf8, 0xda, 0x0f, 0x6c, 0x90, 0x89, - 0x90, 0x2f, 0x4a, 0x04, 0xea, 0xed, 0x50, 0x0c, 0xe6, 0x85, 0x60, 0xcc, 0xf7, 0x3e, 0xa7, 0xce, - 0x7d, 0xd4, 0x28, 0x44, 0xc0, 0x1b, 0x33, 0x36, 0x1d, 0x29, 0x38, 0x16, 0x3d, 0xb4, 0xdf, 0x54, - 0xd0, 0xc5, 0xd2, 0xe2, 0x0e, 0x3d, 0x22, 0xe2, 0x54, 0x68, 0xd2, 0x2a, 0xeb, 0x89, 0x6f, 0xcc, - 0xf5, 0xf1, 0x5b, 0xb9, 0x8d, 0x86, 0x94, 0xab, 0xe3, 0xc7, 0xb8, 0x8b, 0x6a, 0x4a, 0x67, 0x7c, - 0x79, 0x2e, 0xb3, 0xe4, 0x87, 0x5f, 0xe6, 0x2b, 0x9b, 0x63, 0x6f, 0xed, 0xdd, 0x97, 0xad, 0xca, - 0x7b, 0xf1, 0x7d, 0x16, 0xdf, 0xeb, 0xaf, 0x5b, 0xff, 0xf5, 0x6b, 0xf2, 0x6f, 0xe7, 0xce, 0xf7, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x46, 0xc8, 0x19, 0x0c, 0x65, 0x05, 0x00, 0x00, + // 603 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xc1, 0x6e, 0xd3, 0x4c, + 0x10, 0xfe, 0x93, 0xfe, 0x24, 0xe9, 0xb6, 0x69, 0xcb, 0x96, 0xaa, 0x95, 0x05, 0xa5, 0x18, 0x90, + 0x7a, 0xc1, 0x16, 0x41, 0x85, 0x0b, 0x12, 0xa4, 0xb4, 0x42, 0x50, 0xb5, 0x02, 0xb7, 0x3d, 0x5b, + 0x8e, 0x3d, 0x75, 0x2d, 0x97, 0xb5, 0xd9, 0x5d, 0x27, 0xf4, 0x4d, 0x10, 0x0f, 0xc2, 0x33, 0x70, + 0x84, 0x2b, 0x07, 0x84, 0xe0, 0x35, 0x38, 0x30, 0xde, 0xdd, 0x04, 0x1c, 0x35, 0x14, 0x7a, 0xb0, + 0xb2, 0xf3, 0xed, 0x37, 0xdf, 0xce, 0xcc, 0x37, 0x0a, 0xd9, 0x8e, 0x13, 0x79, 0x5c, 0xf4, 0x9c, + 0x30, 0x7b, 0xe5, 0x1e, 0x1c, 0xc3, 0xc1, 0x71, 0xc2, 0x62, 0xb1, 0x07, 0x72, 0x90, 0xf1, 0xd4, + 0x95, 0x92, 0xb9, 0x41, 0x9e, 0xb8, 0x4c, 0xc7, 0x02, 0x78, 0x1f, 0x78, 0x35, 0x72, 0x72, 0x9e, + 0xc9, 0x8c, 0xb6, 0x2b, 0xa0, 0x75, 0xe7, 0x37, 0xd5, 0x38, 0x8b, 0x33, 0x57, 0xb1, 0x7a, 0xc5, + 0x91, 0x8a, 0x54, 0xa0, 0x4e, 0x3a, 0xbb, 0x42, 0x9f, 0x58, 0x04, 0x7e, 0x86, 0xfe, 0xe0, 0x6f, + 0xe8, 0x3d, 0x9e, 0xa5, 0x58, 0xac, 0xfe, 0xd1, 0x89, 0xf6, 0x1b, 0x32, 0xb7, 0x05, 0xfd, 0x24, + 0x04, 0xe1, 0xc1, 0xeb, 0x02, 0x84, 0xa4, 0x2f, 0x49, 0x2b, 0x82, 0xbe, 0x1f, 0x44, 0x11, 0x5f, + 0xa9, 0xad, 0xd5, 0xd6, 0x67, 0x37, 0xef, 0x7f, 0xfe, 0x72, 0xbd, 0x73, 0xde, 0x03, 0x61, 0xc6, + 0xc1, 0x95, 0xa7, 0x39, 0x08, 0x07, 0x05, 0xbb, 0x98, 0xed, 0x35, 0x23, 0x7d, 0xa0, 0x8b, 0xe4, + 0xd2, 0x91, 0x1f, 0x32, 0xb9, 0x52, 0x47, 0xbd, 0xb6, 0xf7, 0xff, 0xd1, 0x13, 0x26, 0xed, 0x4f, + 0x75, 0x32, 0x3f, 0x7a, 0x5a, 0xe4, 0x19, 0x13, 0x40, 0x1f, 0x91, 0x26, 0x07, 0x51, 0x9c, 0x48, + 0x81, 0x4f, 0x4f, 0xad, 0xcf, 0x74, 0x6e, 0x3b, 0xd5, 0xd1, 0x8e, 0x25, 0x98, 0xd8, 0x1b, 0x66, + 0x59, 0x3f, 0x6a, 0xa4, 0xa1, 0x31, 0xba, 0x47, 0x9a, 0x41, 0x9e, 0xfb, 0x50, 0x24, 0xa6, 0x8d, + 0x0d, 0x6c, 0xe3, 0xee, 0x3f, 0xb4, 0xd1, 0xcd, 0xf3, 0xed, 0xc3, 0x67, 0x5e, 0x03, 0x55, 0xb6, + 0x8b, 0xa4, 0xd4, 0x2b, 0xe7, 0x52, 0xea, 0xd5, 0x2f, 0xa4, 0x87, 0x75, 0x29, 0x3d, 0x54, 0x29, + 0xf5, 0x3c, 0x32, 0xcd, 0x06, 0xa9, 0x2f, 0xfc, 0x14, 0x4e, 0x57, 0xa6, 0x2e, 0x34, 0xe8, 0xbd, + 0x41, 0xba, 0xbf, 0x03, 0xa7, 0x5e, 0x93, 0xe9, 0x83, 0xbd, 0x41, 0xe6, 0x0e, 0xf3, 0x93, 0x84, + 0xa5, 0xa3, 0x89, 0xde, 0x24, 0xb8, 0x87, 0x10, 0xf9, 0x51, 0x36, 0x60, 0xe5, 0x85, 0x9a, 0x45, + 0xcb, 0x9b, 0x2d, 0xc1, 0x2d, 0x83, 0xd9, 0xcb, 0x64, 0xc9, 0x83, 0x38, 0x11, 0x12, 0xb8, 0x19, + 0xa8, 0xde, 0x05, 0x7b, 0x9e, 0xb4, 0xf7, 0x65, 0x20, 0x8b, 0xe1, 0x72, 0xd8, 0xcf, 0x49, 0x43, + 0x03, 0xf4, 0x31, 0x59, 0x8c, 0xb4, 0x19, 0x7e, 0x0e, 0x5c, 0xad, 0x0b, 0x08, 0xa1, 0xe4, 0x67, + 0x3a, 0x0b, 0x4e, 0xb9, 0x9a, 0x2f, 0x80, 0x87, 0xc0, 0x64, 0x72, 0x82, 0x86, 0x5d, 0x36, 0x64, + 0xc4, 0xba, 0x9a, 0xda, 0x79, 0x5f, 0x27, 0x6d, 0xd3, 0xd8, 0xbe, 0x72, 0x97, 0xee, 0x10, 0xf2, + 0x14, 0xa4, 0xf1, 0x98, 0x5e, 0x9b, 0xe4, 0xbd, 0x2a, 0xc5, 0x5a, 0xfd, 0xf3, 0x6a, 0xa0, 0x5f, + 0xad, 0x6e, 0x28, 0x93, 0x7e, 0x20, 0x81, 0xae, 0x39, 0x66, 0xe9, 0x35, 0xc9, 0xe0, 0x49, 0xc6, + 0x86, 0x6c, 0xeb, 0x5c, 0x06, 0xc5, 0xd6, 0xf5, 0x6c, 0xe9, 0x8d, 0x5f, 0xdc, 0xa8, 0x40, 0x28, + 0xc4, 0x27, 0x22, 0x7d, 0xb7, 0x8b, 0x8d, 0x05, 0x31, 0x58, 0xe3, 0xb5, 0x8f, 0xb9, 0xf2, 0x90, + 0xb4, 0x86, 0xc3, 0xa7, 0xcb, 0x23, 0x35, 0x83, 0x0c, 0x35, 0x26, 0x5d, 0x74, 0xde, 0xd5, 0xc8, + 0x95, 0xca, 0xe0, 0x76, 0x03, 0x86, 0x38, 0x47, 0x4f, 0xe6, 0xaa, 0x3e, 0xd2, 0x5b, 0x63, 0x75, + 0x9c, 0x69, 0xb3, 0xd5, 0x52, 0x76, 0x75, 0xc3, 0x14, 0x15, 0xa6, 0xd1, 0x01, 0x63, 0xf1, 0xd5, + 0xb1, 0xe4, 0xca, 0x2a, 0x58, 0x4b, 0x67, 0xde, 0x6e, 0x2e, 0x7c, 0xf8, 0xb6, 0x5a, 0xfb, 0x88, + 0xdf, 0x57, 0xfc, 0xde, 0x7e, 0x5f, 0xfd, 0xaf, 0xd7, 0x50, 0xff, 0x34, 0xf7, 0x7e, 0x06, 0x00, + 0x00, 0xff, 0xff, 0xf9, 0x72, 0x6b, 0x9d, 0x58, 0x05, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 315b7688f..f6cc8aeb0 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -48,8 +48,8 @@ message RegisterDeviceRequest { // message StatusRequest is used to request the status of this NetworkServer message StatusRequest {} -// message StatusResponse is the response to the StatusRequest -message StatusResponse { +// message Status is the response to the StatusRequest +message Status { // GetDevices histogram percentiles api.Percentiles devices_per_address = 1; } @@ -58,5 +58,5 @@ message StatusResponse { // functionality service NetworkServerManager { rpc RegisterDevice(RegisterDeviceRequest) returns (api.Ack); - rpc Status(StatusRequest) returns (StatusResponse); + rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go new file mode 100644 index 000000000..9a2ed5459 --- /dev/null +++ b/api/noc/noc.pb.go @@ -0,0 +1,361 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/noc/noc.proto +// DO NOT EDIT! + +/* + Package noc is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/noc/noc.proto + + It has these top-level messages: +*/ +package noc + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import api "github.com/TheThingsNetwork/ttn/api" +import gateway "github.com/TheThingsNetwork/ttn/api/gateway" +import router "github.com/TheThingsNetwork/ttn/api/router" +import broker "github.com/TheThingsNetwork/ttn/api/broker" +import handler "github.com/TheThingsNetwork/ttn/api/handler" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion1 + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion2 + +// Client API for Monitoring service + +type MonitoringClient interface { + GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_GatewayStatusClient, error) + RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_RouterStatusClient, error) + BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_BrokerStatusClient, error) + HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_HandlerStatusClient, error) +} + +type monitoringClient struct { + cc *grpc.ClientConn +} + +func NewMonitoringClient(cc *grpc.ClientConn) MonitoringClient { + return &monitoringClient{cc} +} + +func (c *monitoringClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_GatewayStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[0], c.cc, "/noc.Monitoring/GatewayStatus", opts...) + if err != nil { + return nil, err + } + x := &monitoringGatewayStatusClient{stream} + return x, nil +} + +type Monitoring_GatewayStatusClient interface { + Send(*gateway.Status) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type monitoringGatewayStatusClient struct { + grpc.ClientStream +} + +func (x *monitoringGatewayStatusClient) Send(m *gateway.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitoringGatewayStatusClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitoringClient) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_RouterStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[1], c.cc, "/noc.Monitoring/RouterStatus", opts...) + if err != nil { + return nil, err + } + x := &monitoringRouterStatusClient{stream} + return x, nil +} + +type Monitoring_RouterStatusClient interface { + Send(*router.Status) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type monitoringRouterStatusClient struct { + grpc.ClientStream +} + +func (x *monitoringRouterStatusClient) Send(m *router.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitoringRouterStatusClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitoringClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_BrokerStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[2], c.cc, "/noc.Monitoring/BrokerStatus", opts...) + if err != nil { + return nil, err + } + x := &monitoringBrokerStatusClient{stream} + return x, nil +} + +type Monitoring_BrokerStatusClient interface { + Send(*broker.Status) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type monitoringBrokerStatusClient struct { + grpc.ClientStream +} + +func (x *monitoringBrokerStatusClient) Send(m *broker.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitoringBrokerStatusClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitoringClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_HandlerStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[3], c.cc, "/noc.Monitoring/HandlerStatus", opts...) + if err != nil { + return nil, err + } + x := &monitoringHandlerStatusClient{stream} + return x, nil +} + +type Monitoring_HandlerStatusClient interface { + Send(*handler.Status) error + CloseAndRecv() (*api.Ack, error) + grpc.ClientStream +} + +type monitoringHandlerStatusClient struct { + grpc.ClientStream +} + +func (x *monitoringHandlerStatusClient) Send(m *handler.Status) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitoringHandlerStatusClient) CloseAndRecv() (*api.Ack, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(api.Ack) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +// Server API for Monitoring service + +type MonitoringServer interface { + GatewayStatus(Monitoring_GatewayStatusServer) error + RouterStatus(Monitoring_RouterStatusServer) error + BrokerStatus(Monitoring_BrokerStatusServer) error + HandlerStatus(Monitoring_HandlerStatusServer) error +} + +func RegisterMonitoringServer(s *grpc.Server, srv MonitoringServer) { + s.RegisterService(&_Monitoring_serviceDesc, srv) +} + +func _Monitoring_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitoringServer).GatewayStatus(&monitoringGatewayStatusServer{stream}) +} + +type Monitoring_GatewayStatusServer interface { + SendAndClose(*api.Ack) error + Recv() (*gateway.Status, error) + grpc.ServerStream +} + +type monitoringGatewayStatusServer struct { + grpc.ServerStream +} + +func (x *monitoringGatewayStatusServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitoringGatewayStatusServer) Recv() (*gateway.Status, error) { + m := new(gateway.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitoring_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitoringServer).RouterStatus(&monitoringRouterStatusServer{stream}) +} + +type Monitoring_RouterStatusServer interface { + SendAndClose(*api.Ack) error + Recv() (*router.Status, error) + grpc.ServerStream +} + +type monitoringRouterStatusServer struct { + grpc.ServerStream +} + +func (x *monitoringRouterStatusServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitoringRouterStatusServer) Recv() (*router.Status, error) { + m := new(router.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitoring_BrokerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitoringServer).BrokerStatus(&monitoringBrokerStatusServer{stream}) +} + +type Monitoring_BrokerStatusServer interface { + SendAndClose(*api.Ack) error + Recv() (*broker.Status, error) + grpc.ServerStream +} + +type monitoringBrokerStatusServer struct { + grpc.ServerStream +} + +func (x *monitoringBrokerStatusServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitoringBrokerStatusServer) Recv() (*broker.Status, error) { + m := new(broker.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitoring_HandlerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitoringServer).HandlerStatus(&monitoringHandlerStatusServer{stream}) +} + +type Monitoring_HandlerStatusServer interface { + SendAndClose(*api.Ack) error + Recv() (*handler.Status, error) + grpc.ServerStream +} + +type monitoringHandlerStatusServer struct { + grpc.ServerStream +} + +func (x *monitoringHandlerStatusServer) SendAndClose(m *api.Ack) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitoringHandlerStatusServer) Recv() (*handler.Status, error) { + m := new(handler.Status) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +var _Monitoring_serviceDesc = grpc.ServiceDesc{ + ServiceName: "noc.Monitoring", + HandlerType: (*MonitoringServer)(nil), + Methods: []grpc.MethodDesc{}, + Streams: []grpc.StreamDesc{ + { + StreamName: "GatewayStatus", + Handler: _Monitoring_GatewayStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "RouterStatus", + Handler: _Monitoring_RouterStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "BrokerStatus", + Handler: _Monitoring_BrokerStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "HandlerStatus", + Handler: _Monitoring_HandlerStatus_Handler, + ClientStreams: true, + }, + }, +} + +var fileDescriptorNoc = []byte{ + // 237 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, + 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, + 0xcf, 0xcb, 0x4f, 0x06, 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x66, 0x20, 0x53, 0x4a, + 0x97, 0x18, 0x7d, 0x40, 0x0c, 0xd1, 0x23, 0x65, 0x49, 0x8c, 0xf2, 0xf4, 0xc4, 0x92, 0xd4, 0xf2, + 0xc4, 0x4a, 0x18, 0x0d, 0xd5, 0x6a, 0x4e, 0x8c, 0xd6, 0xa2, 0xfc, 0xd2, 0x92, 0xd4, 0x22, 0x28, + 0x45, 0x8a, 0xc6, 0xa4, 0xa2, 0xfc, 0x6c, 0xa0, 0x46, 0x08, 0x45, 0x8a, 0x63, 0x33, 0x12, 0xf3, + 0x52, 0x72, 0x80, 0x3a, 0xa1, 0x34, 0x44, 0xab, 0xd1, 0x01, 0x46, 0x2e, 0x2e, 0xdf, 0xfc, 0xbc, + 0xcc, 0x92, 0xfc, 0x22, 0xa0, 0x26, 0x21, 0x1d, 0x2e, 0x5e, 0x77, 0x88, 0x67, 0x82, 0x4b, 0x12, + 0x4b, 0x4a, 0x8b, 0x85, 0xf8, 0xf5, 0x60, 0x9e, 0x83, 0x08, 0x48, 0x71, 0xe8, 0x81, 0x02, 0xc9, + 0x31, 0x39, 0x5b, 0x83, 0x51, 0x48, 0x8b, 0x8b, 0x27, 0x08, 0xec, 0x01, 0xa8, 0x62, 0x3e, 0x3d, + 0xa8, 0x7f, 0xb0, 0xab, 0x75, 0x02, 0xbb, 0x19, 0xae, 0x16, 0xea, 0x05, 0x2c, 0x6a, 0x81, 0xae, + 0xf0, 0x80, 0xb8, 0x12, 0xee, 0x0a, 0x98, 0xab, 0x31, 0x55, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, + 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0xbf, 0x19, 0x03, + 0x02, 0x00, 0x00, 0xff, 0xff, 0x34, 0xb1, 0xcc, 0x36, 0x2c, 0x02, 0x00, 0x00, +} diff --git a/api/noc/noc.proto b/api/noc/noc.proto new file mode 100644 index 000000000..989c7d580 --- /dev/null +++ b/api/noc/noc.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; +import "github.com/TheThingsNetwork/ttn/api/router/router.proto"; +import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; +import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; + +package noc; + +service Monitoring { + rpc GatewayStatus(stream gateway.Status) returns (api.Ack); + rpc RouterStatus(stream router.Status) returns (api.Ack); + rpc BrokerStatus(stream broker.Status) returns (api.Ack); + rpc HandlerStatus(stream handler.Status) returns (api.Ack); +} diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 4eaa1202b..babd3e1e2 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -21,7 +21,7 @@ GatewayStatusRequest GatewayStatusResponse StatusRequest - StatusResponse + Status */ package router @@ -215,7 +215,7 @@ func (*GatewayStatusRequest) Descriptor() ([]byte, []int) { return fileDescripto // message GatewayStatusResponse is the response to the GatewayStatusRequest type GatewayStatusResponse struct { - LastStatus *gateway.StatusMessage `protobuf:"bytes,1,opt,name=last_status,json=lastStatus" json:"last_status,omitempty"` + LastStatus *gateway.Status `protobuf:"bytes,1,opt,name=last_status,json=lastStatus" json:"last_status,omitempty"` } func (m *GatewayStatusResponse) Reset() { *m = GatewayStatusResponse{} } @@ -223,7 +223,7 @@ func (m *GatewayStatusResponse) String() string { return proto.Compac func (*GatewayStatusResponse) ProtoMessage() {} func (*GatewayStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{10} } -func (m *GatewayStatusResponse) GetLastStatus() *gateway.StatusMessage { +func (m *GatewayStatusResponse) GetLastStatus() *gateway.Status { if m != nil { return m.LastStatus } @@ -239,8 +239,8 @@ func (m *StatusRequest) String() string { return proto.CompactTextStr func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{11} } -// message StatusResponse is the response to the StatusRequest -type StatusResponse struct { +// message Status is the response to the StatusRequest +type Status struct { // Gateways GatewayStatus *api.Rates `protobuf:"bytes,1,opt,name=gateway_status,json=gatewayStatus" json:"gateway_status,omitempty"` ActiveGateways uint32 `protobuf:"varint,4,opt,name=active_gateways,json=activeGateways,proto3" json:"active_gateways,omitempty"` @@ -256,40 +256,40 @@ type StatusResponse struct { ConnectedBrokers uint32 `protobuf:"varint,42,opt,name=connected_brokers,json=connectedBrokers,proto3" json:"connected_brokers,omitempty"` } -func (m *StatusResponse) Reset() { *m = StatusResponse{} } -func (m *StatusResponse) String() string { return proto.CompactTextString(m) } -func (*StatusResponse) ProtoMessage() {} -func (*StatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{12} } +func (m *Status) Reset() { *m = Status{} } +func (m *Status) String() string { return proto.CompactTextString(m) } +func (*Status) ProtoMessage() {} +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{12} } -func (m *StatusResponse) GetGatewayStatus() *api.Rates { +func (m *Status) GetGatewayStatus() *api.Rates { if m != nil { return m.GatewayStatus } return nil } -func (m *StatusResponse) GetUplink() *api.Rates { +func (m *Status) GetUplink() *api.Rates { if m != nil { return m.Uplink } return nil } -func (m *StatusResponse) GetDownlink() *api.Rates { +func (m *Status) GetDownlink() *api.Rates { if m != nil { return m.Downlink } return nil } -func (m *StatusResponse) GetActivations() *api.Rates { +func (m *Status) GetActivations() *api.Rates { if m != nil { return m.Activations } return nil } -func (m *StatusResponse) GetActivationsAccepted() *api.Rates { +func (m *Status) GetActivationsAccepted() *api.Rates { if m != nil { return m.ActivationsAccepted } @@ -309,7 +309,7 @@ func init() { proto.RegisterType((*GatewayStatusRequest)(nil), "router.GatewayStatusRequest") proto.RegisterType((*GatewayStatusResponse)(nil), "router.GatewayStatusResponse") proto.RegisterType((*StatusRequest)(nil), "router.StatusRequest") - proto.RegisterType((*StatusResponse)(nil), "router.StatusResponse") + proto.RegisterType((*Status)(nil), "router.Status") } // Reference imports to suppress errors if they are not otherwise used. @@ -351,7 +351,7 @@ func (c *routerClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOptio } type Router_GatewayStatusClient interface { - Send(*gateway.StatusMessage) error + Send(*gateway.Status) error CloseAndRecv() (*api.Ack, error) grpc.ClientStream } @@ -360,7 +360,7 @@ type routerGatewayStatusClient struct { grpc.ClientStream } -func (x *routerGatewayStatusClient) Send(m *gateway.StatusMessage) error { +func (x *routerGatewayStatusClient) Send(m *gateway.Status) error { return x.ClientStream.SendMsg(m) } @@ -473,7 +473,7 @@ func _Router_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) er type Router_GatewayStatusServer interface { SendAndClose(*api.Ack) error - Recv() (*gateway.StatusMessage, error) + Recv() (*gateway.Status, error) grpc.ServerStream } @@ -485,8 +485,8 @@ func (x *routerGatewayStatusServer) SendAndClose(m *api.Ack) error { return x.ServerStream.SendMsg(m) } -func (x *routerGatewayStatusServer) Recv() (*gateway.StatusMessage, error) { - m := new(gateway.StatusMessage) +func (x *routerGatewayStatusServer) Recv() (*gateway.Status, error) { + m := new(gateway.Status) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } @@ -598,7 +598,7 @@ type RouterManagerClient interface { // Gateway owner or network operator requests Gateway status from Router Manager GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) // Network operator requests Router status - Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) + GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } type routerManagerClient struct { @@ -645,9 +645,9 @@ func (c *routerManagerClient) GatewayStatus(ctx context.Context, in *GatewayStat return out, nil } -func (c *routerManagerClient) Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error) { - out := new(StatusResponse) - err := grpc.Invoke(ctx, "/router.RouterManager/Status", in, out, c.cc, opts...) +func (c *routerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + out := new(Status) + err := grpc.Invoke(ctx, "/router.RouterManager/GetStatus", in, out, c.cc, opts...) if err != nil { return nil, err } @@ -666,7 +666,7 @@ type RouterManagerServer interface { // Gateway owner or network operator requests Gateway status from Router Manager GatewayStatus(context.Context, *GatewayStatusRequest) (*GatewayStatusResponse, error) // Network operator requests Router status - Status(context.Context, *StatusRequest) (*StatusResponse, error) + GetStatus(context.Context, *StatusRequest) (*Status, error) } func RegisterRouterManagerServer(s *grpc.Server, srv RouterManagerServer) { @@ -745,20 +745,20 @@ func _RouterManager_GatewayStatus_Handler(srv interface{}, ctx context.Context, return interceptor(ctx, in, info, handler) } -func _RouterManager_Status_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { +func _RouterManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(RouterManagerServer).Status(ctx, in) + return srv.(RouterManagerServer).GetStatus(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/router.RouterManager/Status", + FullMethod: "/router.RouterManager/GetStatus", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterManagerServer).Status(ctx, req.(*StatusRequest)) + return srv.(RouterManagerServer).GetStatus(ctx, req.(*StatusRequest)) } return interceptor(ctx, in, info, handler) } @@ -784,8 +784,8 @@ var _RouterManager_serviceDesc = grpc.ServiceDesc{ Handler: _RouterManager_GatewayStatus_Handler, }, { - MethodName: "Status", - Handler: _RouterManager_Status_Handler, + MethodName: "GetStatus", + Handler: _RouterManager_GetStatus_Handler, }, }, Streams: []grpc.StreamDesc{}, @@ -1193,7 +1193,7 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusResponse) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -1203,7 +1203,7 @@ func (m *StatusResponse) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *StatusResponse) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -1467,7 +1467,7 @@ func (m *StatusRequest) Size() (n int) { return n } -func (m *StatusResponse) Size() (n int) { +func (m *Status) Size() (n int) { var l int _ = l if m.GatewayStatus != nil { @@ -2680,7 +2680,7 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } if m.LastStatus == nil { - m.LastStatus = &gateway.StatusMessage{} + m.LastStatus = &gateway.Status{} } if err := m.LastStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { return err @@ -2757,7 +2757,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *StatusResponse) Unmarshal(data []byte) error { +func (m *Status) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -2780,10 +2780,10 @@ func (m *StatusResponse) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: StatusResponse: wiretype end group for non-group") + return fmt.Errorf("proto: Status: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: StatusResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -3135,62 +3135,61 @@ var ( ) var fileDescriptorRouter = []byte{ - // 898 bytes of a gzipped FileDescriptorProto + // 894 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0xc7, 0xbb, 0x28, 0x9b, 0xbe, 0x24, 0x4d, 0x32, 0x9b, 0x64, 0x4d, 0xc4, 0xb6, 0x95, 0x0f, - 0xb0, 0xfc, 0xd9, 0x84, 0x66, 0xb5, 0x5a, 0xad, 0x56, 0xfc, 0x49, 0x69, 0x85, 0x2a, 0x91, 0xaa, - 0x72, 0xdb, 0x0b, 0x97, 0x68, 0xe2, 0x4c, 0x5d, 0x2b, 0xa9, 0x6d, 0x3c, 0xe3, 0xb4, 0x3d, 0xf2, - 0x2d, 0xf8, 0x10, 0x7c, 0x10, 0x0e, 0x1c, 0x90, 0x10, 0x1c, 0x38, 0x20, 0x04, 0x1f, 0x83, 0x0b, - 0x93, 0xf1, 0xcc, 0xc4, 0x76, 0x1a, 0xd1, 0x02, 0xaa, 0xb4, 0x07, 0x2b, 0x99, 0xdf, 0xfb, 0xcd, - 0xef, 0xbd, 0xe7, 0xf7, 0xe6, 0x8d, 0xe1, 0x85, 0xeb, 0xb1, 0xb3, 0x78, 0xd4, 0x71, 0x82, 0xf3, - 0xee, 0xf1, 0x19, 0x39, 0x3e, 0xf3, 0x7c, 0x97, 0x1e, 0x10, 0x76, 0x11, 0x44, 0x93, 0x2e, 0x63, - 0x7e, 0x17, 0x87, 0x5e, 0x37, 0x0a, 0x62, 0x46, 0x22, 0xf9, 0xd3, 0x09, 0xa3, 0x80, 0x05, 0xa8, - 0x90, 0xac, 0xda, 0x4f, 0x53, 0x02, 0x6e, 0xe0, 0x06, 0x5d, 0x61, 0x1e, 0xc5, 0xa7, 0x62, 0x25, - 0x16, 0xe2, 0x5f, 0xb2, 0x2d, 0x43, 0x5f, 0xe9, 0x8f, 0x3f, 0x92, 0xfe, 0xea, 0x26, 0x74, 0x41, - 0x75, 0x82, 0xa9, 0xfe, 0x23, 0x37, 0xbf, 0xbc, 0xc9, 0x66, 0x17, 0x33, 0x72, 0x81, 0xaf, 0xd4, - 0x6f, 0xb2, 0xd5, 0xda, 0x86, 0xda, 0x51, 0x3c, 0xa2, 0x4e, 0xe4, 0x8d, 0x88, 0x4d, 0xbe, 0x8e, - 0x09, 0x65, 0xe8, 0x31, 0x80, 0x24, 0x0d, 0xbd, 0xb1, 0x69, 0x6c, 0x19, 0x4f, 0xd6, 0xec, 0x35, - 0x89, 0xec, 0x8f, 0xad, 0xef, 0x0c, 0xa8, 0x9c, 0x84, 0x53, 0xcf, 0x9f, 0x0c, 0x08, 0xa5, 0xd8, - 0x25, 0xc8, 0x84, 0x07, 0x21, 0xbe, 0x9a, 0x06, 0x38, 0x61, 0x97, 0x6d, 0xb5, 0x44, 0x7d, 0xa8, - 0xab, 0x58, 0x87, 0xe7, 0x84, 0xe1, 0x31, 0x66, 0xd8, 0x2c, 0x71, 0x4e, 0xa9, 0xd7, 0xe8, 0xe8, - 0x2c, 0xec, 0xcb, 0x81, 0xb4, 0xd9, 0x35, 0x05, 0x2a, 0x04, 0x7d, 0x02, 0x35, 0x15, 0x8d, 0x56, - 0x28, 0x0b, 0x85, 0x87, 0x1d, 0x95, 0x4b, 0x4a, 0xa0, 0x2a, 0x31, 0x05, 0x58, 0x3f, 0x18, 0x50, - 0xdd, 0x0d, 0x2e, 0xfc, 0x9b, 0x05, 0x7c, 0x08, 0x2d, 0x1d, 0xb0, 0x13, 0xf8, 0xa7, 0x9e, 0x1b, - 0x47, 0x98, 0x79, 0x81, 0x2f, 0xa3, 0x7e, 0x6b, 0x11, 0xf5, 0xf1, 0xe5, 0xe7, 0x69, 0x82, 0xdd, - 0x54, 0x96, 0x0c, 0x8c, 0x06, 0xd0, 0x54, 0xf1, 0x67, 0x05, 0x93, 0x24, 0x4c, 0x9d, 0x44, 0x5e, - 0xaf, 0x21, 0x0d, 0x19, 0xd4, 0xfa, 0xe5, 0x1e, 0x3c, 0xda, 0x25, 0x33, 0xcf, 0x21, 0x7d, 0x87, - 0x79, 0xb3, 0x84, 0x2a, 0x0b, 0xb7, 0x3a, 0xad, 0x03, 0x78, 0x30, 0x26, 0xb3, 0x21, 0x89, 0x3d, - 0x91, 0x47, 0x79, 0xe7, 0xf9, 0xaf, 0xbf, 0x6d, 0x6e, 0xff, 0x53, 0xdb, 0x38, 0x41, 0x44, 0xba, - 0xec, 0x2a, 0x24, 0xb4, 0xc3, 0x5d, 0xee, 0x9d, 0xec, 0xdb, 0x05, 0xae, 0xb2, 0x17, 0x7b, 0x73, - 0x3d, 0x1c, 0x86, 0x42, 0xaf, 0xfc, 0xaf, 0xf4, 0xfa, 0x61, 0x28, 0xf4, 0xb8, 0xca, 0x5c, 0xef, - 0xda, 0x3e, 0x69, 0xfe, 0xe7, 0x3e, 0x69, 0xdd, 0xa2, 0x4f, 0x7e, 0x36, 0xc0, 0x5c, 0x7e, 0xb1, - 0x34, 0x0c, 0x7c, 0xfa, 0x5a, 0x37, 0x4c, 0x1d, 0xaa, 0x5f, 0x24, 0x38, 0x95, 0x7d, 0x62, 0xf9, - 0x50, 0x5b, 0x40, 0x32, 0xc3, 0xaf, 0xa0, 0xb4, 0x38, 0xf4, 0x94, 0x67, 0x79, 0x9f, 0x57, 0xf5, - 0x25, 0xaf, 0xea, 0xf3, 0x5b, 0x54, 0x55, 0xaa, 0xce, 0x2b, 0x0b, 0x7a, 0x60, 0x50, 0x8b, 0x41, - 0xcb, 0x26, 0xae, 0x47, 0xf9, 0x18, 0x95, 0x0c, 0xd5, 0xb1, 0x29, 0xaf, 0xf3, 0x5e, 0x12, 0xef, - 0xf6, 0xff, 0xf0, 0xca, 0x7b, 0xca, 0x9a, 0x81, 0x79, 0xe2, 0x47, 0x77, 0xef, 0x37, 0x82, 0x86, - 0xb4, 0x1c, 0x31, 0xcc, 0x62, 0x7a, 0x17, 0x3e, 0x0f, 0xa1, 0x99, 0xf3, 0x29, 0xcb, 0xfa, 0x02, - 0x4a, 0x53, 0x4c, 0xd9, 0x90, 0x0a, 0x58, 0x38, 0x2d, 0xf5, 0x5a, 0xba, 0x85, 0x12, 0xb6, 0x1c, - 0x8b, 0x36, 0xcc, 0xa9, 0x09, 0x64, 0x55, 0xa1, 0x92, 0x09, 0xdf, 0xfa, 0xe6, 0x3e, 0xac, 0xe7, - 0xc4, 0xb7, 0x61, 0x5d, 0x65, 0x94, 0xd1, 0x87, 0xce, 0xfc, 0x62, 0xb3, 0xb9, 0x89, 0xda, 0x15, - 0x37, 0x1d, 0x17, 0x7a, 0x17, 0xaa, 0x78, 0x7e, 0xbc, 0xc8, 0x50, 0xe2, 0xd4, 0x7c, 0x93, 0xef, - 0xa9, 0xd8, 0xeb, 0x09, 0xac, 0xfa, 0x12, 0x59, 0x50, 0x88, 0xc5, 0x25, 0x23, 0xcf, 0x51, 0x5a, - 0x53, 0x5a, 0xd0, 0x3b, 0x50, 0x1c, 0xcb, 0xc9, 0x2e, 0x87, 0x45, 0x9a, 0xa5, 0x6d, 0xe8, 0x43, - 0x28, 0x61, 0x7d, 0xa6, 0xa9, 0xb9, 0xb9, 0x44, 0x4d, 0x9b, 0xd1, 0xc7, 0xd0, 0x48, 0x2d, 0x87, - 0xd8, 0x71, 0x48, 0xc8, 0xc8, 0xd8, 0xdc, 0x5a, 0xda, 0xf6, 0x30, 0xc5, 0xeb, 0x4b, 0x1a, 0x7a, - 0x0a, 0x88, 0x1f, 0x5b, 0x9f, 0x38, 0x7c, 0xb1, 0x48, 0xf2, 0x3d, 0x91, 0x64, 0x5d, 0x5b, 0x74, - 0x9e, 0x1f, 0xc0, 0x02, 0x1c, 0x8e, 0xa2, 0x60, 0x42, 0x22, 0x6a, 0xbe, 0x2f, 0xd8, 0x35, 0x6d, - 0xd8, 0x49, 0xf0, 0xde, 0x5f, 0x06, 0x14, 0x6c, 0xf1, 0x39, 0x82, 0x9e, 0x41, 0x25, 0x53, 0x71, - 0xb4, 0xa2, 0xa8, 0xed, 0xa2, 0x08, 0xb8, 0xef, 0x4c, 0x9e, 0x18, 0xdc, 0x59, 0x21, 0xb9, 0xb9, - 0x51, 0xb3, 0x23, 0x3f, 0x72, 0x32, 0x37, 0x79, 0x86, 0xfc, 0x19, 0xac, 0xe9, 0x4f, 0x03, 0x64, - 0x2a, 0x7e, 0xfe, 0x6b, 0xa1, 0xfd, 0x48, 0x59, 0x72, 0x97, 0xec, 0x47, 0x06, 0x9f, 0x64, 0x45, - 0x39, 0x4b, 0x09, 0xda, 0xd4, 0xb4, 0xeb, 0x2f, 0xaf, 0xf6, 0xd6, 0x6a, 0x42, 0xd2, 0x6e, 0xbd, - 0x9f, 0xee, 0x41, 0x25, 0xc9, 0x7e, 0x80, 0x7d, 0xee, 0x21, 0xe2, 0xa5, 0x2a, 0xea, 0x17, 0xa9, - 0xe3, 0xc8, 0x4d, 0xbb, 0xb6, 0xb9, 0x6c, 0x90, 0xfd, 0xfb, 0x0a, 0xaa, 0xb9, 0xb9, 0x84, 0x36, - 0x14, 0xf9, 0xfa, 0x81, 0xb5, 0x78, 0x41, 0xe8, 0x53, 0xa8, 0x2f, 0x8d, 0x17, 0xa4, 0x93, 0x58, - 0x35, 0x79, 0x52, 0x02, 0x5f, 0xe6, 0x2b, 0xf8, 0x76, 0x2e, 0xd0, 0xcc, 0xf9, 0x6b, 0x3f, 0x5e, - 0x61, 0xd5, 0x07, 0xbd, 0x20, 0x65, 0x74, 0x69, 0xb3, 0xfb, 0x5b, 0x79, 0x38, 0xd9, 0xb8, 0x53, - 0xfb, 0xfe, 0x8f, 0x0d, 0xe3, 0x47, 0xfe, 0xfc, 0xce, 0x9f, 0x6f, 0xff, 0xdc, 0x78, 0x63, 0x54, - 0x10, 0xf7, 0xd2, 0xb3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xcf, 0xbb, 0xfa, 0x29, 0x33, 0x0b, - 0x00, 0x00, + 0x14, 0xc7, 0x05, 0x65, 0x93, 0x97, 0xa4, 0x49, 0x66, 0x93, 0xae, 0x89, 0xd8, 0xb6, 0xf2, 0x01, + 0x96, 0x3f, 0x9b, 0x6c, 0x83, 0x56, 0x68, 0xb5, 0xe2, 0x4f, 0x4a, 0xab, 0xaa, 0x12, 0xa9, 0x90, + 0xdb, 0x5e, 0xb8, 0x44, 0x13, 0x67, 0xea, 0x5a, 0x49, 0x6d, 0xe3, 0x19, 0xa7, 0xed, 0x37, 0xe1, + 0x43, 0xf0, 0x41, 0x38, 0x20, 0xc4, 0x05, 0x0e, 0x1c, 0x10, 0x82, 0xaf, 0xc0, 0x85, 0x1b, 0x93, + 0xf1, 0xcc, 0xc4, 0x76, 0x1a, 0xd1, 0x02, 0xaa, 0xb4, 0x07, 0xcb, 0x9e, 0xf7, 0x7e, 0xf3, 0x7b, + 0xef, 0xf9, 0xfd, 0x99, 0x81, 0x8f, 0x5c, 0x8f, 0x9d, 0xc7, 0xa3, 0x8e, 0x13, 0x5c, 0x74, 0x4f, + 0xce, 0xc9, 0xc9, 0xb9, 0xe7, 0xbb, 0xf4, 0x88, 0xb0, 0xcb, 0x20, 0x9a, 0x74, 0x19, 0xf3, 0xbb, + 0x38, 0xf4, 0xba, 0x51, 0x10, 0x33, 0x12, 0xc9, 0x57, 0x27, 0x8c, 0x02, 0x16, 0xa0, 0x42, 0xb2, + 0x6a, 0x3f, 0x4d, 0x11, 0xb8, 0x81, 0x1b, 0x74, 0x85, 0x7a, 0x14, 0x9f, 0x89, 0x95, 0x58, 0x88, + 0xaf, 0x64, 0x5b, 0x06, 0xbe, 0xd2, 0x1e, 0x7f, 0x24, 0xfc, 0xe5, 0x6d, 0xe0, 0x02, 0xea, 0x04, + 0x53, 0xfd, 0x21, 0x37, 0xbf, 0xb8, 0xcd, 0x66, 0x17, 0x33, 0x72, 0x89, 0xaf, 0xd5, 0x3b, 0xd9, + 0x6a, 0xed, 0x40, 0xfd, 0x38, 0x1e, 0x51, 0x27, 0xf2, 0x46, 0xc4, 0x26, 0x5f, 0xc7, 0x84, 0x32, + 0xf4, 0x18, 0x40, 0x82, 0x86, 0xde, 0xd8, 0x34, 0xb6, 0x8d, 0x27, 0x25, 0xbb, 0x24, 0x25, 0x87, + 0x63, 0xeb, 0x5b, 0x03, 0xaa, 0xa7, 0xe1, 0xd4, 0xf3, 0x27, 0x03, 0x42, 0x29, 0x76, 0x09, 0x32, + 0xe1, 0x41, 0x88, 0xaf, 0xa7, 0x01, 0x4e, 0xd0, 0x15, 0x5b, 0x2d, 0x51, 0x1f, 0x1a, 0xca, 0xd7, + 0xe1, 0x05, 0x61, 0x78, 0x8c, 0x19, 0x36, 0xcb, 0x1c, 0x53, 0xee, 0x35, 0x3b, 0x3a, 0x0a, 0xfb, + 0x6a, 0x20, 0x75, 0x76, 0x5d, 0x09, 0x95, 0x04, 0x7d, 0x02, 0x75, 0xe5, 0x8d, 0x66, 0xa8, 0x08, + 0x86, 0x87, 0x1d, 0x15, 0x4b, 0x8a, 0xa0, 0x26, 0x65, 0x4a, 0x60, 0x7d, 0x6f, 0x40, 0x6d, 0x2f, + 0xb8, 0xf4, 0x6f, 0xe7, 0xf0, 0x97, 0xb0, 0xa1, 0x1d, 0x76, 0x02, 0xff, 0xcc, 0x73, 0xe3, 0x08, + 0x33, 0x2f, 0xf0, 0xa5, 0xd7, 0x6f, 0x2e, 0xbc, 0x3e, 0xb9, 0xfa, 0x3c, 0x0d, 0xb0, 0x5b, 0x4a, + 0x93, 0x11, 0xa3, 0x01, 0xb4, 0x94, 0xff, 0x59, 0xc2, 0x24, 0x08, 0x53, 0x07, 0x91, 0xe7, 0x6b, + 0x4a, 0x45, 0x46, 0x6a, 0xfd, 0xbc, 0x06, 0x8f, 0xf6, 0xc8, 0xcc, 0x73, 0x48, 0xdf, 0x61, 0xde, + 0x2c, 0x81, 0xca, 0xc4, 0xad, 0x0e, 0xeb, 0x08, 0x1e, 0x8c, 0xc9, 0x6c, 0x48, 0x62, 0x4f, 0xc4, + 0x51, 0xd9, 0x7d, 0xfe, 0xcb, 0xaf, 0x5b, 0x3b, 0xff, 0x54, 0x36, 0x4e, 0x10, 0x91, 0x2e, 0xbb, + 0x0e, 0x09, 0xed, 0x70, 0x93, 0xfb, 0xa7, 0x87, 0x76, 0x81, 0xb3, 0xec, 0xc7, 0xde, 0x9c, 0x0f, + 0x87, 0xa1, 0xe0, 0xab, 0xfc, 0x2b, 0xbe, 0x7e, 0x18, 0x0a, 0x3e, 0xce, 0x32, 0xe7, 0xbb, 0xb1, + 0x4e, 0x5a, 0xff, 0xb9, 0x4e, 0x36, 0xee, 0x50, 0x27, 0x3f, 0x19, 0x60, 0x2e, 0xff, 0x58, 0x1a, + 0x06, 0x3e, 0x7d, 0xa5, 0x0b, 0xa6, 0x01, 0xb5, 0x83, 0x44, 0x4e, 0x65, 0x9d, 0x58, 0x3e, 0xd4, + 0x17, 0x22, 0x19, 0xe1, 0x57, 0x50, 0x5e, 0x34, 0x3d, 0xe5, 0x51, 0xbe, 0xce, 0xb3, 0xfa, 0x82, + 0x67, 0xf5, 0xf9, 0x1d, 0xb2, 0x2a, 0x59, 0xe7, 0x99, 0x05, 0x3d, 0x30, 0xa8, 0xc5, 0x60, 0xc3, + 0x26, 0xae, 0x47, 0xf9, 0x18, 0x95, 0x08, 0x55, 0xb1, 0x29, 0xab, 0xf3, 0x5a, 0x12, 0xff, 0xf6, + 0xff, 0xb0, 0xca, 0x6b, 0xca, 0x9a, 0x81, 0x79, 0xea, 0x47, 0xf7, 0x6f, 0x37, 0x82, 0xa6, 0xd4, + 0x1c, 0x33, 0xcc, 0x62, 0x7a, 0x1f, 0x36, 0x0f, 0xa1, 0x95, 0xb3, 0x29, 0xd3, 0xfa, 0x0c, 0xca, + 0x53, 0x4c, 0xd9, 0x90, 0x0a, 0xb1, 0x30, 0x5a, 0xee, 0xd5, 0x74, 0x09, 0x49, 0x34, 0xcc, 0x31, + 0xc9, 0xb7, 0x55, 0x83, 0x6a, 0xc6, 0x6f, 0xeb, 0xaf, 0x35, 0x28, 0x24, 0x12, 0xb4, 0x03, 0xeb, + 0x2a, 0x84, 0x0c, 0x21, 0x74, 0xe6, 0x27, 0x99, 0xcd, 0x55, 0xd4, 0xae, 0xba, 0x69, 0x47, 0xd0, + 0x3b, 0x50, 0xc3, 0xf3, 0x7e, 0x22, 0x43, 0x29, 0xa7, 0xe6, 0x1b, 0x7c, 0x4f, 0xd5, 0x5e, 0x4f, + 0xc4, 0xaa, 0x10, 0x91, 0x05, 0x85, 0x58, 0x9c, 0x2a, 0xb2, 0x71, 0xd2, 0x9c, 0x52, 0x83, 0xde, + 0x86, 0xe2, 0x58, 0x8e, 0x72, 0x39, 0x1d, 0xd2, 0x28, 0xad, 0x43, 0x1f, 0x40, 0x19, 0xeb, 0x26, + 0xa6, 0xe6, 0xd6, 0x12, 0x34, 0xad, 0x46, 0x1f, 0x43, 0x33, 0xb5, 0x1c, 0x62, 0xc7, 0x21, 0x21, + 0x23, 0x63, 0x73, 0x7b, 0x69, 0xdb, 0xc3, 0x14, 0xae, 0x2f, 0x61, 0xe8, 0x29, 0x20, 0xde, 0xa7, + 0x3e, 0x71, 0xf8, 0x62, 0x11, 0xe4, 0xbb, 0x22, 0xc8, 0x86, 0xd6, 0xe8, 0x38, 0xdf, 0x87, 0x85, + 0x70, 0x38, 0x8a, 0x82, 0x09, 0x89, 0xa8, 0xf9, 0x9e, 0x40, 0xd7, 0xb5, 0x62, 0x37, 0x91, 0xf7, + 0xfe, 0x34, 0xa0, 0x60, 0x8b, 0xfb, 0x07, 0x8f, 0xa9, 0x9a, 0x49, 0x31, 0xca, 0x67, 0xb1, 0x5d, + 0x14, 0x9e, 0xf6, 0x9d, 0xc9, 0x13, 0x83, 0x5b, 0x29, 0x24, 0x67, 0x34, 0x6a, 0x75, 0xe4, 0x75, + 0x26, 0x73, 0x66, 0x67, 0xc0, 0x9f, 0x41, 0x49, 0x5f, 0x02, 0x90, 0xa9, 0xf0, 0xf9, 0x7b, 0x41, + 0xfb, 0x91, 0xd2, 0xe4, 0x8e, 0xd3, 0x67, 0x06, 0x9f, 0x59, 0x45, 0x39, 0x35, 0x09, 0xda, 0xd2, + 0xb0, 0x9b, 0x8f, 0xa9, 0xf6, 0xf6, 0x6a, 0x40, 0x52, 0xb5, 0xbd, 0x1f, 0xd6, 0xa0, 0x9a, 0x84, + 0x3d, 0xc0, 0x3e, 0xb7, 0x10, 0xf1, 0x1c, 0x15, 0xf5, 0x1f, 0xd4, 0x7e, 0xe4, 0xe6, 0x5a, 0xdb, + 0x5c, 0x56, 0xc8, 0x36, 0x78, 0x09, 0xb5, 0xdc, 0x04, 0x42, 0x9b, 0x0a, 0x7c, 0xf3, 0x68, 0x5a, + 0xfc, 0x20, 0xf4, 0x29, 0x34, 0x96, 0x06, 0x09, 0xd2, 0x41, 0xac, 0x9a, 0x31, 0x29, 0x82, 0x2f, + 0xf2, 0xa9, 0x7b, 0x2b, 0xe7, 0x68, 0xa6, 0xe1, 0xda, 0x8f, 0x57, 0x68, 0x65, 0x2c, 0x3d, 0x28, + 0x1d, 0x10, 0xd9, 0xad, 0x8b, 0xec, 0x66, 0x29, 0xd6, 0xb3, 0xe2, 0xdd, 0xfa, 0x77, 0xbf, 0x6f, + 0x1a, 0x3f, 0xf2, 0xe7, 0x37, 0xfe, 0x7c, 0xf3, 0xc7, 0xe6, 0x6b, 0xa3, 0x82, 0x38, 0x7c, 0x3e, + 0xfc, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x37, 0x13, 0x7b, 0x18, 0x0b, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index 20b7ba043..dbef8881d 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -41,7 +41,7 @@ message DeviceActivationResponse { // The Router service provides pure network functionality service Router { // Gateway streams status messages to Router - rpc GatewayStatus(stream gateway.StatusMessage) returns (api.Ack); + rpc GatewayStatus(stream gateway.Status) returns (api.Ack); // Gateway streams uplink messages to Router rpc Uplink(stream UplinkMessage) returns (api.Ack); @@ -80,14 +80,14 @@ message GatewayStatusRequest { // message GatewayStatusResponse is the response to the GatewayStatusRequest message GatewayStatusResponse { - gateway.StatusMessage last_status = 1; + gateway.Status last_status = 1; } // message StatusRequest is used to request the status of this Router message StatusRequest {} -// message StatusResponse is the response to the StatusRequest -message StatusResponse { +// message Status is the response to the StatusRequest +message Status { // Gateways api.Rates gateway_status = 1; uint32 active_gateways = 4; @@ -122,5 +122,5 @@ service RouterManager { rpc GatewayStatus(GatewayStatusRequest) returns (GatewayStatusResponse); // Network operator requests Router status - rpc Status(StatusRequest) returns (StatusResponse); + rpc GetStatus(StatusRequest) returns (Status); } diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index e00c7b738..24b5fa40c 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -11,9 +11,9 @@ import ( // StatusStore is a database for setting and retrieving the latest gateway status type StatusStore interface { // Insert or Update the status - Update(status *pb_gateway.StatusMessage) error + Update(status *pb_gateway.Status) error // Get the last status - Get() (*pb_gateway.StatusMessage, error) + Get() (*pb_gateway.Status, error) } // NewStatusStore creates a new in-memory status store @@ -22,19 +22,19 @@ func NewStatusStore() StatusStore { } type statusStore struct { - lastStatus *pb_gateway.StatusMessage + lastStatus *pb_gateway.Status } -func (s *statusStore) Update(status *pb_gateway.StatusMessage) error { +func (s *statusStore) Update(status *pb_gateway.Status) error { s.lastStatus = status return nil } -func (s *statusStore) Get() (*pb_gateway.StatusMessage, error) { +func (s *statusStore) Get() (*pb_gateway.Status, error) { if s.lastStatus != nil { return s.lastStatus, nil } - return &pb_gateway.StatusMessage{}, nil + return &pb_gateway.Status{}, nil } // NewRedisStatusStore creates a new Redis-based status store @@ -50,7 +50,7 @@ type redisStatusStore struct { key string } -func (s *redisStatusStore) Update(status *pb_gateway.StatusMessage) error { +func (s *redisStatusStore) Update(status *pb_gateway.Status) error { m, err := status.ToStringStringMap(pb_gateway.StatusMessageProperties...) if err != nil { return err @@ -58,8 +58,8 @@ func (s *redisStatusStore) Update(status *pb_gateway.StatusMessage) error { return s.client.HMSetMap(s.key, m).Err() } -func (s *redisStatusStore) Get() (*pb_gateway.StatusMessage, error) { - status := &pb_gateway.StatusMessage{} +func (s *redisStatusStore) Get() (*pb_gateway.Status, error) { + status := &pb_gateway.Status{} res, err := s.client.HGetAllMap(s.key).Result() if err != nil { return nil, err diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go index 202666722..e20f1532f 100644 --- a/core/router/gateway/status_test.go +++ b/core/router/gateway/status_test.go @@ -36,10 +36,10 @@ func TestStatusGetUpsert(t *testing.T) { status, err := store.Get() a.So(err, ShouldBeNil) a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, pb_gateway.StatusMessage{}) + a.So(*status, ShouldResemble, pb_gateway.Status{}) // Update -> expect no error - statusMessage := &pb_gateway.StatusMessage{Description: "Fake Gateway"} + statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} err = store.Update(statusMessage) a.So(err, ShouldBeNil) @@ -64,10 +64,10 @@ func TestRedisStatusGetUpsert(t *testing.T) { status, err := store.Get() a.So(err, ShouldBeNil) a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, pb_gateway.StatusMessage{}) + a.So(*status, ShouldResemble, pb_gateway.Status{}) // Update -> expect no error - statusMessage := &pb_gateway.StatusMessage{Description: "Fake Gateway"} + statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} err = store.Update(statusMessage) a.So(err, ShouldBeNil) diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index 076b5b01b..4f6c0b3b4 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -5,7 +5,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.StatusMessage) error { +func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error { gateway := r.getGateway(gatewayEUI) return gateway.Status.Update(status) } diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index fa111696c..929d414e2 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -18,7 +18,7 @@ func TestHandleGatewayStatus(t *testing.T) { } // Handle - statusMessage := &pb_gateway.StatusMessage{Description: "Fake Gateway"} + statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} err := router.HandleGatewayStatus(eui, statusMessage) a.So(err, ShouldBeNil) diff --git a/core/router/router.go b/core/router/router.go index ec7a32977..08f7722c5 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -20,7 +20,7 @@ import ( // Router component type Router interface { // Handle a status message from a gateway - HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.StatusMessage) error + HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error // Handle an uplink message from a gateway HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error // Handle a downlink message From af5190736736ccccf1aa526dff03ebbedc5484b5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 8 May 2016 16:52:12 +0200 Subject: [PATCH 1430/2266] Add Gateway Schedule --- core/router/gateway/schedule.go | 110 ++++++++++++++++++ core/router/gateway/schedule_datastructure.go | 89 ++++++++++++++ core/router/gateway/schedule_test.go | 106 +++++++++++++++++ 3 files changed, 305 insertions(+) create mode 100644 core/router/gateway/schedule.go create mode 100644 core/router/gateway/schedule_datastructure.go create mode 100644 core/router/gateway/schedule_test.go diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go new file mode 100644 index 000000000..f8179f72a --- /dev/null +++ b/core/router/gateway/schedule.go @@ -0,0 +1,110 @@ +package gateway + +import ( + "container/heap" + "errors" + "sync" + "sync/atomic" + "time" + + router_pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/random" +) + +// Schedule is used to schedule downlink transmissions +type Schedule interface { + // Synchronize the schedule with the gateway timestamp (in microseconds) + Sync(timestamp uint32) + // Get an "option" on a transmission slot at timestamp for the maximum duration of length (in nanoseconds) + GetOption(timestamp uint32, length uint32) (id string, score uint) + // Schedule a transmission on a slot + Schedule(id string, downlink *router_pb.DownlinkMessage) error + // TODO: Add some way to retrieve the next scheduled item (preferably at the right time) +} + +// NewSchedule creates a new Schedule +func NewSchedule() Schedule { + return &schedule{ + queue: NewDownlinkQueue(), + byID: make(map[string]*scheduledItem), + } +} + +type schedule struct { + sync.RWMutex + active bool + offset int64 + queue *downlinkQueue + byID map[string]*scheduledItem + // some schedule datastructure +} + +const uintmax = 1 << 32 + +func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint) { + s.RLock() + snapshot := s.queue.Snapshot() + s.RUnlock() + for _, item := range snapshot { + scheduledFrom := uint64(item.timestamp) % uintmax + scheduledTo := scheduledFrom + uint64(item.length) + from := uint64(timestamp) + to := from + uint64(length) + + if scheduledTo > uintmax || to > uintmax { + if scheduledTo-uintmax < from || scheduledFrom > to-uintmax { + continue + } + } else if scheduledTo < from || scheduledFrom > to { + continue + } + + if item.payload == nil { + conflicts++ + } else { + conflicts += 10 // TODO: Configure this + } + } + return +} + +func (s *schedule) realtime(timestamp uint32) (t time.Time) { + offset := atomic.LoadInt64(&s.offset) + t = time.Unix(0, 0) + t = t.Add(time.Duration(int64(timestamp)*1000 + offset)) + if t.Before(time.Now()) { + t = t.Add(time.Duration(int64(1<<32) * 1000)) + } + return +} + +func (s *schedule) Sync(timestamp uint32) { + atomic.StoreInt64(&s.offset, time.Now().UnixNano()-int64(timestamp)*1000) +} + +func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) { + id = random.String(32) + score = s.getConflicts(timestamp, length) + item := &scheduledItem{ + id: id, + time: s.realtime(timestamp), + timestamp: timestamp, + length: length, + score: score, + } + s.Lock() + defer s.Unlock() + heap.Push(s.queue, item) + s.byID[id] = item + return id, score +} + +func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { + s.RLock() + defer s.RUnlock() + if item, ok := s.byID[id]; ok { + item.payload = downlink + return nil + } + return errors.New("ID not found") +} diff --git a/core/router/gateway/schedule_datastructure.go b/core/router/gateway/schedule_datastructure.go new file mode 100644 index 000000000..2dab27096 --- /dev/null +++ b/core/router/gateway/schedule_datastructure.go @@ -0,0 +1,89 @@ +package gateway + +import ( + "container/heap" + "sync" + "time" + + router_pb "github.com/TheThingsNetwork/ttn/api/router" +) + +type scheduledItem struct { + id string + time time.Time + timestamp uint32 + length uint32 + score uint + payload *router_pb.DownlinkMessage +} + +// A downlinkQueue implements heap.Interface and holds scheduledItems. +type downlinkQueue struct { + sync.RWMutex + items []*scheduledItem +} + +func NewDownlinkQueue(items ...*scheduledItem) *downlinkQueue { + dq := &downlinkQueue{ + items: items, + } + heap.Init(dq) + return dq +} + +// Len is used by heap.Interface +func (dq *downlinkQueue) Len() int { return len(dq.items) } + +// Less is used by heap.Interface +func (dq *downlinkQueue) Less(i, j int) bool { + return dq.items[i].time.Before(dq.items[j].time) +} + +// Swap is used by heap.Interface. +func (dq *downlinkQueue) Swap(i, j int) { + if len(dq.items) == 0 { + return + } + dq.Lock() + defer dq.Unlock() + dq.items[i], dq.items[j] = dq.items[j], dq.items[i] +} + +// Push is used by heap.Interface +func (dq *downlinkQueue) Push(x interface{}) { + item := x.(*scheduledItem) + dq.Lock() + defer dq.Unlock() + dq.items = append(dq.items, item) +} + +// Pop is used by heap.Interface. +func (dq *downlinkQueue) Pop() interface{} { + dq.Lock() + defer dq.Unlock() + n := len(dq.items) + if n == 0 { + return nil + } + item := dq.items[n-1] + dq.items = dq.items[0 : n-1] + return item +} + +// Snapshot returns a snapshot of the downlinkQueue +func (dq *downlinkQueue) Snapshot() []*scheduledItem { + dq.RLock() + defer dq.RUnlock() + return dq.items +} + +// Peek returns the next item in the queue +func (dq *downlinkQueue) Peek() interface{} { + snapshot := dq.Snapshot() + n := len(snapshot) + if n == 0 { + return nil + } + item := snapshot[n-1] + return item +} diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go new file mode 100644 index 000000000..72cc15804 --- /dev/null +++ b/core/router/gateway/schedule_test.go @@ -0,0 +1,106 @@ +package gateway + +import ( + "testing" + "time" + + router_pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/smartystreets/assertions" +) + +const almostEqual = time.Millisecond + +func TestScheduleSync(t *testing.T) { + a := New(t) + s := &schedule{} + s.Sync(0) + a.So(s.offset, ShouldAlmostEqual, time.Now().UnixNano(), almostEqual) + + s.Sync(1000) + a.So(s.offset, ShouldAlmostEqual, time.Now().UnixNano()-1000*1000, almostEqual) +} + +func TestScheduleRealtime(t *testing.T) { + a := New(t) + s := &schedule{} + s.Sync(0) + tm := s.realtime(10) + a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+10*1000, almostEqual) + + s.Sync(1000) + tm = s.realtime(1010) + a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+10*1000, almostEqual) + + // Don't go back in time when uint32 overflows + s.Sync(uintmax - 1) + tm = s.realtime(10) + a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+9*1000, almostEqual) +} + +func TestScheduleGetConflicts(t *testing.T) { + a := New(t) + + // Test without overflow + s := &schedule{ + queue: NewDownlinkQueue( + &scheduledItem{timestamp: 5, length: 15}, + &scheduledItem{timestamp: 25, length: 10}, + &scheduledItem{timestamp: 55, length: 5}, + &scheduledItem{timestamp: 70, length: 20}, + &scheduledItem{timestamp: 95, length: 5}, + &scheduledItem{timestamp: 105, length: 10}, + ), + } + a.So(s.getConflicts(0, 10), ShouldEqual, 1) + a.So(s.getConflicts(15, 15), ShouldEqual, 2) + a.So(s.getConflicts(40, 5), ShouldEqual, 0) + a.So(s.getConflicts(50, 15), ShouldEqual, 1) + a.So(s.getConflicts(75, 5), ShouldEqual, 1) + a.So(s.getConflicts(85, 25), ShouldEqual, 3) + + // Test with overflow (already scheduled) + s = &schedule{ + queue: NewDownlinkQueue( + &scheduledItem{timestamp: 1<<32 - 1, length: 20}, + ), + } + a.So(s.getConflicts(0, 20), ShouldEqual, 1) + a.So(s.getConflicts(25, 5), ShouldEqual, 0) + + // Test with overflow (to schedule) + s = &schedule{ + queue: NewDownlinkQueue( + &scheduledItem{timestamp: 10, length: 20}, + ), + } + a.So(s.getConflicts(1<<32-1, 5), ShouldEqual, 0) + a.So(s.getConflicts(1<<32-1, 20), ShouldEqual, 1) +} + +func TestScheduleGetOption(t *testing.T) { + a := New(t) + s := NewSchedule().(*schedule) + + s.Sync(0) + _, conflicts := s.GetOption(100, 100) + a.So(conflicts, ShouldEqual, 0) + _, conflicts = s.GetOption(50, 100) + a.So(conflicts, ShouldEqual, 1) +} + +func TestScheduleSchedule(t *testing.T) { + a := New(t) + s := NewSchedule().(*schedule) + + s.Sync(0) + + err := s.Schedule("random", &router_pb.DownlinkMessage{}) + a.So(err, ShouldNotBeNil) + + id, conflicts := s.GetOption(100, 100) + err = s.Schedule(id, &router_pb.DownlinkMessage{}) + a.So(err, ShouldBeNil) + + _, conflicts = s.GetOption(50, 100) + a.So(conflicts, ShouldEqual, 10) +} From 962952e8ee087b9eb15acc9e918770b38b4ba766 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 8 May 2016 16:53:47 +0200 Subject: [PATCH 1431/2266] Move DialOptions to API --- api/api.go | 9 +++++++++ core/discovery/broker_discovery.go | 3 ++- core/discovery/discovery.go | 7 ------- 3 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 api/api.go diff --git a/api/api.go b/api/api.go new file mode 100644 index 000000000..3bc168a03 --- /dev/null +++ b/api/api.go @@ -0,0 +1,9 @@ +package api + +import "google.golang.org/grpc" + +// DialOptions are the gRPC dial options for discovery calls +// TODO: disable insecure connections +var DialOptions = []grpc.DialOption{ + grpc.WithInsecure(), +} diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 187a5835f..bad99d1d8 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc" + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core/types" "golang.org/x/net/context" @@ -34,7 +35,7 @@ func NewBrokerDiscovery(serverAddress string) BrokerDiscovery { func (d *brokerDiscovery) refreshCache() error { // Connect to the server - conn, err := grpc.Dial(d.serverAddress, DialOptions...) + conn, err := grpc.Dial(d.serverAddress, api.DialOptions...) if err != nil { return err } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 9afa82550..a2ffe06f1 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -6,15 +6,8 @@ import ( "sync" pb "github.com/TheThingsNetwork/ttn/api/discovery" - "google.golang.org/grpc" ) -// DialOptions are the gRPC dial options for discovery calls -// TODO: disable insecure connections -var DialOptions = []grpc.DialOption{ - grpc.WithInsecure(), -} - // Discovery specifies the interface for the TTN Service Discovery component type Discovery interface { Announce(announcement *pb.Announcement) error From 4ca9a3beaa1aed1f082ee5dd4d19e48485108393 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 9 May 2016 12:59:42 +0200 Subject: [PATCH 1432/2266] Store Gateway Region --- api/gateway/gateway.pb.go | 104 ++++++++++++++++++++--------- api/gateway/gateway.proto | 1 + api/gateway/status_message.go | 5 ++ api/gateway/status_message_test.go | 2 + 4 files changed, 80 insertions(+), 32 deletions(-) diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index ccbc04fba..3566d11a7 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -242,6 +242,7 @@ type Status struct { Platform string `protobuf:"bytes,12,opt,name=platform,proto3" json:"platform,omitempty"` ContactEmail string `protobuf:"bytes,13,opt,name=contact_email,json=contactEmail,proto3" json:"contact_email,omitempty"` Description string `protobuf:"bytes,14,opt,name=description,proto3" json:"description,omitempty"` + Region string `protobuf:"bytes,15,opt,name=region,proto3" json:"region,omitempty"` Gps *GPSMetadata `protobuf:"bytes,21,opt,name=gps" json:"gps,omitempty"` Rtt uint32 `protobuf:"varint,31,opt,name=rtt,proto3" json:"rtt,omitempty"` RxIn uint32 `protobuf:"varint,41,opt,name=rx_in,json=rxIn,proto3" json:"rx_in,omitempty"` @@ -467,6 +468,12 @@ func (m *Status) MarshalTo(data []byte) (int, error) { i = encodeVarintGateway(data, i, uint64(len(m.Description))) i += copy(data[i:], m.Description) } + if len(m.Region) > 0 { + data[i] = 0x7a + i++ + i = encodeVarintGateway(data, i, uint64(len(m.Region))) + i += copy(data[i:], m.Region) + } if m.Gps != nil { data[i] = 0xaa i++ @@ -640,6 +647,10 @@ func (m *Status) Size() (n int) { if l > 0 { n += 1 + l + sovGateway(uint64(l)) } + l = len(m.Region) + if l > 0 { + n += 1 + l + sovGateway(uint64(l)) + } if m.Gps != nil { l = m.Gps.Size() n += 2 + l + sovGateway(uint64(l)) @@ -1223,6 +1234,35 @@ func (m *Status) Unmarshal(data []byte) error { } m.Description = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 15: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Region", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Region = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Gps", wireType) @@ -1478,36 +1518,36 @@ var ( ) var fileDescriptorGateway = []byte{ - // 482 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x92, 0xdf, 0x6e, 0xd3, 0x30, - 0x14, 0xc6, 0xd7, 0xa4, 0xdd, 0xe8, 0xe9, 0x3a, 0x26, 0x6f, 0x48, 0xd6, 0x84, 0xc6, 0x54, 0x24, - 0x34, 0xfe, 0xa8, 0x91, 0x80, 0x1b, 0x2e, 0x19, 0x42, 0xc0, 0x05, 0x0c, 0x79, 0xbd, 0x9f, 0xbc, - 0xd4, 0x4d, 0xad, 0x36, 0x76, 0xb0, 0x4f, 0xd4, 0xee, 0x05, 0xb8, 0xe2, 0x01, 0x78, 0x17, 0x5e, - 0x80, 0x4b, 0x1e, 0x01, 0xc1, 0x8b, 0x60, 0xbb, 0x49, 0x5a, 0x4d, 0x42, 0x88, 0x8b, 0x28, 0xe7, - 0xfc, 0xfc, 0xe5, 0x7c, 0xe7, 0xe4, 0x18, 0x5e, 0x64, 0x12, 0xa7, 0xe5, 0xd5, 0x30, 0xd5, 0x79, - 0x32, 0x9a, 0x8a, 0xd1, 0x54, 0xaa, 0xcc, 0x7e, 0x10, 0xb8, 0xd0, 0x66, 0x96, 0x20, 0xaa, 0x84, - 0x17, 0x32, 0xc9, 0x38, 0x8a, 0x05, 0xbf, 0xae, 0xdf, 0xc3, 0xc2, 0x68, 0xd4, 0x64, 0xa7, 0x4a, - 0x8f, 0x5e, 0xfe, 0x4f, 0x0d, 0x2b, 0x72, 0x14, 0xe9, 0xb4, 0x7e, 0xaf, 0x6a, 0x0d, 0x16, 0xd0, - 0x7b, 0xf3, 0xf1, 0xe2, 0xbd, 0x40, 0x3e, 0xe6, 0xc8, 0x09, 0x81, 0x36, 0xca, 0x5c, 0xd0, 0xd6, - 0x49, 0xeb, 0x34, 0x66, 0x21, 0x26, 0x47, 0x70, 0x6b, 0xce, 0x51, 0x62, 0x39, 0x16, 0x34, 0x72, - 0x3c, 0x62, 0x4d, 0x4e, 0xee, 0x42, 0x77, 0xae, 0x55, 0xb6, 0x3a, 0x8c, 0xc3, 0xe1, 0x1a, 0xf8, - 0x2f, 0xf9, 0xbc, 0xfa, 0xb2, 0xed, 0x0e, 0x3b, 0xac, 0xc9, 0x07, 0x5f, 0x5a, 0x00, 0x6c, 0xd9, - 0x18, 0xbb, 0x42, 0x13, 0x23, 0x3e, 0x95, 0x42, 0xa5, 0xd7, 0xc1, 0xbd, 0xcd, 0xd6, 0xc0, 0xb7, - 0x65, 0xac, 0x95, 0x95, 0x7d, 0x88, 0xc9, 0x3e, 0xc4, 0x56, 0x99, 0xca, 0xd4, 0x87, 0x24, 0x81, - 0x9d, 0x6a, 0x38, 0xda, 0x73, 0xb4, 0xf7, 0xf4, 0x60, 0x58, 0x0f, 0xbb, 0x76, 0x7a, 0xbb, 0xc5, - 0x6a, 0xd5, 0x59, 0x17, 0xea, 0x5f, 0x39, 0xf8, 0xdc, 0x82, 0xdb, 0xa3, 0xe5, 0x2b, 0xad, 0x26, - 0x32, 0x2b, 0x8d, 0x1b, 0x4f, 0xab, 0x7f, 0xf4, 0x74, 0x08, 0x9d, 0x42, 0x2f, 0x84, 0x09, 0x4d, - 0x75, 0xd8, 0x2a, 0x21, 0xcf, 0x6f, 0xf6, 0x40, 0x9b, 0x1e, 0x6e, 0x94, 0xff, 0x4b, 0x23, 0xdf, - 0x22, 0xd8, 0xbe, 0x40, 0x8e, 0xa5, 0xf5, 0xfe, 0x7e, 0x01, 0x16, 0x79, 0x5e, 0x04, 0xff, 0x3e, - 0x5b, 0x83, 0x66, 0x55, 0xd1, 0xc6, 0xaa, 0xf6, 0x20, 0x92, 0x85, 0x33, 0x8e, 0x4f, 0xbb, 0xcc, - 0x45, 0x7e, 0x01, 0x85, 0xdb, 0xd5, 0x44, 0x9b, 0x9c, 0xee, 0x3a, 0x5d, 0x97, 0x35, 0x39, 0xb9, - 0x0f, 0xfd, 0x54, 0x2b, 0xe4, 0x29, 0x5e, 0x8a, 0x9c, 0xcb, 0x39, 0xed, 0x07, 0xc1, 0x6e, 0x05, - 0x5f, 0x7b, 0x46, 0x4e, 0xa0, 0x37, 0x16, 0x36, 0x35, 0xb2, 0xf0, 0x2d, 0xd3, 0xbd, 0x20, 0xd9, - 0x44, 0xe4, 0x01, 0xc4, 0x59, 0x61, 0xe9, 0x9d, 0x30, 0xec, 0xe1, 0xb0, 0xbe, 0xa9, 0x1b, 0x97, - 0x8a, 0x79, 0x81, 0x5f, 0x97, 0x41, 0xa4, 0xf7, 0xc2, 0x18, 0x3e, 0x24, 0x07, 0xd0, 0x31, 0xcb, - 0x4b, 0xa9, 0xe8, 0xc3, 0xc0, 0xda, 0x66, 0xf9, 0x4e, 0x55, 0x50, 0xcf, 0xe8, 0xa3, 0x1a, 0x9e, - 0xcf, 0x3c, 0xc4, 0xa0, 0x7c, 0xbc, 0x82, 0x58, 0x29, 0x31, 0x28, 0x9f, 0xd4, 0xf0, 0x7c, 0x76, - 0xb6, 0xff, 0xfd, 0xd7, 0x71, 0xeb, 0x87, 0x7b, 0x7e, 0xba, 0xe7, 0xeb, 0xef, 0xe3, 0xad, 0xab, - 0xed, 0x70, 0xcf, 0x9f, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x35, 0x0c, 0x13, 0x0c, 0x70, 0x03, - 0x00, 0x00, + // 492 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x52, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0x5e, 0x92, 0xb6, 0xa3, 0xa7, 0xeb, 0x36, 0x79, 0x03, 0x59, 0x13, 0x1a, 0x53, 0x91, 0xd0, + 0xf8, 0x51, 0x23, 0x01, 0x37, 0x5c, 0x32, 0x84, 0x80, 0x0b, 0x18, 0xf2, 0x7a, 0x3f, 0x79, 0xa9, + 0x9b, 0x5a, 0x6d, 0xec, 0xe0, 0x9c, 0xa8, 0xdd, 0x0b, 0x70, 0xc5, 0x03, 0xf0, 0x48, 0x5c, 0x22, + 0xf1, 0x02, 0x08, 0x5e, 0x04, 0xdb, 0x4d, 0xd2, 0x6a, 0x12, 0x42, 0x5c, 0x44, 0x39, 0xe7, 0x3b, + 0x9f, 0xcf, 0xf7, 0x1d, 0x1f, 0xc3, 0x8b, 0x54, 0xe2, 0xb4, 0xbc, 0x1a, 0x26, 0x3a, 0x8b, 0x47, + 0x53, 0x31, 0x9a, 0x4a, 0x95, 0x16, 0x1f, 0x04, 0x2e, 0xb4, 0x99, 0xc5, 0x88, 0x2a, 0xe6, 0xb9, + 0x8c, 0x53, 0x8e, 0x62, 0xc1, 0xaf, 0xeb, 0xff, 0x30, 0x37, 0x1a, 0x35, 0xd9, 0xae, 0xd2, 0xa3, + 0x97, 0xff, 0xd3, 0xa3, 0x10, 0x19, 0x8a, 0x64, 0x5a, 0xff, 0x57, 0xbd, 0x06, 0x0b, 0xe8, 0xbd, + 0xf9, 0x78, 0xf1, 0x5e, 0x20, 0x1f, 0x73, 0xe4, 0x84, 0x40, 0x0b, 0x65, 0x26, 0x68, 0x70, 0x12, + 0x9c, 0x46, 0xcc, 0xc7, 0xe4, 0x08, 0x6e, 0xcd, 0x39, 0x4a, 0x2c, 0xc7, 0x82, 0x86, 0x16, 0x0f, + 0x59, 0x93, 0x93, 0xbb, 0xd0, 0x9d, 0x6b, 0x95, 0xae, 0x8a, 0x91, 0x2f, 0xae, 0x01, 0x77, 0x92, + 0xcf, 0xab, 0x93, 0x2d, 0x5b, 0x6c, 0xb3, 0x26, 0x1f, 0x7c, 0x09, 0x00, 0xd8, 0xb2, 0x11, 0xb6, + 0x8d, 0x26, 0x46, 0x7c, 0x2a, 0x85, 0x4a, 0xae, 0xbd, 0x7a, 0x8b, 0xad, 0x01, 0x67, 0xcb, 0x14, + 0x85, 0xac, 0xe4, 0x7d, 0x4c, 0xf6, 0x21, 0x2a, 0x94, 0xa9, 0x44, 0x5d, 0x48, 0x62, 0xd8, 0xae, + 0x86, 0xa3, 0x3d, 0x8b, 0xf6, 0x9e, 0x1e, 0x0c, 0xeb, 0x61, 0xd7, 0x4a, 0x6f, 0xb7, 0x58, 0xcd, + 0x3a, 0xeb, 0x42, 0x7d, 0x95, 0x83, 0xcf, 0x01, 0xec, 0x8d, 0x96, 0xaf, 0xb4, 0x9a, 0xc8, 0xb4, + 0x34, 0x76, 0x3c, 0xad, 0xfe, 0xe1, 0xe9, 0x10, 0xda, 0xb9, 0x5e, 0x08, 0xe3, 0x4d, 0xb5, 0xd9, + 0x2a, 0x21, 0xcf, 0x6f, 0x7a, 0xa0, 0x8d, 0x87, 0x1b, 0xed, 0xff, 0x62, 0xe4, 0x47, 0x08, 0x9d, + 0x0b, 0xe4, 0x58, 0x16, 0x4e, 0xdf, 0x2d, 0xa0, 0x40, 0x9e, 0xe5, 0x5e, 0xbf, 0xcf, 0xd6, 0x40, + 0xb3, 0xaa, 0x70, 0x63, 0x55, 0xbb, 0x10, 0xca, 0xdc, 0x0a, 0x47, 0xa7, 0x5d, 0x66, 0x23, 0xb7, + 0x80, 0xdc, 0xee, 0x6a, 0xa2, 0x4d, 0x46, 0x77, 0x2c, 0xaf, 0xcb, 0x9a, 0x9c, 0xdc, 0x87, 0x7e, + 0xa2, 0x15, 0xf2, 0x04, 0x2f, 0x45, 0xc6, 0xe5, 0x9c, 0xf6, 0x3d, 0x61, 0xa7, 0x02, 0x5f, 0x3b, + 0x8c, 0x9c, 0x40, 0x6f, 0x2c, 0x8a, 0xc4, 0xc8, 0xdc, 0x59, 0xa6, 0xbb, 0x9e, 0xb2, 0x09, 0x91, + 0x3b, 0xd0, 0x31, 0x22, 0x75, 0xc5, 0x3d, 0x5f, 0xac, 0x32, 0xf2, 0x00, 0xa2, 0x34, 0x2f, 0xe8, + 0x6d, 0x7f, 0x09, 0x87, 0xc3, 0xfa, 0x05, 0x6f, 0x3c, 0x36, 0xe6, 0x08, 0x6e, 0x8d, 0x06, 0x91, + 0xde, 0xf3, 0xe3, 0xb9, 0x90, 0x1c, 0x40, 0xdb, 0x2c, 0x2f, 0xa5, 0xa2, 0x0f, 0x3d, 0xd6, 0x32, + 0xcb, 0x77, 0xaa, 0x02, 0xf5, 0x8c, 0x3e, 0xaa, 0xc1, 0xf3, 0x99, 0x03, 0xd1, 0x33, 0x1f, 0xaf, + 0x40, 0xac, 0x98, 0xe8, 0x99, 0x4f, 0x6a, 0xf0, 0x7c, 0x76, 0xb6, 0xff, 0xed, 0xd7, 0x71, 0xf0, + 0xdd, 0x7e, 0x3f, 0xed, 0xf7, 0xf5, 0xf7, 0xf1, 0xd6, 0x55, 0xc7, 0xbf, 0xff, 0x67, 0x7f, 0x02, + 0x00, 0x00, 0xff, 0xff, 0xa4, 0xf2, 0xc5, 0x82, 0x88, 0x03, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 2271b114f..9e4f9b140 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -38,6 +38,7 @@ message Status { string platform = 12; string contact_email = 13; string description = 14; + string region = 15; GPSMetadata gps = 21; diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go index 7ece5f2a1..48980b14e 100644 --- a/api/gateway/status_message.go +++ b/api/gateway/status_message.go @@ -16,6 +16,7 @@ var StatusMessageProperties = []string{ "platform", "contact_email", "description", + "region", "gps.time", "gps.latitude", "gps.longitude", @@ -65,6 +66,8 @@ func (status *Status) formatProperty(property string) (formatted string, err err formatted = status.ContactEmail case "description": formatted = status.Description + case "region": + formatted = status.Region case "gps.time": if status.Gps != nil { formatted = storage.FormatInt64(status.Gps.Time) @@ -123,6 +126,8 @@ func (status *Status) parseProperty(property string, value string) error { status.ContactEmail = value case "description": status.Description = value + case "region": + status.Region = value case "gps.time": if status.Gps == nil { status.Gps = &GPSMetadata{} diff --git a/api/gateway/status_message_test.go b/api/gateway/status_message_test.go index 3cad8169e..c30556a80 100644 --- a/api/gateway/status_message_test.go +++ b/api/gateway/status_message_test.go @@ -15,6 +15,7 @@ func getStatusMessage() (status *Status, smap map[string]string) { Platform: "The Things Gateway", ContactEmail: "contact@email.net", Description: "Description", + Region: "EU_863_870", Gps: &GPSMetadata{ Time: t, Latitude: 52.3737171, @@ -33,6 +34,7 @@ func getStatusMessage() (status *Status, smap map[string]string) { "platform": "The Things Gateway", "contact_email": "contact@email.net", "description": "Description", + "region": "EU_863_870", "gps.time": "1462201853428843766", "gps.latitude": "52.37372", "gps.longitude": "4.884567", From 0ab5aa2bdb95ab88100605c8522bc8aec8c782d2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 9 May 2016 15:05:01 +0200 Subject: [PATCH 1433/2266] Remove GatewayID from SubscribeRequest GatewayID is passed in Context --- api/router/router.pb.go | 153 +++++++++++++++------------------------- api/router/router.proto | 4 +- 2 files changed, 57 insertions(+), 100 deletions(-) diff --git a/api/router/router.pb.go b/api/router/router.pb.go index babd3e1e2..54c51fee5 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -52,7 +52,6 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 type SubscribeRequest struct { - GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` } func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} } @@ -806,12 +805,6 @@ func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.GatewayId) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) - } return i, nil } @@ -1316,10 +1309,6 @@ func encodeVarintRouter(data []byte, offset int, v uint64) int { func (m *SubscribeRequest) Size() (n int) { var l int _ = l - l = len(m.GatewayId) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } return n } @@ -1544,35 +1533,6 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { return fmt.Errorf("proto: SubscribeRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.GatewayId = string(data[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRouter(data[iNdEx:]) @@ -3135,61 +3095,60 @@ var ( ) var fileDescriptorRouter = []byte{ - // 894 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0xc7, 0x05, 0x65, 0x93, 0x97, 0xa4, 0x49, 0x66, 0x93, 0xae, 0x89, 0xd8, 0xb6, 0xf2, 0x01, - 0x96, 0x3f, 0x9b, 0x6c, 0x83, 0x56, 0x68, 0xb5, 0xe2, 0x4f, 0x4a, 0xab, 0xaa, 0x12, 0xa9, 0x90, - 0xdb, 0x5e, 0xb8, 0x44, 0x13, 0x67, 0xea, 0x5a, 0x49, 0x6d, 0xe3, 0x19, 0xa7, 0xed, 0x37, 0xe1, - 0x43, 0xf0, 0x41, 0x38, 0x20, 0xc4, 0x05, 0x0e, 0x1c, 0x10, 0x82, 0xaf, 0xc0, 0x85, 0x1b, 0x93, - 0xf1, 0xcc, 0xc4, 0x76, 0x1a, 0xd1, 0x02, 0xaa, 0xb4, 0x07, 0xcb, 0x9e, 0xf7, 0x7e, 0xf3, 0x7b, - 0xef, 0xf9, 0xfd, 0x99, 0x81, 0x8f, 0x5c, 0x8f, 0x9d, 0xc7, 0xa3, 0x8e, 0x13, 0x5c, 0x74, 0x4f, - 0xce, 0xc9, 0xc9, 0xb9, 0xe7, 0xbb, 0xf4, 0x88, 0xb0, 0xcb, 0x20, 0x9a, 0x74, 0x19, 0xf3, 0xbb, - 0x38, 0xf4, 0xba, 0x51, 0x10, 0x33, 0x12, 0xc9, 0x57, 0x27, 0x8c, 0x02, 0x16, 0xa0, 0x42, 0xb2, - 0x6a, 0x3f, 0x4d, 0x11, 0xb8, 0x81, 0x1b, 0x74, 0x85, 0x7a, 0x14, 0x9f, 0x89, 0x95, 0x58, 0x88, - 0xaf, 0x64, 0x5b, 0x06, 0xbe, 0xd2, 0x1e, 0x7f, 0x24, 0xfc, 0xe5, 0x6d, 0xe0, 0x02, 0xea, 0x04, - 0x53, 0xfd, 0x21, 0x37, 0xbf, 0xb8, 0xcd, 0x66, 0x17, 0x33, 0x72, 0x89, 0xaf, 0xd5, 0x3b, 0xd9, - 0x6a, 0xed, 0x40, 0xfd, 0x38, 0x1e, 0x51, 0x27, 0xf2, 0x46, 0xc4, 0x26, 0x5f, 0xc7, 0x84, 0x32, - 0xf4, 0x18, 0x40, 0x82, 0x86, 0xde, 0xd8, 0x34, 0xb6, 0x8d, 0x27, 0x25, 0xbb, 0x24, 0x25, 0x87, - 0x63, 0xeb, 0x5b, 0x03, 0xaa, 0xa7, 0xe1, 0xd4, 0xf3, 0x27, 0x03, 0x42, 0x29, 0x76, 0x09, 0x32, - 0xe1, 0x41, 0x88, 0xaf, 0xa7, 0x01, 0x4e, 0xd0, 0x15, 0x5b, 0x2d, 0x51, 0x1f, 0x1a, 0xca, 0xd7, - 0xe1, 0x05, 0x61, 0x78, 0x8c, 0x19, 0x36, 0xcb, 0x1c, 0x53, 0xee, 0x35, 0x3b, 0x3a, 0x0a, 0xfb, - 0x6a, 0x20, 0x75, 0x76, 0x5d, 0x09, 0x95, 0x04, 0x7d, 0x02, 0x75, 0xe5, 0x8d, 0x66, 0xa8, 0x08, - 0x86, 0x87, 0x1d, 0x15, 0x4b, 0x8a, 0xa0, 0x26, 0x65, 0x4a, 0x60, 0x7d, 0x6f, 0x40, 0x6d, 0x2f, - 0xb8, 0xf4, 0x6f, 0xe7, 0xf0, 0x97, 0xb0, 0xa1, 0x1d, 0x76, 0x02, 0xff, 0xcc, 0x73, 0xe3, 0x08, - 0x33, 0x2f, 0xf0, 0xa5, 0xd7, 0x6f, 0x2e, 0xbc, 0x3e, 0xb9, 0xfa, 0x3c, 0x0d, 0xb0, 0x5b, 0x4a, - 0x93, 0x11, 0xa3, 0x01, 0xb4, 0x94, 0xff, 0x59, 0xc2, 0x24, 0x08, 0x53, 0x07, 0x91, 0xe7, 0x6b, - 0x4a, 0x45, 0x46, 0x6a, 0xfd, 0xbc, 0x06, 0x8f, 0xf6, 0xc8, 0xcc, 0x73, 0x48, 0xdf, 0x61, 0xde, - 0x2c, 0x81, 0xca, 0xc4, 0xad, 0x0e, 0xeb, 0x08, 0x1e, 0x8c, 0xc9, 0x6c, 0x48, 0x62, 0x4f, 0xc4, - 0x51, 0xd9, 0x7d, 0xfe, 0xcb, 0xaf, 0x5b, 0x3b, 0xff, 0x54, 0x36, 0x4e, 0x10, 0x91, 0x2e, 0xbb, - 0x0e, 0x09, 0xed, 0x70, 0x93, 0xfb, 0xa7, 0x87, 0x76, 0x81, 0xb3, 0xec, 0xc7, 0xde, 0x9c, 0x0f, - 0x87, 0xa1, 0xe0, 0xab, 0xfc, 0x2b, 0xbe, 0x7e, 0x18, 0x0a, 0x3e, 0xce, 0x32, 0xe7, 0xbb, 0xb1, - 0x4e, 0x5a, 0xff, 0xb9, 0x4e, 0x36, 0xee, 0x50, 0x27, 0x3f, 0x19, 0x60, 0x2e, 0xff, 0x58, 0x1a, - 0x06, 0x3e, 0x7d, 0xa5, 0x0b, 0xa6, 0x01, 0xb5, 0x83, 0x44, 0x4e, 0x65, 0x9d, 0x58, 0x3e, 0xd4, - 0x17, 0x22, 0x19, 0xe1, 0x57, 0x50, 0x5e, 0x34, 0x3d, 0xe5, 0x51, 0xbe, 0xce, 0xb3, 0xfa, 0x82, - 0x67, 0xf5, 0xf9, 0x1d, 0xb2, 0x2a, 0x59, 0xe7, 0x99, 0x05, 0x3d, 0x30, 0xa8, 0xc5, 0x60, 0xc3, - 0x26, 0xae, 0x47, 0xf9, 0x18, 0x95, 0x08, 0x55, 0xb1, 0x29, 0xab, 0xf3, 0x5a, 0x12, 0xff, 0xf6, - 0xff, 0xb0, 0xca, 0x6b, 0xca, 0x9a, 0x81, 0x79, 0xea, 0x47, 0xf7, 0x6f, 0x37, 0x82, 0xa6, 0xd4, - 0x1c, 0x33, 0xcc, 0x62, 0x7a, 0x1f, 0x36, 0x0f, 0xa1, 0x95, 0xb3, 0x29, 0xd3, 0xfa, 0x0c, 0xca, - 0x53, 0x4c, 0xd9, 0x90, 0x0a, 0xb1, 0x30, 0x5a, 0xee, 0xd5, 0x74, 0x09, 0x49, 0x34, 0xcc, 0x31, - 0xc9, 0xb7, 0x55, 0x83, 0x6a, 0xc6, 0x6f, 0xeb, 0xaf, 0x35, 0x28, 0x24, 0x12, 0xb4, 0x03, 0xeb, - 0x2a, 0x84, 0x0c, 0x21, 0x74, 0xe6, 0x27, 0x99, 0xcd, 0x55, 0xd4, 0xae, 0xba, 0x69, 0x47, 0xd0, - 0x3b, 0x50, 0xc3, 0xf3, 0x7e, 0x22, 0x43, 0x29, 0xa7, 0xe6, 0x1b, 0x7c, 0x4f, 0xd5, 0x5e, 0x4f, - 0xc4, 0xaa, 0x10, 0x91, 0x05, 0x85, 0x58, 0x9c, 0x2a, 0xb2, 0x71, 0xd2, 0x9c, 0x52, 0x83, 0xde, - 0x86, 0xe2, 0x58, 0x8e, 0x72, 0x39, 0x1d, 0xd2, 0x28, 0xad, 0x43, 0x1f, 0x40, 0x19, 0xeb, 0x26, - 0xa6, 0xe6, 0xd6, 0x12, 0x34, 0xad, 0x46, 0x1f, 0x43, 0x33, 0xb5, 0x1c, 0x62, 0xc7, 0x21, 0x21, - 0x23, 0x63, 0x73, 0x7b, 0x69, 0xdb, 0xc3, 0x14, 0xae, 0x2f, 0x61, 0xe8, 0x29, 0x20, 0xde, 0xa7, - 0x3e, 0x71, 0xf8, 0x62, 0x11, 0xe4, 0xbb, 0x22, 0xc8, 0x86, 0xd6, 0xe8, 0x38, 0xdf, 0x87, 0x85, - 0x70, 0x38, 0x8a, 0x82, 0x09, 0x89, 0xa8, 0xf9, 0x9e, 0x40, 0xd7, 0xb5, 0x62, 0x37, 0x91, 0xf7, - 0xfe, 0x34, 0xa0, 0x60, 0x8b, 0xfb, 0x07, 0x8f, 0xa9, 0x9a, 0x49, 0x31, 0xca, 0x67, 0xb1, 0x5d, - 0x14, 0x9e, 0xf6, 0x9d, 0xc9, 0x13, 0x83, 0x5b, 0x29, 0x24, 0x67, 0x34, 0x6a, 0x75, 0xe4, 0x75, - 0x26, 0x73, 0x66, 0x67, 0xc0, 0x9f, 0x41, 0x49, 0x5f, 0x02, 0x90, 0xa9, 0xf0, 0xf9, 0x7b, 0x41, - 0xfb, 0x91, 0xd2, 0xe4, 0x8e, 0xd3, 0x67, 0x06, 0x9f, 0x59, 0x45, 0x39, 0x35, 0x09, 0xda, 0xd2, - 0xb0, 0x9b, 0x8f, 0xa9, 0xf6, 0xf6, 0x6a, 0x40, 0x52, 0xb5, 0xbd, 0x1f, 0xd6, 0xa0, 0x9a, 0x84, - 0x3d, 0xc0, 0x3e, 0xb7, 0x10, 0xf1, 0x1c, 0x15, 0xf5, 0x1f, 0xd4, 0x7e, 0xe4, 0xe6, 0x5a, 0xdb, - 0x5c, 0x56, 0xc8, 0x36, 0x78, 0x09, 0xb5, 0xdc, 0x04, 0x42, 0x9b, 0x0a, 0x7c, 0xf3, 0x68, 0x5a, - 0xfc, 0x20, 0xf4, 0x29, 0x34, 0x96, 0x06, 0x09, 0xd2, 0x41, 0xac, 0x9a, 0x31, 0x29, 0x82, 0x2f, - 0xf2, 0xa9, 0x7b, 0x2b, 0xe7, 0x68, 0xa6, 0xe1, 0xda, 0x8f, 0x57, 0x68, 0x65, 0x2c, 0x3d, 0x28, - 0x1d, 0x10, 0xd9, 0xad, 0x8b, 0xec, 0x66, 0x29, 0xd6, 0xb3, 0xe2, 0xdd, 0xfa, 0x77, 0xbf, 0x6f, - 0x1a, 0x3f, 0xf2, 0xe7, 0x37, 0xfe, 0x7c, 0xf3, 0xc7, 0xe6, 0x6b, 0xa3, 0x82, 0x38, 0x7c, 0x3e, - 0xfc, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x1d, 0x37, 0x13, 0x7b, 0x18, 0x0b, 0x00, 0x00, + // 880 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4d, 0x6f, 0xf3, 0x44, + 0x10, 0xc6, 0x05, 0xa5, 0xe9, 0x24, 0x69, 0x92, 0x6d, 0xd2, 0x9a, 0x08, 0xda, 0xca, 0x07, 0x28, + 0x1f, 0x4d, 0xda, 0xa0, 0x0a, 0x55, 0x15, 0x1f, 0x29, 0xad, 0xaa, 0x4a, 0xa4, 0x42, 0x6e, 0x7b, + 0xe1, 0x12, 0x6d, 0x9c, 0xad, 0x6b, 0x25, 0xb5, 0x8d, 0x77, 0x9d, 0xb6, 0xff, 0x84, 0x1f, 0xc1, + 0x0f, 0xe1, 0x80, 0x10, 0x17, 0x38, 0x70, 0x40, 0x08, 0xfe, 0x02, 0x17, 0x6e, 0x6c, 0xd6, 0xbb, + 0x8e, 0xed, 0x34, 0xd0, 0xf2, 0xbe, 0xaa, 0xf4, 0x1e, 0xac, 0x78, 0x67, 0x9e, 0x7d, 0x66, 0xc6, + 0xf3, 0x15, 0xf8, 0xd8, 0x76, 0xd8, 0x75, 0xd8, 0x6f, 0x5a, 0xde, 0x4d, 0xeb, 0xe2, 0x9a, 0x5c, + 0x5c, 0x3b, 0xae, 0x4d, 0xcf, 0x08, 0xbb, 0xf5, 0x82, 0x61, 0x8b, 0x31, 0xb7, 0x85, 0x7d, 0xa7, + 0x15, 0x78, 0x21, 0x23, 0x81, 0xfc, 0x69, 0xfa, 0x81, 0xc7, 0x3c, 0x94, 0x8b, 0x4e, 0x8d, 0xed, + 0x04, 0x81, 0xed, 0xd9, 0x5e, 0x4b, 0xa8, 0xfb, 0xe1, 0x95, 0x38, 0x89, 0x83, 0x78, 0x8b, 0xae, + 0xa5, 0xe0, 0x73, 0xed, 0xf1, 0x47, 0xc2, 0x0f, 0x1e, 0x03, 0x17, 0x50, 0xcb, 0x1b, 0xc5, 0x2f, + 0xf2, 0xf2, 0xfe, 0x63, 0x2e, 0xdb, 0x98, 0x91, 0x5b, 0x7c, 0xaf, 0x7e, 0xa3, 0xab, 0x06, 0x82, + 0xca, 0x79, 0xd8, 0xa7, 0x56, 0xe0, 0xf4, 0x89, 0x49, 0xbe, 0x09, 0x09, 0x65, 0xc6, 0x77, 0x1a, + 0x94, 0x2e, 0xfd, 0x91, 0xe3, 0x0e, 0xbb, 0x84, 0x52, 0x6c, 0x13, 0xa4, 0xc3, 0xa2, 0x8f, 0xef, + 0x47, 0x1e, 0x1e, 0xe8, 0xda, 0xa6, 0xb6, 0x55, 0x34, 0xd5, 0x11, 0x75, 0xa0, 0xaa, 0x9c, 0xe9, + 0xdd, 0x10, 0x86, 0x07, 0x98, 0x61, 0xbd, 0xc0, 0x31, 0x85, 0x76, 0xad, 0x19, 0xbb, 0x69, 0xde, + 0x75, 0xa5, 0xce, 0xac, 0x28, 0xa1, 0x92, 0xa0, 0x4f, 0xa1, 0x22, 0x7d, 0x9a, 0x32, 0x14, 0x05, + 0xc3, 0x4a, 0x53, 0x39, 0x9b, 0x20, 0x28, 0x4b, 0x99, 0x12, 0x18, 0x3f, 0x68, 0x50, 0x3e, 0xf2, + 0x6e, 0xdd, 0xc7, 0x39, 0xfc, 0x15, 0xac, 0xc6, 0x0e, 0x5b, 0x9e, 0x7b, 0xe5, 0xd8, 0x61, 0x80, + 0x99, 0xe3, 0xb9, 0xd2, 0xeb, 0x37, 0xa7, 0x5e, 0x5f, 0xdc, 0x7d, 0x91, 0x04, 0x98, 0x75, 0xa5, + 0x49, 0x89, 0x51, 0x17, 0xea, 0xca, 0xff, 0x34, 0x61, 0x14, 0x84, 0x1e, 0x07, 0x91, 0xe5, 0xab, + 0x49, 0x45, 0x4a, 0x6a, 0xfc, 0xb2, 0x00, 0x6b, 0x47, 0x64, 0xec, 0x58, 0xa4, 0x63, 0x31, 0x67, + 0x1c, 0x41, 0xa3, 0xcc, 0xfc, 0x4b, 0x58, 0x67, 0xb0, 0x38, 0x20, 0xe3, 0x1e, 0x09, 0x1d, 0x11, + 0x47, 0xf1, 0x70, 0xef, 0xd7, 0xdf, 0x36, 0x76, 0xff, 0xab, 0x2e, 0x2c, 0x2f, 0x20, 0x2d, 0x76, + 0xef, 0x13, 0xda, 0xe4, 0x26, 0x8f, 0x2f, 0x4f, 0xcd, 0x1c, 0x67, 0x39, 0x0e, 0x9d, 0x09, 0x1f, + 0xf6, 0x7d, 0xc1, 0x57, 0xfc, 0x5f, 0x7c, 0x1d, 0xdf, 0x17, 0x7c, 0x9c, 0x65, 0xc2, 0xf7, 0x60, + 0x9d, 0xd4, 0x5f, 0xb8, 0x4e, 0x56, 0x9f, 0x50, 0x27, 0x3f, 0x6b, 0xa0, 0xcf, 0x7e, 0x58, 0xea, + 0x7b, 0x2e, 0x7d, 0xa5, 0x0b, 0xa6, 0x0a, 0xe5, 0x93, 0x48, 0x4e, 0x55, 0x07, 0xbb, 0x50, 0x99, + 0x8a, 0x64, 0x84, 0x5f, 0x43, 0x41, 0x59, 0x75, 0x06, 0x94, 0x47, 0xf9, 0x3a, 0xcf, 0xea, 0x3e, + 0xcf, 0xea, 0xde, 0x13, 0xb2, 0x2a, 0x59, 0x27, 0x99, 0x05, 0xc9, 0x76, 0x3a, 0xa0, 0x06, 0x83, + 0x55, 0x93, 0xd8, 0x0e, 0xe5, 0x73, 0x52, 0x22, 0x54, 0xc5, 0x26, 0xac, 0x4e, 0x6a, 0x49, 0x7c, + 0xdb, 0x97, 0x61, 0x95, 0xd7, 0x94, 0x31, 0x06, 0xfd, 0xd2, 0x0d, 0x9e, 0xdf, 0x6e, 0x00, 0x35, + 0xa9, 0x39, 0x67, 0x98, 0x85, 0xf4, 0x39, 0x6c, 0x9e, 0x42, 0x3d, 0x63, 0x53, 0xa6, 0x75, 0x07, + 0x0a, 0x23, 0x4c, 0x59, 0x8f, 0x0a, 0xb1, 0x30, 0x5a, 0x68, 0x97, 0xe3, 0x12, 0x92, 0x68, 0x98, + 0x60, 0xa2, 0x77, 0xa3, 0x0c, 0xa5, 0x94, 0xdf, 0xc6, 0xdf, 0x0b, 0x90, 0x8b, 0x24, 0x68, 0x17, + 0x96, 0x55, 0x08, 0x29, 0x42, 0x68, 0x4e, 0x56, 0x95, 0xc9, 0x55, 0xd4, 0x2c, 0xd9, 0x49, 0x47, + 0xd0, 0xbb, 0x50, 0xc6, 0x93, 0x7e, 0x22, 0x3d, 0x29, 0xa7, 0xfa, 0x1b, 0xfc, 0x4e, 0xc9, 0x5c, + 0x8e, 0xc4, 0xaa, 0x10, 0x91, 0x01, 0xb9, 0x50, 0x6c, 0x15, 0xd9, 0x38, 0x49, 0x4e, 0xa9, 0x41, + 0xef, 0x40, 0x7e, 0x20, 0x47, 0xb9, 0x9c, 0x0e, 0x49, 0x54, 0xac, 0x43, 0x1f, 0x42, 0x01, 0xc7, + 0x4d, 0x4c, 0xf5, 0x8d, 0x19, 0x68, 0x52, 0x8d, 0x3e, 0x81, 0x5a, 0xe2, 0xd8, 0xc3, 0x96, 0x45, + 0x7c, 0x46, 0x06, 0xfa, 0xe6, 0xcc, 0xb5, 0x95, 0x04, 0xae, 0x23, 0x61, 0x68, 0x1b, 0x10, 0xef, + 0x53, 0x97, 0x58, 0xfc, 0x30, 0x0d, 0xf2, 0x3d, 0x11, 0x64, 0x35, 0xd6, 0xc4, 0x71, 0x7e, 0x00, + 0x53, 0x61, 0xaf, 0x1f, 0x78, 0x43, 0x12, 0x50, 0xfd, 0x7d, 0x81, 0xae, 0xc4, 0x8a, 0xc3, 0x48, + 0xde, 0xfe, 0x4b, 0x83, 0x9c, 0x29, 0xfe, 0x60, 0xf0, 0x98, 0x4a, 0xa9, 0x14, 0xa3, 0x6c, 0x16, + 0x1b, 0x79, 0xe1, 0x69, 0xc7, 0x1a, 0x6e, 0x69, 0xdc, 0x4a, 0x2e, 0xda, 0xd1, 0xa8, 0xde, 0x94, + 0xff, 0x57, 0x52, 0x3b, 0x3b, 0x05, 0xfe, 0x1c, 0x96, 0xe2, 0x2d, 0x8f, 0x74, 0x85, 0xcf, 0x2e, + 0xfe, 0xc6, 0x9a, 0xd2, 0x64, 0xd6, 0xe9, 0x8e, 0xc6, 0x67, 0x56, 0x5e, 0x4e, 0x4d, 0x82, 0x36, + 0x62, 0xd8, 0xc3, 0x6b, 0xaa, 0xb1, 0x39, 0x1f, 0x10, 0x55, 0x6d, 0xfb, 0xc7, 0x05, 0x28, 0x45, + 0x61, 0x77, 0xb1, 0xcb, 0x2d, 0x04, 0x3c, 0x47, 0xf9, 0xf8, 0x0b, 0xc6, 0x7e, 0x64, 0xe6, 0x5a, + 0x43, 0x9f, 0x55, 0xc8, 0x36, 0x38, 0x80, 0x72, 0x66, 0x02, 0xa1, 0x75, 0x05, 0x7e, 0x78, 0x34, + 0x4d, 0x3f, 0x10, 0xfa, 0x0c, 0xaa, 0x33, 0x83, 0x04, 0xc5, 0x41, 0xcc, 0x9b, 0x31, 0x09, 0x82, + 0x2f, 0xb3, 0xa9, 0x7b, 0x2b, 0xe3, 0x68, 0xaa, 0xe1, 0x1a, 0x6f, 0xcf, 0xd1, 0xca, 0x58, 0xda, + 0xb0, 0x74, 0x42, 0x64, 0xb7, 0x4e, 0xb3, 0x9b, 0xa6, 0x58, 0x4e, 0x8b, 0x0f, 0x2b, 0xdf, 0xff, + 0xb1, 0xae, 0xfd, 0xc4, 0x9f, 0xdf, 0xf9, 0xf3, 0xed, 0x9f, 0xeb, 0xaf, 0xf5, 0x73, 0x62, 0xf9, + 0x7c, 0xf4, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5a, 0xf7, 0x9e, 0xe3, 0xf9, 0x0a, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index dbef8881d..f5f680599 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -8,9 +8,7 @@ import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; package router; -message SubscribeRequest { - string gateway_id = 1; -} +message SubscribeRequest {} message UplinkMessage { bytes payload = 1; From 5af092a9eaa0a7ebb70f06215b5991336d127142 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 9 May 2016 17:04:45 +0200 Subject: [PATCH 1434/2266] Update gateway schedule and implementation --- core/router/gateway/schedule.go | 60 ++++++++++++++++--- core/router/gateway/schedule_datastructure.go | 54 +++++++---------- .../gateway/schedule_datastructure_test.go | 38 ++++++++++++ core/router/gateway/schedule_test.go | 40 +++++++++++++ 4 files changed, 151 insertions(+), 41 deletions(-) create mode 100644 core/router/gateway/schedule_datastructure_test.go diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index f8179f72a..cfea22558 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -1,7 +1,6 @@ package gateway import ( - "container/heap" "errors" "sync" "sync/atomic" @@ -15,7 +14,7 @@ import ( type Schedule interface { // Synchronize the schedule with the gateway timestamp (in microseconds) Sync(timestamp uint32) - // Get an "option" on a transmission slot at timestamp for the maximum duration of length (in nanoseconds) + // Get an "option" on a transmission slot at timestamp for the maximum duration of length (both in microseconds) GetOption(timestamp uint32, length uint32) (id string, score uint) // Schedule a transmission on a slot Schedule(id string, downlink *router_pb.DownlinkMessage) error @@ -32,15 +31,18 @@ func NewSchedule() Schedule { type schedule struct { sync.RWMutex - active bool - offset int64 - queue *downlinkQueue - byID map[string]*scheduledItem - // some schedule datastructure + active bool + offset int64 + queue *downlinkQueue + byID map[string]*scheduledItem + downlinkActive bool + downlink chan *router_pb.DownlinkMessage } const uintmax = 1 << 32 +// getConflicts walks over the schedule and returns the number of conflicts. +// Both timestamp and length are in microseconds func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint) { s.RLock() snapshot := s.queue.Snapshot() @@ -68,6 +70,8 @@ func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint return } +// realtime gets the synchronized time for a timestamp (in microseconds). Time +// should first be syncronized using func Sync() func (s *schedule) realtime(timestamp uint32) (t time.Time) { offset := atomic.LoadInt64(&s.offset) t = time.Unix(0, 0) @@ -78,10 +82,12 @@ func (s *schedule) realtime(timestamp uint32) (t time.Time) { return } +// see interface func (s *schedule) Sync(timestamp uint32) { atomic.StoreInt64(&s.offset, time.Now().UnixNano()-int64(timestamp)*1000) } +// see interface func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) { id = random.String(32) score = s.getConflicts(timestamp, length) @@ -94,11 +100,12 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score } s.Lock() defer s.Unlock() - heap.Push(s.queue, item) + s.queue.Push(item) s.byID[id] = item return id, score } +// see interface func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { s.RLock() defer s.RUnlock() @@ -108,3 +115,40 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro } return errors.New("ID not found") } + +func (s *schedule) Stop() { + s.downlinkActive = false +} + +// TODO: Make configurable +const deadline = 50 +const waitTime = 10 * time.Millisecond + +func (s *schedule) Subscribe() <-chan *router_pb.DownlinkMessage { + if s.downlinkActive { + return nil + } + s.downlink = make(chan *router_pb.DownlinkMessage) + s.downlinkActive = true + go func() { + for s.downlinkActive { + <-time.After(waitTime) + s.Lock() + for { + item := s.queue.Peek() + if item != nil && time.Now().Add(-1*deadline*time.Millisecond).After(item.time) { + s.queue.Pop() + delete(s.byID, item.id) + if item.payload != nil { + s.downlink <- item.payload + } + } else { + break + } + } + s.Unlock() + } + close(s.downlink) + }() + return s.downlink +} diff --git a/core/router/gateway/schedule_datastructure.go b/core/router/gateway/schedule_datastructure.go index 2dab27096..aa8f69866 100644 --- a/core/router/gateway/schedule_datastructure.go +++ b/core/router/gateway/schedule_datastructure.go @@ -1,7 +1,6 @@ package gateway import ( - "container/heap" "sync" "time" @@ -17,7 +16,7 @@ type scheduledItem struct { payload *router_pb.DownlinkMessage } -// A downlinkQueue implements heap.Interface and holds scheduledItems. +// A downlinkQueue holds scheduledItems. type downlinkQueue struct { sync.RWMutex items []*scheduledItem @@ -27,63 +26,52 @@ func NewDownlinkQueue(items ...*scheduledItem) *downlinkQueue { dq := &downlinkQueue{ items: items, } - heap.Init(dq) return dq } -// Len is used by heap.Interface -func (dq *downlinkQueue) Len() int { return len(dq.items) } - -// Less is used by heap.Interface -func (dq *downlinkQueue) Less(i, j int) bool { +// less is used for sorting +func (dq *downlinkQueue) less(i, j int) bool { return dq.items[i].time.Before(dq.items[j].time) } -// Swap is used by heap.Interface. -func (dq *downlinkQueue) Swap(i, j int) { - if len(dq.items) == 0 { - return - } - dq.Lock() - defer dq.Unlock() +// swap is used for sorting +func (dq *downlinkQueue) swap(i, j int) { dq.items[i], dq.items[j] = dq.items[j], dq.items[i] } -// Push is used by heap.Interface -func (dq *downlinkQueue) Push(x interface{}) { - item := x.(*scheduledItem) - dq.Lock() - defer dq.Unlock() +// Push an item to the queue +func (dq *downlinkQueue) Push(item *scheduledItem) { dq.items = append(dq.items, item) + for i := len(dq.items); i > 1; i-- { + if dq.less(i-1, i-2) { + dq.swap(i-1, i-2) + } else { + return + } + } } -// Pop is used by heap.Interface. -func (dq *downlinkQueue) Pop() interface{} { - dq.Lock() - defer dq.Unlock() +// Pop an item from the queue +func (dq *downlinkQueue) Pop() *scheduledItem { n := len(dq.items) if n == 0 { return nil } - item := dq.items[n-1] - dq.items = dq.items[0 : n-1] + item := dq.items[0] + dq.items = dq.items[1:] return item } // Snapshot returns a snapshot of the downlinkQueue func (dq *downlinkQueue) Snapshot() []*scheduledItem { - dq.RLock() - defer dq.RUnlock() return dq.items } // Peek returns the next item in the queue -func (dq *downlinkQueue) Peek() interface{} { - snapshot := dq.Snapshot() - n := len(snapshot) +func (dq *downlinkQueue) Peek() *scheduledItem { + n := len(dq.items) if n == 0 { return nil } - item := snapshot[n-1] - return item + return dq.items[0] } diff --git a/core/router/gateway/schedule_datastructure_test.go b/core/router/gateway/schedule_datastructure_test.go new file mode 100644 index 000000000..823342cee --- /dev/null +++ b/core/router/gateway/schedule_datastructure_test.go @@ -0,0 +1,38 @@ +package gateway + +import ( + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestScheduleDatastructure(t *testing.T) { + a := New(t) + dq := NewDownlinkQueue() + + a.So(dq.Peek(), ShouldBeNil) + a.So(dq.Pop(), ShouldBeNil) + + now := time.Now() + + i1 := &scheduledItem{time: now.Add(300 * time.Millisecond)} + i2 := &scheduledItem{time: now.Add(200 * time.Millisecond)} + i3 := &scheduledItem{time: now.Add(100 * time.Millisecond)} + i4 := &scheduledItem{time: now.Add(250 * time.Millisecond)} + i5 := &scheduledItem{time: now.Add(50 * time.Millisecond)} + + dq.Push(i1) + a.So(dq.Peek(), ShouldEqual, i1) + dq.Push(i2) + a.So(dq.Peek(), ShouldEqual, i2) + dq.Push(i3) + a.So(dq.Peek(), ShouldEqual, i3) + dq.Push(i4) + a.So(dq.Peek(), ShouldEqual, i3) + a.So(dq.Pop(), ShouldEqual, i3) + a.So(dq.Peek(), ShouldEqual, i2) + dq.Push(i5) + a.So(dq.Peek(), ShouldEqual, i5) + +} diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index 72cc15804..8c9a58d5e 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -104,3 +104,43 @@ func TestScheduleSchedule(t *testing.T) { _, conflicts = s.GetOption(50, 100) a.So(conflicts, ShouldEqual, 10) } + +func TestScheduleSubscribe(t *testing.T) { + a := New(t) + s := NewSchedule().(*schedule) + s.Sync(0) + + downlink1 := &router_pb.DownlinkMessage{Payload: []byte{1}} + downlink2 := &router_pb.DownlinkMessage{Payload: []byte{2}} + downlink3 := &router_pb.DownlinkMessage{Payload: []byte{3}} + + go func() { + var i int + for out := range s.Subscribe() { + switch i { + case 0: + a.So(out, ShouldEqual, downlink2) + case 1: + a.So(out, ShouldEqual, downlink1) + case 3: + a.So(out, ShouldEqual, downlink3) + } + i++ + } + }() + + id, _ := s.GetOption(300, 50) + s.Schedule(id, downlink1) + id, _ = s.GetOption(200, 50) + s.Schedule(id, downlink2) + id, _ = s.GetOption(400, 50) + s.Schedule(id, downlink3) + + go func() { + <-time.After(400 * time.Millisecond) + s.Stop() + }() + + <-time.After(500 * time.Millisecond) + +} From b6a821d52a81f07a07a4a05d935b9867ee523a6e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 10 May 2016 13:00:45 +0200 Subject: [PATCH 1435/2266] Use Token instead of Fingerprint --- api/discovery/discovery.pb.go | 67 ++++++++++++------------- api/discovery/discovery.proto | 2 +- core/discovery/broker_discovery_test.go | 8 +-- core/discovery/discovery.go | 4 +- core/discovery/discovery_test.go | 12 ++--- 5 files changed, 46 insertions(+), 47 deletions(-) diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 0c2c87c5b..ef7d4bc95 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -70,7 +70,7 @@ type Announcement struct { ServiceVersion string `protobuf:"bytes,2,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` NetAddress string `protobuf:"bytes,3,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` Description string `protobuf:"bytes,11,opt,name=description,proto3" json:"description,omitempty"` - Fingerprint string `protobuf:"bytes,12,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` + Token string `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } @@ -320,11 +320,11 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) i += copy(data[i:], m.Description) } - if len(m.Fingerprint) > 0 { + if len(m.Token) > 0 { data[i] = 0x62 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Fingerprint))) - i += copy(data[i:], m.Fingerprint) + i = encodeVarintDiscovery(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { @@ -456,7 +456,7 @@ func (m *Announcement) Size() (n int) { if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.Fingerprint) + l = len(m.Token) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } @@ -751,7 +751,7 @@ func (m *Announcement) Unmarshal(data []byte) error { iNdEx = postIndex case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Fingerprint", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -776,7 +776,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Fingerprint = string(data[iNdEx:postIndex]) + m.Token = string(data[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -1096,32 +1096,31 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 422 bytes of a gzipped FileDescriptorProto + // 416 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x6a, 0xd4, 0x40, - 0x14, 0xc6, 0xbb, 0x2e, 0x96, 0xec, 0xc9, 0xd2, 0xae, 0xa3, 0x62, 0x88, 0x50, 0xd7, 0xdc, 0x58, - 0x2f, 0x9a, 0x40, 0xea, 0xad, 0x17, 0xeb, 0x5f, 0x44, 0x5a, 0x64, 0x28, 0xe2, 0x95, 0x65, 0x9a, - 0x1c, 0xbb, 0xc3, 0x9a, 0x99, 0x38, 0x33, 0x59, 0xc9, 0x95, 0xaf, 0xe1, 0x23, 0x79, 0xe9, 0x23, - 0x88, 0xbe, 0x80, 0x8f, 0xe0, 0xe4, 0xdf, 0x26, 0x48, 0x0b, 0x7b, 0x71, 0x20, 0xf9, 0xe6, 0xf7, - 0x9d, 0x2f, 0xe7, 0x64, 0xe0, 0xe9, 0x25, 0x37, 0xcb, 0xe2, 0x22, 0x4c, 0x64, 0x16, 0x9d, 0x2d, - 0xf1, 0x6c, 0xc9, 0xc5, 0xa5, 0x3e, 0x45, 0xf3, 0x55, 0xaa, 0x55, 0x64, 0x8c, 0x88, 0x58, 0xce, - 0xa3, 0x94, 0xeb, 0x44, 0xae, 0x51, 0x95, 0xfd, 0x53, 0x98, 0x2b, 0x69, 0x24, 0x99, 0x6c, 0x04, - 0xff, 0x68, 0x9b, 0x4e, 0xb6, 0x1a, 0x67, 0xf0, 0x11, 0x9c, 0x13, 0x34, 0x2c, 0x65, 0x86, 0x91, - 0xc7, 0x30, 0x5e, 0x61, 0xe9, 0x8d, 0xe6, 0xa3, 0xc3, 0xbd, 0xf8, 0x5e, 0xd8, 0x87, 0x74, 0x44, - 0xf8, 0x16, 0x4b, 0x5a, 0x31, 0xe4, 0x0e, 0xdc, 0x5c, 0xb3, 0xcf, 0x05, 0x7a, 0x37, 0x2c, 0x3c, - 0xa5, 0xcd, 0x4b, 0x70, 0x0b, 0xc6, 0x96, 0x20, 0x00, 0xbb, 0xef, 0xe8, 0xcb, 0x57, 0x6f, 0x3e, - 0xcc, 0x76, 0x82, 0xbf, 0x23, 0x98, 0x2e, 0x84, 0x90, 0x85, 0x48, 0x30, 0x43, 0x61, 0xc8, 0x43, - 0x98, 0x6a, 0x54, 0x6b, 0x9e, 0xe0, 0xb9, 0x60, 0x19, 0xd6, 0x69, 0x13, 0xea, 0xb6, 0xda, 0xa9, - 0x95, 0xc8, 0x23, 0xd8, 0xef, 0x10, 0x1b, 0xaf, 0xb9, 0x14, 0x75, 0xcc, 0x84, 0xee, 0xb5, 0xf2, - 0xfb, 0x46, 0x25, 0x0f, 0xc0, 0x15, 0x68, 0xce, 0x59, 0x9a, 0x2a, 0xd4, 0xda, 0x1b, 0xd7, 0x10, - 0x58, 0x69, 0xd1, 0x28, 0x64, 0x0e, 0x6e, 0x8a, 0x3a, 0x51, 0x3c, 0x37, 0x55, 0x17, 0xb7, 0xc9, - 0x1a, 0x48, 0x15, 0xf1, 0xc9, 0x6e, 0x08, 0x55, 0xae, 0xb8, 0x30, 0xde, 0xb4, 0x21, 0x06, 0x12, - 0x89, 0xc0, 0xc9, 0xda, 0xf9, 0xbd, 0xbb, 0xf3, 0xf1, 0xa1, 0x1b, 0xdf, 0xbe, 0x62, 0x35, 0x74, - 0x03, 0x05, 0x4f, 0x60, 0xff, 0x45, 0x7b, 0x4e, 0xf1, 0x4b, 0x81, 0x7a, 0x9b, 0xa1, 0x83, 0xd7, - 0x30, 0xeb, 0x5d, 0x3a, 0x97, 0x42, 0x23, 0x39, 0x06, 0xa7, 0x45, 0xb4, 0xb5, 0x54, 0xd1, 0xc3, - 0xbf, 0x32, 0x5c, 0x2b, 0xdd, 0x80, 0xf1, 0x37, 0x98, 0x74, 0x8d, 0x4a, 0x72, 0x04, 0x4e, 0x87, - 0x91, 0xeb, 0xbc, 0xbe, 0x13, 0x56, 0xf7, 0x61, 0x91, 0xac, 0xc8, 0x73, 0x70, 0x3a, 0x2f, 0xf1, - 0x07, 0xf8, 0x7f, 0xf3, 0xf8, 0xf7, 0xaf, 0x3c, 0x6b, 0xbe, 0x3a, 0x26, 0xfd, 0x24, 0xe5, 0x09, - 0x13, 0xcc, 0x6e, 0xf2, 0xd9, 0xec, 0xc7, 0xef, 0x83, 0xd1, 0x4f, 0x5b, 0xbf, 0x6c, 0x7d, 0xff, - 0x73, 0xb0, 0x73, 0xb1, 0x5b, 0xdf, 0xbf, 0xe3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x67, 0x53, - 0x9f, 0x27, 0xfa, 0x02, 0x00, 0x00, + 0x14, 0xc6, 0xbb, 0x06, 0x4b, 0x72, 0x12, 0xda, 0x75, 0x54, 0x0c, 0x11, 0xea, 0x9a, 0x1b, 0xeb, + 0x45, 0x13, 0x48, 0xbd, 0xf5, 0x62, 0xfd, 0x8b, 0x48, 0x8b, 0x0c, 0x45, 0xbc, 0xb2, 0x4c, 0x93, + 0xc3, 0x6e, 0x88, 0x99, 0x89, 0x99, 0xc9, 0x4a, 0xae, 0x7c, 0x0d, 0x1f, 0xc9, 0x4b, 0x1f, 0x41, + 0xd4, 0x07, 0x71, 0xf2, 0x6f, 0x13, 0xa4, 0xc2, 0x5e, 0x1c, 0xd8, 0xf9, 0xce, 0xef, 0x9c, 0x6f, + 0xbf, 0xc9, 0xc0, 0xd3, 0x55, 0xaa, 0xd6, 0xd5, 0x55, 0x10, 0x8b, 0x3c, 0xbc, 0x58, 0xe3, 0xc5, + 0x3a, 0xe5, 0x2b, 0x79, 0x8e, 0xea, 0x8b, 0x28, 0xb3, 0x50, 0x29, 0x1e, 0xb2, 0x22, 0x0d, 0x93, + 0x54, 0xc6, 0x62, 0x83, 0x65, 0x3d, 0xfe, 0x0a, 0x8a, 0x52, 0x28, 0x41, 0xac, 0xad, 0xe0, 0x9d, + 0xec, 0xb2, 0x49, 0x57, 0x37, 0xe9, 0x7f, 0x04, 0xf3, 0x0c, 0x15, 0x4b, 0x98, 0x62, 0xe4, 0x31, + 0x18, 0x19, 0xd6, 0xee, 0x6c, 0x31, 0x3b, 0x3e, 0x88, 0xee, 0x05, 0xa3, 0xc9, 0x40, 0x04, 0x6f, + 0xb1, 0xa6, 0x0d, 0x43, 0xee, 0xc0, 0xcd, 0x0d, 0xfb, 0x54, 0xa1, 0x7b, 0x43, 0xc3, 0x0e, 0xed, + 0x0e, 0xfe, 0x2d, 0x30, 0x34, 0x41, 0x00, 0xf6, 0xdf, 0xd1, 0x97, 0xaf, 0xde, 0x7c, 0x98, 0xef, + 0xf9, 0x7f, 0x66, 0xe0, 0x2c, 0x39, 0x17, 0x15, 0x8f, 0x31, 0x47, 0xae, 0xc8, 0x43, 0x70, 0x24, + 0x96, 0x9b, 0x34, 0xc6, 0x4b, 0xce, 0x72, 0x6c, 0xdd, 0x2c, 0x6a, 0xf7, 0xda, 0xb9, 0x96, 0xc8, + 0x23, 0x38, 0x1c, 0x10, 0x6d, 0x2f, 0x53, 0xc1, 0x5b, 0x1b, 0x8b, 0x1e, 0xf4, 0xf2, 0xfb, 0x4e, + 0x25, 0x0f, 0xc0, 0xe6, 0xa8, 0x2e, 0x59, 0x92, 0x94, 0x28, 0xa5, 0x6b, 0xb4, 0x10, 0x68, 0x69, + 0xd9, 0x29, 0x64, 0x01, 0x76, 0x82, 0x32, 0x2e, 0xd3, 0x42, 0x35, 0x5b, 0xec, 0xce, 0x6b, 0x22, + 0x35, 0x41, 0x94, 0xc8, 0x90, 0xbb, 0x4e, 0xdb, 0xeb, 0x0e, 0x24, 0x04, 0x33, 0xef, 0x33, 0xbb, + 0x77, 0x17, 0xc6, 0xb1, 0x1d, 0xdd, 0xbe, 0xe6, 0x3a, 0xe8, 0x16, 0xf2, 0x9f, 0xc0, 0xe1, 0x8b, + 0xbe, 0x4f, 0xf1, 0x73, 0x85, 0x72, 0x97, 0xa0, 0xfe, 0x6b, 0x98, 0x8f, 0x53, 0xb2, 0x10, 0x5c, + 0x22, 0x39, 0x05, 0xb3, 0x47, 0xa4, 0x1e, 0x69, 0xac, 0xa7, 0x5f, 0x62, 0x7a, 0x95, 0x74, 0x0b, + 0x46, 0x5f, 0xc1, 0x1a, 0x16, 0xd5, 0xe4, 0x04, 0xcc, 0x01, 0x23, 0xff, 0x9b, 0xf5, 0xcc, 0xa0, + 0x79, 0x03, 0xcb, 0x38, 0x23, 0xcf, 0xc1, 0x1c, 0x66, 0x89, 0x37, 0xc1, 0xff, 0xc9, 0xe3, 0xdd, + 0xbf, 0xb6, 0xd7, 0xfd, 0xeb, 0x88, 0x8c, 0x49, 0xea, 0x33, 0xc6, 0xd9, 0x0a, 0xcb, 0x67, 0xf3, + 0xef, 0xbf, 0x8e, 0x66, 0x3f, 0x74, 0xfd, 0xd4, 0xf5, 0xed, 0xf7, 0xd1, 0xde, 0xd5, 0x7e, 0xfb, + 0xe6, 0x4e, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x9d, 0x63, 0x0e, 0xee, 0x02, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 696575856..d1066cc33 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -17,7 +17,7 @@ message Announcement { string service_version = 2; string net_address = 3; string description = 11; - string fingerprint = 12; + string token = 12; repeated Metadata metadata = 21; } diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index f82d906c6..df1324f54 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -20,21 +20,21 @@ func TestBrokerDiscoveryDiscover(t *testing.T) { a := New(t) // Broker1 has a prefix with all DevAddrs - broker1 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker1", NetAddress: "localhost1:1881", + broker1 := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{}}, }, } // Broker2 has one DevAddr prefix - broker2 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker2", NetAddress: "localhost2:1881", + broker2 := &pb.Announcement{ServiceName: "broker", Token: "broker2", NetAddress: "localhost2:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x01}}, }, } // Broker3 has multiple DevAddr prefixes - broker3 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker3", NetAddress: "localhost3:1881", + broker3 := &pb.Announcement{ServiceName: "broker", Token: "broker3", NetAddress: "localhost3:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x02, 0x03}}, }, @@ -74,7 +74,7 @@ func TestBrokerDiscoveryCache(t *testing.T) { discoveryServer, _ := buildMockDiscoveryServer(port) - broker := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker", NetAddress: "localhost1:1881", + broker := &pb.Announcement{ServiceName: "broker", Token: "broker", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{}}}, } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index a2ffe06f1..9dd304a26 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -33,11 +33,11 @@ func (d *discovery) Announce(announcement *pb.Announcement) error { } // Find an existing announcement - service, ok := services[announcement.Fingerprint] + service, ok := services[announcement.Token] if ok { *service = *announcement } else { - services[announcement.Fingerprint] = announcement + services[announcement.Token] = announcement } return nil diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 41868fc76..dade59efc 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -10,9 +10,9 @@ import ( func TestDiscoveryDiscover(t *testing.T) { a := New(t) - router := &pb.Announcement{Fingerprint: "router"} - broker1 := &pb.Announcement{Fingerprint: "broker1"} - broker2 := &pb.Announcement{Fingerprint: "broker2"} + router := &pb.Announcement{Token: "router"} + broker1 := &pb.Announcement{Token: "broker1"} + broker2 := &pb.Announcement{Token: "broker2"} d := &discovery{ services: map[string]map[string]*pb.Announcement{ @@ -44,9 +44,9 @@ func TestDiscoveryDiscover(t *testing.T) { func TestDiscoveryAnnounce(t *testing.T) { a := New(t) - broker1a := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker1", NetAddress: "old address"} - broker1b := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker1", NetAddress: "new address"} - broker2 := &pb.Announcement{ServiceName: "broker", Fingerprint: "broker2", NetAddress: "other address"} + broker1a := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "old address"} + broker1b := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "new address"} + broker2 := &pb.Announcement{ServiceName: "broker", Token: "broker2", NetAddress: "other address"} d := &discovery{ services: map[string]map[string]*pb.Announcement{}, From dfa79e00bb5669a36e1b95b6873aab24c7a20b26 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 12 May 2016 10:09:48 +0200 Subject: [PATCH 1436/2266] Update uplink/downlink protos --- api/broker/broker.pb.go | 386 ++++++++++++++++++----------- api/broker/broker.proto | 8 +- api/gateway/gateway.pb.go | 171 ++++++++----- api/gateway/gateway.proto | 13 +- api/protocol/lorawan/lorawan.pb.go | 175 +++++-------- api/protocol/lorawan/lorawan.proto | 1 - 6 files changed, 438 insertions(+), 316 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 317647411..df5d8ec85 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -53,10 +53,12 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion1 type DownlinkOption struct { - Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` - Score uint32 `protobuf:"varint,2,opt,name=score,proto3" json:"score,omitempty"` - Deadline int64 `protobuf:"varint,3,opt,name=deadline,proto3" json:"deadline,omitempty"` - Configuration *gateway.TxConfiguration `protobuf:"bytes,4,opt,name=configuration" json:"configuration,omitempty"` + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,2,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` + Score uint32 `protobuf:"varint,3,opt,name=score,proto3" json:"score,omitempty"` + Deadline int64 `protobuf:"varint,4,opt,name=deadline,proto3" json:"deadline,omitempty"` + ProtocolConfig *protocol.TxConfiguration `protobuf:"bytes,5,opt,name=protocol_config,json=protocolConfig" json:"protocol_config,omitempty"` + GatewayConfig *gateway.TxConfiguration `protobuf:"bytes,6,opt,name=gateway_config,json=gatewayConfig" json:"gateway_config,omitempty"` } func (m *DownlinkOption) Reset() { *m = DownlinkOption{} } @@ -64,9 +66,16 @@ func (m *DownlinkOption) String() string { return proto.CompactTextSt func (*DownlinkOption) ProtoMessage() {} func (*DownlinkOption) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{0} } -func (m *DownlinkOption) GetConfiguration() *gateway.TxConfiguration { +func (m *DownlinkOption) GetProtocolConfig() *protocol.TxConfiguration { if m != nil { - return m.Configuration + return m.ProtocolConfig + } + return nil +} + +func (m *DownlinkOption) GetGatewayConfig() *gateway.TxConfiguration { + if m != nil { + return m.GatewayConfig } return nil } @@ -882,25 +891,45 @@ func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Identifier))) i += copy(data[i:], m.Identifier) } + if m.GatewayEui != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.GatewayEui.Size())) + n1, err := m.GatewayEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } if m.Score != 0 { - data[i] = 0x10 + data[i] = 0x18 i++ i = encodeVarintBroker(data, i, uint64(m.Score)) } if m.Deadline != 0 { - data[i] = 0x18 + data[i] = 0x20 i++ i = encodeVarintBroker(data, i, uint64(m.Deadline)) } - if m.Configuration != nil { - data[i] = 0x22 + if m.ProtocolConfig != nil { + data[i] = 0x2a i++ - i = encodeVarintBroker(data, i, uint64(m.Configuration.Size())) - n1, err := m.Configuration.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.ProtocolConfig.Size())) + n2, err := m.ProtocolConfig.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n1 + i += n2 + } + if m.GatewayConfig != nil { + data[i] = 0x32 + i++ + i = encodeVarintBroker(data, i, uint64(m.GatewayConfig.Size())) + n3, err := m.GatewayConfig.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 } return i, nil } @@ -930,21 +959,21 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n2, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n4, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n4 } if m.GatewayMetadata != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n3, err := m.GatewayMetadata.MarshalTo(data[i:]) + n5, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n5 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -988,11 +1017,11 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n4, err := m.DownlinkOption.MarshalTo(data[i:]) + n6, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n6 } return i, nil } @@ -1022,11 +1051,11 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n5, err := m.DownlinkOption.MarshalTo(data[i:]) + n7, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n7 } return i, nil } @@ -1056,21 +1085,21 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n6, err := m.DevEui.MarshalTo(data[i:]) + n8, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n8 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n7, err := m.AppEui.MarshalTo(data[i:]) + n9, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n9 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1078,11 +1107,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n8, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n10, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n10 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1104,11 +1133,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n9, err := m.ResponseTemplate.MarshalTo(data[i:]) + n11, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n11 } if m.NeedDownlink { data[i] = 0x80 @@ -1150,21 +1179,21 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n10, err := m.DevEui.MarshalTo(data[i:]) + n12, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n12 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n11, err := m.AppEui.MarshalTo(data[i:]) + n13, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n13 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1172,11 +1201,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n12, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n14, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n14 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1184,11 +1213,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n13, err := m.GatewayMetadata.MarshalTo(data[i:]) + n15, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n15 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1196,11 +1225,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n14, err := m.ActivationMetadata.MarshalTo(data[i:]) + n16, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n16 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -1244,21 +1273,21 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n15, err := m.DevEui.MarshalTo(data[i:]) + n17, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n17 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n16, err := m.AppEui.MarshalTo(data[i:]) + n18, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n18 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1266,11 +1295,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n17, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n19, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n19 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1278,11 +1307,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n18, err := m.GatewayMetadata.MarshalTo(data[i:]) + n20, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n20 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1290,11 +1319,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n19, err := m.ActivationMetadata.MarshalTo(data[i:]) + n21, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n21 } if m.ResponseTemplate != nil { data[i] = 0xfa @@ -1302,11 +1331,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n20, err := m.ResponseTemplate.MarshalTo(data[i:]) + n22, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n20 + i += n22 } return i, nil } @@ -1465,31 +1494,31 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n21, err := m.Uplink.MarshalTo(data[i:]) + n23, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n21 + i += n23 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n22, err := m.UplinkUnique.MarshalTo(data[i:]) + n24, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n22 + i += n24 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n23, err := m.Downlink.MarshalTo(data[i:]) + n25, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n23 + i += n25 } if m.Activations != nil { data[i] = 0xaa @@ -1497,11 +1526,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n24, err := m.Activations.MarshalTo(data[i:]) + n26, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n24 + i += n26 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1509,11 +1538,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n25, err := m.ActivationsUnique.MarshalTo(data[i:]) + n27, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n25 + i += n27 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1521,11 +1550,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n26, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n28, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n28 } if m.Deduplication != nil { data[i] = 0xfa @@ -1533,11 +1562,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n27, err := m.Deduplication.MarshalTo(data[i:]) + n29, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n27 + i += n29 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1590,14 +1619,22 @@ func (m *DownlinkOption) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.GatewayEui != nil { + l = m.GatewayEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.Score != 0 { n += 1 + sovBroker(uint64(m.Score)) } if m.Deadline != 0 { n += 1 + sovBroker(uint64(m.Deadline)) } - if m.Configuration != nil { - l = m.Configuration.Size() + if m.ProtocolConfig != nil { + l = m.ProtocolConfig.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.GatewayConfig != nil { + l = m.GatewayConfig.Size() n += 1 + l + sovBroker(uint64(l)) } return n @@ -1922,6 +1959,38 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { m.Identifier = string(data[iNdEx:postIndex]) iNdEx = postIndex case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayEui = &v + if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Score", wireType) } @@ -1940,7 +2009,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { break } } - case 3: + case 4: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Deadline", wireType) } @@ -1959,9 +2028,42 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { break } } - case 4: + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfig", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ProtocolConfig == nil { + m.ProtocolConfig = &protocol.TxConfiguration{} + } + if err := m.ProtocolConfig.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Configuration", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayConfig", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -1985,10 +2087,10 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Configuration == nil { - m.Configuration = &gateway.TxConfiguration{} + if m.GatewayConfig == nil { + m.GatewayConfig = &gateway.TxConfiguration{} } - if err := m.Configuration.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayConfig.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4045,71 +4147,75 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1056 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xc7, 0x71, 0x71, 0x9c, 0x67, 0x3b, 0x76, 0x26, 0x71, 0xb2, 0x35, 0x51, 0x12, 0xb6, 0x08, - 0x85, 0x3f, 0xb5, 0xa9, 0x51, 0xa9, 0x2a, 0x04, 0xc8, 0x69, 0xaa, 0x52, 0x24, 0x97, 0x6a, 0x9b, - 0x9c, 0xad, 0xf1, 0xee, 0xc4, 0x1e, 0xc5, 0xd9, 0x5d, 0x76, 0x66, 0x93, 0xe6, 0xc6, 0x87, 0xe0, - 0xc0, 0x81, 0x13, 0xdf, 0x04, 0x71, 0xe1, 0xc8, 0x99, 0x03, 0x42, 0x70, 0xe1, 0xc6, 0x57, 0x60, - 0x76, 0x76, 0x66, 0xff, 0xc4, 0x36, 0x31, 0x45, 0x48, 0x3d, 0xf4, 0x60, 0x79, 0xe7, 0xfd, 0xf9, - 0xbd, 0xb7, 0xef, 0xf7, 0xde, 0xec, 0x83, 0x7b, 0x23, 0xca, 0xc7, 0xe1, 0xb0, 0x6d, 0x7b, 0x67, - 0x9d, 0xa3, 0x31, 0x39, 0x1a, 0x53, 0x77, 0xc4, 0x9e, 0x10, 0x7e, 0xe1, 0x05, 0xa7, 0x1d, 0xce, - 0xdd, 0x0e, 0xf6, 0x69, 0x67, 0x18, 0x78, 0xa7, 0x24, 0x50, 0x7f, 0x6d, 0x3f, 0xf0, 0xb8, 0x87, - 0x4a, 0xf1, 0xa9, 0x75, 0x3b, 0x03, 0x30, 0xf2, 0x46, 0x5e, 0x47, 0xaa, 0x87, 0xe1, 0x89, 0x3c, - 0xc9, 0x83, 0x7c, 0x8a, 0xdd, 0x72, 0xe6, 0x73, 0xe3, 0x89, 0x9f, 0x32, 0xff, 0x78, 0x11, 0x73, - 0x69, 0x6a, 0x7b, 0x93, 0xe4, 0x41, 0x39, 0xdf, 0x5f, 0xc4, 0x79, 0x84, 0x39, 0xb9, 0xc0, 0x97, - 0xfa, 0x3f, 0x76, 0x35, 0xbf, 0x2f, 0xc0, 0xea, 0xa1, 0x77, 0xe1, 0x4e, 0xa8, 0x7b, 0xfa, 0xa5, - 0xcf, 0xa9, 0xe7, 0xa2, 0x1d, 0x00, 0xea, 0x10, 0x97, 0xd3, 0x13, 0x4a, 0x02, 0xa3, 0xb0, 0x57, - 0xd8, 0x5f, 0xb1, 0x32, 0x12, 0xb4, 0x01, 0xaf, 0x33, 0xdb, 0x0b, 0x88, 0xb1, 0x24, 0x54, 0x35, - 0x2b, 0x3e, 0xa0, 0x16, 0x94, 0x1d, 0x82, 0x1d, 0x81, 0x43, 0x8c, 0xa2, 0x50, 0x14, 0xad, 0xe4, - 0x8c, 0x3e, 0x85, 0x9a, 0xed, 0xb9, 0x27, 0x74, 0x14, 0x06, 0x38, 0x0a, 0x61, 0xdc, 0x10, 0x06, - 0x95, 0xae, 0xd1, 0xd6, 0xb9, 0x1c, 0x3d, 0x7f, 0x90, 0xd5, 0x5b, 0x79, 0x73, 0xf3, 0xaf, 0x02, - 0xd4, 0x8e, 0xfd, 0x28, 0xc5, 0x3e, 0x61, 0x0c, 0x8f, 0x08, 0x32, 0x60, 0xd9, 0xc7, 0x97, 0x13, - 0x0f, 0x3b, 0x32, 0xc1, 0xaa, 0xa5, 0x8f, 0xa8, 0x07, 0x6b, 0xba, 0x3a, 0x83, 0x33, 0xc2, 0xb1, - 0x83, 0x39, 0x36, 0x2a, 0x32, 0xde, 0x46, 0x3b, 0xa9, 0x9b, 0xf5, 0xbc, 0xaf, 0x74, 0x56, 0x43, - 0x0b, 0xb5, 0x44, 0xa4, 0xdb, 0x50, 0x89, 0xa5, 0x08, 0x55, 0x89, 0xb0, 0x9e, 0x64, 0x9c, 0x01, - 0xa8, 0x2b, 0x59, 0xe2, 0xdf, 0x83, 0x86, 0xa3, 0x4a, 0x3a, 0xf0, 0x64, 0x4d, 0x99, 0xd1, 0xdc, - 0x2b, 0x0a, 0xff, 0xcd, 0xb6, 0x6a, 0xad, 0x7c, 0xc9, 0xad, 0xba, 0x93, 0x3b, 0x33, 0x73, 0x02, - 0x75, 0x6d, 0x72, 0xfd, 0x2b, 0x7f, 0x06, 0xf5, 0x2b, 0xf1, 0xd4, 0x0b, 0xcf, 0x0b, 0xb7, 0x9a, - 0x0f, 0x67, 0x86, 0x60, 0x1c, 0x92, 0x73, 0x6a, 0x93, 0x9e, 0xcd, 0xe9, 0x79, 0x4c, 0x01, 0x61, - 0xbe, 0x48, 0xe4, 0x7f, 0x0d, 0xfb, 0x63, 0x11, 0x6e, 0x1e, 0x12, 0x27, 0x14, 0xcc, 0xda, 0xa2, - 0x84, 0xce, 0xa2, 0x14, 0x3f, 0x81, 0x65, 0x87, 0x9c, 0x0f, 0x48, 0x48, 0x65, 0xc0, 0xea, 0xc1, - 0xdd, 0x5f, 0x7e, 0xdd, 0xbd, 0x73, 0xdd, 0x0c, 0x44, 0x5d, 0xda, 0xe1, 0x97, 0x3e, 0x61, 0x6d, - 0xf1, 0xb2, 0x0f, 0x8f, 0x1f, 0x5b, 0x25, 0x81, 0xf2, 0x30, 0xa4, 0x11, 0x1e, 0xf6, 0x7d, 0x89, - 0x57, 0x7d, 0x21, 0xbc, 0x9e, 0xef, 0x4b, 0x3c, 0x81, 0x12, 0xe1, 0xcd, 0x6c, 0xc1, 0xe6, 0x7f, - 0x6e, 0xc1, 0x4d, 0xd9, 0x42, 0x8b, 0xb5, 0xe0, 0x21, 0xac, 0x05, 0x8a, 0xc1, 0x01, 0x27, 0x67, - 0xfe, 0x44, 0xe8, 0x8d, 0x5d, 0x99, 0xc2, 0xd6, 0x55, 0x76, 0x54, 0xc1, 0xad, 0x86, 0xf6, 0x38, - 0x52, 0x0e, 0xe8, 0x16, 0xd4, 0x5c, 0x42, 0x9c, 0x81, 0xe6, 0xcd, 0xd8, 0x13, 0x08, 0x65, 0xab, - 0x1a, 0x09, 0xb5, 0xb7, 0xf9, 0x67, 0x11, 0xb6, 0xa6, 0xbb, 0xe7, 0xab, 0x90, 0x30, 0xfe, 0x8a, - 0xc3, 0x69, 0x0e, 0x17, 0xbf, 0x46, 0xfa, 0xb0, 0x8e, 0x93, 0x8a, 0xa6, 0x10, 0x5b, 0x12, 0x62, - 0x3b, 0x4d, 0x22, 0x2d, 0x7b, 0x82, 0x85, 0xf0, 0x94, 0x6c, 0xe6, 0xad, 0xb4, 0xfb, 0xef, 0x6e, - 0xa5, 0xaf, 0x6f, 0xc0, 0xad, 0xec, 0xc0, 0xbe, 0xa2, 0xfd, 0xe5, 0xa7, 0xbd, 0x3f, 0xff, 0x26, - 0xd8, 0x4b, 0x78, 0x9f, 0x73, 0xf9, 0x4f, 0x5f, 0x09, 0x26, 0x82, 0xc6, 0xb3, 0x70, 0xc8, 0xec, - 0x80, 0x0e, 0x89, 0xa2, 0xdb, 0x6c, 0xc2, 0xba, 0x28, 0xa3, 0xec, 0x89, 0xa8, 0x4d, 0xb4, 0xf8, - 0x0e, 0x6c, 0xe4, 0xc5, 0xea, 0x8b, 0x72, 0x13, 0xca, 0x8a, 0x33, 0x26, 0xda, 0xa3, 0x28, 0xb6, - 0x8b, 0xe5, 0xb8, 0xfa, 0xcc, 0xbc, 0x0b, 0x2d, 0x8b, 0x8c, 0x28, 0xe3, 0x24, 0xc8, 0xb8, 0xea, - 0xb6, 0xda, 0x4a, 0xc9, 0x8e, 0xb7, 0x12, 0xc5, 0x9a, 0x79, 0x0f, 0xb6, 0x8f, 0xdd, 0xe0, 0x05, - 0x1c, 0xeb, 0x50, 0x7b, 0xc6, 0x31, 0x0f, 0x93, 0x9c, 0x7f, 0x28, 0x42, 0x29, 0x96, 0x20, 0x13, - 0x4a, 0xa1, 0xfc, 0x20, 0x49, 0x9f, 0x4a, 0x17, 0xda, 0xd1, 0xb6, 0x66, 0x89, 0x22, 0x30, 0x4b, - 0x69, 0x50, 0x07, 0x6a, 0xf1, 0xd3, 0x20, 0x74, 0xa9, 0x40, 0x90, 0x2b, 0x51, 0xde, 0xb4, 0x1a, - 0x1b, 0x1c, 0x4b, 0x3d, 0x7a, 0x5b, 0x6c, 0x49, 0xfa, 0x32, 0xad, 0x4c, 0xd9, 0x26, 0x3a, 0xf4, - 0x3e, 0x54, 0x52, 0x2e, 0x99, 0xea, 0xc0, 0xac, 0x69, 0x56, 0x8d, 0xee, 0x43, 0x86, 0x79, 0xa6, - 0x73, 0xd9, 0x9c, 0x72, 0x5a, 0xcb, 0x58, 0xa9, 0x84, 0x3e, 0x81, 0x8d, 0xac, 0x2b, 0xb6, 0x6d, - 0xe2, 0x8b, 0xc9, 0x56, 0xed, 0x96, 0x75, 0xce, 0x74, 0x25, 0xeb, 0x29, 0x33, 0xf4, 0x11, 0xd4, - 0x9c, 0xe4, 0x42, 0x88, 0x36, 0x80, 0xb8, 0xb3, 0x1a, 0xd2, 0xef, 0x29, 0x09, 0xec, 0x68, 0x6b, - 0x9c, 0x08, 0xef, 0xbc, 0x19, 0x7a, 0x0f, 0xd6, 0xc4, 0x8a, 0xe7, 0x12, 0x5b, 0x80, 0x0c, 0x02, - 0x2f, 0x14, 0xbc, 0x31, 0xe3, 0x1d, 0xb9, 0x4f, 0x36, 0x12, 0x85, 0x15, 0xcb, 0xd1, 0x6d, 0x40, - 0xa9, 0xf1, 0x18, 0xbb, 0xce, 0x24, 0xb2, 0x7e, 0x57, 0x5a, 0xa7, 0x30, 0x9f, 0x2b, 0x45, 0xf7, - 0x9b, 0x25, 0x28, 0x1d, 0xc8, 0xc6, 0x16, 0x2b, 0xca, 0x4a, 0x8f, 0x31, 0xcf, 0xa6, 0xd1, 0xd7, - 0xac, 0xa9, 0xdb, 0x3d, 0xb7, 0x67, 0xb4, 0xe6, 0x7d, 0x0f, 0xf7, 0x0b, 0x1f, 0x14, 0xd0, 0x17, - 0xb0, 0x92, 0xb4, 0x3b, 0x32, 0xb4, 0xe5, 0xd5, 0x09, 0x68, 0xbd, 0x99, 0x4e, 0xd2, 0x9c, 0x75, - 0x46, 0x60, 0xb5, 0x61, 0xf9, 0x69, 0x38, 0x9c, 0x50, 0x36, 0x46, 0xf3, 0x62, 0xb6, 0xca, 0xb2, - 0x70, 0x3d, 0xfb, 0x74, 0xbf, 0x20, 0x26, 0xb7, 0xac, 0x46, 0x92, 0xa0, 0xdd, 0xf9, 0xa3, 0x1a, - 0x67, 0x70, 0xed, 0x2c, 0x77, 0xbf, 0x5b, 0x82, 0x5a, 0x5c, 0x96, 0x3e, 0x76, 0x45, 0xac, 0x00, - 0x3d, 0x86, 0x6a, 0x76, 0x40, 0xd1, 0x1b, 0x1a, 0x63, 0xc6, 0x34, 0xb7, 0xb6, 0x67, 0x2b, 0xd5, - 0x4c, 0x3f, 0x80, 0xf5, 0x19, 0x83, 0x8b, 0x4c, 0xed, 0x34, 0x7f, 0xaa, 0xd3, 0x57, 0x46, 0x8f, - 0xa0, 0x39, 0x73, 0x8c, 0xd1, 0x5b, 0x09, 0x73, 0xff, 0x30, 0xe5, 0x19, 0xa0, 0x2e, 0xac, 0x3c, - 0x22, 0x5c, 0xcd, 0x71, 0x42, 0x7b, 0x6e, 0xd2, 0x5b, 0xab, 0x79, 0xf1, 0x41, 0xe3, 0xa7, 0xdf, - 0x77, 0x0a, 0x3f, 0x8b, 0xdf, 0x6f, 0xe2, 0xf7, 0xed, 0x1f, 0x3b, 0xaf, 0x0d, 0x4b, 0xf2, 0xaa, - 0xfd, 0xf0, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xf0, 0x69, 0xae, 0xa8, 0x3a, 0x0e, 0x00, 0x00, + // 1106 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xcd, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0x71, 0xeb, 0x38, 0xcf, 0x9f, 0x99, 0xc4, 0xc9, 0xc6, 0x44, 0x49, 0xd8, 0x22, 0x14, + 0x3e, 0x6a, 0x53, 0xa3, 0x50, 0x45, 0x08, 0x2a, 0xa7, 0xa9, 0x42, 0x91, 0x5c, 0xaa, 0x6d, 0x72, + 0xe1, 0x62, 0xad, 0x77, 0x27, 0xf6, 0x28, 0xce, 0xee, 0xb2, 0x33, 0x9b, 0x34, 0x37, 0xfe, 0x08, + 0x0e, 0x1c, 0xf8, 0x67, 0x50, 0x2f, 0x1c, 0x39, 0x73, 0x40, 0x08, 0x2e, 0xdc, 0xf8, 0x17, 0x98, + 0x9d, 0x9d, 0xd9, 0x8f, 0xd8, 0x26, 0x6e, 0x11, 0x52, 0x0f, 0x3d, 0xac, 0x76, 0xe7, 0x7d, 0xfc, + 0xe6, 0xcd, 0x7b, 0xef, 0x37, 0xfb, 0xe0, 0xfe, 0x90, 0xb0, 0x51, 0x30, 0x68, 0x59, 0xee, 0x79, + 0xfb, 0x78, 0x84, 0x8f, 0x47, 0xc4, 0x19, 0xd2, 0x27, 0x98, 0x5d, 0xba, 0xfe, 0x59, 0x9b, 0x31, + 0xa7, 0x6d, 0x7a, 0xa4, 0x3d, 0xf0, 0xdd, 0x33, 0xec, 0xcb, 0x57, 0xcb, 0xf3, 0x5d, 0xe6, 0xa2, + 0x42, 0xb4, 0x6a, 0xde, 0x4d, 0x01, 0x0c, 0xdd, 0xa1, 0xdb, 0x16, 0xea, 0x41, 0x70, 0x2a, 0x56, + 0x62, 0x21, 0xbe, 0x22, 0xb7, 0x8c, 0xf9, 0xcc, 0xfd, 0xf8, 0x23, 0xcd, 0x3f, 0x9b, 0xc7, 0x5c, + 0x98, 0x5a, 0xee, 0x38, 0xfe, 0x90, 0xce, 0xfb, 0xf3, 0x38, 0x0f, 0x4d, 0x86, 0x2f, 0xcd, 0x2b, + 0xf5, 0x8e, 0x5c, 0xf5, 0x17, 0x0b, 0x50, 0x3d, 0x74, 0x2f, 0x9d, 0x31, 0x71, 0xce, 0xbe, 0xf6, + 0x18, 0x71, 0x1d, 0xb4, 0x05, 0x40, 0x6c, 0xec, 0x30, 0x72, 0x4a, 0xb0, 0xaf, 0xe5, 0x76, 0x72, + 0xbb, 0x4b, 0x46, 0x4a, 0x82, 0xbe, 0x81, 0x92, 0xc4, 0xe8, 0xe3, 0x80, 0x68, 0x0b, 0xdc, 0xa0, + 0x7c, 0xb0, 0xff, 0xeb, 0x6f, 0xdb, 0x7b, 0x37, 0x85, 0x61, 0xb9, 0x3e, 0x6e, 0xb3, 0x2b, 0x0f, + 0xd3, 0xd6, 0x51, 0x84, 0xf0, 0xe8, 0xe4, 0xb1, 0x01, 0x12, 0xed, 0x51, 0x40, 0xd0, 0x2a, 0xdc, + 0xa6, 0xa1, 0x95, 0x96, 0xe7, 0xa8, 0x15, 0x23, 0x5a, 0xa0, 0x26, 0x14, 0x6d, 0x6c, 0xda, 0x3c, + 0x46, 0xac, 0xdd, 0xe2, 0x8a, 0xbc, 0x11, 0xaf, 0xd1, 0x01, 0xd4, 0x54, 0x36, 0xfa, 0x96, 0xeb, + 0x9c, 0x92, 0xa1, 0x76, 0x9b, 0x9b, 0x94, 0x3a, 0x1b, 0xad, 0x38, 0x4b, 0xc7, 0xcf, 0x1f, 0x0a, + 0x4d, 0xe0, 0x9b, 0xe1, 0x09, 0x8d, 0xaa, 0xd2, 0x44, 0x62, 0xf4, 0x00, 0xaa, 0xea, 0x44, 0x12, + 0xa2, 0x20, 0x20, 0xb4, 0x96, 0x4a, 0xd6, 0x75, 0x84, 0x8a, 0x54, 0x44, 0x52, 0xfd, 0xef, 0x1c, + 0x54, 0x4e, 0xbc, 0x30, 0x87, 0x3d, 0x4c, 0xa9, 0x39, 0xc4, 0x48, 0x83, 0x45, 0xcf, 0xbc, 0x1a, + 0xbb, 0xa6, 0x2d, 0x32, 0x58, 0x36, 0xd4, 0x12, 0x75, 0x61, 0x39, 0x0e, 0xf8, 0x1c, 0x33, 0xd3, + 0x36, 0x99, 0xa9, 0x95, 0xc4, 0x7e, 0xab, 0x49, 0xc8, 0xc6, 0xf3, 0x9e, 0xd4, 0x19, 0x75, 0x25, + 0x54, 0x12, 0xf4, 0x05, 0xd4, 0x55, 0xbc, 0x31, 0x42, 0x59, 0x20, 0xac, 0xc4, 0x11, 0xa7, 0x00, + 0x6a, 0x52, 0x16, 0xfb, 0x77, 0xa1, 0x6e, 0xcb, 0x9a, 0xf7, 0x5d, 0x51, 0x74, 0xaa, 0x35, 0x76, + 0xf2, 0xdc, 0x7f, 0xad, 0x25, 0x7b, 0x3f, 0xdb, 0x13, 0x46, 0xcd, 0xce, 0xac, 0xa9, 0x3e, 0x86, + 0x9a, 0x32, 0xb9, 0xf9, 0xc8, 0x0f, 0xa0, 0x76, 0x6d, 0x3f, 0x79, 0xe0, 0x59, 0xdb, 0x55, 0xb3, + 0xdb, 0xe9, 0x01, 0x68, 0x87, 0xf8, 0x82, 0x58, 0xb8, 0x6b, 0x31, 0x72, 0x11, 0x95, 0x00, 0x53, + 0x8f, 0x07, 0xf2, 0xbf, 0x6e, 0xfb, 0x22, 0x0f, 0x1b, 0x87, 0xd8, 0x0e, 0x78, 0x65, 0x2d, 0x9e, + 0x42, 0x7b, 0xde, 0x12, 0x3f, 0x81, 0x45, 0x1b, 0x5f, 0x08, 0x76, 0x94, 0x04, 0x3b, 0xf6, 0x38, + 0x3b, 0xee, 0xbd, 0x04, 0x3b, 0xf8, 0x61, 0x43, 0x66, 0x14, 0x38, 0x4a, 0xc8, 0x0a, 0x8e, 0x67, + 0x7a, 0x9e, 0xc0, 0x2b, 0xbf, 0x12, 0x5e, 0xd7, 0xf3, 0x04, 0x1e, 0x47, 0x09, 0xf1, 0xa6, 0xb6, + 0x60, 0xe3, 0x3f, 0xb7, 0xe0, 0x9a, 0x68, 0xa1, 0xf9, 0x5a, 0xf0, 0x10, 0x96, 0x7d, 0x59, 0xc1, + 0x3e, 0xc3, 0xe7, 0xde, 0x98, 0xeb, 0xb5, 0x6d, 0x11, 0xc2, 0xfa, 0xf5, 0xea, 0xc8, 0x84, 0x1b, + 0x75, 0xe5, 0x71, 0x2c, 0x1d, 0xd0, 0x1d, 0xa8, 0x38, 0x18, 0xdb, 0x7d, 0x55, 0x37, 0x6d, 0x87, + 0x23, 0x14, 0x8d, 0x72, 0x28, 0x54, 0xde, 0xfa, 0x5f, 0x79, 0x58, 0x9f, 0xec, 0x9e, 0x6f, 0x03, + 0x4c, 0xd9, 0x9b, 0x1a, 0x4e, 0xd6, 0x70, 0xfe, 0x6b, 0xa4, 0x07, 0x2b, 0x66, 0x9c, 0xd1, 0x04, + 0x62, 0x5d, 0x40, 0x6c, 0x26, 0x41, 0x24, 0x69, 0x8f, 0xb1, 0x90, 0x39, 0x21, 0x9b, 0x7a, 0x2b, + 0x6d, 0xbf, 0xdc, 0xad, 0xf4, 0xdd, 0x2d, 0xb8, 0x93, 0x26, 0xec, 0x9b, 0xb2, 0xbf, 0xfe, 0x65, + 0xef, 0xcd, 0xbe, 0x09, 0x76, 0xe2, 0xba, 0xcf, 0xb8, 0xfc, 0x27, 0xaf, 0x04, 0x1d, 0x41, 0xfd, + 0x59, 0x30, 0xa0, 0x96, 0x4f, 0x06, 0x58, 0x96, 0x5b, 0x6f, 0xc0, 0x0a, 0x4f, 0xa3, 0xe8, 0x89, + 0xb0, 0x4d, 0x94, 0xf8, 0x1e, 0xac, 0x66, 0xc5, 0xf2, 0x8f, 0xb2, 0x01, 0x45, 0x59, 0x33, 0xca, + 0xdb, 0x23, 0xcf, 0xc7, 0x9f, 0xc5, 0x28, 0xfb, 0x54, 0xdf, 0x83, 0xa6, 0x81, 0x87, 0x84, 0x32, + 0xec, 0xa7, 0x5c, 0x55, 0x5b, 0xad, 0x27, 0xc5, 0x8e, 0xc6, 0x26, 0x59, 0x35, 0xfd, 0x3e, 0x6c, + 0x9e, 0x38, 0xfe, 0x2b, 0x38, 0xd6, 0xa0, 0xf2, 0x8c, 0x99, 0x2c, 0x88, 0x63, 0xfe, 0x29, 0x0f, + 0x85, 0x48, 0x82, 0x74, 0x28, 0x04, 0xe2, 0x87, 0x24, 0x7c, 0x4a, 0x1d, 0x68, 0x85, 0xe3, 0xa4, + 0xc1, 0x93, 0x40, 0x0d, 0xa9, 0x41, 0x6d, 0xa8, 0x44, 0x5f, 0xfd, 0xc0, 0x21, 0x1c, 0x41, 0x4c, + 0x6b, 0x59, 0xd3, 0x72, 0x64, 0x70, 0x22, 0xf4, 0xe8, 0x3d, 0x3e, 0x6a, 0xa9, 0xcb, 0xb4, 0x34, + 0x61, 0x1b, 0xeb, 0xd0, 0x47, 0x50, 0x4a, 0x6a, 0x49, 0x65, 0x07, 0xa6, 0x4d, 0xd3, 0x6a, 0xb4, + 0x0f, 0xa9, 0xca, 0x53, 0x15, 0xcb, 0xda, 0x84, 0xd3, 0x72, 0xca, 0x4a, 0x06, 0xf4, 0x39, 0xac, + 0xa6, 0x5d, 0x4d, 0xcb, 0xc2, 0x1e, 0x67, 0xb6, 0x6c, 0xb7, 0xb4, 0x73, 0xaa, 0x2b, 0x69, 0x57, + 0x9a, 0xa1, 0x4f, 0xa1, 0x62, 0xc7, 0x17, 0x42, 0x38, 0x01, 0x44, 0x9d, 0x55, 0x17, 0x7e, 0x4f, + 0xb1, 0x6f, 0x85, 0x63, 0xed, 0x98, 0x7b, 0x67, 0xcd, 0xd0, 0x87, 0xb0, 0xcc, 0x47, 0x41, 0x07, + 0x5b, 0x1c, 0xa4, 0xef, 0xbb, 0x01, 0xaf, 0x1b, 0xd5, 0xde, 0x17, 0x43, 0x69, 0x3d, 0x56, 0x18, + 0x91, 0x1c, 0xdd, 0x05, 0x94, 0x18, 0x8f, 0x4c, 0xc7, 0x1e, 0x87, 0xd6, 0x1f, 0x08, 0xeb, 0x04, + 0xe6, 0x4b, 0xa9, 0xe8, 0x7c, 0xbf, 0x00, 0x85, 0x03, 0xd1, 0xd8, 0x7c, 0x44, 0x59, 0xea, 0x52, + 0xea, 0x5a, 0x24, 0xfc, 0x9b, 0x35, 0x54, 0xbb, 0x67, 0xe6, 0x8c, 0xe6, 0xac, 0xff, 0xe1, 0x6e, + 0xee, 0xe3, 0x1c, 0xfa, 0x0a, 0x96, 0xe2, 0x76, 0x47, 0x9a, 0xb2, 0xbc, 0xce, 0x80, 0xe6, 0x3b, + 0x09, 0x93, 0x66, 0x8c, 0x33, 0x1c, 0xab, 0x05, 0x8b, 0x4f, 0x83, 0xc1, 0x98, 0xd0, 0x11, 0x9a, + 0xb5, 0x67, 0xb3, 0x28, 0x12, 0xd7, 0xb5, 0xce, 0x76, 0x73, 0x9c, 0xb9, 0x45, 0x49, 0x49, 0x8c, + 0xb6, 0x67, 0x53, 0x35, 0x8a, 0xe0, 0x46, 0x2e, 0x77, 0x7e, 0x5c, 0x80, 0x4a, 0x94, 0x96, 0x9e, + 0xe9, 0xf0, 0xbd, 0x7c, 0xf4, 0x18, 0xca, 0x69, 0x82, 0xa2, 0xb7, 0x15, 0xc6, 0x14, 0x36, 0x37, + 0x37, 0xa7, 0x2b, 0x25, 0xa7, 0x1f, 0xc2, 0xca, 0x14, 0xe2, 0x22, 0x5d, 0x39, 0xcd, 0x66, 0x75, + 0x72, 0x64, 0x74, 0x04, 0x8d, 0xa9, 0x34, 0x46, 0xef, 0xc6, 0x95, 0xfb, 0x17, 0x96, 0xa7, 0x80, + 0x3a, 0xb0, 0x74, 0x84, 0x99, 0xe4, 0x71, 0x5c, 0xf6, 0x0c, 0xd3, 0x9b, 0xd5, 0xac, 0xf8, 0xa0, + 0xfe, 0xf3, 0x1f, 0x5b, 0xb9, 0x5f, 0xf8, 0xf3, 0x3b, 0x7f, 0x7e, 0xf8, 0x73, 0xeb, 0xad, 0x41, + 0x41, 0x5c, 0xb5, 0x9f, 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0xd0, 0xf0, 0x03, 0x38, 0xdb, 0x0e, + 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 00b4f7086..ebda44adf 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -10,9 +10,11 @@ package broker; message DownlinkOption { string identifier = 1; - uint32 score = 2; // lower is better, 0 is best - int64 deadline = 3; // deadline time at server represented as the number of nanoseconds elapsed since January 1, 1970 UTC - gateway.TxConfiguration configuration = 4; + bytes gateway_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; + uint32 score = 3; // lower is better, 0 is best + int64 deadline = 4; // deadline time at server represented as the number of nanoseconds elapsed since January 1, 1970 UTC + protocol.TxConfiguration protocol_config = 5; + gateway.TxConfiguration gateway_config = 6; } // received from the Router diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 3566d11a7..f80975bd4 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -19,8 +19,11 @@ package gateway import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import _ "github.com/gogo/protobuf/gogoproto" import semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + import io "io" // Reference imports to suppress errors if they are not otherwise used. @@ -45,9 +48,10 @@ func (*GPSMetadata) ProtoMessage() {} func (*GPSMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{0} } type RxMetadata struct { - Frequency uint64 `protobuf:"varint,1,opt,name=frequency,proto3" json:"frequency,omitempty"` - Rssi float32 `protobuf:"fixed32,2,opt,name=rssi,proto3" json:"rssi,omitempty"` - Snr float32 `protobuf:"fixed32,3,opt,name=snr,proto3" json:"snr,omitempty"` + GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` + Frequency uint64 `protobuf:"varint,11,opt,name=frequency,proto3" json:"frequency,omitempty"` + Rssi float32 `protobuf:"fixed32,12,opt,name=rssi,proto3" json:"rssi,omitempty"` + Snr float32 `protobuf:"fixed32,13,opt,name=snr,proto3" json:"snr,omitempty"` // Types that are valid to be assigned to Gateway: // *RxMetadata_Semtech Gateway isRxMetadata_Gateway `protobuf_oneof:"gateway"` @@ -65,7 +69,7 @@ type isRxMetadata_Gateway interface { } type RxMetadata_Semtech struct { - Semtech *semtech.RxMetadata `protobuf:"bytes,11,opt,name=semtech,oneof"` + Semtech *semtech.RxMetadata `protobuf:"bytes,21,opt,name=semtech,oneof"` } func (*RxMetadata_Semtech) isRxMetadata_Gateway() {} @@ -96,7 +100,7 @@ func _RxMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { // gateway switch x := m.Gateway.(type) { case *RxMetadata_Semtech: - _ = b.EncodeVarint(11<<3 | proto.WireBytes) + _ = b.EncodeVarint(21<<3 | proto.WireBytes) if err := b.EncodeMessage(x.Semtech); err != nil { return err } @@ -110,7 +114,7 @@ func _RxMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { func _RxMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { m := msg.(*RxMetadata) switch tag { - case 11: // gateway.semtech + case 21: // gateway.semtech if wire != proto.WireBytes { return true, proto.ErrInternalBadWireType } @@ -129,7 +133,7 @@ func _RxMetadata_OneofSizer(msg proto.Message) (n int) { switch x := m.Gateway.(type) { case *RxMetadata_Semtech: s := proto.Size(x.Semtech) - n += proto.SizeVarint(11<<3 | proto.WireBytes) + n += proto.SizeVarint(21<<3 | proto.WireBytes) n += proto.SizeVarint(uint64(s)) n += s case nil: @@ -322,27 +326,37 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if m.GatewayEui != nil { + data[i] = 0xa + i++ + i = encodeVarintGateway(data, i, uint64(m.GatewayEui.Size())) + n1, err := m.GatewayEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } if m.Frequency != 0 { - data[i] = 0x8 + data[i] = 0x58 i++ i = encodeVarintGateway(data, i, uint64(m.Frequency)) } if m.Rssi != 0 { - data[i] = 0x15 + data[i] = 0x65 i++ i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Rssi)))) } if m.Snr != 0 { - data[i] = 0x1d + data[i] = 0x6d i++ i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Snr)))) } if m.Gateway != nil { - nn1, err := m.Gateway.MarshalTo(data[i:]) + nn2, err := m.Gateway.MarshalTo(data[i:]) if err != nil { return 0, err } - i += nn1 + i += nn2 } return i, nil } @@ -350,14 +364,16 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { func (m *RxMetadata_Semtech) MarshalTo(data []byte) (int, error) { i := 0 if m.Semtech != nil { - data[i] = 0x5a + data[i] = 0xaa + i++ + data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) - n2, err := m.Semtech.MarshalTo(data[i:]) + n3, err := m.Semtech.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n3 } return i, nil } @@ -387,11 +403,11 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { i = encodeVarintGateway(data, i, uint64(m.Power)) } if m.Gateway != nil { - nn3, err := m.Gateway.MarshalTo(data[i:]) + nn4, err := m.Gateway.MarshalTo(data[i:]) if err != nil { return 0, err } - i += nn3 + i += nn4 } return i, nil } @@ -402,11 +418,11 @@ func (m *TxConfiguration_Semtech) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) - n4, err := m.Semtech.MarshalTo(data[i:]) + n5, err := m.Semtech.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n5 } return i, nil } @@ -480,11 +496,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n5, err := m.Gps.MarshalTo(data[i:]) + n6, err := m.Gps.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n6 } if m.Rtt != 0 { data[i] = 0xf8 @@ -572,6 +588,10 @@ func (m *GPSMetadata) Size() (n int) { func (m *RxMetadata) Size() (n int) { var l int _ = l + if m.GatewayEui != nil { + l = m.GatewayEui.Size() + n += 1 + l + sovGateway(uint64(l)) + } if m.Frequency != 0 { n += 1 + sovGateway(uint64(m.Frequency)) } @@ -592,7 +612,7 @@ func (m *RxMetadata_Semtech) Size() (n int) { _ = l if m.Semtech != nil { l = m.Semtech.Size() - n += 1 + l + sovGateway(uint64(l)) + n += 2 + l + sovGateway(uint64(l)) } return n } @@ -832,6 +852,38 @@ func (m *RxMetadata) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayEui = &v + if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) } @@ -850,7 +902,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { break } } - case 2: + case 12: if wireType != 5 { return fmt.Errorf("proto: wrong wireType = %d for field Rssi", wireType) } @@ -864,7 +916,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { v |= uint32(data[iNdEx-2]) << 16 v |= uint32(data[iNdEx-1]) << 24 m.Rssi = float32(math.Float32frombits(v)) - case 3: + case 13: if wireType != 5 { return fmt.Errorf("proto: wrong wireType = %d for field Snr", wireType) } @@ -878,7 +930,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { v |= uint32(data[iNdEx-2]) << 16 v |= uint32(data[iNdEx-1]) << 24 m.Snr = float32(math.Float32frombits(v)) - case 11: + case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) } @@ -1518,36 +1570,41 @@ var ( ) var fileDescriptorGateway = []byte{ - // 492 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x52, 0xdd, 0x6e, 0xd3, 0x30, - 0x14, 0x5e, 0x92, 0xb6, 0xa3, 0xa7, 0xeb, 0x36, 0x79, 0x03, 0x59, 0x13, 0x1a, 0x53, 0x91, 0xd0, - 0xf8, 0x51, 0x23, 0x01, 0x37, 0x5c, 0x32, 0x84, 0x80, 0x0b, 0x18, 0xf2, 0x7a, 0x3f, 0x79, 0xa9, - 0x9b, 0x5a, 0x6d, 0xec, 0xe0, 0x9c, 0xa8, 0xdd, 0x0b, 0x70, 0xc5, 0x03, 0xf0, 0x48, 0x5c, 0x22, - 0xf1, 0x02, 0x08, 0x5e, 0x04, 0xdb, 0x4d, 0xd2, 0x6a, 0x12, 0x42, 0x5c, 0x44, 0x39, 0xe7, 0x3b, - 0x9f, 0xcf, 0xf7, 0x1d, 0x1f, 0xc3, 0x8b, 0x54, 0xe2, 0xb4, 0xbc, 0x1a, 0x26, 0x3a, 0x8b, 0x47, - 0x53, 0x31, 0x9a, 0x4a, 0x95, 0x16, 0x1f, 0x04, 0x2e, 0xb4, 0x99, 0xc5, 0x88, 0x2a, 0xe6, 0xb9, - 0x8c, 0x53, 0x8e, 0x62, 0xc1, 0xaf, 0xeb, 0xff, 0x30, 0x37, 0x1a, 0x35, 0xd9, 0xae, 0xd2, 0xa3, - 0x97, 0xff, 0xd3, 0xa3, 0x10, 0x19, 0x8a, 0x64, 0x5a, 0xff, 0x57, 0xbd, 0x06, 0x0b, 0xe8, 0xbd, - 0xf9, 0x78, 0xf1, 0x5e, 0x20, 0x1f, 0x73, 0xe4, 0x84, 0x40, 0x0b, 0x65, 0x26, 0x68, 0x70, 0x12, - 0x9c, 0x46, 0xcc, 0xc7, 0xe4, 0x08, 0x6e, 0xcd, 0x39, 0x4a, 0x2c, 0xc7, 0x82, 0x86, 0x16, 0x0f, - 0x59, 0x93, 0x93, 0xbb, 0xd0, 0x9d, 0x6b, 0x95, 0xae, 0x8a, 0x91, 0x2f, 0xae, 0x01, 0x77, 0x92, - 0xcf, 0xab, 0x93, 0x2d, 0x5b, 0x6c, 0xb3, 0x26, 0x1f, 0x7c, 0x09, 0x00, 0xd8, 0xb2, 0x11, 0xb6, - 0x8d, 0x26, 0x46, 0x7c, 0x2a, 0x85, 0x4a, 0xae, 0xbd, 0x7a, 0x8b, 0xad, 0x01, 0x67, 0xcb, 0x14, - 0x85, 0xac, 0xe4, 0x7d, 0x4c, 0xf6, 0x21, 0x2a, 0x94, 0xa9, 0x44, 0x5d, 0x48, 0x62, 0xd8, 0xae, - 0x86, 0xa3, 0x3d, 0x8b, 0xf6, 0x9e, 0x1e, 0x0c, 0xeb, 0x61, 0xd7, 0x4a, 0x6f, 0xb7, 0x58, 0xcd, - 0x3a, 0xeb, 0x42, 0x7d, 0x95, 0x83, 0xcf, 0x01, 0xec, 0x8d, 0x96, 0xaf, 0xb4, 0x9a, 0xc8, 0xb4, - 0x34, 0x76, 0x3c, 0xad, 0xfe, 0xe1, 0xe9, 0x10, 0xda, 0xb9, 0x5e, 0x08, 0xe3, 0x4d, 0xb5, 0xd9, - 0x2a, 0x21, 0xcf, 0x6f, 0x7a, 0xa0, 0x8d, 0x87, 0x1b, 0xed, 0xff, 0x62, 0xe4, 0x47, 0x08, 0x9d, - 0x0b, 0xe4, 0x58, 0x16, 0x4e, 0xdf, 0x2d, 0xa0, 0x40, 0x9e, 0xe5, 0x5e, 0xbf, 0xcf, 0xd6, 0x40, - 0xb3, 0xaa, 0x70, 0x63, 0x55, 0xbb, 0x10, 0xca, 0xdc, 0x0a, 0x47, 0xa7, 0x5d, 0x66, 0x23, 0xb7, - 0x80, 0xdc, 0xee, 0x6a, 0xa2, 0x4d, 0x46, 0x77, 0x2c, 0xaf, 0xcb, 0x9a, 0x9c, 0xdc, 0x87, 0x7e, - 0xa2, 0x15, 0xf2, 0x04, 0x2f, 0x45, 0xc6, 0xe5, 0x9c, 0xf6, 0x3d, 0x61, 0xa7, 0x02, 0x5f, 0x3b, - 0x8c, 0x9c, 0x40, 0x6f, 0x2c, 0x8a, 0xc4, 0xc8, 0xdc, 0x59, 0xa6, 0xbb, 0x9e, 0xb2, 0x09, 0x91, - 0x3b, 0xd0, 0x31, 0x22, 0x75, 0xc5, 0x3d, 0x5f, 0xac, 0x32, 0xf2, 0x00, 0xa2, 0x34, 0x2f, 0xe8, - 0x6d, 0x7f, 0x09, 0x87, 0xc3, 0xfa, 0x05, 0x6f, 0x3c, 0x36, 0xe6, 0x08, 0x6e, 0x8d, 0x06, 0x91, - 0xde, 0xf3, 0xe3, 0xb9, 0x90, 0x1c, 0x40, 0xdb, 0x2c, 0x2f, 0xa5, 0xa2, 0x0f, 0x3d, 0xd6, 0x32, - 0xcb, 0x77, 0xaa, 0x02, 0xf5, 0x8c, 0x3e, 0xaa, 0xc1, 0xf3, 0x99, 0x03, 0xd1, 0x33, 0x1f, 0xaf, - 0x40, 0xac, 0x98, 0xe8, 0x99, 0x4f, 0x6a, 0xf0, 0x7c, 0x76, 0xb6, 0xff, 0xed, 0xd7, 0x71, 0xf0, - 0xdd, 0x7e, 0x3f, 0xed, 0xf7, 0xf5, 0xf7, 0xf1, 0xd6, 0x55, 0xc7, 0xbf, 0xff, 0x67, 0x7f, 0x02, - 0x00, 0x00, 0xff, 0xff, 0xa4, 0xf2, 0xc5, 0x82, 0x88, 0x03, 0x00, 0x00, + // 561 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xae, 0xf3, 0xd3, 0x92, 0x75, 0xd2, 0x56, 0xdb, 0x82, 0xac, 0x08, 0xb5, 0x55, 0x90, 0x50, + 0xf9, 0x8b, 0x25, 0x7e, 0x0e, 0x3d, 0x12, 0x14, 0x95, 0x1e, 0xa0, 0x68, 0x1b, 0x2e, 0x5c, 0xa2, + 0x8d, 0xb3, 0x71, 0x56, 0x89, 0xbd, 0x66, 0x3d, 0x56, 0x92, 0x17, 0xe0, 0x19, 0x78, 0x24, 0x8e, + 0x48, 0xdc, 0x38, 0x20, 0x04, 0x17, 0x1e, 0x03, 0xef, 0xf8, 0x27, 0xa1, 0x12, 0x42, 0x1c, 0x2c, + 0xcf, 0xf7, 0xcd, 0xec, 0xcc, 0xb7, 0x33, 0xb3, 0xe4, 0xcc, 0x97, 0x30, 0x4d, 0x46, 0x5d, 0x4f, + 0x05, 0xee, 0x60, 0x2a, 0x06, 0x53, 0x19, 0xfa, 0xf1, 0x6b, 0x01, 0x0b, 0xa5, 0x67, 0x2e, 0x40, + 0xe8, 0xf2, 0x48, 0xba, 0x3e, 0x07, 0xb1, 0xe0, 0xab, 0xe2, 0xdf, 0x8d, 0xb4, 0x02, 0x45, 0x77, + 0x72, 0xd8, 0x7e, 0xb4, 0x91, 0xc3, 0x57, 0xbe, 0x72, 0xd1, 0x3f, 0x4a, 0x26, 0x88, 0x10, 0xa0, + 0x95, 0x9d, 0x6b, 0x3f, 0xff, 0x9f, 0x92, 0xb1, 0x08, 0x40, 0x78, 0xd3, 0xe2, 0x9f, 0xa5, 0xe8, + 0x2c, 0x88, 0x7d, 0xfe, 0xe6, 0xea, 0x95, 0x00, 0x3e, 0xe6, 0xc0, 0x29, 0x25, 0x35, 0x90, 0x81, + 0x70, 0xac, 0x13, 0xeb, 0xb4, 0xca, 0xd0, 0xa6, 0x6d, 0x72, 0x63, 0xce, 0x41, 0x42, 0x32, 0x16, + 0x4e, 0x25, 0xe5, 0x2b, 0xac, 0xc4, 0xf4, 0x36, 0x69, 0xcc, 0x55, 0xe8, 0x67, 0xce, 0x2a, 0x3a, + 0xd7, 0x84, 0x39, 0xc9, 0xe7, 0xf9, 0xc9, 0x5a, 0xea, 0xac, 0xb3, 0x12, 0x77, 0x7e, 0x59, 0x84, + 0xb0, 0x65, 0x59, 0xf8, 0x1d, 0xb1, 0x73, 0xa1, 0x43, 0x91, 0x48, 0xac, 0xdf, 0xec, 0x9d, 0x7d, + 0xfd, 0x76, 0xfc, 0xec, 0x5f, 0x77, 0xf4, 0x94, 0x16, 0x2e, 0xac, 0x22, 0x11, 0x77, 0xcf, 0xb3, + 0x0c, 0xfd, 0xb7, 0x17, 0x8c, 0xe4, 0xd9, 0xfa, 0x89, 0x34, 0x22, 0x27, 0x5a, 0xbc, 0x4f, 0x44, + 0xe8, 0xad, 0x1c, 0x3b, 0xcd, 0x5c, 0x63, 0x6b, 0xc2, 0x5c, 0x59, 0xc7, 0xb1, 0x74, 0x9a, 0xa8, + 0x1e, 0x6d, 0xba, 0x4f, 0xaa, 0x71, 0xa8, 0x9d, 0x16, 0x52, 0xc6, 0xa4, 0x2e, 0xd9, 0xc9, 0x1b, + 0xe7, 0xdc, 0x4c, 0x59, 0xfb, 0xf1, 0x41, 0xb7, 0x68, 0xe4, 0xfa, 0x16, 0x2f, 0xb7, 0x58, 0x11, + 0xd5, 0x6b, 0x90, 0x62, 0xaa, 0x9d, 0x0f, 0x16, 0xd9, 0x1b, 0x2c, 0x5f, 0xa8, 0x70, 0x22, 0xfd, + 0x44, 0xa7, 0xad, 0x53, 0xe1, 0x9f, 0x9a, 0xac, 0xeb, 0x9a, 0x0e, 0x49, 0x3d, 0x52, 0x0b, 0xa1, + 0xb1, 0xdf, 0x75, 0x96, 0x01, 0xfa, 0x74, 0xad, 0xc1, 0x46, 0x0d, 0x4e, 0xa9, 0xe1, 0x5a, 0xfa, + 0xbf, 0x08, 0xf9, 0x52, 0x21, 0xdb, 0x57, 0xc0, 0x21, 0x89, 0x4d, 0x7d, 0x33, 0xdc, 0x18, 0x78, + 0x10, 0x61, 0xfd, 0x16, 0x5b, 0x13, 0xe5, 0x1a, 0x54, 0x36, 0xd6, 0x60, 0x97, 0x54, 0x64, 0x94, + 0x16, 0xae, 0x9e, 0x36, 0x58, 0x6a, 0x99, 0xe1, 0x46, 0xe9, 0x1e, 0x4c, 0x94, 0x0e, 0xb0, 0x77, + 0x0d, 0x56, 0x62, 0x7a, 0x87, 0xb4, 0x3c, 0x15, 0x02, 0xf7, 0x60, 0x28, 0x02, 0x2e, 0xe7, 0xd8, + 0xc9, 0x06, 0x6b, 0xe6, 0x64, 0xdf, 0x70, 0xf4, 0x84, 0xd8, 0x63, 0x11, 0x7b, 0x5a, 0x46, 0x46, + 0xb2, 0xb3, 0x8b, 0x21, 0x9b, 0x14, 0xbd, 0x45, 0xb6, 0xb5, 0xf0, 0x8d, 0x73, 0x0f, 0x9d, 0x39, + 0xa2, 0x77, 0x49, 0xd5, 0x8f, 0xe2, 0x7c, 0x10, 0x87, 0xdd, 0xe2, 0x31, 0x6d, 0x2c, 0x32, 0x33, + 0x01, 0x66, 0x8c, 0x1a, 0xc0, 0x39, 0xc6, 0xeb, 0x19, 0x93, 0x1e, 0x90, 0xba, 0x5e, 0x0e, 0x65, + 0xe8, 0xdc, 0x43, 0xae, 0xa6, 0x97, 0x17, 0x61, 0x4e, 0xaa, 0x99, 0x73, 0xbf, 0x20, 0x2f, 0x67, + 0x86, 0x04, 0x8c, 0x7c, 0x90, 0x91, 0x90, 0x47, 0x02, 0x46, 0x3e, 0x2c, 0xc8, 0xcb, 0x59, 0x6f, + 0xff, 0xd3, 0x8f, 0x23, 0xeb, 0x73, 0xfa, 0x7d, 0x4f, 0xbf, 0x8f, 0x3f, 0x8f, 0xb6, 0x46, 0xdb, + 0xf8, 0xb6, 0x9e, 0xfc, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x53, 0x5b, 0x1b, 0xe0, 0x13, 0x04, 0x00, + 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 9e4f9b140..24a60b4ac 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -1,5 +1,7 @@ syntax = "proto3"; +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + import "github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto"; package gateway; @@ -12,11 +14,14 @@ message GPSMetadata { } message RxMetadata { - uint64 frequency = 1; // frequency in Hz - float rssi = 2; // received signal strength in dBm - float snr = 3; // signal-to-noise-ratio in dB + bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; + + uint64 frequency = 11; // frequency in Hz + float rssi = 12; // received signal strength in dBm + float snr = 13; // signal-to-noise-ratio in dB + oneof gateway { - semtech.RxMetadata semtech = 11; + semtech.RxMetadata semtech = 21; } } diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 172f583af..79f049129 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -105,11 +105,10 @@ func (*Metadata) ProtoMessage() {} func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } type TxConfiguration struct { - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` - Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` - DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` - BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` - CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` + Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` + DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` + BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` + CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` } func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } @@ -279,16 +278,6 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevAddr != nil { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n1, err := m.DevAddr.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 - } if m.Modulation != 0 { data[i] = 0x58 i++ @@ -333,21 +322,21 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n2, err := m.DevAddr.MarshalTo(data[i:]) + n1, err := m.DevAddr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n1 } if m.NwkSKey != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.NwkSKey.Size())) - n3, err := m.NwkSKey.MarshalTo(data[i:]) + n2, err := m.NwkSKey.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n2 } return i, nil } @@ -371,21 +360,21 @@ func (m *PHYPayload) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.Mhdr.Size())) - n4, err := m.Mhdr.MarshalTo(data[i:]) + n3, err := m.Mhdr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n3 } if m.MACPayload != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) - n5, err := m.MACPayload.MarshalTo(data[i:]) + n4, err := m.MACPayload.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n4 } if len(m.Mic) > 0 { data[i] = 0x1a @@ -443,11 +432,11 @@ func (m *MACPayload) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.FHdr.Size())) - n6, err := m.FHdr.MarshalTo(data[i:]) + n5, err := m.FHdr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n5 } if m.FPort != 0 { data[i] = 0x10 @@ -482,21 +471,21 @@ func (m *FHdr) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n7, err := m.DevAddr.MarshalTo(data[i:]) + n6, err := m.DevAddr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n6 } if m.FCtrl != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) - n8, err := m.FCtrl.MarshalTo(data[i:]) + n7, err := m.FCtrl.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n7 } if m.FCnt != 0 { data[i] = 0x18 @@ -628,10 +617,6 @@ func (m *Metadata) Size() (n int) { func (m *TxConfiguration) Size() (n int) { var l int _ = l - if m.DevAddr != nil { - l = m.DevAddr.Size() - n += 1 + l + sovLorawan(uint64(l)) - } if m.Modulation != 0 { n += 1 + sovLorawan(uint64(m.Modulation)) } @@ -943,38 +928,6 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.DevAddr - m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) @@ -2004,50 +1957,50 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 713 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xd1, 0x6e, 0xda, 0x4a, - 0x10, 0x8d, 0x03, 0x0e, 0x66, 0x08, 0xb9, 0xd6, 0xe6, 0x5e, 0x89, 0x7b, 0x6f, 0x95, 0xa4, 0x96, - 0x2a, 0x55, 0x91, 0x0a, 0x04, 0x92, 0x00, 0x8f, 0x84, 0x26, 0x8a, 0x94, 0x90, 0xd0, 0x0d, 0x3c, - 0xf4, 0xa5, 0x2b, 0x63, 0xaf, 0xc1, 0x35, 0x78, 0x89, 0x59, 0x42, 0xf9, 0x81, 0x7e, 0x43, 0x3e, - 0xa2, 0x7f, 0xd0, 0x1f, 0xe8, 0x63, 0x9f, 0xfb, 0x50, 0x55, 0xed, 0x1f, 0xf4, 0x0b, 0xba, 0xbb, - 0x26, 0x90, 0x87, 0x4a, 0x95, 0x2a, 0x45, 0xea, 0x83, 0xb5, 0x73, 0x66, 0xe6, 0xcc, 0x9c, 0x99, - 0x65, 0x81, 0xa3, 0x9e, 0xcf, 0xfb, 0x93, 0x6e, 0xde, 0x61, 0xc3, 0x42, 0xbb, 0x4f, 0xdb, 0x7d, - 0x3f, 0xec, 0x8d, 0x2f, 0x28, 0x9f, 0xb2, 0x28, 0x28, 0x70, 0x1e, 0x16, 0xec, 0x91, 0x5f, 0x18, - 0x45, 0x8c, 0x33, 0x87, 0x0d, 0x0a, 0x03, 0x16, 0xd9, 0x53, 0x3b, 0xbc, 0x3b, 0xf3, 0x2a, 0x80, - 0x52, 0x73, 0xf8, 0xdf, 0xb3, 0x7b, 0xc5, 0x7a, 0xac, 0xc7, 0x62, 0x62, 0x77, 0xe2, 0x29, 0xa4, - 0x80, 0xb2, 0x62, 0x9e, 0x75, 0xab, 0x81, 0xd1, 0xa4, 0xdc, 0x76, 0x6d, 0x6e, 0xa3, 0x32, 0xc0, - 0x90, 0xb9, 0x93, 0x81, 0xcd, 0x7d, 0x16, 0xe6, 0x32, 0x3b, 0xda, 0xd3, 0x8d, 0xd2, 0x66, 0xfe, - 0xae, 0x51, 0x73, 0x11, 0xc2, 0xf7, 0xd2, 0xd0, 0xff, 0x90, 0x96, 0x64, 0x12, 0xd9, 0x9c, 0xe6, - 0xd6, 0x05, 0x27, 0x8d, 0x0d, 0xe9, 0xc0, 0x02, 0xa3, 0x7f, 0xc1, 0xe8, 0xfa, 0x3c, 0x8e, 0x65, - 0x45, 0x2c, 0x8b, 0x53, 0x02, 0xab, 0xd0, 0x36, 0x64, 0x1c, 0xe6, 0x8a, 0x51, 0xe3, 0xe8, 0x86, - 0x62, 0x42, 0xec, 0x92, 0x09, 0xd6, 0x77, 0x0d, 0xfe, 0x6a, 0xbf, 0x69, 0xb0, 0xd0, 0xf3, 0x7b, - 0x93, 0x28, 0x6e, 0xf6, 0x02, 0x0c, 0x97, 0xde, 0x10, 0xdb, 0x75, 0xa3, 0x9c, 0x26, 0x18, 0xeb, - 0x47, 0x87, 0x9f, 0x3e, 0x6f, 0x97, 0x7e, 0xb5, 0x40, 0x87, 0x45, 0xb4, 0xc0, 0x67, 0x23, 0x3a, - 0xce, 0x3f, 0xa7, 0x37, 0x75, 0xc1, 0xc6, 0x29, 0x37, 0x36, 0xfe, 0xb0, 0xa1, 0xdf, 0x6b, 0x80, - 0xea, 0x0e, 0xf7, 0x6f, 0x54, 0x9f, 0xc5, 0xcd, 0x3c, 0xc0, 0xdc, 0x18, 0xd2, 0xe1, 0x34, 0x20, - 0x63, 0x12, 0xd0, 0x59, 0x6e, 0xf5, 0xb7, 0x6a, 0x5e, 0x4c, 0x83, 0xab, 0x33, 0x3a, 0xc3, 0xa9, - 0x30, 0x36, 0xac, 0x29, 0x40, 0xeb, 0xf4, 0x65, 0xcb, 0x9e, 0x0d, 0x98, 0xed, 0xa2, 0xc7, 0x90, - 0x1c, 0xf6, 0xe7, 0x82, 0x33, 0xa5, 0xec, 0x72, 0xa7, 0xa7, 0x42, 0x87, 0x0a, 0xa1, 0x7d, 0xc8, - 0x34, 0xeb, 0x0d, 0x32, 0x8a, 0x19, 0x4a, 0x46, 0xe6, 0xfe, 0xf6, 0xeb, 0x8d, 0x79, 0x31, 0xb1, - 0xfd, 0x85, 0x8d, 0x4c, 0x48, 0x0c, 0x7d, 0x27, 0x97, 0x90, 0xa2, 0xb1, 0x34, 0xad, 0x32, 0x24, - 0x65, 0x55, 0xf4, 0x0f, 0xac, 0x0d, 0x89, 0x14, 0xa7, 0x9a, 0x66, 0xb1, 0x3e, 0x6c, 0x0b, 0x80, - 0xfe, 0x06, 0x7d, 0x68, 0xbf, 0x66, 0x91, 0x6a, 0x20, 0xbd, 0x12, 0x58, 0x7d, 0x80, 0x65, 0x03, - 0x64, 0x81, 0xee, 0x91, 0x9f, 0xc9, 0x3d, 0x51, 0x72, 0xbd, 0x79, 0x79, 0x8f, 0x8c, 0x58, 0xc4, - 0xef, 0x0a, 0x79, 0x2d, 0x01, 0xe4, 0xad, 0x9e, 0xe0, 0xe6, 0x62, 0x8a, 0x58, 0x17, 0x78, 0xb8, - 0x39, 0xaf, 0x6d, 0xbd, 0xd3, 0x20, 0x29, 0xcb, 0x3c, 0xc4, 0x3d, 0x3e, 0x91, 0x9a, 0x1c, 0x1e, - 0x0d, 0xe6, 0xdb, 0xdb, 0x58, 0x0a, 0x6f, 0x08, 0xaf, 0xd0, 0x28, 0x0f, 0xb4, 0x29, 0xc7, 0x73, - 0x42, 0xae, 0xd4, 0x65, 0xc5, 0x3c, 0x8d, 0x90, 0xc7, 0xf3, 0xb0, 0x11, 0x1f, 0xe7, 0x92, 0x3b, - 0x09, 0xa1, 0x59, 0xf7, 0x2e, 0x05, 0xb0, 0xde, 0x6a, 0xa0, 0x2b, 0xb2, 0xdc, 0xb4, 0x3d, 0x97, - 0x6a, 0x60, 0x69, 0xa2, 0x2d, 0xc8, 0x88, 0x83, 0xd8, 0x4e, 0x40, 0x22, 0x7a, 0xad, 0x7a, 0x1a, - 0x38, 0x2d, 0x5c, 0x75, 0x27, 0xc0, 0xf4, 0x5a, 0x31, 0x9c, 0x40, 0x75, 0x91, 0x0c, 0x27, 0x90, - 0x6f, 0x45, 0x2c, 0x8d, 0x86, 0xf2, 0x37, 0x2e, 0xfa, 0x48, 0xbf, 0xe1, 0xb5, 0x62, 0x8c, 0x1e, - 0x01, 0xc4, 0x0a, 0xc8, 0x80, 0x86, 0x39, 0x5d, 0x6d, 0xce, 0x50, 0x2a, 0xce, 0x69, 0xb8, 0xbb, - 0x2d, 0x6e, 0x68, 0xf9, 0xe8, 0x0c, 0x48, 0x9e, 0x5f, 0xe2, 0xba, 0xb9, 0x82, 0x52, 0x90, 0x38, - 0xb9, 0x3a, 0x33, 0xb5, 0xdd, 0x57, 0xb0, 0x86, 0x69, 0x4f, 0x06, 0xb3, 0x90, 0x3e, 0xee, 0x54, - 0x0f, 0xcb, 0xa4, 0x5a, 0x29, 0x8a, 0x0c, 0x01, 0x3b, 0x57, 0xb5, 0x62, 0x89, 0xd4, 0x4a, 0x55, - 0x53, 0x93, 0xb0, 0x71, 0x51, 0xa9, 0xd4, 0x48, 0xa5, 0x5a, 0x31, 0x57, 0x51, 0x1a, 0xf4, 0xe3, - 0xce, 0x7e, 0xb9, 0x6c, 0x26, 0x64, 0xa4, 0xde, 0xa9, 0xed, 0x1d, 0xa8, 0xc4, 0x64, 0x9c, 0xb8, - 0x5f, 0x29, 0x92, 0x83, 0xbd, 0xa2, 0xa9, 0x1f, 0x99, 0x1f, 0xbe, 0x6e, 0x69, 0x1f, 0xc5, 0xf7, - 0x45, 0x7c, 0xb7, 0xdf, 0xb6, 0x56, 0xba, 0x6b, 0xea, 0x7f, 0xb3, 0xfc, 0x23, 0x00, 0x00, 0xff, - 0xff, 0x1e, 0x91, 0x6f, 0x01, 0xb5, 0x05, 0x00, 0x00, + // 710 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xc1, 0x6e, 0xda, 0x4c, + 0x10, 0x8e, 0x03, 0x0e, 0x66, 0x08, 0xf9, 0xad, 0xcd, 0xff, 0x4b, 0xfc, 0x6d, 0x95, 0xa4, 0x96, + 0x2a, 0x55, 0x91, 0x0a, 0x04, 0x92, 0x00, 0x47, 0x42, 0x13, 0x45, 0x4a, 0x48, 0xe8, 0x06, 0x0e, + 0xbd, 0x74, 0x65, 0xec, 0x35, 0xb8, 0x06, 0x2f, 0x31, 0x4b, 0x28, 0x2f, 0xd0, 0x67, 0xc8, 0xad, + 0x2f, 0xd0, 0x37, 0xe8, 0x0b, 0xf4, 0xd8, 0x73, 0x0f, 0x55, 0xd5, 0xbe, 0x48, 0x77, 0xd7, 0x04, + 0x72, 0xa8, 0x54, 0xa9, 0x52, 0xa5, 0x1e, 0xac, 0x9d, 0x6f, 0x66, 0xbe, 0x99, 0x6f, 0x66, 0x59, + 0xe0, 0xa8, 0xe7, 0xf3, 0xfe, 0xa4, 0x9b, 0x77, 0xd8, 0xb0, 0xd0, 0xee, 0xd3, 0x76, 0xdf, 0x0f, + 0x7b, 0xe3, 0x0b, 0xca, 0xa7, 0x2c, 0x0a, 0x0a, 0x9c, 0x87, 0x05, 0x7b, 0xe4, 0x17, 0x46, 0x11, + 0xe3, 0xcc, 0x61, 0x83, 0xc2, 0x80, 0x45, 0xf6, 0xd4, 0x0e, 0xef, 0xce, 0xbc, 0x0a, 0xa0, 0xd4, + 0x1c, 0x3e, 0x78, 0x76, 0xaf, 0x58, 0x8f, 0xf5, 0x58, 0x4c, 0xec, 0x4e, 0x3c, 0x85, 0x14, 0x50, + 0x56, 0xcc, 0xb3, 0x6e, 0x35, 0x30, 0x9a, 0x94, 0xdb, 0xae, 0xcd, 0x6d, 0x54, 0x06, 0x18, 0x32, + 0x77, 0x32, 0xb0, 0xb9, 0xcf, 0xc2, 0x5c, 0x66, 0x47, 0x7b, 0xba, 0x51, 0xda, 0xcc, 0xdf, 0x35, + 0x6a, 0x2e, 0x42, 0xf8, 0x5e, 0x1a, 0x7a, 0x08, 0x69, 0x49, 0x26, 0x91, 0xcd, 0x69, 0x6e, 0x5d, + 0x70, 0xd2, 0xd8, 0x90, 0x0e, 0x2c, 0x30, 0xfa, 0x1f, 0x8c, 0xae, 0xcf, 0xe3, 0x58, 0x56, 0xc4, + 0xb2, 0x38, 0x25, 0xb0, 0x0a, 0x6d, 0x43, 0xc6, 0x61, 0xae, 0x18, 0x35, 0x8e, 0x6e, 0x28, 0x26, + 0xc4, 0x2e, 0x99, 0x60, 0xbd, 0xd3, 0xe0, 0x9f, 0xf6, 0x9b, 0x06, 0x0b, 0x3d, 0xbf, 0x37, 0x89, + 0xe2, 0x66, 0x7f, 0x97, 0xc2, 0x0f, 0x1a, 0xa0, 0xba, 0xc3, 0xfd, 0x1b, 0xd5, 0x67, 0xb1, 0xc6, + 0x17, 0x60, 0xb8, 0xf4, 0x86, 0xd8, 0xae, 0x1b, 0xe5, 0x34, 0x41, 0x5a, 0x3f, 0x3a, 0xfc, 0xfc, + 0x65, 0xbb, 0xf4, 0xab, 0x5b, 0x76, 0x58, 0x44, 0x0b, 0x7c, 0x36, 0xa2, 0xe3, 0xfc, 0x73, 0x7a, + 0x53, 0x17, 0x6c, 0x9c, 0x72, 0x63, 0x03, 0x61, 0x48, 0x87, 0xd3, 0x80, 0x8c, 0x49, 0x40, 0x67, + 0xb9, 0xd5, 0xdf, 0xaa, 0x79, 0x31, 0x0d, 0xae, 0xce, 0xe8, 0x0c, 0xa7, 0xc2, 0xd8, 0xb0, 0xa6, + 0x00, 0xad, 0xd3, 0x97, 0x2d, 0x7b, 0x36, 0x60, 0xb6, 0x8b, 0x1e, 0x43, 0x72, 0xd8, 0x9f, 0x0b, + 0xce, 0x94, 0xb2, 0xcb, 0x9d, 0x9e, 0x0a, 0x1d, 0x2a, 0x84, 0xf6, 0x21, 0xd3, 0xac, 0x37, 0xc8, + 0x28, 0x66, 0x28, 0x19, 0x99, 0xfb, 0xdb, 0xaf, 0x37, 0xe6, 0xc5, 0xc4, 0xf6, 0x17, 0x36, 0x32, + 0x21, 0x31, 0xf4, 0x9d, 0x5c, 0x42, 0x8a, 0xc6, 0xd2, 0xb4, 0xca, 0x90, 0x94, 0x55, 0xd1, 0x7f, + 0xb0, 0x36, 0x24, 0x52, 0x9c, 0x6a, 0x9a, 0xc5, 0xfa, 0xb0, 0x2d, 0x00, 0xfa, 0x17, 0xf4, 0xa1, + 0xfd, 0x9a, 0x45, 0xaa, 0x81, 0xf4, 0x4a, 0x60, 0xf5, 0x01, 0x96, 0x0d, 0x90, 0x05, 0xba, 0x47, + 0x7e, 0x26, 0xf7, 0x44, 0xc9, 0xf5, 0xe6, 0xe5, 0x3d, 0x32, 0x62, 0x11, 0xbf, 0x2b, 0xe4, 0xb5, + 0x04, 0x90, 0xb7, 0x7a, 0x82, 0x9b, 0x8b, 0x29, 0x62, 0x5d, 0xe0, 0xe1, 0xe6, 0xbc, 0xb6, 0xf5, + 0x5e, 0x83, 0xa4, 0x2c, 0xf3, 0x27, 0xee, 0xf1, 0x89, 0xd4, 0xe4, 0xf0, 0x68, 0x30, 0xdf, 0xde, + 0xc6, 0x52, 0x78, 0x43, 0x78, 0x85, 0x46, 0x79, 0xa0, 0x4d, 0x39, 0x9e, 0x13, 0x72, 0xa5, 0x2e, + 0x2b, 0xe6, 0x69, 0x84, 0x3c, 0x9e, 0x87, 0x8d, 0xf8, 0x38, 0x97, 0xdc, 0x49, 0x08, 0xcd, 0xba, + 0x77, 0x29, 0x80, 0xf5, 0x56, 0x03, 0x5d, 0x91, 0xe5, 0xa6, 0xed, 0xb9, 0x54, 0x03, 0x4b, 0x13, + 0x6d, 0x41, 0x46, 0x1c, 0xc4, 0x76, 0x02, 0x12, 0xd1, 0x6b, 0xd5, 0xd3, 0xc0, 0x69, 0xe1, 0xaa, + 0x3b, 0x01, 0xa6, 0xd7, 0x8a, 0xe1, 0x04, 0xaa, 0x8b, 0x64, 0x38, 0x81, 0x7c, 0x2b, 0x62, 0x69, + 0x34, 0x94, 0xbf, 0x71, 0xd1, 0x47, 0xfa, 0x0d, 0xaf, 0x15, 0x63, 0xf4, 0x08, 0x20, 0x56, 0x40, + 0x06, 0x34, 0xcc, 0xe9, 0x6a, 0x73, 0x86, 0x52, 0x71, 0x4e, 0xc3, 0xdd, 0x6d, 0x71, 0x43, 0xcb, + 0x47, 0x67, 0x40, 0xf2, 0xfc, 0x12, 0xd7, 0xcd, 0x15, 0x94, 0x82, 0xc4, 0xc9, 0xd5, 0x99, 0xa9, + 0xed, 0xbe, 0x82, 0x35, 0x4c, 0x7b, 0x32, 0x98, 0x85, 0xf4, 0x71, 0xa7, 0x7a, 0x58, 0x26, 0xd5, + 0x4a, 0x51, 0x64, 0x08, 0xd8, 0xb9, 0xaa, 0x15, 0x4b, 0xa4, 0x56, 0xaa, 0x9a, 0x9a, 0x84, 0x8d, + 0x8b, 0x4a, 0xa5, 0x46, 0x2a, 0xd5, 0x8a, 0xb9, 0x8a, 0xd2, 0xa0, 0x1f, 0x77, 0xf6, 0xcb, 0x65, + 0x33, 0x21, 0x23, 0xf5, 0x4e, 0x6d, 0xef, 0x40, 0x25, 0x26, 0xe3, 0xc4, 0xfd, 0x4a, 0x91, 0x1c, + 0xec, 0x15, 0x4d, 0xfd, 0xc8, 0xfc, 0xf8, 0x6d, 0x4b, 0xfb, 0x24, 0xbe, 0xaf, 0xe2, 0xbb, 0xfd, + 0xbe, 0xb5, 0xd2, 0x5d, 0x53, 0x7f, 0x72, 0xe5, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x03, 0x9c, + 0xd8, 0x8d, 0x62, 0x05, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 2faf90c6b..9fa9e524b 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -17,7 +17,6 @@ message Metadata { } message TxConfiguration { - bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; Modulation modulation = 11; string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} uint32 bit_rate = 13; // FSK bit rate in bit/s From ccf28576a162b28a4e0cae7adb87538576ef76f7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 12 May 2016 15:04:20 +0200 Subject: [PATCH 1437/2266] Rename LoRaWAN Regions --- api/protocol/lorawan/lorawan.pb.go | 128 ++++++++++++++--------------- api/protocol/lorawan/lorawan.proto | 12 +-- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 79f049129..4404b2d61 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -62,29 +62,29 @@ func (Modulation) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawa type Region int32 const ( - Region_EU863_870 Region = 0 - Region_US902_928 Region = 1 - Region_CN779_787 Region = 2 - Region_EU433 Region = 3 - Region_AU915_928 Region = 4 - Region_CN470_510 Region = 5 + Region_EU_863_870 Region = 0 + Region_US_902_928 Region = 1 + Region_CN_779_787 Region = 2 + Region_EU_433 Region = 3 + Region_AU_915_928 Region = 4 + Region_CN_470_510 Region = 5 ) var Region_name = map[int32]string{ - 0: "EU863_870", - 1: "US902_928", - 2: "CN779_787", - 3: "EU433", - 4: "AU915_928", - 5: "CN470_510", + 0: "EU_863_870", + 1: "US_902_928", + 2: "CN_779_787", + 3: "EU_433", + 4: "AU_915_928", + 5: "CN_470_510", } var Region_value = map[string]int32{ - "EU863_870": 0, - "US902_928": 1, - "CN779_787": 2, - "EU433": 3, - "AU915_928": 4, - "CN470_510": 5, + "EU_863_870": 0, + "US_902_928": 1, + "CN_779_787": 2, + "EU_433": 3, + "AU_915_928": 4, + "CN_470_510": 5, } func (x Region) String() string { @@ -1957,50 +1957,50 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 710 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xc1, 0x6e, 0xda, 0x4c, - 0x10, 0x8e, 0x03, 0x0e, 0x66, 0x08, 0xf9, 0xad, 0xcd, 0xff, 0x4b, 0xfc, 0x6d, 0x95, 0xa4, 0x96, - 0x2a, 0x55, 0x91, 0x0a, 0x04, 0x92, 0x00, 0x47, 0x42, 0x13, 0x45, 0x4a, 0x48, 0xe8, 0x06, 0x0e, - 0xbd, 0x74, 0x65, 0xec, 0x35, 0xb8, 0x06, 0x2f, 0x31, 0x4b, 0x28, 0x2f, 0xd0, 0x67, 0xc8, 0xad, - 0x2f, 0xd0, 0x37, 0xe8, 0x0b, 0xf4, 0xd8, 0x73, 0x0f, 0x55, 0xd5, 0xbe, 0x48, 0x77, 0xd7, 0x04, - 0x72, 0xa8, 0x54, 0xa9, 0x52, 0xa5, 0x1e, 0xac, 0x9d, 0x6f, 0x66, 0xbe, 0x99, 0x6f, 0x66, 0x59, - 0xe0, 0xa8, 0xe7, 0xf3, 0xfe, 0xa4, 0x9b, 0x77, 0xd8, 0xb0, 0xd0, 0xee, 0xd3, 0x76, 0xdf, 0x0f, - 0x7b, 0xe3, 0x0b, 0xca, 0xa7, 0x2c, 0x0a, 0x0a, 0x9c, 0x87, 0x05, 0x7b, 0xe4, 0x17, 0x46, 0x11, - 0xe3, 0xcc, 0x61, 0x83, 0xc2, 0x80, 0x45, 0xf6, 0xd4, 0x0e, 0xef, 0xce, 0xbc, 0x0a, 0xa0, 0xd4, - 0x1c, 0x3e, 0x78, 0x76, 0xaf, 0x58, 0x8f, 0xf5, 0x58, 0x4c, 0xec, 0x4e, 0x3c, 0x85, 0x14, 0x50, - 0x56, 0xcc, 0xb3, 0x6e, 0x35, 0x30, 0x9a, 0x94, 0xdb, 0xae, 0xcd, 0x6d, 0x54, 0x06, 0x18, 0x32, - 0x77, 0x32, 0xb0, 0xb9, 0xcf, 0xc2, 0x5c, 0x66, 0x47, 0x7b, 0xba, 0x51, 0xda, 0xcc, 0xdf, 0x35, - 0x6a, 0x2e, 0x42, 0xf8, 0x5e, 0x1a, 0x7a, 0x08, 0x69, 0x49, 0x26, 0x91, 0xcd, 0x69, 0x6e, 0x5d, - 0x70, 0xd2, 0xd8, 0x90, 0x0e, 0x2c, 0x30, 0xfa, 0x1f, 0x8c, 0xae, 0xcf, 0xe3, 0x58, 0x56, 0xc4, - 0xb2, 0x38, 0x25, 0xb0, 0x0a, 0x6d, 0x43, 0xc6, 0x61, 0xae, 0x18, 0x35, 0x8e, 0x6e, 0x28, 0x26, - 0xc4, 0x2e, 0x99, 0x60, 0xbd, 0xd3, 0xe0, 0x9f, 0xf6, 0x9b, 0x06, 0x0b, 0x3d, 0xbf, 0x37, 0x89, - 0xe2, 0x66, 0x7f, 0x97, 0xc2, 0x0f, 0x1a, 0xa0, 0xba, 0xc3, 0xfd, 0x1b, 0xd5, 0x67, 0xb1, 0xc6, - 0x17, 0x60, 0xb8, 0xf4, 0x86, 0xd8, 0xae, 0x1b, 0xe5, 0x34, 0x41, 0x5a, 0x3f, 0x3a, 0xfc, 0xfc, - 0x65, 0xbb, 0xf4, 0xab, 0x5b, 0x76, 0x58, 0x44, 0x0b, 0x7c, 0x36, 0xa2, 0xe3, 0xfc, 0x73, 0x7a, - 0x53, 0x17, 0x6c, 0x9c, 0x72, 0x63, 0x03, 0x61, 0x48, 0x87, 0xd3, 0x80, 0x8c, 0x49, 0x40, 0x67, - 0xb9, 0xd5, 0xdf, 0xaa, 0x79, 0x31, 0x0d, 0xae, 0xce, 0xe8, 0x0c, 0xa7, 0xc2, 0xd8, 0xb0, 0xa6, - 0x00, 0xad, 0xd3, 0x97, 0x2d, 0x7b, 0x36, 0x60, 0xb6, 0x8b, 0x1e, 0x43, 0x72, 0xd8, 0x9f, 0x0b, - 0xce, 0x94, 0xb2, 0xcb, 0x9d, 0x9e, 0x0a, 0x1d, 0x2a, 0x84, 0xf6, 0x21, 0xd3, 0xac, 0x37, 0xc8, - 0x28, 0x66, 0x28, 0x19, 0x99, 0xfb, 0xdb, 0xaf, 0x37, 0xe6, 0xc5, 0xc4, 0xf6, 0x17, 0x36, 0x32, - 0x21, 0x31, 0xf4, 0x9d, 0x5c, 0x42, 0x8a, 0xc6, 0xd2, 0xb4, 0xca, 0x90, 0x94, 0x55, 0xd1, 0x7f, - 0xb0, 0x36, 0x24, 0x52, 0x9c, 0x6a, 0x9a, 0xc5, 0xfa, 0xb0, 0x2d, 0x00, 0xfa, 0x17, 0xf4, 0xa1, - 0xfd, 0x9a, 0x45, 0xaa, 0x81, 0xf4, 0x4a, 0x60, 0xf5, 0x01, 0x96, 0x0d, 0x90, 0x05, 0xba, 0x47, - 0x7e, 0x26, 0xf7, 0x44, 0xc9, 0xf5, 0xe6, 0xe5, 0x3d, 0x32, 0x62, 0x11, 0xbf, 0x2b, 0xe4, 0xb5, - 0x04, 0x90, 0xb7, 0x7a, 0x82, 0x9b, 0x8b, 0x29, 0x62, 0x5d, 0xe0, 0xe1, 0xe6, 0xbc, 0xb6, 0xf5, - 0x5e, 0x83, 0xa4, 0x2c, 0xf3, 0x27, 0xee, 0xf1, 0x89, 0xd4, 0xe4, 0xf0, 0x68, 0x30, 0xdf, 0xde, - 0xc6, 0x52, 0x78, 0x43, 0x78, 0x85, 0x46, 0x79, 0xa0, 0x4d, 0x39, 0x9e, 0x13, 0x72, 0xa5, 0x2e, - 0x2b, 0xe6, 0x69, 0x84, 0x3c, 0x9e, 0x87, 0x8d, 0xf8, 0x38, 0x97, 0xdc, 0x49, 0x08, 0xcd, 0xba, - 0x77, 0x29, 0x80, 0xf5, 0x56, 0x03, 0x5d, 0x91, 0xe5, 0xa6, 0xed, 0xb9, 0x54, 0x03, 0x4b, 0x13, - 0x6d, 0x41, 0x46, 0x1c, 0xc4, 0x76, 0x02, 0x12, 0xd1, 0x6b, 0xd5, 0xd3, 0xc0, 0x69, 0xe1, 0xaa, - 0x3b, 0x01, 0xa6, 0xd7, 0x8a, 0xe1, 0x04, 0xaa, 0x8b, 0x64, 0x38, 0x81, 0x7c, 0x2b, 0x62, 0x69, - 0x34, 0x94, 0xbf, 0x71, 0xd1, 0x47, 0xfa, 0x0d, 0xaf, 0x15, 0x63, 0xf4, 0x08, 0x20, 0x56, 0x40, - 0x06, 0x34, 0xcc, 0xe9, 0x6a, 0x73, 0x86, 0x52, 0x71, 0x4e, 0xc3, 0xdd, 0x6d, 0x71, 0x43, 0xcb, - 0x47, 0x67, 0x40, 0xf2, 0xfc, 0x12, 0xd7, 0xcd, 0x15, 0x94, 0x82, 0xc4, 0xc9, 0xd5, 0x99, 0xa9, - 0xed, 0xbe, 0x82, 0x35, 0x4c, 0x7b, 0x32, 0x98, 0x85, 0xf4, 0x71, 0xa7, 0x7a, 0x58, 0x26, 0xd5, - 0x4a, 0x51, 0x64, 0x08, 0xd8, 0xb9, 0xaa, 0x15, 0x4b, 0xa4, 0x56, 0xaa, 0x9a, 0x9a, 0x84, 0x8d, - 0x8b, 0x4a, 0xa5, 0x46, 0x2a, 0xd5, 0x8a, 0xb9, 0x8a, 0xd2, 0xa0, 0x1f, 0x77, 0xf6, 0xcb, 0x65, - 0x33, 0x21, 0x23, 0xf5, 0x4e, 0x6d, 0xef, 0x40, 0x25, 0x26, 0xe3, 0xc4, 0xfd, 0x4a, 0x91, 0x1c, - 0xec, 0x15, 0x4d, 0xfd, 0xc8, 0xfc, 0xf8, 0x6d, 0x4b, 0xfb, 0x24, 0xbe, 0xaf, 0xe2, 0xbb, 0xfd, - 0xbe, 0xb5, 0xd2, 0x5d, 0x53, 0x7f, 0x72, 0xe5, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x03, 0x9c, - 0xd8, 0x8d, 0x62, 0x05, 0x00, 0x00, + // 711 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xcf, 0x6e, 0xda, 0x4c, + 0x10, 0x8f, 0xc3, 0x3f, 0x33, 0x04, 0x3e, 0x6b, 0xf3, 0x7d, 0x12, 0x5f, 0x5b, 0x25, 0xa9, 0xa5, + 0x4a, 0x55, 0xa4, 0x02, 0x81, 0x24, 0xc0, 0x91, 0xd0, 0x46, 0x91, 0x12, 0x12, 0xba, 0x21, 0x87, + 0x9e, 0x56, 0xc6, 0x5e, 0x83, 0x6b, 0xf0, 0x12, 0xb3, 0x84, 0xf2, 0x02, 0x7d, 0x86, 0xdc, 0xfa, + 0x02, 0x7d, 0x83, 0xbe, 0x40, 0x8f, 0x3d, 0xf7, 0x50, 0x55, 0xed, 0x8b, 0x74, 0x77, 0xed, 0x40, + 0x0e, 0x95, 0x2a, 0x55, 0xaa, 0xd4, 0x83, 0xb5, 0xf3, 0x9b, 0x99, 0xdf, 0xcc, 0x6f, 0x66, 0x59, + 0xe0, 0x68, 0xe0, 0xf1, 0xe1, 0xac, 0x5f, 0xb2, 0xd9, 0xb8, 0xdc, 0x1b, 0xd2, 0xde, 0xd0, 0x0b, + 0x06, 0xd3, 0x73, 0xca, 0xe7, 0x2c, 0xf4, 0xcb, 0x9c, 0x07, 0x65, 0x6b, 0xe2, 0x95, 0x27, 0x21, + 0xe3, 0xcc, 0x66, 0xa3, 0xf2, 0x88, 0x85, 0xd6, 0xdc, 0x0a, 0xee, 0xce, 0x92, 0x0a, 0xa0, 0x4c, + 0x0c, 0x1f, 0x3c, 0xbb, 0x57, 0x6c, 0xc0, 0x06, 0x2c, 0x22, 0xf6, 0x67, 0xae, 0x42, 0x0a, 0x28, + 0x2b, 0xe2, 0x99, 0xb7, 0x1a, 0xe8, 0x1d, 0xca, 0x2d, 0xc7, 0xe2, 0x16, 0xaa, 0x01, 0x8c, 0x99, + 0x33, 0x1b, 0x59, 0xdc, 0x63, 0x41, 0x31, 0xb7, 0xa3, 0x3d, 0x2d, 0x54, 0x37, 0x4b, 0x77, 0x8d, + 0x3a, 0xcb, 0x10, 0xbe, 0x97, 0x86, 0x1e, 0x42, 0x56, 0x92, 0x49, 0x68, 0x71, 0x5a, 0xdc, 0x10, + 0x9c, 0x2c, 0xd6, 0xa5, 0x03, 0x0b, 0x8c, 0xfe, 0x07, 0xbd, 0xef, 0xf1, 0x28, 0x96, 0x17, 0xb1, + 0x3c, 0xce, 0x08, 0xac, 0x42, 0xdb, 0x90, 0xb3, 0x99, 0x23, 0x46, 0x8d, 0xa2, 0x05, 0xc5, 0x84, + 0xc8, 0x25, 0x13, 0xcc, 0x77, 0x1a, 0xfc, 0xd3, 0x7b, 0xd3, 0x66, 0x81, 0xeb, 0x0d, 0x66, 0x61, + 0xd4, 0xec, 0xef, 0x52, 0xf8, 0x41, 0x03, 0xd4, 0xb2, 0xb9, 0x77, 0xa3, 0xfa, 0x2c, 0xd7, 0xf8, + 0x12, 0x74, 0x87, 0xde, 0x10, 0xcb, 0x71, 0xc2, 0xa2, 0x26, 0x48, 0x1b, 0x47, 0x87, 0x9f, 0xbf, + 0x6c, 0x57, 0x7f, 0x75, 0xcb, 0x36, 0x0b, 0x69, 0x99, 0x2f, 0x26, 0x74, 0x5a, 0x7a, 0x4e, 0x6f, + 0x5a, 0x82, 0x8d, 0x33, 0x4e, 0x64, 0x20, 0x0c, 0xd9, 0x60, 0xee, 0x93, 0x29, 0xf1, 0xe9, 0xa2, + 0xb8, 0xfe, 0x5b, 0x35, 0xcf, 0xe7, 0xfe, 0xe5, 0x29, 0x5d, 0xe0, 0x4c, 0x10, 0x19, 0xe6, 0x1c, + 0xa0, 0x7b, 0xf2, 0xaa, 0x6b, 0x2d, 0x46, 0xcc, 0x72, 0xd0, 0x63, 0x48, 0x8e, 0x87, 0xb1, 0xe0, + 0x5c, 0x35, 0xbf, 0xda, 0xe9, 0x89, 0xd0, 0xa1, 0x42, 0x68, 0x1f, 0x72, 0x9d, 0x56, 0x9b, 0x4c, + 0x22, 0x86, 0x92, 0x91, 0xbb, 0xbf, 0xfd, 0x56, 0x3b, 0x2e, 0x26, 0xb6, 0xbf, 0xb4, 0x91, 0x01, + 0x89, 0xb1, 0x67, 0x17, 0x13, 0x52, 0x34, 0x96, 0xa6, 0x59, 0x83, 0xa4, 0xac, 0x8a, 0xfe, 0x83, + 0xf4, 0x98, 0x48, 0x71, 0xaa, 0x69, 0x1e, 0xa7, 0xc6, 0x3d, 0x01, 0xd0, 0xbf, 0x90, 0x1a, 0x5b, + 0xaf, 0x59, 0xa8, 0x1a, 0x48, 0xaf, 0x04, 0xe6, 0x10, 0x60, 0xd5, 0x00, 0x99, 0x90, 0x72, 0xc9, + 0xcf, 0xe4, 0x1e, 0x2b, 0xb9, 0x6e, 0x5c, 0xde, 0x25, 0x13, 0x16, 0xf2, 0xbb, 0x42, 0x6e, 0x57, + 0x00, 0x79, 0xab, 0xc7, 0xb8, 0xb3, 0x9c, 0x22, 0xd2, 0x05, 0x2e, 0xee, 0xc4, 0xb5, 0xcd, 0xf7, + 0x1a, 0x24, 0x65, 0x99, 0x3f, 0x71, 0x8f, 0x4f, 0xa4, 0x26, 0x9b, 0x87, 0xa3, 0x78, 0x7b, 0x85, + 0x95, 0xf0, 0xb6, 0xf0, 0x0a, 0x8d, 0xf2, 0x40, 0x9b, 0x72, 0x3c, 0x3b, 0xe0, 0x4a, 0x5d, 0x5e, + 0xcc, 0xd3, 0x0e, 0x78, 0x34, 0x0f, 0x9b, 0xf0, 0x69, 0x31, 0xb9, 0x93, 0x10, 0x9a, 0x53, 0xee, + 0x85, 0x00, 0xe6, 0x5b, 0x0d, 0x52, 0x8a, 0x2c, 0x37, 0x6d, 0xc5, 0x52, 0x75, 0x2c, 0x4d, 0xb4, + 0x05, 0x39, 0x71, 0x10, 0xcb, 0xf6, 0x49, 0x48, 0xaf, 0x55, 0x4f, 0x1d, 0x67, 0x85, 0xab, 0x65, + 0xfb, 0x98, 0x5e, 0x2b, 0x86, 0xed, 0xab, 0x2e, 0x92, 0x61, 0xfb, 0xf2, 0xad, 0x88, 0xa5, 0xd1, + 0x40, 0xfe, 0xc6, 0x45, 0x1f, 0xe9, 0xd7, 0xdd, 0x6e, 0x84, 0xd1, 0x23, 0x80, 0x48, 0x01, 0x19, + 0xd1, 0xa0, 0x98, 0x52, 0x9b, 0xd3, 0x95, 0x8a, 0x33, 0x1a, 0xec, 0x6e, 0x8b, 0x1b, 0x5a, 0x3d, + 0x3a, 0x1d, 0x92, 0x67, 0x17, 0xb8, 0x65, 0xac, 0xa1, 0x0c, 0x24, 0x8e, 0x2f, 0x4f, 0x0d, 0x6d, + 0xd7, 0x81, 0x34, 0xa6, 0x03, 0x19, 0x2c, 0x00, 0xbc, 0xb8, 0x22, 0x8d, 0xc3, 0x1a, 0x69, 0xd4, + 0x2b, 0x22, 0x45, 0xe0, 0xab, 0x4b, 0xd2, 0xac, 0x54, 0x49, 0xb3, 0xda, 0x30, 0x34, 0x89, 0xdb, + 0xe7, 0xa4, 0x5e, 0x6f, 0x92, 0x7a, 0xa3, 0x6e, 0xac, 0x23, 0x80, 0xb4, 0xc8, 0xdf, 0xaf, 0xd5, + 0x8c, 0x84, 0x8c, 0xb5, 0xae, 0x48, 0x73, 0xef, 0x40, 0xe5, 0x26, 0xe3, 0xdc, 0xfd, 0x7a, 0x85, + 0x1c, 0xec, 0x55, 0x8c, 0xd4, 0x91, 0xf1, 0xf1, 0xdb, 0x96, 0xf6, 0x49, 0x7c, 0x5f, 0xc5, 0x77, + 0xfb, 0x7d, 0x6b, 0xad, 0x9f, 0x56, 0x7f, 0x75, 0xb5, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x65, + 0x80, 0x5d, 0x75, 0x68, 0x05, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 9fa9e524b..e8aca5540 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -61,10 +61,10 @@ message FCtrl { } enum Region { - EU863_870 = 0; - US902_928 = 1; - CN779_787 = 2; - EU433 = 3; - AU915_928 = 4; - CN470_510 = 5; + EU_863_870 = 0; + US_902_928 = 1; + CN_779_787 = 2; + EU_433 = 3; + AU_915_928 = 4; + CN_470_510 = 5; } From d590f21ccb73646016bfb2fec1ed47931956c77e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 12 May 2016 15:04:34 +0200 Subject: [PATCH 1438/2266] Finalize router/gateway package --- core/router/gateway/gateway.go | 2 ++ core/router/gateway/gateway_test.go | 14 ++++++++++++++ core/router/gateway/schedule.go | 5 ++++- core/router/gateway/schedule_datastructure.go | 2 ++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 core/router/gateway/gateway_test.go diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 5e3fc02f3..ac00e67b4 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -2,6 +2,7 @@ package gateway import "github.com/TheThingsNetwork/ttn/core/types" +// NewGateway creates a new in-memory Gateway structure func NewGateway(eui types.GatewayEUI) *Gateway { return &Gateway{ EUI: eui, @@ -11,6 +12,7 @@ func NewGateway(eui types.GatewayEUI) *Gateway { } } +// Gateway contains the state of a gateway type Gateway struct { EUI types.GatewayEUI Status StatusStore diff --git a/core/router/gateway/gateway_test.go b/core/router/gateway/gateway_test.go new file mode 100644 index 000000000..28c41b009 --- /dev/null +++ b/core/router/gateway/gateway_test.go @@ -0,0 +1,14 @@ +package gateway + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestNewGateway(t *testing.T) { + a := New(t) + gtw := NewGateway(types.GatewayEUI{1, 2, 3, 4, 5, 6, 7}) + a.So(gtw, ShouldNotBeNil) +} diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index cfea22558..7e842a4f6 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -18,7 +18,10 @@ type Schedule interface { GetOption(timestamp uint32, length uint32) (id string, score uint) // Schedule a transmission on a slot Schedule(id string, downlink *router_pb.DownlinkMessage) error - // TODO: Add some way to retrieve the next scheduled item (preferably at the right time) + // Subscribe to downlink messages + Subscribe() <-chan *router_pb.DownlinkMessage + // Stop the subscription + Stop() } // NewSchedule creates a new Schedule diff --git a/core/router/gateway/schedule_datastructure.go b/core/router/gateway/schedule_datastructure.go index aa8f69866..79d6bd7e7 100644 --- a/core/router/gateway/schedule_datastructure.go +++ b/core/router/gateway/schedule_datastructure.go @@ -22,6 +22,7 @@ type downlinkQueue struct { items []*scheduledItem } +// NewDownlinkQueue creates a new downlinkQueue func NewDownlinkQueue(items ...*scheduledItem) *downlinkQueue { dq := &downlinkQueue{ items: items, @@ -42,6 +43,7 @@ func (dq *downlinkQueue) swap(i, j int) { // Push an item to the queue func (dq *downlinkQueue) Push(item *scheduledItem) { dq.items = append(dq.items, item) + // TODO: Insertion sort is nice, but can be optimized for the use-case of TTN (LoRaWAN RX1 and RX2) for i := len(dq.items); i > 1; i-- { if dq.less(i-1, i-2) { dq.swap(i-1, i-2) From e870a08cc51472a8727ec525059b66c6f480aa50 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 12 May 2016 18:50:30 +0200 Subject: [PATCH 1439/2266] Update schedule time if ToA is known --- core/router/gateway/schedule.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 7e842a4f6..eb9a52e22 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -8,6 +8,7 @@ import ( router_pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/TheThingsNetwork/ttn/utils/toa" ) // Schedule is used to schedule downlink transmissions @@ -110,10 +111,18 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score // see interface func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { - s.RLock() - defer s.RUnlock() + s.Lock() + defer s.Unlock() if item, ok := s.byID[id]; ok { item.payload = downlink + if lora := downlink.GetProtocolConfiguration().GetLorawan(); lora != nil { + time, _ := toa.Compute( + uint(len(downlink.Payload)), + lora.DataRate, + lora.CodingRate, + ) + item.length = uint32(time / 1000) + } return nil } return errors.New("ID not found") From 2c38da4ebdabb51965991135ead7654da965c849 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 12 May 2016 18:51:12 +0200 Subject: [PATCH 1440/2266] Update conflict detection to include all beginning and end tmst --- core/router/gateway/schedule.go | 6 +++--- core/router/gateway/schedule_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index eb9a52e22..d66bbf575 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -58,17 +58,17 @@ func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint to := from + uint64(length) if scheduledTo > uintmax || to > uintmax { - if scheduledTo-uintmax < from || scheduledFrom > to-uintmax { + if scheduledTo-uintmax <= from || scheduledFrom >= to-uintmax { continue } - } else if scheduledTo < from || scheduledFrom > to { + } else if scheduledTo <= from || scheduledFrom >= to { continue } if item.payload == nil { conflicts++ } else { - conflicts += 10 // TODO: Configure this + conflicts += 100 } } return diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index 8c9a58d5e..1d844b3a6 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -102,7 +102,7 @@ func TestScheduleSchedule(t *testing.T) { a.So(err, ShouldBeNil) _, conflicts = s.GetOption(50, 100) - a.So(conflicts, ShouldEqual, 10) + a.So(conflicts, ShouldEqual, 100) } func TestScheduleSubscribe(t *testing.T) { From e7165ee2336fa86758db43d2ecc81116e52bd171 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 17 May 2016 13:14:30 +0200 Subject: [PATCH 1441/2266] Update vendor brocaar/lorawan --- .gitmodules | 3 --- core/components/handler/handler.go | 4 ++-- vendor/github.com/brocaar/lorawan | 2 +- vendor/google.golang.org/grpc | 1 - 4 files changed, 3 insertions(+), 7 deletions(-) delete mode 160000 vendor/google.golang.org/grpc diff --git a/.gitmodules b/.gitmodules index 9b3eea699..52f98dfd2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,3 @@ [submodule "vendor/github.com/brocaar/lorawan"] path = vendor/github.com/brocaar/lorawan url = https://github.com/brocaar/lorawan.git -[submodule "vendor/google.golang.org/grpc"] - path = vendor/google.golang.org/grpc - url = https://github.com/grpc/grpc-go.git diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go index c41f1742b..7dc771b1b 100644 --- a/core/components/handler/handler.go +++ b/core/components/handler/handler.go @@ -752,8 +752,8 @@ func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte joinAcceptPayload := &lorawan.JoinAcceptPayload{ NetID: lorawan.NetID(h.Configuration.NetID), DevAddr: lorawan.DevAddr(devAddr), - DLSettings: lorawan.DLsettings{ - RX1DRoffset: h.Configuration.RX1DROffset, + DLSettings: lorawan.DLSettings{ + RX1DROffset: h.Configuration.RX1DROffset, RX2DataRate: dataRates[h.Configuration.RX2DataRate], }, RXDelay: h.Configuration.RXDelay, diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan index a932a7086..bd4dcf15f 160000 --- a/vendor/github.com/brocaar/lorawan +++ b/vendor/github.com/brocaar/lorawan @@ -1 +1 @@ -Subproject commit a932a70865e10d377ff5863ed86fa9f8b35c6f22 +Subproject commit bd4dcf15f7cde1e8c4639b1aa7143aed268aa0af diff --git a/vendor/google.golang.org/grpc b/vendor/google.golang.org/grpc deleted file mode 160000 index b062a3c00..000000000 --- a/vendor/google.golang.org/grpc +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b062a3c003c22bfef58fa99d689e6a892b408f9d From 60264c5ee2ceca0c952ae2dcc5611812b0a9ee39 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 17 May 2016 13:26:17 +0200 Subject: [PATCH 1442/2266] Fix applicationsPayloadFunctions --- ttnctl/cmd/applicationsPayloadFunctions.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go index a598cb6a6..e87deccc0 100644 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -10,6 +10,7 @@ import ( "golang.org/x/net/context" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -143,7 +144,7 @@ the Handler, as well as a payload to test them on and returns the fields and val ctx.Fatal("No authentication found. Please login") } - payload, err := util.ParseHEX(args[0], len(args[0])) + payload, err := types.ParseHEX(args[0], len(args[0])) if err != nil { ctx.WithError(err).Fatal("Invalid payload") } From f8a22344d37660264b6f4eb1f2e41cd3f16f0839 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 17 May 2016 15:39:47 +0200 Subject: [PATCH 1443/2266] Validate token in Discovery --- api/discovery/discovery.pb.go | 169 +++++++++++++++--------- api/discovery/discovery.proto | 11 +- core/discovery/broker_discovery.go | 12 ++ core/discovery/broker_discovery_test.go | 11 +- core/discovery/discovery.go | 15 ++- core/discovery/discovery_test.go | 25 ++-- 6 files changed, 157 insertions(+), 86 deletions(-) diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index ef7d4bc95..050a0683f 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -66,11 +66,12 @@ func (*Metadata) ProtoMessage() {} func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } type Announcement struct { - ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - ServiceVersion string `protobuf:"bytes,2,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` - NetAddress string `protobuf:"bytes,3,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` - Description string `protobuf:"bytes,11,opt,name=description,proto3" json:"description,omitempty"` - Token string `protobuf:"bytes,12,opt,name=token,proto3" json:"token,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` + Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` + ServiceName string `protobuf:"bytes,4,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + ServiceVersion string `protobuf:"bytes,5,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } @@ -296,36 +297,42 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.ServiceName) > 0 { + if len(m.Id) > 0 { data[i] = 0xa i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) + i += copy(data[i:], m.Id) + } + if len(m.Token) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Token))) + i += copy(data[i:], m.Token) + } + if len(m.Description) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) + i += copy(data[i:], m.Description) + } + if len(m.ServiceName) > 0 { + data[i] = 0x22 + i++ i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) i += copy(data[i:], m.ServiceName) } if len(m.ServiceVersion) > 0 { - data[i] = 0x12 + data[i] = 0x2a i++ i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceVersion))) i += copy(data[i:], m.ServiceVersion) } if len(m.NetAddress) > 0 { - data[i] = 0x1a + data[i] = 0x5a i++ i = encodeVarintDiscovery(data, i, uint64(len(m.NetAddress))) i += copy(data[i:], m.NetAddress) } - if len(m.Description) > 0 { - data[i] = 0x5a - i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) - i += copy(data[i:], m.Description) - } - if len(m.Token) > 0 { - data[i] = 0x62 - i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { data[i] = 0xaa @@ -440,23 +447,27 @@ func (m *Metadata) Size() (n int) { func (m *Announcement) Size() (n int) { var l int _ = l - l = len(m.ServiceName) + l = len(m.Id) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.ServiceVersion) + l = len(m.Token) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.NetAddress) + l = len(m.Description) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.Description) + l = len(m.ServiceName) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.Token) + l = len(m.ServiceVersion) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.NetAddress) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } @@ -635,7 +646,7 @@ func (m *Announcement) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -660,11 +671,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.Id = string(data[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServiceVersion", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -689,11 +700,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceVersion = string(data[iNdEx:postIndex]) + m.Token = string(data[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -718,11 +729,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NetAddress = string(data[iNdEx:postIndex]) + m.Description = string(data[iNdEx:postIndex]) iNdEx = postIndex - case 11: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -747,11 +758,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Description = string(data[iNdEx:postIndex]) + m.ServiceName = string(data[iNdEx:postIndex]) iNdEx = postIndex - case 12: + case 5: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ServiceVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -776,7 +787,36 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Token = string(data[iNdEx:postIndex]) + m.ServiceVersion = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.NetAddress = string(data[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -1096,31 +1136,32 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 416 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x92, 0xdf, 0x6a, 0xd4, 0x40, - 0x14, 0xc6, 0xbb, 0x06, 0x4b, 0x72, 0x12, 0xda, 0x75, 0x54, 0x0c, 0x11, 0xea, 0x9a, 0x1b, 0xeb, - 0x45, 0x13, 0x48, 0xbd, 0xf5, 0x62, 0xfd, 0x8b, 0x48, 0x8b, 0x0c, 0x45, 0xbc, 0xb2, 0x4c, 0x93, - 0xc3, 0x6e, 0x88, 0x99, 0x89, 0x99, 0xc9, 0x4a, 0xae, 0x7c, 0x0d, 0x1f, 0xc9, 0x4b, 0x1f, 0x41, - 0xd4, 0x07, 0x71, 0xf2, 0x6f, 0x13, 0xa4, 0xc2, 0x5e, 0x1c, 0xd8, 0xf9, 0xce, 0xef, 0x9c, 0x6f, - 0xbf, 0xc9, 0xc0, 0xd3, 0x55, 0xaa, 0xd6, 0xd5, 0x55, 0x10, 0x8b, 0x3c, 0xbc, 0x58, 0xe3, 0xc5, - 0x3a, 0xe5, 0x2b, 0x79, 0x8e, 0xea, 0x8b, 0x28, 0xb3, 0x50, 0x29, 0x1e, 0xb2, 0x22, 0x0d, 0x93, - 0x54, 0xc6, 0x62, 0x83, 0x65, 0x3d, 0xfe, 0x0a, 0x8a, 0x52, 0x28, 0x41, 0xac, 0xad, 0xe0, 0x9d, - 0xec, 0xb2, 0x49, 0x57, 0x37, 0xe9, 0x7f, 0x04, 0xf3, 0x0c, 0x15, 0x4b, 0x98, 0x62, 0xe4, 0x31, - 0x18, 0x19, 0xd6, 0xee, 0x6c, 0x31, 0x3b, 0x3e, 0x88, 0xee, 0x05, 0xa3, 0xc9, 0x40, 0x04, 0x6f, - 0xb1, 0xa6, 0x0d, 0x43, 0xee, 0xc0, 0xcd, 0x0d, 0xfb, 0x54, 0xa1, 0x7b, 0x43, 0xc3, 0x0e, 0xed, - 0x0e, 0xfe, 0x2d, 0x30, 0x34, 0x41, 0x00, 0xf6, 0xdf, 0xd1, 0x97, 0xaf, 0xde, 0x7c, 0x98, 0xef, - 0xf9, 0x7f, 0x66, 0xe0, 0x2c, 0x39, 0x17, 0x15, 0x8f, 0x31, 0x47, 0xae, 0xc8, 0x43, 0x70, 0x24, - 0x96, 0x9b, 0x34, 0xc6, 0x4b, 0xce, 0x72, 0x6c, 0xdd, 0x2c, 0x6a, 0xf7, 0xda, 0xb9, 0x96, 0xc8, - 0x23, 0x38, 0x1c, 0x10, 0x6d, 0x2f, 0x53, 0xc1, 0x5b, 0x1b, 0x8b, 0x1e, 0xf4, 0xf2, 0xfb, 0x4e, - 0x25, 0x0f, 0xc0, 0xe6, 0xa8, 0x2e, 0x59, 0x92, 0x94, 0x28, 0xa5, 0x6b, 0xb4, 0x10, 0x68, 0x69, - 0xd9, 0x29, 0x64, 0x01, 0x76, 0x82, 0x32, 0x2e, 0xd3, 0x42, 0x35, 0x5b, 0xec, 0xce, 0x6b, 0x22, - 0x35, 0x41, 0x94, 0xc8, 0x90, 0xbb, 0x4e, 0xdb, 0xeb, 0x0e, 0x24, 0x04, 0x33, 0xef, 0x33, 0xbb, - 0x77, 0x17, 0xc6, 0xb1, 0x1d, 0xdd, 0xbe, 0xe6, 0x3a, 0xe8, 0x16, 0xf2, 0x9f, 0xc0, 0xe1, 0x8b, - 0xbe, 0x4f, 0xf1, 0x73, 0x85, 0x72, 0x97, 0xa0, 0xfe, 0x6b, 0x98, 0x8f, 0x53, 0xb2, 0x10, 0x5c, - 0x22, 0x39, 0x05, 0xb3, 0x47, 0xa4, 0x1e, 0x69, 0xac, 0xa7, 0x5f, 0x62, 0x7a, 0x95, 0x74, 0x0b, - 0x46, 0x5f, 0xc1, 0x1a, 0x16, 0xd5, 0xe4, 0x04, 0xcc, 0x01, 0x23, 0xff, 0x9b, 0xf5, 0xcc, 0xa0, - 0x79, 0x03, 0xcb, 0x38, 0x23, 0xcf, 0xc1, 0x1c, 0x66, 0x89, 0x37, 0xc1, 0xff, 0xc9, 0xe3, 0xdd, - 0xbf, 0xb6, 0xd7, 0xfd, 0xeb, 0x88, 0x8c, 0x49, 0xea, 0x33, 0xc6, 0xd9, 0x0a, 0xcb, 0x67, 0xf3, - 0xef, 0xbf, 0x8e, 0x66, 0x3f, 0x74, 0xfd, 0xd4, 0xf5, 0xed, 0xf7, 0xd1, 0xde, 0xd5, 0x7e, 0xfb, - 0xe6, 0x4e, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x9d, 0x63, 0x0e, 0xee, 0x02, 0x00, 0x00, + // 429 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x92, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xc7, 0xeb, 0x86, 0x56, 0xce, 0x24, 0x4a, 0xc3, 0x00, 0xc2, 0x0a, 0x52, 0x09, 0xbe, 0x50, + 0x0e, 0xb5, 0xa5, 0x94, 0x2b, 0x87, 0xf0, 0x29, 0x84, 0x5a, 0xa1, 0x55, 0x85, 0x38, 0x51, 0x6d, + 0xed, 0x51, 0xb2, 0x0a, 0xd9, 0x35, 0xde, 0x75, 0x90, 0x4f, 0xbc, 0x06, 0x8f, 0xc4, 0x91, 0x47, + 0x40, 0xf0, 0x0a, 0x3c, 0x00, 0xeb, 0xaf, 0xd8, 0xa2, 0x45, 0xe2, 0x30, 0xd2, 0xee, 0x7f, 0x7e, + 0xf3, 0xb9, 0x0b, 0x4f, 0x16, 0xc2, 0x2c, 0xb3, 0xcb, 0x20, 0x52, 0xeb, 0xf0, 0x7c, 0x49, 0xe7, + 0x4b, 0x21, 0x17, 0xfa, 0x8c, 0xcc, 0x67, 0x95, 0xae, 0x42, 0x63, 0x64, 0xc8, 0x13, 0x11, 0xc6, + 0x42, 0x47, 0x6a, 0x43, 0x69, 0xde, 0x9e, 0x82, 0x24, 0x55, 0x46, 0x61, 0x7f, 0x2b, 0x4c, 0x8e, + 0xff, 0x27, 0x93, 0xb5, 0x2a, 0xd2, 0xff, 0x00, 0xee, 0x29, 0x19, 0x1e, 0x73, 0xc3, 0xf1, 0x11, + 0xf4, 0x56, 0x94, 0x7b, 0xce, 0xd4, 0x39, 0x1a, 0xcd, 0xee, 0x06, 0x6d, 0x91, 0x86, 0x08, 0xde, + 0x50, 0xce, 0x0a, 0x06, 0x6f, 0xc3, 0xde, 0x86, 0x7f, 0xcc, 0xc8, 0xdb, 0xb5, 0xf0, 0x90, 0x55, + 0x17, 0xff, 0x26, 0xf4, 0x2c, 0x81, 0x00, 0xfb, 0x6f, 0xd9, 0x8b, 0x97, 0xaf, 0xdf, 0x8f, 0x77, + 0xfc, 0xdf, 0x0e, 0x0c, 0xe7, 0x52, 0xaa, 0x4c, 0x46, 0xb4, 0x26, 0x69, 0x70, 0x04, 0xbb, 0x22, + 0x2e, 0x6b, 0xf4, 0x99, 0x3d, 0x15, 0x99, 0x8c, 0x5a, 0x91, 0x2c, 0x33, 0xf5, 0x59, 0x75, 0xc1, + 0x29, 0x0c, 0x62, 0xd2, 0x51, 0x2a, 0x12, 0x23, 0x94, 0xf4, 0x7a, 0xa5, 0xaf, 0x2b, 0xe1, 0x03, + 0x18, 0x6a, 0x4a, 0x37, 0x22, 0xa2, 0x0b, 0xc9, 0xd7, 0xe4, 0xdd, 0xa8, 0x90, 0x5a, 0x3b, 0xb3, + 0x12, 0x3e, 0x84, 0x83, 0x06, 0xb1, 0x63, 0xe8, 0x22, 0xd1, 0x5e, 0x49, 0x8d, 0x6a, 0xf9, 0x5d, + 0xa5, 0xe2, 0x7d, 0x18, 0x48, 0x32, 0x17, 0x3c, 0x8e, 0x53, 0xd2, 0xda, 0x1b, 0x94, 0x10, 0x58, + 0x69, 0x5e, 0x29, 0x18, 0x82, 0xbb, 0xae, 0x77, 0xe0, 0xdd, 0x99, 0xf6, 0x8e, 0x06, 0xb3, 0x5b, + 0xd7, 0xac, 0x87, 0x6d, 0x21, 0xff, 0x31, 0x1c, 0x3c, 0xaf, 0xfd, 0x8c, 0x3e, 0x65, 0xa4, 0xcd, + 0x95, 0x86, 0x9d, 0x2b, 0x0d, 0xfb, 0xaf, 0x60, 0xdc, 0x46, 0xe9, 0x44, 0x49, 0x4d, 0x78, 0x02, + 0x6e, 0x8d, 0x68, 0x1b, 0x52, 0x94, 0xee, 0xbe, 0x4c, 0x77, 0xb5, 0x6c, 0x0b, 0xce, 0xbe, 0x40, + 0xbf, 0x49, 0x94, 0xe3, 0x31, 0xb8, 0x0d, 0x86, 0xff, 0x8a, 0x9d, 0xb8, 0x41, 0xf1, 0x27, 0xe6, + 0xd1, 0x0a, 0x9f, 0x81, 0xdb, 0xc4, 0xe2, 0xa4, 0x83, 0xff, 0x35, 0xcf, 0xe4, 0xde, 0xb5, 0xbe, + 0xaa, 0xeb, 0x19, 0xb6, 0x93, 0xe4, 0xa7, 0x5c, 0xf2, 0x05, 0xa5, 0x4f, 0xc7, 0xdf, 0x7e, 0x1e, + 0x3a, 0xdf, 0xad, 0xfd, 0xb0, 0xf6, 0xf5, 0xd7, 0xe1, 0xce, 0xe5, 0x7e, 0xf9, 0x07, 0x4f, 0xfe, + 0x04, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x4a, 0x47, 0x36, 0xfe, 0x02, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index d1066cc33..63db18532 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -13,11 +13,12 @@ message Metadata { } message Announcement { - string service_name = 1; - string service_version = 2; - string net_address = 3; - string description = 11; - string token = 12; + string id = 1; + string token = 2; + string description = 3; + string service_name = 4; + string service_version = 5; + string net_address = 11; repeated Metadata metadata = 21; } diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index bad99d1d8..74756de08 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -19,6 +19,7 @@ var BrokerCacheTime = 30 * time.Minute // BrokerDiscovery is used as a client to discover Brokers type BrokerDiscovery interface { Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) + All() ([]*pb.Announcement, error) } type brokerDiscovery struct { @@ -53,6 +54,17 @@ func (d *brokerDiscovery) refreshCache() error { return nil } +func (d *brokerDiscovery) All() (announcements []*pb.Announcement, err error) { + d.cacheLock.Lock() + if time.Now().After(d.cacheValidUntil) { + d.cacheValidUntil = time.Now().Add(10 * time.Second) + go d.refreshCache() + } + announcements = d.cache + d.cacheLock.Unlock() + return +} + func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) { d.cacheLock.Lock() if time.Now().After(d.cacheValidUntil) { diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index df1324f54..e6bff9926 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -16,7 +16,7 @@ func buildTestBrokerDiscoveryClient(port uint) *brokerDiscovery { return discovery } -func TestBrokerDiscoveryDiscover(t *testing.T) { +func TestBrokerDiscovery(t *testing.T) { a := New(t) // Broker1 has a prefix with all DevAddrs @@ -45,7 +45,14 @@ func TestBrokerDiscoveryDiscover(t *testing.T) { cache: []*pb.Announcement{broker1, broker2, broker3}, } - results, err := d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) + results, err := d.All() + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldContain, broker1) + a.So(results, ShouldContain, broker2) + a.So(results, ShouldContain, broker3) + + results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) a.So(err, ShouldBeNil) a.So(results, ShouldNotBeEmpty) a.So(results, ShouldContain, broker1) diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 9dd304a26..ae4f2096b 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -2,6 +2,7 @@ package discovery import ( + "errors" "fmt" "sync" @@ -33,11 +34,15 @@ func (d *discovery) Announce(announcement *pb.Announcement) error { } // Find an existing announcement - service, ok := services[announcement.Token] + service, ok := services[announcement.Id] if ok { - *service = *announcement + if announcement.Token == service.Token { + *service = *announcement + } else { + return errors.New("ttn/core: Invalid token") + } } else { - services[announcement.Token] = announcement + services[announcement.Id] = announcement } return nil @@ -56,7 +61,9 @@ func (d *discovery) Discover(serviceName string) ([]*pb.Announcement, error) { // Traverse the list announcements := make([]*pb.Announcement, 0, len(services)) for _, service := range services { - announcements = append(announcements, service) + serviceCopy := *service + serviceCopy.Token = "" + announcements = append(announcements, &serviceCopy) } return announcements, nil } diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index dade59efc..6e369bf53 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -10,9 +10,9 @@ import ( func TestDiscoveryDiscover(t *testing.T) { a := New(t) - router := &pb.Announcement{Token: "router"} - broker1 := &pb.Announcement{Token: "broker1"} - broker2 := &pb.Announcement{Token: "broker2"} + router := &pb.Announcement{Id: "router", Token: "abcd"} + broker1 := &pb.Announcement{Id: "broker1"} + broker2 := &pb.Announcement{Id: "broker2"} d := &discovery{ services: map[string]map[string]*pb.Announcement{ @@ -31,22 +31,22 @@ func TestDiscoveryDiscover(t *testing.T) { services, err := d.Discover("router") a.So(err, ShouldBeNil) - a.So(services, ShouldContain, router) - a.So(services, ShouldNotContain, broker1) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].Id, ShouldEqual, router.Id) + a.So(services[0].Token, ShouldBeEmpty) services, err = d.Discover("broker") a.So(err, ShouldBeNil) - a.So(services, ShouldContain, broker1) - a.So(services, ShouldContain, broker2) - a.So(services, ShouldNotContain, router) + a.So(services, ShouldHaveLength, 2) } func TestDiscoveryAnnounce(t *testing.T) { a := New(t) - broker1a := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "old address"} - broker1b := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "new address"} - broker2 := &pb.Announcement{ServiceName: "broker", Token: "broker2", NetAddress: "other address"} + broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1", Token: "abcd", NetAddress: "old address"} + broker1aNoToken := &pb.Announcement{ServiceName: "broker", Id: "broker1", NetAddress: "new address"} + broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1", Token: "abcd", NetAddress: "new address"} + broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker2", NetAddress: "other address"} d := &discovery{ services: map[string]map[string]*pb.Announcement{}, @@ -55,6 +55,9 @@ func TestDiscoveryAnnounce(t *testing.T) { err := d.Announce(broker1a) a.So(err, ShouldBeNil) + err = d.Announce(broker1aNoToken) + a.So(err, ShouldNotBeNil) + services, err := d.Discover("broker") a.So(err, ShouldBeNil) a.So(services, ShouldHaveLength, 1) From b13f02b61367f7252e0a836ff0076a46c6c3a2e0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 18 May 2016 11:54:51 +0200 Subject: [PATCH 1444/2266] Remove payload from DeviceActivationResponse --- api/router/router.pb.go | 298 ++++++++++------------------------------ api/router/router.proto | 9 +- 2 files changed, 79 insertions(+), 228 deletions(-) diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 54c51fee5..9aeb60b2e 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -137,9 +137,6 @@ func (m *DeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { } type DeviceActivationResponse struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - ProtocolConfiguration *protocol.TxConfiguration `protobuf:"bytes,11,opt,name=protocol_configuration,json=protocolConfiguration" json:"protocol_configuration,omitempty"` - GatewayConfiguration *gateway.TxConfiguration `protobuf:"bytes,12,opt,name=gateway_configuration,json=gatewayConfiguration" json:"gateway_configuration,omitempty"` } func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } @@ -147,20 +144,6 @@ func (m *DeviceActivationResponse) String() string { return proto.Com func (*DeviceActivationResponse) ProtoMessage() {} func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{4} } -func (m *DeviceActivationResponse) GetProtocolConfiguration() *protocol.TxConfiguration { - if m != nil { - return m.ProtocolConfiguration - } - return nil -} - -func (m *DeviceActivationResponse) GetGatewayConfiguration() *gateway.TxConfiguration { - if m != nil { - return m.GatewayConfiguration - } - return nil -} - // message GatewaysRequest is used to list all Gateways on this Router type GatewaysRequest struct { } @@ -979,32 +962,6 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.Payload) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - if m.ProtocolConfiguration != nil { - data[i] = 0x5a - i++ - i = encodeVarintRouter(data, i, uint64(m.ProtocolConfiguration.Size())) - n9, err := m.ProtocolConfiguration.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n9 - } - if m.GatewayConfiguration != nil { - data[i] = 0x62 - i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayConfiguration.Size())) - n10, err := m.GatewayConfiguration.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n10 - } return i, nil } @@ -1075,11 +1032,11 @@ func (m *RegisterGatewayRequest) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n11, err := m.GatewayEui.MarshalTo(data[i:]) + n9, err := m.GatewayEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n9 } return i, nil } @@ -1103,11 +1060,11 @@ func (m *UnregisterGatewayRequest) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n12, err := m.GatewayEui.MarshalTo(data[i:]) + n10, err := m.GatewayEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n10 } return i, nil } @@ -1131,11 +1088,11 @@ func (m *GatewayStatusRequest) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n13, err := m.GatewayEui.MarshalTo(data[i:]) + n11, err := m.GatewayEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n11 } return i, nil } @@ -1159,11 +1116,11 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.LastStatus.Size())) - n14, err := m.LastStatus.MarshalTo(data[i:]) + n12, err := m.LastStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n12 } return i, nil } @@ -1205,11 +1162,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n15, err := m.GatewayStatus.MarshalTo(data[i:]) + n13, err := m.GatewayStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n13 } if m.ActiveGateways != 0 { data[i] = 0x20 @@ -1220,11 +1177,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n16, err := m.Uplink.MarshalTo(data[i:]) + n14, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n14 } if m.Downlink != nil { data[i] = 0xaa @@ -1232,11 +1189,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n17, err := m.Downlink.MarshalTo(data[i:]) + n15, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n15 } if m.Activations != nil { data[i] = 0xfa @@ -1244,11 +1201,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n18, err := m.Activations.MarshalTo(data[i:]) + n16, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n16 } if m.ActivationsAccepted != nil { data[i] = 0x82 @@ -1256,11 +1213,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x2 i++ i = encodeVarintRouter(data, i, uint64(m.ActivationsAccepted.Size())) - n19, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n17, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n17 } if m.ConnectedGateways != 0 { data[i] = 0xc8 @@ -1377,18 +1334,6 @@ func (m *DeviceActivationRequest) Size() (n int) { func (m *DeviceActivationResponse) Size() (n int) { var l int _ = l - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - if m.ProtocolConfiguration != nil { - l = m.ProtocolConfiguration.Size() - n += 1 + l + sovRouter(uint64(l)) - } - if m.GatewayConfiguration != nil { - l = m.GatewayConfiguration.Size() - n += 1 + l + sovRouter(uint64(l)) - } return n } @@ -2088,103 +2033,6 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} - } - iNdEx = postIndex - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfiguration", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.ProtocolConfiguration == nil { - m.ProtocolConfiguration = &protocol.TxConfiguration{} - } - if err := m.ProtocolConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 12: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayConfiguration", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.GatewayConfiguration == nil { - m.GatewayConfiguration = &gateway.TxConfiguration{} - } - if err := m.GatewayConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRouter(data[iNdEx:]) @@ -3095,60 +2943,60 @@ var ( ) var fileDescriptorRouter = []byte{ - // 880 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4d, 0x6f, 0xf3, 0x44, - 0x10, 0xc6, 0x05, 0xa5, 0xe9, 0x24, 0x69, 0x92, 0x6d, 0xd2, 0x9a, 0x08, 0xda, 0xca, 0x07, 0x28, - 0x1f, 0x4d, 0xda, 0xa0, 0x0a, 0x55, 0x15, 0x1f, 0x29, 0xad, 0xaa, 0x4a, 0xa4, 0x42, 0x6e, 0x7b, - 0xe1, 0x12, 0x6d, 0x9c, 0xad, 0x6b, 0x25, 0xb5, 0x8d, 0x77, 0x9d, 0xb6, 0xff, 0x84, 0x1f, 0xc1, - 0x0f, 0xe1, 0x80, 0x10, 0x17, 0x38, 0x70, 0x40, 0x08, 0xfe, 0x02, 0x17, 0x6e, 0x6c, 0xd6, 0xbb, - 0x8e, 0xed, 0x34, 0xd0, 0xf2, 0xbe, 0xaa, 0xf4, 0x1e, 0xac, 0x78, 0x67, 0x9e, 0x7d, 0x66, 0xc6, - 0xf3, 0x15, 0xf8, 0xd8, 0x76, 0xd8, 0x75, 0xd8, 0x6f, 0x5a, 0xde, 0x4d, 0xeb, 0xe2, 0x9a, 0x5c, + // 876 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x55, 0x4f, 0x4f, 0xeb, 0x46, + 0x10, 0xaf, 0x69, 0x15, 0xc2, 0x24, 0x21, 0xc9, 0x92, 0x80, 0x1b, 0xb5, 0x80, 0x7c, 0x68, 0xe9, + 0x1f, 0x12, 0x48, 0x85, 0x2a, 0x84, 0xfa, 0x27, 0x14, 0x84, 0x90, 0x1a, 0x54, 0x19, 0xb8, 0xf4, + 0x12, 0x6d, 0x9c, 0xc5, 0x58, 0x09, 0xb6, 0xeb, 0x5d, 0x07, 0xf8, 0x26, 0xfd, 0x10, 0xfd, 0x20, + 0x3d, 0x54, 0x55, 0x4f, 0x3d, 0xf4, 0x50, 0x55, 0xed, 0x57, 0xe8, 0xa5, 0xb7, 0x6e, 0xd6, 0xbb, + 0x8e, 0xed, 0x90, 0xf7, 0xe0, 0xbd, 0x27, 0x0e, 0x96, 0x77, 0x67, 0x7e, 0xfb, 0x9b, 0x99, 0x9d, + 0xd9, 0x19, 0xf8, 0xdc, 0x76, 0xd8, 0x75, 0xd8, 0x6f, 0x5a, 0xde, 0x4d, 0xeb, 0xe2, 0x9a, 0x5c, 0x5c, 0x3b, 0xae, 0x4d, 0xcf, 0x08, 0xbb, 0xf5, 0x82, 0x61, 0x8b, 0x31, 0xb7, 0x85, 0x7d, 0xa7, - 0x15, 0x78, 0x21, 0x23, 0x81, 0xfc, 0x69, 0xfa, 0x81, 0xc7, 0x3c, 0x94, 0x8b, 0x4e, 0x8d, 0xed, - 0x04, 0x81, 0xed, 0xd9, 0x5e, 0x4b, 0xa8, 0xfb, 0xe1, 0x95, 0x38, 0x89, 0x83, 0x78, 0x8b, 0xae, - 0xa5, 0xe0, 0x73, 0xed, 0xf1, 0x47, 0xc2, 0x0f, 0x1e, 0x03, 0x17, 0x50, 0xcb, 0x1b, 0xc5, 0x2f, - 0xf2, 0xf2, 0xfe, 0x63, 0x2e, 0xdb, 0x98, 0x91, 0x5b, 0x7c, 0xaf, 0x7e, 0xa3, 0xab, 0x06, 0x82, - 0xca, 0x79, 0xd8, 0xa7, 0x56, 0xe0, 0xf4, 0x89, 0x49, 0xbe, 0x09, 0x09, 0x65, 0xc6, 0x77, 0x1a, - 0x94, 0x2e, 0xfd, 0x91, 0xe3, 0x0e, 0xbb, 0x84, 0x52, 0x6c, 0x13, 0xa4, 0xc3, 0xa2, 0x8f, 0xef, - 0x47, 0x1e, 0x1e, 0xe8, 0xda, 0xa6, 0xb6, 0x55, 0x34, 0xd5, 0x11, 0x75, 0xa0, 0xaa, 0x9c, 0xe9, - 0xdd, 0x10, 0x86, 0x07, 0x98, 0x61, 0xbd, 0xc0, 0x31, 0x85, 0x76, 0xad, 0x19, 0xbb, 0x69, 0xde, - 0x75, 0xa5, 0xce, 0xac, 0x28, 0xa1, 0x92, 0xa0, 0x4f, 0xa1, 0x22, 0x7d, 0x9a, 0x32, 0x14, 0x05, - 0xc3, 0x4a, 0x53, 0x39, 0x9b, 0x20, 0x28, 0x4b, 0x99, 0x12, 0x18, 0x3f, 0x68, 0x50, 0x3e, 0xf2, - 0x6e, 0xdd, 0xc7, 0x39, 0xfc, 0x15, 0xac, 0xc6, 0x0e, 0x5b, 0x9e, 0x7b, 0xe5, 0xd8, 0x61, 0x80, - 0x99, 0xe3, 0xb9, 0xd2, 0xeb, 0x37, 0xa7, 0x5e, 0x5f, 0xdc, 0x7d, 0x91, 0x04, 0x98, 0x75, 0xa5, - 0x49, 0x89, 0x51, 0x17, 0xea, 0xca, 0xff, 0x34, 0x61, 0x14, 0x84, 0x1e, 0x07, 0x91, 0xe5, 0xab, - 0x49, 0x45, 0x4a, 0x6a, 0xfc, 0xb2, 0x00, 0x6b, 0x47, 0x64, 0xec, 0x58, 0xa4, 0x63, 0x31, 0x67, - 0x1c, 0x41, 0xa3, 0xcc, 0xfc, 0x4b, 0x58, 0x67, 0xb0, 0x38, 0x20, 0xe3, 0x1e, 0x09, 0x1d, 0x11, - 0x47, 0xf1, 0x70, 0xef, 0xd7, 0xdf, 0x36, 0x76, 0xff, 0xab, 0x2e, 0x2c, 0x2f, 0x20, 0x2d, 0x76, - 0xef, 0x13, 0xda, 0xe4, 0x26, 0x8f, 0x2f, 0x4f, 0xcd, 0x1c, 0x67, 0x39, 0x0e, 0x9d, 0x09, 0x1f, - 0xf6, 0x7d, 0xc1, 0x57, 0xfc, 0x5f, 0x7c, 0x1d, 0xdf, 0x17, 0x7c, 0x9c, 0x65, 0xc2, 0xf7, 0x60, - 0x9d, 0xd4, 0x5f, 0xb8, 0x4e, 0x56, 0x9f, 0x50, 0x27, 0x3f, 0x6b, 0xa0, 0xcf, 0x7e, 0x58, 0xea, - 0x7b, 0x2e, 0x7d, 0xa5, 0x0b, 0xa6, 0x0a, 0xe5, 0x93, 0x48, 0x4e, 0x55, 0x07, 0xbb, 0x50, 0x99, - 0x8a, 0x64, 0x84, 0x5f, 0x43, 0x41, 0x59, 0x75, 0x06, 0x94, 0x47, 0xf9, 0x3a, 0xcf, 0xea, 0x3e, - 0xcf, 0xea, 0xde, 0x13, 0xb2, 0x2a, 0x59, 0x27, 0x99, 0x05, 0xc9, 0x76, 0x3a, 0xa0, 0x06, 0x83, - 0x55, 0x93, 0xd8, 0x0e, 0xe5, 0x73, 0x52, 0x22, 0x54, 0xc5, 0x26, 0xac, 0x4e, 0x6a, 0x49, 0x7c, - 0xdb, 0x97, 0x61, 0x95, 0xd7, 0x94, 0x31, 0x06, 0xfd, 0xd2, 0x0d, 0x9e, 0xdf, 0x6e, 0x00, 0x35, - 0xa9, 0x39, 0x67, 0x98, 0x85, 0xf4, 0x39, 0x6c, 0x9e, 0x42, 0x3d, 0x63, 0x53, 0xa6, 0x75, 0x07, - 0x0a, 0x23, 0x4c, 0x59, 0x8f, 0x0a, 0xb1, 0x30, 0x5a, 0x68, 0x97, 0xe3, 0x12, 0x92, 0x68, 0x98, - 0x60, 0xa2, 0x77, 0xa3, 0x0c, 0xa5, 0x94, 0xdf, 0xc6, 0xdf, 0x0b, 0x90, 0x8b, 0x24, 0x68, 0x17, - 0x96, 0x55, 0x08, 0x29, 0x42, 0x68, 0x4e, 0x56, 0x95, 0xc9, 0x55, 0xd4, 0x2c, 0xd9, 0x49, 0x47, - 0xd0, 0xbb, 0x50, 0xc6, 0x93, 0x7e, 0x22, 0x3d, 0x29, 0xa7, 0xfa, 0x1b, 0xfc, 0x4e, 0xc9, 0x5c, - 0x8e, 0xc4, 0xaa, 0x10, 0x91, 0x01, 0xb9, 0x50, 0x6c, 0x15, 0xd9, 0x38, 0x49, 0x4e, 0xa9, 0x41, - 0xef, 0x40, 0x7e, 0x20, 0x47, 0xb9, 0x9c, 0x0e, 0x49, 0x54, 0xac, 0x43, 0x1f, 0x42, 0x01, 0xc7, - 0x4d, 0x4c, 0xf5, 0x8d, 0x19, 0x68, 0x52, 0x8d, 0x3e, 0x81, 0x5a, 0xe2, 0xd8, 0xc3, 0x96, 0x45, - 0x7c, 0x46, 0x06, 0xfa, 0xe6, 0xcc, 0xb5, 0x95, 0x04, 0xae, 0x23, 0x61, 0x68, 0x1b, 0x10, 0xef, - 0x53, 0x97, 0x58, 0xfc, 0x30, 0x0d, 0xf2, 0x3d, 0x11, 0x64, 0x35, 0xd6, 0xc4, 0x71, 0x7e, 0x00, - 0x53, 0x61, 0xaf, 0x1f, 0x78, 0x43, 0x12, 0x50, 0xfd, 0x7d, 0x81, 0xae, 0xc4, 0x8a, 0xc3, 0x48, - 0xde, 0xfe, 0x4b, 0x83, 0x9c, 0x29, 0xfe, 0x60, 0xf0, 0x98, 0x4a, 0xa9, 0x14, 0xa3, 0x6c, 0x16, - 0x1b, 0x79, 0xe1, 0x69, 0xc7, 0x1a, 0x6e, 0x69, 0xdc, 0x4a, 0x2e, 0xda, 0xd1, 0xa8, 0xde, 0x94, - 0xff, 0x57, 0x52, 0x3b, 0x3b, 0x05, 0xfe, 0x1c, 0x96, 0xe2, 0x2d, 0x8f, 0x74, 0x85, 0xcf, 0x2e, - 0xfe, 0xc6, 0x9a, 0xd2, 0x64, 0xd6, 0xe9, 0x8e, 0xc6, 0x67, 0x56, 0x5e, 0x4e, 0x4d, 0x82, 0x36, - 0x62, 0xd8, 0xc3, 0x6b, 0xaa, 0xb1, 0x39, 0x1f, 0x10, 0x55, 0x6d, 0xfb, 0xc7, 0x05, 0x28, 0x45, - 0x61, 0x77, 0xb1, 0xcb, 0x2d, 0x04, 0x3c, 0x47, 0xf9, 0xf8, 0x0b, 0xc6, 0x7e, 0x64, 0xe6, 0x5a, - 0x43, 0x9f, 0x55, 0xc8, 0x36, 0x38, 0x80, 0x72, 0x66, 0x02, 0xa1, 0x75, 0x05, 0x7e, 0x78, 0x34, - 0x4d, 0x3f, 0x10, 0xfa, 0x0c, 0xaa, 0x33, 0x83, 0x04, 0xc5, 0x41, 0xcc, 0x9b, 0x31, 0x09, 0x82, - 0x2f, 0xb3, 0xa9, 0x7b, 0x2b, 0xe3, 0x68, 0xaa, 0xe1, 0x1a, 0x6f, 0xcf, 0xd1, 0xca, 0x58, 0xda, - 0xb0, 0x74, 0x42, 0x64, 0xb7, 0x4e, 0xb3, 0x9b, 0xa6, 0x58, 0x4e, 0x8b, 0x0f, 0x2b, 0xdf, 0xff, - 0xb1, 0xae, 0xfd, 0xc4, 0x9f, 0xdf, 0xf9, 0xf3, 0xed, 0x9f, 0xeb, 0xaf, 0xf5, 0x73, 0x62, 0xf9, - 0x7c, 0xf4, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5a, 0xf7, 0x9e, 0xe3, 0xf9, 0x0a, 0x00, 0x00, + 0x15, 0x78, 0x21, 0x23, 0x81, 0xfc, 0x35, 0xfd, 0xc0, 0x63, 0x1e, 0xca, 0x45, 0xbb, 0xc6, 0x76, + 0x82, 0xc0, 0xf6, 0x6c, 0xaf, 0x25, 0xd4, 0xfd, 0xf0, 0x4a, 0xec, 0xc4, 0x46, 0xac, 0xa2, 0x63, + 0x29, 0xf8, 0x5c, 0x7b, 0xfc, 0x93, 0xf0, 0x83, 0xc7, 0xc0, 0x05, 0xd4, 0xf2, 0x46, 0xf1, 0x42, + 0x1e, 0xde, 0x7f, 0xcc, 0x61, 0x1b, 0x33, 0x72, 0x8b, 0xef, 0xd5, 0x3f, 0x3a, 0x6a, 0x20, 0xa8, + 0x9c, 0x87, 0x7d, 0x6a, 0x05, 0x4e, 0x9f, 0x98, 0xe4, 0x87, 0x90, 0x50, 0x66, 0xfc, 0xa4, 0x41, + 0xe9, 0xd2, 0x1f, 0x39, 0xee, 0xb0, 0x4b, 0x28, 0xc5, 0x36, 0x41, 0x3a, 0x2c, 0xfa, 0xf8, 0x7e, + 0xe4, 0xe1, 0x81, 0xae, 0x6d, 0x6a, 0x5b, 0x45, 0x53, 0x6d, 0x51, 0x07, 0xaa, 0xca, 0x99, 0xde, + 0x0d, 0x61, 0x78, 0x80, 0x19, 0xd6, 0x0b, 0x1c, 0x53, 0x68, 0xd7, 0x9a, 0xb1, 0x9b, 0xe6, 0x5d, + 0x57, 0xea, 0xcc, 0x8a, 0x12, 0x2a, 0x09, 0xfa, 0x12, 0x2a, 0xd2, 0xa7, 0x29, 0x43, 0x51, 0x30, + 0xac, 0x34, 0x95, 0xb3, 0x09, 0x82, 0xb2, 0x94, 0x29, 0x81, 0xf1, 0x8b, 0x06, 0xe5, 0x23, 0xef, + 0xd6, 0x7d, 0x9c, 0xc3, 0xdf, 0xc1, 0x6a, 0xec, 0xb0, 0xe5, 0xb9, 0x57, 0x8e, 0x1d, 0x06, 0x98, + 0x39, 0x9e, 0x2b, 0xbd, 0x7e, 0x77, 0xea, 0xf5, 0xc5, 0xdd, 0x37, 0x49, 0x80, 0x59, 0x57, 0x9a, + 0x94, 0x18, 0x75, 0xa1, 0xae, 0xfc, 0x4f, 0x13, 0x46, 0x41, 0xe8, 0x71, 0x10, 0x59, 0xbe, 0x9a, + 0x54, 0xa4, 0xa4, 0xc6, 0xef, 0x0b, 0xb0, 0x76, 0x44, 0xc6, 0x8e, 0x45, 0x3a, 0x16, 0x73, 0xc6, + 0x11, 0x34, 0xca, 0xcc, 0x0b, 0xc2, 0x3a, 0x83, 0xc5, 0x01, 0x19, 0xf7, 0x48, 0xe8, 0x88, 0x38, + 0x8a, 0x87, 0x7b, 0x7f, 0xfc, 0xb9, 0xb1, 0xfb, 0xb2, 0xba, 0xb0, 0xbc, 0x80, 0xb4, 0xd8, 0xbd, + 0x4f, 0x68, 0x93, 0x9b, 0x3c, 0xbe, 0x3c, 0x35, 0x73, 0x9c, 0xe5, 0x38, 0x74, 0x26, 0x7c, 0xd8, + 0xf7, 0x05, 0x5f, 0xf1, 0x95, 0xf8, 0x3a, 0xbe, 0x2f, 0xf8, 0x38, 0xcb, 0x84, 0xef, 0xc1, 0x3a, + 0xa9, 0xbf, 0x76, 0x9d, 0xac, 0x3e, 0xa1, 0x4e, 0x1a, 0xa0, 0xcf, 0xde, 0x2b, 0xf5, 0x3d, 0x97, + 0x12, 0xa3, 0x0a, 0xe5, 0x93, 0x08, 0x4e, 0xd5, 0x2b, 0x70, 0xa1, 0x32, 0x15, 0x45, 0x30, 0xf4, + 0x3d, 0x14, 0x94, 0x0b, 0xce, 0x80, 0xf2, 0x1c, 0xbc, 0xcd, 0x6f, 0x66, 0x9f, 0xdf, 0xcc, 0xde, + 0x13, 0x6e, 0x46, 0xb2, 0x4e, 0x6e, 0x07, 0x24, 0xdb, 0xe9, 0x80, 0x1a, 0x0c, 0x56, 0x4d, 0x62, + 0x3b, 0x94, 0xf7, 0x1a, 0x89, 0x50, 0x59, 0x4f, 0x58, 0x9d, 0xe4, 0x43, 0x64, 0xfe, 0x4d, 0x58, + 0xe5, 0x79, 0x31, 0xc6, 0xa0, 0x5f, 0xba, 0xc1, 0xf3, 0xdb, 0x0d, 0xa0, 0x26, 0x35, 0xe7, 0x0c, + 0xb3, 0x90, 0x3e, 0x87, 0xcd, 0x53, 0xa8, 0x67, 0x6c, 0xca, 0xb4, 0xee, 0x40, 0x61, 0x84, 0x29, + 0xeb, 0x51, 0x21, 0x16, 0x46, 0x0b, 0xed, 0x72, 0x5c, 0x54, 0x12, 0x0d, 0x13, 0x4c, 0xb4, 0x36, + 0xca, 0x50, 0x4a, 0xf9, 0x6d, 0xfc, 0xb7, 0x00, 0xb9, 0x48, 0x82, 0x76, 0x61, 0x59, 0x85, 0x90, + 0x22, 0x84, 0xe6, 0xa4, 0xdd, 0x9b, 0x5c, 0x45, 0xcd, 0x92, 0x9d, 0x74, 0x04, 0x7d, 0x08, 0x65, + 0x3c, 0x29, 0x4a, 0xd2, 0x93, 0x72, 0xaa, 0xbf, 0xc3, 0xcf, 0x94, 0xcc, 0xe5, 0x48, 0xac, 0x0a, + 0x11, 0x19, 0x90, 0x0b, 0x45, 0x67, 0x96, 0xdd, 0x2a, 0xc9, 0x29, 0x35, 0xe8, 0x03, 0xc8, 0x0f, + 0x64, 0x3b, 0x94, 0x2f, 0x2c, 0x89, 0x8a, 0x75, 0xe8, 0x53, 0x28, 0xe0, 0xf8, 0x25, 0x50, 0x7d, + 0x63, 0x06, 0x9a, 0x54, 0xa3, 0x2f, 0xa0, 0x96, 0xd8, 0xf6, 0xb0, 0x65, 0x11, 0x9f, 0x91, 0x81, + 0xbe, 0x39, 0x73, 0x6c, 0x25, 0x81, 0xeb, 0x48, 0x18, 0xda, 0x06, 0xc4, 0x9b, 0xa3, 0x4b, 0x2c, + 0xbe, 0x99, 0x06, 0xf9, 0x91, 0x08, 0xb2, 0x1a, 0x6b, 0xe2, 0x38, 0x3f, 0x81, 0xa9, 0xb0, 0xd7, + 0x0f, 0xbc, 0x21, 0x09, 0xa8, 0xfe, 0xb1, 0x40, 0x57, 0x62, 0xc5, 0x61, 0x24, 0x6f, 0xff, 0xab, + 0x41, 0xce, 0x14, 0x43, 0x9a, 0xc7, 0x54, 0x4a, 0xa5, 0x18, 0x65, 0xb3, 0xd8, 0xc8, 0x0b, 0x4f, + 0x3b, 0xd6, 0x70, 0x4b, 0xe3, 0x56, 0x72, 0xd1, 0x9c, 0x43, 0xf5, 0xa6, 0x9c, 0xf9, 0xa9, 0xb9, + 0x97, 0x02, 0x7f, 0x0d, 0x4b, 0xf1, 0xa4, 0x44, 0xba, 0xc2, 0x67, 0x87, 0x67, 0x63, 0x4d, 0x69, + 0x32, 0x23, 0x69, 0x47, 0xe3, 0x83, 0x22, 0x2f, 0x5b, 0x0f, 0x41, 0x1b, 0x31, 0xec, 0xe1, 0x56, + 0xdf, 0xd8, 0x9c, 0x0f, 0x88, 0xaa, 0xb6, 0xfd, 0xeb, 0x02, 0x94, 0xa2, 0xb0, 0xbb, 0xd8, 0xe5, + 0x16, 0x02, 0x9e, 0xa3, 0x7c, 0x7c, 0x83, 0xb1, 0x1f, 0x99, 0xbe, 0xd6, 0xd0, 0x67, 0x15, 0xf2, + 0x19, 0x1c, 0x40, 0x39, 0xd3, 0x81, 0xd0, 0xba, 0x02, 0x3f, 0xdc, 0x9a, 0xa6, 0x17, 0x84, 0xbe, + 0x82, 0xea, 0x4c, 0x23, 0x41, 0x71, 0x10, 0xf3, 0x7a, 0x4c, 0x82, 0xe0, 0xdb, 0x6c, 0xea, 0xde, + 0xcb, 0x38, 0x9a, 0x7a, 0x70, 0x8d, 0xf7, 0xe7, 0x68, 0x65, 0x2c, 0x6d, 0x58, 0x3a, 0x21, 0xf2, + 0xb5, 0x4e, 0xb3, 0x9b, 0xa6, 0x58, 0x4e, 0x8b, 0x0f, 0x2b, 0x3f, 0xff, 0xbd, 0xae, 0xfd, 0xc6, + 0xbf, 0xbf, 0xf8, 0xf7, 0xe3, 0x3f, 0xeb, 0x6f, 0xf5, 0x73, 0x62, 0x08, 0x7d, 0xf6, 0x7f, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x6d, 0x1f, 0x93, 0x28, 0x3d, 0x0a, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index f5f680599..ae3c66f86 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -31,9 +31,12 @@ message DeviceActivationRequest { } message DeviceActivationResponse { - bytes payload = 1; - protocol.TxConfiguration protocol_configuration = 11; - gateway.TxConfiguration gateway_configuration = 12; + // NOTE: In LoRaWAN, device activations are accepted with DownlinkMessages, so + // this message is just an Ack. + // + // bytes payload = 1; + // protocol.TxConfiguration protocol_configuration = 11; + // gateway.TxConfiguration gateway_configuration = 12; } // The Router service provides pure network functionality From f3b548e75dc564f571e01697960642bf02e46c1c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 18 May 2016 11:56:10 +0200 Subject: [PATCH 1445/2266] [refactor] Router implementation --- core/router/activation.go | 89 ++++++++ core/router/activation_test.go | 37 ++++ core/router/downlink.go | 288 +++++++++++++++++++++++++ core/router/downlink_test.go | 374 +++++++++++++++++++++++++++++++++ core/router/router.go | 52 ++++- core/router/router_test.go | 6 + core/router/server.go | 111 ++++++++++ core/router/server_test.go | 198 +++++++++++++++++ core/router/uplink.go | 38 ++++ core/router/uplink_test.go | 75 +++++++ core/types/data_rate.go | 13 ++ 11 files changed, 1270 insertions(+), 11 deletions(-) create mode 100644 core/router/activation.go create mode 100644 core/router/activation_test.go create mode 100644 core/router/downlink.go create mode 100644 core/router/downlink_test.go create mode 100644 core/router/server.go create mode 100644 core/router/server_test.go create mode 100644 core/router/uplink.go create mode 100644 core/router/uplink_test.go diff --git a/core/router/activation.go b/core/router/activation.go new file mode 100644 index 000000000..1b5012530 --- /dev/null +++ b/core/router/activation.go @@ -0,0 +1,89 @@ +package router + +import ( + "errors" + "sync" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "golang.org/x/net/context" +) + +func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + + gateway := r.getGateway(gatewayEUI) + + uplink := &pb.UplinkMessage{ + Payload: activation.Payload, + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + } + + // Only for LoRaWAN + gateway.Utilization.AddRx(uplink) + + downlinkOptions := buildDownlinkOptions(uplink, true, gateway) + + // Find Broker + brokers, err := r.brokerDiscovery.All() + if err != nil { + return nil, err + } + + // Forward to all brokers and collect responses + var wg sync.WaitGroup + responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers)) + for _, broker := range brokers { + broker, err := r.getBroker(broker) + if err != nil { + continue + } + + // Do async request + wg.Add(1) + go func() { + res, err := broker.client.Activate(context.Background(), &pb_broker.DeviceActivationRequest{ + Payload: activation.Payload, + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + DownlinkOptions: downlinkOptions, + }) + if err == nil && res != nil { + responses <- res + } + wg.Done() + }() + } + + // Make sure to close channel when all requests are done + go func() { + wg.Wait() + close(responses) + }() + + var gotFirst bool + for res := range responses { + if gotFirst { + // warn for duplicate responses + } else { + gotFirst = true + downlink := &pb_broker.DownlinkMessage{ + Payload: res.Payload, + DownlinkOption: res.DownlinkOption, + } + err := r.HandleDownlink(downlink) + if err != nil { + gotFirst = false // try again + } + } + } + + // Activation not accepted by any broker + if !gotFirst { + return nil, errors.New("ttn/router: Activation not accepted at this Gateway") + } + + // Activation accepted by (at least one) broker + return &pb.DeviceActivationResponse{}, nil +} diff --git a/core/router/activation_test.go b/core/router/activation_test.go new file mode 100644 index 000000000..9ecb79b0a --- /dev/null +++ b/core/router/activation_test.go @@ -0,0 +1,37 @@ +package router + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestHandleActivation(t *testing.T) { + a := New(t) + + r := &router{ + gateways: map[types.GatewayEUI]*gateway.Gateway{}, + brokerDiscovery: &mockBrokerDiscovery{}, + } + + uplink := newReferenceUplink() + activation := &pb.DeviceActivationRequest{ + Payload: []byte{}, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + } + gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + + res, err := r.HandleActivation(gtwEUI, activation) + a.So(res, ShouldBeNil) + a.So(err, ShouldNotBeNil) + utilization := r.getGateway(gtwEUI).Utilization + utilization.Tick() + rx, _ := utilization.Get() + a.So(rx, ShouldBeGreaterThan, 0) + + // TODO: Integration test that checks broker forward +} diff --git a/core/router/downlink.go b/core/router/downlink.go new file mode 100644 index 000000000..84a98e1f3 --- /dev/null +++ b/core/router/downlink.go @@ -0,0 +1,288 @@ +package router + +import ( + "errors" + "math" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/toa" + lora "github.com/brocaar/lorawan/band" +) + +func (r *router) SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.DownlinkMessage, error) { + gateway := r.getGateway(gatewayEUI) + if fromSchedule := gateway.Schedule.Subscribe(); fromSchedule != nil { + toGateway := make(chan *pb.DownlinkMessage) + go func() { + for message := range fromSchedule { + gateway.Utilization.AddTx(message) + toGateway <- message + } + close(toGateway) + }() + return toGateway, nil + } + return nil, errors.New("ttn/router: Gateway downlink not available") +} + +func (r *router) UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error { + r.getGateway(gatewayEUI).Schedule.Stop() + return nil +} + +func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { + option := downlink.DownlinkOption + + gateway := r.getGateway(*option.GatewayEui) + + downlinkMessage := &pb.DownlinkMessage{ + Payload: downlink.Payload, + ProtocolConfiguration: option.ProtocolConfig, + GatewayConfiguration: option.GatewayConfig, + } + + err := gateway.Schedule.Schedule(option.Identifier, downlinkMessage) + if err != nil { + return err + } + + return nil +} + +func getBand(region string) (band *lora.Band, err error) { + var b lora.Band + + switch region { + case "EU_863_870": + b, err = lora.GetConfig(lora.EU_863_870) + case "US_902_928": + b, err = lora.GetConfig(lora.US_902_928) + case "CN_779_787": + err = errors.New("ttn/router: China 779-787 MHz band not supported") + case "EU_433": + err = errors.New("ttn/router: Europe 433 MHz band not supported") + case "AU_915_928": + b, err = lora.GetConfig(lora.AU_915_928) + case "CN_470_510": + err = errors.New("ttn/router: China 470-510 MHz band not supported") + default: + err = errors.New("ttn/router: Unknown band") + } + if err != nil { + return + } + band = &b + + // TTN-specific configuration + if region == "EU_863_870" { + // TTN uses SF9BW125 in RX2 + band.RX2DataRate = 3 + // TTN frequency plan includes extra channels next to the default channels: + band.UplinkChannels = []lora.Channel{ + lora.Channel{Frequency: 868100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 868300000, DataRates: []int{0, 1, 2, 3, 4, 5, 6}}, // Also SF7BW250 + lora.Channel{Frequency: 868500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 868800000, DataRates: []int{7}}, // FSK 50kbps + lora.Channel{Frequency: 867100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + } + band.DownlinkChannels = band.UplinkChannels + } + + return +} + +func buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway *gateway.Gateway) (downlinkOptions []*pb_broker.DownlinkOption) { + var options []*pb_broker.DownlinkOption + + gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing + + lorawanMetadata := uplink.ProtocolMetadata.GetLorawan() + if lorawanMetadata == nil { + return // We can't handle any other protocols than LoRaWAN yet + } + + semtechMetadata := uplink.GatewayMetadata.GetSemtech() + if semtechMetadata == nil { + return // We can't handle any other gateways than Semtech yet + } + + band, err := getBand(gatewayStatus.Region) + if err != nil { + return // We can't handle this region + } + + dr, err := types.ParseDataRate(lorawanMetadata.DataRate) + if err != nil { + return // Invalid packet, probably won't happen if the gateway is just doing its job + } + uplinkDRIndex, err := band.GetDataRate(lora.DataRate{Modulation: lora.LoRaModulation, SpreadFactor: int(dr.SpreadingFactor), Bandwidth: int(dr.Bandwidth)}) + if err != nil { + return // Invalid packet, probably won't happen if the gateway is just doing its job + } + + // Configuration for RX1 + { + uplinkChannel, err := band.GetChannel(int(uplink.GatewayMetadata.Frequency), uplinkDRIndex) + if err == nil { + downlinkChannel := band.DownlinkChannels[band.GetRX1Channel(uplinkChannel)] + downlinkDRIndex, err := band.GetRX1DataRateForOffset(uplinkDRIndex, 0) + if err == nil { + dataRate, _ := types.ConvertDataRate(band.DataRates[downlinkDRIndex]) + delay := band.ReceiveDelay1 + if isActivation { + delay = band.JoinAcceptDelay1 + } + rx1 := &pb_broker.DownlinkOption{ + GatewayEui: &gateway.EUI, + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa + DataRate: dataRate.String(), // This is default + CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx + }}}, + GatewayConfig: &pb_gateway.TxConfiguration{Gateway: &pb_gateway.TxConfiguration_Semtech{Semtech: &pb_semtech.TxConfiguration{ + Timestamp: semtechMetadata.Timestamp + uint32(delay/1000), + RfChain: 0, + PolarizationInversion: true, + }}, + Frequency: uint64(downlinkChannel.Frequency), + Power: int32(band.DefaultTXPower), + }, + } + options = append(options, rx1) + } + } + } + + // Configuration for RX2 + { + power := int32(band.DefaultTXPower) + if gatewayStatus.Region == "EU_863_870" { + power = 27 // The EU Downlink frequency allows up to 27dBm + if isActivation { + // TTN uses SF9BW125 in RX2, we have to reset this for joins + band.RX2DataRate = 0 + } + } + dataRate, _ := types.ConvertDataRate(band.DataRates[band.RX2DataRate]) + delay := band.ReceiveDelay2 + if isActivation { + delay = band.JoinAcceptDelay2 + } + rx2 := &pb_broker.DownlinkOption{ + GatewayEui: &gateway.EUI, + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa + DataRate: dataRate.String(), // This is default + CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx + }}}, + GatewayConfig: &pb_gateway.TxConfiguration{Gateway: &pb_gateway.TxConfiguration_Semtech{Semtech: &pb_semtech.TxConfiguration{ + Timestamp: semtechMetadata.Timestamp + uint32(delay/1000), + RfChain: 0, + PolarizationInversion: true, + }}, + Frequency: uint64(band.RX2Frequency), + Power: power, + }, + } + options = append(options, rx2) + } + + computeDownlinkScores(gateway, uplink, options) + + // Filter all illegal options + for _, option := range options { + if option.Score < 1000 { + downlinkOptions = append(downlinkOptions, option) + } + } + + return +} + +// Calculating the score for each downlink option; lower is better, 0 is best +// If a score is over 1000, it may should not be used as feasible option. +// TODO: The weights of these parameters should be optimized. I'm sure someone +// can do some computer simulations to find the right values. +func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, options []*pb_broker.DownlinkOption) { + gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing + + gatewayRx, _ := gateway.Utilization.Get() + for _, option := range options { + + // Calculate max ToA + time, _ := toa.Compute( + 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? + option.GetProtocolConfig().GetLorawan().DataRate, + option.GetProtocolConfig().GetLorawan().CodingRate, + ) + + timeScore := math.Min(time.Seconds()*5, 10) // 2 seconds will be 10 (max) + + signalScore := 0.0 // Between 0 and 20 (lower is better) + { + // Prefer high SNR + if uplink.GatewayMetadata.Snr < 5 { + signalScore += 10 + } + // Prefer good RSSI + signalScore += math.Min(float64(uplink.GatewayMetadata.Rssi*-0.1), 10) + } + + utilizationScore := 0.0 // Between 0 and 40 (lower is better) will be over 100 if forbidden + { + // Avoid gateways that do more Rx + utilizationScore += math.Min(gatewayRx*50, 20) // 40% utilization = 20 (max) + + // Avoid busy channels + freq := option.GatewayConfig.Frequency + channelRx, channelTx := gateway.Utilization.GetChannel(freq) + utilizationScore += math.Min((channelTx+channelRx)*200, 20) // 10% utilization = 20 (max) + + // Enforce European Duty Cycle + if gatewayStatus.Region == "EU_863_870" { + var duty float64 + switch { + case freq >= 865000000 && freq < 868000000: + duty = 0.01 // g 865.0 – 868.0 MHz 1% + case freq >= 868000000 && freq < 868600000: + duty = 0.01 // g1 868.0 – 868.6 MHz 1% + case freq >= 868700000 && freq < 869200000: + duty = 0.001 // g2 868.7 – 869.2 MHz 0.1% + case freq >= 869400000 && freq < 869650000: + duty = 0.1 // g3 869.4 – 869.65 MHz 10% + case freq >= 869700000 && freq < 870000000: + duty = 0.01 // g4 869.7 – 870.0 MHz 1% + default: + utilizationScore += 100 // Transmissions on this frequency are forbidden + } + if channelTx > duty { + utilizationScore += 100 // Transmissions on this frequency are forbidden + } + } + } + + scheduleScore := 0.0 // Between 0 and 30 (lower is better) will be over 100 if forbidden + { + id, conflicts := gateway.Schedule.GetOption(option.GatewayConfig.GetSemtech().Timestamp, uint32(time/1000)) + option.Identifier = id + if conflicts >= 100 { + scheduleScore += 100 + } else { + scheduleScore += math.Min(float64(conflicts*10), 30) // max 30 + } + } + + option.Score = uint32((timeScore + signalScore + utilizationScore + scheduleScore) * 10) + } +} diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go new file mode 100644 index 000000000..17c09cde1 --- /dev/null +++ b/core/router/downlink_test.go @@ -0,0 +1,374 @@ +package router + +import ( + "sync" + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +// newReferenceDownlink returns a default uplink message +func newReferenceDownlink() *pb.DownlinkMessage { + up := &pb.DownlinkMessage{ + Payload: make([]byte, 20), + ProtocolConfiguration: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + CodingRate: "4/5", + DataRate: "SF7BW125", + Modulation: pb_lorawan.Modulation_LORA, + }}}, + GatewayConfiguration: &pb_gateway.TxConfiguration{Gateway: &pb_gateway.TxConfiguration_Semtech{Semtech: &pb_semtech.TxConfiguration{ + Timestamp: 100, + }}, + Frequency: 868100000, + }, + } + return up +} + +func TestHandleDownlink(t *testing.T) { + a := New(t) + + r := &router{ + gateways: map[types.GatewayEUI]*gateway.Gateway{}, + brokerDiscovery: &mockBrokerDiscovery{}, + } + + eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + id, _ := r.getGateway(eui).Schedule.GetOption(0, 10*1000) + err := r.HandleDownlink(&pb_broker.DownlinkMessage{ + Payload: []byte{}, + DownlinkOption: &pb_broker.DownlinkOption{ + GatewayEui: &eui, + Identifier: id, + ProtocolConfig: &pb_protocol.TxConfiguration{}, + GatewayConfig: &pb_gateway.TxConfiguration{}, + }, + }) + + a.So(err, ShouldBeNil) +} + +func TestSubscribeUnsubscribeDownlink(t *testing.T) { + a := New(t) + + r := &router{ + gateways: map[types.GatewayEUI]*gateway.Gateway{}, + brokerDiscovery: &mockBrokerDiscovery{}, + } + + eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + ch, err := r.SubscribeDownlink(eui) + a.So(err, ShouldBeNil) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + var gotDownlink bool + for dl := range ch { + gotDownlink = true + a.So(dl.Payload, ShouldResemble, []byte{0x02}) + } + a.So(gotDownlink, ShouldBeTrue) + wg.Done() + }() + + id, _ := r.getGateway(eui).Schedule.GetOption(0, 10*1000) + r.HandleDownlink(&pb_broker.DownlinkMessage{ + Payload: []byte{0x02}, + DownlinkOption: &pb_broker.DownlinkOption{ + GatewayEui: &eui, + Identifier: id, + ProtocolConfig: &pb_protocol.TxConfiguration{}, + GatewayConfig: &pb_gateway.TxConfiguration{}, + }, + }) + + // Wait for the downlink to arrive + <-time.After(5 * time.Millisecond) + + err = r.UnsubscribeDownlink(eui) + a.So(err, ShouldBeNil) + + wg.Wait() +} + +func TestUplinkBuildDownlinkOptions(t *testing.T) { + a := New(t) + + // If something is incorrect, it just returns an empty list + up := &pb.UplinkMessage{} + gtw := gateway.NewGateway(types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldBeEmpty) + + // The reference gateway and uplink work as expected + gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() + options = buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].Score, ShouldBeLessThan, options[1].Score) + + // Check Delay + a.So(options[0].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 1000100) + a.So(options[1].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 2000100) + + // Check Frequency + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 868100000) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 869525000) + + // Check Power + a.So(options[0].GatewayConfig.Power, ShouldEqual, 14) + a.So(options[1].GatewayConfig.Power, ShouldEqual, 27) + + // Check Data Rate + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW125") + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF9BW125") + + // Check Coding Rate + a.So(options[0].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") + a.So(options[1].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") + + // And for joins we want a different delay (both RX1 and RX2) and DataRate (RX2) + gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() + options = buildDownlinkOptions(up, true, gtw) + a.So(options[0].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 5000100) + a.So(options[1].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 6000100) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") +} + +func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { + a := New(t) + + // Unsupported frequencies use only RX2 for downlink + gtw, up := newReferenceGateway("EU_863_870"), newReferenceUplink() + up.GatewayMetadata.Frequency = 869300000 + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 1) + + // Supported frequencies use RX1 (on the same frequency) for downlink + ttnEUFrequencies := []uint64{ + 868100000, + 868300000, + 868500000, + 867100000, + 867300000, + 867500000, + 867700000, + 867900000, + } + for _, freq := range ttnEUFrequencies { + up = newReferenceUplink() + up.GatewayMetadata.Frequency = freq + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, freq) + } + + // Unsupported frequencies use only RX2 for downlink + gtw, up = newReferenceGateway("US_902_928"), newReferenceUplink() + up.GatewayMetadata.Frequency = 923300000 + options = buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 1) + + // Supported frequencies use RX1 (on the same frequency) for downlink + ttnUSFrequencies := map[uint64]uint64{ + 903900000: 923300000, + 904100000: 923900000, + 904300000: 924500000, + 904500000: 925100000, + 904700000: 925700000, + 904900000: 926300000, + 905100000: 926900000, + 905300000: 927500000, + } + for upFreq, downFreq := range ttnUSFrequencies { + up = newReferenceUplink() + up.GatewayMetadata.Frequency = upFreq + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, downFreq) + } + + // Unsupported frequencies use only RX2 for downlink + gtw, up = newReferenceGateway("AU_915_928"), newReferenceUplink() + up.GatewayMetadata.Frequency = 923300000 + options = buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 1) + + // Supported frequencies use RX1 (on the same frequency) for downlink + ttnAUFrequencies := map[uint64]uint64{ + 916800000: 923300000, + 917000000: 923900000, + 917200000: 924500000, + 917400000: 925100000, + 917600000: 925700000, + 917800000: 926300000, + 918000000: 926900000, + 918200000: 927500000, + } + for upFreq, downFreq := range ttnAUFrequencies { + up = newReferenceUplink() + up.GatewayMetadata.Frequency = upFreq + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, downFreq) + } +} + +func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { + a := New(t) + + gtw := newReferenceGateway("EU_863_870") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnEUDataRates := []string{ + "SF7BW125", + "SF8BW125", + "SF9BW125", + "SF10BW125", + "SF11BW125", + "SF12BW125", + } + for _, dr := range ttnEUDataRates { + up := newReferenceUplink() + up.ProtocolMetadata.GetLorawan().DataRate = dr + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + } + + gtw = newReferenceGateway("US_902_928") + + // Test 500kHz channel + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 904600000 + up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnUSDataRates := map[string]string{ + "SF7BW125": "SF7BW500", + "SF8BW125": "SF8BW500", + "SF9BW125": "SF9BW500", + "SF10BW125": "SF10BW500", + } + for drUp, drDown := range ttnUSDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 903900000 + up.ProtocolMetadata.GetLorawan().DataRate = drUp + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + } + + gtw = newReferenceGateway("AU_915_928") + + // Test 500kHz channel + up = newReferenceUplink() + up.GatewayMetadata.Frequency = 917500000 + up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" + options = buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnAUDataRates := map[string]string{ + "SF7BW125": "SF7BW500", + "SF8BW125": "SF8BW500", + "SF9BW125": "SF9BW500", + "SF10BW125": "SF10BW500", + } + for drUp, drDown := range ttnAUDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 916800000 + up.ProtocolMetadata.GetLorawan().DataRate = drUp + options := buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + } +} + +// Note: This test uses buildDownlinkOptions which in turn calls computeDownlinkScores +func TestComputeDownlinkScores(t *testing.T) { + a := New(t) + gtw := newReferenceGateway("EU_863_870") + refScore := buildDownlinkOptions(newReferenceUplink(), false, gtw)[0].Score + + // Lower RSSI -> worse score + testSubject := newReferenceUplink() + testSubject.GatewayMetadata.Rssi = -80.0 + testSubjectgtw := newReferenceGateway("EU_863_870") + testSubjectScore := buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + a.So(testSubjectScore, ShouldBeGreaterThan, refScore) + + // Lower SNR -> worse score + testSubject = newReferenceUplink() + testSubject.GatewayMetadata.Snr = 2.0 + testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectScore = buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + a.So(testSubjectScore, ShouldBeGreaterThan, refScore) + + // Slower DataRate -> worse score + testSubject = newReferenceUplink() + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF10BW125" + testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectScore = buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + a.So(testSubjectScore, ShouldBeGreaterThan, refScore) + + // Gateway used for Rx -> worse score + testSubject1 := newReferenceUplink() + testSubject2 := newReferenceUplink() + testSubject2.GatewayMetadata.GetSemtech().Timestamp = 10000000 + testSubject2.GatewayMetadata.Frequency = 868500000 + testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw.Utilization.AddRx(newReferenceUplink()) + testSubjectgtw.Utilization.Tick() + testSubject1Score := buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score + testSubject2Score := buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score + a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway + a.So(testSubject2Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway + a.So(testSubject1Score, ShouldBeGreaterThan, testSubject2Score) // Because of Rx on the same channel + + // European Alarm Band + // NOTE: This frequency is not part of the TTN DownlinkChannels. This test + // case makes sure we don't allow Tx on the alarm bands even if someone + // changes the frequency plan. + testSubject = newReferenceUplink() + testSubject.GatewayMetadata.Frequency = 869300000 + testSubjectgtw = newReferenceGateway("EU_863_870") + options := buildDownlinkOptions(testSubject, false, testSubjectgtw) + a.So(options, ShouldHaveLength, 1) // RX1 Removed + a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 869300000) + + // European Duty-cycle + testSubject = newReferenceUplink() + testSubjectgtw = newReferenceGateway("EU_863_870") + for i := 0; i < 5; i++ { + testSubjectgtw.Utilization.AddTx(newReferenceDownlink()) + } + testSubjectgtw.Utilization.Tick() + options = buildDownlinkOptions(testSubject, false, testSubjectgtw) + a.So(options, ShouldHaveLength, 1) // RX1 Removed + a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 868100000) + + // Scheduling Conflicts + testSubject1 = newReferenceUplink() + testSubject2 = newReferenceUplink() + testSubject2.GatewayMetadata.GetSemtech().Timestamp = 2000000 + testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw.Schedule.GetOption(1000100, 50000) + testSubject1Score = buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score + testSubject2Score = buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score + a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Scheduling conflict with RX1 + a.So(testSubject2Score, ShouldEqual, refScore) // No scheduling conflicts +} diff --git a/core/router/router.go b/core/router/router.go index 08f7722c5..65d04d5c2 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -1,6 +1,7 @@ package router import ( + "io" "sync" "golang.org/x/net/context" @@ -24,21 +25,26 @@ type Router interface { // Handle an uplink message from a gateway HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error // Handle a downlink message - HandleDownlink(message *pb.DownlinkMessage) error + HandleDownlink(message *pb_broker.DownlinkMessage) error // Subscribe to downlink messages - SubscribeDownlink(gatewayEUI types.GatewayEUI) (chan pb.DownlinkMessage, error) + SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.DownlinkMessage, error) // Unsubscribe from downlink messages UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error // Handle a device activation HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) } +type broker struct { + client pb_broker.BrokerClient + association pb_broker.Broker_AssociateClient +} + type router struct { identity *pb_discovery.Announcement gateways map[types.GatewayEUI]*gateway.Gateway gatewaysLock sync.RWMutex brokerDiscovery discovery.BrokerDiscovery - brokers map[string]pb_broker.Broker_AssociateClient + brokers map[string]*broker brokersLock sync.RWMutex } @@ -52,22 +58,46 @@ func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { return r.gateways[eui] } -// getBroker gets or creates a broker association -func (r *router) getBroker(broker *pb_discovery.Announcement) (pb_broker.Broker_AssociateClient, error) { - r.gatewaysLock.Lock() - defer r.gatewaysLock.Unlock() - if _, ok := r.brokers[broker.NetAddress]; !ok { +// getBroker gets or creates a broker association and returns the broker +// the first time it also starts a goroutine that receives downlink from the broker +func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { + r.brokersLock.Lock() + defer r.brokersLock.Unlock() + if _, ok := r.brokers[req.NetAddress]; !ok { // Connect to the server - conn, err := grpc.Dial(broker.NetAddress, api.DialOptions...) + conn, err := grpc.Dial(req.NetAddress, api.DialOptions...) if err != nil { return nil, err } client := pb_broker.NewBrokerClient(conn) + association, err := client.Associate(context.Background()) if err != nil { return nil, err } - r.brokers[broker.NetAddress] = association + // Start a goroutine that receives and processes downlink + go func() { + for { + downlink, err := association.Recv() + if err == io.EOF { + association.CloseSend() + break + } + if err != nil { + break + } + go r.HandleDownlink(downlink) + } + // When the loop is broken: close connection and unregister broker. + conn.Close() + r.brokersLock.Lock() + defer r.brokersLock.Unlock() + delete(r.brokers, req.NetAddress) + }() + r.brokers[req.NetAddress] = &broker{ + client: client, + association: association, + } } - return r.brokers[broker.NetAddress], nil + return r.brokers[req.NetAddress], nil } diff --git a/core/router/router_test.go b/core/router/router_test.go index 7ef135b39..e3a4fd70a 100644 --- a/core/router/router_test.go +++ b/core/router/router_test.go @@ -1 +1,7 @@ package router + +import "testing" + +func TestRouterIntegration(t *testing.T) { + +} diff --git a/core/router/server.go b/core/router/server.go new file mode 100644 index 000000000..fc0eab161 --- /dev/null +++ b/core/router/server.go @@ -0,0 +1,111 @@ +package router + +import ( + "errors" + "io" + + api "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type routerRPC struct { + router Router +} + +func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, err error) { + md, ok := metadata.FromContext(ctx) + // TODO: Check OK + euiString, ok := md["gateway_eui"] + if !ok || len(euiString) < 1 { + err = errors.New("ttn/router: Gateway did not provide \"gateway_eui\" in context") + return + } + gatewayEUI, err = types.ParseGatewayEUI(euiString[0]) + if err != nil { + return + } + token, ok := md["token"] + if !ok || len(token) < 1 { + err = errors.New("ttn/router: Gateway did not provide \"token\" in context") + return + } + if token[0] != "token" { + // TODO: Validate Token + err = errors.New("ttn/router: Gateway not authorized") + return + } + + return +} + +// GatewayStatus implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) +func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { + gatewayEUI, err := getGatewayFromMetadata(stream.Context()) + if err != nil { + return err + } + for { + status, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&api.Ack{}) + } + if err != nil { + return err + } + go r.router.HandleGatewayStatus(gatewayEUI, status) + } +} + +// Uplink implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) +func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { + gatewayEUI, err := getGatewayFromMetadata(stream.Context()) + if err != nil { + return err + } + for { + uplink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&api.Ack{}) + } + if err != nil { + return err + } + go r.router.HandleUplink(gatewayEUI, uplink) + } +} + +// Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) +func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { + gatewayEUI, err := getGatewayFromMetadata(stream.Context()) + if err != nil { + return err + } + downlinkChannel, err := r.router.SubscribeDownlink(gatewayEUI) + if err != nil { + return err + } + defer r.router.UnsubscribeDownlink(gatewayEUI) + for downlink := range downlinkChannel { + stream.Send(downlink) + } + return nil +} + +// Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) +func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + gatewayEUI, err := getGatewayFromMetadata(ctx) + if err != nil { + return nil, err + } + return r.router.HandleActivation(gatewayEUI, req) +} + +// RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) +func (r *router) RegisterRPC(s *grpc.Server) { + server := &routerRPC{r} + pb.RegisterRouterServer(s, server) +} diff --git a/core/router/server_test.go b/core/router/server_test.go new file mode 100644 index 000000000..c37245c4f --- /dev/null +++ b/core/router/server_test.go @@ -0,0 +1,198 @@ +package router + +import ( + "fmt" + "math/rand" + "net" + "sync" + "testing" + "time" + + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/TheThingsNetwork/ttn/api" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func randomPort() uint { + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(5000) + 5000 + return uint(port) +} + +func buildTestRouterServer(port uint) (*router, *grpc.Server) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + r := &router{ + gateways: map[types.GatewayEUI]*gateway.Gateway{}, + brokerDiscovery: &mockBrokerDiscovery{}, + } + s := grpc.NewServer() + r.RegisterRPC(s) + go s.Serve(lis) + return r, s +} + +func TestGatewayStatusRPC(t *testing.T) { + a := New(t) + + port := randomPort() + r, s := buildTestRouterServer(port) + defer s.Stop() + + eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewRouterClient(conn) + md := metadata.Pairs( + "token", "token", + "gateway_eui", eui.String(), + ) + ctx := metadata.NewContext(context.Background(), md) + stream, err := client.GatewayStatus(ctx) + if err != nil { + panic(err) + } + statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} + stream.Send(statusMessage) + ack, err := stream.CloseAndRecv() + a.So(err, ShouldBeNil) + a.So(ack, ShouldResemble, &api.Ack{}) + + <-time.After(5 * time.Millisecond) + + status, err := r.getGateway(eui).Status.Get() + a.So(err, ShouldBeNil) + a.So(status, ShouldNotBeNil) + a.So(*status, ShouldResemble, *statusMessage) +} + +func TestUplinkRPC(t *testing.T) { + a := New(t) + + port := randomPort() + r, s := buildTestRouterServer(port) + defer s.Stop() + + eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewRouterClient(conn) + md := metadata.Pairs( + "token", "token", + "gateway_eui", eui.String(), + ) + ctx := metadata.NewContext(context.Background(), md) + stream, err := client.Uplink(ctx) + if err != nil { + panic(err) + } + stream.Send(newReferenceUplink()) + ack, err := stream.CloseAndRecv() + a.So(err, ShouldBeNil) + a.So(ack, ShouldResemble, &api.Ack{}) + + <-time.After(5 * time.Millisecond) + + utilization := r.getGateway(eui).Utilization + utilization.Tick() + rx, _ := utilization.Get() + a.So(rx, ShouldBeGreaterThan, 0) +} + +func TestSubscribeRPC(t *testing.T) { + a := New(t) + + port := randomPort() + r, s := buildTestRouterServer(port) + defer s.Stop() + + eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewRouterClient(conn) + md := metadata.Pairs( + "token", "token", + "gateway_eui", eui.String(), + ) + ctx := metadata.NewContext(context.Background(), md) + + stream, err := client.Subscribe(ctx, &pb.SubscribeRequest{}) + a.So(err, ShouldBeNil) + + downlink := &pb.DownlinkMessage{Payload: []byte{1}} + + var wg sync.WaitGroup + go func() { + dl, err := stream.Recv() + a.So(err, ShouldBeNil) + a.So(*dl, ShouldResemble, *downlink) + wg.Done() + }() + + wg.Add(1) + schedule := r.getGateway(eui).Schedule + id, _ := schedule.GetOption(300, 50) + schedule.Schedule(id, downlink) + + wg.Wait() +} + +func TestActivateRPC(t *testing.T) { + a := New(t) + + port := randomPort() + r, s := buildTestRouterServer(port) + defer s.Stop() + + eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewRouterClient(conn) + md := metadata.Pairs( + "token", "token", + "gateway_eui", eui.String(), + ) + ctx := metadata.NewContext(context.Background(), md) + uplink := newReferenceUplink() + activation := &pb.DeviceActivationRequest{ + Payload: []byte{}, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + } + res, err := client.Activate(ctx, activation) + a.So(res, ShouldBeNil) + a.So(err, ShouldNotBeNil) + + <-time.After(5 * time.Millisecond) + + utilization := r.getGateway(eui).Utilization + utilization.Tick() + rx, _ := utilization.Get() + a.So(rx, ShouldBeGreaterThan, 0) +} diff --git a/core/router/uplink.go b/core/router/uplink.go new file mode 100644 index 000000000..df32ed73b --- /dev/null +++ b/core/router/uplink.go @@ -0,0 +1,38 @@ +package router + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" +) + +func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error { + gateway := r.getGateway(gatewayEUI) + + gateway.Utilization.AddRx(uplink) + + downlinkOptions := buildDownlinkOptions(uplink, false, gateway) + + // Find Broker + devAddr := types.DevAddr{1, 2, 3, 4} + brokers, err := r.brokerDiscovery.Discover(devAddr) + if err != nil { + return err + } + + // Forward to all brokers + for _, broker := range brokers { + broker, err := r.getBroker(broker) + if err != nil { + continue + } + broker.association.Send(&pb_broker.UplinkMessage{ + Payload: uplink.Payload, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + DownlinkOptions: downlinkOptions, + }) + } + + return nil +} diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go new file mode 100644 index 000000000..3154d3b15 --- /dev/null +++ b/core/router/uplink_test.go @@ -0,0 +1,75 @@ +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/discovery" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +// newReferenceGateway returns a default gateway +func newReferenceGateway(region string) *gateway.Gateway { + gtw := gateway.NewGateway(types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) + gtw.Status.Update(&pb_gateway.Status{ + Region: region, + }) + return gtw +} + +// newReferenceUplink returns a default uplink message +func newReferenceUplink() *pb.UplinkMessage { + up := &pb.UplinkMessage{ + Payload: make([]byte, 20), + ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{ + CodingRate: "4/5", + DataRate: "SF7BW125", + Modulation: pb_lorawan.Modulation_LORA, + }}}, + GatewayMetadata: &pb_gateway.RxMetadata{Gateway: &pb_gateway.RxMetadata_Semtech{Semtech: &pb_semtech.RxMetadata{ + Timestamp: 100, + }}, + Frequency: 868100000, + Rssi: -25.0, + Snr: 5.0, + }, + } + return up +} + +type mockBrokerDiscovery struct{} + +func (d *mockBrokerDiscovery) Discover(devAddr types.DevAddr) ([]*discovery.Announcement, error) { + return []*discovery.Announcement{}, nil +} + +func (d *mockBrokerDiscovery) All() ([]*discovery.Announcement, error) { + return []*discovery.Announcement{}, nil +} + +func TestHandleUplink(t *testing.T) { + a := New(t) + + r := &router{ + gateways: map[types.GatewayEUI]*gateway.Gateway{}, + brokerDiscovery: &mockBrokerDiscovery{}, + } + + uplink := newReferenceUplink() + gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + + err := r.HandleUplink(gtwEUI, uplink) + a.So(err, ShouldBeNil) + utilization := r.getGateway(gtwEUI).Utilization + utilization.Tick() + rx, _ := utilization.Get() + a.So(rx, ShouldBeGreaterThan, 0) + + // TODO: Integration test that checks broker forward +} diff --git a/core/types/data_rate.go b/core/types/data_rate.go index 02bdeef68..58bf8e107 100644 --- a/core/types/data_rate.go +++ b/core/types/data_rate.go @@ -5,6 +5,8 @@ import ( "fmt" "regexp" "strconv" + + "github.com/brocaar/lorawan/band" ) type DataRate struct { @@ -29,6 +31,17 @@ func ParseDataRate(input string) (datr *DataRate, err error) { }, nil } +func ConvertDataRate(input band.DataRate) (datr *DataRate, err error) { + if input.Modulation != band.LoRaModulation { + err = errors.New("ttn/core: FSK not yet supported") + } + datr = &DataRate{ + SpreadingFactor: uint(input.SpreadFactor), + Bandwidth: uint(input.Bandwidth), + } + return +} + // Bytes returns the DataRate as a byte slice func (datr DataRate) Bytes() []byte { return []byte(datr.String()) From d7bb40338cc208e01b7bee69b412c9ad53a14fc7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 18 May 2016 12:00:57 +0200 Subject: [PATCH 1446/2266] EU g1 band starts at 863 --- core/router/downlink.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index 84a98e1f3..0cc55a0f1 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -253,8 +253,8 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o if gatewayStatus.Region == "EU_863_870" { var duty float64 switch { - case freq >= 865000000 && freq < 868000000: - duty = 0.01 // g 865.0 – 868.0 MHz 1% + case freq >= 863000000 && freq < 868000000: + duty = 0.01 // g 863.0 – 868.0 MHz 1% case freq >= 868000000 && freq < 868600000: duty = 0.01 // g1 868.0 – 868.6 MHz 1% case freq >= 868700000 && freq < 869200000: From b571d5b66a82872426a523f3d4e8caeae6721dd1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 19 May 2016 15:05:17 +0200 Subject: [PATCH 1447/2266] Use real DevAddr in Router --- core/router/uplink.go | 16 +++++++++++++++- core/router/uplink_test.go | 16 +++++++++++++++- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/core/router/uplink.go b/core/router/uplink.go index df32ed73b..732de6fc1 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -1,9 +1,12 @@ package router import ( + "errors" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" ) func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error { @@ -13,8 +16,19 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess downlinkOptions := buildDownlinkOptions(uplink, false, gateway) + // LoRaWAN: Unmarshal + var phyPayload lorawan.PHYPayload + err := phyPayload.UnmarshalBinary(uplink.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.New("Uplink message does not contain a MAC payload.") + } + devAddr := types.DevAddr(macPayload.FHDR.DevAddr) + // Find Broker - devAddr := types.DevAddr{1, 2, 3, 4} brokers, err := r.brokerDiscovery.Discover(devAddr) if err != nil { return err diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index 3154d3b15..790b9dd06 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -11,6 +11,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -25,8 +26,21 @@ func newReferenceGateway(region string) *gateway.Gateway { // newReferenceUplink returns a default uplink message func newReferenceUplink() *pb.UplinkMessage { + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + }, + }, + } + bytes, _ := phy.MarshalBinary() + up := &pb.UplinkMessage{ - Payload: make([]byte, 20), + Payload: bytes, ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{ CodingRate: "4/5", DataRate: "SF7BW125", From 0b84e82ab03db018f4114123c5afaa8ac47c1a0f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 19 May 2016 18:17:32 +0200 Subject: [PATCH 1448/2266] Add NetworkServer implementation --- api/broker/broker.pb.go | 505 +++++++++++++++-------- api/broker/broker.proto | 14 +- api/handler/handler.pb.go | 290 ++++++++++++- api/handler/handler.proto | 9 +- api/networkserver/networkserver.pb.go | 278 ++++++------- api/networkserver/networkserver.proto | 15 +- api/protocol/lorawan/lorawan.pb.go | 227 +++++++--- api/protocol/lorawan/lorawan.proto | 6 +- core/networkserver/device/device.go | 135 ++++++ core/networkserver/device/device_test.go | 43 ++ core/networkserver/device/store.go | 297 +++++++++++++ core/networkserver/device/store_test.go | 170 ++++++++ core/networkserver/device/utilization.go | 4 + core/networkserver/networkserver.go | 191 +++++++++ core/networkserver/networkserver_test.go | 261 ++++++++++++ core/networkserver/server.go | 87 ++++ core/networkserver/server_test.go | 3 + 17 files changed, 2116 insertions(+), 419 deletions(-) create mode 100644 core/networkserver/device/device.go create mode 100644 core/networkserver/device/device_test.go create mode 100644 core/networkserver/device/store.go create mode 100644 core/networkserver/device/store_test.go create mode 100644 core/networkserver/device/utilization.go create mode 100644 core/networkserver/networkserver.go create mode 100644 core/networkserver/networkserver_test.go create mode 100644 core/networkserver/server.go create mode 100644 core/networkserver/server_test.go diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index df5d8ec85..e6be7e090 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -82,10 +82,13 @@ func (m *DownlinkOption) GetGatewayConfig() *gateway.TxConfiguration { // received from the Router type UplinkMessage struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,11,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,12,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` - DownlinkOptions []*DownlinkOption `protobuf:"bytes,21,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // NOTE: For LoRaWAN, the Router doesn't know the DevEUI and AppEUI + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` } func (m *UplinkMessage) Reset() { *m = UplinkMessage{} } @@ -116,8 +119,10 @@ func (m *UplinkMessage) GetDownlinkOptions() []*DownlinkOption { // received from the Handler, sent to the Router, used as Template type DownlinkMessage struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DownlinkOption *DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DownlinkOption *DownlinkOption `protobuf:"bytes,21,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` } func (m *DownlinkMessage) Reset() { *m = DownlinkMessage{} } @@ -158,7 +163,6 @@ type DeduplicatedUplinkMessage struct { ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` - NeedDownlink bool `protobuf:"varint,32,opt,name=need_downlink,json=needDownlink,proto3" json:"need_downlink,omitempty"` } func (m *DeduplicatedUplinkMessage) Reset() { *m = DeduplicatedUplinkMessage{} } @@ -955,29 +959,53 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if m.ProtocolMetadata != nil { + if m.DevEui != nil { data[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n4, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) + n4, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } i += n4 } - if m.GatewayMetadata != nil { + if m.AppEui != nil { data[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n5, err := m.GatewayMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) + n5, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } i += n5 } + if m.ProtocolMetadata != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) + n6, err := m.ProtocolMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.GatewayMetadata != nil { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) + n7, err := m.GatewayMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { - data[i] = 0xaa + data[i] = 0xfa i++ data[i] = 0x1 i++ @@ -1013,15 +1041,37 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if m.DownlinkOption != nil { + if m.DevEui != nil { data[i] = 0x5a i++ + i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) + n8, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } + if m.AppEui != nil { + data[i] = 0x62 + i++ + i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) + n9, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n9 + } + if m.DownlinkOption != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n6, err := m.DownlinkOption.MarshalTo(data[i:]) + n10, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n10 } return i, nil } @@ -1051,11 +1101,11 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n7, err := m.DownlinkOption.MarshalTo(data[i:]) + n11, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n11 } return i, nil } @@ -1085,21 +1135,21 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n8, err := m.DevEui.MarshalTo(data[i:]) + n12, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n12 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n9, err := m.AppEui.MarshalTo(data[i:]) + n13, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n13 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1107,11 +1157,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n10, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n14, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n14 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1133,23 +1183,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n11, err := m.ResponseTemplate.MarshalTo(data[i:]) + n15, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 - } - if m.NeedDownlink { - data[i] = 0x80 - i++ - data[i] = 0x2 - i++ - if m.NeedDownlink { - data[i] = 1 - } else { - data[i] = 0 - } - i++ + i += n15 } return i, nil } @@ -1179,21 +1217,21 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n12, err := m.DevEui.MarshalTo(data[i:]) + n16, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n16 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n13, err := m.AppEui.MarshalTo(data[i:]) + n17, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n17 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1201,11 +1239,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n14, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n18, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n18 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1213,11 +1251,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n15, err := m.GatewayMetadata.MarshalTo(data[i:]) + n19, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n19 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1225,11 +1263,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n16, err := m.ActivationMetadata.MarshalTo(data[i:]) + n20, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n20 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -1273,21 +1311,21 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n17, err := m.DevEui.MarshalTo(data[i:]) + n21, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n21 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n18, err := m.AppEui.MarshalTo(data[i:]) + n22, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n22 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1295,11 +1333,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n19, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n23, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n23 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1307,11 +1345,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n20, err := m.GatewayMetadata.MarshalTo(data[i:]) + n24, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n20 + i += n24 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1319,11 +1357,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n21, err := m.ActivationMetadata.MarshalTo(data[i:]) + n25, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n21 + i += n25 } if m.ResponseTemplate != nil { data[i] = 0xfa @@ -1331,11 +1369,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n22, err := m.ResponseTemplate.MarshalTo(data[i:]) + n26, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n22 + i += n26 } return i, nil } @@ -1494,31 +1532,31 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n23, err := m.Uplink.MarshalTo(data[i:]) + n27, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n23 + i += n27 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n24, err := m.UplinkUnique.MarshalTo(data[i:]) + n28, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n24 + i += n28 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n25, err := m.Downlink.MarshalTo(data[i:]) + n29, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n25 + i += n29 } if m.Activations != nil { data[i] = 0xaa @@ -1526,11 +1564,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n26, err := m.Activations.MarshalTo(data[i:]) + n30, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n30 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1538,11 +1576,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n27, err := m.ActivationsUnique.MarshalTo(data[i:]) + n31, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n27 + i += n31 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1550,11 +1588,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n28, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n32, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n28 + i += n32 } if m.Deduplication != nil { data[i] = 0xfa @@ -1562,11 +1600,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n29, err := m.Deduplication.MarshalTo(data[i:]) + n33, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n29 + i += n33 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1647,13 +1685,21 @@ func (m *UplinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() - n += 1 + l + sovBroker(uint64(l)) + n += 2 + l + sovBroker(uint64(l)) } if m.GatewayMetadata != nil { l = m.GatewayMetadata.Size() - n += 1 + l + sovBroker(uint64(l)) + n += 2 + l + sovBroker(uint64(l)) } if len(m.DownlinkOptions) > 0 { for _, e := range m.DownlinkOptions { @@ -1671,9 +1717,17 @@ func (m *DownlinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DownlinkOption != nil { l = m.DownlinkOption.Size() - n += 1 + l + sovBroker(uint64(l)) + n += 2 + l + sovBroker(uint64(l)) } return n } @@ -1721,9 +1775,6 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { l = m.ResponseTemplate.Size() n += 2 + l + sovBroker(uint64(l)) } - if m.NeedDownlink { - n += 3 - } return n } @@ -2176,6 +2227,70 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { } iNdEx = postIndex case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) } @@ -2208,7 +2323,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 12: + case 22: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field GatewayMetadata", wireType) } @@ -2241,7 +2356,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 21: + case 31: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOptions", wireType) } @@ -2354,6 +2469,70 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { } iNdEx = postIndex case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) } @@ -2742,26 +2921,6 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 32: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field NeedDownlink", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.NeedDownlink = bool(v != 0) default: iNdEx = preIndex skippy, err := skipBroker(data[iNdEx:]) @@ -4147,75 +4306,73 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1106 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xcd, 0x6f, 0x1b, 0x45, - 0x14, 0xc7, 0x71, 0xeb, 0x38, 0xcf, 0x9f, 0x99, 0xc4, 0xc9, 0xc6, 0x44, 0x49, 0xd8, 0x22, 0x14, - 0x3e, 0x6a, 0x53, 0xa3, 0x50, 0x45, 0x08, 0x2a, 0xa7, 0xa9, 0x42, 0x91, 0x5c, 0xaa, 0x6d, 0x72, - 0xe1, 0x62, 0xad, 0x77, 0x27, 0xf6, 0x28, 0xce, 0xee, 0xb2, 0x33, 0x9b, 0x34, 0x37, 0xfe, 0x08, - 0x0e, 0x1c, 0xf8, 0x67, 0x50, 0x2f, 0x1c, 0x39, 0x73, 0x40, 0x08, 0x2e, 0xdc, 0xf8, 0x17, 0x98, - 0x9d, 0x9d, 0xd9, 0x8f, 0xd8, 0x26, 0x6e, 0x11, 0x52, 0x0f, 0x3d, 0xac, 0x76, 0xe7, 0x7d, 0xfc, - 0xe6, 0xcd, 0x7b, 0xef, 0x37, 0xfb, 0xe0, 0xfe, 0x90, 0xb0, 0x51, 0x30, 0x68, 0x59, 0xee, 0x79, - 0xfb, 0x78, 0x84, 0x8f, 0x47, 0xc4, 0x19, 0xd2, 0x27, 0x98, 0x5d, 0xba, 0xfe, 0x59, 0x9b, 0x31, - 0xa7, 0x6d, 0x7a, 0xa4, 0x3d, 0xf0, 0xdd, 0x33, 0xec, 0xcb, 0x57, 0xcb, 0xf3, 0x5d, 0xe6, 0xa2, - 0x42, 0xb4, 0x6a, 0xde, 0x4d, 0x01, 0x0c, 0xdd, 0xa1, 0xdb, 0x16, 0xea, 0x41, 0x70, 0x2a, 0x56, - 0x62, 0x21, 0xbe, 0x22, 0xb7, 0x8c, 0xf9, 0xcc, 0xfd, 0xf8, 0x23, 0xcd, 0x3f, 0x9b, 0xc7, 0x5c, - 0x98, 0x5a, 0xee, 0x38, 0xfe, 0x90, 0xce, 0xfb, 0xf3, 0x38, 0x0f, 0x4d, 0x86, 0x2f, 0xcd, 0x2b, - 0xf5, 0x8e, 0x5c, 0xf5, 0x17, 0x0b, 0x50, 0x3d, 0x74, 0x2f, 0x9d, 0x31, 0x71, 0xce, 0xbe, 0xf6, - 0x18, 0x71, 0x1d, 0xb4, 0x05, 0x40, 0x6c, 0xec, 0x30, 0x72, 0x4a, 0xb0, 0xaf, 0xe5, 0x76, 0x72, - 0xbb, 0x4b, 0x46, 0x4a, 0x82, 0xbe, 0x81, 0x92, 0xc4, 0xe8, 0xe3, 0x80, 0x68, 0x0b, 0xdc, 0xa0, - 0x7c, 0xb0, 0xff, 0xeb, 0x6f, 0xdb, 0x7b, 0x37, 0x85, 0x61, 0xb9, 0x3e, 0x6e, 0xb3, 0x2b, 0x0f, - 0xd3, 0xd6, 0x51, 0x84, 0xf0, 0xe8, 0xe4, 0xb1, 0x01, 0x12, 0xed, 0x51, 0x40, 0xd0, 0x2a, 0xdc, - 0xa6, 0xa1, 0x95, 0x96, 0xe7, 0xa8, 0x15, 0x23, 0x5a, 0xa0, 0x26, 0x14, 0x6d, 0x6c, 0xda, 0x3c, - 0x46, 0xac, 0xdd, 0xe2, 0x8a, 0xbc, 0x11, 0xaf, 0xd1, 0x01, 0xd4, 0x54, 0x36, 0xfa, 0x96, 0xeb, - 0x9c, 0x92, 0xa1, 0x76, 0x9b, 0x9b, 0x94, 0x3a, 0x1b, 0xad, 0x38, 0x4b, 0xc7, 0xcf, 0x1f, 0x0a, - 0x4d, 0xe0, 0x9b, 0xe1, 0x09, 0x8d, 0xaa, 0xd2, 0x44, 0x62, 0xf4, 0x00, 0xaa, 0xea, 0x44, 0x12, - 0xa2, 0x20, 0x20, 0xb4, 0x96, 0x4a, 0xd6, 0x75, 0x84, 0x8a, 0x54, 0x44, 0x52, 0xfd, 0xef, 0x1c, - 0x54, 0x4e, 0xbc, 0x30, 0x87, 0x3d, 0x4c, 0xa9, 0x39, 0xc4, 0x48, 0x83, 0x45, 0xcf, 0xbc, 0x1a, - 0xbb, 0xa6, 0x2d, 0x32, 0x58, 0x36, 0xd4, 0x12, 0x75, 0x61, 0x39, 0x0e, 0xf8, 0x1c, 0x33, 0xd3, - 0x36, 0x99, 0xa9, 0x95, 0xc4, 0x7e, 0xab, 0x49, 0xc8, 0xc6, 0xf3, 0x9e, 0xd4, 0x19, 0x75, 0x25, - 0x54, 0x12, 0xf4, 0x05, 0xd4, 0x55, 0xbc, 0x31, 0x42, 0x59, 0x20, 0xac, 0xc4, 0x11, 0xa7, 0x00, - 0x6a, 0x52, 0x16, 0xfb, 0x77, 0xa1, 0x6e, 0xcb, 0x9a, 0xf7, 0x5d, 0x51, 0x74, 0xaa, 0x35, 0x76, - 0xf2, 0xdc, 0x7f, 0xad, 0x25, 0x7b, 0x3f, 0xdb, 0x13, 0x46, 0xcd, 0xce, 0xac, 0xa9, 0x3e, 0x86, - 0x9a, 0x32, 0xb9, 0xf9, 0xc8, 0x0f, 0xa0, 0x76, 0x6d, 0x3f, 0x79, 0xe0, 0x59, 0xdb, 0x55, 0xb3, - 0xdb, 0xe9, 0x01, 0x68, 0x87, 0xf8, 0x82, 0x58, 0xb8, 0x6b, 0x31, 0x72, 0x11, 0x95, 0x00, 0x53, - 0x8f, 0x07, 0xf2, 0xbf, 0x6e, 0xfb, 0x22, 0x0f, 0x1b, 0x87, 0xd8, 0x0e, 0x78, 0x65, 0x2d, 0x9e, - 0x42, 0x7b, 0xde, 0x12, 0x3f, 0x81, 0x45, 0x1b, 0x5f, 0x08, 0x76, 0x94, 0x04, 0x3b, 0xf6, 0x38, - 0x3b, 0xee, 0xbd, 0x04, 0x3b, 0xf8, 0x61, 0x43, 0x66, 0x14, 0x38, 0x4a, 0xc8, 0x0a, 0x8e, 0x67, - 0x7a, 0x9e, 0xc0, 0x2b, 0xbf, 0x12, 0x5e, 0xd7, 0xf3, 0x04, 0x1e, 0x47, 0x09, 0xf1, 0xa6, 0xb6, - 0x60, 0xe3, 0x3f, 0xb7, 0xe0, 0x9a, 0x68, 0xa1, 0xf9, 0x5a, 0xf0, 0x10, 0x96, 0x7d, 0x59, 0xc1, - 0x3e, 0xc3, 0xe7, 0xde, 0x98, 0xeb, 0xb5, 0x6d, 0x11, 0xc2, 0xfa, 0xf5, 0xea, 0xc8, 0x84, 0x1b, - 0x75, 0xe5, 0x71, 0x2c, 0x1d, 0xd0, 0x1d, 0xa8, 0x38, 0x18, 0xdb, 0x7d, 0x55, 0x37, 0x6d, 0x87, - 0x23, 0x14, 0x8d, 0x72, 0x28, 0x54, 0xde, 0xfa, 0x5f, 0x79, 0x58, 0x9f, 0xec, 0x9e, 0x6f, 0x03, - 0x4c, 0xd9, 0x9b, 0x1a, 0x4e, 0xd6, 0x70, 0xfe, 0x6b, 0xa4, 0x07, 0x2b, 0x66, 0x9c, 0xd1, 0x04, - 0x62, 0x5d, 0x40, 0x6c, 0x26, 0x41, 0x24, 0x69, 0x8f, 0xb1, 0x90, 0x39, 0x21, 0x9b, 0x7a, 0x2b, - 0x6d, 0xbf, 0xdc, 0xad, 0xf4, 0xdd, 0x2d, 0xb8, 0x93, 0x26, 0xec, 0x9b, 0xb2, 0xbf, 0xfe, 0x65, - 0xef, 0xcd, 0xbe, 0x09, 0x76, 0xe2, 0xba, 0xcf, 0xb8, 0xfc, 0x27, 0xaf, 0x04, 0x1d, 0x41, 0xfd, - 0x59, 0x30, 0xa0, 0x96, 0x4f, 0x06, 0x58, 0x96, 0x5b, 0x6f, 0xc0, 0x0a, 0x4f, 0xa3, 0xe8, 0x89, - 0xb0, 0x4d, 0x94, 0xf8, 0x1e, 0xac, 0x66, 0xc5, 0xf2, 0x8f, 0xb2, 0x01, 0x45, 0x59, 0x33, 0xca, - 0xdb, 0x23, 0xcf, 0xc7, 0x9f, 0xc5, 0x28, 0xfb, 0x54, 0xdf, 0x83, 0xa6, 0x81, 0x87, 0x84, 0x32, - 0xec, 0xa7, 0x5c, 0x55, 0x5b, 0xad, 0x27, 0xc5, 0x8e, 0xc6, 0x26, 0x59, 0x35, 0xfd, 0x3e, 0x6c, - 0x9e, 0x38, 0xfe, 0x2b, 0x38, 0xd6, 0xa0, 0xf2, 0x8c, 0x99, 0x2c, 0x88, 0x63, 0xfe, 0x29, 0x0f, - 0x85, 0x48, 0x82, 0x74, 0x28, 0x04, 0xe2, 0x87, 0x24, 0x7c, 0x4a, 0x1d, 0x68, 0x85, 0xe3, 0xa4, - 0xc1, 0x93, 0x40, 0x0d, 0xa9, 0x41, 0x6d, 0xa8, 0x44, 0x5f, 0xfd, 0xc0, 0x21, 0x1c, 0x41, 0x4c, - 0x6b, 0x59, 0xd3, 0x72, 0x64, 0x70, 0x22, 0xf4, 0xe8, 0x3d, 0x3e, 0x6a, 0xa9, 0xcb, 0xb4, 0x34, - 0x61, 0x1b, 0xeb, 0xd0, 0x47, 0x50, 0x4a, 0x6a, 0x49, 0x65, 0x07, 0xa6, 0x4d, 0xd3, 0x6a, 0xb4, - 0x0f, 0xa9, 0xca, 0x53, 0x15, 0xcb, 0xda, 0x84, 0xd3, 0x72, 0xca, 0x4a, 0x06, 0xf4, 0x39, 0xac, - 0xa6, 0x5d, 0x4d, 0xcb, 0xc2, 0x1e, 0x67, 0xb6, 0x6c, 0xb7, 0xb4, 0x73, 0xaa, 0x2b, 0x69, 0x57, - 0x9a, 0xa1, 0x4f, 0xa1, 0x62, 0xc7, 0x17, 0x42, 0x38, 0x01, 0x44, 0x9d, 0x55, 0x17, 0x7e, 0x4f, - 0xb1, 0x6f, 0x85, 0x63, 0xed, 0x98, 0x7b, 0x67, 0xcd, 0xd0, 0x87, 0xb0, 0xcc, 0x47, 0x41, 0x07, - 0x5b, 0x1c, 0xa4, 0xef, 0xbb, 0x01, 0xaf, 0x1b, 0xd5, 0xde, 0x17, 0x43, 0x69, 0x3d, 0x56, 0x18, - 0x91, 0x1c, 0xdd, 0x05, 0x94, 0x18, 0x8f, 0x4c, 0xc7, 0x1e, 0x87, 0xd6, 0x1f, 0x08, 0xeb, 0x04, - 0xe6, 0x4b, 0xa9, 0xe8, 0x7c, 0xbf, 0x00, 0x85, 0x03, 0xd1, 0xd8, 0x7c, 0x44, 0x59, 0xea, 0x52, - 0xea, 0x5a, 0x24, 0xfc, 0x9b, 0x35, 0x54, 0xbb, 0x67, 0xe6, 0x8c, 0xe6, 0xac, 0xff, 0xe1, 0x6e, - 0xee, 0xe3, 0x1c, 0xfa, 0x0a, 0x96, 0xe2, 0x76, 0x47, 0x9a, 0xb2, 0xbc, 0xce, 0x80, 0xe6, 0x3b, - 0x09, 0x93, 0x66, 0x8c, 0x33, 0x1c, 0xab, 0x05, 0x8b, 0x4f, 0x83, 0xc1, 0x98, 0xd0, 0x11, 0x9a, - 0xb5, 0x67, 0xb3, 0x28, 0x12, 0xd7, 0xb5, 0xce, 0x76, 0x73, 0x9c, 0xb9, 0x45, 0x49, 0x49, 0x8c, - 0xb6, 0x67, 0x53, 0x35, 0x8a, 0xe0, 0x46, 0x2e, 0x77, 0x7e, 0x5c, 0x80, 0x4a, 0x94, 0x96, 0x9e, - 0xe9, 0xf0, 0xbd, 0x7c, 0xf4, 0x18, 0xca, 0x69, 0x82, 0xa2, 0xb7, 0x15, 0xc6, 0x14, 0x36, 0x37, - 0x37, 0xa7, 0x2b, 0x25, 0xa7, 0x1f, 0xc2, 0xca, 0x14, 0xe2, 0x22, 0x5d, 0x39, 0xcd, 0x66, 0x75, - 0x72, 0x64, 0x74, 0x04, 0x8d, 0xa9, 0x34, 0x46, 0xef, 0xc6, 0x95, 0xfb, 0x17, 0x96, 0xa7, 0x80, - 0x3a, 0xb0, 0x74, 0x84, 0x99, 0xe4, 0x71, 0x5c, 0xf6, 0x0c, 0xd3, 0x9b, 0xd5, 0xac, 0xf8, 0xa0, - 0xfe, 0xf3, 0x1f, 0x5b, 0xb9, 0x5f, 0xf8, 0xf3, 0x3b, 0x7f, 0x7e, 0xf8, 0x73, 0xeb, 0xad, 0x41, - 0x41, 0x5c, 0xb5, 0x9f, 0xfc, 0x13, 0x00, 0x00, 0xff, 0xff, 0xd0, 0xf0, 0x03, 0x38, 0xdb, 0x0e, - 0x00, 0x00, + // 1085 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0xe3, 0x44, + 0x18, 0x26, 0xc9, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xf4, 0xcb, 0x0d, 0x55, 0x5b, 0x0c, 0x42, 0xe5, + 0x63, 0x13, 0x36, 0xa8, 0xac, 0x2a, 0x04, 0xab, 0x74, 0xbb, 0x2a, 0x8b, 0x94, 0x65, 0xe5, 0x6d, + 0x2f, 0x5c, 0x22, 0xc7, 0x9e, 0x26, 0xa3, 0xa6, 0xb6, 0xf1, 0x8c, 0xdb, 0xed, 0x8d, 0x1f, 0x80, + 0xc4, 0x85, 0x03, 0x07, 0xc4, 0x7f, 0x41, 0x5c, 0x38, 0x72, 0xe6, 0x80, 0x10, 0x5c, 0xf8, 0x19, + 0x8c, 0xc7, 0x33, 0xfe, 0x68, 0xe3, 0x6d, 0x77, 0xe1, 0xc0, 0x4a, 0x3d, 0x44, 0xf1, 0xbc, 0x1f, + 0x8f, 0xc7, 0xcf, 0xfb, 0x3e, 0x33, 0x2f, 0xdc, 0x1b, 0x11, 0x36, 0x0e, 0x86, 0x6d, 0xcb, 0x3d, + 0xe9, 0x1c, 0x8c, 0xf1, 0xc1, 0x98, 0x38, 0x23, 0xfa, 0x18, 0xb3, 0x33, 0xd7, 0x3f, 0xee, 0x30, + 0xe6, 0x74, 0x4c, 0x8f, 0x74, 0x86, 0xbe, 0x7b, 0x8c, 0x7d, 0xf9, 0xd7, 0xf6, 0x7c, 0x97, 0xb9, + 0xa8, 0x1c, 0xad, 0x5a, 0x77, 0x52, 0x00, 0x23, 0x77, 0xe4, 0x76, 0x84, 0x7b, 0x18, 0x1c, 0x89, + 0x95, 0x58, 0x88, 0xa7, 0x28, 0x2d, 0x13, 0x9e, 0xfb, 0x3e, 0xfe, 0x93, 0xe1, 0x1f, 0x5f, 0x27, + 0x5c, 0x84, 0x5a, 0xee, 0x24, 0x7e, 0x90, 0xc9, 0x3b, 0xd7, 0x49, 0x1e, 0x99, 0x0c, 0x9f, 0x99, + 0xe7, 0xea, 0x3f, 0x4a, 0xd5, 0x7f, 0x2e, 0x42, 0x7d, 0xcf, 0x3d, 0x73, 0x26, 0xc4, 0x39, 0xfe, + 0xc2, 0x63, 0xc4, 0x75, 0xd0, 0x3a, 0x00, 0xb1, 0xb1, 0xc3, 0xc8, 0x11, 0xc1, 0xbe, 0x56, 0xd8, + 0x2c, 0x6c, 0xcd, 0x19, 0x29, 0x0b, 0xfa, 0x12, 0x2a, 0x12, 0x63, 0x80, 0x03, 0xa2, 0x15, 0x79, + 0x40, 0x75, 0x77, 0xe7, 0xb7, 0xdf, 0x37, 0xb6, 0xaf, 0xda, 0x86, 0xe5, 0xfa, 0xb8, 0xc3, 0xce, + 0x3d, 0x4c, 0xdb, 0xfb, 0x11, 0xc2, 0xc3, 0xc3, 0x47, 0x06, 0x48, 0xb4, 0x87, 0x01, 0x41, 0x8b, + 0x70, 0x9b, 0x86, 0x51, 0x5a, 0x89, 0xa3, 0xd6, 0x8c, 0x68, 0x81, 0x5a, 0x30, 0x6b, 0x63, 0xd3, + 0xe6, 0x7b, 0xc4, 0xda, 0x2d, 0xee, 0x28, 0x19, 0xf1, 0x1a, 0xed, 0x42, 0x43, 0xb1, 0x31, 0xb0, + 0x5c, 0xe7, 0x88, 0x8c, 0xb4, 0xdb, 0x3c, 0xa4, 0xd2, 0x5d, 0x6d, 0xc7, 0x2c, 0x1d, 0x3c, 0x7b, + 0x20, 0x3c, 0x81, 0x6f, 0x86, 0x5f, 0x68, 0xd4, 0x95, 0x27, 0x32, 0xa3, 0xfb, 0x50, 0x57, 0x5f, + 0x24, 0x21, 0xca, 0x02, 0x42, 0x6b, 0x2b, 0xb2, 0x2e, 0x22, 0xd4, 0xa4, 0x23, 0xb2, 0xea, 0xdf, + 0x96, 0xa0, 0x76, 0xe8, 0x85, 0x1c, 0xf6, 0x31, 0xa5, 0xe6, 0x08, 0x23, 0x0d, 0x66, 0x3c, 0xf3, + 0x7c, 0xe2, 0x9a, 0xb6, 0x60, 0xb0, 0x6a, 0xa8, 0x25, 0x7a, 0x0c, 0x33, 0x36, 0x3e, 0x15, 0xd4, + 0x55, 0x04, 0x75, 0xdb, 0x9c, 0xba, 0xbb, 0x2f, 0x40, 0xdd, 0x1e, 0x3e, 0x0d, 0x69, 0x2b, 0x73, + 0x94, 0x90, 0x32, 0x8e, 0x67, 0x7a, 0x9e, 0xc0, 0xab, 0xbe, 0x14, 0x5e, 0xcf, 0xf3, 0x04, 0x1e, + 0x47, 0x09, 0xf1, 0x7a, 0x30, 0x1f, 0x13, 0x7a, 0x82, 0x99, 0x69, 0x9b, 0xcc, 0xd4, 0x96, 0x04, + 0x1f, 0x8b, 0x09, 0xa5, 0xc6, 0xb3, 0xbe, 0xf4, 0x19, 0x4d, 0x65, 0x54, 0x16, 0xf4, 0x29, 0x34, + 0x15, 0x9f, 0x31, 0xc2, 0xb2, 0x40, 0x58, 0x88, 0x19, 0x4d, 0x01, 0x34, 0xa4, 0x2d, 0xce, 0xef, + 0x41, 0xd3, 0x96, 0x3d, 0x39, 0x70, 0x45, 0x53, 0x52, 0x6d, 0x63, 0xb3, 0xc4, 0xf3, 0x97, 0xdb, + 0x52, 0x9b, 0xd9, 0x9e, 0x35, 0x1a, 0x76, 0x66, 0x4d, 0xf5, 0x6f, 0x8a, 0xd0, 0x50, 0x31, 0xaf, + 0x7e, 0x4d, 0xee, 0x43, 0xe3, 0x02, 0x21, 0xb2, 0x22, 0x79, 0x7c, 0xd4, 0xb3, 0x7c, 0xe8, 0x01, + 0x68, 0x7c, 0x8b, 0xc4, 0xc2, 0x3d, 0x8b, 0x91, 0xd3, 0xa8, 0x87, 0x31, 0xf5, 0x38, 0x53, 0xcf, + 0xa3, 0x65, 0xca, 0x6b, 0x2b, 0x2f, 0xf4, 0xda, 0x1f, 0x4b, 0xb0, 0xba, 0x87, 0xed, 0x80, 0x4b, + 0xc3, 0xe2, 0x35, 0xb6, 0x6f, 0x34, 0x72, 0x85, 0x46, 0x4a, 0xd7, 0xd6, 0xc8, 0x1e, 0xcc, 0xfb, + 0xb2, 0x82, 0x03, 0x86, 0x4f, 0xbc, 0x09, 0xf7, 0x73, 0x91, 0x84, 0x5b, 0x58, 0xb9, 0x58, 0x1d, + 0x49, 0xb8, 0xd1, 0x54, 0x19, 0x07, 0x32, 0x41, 0xff, 0xbb, 0x04, 0x2b, 0x97, 0x1b, 0xe3, 0xab, + 0x00, 0x53, 0x76, 0x53, 0x9e, 0x7f, 0x73, 0x84, 0xf5, 0x61, 0xc1, 0x8c, 0x19, 0x4d, 0x20, 0x56, + 0x04, 0xc4, 0x5a, 0xb2, 0x89, 0x84, 0xf6, 0x18, 0x0b, 0x99, 0x97, 0x6c, 0xff, 0xc5, 0x89, 0xf8, + 0xf5, 0x2d, 0x78, 0x33, 0xad, 0xc5, 0x9b, 0xb2, 0xff, 0xff, 0xcb, 0xde, 0xcf, 0x17, 0xf9, 0x66, + 0x5c, 0xf7, 0x9c, 0x73, 0x7d, 0x8a, 0xda, 0x11, 0x34, 0x9f, 0x06, 0x43, 0x6a, 0xf9, 0x64, 0x88, + 0x65, 0xb9, 0xf5, 0x25, 0x58, 0xe0, 0x34, 0x8a, 0x9e, 0x08, 0xdb, 0x44, 0x99, 0xef, 0xc2, 0x62, + 0xd6, 0x2c, 0x2f, 0x8b, 0x55, 0x98, 0x95, 0x35, 0xa3, 0xbc, 0x3d, 0x4a, 0x7c, 0x34, 0x9c, 0x89, + 0xd8, 0xa7, 0xfa, 0x36, 0xb4, 0x0c, 0x3c, 0x22, 0x94, 0x61, 0x3f, 0x95, 0xaa, 0xda, 0x6a, 0x25, + 0x29, 0x76, 0x34, 0x52, 0xca, 0xaa, 0xe9, 0xf7, 0x60, 0xed, 0xd0, 0xf1, 0x5f, 0x22, 0xb1, 0x01, + 0xb5, 0xa7, 0xcc, 0x64, 0x41, 0xbc, 0xe7, 0x9f, 0x4a, 0x50, 0x8e, 0x2c, 0x48, 0x87, 0x72, 0x20, + 0xee, 0x1a, 0x91, 0x53, 0xe9, 0x42, 0x3b, 0x1c, 0xb5, 0x0d, 0x4e, 0x02, 0x35, 0xa4, 0x07, 0x75, + 0xa0, 0x16, 0x3d, 0x0d, 0x02, 0x87, 0x70, 0x04, 0x31, 0xc9, 0x66, 0x43, 0xab, 0x51, 0xc0, 0xa1, + 0xf0, 0xa3, 0xb7, 0xf9, 0x18, 0x2a, 0x45, 0x25, 0xef, 0xc1, 0x74, 0x6c, 0xec, 0x43, 0xef, 0x43, + 0x25, 0xa9, 0x25, 0x95, 0x1d, 0x98, 0x0e, 0x4d, 0xbb, 0xd1, 0x0e, 0xa4, 0x2a, 0x4f, 0xd5, 0x5e, + 0x96, 0x2f, 0x25, 0xcd, 0xa7, 0xa2, 0xe4, 0x86, 0x3e, 0x81, 0xc5, 0x74, 0xaa, 0x69, 0x59, 0xd8, + 0xe3, 0xca, 0x96, 0xed, 0x96, 0x4e, 0x4e, 0x75, 0x25, 0xed, 0xc9, 0x30, 0xf4, 0x11, 0xd4, 0xec, + 0xf8, 0x40, 0x08, 0x2f, 0xf7, 0xa8, 0xb3, 0x9a, 0x22, 0xef, 0x09, 0xf6, 0xad, 0x70, 0xe4, 0x9f, + 0xf0, 0xec, 0x6c, 0x18, 0x7a, 0x0f, 0xe6, 0xf9, 0x98, 0xec, 0x60, 0x8b, 0x83, 0x0c, 0x7c, 0x37, + 0xe0, 0x75, 0xa3, 0xda, 0x3b, 0x62, 0x60, 0x6f, 0xc6, 0x0e, 0x23, 0xb2, 0xa3, 0x3b, 0x80, 0x92, + 0xe0, 0xb1, 0xe9, 0xd8, 0x93, 0x30, 0xfa, 0x5d, 0x11, 0x9d, 0xc0, 0x7c, 0x26, 0x1d, 0xdd, 0xef, + 0x8a, 0x50, 0xde, 0x15, 0x8d, 0xcd, 0xa7, 0x8f, 0xb9, 0x1e, 0xa5, 0xae, 0x45, 0xf8, 0x17, 0xa0, + 0x25, 0xd5, 0xee, 0x99, 0x11, 0xa2, 0x95, 0x77, 0xd5, 0x6d, 0x15, 0x3e, 0x28, 0xa0, 0xcf, 0x61, + 0x2e, 0x6e, 0x77, 0xa4, 0xa9, 0xc8, 0x8b, 0x0a, 0x68, 0xbd, 0x91, 0x28, 0x29, 0x67, 0x52, 0xe1, + 0x58, 0x6d, 0x98, 0x79, 0x12, 0x0c, 0x27, 0x84, 0x8e, 0x51, 0xde, 0x3b, 0x5b, 0xb3, 0x82, 0xb8, + 0x9e, 0x75, 0xbc, 0x55, 0xe0, 0xca, 0x9d, 0x95, 0x92, 0xc4, 0x68, 0x23, 0x5f, 0xaa, 0xd1, 0x0e, + 0xae, 0xd4, 0x72, 0xf7, 0x87, 0x22, 0xd4, 0x22, 0x5a, 0xfa, 0xa6, 0xc3, 0xdf, 0xe5, 0xa3, 0x47, + 0x50, 0x4d, 0x0b, 0x14, 0xbd, 0xae, 0x30, 0xa6, 0xa8, 0xb9, 0xb5, 0x36, 0xdd, 0x29, 0x35, 0xfd, + 0x00, 0x16, 0xa6, 0x08, 0x17, 0xe9, 0x2a, 0x29, 0x5f, 0xd5, 0xc9, 0x27, 0xa3, 0x7d, 0x58, 0x9a, + 0x2a, 0x63, 0xf4, 0x56, 0x5c, 0xb9, 0xe7, 0xa8, 0x3c, 0x05, 0xd4, 0x85, 0xb9, 0x7d, 0xcc, 0xa4, + 0x8e, 0xe3, 0xb2, 0x67, 0x94, 0xde, 0xaa, 0x67, 0xcd, 0xbb, 0xcd, 0x5f, 0xfe, 0x5c, 0x2f, 0xfc, + 0xca, 0x7f, 0x7f, 0xf0, 0xdf, 0xf7, 0x7f, 0xad, 0xbf, 0x36, 0x2c, 0x8b, 0xa3, 0xf6, 0xc3, 0x7f, + 0x02, 0x00, 0x00, 0xff, 0xff, 0x46, 0x34, 0x62, 0x46, 0xf7, 0x0f, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index ebda44adf..d3d8f23ee 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -20,15 +20,20 @@ message DownlinkOption { // received from the Router message UplinkMessage { bytes payload = 1; - protocol.RxMetadata protocol_metadata = 11; - gateway.RxMetadata gateway_metadata = 12; - repeated DownlinkOption downlink_options = 21; + // NOTE: For LoRaWAN, the Router doesn't know the DevEUI and AppEUI + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + repeated DownlinkOption downlink_options = 31; } // received from the Handler, sent to the Router, used as Template message DownlinkMessage { bytes payload = 1; - DownlinkOption downlink_option = 11; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + DownlinkOption downlink_option = 21; } //sent to the Router, used as Template @@ -45,7 +50,6 @@ message DeduplicatedUplinkMessage { protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; DownlinkMessage response_template = 31; - bool need_downlink = 32; } // received from the Router diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index abdce47fe..d45a818bc 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -9,6 +9,7 @@ github.com/TheThingsNetwork/ttn/api/handler/handler.proto It has these top-level messages: + DeviceActivationResponse StatusRequest Status */ @@ -19,6 +20,7 @@ import fmt "fmt" import math "math" import _ "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" +import protocol "github.com/TheThingsNetwork/ttn/api/protocol" import ( context "golang.org/x/net/context" @@ -36,6 +38,31 @@ var _ = math.Inf // is compatible with the proto package it is being compiled against. const _ = proto.ProtoPackageIsVersion1 +type DeviceActivationResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DownlinkOption *broker.DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` +} + +func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } +func (m *DeviceActivationResponse) String() string { return proto.CompactTextString(m) } +func (*DeviceActivationResponse) ProtoMessage() {} +func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } + +func (m *DeviceActivationResponse) GetDownlinkOption() *broker.DownlinkOption { + if m != nil { + return m.DownlinkOption + } + return nil +} + +func (m *DeviceActivationResponse) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + // message StatusRequest is used to request the status of this Handler type StatusRequest struct { } @@ -43,7 +70,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } // message Status is the response to the StatusRequest type Status struct { @@ -52,9 +79,10 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{2} } func init() { + proto.RegisterType((*DeviceActivationResponse)(nil), "handler.DeviceActivationResponse") proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") proto.RegisterType((*Status)(nil), "handler.Status") } @@ -70,7 +98,7 @@ const _ = grpc.SupportPackageIsVersion2 // Client API for Handler service type HandlerClient interface { - Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) + Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) } type handlerClient struct { @@ -81,8 +109,8 @@ func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { return &handlerClient{cc} } -func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) { - out := new(broker.DeviceActivationResponse) +func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { + out := new(DeviceActivationResponse) err := grpc.Invoke(ctx, "/handler.Handler/Activate", in, out, c.cc, opts...) if err != nil { return nil, err @@ -93,7 +121,7 @@ func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDev // Server API for Handler service type HandlerServer interface { - Activate(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*broker.DeviceActivationResponse, error) + Activate(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*DeviceActivationResponse, error) } func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { @@ -193,6 +221,52 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, } +func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.DownlinkOption != nil { + data[i] = 0x5a + i++ + i = encodeVarintHandler(data, i, uint64(m.DownlinkOption.Size())) + n1, err := m.DownlinkOption.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.ActivationMetadata != nil { + data[i] = 0xba + i++ + data[i] = 0x1 + i++ + i = encodeVarintHandler(data, i, uint64(m.ActivationMetadata.Size())) + n2, err := m.ActivationMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + return i, nil +} + func (m *StatusRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -256,6 +330,24 @@ func encodeVarintHandler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } +func (m *DeviceActivationResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.DownlinkOption != nil { + l = m.DownlinkOption.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovHandler(uint64(l)) + } + return n +} + func (m *StatusRequest) Size() (n int) { var l int _ = l @@ -281,6 +373,153 @@ func sovHandler(x uint64) (n int) { func sozHandler(x uint64) (n int) { return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *DeviceActivationResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceActivationResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceActivationResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &broker.DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *StatusRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -487,20 +726,27 @@ var ( ) var fileDescriptorHandler = []byte{ - // 240 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4c, 0xcf, 0x2c, 0xc9, - 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, - 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, - 0xcf, 0x48, 0xcc, 0x4b, 0xc9, 0x49, 0x2d, 0x82, 0xd1, 0x7a, 0x05, 0x45, 0xf9, 0x25, 0xf9, 0x42, - 0xec, 0x50, 0xae, 0x94, 0x2e, 0x31, 0x66, 0x00, 0x31, 0x44, 0x9f, 0x94, 0x39, 0x31, 0xca, 0x93, - 0x8a, 0xf2, 0xb3, 0x81, 0x36, 0x42, 0x28, 0x88, 0x46, 0x25, 0x7e, 0x2e, 0xde, 0xe0, 0x92, 0xc4, - 0x92, 0xd2, 0xe2, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x25, 0x0e, 0x2e, 0x36, 0x88, 0x80, - 0x51, 0x0a, 0x17, 0xbb, 0x07, 0xc4, 0x35, 0x42, 0x91, 0x5c, 0x1c, 0x8e, 0xc9, 0x25, 0x99, 0x65, - 0x89, 0x25, 0xa9, 0x42, 0xda, 0x7a, 0x50, 0x03, 0x5c, 0x52, 0x53, 0x4a, 0x0b, 0x72, 0x32, 0x93, - 0x81, 0x82, 0x29, 0x2e, 0xa9, 0x65, 0x99, 0xc9, 0xa9, 0x50, 0x35, 0x99, 0xf9, 0x79, 0x50, 0xd3, - 0xa4, 0x14, 0x10, 0x8a, 0xd1, 0x15, 0x14, 0x17, 0xe4, 0xe7, 0x15, 0xa7, 0x1a, 0xb9, 0x71, 0xf1, - 0x41, 0x6d, 0xf1, 0x4d, 0xcc, 0x4b, 0x4c, 0x07, 0x5a, 0x66, 0xc2, 0xc5, 0xe9, 0x9e, 0x5a, 0x02, - 0x71, 0x84, 0x90, 0x98, 0x1e, 0x2c, 0x80, 0x50, 0x9c, 0x29, 0xc5, 0x8f, 0x26, 0xee, 0x24, 0x70, - 0xe2, 0x91, 0x1c, 0xe3, 0x05, 0x20, 0x7e, 0x00, 0xc4, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, - 0x7d, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x70, 0x1c, 0x54, 0x57, 0x8f, 0x01, 0x00, 0x00, + // 349 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0xcf, 0x4a, 0xf3, 0x40, + 0x10, 0xff, 0x72, 0x69, 0xfb, 0x6d, 0xbf, 0xaf, 0x95, 0x15, 0x6a, 0x08, 0x52, 0xb4, 0x27, 0x41, + 0x4c, 0xa0, 0x0a, 0x22, 0x1e, 0x44, 0x29, 0xea, 0xa5, 0x0a, 0xb1, 0x27, 0x2f, 0x65, 0x9b, 0x1d, + 0x9a, 0xa5, 0xe9, 0x6e, 0x4c, 0x26, 0x2d, 0xbe, 0x89, 0x0f, 0xe4, 0xc1, 0xa3, 0x8f, 0x20, 0xfa, + 0x22, 0xae, 0xc9, 0x36, 0x52, 0x8b, 0xd0, 0xc3, 0xb0, 0x3b, 0xb3, 0xbf, 0x7f, 0xcc, 0x92, 0x93, + 0xb1, 0xc0, 0x30, 0x1b, 0xb9, 0x81, 0x9a, 0x7a, 0x83, 0x10, 0x06, 0xa1, 0x90, 0xe3, 0xf4, 0x06, + 0x70, 0xae, 0x92, 0x89, 0x87, 0x28, 0x3d, 0x16, 0x0b, 0x2f, 0x64, 0x92, 0x47, 0x90, 0x2c, 0x4e, + 0x37, 0x4e, 0x14, 0x2a, 0x5a, 0x35, 0xad, 0x73, 0xb0, 0x8e, 0x86, 0xae, 0x82, 0xe7, 0x1c, 0xaf, + 0x03, 0x1f, 0x25, 0x6a, 0xa2, 0x1d, 0x8b, 0xc3, 0x10, 0x4f, 0xd7, 0x21, 0xe6, 0xd0, 0x40, 0x45, + 0xe5, 0xa5, 0x20, 0x77, 0x9e, 0x2d, 0x62, 0xf7, 0x60, 0x26, 0x02, 0x38, 0x0f, 0x50, 0xcc, 0x18, + 0x0a, 0x25, 0x7d, 0x48, 0x63, 0x25, 0x53, 0xa0, 0x36, 0xa9, 0xc6, 0xec, 0x31, 0x52, 0x8c, 0xdb, + 0xd6, 0x8e, 0xb5, 0xf7, 0xcf, 0x5f, 0xb4, 0xf4, 0x8c, 0x34, 0xb9, 0x9a, 0xcb, 0x48, 0xc8, 0xc9, + 0x50, 0xc5, 0x5f, 0x24, 0xbb, 0xae, 0x11, 0xf5, 0x6e, 0xcb, 0x35, 0xd9, 0x7a, 0xe6, 0xf9, 0x36, + 0x7f, 0xf5, 0x1b, 0x7c, 0xa9, 0xa7, 0x7d, 0xb2, 0xc9, 0x4a, 0xc3, 0xe1, 0x14, 0x90, 0x71, 0x86, + 0xcc, 0xde, 0xca, 0x45, 0xb6, 0xdd, 0x32, 0xe5, 0x77, 0xaa, 0xbe, 0xc1, 0xf8, 0x94, 0xad, 0xcc, + 0x3a, 0x4d, 0xf2, 0xff, 0x0e, 0x19, 0x66, 0xa9, 0x0f, 0x0f, 0x19, 0xa4, 0xd8, 0xa9, 0x91, 0x4a, + 0x31, 0xe8, 0x02, 0xa9, 0x5e, 0x17, 0x3f, 0x42, 0xef, 0x49, 0xcd, 0xe8, 0x01, 0xdd, 0x2f, 0x83, + 0x02, 0xcf, 0xe2, 0x48, 0x04, 0x7a, 0xc8, 0x57, 0x37, 0x91, 0xab, 0x39, 0xbb, 0xee, 0xe2, 0x8f, + 0x7f, 0xdb, 0x55, 0xf7, 0x92, 0x34, 0x8c, 0x4d, 0x9f, 0x49, 0x36, 0xd6, 0x6e, 0x47, 0xe4, 0xef, + 0x15, 0x60, 0x91, 0x82, 0xb6, 0x4a, 0x85, 0xa5, 0x9c, 0x4e, 0xf3, 0xc7, 0xfc, 0x62, 0xe3, 0xe5, + 0xbd, 0x6d, 0xbd, 0xea, 0x7a, 0xd3, 0xf5, 0xf4, 0xd1, 0xfe, 0x33, 0xaa, 0xe4, 0xcb, 0x38, 0xfc, + 0x0c, 0x00, 0x00, 0xff, 0xff, 0xf5, 0xc0, 0x30, 0xa7, 0x94, 0x02, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 99574c7e3..5b9facabd 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -2,12 +2,19 @@ syntax = "proto3"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; +import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; package handler; +message DeviceActivationResponse { + bytes payload = 1; + broker.DownlinkOption downlink_option = 11; + protocol.ActivationMetadata activation_metadata = 23; +} + // The Handler service provides pure network functionality service Handler { - rpc Activate(broker.DeduplicatedDeviceActivationRequest) returns (broker.DeviceActivationResponse); + rpc Activate(broker.DeduplicatedDeviceActivationRequest) returns (DeviceActivationResponse); } // message StatusRequest is used to request the status of this Handler diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 57e5793a7..2fb97b4a2 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -11,7 +11,6 @@ It has these top-level messages: DevicesRequest DevicesResponse - UplinkResponse RegisterDeviceRequest StatusRequest Status @@ -24,6 +23,7 @@ import math "math" import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" +import handler "github.com/TheThingsNetwork/ttn/api/handler" import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" @@ -70,9 +70,11 @@ func (m *DevicesResponse) GetResults() []*DevicesResponse_Device { } type DevicesResponse_Device struct { - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + DisableFCntCheck bool `protobuf:"varint,4,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + Uses32BitFCnt bool `protobuf:"varint,5,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` } func (m *DevicesResponse_Device) Reset() { *m = DevicesResponse_Device{} } @@ -82,15 +84,6 @@ func (*DevicesResponse_Device) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1, 0} } -type UplinkResponse struct { - NeedDownlink bool `protobuf:"varint,1,opt,name=need_downlink,json=needDownlink,proto3" json:"need_downlink,omitempty"` -} - -func (m *UplinkResponse) Reset() { *m = UplinkResponse{} } -func (m *UplinkResponse) String() string { return proto.CompactTextString(m) } -func (*UplinkResponse) ProtoMessage() {} -func (*UplinkResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{2} } - // message RegisterDeviceRequest is used to register a device in the // NetworkServer type RegisterDeviceRequest struct { @@ -100,7 +93,7 @@ func (m *RegisterDeviceRequest) Reset() { *m = RegisterDeviceRequest{} } func (m *RegisterDeviceRequest) String() string { return proto.CompactTextString(m) } func (*RegisterDeviceRequest) ProtoMessage() {} func (*RegisterDeviceRequest) Descriptor() ([]byte, []int) { - return fileDescriptorNetworkserver, []int{3} + return fileDescriptorNetworkserver, []int{2} } // message StatusRequest is used to request the status of this NetworkServer @@ -110,7 +103,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{3} } // message Status is the response to the StatusRequest type Status struct { @@ -121,7 +114,7 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{5} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } func (m *Status) GetDevicesPerAddress() *api.Percentiles { if m != nil { @@ -134,7 +127,6 @@ func init() { proto.RegisterType((*DevicesRequest)(nil), "networkserver.DevicesRequest") proto.RegisterType((*DevicesResponse)(nil), "networkserver.DevicesResponse") proto.RegisterType((*DevicesResponse_Device)(nil), "networkserver.DevicesResponse.Device") - proto.RegisterType((*UplinkResponse)(nil), "networkserver.UplinkResponse") proto.RegisterType((*RegisterDeviceRequest)(nil), "networkserver.RegisterDeviceRequest") proto.RegisterType((*StatusRequest)(nil), "networkserver.StatusRequest") proto.RegisterType((*Status)(nil), "networkserver.Status") @@ -154,9 +146,11 @@ type NetworkServerClient interface { // Broker requests devices with DevAddr for MIC check GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) // Broker requests device activation "template" from Network Server - Activate(ctx context.Context, in *broker.DeviceActivationResponse, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) + PrepareActivation(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeduplicatedDeviceActivationRequest, error) + // Broker confirms device activation + Activate(ctx context.Context, in *handler.DeviceActivationResponse, opts ...grpc.CallOption) (*handler.DeviceActivationResponse, error) // Broker informs Network Server about Uplink - Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*UplinkResponse, error) + Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*broker.DeduplicatedUplinkMessage, error) // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) } @@ -178,8 +172,17 @@ func (c *networkServerClient) GetDevices(ctx context.Context, in *DevicesRequest return out, nil } -func (c *networkServerClient) Activate(ctx context.Context, in *broker.DeviceActivationResponse, opts ...grpc.CallOption) (*broker.DeviceActivationResponse, error) { - out := new(broker.DeviceActivationResponse) +func (c *networkServerClient) PrepareActivation(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeduplicatedDeviceActivationRequest, error) { + out := new(broker.DeduplicatedDeviceActivationRequest) + err := grpc.Invoke(ctx, "/networkserver.NetworkServer/PrepareActivation", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *networkServerClient) Activate(ctx context.Context, in *handler.DeviceActivationResponse, opts ...grpc.CallOption) (*handler.DeviceActivationResponse, error) { + out := new(handler.DeviceActivationResponse) err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Activate", in, out, c.cc, opts...) if err != nil { return nil, err @@ -187,8 +190,8 @@ func (c *networkServerClient) Activate(ctx context.Context, in *broker.DeviceAct return out, nil } -func (c *networkServerClient) Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*UplinkResponse, error) { - out := new(UplinkResponse) +func (c *networkServerClient) Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*broker.DeduplicatedUplinkMessage, error) { + out := new(broker.DeduplicatedUplinkMessage) err := grpc.Invoke(ctx, "/networkserver.NetworkServer/Uplink", in, out, c.cc, opts...) if err != nil { return nil, err @@ -211,9 +214,11 @@ type NetworkServerServer interface { // Broker requests devices with DevAddr for MIC check GetDevices(context.Context, *DevicesRequest) (*DevicesResponse, error) // Broker requests device activation "template" from Network Server - Activate(context.Context, *broker.DeviceActivationResponse) (*broker.DeviceActivationResponse, error) + PrepareActivation(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) + // Broker confirms device activation + Activate(context.Context, *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) // Broker informs Network Server about Uplink - Uplink(context.Context, *broker.DeduplicatedUplinkMessage) (*UplinkResponse, error) + Uplink(context.Context, *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... Downlink(context.Context, *broker.DownlinkMessage) (*broker.DownlinkMessage, error) } @@ -240,8 +245,26 @@ func _NetworkServer_GetDevices_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } +func _NetworkServer_PrepareActivation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.DeduplicatedDeviceActivationRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NetworkServerServer).PrepareActivation(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/networkserver.NetworkServer/PrepareActivation", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NetworkServerServer).PrepareActivation(ctx, req.(*broker.DeduplicatedDeviceActivationRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _NetworkServer_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(broker.DeviceActivationResponse) + in := new(handler.DeviceActivationResponse) if err := dec(in); err != nil { return nil, err } @@ -253,7 +276,7 @@ func _NetworkServer_Activate_Handler(srv interface{}, ctx context.Context, dec f FullMethod: "/networkserver.NetworkServer/Activate", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServerServer).Activate(ctx, req.(*broker.DeviceActivationResponse)) + return srv.(NetworkServerServer).Activate(ctx, req.(*handler.DeviceActivationResponse)) } return interceptor(ctx, in, info, handler) } @@ -302,6 +325,10 @@ var _NetworkServer_serviceDesc = grpc.ServiceDesc{ MethodName: "GetDevices", Handler: _NetworkServer_GetDevices_Handler, }, + { + MethodName: "PrepareActivation", + Handler: _NetworkServer_PrepareActivation_Handler, + }, { MethodName: "Activate", Handler: _NetworkServer_Activate_Handler, @@ -522,28 +549,20 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { } i += n4 } - return i, nil -} - -func (m *UplinkResponse) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err + if m.DisableFCntCheck { + data[i] = 0x20 + i++ + if m.DisableFCntCheck { + data[i] = 1 + } else { + data[i] = 0 + } + i++ } - return data[:n], nil -} - -func (m *UplinkResponse) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.NeedDownlink { - data[i] = 0x8 + if m.Uses32BitFCnt { + data[i] = 0x28 i++ - if m.NeedDownlink { + if m.Uses32BitFCnt { data[i] = 1 } else { data[i] = 0 @@ -684,13 +703,10 @@ func (m *DevicesResponse_Device) Size() (n int) { l = m.NwkSKey.Size() n += 1 + l + sovNetworkserver(uint64(l)) } - return n -} - -func (m *UplinkResponse) Size() (n int) { - var l int - _ = l - if m.NeedDownlink { + if m.DisableFCntCheck { + n += 2 + } + if m.Uses32BitFCnt { n += 2 } return n @@ -1038,59 +1054,29 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { return err } iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthNetworkserver - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UplinkResponse) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DisableFCntCheck", wireType) } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UplinkResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UplinkResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: + m.DisableFCntCheck = bool(v != 0) + case 5: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field NeedDownlink", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Uses32BitFCnt", wireType) } var v int for shift := uint(0); ; shift += 7 { @@ -1107,7 +1093,7 @@ func (m *UplinkResponse) Unmarshal(data []byte) error { break } } - m.NeedDownlink = bool(v != 0) + m.Uses32BitFCnt = bool(v != 0) default: iNdEx = preIndex skippy, err := skipNetworkserver(data[iNdEx:]) @@ -1418,43 +1404,47 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 603 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xc1, 0x6e, 0xd3, 0x4c, - 0x10, 0xfe, 0x93, 0xfe, 0x24, 0xe9, 0xb6, 0x69, 0xcb, 0x96, 0xaa, 0x95, 0x05, 0xa5, 0x18, 0x90, - 0x7a, 0xc1, 0x16, 0x41, 0x85, 0x0b, 0x12, 0xa4, 0xb4, 0x42, 0x50, 0xb5, 0x02, 0xb7, 0x3d, 0x5b, - 0x8e, 0x3d, 0x75, 0x2d, 0x97, 0xb5, 0xd9, 0x5d, 0x27, 0xf4, 0x4d, 0x10, 0x0f, 0xc2, 0x33, 0x70, - 0x84, 0x2b, 0x07, 0x84, 0xe0, 0x35, 0x38, 0x30, 0xde, 0xdd, 0x04, 0x1c, 0x35, 0x14, 0x7a, 0xb0, - 0xb2, 0xf3, 0xed, 0x37, 0xdf, 0xce, 0xcc, 0x37, 0x0a, 0xd9, 0x8e, 0x13, 0x79, 0x5c, 0xf4, 0x9c, - 0x30, 0x7b, 0xe5, 0x1e, 0x1c, 0xc3, 0xc1, 0x71, 0xc2, 0x62, 0xb1, 0x07, 0x72, 0x90, 0xf1, 0xd4, - 0x95, 0x92, 0xb9, 0x41, 0x9e, 0xb8, 0x4c, 0xc7, 0x02, 0x78, 0x1f, 0x78, 0x35, 0x72, 0x72, 0x9e, - 0xc9, 0x8c, 0xb6, 0x2b, 0xa0, 0x75, 0xe7, 0x37, 0xd5, 0x38, 0x8b, 0x33, 0x57, 0xb1, 0x7a, 0xc5, - 0x91, 0x8a, 0x54, 0xa0, 0x4e, 0x3a, 0xbb, 0x42, 0x9f, 0x58, 0x04, 0x7e, 0x86, 0xfe, 0xe0, 0x6f, - 0xe8, 0x3d, 0x9e, 0xa5, 0x58, 0xac, 0xfe, 0xd1, 0x89, 0xf6, 0x1b, 0x32, 0xb7, 0x05, 0xfd, 0x24, - 0x04, 0xe1, 0xc1, 0xeb, 0x02, 0x84, 0xa4, 0x2f, 0x49, 0x2b, 0x82, 0xbe, 0x1f, 0x44, 0x11, 0x5f, - 0xa9, 0xad, 0xd5, 0xd6, 0x67, 0x37, 0xef, 0x7f, 0xfe, 0x72, 0xbd, 0x73, 0xde, 0x03, 0x61, 0xc6, - 0xc1, 0x95, 0xa7, 0x39, 0x08, 0x07, 0x05, 0xbb, 0x98, 0xed, 0x35, 0x23, 0x7d, 0xa0, 0x8b, 0xe4, - 0xd2, 0x91, 0x1f, 0x32, 0xb9, 0x52, 0x47, 0xbd, 0xb6, 0xf7, 0xff, 0xd1, 0x13, 0x26, 0xed, 0x4f, - 0x75, 0x32, 0x3f, 0x7a, 0x5a, 0xe4, 0x19, 0x13, 0x40, 0x1f, 0x91, 0x26, 0x07, 0x51, 0x9c, 0x48, - 0x81, 0x4f, 0x4f, 0xad, 0xcf, 0x74, 0x6e, 0x3b, 0xd5, 0xd1, 0x8e, 0x25, 0x98, 0xd8, 0x1b, 0x66, - 0x59, 0x3f, 0x6a, 0xa4, 0xa1, 0x31, 0xba, 0x47, 0x9a, 0x41, 0x9e, 0xfb, 0x50, 0x24, 0xa6, 0x8d, - 0x0d, 0x6c, 0xe3, 0xee, 0x3f, 0xb4, 0xd1, 0xcd, 0xf3, 0xed, 0xc3, 0x67, 0x5e, 0x03, 0x55, 0xb6, - 0x8b, 0xa4, 0xd4, 0x2b, 0xe7, 0x52, 0xea, 0xd5, 0x2f, 0xa4, 0x87, 0x75, 0x29, 0x3d, 0x54, 0x29, - 0xf5, 0x3c, 0x32, 0xcd, 0x06, 0xa9, 0x2f, 0xfc, 0x14, 0x4e, 0x57, 0xa6, 0x2e, 0x34, 0xe8, 0xbd, - 0x41, 0xba, 0xbf, 0x03, 0xa7, 0x5e, 0x93, 0xe9, 0x83, 0xbd, 0x41, 0xe6, 0x0e, 0xf3, 0x93, 0x84, - 0xa5, 0xa3, 0x89, 0xde, 0x24, 0xb8, 0x87, 0x10, 0xf9, 0x51, 0x36, 0x60, 0xe5, 0x85, 0x9a, 0x45, - 0xcb, 0x9b, 0x2d, 0xc1, 0x2d, 0x83, 0xd9, 0xcb, 0x64, 0xc9, 0x83, 0x38, 0x11, 0x12, 0xb8, 0x19, - 0xa8, 0xde, 0x05, 0x7b, 0x9e, 0xb4, 0xf7, 0x65, 0x20, 0x8b, 0xe1, 0x72, 0xd8, 0xcf, 0x49, 0x43, - 0x03, 0xf4, 0x31, 0x59, 0x8c, 0xb4, 0x19, 0x7e, 0x0e, 0x5c, 0xad, 0x0b, 0x08, 0xa1, 0xe4, 0x67, - 0x3a, 0x0b, 0x4e, 0xb9, 0x9a, 0x2f, 0x80, 0x87, 0xc0, 0x64, 0x72, 0x82, 0x86, 0x5d, 0x36, 0x64, - 0xc4, 0xba, 0x9a, 0xda, 0x79, 0x5f, 0x27, 0x6d, 0xd3, 0xd8, 0xbe, 0x72, 0x97, 0xee, 0x10, 0xf2, - 0x14, 0xa4, 0xf1, 0x98, 0x5e, 0x9b, 0xe4, 0xbd, 0x2a, 0xc5, 0x5a, 0xfd, 0xf3, 0x6a, 0xa0, 0x5f, - 0xad, 0x6e, 0x28, 0x93, 0x7e, 0x20, 0x81, 0xae, 0x39, 0x66, 0xe9, 0x35, 0xc9, 0xe0, 0x49, 0xc6, - 0x86, 0x6c, 0xeb, 0x5c, 0x06, 0xc5, 0xd6, 0xf5, 0x6c, 0xe9, 0x8d, 0x5f, 0xdc, 0xa8, 0x40, 0x28, - 0xc4, 0x27, 0x22, 0x7d, 0xb7, 0x8b, 0x8d, 0x05, 0x31, 0x58, 0xe3, 0xb5, 0x8f, 0xb9, 0xf2, 0x90, - 0xb4, 0x86, 0xc3, 0xa7, 0xcb, 0x23, 0x35, 0x83, 0x0c, 0x35, 0x26, 0x5d, 0x74, 0xde, 0xd5, 0xc8, - 0x95, 0xca, 0xe0, 0x76, 0x03, 0x86, 0x38, 0x47, 0x4f, 0xe6, 0xaa, 0x3e, 0xd2, 0x5b, 0x63, 0x75, - 0x9c, 0x69, 0xb3, 0xd5, 0x52, 0x76, 0x75, 0xc3, 0x14, 0x15, 0xa6, 0xd1, 0x01, 0x63, 0xf1, 0xd5, - 0xb1, 0xe4, 0xca, 0x2a, 0x58, 0x4b, 0x67, 0xde, 0x6e, 0x2e, 0x7c, 0xf8, 0xb6, 0x5a, 0xfb, 0x88, - 0xdf, 0x57, 0xfc, 0xde, 0x7e, 0x5f, 0xfd, 0xaf, 0xd7, 0x50, 0xff, 0x34, 0xf7, 0x7e, 0x06, 0x00, - 0x00, 0xff, 0xff, 0xf9, 0x72, 0x6b, 0x9d, 0x58, 0x05, 0x00, 0x00, + // 671 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x5f, 0x4f, 0x13, 0x4b, + 0x14, 0xbf, 0xa5, 0xd0, 0x96, 0xe1, 0x16, 0xca, 0x70, 0x09, 0x4d, 0x73, 0x2f, 0x57, 0x1b, 0x8d, + 0x24, 0x86, 0x36, 0x96, 0xa8, 0x31, 0x31, 0x91, 0xf2, 0x47, 0xa3, 0x04, 0x82, 0x0b, 0x3c, 0x6f, + 0xb6, 0xbb, 0x87, 0x76, 0xb2, 0x65, 0x76, 0x9d, 0x99, 0x6d, 0xe5, 0x9b, 0x18, 0xbf, 0x81, 0xf1, + 0x8b, 0xf8, 0xe8, 0x83, 0x4f, 0x3e, 0x18, 0xa3, 0x5f, 0xc4, 0xb3, 0x33, 0x53, 0x74, 0x11, 0xe4, + 0xcf, 0xc3, 0xa4, 0x33, 0x67, 0x7e, 0xbf, 0xdf, 0x39, 0x73, 0xce, 0x6f, 0x4b, 0x36, 0xbb, 0x4c, + 0xf5, 0x92, 0x4e, 0xc3, 0x8f, 0x8e, 0x9a, 0xfb, 0x3d, 0xd8, 0xef, 0x31, 0xde, 0x95, 0x3b, 0xa0, + 0x86, 0x91, 0x08, 0x9b, 0x4a, 0xf1, 0xa6, 0x17, 0xb3, 0x26, 0x37, 0x67, 0x09, 0x62, 0x00, 0x22, + 0x7b, 0x6a, 0xc4, 0x22, 0x52, 0x11, 0x2d, 0x67, 0x82, 0xb5, 0xe5, 0x5f, 0x54, 0xbb, 0x51, 0x37, + 0x6a, 0x6a, 0x54, 0x27, 0x39, 0xd4, 0x27, 0x7d, 0xd0, 0x3b, 0xc3, 0xce, 0xc0, 0xcf, 0x2d, 0x02, + 0x97, 0x85, 0x3f, 0xbc, 0x0c, 0xbc, 0x23, 0xa2, 0x10, 0x8b, 0x35, 0x3f, 0x96, 0xf8, 0xe8, 0x32, + 0xc4, 0x9e, 0xc7, 0x83, 0x3e, 0x32, 0xed, 0xaf, 0xa1, 0xd6, 0x5f, 0x93, 0xe9, 0x0d, 0x18, 0x30, + 0x1f, 0xa4, 0x03, 0xaf, 0x12, 0x90, 0x8a, 0xbe, 0x24, 0xa5, 0x00, 0x06, 0xae, 0x17, 0x04, 0xa2, + 0x9a, 0xbb, 0x91, 0x5b, 0xfa, 0x7b, 0xed, 0xc1, 0xe7, 0x2f, 0xff, 0xb7, 0x2e, 0x4a, 0xe1, 0x47, + 0x02, 0x9a, 0xea, 0x38, 0x06, 0xd9, 0x40, 0xc1, 0x36, 0xb2, 0x9d, 0x62, 0x60, 0x36, 0x74, 0x8e, + 0x4c, 0x1c, 0xba, 0x3e, 0x57, 0xd5, 0x31, 0xd4, 0x2b, 0x3b, 0xe3, 0x87, 0xeb, 0x5c, 0xd5, 0xdf, + 0xe5, 0xc9, 0xcc, 0x49, 0x6a, 0x19, 0x47, 0x5c, 0x02, 0x7d, 0x42, 0x8a, 0x02, 0x64, 0xd2, 0x57, + 0x12, 0x53, 0xe7, 0x97, 0xa6, 0x5a, 0xb7, 0x1b, 0xd9, 0xa9, 0x9c, 0x22, 0xd8, 0xb3, 0x33, 0x62, + 0xd5, 0x3e, 0x8d, 0x91, 0x82, 0x89, 0xd1, 0x1d, 0x52, 0xf4, 0xe2, 0xd8, 0x85, 0x84, 0xd9, 0x67, + 0xdc, 0xc7, 0x67, 0xdc, 0xbb, 0xc2, 0x33, 0xda, 0x71, 0xbc, 0x79, 0xf0, 0xdc, 0x29, 0xa0, 0xca, + 0x66, 0xc2, 0x52, 0xbd, 0xb4, 0x2f, 0xa9, 0xde, 0xd8, 0xb5, 0xf4, 0xb0, 0x2e, 0xad, 0x87, 0x2a, + 0xa9, 0x9e, 0x43, 0x26, 0xf9, 0x30, 0x74, 0xa5, 0x1b, 0xc2, 0x71, 0x35, 0x7f, 0xad, 0x46, 0xef, + 0x0c, 0xc3, 0xbd, 0x2d, 0x38, 0x76, 0x8a, 0xdc, 0x6c, 0xe8, 0x32, 0x99, 0x0b, 0x98, 0xf4, 0x3a, + 0x7d, 0x70, 0x75, 0xc3, 0x5d, 0xbf, 0x07, 0x7e, 0x58, 0x1d, 0x47, 0xf5, 0x92, 0x53, 0xb1, 0x57, + 0x4f, 0xb1, 0xfb, 0xeb, 0x69, 0x9c, 0xde, 0x21, 0x95, 0x44, 0x82, 0x5c, 0x69, 0xb9, 0x1d, 0xa6, + 0x0c, 0xa3, 0x3a, 0xa1, 0xb1, 0x65, 0x13, 0x5f, 0x63, 0x2a, 0x45, 0xd7, 0x17, 0xc8, 0xbc, 0x03, + 0x5d, 0x26, 0x15, 0x08, 0xdb, 0x71, 0x63, 0x96, 0xfa, 0x0c, 0x29, 0xef, 0x29, 0x4f, 0x25, 0x23, + 0xf7, 0xd4, 0x5f, 0x90, 0x82, 0x09, 0xd0, 0x55, 0xac, 0xc5, 0x4c, 0xcb, 0x8d, 0x41, 0x68, 0x3f, + 0x81, 0x94, 0x7a, 0x16, 0x53, 0xad, 0x4a, 0x23, 0xb5, 0xfd, 0x2e, 0x08, 0x1f, 0xb8, 0x62, 0x7d, + 0x9c, 0xe8, 0xac, 0x05, 0x63, 0xac, 0x6d, 0xa0, 0xad, 0xf7, 0x79, 0x52, 0xb6, 0x2f, 0xdf, 0xd3, + 0xe3, 0xa7, 0x5b, 0x84, 0x3c, 0x03, 0x65, 0x4d, 0x40, 0xff, 0x3b, 0xcf, 0x1c, 0xba, 0x94, 0xda, + 0xe2, 0x9f, 0xbd, 0x43, 0x8f, 0xc8, 0xec, 0xae, 0x80, 0xd8, 0x13, 0xd0, 0xf6, 0x15, 0x1b, 0x78, + 0x8a, 0x45, 0x9c, 0xde, 0x6d, 0xd8, 0x2f, 0x6b, 0x03, 0x82, 0x24, 0xee, 0x33, 0xdf, 0x53, 0x10, + 0x18, 0xe6, 0x4f, 0xd4, 0x28, 0xc3, 0x55, 0xc0, 0x74, 0x97, 0x94, 0x6c, 0x10, 0xe8, 0xcd, 0xc6, + 0xe8, 0x2b, 0xfc, 0x1d, 0x6d, 0xaa, 0xab, 0x5d, 0x0c, 0x41, 0x47, 0x16, 0x0e, 0x30, 0x2b, 0x0f, + 0x51, 0xef, 0x8c, 0x42, 0xcc, 0xdd, 0x36, 0x76, 0xd2, 0xeb, 0xa6, 0x7a, 0x17, 0x41, 0xe8, 0x63, + 0x52, 0xda, 0x88, 0x86, 0x5c, 0x2b, 0x2e, 0x9c, 0xc0, 0x6d, 0x64, 0xa4, 0x73, 0xde, 0x45, 0xeb, + 0x6d, 0x8e, 0xfc, 0x93, 0x99, 0xd6, 0xb6, 0xc7, 0x31, 0x2e, 0xd0, 0x08, 0xd3, 0x59, 0xf3, 0xd0, + 0x5b, 0xa7, 0x26, 0x73, 0xa6, 0xb7, 0x6a, 0x25, 0xed, 0x91, 0x36, 0xfa, 0x74, 0x95, 0x4c, 0xe2, + 0xd8, 0xad, 0xaf, 0xfe, 0x3d, 0x45, 0xce, 0xf8, 0xaf, 0x36, 0x7f, 0xe6, 0xed, 0x5a, 0xe5, 0xc3, + 0xb7, 0xc5, 0xdc, 0x47, 0x5c, 0x5f, 0x71, 0xbd, 0xf9, 0xbe, 0xf8, 0x57, 0xa7, 0xa0, 0xff, 0xff, + 0x56, 0x7e, 0x04, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x89, 0x55, 0x49, 0x29, 0x06, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index f6cc8aeb0..7ba29a3cb 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -4,6 +4,8 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; +import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; + package networkserver; @@ -17,23 +19,24 @@ message DevicesResponse { bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + bool disable_f_cnt_check = 4; + bool uses32_bit_f_cnt = 5; } repeated Device results = 1; } -message UplinkResponse { - bool need_downlink = 1; -} - service NetworkServer { // Broker requests devices with DevAddr for MIC check rpc GetDevices(DevicesRequest) returns (DevicesResponse); // Broker requests device activation "template" from Network Server - rpc Activate(broker.DeviceActivationResponse) returns (broker.DeviceActivationResponse); + rpc PrepareActivation(broker.DeduplicatedDeviceActivationRequest) returns (broker.DeduplicatedDeviceActivationRequest); + + // Broker confirms device activation + rpc Activate(handler.DeviceActivationResponse) returns (handler.DeviceActivationResponse); // Broker informs Network Server about Uplink - rpc Uplink(broker.DeduplicatedUplinkMessage) returns (UplinkResponse); + rpc Uplink(broker.DeduplicatedUplinkMessage) returns (broker.DeduplicatedUplinkMessage); // Broker informs Network Server about Downlink, NetworkServer should sign, add MIC, ... rpc Downlink(broker.DownlinkMessage) returns (broker.DownlinkMessage); diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 4404b2d61..056ca709e 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -117,8 +117,10 @@ func (*TxConfiguration) ProtoMessage() {} func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } type ActivationMetadata struct { - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,2,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,3,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,4,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` } func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } @@ -318,26 +320,46 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevAddr != nil { + if m.AppEui != nil { data[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n1, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintLorawan(data, i, uint64(m.AppEui.Size())) + n1, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } i += n1 } - if m.NwkSKey != nil { + if m.DevEui != nil { data[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(m.NwkSKey.Size())) - n2, err := m.NwkSKey.MarshalTo(data[i:]) + i = encodeVarintLorawan(data, i, uint64(m.DevEui.Size())) + n2, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } i += n2 } + if m.DevAddr != nil { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) + n3, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.NwkSKey != nil { + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(m.NwkSKey.Size())) + n4, err := m.NwkSKey.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } return i, nil } @@ -360,21 +382,21 @@ func (m *PHYPayload) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.Mhdr.Size())) - n3, err := m.Mhdr.MarshalTo(data[i:]) + n5, err := m.Mhdr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n5 } if m.MACPayload != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) - n4, err := m.MACPayload.MarshalTo(data[i:]) + n6, err := m.MACPayload.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n6 } if len(m.Mic) > 0 { data[i] = 0x1a @@ -432,11 +454,11 @@ func (m *MACPayload) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.FHdr.Size())) - n5, err := m.FHdr.MarshalTo(data[i:]) + n7, err := m.FHdr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n7 } if m.FPort != 0 { data[i] = 0x10 @@ -471,21 +493,21 @@ func (m *FHdr) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n6, err := m.DevAddr.MarshalTo(data[i:]) + n8, err := m.DevAddr.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n8 } if m.FCtrl != nil { data[i] = 0x12 i++ i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) - n7, err := m.FCtrl.MarshalTo(data[i:]) + n9, err := m.FCtrl.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n9 } if m.FCnt != 0 { data[i] = 0x18 @@ -637,6 +659,14 @@ func (m *TxConfiguration) Size() (n int) { func (m *ActivationMetadata) Size() (n int) { var l int _ = l + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + } if m.DevAddr != nil { l = m.DevAddr.Size() n += 1 + l + sovLorawan(uint64(l)) @@ -1075,6 +1105,70 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } @@ -1106,7 +1200,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 2: + case 4: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -1957,50 +2051,53 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 711 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xcf, 0x6e, 0xda, 0x4c, - 0x10, 0x8f, 0xc3, 0x3f, 0x33, 0x04, 0x3e, 0x6b, 0xf3, 0x7d, 0x12, 0x5f, 0x5b, 0x25, 0xa9, 0xa5, - 0x4a, 0x55, 0xa4, 0x02, 0x81, 0x24, 0xc0, 0x91, 0xd0, 0x46, 0x91, 0x12, 0x12, 0xba, 0x21, 0x87, - 0x9e, 0x56, 0xc6, 0x5e, 0x83, 0x6b, 0xf0, 0x12, 0xb3, 0x84, 0xf2, 0x02, 0x7d, 0x86, 0xdc, 0xfa, - 0x02, 0x7d, 0x83, 0xbe, 0x40, 0x8f, 0x3d, 0xf7, 0x50, 0x55, 0xed, 0x8b, 0x74, 0x77, 0xed, 0x40, - 0x0e, 0x95, 0x2a, 0x55, 0xaa, 0xd4, 0x83, 0xb5, 0xf3, 0x9b, 0x99, 0xdf, 0xcc, 0x6f, 0x66, 0x59, - 0xe0, 0x68, 0xe0, 0xf1, 0xe1, 0xac, 0x5f, 0xb2, 0xd9, 0xb8, 0xdc, 0x1b, 0xd2, 0xde, 0xd0, 0x0b, - 0x06, 0xd3, 0x73, 0xca, 0xe7, 0x2c, 0xf4, 0xcb, 0x9c, 0x07, 0x65, 0x6b, 0xe2, 0x95, 0x27, 0x21, - 0xe3, 0xcc, 0x66, 0xa3, 0xf2, 0x88, 0x85, 0xd6, 0xdc, 0x0a, 0xee, 0xce, 0x92, 0x0a, 0xa0, 0x4c, - 0x0c, 0x1f, 0x3c, 0xbb, 0x57, 0x6c, 0xc0, 0x06, 0x2c, 0x22, 0xf6, 0x67, 0xae, 0x42, 0x0a, 0x28, - 0x2b, 0xe2, 0x99, 0xb7, 0x1a, 0xe8, 0x1d, 0xca, 0x2d, 0xc7, 0xe2, 0x16, 0xaa, 0x01, 0x8c, 0x99, - 0x33, 0x1b, 0x59, 0xdc, 0x63, 0x41, 0x31, 0xb7, 0xa3, 0x3d, 0x2d, 0x54, 0x37, 0x4b, 0x77, 0x8d, - 0x3a, 0xcb, 0x10, 0xbe, 0x97, 0x86, 0x1e, 0x42, 0x56, 0x92, 0x49, 0x68, 0x71, 0x5a, 0xdc, 0x10, - 0x9c, 0x2c, 0xd6, 0xa5, 0x03, 0x0b, 0x8c, 0xfe, 0x07, 0xbd, 0xef, 0xf1, 0x28, 0x96, 0x17, 0xb1, - 0x3c, 0xce, 0x08, 0xac, 0x42, 0xdb, 0x90, 0xb3, 0x99, 0x23, 0x46, 0x8d, 0xa2, 0x05, 0xc5, 0x84, - 0xc8, 0x25, 0x13, 0xcc, 0x77, 0x1a, 0xfc, 0xd3, 0x7b, 0xd3, 0x66, 0x81, 0xeb, 0x0d, 0x66, 0x61, - 0xd4, 0xec, 0xef, 0x52, 0xf8, 0x41, 0x03, 0xd4, 0xb2, 0xb9, 0x77, 0xa3, 0xfa, 0x2c, 0xd7, 0xf8, - 0x12, 0x74, 0x87, 0xde, 0x10, 0xcb, 0x71, 0xc2, 0xa2, 0x26, 0x48, 0x1b, 0x47, 0x87, 0x9f, 0xbf, - 0x6c, 0x57, 0x7f, 0x75, 0xcb, 0x36, 0x0b, 0x69, 0x99, 0x2f, 0x26, 0x74, 0x5a, 0x7a, 0x4e, 0x6f, - 0x5a, 0x82, 0x8d, 0x33, 0x4e, 0x64, 0x20, 0x0c, 0xd9, 0x60, 0xee, 0x93, 0x29, 0xf1, 0xe9, 0xa2, - 0xb8, 0xfe, 0x5b, 0x35, 0xcf, 0xe7, 0xfe, 0xe5, 0x29, 0x5d, 0xe0, 0x4c, 0x10, 0x19, 0xe6, 0x1c, - 0xa0, 0x7b, 0xf2, 0xaa, 0x6b, 0x2d, 0x46, 0xcc, 0x72, 0xd0, 0x63, 0x48, 0x8e, 0x87, 0xb1, 0xe0, - 0x5c, 0x35, 0xbf, 0xda, 0xe9, 0x89, 0xd0, 0xa1, 0x42, 0x68, 0x1f, 0x72, 0x9d, 0x56, 0x9b, 0x4c, - 0x22, 0x86, 0x92, 0x91, 0xbb, 0xbf, 0xfd, 0x56, 0x3b, 0x2e, 0x26, 0xb6, 0xbf, 0xb4, 0x91, 0x01, - 0x89, 0xb1, 0x67, 0x17, 0x13, 0x52, 0x34, 0x96, 0xa6, 0x59, 0x83, 0xa4, 0xac, 0x8a, 0xfe, 0x83, - 0xf4, 0x98, 0x48, 0x71, 0xaa, 0x69, 0x1e, 0xa7, 0xc6, 0x3d, 0x01, 0xd0, 0xbf, 0x90, 0x1a, 0x5b, - 0xaf, 0x59, 0xa8, 0x1a, 0x48, 0xaf, 0x04, 0xe6, 0x10, 0x60, 0xd5, 0x00, 0x99, 0x90, 0x72, 0xc9, - 0xcf, 0xe4, 0x1e, 0x2b, 0xb9, 0x6e, 0x5c, 0xde, 0x25, 0x13, 0x16, 0xf2, 0xbb, 0x42, 0x6e, 0x57, - 0x00, 0x79, 0xab, 0xc7, 0xb8, 0xb3, 0x9c, 0x22, 0xd2, 0x05, 0x2e, 0xee, 0xc4, 0xb5, 0xcd, 0xf7, - 0x1a, 0x24, 0x65, 0x99, 0x3f, 0x71, 0x8f, 0x4f, 0xa4, 0x26, 0x9b, 0x87, 0xa3, 0x78, 0x7b, 0x85, - 0x95, 0xf0, 0xb6, 0xf0, 0x0a, 0x8d, 0xf2, 0x40, 0x9b, 0x72, 0x3c, 0x3b, 0xe0, 0x4a, 0x5d, 0x5e, - 0xcc, 0xd3, 0x0e, 0x78, 0x34, 0x0f, 0x9b, 0xf0, 0x69, 0x31, 0xb9, 0x93, 0x10, 0x9a, 0x53, 0xee, - 0x85, 0x00, 0xe6, 0x5b, 0x0d, 0x52, 0x8a, 0x2c, 0x37, 0x6d, 0xc5, 0x52, 0x75, 0x2c, 0x4d, 0xb4, - 0x05, 0x39, 0x71, 0x10, 0xcb, 0xf6, 0x49, 0x48, 0xaf, 0x55, 0x4f, 0x1d, 0x67, 0x85, 0xab, 0x65, - 0xfb, 0x98, 0x5e, 0x2b, 0x86, 0xed, 0xab, 0x2e, 0x92, 0x61, 0xfb, 0xf2, 0xad, 0x88, 0xa5, 0xd1, - 0x40, 0xfe, 0xc6, 0x45, 0x1f, 0xe9, 0xd7, 0xdd, 0x6e, 0x84, 0xd1, 0x23, 0x80, 0x48, 0x01, 0x19, - 0xd1, 0xa0, 0x98, 0x52, 0x9b, 0xd3, 0x95, 0x8a, 0x33, 0x1a, 0xec, 0x6e, 0x8b, 0x1b, 0x5a, 0x3d, - 0x3a, 0x1d, 0x92, 0x67, 0x17, 0xb8, 0x65, 0xac, 0xa1, 0x0c, 0x24, 0x8e, 0x2f, 0x4f, 0x0d, 0x6d, - 0xd7, 0x81, 0x34, 0xa6, 0x03, 0x19, 0x2c, 0x00, 0xbc, 0xb8, 0x22, 0x8d, 0xc3, 0x1a, 0x69, 0xd4, - 0x2b, 0x22, 0x45, 0xe0, 0xab, 0x4b, 0xd2, 0xac, 0x54, 0x49, 0xb3, 0xda, 0x30, 0x34, 0x89, 0xdb, - 0xe7, 0xa4, 0x5e, 0x6f, 0x92, 0x7a, 0xa3, 0x6e, 0xac, 0x23, 0x80, 0xb4, 0xc8, 0xdf, 0xaf, 0xd5, - 0x8c, 0x84, 0x8c, 0xb5, 0xae, 0x48, 0x73, 0xef, 0x40, 0xe5, 0x26, 0xe3, 0xdc, 0xfd, 0x7a, 0x85, - 0x1c, 0xec, 0x55, 0x8c, 0xd4, 0x91, 0xf1, 0xf1, 0xdb, 0x96, 0xf6, 0x49, 0x7c, 0x5f, 0xc5, 0x77, - 0xfb, 0x7d, 0x6b, 0xad, 0x9f, 0x56, 0x7f, 0x75, 0xb5, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x65, - 0x80, 0x5d, 0x75, 0x68, 0x05, 0x00, 0x00, + // 758 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0xea, 0x46, + 0x14, 0x8e, 0xc3, 0x9f, 0x39, 0x04, 0x6a, 0x4d, 0x5a, 0x89, 0xfe, 0x28, 0x69, 0x2d, 0x55, 0xaa, + 0x22, 0x15, 0x08, 0x24, 0x01, 0x96, 0x0e, 0x4d, 0x94, 0x2a, 0x81, 0xd0, 0x09, 0x2c, 0xba, 0x1a, + 0x19, 0x7b, 0x0c, 0xae, 0xc1, 0xe3, 0x98, 0x21, 0x94, 0x17, 0xe8, 0x33, 0x64, 0xd7, 0x17, 0xe8, + 0x83, 0x74, 0xd9, 0x75, 0x2b, 0x55, 0x55, 0xef, 0x8b, 0xdc, 0x99, 0x31, 0x81, 0x5c, 0xe9, 0x4a, + 0x57, 0x37, 0xba, 0x8b, 0xbb, 0xb0, 0x7c, 0xbe, 0x39, 0xe7, 0xfb, 0xce, 0x37, 0x67, 0xc6, 0x86, + 0xf3, 0xb1, 0xcf, 0x27, 0x8b, 0x51, 0xc5, 0x61, 0xb3, 0xea, 0x60, 0x42, 0x07, 0x13, 0x3f, 0x1c, + 0xcf, 0x7b, 0x94, 0x2f, 0x59, 0x1c, 0x54, 0x39, 0x0f, 0xab, 0x76, 0xe4, 0x57, 0xa3, 0x98, 0x71, + 0xe6, 0xb0, 0x69, 0x75, 0xca, 0x62, 0x7b, 0x69, 0x87, 0x4f, 0xef, 0x8a, 0x4a, 0xa0, 0xdc, 0x1a, + 0x7e, 0xf1, 0xfd, 0x33, 0xb1, 0x31, 0x1b, 0xb3, 0x84, 0x38, 0x5a, 0x78, 0x0a, 0x29, 0xa0, 0xa2, + 0x84, 0x67, 0x3e, 0x6a, 0xa0, 0x77, 0x29, 0xb7, 0x5d, 0x9b, 0xdb, 0xa8, 0x01, 0x30, 0x63, 0xee, + 0x62, 0x6a, 0x73, 0x9f, 0x85, 0xe5, 0xc2, 0xd7, 0xda, 0x77, 0xa5, 0xfa, 0x7e, 0xe5, 0xa9, 0x51, + 0x77, 0x93, 0xc2, 0xcf, 0xca, 0xd0, 0x97, 0x90, 0x97, 0x64, 0x12, 0xdb, 0x9c, 0x96, 0xf7, 0x04, + 0x27, 0x8f, 0x75, 0xb9, 0x80, 0x05, 0x46, 0x9f, 0x83, 0x3e, 0xf2, 0x79, 0x92, 0x2b, 0x8a, 0x5c, + 0x11, 0xe7, 0x04, 0x56, 0xa9, 0x43, 0x28, 0x38, 0xcc, 0x15, 0x5b, 0x4d, 0xb2, 0x25, 0xc5, 0x84, + 0x64, 0x49, 0x16, 0x98, 0xbf, 0x6b, 0xf0, 0xc9, 0xe0, 0xd7, 0x0e, 0x0b, 0x3d, 0x7f, 0xbc, 0x88, + 0x93, 0x66, 0x1f, 0x97, 0xc3, 0x7f, 0x76, 0x01, 0x59, 0x0e, 0xf7, 0x1f, 0x54, 0x9f, 0xcd, 0x18, + 0x7b, 0x90, 0xb3, 0xa3, 0x88, 0xd0, 0x85, 0x5f, 0xd6, 0x04, 0x67, 0xef, 0xfc, 0xf4, 0xef, 0x7f, + 0x0f, 0x8f, 0xdf, 0x75, 0xc8, 0x0e, 0x8b, 0x69, 0x95, 0xaf, 0x22, 0x3a, 0xaf, 0x58, 0x51, 0x74, + 0x31, 0xfc, 0x11, 0x67, 0x85, 0xca, 0xc5, 0xc2, 0x97, 0x7a, 0x2e, 0x7d, 0x50, 0x7a, 0xbb, 0x2f, + 0xd2, 0xfb, 0x81, 0x3e, 0x28, 0x3d, 0xa1, 0x22, 0xf5, 0x7e, 0x02, 0x5d, 0xea, 0xd9, 0xae, 0x1b, + 0x97, 0x53, 0x4a, 0xf0, 0x4c, 0x08, 0xd6, 0xdf, 0x4f, 0xd0, 0x12, 0x6c, 0x2c, 0x7d, 0xc9, 0x00, + 0x61, 0xc8, 0x87, 0xcb, 0x80, 0xcc, 0x49, 0x40, 0x57, 0xe5, 0xf4, 0x8b, 0x34, 0x7b, 0xcb, 0xe0, + 0xee, 0x9a, 0xae, 0x70, 0x2e, 0x4c, 0x02, 0x73, 0x09, 0xd0, 0xbf, 0xfa, 0xb9, 0x6f, 0xaf, 0xa6, + 0xcc, 0x76, 0xd1, 0x37, 0x90, 0x9e, 0x4d, 0x84, 0x61, 0x39, 0xd1, 0x42, 0xbd, 0xb8, 0x3d, 0xf3, + 0x2b, 0xe1, 0x43, 0xa5, 0xd0, 0x09, 0x14, 0xba, 0x56, 0x87, 0x44, 0x09, 0x43, 0xcd, 0xaa, 0xf0, + 0xfc, 0x76, 0x58, 0x9d, 0xb5, 0x98, 0xb8, 0x1d, 0x9b, 0x18, 0x19, 0x90, 0x9a, 0xf9, 0x4e, 0x32, + 0x08, 0x2c, 0x43, 0xb3, 0x01, 0x69, 0xa9, 0x8a, 0x3e, 0x83, 0xec, 0x8c, 0x48, 0x73, 0xaa, 0x69, + 0x11, 0x67, 0x66, 0x03, 0x01, 0xd0, 0xa7, 0x90, 0x99, 0xd9, 0xbf, 0xb0, 0x58, 0x35, 0x90, 0xab, + 0x12, 0x98, 0x13, 0x80, 0x6d, 0x03, 0x64, 0x42, 0xc6, 0x23, 0x6f, 0xb3, 0x7b, 0xa9, 0xec, 0x7a, + 0x6b, 0x79, 0x8f, 0x44, 0x2c, 0xe6, 0x4f, 0x42, 0x5e, 0x5f, 0x00, 0x79, 0xeb, 0x2e, 0x71, 0x77, + 0xb3, 0x8b, 0xc4, 0x17, 0x78, 0xb8, 0xbb, 0xd6, 0x36, 0xff, 0xd0, 0x20, 0x2d, 0x65, 0xde, 0x38, + 0x47, 0xed, 0xc3, 0x9c, 0xe3, 0xb7, 0xd2, 0x93, 0xc3, 0xe3, 0xe9, 0x7a, 0x7a, 0xa5, 0xad, 0xf1, + 0x8e, 0x58, 0x15, 0x1e, 0xe5, 0x0b, 0xed, 0xcb, 0xed, 0x39, 0x21, 0x57, 0xee, 0x8a, 0x62, 0x3f, + 0x9d, 0x90, 0x27, 0xfb, 0x61, 0x11, 0x9f, 0x8b, 0x0b, 0x90, 0x12, 0x9e, 0x33, 0xde, 0xad, 0x00, + 0xe6, 0x6f, 0x1a, 0x64, 0x14, 0x59, 0x4e, 0xda, 0x5e, 0x5b, 0xd5, 0xb1, 0x0c, 0xd1, 0x01, 0x14, + 0xc4, 0x8b, 0xd8, 0x4e, 0x40, 0x62, 0x7a, 0xaf, 0x7a, 0xea, 0x38, 0x2f, 0x96, 0x2c, 0x27, 0xc0, + 0xf4, 0x5e, 0x31, 0x9c, 0x40, 0x75, 0x91, 0x0c, 0x27, 0x90, 0xdf, 0xb2, 0x18, 0x1a, 0x0d, 0xe5, + 0x37, 0xa8, 0x2e, 0x9a, 0x8e, 0x75, 0xaf, 0x9f, 0x60, 0xf4, 0x15, 0x40, 0xe2, 0x80, 0x4c, 0x69, + 0x58, 0xce, 0xa8, 0xc9, 0xe9, 0xca, 0xc5, 0x0d, 0x0d, 0x8f, 0x0e, 0xc5, 0x09, 0x6d, 0x7f, 0x0a, + 0x3a, 0xa4, 0x6f, 0x6e, 0xb1, 0x65, 0xec, 0xa0, 0x1c, 0xa4, 0x2e, 0xef, 0xae, 0x0d, 0xed, 0xc8, + 0x85, 0x2c, 0xa6, 0x63, 0x99, 0x2c, 0x01, 0x5c, 0x0c, 0x49, 0xeb, 0xac, 0x41, 0x5a, 0xcd, 0x9a, + 0x28, 0x11, 0x78, 0x78, 0x47, 0xda, 0xb5, 0x3a, 0x69, 0xd7, 0x5b, 0x86, 0x26, 0x71, 0xa7, 0x47, + 0x9a, 0xcd, 0x36, 0x69, 0xb6, 0x9a, 0xc6, 0x2e, 0x02, 0xc8, 0x8a, 0xfa, 0x93, 0x46, 0xc3, 0x48, + 0xc9, 0x9c, 0x35, 0x24, 0xed, 0xe3, 0x53, 0x55, 0x9b, 0x5e, 0xd7, 0x9e, 0x34, 0x6b, 0xe4, 0xf4, + 0xb8, 0x66, 0x64, 0xce, 0x8d, 0x3f, 0xff, 0x3f, 0xd0, 0xfe, 0x12, 0xcf, 0x7f, 0xe2, 0x79, 0x7c, + 0x75, 0xb0, 0x33, 0xca, 0xaa, 0x5f, 0x71, 0xe3, 0x75, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x33, + 0x7d, 0x5b, 0x08, 0x06, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index e8aca5540..a51329c61 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -24,8 +24,10 @@ message TxConfiguration { } message ActivationMetadata { - bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; - bytes nwk_s_key = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes dev_addr = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + bytes nwk_s_key = 4 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; } message PHYPayload { diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go new file mode 100644 index 000000000..297e85766 --- /dev/null +++ b/core/networkserver/device/device.go @@ -0,0 +1,135 @@ +package device + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/core/types" +) + +// Options for the specified device +type Options struct { + DisableFCntCheck bool // Disable Frame counter check (insecure) + Uses32BitFCnt bool // Use 32-bit Frame counters +} + +// Device contains the state of a device +type Device struct { + DevEUI types.DevEUI + AppEUI types.AppEUI + DevAddr types.DevAddr + NwkSKey types.NwkSKey + FCntUp uint32 + FCntDown uint32 + Options Options + Utilization Utilization +} + +// DeviceProperties contains all properties of a Device that can be stored in Redis. +var DeviceProperties = []string{ + "dev_eui", + "app_eui", + "dev_addr", + "nwk_s_key", + "f_cnt_up", + "f_cnt_down", + "options", + "utilization", +} + +// ToStringStringMap converts the given properties of Device to a +// map[string]string for storage in Redis. +func (device *Device) ToStringStringMap(properties ...string) (map[string]string, error) { + output := make(map[string]string) + for _, p := range properties { + property, err := device.formatProperty(p) + if err != nil { + return output, err + } + if property != "" { + output[p] = property + } + } + return output, nil +} + +// FromStringStringMap imports known values from the input to a Device. +func (device *Device) FromStringStringMap(input map[string]string) error { + for k, v := range input { + device.parseProperty(k, v) + } + return nil +} + +func (device *Device) formatProperty(property string) (formatted string, err error) { + switch property { + case "dev_eui": + formatted = device.DevEUI.String() + case "app_eui": + formatted = device.AppEUI.String() + case "dev_addr": + formatted = device.DevAddr.String() + case "nwk_s_key": + formatted = device.NwkSKey.String() + case "f_cnt_up": + formatted = storage.FormatUint32(device.FCntUp) + case "f_cnt_down": + formatted = storage.FormatUint32(device.FCntDown) + case "options": + // TODO + case "utilization": + // TODO + default: + err = fmt.Errorf("Property %s does not exist in Status", property) + } + return +} + +func (device *Device) parseProperty(property string, value string) error { + if value == "" { + return nil + } + switch property { + case "dev_eui": + val, err := types.ParseDevEUI(value) + if err != nil { + return err + } + device.DevEUI = val + case "app_eui": + val, err := types.ParseAppEUI(value) + if err != nil { + return err + } + device.AppEUI = val + case "dev_addr": + val, err := types.ParseDevAddr(value) + if err != nil { + return err + } + device.DevAddr = val + case "nwk_s_key": + val, err := types.ParseNwkSKey(value) + if err != nil { + return err + } + device.NwkSKey = val + case "f_cnt_up": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + device.FCntUp = val + case "f_cnt_down": + val, err := storage.ParseUint32(value) + if err != nil { + return err + } + device.FCntDown = val + case "options": + // TODO + case "utilization": + // TODO + } + return nil +} diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go new file mode 100644 index 000000000..0db32da6d --- /dev/null +++ b/core/networkserver/device/device_test.go @@ -0,0 +1,43 @@ +package device + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getTestDevice() (device *Device, dmap map[string]string) { + return &Device{ + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + DevAddr: types.DevAddr{1, 2, 3, 4}, + NwkSKey: types.NwkSKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, + FCntUp: 42, + FCntDown: 24, + }, map[string]string{ + "dev_eui": "0102030405060708", + "app_eui": "0807060504030201", + "dev_addr": "01020304", + "nwk_s_key": "00010002000300040005000600070008", + "f_cnt_up": "42", + "f_cnt_down": "24", + } +} + +func TestToStringMap(t *testing.T) { + a := New(t) + device, expected := getTestDevice() + dmap, err := device.ToStringStringMap(DeviceProperties...) + a.So(err, ShouldBeNil) + a.So(dmap, ShouldResemble, expected) +} + +func TestFromStringMap(t *testing.T) { + a := New(t) + device := &Device{} + expected, dmap := getTestDevice() + err := device.FromStringStringMap(dmap) + a.So(err, ShouldBeNil) + a.So(device, ShouldResemble, expected) +} diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go new file mode 100644 index 000000000..e57dc1ea5 --- /dev/null +++ b/core/networkserver/device/store.go @@ -0,0 +1,297 @@ +package device + +import ( + "errors" + "fmt" + "sync" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +var ( + // ErrNotFound is returned when a device was not found + ErrNotFound = errors.New("ttn/networkserver: Device not found") +) + +// Store is used to store device configurations +type Store interface { + // Get the full information about a device + Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) + // Get a list of devices matching the DevAddr + GetWithAddress(devAddr types.DevAddr) ([]*Device, error) + // Set the given fields of a device. If fields empty, it sets all fields. + Set(device *Device, fields ...string) error + // Activate a device + Activate(types.AppEUI, types.DevEUI, types.DevAddr, types.NwkSKey) error + // Delete a device + Delete(types.AppEUI, types.DevEUI) error +} + +// NewDeviceStore creates a new in-memory Device store +func NewDeviceStore() Store { + return &deviceStore{ + devices: make(map[types.AppEUI]map[types.DevEUI]*Device), + byAddress: make(map[types.DevAddr][]*Device), + } +} + +// deviceStore is an in-memory Device store. It should only be used for testing +// purposes. Use the redisDeviceStore for actual deployments. +type deviceStore struct { + devices map[types.AppEUI]map[types.DevEUI]*Device + byAddress map[types.DevAddr][]*Device +} + +func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { + if app, ok := s.devices[appEUI]; ok { + if dev, ok := app[devEUI]; ok { + return dev, nil + } + } + return nil, ErrNotFound +} + +func (s *deviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, error) { + if devices, ok := s.byAddress[devAddr]; ok { + return devices, nil + } + return []*Device{}, nil +} + +func (s *deviceStore) Set(new *Device, fields ...string) error { + // NOTE: We don't care about fields for testing + if app, ok := s.devices[new.AppEUI]; ok { + if old, ok := app[new.DevEUI]; ok { + // DevAddr Updated + if new.DevAddr != old.DevAddr && !old.DevAddr.IsEmpty() { + // Remove the old DevAddr + newList := make([]*Device, 0, len(s.byAddress[old.DevAddr])) + for _, candidate := range s.byAddress[old.DevAddr] { + if candidate.DevEUI != old.DevEUI || candidate.AppEUI != old.AppEUI { + newList = append(newList, candidate) + } + } + s.byAddress[old.DevAddr] = newList + } + } + app[new.DevEUI] = new + } else { + s.devices[new.AppEUI] = map[types.DevEUI]*Device{new.DevEUI: new} + } + + if !new.DevAddr.IsEmpty() { + if devices, ok := s.byAddress[new.DevAddr]; ok { + s.byAddress[new.DevAddr] = append(devices, new) + } else { + s.byAddress[new.DevAddr] = []*Device{new} + } + } + + return nil +} + +func (s *deviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { + dev, err := s.Get(appEUI, devEUI) + if err == ErrNotFound { + dev = &Device{ + AppEUI: appEUI, + DevEUI: devEUI, + } + } else if err != nil { + return err + } + dev.DevAddr = devAddr + dev.NwkSKey = nwkSKey + dev.FCntUp = 0 + dev.FCntDown = 0 + return s.Set(dev) +} + +func (s *deviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { + if app, ok := s.devices[appEUI]; ok { + if old, ok := app[devEUI]; ok { + delete(app, devEUI) + newList := make([]*Device, 0, len(s.byAddress[old.DevAddr])) + for _, candidate := range s.byAddress[old.DevAddr] { + if candidate.DevEUI != old.DevEUI || candidate.AppEUI != old.AppEUI { + newList = append(newList, candidate) + } + } + s.byAddress[old.DevAddr] = newList + } + } + + return nil +} + +// NewRedisDeviceStore creates a new Redis-based status store +func NewRedisDeviceStore(client *redis.Client) Store { + return &redisDeviceStore{ + client: client, + } +} + +const redisDevicePrefix = "device" +const redisDevAddrPrefix = "dev_addr" + +type redisDeviceStore struct { + client *redis.Client +} + +func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() + if err != nil { + return nil, err + } else if len(res) == 0 { + return nil, redis.Nil // This might be a bug in redis package + } + device := &Device{} + err = device.FromStringStringMap(res) + if err != nil { + return nil, err + } + return device, nil +} + +func (s *redisDeviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, error) { + keys, err := s.client.SMembers(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr)).Result() + if err != nil { + return nil, err + } + + // TODO: If someone finds a nice way to do this more efficiently, please submit a PR! + + var wg sync.WaitGroup + responses := make(chan *Device) + for _, key := range keys { + wg.Add(1) + go func(key string) { + dmap, err := s.client.HGetAllMap(key).Result() + if err == nil { + device := &Device{} + err := device.FromStringStringMap(dmap) + if err == nil { + responses <- device + } + } + wg.Done() + }(key) + } + + go func() { + wg.Wait() + close(responses) + }() + + devices := make([]*Device, 0, len(keys)) + for res := range responses { + devices = append(devices, res) + } + + return devices, nil +} + +func (s *redisDeviceStore) Set(new *Device, fields ...string) error { + if len(fields) == 0 { + fields = DeviceProperties + } + + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppEUI, new.DevEUI) + tx, err := s.client.Watch(key) + if err == nil { + defer tx.Close() + // Check for old DevAddr + if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { + // Delete old DevAddr + if devAddr != "" { + err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() + if err != nil { + return err + } + } + } + } else if err != redis.Nil { + return err + } + + dmap, err := new.ToStringStringMap(fields...) + if err != nil { + return err + } + s.client.HMSetMap(key, dmap) + + if !new.DevAddr.IsEmpty() { + err := s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, new.DevAddr), key).Err() + if err != nil { + return err + } + } + + return nil +} + +func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) + tx, err := s.client.Watch(key) + var dmap map[string]string + if err != nil && err != redis.Nil { + return err + } + defer tx.Close() + // Check for old DevAddr + if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { + // Delete old DevAddr + if devAddr != "" { + err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() + if err != nil { + return err + } + } + } + // Update Device + dev := &Device{ + AppEUI: appEUI, + DevEUI: devEUI, + DevAddr: devAddr, + NwkSKey: nwkSKey, + FCntUp: 0, + FCntDown: 0, + } + + // Don't touch Utilization and Options + dmap, err = dev.ToStringStringMap([]string{"dev_eui", "app_eui", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down"}...) + + // Register Device + err = s.client.HMSetMap(key, dmap).Err() + if err != nil { + return err + } + + // Register DevAddr + err = s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() + if err != nil { + return err + } + + return nil +} + +func (s *redisDeviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) + if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { + // Delete old DevAddr + if devAddr != "" { + err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() + if err != nil { + return err + } + } + } + err := s.client.Del(key).Err() + if err != nil { + return err + } + return nil +} diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go new file mode 100644 index 000000000..61d09d171 --- /dev/null +++ b/core/networkserver/device/store_test.go @@ -0,0 +1,170 @@ +package device + +import ( + "testing" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) +} + +func TestDeviceStore(t *testing.T) { + a := New(t) + + stores := map[string]Store{ + "local": NewDeviceStore(), + "redis": NewRedisDeviceStore(getRedisClient()), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Non-existing App + err := s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + a.So(err, ShouldBeNil) + + // Existing App + err = s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + a.So(err, ShouldBeNil) + + res, err := s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 2}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 0) + + // Existing Device, New DevAddr + err = s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 3}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + a.So(err, ShouldBeNil) + + res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + + s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 3}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + + res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 0) + res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 2}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 3}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 3}) + + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + + res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + + // Cleanup + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + } + +} + +func TestDeviceActivate(t *testing.T) { + + a := New(t) + + stores := map[string]Store{ + "local": NewDeviceStore(), + "redis": NewRedisDeviceStore(getRedisClient()), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Activate a device for the first time + err := s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 1}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + ) + a.So(err, ShouldBeNil) + + // It should register the device + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 1}) + + // It should register the DevAddr + res, err := s.GetWithAddress(types.DevAddr{0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + + s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 1, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + FCntUp: 42, + FCntDown: 42, + }) + + // Activate the same device again + err = s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 2}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}, + ) + a.So(err, ShouldBeNil) + + // It should reset the DevAddr, NwkSKey and Frame Counters + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 2}) + a.So(dev.NwkSKey, ShouldEqual, types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}) + a.So(dev.FCntUp, ShouldEqual, 0) + a.So(dev.FCntDown, ShouldEqual, 0) + + // Cleanup + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + + } + +} diff --git a/core/networkserver/device/utilization.go b/core/networkserver/device/utilization.go new file mode 100644 index 000000000..ca16ebf21 --- /dev/null +++ b/core/networkserver/device/utilization.go @@ -0,0 +1,4 @@ +package device + +// Utilization of the device +type Utilization interface{} diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go new file mode 100644 index 000000000..ff1c358ee --- /dev/null +++ b/core/networkserver/networkserver.go @@ -0,0 +1,191 @@ +package networkserver + +import ( + "errors" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/brocaar/lorawan" +) + +// NetworkServer implements LoRaWAN-specific functionality for TTN +type NetworkServer interface { + HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error) + HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) + HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) + HandleUplink(*pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) + HandleDownlink(*pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) +} + +type networkServer struct { + devices device.Store +} + +func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { + devices, err := n.devices.GetWithAddress(*req.DevAddr) + if err != nil { + return nil, err + } + + // Return all devices with DevAddr with FCnt <= fCnt or Security off + + res := &pb.DevicesResponse{ + Results: make([]*pb.DevicesResponse_Device, 0, len(devices)), + } + + for _, device := range devices { + dev := &pb.DevicesResponse_Device{ + AppEui: &device.AppEUI, + DevEui: &device.DevEUI, + NwkSKey: &device.NwkSKey, + Uses32BitFCnt: device.Options.Uses32BitFCnt, + DisableFCntCheck: device.Options.DisableFCntCheck, + } + if device.Options.DisableFCntCheck { + res.Results = append(res.Results, dev) + continue + } + if device.Options.Uses32BitFCnt { + ms := device.FCntUp / (1 << 16) + if device.FCntUp <= req.FCnt+(ms*(1<<16)) { + res.Results = append(res.Results, dev) + continue + } + } else if device.FCntUp <= req.FCnt { + res.Results = append(res.Results, dev) + continue + } + } + + return res, nil +} + +const netID = 0x13 + +func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { + // Build activation metadata if not present + if meta := activation.GetActivationMetadata(); meta == nil { + activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} + } + // Build lorawan metadata if not present + if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { + activation.ActivationMetadata.Protocol = &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{}, + } + } + // Generate random DevAddr + // TODO: Be smarter than just randomly generating addresses. + var devAddr types.DevAddr + copy(devAddr[:], random.Bytes(4)) + devAddr[0] = (netID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb + + // Set the DevAddr in the Activation + activation.ActivationMetadata.GetLorawan().DevAddr = &devAddr + + return activation, nil +} + +func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) { + meta := activation.GetActivationMetadata() + if meta == nil { + return nil, errors.New("ttn/networkserver: invalid ActivationMetadata") + } + lorawan := meta.GetLorawan() + if lorawan == nil { + return nil, errors.New("ttn/networkserver: invalid LoRaWAN ActivationMetadata") + } + err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) + if err != nil { + return nil, err + } + return activation, nil +} + +func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) { + // Get Device + dev, err := n.devices.Get(*message.AppEui, *message.DevEui) + if err != nil { + return nil, err + } + + // Unmarshal LoRaWAN Payload + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(message.Payload) + if err != nil { + return nil, err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload") + } + + // Update FCntUp + dev.FCntUp = macPayload.FHDR.FCnt + err = n.devices.Set(dev, "f_cnt_up") + if err != nil { + return nil, err + } + + // Prepare Downlink + if message.ResponseTemplate == nil { + message.ResponseTemplate = &pb_broker.DownlinkMessage{} + } + message.ResponseTemplate.AppEui = message.AppEui + message.ResponseTemplate.DevEui = message.DevEui + + // TODO: Maybe we need to add MAC commands on downlink + message.ResponseTemplate.Payload = []byte{} + + return message, nil +} + +func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) { + // Get Device + dev, err := n.devices.Get(*message.AppEui, *message.DevEui) + if err != nil { + return nil, err + } + + // Unmarshal LoRaWAN Payload + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(message.Payload) + if err != nil { + return nil, err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload") + } + + // Set DevAddr + macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr) + + // FIRST set and THEN increment FCntDown + // TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK + macPayload.FHDR.FCnt = dev.FCntDown + dev.FCntDown++ + err = n.devices.Set(dev, "f_cnt_down") + if err != nil { + return nil, err + } + + // TODO: Maybe we need to add MAC commands on downlink + + // Sign MIC + phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) + + // Update message + bytes, err := phyPayload.MarshalBinary() + if err != nil { + return nil, err + } + message.Payload = bytes + + return message, nil +} diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go new file mode 100644 index 000000000..5094cea6b --- /dev/null +++ b/core/networkserver/networkserver_test.go @@ -0,0 +1,261 @@ +package networkserver + +import ( + "testing" + + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" +) + +func TestHandleGetDevices(t *testing.T) { + a := New(t) + + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + // No Devices + res, err := ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{1, 2, 3, 4}, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldBeEmpty) + + // Matching Device + ns.devices.Set(&device.Device{ + DevAddr: types.DevAddr{1, 2, 3, 4}, + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + FCntUp: 5, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{1, 2, 3, 4}, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // Non-Matching DevAddr + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{5, 6, 7, 8}, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 0) + + // Non-Matching FCnt + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{1, 2, 3, 4}, + FCnt: 4, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 0) + + // Non-Matching FCnt, but FCnt Check Disabled + ns.devices.Set(&device.Device{ + DevAddr: types.DevAddr{5, 6, 7, 8}, + AppEUI: types.AppEUI{5, 6, 7, 8, 1, 2, 3, 4}, + DevEUI: types.DevEUI{5, 6, 7, 8, 1, 2, 3, 4}, + FCntUp: 5, + Options: device.Options{ + DisableFCntCheck: true, + }, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{5, 6, 7, 8}, + FCnt: 4, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // 32 Bit Frame Counter + ns.devices.Set(&device.Device{ + DevAddr: types.DevAddr{2, 2, 3, 4}, + AppEUI: types.AppEUI{2, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: types.DevEUI{2, 2, 3, 4, 5, 6, 7, 8}, + FCntUp: 5 + (2 << 16), + Options: device.Options{ + Uses32BitFCnt: true, + }, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{2, 2, 3, 4}, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + +} + +func TestHandlePrepareActivation(t *testing.T) { + a := New(t) + ns := &networkServer{} + resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{}) + a.So(err, ShouldBeNil) + devAddr := resp.ActivationMetadata.GetLorawan().DevAddr + a.So(devAddr.IsEmpty(), ShouldBeFalse) + a.So(devAddr[0]&254, ShouldEqual, 19<<1) // 7 MSB should be NetID +} + +func TestHandleActivate(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + _, err := ns.HandleActivate(&pb_handler.DeviceActivationResponse{}) + a.So(err, ShouldNotBeNil) + + _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ + ActivationMetadata: &pb_protocol.ActivationMetadata{}, + }) + a.So(err, ShouldNotBeNil) + + _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + AppEui: &types.AppEUI{0, 0, 0, 0, 0, 0, 3, 1}, + DevEui: &types.DevEUI{0, 0, 0, 0, 0, 0, 3, 1}, + DevAddr: &types.DevAddr{0, 0, 3, 1}, + NwkSKey: &types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1}, + }, + }}, + }) + a.So(err, ShouldBeNil) +} + +func TestHandleUplink(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + // Device Not Found + message := &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: []byte{}, + } + _, err := ns.HandleUplink(message) + a.So(err, ShouldNotBeNil) + + ns.devices.Set(&device.Device{ + DevAddr: types.DevAddr{1, 2, 3, 4}, + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + }) + + // Invalid Payload + message = &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: []byte{}, + } + _, err = ns.HandleUplink(message) + a.So(err, ShouldNotBeNil) + + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + // Valid Uplink + message = &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: bytes, + } + res, err := ns.HandleUplink(message) + a.So(err, ShouldBeNil) + a.So(res.ResponseTemplate, ShouldNotBeNil) + + // Frame Counter should have been updated + dev, _ := ns.devices.Get(types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}) + a.So(dev.FCntUp, ShouldEqual, 1) +} + +func TestHandleDownlink(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + // Device Not Found + message := &pb_broker.DownlinkMessage{ + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: []byte{}, + } + _, err := ns.HandleDownlink(message) + a.So(err, ShouldNotBeNil) + + ns.devices.Set(&device.Device{ + DevAddr: types.DevAddr{1, 2, 3, 4}, + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + }) + + // Invalid Payload + message = &pb_broker.DownlinkMessage{ + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: []byte{}, + } + _, err = ns.HandleDownlink(message) + a.So(err, ShouldNotBeNil) + + fPort := uint8(3) + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FPort: &fPort, + FHDR: lorawan.FHDR{ + FCtrl: lorawan.FCtrl{ + ACK: true, + }, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + message = &pb_broker.DownlinkMessage{ + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: bytes, + } + res, err := ns.HandleDownlink(message) + a.So(err, ShouldBeNil) + + var phyPayload lorawan.PHYPayload + phyPayload.UnmarshalBinary(res.Payload) + macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + a.So(*macPayload.FPort, ShouldEqual, 3) + a.So(macPayload.FHDR.DevAddr, ShouldEqual, lorawan.DevAddr{1, 2, 3, 4}) + a.So(macPayload.FHDR.FCnt, ShouldEqual, 0) // The first Frame counter is zero + a.So(phyPayload.MIC, ShouldNotEqual, [4]byte{0, 0, 0, 0}) // MIC should be set, we'll check it with actual examples in the integration test + + dev, _ := ns.devices.Get(types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}) + a.So(dev.FCntDown, ShouldEqual, 1) + +} diff --git a/core/networkserver/server.go b/core/networkserver/server.go new file mode 100644 index 000000000..68d828a5f --- /dev/null +++ b/core/networkserver/server.go @@ -0,0 +1,87 @@ +package networkserver + +import ( + "errors" + + "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/handler" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type networkServerRPC struct { + networkServer NetworkServer +} + +func validateBrokerFromMetadata(ctx context.Context) (err error) { + md, ok := metadata.FromContext(ctx) + // TODO: Check OK + id, ok := md["id"] + if !ok || len(id) < 1 { + err = errors.New("ttn/networkserver: Broker did not provide \"id\" in context") + return + } + if err != nil { + return + } + token, ok := md["token"] + if !ok || len(token) < 1 { + err = errors.New("ttn/networkserver: Broker did not provide \"token\" in context") + return + } + if token[0] != "token" { + // TODO: Validate Token + err = errors.New("ttn/networkserver: Broker not authorized") + return + } + + return +} + +func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesRequest) (*pb.DevicesResponse, error) { + err := validateBrokerFromMetadata(ctx) + if err != nil { + return nil, err + } + return nil, nil +} + +func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { + err := validateBrokerFromMetadata(ctx) + if err != nil { + return nil, err + } + return s.networkServer.HandlePrepareActivation(activation) +} + +func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { + err := validateBrokerFromMetadata(ctx) + if err != nil { + return nil, err + } + return s.networkServer.HandleActivate(activation) +} + +func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { + err := validateBrokerFromMetadata(ctx) + if err != nil { + return nil, err + } + return s.networkServer.HandleUplink(message) +} + +func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { + err := validateBrokerFromMetadata(ctx) + if err != nil { + return nil, err + } + return s.networkServer.HandleDownlink(message) +} + +// RegisterRPC registers this networkserver as a NetworkServerServer (github.com/TheThingsNetwork/ttn/api/networkserver) +func (n *networkServer) RegisterRPC(s *grpc.Server) { + server := &networkServerRPC{n} + pb.RegisterNetworkServerServer(s, server) +} diff --git a/core/networkserver/server_test.go b/core/networkserver/server_test.go new file mode 100644 index 000000000..20243c814 --- /dev/null +++ b/core/networkserver/server_test.go @@ -0,0 +1,3 @@ +package networkserver + +// TODO: The RPC server is super simple, but we should still test it. From 0fc40bb5c414dc799120414a1bfa836a56a56d2a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 20 May 2016 16:22:19 +0200 Subject: [PATCH 1449/2266] Include FCnt in NetworkServer devices response --- api/networkserver/networkserver.pb.go | 125 ++++++++++++++++---------- api/networkserver/networkserver.proto | 11 +-- core/networkserver/networkserver.go | 1 + 3 files changed, 84 insertions(+), 53 deletions(-) diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 2fb97b4a2..91344b262 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -73,8 +73,9 @@ type DevicesResponse_Device struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - DisableFCntCheck bool `protobuf:"varint,4,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` - Uses32BitFCnt bool `protobuf:"varint,5,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + FCnt uint32 `protobuf:"varint,4,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` } func (m *DevicesResponse_Device) Reset() { *m = DevicesResponse_Device{} } @@ -549,9 +550,14 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { } i += n4 } - if m.DisableFCntCheck { + if m.FCnt != 0 { data[i] = 0x20 i++ + i = encodeVarintNetworkserver(data, i, uint64(m.FCnt)) + } + if m.DisableFCntCheck { + data[i] = 0x58 + i++ if m.DisableFCntCheck { data[i] = 1 } else { @@ -560,7 +566,7 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { i++ } if m.Uses32BitFCnt { - data[i] = 0x28 + data[i] = 0x60 i++ if m.Uses32BitFCnt { data[i] = 1 @@ -703,6 +709,9 @@ func (m *DevicesResponse_Device) Size() (n int) { l = m.NwkSKey.Size() n += 1 + l + sovNetworkserver(uint64(l)) } + if m.FCnt != 0 { + n += 1 + sovNetworkserver(uint64(m.FCnt)) + } if m.DisableFCntCheck { n += 2 } @@ -1055,6 +1064,25 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { } iNdEx = postIndex case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field DisableFCntCheck", wireType) } @@ -1074,7 +1102,7 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { } } m.DisableFCntCheck = bool(v != 0) - case 5: + case 12: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Uses32BitFCnt", wireType) } @@ -1404,47 +1432,48 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 671 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x5f, 0x4f, 0x13, 0x4b, - 0x14, 0xbf, 0xa5, 0xd0, 0x96, 0xe1, 0x16, 0xca, 0x70, 0x09, 0x4d, 0x73, 0x2f, 0x57, 0x1b, 0x8d, - 0x24, 0x86, 0x36, 0x96, 0xa8, 0x31, 0x31, 0x91, 0xf2, 0x47, 0xa3, 0x04, 0x82, 0x0b, 0x3c, 0x6f, - 0xb6, 0xbb, 0x87, 0x76, 0xb2, 0x65, 0x76, 0x9d, 0x99, 0x6d, 0xe5, 0x9b, 0x18, 0xbf, 0x81, 0xf1, - 0x8b, 0xf8, 0xe8, 0x83, 0x4f, 0x3e, 0x18, 0xa3, 0x5f, 0xc4, 0xb3, 0x33, 0x53, 0x74, 0x11, 0xe4, - 0xcf, 0xc3, 0xa4, 0x33, 0x67, 0x7e, 0xbf, 0xdf, 0x39, 0x73, 0xce, 0x6f, 0x4b, 0x36, 0xbb, 0x4c, - 0xf5, 0x92, 0x4e, 0xc3, 0x8f, 0x8e, 0x9a, 0xfb, 0x3d, 0xd8, 0xef, 0x31, 0xde, 0x95, 0x3b, 0xa0, - 0x86, 0x91, 0x08, 0x9b, 0x4a, 0xf1, 0xa6, 0x17, 0xb3, 0x26, 0x37, 0x67, 0x09, 0x62, 0x00, 0x22, - 0x7b, 0x6a, 0xc4, 0x22, 0x52, 0x11, 0x2d, 0x67, 0x82, 0xb5, 0xe5, 0x5f, 0x54, 0xbb, 0x51, 0x37, - 0x6a, 0x6a, 0x54, 0x27, 0x39, 0xd4, 0x27, 0x7d, 0xd0, 0x3b, 0xc3, 0xce, 0xc0, 0xcf, 0x2d, 0x02, - 0x97, 0x85, 0x3f, 0xbc, 0x0c, 0xbc, 0x23, 0xa2, 0x10, 0x8b, 0x35, 0x3f, 0x96, 0xf8, 0xe8, 0x32, - 0xc4, 0x9e, 0xc7, 0x83, 0x3e, 0x32, 0xed, 0xaf, 0xa1, 0xd6, 0x5f, 0x93, 0xe9, 0x0d, 0x18, 0x30, - 0x1f, 0xa4, 0x03, 0xaf, 0x12, 0x90, 0x8a, 0xbe, 0x24, 0xa5, 0x00, 0x06, 0xae, 0x17, 0x04, 0xa2, - 0x9a, 0xbb, 0x91, 0x5b, 0xfa, 0x7b, 0xed, 0xc1, 0xe7, 0x2f, 0xff, 0xb7, 0x2e, 0x4a, 0xe1, 0x47, - 0x02, 0x9a, 0xea, 0x38, 0x06, 0xd9, 0x40, 0xc1, 0x36, 0xb2, 0x9d, 0x62, 0x60, 0x36, 0x74, 0x8e, - 0x4c, 0x1c, 0xba, 0x3e, 0x57, 0xd5, 0x31, 0xd4, 0x2b, 0x3b, 0xe3, 0x87, 0xeb, 0x5c, 0xd5, 0xdf, - 0xe5, 0xc9, 0xcc, 0x49, 0x6a, 0x19, 0x47, 0x5c, 0x02, 0x7d, 0x42, 0x8a, 0x02, 0x64, 0xd2, 0x57, - 0x12, 0x53, 0xe7, 0x97, 0xa6, 0x5a, 0xb7, 0x1b, 0xd9, 0xa9, 0x9c, 0x22, 0xd8, 0xb3, 0x33, 0x62, - 0xd5, 0x3e, 0x8d, 0x91, 0x82, 0x89, 0xd1, 0x1d, 0x52, 0xf4, 0xe2, 0xd8, 0x85, 0x84, 0xd9, 0x67, - 0xdc, 0xc7, 0x67, 0xdc, 0xbb, 0xc2, 0x33, 0xda, 0x71, 0xbc, 0x79, 0xf0, 0xdc, 0x29, 0xa0, 0xca, - 0x66, 0xc2, 0x52, 0xbd, 0xb4, 0x2f, 0xa9, 0xde, 0xd8, 0xb5, 0xf4, 0xb0, 0x2e, 0xad, 0x87, 0x2a, - 0xa9, 0x9e, 0x43, 0x26, 0xf9, 0x30, 0x74, 0xa5, 0x1b, 0xc2, 0x71, 0x35, 0x7f, 0xad, 0x46, 0xef, - 0x0c, 0xc3, 0xbd, 0x2d, 0x38, 0x76, 0x8a, 0xdc, 0x6c, 0xe8, 0x32, 0x99, 0x0b, 0x98, 0xf4, 0x3a, - 0x7d, 0x70, 0x75, 0xc3, 0x5d, 0xbf, 0x07, 0x7e, 0x58, 0x1d, 0x47, 0xf5, 0x92, 0x53, 0xb1, 0x57, - 0x4f, 0xb1, 0xfb, 0xeb, 0x69, 0x9c, 0xde, 0x21, 0x95, 0x44, 0x82, 0x5c, 0x69, 0xb9, 0x1d, 0xa6, - 0x0c, 0xa3, 0x3a, 0xa1, 0xb1, 0x65, 0x13, 0x5f, 0x63, 0x2a, 0x45, 0xd7, 0x17, 0xc8, 0xbc, 0x03, - 0x5d, 0x26, 0x15, 0x08, 0xdb, 0x71, 0x63, 0x96, 0xfa, 0x0c, 0x29, 0xef, 0x29, 0x4f, 0x25, 0x23, - 0xf7, 0xd4, 0x5f, 0x90, 0x82, 0x09, 0xd0, 0x55, 0xac, 0xc5, 0x4c, 0xcb, 0x8d, 0x41, 0x68, 0x3f, - 0x81, 0x94, 0x7a, 0x16, 0x53, 0xad, 0x4a, 0x23, 0xb5, 0xfd, 0x2e, 0x08, 0x1f, 0xb8, 0x62, 0x7d, - 0x9c, 0xe8, 0xac, 0x05, 0x63, 0xac, 0x6d, 0xa0, 0xad, 0xf7, 0x79, 0x52, 0xb6, 0x2f, 0xdf, 0xd3, - 0xe3, 0xa7, 0x5b, 0x84, 0x3c, 0x03, 0x65, 0x4d, 0x40, 0xff, 0x3b, 0xcf, 0x1c, 0xba, 0x94, 0xda, - 0xe2, 0x9f, 0xbd, 0x43, 0x8f, 0xc8, 0xec, 0xae, 0x80, 0xd8, 0x13, 0xd0, 0xf6, 0x15, 0x1b, 0x78, - 0x8a, 0x45, 0x9c, 0xde, 0x6d, 0xd8, 0x2f, 0x6b, 0x03, 0x82, 0x24, 0xee, 0x33, 0xdf, 0x53, 0x10, - 0x18, 0xe6, 0x4f, 0xd4, 0x28, 0xc3, 0x55, 0xc0, 0x74, 0x97, 0x94, 0x6c, 0x10, 0xe8, 0xcd, 0xc6, - 0xe8, 0x2b, 0xfc, 0x1d, 0x6d, 0xaa, 0xab, 0x5d, 0x0c, 0x41, 0x47, 0x16, 0x0e, 0x30, 0x2b, 0x0f, - 0x51, 0xef, 0x8c, 0x42, 0xcc, 0xdd, 0x36, 0x76, 0xd2, 0xeb, 0xa6, 0x7a, 0x17, 0x41, 0xe8, 0x63, - 0x52, 0xda, 0x88, 0x86, 0x5c, 0x2b, 0x2e, 0x9c, 0xc0, 0x6d, 0x64, 0xa4, 0x73, 0xde, 0x45, 0xeb, - 0x6d, 0x8e, 0xfc, 0x93, 0x99, 0xd6, 0xb6, 0xc7, 0x31, 0x2e, 0xd0, 0x08, 0xd3, 0x59, 0xf3, 0xd0, - 0x5b, 0xa7, 0x26, 0x73, 0xa6, 0xb7, 0x6a, 0x25, 0xed, 0x91, 0x36, 0xfa, 0x74, 0x95, 0x4c, 0xe2, - 0xd8, 0xad, 0xaf, 0xfe, 0x3d, 0x45, 0xce, 0xf8, 0xaf, 0x36, 0x7f, 0xe6, 0xed, 0x5a, 0xe5, 0xc3, - 0xb7, 0xc5, 0xdc, 0x47, 0x5c, 0x5f, 0x71, 0xbd, 0xf9, 0xbe, 0xf8, 0x57, 0xa7, 0xa0, 0xff, 0xff, - 0x56, 0x7e, 0x04, 0x00, 0x00, 0xff, 0xff, 0x3b, 0x89, 0x55, 0x49, 0x29, 0x06, 0x00, 0x00, + // 673 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x4e, 0x13, 0x41, + 0x14, 0xb6, 0x14, 0xdb, 0x32, 0x50, 0x28, 0x83, 0x84, 0xa6, 0x51, 0xd4, 0x46, 0x23, 0x89, 0xa1, + 0x8d, 0x25, 0x6a, 0x4c, 0x4c, 0xa4, 0xfc, 0x68, 0x94, 0x40, 0x70, 0x81, 0xeb, 0xcd, 0x76, 0xf7, + 0xd0, 0x4e, 0x5a, 0x66, 0xd7, 0x99, 0xd9, 0x56, 0xde, 0xc4, 0xf8, 0x0a, 0xbe, 0x83, 0xd7, 0x5e, + 0x7a, 0xed, 0x85, 0x31, 0x7a, 0xe5, 0x5b, 0x78, 0x76, 0x66, 0x0a, 0x2e, 0x82, 0xfc, 0x5c, 0x4c, + 0x76, 0xe6, 0xcc, 0xf7, 0x7d, 0x73, 0xe6, 0x9c, 0x6f, 0x96, 0xac, 0xb7, 0x99, 0xea, 0xc4, 0xad, + 0x9a, 0x1f, 0x1e, 0xd4, 0x77, 0x3b, 0xb0, 0xdb, 0x61, 0xbc, 0x2d, 0xb7, 0x40, 0x0d, 0x42, 0xd1, + 0xad, 0x2b, 0xc5, 0xeb, 0x5e, 0xc4, 0xea, 0xdc, 0xac, 0x25, 0x88, 0x3e, 0x88, 0xf4, 0xaa, 0x16, + 0x89, 0x50, 0x85, 0xb4, 0x98, 0x0a, 0x56, 0x16, 0xff, 0x52, 0x6d, 0x87, 0xed, 0xb0, 0xae, 0x51, + 0xad, 0x78, 0x5f, 0xaf, 0xf4, 0x42, 0xcf, 0x0c, 0x3b, 0x05, 0x3f, 0x33, 0x09, 0x1c, 0x16, 0xfe, + 0xf4, 0x22, 0xf0, 0x96, 0x08, 0xbb, 0x98, 0xac, 0xf9, 0x58, 0xe2, 0xb3, 0x8b, 0x10, 0x3b, 0x1e, + 0x0f, 0x7a, 0xc8, 0xb4, 0x5f, 0x43, 0xad, 0xbe, 0x27, 0x93, 0x6b, 0xd0, 0x67, 0x3e, 0x48, 0x07, + 0xde, 0xc5, 0x20, 0x15, 0x7d, 0x4b, 0x0a, 0x01, 0xf4, 0x5d, 0x2f, 0x08, 0x44, 0x39, 0x73, 0x27, + 0xb3, 0x30, 0xb1, 0xf2, 0xe4, 0xdb, 0xf7, 0xdb, 0x8d, 0xf3, 0x8e, 0xf0, 0x43, 0x01, 0x75, 0x75, + 0x18, 0x81, 0xac, 0xa1, 0x60, 0x13, 0xd9, 0x4e, 0x3e, 0x30, 0x13, 0x3a, 0x43, 0xae, 0xef, 0xbb, + 0x3e, 0x57, 0xe5, 0x11, 0xd4, 0x2b, 0x3a, 0xa3, 0xfb, 0xab, 0x5c, 0x55, 0x3f, 0x67, 0xc9, 0xd4, + 0xd1, 0xd1, 0x32, 0x0a, 0xb9, 0x04, 0xfa, 0x82, 0xe4, 0x05, 0xc8, 0xb8, 0xa7, 0x24, 0x1e, 0x9d, + 0x5d, 0x18, 0x6f, 0xdc, 0xaf, 0xa5, 0xbb, 0x72, 0x82, 0x60, 0xd7, 0xce, 0x90, 0x55, 0xf9, 0x3d, + 0x42, 0x72, 0x26, 0x46, 0xb7, 0x48, 0xde, 0x8b, 0x22, 0x17, 0x62, 0x66, 0xaf, 0xf1, 0x18, 0xaf, + 0xf1, 0xe8, 0x12, 0xd7, 0x68, 0x46, 0xd1, 0xfa, 0xde, 0x6b, 0x27, 0x87, 0x2a, 0xeb, 0x31, 0x4b, + 0xf4, 0x92, 0xba, 0x24, 0x7a, 0x23, 0x57, 0xd2, 0xc3, 0xbc, 0xb4, 0x1e, 0xaa, 0x24, 0x7a, 0x0e, + 0x19, 0xe3, 0x83, 0xae, 0x2b, 0xdd, 0x2e, 0x1c, 0x96, 0xb3, 0x57, 0x2a, 0xf4, 0xd6, 0xa0, 0xbb, + 0xb3, 0x01, 0x87, 0x4e, 0x9e, 0x9b, 0xc9, 0x71, 0xa1, 0x47, 0x8f, 0x0b, 0x4d, 0x17, 0xc9, 0x4c, + 0xc0, 0xa4, 0xd7, 0xea, 0x81, 0xab, 0x37, 0x5d, 0xbf, 0x03, 0x7e, 0xb7, 0x3c, 0x8e, 0x90, 0x82, + 0x53, 0xb2, 0x5b, 0x2f, 0x11, 0xb9, 0x9a, 0xc4, 0xe9, 0x03, 0x52, 0x8a, 0x25, 0xc8, 0xa5, 0x86, + 0xdb, 0x62, 0xca, 0x30, 0xca, 0x13, 0x1a, 0x5b, 0x34, 0xf1, 0x15, 0xa6, 0x12, 0x74, 0x75, 0x8e, + 0xcc, 0x3a, 0xd0, 0x66, 0x52, 0x81, 0xb0, 0x6d, 0x30, 0x0e, 0xaa, 0x4e, 0x91, 0xe2, 0x8e, 0xf2, + 0x54, 0x3c, 0xb4, 0x54, 0xf5, 0x0d, 0xc9, 0x99, 0x00, 0x5d, 0xc6, 0x5c, 0x4c, 0x0b, 0xdd, 0x08, + 0x84, 0x36, 0x19, 0x48, 0xa9, 0x1b, 0x34, 0xde, 0x28, 0xd5, 0x92, 0xb7, 0xb0, 0x0d, 0xc2, 0x07, + 0xae, 0x58, 0x0f, 0xdb, 0x3c, 0x6d, 0xc1, 0x18, 0x6b, 0x1a, 0x68, 0xe3, 0x53, 0x96, 0x14, 0x6d, + 0x39, 0x76, 0xb4, 0x27, 0xe8, 0x06, 0x21, 0xaf, 0x40, 0x59, 0x67, 0xd0, 0x5b, 0x67, 0x39, 0x46, + 0xa7, 0x52, 0x99, 0xff, 0xbf, 0xa1, 0xe8, 0x01, 0x99, 0xde, 0x16, 0x10, 0x79, 0x02, 0x9a, 0xbe, + 0x62, 0x7d, 0x4f, 0xb1, 0x90, 0xd3, 0x87, 0x35, 0xfb, 0xdc, 0xd6, 0x20, 0x88, 0xa3, 0x1e, 0xf3, + 0x3d, 0x05, 0x81, 0x61, 0x1e, 0xa3, 0x86, 0x27, 0x5c, 0x06, 0x4c, 0xb7, 0x49, 0xc1, 0x06, 0x81, + 0xde, 0xad, 0x0d, 0x9f, 0xe6, 0xbf, 0x68, 0x93, 0x5d, 0xe5, 0x7c, 0x08, 0xda, 0x34, 0xb7, 0x87, + 0xa7, 0xf2, 0x2e, 0xea, 0x9d, 0x92, 0x88, 0xd9, 0xdb, 0xc4, 0x4a, 0x7a, 0xed, 0x44, 0xef, 0x3c, + 0x08, 0x7d, 0x4e, 0x0a, 0x6b, 0xe1, 0x80, 0x6b, 0xc5, 0xb9, 0x23, 0xb8, 0x8d, 0x0c, 0x75, 0xce, + 0xda, 0x68, 0x7c, 0xcc, 0x90, 0x1b, 0xa9, 0x6e, 0x6d, 0x7a, 0x1c, 0xe3, 0x02, 0x8d, 0x30, 0x99, + 0x36, 0x0f, 0xbd, 0x77, 0xa2, 0x33, 0xa7, 0x7a, 0xab, 0x52, 0xd0, 0x1e, 0x69, 0xa2, 0x4f, 0x97, + 0xc9, 0x18, 0xb6, 0xdd, 0xfa, 0xea, 0xe6, 0x09, 0x72, 0xca, 0x7f, 0x95, 0xd9, 0x53, 0x77, 0x57, + 0x4a, 0x5f, 0x7e, 0xce, 0x67, 0xbe, 0xe2, 0xf8, 0x81, 0xe3, 0xc3, 0xaf, 0xf9, 0x6b, 0xad, 0x9c, + 0xfe, 0x29, 0x2e, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xef, 0xf8, 0xb4, 0x3e, 0x06, 0x00, + 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 7ba29a3cb..346372309 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -16,11 +16,12 @@ message DevicesRequest { message DevicesResponse { message Device { - bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; - bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; - bool disable_f_cnt_check = 4; - bool uses32_bit_f_cnt = 5; + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + uint32 f_cnt = 4; + bool disable_f_cnt_check = 11; + bool uses32_bit_f_cnt = 12; } repeated Device results = 1; } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index ff1c358ee..ae1306e50 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -44,6 +44,7 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes AppEui: &device.AppEUI, DevEui: &device.DevEUI, NwkSKey: &device.NwkSKey, + FCnt: device.FCntUp, Uses32BitFCnt: device.Options.Uses32BitFCnt, DisableFCntCheck: device.Options.DisableFCntCheck, } From b25c86c0fc52c5d4d3096c64858392b0626bb610 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 20 May 2016 19:05:16 +0200 Subject: [PATCH 1450/2266] Add Redis Discovery --- api/discovery/announcement.go | 98 ++++++++++ api/discovery/announcement_test.go | 53 ++++++ api/discovery/discovery.pb.go | 278 +++++++++++++++++++++++++---- api/discovery/discovery.proto | 9 +- core/discovery/discovery.go | 103 ++++++++++- core/discovery/discovery_test.go | 152 ++++++++++------ core/discovery/server.go | 10 ++ core/discovery/server_test.go | 8 + 8 files changed, 624 insertions(+), 87 deletions(-) create mode 100644 api/discovery/announcement.go create mode 100644 api/discovery/announcement_test.go diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go new file mode 100644 index 000000000..18500f967 --- /dev/null +++ b/api/discovery/announcement.go @@ -0,0 +1,98 @@ +package discovery + +import ( + "encoding/json" + "fmt" +) + +// AnnouncementProperties contains all properties of an Announcement that can +// be stored in Redis. +var AnnouncementProperties = []string{ + "id", + "token", + "description", + "service_name", + "service_version", + "net_address", + "metadata", +} + +// ToStringStringMap converts the given properties of Announcement to a +// map[string]string for storage in Redis. +func (announcement *Announcement) ToStringStringMap(properties ...string) (map[string]string, error) { + output := make(map[string]string) + for _, p := range properties { + property, err := announcement.formatProperty(p) + if err != nil { + return output, err + } + if property != "" { + output[p] = property + } + } + return output, nil +} + +// FromStringStringMap imports known values from the input to a Status. +func (announcement *Announcement) FromStringStringMap(input map[string]string) error { + for k, v := range input { + announcement.parseProperty(k, v) + } + return nil +} + +func (announcement *Announcement) formatProperty(property string) (formatted string, err error) { + switch property { + case "id": + formatted = announcement.Id + case "token": + formatted = announcement.Token + case "description": + formatted = announcement.Description + case "service_name": + formatted = announcement.ServiceName + case "service_version": + formatted = announcement.ServiceVersion + case "net_address": + formatted = announcement.NetAddress + case "metadata": + json, err := json.Marshal(announcement.Metadata) + if err != nil { + return "", err + } + formatted = string(json) + default: + err = fmt.Errorf("Property %s does not exist in Announcement", property) + } + return +} + +func (announcement *Announcement) parseProperty(property string, value string) error { + if value == "" { + return nil + } + switch property { + case "id": + announcement.Id = value + case "token": + announcement.Token = value + case "description": + announcement.Description = value + case "service_name": + announcement.ServiceName = value + case "service_version": + announcement.ServiceVersion = value + case "net_address": + announcement.NetAddress = value + case "metadata": + metadata := []*Metadata{} + err := json.Unmarshal([]byte(value), &metadata) + if err != nil { + return err + } + announcement.Metadata = metadata + default: + return fmt.Errorf("Property %s does not exist in Announcement", property) + } + return nil +} diff --git a/api/discovery/announcement_test.go b/api/discovery/announcement_test.go new file mode 100644 index 000000000..fd5ebf9c9 --- /dev/null +++ b/api/discovery/announcement_test.go @@ -0,0 +1,53 @@ +package discovery + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func getTestAnnouncement() (announcement *Announcement, dmap map[string]string) { + return &Announcement{ + Id: "abcdef", + Token: "ghijkl", + Description: "Test Description", + ServiceName: "router", + ServiceVersion: "1.0-preview build abcdef", + NetAddress: "localhost:1234", + Metadata: []*Metadata{ + &Metadata{ + Key: Metadata_PREFIX, + Value: []byte("38"), + }, + &Metadata{ + Key: Metadata_PREFIX, + Value: []byte("39"), + }, + }, + }, map[string]string{ + "id": "abcdef", + "token": "ghijkl", + "description": "Test Description", + "service_name": "router", + "service_version": "1.0-preview build abcdef", + "net_address": "localhost:1234", + "metadata": `[{"key":1,"value":"Mzg="},{"key":1,"value":"Mzk="}]`, + } +} + +func TestToStringMap(t *testing.T) { + a := New(t) + announcement, expected := getTestAnnouncement() + dmap, err := announcement.ToStringStringMap(AnnouncementProperties...) + a.So(err, ShouldBeNil) + a.So(dmap, ShouldResemble, expected) +} + +func TestFromStringMap(t *testing.T) { + a := New(t) + announcement := &Announcement{} + expected, dmap := getTestAnnouncement() + err := announcement.FromStringStringMap(dmap) + a.So(err, ShouldBeNil) + a.So(announcement, ShouldResemble, expected) +} diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 050a0683f..b18b81290 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -12,6 +12,7 @@ Metadata Announcement DiscoverRequest + GetRequest DiscoverResponse */ package discovery @@ -40,14 +41,17 @@ const _ = proto.ProtoPackageIsVersion1 type Metadata_Key int32 const ( - Metadata_PREFIX Metadata_Key = 0 + Metadata_OTHER Metadata_Key = 0 + Metadata_PREFIX Metadata_Key = 1 ) var Metadata_Key_name = map[int32]string{ - 0: "PREFIX", + 0: "OTHER", + 1: "PREFIX", } var Metadata_Key_value = map[string]int32{ - "PREFIX": 0, + "OTHER": 0, + "PREFIX": 1, } func (x Metadata_Key) String() string { @@ -96,6 +100,16 @@ func (m *DiscoverRequest) String() string { return proto.CompactTextS func (*DiscoverRequest) ProtoMessage() {} func (*DiscoverRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } +type GetRequest struct { + ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + Id []string `protobuf:"bytes,2,rep,name=id" json:"id,omitempty"` +} + +func (m *GetRequest) Reset() { *m = GetRequest{} } +func (m *GetRequest) String() string { return proto.CompactTextString(m) } +func (*GetRequest) ProtoMessage() {} +func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{3} } + type DiscoverResponse struct { Services []*Announcement `protobuf:"bytes,1,rep,name=services" json:"services,omitempty"` } @@ -103,7 +117,7 @@ type DiscoverResponse struct { func (m *DiscoverResponse) Reset() { *m = DiscoverResponse{} } func (m *DiscoverResponse) String() string { return proto.CompactTextString(m) } func (*DiscoverResponse) ProtoMessage() {} -func (*DiscoverResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{3} } +func (*DiscoverResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{4} } func (m *DiscoverResponse) GetServices() []*Announcement { if m != nil { @@ -116,6 +130,7 @@ func init() { proto.RegisterType((*Metadata)(nil), "discovery.Metadata") proto.RegisterType((*Announcement)(nil), "discovery.Announcement") proto.RegisterType((*DiscoverRequest)(nil), "discovery.DiscoverRequest") + proto.RegisterType((*GetRequest)(nil), "discovery.GetRequest") proto.RegisterType((*DiscoverResponse)(nil), "discovery.DiscoverResponse") proto.RegisterEnum("discovery.Metadata_Key", Metadata_Key_name, Metadata_Key_value) } @@ -133,6 +148,7 @@ const _ = grpc.SupportPackageIsVersion2 type DiscoveryClient interface { Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*api.Ack, error) Discover(ctx context.Context, in *DiscoverRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) + Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) } type discoveryClient struct { @@ -161,11 +177,21 @@ func (c *discoveryClient) Discover(ctx context.Context, in *DiscoverRequest, opt return out, nil } +func (c *discoveryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) { + out := new(DiscoverResponse) + err := grpc.Invoke(ctx, "/discovery.Discovery/Get", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Discovery service type DiscoveryServer interface { Announce(context.Context, *Announcement) (*api.Ack, error) Discover(context.Context, *DiscoverRequest) (*DiscoverResponse, error) + Get(context.Context, *GetRequest) (*DiscoverResponse, error) } func RegisterDiscoveryServer(s *grpc.Server, srv DiscoveryServer) { @@ -208,6 +234,24 @@ func _Discovery_Discover_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } +func _Discovery_Get_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).Get(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/Get", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).Get(ctx, req.(*GetRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Discovery_serviceDesc = grpc.ServiceDesc{ ServiceName: "discovery.Discovery", HandlerType: (*DiscoveryServer)(nil), @@ -220,6 +264,10 @@ var _Discovery_serviceDesc = grpc.ServiceDesc{ MethodName: "Discover", Handler: _Discovery_Discover_Handler, }, + { + MethodName: "Get", + Handler: _Discovery_Get_Handler, + }, }, Streams: []grpc.StreamDesc{}, } @@ -374,6 +422,45 @@ func (m *DiscoverRequest) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *GetRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *GetRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.ServiceName) > 0 { + data[i] = 0xa + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) + i += copy(data[i:], m.ServiceName) + } + if len(m.Id) > 0 { + for _, s := range m.Id { + data[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + return i, nil +} + func (m *DiscoverResponse) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -490,6 +577,22 @@ func (m *DiscoverRequest) Size() (n int) { return n } +func (m *GetRequest) Size() (n int) { + var l int + _ = l + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + if len(m.Id) > 0 { + for _, s := range m.Id { + l = len(s) + n += 1 + l + sovDiscovery(uint64(l)) + } + } + return n +} + func (m *DiscoverResponse) Size() (n int) { var l int _ = l @@ -949,6 +1052,114 @@ func (m *DiscoverRequest) Unmarshal(data []byte) error { } return nil } +func (m *GetRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ServiceName = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = append(m.Id, string(data[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDiscovery(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DiscoverResponse) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -1136,32 +1347,35 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 429 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x92, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xc7, 0xeb, 0x86, 0x56, 0xce, 0x24, 0x4a, 0xc3, 0x00, 0xc2, 0x0a, 0x52, 0x09, 0xbe, 0x50, - 0x0e, 0xb5, 0xa5, 0x94, 0x2b, 0x87, 0xf0, 0x29, 0x84, 0x5a, 0xa1, 0x55, 0x85, 0x38, 0x51, 0x6d, - 0xed, 0x51, 0xb2, 0x0a, 0xd9, 0x35, 0xde, 0x75, 0x90, 0x4f, 0xbc, 0x06, 0x8f, 0xc4, 0x91, 0x47, - 0x40, 0xf0, 0x0a, 0x3c, 0x00, 0xeb, 0xaf, 0xd8, 0xa2, 0x45, 0xe2, 0x30, 0xd2, 0xee, 0x7f, 0x7e, - 0xf3, 0xb9, 0x0b, 0x4f, 0x16, 0xc2, 0x2c, 0xb3, 0xcb, 0x20, 0x52, 0xeb, 0xf0, 0x7c, 0x49, 0xe7, - 0x4b, 0x21, 0x17, 0xfa, 0x8c, 0xcc, 0x67, 0x95, 0xae, 0x42, 0x63, 0x64, 0xc8, 0x13, 0x11, 0xc6, - 0x42, 0x47, 0x6a, 0x43, 0x69, 0xde, 0x9e, 0x82, 0x24, 0x55, 0x46, 0x61, 0x7f, 0x2b, 0x4c, 0x8e, - 0xff, 0x27, 0x93, 0xb5, 0x2a, 0xd2, 0xff, 0x00, 0xee, 0x29, 0x19, 0x1e, 0x73, 0xc3, 0xf1, 0x11, - 0xf4, 0x56, 0x94, 0x7b, 0xce, 0xd4, 0x39, 0x1a, 0xcd, 0xee, 0x06, 0x6d, 0x91, 0x86, 0x08, 0xde, - 0x50, 0xce, 0x0a, 0x06, 0x6f, 0xc3, 0xde, 0x86, 0x7f, 0xcc, 0xc8, 0xdb, 0xb5, 0xf0, 0x90, 0x55, - 0x17, 0xff, 0x26, 0xf4, 0x2c, 0x81, 0x00, 0xfb, 0x6f, 0xd9, 0x8b, 0x97, 0xaf, 0xdf, 0x8f, 0x77, - 0xfc, 0xdf, 0x0e, 0x0c, 0xe7, 0x52, 0xaa, 0x4c, 0x46, 0xb4, 0x26, 0x69, 0x70, 0x04, 0xbb, 0x22, - 0x2e, 0x6b, 0xf4, 0x99, 0x3d, 0x15, 0x99, 0x8c, 0x5a, 0x91, 0x2c, 0x33, 0xf5, 0x59, 0x75, 0xc1, - 0x29, 0x0c, 0x62, 0xd2, 0x51, 0x2a, 0x12, 0x23, 0x94, 0xf4, 0x7a, 0xa5, 0xaf, 0x2b, 0xe1, 0x03, - 0x18, 0x6a, 0x4a, 0x37, 0x22, 0xa2, 0x0b, 0xc9, 0xd7, 0xe4, 0xdd, 0xa8, 0x90, 0x5a, 0x3b, 0xb3, - 0x12, 0x3e, 0x84, 0x83, 0x06, 0xb1, 0x63, 0xe8, 0x22, 0xd1, 0x5e, 0x49, 0x8d, 0x6a, 0xf9, 0x5d, - 0xa5, 0xe2, 0x7d, 0x18, 0x48, 0x32, 0x17, 0x3c, 0x8e, 0x53, 0xd2, 0xda, 0x1b, 0x94, 0x10, 0x58, - 0x69, 0x5e, 0x29, 0x18, 0x82, 0xbb, 0xae, 0x77, 0xe0, 0xdd, 0x99, 0xf6, 0x8e, 0x06, 0xb3, 0x5b, - 0xd7, 0xac, 0x87, 0x6d, 0x21, 0xff, 0x31, 0x1c, 0x3c, 0xaf, 0xfd, 0x8c, 0x3e, 0x65, 0xa4, 0xcd, - 0x95, 0x86, 0x9d, 0x2b, 0x0d, 0xfb, 0xaf, 0x60, 0xdc, 0x46, 0xe9, 0x44, 0x49, 0x4d, 0x78, 0x02, - 0x6e, 0x8d, 0x68, 0x1b, 0x52, 0x94, 0xee, 0xbe, 0x4c, 0x77, 0xb5, 0x6c, 0x0b, 0xce, 0xbe, 0x40, - 0xbf, 0x49, 0x94, 0xe3, 0x31, 0xb8, 0x0d, 0x86, 0xff, 0x8a, 0x9d, 0xb8, 0x41, 0xf1, 0x27, 0xe6, - 0xd1, 0x0a, 0x9f, 0x81, 0xdb, 0xc4, 0xe2, 0xa4, 0x83, 0xff, 0x35, 0xcf, 0xe4, 0xde, 0xb5, 0xbe, - 0xaa, 0xeb, 0x19, 0xb6, 0x93, 0xe4, 0xa7, 0x5c, 0xf2, 0x05, 0xa5, 0x4f, 0xc7, 0xdf, 0x7e, 0x1e, - 0x3a, 0xdf, 0xad, 0xfd, 0xb0, 0xf6, 0xf5, 0xd7, 0xe1, 0xce, 0xe5, 0x7e, 0xf9, 0x07, 0x4f, 0xfe, - 0x04, 0x00, 0x00, 0xff, 0xff, 0xe1, 0x4a, 0x47, 0x36, 0xfe, 0x02, 0x00, 0x00, + // 470 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x93, 0x4f, 0x6e, 0xd3, 0x40, + 0x14, 0xc6, 0xeb, 0x98, 0x54, 0xf6, 0x4b, 0x94, 0x46, 0x0f, 0x2a, 0xac, 0x80, 0x4a, 0xf0, 0x86, + 0xb2, 0xa8, 0x2d, 0xa5, 0x6c, 0x58, 0x20, 0x14, 0xa0, 0x14, 0x84, 0x5a, 0x90, 0x15, 0x21, 0x76, + 0x95, 0x6b, 0x3f, 0x25, 0xa3, 0x90, 0x99, 0xe0, 0x99, 0x04, 0xf9, 0x26, 0x9c, 0x85, 0x13, 0xb0, + 0xe4, 0x08, 0x08, 0xae, 0xc0, 0x01, 0x18, 0xff, 0x8b, 0x2d, 0x5a, 0x50, 0x16, 0x23, 0xcd, 0x7c, + 0xf3, 0x7b, 0xdf, 0x7b, 0xf9, 0x26, 0x86, 0x27, 0x53, 0xa6, 0x66, 0xab, 0x4b, 0x2f, 0x12, 0x0b, + 0x7f, 0x32, 0xa3, 0xc9, 0x8c, 0xf1, 0xa9, 0x3c, 0x27, 0xf5, 0x59, 0x24, 0x73, 0x5f, 0x29, 0xee, + 0x87, 0x4b, 0xe6, 0xc7, 0x4c, 0x46, 0x62, 0x4d, 0x49, 0x5a, 0xef, 0xbc, 0x65, 0x22, 0x94, 0x40, + 0x7b, 0x23, 0x0c, 0x8e, 0xb6, 0x71, 0xd2, 0xab, 0xa8, 0x74, 0x19, 0x58, 0x67, 0xa4, 0xc2, 0x38, + 0x54, 0x21, 0x3e, 0x04, 0x73, 0x4e, 0xa9, 0x63, 0x0c, 0x8d, 0xc3, 0xde, 0xe8, 0xb6, 0x57, 0x37, + 0xa9, 0x08, 0xef, 0x0d, 0xa5, 0x41, 0xc6, 0xe0, 0x2d, 0x68, 0xaf, 0xc3, 0x8f, 0x2b, 0x72, 0x5a, + 0x1a, 0xee, 0x06, 0xc5, 0xc1, 0xbd, 0x0b, 0xa6, 0x26, 0xd0, 0x86, 0xf6, 0xdb, 0xc9, 0xab, 0x93, + 0xa0, 0xbf, 0x83, 0x00, 0xbb, 0xef, 0x82, 0x93, 0x97, 0xaf, 0x3f, 0xf4, 0x0d, 0xf7, 0xb7, 0x01, + 0xdd, 0x31, 0xe7, 0x62, 0xc5, 0x23, 0x5a, 0x10, 0x57, 0xd8, 0x83, 0x16, 0x8b, 0xf3, 0x76, 0x76, + 0xa0, 0x77, 0x99, 0xa9, 0x12, 0x73, 0xe2, 0xb9, 0xa9, 0x1d, 0x14, 0x07, 0x1c, 0x42, 0x27, 0x26, + 0x19, 0x25, 0x6c, 0xa9, 0x98, 0xe0, 0x8e, 0x99, 0xdf, 0x35, 0x25, 0xbc, 0x0f, 0x5d, 0x49, 0xc9, + 0x9a, 0x45, 0x74, 0xc1, 0xc3, 0x05, 0x39, 0x37, 0x0a, 0xa4, 0xd4, 0xce, 0xb5, 0x84, 0x0f, 0x60, + 0xaf, 0x42, 0xf4, 0x2f, 0x92, 0x99, 0x51, 0x3b, 0xa7, 0x7a, 0xa5, 0xfc, 0xbe, 0x50, 0xf1, 0x1e, + 0x74, 0x38, 0xa9, 0x8b, 0x30, 0x8e, 0x13, 0x92, 0xd2, 0xe9, 0xe4, 0x10, 0x68, 0x69, 0x5c, 0x28, + 0xe8, 0x83, 0xb5, 0x28, 0xe3, 0x70, 0xf6, 0x87, 0xe6, 0x61, 0x67, 0x74, 0xf3, 0x9a, 0xa4, 0x82, + 0x0d, 0xe4, 0x3e, 0x82, 0xbd, 0x17, 0xe5, 0x7d, 0x40, 0x9f, 0x56, 0x24, 0xd5, 0x95, 0x81, 0x8d, + 0x2b, 0x03, 0xbb, 0x4f, 0x01, 0x4e, 0x49, 0x6d, 0x5f, 0x50, 0x86, 0xd9, 0xd2, 0x13, 0xe5, 0x61, + 0xba, 0xa7, 0xd0, 0xaf, 0xdb, 0xca, 0xa5, 0xe0, 0x92, 0xf0, 0x18, 0xac, 0xb2, 0x44, 0x6a, 0x8b, + 0x6c, 0xf6, 0xe6, 0x2b, 0x37, 0xdf, 0x26, 0xd8, 0x80, 0xa3, 0xaf, 0x06, 0xd8, 0x95, 0x53, 0x8a, + 0x47, 0x60, 0x55, 0x1c, 0xfe, 0xab, 0x78, 0x60, 0x79, 0xd9, 0x1f, 0x6c, 0x1c, 0xcd, 0xf1, 0x39, + 0x58, 0x55, 0x2d, 0x0e, 0x1a, 0xf8, 0x5f, 0x89, 0x0c, 0xee, 0x5c, 0x7b, 0x57, 0x8e, 0xfd, 0x18, + 0x4c, 0x9d, 0x05, 0xee, 0x37, 0x98, 0x3a, 0x9b, 0xff, 0x96, 0x8e, 0xb0, 0x4e, 0x21, 0x3d, 0x0b, + 0x79, 0x38, 0xa5, 0xe4, 0x59, 0xff, 0xdb, 0xcf, 0x03, 0xe3, 0xbb, 0x5e, 0x3f, 0xf4, 0xfa, 0xf2, + 0xeb, 0x60, 0xe7, 0x72, 0x37, 0xff, 0x16, 0x8e, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x15, 0x47, + 0xb9, 0x40, 0x86, 0x03, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 63db18532..11fbb07b3 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -6,7 +6,8 @@ package discovery; message Metadata { enum Key { - PREFIX = 0; + OTHER = 0; + PREFIX = 1; } Key key = 1; bytes value = 2; @@ -26,6 +27,11 @@ message DiscoverRequest { string service_name = 1; } +message GetRequest { + string service_name = 1; + repeated string id = 2; +} + message DiscoverResponse { repeated Announcement services = 1; } @@ -33,6 +39,7 @@ message DiscoverResponse { service Discovery { rpc Announce(Announcement) returns (api.Ack); rpc Discover(DiscoverRequest) returns (DiscoverResponse); + rpc Get(GetRequest) returns (DiscoverResponse); } // The DiscoveryManager service provides configuration and monitoring functionality diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index ae4f2096b..563c5e379 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -4,15 +4,18 @@ package discovery import ( "errors" "fmt" + "strings" "sync" + "gopkg.in/redis.v3" + pb "github.com/TheThingsNetwork/ttn/api/discovery" ) // Discovery specifies the interface for the TTN Service Discovery component type Discovery interface { Announce(announcement *pb.Announcement) error - Discover(serviceName string) ([]*pb.Announcement, error) + Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) } // discovery is a reference implementation for a TTN Service Discovery component. @@ -48,14 +51,14 @@ func (d *discovery) Announce(announcement *pb.Announcement) error { return nil } -func (d *discovery) Discover(serviceName string) ([]*pb.Announcement, error) { +func (d *discovery) Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) { d.RLock() defer d.RUnlock() // Get the list services, ok := d.services[serviceName] if !ok { - return nil, fmt.Errorf("Service %s does not exist", serviceName) + return []*pb.Announcement{}, nil } // Traverse the list @@ -63,7 +66,99 @@ func (d *discovery) Discover(serviceName string) ([]*pb.Announcement, error) { for _, service := range services { serviceCopy := *service serviceCopy.Token = "" - announcements = append(announcements, &serviceCopy) + if len(ids) == 0 { + announcements = append(announcements, &serviceCopy) + } else { + for _, id := range ids { + if service.Id == id { + announcements = append(announcements, &serviceCopy) + break + } + } + } } return announcements, nil } + +// NewRedisDiscovery creates a new Redis-based discovery service +func NewRedisDiscovery(client *redis.Client) Discovery { + return &redisDiscovery{ + client: client, + } +} + +const redisAnnouncementPrefix = "service" + +type redisDiscovery struct { + client *redis.Client +} + +func (d *redisDiscovery) Announce(announcement *pb.Announcement) error { + key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, announcement.ServiceName, announcement.Id) + + if token, err := d.client.HGet(key, "token").Result(); err == nil && token != announcement.Token { + return errors.New("ttn/core: Invalid token") + } + + dmap, err := announcement.ToStringStringMap(pb.AnnouncementProperties...) + if err != nil { + return err + } + + return d.client.HMSetMap(key, dmap).Err() +} + +func (d *redisDiscovery) Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) { + announcements := []*pb.Announcement{} + if len(ids) == 0 { + keys, err := d.client.Keys(fmt.Sprintf("%s:%s:*", redisAnnouncementPrefix, serviceName)).Result() + if err != nil { + return nil, err + } + for _, key := range keys { + if parts := strings.Split(key, ":"); len(parts) == 3 { + ids = append(ids, parts[2]) + } + } + } + + var wg sync.WaitGroup + wg.Add(len(ids)) + results := make(chan *pb.Announcement) + go func() { + wg.Wait() + close(results) + }() + for _, id := range ids { + go func(id string) { + res, err := d.Get(serviceName, id) + if err == nil && res != nil { + results <- res + } + wg.Done() + }(id) + } + + for res := range results { + announcements = append(announcements, res) + } + + return announcements, nil +} + +func (d *redisDiscovery) Get(serviceName string, id string) (*pb.Announcement, error) { + key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, id) + res, err := d.client.HGetAllMap(key).Result() + if err != nil { + return nil, err + } else if len(res) == 0 { + return nil, redis.Nil // This might be a bug in redis package + } + announcement := &pb.Announcement{} + err = announcement.FromStringStringMap(res) + if err != nil { + return nil, err + } + announcement.Token = "" + return announcement, nil +} diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 6e369bf53..db3a88f75 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -3,18 +3,85 @@ package discovery import ( "testing" + "gopkg.in/redis.v3" + pb "github.com/TheThingsNetwork/ttn/api/discovery" . "github.com/smartystreets/assertions" ) +func getRedisClient(db int64) *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: db, // use default DB + }) +} + +func TestDiscoveryAnnounce(t *testing.T) { + a := New(t) + + localDiscovery := &discovery{ + services: map[string]map[string]*pb.Announcement{}, + } + + client := getRedisClient(1) + redisDiscovery := NewRedisDiscovery(client) + defer func() { + client.Del("service:broker:broker1.1") + client.Del("service:broker:broker1.2") + }() + client.FlushAll() + + discoveries := map[string]Discovery{ + "local": localDiscovery, + "redis": redisDiscovery, + } + + for name, d := range discoveries { + broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", Token: "abcd", NetAddress: "current address"} + broker1aNoToken := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "attacker address"} + broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", Token: "abcd", NetAddress: "updated address"} + broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker1.2", NetAddress: "other address"} + + t.Logf("Testing %s\n", name) + + err := d.Announce(broker1a) + a.So(err, ShouldBeNil) + + err = d.Announce(broker1aNoToken) + a.So(err, ShouldNotBeNil) + + services, err := d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "current address") + + err = d.Announce(broker1b) + a.So(err, ShouldBeNil) + + services, err = d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "updated address") + + err = d.Announce(broker2) + a.So(err, ShouldBeNil) + + services, err = d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) + } + +} + func TestDiscoveryDiscover(t *testing.T) { a := New(t) - router := &pb.Announcement{Id: "router", Token: "abcd"} - broker1 := &pb.Announcement{Id: "broker1"} - broker2 := &pb.Announcement{Id: "broker2"} + router := &pb.Announcement{ServiceName: "router", Id: "router2.0", Token: "abcd"} + broker1 := &pb.Announcement{ServiceName: "broker", Id: "broker2.1"} + broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker2.2"} - d := &discovery{ + localDiscovery := &discovery{ services: map[string]map[string]*pb.Announcement{ "router": map[string]*pb.Announcement{ "router": router, @@ -26,56 +93,41 @@ func TestDiscoveryDiscover(t *testing.T) { }, } - _, err := d.Discover("random") - a.So(err, ShouldNotBeNil) - - services, err := d.Discover("router") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 1) - a.So(services[0].Id, ShouldEqual, router.Id) - a.So(services[0].Token, ShouldBeEmpty) - - services, err = d.Discover("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 2) -} - -func TestDiscoveryAnnounce(t *testing.T) { - a := New(t) - - broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1", Token: "abcd", NetAddress: "old address"} - broker1aNoToken := &pb.Announcement{ServiceName: "broker", Id: "broker1", NetAddress: "new address"} - broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1", Token: "abcd", NetAddress: "new address"} - broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker2", NetAddress: "other address"} - - d := &discovery{ - services: map[string]map[string]*pb.Announcement{}, + client := getRedisClient(2) + redisDiscovery := NewRedisDiscovery(client) + defer func() { + client.Del("service:router:router2.0") + client.Del("service:broker:broker2.1") + client.Del("service:broker:broker2.2") + }() + client.FlushAll() + + // This depends on the previous test to pass + redisDiscovery.Announce(router) + redisDiscovery.Announce(broker1) + redisDiscovery.Announce(broker2) + + discoveries := map[string]Discovery{ + "local": localDiscovery, + "redis": redisDiscovery, } - err := d.Announce(broker1a) - a.So(err, ShouldBeNil) + for name, d := range discoveries { + t.Logf("Testing %s\n", name) - err = d.Announce(broker1aNoToken) - a.So(err, ShouldNotBeNil) + services, err := d.Discover("random") + a.So(err, ShouldBeNil) + a.So(services, ShouldBeEmpty) - services, err := d.Discover("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 1) - a.So(services[0].NetAddress, ShouldEqual, "old address") + services, err = d.Discover("router") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].Id, ShouldEqual, router.Id) + a.So(services[0].Token, ShouldBeEmpty) - err = d.Announce(broker1b) - a.So(err, ShouldBeNil) - - services, err = d.Discover("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 1) - a.So(services[0].NetAddress, ShouldEqual, "new address") - - err = d.Announce(broker2) - a.So(err, ShouldBeNil) - - services, err = d.Discover("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 2) + services, err = d.Discover("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) + } } diff --git a/core/discovery/server.go b/core/discovery/server.go index 1288106f9..e4c4c45cb 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -29,6 +29,16 @@ func (d *discoveryServer) Discover(ctx context.Context, req *pb.DiscoverRequest) }, nil } +func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.DiscoverResponse, error) { + services, err := d.discovery.Discover(req.ServiceName, req.Id...) + if err != nil { + return nil, err + } + return &pb.DiscoverResponse{ + Services: services, + }, nil +} + func (d *discovery) RegisterDiscoveryServer(s *grpc.Server) { server := &discoveryServer{d} pb.RegisterDiscoveryServer(s, server) diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index a86b10d30..86f47baa9 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -46,6 +46,7 @@ func buildMockDiscoveryServer(port uint) (*mockDiscoveryServer, *grpc.Server) { type mockDiscoveryServer struct { announce uint discover uint + get uint } func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { @@ -60,3 +61,10 @@ func (d *mockDiscoveryServer) Discover(ctx context.Context, req *pb.DiscoverRequ Services: []*pb.Announcement{}, }, nil } +func (d *mockDiscoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.DiscoverResponse, error) { + d.get++ + <-time.After(5 * time.Millisecond) + return &pb.DiscoverResponse{ + Services: []*pb.Announcement{}, + }, nil +} From ad26b1d1d851a9887f0fa5c1c9cc820931d01917 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 21 May 2016 12:51:33 +0200 Subject: [PATCH 1451/2266] [ttnctl] Update ParseHEX --- ttnctl/cmd/applicationsPayloadFunctions.go | 2 +- ttnctl/cmd/downlink.go | 2 +- ttnctl/cmd/uplink.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go index e87deccc0..b113496f6 100644 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ b/ttnctl/cmd/applicationsPayloadFunctions.go @@ -144,7 +144,7 @@ the Handler, as well as a payload to test them on and returns the fields and val ctx.Fatal("No authentication found. Please login") } - payload, err := types.ParseHEX(args[0], len(args[0])) + payload, err := types.ParseHEX(args[0], len(args[0])/2) if err != nil { ctx.WithError(err).Fatal("Invalid payload") } diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index f08cbc144..e9fe64d3c 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -39,7 +39,7 @@ formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") payload = []byte(args[1]) } else { - payload, err = types.ParseHEX(args[1], len(args[1])) + payload, err = types.ParseHEX(args[1], len(args[1])/2) if err != nil { ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index b2669a767..70f1e7fd7 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -72,7 +72,7 @@ var uplinkCmd = &cobra.Command{ ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} } else { - payload, err := types.ParseHEX(args[4], len(args[4])) + payload, err := types.ParseHEX(args[4], len(args[4])/2) if err != nil { ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") } From 304e9d665120d6e9ae4f5d11a87bcbb99a2703b7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 23 May 2016 10:07:00 +0200 Subject: [PATCH 1452/2266] Add RegisterRPC for redisDiscovery --- core/discovery/server.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/discovery/server.go b/core/discovery/server.go index e4c4c45cb..0f5c597c7 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -39,7 +39,14 @@ func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Disc }, nil } -func (d *discovery) RegisterDiscoveryServer(s *grpc.Server) { +// RegisterRPC registers the local discovery with a gRPC server +func (d *discovery) RegisterRPC(s *grpc.Server) { + server := &discoveryServer{d} + pb.RegisterDiscoveryServer(s, server) +} + +// RegisterRPC registers the Redis-based discovery with a gRPC server +func (d *redisDiscovery) RegisterRPC(s *grpc.Server) { server := &discoveryServer{d} pb.RegisterDiscoveryServer(s, server) } From 3daf934136ff51673a7c91a5d07543e831898a9b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 14:36:49 +0200 Subject: [PATCH 1453/2266] work with FCnt overflow in NetworkServer --- core/networkserver/device/store.go | 11 ++++++++++- core/networkserver/networkserver.go | 7 ++++++- core/networkserver/networkserver_test.go | 19 ++++++++++++++++++- 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index e57dc1ea5..da18d48c0 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -83,7 +83,16 @@ func (s *deviceStore) Set(new *Device, fields ...string) error { if !new.DevAddr.IsEmpty() { if devices, ok := s.byAddress[new.DevAddr]; ok { - s.byAddress[new.DevAddr] = append(devices, new) + var exists bool + for _, candidate := range devices { + if candidate.AppEUI == new.AppEUI && candidate.DevEUI == new.DevEUI { + exists = true + break + } + } + if !exists { + s.byAddress[new.DevAddr] = append(devices, new) + } } else { s.byAddress[new.DevAddr] = []*Device{new} } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index ae1306e50..7c6445f36 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -27,6 +27,8 @@ type networkServer struct { devices device.Store } +const maxFCntGap = 16384 + func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { devices, err := n.devices.GetWithAddress(*req.DevAddr) if err != nil { @@ -54,7 +56,10 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes } if device.Options.Uses32BitFCnt { ms := device.FCntUp / (1 << 16) - if device.FCntUp <= req.FCnt+(ms*(1<<16)) { + if device.FCntUp+maxFCntGap >= (ms+1)*(1<<16) && device.FCntUp <= req.FCnt+((ms+1)*(1<<16)) { + res.Results = append(res.Results, dev) + continue + } else if device.FCntUp <= req.FCnt+(ms*(1<<16)) { res.Results = append(res.Results, dev) continue } diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 5094cea6b..9c71c3c23 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -77,7 +77,7 @@ func TestHandleGetDevices(t *testing.T) { a.So(err, ShouldBeNil) a.So(res.Results, ShouldHaveLength, 1) - // 32 Bit Frame Counter + // 32 Bit Frame Counter (A) ns.devices.Set(&device.Device{ DevAddr: types.DevAddr{2, 2, 3, 4}, AppEUI: types.AppEUI{2, 2, 3, 4, 5, 6, 7, 8}, @@ -94,6 +94,23 @@ func TestHandleGetDevices(t *testing.T) { a.So(err, ShouldBeNil) a.So(res.Results, ShouldHaveLength, 1) + // 32 Bit Frame Counter (B) + ns.devices.Set(&device.Device{ + DevAddr: types.DevAddr{2, 2, 3, 4}, + AppEUI: types.AppEUI{2, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: types.DevEUI{2, 2, 3, 4, 5, 6, 7, 8}, + FCntUp: (2 << 16) - 1, + Options: device.Options{ + Uses32BitFCnt: true, + }, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &types.DevAddr{2, 2, 3, 4}, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + } func TestHandlePrepareActivation(t *testing.T) { From 68817fa4a28ede7212f03e48cfd08d83f2dadec1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 15:41:45 +0200 Subject: [PATCH 1454/2266] Work around go vet errors --- core/networkserver/networkserver_test.go | 112 ++++++++++++++--------- 1 file changed, 69 insertions(+), 43 deletions(-) diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 9c71c3c23..e80009439 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -15,6 +15,16 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) +func getDevAddr(bytes ...byte) (addr types.DevAddr) { + copy(addr[:], bytes[:4]) + return +} + +func getEUI(bytes ...byte) (eui types.EUI64) { + copy(eui[:], bytes[:8]) + return +} + func TestHandleGetDevices(t *testing.T) { a := New(t) @@ -23,8 +33,9 @@ func TestHandleGetDevices(t *testing.T) { } // No Devices + devAddr1 := getDevAddr(1, 2, 3, 4) res, err := ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{1, 2, 3, 4}, + DevAddr: &devAddr1, FCnt: 5, }) a.So(err, ShouldBeNil) @@ -32,21 +43,22 @@ func TestHandleGetDevices(t *testing.T) { // Matching Device ns.devices.Set(&device.Device{ - DevAddr: types.DevAddr{1, 2, 3, 4}, - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: getDevAddr(1, 2, 3, 4), + AppEUI: types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), FCntUp: 5, }) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{1, 2, 3, 4}, + DevAddr: &devAddr1, FCnt: 5, }) a.So(err, ShouldBeNil) a.So(res.Results, ShouldHaveLength, 1) // Non-Matching DevAddr + devAddr2 := getDevAddr(5, 6, 7, 8) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{5, 6, 7, 8}, + DevAddr: &devAddr2, FCnt: 5, }) a.So(err, ShouldBeNil) @@ -54,7 +66,7 @@ func TestHandleGetDevices(t *testing.T) { // Non-Matching FCnt res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{1, 2, 3, 4}, + DevAddr: &devAddr1, FCnt: 4, }) a.So(err, ShouldBeNil) @@ -62,33 +74,34 @@ func TestHandleGetDevices(t *testing.T) { // Non-Matching FCnt, but FCnt Check Disabled ns.devices.Set(&device.Device{ - DevAddr: types.DevAddr{5, 6, 7, 8}, - AppEUI: types.AppEUI{5, 6, 7, 8, 1, 2, 3, 4}, - DevEUI: types.DevEUI{5, 6, 7, 8, 1, 2, 3, 4}, + DevAddr: getDevAddr(5, 6, 7, 8), + AppEUI: types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), + DevEUI: types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), FCntUp: 5, Options: device.Options{ DisableFCntCheck: true, }, }) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{5, 6, 7, 8}, + DevAddr: &devAddr2, FCnt: 4, }) a.So(err, ShouldBeNil) a.So(res.Results, ShouldHaveLength, 1) // 32 Bit Frame Counter (A) + devAddr3 := getDevAddr(2, 2, 3, 4) ns.devices.Set(&device.Device{ - DevAddr: types.DevAddr{2, 2, 3, 4}, - AppEUI: types.AppEUI{2, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: types.DevEUI{2, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: getDevAddr(2, 2, 3, 4), + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), FCntUp: 5 + (2 << 16), Options: device.Options{ Uses32BitFCnt: true, }, }) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{2, 2, 3, 4}, + DevAddr: &devAddr3, FCnt: 5, }) a.So(err, ShouldBeNil) @@ -96,16 +109,16 @@ func TestHandleGetDevices(t *testing.T) { // 32 Bit Frame Counter (B) ns.devices.Set(&device.Device{ - DevAddr: types.DevAddr{2, 2, 3, 4}, - AppEUI: types.AppEUI{2, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: types.DevEUI{2, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: devAddr3, + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), FCntUp: (2 << 16) - 1, Options: device.Options{ Uses32BitFCnt: true, }, }) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &types.DevAddr{2, 2, 3, 4}, + DevAddr: &devAddr3, FCnt: 5, }) a.So(err, ShouldBeNil) @@ -137,13 +150,18 @@ func TestHandleActivate(t *testing.T) { }) a.So(err, ShouldNotBeNil) + devAddr := getDevAddr(0, 0, 3, 1) + var nwkSKey types.NwkSKey + copy(nwkSKey[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1}) + appEUI := types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) + devEUI := types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ - AppEui: &types.AppEUI{0, 0, 0, 0, 0, 0, 3, 1}, - DevEui: &types.DevEUI{0, 0, 0, 0, 0, 0, 3, 1}, - DevAddr: &types.DevAddr{0, 0, 3, 1}, - NwkSKey: &types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1}, + AppEui: &appEUI, + DevEui: &devEUI, + DevAddr: &devAddr, + NwkSKey: &nwkSKey, }, }}, }) @@ -156,25 +174,29 @@ func TestHandleUplink(t *testing.T) { devices: device.NewDeviceStore(), } + appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devAddr := getDevAddr(1, 2, 3, 4) + // Device Not Found message := &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &appEUI, + DevEui: &devEUI, Payload: []byte{}, } _, err := ns.HandleUplink(message) a.So(err, ShouldNotBeNil) ns.devices.Set(&device.Device{ - DevAddr: types.DevAddr{1, 2, 3, 4}, - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: devAddr, + AppEUI: appEUI, + DevEUI: devEUI, }) // Invalid Payload message = &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &appEUI, + DevEui: &devEUI, Payload: []byte{}, } _, err = ns.HandleUplink(message) @@ -196,8 +218,8 @@ func TestHandleUplink(t *testing.T) { // Valid Uplink message = &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &appEUI, + DevEui: &devEUI, Payload: bytes, } res, err := ns.HandleUplink(message) @@ -205,7 +227,7 @@ func TestHandleUplink(t *testing.T) { a.So(res.ResponseTemplate, ShouldNotBeNil) // Frame Counter should have been updated - dev, _ := ns.devices.Get(types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}) + dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntUp, ShouldEqual, 1) } @@ -215,25 +237,29 @@ func TestHandleDownlink(t *testing.T) { devices: device.NewDeviceStore(), } + appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devAddr := getDevAddr(1, 2, 3, 4) + // Device Not Found message := &pb_broker.DownlinkMessage{ - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &appEUI, + DevEui: &devEUI, Payload: []byte{}, } _, err := ns.HandleDownlink(message) a.So(err, ShouldNotBeNil) ns.devices.Set(&device.Device{ - DevAddr: types.DevAddr{1, 2, 3, 4}, - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: devAddr, + AppEUI: appEUI, + DevEUI: devEUI, }) // Invalid Payload message = &pb_broker.DownlinkMessage{ - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &appEUI, + DevEui: &devEUI, Payload: []byte{}, } _, err = ns.HandleDownlink(message) @@ -257,8 +283,8 @@ func TestHandleDownlink(t *testing.T) { bytes, _ := phy.MarshalBinary() message = &pb_broker.DownlinkMessage{ - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &appEUI, + DevEui: &devEUI, Payload: bytes, } res, err := ns.HandleDownlink(message) @@ -272,7 +298,7 @@ func TestHandleDownlink(t *testing.T) { a.So(macPayload.FHDR.FCnt, ShouldEqual, 0) // The first Frame counter is zero a.So(phyPayload.MIC, ShouldNotEqual, [4]byte{0, 0, 0, 0}) // MIC should be set, we'll check it with actual examples in the integration test - dev, _ := ns.devices.Get(types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}) + dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntDown, ShouldEqual, 1) } From d0fe37f3f4aa443ee5f88bf622b3321031bfb124 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 15:50:39 +0200 Subject: [PATCH 1455/2266] Fix discovery server test --- core/discovery/server_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index 86f47baa9..a7879b096 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -25,7 +25,7 @@ func buildTestDiscoveryServer(port uint) (*discovery, *grpc.Server) { } d := &discovery{} s := grpc.NewServer() - d.RegisterDiscoveryServer(s) + d.RegisterRPC(s) go s.Serve(lis) return d, s From 8c3e8b4dd9f9528d2ad2b6ac50ed567c1a645cee Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 17:29:48 +0200 Subject: [PATCH 1456/2266] [router] More efficient gateway and broker lookup Most of the time ;) --- core/router/router.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/core/router/router.go b/core/router/router.go index 65d04d5c2..ec8beaf5e 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -50,6 +50,14 @@ type router struct { // getGateway gets or creates a Gateway func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { + // We're going to be optimistic and guess that the gateway is already active + r.gatewaysLock.RLock() + gtw, ok := r.gateways[eui] + r.gatewaysLock.RUnlock() + if ok { + return gtw + } + // If it doesn't we still have to lock r.gatewaysLock.Lock() defer r.gatewaysLock.Unlock() if _, ok := r.gateways[eui]; !ok { @@ -61,6 +69,14 @@ func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { // getBroker gets or creates a broker association and returns the broker // the first time it also starts a goroutine that receives downlink from the broker func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { + // We're going to be optimistic and guess that the broker is already active + r.brokersLock.RLock() + brk, ok := r.brokers[req.NetAddress] + r.brokersLock.RUnlock() + if ok { + return brk, nil + } + // If it doesn't we still have to lock r.brokersLock.Lock() defer r.brokersLock.Unlock() if _, ok := r.brokers[req.NetAddress]; !ok { From 8a5d951e1d7cc3822358085875455d27c656f20d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 19:10:16 +0200 Subject: [PATCH 1457/2266] Move 32-bit FCnt conversion to separate component --- core/fcnt/fcnt.go | 14 ++++++++++++++ core/fcnt/fcnt_test.go | 27 +++++++++++++++++++++++++++ core/networkserver/networkserver.go | 17 +++++------------ 3 files changed, 46 insertions(+), 12 deletions(-) create mode 100644 core/fcnt/fcnt.go create mode 100644 core/fcnt/fcnt_test.go diff --git a/core/fcnt/fcnt.go b/core/fcnt/fcnt.go new file mode 100644 index 000000000..aba1dd2cf --- /dev/null +++ b/core/fcnt/fcnt.go @@ -0,0 +1,14 @@ +package fcnt + +const maxUint16 = (1 << 16) + +// GetFull calculates the full 32-bit frame counter +func GetFull(full uint32, lsb uint16) uint32 { + if int(lsb)-int(full) > 0 { + return uint32(lsb) + } + if uint16(full%maxUint16) <= lsb { + return uint32(lsb) + (full/maxUint16)*maxUint16 + } + return uint32(lsb) + ((full/maxUint16)+1)*maxUint16 +} diff --git a/core/fcnt/fcnt_test.go b/core/fcnt/fcnt_test.go new file mode 100644 index 000000000..c9193ad9a --- /dev/null +++ b/core/fcnt/fcnt_test.go @@ -0,0 +1,27 @@ +package fcnt + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestGetFullFCnt(t *testing.T) { + a := New(t) + + a.So(GetFull(0, 0), ShouldEqual, 0) + a.So(GetFull(0, 1), ShouldEqual, 1) + a.So(GetFull(0, 65535), ShouldEqual, 65535) + + a.So(GetFull(2000, 0), ShouldEqual, 65536) + a.So(GetFull(2000, 1), ShouldEqual, 65537) + + a.So(GetFull(65536, 0), ShouldEqual, 65536) + a.So(GetFull(65536, 1), ShouldEqual, 65537) + + a.So(GetFull(524287, 0), ShouldEqual, 524288) + a.So(GetFull(524287, 1), ShouldEqual, 524289) + + a.So(GetFull(524288, 0), ShouldEqual, 524288) + a.So(GetFull(524288, 1), ShouldEqual, 524289) +} diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 7c6445f36..72bb0e9fa 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -8,6 +8,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" @@ -27,8 +28,6 @@ type networkServer struct { devices device.Store } -const maxFCntGap = 16384 - func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { devices, err := n.devices.GetWithAddress(*req.DevAddr) if err != nil { @@ -54,16 +53,10 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes res.Results = append(res.Results, dev) continue } - if device.Options.Uses32BitFCnt { - ms := device.FCntUp / (1 << 16) - if device.FCntUp+maxFCntGap >= (ms+1)*(1<<16) && device.FCntUp <= req.FCnt+((ms+1)*(1<<16)) { - res.Results = append(res.Results, dev) - continue - } else if device.FCntUp <= req.FCnt+(ms*(1<<16)) { - res.Results = append(res.Results, dev) - continue - } - } else if device.FCntUp <= req.FCnt { + if device.FCntUp <= req.FCnt { + res.Results = append(res.Results, dev) + continue + } else if device.Options.Uses32BitFCnt && device.FCntUp <= fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) { res.Results = append(res.Results, dev) continue } From 7dc1950b98f65ce16e2f24bb736a7cf6326e5d63 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 19:19:33 +0200 Subject: [PATCH 1458/2266] Simple locking in Broker Discovery --- core/discovery/broker_discovery.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 74756de08..fde175626 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -56,24 +56,24 @@ func (d *brokerDiscovery) refreshCache() error { func (d *brokerDiscovery) All() (announcements []*pb.Announcement, err error) { d.cacheLock.Lock() + defer d.cacheLock.Unlock() if time.Now().After(d.cacheValidUntil) { d.cacheValidUntil = time.Now().Add(10 * time.Second) go d.refreshCache() } announcements = d.cache - d.cacheLock.Unlock() return } func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) { d.cacheLock.Lock() + defer d.cacheLock.Unlock() + if time.Now().After(d.cacheValidUntil) { d.cacheValidUntil = time.Now().Add(10 * time.Second) go d.refreshCache() } - d.cacheLock.Unlock() - d.cacheLock.RLock() - defer d.cacheLock.RUnlock() + matches := []*pb.Announcement{} for _, service := range d.cache { for _, meta := range service.Metadata { @@ -83,5 +83,6 @@ func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, e } } } + return matches, nil } From f6cc845ec67358173a0c2e091359cb5f506a1fda Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 19:38:44 +0200 Subject: [PATCH 1459/2266] [ns] Add LastSeen to devices --- core/networkserver/device/device.go | 11 +++++++++++ core/networkserver/device/device_test.go | 3 +++ core/networkserver/networkserver.go | 2 ++ core/networkserver/networkserver_test.go | 2 ++ 4 files changed, 18 insertions(+) diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 297e85766..8e3e37e76 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -2,6 +2,7 @@ package device import ( "fmt" + "time" "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/core/types" @@ -21,6 +22,7 @@ type Device struct { NwkSKey types.NwkSKey FCntUp uint32 FCntDown uint32 + LastSeen time.Time Options Options Utilization Utilization } @@ -33,6 +35,7 @@ var DeviceProperties = []string{ "nwk_s_key", "f_cnt_up", "f_cnt_down", + "last_seen", "options", "utilization", } @@ -75,6 +78,8 @@ func (device *Device) formatProperty(property string) (formatted string, err err formatted = storage.FormatUint32(device.FCntUp) case "f_cnt_down": formatted = storage.FormatUint32(device.FCntDown) + case "last_seen": + formatted = device.LastSeen.Format(time.RFC3339Nano) case "options": // TODO case "utilization": @@ -126,6 +131,12 @@ func (device *Device) parseProperty(property string, value string) error { return err } device.FCntDown = val + case "last_seen": + val, err := time.Parse(time.RFC3339Nano, value) + if err != nil { + return err + } + device.LastSeen = val case "options": // TODO case "utilization": diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go index 0db32da6d..906c08e84 100644 --- a/core/networkserver/device/device_test.go +++ b/core/networkserver/device/device_test.go @@ -2,6 +2,7 @@ package device import ( "testing" + "time" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" @@ -15,6 +16,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { NwkSKey: types.NwkSKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, FCntUp: 42, FCntDown: 24, + LastSeen: time.Unix(0, 0).UTC(), }, map[string]string{ "dev_eui": "0102030405060708", "app_eui": "0807060504030201", @@ -22,6 +24,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { "nwk_s_key": "00010002000300040005000600070008", "f_cnt_up": "42", "f_cnt_down": "24", + "last_seen": "1970-01-01T00:00:00Z", } } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 72bb0e9fa..1a15c910c 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -2,6 +2,7 @@ package networkserver import ( "errors" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" @@ -126,6 +127,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag // Update FCntUp dev.FCntUp = macPayload.FHDR.FCnt + dev.LastSeen = time.Now().UTC() err = n.devices.Set(dev, "f_cnt_up") if err != nil { return nil, err diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index e80009439..9f835c781 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -2,6 +2,7 @@ package networkserver import ( "testing" + "time" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" @@ -229,6 +230,7 @@ func TestHandleUplink(t *testing.T) { // Frame Counter should have been updated dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntUp, ShouldEqual, 1) + a.So(time.Now().Sub(dev.LastSeen), ShouldBeLessThan, 1*time.Second) } func TestHandleDownlink(t *testing.T) { From 11160a82cece447d3b3944c8f0b03214991bffed Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 25 May 2016 19:58:56 +0200 Subject: [PATCH 1460/2266] Add lock to gateway status --- core/router/gateway/status.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index 24b5fa40c..c65e89690 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -2,6 +2,7 @@ package gateway import ( "fmt" + "sync" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/core/types" @@ -22,15 +23,20 @@ func NewStatusStore() StatusStore { } type statusStore struct { + sync.RWMutex lastStatus *pb_gateway.Status } func (s *statusStore) Update(status *pb_gateway.Status) error { + s.Lock() + defer s.Unlock() s.lastStatus = status return nil } func (s *statusStore) Get() (*pb_gateway.Status, error) { + s.RLock() + defer s.RUnlock() if s.lastStatus != nil { return s.lastStatus, nil } From ef4ae18b155149d3aa89f293c9a2afbff5141e92 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 12:52:25 +0200 Subject: [PATCH 1461/2266] Add DevEUI and AppEUI to ActivationMetadata --- core/networkserver/networkserver.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 1a15c910c..c161bfcb8 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -76,7 +76,10 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat // Build lorawan metadata if not present if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { activation.ActivationMetadata.Protocol = &pb_protocol.ActivationMetadata_Lorawan{ - Lorawan: &pb_lorawan.ActivationMetadata{}, + Lorawan: &pb_lorawan.ActivationMetadata{ + DevEui: activation.DevEui, + AppEui: activation.AppEui, + }, } } // Generate random DevAddr From a70c8f1f9ab088307db78a8d40ac3dfcceafe722 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 12:56:11 +0200 Subject: [PATCH 1462/2266] Handle LoRaWAN Join Request as Activation --- core/router/uplink.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/router/uplink.go b/core/router/uplink.go index 732de6fc1..ddb19e5de 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -22,6 +22,29 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess if err != nil { return err } + + switch phyPayload.MHDR.MType { + case lorawan.JoinRequest: + joinRequestPayload, ok := phyPayload.MACPayload.(*lorawan.JoinRequestPayload) + if !ok { + return errors.New("Join Request message does not contain a join payload.") + } + devEUI := types.DevEUI(joinRequestPayload.DevEUI) + appEUI := types.AppEUI(joinRequestPayload.AppEUI) + _, err := r.HandleActivation(gatewayEUI, &pb.DeviceActivationRequest{ + Payload: uplink.Payload, + DevEui: &devEUI, + AppEui: &appEUI, + ProtocolMetadata: uplink.ProtocolMetadata, + GatewayMetadata: uplink.GatewayMetadata, + }) + return err + case lorawan.UnconfirmedDataUp, lorawan.ConfirmedDataUp: + // Just continue handling uplink + default: + return errors.New("ttn/router: Unhandled message type") + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return errors.New("Uplink message does not contain a MAC payload.") From 3fa19e539125db29fea0124beb55b98e8ee9060f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 12:56:41 +0200 Subject: [PATCH 1463/2266] Forward AppEUI and DevEUI to Broker --- core/router/activation.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/router/activation.go b/core/router/activation.go index 1b5012530..b736ec349 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -45,6 +45,8 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De go func() { res, err := broker.client.Activate(context.Background(), &pb_broker.DeviceActivationRequest{ Payload: activation.Payload, + DevEui: activation.DevEui, + AppEui: activation.AppEui, ProtocolMetadata: activation.ProtocolMetadata, GatewayMetadata: activation.GatewayMetadata, DownlinkOptions: downlinkOptions, From 97174ddaad522e7d3f0962d288c9eadd7651d285 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 12:57:11 +0200 Subject: [PATCH 1464/2266] GatewayMetadata can be more than one --- api/broker/broker.pb.go | 156 ++++++++++++++++++++-------------------- api/broker/broker.proto | 2 +- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index e6be7e090..3e3ecfc34 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -241,7 +241,7 @@ type DeduplicatedDeviceActivationRequest struct { DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` ResponseTemplate *DeviceActivationResponse `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` } @@ -260,7 +260,7 @@ func (m *DeduplicatedDeviceActivationRequest) GetProtocolMetadata() *protocol.Rx return nil } -func (m *DeduplicatedDeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { +func (m *DeduplicatedDeviceActivationRequest) GetGatewayMetadata() []*gateway.RxMetadata { if m != nil { return m.GatewayMetadata } @@ -1339,17 +1339,19 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error } i += n23 } - if m.GatewayMetadata != nil { - data[i] = 0xb2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n24, err := m.GatewayMetadata.MarshalTo(data[i:]) - if err != nil { - return 0, err + if len(m.GatewayMetadata) > 0 { + for _, msg := range m.GatewayMetadata { + data[i] = 0xb2 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n } - i += n24 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1357,11 +1359,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n25, err := m.ActivationMetadata.MarshalTo(data[i:]) + n24, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n25 + i += n24 } if m.ResponseTemplate != nil { data[i] = 0xfa @@ -1369,11 +1371,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n26, err := m.ResponseTemplate.MarshalTo(data[i:]) + n25, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n25 } return i, nil } @@ -1532,31 +1534,31 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n27, err := m.Uplink.MarshalTo(data[i:]) + n26, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n27 + i += n26 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n28, err := m.UplinkUnique.MarshalTo(data[i:]) + n27, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n28 + i += n27 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n29, err := m.Downlink.MarshalTo(data[i:]) + n28, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n29 + i += n28 } if m.Activations != nil { data[i] = 0xaa @@ -1564,11 +1566,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n30, err := m.Activations.MarshalTo(data[i:]) + n29, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n30 + i += n29 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1576,11 +1578,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n31, err := m.ActivationsUnique.MarshalTo(data[i:]) + n30, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n31 + i += n30 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1588,11 +1590,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n32, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n31, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n32 + i += n31 } if m.Deduplication != nil { data[i] = 0xfa @@ -1600,11 +1602,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n33, err := m.Deduplication.MarshalTo(data[i:]) + n32, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n33 + i += n32 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1833,9 +1835,11 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { l = m.ProtocolMetadata.Size() n += 2 + l + sovBroker(uint64(l)) } - if m.GatewayMetadata != nil { - l = m.GatewayMetadata.Size() - n += 2 + l + sovBroker(uint64(l)) + if len(m.GatewayMetadata) > 0 { + for _, e := range m.GatewayMetadata { + l = e.Size() + n += 2 + l + sovBroker(uint64(l)) + } } if m.ActivationMetadata != nil { l = m.ActivationMetadata.Size() @@ -3400,10 +3404,8 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.GatewayMetadata == nil { - m.GatewayMetadata = &gateway.RxMetadata{} - } - if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + m.GatewayMetadata = append(m.GatewayMetadata, &gateway.RxMetadata{}) + if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4306,13 +4308,13 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1085 bytes of a gzipped FileDescriptorProto + // 1088 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0xe3, 0x44, 0x18, 0x26, 0xc9, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xf4, 0xcb, 0x0d, 0x55, 0x5b, 0x0c, 0x42, 0xe5, 0x63, 0x13, 0x36, 0xa8, 0xac, 0x2a, 0x04, 0xab, 0x74, 0xbb, 0x2a, 0x8b, 0x94, 0x65, 0xe5, 0x6d, 0x2f, 0x5c, 0x22, 0xc7, 0x9e, 0x26, 0xa3, 0xa6, 0xb6, 0xf1, 0x8c, 0xdb, 0xed, 0x8d, 0x1f, 0x80, 0xc4, 0x85, 0x03, 0x07, 0xc4, 0x7f, 0x41, 0x5c, 0x38, 0x72, 0xe6, 0x80, 0x10, 0x5c, 0xf8, 0x19, - 0x8c, 0xc7, 0x33, 0xfe, 0x68, 0xe3, 0x6d, 0x77, 0xe1, 0xc0, 0x4a, 0x3d, 0x44, 0xf1, 0xbc, 0x1f, + 0x8c, 0xc7, 0x33, 0xfe, 0x68, 0xe3, 0x6d, 0x77, 0x59, 0x0e, 0x48, 0x3d, 0x44, 0xf1, 0xbc, 0x1f, 0x8f, 0xc7, 0xcf, 0xfb, 0x3e, 0x33, 0x2f, 0xdc, 0x1b, 0x11, 0x36, 0x0e, 0x86, 0x6d, 0xcb, 0x3d, 0xe9, 0x1c, 0x8c, 0xf1, 0xc1, 0x98, 0x38, 0x23, 0xfa, 0x18, 0xb3, 0x33, 0xd7, 0x3f, 0xee, 0x30, 0xe6, 0x74, 0x4c, 0x8f, 0x74, 0x86, 0xbe, 0x7b, 0x8c, 0x7d, 0xf9, 0xd7, 0xf6, 0x7c, 0x97, 0xb9, @@ -4337,42 +4339,42 @@ var fileDescriptorBroker = []byte{ 0x1f, 0x8b, 0x09, 0xa5, 0xc6, 0xb3, 0xbe, 0xf4, 0x19, 0x4d, 0x65, 0x54, 0x16, 0xf4, 0x29, 0x34, 0x15, 0x9f, 0x31, 0xc2, 0xb2, 0x40, 0x58, 0x88, 0x19, 0x4d, 0x01, 0x34, 0xa4, 0x2d, 0xce, 0xef, 0x41, 0xd3, 0x96, 0x3d, 0x39, 0x70, 0x45, 0x53, 0x52, 0x6d, 0x63, 0xb3, 0xc4, 0xf3, 0x97, 0xdb, - 0x52, 0x9b, 0xd9, 0x9e, 0x35, 0x1a, 0x76, 0x66, 0x4d, 0xf5, 0x6f, 0x8a, 0xd0, 0x50, 0x31, 0xaf, - 0x7e, 0x4d, 0xee, 0x43, 0xe3, 0x02, 0x21, 0xb2, 0x22, 0x79, 0x7c, 0xd4, 0xb3, 0x7c, 0xe8, 0x01, - 0x68, 0x7c, 0x8b, 0xc4, 0xc2, 0x3d, 0x8b, 0x91, 0xd3, 0xa8, 0x87, 0x31, 0xf5, 0x38, 0x53, 0xcf, - 0xa3, 0x65, 0xca, 0x6b, 0x2b, 0x2f, 0xf4, 0xda, 0x1f, 0x4b, 0xb0, 0xba, 0x87, 0xed, 0x80, 0x4b, - 0xc3, 0xe2, 0x35, 0xb6, 0x6f, 0x34, 0x72, 0x85, 0x46, 0x4a, 0xd7, 0xd6, 0xc8, 0x1e, 0xcc, 0xfb, - 0xb2, 0x82, 0x03, 0x86, 0x4f, 0xbc, 0x09, 0xf7, 0x73, 0x91, 0x84, 0x5b, 0x58, 0xb9, 0x58, 0x1d, - 0x49, 0xb8, 0xd1, 0x54, 0x19, 0x07, 0x32, 0x41, 0xff, 0xbb, 0x04, 0x2b, 0x97, 0x1b, 0xe3, 0xab, - 0x00, 0x53, 0x76, 0x53, 0x9e, 0x7f, 0x73, 0x84, 0xf5, 0x61, 0xc1, 0x8c, 0x19, 0x4d, 0x20, 0x56, - 0x04, 0xc4, 0x5a, 0xb2, 0x89, 0x84, 0xf6, 0x18, 0x0b, 0x99, 0x97, 0x6c, 0xff, 0xc5, 0x89, 0xf8, - 0xf5, 0x2d, 0x78, 0x33, 0xad, 0xc5, 0x9b, 0xb2, 0xff, 0xff, 0xcb, 0xde, 0xcf, 0x17, 0xf9, 0x66, - 0x5c, 0xf7, 0x9c, 0x73, 0x7d, 0x8a, 0xda, 0x11, 0x34, 0x9f, 0x06, 0x43, 0x6a, 0xf9, 0x64, 0x88, - 0x65, 0xb9, 0xf5, 0x25, 0x58, 0xe0, 0x34, 0x8a, 0x9e, 0x08, 0xdb, 0x44, 0x99, 0xef, 0xc2, 0x62, - 0xd6, 0x2c, 0x2f, 0x8b, 0x55, 0x98, 0x95, 0x35, 0xa3, 0xbc, 0x3d, 0x4a, 0x7c, 0x34, 0x9c, 0x89, - 0xd8, 0xa7, 0xfa, 0x36, 0xb4, 0x0c, 0x3c, 0x22, 0x94, 0x61, 0x3f, 0x95, 0xaa, 0xda, 0x6a, 0x25, - 0x29, 0x76, 0x34, 0x52, 0xca, 0xaa, 0xe9, 0xf7, 0x60, 0xed, 0xd0, 0xf1, 0x5f, 0x22, 0xb1, 0x01, - 0xb5, 0xa7, 0xcc, 0x64, 0x41, 0xbc, 0xe7, 0x9f, 0x4a, 0x50, 0x8e, 0x2c, 0x48, 0x87, 0x72, 0x20, - 0xee, 0x1a, 0x91, 0x53, 0xe9, 0x42, 0x3b, 0x1c, 0xb5, 0x0d, 0x4e, 0x02, 0x35, 0xa4, 0x07, 0x75, - 0xa0, 0x16, 0x3d, 0x0d, 0x02, 0x87, 0x70, 0x04, 0x31, 0xc9, 0x66, 0x43, 0xab, 0x51, 0xc0, 0xa1, - 0xf0, 0xa3, 0xb7, 0xf9, 0x18, 0x2a, 0x45, 0x25, 0xef, 0xc1, 0x74, 0x6c, 0xec, 0x43, 0xef, 0x43, - 0x25, 0xa9, 0x25, 0x95, 0x1d, 0x98, 0x0e, 0x4d, 0xbb, 0xd1, 0x0e, 0xa4, 0x2a, 0x4f, 0xd5, 0x5e, - 0x96, 0x2f, 0x25, 0xcd, 0xa7, 0xa2, 0xe4, 0x86, 0x3e, 0x81, 0xc5, 0x74, 0xaa, 0x69, 0x59, 0xd8, - 0xe3, 0xca, 0x96, 0xed, 0x96, 0x4e, 0x4e, 0x75, 0x25, 0xed, 0xc9, 0x30, 0xf4, 0x11, 0xd4, 0xec, - 0xf8, 0x40, 0x08, 0x2f, 0xf7, 0xa8, 0xb3, 0x9a, 0x22, 0xef, 0x09, 0xf6, 0xad, 0x70, 0xe4, 0x9f, - 0xf0, 0xec, 0x6c, 0x18, 0x7a, 0x0f, 0xe6, 0xf9, 0x98, 0xec, 0x60, 0x8b, 0x83, 0x0c, 0x7c, 0x37, - 0xe0, 0x75, 0xa3, 0xda, 0x3b, 0x62, 0x60, 0x6f, 0xc6, 0x0e, 0x23, 0xb2, 0xa3, 0x3b, 0x80, 0x92, - 0xe0, 0xb1, 0xe9, 0xd8, 0x93, 0x30, 0xfa, 0x5d, 0x11, 0x9d, 0xc0, 0x7c, 0x26, 0x1d, 0xdd, 0xef, - 0x8a, 0x50, 0xde, 0x15, 0x8d, 0xcd, 0xa7, 0x8f, 0xb9, 0x1e, 0xa5, 0xae, 0x45, 0xf8, 0x17, 0xa0, - 0x25, 0xd5, 0xee, 0x99, 0x11, 0xa2, 0x95, 0x77, 0xd5, 0x6d, 0x15, 0x3e, 0x28, 0xa0, 0xcf, 0x61, - 0x2e, 0x6e, 0x77, 0xa4, 0xa9, 0xc8, 0x8b, 0x0a, 0x68, 0xbd, 0x91, 0x28, 0x29, 0x67, 0x52, 0xe1, - 0x58, 0x6d, 0x98, 0x79, 0x12, 0x0c, 0x27, 0x84, 0x8e, 0x51, 0xde, 0x3b, 0x5b, 0xb3, 0x82, 0xb8, - 0x9e, 0x75, 0xbc, 0x55, 0xe0, 0xca, 0x9d, 0x95, 0x92, 0xc4, 0x68, 0x23, 0x5f, 0xaa, 0xd1, 0x0e, - 0xae, 0xd4, 0x72, 0xf7, 0x87, 0x22, 0xd4, 0x22, 0x5a, 0xfa, 0xa6, 0xc3, 0xdf, 0xe5, 0xa3, 0x47, - 0x50, 0x4d, 0x0b, 0x14, 0xbd, 0xae, 0x30, 0xa6, 0xa8, 0xb9, 0xb5, 0x36, 0xdd, 0x29, 0x35, 0xfd, - 0x00, 0x16, 0xa6, 0x08, 0x17, 0xe9, 0x2a, 0x29, 0x5f, 0xd5, 0xc9, 0x27, 0xa3, 0x7d, 0x58, 0x9a, - 0x2a, 0x63, 0xf4, 0x56, 0x5c, 0xb9, 0xe7, 0xa8, 0x3c, 0x05, 0xd4, 0x85, 0xb9, 0x7d, 0xcc, 0xa4, - 0x8e, 0xe3, 0xb2, 0x67, 0x94, 0xde, 0xaa, 0x67, 0xcd, 0xbb, 0xcd, 0x5f, 0xfe, 0x5c, 0x2f, 0xfc, - 0xca, 0x7f, 0x7f, 0xf0, 0xdf, 0xf7, 0x7f, 0xad, 0xbf, 0x36, 0x2c, 0x8b, 0xa3, 0xf6, 0xc3, 0x7f, - 0x02, 0x00, 0x00, 0xff, 0xff, 0x46, 0x34, 0x62, 0x46, 0xf7, 0x0f, 0x00, 0x00, + 0x52, 0x9b, 0xd9, 0x9e, 0x35, 0x1a, 0x76, 0x66, 0x4d, 0xf5, 0x6f, 0x8a, 0xd0, 0x50, 0x31, 0xff, + 0xff, 0x9a, 0xdc, 0x87, 0xc6, 0x05, 0x42, 0x64, 0x45, 0xf2, 0xf8, 0xa8, 0x67, 0xf9, 0xd0, 0x03, + 0xd0, 0xf8, 0x16, 0x89, 0x85, 0x7b, 0x16, 0x23, 0xa7, 0x51, 0x0f, 0x63, 0xea, 0x71, 0xa6, 0x9e, + 0x47, 0xcb, 0x94, 0xd7, 0x56, 0x5e, 0xe8, 0xb5, 0x3f, 0x96, 0x60, 0x75, 0x0f, 0xdb, 0x01, 0x97, + 0x86, 0xc5, 0x6b, 0x6c, 0xdf, 0x68, 0xe4, 0x0a, 0x8d, 0x94, 0xae, 0xad, 0x91, 0x3d, 0x98, 0xf7, + 0x65, 0x05, 0x07, 0x0c, 0x9f, 0x78, 0x13, 0xee, 0xe7, 0x22, 0x09, 0xb7, 0xb0, 0x72, 0xb1, 0x3a, + 0x92, 0x70, 0xa3, 0xa9, 0x32, 0x0e, 0x64, 0x82, 0xfe, 0x77, 0x09, 0x56, 0x2e, 0x37, 0xc6, 0x57, + 0x01, 0xa6, 0xec, 0xa6, 0x3c, 0xff, 0xe6, 0x08, 0xeb, 0xc3, 0x82, 0x19, 0x33, 0x9a, 0x40, 0xac, + 0x08, 0x88, 0xb5, 0x64, 0x13, 0x09, 0xed, 0x31, 0x16, 0x32, 0x2f, 0xd9, 0x5e, 0xc5, 0x89, 0xf8, + 0xf5, 0x2d, 0x78, 0x33, 0xad, 0xc5, 0x9b, 0xb2, 0xff, 0x07, 0xaa, 0x7c, 0xc5, 0x65, 0xef, 0xe7, + 0x8b, 0x7c, 0x33, 0xae, 0x7b, 0xce, 0xb9, 0x3e, 0x45, 0xed, 0x08, 0x9a, 0x4f, 0x83, 0x21, 0xb5, + 0x7c, 0x32, 0xc4, 0xb2, 0xdc, 0xfa, 0x12, 0x2c, 0x70, 0x1a, 0x45, 0x4f, 0x84, 0x6d, 0xa2, 0xcc, + 0x77, 0x61, 0x31, 0x6b, 0x96, 0x97, 0xc5, 0x2a, 0xcc, 0xca, 0x9a, 0x51, 0xde, 0x1e, 0x25, 0x3e, + 0x1a, 0xce, 0x44, 0xec, 0x53, 0x7d, 0x1b, 0x5a, 0x06, 0x1e, 0x11, 0xca, 0xb0, 0x9f, 0x4a, 0x55, + 0x6d, 0xb5, 0x92, 0x14, 0x3b, 0x1a, 0x29, 0x65, 0xd5, 0xf4, 0x7b, 0xb0, 0x76, 0xe8, 0xf8, 0x2f, + 0x91, 0xd8, 0x80, 0xda, 0x53, 0x66, 0xb2, 0x20, 0xde, 0xf3, 0x4f, 0x25, 0x28, 0x47, 0x16, 0xa4, + 0x43, 0x39, 0x10, 0x77, 0x8d, 0xc8, 0xa9, 0x74, 0xa1, 0x1d, 0x8e, 0xda, 0x06, 0x27, 0x81, 0x1a, + 0xd2, 0x83, 0x3a, 0x50, 0x8b, 0x9e, 0x06, 0x81, 0x43, 0x38, 0x82, 0x98, 0x64, 0xb3, 0xa1, 0xd5, + 0x28, 0xe0, 0x50, 0xf8, 0xd1, 0xdb, 0x7c, 0x0c, 0x95, 0xa2, 0x92, 0xf7, 0x60, 0x3a, 0x36, 0xf6, + 0xa1, 0xf7, 0xa1, 0x92, 0xd4, 0x92, 0xca, 0x0e, 0x4c, 0x87, 0xa6, 0xdd, 0x68, 0x07, 0x52, 0x95, + 0xa7, 0x6a, 0x2f, 0xcb, 0x97, 0x92, 0xe6, 0x53, 0x51, 0x72, 0x43, 0x9f, 0xc0, 0x62, 0x3a, 0xd5, + 0xb4, 0x2c, 0xec, 0x71, 0x65, 0xcb, 0x76, 0x4b, 0x27, 0xa7, 0xba, 0x92, 0xf6, 0x64, 0x18, 0xfa, + 0x08, 0x6a, 0x76, 0x7c, 0x20, 0x84, 0x97, 0x7b, 0xd4, 0x59, 0x4d, 0x91, 0xf7, 0x04, 0xfb, 0x56, + 0x38, 0xf2, 0x4f, 0x78, 0x76, 0x36, 0x0c, 0xbd, 0x07, 0xf3, 0x7c, 0x4c, 0x76, 0xb0, 0xc5, 0x41, + 0x06, 0xbe, 0x1b, 0xf0, 0xba, 0x51, 0xed, 0x1d, 0x31, 0xb0, 0x37, 0x63, 0x87, 0x11, 0xd9, 0xd1, + 0x1d, 0x40, 0x49, 0xf0, 0xd8, 0x74, 0xec, 0x49, 0x18, 0xfd, 0xae, 0x88, 0x4e, 0x60, 0x3e, 0x93, + 0x8e, 0xee, 0x77, 0x45, 0x28, 0xef, 0x8a, 0xc6, 0xe6, 0xd3, 0xc7, 0x5c, 0x8f, 0x52, 0xd7, 0x22, + 0xfc, 0x0b, 0xd0, 0x92, 0x6a, 0xf7, 0xcc, 0x08, 0xd1, 0xca, 0xbb, 0xea, 0xb6, 0x0a, 0x1f, 0x14, + 0xd0, 0xe7, 0x30, 0x17, 0xb7, 0x3b, 0xd2, 0x54, 0xe4, 0x45, 0x05, 0xb4, 0xde, 0x48, 0x94, 0x94, + 0x33, 0xa9, 0x70, 0xac, 0x36, 0xcc, 0x3c, 0x09, 0x86, 0x13, 0x42, 0xc7, 0x28, 0xef, 0x9d, 0xad, + 0x59, 0x41, 0x5c, 0xcf, 0x3a, 0xde, 0x2a, 0x70, 0xe5, 0xce, 0x4a, 0x49, 0x62, 0xb4, 0x91, 0x2f, + 0xd5, 0x68, 0x07, 0x57, 0x6a, 0xb9, 0xfb, 0x43, 0x11, 0x6a, 0x11, 0x2d, 0x7d, 0xd3, 0xe1, 0xef, + 0xf2, 0xd1, 0x23, 0xa8, 0xa6, 0x05, 0x8a, 0x5e, 0x57, 0x18, 0x53, 0xd4, 0xdc, 0x5a, 0x9b, 0xee, + 0x94, 0x9a, 0x7e, 0x00, 0x0b, 0x53, 0x84, 0x8b, 0x74, 0x95, 0x94, 0xaf, 0xea, 0xe4, 0x93, 0xd1, + 0x3e, 0x2c, 0x4d, 0x95, 0x31, 0x7a, 0x2b, 0xae, 0xdc, 0x73, 0x54, 0x9e, 0x02, 0xea, 0xc2, 0xdc, + 0x3e, 0x66, 0x52, 0xc7, 0x71, 0xd9, 0x33, 0x4a, 0x6f, 0xd5, 0xb3, 0xe6, 0xdd, 0xe6, 0x2f, 0x7f, + 0xae, 0x17, 0x7e, 0xe5, 0xbf, 0x3f, 0xf8, 0xef, 0xfb, 0xbf, 0xd6, 0x5f, 0x1b, 0x96, 0xc5, 0x51, + 0xfb, 0xe1, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x43, 0x54, 0x55, 0xf7, 0x0f, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index d3d8f23ee..4cf2b5bc6 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -69,7 +69,7 @@ message DeduplicatedDeviceActivationRequest { bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; - gateway.RxMetadata gateway_metadata = 22; + repeated gateway.RxMetadata gateway_metadata = 22; protocol.ActivationMetadata activation_metadata = 23; DeviceActivationResponse response_template = 31; } From 8c8a3e57168519a07309e9f2ca587ce79054689d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 13:16:02 +0200 Subject: [PATCH 1465/2266] Use context in Router --- core/router/activation.go | 3 +-- core/router/router.go | 13 ++++++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/router/activation.go b/core/router/activation.go index b736ec349..bdcaf5f98 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -7,7 +7,6 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" - "golang.org/x/net/context" ) func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { @@ -43,7 +42,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Do async request wg.Add(1) go func() { - res, err := broker.client.Activate(context.Background(), &pb_broker.DeviceActivationRequest{ + res, err := broker.client.Activate(r.getContext(), &pb_broker.DeviceActivationRequest{ Payload: activation.Payload, DevEui: activation.DevEui, AppEui: activation.AppEui, diff --git a/core/router/router.go b/core/router/router.go index ec8beaf5e..776814892 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -7,6 +7,7 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" @@ -87,7 +88,7 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { } client := pb_broker.NewBrokerClient(conn) - association, err := client.Associate(context.Background()) + association, err := client.Associate(r.getContext()) if err != nil { return nil, err } @@ -117,3 +118,13 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { } return r.brokers[req.NetAddress], nil } + +func (r *router) getContext() context.Context { + // TODO: insert correct metadata + md := metadata.Pairs( + "token", "token", + "id", "RouterID", + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} From c53032c447d249e36977b8e960db2fb97601e17f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 13:16:23 +0200 Subject: [PATCH 1466/2266] Include router ID in downlink option --- core/router/activation.go | 2 +- core/router/downlink.go | 18 ++++++++++-- core/router/downlink_test.go | 57 ++++++++++++++++++++---------------- core/router/uplink.go | 2 +- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/core/router/activation.go b/core/router/activation.go index bdcaf5f98..39dfafdce 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -22,7 +22,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Only for LoRaWAN gateway.Utilization.AddRx(uplink) - downlinkOptions := buildDownlinkOptions(uplink, true, gateway) + downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) // Find Broker brokers, err := r.brokerDiscovery.All() diff --git a/core/router/downlink.go b/core/router/downlink.go index 0cc55a0f1..2fc354bdb 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -2,7 +2,9 @@ package router import ( "errors" + "fmt" "math" + "strings" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -48,7 +50,12 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { GatewayConfiguration: option.GatewayConfig, } - err := gateway.Schedule.Schedule(option.Identifier, downlinkMessage) + identifier := option.Identifier + if r.identity != nil { + identifier = strings.TrimPrefix(option.Identifier, fmt.Sprintf("%s:", r.identity.Id)) + } + + err := gateway.Schedule.Schedule(identifier, downlinkMessage) if err != nil { return err } @@ -102,7 +109,7 @@ func getBand(region string) (band *lora.Band, err error) { return } -func buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway *gateway.Gateway) (downlinkOptions []*pb_broker.DownlinkOption) { +func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway *gateway.Gateway) (downlinkOptions []*pb_broker.DownlinkOption) { var options []*pb_broker.DownlinkOption gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing @@ -200,8 +207,13 @@ func buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway * computeDownlinkScores(gateway, uplink, options) - // Filter all illegal options for _, option := range options { + // Add router ID to downlink option + if r.identity != nil { + option.Identifier = fmt.Sprintf("%s:%s", option.Identifier, r.identity.Id) + } + + // Filter all illegal options if option.Score < 1000 { downlinkOptions = append(downlinkOptions, option) } diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 17c09cde1..222639d7a 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -104,15 +104,17 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { func TestUplinkBuildDownlinkOptions(t *testing.T) { a := New(t) + r := &router{} + // If something is incorrect, it just returns an empty list up := &pb.UplinkMessage{} gtw := gateway.NewGateway(types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldBeEmpty) // The reference gateway and uplink work as expected gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() - options = buildDownlinkOptions(up, false, gtw) + options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].Score, ShouldBeLessThan, options[1].Score) @@ -138,7 +140,7 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { // And for joins we want a different delay (both RX1 and RX2) and DataRate (RX2) gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() - options = buildDownlinkOptions(up, true, gtw) + options = r.buildDownlinkOptions(up, true, gtw) a.So(options[0].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 5000100) a.So(options[1].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 6000100) a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") @@ -147,10 +149,12 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { a := New(t) + r := &router{} + // Unsupported frequencies use only RX2 for downlink gtw, up := newReferenceGateway("EU_863_870"), newReferenceUplink() up.GatewayMetadata.Frequency = 869300000 - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 1) // Supported frequencies use RX1 (on the same frequency) for downlink @@ -167,7 +171,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { for _, freq := range ttnEUFrequencies { up = newReferenceUplink() up.GatewayMetadata.Frequency = freq - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].GatewayConfig.Frequency, ShouldEqual, freq) } @@ -175,7 +179,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { // Unsupported frequencies use only RX2 for downlink gtw, up = newReferenceGateway("US_902_928"), newReferenceUplink() up.GatewayMetadata.Frequency = 923300000 - options = buildDownlinkOptions(up, false, gtw) + options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 1) // Supported frequencies use RX1 (on the same frequency) for downlink @@ -192,7 +196,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { for upFreq, downFreq := range ttnUSFrequencies { up = newReferenceUplink() up.GatewayMetadata.Frequency = upFreq - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].GatewayConfig.Frequency, ShouldEqual, downFreq) } @@ -200,7 +204,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { // Unsupported frequencies use only RX2 for downlink gtw, up = newReferenceGateway("AU_915_928"), newReferenceUplink() up.GatewayMetadata.Frequency = 923300000 - options = buildDownlinkOptions(up, false, gtw) + options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 1) // Supported frequencies use RX1 (on the same frequency) for downlink @@ -217,7 +221,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { for upFreq, downFreq := range ttnAUFrequencies { up = newReferenceUplink() up.GatewayMetadata.Frequency = upFreq - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].GatewayConfig.Frequency, ShouldEqual, downFreq) } @@ -226,6 +230,8 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { a := New(t) + r := &router{} + gtw := newReferenceGateway("EU_863_870") // Supported datarates use RX1 (on the same datarate) for downlink @@ -240,7 +246,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { for _, dr := range ttnEUDataRates { up := newReferenceUplink() up.ProtocolMetadata.GetLorawan().DataRate = dr - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) } @@ -251,7 +257,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up := newReferenceUplink() up.GatewayMetadata.Frequency = 904600000 up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") @@ -266,7 +272,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up := newReferenceUplink() up.GatewayMetadata.Frequency = 903900000 up.ProtocolMetadata.GetLorawan().DataRate = drUp - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) } @@ -277,7 +283,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up = newReferenceUplink() up.GatewayMetadata.Frequency = 917500000 up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" - options = buildDownlinkOptions(up, false, gtw) + options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") @@ -292,37 +298,38 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up := newReferenceUplink() up.GatewayMetadata.Frequency = 916800000 up.ProtocolMetadata.GetLorawan().DataRate = drUp - options := buildDownlinkOptions(up, false, gtw) + options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) } } -// Note: This test uses buildDownlinkOptions which in turn calls computeDownlinkScores +// Note: This test uses r.buildDownlinkOptions which in turn calls computeDownlinkScores func TestComputeDownlinkScores(t *testing.T) { a := New(t) + r := &router{} gtw := newReferenceGateway("EU_863_870") - refScore := buildDownlinkOptions(newReferenceUplink(), false, gtw)[0].Score + refScore := r.buildDownlinkOptions(newReferenceUplink(), false, gtw)[0].Score // Lower RSSI -> worse score testSubject := newReferenceUplink() testSubject.GatewayMetadata.Rssi = -80.0 testSubjectgtw := newReferenceGateway("EU_863_870") - testSubjectScore := buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + testSubjectScore := r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Lower SNR -> worse score testSubject = newReferenceUplink() testSubject.GatewayMetadata.Snr = 2.0 testSubjectgtw = newReferenceGateway("EU_863_870") - testSubjectScore = buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Slower DataRate -> worse score testSubject = newReferenceUplink() testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF10BW125" testSubjectgtw = newReferenceGateway("EU_863_870") - testSubjectScore = buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Gateway used for Rx -> worse score @@ -333,8 +340,8 @@ func TestComputeDownlinkScores(t *testing.T) { testSubjectgtw = newReferenceGateway("EU_863_870") testSubjectgtw.Utilization.AddRx(newReferenceUplink()) testSubjectgtw.Utilization.Tick() - testSubject1Score := buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score - testSubject2Score := buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score + testSubject1Score := r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score + testSubject2Score := r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway a.So(testSubject2Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway a.So(testSubject1Score, ShouldBeGreaterThan, testSubject2Score) // Because of Rx on the same channel @@ -346,7 +353,7 @@ func TestComputeDownlinkScores(t *testing.T) { testSubject = newReferenceUplink() testSubject.GatewayMetadata.Frequency = 869300000 testSubjectgtw = newReferenceGateway("EU_863_870") - options := buildDownlinkOptions(testSubject, false, testSubjectgtw) + options := r.buildDownlinkOptions(testSubject, false, testSubjectgtw) a.So(options, ShouldHaveLength, 1) // RX1 Removed a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 869300000) @@ -357,7 +364,7 @@ func TestComputeDownlinkScores(t *testing.T) { testSubjectgtw.Utilization.AddTx(newReferenceDownlink()) } testSubjectgtw.Utilization.Tick() - options = buildDownlinkOptions(testSubject, false, testSubjectgtw) + options = r.buildDownlinkOptions(testSubject, false, testSubjectgtw) a.So(options, ShouldHaveLength, 1) // RX1 Removed a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 868100000) @@ -367,8 +374,8 @@ func TestComputeDownlinkScores(t *testing.T) { testSubject2.GatewayMetadata.GetSemtech().Timestamp = 2000000 testSubjectgtw = newReferenceGateway("EU_863_870") testSubjectgtw.Schedule.GetOption(1000100, 50000) - testSubject1Score = buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score - testSubject2Score = buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score + testSubject1Score = r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score + testSubject2Score = r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Scheduling conflict with RX1 a.So(testSubject2Score, ShouldEqual, refScore) // No scheduling conflicts } diff --git a/core/router/uplink.go b/core/router/uplink.go index ddb19e5de..f78852810 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -14,7 +14,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess gateway.Utilization.AddRx(uplink) - downlinkOptions := buildDownlinkOptions(uplink, false, gateway) + downlinkOptions := r.buildDownlinkOptions(uplink, false, gateway) // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload From 01fd18affa6a0759c9017655aff4c9b48dcb2b90 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 13:26:47 +0200 Subject: [PATCH 1467/2266] Use actual identity in Router --- core/router/router.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/router/router.go b/core/router/router.go index 776814892..1203cc6fc 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -120,10 +120,14 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { } func (r *router) getContext() context.Context { - // TODO: insert correct metadata + var id, token string + if r.identity != nil { + id = r.identity.Id + token = r.identity.Token + } md := metadata.Pairs( - "token", "token", - "id", "RouterID", + "token", token, + "id", id, ) ctx := metadata.NewContext(context.Background(), md) return ctx From c11c69d6c07073f4cefcab78809962e21cecb80e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 14:30:33 +0200 Subject: [PATCH 1468/2266] Set timeout on gRPC connect --- api/api.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/api/api.go b/api/api.go index 3bc168a03..87e4be6fa 100644 --- a/api/api.go +++ b/api/api.go @@ -1,9 +1,14 @@ package api -import "google.golang.org/grpc" +import ( + "time" + + "google.golang.org/grpc" +) // DialOptions are the gRPC dial options for discovery calls // TODO: disable insecure connections var DialOptions = []grpc.DialOption{ grpc.WithInsecure(), + grpc.WithTimeout(2 * time.Second), } From 67d0b56c6f068b2a192c4707cc548cf1ec34e553 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 14:30:48 +0200 Subject: [PATCH 1469/2266] [refactor] Broker implementation --- core/broker/activation.go | 101 +++++++++++++ core/broker/activation_test.go | 87 +++++++++++ core/broker/application/application.go | 78 ++++++++++ core/broker/application/application_test.go | 37 +++++ core/broker/application/store.go | 106 +++++++++++++ core/broker/application/store_test.go | 64 ++++++++ core/broker/broker.go | 112 ++++++++++++++ core/broker/broker_test.go | 101 +++++++++++++ core/broker/deduplicator.go | 90 +++++++++++ core/broker/deduplicator_test.go | 67 +++++++++ core/broker/downlink.go | 39 +++++ core/broker/downlink_test.go | 43 ++++++ core/broker/server.go | 122 +++++++++++++++ core/broker/server_test.go | 157 ++++++++++++++++++++ core/broker/uplink.go | 149 +++++++++++++++++++ core/broker/uplink_test.go | 153 +++++++++++++++++++ 16 files changed, 1506 insertions(+) create mode 100644 core/broker/activation.go create mode 100644 core/broker/activation_test.go create mode 100644 core/broker/application/application.go create mode 100644 core/broker/application/application_test.go create mode 100644 core/broker/application/store.go create mode 100644 core/broker/application/store_test.go create mode 100644 core/broker/broker.go create mode 100644 core/broker/broker_test.go create mode 100644 core/broker/deduplicator.go create mode 100644 core/broker/deduplicator_test.go create mode 100644 core/broker/downlink.go create mode 100644 core/broker/downlink_test.go create mode 100644 core/broker/server.go create mode 100644 core/broker/server_test.go create mode 100644 core/broker/uplink.go create mode 100644 core/broker/uplink_test.go diff --git a/core/broker/activation.go b/core/broker/activation.go new file mode 100644 index 000000000..c9124ec2c --- /dev/null +++ b/core/broker/activation.go @@ -0,0 +1,101 @@ +package broker + +import ( + "crypto/md5" + "encoding/hex" + "errors" + + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/gateway" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" +) + +func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + var err error + + // De-duplicate uplink messages + duplicates := b.deduplicateActivation(activation) + if len(duplicates) == 0 { + return nil, errors.New("ttn/broker: No duplicates") + } + + base := duplicates[0] + + // Collect GatewayMetadata and DownlinkOptions + var gatewayMetadata []*gateway.RxMetadata + var downlinkOptions []*pb.DownlinkOption + var deviceActivationResponse *pb.DeviceActivationResponse + for _, duplicate := range duplicates { + gatewayMetadata = append(gatewayMetadata, duplicate.GatewayMetadata) + downlinkOptions = append(downlinkOptions, duplicate.DownlinkOptions...) + } + + // Select best DownlinkOption + if len(downlinkOptions) > 0 { + deviceActivationResponse = &pb.DeviceActivationResponse{ + DownlinkOption: selectBestDownlink(downlinkOptions), + } + } + + // Build Uplink + deduplicatedActivationRequest := &pb.DeduplicatedDeviceActivationRequest{ + Payload: base.Payload, + DevEui: base.DevEui, + AppEui: base.AppEui, + ProtocolMetadata: base.ProtocolMetadata, + GatewayMetadata: gatewayMetadata, + ActivationMetadata: base.ActivationMetadata, + ResponseTemplate: deviceActivationResponse, + } + + // Send Activate to NS + deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.getContext(), deduplicatedActivationRequest) + if err != nil { + return nil, err + } + + // Find Handler (based on AppEUI) + application, err := b.applications.Get(*base.AppEui) + if err != nil { + return nil, err + } + + conn, err := grpc.Dial(application.HandlerNetAddress, api.DialOptions...) + if err != nil { + return nil, err + } + defer conn.Close() + client := pb_handler.NewHandlerClient(conn) + handlerResponse, err := client.Activate(b.getContext(), deduplicatedActivationRequest) + if err != nil { + return nil, err + } + + handlerResponse, err = b.ns.Activate(b.getContext(), handlerResponse) + if err != nil { + return nil, err + } + + deviceActivationResponse = &pb.DeviceActivationResponse{ + Payload: handlerResponse.Payload, + DownlinkOption: handlerResponse.DownlinkOption, + } + + return deviceActivationResponse, nil +} + +func (b *broker) deduplicateActivation(duplicate *pb.DeviceActivationRequest) (activations []*pb.DeviceActivationRequest) { + sum := md5.Sum(duplicate.Payload) + key := hex.EncodeToString(sum[:]) + list := b.activationDeduplicator.Deduplicate(key, duplicate) + if len(list) == 0 { + return + } + for _, duplicate := range list { + activations = append(activations, duplicate.(*pb.DeviceActivationRequest)) + } + return +} diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go new file mode 100644 index 000000000..ecef2a5f3 --- /dev/null +++ b/core/broker/activation_test.go @@ -0,0 +1,87 @@ +package broker + +import ( + "sync" + "testing" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core/broker/application" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestHandleActivation(t *testing.T) { + a := New(t) + + devEUI := types.DevEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + appEUI := types.AppEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + + b := &broker{ + activationDeduplicator: NewDeduplicator(10 * time.Millisecond), + applications: application.NewApplicationStore(), + ns: &mockNetworkServer{}, + } + + // Non-existing Application + res, err := b.HandleActivation(&pb.DeviceActivationRequest{ + Payload: []byte{}, + DevEui: &devEUI, + AppEui: &appEUI, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + // Non-existing Broker + b.applications.Set(&application.Application{ + AppEUI: appEUI, + HandlerNetAddress: "localhost:1234", + }) + res, err = b.HandleActivation(&pb.DeviceActivationRequest{ + Payload: []byte{}, + DevEui: &devEUI, + AppEui: &appEUI, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + // TODO: Integration test with Handler +} + +func TestDeduplicateActivation(t *testing.T) { + a := New(t) + d := NewDeduplicator(10 * time.Millisecond).(*deduplicator) + + payload := []byte{0x01, 0x02, 0x03} + protocolMetadata := &protocol.RxMetadata{} + activation1 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, ProtocolMetadata: protocolMetadata} + activation2 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 3.4}, ProtocolMetadata: protocolMetadata} + activation3 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} + activation4 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} + + b := &broker{activationDeduplicator: d} + var wg sync.WaitGroup + wg.Add(1) + go func() { + res := b.deduplicateActivation(activation1) + a.So(res, ShouldResemble, []*pb.DeviceActivationRequest{activation1, activation2, activation3}) + wg.Done() + }() + + <-time.After(5 * time.Millisecond) + + a.So(b.deduplicateActivation(activation2), ShouldBeNil) + a.So(b.deduplicateActivation(activation3), ShouldBeNil) + + wg.Wait() + + <-time.After(20 * time.Millisecond) + + a.So(b.deduplicateActivation(activation4), ShouldNotBeNil) +} diff --git a/core/broker/application/application.go b/core/broker/application/application.go new file mode 100644 index 000000000..f640e1449 --- /dev/null +++ b/core/broker/application/application.go @@ -0,0 +1,78 @@ +package application + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// Application contains the state of an application +type Application struct { + AppEUI types.AppEUI + HandlerID string + HandlerNetAddress string +} + +// ApplicationProperties contains all properties of an Application that can be stored in Redis. +var ApplicationProperties = []string{ + "app_eui", + "handler_id", + "handler_net_address", +} + +// ToStringStringMap converts the given properties of Application to a +// map[string]string for storage in Redis. +func (app *Application) ToStringStringMap(properties ...string) (map[string]string, error) { + output := make(map[string]string) + for _, p := range properties { + property, err := app.formatProperty(p) + if err != nil { + return output, err + } + if property != "" { + output[p] = property + } + } + return output, nil +} + +// FromStringStringMap imports known values from the input to a Application. +func (app *Application) FromStringStringMap(input map[string]string) error { + for k, v := range input { + app.parseProperty(k, v) + } + return nil +} + +func (app *Application) formatProperty(property string) (formatted string, err error) { + switch property { + case "app_eui": + formatted = app.AppEUI.String() + case "handler_id": + formatted = app.HandlerID + case "handler_net_address": + formatted = app.HandlerNetAddress + default: + err = fmt.Errorf("Property %s does not exist in Application", property) + } + return +} + +func (app *Application) parseProperty(property string, value string) error { + if value == "" { + return nil + } + switch property { + case "app_eui": + val, err := types.ParseAppEUI(value) + if err != nil { + return err + } + app.AppEUI = val + case "handler_id": + app.HandlerID = value + case "handler_net_address": + app.HandlerNetAddress = value + } + return nil +} diff --git a/core/broker/application/application_test.go b/core/broker/application/application_test.go new file mode 100644 index 000000000..ce10648e5 --- /dev/null +++ b/core/broker/application/application_test.go @@ -0,0 +1,37 @@ +package application + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getTestApplication() (application *Application, dmap map[string]string) { + return &Application{ + AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + HandlerID: "handlerID", + HandlerNetAddress: "handlerNetAddress:1234", + }, map[string]string{ + "app_eui": "0807060504030201", + "handler_id": "handlerID", + "handler_net_address": "handlerNetAddress:1234", + } +} + +func TestToStringMap(t *testing.T) { + a := New(t) + application, expected := getTestApplication() + dmap, err := application.ToStringStringMap(ApplicationProperties...) + a.So(err, ShouldBeNil) + a.So(dmap, ShouldResemble, expected) +} + +func TestFromStringMap(t *testing.T) { + a := New(t) + application := &Application{} + expected, dmap := getTestApplication() + err := application.FromStringStringMap(dmap) + a.So(err, ShouldBeNil) + a.So(application, ShouldResemble, expected) +} diff --git a/core/broker/application/store.go b/core/broker/application/store.go new file mode 100644 index 000000000..48cd21009 --- /dev/null +++ b/core/broker/application/store.go @@ -0,0 +1,106 @@ +package application + +import ( + "errors" + "fmt" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +var ( + // ErrNotFound is returned when an application was not found + ErrNotFound = errors.New("ttn/networkserver: Application not found") +) + +// Store is used to store application configurations +type Store interface { + // Get the full information about an application + Get(appEUI types.AppEUI) (*Application, error) + // Set the given fields of an application. If fields empty, it sets all fields. + Set(application *Application, fields ...string) error + // Delete an application + Delete(types.AppEUI) error +} + +// NewApplicationStore creates a new in-memory Application store +func NewApplicationStore() Store { + return &applicationStore{ + applications: make(map[types.AppEUI]*Application), + } +} + +// applicationStore is an in-memory Application store. It should only be used for testing +// purposes. Use the redisApplicationStore for actual deployments. +type applicationStore struct { + applications map[types.AppEUI]*Application +} + +func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { + if app, ok := s.applications[appEUI]; ok { + return app, nil + } + return nil, ErrNotFound +} + +func (s *applicationStore) Set(new *Application, fields ...string) error { + // NOTE: We don't care about fields for testing + s.applications[new.AppEUI] = new + return nil +} + +func (s *applicationStore) Delete(appEUI types.AppEUI) error { + delete(s.applications, appEUI) + return nil +} + +// NewRedisApplicationStore creates a new Redis-based status store +func NewRedisApplicationStore(client *redis.Client) Store { + return &redisApplicationStore{ + client: client, + } +} + +const redisApplicationPrefix = "application" + +type redisApplicationStore struct { + client *redis.Client +} + +func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() + if err != nil { + return nil, err + } else if len(res) == 0 { + return nil, redis.Nil // This might be a bug in redis package + } + application := &Application{} + err = application.FromStringStringMap(res) + if err != nil { + return nil, err + } + return application, nil +} + +func (s *redisApplicationStore) Set(new *Application, fields ...string) error { + if len(fields) == 0 { + fields = ApplicationProperties + } + key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppEUI) + dmap, err := new.ToStringStringMap(fields...) + if err != nil { + return err + } + s.client.HMSetMap(key, dmap) + return nil +} + +func (s *redisApplicationStore) Delete(appEUI types.AppEUI) error { + key := fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI) + err := s.client.Del(key).Err() + if err != nil { + return err + } + return nil +} diff --git a/core/broker/application/store_test.go b/core/broker/application/store_test.go new file mode 100644 index 000000000..734b4637b --- /dev/null +++ b/core/broker/application/store_test.go @@ -0,0 +1,64 @@ +package application + +import ( + "testing" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) +} + +func TestApplicationStore(t *testing.T) { + a := New(t) + + stores := map[string]Store{ + "local": NewApplicationStore(), + "redis": NewRedisApplicationStore(getRedisClient()), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Non-existing App + err := s.Set(&Application{ + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + HandlerID: "handler1ID", + HandlerNetAddress: "handler1NetAddress:1234", + }) + a.So(err, ShouldBeNil) + + // Existing App + err = s.Set(&Application{ + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + HandlerID: "handler1ID", + HandlerNetAddress: "handler1NetAddress2:1234", + }) + a.So(err, ShouldBeNil) + + app, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 2}) + a.So(err, ShouldNotBeNil) + a.So(app, ShouldBeNil) + + app, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(app, ShouldNotBeNil) + a.So(app.HandlerNetAddress, ShouldEqual, "handler1NetAddress2:1234") + + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + + app, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldNotBeNil) + a.So(app, ShouldBeNil) + } +} diff --git a/core/broker/broker.go b/core/broker/broker.go new file mode 100644 index 000000000..5fc133a61 --- /dev/null +++ b/core/broker/broker.go @@ -0,0 +1,112 @@ +package broker + +import ( + "errors" + "sync" + + "google.golang.org/grpc/metadata" + + "golang.org/x/net/context" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/broker/application" +) + +type Broker interface { + HandleUplink(uplink *pb.UplinkMessage) error + HandleDownlink(downlink *pb.DownlinkMessage) error + HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) + + ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) + DeactivateRouter(id string) error + ActivateHandler(id string) (<-chan *pb.DeduplicatedUplinkMessage, error) + DeactivateHandler(id string) error +} + +type broker struct { + identity *pb_discovery.Announcement + routers map[string]chan *pb.DownlinkMessage + routersLock sync.RWMutex + handlers map[string]chan *pb.DeduplicatedUplinkMessage + handlersLock sync.RWMutex + applications application.Store + ns networkserver.NetworkServerClient + uplinkDeduplicator Deduplicator + activationDeduplicator Deduplicator +} + +func (b *broker) ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) { + b.routersLock.Lock() + defer b.routersLock.Unlock() + if existing, ok := b.routers[id]; ok { + return existing, errors.New("Router already active") + } + b.routers[id] = make(chan *pb.DownlinkMessage) + return b.routers[id], nil +} + +func (b *broker) DeactivateRouter(id string) error { + b.routersLock.Lock() + defer b.routersLock.Unlock() + if channel, ok := b.routers[id]; ok { + close(channel) + delete(b.routers, id) + return nil + } + return errors.New("Router not active") +} + +func (b *broker) getRouter(id string) (chan<- *pb.DownlinkMessage, error) { + b.routersLock.RLock() + defer b.routersLock.RUnlock() + if router, ok := b.routers[id]; ok { + return router, nil + } + return nil, errors.New("Router not active") +} + +func (b *broker) ActivateHandler(id string) (<-chan *pb.DeduplicatedUplinkMessage, error) { + b.handlersLock.Lock() + defer b.handlersLock.Unlock() + if existing, ok := b.handlers[id]; ok { + return existing, errors.New("Handler already active") + } + b.handlers[id] = make(chan *pb.DeduplicatedUplinkMessage) + return b.handlers[id], nil +} + +func (b *broker) DeactivateHandler(id string) error { + b.handlersLock.Lock() + defer b.handlersLock.Unlock() + if channel, ok := b.handlers[id]; ok { + close(channel) + delete(b.handlers, id) + return nil + } + return errors.New("Handler not active") +} + +func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, error) { + b.handlersLock.RLock() + defer b.handlersLock.RUnlock() + if handler, ok := b.handlers[id]; ok { + return handler, nil + } + return nil, errors.New("Handler not active") +} + +func (b *broker) getContext() context.Context { + var id, token string + if b.identity != nil { + id = b.identity.Id + token = b.identity.Token + } + md := metadata.Pairs( + "token", token, + "id", id, + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go new file mode 100644 index 000000000..b11ece57e --- /dev/null +++ b/core/broker/broker_test.go @@ -0,0 +1,101 @@ +package broker + +import ( + "sync" + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" + "google.golang.org/grpc" +) + +type mockNetworkServer struct { + devices []*pb_networkserver.DevicesResponse_Device +} + +func (s *mockNetworkServer) GetDevices(ctx context.Context, req *pb_networkserver.DevicesRequest, options ...grpc.CallOption) (*pb_networkserver.DevicesResponse, error) { + return &pb_networkserver.DevicesResponse{ + Results: s.devices, + }, nil +} + +func (s *mockNetworkServer) PrepareActivation(ctx context.Context, activation *pb.DeduplicatedDeviceActivationRequest, options ...grpc.CallOption) (*pb.DeduplicatedDeviceActivationRequest, error) { + return activation, nil +} + +func (s *mockNetworkServer) Activate(ctx context.Context, activation *pb_handler.DeviceActivationResponse, options ...grpc.CallOption) (*pb_handler.DeviceActivationResponse, error) { + return activation, nil +} + +func (s *mockNetworkServer) Uplink(ctx context.Context, message *pb.DeduplicatedUplinkMessage, options ...grpc.CallOption) (*pb.DeduplicatedUplinkMessage, error) { + return message, nil +} + +func (s *mockNetworkServer) Downlink(ctx context.Context, message *pb.DownlinkMessage, options ...grpc.CallOption) (*pb.DownlinkMessage, error) { + return message, nil +} + +func TestActivateDeactivateRouter(t *testing.T) { + a := New(t) + + b := &broker{ + routers: make(map[string]chan *pb.DownlinkMessage), + } + + err := b.DeactivateRouter("RouterID") + a.So(err, ShouldNotBeNil) + + ch, err := b.ActivateRouter("RouterID") + a.So(err, ShouldBeNil) + a.So(ch, ShouldNotBeNil) + + _, err = b.ActivateRouter("RouterID") + a.So(err, ShouldNotBeNil) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + for range ch { + } + wg.Done() + }() + + err = b.DeactivateRouter("RouterID") + a.So(err, ShouldBeNil) + + wg.Wait() +} + +func TestActivateDeactivateHandler(t *testing.T) { + a := New(t) + + b := &broker{ + handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), + } + + err := b.DeactivateHandler("HandlerID") + a.So(err, ShouldNotBeNil) + + ch, err := b.ActivateHandler("HandlerID") + a.So(err, ShouldBeNil) + a.So(ch, ShouldNotBeNil) + + _, err = b.ActivateHandler("HandlerID") + a.So(err, ShouldNotBeNil) + + var wg sync.WaitGroup + wg.Add(1) + go func() { + for range ch { + } + wg.Done() + }() + + err = b.DeactivateHandler("HandlerID") + a.So(err, ShouldBeNil) + + wg.Wait() +} diff --git a/core/broker/deduplicator.go b/core/broker/deduplicator.go new file mode 100644 index 000000000..26ab1e6d9 --- /dev/null +++ b/core/broker/deduplicator.go @@ -0,0 +1,90 @@ +package broker + +import ( + "sync" + "time" +) + +type collection struct { + sync.Mutex + ready chan bool + values []interface{} +} + +func newCollection() *collection { + return &collection{ + ready: make(chan bool, 1), + values: []interface{}{}, + } +} + +func (c *collection) Add(value interface{}) { + c.Lock() + defer c.Unlock() + c.values = append(c.values, value) +} + +func (c *collection) GetAndClear() []interface{} { + c.Lock() + defer c.Unlock() + values := c.values + c.values = []interface{}{} + return values +} + +func (c *collection) done() { + c.ready <- true +} + +func (c *collection) wait() { + <-c.ready +} + +type Deduplicator interface { + Deduplicate(key string, value interface{}) []interface{} +} + +type deduplicator struct { + sync.Mutex + timeout time.Duration + collections map[string]*collection +} + +func (d *deduplicator) add(key string, value interface{}) (c *collection, isFirst bool) { + d.Lock() + defer d.Unlock() + var ok bool + if c, ok = d.collections[key]; ok { + c.Add(value) + } else { + isFirst = true + c = newCollection() + c.Add(value) + d.collections[key] = c + } + return +} + +func (d *deduplicator) Deduplicate(key string, value interface{}) (values []interface{}) { + collection, isFirst := d.add(key, value) + if isFirst { + go func() { + <-time.After(d.timeout) + collection.done() + <-time.After(d.timeout) + d.Lock() + defer d.Unlock() + delete(d.collections, key) + }() + collection.wait() + values = collection.GetAndClear() + } + return +} + +func NewDeduplicator(timeout time.Duration) Deduplicator { + return &deduplicator{ + timeout: timeout, + collections: map[string]*collection{}, + } +} diff --git a/core/broker/deduplicator_test.go b/core/broker/deduplicator_test.go new file mode 100644 index 000000000..b82acd5c7 --- /dev/null +++ b/core/broker/deduplicator_test.go @@ -0,0 +1,67 @@ +package broker + +import ( + "sync" + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestCollectionAdd(t *testing.T) { + a := New(t) + c := newCollection() + c.Add("item") + a.So(c.values, ShouldContain, "item") +} + +func TestCollectionGetAndClear(t *testing.T) { + a := New(t) + c := newCollection() + a.So(c.GetAndClear(), ShouldBeEmpty) + c.Add("item1") + c.Add("item2") + a.So(c.GetAndClear(), ShouldResemble, []interface{}{"item1", "item2"}) + a.So(c.GetAndClear(), ShouldBeEmpty) +} + +func TestCollectionWaitDone(t *testing.T) { + c := newCollection() + c.done() + c.wait() + var wg sync.WaitGroup + wg.Add(1) + go func() { + c.wait() + wg.Done() + }() + c.done() + wg.Wait() +} + +func TestDeduplicatorAdd(t *testing.T) { + a := New(t) + d := NewDeduplicator(5 * time.Millisecond).(*deduplicator) + a.So(d.collections, ShouldBeEmpty) + d.add("key", "item") + a.So(d.collections, ShouldNotBeEmpty) +} + +func TestDeduplicatorDeduplicate(t *testing.T) { + a := New(t) + d := NewDeduplicator(10 * time.Millisecond).(*deduplicator) + var wg sync.WaitGroup + wg.Add(1) + go func() { + res := d.Deduplicate("key", "value1") + a.So(res, ShouldResemble, []interface{}{"value1", "value2", "value3"}) + wg.Done() + }() + + <-time.After(5 * time.Millisecond) + + a.So(d.Deduplicate("key", "value2"), ShouldBeNil) + a.So(d.Deduplicate("key", "value3"), ShouldBeNil) + + wg.Wait() +} diff --git a/core/broker/downlink.go b/core/broker/downlink.go new file mode 100644 index 000000000..e4d4b67df --- /dev/null +++ b/core/broker/downlink.go @@ -0,0 +1,39 @@ +package broker + +import ( + "errors" + "strings" + + pb "github.com/TheThingsNetwork/ttn/api/broker" +) + +// ByScore is used to sort a list of DownlinkOptions based on Score +type ByScore []*pb.DownlinkOption + +func (a ByScore) Len() int { return len(a) } +func (a ByScore) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByScore) Less(i, j int) bool { return a[i].Score < a[j].Score } + +func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { + var err error + downlink, err = b.ns.Downlink(b.getContext(), downlink) + if err != nil { + return err + } + + var routerID string + if id := strings.Split(downlink.DownlinkOption.Identifier, ":"); len(id) == 2 { + routerID = id[0] + } else { + return errors.New("ttn/broker: Invalid downlink option") + } + + router, err := b.getRouter(routerID) + if err != nil { + return err + } + + router <- downlink + + return nil +} diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go new file mode 100644 index 000000000..cd67fef15 --- /dev/null +++ b/core/broker/downlink_test.go @@ -0,0 +1,43 @@ +package broker + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + . "github.com/smartystreets/assertions" +) + +func TestDownlink(t *testing.T) { + a := New(t) + + dlch := make(chan *pb.DownlinkMessage, 2) + + b := &broker{ + ns: &mockNetworkServer{}, + routers: map[string]chan *pb.DownlinkMessage{ + "routerID": dlch, + }, + } + + err := b.HandleDownlink(&pb.DownlinkMessage{ + DownlinkOption: &pb.DownlinkOption{ + Identifier: "fakeID", + }, + }) + a.So(err, ShouldNotBeNil) + + err = b.HandleDownlink(&pb.DownlinkMessage{ + DownlinkOption: &pb.DownlinkOption{ + Identifier: "nonExistentRouterID:scheduleID", + }, + }) + a.So(err, ShouldNotBeNil) + + err = b.HandleDownlink(&pb.DownlinkMessage{ + DownlinkOption: &pb.DownlinkOption{ + Identifier: "routerID:scheduleID", + }, + }) + a.So(err, ShouldBeNil) + a.So(len(dlch), ShouldEqual, 1) +} diff --git a/core/broker/server.go b/core/broker/server.go new file mode 100644 index 000000000..436082b05 --- /dev/null +++ b/core/broker/server.go @@ -0,0 +1,122 @@ +package broker + +import ( + "errors" + "io" + + pb_api "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type brokerRPC struct { + broker Broker +} + +func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { + md, ok := metadata.FromContext(ctx) + // TODO: Check OK + id, ok := md["id"] + if !ok || len(id) < 1 { + err = errors.New("ttn/broker: Caller did not provide \"id\" in context") + return + } + callerID = id[0] + if err != nil { + return + } + token, ok := md["token"] + if !ok || len(token) < 1 { + err = errors.New("ttn/broker: Caller did not provide \"token\" in context") + return + } + if token[0] != "token" { + // TODO: Validate Token + err = errors.New("ttn/broker: Caller not authorized") + return + } + + return +} + +func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { + routerID, err := getCallerFromMetadata(stream.Context()) + if err != nil { + return err + } + downlinkChannel, err := b.broker.ActivateRouter(routerID) + if err != nil { + return err + } + defer b.broker.DeactivateRouter(routerID) + go func() { + // DeactivateRouter closes the channel, so this goroutine will return + for downlink := range downlinkChannel { + if err := stream.Send(downlink); err != nil { + return // TODO: panic or something + } + } + }() + for { + uplink, err := stream.Recv() + if err == io.EOF { + return nil // TODO: Close stream + } + if err != nil { + return err + } + go b.broker.HandleUplink(uplink) + } +} + +func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_SubscribeServer) error { + handlerID, err := getCallerFromMetadata(stream.Context()) + if err != nil { + return err + } + uplinkChannel, err := b.broker.ActivateHandler(handlerID) + if err != nil { + return err + } + defer b.broker.DeactivateHandler(handlerID) + for uplink := range uplinkChannel { + if err := stream.Send(uplink); err != nil { + return err + } + } + return nil +} + +func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { + handlerID, err := getCallerFromMetadata(stream.Context()) + if err != nil { + return err + } + for { + downlink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&pb_api.Ack{}) + } + if err != nil { + return err + } + // TODO: Validate that this handler can publish downlink for the application + _ = handlerID + go b.broker.HandleDownlink(downlink) + } +} + +func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { + _, err = getCallerFromMetadata(ctx) + if err != nil { + return nil, err + } + return b.broker.HandleActivation(req) +} + +func (b *broker) RegisterRPC(s *grpc.Server) { + server := &brokerRPC{b} + pb.RegisterBrokerServer(s, server) +} diff --git a/core/broker/server_test.go b/core/broker/server_test.go new file mode 100644 index 000000000..c8299564b --- /dev/null +++ b/core/broker/server_test.go @@ -0,0 +1,157 @@ +package broker + +import ( + "fmt" + "math/rand" + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +func randomPort() uint { + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(5000) + 5000 + return uint(port) +} + +func buildTestBrokerServer(port uint) (*broker, *grpc.Server) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + b := &broker{ + routers: make(map[string]chan *pb.DownlinkMessage), + handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), + ns: &mockNetworkServer{}, + uplinkDeduplicator: NewDeduplicator(300 * time.Millisecond), + activationDeduplicator: NewDeduplicator(1000 * time.Millisecond), + } + s := grpc.NewServer() + b.RegisterRPC(s) + go s.Serve(lis) + return b, s +} + +func TestAssociateRPC(t *testing.T) { + a := New(t) + + port := randomPort() + b, s := buildTestBrokerServer(port) + defer s.Stop() + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewBrokerClient(conn) + md := metadata.Pairs( + "token", "token", + "id", "RouterID", + ) + ctx := metadata.NewContext(context.Background(), md) + + stream, err := client.Associate(ctx) + a.So(err, ShouldBeNil) + + <-time.After(5 * time.Millisecond) + + a.So(b.routers, ShouldNotBeEmpty) + + err = stream.CloseSend() + a.So(err, ShouldBeNil) + + <-time.After(5 * time.Millisecond) + + a.So(b.routers, ShouldBeEmpty) + +} + +func TestSubscribeRPC(t *testing.T) { + a := New(t) + + port := randomPort() + b, s := buildTestBrokerServer(port) + defer s.Stop() + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewBrokerClient(conn) + md := metadata.Pairs( + "token", "token", + "id", "HandlerID", + ) + ctx := metadata.NewContext(context.Background(), md) + + stream, err := client.Subscribe(ctx, &pb.SubscribeRequest{}) + a.So(err, ShouldBeNil) + + <-time.After(5 * time.Millisecond) + + a.So(b.handlers, ShouldNotBeEmpty) + + err = stream.CloseSend() + a.So(err, ShouldBeNil) + + err = conn.Close() + a.So(err, ShouldBeNil) + + <-time.After(5 * time.Millisecond) + + b.handlers["HandlerID"] <- &pb.DeduplicatedUplinkMessage{} + + <-time.After(5 * time.Millisecond) + + a.So(b.handlers, ShouldBeEmpty) + +} + +func TestPublishRPC(t *testing.T) { + a := New(t) + + port := randomPort() + b, s := buildTestBrokerServer(port) + defer s.Stop() + + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) + if err != nil { + panic(err) + } + defer conn.Close() + client := pb.NewBrokerClient(conn) + md := metadata.Pairs( + "token", "token", + "id", "HandlerID", + ) + ctx := metadata.NewContext(context.Background(), md) + + dlch := make(chan *pb.DownlinkMessage, 2) + b.routers["routerID"] = dlch + + stream, _ := client.Publish(ctx) + stream.Send(&pb.DownlinkMessage{ + DownlinkOption: &pb.DownlinkOption{ + Identifier: "routerID:scheduleID", + }, + }) + ack, err := stream.CloseAndRecv() + a.So(err, ShouldBeNil) + a.So(ack, ShouldNotBeNil) + + a.So(len(dlch), ShouldEqual, 1) +} + +func TestActivate(t *testing.T) { + // TODO +} diff --git a/core/broker/uplink.go b/core/broker/uplink.go new file mode 100644 index 000000000..3ba1a2a5b --- /dev/null +++ b/core/broker/uplink.go @@ -0,0 +1,149 @@ +package broker + +import ( + "crypto/md5" + "encoding/hex" + "errors" + "sort" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/fcnt" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" +) + +const maxFCntGap = 16384 + +var ( + ErrNotFound = errors.New("ttn/broker: Device not found") + ErrNoMatch = errors.New("ttn/broker: No matching device") + ErrInvalidFCnt = errors.New("ttn/broker: Invalid Frame Counter") +) + +func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { + // De-duplicate uplink messages + duplicates := b.deduplicateUplink(uplink) + if len(duplicates) == 0 { + return nil + } + + // LoRaWAN: Unmarshal + base := duplicates[0] + var phyPayload lorawan.PHYPayload + err := phyPayload.UnmarshalBinary(base.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.New("Uplink message does not contain a MAC payload.") + } + + // Request devices from NS + devAddr := types.DevAddr(macPayload.FHDR.DevAddr) + getDevicesResp, err := b.ns.GetDevices(b.getContext(), &networkserver.DevicesRequest{ + DevAddr: &devAddr, + FCnt: macPayload.FHDR.FCnt, + }) + if err != nil { + return err + } + if len(getDevicesResp.Results) == 0 { + return ErrNotFound + } + + // Find AppEUI/DevEUI through MIC check + var device *pb_networkserver.DevicesResponse_Device + for _, candidate := range getDevicesResp.Results { + var nwkSKey lorawan.AES128Key + copy(nwkSKey[:], candidate.NwkSKey.Bytes()) + if candidate.Uses32BitFCnt { + macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCnt, uint16(macPayload.FHDR.FCnt)) + } + ok, err = phyPayload.ValidateMIC(nwkSKey) + if err != nil { + return err + } + if ok { + device = candidate + break + } + } + if device == nil { + return ErrNoMatch + } + + if device.DisableFCntCheck { + // TODO: Add warning to message? + } else if macPayload.FHDR.FCnt < device.FCnt || macPayload.FHDR.FCnt-device.FCnt > maxFCntGap { + // Replay attack or FCnt gap too big + return ErrInvalidFCnt + } + + // Collect GatewayMetadata and DownlinkOptions + var gatewayMetadata []*gateway.RxMetadata + var downlinkOptions []*pb.DownlinkOption + var downlinkMessage *pb.DownlinkMessage + for _, duplicate := range duplicates { + gatewayMetadata = append(gatewayMetadata, duplicate.GatewayMetadata) + downlinkOptions = append(downlinkOptions, duplicate.DownlinkOptions...) + } + + // Select best DownlinkOption + if len(downlinkOptions) > 0 { + downlinkMessage = &pb.DownlinkMessage{ + DownlinkOption: selectBestDownlink(downlinkOptions), + } + } + + // Build Uplink + deduplicatedUplink := &pb.DeduplicatedUplinkMessage{ + Payload: base.Payload, + DevEui: device.DevEui, + AppEui: device.AppEui, + ProtocolMetadata: base.ProtocolMetadata, + GatewayMetadata: gatewayMetadata, + ResponseTemplate: downlinkMessage, + } + + // Pass Uplink through NS + deduplicatedUplink, err = b.ns.Uplink(b.getContext(), deduplicatedUplink) + if err != nil { + return err + } + + application, err := b.applications.Get(*device.AppEui) + if err != nil { + return err + } + + handler, err := b.getHandler(application.HandlerID) + if err != nil { + return err + } + + handler <- deduplicatedUplink + + return nil +} + +func (b *broker) deduplicateUplink(duplicate *pb.UplinkMessage) (uplinks []*pb.UplinkMessage) { + sum := md5.Sum(duplicate.Payload) + key := hex.EncodeToString(sum[:]) + list := b.uplinkDeduplicator.Deduplicate(key, duplicate) + if len(list) == 0 { + return + } + for _, duplicate := range list { + uplinks = append(uplinks, duplicate.(*pb.UplinkMessage)) + } + return +} + +func selectBestDownlink(options []*pb.DownlinkOption) *pb.DownlinkOption { + sort.Sort(ByScore(options)) + return options[0] +} diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go new file mode 100644 index 000000000..99dc0f955 --- /dev/null +++ b/core/broker/uplink_test.go @@ -0,0 +1,153 @@ +package broker + +import ( + "sync" + "testing" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/gateway" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core/broker/application" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandleUplink(t *testing.T) { + a := New(t) + + b := &broker{ + uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), + ns: &mockNetworkServer{ + devices: []*pb_networkserver.DevicesResponse_Device{}, + }, + } + + // Invalid Payload + err := b.HandleUplink(&pb.UplinkMessage{ + Payload: []byte{0x01, 0x02, 0x03}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldNotBeNil) + + // Valid Payload + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + // Device not found + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldEqual, ErrNotFound) + + // Add devices + b = &broker{ + handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), + uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), + ns: &mockNetworkServer{ + devices: []*pb_networkserver.DevicesResponse_Device{ + &pb_networkserver.DevicesResponse_Device{ + DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: &types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + FCnt: 3, + }, + }, + }, + applications: application.NewApplicationStore(), + } + b.applications.Set(&application.Application{AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, HandlerID: "handlerID"}) + b.handlers["handlerID"] = make(chan *pb.DeduplicatedUplinkMessage, 10) + + // Device doesn't match + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldEqual, ErrNoMatch) + + phy.SetMIC(lorawan.AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) + bytes, _ = phy.MarshalBinary() + + // Wrong FCnt + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldEqual, ErrInvalidFCnt) + + // Disable FCnt Check + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = true + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldBeNil) + + // OK FCnt + b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.(*mockNetworkServer).devices[0].FCnt = 0 + b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = false + err = b.HandleUplink(&pb.UplinkMessage{ + Payload: bytes, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + ProtocolMetadata: &protocol.RxMetadata{}, + }) + a.So(err, ShouldBeNil) +} + +func TestDeduplicateUplink(t *testing.T) { + a := New(t) + d := NewDeduplicator(10 * time.Millisecond).(*deduplicator) + + payload := []byte{0x01, 0x02, 0x03} + protocolMetadata := &protocol.RxMetadata{} + uplink1 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, ProtocolMetadata: protocolMetadata} + uplink2 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 3.4}, ProtocolMetadata: protocolMetadata} + uplink3 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} + uplink4 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} + + b := &broker{uplinkDeduplicator: d} + var wg sync.WaitGroup + wg.Add(1) + go func() { + res := b.deduplicateUplink(uplink1) + a.So(res, ShouldResemble, []*pb.UplinkMessage{uplink1, uplink2, uplink3}) + a.So(res, ShouldNotContain, uplink4) + wg.Done() + }() + + <-time.After(5 * time.Millisecond) + + a.So(b.deduplicateUplink(uplink2), ShouldBeNil) + a.So(b.deduplicateUplink(uplink3), ShouldBeNil) + + <-time.After(10 * time.Millisecond) + + a.So(b.deduplicateUplink(uplink4), ShouldBeNil) + + wg.Wait() +} From 7f1a558a1ee0569abe2e16e91ae848897df6cba4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 15:45:05 +0200 Subject: [PATCH 1470/2266] Rename NetID to NwkID --- core/networkserver/networkserver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index c161bfcb8..074b099c2 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -66,7 +66,7 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes return res, nil } -const netID = 0x13 +const nwkID = 0x13 func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { // Build activation metadata if not present @@ -86,7 +86,7 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat // TODO: Be smarter than just randomly generating addresses. var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) - devAddr[0] = (netID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb + devAddr[0] = (nwkID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb // Set the DevAddr in the Activation activation.ActivationMetadata.GetLorawan().DevAddr = &devAddr From 799f328823af18626fea7dc471ad3fe9ee45a2d4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 17:42:46 +0200 Subject: [PATCH 1471/2266] Prepare activation and downlink in router and networkserver --- api/protocol/lorawan/lorawan.pb.go | 227 +++++++++++++++++------ api/protocol/lorawan/lorawan.proto | 5 + core/networkserver/networkserver.go | 74 ++++++-- core/networkserver/networkserver_test.go | 25 ++- core/router/activation.go | 47 ++++- core/router/activation_test.go | 4 +- 6 files changed, 308 insertions(+), 74 deletions(-) diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 056ca709e..a25976f3a 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -117,10 +117,14 @@ func (*TxConfiguration) ProtoMessage() {} func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } type ActivationMetadata struct { - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,3,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,4,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,3,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,4,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + Rx1DrOffset uint32 `protobuf:"varint,11,opt,name=rx1_dr_offset,json=rx1DrOffset,proto3" json:"rx1_dr_offset,omitempty"` + Rx2Dr uint32 `protobuf:"varint,12,opt,name=rx2_dr,json=rx2Dr,proto3" json:"rx2_dr,omitempty"` + RxDelay uint32 `protobuf:"varint,13,opt,name=rx_delay,json=rxDelay,proto3" json:"rx_delay,omitempty"` + CfList []uint64 `protobuf:"varint,14,rep,name=cf_list,json=cfList" json:"cf_list,omitempty"` } func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } @@ -360,6 +364,28 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { } i += n4 } + if m.Rx1DrOffset != 0 { + data[i] = 0x58 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + data[i] = 0x60 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Rx2Dr)) + } + if m.RxDelay != 0 { + data[i] = 0x68 + i++ + i = encodeVarintLorawan(data, i, uint64(m.RxDelay)) + } + if len(m.CfList) > 0 { + for _, num := range m.CfList { + data[i] = 0x70 + i++ + i = encodeVarintLorawan(data, i, uint64(num)) + } + } return i, nil } @@ -675,6 +701,20 @@ func (m *ActivationMetadata) Size() (n int) { l = m.NwkSKey.Size() n += 1 + l + sovLorawan(uint64(l)) } + if m.Rx1DrOffset != 0 { + n += 1 + sovLorawan(uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + n += 1 + sovLorawan(uint64(m.Rx2Dr)) + } + if m.RxDelay != 0 { + n += 1 + sovLorawan(uint64(m.RxDelay)) + } + if len(m.CfList) > 0 { + for _, e := range m.CfList { + n += 1 + sovLorawan(uint64(e)) + } + } return n } @@ -1232,6 +1272,83 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx1DrOffset", wireType) + } + m.Rx1DrOffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx2Dr", wireType) + } + m.Rx2Dr = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Rx2Dr |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxDelay", wireType) + } + m.RxDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RxDelay |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) + } + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.CfList = append(m.CfList, v) default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -2051,53 +2168,57 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 758 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0xea, 0x46, - 0x14, 0x8e, 0xc3, 0x9f, 0x39, 0x04, 0x6a, 0x4d, 0x5a, 0x89, 0xfe, 0x28, 0x69, 0x2d, 0x55, 0xaa, - 0x22, 0x15, 0x08, 0x24, 0x01, 0x96, 0x0e, 0x4d, 0x94, 0x2a, 0x81, 0xd0, 0x09, 0x2c, 0xba, 0x1a, - 0x19, 0x7b, 0x0c, 0xae, 0xc1, 0xe3, 0x98, 0x21, 0x94, 0x17, 0xe8, 0x33, 0x64, 0xd7, 0x17, 0xe8, - 0x83, 0x74, 0xd9, 0x75, 0x2b, 0x55, 0x55, 0xef, 0x8b, 0xdc, 0x99, 0x31, 0x81, 0x5c, 0xe9, 0x4a, - 0x57, 0x37, 0xba, 0x8b, 0xbb, 0xb0, 0x7c, 0xbe, 0x39, 0xe7, 0xfb, 0xce, 0x37, 0x67, 0xc6, 0x86, - 0xf3, 0xb1, 0xcf, 0x27, 0x8b, 0x51, 0xc5, 0x61, 0xb3, 0xea, 0x60, 0x42, 0x07, 0x13, 0x3f, 0x1c, - 0xcf, 0x7b, 0x94, 0x2f, 0x59, 0x1c, 0x54, 0x39, 0x0f, 0xab, 0x76, 0xe4, 0x57, 0xa3, 0x98, 0x71, - 0xe6, 0xb0, 0x69, 0x75, 0xca, 0x62, 0x7b, 0x69, 0x87, 0x4f, 0xef, 0x8a, 0x4a, 0xa0, 0xdc, 0x1a, - 0x7e, 0xf1, 0xfd, 0x33, 0xb1, 0x31, 0x1b, 0xb3, 0x84, 0x38, 0x5a, 0x78, 0x0a, 0x29, 0xa0, 0xa2, - 0x84, 0x67, 0x3e, 0x6a, 0xa0, 0x77, 0x29, 0xb7, 0x5d, 0x9b, 0xdb, 0xa8, 0x01, 0x30, 0x63, 0xee, - 0x62, 0x6a, 0x73, 0x9f, 0x85, 0xe5, 0xc2, 0xd7, 0xda, 0x77, 0xa5, 0xfa, 0x7e, 0xe5, 0xa9, 0x51, - 0x77, 0x93, 0xc2, 0xcf, 0xca, 0xd0, 0x97, 0x90, 0x97, 0x64, 0x12, 0xdb, 0x9c, 0x96, 0xf7, 0x04, - 0x27, 0x8f, 0x75, 0xb9, 0x80, 0x05, 0x46, 0x9f, 0x83, 0x3e, 0xf2, 0x79, 0x92, 0x2b, 0x8a, 0x5c, - 0x11, 0xe7, 0x04, 0x56, 0xa9, 0x43, 0x28, 0x38, 0xcc, 0x15, 0x5b, 0x4d, 0xb2, 0x25, 0xc5, 0x84, - 0x64, 0x49, 0x16, 0x98, 0xbf, 0x6b, 0xf0, 0xc9, 0xe0, 0xd7, 0x0e, 0x0b, 0x3d, 0x7f, 0xbc, 0x88, - 0x93, 0x66, 0x1f, 0x97, 0xc3, 0x7f, 0x76, 0x01, 0x59, 0x0e, 0xf7, 0x1f, 0x54, 0x9f, 0xcd, 0x18, - 0x7b, 0x90, 0xb3, 0xa3, 0x88, 0xd0, 0x85, 0x5f, 0xd6, 0x04, 0x67, 0xef, 0xfc, 0xf4, 0xef, 0x7f, - 0x0f, 0x8f, 0xdf, 0x75, 0xc8, 0x0e, 0x8b, 0x69, 0x95, 0xaf, 0x22, 0x3a, 0xaf, 0x58, 0x51, 0x74, - 0x31, 0xfc, 0x11, 0x67, 0x85, 0xca, 0xc5, 0xc2, 0x97, 0x7a, 0x2e, 0x7d, 0x50, 0x7a, 0xbb, 0x2f, - 0xd2, 0xfb, 0x81, 0x3e, 0x28, 0x3d, 0xa1, 0x22, 0xf5, 0x7e, 0x02, 0x5d, 0xea, 0xd9, 0xae, 0x1b, - 0x97, 0x53, 0x4a, 0xf0, 0x4c, 0x08, 0xd6, 0xdf, 0x4f, 0xd0, 0x12, 0x6c, 0x2c, 0x7d, 0xc9, 0x00, - 0x61, 0xc8, 0x87, 0xcb, 0x80, 0xcc, 0x49, 0x40, 0x57, 0xe5, 0xf4, 0x8b, 0x34, 0x7b, 0xcb, 0xe0, - 0xee, 0x9a, 0xae, 0x70, 0x2e, 0x4c, 0x02, 0x73, 0x09, 0xd0, 0xbf, 0xfa, 0xb9, 0x6f, 0xaf, 0xa6, - 0xcc, 0x76, 0xd1, 0x37, 0x90, 0x9e, 0x4d, 0x84, 0x61, 0x39, 0xd1, 0x42, 0xbd, 0xb8, 0x3d, 0xf3, - 0x2b, 0xe1, 0x43, 0xa5, 0xd0, 0x09, 0x14, 0xba, 0x56, 0x87, 0x44, 0x09, 0x43, 0xcd, 0xaa, 0xf0, - 0xfc, 0x76, 0x58, 0x9d, 0xb5, 0x98, 0xb8, 0x1d, 0x9b, 0x18, 0x19, 0x90, 0x9a, 0xf9, 0x4e, 0x32, - 0x08, 0x2c, 0x43, 0xb3, 0x01, 0x69, 0xa9, 0x8a, 0x3e, 0x83, 0xec, 0x8c, 0x48, 0x73, 0xaa, 0x69, - 0x11, 0x67, 0x66, 0x03, 0x01, 0xd0, 0xa7, 0x90, 0x99, 0xd9, 0xbf, 0xb0, 0x58, 0x35, 0x90, 0xab, - 0x12, 0x98, 0x13, 0x80, 0x6d, 0x03, 0x64, 0x42, 0xc6, 0x23, 0x6f, 0xb3, 0x7b, 0xa9, 0xec, 0x7a, - 0x6b, 0x79, 0x8f, 0x44, 0x2c, 0xe6, 0x4f, 0x42, 0x5e, 0x5f, 0x00, 0x79, 0xeb, 0x2e, 0x71, 0x77, - 0xb3, 0x8b, 0xc4, 0x17, 0x78, 0xb8, 0xbb, 0xd6, 0x36, 0xff, 0xd0, 0x20, 0x2d, 0x65, 0xde, 0x38, - 0x47, 0xed, 0xc3, 0x9c, 0xe3, 0xb7, 0xd2, 0x93, 0xc3, 0xe3, 0xe9, 0x7a, 0x7a, 0xa5, 0xad, 0xf1, - 0x8e, 0x58, 0x15, 0x1e, 0xe5, 0x0b, 0xed, 0xcb, 0xed, 0x39, 0x21, 0x57, 0xee, 0x8a, 0x62, 0x3f, - 0x9d, 0x90, 0x27, 0xfb, 0x61, 0x11, 0x9f, 0x8b, 0x0b, 0x90, 0x12, 0x9e, 0x33, 0xde, 0xad, 0x00, - 0xe6, 0x6f, 0x1a, 0x64, 0x14, 0x59, 0x4e, 0xda, 0x5e, 0x5b, 0xd5, 0xb1, 0x0c, 0xd1, 0x01, 0x14, - 0xc4, 0x8b, 0xd8, 0x4e, 0x40, 0x62, 0x7a, 0xaf, 0x7a, 0xea, 0x38, 0x2f, 0x96, 0x2c, 0x27, 0xc0, - 0xf4, 0x5e, 0x31, 0x9c, 0x40, 0x75, 0x91, 0x0c, 0x27, 0x90, 0xdf, 0xb2, 0x18, 0x1a, 0x0d, 0xe5, - 0x37, 0xa8, 0x2e, 0x9a, 0x8e, 0x75, 0xaf, 0x9f, 0x60, 0xf4, 0x15, 0x40, 0xe2, 0x80, 0x4c, 0x69, - 0x58, 0xce, 0xa8, 0xc9, 0xe9, 0xca, 0xc5, 0x0d, 0x0d, 0x8f, 0x0e, 0xc5, 0x09, 0x6d, 0x7f, 0x0a, - 0x3a, 0xa4, 0x6f, 0x6e, 0xb1, 0x65, 0xec, 0xa0, 0x1c, 0xa4, 0x2e, 0xef, 0xae, 0x0d, 0xed, 0xc8, - 0x85, 0x2c, 0xa6, 0x63, 0x99, 0x2c, 0x01, 0x5c, 0x0c, 0x49, 0xeb, 0xac, 0x41, 0x5a, 0xcd, 0x9a, - 0x28, 0x11, 0x78, 0x78, 0x47, 0xda, 0xb5, 0x3a, 0x69, 0xd7, 0x5b, 0x86, 0x26, 0x71, 0xa7, 0x47, - 0x9a, 0xcd, 0x36, 0x69, 0xb6, 0x9a, 0xc6, 0x2e, 0x02, 0xc8, 0x8a, 0xfa, 0x93, 0x46, 0xc3, 0x48, - 0xc9, 0x9c, 0x35, 0x24, 0xed, 0xe3, 0x53, 0x55, 0x9b, 0x5e, 0xd7, 0x9e, 0x34, 0x6b, 0xe4, 0xf4, - 0xb8, 0x66, 0x64, 0xce, 0x8d, 0x3f, 0xff, 0x3f, 0xd0, 0xfe, 0x12, 0xcf, 0x7f, 0xe2, 0x79, 0x7c, - 0x75, 0xb0, 0x33, 0xca, 0xaa, 0x5f, 0x71, 0xe3, 0x75, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x33, - 0x7d, 0x5b, 0x08, 0x06, 0x00, 0x00, + // 828 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0x23, 0x45, + 0x10, 0xde, 0x59, 0xff, 0x4d, 0xca, 0xeb, 0x60, 0xf5, 0x82, 0x30, 0x3f, 0x4a, 0x60, 0x24, 0xa4, + 0xd5, 0x4a, 0xc4, 0x89, 0x9d, 0xac, 0x93, 0xe3, 0xc4, 0x49, 0xb4, 0x68, 0xe3, 0x24, 0x74, 0x92, + 0x03, 0xa7, 0x56, 0x67, 0xa6, 0xc7, 0x1e, 0x66, 0x3c, 0x3d, 0xdb, 0x6e, 0xc7, 0xf6, 0x0b, 0xf0, + 0x0c, 0x7b, 0xe3, 0x05, 0x78, 0x10, 0xc4, 0x89, 0x33, 0x07, 0x84, 0xe0, 0x45, 0xa8, 0xee, 0x71, + 0xe2, 0x20, 0x21, 0x21, 0x22, 0x0e, 0x1c, 0xac, 0xe9, 0xaf, 0xab, 0xea, 0xab, 0xaf, 0x7e, 0xdc, + 0x70, 0x38, 0x8c, 0xf5, 0x68, 0x7a, 0xb3, 0x15, 0xc8, 0x71, 0xfb, 0x6a, 0x24, 0xae, 0x46, 0x71, + 0x36, 0x9c, 0x9c, 0x09, 0x3d, 0x93, 0x2a, 0x69, 0x6b, 0x9d, 0xb5, 0x79, 0x1e, 0xb7, 0x73, 0x25, + 0xb5, 0x0c, 0x64, 0xda, 0x4e, 0xa5, 0xe2, 0x33, 0x9e, 0xdd, 0x7d, 0xb7, 0xac, 0x81, 0xd4, 0x96, + 0xf0, 0xe3, 0x2f, 0x1f, 0x90, 0x0d, 0xe5, 0x50, 0x16, 0x81, 0x37, 0xd3, 0xc8, 0x22, 0x0b, 0xec, + 0xa9, 0x88, 0xf3, 0xde, 0x39, 0xe0, 0x0e, 0x84, 0xe6, 0x21, 0xd7, 0x9c, 0x74, 0x01, 0xc6, 0x32, + 0x9c, 0xa6, 0x5c, 0xc7, 0x32, 0x6b, 0xd5, 0x3f, 0x73, 0x5e, 0xac, 0x77, 0x9e, 0x6f, 0xdd, 0x25, + 0x1a, 0xdc, 0x9b, 0xe8, 0x03, 0x37, 0xf2, 0x09, 0xac, 0x99, 0x60, 0xa6, 0xb8, 0x16, 0xad, 0x67, + 0x18, 0xb3, 0x46, 0x5d, 0x73, 0x41, 0x11, 0x93, 0x8f, 0xc0, 0xbd, 0x89, 0x75, 0x61, 0x6b, 0xa0, + 0xad, 0x41, 0x6b, 0x88, 0xad, 0x69, 0x13, 0xea, 0x81, 0x0c, 0xb1, 0xd4, 0xc2, 0xba, 0x6e, 0x23, + 0xa1, 0xb8, 0x32, 0x0e, 0xde, 0xf7, 0x0e, 0xbc, 0x77, 0x35, 0xef, 0xcb, 0x2c, 0x8a, 0x87, 0x53, + 0x55, 0x24, 0xfb, 0x7f, 0x29, 0xfc, 0xa9, 0x04, 0xc4, 0x0f, 0x74, 0x7c, 0x6b, 0xf3, 0xdc, 0xb7, + 0xf1, 0x0c, 0x6a, 0x3c, 0xcf, 0x99, 0x98, 0xc6, 0x2d, 0x07, 0x63, 0x9e, 0x1d, 0xee, 0xfd, 0xf2, + 0xeb, 0xe6, 0xce, 0x3f, 0x0d, 0x39, 0x90, 0x4a, 0xb4, 0xf5, 0x22, 0x17, 0x93, 0x2d, 0x3f, 0xcf, + 0x8f, 0xaf, 0xbf, 0xa2, 0x55, 0x64, 0x39, 0x9e, 0xc6, 0x86, 0x2f, 0x14, 0xb7, 0x96, 0xef, 0xe9, + 0xa3, 0xf8, 0x8e, 0xc4, 0xad, 0xe5, 0x43, 0x16, 0xc3, 0xf7, 0x35, 0xb8, 0x86, 0x8f, 0x87, 0xa1, + 0x6a, 0x95, 0x2c, 0xe1, 0x2b, 0x24, 0xec, 0xfc, 0x3b, 0x42, 0x1f, 0xa3, 0xa9, 0xd1, 0x65, 0x0e, + 0x84, 0xc2, 0x5a, 0x36, 0x4b, 0xd8, 0x84, 0x25, 0x62, 0xd1, 0x2a, 0x3f, 0x8a, 0xf3, 0x6c, 0x96, + 0x5c, 0xbe, 0x11, 0x0b, 0x5a, 0xcb, 0x8a, 0x03, 0xf1, 0xa0, 0xa1, 0xe6, 0x3b, 0x2c, 0x54, 0x4c, + 0x46, 0xd1, 0x44, 0x68, 0x3b, 0xee, 0x06, 0xad, 0xe3, 0xe5, 0x91, 0x3a, 0xb7, 0x57, 0xe4, 0x03, + 0xa8, 0xaa, 0x79, 0x07, 0x7d, 0xec, 0x5c, 0x1b, 0xb4, 0x82, 0xe8, 0x48, 0x99, 0xa1, 0xaa, 0x39, + 0x0b, 0x45, 0xca, 0x17, 0x77, 0x43, 0x55, 0xf3, 0x23, 0x03, 0xc9, 0x87, 0x50, 0x0b, 0x22, 0x96, + 0xc6, 0x13, 0x8d, 0x03, 0x2d, 0xbd, 0x28, 0xd3, 0x6a, 0x10, 0x9d, 0x22, 0xf2, 0x66, 0x00, 0x17, + 0xaf, 0xbf, 0xb9, 0xe0, 0x8b, 0x54, 0xf2, 0x90, 0x7c, 0x0e, 0xe5, 0xf1, 0x08, 0x69, 0xcd, 0x00, + 0xeb, 0x9d, 0xc6, 0x6a, 0xc5, 0x5e, 0x63, 0xd9, 0xd6, 0x44, 0x76, 0xa1, 0x3e, 0xf0, 0xfb, 0x2c, + 0x2f, 0x22, 0xec, 0x68, 0xea, 0x0f, 0x97, 0xd1, 0xef, 0x2f, 0xc9, 0x70, 0x19, 0xef, 0xcf, 0xa4, + 0x09, 0xa5, 0x71, 0x1c, 0x14, 0x7d, 0xa7, 0xe6, 0xe8, 0x75, 0xa1, 0x6c, 0x58, 0x4d, 0x2d, 0x63, + 0x66, 0x7a, 0x61, 0x93, 0x62, 0x2d, 0xe3, 0x2b, 0x04, 0xe4, 0x7d, 0xa8, 0x8c, 0xf9, 0xb7, 0x52, + 0xd9, 0x04, 0xe6, 0xd6, 0x00, 0x6f, 0x04, 0xb0, 0x4a, 0x80, 0xad, 0xaa, 0x44, 0xec, 0xef, 0xe4, + 0x9e, 0x58, 0xb9, 0xd1, 0x92, 0x3e, 0x62, 0xb9, 0x54, 0xfa, 0x8e, 0x28, 0xba, 0x40, 0x60, 0x96, + 0xfc, 0x84, 0x0e, 0xee, 0xab, 0x28, 0x74, 0x41, 0x44, 0x07, 0x4b, 0x6e, 0xef, 0x07, 0x07, 0xca, + 0x86, 0xe6, 0x2f, 0x6b, 0xe3, 0xfc, 0x37, 0x6b, 0xf3, 0x85, 0xd1, 0x14, 0x68, 0x95, 0x2e, 0xbb, + 0xb7, 0xbe, 0x12, 0xde, 0xc7, 0x5b, 0xd4, 0x68, 0x3e, 0xe4, 0xb9, 0x29, 0x2f, 0xc8, 0xb4, 0x55, + 0xd7, 0xc0, 0x7a, 0xfa, 0x99, 0x2e, 0xea, 0x91, 0xb9, 0x9e, 0xe0, 0xbe, 0x95, 0x50, 0x73, 0x25, + 0x3a, 0x47, 0xe0, 0x7d, 0xe7, 0x40, 0xc5, 0x06, 0x9b, 0x4e, 0xf3, 0xa5, 0x54, 0x97, 0x9a, 0x23, + 0xd9, 0x80, 0x3a, 0x7e, 0x18, 0x0f, 0x12, 0xa6, 0xc4, 0x5b, 0x9b, 0xd3, 0xa5, 0x6b, 0x78, 0xe5, + 0x07, 0x09, 0x15, 0x6f, 0x6d, 0x44, 0x90, 0xd8, 0x2c, 0x26, 0x22, 0x48, 0xcc, 0xd3, 0x81, 0x4d, + 0x13, 0x99, 0xf9, 0xcb, 0xdb, 0xbd, 0x76, 0xa9, 0x1b, 0x5d, 0x14, 0x98, 0x7c, 0x0a, 0x50, 0x28, + 0x60, 0xa9, 0xc8, 0x5a, 0x15, 0xdb, 0x39, 0xd7, 0xaa, 0x38, 0x15, 0xd9, 0xcb, 0x4d, 0x9c, 0xd0, + 0xea, 0x0d, 0x72, 0xa1, 0x7c, 0x7a, 0x4e, 0xfd, 0xe6, 0x13, 0x52, 0x83, 0xd2, 0xc9, 0xe5, 0x9b, + 0xa6, 0xf3, 0x32, 0x84, 0x2a, 0x15, 0x43, 0x63, 0x5c, 0x07, 0x38, 0xbe, 0x66, 0xfb, 0xaf, 0xba, + 0x6c, 0xbf, 0xb7, 0x8d, 0x2e, 0x88, 0xaf, 0x2f, 0xd9, 0xc1, 0x76, 0x87, 0x1d, 0x74, 0xf6, 0x9b, + 0x8e, 0xc1, 0xfd, 0x33, 0xd6, 0xeb, 0x1d, 0xb0, 0xde, 0x7e, 0xaf, 0xf9, 0x94, 0x00, 0x54, 0xd1, + 0x7f, 0xb7, 0xdb, 0x6d, 0x96, 0x8c, 0xcd, 0xbf, 0x66, 0x07, 0x3b, 0x7b, 0xd6, 0xb7, 0xbc, 0xf4, + 0xdd, 0xed, 0x6d, 0xb3, 0xbd, 0x9d, 0xed, 0x66, 0xe5, 0xb0, 0xf9, 0xe3, 0xef, 0x1b, 0xce, 0xcf, + 0xf8, 0xfb, 0x0d, 0x7f, 0xef, 0xfe, 0xd8, 0x78, 0x72, 0x53, 0xb5, 0x2f, 0x7f, 0xf7, 0xcf, 0x00, + 0x00, 0x00, 0xff, 0xff, 0x98, 0xaf, 0x2d, 0x84, 0x77, 0x06, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index a51329c61..4828c575b 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -28,6 +28,11 @@ message ActivationMetadata { bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes dev_addr = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; bytes nwk_s_key = 4 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + + uint32 rx1_dr_offset = 11; + uint32 rx2_dr = 12; + uint32 rx_delay = 13; + repeated uint64 cf_list = 14; } message PHYPayload { diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 074b099c2..7e9658e99 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -8,7 +8,6 @@ import ( pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" - pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -66,7 +65,8 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes return res, nil } -const nwkID = 0x13 +var netID = [3]byte{0x14, 0x14, 0x14} // TODO: Change to actual NetID +var nwkID byte = 0x13 func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { // Build activation metadata if not present @@ -75,21 +75,51 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat } // Build lorawan metadata if not present if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - activation.ActivationMetadata.Protocol = &pb_protocol.ActivationMetadata_Lorawan{ - Lorawan: &pb_lorawan.ActivationMetadata{ - DevEui: activation.DevEui, - AppEui: activation.AppEui, - }, - } + return nil, errors.New("ttn/networkserver: Can only handle LoRaWAN activations") } + + // Build response template if not present + if pld := activation.GetResponseTemplate(); pld == nil { + return nil, errors.New("ttn/networkserver: Activation does not contain a response template") + } + lorawanMeta := activation.ActivationMetadata.GetLorawan() + // Generate random DevAddr // TODO: Be smarter than just randomly generating addresses. var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) devAddr[0] = (nwkID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb - // Set the DevAddr in the Activation - activation.ActivationMetadata.GetLorawan().DevAddr = &devAddr + // Set the DevAddr in the Activation Metadata + lorawanMeta.DevAddr = &devAddr + + // Build JoinAccept Payload + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinAccept, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinAcceptPayload{ + NetID: netID, + DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)}, + RXDelay: uint8(lorawanMeta.RxDelay), + DevAddr: lorawan.DevAddr(devAddr), + }, + } + if len(lorawanMeta.CfList) == 5 { + var cfList lorawan.CFList + for i, cfListItem := range lorawanMeta.CfList { + cfList[i] = uint32(cfListItem) + } + phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList + } + + // Set the Payload + phyBytes, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + activation.ResponseTemplate.Payload = phyBytes return activation, nil } @@ -143,8 +173,30 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag message.ResponseTemplate.AppEui = message.AppEui message.ResponseTemplate.DevEui = message.DevEui + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: macPayload.FHDR.DevAddr, + FCtrl: lorawan.FCtrl{ + ACK: phyPayload.MHDR.MType == lorawan.ConfirmedDataUp, + }, + FCnt: dev.FCntDown, + }, + FPort: macPayload.FPort, + }, + } + phyBytes, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + // TODO: Maybe we need to add MAC commands on downlink - message.ResponseTemplate.Payload = []byte{} + + message.ResponseTemplate.Payload = phyBytes return message, nil } diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 9f835c781..e728a9045 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -130,11 +130,27 @@ func TestHandleGetDevices(t *testing.T) { func TestHandlePrepareActivation(t *testing.T) { a := New(t) ns := &networkServer{} - resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{}) + resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) a.So(err, ShouldBeNil) devAddr := resp.ActivationMetadata.GetLorawan().DevAddr a.So(devAddr.IsEmpty(), ShouldBeFalse) a.So(devAddr[0]&254, ShouldEqual, 19<<1) // 7 MSB should be NetID + + var resPHY lorawan.PHYPayload + resPHY.UnmarshalBinary(resp.ResponseTemplate.Payload) + resMAC, _ := resPHY.MACPayload.(*lorawan.DataPayload) + joinAccept := &lorawan.JoinAcceptPayload{} + joinAccept.UnmarshalBinary(false, resMAC.Bytes) + + a.So(joinAccept.DevAddr[0]&254, ShouldEqual, 19<<1) + a.So(*joinAccept.CFList, ShouldEqual, lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000}) } func TestHandleActivate(t *testing.T) { @@ -227,6 +243,13 @@ func TestHandleUplink(t *testing.T) { a.So(err, ShouldBeNil) a.So(res.ResponseTemplate, ShouldNotBeNil) + // LoRaWAN: Unmarshal + var phyPayload lorawan.PHYPayload + phyPayload.UnmarshalBinary(res.ResponseTemplate.Payload) + macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + + a.So([4]byte(macPayload.FHDR.DevAddr), ShouldEqual, [4]byte(devAddr)) + // Frame Counter should have been updated dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntUp, ShouldEqual, 1) diff --git a/core/router/activation.go b/core/router/activation.go index 39dfafdce..c50b5eea3 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -5,6 +5,8 @@ import ( "sync" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -30,6 +32,42 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De return nil, err } + // Prepare request + request := &pb_broker.DeviceActivationRequest{ + Payload: activation.Payload, + DevEui: activation.DevEui, + AppEui: activation.AppEui, + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + ActivationMetadata: &pb_protocol.ActivationMetadata{ + Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + AppEui: activation.AppEui, + DevEui: activation.DevEui, + }, + }, + }, + DownlinkOptions: downlinkOptions, + } + + // Prepare LoRaWAN activation + status, err := gateway.Status.Get() + if err != nil { + return nil, err + } + band, err := getBand(status.Region) + if err != nil { + return nil, err + } + lorawan := request.ActivationMetadata.GetLorawan() + lorawan.Rx1DrOffset = 0 + lorawan.Rx2Dr = uint32(band.RX2DataRate) + lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds()) + switch status.Region { + case "EU_863_870": + lorawan.CfList = []uint64{867100000, 867300000, 867500000, 867700000, 867900000} + } + // Forward to all brokers and collect responses var wg sync.WaitGroup responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers)) @@ -42,14 +80,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Do async request wg.Add(1) go func() { - res, err := broker.client.Activate(r.getContext(), &pb_broker.DeviceActivationRequest{ - Payload: activation.Payload, - DevEui: activation.DevEui, - AppEui: activation.AppEui, - ProtocolMetadata: activation.ProtocolMetadata, - GatewayMetadata: activation.GatewayMetadata, - DownlinkOptions: downlinkOptions, - }) + res, err := broker.client.Activate(r.getContext(), request) if err == nil && res != nil { responses <- res } diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 9ecb79b0a..5f174c822 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -13,7 +13,9 @@ func TestHandleActivation(t *testing.T) { a := New(t) r := &router{ - gateways: map[types.GatewayEUI]*gateway.Gateway{}, + gateways: map[types.GatewayEUI]*gateway.Gateway{ + types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}: newReferenceGateway("EU_863_870"), + }, brokerDiscovery: &mockBrokerDiscovery{}, } From a66e235681e48f161ce2ee823be5501e94532350 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 17:45:11 +0200 Subject: [PATCH 1472/2266] Use core/types in OTAA key calculation --- core/otaa/session_keys.go | 3 ++- core/otaa/session_keys_test.go | 7 ++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/core/otaa/session_keys.go b/core/otaa/session_keys.go index d54ee3bc4..eb345311d 100644 --- a/core/otaa/session_keys.go +++ b/core/otaa/session_keys.go @@ -6,12 +6,13 @@ package otaa import ( "crypto/aes" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" ) // CalculateSessionKeys calculates the AppSKey and NwkSKey // All arguments are MSB-first -func CalculateSessionKeys(appKey [16]byte, appNonce [3]byte, netID [3]byte, devNonce [2]byte) (appSKey, nwkSKey [16]byte, err error) { +func CalculateSessionKeys(appKey types.AppKey, appNonce [3]byte, netID [3]byte, devNonce [2]byte) (appSKey types.AppSKey, nwkSKey types.NwkSKey, err error) { buf := make([]byte, 16) copy(buf[1:4], reverse(appNonce[:])) diff --git a/core/otaa/session_keys_test.go b/core/otaa/session_keys_test.go index a58baec47..e3251fb56 100644 --- a/core/otaa/session_keys_test.go +++ b/core/otaa/session_keys_test.go @@ -6,6 +6,7 @@ package otaa import ( "testing" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) @@ -13,7 +14,7 @@ func TestCalculateSessionKeys(t *testing.T) { a := New(t) // MSB first - appKey := [16]byte{0xBE, 0xC4, 0x99, 0xC6, 0x9E, 0x9C, 0x93, 0x9E, 0x41, 0x3B, 0x66, 0x39, 0x61, 0x63, 0x6C, 0x61} + appKey := types.AppKey{0xBE, 0xC4, 0x99, 0xC6, 0x9E, 0x9C, 0x93, 0x9E, 0x41, 0x3B, 0x66, 0x39, 0x61, 0x63, 0x6C, 0x61} devNonce := [2]byte{0x73, 0x69} netID := [3]byte{0x00, 0x00, 0x00} appNonce := [3]byte{0xAE, 0x3B, 0x1C} @@ -22,8 +23,8 @@ func TestCalculateSessionKeys(t *testing.T) { a.So(err, ShouldBeNil) // MSB first - expectedNwkSKey := [16]byte{0x33, 0xD5, 0xF3, 0x74, 0x29, 0xDA, 0x60, 0xF0, 0xA5, 0x7A, 0xB5, 0xAA, 0x06, 0x95, 0xE4, 0x98} - expectedAppSKey := [16]byte{0x71, 0x4F, 0xA5, 0x53, 0x03, 0x07, 0xD6, 0x03, 0xE8, 0x7C, 0x78, 0x65, 0xDF, 0x86, 0x2A, 0x85} + expectedNwkSKey := types.NwkSKey{0x33, 0xD5, 0xF3, 0x74, 0x29, 0xDA, 0x60, 0xF0, 0xA5, 0x7A, 0xB5, 0xAA, 0x06, 0x95, 0xE4, 0x98} + expectedAppSKey := types.AppSKey{0x71, 0x4F, 0xA5, 0x53, 0x03, 0x07, 0xD6, 0x03, 0xE8, 0x7C, 0x78, 0x65, 0xDF, 0x86, 0x2A, 0x85} a.So(appSKey, ShouldResemble, expectedAppSKey) a.So(nwkSKey, ShouldResemble, expectedNwkSKey) From 7c7c73f6bfecbe9b2fa955a96ce1cb82e4abc59d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 18:38:35 +0200 Subject: [PATCH 1473/2266] Fix govet errors --- core/broker/uplink_test.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 99dc0f955..5b75fbe5c 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -57,6 +57,10 @@ func TestHandleUplink(t *testing.T) { }) a.So(err, ShouldEqual, ErrNotFound) + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} + // Add devices b = &broker{ handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), @@ -64,16 +68,16 @@ func TestHandleUplink(t *testing.T) { ns: &mockNetworkServer{ devices: []*pb_networkserver.DevicesResponse_Device{ &pb_networkserver.DevicesResponse_Device{ - DevEui: &types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - AppEui: &types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - NwkSKey: &types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + DevEui: &devEUI, + AppEui: &appEUI, + NwkSKey: &nwkSKey, FCnt: 3, }, }, }, applications: application.NewApplicationStore(), } - b.applications.Set(&application.Application{AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, HandlerID: "handlerID"}) + b.applications.Set(&application.Application{AppEUI: appEUI, HandlerID: "handlerID"}) b.handlers["handlerID"] = make(chan *pb.DeduplicatedUplinkMessage, 10) // Device doesn't match From 4c203546b4cd4b209c60bc2c374a00d62881405a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 30 May 2016 18:50:48 +0200 Subject: [PATCH 1474/2266] Use TTN's actual NetID --- core/networkserver/networkserver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 7e9658e99..de845b5e7 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -65,7 +65,7 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes return res, nil } -var netID = [3]byte{0x14, 0x14, 0x14} // TODO: Change to actual NetID +var netID = [3]byte{0x00, 0x00, 0x13} var nwkID byte = 0x13 func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { From 367c85195e99ad5be33809c8829909ca06efad0c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 31 May 2016 11:48:10 +0200 Subject: [PATCH 1475/2266] Communicate full FCnt in metadata Only the 16 lsb are stored in binary payload --- api/networkserver/networkserver.pb.go | 131 +++++++++++++-------- api/networkserver/networkserver.proto | 5 +- api/protocol/lorawan/lorawan.pb.go | 163 +++++++++++++++++--------- api/protocol/lorawan/lorawan.proto | 4 + core/broker/uplink.go | 18 ++- core/broker/uplink_test.go | 21 ++-- core/networkserver/networkserver.go | 14 ++- 7 files changed, 230 insertions(+), 126 deletions(-) diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 91344b262..38873d85c 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -73,7 +73,8 @@ type DevicesResponse_Device struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - FCnt uint32 `protobuf:"varint,4,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + StoredFCnt uint32 `protobuf:"varint,4,opt,name=stored_f_cnt,json=storedFCnt,proto3" json:"stored_f_cnt,omitempty"` + FullFCnt uint32 `protobuf:"varint,5,opt,name=full_f_cnt,json=fullFCnt,proto3" json:"full_f_cnt,omitempty"` DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` } @@ -550,10 +551,15 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { } i += n4 } - if m.FCnt != 0 { + if m.StoredFCnt != 0 { data[i] = 0x20 i++ - i = encodeVarintNetworkserver(data, i, uint64(m.FCnt)) + i = encodeVarintNetworkserver(data, i, uint64(m.StoredFCnt)) + } + if m.FullFCnt != 0 { + data[i] = 0x28 + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.FullFCnt)) } if m.DisableFCntCheck { data[i] = 0x58 @@ -709,8 +715,11 @@ func (m *DevicesResponse_Device) Size() (n int) { l = m.NwkSKey.Size() n += 1 + l + sovNetworkserver(uint64(l)) } - if m.FCnt != 0 { - n += 1 + sovNetworkserver(uint64(m.FCnt)) + if m.StoredFCnt != 0 { + n += 1 + sovNetworkserver(uint64(m.StoredFCnt)) + } + if m.FullFCnt != 0 { + n += 1 + sovNetworkserver(uint64(m.FullFCnt)) } if m.DisableFCntCheck { n += 2 @@ -1065,9 +1074,9 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { iNdEx = postIndex case 4: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field StoredFCnt", wireType) } - m.FCnt = 0 + m.StoredFCnt = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowNetworkserver @@ -1077,7 +1086,26 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift + m.StoredFCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FullFCnt", wireType) + } + m.FullFCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FullFCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } @@ -1432,48 +1460,49 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 673 bytes of a gzipped FileDescriptorProto + // 696 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x4e, 0x13, 0x41, - 0x14, 0xb6, 0x14, 0xdb, 0x32, 0x50, 0x28, 0x83, 0x84, 0xa6, 0x51, 0xd4, 0x46, 0x23, 0x89, 0xa1, - 0x8d, 0x25, 0x6a, 0x4c, 0x4c, 0xa4, 0xfc, 0x68, 0x94, 0x40, 0x70, 0x81, 0xeb, 0xcd, 0x76, 0xf7, - 0xd0, 0x4e, 0x5a, 0x66, 0xd7, 0x99, 0xd9, 0x56, 0xde, 0xc4, 0xf8, 0x0a, 0xbe, 0x83, 0xd7, 0x5e, - 0x7a, 0xed, 0x85, 0x31, 0x7a, 0xe5, 0x5b, 0x78, 0x76, 0x66, 0x0a, 0x2e, 0x82, 0xfc, 0x5c, 0x4c, - 0x76, 0xe6, 0xcc, 0xf7, 0x7d, 0x73, 0xe6, 0x9c, 0x6f, 0x96, 0xac, 0xb7, 0x99, 0xea, 0xc4, 0xad, - 0x9a, 0x1f, 0x1e, 0xd4, 0x77, 0x3b, 0xb0, 0xdb, 0x61, 0xbc, 0x2d, 0xb7, 0x40, 0x0d, 0x42, 0xd1, - 0xad, 0x2b, 0xc5, 0xeb, 0x5e, 0xc4, 0xea, 0xdc, 0xac, 0x25, 0x88, 0x3e, 0x88, 0xf4, 0xaa, 0x16, - 0x89, 0x50, 0x85, 0xb4, 0x98, 0x0a, 0x56, 0x16, 0xff, 0x52, 0x6d, 0x87, 0xed, 0xb0, 0xae, 0x51, - 0xad, 0x78, 0x5f, 0xaf, 0xf4, 0x42, 0xcf, 0x0c, 0x3b, 0x05, 0x3f, 0x33, 0x09, 0x1c, 0x16, 0xfe, - 0xf4, 0x22, 0xf0, 0x96, 0x08, 0xbb, 0x98, 0xac, 0xf9, 0x58, 0xe2, 0xb3, 0x8b, 0x10, 0x3b, 0x1e, - 0x0f, 0x7a, 0xc8, 0xb4, 0x5f, 0x43, 0xad, 0xbe, 0x27, 0x93, 0x6b, 0xd0, 0x67, 0x3e, 0x48, 0x07, - 0xde, 0xc5, 0x20, 0x15, 0x7d, 0x4b, 0x0a, 0x01, 0xf4, 0x5d, 0x2f, 0x08, 0x44, 0x39, 0x73, 0x27, - 0xb3, 0x30, 0xb1, 0xf2, 0xe4, 0xdb, 0xf7, 0xdb, 0x8d, 0xf3, 0x8e, 0xf0, 0x43, 0x01, 0x75, 0x75, - 0x18, 0x81, 0xac, 0xa1, 0x60, 0x13, 0xd9, 0x4e, 0x3e, 0x30, 0x13, 0x3a, 0x43, 0xae, 0xef, 0xbb, - 0x3e, 0x57, 0xe5, 0x11, 0xd4, 0x2b, 0x3a, 0xa3, 0xfb, 0xab, 0x5c, 0x55, 0x3f, 0x67, 0xc9, 0xd4, - 0xd1, 0xd1, 0x32, 0x0a, 0xb9, 0x04, 0xfa, 0x82, 0xe4, 0x05, 0xc8, 0xb8, 0xa7, 0x24, 0x1e, 0x9d, - 0x5d, 0x18, 0x6f, 0xdc, 0xaf, 0xa5, 0xbb, 0x72, 0x82, 0x60, 0xd7, 0xce, 0x90, 0x55, 0xf9, 0x3d, - 0x42, 0x72, 0x26, 0x46, 0xb7, 0x48, 0xde, 0x8b, 0x22, 0x17, 0x62, 0x66, 0xaf, 0xf1, 0x18, 0xaf, - 0xf1, 0xe8, 0x12, 0xd7, 0x68, 0x46, 0xd1, 0xfa, 0xde, 0x6b, 0x27, 0x87, 0x2a, 0xeb, 0x31, 0x4b, - 0xf4, 0x92, 0xba, 0x24, 0x7a, 0x23, 0x57, 0xd2, 0xc3, 0xbc, 0xb4, 0x1e, 0xaa, 0x24, 0x7a, 0x0e, - 0x19, 0xe3, 0x83, 0xae, 0x2b, 0xdd, 0x2e, 0x1c, 0x96, 0xb3, 0x57, 0x2a, 0xf4, 0xd6, 0xa0, 0xbb, - 0xb3, 0x01, 0x87, 0x4e, 0x9e, 0x9b, 0xc9, 0x71, 0xa1, 0x47, 0x8f, 0x0b, 0x4d, 0x17, 0xc9, 0x4c, - 0xc0, 0xa4, 0xd7, 0xea, 0x81, 0xab, 0x37, 0x5d, 0xbf, 0x03, 0x7e, 0xb7, 0x3c, 0x8e, 0x90, 0x82, - 0x53, 0xb2, 0x5b, 0x2f, 0x11, 0xb9, 0x9a, 0xc4, 0xe9, 0x03, 0x52, 0x8a, 0x25, 0xc8, 0xa5, 0x86, - 0xdb, 0x62, 0xca, 0x30, 0xca, 0x13, 0x1a, 0x5b, 0x34, 0xf1, 0x15, 0xa6, 0x12, 0x74, 0x75, 0x8e, - 0xcc, 0x3a, 0xd0, 0x66, 0x52, 0x81, 0xb0, 0x6d, 0x30, 0x0e, 0xaa, 0x4e, 0x91, 0xe2, 0x8e, 0xf2, - 0x54, 0x3c, 0xb4, 0x54, 0xf5, 0x0d, 0xc9, 0x99, 0x00, 0x5d, 0xc6, 0x5c, 0x4c, 0x0b, 0xdd, 0x08, - 0x84, 0x36, 0x19, 0x48, 0xa9, 0x1b, 0x34, 0xde, 0x28, 0xd5, 0x92, 0xb7, 0xb0, 0x0d, 0xc2, 0x07, - 0xae, 0x58, 0x0f, 0xdb, 0x3c, 0x6d, 0xc1, 0x18, 0x6b, 0x1a, 0x68, 0xe3, 0x53, 0x96, 0x14, 0x6d, - 0x39, 0x76, 0xb4, 0x27, 0xe8, 0x06, 0x21, 0xaf, 0x40, 0x59, 0x67, 0xd0, 0x5b, 0x67, 0x39, 0x46, - 0xa7, 0x52, 0x99, 0xff, 0xbf, 0xa1, 0xe8, 0x01, 0x99, 0xde, 0x16, 0x10, 0x79, 0x02, 0x9a, 0xbe, - 0x62, 0x7d, 0x4f, 0xb1, 0x90, 0xd3, 0x87, 0x35, 0xfb, 0xdc, 0xd6, 0x20, 0x88, 0xa3, 0x1e, 0xf3, - 0x3d, 0x05, 0x81, 0x61, 0x1e, 0xa3, 0x86, 0x27, 0x5c, 0x06, 0x4c, 0xb7, 0x49, 0xc1, 0x06, 0x81, - 0xde, 0xad, 0x0d, 0x9f, 0xe6, 0xbf, 0x68, 0x93, 0x5d, 0xe5, 0x7c, 0x08, 0xda, 0x34, 0xb7, 0x87, - 0xa7, 0xf2, 0x2e, 0xea, 0x9d, 0x92, 0x88, 0xd9, 0xdb, 0xc4, 0x4a, 0x7a, 0xed, 0x44, 0xef, 0x3c, - 0x08, 0x7d, 0x4e, 0x0a, 0x6b, 0xe1, 0x80, 0x6b, 0xc5, 0xb9, 0x23, 0xb8, 0x8d, 0x0c, 0x75, 0xce, - 0xda, 0x68, 0x7c, 0xcc, 0x90, 0x1b, 0xa9, 0x6e, 0x6d, 0x7a, 0x1c, 0xe3, 0x02, 0x8d, 0x30, 0x99, - 0x36, 0x0f, 0xbd, 0x77, 0xa2, 0x33, 0xa7, 0x7a, 0xab, 0x52, 0xd0, 0x1e, 0x69, 0xa2, 0x4f, 0x97, - 0xc9, 0x18, 0xb6, 0xdd, 0xfa, 0xea, 0xe6, 0x09, 0x72, 0xca, 0x7f, 0x95, 0xd9, 0x53, 0x77, 0x57, - 0x4a, 0x5f, 0x7e, 0xce, 0x67, 0xbe, 0xe2, 0xf8, 0x81, 0xe3, 0xc3, 0xaf, 0xf9, 0x6b, 0xad, 0x9c, - 0xfe, 0x29, 0x2e, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x0e, 0xef, 0xf8, 0xb4, 0x3e, 0x06, 0x00, - 0x00, + 0x14, 0xb6, 0x14, 0xda, 0x32, 0x50, 0x28, 0x83, 0x84, 0xa6, 0x41, 0xc4, 0x46, 0x23, 0x89, 0x61, + 0x1b, 0x4b, 0xd4, 0x98, 0x98, 0x48, 0xf9, 0xd1, 0x28, 0x81, 0xe0, 0x02, 0xd7, 0x9b, 0xed, 0xee, + 0x69, 0x3b, 0xd9, 0x65, 0x76, 0x9d, 0x99, 0x6d, 0xe5, 0x3d, 0xbc, 0x30, 0xbe, 0x82, 0x2f, 0xe2, + 0xa5, 0xd7, 0x5e, 0x18, 0xa3, 0x57, 0xbe, 0x85, 0xb3, 0x33, 0x53, 0x74, 0x11, 0xe4, 0xe7, 0x62, + 0xd3, 0x99, 0xef, 0x7c, 0xe7, 0x9b, 0x33, 0xe7, 0x7c, 0x53, 0xb4, 0xd5, 0x25, 0xa2, 0x97, 0xb4, + 0x2d, 0x2f, 0x3a, 0x6a, 0x1c, 0xf4, 0xe0, 0xa0, 0x47, 0x68, 0x97, 0xef, 0x82, 0x18, 0x44, 0x2c, + 0x68, 0x08, 0x41, 0x1b, 0x6e, 0x4c, 0x1a, 0x54, 0xef, 0x39, 0xb0, 0x3e, 0xb0, 0xec, 0xce, 0x8a, + 0x59, 0x24, 0x22, 0x5c, 0xce, 0x80, 0xb5, 0x95, 0xbf, 0x54, 0xbb, 0x51, 0x37, 0x6a, 0x28, 0x56, + 0x3b, 0xe9, 0xa8, 0x9d, 0xda, 0xa8, 0x95, 0xce, 0xce, 0xd0, 0xcf, 0x2d, 0x42, 0x7e, 0x86, 0xfe, + 0xe4, 0x32, 0xf4, 0x36, 0x8b, 0x02, 0x59, 0xac, 0xfe, 0x31, 0x89, 0x4f, 0x2f, 0x93, 0xd8, 0x73, + 0xa9, 0x1f, 0xca, 0x4c, 0xf3, 0xab, 0x53, 0xeb, 0xef, 0xd0, 0xd4, 0x26, 0xf4, 0x89, 0x07, 0xdc, + 0x86, 0xb7, 0x09, 0x70, 0x81, 0xdf, 0xa0, 0x92, 0x0f, 0x7d, 0xc7, 0xf5, 0x7d, 0x56, 0xcd, 0x2d, + 0xe5, 0x96, 0x27, 0xd7, 0x1f, 0x7f, 0xfd, 0x76, 0xbb, 0x79, 0xd1, 0x11, 0x5e, 0xc4, 0xa0, 0x21, + 0x8e, 0x63, 0xe0, 0x96, 0x14, 0x6c, 0xc9, 0x6c, 0xbb, 0xe8, 0xeb, 0x05, 0x9e, 0x45, 0x63, 0x1d, + 0xc7, 0xa3, 0xa2, 0x3a, 0x22, 0xf5, 0xca, 0xf6, 0x68, 0x67, 0x83, 0x8a, 0xfa, 0xaf, 0x3c, 0x9a, + 0x3e, 0x39, 0x9a, 0xc7, 0x11, 0xe5, 0x80, 0x9f, 0xa3, 0x22, 0x03, 0x9e, 0x84, 0x82, 0xcb, 0xa3, + 0xf3, 0xcb, 0x13, 0xcd, 0x7b, 0x56, 0x76, 0x2a, 0xa7, 0x12, 0xcc, 0xde, 0x1e, 0x66, 0xd5, 0xde, + 0xe7, 0x51, 0x41, 0x63, 0x78, 0x17, 0x15, 0xdd, 0x38, 0x76, 0x20, 0x21, 0xe6, 0x1a, 0x8f, 0xe4, + 0x35, 0x1e, 0x5e, 0xe1, 0x1a, 0xad, 0x38, 0xde, 0x3a, 0x7c, 0x65, 0x17, 0xa4, 0xca, 0x56, 0x42, + 0x52, 0xbd, 0xb4, 0x2f, 0xa9, 0xde, 0xc8, 0xb5, 0xf4, 0x64, 0x5d, 0x4a, 0x4f, 0xaa, 0xa4, 0x7a, + 0x36, 0x1a, 0xa7, 0x83, 0xc0, 0xe1, 0x4e, 0x00, 0xc7, 0xd5, 0xfc, 0xb5, 0x1a, 0xbd, 0x3b, 0x08, + 0xf6, 0xb7, 0xe1, 0xd8, 0x2e, 0x52, 0xbd, 0xc0, 0x4b, 0x68, 0x92, 0x0b, 0x19, 0xf7, 0x1d, 0xdd, + 0xef, 0x51, 0xd5, 0x6f, 0xa4, 0xb1, 0x17, 0xb2, 0xeb, 0x78, 0x01, 0xa1, 0x4e, 0x12, 0x86, 0x26, + 0x3e, 0xa6, 0xe2, 0xa5, 0x14, 0x51, 0xd1, 0x15, 0x34, 0xeb, 0x13, 0xee, 0xb6, 0x43, 0xd0, 0x04, + 0xc7, 0xeb, 0x81, 0x17, 0x54, 0x27, 0x24, 0xad, 0x64, 0x57, 0x4c, 0x28, 0x65, 0x6e, 0xa4, 0x38, + 0xbe, 0x8f, 0x2a, 0x09, 0x07, 0xbe, 0xda, 0x74, 0xda, 0x44, 0x18, 0xc9, 0x49, 0xc5, 0x2d, 0x6b, + 0x7c, 0x9d, 0x88, 0x94, 0x5d, 0x9f, 0x47, 0x73, 0x36, 0x74, 0x09, 0x17, 0xc0, 0xcc, 0xc4, 0xb4, + 0xd9, 0xea, 0xd3, 0xa8, 0xbc, 0x2f, 0x5c, 0x91, 0x0c, 0xdd, 0x57, 0x7f, 0x8d, 0x0a, 0x1a, 0xc0, + 0x6b, 0xb2, 0x16, 0x3d, 0x6d, 0x27, 0x06, 0xa6, 0xfc, 0x08, 0x9c, 0xab, 0x59, 0x4e, 0x34, 0x2b, + 0x56, 0xfa, 0x6c, 0xf6, 0x80, 0x79, 0x40, 0x05, 0x09, 0xa5, 0x23, 0x66, 0x0c, 0x59, 0x62, 0x2d, + 0x4d, 0x6d, 0x7e, 0xca, 0xa3, 0xb2, 0xe9, 0xdc, 0xbe, 0xb2, 0x0f, 0xde, 0x46, 0xe8, 0x25, 0x08, + 0x63, 0x22, 0x7c, 0xeb, 0x3c, 0x73, 0xa9, 0x52, 0x6a, 0x8b, 0xff, 0xf7, 0x1e, 0x3e, 0x42, 0x33, + 0x7b, 0x0c, 0x62, 0x97, 0x41, 0xcb, 0x13, 0xa4, 0xef, 0x0a, 0x12, 0x51, 0xfc, 0xc0, 0x32, 0x2f, + 0x73, 0x13, 0xfc, 0x24, 0x0e, 0x89, 0xe7, 0x0a, 0xf0, 0x75, 0xe6, 0x1f, 0xd6, 0xf0, 0x84, 0xab, + 0x90, 0xf1, 0x1e, 0x2a, 0x19, 0x10, 0xf0, 0x1d, 0x6b, 0xf8, 0x8a, 0xff, 0x65, 0xeb, 0xea, 0x6a, + 0x17, 0x53, 0xa4, 0xa3, 0x0b, 0x87, 0xf2, 0x54, 0x1a, 0x48, 0xbd, 0x33, 0x0a, 0xd1, 0xb1, 0x1d, + 0xd9, 0x49, 0xb7, 0x9b, 0xea, 0x5d, 0x44, 0xc1, 0xcf, 0x50, 0x69, 0x33, 0x1a, 0x50, 0xa5, 0x38, + 0x7f, 0x42, 0x37, 0xc8, 0x50, 0xe7, 0xbc, 0x40, 0xf3, 0x63, 0x0e, 0xdd, 0xcc, 0x4c, 0x6b, 0xc7, + 0xa5, 0x12, 0x67, 0xd2, 0x08, 0x53, 0x59, 0xf3, 0xe0, 0xbb, 0xa7, 0x26, 0x73, 0xa6, 0xb7, 0x6a, + 0x25, 0xe5, 0x91, 0x96, 0xf4, 0xe9, 0x1a, 0x1a, 0x97, 0x63, 0x37, 0xbe, 0x5a, 0x38, 0x95, 0x9c, + 0xf1, 0x5f, 0x6d, 0xee, 0xcc, 0xe8, 0x7a, 0xe5, 0xf3, 0x8f, 0xc5, 0xdc, 0x17, 0xf9, 0x7d, 0x97, + 0xdf, 0x87, 0x9f, 0x8b, 0x37, 0xda, 0x05, 0xf5, 0xff, 0xb9, 0xfa, 0x3b, 0x00, 0x00, 0xff, 0xff, + 0x6b, 0xba, 0xbc, 0xa2, 0x69, 0x06, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 346372309..05713b76b 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -19,9 +19,10 @@ message DevicesResponse { bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; - uint32 f_cnt = 4; + uint32 stored_f_cnt = 4; + uint32 full_f_cnt = 5; bool disable_f_cnt_check = 11; - bool uses32_bit_f_cnt = 12; + bool uses32_bit_f_cnt = 12; } repeated Device results = 1; } diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index a25976f3a..b6bebafc1 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -97,6 +97,7 @@ type Metadata struct { DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` + FCnt uint32 `protobuf:"varint,15,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -109,6 +110,7 @@ type TxConfiguration struct { DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` BitRate uint32 `protobuf:"varint,13,opt,name=bit_rate,json=bitRate,proto3" json:"bit_rate,omitempty"` CodingRate string `protobuf:"bytes,14,opt,name=coding_rate,json=codingRate,proto3" json:"coding_rate,omitempty"` + FCnt uint32 `protobuf:"varint,15,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` } func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } @@ -266,6 +268,11 @@ func (m *Metadata) MarshalTo(data []byte) (int, error) { i = encodeVarintLorawan(data, i, uint64(len(m.CodingRate))) i += copy(data[i:], m.CodingRate) } + if m.FCnt != 0 { + data[i] = 0x78 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + } return i, nil } @@ -306,6 +313,11 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { i = encodeVarintLorawan(data, i, uint64(len(m.CodingRate))) i += copy(data[i:], m.CodingRate) } + if m.FCnt != 0 { + data[i] = 0x78 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + } return i, nil } @@ -659,6 +671,9 @@ func (m *Metadata) Size() (n int) { if l > 0 { n += 1 + l + sovLorawan(uint64(l)) } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } return n } @@ -679,6 +694,9 @@ func (m *TxConfiguration) Size() (n int) { if l > 0 { n += 1 + l + sovLorawan(uint64(l)) } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } return n } @@ -948,6 +966,25 @@ func (m *Metadata) Unmarshal(data []byte) error { } m.CodingRate = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -1094,6 +1131,25 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } m.CodingRate = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -2168,57 +2224,58 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 828 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x54, 0xcd, 0x6e, 0x23, 0x45, - 0x10, 0xde, 0x59, 0xff, 0x4d, 0xca, 0xeb, 0x60, 0xf5, 0x82, 0x30, 0x3f, 0x4a, 0x60, 0x24, 0xa4, - 0xd5, 0x4a, 0xc4, 0x89, 0x9d, 0xac, 0x93, 0xe3, 0xc4, 0x49, 0xb4, 0x68, 0xe3, 0x24, 0x74, 0x92, - 0x03, 0xa7, 0x56, 0x67, 0xa6, 0xc7, 0x1e, 0x66, 0x3c, 0x3d, 0xdb, 0x6e, 0xc7, 0xf6, 0x0b, 0xf0, - 0x0c, 0x7b, 0xe3, 0x05, 0x78, 0x10, 0xc4, 0x89, 0x33, 0x07, 0x84, 0xe0, 0x45, 0xa8, 0xee, 0x71, - 0xe2, 0x20, 0x21, 0x21, 0x22, 0x0e, 0x1c, 0xac, 0xe9, 0xaf, 0xab, 0xea, 0xab, 0xaf, 0x7e, 0xdc, - 0x70, 0x38, 0x8c, 0xf5, 0x68, 0x7a, 0xb3, 0x15, 0xc8, 0x71, 0xfb, 0x6a, 0x24, 0xae, 0x46, 0x71, - 0x36, 0x9c, 0x9c, 0x09, 0x3d, 0x93, 0x2a, 0x69, 0x6b, 0x9d, 0xb5, 0x79, 0x1e, 0xb7, 0x73, 0x25, - 0xb5, 0x0c, 0x64, 0xda, 0x4e, 0xa5, 0xe2, 0x33, 0x9e, 0xdd, 0x7d, 0xb7, 0xac, 0x81, 0xd4, 0x96, - 0xf0, 0xe3, 0x2f, 0x1f, 0x90, 0x0d, 0xe5, 0x50, 0x16, 0x81, 0x37, 0xd3, 0xc8, 0x22, 0x0b, 0xec, - 0xa9, 0x88, 0xf3, 0xde, 0x39, 0xe0, 0x0e, 0x84, 0xe6, 0x21, 0xd7, 0x9c, 0x74, 0x01, 0xc6, 0x32, - 0x9c, 0xa6, 0x5c, 0xc7, 0x32, 0x6b, 0xd5, 0x3f, 0x73, 0x5e, 0xac, 0x77, 0x9e, 0x6f, 0xdd, 0x25, - 0x1a, 0xdc, 0x9b, 0xe8, 0x03, 0x37, 0xf2, 0x09, 0xac, 0x99, 0x60, 0xa6, 0xb8, 0x16, 0xad, 0x67, - 0x18, 0xb3, 0x46, 0x5d, 0x73, 0x41, 0x11, 0x93, 0x8f, 0xc0, 0xbd, 0x89, 0x75, 0x61, 0x6b, 0xa0, - 0xad, 0x41, 0x6b, 0x88, 0xad, 0x69, 0x13, 0xea, 0x81, 0x0c, 0xb1, 0xd4, 0xc2, 0xba, 0x6e, 0x23, - 0xa1, 0xb8, 0x32, 0x0e, 0xde, 0xf7, 0x0e, 0xbc, 0x77, 0x35, 0xef, 0xcb, 0x2c, 0x8a, 0x87, 0x53, - 0x55, 0x24, 0xfb, 0x7f, 0x29, 0xfc, 0xa9, 0x04, 0xc4, 0x0f, 0x74, 0x7c, 0x6b, 0xf3, 0xdc, 0xb7, - 0xf1, 0x0c, 0x6a, 0x3c, 0xcf, 0x99, 0x98, 0xc6, 0x2d, 0x07, 0x63, 0x9e, 0x1d, 0xee, 0xfd, 0xf2, - 0xeb, 0xe6, 0xce, 0x3f, 0x0d, 0x39, 0x90, 0x4a, 0xb4, 0xf5, 0x22, 0x17, 0x93, 0x2d, 0x3f, 0xcf, - 0x8f, 0xaf, 0xbf, 0xa2, 0x55, 0x64, 0x39, 0x9e, 0xc6, 0x86, 0x2f, 0x14, 0xb7, 0x96, 0xef, 0xe9, - 0xa3, 0xf8, 0x8e, 0xc4, 0xad, 0xe5, 0x43, 0x16, 0xc3, 0xf7, 0x35, 0xb8, 0x86, 0x8f, 0x87, 0xa1, - 0x6a, 0x95, 0x2c, 0xe1, 0x2b, 0x24, 0xec, 0xfc, 0x3b, 0x42, 0x1f, 0xa3, 0xa9, 0xd1, 0x65, 0x0e, - 0x84, 0xc2, 0x5a, 0x36, 0x4b, 0xd8, 0x84, 0x25, 0x62, 0xd1, 0x2a, 0x3f, 0x8a, 0xf3, 0x6c, 0x96, - 0x5c, 0xbe, 0x11, 0x0b, 0x5a, 0xcb, 0x8a, 0x03, 0xf1, 0xa0, 0xa1, 0xe6, 0x3b, 0x2c, 0x54, 0x4c, - 0x46, 0xd1, 0x44, 0x68, 0x3b, 0xee, 0x06, 0xad, 0xe3, 0xe5, 0x91, 0x3a, 0xb7, 0x57, 0xe4, 0x03, - 0xa8, 0xaa, 0x79, 0x07, 0x7d, 0xec, 0x5c, 0x1b, 0xb4, 0x82, 0xe8, 0x48, 0x99, 0xa1, 0xaa, 0x39, - 0x0b, 0x45, 0xca, 0x17, 0x77, 0x43, 0x55, 0xf3, 0x23, 0x03, 0xc9, 0x87, 0x50, 0x0b, 0x22, 0x96, - 0xc6, 0x13, 0x8d, 0x03, 0x2d, 0xbd, 0x28, 0xd3, 0x6a, 0x10, 0x9d, 0x22, 0xf2, 0x66, 0x00, 0x17, - 0xaf, 0xbf, 0xb9, 0xe0, 0x8b, 0x54, 0xf2, 0x90, 0x7c, 0x0e, 0xe5, 0xf1, 0x08, 0x69, 0xcd, 0x00, - 0xeb, 0x9d, 0xc6, 0x6a, 0xc5, 0x5e, 0x63, 0xd9, 0xd6, 0x44, 0x76, 0xa1, 0x3e, 0xf0, 0xfb, 0x2c, - 0x2f, 0x22, 0xec, 0x68, 0xea, 0x0f, 0x97, 0xd1, 0xef, 0x2f, 0xc9, 0x70, 0x19, 0xef, 0xcf, 0xa4, - 0x09, 0xa5, 0x71, 0x1c, 0x14, 0x7d, 0xa7, 0xe6, 0xe8, 0x75, 0xa1, 0x6c, 0x58, 0x4d, 0x2d, 0x63, - 0x66, 0x7a, 0x61, 0x93, 0x62, 0x2d, 0xe3, 0x2b, 0x04, 0xe4, 0x7d, 0xa8, 0x8c, 0xf9, 0xb7, 0x52, - 0xd9, 0x04, 0xe6, 0xd6, 0x00, 0x6f, 0x04, 0xb0, 0x4a, 0x80, 0xad, 0xaa, 0x44, 0xec, 0xef, 0xe4, - 0x9e, 0x58, 0xb9, 0xd1, 0x92, 0x3e, 0x62, 0xb9, 0x54, 0xfa, 0x8e, 0x28, 0xba, 0x40, 0x60, 0x96, - 0xfc, 0x84, 0x0e, 0xee, 0xab, 0x28, 0x74, 0x41, 0x44, 0x07, 0x4b, 0x6e, 0xef, 0x07, 0x07, 0xca, - 0x86, 0xe6, 0x2f, 0x6b, 0xe3, 0xfc, 0x37, 0x6b, 0xf3, 0x85, 0xd1, 0x14, 0x68, 0x95, 0x2e, 0xbb, - 0xb7, 0xbe, 0x12, 0xde, 0xc7, 0x5b, 0xd4, 0x68, 0x3e, 0xe4, 0xb9, 0x29, 0x2f, 0xc8, 0xb4, 0x55, - 0xd7, 0xc0, 0x7a, 0xfa, 0x99, 0x2e, 0xea, 0x91, 0xb9, 0x9e, 0xe0, 0xbe, 0x95, 0x50, 0x73, 0x25, - 0x3a, 0x47, 0xe0, 0x7d, 0xe7, 0x40, 0xc5, 0x06, 0x9b, 0x4e, 0xf3, 0xa5, 0x54, 0x97, 0x9a, 0x23, - 0xd9, 0x80, 0x3a, 0x7e, 0x18, 0x0f, 0x12, 0xa6, 0xc4, 0x5b, 0x9b, 0xd3, 0xa5, 0x6b, 0x78, 0xe5, - 0x07, 0x09, 0x15, 0x6f, 0x6d, 0x44, 0x90, 0xd8, 0x2c, 0x26, 0x22, 0x48, 0xcc, 0xd3, 0x81, 0x4d, - 0x13, 0x99, 0xf9, 0xcb, 0xdb, 0xbd, 0x76, 0xa9, 0x1b, 0x5d, 0x14, 0x98, 0x7c, 0x0a, 0x50, 0x28, - 0x60, 0xa9, 0xc8, 0x5a, 0x15, 0xdb, 0x39, 0xd7, 0xaa, 0x38, 0x15, 0xd9, 0xcb, 0x4d, 0x9c, 0xd0, - 0xea, 0x0d, 0x72, 0xa1, 0x7c, 0x7a, 0x4e, 0xfd, 0xe6, 0x13, 0x52, 0x83, 0xd2, 0xc9, 0xe5, 0x9b, - 0xa6, 0xf3, 0x32, 0x84, 0x2a, 0x15, 0x43, 0x63, 0x5c, 0x07, 0x38, 0xbe, 0x66, 0xfb, 0xaf, 0xba, - 0x6c, 0xbf, 0xb7, 0x8d, 0x2e, 0x88, 0xaf, 0x2f, 0xd9, 0xc1, 0x76, 0x87, 0x1d, 0x74, 0xf6, 0x9b, - 0x8e, 0xc1, 0xfd, 0x33, 0xd6, 0xeb, 0x1d, 0xb0, 0xde, 0x7e, 0xaf, 0xf9, 0x94, 0x00, 0x54, 0xd1, - 0x7f, 0xb7, 0xdb, 0x6d, 0x96, 0x8c, 0xcd, 0xbf, 0x66, 0x07, 0x3b, 0x7b, 0xd6, 0xb7, 0xbc, 0xf4, - 0xdd, 0xed, 0x6d, 0xb3, 0xbd, 0x9d, 0xed, 0x66, 0xe5, 0xb0, 0xf9, 0xe3, 0xef, 0x1b, 0xce, 0xcf, - 0xf8, 0xfb, 0x0d, 0x7f, 0xef, 0xfe, 0xd8, 0x78, 0x72, 0x53, 0xb5, 0x2f, 0x7f, 0xf7, 0xcf, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x98, 0xaf, 0x2d, 0x84, 0x77, 0x06, 0x00, 0x00, + // 837 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x55, 0xdd, 0x6e, 0xe3, 0x44, + 0x14, 0x5e, 0x6f, 0xfe, 0xdc, 0x93, 0x4d, 0x37, 0x9a, 0x05, 0x11, 0x7e, 0xd4, 0x82, 0x25, 0xa4, + 0xd5, 0x4a, 0x34, 0x6d, 0xd2, 0x6e, 0xda, 0x4b, 0x37, 0x6d, 0xb5, 0x68, 0x9b, 0xb6, 0x4c, 0xdb, + 0x0b, 0xae, 0x46, 0x53, 0x7b, 0x9c, 0x18, 0x3b, 0x1e, 0xef, 0x64, 0xd2, 0x24, 0x2f, 0xc0, 0x33, + 0xf0, 0x10, 0x70, 0xc7, 0x43, 0x20, 0xae, 0xb8, 0xe6, 0x02, 0x21, 0x78, 0x11, 0xce, 0x8c, 0xd3, + 0xa6, 0x48, 0x48, 0x88, 0x15, 0x37, 0x7b, 0x11, 0x79, 0xbe, 0xf3, 0xf3, 0x9d, 0x6f, 0xce, 0x39, + 0x76, 0xe0, 0x70, 0x18, 0xeb, 0xd1, 0xf4, 0x66, 0x2b, 0x90, 0xe3, 0xf6, 0xd5, 0x48, 0x5c, 0x8d, + 0xe2, 0x6c, 0x38, 0x39, 0x13, 0x7a, 0x26, 0x55, 0xd2, 0xd6, 0x3a, 0x6b, 0xf3, 0x3c, 0x6e, 0xe7, + 0x4a, 0x6a, 0x19, 0xc8, 0xb4, 0x9d, 0x4a, 0xc5, 0x67, 0x3c, 0xbb, 0x7b, 0x6e, 0x59, 0x07, 0xa9, + 0x2d, 0xe1, 0x47, 0x5f, 0x3c, 0x20, 0x1b, 0xca, 0xa1, 0x2c, 0x12, 0x6f, 0xa6, 0x91, 0x45, 0x16, + 0xd8, 0x53, 0x91, 0xe7, 0xfd, 0xe0, 0x80, 0x3b, 0x10, 0x9a, 0x87, 0x5c, 0x73, 0xd2, 0x05, 0x18, + 0xcb, 0x70, 0x9a, 0x72, 0x1d, 0xcb, 0xac, 0x55, 0xff, 0xd4, 0x79, 0xbe, 0xde, 0x79, 0xb6, 0x75, + 0x57, 0x68, 0x70, 0xef, 0xa2, 0x0f, 0xc2, 0xc8, 0xc7, 0xb0, 0x66, 0x92, 0x99, 0xe2, 0x5a, 0xb4, + 0x9e, 0x60, 0xce, 0x1a, 0x75, 0x8d, 0x81, 0x22, 0x26, 0x1f, 0x82, 0x7b, 0x13, 0xeb, 0xc2, 0xd7, + 0x40, 0x5f, 0x83, 0xd6, 0x10, 0x5b, 0xd7, 0x26, 0xd4, 0x03, 0x19, 0xe2, 0x55, 0x0b, 0xef, 0xba, + 0xcd, 0x84, 0xc2, 0x64, 0x03, 0x9e, 0x41, 0x25, 0x62, 0x41, 0xa6, 0x5b, 0x4f, 0x6d, 0x62, 0x39, + 0xea, 0x67, 0xda, 0xfb, 0xd1, 0x81, 0xa7, 0x57, 0xf3, 0xbe, 0xcc, 0xa2, 0x78, 0x38, 0x55, 0x85, + 0x82, 0x77, 0x40, 0xf6, 0xcf, 0x25, 0x20, 0x7e, 0xa0, 0xe3, 0x5b, 0x5b, 0xfc, 0xbe, 0xe1, 0x67, + 0x50, 0xe3, 0x79, 0xce, 0xc4, 0x34, 0x6e, 0x39, 0x18, 0xfd, 0xe4, 0x70, 0xef, 0xd7, 0xdf, 0x36, + 0x77, 0xfe, 0x6d, 0x1d, 0x02, 0xa9, 0x44, 0x5b, 0x2f, 0x72, 0x31, 0xd9, 0xf2, 0xf3, 0xfc, 0xf8, + 0xfa, 0x4b, 0x5a, 0x45, 0x96, 0xe3, 0x69, 0x6c, 0xf8, 0x42, 0x71, 0x6b, 0xf9, 0x1e, 0xbf, 0x15, + 0xdf, 0x91, 0xb8, 0xb5, 0x7c, 0xc8, 0x62, 0xf8, 0xbe, 0x02, 0xd7, 0xf0, 0xf1, 0x30, 0x54, 0xad, + 0x92, 0x25, 0x7c, 0x89, 0x84, 0x9d, 0xff, 0x46, 0xe8, 0x63, 0x36, 0x35, 0xba, 0xcc, 0x81, 0x50, + 0x58, 0xcb, 0x66, 0x09, 0x9b, 0xb0, 0x44, 0x2c, 0x5a, 0xe5, 0xb7, 0xe2, 0x3c, 0x9b, 0x25, 0x97, + 0xaf, 0xc5, 0x82, 0xd6, 0xb2, 0xe2, 0x40, 0x3c, 0x68, 0xa8, 0xf9, 0x0e, 0x0b, 0x15, 0x93, 0x51, + 0x34, 0x11, 0xda, 0xee, 0x40, 0x83, 0xd6, 0xd1, 0x78, 0xa4, 0xce, 0xad, 0x89, 0xbc, 0x0f, 0x55, + 0x35, 0xef, 0x60, 0x8c, 0x1d, 0x76, 0x83, 0x56, 0x10, 0x1d, 0x29, 0x33, 0x69, 0x35, 0x67, 0xa1, + 0x48, 0xf9, 0xe2, 0x6e, 0xd2, 0x6a, 0x7e, 0x64, 0x20, 0xf9, 0x00, 0x6a, 0x41, 0xc4, 0xd2, 0x78, + 0xa2, 0x71, 0xca, 0xa5, 0xe7, 0x65, 0x5a, 0x0d, 0xa2, 0x53, 0x44, 0xde, 0x0c, 0xe0, 0xe2, 0xd5, + 0xd7, 0x17, 0x7c, 0x91, 0x4a, 0x1e, 0x92, 0xcf, 0xa0, 0x3c, 0x1e, 0x21, 0xad, 0x19, 0x60, 0xbd, + 0xd3, 0x58, 0xed, 0xdd, 0x2b, 0xbc, 0xb6, 0x75, 0x91, 0x5d, 0xa8, 0x0f, 0xfc, 0x3e, 0xcb, 0x8b, + 0x0c, 0x3b, 0x9a, 0xfa, 0xc3, 0x0d, 0xf5, 0xfb, 0x4b, 0x32, 0xdc, 0xd0, 0xfb, 0x33, 0x69, 0x42, + 0x69, 0x1c, 0x07, 0x45, 0xdf, 0xa9, 0x39, 0x7a, 0x5d, 0x28, 0x1b, 0x56, 0x73, 0x97, 0x31, 0x33, + 0xbd, 0xb0, 0x45, 0xf1, 0x2e, 0xe3, 0x2b, 0x04, 0xe4, 0x3d, 0xa8, 0x8c, 0xf9, 0x37, 0x52, 0xd9, + 0x02, 0xc6, 0x6a, 0x80, 0x37, 0x02, 0x58, 0x15, 0xc0, 0x56, 0xe1, 0x76, 0xfe, 0x93, 0xdc, 0x13, + 0x2b, 0x37, 0x5a, 0xd2, 0x47, 0x2c, 0x97, 0x4a, 0xdf, 0x11, 0x45, 0x17, 0x08, 0xcc, 0xe6, 0x9f, + 0xd0, 0xc1, 0xfd, 0x2d, 0x0a, 0x5d, 0x10, 0xd1, 0xc1, 0x92, 0xdb, 0xfb, 0xde, 0x81, 0xb2, 0xa1, + 0xf9, 0xdb, 0xda, 0x38, 0xff, 0xcf, 0xda, 0x7c, 0x6e, 0x34, 0x05, 0x5a, 0xa5, 0xcb, 0xee, 0xad, + 0xaf, 0x84, 0xf7, 0xd1, 0x8a, 0x1a, 0xcd, 0x63, 0xf5, 0xf2, 0x95, 0x56, 0x2f, 0x5f, 0x71, 0x1f, + 0x99, 0xeb, 0x09, 0xee, 0x5b, 0x09, 0x35, 0x57, 0xa2, 0x73, 0x04, 0xde, 0xb7, 0x0e, 0x54, 0x6c, + 0xb2, 0xe9, 0x34, 0x5f, 0x4a, 0x75, 0xa9, 0x39, 0x92, 0x0d, 0xa8, 0xe3, 0x83, 0xf1, 0x20, 0x61, + 0x4a, 0xbc, 0xb1, 0x35, 0x5d, 0xba, 0x86, 0x26, 0x3f, 0x48, 0xa8, 0x78, 0x63, 0x33, 0x82, 0xc4, + 0x56, 0x31, 0x19, 0x41, 0x62, 0xbe, 0x27, 0xd8, 0x34, 0x91, 0x99, 0xef, 0x80, 0xdd, 0x6b, 0x97, + 0xba, 0xd1, 0x45, 0x81, 0xc9, 0x27, 0x00, 0x85, 0x02, 0x96, 0x8a, 0xac, 0x55, 0xb1, 0x9d, 0x73, + 0xad, 0x8a, 0x53, 0x91, 0xbd, 0xd8, 0xc4, 0x09, 0xad, 0x3e, 0x4c, 0x2e, 0x94, 0x4f, 0xcf, 0xa9, + 0xdf, 0x7c, 0x44, 0x6a, 0x50, 0x3a, 0xb9, 0x7c, 0xdd, 0x74, 0x5e, 0x84, 0x50, 0xa5, 0x62, 0x68, + 0x9c, 0xeb, 0x00, 0xc7, 0xd7, 0x6c, 0xff, 0x65, 0x97, 0xed, 0xf7, 0xb6, 0x31, 0x04, 0xf1, 0xf5, + 0x25, 0x3b, 0xd8, 0xee, 0xb0, 0x83, 0xce, 0x7e, 0xd3, 0x31, 0xb8, 0x7f, 0xc6, 0x7a, 0xbd, 0x03, + 0xd6, 0xdb, 0xef, 0x35, 0x1f, 0x13, 0x80, 0x2a, 0xc6, 0xef, 0x76, 0xbb, 0xcd, 0x92, 0xf1, 0xf9, + 0xd7, 0xec, 0x60, 0x67, 0xcf, 0xc6, 0x96, 0x97, 0xb1, 0xbb, 0xbd, 0x6d, 0xb6, 0xb7, 0xb3, 0xdd, + 0xac, 0x1c, 0x36, 0x7f, 0xfa, 0x63, 0xc3, 0xf9, 0x05, 0x7f, 0xbf, 0xe3, 0xef, 0xbb, 0x3f, 0x37, + 0x1e, 0xdd, 0x54, 0xed, 0x7f, 0x44, 0xf7, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x1e, 0x03, + 0xe2, 0xa1, 0x06, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 4828c575b..73aeddb80 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -14,6 +14,8 @@ message Metadata { string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} uint32 bit_rate = 13; // FSK bit rate in bit/s string coding_rate = 14; // LoRa coding rate + + uint32 f_cnt = 15; // Store the full 32 bit FCnt } message TxConfiguration { @@ -21,6 +23,8 @@ message TxConfiguration { string data_rate = 12; // LoRa data rate - SF{spreadingfactor}BW{bandwidth} uint32 bit_rate = 13; // FSK bit rate in bit/s string coding_rate = 14; // LoRa coding rate + + uint32 f_cnt = 15; // Store the full 32 bit FCnt } message ActivationMetadata { diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 3ba1a2a5b..5670148b9 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -10,7 +10,6 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" - "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/types" "github.com/brocaar/lorawan" ) @@ -30,8 +29,13 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return nil } - // LoRaWAN: Unmarshal base := duplicates[0] + + if base.ProtocolMetadata.GetLorawan() == nil { + return errors.New("ttn/broker: Can not handle uplink from non-LoRaWAN device") + } + + // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload err := phyPayload.UnmarshalBinary(base.Payload) if err != nil { @@ -58,10 +62,9 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { // Find AppEUI/DevEUI through MIC check var device *pb_networkserver.DevicesResponse_Device for _, candidate := range getDevicesResp.Results { - var nwkSKey lorawan.AES128Key - copy(nwkSKey[:], candidate.NwkSKey.Bytes()) + nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) if candidate.Uses32BitFCnt { - macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCnt, uint16(macPayload.FHDR.FCnt)) + macPayload.FHDR.FCnt = candidate.FullFCnt } ok, err = phyPayload.ValidateMIC(nwkSKey) if err != nil { @@ -78,11 +81,14 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { if device.DisableFCntCheck { // TODO: Add warning to message? - } else if macPayload.FHDR.FCnt < device.FCnt || macPayload.FHDR.FCnt-device.FCnt > maxFCntGap { + } else if macPayload.FHDR.FCnt < device.StoredFCnt || macPayload.FHDR.FCnt-device.StoredFCnt > maxFCntGap { // Replay attack or FCnt gap too big return ErrInvalidFCnt } + // Add FCnt to Metadata (because it's not marshaled in lorawan payload) + base.ProtocolMetadata.GetLorawan().FCnt = device.FullFCnt + // Collect GatewayMetadata and DownlinkOptions var gatewayMetadata []*gateway.RxMetadata var downlinkOptions []*pb.DownlinkOption diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 5b75fbe5c..885cb12cb 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -9,6 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" "github.com/brocaar/lorawan" @@ -53,7 +54,7 @@ func TestHandleUplink(t *testing.T) { err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, - ProtocolMetadata: &protocol.RxMetadata{}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldEqual, ErrNotFound) @@ -68,10 +69,10 @@ func TestHandleUplink(t *testing.T) { ns: &mockNetworkServer{ devices: []*pb_networkserver.DevicesResponse_Device{ &pb_networkserver.DevicesResponse_Device{ - DevEui: &devEUI, - AppEui: &appEUI, - NwkSKey: &nwkSKey, - FCnt: 3, + DevEui: &devEUI, + AppEui: &appEUI, + NwkSKey: &nwkSKey, + StoredFCnt: 3, }, }, }, @@ -85,7 +86,7 @@ func TestHandleUplink(t *testing.T) { err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, - ProtocolMetadata: &protocol.RxMetadata{}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldEqual, ErrNoMatch) @@ -97,7 +98,7 @@ func TestHandleUplink(t *testing.T) { err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, - ProtocolMetadata: &protocol.RxMetadata{}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldEqual, ErrInvalidFCnt) @@ -107,18 +108,18 @@ func TestHandleUplink(t *testing.T) { err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, - ProtocolMetadata: &protocol.RxMetadata{}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) // OK FCnt b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) - b.ns.(*mockNetworkServer).devices[0].FCnt = 0 + b.ns.(*mockNetworkServer).devices[0].StoredFCnt = 0 b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = false err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, - ProtocolMetadata: &protocol.RxMetadata{}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index de845b5e7..110fdf7c1 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -41,11 +41,13 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes } for _, device := range devices { + fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) dev := &pb.DevicesResponse_Device{ AppEui: &device.AppEUI, DevEui: &device.DevEUI, NwkSKey: &device.NwkSKey, - FCnt: device.FCntUp, + StoredFCnt: device.FCntUp, + FullFCnt: fullFCnt, Uses32BitFCnt: device.Options.Uses32BitFCnt, DisableFCntCheck: device.Options.DisableFCntCheck, } @@ -56,7 +58,7 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes if device.FCntUp <= req.FCnt { res.Results = append(res.Results, dev) continue - } else if device.Options.Uses32BitFCnt && device.FCntUp <= fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) { + } else if device.Options.Uses32BitFCnt && device.FCntUp <= fullFCnt { res.Results = append(res.Results, dev) continue } @@ -158,8 +160,12 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload") } - // Update FCntUp - dev.FCntUp = macPayload.FHDR.FCnt + // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) + if lorawan := message.GetProtocolMetadata().GetLorawan(); lorawan != nil { + dev.FCntUp = lorawan.FCnt + } else { + dev.FCntUp = macPayload.FHDR.FCnt + } dev.LastSeen = time.Now().UTC() err = n.devices.Set(dev, "f_cnt_up") if err != nil { From 9632616a83d56dc2f47849f05a7160979d9374b3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 31 May 2016 14:01:47 +0200 Subject: [PATCH 1476/2266] Merge Semtech into Gateway prot --- Makefile | 1 - api/gateway/gateway.pb.go | 655 ++++++++++++++++-------------- api/gateway/gateway.proto | 33 +- api/gateway/semtech/semtech.pb.go | 570 -------------------------- api/gateway/semtech/semtech.proto | 16 - core/router/downlink.go | 26 +- core/router/downlink_test.go | 16 +- core/router/uplink_test.go | 4 +- 8 files changed, 392 insertions(+), 929 deletions(-) delete mode 100644 api/gateway/semtech/semtech.pb.go delete mode 100644 api/gateway/semtech/semtech.proto diff --git a/Makefile b/Makefile index 68521f4ba..99bedefbd 100644 --- a/Makefile +++ b/Makefile @@ -56,7 +56,6 @@ proto: @$(PROTOC)/api/protocol/protocol.proto @$(PROTOC)/api/protocol/**/*.proto @$(PROTOC)/api/gateway/gateway.proto - @$(PROTOC)/api/gateway/**/*.proto @$(PROTOC)/api/router/router.proto @$(PROTOC)/api/broker/broker.proto @$(PROTOC)/api/handler/handler.proto diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index f80975bd4..893f98bce 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -20,7 +20,6 @@ import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import _ "github.com/gogo/protobuf/gogoproto" -import semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" @@ -49,12 +48,14 @@ func (*GPSMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, type RxMetadata struct { GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` - Frequency uint64 `protobuf:"varint,11,opt,name=frequency,proto3" json:"frequency,omitempty"` - Rssi float32 `protobuf:"fixed32,12,opt,name=rssi,proto3" json:"rssi,omitempty"` - Snr float32 `protobuf:"fixed32,13,opt,name=snr,proto3" json:"snr,omitempty"` - // Types that are valid to be assigned to Gateway: - // *RxMetadata_Semtech - Gateway isRxMetadata_Gateway `protobuf_oneof:"gateway"` + Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Time int64 `protobuf:"varint,12,opt,name=time,proto3" json:"time,omitempty"` + RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Channel uint32 `protobuf:"varint,22,opt,name=channel,proto3" json:"channel,omitempty"` + Frequency uint64 `protobuf:"varint,31,opt,name=frequency,proto3" json:"frequency,omitempty"` + Rssi float32 `protobuf:"fixed32,32,opt,name=rssi,proto3" json:"rssi,omitempty"` + Snr float32 `protobuf:"fixed32,33,opt,name=snr,proto3" json:"snr,omitempty"` + Gps *GPSMetadata `protobuf:"bytes,41,opt,name=gps" json:"gps,omitempty"` } func (m *RxMetadata) Reset() { *m = RxMetadata{} } @@ -62,93 +63,21 @@ func (m *RxMetadata) String() string { return proto.CompactTextString func (*RxMetadata) ProtoMessage() {} func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{1} } -type isRxMetadata_Gateway interface { - isRxMetadata_Gateway() - MarshalTo([]byte) (int, error) - Size() int -} - -type RxMetadata_Semtech struct { - Semtech *semtech.RxMetadata `protobuf:"bytes,21,opt,name=semtech,oneof"` -} - -func (*RxMetadata_Semtech) isRxMetadata_Gateway() {} - -func (m *RxMetadata) GetGateway() isRxMetadata_Gateway { +func (m *RxMetadata) GetGps() *GPSMetadata { if m != nil { - return m.Gateway - } - return nil -} - -func (m *RxMetadata) GetSemtech() *semtech.RxMetadata { - if x, ok := m.GetGateway().(*RxMetadata_Semtech); ok { - return x.Semtech - } - return nil -} - -// XXX_OneofFuncs is for the internal use of the proto package. -func (*RxMetadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _RxMetadata_OneofMarshaler, _RxMetadata_OneofUnmarshaler, _RxMetadata_OneofSizer, []interface{}{ - (*RxMetadata_Semtech)(nil), - } -} - -func _RxMetadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*RxMetadata) - // gateway - switch x := m.Gateway.(type) { - case *RxMetadata_Semtech: - _ = b.EncodeVarint(21<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Semtech); err != nil { - return err - } - case nil: - default: - return fmt.Errorf("RxMetadata.Gateway has unexpected type %T", x) + return m.Gps } return nil } -func _RxMetadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*RxMetadata) - switch tag { - case 21: // gateway.semtech - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(semtech.RxMetadata) - err := b.DecodeMessage(msg) - m.Gateway = &RxMetadata_Semtech{msg} - return true, err - default: - return false, nil - } -} - -func _RxMetadata_OneofSizer(msg proto.Message) (n int) { - m := msg.(*RxMetadata) - // gateway - switch x := m.Gateway.(type) { - case *RxMetadata_Semtech: - s := proto.Size(x.Semtech) - n += proto.SizeVarint(21<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case nil: - default: - panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) - } - return n -} - type TxConfiguration struct { - Frequency uint64 `protobuf:"varint,1,opt,name=frequency,proto3" json:"frequency,omitempty"` - Power int32 `protobuf:"varint,2,opt,name=power,proto3" json:"power,omitempty"` - // Types that are valid to be assigned to Gateway: - // *TxConfiguration_Semtech - Gateway isTxConfiguration_Gateway `protobuf_oneof:"gateway"` + GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` + Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Frequency uint64 `protobuf:"varint,22,opt,name=frequency,proto3" json:"frequency,omitempty"` + Power int32 `protobuf:"varint,23,opt,name=power,proto3" json:"power,omitempty"` + PolarizationInversion bool `protobuf:"varint,31,opt,name=polarization_inversion,json=polarizationInversion,proto3" json:"polarization_inversion,omitempty"` + FrequencyDeviation uint32 `protobuf:"varint,32,opt,name=frequency_deviation,json=frequencyDeviation,proto3" json:"frequency_deviation,omitempty"` } func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } @@ -156,87 +85,6 @@ func (m *TxConfiguration) String() string { return proto.CompactTextS func (*TxConfiguration) ProtoMessage() {} func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{2} } -type isTxConfiguration_Gateway interface { - isTxConfiguration_Gateway() - MarshalTo([]byte) (int, error) - Size() int -} - -type TxConfiguration_Semtech struct { - Semtech *semtech.TxConfiguration `protobuf:"bytes,11,opt,name=semtech,oneof"` -} - -func (*TxConfiguration_Semtech) isTxConfiguration_Gateway() {} - -func (m *TxConfiguration) GetGateway() isTxConfiguration_Gateway { - if m != nil { - return m.Gateway - } - return nil -} - -func (m *TxConfiguration) GetSemtech() *semtech.TxConfiguration { - if x, ok := m.GetGateway().(*TxConfiguration_Semtech); ok { - return x.Semtech - } - return nil -} - -// XXX_OneofFuncs is for the internal use of the proto package. -func (*TxConfiguration) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _TxConfiguration_OneofMarshaler, _TxConfiguration_OneofUnmarshaler, _TxConfiguration_OneofSizer, []interface{}{ - (*TxConfiguration_Semtech)(nil), - } -} - -func _TxConfiguration_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*TxConfiguration) - // gateway - switch x := m.Gateway.(type) { - case *TxConfiguration_Semtech: - _ = b.EncodeVarint(11<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Semtech); err != nil { - return err - } - case nil: - default: - return fmt.Errorf("TxConfiguration.Gateway has unexpected type %T", x) - } - return nil -} - -func _TxConfiguration_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*TxConfiguration) - switch tag { - case 11: // gateway.semtech - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(semtech.TxConfiguration) - err := b.DecodeMessage(msg) - m.Gateway = &TxConfiguration_Semtech{msg} - return true, err - default: - return false, nil - } -} - -func _TxConfiguration_OneofSizer(msg proto.Message) (n int) { - m := msg.(*TxConfiguration) - // gateway - switch x := m.Gateway.(type) { - case *TxConfiguration_Semtech: - s := proto.Size(x.Semtech) - n += proto.SizeVarint(11<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case nil: - default: - panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) - } - return n -} - // message Status represents a status update from a Gateway. // See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat type Status struct { @@ -336,47 +184,66 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { } i += n1 } - if m.Frequency != 0 { + if m.Timestamp != 0 { data[i] = 0x58 i++ + i = encodeVarintGateway(data, i, uint64(m.Timestamp)) + } + if m.Time != 0 { + data[i] = 0x60 + i++ + i = encodeVarintGateway(data, i, uint64(m.Time)) + } + if m.RfChain != 0 { + data[i] = 0xa8 + i++ + data[i] = 0x1 + i++ + i = encodeVarintGateway(data, i, uint64(m.RfChain)) + } + if m.Channel != 0 { + data[i] = 0xb0 + i++ + data[i] = 0x1 + i++ + i = encodeVarintGateway(data, i, uint64(m.Channel)) + } + if m.Frequency != 0 { + data[i] = 0xf8 + i++ + data[i] = 0x1 + i++ i = encodeVarintGateway(data, i, uint64(m.Frequency)) } if m.Rssi != 0 { - data[i] = 0x65 + data[i] = 0x85 + i++ + data[i] = 0x2 i++ i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Rssi)))) } if m.Snr != 0 { - data[i] = 0x6d + data[i] = 0x8d + i++ + data[i] = 0x2 i++ i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Snr)))) } - if m.Gateway != nil { - nn2, err := m.Gateway.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += nn2 - } - return i, nil -} - -func (m *RxMetadata_Semtech) MarshalTo(data []byte) (int, error) { - i := 0 - if m.Semtech != nil { - data[i] = 0xaa + if m.Gps != nil { + data[i] = 0xca i++ - data[i] = 0x1 + data[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) - n3, err := m.Semtech.MarshalTo(data[i:]) + i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) + n2, err := m.Gps.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n2 } return i, nil } + func (m *TxConfiguration) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -392,40 +259,64 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if m.GatewayEui != nil { + data[i] = 0xa + i++ + i = encodeVarintGateway(data, i, uint64(m.GatewayEui.Size())) + n3, err := m.GatewayEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Timestamp != 0 { + data[i] = 0x58 + i++ + i = encodeVarintGateway(data, i, uint64(m.Timestamp)) + } + if m.RfChain != 0 { + data[i] = 0xa8 + i++ + data[i] = 0x1 + i++ + i = encodeVarintGateway(data, i, uint64(m.RfChain)) + } if m.Frequency != 0 { - data[i] = 0x8 + data[i] = 0xb0 + i++ + data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Frequency)) } if m.Power != 0 { - data[i] = 0x10 + data[i] = 0xb8 + i++ + data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Power)) } - if m.Gateway != nil { - nn4, err := m.Gateway.MarshalTo(data[i:]) - if err != nil { - return 0, err + if m.PolarizationInversion { + data[i] = 0xf8 + i++ + data[i] = 0x1 + i++ + if m.PolarizationInversion { + data[i] = 1 + } else { + data[i] = 0 } - i += nn4 + i++ } - return i, nil -} - -func (m *TxConfiguration_Semtech) MarshalTo(data []byte) (int, error) { - i := 0 - if m.Semtech != nil { - data[i] = 0x5a + if m.FrequencyDeviation != 0 { + data[i] = 0x80 i++ - i = encodeVarintGateway(data, i, uint64(m.Semtech.Size())) - n5, err := m.Semtech.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n5 + data[i] = 0x2 + i++ + i = encodeVarintGateway(data, i, uint64(m.FrequencyDeviation)) } return i, nil } + func (m *Status) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -496,11 +387,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n6, err := m.Gps.MarshalTo(data[i:]) + n4, err := m.Gps.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n4 } if m.Rtt != 0 { data[i] = 0xf8 @@ -592,54 +483,62 @@ func (m *RxMetadata) Size() (n int) { l = m.GatewayEui.Size() n += 1 + l + sovGateway(uint64(l)) } + if m.Timestamp != 0 { + n += 1 + sovGateway(uint64(m.Timestamp)) + } + if m.Time != 0 { + n += 1 + sovGateway(uint64(m.Time)) + } + if m.RfChain != 0 { + n += 2 + sovGateway(uint64(m.RfChain)) + } + if m.Channel != 0 { + n += 2 + sovGateway(uint64(m.Channel)) + } if m.Frequency != 0 { - n += 1 + sovGateway(uint64(m.Frequency)) + n += 2 + sovGateway(uint64(m.Frequency)) } if m.Rssi != 0 { - n += 5 + n += 6 } if m.Snr != 0 { - n += 5 + n += 6 } - if m.Gateway != nil { - n += m.Gateway.Size() - } - return n -} - -func (m *RxMetadata_Semtech) Size() (n int) { - var l int - _ = l - if m.Semtech != nil { - l = m.Semtech.Size() + if m.Gps != nil { + l = m.Gps.Size() n += 2 + l + sovGateway(uint64(l)) } return n } + func (m *TxConfiguration) Size() (n int) { var l int _ = l + if m.GatewayEui != nil { + l = m.GatewayEui.Size() + n += 1 + l + sovGateway(uint64(l)) + } + if m.Timestamp != 0 { + n += 1 + sovGateway(uint64(m.Timestamp)) + } + if m.RfChain != 0 { + n += 2 + sovGateway(uint64(m.RfChain)) + } if m.Frequency != 0 { - n += 1 + sovGateway(uint64(m.Frequency)) + n += 2 + sovGateway(uint64(m.Frequency)) } if m.Power != 0 { - n += 1 + sovGateway(uint64(m.Power)) + n += 2 + sovGateway(uint64(m.Power)) } - if m.Gateway != nil { - n += m.Gateway.Size() + if m.PolarizationInversion { + n += 3 } - return n -} - -func (m *TxConfiguration_Semtech) Size() (n int) { - var l int - _ = l - if m.Semtech != nil { - l = m.Semtech.Size() - n += 1 + l + sovGateway(uint64(l)) + if m.FrequencyDeviation != 0 { + n += 2 + sovGateway(uint64(m.FrequencyDeviation)) } return n } + func (m *Status) Size() (n int) { var l int _ = l @@ -884,6 +783,82 @@ func (m *RxMetadata) Unmarshal(data []byte) error { } iNdEx = postIndex case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + m.Time = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Time |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) + } + m.RfChain = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RfChain |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) + } + m.Channel = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Channel |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 31: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) } @@ -902,7 +877,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { break } } - case 12: + case 32: if wireType != 5 { return fmt.Errorf("proto: wrong wireType = %d for field Rssi", wireType) } @@ -916,7 +891,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { v |= uint32(data[iNdEx-2]) << 16 v |= uint32(data[iNdEx-1]) << 24 m.Rssi = float32(math.Float32frombits(v)) - case 13: + case 33: if wireType != 5 { return fmt.Errorf("proto: wrong wireType = %d for field Snr", wireType) } @@ -930,9 +905,9 @@ func (m *RxMetadata) Unmarshal(data []byte) error { v |= uint32(data[iNdEx-2]) << 16 v |= uint32(data[iNdEx-1]) << 24 m.Snr = float32(math.Float32frombits(v)) - case 21: + case 41: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Gps", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -956,11 +931,12 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - v := &semtech.RxMetadata{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if m.Gps == nil { + m.Gps = &GPSMetadata{} + } + if err := m.Gps.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } - m.Gateway = &RxMetadata_Semtech{v} iNdEx = postIndex default: iNdEx = preIndex @@ -1013,6 +989,76 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI + m.GatewayEui = &v + if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) + } + m.Timestamp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Timestamp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) + } + m.RfChain = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RfChain |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 22: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) } @@ -1031,7 +1077,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { break } } - case 2: + case 23: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) } @@ -1050,11 +1096,11 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { break } } - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Semtech", wireType) + case 31: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PolarizationInversion", wireType) } - var msglen int + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGateway @@ -1064,24 +1110,31 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + v |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { - return ErrInvalidLengthGateway - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF + m.PolarizationInversion = bool(v != 0) + case 32: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FrequencyDeviation", wireType) } - v := &semtech.TxConfiguration{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.FrequencyDeviation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FrequencyDeviation |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } } - m.Gateway = &TxConfiguration_Semtech{v} - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGateway(data[iNdEx:]) @@ -1570,41 +1623,43 @@ var ( ) var fileDescriptorGateway = []byte{ - // 561 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xae, 0xf3, 0xd3, 0x92, 0x75, 0xd2, 0x56, 0xdb, 0x82, 0xac, 0x08, 0xb5, 0x55, 0x90, 0x50, - 0xf9, 0x8b, 0x25, 0x7e, 0x0e, 0x3d, 0x12, 0x14, 0x95, 0x1e, 0xa0, 0x68, 0x1b, 0x2e, 0x5c, 0xa2, - 0x8d, 0xb3, 0x71, 0x56, 0x89, 0xbd, 0x66, 0x3d, 0x56, 0x92, 0x17, 0xe0, 0x19, 0x78, 0x24, 0x8e, - 0x48, 0xdc, 0x38, 0x20, 0x04, 0x17, 0x1e, 0x03, 0xef, 0xf8, 0x27, 0xa1, 0x12, 0x42, 0x1c, 0x2c, - 0xcf, 0xf7, 0xcd, 0xec, 0xcc, 0xb7, 0x33, 0xb3, 0xe4, 0xcc, 0x97, 0x30, 0x4d, 0x46, 0x5d, 0x4f, - 0x05, 0xee, 0x60, 0x2a, 0x06, 0x53, 0x19, 0xfa, 0xf1, 0x6b, 0x01, 0x0b, 0xa5, 0x67, 0x2e, 0x40, - 0xe8, 0xf2, 0x48, 0xba, 0x3e, 0x07, 0xb1, 0xe0, 0xab, 0xe2, 0xdf, 0x8d, 0xb4, 0x02, 0x45, 0x77, - 0x72, 0xd8, 0x7e, 0xb4, 0x91, 0xc3, 0x57, 0xbe, 0x72, 0xd1, 0x3f, 0x4a, 0x26, 0x88, 0x10, 0xa0, - 0x95, 0x9d, 0x6b, 0x3f, 0xff, 0x9f, 0x92, 0xb1, 0x08, 0x40, 0x78, 0xd3, 0xe2, 0x9f, 0xa5, 0xe8, - 0x2c, 0x88, 0x7d, 0xfe, 0xe6, 0xea, 0x95, 0x00, 0x3e, 0xe6, 0xc0, 0x29, 0x25, 0x35, 0x90, 0x81, - 0x70, 0xac, 0x13, 0xeb, 0xb4, 0xca, 0xd0, 0xa6, 0x6d, 0x72, 0x63, 0xce, 0x41, 0x42, 0x32, 0x16, - 0x4e, 0x25, 0xe5, 0x2b, 0xac, 0xc4, 0xf4, 0x36, 0x69, 0xcc, 0x55, 0xe8, 0x67, 0xce, 0x2a, 0x3a, - 0xd7, 0x84, 0x39, 0xc9, 0xe7, 0xf9, 0xc9, 0x5a, 0xea, 0xac, 0xb3, 0x12, 0x77, 0x7e, 0x59, 0x84, - 0xb0, 0x65, 0x59, 0xf8, 0x1d, 0xb1, 0x73, 0xa1, 0x43, 0x91, 0x48, 0xac, 0xdf, 0xec, 0x9d, 0x7d, - 0xfd, 0x76, 0xfc, 0xec, 0x5f, 0x77, 0xf4, 0x94, 0x16, 0x2e, 0xac, 0x22, 0x11, 0x77, 0xcf, 0xb3, - 0x0c, 0xfd, 0xb7, 0x17, 0x8c, 0xe4, 0xd9, 0xfa, 0x89, 0x34, 0x22, 0x27, 0x5a, 0xbc, 0x4f, 0x44, - 0xe8, 0xad, 0x1c, 0x3b, 0xcd, 0x5c, 0x63, 0x6b, 0xc2, 0x5c, 0x59, 0xc7, 0xb1, 0x74, 0x9a, 0xa8, - 0x1e, 0x6d, 0xba, 0x4f, 0xaa, 0x71, 0xa8, 0x9d, 0x16, 0x52, 0xc6, 0xa4, 0x2e, 0xd9, 0xc9, 0x1b, - 0xe7, 0xdc, 0x4c, 0x59, 0xfb, 0xf1, 0x41, 0xb7, 0x68, 0xe4, 0xfa, 0x16, 0x2f, 0xb7, 0x58, 0x11, - 0xd5, 0x6b, 0x90, 0x62, 0xaa, 0x9d, 0x0f, 0x16, 0xd9, 0x1b, 0x2c, 0x5f, 0xa8, 0x70, 0x22, 0xfd, - 0x44, 0xa7, 0xad, 0x53, 0xe1, 0x9f, 0x9a, 0xac, 0xeb, 0x9a, 0x0e, 0x49, 0x3d, 0x52, 0x0b, 0xa1, - 0xb1, 0xdf, 0x75, 0x96, 0x01, 0xfa, 0x74, 0xad, 0xc1, 0x46, 0x0d, 0x4e, 0xa9, 0xe1, 0x5a, 0xfa, - 0xbf, 0x08, 0xf9, 0x52, 0x21, 0xdb, 0x57, 0xc0, 0x21, 0x89, 0x4d, 0x7d, 0x33, 0xdc, 0x18, 0x78, - 0x10, 0x61, 0xfd, 0x16, 0x5b, 0x13, 0xe5, 0x1a, 0x54, 0x36, 0xd6, 0x60, 0x97, 0x54, 0x64, 0x94, - 0x16, 0xae, 0x9e, 0x36, 0x58, 0x6a, 0x99, 0xe1, 0x46, 0xe9, 0x1e, 0x4c, 0x94, 0x0e, 0xb0, 0x77, - 0x0d, 0x56, 0x62, 0x7a, 0x87, 0xb4, 0x3c, 0x15, 0x02, 0xf7, 0x60, 0x28, 0x02, 0x2e, 0xe7, 0xd8, - 0xc9, 0x06, 0x6b, 0xe6, 0x64, 0xdf, 0x70, 0xf4, 0x84, 0xd8, 0x63, 0x11, 0x7b, 0x5a, 0x46, 0x46, - 0xb2, 0xb3, 0x8b, 0x21, 0x9b, 0x14, 0xbd, 0x45, 0xb6, 0xb5, 0xf0, 0x8d, 0x73, 0x0f, 0x9d, 0x39, - 0xa2, 0x77, 0x49, 0xd5, 0x8f, 0xe2, 0x7c, 0x10, 0x87, 0xdd, 0xe2, 0x31, 0x6d, 0x2c, 0x32, 0x33, - 0x01, 0x66, 0x8c, 0x1a, 0xc0, 0x39, 0xc6, 0xeb, 0x19, 0x93, 0x1e, 0x90, 0xba, 0x5e, 0x0e, 0x65, - 0xe8, 0xdc, 0x43, 0xae, 0xa6, 0x97, 0x17, 0x61, 0x4e, 0xaa, 0x99, 0x73, 0xbf, 0x20, 0x2f, 0x67, - 0x86, 0x04, 0x8c, 0x7c, 0x90, 0x91, 0x90, 0x47, 0x02, 0x46, 0x3e, 0x2c, 0xc8, 0xcb, 0x59, 0x6f, - 0xff, 0xd3, 0x8f, 0x23, 0xeb, 0x73, 0xfa, 0x7d, 0x4f, 0xbf, 0x8f, 0x3f, 0x8f, 0xb6, 0x46, 0xdb, - 0xf8, 0xb6, 0x9e, 0xfc, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x53, 0x5b, 0x1b, 0xe0, 0x13, 0x04, 0x00, - 0x00, + // 607 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x54, 0xcb, 0x6e, 0x13, 0x31, + 0x14, 0x25, 0x93, 0x3e, 0x9d, 0xa6, 0xad, 0xdc, 0x36, 0x98, 0x0a, 0xb5, 0x25, 0x48, 0xa8, 0xbc, + 0x32, 0x12, 0xa8, 0x8b, 0x6e, 0x5b, 0xaa, 0xaa, 0x0b, 0x28, 0x72, 0xcb, 0x86, 0x4d, 0xe4, 0x4e, + 0x9c, 0x89, 0x95, 0x89, 0x3d, 0x78, 0x3c, 0x4d, 0xca, 0x97, 0xf0, 0x2f, 0xec, 0x58, 0xb1, 0x44, + 0x62, 0xc7, 0x02, 0x21, 0xf8, 0x11, 0xec, 0x3b, 0x8f, 0x4c, 0x91, 0xa0, 0x4b, 0x16, 0xa3, 0xdc, + 0x73, 0xce, 0xb5, 0x3d, 0xf7, 0xf8, 0x64, 0xd0, 0x7e, 0x28, 0xcc, 0x20, 0xbd, 0xe8, 0x04, 0x6a, + 0xe4, 0x9f, 0x0f, 0xf8, 0xf9, 0x40, 0xc8, 0x30, 0x79, 0xc5, 0xcd, 0x58, 0xe9, 0xa1, 0x6f, 0x8c, + 0xf4, 0x59, 0x2c, 0xfc, 0x90, 0x19, 0x3e, 0x66, 0x57, 0xc5, 0x6f, 0x27, 0xd6, 0xca, 0x28, 0x3c, + 0x9f, 0xc3, 0xcd, 0xa7, 0x95, 0x3d, 0x42, 0x15, 0x2a, 0x1f, 0xf4, 0x8b, 0xb4, 0x0f, 0x08, 0x00, + 0x54, 0xd9, 0xba, 0xf6, 0x18, 0x35, 0x8e, 0x5f, 0x9f, 0xbd, 0xe4, 0x86, 0xf5, 0x98, 0x61, 0x18, + 0xa3, 0x19, 0x23, 0x46, 0x9c, 0xd4, 0x76, 0x6a, 0xbb, 0x75, 0x0a, 0x35, 0xde, 0x44, 0x0b, 0x11, + 0x33, 0xc2, 0xa4, 0x3d, 0x4e, 0x3c, 0xcb, 0x7b, 0xb4, 0xc4, 0xf8, 0x2e, 0x5a, 0x8c, 0x94, 0x0c, + 0x33, 0xb1, 0x0e, 0xe2, 0x94, 0x70, 0x2b, 0x59, 0x94, 0xaf, 0x9c, 0xb1, 0xe2, 0x2c, 0x2d, 0x71, + 0xfb, 0xa3, 0x87, 0x10, 0x9d, 0x94, 0x07, 0xbf, 0x45, 0x8d, 0x7c, 0x82, 0x2e, 0x4f, 0x05, 0x9c, + 0xbf, 0x74, 0xb0, 0xff, 0xed, 0xfb, 0xf6, 0xde, 0x4d, 0x9e, 0x04, 0x4a, 0x73, 0xdf, 0x5c, 0xc5, + 0x3c, 0xe9, 0x1c, 0x67, 0x3b, 0x1c, 0xbd, 0x39, 0xa1, 0x28, 0xdf, 0xed, 0x28, 0x15, 0xee, 0x25, + 0xdd, 0x20, 0x89, 0x61, 0xa3, 0x98, 0x34, 0xec, 0xce, 0x4d, 0x3a, 0x25, 0xca, 0x91, 0x97, 0x2a, + 0x23, 0xdf, 0x41, 0x0b, 0xba, 0xdf, 0x0d, 0x06, 0x4c, 0x48, 0xb2, 0x01, 0x0b, 0xe6, 0x75, 0xff, + 0xd0, 0x41, 0x4c, 0xd0, 0xbc, 0xe5, 0xa5, 0xe4, 0x11, 0x69, 0x65, 0x4a, 0x0e, 0xdd, 0x31, 0x7d, + 0xcd, 0xdf, 0xa5, 0x5c, 0x06, 0x57, 0x64, 0xdb, 0x6a, 0x33, 0x74, 0x4a, 0xb8, 0x63, 0x74, 0x92, + 0x08, 0xb2, 0x03, 0x26, 0x41, 0x8d, 0x57, 0x51, 0x3d, 0x91, 0x9a, 0xdc, 0x03, 0xca, 0x95, 0xf8, + 0x01, 0xaa, 0x87, 0x71, 0x42, 0x1e, 0x5a, 0xa6, 0xf1, 0x6c, 0xbd, 0x53, 0xdc, 0x71, 0xe5, 0x8a, + 0xa8, 0x6b, 0x68, 0x7f, 0xf2, 0xd0, 0xca, 0xf9, 0xe4, 0x50, 0xc9, 0xbe, 0x08, 0x53, 0x6d, 0x6f, + 0x43, 0xc9, 0xff, 0x68, 0xe1, 0x3f, 0xec, 0xba, 0x66, 0x4a, 0xeb, 0x4f, 0x53, 0xd6, 0xd1, 0x6c, + 0xac, 0xc6, 0x5c, 0x93, 0xdb, 0x90, 0x8e, 0x0c, 0xe0, 0x3d, 0xd4, 0x8a, 0x55, 0xc4, 0xb4, 0x78, + 0x0f, 0x83, 0x75, 0x85, 0xbc, 0xe4, 0x3a, 0xb1, 0x15, 0xb8, 0xba, 0x40, 0x37, 0xaa, 0xea, 0x49, + 0x21, 0x62, 0x1f, 0xad, 0x95, 0x3b, 0x77, 0x7b, 0xfc, 0x52, 0x80, 0x0e, 0x86, 0x37, 0x29, 0x2e, + 0xa5, 0x17, 0x85, 0xd2, 0xfe, 0xea, 0xa1, 0xb9, 0x33, 0xc3, 0x4c, 0x9a, 0x5c, 0x9f, 0xaf, 0xf6, + 0xb7, 0x88, 0x78, 0x95, 0x88, 0x2c, 0x23, 0x4f, 0x38, 0x2b, 0xea, 0xbb, 0x8b, 0xd4, 0x56, 0x2e, + 0xeb, 0xb1, 0xfd, 0x5b, 0xf4, 0x95, 0x1e, 0x41, 0x94, 0x16, 0x69, 0x89, 0xf1, 0x7d, 0xd4, 0x0c, + 0x94, 0x34, 0x2c, 0x30, 0x5d, 0x3e, 0x62, 0x22, 0x22, 0x4d, 0x68, 0x58, 0xca, 0xc9, 0x23, 0xc7, + 0xe1, 0x1d, 0xd4, 0xe8, 0xf1, 0x24, 0xd0, 0x22, 0x86, 0xd7, 0x5e, 0x86, 0x96, 0x2a, 0x85, 0x5b, + 0x68, 0x4e, 0xf3, 0xd0, 0x89, 0x2b, 0x20, 0xe6, 0xa8, 0x08, 0xcd, 0xc6, 0x0d, 0xa1, 0x71, 0x71, + 0xd3, 0xc6, 0x80, 0x89, 0x4d, 0xea, 0x4a, 0xbc, 0x86, 0x66, 0xf5, 0xc4, 0xfa, 0x0b, 0x81, 0x6b, + 0xda, 0x54, 0x4e, 0x4e, 0x64, 0x4e, 0xaa, 0x21, 0x79, 0x54, 0x90, 0xa7, 0x43, 0x47, 0x1a, 0xe8, + 0x7c, 0x9c, 0x91, 0x26, 0xef, 0x34, 0xd0, 0xf9, 0xa4, 0x20, 0x4f, 0x87, 0x07, 0xab, 0x9f, 0x7f, + 0x6e, 0xd5, 0xbe, 0xd8, 0xe7, 0x87, 0x7d, 0x3e, 0xfc, 0xda, 0xba, 0x75, 0x31, 0x07, 0x9f, 0x9a, + 0xe7, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x87, 0xc4, 0x95, 0x62, 0xdf, 0x04, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 24a60b4ac..7ada22ee7 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -2,8 +2,6 @@ syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto"; - package gateway; message GPSMetadata { @@ -16,21 +14,30 @@ message GPSMetadata { message RxMetadata { bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; - uint64 frequency = 11; // frequency in Hz - float rssi = 12; // received signal strength in dBm - float snr = 13; // signal-to-noise-ratio in dB + uint32 timestamp = 11; + int64 time = 12; + + uint32 rf_chain = 21; + uint32 channel = 22; - oneof gateway { - semtech.RxMetadata semtech = 21; - } + uint64 frequency = 31; // frequency in Hz + float rssi = 32; // received signal strength in dBm + float snr = 33; // signal-to-noise-ratio in dB + + GPSMetadata gps = 41; } message TxConfiguration { - uint64 frequency = 1; // frequency in Hz - int32 power = 2; // transmit power in dBm - oneof gateway { - semtech.TxConfiguration semtech = 11; - } + bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; + + uint32 timestamp = 11; + + uint32 rf_chain = 21; + uint64 frequency = 22; // frequency in Hz + int32 power = 23; // transmit power in dBm + + bool polarization_inversion = 31; // LoRa polarization inversion (basically always true) + uint32 frequency_deviation = 32; // FSK frequency deviation in Hz } // message Status represents a status update from a Gateway. diff --git a/api/gateway/semtech/semtech.pb.go b/api/gateway/semtech/semtech.pb.go deleted file mode 100644 index 7a1bddef1..000000000 --- a/api/gateway/semtech/semtech.pb.go +++ /dev/null @@ -1,570 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto -// DO NOT EDIT! - -/* - Package semtech is a generated protocol buffer package. - - It is generated from these files: - github.com/TheThingsNetwork/ttn/api/gateway/semtech/semtech.proto - - It has these top-level messages: - RxMetadata - TxConfiguration -*/ -package semtech - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - -type RxMetadata struct { - Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - RfChain uint32 `protobuf:"varint,11,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` - Channel uint32 `protobuf:"varint,12,opt,name=channel,proto3" json:"channel,omitempty"` -} - -func (m *RxMetadata) Reset() { *m = RxMetadata{} } -func (m *RxMetadata) String() string { return proto.CompactTextString(m) } -func (*RxMetadata) ProtoMessage() {} -func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{0} } - -type TxConfiguration struct { - Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - RfChain uint32 `protobuf:"varint,11,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` - PolarizationInversion bool `protobuf:"varint,21,opt,name=polarization_inversion,json=polarizationInversion,proto3" json:"polarization_inversion,omitempty"` - FrequencyDeviation uint32 `protobuf:"varint,22,opt,name=frequency_deviation,json=frequencyDeviation,proto3" json:"frequency_deviation,omitempty"` -} - -func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } -func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } -func (*TxConfiguration) ProtoMessage() {} -func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorSemtech, []int{1} } - -func init() { - proto.RegisterType((*RxMetadata)(nil), "semtech.RxMetadata") - proto.RegisterType((*TxConfiguration)(nil), "semtech.TxConfiguration") -} -func (m *RxMetadata) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *RxMetadata) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Timestamp != 0 { - data[i] = 0x8 - i++ - i = encodeVarintSemtech(data, i, uint64(m.Timestamp)) - } - if m.RfChain != 0 { - data[i] = 0x58 - i++ - i = encodeVarintSemtech(data, i, uint64(m.RfChain)) - } - if m.Channel != 0 { - data[i] = 0x60 - i++ - i = encodeVarintSemtech(data, i, uint64(m.Channel)) - } - return i, nil -} - -func (m *TxConfiguration) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Timestamp != 0 { - data[i] = 0x8 - i++ - i = encodeVarintSemtech(data, i, uint64(m.Timestamp)) - } - if m.RfChain != 0 { - data[i] = 0x58 - i++ - i = encodeVarintSemtech(data, i, uint64(m.RfChain)) - } - if m.PolarizationInversion { - data[i] = 0xa8 - i++ - data[i] = 0x1 - i++ - if m.PolarizationInversion { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.FrequencyDeviation != 0 { - data[i] = 0xb0 - i++ - data[i] = 0x1 - i++ - i = encodeVarintSemtech(data, i, uint64(m.FrequencyDeviation)) - } - return i, nil -} - -func encodeFixed64Semtech(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Semtech(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintSemtech(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *RxMetadata) Size() (n int) { - var l int - _ = l - if m.Timestamp != 0 { - n += 1 + sovSemtech(uint64(m.Timestamp)) - } - if m.RfChain != 0 { - n += 1 + sovSemtech(uint64(m.RfChain)) - } - if m.Channel != 0 { - n += 1 + sovSemtech(uint64(m.Channel)) - } - return n -} - -func (m *TxConfiguration) Size() (n int) { - var l int - _ = l - if m.Timestamp != 0 { - n += 1 + sovSemtech(uint64(m.Timestamp)) - } - if m.RfChain != 0 { - n += 1 + sovSemtech(uint64(m.RfChain)) - } - if m.PolarizationInversion { - n += 3 - } - if m.FrequencyDeviation != 0 { - n += 2 + sovSemtech(uint64(m.FrequencyDeviation)) - } - return n -} - -func sovSemtech(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozSemtech(x uint64) (n int) { - return sovSemtech(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *RxMetadata) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RxMetadata: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RxMetadata: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) - } - m.Timestamp = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Timestamp |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) - } - m.RfChain = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.RfChain |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) - } - m.Channel = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Channel |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipSemtech(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthSemtech - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *TxConfiguration) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) - } - m.Timestamp = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Timestamp |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RfChain", wireType) - } - m.RfChain = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.RfChain |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 21: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field PolarizationInversion", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.PolarizationInversion = bool(v != 0) - case 22: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FrequencyDeviation", wireType) - } - m.FrequencyDeviation = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowSemtech - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FrequencyDeviation |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipSemtech(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthSemtech - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipSemtech(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowSemtech - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowSemtech - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowSemtech - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthSemtech - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowSemtech - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipSemtech(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthSemtech = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowSemtech = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorSemtech = []byte{ - // 267 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x90, 0x3d, 0x4e, 0xc3, 0x40, - 0x10, 0x85, 0x71, 0x83, 0xc3, 0x02, 0x02, 0x2d, 0x4a, 0xb4, 0x48, 0x28, 0x42, 0xa9, 0xa8, 0xb2, - 0x05, 0xe2, 0x00, 0x10, 0x1a, 0x0a, 0x28, 0x2c, 0xf7, 0xd6, 0xc4, 0x19, 0xdb, 0x23, 0xe2, 0x5d, - 0xb3, 0x1e, 0xe7, 0x87, 0x93, 0x70, 0x0e, 0x4e, 0x41, 0xc9, 0x11, 0x10, 0x5c, 0x04, 0xb3, 0xc4, - 0x40, 0x4d, 0xf1, 0xb4, 0xfb, 0xde, 0x37, 0x7a, 0x23, 0x8d, 0xb8, 0xcc, 0x89, 0x8b, 0x66, 0x3a, - 0x4e, 0x6d, 0xa9, 0xe3, 0x02, 0xe3, 0x82, 0x4c, 0x5e, 0xdf, 0x21, 0x2f, 0xad, 0xbb, 0xd7, 0xcc, - 0x46, 0x43, 0x45, 0x3a, 0x07, 0xc6, 0x25, 0xac, 0x75, 0x8d, 0x25, 0x63, 0x5a, 0x74, 0xef, 0xb8, - 0x72, 0x96, 0xad, 0x0c, 0x37, 0x76, 0x94, 0x08, 0x11, 0xad, 0x6e, 0x91, 0x61, 0x06, 0x0c, 0xf2, - 0x44, 0xec, 0x30, 0x95, 0x58, 0x33, 0x94, 0x95, 0x0a, 0x4e, 0x83, 0xb3, 0xfd, 0xe8, 0x37, 0x90, - 0xc7, 0xa2, 0xe7, 0xb2, 0x24, 0x2d, 0x80, 0x8c, 0xda, 0xf5, 0x30, 0x74, 0xd9, 0xe4, 0xcb, 0x4a, - 0x25, 0xc2, 0x36, 0x37, 0x06, 0xe7, 0x6a, 0xef, 0x9b, 0x6c, 0xec, 0xe8, 0x39, 0x10, 0x07, 0xf1, - 0x6a, 0x62, 0x4d, 0x46, 0x79, 0xe3, 0x80, 0xc9, 0x9a, 0xff, 0xaf, 0xb9, 0x10, 0x83, 0xca, 0xce, - 0xc1, 0xd1, 0xa3, 0x2f, 0x4a, 0xc8, 0x2c, 0xd0, 0xd5, 0xed, 0x4f, 0xf5, 0xdb, 0xc1, 0x5e, 0xd4, - 0xff, 0x4b, 0x6f, 0x3a, 0x28, 0xb5, 0x38, 0xca, 0x1c, 0x3e, 0x34, 0x68, 0xd2, 0x75, 0x32, 0xc3, - 0x05, 0x79, 0xae, 0x06, 0xbe, 0x5c, 0xfe, 0xa0, 0xeb, 0x8e, 0x5c, 0x1d, 0xbe, 0xbc, 0x0f, 0x83, - 0xd7, 0x56, 0x6f, 0xad, 0x9e, 0x3e, 0x86, 0x5b, 0xd3, 0x6d, 0x7f, 0xb7, 0xf3, 0xcf, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x0e, 0x9b, 0x08, 0xee, 0x7c, 0x01, 0x00, 0x00, -} diff --git a/api/gateway/semtech/semtech.proto b/api/gateway/semtech/semtech.proto deleted file mode 100644 index 7a68b5d3c..000000000 --- a/api/gateway/semtech/semtech.proto +++ /dev/null @@ -1,16 +0,0 @@ -syntax = "proto3"; - -package semtech; - -message RxMetadata { - uint32 timestamp = 1; - uint32 rf_chain = 11; - uint32 channel = 12; -} - -message TxConfiguration { - uint32 timestamp = 1; - uint32 rf_chain = 11; - bool polarization_inversion = 21; // LoRa polarization inversion (basically always true) - uint32 frequency_deviation = 22; // FSK frequency deviation in Hz -} diff --git a/core/router/downlink.go b/core/router/downlink.go index 2fc354bdb..5c7a12ebc 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -8,7 +8,6 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - pb_semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" @@ -119,11 +118,6 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo return // We can't handle any other protocols than LoRaWAN yet } - semtechMetadata := uplink.GatewayMetadata.GetSemtech() - if semtechMetadata == nil { - return // We can't handle any other gateways than Semtech yet - } - band, err := getBand(gatewayStatus.Region) if err != nil { return // We can't handle this region @@ -157,13 +151,12 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo DataRate: dataRate.String(), // This is default CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx }}}, - GatewayConfig: &pb_gateway.TxConfiguration{Gateway: &pb_gateway.TxConfiguration_Semtech{Semtech: &pb_semtech.TxConfiguration{ - Timestamp: semtechMetadata.Timestamp + uint32(delay/1000), + GatewayConfig: &pb_gateway.TxConfiguration{ + Timestamp: uplink.GatewayMetadata.Timestamp + uint32(delay/1000), RfChain: 0, PolarizationInversion: true, - }}, - Frequency: uint64(downlinkChannel.Frequency), - Power: int32(band.DefaultTXPower), + Frequency: uint64(downlinkChannel.Frequency), + Power: int32(band.DefaultTXPower), }, } options = append(options, rx1) @@ -193,13 +186,12 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo DataRate: dataRate.String(), // This is default CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx }}}, - GatewayConfig: &pb_gateway.TxConfiguration{Gateway: &pb_gateway.TxConfiguration_Semtech{Semtech: &pb_semtech.TxConfiguration{ - Timestamp: semtechMetadata.Timestamp + uint32(delay/1000), + GatewayConfig: &pb_gateway.TxConfiguration{ + Timestamp: uplink.GatewayMetadata.Timestamp + uint32(delay/1000), RfChain: 0, PolarizationInversion: true, - }}, - Frequency: uint64(band.RX2Frequency), - Power: power, + Frequency: uint64(band.RX2Frequency), + Power: power, }, } options = append(options, rx2) @@ -286,7 +278,7 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o scheduleScore := 0.0 // Between 0 and 30 (lower is better) will be over 100 if forbidden { - id, conflicts := gateway.Schedule.GetOption(option.GatewayConfig.GetSemtech().Timestamp, uint32(time/1000)) + id, conflicts := gateway.Schedule.GetOption(option.GatewayConfig.Timestamp, uint32(time/1000)) option.Identifier = id if conflicts >= 100 { scheduleScore += 100 diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 222639d7a..914823f3b 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -7,7 +7,6 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - pb_semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" @@ -25,9 +24,8 @@ func newReferenceDownlink() *pb.DownlinkMessage { DataRate: "SF7BW125", Modulation: pb_lorawan.Modulation_LORA, }}}, - GatewayConfiguration: &pb_gateway.TxConfiguration{Gateway: &pb_gateway.TxConfiguration_Semtech{Semtech: &pb_semtech.TxConfiguration{ + GatewayConfiguration: &pb_gateway.TxConfiguration{ Timestamp: 100, - }}, Frequency: 868100000, }, } @@ -119,8 +117,8 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { a.So(options[0].Score, ShouldBeLessThan, options[1].Score) // Check Delay - a.So(options[0].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 1000100) - a.So(options[1].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 2000100) + a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 1000100) + a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 2000100) // Check Frequency a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 868100000) @@ -141,8 +139,8 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { // And for joins we want a different delay (both RX1 and RX2) and DataRate (RX2) gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() options = r.buildDownlinkOptions(up, true, gtw) - a.So(options[0].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 5000100) - a.So(options[1].GatewayConfig.GetSemtech().Timestamp, ShouldEqual, 6000100) + a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 5000100) + a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 6000100) a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") } @@ -335,7 +333,7 @@ func TestComputeDownlinkScores(t *testing.T) { // Gateway used for Rx -> worse score testSubject1 := newReferenceUplink() testSubject2 := newReferenceUplink() - testSubject2.GatewayMetadata.GetSemtech().Timestamp = 10000000 + testSubject2.GatewayMetadata.Timestamp = 10000000 testSubject2.GatewayMetadata.Frequency = 868500000 testSubjectgtw = newReferenceGateway("EU_863_870") testSubjectgtw.Utilization.AddRx(newReferenceUplink()) @@ -371,7 +369,7 @@ func TestComputeDownlinkScores(t *testing.T) { // Scheduling Conflicts testSubject1 = newReferenceUplink() testSubject2 = newReferenceUplink() - testSubject2.GatewayMetadata.GetSemtech().Timestamp = 2000000 + testSubject2.GatewayMetadata.Timestamp = 2000000 testSubjectgtw = newReferenceGateway("EU_863_870") testSubjectgtw.Schedule.GetOption(1000100, 50000) testSubject1Score = r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index 790b9dd06..b6045cbe2 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -5,7 +5,6 @@ import ( "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - pb_semtech "github.com/TheThingsNetwork/ttn/api/gateway/semtech" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" @@ -46,9 +45,8 @@ func newReferenceUplink() *pb.UplinkMessage { DataRate: "SF7BW125", Modulation: pb_lorawan.Modulation_LORA, }}}, - GatewayMetadata: &pb_gateway.RxMetadata{Gateway: &pb_gateway.RxMetadata_Semtech{Semtech: &pb_semtech.RxMetadata{ + GatewayMetadata: &pb_gateway.RxMetadata{ Timestamp: 100, - }}, Frequency: 868100000, Rssi: -25.0, Snr: 5.0, From e3c9049ce157302f864bd55b78222132caa6c828 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 31 May 2016 14:54:55 +0200 Subject: [PATCH 1477/2266] Fix flakey discovery test --- core/discovery/broker_discovery_test.go | 2 +- core/discovery/discovery.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index e6bff9926..e27072c8e 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -101,7 +101,7 @@ func TestBrokerDiscoveryCache(t *testing.T) { a.So(err, ShouldBeNil) a.So(results, ShouldContain, broker) - <-time.After(10 * time.Millisecond) + <-time.After(20 * time.Millisecond) // It should return the refreshed (empty) broker list results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 563c5e379..1942be06c 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -19,7 +19,6 @@ type Discovery interface { } // discovery is a reference implementation for a TTN Service Discovery component. -// TODO: Implement one with a real database type discovery struct { services map[string]map[string]*pb.Announcement sync.RWMutex From 9e7dec10835beae2baaa8e454d6853b7144b8d9d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 31 May 2016 17:05:24 +0200 Subject: [PATCH 1478/2266] Move common logic to core/component.go --- core/broker/activation.go | 6 +- core/broker/activation_test.go | 2 + core/broker/broker.go | 51 +++++++---- core/broker/downlink.go | 2 +- core/broker/downlink_test.go | 4 +- core/broker/server_test.go | 2 + core/broker/uplink.go | 4 +- core/broker/uplink_test.go | 3 + core/component.go | 130 ++++++++++++++++++++++++++++ core/discovery/discovery.go | 22 +++++ core/networkserver/networkserver.go | 21 +++++ core/router/activation.go | 2 +- core/router/downlink.go | 8 +- core/router/router.go | 45 ++++++---- 14 files changed, 252 insertions(+), 50 deletions(-) create mode 100644 core/component.go diff --git a/core/broker/activation.go b/core/broker/activation.go index c9124ec2c..675102be3 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -52,7 +52,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } // Send Activate to NS - deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.getContext(), deduplicatedActivationRequest) + deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(), deduplicatedActivationRequest) if err != nil { return nil, err } @@ -69,12 +69,12 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } defer conn.Close() client := pb_handler.NewHandlerClient(conn) - handlerResponse, err := client.Activate(b.getContext(), deduplicatedActivationRequest) + handlerResponse, err := client.Activate(b.Component.GetContext(), deduplicatedActivationRequest) if err != nil { return nil, err } - handlerResponse, err = b.ns.Activate(b.getContext(), handlerResponse) + handlerResponse, err = b.ns.Activate(b.Component.GetContext(), handlerResponse) if err != nil { return nil, err } diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index ecef2a5f3..0468e991f 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -8,6 +8,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" @@ -20,6 +21,7 @@ func TestHandleActivation(t *testing.T) { appEUI := types.AppEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) b := &broker{ + Component: &core.Component{}, activationDeduplicator: NewDeduplicator(10 * time.Millisecond), applications: application.NewApplicationStore(), ns: &mockNetworkServer{}, diff --git a/core/broker/broker.go b/core/broker/broker.go index 5fc133a61..638313884 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -3,18 +3,19 @@ package broker import ( "errors" "sync" + "time" - "google.golang.org/grpc/metadata" - - "golang.org/x/net/context" + "gopkg.in/redis.v3" pb "github.com/TheThingsNetwork/ttn/api/broker" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker/application" ) type Broker interface { + core.ComponentInterface + HandleUplink(uplink *pb.UplinkMessage) error HandleDownlink(downlink *pb.DownlinkMessage) error HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) @@ -25,18 +26,44 @@ type Broker interface { DeactivateHandler(id string) error } +func NewRedisBroker(client *redis.Client, networkserver string, timeout time.Duration) Broker { + return &broker{ + routers: make(map[string]chan *pb.DownlinkMessage), + handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), + applications: application.NewRedisApplicationStore(client), + uplinkDeduplicator: NewDeduplicator(timeout), + activationDeduplicator: NewDeduplicator(timeout), + nsAddr: networkserver, + } +} + type broker struct { - identity *pb_discovery.Announcement + *core.Component routers map[string]chan *pb.DownlinkMessage routersLock sync.RWMutex handlers map[string]chan *pb.DeduplicatedUplinkMessage handlersLock sync.RWMutex applications application.Store + nsAddr string ns networkserver.NetworkServerClient uplinkDeduplicator Deduplicator activationDeduplicator Deduplicator } +func (b *broker) Init(c *core.Component) error { + b.Component = c + err := b.Component.UpdateTokenKey() + if err != nil { + return err + } + err = b.Component.Announce() + if err != nil { + return err + } + + return nil +} + func (b *broker) ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) { b.routersLock.Lock() defer b.routersLock.Unlock() @@ -96,17 +123,3 @@ func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, er } return nil, errors.New("Handler not active") } - -func (b *broker) getContext() context.Context { - var id, token string - if b.identity != nil { - id = b.identity.Id - token = b.identity.Token - } - md := metadata.Pairs( - "token", token, - "id", id, - ) - ctx := metadata.NewContext(context.Background(), md) - return ctx -} diff --git a/core/broker/downlink.go b/core/broker/downlink.go index e4d4b67df..270bfff91 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -16,7 +16,7 @@ func (a ByScore) Less(i, j int) bool { return a[i].Score < a[j].Score } func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { var err error - downlink, err = b.ns.Downlink(b.getContext(), downlink) + downlink, err = b.ns.Downlink(b.Component.GetContext(), downlink) if err != nil { return err } diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go index cd67fef15..8a65311ff 100644 --- a/core/broker/downlink_test.go +++ b/core/broker/downlink_test.go @@ -4,6 +4,7 @@ import ( "testing" pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" . "github.com/smartystreets/assertions" ) @@ -13,7 +14,8 @@ func TestDownlink(t *testing.T) { dlch := make(chan *pb.DownlinkMessage, 2) b := &broker{ - ns: &mockNetworkServer{}, + Component: &core.Component{}, + ns: &mockNetworkServer{}, routers: map[string]chan *pb.DownlinkMessage{ "routerID": dlch, }, diff --git a/core/broker/server_test.go b/core/broker/server_test.go index c8299564b..14e734d2b 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -9,6 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" . "github.com/smartystreets/assertions" "golang.org/x/net/context" @@ -28,6 +29,7 @@ func buildTestBrokerServer(port uint) (*broker, *grpc.Server) { panic(err) } b := &broker{ + Component: &core.Component{}, routers: make(map[string]chan *pb.DownlinkMessage), handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), ns: &mockNetworkServer{}, diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 5670148b9..3a623dd82 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -48,7 +48,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { // Request devices from NS devAddr := types.DevAddr(macPayload.FHDR.DevAddr) - getDevicesResp, err := b.ns.GetDevices(b.getContext(), &networkserver.DevicesRequest{ + getDevicesResp, err := b.ns.GetDevices(b.Component.GetContext(), &networkserver.DevicesRequest{ DevAddr: &devAddr, FCnt: macPayload.FHDR.FCnt, }) @@ -116,7 +116,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } // Pass Uplink through NS - deduplicatedUplink, err = b.ns.Uplink(b.getContext(), deduplicatedUplink) + deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(), deduplicatedUplink) if err != nil { return err } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 885cb12cb..0a307a03d 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -10,6 +10,7 @@ import ( pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" "github.com/brocaar/lorawan" @@ -20,6 +21,7 @@ func TestHandleUplink(t *testing.T) { a := New(t) b := &broker{ + Component: &core.Component{}, uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ devices: []*pb_networkserver.DevicesResponse_Device{}, @@ -64,6 +66,7 @@ func TestHandleUplink(t *testing.T) { // Add devices b = &broker{ + Component: &core.Component{}, handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ diff --git a/core/component.go b/core/component.go new file mode 100644 index 000000000..3d4577e6b --- /dev/null +++ b/core/component.go @@ -0,0 +1,130 @@ +package core + +import ( + "errors" + "fmt" + + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" + + "github.com/TheThingsNetwork/ttn/api" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/tokenkey" + "github.com/apex/log" + "github.com/dgrijalva/jwt-go" + "github.com/spf13/viper" +) + +type ComponentInterface interface { + RegisterRPC(s *grpc.Server) + Init(c *Component) error +} + +// NewComponent creates a new Component +func NewComponent(ctx log.Interface, serviceName string, announcedAddress string) *Component { + return &Component{ + Ctx: ctx, + Identity: &pb_discovery.Announcement{ + Id: viper.GetString("id"), + Token: viper.GetString("token"), + Description: viper.GetString("description"), + ServiceName: serviceName, + NetAddress: announcedAddress, + }, + DiscoveryServer: viper.GetString("discovery-server"), + TokenKeyProvider: tokenkey.NewHTTPProvider( + fmt.Sprintf("%s/key", viper.GetString("auth-server")), + viper.GetString("oauth2-keyfile"), + ), + } +} + +// Component contains the common attributes for all TTN components +type Component struct { + Identity *pb_discovery.Announcement + DiscoveryServer string + Ctx log.Interface + TokenKeyProvider tokenkey.Provider +} + +// Announce the component to TTN discovery +func (c *Component) Announce() error { + if c.DiscoveryServer == "" { + return errors.New("ttn: No discovery server configured") + } + + if c.Identity.Id == "" { + return errors.New("ttn: No ID configured") + } + + conn, err := grpc.Dial(c.DiscoveryServer, append(api.DialOptions, grpc.WithBlock())...) + if err != nil { + return err + } + defer conn.Close() + client := pb_discovery.NewDiscoveryClient(conn) + _, err = client.Announce(context.Background(), c.Identity) + if err != nil { + return fmt.Errorf("ttn: Failed to announce this component to TTN discovery: %s", err.Error()) + } + c.Ctx.Info("ttn: Announced to TTN discovery") + + return nil +} + +// UpdateTokenKey updates the OAuth Bearer token key +func (c *Component) UpdateTokenKey() error { + if c.TokenKeyProvider == nil { + return errors.New("No token provider configured") + } + + // Set up Auth Server Token Validation + tokenKey, err := c.TokenKeyProvider.Get(true) + if err != nil { + return fmt.Errorf("ttn: Failed to refresh token key: %s", err.Error()) + } + c.Ctx.Infof("ttn: Got token key for algorithm %v", tokenKey.Algorithm) + + return nil + +} + +// ValidateToken verifies an OAuth Bearer token +func (c *Component) ValidateToken(token string) (claims map[string]interface{}, err error) { + parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { + if c.TokenKeyProvider == nil { + return nil, errors.New("No token provider configured") + } + k, err := c.TokenKeyProvider.Get(false) + if err != nil { + return nil, err + } + if k.Algorithm != token.Header["alg"] { + return nil, fmt.Errorf("Expected algorithm %v but got %v", k.Algorithm, token.Header["alg"]) + } + return []byte(k.Key), nil + }) + if err != nil { + return nil, fmt.Errorf("Unable to parse token: %s", err.Error()) + } + if !parsed.Valid { + return nil, errors.New("The token is not valid or is expired") + } + return parsed.Claims, nil +} + +// GetContext returns a context for outgoing RPC requests +func (c *Component) GetContext() context.Context { + var id, token string + if c.Identity != nil { + id = c.Identity.Id + token = c.Identity.Token + } + md := metadata.Pairs( + "token", token, + "id", id, + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 1942be06c..85778efc8 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -10,20 +10,32 @@ import ( "gopkg.in/redis.v3" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" ) // Discovery specifies the interface for the TTN Service Discovery component type Discovery interface { + core.ComponentInterface Announce(announcement *pb.Announcement) error Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) } // discovery is a reference implementation for a TTN Service Discovery component. type discovery struct { + *core.Component services map[string]map[string]*pb.Announcement sync.RWMutex } +func (d *discovery) Init(c *core.Component) error { + d.Component = c + err := d.Component.UpdateTokenKey() + if err != nil { + return err + } + return nil +} + func (d *discovery) Announce(announcement *pb.Announcement) error { d.Lock() defer d.Unlock() @@ -89,9 +101,19 @@ func NewRedisDiscovery(client *redis.Client) Discovery { const redisAnnouncementPrefix = "service" type redisDiscovery struct { + *core.Component client *redis.Client } +func (d *redisDiscovery) Init(c *core.Component) error { + d.Component = c + err := d.Component.UpdateTokenKey() + if err != nil { + return err + } + return nil +} + func (d *redisDiscovery) Announce(announcement *pb.Announcement) error { key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, announcement.ServiceName, announcement.Id) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 110fdf7c1..2448fccef 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -4,10 +4,13 @@ import ( "errors" "time" + "gopkg.in/redis.v3" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -17,6 +20,7 @@ import ( // NetworkServer implements LoRaWAN-specific functionality for TTN type NetworkServer interface { + core.ComponentInterface HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error) HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) @@ -24,10 +28,27 @@ type NetworkServer interface { HandleDownlink(*pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) } +// NewRedisNetworkServer creates a new Redis-backed NetworkServer +func NewRedisNetworkServer(client *redis.Client) NetworkServer { + return &networkServer{ + devices: device.NewRedisDeviceStore(client), + } +} + type networkServer struct { + *core.Component devices device.Store } +func (n *networkServer) Init(c *core.Component) error { + n.Component = c + err := n.Component.UpdateTokenKey() + if err != nil { + return err + } + return nil +} + func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { devices, err := n.devices.GetWithAddress(*req.DevAddr) if err != nil { diff --git a/core/router/activation.go b/core/router/activation.go index c50b5eea3..6e93c1953 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -80,7 +80,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Do async request wg.Add(1) go func() { - res, err := broker.client.Activate(r.getContext(), request) + res, err := broker.client.Activate(r.Component.GetContext(), request) if err == nil && res != nil { responses <- res } diff --git a/core/router/downlink.go b/core/router/downlink.go index 5c7a12ebc..ac767a411 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -50,8 +50,8 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { } identifier := option.Identifier - if r.identity != nil { - identifier = strings.TrimPrefix(option.Identifier, fmt.Sprintf("%s:", r.identity.Id)) + if r.Component != nil && r.Component.Identity != nil { + identifier = strings.TrimPrefix(option.Identifier, fmt.Sprintf("%s:", r.Component.Identity.Id)) } err := gateway.Schedule.Schedule(identifier, downlinkMessage) @@ -201,8 +201,8 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo for _, option := range options { // Add router ID to downlink option - if r.identity != nil { - option.Identifier = fmt.Sprintf("%s:%s", option.Identifier, r.identity.Id) + if r.Component != nil && r.Component.Identity != nil { + option.Identifier = fmt.Sprintf("%s:%s", option.Identifier, r.Component.Identity.Id) } // Filter all illegal options diff --git a/core/router/router.go b/core/router/router.go index 1203cc6fc..9b20dab88 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -4,16 +4,14 @@ import ( "io" "sync" - "golang.org/x/net/context" - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" @@ -21,6 +19,7 @@ import ( // Router component type Router interface { + core.ComponentInterface // Handle a status message from a gateway HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error // Handle an uplink message from a gateway @@ -40,8 +39,16 @@ type broker struct { association pb_broker.Broker_AssociateClient } +// NewRouter creates a new Router +func NewRouter() Router { + return &router{ + gateways: make(map[types.GatewayEUI]*gateway.Gateway), + brokers: make(map[string]*broker), + } +} + type router struct { - identity *pb_discovery.Announcement + *core.Component gateways map[types.GatewayEUI]*gateway.Gateway gatewaysLock sync.RWMutex brokerDiscovery discovery.BrokerDiscovery @@ -49,6 +56,20 @@ type router struct { brokersLock sync.RWMutex } +func (r *router) Init(c *core.Component) error { + r.Component = c + err := r.Component.UpdateTokenKey() + if err != nil { + return err + } + err = r.Component.Announce() + if err != nil { + return err + } + r.brokerDiscovery = discovery.NewBrokerDiscovery(r.Component.DiscoveryServer) + return nil +} + // getGateway gets or creates a Gateway func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { // We're going to be optimistic and guess that the gateway is already active @@ -88,7 +109,7 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { } client := pb_broker.NewBrokerClient(conn) - association, err := client.Associate(r.getContext()) + association, err := client.Associate(r.Component.GetContext()) if err != nil { return nil, err } @@ -118,17 +139,3 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { } return r.brokers[req.NetAddress], nil } - -func (r *router) getContext() context.Context { - var id, token string - if r.identity != nil { - id = r.identity.Id - token = r.identity.Token - } - md := metadata.Pairs( - "token", token, - "id", id, - ) - ctx := metadata.NewContext(context.Background(), md) - return ctx -} From 2b21bcf896b1973580f026234915e6597a9fa75a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 31 May 2016 17:28:10 +0200 Subject: [PATCH 1479/2266] Stop flushing all DBs --- core/discovery/discovery_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index db3a88f75..8c09f6d5b 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -30,7 +30,6 @@ func TestDiscoveryAnnounce(t *testing.T) { client.Del("service:broker:broker1.1") client.Del("service:broker:broker1.2") }() - client.FlushAll() discoveries := map[string]Discovery{ "local": localDiscovery, @@ -100,7 +99,6 @@ func TestDiscoveryDiscover(t *testing.T) { client.Del("service:broker:broker2.1") client.Del("service:broker:broker2.2") }() - client.FlushAll() // This depends on the previous test to pass redisDiscovery.Announce(router) From 5a2fabbfc89afacf0c07eb546d264de0c850ee13 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 31 May 2016 17:28:36 +0200 Subject: [PATCH 1480/2266] Don't use redis tx --- core/networkserver/device/store.go | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index da18d48c0..d1dc4b7be 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -208,21 +208,16 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { } key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppEUI, new.DevEUI) - tx, err := s.client.Watch(key) - if err == nil { - defer tx.Close() - // Check for old DevAddr - if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { - // Delete old DevAddr - if devAddr != "" { - err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() - if err != nil { - return err - } + + // Check for old DevAddr + if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { + // Delete old DevAddr + if devAddr != "" { + err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() + if err != nil { + return err } } - } else if err != redis.Nil { - return err } dmap, err := new.ToStringStringMap(fields...) @@ -243,12 +238,8 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) - tx, err := s.client.Watch(key) var dmap map[string]string - if err != nil && err != redis.Nil { - return err - } - defer tx.Close() + // Check for old DevAddr if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { // Delete old DevAddr @@ -259,6 +250,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de } } } + // Update Device dev := &Device{ AppEUI: appEUI, @@ -270,7 +262,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de } // Don't touch Utilization and Options - dmap, err = dev.ToStringStringMap([]string{"dev_eui", "app_eui", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down"}...) + dmap, err := dev.ToStringStringMap("dev_eui", "app_eui", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") // Register Device err = s.client.HMSetMap(key, dmap).Err() From ca8b73a43f49f545d8015e8baa7b451e15c3955f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 2 Jun 2016 19:15:16 +0200 Subject: [PATCH 1481/2266] Handle connection teardown in gRPC servers --- core/broker/server.go | 37 ++++++++++++++++++++++++++++--------- core/broker/server_test.go | 4 ---- core/router/server.go | 15 ++++++++++++--- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/core/broker/server.go b/core/broker/server.go index 436082b05..96ec559ba 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -52,17 +52,27 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { } defer b.broker.DeactivateRouter(routerID) go func() { - // DeactivateRouter closes the channel, so this goroutine will return - for downlink := range downlinkChannel { - if err := stream.Send(downlink); err != nil { - return // TODO: panic or something + for { + if downlinkChannel == nil { + return + } + select { + case <-stream.Context().Done(): + return + case downlink := <-downlinkChannel: + if downlink != nil { + if err := stream.Send(downlink); err != nil { + // TODO: Check if the stream should be closed here + return + } + } } } }() for { uplink, err := stream.Recv() if err == io.EOF { - return nil // TODO: Close stream + return nil } if err != nil { return err @@ -81,12 +91,21 @@ func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_Subscri return err } defer b.broker.DeactivateHandler(handlerID) - for uplink := range uplinkChannel { - if err := stream.Send(uplink); err != nil { - return err + for { + if uplinkChannel == nil { + return nil + } + select { + case <-stream.Context().Done(): + return stream.Context().Err() + case uplink := <-uplinkChannel: + if uplink != nil { + if err := stream.Send(uplink); err != nil { + return err + } + } } } - return nil } func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { diff --git a/core/broker/server_test.go b/core/broker/server_test.go index 14e734d2b..f3ac41967 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -111,10 +111,6 @@ func TestSubscribeRPC(t *testing.T) { <-time.After(5 * time.Millisecond) - b.handlers["HandlerID"] <- &pb.DeduplicatedUplinkMessage{} - - <-time.After(5 * time.Millisecond) - a.So(b.handlers, ShouldBeEmpty) } diff --git a/core/router/server.go b/core/router/server.go index fc0eab161..b17dd4b53 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -89,10 +89,19 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri return err } defer r.router.UnsubscribeDownlink(gatewayEUI) - for downlink := range downlinkChannel { - stream.Send(downlink) + for { + if downlinkChannel == nil { + return nil + } + select { + case <-stream.Context().Done(): + return stream.Context().Err() + case downlink := <-downlinkChannel: + if err := stream.Send(downlink); err != nil { + return err + } + } } - return nil } // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) From c91dc99193f97095065f972893ef8840081a4fc0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 3 Jun 2016 10:28:44 +0200 Subject: [PATCH 1482/2266] Add server time in Broker --- api/broker/broker.pb.go | 200 +++++++++++++++++++++++++------------- api/broker/broker.proto | 2 + core/broker/activation.go | 3 + core/broker/uplink.go | 4 + 4 files changed, 140 insertions(+), 69 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 3e3ecfc34..abec9ffc2 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -162,6 +162,7 @@ type DeduplicatedUplinkMessage struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ServerTime int64 `protobuf:"varint,23,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"` ResponseTemplate *DownlinkMessage `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` } @@ -243,6 +244,7 @@ type DeduplicatedDeviceActivationRequest struct { ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` + ServerTime int64 `protobuf:"varint,24,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"` ResponseTemplate *DeviceActivationResponse `protobuf:"bytes,31,opt,name=response_template,json=responseTemplate" json:"response_template,omitempty"` } @@ -1177,6 +1179,13 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { i += n } } + if m.ServerTime != 0 { + data[i] = 0xb8 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ServerTime)) + } if m.ResponseTemplate != nil { data[i] = 0xfa i++ @@ -1365,6 +1374,13 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error } i += n24 } + if m.ServerTime != 0 { + data[i] = 0xc0 + i++ + data[i] = 0x1 + i++ + i = encodeVarintBroker(data, i, uint64(m.ServerTime)) + } if m.ResponseTemplate != nil { data[i] = 0xfa i++ @@ -1773,6 +1789,9 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { n += 2 + l + sovBroker(uint64(l)) } } + if m.ServerTime != 0 { + n += 2 + sovBroker(uint64(m.ServerTime)) + } if m.ResponseTemplate != nil { l = m.ResponseTemplate.Size() n += 2 + l + sovBroker(uint64(l)) @@ -1845,6 +1864,9 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { l = m.ActivationMetadata.Size() n += 2 + l + sovBroker(uint64(l)) } + if m.ServerTime != 0 { + n += 2 + sovBroker(uint64(m.ServerTime)) + } if m.ResponseTemplate != nil { l = m.ResponseTemplate.Size() n += 2 + l + sovBroker(uint64(l)) @@ -2892,6 +2914,25 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 23: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerTime", wireType) + } + m.ServerTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ServerTime |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 31: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResponseTemplate", wireType) @@ -3442,6 +3483,25 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 24: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ServerTime", wireType) + } + m.ServerTime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.ServerTime |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } case 31: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ResponseTemplate", wireType) @@ -4308,73 +4368,75 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1088 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0xe3, 0x44, - 0x18, 0x26, 0xc9, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xf4, 0xcb, 0x0d, 0x55, 0x5b, 0x0c, 0x42, 0xe5, - 0x63, 0x13, 0x36, 0xa8, 0xac, 0x2a, 0x04, 0xab, 0x74, 0xbb, 0x2a, 0x8b, 0x94, 0x65, 0xe5, 0x6d, - 0x2f, 0x5c, 0x22, 0xc7, 0x9e, 0x26, 0xa3, 0xa6, 0xb6, 0xf1, 0x8c, 0xdb, 0xed, 0x8d, 0x1f, 0x80, - 0xc4, 0x85, 0x03, 0x07, 0xc4, 0x7f, 0x41, 0x5c, 0x38, 0x72, 0xe6, 0x80, 0x10, 0x5c, 0xf8, 0x19, - 0x8c, 0xc7, 0x33, 0xfe, 0x68, 0xe3, 0x6d, 0x77, 0x59, 0x0e, 0x48, 0x3d, 0x44, 0xf1, 0xbc, 0x1f, - 0x8f, 0xc7, 0xcf, 0xfb, 0x3e, 0x33, 0x2f, 0xdc, 0x1b, 0x11, 0x36, 0x0e, 0x86, 0x6d, 0xcb, 0x3d, - 0xe9, 0x1c, 0x8c, 0xf1, 0xc1, 0x98, 0x38, 0x23, 0xfa, 0x18, 0xb3, 0x33, 0xd7, 0x3f, 0xee, 0x30, - 0xe6, 0x74, 0x4c, 0x8f, 0x74, 0x86, 0xbe, 0x7b, 0x8c, 0x7d, 0xf9, 0xd7, 0xf6, 0x7c, 0x97, 0xb9, - 0xa8, 0x1c, 0xad, 0x5a, 0x77, 0x52, 0x00, 0x23, 0x77, 0xe4, 0x76, 0x84, 0x7b, 0x18, 0x1c, 0x89, - 0x95, 0x58, 0x88, 0xa7, 0x28, 0x2d, 0x13, 0x9e, 0xfb, 0x3e, 0xfe, 0x93, 0xe1, 0x1f, 0x5f, 0x27, - 0x5c, 0x84, 0x5a, 0xee, 0x24, 0x7e, 0x90, 0xc9, 0x3b, 0xd7, 0x49, 0x1e, 0x99, 0x0c, 0x9f, 0x99, - 0xe7, 0xea, 0x3f, 0x4a, 0xd5, 0x7f, 0x2e, 0x42, 0x7d, 0xcf, 0x3d, 0x73, 0x26, 0xc4, 0x39, 0xfe, - 0xc2, 0x63, 0xc4, 0x75, 0xd0, 0x3a, 0x00, 0xb1, 0xb1, 0xc3, 0xc8, 0x11, 0xc1, 0xbe, 0x56, 0xd8, - 0x2c, 0x6c, 0xcd, 0x19, 0x29, 0x0b, 0xfa, 0x12, 0x2a, 0x12, 0x63, 0x80, 0x03, 0xa2, 0x15, 0x79, - 0x40, 0x75, 0x77, 0xe7, 0xb7, 0xdf, 0x37, 0xb6, 0xaf, 0xda, 0x86, 0xe5, 0xfa, 0xb8, 0xc3, 0xce, - 0x3d, 0x4c, 0xdb, 0xfb, 0x11, 0xc2, 0xc3, 0xc3, 0x47, 0x06, 0x48, 0xb4, 0x87, 0x01, 0x41, 0x8b, - 0x70, 0x9b, 0x86, 0x51, 0x5a, 0x89, 0xa3, 0xd6, 0x8c, 0x68, 0x81, 0x5a, 0x30, 0x6b, 0x63, 0xd3, - 0xe6, 0x7b, 0xc4, 0xda, 0x2d, 0xee, 0x28, 0x19, 0xf1, 0x1a, 0xed, 0x42, 0x43, 0xb1, 0x31, 0xb0, - 0x5c, 0xe7, 0x88, 0x8c, 0xb4, 0xdb, 0x3c, 0xa4, 0xd2, 0x5d, 0x6d, 0xc7, 0x2c, 0x1d, 0x3c, 0x7b, - 0x20, 0x3c, 0x81, 0x6f, 0x86, 0x5f, 0x68, 0xd4, 0x95, 0x27, 0x32, 0xa3, 0xfb, 0x50, 0x57, 0x5f, - 0x24, 0x21, 0xca, 0x02, 0x42, 0x6b, 0x2b, 0xb2, 0x2e, 0x22, 0xd4, 0xa4, 0x23, 0xb2, 0xea, 0xdf, - 0x96, 0xa0, 0x76, 0xe8, 0x85, 0x1c, 0xf6, 0x31, 0xa5, 0xe6, 0x08, 0x23, 0x0d, 0x66, 0x3c, 0xf3, - 0x7c, 0xe2, 0x9a, 0xb6, 0x60, 0xb0, 0x6a, 0xa8, 0x25, 0x7a, 0x0c, 0x33, 0x36, 0x3e, 0x15, 0xd4, - 0x55, 0x04, 0x75, 0xdb, 0x9c, 0xba, 0xbb, 0x2f, 0x40, 0xdd, 0x1e, 0x3e, 0x0d, 0x69, 0x2b, 0x73, - 0x94, 0x90, 0x32, 0x8e, 0x67, 0x7a, 0x9e, 0xc0, 0xab, 0xbe, 0x14, 0x5e, 0xcf, 0xf3, 0x04, 0x1e, - 0x47, 0x09, 0xf1, 0x7a, 0x30, 0x1f, 0x13, 0x7a, 0x82, 0x99, 0x69, 0x9b, 0xcc, 0xd4, 0x96, 0x04, - 0x1f, 0x8b, 0x09, 0xa5, 0xc6, 0xb3, 0xbe, 0xf4, 0x19, 0x4d, 0x65, 0x54, 0x16, 0xf4, 0x29, 0x34, - 0x15, 0x9f, 0x31, 0xc2, 0xb2, 0x40, 0x58, 0x88, 0x19, 0x4d, 0x01, 0x34, 0xa4, 0x2d, 0xce, 0xef, - 0x41, 0xd3, 0x96, 0x3d, 0x39, 0x70, 0x45, 0x53, 0x52, 0x6d, 0x63, 0xb3, 0xc4, 0xf3, 0x97, 0xdb, - 0x52, 0x9b, 0xd9, 0x9e, 0x35, 0x1a, 0x76, 0x66, 0x4d, 0xf5, 0x6f, 0x8a, 0xd0, 0x50, 0x31, 0xff, - 0xff, 0x9a, 0xdc, 0x87, 0xc6, 0x05, 0x42, 0x64, 0x45, 0xf2, 0xf8, 0xa8, 0x67, 0xf9, 0xd0, 0x03, - 0xd0, 0xf8, 0x16, 0x89, 0x85, 0x7b, 0x16, 0x23, 0xa7, 0x51, 0x0f, 0x63, 0xea, 0x71, 0xa6, 0x9e, - 0x47, 0xcb, 0x94, 0xd7, 0x56, 0x5e, 0xe8, 0xb5, 0x3f, 0x96, 0x60, 0x75, 0x0f, 0xdb, 0x01, 0x97, - 0x86, 0xc5, 0x6b, 0x6c, 0xdf, 0x68, 0xe4, 0x0a, 0x8d, 0x94, 0xae, 0xad, 0x91, 0x3d, 0x98, 0xf7, - 0x65, 0x05, 0x07, 0x0c, 0x9f, 0x78, 0x13, 0xee, 0xe7, 0x22, 0x09, 0xb7, 0xb0, 0x72, 0xb1, 0x3a, - 0x92, 0x70, 0xa3, 0xa9, 0x32, 0x0e, 0x64, 0x82, 0xfe, 0x77, 0x09, 0x56, 0x2e, 0x37, 0xc6, 0x57, - 0x01, 0xa6, 0xec, 0xa6, 0x3c, 0xff, 0xe6, 0x08, 0xeb, 0xc3, 0x82, 0x19, 0x33, 0x9a, 0x40, 0xac, - 0x08, 0x88, 0xb5, 0x64, 0x13, 0x09, 0xed, 0x31, 0x16, 0x32, 0x2f, 0xd9, 0x5e, 0xc5, 0x89, 0xf8, - 0xf5, 0x2d, 0x78, 0x33, 0xad, 0xc5, 0x9b, 0xb2, 0xff, 0x07, 0xaa, 0x7c, 0xc5, 0x65, 0xef, 0xe7, - 0x8b, 0x7c, 0x33, 0xae, 0x7b, 0xce, 0xb9, 0x3e, 0x45, 0xed, 0x08, 0x9a, 0x4f, 0x83, 0x21, 0xb5, - 0x7c, 0x32, 0xc4, 0xb2, 0xdc, 0xfa, 0x12, 0x2c, 0x70, 0x1a, 0x45, 0x4f, 0x84, 0x6d, 0xa2, 0xcc, - 0x77, 0x61, 0x31, 0x6b, 0x96, 0x97, 0xc5, 0x2a, 0xcc, 0xca, 0x9a, 0x51, 0xde, 0x1e, 0x25, 0x3e, - 0x1a, 0xce, 0x44, 0xec, 0x53, 0x7d, 0x1b, 0x5a, 0x06, 0x1e, 0x11, 0xca, 0xb0, 0x9f, 0x4a, 0x55, - 0x6d, 0xb5, 0x92, 0x14, 0x3b, 0x1a, 0x29, 0x65, 0xd5, 0xf4, 0x7b, 0xb0, 0x76, 0xe8, 0xf8, 0x2f, - 0x91, 0xd8, 0x80, 0xda, 0x53, 0x66, 0xb2, 0x20, 0xde, 0xf3, 0x4f, 0x25, 0x28, 0x47, 0x16, 0xa4, - 0x43, 0x39, 0x10, 0x77, 0x8d, 0xc8, 0xa9, 0x74, 0xa1, 0x1d, 0x8e, 0xda, 0x06, 0x27, 0x81, 0x1a, - 0xd2, 0x83, 0x3a, 0x50, 0x8b, 0x9e, 0x06, 0x81, 0x43, 0x38, 0x82, 0x98, 0x64, 0xb3, 0xa1, 0xd5, - 0x28, 0xe0, 0x50, 0xf8, 0xd1, 0xdb, 0x7c, 0x0c, 0x95, 0xa2, 0x92, 0xf7, 0x60, 0x3a, 0x36, 0xf6, - 0xa1, 0xf7, 0xa1, 0x92, 0xd4, 0x92, 0xca, 0x0e, 0x4c, 0x87, 0xa6, 0xdd, 0x68, 0x07, 0x52, 0x95, - 0xa7, 0x6a, 0x2f, 0xcb, 0x97, 0x92, 0xe6, 0x53, 0x51, 0x72, 0x43, 0x9f, 0xc0, 0x62, 0x3a, 0xd5, - 0xb4, 0x2c, 0xec, 0x71, 0x65, 0xcb, 0x76, 0x4b, 0x27, 0xa7, 0xba, 0x92, 0xf6, 0x64, 0x18, 0xfa, - 0x08, 0x6a, 0x76, 0x7c, 0x20, 0x84, 0x97, 0x7b, 0xd4, 0x59, 0x4d, 0x91, 0xf7, 0x04, 0xfb, 0x56, - 0x38, 0xf2, 0x4f, 0x78, 0x76, 0x36, 0x0c, 0xbd, 0x07, 0xf3, 0x7c, 0x4c, 0x76, 0xb0, 0xc5, 0x41, - 0x06, 0xbe, 0x1b, 0xf0, 0xba, 0x51, 0xed, 0x1d, 0x31, 0xb0, 0x37, 0x63, 0x87, 0x11, 0xd9, 0xd1, - 0x1d, 0x40, 0x49, 0xf0, 0xd8, 0x74, 0xec, 0x49, 0x18, 0xfd, 0xae, 0x88, 0x4e, 0x60, 0x3e, 0x93, - 0x8e, 0xee, 0x77, 0x45, 0x28, 0xef, 0x8a, 0xc6, 0xe6, 0xd3, 0xc7, 0x5c, 0x8f, 0x52, 0xd7, 0x22, - 0xfc, 0x0b, 0xd0, 0x92, 0x6a, 0xf7, 0xcc, 0x08, 0xd1, 0xca, 0xbb, 0xea, 0xb6, 0x0a, 0x1f, 0x14, - 0xd0, 0xe7, 0x30, 0x17, 0xb7, 0x3b, 0xd2, 0x54, 0xe4, 0x45, 0x05, 0xb4, 0xde, 0x48, 0x94, 0x94, - 0x33, 0xa9, 0x70, 0xac, 0x36, 0xcc, 0x3c, 0x09, 0x86, 0x13, 0x42, 0xc7, 0x28, 0xef, 0x9d, 0xad, - 0x59, 0x41, 0x5c, 0xcf, 0x3a, 0xde, 0x2a, 0x70, 0xe5, 0xce, 0x4a, 0x49, 0x62, 0xb4, 0x91, 0x2f, - 0xd5, 0x68, 0x07, 0x57, 0x6a, 0xb9, 0xfb, 0x43, 0x11, 0x6a, 0x11, 0x2d, 0x7d, 0xd3, 0xe1, 0xef, - 0xf2, 0xd1, 0x23, 0xa8, 0xa6, 0x05, 0x8a, 0x5e, 0x57, 0x18, 0x53, 0xd4, 0xdc, 0x5a, 0x9b, 0xee, - 0x94, 0x9a, 0x7e, 0x00, 0x0b, 0x53, 0x84, 0x8b, 0x74, 0x95, 0x94, 0xaf, 0xea, 0xe4, 0x93, 0xd1, - 0x3e, 0x2c, 0x4d, 0x95, 0x31, 0x7a, 0x2b, 0xae, 0xdc, 0x73, 0x54, 0x9e, 0x02, 0xea, 0xc2, 0xdc, - 0x3e, 0x66, 0x52, 0xc7, 0x71, 0xd9, 0x33, 0x4a, 0x6f, 0xd5, 0xb3, 0xe6, 0xdd, 0xe6, 0x2f, 0x7f, - 0xae, 0x17, 0x7e, 0xe5, 0xbf, 0x3f, 0xf8, 0xef, 0xfb, 0xbf, 0xd6, 0x5f, 0x1b, 0x96, 0xc5, 0x51, - 0xfb, 0xe1, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xb6, 0x43, 0x54, 0x55, 0xf7, 0x0f, 0x00, 0x00, + // 1117 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4d, 0x6f, 0xe3, 0x44, + 0x18, 0x26, 0xcd, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xf4, 0x23, 0x6e, 0xa8, 0xda, 0x62, 0x10, 0x2a, + 0x1f, 0x9b, 0xb0, 0x41, 0x65, 0x55, 0x21, 0x58, 0xa5, 0xdb, 0x55, 0x59, 0xa4, 0x2c, 0x2b, 0x6f, + 0x7a, 0xe1, 0x12, 0x39, 0xf6, 0x34, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x6e, 0xff, 0x03, 0x12, + 0x17, 0x0e, 0x1c, 0xb8, 0xf0, 0x4b, 0x10, 0xe2, 0xc2, 0x91, 0x33, 0x07, 0x84, 0xe0, 0xc2, 0xcf, + 0x60, 0x3c, 0x9e, 0xf1, 0x47, 0x9a, 0x6c, 0xbb, 0xcb, 0x72, 0x40, 0xea, 0xc1, 0x8a, 0xe7, 0xfd, + 0x78, 0x3c, 0x7e, 0xde, 0xf7, 0x19, 0xbf, 0x81, 0x7b, 0x43, 0xc2, 0x46, 0xc1, 0xa0, 0x69, 0xb9, + 0xa7, 0xad, 0xde, 0x08, 0xf7, 0x46, 0xc4, 0x19, 0xd2, 0xc7, 0x98, 0x9d, 0xbb, 0xfe, 0x49, 0x8b, + 0x31, 0xa7, 0x65, 0x7a, 0xa4, 0x35, 0xf0, 0xdd, 0x13, 0xec, 0xcb, 0x9f, 0xa6, 0xe7, 0xbb, 0xcc, + 0x45, 0x85, 0x68, 0xd5, 0xb8, 0x93, 0x02, 0x18, 0xba, 0x43, 0xb7, 0x25, 0xdc, 0x83, 0xe0, 0x58, + 0xac, 0xc4, 0x42, 0xdc, 0x45, 0x69, 0x99, 0xf0, 0x99, 0xcf, 0xe3, 0x97, 0x0c, 0xff, 0xf8, 0x3a, + 0xe1, 0x22, 0xd4, 0x72, 0xc7, 0xf1, 0x8d, 0x4c, 0xde, 0xbb, 0x4e, 0xf2, 0xd0, 0x64, 0xf8, 0xdc, + 0xbc, 0x50, 0xbf, 0x51, 0xaa, 0xfe, 0xf3, 0x1c, 0x54, 0x0e, 0xdc, 0x73, 0x67, 0x4c, 0x9c, 0x93, + 0x2f, 0x3c, 0x46, 0x5c, 0x07, 0x6d, 0x02, 0x10, 0x1b, 0x3b, 0x8c, 0x1c, 0x13, 0xec, 0x6b, 0xb9, + 0xed, 0xdc, 0xce, 0xa2, 0x91, 0xb2, 0xa0, 0x2f, 0xa1, 0x28, 0x31, 0xfa, 0x38, 0x20, 0xda, 0x1c, + 0x0f, 0x28, 0xed, 0xef, 0xfd, 0xf6, 0xfb, 0xd6, 0xee, 0x55, 0xdb, 0xb0, 0x5c, 0x1f, 0xb7, 0xd8, + 0x85, 0x87, 0x69, 0xf3, 0x30, 0x42, 0x78, 0x78, 0xf4, 0xc8, 0x00, 0x89, 0xf6, 0x30, 0x20, 0x68, + 0x05, 0x6e, 0xd3, 0x30, 0x4a, 0xcb, 0x73, 0xd4, 0xb2, 0x11, 0x2d, 0x50, 0x03, 0x16, 0x6c, 0x6c, + 0xda, 0x7c, 0x8f, 0x58, 0xbb, 0xc5, 0x1d, 0x79, 0x23, 0x5e, 0xa3, 0x7d, 0xa8, 0x2a, 0x36, 0xfa, + 0x96, 0xeb, 0x1c, 0x93, 0xa1, 0x76, 0x9b, 0x87, 0x14, 0xdb, 0xeb, 0xcd, 0x98, 0xa5, 0xde, 0xb3, + 0x07, 0xc2, 0x13, 0xf8, 0x66, 0xf8, 0x86, 0x46, 0x45, 0x79, 0x22, 0x33, 0xba, 0x0f, 0x15, 0xf5, + 0x46, 0x12, 0xa2, 0x20, 0x20, 0xb4, 0xa6, 0x22, 0x6b, 0x12, 0xa1, 0x2c, 0x1d, 0x91, 0x55, 0xff, + 0x26, 0x0f, 0xe5, 0x23, 0x2f, 0xe4, 0xb0, 0x8b, 0x29, 0x35, 0x87, 0x18, 0x69, 0x30, 0xef, 0x99, + 0x17, 0x63, 0xd7, 0xb4, 0x05, 0x83, 0x25, 0x43, 0x2d, 0xd1, 0x63, 0x98, 0xb7, 0xf1, 0x99, 0xa0, + 0xae, 0x28, 0xa8, 0xdb, 0xe5, 0xd4, 0xdd, 0x7d, 0x01, 0xea, 0x0e, 0xf0, 0x59, 0x48, 0x5b, 0x81, + 0xa3, 0x84, 0x94, 0x71, 0x3c, 0xd3, 0xf3, 0x04, 0x5e, 0xe9, 0xa5, 0xf0, 0x3a, 0x9e, 0x27, 0xf0, + 0x38, 0x4a, 0x88, 0xd7, 0x81, 0xa5, 0x98, 0xd0, 0x53, 0xcc, 0x4c, 0xdb, 0x64, 0xa6, 0xb6, 0x2a, + 0xf8, 0x58, 0x49, 0x28, 0x35, 0x9e, 0x75, 0xa5, 0xcf, 0xa8, 0x29, 0xa3, 0xb2, 0xa0, 0x4f, 0xa1, + 0xa6, 0xf8, 0x8c, 0x11, 0xd6, 0x04, 0xc2, 0x72, 0xcc, 0x68, 0x0a, 0xa0, 0x2a, 0x6d, 0x71, 0x7e, + 0x07, 0x6a, 0xb6, 0xec, 0xc9, 0xbe, 0x2b, 0x9a, 0x92, 0x6a, 0x5b, 0xdb, 0x79, 0x9e, 0xbf, 0xd6, + 0x94, 0xda, 0xcc, 0xf6, 0xac, 0x51, 0xb5, 0x33, 0x6b, 0xaa, 0x7f, 0x3d, 0x07, 0x55, 0x15, 0xf3, + 0xff, 0xaf, 0xc9, 0x7d, 0xa8, 0x4e, 0x10, 0x22, 0x2b, 0x32, 0x8b, 0x8f, 0x4a, 0x96, 0x0f, 0x3d, + 0x00, 0x8d, 0x6f, 0x91, 0x58, 0xb8, 0x63, 0x31, 0x72, 0x16, 0xf5, 0x30, 0xa6, 0x1e, 0x67, 0xea, + 0x79, 0xb4, 0x4c, 0x79, 0x6c, 0xf1, 0x85, 0x1e, 0xfb, 0x63, 0x1e, 0xd6, 0x0f, 0xb0, 0x1d, 0x70, + 0x69, 0x58, 0xbc, 0xc6, 0xf6, 0x8d, 0x46, 0xae, 0xd0, 0x48, 0xfe, 0xda, 0x1a, 0xd9, 0x82, 0x22, + 0xc5, 0xfe, 0x19, 0xf6, 0xfb, 0x8c, 0x9c, 0x62, 0xad, 0x2e, 0x8e, 0x45, 0x88, 0x4c, 0x3d, 0x6e, + 0x41, 0x07, 0xb0, 0xe4, 0xcb, 0x12, 0xf7, 0x19, 0x3e, 0xf5, 0xc6, 0x1c, 0x80, 0xab, 0x28, 0xdc, + 0x63, 0x7d, 0xb2, 0x7c, 0xb2, 0x22, 0x46, 0x4d, 0x65, 0xf4, 0x64, 0x82, 0xfe, 0x77, 0x1e, 0xea, + 0x97, 0x3b, 0xe7, 0xab, 0x00, 0x53, 0x76, 0x53, 0xbf, 0x7f, 0x73, 0xc6, 0x75, 0x61, 0xd9, 0x8c, + 0x19, 0x4d, 0x20, 0xea, 0x02, 0x62, 0x23, 0xd9, 0x44, 0x42, 0x7b, 0x8c, 0x85, 0xcc, 0x4b, 0xb6, + 0x57, 0x71, 0x64, 0xfe, 0x70, 0x0b, 0xde, 0x4c, 0x8b, 0xf5, 0xa6, 0xec, 0xff, 0x81, 0x6c, 0x5f, + 0x71, 0xd9, 0x27, 0x4e, 0x01, 0xed, 0xd2, 0x29, 0xd0, 0x9d, 0x7d, 0x0a, 0x6c, 0xc7, 0x8d, 0x31, + 0xe3, 0xcb, 0x30, 0xe5, 0x38, 0x40, 0x50, 0x7b, 0x1a, 0x0c, 0xa8, 0xe5, 0x93, 0x01, 0x96, 0xfd, + 0xa0, 0xaf, 0xc2, 0x32, 0xe7, 0x59, 0x34, 0x4d, 0xd8, 0x47, 0xca, 0x7c, 0x17, 0x56, 0xb2, 0x66, + 0xf9, 0xb9, 0x59, 0x87, 0x05, 0x59, 0x54, 0xca, 0xfb, 0x27, 0xcf, 0x87, 0xcb, 0xf9, 0xa8, 0x3c, + 0x54, 0xdf, 0x85, 0x86, 0x81, 0x87, 0x84, 0x32, 0xec, 0xa7, 0x52, 0x55, 0xdf, 0xd5, 0x93, 0x6e, + 0x88, 0x86, 0x52, 0x59, 0x56, 0xfd, 0x1e, 0x6c, 0x1c, 0x39, 0xfe, 0x4b, 0x24, 0x56, 0xa1, 0xfc, + 0x94, 0x99, 0x2c, 0x88, 0xf7, 0xfc, 0x53, 0x1e, 0x0a, 0x91, 0x05, 0xe9, 0x50, 0x08, 0xc4, 0xd7, + 0x4a, 0xe4, 0x14, 0xdb, 0xd0, 0x0c, 0x87, 0x75, 0x83, 0x93, 0x40, 0x0d, 0xe9, 0x41, 0x2d, 0x28, + 0x47, 0x77, 0xfd, 0xc0, 0x21, 0x1c, 0x41, 0xcc, 0xc2, 0xd9, 0xd0, 0x52, 0x14, 0x70, 0x24, 0xfc, + 0xe8, 0x6d, 0x3e, 0xc8, 0x4a, 0xd5, 0xc9, 0x2f, 0x69, 0x3a, 0x36, 0xf6, 0xa1, 0xf7, 0xa1, 0x98, + 0x14, 0x9b, 0xca, 0x16, 0x4d, 0x87, 0xa6, 0xdd, 0x68, 0x0f, 0x52, 0xad, 0x41, 0xd5, 0x5e, 0xd6, + 0x2e, 0x25, 0x2d, 0xa5, 0xa2, 0xe4, 0x86, 0x3e, 0x81, 0x95, 0x74, 0xaa, 0x69, 0x59, 0xd8, 0xe3, + 0xd2, 0x97, 0xfd, 0x98, 0x4e, 0x4e, 0xb5, 0x2d, 0xed, 0xc8, 0x30, 0xf4, 0x11, 0x94, 0xed, 0xf8, + 0xc4, 0x08, 0xc7, 0x83, 0xa8, 0xb3, 0x6a, 0x22, 0xef, 0x09, 0xf6, 0xad, 0xf0, 0x4f, 0xc3, 0x98, + 0x67, 0x67, 0xc3, 0xd0, 0x7b, 0xb0, 0xc4, 0x07, 0x6d, 0x07, 0x5b, 0x1c, 0xa4, 0xef, 0xbb, 0x01, + 0xaf, 0x1b, 0xd5, 0xde, 0x11, 0x23, 0x7f, 0x2d, 0x76, 0x18, 0x91, 0x1d, 0xdd, 0x01, 0x94, 0x04, + 0x8f, 0x4c, 0xc7, 0x1e, 0x87, 0xd1, 0xef, 0x8a, 0xe8, 0x04, 0xe6, 0x33, 0xe9, 0x68, 0x7f, 0x3b, + 0x07, 0x85, 0x7d, 0xd1, 0xd8, 0x7c, 0x7e, 0x59, 0xec, 0x50, 0xea, 0x5a, 0x84, 0xbf, 0x01, 0x5a, + 0x55, 0xed, 0x9e, 0x19, 0x42, 0x1a, 0xb3, 0xbe, 0x85, 0x3b, 0xb9, 0x0f, 0x72, 0xe8, 0x73, 0x58, + 0x8c, 0xdb, 0x1d, 0x69, 0x2a, 0x72, 0x52, 0x01, 0x8d, 0x37, 0x12, 0x25, 0xcd, 0x98, 0x75, 0x38, + 0x56, 0x13, 0xe6, 0x9f, 0x04, 0x83, 0x31, 0xa1, 0x23, 0x34, 0xeb, 0x99, 0x8d, 0x05, 0x41, 0x5c, + 0xc7, 0x3a, 0xd9, 0xc9, 0x71, 0xe5, 0x2e, 0x48, 0x49, 0x62, 0xb4, 0x35, 0x5b, 0xaa, 0xd1, 0x0e, + 0xae, 0xd4, 0x72, 0xfb, 0xfb, 0x39, 0x28, 0x47, 0xb4, 0x74, 0x4d, 0x87, 0x3f, 0xcb, 0x47, 0x8f, + 0xa0, 0x94, 0x16, 0x28, 0x7a, 0x5d, 0x61, 0x4c, 0x51, 0x73, 0x63, 0x63, 0xba, 0x53, 0x6a, 0xfa, + 0x01, 0x2c, 0x4f, 0x11, 0x2e, 0xd2, 0x55, 0xd2, 0x6c, 0x55, 0x27, 0xaf, 0x8c, 0x0e, 0x61, 0x75, + 0xaa, 0x8c, 0xd1, 0x5b, 0x71, 0xe5, 0x9e, 0xa3, 0xf2, 0x14, 0x50, 0x1b, 0x16, 0x0f, 0x31, 0x93, + 0x3a, 0x8e, 0xcb, 0x9e, 0x51, 0x7a, 0xa3, 0x92, 0x35, 0xef, 0xd7, 0x7e, 0xf9, 0x73, 0x33, 0xf7, + 0x2b, 0xbf, 0xfe, 0xe0, 0xd7, 0x77, 0x7f, 0x6d, 0xbe, 0x36, 0x28, 0x88, 0xb3, 0xf8, 0xc3, 0x7f, + 0x02, 0x00, 0x00, 0xff, 0xff, 0xb7, 0xa4, 0x23, 0xb1, 0x39, 0x10, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 4cf2b5bc6..c87bf223d 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -49,6 +49,7 @@ message DeduplicatedUplinkMessage { bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; + int64 server_time = 23; DownlinkMessage response_template = 31; } @@ -71,6 +72,7 @@ message DeduplicatedDeviceActivationRequest { protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; protocol.ActivationMetadata activation_metadata = 23; + int64 server_time = 24; DeviceActivationResponse response_template = 31; } diff --git a/core/broker/activation.go b/core/broker/activation.go index 675102be3..5176e7740 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -4,6 +4,7 @@ import ( "crypto/md5" "encoding/hex" "errors" + "time" "google.golang.org/grpc" @@ -15,6 +16,7 @@ import ( func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { var err error + time := time.Now() // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) @@ -48,6 +50,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D ProtocolMetadata: base.ProtocolMetadata, GatewayMetadata: gatewayMetadata, ActivationMetadata: base.ActivationMetadata, + ServerTime: time.UnixNano(), ResponseTemplate: deviceActivationResponse, } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 3a623dd82..a9ab5f8dd 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "errors" "sort" + "time" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/gateway" @@ -23,6 +24,8 @@ var ( ) func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { + time := time.Now() + // De-duplicate uplink messages duplicates := b.deduplicateUplink(uplink) if len(duplicates) == 0 { @@ -112,6 +115,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { AppEui: device.AppEui, ProtocolMetadata: base.ProtocolMetadata, GatewayMetadata: gatewayMetadata, + ServerTime: time.UnixNano(), ResponseTemplate: downlinkMessage, } From b84fa1591b979a48cc2de0434642e7ccb4f45039 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 3 Jun 2016 14:25:56 +0200 Subject: [PATCH 1483/2266] Remove code for previous version --- core/adapters/fields/adapter.go | 103 - core/adapters/fields/adapter_test.go | 137 - core/adapters/fields/appStorage.go | 78 - core/adapters/fields/appStorage_test.go | 67 - core/adapters/fields/functions.go | 105 - core/adapters/fields/functions_test.go | 154 - core/adapters/http/doc.go | 5 - core/adapters/http/healthz.go | 25 - core/adapters/http/healthz_test.go | 34 - core/adapters/http/http.go | 75 - core/adapters/http/http_test.go | 97 - core/adapters/http/statuspage.go | 96 - core/adapters/http/statuspage_test.go | 60 - core/adapters/mqtt/adapter.go | 110 - core/adapters/mqtt/adapter_test.go | 147 - core/adapters/udp/connection.go | 90 - core/adapters/udp/connection_test.go | 39 - core/adapters/udp/conversions.go | 187 - core/adapters/udp/conversions_test.go | 469 -- core/adapters/udp/udp.go | 289 -- core/adapters/udp/udp_test.go | 1297 ------ core/application.pb.go | 1094 ----- core/broker.pb.go | 1225 ----- core/broker_manager.pb.go | 1485 ------ core/components/broker/appStorage.go | 84 - core/components/broker/appStorage_test.go | 124 - core/components/broker/broker.go | 361 -- core/components/broker/brokerClient.go | 34 - core/components/broker/brokerManager.go | 136 - core/components/broker/broker_test.go | 1748 ------- core/components/broker/controller.go | 205 - core/components/broker/controller_test.go | 422 -- core/components/broker/dialer.go | 60 - core/components/broker/mocks_test.go | 189 - core/components/handler/devStorage.go | 159 - core/components/handler/devStorage_test.go | 319 -- core/components/handler/handler.go | 812 ---- core/components/handler/handlerClient.go | 34 - core/components/handler/handlerClient_test.go | 1141 ----- core/components/handler/handlerManager.go | 319 -- core/components/handler/handler_test.go | 2454 ---------- core/components/handler/mocks_test.go | 142 - core/components/handler/pktStorage.go | 170 - core/components/handler/pktStorage_test.go | 205 - core/components/handler/queue.go | 83 - core/components/handler/queue_test.go | 85 - core/components/router/gtwStorage.go | 82 - core/components/router/gtwStorage_test.go | 172 - core/components/router/mockstorage_test.go | 88 - core/components/router/router.go | 393 -- core/components/router/router_test.go | 2129 --------- core/components/router/storage.go | 149 - core/components/router/storage_test.go | 242 - core/core.go | 110 - core/core.pb.go | 1093 ----- core/definitions.go | 65 - core/dutycycle/dutyManager.go | 302 -- core/dutycycle/dutyManager_test.go | 315 -- core/dutycycle/helpers_test.go | 27 - core/dutycycle/scoreComputer.go | 113 - core/dutycycle/scoreComputer_test.go | 251 - core/flags.go | 8 - core/handler.pb.go | 1849 -------- core/handler_manager.pb.go | 4096 ----------------- core/lorawan.pb.go | 1767 ------- core/mocks/mocks.go | 368 -- core/otaa/session_keys.go | 4 +- core/protos/application.proto | 28 - core/protos/broker.proto | 34 - core/protos/broker_manager.proto | 41 - core/protos/core.proto | 36 - core/protos/handler.proto | 51 - core/protos/handler_manager.proto | 116 - core/protos/lorawan.proto | 53 - core/protos/router.proto | 43 - core/router.pb.go | 1566 ------- core/storage/storage.go | 315 -- core/storage/storage_test.go | 272 -- semtech/decode.go | 142 - semtech/decode_test.go | 354 -- semtech/encode.go | 129 - semtech/encode_test.go | 420 -- semtech/helpers_test.go | 54 - semtech/proxies.go | 45 - semtech/semtech.go | 129 - semtech/semtech_test.go | 146 - utils/errors/errors.go | 63 - utils/readwriter/readwriter.go | 150 - utils/readwriter/readwriter_test.go | 300 -- utils/shield/shield.go | 41 - utils/shield/shield_test.go | 36 - utils/testing/response_writer.go | 36 - utils/testing/testing.go | 61 - utils/toa/toa.go | 4 +- 94 files changed, 4 insertions(+), 35038 deletions(-) delete mode 100644 core/adapters/fields/adapter.go delete mode 100644 core/adapters/fields/adapter_test.go delete mode 100644 core/adapters/fields/appStorage.go delete mode 100644 core/adapters/fields/appStorage_test.go delete mode 100644 core/adapters/fields/functions.go delete mode 100644 core/adapters/fields/functions_test.go delete mode 100644 core/adapters/http/doc.go delete mode 100644 core/adapters/http/healthz.go delete mode 100644 core/adapters/http/healthz_test.go delete mode 100644 core/adapters/http/http.go delete mode 100644 core/adapters/http/http_test.go delete mode 100644 core/adapters/http/statuspage.go delete mode 100644 core/adapters/http/statuspage_test.go delete mode 100644 core/adapters/mqtt/adapter.go delete mode 100644 core/adapters/mqtt/adapter_test.go delete mode 100644 core/adapters/udp/connection.go delete mode 100644 core/adapters/udp/connection_test.go delete mode 100644 core/adapters/udp/conversions.go delete mode 100644 core/adapters/udp/conversions_test.go delete mode 100644 core/adapters/udp/udp.go delete mode 100644 core/adapters/udp/udp_test.go delete mode 100644 core/application.pb.go delete mode 100644 core/broker.pb.go delete mode 100644 core/broker_manager.pb.go delete mode 100644 core/components/broker/appStorage.go delete mode 100644 core/components/broker/appStorage_test.go delete mode 100644 core/components/broker/broker.go delete mode 100644 core/components/broker/brokerClient.go delete mode 100644 core/components/broker/brokerManager.go delete mode 100644 core/components/broker/broker_test.go delete mode 100644 core/components/broker/controller.go delete mode 100644 core/components/broker/controller_test.go delete mode 100644 core/components/broker/dialer.go delete mode 100644 core/components/broker/mocks_test.go delete mode 100644 core/components/handler/devStorage.go delete mode 100644 core/components/handler/devStorage_test.go delete mode 100644 core/components/handler/handler.go delete mode 100644 core/components/handler/handlerClient.go delete mode 100644 core/components/handler/handlerClient_test.go delete mode 100644 core/components/handler/handlerManager.go delete mode 100644 core/components/handler/handler_test.go delete mode 100644 core/components/handler/mocks_test.go delete mode 100644 core/components/handler/pktStorage.go delete mode 100644 core/components/handler/pktStorage_test.go delete mode 100644 core/components/handler/queue.go delete mode 100644 core/components/handler/queue_test.go delete mode 100644 core/components/router/gtwStorage.go delete mode 100644 core/components/router/gtwStorage_test.go delete mode 100644 core/components/router/mockstorage_test.go delete mode 100644 core/components/router/router.go delete mode 100644 core/components/router/router_test.go delete mode 100644 core/components/router/storage.go delete mode 100644 core/components/router/storage_test.go delete mode 100644 core/core.go delete mode 100644 core/core.pb.go delete mode 100644 core/definitions.go delete mode 100644 core/dutycycle/dutyManager.go delete mode 100644 core/dutycycle/dutyManager_test.go delete mode 100644 core/dutycycle/helpers_test.go delete mode 100644 core/dutycycle/scoreComputer.go delete mode 100644 core/dutycycle/scoreComputer_test.go delete mode 100644 core/flags.go delete mode 100644 core/handler.pb.go delete mode 100644 core/handler_manager.pb.go delete mode 100644 core/lorawan.pb.go delete mode 100644 core/mocks/mocks.go delete mode 100644 core/protos/application.proto delete mode 100644 core/protos/broker.proto delete mode 100644 core/protos/broker_manager.proto delete mode 100644 core/protos/core.proto delete mode 100644 core/protos/handler.proto delete mode 100644 core/protos/handler_manager.proto delete mode 100644 core/protos/lorawan.proto delete mode 100644 core/protos/router.proto delete mode 100644 core/router.pb.go delete mode 100644 core/storage/storage.go delete mode 100644 core/storage/storage_test.go delete mode 100644 semtech/decode.go delete mode 100644 semtech/decode_test.go delete mode 100644 semtech/encode.go delete mode 100644 semtech/encode_test.go delete mode 100644 semtech/helpers_test.go delete mode 100644 semtech/proxies.go delete mode 100644 semtech/semtech.go delete mode 100644 semtech/semtech_test.go delete mode 100644 utils/errors/errors.go delete mode 100644 utils/readwriter/readwriter.go delete mode 100644 utils/readwriter/readwriter_test.go delete mode 100644 utils/shield/shield.go delete mode 100644 utils/shield/shield_test.go delete mode 100644 utils/testing/response_writer.go diff --git a/core/adapters/fields/adapter.go b/core/adapters/fields/adapter.go deleted file mode 100644 index d7690742f..000000000 --- a/core/adapters/fields/adapter.go +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package fields - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/apex/log" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -// Adapter represents the interface of an application -type Adapter interface { - core.AppClient - Storage() AppStorage - SubscribeDownlink(handler core.HandlerServer) error -} - -type adapter struct { - ctx log.Interface - storage AppStorage - mqtt mqtt.Adapter -} - -// NewAdapter returns a new adapter that processes binary payload to fields -func NewAdapter(ctx log.Interface, storage AppStorage, next mqtt.Adapter) Adapter { - return &adapter{ctx, storage, next} -} - -func (s *adapter) HandleData(context context.Context, in *core.DataAppReq, opt ...grpc.CallOption) (*core.DataAppRes, error) { - var appEUI types.AppEUI - appEUI.Unmarshal(in.AppEUI) - var devEUI types.DevEUI - devEUI.Unmarshal(in.DevEUI) - ctx := s.ctx.WithFields(&log.Fields{"AppEUI": appEUI, "DevEUI": devEUI}) - - req := core.DataUpAppReq{ - Payload: in.Payload, - Metadata: core.ProtoMetaToAppMeta(in.Metadata...), - FPort: uint8(in.FPort), - FCnt: in.FCnt, - DevEUI: devEUI.String(), - } - - functions, err := s.storage.GetFunctions(appEUI) - if err != nil { - ctx.WithError(err).Warn("Failed to get functions") - // If we can't get the functions here, just publish it anyway without fields - return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) - } - - if functions == nil { - // Publish when there are no payload functions set - return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) - } - - fields, valid, err := functions.Process(in.Payload) - if err != nil { - // If there were errors processing the payload, just publish it anyway - // without fields - ctx.WithError(err).Warn("Failed to process payload") - return new(core.DataAppRes), s.mqtt.PublishUplink(appEUI, devEUI, req) - } - - if !valid { - // If the payload has been processed successfully but is not valid, it should - // not be published - ctx.Info("The processed payload is not valid") - return new(core.DataAppRes), errors.New(errors.Operational, "The processed payload is not valid") - } - - req.Fields = fields - if err := s.mqtt.PublishUplink(appEUI, devEUI, req); err != nil { - return new(core.DataAppRes), errors.New(errors.Operational, "Failed to publish data") - } - - return new(core.DataAppRes), nil -} - -func (s *adapter) HandleJoin(context context.Context, in *core.JoinAppReq, opt ...grpc.CallOption) (*core.JoinAppRes, error) { - var appEUI types.AppEUI - appEUI.Unmarshal(in.AppEUI) - var devEUI types.DevEUI - devEUI.Unmarshal(in.DevEUI) - - req := core.OTAAAppReq{ - Metadata: core.ProtoMetaToAppMeta(in.Metadata...), - } - - return new(core.JoinAppRes), s.mqtt.PublishActivation(appEUI, devEUI, req) -} - -func (s *adapter) SubscribeDownlink(handler core.HandlerServer) error { - return s.mqtt.SubscribeDownlink(handler) -} - -func (s *adapter) Storage() AppStorage { - return s.storage -} diff --git a/core/adapters/fields/adapter_test.go b/core/adapters/fields/adapter_test.go deleted file mode 100644 index f6848e60a..000000000 --- a/core/adapters/fields/adapter_test.go +++ /dev/null @@ -1,137 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package fields - -import ( - "testing" - - "golang.org/x/net/context" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" - . "github.com/smartystreets/assertions" -) - -type mockPublisher struct { - appEUI *types.AppEUI - devEUI *types.DevEUI - uplinkReq *core.DataUpAppReq - activationReq *core.OTAAAppReq -} - -func TestHandleData(t *testing.T) { - a := New(t) - - ctx := GetLogger(t, "TestHandleData") - storage, err := ConnectRedis("localhost:6379", 0) - a.So(err, ShouldBeNil) - defer storage.Close() - defer storage.Reset() - - publisher := &mockPublisher{} - adapter := NewAdapter(ctx, storage, publisher) - - appEUI, _ := types.ParseAppEUI("0102030405060708") - devEUI, _ := types.ParseDevEUI("00000000AABBCCDD") - - req := &core.DataAppReq{ - AppEUI: appEUI.Bytes(), - DevEUI: devEUI.Bytes(), - FPort: 1, - Payload: []byte{0x08, 0x70}, - } - - // No functions - res, err := adapter.HandleData(context.Background(), req) - a.So(res, ShouldEqual, new(core.DataAppRes)) - a.So(err, ShouldBeNil) - a.So(publisher.appEUI, ShouldResemble, &appEUI) - a.So(publisher.devEUI, ShouldResemble, &devEUI) - a.So(publisher.uplinkReq, ShouldNotBeNil) - a.So(publisher.uplinkReq.Fields, ShouldBeEmpty) - - // Normal flow - functions := &Functions{ - Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, - } - err = storage.SetFunctions(appEUI, functions) - a.So(err, ShouldBeNil) - res, err = adapter.HandleData(context.Background(), req) - a.So(res, ShouldEqual, new(core.DataAppRes)) - a.So(err, ShouldBeNil) - a.So(publisher.appEUI, ShouldResemble, &appEUI) - a.So(publisher.devEUI, ShouldResemble, &devEUI) - a.So(publisher.uplinkReq, ShouldNotBeNil) - a.So(publisher.uplinkReq.Fields, ShouldResemble, map[string]interface{}{ - "temperature": 21.6, - }) - - // Invalidate data - functions.Validator = `function(data) { return false; }` - err = storage.SetFunctions(appEUI, functions) - a.So(err, ShouldBeNil) - res, err = adapter.HandleData(context.Background(), req) - a.So(res, ShouldEqual, new(core.DataAppRes)) - a.So(err, ShouldNotBeNil) - - // Function error - functions.Converter = `function(data) { throw "expected"; }` - err = storage.SetFunctions(appEUI, functions) - a.So(err, ShouldBeNil) - res, err = adapter.HandleData(context.Background(), req) - a.So(res, ShouldEqual, new(core.DataAppRes)) - a.So(err, ShouldBeNil) - a.So(publisher.appEUI, ShouldResemble, &appEUI) - a.So(publisher.devEUI, ShouldResemble, &devEUI) - a.So(publisher.uplinkReq, ShouldNotBeNil) - a.So(publisher.uplinkReq.Fields, ShouldResemble, *new(map[string]interface{})) -} - -func TestHandleJoin(t *testing.T) { - a := New(t) - - ctx := GetLogger(t, "TestHandleJoin") - storage, err := ConnectRedis("localhost:6379", 0) - a.So(err, ShouldBeNil) - defer storage.Close() - defer storage.Reset() - - publisher := &mockPublisher{} - adapter := NewAdapter(ctx, storage, publisher) - - appEUI, _ := types.ParseAppEUI("0102030405060708") - devEUI, _ := types.ParseDevEUI("00000000AABBCCDD") - - req := &core.JoinAppReq{ - AppEUI: appEUI.Bytes(), - DevEUI: devEUI.Bytes(), - } - - res, err := adapter.HandleJoin(context.Background(), req) - a.So(res, ShouldEqual, new(core.JoinAppRes)) - a.So(err, ShouldBeNil) - - a.So(publisher.appEUI, ShouldResemble, &appEUI) - a.So(publisher.devEUI, ShouldResemble, &devEUI) - a.So(publisher.activationReq, ShouldNotBeNil) -} - -func (p *mockPublisher) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error { - p.appEUI = &appEUI - p.devEUI = &devEUI - p.uplinkReq = &req - return nil -} - -func (p *mockPublisher) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) error { - p.appEUI = &appEUI - p.devEUI = &devEUI - p.activationReq = &req - return nil -} - -func (p *mockPublisher) SubscribeDownlink(handler core.HandlerServer) error { - return nil -} diff --git a/core/adapters/fields/appStorage.go b/core/adapters/fields/appStorage.go deleted file mode 100644 index cca0565a0..000000000 --- a/core/adapters/fields/appStorage.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package fields - -import ( - "fmt" - - "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -const appKey = "fields:app:%s" - -// AppStorage provides storage for applications -type AppStorage interface { - SetFunctions(eui types.AppEUI, functions *Functions) error - GetFunctions(eui types.AppEUI) (*Functions, error) - Reset() error - Close() error -} - -type redisAppStorage struct { - client *redis.Client -} - -// ConnectRedis connects to Redis using the specified options -func ConnectRedis(addr string, db int64) (AppStorage, error) { - client := redis.NewClient(&redis.Options{ - Addr: addr, - DB: db, - }) - _, err := client.Ping().Result() - if err != nil { - client.Close() - return nil, err - } - return &redisAppStorage{client}, nil -} - -func makeKey(eui types.AppEUI) string { - return fmt.Sprintf(appKey, eui.String()) -} - -func (s *redisAppStorage) SetFunctions(eui types.AppEUI, functions *Functions) error { - return s.client.HMSetMap(makeKey(eui), map[string]string{ - "decoder": functions.Decoder, - "converter": functions.Converter, - "validator": functions.Validator, - }).Err() -} - -func (s *redisAppStorage) GetFunctions(eui types.AppEUI) (*Functions, error) { - m, err := s.client.HGetAllMap(makeKey(eui)).Result() - if err == redis.Nil { - return nil, nil - } else if err != nil { - return nil, err - } - decoder, ok := m["decoder"] - if !ok { - return nil, nil - } - return &Functions{ - Decoder: decoder, - Converter: m["converter"], - Validator: m["validator"], - }, nil -} - -func (s *redisAppStorage) Reset() error { - return s.client.FlushDb().Err() -} - -func (s *redisAppStorage) Close() error { - return s.client.Close() -} diff --git a/core/adapters/fields/appStorage_test.go b/core/adapters/fields/appStorage_test.go deleted file mode 100644 index f4df7cfb3..000000000 --- a/core/adapters/fields/appStorage_test.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package fields - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/smartystreets/assertions" -) - -func createStorage() AppStorage { - storage, err := ConnectRedis("localhost:6379", 0) - if err != nil { - panic(err) - } - return storage -} - -func TestConnect(t *testing.T) { - a := New(t) - - c, err := ConnectRedis("localhost:6379", 0) - a.So(err, ShouldBeNil) - defer c.Close() - - _, err = ConnectRedis("", 0) - a.So(err, ShouldNotBeNil) -} - -func TestGetFunctions(t *testing.T) { - a := New(t) - - eui, _ := types.ParseAppEUI("8000000000000001") - - storage := createStorage() - defer storage.Close() - - fetchedFunctions, err := storage.GetFunctions(eui) - a.So(err, ShouldBeNil) - a.So(fetchedFunctions, ShouldBeNil) -} - -func TestSetFunctions(t *testing.T) { - a := New(t) - - eui, _ := types.ParseAppEUI("8000000000000001") - functions := &Functions{ - Decoder: `function (payload) { return { size: payload.length; } }`, - Converter: `function (data) { return data; }`, - Validator: `function (data) { return data.size % 2 == 0; }`, - } - - storage := createStorage() - defer storage.Close() - defer storage.Reset() - - err := storage.SetFunctions(eui, functions) - a.So(err, ShouldBeNil) - - fetchedFunctions, err := storage.GetFunctions(eui) - a.So(err, ShouldBeNil) - a.So(fetchedFunctions.Decoder, ShouldEqual, functions.Decoder) - a.So(fetchedFunctions.Converter, ShouldEqual, functions.Converter) - a.So(fetchedFunctions.Validator, ShouldEqual, functions.Validator) -} diff --git a/core/adapters/fields/functions.go b/core/adapters/fields/functions.go deleted file mode 100644 index 626448c40..000000000 --- a/core/adapters/fields/functions.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package fields - -import ( - "errors" - "fmt" - - "github.com/robertkrimen/otto" -) - -// Functions decodes, converts and validates payload using JavaScript functions -type Functions struct { - // Decoder is a JavaScript function that accepts the payload as byte array and - // returns an object containing the decoded values - Decoder string - // Converter is a JavaScript function that accepts the data as decoded by - // Decoder and returns an object containing the converted values - Converter string - // Validator is a JavaScript function that validates the data is converted by - // Converter and returns a boolean value indicating the validity of the data - Validator string -} - -// Decode decodes the payload using the Decoder function into a map -func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { - if f.Decoder == "" { - return nil, errors.New("Decoder function not set") - } - - vm := otto.New() - vm.Set("payload", payload) - value, err := vm.Run(fmt.Sprintf("(%s)(payload)", f.Decoder)) - if err != nil { - return nil, err - } - - if !value.IsObject() { - return nil, errors.New("Decoder does not return an object") - } - - v, _ := value.Export() - return v.(map[string]interface{}), nil -} - -// Convert converts the values in the specified map to a another map using the -// Converter function. If the Converter function is not set, this function -// returns the data as-is -func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{}, error) { - if f.Converter == "" { - return data, nil - } - - vm := otto.New() - vm.Set("data", data) - value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Converter)) - if err != nil { - return nil, err - } - - if !value.IsObject() { - return nil, errors.New("Converter does not return an object") - } - - v, _ := value.Export() - return v.(map[string]interface{}), nil -} - -// Validate validates the values in the specified map using the Validator -// function. If the Validator function is not set, this function returns true -func (f *Functions) Validate(data map[string]interface{}) (bool, error) { - if f.Validator == "" { - return true, nil - } - - vm := otto.New() - vm.Set("data", data) - value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Validator)) - if err != nil { - return false, err - } - - if !value.IsBoolean() { - return false, errors.New("Validator does not return a boolean") - } - - return value.ToBoolean() -} - -// Process decodes the specified payload, converts it and test the validity -func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error) { - decoded, err := f.Decode(payload) - if err != nil { - return nil, false, err - } - - converted, err := f.Convert(decoded) - if err != nil { - return nil, false, err - } - - valid, err := f.Validate(converted) - return converted, valid, err -} diff --git a/core/adapters/fields/functions_test.go b/core/adapters/fields/functions_test.go deleted file mode 100644 index f94ba5ee5..000000000 --- a/core/adapters/fields/functions_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package fields - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestDecode(t *testing.T) { - a := New(t) - - functions := &Functions{ - Decoder: `function(payload) { - return { - value: (payload[0] << 8) | payload[1] - }; -}`, - } - payload := []byte{0x48, 0x65} - - m, err := functions.Decode(payload) - a.So(err, ShouldBeNil) - - size, ok := m["value"] - a.So(ok, ShouldBeTrue) - a.So(size, ShouldEqual, 18533) -} - -func TestConvert(t *testing.T) { - a := New(t) - - withFunction := &Functions{ - Converter: `function(data) { - return { - celcius: data.temperature * 2 - }; -}`, - } - data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) - a.So(err, ShouldBeNil) - a.So(data["celcius"], ShouldEqual, 22) - - withoutFunction := &Functions{} - data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) - a.So(err, ShouldBeNil) - a.So(data["temperature"], ShouldEqual, 11) -} - -func TestValidate(t *testing.T) { - a := New(t) - - withFunction := &Functions{ - Validator: `function(data) { - return data.temperature < 20; - }`, - } - valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeTrue) - valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeFalse) - - withoutFunction := &Functions{} - valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeTrue) -} - -func TestProcess(t *testing.T) { - a := New(t) - - functions := &Functions{ - Decoder: `function(payload) { - return { - temperature: payload[0], - humidity: payload[1] - } -}`, - Converter: `function(data) { - data.temperature /= 2; - return data; -}`, - Validator: `function(data) { - return data.humidity >= 0 && data.humidity <= 100; -}`, - } - - data, valid, err := functions.Process([]byte{40, 110}) - a.So(err, ShouldBeNil) - a.So(valid, ShouldBeFalse) - a.So(data["temperature"], ShouldEqual, 20) - a.So(data["humidity"], ShouldEqual, 110) -} - -func TestProcessInvalidFunction(t *testing.T) { - a := New(t) - - // Empty Function - functions := &Functions{ - Decoder: ``, - } - _, _, err := functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) - - // Invalid Function - functions = &Functions{ - Decoder: `this is not valid JavaScript`, - } - _, _, err = functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) - - // Invalid return - functions = &Functions{ - Decoder: `function(payload) { return "Hello" }`, - } - _, _, err = functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) - - // Invalid Function - functions = &Functions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Converter: `this is not valid JavaScript`, - } - _, _, err = functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) - - // Invalid Return - functions = &Functions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Converter: `function(data) { return "Hello" }`, - } - _, _, err = functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) - - // Invalid Function - functions = &Functions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Validator: `this is not valid JavaScript`, - } - _, _, err = functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) - - // Invalid Return - functions = &Functions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Validator: `function(data) { return "Hello" }`, - } - _, _, err = functions.Process([]byte{40, 110}) - a.So(err, ShouldNotBeNil) -} diff --git a/core/adapters/http/doc.go b/core/adapters/http/doc.go deleted file mode 100644 index 96ffc9720..000000000 --- a/core/adapters/http/doc.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package http provides adapter implementations which run on top of http. -package http diff --git a/core/adapters/http/healthz.go b/core/adapters/http/healthz.go deleted file mode 100644 index f8aeb0dfe..000000000 --- a/core/adapters/http/healthz.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" -) - -// Healthz defines a handler to ping adapters via a GET request. -// -// It listens to requests of the form: [GET] /healthz -type Healthz struct{} - -// URL implements the http.Handler interface -func (p Healthz) URL() string { - return "/healthz" -} - -// Handle implements the http.Handler interface -func (p Healthz) Handle(w http.ResponseWriter, req *http.Request) error { - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) - return nil -} diff --git a/core/adapters/http/healthz_test.go b/core/adapters/http/healthz_test.go deleted file mode 100644 index af93fe29a..000000000 --- a/core/adapters/http/healthz_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/smartystreets/assertions" -) - -func TestHealthzURL(t *testing.T) { - a := assertions.New(t) - - h := Healthz{} - - a.So(h.URL(), assertions.ShouldEqual, "/healthz") -} - -func TestHealthzHandle(t *testing.T) { - a := assertions.New(t) - - h := Healthz{} - - req, _ := http.NewRequest("GET", "/healthz", nil) - req.RemoteAddr = "127.0.0.1:12345" - rw := NewResponseWriter() - - h.Handle(&rw, req) - a.So(rw.TheStatus, assertions.ShouldEqual, 200) - a.So(string(rw.TheBody), assertions.ShouldEqual, "ok") -} diff --git a/core/adapters/http/http.go b/core/adapters/http/http.go deleted file mode 100644 index dbb33081d..000000000 --- a/core/adapters/http/http.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" - "time" - - "github.com/apex/log" -) - -// Interface defines the public interfaces used by the adapter -type Interface interface { - Bind(Handler) -} - -// adapter type materializes an http adapter -type adapter struct { - Client http.Client // Adapter is also an http client - ServeMux *http.ServeMux // Holds a references to the adapter servemux in order to dynamically define endpoints - Components -} - -// Handler defines endpoint-specific handler. -type Handler interface { - URL() string - Handle(w http.ResponseWriter, req *http.Request) error -} - -// Components makes instantiation easier to read -type Components struct { - Ctx log.Interface -} - -// Options makes instantiation easier to read -type Options struct { - NetAddr string - Timeout time.Duration -} - -// New instantiates a new adapter -func New(c Components, o Options) Interface { - a := adapter{ - Components: c, - ServeMux: http.NewServeMux(), - Client: http.Client{Timeout: o.Timeout}, - } - go a.listenRequests(o.NetAddr) - return &a -} - -// Bind registers a handler to a specific endpoint -func (a *adapter) Bind(h Handler) { - a.Ctx.WithField("url", h.URL()).Info("Register new endpoint") - a.ServeMux.HandleFunc(h.URL(), func(w http.ResponseWriter, req *http.Request) { - Ctx := a.Ctx.WithField("url", h.URL()) - Ctx.Debug("Handle new request") - err := h.Handle(w, req) - if err != nil { - Ctx.WithError(err).Debug("Failed to handle the request") - } - }) -} - -// listenRequests handles incoming registration request sent through http to the adapter -func (a *adapter) listenRequests(net string) { - server := http.Server{ - Addr: net, - Handler: a.ServeMux, - } - a.Ctx.WithField("bind", net).Info("Starting Server") - err := server.ListenAndServe() - a.Ctx.WithError(err).Warn("HTTP connection lost") -} diff --git a/core/adapters/http/http_test.go b/core/adapters/http/http_test.go deleted file mode 100644 index 91dc74a6d..000000000 --- a/core/adapters/http/http_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "fmt" - "net/http" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// MockHandler mocks the http.Handler interface -type MockHandler struct { - Failures map[string]error - InURL struct { - Called bool - } - OutURL struct { - URL string - } - InHandle struct { - Called bool - } -} - -// NewMockHandler constructs a new MockHandler object -func NewMockHandler() *MockHandler { - return &MockHandler{ - Failures: make(map[string]error), - } -} - -// URL implements the http.Handler interface -func (m *MockHandler) URL() string { - m.InURL.Called = true - return m.OutURL.URL -} - -// Handle implements the http.Handler interface -func (m *MockHandler) Handle(w http.ResponseWriter, req *http.Request) error { - m.InHandle.Called = true - return m.Failures["Handle"] -} - -func TestBind(t *testing.T) { - { - Desc(t, "Bind a handler and handle a request") - - // Build - options := Options{NetAddr: "0.0.0.0:3001", Timeout: time.Millisecond * 50} - a := New( - Components{Ctx: GetLogger(t, "Adapter")}, - options, - ) - cli := http.Client{} - hdl := NewMockHandler() - hdl.OutURL.URL = "/mock" - <-time.After(time.Millisecond * 50) - - // Operate - a.Bind(hdl) - _, _ = cli.Get(fmt.Sprintf("http://%s%s", options.NetAddr, hdl.URL())) - - // Check - Check(t, true, hdl.InURL.Called, "Url() Calls") - Check(t, true, hdl.InHandle.Called, "Handle() Calls") - } - - // -------------------- - - { - Desc(t, "Bind a handler and handle a request | handle fails") - - // Build - options := Options{NetAddr: "0.0.0.0:3002", Timeout: time.Millisecond * 50} - a := New( - Components{Ctx: GetLogger(t, "Adapter")}, - options, - ) - cli := http.Client{} - hdl := NewMockHandler() - hdl.OutURL.URL = "/mock" - hdl.Failures["Handle"] = fmt.Errorf("Mock Error") - <-time.After(time.Millisecond * 50) - - // Operate - a.Bind(hdl) - _, _ = cli.Get(fmt.Sprintf("http://%s%s", options.NetAddr, hdl.URL())) - - // Check - Check(t, true, hdl.InURL.Called, "Url() Calls") - Check(t, true, hdl.InHandle.Called, "Handle() Calls") - } -} diff --git a/core/adapters/http/statuspage.go b/core/adapters/http/statuspage.go deleted file mode 100644 index c601f23bb..000000000 --- a/core/adapters/http/statuspage.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "encoding/json" - "net/http" - "strings" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/rcrowley/go-metrics" -) - -// StatusPage shows statistic on GET request -// -// It listens to requests of the form: [GET] /status -// -// No body or query param are expected -type StatusPage struct{} - -// URL implements the http.Handler interface -func (p StatusPage) URL() string { - return "/status" -} - -// Handle implements the http.Handler interface -func (p StatusPage) Handle(w http.ResponseWriter, req *http.Request) error { - // Check the http method - if req.Method != "GET" { - err := errors.New(errors.Structural, "Unreckognized HTTP method. Please use [GET] to request the status") - w.WriteHeader(http.StatusMethodNotAllowed) - w.Write([]byte(err.Error())) - return err - } - - allStats := make(map[string]interface{}) - - stats.Registry.Each(func(name string, i interface{}) { - // Make sure we put things in the right place - thisStat := allStats - for _, path := range strings.Split(name, ".") { - if thisStat[path] == nil { - thisStat[path] = make(map[string]interface{}) - } - if _, ok := thisStat[path].(map[string]interface{}); ok { - thisStat = thisStat[path].(map[string]interface{}) - } else { - return - } - } - - switch metric := i.(type) { - - case metrics.Counter: - m := metric.Snapshot() - thisStat["count"] = m.Count() - - case metrics.Histogram: - h := metric.Snapshot() - ps := h.Percentiles([]float64{0.25, 0.5, 0.75}) - - thisStat["avg"] = h.Mean() - thisStat["min"] = h.Min() - thisStat["max"] = h.Max() - thisStat["p_25"] = ps[0] - thisStat["p_50"] = ps[1] - thisStat["p_75"] = ps[2] - - case metrics.Meter: - m := metric.Snapshot() - - thisStat["rate_1"] = m.Rate1() - thisStat["rate_5"] = m.Rate5() - thisStat["rate_15"] = m.Rate15() - thisStat["count"] = m.Count() - - case stats.String: - m := metric.Snapshot() - for t, v := range m.Get() { - thisStat[t] = v - } - } - }) - - response, err := json.Marshal(allStats) - if err != nil { - return errors.New(errors.Structural, err) - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(response) - return nil -} diff --git a/core/adapters/http/statuspage_test.go b/core/adapters/http/statuspage_test.go deleted file mode 100644 index ba94f956e..000000000 --- a/core/adapters/http/statuspage_test.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package http - -import ( - "net/http" - "testing" - - "github.com/TheThingsNetwork/ttn/utils/stats" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/smartystreets/assertions" -) - -func TestStatusPageURL(t *testing.T) { - a := assertions.New(t) - - s := StatusPage{} - - a.So(s.URL(), assertions.ShouldEqual, "/status") -} - -func TestStatusPageHandler(t *testing.T) { - a := assertions.New(t) - - s := StatusPage{} - - // Only GET allowed - r1, _ := http.NewRequest("POST", "/status", nil) - r1.RemoteAddr = "127.0.0.1:12345" - rw1 := NewResponseWriter() - s.Handle(&rw1, r1) - a.So(rw1.TheStatus, assertions.ShouldEqual, 405) - - // Initially Empty - r3, _ := http.NewRequest("GET", "/status", nil) - r3.RemoteAddr = "127.0.0.1:12345" - rw3 := NewResponseWriter() - s.Handle(&rw3, r3) - a.So(rw3.TheStatus, assertions.ShouldEqual, 200) - a.So(string(rw3.TheBody), assertions.ShouldEqual, "{}") - - // Create some stats - stats.IncCounter("this.is-a-counter") - stats.UpdateHistogram("and.this.is.a-histogram", 123) - stats.MarkMeter("and.this.is.a-meter") - stats.SetString("and.this.is.a-string", "with_key", "with_value") - - // Not Empty anymore - r4, _ := http.NewRequest("GET", "/status", nil) - r4.RemoteAddr = "127.0.0.1:12345" - rw4 := NewResponseWriter() - s.Handle(&rw4, r4) - a.So(rw4.TheStatus, assertions.ShouldEqual, 200) - a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "\"is-a-counter\"") - a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "p_50") - a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "rate_15") - a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "with_key") - a.So(string(rw4.TheBody), assertions.ShouldContainSubstring, "with_value") -} diff --git a/core/adapters/mqtt/adapter.go b/core/adapters/mqtt/adapter.go deleted file mode 100644 index 1b4782468..000000000 --- a/core/adapters/mqtt/adapter.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/apex/log" - "golang.org/x/net/context" -) - -const publishTimeout = 20 * time.Millisecond - -// Adapter represents the interface of an application -type Adapter interface { - PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error - PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) error - SubscribeDownlink(handler core.HandlerServer) error -} - -type defaultAdapter struct { - ctx log.Interface - client ttnMQTT.Client -} - -// NewAdapter returns a new MQTT handler adapter -func NewAdapter(ctx log.Interface, client ttnMQTT.Client) Adapter { - return &defaultAdapter{ctx, client} -} - -// HandleData implements the core.AppClient interface -func (a *defaultAdapter) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) error { - ctx := a.ctx.WithFields(log.Fields{ - "AppEUI": appEUI, - "DevEUI": devEUI, - }) - ctx.Debug("Publishing Uplink") - - token := a.client.PublishUplink(appEUI, devEUI, req) - if token.WaitTimeout(publishTimeout) { - // token did not timeout: just return - if token.Error() != nil { - return errors.New(errors.Structural, token.Error()) - } - } else { - // token did timeout: wait for it in background and just return - go func() { - token.Wait() - if token.Error() != nil { - ctx.WithError(token.Error()).Warn("Could not publish uplink") - } - }() - } - return nil -} - -func (a *defaultAdapter) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) error { - ctx := a.ctx.WithFields(log.Fields{ - "AppEUI": appEUI, - "DevEUI": devEUI, - }) - ctx.Debug("Publishing Activation") - - token := a.client.PublishActivation(appEUI, devEUI, req) - if token.WaitTimeout(publishTimeout) { - // token did not timeout: just return - if token.Error() != nil { - return errors.New(errors.Structural, token.Error()) - } - } else { - // token did timeout: wait for it in background and just return - go func() { - token.Wait() - if token.Error() != nil { - ctx.WithError(token.Error()).Warn("Could not publish activation") - } - }() - } - return nil -} - -func (a *defaultAdapter) SubscribeDownlink(handler core.HandlerServer) error { - token := a.client.SubscribeDownlink(func(client ttnMQTT.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { - if len(req.Payload) == 0 { - a.ctx.Debug("Skipping empty downlink") - return - } - - a.ctx.WithFields(log.Fields{ - "AppEUI": appEUI, - "DevEUI": devEUI, - }).Debug("Receiving Downlink") - - // Convert it to an handler downlink - handler.HandleDataDown(context.Background(), &core.DataDownHandlerReq{ - Payload: req.Payload, - TTL: req.TTL, - AppEUI: appEUI.Bytes(), - DevEUI: devEUI.Bytes(), - }) - }) - - token.Wait() - return token.Error() -} diff --git a/core/adapters/mqtt/adapter_test.go b/core/adapters/mqtt/adapter_test.go deleted file mode 100644 index c53369bab..000000000 --- a/core/adapters/mqtt/adapter_test.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import ( - "sync" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/core/types" - ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" - . "github.com/TheThingsNetwork/ttn/utils/testing" - . "github.com/smartystreets/assertions" -) - -func TestNewAdapter(t *testing.T) { - a := New(t) - ctx := GetLogger(t, "TestNewAdapter") - client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") - adapter := NewAdapter(ctx, client) - - a.So(adapter.(*defaultAdapter).client, ShouldEqual, client) -} - -func TestPublishUplink(t *testing.T) { - a := New(t) - ctx := GetLogger(t, "TestPublishUplink") - client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") - client.Connect() - - adapter := NewAdapter(ctx, client) - - appEUI := types.AppEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - req := core.DataUpAppReq{ - Payload: []byte{0x01, 0x02}, - Metadata: []core.AppMetadata{ - core.AppMetadata{DataRate: "SF7BW125"}, - }, - DevEUI: devEUI.String(), - FPort: 14, - FCnt: 200, - } - - var wg sync.WaitGroup - wg.Add(1) - - client.SubscribeDeviceUplink(appEUI, devEUI, func(client ttnMQTT.Client, rappEUI types.AppEUI, rdevEUI types.DevEUI, dataUp core.DataUpAppReq) { - a.So(rappEUI, ShouldEqual, appEUI) - a.So(rdevEUI, ShouldEqual, devEUI) - a.So(dataUp.FPort, ShouldEqual, 14) - a.So(dataUp.FCnt, ShouldEqual, 200) - a.So(dataUp.Payload, ShouldResemble, []byte{0x01, 0x02}) - a.So(dataUp.Metadata[0].DataRate, ShouldEqual, "SF7BW125") - wg.Done() - }).Wait() - - err := adapter.PublishUplink(appEUI, devEUI, req) - a.So(err, ShouldBeNil) - - wg.Wait() -} - -func TestHandleJoin(t *testing.T) { - a := New(t) - ctx := GetLogger(t, "TestHandleJoin") - client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") - client.Connect() - - adapter := NewAdapter(ctx, client) - - appEUI := types.AppEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x0a, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - req := core.OTAAAppReq{ - Metadata: []core.AppMetadata{ - core.AppMetadata{DataRate: "SF7BW125"}, - }, - } - - var wg sync.WaitGroup - wg.Add(1) - - client.SubscribeDeviceActivations(appEUI, devEUI, func(client ttnMQTT.Client, rappEUI types.AppEUI, rdevEUI types.DevEUI, activation core.OTAAAppReq) { - a.So(rappEUI, ShouldResemble, appEUI) - a.So(rdevEUI, ShouldResemble, devEUI) - a.So(activation.Metadata[0].DataRate, ShouldEqual, "SF7BW125") - wg.Done() - }).Wait() - - err := adapter.PublishActivation(appEUI, devEUI, req) - a.So(err, ShouldBeNil) - - wg.Wait() -} - -func TestSubscribeDownlink(t *testing.T) { - a := New(t) - ctx := GetLogger(t, "TestSubscribeDownlink") - client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") - client.Connect() - - adapter := NewAdapter(ctx, client) - handler := mocks.NewHandlerServer() - - err := adapter.SubscribeDownlink(handler) - a.So(err, ShouldBeNil) - - appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - - <-time.After(50 * time.Millisecond) - - expected := &core.DataDownHandlerReq{ - AppEUI: appEUI.Bytes(), - DevEUI: devEUI.Bytes(), - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - } - - a.So(handler.InHandleDataDown.Req, ShouldResemble, expected) -} - -func TestSubscribeInvalidDownlink(t *testing.T) { - a := New(t) - ctx := GetLogger(t, "TestSubscribeInvalidDownlink") - client := ttnMQTT.NewClient(ctx, "test", "", "", "tcp://localhost:1883") - client.Connect() - - adapter := NewAdapter(ctx, client) - handler := mocks.NewHandlerServer() - - err := adapter.SubscribeDownlink(handler) - a.So(err, ShouldBeNil) - - appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x09, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x09, 0x04, 0x03, 0x02, 0x01} - client.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{}}).Wait() - - <-time.After(50 * time.Millisecond) - - a.So(handler.InHandleDataDown.Req, ShouldBeNil) -} diff --git a/core/adapters/udp/connection.go b/core/adapters/udp/connection.go deleted file mode 100644 index 7fca8f633..000000000 --- a/core/adapters/udp/connection.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "fmt" - "net" - "sync" -) - -type replier interface { - DestinationID() []byte - WriteToUplink(data []byte) error - WriteToDownlink(data []byte) error -} - -type gatewayConn struct { - sync.RWMutex - gatewayID []byte - conn *net.UDPConn - uplinkAddr *net.UDPAddr - downlinkAddr *net.UDPAddr -} - -func (c *gatewayConn) SetConn(conn *net.UDPConn) { - c.Lock() - defer c.Unlock() - c.conn = conn -} - -func (c *gatewayConn) SetUplinkAddr(addr *net.UDPAddr) { - c.Lock() - defer c.Unlock() - c.uplinkAddr = addr -} - -func (c *gatewayConn) SetDownlinkAddr(addr *net.UDPAddr) { - c.Lock() - defer c.Unlock() - c.downlinkAddr = addr -} - -func (c *gatewayConn) DestinationID() []byte { - return c.gatewayID -} - -func (c *gatewayConn) WriteToUplink(data []byte) error { - c.RLock() - defer c.RUnlock() - if c.conn == nil || c.uplinkAddr == nil { - return fmt.Errorf("Uplink connection unavailable.") - } - - _, err := c.conn.WriteToUDP(data, c.uplinkAddr) - return err -} - -func (c *gatewayConn) WriteToDownlink(data []byte) error { - c.RLock() - defer c.RUnlock() - if c.conn == nil || c.downlinkAddr == nil { - return fmt.Errorf("Downlink connection unavailable.") - } - - _, err := c.conn.WriteToUDP(data, c.downlinkAddr) - return err -} - -type gatewayPool struct { - sync.Mutex - connections map[[8]byte]*gatewayConn -} - -func newPool() *gatewayPool { - return &gatewayPool{ - connections: make(map[[8]byte]*gatewayConn), - } -} - -func (p *gatewayPool) GetOrCreate(gatewayID []byte) *gatewayConn { - p.Lock() - defer p.Unlock() - var id [8]byte - copy(id[:], gatewayID) - if _, ok := p.connections[id]; !ok { - p.connections[id] = &gatewayConn{gatewayID: gatewayID} - } - return p.connections[id] -} diff --git a/core/adapters/udp/connection_test.go b/core/adapters/udp/connection_test.go deleted file mode 100644 index 0867b75cf..000000000 --- a/core/adapters/udp/connection_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "net" - "testing" - - . "github.com/smartystreets/assertions" -) - -func mkConn() *gatewayConn { - return &gatewayConn{} -} - -func TestSetConn(t *testing.T) { - a := New(t) - exp := &net.UDPConn{} - c := mkConn() - c.SetConn(exp) - a.So(c.conn, ShouldEqual, exp) -} - -func TestSetUplinkAddr(t *testing.T) { - a := New(t) - exp := &net.UDPAddr{} - c := mkConn() - c.SetUplinkAddr(exp) - a.So(c.uplinkAddr, ShouldEqual, exp) -} - -func TestSetDownlinkAddr(t *testing.T) { - a := New(t) - exp := &net.UDPAddr{} - c := mkConn() - c.SetDownlinkAddr(exp) - a.So(c.downlinkAddr, ShouldEqual, exp) -} diff --git a/core/adapters/udp/conversions.go b/core/adapters/udp/conversions.go deleted file mode 100644 index 975c2a64c..000000000 --- a/core/adapters/udp/conversions.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "encoding" - "encoding/base64" - "reflect" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/apex/log" - "github.com/brocaar/lorawan" -) - -func toLoRaWANPayload(rxpk semtech.RXPK, gid []byte, ctx log.Interface) (interface{}, error) { - // First, we have to get the physical payload which is encoded in the Data field - if rxpk.Data == nil { - return nil, errors.New(errors.Structural, "There's no data in the packet") - } - - // RXPK Data are base64 encoded - raw, err := base64.RawStdEncoding.DecodeString(*rxpk.Data) - if err != nil { - if raw, err = base64.StdEncoding.DecodeString(*rxpk.Data); err != nil { - return nil, errors.New(errors.Structural, err) - } - } - payload := &lorawan.PHYPayload{} - if err = payload.UnmarshalBinary(raw); err != nil { - return nil, errors.New(errors.Structural, err) - } - - // Switch over MType - switch payload.MHDR.MType { - case lorawan.ConfirmedDataUp: - fallthrough - case lorawan.UnconfirmedDataUp: - macpayload, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a MACPayload") - } - if len(macpayload.FRMPayload) != 1 { - // TODO Handle pure MAC Commands payloads (FType = 0) - return nil, errors.New(errors.Implementation, "Unhandled Physical payload. Expected a Data Payload") - } - frmpayload, err := macpayload.FRMPayload[0].MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - var fopts [][]byte - for _, cmd := range macpayload.FHDR.FOpts { - if data, err := cmd.MarshalBinary(); err == nil { // We just ignore invalid MAC Commands - fopts = append(fopts, data) - } - } - - // At the end, our converted packet hold the same metadata than the RXPK packet but the Data - // which as been completely transformed into a lorawan Physical Payload. - return &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, - }, - FOpts: fopts, - }, - FPort: uint32(*macpayload.FPort), - FRMPayload: frmpayload, - }, - MIC: payload.MIC[:], - }, - Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), - GatewayID: gid, - }, nil - case lorawan.JoinRequest: - joinpayload, ok := payload.MACPayload.(*lorawan.JoinRequestPayload) - if !ok { - return nil, errors.New(errors.Structural, "Unhandled Physical payload. Expected a JoinRequest Payload") - } - return &core.JoinRouterReq{ - GatewayID: gid, - AppEUI: joinpayload.AppEUI[:], - DevEUI: joinpayload.DevEUI[:], - DevNonce: joinpayload.DevNonce[:], - MIC: payload.MIC[:], - Metadata: extractMetadata(rxpk, new(core.Metadata)).(*core.Metadata), - }, nil - default: - return nil, errors.New(errors.Structural, "Unexpected and unhandled LoRaWAN MHDR Mtype") - } -} - -func newTXPK(payload encoding.BinaryMarshaler, metadata *core.Metadata, ctx log.Interface) (semtech.TXPK, error) { - // Step 0: validate the response - if metadata == nil { - return semtech.TXPK{}, errors.New(errors.Structural, "Missing mandatory Metadata") - } - - // Step2: Convert the physical payload to a base64 string (without the padding) - raw, err := payload.MarshalBinary() - if err != nil { - return semtech.TXPK{}, errors.New(errors.Structural, err) - } - data := base64.RawStdEncoding.EncodeToString(raw) - txpk := semtech.TXPK{Data: pointer.String(data)} - - // Step 3, copy every compatible metadata from the packet to the TXPK packet. - // We are possibly loosing information here. - injectMetadata(&txpk, *metadata) - - // Step 4, set required Rfch to its default value. // See issue # - if txpk.Rfch == nil { - txpk.Rfch = pointer.Uint32(0) - } - - return txpk, nil -} - -// injectMetadata takes metadata from a Struct and inject them into an xpk Struct (rxpk or txpk). -// The xpk is expected to be a pointer to an existing struct. Struct-tag in the rxpk struct are used -// to indicate with field from the struct is bound to which field on the xpk. -// -// All fields in the src struct are expected to be non-pointer values. -// -// It eventually returns the initial xpk argument -func injectMetadata(xpk interface{}, src interface{}) interface{} { - m := reflect.ValueOf(src) - x := reflect.ValueOf(xpk).Elem() - tx := x.Type() - - for i := 0; i < tx.NumField(); i++ { - t := tx.Field(i).Tag.Get("full") - f := m.FieldByName(t) - if f.IsValid() && f.Interface() != reflect.Zero(f.Type()).Interface() { - p := reflect.New(f.Type()) - p.Elem().Set(f) - if p.Type().AssignableTo(x.Field(i).Type()) { - x.Field(i).Set(p) - } - } - } - - return xpk -} - -// extractMetadata does the reverse operation than injectMetadata. It takes metadata from an xpk -// structure and inject all compatible field into a given Struct. -// This time, the xpk is expected to be a plain xpk struct (not a pointer) and the target struct -// should be a reference to that struct. -// -// It eventually returns the completed target element. -func extractMetadata(xpk interface{}, target interface{}) interface{} { - x := reflect.ValueOf(xpk) - tx := x.Type() - m := reflect.ValueOf(target).Elem() - - for i := 0; i < tx.NumField(); i++ { - t := tx.Field(i).Tag.Get("full") - f := m.FieldByName(t) - if f.IsValid() && !x.Field(i).IsNil() { - e := x.Field(i).Elem() - if e.Type().AssignableTo(m.FieldByName(t).Type()) { - m.FieldByName(t).Set(e) - } else if e.Type().AssignableTo(reflect.TypeOf(time.Time{})) { - m.FieldByName(t).Set(reflect.ValueOf(e.Interface().(time.Time).Format(time.RFC3339Nano))) - } - } - } - - return target -} diff --git a/core/adapters/udp/conversions_test.go b/core/adapters/udp/conversions_test.go deleted file mode 100644 index 944082211..000000000 --- a/core/adapters/udp/conversions_test.go +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "encoding/base64" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestInjectMetadata(t *testing.T) { - { - Desc(t, "Inject full set of Metadata") - - // Build - rxpk := new(semtech.RXPK) - meta := core.Metadata{ - Altitude: -12, - CodingRate: "4/5", - DataRate: "SF7BW125", - DutyRX1: 12, - DutyRX2: 0, - Frequency: 868.34, - Latitude: 12.67, - Longitude: 33.45, - Lsnr: 5.2, - PayloadSize: 14, - Rssi: -12, - Timestamp: 123455, - } - - // Expectation - var want = &semtech.RXPK{ - Codr: pointer.String(meta.CodingRate), - Datr: pointer.String(meta.DataRate), - Freq: pointer.Float32(meta.Frequency), - Lsnr: pointer.Float32(meta.Lsnr), - Size: pointer.Uint32(meta.PayloadSize), - Rssi: pointer.Int32(meta.Rssi), - Tmst: pointer.Uint32(meta.Timestamp), - } - - // Operate - _ = injectMetadata(rxpk, meta) - - // Check - Check(t, want, rxpk, "RXPKs") - } - - // -------------------- - - { - Desc(t, "Partial Set of Metadata") - - // Build - rxpk := new(semtech.RXPK) - meta := core.Metadata{ - Altitude: -12, - CodingRate: "4/5", - Rssi: -12, - Timestamp: 123455, - } - - // Expectation - var want = &semtech.RXPK{ - Codr: pointer.String(meta.CodingRate), - Rssi: pointer.Int32(meta.Rssi), - Tmst: pointer.Uint32(meta.Timestamp), - } - - // Operate - _ = injectMetadata(rxpk, meta) - - // Check - Check(t, want, rxpk, "RXPKs") - } -} - -func TestExtractMetadta(t *testing.T) { - { - Desc(t, "Extract full set of Metadata") - - // Build - txpk := semtech.TXPK{ - Codr: pointer.String("4/5"), - Data: pointer.String("4xvansicvni7bvcxxcvxds=="), - Datr: pointer.String("SF7BW125"), - Fdev: pointer.Uint32(42), - Freq: pointer.Float32(868.45), - Imme: pointer.Bool(true), - Ipol: pointer.Bool(false), - Modu: pointer.String("Lora"), - Ncrc: pointer.Bool(false), - Powe: pointer.Uint32(12000), - Prea: pointer.Uint32(1000), - Rfch: pointer.Uint32(3), - Size: pointer.Uint32(14), - Time: pointer.Time(time.Now()), - Tmst: pointer.Uint32(23456789), - } - meta := new(core.AppMetadata) - - // Expectation - var want = &core.AppMetadata{ - CodingRate: *txpk.Codr, - DataRate: *txpk.Datr, - Frequency: *txpk.Freq, - Timestamp: *txpk.Tmst, - Modulation: *txpk.Modu, - RFChain: *txpk.Rfch, - Time: txpk.Time.Format(time.RFC3339Nano), - } - - // Operate - _ = extractMetadata(txpk, meta) - - // Check - Check(t, want, meta, "Metadata") - } - - // -------------------- - - { - Desc(t, "Extract partial set of Metadata") - - // Build - txpk := semtech.TXPK{ - Codr: pointer.String("4/5"), - Datr: pointer.String("SF7BW125"), - Fdev: pointer.Uint32(42), - Imme: pointer.Bool(true), - Ipol: pointer.Bool(false), - Ncrc: pointer.Bool(false), - Powe: pointer.Uint32(12000), - Prea: pointer.Uint32(1000), - Size: pointer.Uint32(14), - } - meta := new(core.AppMetadata) - - // Expectation - var want = &core.AppMetadata{ - CodingRate: *txpk.Codr, - DataRate: *txpk.Datr, - } - - // Operate - _ = extractMetadata(txpk, meta) - - // Check - Check(t, want, meta, "Metadata") - } - -} - -// func newTXPK(resp core.DataRouterRes, ctx log.Interface) (semtech.TXPK, error) { -func TestNewTXPKAndtoLoRaWANPayloadReq(t *testing.T) { - { - Desc(t, "Test Valid marshal / unmarshal") - - // Build - res := core.DataRouterRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: &core.LoRaWANFCtrl{ - ADR: false, - ADRAckReq: false, - Ack: false, - FPending: false, - FOptsLen: nil, - }, - FOpts: nil, - }, - FPort: 1, - FRMPayload: []byte{5, 6, 7, 8}, - }, - MIC: []byte{14, 42, 14, 42}, - }, - Metadata: &core.Metadata{ - CodingRate: "4/5", - DataRate: "SF8BW125", - }, - } - payload, err := core.NewLoRaWANData(res.Payload, false) - FatalUnless(t, err) - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - - // Expectations - var wantReq = &core.DataRouterReq{ - Payload: res.Payload, - Metadata: res.Metadata, - GatewayID: gid, - } - var wantErrTXPK *string - var wantErrReq *string - - // Operate - txpk, errTXPK := newTXPK(payload, res.Metadata, GetLogger(t, "Convert TXPK")) - req, errReq := toLoRaWANPayload(semtech.RXPK{ - Codr: txpk.Codr, - Datr: txpk.Datr, - Data: txpk.Data, - }, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErrTXPK, errTXPK) - CheckErrors(t, wantErrReq, errReq) - Check(t, wantReq, req, "Data Router Requests") - } - - // -------------------- - - { - Desc(t, "New TXPK with no Metadata") - - // Build - res := core.DataRouterRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: &core.LoRaWANFCtrl{ - ADR: false, - ADRAckReq: false, - Ack: false, - FPending: false, - FOptsLen: nil, - }, - FOpts: nil, - }, - FPort: 1, - FRMPayload: []byte{5, 6, 7, 8}, - }, - MIC: []byte{14, 42, 14, 42}, - }, - Metadata: nil, - } - payload, err := core.NewLoRaWANData(res.Payload, false) - FatalUnless(t, err) - - // Expectations - var wantTXPK semtech.TXPK - var wantErr = ErrStructural - - // Operate - txpk, errTXPK := newTXPK(payload, res.Metadata, GetLogger(t, "Convert TXPK")) - - // Check - CheckErrors(t, wantErr, errTXPK) - Check(t, wantTXPK, txpk, "TXPKs") - } - - // -------------------- - - { - Desc(t, "Test toLoRaWANPayload with no data in rxpk") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - } - - // Expectations - var wantReq interface{} - var wantErr = ErrStructural - - // Operate - req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantReq, req, "Data Router Requests") - } - - // -------------------- - - { - Desc(t, "Test toLoRaWANPayload with random encoded data in rxpk") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - Data: pointer.String("$#%$^ffg"), - } - - // Expectations - var wantReq interface{} - var wantErr = ErrStructural - - // Operate - req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantReq, req, "Data Router Requests") - } - - // -------------------- - - { - Desc(t, "Test toLoRaWANPayload with not base64 data") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - Data: pointer.String("Patate"), - } - - // Expectations - var wantReq interface{} - var wantErr = ErrStructural - - // Operate - req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantReq, req, "Data Router Requests") - } - - // -------------------- - - { - Desc(t, "Test toLoRaWANPayload with mac commands in fopts") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.ConfirmedDataUp - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := &lorawan.MACPayload{} - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - - macpayload.FHDR.FOpts = []lorawan.MACCommand{ - lorawan.MACCommand{ - CID: lorawan.DutyCycleReq, - Payload: &lorawan.DutyCycleReqPayload{ - MaxDCCycle: 14, - }, - }, - } - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} - payload.MACPayload = macpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - } - - // Expectations - var wantErr *string - - // Operate - _, err = toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - } - - // -------------------- - - { - Desc(t, "Test toLoRaWANPayload with unhandled mtype") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.UnconfirmedDataDown - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := &lorawan.MACPayload{} - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} - payload.MACPayload = macpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - } - - // Expectations - var wantErr = ErrStructural - - // Operate - _, err = toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - } - - // -------------------- - - { - Desc(t, "Test toLoRaWANPayload with join-request mtype") - - // Build - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.JoinRequest - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - joinpayload := &lorawan.JoinRequestPayload{ - AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevNonce: [2]byte{1, 2}, - } - payload.MACPayload = joinpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - rxpk := semtech.RXPK{ - Codr: pointer.String("4/5"), - Freq: pointer.Float32(867.345), - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - } - - // Expectations - var wantErr *string - var wantReq = &core.JoinRouterReq{ - GatewayID: gid, - AppEUI: joinpayload.AppEUI[:], - DevEUI: joinpayload.DevEUI[:], - DevNonce: joinpayload.DevNonce[:], - MIC: payload.MIC[:], - Metadata: &core.Metadata{ - CodingRate: *rxpk.Codr, - Frequency: *rxpk.Freq, - }, - } - - // Operate - req, err := toLoRaWANPayload(rxpk, gid, GetLogger(t, "Convert DataRouterReq")) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantReq, req, "Join Router Requests") - } -} diff --git a/core/adapters/udp/udp.go b/core/adapters/udp/udp.go deleted file mode 100644 index af1b82260..000000000 --- a/core/adapters/udp/udp.go +++ /dev/null @@ -1,289 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "fmt" - "net" - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" - "golang.org/x/net/context" -) - -type adapter struct { - Components - pool *gatewayPool -} - -// Components defines a structure to make the instantiation easier to read -type Components struct { - Ctx log.Interface - Router core.RouterServer -} - -// Options defines a structure to make the instantiation easier to read -type Options struct { - // NetAddr refers to the udp address + port the adapter will have to listen - NetAddr string - // MaxReconnectionDelay defines the delay of the longest attempt to reconnect a lost connection - // before giving up. - MaxReconnectionDelay time.Duration -} - -// straightMarshaler can be used to easily obtain a binary marshaler from a sequence of byte -type straightMarshaler []byte - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (s straightMarshaler) MarshalBinary() ([]byte, error) { - return s, nil -} - -// Start constructs and launches a new udp adapter -func Start(c Components, o Options) (err error) { - // Create the udp connection and start listening with a goroutine - var udpConn *net.UDPConn - if udpConn, err = tryConnect(o.NetAddr); err != nil { - c.Ctx.WithError(err).Error("Unable to start server") - return errors.New(errors.Operational, fmt.Sprintf("Invalid bind address %v", o.NetAddr)) - } - - c.Ctx.WithField("bind", o.NetAddr).Info("Starting Server") - a := adapter{Components: c, pool: newPool()} - go a.listen(o.NetAddr, udpConn, o.MaxReconnectionDelay) - return nil -} - -// tryConnect attempt to connect to a udp connection -func tryConnect(netAddr string) (*net.UDPConn, error) { - addr, err := net.ResolveUDPAddr("udp", netAddr) - if err != nil { - return nil, err - } - return net.ListenUDP("udp", addr) -} - -// listen Handle incoming packets and forward them. -// -// Runs in its own goroutine. -func (a adapter) listen(netAddr string, conn *net.UDPConn, maxReconnectionDelay time.Duration) { - var err error - a.Ctx.WithField("address", conn.LocalAddr()).Debug("Starting accept loop") - for { - // Read on the UDP connection - buf := make([]byte, 5000) - n, addr, fault := conn.ReadFromUDP(buf) - if fault != nil { // Problem with the connection - for delay := time.Millisecond * 25; delay < maxReconnectionDelay; delay *= 10 { - a.Ctx.Infof("UDP connection lost. Trying to reconnect in %s", delay) - <-time.After(delay) - conn, err = tryConnect(netAddr) - if err == nil { - a.Ctx.Info("UDP connection recovered") - break - } - } - a.Ctx.WithError(fault).Error("Unable to restore UDP connection") - break - } - - ctx := a.Ctx.WithField("Source", addr.IP.String()) - - // Handle the incoming datagram - go func(data []byte, conn *net.UDPConn) { - pkt := new(semtech.Packet) - if err := pkt.UnmarshalBinary(data); err != nil { - ctx.WithError(err).Debug("Invalid datagram") - } - - gtwConn := a.pool.GetOrCreate(pkt.GatewayId) - gtwConn.SetConn(conn) - - switch pkt.Identifier { - case semtech.PULL_DATA: - gtwConn.SetDownlinkAddr(addr) - err = a.handlePullData(*pkt, gtwConn) - case semtech.PUSH_DATA: - gtwConn.SetUplinkAddr(addr) - err = a.handlePushData(*pkt, gtwConn) - default: - err = errors.New(errors.Implementation, "Unhandled packet type") - } - if err != nil { - ctx.WithError(err).Debug("Unable to handle datagram") - } - }(buf[:n], conn) - } - - if conn != nil { - _ = conn.Close() - } -} - -// Handle a PULL_DATA packet coming from a gateway -func (a adapter) handlePullData(pkt semtech.Packet, reply replier) error { - stats.MarkMeter("semtech_adapter.pull_data") - stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.pull_data", pkt.GatewayId)) - stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_pull_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) - ctx := a.Ctx.WithField("GatewayID", pkt.GatewayId) - ctx.Debug("Handle PULL_DATA") - - data, err := semtech.Packet{ - Version: semtech.VERSION, - Token: pkt.Token, - Identifier: semtech.PULL_ACK, - }.MarshalBinary() - - if err != nil || reply.WriteToDownlink(data) != nil { - ctx.Debug("Unable to send PULL_ACK") - return errors.New(errors.Operational, "Unable to send PULL_ACK") - } - - return nil -} - -// Handle a PUSH_DATA packet coming from a gateway -func (a adapter) handlePushData(pkt semtech.Packet, reply replier) error { - stats.MarkMeter("semtech_adapter.push_data") - stats.MarkMeter(fmt.Sprintf("semtech_adapter.gateways.%X.push_data", pkt.GatewayId)) - stats.SetString(fmt.Sprintf("semtech_adapter.gateways.%X.last_push_data", pkt.GatewayId), "date", time.Now().UTC().Format(time.RFC3339)) - ctx := a.Ctx.WithField("GatewayID", pkt.GatewayId) - ctx.Debug("Handle PUSH_DATA") - - // AckNowledge with a PUSH_ACK - data, err := semtech.Packet{ - Version: semtech.VERSION, - Token: pkt.Token, - Identifier: semtech.PUSH_ACK, - }.MarshalBinary() - - if err != nil || reply.WriteToUplink(data) != nil || pkt.Payload == nil { - ctx.Debug("Unable to send PUSH_ACK") - return errors.New(errors.Operational, "Unable to send PUSH_ACK") - } - - // Process Stat payload - if pkt.Payload.Stat != nil { - ctx.Debug("Handle stat") - go a.Router.HandleStats(context.Background(), &core.StatsReq{ - GatewayID: pkt.GatewayId, - Metadata: extractMetadata(*pkt.Payload.Stat, new(core.StatsMetadata)).(*core.StatsMetadata), - }) - } - - // Process rxpks payloads - wait := sync.WaitGroup{} - - if len(pkt.Payload.RXPK) > 0 { - wait.Add(len(pkt.Payload.RXPK)) - ctx.Debug("Handle rxpk") - for _, rxpk := range pkt.Payload.RXPK { - go func(rxpk semtech.RXPK) { - defer wait.Done() - if err := a.handleDataUp(rxpk, pkt.GatewayId, reply); err != nil { - ctx.WithError(err).Debug("rxpk not processed") - } - }(rxpk) - } - } - - // Retrieve any errors - wait.Wait() - return nil -} - -func (a adapter) handleDataUp(rxpk semtech.RXPK, gid []byte, reply replier) error { - ctx := a.Ctx.WithField("GatewayID", gid) - - itf, err := toLoRaWANPayload(rxpk, gid, ctx) - if err != nil { - ctx.WithError(err).Debug("Invalid up RXPK packet") - return errors.New(errors.Structural, err) - } - - switch itf.(type) { - case *core.DataRouterReq: - ctx.Debug("Handle uplink data") - resp, err := a.Router.HandleData(context.Background(), itf.(*core.DataRouterReq)) - if err != nil { - return errors.New(errors.Operational, err) - } - return a.handleDataDown(resp, reply) - case *core.JoinRouterReq: - ctx.Debug("Handle join request") - resp, err := a.Router.HandleJoin(context.Background(), itf.(*core.JoinRouterReq)) - if err != nil { - return errors.New(errors.Operational, err) - } - return a.handleJoinAccept(resp, reply) - default: - ctx.Warn("Unhandled LoRaWAN Payload type") - return errors.New(errors.Implementation, "Unhandled LoRaWAN Payload type") - } -} - -func (a adapter) handleDataDown(resp *core.DataRouterRes, reply replier) error { - ctx := a.Ctx.WithField("GatewayID", reply.DestinationID()) - - if resp == nil || resp.Payload == nil { // No response - ctx.Debug("No response to send") - return nil - } - - payload, err := core.NewLoRaWANData(resp.Payload, false) - if err != nil { - return errors.New(errors.Structural, err) - } - - txpk, err := newTXPK(payload, resp.Metadata, ctx) - if err != nil { - return errors.New(errors.Structural, err) - } - - ctx.WithFields(log.Fields{ - "Metadata": resp.Metadata, - "DevAddr": resp.Payload.MACPayload.FHDR.DevAddr, - }).Debug("Send txpk") - - data, err := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{TXPK: &txpk}, - }.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - - return reply.WriteToDownlink(data) -} - -func (a adapter) handleJoinAccept(resp *core.JoinRouterRes, reply replier) error { - ctx := a.Ctx.WithField("GatewayID", reply.DestinationID()) - - if resp == nil || resp.Payload == nil { - return errors.New(errors.Structural, "Invalid Join-Accept response. Expected a payload") - } - - txpk, err := newTXPK(straightMarshaler(resp.Payload.Payload), resp.Metadata, a.Ctx) - if err != nil { - return errors.New(errors.Structural, err) - } - - ctx.WithField("Metadata", resp.Metadata).Debug("Send join-accept") - - data, err := semtech.Packet{ - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{TXPK: &txpk}, - }.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - return reply.WriteToDownlink(data) -} diff --git a/core/adapters/udp/udp_test.go b/core/adapters/udp/udp_test.go deleted file mode 100644 index 62ff97254..000000000 --- a/core/adapters/udp/udp_test.go +++ /dev/null @@ -1,1297 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package udp - -import ( - "encoding/base64" - "fmt" - "net" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func listenPackets(conn *net.UDPConn) chan semtech.Packet { - chresp := make(chan semtech.Packet, 2) - go func() { - for { - buf := make([]byte, 5000) - n, err := conn.Read(buf) - if err == nil { - pkt := new(semtech.Packet) - if err := pkt.UnmarshalBinary(buf[:n]); err == nil { - chresp <- *pkt - } - } - } - }() - return chresp -} - -func TestUDPAdapter(t *testing.T) { - port := 10000 - newAddr := func() string { - port++ - return fmt.Sprintf("0.0.0.0:%d", port) - } - - // ------------------- - - { - Desc(t, "Send a PULL_DATA through udp") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PULL_DATA, - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PULL_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - { - Desc(t, "Send a valid packet through udp, no downlink") - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.UnconfirmedDataUp - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := &lorawan.MACPayload{} - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} - payload.MACPayload = macpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq = &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, - FOptsLen: nil, - }, - FOpts: nil, - }, - FPort: uint32(*macpayload.FPort), - FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, - }, - MIC: payload.MIC[:], - }, - Metadata: new(core.Metadata), - GatewayID: packet.GatewayId, - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid packet through udp, with valid downlink") - - // Build pull packet - pullPacket := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PULL_DATA, - } - pullData, err := pullPacket.MarshalBinary() - FatalUnless(t, err) - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.UnconfirmedDataUp - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := &lorawan.MACPayload{} - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} - payload.MACPayload = macpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - router.OutHandleData.Res = &core.DataRouterRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 2, - FCtrl: new(core.LoRaWANFCtrl), - FOpts: nil, - }, - FPort: 2, - FRMPayload: []byte{1, 2, 3, 4}, - }, - MIC: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - - payloadDown := &lorawan.PHYPayload{} - payloadDown.MHDR.MType = lorawan.MType(router.OutHandleData.Res.Payload.MHDR.MType) - payloadDown.MHDR.Major = lorawan.Major(router.OutHandleData.Res.Payload.MHDR.Major) - copy(payloadDown.MIC[:], router.OutHandleData.Res.Payload.MIC) - macpayloadDown := &lorawan.MACPayload{} - macpayloadDown.FPort = new(uint8) - *macpayloadDown.FPort = uint8(router.OutHandleData.Res.Payload.MACPayload.FPort) - macpayloadDown.FHDR.FCnt = router.OutHandleData.Res.Payload.MACPayload.FHDR.FCnt - copy(macpayloadDown.FHDR.DevAddr[:], router.OutHandleData.Res.Payload.MACPayload.FHDR.DevAddr) - macpayloadDown.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: router.OutHandleData.Res.Payload.MACPayload.FRMPayload, - }} - payloadDown.MACPayload = macpayloadDown - dataDown, err := payloadDown.MarshalBinary() - FatalUnless(t, err) - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn1, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - conn2, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq = &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, - FOptsLen: nil, - }, - FOpts: nil, - }, - FPort: uint32(*macpayload.FPort), - FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, - }, - MIC: payload.MIC[:], - }, - Metadata: new(core.Metadata), - GatewayID: packet.GatewayId, - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - { - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{ - TXPK: &semtech.TXPK{ - Data: pointer.String(base64.RawStdEncoding.EncodeToString(dataDown)), - Rfch: pointer.Uint32(0), - }, - }, - }, - } - - // Operate - chpkt1 := listenPackets(conn1) - chpkt2 := listenPackets(conn2) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - - <-time.After(time.Millisecond * 50) - _, err = conn1.Write(pullData) - <-time.After(time.Millisecond * 50) - _, err = conn2.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt1) - close(chpkt2) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt2, "Acknowledgements") - <-chpkt1 - Check(t, wantSemtechResp[1], <-chpkt1, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid packet through udp, with invalid downlink") - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.UnconfirmedDataUp - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := &lorawan.MACPayload{} - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} - payload.MACPayload = macpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - router.OutHandleData.Res = &core.DataRouterRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: nil, - MIC: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq = &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, - FOptsLen: nil, - }, - FOpts: nil, - }, - FPort: uint32(*macpayload.FPort), - FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, - }, - MIC: payload.MIC[:], - }, - Metadata: new(core.Metadata), - GatewayID: packet.GatewayId, - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a packet through udp, no payload") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: nil, - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a packet through udp, empty payload") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: new(semtech.Payload), - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid packet through udp, no downlink, router fails") - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.UnconfirmedDataUp - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - macpayload := &lorawan.MACPayload{} - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte{1, 2, 3}}} - payload.MACPayload = macpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - router.Failures["HandleData"] = fmt.Errorf("Mock Error") - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq = &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, - FOptsLen: nil, - }, - FOpts: nil, - }, - FPort: uint32(*macpayload.FPort), - FRMPayload: macpayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, - }, - MIC: payload.MIC[:], - }, - Metadata: new(core.Metadata), - GatewayID: packet.GatewayId, - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid packet through udp with stats, no downlink") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - Stat: &semtech.Stat{ - Alti: pointer.Int32(14), - Long: pointer.Float32(44.7654), - Lati: pointer.Float32(23.56), - }, - }, - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - router.Failures["HandleData"] = fmt.Errorf("Mock Error") - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats = &core.StatsReq{ - GatewayID: packet.GatewayId, - Metadata: &core.StatsMetadata{ - Altitude: *packet.Payload.Stat.Alti, - Longitude: *packet.Payload.Stat.Long, - Latitude: *packet.Payload.Stat.Lati, - }, - } - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a packet through udp, no data in rxpk") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Codr: pointer.String("4/6"), - Datr: pointer.String("SF8BW125"), - }, - }, - }, - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // ------------------- - - { - Desc(t, "Invalid options NetAddr") - - // Build - router := mocks.NewRouterServer() - - // Expectations - var wantErrStart = ErrOperational - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - - // Operate - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: "patate", MaxReconnectionDelay: 25 * time.Millisecond}, - ) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - } - - // ------------------- - - { - Desc(t, "Send an invalid semtech as uplink") - - // Build - packet := semtech.Packet{ - Version: semtech.VERSION, - Token: []byte{1, 2}, - Identifier: semtech.PULL_ACK, - } - data, err := packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantDataRouterReq *core.DataRouterReq - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - {}, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantDataRouterReq, router.InHandleData.Req, "Data Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid join through udp, with valid join-accept") - - // Build pull packet - pullPacket := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PULL_DATA, - } - pullData, err := pullPacket.MarshalBinary() - FatalUnless(t, err) - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.JoinRequest - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - joinpayload := &lorawan.JoinRequestPayload{ - AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevNonce: [2]byte{14, 42}, - } - payload.MACPayload = joinpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - dataDown := []byte("accept") - router := mocks.NewRouterServer() - router.OutHandleJoin.Res = &core.JoinRouterRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: dataDown, - }, - Metadata: new(core.Metadata), - } - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn1, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - conn2, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantJoinRouterReq = &core.JoinRouterReq{ - GatewayID: packet.GatewayId, - DevEUI: joinpayload.DevEUI[:], - AppEUI: joinpayload.AppEUI[:], - DevNonce: joinpayload.DevNonce[:], - MIC: payload.MIC[:], - Metadata: new(core.Metadata), - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - { - Version: semtech.VERSION, - Identifier: semtech.PULL_RESP, - Payload: &semtech.Payload{ - TXPK: &semtech.TXPK{ - Data: pointer.String(base64.RawStdEncoding.EncodeToString(dataDown)), - Rfch: pointer.Uint32(0), - }, - }, - }, - } - - // Operate - chpkt1 := listenPackets(conn1) - chpkt2 := listenPackets(conn2) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn1.Write(pullData) - <-time.After(time.Millisecond * 50) - _, err = conn2.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt1) - close(chpkt2) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - <-chpkt1 // get the PULL_RESP - Check(t, wantSemtechResp[0], <-chpkt2, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt1, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid join through udp, no payload in accept") - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.JoinRequest - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - joinpayload := &lorawan.JoinRequestPayload{ - AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevNonce: [2]byte{14, 42}, - } - payload.MACPayload = joinpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - router.OutHandleJoin.Res = &core.JoinRouterRes{ - Payload: nil, - Metadata: new(core.Metadata), - } - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantJoinRouterReq = &core.JoinRouterReq{ - GatewayID: packet.GatewayId, - DevEUI: joinpayload.DevEUI[:], - AppEUI: joinpayload.AppEUI[:], - DevNonce: joinpayload.DevNonce[:], - MIC: payload.MIC[:], - Metadata: new(core.Metadata), - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid join through udp, no metadata in response") - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.JoinRequest - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - joinpayload := &lorawan.JoinRequestPayload{ - AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevNonce: [2]byte{14, 42}, - } - payload.MACPayload = joinpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - dataDown := []byte("accept") - router := mocks.NewRouterServer() - router.OutHandleJoin.Res = &core.JoinRouterRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: dataDown, - }, - Metadata: nil, - } - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantJoinRouterReq = &core.JoinRouterReq{ - GatewayID: packet.GatewayId, - DevEUI: joinpayload.DevEUI[:], - AppEUI: joinpayload.AppEUI[:], - DevNonce: joinpayload.DevNonce[:], - MIC: payload.MIC[:], - Metadata: new(core.Metadata), - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } - - // -------------------- - - { - Desc(t, "Send a valid join through udp, router fails to handle") - - // Build - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.JoinRequest - payload.MHDR.Major = lorawan.LoRaWANR1 - payload.MIC = [4]byte{1, 2, 3, 4} - joinpayload := &lorawan.JoinRequestPayload{ - AppEUI: [8]byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: [8]byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevNonce: [2]byte{14, 42}, - } - payload.MACPayload = joinpayload - data, err := payload.MarshalBinary() - FatalUnless(t, err) - - packet := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PUSH_DATA, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Data: pointer.String(base64.RawStdEncoding.EncodeToString(data)), - }, - }, - }, - } - data, err = packet.MarshalBinary() - FatalUnless(t, err) - - router := mocks.NewRouterServer() - router.Failures["HandleJoin"] = fmt.Errorf("Mock Error") - - netAddr := newAddr() - addr, err := net.ResolveUDPAddr("udp", netAddr) - FatalUnless(t, err) - conn, err := net.DialUDP("udp", nil, addr) - FatalUnless(t, err) - - // Expectations - var wantErrStart *string - var wantJoinRouterReq = &core.JoinRouterReq{ - GatewayID: packet.GatewayId, - DevEUI: joinpayload.DevEUI[:], - AppEUI: joinpayload.AppEUI[:], - DevNonce: joinpayload.DevNonce[:], - MIC: payload.MIC[:], - Metadata: new(core.Metadata), - } - var wantStats *core.StatsReq - var wantSemtechResp = []semtech.Packet{ - { - Version: semtech.VERSION, - Token: packet.Token, - Identifier: semtech.PUSH_ACK, - }, - {}, - } - - // Operate - chpkt := listenPackets(conn) - errStart := Start( - Components{Router: router, Ctx: GetLogger(t, "Adapter")}, - Options{NetAddr: netAddr, MaxReconnectionDelay: 25 * time.Millisecond}, - ) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - _, err = conn.Write(data) - FatalUnless(t, err) - <-time.After(time.Millisecond * 50) - close(chpkt) - - // Check - CheckErrors(t, wantErrStart, errStart) - Check(t, wantJoinRouterReq, router.InHandleJoin.Req, "Join Router Requests") - Check(t, wantStats, router.InHandleStats.Req, "Data Router Stats") - Check(t, wantSemtechResp[0], <-chpkt, "Acknowledgements") - Check(t, wantSemtechResp[1], <-chpkt, "Downlinks") - } -} diff --git a/core/application.pb.go b/core/application.pb.go deleted file mode 100644 index a4daa8500..000000000 --- a/core/application.pb.go +++ /dev/null @@ -1,1094 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: application.proto -// DO NOT EDIT! - -/* - Package core is a generated protocol buffer package. - - It is generated from these files: - application.proto - broker.proto - broker_manager.proto - collector.proto - core.proto - handler.proto - handler_manager.proto - lorawan.proto - router.proto - - It has these top-level messages: - DataAppReq - DataAppRes - JoinAppReq - JoinAppRes - DataBrokerReq - DataBrokerRes - JoinBrokerReq - JoinBrokerRes - ValidateOTAABrokerReq - ValidateOTAABrokerRes - UpsertABPBrokerReq - UpsertABPBrokerRes - BrokerDevice - ValidateTokenBrokerReq - ValidateTokenBrokerRes - GetApplicationsCollectorReq - GetApplicationsCollectorRes - CollectorApplication - AddApplicationCollectorReq - AddApplicationCollectorRes - RemoveApplicationCollectorReq - RemoveApplicationCollectorRes - Metadata - StatsMetadata - DataUpHandlerReq - DataUpHandlerRes - DataDownHandlerReq - DataDownHandlerRes - JoinHandlerReq - JoinHandlerRes - UpsertOTAAHandlerReq - UpsertOTAAHandlerRes - UpsertABPHandlerReq - UpsertABPHandlerRes - ListDevicesHandlerReq - ListDevicesHandlerRes - HandlerABPDevice - HandlerOTAADevice - GetDefaultDeviceReq - GetDefaultDeviceRes - SetDefaultDeviceReq - GetPayloadFunctionsReq - GetPayloadFunctionsRes - SetPayloadFunctionsReq - SetPayloadFunctionsRes - TestPayloadFunctionsReq - TestPayloadFunctionsRes - SetDefaultDeviceRes - LoRaWANData - LoRaWANMHDR - LoRaWANMACPayload - LoRaWANFHDR - LoRaWANFCtrl - LoRaWANJoinRequest - LoRaWANJoinAccept - LoRaWANDLSettings - DataRouterReq - DataRouterRes - StatsReq - StatsRes - JoinRouterReq - JoinRouterRes -*/ -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - -type DataAppReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - FPort uint32 `protobuf:"varint,10,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` - FCnt uint32 `protobuf:"varint,11,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` - Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - Metadata []*Metadata `protobuf:"bytes,30,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataAppReq) Reset() { *m = DataAppReq{} } -func (m *DataAppReq) String() string { return proto.CompactTextString(m) } -func (*DataAppReq) ProtoMessage() {} -func (*DataAppReq) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{0} } - -func (m *DataAppReq) GetMetadata() []*Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type DataAppRes struct { -} - -func (m *DataAppRes) Reset() { *m = DataAppRes{} } -func (m *DataAppRes) String() string { return proto.CompactTextString(m) } -func (*DataAppRes) ProtoMessage() {} -func (*DataAppRes) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{1} } - -type JoinAppReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - Metadata []*Metadata `protobuf:"bytes,30,rep,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinAppReq) Reset() { *m = JoinAppReq{} } -func (m *JoinAppReq) String() string { return proto.CompactTextString(m) } -func (*JoinAppReq) ProtoMessage() {} -func (*JoinAppReq) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{2} } - -func (m *JoinAppReq) GetMetadata() []*Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type JoinAppRes struct { -} - -func (m *JoinAppRes) Reset() { *m = JoinAppRes{} } -func (m *JoinAppRes) String() string { return proto.CompactTextString(m) } -func (*JoinAppRes) ProtoMessage() {} -func (*JoinAppRes) Descriptor() ([]byte, []int) { return fileDescriptorApplication, []int{3} } - -func init() { - proto.RegisterType((*DataAppReq)(nil), "core.DataAppReq") - proto.RegisterType((*DataAppRes)(nil), "core.DataAppRes") - proto.RegisterType((*JoinAppReq)(nil), "core.JoinAppReq") - proto.RegisterType((*JoinAppRes)(nil), "core.JoinAppRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 - -// Client API for App service - -type AppClient interface { - HandleData(ctx context.Context, in *DataAppReq, opts ...grpc.CallOption) (*DataAppRes, error) - HandleJoin(ctx context.Context, in *JoinAppReq, opts ...grpc.CallOption) (*JoinAppRes, error) -} - -type appClient struct { - cc *grpc.ClientConn -} - -func NewAppClient(cc *grpc.ClientConn) AppClient { - return &appClient{cc} -} - -func (c *appClient) HandleData(ctx context.Context, in *DataAppReq, opts ...grpc.CallOption) (*DataAppRes, error) { - out := new(DataAppRes) - err := grpc.Invoke(ctx, "/core.App/HandleData", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *appClient) HandleJoin(ctx context.Context, in *JoinAppReq, opts ...grpc.CallOption) (*JoinAppRes, error) { - out := new(JoinAppRes) - err := grpc.Invoke(ctx, "/core.App/HandleJoin", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for App service - -type AppServer interface { - HandleData(context.Context, *DataAppReq) (*DataAppRes, error) - HandleJoin(context.Context, *JoinAppReq) (*JoinAppRes, error) -} - -func RegisterAppServer(s *grpc.Server, srv AppServer) { - s.RegisterService(&_App_serviceDesc, srv) -} - -func _App_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DataAppReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AppServer).HandleData(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.App/HandleData", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AppServer).HandleData(ctx, req.(*DataAppReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _App_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(JoinAppReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(AppServer).HandleJoin(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.App/HandleJoin", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(AppServer).HandleJoin(ctx, req.(*JoinAppReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _App_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.App", - HandlerType: (*AppServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "HandleData", - Handler: _App_HandleData_Handler, - }, - { - MethodName: "HandleJoin", - Handler: _App_HandleJoin_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -func (m *DataAppReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataAppReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if m.FPort != 0 { - data[i] = 0x50 - i++ - i = encodeVarintApplication(data, i, uint64(m.FPort)) - } - if m.FCnt != 0 { - data[i] = 0x58 - i++ - i = encodeVarintApplication(data, i, uint64(m.FCnt)) - } - if len(m.Payload) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintApplication(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - if len(m.Metadata) > 0 { - for _, msg := range m.Metadata { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintApplication(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *DataAppRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataAppRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *JoinAppReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinAppReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintApplication(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintApplication(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.Metadata) > 0 { - for _, msg := range m.Metadata { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintApplication(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *JoinAppRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinAppRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func encodeFixed64Application(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Application(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintApplication(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *DataAppReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } - if m.FPort != 0 { - n += 1 + sovApplication(uint64(m.FPort)) - } - if m.FCnt != 0 { - n += 1 + sovApplication(uint64(m.FCnt)) - } - l = len(m.Payload) - if l > 0 { - n += 2 + l + sovApplication(uint64(l)) - } - if len(m.Metadata) > 0 { - for _, e := range m.Metadata { - l = e.Size() - n += 2 + l + sovApplication(uint64(l)) - } - } - return n -} - -func (m *DataAppRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *JoinAppReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovApplication(uint64(l)) - } - if len(m.Metadata) > 0 { - for _, e := range m.Metadata { - l = e.Size() - n += 2 + l + sovApplication(uint64(l)) - } - } - return n -} - -func (m *JoinAppRes) Size() (n int) { - var l int - _ = l - return n -} - -func sovApplication(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozApplication(x uint64) (n int) { - return sovApplication(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *DataAppReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataAppReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataAppReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 10: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) - } - m.FPort = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FPort |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) - } - m.FCnt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 20: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Metadata = append(m.Metadata, &Metadata{}) - if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipApplication(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthApplication - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataAppRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataAppRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataAppRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipApplication(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthApplication - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinAppReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinAppReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinAppReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthApplication - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Metadata = append(m.Metadata, &Metadata{}) - if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipApplication(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthApplication - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinAppRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApplication - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinAppRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinAppRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipApplication(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthApplication - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipApplication(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowApplication - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowApplication - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowApplication - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthApplication - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowApplication - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipApplication(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthApplication = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowApplication = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorApplication = []byte{ - // 267 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4c, 0x2c, 0x28, 0xc8, - 0xc9, 0x4c, 0x4e, 0x2c, 0xc9, 0xcc, 0xcf, 0xd3, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, - 0xce, 0x2f, 0x4a, 0x95, 0xe2, 0x02, 0x91, 0x10, 0x11, 0xa5, 0x35, 0x8c, 0x5c, 0x5c, 0x2e, 0x89, - 0x25, 0x89, 0x8e, 0x05, 0x05, 0x41, 0xa9, 0x85, 0x42, 0x62, 0x5c, 0x6c, 0x40, 0x96, 0x6b, 0xa8, - 0xa7, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x07, 0x12, 0x77, 0x49, 0x2d, - 0x03, 0x89, 0x33, 0x41, 0xc4, 0x53, 0xc0, 0x3c, 0x21, 0x11, 0x2e, 0x56, 0xb7, 0x80, 0xfc, 0xa2, - 0x12, 0x09, 0x2e, 0xa0, 0x30, 0x6f, 0x10, 0x6b, 0x1a, 0x88, 0x23, 0x24, 0xc4, 0xc5, 0xe2, 0xe6, - 0x9c, 0x57, 0x22, 0xc1, 0x0d, 0x16, 0x64, 0x49, 0x03, 0xb2, 0x85, 0x24, 0xb8, 0xd8, 0x03, 0x12, - 0x2b, 0x73, 0xf2, 0x13, 0x53, 0x24, 0x44, 0xc0, 0x46, 0xb0, 0x17, 0x40, 0xb8, 0x42, 0x5a, 0x5c, - 0x1c, 0xbe, 0xa9, 0x25, 0x89, 0x29, 0x40, 0x57, 0x48, 0xc8, 0x29, 0x30, 0x6b, 0x70, 0x1b, 0xf1, - 0xe9, 0x81, 0x5d, 0x08, 0x13, 0x0d, 0xe2, 0xc8, 0x85, 0xb2, 0x94, 0x78, 0x90, 0x5c, 0x5b, 0xac, - 0x94, 0xc1, 0xc5, 0xe5, 0x95, 0x9f, 0x99, 0x47, 0xa6, 0xdb, 0x49, 0xb4, 0x17, 0x6e, 0x53, 0xb1, - 0x51, 0x26, 0x17, 0x33, 0x90, 0x25, 0x64, 0xc0, 0xc5, 0xe5, 0x91, 0x98, 0x97, 0x92, 0x93, 0x0a, - 0x72, 0x92, 0x90, 0x00, 0x44, 0x33, 0x22, 0x30, 0xa5, 0xd0, 0x45, 0x8a, 0x11, 0x3a, 0x40, 0x86, - 0xc1, 0x74, 0x20, 0xbc, 0x20, 0x85, 0x2e, 0x52, 0xec, 0x24, 0x70, 0xe2, 0x91, 0x1c, 0xe3, 0x05, - 0x20, 0x7e, 0x00, 0xc4, 0x33, 0x1e, 0xcb, 0x31, 0x24, 0xb1, 0x81, 0x23, 0xce, 0x18, 0x10, 0x00, - 0x00, 0xff, 0xff, 0x31, 0xc2, 0xe7, 0xa9, 0xdf, 0x01, 0x00, 0x00, -} diff --git a/core/broker.pb.go b/core/broker.pb.go deleted file mode 100644 index 5452b4e01..000000000 --- a/core/broker.pb.go +++ /dev/null @@ -1,1225 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: broker.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type DataBrokerReq struct { - Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataBrokerReq) Reset() { *m = DataBrokerReq{} } -func (m *DataBrokerReq) String() string { return proto.CompactTextString(m) } -func (*DataBrokerReq) ProtoMessage() {} -func (*DataBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{0} } - -func (m *DataBrokerReq) GetPayload() *LoRaWANData { - if m != nil { - return m.Payload - } - return nil -} - -func (m *DataBrokerReq) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type DataBrokerRes struct { - Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataBrokerRes) Reset() { *m = DataBrokerRes{} } -func (m *DataBrokerRes) String() string { return proto.CompactTextString(m) } -func (*DataBrokerRes) ProtoMessage() {} -func (*DataBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{1} } - -func (m *DataBrokerRes) GetPayload() *LoRaWANData { - if m != nil { - return m.Payload - } - return nil -} - -func (m *DataBrokerRes) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type JoinBrokerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - MIC []byte `protobuf:"bytes,4,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` - Metadata *Metadata `protobuf:"bytes,5,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinBrokerReq) Reset() { *m = JoinBrokerReq{} } -func (m *JoinBrokerReq) String() string { return proto.CompactTextString(m) } -func (*JoinBrokerReq) ProtoMessage() {} -func (*JoinBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } - -func (m *JoinBrokerReq) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type JoinBrokerRes struct { - Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - Metadata *Metadata `protobuf:"bytes,3,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinBrokerRes) Reset() { *m = JoinBrokerRes{} } -func (m *JoinBrokerRes) String() string { return proto.CompactTextString(m) } -func (*JoinBrokerRes) ProtoMessage() {} -func (*JoinBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } - -func (m *JoinBrokerRes) GetPayload() *LoRaWANJoinAccept { - if m != nil { - return m.Payload - } - return nil -} - -func (m *JoinBrokerRes) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -func init() { - proto.RegisterType((*DataBrokerReq)(nil), "core.DataBrokerReq") - proto.RegisterType((*DataBrokerRes)(nil), "core.DataBrokerRes") - proto.RegisterType((*JoinBrokerReq)(nil), "core.JoinBrokerReq") - proto.RegisterType((*JoinBrokerRes)(nil), "core.JoinBrokerRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 - -// Client API for Broker service - -type BrokerClient interface { - HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) - HandleJoin(ctx context.Context, in *JoinBrokerReq, opts ...grpc.CallOption) (*JoinBrokerRes, error) -} - -type brokerClient struct { - cc *grpc.ClientConn -} - -func NewBrokerClient(cc *grpc.ClientConn) BrokerClient { - return &brokerClient{cc} -} - -func (c *brokerClient) HandleData(ctx context.Context, in *DataBrokerReq, opts ...grpc.CallOption) (*DataBrokerRes, error) { - out := new(DataBrokerRes) - err := grpc.Invoke(ctx, "/core.Broker/HandleData", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *brokerClient) HandleJoin(ctx context.Context, in *JoinBrokerReq, opts ...grpc.CallOption) (*JoinBrokerRes, error) { - out := new(JoinBrokerRes) - err := grpc.Invoke(ctx, "/core.Broker/HandleJoin", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Broker service - -type BrokerServer interface { - HandleData(context.Context, *DataBrokerReq) (*DataBrokerRes, error) - HandleJoin(context.Context, *JoinBrokerReq) (*JoinBrokerRes, error) -} - -func RegisterBrokerServer(s *grpc.Server, srv BrokerServer) { - s.RegisterService(&_Broker_serviceDesc, srv) -} - -func _Broker_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DataBrokerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerServer).HandleData(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Broker/HandleData", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerServer).HandleData(ctx, req.(*DataBrokerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _Broker_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(JoinBrokerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerServer).HandleJoin(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Broker/HandleJoin", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerServer).HandleJoin(ctx, req.(*JoinBrokerReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _Broker_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.Broker", - HandlerType: (*BrokerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "HandleData", - Handler: _Broker_HandleData_Handler, - }, - { - MethodName: "HandleJoin", - Handler: _Broker_HandleJoin_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -func (m *DataBrokerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataBrokerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Payload != nil { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(m.Payload.Size())) - n1, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 - } - if m.Metadata != nil { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) - n2, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 - } - return i, nil -} - -func (m *DataBrokerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataBrokerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Payload != nil { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(m.Payload.Size())) - n3, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } - if m.Metadata != nil { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) - n4, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n4 - } - return i, nil -} - -func (m *JoinBrokerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinBrokerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.DevNonce) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } - if len(m.MIC) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } - if m.Metadata != nil { - data[i] = 0x2a - i++ - i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) - n5, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n5 - } - return i, nil -} - -func (m *JoinBrokerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinBrokerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Payload != nil { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(m.Payload.Size())) - n6, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n6 - } - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if m.Metadata != nil { - data[i] = 0x1a - i++ - i = encodeVarintBroker(data, i, uint64(m.Metadata.Size())) - n7, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n7 - } - return i, nil -} - -func encodeFixed64Broker(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Broker(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintBroker(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *DataBrokerReq) Size() (n int) { - var l int - _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovBroker(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovBroker(uint64(l)) - } - return n -} - -func (m *DataBrokerRes) Size() (n int) { - var l int - _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovBroker(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovBroker(uint64(l)) - } - return n -} - -func (m *JoinBrokerReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovBroker(uint64(l)) - } - return n -} - -func (m *JoinBrokerRes) Size() (n int) { - var l int - _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovBroker(uint64(l)) - } - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovBroker(uint64(l)) - } - return n -} - -func sovBroker(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozBroker(x uint64) (n int) { - return sovBroker(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *DataBrokerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataBrokerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANData{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataBrokerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataBrokerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANData{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinBrokerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinBrokerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) - if m.DevNonce == nil { - m.DevNonce = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) - if m.MIC == nil { - m.MIC = []byte{} - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinBrokerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinBrokerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANJoinAccept{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipBroker(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBroker - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBroker - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBroker - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthBroker - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBroker - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipBroker(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthBroker = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowBroker = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorBroker = []byte{ - // 327 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x49, 0x2a, 0xca, 0xcf, - 0x4e, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, - 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, - 0x0c, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0x44, 0x27, 0xb0, 0xa6, 0xa0, 0xd4, 0x42, 0x21, 0x6d, 0x2e, - 0xf6, 0x80, 0xc4, 0xca, 0x9c, 0xfc, 0xc4, 0x14, 0x09, 0x46, 0x05, 0x46, 0x0d, 0x6e, 0x23, 0x41, - 0x3d, 0xb0, 0x72, 0x9f, 0xfc, 0xa0, 0xc4, 0x70, 0x47, 0x3f, 0x90, 0xe2, 0x20, 0xf6, 0x02, 0x88, - 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, 0x14, 0xa0, 0xa0, 0x04, 0x13, 0x58, 0x35, - 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, 0x42, 0xb7, 0xa9, 0x98, 0x76, 0x36, 0xcd, - 0x64, 0xe4, 0xe2, 0xf5, 0xca, 0xcf, 0xcc, 0x43, 0x78, 0x4a, 0x8c, 0x8b, 0xcd, 0xb1, 0xa0, 0xc0, - 0x35, 0xd4, 0x13, 0x6c, 0x13, 0x4f, 0x10, 0x5b, 0x22, 0x98, 0x07, 0x12, 0x77, 0x49, 0x2d, 0x03, - 0x89, 0x33, 0x41, 0xc4, 0x53, 0xc0, 0x3c, 0x21, 0x29, 0x2e, 0x0e, 0xa0, 0xb8, 0x5f, 0x7e, 0x5e, - 0x72, 0xaa, 0x04, 0x33, 0x58, 0x86, 0x23, 0x05, 0xca, 0x17, 0x12, 0xe0, 0x62, 0xf6, 0xf5, 0x74, - 0x96, 0x60, 0x01, 0x0b, 0x33, 0xe7, 0x7a, 0x3a, 0xa3, 0xb8, 0x8d, 0x95, 0x80, 0xdb, 0x3a, 0xd0, - 0xdc, 0x56, 0x2c, 0x64, 0x88, 0x1e, 0x0c, 0xe2, 0x28, 0xc1, 0x00, 0x52, 0xec, 0x98, 0x9c, 0x9c, - 0x5a, 0x50, 0x82, 0x08, 0x0c, 0x09, 0x2e, 0x76, 0xa0, 0xf3, 0x1c, 0x53, 0x52, 0x8a, 0xa0, 0xee, - 0x66, 0x4f, 0x81, 0x70, 0x51, 0x9c, 0xc2, 0x8c, 0xdf, 0x29, 0x46, 0x15, 0x5c, 0x6c, 0x10, 0x57, - 0x08, 0x99, 0x71, 0x71, 0x79, 0x24, 0xe6, 0xa5, 0xe4, 0xa4, 0x82, 0xc2, 0x5c, 0x48, 0x18, 0xa2, - 0x03, 0x25, 0x59, 0x48, 0x61, 0x11, 0x2c, 0x46, 0xe8, 0x03, 0x39, 0x12, 0xa6, 0x0f, 0x25, 0xe4, - 0xa5, 0xb0, 0x08, 0x16, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, - 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0xd4, 0x68, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0x48, - 0xfb, 0x4c, 0x62, 0xbe, 0x02, 0x00, 0x00, -} diff --git a/core/broker_manager.pb.go b/core/broker_manager.pb.go deleted file mode 100644 index 0cdf14036..000000000 --- a/core/broker_manager.pb.go +++ /dev/null @@ -1,1485 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: broker_manager.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type ValidateOTAABrokerReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` -} - -func (m *ValidateOTAABrokerReq) Reset() { *m = ValidateOTAABrokerReq{} } -func (m *ValidateOTAABrokerReq) String() string { return proto.CompactTextString(m) } -func (*ValidateOTAABrokerReq) ProtoMessage() {} -func (*ValidateOTAABrokerReq) Descriptor() ([]byte, []int) { - return fileDescriptorBrokerManager, []int{0} -} - -type ValidateOTAABrokerRes struct { -} - -func (m *ValidateOTAABrokerRes) Reset() { *m = ValidateOTAABrokerRes{} } -func (m *ValidateOTAABrokerRes) String() string { return proto.CompactTextString(m) } -func (*ValidateOTAABrokerRes) ProtoMessage() {} -func (*ValidateOTAABrokerRes) Descriptor() ([]byte, []int) { - return fileDescriptorBrokerManager, []int{1} -} - -type UpsertABPBrokerReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - NetAddress string `protobuf:"bytes,3,opt,name=NetAddress,json=netAddress,proto3" json:"NetAddress,omitempty"` - DevAddr []byte `protobuf:"bytes,4,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,5,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - Flags uint32 `protobuf:"varint,6,opt,name=Flags,json=flags,proto3" json:"Flags,omitempty"` -} - -func (m *UpsertABPBrokerReq) Reset() { *m = UpsertABPBrokerReq{} } -func (m *UpsertABPBrokerReq) String() string { return proto.CompactTextString(m) } -func (*UpsertABPBrokerReq) ProtoMessage() {} -func (*UpsertABPBrokerReq) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{2} } - -type UpsertABPBrokerRes struct { -} - -func (m *UpsertABPBrokerRes) Reset() { *m = UpsertABPBrokerRes{} } -func (m *UpsertABPBrokerRes) String() string { return proto.CompactTextString(m) } -func (*UpsertABPBrokerRes) ProtoMessage() {} -func (*UpsertABPBrokerRes) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{3} } - -type BrokerDevice struct { - DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` -} - -func (m *BrokerDevice) Reset() { *m = BrokerDevice{} } -func (m *BrokerDevice) String() string { return proto.CompactTextString(m) } -func (*BrokerDevice) ProtoMessage() {} -func (*BrokerDevice) Descriptor() ([]byte, []int) { return fileDescriptorBrokerManager, []int{4} } - -type ValidateTokenBrokerReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` -} - -func (m *ValidateTokenBrokerReq) Reset() { *m = ValidateTokenBrokerReq{} } -func (m *ValidateTokenBrokerReq) String() string { return proto.CompactTextString(m) } -func (*ValidateTokenBrokerReq) ProtoMessage() {} -func (*ValidateTokenBrokerReq) Descriptor() ([]byte, []int) { - return fileDescriptorBrokerManager, []int{5} -} - -type ValidateTokenBrokerRes struct { -} - -func (m *ValidateTokenBrokerRes) Reset() { *m = ValidateTokenBrokerRes{} } -func (m *ValidateTokenBrokerRes) String() string { return proto.CompactTextString(m) } -func (*ValidateTokenBrokerRes) ProtoMessage() {} -func (*ValidateTokenBrokerRes) Descriptor() ([]byte, []int) { - return fileDescriptorBrokerManager, []int{6} -} - -func init() { - proto.RegisterType((*ValidateOTAABrokerReq)(nil), "core.ValidateOTAABrokerReq") - proto.RegisterType((*ValidateOTAABrokerRes)(nil), "core.ValidateOTAABrokerRes") - proto.RegisterType((*UpsertABPBrokerReq)(nil), "core.UpsertABPBrokerReq") - proto.RegisterType((*UpsertABPBrokerRes)(nil), "core.UpsertABPBrokerRes") - proto.RegisterType((*BrokerDevice)(nil), "core.BrokerDevice") - proto.RegisterType((*ValidateTokenBrokerReq)(nil), "core.ValidateTokenBrokerReq") - proto.RegisterType((*ValidateTokenBrokerRes)(nil), "core.ValidateTokenBrokerRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 - -// Client API for BrokerManager service - -type BrokerManagerClient interface { - ValidateOTAA(ctx context.Context, in *ValidateOTAABrokerReq, opts ...grpc.CallOption) (*ValidateOTAABrokerRes, error) - UpsertABP(ctx context.Context, in *UpsertABPBrokerReq, opts ...grpc.CallOption) (*UpsertABPBrokerRes, error) - ValidateToken(ctx context.Context, in *ValidateTokenBrokerReq, opts ...grpc.CallOption) (*ValidateTokenBrokerRes, error) -} - -type brokerManagerClient struct { - cc *grpc.ClientConn -} - -func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { - return &brokerManagerClient{cc} -} - -func (c *brokerManagerClient) ValidateOTAA(ctx context.Context, in *ValidateOTAABrokerReq, opts ...grpc.CallOption) (*ValidateOTAABrokerRes, error) { - out := new(ValidateOTAABrokerRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/ValidateOTAA", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *brokerManagerClient) UpsertABP(ctx context.Context, in *UpsertABPBrokerReq, opts ...grpc.CallOption) (*UpsertABPBrokerRes, error) { - out := new(UpsertABPBrokerRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/UpsertABP", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *brokerManagerClient) ValidateToken(ctx context.Context, in *ValidateTokenBrokerReq, opts ...grpc.CallOption) (*ValidateTokenBrokerRes, error) { - out := new(ValidateTokenBrokerRes) - err := grpc.Invoke(ctx, "/core.BrokerManager/ValidateToken", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for BrokerManager service - -type BrokerManagerServer interface { - ValidateOTAA(context.Context, *ValidateOTAABrokerReq) (*ValidateOTAABrokerRes, error) - UpsertABP(context.Context, *UpsertABPBrokerReq) (*UpsertABPBrokerRes, error) - ValidateToken(context.Context, *ValidateTokenBrokerReq) (*ValidateTokenBrokerRes, error) -} - -func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { - s.RegisterService(&_BrokerManager_serviceDesc, srv) -} - -func _BrokerManager_ValidateOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ValidateOTAABrokerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerManagerServer).ValidateOTAA(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.BrokerManager/ValidateOTAA", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).ValidateOTAA(ctx, req.(*ValidateOTAABrokerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _BrokerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertABPBrokerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerManagerServer).UpsertABP(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.BrokerManager/UpsertABP", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).UpsertABP(ctx, req.(*UpsertABPBrokerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _BrokerManager_ValidateToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ValidateTokenBrokerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerManagerServer).ValidateToken(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.BrokerManager/ValidateToken", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).ValidateToken(ctx, req.(*ValidateTokenBrokerReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _BrokerManager_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.BrokerManager", - HandlerType: (*BrokerManagerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "ValidateOTAA", - Handler: _BrokerManager_ValidateOTAA_Handler, - }, - { - MethodName: "UpsertABP", - Handler: _BrokerManager_UpsertABP_Handler, - }, - { - MethodName: "ValidateToken", - Handler: _BrokerManager_ValidateToken_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -func (m *ValidateOTAABrokerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ValidateOTAABrokerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.NetAddress) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) - i += copy(data[i:], m.NetAddress) - } - return i, nil -} - -func (m *ValidateOTAABrokerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ValidateOTAABrokerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *UpsertABPBrokerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UpsertABPBrokerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.NetAddress) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NetAddress))) - i += copy(data[i:], m.NetAddress) - } - if len(m.DevAddr) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if len(m.NwkSKey) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - if m.Flags != 0 { - data[i] = 0x30 - i++ - i = encodeVarintBrokerManager(data, i, uint64(m.Flags)) - } - return i, nil -} - -func (m *UpsertABPBrokerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UpsertABPBrokerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *BrokerDevice) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *BrokerDevice) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.DevEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - return i, nil -} - -func (m *ValidateTokenBrokerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ValidateTokenBrokerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintBrokerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - return i, nil -} - -func (m *ValidateTokenBrokerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ValidateTokenBrokerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func encodeFixed64BrokerManager(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32BrokerManager(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintBrokerManager(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *ValidateOTAABrokerReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.NetAddress) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - return n -} - -func (m *ValidateOTAABrokerRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *UpsertABPBrokerReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.NetAddress) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - if m.Flags != 0 { - n += 1 + sovBrokerManager(uint64(m.Flags)) - } - return n -} - -func (m *UpsertABPBrokerRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *BrokerDevice) Size() (n int) { - var l int - _ = l - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - return n -} - -func (m *ValidateTokenBrokerReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovBrokerManager(uint64(l)) - } - return n -} - -func (m *ValidateTokenBrokerRes) Size() (n int) { - var l int - _ = l - return n -} - -func sovBrokerManager(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozBrokerManager(x uint64) (n int) { - return sovBrokerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *ValidateOTAABrokerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ValidateOTAABrokerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ValidateOTAABrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NetAddress = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ValidateOTAABrokerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ValidateOTAABrokerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ValidateOTAABrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UpsertABPBrokerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UpsertABPBrokerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertABPBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NetAddress = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) - } - m.Flags = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Flags |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UpsertABPBrokerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UpsertABPBrokerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertABPBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *BrokerDevice) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: BrokerDevice: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: BrokerDevice: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ValidateTokenBrokerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ValidateTokenBrokerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ValidateTokenBrokerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthBrokerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ValidateTokenBrokerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ValidateTokenBrokerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ValidateTokenBrokerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipBrokerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBrokerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipBrokerManager(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthBrokerManager - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowBrokerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipBrokerManager(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthBrokerManager = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowBrokerManager = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorBrokerManager = []byte{ - // 346 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x49, 0x2a, 0xca, 0xcf, - 0x4e, 0x2d, 0x8a, 0xcf, 0x4d, 0xcc, 0x4b, 0x4c, 0x4f, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, - 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x55, 0x4a, 0xe5, 0x12, 0x0d, 0x4b, 0xcc, 0xc9, 0x4c, 0x49, - 0x2c, 0x49, 0xf5, 0x0f, 0x71, 0x74, 0x74, 0x02, 0xab, 0x0c, 0x4a, 0x2d, 0x14, 0x12, 0xe1, 0x62, - 0x0d, 0x01, 0xb2, 0xf3, 0x24, 0x18, 0x15, 0x18, 0x35, 0x38, 0x83, 0x58, 0x4b, 0x40, 0x1c, 0x21, - 0x31, 0x2e, 0x36, 0xc7, 0x82, 0x02, 0xd7, 0x50, 0x4f, 0x09, 0x26, 0xa0, 0x30, 0x4f, 0x10, 0x5b, - 0x22, 0x98, 0x27, 0x24, 0xc7, 0xc5, 0xe5, 0x97, 0x5a, 0xe2, 0x98, 0x92, 0x52, 0x94, 0x5a, 0x5c, - 0x2c, 0xc1, 0x0c, 0xd6, 0xc2, 0x95, 0x07, 0x17, 0x51, 0x12, 0xc7, 0x6e, 0x4d, 0xb1, 0xd2, 0x1a, - 0x46, 0x2e, 0xa1, 0xd0, 0x82, 0xe2, 0xd4, 0xa2, 0x12, 0x47, 0xa7, 0x00, 0x1a, 0xd9, 0x2e, 0x24, - 0xc1, 0xc5, 0xee, 0x92, 0x5a, 0x06, 0xe2, 0x49, 0xb0, 0x80, 0x35, 0xb2, 0xa7, 0x40, 0xb8, 0x20, - 0x19, 0xbf, 0xf2, 0xec, 0x60, 0xef, 0xd4, 0x4a, 0x09, 0x56, 0x88, 0x4c, 0x1e, 0x84, 0x0b, 0x72, - 0x81, 0x5b, 0x4e, 0x62, 0x7a, 0xb1, 0x04, 0x1b, 0x50, 0x9c, 0x37, 0x88, 0x35, 0x0d, 0xc4, 0x51, - 0x12, 0xc1, 0xe2, 0xda, 0x62, 0xa5, 0x28, 0x2e, 0x1e, 0x08, 0x07, 0x68, 0x4b, 0x66, 0x72, 0x2a, - 0xc8, 0x9d, 0x40, 0x16, 0xc8, 0x9d, 0x8c, 0x10, 0x77, 0xa6, 0x80, 0x79, 0xc8, 0xee, 0x60, 0xc2, - 0xe9, 0x0e, 0x66, 0x14, 0x77, 0x28, 0xb9, 0x71, 0x89, 0xc1, 0x42, 0x0e, 0x1c, 0x22, 0x64, 0x86, - 0x91, 0x92, 0x04, 0x0e, 0x73, 0x8a, 0x8d, 0x9e, 0x33, 0x72, 0xf1, 0x42, 0x78, 0xbe, 0x90, 0x04, - 0x22, 0xe4, 0xc1, 0xc5, 0x83, 0x1c, 0x5b, 0x42, 0xd2, 0x7a, 0xa0, 0xb4, 0xa2, 0x87, 0x35, 0xa1, - 0x48, 0xe1, 0x91, 0x2c, 0x16, 0xb2, 0xe7, 0xe2, 0x84, 0x87, 0x97, 0x90, 0x04, 0x44, 0x25, 0x66, - 0x74, 0x4b, 0xe1, 0x92, 0x29, 0x16, 0xf2, 0xe6, 0xe2, 0x45, 0x71, 0xb6, 0x90, 0x0c, 0xaa, 0x75, - 0xa8, 0x61, 0x22, 0x85, 0x4f, 0xb6, 0xd8, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x40, 0xfc, - 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xe7, 0x05, 0x63, 0x40, 0x00, 0x00, 0x00, - 0xff, 0xff, 0xc4, 0x86, 0x11, 0x98, 0x23, 0x03, 0x00, 0x00, -} diff --git a/core/components/broker/appStorage.go b/core/components/broker/appStorage.go deleted file mode 100644 index a91ad7fd8..000000000 --- a/core/components/broker/appStorage.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "encoding" - - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -// NOTE: This is a partial duplication of handler.DevStorage - -// AppStorage gives a facade for manipulating the broker applications infos -type AppStorage interface { - read(appEUI []byte) (appEntry, error) - upsert(entry appEntry) error - done() error -} - -type appEntry struct { - Dialer Dialer - AppEUI []byte -} - -type appStorage struct { - db dbutil.Interface -} - -// NewAppStorage constructs a new broker controller -func NewAppStorage(name string) (AppStorage, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - - return &appStorage{db: itf}, nil -} - -// read implements the AppStorage interface -func (s *appStorage) read(appEUI []byte) (appEntry, error) { - itf, err := s.db.Read(nil, &appEntry{}, appEUI) - if err != nil { - return appEntry{}, err - } - entries, ok := itf.([]appEntry) - if !ok || len(entries) != 1 { - return appEntry{}, errors.New(errors.Structural, "Invalid stored entry") - } - return entries[0], nil -} - -// upsert implements the AppStorage interface -func (s *appStorage) upsert(entry appEntry) error { - return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI) -} - -// done implements the AppStorage interface { -func (s *appStorage) done() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interfaceA -func (e appEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.AppEUI) - rw.Write(e.Dialer.MarshalSafely()) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *appEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { - e.AppEUI = make([]byte, len(data)) - copy(e.AppEUI, data) - }) - rw.Read(func(data []byte) { - e.Dialer = NewDialer(data) - }) - return rw.Err() -} diff --git a/core/components/broker/appStorage_test.go b/core/components/broker/appStorage_test.go deleted file mode 100644 index d6aa10654..000000000 --- a/core/components/broker/appStorage_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const devDB = "TestDevStorage.db" - -func TestReadStore(t *testing.T) { - var db AppStorage - defer func() { - os.Remove(path.Join(os.TempDir(), devDB)) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - var err error - db, err = NewAppStorage(path.Join(os.TempDir(), devDB)) - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Store and read a registration") - - // Build - entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 2}, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - got, err := db.read(entry.AppEUI) - - // Check - CheckErrors(t, nil, err) - Check(t, entry, got, "Device Entries") - } - - // ------------------ - - { - Desc(t, "read a non-existing registration") - - // Build - appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 2} - - // Operate - _, err := db.read(appEUI) - - // Check - CheckErrors(t, ErrNotFound, err) - } - - // ------------------ - - { - Desc(t, "Store twice the same registration") - - // Build - entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 1}, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - err = db.upsert(entry) - FatalUnless(t, err) - got, err := db.read(entry.AppEUI) - - // Check - CheckErrors(t, nil, err) - Check(t, entry, got, "Device Entries") - } - - // ------------------ - - { - Desc(t, "Update an entry") - - // Build - entry := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 4}, - } - update := appEntry{ - Dialer: NewDialer([]byte("dialer")), - AppEUI: []byte{0, 4}, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - err = db.upsert(update) - got, errRead := db.read(entry.AppEUI) - FatalUnless(t, errRead) - - // Check - CheckErrors(t, nil, err) - Check(t, update, got, "Device Entries") - } - - // ------------------ - - { - Desc(t, "Close the storage") - err := db.done() - CheckErrors(t, nil, err) - } -} diff --git a/core/components/broker/broker.go b/core/components/broker/broker.go deleted file mode 100644 index 162d1c448..000000000 --- a/core/components/broker/broker.go +++ /dev/null @@ -1,361 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "fmt" - "net" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/TheThingsNetwork/ttn/utils/tokenkey" - "github.com/apex/log" - "github.com/brocaar/lorawan" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -// component implements the core.BrokerServer interface -type component struct { - Components - NetAddrUp string - NetAddrDown string - TokenKeyProvider tokenkey.Provider - MaxDevNonces uint -} - -// Components defines a structure to make the instantiation easier to read -type Components struct { - NetworkController NetworkController - AppStorage AppStorage - Ctx log.Interface -} - -// Options defines a structure to make the instantiation easier to read -type Options struct { - NetAddrUp string - NetAddrDown string - TokenKeyProvider tokenkey.Provider -} - -// Interface defines the Broker interface -type Interface interface { - core.BrokerServer - core.BrokerManagerServer - Start() error -} - -// New construct a new Broker component -func New(c Components, o Options) Interface { - return component{ - Components: c, - NetAddrUp: o.NetAddrUp, - NetAddrDown: o.NetAddrDown, - TokenKeyProvider: o.TokenKeyProvider, - MaxDevNonces: 10, - } -} - -// Start actually runs the component and starts the rpc server -func (b component) Start() error { - connUp, err := net.Listen("tcp", b.NetAddrUp) - if err != nil { - return errors.New(errors.Operational, err) - } - - connDown, err := net.Listen("tcp", b.NetAddrDown) - if err != nil { - return errors.New(errors.Operational, err) - } - - if b.TokenKeyProvider != nil { - tokenKey, err := b.TokenKeyProvider.Get(true) - if err != nil { - return errors.New(errors.Operational, fmt.Sprintf("Failed to refresh token key: %s", err.Error())) - } - b.Ctx.WithField("provider", b.TokenKeyProvider).Infof("Got token key for algorithm %v", tokenKey.Algorithm) - } - - server := grpc.NewServer() - core.RegisterBrokerServer(server, b) - core.RegisterBrokerManagerServer(server, b) - - cherr := make(chan error) - - go func() { - cherr <- server.Serve(connUp) - }() - - go func() { - cherr <- server.Serve(connDown) - }() - - if err := <-cherr; err != nil { - return errors.New(errors.Operational, err) - } - return nil -} - -// HandleJoin implements the core.BrokerServer interface -func (b component) HandleJoin(bctx context.Context, req *core.JoinBrokerReq) (*core.JoinBrokerRes, error) { - // Validate incoming data - if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || len(req.MIC) != 4 || req.Metadata == nil { - b.Ctx.Debug("Invalid join request. Parameters are incorrect.") - return new(core.JoinBrokerRes), errors.New(errors.Structural, "Invalid parameters") - } - - ctx := b.Ctx.WithFields(log.Fields{ - "AppEUI": req.AppEUI, - "DevEUI": req.DevEUI, - }) - - ctx.Debug("Handle join request") - - // Check if devNonce already referenced - appEntry, err := b.AppStorage.read(req.AppEUI) - if err != nil { - ctx.WithError(err).Debug("Unable to lookup activation") - return new(core.JoinBrokerRes), err - } - nonces, err := b.NetworkController.readNonces(req.AppEUI, req.DevEUI) - if err != nil { - if err.(errors.Failure).Nature != errors.NotFound { - ctx.WithError(err).Debug("Unable to retrieve associated devNonces") - return new(core.JoinBrokerRes), err - } - nonces = noncesEntry{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - } - - var found bool - for _, n := range nonces.DevNonces { - if n[0] == req.DevNonce[0] && n[1] == req.DevNonce[1] { - found = true - break - } - } - if found { - ctx.Debug("DevNonce already used in the past") - return new(core.JoinBrokerRes), errors.New(errors.Structural, "DevNonce used by the past") - } - - // Forward the registration to the handler - handler, closer, err := appEntry.Dialer.Dial() - if err != nil { - ctx.WithError(err).Debug("Unable to dial handler") - return new(core.JoinBrokerRes), err - } - defer closer.Close() - res, err := handler.HandleJoin(context.Background(), &core.JoinHandlerReq{ - DevEUI: req.DevEUI, - AppEUI: req.AppEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - }) - if err != nil { - ctx.WithError(err).Debug("Error while contacting handler") - return new(core.JoinBrokerRes), errors.New(errors.Operational, err) - } - - // Handle the response - if res == nil || res.Payload == nil { - ctx.Debug("No join-accept received") - return new(core.JoinBrokerRes), nil - } - - if len(res.DevAddr) != 4 || len(res.NwkSKey) != 16 { - ctx.Debug("Invalid response from handler") - return new(core.JoinBrokerRes), errors.New(errors.Operational, "Invalid response from handler") - } - - ctx.WithField("DevAddr", res.DevAddr).Debug("Handle join-accept") - - // Update the DevNonce - nonces.DevNonces = append(nonces.DevNonces, req.DevNonce) - if uint(len(nonces.DevNonces)) > b.MaxDevNonces { - nonces.DevNonces = nonces.DevNonces[1:] - } - err = b.NetworkController.upsertNonces(nonces) - if err != nil { - ctx.WithError(err).Debug("Unable to update activation entry") - return new(core.JoinBrokerRes), err - } - - var nwkSKey [16]byte - copy(nwkSKey[:], res.NwkSKey) - err = b.NetworkController.upsert(devEntry{ - Dialer: appEntry.Dialer, - DevAddr: res.DevAddr, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - NwkSKey: nwkSKey, - Flags: 0, - FCntUp: 0, - }) - if err != nil { - ctx.WithError(err).Debug("Unable to store device") - return new(core.JoinBrokerRes), err - } - return &core.JoinBrokerRes{ - Payload: res.Payload, - Metadata: res.Metadata, - }, nil -} - -// HandleData implements the core.BrokerServer interface -func (b component) HandleData(bctx context.Context, req *core.DataBrokerReq) (*core.DataBrokerRes, error) { - // Get some logs / analytics - stats.MarkMeter("broker.uplink.in") - - // Validate incoming data - uplinkPayload, err := core.NewLoRaWANData(req.Payload, true) - if err != nil { - b.Ctx.WithError(err).Debug("Unable to interpret LoRaWAN payload") - return new(core.DataBrokerRes), errors.New(errors.Structural, err) - } - devAddr := req.Payload.MACPayload.FHDR.DevAddr // No nil ref, ensured by NewLoRaWANData() - - ctx := b.Ctx.WithField("DevAddr", devAddr) - ctx.Debug("Handle uplink") - - // Check whether we should handle it - entries, err := b.NetworkController.read(devAddr) - if err != nil { - switch err.(errors.Failure).Nature { - case errors.NotFound: - stats.MarkMeter("broker.uplink.handler_lookup.device_not_found") - ctx.Debug("Uplink device not found") - default: - ctx.WithError(err).Warn("Database lookup failed") - } - return new(core.DataBrokerRes), err - } - stats.UpdateHistogram("broker.uplink.handler_lookup.entries", int64(len(entries))) - - // Several handlers might be associated to the same device, we distinguish them using - // MIC check. Only one should verify the MIC check - // The device only stores a 16-bits counter but could reflect a 32-bits one. - // The counter is used for the MIC computation, thus, we're gonna try both 16-bits and - // 32-bits counters. - // We keep track of the real counter in the network controller. - fhdr := &uplinkPayload.MACPayload.(*lorawan.MACPayload).FHDR // No nil ref, ensured by NewLoRaWANData() - fcnt16 := fhdr.FCnt // Keep a reference to the original counter - - var fcntReset bool - var mEntry *devEntry - for _, entry := range entries { - fcntReset = false - // retrieve the network session key - key := lorawan.AES128Key(entry.NwkSKey) - - // Check frame counter is in valid range - fcnt32, err := b.NetworkController.wholeCounter(fcnt16, entry.FCntUp) - if err != nil { - // invalid, is device in developer mode - if (entry.Flags & core.RelaxFcntCheck) != 0 { - fcnt32 = fcnt16 - fcntReset = true - } else { - continue - } - } - - // Check with 16-bits counters - fhdr.FCnt = fcnt16 - ok, err := uplinkPayload.ValidateMIC(key) - if err != nil { - continue - } - fhdr.FCnt = fcnt32 - if !ok { // Check with 32-bits counter - ok, err = uplinkPayload.ValidateMIC(key) - if err != nil { - continue - } - } - - if ok { - mEntry = &entry - stats.MarkMeter("broker.uplink.handler_lookup.mic_match") - ctx = ctx.WithFields(log.Fields{ - "DevEUI": mEntry.DevEUI, - "Handler": string(entry.Dialer.MarshalSafely()), - }) - ctx.Debug("MIC check succeeded") - break // We stop at the first valid check ... - } - } - - if mEntry == nil { - stats.MarkMeter("broker.uplink.handler_lookup.no_mic_match") - err := errors.New(errors.NotFound, "FCntUp or MIC check did not match") - ctx.WithError(err).Debug("Unable to handle uplink") - return new(core.DataBrokerRes), err - } - - // It does matter here to use the DevEUI from the entry and not from the packet. - // The packet actually holds a DevAddr and the real DevEUI has been determined thanks - // to the MIC check + persistence - mEntry.FCntUp = fhdr.FCnt - if err := b.NetworkController.upsert(*mEntry); err != nil { - ctx.WithError(err).Debug("Unable to update Frame Counter") - return new(core.DataBrokerRes), err - } - - // Then we forward the packet to the handler and wait for the response - handler, closer, err := mEntry.Dialer.Dial() - if err != nil { - ctx.WithError(err).Debug("Unable to dial handler") - return new(core.DataBrokerRes), err - } - defer closer.Close() - resp, err := handler.HandleDataUp(context.Background(), &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - DevEUI: mEntry.DevEUI, - AppEUI: mEntry.AppEUI, - FCnt: fhdr.FCnt, - FPort: req.Payload.MACPayload.FPort, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - FCntUpReset: fcntReset, - }) - - if err != nil { - stats.MarkMeter("broker.uplink.bad_handler_response") - ctx.WithError(err).Debug("Unexpected answer from handler") - return new(core.DataBrokerRes), errors.New(errors.Operational, err) - } - stats.MarkMeter("broker.uplink.ok") - - // No response, we stop here and propagate the "no answer". - // In case of confirmed data, the handler is in charge of creating the confirmation - if resp == nil || resp.Payload == nil { - ctx.Debug("No downlink response") - return new(core.DataBrokerRes), nil - } - ctx.Debug("Handle downlink response") - - // If a response was sent, i.e. a downlink data, we need to compute the right MIC - downlinkPayload, err := core.NewLoRaWANData(resp.Payload, false) - if err != nil { - ctx.WithError(err).Debug("Unable to interpret LoRaWAN downlink datagram") - return new(core.DataBrokerRes), errors.New(errors.Structural, err) - } - stats.MarkMeter("broker.downlink.in") - if err := downlinkPayload.SetMIC(lorawan.AES128Key(mEntry.NwkSKey)); err != nil { - ctx.WithError(err).Debug("Unable to set MIC") - return new(core.DataBrokerRes), errors.New(errors.Structural, "Unable to set response MIC") - } - resp.Payload.MIC = downlinkPayload.MIC[:] - - // And finally, we acknowledge the answer - return &core.DataBrokerRes{ - Payload: resp.Payload, - Metadata: resp.Metadata, - }, nil -} diff --git a/core/components/broker/brokerClient.go b/core/components/broker/brokerClient.go deleted file mode 100644 index a1ba08fa2..000000000 --- a/core/components/broker/brokerClient.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - "google.golang.org/grpc" -) - -type brokerClient struct { - core.BrokerClient - core.BrokerManagerClient -} - -// NewClient instantiates a new core.Broker client -func NewClient(netAddr string) (core.AuthBrokerClient, error) { - brokerConn, err := grpc.Dial( - netAddr, - grpc.WithInsecure(), // TODO Use of TLS - grpc.WithTimeout(time.Second*15), - ) - if err != nil { - return nil, err - } - broker := core.NewBrokerClient(brokerConn) - brokerManager := core.NewBrokerManagerClient(brokerConn) - return &brokerClient{ - BrokerClient: broker, - BrokerManagerClient: brokerManager, - }, nil -} diff --git a/core/components/broker/brokerManager.go b/core/components/broker/brokerManager.go deleted file mode 100644 index 7fb7a7be8..000000000 --- a/core/components/broker/brokerManager.go +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "fmt" - "regexp" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - jwt "github.com/dgrijalva/jwt-go" - "golang.org/x/net/context" -) - -// ValidateToken implements the core.BrokerManagerServer interface -func (b component) ValidateToken(bctx context.Context, req *core.ValidateTokenBrokerReq) (*core.ValidateTokenBrokerRes, error) { - b.Ctx.Debug("Handle ValidateToken request") - if len(req.AppEUI) != 8 { - err := errors.New(errors.Structural, "Invalid request parameters") - b.Ctx.WithError(err).Debug("Unable to handle ValidateToken request") - return new(core.ValidateTokenBrokerRes), err - } - if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { - b.Ctx.WithError(err).Debug("The token is invalid") - return new(core.ValidateTokenBrokerRes), err - } - return new(core.ValidateTokenBrokerRes), nil -} - -// ValidateOTAA implements the core.BrokerManager interface -func (b component) ValidateOTAA(bctx context.Context, req *core.ValidateOTAABrokerReq) (*core.ValidateOTAABrokerRes, error) { - b.Ctx.Debug("Handle ValidateOTAA request") - - // 1. Validate the request - re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") - if len(req.AppEUI) != 8 || !re.Match([]byte(req.NetAddress)) { - err := errors.New(errors.Structural, "Invalid request parameters") - b.Ctx.WithError(err).Debug("Unable to validate OTAA request") - return new(core.ValidateOTAABrokerRes), err - } - - // 2. Verify and validate the token - if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { - return new(core.ValidateOTAABrokerRes), err - } - - // 3. Update the internal storage - b.Ctx.WithField("AppEUI", req.AppEUI).Debug("Request accepted by broker. Registering / Updating App.") - err := b.AppStorage.upsert(appEntry{ - Dialer: NewDialer([]byte(req.NetAddress)), - AppEUI: req.AppEUI, - }) - if err != nil { - b.Ctx.WithError(err).Debug("Error while trying to save valid request") - return new(core.ValidateOTAABrokerRes), errors.New(errors.Operational, err) - } - - // 4. Done. - return new(core.ValidateOTAABrokerRes), nil -} - -// UpsertABP implements the core.BrokerManager interface -func (b component) UpsertABP(bctx context.Context, req *core.UpsertABPBrokerReq) (*core.UpsertABPBrokerRes, error) { - b.Ctx.Debug("Handle UpsertABP request") - - // 1. Validate the request - re := regexp.MustCompile("^([-\\w]+\\.?)+:\\d+$") - if len(req.AppEUI) != 8 || !re.Match([]byte(req.NetAddress)) || len(req.DevAddr) != 4 || len(req.NwkSKey) != 16 { - err := errors.New(errors.Structural, "Invalid request parameters") - b.Ctx.WithError(err).Debug("Unable to proceed Upsert ABP request") - return new(core.UpsertABPBrokerRes), err - } - - // 2. Verify and validate the token - if err := b.validateToken(bctx, req.Token, req.AppEUI); err != nil { - return new(core.UpsertABPBrokerRes), err - } - - // 3. Update the internal storage - b.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering device.") - var nwkSKey [16]byte - copy(nwkSKey[:], req.NwkSKey) - err := b.NetworkController.upsert(devEntry{ - Dialer: NewDialer([]byte(req.NetAddress)), - AppEUI: req.AppEUI, - DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), - DevAddr: req.DevAddr, - NwkSKey: nwkSKey, - Flags: req.Flags, - FCntUp: 0, - }) - if err != nil { - b.Ctx.WithError(err).Debug("Error while trying to save valid request") - return new(core.UpsertABPBrokerRes), errors.New(errors.Operational, err) - } - - // 4. Done. - return new(core.UpsertABPBrokerRes), nil -} - -// validateToken verify an OAuth Bearer token pass through metadata during RPC -func (b component) validateToken(ctx context.Context, token string, appEUI []byte) error { - parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - if b.TokenKeyProvider == nil { - return nil, errors.New(errors.Structural, "No token provider configured") - } - k, err := b.TokenKeyProvider.Get(false) - if err != nil { - return nil, err - } - if k.Algorithm != token.Header["alg"] { - return nil, errors.New(errors.Structural, fmt.Sprintf("Expected algorithm %v but got %v", k.Algorithm, token.Header["alg"])) - } - return []byte(k.Key), nil - }) - if err != nil { - return errors.New(errors.Structural, fmt.Sprintf("Unable to parse token: %s", err.Error())) - } - if !parsed.Valid { - return errors.New(errors.Operational, "The token is not valid or is expired") - } - - apps, ok := parsed.Claims["apps"].([]interface{}) - if !ok { - return fmt.Errorf("Invalid type of apps claim: %T", parsed.Claims["apps"]) - } - - for _, a := range apps { - if s, ok := a.(string); ok && s == fmt.Sprintf("%X", appEUI) { - return nil - } - } - - return errors.New(errors.Operational, "Unauthorized") -} diff --git a/core/components/broker/broker_test.go b/core/components/broker/broker_test.go deleted file mode 100644 index 3031dd9c4..000000000 --- a/core/components/broker/broker_test.go +++ /dev/null @@ -1,1748 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "fmt" - "net" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" - "golang.org/x/net/context" -) - -func TestHandleData(t *testing.T) { - { - Desc(t, "Invalid LoRaWAN payload") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: nil, - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrStructural - var wantDataUp *core.DataUpHandlerReq - var wantRes = new(core.DataBrokerRes) - var wantFCnt uint32 - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Fail to lookup device -> Operational") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["read"] = errors.New(errors.Operational, "Mock Error") - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantDataUp *core.DataUpHandlerReq - var wantRes = new(core.DataBrokerRes) - var wantFCnt uint32 - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Fail to lookup device -> Not Found") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrNotFound - var wantDataUp *core.DataUpHandlerReq - var wantRes = new(core.DataBrokerRes) - var wantFCnt uint32 - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Valid uplink | Two db entries, second MIC valid") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 2 - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - dl2 := NewMockDialer() - dl2.OutDial.Client = hl - dl2.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl2, - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 1, - }, - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 1, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[1].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - - // Expect - var wantErr *string - var wantDataUp = &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutRead.Entries[1].AppEUI, - DevEUI: nc.OutRead.Entries[1].DevEUI, - FCnt: req.Payload.MACPayload.FHDR.FCnt, - FPort: 1, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - } - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry, FCnt invalid") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["wholeCounter"] = errors.New(errors.Structural, "Mock Error") - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 1, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 44567, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - - // Expect - var wantErr = ErrNotFound - var wantDataUp *core.DataUpHandlerReq - var wantRes = new(core.DataBrokerRes) - var wantFCnt uint32 - var wantDialer bool - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry, FCnt above 16-bits") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 112534 - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 112500, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - req.Payload.MACPayload.FHDR.FCnt %= 65536 - - // Expect - var wantErr *string - var wantDataUp = &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutRead.Entries[0].AppEUI, - DevEUI: nc.OutRead.Entries[0].DevEUI, - FCnt: nc.OutWholeCounter.FCnt, - FPort: 1, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - } - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry, Dial failed") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 14 - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 10, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - req.Payload.MACPayload.FHDR.FCnt %= 65536 - - // Expect - var wantErr = ErrOperational - var wantDataUp *core.DataUpHandlerReq - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry, HandleDataUp failed") - - // Build - hl := mocks.NewHandlerClient() - hl.Failures["HandleDataUp"] = fmt.Errorf("Mock Error") - - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 14 - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 10, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - req.Payload.MACPayload.FHDR.FCnt %= 65536 - - // Expect - var wantErr = ErrOperational - var wantDataUp = &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutRead.Entries[0].AppEUI, - DevEUI: nc.OutRead.Entries[0].DevEUI, - FCnt: nc.OutWholeCounter.FCnt, - FPort: 1, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - } - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry, FCnt invalid, RelaxFcntCheck set") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["wholeCounter"] = errors.New(errors.Structural, "Mock Error") - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 40, - Flags: core.RelaxFcntCheck, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 0, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - - // Expect - var wantErr *string - var wantDataUp = &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutRead.Entries[0].AppEUI, - DevEUI: nc.OutRead.Entries[0].DevEUI, - FCnt: 0, - FPort: 1, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - FCntUpReset: true, - } - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry | One valid downlink") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 14 - - hl := mocks.NewHandlerClient() - hl.OutHandleDataUp.Res = &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, - }, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 10, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - req.Payload.MACPayload.FHDR.FCnt %= 65536 - - // Expect - var wantErr *string - var wantDataUp = &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutRead.Entries[0].AppEUI, - DevEUI: nc.OutRead.Entries[0].DevEUI, - FCnt: nc.OutWholeCounter.FCnt, - FPort: 1, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - } - var wantRes = &core.DataBrokerRes{ - Payload: hl.OutHandleDataUp.Res.Payload, - Metadata: hl.OutHandleDataUp.Res.Metadata, - } - payloadDown, err := core.NewLoRaWANData(req.Payload, false) - FatalUnless(t, err) - err = payloadDown.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - wantRes.Payload.MIC = payloadDown.MIC[:] - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry, UpdateFcnt failed") - - // Build - hl := mocks.NewHandlerClient() - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 14 - nc.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 10, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - req.Payload.MACPayload.FHDR.FCnt %= 65536 - - // Expect - var wantErr = ErrOperational - var wantDataUp *core.DataUpHandlerReq - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer bool - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid uplink | One entry | Invalid downlink") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.OutWholeCounter.FCnt = 14 - - hl := mocks.NewHandlerClient() - hl.OutHandleDataUp.Res = &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: nil, - MIC: []byte{0, 0, 0, 0}, - }, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc.OutRead.Entries = []devEntry{ - { - Dialer: dl, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 10, - }, - } - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.DataBrokerReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: nc.OutWholeCounter.FCnt, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{0, 0, 0, 0}, // Temporary, computed below - }, - Metadata: new(core.Metadata), - } - payload, err := core.NewLoRaWANData(req.Payload, true) - FatalUnless(t, err) - err = payload.SetMIC(lorawan.AES128Key(nc.OutRead.Entries[0].NwkSKey)) - FatalUnless(t, err) - req.Payload.MIC = payload.MIC[:] - req.Payload.MACPayload.FHDR.FCnt %= 65536 - - // Expect - var wantErr = ErrStructural - var wantDataUp = &core.DataUpHandlerReq{ - Payload: req.Payload.MACPayload.FRMPayload, - AppEUI: nc.OutRead.Entries[0].AppEUI, - DevEUI: nc.OutRead.Entries[0].DevEUI, - FCnt: nc.OutWholeCounter.FCnt, - FPort: 1, - MType: req.Payload.MHDR.MType, - Metadata: req.Metadata, - } - var wantRes = new(core.DataBrokerRes) - var wantFCnt = nc.OutWholeCounter.FCnt - var wantDialer = true - - // Operate - res, err := br.HandleData(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantDataUp, hl.InHandleDataUp.Req, "Handler Data Requests") - Check(t, wantRes, res, "Broker Data Responses") - Check(t, wantFCnt, nc.InUpsert.Entry.FCntUp, "Frame counters") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } -} - -func TestDialerCloser(t *testing.T) { - { - Desc(t, "Dial on a valid address, server is listening") - - // Build - addr := "0.0.0.0:3300" - conn, err := net.Listen("tcp", addr) - FatalUnless(t, err) - defer conn.Close() - - // Operate & Check - dl := NewDialer([]byte(addr)) - _, cl, errDial := dl.Dial() - CheckErrors(t, nil, errDial) - errClose := cl.Close() - CheckErrors(t, nil, errClose) - } - - // -------------------- - - { - Desc(t, "Dial an invalid address") - - // Build & Operate & Check - dl := NewDialer([]byte("")) - _, _, errDial := dl.Dial() - CheckErrors(t, ErrOperational, errDial) - } -} - -func TestHandleJoin(t *testing.T) { - { - Desc(t, "Valid Join Request | Valid Join Accept") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr *string - var wantJoinReq = &core.JoinHandlerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - var wantRes = &core.JoinBrokerRes{ - Payload: hl.OutHandleJoin.Res.Payload, - Metadata: hl.OutHandleJoin.Res.Metadata, - } - var wantActivation = noncesEntry{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonces: [][]byte{req.DevNonce}, - } - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request - very first time, no devNonces | Valid Join Accept") - - // Build - nc := NewMockNetworkController() - nc.Failures["readNonces"] = errors.New(errors.NotFound, "Mock Error") - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr *string - var wantJoinReq = &core.JoinHandlerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - var wantRes = &core.JoinBrokerRes{ - Payload: hl.OutHandleJoin.Res.Payload, - Metadata: hl.OutHandleJoin.Res.Metadata, - } - var wantActivation = noncesEntry{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonces: [][]byte{req.DevNonce}, - } - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Invalid Join Request -> Invalid AppEUI") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nil, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrStructural - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer bool - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Invalid Join Request -> Invalid DevEUI") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: as.OutRead.Entry.AppEUI, - DevEUI: []byte{1, 2, 3}, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrStructural - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer bool - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Invalid Join Request -> Invalid DevNonce") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14, 15, 16}, - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrStructural - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer bool - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Invalid Join Request -> Invalid Metadata") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: nil, - } - - // Expect - var wantErr = ErrStructural - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer bool - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request | ReadNonces failed") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["readNonces"] = errors.New(errors.Operational, "Mock Error") - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer bool - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request | DevNonce already exists") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{[]byte{14, 14}}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrStructural - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer bool - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request | Dial fails") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - dl.Failures["Dial"] = errors.New(errors.Operational, "Mock Error") - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantJoinReq *core.JoinHandlerReq - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - // -------------------- - - { - Desc(t, "Valid Join Request | Handle join fails") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - hl.Failures["HandleJoin"] = fmt.Errorf("Mock Error") - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantJoinReq = &core.JoinHandlerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request | Invalid response from handler") - - // Build - nc := NewMockNetworkController() - as := NewMockAppStorage() - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: nil, - NwkSKey: nil, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantJoinReq = &core.JoinHandlerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - var wantRes = new(core.JoinBrokerRes) - var wantActivation noncesEntry - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request | Update Activation fails") - - // Build - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["upsertNonces"] = errors.New(errors.Operational, "Mock Error") - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantJoinReq = &core.JoinHandlerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - var wantRes = new(core.JoinBrokerRes) - var wantActivation = noncesEntry{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonces: [][]byte{req.DevNonce}, - } - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } - - // -------------------- - - { - Desc(t, "Valid Join Request | Store device fails") - - // Build - hl := mocks.NewHandlerClient() - hl.OutHandleJoin.Res = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{14, 42}, - }, - DevAddr: []byte{1, 1, 1, 1}, - NwkSKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - Metadata: new(core.Metadata), - } - - dl := NewMockDialer() - dl.OutDial.Client = hl - dl.OutDial.Closer = NewMockCloser() - - nc := NewMockNetworkController() - as := NewMockAppStorage() - nc.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - as.OutRead.Entry = appEntry{ - Dialer: dl, - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - } - nc.OutReadNonces.Entry = noncesEntry{ - AppEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonces: [][]byte{}, - } - - br := New(Components{NetworkController: nc, AppStorage: as, Ctx: GetLogger(t, "Broker")}, Options{}) - req := &core.JoinBrokerReq{ - AppEUI: nc.OutReadNonces.Entry.AppEUI, - DevEUI: nc.OutReadNonces.Entry.DevEUI, - DevNonce: []byte{14, 14}, - MIC: []byte{14, 14, 14, 14}, - - Metadata: new(core.Metadata), - } - - // Expect - var wantErr = ErrOperational - var wantJoinReq = &core.JoinHandlerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - var wantRes = new(core.JoinBrokerRes) - var wantActivation = noncesEntry{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonces: [][]byte{req.DevNonce}, - } - var wantDialer = true - - // Operate - res, err := br.HandleJoin(context.Background(), req) - - // Checks - CheckErrors(t, wantErr, err) - Check(t, wantJoinReq, hl.InHandleJoin.Req, "Handler Join Requests") - Check(t, wantRes, res, "Broker Join Responses") - Check(t, wantActivation, nc.InUpsertNonces.Entry, "Activations") - Check(t, wantDialer, dl.InDial.Called, "Dialer calls") - } -} - -func TestStart(t *testing.T) { - broker := New( - Components{ - Ctx: GetLogger(t, "Broker"), - NetworkController: NewMockNetworkController(), - }, - Options{ - NetAddrUp: "localhost:8883", - NetAddrDown: "localhost:8884", - }, - ) - - cherr := make(chan error) - go func() { - err := broker.Start() - cherr <- err - }() - - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 250): - } - CheckErrors(t, nil, err) -} diff --git a/core/components/broker/controller.go b/core/components/broker/controller.go deleted file mode 100644 index 610f7cd86..000000000 --- a/core/components/broker/controller.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "bytes" - "encoding" - "encoding/binary" - "math" - "reflect" - "sync" - - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -// NetworkController gives a facade for manipulating the broker databases and devices -type NetworkController interface { - read(devAddr []byte) ([]devEntry, error) - readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) - upsertNonces(entry noncesEntry) error - upsert(entry devEntry) error - wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) - done() error -} - -type devEntry struct { - AppEUI []byte - DevEUI []byte - DevAddr []byte - Dialer Dialer - FCntUp uint32 - NwkSKey [16]byte - Flags uint32 -} - -type noncesEntry struct { - AppEUI []byte - DevEUI []byte - DevNonces [][]byte -} - -type controller struct { - sync.RWMutex - db dbutil.Interface -} - -var dbDevices = []byte("devices") - -// NewNetworkController constructs a new broker controller -func NewNetworkController(name string) (NetworkController, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - - return &controller{db: itf}, nil -} - -// read implements the NetworkController interface -func (s *controller) read(devAddr []byte) ([]devEntry, error) { - entries, err := s.db.Read(devAddr, &devEntry{}, dbDevices) - if err != nil { - return nil, err - } - return entries.([]devEntry), nil -} - -// wholeCounter implements the broker.NetworkController interface -func (s *controller) wholeCounter(devCnt uint32, entryCnt uint32) (uint32, error) { - upperSup := int(math.Pow(2, 16)) - diff := int(devCnt) - (int(entryCnt) % upperSup) - var offset int - if diff >= 0 { - offset = diff - } else { - offset = upperSup + diff - } - if offset > upperSup/4 { - return 0, errors.New(errors.Structural, "Gap too big, counter is errored") - } - return entryCnt + uint32(offset), nil -} - -// upsert implements the broker.NetworkController interface -func (s *controller) upsert(update devEntry) error { - s.Lock() - defer s.Unlock() - itf, err := s.db.Read(update.DevAddr, &devEntry{}, dbDevices) - if err != nil { - if err.(errors.Failure).Nature != errors.NotFound { - return err - } - return s.db.Update(update.DevAddr, []encoding.BinaryMarshaler{update}, dbDevices) - } - entries := itf.([]devEntry) - - var newEntries []encoding.BinaryMarshaler - var replaced bool - for _, e := range entries { - entry := new(devEntry) - *entry = e - if reflect.DeepEqual(entry.AppEUI, update.AppEUI) && reflect.DeepEqual(entry.DevEUI, update.DevEUI) { - newEntries = append(newEntries, update) - replaced = true - } else { - newEntries = append(newEntries, entry) - } - } - if !replaced { - newEntries = append(newEntries, update) - } - return s.db.Update(update.DevAddr, newEntries, dbDevices) -} - -// readNonces implements the broker.NetworkController interface -func (s *controller) readNonces(appEUI []byte, devEUI []byte) (noncesEntry, error) { - itf, err := s.db.Read(nil, &noncesEntry{}, appEUI, devEUI) - if err != nil { - return noncesEntry{}, err - } - return itf.([]noncesEntry)[0], nil // Storage guarantee to have one entry -} - -// upsertNonces implements the broker.NetworkController interface -func (s *controller) upsertNonces(entry noncesEntry) error { - return s.db.Update(nil, []encoding.BinaryMarshaler{entry}, entry.AppEUI, entry.DevEUI) -} - -// done implements the broker.NetworkController interface -func (s *controller) done() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e devEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.AppEUI) - rw.Write(e.DevEUI) - rw.Write(e.DevAddr) - rw.Write(e.NwkSKey[:]) - rw.Write(e.FCntUp) - rw.Write(e.Flags) - rw.Write(e.Dialer.MarshalSafely()) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *devEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { - e.AppEUI = make([]byte, len(data)) - copy(e.AppEUI, data) - }) - rw.Read(func(data []byte) { - e.DevEUI = make([]byte, len(data)) - copy(e.DevEUI, data) - }) - rw.Read(func(data []byte) { - e.DevAddr = make([]byte, len(data)) - copy(e.DevAddr, data) - }) - rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) - rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.Flags = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.Dialer = NewDialer(data) }) - return rw.Err() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e noncesEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.AppEUI) - rw.Write(e.DevEUI) - buf := new(bytes.Buffer) - for _, n := range e.DevNonces { - _, _ = buf.Write(n) - } - rw.Write(buf.Bytes()) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *noncesEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { - e.AppEUI = make([]byte, len(data)) - copy(e.AppEUI, data) - }) - rw.Read(func(data []byte) { - e.DevEUI = make([]byte, len(data)) - copy(e.DevEUI, data) - }) - rw.Read(func(data []byte) { - n := len(data) / 2 // DevNonce -> 2-bytes - for i := 0; i < int(n); i++ { - devNonce := make([]byte, 2, 2) - copy(devNonce, data[2*i:2*i+2]) - e.DevNonces = append(e.DevNonces, devNonce) - } - }) - return rw.Err() -} diff --git a/core/components/broker/controller_test.go b/core/components/broker/controller_test.go deleted file mode 100644 index bfd1d9b14..000000000 --- a/core/components/broker/controller_test.go +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const NetworkControllerDB = "TestBrokerNetworkController.db" - -func TestNetworkControllerDevice(t *testing.T) { - NetworkControllerDB := path.Join(os.TempDir(), NetworkControllerDB) - defer func() { - os.Remove(NetworkControllerDB) - }() - - // ------------------- - - { - Desc(t, "Create a new NetworkController") - db, err := NewNetworkController(NetworkControllerDB) - CheckErrors(t, nil, err) - err = db.done() - CheckErrors(t, nil, err) - } - - // ------------------- - - { - Desc(t, "Store then lookup a registration") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry := devEntry{ - DevAddr: []byte{1, 2, 3, 4}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - entries, err := db.read(entry.DevAddr) - - // Expect - want := []devEntry{entry} - - // Check - CheckErrors(t, nil, err) - Check(t, want, entries, "DevEntries") - _ = db.done() - } - - // ------------------- - - { - Desc(t, "Store entries with same DevAddr") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry1 := devEntry{ - DevAddr: []byte{1, 2, 3, 5}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, - } - entry2 := devEntry{ - DevAddr: []byte{1, 2, 3, 5}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 42, - } - - // Operate - err := db.upsert(entry1) - FatalUnless(t, err) - err = db.upsert(entry2) - FatalUnless(t, err) - entries, err := db.read(entry1.DevAddr) - - // Expectations - want := []devEntry{entry1, entry2} - - // Check - CheckErrors(t, nil, err) - Check(t, want, entries, "DevEntries") - _ = db.done() - } - - // ------------------- - - { - Desc(t, "Lookup non-existing entry") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - devAddr := []byte{0, 0, 0, 1} - - // Operate - entries, err := db.read(devAddr) - - // Expect - var want []devEntry - - // Checks - CheckErrors(t, ErrNotFound, err) - Check(t, want, entries, "DevEntries") - _ = db.done() - } - - // ------------------- - - { - Desc(t, "Store on a closed database") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - _ = db.done() - entry := devEntry{ - DevAddr: []byte{1, 0, 0, 2}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, - } - - // Operate - err := db.upsert(entry) - - // Checks - CheckErrors(t, ErrOperational, err) - } - - // ------------------- - - { - Desc(t, "Lookup on a closed database") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - _ = db.done() - devAddr := []byte{1, 2, 3, 4} - - // Operate - entries, err := db.read(devAddr) - - // Expect - var want []devEntry - - // Checks - CheckErrors(t, ErrOperational, err) - Check(t, want, entries, "DevEntries") - } - - // ------------------- - - { - Desc(t, "Update counter up of an entry -> one device") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry := devEntry{ - DevAddr: []byte{1, 0, 0, 4}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - entry.FCntUp = 42 - err = db.upsert(entry) - FatalUnless(t, err) - entries, err := db.read(entry.DevAddr) - FatalUnless(t, err) - - // Expectation - want := []devEntry{entry} - - // Check - Check(t, want, entries, "DevEntries") - _ = db.done() - } - - // ------------------- - - { - Desc(t, "Update counter several entries") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry1 := devEntry{ - DevAddr: []byte{8, 8, 8, 8}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - FCntUp: 14, - } - entry2 := devEntry{ - DevAddr: []byte{8, 8, 8, 8}, - Dialer: NewDialer([]byte("url")), - AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntUp: 42, - } - - // Operate - err := db.upsert(entry1) - FatalUnless(t, err) - err = db.upsert(entry2) - FatalUnless(t, err) - entry2.FCntUp = 8 - err = db.upsert(entry2) - FatalUnless(t, err) - entries, err := db.read(entry1.DevAddr) - FatalUnless(t, err) - - // Expectations - want := []devEntry{entry1, entry2} - - // Check - Check(t, want, entries, "DevEntries") - _ = db.done() - } - - // -------------------- - - { - Desc(t, "Test counters, both < 65535") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - wholeCnt := uint32(13) - cnt16 := wholeCnt + 1 - - // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) - - // Check - CheckErrors(t, nil, err) - Check(t, wholeCnt+1, cnt32, "Counters") - - _ = db.done() - } - - // -------------------- - - { - Desc(t, "Test counters, devCnt < wholeCnt, | | > max_gap") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - wholeCnt := uint32(14) - cnt16 := wholeCnt - 1 - - // Operate - _, err := db.wholeCounter(cnt16, wholeCnt) - - // Check - CheckErrors(t, ErrStructural, err) - - _ = db.done() - } - - // -------------------- - - { - Desc(t, "Test counters, whole > 65535, | | < max_gap") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - wholeCnt := uint32(3824624235) - cnt16 := uint32(wholeCnt%65536 + 2) - - // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) - - // Check - CheckErrors(t, nil, err) - Check(t, wholeCnt+2, cnt32, "Counters") - - _ = db.done() - } - - // -------------------- - - { - Desc(t, "Test counters, whole > 65535, | | > max_gap") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - wholeCnt := uint32(3824624235) - cnt16 := uint32(wholeCnt%65536 + 45000) - - // Operate - _, err := db.wholeCounter(cnt16, wholeCnt) - - // Check - CheckErrors(t, ErrStructural, err) - - _ = db.done() - } - - // -------------------- - - { - Desc(t, "Test counters, whole > 65535, | | < max_gap, via inf") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - wholeCnt := uint32(65535) - cnt16 := uint32(2) - - // Operate - cnt32, err := db.wholeCounter(cnt16, wholeCnt) - - // Check - CheckErrors(t, nil, err) - Check(t, wholeCnt+3, cnt32, "Counters") - - _ = db.done() - } -} - -func TestNonces(t *testing.T) { - NetworkControllerDB := path.Join(os.TempDir(), NetworkControllerDB) - defer func() { - os.Remove(NetworkControllerDB) - }() - - // ------------------- - - { - Desc(t, "Store then lookup a noncesEntry") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry := noncesEntry{ - AppEUI: []byte{1, 2, 3}, - DevEUI: []byte{4, 5, 6}, - DevNonces: [][]byte{[]byte{14, 42}}, - } - - // Operate - err := db.upsertNonces(entry) - FatalUnless(t, err) - got, err := db.readNonces(entry.AppEUI, entry.DevEUI) - FatalUnless(t, err) - - // Check - Check(t, entry, got, "Nonces Entries") - _ = db.done() - } - - // ------------------- - - { - Desc(t, "Update an existing nonce") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - entry := noncesEntry{ - AppEUI: []byte{26, 2, 3}, - DevEUI: []byte{4, 26, 26}, - DevNonces: [][]byte{[]byte{14, 42}}, - } - update := noncesEntry{ - AppEUI: entry.AppEUI, - DevEUI: entry.DevEUI, - DevNonces: [][]byte{[]byte{58, 27}, []byte{12, 11}}, - } - - // Operate - err := db.upsertNonces(entry) - FatalUnless(t, err) - err = db.upsertNonces(update) - FatalUnless(t, err) - got, err := db.readNonces(entry.AppEUI, entry.DevEUI) - FatalUnless(t, err) - - // Check - Check(t, update, got, "Nonces Entries") - _ = db.done() - } - - // ------------------- - - { - - Desc(t, "Lookup an non-existing nonces entry") - - // Build - db, _ := NewNetworkController(NetworkControllerDB) - appEUI := []byte{4, 5, 3, 4} - devEUI := []byte{1, 2, 2, 2} - - // Operate - _, err := db.readNonces(appEUI, devEUI) - - // Check - CheckErrors(t, ErrNotFound, err) - _ = db.done() - } -} diff --git a/core/components/broker/dialer.go b/core/components/broker/dialer.go deleted file mode 100644 index 2e5de7253..000000000 --- a/core/components/broker/dialer.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "google.golang.org/grpc" -) - -// Dialer abstracts the connection to grpc, or anything else -type Dialer interface { - MarshalSafely() []byte - Dial() (core.HandlerClient, Closer, error) // Dial actually attempts to dial a connection -} - -// Closer is returned by a Dialer to give a hand on closing the dialed connection -type Closer interface { - Close() error -} - -// dialer implements the Dialer interface -type dialer struct { - NetAddr string -} - -// closer implements the Closer interface -type closer struct { - Conn *grpc.ClientConn -} - -// Close implements the Closer interface -func (c closer) Close() error { - if err := c.Conn.Close(); err != nil { - return errors.New(errors.Operational, err) - } - return nil -} - -// NewDialer constructs a new dialer from a given net address -func NewDialer(netAddr []byte) Dialer { - return &dialer{NetAddr: string(netAddr)} -} - -// Dial implements the Dialer interface -func (d dialer) Dial() (core.HandlerClient, Closer, error) { - conn, err := grpc.Dial(d.NetAddr, grpc.WithInsecure(), grpc.WithTimeout(time.Second*2)) - if err != nil { - return nil, nil, errors.New(errors.Operational, err) - } - return core.NewHandlerClient(conn), closer{Conn: conn}, nil -} - -// MarshalSafely implements the Dialer interface -func (d dialer) MarshalSafely() []byte { - return []byte(d.NetAddr) -} diff --git a/core/components/broker/mocks_test.go b/core/components/broker/mocks_test.go deleted file mode 100644 index 46f1a36ed..000000000 --- a/core/components/broker/mocks_test.go +++ /dev/null @@ -1,189 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "github.com/TheThingsNetwork/ttn/core" -) - -// NOTE All the code below could be generated - -// MockDialer mocks the Dialer interface -type MockDialer struct { - Failures map[string]error - InMarshalSafely struct { - Called bool - } - OutMarshalSafely struct { - Data []byte - } - InDial struct { - Called bool - } - OutDial struct { - Client core.HandlerClient - Closer Closer - } -} - -// NewMockDialer instantiates a new MockDialer object -func NewMockDialer() *MockDialer { - return &MockDialer{ - Failures: make(map[string]error), - } -} - -// MarshalSafely implements the Dialer interface -func (m *MockDialer) MarshalSafely() []byte { - m.InMarshalSafely.Called = true - return m.OutMarshalSafely.Data -} - -// Dial implements the Dialer interface -func (m *MockDialer) Dial() (core.HandlerClient, Closer, error) { - m.InDial.Called = true - return m.OutDial.Client, m.OutDial.Closer, m.Failures["Dial"] -} - -// MockCloser mocks the Closer interface -type MockCloser struct { - Failures map[string]error - InClose struct { - Called bool - } -} - -// NewMockCloser instantiates a new MockCloser object -func NewMockCloser() *MockCloser { - return &MockCloser{ - Failures: make(map[string]error), - } -} - -// Close implements the Closer interface -func (m *MockCloser) Close() error { - m.InClose.Called = true - return m.Failures["Close"] -} - -// MockAppStorage mocks the AppStorage interface -type MockAppStorage struct { - Failures map[string]error - InRead struct { - AppEUI []byte - } - OutRead struct { - Entry appEntry - } - InUpsert struct { - Entry appEntry - } - InDone struct { - Called bool - } -} - -// NewMockAppStorage creates a new MockAppStorage -func NewMockAppStorage() *MockAppStorage { - return &MockAppStorage{ - Failures: make(map[string]error), - } -} - -// read implements the AppStorage interface -func (m *MockAppStorage) read(appEUI []byte) (appEntry, error) { - m.InRead.AppEUI = appEUI - return m.OutRead.Entry, m.Failures["read"] -} - -// upsert implements the AppStorage interface -func (m *MockAppStorage) upsert(entry appEntry) error { - m.InUpsert.Entry = entry - return m.Failures["upsert"] -} - -// done implements the AppStorage Interface -func (m *MockAppStorage) done() error { - m.InDone.Called = true - return m.Failures["done"] -} - -// MockNetworkController mocks the NetworkController interface -type MockNetworkController struct { - Failures map[string]error - InRead struct { - DevAddr []byte - } - OutRead struct { - Entries []devEntry - } - InUpsert struct { - Entry devEntry - } - InReadNonces struct { - AppEUI []byte - DevEUI []byte - } - OutReadNonces struct { - Entry noncesEntry - } - InUpsertNonces struct { - Entry noncesEntry - } - InWholeCounter struct { - DevCnt uint32 - EntryCnt uint32 - } - OutWholeCounter struct { - FCnt uint32 - } - InDone struct { - Called bool - } -} - -// NewMockNetworkController creates a new MockNetworkController -func NewMockNetworkController() *MockNetworkController { - return &MockNetworkController{ - Failures: make(map[string]error), - } -} - -// read implements the NetworkController interface -func (m *MockNetworkController) read(devAddr []byte) ([]devEntry, error) { - m.InRead.DevAddr = devAddr - return m.OutRead.Entries, m.Failures["read"] -} - -// upsert implements the NetworkController interface -func (m *MockNetworkController) upsert(entry devEntry) error { - m.InUpsert.Entry = entry - return m.Failures["upsert"] -} - -// readNonces implements the NetworkController interface -func (m *MockNetworkController) readNonces(appEUI, devEUI []byte) (noncesEntry, error) { - m.InReadNonces.AppEUI = appEUI - m.InReadNonces.DevEUI = devEUI - return m.OutReadNonces.Entry, m.Failures["readNonces"] -} - -// upsertNonces implements the NetworkController interface -func (m *MockNetworkController) upsertNonces(entry noncesEntry) error { - m.InUpsertNonces.Entry = entry - return m.Failures["upsertNonces"] -} - -// wholeCnt implements the NetworkController interface -func (m *MockNetworkController) wholeCounter(devCnt, entryCnt uint32) (uint32, error) { - m.InWholeCounter.DevCnt = devCnt - m.InWholeCounter.EntryCnt = entryCnt - return m.OutWholeCounter.FCnt, m.Failures["wholeCounter"] -} - -// done implements the NetworkController Interface -func (m *MockNetworkController) done() error { - m.InDone.Called = true - return m.Failures["done"] -} diff --git a/core/components/handler/devStorage.go b/core/components/handler/devStorage.go deleted file mode 100644 index 667ee2b91..000000000 --- a/core/components/handler/devStorage.go +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "encoding" - "encoding/binary" - - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -// DevStorage gives a facade to manipulate the handler devices database -type DevStorage interface { - read(appEUI []byte, devEUI []byte) (devEntry, error) - readAll(appEUI []byte) ([]devEntry, error) - upsert(entry devEntry) error - setDefault(appEUI []byte, entry *devDefaultEntry) error - getDefault(appEUI []byte) (*devDefaultEntry, error) - done() error -} - -const dbDevices = "devices" - -type devEntry struct { - AppEUI []byte - AppKey *[16]byte - AppSKey [16]byte - DevAddr []byte - DevEUI []byte - FCntDown uint32 - FCntUp uint32 - NwkSKey [16]byte - Flags uint32 -} - -type devDefaultEntry struct { - AppKey [16]byte -} - -type devStorage struct { - db dbutil.Interface -} - -// NewDevStorage creates a new Device Storage for handler -func NewDevStorage(name string) (DevStorage, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - - return &devStorage{db: itf}, nil -} - -func (s *devStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { - itf, err := s.db.Read(devEUI, &devEntry{}, appEUI) - if err != nil { - return devEntry{}, err - } - return itf.([]devEntry)[0], nil // Type and dimensio guaranteed by db.Read() -} - -func (s *devStorage) readAll(appEUI []byte) ([]devEntry, error) { - itf, err := s.db.ReadAll(&devEntry{}, appEUI) - if err != nil { - return nil, err - } - return itf.([]devEntry), nil -} - -func (s *devStorage) upsert(entry devEntry) error { - return s.db.Update(entry.DevEUI, []encoding.BinaryMarshaler{entry}, entry.AppEUI) -} - -func (s *devStorage) setDefault(appEUI []byte, entry *devDefaultEntry) error { - return s.db.Update([]byte("default"), []encoding.BinaryMarshaler{entry}, appEUI) -} - -func (s *devStorage) getDefault(appEUI []byte) (*devDefaultEntry, error) { - itf, err := s.db.Read([]byte("default"), &devDefaultEntry{}, appEUI) - if err != nil { - if ferr, ok := err.(errors.Failure); ok && ferr.Nature == errors.NotFound { - return nil, nil - } - return nil, err - } - return &itf.([]devDefaultEntry)[0], nil -} - -// done implements the handler.DevStorage interface -func (s *devStorage) done() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e devEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - if e.AppKey != nil { - rw.Write(e.AppKey[:]) - } else { - rw.Write([]byte{}) - } - rw.Write(e.AppSKey[:]) - rw.Write(e.NwkSKey[:]) - rw.Write(e.FCntUp) - rw.Write(e.FCntDown) - rw.Write(e.Flags) - rw.Write(e.AppEUI) - rw.Write(e.DevEUI) - rw.Write(e.DevAddr) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *devEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { - if len(data) == 16 { - e.AppKey = new([16]byte) - copy(e.AppKey[:], data) - } else { - e.AppKey = nil - } - }) - rw.Read(func(data []byte) { copy(e.AppSKey[:], data) }) - rw.Read(func(data []byte) { copy(e.NwkSKey[:], data) }) - rw.Read(func(data []byte) { e.FCntUp = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.FCntDown = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { e.Flags = binary.BigEndian.Uint32(data) }) - rw.Read(func(data []byte) { - e.AppEUI = make([]byte, len(data)) - copy(e.AppEUI, data) - }) - rw.Read(func(data []byte) { - e.DevEUI = make([]byte, len(data)) - copy(e.DevEUI, data) - }) - rw.Read(func(data []byte) { - e.DevAddr = make([]byte, len(data)) - copy(e.DevAddr, data) - }) - return rw.Err() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e devDefaultEntry) MarshalBinary() ([]byte, error) { - rw := readwriter.New(nil) - rw.Write(e.AppKey[:]) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *devDefaultEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { copy(e.AppKey[:], data) }) - return rw.Err() -} diff --git a/core/components/handler/devStorage_test.go b/core/components/handler/devStorage_test.go deleted file mode 100644 index a39ddf9a8..000000000 --- a/core/components/handler/devStorage_test.go +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const devDB = "TestDevStorage.db" - -func TestStore(t *testing.T) { - var db DevStorage - defer func() { - os.Remove(path.Join(os.TempDir(), devDB)) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - var err error - db, err = NewDevStorage(path.Join(os.TempDir(), devDB)) - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Store and read a registration") - - // Build - entry := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - DevAddr: []byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - got, err := db.read(entry.AppEUI, entry.DevEUI) - - // Check - CheckErrors(t, nil, err) - Check(t, entry, got, "Device Entries") - } - - // ------------------ - - { - Desc(t, "Read a non-existing registration") - - // Build - appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} - devEUI := []byte{0, 0, 0, 0, 1, 2, 3, 4} - - // Operate - _, err := db.read(appEUI, devEUI) - - // Check - CheckErrors(t, ErrNotFound, err) - } - - // ------------------ - - { - Desc(t, "Store twice the same registration") - - // Build - entry := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 9}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - DevAddr: []byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - err = db.upsert(entry) - FatalUnless(t, err) - got, err := db.read(entry.AppEUI, entry.DevEUI) - - // Check - CheckErrors(t, nil, err) - Check(t, entry, got, "Device Entries") - } - - // ------------------ - - { - Desc(t, "Update FCnt") - - // Build - entry := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 14}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - DevAddr: []byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 2, - } - update := devEntry{ - AppEUI: entry.AppEUI, - DevEUI: entry.DevEUI, - DevAddr: entry.DevAddr, - AppSKey: entry.AppSKey, - NwkSKey: entry.NwkSKey, - FCntDown: 14, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - err = db.upsert(update) - got, errRead := db.read(entry.AppEUI, entry.DevEUI) - FatalUnless(t, errRead) - - // Check - CheckErrors(t, nil, err) - Check(t, update, got, "Device Entries") - } - - // ------------------ - - { - Desc(t, "Store several, then readAll") - - // Build - entry1 := devEntry{ - AppEUI: []byte{1, 2, 3, 44, 54, 6, 7, 14}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 4}, - DevAddr: []byte{1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 2, - } - entry2 := devEntry{ - AppEUI: []byte{1, 2, 3, 44, 54, 6, 7, 14}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - DevAddr: []byte{2, 2, 3, 4}, - AppSKey: [16]byte{2, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{7, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - entry3 := devEntry{ - AppEUI: []byte{1, 8, 9, 44, 54, 6, 7, 14}, - DevEUI: []byte{0, 0, 0, 0, 1, 2, 3, 5}, - DevAddr: []byte{2, 2, 3, 4}, - AppSKey: [16]byte{2, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{7, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - - // Operate - err := db.upsert(entry1) - FatalUnless(t, err) - err = db.upsert(entry2) - FatalUnless(t, err) - err = db.upsert(entry3) - FatalUnless(t, err) - entries, err := db.readAll(entry1.AppEUI) - - // Check - CheckErrors(t, nil, err) - Check(t, []devEntry{entry1, entry2}, entries, "Devices Entries") - } - - // ------------------ - - { - Desc(t, "Read a non-existing default device entry") - - // Build - appEUI := []byte{0, 0, 0, 0, 0, 0, 0, 1} - - // Operate - entry, err := db.getDefault(appEUI) - - // Expect - var want *devDefaultEntry - - // Check - CheckErrors(t, nil, err) - Check(t, want, entry, "Default Entry") - } - - // ------------------ - - { - Desc(t, "Set and get default device entry") - - // Build - appEUI := []byte{1, 2, 3, 4, 5, 6, 7, 8} - entry := devDefaultEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Operate - err := db.setDefault(appEUI, &entry) - FatalUnless(t, err) - want, err := db.getDefault(appEUI) - - // Check - FatalUnless(t, err) - Check(t, *want, entry, "Default Entry") - } - - // ------------------ - - { - Desc(t, "Close the storage") - err := db.done() - CheckErrors(t, nil, err) - } -} - -func TestMarshalUnmarshalDevEntries(t *testing.T) { - { - Desc(t, "Complete Entry") - entry := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - AppSKey: [16]byte{0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2, 1}, - DevAddr: []byte{4, 4, 4, 4}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - FCntDown: 42, - NwkSKey: [16]byte{28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13}, - } - - data, err := entry.MarshalBinary() - CheckErrors(t, nil, err) - unmarshaled := new(devEntry) - err = unmarshaled.UnmarshalBinary(data) - CheckErrors(t, nil, err) - Check(t, entry, *unmarshaled, "Entries") - } - - // -------------------- - - { - Desc(t, "Partial Entry") - entry := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - FCntDown: 0, - } - want := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - AppSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - DevAddr: make([]byte, 0, 0), - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - FCntDown: 0, - NwkSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - } - - data, err := entry.MarshalBinary() - CheckErrors(t, nil, err) - unmarshaled := new(devEntry) - err = unmarshaled.UnmarshalBinary(data) - CheckErrors(t, nil, err) - Check(t, want, *unmarshaled, "Entries") - } - - // -------------------- - - { - Desc(t, "Partial Entry bis") - entry := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - DevAddr: []byte{}, - FCntDown: 0, - } - want := devEntry{ - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: &[16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - AppSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - DevAddr: make([]byte, 0, 0), - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - FCntDown: 0, - NwkSKey: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - } - - data, err := entry.MarshalBinary() - CheckErrors(t, nil, err) - unmarshaled := new(devEntry) - err = unmarshaled.UnmarshalBinary(data) - CheckErrors(t, nil, err) - Check(t, want, *unmarshaled, "Entries") - } -} - -func TestMarshalUnmarshalDevDefaultEntries(t *testing.T) { - { - Desc(t, "Entry") - entry := devDefaultEntry{ - AppKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - data, err := entry.MarshalBinary() - CheckErrors(t, nil, err) - unmarshaled := new(devDefaultEntry) - err = unmarshaled.UnmarshalBinary(data) - CheckErrors(t, nil, err) - Check(t, entry, *unmarshaled, "Entries") - } -} diff --git a/core/components/handler/handler.go b/core/components/handler/handler.go deleted file mode 100644 index 7dc771b1b..000000000 --- a/core/components/handler/handler.go +++ /dev/null @@ -1,812 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "bytes" - "encoding/binary" - "fmt" - "net" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - "github.com/TheThingsNetwork/ttn/core/otaa" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" - "github.com/brocaar/lorawan" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -// bufferDelay defines the timeframe length during which we bufferize packets -const bufferDelay time.Duration = time.Millisecond * 300 - -// dataRates makes correspondance between string datarate identifier and lorawan uint descriptors -var dataRates = map[string]uint8{ - "SF12BW125": 0, - "SF11BW125": 1, - "SF10BW125": 2, - "SF9BW125": 3, - "SF8BW125": 4, - "SF7BW125": 5, -} - -// component implements the core.Component interface -type component struct { - Components - ChBundles chan<- bundle - Processed pQueue - PublicNetAddr string - PrivateNetAddr string - PrivateNetAddrAnnounce string - Configuration struct { - CFList [5]uint32 - NetID [3]byte - RX1DROffset uint8 - RX2DataRate string - RX2JoinRate string - RX2Freq float32 - RXDelay uint8 - PowerRX1 uint32 - PowerRX2 uint32 - RFChain uint32 - InvPolarity bool - JoinDelay uint8 - } -} - -// Interface defines the Handler interface -type Interface interface { - core.HandlerServer - core.HandlerManagerServer - Start() error -} - -// Components is used to make handler instantiation easier -type Components struct { - Broker core.AuthBrokerClient - Ctx log.Interface - DevStorage DevStorage - PktStorage PktStorage - AppAdapter core.AppClient -} - -// Options is used to make handler instantiation easier -type Options struct { - PublicNetAddr string // Net Address used to communicate with the handler from the outside - PrivateNetAddr string // Net Address the handler listens on for internal communications - PrivateNetAddrAnnounce string // Net Address the handler announces to brokers for internal communications - ProcessedQueueSize uint // The maximum number of appEUI + devEUI the handler can process at the same time -} - -// bundle are used to materialize an incoming request being bufferized, waiting for the others. -type bundle struct { - Chresp chan interface{} - Entry devEntry - ID [21]byte - Packet interface{} - DataRate string - Time time.Time -} - -// New construct a new Handler -func New(c Components, o Options) Interface { - if o.ProcessedQueueSize == 0 { - o.ProcessedQueueSize = 5000 - } - - h := &component{ - Components: c, - PublicNetAddr: o.PublicNetAddr, - PrivateNetAddr: o.PrivateNetAddr, - PrivateNetAddrAnnounce: o.PrivateNetAddrAnnounce, - Processed: newPQueue(o.ProcessedQueueSize), - } - - // TODO Make it configurable - h.Configuration.CFList = [5]uint32{867100000, 867300000, 867500000, 867700000, 867900000} - h.Configuration.NetID = [3]byte{14, 14, 14} - h.Configuration.RX1DROffset = 0 - h.Configuration.RX2DataRate = "SF9BW125" - h.Configuration.RX2JoinRate = "SF12BW125" - h.Configuration.RX2Freq = 869.525 - h.Configuration.RXDelay = 1 - h.Configuration.JoinDelay = 5 - h.Configuration.PowerRX1 = 14 - h.Configuration.PowerRX2 = 27 - h.Configuration.RFChain = 0 - h.Configuration.InvPolarity = true - - set := make(chan bundle) - bundles := make(chan []bundle) - - h.ChBundles = set - go h.consumeBundles(bundles) - go h.consumeSet(bundles, set) - - return h -} - -// Start actually runs the component and starts the rpc server -func (h *component) Start() error { - pubConn, errPub := net.Listen("tcp", h.PublicNetAddr) - priConn, errPri := net.Listen("tcp", h.PrivateNetAddr) - - if errPub != nil || errPri != nil { - return errors.New(errors.Operational, fmt.Sprintf("Unable to open connections: %s | %s", errPub, errPri)) - } - - server := grpc.NewServer() - core.RegisterHandlerServer(server, h) - core.RegisterHandlerManagerServer(server, h) - - cherr := make(chan error) - - go func() { cherr <- server.Serve(pubConn) }() - go func() { cherr <- server.Serve(priConn) }() - - if err := <-cherr; err != nil { - return errors.New(errors.Operational, err) - } - return nil -} - -// HandleJoin implements the core.HandlerServer interface -func (h component) HandleJoin(bctx context.Context, req *core.JoinHandlerReq) (*core.JoinHandlerRes, error) { - stats.MarkMeter("handler.joinrequest.in") - - // 0. Check the packet integrity - if req == nil || len(req.DevEUI) != 8 || len(req.AppEUI) != 8 || len(req.DevNonce) != 2 || req.Metadata == nil { - h.Ctx.Debug("Invalid join request packet") - return new(core.JoinHandlerRes), errors.New(errors.Structural, "Invalid parameters") - } - - ctx := h.Ctx.WithFields(log.Fields{ - "AppEUI": req.AppEUI, - "DevEUI": req.DevEUI, - }) - - ctx.Debug("Handle join request") - - // 1. Lookup the associated entry or create new entry based on default - entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) - if ferr, ok := err.(errors.Failure); ok && ferr.Nature == errors.NotFound { // The device is unknown, check if there are default settings - defaultEntry, err := h.DevStorage.getDefault(req.AppEUI) - if err != nil { - ctx.WithError(err).Debug("Failed to retrieve default device settings") - return new(core.JoinHandlerRes), err - } - if defaultEntry == nil { - ctx.Debug("Device unknown and no default device settings configured") - return new(core.JoinHandlerRes), errors.New(errors.NotFound, "Device unknown and no default device settings configured") - } - // Register a new OTAA device based on default - ctx.Debug("Registering a new OTAA device based on default settings") - entry = devEntry{ - AppEUI: req.AppEUI, - AppKey: &defaultEntry.AppKey, - DevEUI: req.DevEUI, - } - if err = h.DevStorage.upsert(entry); err != nil { - ctx.WithError(err).Debug("Failed to store new device based on default settings") - return new(core.JoinHandlerRes), err - } - } else if err != nil { // General error - ctx.WithError(err).Debug("Failed to retrieve device entry from storage") - return new(core.JoinHandlerRes), err - } else if entry.AppKey == nil { // Trying to activate an ABP device - ctx.Debug("Cannot activate a personalized device over the air") - return new(core.JoinHandlerRes), errors.New(errors.Behavioural, "Cannot activate a personalized device over the air") - } - - // 2. Verify MIC - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{Major: lorawan.LoRaWANR1, MType: lorawan.JoinRequest} - joinPayload := lorawan.JoinRequestPayload{} - copy(payload.MIC[:], req.MIC) - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - if ok, err := payload.ValidateMIC(lorawan.AES128Key(*entry.AppKey)); err != nil || !ok { - ctx.Debug("Invalid join-request MIC") - return new(core.JoinHandlerRes), errors.New(errors.Structural, "Unable to validate MIC") - } - - // 3. Prepare a channel to receive the response from the consumer - chresp := make(chan interface{}) - - // 4. Create a "bundle" which holds info waiting for other related packets - var bundleID [21]byte // Type | AppEUI(8) | DevEUI(8) | DevNonce | [ 0 0 ] - buf := bytes.NewBuffer([]byte{0}) // 0 for join - binary.Write(buf, binary.BigEndian, req.AppEUI) - binary.Write(buf, binary.BigEndian, req.DevEUI) - binary.Write(buf, binary.BigEndian, req.DevNonce) - copy(bundleID[:], buf.Bytes()) - - // 5. Send the actual bundle to the consumer - ctx.WithField("BundleID", bundleID).Debug("Define new bundle") - - h.ChBundles <- bundle{ - ID: bundleID, - Packet: req, - DataRate: req.Metadata.DataRate, - Entry: entry, - Chresp: chresp, - Time: time.Now(), - } - - // 6. Control the response - resp := <-chresp - switch resp.(type) { - case *core.JoinHandlerRes: - stats.MarkMeter("handler.join.send_accept") - ctx.Debug("Sending join-accept") - return resp.(*core.JoinHandlerRes), nil - case error: - stats.MarkMeter("handler.join.error") - ctx.WithError(resp.(error)).Warn("Error while processing join-request.") - return new(core.JoinHandlerRes), resp.(error) - default: - ctx.Debug("No response to send.") - return new(core.JoinHandlerRes), nil - } -} - -// HandleDataDown implements the core.HandlerServer interface -func (h component) HandleDataDown(bctx context.Context, req *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { - stats.MarkMeter("handler.downlink.in") - - // Unmarshal the given packet and see what gift we get - - if len(req.AppEUI) != 8 { - stats.MarkMeter("handler.downlink.invalid") - return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid Application EUI") - } - - if len(req.DevEUI) != 8 { - stats.MarkMeter("handler.downlink.invalid") - return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid Device EUI") - } - - if len(req.Payload) == 0 { - stats.MarkMeter("handler.downlink.invalid") - return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid payload") - } - - ttl, err := time.ParseDuration(req.TTL) - if err != nil || ttl == 0 { - stats.MarkMeter("handler.downlink.invalid") - return new(core.DataDownHandlerRes), errors.New(errors.Structural, "Invalid TTL") - } - - h.Ctx.WithField("DevEUI", req.DevEUI).WithField("AppEUI", req.AppEUI).Debug("Handle downlink - enqueue") - - return new(core.DataDownHandlerRes), h.PktStorage.enqueue(pktEntry{ - Payload: req.Payload, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - TTL: time.Now().Add(ttl), - }) -} - -// HandleDataUp implements the core.HandlerServer interface -func (h component) HandleDataUp(bctx context.Context, req *core.DataUpHandlerReq) (*core.DataUpHandlerRes, error) { - stats.MarkMeter("handler.uplink.in") - - // 0. Check the packet integrity - if len(req.Payload) == 0 { - stats.MarkMeter("handler.uplink.invalid") - return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Invalid Packet Payload") - } - if len(req.DevEUI) != 8 { - stats.MarkMeter("handler.uplink.invalid") - return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Invalid Device EUI") - } - if len(req.AppEUI) != 8 { - stats.MarkMeter("handler.uplink.invalid") - return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Invalid Application EUI") - } - if req.Metadata == nil { - stats.MarkMeter("handler.uplink.invalid") - return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Missing Mandatory Metadata") - } - stats.MarkMeter("handler.uplink.data") - - ctx := h.Ctx.WithFields(log.Fields{ - "AppEUI": req.AppEUI, - "DevEUI": req.DevEUI, - }) - - ctx.Debug("Handle Uplink") - - // 1. Lookup for the associated AppSKey + Application - entry, err := h.DevStorage.read(req.AppEUI, req.DevEUI) - if err != nil { - return new(core.DataUpHandlerRes), err - } - if len(entry.DevAddr) != 4 { // Not Activated - return new(core.DataUpHandlerRes), errors.New(errors.Structural, "Tried to send uplink on non-activated device") - } - - // 2. Prepare a channel to receive the response from the consumer - chresp := make(chan interface{}) - - // 3. Create a "bundle" which holds info waiting for other related packets - var bundleID [21]byte // Type | AppEUI(8) | DevEUI(8) | FCnt - buf := bytes.NewBuffer([]byte{1}) // 1 for uplink - _ = binary.Write(buf, binary.BigEndian, req.AppEUI) - _ = binary.Write(buf, binary.BigEndian, req.DevEUI) - _ = binary.Write(buf, binary.BigEndian, req.FCnt) - copy(bundleID[:], buf.Bytes()) - - // 4. Send the actual bundle to the consumer - ctx.WithField("BundleID", bundleID).Debug("Define new bundle") - h.ChBundles <- bundle{ - ID: bundleID, - Packet: req, - DataRate: req.Metadata.DataRate, - Entry: entry, - Chresp: chresp, - Time: time.Now(), - } - - // 5. Wait for the response. Could be an error, a packet or nothing. - // We'll respond to a maximum of one node. The handler will use the - // rssi + gateway's duty cycle to select to best fit. - // All other channels will get a nil response. - // If there's an error, all channels get the error. - resp := <-chresp - switch resp.(type) { - case *core.DataUpHandlerRes: - stats.MarkMeter("handler.uplink.ack.with_response") - stats.MarkMeter("handler.downlink.out") - ctx.Debug("Sending downlink packet as response") - return resp.(*core.DataUpHandlerRes), nil - case error: - stats.MarkMeter("handler.uplink.error") - ctx.WithError(resp.(error)).Warn("Error while processing dowlink") - return new(core.DataUpHandlerRes), resp.(error) - default: - stats.MarkMeter("handler.uplink.ack.without_response") - ctx.Debug("No response to send") - return new(core.DataUpHandlerRes), nil - } -} - -// consumeSet gathers new incoming bundles which possess the same id (i.e. appEUI & devEUI & Fcnt) -// It then flushes them once a given delay has passed since the reception of the first bundle. -func (h component) consumeSet(chbundles chan<- []bundle, chset <-chan bundle) { - ctx := h.Ctx.WithField("goroutine", "set consumer") - ctx.Debug("Starting set consumer") - - buffers := make(map[[21]byte][]bundle) // AppEUI | DevEUI | FCnt -> buffered bundles - alarm := make(chan [21]byte) // Communication channel with subsequent alarms - - for { - select { - case id := <-alarm: - // Get all bundles - bundles := buffers[id] - delete(buffers, id) - h.Processed.Put(id[:]) // Register the last processed entry - - // Actually send the bundle to the be processed - ctx.WithField("BundleID", id).Debug("End buffering") - go func(bundles []bundle) { chbundles <- bundles }(bundles) - case b := <-chset: - ctx = ctx.WithField("BundleID", b.ID) - - // Check if bundle has already been processed - if h.Processed.Contains(b.ID[:]) { - ctx.Debug("Already processed - Reject") - go func(b bundle) { - b.Chresp <- errors.New(errors.Behavioural, "Already processed") - }(b) - continue - } - - // Add the bundle to the stack, and set the alarm if its the first - bundles := append(buffers[b.ID], b) - if len(bundles) == 1 { - ctx.Debug("Start buffering") - go setAlarm(alarm, b.ID, bufferDelay) - } - buffers[b.ID] = bundles - } - } -} - -// setAlarm will trigger a message on the given channel after the given delay -func setAlarm(alarm chan<- [21]byte, id [21]byte, delay time.Duration) { - <-time.After(delay) - alarm <- id -} - -// consumeBundles processes list of bundle generated overtime, decrypt the underlying packet, -// deduplicate them, and send a single enhanced packet to the upadapter for further processing. -func (h component) consumeBundles(chbundle <-chan []bundle) { - ctx := h.Ctx.WithField("goroutine", "bundle consumer") - ctx.Debug("Starting bundle consumer") - -browseBundles: - for bundles := range chbundle { - ctx.WithField("BundleID", bundles[0].ID).Debug("Consume bundle") - if len(bundles) < 1 { - continue browseBundles - } - b := bundles[0] - switch b.Packet.(type) { - case *core.DataUpHandlerReq: - pkt := b.Packet.(*core.DataUpHandlerReq) - go h.consumeDown(pkt.AppEUI, pkt.DevEUI, b.DataRate, bundles) - case *core.JoinHandlerReq: - pkt := b.Packet.(*core.JoinHandlerReq) - // Entry.AppKey not nil, checked before creating any bundles - go h.consumeJoin(pkt.AppEUI, pkt.DevEUI, *b.Entry.AppKey, b.DataRate, bundles) - } - } -} - -// consume Join actually consumes a set of join-request packets -func (h component) consumeJoin(appEUI []byte, devEUI []byte, appKey [16]byte, dataRate string, bundles []bundle) { - ctx := h.Ctx.WithField("AppEUI", appEUI).WithField("DevEUI", devEUI) - ctx.Debug("Consuming join-request") - - // Compute score while gathering metadata - var metadata []*core.Metadata - computer, scores, err := dutycycle.NewScoreComputer(dataRate) - if err != nil { - ctx.WithError(err).Debug("Unable to instantiate score computer") - h.abortConsume(err, bundles) - return - } - - for i, bundle := range bundles { - packet := bundle.Packet.(*core.JoinHandlerReq) - metadata = append(metadata, packet.Metadata) - scores = computer.Update(scores, i, *packet.Metadata) - } - - // Check if at least one is available - best := computer.Get(scores) - ctx.WithField("Best", best).Debug("Determine best response gateway") - if best == nil { - h.abortConsume(errors.New(errors.Operational, "No gateway is available for an answer"), bundles) - return - } - packet := bundles[best.ID].Packet.(*core.JoinHandlerReq) - - // Generate a DevAddr - Note: this should be done by the Broker (issue #90). - var devAddr [4]byte - copy(devAddr[:], random.Bytes(4)) - devAddr[0] = (h.Configuration.NetID[2] << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb - - // Generate appNonce - var appNonce [3]byte - copy(appNonce[:], random.Bytes(3)) - - var devNonce [2]byte - copy(devNonce[:], packet.DevNonce) - - // Generate Session keys - appSKey, nwkSKey, err := otaa.CalculateSessionKeys(appKey, appNonce, h.Configuration.NetID, devNonce) - if err != nil { - h.abortConsume(errors.New(errors.Structural, "Unable to generate session keys"), bundles) - return - } - - // Update the internal storage entry - err = h.DevStorage.upsert(devEntry{ - AppEUI: appEUI, - AppKey: &appKey, - AppSKey: appSKey, - DevAddr: devAddr[:], - DevEUI: devEUI, - FCntDown: 0, - FCntUp: 0, - NwkSKey: nwkSKey, - Flags: 0, - }) - if err != nil { - ctx.WithError(err).Debug("Unable to initialize devEntry with activation") - h.abortConsume(err, bundles) - return - } - - // Build join-accept and send it - joinAccept, err := h.buildJoinAccept(packet, appKey, appNonce[:3], devAddr, best.IsRX2) - if err != nil { - ctx.WithError(err).Debug("Unable to build join accept") - h.abortConsume(err, bundles) - return - } - joinAccept.NwkSKey = nwkSKey[:] - joinAccept.DevAddr = devAddr[:] - - // Notify the application - _, err = h.AppAdapter.HandleJoin(context.Background(), &core.JoinAppReq{ - Metadata: metadata, - AppEUI: appEUI, - DevEUI: devEUI, - }) - if err != nil { - ctx.WithError(err).Debug("Fails to notify application") - } - - for i, bundle := range bundles { - if i == best.ID { - // Reset processed packets for that appEUI + devEUi - h.Processed.Remove(append([]byte{1}, bundle.ID[1:]...)) - bundle.Chresp <- joinAccept - } else { - bundle.Chresp <- nil - } - } -} - -// consume Down actually consumes a set of downlink packets -func (h component) consumeDown(appEUI []byte, devEUI []byte, dataRate string, bundles []bundle) { - stats.UpdateHistogram("handler.uplink.duplicate.count", int64(len(bundles))) - var metadata []*core.Metadata - var fPort uint32 - var fCnt uint32 - var payload []byte - var firstTime time.Time - - computer, scores, err := dutycycle.NewScoreComputer(dataRate) - if err != nil { - h.abortConsume(err, bundles) - return - } - - for i, bundle := range bundles { - // We only decrypt the payload of the first bundle's packet. - // We assume all the other to be equal and we'll merely collect - // metadata from other bundle. - packet := bundle.Packet.(*core.DataUpHandlerReq) - if i == 0 { - var err error - var devAddr lorawan.DevAddr - copy(devAddr[:], bundle.Entry.DevAddr) - payload, err = lorawan.EncryptFRMPayload( - bundle.Entry.AppSKey, - true, - devAddr, - packet.FCnt, - packet.Payload, - ) - if err != nil { - h.abortConsume(err, bundles) - return - } - firstTime = bundle.Time - fPort = packet.FPort - fCnt = packet.FCnt - stats.MarkMeter("handler.uplink.in.unique") - } else { - diff := bundle.Time.Sub(firstTime).Nanoseconds() - stats.UpdateHistogram("handler.uplink.duplicate.delay", diff/1000) - } - - // Append metadata for each of them - metadata = append(metadata, packet.Metadata) - scores = computer.Update(scores, i, *packet.Metadata) // Nil check already done - } - - // Then create an application-level packet and send it to the wild open - // we don't expect a response from the adapter, end of the chain. - _, err = h.AppAdapter.HandleData(context.Background(), &core.DataAppReq{ - AppEUI: appEUI, - DevEUI: devEUI, - FPort: fPort, - FCnt: fCnt, - Payload: payload, - Metadata: metadata, - }) - if err != nil { - h.abortConsume(errors.New(errors.Operational, err), bundles) - return - } - - stats.MarkMeter("handler.uplink.out") - - // Now handle the downlink and respond to node - best := computer.Get(scores) - var downlink pktEntry - if best != nil { // Avoid pulling when there's no gateway available for an answer - downlink, err = h.PktStorage.dequeue(appEUI, devEUI) - } - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - h.abortConsume(err, bundles) - return - } - - // One of those bundle might be available for a response - upType := lorawan.MType(bundles[0].Packet.(*core.DataUpHandlerReq).MType) - for i, bundle := range bundles { - if best != nil && best.ID == i && (downlink.Payload != nil || upType == lorawan.ConfirmedDataUp) { - stats.MarkMeter("handler.downlink.pull") - downType := lorawan.UnconfirmedDataDown - ack := (upType == lorawan.ConfirmedDataUp) - if bundle.Packet.(*core.DataUpHandlerReq).FCntUpReset { - bundle.Entry.FCntDown = 0 - } - downlink, err := h.buildDownlink(downlink.Payload, downType, ack, *bundle.Packet.(*core.DataUpHandlerReq), bundle.Entry, best.IsRX2) - if err != nil { - h.abortConsume(errors.New(errors.Structural, err), bundles) - return - } - - bundle.Entry.FCntDown = downlink.Payload.MACPayload.FHDR.FCnt - bundle.Entry.FCntUp = bundle.Packet.(*core.DataUpHandlerReq).FCnt - err = h.DevStorage.upsert(bundle.Entry) - if err != nil { - h.abortConsume(err, bundles) - return - } - bundle.Chresp <- downlink - } else { - bundle.Chresp <- nil - } - } - - // Then, if there was no downlink, we still update the Frame Counter Up in the storage - if best == nil || downlink.Payload == nil && upType != lorawan.ConfirmedDataUp { - bundles[0].Entry.FCntUp = bundles[0].Packet.(*core.DataUpHandlerReq).FCnt - if err := h.DevStorage.upsert(bundles[0].Entry); err != nil { - h.Ctx.WithError(err).Debug("Unable to update Frame Counter Up") - } - } -} - -// Abort consume forward the given error to all bundle recipients -func (h component) abortConsume(err error, bundles []bundle) { - stats.MarkMeter("handler.uplink.invalid") - h.Ctx.WithError(err).Debug("Unable to consume bundle") - for _, bundle := range bundles { - bundle.Chresp <- err - } -} - -// constructs a downlink packet from something we pulled from the gathered downlink, and, the actual -// uplink. -func (h component) buildDownlink(down []byte, mtype lorawan.MType, ack bool, up core.DataUpHandlerReq, entry devEntry, isRX2 bool) (*core.DataUpHandlerRes, error) { - macpayload := &lorawan.MACPayload{} - macpayload.FHDR = lorawan.FHDR{ - FCnt: entry.FCntDown + 1, - } - copy(macpayload.FHDR.DevAddr[:], entry.DevAddr) - macpayload.FPort = new(uint8) - *macpayload.FPort = 1 - if ack { - macpayload.FHDR.FCtrl.ACK = true - } - - if down != nil { - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: down}} - } - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{ - MType: mtype, - Major: lorawan.LoRaWANR1, - } - payload.MACPayload = macpayload - - data, err := payload.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - var frmpayload []byte - err = payload.EncryptFRMPayload(entry.AppSKey) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - if down != nil { - frmpayload, err = macpayload.FRMPayload[0].MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - } - - metadata := h.buildMetadata(*up.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.RXDelay), isRX2, false) - - return &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(payload.MHDR.MType), - Major: uint32(payload.MHDR.Major), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: macpayload.FHDR.DevAddr[:], - FCnt: macpayload.FHDR.FCnt, - FCtrl: &core.LoRaWANFCtrl{ - ADR: macpayload.FHDR.FCtrl.ADR, - ADRAckReq: macpayload.FHDR.FCtrl.ADRACKReq, - Ack: macpayload.FHDR.FCtrl.ACK, - FPending: macpayload.FHDR.FCtrl.FPending, - }, - }, - FPort: uint32(*macpayload.FPort), - FRMPayload: frmpayload, - }, - MIC: payload.MIC[:], - }, - Metadata: &metadata, - }, nil -} - -func (h component) buildJoinAccept(joinReq *core.JoinHandlerReq, appKey [16]byte, appNonce []byte, devAddr [4]byte, isRX2 bool) (*core.JoinHandlerRes, error) { - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{ - MType: lorawan.JoinAccept, - Major: lorawan.LoRaWANR1, - } - joinAcceptPayload := &lorawan.JoinAcceptPayload{ - NetID: lorawan.NetID(h.Configuration.NetID), - DevAddr: lorawan.DevAddr(devAddr), - DLSettings: lorawan.DLSettings{ - RX1DROffset: h.Configuration.RX1DROffset, - RX2DataRate: dataRates[h.Configuration.RX2DataRate], - }, - RXDelay: h.Configuration.RXDelay, - } - cflist := lorawan.CFList(h.Configuration.CFList) - joinAcceptPayload.CFList = &cflist - copy(joinAcceptPayload.AppNonce[:], appNonce) - payload.MACPayload = joinAcceptPayload - if err := payload.SetMIC(lorawan.AES128Key(appKey)); err != nil { - return nil, errors.New(errors.Structural, err) - } - if err := payload.EncryptJoinAcceptPayload(lorawan.AES128Key(appKey)); err != nil { - return nil, errors.New(errors.Structural, err) - } - data, err := payload.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - - // force RX2 for testing - m := h.buildMetadata(*joinReq.Metadata, uint32(len(data)), 1000000*uint32(h.Configuration.JoinDelay), isRX2, true) - return &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: data, - }, - Metadata: &m, - }, nil -} - -// buildMetadata construct a new Metadata -func (h component) buildMetadata(metadata core.Metadata, size uint32, baseDelay uint32, isRX2 bool, isJoin bool) core.Metadata { - m := core.Metadata{ - Frequency: metadata.Frequency, - CodingRate: metadata.CodingRate, - DataRate: metadata.DataRate, - Modulation: metadata.Modulation, - RFChain: h.Configuration.RFChain, - InvPolarity: h.Configuration.InvPolarity, - Power: h.Configuration.PowerRX1, - PayloadSize: size, - Timestamp: metadata.Timestamp + baseDelay, - } - - if isRX2 { // Should we reply on RX2, metadata aren't the same - // TODO Handle different regions with non hard-coded values - m.Frequency = h.Configuration.RX2Freq - if isJoin { - m.DataRate = h.Configuration.RX2JoinRate - } else { - m.DataRate = h.Configuration.RX2DataRate - } - m.Power = h.Configuration.PowerRX2 - m.Timestamp = metadata.Timestamp + baseDelay + 1000000 - } - return m -} diff --git a/core/components/handler/handlerClient.go b/core/components/handler/handlerClient.go deleted file mode 100644 index 0672b0594..000000000 --- a/core/components/handler/handlerClient.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core" - "google.golang.org/grpc" -) - -type handlerClient struct { - core.HandlerClient - core.HandlerManagerClient -} - -// NewClient instantiates a new core.handler client -func NewClient(netAddr string) (core.AuthHandlerClient, error) { - handlerConn, err := grpc.Dial( - netAddr, - grpc.WithInsecure(), // TODO Use of TLS - grpc.WithTimeout(time.Second*15), - ) - if err != nil { - return nil, err - } - handler := core.NewHandlerClient(handlerConn) - handlerManager := core.NewHandlerManagerClient(handlerConn) - return &handlerClient{ - HandlerClient: handler, - HandlerManagerClient: handlerManager, - }, nil -} diff --git a/core/components/handler/handlerClient_test.go b/core/components/handler/handlerClient_test.go deleted file mode 100644 index 6858f129a..000000000 --- a/core/components/handler/handlerClient_test.go +++ /dev/null @@ -1,1141 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "golang.org/x/net/context" -) - -func TestNewClient(t *testing.T) { - _, err := NewClient("0.0.0.0:12345") - CheckErrors(t, nil, err) -} - -func TestListDevices(t *testing.T) { - { - Desc(t, "Valid request, no issue") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.OutReadAll.Entries = []devEntry{ - { - DevEUI: []byte{1, 2, 1, 2, 1, 2, 1, 2}, - DevAddr: []byte{14, 14, 14, 14}, - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - NwkSKey: [16]byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: [16]byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - FCntDown: 14, - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - }, - { - DevAddr: []byte{42, 42, 42, 42}, - AppEUI: []byte{8, 7, 6, 5, 4, 3, 2, 1}, - NwkSKey: [16]byte{1, 2, 3, 6, 1, 2, 3, 6, 1, 2, 3, 6, 1, 2, 3, 6}, - AppSKey: [16]byte{48, 2, 48, 2, 48, 2, 48, 2, 48, 2, 48, 2, 48, 2, 48, 2}, - FCntDown: 5, - }, - } - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.ListDevicesHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - } - var wantRes = &core.ListDevicesHandlerRes{ - OTAA: []*core.HandlerOTAADevice{ - &core.HandlerOTAADevice{ - DevEUI: st.OutReadAll.Entries[0].DevEUI, - DevAddr: st.OutReadAll.Entries[0].DevAddr, - NwkSKey: st.OutReadAll.Entries[0].NwkSKey[:], - AppSKey: st.OutReadAll.Entries[0].AppSKey[:], - AppKey: st.OutReadAll.Entries[0].AppKey[:], - FCntDown: 14, - FCntUp: 0, - }, - }, - ABP: []*core.HandlerABPDevice{ - &core.HandlerABPDevice{ - DevAddr: st.OutReadAll.Entries[1].DevAddr, - NwkSKey: st.OutReadAll.Entries[1].NwkSKey[:], - AppSKey: st.OutReadAll.Entries[1].AppSKey[:], - FCntDown: 5, - FCntUp: 0, - }, - }, - } - - // Operate - res, err := h.ListDevices(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | readAll fails") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.Failures["readAll"] = errors.New(errors.Operational, "Mock Error") - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.ListDevicesHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - } - var wantRes = new(core.ListDevicesHandlerRes) - - // Operate - res, err := h.ListDevices(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | broker fails") - - // Build - br := mocks.NewAuthBrokerClient() - br.Failures["ValidateToken"] = errors.New(errors.Operational, "Mock Error") - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.ListDevicesHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - } - var wantRes = new(core.ListDevicesHandlerRes) - - // Operate - res, err := h.ListDevices(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid AppEUI") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.ListDevicesHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateTokenBrokerReq - var wantRes = new(core.ListDevicesHandlerRes) - - // Operate - res, err := h.ListDevices(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } -} - -func TestUpsertABP(t *testing.T) { - { - Desc(t, "Valid request, no issue") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr *string - var wantBrkCall = &core.UpsertABPBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).PrivateNetAddrAnnounce, - } - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | storage fails") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.UpsertABPBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).PrivateNetAddrAnnounce, - } - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | broker fails") - - // Build - br := mocks.NewAuthBrokerClient() - br.Failures["UpsertABP"] = errors.New(errors.Operational, "Mock Error") - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.UpsertABPBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - NetAddress: h.(*component).PrivateNetAddrAnnounce, - } - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | DevAddr invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{14, 14, 14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.UpsertABPBrokerReq - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | AppEUI invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6}, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.UpsertABPBrokerReq - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | NwkSKey invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: nil, - AppSKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.UpsertABPBrokerReq - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | AppSKey invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertABPHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: []byte{14, 14, 14, 14}, - NwkSKey: []byte{1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4}, - AppSKey: []byte{1, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.UpsertABPBrokerReq - var wantRes = new(core.UpsertABPHandlerRes) - - // Operate - res, err := h.UpsertABP(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InUpsertABP.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } -} - -func TestUpsertOTAA(t *testing.T) { - { - Desc(t, "Valid request, no issue") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertOTAAHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr *string - var wantBrkCall = &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: h.(*component).PrivateNetAddrAnnounce, - } - var wantRes = new(core.UpsertOTAAHandlerRes) - - // Operate - res, err := h.UpsertOTAA(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | storage fails") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertOTAAHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: h.(*component).PrivateNetAddrAnnounce, - } - var wantRes = new(core.UpsertOTAAHandlerRes) - - // Operate - res, err := h.UpsertOTAA(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | broker fails") - - // Build - br := mocks.NewAuthBrokerClient() - br.Failures["ValidateOTAA"] = errors.New(errors.Operational, "Mock Error") - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertOTAAHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: h.(*component).PrivateNetAddrAnnounce, - } - var wantRes = new(core.UpsertOTAAHandlerRes) - - // Operate - res, err := h.UpsertOTAA(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | DevEUI invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertOTAAHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{14, 14, 14, 14, 14, 14}, - AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateOTAABrokerReq - var wantRes = new(core.UpsertOTAAHandlerRes) - - // Operate - res, err := h.UpsertOTAA(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | AppEUI invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertOTAAHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - AppKey: []byte{1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateOTAABrokerReq - var wantRes = new(core.UpsertOTAAHandlerRes) - - // Operate - res, err := h.UpsertOTAA(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | AppKey invalid") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.UpsertOTAAHandlerReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{14, 14, 14, 14, 14, 14, 14, 14}, - AppKey: []byte{1, 1, 2, 1, 2, 1, 2, 1, 2}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateOTAABrokerReq - var wantRes = new(core.UpsertOTAAHandlerRes) - - // Operate - res, err := h.UpsertOTAA(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } -} - -func TestGetDefault(t *testing.T) { - { - Desc(t, "Valid request, no issue") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.OutGetDefault.Entry = &devDefaultEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.GetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - } - var wantRes = &core.GetDefaultDeviceRes{ - AppKey: st.OutGetDefault.Entry.AppKey[:], - } - - // Operate - res, err := h.GetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | Invalid AppEUI") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.OutGetDefault.Entry = &devDefaultEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.GetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateTokenBrokerReq - var wantRes = new(core.GetDefaultDeviceRes) - - // Operate - res, err := h.GetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Invalid request | Invalid token") - - // Build - br := mocks.NewAuthBrokerClient() - br.Failures["ValidateToken"] = errors.New(errors.Operational, "Mock Error") - st := NewMockDevStorage() - st.OutGetDefault.Entry = &devDefaultEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.GetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - } - var wantRes = new(core.GetDefaultDeviceRes) - - // Operate - res, err := h.GetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - { - Desc(t, "Invalid request | Default not found") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.Failures["getDefault"] = errors.New(errors.NotFound, "Mock Error") - st.OutGetDefault.Entry = &devDefaultEntry{ - AppKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.GetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrNotFound - var wantBrkCall = &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - } - var wantRes = new(core.GetDefaultDeviceRes) - - // Operate - res, err := h.GetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateToken.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } -} - -func TestSetDefault(t *testing.T) { - { - Desc(t, "Valid request, no issue") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.SetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Expect - var wantErr *string - var wantBrkCall = &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: "PrivateNetAddrAnnounce", - } - var wantRes = new(core.SetDefaultDeviceRes) - - // Operate - res, err := h.SetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | Invalid AppEUI") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.SetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{5, 6, 7, 8}, - AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateOTAABrokerReq - var wantRes = new(core.SetDefaultDeviceRes) - - // Operate - res, err := h.SetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | Invalid AppKey") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.SetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: []byte{1, 2, 3, 4}, - } - - // Expect - var wantErr = ErrStructural - var wantBrkCall *core.ValidateOTAABrokerReq - var wantRes = new(core.SetDefaultDeviceRes) - - // Operate - res, err := h.SetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | Invalid token") - - // Build - br := mocks.NewAuthBrokerClient() - br.Failures["ValidateOTAA"] = errors.New(errors.Operational, "Mock Error") - st := NewMockDevStorage() - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.SetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: "PrivateNetAddrAnnounce", - } - var wantRes = new(core.SetDefaultDeviceRes) - - // Operate - res, err := h.SetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } - - // -------------------- - - { - Desc(t, "Valid request | Storage error") - - // Build - br := mocks.NewAuthBrokerClient() - st := NewMockDevStorage() - st.Failures["setDefault"] = errors.New(errors.Operational, "Mock Error") - h := New( - Components{ - Ctx: GetLogger(t, "Handler"), - Broker: br, - DevStorage: st, - }, Options{ - PublicNetAddr: "NetAddr", - PrivateNetAddr: "PrivNetAddr", - PrivateNetAddrAnnounce: "PrivateNetAddrAnnounce", - }) - req := &core.SetDefaultDeviceReq{ - Token: "==OAuth==Token==", - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppKey: []byte{1, 2, 3, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 3, 2, 1}, - } - - // Expect - var wantErr = ErrOperational - var wantBrkCall = &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: "PrivateNetAddrAnnounce", - } - var wantRes = new(core.SetDefaultDeviceRes) - - // Operate - res, err := h.SetDefaultDevice(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantBrkCall, br.InValidateOTAA.Req, "Broker Calls") - Check(t, wantRes, res, "Handler responses") - } -} diff --git a/core/components/handler/handlerManager.go b/core/components/handler/handlerManager.go deleted file mode 100644 index 1777e1c82..000000000 --- a/core/components/handler/handlerManager.go +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "encoding/json" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/fields" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" -) - -func (h component) ListDevices(bctx context.Context, req *core.ListDevicesHandlerReq) (*core.ListDevicesHandlerRes, error) { - h.Ctx.Debug("Handle list devices request") - - // 1. Validate the request - if len(req.AppEUI) != 8 { - err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle list devices request") - return new(core.ListDevicesHandlerRes), err - } - - // 2. Validate token and retrieve devices from the storage - if _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{AppEUI: req.AppEUI, Token: req.Token}); err != nil { - h.Ctx.WithError(err).Debug("Unable to handle list devices request") - return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) - } - entries, err := h.DevStorage.readAll(req.AppEUI) - if err != nil { - h.Ctx.WithError(err).Debug("Unable to handle list devices request") - return new(core.ListDevicesHandlerRes), errors.New(errors.Operational, err) - } - - // 3. Build the reply, separate OTAA from ABP - var abp []*core.HandlerABPDevice - var otaa []*core.HandlerOTAADevice - for _, dev := range entries { - // WTF? - d := new(devEntry) - *d = dev - if dev.AppKey == nil { - abp = append(abp, &core.HandlerABPDevice{ - DevAddr: d.DevAddr, - NwkSKey: d.NwkSKey[:], - AppSKey: d.AppSKey[:], - FCntUp: d.FCntUp, - FCntDown: d.FCntDown, - Flags: d.Flags, - }) - } else { - otaa = append(otaa, &core.HandlerOTAADevice{ - DevEUI: d.DevEUI, - DevAddr: d.DevAddr, - NwkSKey: d.NwkSKey[:], - AppSKey: d.AppSKey[:], - AppKey: d.AppKey[:], - FCntUp: d.FCntUp, - FCntDown: d.FCntDown, - }) - } - } - - return &core.ListDevicesHandlerRes{ABP: abp, OTAA: otaa}, nil -} - -func (h component) UpsertABP(bctx context.Context, req *core.UpsertABPHandlerReq) (*core.UpsertABPHandlerRes, error) { - h.Ctx.Debug("Handle upsert ABP request") - - // 1. Validate the request - if len(req.AppEUI) != 8 || len(req.DevAddr) != 4 || len(req.NwkSKey) != 16 || len(req.AppSKey) != 16 { - err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle ABP request") - return new(core.UpsertABPHandlerRes), err - } - - // 2. Forward to the broker first -> The Broker also does the token verification - _, err := h.Broker.UpsertABP(context.Background(), &core.UpsertABPBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - DevAddr: req.DevAddr, - NwkSKey: req.NwkSKey, - NetAddress: h.PrivateNetAddrAnnounce, - Flags: req.Flags, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected ABP") - return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) - } - - // 3. Save the device in the storage - h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevAddr", req.DevAddr).Debug("Request accepted by broker. Registering device") - entry := devEntry{ - AppEUI: req.AppEUI, - DevEUI: append([]byte{0, 0, 0, 0}, req.DevAddr...), - DevAddr: req.DevAddr, - FCntDown: 0, - FCntUp: 0, - Flags: req.Flags, - } - copy(entry.NwkSKey[:], req.NwkSKey) - copy(entry.AppSKey[:], req.AppSKey) - if err = h.DevStorage.upsert(entry); err != nil { - h.Ctx.WithError(err).Debug("Error while trying to handle valid request") - return new(core.UpsertABPHandlerRes), errors.New(errors.Operational, err) - } - h.Processed.Remove(append([]byte{1}, append(entry.AppEUI, entry.DevEUI...)...)) - - return new(core.UpsertABPHandlerRes), nil -} - -func (h component) UpsertOTAA(bctx context.Context, req *core.UpsertOTAAHandlerReq) (*core.UpsertOTAAHandlerRes, error) { - h.Ctx.Debug("Handle upsert OTAA request") - - // 1. Validate the request - if len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.AppKey) != 16 { - err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle OTAA request") - return new(core.UpsertOTAAHandlerRes), err - } - - // 2. Notify the broker -> The Broker also does the token verification - _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ - Token: req.Token, - NetAddress: h.PrivateNetAddrAnnounce, - AppEUI: req.AppEUI, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected OTAA") - return new(core.UpsertOTAAHandlerRes), errors.New(errors.Operational, err) - } - - // 3. Save the device in the storage - h.Ctx.WithField("AppEUI", req.AppEUI).WithField("DevEUI", req.DevEUI).Debug("Request accepted by broker. Registering device") - var appKey [16]byte - copy(appKey[:], req.AppKey) - err = h.DevStorage.upsert(devEntry{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - AppKey: &appKey, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Error while trying to handle valid request") - return new(core.UpsertOTAAHandlerRes), err - } - - return new(core.UpsertOTAAHandlerRes), nil -} - -func (h component) GetDefaultDevice(bctx context.Context, req *core.GetDefaultDeviceReq) (*core.GetDefaultDeviceRes, error) { - h.Ctx.Debug("Handle get default device request") - - // 1. Validate the request - if len(req.AppEUI) != 8 { - err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle set default device request") - return new(core.GetDefaultDeviceRes), err - } - - // 2. Validate the token - _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected token") - return new(core.GetDefaultDeviceRes), errors.New(errors.Operational, err) - } - - // 3. Get default device entry from storage - entry, err := h.DevStorage.getDefault(req.AppEUI) - if err != nil { - h.Ctx.WithError(err).Debug("Error while trying to retrieve default device") - return new(core.GetDefaultDeviceRes), err - } - if entry == nil { - return new(core.GetDefaultDeviceRes), errors.New(errors.NotFound, "No default device found") - } - - return &core.GetDefaultDeviceRes{AppKey: entry.AppKey[:]}, nil -} - -func (h component) SetDefaultDevice(bctx context.Context, req *core.SetDefaultDeviceReq) (*core.SetDefaultDeviceRes, error) { - h.Ctx.Debug("Handle set default device request") - - // 1. Validate the request - if len(req.AppEUI) != 8 || len(req.AppKey) != 16 { - err := errors.New(errors.Structural, "Invalid request parameters") - h.Ctx.WithError(err).Debug("Unable to handle set default device request") - return new(core.SetDefaultDeviceRes), err - } - - // 2. Validate the token - _, err := h.Broker.ValidateOTAA(context.Background(), &core.ValidateOTAABrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - NetAddress: h.PrivateNetAddrAnnounce, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected token") - return new(core.SetDefaultDeviceRes), errors.New(errors.Operational, err) - } - - // 3. Set the default device in the storage - h.Ctx.WithField("AppEUI", req.AppEUI).Debug("Valid token. Registering default device") - var appKey [16]byte - copy(appKey[:], req.AppKey) - err = h.DevStorage.setDefault(req.AppEUI, &devDefaultEntry{ - AppKey: appKey, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Storage error") - return new(core.SetDefaultDeviceRes), errors.New(errors.Operational, err) - } - - return new(core.SetDefaultDeviceRes), nil -} - -func (h component) GetPayloadFunctions(ctx context.Context, req *core.GetPayloadFunctionsReq) (*core.GetPayloadFunctionsRes, error) { - res := new(core.GetPayloadFunctionsRes) - - _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected token") - return res, errors.New(errors.Operational, err) - } - - adapter, ok := h.AppAdapter.(fields.Adapter) - if !ok { - return res, errors.New(errors.Structural, "Invalid adapter") - } - - var appEUI types.AppEUI - appEUI.Unmarshal(req.AppEUI) - functions, err := adapter.Storage().GetFunctions(appEUI) - if err != nil { - return res, err - } - if functions == nil { - return res, errors.New(errors.Operational, "Not found") - } - - res.Decoder = functions.Decoder - res.Converter = functions.Converter - res.Validator = functions.Validator - return res, nil -} - -func (h component) SetPayloadFunctions(ctx context.Context, req *core.SetPayloadFunctionsReq) (*core.SetPayloadFunctionsRes, error) { - res := new(core.SetPayloadFunctionsRes) - - _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected token") - return res, errors.New(errors.Operational, err) - } - - adapter, ok := h.AppAdapter.(fields.Adapter) - if !ok { - return res, errors.New(errors.Structural, "Invalid adapter") - } - - var appEUI types.AppEUI - appEUI.Unmarshal(req.AppEUI) - err = adapter.Storage().SetFunctions(appEUI, &fields.Functions{ - Decoder: req.Decoder, - Converter: req.Converter, - Validator: req.Validator, - }) - if err != nil { - return res, err - } - - return res, nil -} - -func (h component) TestPayloadFunctions(ctx context.Context, req *core.TestPayloadFunctionsReq) (*core.TestPayloadFunctionsRes, error) { - res := new(core.TestPayloadFunctionsRes) - - _, err := h.Broker.ValidateToken(context.Background(), &core.ValidateTokenBrokerReq{ - Token: req.Token, - AppEUI: req.AppEUI, - }) - if err != nil { - h.Ctx.WithError(err).Debug("Broker rejected token") - return res, errors.New(errors.Operational, err) - } - - var appEUI types.AppEUI - appEUI.Unmarshal(req.AppEUI) - - functions := fields.Functions{ - Decoder: req.Decoder, - Converter: req.Converter, - Validator: req.Validator, - } - - fields, valid, err := functions.Process(req.Payload) - if err != nil { - return res, err - } - - buf, err := json.Marshal(fields) - if err != nil { - return res, err - } - - res.Valid = valid - res.Fields = string(buf) - return res, nil -} diff --git a/core/components/handler/handler_test.go b/core/components/handler/handler_test.go deleted file mode 100644 index 44e1a630e..000000000 --- a/core/components/handler/handler_test.go +++ /dev/null @@ -1,2454 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "fmt" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" - "golang.org/x/net/context" -) - -func TestHandleDataDown(t *testing.T) { - { - Desc(t, "Handle valid downlink") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataDownHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - Payload: []byte("TheThingsNetwork"), - TTL: "2h", - } - - // Expect - var wantError *string - var wantRes = new(core.DataDownHandlerRes) - var wantEntry = pktEntry{Payload: req.Payload} - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataDown(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry.Payload, pktStorage.InEnqueue.Entry.Payload, "Packet Entries") - } - - // -------------------- - - { - Desc(t, "Handle invalid downlink ~> Invalid Payload") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataDownHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - TTL: "2h", - Payload: nil, - } - - // Expect - var wantError = ErrStructural - var wantRes = new(core.DataDownHandlerRes) - var wantEntry pktEntry - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataDown(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") - } - - // -------------------- - - { - Desc(t, "Handle invalid downlink ~> Invalid AppEUI") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataDownHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - TTL: "2h", - Payload: []byte("TheThingsNetwork"), - } - - // Expect - var wantError = ErrStructural - var wantRes = new(core.DataDownHandlerRes) - var wantEntry pktEntry - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataDown(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") - } - - // -------------------- - - { - Desc(t, "Handle invalid downlink ~> Invalid DevEUI") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataDownHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, - TTL: "2h", - Payload: []byte("TheThingsNetwork"), - } - - // Expect - var wantError = ErrStructural - var wantRes = new(core.DataDownHandlerRes) - var wantEntry pktEntry - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataDown(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") - } - - // -------------------- - - { - Desc(t, "Handle invalid downlink ~> Invalid TTL") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataDownHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2, 2}, - TTL: "0s", - Payload: []byte("TheThingsNetwork"), - } - - // Expect - var wantError = ErrStructural - var wantRes = new(core.DataDownHandlerRes) - var wantEntry pktEntry - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataDown(context.Background(), req) - - // Check - CheckErrors(t, wantError, err) - Check(t, wantRes, res, "Data Down Handler Responses") - Check(t, wantEntry, pktStorage.InEnqueue.Entry, "Packet Entries") - } -} - -func TestHandleDataUp(t *testing.T) { - { - Desc(t, "Handle uplink, 1 packet | Unknown") - - // Build - devStorage := NewMockDevStorage() - devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataUpHandlerReq{ - Payload: []byte("Payload"), - Metadata: new(core.Metadata), - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: 14, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrNotFound - var wantRes = new(core.DataUpHandlerRes) - var wantData *core.DataAppReq - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, invalid Payload") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataUpHandlerReq{ - Payload: nil, - Metadata: new(core.Metadata), - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: 14, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataUpHandlerRes) - var wantData *core.DataAppReq - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, invalid Metadata") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataUpHandlerReq{ - Payload: []byte("Payload"), - Metadata: nil, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: 14, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataUpHandlerRes) - var wantData *core.DataAppReq - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, invalid DevEUI") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataUpHandlerReq{ - Payload: []byte("Payload"), - Metadata: new(core.Metadata), - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: nil, - FCnt: 14, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataUpHandlerRes) - var wantData *core.DataAppReq - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, invalid AppEUI") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - req := &core.DataUpHandlerReq{ - Payload: []byte("Payload"), - Metadata: new(core.Metadata), - AppEUI: []byte{1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: 14, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataUpHandlerRes) - var wantData *core.DataAppReq - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, 1 packet | No downlink") - - // Build - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr *string - var wantRes = new(core.DataUpHandlerRes) - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt = devStorage.OutRead.Entry.FCntDown - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "2 packets in a row, same device | No Downlink") - - // Build - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req1 := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - DutyRX1: uint32(dutycycle.StateWarning), - DutyRX2: uint32(dutycycle.StateWarning), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 10, - MType: uint32(lorawan.UnconfirmedDataUp), - } - req2 := &core.DataUpHandlerReq{ - Payload: req1.Payload, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - FCnt: req1.FCnt, - FPort: req1.FPort, - MType: req1.MType, - } - - // Expect - var wantErr1 *string - var wantErr2 *string - var wantRes1 = new(core.DataUpHandlerRes) - var wantRes2 = new(core.DataUpHandlerRes) - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req1.Metadata, req2.Metadata}, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - FPort: 10, - FCnt: 14, - } - var wantFCnt = devStorage.OutRead.Entry.FCntDown - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - - chack := make(chan bool) - go func() { - var ok bool - defer func(ok *bool) { chack <- *ok }(&ok) - res, err := handler.HandleDataUp(context.Background(), req1) - - // Check - CheckErrors(t, wantErr1, err) - Check(t, wantRes1, res, "Data Up Handler Responses") - ok = true - }() - - go func() { - <-time.After(time.Millisecond * 50) - var ok bool - defer func(ok *bool) { chack <- *ok }(&ok) - res, err := handler.HandleDataUp(context.Background(), req2) - - // Check - CheckErrors(t, wantErr2, err) - Check(t, wantRes2, res, "Data Up Handler Responses") - ok = true - }() - - // Check - ok1, ok2 := <-chack, <-chack - Check(t, true, ok1 && ok2, "Acknowledgements") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, 1 packet | one downlink ready") - - // Build - tmst := time.Now() - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr *string - encodedDown, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - false, - devAddr, - devStorage.OutRead.Entry.FCntDown+1, - pktStorage.OutDequeue.Entry.Payload, - ) - FatalUnless(t, err) - var wantRes = &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: devStorage.OutRead.Entry.DevAddr[:], - FCnt: devStorage.OutRead.Entry.FCntDown + 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: uint32(1), - FRMPayload: encodedDown, - }, - MIC: []byte{0, 0, 0, 0}, - }, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000000), - PayloadSize: 21, - Power: 14, - InvPolarity: true, - }, - } - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - // See Issue #87 - // { - // Desc(t, "Handle late uplink | No Downlink") - // - // // Build - // devStorage := NewMockDevStorage() - // devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - // devStorage.OutRead.Entry = devEntry{ - // DevAddr: devAddr[:], - // AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - // NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - // FCntDown: 3, - // } - // pktStorage := NewMockPktStorage() - // appAdapter := mocks.NewAppClient() - // broker := mocks.NewAuthBrokerClient() - // payload, fcnt := []byte("Payload"), uint32(14) - // encoded, err := lorawan.EncryptFRMPayload( - // devStorage.OutRead.Entry.AppSKey, - // true, - // devAddr, - // fcnt, - // payload, - // ) - // FatalUnless(t, err) - // req1 := &core.DataUpHandlerReq{ - // Payload: encoded, - // Metadata: &core.Metadata{ - // DataRate: "SF7BW125", - // DutyRX1: uint32(dutycycle.StateWarning), - // DutyRX2: uint32(dutycycle.StateWarning), - // Rssi: -20, - // Lsnr: 5.0, - // }, - // AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - // DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - // FCnt: fcnt, - // MType: uint32(lorawan.UnconfirmedDataUp), - // } - // req2 := &core.DataUpHandlerReq{ - // Payload: req1.Payload, - // Metadata: &core.Metadata{ - // DataRate: "SF7BW125", - // DutyRX1: uint32(dutycycle.StateAvailable), - // DutyRX2: uint32(dutycycle.StateAvailable), - // Rssi: -20, - // Lsnr: 5.0, - // }, - // AppEUI: req1.AppEUI, - // DevEUI: req1.DevEUI, - // FCnt: req1.FCnt, - // MType: req1.MType, - // } - // - // // Expect - // var wantErr1 *string - // var wantErr2 = ErrBehavioural - // var wantRes1 = new(core.DataUpHandlerRes) - // var wantRes2 = new(core.DataUpHandlerRes) - // var wantData = &core.DataAppReq{ - // Payload: payload, - // Metadata: []*core.Metadata{req1.Metadata}, - // AppEUI: req1.AppEUI, - // DevEUI: req1.DevEUI, - // } - // var wantFCnt = devStorage.OutRead.Entry.FCntDown - // - // // Operate - // handler := New(Components{ - // Ctx: GetLogger(t, "Handler"), - // Broker: broker, - // AppAdapter: appAdapter, - // DevStorage: devStorage, - // PktStorage: pktStorage, - // }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - // - // chack := make(chan bool) - // go func() { - // var ok bool - // defer func(ok *bool) { chack <- *ok }(&ok) - // res, err := handler.HandleDataUp(context.Background(), req1) - // - // // Check - // CheckErrors(t, wantErr1, err) - // Check(t, wantRes1, res, "Data Up Handler Responses") - // ok = true - // }() - // - // go func() { - // <-time.After(2 * bufferDelay) - // var ok bool - // defer func(ok *bool) { chack <- *ok }(&ok) - // res, err := handler.HandleDataUp(context.Background(), req2) - // - // // Check - // CheckErrors(t, wantErr2, err) - // Check(t, wantRes2, res, "Data Up Handler Responses") - // ok = true - // }() - // - // // Check - // ok1, ok2 := <-chack, <-chack - // Check(t, true, ok1 && ok2, "Acknowledgements") - // Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - // Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - // } - - // -------------------- - - { - Desc(t, "Handle uplink, 1 packet | AppAdapter fails") - - // Build - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - appAdapter.Failures["HandleData"] = fmt.Errorf("Mock Error") - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.DataUpHandlerRes) - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, 1 packet | PktStorage fails") - - // Build - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - pktStorage.Failures["dequeue"] = errors.New(errors.Operational, "Mock Error") - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.DataUpHandlerRes) - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt uint32 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle two successive uplink | no downlink") - - // Build - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload1, fcnt1 := []byte("Payload1"), uint32(14) - devAddr1, appSKey1 := lorawan.DevAddr([4]byte{1, 2, 3, 4}), [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} - payload2, fcnt2 := []byte("Payload2"), uint32(35346) - devAddr2, appSKey2 := lorawan.DevAddr([4]byte{4, 3, 2, 1}), [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1} - encoded1, err := lorawan.EncryptFRMPayload( - appSKey1, - true, - devAddr1, - fcnt1, - payload1, - ) - FatalUnless(t, err) - encoded2, err := lorawan.EncryptFRMPayload( - appSKey2, - true, - devAddr2, - fcnt2, - payload2, - ) - FatalUnless(t, err) - req1 := &core.DataUpHandlerReq{ - Payload: encoded1, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - DutyRX1: uint32(dutycycle.StateWarning), - DutyRX2: uint32(dutycycle.StateWarning), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt1, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - req2 := &core.DataUpHandlerReq{ - Payload: encoded2, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - FCnt: fcnt2, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr1 *string - var wantErr2 *string - var wantRes1 = new(core.DataUpHandlerRes) - var wantRes2 = new(core.DataUpHandlerRes) - var wantData1 = &core.DataAppReq{ - Payload: payload1, - Metadata: []*core.Metadata{req1.Metadata}, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantData2 = &core.DataAppReq{ - Payload: payload2, - Metadata: []*core.Metadata{req2.Metadata}, - AppEUI: req2.AppEUI, - DevEUI: req2.DevEUI, - FPort: 1, - FCnt: 35346, - } - var wantFCnt1 uint32 = 3 - var wantFCnt2 uint32 = 11 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr1[:], - AppSKey: appSKey1, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - res1, err1 := handler.HandleDataUp(context.Background(), req1) - - // Check - CheckErrors(t, wantErr1, err1) - Check(t, wantRes1, res1, "Data Up Handler Responses") - Check(t, wantData1, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt1, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - - // Operate - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr2[:], - AppSKey: appSKey2, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 11, - } - res2, err2 := handler.HandleDataUp(context.Background(), req2) - - // Check - CheckErrors(t, wantErr2, err2) - Check(t, wantRes2, res2, "Data Up Handler Responses") - Check(t, wantData2, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt2, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, 1 packet | one downlink ready | Only RX2 available") - - // Build - tmst := time.Now() - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateBlocked), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr *string - encodedDown, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - false, - devAddr, - devStorage.OutRead.Entry.FCntDown+1, - pktStorage.OutDequeue.Entry.Payload, - ) - FatalUnless(t, err) - var wantRes = &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: devStorage.OutRead.Entry.DevAddr[:], - FCnt: devStorage.OutRead.Entry.FCntDown + 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: uint32(1), - FRMPayload: encodedDown, - }, - MIC: []byte{0, 0, 0, 0}, - }, - Metadata: &core.Metadata{ - DataRate: "SF9BW125", - Frequency: 869.525, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(2*time.Second).Unix() * 1000000), - PayloadSize: 21, - Power: 27, - InvPolarity: true, - }, - } - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle uplink, 1 packet | one downlink ready | Update FCnt fails") - - // Build - tmst := time.Now() - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - devStorage.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - pktStorage := NewMockPktStorage() - pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateBlocked), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.UnconfirmedDataUp), - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.DataUpHandlerRes) - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt = devStorage.OutRead.Entry.FCntDown + 1 - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle confirmed uplink, 1 packet | one downlink ready") - - // Build - tmst := time.Now() - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - pktStorage.OutDequeue.Entry.Payload = []byte("Downlink") - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.ConfirmedDataUp), - } - - // Expect - var wantErr *string - encodedDown, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - false, - devAddr, - devStorage.OutRead.Entry.FCntDown+1, - pktStorage.OutDequeue.Entry.Payload, - ) - FatalUnless(t, err) - var wantRes = &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: devStorage.OutRead.Entry.DevAddr[:], - FCnt: devStorage.OutRead.Entry.FCntDown + 1, - FCtrl: &core.LoRaWANFCtrl{ - Ack: true, - }, - }, - FPort: uint32(1), - FRMPayload: encodedDown, - }, - MIC: []byte{0, 0, 0, 0}, - }, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000000), - PayloadSize: 21, - Power: 14, - InvPolarity: true, - }, - } - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - - // -------------------- - - { - Desc(t, "Handle confirmed uplink, 1 packet | no downlink") - - // Build - tmst := time.Now() - devAddr := lorawan.DevAddr([4]byte{3, 4, 2, 4}) - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - DevAddr: devAddr[:], - AppSKey: [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - NwkSKey: [16]byte{6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1}, - FCntDown: 3, - } - pktStorage := NewMockPktStorage() - pktStorage.Failures["dequeue"] = errors.New(errors.NotFound, "Mock Error") - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - payload, fcnt := []byte("Payload"), uint32(14) - encoded, err := lorawan.EncryptFRMPayload( - devStorage.OutRead.Entry.AppSKey, - true, - devAddr, - fcnt, - payload, - ) - FatalUnless(t, err) - req := &core.DataUpHandlerReq{ - Payload: encoded, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - FCnt: fcnt, - FPort: 1, - MType: uint32(lorawan.ConfirmedDataUp), - } - - // Expect - var wantErr *string - var wantRes = &core.DataUpHandlerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: devStorage.OutRead.Entry.DevAddr[:], - FCnt: devStorage.OutRead.Entry.FCntDown + 1, - FCtrl: &core.LoRaWANFCtrl{ - Ack: true, - }, - }, - FPort: uint32(1), - FRMPayload: nil, - }, - MIC: []byte{0, 0, 0, 0}, - }, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(time.Second).Unix() * 1000000), - PayloadSize: 13, - Power: 14, - InvPolarity: true, - }, - } - var wantData = &core.DataAppReq{ - Payload: payload, - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - FPort: 1, - FCnt: 14, - } - var wantFCnt = wantRes.Payload.MACPayload.FHDR.FCnt - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleDataUp(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Data Up Handler Responses") - Check(t, wantData, appAdapter.InHandleData.Req, "Data Application Requests") - Check(t, wantFCnt, devStorage.InUpsert.Entry.FCntDown, "Frame counters") - } - -} - -func TestHandleJoin(t *testing.T) { - { - Desc(t, "Handle valid join-request | get join-accept") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr *string - var wantRes = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding - NwkSKey: nil, // We'll assume it's correct if payload is okay - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000), - PayloadSize: 33, - Power: 14, - InvPolarity: true, - }, - } - var wantAppReq = &core.JoinAppReq{ - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") - Check(t, 16, len(res.NwkSKey), "Network session keys' length") - Check(t, 4, len(res.DevAddr), "Device addresses' length") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := &lorawan.PHYPayload{} - err = joinaccept.UnmarshalBinary(res.Payload.Payload) - CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) - CheckErrors(t, nil, err) - Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") - } - - // -------------------- - - { - Desc(t, "Handle valid join-request | get join-accept for RX2") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF9BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr *string - var wantRes = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding - NwkSKey: nil, // We'll assume it's correct if payload is okay - Metadata: &core.Metadata{ - DataRate: "SF12BW125", - Frequency: 869.525, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix()*1000000 + 1000000), - PayloadSize: 33, - Power: 27, - InvPolarity: true, - }, - } - var wantAppReq = &core.JoinAppReq{ - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") - Check(t, 16, len(res.NwkSKey), "Network session keys' length") - Check(t, 4, len(res.DevAddr), "Device addresses' length") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := &lorawan.PHYPayload{} - err = joinaccept.UnmarshalBinary(res.Payload.Payload) - CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) - CheckErrors(t, nil, err) - Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") - } - - // -------------------- - - { - Desc(t, "Handle valid join-request, fails to notify app.") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - appAdapter.Failures["HandleJoin"] = errors.New(errors.Operational, "Mock Error") - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr *string - var wantRes = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding - NwkSKey: nil, // We'll assume it's correct if payload is okay - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000), - PayloadSize: 33, - Power: 14, - InvPolarity: true, - }, - } - var wantAppReq = &core.JoinAppReq{ - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") - Check(t, 16, len(res.NwkSKey), "Network session keys' length") - Check(t, 4, len(res.DevAddr), "Device addresses' length") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := &lorawan.PHYPayload{} - err = joinaccept.UnmarshalBinary(res.Payload.Payload) - CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) - CheckErrors(t, nil, err) - Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") - } - - // -------------------- - - { - Desc(t, "Handle valid join-request, fails to store") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - devStorage.Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle valid join-request, no gateway available") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateBlocked), - DutyRX2: uint32(dutycycle.StateBlocked), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle invalid join request: invalid datarate") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "Not A DataRate", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle invalid join-request: lookup fails") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - MIC: []byte{1, 2, 3, 4}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - // Expect - var wantErr = ErrNotFound - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle valid join-request | create new device with default AppKey") - - // Build - tmst := time.Now() - appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6} - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{3, 3, 3, 3, 3, 3, 3, 3}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - devStorage.OutGetDefault.Entry = &devDefaultEntry{ - AppKey: appKey, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req.AppEUI) - copy(joinPayload.DevEUI[:], req.DevEUI) - copy(joinPayload.DevNonce[:], req.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(appKey)) - FatalUnless(t, err) - req.MIC = payload.MIC[:] - - // Expect - var wantErr *string - var wantRes = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding - NwkSKey: nil, // We'll assume it's correct if payload is okay - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - CodingRate: "4/5", - Timestamp: uint32(tmst.Add(5*time.Second).Unix() * 1000000), - PayloadSize: 33, - Power: 14, - InvPolarity: true, - }, - } - var wantAppReq = &core.JoinAppReq{ - Metadata: []*core.Metadata{req.Metadata}, - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes.Metadata, res.Metadata, "Join Handler Responses") - Check(t, 16, len(res.NwkSKey), "Network session keys' length") - Check(t, 4, len(res.DevAddr), "Device addresses' length") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - joinaccept := &lorawan.PHYPayload{} - err = joinaccept.UnmarshalBinary(res.Payload.Payload) - CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) - CheckErrors(t, nil, err) - Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") - Check(t, req.AppEUI, devStorage.InUpsert.Entry.AppEUI, "New Device's AppEUI") - Check(t, appKey, *devStorage.InUpsert.Entry.AppKey, "New Device's AppKey") - Check(t, req.DevEUI, devStorage.InUpsert.Entry.DevEUI, "New Device's DevEUI") - } - - // -------------------- - - { - Desc(t, "Handle invalid join-request: invalid devEUI") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle invalid join-request: invalid appEUI") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle invalid join-request: invalid devNonce") - - // Build - tmst := time.Now() - - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: nil, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // -------------------- - - { - Desc(t, "Handle invalid join-request: invalid Metadata") - - // Build - req := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: nil, - } - - devStorage := NewMockDevStorage() - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinHandlerRes) - var wantAppReq *core.JoinAppReq - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - res, err := handler.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Join Handler Responses") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - - // ------------------- - - { - Desc(t, "Handle valid join-request (2 packets)") - - // Build - tmst1 := time.Now() - tmst2 := time.Now().Add(42 * time.Millisecond) - - req1 := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF7BW125", - Frequency: 865.5, - Timestamp: uint32(tmst1.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateWarning), - DutyRX2: uint32(dutycycle.StateWarning), - Rssi: -20, - Lsnr: 5.0, - }, - } - - req2 := &core.JoinHandlerReq{ - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{14, 42}, - Metadata: &core.Metadata{ - DataRate: "SF8BW125", - Frequency: 867.234, - Timestamp: uint32(tmst2.Unix() * 1000000), - CodingRate: "4/5", - DutyRX1: uint32(dutycycle.StateAvailable), - DutyRX2: uint32(dutycycle.StateAvailable), - Rssi: -20, - Lsnr: 5.0, - }, - } - - devStorage := NewMockDevStorage() - devStorage.OutRead.Entry = devEntry{ - AppKey: &[16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - } - pktStorage := NewMockPktStorage() - appAdapter := mocks.NewAppClient() - broker := mocks.NewAuthBrokerClient() - - payload := &lorawan.PHYPayload{} - payload.MHDR = lorawan.MHDR{MType: lorawan.JoinRequest, Major: lorawan.LoRaWANR1} - joinPayload := lorawan.JoinRequestPayload{} - copy(joinPayload.AppEUI[:], req1.AppEUI) - copy(joinPayload.DevEUI[:], req1.DevEUI) - copy(joinPayload.DevNonce[:], req1.DevNonce) - payload.MACPayload = &joinPayload - err := payload.SetMIC(lorawan.AES128Key(*devStorage.OutRead.Entry.AppKey)) - FatalUnless(t, err) - req1.MIC = payload.MIC[:] - req2.MIC = payload.MIC[:] - - // Expect - var wantErr1 *string - var wantErr2 *string - var wantRes1 = new(core.JoinHandlerRes) - var wantRes2 = &core.JoinHandlerRes{ - Payload: &core.LoRaWANJoinAccept{}, // We'll check it by decoding - NwkSKey: nil, // We'll assume it's correct if payload is okay - Metadata: &core.Metadata{ - DataRate: "SF8BW125", - Frequency: 867.234, - CodingRate: "4/5", - Timestamp: uint32(tmst2.Add(5*time.Second).Unix() * 1000000), - PayloadSize: 33, - Power: 14, - InvPolarity: true, - }, - } - var wantAppReq = &core.JoinAppReq{ - Metadata: []*core.Metadata{req1.Metadata, req2.Metadata}, - AppEUI: req1.AppEUI, - DevEUI: req1.DevEUI, - } - - // Operate - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - Broker: broker, - AppAdapter: appAdapter, - DevStorage: devStorage, - PktStorage: pktStorage, - }, Options{PublicNetAddr: "localhost", PrivateNetAddr: "localhost"}) - - chack := make(chan bool) - go func() { - var ok bool - defer func(ok *bool) { chack <- *ok }(&ok) - res, err := handler.HandleJoin(context.Background(), req1) - - // Check - CheckErrors(t, wantErr1, err) - Check(t, wantRes1, res, "Data Up Handler Responses") - ok = true - }() - - go func() { - <-time.After(bufferDelay / 3) - var ok bool - defer func(ok *bool) { chack <- *ok }(&ok) - res, err := handler.HandleJoin(context.Background(), req2) - - // Check - CheckErrors(t, wantErr2, err) - Check(t, wantRes2.Metadata, res.Metadata, "Join Handler Responses") - Check(t, 16, len(res.NwkSKey), "Network session keys' length") - Check(t, 4, len(res.DevAddr), "Device addresses' length") - joinaccept := &lorawan.PHYPayload{} - err = joinaccept.UnmarshalBinary(res.Payload.Payload) - CheckErrors(t, nil, err) - err = joinaccept.DecryptJoinAcceptPayload(lorawan.AES128Key(*devStorage.InUpsert.Entry.AppKey)) - CheckErrors(t, nil, err) - Check(t, handler.(*component).Configuration.NetID, joinaccept.MACPayload.(*lorawan.JoinAcceptPayload).NetID, "Network IDs") - ok = true - }() - - // Check - ok1, ok2 := <-chack, <-chack - Check(t, true, ok1 && ok2, "Acknowledgements") - Check(t, wantAppReq, appAdapter.InHandleJoin.Req, "Join Application Requests") - } - -} - -func TestStart(t *testing.T) { - handler := New(Components{ - Ctx: GetLogger(t, "Handler"), - DevStorage: NewMockDevStorage(), - PktStorage: NewMockPktStorage(), - AppAdapter: mocks.NewAppClient(), - }, Options{PublicNetAddr: "localhost:8888", PrivateNetAddr: "localhost:8889"}) - - cherr := make(chan error) - go func() { - err := handler.Start() - cherr <- err - }() - - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 250): - } - CheckErrors(t, nil, err) -} diff --git a/core/components/handler/mocks_test.go b/core/components/handler/mocks_test.go deleted file mode 100644 index 22d4585c2..000000000 --- a/core/components/handler/mocks_test.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -// NOTE: All the code below could be generated - -// MockDevStorage mocks the DevStorage interface -type MockDevStorage struct { - Failures map[string]error - InRead struct { - AppEUI []byte - DevEUI []byte - } - OutRead struct { - Entry devEntry - } - InReadAll struct { - AppEUI []byte - } - OutReadAll struct { - Entries []devEntry - } - InUpsert struct { - Entry devEntry - } - InGetDefault struct { - AppEUI []byte - } - OutGetDefault struct { - Entry *devDefaultEntry - } - InSetDefault struct { - AppEUI []byte - Entry *devDefaultEntry - } - InDone struct { - Called bool - } -} - -// NewMockDevStorage creates a new MockDevStorage -func NewMockDevStorage() *MockDevStorage { - return &MockDevStorage{ - Failures: make(map[string]error), - } -} - -// read implements the DevStorage interface -func (m *MockDevStorage) read(appEUI []byte, devEUI []byte) (devEntry, error) { - m.InRead.AppEUI = appEUI - m.InRead.DevEUI = devEUI - return m.OutRead.Entry, m.Failures["read"] -} - -// readAll implements the DevStorage interface -func (m *MockDevStorage) readAll(appEUI []byte) ([]devEntry, error) { - m.InReadAll.AppEUI = appEUI - return m.OutReadAll.Entries, m.Failures["readAll"] -} - -// upsert implements the DevStorage interface -func (m *MockDevStorage) upsert(entry devEntry) error { - m.InUpsert.Entry = entry - return m.Failures["upsert"] -} - -// getDefault implements the DevStorage interface -func (m *MockDevStorage) getDefault(appEUI []byte) (*devDefaultEntry, error) { - m.InGetDefault.AppEUI = appEUI - return m.OutGetDefault.Entry, m.Failures["getDefault"] -} - -// setDefault implements the DevStorage Interface -func (m *MockDevStorage) setDefault(appEUI []byte, e *devDefaultEntry) error { - m.InSetDefault.Entry = e - return m.Failures["setDefault"] -} - -// done implements the DevStorage Interface -func (m *MockDevStorage) done() error { - m.InDone.Called = true - return m.Failures["done"] -} - -// MockPktStorage mocks the PktStorage interface -type MockPktStorage struct { - Failures map[string]error - InDequeue struct { - AppEUI []byte - DevEUI []byte - } - OutDequeue struct { - Entry pktEntry - } - InPeek struct { - AppEUI []byte - DevEUI []byte - } - OutPeek struct { - Entry pktEntry - } - InEnqueue struct { - Entry pktEntry - } - InDone struct { - Called bool - } -} - -// NewMockPktStorage creates a new MockPktStorage -func NewMockPktStorage() *MockPktStorage { - return &MockPktStorage{ - Failures: make(map[string]error), - } -} - -// done implements the PktStorage Interface -func (m *MockPktStorage) done() error { - m.InDone.Called = true - return m.Failures["done"] -} - -// enqueue implements the PktStorage interface -func (m *MockPktStorage) enqueue(entry pktEntry) error { - m.InEnqueue.Entry = entry - return m.Failures["enqueue"] -} - -// dequeue implements the PktStorage interface -func (m *MockPktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { - m.InDequeue.AppEUI = appEUI - m.InDequeue.DevEUI = devEUI - return m.OutDequeue.Entry, m.Failures["dequeue"] -} - -// peek implements the PktStorage interface -func (m *MockPktStorage) peek(appEUI []byte, devEUI []byte) (pktEntry, error) { - m.InPeek.AppEUI = appEUI - m.InPeek.DevEUI = devEUI - return m.OutPeek.Entry, m.Failures["peek"] -} diff --git a/core/components/handler/pktStorage.go b/core/components/handler/pktStorage.go deleted file mode 100644 index 9ad39bd48..000000000 --- a/core/components/handler/pktStorage.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "encoding" - "sync" - "time" - - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -// PktStorage gives a facade to manipulate the handler packets database -type PktStorage interface { - enqueue(entry pktEntry) error - dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) - peek(appEUI []byte, devEUI []byte) (pktEntry, error) - done() error -} - -const dbPackets = "packets" - -type pktStorage struct { - sync.RWMutex - size uint - db dbutil.Interface -} - -type pktEntry struct { - AppEUI []byte - DevEUI []byte - Payload []byte - TTL time.Time -} - -// NewPktStorage creates a new PktStorage -func NewPktStorage(name string, size uint) (PktStorage, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - return &pktStorage{db: itf, size: size}, nil -} - -func filterExpired(entries []pktEntry) []pktEntry { - var filtered []pktEntry - now := time.Now() - for _, e := range entries { - if e.TTL.After(now) { - filtered = append(filtered, e) - } - } - return filtered -} - -func pop(entries []pktEntry) (pktEntry, []encoding.BinaryMarshaler) { - head := entries[0] - var tail []encoding.BinaryMarshaler - for _, e := range entries[1:] { - tail = append(tail, e) - } - return head, tail -} - -// enqueue implements the PktStorage interface -func (s *pktStorage) enqueue(entry pktEntry) error { - s.Lock() - defer s.Unlock() - itf, err := s.db.Read(entry.DevEUI, &pktEntry{}, entry.AppEUI) - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - return err - } - var entries []pktEntry - if itf != nil { - entries = filterExpired(itf.([]pktEntry)) - } - if len(entries) >= int(s.size) { - _, tail := pop(entries) - return s.db.Update(entry.DevEUI, append(tail, entry), entry.AppEUI) - } - // NOTE: We append, even if there're still expired entries, we'll filter them - // during dequeuing - return s.db.Append(entry.DevEUI, []encoding.BinaryMarshaler{entry}, entry.AppEUI) -} - -// dequeue implements the PktStorage interface -func (s *pktStorage) dequeue(appEUI []byte, devEUI []byte) (pktEntry, error) { - s.Lock() - defer s.Unlock() - itf, err := s.db.Read(devEUI, &pktEntry{}, appEUI) - if err != nil { - return pktEntry{}, err - } - entries := itf.([]pktEntry) - filtered := filterExpired(entries) - - if len(filtered) < 1 { // No entry left, return NotFound - if len(entries) != len(filtered) { // Get rid of expired entries - var replaces []encoding.BinaryMarshaler - for _, e := range filtered { - replaces = append(replaces, e) - } - _ = s.db.Update(devEUI, replaces, appEUI) - } - return pktEntry{}, errors.New(errors.NotFound, "There's no available entry") - } - - // Otherwise dequeue the first one and send it - head, tail := pop(filtered) - if err := s.db.Update(devEUI, tail, appEUI); err != nil { - return pktEntry{}, err - } - return head, nil -} - -// peek implements the PktStorage interface -func (s *pktStorage) peek(appEUI []byte, devEUI []byte) (pktEntry, error) { - s.RLock() - defer s.RUnlock() - itf, err := s.db.Read(devEUI, &pktEntry{}, appEUI) - if err != nil { - return pktEntry{}, err - } - entries := filterExpired(itf.([]pktEntry)) - if len(entries) < 1 { - return pktEntry{}, errors.New(errors.NotFound, "There's no available entry") - } - return entries[0], nil -} - -// done implements the PktStorage interface -func (s *pktStorage) done() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e pktEntry) MarshalBinary() ([]byte, error) { - data, err := e.TTL.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - rw := readwriter.New(nil) - rw.Write(e.AppEUI) - rw.Write(e.DevEUI) - rw.Write(e.Payload) - rw.Write(data) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *pktEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { - e.AppEUI = make([]byte, len(data)) - copy(e.AppEUI, data) - }) - rw.Read(func(data []byte) { - e.DevEUI = make([]byte, len(data)) - copy(e.DevEUI, data) - }) - rw.Read(func(data []byte) { - e.Payload = make([]byte, len(data)) - copy(e.Payload, data) - }) - rw.TryRead(func(data []byte) error { return e.TTL.UnmarshalBinary(data) }) - return rw.Err() -} diff --git a/core/components/handler/pktStorage_test.go b/core/components/handler/pktStorage_test.go deleted file mode 100644 index 45a89c611..000000000 --- a/core/components/handler/pktStorage_test.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "os" - "path" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const pktDB = "TestPktStorage.db" - -func TestPullSize1(t *testing.T) { - var db PktStorage - defer func() { - os.Remove(path.Join(os.TempDir(), pktDB)) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - var err error - db, err = NewPktStorage(path.Join(os.TempDir(), pktDB), 1) - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Dequeue an empty area") - got, err := db.dequeue([]byte{1, 2}, []byte{3, 4}) - CheckErrors(t, ErrNotFound, err) - Check(t, pktEntry{}, got, "Packet entries") - } - - // ------------------ - - { - - Desc(t, "Peek an empty area") - got, err := db.peek([]byte{1, 2}, []byte{3, 4}) - CheckErrors(t, ErrNotFound, err) - Check(t, pktEntry{}, got, "Packet entries") - } - - // ------------------ - - { - Desc(t, "Queue / Dequeue an entry") - entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14, 42}} - err := db.enqueue(entry) - FatalUnless(t, err) - got, err := db.dequeue(entry.AppEUI, entry.DevEUI) - FatalUnless(t, err) - Check(t, entry, got, "Packet entries") - _, err = db.dequeue(entry.AppEUI, entry.DevEUI) - CheckErrors(t, ErrNotFound, err) - } - - // ------------------ - - { - Desc(t, "Queue / Peek an entry") - entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14, 42}} - err := db.enqueue(entry) - FatalUnless(t, err) - got, err := db.peek(entry.AppEUI, entry.DevEUI) - FatalUnless(t, err) - Check(t, entry, got, "Packet entries") - got, err = db.peek(entry.AppEUI, entry.DevEUI) - FatalUnless(t, err) - Check(t, entry, got, "Packet entries") - } - - // ------------------ - - { - Desc(t, "Queue on an existing entry") - entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14, 42}} - entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{1, 2, 3, 4}} - err := db.enqueue(entry1) - FatalUnless(t, err) - err = db.enqueue(entry2) - FatalUnless(t, err) - got, err := db.dequeue(entry1.AppEUI, entry1.DevEUI) - FatalUnless(t, err) - Check(t, entry2, got, "Packet entries") - } - - // ------------------ - - { - Desc(t, "Queue / Wait expiry / Dequeue") - entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now(), Payload: []byte{14, 42}} - err := db.enqueue(entry) - FatalUnless(t, err) - <-time.After(time.Millisecond * 10) - _, err = db.dequeue(entry.AppEUI, entry.DevEUI) - CheckErrors(t, ErrNotFound, err) - } - - // ------------------ - - { - Desc(t, "Queue / Wait expiry / Peek") - entry := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now(), Payload: []byte{14, 42}} - err := db.enqueue(entry) - FatalUnless(t, err) - <-time.After(time.Millisecond * 10) - _, err = db.peek(entry.AppEUI, entry.DevEUI) - CheckErrors(t, ErrNotFound, err) - } - - // ------------------ - - { - Desc(t, "Close") - err := db.done() - CheckErrors(t, nil, err) - } -} - -func TestPullSize2(t *testing.T) { - var db PktStorage - defer func() { - os.Remove(path.Join(os.TempDir(), pktDB)) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - var err error - db, err = NewPktStorage(path.Join(os.TempDir(), pktDB), 2) - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "Queue two, then Dequeue two") - entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{42}} - entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14}} - err := db.enqueue(entry1) - FatalUnless(t, err) - err = db.enqueue(entry2) - FatalUnless(t, err) - got1, err := db.dequeue(entry1.AppEUI, entry1.DevEUI) - FatalUnless(t, err) - got2, err := db.dequeue(entry2.AppEUI, entry2.DevEUI) - FatalUnless(t, err) - Check(t, entry1, got1, "Packet Entries") - Check(t, entry2, got2, "Packet Entries") - } - - // ------------------ - - { - Desc(t, "Queue two, wait first expires, then peek") - entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now(), Payload: []byte{42}} - entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14}} - err := db.enqueue(entry1) - FatalUnless(t, err) - err = db.enqueue(entry2) - FatalUnless(t, err) - <-time.After(time.Millisecond * 10) - got, err := db.peek(entry2.AppEUI, entry2.DevEUI) - CheckErrors(t, nil, err) - Check(t, entry2, got, "Packet Entries") - } - - // ------------------ - - { - Desc(t, "Queue three, dequeue then peek") - entry1 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{42}} - entry2 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{14}} - entry3 := pktEntry{AppEUI: []byte{1, 2}, DevEUI: []byte{3, 4}, TTL: time.Now().Add(time.Hour), Payload: []byte{6}} - err := db.enqueue(entry1) - FatalUnless(t, err) - err = db.enqueue(entry2) - FatalUnless(t, err) - err = db.enqueue(entry3) - FatalUnless(t, err) - got1, err := db.dequeue(entry1.AppEUI, entry1.DevEUI) - FatalUnless(t, err) - got2, err := db.peek(entry2.AppEUI, entry2.DevEUI) - FatalUnless(t, err) - Check(t, entry2, got1, "Packet Entries") - Check(t, entry3, got2, "Packet Entries") - } - - // ------------------ - - { - Desc(t, "Close") - err := db.done() - CheckErrors(t, nil, err) - } -} diff --git a/core/components/handler/queue.go b/core/components/handler/queue.go deleted file mode 100644 index 2dfb692c4..000000000 --- a/core/components/handler/queue.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "reflect" - "sync" -) - -type pQueue interface { - Put([]byte) - Contains([]byte) bool - Remove([]byte) -} - -type pq struct { - sync.RWMutex // Guars ids & values - ids []pQueueEntry - values map[[17]byte]pQueueEntry - size int - ticket int64 -} - -type pQueueEntry struct { - Ticket int64 - ID [17]byte - Value []byte -} - -func newPQueue(size uint) pQueue { - return &pq{ - size: int(size), - values: make(map[[17]byte]pQueueEntry), - } -} - -// Put Insert a new entry in the queue. If the queue is full, then the oldest entry is discarded -func (q *pq) Put(pid []byte) { - q.Lock() - id, v := q.fromPid(pid) - q.ticket++ - entry := pQueueEntry{Ticket: q.ticket, Value: v, ID: id} - q.values[id] = entry - if len(q.values) > q.size { - var i = 0 - for _, e := range q.ids { - if v, ok := q.values[e.ID]; ok && v.ID == e.ID && v.Ticket == e.Ticket { - delete(q.values, e.ID) - q.ids = q.ids[i+1:] - break - } - i++ - } - } - q.ids = append(q.ids, entry) - q.Unlock() -} - -// Remove discard an entry from the map -func (q *pq) Remove(pid []byte) { - q.Lock() - id, _ := q.fromPid(pid) - delete(q.values, id) - q.Unlock() -} - -// Contains check whether a value exist for the given id -func (q *pq) Contains(pid []byte) bool { - q.RLock() - defer q.RUnlock() - id, v := q.fromPid(pid) - if e, ok := q.values[id]; ok { - return reflect.DeepEqual(e.Value[:], v) - } - return false -} - -func (q *pq) fromPid(pid []byte) ([17]byte, []byte) { - var id [17]byte - copy(id[:], pid[:17]) - return id, pid[17:] -} diff --git a/core/components/handler/queue_test.go b/core/components/handler/queue_test.go deleted file mode 100644 index 1798dae62..000000000 --- a/core/components/handler/queue_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestQueue(t *testing.T) { - { - Desc(t, "Put then Contain") - id := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} - q := newPQueue(1) - q.Put(id) - Check(t, true, q.Contains(id), "Contains") - } - - // ---------- - - { - Desc(t, "Put three on size 2") - id1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} - id2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 99} - id3 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 99} - q := newPQueue(2) - q.Put(id1) - q.Put(id2) - q.Put(id3) - Check(t, false, q.Contains(id1), "Contains") - Check(t, true, q.Contains(id2), "Contains") - Check(t, true, q.Contains(id3), "Contains") - } - - // ---------- - - { - Desc(t, "Put -> Remove -> Contain") - id := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} - q := newPQueue(2) - q.Put(id) - q.Remove(id[:17]) - Check(t, false, q.Contains(id), "Contains") - } - - // ---------- - - { - Desc(t, "Size 2, Put 2, update first, put 1") - id1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} - id2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 99} - id3 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 99} - q := newPQueue(2) - q.Put(id1) - q.Put(id2) - q.Put(id1) - q.Put(id3) - Check(t, true, q.Contains(id1), "Contains") - Check(t, false, q.Contains(id2), "Contains") - Check(t, true, q.Contains(id3), "Contains") - } - - // ---------- - - { - Desc(t, "size 2 | Put 1 and update 1 several times, remove, then put 2") - id1 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 99} - id2 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 99} - id3 := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 18, 99} - q := newPQueue(1) - q.Put(id1) - q.Put(id1) - q.Put(id1) - q.Put(id1) - q.Put(id1) - q.Remove(id1) - q.Put(id2) - q.Put(id3) - Check(t, false, q.Contains(id1), "Contains") - Check(t, false, q.Contains(id2), "Contains") - Check(t, true, q.Contains(id3), "Contains") - } -} diff --git a/core/components/router/gtwStorage.go b/core/components/router/gtwStorage.go deleted file mode 100644 index 5f6bef144..000000000 --- a/core/components/router/gtwStorage.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "encoding" - - "github.com/TheThingsNetwork/ttn/core" - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -var dbGateways = []byte("gateways") - -// GtwStorage gives a facade to manipulate the router's gateways data -type GtwStorage interface { - read(gid []byte) (gtwEntry, error) - upsert(entry gtwEntry) error - done() error -} - -type gtwEntry struct { - GatewayID []byte - Metadata core.StatsMetadata -} - -type gtwStorage struct { - db dbutil.Interface -} - -// NewGtwStorage creates a new internal storage for the router -func NewGtwStorage(name string) (GtwStorage, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - return >wStorage{db: itf}, nil -} - -// read implements the router.GtwStorage interface { -func (s *gtwStorage) read(gid []byte) (gtwEntry, error) { - itf, err := s.db.Read(gid, >wEntry{}, dbGateways) - if err != nil { - return gtwEntry{}, err - } - return itf.([]gtwEntry)[0], nil // Storage guarantee at least one entry -} - -// upsert implements the router.GtwStorage interface -func (s *gtwStorage) upsert(entry gtwEntry) error { - return s.db.Update(entry.GatewayID, []encoding.BinaryMarshaler{entry}, dbGateways) -} - -// done implements the router.GtwStorage interface -func (s *gtwStorage) done() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e gtwEntry) MarshalBinary() ([]byte, error) { - data, err := e.Metadata.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - rw := readwriter.New(nil) - rw.Write(e.GatewayID) - rw.Write(data) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *gtwEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { - e.GatewayID = make([]byte, len(data)) - copy(e.GatewayID, data) - }) - rw.TryRead(func(data []byte) error { return e.Metadata.UnmarshalBinary(data) }) - return rw.Err() -} diff --git a/core/components/router/gtwStorage_test.go b/core/components/router/gtwStorage_test.go deleted file mode 100644 index 68a1d7eea..000000000 --- a/core/components/router/gtwStorage_test.go +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "os" - "path" - "testing" - - "github.com/TheThingsNetwork/ttn/core" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const gatewaysDB = "TestGtwStorage.db" - -func TestUpsertAndRead(t *testing.T) { - gatewaysDB := path.Join(os.TempDir(), gatewaysDB) - - defer func() { - os.Remove(gatewaysDB) - }() - - // ------------------ - - { - Desc(t, "Createa new storage") - db, err := NewGtwStorage(gatewaysDB) - CheckErrors(t, nil, err) - err = db.done() - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "upsert then read a device") - - // Build - db, _ := NewGtwStorage(gatewaysDB) - entry := gtwEntry{ - GatewayID: []byte{0, 0, 0, 1}, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: -14, - }, - } - - // Operate - err := db.upsert(entry) - FatalUnless(t, err) - gotGtwEntry, err := db.read(entry.GatewayID) - - // Expectations - wantGtwEntry := gtwEntry{ - GatewayID: entry.GatewayID, - Metadata: entry.Metadata, - } - - // Check - CheckErrors(t, nil, err) - Check(t, wantGtwEntry, gotGtwEntry, "Gateway Entries") - _ = db.done() - } - - // ------------------ - - { - Desc(t, "read non-existing gtwEntry") - - // Build - db, _ := NewGtwStorage(gatewaysDB) - entry := gtwEntry{ - GatewayID: []byte{0, 0, 0, 2}, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: -14, - }, - } - - // Operate - gotGtwEntry, err := db.read(entry.GatewayID) - - // Checks - CheckErrors(t, ErrNotFound, err) - Check(t, gtwEntry{}, gotGtwEntry, "Gateway Entries") - _ = db.done() - } - - // ------------------ - - { - Desc(t, "upsert on a closed database") - - // Build - db, _ := NewGtwStorage(gatewaysDB) - _ = db.done() - entry := gtwEntry{ - GatewayID: []byte{0, 0, 0, 5}, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: -14, - }, - } - - // Operate - err := db.upsert(entry) - - // Checks - CheckErrors(t, ErrOperational, err) - } - - // ------------------ - - { - Desc(t, "read on a closed database") - - // Build - db, _ := NewGtwStorage(gatewaysDB) - _ = db.done() - devAddr := []byte{0, 0, 0, 1} - - // Operate - gotGtwEntry, err := db.read(devAddr) - - // Checks - CheckErrors(t, ErrOperational, err) - Check(t, gtwEntry{}, gotGtwEntry, "Gateway Entries") - } - - // ------------------ - - { - Desc(t, "upsert two entries in a row") - - // Build - db, _ := NewGtwStorage(gatewaysDB) - entry1 := gtwEntry{ - GatewayID: []byte{0, 0, 0, 6}, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: -14, - }, - } - - entry2 := gtwEntry{ - GatewayID: []byte{0, 0, 0, 6}, - Metadata: core.StatsMetadata{ - Altitude: 42, - Longitude: -42, - }, - } - - // Operate - err := db.upsert(entry1) - FatalUnless(t, err) - err = db.upsert(entry2) - FatalUnless(t, err) - gotEntries, err := db.read(entry1.GatewayID) - FatalUnless(t, err) - - // Expectations - wantEntries := gtwEntry{ - GatewayID: entry1.GatewayID, - Metadata: entry2.Metadata, - } - - // Check - Check(t, wantEntries, gotEntries, "Gateway Entries") - _ = db.done() - } -} diff --git a/core/components/router/mockstorage_test.go b/core/components/router/mockstorage_test.go deleted file mode 100644 index d128c3a19..000000000 --- a/core/components/router/mockstorage_test.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -// MockBrkStorage mocks the router.BrkStorage interface -type MockBrkStorage struct { - Failures map[string]error - InRead struct { - DevAddr []byte - } - OutRead struct { - Entries []brkEntry - } - InCreate struct { - Entry brkEntry - } - InDone struct { - Called bool - } -} - -// NewMockBrkStorage creates a new mock BrkStorage -func NewMockBrkStorage() *MockBrkStorage { - return &MockBrkStorage{ - Failures: make(map[string]error), - } -} - -// read implements the router.BrkStorage interface -func (m *MockBrkStorage) read(devAddr []byte) ([]brkEntry, error) { - m.InRead.DevAddr = devAddr - return m.OutRead.Entries, m.Failures["read"] -} - -// create implements the router.BrkStorage interface -func (m *MockBrkStorage) create(entry brkEntry) error { - m.InCreate.Entry = entry - return m.Failures["create"] -} - -// done implements the router.BrkStorage interface -func (m *MockBrkStorage) done() error { - m.InDone.Called = true - return m.Failures["done"] -} - -// MockGtwStorage mocks the router.GtwStorage interface -type MockGtwStorage struct { - Failures map[string]error - InRead struct { - DevAddr []byte - } - OutRead struct { - Entry gtwEntry - } - InUpsert struct { - Entry gtwEntry - } - InDone struct { - Called bool - } -} - -// NewMockGtwStorage Upserts a new mock GtwStorage -func NewMockGtwStorage() *MockGtwStorage { - return &MockGtwStorage{ - Failures: make(map[string]error), - } -} - -// read implements the router.GtwStorage interface -func (m *MockGtwStorage) read(devAddr []byte) (gtwEntry, error) { - m.InRead.DevAddr = devAddr - return m.OutRead.Entry, m.Failures["read"] -} - -// Upsert implements the router.GtwStorage interface -func (m *MockGtwStorage) upsert(entry gtwEntry) error { - m.InUpsert.Entry = entry - return m.Failures["upsert"] -} - -// done implements the router.GtwStorage interface -func (m *MockGtwStorage) done() error { - m.InDone.Called = true - return m.Failures["done"] -} diff --git a/core/components/router/router.go b/core/components/router/router.go deleted file mode 100644 index ec9b07fb8..000000000 --- a/core/components/router/router.go +++ /dev/null @@ -1,393 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "fmt" - "net" - "strings" - "sync" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/apex/log" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -// Components defines a structure to make the instantiation easier to read -type Components struct { - DutyManager dutycycle.DutyManager - Brokers []core.BrokerClient - Ctx log.Interface - BrkStorage BrkStorage - GtwStorage GtwStorage -} - -// Options defines a structure to make the instantiation easier to read -type Options struct { - NetAddr string -} - -// component implements the core.RouterServer interface -type component struct { - Components - NetAddr string -} - -// Server defines the Router Server interface -type Server interface { - core.RouterServer - Start() error -} - -// New constructs a new router -func New(c Components, o Options) Server { - return component{Components: c, NetAddr: o.NetAddr} -} - -// Start actually runs the component and starts the rpc server -func (r component) Start() error { - conn, err := net.Listen("tcp", r.NetAddr) - if err != nil { - return errors.New(errors.Operational, err) - } - - server := grpc.NewServer() - core.RegisterRouterServer(server, r) - - if err := server.Serve(conn); err != nil { - return errors.New(errors.Operational, err) - } - return nil -} - -// HandleStats implements the core.RouterClient interface -func (r component) HandleStats(ctx context.Context, req *core.StatsReq) (*core.StatsRes, error) { - if req == nil { - return new(core.StatsRes), errors.New(errors.Structural, "Invalid nil stats request") - } - - if len(req.GatewayID) != 8 { - return new(core.StatsRes), errors.New(errors.Structural, "Invalid gateway identifier") - } - - if req.Metadata == nil { - return new(core.StatsRes), errors.New(errors.Structural, "Missing mandatory Metadata") - } - - stats.MarkMeter("router.stat.in") - return new(core.StatsRes), r.GtwStorage.upsert(gtwEntry{ - GatewayID: req.GatewayID, - Metadata: *req.Metadata, - }) -} - -// HandleJoin implements the core.RouterClient interface -func (r component) HandleJoin(_ context.Context, req *core.JoinRouterReq) (routerRes *core.JoinRouterRes, err error) { - ctx := r.Ctx.WithField("GatewayID", req.GatewayID) - stats.MarkMeter("router.join.in") - - if len(req.GatewayID) != 8 || len(req.AppEUI) != 8 || len(req.DevEUI) != 8 || len(req.DevNonce) != 2 || len(req.MIC) != 4 || req.Metadata == nil { - ctx.Debug("Invalid request. Parameters are incorrects") - return new(core.JoinRouterRes), errors.New(errors.Structural, "Invalid Request") - } - - // Update Metadata with Gateway infos - req.Metadata, err = r.injectMetadata(req.GatewayID, *req.Metadata) - if err != nil { - return new(core.JoinRouterRes), err - } - - ctx = ctx.WithFields(log.Fields{ - "AppEUI": req.AppEUI, - "DevEUI": req.DevEUI, - }) - - ctx.WithField("Metadata", req.Metadata).Debug("Inject Metadata") - - // Broadcast the join request - bpacket := &core.JoinBrokerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: req.Metadata, - } - response, err := r.send(bpacket, true, r.Brokers...) - if err != nil { - return new(core.JoinRouterRes), err - } - - // Update Gateway Duty cycle with response metadata - res := response.(*core.JoinBrokerRes) - if res == nil || res.Payload == nil { // No response - ctx.Debug("No join-accept received") - return new(core.JoinRouterRes), nil - } - ctx.Debug("Handle join-accept") - - if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { - return new(core.JoinRouterRes), err - } - return &core.JoinRouterRes{Payload: res.Payload, Metadata: res.Metadata}, nil -} - -// HandleData implements the core.RouterClient interface -func (r component) HandleData(_ context.Context, req *core.DataRouterReq) (*core.DataRouterRes, error) { - // Get some logs / analytics - ctx := r.Ctx.WithField("GatewayID", req.GatewayID) - stats.MarkMeter("router.uplink.in") - - // Validate coming data - _, _, fhdr, _, err := core.ValidateLoRaWANData(req.Payload) - if err != nil { - ctx.WithError(err).Debug("Invalid request payload") - return new(core.DataRouterRes), errors.New(errors.Structural, err) - } - if req.Metadata == nil { - ctx.Debug("Invalid request Metadata") - return new(core.DataRouterRes), errors.New(errors.Structural, "Missing mandatory Metadata") - } - if len(req.GatewayID) != 8 { - ctx.Debug("Invalid request GatewayID") - return new(core.DataRouterRes), errors.New(errors.Structural, "Invalid gatewayID") - } - - // Update Metadata with Gateway infos - req.Metadata, err = r.injectMetadata(req.GatewayID, *req.Metadata) - if err != nil { - return new(core.DataRouterRes), err - } - ctx.WithFields(log.Fields{ - "DevAddr": fhdr.DevAddr, - "Metadata": req.Metadata, - }).Debug("Inject Metadata") - - ctx = r.Ctx.WithField("DevAddr", fhdr.DevAddr) - - // Lookup for an existing broker - entries, err := r.BrkStorage.read(fhdr.DevAddr) - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - ctx.Warn("Database lookup failed") - return new(core.DataRouterRes), errors.New(errors.Operational, err) - } - shouldBroadcast := err != nil - - bpacket := &core.DataBrokerReq{Payload: req.Payload, Metadata: req.Metadata} - - // Send packet to broker(s) - var response interface{} - if shouldBroadcast { - // No Recipient available -> broadcast - stats.MarkMeter("router.broadcast") - ctx.Debug("Broadcast to brokers") - response, err = r.send(bpacket, true, r.Brokers...) - } else { - // Recipients are available - stats.MarkMeter("router.send") - var brokers []core.BrokerClient - ctx.Debug("Send to known brokers") - for _, e := range entries { - brokers = append(brokers, r.Brokers[e.BrokerIndex]) - } - response, err = r.send(bpacket, false, brokers...) - if err != nil && err.(errors.Failure).Nature == errors.NotFound { - ctx.Debug("Retry with broadcast") - // Might be a collision with the dev addr, we better broadcast - response, err = r.send(bpacket, true, r.Brokers...) - } - stats.MarkMeter("router.uplink.out") - } - - if err != nil { - switch err.(errors.Failure).Nature { - case errors.NotFound: - ctx.Debug("All brokers rejected") - stats.MarkMeter("router.uplink.negative_broker_response") - default: - ctx.WithError(err).Warn("Failed forward to broker") - stats.MarkMeter("router.uplink.bad_broker_response") - } - return new(core.DataRouterRes), err - } - - res := response.(*core.DataBrokerRes) - if res == nil || res.Payload == nil { // No response - ctx.Debug("No downlink response") - return new(core.DataRouterRes), nil - } - ctx.WithField("GatewayID", req.GatewayID).Debug("Handle downlink response") - - // Update Gateway Duty cycle with response metadata - if err := r.handleDown(req.GatewayID, res.Metadata); err != nil { - return new(core.DataRouterRes), err - } - - // Send Back the response - return &core.DataRouterRes{Payload: res.Payload, Metadata: res.Metadata}, nil -} - -func (r component) injectMetadata(gid []byte, metadata core.Metadata) (*core.Metadata, error) { - ctx := r.Ctx.WithField("GatewayID", gid) - - metadata.GatewayEUI = fmt.Sprintf("%X", gid) - metadata.ServerTime = time.Now().UTC().Format(time.RFC3339Nano) - - // Add Gateway location metadata - if entry, err := r.GtwStorage.read(gid); err == nil { - metadata.Latitude = entry.Metadata.Latitude - metadata.Longitude = entry.Metadata.Longitude - metadata.Altitude = entry.Metadata.Altitude - } - - // Add Gateway duty metadata - cycles, err := r.DutyManager.Lookup(gid) - if err != nil { - ctx.WithError(err).Debug("No duty-cycle metadata available") - cycles = make(dutycycle.Cycles) - } - - sb1, err := dutycycle.GetSubBand(float32(metadata.Frequency)) - if err != nil { - stats.MarkMeter("router.uplink.not_supported") - ctx.WithField("Frequency", metadata.Frequency).Debug("Unsupported frequency") - return nil, errors.New(errors.Structural, "Unsupported frequency") - } - - rx1, rx2 := dutycycle.StateFromDuty(cycles[sb1]), dutycycle.StateFromDuty(cycles[dutycycle.EuropeG3]) - metadata.DutyRX1, metadata.DutyRX2 = uint32(rx1), uint32(rx2) - ctx.WithField("Frequency", metadata.Frequency).WithField("Rx1", rx1).WithField("Rx2", rx2).Debug("Set duty cycles") - return &metadata, nil - -} - -func (r component) handleDown(gatewayID []byte, metadata *core.Metadata) error { - ctx := r.Ctx.WithField("GatewayID", gatewayID) - - // Update downlink metadata for the related gateway - if metadata == nil { - stats.MarkMeter("router.uplink.bad_broker_response") - ctx.Warn("Missing mandatory Metadata in response") - return errors.New(errors.Structural, "Missing mandatory Metadata in response") - } - freq := metadata.Frequency - datr := metadata.DataRate - codr := metadata.CodingRate - size := metadata.PayloadSize - if err := r.DutyManager.Update(gatewayID, freq, size, datr, codr); err != nil { - ctx.WithError(err).Debug("Unable to update DutyManager") - return errors.New(errors.Operational, err) - } - return nil -} - -func (r component) send(req interface{}, isBroadcast bool, brokers ...core.BrokerClient) (interface{}, error) { - // Define a more helpful context - nb := len(brokers) - stats.UpdateHistogram("router.send_recipients", int64(nb)) - - // Prepare ground for parrallel requests - cherr := make(chan error, nb) - chresp := make(chan struct { - Response interface{} - BrokerIndex uint16 - }, nb) - wg := sync.WaitGroup{} - wg.Add(nb) - - // Run each request - for i, broker := range brokers { - go func(index uint16, broker core.BrokerClient) { - defer wg.Done() - - // Send request - var resp interface{} - var err error - switch req.(type) { - case *core.DataBrokerReq: - resp, err = broker.HandleData(context.Background(), req.(*core.DataBrokerReq)) - case *core.JoinBrokerReq: - resp, err = broker.HandleJoin(context.Background(), req.(*core.JoinBrokerReq)) - default: - cherr <- errors.New(errors.Structural, "Unknown request type") - return - } - - // Handle error - if err != nil { - if strings.Contains(err.Error(), string(errors.NotFound)) { // FIXME Find a better way to analyze the error - cherr <- errors.New(errors.NotFound, "Broker not responsible for the node") - return - } - cherr <- errors.New(errors.Operational, err) - return - } - - // Transfer the response - chresp <- struct { - Response interface{} - BrokerIndex uint16 - }{resp, index} - }(uint16(i), broker) - } - - // Wait for each request to be done - stats.IncCounter("router.waiting_for_send") - wg.Wait() - stats.DecCounter("router.waiting_for_send") - close(cherr) - close(chresp) - - var errored uint8 - var notFound uint8 - for err := range cherr { - if err.(errors.Failure).Nature != errors.NotFound { - errored++ - r.Ctx.WithError(err).Warn("Unexpected response") - } else { - notFound++ - } - } - - // Collect response - if len(chresp) > 1 { - return nil, errors.New(errors.Behavioural, "Too many positive answers") - } - - if len(chresp) == 0 && errored > 0 { - return nil, errors.New(errors.Operational, "Unexpected response") - } - - if len(chresp) == 0 && notFound > 0 { - return nil, errors.New(errors.NotFound, "No available recipient found") - } - - if len(chresp) == 0 { - return nil, nil - } - - resp := <-chresp - // Save the broker for later if it was a broadcast - if isBroadcast { - var devAddr []byte - switch req.(type) { - case *core.DataBrokerReq: - devAddr = req.(*core.DataBrokerReq).Payload.MACPayload.FHDR.DevAddr - case *core.JoinBrokerReq: - devAddr = resp.Response.(*core.JoinBrokerRes).DevAddr - } - err := r.BrkStorage.create(brkEntry{ - DevAddr: devAddr, - BrokerIndex: resp.BrokerIndex, - }) - if err != nil { - r.Ctx.WithError(err).Warn("Failed to store accepted broker") - } - } - return resp.Response, nil -} diff --git a/core/components/router/router_test.go b/core/components/router/router_test.go deleted file mode 100644 index 7fb4d4332..000000000 --- a/core/components/router/router_test.go +++ /dev/null @@ -1,2129 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/mocks" - "github.com/TheThingsNetwork/ttn/utils/errors" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" - "golang.org/x/net/context" -) - -func TestHandleStats(t *testing.T) { - { - Desc(t, "Handle Valid Stats Request") - - // Build - components := Components{ - Ctx: GetLogger(t, "Router"), - BrkStorage: NewMockBrkStorage(), - GtwStorage: NewMockGtwStorage(), - } - r := New(components, Options{}) - req := &core.StatsReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata: &core.StatsMetadata{ - Altitude: -14, - Longitude: 43.333, - Latitude: -2.342, - }, - } - - // Expect - var wantErr *string - var wantRes = new(core.StatsRes) - var wantEntry = gtwEntry{ - GatewayID: req.GatewayID, - Metadata: *req.Metadata, - } - - // Operate - res, err := r.HandleStats(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Stats Responses") - Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") - } - - // -------------------- - - { - Desc(t, "Handle Nil Stats Requests") - - // Build - components := Components{ - Ctx: GetLogger(t, "Router"), - BrkStorage: NewMockBrkStorage(), - GtwStorage: NewMockGtwStorage(), - } - r := New(components, Options{}) - var req *core.StatsReq - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.StatsRes) - var wantEntry gtwEntry - - // Operate - res, err := r.HandleStats(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Stats Responses") - Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") - } - - // -------------------- - - { - Desc(t, "Handle Stats Request | Invalid GatewayID") - - // Build - components := Components{ - Ctx: GetLogger(t, "Router"), - BrkStorage: NewMockBrkStorage(), - GtwStorage: NewMockGtwStorage(), - } - r := New(components, Options{}) - req := &core.StatsReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, - Metadata: &core.StatsMetadata{ - Altitude: -14, - Longitude: 43.333, - Latitude: -2.342, - }, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.StatsRes) - var wantEntry gtwEntry - - // Operate - res, err := r.HandleStats(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Stats Responses") - Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") - } - - // -------------------- - - { - Desc(t, "Handle Stats Request | Nil Metadata") - - // Build - components := Components{ - Ctx: GetLogger(t, "Router"), - BrkStorage: NewMockBrkStorage(), - GtwStorage: NewMockGtwStorage(), - } - r := New(components, Options{}) - req := &core.StatsReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.StatsRes) - var wantEntry gtwEntry - - // Operate - res, err := r.HandleStats(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Stats Responses") - Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") - } - - // -------------------- - - { - Desc(t, "Handle Stats Request | Storage fails ") - - // Build - components := Components{ - Ctx: GetLogger(t, "Router"), - BrkStorage: NewMockBrkStorage(), - GtwStorage: NewMockGtwStorage(), - } - components.GtwStorage.(*MockGtwStorage).Failures["upsert"] = errors.New(errors.Operational, "Mock Error") - r := New(components, Options{}) - req := &core.StatsReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Metadata: &core.StatsMetadata{ - Altitude: -14, - Longitude: 43.333, - Latitude: -2.342, - }, - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.StatsRes) - var wantEntry = gtwEntry{ - GatewayID: req.GatewayID, - Metadata: *req.Metadata, - } - - // Operate - res, err := r.HandleStats(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Stats Responses") - Check(t, wantEntry, components.GtwStorage.(*MockGtwStorage).InUpsert.Entry, "Gateway Entries") - } -} - -func TestHandleData(t *testing.T) { - { - Desc(t, "Handle invalid uplink | Invalid DevAddr") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4, 5}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: new(core.Metadata), - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle invalid uplink | Invalid MIC") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3}, - }, - Metadata: new(core.Metadata), - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle invalid uplink | No Metadata") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: nil, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle invalid uplink | No Payload") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: nil, - Metadata: new(core.Metadata), - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle invalid uplink | Invalid GatewayID") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: new(core.Metadata), - GatewayID: []byte{1, 2, 3, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 1 broker ok | no downlink") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 1, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br, br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 2 brokers unknown | no downlink") - - // Build - dm := mocks.NewDutyManager() - br1 := mocks.NewAuthBrokerClient() - br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br1, br2}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 = 1 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br1.InHandleData.Req.Metadata.ServerTime = "" - br2.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") - Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 2 brokers unknown | no downlink | Fail to store") - - // Build - dm := mocks.NewDutyManager() - br1 := mocks.NewAuthBrokerClient() - br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - st.Failures["Store"] = errors.New(errors.Operational, "Mock Error") - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br1, br2}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 = 1 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br1.InHandleData.Req.Metadata.ServerTime = "" - br2.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") - Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | Fail Storage Lookup") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.Failures["read"] = errors.New(errors.Operational, "Mock Error") - - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br, br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | Fail DutyManager Lookup") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 1, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br, br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | Unreckognized frequency") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 1, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br, br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 12.3, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq *core.DataBrokerReq - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 2 brokers unknown | both errored") - - // Build - dm := mocks.NewDutyManager() - br1 := mocks.NewAuthBrokerClient() - br1.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewAuthBrokerClient() - br2.Failures["HandleData"] = errors.New(errors.Operational, "Mock Error") - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br1, br2}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br1.InHandleData.Req.Metadata.ServerTime = "" - br2.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") - Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 2 brokers unknown | both respond positively") - - // Build - dm := mocks.NewDutyManager() - br1 := mocks.NewAuthBrokerClient() - br2 := mocks.NewAuthBrokerClient() - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.Failures["read"] = errors.New(errors.NotFound, "Mock Error") - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br1, br2}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrBehavioural - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br1.InHandleData.Req.Metadata.ServerTime = "" - br2.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br1.InHandleData.Req, "Broker Data Requests") - Check(t, wantBrReq, br2.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 1 broker known, not ok | no downlink") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - br.Failures["HandleData"] = errors.New(errors.NotFound, "Mock Error") - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 1, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br, br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrNotFound - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 1 broker known ok | valid downlink") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - br.OutHandleData.Res = &core.DataBrokerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{5, 6, 7, 8}, - FCnt: 2, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 4, - FRMPayload: []byte{42, 42, 14, 14}, - }, - MIC: []byte{8, 7, 6, 5}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 0, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr *string - var wantRes = &core.DataRouterRes{ - Payload: br.OutHandleData.Res.Payload, - Metadata: br.OutHandleData.Res.Metadata, - } - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - var wantUpdateGtw = req.GatewayID - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 1 broker known ok | invalid downlink | no metadata") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - br.OutHandleData.Res = &core.DataBrokerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{5, 6, 7, 8}, - FCnt: 2, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 4, - FRMPayload: []byte{42, 42, 14, 14}, - }, - MIC: []byte{8, 7, 6, 5}, - }, - Metadata: nil, - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 0, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle valid uplink | 1 broker known ok | valid downlink | fail update Duty") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewAuthBrokerClient() - br.OutHandleData.Res = &core.DataBrokerRes{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataDown), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{5, 6, 7, 8}, - FCnt: 2, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 4, - FRMPayload: []byte{42, 42, 14, 14}, - }, - MIC: []byte{8, 7, 6, 5}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - st.OutRead.Entries = []brkEntry{ - { - BrokerIndex: 0, - until: time.Now().Add(time.Hour), - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.DataRouterReq{ - Payload: &core.LoRaWANData{ - MHDR: &core.LoRaWANMHDR{ - MType: uint32(lorawan.UnconfirmedDataUp), - Major: uint32(lorawan.LoRaWANR1), - }, - MACPayload: &core.LoRaWANMACPayload{ - FHDR: &core.LoRaWANFHDR{ - DevAddr: []byte{1, 2, 3, 4}, - FCnt: 1, - FCtrl: new(core.LoRaWANFCtrl), - }, - FPort: 1, - FRMPayload: []byte{14, 14, 42, 42}, - }, - MIC: []byte{4, 3, 2, 1}, - }, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.DataRouterRes) - var wantBrReq = &core.DataBrokerReq{ - Payload: req.Payload, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - var wantUpdateGtw = req.GatewayID - - // Operate - res, err := r.HandleData(context.Background(), req) - - // Ignore ServerTime - br.InHandleData.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Data Responses") - Check(t, wantBrReq, br.InHandleData.Req, "Broker Data Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } -} - -func TestHandleJoin(t *testing.T) { - { - Desc(t, "Handle valid join request | valid join response") - - // Build - dm := mocks.NewDutyManager() - br1 := mocks.NewAuthBrokerClient() - br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewAuthBrokerClient() - br2.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: &core.Metadata{}, - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br1, br2}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr *string - var wantRes = &core.JoinRouterRes{ - Payload: br2.OutHandleJoin.Res.Payload, - Metadata: br2.OutHandleJoin.Res.Metadata, - } - var wantBrReq = &core.JoinBrokerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 = 1 - var wantUpdateGtw = req.GatewayID - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Ignore ServerTime - br1.InHandleJoin.Req.Metadata.ServerTime = "" - br2.InHandleJoin.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br1.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantBrReq, br2.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle valid join request | invalid join response -> fails to handle down") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br1 := mocks.NewAuthBrokerClient() - br1.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - br2 := mocks.NewAuthBrokerClient() - br2.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: &core.Metadata{}, - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br1, br2}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr = ErrOperational - var wantRes = new(core.JoinRouterRes) - var wantBrReq = &core.JoinBrokerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 = 1 - var wantUpdateGtw = req.GatewayID - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Ignore ServerTime - br1.InHandleJoin.Req.Metadata.ServerTime = "" - br2.InHandleJoin.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br1.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantBrReq, br2.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle invalid join -> No Metadata") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewAuthBrokerClient() - br.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: nil, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinRouterRes) - var wantBrReq *core.JoinBrokerReq - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle invalid Join Request -> Invalid DevEUI") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewAuthBrokerClient() - br.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinRouterRes) - var wantBrReq *core.JoinBrokerReq - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle invalid Join Request -> Invalid AppEUI") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewAuthBrokerClient() - br.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinRouterRes) - var wantBrReq *core.JoinBrokerReq - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle invalid Join Request -> Invalid DevNonce") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewAuthBrokerClient() - br.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinRouterRes) - var wantBrReq *core.JoinBrokerReq - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle invalid Join Request -> Invalid GatewayID") - - // Build - dm := mocks.NewDutyManager() - dm.Failures["Update"] = errors.New(errors.Operational, "Mock Error") - br := mocks.NewAuthBrokerClient() - br.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: new(core.Metadata), - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: nil, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinRouterRes) - var wantBrReq *core.JoinBrokerReq - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle valid join request | fails to send, no broker") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - br.Failures["HandleJoin"] = errors.New(errors.NotFound, "Mock Error") - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 868.5, - }, - } - - // Expect - var wantErr = ErrNotFound - var wantRes = new(core.JoinRouterRes) - var wantBrReq = &core.JoinBrokerReq{ - AppEUI: req.AppEUI, - DevEUI: req.DevEUI, - DevNonce: req.DevNonce, - MIC: req.MIC, - Metadata: &core.Metadata{ - Altitude: gt.OutRead.Entry.Metadata.Altitude, - Longitude: gt.OutRead.Entry.Metadata.Longitude, - Latitude: gt.OutRead.Entry.Metadata.Latitude, - Frequency: req.Metadata.Frequency, - GatewayEUI: "0102030405060708", - }, - } - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Ignore ServerTime - br.InHandleJoin.Req.Metadata.ServerTime = "" - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - - // -------------------- - - { - Desc(t, "Handle invalid join request -> bad frequency") - - // Build - dm := mocks.NewDutyManager() - br := mocks.NewAuthBrokerClient() - br.OutHandleJoin.Res = &core.JoinBrokerRes{ - Payload: &core.LoRaWANJoinAccept{ - Payload: []byte{1, 2, 3, 4}, - }, - Metadata: &core.Metadata{}, - } - st := NewMockBrkStorage() - gt := NewMockGtwStorage() - - gid := []byte{1, 2, 3, 4, 5, 6, 7, 8} - gt.OutRead.Entry = gtwEntry{ - GatewayID: gid, - Metadata: core.StatsMetadata{ - Altitude: 14, - Longitude: 14.0, - Latitude: -14.0, - }, - } - r := New(Components{ - DutyManager: dm, - Brokers: []core.BrokerClient{br}, - Ctx: GetLogger(t, "Router"), - BrkStorage: st, - GtwStorage: gt, - }, Options{}) - req := &core.JoinRouterReq{ - GatewayID: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: []byte{1, 1, 1, 1, 1, 1, 1, 1}, - DevEUI: []byte{2, 2, 2, 2, 2, 2, 2, 2}, - DevNonce: []byte{3, 3}, - MIC: []byte{14, 14, 14, 14}, - Metadata: &core.Metadata{ - Frequency: 14.42, - }, - } - - // Expect - var wantErr = ErrStructural - var wantRes = new(core.JoinRouterRes) - var wantBrReq *core.JoinBrokerReq - var wantStore uint16 - var wantUpdateGtw []byte - - // Operate - res, err := r.HandleJoin(context.Background(), req) - - // Check - CheckErrors(t, wantErr, err) - Check(t, wantRes, res, "Router Join Responses") - Check(t, wantBrReq, br.InHandleJoin.Req, "Broker Join Requests") - Check(t, wantStore, st.InCreate.Entry.BrokerIndex, "Brokers stored") - Check(t, wantUpdateGtw, dm.InUpdate.ID, "Gateway updated") - } - -} - -func TestStart(t *testing.T) { - router := New(Components{ - Ctx: GetLogger(t, "Router"), - DutyManager: mocks.NewDutyManager(), - Brokers: []core.BrokerClient{mocks.NewAuthBrokerClient()}, - BrkStorage: NewMockBrkStorage(), - GtwStorage: NewMockGtwStorage(), - }, Options{NetAddr: "localhost:8886"}) - - cherr := make(chan error) - go func() { - err := router.Start() - cherr <- err - }() - - var err error - select { - case err = <-cherr: - case <-time.After(time.Millisecond * 250): - } - CheckErrors(t, nil, err) -} diff --git a/core/components/router/storage.go b/core/components/router/storage.go deleted file mode 100644 index 8d872573b..000000000 --- a/core/components/router/storage.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "encoding" - "encoding/binary" - "fmt" - "sync" - "time" - - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" -) - -var dbBrokers = []byte("brokers") - -// BrkStorage gives a facade to manipulate the router's brokers -type BrkStorage interface { - read(devAddr []byte) ([]brkEntry, error) - create(entry brkEntry) error - //remove(entry brkEntry) error - done() error -} - -type brkEntry struct { - DevAddr []byte - BrokerIndex uint16 - until time.Time -} - -type brkStorage struct { - sync.Mutex - ExpiryDelay time.Duration - db dbutil.Interface -} - -// NewBrkStorage creates a new internal storage for the router -func NewBrkStorage(name string, delay time.Duration) (BrkStorage, error) { - itf, err := dbutil.New(name) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - return &brkStorage{db: itf, ExpiryDelay: delay}, nil -} - -// read implements the router.BrkStorage interface -func (s *brkStorage) read(devAddr []byte) ([]brkEntry, error) { - return s._read(devAddr, true) -} - -// _read implements the router.BrkStorage interface logic but gives control over mutex to the caller -func (s *brkStorage) _read(devAddr []byte, shouldLock bool) ([]brkEntry, error) { - if shouldLock { - s.Lock() - defer s.Unlock() - } - itf, err := s.db.Read(devAddr, &brkEntry{}, dbBrokers) - if err != nil { - return nil, err - } - entries := itf.([]brkEntry) - - if s.ExpiryDelay != 0 { - // Get rid of expired entries - var newEntries []encoding.BinaryMarshaler - var filtered []brkEntry - for _, e := range entries { - if e.until.After(time.Now()) { - newEntry := new(brkEntry) - *newEntry = e - newEntries = append(newEntries, newEntry) - filtered = append(filtered, e) - } - } - // Replace filtered entries - if err := s.db.Update(devAddr, newEntries, dbBrokers); err != nil { - return nil, errors.New(errors.Operational, err) - } - entries = filtered - } - - if len(entries) == 0 { - return nil, errors.New(errors.NotFound, fmt.Sprintf("No entry for: %v", devAddr)) - } - return entries, nil -} - -// create implements the router.BrkStorage interface -func (s *brkStorage) create(entry brkEntry) error { - s.Lock() - defer s.Unlock() - - entries, err := s._read(entry.DevAddr, false) - if err != nil && err.(errors.Failure).Nature != errors.NotFound { - return err - } - - var updates []encoding.BinaryMarshaler - until, found := time.Now().Add(s.ExpiryDelay), false - for i, e := range entries { - if entry.BrokerIndex == e.BrokerIndex { - // Entry already there, just update the TTL - entries[i].until = until - found = true - } - updates = append(updates, e) - } - - if found { // The entry was already existing - return s.db.Update(entry.DevAddr, updates, dbBrokers) - } - - // Otherwise, we just happend it - entry.until = until - return s.db.Append(entry.DevAddr, []encoding.BinaryMarshaler{entry}, dbBrokers) -} - -// done implements the router.BrkStorage interface -func (s *brkStorage) done() error { - return s.db.Close() -} - -// MarshalBinary implements the encoding.BinaryMarshaler -func (e brkEntry) MarshalBinary() ([]byte, error) { - data, err := e.until.MarshalBinary() - if err != nil { - return nil, errors.New(errors.Structural, err) - } - rw := readwriter.New(nil) - rw.Write(e.BrokerIndex) - rw.Write(e.DevAddr) - rw.Write(data) - return rw.Bytes() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler -func (e *brkEntry) UnmarshalBinary(data []byte) error { - rw := readwriter.New(data) - rw.Read(func(data []byte) { e.BrokerIndex = binary.BigEndian.Uint16(data) }) - rw.Read(func(data []byte) { - e.DevAddr = make([]byte, len(data)) - copy(e.DevAddr, data) - }) - rw.TryRead(func(data []byte) error { return e.until.UnmarshalBinary(data) }) - return rw.Err() -} diff --git a/core/components/router/storage_test.go b/core/components/router/storage_test.go deleted file mode 100644 index f0b977f6a..000000000 --- a/core/components/router/storage_test.go +++ /dev/null @@ -1,242 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "os" - "path" - "testing" - "time" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const storageDB = "TestBrkStorage.db" - -func CheckEntries(t *testing.T, want []brkEntry, got []brkEntry) { - for i, w := range want { - if i >= len(got) { - Ko(t, "Didn't got enough entries: %v", got) - } - tmin := w.until.Add(-time.Second) - tmax := w.until.Add(time.Second) - if !tmin.Before(got[i].until) || !got[i].until.Before(tmax) { - Ko(t, "Unexpected expiry time.\nWant: %s\nGot: %s", w.until, got[i].until) - } - Check(t, w.BrokerIndex, got[i].BrokerIndex, "Brokers") - } -} - -func TestCreateAndRead(t *testing.T) { - storageDB := path.Join(os.TempDir(), storageDB) - - defer func() { - os.Remove(storageDB) - }() - - // ------------------ - - { - Desc(t, "Create a new storage") - db, err := NewBrkStorage(storageDB, time.Hour) - CheckErrors(t, nil, err) - err = db.done() - CheckErrors(t, nil, err) - } - - // ------------------ - - { - Desc(t, "create then read a device") - - // Build - db, _ := NewBrkStorage(storageDB, time.Hour) - entry := brkEntry{ - DevAddr: []byte{0, 0, 0, 1}, - BrokerIndex: 1, - } - - // Operate - err := db.create(entry) - FatalUnless(t, err) - gotbrkEntry, err := db.read(entry.DevAddr) - - // Expectations - wantbrkEntry := []brkEntry{ - { - DevAddr: entry.DevAddr, - BrokerIndex: entry.BrokerIndex, - until: time.Now().Add(time.Hour), - }, - } - - // Check - CheckErrors(t, nil, err) - CheckEntries(t, wantbrkEntry, gotbrkEntry) - _ = db.done() - } - - // ------------------ - - { - Desc(t, "read non-existing brkEntry") - - // Build - db, _ := NewBrkStorage(storageDB, time.Hour) - entry := brkEntry{ - DevAddr: []byte{0, 0, 0, 2}, - BrokerIndex: 1, - } - - // Operate - gotbrkEntry, err := db.read(entry.DevAddr) - - // Checks - CheckErrors(t, ErrNotFound, err) - CheckEntries(t, nil, gotbrkEntry) - _ = db.done() - } - - // ------------------ - - { - Desc(t, "read an expired brkEntry") - - // Build - db, _ := NewBrkStorage(storageDB, time.Millisecond*100) - entry := brkEntry{ - DevAddr: []byte{0, 0, 0, 3}, - BrokerIndex: 1, - } - - // Operate - _ = db.create(entry) - <-time.After(time.Millisecond * 200) - gotbrkEntry, err := db.read(entry.DevAddr) - - // Checks - CheckErrors(t, ErrNotFound, err) - CheckEntries(t, nil, gotbrkEntry) - _ = db.done() - } - - // ------------------ - - { - Desc(t, "create above an expired brkEntry") - - // Build - db, _ := NewBrkStorage(storageDB, time.Millisecond*100) - entry := brkEntry{ - DevAddr: []byte{0, 0, 0, 4}, - BrokerIndex: 1, - } - entry2 := brkEntry{ - DevAddr: []byte{0, 0, 0, 4}, - BrokerIndex: 12, - } - - // Operate - _ = db.create(entry) - <-time.After(time.Millisecond * 200) - err := db.create(entry2) - FatalUnless(t, err) - gotbrkEntry, err := db.read(entry.DevAddr) - - // Expectations - wantbrkEntry := []brkEntry{ - { - DevAddr: entry2.DevAddr, - BrokerIndex: entry2.BrokerIndex, - until: time.Now().Add(time.Millisecond * 100), - }, - } - - // Checks - CheckErrors(t, nil, err) - CheckEntries(t, wantbrkEntry, gotbrkEntry) - _ = db.done() - } - - // ------------------ - - { - Desc(t, "create on a closed database") - - // Build - db, _ := NewBrkStorage(storageDB, time.Hour) - _ = db.done() - entry := brkEntry{ - DevAddr: []byte{0, 0, 0, 5}, - } - - // Operate - err := db.create(entry) - - // Checks - CheckErrors(t, ErrOperational, err) - } - - // ------------------ - - { - Desc(t, "read on a closed database") - - // Build - db, _ := NewBrkStorage(storageDB, time.Hour) - _ = db.done() - devAddr := []byte{0, 0, 0, 1} - - // Operate - gotbrkEntry, err := db.read(devAddr) - - // Checks - CheckErrors(t, ErrOperational, err) - CheckEntries(t, nil, gotbrkEntry) - } - - // ------------------ - - { - Desc(t, "create two entries in a row") - - // Build - db, _ := NewBrkStorage(storageDB, time.Hour) - entry1 := brkEntry{ - DevAddr: []byte{0, 0, 0, 6}, - BrokerIndex: 14, - } - - entry2 := brkEntry{ - DevAddr: []byte{0, 0, 0, 6}, - BrokerIndex: 20, - } - - // Operate - err := db.create(entry1) - FatalUnless(t, err) - err = db.create(entry2) - FatalUnless(t, err) - gotEntries, err := db.read(entry1.DevAddr) - FatalUnless(t, err) - - // Expectations - wantEntries := []brkEntry{ - { - DevAddr: entry1.DevAddr, - BrokerIndex: entry1.BrokerIndex, - until: time.Now().Add(time.Hour), - }, - { - DevAddr: entry2.DevAddr, - BrokerIndex: entry2.BrokerIndex, - until: time.Now().Add(time.Hour), - }, - } - - // Check - CheckEntries(t, wantEntries, gotEntries) - _ = db.done() - } -} diff --git a/core/core.go b/core/core.go deleted file mode 100644 index 3e4c21ac0..000000000 --- a/core/core.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -//go:generate sh -c "find protos -name '*.proto' | xargs protoc --gofast_out=plugins=grpc:. -I=protos" - -package core - -import ( - "reflect" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/brocaar/lorawan" -) - -// ValidateLoRaWANData makes sure that the provided lorawanData reference is valid by checking if -// All required parameters are present and conform. -func ValidateLoRaWANData(data *LoRaWANData) (*LoRaWANMACPayload, *LoRaWANMHDR, *LoRaWANFHDR, *LoRaWANFCtrl, error) { - // Validation::0 -> Data isn't nil - if data == nil { - return nil, nil, nil, nil, errors.New(errors.Structural, "No data") - } - - // Validation::1 -> All required fields are there - if data.MHDR == nil || data.MACPayload == nil || data.MACPayload.FHDR == nil || data.MACPayload.FHDR.FCtrl == nil { - return nil, nil, nil, nil, errors.New(errors.Structural, "Invalid Payload Structure") - } - - // Validation::2 -> The MIC is 4-bytes long - if len(data.MIC) != 4 { - return nil, nil, nil, nil, errors.New(errors.Structural, "Invalid MIC") - } - - // Validation::3 -> Device address is 4-bytes long - if len(data.MACPayload.FHDR.DevAddr) != 4 { - return nil, nil, nil, nil, errors.New(errors.Structural, "Invalid Device Address") - } - - return data.MACPayload, data.MHDR, data.MACPayload.FHDR, data.MACPayload.FHDR.FCtrl, nil -} - -// NewLoRaWANData converts a LoRaWANData to a brocaar/lorawan.PHYPayload -func NewLoRaWANData(reqPayload *LoRaWANData, uplink bool) (lorawan.PHYPayload, error) { - mac, mhdr, fhdr, fctrl, err := ValidateLoRaWANData(reqPayload) - if err != nil { - return lorawan.PHYPayload{}, errors.New(errors.Structural, err) - } - - macpayload := &lorawan.MACPayload{} - macpayload.FPort = pointer.Uint8(uint8(mac.FPort)) - copy(macpayload.FHDR.DevAddr[:], fhdr.DevAddr) - macpayload.FHDR.FCnt = fhdr.FCnt - for _, data := range fhdr.FOpts { - cmd := new(lorawan.MACCommand) - if err := cmd.UnmarshalBinary(uplink, data); err == nil { // We ignore invalid commands - macpayload.FHDR.FOpts = append(macpayload.FHDR.FOpts, *cmd) - } - } - macpayload.FHDR.FCtrl.ADR = fctrl.ADR - macpayload.FHDR.FCtrl.ACK = fctrl.Ack - macpayload.FHDR.FCtrl.ADRACKReq = fctrl.ADRAckReq - macpayload.FHDR.FCtrl.FPending = fctrl.FPending - macpayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{ - Bytes: mac.FRMPayload, - }} - payload := &lorawan.PHYPayload{} - payload.MHDR.MType = lorawan.MType(mhdr.MType) - payload.MHDR.Major = lorawan.Major(mhdr.Major) - copy(payload.MIC[:], reqPayload.MIC) - payload.MACPayload = macpayload - - return *payload, nil -} - -// ProtoMetaToAppMeta converts a set of Metadata generate with Protobuf to a set of valid -// AppMetadata ready to be marshaled to json -func ProtoMetaToAppMeta(srcs ...*Metadata) []AppMetadata { - var dest []AppMetadata - - for _, src := range srcs { - if src == nil { - continue - } - to := new(AppMetadata) - v := reflect.ValueOf(src).Elem() - t := v.Type() - d := reflect.ValueOf(to).Elem() - - for i := 0; i < t.NumField(); i++ { - field := t.Field(i).Name - if d.FieldByName(field).CanSet() { - d.FieldByName(field).Set(v.Field(i)) - } - } - - dest = append(dest, *to) - } - - return dest -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (m StatsMetadata) MarshalBinary() ([]byte, error) { - return m.Marshal() -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (m *StatsMetadata) UnmarshalBinary(data []byte) error { - return m.Unmarshal(data) -} diff --git a/core/core.pb.go b/core/core.pb.go deleted file mode 100644 index 0cf07130f..000000000 --- a/core/core.pb.go +++ /dev/null @@ -1,1093 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: core.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type Metadata struct { - DutyRX1 uint32 `protobuf:"varint,1,opt,name=DutyRX1,json=dutyRX1,proto3" json:"DutyRX1,omitempty"` - DutyRX2 uint32 `protobuf:"varint,2,opt,name=DutyRX2,json=dutyRX2,proto3" json:"DutyRX2,omitempty"` - Frequency float32 `protobuf:"fixed32,3,opt,name=Frequency,json=frequency,proto3" json:"Frequency,omitempty"` - DataRate string `protobuf:"bytes,4,opt,name=DataRate,json=dataRate,proto3" json:"DataRate,omitempty"` - CodingRate string `protobuf:"bytes,5,opt,name=CodingRate,json=codingRate,proto3" json:"CodingRate,omitempty"` - Timestamp uint32 `protobuf:"varint,6,opt,name=Timestamp,json=timestamp,proto3" json:"Timestamp,omitempty"` - Rssi int32 `protobuf:"varint,7,opt,name=Rssi,json=rssi,proto3" json:"Rssi,omitempty"` - Lsnr float32 `protobuf:"fixed32,8,opt,name=Lsnr,json=lsnr,proto3" json:"Lsnr,omitempty"` - PayloadSize uint32 `protobuf:"varint,9,opt,name=PayloadSize,json=payloadSize,proto3" json:"PayloadSize,omitempty"` - Time string `protobuf:"bytes,10,opt,name=Time,json=time,proto3" json:"Time,omitempty"` - RFChain uint32 `protobuf:"varint,11,opt,name=RFChain,json=rFChain,proto3" json:"RFChain,omitempty"` - CRCStatus int32 `protobuf:"varint,12,opt,name=CRCStatus,json=cRCStatus,proto3" json:"CRCStatus,omitempty"` - Modulation string `protobuf:"bytes,13,opt,name=Modulation,json=modulation,proto3" json:"Modulation,omitempty"` - InvPolarity bool `protobuf:"varint,14,opt,name=InvPolarity,json=invPolarity,proto3" json:"InvPolarity,omitempty"` - Power uint32 `protobuf:"varint,15,opt,name=Power,json=power,proto3" json:"Power,omitempty"` - Channel uint32 `protobuf:"varint,16,opt,name=Channel,json=channel,proto3" json:"Channel,omitempty"` - GatewayEUI string `protobuf:"bytes,20,opt,name=GatewayEUI,json=gatewayEUI,proto3" json:"GatewayEUI,omitempty"` - Altitude int32 `protobuf:"varint,21,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` - Longitude float32 `protobuf:"fixed32,22,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` - Latitude float32 `protobuf:"fixed32,23,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` - ServerTime string `protobuf:"bytes,31,opt,name=ServerTime,json=serverTime,proto3" json:"ServerTime,omitempty"` -} - -func (m *Metadata) Reset() { *m = Metadata{} } -func (m *Metadata) String() string { return proto.CompactTextString(m) } -func (*Metadata) ProtoMessage() {} -func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorCore, []int{0} } - -type StatsMetadata struct { - Altitude int32 `protobuf:"varint,1,opt,name=Altitude,json=altitude,proto3" json:"Altitude,omitempty"` - Longitude float32 `protobuf:"fixed32,2,opt,name=Longitude,json=longitude,proto3" json:"Longitude,omitempty"` - Latitude float32 `protobuf:"fixed32,3,opt,name=Latitude,json=latitude,proto3" json:"Latitude,omitempty"` -} - -func (m *StatsMetadata) Reset() { *m = StatsMetadata{} } -func (m *StatsMetadata) String() string { return proto.CompactTextString(m) } -func (*StatsMetadata) ProtoMessage() {} -func (*StatsMetadata) Descriptor() ([]byte, []int) { return fileDescriptorCore, []int{1} } - -func init() { - proto.RegisterType((*Metadata)(nil), "core.Metadata") - proto.RegisterType((*StatsMetadata)(nil), "core.StatsMetadata") -} -func (m *Metadata) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *Metadata) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.DutyRX1 != 0 { - data[i] = 0x8 - i++ - i = encodeVarintCore(data, i, uint64(m.DutyRX1)) - } - if m.DutyRX2 != 0 { - data[i] = 0x10 - i++ - i = encodeVarintCore(data, i, uint64(m.DutyRX2)) - } - if m.Frequency != 0 { - data[i] = 0x1d - i++ - i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Frequency)))) - } - if len(m.DataRate) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintCore(data, i, uint64(len(m.DataRate))) - i += copy(data[i:], m.DataRate) - } - if len(m.CodingRate) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintCore(data, i, uint64(len(m.CodingRate))) - i += copy(data[i:], m.CodingRate) - } - if m.Timestamp != 0 { - data[i] = 0x30 - i++ - i = encodeVarintCore(data, i, uint64(m.Timestamp)) - } - if m.Rssi != 0 { - data[i] = 0x38 - i++ - i = encodeVarintCore(data, i, uint64(m.Rssi)) - } - if m.Lsnr != 0 { - data[i] = 0x45 - i++ - i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Lsnr)))) - } - if m.PayloadSize != 0 { - data[i] = 0x48 - i++ - i = encodeVarintCore(data, i, uint64(m.PayloadSize)) - } - if len(m.Time) > 0 { - data[i] = 0x52 - i++ - i = encodeVarintCore(data, i, uint64(len(m.Time))) - i += copy(data[i:], m.Time) - } - if m.RFChain != 0 { - data[i] = 0x58 - i++ - i = encodeVarintCore(data, i, uint64(m.RFChain)) - } - if m.CRCStatus != 0 { - data[i] = 0x60 - i++ - i = encodeVarintCore(data, i, uint64(m.CRCStatus)) - } - if len(m.Modulation) > 0 { - data[i] = 0x6a - i++ - i = encodeVarintCore(data, i, uint64(len(m.Modulation))) - i += copy(data[i:], m.Modulation) - } - if m.InvPolarity { - data[i] = 0x70 - i++ - if m.InvPolarity { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.Power != 0 { - data[i] = 0x78 - i++ - i = encodeVarintCore(data, i, uint64(m.Power)) - } - if m.Channel != 0 { - data[i] = 0x80 - i++ - data[i] = 0x1 - i++ - i = encodeVarintCore(data, i, uint64(m.Channel)) - } - if len(m.GatewayEUI) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintCore(data, i, uint64(len(m.GatewayEUI))) - i += copy(data[i:], m.GatewayEUI) - } - if m.Altitude != 0 { - data[i] = 0xa8 - i++ - data[i] = 0x1 - i++ - i = encodeVarintCore(data, i, uint64(m.Altitude)) - } - if m.Longitude != 0 { - data[i] = 0xb5 - i++ - data[i] = 0x1 - i++ - i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Longitude)))) - } - if m.Latitude != 0 { - data[i] = 0xbd - i++ - data[i] = 0x1 - i++ - i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Latitude)))) - } - if len(m.ServerTime) > 0 { - data[i] = 0xfa - i++ - data[i] = 0x1 - i++ - i = encodeVarintCore(data, i, uint64(len(m.ServerTime))) - i += copy(data[i:], m.ServerTime) - } - return i, nil -} - -func (m *StatsMetadata) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *StatsMetadata) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Altitude != 0 { - data[i] = 0x8 - i++ - i = encodeVarintCore(data, i, uint64(m.Altitude)) - } - if m.Longitude != 0 { - data[i] = 0x15 - i++ - i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Longitude)))) - } - if m.Latitude != 0 { - data[i] = 0x1d - i++ - i = encodeFixed32Core(data, i, uint32(math.Float32bits(float32(m.Latitude)))) - } - return i, nil -} - -func encodeFixed64Core(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Core(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintCore(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *Metadata) Size() (n int) { - var l int - _ = l - if m.DutyRX1 != 0 { - n += 1 + sovCore(uint64(m.DutyRX1)) - } - if m.DutyRX2 != 0 { - n += 1 + sovCore(uint64(m.DutyRX2)) - } - if m.Frequency != 0 { - n += 5 - } - l = len(m.DataRate) - if l > 0 { - n += 1 + l + sovCore(uint64(l)) - } - l = len(m.CodingRate) - if l > 0 { - n += 1 + l + sovCore(uint64(l)) - } - if m.Timestamp != 0 { - n += 1 + sovCore(uint64(m.Timestamp)) - } - if m.Rssi != 0 { - n += 1 + sovCore(uint64(m.Rssi)) - } - if m.Lsnr != 0 { - n += 5 - } - if m.PayloadSize != 0 { - n += 1 + sovCore(uint64(m.PayloadSize)) - } - l = len(m.Time) - if l > 0 { - n += 1 + l + sovCore(uint64(l)) - } - if m.RFChain != 0 { - n += 1 + sovCore(uint64(m.RFChain)) - } - if m.CRCStatus != 0 { - n += 1 + sovCore(uint64(m.CRCStatus)) - } - l = len(m.Modulation) - if l > 0 { - n += 1 + l + sovCore(uint64(l)) - } - if m.InvPolarity { - n += 2 - } - if m.Power != 0 { - n += 1 + sovCore(uint64(m.Power)) - } - if m.Channel != 0 { - n += 2 + sovCore(uint64(m.Channel)) - } - l = len(m.GatewayEUI) - if l > 0 { - n += 2 + l + sovCore(uint64(l)) - } - if m.Altitude != 0 { - n += 2 + sovCore(uint64(m.Altitude)) - } - if m.Longitude != 0 { - n += 6 - } - if m.Latitude != 0 { - n += 6 - } - l = len(m.ServerTime) - if l > 0 { - n += 2 + l + sovCore(uint64(l)) - } - return n -} - -func (m *StatsMetadata) Size() (n int) { - var l int - _ = l - if m.Altitude != 0 { - n += 1 + sovCore(uint64(m.Altitude)) - } - if m.Longitude != 0 { - n += 5 - } - if m.Latitude != 0 { - n += 5 - } - return n -} - -func sovCore(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozCore(x uint64) (n int) { - return sovCore(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *Metadata) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Metadata: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DutyRX1", wireType) - } - m.DutyRX1 = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.DutyRX1 |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DutyRX2", wireType) - } - m.DutyRX2 = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.DutyRX2 |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 5 { - return fmt.Errorf("proto: wrong wireType = %d for field Frequency", wireType) - } - var v uint32 - if (iNdEx + 4) > l { - return io.ErrUnexpectedEOF - } - iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 - m.Frequency = float32(math.Float32frombits(v)) - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCore - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DataRate = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCore - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.CodingRate = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) - } - m.Timestamp = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Timestamp |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Rssi", wireType) - } - m.Rssi = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Rssi |= (int32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 8: - if wireType != 5 { - return fmt.Errorf("proto: wrong wireType = %d for field Lsnr", wireType) - } - var v uint32 - if (iNdEx + 4) > l { - return io.ErrUnexpectedEOF - } - iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 - m.Lsnr = float32(math.Float32frombits(v)) - case 9: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field PayloadSize", wireType) - } - m.PayloadSize = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.PayloadSize |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 10: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCore - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Time = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RFChain", wireType) - } - m.RFChain = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.RFChain |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CRCStatus", wireType) - } - m.CRCStatus = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.CRCStatus |= (int32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 13: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCore - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Modulation = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 14: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field InvPolarity", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.InvPolarity = bool(v != 0) - case 15: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Power", wireType) - } - m.Power = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Power |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 16: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Channel", wireType) - } - m.Channel = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Channel |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 20: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEUI", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCore - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.GatewayEUI = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 21: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) - } - m.Altitude = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Altitude |= (int32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 22: - if wireType != 5 { - return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) - } - var v uint32 - if (iNdEx + 4) > l { - return io.ErrUnexpectedEOF - } - iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 - m.Longitude = float32(math.Float32frombits(v)) - case 23: - if wireType != 5 { - return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) - } - var v uint32 - if (iNdEx + 4) > l { - return io.ErrUnexpectedEOF - } - iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 - m.Latitude = float32(math.Float32frombits(v)) - case 31: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServerTime", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCore - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ServerTime = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipCore(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCore - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *StatsMetadata) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StatsMetadata: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StatsMetadata: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Altitude", wireType) - } - m.Altitude = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCore - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Altitude |= (int32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 5 { - return fmt.Errorf("proto: wrong wireType = %d for field Longitude", wireType) - } - var v uint32 - if (iNdEx + 4) > l { - return io.ErrUnexpectedEOF - } - iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 - m.Longitude = float32(math.Float32frombits(v)) - case 3: - if wireType != 5 { - return fmt.Errorf("proto: wrong wireType = %d for field Latitude", wireType) - } - var v uint32 - if (iNdEx + 4) > l { - return io.ErrUnexpectedEOF - } - iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 - m.Latitude = float32(math.Float32frombits(v)) - default: - iNdEx = preIndex - skippy, err := skipCore(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCore - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipCore(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCore - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCore - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCore - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthCore - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCore - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipCore(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthCore = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowCore = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorCore = []byte{ - // 434 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x7c, 0x52, 0xd1, 0x6e, 0xd3, 0x30, - 0x14, 0xa5, 0x5d, 0xba, 0x26, 0x2e, 0x85, 0xc9, 0x1a, 0x70, 0x85, 0x50, 0x99, 0xf6, 0xc4, 0x13, - 0x12, 0xe3, 0x0b, 0x20, 0x63, 0x68, 0x52, 0x27, 0x55, 0x2e, 0x48, 0xbc, 0x9a, 0xc4, 0x14, 0x4b, - 0xae, 0x1d, 0x1c, 0x67, 0x53, 0xf8, 0x12, 0x3e, 0x89, 0x47, 0x1e, 0xf8, 0x00, 0x04, 0x3f, 0x82, - 0xaf, 0x9d, 0xa4, 0xdb, 0x4b, 0x1f, 0x22, 0xf9, 0x9c, 0x93, 0x7b, 0xcf, 0xb9, 0xd7, 0x26, 0xa4, - 0x30, 0x56, 0xbc, 0xac, 0xac, 0x71, 0x86, 0x26, 0x78, 0x3e, 0xfd, 0x9d, 0x90, 0xf4, 0x4a, 0x38, - 0x5e, 0x72, 0xc7, 0x29, 0x90, 0xe9, 0x79, 0xe3, 0x5a, 0xf6, 0xe9, 0x15, 0x8c, 0x4e, 0x46, 0x2f, - 0xe6, 0x6c, 0x5a, 0x46, 0xb8, 0x53, 0xce, 0x60, 0x7c, 0x5b, 0x39, 0xa3, 0xcf, 0x48, 0x76, 0x61, - 0xc5, 0xb7, 0x46, 0xe8, 0xa2, 0x85, 0x03, 0xaf, 0x8d, 0x59, 0xf6, 0xa5, 0x27, 0xe8, 0x53, 0x92, - 0x9e, 0xfb, 0xce, 0x8c, 0x3b, 0x01, 0x89, 0x17, 0x33, 0x96, 0x96, 0x1d, 0xa6, 0x0b, 0x42, 0x72, - 0x53, 0x4a, 0xbd, 0x09, 0xea, 0x24, 0xa8, 0x3e, 0x60, 0xcf, 0x60, 0xe7, 0x0f, 0x72, 0x2b, 0x6a, - 0xc7, 0xb7, 0x15, 0x1c, 0x06, 0xd7, 0xcc, 0xf5, 0x04, 0xa5, 0x24, 0x61, 0x75, 0x2d, 0x61, 0xea, - 0x85, 0x09, 0x4b, 0xac, 0x3f, 0x23, 0xb7, 0xac, 0xb5, 0x85, 0x34, 0xc4, 0x48, 0x94, 0x3f, 0xd3, - 0x13, 0x32, 0x5b, 0xf1, 0x56, 0x19, 0x5e, 0xae, 0xe5, 0x77, 0x01, 0x59, 0xe8, 0x33, 0xab, 0x76, - 0x14, 0x56, 0xa1, 0x0f, 0x90, 0x90, 0x20, 0x41, 0x0b, 0x9c, 0x97, 0x5d, 0xe4, 0x5f, 0xb9, 0xd4, - 0x30, 0x8b, 0xf3, 0xda, 0x08, 0x31, 0x55, 0xce, 0xf2, 0xb5, 0xe3, 0xae, 0xa9, 0xe1, 0x7e, 0x30, - 0xcf, 0x8a, 0x9e, 0xc0, 0x99, 0xae, 0x4c, 0xd9, 0x28, 0xee, 0xa4, 0xd1, 0x30, 0x8f, 0x33, 0x6d, - 0x07, 0x06, 0xd3, 0x5c, 0xea, 0xeb, 0x95, 0x51, 0xdc, 0x4a, 0xd7, 0xc2, 0x03, 0xff, 0x43, 0xca, - 0x66, 0x72, 0x47, 0xd1, 0x63, 0x32, 0x59, 0x99, 0x1b, 0x61, 0xe1, 0x61, 0xf0, 0x9d, 0x54, 0x08, - 0x30, 0x8f, 0xb7, 0xd7, 0x5a, 0x28, 0x38, 0x8a, 0x79, 0x8a, 0x08, 0xd1, 0xf1, 0xbd, 0xdf, 0xd6, - 0x0d, 0x6f, 0xdf, 0x7d, 0xbc, 0x84, 0xe3, 0xe8, 0xb8, 0x19, 0x18, 0xbc, 0x81, 0x37, 0xca, 0x49, - 0xd7, 0x94, 0x02, 0x1e, 0x85, 0xb8, 0x29, 0xef, 0x30, 0xce, 0xb2, 0x34, 0x7a, 0x13, 0xc5, 0xc7, - 0xf1, 0xee, 0x54, 0x4f, 0x60, 0xe5, 0x92, 0x77, 0x95, 0x4f, 0x82, 0x98, 0xaa, 0x0e, 0xa3, 0xeb, - 0x5a, 0xd8, 0x6b, 0x61, 0xc3, 0xe6, 0x9e, 0x47, 0xd7, 0x7a, 0x60, 0x4e, 0x05, 0x99, 0xe3, 0x46, - 0xea, 0xe1, 0x69, 0xdd, 0x8e, 0x31, 0xda, 0x17, 0x63, 0xbc, 0x2f, 0xc6, 0xc1, 0xdd, 0x18, 0x6f, - 0x8f, 0x7e, 0xfe, 0x5d, 0x8c, 0x7e, 0xf9, 0xef, 0x8f, 0xff, 0x7e, 0xfc, 0x5b, 0xdc, 0xfb, 0x7c, - 0x18, 0x1e, 0xf7, 0xeb, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x23, 0x2c, 0xd8, 0xcc, 0xea, 0x02, - 0x00, 0x00, -} diff --git a/core/definitions.go b/core/definitions.go deleted file mode 100644 index 441a24f46..000000000 --- a/core/definitions.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -// DataUpAppReq represents the actual payloads sent to application on uplink -type DataUpAppReq struct { - Payload []byte `json:"payload"` - Fields map[string]interface{} `json:"fields,omitempty"` - FPort uint8 `json:"port,omitempty"` - FCnt uint32 `json:"counter"` - DevEUI string `json:"dev_eui"` - Metadata []AppMetadata `json:"metadata"` -} - -// OTAAAppReq are used to notify application of an accepted OTAA -type OTAAAppReq struct { - Metadata []AppMetadata `json:"metadata"` -} - -// AppMetadata represents gathered metadata that are sent to gateways -type AppMetadata struct { - Frequency float32 `json:"frequency"` - DataRate string `json:"datarate"` - CodingRate string `json:"codingrate"` - Timestamp uint32 `json:"gateway_timestamp"` - Time string `json:"gateway_time,omitempty"` - Channel uint32 `json:"channel"` - ServerTime string `json:"server_time"` - Rssi int32 `json:"rssi"` - Lsnr float32 `json:"lsnr"` - RFChain uint32 `json:"rfchain"` - CRCStatus int32 `json:"crc"` - Modulation string `json:"modulation"` - GatewayEUI string `json:"gateway_eui"` - Altitude int32 `json:"altitude"` - Longitude float32 `json:"longitude"` - Latitude float32 `json:"latitude"` -} - -// DataDownAppReq represents downlink messages sent by applications -type DataDownAppReq struct { - Payload []byte `json:"payload"` - FPort uint8 `json:"port,omitempty"` - TTL string `json:"ttl"` -} - -// ABPSubAppReq defines the shape of the request made by an application to the handler -type ABPSubAppReq struct { - DevAddr string `json:"devaddr"` - NwkSKey string `json:"nwkskey"` - AppSKey string `json:"appskey"` -} - -// AuthBrokerClient gathers both BrokerClient & BrokerManagerClient interfaces -type AuthBrokerClient interface { - BrokerClient - BrokerManagerClient -} - -// AuthHandlerClient gathers both HandlerClient & HandlerManagerClient interfaces -type AuthHandlerClient interface { - HandlerClient - HandlerManagerClient -} diff --git a/core/dutycycle/dutyManager.go b/core/dutycycle/dutyManager.go deleted file mode 100644 index f5941c496..000000000 --- a/core/dutycycle/dutyManager.go +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package dutycycle - -import ( - "encoding" - "encoding/json" - "fmt" - "math" - "regexp" - "strconv" - "sync" - "time" - - dbutil "github.com/TheThingsNetwork/ttn/core/storage" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// DutyManager provides an interface to manipulate and compute gateways duty-cycles. -type DutyManager interface { - Update(id []byte, freq float32, size uint32, datr string, codr string) error - Lookup(id []byte) (Cycles, error) - Close() error -} - -// Cycles gives a representation of sub-band usages -type Cycles map[subBand]uint32 - -type dutyManager struct { - sync.RWMutex - db dbutil.Interface - bucket string - CycleLength time.Duration // Duration upon which the duty-cycle is evaluated - MaxDutyCycle map[subBand]float32 // The percentage max duty cycle accepted for each sub-band -} - -// Available sub-bands -const ( - EuropeG subBand = "europe g" - EuropeG1 = "europe g1" - EuropeG2 = "europe g2" - EuropeG3 = "europe g3" - EuropeG4 = "europe g4" -) - -type subBand string - -// State Refers to an actual State of a transmitter -type State uint - -const ( - StateHighlyAvailable State = iota - StateAvailable - StateWarning - StateBlocked -) - -// Available regions for LoRaWAN -const ( - Europe region = iota - US - China -) - -type region byte - -var bucket = []byte("cycles") - -// GetSubBand returns the subband associated to a given frequency -func GetSubBand(freq float32) (subBand, error) { - // g 865.0 – 868.0 MHz 1% or LBT+AFA, 25 mW (=14dBm) - if freq >= 865.0 && freq < 868.0 { - return EuropeG, nil - } - - // g1 868.0 – 868.6 MHz 1% or LBT+AFA, 25 mW - if freq >= 868.0 && freq < 868.6 { - return EuropeG1, nil - } - - // g2 868.7 – 869.2 MHz 0.1% or LBT+AFA, 25 mW - if freq >= 868.7 && freq < 869.2 { - return EuropeG2, nil - } - - // g3 869.4 – 869.65 MHz 10% or LBT+AFA, 500 mW (=27dBm) - if freq >= 869.4 && freq < 869.65 { - return EuropeG3, nil - } - - // g4 869.7 – 870.0 MHz 1% or LBT+AFA, 25 mW - if freq >= 869.7 && freq < 870 { - return EuropeG4, nil - } - - return "", errors.New(errors.Structural, "Unknown frequency") -} - -// NewManager constructs a new gateway manager from -func NewManager(filepath string, cycleLength time.Duration, r region) (DutyManager, error) { - var maxDuty map[subBand]float32 - switch r { - case Europe: - maxDuty = map[subBand]float32{ - EuropeG: 0.01, - EuropeG1: 0.01, - EuropeG2: 0.001, - EuropeG3: 0.1, - EuropeG4: 0.01, - } - default: - return nil, errors.New(errors.Implementation, "Region not supported") - } - - if cycleLength == 0 { - return nil, errors.New(errors.Structural, "Invalid cycleLength. Should be > 0") - } - - // Try to start a database - db, err := dbutil.New(filepath) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - - return &dutyManager{ - db: db, - CycleLength: cycleLength, - MaxDutyCycle: maxDuty, - }, nil -} - -// Update update an entry with the corresponding time-on-air -// -// Datr represents a LoRaWAN data-rate indicator of the form SFxxBWyyy, -// where xx C [[7;12]] and yyy C { 125, 250, 500 } -// Codr represents a LoRaWAN code rate indicator fo the form 4/x with x C [[5;8]] -func (m *dutyManager) Update(id []byte, freq float32, size uint32, datr string, codr string) error { - sub, err := GetSubBand(freq) - if err != nil { - return err - } - - // Compute the ime-on-air - timeOnAir, err := computeTOA(size, datr, codr) - if err != nil { - return err - } - - // Lookup and update the entry - m.Lock() - defer m.Unlock() - itf, err := m.db.Read(id, &dutyEntry{}, bucket) - - var entry dutyEntry - if err == nil { - entry = itf.([]dutyEntry)[0] - } else if err.(errors.Failure).Nature == errors.NotFound { - entry = dutyEntry{ - Until: time.Unix(0, 0), - OnAir: make(map[subBand]time.Duration), - } - } else { - return err - } - - // If the previous cycle is done, we create a new one - if entry.Until.Before(time.Now()) { - entry.Until = time.Now().Add(m.CycleLength) - entry.OnAir[sub] = timeOnAir - } else { - entry.OnAir[sub] += timeOnAir - } - - return m.db.Update(id, []encoding.BinaryMarshaler{&entry}, bucket) -} - -// Lookup returns the current bandwidth usages for a set of subband -// -// The usage is an integer between 0 and 100 (maybe above 100 if the usage exceed the limitation). -// The closest to 0, the more usage we have -func (m *dutyManager) Lookup(id []byte) (Cycles, error) { - m.RLock() - defer m.RUnlock() - - // Lookup the entry - itf, err := m.db.Read(id, &dutyEntry{}, bucket) - if err != nil { - return nil, err - } - entry := itf.([]dutyEntry)[0] - - // For each sub-band, compute the remaining time-on-air available - cycles := make(map[subBand]uint32) - if entry.Until.After(time.Now()) { - for s, toa := range entry.OnAir { - // The actual duty cycle - dutyCycle := float32(toa.Nanoseconds()) / float32(m.CycleLength.Nanoseconds()) - // Now, how full are we comparing to the limitation, in percent - cycles[s] = uint32(100 * dutyCycle / m.MaxDutyCycle[s]) - } - } - - return cycles, nil -} - -// Close releases the database access -func (m *dutyManager) Close() error { - return m.db.Close() -} - -// computeTOA computes the time-on-air given a size in byte, a LoRaWAN datr identifier, an LoRa Codr -// identifier. -func computeTOA(size uint32, datr string, codr string) (time.Duration, error) { - // Ensure the datr and codr are correct - var rc float64 - switch codr { - case "4/5": - rc = 4.0 / 5.0 - case "4/6": - rc = 4.0 / 6.0 - case "4/7": - rc = 4.0 / 7.0 - case "4/8": - rc = 4.0 / 8.0 - default: - return 0, errors.New(errors.Structural, "Invalid Codr") - } - - sf, bw, err := ParseDatr(datr) - if err != nil { - return 0, err - } - - // Additional variables needed to compute times on air - s := float64(size) - var de float64 - if bw == 125 && (sf == 11 || sf == 12) { - de = 1.0 - } - - // Compute toa, Page 7: http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf - payloadNb := 8.0 + math.Max(0, 4*math.Ceil((2*s-sf-6)/(sf-2*de))/rc) - timeOnAir := (payloadNb + 12.25) * math.Pow(2, sf) / bw // in ms - - return time.ParseDuration(fmt.Sprintf("%fms", timeOnAir)) -} - -// ParseDatr extract the spread factor and the bandwidth from a DataRate identifier -func ParseDatr(datr string) (float64, float64, error) { - re := regexp.MustCompile("^SF(7|8|9|10|11|12)BW(125|250|500)$") - matches := re.FindStringSubmatch(datr) - - if len(matches) != 3 { - return 0, 0, errors.New(errors.Structural, "Invalid Datr") - } - - sf, _ := strconv.ParseFloat(matches[1], 64) - bw, _ := strconv.ParseFloat(matches[2], 64) - - return sf, bw, nil -} - -// StateFromDuty retrieve the associated transmitter state from a duty value -func StateFromDuty(duty uint32) State { - if duty >= 100 { - return StateBlocked - } - - if duty > 85 { - return StateWarning - } - - if duty > 30 { - return StateAvailable - } - - return StateHighlyAvailable - -} - -type dutyEntry struct { - Until time.Time `json:"until"` - OnAir map[subBand]time.Duration `json:"on_air"` -} - -// MarshalBinary implements the encoding.BinaryMarshaler interface -func (e dutyEntry) MarshalBinary() ([]byte, error) { - data, err := json.Marshal(e) - if err != nil { - return nil, errors.New(errors.Structural, err) - } - return data, nil -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface -func (e *dutyEntry) UnmarshalBinary(data []byte) error { - if err := json.Unmarshal(data, e); err != nil { - return errors.New(errors.Structural, err) - } - return nil -} diff --git a/core/dutycycle/dutyManager_test.go b/core/dutycycle/dutyManager_test.go deleted file mode 100644 index 36da0140c..000000000 --- a/core/dutycycle/dutyManager_test.go +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package dutycycle - -import ( - "os" - "path" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -var dutyManagerDB = path.Join(os.TempDir(), "TestDutyCycleStorage.db") - -func TestGetSubBand(t *testing.T) { - { - Desc(t, "Test EuropeG") - sb, err := GetSubBand(867.127) - CheckErrors(t, nil, err) - CheckSubBands(t, EuropeG, sb) - } - - // -------------------- - - { - Desc(t, "Test EuropeG1") - sb, err := GetSubBand(868.38) - CheckErrors(t, nil, err) - CheckSubBands(t, EuropeG1, sb) - } - - // -------------------- - - { - Desc(t, "Test EuropeG2") - sb, err := GetSubBand(868.865) - CheckErrors(t, nil, err) - CheckSubBands(t, EuropeG2, sb) - } - - // -------------------- - - { - Desc(t, "Test EuropeG3") - sb, err := GetSubBand(869.567) - CheckErrors(t, nil, err) - CheckSubBands(t, EuropeG3, sb) - } - - // -------------------- - - { - Desc(t, "Test EuropeG4") - sb, err := GetSubBand(869.856) - CheckErrors(t, nil, err) - CheckSubBands(t, EuropeG4, sb) - } - - // -------------------- - - { - Desc(t, "Test Unknown") - sb, err := GetSubBand(433.5) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - CheckSubBands(t, "", sb) - } -} - -func TestNewManager(t *testing.T) { - defer func() { - os.Remove(dutyManagerDB) - }() - - { - Desc(t, "Europe with valid cycleLength") - m, err := NewManager(dutyManagerDB, time.Minute, Europe) - CheckErrors(t, nil, err) - err = m.Close() - CheckErrors(t, nil, err) - } - - // -------------------- - - { - Desc(t, "Europe with invalid cycleLength") - _, err := NewManager(dutyManagerDB, 0, Europe) - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - // -------------------- - - { - Desc(t, "Not europe with valid cycleLength") - _, err := NewManager(dutyManagerDB, time.Minute, China) - CheckErrors(t, pointer.String(string(errors.Implementation)), err) - } -} - -func TestUpdateAndLookup(t *testing.T) { - defer func() { - os.Remove(dutyManagerDB) - }() - - { - Desc(t, "Update unsupported frequency") - - // Build - m, _ := NewManager(dutyManagerDB, time.Minute, Europe) - - // Operate - err := m.Update([]byte{1, 2, 3}, 433.65, 100, "SF8BW125", "4/5") - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - - // Clean - m.Close() - } - - // -------------------- - - { - Desc(t, "Update invalid datr") - - // Build - m, _ := NewManager(dutyManagerDB, time.Minute, Europe) - - // Operate - err := m.Update([]byte{1, 2, 3}, 868.1, 100, "SF3BW125", "4/5") - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - - // Clean - m.Close() - } - - // -------------------- - - { - Desc(t, "Update invalid codr") - - // Build - m, _ := NewManager(dutyManagerDB, time.Minute, Europe) - - // Operate - err := m.Update([]byte{1, 2, 3}, 869.5, 100, "SF8BW125", "14") - - // Check - CheckErrors(t, pointer.String(string(errors.Structural)), err) - - // Clean - m.Close() - } - - // -------------------- - - { - Desc(t, "Update once then lookup") - - // Build - m, _ := NewManager(dutyManagerDB, time.Minute, Europe) - - // Operate - err := m.Update([]byte{1, 2, 3}, 868.5, 14, "SF8BW125", "4/5") - CheckErrors(t, nil, err) - bands, err := m.Lookup([]byte{1, 2, 3}) - - // Expectation - want := map[subBand]uint32{ - EuropeG1: 10, - } - - // Check - CheckErrors(t, nil, err) - CheckUsages(t, want, bands) - - // Clean - m.Close() - } - - // -------------------- - - { - Desc(t, "Update several then lookup") - - // Build - m, _ := NewManager(dutyManagerDB, time.Minute, Europe) - - // Operate - err := m.Update([]byte{4, 5, 6}, 868.523, 14, "SF8BW125", "4/5") - CheckErrors(t, nil, err) - err = m.Update([]byte{4, 5, 6}, 868.123, 42, "SF9BW125", "4/5") - CheckErrors(t, nil, err) - err = m.Update([]byte{4, 5, 6}, 867.785, 42, "SF8BW125", "4/6") - CheckErrors(t, nil, err) - bands, err := m.Lookup([]byte{4, 5, 6}) - - // Expectation - want := map[subBand]uint32{ - EuropeG1: 51, - EuropeG: 25, - } - - // Check - CheckErrors(t, nil, err) - CheckUsages(t, want, bands) - - // Clean - m.Close() - } - - // -------------------- - - { - Desc(t, "Update out of cycle then lookup") - - // Build - m, _ := NewManager(dutyManagerDB, 250*time.Millisecond, Europe) - - // Operate - err := m.Update([]byte{16, 2, 3}, 868.523, 14, "SF8BW125", "4/7") - CheckErrors(t, nil, err) - <-time.After(300 * time.Millisecond) - err = m.Update([]byte{16, 2, 3}, 868.123, 42, "SF9BW125", "4/5") - CheckErrors(t, nil, err) - bands, err := m.Lookup([]byte{16, 2, 3}) - - // Expectation - want := map[subBand]uint32{ - EuropeG1: 9871, - } - - // Check - CheckErrors(t, nil, err) - CheckUsages(t, want, bands) - - // Clean - m.Close() - } - - // -------------------- - - { - Desc(t, "Lookup out of cycle") - - // Build - m, _ := NewManager(dutyManagerDB, time.Millisecond, Europe) - - // Operate - err := m.Update([]byte{1, 2, 35}, 868.523, 14, "SF8BW125", "4/8") - CheckErrors(t, nil, err) - <-time.After(300 * time.Millisecond) - bands, err := m.Lookup([]byte{1, 2, 35}) - - // Expectation - want := map[subBand]uint32{} - - // Check - CheckErrors(t, nil, err) - CheckUsages(t, want, bands) - - // Clean - m.Close() - } - - // ------------------- - - { - Desc(t, "Update on sf11 et sf12 with a 125 bandwidth -> optimization for low datarate") - - // Build - m, _ := NewManager(dutyManagerDB, time.Minute, Europe) - - // Operate - err := m.Update([]byte{4, 12, 6}, 868.523, 14, "SF11BW125", "4/5") - CheckErrors(t, nil, err) - err = m.Update([]byte{4, 12, 6}, 868.523, 42, "SF12BW125", "4/5") - CheckErrors(t, nil, err) - bands, err := m.Lookup([]byte{4, 12, 6}) - - // Expectation - want := map[subBand]uint32{ - EuropeG1: 384, - } - - // Check - CheckErrors(t, nil, err) - CheckUsages(t, want, bands) - - // Clean - m.Close() - } -} - -func TestStateFromDuty(t *testing.T) { - Desc(t, "Duty = 100 -> Blocked") - CheckStates(t, StateBlocked, StateFromDuty(100)) - - Desc(t, "Duty = 324 -> Blocked") - CheckStates(t, StateBlocked, StateFromDuty(324)) - - Desc(t, "Duty = 90 -> Warning") - CheckStates(t, StateWarning, StateFromDuty(90)) - - Desc(t, "Duty = 50 -> Available") - CheckStates(t, StateAvailable, StateFromDuty(50)) - - Desc(t, "Duty = 3 -> Highly Available") - CheckStates(t, StateHighlyAvailable, StateFromDuty(3)) -} diff --git a/core/dutycycle/helpers_test.go b/core/dutycycle/helpers_test.go deleted file mode 100644 index 8af6f2ab8..000000000 --- a/core/dutycycle/helpers_test.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package dutycycle - -import ( - "testing" - - testutil "github.com/TheThingsNetwork/ttn/utils/testing" -) - -// ----- CHECK utilities -func CheckSubBands(t *testing.T, want subBand, got subBand) { - testutil.Check(t, want, got, "SubBands") -} - -func CheckUsages(t *testing.T, want map[subBand]uint32, got map[subBand]uint32) { - testutil.Check(t, want, got, "Usages") -} - -func CheckBestTargets(t *testing.T, want *BestTarget, got *BestTarget) { - testutil.Check(t, want, got, "Targets") -} - -func CheckStates(t *testing.T, want State, got State) { - testutil.Check(t, want, got, "States") -} diff --git a/core/dutycycle/scoreComputer.go b/core/dutycycle/scoreComputer.go deleted file mode 100644 index c39add121..000000000 --- a/core/dutycycle/scoreComputer.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package dutycycle - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// ScoreComputer enables an external component to manipulate metadata associated to several targets -// in order to determine which target is the most suitable for a downlink response. -// It considers two windows RX1 and RX2 with the following conventions: -// -// For SF7 & SF8, RX1, the algorithm favors RX1 -// -// For SF9+ or, if no target is available on RX1, then RX2 is used -// -// Within RX1 or RX2, the SNR is considered first (the higher the better), then the RSSI on a lower -// plan. -type ScoreComputer struct { - sf uint -} - -// BestTarget represents the best result that has been computed after all updates. -type BestTarget struct { - ID int // The ID provided during updates - IsRX2 bool // Whether it should use RX2 -} - -type scores struct { - rx1 struct { - ID int - Score int - } - rx2 struct { - ID int - Score int - } -} - -// NewScoreComputer constructs a new ScoreComputer and initiate an empty scores table -func NewScoreComputer(datr string) (*ScoreComputer, scores, error) { - sf, _, err := ParseDatr(datr) - if err != nil { - return nil, scores{}, errors.New(errors.Structural, err) - } - - return &ScoreComputer{sf: uint(sf)}, scores{}, nil -} - -// Update computes the score associated to the given target and update the internal score -// accordingly whether it is better than the existing one -func (c *ScoreComputer) Update(s scores, id int, metadata core.Metadata) scores { - dutyRX1, dutyRX2 := metadata.DutyRX1, metadata.DutyRX2 - lsnr, rssi := float64(metadata.Lsnr), int(metadata.Rssi) - - rx1 := computeScore(State(dutyRX1), lsnr, rssi) - if rx1 > s.rx1.Score { - s.rx1.Score, s.rx1.ID = rx1, id - } - - rx2 := computeScore(State(dutyRX2), lsnr, rssi) - if rx2 > s.rx2.Score { - s.rx2.Score, s.rx2.ID = rx2, id - } - - return s -} - -// Get returns the best score according to the configured spread factor and all updates. -// It returns nil if none of the target is available for a response -func (c *ScoreComputer) Get(s scores) *BestTarget { - if s.rx1.Score > 0 && (c.sf == 7 || c.sf == 8) { // Favor RX1 on SF7 & SF8 - return &BestTarget{ID: s.rx1.ID, IsRX2: false} - } - if s.rx2.Score > 0 { - return &BestTarget{ID: s.rx2.ID, IsRX2: true} - } - return nil -} - -func computeScore(duty State, lsnr float64, rssi int) int { - var score int - - // Most importance on the duty cycle - switch duty { - case StateHighlyAvailable: - score += 5000 - case StateAvailable: - score += 4900 - case StateWarning: - score += 3000 - case StateBlocked: - return 0 - } - - // Then, we favor lsnr - if lsnr > 5.0 { - score += 1000 - } else if lsnr > 4.0 { - score += 750 - } else if lsnr > 3.0 { - score += 500 - } - - // Rssi, negative will lower the score again. - // For similar lsnr, this will make the difference. - // We don't expect rssi to be lower than 500 though ... - score += rssi - - return score -} diff --git a/core/dutycycle/scoreComputer_test.go b/core/dutycycle/scoreComputer_test.go deleted file mode 100644 index 31bb74077..000000000 --- a/core/dutycycle/scoreComputer_test.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright © 2015 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package dutycycle - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestNewScoreComputer(t *testing.T) { - { - Desc(t, "Invalid datr as argument") - _, _, err := NewScoreComputer("TheThingsNetwork") - CheckErrors(t, pointer.String(string(errors.Structural)), err) - } - - // -------------------- - - { - Desc(t, "Valid datr") - _, _, err := NewScoreComputer("SF8BW250") - CheckErrors(t, nil, err) - } -} - -func TestUpdateGet(t *testing.T) { - t.Log(` - The following set of tests follows the given notation: - Desc := SF | Upgrade - SF := { SF7, SF8, SF9, SF10, SF11, SF12 } - Upgrade := ... | UpgradeDesc - UpgradeDesc := (ID, State, State, Rssi, Lsnr) - ID := uint - State := { Ha, Av, Wa, Bl } - Rssi := int - Lsnr := int - `) - - { - Desc(t, "SF7 | ...") - - // Build - c, s, err := NewScoreComputer("SF7BW125") - CheckErrors(t, nil, err) - - // Operate - got := c.Get(s) - - // Check - CheckBestTargets(t, nil, got) - } - - // -------------------- - - { - Desc(t, "SF7 | (1, Av, Bl, -25, 5.0)") - - // Build - c, s, err := NewScoreComputer("SF7BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateBlocked), - Rssi: -25, - Lsnr: 5.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: false}, got) - } - - // -------------------- - - { - Desc(t, "SF7 | (1, Bl, Ha, -25, 5.0)") - - // Build - c, s, err := NewScoreComputer("SF7BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateBlocked), - DutyRX2: uint32(StateHighlyAvailable), - Rssi: -25, - Lsnr: 5.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) - } - - // -------------------- - - { - Desc(t, "SF7 | (1, Bl, Bl, -25, 5.0)") - - // Build - c, s, err := NewScoreComputer("SF7BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateBlocked), - DutyRX2: uint32(StateBlocked), - Rssi: -25, - Lsnr: 5.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, nil, got) - } - - // -------------------- - - { - Desc(t, "SF9 | (1, Av, Av, -25, 5.0) ") - - // Build - c, s, err := NewScoreComputer("SF9BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateAvailable), - Rssi: -25, - Lsnr: 5.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) - } - - // -------------------- - - { - Desc(t, "SF10 | (1, Av, Av, -25, 5.0) :: (2, Av, Av, -25, 3.0)") - - // Build - c, s, err := NewScoreComputer("SF10BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateAvailable), - Rssi: -25, - Lsnr: 5.0, - }) - s = c.Update(s, 2, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateAvailable), - Rssi: -25, - Lsnr: 3.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) - } - - // -------------------- - - { - Desc(t, "SF10 | (1, Av, Bl, -25, 5.0)") - - // Build - c, s, err := NewScoreComputer("SF10BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateBlocked), - Rssi: -25, - Lsnr: 5.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, nil, got) - } - - // -------------------- - - { - Desc(t, "SF8 | (1, Wa, Av, -25, 5.0) :: (2, Av, Av, -25, 5.0)") - - // Build - c, s, err := NewScoreComputer("SF8BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateWarning), - DutyRX2: uint32(StateAvailable), - Rssi: -25, - Lsnr: 5.0, - }) - s = c.Update(s, 2, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateAvailable), - Rssi: -25, - Lsnr: 5.0, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, &BestTarget{ID: 2, IsRX2: false}, got) - } - - // -------------------- - - { - Desc(t, "SF12 | (1, Av, Av, -25, 5.1) :: (2, Ha, Ha, -25, 3.4)") - - // Build - c, s, err := NewScoreComputer("SF12BW125") - CheckErrors(t, nil, err) - - // Operate - s = c.Update(s, 1, core.Metadata{ - DutyRX1: uint32(StateAvailable), - DutyRX2: uint32(StateAvailable), - Rssi: -25, - Lsnr: 5.1, - }) - s = c.Update(s, 2, core.Metadata{ - DutyRX1: uint32(StateHighlyAvailable), - DutyRX2: uint32(StateHighlyAvailable), - Rssi: -25, - Lsnr: 3.4, - }) - got := c.Get(s) - - // Check - CheckBestTargets(t, &BestTarget{ID: 1, IsRX2: true}, got) - } -} diff --git a/core/flags.go b/core/flags.go deleted file mode 100644 index 37a2fcf36..000000000 --- a/core/flags.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -const ( - RelaxFcntCheck uint32 = 1 << iota -) diff --git a/core/handler.pb.go b/core/handler.pb.go deleted file mode 100644 index 8e65f6ba4..000000000 --- a/core/handler.pb.go +++ /dev/null @@ -1,1849 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: handler.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type DataUpHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - FCnt uint32 `protobuf:"varint,10,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` - FPort uint32 `protobuf:"varint,11,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` - MType uint32 `protobuf:"varint,12,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` - FCntUpReset bool `protobuf:"varint,13,opt,name=FCntUpReset,json=fCntUpReset,proto3" json:"FCntUpReset,omitempty"` - Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataUpHandlerReq) Reset() { *m = DataUpHandlerReq{} } -func (m *DataUpHandlerReq) String() string { return proto.CompactTextString(m) } -func (*DataUpHandlerReq) ProtoMessage() {} -func (*DataUpHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } - -func (m *DataUpHandlerReq) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type DataUpHandlerRes struct { - Payload *LoRaWANData `protobuf:"bytes,20,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataUpHandlerRes) Reset() { *m = DataUpHandlerRes{} } -func (m *DataUpHandlerRes) String() string { return proto.CompactTextString(m) } -func (*DataUpHandlerRes) ProtoMessage() {} -func (*DataUpHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{1} } - -func (m *DataUpHandlerRes) GetPayload() *LoRaWANData { - if m != nil { - return m.Payload - } - return nil -} - -func (m *DataUpHandlerRes) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type DataDownHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - Payload []byte `protobuf:"bytes,20,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - TTL string `protobuf:"bytes,30,opt,name=TTL,json=tTL,proto3" json:"TTL,omitempty"` -} - -func (m *DataDownHandlerReq) Reset() { *m = DataDownHandlerReq{} } -func (m *DataDownHandlerReq) String() string { return proto.CompactTextString(m) } -func (*DataDownHandlerReq) ProtoMessage() {} -func (*DataDownHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{2} } - -type DataDownHandlerRes struct { -} - -func (m *DataDownHandlerRes) Reset() { *m = DataDownHandlerRes{} } -func (m *DataDownHandlerRes) String() string { return proto.CompactTextString(m) } -func (*DataDownHandlerRes) ProtoMessage() {} -func (*DataDownHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } - -type JoinHandlerReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,2,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevNonce []byte `protobuf:"bytes,10,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - MIC []byte `protobuf:"bytes,11,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` - Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinHandlerReq) Reset() { *m = JoinHandlerReq{} } -func (m *JoinHandlerReq) String() string { return proto.CompactTextString(m) } -func (*JoinHandlerReq) ProtoMessage() {} -func (*JoinHandlerReq) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } - -func (m *JoinHandlerReq) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type JoinHandlerRes struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - Payload *LoRaWANJoinAccept `protobuf:"bytes,20,opt,name=Payload,json=payload" json:"Payload,omitempty"` - NwkSKey []byte `protobuf:"bytes,21,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - Metadata *Metadata `protobuf:"bytes,30,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinHandlerRes) Reset() { *m = JoinHandlerRes{} } -func (m *JoinHandlerRes) String() string { return proto.CompactTextString(m) } -func (*JoinHandlerRes) ProtoMessage() {} -func (*JoinHandlerRes) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } - -func (m *JoinHandlerRes) GetPayload() *LoRaWANJoinAccept { - if m != nil { - return m.Payload - } - return nil -} - -func (m *JoinHandlerRes) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -func init() { - proto.RegisterType((*DataUpHandlerReq)(nil), "core.DataUpHandlerReq") - proto.RegisterType((*DataUpHandlerRes)(nil), "core.DataUpHandlerRes") - proto.RegisterType((*DataDownHandlerReq)(nil), "core.DataDownHandlerReq") - proto.RegisterType((*DataDownHandlerRes)(nil), "core.DataDownHandlerRes") - proto.RegisterType((*JoinHandlerReq)(nil), "core.JoinHandlerReq") - proto.RegisterType((*JoinHandlerRes)(nil), "core.JoinHandlerRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 - -// Client API for Handler service - -type HandlerClient interface { - HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) - HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) - HandleJoin(ctx context.Context, in *JoinHandlerReq, opts ...grpc.CallOption) (*JoinHandlerRes, error) -} - -type handlerClient struct { - cc *grpc.ClientConn -} - -func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { - return &handlerClient{cc} -} - -func (c *handlerClient) HandleDataUp(ctx context.Context, in *DataUpHandlerReq, opts ...grpc.CallOption) (*DataUpHandlerRes, error) { - out := new(DataUpHandlerRes) - err := grpc.Invoke(ctx, "/core.Handler/HandleDataUp", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerClient) HandleDataDown(ctx context.Context, in *DataDownHandlerReq, opts ...grpc.CallOption) (*DataDownHandlerRes, error) { - out := new(DataDownHandlerRes) - err := grpc.Invoke(ctx, "/core.Handler/HandleDataDown", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerClient) HandleJoin(ctx context.Context, in *JoinHandlerReq, opts ...grpc.CallOption) (*JoinHandlerRes, error) { - out := new(JoinHandlerRes) - err := grpc.Invoke(ctx, "/core.Handler/HandleJoin", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Handler service - -type HandlerServer interface { - HandleDataUp(context.Context, *DataUpHandlerReq) (*DataUpHandlerRes, error) - HandleDataDown(context.Context, *DataDownHandlerReq) (*DataDownHandlerRes, error) - HandleJoin(context.Context, *JoinHandlerReq) (*JoinHandlerRes, error) -} - -func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { - s.RegisterService(&_Handler_serviceDesc, srv) -} - -func _Handler_HandleDataUp_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DataUpHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerServer).HandleDataUp(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Handler/HandleDataUp", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerServer).HandleDataUp(ctx, req.(*DataUpHandlerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _Handler_HandleDataDown_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DataDownHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerServer).HandleDataDown(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Handler/HandleDataDown", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerServer).HandleDataDown(ctx, req.(*DataDownHandlerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _Handler_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(JoinHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerServer).HandleJoin(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Handler/HandleJoin", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerServer).HandleJoin(ctx, req.(*JoinHandlerReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _Handler_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.Handler", - HandlerType: (*HandlerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "HandleDataUp", - Handler: _Handler_HandleDataUp_Handler, - }, - { - MethodName: "HandleDataDown", - Handler: _Handler_HandleDataDown_Handler, - }, - { - MethodName: "HandleJoin", - Handler: _Handler_HandleJoin_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -func (m *DataUpHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataUpHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if m.FCnt != 0 { - data[i] = 0x50 - i++ - i = encodeVarintHandler(data, i, uint64(m.FCnt)) - } - if m.FPort != 0 { - data[i] = 0x58 - i++ - i = encodeVarintHandler(data, i, uint64(m.FPort)) - } - if m.MType != 0 { - data[i] = 0x60 - i++ - i = encodeVarintHandler(data, i, uint64(m.MType)) - } - if m.FCntUpReset { - data[i] = 0x68 - i++ - if m.FCntUpReset { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if len(m.Payload) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - if m.Metadata != nil { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) - n1, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 - } - return i, nil -} - -func (m *DataUpHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataUpHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Payload != nil { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(m.Payload.Size())) - n2, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 - } - if m.Metadata != nil { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) - n3, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } - return i, nil -} - -func (m *DataDownHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataDownHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.Payload) > 0 { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - if len(m.TTL) > 0 { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.TTL))) - i += copy(data[i:], m.TTL) - } - return i, nil -} - -func (m *DataDownHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataDownHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *JoinHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.DevNonce) > 0 { - data[i] = 0x52 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } - if len(m.MIC) > 0 { - data[i] = 0x5a - i++ - i = encodeVarintHandler(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } - if m.Metadata != nil { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) - n4, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n4 - } - return i, nil -} - -func (m *JoinHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.DevAddr) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if m.Payload != nil { - data[i] = 0xa2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(m.Payload.Size())) - n5, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n5 - } - if len(m.NwkSKey) > 0 { - data[i] = 0xaa - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - if m.Metadata != nil { - data[i] = 0xf2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandler(data, i, uint64(m.Metadata.Size())) - n6, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n6 - } - return i, nil -} - -func encodeFixed64Handler(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Handler(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintHandler(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *DataUpHandlerReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - if m.FCnt != 0 { - n += 1 + sovHandler(uint64(m.FCnt)) - } - if m.FPort != 0 { - n += 1 + sovHandler(uint64(m.FPort)) - } - if m.MType != 0 { - n += 1 + sovHandler(uint64(m.MType)) - } - if m.FCntUpReset { - n += 2 - } - l = len(m.Payload) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 2 + l + sovHandler(uint64(l)) - } - return n -} - -func (m *DataUpHandlerRes) Size() (n int) { - var l int - _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 2 + l + sovHandler(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 2 + l + sovHandler(uint64(l)) - } - return n -} - -func (m *DataDownHandlerReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - l = len(m.Payload) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } - l = len(m.TTL) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } - return n -} - -func (m *DataDownHandlerRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *JoinHandlerReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 2 + l + sovHandler(uint64(l)) - } - return n -} - -func (m *JoinHandlerRes) Size() (n int) { - var l int - _ = l - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandler(uint64(l)) - } - if m.Payload != nil { - l = m.Payload.Size() - n += 2 + l + sovHandler(uint64(l)) - } - l = len(m.NwkSKey) - if l > 0 { - n += 2 + l + sovHandler(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 2 + l + sovHandler(uint64(l)) - } - return n -} - -func sovHandler(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozHandler(x uint64) (n int) { - return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *DataUpHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataUpHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataUpHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 10: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) - } - m.FCnt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) - } - m.FPort = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FPort |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) - } - m.MType = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.MType |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 13: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCntUpReset", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.FCntUpReset = bool(v != 0) - case 20: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataUpHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataUpHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataUpHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 20: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANData{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataDownHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataDownHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataDownHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 20: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.TTL = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataDownHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataDownHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataDownHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 10: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) - if m.DevNonce == nil { - m.DevNonce = []byte{} - } - iNdEx = postIndex - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) - if m.MIC == nil { - m.MIC = []byte{} - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 20: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANJoinAccept{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 21: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - case 30: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandler - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandler - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandler - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipHandler(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandler - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandler - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandler - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthHandler - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandler - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipHandler(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthHandler = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowHandler = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorHandler = []byte{ - // 461 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x94, 0x4d, 0x8f, 0x12, 0x31, - 0x18, 0xc7, 0x1d, 0x61, 0x61, 0x7c, 0x66, 0x20, 0xf8, 0x04, 0xd7, 0x66, 0x0e, 0x64, 0xc3, 0xc9, - 0x68, 0xb2, 0x89, 0x78, 0xf1, 0x64, 0x82, 0xe0, 0x46, 0x14, 0xc8, 0xa6, 0x42, 0x3c, 0x57, 0x5a, - 0xa2, 0x59, 0x98, 0xd6, 0x99, 0x46, 0xc2, 0x37, 0x31, 0x5e, 0xfd, 0x32, 0x7a, 0xf3, 0x23, 0x18, - 0xbd, 0xf9, 0x29, 0xec, 0x0b, 0x66, 0x32, 0xc8, 0x1e, 0x76, 0xf7, 0xd0, 0xe4, 0xf9, 0xff, 0xda, - 0xe7, 0xad, 0x4f, 0x67, 0xa0, 0xf1, 0x9e, 0xa5, 0x7c, 0x25, 0xb2, 0x53, 0x95, 0x49, 0x2d, 0xb1, - 0xba, 0x90, 0x99, 0x48, 0x1a, 0x2b, 0x99, 0xb1, 0x0d, 0x4b, 0x3d, 0x4c, 0xc0, 0x42, 0x6f, 0x77, - 0xff, 0x04, 0xd0, 0x1a, 0x32, 0xcd, 0xe6, 0xea, 0xa5, 0x77, 0xa4, 0xe2, 0x23, 0x1e, 0x43, 0xad, - 0xaf, 0xd4, 0x8b, 0xf9, 0x88, 0x04, 0x27, 0xc1, 0x83, 0x98, 0xd6, 0x98, 0x53, 0x96, 0x0f, 0xc5, - 0x27, 0xcb, 0x6f, 0x7b, 0xce, 0x9d, 0x42, 0x84, 0xea, 0xd9, 0x20, 0xd5, 0x04, 0x0c, 0x6d, 0xd0, - 0xea, 0xd2, 0xd8, 0xd8, 0x86, 0xa3, 0xb3, 0x73, 0x99, 0x69, 0x12, 0x39, 0x78, 0xb4, 0xb4, 0xc2, - 0xd2, 0xc9, 0x6c, 0xab, 0x04, 0x89, 0x3d, 0x5d, 0x5b, 0x81, 0x27, 0x10, 0x59, 0xff, 0xb9, 0xa2, - 0x22, 0x17, 0x9a, 0x34, 0xcc, 0x5e, 0x48, 0xa3, 0x65, 0x81, 0x90, 0x40, 0xfd, 0x9c, 0x6d, 0x57, - 0x92, 0x71, 0xd2, 0x76, 0xa9, 0xeb, 0xca, 0x4b, 0x7c, 0x08, 0xe1, 0x44, 0x68, 0xc6, 0x4d, 0x0f, - 0xa4, 0x63, 0xb6, 0xa2, 0x5e, 0xf3, 0xd4, 0xf5, 0xf7, 0x8f, 0xd2, 0x70, 0xbd, 0xb3, 0xba, 0x17, - 0xff, 0xf5, 0x9a, 0xe3, 0xa3, 0x72, 0xe4, 0xa8, 0x77, 0xd7, 0xbb, 0x8f, 0x25, 0x65, 0x6f, 0xfb, - 0x53, 0x7b, 0xfe, 0x7a, 0xc9, 0x14, 0xa0, 0x75, 0x1e, 0xca, 0x4d, 0x7a, 0x83, 0xab, 0xbd, 0xbc, - 0xf1, 0x16, 0x54, 0x66, 0xb3, 0xb1, 0x2b, 0xe3, 0x0e, 0xad, 0xe8, 0xd9, 0xb8, 0xdb, 0x3e, 0x90, - 0x31, 0xef, 0x7e, 0x09, 0xa0, 0xf9, 0x4a, 0x7e, 0xb8, 0x49, 0x11, 0x09, 0x84, 0x86, 0x4f, 0x65, - 0xba, 0x10, 0x6e, 0xc6, 0x31, 0x0d, 0xf9, 0x4e, 0xdb, 0x32, 0x26, 0xa3, 0x81, 0x9b, 0x72, 0x4c, - 0x2b, 0xeb, 0xd1, 0xe0, 0x4a, 0x97, 0xf4, 0x75, 0xbf, 0xb8, 0xdc, 0x76, 0x6c, 0x92, 0xf5, 0x39, - 0xcf, 0x76, 0xd5, 0xd5, 0xb9, 0x97, 0xf8, 0x78, 0x7f, 0x54, 0xf7, 0x4b, 0xa3, 0xb2, 0x71, 0xfa, - 0x8b, 0x85, 0x50, 0xba, 0xb8, 0x24, 0x13, 0x6c, 0xba, 0xb9, 0x78, 0xf3, 0x5a, 0x6c, 0xc9, 0x3d, - 0x1f, 0x2c, 0xf5, 0xf2, 0x2a, 0x55, 0xf6, 0xbe, 0x07, 0x50, 0xdf, 0x55, 0x88, 0xcf, 0x20, 0xf6, - 0xa6, 0x7f, 0x49, 0x78, 0xec, 0xbd, 0xf6, 0xbf, 0xa1, 0xe4, 0x30, 0xcf, 0x71, 0x08, 0xcd, 0xc2, - 0xdf, 0x8e, 0x0a, 0x49, 0x71, 0xb2, 0xfc, 0x58, 0x92, 0xcb, 0x76, 0x72, 0x7c, 0x0a, 0xe0, 0x95, - 0x6d, 0x1a, 0xdb, 0xfe, 0x5c, 0x79, 0xca, 0xc9, 0x21, 0x9a, 0x3f, 0x6f, 0x7d, 0xfb, 0xd5, 0x09, - 0x7e, 0x98, 0xf5, 0xd3, 0xac, 0xcf, 0xbf, 0x3b, 0xb7, 0xde, 0xd5, 0xdc, 0x9f, 0xe0, 0xc9, 0xdf, - 0x00, 0x00, 0x00, 0xff, 0xff, 0xae, 0x68, 0x70, 0x32, 0x3b, 0x04, 0x00, 0x00, -} diff --git a/core/handler_manager.pb.go b/core/handler_manager.pb.go deleted file mode 100644 index 48120bad6..000000000 --- a/core/handler_manager.pb.go +++ /dev/null @@ -1,4096 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: handler_manager.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type UpsertOTAAHandlerReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - AppKey []byte `protobuf:"bytes,4,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` -} - -func (m *UpsertOTAAHandlerReq) Reset() { *m = UpsertOTAAHandlerReq{} } -func (m *UpsertOTAAHandlerReq) String() string { return proto.CompactTextString(m) } -func (*UpsertOTAAHandlerReq) ProtoMessage() {} -func (*UpsertOTAAHandlerReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{0} -} - -type UpsertOTAAHandlerRes struct { -} - -func (m *UpsertOTAAHandlerRes) Reset() { *m = UpsertOTAAHandlerRes{} } -func (m *UpsertOTAAHandlerRes) String() string { return proto.CompactTextString(m) } -func (*UpsertOTAAHandlerRes) ProtoMessage() {} -func (*UpsertOTAAHandlerRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{1} -} - -type UpsertABPHandlerReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,3,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,4,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,5,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` - Flags uint32 `protobuf:"varint,6,opt,name=Flags,json=flags,proto3" json:"Flags,omitempty"` -} - -func (m *UpsertABPHandlerReq) Reset() { *m = UpsertABPHandlerReq{} } -func (m *UpsertABPHandlerReq) String() string { return proto.CompactTextString(m) } -func (*UpsertABPHandlerReq) ProtoMessage() {} -func (*UpsertABPHandlerReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{2} -} - -type UpsertABPHandlerRes struct { -} - -func (m *UpsertABPHandlerRes) Reset() { *m = UpsertABPHandlerRes{} } -func (m *UpsertABPHandlerRes) String() string { return proto.CompactTextString(m) } -func (*UpsertABPHandlerRes) ProtoMessage() {} -func (*UpsertABPHandlerRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{3} -} - -type ListDevicesHandlerReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` -} - -func (m *ListDevicesHandlerReq) Reset() { *m = ListDevicesHandlerReq{} } -func (m *ListDevicesHandlerReq) String() string { return proto.CompactTextString(m) } -func (*ListDevicesHandlerReq) ProtoMessage() {} -func (*ListDevicesHandlerReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{4} -} - -type ListDevicesHandlerRes struct { - OTAA []*HandlerOTAADevice `protobuf:"bytes,1,rep,name=OTAA,json=oTAA" json:"OTAA,omitempty"` - ABP []*HandlerABPDevice `protobuf:"bytes,2,rep,name=ABP,json=aBP" json:"ABP,omitempty"` -} - -func (m *ListDevicesHandlerRes) Reset() { *m = ListDevicesHandlerRes{} } -func (m *ListDevicesHandlerRes) String() string { return proto.CompactTextString(m) } -func (*ListDevicesHandlerRes) ProtoMessage() {} -func (*ListDevicesHandlerRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{5} -} - -func (m *ListDevicesHandlerRes) GetOTAA() []*HandlerOTAADevice { - if m != nil { - return m.OTAA - } - return nil -} - -func (m *ListDevicesHandlerRes) GetABP() []*HandlerABPDevice { - if m != nil { - return m.ABP - } - return nil -} - -type HandlerABPDevice struct { - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` - FCntUp uint32 `protobuf:"varint,5,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` - FCntDown uint32 `protobuf:"varint,6,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` - Flags uint32 `protobuf:"varint,7,opt,name=Flags,json=flags,proto3" json:"Flags,omitempty"` -} - -func (m *HandlerABPDevice) Reset() { *m = HandlerABPDevice{} } -func (m *HandlerABPDevice) String() string { return proto.CompactTextString(m) } -func (*HandlerABPDevice) ProtoMessage() {} -func (*HandlerABPDevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{6} } - -type HandlerOTAADevice struct { - DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevAddr []byte `protobuf:"bytes,2,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - NwkSKey []byte `protobuf:"bytes,3,opt,name=NwkSKey,json=nwkSKey,proto3" json:"NwkSKey,omitempty"` - AppSKey []byte `protobuf:"bytes,4,opt,name=AppSKey,json=appSKey,proto3" json:"AppSKey,omitempty"` - AppKey []byte `protobuf:"bytes,5,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` - FCntUp uint32 `protobuf:"varint,7,opt,name=FCntUp,json=fCntUp,proto3" json:"FCntUp,omitempty"` - FCntDown uint32 `protobuf:"varint,8,opt,name=FCntDown,json=fCntDown,proto3" json:"FCntDown,omitempty"` -} - -func (m *HandlerOTAADevice) Reset() { *m = HandlerOTAADevice{} } -func (m *HandlerOTAADevice) String() string { return proto.CompactTextString(m) } -func (*HandlerOTAADevice) ProtoMessage() {} -func (*HandlerOTAADevice) Descriptor() ([]byte, []int) { return fileDescriptorHandlerManager, []int{7} } - -type GetDefaultDeviceReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` -} - -func (m *GetDefaultDeviceReq) Reset() { *m = GetDefaultDeviceReq{} } -func (m *GetDefaultDeviceReq) String() string { return proto.CompactTextString(m) } -func (*GetDefaultDeviceReq) ProtoMessage() {} -func (*GetDefaultDeviceReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{8} -} - -type GetDefaultDeviceRes struct { - AppKey []byte `protobuf:"bytes,1,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` -} - -func (m *GetDefaultDeviceRes) Reset() { *m = GetDefaultDeviceRes{} } -func (m *GetDefaultDeviceRes) String() string { return proto.CompactTextString(m) } -func (*GetDefaultDeviceRes) ProtoMessage() {} -func (*GetDefaultDeviceRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{9} -} - -type SetDefaultDeviceReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - AppKey []byte `protobuf:"bytes,3,opt,name=AppKey,json=appKey,proto3" json:"AppKey,omitempty"` -} - -func (m *SetDefaultDeviceReq) Reset() { *m = SetDefaultDeviceReq{} } -func (m *SetDefaultDeviceReq) String() string { return proto.CompactTextString(m) } -func (*SetDefaultDeviceReq) ProtoMessage() {} -func (*SetDefaultDeviceReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{10} -} - -type GetPayloadFunctionsReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` -} - -func (m *GetPayloadFunctionsReq) Reset() { *m = GetPayloadFunctionsReq{} } -func (m *GetPayloadFunctionsReq) String() string { return proto.CompactTextString(m) } -func (*GetPayloadFunctionsReq) ProtoMessage() {} -func (*GetPayloadFunctionsReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{11} -} - -type GetPayloadFunctionsRes struct { - Decoder string `protobuf:"bytes,1,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` - Converter string `protobuf:"bytes,2,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` - Validator string `protobuf:"bytes,3,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` -} - -func (m *GetPayloadFunctionsRes) Reset() { *m = GetPayloadFunctionsRes{} } -func (m *GetPayloadFunctionsRes) String() string { return proto.CompactTextString(m) } -func (*GetPayloadFunctionsRes) ProtoMessage() {} -func (*GetPayloadFunctionsRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{12} -} - -type SetPayloadFunctionsReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Decoder string `protobuf:"bytes,11,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` - Converter string `protobuf:"bytes,12,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` - Validator string `protobuf:"bytes,13,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` -} - -func (m *SetPayloadFunctionsReq) Reset() { *m = SetPayloadFunctionsReq{} } -func (m *SetPayloadFunctionsReq) String() string { return proto.CompactTextString(m) } -func (*SetPayloadFunctionsReq) ProtoMessage() {} -func (*SetPayloadFunctionsReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{13} -} - -type SetPayloadFunctionsRes struct { -} - -func (m *SetPayloadFunctionsRes) Reset() { *m = SetPayloadFunctionsRes{} } -func (m *SetPayloadFunctionsRes) String() string { return proto.CompactTextString(m) } -func (*SetPayloadFunctionsRes) ProtoMessage() {} -func (*SetPayloadFunctionsRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{14} -} - -type TestPayloadFunctionsReq struct { - Token string `protobuf:"bytes,1,opt,name=Token,json=token,proto3" json:"Token,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - Payload []byte `protobuf:"bytes,11,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` - Decoder string `protobuf:"bytes,21,opt,name=Decoder,json=decoder,proto3" json:"Decoder,omitempty"` - Converter string `protobuf:"bytes,22,opt,name=Converter,json=converter,proto3" json:"Converter,omitempty"` - Validator string `protobuf:"bytes,23,opt,name=Validator,json=validator,proto3" json:"Validator,omitempty"` -} - -func (m *TestPayloadFunctionsReq) Reset() { *m = TestPayloadFunctionsReq{} } -func (m *TestPayloadFunctionsReq) String() string { return proto.CompactTextString(m) } -func (*TestPayloadFunctionsReq) ProtoMessage() {} -func (*TestPayloadFunctionsReq) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{15} -} - -type TestPayloadFunctionsRes struct { - Fields string `protobuf:"bytes,1,opt,name=Fields,json=fields,proto3" json:"Fields,omitempty"` - Valid bool `protobuf:"varint,2,opt,name=Valid,json=valid,proto3" json:"Valid,omitempty"` -} - -func (m *TestPayloadFunctionsRes) Reset() { *m = TestPayloadFunctionsRes{} } -func (m *TestPayloadFunctionsRes) String() string { return proto.CompactTextString(m) } -func (*TestPayloadFunctionsRes) ProtoMessage() {} -func (*TestPayloadFunctionsRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{16} -} - -type SetDefaultDeviceRes struct { -} - -func (m *SetDefaultDeviceRes) Reset() { *m = SetDefaultDeviceRes{} } -func (m *SetDefaultDeviceRes) String() string { return proto.CompactTextString(m) } -func (*SetDefaultDeviceRes) ProtoMessage() {} -func (*SetDefaultDeviceRes) Descriptor() ([]byte, []int) { - return fileDescriptorHandlerManager, []int{17} -} - -func init() { - proto.RegisterType((*UpsertOTAAHandlerReq)(nil), "core.UpsertOTAAHandlerReq") - proto.RegisterType((*UpsertOTAAHandlerRes)(nil), "core.UpsertOTAAHandlerRes") - proto.RegisterType((*UpsertABPHandlerReq)(nil), "core.UpsertABPHandlerReq") - proto.RegisterType((*UpsertABPHandlerRes)(nil), "core.UpsertABPHandlerRes") - proto.RegisterType((*ListDevicesHandlerReq)(nil), "core.ListDevicesHandlerReq") - proto.RegisterType((*ListDevicesHandlerRes)(nil), "core.ListDevicesHandlerRes") - proto.RegisterType((*HandlerABPDevice)(nil), "core.HandlerABPDevice") - proto.RegisterType((*HandlerOTAADevice)(nil), "core.HandlerOTAADevice") - proto.RegisterType((*GetDefaultDeviceReq)(nil), "core.GetDefaultDeviceReq") - proto.RegisterType((*GetDefaultDeviceRes)(nil), "core.GetDefaultDeviceRes") - proto.RegisterType((*SetDefaultDeviceReq)(nil), "core.SetDefaultDeviceReq") - proto.RegisterType((*GetPayloadFunctionsReq)(nil), "core.GetPayloadFunctionsReq") - proto.RegisterType((*GetPayloadFunctionsRes)(nil), "core.GetPayloadFunctionsRes") - proto.RegisterType((*SetPayloadFunctionsReq)(nil), "core.SetPayloadFunctionsReq") - proto.RegisterType((*SetPayloadFunctionsRes)(nil), "core.SetPayloadFunctionsRes") - proto.RegisterType((*TestPayloadFunctionsReq)(nil), "core.TestPayloadFunctionsReq") - proto.RegisterType((*TestPayloadFunctionsRes)(nil), "core.TestPayloadFunctionsRes") - proto.RegisterType((*SetDefaultDeviceRes)(nil), "core.SetDefaultDeviceRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 - -// Client API for HandlerManager service - -type HandlerManagerClient interface { - UpsertOTAA(ctx context.Context, in *UpsertOTAAHandlerReq, opts ...grpc.CallOption) (*UpsertOTAAHandlerRes, error) - UpsertABP(ctx context.Context, in *UpsertABPHandlerReq, opts ...grpc.CallOption) (*UpsertABPHandlerRes, error) - ListDevices(ctx context.Context, in *ListDevicesHandlerReq, opts ...grpc.CallOption) (*ListDevicesHandlerRes, error) - GetDefaultDevice(ctx context.Context, in *GetDefaultDeviceReq, opts ...grpc.CallOption) (*GetDefaultDeviceRes, error) - SetDefaultDevice(ctx context.Context, in *SetDefaultDeviceReq, opts ...grpc.CallOption) (*SetDefaultDeviceRes, error) - GetPayloadFunctions(ctx context.Context, in *GetPayloadFunctionsReq, opts ...grpc.CallOption) (*GetPayloadFunctionsRes, error) - SetPayloadFunctions(ctx context.Context, in *SetPayloadFunctionsReq, opts ...grpc.CallOption) (*SetPayloadFunctionsRes, error) - TestPayloadFunctions(ctx context.Context, in *TestPayloadFunctionsReq, opts ...grpc.CallOption) (*TestPayloadFunctionsRes, error) -} - -type handlerManagerClient struct { - cc *grpc.ClientConn -} - -func NewHandlerManagerClient(cc *grpc.ClientConn) HandlerManagerClient { - return &handlerManagerClient{cc} -} - -func (c *handlerManagerClient) UpsertOTAA(ctx context.Context, in *UpsertOTAAHandlerReq, opts ...grpc.CallOption) (*UpsertOTAAHandlerRes, error) { - out := new(UpsertOTAAHandlerRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/UpsertOTAA", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) UpsertABP(ctx context.Context, in *UpsertABPHandlerReq, opts ...grpc.CallOption) (*UpsertABPHandlerRes, error) { - out := new(UpsertABPHandlerRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/UpsertABP", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) ListDevices(ctx context.Context, in *ListDevicesHandlerReq, opts ...grpc.CallOption) (*ListDevicesHandlerRes, error) { - out := new(ListDevicesHandlerRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/ListDevices", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) GetDefaultDevice(ctx context.Context, in *GetDefaultDeviceReq, opts ...grpc.CallOption) (*GetDefaultDeviceRes, error) { - out := new(GetDefaultDeviceRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/GetDefaultDevice", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) SetDefaultDevice(ctx context.Context, in *SetDefaultDeviceReq, opts ...grpc.CallOption) (*SetDefaultDeviceRes, error) { - out := new(SetDefaultDeviceRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/SetDefaultDevice", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) GetPayloadFunctions(ctx context.Context, in *GetPayloadFunctionsReq, opts ...grpc.CallOption) (*GetPayloadFunctionsRes, error) { - out := new(GetPayloadFunctionsRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/GetPayloadFunctions", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) SetPayloadFunctions(ctx context.Context, in *SetPayloadFunctionsReq, opts ...grpc.CallOption) (*SetPayloadFunctionsRes, error) { - out := new(SetPayloadFunctionsRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/SetPayloadFunctions", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *handlerManagerClient) TestPayloadFunctions(ctx context.Context, in *TestPayloadFunctionsReq, opts ...grpc.CallOption) (*TestPayloadFunctionsRes, error) { - out := new(TestPayloadFunctionsRes) - err := grpc.Invoke(ctx, "/core.HandlerManager/TestPayloadFunctions", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for HandlerManager service - -type HandlerManagerServer interface { - UpsertOTAA(context.Context, *UpsertOTAAHandlerReq) (*UpsertOTAAHandlerRes, error) - UpsertABP(context.Context, *UpsertABPHandlerReq) (*UpsertABPHandlerRes, error) - ListDevices(context.Context, *ListDevicesHandlerReq) (*ListDevicesHandlerRes, error) - GetDefaultDevice(context.Context, *GetDefaultDeviceReq) (*GetDefaultDeviceRes, error) - SetDefaultDevice(context.Context, *SetDefaultDeviceReq) (*SetDefaultDeviceRes, error) - GetPayloadFunctions(context.Context, *GetPayloadFunctionsReq) (*GetPayloadFunctionsRes, error) - SetPayloadFunctions(context.Context, *SetPayloadFunctionsReq) (*SetPayloadFunctionsRes, error) - TestPayloadFunctions(context.Context, *TestPayloadFunctionsReq) (*TestPayloadFunctionsRes, error) -} - -func RegisterHandlerManagerServer(s *grpc.Server, srv HandlerManagerServer) { - s.RegisterService(&_HandlerManager_serviceDesc, srv) -} - -func _HandlerManager_UpsertOTAA_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertOTAAHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).UpsertOTAA(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/UpsertOTAA", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).UpsertOTAA(ctx, req.(*UpsertOTAAHandlerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_UpsertABP_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UpsertABPHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).UpsertABP(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/UpsertABP", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).UpsertABP(ctx, req.(*UpsertABPHandlerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_ListDevices_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ListDevicesHandlerReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).ListDevices(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/ListDevices", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).ListDevices(ctx, req.(*ListDevicesHandlerReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_GetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetDefaultDeviceReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).GetDefaultDevice(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/GetDefaultDevice", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).GetDefaultDevice(ctx, req.(*GetDefaultDeviceReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_SetDefaultDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SetDefaultDeviceReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).SetDefaultDevice(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/SetDefaultDevice", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).SetDefaultDevice(ctx, req.(*SetDefaultDeviceReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_GetPayloadFunctions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetPayloadFunctionsReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).GetPayloadFunctions(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/GetPayloadFunctions", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).GetPayloadFunctions(ctx, req.(*GetPayloadFunctionsReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_SetPayloadFunctions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(SetPayloadFunctionsReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).SetPayloadFunctions(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/SetPayloadFunctions", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).SetPayloadFunctions(ctx, req.(*SetPayloadFunctionsReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _HandlerManager_TestPayloadFunctions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(TestPayloadFunctionsReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(HandlerManagerServer).TestPayloadFunctions(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.HandlerManager/TestPayloadFunctions", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(HandlerManagerServer).TestPayloadFunctions(ctx, req.(*TestPayloadFunctionsReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _HandlerManager_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.HandlerManager", - HandlerType: (*HandlerManagerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "UpsertOTAA", - Handler: _HandlerManager_UpsertOTAA_Handler, - }, - { - MethodName: "UpsertABP", - Handler: _HandlerManager_UpsertABP_Handler, - }, - { - MethodName: "ListDevices", - Handler: _HandlerManager_ListDevices_Handler, - }, - { - MethodName: "GetDefaultDevice", - Handler: _HandlerManager_GetDefaultDevice_Handler, - }, - { - MethodName: "SetDefaultDevice", - Handler: _HandlerManager_SetDefaultDevice_Handler, - }, - { - MethodName: "GetPayloadFunctions", - Handler: _HandlerManager_GetPayloadFunctions_Handler, - }, - { - MethodName: "SetPayloadFunctions", - Handler: _HandlerManager_SetPayloadFunctions_Handler, - }, - { - MethodName: "TestPayloadFunctions", - Handler: _HandlerManager_TestPayloadFunctions_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -func (m *UpsertOTAAHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UpsertOTAAHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.AppKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } - return i, nil -} - -func (m *UpsertOTAAHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UpsertOTAAHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *UpsertABPHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UpsertABPHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevAddr) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if len(m.NwkSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - if len(m.AppSKey) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } - if m.Flags != 0 { - data[i] = 0x30 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.Flags)) - } - return i, nil -} - -func (m *UpsertABPHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UpsertABPHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *ListDevicesHandlerReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ListDevicesHandlerReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - return i, nil -} - -func (m *ListDevicesHandlerRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ListDevicesHandlerRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.OTAA) > 0 { - for _, msg := range m.OTAA { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - if len(m.ABP) > 0 { - for _, msg := range m.ABP { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *HandlerABPDevice) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *HandlerABPDevice) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - if len(m.AppSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } - if m.FCntUp != 0 { - data[i] = 0x28 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.FCntUp)) - } - if m.FCntDown != 0 { - data[i] = 0x30 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) - } - if m.Flags != 0 { - data[i] = 0x38 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.Flags)) - } - return i, nil -} - -func (m *HandlerOTAADevice) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *HandlerOTAADevice) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.DevEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.DevAddr) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if len(m.NwkSKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.NwkSKey))) - i += copy(data[i:], m.NwkSKey) - } - if len(m.AppSKey) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppSKey))) - i += copy(data[i:], m.AppSKey) - } - if len(m.AppKey) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } - if m.FCntUp != 0 { - data[i] = 0x38 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.FCntUp)) - } - if m.FCntDown != 0 { - data[i] = 0x40 - i++ - i = encodeVarintHandlerManager(data, i, uint64(m.FCntDown)) - } - return i, nil -} - -func (m *GetDefaultDeviceReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - return i, nil -} - -func (m *GetDefaultDeviceRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GetDefaultDeviceRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppKey) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } - return i, nil -} - -func (m *SetDefaultDeviceReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *SetDefaultDeviceReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.AppKey) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppKey))) - i += copy(data[i:], m.AppKey) - } - return i, nil -} - -func (m *GetPayloadFunctionsReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GetPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - return i, nil -} - -func (m *GetPayloadFunctionsRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GetPayloadFunctionsRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Decoder) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Decoder))) - i += copy(data[i:], m.Decoder) - } - if len(m.Converter) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Converter))) - i += copy(data[i:], m.Converter) - } - if len(m.Validator) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Validator))) - i += copy(data[i:], m.Validator) - } - return i, nil -} - -func (m *SetPayloadFunctionsReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *SetPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.Decoder) > 0 { - data[i] = 0x5a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Decoder))) - i += copy(data[i:], m.Decoder) - } - if len(m.Converter) > 0 { - data[i] = 0x62 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Converter))) - i += copy(data[i:], m.Converter) - } - if len(m.Validator) > 0 { - data[i] = 0x6a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Validator))) - i += copy(data[i:], m.Validator) - } - return i, nil -} - -func (m *SetPayloadFunctionsRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *SetPayloadFunctionsRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *TestPayloadFunctionsReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *TestPayloadFunctionsReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Token) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.Payload) > 0 { - data[i] = 0x5a - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - if len(m.Decoder) > 0 { - data[i] = 0xaa - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Decoder))) - i += copy(data[i:], m.Decoder) - } - if len(m.Converter) > 0 { - data[i] = 0xb2 - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Converter))) - i += copy(data[i:], m.Converter) - } - if len(m.Validator) > 0 { - data[i] = 0xba - i++ - data[i] = 0x1 - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Validator))) - i += copy(data[i:], m.Validator) - } - return i, nil -} - -func (m *TestPayloadFunctionsRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *TestPayloadFunctionsRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Fields) > 0 { - data[i] = 0xa - i++ - i = encodeVarintHandlerManager(data, i, uint64(len(m.Fields))) - i += copy(data[i:], m.Fields) - } - if m.Valid { - data[i] = 0x10 - i++ - if m.Valid { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - return i, nil -} - -func (m *SetDefaultDeviceRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *SetDefaultDeviceRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func encodeFixed64HandlerManager(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32HandlerManager(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintHandlerManager(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *UpsertOTAAHandlerReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *UpsertOTAAHandlerRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *UpsertABPHandlerReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - if m.Flags != 0 { - n += 1 + sovHandlerManager(uint64(m.Flags)) - } - return n -} - -func (m *UpsertABPHandlerRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *ListDevicesHandlerReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *ListDevicesHandlerRes) Size() (n int) { - var l int - _ = l - if len(m.OTAA) > 0 { - for _, e := range m.OTAA { - l = e.Size() - n += 1 + l + sovHandlerManager(uint64(l)) - } - } - if len(m.ABP) > 0 { - for _, e := range m.ABP { - l = e.Size() - n += 1 + l + sovHandlerManager(uint64(l)) - } - } - return n -} - -func (m *HandlerABPDevice) Size() (n int) { - var l int - _ = l - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - if m.FCntUp != 0 { - n += 1 + sovHandlerManager(uint64(m.FCntUp)) - } - if m.FCntDown != 0 { - n += 1 + sovHandlerManager(uint64(m.FCntDown)) - } - if m.Flags != 0 { - n += 1 + sovHandlerManager(uint64(m.Flags)) - } - return n -} - -func (m *HandlerOTAADevice) Size() (n int) { - var l int - _ = l - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.NwkSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppSKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - if m.FCntUp != 0 { - n += 1 + sovHandlerManager(uint64(m.FCntUp)) - } - if m.FCntDown != 0 { - n += 1 + sovHandlerManager(uint64(m.FCntDown)) - } - return n -} - -func (m *GetDefaultDeviceReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *GetDefaultDeviceRes) Size() (n int) { - var l int - _ = l - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *SetDefaultDeviceReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppKey) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *GetPayloadFunctionsReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *GetPayloadFunctionsRes) Size() (n int) { - var l int - _ = l - l = len(m.Decoder) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Converter) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Validator) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *SetPayloadFunctionsReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Decoder) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Converter) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Validator) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *SetPayloadFunctionsRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *TestPayloadFunctionsReq) Size() (n int) { - var l int - _ = l - l = len(m.Token) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Decoder) - if l > 0 { - n += 2 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Converter) - if l > 0 { - n += 2 + l + sovHandlerManager(uint64(l)) - } - l = len(m.Validator) - if l > 0 { - n += 2 + l + sovHandlerManager(uint64(l)) - } - return n -} - -func (m *TestPayloadFunctionsRes) Size() (n int) { - var l int - _ = l - l = len(m.Fields) - if l > 0 { - n += 1 + l + sovHandlerManager(uint64(l)) - } - if m.Valid { - n += 2 - } - return n -} - -func (m *SetDefaultDeviceRes) Size() (n int) { - var l int - _ = l - return n -} - -func sovHandlerManager(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozHandlerManager(x uint64) (n int) { - return sovHandlerManager(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *UpsertOTAAHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UpsertOTAAHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertOTAAHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) - if m.AppKey == nil { - m.AppKey = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UpsertOTAAHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UpsertOTAAHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertOTAAHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UpsertABPHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UpsertABPHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertABPHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) - if m.AppSKey == nil { - m.AppSKey = []byte{} - } - iNdEx = postIndex - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) - } - m.Flags = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Flags |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UpsertABPHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UpsertABPHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UpsertABPHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ListDevicesHandlerReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ListDevicesHandlerReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ListDevicesHandlerReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ListDevicesHandlerRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ListDevicesHandlerRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ListDevicesHandlerRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field OTAA", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.OTAA = append(m.OTAA, &HandlerOTAADevice{}) - if err := m.OTAA[len(m.OTAA)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ABP", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.ABP = append(m.ABP, &HandlerABPDevice{}) - if err := m.ABP[len(m.ABP)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HandlerABPDevice) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HandlerABPDevice: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerABPDevice: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) - if m.AppSKey == nil { - m.AppSKey = []byte{} - } - iNdEx = postIndex - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) - } - m.FCntUp = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCntUp |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 6: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) - } - m.FCntDown = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCntDown |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Flags", wireType) - } - m.Flags = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Flags |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *HandlerOTAADevice) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: HandlerOTAADevice: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: HandlerOTAADevice: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.NwkSKey = append(m.NwkSKey[:0], data[iNdEx:postIndex]...) - if m.NwkSKey == nil { - m.NwkSKey = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppSKey = append(m.AppSKey[:0], data[iNdEx:postIndex]...) - if m.AppSKey == nil { - m.AppSKey = []byte{} - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) - if m.AppKey == nil { - m.AppKey = []byte{} - } - iNdEx = postIndex - case 7: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) - } - m.FCntUp = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCntUp |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 8: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) - } - m.FCntDown = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCntDown |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetDefaultDeviceReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetDefaultDeviceReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetDefaultDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetDefaultDeviceRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetDefaultDeviceRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetDefaultDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) - if m.AppKey == nil { - m.AppKey = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SetDefaultDeviceReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SetDefaultDeviceReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SetDefaultDeviceReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppKey = append(m.AppKey[:0], data[iNdEx:postIndex]...) - if m.AppKey == nil { - m.AppKey = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetPayloadFunctionsReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetPayloadFunctionsReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetPayloadFunctionsReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetPayloadFunctionsRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetPayloadFunctionsRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetPayloadFunctionsRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Decoder = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Converter = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Validator = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SetPayloadFunctionsReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SetPayloadFunctionsReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SetPayloadFunctionsReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Decoder = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 12: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Converter = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 13: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Validator = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SetPayloadFunctionsRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SetPayloadFunctionsRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SetPayloadFunctionsRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *TestPayloadFunctionsReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: TestPayloadFunctionsReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: TestPayloadFunctionsReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Token = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 11: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} - } - iNdEx = postIndex - case 21: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Decoder = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 22: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Converter = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 23: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Validator = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *TestPayloadFunctionsRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: TestPayloadFunctionsRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: TestPayloadFunctionsRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthHandlerManager - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Fields = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Valid", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.Valid = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *SetDefaultDeviceRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: SetDefaultDeviceRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: SetDefaultDeviceRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipHandlerManager(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthHandlerManager - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipHandlerManager(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthHandlerManager - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowHandlerManager - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipHandlerManager(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthHandlerManager = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowHandlerManager = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorHandlerManager = []byte{ - // 709 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdb, 0x4e, 0xd5, 0x4c, - 0x14, 0xfe, 0xcb, 0x3e, 0x2f, 0xe0, 0x0f, 0x0e, 0xec, 0x52, 0x2b, 0x12, 0xd3, 0x2b, 0x12, 0x23, - 0x17, 0xf8, 0x04, 0xe5, 0xb0, 0xc1, 0x78, 0xc2, 0x76, 0xe3, 0x8d, 0x17, 0x66, 0xdc, 0x9d, 0x8d, - 0x3b, 0xd4, 0xb6, 0x76, 0x0a, 0x84, 0x37, 0xf1, 0xc2, 0x7b, 0x13, 0x5f, 0xc0, 0x27, 0x30, 0xf1, - 0xd2, 0x47, 0x30, 0xfa, 0x06, 0x3e, 0x81, 0x73, 0x02, 0xda, 0xcd, 0x4c, 0x13, 0x91, 0x8b, 0x9d, - 0xb0, 0xd6, 0xb7, 0xfa, 0xcd, 0xb7, 0x0e, 0xb3, 0x06, 0xe8, 0xbf, 0xc5, 0x49, 0x14, 0x93, 0xfc, - 0xf5, 0x3b, 0x9c, 0xe0, 0x43, 0x92, 0xaf, 0x67, 0x79, 0x5a, 0xa4, 0xa8, 0x39, 0x4a, 0x73, 0xe2, - 0x15, 0xb0, 0x74, 0x90, 0x51, 0x92, 0x17, 0xcf, 0x87, 0xbe, 0xbf, 0x27, 0x03, 0x03, 0xf2, 0x1e, - 0x2d, 0x41, 0x6b, 0x98, 0x1e, 0x91, 0xc4, 0xb1, 0xee, 0x59, 0x6b, 0xbd, 0xa0, 0x55, 0x70, 0x03, - 0xd9, 0xd0, 0xf6, 0xb3, 0x6c, 0xe7, 0xe0, 0x91, 0x33, 0xc3, 0xdc, 0x73, 0x41, 0x1b, 0x0b, 0x8b, - 0xfb, 0xb7, 0xc9, 0x09, 0xf7, 0x37, 0xa4, 0x3f, 0x12, 0x96, 0x8a, 0x7f, 0x4c, 0xce, 0x9c, 0xe6, - 0x45, 0x3c, 0xb3, 0x3c, 0x5b, 0x7b, 0x2a, 0xf5, 0x3e, 0x59, 0xb0, 0x28, 0x01, 0x7f, 0x73, 0xff, - 0xda, 0x6a, 0x1c, 0xe8, 0x30, 0x35, 0x7e, 0x14, 0xe5, 0x4a, 0x4e, 0x27, 0x92, 0x26, 0x47, 0x9e, - 0x9d, 0x1e, 0x85, 0x97, 0x82, 0x3a, 0x89, 0x34, 0x39, 0xc2, 0xb8, 0x04, 0xd2, 0x92, 0x08, 0x96, - 0x26, 0x3f, 0x7b, 0x10, 0xe3, 0x43, 0xea, 0xb4, 0x99, 0x7f, 0x3e, 0x68, 0x8d, 0xb9, 0xe1, 0xf5, - 0x75, 0x42, 0xa9, 0xb7, 0x03, 0xfd, 0x27, 0x13, 0x5a, 0xb0, 0xe3, 0x27, 0x23, 0x42, 0xaf, 0x9b, - 0x81, 0x97, 0xe8, 0x69, 0x28, 0xba, 0x0f, 0x4d, 0x5e, 0x32, 0xc6, 0xd2, 0x58, 0x9b, 0xdd, 0x58, - 0x5e, 0xe7, 0x3d, 0x5c, 0x57, 0x38, 0x07, 0xe4, 0x17, 0x41, 0x33, 0x65, 0x7f, 0xa3, 0x35, 0x68, - 0x30, 0x75, 0x8c, 0x9a, 0xc7, 0xda, 0x95, 0x58, 0xe6, 0x57, 0xa1, 0x0d, 0xbc, 0xb9, 0xef, 0x7d, - 0xb6, 0x60, 0x61, 0x1a, 0x29, 0x97, 0x71, 0xc6, 0x58, 0xc6, 0x86, 0xb1, 0x8c, 0xcd, 0x6a, 0x19, - 0x59, 0xaa, 0x83, 0xad, 0xa4, 0x38, 0xc8, 0x44, 0x7d, 0xe7, 0x83, 0xf6, 0x58, 0x58, 0xc8, 0x85, - 0x2e, 0xf7, 0x6f, 0xa7, 0xa7, 0x89, 0xaa, 0x70, 0x77, 0xac, 0xec, 0xcb, 0xd2, 0x77, 0xca, 0xa5, - 0xff, 0x6a, 0xc1, 0xad, 0x2b, 0x29, 0x97, 0x46, 0xd0, 0xaa, 0x8c, 0xe0, 0x8d, 0x67, 0xa1, 0x06, - 0xba, 0x55, 0x1e, 0xe8, 0x52, 0x76, 0x1d, 0x63, 0x76, 0xdd, 0x6a, 0x76, 0xde, 0x16, 0x2c, 0xee, - 0x12, 0xd6, 0xe3, 0x31, 0x3e, 0x8e, 0x55, 0xab, 0xff, 0x7e, 0x52, 0x1e, 0xe8, 0x48, 0x68, 0x49, - 0xa7, 0x55, 0xb9, 0x78, 0xaf, 0x60, 0x31, 0xfc, 0xd7, 0x33, 0x4b, 0xe4, 0x8d, 0x0a, 0xf9, 0x00, - 0x6c, 0xa6, 0x65, 0x1f, 0x9f, 0xc5, 0x29, 0x8e, 0x06, 0xc7, 0xc9, 0xa8, 0x98, 0xa4, 0x09, 0xbd, - 0xce, 0xf4, 0xeb, 0x79, 0xa8, 0x6c, 0xe6, 0x28, 0x8d, 0x48, 0xae, 0x98, 0x58, 0x33, 0x85, 0x89, - 0x56, 0xa0, 0xb7, 0x95, 0x26, 0x27, 0xec, 0x42, 0x12, 0xd9, 0xe8, 0x5e, 0xd0, 0x1b, 0x9d, 0x3b, - 0x38, 0xfa, 0x12, 0xc7, 0x93, 0x08, 0x17, 0xa9, 0xdc, 0x09, 0x0c, 0x3d, 0x39, 0x77, 0x78, 0x1f, - 0x2d, 0xb0, 0xc3, 0x1b, 0x10, 0x5e, 0x96, 0x37, 0x5b, 0x23, 0x6f, 0xae, 0x56, 0xde, 0xfc, 0xb4, - 0x3c, 0xc7, 0xa0, 0x8e, 0x7a, 0x5f, 0x2c, 0x58, 0x1e, 0x12, 0x7a, 0x33, 0xca, 0x15, 0x89, 0x50, - 0xce, 0x26, 0x3e, 0x93, 0x66, 0x39, 0xa7, 0x7e, 0x4d, 0x4e, 0x76, 0x6d, 0x4e, 0xcb, 0xd3, 0x39, - 0xed, 0x9a, 0x84, 0x8b, 0xd1, 0x1d, 0x4c, 0x48, 0x1c, 0x51, 0xa5, 0xbc, 0x3d, 0x16, 0x16, 0x4f, - 0x48, 0x10, 0x0a, 0xe5, 0xdd, 0xa0, 0x25, 0xc8, 0xf8, 0x1e, 0xbe, 0x3a, 0xd0, 0x74, 0xe3, 0x77, - 0x13, 0xfe, 0x57, 0x3b, 0xe2, 0xa9, 0x7c, 0xf5, 0xd0, 0x36, 0xc0, 0xe5, 0x9b, 0x83, 0x5c, 0xb9, - 0x0e, 0x75, 0x6f, 0x9f, 0x6b, 0xc6, 0x28, 0xf2, 0xa1, 0x77, 0xb1, 0xf7, 0xd1, 0xed, 0x72, 0x60, - 0xe5, 0xc5, 0x72, 0x8d, 0x10, 0x45, 0xbb, 0x30, 0x5b, 0x5a, 0xee, 0xe8, 0x8e, 0x8c, 0xd4, 0x3e, - 0x1b, 0x6e, 0x0d, 0x48, 0xd1, 0x1e, 0x2c, 0x4c, 0xdf, 0xfd, 0x73, 0x49, 0x9a, 0xc5, 0xe2, 0x1a, - 0x21, 0xc1, 0x14, 0x1a, 0x98, 0x42, 0x33, 0x93, 0xa6, 0xf0, 0xe8, 0x85, 0xd8, 0x47, 0xd3, 0x7d, - 0x45, 0x2b, 0x17, 0x67, 0x6b, 0x66, 0xd5, 0xad, 0x43, 0x05, 0x65, 0x68, 0xa6, 0x0c, 0x6b, 0x29, - 0xf5, 0x17, 0x07, 0x0d, 0x61, 0x49, 0x37, 0x7e, 0xe8, 0xae, 0xfc, 0xca, 0x70, 0xa7, 0xdc, 0x5a, - 0x98, 0x6e, 0x2e, 0x7c, 0xfb, 0xb9, 0x6a, 0x7d, 0x67, 0xbf, 0x1f, 0xec, 0xf7, 0xe1, 0xd7, 0xea, - 0x7f, 0x6f, 0xda, 0xe2, 0x5f, 0xad, 0x87, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x74, 0xe9, 0xf9, - 0xf9, 0x83, 0x09, 0x00, 0x00, -} diff --git a/core/lorawan.pb.go b/core/lorawan.pb.go deleted file mode 100644 index ea6236aa0..000000000 --- a/core/lorawan.pb.go +++ /dev/null @@ -1,1767 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: lorawan.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// Uplink & Downlink Data (confirmed or unconfirmed) -type LoRaWANData struct { - MHDR *LoRaWANMHDR `protobuf:"bytes,1,opt,name=MHDR,json=mHDR" json:"MHDR,omitempty"` - MACPayload *LoRaWANMACPayload `protobuf:"bytes,2,opt,name=MACPayload,json=mACPayload" json:"MACPayload,omitempty"` - MIC []byte `protobuf:"bytes,3,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` -} - -func (m *LoRaWANData) Reset() { *m = LoRaWANData{} } -func (m *LoRaWANData) String() string { return proto.CompactTextString(m) } -func (*LoRaWANData) ProtoMessage() {} -func (*LoRaWANData) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{0} } - -func (m *LoRaWANData) GetMHDR() *LoRaWANMHDR { - if m != nil { - return m.MHDR - } - return nil -} - -func (m *LoRaWANData) GetMACPayload() *LoRaWANMACPayload { - if m != nil { - return m.MACPayload - } - return nil -} - -type LoRaWANMHDR struct { - MType uint32 `protobuf:"varint,1,opt,name=MType,json=mType,proto3" json:"MType,omitempty"` - Major uint32 `protobuf:"varint,2,opt,name=Major,json=major,proto3" json:"Major,omitempty"` -} - -func (m *LoRaWANMHDR) Reset() { *m = LoRaWANMHDR{} } -func (m *LoRaWANMHDR) String() string { return proto.CompactTextString(m) } -func (*LoRaWANMHDR) ProtoMessage() {} -func (*LoRaWANMHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } - -type LoRaWANMACPayload struct { - FHDR *LoRaWANFHDR `protobuf:"bytes,1,opt,name=FHDR,json=fHDR" json:"FHDR,omitempty"` - FPort uint32 `protobuf:"varint,2,opt,name=FPort,json=fPort,proto3" json:"FPort,omitempty"` - FRMPayload []byte `protobuf:"bytes,3,opt,name=FRMPayload,json=fRMPayload,proto3" json:"FRMPayload,omitempty"` -} - -func (m *LoRaWANMACPayload) Reset() { *m = LoRaWANMACPayload{} } -func (m *LoRaWANMACPayload) String() string { return proto.CompactTextString(m) } -func (*LoRaWANMACPayload) ProtoMessage() {} -func (*LoRaWANMACPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } - -func (m *LoRaWANMACPayload) GetFHDR() *LoRaWANFHDR { - if m != nil { - return m.FHDR - } - return nil -} - -type LoRaWANFHDR struct { - DevAddr []byte `protobuf:"bytes,1,opt,name=DevAddr,json=devAddr,proto3" json:"DevAddr,omitempty"` - FCtrl *LoRaWANFCtrl `protobuf:"bytes,2,opt,name=FCtrl,json=fCtrl" json:"FCtrl,omitempty"` - FCnt uint32 `protobuf:"varint,3,opt,name=FCnt,json=fCnt,proto3" json:"FCnt,omitempty"` - FOpts [][]byte `protobuf:"bytes,4,rep,name=FOpts,json=fOpts" json:"FOpts,omitempty"` -} - -func (m *LoRaWANFHDR) Reset() { *m = LoRaWANFHDR{} } -func (m *LoRaWANFHDR) String() string { return proto.CompactTextString(m) } -func (*LoRaWANFHDR) ProtoMessage() {} -func (*LoRaWANFHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } - -func (m *LoRaWANFHDR) GetFCtrl() *LoRaWANFCtrl { - if m != nil { - return m.FCtrl - } - return nil -} - -type LoRaWANFCtrl struct { - ADR bool `protobuf:"varint,1,opt,name=ADR,json=aDR,proto3" json:"ADR,omitempty"` - ADRAckReq bool `protobuf:"varint,2,opt,name=ADRAckReq,json=aDRAckReq,proto3" json:"ADRAckReq,omitempty"` - Ack bool `protobuf:"varint,3,opt,name=Ack,json=ack,proto3" json:"Ack,omitempty"` - FPending bool `protobuf:"varint,4,opt,name=FPending,json=fPending,proto3" json:"FPending,omitempty"` - FOptsLen []byte `protobuf:"bytes,5,opt,name=FOptsLen,json=fOptsLen,proto3" json:"FOptsLen,omitempty"` -} - -func (m *LoRaWANFCtrl) Reset() { *m = LoRaWANFCtrl{} } -func (m *LoRaWANFCtrl) String() string { return proto.CompactTextString(m) } -func (*LoRaWANFCtrl) ProtoMessage() {} -func (*LoRaWANFCtrl) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{4} } - -type LoRaWANJoinRequest struct { - DevEUI []byte `protobuf:"bytes,1,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevNonce []byte `protobuf:"bytes,3,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` -} - -func (m *LoRaWANJoinRequest) Reset() { *m = LoRaWANJoinRequest{} } -func (m *LoRaWANJoinRequest) String() string { return proto.CompactTextString(m) } -func (*LoRaWANJoinRequest) ProtoMessage() {} -func (*LoRaWANJoinRequest) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } - -type LoRaWANJoinAccept struct { - Payload []byte `protobuf:"bytes,1,opt,name=Payload,json=payload,proto3" json:"Payload,omitempty"` -} - -func (m *LoRaWANJoinAccept) Reset() { *m = LoRaWANJoinAccept{} } -func (m *LoRaWANJoinAccept) String() string { return proto.CompactTextString(m) } -func (*LoRaWANJoinAccept) ProtoMessage() {} -func (*LoRaWANJoinAccept) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } - -type LoRaWANDLSettings struct { - RX1DRoffset uint32 `protobuf:"varint,1,opt,name=RX1DRoffset,json=rX1DRoffset,proto3" json:"RX1DRoffset,omitempty"` - RX2DataRate uint32 `protobuf:"varint,2,opt,name=RX2DataRate,json=rX2DataRate,proto3" json:"RX2DataRate,omitempty"` -} - -func (m *LoRaWANDLSettings) Reset() { *m = LoRaWANDLSettings{} } -func (m *LoRaWANDLSettings) String() string { return proto.CompactTextString(m) } -func (*LoRaWANDLSettings) ProtoMessage() {} -func (*LoRaWANDLSettings) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{7} } - -func init() { - proto.RegisterType((*LoRaWANData)(nil), "core.LoRaWANData") - proto.RegisterType((*LoRaWANMHDR)(nil), "core.LoRaWANMHDR") - proto.RegisterType((*LoRaWANMACPayload)(nil), "core.LoRaWANMACPayload") - proto.RegisterType((*LoRaWANFHDR)(nil), "core.LoRaWANFHDR") - proto.RegisterType((*LoRaWANFCtrl)(nil), "core.LoRaWANFCtrl") - proto.RegisterType((*LoRaWANJoinRequest)(nil), "core.LoRaWANJoinRequest") - proto.RegisterType((*LoRaWANJoinAccept)(nil), "core.LoRaWANJoinAccept") - proto.RegisterType((*LoRaWANDLSettings)(nil), "core.LoRaWANDLSettings") -} -func (m *LoRaWANData) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANData) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.MHDR != nil { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(m.MHDR.Size())) - n1, err := m.MHDR.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 - } - if m.MACPayload != nil { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) - n2, err := m.MACPayload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 - } - if len(m.MIC) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } - return i, nil -} - -func (m *LoRaWANMHDR) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANMHDR) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.MType != 0 { - data[i] = 0x8 - i++ - i = encodeVarintLorawan(data, i, uint64(m.MType)) - } - if m.Major != 0 { - data[i] = 0x10 - i++ - i = encodeVarintLorawan(data, i, uint64(m.Major)) - } - return i, nil -} - -func (m *LoRaWANMACPayload) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANMACPayload) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.FHDR != nil { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(m.FHDR.Size())) - n3, err := m.FHDR.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } - if m.FPort != 0 { - data[i] = 0x10 - i++ - i = encodeVarintLorawan(data, i, uint64(m.FPort)) - } - if len(m.FRMPayload) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FRMPayload))) - i += copy(data[i:], m.FRMPayload) - } - return i, nil -} - -func (m *LoRaWANFHDR) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANFHDR) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.DevAddr) > 0 { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevAddr))) - i += copy(data[i:], m.DevAddr) - } - if m.FCtrl != nil { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) - n4, err := m.FCtrl.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n4 - } - if m.FCnt != 0 { - data[i] = 0x18 - i++ - i = encodeVarintLorawan(data, i, uint64(m.FCnt)) - } - if len(m.FOpts) > 0 { - for _, b := range m.FOpts { - data[i] = 0x22 - i++ - i = encodeVarintLorawan(data, i, uint64(len(b))) - i += copy(data[i:], b) - } - } - return i, nil -} - -func (m *LoRaWANFCtrl) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANFCtrl) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.ADR { - data[i] = 0x8 - i++ - if m.ADR { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.ADRAckReq { - data[i] = 0x10 - i++ - if m.ADRAckReq { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.Ack { - data[i] = 0x18 - i++ - if m.Ack { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.FPending { - data[i] = 0x20 - i++ - if m.FPending { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if len(m.FOptsLen) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FOptsLen))) - i += copy(data[i:], m.FOptsLen) - } - return i, nil -} - -func (m *LoRaWANJoinRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANJoinRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.DevEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevNonce) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } - return i, nil -} - -func (m *LoRaWANJoinAccept) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANJoinAccept) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Payload) > 0 { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) - } - return i, nil -} - -func (m *LoRaWANDLSettings) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *LoRaWANDLSettings) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.RX1DRoffset != 0 { - data[i] = 0x8 - i++ - i = encodeVarintLorawan(data, i, uint64(m.RX1DRoffset)) - } - if m.RX2DataRate != 0 { - data[i] = 0x10 - i++ - i = encodeVarintLorawan(data, i, uint64(m.RX2DataRate)) - } - return i, nil -} - -func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Lorawan(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintLorawan(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *LoRaWANData) Size() (n int) { - var l int - _ = l - if m.MHDR != nil { - l = m.MHDR.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.MACPayload != nil { - l = m.MACPayload.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *LoRaWANMHDR) Size() (n int) { - var l int - _ = l - if m.MType != 0 { - n += 1 + sovLorawan(uint64(m.MType)) - } - if m.Major != 0 { - n += 1 + sovLorawan(uint64(m.Major)) - } - return n -} - -func (m *LoRaWANMACPayload) Size() (n int) { - var l int - _ = l - if m.FHDR != nil { - l = m.FHDR.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.FPort != 0 { - n += 1 + sovLorawan(uint64(m.FPort)) - } - l = len(m.FRMPayload) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *LoRaWANFHDR) Size() (n int) { - var l int - _ = l - l = len(m.DevAddr) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - if m.FCtrl != nil { - l = m.FCtrl.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.FCnt != 0 { - n += 1 + sovLorawan(uint64(m.FCnt)) - } - if len(m.FOpts) > 0 { - for _, b := range m.FOpts { - l = len(b) - n += 1 + l + sovLorawan(uint64(l)) - } - } - return n -} - -func (m *LoRaWANFCtrl) Size() (n int) { - var l int - _ = l - if m.ADR { - n += 2 - } - if m.ADRAckReq { - n += 2 - } - if m.Ack { - n += 2 - } - if m.FPending { - n += 2 - } - l = len(m.FOptsLen) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *LoRaWANJoinRequest) Size() (n int) { - var l int - _ = l - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *LoRaWANJoinAccept) Size() (n int) { - var l int - _ = l - l = len(m.Payload) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *LoRaWANDLSettings) Size() (n int) { - var l int - _ = l - if m.RX1DRoffset != 0 { - n += 1 + sovLorawan(uint64(m.RX1DRoffset)) - } - if m.RX2DataRate != 0 { - n += 1 + sovLorawan(uint64(m.RX2DataRate)) - } - return n -} - -func sovLorawan(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozLorawan(x uint64) (n int) { - return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *LoRaWANData) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANData: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANData: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MHDR", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.MHDR == nil { - m.MHDR = &LoRaWANMHDR{} - } - if err := m.MHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MACPayload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.MACPayload == nil { - m.MACPayload = &LoRaWANMACPayload{} - } - if err := m.MACPayload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) - if m.MIC == nil { - m.MIC = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANMHDR) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANMHDR: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANMHDR: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) - } - m.MType = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.MType |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) - } - m.Major = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Major |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANMACPayload) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANMACPayload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANMACPayload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FHDR", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.FHDR == nil { - m.FHDR = &LoRaWANFHDR{} - } - if err := m.FHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) - } - m.FPort = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FPort |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FRMPayload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FRMPayload = append(m.FRMPayload[:0], data[iNdEx:postIndex]...) - if m.FRMPayload == nil { - m.FRMPayload = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANFHDR) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANFHDR: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANFHDR: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevAddr = append(m.DevAddr[:0], data[iNdEx:postIndex]...) - if m.DevAddr == nil { - m.DevAddr = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FCtrl", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.FCtrl == nil { - m.FCtrl = &LoRaWANFCtrl{} - } - if err := m.FCtrl.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) - } - m.FCnt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FOpts", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FOpts = append(m.FOpts, make([]byte, postIndex-iNdEx)) - copy(m.FOpts[len(m.FOpts)-1], data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANFCtrl) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANFCtrl: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANFCtrl: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ADR", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.ADR = bool(v != 0) - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ADRAckReq", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.ADRAckReq = bool(v != 0) - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.Ack = bool(v != 0) - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FPending", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.FPending = bool(v != 0) - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FOptsLen", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FOptsLen = append(m.FOptsLen[:0], data[iNdEx:postIndex]...) - if m.FOptsLen == nil { - m.FOptsLen = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANJoinRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANJoinRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANJoinRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) - if m.DevNonce == nil { - m.DevNonce = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANJoinAccept) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANJoinAccept: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANJoinAccept: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) - if m.Payload == nil { - m.Payload = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *LoRaWANDLSettings) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: LoRaWANDLSettings: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: LoRaWANDLSettings: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RX1DRoffset", wireType) - } - m.RX1DRoffset = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.RX1DRoffset |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RX2DataRate", wireType) - } - m.RX2DataRate = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.RX2DataRate |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipLorawan(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLorawan - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLorawan - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLorawan - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthLorawan - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowLorawan - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipLorawan(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthLorawan = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowLorawan = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorLorawan = []byte{ - // 491 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x74, 0x53, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0x25, 0x75, 0x92, 0xba, 0x93, 0x44, 0x6a, 0x57, 0x08, 0x2c, 0x84, 0xa2, 0xc8, 0x12, 0x52, - 0x2f, 0x44, 0xa2, 0x1c, 0x10, 0x47, 0x63, 0x63, 0x11, 0x14, 0x97, 0x68, 0x01, 0x95, 0x23, 0x8b, - 0xbd, 0x46, 0x25, 0x89, 0xd7, 0x38, 0x0b, 0xa8, 0xe2, 0xc0, 0x95, 0x4f, 0xe0, 0x93, 0x38, 0xf2, - 0x09, 0x08, 0x7e, 0x84, 0x99, 0xc9, 0xc6, 0x50, 0xa1, 0x1e, 0x9c, 0xf8, 0xbd, 0x79, 0x99, 0xf7, - 0x66, 0x76, 0x03, 0xa3, 0x95, 0x69, 0xd4, 0x27, 0x55, 0x4d, 0xeb, 0xc6, 0x58, 0x23, 0xba, 0xb9, - 0x69, 0x74, 0xf8, 0x05, 0x06, 0x73, 0x23, 0xd5, 0x59, 0x74, 0x9a, 0x28, 0xab, 0xc4, 0x1d, 0xe8, - 0x66, 0x4f, 0x12, 0x19, 0x74, 0x26, 0x9d, 0xe3, 0xc1, 0xc9, 0xd1, 0x94, 0x34, 0x53, 0x27, 0xa0, - 0x82, 0xec, 0xae, 0xf1, 0x53, 0x3c, 0x00, 0xc8, 0xa2, 0x78, 0xa1, 0x2e, 0x56, 0x46, 0x15, 0xc1, - 0x1e, 0x8b, 0x6f, 0x5e, 0x16, 0xb7, 0x65, 0x09, 0xeb, 0xf6, 0x5d, 0x1c, 0x82, 0x97, 0xcd, 0xe2, - 0xc0, 0xc3, 0x5f, 0x0c, 0xa5, 0xb7, 0x9e, 0xc5, 0xe1, 0xc3, 0x36, 0x00, 0xf5, 0x17, 0xd7, 0xa1, - 0x97, 0xbd, 0xb8, 0xa8, 0x35, 0x27, 0x18, 0xc9, 0xde, 0x9a, 0x00, 0xb3, 0xea, 0x9d, 0x69, 0xd8, - 0x8a, 0x58, 0x02, 0x61, 0x0d, 0x47, 0xff, 0xb9, 0xd1, 0x04, 0xe9, 0x55, 0x13, 0xa4, 0x3c, 0x41, - 0xe9, 0x7c, 0xd2, 0x85, 0x69, 0xec, 0xae, 0x63, 0x49, 0x40, 0x8c, 0x01, 0x52, 0x99, 0xed, 0xe6, - 0xda, 0xa6, 0x84, 0xb2, 0x65, 0xc2, 0xcf, 0x6d, 0x58, 0x6a, 0x25, 0x02, 0xd8, 0x4f, 0xf4, 0xc7, - 0xa8, 0x28, 0x1a, 0xb6, 0x1b, 0xca, 0xfd, 0x62, 0x0b, 0xc5, 0x31, 0xb6, 0x8f, 0x6d, 0xb3, 0x72, - 0xbb, 0x11, 0x97, 0x63, 0x50, 0x05, 0x2d, 0xe9, 0x4b, 0x08, 0xcc, 0x1b, 0x57, 0x96, 0xcd, 0x46, - 0x18, 0x0e, 0xdf, 0x39, 0xdc, 0xb3, 0xda, 0x6e, 0x82, 0xee, 0xc4, 0xc3, 0xae, 0xbd, 0x92, 0x40, - 0xf8, 0xb5, 0x03, 0xc3, 0x7f, 0x3b, 0xd0, 0x32, 0x23, 0x37, 0xa9, 0x2f, 0x3d, 0x85, 0x81, 0x6e, - 0xc3, 0x01, 0x32, 0x51, 0xbe, 0x94, 0xfa, 0x3d, 0x5b, 0xfb, 0xf2, 0x40, 0xed, 0x08, 0xd6, 0xe7, - 0x4b, 0x76, 0x22, 0x7d, 0xbe, 0x14, 0xb7, 0xc0, 0x4f, 0x17, 0xba, 0x2a, 0xce, 0xab, 0xb7, 0xe8, - 0x45, 0xb4, 0x5f, 0x3a, 0xcc, 0x35, 0xf2, 0x9d, 0xeb, 0x2a, 0xe8, 0xf1, 0x74, 0x7e, 0xe9, 0x70, - 0xf8, 0x1a, 0x84, 0x4b, 0xf2, 0xd4, 0x9c, 0x57, 0xd8, 0xfb, 0x83, 0xde, 0x58, 0x71, 0x03, 0xfa, - 0xb8, 0x8e, 0xc7, 0x2f, 0x67, 0x6e, 0x1b, 0xfd, 0x82, 0x11, 0xf1, 0x51, 0x5d, 0x13, 0xbf, 0xb7, - 0xe5, 0x15, 0x23, 0x72, 0x40, 0xfd, 0xa9, 0xa9, 0x72, 0xed, 0x76, 0xed, 0x17, 0x0e, 0x87, 0x77, - 0xdb, 0xb3, 0x25, 0x87, 0x28, 0xcf, 0x75, 0x6d, 0x69, 0xdf, 0xbb, 0xb3, 0x71, 0xfb, 0xae, 0xdd, - 0xc1, 0x9c, 0xb5, 0xf2, 0x64, 0xfe, 0x5c, 0x5b, 0x8b, 0x03, 0x6c, 0xc4, 0x04, 0x06, 0xf2, 0xd5, - 0xbd, 0x44, 0x9a, 0xb2, 0xdc, 0x68, 0xeb, 0x6e, 0xd4, 0xa0, 0xf9, 0x4b, 0x6d, 0x15, 0x27, 0x74, - 0xf3, 0xa5, 0xb2, 0xda, 0xdd, 0x05, 0x54, 0xb4, 0xd4, 0xa3, 0xc3, 0xef, 0xbf, 0xc6, 0x9d, 0x1f, - 0xf8, 0xfc, 0xc4, 0xe7, 0xdb, 0xef, 0xf1, 0xb5, 0x37, 0x7d, 0xfe, 0xfb, 0xdc, 0xff, 0x13, 0x00, - 0x00, 0xff, 0xff, 0x3a, 0x8f, 0x5b, 0x3a, 0x4f, 0x03, 0x00, 0x00, -} diff --git a/core/mocks/mocks.go b/core/mocks/mocks.go deleted file mode 100644 index 2dc986f03..000000000 --- a/core/mocks/mocks.go +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package mocks offers dedicated mocking interface / structures for testing -package mocks - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -// NOTE: All the code below could be generated - -// AppClient mocks the core.AppClient interface -type AppClient struct { - Failures map[string]error - InHandleData struct { - Ctx context.Context - Req *core.DataAppReq - Opts []grpc.CallOption - } - OutHandleData struct { - Res *core.DataAppRes - } - InHandleJoin struct { - Ctx context.Context - Req *core.JoinAppReq - Opts []grpc.CallOption - } - OutHandleJoin struct { - Res *core.JoinAppRes - } -} - -// NewAppClient creates a new mock AppClient -func NewAppClient() *AppClient { - return &AppClient{ - Failures: make(map[string]error), - } -} - -// HandleJoin implements the core.AppClient interface -func (m *AppClient) HandleJoin(ctx context.Context, in *core.JoinAppReq, opts ...grpc.CallOption) (*core.JoinAppRes, error) { - m.InHandleJoin.Ctx = ctx - m.InHandleJoin.Req = in - m.InHandleJoin.Opts = opts - return m.OutHandleJoin.Res, m.Failures["HandleJoin"] -} - -// HandleData implements the core.AppClient interface -func (m *AppClient) HandleData(ctx context.Context, in *core.DataAppReq, opts ...grpc.CallOption) (*core.DataAppRes, error) { - m.InHandleData.Ctx = ctx - m.InHandleData.Req = in - m.InHandleData.Opts = opts - return m.OutHandleData.Res, m.Failures["HandleData"] -} - -// HandlerClient mocks the core.HandlerClient interface -type HandlerClient struct { - Failures map[string]error - InHandleJoin struct { - Ctx context.Context - Req *core.JoinHandlerReq - Opts []grpc.CallOption - } - OutHandleJoin struct { - Res *core.JoinHandlerRes - } - InHandleDataUp struct { - Ctx context.Context - Req *core.DataUpHandlerReq - Opts []grpc.CallOption - } - OutHandleDataUp struct { - Res *core.DataUpHandlerRes - } - InHandleDataDown struct { - Ctx context.Context - Req *core.DataDownHandlerReq - Opts []grpc.CallOption - } - OutHandleDataDown struct { - Res *core.DataDownHandlerRes - } -} - -// NewHandlerClient creates a new mock HandlerClient -func NewHandlerClient() *HandlerClient { - return &HandlerClient{ - Failures: make(map[string]error), - } -} - -// HandleJoin implements the core.HandlerClient interface -func (m *HandlerClient) HandleJoin(ctx context.Context, in *core.JoinHandlerReq, opts ...grpc.CallOption) (*core.JoinHandlerRes, error) { - m.InHandleJoin.Ctx = ctx - m.InHandleJoin.Req = in - m.InHandleJoin.Opts = opts - return m.OutHandleJoin.Res, m.Failures["HandleJoin"] -} - -// HandleDataUp implements the core.HandlerClient interface -func (m *HandlerClient) HandleDataUp(ctx context.Context, in *core.DataUpHandlerReq, opts ...grpc.CallOption) (*core.DataUpHandlerRes, error) { - m.InHandleDataUp.Ctx = ctx - m.InHandleDataUp.Req = in - m.InHandleDataUp.Opts = opts - return m.OutHandleDataUp.Res, m.Failures["HandleDataUp"] -} - -// HandleDataDown implements the core.HandlerClient interface -func (m *HandlerClient) HandleDataDown(ctx context.Context, in *core.DataDownHandlerReq, opts ...grpc.CallOption) (*core.DataDownHandlerRes, error) { - m.InHandleDataDown.Ctx = ctx - m.InHandleDataDown.Req = in - m.InHandleDataDown.Opts = opts - return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] -} - -// AuthBrokerClient mocks the core.AuthBrokerClient interface -type AuthBrokerClient struct { - Failures map[string]error - InHandleData struct { - Ctx context.Context - Req *core.DataBrokerReq - Opts []grpc.CallOption - } - OutHandleData struct { - Res *core.DataBrokerRes - } - InHandleJoin struct { - Ctx context.Context - Req *core.JoinBrokerReq - Opts []grpc.CallOption - } - OutHandleJoin struct { - Res *core.JoinBrokerRes - } - InUpsertABP struct { - Ctx context.Context - Req *core.UpsertABPBrokerReq - Opts []grpc.CallOption - } - OutUpsertABP struct { - Res *core.UpsertABPBrokerRes - } - InValidateOTAA struct { - Ctx context.Context - Req *core.ValidateOTAABrokerReq - Opts []grpc.CallOption - } - OutValidateOTAA struct { - Res *core.ValidateOTAABrokerRes - } - InValidateToken struct { - Ctx context.Context - Req *core.ValidateTokenBrokerReq - Opts []grpc.CallOption - } - OutValidateToken struct { - Res *core.ValidateTokenBrokerRes - } -} - -// NewAuthBrokerClient creates a new mock AuthBrokerClient -func NewAuthBrokerClient() *AuthBrokerClient { - return &AuthBrokerClient{ - Failures: make(map[string]error), - } -} - -// HandleData implements the core.Broker interface -func (m *AuthBrokerClient) HandleData(ctx context.Context, in *core.DataBrokerReq, opts ...grpc.CallOption) (*core.DataBrokerRes, error) { - m.InHandleData.Ctx = ctx - m.InHandleData.Req = in - m.InHandleData.Opts = opts - return m.OutHandleData.Res, m.Failures["HandleData"] -} - -// HandleJoin implements the core.Broker interface -func (m *AuthBrokerClient) HandleJoin(ctx context.Context, in *core.JoinBrokerReq, opts ...grpc.CallOption) (*core.JoinBrokerRes, error) { - m.InHandleJoin.Ctx = ctx - m.InHandleJoin.Req = in - m.InHandleJoin.Opts = opts - return m.OutHandleJoin.Res, m.Failures["HandleJoin"] -} - -// UpsertABP implements the core.Broker interface -func (m *AuthBrokerClient) UpsertABP(ctx context.Context, in *core.UpsertABPBrokerReq, opts ...grpc.CallOption) (*core.UpsertABPBrokerRes, error) { - m.InUpsertABP.Ctx = ctx - m.InUpsertABP.Req = in - m.InUpsertABP.Opts = opts - return m.OutUpsertABP.Res, m.Failures["UpsertABP"] -} - -// ValidateOTAA implements the core.Broker interface -func (m *AuthBrokerClient) ValidateOTAA(ctx context.Context, in *core.ValidateOTAABrokerReq, opts ...grpc.CallOption) (*core.ValidateOTAABrokerRes, error) { - m.InValidateOTAA.Ctx = ctx - m.InValidateOTAA.Req = in - m.InValidateOTAA.Opts = opts - return m.OutValidateOTAA.Res, m.Failures["ValidateOTAA"] -} - -// ValidateToken implements the core.Broker interface -func (m *AuthBrokerClient) ValidateToken(ctx context.Context, in *core.ValidateTokenBrokerReq, opts ...grpc.CallOption) (*core.ValidateTokenBrokerRes, error) { - m.InValidateToken.Ctx = ctx - m.InValidateToken.Req = in - m.InValidateToken.Opts = opts - return m.OutValidateToken.Res, m.Failures["ValidateToken"] -} - -// RouterServer mocks the core.RouterServer interface -type RouterServer struct { - Failures map[string]error - InHandleData struct { - Ctx context.Context - Req *core.DataRouterReq - } - OutHandleData struct { - Res *core.DataRouterRes - } - InHandleStats struct { - Ctx context.Context - Req *core.StatsReq - } - OutHandleStats struct { - Res *core.StatsRes - } - InHandleJoin struct { - Ctx context.Context - Req *core.JoinRouterReq - } - OutHandleJoin struct { - Res *core.JoinRouterRes - } -} - -// NewRouterServer creates a new mock RouterServer -func NewRouterServer() *RouterServer { - return &RouterServer{ - Failures: make(map[string]error), - } -} - -// HandleData implements the core.RouterServer interface -func (m *RouterServer) HandleData(ctx context.Context, in *core.DataRouterReq) (*core.DataRouterRes, error) { - m.InHandleData.Ctx = ctx - m.InHandleData.Req = in - return m.OutHandleData.Res, m.Failures["HandleData"] -} - -// HandleStats implements the core.RouterServer interface -func (m *RouterServer) HandleStats(ctx context.Context, in *core.StatsReq) (*core.StatsRes, error) { - m.InHandleStats.Ctx = ctx - m.InHandleStats.Req = in - return m.OutHandleStats.Res, m.Failures["HandleStats"] -} - -// HandleJoin implements the core.RouterServer interface -func (m *RouterServer) HandleJoin(ctx context.Context, in *core.JoinRouterReq) (*core.JoinRouterRes, error) { - m.InHandleJoin.Ctx = ctx - m.InHandleJoin.Req = in - return m.OutHandleJoin.Res, m.Failures["HandleJoin"] -} - -// DutyManager mocks the dutycycle.DutyManager interface -type DutyManager struct { - Failures map[string]error - InUpdate struct { - ID []byte - Freq float32 - Size uint32 - Datr string - Codr string - } - InLookup struct { - ID []byte - } - OutLookup struct { - Cycles dutycycle.Cycles - } - InClose struct { - Called bool - } -} - -// NewDutyManager creates a new mock DutyManager -func NewDutyManager() *DutyManager { - return &DutyManager{ - Failures: make(map[string]error), - } -} - -// Update implements the dutycycle.DutyManager interface -func (m *DutyManager) Update(id []byte, freq float32, size uint32, datr string, codr string) error { - m.InUpdate.ID = id - m.InUpdate.Freq = freq - m.InUpdate.Size = size - m.InUpdate.Datr = datr - m.InUpdate.Codr = codr - return m.Failures["Update"] -} - -// Lookup implements the dutycycle.DutyManager interface -func (m *DutyManager) Lookup(id []byte) (dutycycle.Cycles, error) { - m.InLookup.ID = id - return m.OutLookup.Cycles, m.Failures["Lookup"] -} - -// Close implements the dutycycle.DutyManager interface -func (m *DutyManager) Close() error { - m.InClose.Called = true - return m.Failures["Close"] -} - -// HandlerServer mocks the core.HandlerServer interface -type HandlerServer struct { - Failures map[string]error - InHandleDataUp struct { - Ctx context.Context - Req *core.DataUpHandlerReq - } - OutHandleDataUp struct { - Res *core.DataUpHandlerRes - } - InHandleDataDown struct { - Ctx context.Context - Req *core.DataDownHandlerReq - } - OutHandleDataDown struct { - Res *core.DataDownHandlerRes - } - InHandleJoin struct { - Ctx context.Context - Req *core.JoinHandlerReq - } - OutHandleJoin struct { - Res *core.JoinHandlerRes - } -} - -// NewHandlerServer creates a new mock HandlerServer -func NewHandlerServer() *HandlerServer { - return &HandlerServer{ - Failures: make(map[string]error), - } -} - -// HandleDataUp implements the core.HandlerServer interface -func (m *HandlerServer) HandleDataUp(ctx context.Context, in *core.DataUpHandlerReq) (*core.DataUpHandlerRes, error) { - m.InHandleDataUp.Ctx = ctx - m.InHandleDataUp.Req = in - return m.OutHandleDataUp.Res, m.Failures["HandleDataUp"] -} - -// HandleDataDown implements the core.HandlerServer interface -func (m *HandlerServer) HandleDataDown(ctx context.Context, in *core.DataDownHandlerReq) (*core.DataDownHandlerRes, error) { - m.InHandleDataDown.Ctx = ctx - m.InHandleDataDown.Req = in - return m.OutHandleDataDown.Res, m.Failures["HandleDataDown"] -} - -// HandleJoin implements the core.HandlerServer interface -func (m *HandlerServer) HandleJoin(ctx context.Context, in *core.JoinHandlerReq) (*core.JoinHandlerRes, error) { - m.InHandleJoin.Ctx = ctx - m.InHandleJoin.Req = in - return m.OutHandleJoin.Res, m.Failures["HandleJoin"] -} diff --git a/core/otaa/session_keys.go b/core/otaa/session_keys.go index eb345311d..7c4fc95a1 100644 --- a/core/otaa/session_keys.go +++ b/core/otaa/session_keys.go @@ -5,9 +5,9 @@ package otaa import ( "crypto/aes" + "errors" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/utils/errors" ) // CalculateSessionKeys calculates the AppSKey and NwkSKey @@ -22,7 +22,7 @@ func CalculateSessionKeys(appKey types.AppKey, appNonce [3]byte, netID [3]byte, block, err := aes.NewCipher(appKey[:]) if err != nil || block.BlockSize() != 16 { - err = errors.New(errors.Structural, "Unable to create cipher to generate keys") + err = errors.New("Unable to create cipher to generate keys") return } diff --git a/core/protos/application.proto b/core/protos/application.proto deleted file mode 100644 index 551db23e8..000000000 --- a/core/protos/application.proto +++ /dev/null @@ -1,28 +0,0 @@ -syntax = "proto3"; -import "core.proto"; - -package core; - -message DataAppReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - uint32 FPort = 10; - uint32 FCnt = 11; - bytes Payload = 20; - repeated Metadata Metadata = 30; -} - -message DataAppRes {} - -message JoinAppReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - repeated Metadata Metadata = 30; -} - -message JoinAppRes {} - -service App { - rpc HandleData (DataAppReq) returns (DataAppRes); - rpc HandleJoin (JoinAppReq) returns (JoinAppRes); -} diff --git a/core/protos/broker.proto b/core/protos/broker.proto deleted file mode 100644 index 30bcb27e0..000000000 --- a/core/protos/broker.proto +++ /dev/null @@ -1,34 +0,0 @@ -syntax = "proto3"; -import "lorawan.proto"; -import "core.proto"; - -package core; - -message DataBrokerReq { - LoRaWANData Payload = 1; - Metadata Metadata = 2; -} - -message DataBrokerRes { - LoRaWANData Payload = 1; - Metadata Metadata = 2; -} - -message JoinBrokerReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - bytes DevNonce = 3; - bytes MIC = 4; - Metadata Metadata = 5; -} - -message JoinBrokerRes { - LoRaWANJoinAccept Payload = 1; - bytes DevAddr = 2; - Metadata Metadata = 3; -} - -service Broker { - rpc HandleData (DataBrokerReq) returns (DataBrokerRes); - rpc HandleJoin (JoinBrokerReq) returns (JoinBrokerRes); -} diff --git a/core/protos/broker_manager.proto b/core/protos/broker_manager.proto deleted file mode 100644 index e4a93e53a..000000000 --- a/core/protos/broker_manager.proto +++ /dev/null @@ -1,41 +0,0 @@ -syntax = "proto3"; - -package core; - -message ValidateOTAABrokerReq { - string Token = 1; - bytes AppEUI = 2; - string NetAddress = 3; -} - -message ValidateOTAABrokerRes{} - -message UpsertABPBrokerReq { - string Token = 1; - bytes AppEUI = 2; - string NetAddress = 3; - bytes DevAddr = 4; - bytes NwkSKey = 5; - uint32 Flags = 6; -} - -message UpsertABPBrokerRes {} - -message BrokerDevice { - bytes DevEUI = 1; - bytes DevAddr = 2; - bytes NwkSKey = 3; -} - -message ValidateTokenBrokerReq { - string Token = 1; - bytes AppEUI = 2; -} - -message ValidateTokenBrokerRes {} - -service BrokerManager { - rpc ValidateOTAA (ValidateOTAABrokerReq) returns (ValidateOTAABrokerRes); - rpc UpsertABP (UpsertABPBrokerReq) returns (UpsertABPBrokerRes); - rpc ValidateToken (ValidateTokenBrokerReq) returns (ValidateTokenBrokerRes); -} diff --git a/core/protos/core.proto b/core/protos/core.proto deleted file mode 100644 index 01bef9f8b..000000000 --- a/core/protos/core.proto +++ /dev/null @@ -1,36 +0,0 @@ -syntax = "proto3"; - -package core; - -message Metadata { - uint32 DutyRX1 = 1; - uint32 DutyRX2 = 2; - - float Frequency = 3; - string DataRate = 4; - string CodingRate = 5; - uint32 Timestamp = 6; - int32 Rssi = 7; - float Lsnr = 8; - uint32 PayloadSize = 9; - string Time = 10; - uint32 RFChain = 11; - int32 CRCStatus = 12; - string Modulation = 13; - bool InvPolarity = 14; - uint32 Power = 15; - uint32 Channel = 16; - - string GatewayEUI = 20; - int32 Altitude = 21; - float Longitude = 22; - float Latitude = 23; - - string ServerTime = 31; -} - -message StatsMetadata { - int32 Altitude = 1; - float Longitude = 2; - float Latitude = 3; -} diff --git a/core/protos/handler.proto b/core/protos/handler.proto deleted file mode 100644 index d75539f64..000000000 --- a/core/protos/handler.proto +++ /dev/null @@ -1,51 +0,0 @@ -syntax = "proto3"; -import "lorawan.proto"; -import "core.proto"; - -package core; - -message DataUpHandlerReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - uint32 FCnt = 10; - uint32 FPort = 11; - uint32 MType = 12; - bool FCntUpReset = 13; - bytes Payload = 20; - Metadata Metadata = 30; -} - -message DataUpHandlerRes { - LoRaWANData Payload = 20; - Metadata Metadata = 30; -} - -message DataDownHandlerReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - bytes Payload = 20; - string TTL = 30; -} - -message DataDownHandlerRes {} - -message JoinHandlerReq { - bytes AppEUI = 1; - bytes DevEUI = 2; - bytes DevNonce = 10; - bytes MIC = 11; - Metadata Metadata = 30; -} - -message JoinHandlerRes { - bytes DevAddr = 1; - LoRaWANJoinAccept Payload = 20; - bytes NwkSKey = 21; - Metadata Metadata = 30; -} - -service Handler { - rpc HandleDataUp (DataUpHandlerReq) returns (DataUpHandlerRes); - rpc HandleDataDown (DataDownHandlerReq) returns (DataDownHandlerRes); - rpc HandleJoin (JoinHandlerReq) returns (JoinHandlerRes); -} diff --git a/core/protos/handler_manager.proto b/core/protos/handler_manager.proto deleted file mode 100644 index 32cc6a2d2..000000000 --- a/core/protos/handler_manager.proto +++ /dev/null @@ -1,116 +0,0 @@ -syntax = "proto3"; - -package core; - -message UpsertOTAAHandlerReq { - string Token = 1; - bytes AppEUI = 2; - bytes DevEUI = 3; - bytes AppKey = 4; -} - -message UpsertOTAAHandlerRes{} - -message UpsertABPHandlerReq { - string Token = 1; - bytes AppEUI = 2; - bytes DevAddr = 3; - bytes NwkSKey = 4; - bytes AppSKey = 5; - uint32 Flags = 6; -} - -message UpsertABPHandlerRes {} - -message ListDevicesHandlerReq { - string Token = 1; - bytes AppEUI = 2; -} - -message ListDevicesHandlerRes { - repeated HandlerOTAADevice OTAA = 1; - repeated HandlerABPDevice ABP = 2; -} - -message HandlerABPDevice { - bytes DevAddr = 2; - bytes NwkSKey = 3; - bytes AppSKey = 4; - uint32 FCntUp = 5; - uint32 FCntDown = 6; - uint32 Flags = 7; -} - -message HandlerOTAADevice { - bytes DevEUI = 1; - bytes DevAddr = 2; - bytes NwkSKey = 3; - bytes AppSKey = 4; - bytes AppKey = 5; - uint32 FCntUp = 7; - uint32 FCntDown = 8; -} - -message GetDefaultDeviceReq { - string Token = 1; - bytes AppEUI = 2; -} - -message GetDefaultDeviceRes { - bytes AppKey = 1; -} - -message SetDefaultDeviceReq { - string Token = 1; - bytes AppEUI = 2; - bytes AppKey = 3; -} - -message GetPayloadFunctionsReq { - string Token = 1; - bytes AppEUI = 2; -} - -message GetPayloadFunctionsRes { - string Decoder = 1; - string Converter = 2; - string Validator = 3; -} - -message SetPayloadFunctionsReq { - string Token = 1; - bytes AppEUI = 2; - string Decoder = 11; - string Converter = 12; - string Validator = 13; -} - -message SetPayloadFunctionsRes { -} - -message TestPayloadFunctionsReq { - string Token = 1; - bytes AppEUI = 2; - bytes Payload = 11; - string Decoder = 21; - string Converter = 22; - string Validator = 23; -} - -message TestPayloadFunctionsRes { - string Fields = 1; - bool Valid = 2; -} - -message SetDefaultDeviceRes {} - -service HandlerManager { - rpc UpsertOTAA (UpsertOTAAHandlerReq) returns (UpsertOTAAHandlerRes); - rpc UpsertABP (UpsertABPHandlerReq) returns (UpsertABPHandlerRes); - rpc ListDevices (ListDevicesHandlerReq) returns (ListDevicesHandlerRes); - rpc GetDefaultDevice (GetDefaultDeviceReq) returns (GetDefaultDeviceRes); - rpc SetDefaultDevice (SetDefaultDeviceReq) returns (SetDefaultDeviceRes); - rpc GetPayloadFunctions (GetPayloadFunctionsReq) returns (GetPayloadFunctionsRes); - rpc SetPayloadFunctions (SetPayloadFunctionsReq) returns (SetPayloadFunctionsRes); - rpc TestPayloadFunctions (TestPayloadFunctionsReq) returns (TestPayloadFunctionsRes); -} diff --git a/core/protos/lorawan.proto b/core/protos/lorawan.proto deleted file mode 100644 index 65596253a..000000000 --- a/core/protos/lorawan.proto +++ /dev/null @@ -1,53 +0,0 @@ -syntax = "proto3"; - -package core; - -// Uplink & Downlink Data (confirmed or unconfirmed) -message LoRaWANData { - LoRaWANMHDR MHDR = 1; - LoRaWANMACPayload MACPayload = 2; - bytes MIC = 3; - -} - -message LoRaWANMHDR { - uint32 MType = 1; - uint32 Major = 2; -} - - -message LoRaWANMACPayload { - LoRaWANFHDR FHDR = 1; - uint32 FPort = 2; - bytes FRMPayload = 3; -} - -message LoRaWANFHDR { - bytes DevAddr = 1; - LoRaWANFCtrl FCtrl = 2; - uint32 FCnt = 3; - repeated bytes FOpts = 4; -} - -message LoRaWANFCtrl { - bool ADR = 1; - bool ADRAckReq = 2; - bool Ack = 3; - bool FPending = 4; - bytes FOptsLen = 5; -} - -message LoRaWANJoinRequest { - bytes DevEUI = 1; - bytes AppEUI = 2; - bytes DevNonce = 3; -} - -message LoRaWANJoinAccept { - bytes Payload = 1; -} - -message LoRaWANDLSettings { - uint32 RX1DRoffset = 1; - uint32 RX2DataRate = 2; -} diff --git a/core/protos/router.proto b/core/protos/router.proto deleted file mode 100644 index 5edea63ff..000000000 --- a/core/protos/router.proto +++ /dev/null @@ -1,43 +0,0 @@ -syntax = "proto3"; -import "lorawan.proto"; -import "core.proto"; - -package core; - -message DataRouterReq { - bytes GatewayID = 1; - LoRaWANData Payload = 2; - Metadata Metadata = 3; -} - -message DataRouterRes { - LoRaWANData Payload = 1; - Metadata Metadata = 2; -} - -message StatsReq { - bytes GatewayID = 1; - StatsMetadata Metadata = 2; -} - -message StatsRes{} - -message JoinRouterReq { - bytes GatewayID = 1; - bytes AppEUI = 2; - bytes DevEUI = 3; - bytes DevNonce = 4; - bytes MIC = 5; - Metadata Metadata = 6; -} - -message JoinRouterRes { - LoRaWANJoinAccept Payload = 1; - Metadata Metadata = 2; -} - -service Router { - rpc HandleData (DataRouterReq) returns (DataRouterRes); - rpc HandleStats (StatsReq) returns (StatsRes); - rpc HandleJoin (JoinRouterReq) returns (JoinRouterRes); -} diff --git a/core/router.pb.go b/core/router.pb.go deleted file mode 100644 index 7fe956394..000000000 --- a/core/router.pb.go +++ /dev/null @@ -1,1566 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: router.proto -// DO NOT EDIT! - -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -type DataRouterReq struct { - GatewayID []byte `protobuf:"bytes,1,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` - Payload *LoRaWANData `protobuf:"bytes,2,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,3,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataRouterReq) Reset() { *m = DataRouterReq{} } -func (m *DataRouterReq) String() string { return proto.CompactTextString(m) } -func (*DataRouterReq) ProtoMessage() {} -func (*DataRouterReq) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{0} } - -func (m *DataRouterReq) GetPayload() *LoRaWANData { - if m != nil { - return m.Payload - } - return nil -} - -func (m *DataRouterReq) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type DataRouterRes struct { - Payload *LoRaWANData `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *DataRouterRes) Reset() { *m = DataRouterRes{} } -func (m *DataRouterRes) String() string { return proto.CompactTextString(m) } -func (*DataRouterRes) ProtoMessage() {} -func (*DataRouterRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{1} } - -func (m *DataRouterRes) GetPayload() *LoRaWANData { - if m != nil { - return m.Payload - } - return nil -} - -func (m *DataRouterRes) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type StatsReq struct { - GatewayID []byte `protobuf:"bytes,1,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` - Metadata *StatsMetadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *StatsReq) Reset() { *m = StatsReq{} } -func (m *StatsReq) String() string { return proto.CompactTextString(m) } -func (*StatsReq) ProtoMessage() {} -func (*StatsReq) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{2} } - -func (m *StatsReq) GetMetadata() *StatsMetadata { - if m != nil { - return m.Metadata - } - return nil -} - -type StatsRes struct { -} - -func (m *StatsRes) Reset() { *m = StatsRes{} } -func (m *StatsRes) String() string { return proto.CompactTextString(m) } -func (*StatsRes) ProtoMessage() {} -func (*StatsRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{3} } - -type JoinRouterReq struct { - GatewayID []byte `protobuf:"bytes,1,opt,name=GatewayID,json=gatewayID,proto3" json:"GatewayID,omitempty"` - AppEUI []byte `protobuf:"bytes,2,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - DevEUI []byte `protobuf:"bytes,3,opt,name=DevEUI,json=devEUI,proto3" json:"DevEUI,omitempty"` - DevNonce []byte `protobuf:"bytes,4,opt,name=DevNonce,json=devNonce,proto3" json:"DevNonce,omitempty"` - MIC []byte `protobuf:"bytes,5,opt,name=MIC,json=mIC,proto3" json:"MIC,omitempty"` - Metadata *Metadata `protobuf:"bytes,6,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinRouterReq) Reset() { *m = JoinRouterReq{} } -func (m *JoinRouterReq) String() string { return proto.CompactTextString(m) } -func (*JoinRouterReq) ProtoMessage() {} -func (*JoinRouterReq) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{4} } - -func (m *JoinRouterReq) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -type JoinRouterRes struct { - Payload *LoRaWANJoinAccept `protobuf:"bytes,1,opt,name=Payload,json=payload" json:"Payload,omitempty"` - Metadata *Metadata `protobuf:"bytes,2,opt,name=Metadata,json=metadata" json:"Metadata,omitempty"` -} - -func (m *JoinRouterRes) Reset() { *m = JoinRouterRes{} } -func (m *JoinRouterRes) String() string { return proto.CompactTextString(m) } -func (*JoinRouterRes) ProtoMessage() {} -func (*JoinRouterRes) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{5} } - -func (m *JoinRouterRes) GetPayload() *LoRaWANJoinAccept { - if m != nil { - return m.Payload - } - return nil -} - -func (m *JoinRouterRes) GetMetadata() *Metadata { - if m != nil { - return m.Metadata - } - return nil -} - -func init() { - proto.RegisterType((*DataRouterReq)(nil), "core.DataRouterReq") - proto.RegisterType((*DataRouterRes)(nil), "core.DataRouterRes") - proto.RegisterType((*StatsReq)(nil), "core.StatsReq") - proto.RegisterType((*StatsRes)(nil), "core.StatsRes") - proto.RegisterType((*JoinRouterReq)(nil), "core.JoinRouterReq") - proto.RegisterType((*JoinRouterRes)(nil), "core.JoinRouterRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 - -// Client API for Router service - -type RouterClient interface { - HandleData(ctx context.Context, in *DataRouterReq, opts ...grpc.CallOption) (*DataRouterRes, error) - HandleStats(ctx context.Context, in *StatsReq, opts ...grpc.CallOption) (*StatsRes, error) - HandleJoin(ctx context.Context, in *JoinRouterReq, opts ...grpc.CallOption) (*JoinRouterRes, error) -} - -type routerClient struct { - cc *grpc.ClientConn -} - -func NewRouterClient(cc *grpc.ClientConn) RouterClient { - return &routerClient{cc} -} - -func (c *routerClient) HandleData(ctx context.Context, in *DataRouterReq, opts ...grpc.CallOption) (*DataRouterRes, error) { - out := new(DataRouterRes) - err := grpc.Invoke(ctx, "/core.Router/HandleData", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *routerClient) HandleStats(ctx context.Context, in *StatsReq, opts ...grpc.CallOption) (*StatsRes, error) { - out := new(StatsRes) - err := grpc.Invoke(ctx, "/core.Router/HandleStats", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *routerClient) HandleJoin(ctx context.Context, in *JoinRouterReq, opts ...grpc.CallOption) (*JoinRouterRes, error) { - out := new(JoinRouterRes) - err := grpc.Invoke(ctx, "/core.Router/HandleJoin", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for Router service - -type RouterServer interface { - HandleData(context.Context, *DataRouterReq) (*DataRouterRes, error) - HandleStats(context.Context, *StatsReq) (*StatsRes, error) - HandleJoin(context.Context, *JoinRouterReq) (*JoinRouterRes, error) -} - -func RegisterRouterServer(s *grpc.Server, srv RouterServer) { - s.RegisterService(&_Router_serviceDesc, srv) -} - -func _Router_HandleData_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DataRouterReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterServer).HandleData(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Router/HandleData", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterServer).HandleData(ctx, req.(*DataRouterReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _Router_HandleStats_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(StatsReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterServer).HandleStats(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Router/HandleStats", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterServer).HandleStats(ctx, req.(*StatsReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _Router_HandleJoin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(JoinRouterReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterServer).HandleJoin(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.Router/HandleJoin", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterServer).HandleJoin(ctx, req.(*JoinRouterReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _Router_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.Router", - HandlerType: (*RouterServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "HandleData", - Handler: _Router_HandleData_Handler, - }, - { - MethodName: "HandleStats", - Handler: _Router_HandleStats_Handler, - }, - { - MethodName: "HandleJoin", - Handler: _Router_HandleJoin_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, -} - -func (m *DataRouterReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataRouterReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.GatewayID) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } - if m.Payload != nil { - data[i] = 0x12 - i++ - i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) - n1, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 - } - if m.Metadata != nil { - data[i] = 0x1a - i++ - i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) - n2, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 - } - return i, nil -} - -func (m *DataRouterRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DataRouterRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Payload != nil { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) - n3, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } - if m.Metadata != nil { - data[i] = 0x12 - i++ - i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) - n4, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n4 - } - return i, nil -} - -func (m *StatsReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *StatsReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.GatewayID) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } - if m.Metadata != nil { - data[i] = 0x12 - i++ - i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) - n5, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n5 - } - return i, nil -} - -func (m *StatsRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *StatsRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *JoinRouterReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinRouterReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.GatewayID) > 0 { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayID))) - i += copy(data[i:], m.GatewayID) - } - if len(m.AppEUI) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintRouter(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.DevEUI) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintRouter(data, i, uint64(len(m.DevEUI))) - i += copy(data[i:], m.DevEUI) - } - if len(m.DevNonce) > 0 { - data[i] = 0x22 - i++ - i = encodeVarintRouter(data, i, uint64(len(m.DevNonce))) - i += copy(data[i:], m.DevNonce) - } - if len(m.MIC) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintRouter(data, i, uint64(len(m.MIC))) - i += copy(data[i:], m.MIC) - } - if m.Metadata != nil { - data[i] = 0x32 - i++ - i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) - n6, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n6 - } - return i, nil -} - -func (m *JoinRouterRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *JoinRouterRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Payload != nil { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(m.Payload.Size())) - n7, err := m.Payload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n7 - } - if m.Metadata != nil { - data[i] = 0x12 - i++ - i = encodeVarintRouter(data, i, uint64(m.Metadata.Size())) - n8, err := m.Metadata.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n8 - } - return i, nil -} - -func encodeFixed64Router(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Router(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintRouter(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *DataRouterReq) Size() (n int) { - var l int - _ = l - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovRouter(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - -func (m *DataRouterRes) Size() (n int) { - var l int - _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovRouter(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - -func (m *StatsReq) Size() (n int) { - var l int - _ = l - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - -func (m *StatsRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *JoinRouterReq) Size() (n int) { - var l int - _ = l - l = len(m.GatewayID) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - l = len(m.DevEUI) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - l = len(m.DevNonce) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - l = len(m.MIC) - if l > 0 { - n += 1 + l + sovRouter(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - -func (m *JoinRouterRes) Size() (n int) { - var l int - _ = l - if m.Payload != nil { - l = m.Payload.Size() - n += 1 + l + sovRouter(uint64(l)) - } - if m.Metadata != nil { - l = m.Metadata.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - -func sovRouter(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozRouter(x uint64) (n int) { - return sovRouter(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *DataRouterReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataRouterReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataRouterReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) - if m.GatewayID == nil { - m.GatewayID = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANData{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DataRouterRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DataRouterRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DataRouterRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANData{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *StatsReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StatsReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StatsReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) - if m.GatewayID == nil { - m.GatewayID = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &StatsMetadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *StatsRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: StatsRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: StatsRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinRouterReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinRouterReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinRouterReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayID", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.GatewayID = append(m.GatewayID[:0], data[iNdEx:postIndex]...) - if m.GatewayID == nil { - m.GatewayID = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevEUI = append(m.DevEUI[:0], data[iNdEx:postIndex]...) - if m.DevEUI == nil { - m.DevEUI = []byte{} - } - iNdEx = postIndex - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.DevNonce = append(m.DevNonce[:0], data[iNdEx:postIndex]...) - if m.DevNonce == nil { - m.DevNonce = []byte{} - } - iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MIC", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.MIC = append(m.MIC[:0], data[iNdEx:postIndex]...) - if m.MIC == nil { - m.MIC = []byte{} - } - iNdEx = postIndex - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *JoinRouterRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: JoinRouterRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: JoinRouterRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Payload == nil { - m.Payload = &LoRaWANJoinAccept{} - } - if err := m.Payload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Metadata == nil { - m.Metadata = &Metadata{} - } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipRouter(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowRouter - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowRouter - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowRouter - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthRouter - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowRouter - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipRouter(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthRouter = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowRouter = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorRouter = []byte{ - // 383 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0xca, 0x2f, 0x2d, - 0x49, 0x2d, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, 0x4a, 0x95, 0xe2, - 0xcd, 0xc9, 0x2f, 0x4a, 0x2c, 0x4f, 0xcc, 0x83, 0x08, 0x4a, 0x71, 0x81, 0x04, 0x21, 0x6c, 0xa5, - 0x36, 0x46, 0x2e, 0x5e, 0x97, 0xc4, 0x92, 0xc4, 0x20, 0xb0, 0xae, 0xa0, 0xd4, 0x42, 0x21, 0x19, - 0x2e, 0x4e, 0xf7, 0xc4, 0x92, 0xd4, 0xf2, 0xc4, 0x4a, 0x4f, 0x17, 0x09, 0x46, 0x05, 0x46, 0x0d, - 0x9e, 0x20, 0xce, 0x74, 0x98, 0x80, 0x90, 0x36, 0x17, 0x7b, 0x40, 0x62, 0x65, 0x4e, 0x7e, 0x62, - 0x8a, 0x04, 0x13, 0x50, 0x8e, 0xdb, 0x48, 0x50, 0x0f, 0x6c, 0x9a, 0x4f, 0x7e, 0x50, 0x62, 0xb8, - 0xa3, 0x1f, 0xd8, 0x28, 0xf6, 0x02, 0x88, 0x0a, 0x21, 0x2d, 0x2e, 0x0e, 0xdf, 0xd4, 0x92, 0xc4, - 0x14, 0xa0, 0xa0, 0x04, 0x33, 0x58, 0x35, 0x1f, 0x44, 0x35, 0x4c, 0x34, 0x88, 0x23, 0x17, 0xca, - 0x52, 0xca, 0x40, 0x75, 0x47, 0x31, 0xb2, 0x4d, 0x8c, 0x24, 0xd9, 0xc4, 0x44, 0xc0, 0xa6, 0x48, - 0x2e, 0x8e, 0xe0, 0x92, 0xc4, 0x92, 0x62, 0xc2, 0x9e, 0xd5, 0xc7, 0x30, 0x55, 0x18, 0x62, 0x2a, - 0x58, 0x3f, 0x16, 0xa3, 0xb9, 0xe0, 0x46, 0x17, 0x2b, 0x6d, 0x07, 0x86, 0xac, 0x57, 0x7e, 0x66, - 0x1e, 0xb1, 0x21, 0x2b, 0xc6, 0xc5, 0xe6, 0x58, 0x50, 0xe0, 0x1a, 0xea, 0x09, 0xb6, 0x8a, 0x27, - 0x88, 0x2d, 0x11, 0xcc, 0x03, 0x89, 0xbb, 0xa4, 0x96, 0x81, 0xc4, 0x99, 0x21, 0xe2, 0x29, 0x60, - 0x9e, 0x90, 0x14, 0x17, 0x07, 0x50, 0xdc, 0x2f, 0x3f, 0x2f, 0x39, 0x55, 0x82, 0x05, 0x2c, 0xc3, - 0x91, 0x02, 0xe5, 0x0b, 0x09, 0x70, 0x31, 0xfb, 0x7a, 0x3a, 0x4b, 0xb0, 0x82, 0x85, 0x99, 0x73, - 0x3d, 0x9d, 0x51, 0x02, 0x88, 0x8d, 0x40, 0x00, 0xe5, 0xa1, 0x3a, 0xbc, 0x58, 0xc8, 0x10, 0x3d, - 0x2a, 0xc4, 0x51, 0xa2, 0x02, 0xa4, 0xd8, 0x31, 0x39, 0x39, 0xb5, 0xa0, 0x84, 0xac, 0x08, 0x31, - 0x5a, 0xce, 0xc8, 0xc5, 0x06, 0xb1, 0x4c, 0xc8, 0x8c, 0x8b, 0xcb, 0x23, 0x31, 0x2f, 0x25, 0x27, - 0x15, 0x14, 0xbd, 0x42, 0xd0, 0xd0, 0x46, 0x49, 0x9f, 0x52, 0x58, 0x04, 0x8b, 0x85, 0x74, 0xb9, - 0xb8, 0x21, 0xfa, 0xc0, 0xc1, 0x2f, 0xc4, 0x87, 0x14, 0x4d, 0x20, 0x3d, 0xa8, 0xfc, 0x62, 0x84, - 0x35, 0x20, 0xa7, 0xc3, 0xac, 0x41, 0x89, 0x2c, 0x29, 0x2c, 0x82, 0xc5, 0x4e, 0x02, 0x27, 0x1e, - 0xc9, 0x31, 0x5e, 0x00, 0xe2, 0x07, 0x40, 0x3c, 0xe3, 0xb1, 0x1c, 0x43, 0x12, 0x1b, 0x38, 0x1b, - 0x19, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0xf4, 0xb5, 0x24, 0xc6, 0x77, 0x03, 0x00, 0x00, -} diff --git a/core/storage/storage.go b/core/storage/storage.go deleted file mode 100644 index c1ac5751f..000000000 --- a/core/storage/storage.go +++ /dev/null @@ -1,315 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package storage - -import ( - "encoding" - "fmt" - "reflect" - "time" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/readwriter" - "github.com/boltdb/bolt" -) - -// The storage Interface provides an abstraction on top of the bolt database to store and access -// data in a local in-memory database. -// All methods uses a slice of buckets as bucket selector, each subsequent bucket being nested in -// the previous one. Each time also, the key can be omitted in which case it will fall back to a -// default key. -type Interface interface { - // Read retrieves a slice of entries from the storage. The output is a slice of the same type as - // of `shape`, possibly of length 0. - // The provided type has to implement a binary.Unmarshaler interface - Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) - // ReadAll goes through each key of a bucket and return a slice of all values. This implies - // therefore that all values in each key are of the same type (the shape provided) - ReadAll(shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) - // Update replaces a set of entries by the new given set - Update(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error - // Append adds at the end of an existing set the new given set - Append(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error - // Delete complete removes all targeted entries - Delete(key []byte, buckets ...[]byte) error - // Reset empties a bucket - Reset(buckets ...[]byte) error - // Close terminates the communication with the storage - Close() error -} - -var defaultKey = []byte("entry") - -type store struct { - db *bolt.DB -} - -// New creates a new storage instance ready-to-be-used -func New(name string) (Interface, error) { - db, err := bolt.Open(name, 0600, &bolt.Options{Timeout: time.Second}) - if err != nil { - return nil, errors.New(errors.Operational, err) - } - return store{db}, nil -} - -// Make sure we return a failure -func ensureErr(err error) error { - if err == nil { - return nil - } - _, ok := err.(errors.Failure) - if !ok { - err = errors.New(errors.Operational, err) - } - return err -} - -// getBucket retrieves a bucket based on a slice of ordered identifiers. Each following identifier -// targets a nested bucket in the previous one. If no bucket is found along the path, they are -// created if the write rights are granted by Tx, otherwise, the existing one is used. -func getBucket(tx *bolt.Tx, buckets [][]byte) (*bolt.Bucket, error) { - if len(buckets) < 1 { - return nil, errors.New(errors.Structural, "At least one bucket name is required") - } - var cursor interface { - CreateBucketIfNotExists(b []byte) (*bolt.Bucket, error) - Bucket(b []byte) *bolt.Bucket - } - - var err error - cursor = tx - for _, name := range buckets { - next := cursor.Bucket(name) - if next == nil { - if next, err = cursor.CreateBucketIfNotExists(name); err != nil { - return nil, errors.New(errors.Operational, err) - } - } - cursor = next - } - return cursor.(*bolt.Bucket), nil -} - -// ReadAll implements the storage.Interface interface -func (itf store) ReadAll(shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) { - entryType := reflect.TypeOf(shape) - if entryType.Kind() != reflect.Ptr { - return nil, errors.New(errors.Implementation, "Non-pointer shape not supported") - } - - entries := reflect.MakeSlice(reflect.SliceOf(entryType.Elem()), 0, 0) - err := itf.db.View(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, buckets) - if err != nil { - if err.(errors.Failure).Fault == bolt.ErrTxNotWritable { - return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", buckets)) - } - return err - } - return bucket.ForEach(func(_ []byte, v []byte) error { - r := readwriter.New(v) - r.Read(func(data []byte) { - entry := reflect.New(entryType.Elem()).Interface() - entry.(encoding.BinaryUnmarshaler).UnmarshalBinary(data) - entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) - }) - return r.Err() - }) - }) - if err != nil { - return nil, ensureErr(err) - } - if entries.Len() == 0 { - return nil, errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", buckets)) - } - return entries.Interface(), nil -} - -// Read implements the storage.Interface interface -func (itf store) Read(key []byte, shape encoding.BinaryUnmarshaler, buckets ...[]byte) (interface{}, error) { - if key == nil { - key = defaultKey - } - - entryType := reflect.TypeOf(shape) - if entryType.Kind() != reflect.Ptr { - return nil, errors.New(errors.Implementation, "Non-pointer shape not supported") - } - - // First, lookup the raw entries - var rawEntry []byte - err := itf.db.View(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, buckets) - if err != nil { - if err.(errors.Failure).Fault == bolt.ErrTxNotWritable { - return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) - } - return err - } - rawEntry = bucket.Get(key) - if rawEntry == nil { - return errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) - } - return nil - }) - - if err != nil { - return nil, ensureErr(err) - } - - // Then, interpret them as instance of 'shape' - r := readwriter.New(rawEntry) - entries := reflect.MakeSlice(reflect.SliceOf(entryType.Elem()), 0, 0) - var nb uint - for { - r.Read(func(data []byte) { - entry := reflect.New(entryType.Elem()).Interface() - entry.(encoding.BinaryUnmarshaler).UnmarshalBinary(data) - entries = reflect.Append(entries, reflect.ValueOf(entry).Elem()) - nb++ - }) - if err = r.Err(); err != nil { - failure, ok := err.(errors.Failure) - if ok && failure.Nature == errors.Behavioural { - break - } - return nil, errors.New(errors.Operational, err) - } - } - if nb == 0 { - return nil, errors.New(errors.NotFound, fmt.Sprintf("Not found %+v", key)) - } - return entries.Interface(), nil -} - -// Append implements the storage.Interface interface -func (itf store) Append(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error { - if key == nil { - key = defaultKey - } - var marshalled [][]byte - - for _, entry := range entries { - m, err := entry.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - marshalled = append(marshalled, m) - } - - err := itf.db.Update(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, buckets) - if err != nil { - return err - } - w := readwriter.New(bucket.Get(key)) - for _, m := range marshalled { - w.Write(m) - } - data, err := w.Bytes() - if err != nil { - return errors.New(errors.Structural, err) - } - if err := bucket.Put(key, data); err != nil { - return errors.New(errors.Operational, err) - } - return nil - }) - - return ensureErr(err) -} - -// Update implements the storage.Interface interface -func (itf store) Update(key []byte, entries []encoding.BinaryMarshaler, buckets ...[]byte) error { - if key == nil { - key = defaultKey - } - var marshalled [][]byte - - for _, entry := range entries { - m, err := entry.MarshalBinary() - if err != nil { - return errors.New(errors.Structural, err) - } - marshalled = append(marshalled, m) - } - - return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, buckets) - if err != nil { - return err - } - if err := bucket.Delete(key); err != nil { - return errors.New(errors.Operational, err) - } - w := readwriter.New(bucket.Get(key)) - for _, m := range marshalled { - w.Write(m) - } - data, err := w.Bytes() - if err != nil { - return errors.New(errors.Structural, err) - } - if err := bucket.Put(key, data); err != nil { - return errors.New(errors.Operational, err) - } - return nil - })) -} - -// Delete implements the storage.Interface interface -func (itf store) Delete(key []byte, buckets ...[]byte) error { - return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { - bucket, err := getBucket(tx, buckets) - if err != nil { - return err - } - if err := bucket.Delete(key); err != nil { - return errors.New(errors.Operational, err) - } - return nil - })) -} - -// Reset implements the storage.Interface interface -func (itf store) Reset(buckets ...[]byte) (err error) { - if len(buckets) == 0 { - return errors.New(errors.Structural, "Expected at least one bucket") - } - - return ensureErr(itf.db.Update(func(tx *bolt.Tx) error { - var cursor interface { - DeleteBucket(name []byte) error - CreateBucketIfNotExists(name []byte) (*bolt.Bucket, error) - } - - init, last := buckets[:len(buckets)-1], buckets[len(buckets)-1] - - if len(init) == 0 { - cursor = tx - } else { - cursor, err = getBucket(tx, init) - if err != nil { - return err - } - } - - if err := cursor.DeleteBucket(last); err != nil { - return errors.New(errors.Operational, err) - } - if _, err := cursor.CreateBucketIfNotExists(last); err != nil { - return errors.New(errors.Operational, err) - } - return nil - })) -} - -// Close implements the storage.Interface interface -func (itf store) Close() error { - if err := itf.db.Close(); err != nil { - return errors.New(errors.Operational, err) - } - return nil -} diff --git a/core/storage/storage_test.go b/core/storage/storage_test.go deleted file mode 100644 index 420d14079..000000000 --- a/core/storage/storage_test.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package storage - -import ( - "encoding" - "os" - "path" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -const database = "TestStoreAndLookup.db" - -func TestStoreAndRead(t *testing.T) { - var itf Interface - defer func() { - if itf == nil { - return - } - if err := itf.Close(); err != nil { - panic(err) - } - os.Remove(path.Join(os.TempDir(), database)) - }() - - // -------------------- - - { - Desc(t, "Open database in tmp folder") - var err error - itf, err = New(path.Join(os.TempDir(), database)) - CheckErrors(t, nil, err) - } - - // -------------------- - - { - Desc(t, "Open database in a forbidden place") - _, err := New("/usr/bin") - CheckErrors(t, ErrOperational, err) - } - - // -------------------- - - { - Desc(t, "Store then lookup in a 1-level bucket") - err := itf.Append([]byte{1, 2, 3}, []encoding.BinaryMarshaler{testEntry{Data: "TTN"}}, []byte("bucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("bucket")) - - CheckErrors(t, nil, err) - Check(t, []testEntry{testEntry{Data: "TTN"}}, entries.([]testEntry), "Entries") - } - - // ------------------- - - { - Desc(t, "Store then lookup in a nested bucket") - err := itf.Append([]byte{14, 42}, []encoding.BinaryMarshaler{&testEntry{Data: "IoT"}}, []byte("nested"), []byte("bucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{14, 42}, &testEntry{}, []byte("nested"), []byte("bucket")) - - CheckErrors(t, nil, err) - Check(t, []testEntry{testEntry{Data: "IoT"}}, entries.([]testEntry), "Entries") - } - - // ------------------- - - { - Desc(t, "Store then lookup in a nested bucket with default key") - err := itf.Append(nil, []encoding.BinaryMarshaler{&testEntry{Data: "IoT"}}, []byte("nested"), []byte("buckettt")) - FatalUnless(t, err) - entries, err := itf.Read(nil, &testEntry{}, []byte("nested"), []byte("buckettt")) - - CheckErrors(t, nil, err) - Check(t, []testEntry{testEntry{Data: "IoT"}}, entries.([]testEntry), "Entries") - } - - // ------------------- - - { - Desc(t, "Lookup in non-existing bucket") - entries, err := itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("DoesnExists")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Lookup a non-existing key") - entries, err := itf.Read([]byte{9, 9, 9, 9, 9}, &testEntry{}, []byte("bucket")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Flush an 1-level bucket entry") - itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("bucket")) - err := itf.Delete([]byte{1, 1, 1}, []byte("bucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{1, 1, 1}, &testEntry{}, []byte("bucket")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Flush a nested bucket entry") - itf.Append([]byte{2, 2, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("nested"), []byte("bucket")) - err := itf.Delete([]byte{2, 2, 2}, []byte("nested"), []byte("bucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{2, 2, 2}, &testEntry{}, []byte("nested"), []byte("bucket")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Reset a 1-level bucket") - itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{testEntry{Data: "TTN"}}, []byte("mybucket")) - err := itf.Reset([]byte("mybucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{1, 1, 1}, &testEntry{}, []byte("mybucket")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Reset a nested bucket") - itf.Append([]byte{2, 2, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("mybucket"), []byte("nested")) - err := itf.Reset([]byte("mybucket"), []byte("nested")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{2, 2, 2}, &testEntry{}, []byte("mybucket"), []byte("nested")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Reset a nested bucket parent") - itf.Append([]byte{2, 2, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("mybucket"), []byte("nested")) - err := itf.Reset([]byte("mybucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{2, 2, 2}, &testEntry{}, []byte("mybucket"), []byte("nested")) - - CheckErrors(t, ErrNotFound, err) - Check(t, nil, entries, "Entries") - } - - // ------------------- - - { - Desc(t, "Replace an existing entry in a bucket") - itf.Append([]byte{14, 14, 14}, []encoding.BinaryMarshaler{&testEntry{Data: "I don't like IoT"}}, []byte("anotherbucket")) - err := itf.Update([]byte{14, 14, 14}, []encoding.BinaryMarshaler{testEntry{Data: "IoT is Awesome"}}, []byte("anotherbucket")) - FatalUnless(t, err) - entries, err := itf.Read([]byte{14, 14, 14}, &testEntry{}, []byte("anotherbucket")) - - CheckErrors(t, nil, err) - Check(t, []testEntry{{Data: "IoT is Awesome"}}, entries.([]testEntry), "Entries") - } - - // ------------------- - - { - Desc(t, "Store several entries under the same key") - itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "FirstEntry"}}, []byte("several")) - itf.Append([]byte{1, 1, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "SecondEntry"}, &testEntry{Data: "ThirdEntry"}}, []byte("several")) - entries, err := itf.Read([]byte{1, 1, 1}, &testEntry{}, []byte("several")) - - CheckErrors(t, nil, err) - Check(t, []testEntry{{Data: "FirstEntry"}, {Data: "SecondEntry"}, {Data: "ThirdEntry"}}, entries.([]testEntry), "Entries") - } - - // -------------------- - - { - Desc(t, "Store, Read, Update and Delete with default key") - err := itf.Append(nil, []encoding.BinaryMarshaler{&testEntry{Data: "Patate"}}, []byte("defaultkeybucket")) - CheckErrors(t, nil, err) - _, err = itf.Read(nil, &testEntry{}, []byte("defaultkeybucket")) - CheckErrors(t, nil, err) - err = itf.Update(nil, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("defaultkeybucket")) - CheckErrors(t, nil, err) - err = itf.Delete(nil, []byte("defaultkeybucket")) - CheckErrors(t, nil, err) - } - - // -------------------- - - { - Desc(t, "Store, Read, Update, Delete & Reset with no bucket names") - err := itf.Append([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "Patate"}}) - CheckErrors(t, ErrStructural, err) - _, err = itf.Read([]byte{1, 2, 3}, &testEntry{}) - CheckErrors(t, ErrStructural, err) - err = itf.Update([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}) - CheckErrors(t, ErrStructural, err) - err = itf.Delete([]byte{1, 2, 3}) - CheckErrors(t, ErrStructural, err) - err = itf.Reset() - CheckErrors(t, ErrStructural, err) - } - - // --------------------- - - { - Desc(t, "Read All entry of a bucket") - err := itf.Update([]byte{0, 0, 1}, []encoding.BinaryMarshaler{&testEntry{Data: "The"}}, []byte("level1")) - FatalUnless(t, err) - err = itf.Update([]byte{0, 0, 2}, []encoding.BinaryMarshaler{&testEntry{Data: "Things"}}, []byte("level1")) - FatalUnless(t, err) - err = itf.Update([]byte{0, 0, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "Network"}}, []byte("level1")) - FatalUnless(t, err) - entries, err := itf.ReadAll(&testEntry{}, []byte("level1")) - want := []testEntry{{Data: "The"}, {Data: "Things"}, {Data: "Network"}} - CheckErrors(t, nil, err) - Check(t, want, entries, "Entries") - } - - // --------------------- - - { - Desc(t, "Store, Read, Update, Delete & Reset on closed storage") - _ = itf.Close() - err := itf.Append([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "Patate"}}, []byte("closeddb")) - CheckErrors(t, ErrOperational, err) - _, err = itf.Read([]byte{1, 2, 3}, &testEntry{}, []byte("closeddb")) - CheckErrors(t, ErrOperational, err) - _, err = itf.ReadAll(&testEntry{}, []byte("closeddb")) - CheckErrors(t, ErrOperational, err) - err = itf.Update([]byte{1, 2, 3}, []encoding.BinaryMarshaler{&testEntry{Data: "TTN"}}, []byte("closeddb")) - CheckErrors(t, ErrOperational, err) - err = itf.Delete([]byte{1, 2, 3}, []byte("closeddb")) - CheckErrors(t, ErrOperational, err) - err = itf.Reset([]byte("closeddb")) - CheckErrors(t, ErrOperational, err) - } - -} - -// ----- Type Utilities - -type testEntry struct { - Data string -} - -func (e testEntry) MarshalBinary() ([]byte, error) { - return []byte(e.Data), nil -} - -func (e *testEntry) UnmarshalBinary(data []byte) error { - e.Data = string(data) - return nil -} diff --git a/semtech/decode.go b/semtech/decode.go deleted file mode 100644 index 5cffa38d5..000000000 --- a/semtech/decode.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "encoding/json" - "errors" - "strings" - "time" -) - -// Unmarshal parses a raw response from a server and turn in into a packet. -// Will return an error if the response fields are incorrect. -func (p *Packet) UnmarshalBinary(raw []byte) error { - size := len(raw) - - if size < 4 { - return errors.New("Invalid raw data format") - } - - packet := Packet{ - Version: raw[0], - Token: raw[1:3], - Identifier: raw[3], - } - - if packet.Version != VERSION { - return errors.New("Unreckognized protocol version") - } - - if packet.Identifier > PULL_ACK { - return errors.New("Unreckognized protocol identifier") - } - - if packet.Identifier == PULL_RESP { - packet.Token = nil - } - - cursor := 4 - if packet.Identifier == PULL_DATA || packet.Identifier == PUSH_DATA { - if size < 12 { - return errors.New("Invalid gateway identifier") - } - packet.GatewayId = raw[cursor:12] - cursor = 12 - } - - var err error - if packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { - packet.Payload = new(Payload) - if size > cursor { - err = json.Unmarshal(raw[cursor:], packet.Payload) - } - } - - if err == nil { - *p = packet - } - - return err -} - -// UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (t *Timeparser) UnmarshalJSON(raw []byte) error { - str := strings.Trim(string(raw), `"`) - v, err := time.Parse("2006-01-02 15:04:05 GMT", str) - if err != nil { - v, err = time.Parse(time.RFC3339, str) - } - if err != nil { - v, err = time.Parse(time.RFC3339Nano, str) - } - if err != nil { - return errors.New("Unkown date format. Unable to parse time") - } - t.Value = &v - return nil -} - -// UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (d *Datrparser) UnmarshalJSON(raw []byte) error { - v := strings.Trim(string(raw), `"`) - - if v == "" { - return errors.New("Invalid datr format") - } - - d.Value = v - return nil -} - -// UnmarshalJSON implements the Unmarshaler interface from encoding/json -func (p *Payload) UnmarshalJSON(raw []byte) error { - proxy := payloadProxy{ - ProxStat: new(statProxy), - ProxTXPK: new(txpkProxy), - } - - if err := json.Unmarshal(raw, &proxy); err != nil { - return err - } - - if proxy.ProxStat.Time != nil { - if proxy.ProxStat.Stat == nil { - proxy.ProxStat.Stat = new(Stat) - } - proxy.ProxStat.Stat.Time = proxy.ProxStat.Time.Value - } - p.Stat = proxy.ProxStat.Stat - - if proxy.ProxTXPK.Time != nil { - if proxy.ProxTXPK.TXPK == nil { - proxy.ProxTXPK.TXPK = new(TXPK) - } - proxy.ProxTXPK.TXPK.Time = proxy.ProxTXPK.Time.Value - } - - if proxy.ProxTXPK.Datr != nil { - if proxy.ProxTXPK.TXPK == nil { - proxy.ProxTXPK.TXPK = new(TXPK) - } - proxy.ProxTXPK.TXPK.Datr = &proxy.ProxTXPK.Datr.Value - } - - p.TXPK = proxy.ProxTXPK.TXPK - - for _, rxpk := range proxy.ProxRXPK { - if rxpk.RXPK == nil { - rxpk.RXPK = new(RXPK) - } - if rxpk.Time != nil { - rxpk.RXPK.Time = rxpk.Time.Value - } - if rxpk.Datr != nil { - rxpk.RXPK.Datr = &rxpk.Datr.Value - } - p.RXPK = append(p.RXPK, *rxpk.RXPK) - } - - return nil -} diff --git a/semtech/decode_test.go b/semtech/decode_test.go deleted file mode 100644 index 082ddafa4..000000000 --- a/semtech/decode_test.go +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestUnmarshalBinary(t *testing.T) { - tests := []struct { - Desc string - JSON string - Header []byte - WantPacket Packet - WantError bool - }{ - { - Desc: "Invalid PUSH_DATA, invalid gateway id", - Header: []byte{VERSION, 1, 2, PUSH_DATA, 1, 4, 5, 6}, - JSON: `{}`, - WantError: true, - }, - { - Desc: "PUSH_DATA with no payload", - Header: []byte{VERSION, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{}, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with only basic typed-attributes uint, string, float64 and int", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Chan: pointer.Uint32(14), - Codr: pointer.String("4/7"), - Freq: pointer.Float32(873.14), - Rssi: pointer.Int32(-42), - }, - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with datr field and modu -> LORA", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("LORA"), - }, - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with datr field and modu -> FSK", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"modu":"FSK","datr":50000}]}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Datr: pointer.String("50000"), - Modu: pointer.String("FSK"), - }, - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with time field", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with several RXPK", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"size":14},{"chan":14}]}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Size: pointer.Uint32(14), - }, - RXPK{ - Chan: pointer.Uint32(14), - }, - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with several RXPK and Stat(basic fields)", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Size: pointer.Uint32(14), - }, - }, - Stat: &Stat{ - Ackr: pointer.Float32(0.78), - Alti: pointer.Int32(72), - Rxok: pointer.Uint32(42), - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with Stat(time field)", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - Stat: &Stat{ - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 0, time.UTC)), - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_DATA with rxpk and txpk (?)", - Header: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - JSON: `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Codr: pointer.String("4/7"), - Rssi: pointer.Int32(-42), - }, - }, - TXPK: &TXPK{ - Ipol: pointer.Bool(true), - Powe: pointer.Uint32(12), - }, - }, - }, - WantError: false, - }, - { - Desc: "PUSH_ACK valid", - Header: []byte{1, 0x14, 0x42, PUSH_ACK}, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_ACK, - }, - WantError: false, - }, - { - Desc: "PUSH_ACK missing token", - Header: []byte{1, PUSH_ACK}, - WantError: true, - }, - { - Desc: "PULL_DATA valid", - Header: []byte{1, 0x14, 0x42, PULL_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PULL_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - WantError: false, - }, - { - Desc: "PULL_RESP with data", - Header: []byte{1, 0, 0, PULL_RESP}, - JSON: `{"txpk":{"ipol":true,"powe":12}}`, - WantPacket: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Ipol: pointer.Bool(true), - Powe: pointer.Uint32(12), - }, - }, - }, - WantError: false, - }, - { - Desc: "PULL_RESP with data and time", - Header: []byte{1, 0, 0, PULL_RESP}, - JSON: `{"txpk":{"ipol":true,"powe":12,"time":"2016-01-13T17:40:57.000000376Z"}}`, - WantPacket: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Ipol: pointer.Bool(true), - Powe: pointer.Uint32(12), - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - WantError: false, - }, - { - Desc: "PULL_RESP with datr only", - Header: []byte{1, 0, 0, PULL_RESP}, - JSON: `{"txpk":{"datr":"SF7BW125"}}`, - WantPacket: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Datr: pointer.String("SF7BW125"), - }, - }, - }, - WantError: false, - }, - { - Desc: "PULL_RESP with time only", - Header: []byte{1, 0, 0, PULL_RESP}, - JSON: `{"txpk":{"time":"2016-01-13T17:40:57.000000376Z"}}`, - WantPacket: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - WantError: false, - }, - { - Desc: "PULL_RESP empty payload", - Header: []byte{1, 0, 0, PULL_RESP}, - JSON: `{}`, - WantPacket: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{}, - }, - WantError: false, - }, - { - Desc: "PULL_RESP no payload", - Header: []byte{1, 0, 0, PULL_RESP}, - JSON: ``, - WantPacket: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{}, - }, - WantError: false, - }, - { - Desc: "PULL_ACK valid", - Header: []byte{1, 0x14, 0x42, PULL_ACK}, - WantPacket: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PULL_ACK, - }, - WantError: false, - }, - { - Desc: "Unreckognized version", - Header: []byte{VERSION + 14, 1, 2, PUSH_DATA, 1, 4, 5, 6}, - JSON: `{}`, - WantError: true, - }, - { - Desc: "Unreckognized Identifier", - Header: []byte{VERSION, 1, 2, 178, 1, 4, 5, 6}, - JSON: `{}`, - WantError: true, - }, - } - - for _, test := range tests { - Desc(t, test.Desc) - var packet Packet - err := packet.UnmarshalBinary(append(test.Header, []byte(test.JSON)...)) - checkErrors(t, test.WantError, err) - if test.WantError { - continue - } - checkPackets(t, test.WantPacket, packet) - } -} diff --git a/semtech/encode.go b/semtech/encode.go deleted file mode 100644 index 14c2eba57..000000000 --- a/semtech/encode.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "encoding/json" - "errors" - "time" -) - -// Marshal transforms a packet to a sequence of bytes. -func (packet Packet) MarshalBinary() ([]byte, error) { - raw := append(make([]byte, 0), packet.Version) - - if packet.Identifier == PULL_RESP { - packet.Token = []byte{0x0, 0x0} - } - - if len(packet.Token) != 2 { - return nil, errors.New("Invalid packet token") - } - - raw = append(raw, packet.Token...) - raw = append(raw, packet.Identifier) - - if packet.Identifier == PUSH_DATA || packet.Identifier == PULL_DATA { - if len(packet.GatewayId) != 8 { - return nil, errors.New("Invalid packet gatewayId") - } - raw = append(raw, packet.GatewayId...) - } - - if packet.Identifier == PUSH_DATA || packet.Identifier == PULL_RESP { - if packet.Payload != nil { - payload, err := json.Marshal(packet.Payload) - if err != nil { - return nil, err - } - raw = append(raw, payload...) - } else { - raw = append(raw, []byte("{}")...) - } - } - - return raw, nil -} - -// MarshalJSON implements the Marshaler interface from encoding/json -func (t *Timeparser) MarshalJSON() ([]byte, error) { - if t.Value == nil { - return nil, errors.New("Cannot marshal a null time") - } - return append(append([]byte(`"`), []byte(t.Value.Format(t.Layout))...), []byte(`"`)...), nil -} - -// MarshalJSON implements the Marshaler interface from encoding/json -func (d *Datrparser) MarshalJSON() ([]byte, error) { - if d.Kind == "uint" { - return []byte(d.Value), nil - } - return append(append([]byte(`"`), []byte(d.Value)...), []byte(`"`)...), nil -} - -// MarshalJSON implements the Marshaler interface from encoding/json -func (p *Payload) MarshalJSON() ([]byte, error) { - // Define Stat Proxy - var proxStat *statProxy - if p.Stat != nil { - proxStat = new(statProxy) - proxStat.Stat = p.Stat - if p.Stat.Time != nil { - proxStat.Time = &Timeparser{Layout: "2006-01-02 15:04:05 GMT", Value: p.Stat.Time} - } - } - - // Define RXPK Proxy - proxRXPK := make([]rxpkProxy, 0) - for _, rxpk := range p.RXPK { - proxr := new(rxpkProxy) - proxr.RXPK = new(RXPK) - *proxr.RXPK = rxpk - if rxpk.Time != nil { - proxr.Time = &Timeparser{time.RFC3339Nano, rxpk.Time} - } - - if rxpk.Modu != nil && rxpk.Datr != nil { - switch *rxpk.Modu { - case "FSK": - proxr.Datr = &Datrparser{Kind: "uint", Value: *rxpk.Datr} - case "LORA": - fallthrough - default: - proxr.Datr = &Datrparser{Kind: "string", Value: *rxpk.Datr} - } - } - proxRXPK = append(proxRXPK, *proxr) - } - - // Define TXPK Proxy - var proxTXPK *txpkProxy - if p.TXPK != nil { - proxTXPK = new(txpkProxy) - proxTXPK.TXPK = p.TXPK - if p.TXPK.Time != nil { - proxTXPK.Time = &Timeparser{time.RFC3339Nano, p.TXPK.Time} - } - if p.TXPK.Modu != nil && p.TXPK.Datr != nil { - switch *p.TXPK.Modu { - case "FSK": - proxTXPK.Datr = &Datrparser{Kind: "uint", Value: *p.TXPK.Datr} - case "LORA": - fallthrough - default: - proxTXPK.Datr = &Datrparser{Kind: "string", Value: *p.TXPK.Datr} - } - } - } - - // Define the whole Proxy - proxy := payloadProxy{ - ProxStat: proxStat, - ProxRXPK: proxRXPK, - ProxTXPK: proxTXPK, - } - - raw, err := json.Marshal(proxy) - return raw, err -} diff --git a/semtech/encode_test.go b/semtech/encode_test.go deleted file mode 100644 index 454964651..000000000 --- a/semtech/encode_test.go +++ /dev/null @@ -1,420 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestMarshalBinary(t *testing.T) { - tests := []struct { - Desc string - Packet Packet - WantError bool - WantHeader []byte - WantJSON string - }{ - { - Desc: "Invalid PUSH_DATA, invalid token", - Packet: Packet{ - Version: VERSION, - //No Token - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Chan: pointer.Uint32(14), - Codr: pointer.String("4/7"), - Freq: pointer.Float32(873.14), - Rssi: pointer.Int32(-42), - }, - }, - }, - }, - WantError: true, - }, - { - Desc: "Invalid PUSH_DATA, invalid gateway id", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - // No Gateway id - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Chan: pointer.Uint32(14), - Codr: pointer.String("4/7"), - Freq: pointer.Float32(873.14), - Rssi: pointer.Int32(-42), - }, - }, - }, - }, - WantError: true, - }, - { - Desc: "PUSH_DATA with no payload", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{}`, - }, - { - Desc: "PUSH_DATA with only basic typed-attributes uint, string, float64 and int", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Chan: pointer.Uint32(14), - Codr: pointer.String("4/7"), - Freq: pointer.Float32(873.14), - Rssi: pointer.Int32(-42), - }, - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"chan":14,"codr":"4/7","freq":873.14,"rssi":-42}]}`, - }, - { - Desc: "PUSH_DATA with datr field and modu -> LORA", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("LORA"), - }, - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"modu":"LORA","datr":"SF7BW125"}]}`, - }, - { - Desc: "PUSH_DATA with datr field and modu -> FSK", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Datr: pointer.String("50000"), - Modu: pointer.String("FSK"), - }, - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"modu":"FSK","datr":50000}]}`, - }, - { - Desc: "PUSH_DATA with time field", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"time":"2016-01-13T17:40:57.000000376Z"}]}`, - }, - { - Desc: "PUSH_DATA with several RXPK", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Size: pointer.Uint32(14), - }, - RXPK{ - Chan: pointer.Uint32(14), - }, - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"size":14},{"chan":14}]}`, - }, - { - Desc: "PUSH_DATA with several RXPK and Stat(basic fields)", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Size: pointer.Uint32(14), - }, - }, - Stat: &Stat{ - Ackr: pointer.Float32(0.78), - Alti: pointer.Int32(72), - Rxok: pointer.Uint32(42), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"size":14}],"stat":{"ackr":0.78,"alti":72,"rxok":42}}`, - }, - { - Desc: "PUSH_DATA with Stat(time field)", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - Stat: &Stat{ - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"stat":{"time":"2016-01-13 17:40:57 GMT"}}`, - }, - { - Desc: "PUSH_DATA with rxpk and txpk (?)", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - RXPK{ - Codr: pointer.String("4/7"), - Rssi: pointer.Int32(-42), - }, - }, - TXPK: &TXPK{ - Ipol: pointer.Bool(true), - Powe: pointer.Uint32(12), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - WantJSON: `{"rxpk":[{"codr":"4/7","rssi":-42}],"txpk":{"ipol":true,"powe":12}}`, - }, - { - Desc: "PUSH_ACK valid", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PUSH_ACK, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PUSH_ACK}, - }, - { - Desc: "PUSH_ACK missing token", - Packet: Packet{ - Version: VERSION, - Identifier: PUSH_ACK, - }, - WantError: true, - }, - { - Desc: "PULL_DATA valid", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PULL_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PULL_DATA, 1, 2, 3, 4, 5, 6, 7, 8}, - }, - { - Desc: "PULL_DATA missing token", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - }, - WantError: true, - }, - { - Desc: "PULL_DATA missing gatewayid", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PULL_DATA, - }, - WantError: true, - }, - { - Desc: "PULL_RESP with data", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Ipol: pointer.Bool(true), - Powe: pointer.Uint32(12), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{"txpk":{"ipol":true,"powe":12}}`, - }, - { - Desc: "PULL_RESP with data and time", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Ipol: pointer.Bool(true), - Powe: pointer.Uint32(12), - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{"txpk":{"ipol":true,"powe":12,"time":"2016-01-13T17:40:57.000000376Z"}}`, - }, - { - Desc: "PULL_RESP with time only", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Time: pointer.Time(time.Date(2016, 1, 13, 17, 40, 57, 376, time.UTC)), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{"txpk":{"time":"2016-01-13T17:40:57.000000376Z"}}`, - }, - { - Desc: "PULL_RESP with datr field and modu -> LORA", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Datr: pointer.String("SF7BW125"), - Modu: pointer.String("LORA"), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{"txpk":{"modu":"LORA","datr":"SF7BW125"}}`, - }, - { - Desc: "PULL_RESP with datr field and modu -> FSK", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{ - TXPK: &TXPK{ - Datr: pointer.String("50000"), - Modu: pointer.String("FSK"), - }, - }, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{"txpk":{"modu":"FSK","datr":50000}}`, - }, - { - Desc: "PULL_RESP empty payload", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - Payload: &Payload{}, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{}`, - }, - { - Desc: "PULL_RESP no payload", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_RESP, - }, - WantError: false, - WantHeader: []byte{1, 0, 0, PULL_RESP}, - WantJSON: `{}`, - }, - { - Desc: "PULL_ACK valid", - Packet: Packet{ - Version: VERSION, - Token: []byte{0x14, 0x42}, - Identifier: PULL_ACK, - }, - WantError: false, - WantHeader: []byte{1, 0x14, 0x42, PULL_ACK}, - }, - { - Desc: "PULL_ACK missing token", - Packet: Packet{ - Version: VERSION, - Identifier: PULL_ACK, - }, - WantError: true, - }, - } - - for _, test := range tests { - Desc(t, test.Desc) - raw, err := test.Packet.MarshalBinary() - checkErrors(t, test.WantError, err) - if test.WantError { - continue - } - checkHeaders(t, test.WantHeader, raw) - checkJSON(t, test.WantJSON, raw) - } -} diff --git a/semtech/helpers_test.go b/semtech/helpers_test.go deleted file mode 100644 index 75b0c152a..000000000 --- a/semtech/helpers_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "reflect" - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func checkErrors(t *testing.T, want bool, got error) { - if (!want && got == nil) || (want && got != nil) { - Ok(t, "Check errors") - return - } - Ko(t, "Received error does not match expectation. Got: %v", got) -} - -func checkHeaders(t *testing.T, want []byte, got []byte) { - l := len(want) - if len(got) < l { - Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got) - return - } - if !reflect.DeepEqual(want[:], got[:l]) { - Ko(t, "Received header does not match expectations.\nWant: %+x\nGot: %+x", want, got[:l]) - return - } - Ok(t, "Check Headers") -} - -func checkJSON(t *testing.T, want string, got []byte) { - l := len([]byte(want)) - if len(got) < l { - Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %v", want, got) - return - } - str := string(got[len(got)-l:]) - if want != str { - Ko(t, "Received JSON does not match expectations.\nWant: %s\nGot: %s", want, str) - return - } - Ok(t, "Check JSON") -} - -func checkPackets(t *testing.T, want Packet, got Packet) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check packets") - return - } - Ko(t, "Received packet does not match expectations.\nWant: %s\nGot: %s", want.String(), got.String()) -} diff --git a/semtech/proxies.go b/semtech/proxies.go deleted file mode 100644 index 45c40564f..000000000 --- a/semtech/proxies.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import "time" - -// Proxies handle annoying json fields like datr and time -type payloadProxy struct { - ProxRXPK []rxpkProxy `json:"rxpk,omitempty"` - ProxStat *statProxy `json:"stat,omitempty"` - ProxTXPK *txpkProxy `json:"txpk,omitempty"` -} - -type statProxy struct { - *Stat - Time *Timeparser `json:"time,omitempty"` -} - -type rxpkProxy struct { - *RXPK - Datr *Datrparser `json:"datr,omitempty"` - Time *Timeparser `json:"time,omitempty"` -} - -type txpkProxy struct { - *TXPK - Datr *Datrparser `json:"datr,omitempty"` - Time *Timeparser `json:"time,omitempty"` -} - -// datrParser is used as a proxy to Unmarshal datr field in json payloads. -// Depending on the modulation type, the datr type could be either a string or a number. -// We're gonna parse it as a string in any case. -type Datrparser struct { - Kind string - Value string // The parsed value -} - -// timeParser is used as a proxy to Unmarshal JSON objects with different date types as the time -// module parse RFC3339 by default -type Timeparser struct { - Layout string - Value *time.Time // The parsed time value -} diff --git a/semtech/semtech.go b/semtech/semtech.go deleted file mode 100644 index f978adbdb..000000000 --- a/semtech/semtech.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package semtech provides useful methods and types to handle communications with a gateway. -// -// This package relies on the SemTech Protocol 1.2 accessible on github: https://github.com/TheThingsNetwork/packet_forwarder/blob/master/PROTOCOL.TXT -package semtech - -import ( - "fmt" - "time" - - "github.com/TheThingsNetwork/ttn/utils/pointer" -) - -type DeviceAddress [4]byte - -// RXPK represents an uplink json message format sent by the gateway -type RXPK struct { - Chan *uint32 `full:"Channel" json:"chan,omitempty"` // Concentrator "IF" channel used for RX (unsigned integer) - Codr *string `full:"CodingRate" json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `full:"Data" json:"data,omitempty"` // Base64 encoded RF packet payload, padded - Datr *string `full:"DataRate" json:"-"` // FSK datarate (unsigned in bit per second) || LoRa datarate identifier - Freq *float32 `full:"Frequency" json:"freq,omitempty"` // RX Central frequency in MHx (unsigned float, Hz precision) - Lsnr *float32 `full:"Lsnr" json:"lsnr,omitempty"` // LoRa SNR ratio in dB (signed float, 0.1 dB precision) - Modu *string `full:"Modulation" json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for RX (unsigned integer) - Rssi *int32 `full:"Rssi" json:"rssi,omitempty"` // RSSI in dBm (signed integer, 1 dB precision) - Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Stat *int32 `full:"CRCStatus" json:"stat,omitempty"` // CRC status: 1 - OK, -1 = fail, 0 = no CRC - Time *time.Time `full:"Time" json:"-"` // UTC time of pkt RX, us precision, ISO 8601 'compact' format - Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Internal timestamp of "RX finished" event (32b unsigned) -} - -// TXPK represents a downlink j,omitemptyson message format received by the gateway. -// Most field are optional. -type TXPK struct { - Codr *string `full:"CodingRate" json:"codr,omitempty"` // LoRa ECC coding rate identifier - Data *string `full:"Data" json:"data,omitempty"` // Base64 encoded RF packet payload, padding optional - Datr *string `full:"DataRate" json:"-"` // LoRa datarate identifier (eg. SF12BW500) || FSK Datarate (unsigned, in bits per second) - Fdev *uint32 `full:"FrequencyDev" json:"fdev,omitempty"` // FSK frequency deviation (unsigned integer, in Hz) - Freq *float32 `full:"Frequency" json:"freq,omitempty"` // TX central frequency in MHz (unsigned float, Hz precision) - Imme *bool `full:"Immediate" json:"imme,omitempty"` // Send packet immediately (will ignore tmst & time) - Ipol *bool `full:"InvPolarity" json:"ipol,omitempty"` // Lora modulation polarization inversion - Modu *string `full:"Modulation" json:"modu,omitempty"` // Modulation identifier "LORA" or "FSK" - Ncrc *bool `full:"NoCRC" json:"ncrc,omitempty"` // If true, disable the CRC of the physical layer (optional) - Powe *uint32 `full:"Power" json:"powe,omitempty"` // TX output power in dBm (unsigned integer, dBm precision) - Prea *uint32 `full:"PreambleSize" json:"prea,omitempty"` // RF preamble size (unsigned integer) - Rfch *uint32 `full:"RFChain" json:"rfch,omitempty"` // Concentrator "RF chain" used for TX (unsigned integer) - Size *uint32 `full:"PayloadSize" json:"size,omitempty"` // RF packet payload size in bytes (unsigned integer) - Time *time.Time `full:"Time" json:"-"` // Send packet at a certain time (GPS synchronization required) - Tmst *uint32 `full:"Timestamp" json:"tmst,omitempty"` // Send packet on a certain timestamp value (will ignore time) -} - -// Stat represents a status json message format sent by the gateway -type Stat struct { - Ackr *float32 `full:"Acknowledgements" json:"ackr,omitempty"` // Percentage of upstream datagrams that were acknowledged - Alti *int32 `full:"Altitude" json:"alti,omitempty"` // GPS altitude of the gateway in meter RX (integer) - Dwnb *uint32 `full:"NbDownlink" json:"dwnb,omitempty"` // Number of downlink datagrams received (unsigned integer) - Lati *float32 `full:"Latitude" json:"lati,omitempty"` // GPS latitude of the gateway in degree (float, N is +) - Long *float32 `full:"Longitude" json:"long,omitempty"` // GPS latitude of the gateway in dgree (float, E is +) - Rxfw *uint32 `full:"RXForwarded" json:"rxfw,omitempty"` // Number of radio packets forwarded (unsigned integer) - Rxnb *uint32 `full:"RXReceived" json:"rxnb,omitempty"` // Number of radio packets received (unsigned integer) - Rxok *uint32 `full:"RXValid" json:"rxok,omitempty"` // Number of radio packets received with a valid PHY CRC - Time *time.Time `full:"Time" json:"-"` // UTC 'system' time of the gateway, ISO 8601 'expanded' format - Txnb *uint32 `full:"TXEmitted" json:"txnb,omitempty"` // Number of packets emitted (unsigned integer) -} - -// Packet as seen by the gateway. -type Packet struct { - Version byte // Protocol version, should always be 1 here - Token []byte // Random number generated by the gateway on some request. 2-bytes long. - Identifier byte // Packet's command identifier - GatewayId []byte // Source gateway's identifier (Only PULL_DATA and PUSH_DATA) - Payload *Payload // JSON payload transmitted if any, nil otherwise -} - -func (p *Packet) String() string { - if p == nil { - return "nil" - } - header := fmt.Sprintf("Version:%x,Token:%v,Identifier:%x,GatewayId:%v", p.Version, p.Token, p.Identifier, p.GatewayId) - if p.Payload == nil { - return fmt.Sprintf("Packet{%s}", header) - } - - var payload string - - if p.Payload.Stat != nil { - payload = fmt.Sprintf("%s,%s", payload, pointer.DumpPStruct(*p.Payload.Stat, false)) - } - - if p.Payload.TXPK != nil { - payload = fmt.Sprintf("%s,%s", payload, pointer.DumpPStruct(*p.Payload.TXPK, false)) - } - - var rxpk string - for _, r := range p.Payload.RXPK { - if rxpk == "" { - rxpk = fmt.Sprintf("%s", pointer.DumpPStruct(r, false)) - } else { - rxpk = fmt.Sprintf("%s,%s", rxpk, pointer.DumpPStruct(r, false)) - } - } - if rxpk != "" { - payload = fmt.Sprintf("%s,RXPK:[%s]", payload, rxpk) - } - return fmt.Sprintf("Packet{%s,Payload:{%s}}", header, payload) -} - -// Payload refers to the JSON payload sent by a gateway or a server. -type Payload struct { - Raw []byte `json:"-"` // The raw unparsed response - RXPK []RXPK `json:"rxpk,omitempty"` // A list of RXPK messages transmitted if any - Stat *Stat `json:"stat,omitempty"` // A Stat message transmitted if any - TXPK *TXPK `json:"txpk,omitempty"` // A TXPK message transmitted if any -} - -// Available packet commands -const ( - PUSH_DATA byte = iota // Sent by the gateway for an uplink message with data - PUSH_ACK // Sent by the gateway's recipient in response to a PUSH_DATA - PULL_DATA // Sent periodically by the gateway to keep a connection open - PULL_RESP // Sent by the gateway's recipient to transmit back data to the Gateway - PULL_ACK // Sent by the gateway's recipient in response to PULL_DATA -) - -// Protocol version in use -const VERSION = 0x01 diff --git a/semtech/semtech_test.go b/semtech/semtech_test.go deleted file mode 100644 index 053039446..000000000 --- a/semtech/semtech_test.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package semtech - -import ( - "fmt" - "strings" - "testing" - - "github.com/TheThingsNetwork/ttn/utils/pointer" - testutil "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestString(t *testing.T) { - { - testutil.Desc(t, "No Payload") - - packet := Packet{ - Version: VERSION, - Token: []byte{1, 2}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - } - - str := packet.String() - CheckStrings(t, "Packet", str) - CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) - CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) - CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) - CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) - } - - // -------------------- - - { - testutil.Desc(t, "With Stat Payload") - - packet := Packet{ - Version: VERSION, - Token: []byte{1, 2}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - Stat: &Stat{ - Ackr: pointer.Float32(3.4), - Alti: pointer.Int32(14), - }, - }, - } - - str := packet.String() - CheckStrings(t, "Packet", str) - CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) - CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) - CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) - CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) - CheckStrings(t, "Payload", str) - CheckStrings(t, "Stat", str) - CheckStrings(t, "Ackr", str) - CheckStrings(t, "Alti", str) - } - - // -------------------- - - { - testutil.Desc(t, "With TXPK Payload") - - packet := Packet{ - Version: VERSION, - Token: []byte{1, 2}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - TXPK: &TXPK{ - Freq: pointer.Float32(883.445), - Codr: pointer.String("4/5"), - }, - }, - } - - str := packet.String() - CheckStrings(t, "Packet", str) - CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) - CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) - CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) - CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) - CheckStrings(t, "Payload", str) - CheckStrings(t, "TXPK", str) - CheckStrings(t, "Codr", str) - CheckStrings(t, "Freq", str) - } - - // -------------------- - - { - testutil.Desc(t, "With RXPK Payloads") - - packet := Packet{ - Version: VERSION, - Token: []byte{1, 2}, - Identifier: PUSH_DATA, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Payload: &Payload{ - RXPK: []RXPK{ - { - Freq: pointer.Float32(883.445), - Codr: pointer.String("4/5"), - }, - { - Freq: pointer.Float32(883.445), - Codr: pointer.String("4/5"), - }, - }, - }, - } - - str := packet.String() - CheckStrings(t, "Packet", str) - CheckStrings(t, fmt.Sprintf("Version:%v", VERSION), str) - CheckStrings(t, fmt.Sprintf("Token:%v", packet.Token), str) - CheckStrings(t, fmt.Sprintf("Identifier:%v", PUSH_DATA), str) - CheckStrings(t, fmt.Sprintf("GatewayId:%v", packet.GatewayId), str) - CheckStrings(t, "Payload", str) - CheckStrings(t, "RXPK", str) - CheckStrings(t, "Codr", str) - CheckStrings(t, "Freq", str) - } - - // -------------------- - - { - testutil.Desc(t, "Nil payload") - - var packet *Packet - str := packet.String() - CheckStrings(t, "nil", str) - } -} - -func CheckStrings(t *testing.T, want string, got string) { - if !strings.Contains(got, want) { - testutil.Ko(t, "Expected %s to contain \"%s\"", got, want) - } - testutil.Ok(t, "Check Strings") -} diff --git a/utils/errors/errors.go b/utils/errors/errors.go deleted file mode 100644 index 033989565..000000000 --- a/utils/errors/errors.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package errors - -import ( - "fmt" - "reflect" - "time" -) - -type Nature string - -const ( - Structural Nature = "Invalid structure" // Requests, parameters or inputs are wrong. Retry won't work. - NotFound Nature = "Unable to find entity" // Failed to lookup something, somewhere - Behavioural Nature = "Wrong behavior or result" // No error but the result isn't the one expected - Operational Nature = "Invalid operation" // An operation failed due to external causes, a retry could work - Implementation Nature = "Illegal call" // Method not implemented or unsupported for the given structure -) - -// Failure states for fault that occurs during a process. -type Failure struct { - Nature Nature // Kind of error, used a comparator - Timestamp time.Time // The moment the error was created - Fault error // The source of the failure -} - -// NewFailure creates a new Failure from a source error -func New(nat Nature, src interface{}) Failure { - var fault error - switch src.(type) { - case string: - fault = fmt.Errorf("%s", src.(string)) - case error: - fault = src.(error) - default: - panic("Unexpected error source") - } - - failure := Failure{ - Nature: nat, - Timestamp: time.Now(), - Fault: fault, - } - - // Pop one level if we made a failure from a failure - t := reflect.TypeOf(src) - tf := reflect.TypeOf(failure) - if t == tf { - failure.Fault = src.(Failure).Fault - } - - return failure -} - -// Error implements the error built-in interface -func (err Failure) Error() string { - if err.Fault == nil { - return fmt.Sprintf("%s", err.Nature) - } - return fmt.Sprintf("%s: %s", err.Nature, err.Fault.Error()) -} diff --git a/utils/readwriter/readwriter.go b/utils/readwriter/readwriter.go deleted file mode 100644 index 90648b247..000000000 --- a/utils/readwriter/readwriter.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package readwriter - -import ( - "bytes" - "encoding/binary" - "fmt" - "io" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/brocaar/lorawan" -) - -type Interface interface { - Write(data interface{}) - TryRead(to func(data []byte) error) - Read(to func(data []byte)) - Bytes() ([]byte, error) - Err() error -} - -// entryReadWriter offers convenient method to write and read successively from a bytes buffer. -type rw struct { - err error - data *bytes.Buffer -} - -// newEntryReadWriter create a new read/writer from an existing buffer. -// -// If a nil or empty buffer is supplied, reading from the read/writer will cause an error (io.EOF) -// Nevertheless, if a valid non-empty buffer is given, the read/writer will start reading from the -// beginning of that buffer, and will start writting at the end of it. -func New(buf []byte) Interface { - return &rw{ - err: nil, - data: bytes.NewBuffer(buf), - } -} - -// Write appends the given data at the end of the existing buffer. -// -// It does nothing if an error was previously noticed and panics if the given data are something -// different from: byte, []byte, string, AES128Key, EUI64, DevAddr. -// -// Also, it writes the length of the given raw data encoded on 2 bytes before writting the data -// itself. In that way, data can be appended and read easily. -func (w *rw) Write(data interface{}) { - var dataLen uint16 - switch data.(type) { - case bool: - if data.(bool) { - w.directWrite(uint16(1)) - w.directWrite(byte(1)) - } else { - w.directWrite(uint16(1)) - w.directWrite(byte(0)) - } - return - case uint8: - dataLen = 1 - case uint16: - dataLen = 2 - case uint32: - dataLen = 4 - case uint64: - dataLen = 8 - case []byte: - dataLen = uint16(len(data.([]byte))) - case lorawan.AES128Key: - dataLen = uint16(len(data.(lorawan.AES128Key))) - case lorawan.EUI64: - dataLen = uint16(len(data.(lorawan.EUI64))) - case lorawan.DevAddr: - dataLen = uint16(len(data.(lorawan.DevAddr))) - case string: - str := data.(string) - w.directWrite(uint16(len(str))) - w.directWrite([]byte(str)) - return - default: - panic(fmt.Errorf("Unreckognized data type: %v", data)) - } - w.directWrite(dataLen) - w.directWrite(data) -} - -// directWrite appends the given data at the end of the existing buffer (without the length). -func (w *rw) directWrite(data interface{}) { - if w.err != nil { - return - } - in := w.data.Next(w.data.Len()) - w.data = new(bytes.Buffer) - binary.Write(w.data, binary.BigEndian, in) - w.err = binary.Write(w.data, binary.BigEndian, data) -} - -// Read retrieves next data from the given buffer. Implicitely, this implies the data to have been -// written using the Write method (len | data). Data are sent back through a callback as an array of -// bytes. -func (w *rw) Read(to func(data []byte)) { - w.err = w.read(func(data []byte) error { to(data); return nil }) -} - -// TryRead retrieves the next data from the given buffer but differs from Read in the way it listen -// to the callback returns for a possible error. -func (w *rw) TryRead(to func(data []byte) error) { - w.err = w.read(to) -} - -func (w *rw) read(to func(data []byte) error) error { - if w.err != nil { - return w.err - } - - lenTo := new(uint16) - if err := binary.Read(w.data, binary.BigEndian, lenTo); err != nil { - return err - } - next := w.data.Next(int(*lenTo)) - if len(next) != int(*lenTo) { - return errors.New(errors.Structural, "Not enough data to read") - } - return to(next) -} - -// Bytes might be used to retrieves the raw buffer after successive writes. It will return nil and -// an error if any issue was encountered during the process. -func (w rw) Bytes() ([]byte, error) { - if w.err != nil { - return nil, w.Err() - } - return w.data.Bytes(), nil -} - -// Err just return the err status of the read-writer. -func (w rw) Err() error { - if w.err != nil { - if w.err == io.EOF { - return errors.New(errors.Behavioural, w.err) - } - if failure, ok := w.err.(errors.Failure); ok { - return failure - } - return errors.New(errors.Operational, w.err) - } - return nil -} diff --git a/utils/readwriter/readwriter_test.go b/utils/readwriter/readwriter_test.go deleted file mode 100644 index 9eb07a26f..000000000 --- a/utils/readwriter/readwriter_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package readwriter - -import ( - "fmt" - "reflect" - "testing" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" - . "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/brocaar/lorawan" -) - -func TestReadWriter(t *testing.T) { - { - Desc(t, "Write to an empty buffer") - rw := New(nil) - rw.Write([]byte{1, 2, 3, 4}) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // ------------- - - { - Desc(t, "Write to a non empty buffer") - rw := New([]byte{1, 2, 3, 4}) - rw.Write([]byte{1, 2}) - CheckErrors(t, nil, rw.Err()) - } - - // ------------- - - { - Desc(t, "Append to an existing buffer") - rw := New(nil) - rw.Write([]byte{1, 2, 3, 4}) - data, _ := rw.Bytes() - - rw = New(data) - rw.Write([]byte{5, 6}) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) - rw.Read(func(data []byte) { checkData(t, []byte{5, 6}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // ------------- - - { - Desc(t, "Read from empty buffer") - rw := New(nil) - rw.Read(func(data []byte) { checkNotCalled(t) }) - CheckErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) - } - - // -------------- - - { - Desc(t, "Write after read from non empty") - rw := New(nil) - rw.Write([]byte{1, 2}) - rw.Write([]byte{3, 4}) - data, _ := rw.Bytes() - - rw = New(data) - rw.Read(func(data []byte) {}) - rw.Write([]byte{5, 6}) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{3, 4}, data) }) - rw.Read(func(data []byte) { checkData(t, []byte{5, 6}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write single byte") - rw := New(nil) - rw.Write(byte(1)) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{1}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write string") - rw := New(nil) - rw.Write("TheThingsNetwork") - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte("TheThingsNetwork"), data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write lorawan.AES128Key") - rw := New(nil) - rw.Write(lorawan.AES128Key([16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6})) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write lorawan.EUI64") - rw := New(nil) - rw.Write(lorawan.EUI64([8]byte{1, 2, 3, 4, 5, 6, 7, 8})) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4, 5, 6, 7, 8}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write lorawan.DevAddr") - rw := New(nil) - rw.Write(lorawan.DevAddr([4]byte{1, 2, 3, 4})) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{1, 2, 3, 4}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write empty slice") - rw := New(nil) - rw.Write([]byte{}) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write uint16") - rw := New(nil) - rw.Write(uint16(35)) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{0, 35}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write uint32") - rw := New(nil) - rw.Write(uint32(4567)) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{0, 0, 17, 215}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write uint64") - rw := New(nil) - rw.Write(uint64(324675)) - data, err := rw.Bytes() - CheckErrors(t, nil, err) - - rw = New(data) - rw.Read(func(data []byte) { checkData(t, []byte{0, 0, 0, 0, 0, 4, 244, 67}, data) }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Write invalid data") - rw := New(nil) - chwait := make(chan bool) - go func() { - defer func() { - recover() - close(chwait) - }() - rw.Write(34.7) - checkNotCalled(t) - }() - <-chwait - } - - // -------------- - - { - Desc(t, "Try read with no error") - rw := New(nil) - rw.Write("TTN") - data, _ := rw.Bytes() - - rw = New(data) - rw.TryRead(func(data []byte) error { - checkData(t, []byte("TTN"), data) - return nil - }) - CheckErrors(t, nil, rw.Err()) - } - - // -------------- - - { - Desc(t, "Try read with classic error") - rw := New(nil) - rw.Write("TTN") - data, _ := rw.Bytes() - - rw = New(data) - rw.TryRead(func(data []byte) error { - return fmt.Errorf("My Error") - }) - CheckErrors(t, pointer.String(string(errors.Operational)), rw.Err()) - } - - // -------------- - - { - Desc(t, "Try read with failure") - rw := New(nil) - rw.Write("TTN") - data, _ := rw.Bytes() - - rw = New(data) - rw.TryRead(func(data []byte) error { - return errors.New(errors.Behavioural, "Don't feel like to read") - }) - CheckErrors(t, pointer.String(string(errors.Behavioural)), rw.Err()) - } - - // ------------- - - { - Desc(t, "Not enough data to read") - rw := New([]byte{2, 3}) - rw.Read(func(data []byte) { - checkNotCalled(t) - }) - CheckErrors(t, pointer.String(string(errors.Structural)), rw.Err()) - } -} - -// ----- CHECK utilities -func checkData(t *testing.T, want []byte, got []byte) { - if reflect.DeepEqual(want, got) { - Ok(t, "Check data") - return - } - Ko(t, "Expected data to be %v but got %v", want, got) -} - -func checkNotCalled(t *testing.T) { - Ko(t, "Unexpected call on method") -} diff --git a/utils/shield/shield.go b/utils/shield/shield.go deleted file mode 100644 index 702b39784..000000000 --- a/utils/shield/shield.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package shield - -import ( - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -// Interfaces materializes the public API of a shield. -type Interface interface { - ThroughIn() error // Try to pass through, will fail if the shield has been passed too many times - ThroughOut() // Terminate a previous session -} - -type shield struct { - Queue chan struct{} -} - -// New constructs a new shield with the given size -func New(size uint) Interface { - return shield{Queue: make(chan struct{}, size)} -} - -// Through implements the shield Interface -func (s shield) ThroughIn() error { - select { - case s.Queue <- struct{}{}: - return nil - default: - return errors.New(errors.Operational, "Impossible to pass. Too many requests.") - } -} - -// Release implements the shield Interface -func (s shield) ThroughOut() { - select { - case <-s.Queue: - default: - } -} diff --git a/utils/shield/shield_test.go b/utils/shield/shield_test.go deleted file mode 100644 index e71e46bc0..000000000 --- a/utils/shield/shield_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package shield - -import ( - "testing" - - . "github.com/TheThingsNetwork/ttn/utils/testing" -) - -func TestShield(t *testing.T) { - { - shield := New(2) - err1 := shield.ThroughIn() - err2 := shield.ThroughIn() - err3 := shield.ThroughIn() - shield.ThroughOut() - err4 := shield.ThroughIn() - CheckErrors(t, nil, err1) - CheckErrors(t, nil, err2) - CheckErrors(t, ErrOperational, err3) - CheckErrors(t, nil, err4) - } - - // ---------- - - { - shield := New(1) - shield.ThroughOut() - err1 := shield.ThroughIn() - err2 := shield.ThroughIn() - CheckErrors(t, nil, err1) - CheckErrors(t, ErrOperational, err2) - } -} diff --git a/utils/testing/response_writer.go b/utils/testing/response_writer.go deleted file mode 100644 index 6f21db791..000000000 --- a/utils/testing/response_writer.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package testing - -import "net/http" - -// ResponseWriter mocks http.ResponseWriter -type responseWriter struct { - TheHeaders *http.Header - TheStatus int - TheBody []byte -} - -// Header implements http.ResponseWriter -func (rw *responseWriter) Header() http.Header { - return *rw.TheHeaders -} - -// Write implements http.ResponseWriter -func (rw *responseWriter) Write(m []byte) (int, error) { - rw.TheBody = m - return len(m), nil -} - -// WriteHeader implements http.ResponseWriter -func (rw *responseWriter) WriteHeader(h int) { - rw.TheStatus = h -} - -func NewResponseWriter() responseWriter { - h := http.Header{} - return responseWriter{ - TheHeaders: &h, - } -} diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 0e6c471c1..2ce75c75a 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -6,20 +6,11 @@ package testing import ( - "fmt" - "reflect" "testing" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" ) -var ErrStructural = pointer.String(string(errors.Structural)) -var ErrOperational = pointer.String(string(errors.Operational)) -var ErrNotFound = pointer.String(string(errors.NotFound)) -var ErrBehavioural = pointer.String(string(errors.Behavioural)) - func GetLogger(t *testing.T, tag string) log.Interface { logger := &log.Logger{ Handler: NewLogHandler(t), @@ -27,55 +18,3 @@ func GetLogger(t *testing.T, tag string) log.Interface { } return logger.WithField("tag", tag) } - -// Ok displays a green check symbol -func Ok(t *testing.T, tag string) { - t.Log(fmt.Sprintf("\033[32;1m\u2714 ok | %s\033[0m", tag)) -} - -// Ko fails the test and display a red cross symbol -func Ko(t *testing.T, format string, a ...interface{}) { - t.Fatalf("\033[31;1m\u2718 ko | \033[0m\033[31m%s\033[0m", fmt.Sprintf(format, a...)) -} - -// Desc displays the provided description in cyan -func Desc(t *testing.T, format string, a ...interface{}) { - t.Logf("\033[36m%s\033[0m", fmt.Sprintf(format, a...)) -} - -// Check serves a for general comparison between two objects -func Check(t *testing.T, want, got interface{}, name string) { - if !reflect.DeepEqual(want, got) { - Ko(t, "%s don't match expectations.\nWant: %+v\nGot: %+v", name, want, got) - } - Ok(t, fmt.Sprintf("Check %s", name)) -} - -// Check errors verify if a given string corresponds to a known error -func CheckErrors(t *testing.T, want *string, got error) { - if got == nil { - if want == nil { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got nothing", *want) - return - } - - if want == nil { - Ko(t, "Expected no error but got {%v}", got) - return - } - - if got.(errors.Failure).Nature == errors.Nature(*want) { - Ok(t, "Check errors") - return - } - Ko(t, "Expected error to be {%s} but got {%v}", *want, got) -} - -func FatalUnless(t *testing.T, err error) { - if err != nil { - Ko(t, "Unexpected error arised: %s", err) - } -} diff --git a/utils/toa/toa.go b/utils/toa/toa.go index e85267cfc..380347c65 100644 --- a/utils/toa/toa.go +++ b/utils/toa/toa.go @@ -1,11 +1,11 @@ package toa import ( + "errors" "math" "time" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/utils/errors" ) // Compute the time-on-air given a PHY payload size in bytes, a datr identifier, @@ -26,7 +26,7 @@ func Compute(payloadSize uint, datr string, codr string) (time.Duration, error) case "4/8": cr = 4 default: - return 0, errors.New(errors.Structural, "Invalid Codr") + return 0, errors.New("Invalid Codr") } // Determine DR dr, err := types.ParseDataRate(datr) From 2c9147bb4310377a4d5e0908bb4780774dd72f84 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 3 Jun 2016 14:27:01 +0200 Subject: [PATCH 1484/2266] Temporarily skip tests for ttnctl --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 99bedefbd..f2e52fe73 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ LDFLAGS = -ldflags "-w -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .Imports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` TEST_DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .TestImports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` -select_pkgs = $(GOCMD) list ./... | grep -vE 'vendor' +select_pkgs = $(GOCMD) list ./... | grep -vE 'vendor|ttnctl' coverage_pkgs = $(GOCMD) list ./... | grep -E 'core' | grep -vE 'core$$|mocks$$' RELEASE_DIR ?= release From 114a4c049e2692b879ae1b82635502d123d7eb10 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 3 Jun 2016 14:27:47 +0200 Subject: [PATCH 1485/2266] Move MQTT data structs to mqtt package --- core/collection/appCollector.go | 3 +- core/collection/appCollector_test.go | 7 ++- mqtt/client.go | 29 ++++++------ mqtt/client_test.go | 67 ++++++++++++++-------------- mqtt/types.go | 50 +++++++++++++++++++++ 5 files changed, 102 insertions(+), 54 deletions(-) create mode 100644 mqtt/types.go diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go index 6ad1071f4..7b67d2564 100644 --- a/core/collection/appCollector.go +++ b/core/collection/appCollector.go @@ -7,7 +7,6 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" @@ -56,7 +55,7 @@ func (c *appCollector) Stop() { c.client.Disconnect() } -func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { +func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req mqtt.UplinkMessage) { if req.Fields == nil || len(req.Fields) == 0 { return } diff --git a/core/collection/appCollector_test.go b/core/collection/appCollector_test.go index 624c88ad9..369532417 100644 --- a/core/collection/appCollector_test.go +++ b/core/collection/appCollector_test.go @@ -7,7 +7,6 @@ import ( "testing" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" @@ -64,11 +63,11 @@ func TestCollect(t *testing.T) { So(err, ShouldBeNil) defer client.Disconnect() - req := core.DataUpAppReq{ - DevEUI: devEUI.String(), + req := mqtt.UplinkMessage{ + DevEUI: devEUI, FCnt: 0, FPort: 1, - Metadata: []core.AppMetadata{core.AppMetadata{ServerTime: time.Now().Format(time.RFC3339)}}, + Metadata: []*mqtt.Metadata{&mqtt.Metadata{ServerTime: time.Now().Format(time.RFC3339)}}, Payload: []byte{0x1, 0x2, 0x3}, Fields: map[string]interface{}{"size": 3}, } diff --git a/mqtt/client.go b/mqtt/client.go index da51812e8..92afdd476 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -8,7 +8,6 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" @@ -25,19 +24,19 @@ type Client interface { IsConnected() bool // Uplink pub/sub - PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, payload core.DataUpAppReq) Token + PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, payload UplinkMessage) Token SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types.DevEUI, handler UplinkHandler) Token SubscribeAppUplink(appEUI types.AppEUI, handler UplinkHandler) Token SubscribeUplink(handler UplinkHandler) Token // Downlink pub/sub - PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, payload core.DataDownAppReq) Token + PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, payload DownlinkMessage) Token SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI types.DevEUI, handler DownlinkHandler) Token SubscribeAppDownlink(appEUI types.AppEUI, handler DownlinkHandler) Token SubscribeDownlink(handler DownlinkHandler) Token // Activation pub/sub - PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, payload core.OTAAAppReq) Token + PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, payload Activation) Token SubscribeDeviceActivations(appEUI types.AppEUI, devEUI types.DevEUI, handler ActivationHandler) Token SubscribeAppActivations(appEUI types.AppEUI, handler ActivationHandler) Token SubscribeActivations(handler ActivationHandler) Token @@ -68,9 +67,9 @@ func (t *simpleToken) Error() error { return t.err } -type UplinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) -type DownlinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) -type ActivationHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) +type UplinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) +type DownlinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) +type ActivationHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) type defaultClient struct { mqtt MQTT.Client @@ -105,7 +104,7 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri }) mqttOpts.SetOnConnectHandler(func(client MQTT.Client) { - ctx.Debug("Connected") + ctx.Debug("Connected to MQTT") }) return &defaultClient{ @@ -127,6 +126,7 @@ func (c *defaultClient) Connect() error { } var err error for retries := 0; retries < ConnectRetries; retries++ { + c.ctx.Debug("Connecting to MQTT...") token := c.mqtt.Connect() token.Wait() err = token.Error() @@ -145,6 +145,7 @@ func (c *defaultClient) Disconnect() { if !c.mqtt.IsConnected() { return } + c.ctx.Debug("Disconnecting from MQTT") c.mqtt.Disconnect(25) } @@ -152,7 +153,7 @@ func (c *defaultClient) IsConnected() bool { return c.mqtt.IsConnected() } -func (c *defaultClient) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, dataUp core.DataUpAppReq) Token { +func (c *defaultClient) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, dataUp UplinkMessage) Token { topic := DeviceTopic{appEUI, devEUI, Uplink} msg, err := json.Marshal(dataUp) if err != nil { @@ -172,7 +173,7 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types. } // Unmarshal the payload - dataUp := &core.DataUpAppReq{} + dataUp := &UplinkMessage{} err = json.Unmarshal(msg.Payload(), dataUp) if err != nil { @@ -193,7 +194,7 @@ func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { return c.SubscribeDeviceUplink(types.AppEUI{}, types.DevEUI{}, handler) } -func (c *defaultClient) PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, dataDown core.DataDownAppReq) Token { +func (c *defaultClient) PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, dataDown DownlinkMessage) Token { topic := DeviceTopic{appEUI, devEUI, Downlink} msg, err := json.Marshal(dataDown) if err != nil { @@ -213,7 +214,7 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI type } // Unmarshal the payload - dataDown := &core.DataDownAppReq{} + dataDown := &DownlinkMessage{} err = json.Unmarshal(msg.Payload(), dataDown) if err != nil { c.ctx.WithError(err).Warn("Could not unmarshal Downlink") @@ -233,7 +234,7 @@ func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { return c.SubscribeDeviceDownlink(types.AppEUI{}, types.DevEUI{}, handler) } -func (c *defaultClient) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, activation core.OTAAAppReq) Token { +func (c *defaultClient) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, activation Activation) Token { topic := DeviceTopic{appEUI, devEUI, Activations} msg, err := json.Marshal(activation) if err != nil { @@ -253,7 +254,7 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI types.AppEUI, devEUI t } // Unmarshal the payload - activation := &core.OTAAAppReq{} + activation := &Activation{} err = json.Unmarshal(msg.Payload(), activation) if err != nil { c.ctx.WithError(err).Warn("Could not unmarshal Activation") diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 589a3ad71..f69010190 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -104,7 +103,7 @@ func TestPublishUplink(t *testing.T) { c.Connect() eui := types.EUI64{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - dataUp := core.DataUpAppReq{ + dataUp := UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, } @@ -121,7 +120,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { eui := types.EUI64{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDeviceUplink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + token := c.SubscribeDeviceUplink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { }) token.Wait() @@ -136,7 +135,7 @@ func TestSubscribeAppUplink(t *testing.T) { eui := types.AppEUI{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + token := c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { }) token.Wait() @@ -149,7 +148,7 @@ func TestSubscribeUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeUplink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + token := c.SubscribeUplink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { }) token.Wait() @@ -169,14 +168,14 @@ func TestPubSubUplink(t *testing.T) { wg.Add(1) - c.SubscribeDeviceUplink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + c.SubscribeDeviceUplink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) wg.Done() }).Wait() - c.PublishUplink(appEUI, devEUI, core.DataUpAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink(appEUI, devEUI, UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } @@ -194,14 +193,14 @@ func TestPubSubAppUplink(t *testing.T) { wg.Add(2) - c.SubscribeAppUplink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { + c.SubscribeAppUplink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { a.So(appEUI, ShouldResemble, appEUI) a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }).Wait() - c.PublishUplink(appEUI, devEUI1, core.DataUpAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - c.PublishUplink(appEUI, devEUI2, core.DataUpAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink(appEUI, devEUI1, UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink(appEUI, devEUI2, UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } @@ -214,8 +213,8 @@ func TestInvalidUplink(t *testing.T) { eui := types.AppEUI{0x06, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataUpAppReq) { - Ko(t, "Did not expect any message") + c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { + t.Error("Did not expect any message") }).Wait() // Invalid Topic @@ -237,7 +236,7 @@ func TestPublishDownlink(t *testing.T) { c.Connect() eui := types.EUI64{0x01, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - dataDown := core.DataDownAppReq{ + dataDown := DownlinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, } @@ -254,7 +253,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { eui := types.EUI64{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDeviceDownlink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { + token := c.SubscribeDeviceDownlink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { }) token.Wait() @@ -269,7 +268,7 @@ func TestSubscribeAppDownlink(t *testing.T) { eui := types.AppEUI{0x03, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { + token := c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { }) token.Wait() @@ -282,7 +281,7 @@ func TestSubscribeDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeDownlink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { + token := c.SubscribeDownlink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { }) token.Wait() @@ -302,14 +301,14 @@ func TestPubSubDownlink(t *testing.T) { wg.Add(1) - c.SubscribeDeviceDownlink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { + c.SubscribeDeviceDownlink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) wg.Done() }).Wait() - c.PublishDownlink(appEUI, devEUI, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink(appEUI, devEUI, DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } @@ -327,14 +326,14 @@ func TestPubSubAppDownlink(t *testing.T) { wg.Add(2) - c.SubscribeAppDownlink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { + c.SubscribeAppDownlink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { a.So(appEUI, ShouldResemble, appEUI) a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }).Wait() - c.PublishDownlink(appEUI, devEUI1, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - c.PublishDownlink(appEUI, devEUI2, core.DataDownAppReq{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink(appEUI, devEUI1, DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink(appEUI, devEUI2, DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } @@ -347,8 +346,8 @@ func TestInvalidDownlink(t *testing.T) { eui := types.AppEUI{0x06, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.DataDownAppReq) { - Ko(t, "Did not expect any message") + c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { + t.Error("Did not expect any message") }).Wait() // Invalid Topic @@ -370,7 +369,7 @@ func TestPublishActivations(t *testing.T) { c.Connect() eui := types.EUI64{0x01, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - dataActivations := core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}} + dataActivations := Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}} token := c.PublishActivation(types.AppEUI(eui), types.DevEUI(eui), dataActivations) token.Wait() @@ -385,7 +384,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { eui := types.EUI64{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeDeviceActivations(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { + token := c.SubscribeDeviceActivations(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { }) token.Wait() @@ -400,7 +399,7 @@ func TestSubscribeAppActivations(t *testing.T) { eui := types.AppEUI{0x03, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - token := c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { + token := c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { }) token.Wait() @@ -413,7 +412,7 @@ func TestSubscribeActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeActivations(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { + token := c.SubscribeActivations(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { }) token.Wait() @@ -433,14 +432,14 @@ func TestPubSubActivations(t *testing.T) { wg.Add(1) - c.SubscribeDeviceActivations(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { + c.SubscribeDeviceActivations(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { a.So(appEUI, ShouldResemble, appEUI) a.So(devEUI, ShouldResemble, devEUI) wg.Done() }).Wait() - c.PublishActivation(appEUI, devEUI, core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation(appEUI, devEUI, Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() wg.Wait() } @@ -458,14 +457,14 @@ func TestPubSubAppActivations(t *testing.T) { wg.Add(2) - c.SubscribeAppActivations(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { + c.SubscribeAppActivations(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { a.So(appEUI, ShouldResemble, appEUI) a.So(req.Metadata[0].DataRate, ShouldEqual, "SF7BW125") wg.Done() }).Wait() - c.PublishActivation(appEUI, devEUI1, core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}}).Wait() - c.PublishActivation(appEUI, devEUI2, core.OTAAAppReq{Metadata: []core.AppMetadata{core.AppMetadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation(appEUI, devEUI1, Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation(appEUI, devEUI2, Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() wg.Wait() } @@ -478,8 +477,8 @@ func TestInvalidActivations(t *testing.T) { eui := types.AppEUI{0x06, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req core.OTAAAppReq) { - Ko(t, "Did not expect any message") + c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { + t.Error("Did not expect any message") }).Wait() // Invalid Topic diff --git a/mqtt/types.go b/mqtt/types.go new file mode 100644 index 000000000..a3d04ddc4 --- /dev/null +++ b/mqtt/types.go @@ -0,0 +1,50 @@ +package mqtt + +import "github.com/TheThingsNetwork/ttn/core/types" + +// Metadata contains the metadata that is passed up to the application +type Metadata struct { + Frequency float32 `json:"frequency"` + DataRate string `json:"datarate"` + CodingRate string `json:"codingrate"` + Timestamp uint32 `json:"gateway_timestamp"` + Time string `json:"gateway_time,omitempty"` + ServerTime string `json:"server_time"` + Channel uint32 `json:"channel"` + Rssi float32 `json:"rssi"` + Lsnr float32 `json:"lsnr"` + RFChain uint32 `json:"rfchain"` + Modulation string `json:"modulation"` + GatewayEUI types.GatewayEUI `json:"gateway_eui"` + Altitude int32 `json:"altitude"` + Longitude float32 `json:"longitude"` + Latitude float32 `json:"latitude"` +} + +// UplinkMessage represents an application-layer uplink message +type UplinkMessage struct { + AppEUI types.AppEUI `json:"app_eui,omitempty"` + DevEUI types.DevEUI `json:"dev_eui,omitempty"` + Payload []byte `json:"payload,omitempty"` + FPort uint8 `json:"port,omitempty"` + Fields map[string]interface{} `json:"fields,omitempty"` + FCnt uint32 `json:"counter,omitempty"` + Metadata []*Metadata `json:"metadata,omitempty"` +} + +// DownlinkMessage represents an application-layer downlink message +type DownlinkMessage struct { + AppEUI types.AppEUI `json:"app_eui,omitempty"` + DevEUI types.DevEUI `json:"dev_eui,omitempty"` + Payload []byte `json:"payload,omitempty"` + FPort uint8 `json:"port,omitempty"` + Fields map[string]interface{} `json:"fields,omitempty"` + TTL string `json:"ttl,omitempty"` +} + +// Activation are used to notify application of a device activation +type Activation struct { + AppEUI types.AppEUI `json:"app_eui,omitempty"` + DevEUI types.DevEUI `json:"dev_eui,omitempty"` + Metadata []Metadata `json:"metadata"` +} From 26ec6fa86ef8c709b8807783a3d8ec98dab20b17 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 6 Jun 2016 13:18:25 +0200 Subject: [PATCH 1486/2266] Use component context in Broker discovery --- core/component.go | 2 +- core/discovery/broker_discovery.go | 12 ++++++------ core/discovery/broker_discovery_test.go | 7 +++++-- core/router/router.go | 2 +- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/core/component.go b/core/component.go index 3d4577e6b..f4af615a9 100644 --- a/core/component.go +++ b/core/component.go @@ -64,7 +64,7 @@ func (c *Component) Announce() error { } defer conn.Close() client := pb_discovery.NewDiscoveryClient(conn) - _, err = client.Announce(context.Background(), c.Identity) + _, err = client.Announce(c.GetContext(), c.Identity) if err != nil { return fmt.Errorf("ttn: Failed to announce this component to TTN discovery: %s", err.Error()) } diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index fde175626..1e884382c 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -9,8 +9,8 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" - "golang.org/x/net/context" ) // BrokerCacheTime indicates how long the BrokerDiscovery should cache the services @@ -23,26 +23,26 @@ type BrokerDiscovery interface { } type brokerDiscovery struct { - serverAddress string + component *core.Component cache []*pb.Announcement cacheLock sync.RWMutex cacheValidUntil time.Time } // NewBrokerDiscovery returns a new BrokerDiscovery on top of the given gRPC connection -func NewBrokerDiscovery(serverAddress string) BrokerDiscovery { - return &brokerDiscovery{serverAddress: serverAddress} +func NewBrokerDiscovery(component *core.Component) BrokerDiscovery { + return &brokerDiscovery{component: component} } func (d *brokerDiscovery) refreshCache() error { // Connect to the server - conn, err := grpc.Dial(d.serverAddress, api.DialOptions...) + conn, err := grpc.Dial(d.component.DiscoveryServer, api.DialOptions...) if err != nil { return err } defer conn.Close() client := pb.NewDiscoveryClient(conn) - res, err := client.Discover(context.Background(), &pb.DiscoverRequest{ServiceName: "broker"}) + res, err := client.Discover(d.component.GetContext(), &pb.DiscoverRequest{ServiceName: "broker"}) if err != nil { return err } diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index e27072c8e..83aa77b97 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -6,12 +6,13 @@ import ( "time" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) func buildTestBrokerDiscoveryClient(port uint) *brokerDiscovery { - discovery := NewBrokerDiscovery(fmt.Sprintf("localhost:%d", port)).(*brokerDiscovery) + discovery := NewBrokerDiscovery(&core.Component{DiscoveryServer: fmt.Sprintf("localhost:%d", port)}).(*brokerDiscovery) discovery.refreshCache() return discovery } @@ -86,7 +87,9 @@ func TestBrokerDiscoveryCache(t *testing.T) { } d := &brokerDiscovery{ - serverAddress: fmt.Sprintf("localhost:%d", port), + component: &core.Component{ + DiscoveryServer: fmt.Sprintf("localhost:%d", port), + }, cacheValidUntil: time.Now().Add(-1 * time.Minute), cache: []*pb.Announcement{broker}, } diff --git a/core/router/router.go b/core/router/router.go index 9b20dab88..184e4b1a1 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -66,7 +66,7 @@ func (r *router) Init(c *core.Component) error { if err != nil { return err } - r.brokerDiscovery = discovery.NewBrokerDiscovery(r.Component.DiscoveryServer) + r.brokerDiscovery = discovery.NewBrokerDiscovery(r.Component) return nil } From 886d20c57fe74a676993ce7c96e621df8ba0fc04 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 6 Jun 2016 18:23:15 +0200 Subject: [PATCH 1487/2266] Tick gateway utilization in Router --- core/router/router.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/router/router.go b/core/router/router.go index 184e4b1a1..0f5016f3b 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -3,6 +3,7 @@ package router import ( "io" "sync" + "time" "google.golang.org/grpc" @@ -56,6 +57,14 @@ type router struct { brokersLock sync.RWMutex } +func (r *router) tickGateways() { + r.gatewaysLock.RLock() + defer r.gatewaysLock.RUnlock() + for _, gtw := range r.gateways { + gtw.Utilization.Tick() + } +} + func (r *router) Init(c *core.Component) error { r.Component = c err := r.Component.UpdateTokenKey() @@ -67,6 +76,11 @@ func (r *router) Init(c *core.Component) error { return err } r.brokerDiscovery = discovery.NewBrokerDiscovery(r.Component) + go func() { + for range time.Tick(5 * time.Second) { + r.tickGateways() + } + }() return nil } From a4e99b91bbd3880c22dea9f9529fdbb42ca4d687 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 6 Jun 2016 19:01:41 +0200 Subject: [PATCH 1488/2266] Actually handle GetDevices in networkserver --- core/networkserver/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 68d828a5f..4b0acd35a 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -45,7 +45,7 @@ func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesReques if err != nil { return nil, err } - return nil, nil + return s.networkServer.HandleGetDevices(req) } func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { From dfaf0baa08fd5bae4fb655f1fc96217d19d75851 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 6 Jun 2016 19:02:59 +0200 Subject: [PATCH 1489/2266] Start NetworkServer connection in Broker Init --- core/broker/broker.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/broker/broker.go b/core/broker/broker.go index 638313884..b8fa33ffb 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -5,8 +5,11 @@ import ( "sync" "time" + "google.golang.org/grpc" + "gopkg.in/redis.v3" + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/core" @@ -60,6 +63,12 @@ func (b *broker) Init(c *core.Component) error { if err != nil { return err } + conn, err := grpc.Dial(b.nsAddr, api.DialOptions...) + if err != nil { + return err + } + client := networkserver.NewNetworkServerClient(conn) + b.ns = client return nil } From dceb9986635d9640d60e4e97dad1d53961a62909 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:02:48 +0200 Subject: [PATCH 1490/2266] Prefix redis keys with component --- core/broker/application/store.go | 2 +- core/networkserver/device/store.go | 4 ++-- core/router/gateway/status.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/broker/application/store.go b/core/broker/application/store.go index 48cd21009..bb97de2d8 100644 --- a/core/broker/application/store.go +++ b/core/broker/application/store.go @@ -62,7 +62,7 @@ func NewRedisApplicationStore(client *redis.Client) Store { } } -const redisApplicationPrefix = "application" +const redisApplicationPrefix = "broker:application" type redisApplicationStore struct { client *redis.Client diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index d1dc4b7be..e8a81b953 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -142,8 +142,8 @@ func NewRedisDeviceStore(client *redis.Client) Store { } } -const redisDevicePrefix = "device" -const redisDevAddrPrefix = "dev_addr" +const redisDevicePrefix = "ns:device" +const redisDevAddrPrefix = "ns:dev_addr" type redisDeviceStore struct { client *redis.Client diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index c65e89690..b0a40af69 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -47,7 +47,7 @@ func (s *statusStore) Get() (*pb_gateway.Status, error) { func NewRedisStatusStore(client *redis.Client, eui types.GatewayEUI) StatusStore { return &redisStatusStore{ client: client, - key: fmt.Sprintf("gateway:%s", eui), + key: fmt.Sprintf("router:gateway:%s", eui), } } From e20b1d7b0a312423767515e50e97143555ab62e4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:37:35 +0200 Subject: [PATCH 1491/2266] Schedule using goroutines --- core/router/gateway/schedule.go | 90 ++++++++++--------- core/router/gateway/schedule_datastructure.go | 79 ---------------- .../gateway/schedule_datastructure_test.go | 38 -------- core/router/gateway/schedule_test.go | 16 +++- 4 files changed, 62 insertions(+), 161 deletions(-) delete mode 100644 core/router/gateway/schedule_datastructure.go delete mode 100644 core/router/gateway/schedule_datastructure_test.go diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index d66bbf575..95c8ec7ee 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -28,30 +28,38 @@ type Schedule interface { // NewSchedule creates a new Schedule func NewSchedule() Schedule { return &schedule{ - queue: NewDownlinkQueue(), - byID: make(map[string]*scheduledItem), + items: make(map[string]*scheduledItem), } } +type scheduledItem struct { + id string + time time.Time + timestamp uint32 + length uint32 + score uint + payload *router_pb.DownlinkMessage +} + type schedule struct { sync.RWMutex - active bool - offset int64 - queue *downlinkQueue - byID map[string]*scheduledItem - downlinkActive bool - downlink chan *router_pb.DownlinkMessage + active bool + offset int64 + items map[string]*scheduledItem + downlink chan *router_pb.DownlinkMessage } +// TODO: Make configurable +var Deadline = 200 * time.Millisecond + const uintmax = 1 << 32 // getConflicts walks over the schedule and returns the number of conflicts. // Both timestamp and length are in microseconds func (s *schedule) getConflicts(timestamp uint32, length uint32) (conflicts uint) { s.RLock() - snapshot := s.queue.Snapshot() - s.RUnlock() - for _, item := range snapshot { + defer s.RUnlock() + for _, item := range s.items { scheduledFrom := uint64(item.timestamp) % uintmax scheduledTo := scheduledFrom + uint64(item.length) from := uint64(timestamp) @@ -104,8 +112,16 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score } s.Lock() defer s.Unlock() - s.queue.Push(item) - s.byID[id] = item + s.items[id] = item + + // Schedule deletion after the option expires + go func() { + <-time.After(10 * time.Second) + s.Lock() + defer s.Unlock() + delete(s.items, id) + }() + return id, score } @@ -113,7 +129,7 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) error { s.Lock() defer s.Unlock() - if item, ok := s.byID[id]; ok { + if item, ok := s.items[id]; ok { item.payload = downlink if lora := downlink.GetProtocolConfiguration().GetLorawan(); lora != nil { time, _ := toa.Compute( @@ -123,44 +139,36 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro ) item.length = uint32(time / 1000) } + + if time.Now().Add(Deadline).Before(item.time) { + // Schedule transmission before the Deadline + go func() { + waitTime := item.time.Sub(time.Now().Add(Deadline)) + <-time.After(waitTime) + if s.downlink != nil { + s.downlink <- item.payload + } + }() + } else if s.downlink != nil { + // Immediately send it + s.downlink <- item.payload + } else { + // We can not send it + } + return nil } return errors.New("ID not found") } func (s *schedule) Stop() { - s.downlinkActive = false + close(s.downlink) } -// TODO: Make configurable -const deadline = 50 -const waitTime = 10 * time.Millisecond - func (s *schedule) Subscribe() <-chan *router_pb.DownlinkMessage { - if s.downlinkActive { + if s.downlink != nil { return nil } s.downlink = make(chan *router_pb.DownlinkMessage) - s.downlinkActive = true - go func() { - for s.downlinkActive { - <-time.After(waitTime) - s.Lock() - for { - item := s.queue.Peek() - if item != nil && time.Now().Add(-1*deadline*time.Millisecond).After(item.time) { - s.queue.Pop() - delete(s.byID, item.id) - if item.payload != nil { - s.downlink <- item.payload - } - } else { - break - } - } - s.Unlock() - } - close(s.downlink) - }() return s.downlink } diff --git a/core/router/gateway/schedule_datastructure.go b/core/router/gateway/schedule_datastructure.go deleted file mode 100644 index 79d6bd7e7..000000000 --- a/core/router/gateway/schedule_datastructure.go +++ /dev/null @@ -1,79 +0,0 @@ -package gateway - -import ( - "sync" - "time" - - router_pb "github.com/TheThingsNetwork/ttn/api/router" -) - -type scheduledItem struct { - id string - time time.Time - timestamp uint32 - length uint32 - score uint - payload *router_pb.DownlinkMessage -} - -// A downlinkQueue holds scheduledItems. -type downlinkQueue struct { - sync.RWMutex - items []*scheduledItem -} - -// NewDownlinkQueue creates a new downlinkQueue -func NewDownlinkQueue(items ...*scheduledItem) *downlinkQueue { - dq := &downlinkQueue{ - items: items, - } - return dq -} - -// less is used for sorting -func (dq *downlinkQueue) less(i, j int) bool { - return dq.items[i].time.Before(dq.items[j].time) -} - -// swap is used for sorting -func (dq *downlinkQueue) swap(i, j int) { - dq.items[i], dq.items[j] = dq.items[j], dq.items[i] -} - -// Push an item to the queue -func (dq *downlinkQueue) Push(item *scheduledItem) { - dq.items = append(dq.items, item) - // TODO: Insertion sort is nice, but can be optimized for the use-case of TTN (LoRaWAN RX1 and RX2) - for i := len(dq.items); i > 1; i-- { - if dq.less(i-1, i-2) { - dq.swap(i-1, i-2) - } else { - return - } - } -} - -// Pop an item from the queue -func (dq *downlinkQueue) Pop() *scheduledItem { - n := len(dq.items) - if n == 0 { - return nil - } - item := dq.items[0] - dq.items = dq.items[1:] - return item -} - -// Snapshot returns a snapshot of the downlinkQueue -func (dq *downlinkQueue) Snapshot() []*scheduledItem { - return dq.items -} - -// Peek returns the next item in the queue -func (dq *downlinkQueue) Peek() *scheduledItem { - n := len(dq.items) - if n == 0 { - return nil - } - return dq.items[0] -} diff --git a/core/router/gateway/schedule_datastructure_test.go b/core/router/gateway/schedule_datastructure_test.go deleted file mode 100644 index 823342cee..000000000 --- a/core/router/gateway/schedule_datastructure_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package gateway - -import ( - "testing" - "time" - - . "github.com/smartystreets/assertions" -) - -func TestScheduleDatastructure(t *testing.T) { - a := New(t) - dq := NewDownlinkQueue() - - a.So(dq.Peek(), ShouldBeNil) - a.So(dq.Pop(), ShouldBeNil) - - now := time.Now() - - i1 := &scheduledItem{time: now.Add(300 * time.Millisecond)} - i2 := &scheduledItem{time: now.Add(200 * time.Millisecond)} - i3 := &scheduledItem{time: now.Add(100 * time.Millisecond)} - i4 := &scheduledItem{time: now.Add(250 * time.Millisecond)} - i5 := &scheduledItem{time: now.Add(50 * time.Millisecond)} - - dq.Push(i1) - a.So(dq.Peek(), ShouldEqual, i1) - dq.Push(i2) - a.So(dq.Peek(), ShouldEqual, i2) - dq.Push(i3) - a.So(dq.Peek(), ShouldEqual, i3) - dq.Push(i4) - a.So(dq.Peek(), ShouldEqual, i3) - a.So(dq.Pop(), ShouldEqual, i3) - a.So(dq.Peek(), ShouldEqual, i2) - dq.Push(i5) - a.So(dq.Peek(), ShouldEqual, i5) - -} diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index 1d844b3a6..fe565527f 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -1,6 +1,7 @@ package gateway import ( + "fmt" "testing" "time" @@ -37,12 +38,20 @@ func TestScheduleRealtime(t *testing.T) { a.So(tm.UnixNano(), ShouldAlmostEqual, time.Now().UnixNano()+9*1000, almostEqual) } +func buildItems(items ...*scheduledItem) map[string]*scheduledItem { + m := make(map[string]*scheduledItem) + for idx, item := range items { + m[fmt.Sprintf("%d", idx)] = item + } + return m +} + func TestScheduleGetConflicts(t *testing.T) { a := New(t) // Test without overflow s := &schedule{ - queue: NewDownlinkQueue( + items: buildItems( &scheduledItem{timestamp: 5, length: 15}, &scheduledItem{timestamp: 25, length: 10}, &scheduledItem{timestamp: 55, length: 5}, @@ -60,7 +69,7 @@ func TestScheduleGetConflicts(t *testing.T) { // Test with overflow (already scheduled) s = &schedule{ - queue: NewDownlinkQueue( + items: buildItems( &scheduledItem{timestamp: 1<<32 - 1, length: 20}, ), } @@ -69,7 +78,7 @@ func TestScheduleGetConflicts(t *testing.T) { // Test with overflow (to schedule) s = &schedule{ - queue: NewDownlinkQueue( + items: buildItems( &scheduledItem{timestamp: 10, length: 20}, ), } @@ -109,6 +118,7 @@ func TestScheduleSubscribe(t *testing.T) { a := New(t) s := NewSchedule().(*schedule) s.Sync(0) + Deadline = 1 // Extremely short deadline downlink1 := &router_pb.DownlinkMessage{Payload: []byte{1}} downlink2 := &router_pb.DownlinkMessage{Payload: []byte{2}} From ac5dc7a8ac4d17a8bdd4448dbd8a71cbdbf2df5d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:39:27 +0200 Subject: [PATCH 1492/2266] Guess region in Router --- core/router/activation.go | 8 ++++++-- core/router/downlink.go | 33 ++++++++++++++++++++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/core/router/activation.go b/core/router/activation.go index 6e93c1953..2108d4edb 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -55,7 +55,11 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De if err != nil { return nil, err } - band, err := getBand(status.Region) + region := status.Region + if region == "" { + region = guessRegion(uplink.GatewayMetadata.Frequency) + } + band, err := getBand(region) if err != nil { return nil, err } @@ -63,7 +67,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De lorawan.Rx1DrOffset = 0 lorawan.Rx2Dr = uint32(band.RX2DataRate) lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds()) - switch status.Region { + switch region { case "EU_863_870": lorawan.CfList = []uint64{867100000, 867300000, 867500000, 867700000, 867900000} } diff --git a/core/router/downlink.go b/core/router/downlink.go index ac767a411..ea45ac75f 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -62,6 +62,24 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { return nil } +func guessRegion(frequency uint64) string { + switch { + case frequency >= 863000000 && frequency <= 870000000: + return "EU_863_870" + case frequency >= 902300000 && frequency <= 914900000: + return "US_902_928" + case frequency >= 779500000 && frequency <= 786500000: + return "CN_779_787" + case frequency >= 433175000 && frequency <= 434665000: + return "EU_433" + case frequency >= 915200000 && frequency <= 927800000: + return "AU_915_928" + case frequency >= 470300000 && frequency <= 489300000: + return "CN_470_510" + } + return "" +} + func getBand(region string) (band *lora.Band, err error) { var b lora.Band @@ -118,7 +136,11 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo return // We can't handle any other protocols than LoRaWAN yet } - band, err := getBand(gatewayStatus.Region) + region := gatewayStatus.Region + if region == "" { + region = guessRegion(uplink.GatewayMetadata.Frequency) + } + band, err := getBand(region) if err != nil { return // We can't handle this region } @@ -167,7 +189,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo // Configuration for RX2 { power := int32(band.DefaultTXPower) - if gatewayStatus.Region == "EU_863_870" { + if region == "EU_863_870" { power = 27 // The EU Downlink frequency allows up to 27dBm if isActivation { // TTN uses SF9BW125 in RX2, we have to reset this for joins @@ -221,6 +243,11 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, options []*pb_broker.DownlinkOption) { gatewayStatus, _ := gateway.Status.Get() // This just returns empty if non-existing + region := gatewayStatus.Region + if region == "" { + region = guessRegion(uplink.GatewayMetadata.Frequency) + } + gatewayRx, _ := gateway.Utilization.Get() for _, option := range options { @@ -254,7 +281,7 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o utilizationScore += math.Min((channelTx+channelRx)*200, 20) // 10% utilization = 20 (max) // Enforce European Duty Cycle - if gatewayStatus.Region == "EU_863_870" { + if region == "EU_863_870" { var duty float64 switch { case freq >= 863000000 && freq < 868000000: From 9eff38c1b1532a609820cefcdf561b875e980a44 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:39:50 +0200 Subject: [PATCH 1493/2266] Fix wrong downlink identifier --- core/router/downlink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index ea45ac75f..21750b923 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -224,7 +224,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo for _, option := range options { // Add router ID to downlink option if r.Component != nil && r.Component.Identity != nil { - option.Identifier = fmt.Sprintf("%s:%s", option.Identifier, r.Component.Identity.Id) + option.Identifier = fmt.Sprintf("%s:%s", r.Component.Identity.Id, option.Identifier) } // Filter all illegal options From 3feb8acda325e7e8092d115b05476d67568114ec Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:40:22 +0200 Subject: [PATCH 1494/2266] Sync gateway schedule on uplink --- core/router/uplink.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/router/uplink.go b/core/router/uplink.go index f78852810..dbf94c4c1 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -10,8 +10,9 @@ import ( ) func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error { - gateway := r.getGateway(gatewayEUI) + gateway := r.getGateway(gatewayEUI) + gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) gateway.Utilization.AddRx(uplink) downlinkOptions := r.buildDownlinkOptions(uplink, false, gateway) From d1cbcfeb0e87d05fc3466accd6e22d71a6a3380c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:40:57 +0200 Subject: [PATCH 1495/2266] Add logging to router --- core/router/activation.go | 20 +++++++++++++++++++- core/router/activation_test.go | 10 ++++++++++ core/router/downlink.go | 14 ++++++++++++++ core/router/downlink_test.go | 8 ++++++++ core/router/gateway_status.go | 14 +++++++++++++- core/router/gateway_status_test.go | 5 +++++ core/router/server_test.go | 21 ++++++++++++++++----- core/router/uplink.go | 19 ++++++++++++++++++- core/router/uplink_test.go | 5 +++++ 9 files changed, 108 insertions(+), 8 deletions(-) diff --git a/core/router/activation.go b/core/router/activation.go index 2108d4edb..386b0c03c 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -9,9 +9,21 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" ) func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + ctx := r.Ctx.WithFields(log.Fields{ + "GatewayEUI": gatewayEUI, + "AppEUI": *activation.AppEui, + "DevEUI": *activation.DevEui, + }) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle activation") + } + }() gateway := r.getGateway(gatewayEUI) @@ -72,6 +84,9 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De lorawan.CfList = []uint64{867100000, 867300000, 867500000, 867700000, 867900000} } + ctx = ctx.WithField("NumBrokers", len(brokers)) + ctx.Debug("Forward Activation") + // Forward to all brokers and collect responses var wg sync.WaitGroup responses := make(chan *pb_broker.DeviceActivationResponse, len(brokers)) @@ -101,7 +116,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De var gotFirst bool for res := range responses { if gotFirst { - // warn for duplicate responses + ctx.Warn("Duplicate Activation Response") } else { gotFirst = true downlink := &pb_broker.DownlinkMessage{ @@ -110,6 +125,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De } err := r.HandleDownlink(downlink) if err != nil { + ctx.Warn("Could not send downlink for Activation") gotFirst = false // try again } } @@ -117,9 +133,11 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Activation not accepted by any broker if !gotFirst { + ctx.Debug("Activation not accepted at this gateway") return nil, errors.New("ttn/router: Activation not accepted at this Gateway") } // Activation accepted by (at least one) broker + ctx.Debug("Activation accepted") return &pb.DeviceActivationResponse{}, nil } diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 5f174c822..4a35e9d91 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -4,8 +4,10 @@ import ( "testing" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -13,17 +15,25 @@ func TestHandleActivation(t *testing.T) { a := New(t) r := &router{ + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, gateways: map[types.GatewayEUI]*gateway.Gateway{ types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}: newReferenceGateway("EU_863_870"), }, brokerDiscovery: &mockBrokerDiscovery{}, } + appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} + devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} + uplink := newReferenceUplink() activation := &pb.DeviceActivationRequest{ Payload: []byte{}, ProtocolMetadata: uplink.ProtocolMetadata, GatewayMetadata: uplink.GatewayMetadata, + AppEui: &appEUI, + DevEui: &devEUI, } gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} diff --git a/core/router/downlink.go b/core/router/downlink.go index 21750b923..e7cfb3dfc 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -14,18 +14,26 @@ import ( "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/apex/log" lora "github.com/brocaar/lorawan/band" ) func (r *router) SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.DownlinkMessage, error) { + ctx := r.Ctx.WithFields(log.Fields{ + "GatewayEUI": gatewayEUI, + }) + gateway := r.getGateway(gatewayEUI) if fromSchedule := gateway.Schedule.Subscribe(); fromSchedule != nil { toGateway := make(chan *pb.DownlinkMessage) go func() { + ctx.Debug("Activate Downlink") for message := range fromSchedule { gateway.Utilization.AddTx(message) + ctx.Debug("Send Downlink") toGateway <- message } + ctx.Debug("Deactivate Downlink") close(toGateway) }() return toGateway, nil @@ -40,6 +48,9 @@ func (r *router) UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error { func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { option := downlink.DownlinkOption + ctx := r.Ctx.WithFields(log.Fields{ + "GatewayEUI": *option.GatewayEui, + }) gateway := r.getGateway(*option.GatewayEui) @@ -53,11 +64,14 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { if r.Component != nil && r.Component.Identity != nil { identifier = strings.TrimPrefix(option.Identifier, fmt.Sprintf("%s:", r.Component.Identity.Id)) } + ctx = ctx.WithField("Identifier", identifier) err := gateway.Schedule.Schedule(identifier, downlinkMessage) if err != nil { + ctx.WithError(err).Warn("Could not schedule Downlink") return err } + ctx.Debug("Schedule Downlink") return nil } diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 914823f3b..934cf01a5 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -10,8 +10,10 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -36,6 +38,9 @@ func TestHandleDownlink(t *testing.T) { a := New(t) r := &router{ + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, } @@ -59,6 +64,9 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { a := New(t) r := &router{ + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, } diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index 4f6c0b3b4..d9caf879b 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -6,6 +6,18 @@ import ( ) func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error { + ctx := r.Ctx.WithField("GatewayEUI", gatewayEUI) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle gateway status") + } + }() + gateway := r.getGateway(gatewayEUI) - return gateway.Status.Update(status) + err = gateway.Status.Update(status) + if err != nil { + return err + } + return nil } diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index 929d414e2..e473eda71 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -4,8 +4,10 @@ import ( "testing" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -14,6 +16,9 @@ func TestHandleGatewayStatus(t *testing.T) { eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 2} router := &router{ + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, } diff --git a/core/router/server_test.go b/core/router/server_test.go index c37245c4f..153dd53e5 100644 --- a/core/router/server_test.go +++ b/core/router/server_test.go @@ -16,8 +16,10 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -27,12 +29,15 @@ func randomPort() uint { return uint(port) } -func buildTestRouterServer(port uint) (*router, *grpc.Server) { +func buildTestRouterServer(t *testing.T, port uint) (*router, *grpc.Server) { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { panic(err) } r := &router{ + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, } @@ -46,7 +51,7 @@ func TestGatewayStatusRPC(t *testing.T) { a := New(t) port := randomPort() - r, s := buildTestRouterServer(port) + r, s := buildTestRouterServer(t, port) defer s.Stop() eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} @@ -84,7 +89,7 @@ func TestUplinkRPC(t *testing.T) { a := New(t) port := randomPort() - r, s := buildTestRouterServer(port) + r, s := buildTestRouterServer(t, port) defer s.Stop() eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} @@ -121,7 +126,7 @@ func TestSubscribeRPC(t *testing.T) { a := New(t) port := randomPort() - r, s := buildTestRouterServer(port) + r, s := buildTestRouterServer(t, port) defer s.Stop() eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} @@ -153,6 +158,8 @@ func TestSubscribeRPC(t *testing.T) { wg.Add(1) schedule := r.getGateway(eui).Schedule + gateway.Deadline = 1 // Extremely short deadline + schedule.Sync(0) id, _ := schedule.GetOption(300, 50) schedule.Schedule(id, downlink) @@ -163,10 +170,12 @@ func TestActivateRPC(t *testing.T) { a := New(t) port := randomPort() - r, s := buildTestRouterServer(port) + r, s := buildTestRouterServer(t, port) defer s.Stop() eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) if err != nil { @@ -184,6 +193,8 @@ func TestActivateRPC(t *testing.T) { Payload: []byte{}, ProtocolMetadata: uplink.ProtocolMetadata, GatewayMetadata: uplink.GatewayMetadata, + AppEui: &appEUI, + DevEui: &devEUI, } res, err := client.Activate(ctx, activation) a.So(res, ShouldBeNil) diff --git a/core/router/uplink.go b/core/router/uplink.go index dbf94c4c1..056535df2 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -6,10 +6,18 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" "github.com/brocaar/lorawan" ) func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error { + ctx := r.Ctx.WithField("GatewayEUI", gatewayEUI) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle uplink") + } + }() gateway := r.getGateway(gatewayEUI) gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) @@ -19,7 +27,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload - err := phyPayload.UnmarshalBinary(uplink.Payload) + err = phyPayload.UnmarshalBinary(uplink.Payload) if err != nil { return err } @@ -32,6 +40,10 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess } devEUI := types.DevEUI(joinRequestPayload.DevEUI) appEUI := types.AppEUI(joinRequestPayload.AppEUI) + ctx.WithFields(log.Fields{ + "DevEUI": devEUI, + "AppEUI": appEUI, + }).Debug("Handle Uplink as Activation") _, err := r.HandleActivation(gatewayEUI, &pb.DeviceActivationRequest{ Payload: uplink.Payload, DevEui: &devEUI, @@ -52,12 +64,17 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess } devAddr := types.DevAddr(macPayload.FHDR.DevAddr) + ctx = ctx.WithField("DevAddr", devAddr) + // Find Broker brokers, err := r.brokerDiscovery.Discover(devAddr) if err != nil { return err } + ctx = ctx.WithField("NumBrokers", len(brokers)) + ctx.Debug("Forward Uplink") + // Forward to all brokers for _, broker := range brokers { broker, err := r.getBroker(broker) diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index b6045cbe2..50b981b7d 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -8,8 +8,10 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -69,6 +71,9 @@ func TestHandleUplink(t *testing.T) { a := New(t) r := &router{ + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, } From 801a6531b695944e043e75883e33dc3311fe3d59 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:41:19 +0200 Subject: [PATCH 1496/2266] Use grpc codes in broker errors --- core/broker/server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/broker/server.go b/core/broker/server.go index 96ec559ba..413d7c868 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -1,13 +1,13 @@ package broker import ( - "errors" "io" pb_api "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -20,7 +20,7 @@ func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { // TODO: Check OK id, ok := md["id"] if !ok || len(id) < 1 { - err = errors.New("ttn/broker: Caller did not provide \"id\" in context") + err = grpc.Errorf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"id\" in context") return } callerID = id[0] @@ -29,12 +29,12 @@ func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { } token, ok := md["token"] if !ok || len(token) < 1 { - err = errors.New("ttn/broker: Caller did not provide \"token\" in context") + err = grpc.Errorf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"token\" in context") return } if token[0] != "token" { // TODO: Validate Token - err = errors.New("ttn/broker: Caller not authorized") + err = grpc.Errorf(codes.Unauthenticated, "ttn/broker: Caller not authorized") return } From a748885ddf8fb3e1eb25f86ad6d0f15797603b6e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:43:43 +0200 Subject: [PATCH 1497/2266] Update broker cache when router starts --- core/router/router.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/router/router.go b/core/router/router.go index 0f5016f3b..61d093ae7 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -76,6 +76,7 @@ func (r *router) Init(c *core.Component) error { return err } r.brokerDiscovery = discovery.NewBrokerDiscovery(r.Component) + r.brokerDiscovery.All() // Update cache go func() { for range time.Tick(5 * time.Second) { r.tickGateways() From d70b5bcdb7dfd55b7365d92183064ea6e7dc41be Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:45:50 +0200 Subject: [PATCH 1498/2266] Add gRPC interceptors --- core/component.go | 60 ++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/core/component.go b/core/component.go index f4af615a9..71ea1f5a6 100644 --- a/core/component.go +++ b/core/component.go @@ -7,12 +7,14 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/dgrijalva/jwt-go" + "github.com/mwitkow/go-grpc-middleware" "github.com/spf13/viper" ) @@ -76,15 +78,16 @@ func (c *Component) Announce() error { // UpdateTokenKey updates the OAuth Bearer token key func (c *Component) UpdateTokenKey() error { if c.TokenKeyProvider == nil { - return errors.New("No token provider configured") + return errors.New("ttn: No public key provider configured for token validation") } // Set up Auth Server Token Validation tokenKey, err := c.TokenKeyProvider.Get(true) if err != nil { - return fmt.Errorf("ttn: Failed to refresh token key: %s", err.Error()) + c.Ctx.Warnf("ttn: Failed to refresh public key for token validation: %s", err.Error()) + } else { + c.Ctx.Infof("ttn: Got public key for token validation (%v)", tokenKey.Algorithm) } - c.Ctx.Infof("ttn: Got token key for algorithm %v", tokenKey.Algorithm) return nil @@ -114,6 +117,57 @@ func (c *Component) ValidateToken(token string) (claims map[string]interface{}, return parsed.Claims, nil } +func (c *Component) ServerOptions() []grpc.ServerOption { + unary := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + var peerAddr string + peer, ok := peer.FromContext(ctx) + if ok { + peerAddr = peer.Addr.String() + } + var peerID string + meta, ok := metadata.FromContext(ctx) + if ok { + id, ok := meta["id"] + if ok && len(id) > 0 { + peerID = id[0] + } + } + c.Ctx.WithFields(log.Fields{ + "CallerID": peerID, + "CallerIP": peerAddr, + "Method": info.FullMethod, + }).Debug("Handle Request") + return handler(ctx, req) + } + + stream := func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + var peerAddr string + peer, ok := peer.FromContext(stream.Context()) + if ok { + peerAddr = peer.Addr.String() + } + var peerID string + meta, ok := metadata.FromContext(stream.Context()) + if ok { + id, ok := meta["id"] + if ok && len(id) > 0 { + peerID = id[0] + } + } + c.Ctx.WithFields(log.Fields{ + "CallerID": peerID, + "CallerIP": peerAddr, + "Method": info.FullMethod, + }).Debug("Start Stream") + return handler(srv, stream) + } + + return []grpc.ServerOption{ + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary)), + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream)), + } +} + // GetContext returns a context for outgoing RPC requests func (c *Component) GetContext() context.Context { var id, token string From e7801c3faad602ea89306031d7c2022c371c6f1e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 16:48:08 +0200 Subject: [PATCH 1499/2266] Use KeepAlive in dialers --- api/api.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/api/api.go b/api/api.go index 87e4be6fa..d2f0ac59f 100644 --- a/api/api.go +++ b/api/api.go @@ -1,14 +1,29 @@ package api import ( + "net" "time" "google.golang.org/grpc" ) -// DialOptions are the gRPC dial options for discovery calls +// Backoff indicates how long a client should wait between failed requests +var Backoff = 1 * time.Second + +// KeepAlive indicates the keep-alive time for the Dialer +var KeepAlive = 10 * time.Second + +// DialOptions to use in TTN gRPC // TODO: disable insecure connections var DialOptions = []grpc.DialOption{ grpc.WithInsecure(), - grpc.WithTimeout(2 * time.Second), + WithKeepAliveDialer(), +} + +// WithKeepAliveDialer creates a dialer with the configured KeepAlive time +func WithKeepAliveDialer() grpc.DialOption { + return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + d := net.Dialer{Timeout: timeout, KeepAlive: KeepAlive} + return d.Dial("tcp", addr) + }) } From 3d59773657da26572fbc6f2d6b0af79b2ff6b9e1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 17:41:23 +0200 Subject: [PATCH 1500/2266] Log component statistics every minute --- core/component.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/component.go b/core/component.go index 71ea1f5a6..a9c95440c 100644 --- a/core/component.go +++ b/core/component.go @@ -3,6 +3,8 @@ package core import ( "errors" "fmt" + "runtime" + "time" "golang.org/x/net/context" "google.golang.org/grpc" @@ -25,6 +27,17 @@ type ComponentInterface interface { // NewComponent creates a new Component func NewComponent(ctx log.Interface, serviceName string, announcedAddress string) *Component { + go func() { + memstats := new(runtime.MemStats) + for range time.Tick(time.Minute) { + runtime.ReadMemStats(memstats) + ctx.WithFields(log.Fields{ + "Goroutines": runtime.NumGoroutine(), + "Memory": float64(memstats.Alloc) / 1000000, + }).Debugf("Stats") + } + }() + return &Component{ Ctx: ctx, Identity: &pb_discovery.Announcement{ From a5d2f05e166cd3ebd28f24802630088a0f1c9c13 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 17:43:49 +0200 Subject: [PATCH 1501/2266] Lazy copy-paste fix --- core/router/downlink_test.go | 4 ++-- core/router/gateway/schedule.go | 2 ++ core/router/gateway_status_test.go | 2 +- core/router/server_test.go | 2 +- core/router/uplink_test.go | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 934cf01a5..6ae9e8c53 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -39,7 +39,7 @@ func TestHandleDownlink(t *testing.T) { r := &router{ Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleActivation"), + Ctx: GetLogger(t, "TestHandleDownlink"), }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, @@ -65,7 +65,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { r := &router{ Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleActivation"), + Ctx: GetLogger(t, "TestSubscribeUnsubscribeDownlink"), }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 95c8ec7ee..a5cb0dd5e 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -115,6 +115,7 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score s.items[id] = item // Schedule deletion after the option expires + // TODO: Periodically clean up instead of this goroutine go func() { <-time.After(10 * time.Second) s.Lock() @@ -163,6 +164,7 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro func (s *schedule) Stop() { close(s.downlink) + s.downlink = nil } func (s *schedule) Subscribe() <-chan *router_pb.DownlinkMessage { diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index e473eda71..a5c6497f8 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -17,7 +17,7 @@ func TestHandleGatewayStatus(t *testing.T) { router := &router{ Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleActivation"), + Ctx: GetLogger(t, "TestHandleGatewayStatus"), }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, } diff --git a/core/router/server_test.go b/core/router/server_test.go index 153dd53e5..3038147e1 100644 --- a/core/router/server_test.go +++ b/core/router/server_test.go @@ -36,7 +36,7 @@ func buildTestRouterServer(t *testing.T, port uint) (*router, *grpc.Server) { } r := &router{ Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleActivation"), + Ctx: GetLogger(t, "TestRouterServer"), }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index 50b981b7d..57993d0e4 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -72,7 +72,7 @@ func TestHandleUplink(t *testing.T) { r := &router{ Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleActivation"), + Ctx: GetLogger(t, "TestHandleUplink"), }, gateways: map[types.GatewayEUI]*gateway.Gateway{}, brokerDiscovery: &mockBrokerDiscovery{}, From 7d04674919e7caf3cd441e2ea3915d9cb9435595 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 7 Jun 2016 18:03:54 +0200 Subject: [PATCH 1502/2266] Add logging to Broker --- core/broker/activation.go | 30 +++++++++++++++++++---- core/broker/activation_test.go | 10 +++++--- core/broker/downlink.go | 17 ++++++++++++- core/broker/downlink_test.go | 17 +++++++++++-- core/broker/server_test.go | 19 +++++++++++---- core/broker/uplink.go | 44 +++++++++++++++++++++++++++------- core/broker/uplink_test.go | 23 +++++++++++------- 7 files changed, 129 insertions(+), 31 deletions(-) diff --git a/core/broker/activation.go b/core/broker/activation.go index 5176e7740..40678f0b5 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -12,16 +12,30 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/gateway" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/broker/application" + "github.com/apex/log" ) func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + ctx := b.Ctx.WithFields(log.Fields{ + "GatewayEUI": *activation.GatewayMetadata.GatewayEui, + "AppEUI": *activation.AppEui, + "DevEUI": *activation.DevEui, + }) var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle activation") + } + }() + time := time.Now() // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) if len(duplicates) == 0 { - return nil, errors.New("ttn/broker: No duplicates") + err = errors.New("ttn/broker: No duplicates") + return nil, err } base := duplicates[0] @@ -61,18 +75,24 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } // Find Handler (based on AppEUI) - application, err := b.applications.Get(*base.AppEui) + var application *application.Application + application, err = b.applications.Get(*base.AppEui) if err != nil { return nil, err } - conn, err := grpc.Dial(application.HandlerNetAddress, api.DialOptions...) + ctx = ctx.WithField("HandlerID", application.HandlerID) + ctx.Debug("Forward Activation") + + var conn *grpc.ClientConn + conn, err = grpc.Dial(application.HandlerNetAddress, api.DialOptions...) if err != nil { return nil, err } defer conn.Close() client := pb_handler.NewHandlerClient(conn) - handlerResponse, err := client.Activate(b.Component.GetContext(), deduplicatedActivationRequest) + var handlerResponse *pb_handler.DeviceActivationResponse + handlerResponse, err = client.Activate(b.Component.GetContext(), deduplicatedActivationRequest) if err != nil { return nil, err } @@ -87,6 +107,8 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D DownlinkOption: handlerResponse.DownlinkOption, } + ctx.Debug("Successful Activation") + return deviceActivationResponse, nil } diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index 0468e991f..d4f08699b 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -11,17 +11,21 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestHandleActivation(t *testing.T) { a := New(t) + gtwEUI := types.GatewayEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) devEUI := types.DevEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) appEUI := types.AppEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) b := &broker{ - Component: &core.Component{}, + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleActivation"), + }, activationDeduplicator: NewDeduplicator(10 * time.Millisecond), applications: application.NewApplicationStore(), ns: &mockNetworkServer{}, @@ -32,7 +36,7 @@ func TestHandleActivation(t *testing.T) { Payload: []byte{}, DevEui: &devEUI, AppEui: &appEUI, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{}, }) a.So(err, ShouldNotBeNil) @@ -47,7 +51,7 @@ func TestHandleActivation(t *testing.T) { Payload: []byte{}, DevEui: &devEUI, AppEui: &appEUI, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{}, }) a.So(err, ShouldNotBeNil) diff --git a/core/broker/downlink.go b/core/broker/downlink.go index 270bfff91..9887c3f38 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -5,6 +5,7 @@ import ( "strings" pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/apex/log" ) // ByScore is used to sort a list of DownlinkOptions based on Score @@ -15,7 +16,17 @@ func (a ByScore) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByScore) Less(i, j int) bool { return a[i].Score < a[j].Score } func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { + ctx := b.Ctx.WithFields(log.Fields{ + "DevEUI": *downlink.DevEui, + "AppEUI": *downlink.AppEui, + }) var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle downlink") + } + }() + downlink, err = b.ns.Downlink(b.Component.GetContext(), downlink) if err != nil { return err @@ -27,12 +38,16 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { } else { return errors.New("ttn/broker: Invalid downlink option") } + ctx = ctx.WithField("RouterID", routerID) - router, err := b.getRouter(routerID) + var router chan<- *pb.DownlinkMessage + router, err = b.getRouter(routerID) if err != nil { return err } + ctx.Debug("Send Downlink") + router <- downlink return nil diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go index 8a65311ff..8180b2e61 100644 --- a/core/broker/downlink_test.go +++ b/core/broker/downlink_test.go @@ -5,23 +5,32 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestDownlink(t *testing.T) { a := New(t) + appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} + devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} + dlch := make(chan *pb.DownlinkMessage, 2) b := &broker{ - Component: &core.Component{}, - ns: &mockNetworkServer{}, + Component: &core.Component{ + Ctx: GetLogger(t, "TestDownlink"), + }, + ns: &mockNetworkServer{}, routers: map[string]chan *pb.DownlinkMessage{ "routerID": dlch, }, } err := b.HandleDownlink(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, DownlinkOption: &pb.DownlinkOption{ Identifier: "fakeID", }, @@ -29,6 +38,8 @@ func TestDownlink(t *testing.T) { a.So(err, ShouldNotBeNil) err = b.HandleDownlink(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, DownlinkOption: &pb.DownlinkOption{ Identifier: "nonExistentRouterID:scheduleID", }, @@ -36,6 +47,8 @@ func TestDownlink(t *testing.T) { a.So(err, ShouldNotBeNil) err = b.HandleDownlink(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, DownlinkOption: &pb.DownlinkOption{ Identifier: "routerID:scheduleID", }, diff --git a/core/broker/server_test.go b/core/broker/server_test.go index f3ac41967..cb5af7203 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -10,6 +10,8 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" "golang.org/x/net/context" @@ -23,13 +25,15 @@ func randomPort() uint { return uint(port) } -func buildTestBrokerServer(port uint) (*broker, *grpc.Server) { +func buildTestBrokerServer(t *testing.T, port uint) (*broker, *grpc.Server) { lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) if err != nil { panic(err) } b := &broker{ - Component: &core.Component{}, + Component: &core.Component{ + Ctx: GetLogger(t, "TestBrokerServer"), + }, routers: make(map[string]chan *pb.DownlinkMessage), handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), ns: &mockNetworkServer{}, @@ -46,7 +50,7 @@ func TestAssociateRPC(t *testing.T) { a := New(t) port := randomPort() - b, s := buildTestBrokerServer(port) + b, s := buildTestBrokerServer(t, port) defer s.Stop() conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) @@ -81,7 +85,7 @@ func TestSubscribeRPC(t *testing.T) { a := New(t) port := randomPort() - b, s := buildTestBrokerServer(port) + b, s := buildTestBrokerServer(t, port) defer s.Stop() conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) @@ -118,8 +122,11 @@ func TestSubscribeRPC(t *testing.T) { func TestPublishRPC(t *testing.T) { a := New(t) + appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} + devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} + port := randomPort() - b, s := buildTestBrokerServer(port) + b, s := buildTestBrokerServer(t, port) defer s.Stop() conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) @@ -139,6 +146,8 @@ func TestPublishRPC(t *testing.T) { stream, _ := client.Publish(ctx) stream.Send(&pb.DownlinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, DownlinkOption: &pb.DownlinkOption{ Identifier: "routerID:scheduleID", }, diff --git a/core/broker/uplink.go b/core/broker/uplink.go index a9ab5f8dd..2d5c80abe 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -11,7 +11,9 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -24,6 +26,14 @@ var ( ) func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { + ctx := b.Ctx.WithField("GatewayEUI", *uplink.GatewayMetadata.GatewayEui) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle uplink") + } + }() + time := time.Now() // De-duplicate uplink messages @@ -40,18 +50,21 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload - err := phyPayload.UnmarshalBinary(base.Payload) + err = phyPayload.UnmarshalBinary(base.Payload) if err != nil { return err } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return errors.New("Uplink message does not contain a MAC payload.") + err = errors.New("Uplink message does not contain a MAC payload.") + return err } // Request devices from NS devAddr := types.DevAddr(macPayload.FHDR.DevAddr) - getDevicesResp, err := b.ns.GetDevices(b.Component.GetContext(), &networkserver.DevicesRequest{ + ctx = ctx.WithField("DevAddr", devAddr) + var getDevicesResp *networkserver.DevicesResponse + getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(), &networkserver.DevicesRequest{ DevAddr: &devAddr, FCnt: macPayload.FHDR.FCnt, }) @@ -59,8 +72,10 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } if len(getDevicesResp.Results) == 0 { - return ErrNotFound + err = ErrNotFound + return err } + ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) // Find AppEUI/DevEUI through MIC check var device *pb_networkserver.DevicesResponse_Device @@ -79,14 +94,21 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } } if device == nil { - return ErrNoMatch + err = ErrNoMatch + return err } + ctx = ctx.WithFields(log.Fields{ + "DevEUI": device.DevEui, + "AppEUI": device.AppEui, + "FCnt": device.FullFCnt, + }) if device.DisableFCntCheck { // TODO: Add warning to message? } else if macPayload.FHDR.FCnt < device.StoredFCnt || macPayload.FHDR.FCnt-device.StoredFCnt > maxFCntGap { // Replay attack or FCnt gap too big - return ErrInvalidFCnt + err = ErrInvalidFCnt + return err } // Add FCnt to Metadata (because it's not marshaled in lorawan payload) @@ -125,16 +147,22 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } - application, err := b.applications.Get(*device.AppEui) + var application *application.Application + application, err = b.applications.Get(*device.AppEui) if err != nil { return err } - handler, err := b.getHandler(application.HandlerID) + ctx = ctx.WithField("HandlerID", application.HandlerID) + + var handler chan<- *pb.DeduplicatedUplinkMessage + handler, err = b.getHandler(application.HandlerID) if err != nil { return err } + ctx.Debug("Forward Uplink") + handler <- deduplicatedUplink return nil diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 0a307a03d..fe0f21686 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -21,17 +22,21 @@ func TestHandleUplink(t *testing.T) { a := New(t) b := &broker{ - Component: &core.Component{}, + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleUplink"), + }, uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ devices: []*pb_networkserver.DevicesResponse_Device{}, }, } + gtwEUI := types.GatewayEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + // Invalid Payload err := b.HandleUplink(&pb.UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03}, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{}, }) a.So(err, ShouldNotBeNil) @@ -55,7 +60,7 @@ func TestHandleUplink(t *testing.T) { b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldEqual, ErrNotFound) @@ -66,7 +71,9 @@ func TestHandleUplink(t *testing.T) { // Add devices b = &broker{ - Component: &core.Component{}, + Component: &core.Component{ + Ctx: GetLogger(t, "TestHandleUplink"), + }, handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ @@ -88,7 +95,7 @@ func TestHandleUplink(t *testing.T) { b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldEqual, ErrNoMatch) @@ -100,7 +107,7 @@ func TestHandleUplink(t *testing.T) { b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldEqual, ErrInvalidFCnt) @@ -110,7 +117,7 @@ func TestHandleUplink(t *testing.T) { b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = true err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) @@ -121,7 +128,7 @@ func TestHandleUplink(t *testing.T) { b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = false err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) From b2ddd513ab5cd91d6bf952a944f31423ee39691e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 13 Jun 2016 13:19:57 +0200 Subject: [PATCH 1503/2266] Implement DevAddr Masking --- core/types/dev_addr.go | 22 ++++++++++++++++++++++ core/types/dev_addr_test.go | 23 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index f95c2de06..9225e012f 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -90,3 +90,25 @@ var empty DevAddr func (addr DevAddr) IsEmpty() bool { return addr == empty } + +// Mask returns a copy of the DevAddr with only the first "bits" bits +func (addr DevAddr) Mask(bits int) (masked DevAddr) { + n := uint(bits) + for i := 0; i < 4; i++ { + if n >= 8 { + masked[i] = addr[i] & 0xff + n -= 8 + continue + } + masked[i] = addr[i] & ^byte(0xff>>n) + n = 0 + } + return +} + +// HasPrefix returns true if the DevAddr has a prefix of given length +func (addr DevAddr) HasPrefix(length int, prefixBytes []byte) bool { + var prefix DevAddr + copy(prefix[:], prefixBytes) + return addr.Mask(length) == prefix.Mask(length) +} diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index 9962f01fa..4e0546ba7 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -63,3 +63,26 @@ func TestDevAddr(t *testing.T) { a.So(empty.IsEmpty(), ShouldEqual, true) a.So(addr.IsEmpty(), ShouldEqual, false) } + +func TestDevAddrMask(t *testing.T) { + a := New(t) + d1 := DevAddr{255, 255, 255, 255} + a.So(d1.Mask(1), ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(d1.Mask(2), ShouldEqual, DevAddr{192, 0, 0, 0}) + a.So(d1.Mask(3), ShouldEqual, DevAddr{224, 0, 0, 0}) + a.So(d1.Mask(4), ShouldEqual, DevAddr{240, 0, 0, 0}) + a.So(d1.Mask(5), ShouldEqual, DevAddr{248, 0, 0, 0}) + a.So(d1.Mask(6), ShouldEqual, DevAddr{252, 0, 0, 0}) + a.So(d1.Mask(7), ShouldEqual, DevAddr{254, 0, 0, 0}) + a.So(d1.Mask(8), ShouldEqual, DevAddr{255, 0, 0, 0}) +} + +func TestDevAddrHasPrefix(t *testing.T) { + a := New(t) + a.So(DevAddr{1, 2, 3, 4}.HasPrefix(0, []byte{}), ShouldBeTrue) + a.So(DevAddr{1, 2, 3, 4}.HasPrefix(32, []byte{1, 2, 3, 4}), ShouldBeTrue) + a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{1, 2, 3, 4}), ShouldBeTrue) + a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{2, 2, 3, 4}), ShouldBeFalse) + a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{1, 1, 3, 4}), ShouldBeFalse) + a.So(DevAddr{1, 2, 3, 4}.HasPrefix(15, []byte{1, 1}), ShouldBeFalse) +} From 12c6624c4fa0cfce1666463e812ae44f86e12dab Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 13 Jun 2016 13:20:44 +0200 Subject: [PATCH 1504/2266] Use configurable NetID in Networkserver --- core/networkserver/networkserver.go | 30 ++++++++++++++++++------ core/networkserver/networkserver_test.go | 19 ++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 2448fccef..791fcc6f4 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -29,14 +29,17 @@ type NetworkServer interface { } // NewRedisNetworkServer creates a new Redis-backed NetworkServer -func NewRedisNetworkServer(client *redis.Client) NetworkServer { - return &networkServer{ +func NewRedisNetworkServer(client *redis.Client, netID int) NetworkServer { + ns := &networkServer{ devices: device.NewRedisDeviceStore(client), } + ns.netID = [3]byte{byte(netID >> 16), byte(netID >> 8), byte(netID)} + return ns } type networkServer struct { *core.Component + netID [3]byte devices device.Store } @@ -46,6 +49,13 @@ func (n *networkServer) Init(c *core.Component) error { if err != nil { return err } + // Set fake device + n.devices.Set(&device.Device{ + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevAddr: types.DevAddr{1, 2, 3, 4}, + NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + }) return nil } @@ -88,9 +98,6 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes return res, nil } -var netID = [3]byte{0x00, 0x00, 0x13} -var nwkID byte = 0x13 - func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { // Build activation metadata if not present if meta := activation.GetActivationMetadata(); meta == nil { @@ -111,7 +118,7 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat // TODO: Be smarter than just randomly generating addresses. var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) - devAddr[0] = (nwkID << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb + devAddr[0] = (n.netID[2] << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb // Set the DevAddr in the Activation Metadata lorawanMeta.DevAddr = &devAddr @@ -123,7 +130,7 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat Major: lorawan.LoRaWANR1, }, MACPayload: &lorawan.JoinAcceptPayload{ - NetID: netID, + NetID: n.netID, DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)}, RXDelay: uint8(lorawanMeta.RxDelay), DevAddr: lorawan.DevAddr(devAddr), @@ -200,6 +207,15 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag message.ResponseTemplate.AppEui = message.AppEui message.ResponseTemplate.DevEui = message.DevEui + // Add Full FCnt (avoiding nil pointer panics) + if option := message.ResponseTemplate.DownlinkOption; option != nil { + if protocol := option.ProtocolConfig; protocol != nil { + if lorawan := protocol.GetLorawan(); lorawan != nil { + lorawan.FCnt = dev.FCntDown + } + } + } + phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index e728a9045..7226069ce 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -4,6 +4,8 @@ import ( "testing" "time" + "gopkg.in/redis.v3" + "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" @@ -26,6 +28,21 @@ func getEUI(bytes ...byte) (eui types.EUI64) { return } +func TestNewNetworkServer(t *testing.T) { + a := New(t) + var client redis.Client + + // TTN NetID + ns := NewRedisNetworkServer(&client, 19) + a.So(ns, ShouldNotBeNil) + a.So(ns.(*networkServer).netID, ShouldEqual, [3]byte{0, 0, 0x13}) + + // Other NetID, same NwkID + ns = NewRedisNetworkServer(&client, 66067) + a.So(ns, ShouldNotBeNil) + a.So(ns.(*networkServer).netID, ShouldEqual, [3]byte{0x01, 0x02, 0x13}) +} + func TestHandleGetDevices(t *testing.T) { a := New(t) @@ -129,7 +146,7 @@ func TestHandleGetDevices(t *testing.T) { func TestHandlePrepareActivation(t *testing.T) { a := New(t) - ns := &networkServer{} + ns := &networkServer{netID: [3]byte{0, 0, 0x13}} resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ From 60fb88d01c99e626e3cf2418c6ae70f38c322ccc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 13 Jun 2016 14:10:43 +0200 Subject: [PATCH 1505/2266] Parsing of DevAddr Prefix --- core/types/dev_addr.go | 16 ++++++++++++++++ core/types/dev_addr_test.go | 18 ++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 9225e012f..36cce701e 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -3,6 +3,8 @@ package types import ( "encoding/hex" "errors" + "regexp" + "strconv" "strings" ) @@ -112,3 +114,17 @@ func (addr DevAddr) HasPrefix(length int, prefixBytes []byte) bool { copy(prefix[:], prefixBytes) return addr.Mask(length) == prefix.Mask(length) } + +// ParseDevAddrPrefix parses a DevAddr in prefix notation (01020304/24) to a prefix (01020300) and length (24) +func ParseDevAddrPrefix(prefix string) (addr DevAddr, length int, err error) { + pattern := regexp.MustCompile("([[:xdigit:]]{8})/([[:digit:]]+)") + matches := pattern.FindStringSubmatch(prefix) + if len(matches) != 3 { + err = errors.New("Invalid Prefix") + return + } + addr, _ = ParseDevAddr(matches[1]) // errors handled in regexp + length, _ = strconv.Atoi(matches[2]) // errors handled in regexp + addr = addr.Mask(length) + return +} diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index 4e0546ba7..f17efdc90 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -86,3 +86,21 @@ func TestDevAddrHasPrefix(t *testing.T) { a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{1, 1, 3, 4}), ShouldBeFalse) a.So(DevAddr{1, 2, 3, 4}.HasPrefix(15, []byte{1, 1}), ShouldBeFalse) } + +func TestParseDevAddrPrefix(t *testing.T) { + a := New(t) + addr, length, err := ParseDevAddrPrefix("XYZ") + a.So(err, ShouldNotBeNil) + addr, length, err = ParseDevAddrPrefix("00/bla") + a.So(err, ShouldNotBeNil) + addr, length, err = ParseDevAddrPrefix("00/1") + a.So(err, ShouldNotBeNil) + addr, length, err = ParseDevAddrPrefix("01020304/1") + a.So(err, ShouldBeNil) + a.So(addr, ShouldEqual, DevAddr{0, 0, 0, 0}) + a.So(length, ShouldEqual, 1) + addr, length, err = ParseDevAddrPrefix("ff020304/1") + a.So(err, ShouldBeNil) + a.So(addr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(length, ShouldEqual, 1) +} From 20f982f064a2c8fe517e3bcd1d8c83e9d857bc20 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 13 Jun 2016 14:14:50 +0200 Subject: [PATCH 1506/2266] Use DevAddr Prefix in Networkserver --- core/networkserver/networkserver.go | 34 +++++++++++++++++++++--- core/networkserver/networkserver_test.go | 13 ++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 791fcc6f4..41398870c 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -21,6 +21,7 @@ import ( // NetworkServer implements LoRaWAN-specific functionality for TTN type NetworkServer interface { core.ComponentInterface + UsePrefix(prefixBytes []byte, length int) error HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error) HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) @@ -34,13 +35,29 @@ func NewRedisNetworkServer(client *redis.Client, netID int) NetworkServer { devices: device.NewRedisDeviceStore(client), } ns.netID = [3]byte{byte(netID >> 16), byte(netID >> 8), byte(netID)} + ns.prefix = [4]byte{ns.netID[2] << 1, 0, 0, 0} + ns.prefixLength = 7 return ns } type networkServer struct { *core.Component - netID [3]byte - devices device.Store + devices device.Store + netID [3]byte + prefix [4]byte + prefixLength int +} + +func (n *networkServer) UsePrefix(prefixBytes []byte, length int) error { + if length < 7 { + return errors.New("ttn/networkserver: Invalid prefix length") + } + if prefixBytes[0]>>1 != n.netID[2] { + return errors.New("ttn/networkserver: Invalid prefix") + } + copy(n.prefix[:], prefixBytes) + n.prefixLength = length + return nil } func (n *networkServer) Init(c *core.Component) error { @@ -115,10 +132,19 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat lorawanMeta := activation.ActivationMetadata.GetLorawan() // Generate random DevAddr - // TODO: Be smarter than just randomly generating addresses. var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) - devAddr[0] = (n.netID[2] << 1) | (devAddr[0] & 1) // DevAddr 7 msb are NetID 7 lsb + // Fill the prefixSize first bits with the prefix + k := uint(n.prefixLength) + for i := 0; i < 4; i++ { + if k >= 8 { + devAddr[i] = n.prefix[i] & 0xff + k -= 8 + continue + } + devAddr[i] = n.prefix[i] & ^byte(0xff>>k) + k = 0 + } // Set the DevAddr in the Activation Metadata lorawanMeta.DevAddr = &devAddr diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 7226069ce..e5eb9dd73 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -43,6 +43,17 @@ func TestNewNetworkServer(t *testing.T) { a.So(ns.(*networkServer).netID, ShouldEqual, [3]byte{0x01, 0x02, 0x13}) } +func TestUsePrefix(t *testing.T) { + a := New(t) + var client redis.Client + ns := NewRedisNetworkServer(&client, 19) + + a.So(ns.UsePrefix([]byte{}, 0), ShouldNotBeNil) + a.So(ns.UsePrefix([]byte{0x14}, 7), ShouldNotBeNil) + a.So(ns.UsePrefix([]byte{0x26}, 7), ShouldBeNil) + a.So(ns.(*networkServer).prefix, ShouldEqual, [4]byte{0x26, 0x00, 0x00, 0x00}) +} + func TestHandleGetDevices(t *testing.T) { a := New(t) @@ -146,7 +157,7 @@ func TestHandleGetDevices(t *testing.T) { func TestHandlePrepareActivation(t *testing.T) { a := New(t) - ns := &networkServer{netID: [3]byte{0, 0, 0x13}} + ns := &networkServer{netID: [3]byte{0x00, 0x00, 0x13}, prefix: [4]byte{0x26, 0x00, 0x00, 0x00}, prefixLength: 7} resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ From 71d850ddb97475a334e2a07df67ffab3225c561f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 13 Jun 2016 14:27:46 +0200 Subject: [PATCH 1507/2266] Use BGP-like prefix announcements --- api/discovery/discovery.proto | 3 +++ core/discovery/broker_discovery.go | 3 +-- core/discovery/broker_discovery_test.go | 8 ++++---- core/discovery/discovery_integration_test.go | 4 ++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 11fbb07b3..ab742c8b5 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -7,6 +7,9 @@ package discovery; message Metadata { enum Key { OTHER = 0; + + // The value for PREFIX consists of 1 byte denoting the number of bits, + // followed by the prefix and enough trailing bits to fill 4 octets PREFIX = 1; } Key key = 1; diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 1e884382c..8e80e954f 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -1,7 +1,6 @@ package discovery import ( - "bytes" "sync" "time" @@ -77,7 +76,7 @@ func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, e matches := []*pb.Announcement{} for _, service := range d.cache { for _, meta := range service.Metadata { - if meta.Key == pb.Metadata_PREFIX && bytes.HasPrefix(devAddr.Bytes(), meta.Value) { + if meta.Key == pb.Metadata_PREFIX && len(meta.Value) == 5 && devAddr.HasPrefix(int(meta.Value[0]), meta.Value[1:]) { matches = append(matches, service) break } diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index 83aa77b97..52138a9b8 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -23,21 +23,21 @@ func TestBrokerDiscovery(t *testing.T) { // Broker1 has a prefix with all DevAddrs broker1 := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{}}, + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0, 0x00, 0x00, 0x00, 0x00}}, }, } // Broker2 has one DevAddr prefix broker2 := &pb.Announcement{ServiceName: "broker", Token: "broker2", NetAddress: "localhost2:1881", Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x01}}, + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, }, } // Broker3 has multiple DevAddr prefixes broker3 := &pb.Announcement{ServiceName: "broker", Token: "broker3", NetAddress: "localhost3:1881", Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x02, 0x03}}, + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{16, 0x02, 0x03, 0x00, 0x00}}, }, } @@ -83,7 +83,7 @@ func TestBrokerDiscoveryCache(t *testing.T) { discoveryServer, _ := buildMockDiscoveryServer(port) broker := &pb.Announcement{ServiceName: "broker", Token: "broker", NetAddress: "localhost1:1881", - Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{}}}, + Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00}}}, } d := &brokerDiscovery{ diff --git a/core/discovery/discovery_integration_test.go b/core/discovery/discovery_integration_test.go index 988fd9e84..28ea63016 100644 --- a/core/discovery/discovery_integration_test.go +++ b/core/discovery/discovery_integration_test.go @@ -19,10 +19,10 @@ func TestIntegrationBrokerDiscovery(t *testing.T) { discoveryServer.services = map[string]map[string]*pb.Announcement{ "broker": map[string]*pb.Announcement{ "broker1": &pb.Announcement{Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x01}}, + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, }}, "broker2": &pb.Announcement{Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x02}}, + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x02, 0x00, 0x00, 0x00}}, }}, }, "other": map[string]*pb.Announcement{ From 4f558e5da1d4daf0b06e23a6df62da16ba73b47b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 13 Jun 2016 17:32:38 +0200 Subject: [PATCH 1508/2266] Always use UTC time --- core/networkserver/device/device.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 8e3e37e76..385478022 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -79,7 +79,7 @@ func (device *Device) formatProperty(property string) (formatted string, err err case "f_cnt_down": formatted = storage.FormatUint32(device.FCntDown) case "last_seen": - formatted = device.LastSeen.Format(time.RFC3339Nano) + formatted = device.LastSeen.UTC().Format(time.RFC3339Nano) case "options": // TODO case "utilization": From 07c97f0dda5285f42d821d61c335fa834e60ca4f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 15 Jun 2016 09:49:40 +0200 Subject: [PATCH 1509/2266] Move SetPrefix to DevAddr --- core/networkserver/networkserver.go | 14 ++------------ core/types/dev_addr.go | 17 +++++++++++------ core/types/dev_addr_test.go | 6 ++++++ 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 41398870c..39cf81e48 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -131,20 +131,10 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat } lorawanMeta := activation.ActivationMetadata.GetLorawan() - // Generate random DevAddr + // Generate random DevAddr with prefix var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) - // Fill the prefixSize first bits with the prefix - k := uint(n.prefixLength) - for i := 0; i < 4; i++ { - if k >= 8 { - devAddr[i] = n.prefix[i] & 0xff - k -= 8 - continue - } - devAddr[i] = n.prefix[i] & ^byte(0xff>>k) - k = 0 - } + devAddr.SetPrefix(types.DevAddr(n.prefix), n.prefixLength) // Set the DevAddr in the Activation Metadata lorawanMeta.DevAddr = &devAddr diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 36cce701e..389b0713f 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -95,15 +95,20 @@ func (addr DevAddr) IsEmpty() bool { // Mask returns a copy of the DevAddr with only the first "bits" bits func (addr DevAddr) Mask(bits int) (masked DevAddr) { - n := uint(bits) + return empty.SetPrefix(addr, bits) +} + +// SetPrefix sets a prefix of given length +func (addr DevAddr) SetPrefix(prefix DevAddr, length int) (prefixed DevAddr) { + k := uint(length) for i := 0; i < 4; i++ { - if n >= 8 { - masked[i] = addr[i] & 0xff - n -= 8 + if k >= 8 { + prefixed[i] = prefix[i] & 0xff + k -= 8 continue } - masked[i] = addr[i] & ^byte(0xff>>n) - n = 0 + prefixed[i] = (prefix[i] & ^byte(0xff>>k)) | (addr[i] & byte(0xff>>k)) + k = 0 } return } diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index f17efdc90..406948e82 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -77,6 +77,12 @@ func TestDevAddrMask(t *testing.T) { a.So(d1.Mask(8), ShouldEqual, DevAddr{255, 0, 0, 0}) } +func TestDevAddrSetPrefix(t *testing.T) { + a := New(t) + d1 := DevAddr{255, 255, 255, 255} + a.So(d1.SetPrefix(DevAddr{1, 2, 3, 4}, 7), ShouldEqual, DevAddr{1, 255, 255, 255}) +} + func TestDevAddrHasPrefix(t *testing.T) { a := New(t) a.So(DevAddr{1, 2, 3, 4}.HasPrefix(0, []byte{}), ShouldBeTrue) From dd4650859a3a3991d7f2cf2da30111b4d233e1a5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 15 Jun 2016 17:41:20 +0200 Subject: [PATCH 1510/2266] Change DevAddr methods --- core/discovery/broker_discovery.go | 4 +++- core/networkserver/networkserver.go | 2 +- core/types/dev_addr.go | 10 ++++------ core/types/dev_addr_test.go | 22 +++++++++++++--------- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 8e80e954f..1405c3ed2 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -76,7 +76,9 @@ func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, e matches := []*pb.Announcement{} for _, service := range d.cache { for _, meta := range service.Metadata { - if meta.Key == pb.Metadata_PREFIX && len(meta.Value) == 5 && devAddr.HasPrefix(int(meta.Value[0]), meta.Value[1:]) { + var prefix types.DevAddr + copy(prefix[:], meta.Value[1:]) + if meta.Key == pb.Metadata_PREFIX && len(meta.Value) == 5 && devAddr.HasPrefix(prefix, int(meta.Value[0])) { matches = append(matches, service) break } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 39cf81e48..2123c0ddb 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -134,7 +134,7 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat // Generate random DevAddr with prefix var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) - devAddr.SetPrefix(types.DevAddr(n.prefix), n.prefixLength) + devAddr = devAddr.WithPrefix(types.DevAddr(n.prefix), n.prefixLength) // Set the DevAddr in the Activation Metadata lorawanMeta.DevAddr = &devAddr diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 389b0713f..fa5515483 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -95,11 +95,11 @@ func (addr DevAddr) IsEmpty() bool { // Mask returns a copy of the DevAddr with only the first "bits" bits func (addr DevAddr) Mask(bits int) (masked DevAddr) { - return empty.SetPrefix(addr, bits) + return empty.WithPrefix(addr, bits) } -// SetPrefix sets a prefix of given length -func (addr DevAddr) SetPrefix(prefix DevAddr, length int) (prefixed DevAddr) { +// WithPrefix returns the DevAddr, but with the first length bits replaced by the Prefix +func (addr DevAddr) WithPrefix(prefix DevAddr, length int) (prefixed DevAddr) { k := uint(length) for i := 0; i < 4; i++ { if k >= 8 { @@ -114,9 +114,7 @@ func (addr DevAddr) SetPrefix(prefix DevAddr, length int) (prefixed DevAddr) { } // HasPrefix returns true if the DevAddr has a prefix of given length -func (addr DevAddr) HasPrefix(length int, prefixBytes []byte) bool { - var prefix DevAddr - copy(prefix[:], prefixBytes) +func (addr DevAddr) HasPrefix(prefix DevAddr, length int) bool { return addr.Mask(length) == prefix.Mask(length) } diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index 406948e82..0a7012789 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -77,20 +77,24 @@ func TestDevAddrMask(t *testing.T) { a.So(d1.Mask(8), ShouldEqual, DevAddr{255, 0, 0, 0}) } -func TestDevAddrSetPrefix(t *testing.T) { +func TestDevAddrWithPrefix(t *testing.T) { a := New(t) - d1 := DevAddr{255, 255, 255, 255} - a.So(d1.SetPrefix(DevAddr{1, 2, 3, 4}, 7), ShouldEqual, DevAddr{1, 255, 255, 255}) + addr := DevAddr{0xAA, 0xAA, 0xAA, 0xAA} + prefix := DevAddr{0x55, 0x55, 0x55, 0x55} + a.So(addr.WithPrefix(prefix, 4), ShouldEqual, DevAddr{0x5A, 0xAA, 0xAA, 0xAA}) + a.So(addr.WithPrefix(prefix, 8), ShouldEqual, DevAddr{0x55, 0xAA, 0xAA, 0xAA}) + a.So(addr.WithPrefix(prefix, 12), ShouldEqual, DevAddr{0x55, 0x5A, 0xAA, 0xAA}) + a.So(addr.WithPrefix(prefix, 16), ShouldEqual, DevAddr{0x55, 0x55, 0xAA, 0xAA}) } func TestDevAddrHasPrefix(t *testing.T) { a := New(t) - a.So(DevAddr{1, 2, 3, 4}.HasPrefix(0, []byte{}), ShouldBeTrue) - a.So(DevAddr{1, 2, 3, 4}.HasPrefix(32, []byte{1, 2, 3, 4}), ShouldBeTrue) - a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{1, 2, 3, 4}), ShouldBeTrue) - a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{2, 2, 3, 4}), ShouldBeFalse) - a.So(DevAddr{1, 2, 3, 4}.HasPrefix(31, []byte{1, 1, 3, 4}), ShouldBeFalse) - a.So(DevAddr{1, 2, 3, 4}.HasPrefix(15, []byte{1, 1}), ShouldBeFalse) + addr := DevAddr{1, 2, 3, 4} + a.So(addr.HasPrefix(DevAddr{0, 0, 0, 0}, 0), ShouldBeTrue) + a.So(addr.HasPrefix(DevAddr{1, 2, 3, 0}, 24), ShouldBeTrue) + a.So(addr.HasPrefix(DevAddr{2, 2, 3, 4}, 31), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddr{1, 1, 3, 4}, 31), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddr{1, 1, 1, 1}, 15), ShouldBeFalse) } func TestParseDevAddrPrefix(t *testing.T) { From 57eb1d856161951ceadfadf4d951857f89d2c76e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 15 Jun 2016 18:00:24 +0200 Subject: [PATCH 1511/2266] Do not create downlink when unavailable --- core/networkserver/networkserver.go | 2 +- core/networkserver/networkserver_test.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 2123c0ddb..df4f15e9a 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -218,7 +218,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag // Prepare Downlink if message.ResponseTemplate == nil { - message.ResponseTemplate = &pb_broker.DownlinkMessage{} + return message, nil } message.ResponseTemplate.AppEui = message.AppEui message.ResponseTemplate.DevEui = message.DevEui diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index e5eb9dd73..192dcd1c6 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -263,9 +263,10 @@ func TestHandleUplink(t *testing.T) { // Valid Uplink message = &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: bytes, + AppEui: &appEUI, + DevEui: &devEUI, + Payload: bytes, + ResponseTemplate: &pb_broker.DownlinkMessage{}, } res, err := ns.HandleUplink(message) a.So(err, ShouldBeNil) From 3015a7215736537f8249b2979c8c9e81ca38419a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 15 Jun 2016 18:13:56 +0200 Subject: [PATCH 1512/2266] Remove Test Device from NS --- core/networkserver/networkserver.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index df4f15e9a..c118b083c 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -66,13 +66,6 @@ func (n *networkServer) Init(c *core.Component) error { if err != nil { return err } - // Set fake device - n.devices.Set(&device.Device{ - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - DevAddr: types.DevAddr{1, 2, 3, 4}, - NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, - }) return nil } From 4ef80e8665e779b12d682b2c8aa1bce421890cba Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 16 Jun 2016 20:15:53 +0200 Subject: [PATCH 1513/2266] Implementation of Handler --- core/handler/activation.go | 144 ++++++++++++ core/handler/activation_test.go | 157 +++++++++++++ core/handler/application/application.go | 104 +++++++++ core/handler/application/application_test.go | 41 ++++ core/handler/application/store.go | 111 ++++++++++ core/handler/application/store_test.go | 57 +++++ core/handler/convert_fields.go | 136 ++++++++++++ core/handler/convert_fields_test.go | 221 +++++++++++++++++++ core/handler/convert_lorawan.go | 114 ++++++++++ core/handler/convert_lorawan_test.go | 90 ++++++++ core/handler/convert_metadata.go | 52 +++++ core/handler/convert_metadata_test.go | 64 ++++++ core/handler/device/device.go | 185 ++++++++++++++++ core/handler/device/device_test.go | 73 ++++++ core/handler/device/store.go | 122 ++++++++++ core/handler/device/store_test.go | 71 ++++++ core/handler/downlink.go | 71 ++++++ core/handler/downlink_test.go | 91 ++++++++ core/handler/handler.go | 151 +++++++++++++ core/handler/handler_test.go | 3 + core/handler/mqtt.go | 60 +++++ core/handler/mqtt_test.go | 68 ++++++ core/handler/server.go | 54 +++++ core/handler/server_test.go | 3 + core/handler/types.go | 17 ++ core/handler/uplink.go | 100 +++++++++ core/handler/uplink_test.go | 131 +++++++++++ 27 files changed, 2491 insertions(+) create mode 100644 core/handler/activation.go create mode 100644 core/handler/activation_test.go create mode 100644 core/handler/application/application.go create mode 100644 core/handler/application/application_test.go create mode 100644 core/handler/application/store.go create mode 100644 core/handler/application/store_test.go create mode 100644 core/handler/convert_fields.go create mode 100644 core/handler/convert_fields_test.go create mode 100644 core/handler/convert_lorawan.go create mode 100644 core/handler/convert_lorawan_test.go create mode 100644 core/handler/convert_metadata.go create mode 100644 core/handler/convert_metadata_test.go create mode 100644 core/handler/device/device.go create mode 100644 core/handler/device/device_test.go create mode 100644 core/handler/device/store.go create mode 100644 core/handler/device/store_test.go create mode 100644 core/handler/downlink.go create mode 100644 core/handler/downlink_test.go create mode 100644 core/handler/handler.go create mode 100644 core/handler/handler_test.go create mode 100644 core/handler/mqtt.go create mode 100644 core/handler/mqtt_test.go create mode 100644 core/handler/server.go create mode 100644 core/handler/server_test.go create mode 100644 core/handler/types.go create mode 100644 core/handler/uplink.go create mode 100644 core/handler/uplink_test.go diff --git a/core/handler/activation.go b/core/handler/activation.go new file mode 100644 index 000000000..be5995ffe --- /dev/null +++ b/core/handler/activation.go @@ -0,0 +1,144 @@ +package handler + +import ( + "errors" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/otaa" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/brocaar/lorawan" +) + +func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + // Find Device + dev, err := h.devices.Get(*activation.AppEui, *activation.DevEui) + if err != nil { + // Find application + app, err := h.applications.Get(*activation.AppEui) + if err != nil || app.DefaultAppKey.IsEmpty() { + return nil, err + } + // Use Default AppKey + dev = &device.Device{ + AppEUI: *activation.AppEui, + DevEUI: *activation.DevEui, + AppKey: app.DefaultAppKey, + } + } + + // Check for LoRaWAN + if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { + return nil, errors.New("ttn/handler: Can not activate non-LoRaWAN device") + } + + // Unmarshal LoRaWAN + var reqPHY lorawan.PHYPayload + if err := reqPHY.UnmarshalBinary(activation.Payload); err != nil { + return nil, err + } + reqMAC, ok := reqPHY.MACPayload.(*lorawan.JoinRequestPayload) + if !ok { + return nil, errors.New("MACPayload must be a *JoinRequestPayload") + } + + // Prepare Device Activation Response + var resPHY lorawan.PHYPayload + if err := resPHY.UnmarshalBinary(activation.ResponseTemplate.Payload); err != nil { + return nil, err + } + resMAC, ok := resPHY.MACPayload.(*lorawan.DataPayload) + if !ok { + return nil, errors.New("MACPayload must be a *DataPayload") + } + joinAccept := &lorawan.JoinAcceptPayload{} + if err := joinAccept.UnmarshalBinary(false, resMAC.Bytes); err != nil { + return nil, err + } + resPHY.MACPayload = joinAccept + + // Validate MIC + if ok, err := reqPHY.ValidateMIC(lorawan.AES128Key(dev.AppKey)); err != nil || !ok { + return nil, errors.New("ttn/handler: Invalid MIC") + } + + // Validate DevNonce + var alreadyUsed bool + for _, usedNonce := range dev.UsedDevNonces { + if usedNonce == device.DevNonce(reqMAC.DevNonce) { + alreadyUsed = true + break + } + } + if alreadyUsed { + return nil, errors.New("ttn/handler: DevNonce already used") + } + + // Publish Activation + h.mqttActivation <- &mqtt.Activation{ + AppEUI: *activation.AppEui, + DevEUI: *activation.DevEui, + } + + // Generate random AppNonce + var appNonce device.AppNonce + for { + // NOTE: As DevNonces are only 2 bytes, we will start rejecting those before we run out of AppNonces. + // It might just take some time to get one we didn't use yet... + alreadyUsed = false + copy(appNonce[:], random.Bytes(3)) + for _, usedNonce := range dev.UsedAppNonces { + if usedNonce == appNonce { + alreadyUsed = true + break + } + } + if !alreadyUsed { + break + } + } + joinAccept.AppNonce = appNonce + + // Calculate session keys + appSKey, nwkSKey, err := otaa.CalculateSessionKeys(dev.AppKey, joinAccept.AppNonce, joinAccept.NetID, reqMAC.DevNonce) + if err != nil { + return nil, err + } + + // Update Device + dev.DevAddr = types.DevAddr(joinAccept.DevAddr) + dev.AppSKey = appSKey + dev.NwkSKey = nwkSKey + dev.UsedAppNonces = append(dev.UsedAppNonces, appNonce) + dev.UsedDevNonces = append(dev.UsedDevNonces, reqMAC.DevNonce) + err = h.devices.Set(dev, "dev_addr", "app_key", "app_s_key", "nwk_s_key", "used_app_nonces", "used_dev_nonces") // app_key is only needed when the default app_key is used to activate the device + if err != nil { + return nil, err + } + + if err := resPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { + return nil, err + } + if err := resPHY.EncryptJoinAcceptPayload(lorawan.AES128Key(dev.AppKey)); err != nil { + return nil, err + } + + resBytes, err := resPHY.MarshalBinary() + if err != nil { + return nil, err + } + + metadata := activation.ActivationMetadata + metadata.GetLorawan().NwkSKey = &dev.NwkSKey + metadata.GetLorawan().DevAddr = &dev.DevAddr + res := &pb.DeviceActivationResponse{ + Payload: resBytes, + DownlinkOption: activation.ResponseTemplate.DownlinkOption, + ActivationMetadata: metadata, + } + + return res, nil +} diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go new file mode 100644 index 000000000..73a3913b2 --- /dev/null +++ b/core/handler/activation_test.go @@ -0,0 +1,157 @@ +package handler + +import ( + "sync" + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func doTestHandleActivation(h *handler, appEUI types.AppEUI, devEUI types.DevEUI, devNonce [2]byte, appKey types.AppKey) (*pb.DeviceActivationResponse, error) { + devAddr := types.DevAddr{1, 2, 3, 4} + + requestPHY := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinRequest, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinRequestPayload{ + AppEUI: lorawan.EUI64(appEUI), + DevEUI: lorawan.EUI64(devEUI), + DevNonce: devNonce, + }, + } + requestPHY.SetMIC(lorawan.AES128Key(appKey)) + requestBytes, _ := requestPHY.MarshalBinary() + + responsePHY := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinAccept, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinAcceptPayload{}, + } + templateBytes, _ := responsePHY.MarshalBinary() + return h.HandleActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + Payload: requestBytes, + AppEui: &appEUI, + DevEui: &devEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + DevAddr: &devAddr, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{ + Payload: templateBytes, + }, + }) +} + +func TestHandleActivation(t *testing.T) { + a := New(t) + + h := &handler{ + applications: application.NewApplicationStore(), + devices: device.NewDeviceStore(), + } + h.mqttActivation = make(chan *mqtt.Activation) + var wg sync.WaitGroup + + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + unknownDevEUI := types.DevEUI{8, 7, 6, 5, 4, 3, 2, 1} + + appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} + defaultAppKey := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8} + + h.applications.Set(&application.Application{ + AppEUI: appEUI, + DefaultAppKey: defaultAppKey, + }) + + h.devices.Set(&device.Device{ + AppEUI: appEUI, + DevEUI: devEUI, + AppKey: appKey, + }) + + // Unknown + res, err := doTestHandleActivation(h, + appEUI, + unknownDevEUI, + [2]byte{1, 2}, + appKey, + ) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + // Wrong AppKey + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{1, 2}, + [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + ) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + wg.Add(1) + go func() { + <-h.mqttActivation + wg.Done() + }() + + // Known + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{1, 2}, + appKey, + ) + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + + wg.Wait() + + // Same DevNonce used twice + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{1, 2}, + appKey, + ) + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeNil) + + wg.Add(1) + go func() { + <-h.mqttActivation + wg.Done() + }() + + // Other DevNonce + res, err = doTestHandleActivation(h, + appEUI, + devEUI, + [2]byte{2, 1}, + appKey, + ) + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + + wg.Wait() + + // TODO: Validate response + + // TODO: Check DB + +} diff --git a/core/handler/application/application.go b/core/handler/application/application.go new file mode 100644 index 000000000..2871054ca --- /dev/null +++ b/core/handler/application/application.go @@ -0,0 +1,104 @@ +package application + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// Application contains the state of an application +type Application struct { + AppEUI types.AppEUI + DefaultAppKey types.AppKey + // Decoder is a JavaScript function that accepts the payload as byte array and + // returns an object containing the decoded values + Decoder string + // Converter is a JavaScript function that accepts the data as decoded by + // Decoder and returns an object containing the converted values + Converter string + // Validator is a JavaScript function that validates the data is converted by + // Converter and returns a boolean value indicating the validity of the data + Validator string +} + +// ApplicationProperties contains all properties of a Application that can be stored in Redis. +var ApplicationProperties = []string{ + "app_eui", + "default_app_key", + "decoder", + "converter", + "validator", +} + +// ToStringStringMap converts the given properties of an Application to a +// map[string]string for storage in Redis. +func (application *Application) ToStringStringMap(properties ...string) (map[string]string, error) { + output := make(map[string]string) + for _, p := range properties { + property, err := application.formatProperty(p) + if err != nil { + return output, err + } + if property != "" { + output[p] = property + } + } + return output, nil +} + +// FromStringStringMap imports known values from the input to an Application. +func (application *Application) FromStringStringMap(input map[string]string) error { + for k, v := range input { + application.parseProperty(k, v) + } + return nil +} + +func (application *Application) formatProperty(property string) (formatted string, err error) { + switch property { + case "app_eui": + formatted = application.AppEUI.String() + case "default_app_key": + formatted = application.DefaultAppKey.String() + case "decoder": + formatted = application.Decoder + case "converter": + formatted = application.Converter + case "validator": + formatted = application.Validator + default: + err = fmt.Errorf("Property %s does not exist in Application", property) + } + return +} + +func (application *Application) parseProperty(property string, value string) error { + if value == "" { + return nil + } + switch property { + case "app_eui": + val, err := types.ParseAppEUI(value) + if err != nil { + return err + } + if !val.IsEmpty() { + application.AppEUI = val + } + case "default_app_key": + val, err := types.ParseAppKey(value) + if err != nil { + return err + } + if !val.IsEmpty() { + application.DefaultAppKey = val + } + case "decoder": + application.Decoder = value + case "converter": + application.Converter = value + case "validator": + application.Validator = value + } + return nil +} diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go new file mode 100644 index 000000000..73c8cd240 --- /dev/null +++ b/core/handler/application/application_test.go @@ -0,0 +1,41 @@ +package application + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getTestApplication() (application *Application, dmap map[string]string) { + return &Application{ + AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + DefaultAppKey: types.AppKey{8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1}, + Decoder: `function (payload) { return { size: payload.length; } }`, + Converter: `function (data) { return data; }`, + Validator: `function (data) { return data.size % 2 == 0; }`, + }, map[string]string{ + "app_eui": "0807060504030201", + "default_app_key": "08070605040302010807060504030201", + "decoder": `function (payload) { return { size: payload.length; } }`, + "converter": `function (data) { return data; }`, + "validator": `function (data) { return data.size % 2 == 0; }`, + } +} + +func TestToStringMap(t *testing.T) { + a := New(t) + application, expected := getTestApplication() + dmap, err := application.ToStringStringMap(ApplicationProperties...) + a.So(err, ShouldBeNil) + a.So(dmap, ShouldResemble, expected) +} + +func TestFromStringMap(t *testing.T) { + a := New(t) + application := &Application{} + expected, dmap := getTestApplication() + err := application.FromStringStringMap(dmap) + a.So(err, ShouldBeNil) + a.So(application, ShouldResemble, expected) +} diff --git a/core/handler/application/store.go b/core/handler/application/store.go new file mode 100644 index 000000000..abc21a78d --- /dev/null +++ b/core/handler/application/store.go @@ -0,0 +1,111 @@ +package application + +import ( + "errors" + "fmt" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +var ( + // ErrNotFound is returned when a application was not found + ErrNotFound = errors.New("ttn/handler: Application not found") +) + +// Store is used to store application configurations +type Store interface { + // Get the full information about a application + Get(appEUI types.AppEUI) (*Application, error) + // Set the given fields of a application. If fields empty, it sets all fields. + Set(application *Application, fields ...string) error + // Delete a application + Delete(types.AppEUI) error +} + +// NewApplicationStore creates a new in-memory Application store +func NewApplicationStore() Store { + return &applicationStore{ + applications: make(map[types.AppEUI]*Application), + } +} + +// applicationStore is an in-memory Application store. It should only be used for testing +// purposes. Use the redisApplicationStore for actual deployments. +type applicationStore struct { + applications map[types.AppEUI]*Application +} + +func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { + if app, ok := s.applications[appEUI]; ok { + return app, nil + } + return nil, ErrNotFound +} + +func (s *applicationStore) Set(new *Application, fields ...string) error { + // NOTE: We don't care about fields for testing + s.applications[new.AppEUI] = new + return nil +} + +func (s *applicationStore) Delete(appEUI types.AppEUI) error { + delete(s.applications, appEUI) + return nil +} + +// NewRedisApplicationStore creates a new Redis-based status store +func NewRedisApplicationStore(client *redis.Client) Store { + return &redisApplicationStore{ + client: client, + } +} + +const redisApplicationPrefix = "handler:application" + +type redisApplicationStore struct { + client *redis.Client +} + +func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() + if err != nil { + return nil, err + } else if len(res) == 0 { + return nil, redis.Nil // This might be a bug in redis package + } + application := &Application{} + err = application.FromStringStringMap(res) + if err != nil { + return nil, err + } + return application, nil +} + +func (s *redisApplicationStore) Set(new *Application, fields ...string) error { + if len(fields) == 0 { + fields = ApplicationProperties + } + + key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppEUI) + dmap, err := new.ToStringStringMap(fields...) + if err != nil { + return err + } + err = s.client.HMSetMap(key, dmap).Err() + if err != nil { + return err + } + + return nil +} + +func (s *redisApplicationStore) Delete(appEUI types.AppEUI) error { + key := fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI) + err := s.client.Del(key).Err() + if err != nil { + return err + } + return nil +} diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go new file mode 100644 index 000000000..71ac1fbd8 --- /dev/null +++ b/core/handler/application/store_test.go @@ -0,0 +1,57 @@ +package application + +import ( + "testing" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) +} + +func TestApplicationStore(t *testing.T) { + a := New(t) + + stores := map[string]Store{ + "local": NewApplicationStore(), + "redis": NewRedisApplicationStore(getRedisClient()), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Get non-existing + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&Application{ + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + a.So(err, ShouldBeNil) + + // Get existing + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + // Delete + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + } +} diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go new file mode 100644 index 000000000..d36bcd010 --- /dev/null +++ b/core/handler/convert_fields.go @@ -0,0 +1,136 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "errors" + "fmt" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" + "github.com/robertkrimen/otto" +) + +// ConvertFields converts the payload to fields using payload functions +func (h *handler) ConvertFields(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { + // Find Application + app, err := h.applications.Get(appUp.AppEUI) + if err != nil { + return nil // Do not process if application not found + } + + functions := &Functions{ + Decoder: app.Decoder, + Converter: app.Converter, + Validator: app.Validator, + } + + fields, valid, err := functions.Process(appUp.Payload) + if err != nil { + return nil // Do not set fields if processing failed + } + + if !valid { + return errors.New("ttn/handler: The processed payload is not valid") + } + + appUp.Fields = fields + + return nil +} + +// Functions decodes, converts and validates payload using JavaScript functions +type Functions struct { + // Decoder is a JavaScript function that accepts the payload as byte array and + // returns an object containing the decoded values + Decoder string + // Converter is a JavaScript function that accepts the data as decoded by + // Decoder and returns an object containing the converted values + Converter string + // Validator is a JavaScript function that validates the data is converted by + // Converter and returns a boolean value indicating the validity of the data + Validator string +} + +// Decode decodes the payload using the Decoder function into a map +func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { + if f.Decoder == "" { + return nil, errors.New("Decoder function not set") + } + + vm := otto.New() + vm.Set("payload", payload) + value, err := vm.Run(fmt.Sprintf("(%s)(payload)", f.Decoder)) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Decoder does not return an object") + } + + v, _ := value.Export() + return v.(map[string]interface{}), nil +} + +// Convert converts the values in the specified map to a another map using the +// Converter function. If the Converter function is not set, this function +// returns the data as-is +func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{}, error) { + if f.Converter == "" { + return data, nil + } + + vm := otto.New() + vm.Set("data", data) + value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Converter)) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Converter does not return an object") + } + + v, _ := value.Export() + return v.(map[string]interface{}), nil +} + +// Validate validates the values in the specified map using the Validator +// function. If the Validator function is not set, this function returns true +func (f *Functions) Validate(data map[string]interface{}) (bool, error) { + if f.Validator == "" { + return true, nil + } + + vm := otto.New() + vm.Set("data", data) + value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Validator)) + if err != nil { + return false, err + } + + if !value.IsBoolean() { + return false, errors.New("Validator does not return a boolean") + } + + return value.ToBoolean() +} + +// Process decodes the specified payload, converts it and test the validity +func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error) { + decoded, err := f.Decode(payload) + if err != nil { + return nil, false, err + } + + converted, err := f.Convert(decoded) + if err != nil { + return nil, false, err + } + + valid, err := f.Validate(converted) + return converted, valid, err +} diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go new file mode 100644 index 000000000..8a76fe389 --- /dev/null +++ b/core/handler/convert_fields_test.go @@ -0,0 +1,221 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/mqtt" + + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { + appEUI, _ := types.ParseAppEUI("0102030405060708") + ttnUp := &pb_broker.DeduplicatedUplinkMessage{} + appUp := &mqtt.UplinkMessage{ + FPort: 1, + AppEUI: appEUI, + Payload: []byte{0x08, 0x70}, + } + return ttnUp, appUp +} + +func TestConvertFields(t *testing.T) { + a := New(t) + appEUI, _ := types.ParseAppEUI("0102030405060708") + + h := &handler{ + applications: application.NewApplicationStore(), + } + + // No functions + ttnUp, appUp := buildConversionUplink() + err := h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Fields, ShouldBeEmpty) + + // Normal flow + h.applications.Set(&application.Application{ + AppEUI: appEUI, + Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, + }) + ttnUp, appUp = buildConversionUplink() + err = h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + a.So(err, ShouldBeNil) + + a.So(appUp.Fields, ShouldResemble, map[string]interface{}{ + "temperature": 21.6, + }) + + // Invalidate data + h.applications.Set(&application.Application{ + AppEUI: appEUI, + Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, + Validator: `function(data) { return false; }`, + }) + ttnUp, appUp = buildConversionUplink() + err = h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + a.So(err, ShouldNotBeNil) + a.So(appUp.Fields, ShouldBeEmpty) + + // Function error + h.applications.Set(&application.Application{ + AppEUI: appEUI, + Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, + Converter: `function(data) { throw "expected"; }`, + }) + ttnUp, appUp = buildConversionUplink() + err = h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Fields, ShouldBeEmpty) +} + +func TestDecode(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `function(payload) { + return { + value: (payload[0] << 8) | payload[1] + }; +}`, + } + payload := []byte{0x48, 0x65} + + m, err := functions.Decode(payload) + a.So(err, ShouldBeNil) + + size, ok := m["value"] + a.So(ok, ShouldBeTrue) + a.So(size, ShouldEqual, 18533) +} + +func TestConvert(t *testing.T) { + a := New(t) + + withFunction := &Functions{ + Converter: `function(data) { + return { + celcius: data.temperature * 2 + }; +}`, + } + data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) + a.So(err, ShouldBeNil) + a.So(data["celcius"], ShouldEqual, 22) + + withoutFunction := &Functions{} + data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) + a.So(err, ShouldBeNil) + a.So(data["temperature"], ShouldEqual, 11) +} + +func TestValidate(t *testing.T) { + a := New(t) + + withFunction := &Functions{ + Validator: `function(data) { + return data.temperature < 20; + }`, + } + valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) + valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) + + withoutFunction := &Functions{} + valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeTrue) +} + +func TestProcess(t *testing.T) { + a := New(t) + + functions := &Functions{ + Decoder: `function(payload) { + return { + temperature: payload[0], + humidity: payload[1] + } +}`, + Converter: `function(data) { + data.temperature /= 2; + return data; +}`, + Validator: `function(data) { + return data.humidity >= 0 && data.humidity <= 100; +}`, + } + + data, valid, err := functions.Process([]byte{40, 110}) + a.So(err, ShouldBeNil) + a.So(valid, ShouldBeFalse) + a.So(data["temperature"], ShouldEqual, 20) + a.So(data["humidity"], ShouldEqual, 110) +} + +func TestProcessInvalidFunction(t *testing.T) { + a := New(t) + + // Empty Function + functions := &Functions{ + Decoder: ``, + } + _, _, err := functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &Functions{ + Decoder: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &Functions{ + Decoder: `function(payload) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Converter: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Return + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Converter: `function(data) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Validator: `this is not valid JavaScript`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Return + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Validator: `function(data) { return "Hello" }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) +} diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go new file mode 100644 index 000000000..24589ff15 --- /dev/null +++ b/core/handler/convert_lorawan.go @@ -0,0 +1,114 @@ +package handler + +import ( + "errors" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" + + "github.com/brocaar/lorawan" +) + +func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { + // Find Device + dev, err := h.devices.Get(*ttnUp.AppEui, *ttnUp.DevEui) + if err != nil { + return err + } + + // Check for LoRaWAN + if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil { + return errors.New("ttn/handler: Could not handle uplink for non-LoRaWAN device") + } + + // LoRaWAN: Unmarshal Uplink + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(ttnUp.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.New("Uplink message does not contain a MAC payload.") + } + macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt + appUp.FCnt = macPayload.FHDR.FCnt + + ctx = ctx.WithField("FCnt", appUp.FCnt) + + // LoRaWAN: Decrypt + if macPayload.FPort != nil && *macPayload.FPort != 0 && len(macPayload.FRMPayload) == 1 { + appUp.FPort = *macPayload.FPort + ctx = ctx.WithField("FCnt", appUp.FPort) + if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil { + return errors.New("ttn/handler: Could not decrypt payload") + } + if len(macPayload.FRMPayload) == 1 { + payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) + if !ok { + return errors.New("FRMPayload must be of type *lorawan.DataPayload") + } + appUp.Payload = payload.Bytes + } + } else { + return errors.New("ttn/handler: Could not get frame payload") + } + + return nil +} + +func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { + // Find Device + dev, err := h.devices.Get(appDown.AppEUI, appDown.DevEUI) + if err != nil { + return err + } + + // LoRaWAN: Unmarshal Downlink + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(ttnDown.Payload) + if err != nil { + return err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.New("Downlink message does not contain a MAC payload.") + } + if ttnDown.DownlinkOption != nil { + macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt + } + + // Abort when downlink not needed + if len(appDown.Payload) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 { + return ErrNotNeeded + } + + // Set FPort + if appDown.FPort != 0 { + macPayload.FPort = &appDown.FPort + } + + // Set Payload + if len(appDown.Payload) > 0 { + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.Payload}} + } else { + macPayload.FRMPayload = []lorawan.Payload{} + } + + // Encrypt + err = phyPayload.EncryptFRMPayload(lorawan.AES128Key(dev.AppSKey)) + if err != nil { + return err + } + + // Marshal + phyPayloadBytes, err := phyPayload.MarshalBinary() + if err != nil { + return err + } + + ttnDown.Payload = phyPayloadBytes + + return nil +} diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go new file mode 100644 index 000000000..426e89d2f --- /dev/null +++ b/core/handler/convert_lorawan_test.go @@ -0,0 +1,90 @@ +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + ttnUp := &pb_broker.DeduplicatedUplinkMessage{ + DevEui: &devEUI, + AppEui: &appEUI, + Payload: payload, + ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ + Lorawan: &pb_lorawan.Metadata{ + FCnt: 1, + }, + }}, + } + appUp := &mqtt.UplinkMessage{} + return ttnUp, appUp +} + +func TestConvertFromLoRaWAN(t *testing.T) { + a := New(t) + h := &handler{ + devices: device.NewDeviceStore(), + Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, + } + h.devices.Set(&device.Device{ + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + }) + ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x46, 0x55, 0x23, 0xf4, 0xf8, 0x45}) + err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Payload, ShouldResemble, []byte{0xaa, 0xbc}) + a.So(appUp.FCnt, ShouldEqual, 1) +} + +func buildLorawanDownlink(payload []byte) (*mqtt.DownlinkMessage, *pb_broker.DownlinkMessage) { + appDown := &mqtt.DownlinkMessage{ + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + Payload: []byte{0xaa, 0xbc}, + } + ttnDown := &pb_broker.DownlinkMessage{ + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + DownlinkOption: &pb_broker.DownlinkOption{ + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{ + Lorawan: &pb_lorawan.TxConfiguration{ + FCnt: 1, + }, + }}, + }, + } + return appDown, ttnDown +} + +func TestConvertToLoRaWAN(t *testing.T) { + a := New(t) + h := &handler{ + devices: device.NewDeviceStore(), + Component: &core.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, + } + h.devices.Set(&device.Device{ + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + }) + appDown, ttnDown := buildLorawanDownlink([]byte{0xaa, 0xbc}) + err := h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0xa1, 0x33, 0x00, 0x00, 0x00, 0x00}) + + appDown, ttnDown = buildLorawanDownlink([]byte{0xaa, 0xbc}) + appDown.FPort = 8 + err = h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x08, 0xa1, 0x33, 0x00, 0x00, 0x00, 0x00}) +} diff --git a/core/handler/convert_metadata.go b/core/handler/convert_metadata.go new file mode 100644 index 000000000..e66b9a76e --- /dev/null +++ b/core/handler/convert_metadata.go @@ -0,0 +1,52 @@ +package handler + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +// ConvertMetadata converts the protobuf matadata to application metadata +func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { + + ctx = ctx.WithField("NumGateways", len(ttnUp.GatewayMetadata)) + + // Transform Metadata + metadata := make([]*mqtt.Metadata, 0, len(ttnUp.GatewayMetadata)) + for _, in := range ttnUp.GatewayMetadata { + out := &mqtt.Metadata{} + + out.ServerTime = time.Unix(0, 0).Add(time.Duration(ttnUp.ServerTime)).UTC().Format(time.RFC3339Nano) + + if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan != nil { + out.DataRate = lorawan.DataRate + out.CodingRate = lorawan.CodingRate + out.Modulation = lorawan.Modulation.String() + } + + if in.GatewayEui != nil { + out.GatewayEUI = *in.GatewayEui + } + out.Timestamp = in.Timestamp + out.Time = time.Unix(0, 0).Add(time.Duration(in.Time)).UTC().Format(time.RFC3339Nano) + out.Channel = in.Channel + out.RFChain = in.RfChain + out.Frequency = float32(float64(in.Frequency) / 1000000) + out.Rssi = in.Rssi + out.Lsnr = in.Snr + + if gps := in.GetGps(); gps != nil { + out.Altitude = gps.Altitude + out.Longitude = gps.Longitude + out.Latitude = gps.Latitude + } + + metadata = append(metadata, out) + } + + appUp.Metadata = metadata + + return nil +} diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go new file mode 100644 index 000000000..59e914b4e --- /dev/null +++ b/core/handler/convert_metadata_test.go @@ -0,0 +1,64 @@ +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestConvertMetadata(t *testing.T) { + a := New(t) + h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestConvertMetadata")}, + } + + ttnUp := &pb_broker.DeduplicatedUplinkMessage{} + appUp := &mqtt.UplinkMessage{} + + err := h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + + gtwEUI := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + ttnUp.GatewayMetadata = []*pb_gateway.RxMetadata{ + &pb_gateway.RxMetadata{ + GatewayEui: >wEUI, + }, + &pb_gateway.RxMetadata{ + GatewayEui: >wEUI, + }, + } + + err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Metadata, ShouldHaveLength, 2) + + ttnUp.ProtocolMetadata = &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ + Lorawan: &pb_lorawan.Metadata{ + DataRate: "SF7BW125", + }, + }} + + err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Metadata[0].DataRate, ShouldEqual, "SF7BW125") + a.So(appUp.Metadata[1].DataRate, ShouldEqual, "SF7BW125") + + ttnUp.GatewayMetadata[0].Time = 1465831736000000000 + ttnUp.GatewayMetadata[0].Gps = &pb_gateway.GPSMetadata{ + Latitude: 42, + } + + err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) + a.So(err, ShouldBeNil) + a.So(appUp.Metadata[0].Latitude, ShouldEqual, 42) + a.So(appUp.Metadata[0].Time, ShouldEqual, "2016-06-13T15:28:56Z") + +} diff --git a/core/handler/device/device.go b/core/handler/device/device.go new file mode 100644 index 000000000..50df11d9c --- /dev/null +++ b/core/handler/device/device.go @@ -0,0 +1,185 @@ +package device + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" +) + +// Options for the specified device +type Options struct { + DisableFCntCheck bool // Disable Frame counter check (insecure) + Uses32BitFCnt bool // Use 32-bit Frame counters +} + +type DevNonce [2]byte +type AppNonce [3]byte + +// Device contains the state of a device +type Device struct { + DevEUI types.DevEUI + AppEUI types.AppEUI + DevAddr types.DevAddr + AppKey types.AppKey + UsedDevNonces []DevNonce + UsedAppNonces []AppNonce + NwkSKey types.NwkSKey + AppSKey types.AppSKey + NextDownlink *mqtt.DownlinkMessage +} + +// DeviceProperties contains all properties of a Device that can be stored in Redis. +var DeviceProperties = []string{ + "dev_eui", + "app_eui", + "dev_addr", + "app_key", + "nwk_s_key", + "app_s_key", + "used_dev_nonces", + "used_app_nonces", + "next_downlink", +} + +// ToStringStringMap converts the given properties of Device to a +// map[string]string for storage in Redis. +func (device *Device) ToStringStringMap(properties ...string) (map[string]string, error) { + output := make(map[string]string) + for _, p := range properties { + property, err := device.formatProperty(p) + if err != nil { + return output, err + } + if property != "" { + output[p] = property + } + } + return output, nil +} + +// FromStringStringMap imports known values from the input to a Device. +func (device *Device) FromStringStringMap(input map[string]string) error { + for k, v := range input { + device.parseProperty(k, v) + } + return nil +} + +func (device *Device) formatProperty(property string) (formatted string, err error) { + switch property { + case "dev_eui": + formatted = device.DevEUI.String() + case "app_eui": + formatted = device.AppEUI.String() + case "dev_addr": + formatted = device.DevAddr.String() + case "app_key": + formatted = device.AppKey.String() + case "nwk_s_key": + formatted = device.NwkSKey.String() + case "app_s_key": + formatted = device.AppSKey.String() + case "used_dev_nonces": + nonces := make([]string, 0, len(device.UsedDevNonces)) + for _, nonce := range device.UsedDevNonces { + nonces = append(nonces, fmt.Sprintf("%X", nonce)) + } + formatted = strings.Join(nonces, ",") + case "used_app_nonces": + nonces := make([]string, 0, len(device.UsedAppNonces)) + for _, nonce := range device.UsedAppNonces { + nonces = append(nonces, fmt.Sprintf("%X", nonce)) + } + formatted = strings.Join(nonces, ",") + case "next_downlink": + var jsonBytes []byte + jsonBytes, err = json.Marshal(device.NextDownlink) + formatted = string(jsonBytes) + default: + err = fmt.Errorf("Property %s does not exist in Device", property) + } + return +} + +func (device *Device) parseProperty(property string, value string) error { + if value == "" { + return nil + } + switch property { + case "dev_eui": + val, err := types.ParseDevEUI(value) + if err != nil { + return err + } + device.DevEUI = val + case "app_eui": + val, err := types.ParseAppEUI(value) + if err != nil { + return err + } + device.AppEUI = val + case "dev_addr": + val, err := types.ParseDevAddr(value) + if err != nil { + return err + } + device.DevAddr = val + case "app_key": + val, err := types.ParseAppKey(value) + if err != nil { + return err + } + device.AppKey = val + case "nwk_s_key": + val, err := types.ParseNwkSKey(value) + if err != nil { + return err + } + device.NwkSKey = val + case "app_s_key": + val, err := types.ParseAppSKey(value) + if err != nil { + return err + } + device.AppSKey = val + case "used_dev_nonces": + nonceStrs := strings.Split(value, ",") + nonces := make([]DevNonce, 0, len(nonceStrs)) + for _, nonceStr := range nonceStrs { + var nonce DevNonce + nonceBytes, err := types.ParseHEX(nonceStr, 2) + if err != nil { + return err + } + copy(nonce[:], nonceBytes) + nonces = append(nonces, nonce) + } + device.UsedDevNonces = nonces + case "used_app_nonces": + nonceStrs := strings.Split(value, ",") + nonces := make([]AppNonce, 0, len(nonceStrs)) + for _, nonceStr := range nonceStrs { + var nonce AppNonce + nonceBytes, err := types.ParseHEX(nonceStr, 3) + if err != nil { + return err + } + copy(nonce[:], nonceBytes) + nonces = append(nonces, nonce) + } + device.UsedAppNonces = nonces + case "next_downlink": + if value != "null" { + nextDownlink := &mqtt.DownlinkMessage{} + err := json.Unmarshal([]byte(value), nextDownlink) + if err != nil { + return err + } + device.NextDownlink = nextDownlink + } + } + return nil +} diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go new file mode 100644 index 000000000..994c8d59c --- /dev/null +++ b/core/handler/device/device_test.go @@ -0,0 +1,73 @@ +package device + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/smartystreets/assertions" +) + +func getTestDevice() (device *Device, dmap map[string]string) { + return &Device{ + DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, + AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + DevAddr: types.DevAddr{1, 2, 3, 4}, + AppKey: types.AppKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, + NwkSKey: types.NwkSKey{1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8}, + AppSKey: types.AppSKey{2, 1, 2, 2, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 8}, + UsedDevNonces: []DevNonce{DevNonce{1, 2}, DevNonce{3, 4}}, + UsedAppNonces: []AppNonce{AppNonce{1, 2, 3}, AppNonce{4, 5, 6}}, + }, map[string]string{ + "dev_eui": "0102030405060708", + "app_eui": "0807060504030201", + "dev_addr": "01020304", + "app_key": "00010002000300040005000600070008", + "nwk_s_key": "01010102010301040105010601070108", + "app_s_key": "02010202020302040205020602070208", + "used_dev_nonces": "0102,0304", + "used_app_nonces": "010203,040506", + "next_downlink": "null", + } +} + +func TestToStringMap(t *testing.T) { + a := New(t) + device, expected := getTestDevice() + dmap, err := device.ToStringStringMap(DeviceProperties...) + a.So(err, ShouldBeNil) + a.So(dmap, ShouldResemble, expected) +} + +func TestFromStringMap(t *testing.T) { + a := New(t) + device := &Device{} + expected, dmap := getTestDevice() + err := device.FromStringStringMap(dmap) + a.So(err, ShouldBeNil) + a.So(device, ShouldResemble, expected) +} + +func TestNextDownlink(t *testing.T) { + a := New(t) + dev := &Device{ + NextDownlink: &mqtt.DownlinkMessage{ + Fields: map[string]interface{}{ + "string": "hello!", + "int": 42, + "bool": true, + }, + }, + } + formatted, err := dev.formatProperty("next_downlink") + a.So(err, ShouldBeNil) + a.So(formatted, ShouldContainSubstring, `"fields":{"bool":true,"int":42,"string":"hello!"}`) + + dev = &Device{} + err = dev.parseProperty("next_downlink", `{"fields":{"bool":true,"int":42,"string":"hello!"}}`) + a.So(err, ShouldBeNil) + a.So(dev.NextDownlink.Fields, ShouldNotBeNil) + a.So(dev.NextDownlink.Fields["bool"], ShouldBeTrue) + a.So(dev.NextDownlink.Fields["int"], ShouldEqual, 42) + a.So(dev.NextDownlink.Fields["string"], ShouldEqual, "hello!") +} diff --git a/core/handler/device/store.go b/core/handler/device/store.go new file mode 100644 index 000000000..875d5240a --- /dev/null +++ b/core/handler/device/store.go @@ -0,0 +1,122 @@ +package device + +import ( + "errors" + "fmt" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +var ( + // ErrNotFound is returned when a device was not found + ErrNotFound = errors.New("ttn/handler: Device not found") +) + +// Store is used to store device configurations +type Store interface { + // Get the full information about a device + Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) + // Set the given fields of a device. If fields empty, it sets all fields. + Set(device *Device, fields ...string) error + // Delete a device + Delete(types.AppEUI, types.DevEUI) error +} + +// NewDeviceStore creates a new in-memory Device store +func NewDeviceStore() Store { + return &deviceStore{ + devices: make(map[types.AppEUI]map[types.DevEUI]*Device), + } +} + +// deviceStore is an in-memory Device store. It should only be used for testing +// purposes. Use the redisDeviceStore for actual deployments. +type deviceStore struct { + devices map[types.AppEUI]map[types.DevEUI]*Device +} + +func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { + if app, ok := s.devices[appEUI]; ok { + if dev, ok := app[devEUI]; ok { + return dev, nil + } + } + return nil, ErrNotFound +} + +func (s *deviceStore) Set(new *Device, fields ...string) error { + // NOTE: We don't care about fields for testing + if app, ok := s.devices[new.AppEUI]; ok { + app[new.DevEUI] = new + } else { + s.devices[new.AppEUI] = map[types.DevEUI]*Device{new.DevEUI: new} + } + return nil +} + +func (s *deviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { + if app, ok := s.devices[appEUI]; ok { + delete(app, devEUI) + } + return nil +} + +// NewRedisDeviceStore creates a new Redis-based status store +func NewRedisDeviceStore(client *redis.Client) Store { + return &redisDeviceStore{ + client: client, + } +} + +const redisDevicePrefix = "handler:device" + +type redisDeviceStore struct { + client *redis.Client +} + +func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() + if err != nil { + return nil, err + } else if len(res) == 0 { + return nil, redis.Nil // This might be a bug in redis package + } + device := &Device{} + err = device.FromStringStringMap(res) + if err != nil { + return nil, err + } + return device, nil +} + +func (s *redisDeviceStore) Set(new *Device, fields ...string) error { + if len(fields) == 0 { + fields = DeviceProperties + } + + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppEUI, new.DevEUI) + dmap, err := new.ToStringStringMap(fields...) + if err != nil { + return err + } + if len(dmap) == 0 { + return nil + } + err = s.client.HMSetMap(key, dmap).Err() + if err != nil { + return err + } + + return nil +} + +func (s *redisDeviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) + err := s.client.Del(key).Err() + if err != nil { + return err + } + return nil +} diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go new file mode 100644 index 000000000..aaeb1c7cb --- /dev/null +++ b/core/handler/device/store_test.go @@ -0,0 +1,71 @@ +package device + +import ( + "testing" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 0, // use default DB + }) +} + +func TestDeviceStore(t *testing.T) { + a := New(t) + + stores := map[string]Store{ + "local": NewDeviceStore(), + "redis": NewRedisDeviceStore(getRedisClient()), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Get non-existing + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + a.So(err, ShouldBeNil) + + // Get existing + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + // Create extra + err = s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 2}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + }) + a.So(err, ShouldBeNil) + + // Delete + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Cleanup + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + } + +} diff --git a/core/handler/downlink.go b/core/handler/downlink.go new file mode 100644 index 000000000..fec6e2879 --- /dev/null +++ b/core/handler/downlink.go @@ -0,0 +1,71 @@ +package handler + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { + ctx := h.Ctx.WithFields(log.Fields{ + "DevEUI": appDownlink.DevEUI, + "AppEUI": appDownlink.AppEUI, + }) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not enqueue downlink") + } + }() + + dev, err := h.devices.Get(appDownlink.AppEUI, appDownlink.DevEUI) + if err != nil { + return err + } + dev.NextDownlink = appDownlink + err = h.devices.Set(dev, "next_downlink") + if err != nil { + return err + } + + ctx.Debug("Enqueue Downlink") + + return nil +} + +func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb_broker.DownlinkMessage) error { + ctx := h.Ctx.WithFields(log.Fields{ + "DevEUI": appDownlink.DevEUI, + "AppEUI": appDownlink.AppEUI, + }) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle downlink") + } + }() + + // Get Processors + processors := []DownlinkProcessor{ + h.ConvertToLoRaWAN, + } + + ctx.WithField("NumProcessors", len(processors)).Debug("Running Downlink Processors") + + // Run Processors + for _, processor := range processors { + err = processor(ctx, appDownlink, downlink) + if err == ErrNotNeeded { + err = nil + return nil + } else if err != nil { + return err + } + } + + ctx.Debug("Send Downlink") + + h.downlink <- downlink + + return nil +} diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go new file mode 100644 index 000000000..c363b9401 --- /dev/null +++ b/core/handler/downlink_test.go @@ -0,0 +1,91 @@ +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestEnqueueDownlink(t *testing.T) { + a := New(t) + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, + devices: device.NewDeviceStore(), + } + err := h.EnqueueDownlink(&mqtt.DownlinkMessage{ + AppEUI: appEUI, + DevEUI: devEUI, + }) + a.So(err, ShouldNotBeNil) + h.devices.Set(&device.Device{ + AppEUI: appEUI, + DevEUI: devEUI, + }) + err = h.EnqueueDownlink(&mqtt.DownlinkMessage{ + AppEUI: appEUI, + DevEUI: devEUI, + Fields: map[string]interface{}{ + "string": "hello!", + "int": 42, + "bool": true, + }, + }) + a.So(err, ShouldBeNil) + dev, _ := h.devices.Get(appEUI, devEUI) + a.So(dev.NextDownlink, ShouldNotBeEmpty) + a.So(dev.NextDownlink.Fields, ShouldHaveLength, 3) +} + +func TestHandleDownlink(t *testing.T) { + a := New(t) + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, + devices: device.NewDeviceStore(), + } + err := h.HandleDownlink(&mqtt.DownlinkMessage{ + AppEUI: appEUI, + DevEUI: devEUI, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + }) + a.So(err, ShouldNotBeNil) + h.devices.Set(&device.Device{ + AppEUI: appEUI, + DevEUI: devEUI, + }) + err = h.HandleDownlink(&mqtt.DownlinkMessage{ + AppEUI: appEUI, + DevEUI: devEUI, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + }) + a.So(err, ShouldBeNil) + h.downlink = make(chan *pb_broker.DownlinkMessage) + go func() { + dl := <-h.downlink + a.So(dl.Payload, ShouldNotBeEmpty) + }() + err = h.HandleDownlink(&mqtt.DownlinkMessage{ + AppEUI: appEUI, + DevEUI: devEUI, + Payload: []byte{0xAA, 0xBC}, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + }) + a.So(err, ShouldBeNil) +} diff --git a/core/handler/handler.go b/core/handler/handler.go new file mode 100644 index 000000000..34166df49 --- /dev/null +++ b/core/handler/handler.go @@ -0,0 +1,151 @@ +package handler + +import ( + "fmt" + "io" + "time" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "github.com/TheThingsNetwork/ttn/api" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/mqtt" + "gopkg.in/redis.v3" +) + +// Handler component +type Handler interface { + core.ComponentInterface + + HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error + HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) + EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error +} + +// NewRedisHandler creates a new Redis-backed Handler +func NewRedisHandler(client *redis.Client, ttnBrokerAddr string, mqttUsername string, mqttPassword string, mqttBrokers ...string) Handler { + return &handler{ + devices: device.NewRedisDeviceStore(client), + applications: application.NewRedisApplicationStore(client), + ttnBrokerAddr: ttnBrokerAddr, + mqttUsername: mqttUsername, + mqttPassword: mqttPassword, + mqttBrokers: mqttBrokers, + } +} + +type handler struct { + *core.Component + + devices device.Store + applications application.Store + + ttnBrokerAddr string + ttnBrokerConn *grpc.ClientConn + ttnBroker pb_broker.BrokerClient + + downlink chan *pb_broker.DownlinkMessage + + mqttClient mqtt.Client + mqttUsername string + mqttPassword string + mqttBrokers []string + + mqttUp chan *mqtt.UplinkMessage + mqttActivation chan *mqtt.Activation +} + +func (h *handler) Init(c *core.Component) error { + h.Component = c + err := h.Component.UpdateTokenKey() + if err != nil { + return err + } + err = h.Component.Announce() + if err != nil { + return err + } + + var brokers []string + for _, broker := range h.mqttBrokers { + brokers = append(brokers, fmt.Sprintf("tcp://%s", broker)) + } + + err = h.HandleMQTT(h.mqttUsername, h.mqttPassword, brokers...) + if err != nil { + return err + } + + err = h.associateBroker() + if err != nil { + return err + } + + return nil +} + +func (h *handler) associateBroker() error { + conn, err := grpc.Dial(h.ttnBrokerAddr, api.DialOptions...) + if err != nil { + return err + } + h.ttnBrokerConn = conn + h.ttnBroker = pb_broker.NewBrokerClient(conn) + + ctx := h.GetContext() + + upStream, err := h.ttnBroker.Subscribe(ctx, &pb_broker.SubscribeRequest{}) + if err != nil { + return err + } + + h.downlink = make(chan *pb_broker.DownlinkMessage) + downStream, err := h.ttnBroker.Publish(ctx) + if err != nil { + return err + } + + go func() { + for { + in, err := upStream.Recv() + if err != nil && (err == io.EOF || + grpc.Code(err) == codes.Canceled || + grpc.Code(err) == codes.Internal || + grpc.Code(err) == codes.Unauthenticated) { + h.Ctx.Fatalf("ttn/handler: Stopping Broker subscribe: %s", err) // TODO: Restart + break + } + if err != nil { + h.Ctx.Warnf("ttn/handler: Error in Broker subscribe: %s", err) + <-time.After(api.Backoff) + continue + } + go h.HandleUplink(in) + } + }() + + go func() { + for downlink := range h.downlink { + err := downStream.Send(downlink) + if err != nil && (err == io.EOF || + grpc.Code(err) == codes.Canceled || + grpc.Code(err) == codes.Internal || + grpc.Code(err) == codes.Unauthenticated) { + h.Ctx.Fatalf("ttn/handler: Stopping Broker publish: %s", err) // TODO: Restart + break + } + if err != nil { + h.Ctx.Warnf("ttn/handler: Error in Broker publish: %s", err) + <-time.After(api.Backoff) + continue + } + } + }() + + return nil +} diff --git a/core/handler/handler_test.go b/core/handler/handler_test.go new file mode 100644 index 000000000..aad4b96e2 --- /dev/null +++ b/core/handler/handler_test.go @@ -0,0 +1,3 @@ +package handler + +// TODO diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go new file mode 100644 index 000000000..a40ffb702 --- /dev/null +++ b/core/handler/mqtt.go @@ -0,0 +1,60 @@ +package handler + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) error { + h.mqttClient = mqtt.NewClient(h.Ctx, "ttnhdl", username, password, mqttBrokers...) + + err := h.mqttClient.Connect() + if err != nil { + return err + } + + h.mqttUp = make(chan *mqtt.UplinkMessage) + h.mqttActivation = make(chan *mqtt.Activation) + + token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, msg mqtt.DownlinkMessage) { + down := &msg + down.DevEUI = devEUI + down.AppEUI = appEUI + go h.EnqueueDownlink(down) + }) + token.Wait() + if token.Error() != nil { + return err + } + + go func() { + for up := range h.mqttUp { + h.Ctx.WithFields(log.Fields{ + "DevEUI": up.DevEUI, + "AppEUI": up.AppEUI, + }).Debug("Publish Uplink") + token := h.mqttClient.PublishUplink(up.AppEUI, up.DevEUI, *up) + go func() { + token.Wait() + if token.Error() != nil { + h.Ctx.WithError(token.Error()).Warn("Could not publish Uplink") + } + }() + } + }() + + go func() { + for activation := range h.mqttActivation { + token := h.mqttClient.PublishActivation(activation.AppEUI, activation.DevEUI, *activation) + go func() { + token.Wait() + if token.Error() != nil { + h.Ctx.WithError(token.Error()).Warn("Could not publish Activation") + } + }() + } + }() + + return nil +} diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go new file mode 100644 index 000000000..1d5273758 --- /dev/null +++ b/core/handler/mqtt_test.go @@ -0,0 +1,68 @@ +package handler + +import ( + "sync" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleMQTT(t *testing.T) { + a := New(t) + var wg sync.WaitGroup + c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", "tcp://localhost:1883") + c.Connect() + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, + devices: device.NewDeviceStore(), + } + h.devices.Set(&device.Device{ + AppEUI: appEUI, + DevEUI: devEUI, + }) + err := h.HandleMQTT("", "", "tcp://localhost:1883") + a.So(err, ShouldBeNil) + + c.PublishDownlink(appEUI, devEUI, mqtt.DownlinkMessage{ + Payload: []byte{0xAA, 0xBC}, + }).Wait() + <-time.After(10 * time.Millisecond) + dev, _ := h.devices.Get(appEUI, devEUI) + a.So(dev.NextDownlink, ShouldNotBeNil) + + wg.Add(1) + c.SubscribeUplink(func(client mqtt.Client, r_appEUI types.AppEUI, r_devEUI types.DevEUI, req mqtt.UplinkMessage) { + a.So(r_appEUI, ShouldEqual, appEUI) + a.So(r_devEUI, ShouldEqual, devEUI) + a.So(req.Payload, ShouldResemble, []byte{0xAA, 0xBC}) + wg.Done() + }) + + h.mqttUp <- &mqtt.UplinkMessage{ + DevEUI: devEUI, + AppEUI: appEUI, + Payload: []byte{0xAA, 0xBC}, + } + + wg.Add(1) + c.SubscribeActivations(func(client mqtt.Client, r_appEUI types.AppEUI, r_devEUI types.DevEUI, req mqtt.Activation) { + a.So(r_appEUI, ShouldEqual, appEUI) + a.So(r_devEUI, ShouldEqual, devEUI) + wg.Done() + }) + + h.mqttActivation <- &mqtt.Activation{ + DevEUI: devEUI, + AppEUI: appEUI, + } + + wg.Wait() +} diff --git a/core/handler/server.go b/core/handler/server.go new file mode 100644 index 000000000..fdc88d4cb --- /dev/null +++ b/core/handler/server.go @@ -0,0 +1,54 @@ +package handler + +import ( + "errors" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +type handlerRPC struct { + handler Handler +} + +func validateBrokerFromMetadata(ctx context.Context) (err error) { + md, ok := metadata.FromContext(ctx) + // TODO: Check OK + id, ok := md["id"] + if !ok || len(id) < 1 { + err = errors.New("ttn/handler: Broker did not provide \"id\" in context") + return + } + if err != nil { + return + } + token, ok := md["token"] + if !ok || len(token) < 1 { + err = errors.New("ttn/handler: Broker did not provide \"token\" in context") + return + } + if token[0] != "token" { + // TODO: Validate Token + err = errors.New("ttn/handler: Broker not authorized") + return + } + + return +} + +func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + err := validateBrokerFromMetadata(ctx) + if err != nil { + return nil, err + } + return h.handler.HandleActivation(activation) +} + +// RegisterRPC registers this handler as a HandlerServer (github.com/TheThingsNetwork/ttn/api/handler) +func (r *handler) RegisterRPC(s *grpc.Server) { + server := &handlerRPC{r} + pb.RegisterHandlerServer(s, server) +} diff --git a/core/handler/server_test.go b/core/handler/server_test.go new file mode 100644 index 000000000..aad4b96e2 --- /dev/null +++ b/core/handler/server_test.go @@ -0,0 +1,3 @@ +package handler + +// TODO diff --git a/core/handler/types.go b/core/handler/types.go new file mode 100644 index 000000000..197e95809 --- /dev/null +++ b/core/handler/types.go @@ -0,0 +1,17 @@ +package handler + +import ( + "errors" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +// UplinkProcessor processes an uplink protobuf to an application-layer uplink message +type UplinkProcessor func(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error + +// DownlinkProcessor processes an application-layer downlink message to a downlik protobuf +type DownlinkProcessor func(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error + +var ErrNotNeeded = errors.New("Further processing not needed") diff --git a/core/handler/uplink.go b/core/handler/uplink.go new file mode 100644 index 000000000..1d6376093 --- /dev/null +++ b/core/handler/uplink.go @@ -0,0 +1,100 @@ +package handler + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/apex/log" +) + +var ResponseDeadline = 100 * time.Millisecond + +func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error { + var appEUI types.AppEUI + if uplink.AppEui != nil { + appEUI = *uplink.AppEui + } + var devEUI types.DevEUI + if uplink.DevEui != nil { + devEUI = *uplink.DevEui + } + + ctx := h.Ctx.WithFields(log.Fields{ + "DevEUI": devEUI, + "AppEUI": appEUI, + }) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle uplink") + } + }() + + // Build AppUplink + appUplink := &mqtt.UplinkMessage{ + DevEUI: devEUI, + AppEUI: appEUI, + } + + // Get Uplink Processors + processors := []UplinkProcessor{ + h.ConvertFromLoRaWAN, + h.ConvertMetadata, + h.ConvertFields, + } + + ctx.WithField("NumProcessors", len(processors)).Debug("Running Uplink Processors") + + // Run Uplink Processors + for _, processor := range processors { + err = processor(ctx, uplink, appUplink) + if err == ErrNotNeeded { + err = nil + return nil + } else if err != nil { + return err + } + } + + // Publish Uplink + h.mqttUp <- appUplink + + <-time.After(ResponseDeadline) + + // Find Device and scheduled downlink + var appDownlink mqtt.DownlinkMessage + dev, err := h.devices.Get(appEUI, devEUI) + if err != nil { + return err + } + if dev.NextDownlink != nil { + appDownlink = *dev.NextDownlink + } + + if uplink.ResponseTemplate == nil { + ctx.Debug("No Downlink Available") + return nil + } + + // Prepare Downlink + downlink := uplink.ResponseTemplate + appDownlink.AppEUI = appEUI + appDownlink.DevEUI = devEUI + + // Handle Downlink + err = h.HandleDownlink(&appDownlink, downlink) + if err != nil { + return err + } + + // Clear Downlink + dev.NextDownlink = nil + err = h.devices.Set(dev, "next_downlink") + if err != nil { + return err + } + + return nil +} diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go new file mode 100644 index 000000000..43ef98165 --- /dev/null +++ b/core/handler/uplink_test.go @@ -0,0 +1,131 @@ +package handler + +import ( + "sync" + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleUplink(t *testing.T) { + a := New(t) + var err error + var wg sync.WaitGroup + appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestHandleUplink")}, + devices: device.NewDeviceStore(), + applications: application.NewApplicationStore(), + } + h.devices.Set(&device.Device{ + AppEUI: appEUI, + DevEUI: devEUI, + }) + h.applications.Set(&application.Application{ + AppEUI: appEUI, + }) + h.mqttUp = make(chan *mqtt.UplinkMessage) + h.downlink = make(chan *pb_broker.DownlinkMessage) + + uplink, _ := buildLorawanUplink([]byte{0x80, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x9C, 0x15, 0x7C, 0xEB, 0x09, 0x80}) + downlinkEmpty := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00} + downlinkACK := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x20, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00} + downlinkMAC := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x05, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00} + expected := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00} + downlink := &pb_broker.DownlinkMessage{ + DownlinkOption: &pb_broker.DownlinkOption{ + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + FCnt: 0, + }}}, + }, + } + + // Test Uplink, no downlink option available + wg.Add(1) + go func() { + <-h.mqttUp + wg.Done() + }() + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.Wait() + + uplink.ResponseTemplate = downlink + + // Test Uplink, no downlink needed + wg.Add(1) + go func() { + <-h.mqttUp + wg.Done() + }() + downlink.Payload = downlinkEmpty + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.Wait() + + // Test Uplink, ACK downlink needed + wg.Add(2) + go func() { + <-h.mqttUp + wg.Done() + }() + go func() { + <-h.downlink + wg.Done() + }() + downlink.Payload = downlinkACK + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.Wait() + + // Test Uplink, MAC downlink needed + wg.Add(2) + go func() { + <-h.mqttUp + wg.Done() + }() + go func() { + <-h.downlink + wg.Done() + }() + downlink.Payload = downlinkMAC + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.Wait() + + // Test Uplink, Data downlink needed + h.devices.Set(&device.Device{ + AppEUI: appEUI, + DevEUI: devEUI, + NextDownlink: &mqtt.DownlinkMessage{ + Payload: []byte{0xaa, 0xbc}, + }, + }) + wg.Add(2) + go func() { + <-h.mqttUp + wg.Done() + }() + go func() { + dl := <-h.downlink + a.So(dl.Payload, ShouldResemble, expected) + wg.Done() + }() + downlink.Payload = downlinkEmpty + err = h.HandleUplink(uplink) + a.So(err, ShouldBeNil) + wg.Wait() + + dev, _ := h.devices.Get(appEUI, devEUI) + a.So(dev.NextDownlink, ShouldBeNil) +} From c6e3ba2a70f74a474ecaa1826e0cf021929ba58c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 16 Jun 2016 20:20:16 +0200 Subject: [PATCH 1514/2266] Update commands --- api/broker/broker.proto | 1 - cmd/broker.go | 187 ++++++++++++++--------------------- cmd/discovery.go | 88 +++++++++++++++++ cmd/handler.go | 212 +++++++++++----------------------------- cmd/networkserver.go | 103 +++++++++++++++++++ cmd/root.go | 29 ++++++ cmd/router.go | 205 +++++++------------------------------- 7 files changed, 382 insertions(+), 443 deletions(-) create mode 100644 cmd/discovery.go create mode 100644 cmd/networkserver.go diff --git a/api/broker/broker.proto b/api/broker/broker.proto index c87bf223d..ed60d1f41 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -77,7 +77,6 @@ message DeduplicatedDeviceActivationRequest { } // message SubscribeRequest is used by a Handler to subscribe to uplink messages -// for a certain application. message SubscribeRequest {} // The Broker service provides pure network functionality diff --git a/cmd/broker.go b/cmd/broker.go index 606441c1c..bf6e32245 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -5,157 +5,112 @@ package cmd import ( "fmt" - "path" - "path/filepath" - "strings" + "net" + "os" + "os/signal" + "syscall" "time" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/components/broker" - "github.com/TheThingsNetwork/ttn/utils/stats" - "github.com/TheThingsNetwork/ttn/utils/tokenkey" + "gopkg.in/redis.v3" + + "google.golang.org/grpc" + + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/broker" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" - "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) -// brokerCmd represents the router command +// brokerCmd represents the broker command var brokerCmd = &cobra.Command{ Use: "broker", Short: "The Things Network broker", - Long: `ttn broker starts the Broker component of The Things Network. - -The Broker is responsible for finding the right handler for uplink packets it -receives from Routers. Handlers have register Applications and personalized -devices (with their network session keys) with the Broker. - `, + Long: ``, PreRun: func(cmd *cobra.Command, args []string) { - var statusServer string - if viper.GetInt("broker.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("broker.status-address"), viper.GetInt("broker.status-port")) - stats.Initialize() - } else { - statusServer = "disabled" - stats.Enabled = false - } ctx.WithFields(log.Fields{ - "devices-database": viper.GetString("broker.db-devices"), - "applications-database": viper.GetString("broker.db-apps"), - "status-server": statusServer, - "main-server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), + "server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - // Status & Health - statusAddr := fmt.Sprintf("%s:%d", viper.GetString("broker.status-address"), viper.GetInt("broker.status-port")) - statusAdapter := http.New( - http.Components{Ctx: ctx.WithField("adapter", "handler-status")}, - http.Options{NetAddr: statusAddr, Timeout: time.Second * 5}, - ) - statusAdapter.Bind(http.Healthz{}) - statusAdapter.Bind(http.StatusPage{}) - - // Storage - var dbDev broker.NetworkController - devDBString := viper.GetString("broker.db-devices") - switch { - case strings.HasPrefix(devDBString, "boltdb:"): + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("broker.redis-address"), + Password: "", // no password set + DB: int64(viper.GetInt("broker.redis-db")), + }) - dbPath, err := filepath.Abs(devDBString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid devices database path") - } + // Component + component := core.NewComponent(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) - dbDev, err = broker.NewNetworkController(dbPath) + component.Identity.Metadata = []*pb_discovery.Metadata{} + for _, prefixString := range viper.GetStringSlice("broker.prefix") { + prefix, length, err := types.ParseDevAddrPrefix(prefixString) if err != nil { - ctx.WithError(err).Fatal("Could not create local storage") + ctx.WithError(err).Fatal("Could not initialize broker") } - - ctx.WithField("devices database", dbPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid devices database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") - } - - var dbApp broker.AppStorage - appDBString := viper.GetString("broker.db-apps") - switch { - case strings.HasPrefix(appDBString, "boltdb:"): - - dbPath, err := filepath.Abs(appDBString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid applications database path") - } - - dbApp, err = broker.NewAppStorage(dbPath) - if err != nil { - ctx.WithError(err).Fatal("Could not create local storage") - } - - ctx.WithField("applications database", dbPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid applications database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") + component.Identity.Metadata = append(component.Identity.Metadata, &pb_discovery.Metadata{ + Key: pb_discovery.Metadata_PREFIX, + Value: []byte{byte(length), prefix[0], prefix[1], prefix[2], prefix[3]}, + }) } // Broker - broker := broker.New( - broker.Components{ - Ctx: ctx, - NetworkController: dbDev, - AppStorage: dbApp, - }, - broker.Options{ - NetAddrUp: fmt.Sprintf("%s:%d", viper.GetString("broker.uplink-address"), viper.GetInt("broker.uplink-port")), - NetAddrDown: fmt.Sprintf("%s:%d", viper.GetString("broker.downlink-address"), viper.GetInt("broker.downlink-port")), - TokenKeyProvider: tokenkey.NewHTTPProvider(fmt.Sprintf("%s/key", viper.GetString("broker.account-server")), viper.GetString("broker.oauth2-keyfile")), - }, + broker := broker.NewRedisBroker( + client, + viper.GetString("broker.networkserver-address"), + time.Duration(viper.GetInt("broker.deduplication-delay"))*time.Millisecond, ) + err := broker.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize broker") + } - // Go - if err := broker.Start(); err != nil { - ctx.WithError(err).Fatal("Broker has fallen...") + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + broker.RegisterRPC(grpc) + go grpc.Serve(lis) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() }, } func init() { RootCmd.AddCommand(brokerCmd) - brokerCmd.Flags().String("db-apps", "boltdb:/tmp/ttn_broker_apps.db", "Applications Database connection") - viper.BindPFlag("broker.db-apps", brokerCmd.Flags().Lookup("db-apps")) - - brokerCmd.Flags().String("db-devices", "boltdb:/tmp/ttn_broker_devices.db", "Devices Database connection") - viper.BindPFlag("broker.db-devices", brokerCmd.Flags().Lookup("db-devices")) + brokerCmd.Flags().String("redis-address", "localhost:6379", "Redis host and port") + viper.BindPFlag("broker.redis-address", brokerCmd.Flags().Lookup("redis-address")) + brokerCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("broker.redis-db", brokerCmd.Flags().Lookup("redis-db")) - brokerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") - brokerCmd.Flags().Int("status-port", 10701, "The port of the status server, use 0 to disable") - viper.BindPFlag("broker.status-address", brokerCmd.Flags().Lookup("status-address")) - viper.BindPFlag("broker.status-port", brokerCmd.Flags().Lookup("status-port")) + brokerCmd.Flags().String("networkserver-address", "localhost:1903", "Networkserver host and port") + viper.BindPFlag("broker.networkserver-address", brokerCmd.Flags().Lookup("networkserver-address")) - brokerCmd.Flags().String("uplink-address", "0.0.0.0", "The IP address to listen for uplink communication") - brokerCmd.Flags().Int("uplink-port", 1881, "The port for uplink communication") - viper.BindPFlag("broker.uplink-address", brokerCmd.Flags().Lookup("uplink-address")) - viper.BindPFlag("broker.uplink-port", brokerCmd.Flags().Lookup("uplink-port")) + brokerCmd.Flags().StringSlice("prefix", []string{"26000000/24"}, "LoRaWAN DevAddr prefix to announce") + viper.BindPFlag("broker.prefix", brokerCmd.Flags().Lookup("prefix")) - brokerCmd.Flags().String("downlink-address", "0.0.0.0", "The IP address to listen for downlink communication") - brokerCmd.Flags().Int("downlink-port", 1781, "The port for downlink communication") - viper.BindPFlag("broker.downlink-address", brokerCmd.Flags().Lookup("downlink-address")) - viper.BindPFlag("broker.downlink-port", brokerCmd.Flags().Lookup("downlink-port")) - - brokerCmd.Flags().String("account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") - viper.BindPFlag("broker.account-server", brokerCmd.Flags().Lookup("account-server")) - - defaultOAuth2KeyFile := "" - dir, err := homedir.Dir() - if err == nil { - expanded, err := homedir.Expand(dir) - if err == nil { - defaultOAuth2KeyFile = path.Join(expanded, ".ttn/oauth2-token.pub") - } - } + brokerCmd.Flags().Int("deduplication-delay", 200, "Deduplication delay (in ms)") + viper.BindPFlag("broker.deduplication-delay", brokerCmd.Flags().Lookup("deduplication-delay")) - brokerCmd.Flags().String("oauth2-keyfile", defaultOAuth2KeyFile, "The OAuth 2.0 public key") - viper.BindPFlag("broker.oauth2-keyfile", brokerCmd.Flags().Lookup("oauth2-keyfile")) + brokerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + brokerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + brokerCmd.Flags().Int("server-port", 1902, "The port for communication") + viper.BindPFlag("broker.server-address", brokerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("broker.server-address-announce", brokerCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("broker.server-port", brokerCmd.Flags().Lookup("server-port")) } diff --git a/cmd/discovery.go b/cmd/discovery.go new file mode 100644 index 000000000..b131df854 --- /dev/null +++ b/cmd/discovery.go @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net" + "os" + "os/signal" + "syscall" + + "google.golang.org/grpc" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/discovery" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// discoveryCmd represents the discovery command +var discoveryCmd = &cobra.Command{ + Use: "discovery", + Short: "The Things Network discovery", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "server": fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port")), + "database": fmt.Sprintf("%s/%d", viper.GetString("discovery.redis-address"), viper.GetInt("discovery.redis-db")), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("discovery.redis-address"), + Password: "", // no password set + DB: int64(viper.GetInt("discovery.redis-db")), + }) + + // Component + component := core.NewComponent(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) + + // Discovery Server + discovery := discovery.NewRedisDiscovery(client) + err := discovery.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize discovery") + } + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + discovery.RegisterRPC(grpc) + go grpc.Serve(lis) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() + }, +} + +func init() { + RootCmd.AddCommand(discoveryCmd) + + discoveryCmd.Flags().String("redis-address", "localhost:6379", "Redis server and port") + viper.BindPFlag("discovery.redis-address", discoveryCmd.Flags().Lookup("redis-address")) + discoveryCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("discovery.redis-db", discoveryCmd.Flags().Lookup("redis-db")) + + discoveryCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + discoveryCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + discoveryCmd.Flags().Int("server-port", 1900, "The port for communication") + viper.BindPFlag("discovery.server-address", discoveryCmd.Flags().Lookup("server-address")) + viper.BindPFlag("discovery.server-address-announce", discoveryCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("discovery.server-port", discoveryCmd.Flags().Lookup("server-port")) +} diff --git a/cmd/handler.go b/cmd/handler.go index 4cfd2b70c..aa9333bed 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -5,17 +5,16 @@ package cmd import ( "fmt" - "path/filepath" - "strings" - "time" - - "github.com/TheThingsNetwork/ttn/core/adapters/fields" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - handlerMQTT "github.com/TheThingsNetwork/ttn/core/adapters/mqtt" - "github.com/TheThingsNetwork/ttn/core/components/broker" - "github.com/TheThingsNetwork/ttn/core/components/handler" - ttnMQTT "github.com/TheThingsNetwork/ttn/mqtt" - "github.com/TheThingsNetwork/ttn/utils/stats" + "net" + "os" + "os/signal" + "syscall" + + "google.golang.org/grpc" + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -25,179 +24,82 @@ import ( var handlerCmd = &cobra.Command{ Use: "handler", Short: "The Things Network handler", - Long: `ttn handler starts a default Handler for The Things Network - -The Handler is the bridge between The Things Network and applications. -`, + Long: ``, PreRun: func(cmd *cobra.Command, args []string) { - var statusServer string - if viper.GetInt("handler.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("handler.status-address"), viper.GetInt("handler.status-port")) - stats.Initialize() - } else { - statusServer = "disabled" - stats.Enabled = false - } ctx.WithFields(log.Fields{ - "devices-database": viper.GetString("handler.db-devices"), - "packets-database": viper.GetString("handler.db-packets"), - "status-server": statusServer, - "internal-server": fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), - "public-server": fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), - "ttn-broker": viper.GetString("handler.ttn-broker"), - "mqtt-broker": viper.GetString("handler.mqtt-broker"), + "server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - // Status & Health - statusAddr := fmt.Sprintf("%s:%d", viper.GetString("handler.status-address"), viper.GetInt("handler.status-port")) - statusAdapter := http.New( - http.Components{Ctx: ctx.WithField("adapter", "handler-status")}, - http.Options{NetAddr: statusAddr, Timeout: time.Second * 5}, - ) - statusAdapter.Bind(http.Healthz{}) - statusAdapter.Bind(http.StatusPage{}) - - // In-memory devices storage - var devicesDB handler.DevStorage - - devDBString := viper.GetString("handler.db-devices") - switch { - case strings.HasPrefix(devDBString, "boltdb:"): - - devDBPath, err := filepath.Abs(devDBString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid devices database path") - } + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("handler.redis-address"), + Password: "", // no password set + DB: int64(viper.GetInt("handler.redis-db")), + }) - devicesDB, err = handler.NewDevStorage(devDBPath) - if err != nil { - ctx.WithError(err).Fatal("Could not create local devices storage") - } - - ctx.WithField("database", devDBPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local devices storage") - } - - // In-memory packets storage - var packetsDB handler.PktStorage - - pktDBString := viper.GetString("handler.db-packets") - switch { - case strings.HasPrefix(pktDBString, "boltdb:"): - - pktDBPath, err := filepath.Abs(pktDBString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid packets database path") - } - - packetsDB, err = handler.NewPktStorage(pktDBPath, 1) - if err != nil { - ctx.WithError(err).Fatal("Could not create local packets storage") - } - - ctx.WithField("database", pktDBPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local packets storage") - } + // Component + component := core.NewComponent(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) - // BrokerClient - brokerClient, err := broker.NewClient(viper.GetString("handler.ttn-broker")) - if err != nil { - ctx.WithError(err).Fatal("Could not dial broker") - } - - // MQTT Client & adapter - mqttClient := ttnMQTT.NewClient( - ctx.WithField("adapter", "handler-mqtt"), - "ttnhdl", + // Broker + handler := handler.NewRedisHandler( + client, + viper.GetString("handler.ttn-broker"), viper.GetString("handler.mqtt-username"), viper.GetString("handler.mqtt-password"), - fmt.Sprintf("tcp://%s", viper.GetString("handler.mqtt-broker")), + viper.GetString("handler.mqtt-broker"), ) - err = mqttClient.Connect() + err := handler.Init(component) if err != nil { - ctx.WithError(err).Fatal("Could not connect to MQTT") + ctx.WithError(err).Fatal("Could not initialize handler") } - storage, err := fields.ConnectRedis(viper.GetString("handler.redis-addr"), int64(viper.GetInt("handler.redis-db"))) + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port"))) if err != nil { - ctx.WithError(err).Fatal("Could not connect to Redis") + ctx.WithError(err).Fatal("Could not start gRPC server") } + grpc := grpc.NewServer(component.ServerOptions()...) - fieldsAdapter := fields.NewAdapter( - ctx.WithField("adapter", "fields-adapter"), - storage, - handlerMQTT.NewAdapter( - ctx.WithField("adapter", "mqtt-adapter"), - mqttClient, - ), - ) + // Register and Listen + handler.RegisterRPC(grpc) + go grpc.Serve(lis) - // Handler - handler := handler.New( - handler.Components{ - Ctx: ctx, - DevStorage: devicesDB, - PktStorage: packetsDB, - Broker: brokerClient, - AppAdapter: fieldsAdapter, - }, - handler.Options{ - PublicNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.public-address"), viper.GetInt("handler.public-port")), - PrivateNetAddr: fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address"), viper.GetInt("handler.internal-port")), - PrivateNetAddrAnnounce: fmt.Sprintf("%s:%d", viper.GetString("handler.internal-address-announce"), viper.GetInt("handler.internal-port")), - }, - ) + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") - fieldsAdapter.SubscribeDownlink(handler) + grpc.Stop() - if err := handler.Start(); err != nil { - ctx.WithError(err).Fatal("Handler has fallen...") - } }, } func init() { RootCmd.AddCommand(handlerCmd) - handlerCmd.Flags().String("db-devices", "boltdb:/tmp/ttn_handler_devices.db", "Devices Database connection") - handlerCmd.Flags().String("db-packets", "boltdb:/tmp/ttn_handler_packets.db", "Packets Database connection") - viper.BindPFlag("handler.db-devices", handlerCmd.Flags().Lookup("db-devices")) - viper.BindPFlag("handler.db-packets", handlerCmd.Flags().Lookup("db-packets")) - - handlerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") - handlerCmd.Flags().Int("status-port", 10702, "The port of the status server, use 0 to disable") - viper.BindPFlag("handler.status-address", handlerCmd.Flags().Lookup("status-address")) - viper.BindPFlag("handler.status-port", handlerCmd.Flags().Lookup("status-port")) - - handlerCmd.Flags().String("internal-address", "0.0.0.0", "The IP address to listen for communication from other components") - handlerCmd.Flags().String("internal-address-announce", "localhost", "The hostname to announce for communication from other components") - handlerCmd.Flags().Int("internal-port", 1882, "The port for communication from other components") - viper.BindPFlag("handler.internal-address", handlerCmd.Flags().Lookup("internal-address")) - viper.BindPFlag("handler.internal-address-announce", handlerCmd.Flags().Lookup("internal-address-announce")) - viper.BindPFlag("handler.internal-port", handlerCmd.Flags().Lookup("internal-port")) - - handlerCmd.Flags().String("public-address", "0.0.0.0", "The IP address to listen for communication with the wild open") - handlerCmd.Flags().Int("public-port", 1782, "The port for communication with the wild open") - viper.BindPFlag("handler.public-address", handlerCmd.Flags().Lookup("public-address")) - viper.BindPFlag("handler.public-port", handlerCmd.Flags().Lookup("public-port")) - - handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker (uplink)") - handlerCmd.Flags().String("mqtt-username", "handler", "The username for the MQTT broker (uplink)") - handlerCmd.Flags().String("mqtt-password", "", "The password for the MQTT broker (uplink)") + handlerCmd.Flags().String("redis-address", "localhost:6379", "Redis host and port") + viper.BindPFlag("handler.redis-address", handlerCmd.Flags().Lookup("redis-address")) + handlerCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("handler.redis-db", handlerCmd.Flags().Lookup("redis-db")) + + handlerCmd.Flags().String("ttn-broker", "localhost:1902", "TTN broker host and port") + viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) + + handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "MQTT broker host and port") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) + + handlerCmd.Flags().String("mqtt-username", "handler", "MQTT username") viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) - viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) - handlerCmd.Flags().String("redis-addr", "localhost:6379", "The address of Redis") - handlerCmd.Flags().Int64("redis-db", 0, "The database of Redis") - viper.BindPFlag("handler.redis-addr", handlerCmd.Flags().Lookup("redis-addr")) - viper.BindPFlag("handler.redis-db", handlerCmd.Flags().Lookup("redis-db")) + handlerCmd.Flags().String("mqtt-password", "", "MQTT password") + viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) - handlerCmd.Flags().String("ttn-broker", "localhost:1781", "The address of the TTN broker (downlink)") - viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) + handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + handlerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + handlerCmd.Flags().Int("server-port", 1904, "The port for communication") + viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("handler.server-address-announce", handlerCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) } diff --git a/cmd/networkserver.go b/cmd/networkserver.go new file mode 100644 index 000000000..b25cef961 --- /dev/null +++ b/cmd/networkserver.go @@ -0,0 +1,103 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "net" + "os" + "os/signal" + "syscall" + + "google.golang.org/grpc" + + "gopkg.in/redis.v3" + + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/networkserver" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// networkserverCmd represents the networkserver command +var networkserverCmd = &cobra.Command{ + Use: "networkserver", + Short: "The Things Network networkserver", + Long: ``, + PreRun: func(cmd *cobra.Command, args []string) { + ctx.WithFields(log.Fields{ + "server": fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port")), + "database": fmt.Sprintf("%s/%d", viper.GetString("networkserver.redis-address"), viper.GetInt("networkserver.redis-db")), + }).Info("Using Configuration") + }, + Run: func(cmd *cobra.Command, args []string) { + ctx.Info("Starting") + + // Redis Client + client := redis.NewClient(&redis.Options{ + Addr: viper.GetString("networkserver.redis-address"), + Password: "", // no password set + DB: int64(viper.GetInt("networkserver.redis-db")), + }) + + // Component + component := core.NewComponent(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) + + // networkserver Server + networkserver := networkserver.NewRedisNetworkServer(client, viper.GetInt("networkserver.net-id")) + prefix, length, err := types.ParseDevAddrPrefix(viper.GetString("networkserver.prefix")) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize networkserver") + } + err = networkserver.UsePrefix(prefix[:], length) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize networkserver") + } + err = networkserver.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize networkserver") + } + + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") + } + grpc := grpc.NewServer(component.ServerOptions()...) + + // Register and Listen + networkserver.RegisterRPC(grpc) + go grpc.Serve(lis) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + + grpc.Stop() + }, +} + +func init() { + RootCmd.AddCommand(networkserverCmd) + + networkserverCmd.Flags().String("redis-address", "localhost:6379", "Redis server and port") + viper.BindPFlag("networkserver.redis-address", networkserverCmd.Flags().Lookup("redis-address")) + networkserverCmd.Flags().Int("redis-db", 0, "Redis database") + viper.BindPFlag("networkserver.redis-db", networkserverCmd.Flags().Lookup("redis-db")) + + networkserverCmd.Flags().Int("net-id", 19, "LoRaWAN NetID") + viper.BindPFlag("networkserver.net-id", networkserverCmd.Flags().Lookup("net-id")) + + networkserverCmd.Flags().String("prefix", "26000000/24", "LoRaWAN DevAddr Prefix that should be used for issuing device addresses") + viper.BindPFlag("networkserver.prefix", networkserverCmd.Flags().Lookup("prefix")) + + networkserverCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + networkserverCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + networkserverCmd.Flags().Int("server-port", 1903, "The port for communication") + viper.BindPFlag("networkserver.server-address", networkserverCmd.Flags().Lookup("server-address")) + viper.BindPFlag("networkserver.server-address-announce", networkserverCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("networkserver.server-port", networkserverCmd.Flags().Lookup("server-port")) +} diff --git a/cmd/root.go b/cmd/root.go index 3adf61614..46f349252 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,10 +6,12 @@ package cmd import ( "fmt" "os" + "path" "strings" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" + "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -48,6 +50,33 @@ func init() { cobra.OnInitialize(initConfig) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yaml\")") + + RootCmd.PersistentFlags().String("id", "", "The id of this component") + viper.BindPFlag("id", RootCmd.PersistentFlags().Lookup("id")) + + RootCmd.PersistentFlags().String("token", "", "The auth token this component should use") + viper.BindPFlag("token", RootCmd.PersistentFlags().Lookup("token")) + + RootCmd.PersistentFlags().String("description", "", "The description of this component") + viper.BindPFlag("description", RootCmd.PersistentFlags().Lookup("description")) + + RootCmd.PersistentFlags().String("discovery-server", "localhost:1900", "The address of the Discovery server") + viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) + + RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") + viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) + + var defaultOAuth2KeyFile string + dir, err := homedir.Dir() + if err == nil { + expanded, err := homedir.Expand(dir) + if err == nil { + defaultOAuth2KeyFile = path.Join(expanded, ".ttn/oauth2-token.pub") + } + } + + RootCmd.PersistentFlags().String("oauth2-keyfile", defaultOAuth2KeyFile, "The OAuth 2.0 public key") + viper.BindPFlag("oauth2-keyfile", RootCmd.PersistentFlags().Lookup("oauth2-keyfile")) } // initConfig reads in config file and ENV variables if set. diff --git a/cmd/router.go b/cmd/router.go index 0d37d1c01..130e97b67 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -5,205 +5,68 @@ package cmd import ( "fmt" - "path/filepath" - "strings" - "time" + "net" + "os" + "os/signal" + "syscall" + + "google.golang.org/grpc" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/adapters/http" - "github.com/TheThingsNetwork/ttn/core/adapters/udp" - "github.com/TheThingsNetwork/ttn/core/components/router" - "github.com/TheThingsNetwork/ttn/core/dutycycle" - "github.com/TheThingsNetwork/ttn/utils/stats" + "github.com/TheThingsNetwork/ttn/core/router" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" - "google.golang.org/grpc" ) // routerCmd represents the router command var routerCmd = &cobra.Command{ Use: "router", Short: "The Things Network router", - Long: `ttn router starts the Router component of The Things Network. - -The Router accepts connections from gateways and forwards uplink packets to one -or more brokers. The router is also responsible for monitoring gateways, -collecting statistics from gateways and for enforcing TTN's fair use policy when -the gateway's duty cycle is (almost) full.`, + Long: ``, PreRun: func(cmd *cobra.Command, args []string) { - var statusServer string - if viper.GetInt("router.status-port") > 0 { - statusServer = fmt.Sprintf("%s:%d", viper.GetString("router.status-address"), viper.GetInt("router.status-port")) - stats.Initialize() - } else { - statusServer = "disabled" - stats.Enabled = false - } - ctx.WithFields(log.Fields{ - "db-brokers": viper.GetString("router.db-brokers"), - "db-gateways": viper.GetString("router.db-gateways"), - "db-duty": viper.GetString("router.db-duty"), - "status-server": statusServer, - "uplink": fmt.Sprintf("%s:%d", viper.GetString("router.uplink-address"), viper.GetInt("router.uplink-port")), - "downlink": fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), - "brokers": viper.GetString("router.brokers"), + "server": fmt.Sprintf("%s:%d", viper.GetString("router.server-address"), viper.GetInt("router.server-port")), }).Info("Using Configuration") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - // Status & Health - statusAddr := fmt.Sprintf("%s:%d", viper.GetString("router.status-address"), viper.GetInt("router.status-port")) - statusAdapter := http.New( - http.Components{Ctx: ctx.WithField("adapter", "router-status")}, - http.Options{NetAddr: statusAddr, Timeout: time.Second * 5}, - ) - statusAdapter.Bind(http.Healthz{}) - statusAdapter.Bind(http.StatusPage{}) - - // In-memory packet storage - var db router.BrkStorage - dbString := viper.GetString("router.db-brokers") - switch { - case strings.HasPrefix(dbString, "boltdb:"): - - dbPath, err := filepath.Abs(dbString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid database path") - } - - db, err = router.NewBrkStorage(dbPath, time.Hour*8) - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - ctx.WithField("database", dbPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") - } - - // Duty Manager - var dm dutycycle.DutyManager - dmString := viper.GetString("router.db-duty") - switch { - case strings.HasPrefix(dmString, "boltdb:"): - - dmPath, err := filepath.Abs(dmString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid database path") - } - - dm, err = dutycycle.NewManager(dmPath, time.Hour, dutycycle.Europe) - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - ctx.WithField("database", dmPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") - } - - // Gateways - var dg router.GtwStorage - dgString := viper.GetString("router.db-gateways") - switch { - case strings.HasPrefix(dmString, "boltdb:"): + // Component + component := core.NewComponent(ctx, "router", fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port"))) - dgPath, err := filepath.Abs(dgString[7:]) - if err != nil { - ctx.WithError(err).Fatal("Invalid database path") - } - - dg, err = router.NewGtwStorage(dgPath) - if err != nil { - ctx.WithError(err).Fatal("Could not create a local storage") - } - - ctx.WithField("database", dgPath).Info("Using local storage") - default: - ctx.WithError(fmt.Errorf("Invalid database string. Format: \"boltdb:/path/to.db\".")).Fatal("Could not instantiate local storage") + // Router + router := router.NewRouter() + err := router.Init(component) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize router") } - // Broker clients - var brokers []core.BrokerClient - brokersStr := strings.Split(viper.GetString("router.brokers"), ",") - for i := range brokersStr { - url := strings.Trim(brokersStr[i], " ") - brokerConn, err := grpc.Dial(url, grpc.WithInsecure(), grpc.WithTimeout(time.Second*15)) - if err != nil { - ctx.WithError(err).Fatal("Could not dial broker") - } - defer brokerConn.Close() - broker := core.NewBrokerClient(brokerConn) - brokers = append(brokers, broker) + // gRPC Server + lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("router.server-address"), viper.GetInt("router.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not start gRPC server") } + grpc := grpc.NewServer(component.ServerOptions()...) - // Router - router := router.New( - router.Components{ - Ctx: ctx, - DutyManager: dm, - Brokers: brokers, - BrkStorage: db, - GtwStorage: dg, - }, - router.Options{ - NetAddr: fmt.Sprintf("%s:%d", viper.GetString("router.downlink-address"), viper.GetInt("router.downlink-port")), - }, - ) + // Register and Listen + router.RegisterRPC(grpc) + go grpc.Serve(lis) - // Gateway Adapter - gtwNet := fmt.Sprintf("%s:%d", viper.GetString("router.uplink-address"), viper.GetInt("router.uplink-port")) - err := udp.Start( - udp.Components{ - Ctx: ctx.WithField("adapter", "gateway-semtech"), - Router: router, - }, - udp.Options{ - NetAddr: gtwNet, - MaxReconnectionDelay: 25 * 10000 * time.Millisecond, - }, - ) - if err != nil { - ctx.WithError(err).Fatal("Could not start Gateway Adapter") - } + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") - // Go - if err := router.Start(); err != nil { - ctx.WithError(err).Fatal("Router has fallen...") - } + grpc.Stop() }, } func init() { RootCmd.AddCommand(routerCmd) - - routerCmd.Flags().String("db-brokers", "boltdb:/tmp/ttn_router_brokers.db", "Database connection of known brokers") - viper.BindPFlag("router.db-brokers", routerCmd.Flags().Lookup("db-brokers")) - - routerCmd.Flags().String("db-gateways", "boltdb:/tmp/ttn_router_gateways.db", "Database connection of managed gateways") - viper.BindPFlag("router.db-gateways", routerCmd.Flags().Lookup("db-gateways")) - - routerCmd.Flags().String("db-duty", "boltdb:/tmp/ttn_router_duty.db", "Database connection of managed dutycycles") - viper.BindPFlag("router.db-duty", routerCmd.Flags().Lookup("db-duty")) - - routerCmd.Flags().String("status-address", "0.0.0.0", "The IP address to listen for serving status information") - routerCmd.Flags().Int("status-port", 10700, "The port of the status server, use 0 to disable") - viper.BindPFlag("router.status-address", routerCmd.Flags().Lookup("status-address")) - viper.BindPFlag("router.status-port", routerCmd.Flags().Lookup("status-port")) - - routerCmd.Flags().String("uplink-address", "0.0.0.0", "The IP address to listen for uplink communication from gateways") - routerCmd.Flags().Int("uplink-port", 1700, "The UDP port for uplink communication from gateways") - viper.BindPFlag("router.uplink-address", routerCmd.Flags().Lookup("uplink-address")) - viper.BindPFlag("router.uplink-port", routerCmd.Flags().Lookup("uplink-port")) - - routerCmd.Flags().String("downlink-address", "0.0.0.0", "The IP address to listen for downlink communication") - routerCmd.Flags().Int("downlink-port", 1780, "The port for downlink communication") - viper.BindPFlag("router.downlink-address", routerCmd.Flags().Lookup("downlink-address")) - viper.BindPFlag("router.downlink-port", routerCmd.Flags().Lookup("downlink-port")) - - routerCmd.Flags().String("brokers", "localhost:1881", "Comma-separated list of brokers") - viper.BindPFlag("router.brokers", routerCmd.Flags().Lookup("brokers")) + routerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") + routerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") + routerCmd.Flags().Int("server-port", 1901, "The port for communication") + viper.BindPFlag("router.server-address", routerCmd.Flags().Lookup("server-address")) + viper.BindPFlag("router.server-address-announce", routerCmd.Flags().Lookup("server-address-announce")) + viper.BindPFlag("router.server-port", routerCmd.Flags().Lookup("server-port")) } From d968848688bdb68a098cb20d76405f900a37fdc5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 16 Jun 2016 21:26:50 +0200 Subject: [PATCH 1515/2266] Update Protobufs --- api/api.pb.go | 2 +- api/broker/broker.pb.go | 5 ++--- api/discovery/discovery.pb.go | 8 +++++--- api/gateway/gateway.pb.go | 2 +- api/handler/handler.pb.go | 4 ++-- api/networkserver/networkserver.pb.go | 4 ++-- api/noc/noc.pb.go | 4 ++-- api/protocol/lorawan/lorawan.pb.go | 2 +- api/protocol/protocol.pb.go | 2 +- api/router/router.pb.go | 4 ++-- core/collector.pb.go | 21 ++++++++++++++++++++- 11 files changed, 39 insertions(+), 19 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index 1bcd89580..c2bec7695 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -29,7 +29,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 // message Ack is used to acknowledge a request, without giving a response. type Ack struct { diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index abec9ffc2..5e0b7cb1c 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -50,7 +50,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type DownlinkOption struct { Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` @@ -284,7 +284,6 @@ func (m *DeduplicatedDeviceActivationRequest) GetResponseTemplate() *DeviceActiv } // message SubscribeRequest is used by a Handler to subscribe to uplink messages -// for a certain application. type SubscribeRequest struct { } @@ -440,7 +439,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for Broker service diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index b18b81290..5edfc07f4 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -36,12 +36,14 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type Metadata_Key int32 const ( - Metadata_OTHER Metadata_Key = 0 + Metadata_OTHER Metadata_Key = 0 + // The value for PREFIX consists of 1 byte denoting the number of bits, + // followed by the prefix and enough trailing bits to fill 4 octets Metadata_PREFIX Metadata_Key = 1 ) @@ -141,7 +143,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for Discovery service diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 893f98bce..b4d87a944 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -32,7 +32,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type GPSMetadata struct { Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index d45a818bc..8c463b7ec 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -36,7 +36,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type DeviceActivationResponse struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` @@ -93,7 +93,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for Handler service diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 38873d85c..34bbf4f10 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -41,7 +41,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type DevicesRequest struct { DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` @@ -140,7 +140,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for NetworkServer service diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 9a2ed5459..d9a7c41b8 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -33,7 +33,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 // Reference imports to suppress errors if they are not otherwise used. var _ context.Context @@ -41,7 +41,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for Monitoring service diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index b6bebafc1..5e2214a92 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -36,7 +36,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type Modulation int32 diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 012c1c75c..66a5ab7e2 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -30,7 +30,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type RxMetadata struct { // Types that are valid to be assigned to Protocol: diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 9aeb60b2e..0cb79ddcb 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -49,7 +49,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +const _ = proto.ProtoPackageIsVersion2 type SubscribeRequest struct { } @@ -300,7 +300,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for Router service diff --git a/core/collector.pb.go b/core/collector.pb.go index e9fce881b..f9bbdf21b 100644 --- a/core/collector.pb.go +++ b/core/collector.pb.go @@ -2,6 +2,21 @@ // source: collector.proto // DO NOT EDIT! +/* +Package core is a generated protocol buffer package. + +It is generated from these files: + collector.proto + +It has these top-level messages: + GetApplicationsCollectorReq + GetApplicationsCollectorRes + CollectorApplication + AddApplicationCollectorReq + AddApplicationCollectorRes + RemoveApplicationCollectorReq + RemoveApplicationCollectorRes +*/ package core import proto "github.com/golang/protobuf/proto" @@ -20,6 +35,10 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion2 + type GetApplicationsCollectorReq struct { } @@ -116,7 +135,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion2 +const _ = grpc.SupportPackageIsVersion3 // Client API for CollectorManager service From 9aff5056a8dccca6b2e9222a029169c2499cd7df Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 16 Jun 2016 21:27:19 +0200 Subject: [PATCH 1516/2266] Improve database errors --- core/broker/application/store.go | 7 +++++-- core/broker/uplink.go | 2 +- core/handler/application/store.go | 5 ++++- core/handler/device/store.go | 5 ++++- core/networkserver/device/store.go | 5 ++++- core/router/gateway/status.go | 2 +- 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/core/broker/application/store.go b/core/broker/application/store.go index bb97de2d8..e171f9c7d 100644 --- a/core/broker/application/store.go +++ b/core/broker/application/store.go @@ -11,7 +11,7 @@ import ( var ( // ErrNotFound is returned when an application was not found - ErrNotFound = errors.New("ttn/networkserver: Application not found") + ErrNotFound = errors.New("ttn/broker: Application not found") ) // Store is used to store application configurations @@ -71,9 +71,12 @@ type redisApplicationStore struct { func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() if err != nil { + if err == redis.Nil { + return nil, ErrNotFound + } return nil, err } else if len(res) == 0 { - return nil, redis.Nil // This might be a bug in redis package + return nil, ErrNotFound } application := &Application{} err = application.FromStringStringMap(res) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 2d5c80abe..239364585 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -105,7 +105,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { if device.DisableFCntCheck { // TODO: Add warning to message? - } else if macPayload.FHDR.FCnt < device.StoredFCnt || macPayload.FHDR.FCnt-device.StoredFCnt > maxFCntGap { + } else if macPayload.FHDR.FCnt <= device.StoredFCnt || macPayload.FHDR.FCnt-device.StoredFCnt > maxFCntGap { // Replay attack or FCnt gap too big err = ErrInvalidFCnt return err diff --git a/core/handler/application/store.go b/core/handler/application/store.go index abc21a78d..3fb4f1bac 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -71,9 +71,12 @@ type redisApplicationStore struct { func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() if err != nil { + if err == redis.Nil { + return nil, ErrNotFound + } return nil, err } else if len(res) == 0 { - return nil, redis.Nil // This might be a bug in redis package + return nil, ErrNotFound } application := &Application{} err = application.FromStringStringMap(res) diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 875d5240a..65b75c9fe 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -79,9 +79,12 @@ type redisDeviceStore struct { func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() if err != nil { + if err == redis.Nil { + return nil, ErrNotFound + } return nil, err } else if len(res) == 0 { - return nil, redis.Nil // This might be a bug in redis package + return nil, ErrNotFound } device := &Device{} err = device.FromStringStringMap(res) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index e8a81b953..b3c5ee6bf 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -152,9 +152,12 @@ type redisDeviceStore struct { func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() if err != nil { + if err == redis.Nil { + return nil, ErrNotFound + } return nil, err } else if len(res) == 0 { - return nil, redis.Nil // This might be a bug in redis package + return nil, ErrNotFound } device := &Device{} err = device.FromStringStringMap(res) diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index b0a40af69..818ba86ff 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -68,7 +68,7 @@ func (s *redisStatusStore) Get() (*pb_gateway.Status, error) { status := &pb_gateway.Status{} res, err := s.client.HGetAllMap(s.key).Result() if err != nil { - return nil, err + return status, nil } err = status.FromStringStringMap(res) if err != nil { From 717b09a292b73d23a5437c310dbd66e6a7941e16 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 16 Jun 2016 22:06:35 +0200 Subject: [PATCH 1517/2266] Add logging to Handler Activation --- core/handler/activation.go | 90 +++++++++++++++++++++++---------- core/handler/activation_test.go | 3 ++ 2 files changed, 66 insertions(+), 27 deletions(-) diff --git a/core/handler/activation.go b/core/handler/activation.go index be5995ffe..9a050a0cf 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -5,23 +5,49 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" "github.com/brocaar/lorawan" ) func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { + var appEUI types.AppEUI + if activation.AppEui != nil { + appEUI = *activation.AppEui + } + var devEUI types.DevEUI + if activation.DevEui != nil { + devEUI = *activation.DevEui + } + ctx := h.Ctx.WithFields(log.Fields{ + "DevEUI": devEUI, + "AppEUI": appEUI, + }) + var err error + defer func() { + if err != nil { + ctx.WithError(err).Warn("Could not handle activation") + } + }() + // Find Device - dev, err := h.devices.Get(*activation.AppEui, *activation.DevEui) + var dev *device.Device + dev, err = h.devices.Get(*activation.AppEui, *activation.DevEui) if err != nil { // Find application - app, err := h.applications.Get(*activation.AppEui) - if err != nil || app.DefaultAppKey.IsEmpty() { + var app *application.Application + app, err = h.applications.Get(*activation.AppEui) + if err != nil { return nil, err } + if app.DefaultAppKey.IsEmpty() { + return nil, errors.New("ttn/handler: Device not found") + } // Use Default AppKey dev = &device.Device{ AppEUI: *activation.AppEui, @@ -32,37 +58,25 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // Check for LoRaWAN if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - return nil, errors.New("ttn/handler: Can not activate non-LoRaWAN device") + err = errors.New("ttn/handler: Can not activate non-LoRaWAN device") + return nil, err } // Unmarshal LoRaWAN var reqPHY lorawan.PHYPayload - if err := reqPHY.UnmarshalBinary(activation.Payload); err != nil { + if err = reqPHY.UnmarshalBinary(activation.Payload); err != nil { return nil, err } reqMAC, ok := reqPHY.MACPayload.(*lorawan.JoinRequestPayload) if !ok { - return nil, errors.New("MACPayload must be a *JoinRequestPayload") - } - - // Prepare Device Activation Response - var resPHY lorawan.PHYPayload - if err := resPHY.UnmarshalBinary(activation.ResponseTemplate.Payload); err != nil { - return nil, err - } - resMAC, ok := resPHY.MACPayload.(*lorawan.DataPayload) - if !ok { - return nil, errors.New("MACPayload must be a *DataPayload") - } - joinAccept := &lorawan.JoinAcceptPayload{} - if err := joinAccept.UnmarshalBinary(false, resMAC.Bytes); err != nil { + err = errors.New("MACPayload must be a *JoinRequestPayload") return nil, err } - resPHY.MACPayload = joinAccept // Validate MIC - if ok, err := reqPHY.ValidateMIC(lorawan.AES128Key(dev.AppKey)); err != nil || !ok { - return nil, errors.New("ttn/handler: Invalid MIC") + if ok, err = reqPHY.ValidateMIC(lorawan.AES128Key(dev.AppKey)); err != nil || !ok { + err = errors.New("ttn/handler: Invalid MIC") + return nil, err } // Validate DevNonce @@ -74,15 +88,34 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } } if alreadyUsed { - return nil, errors.New("ttn/handler: DevNonce already used") + err = errors.New("ttn/handler: DevNonce already used") + return nil, err } + ctx.Debug("Accepting Join Request") + // Publish Activation h.mqttActivation <- &mqtt.Activation{ AppEUI: *activation.AppEui, DevEUI: *activation.DevEui, } + // Prepare Device Activation Response + var resPHY lorawan.PHYPayload + if err = resPHY.UnmarshalBinary(activation.ResponseTemplate.Payload); err != nil { + return nil, err + } + resMAC, ok := resPHY.MACPayload.(*lorawan.DataPayload) + if !ok { + err = errors.New("MACPayload must be a *DataPayload") + return nil, err + } + joinAccept := &lorawan.JoinAcceptPayload{} + if err = joinAccept.UnmarshalBinary(false, resMAC.Bytes); err != nil { + return nil, err + } + resPHY.MACPayload = joinAccept + // Generate random AppNonce var appNonce device.AppNonce for { @@ -103,7 +136,9 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv joinAccept.AppNonce = appNonce // Calculate session keys - appSKey, nwkSKey, err := otaa.CalculateSessionKeys(dev.AppKey, joinAccept.AppNonce, joinAccept.NetID, reqMAC.DevNonce) + var appSKey types.AppSKey + var nwkSKey types.NwkSKey + appSKey, nwkSKey, err = otaa.CalculateSessionKeys(dev.AppKey, joinAccept.AppNonce, joinAccept.NetID, reqMAC.DevNonce) if err != nil { return nil, err } @@ -119,14 +154,15 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv return nil, err } - if err := resPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { + if err = resPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { return nil, err } - if err := resPHY.EncryptJoinAcceptPayload(lorawan.AES128Key(dev.AppKey)); err != nil { + if err = resPHY.EncryptJoinAcceptPayload(lorawan.AES128Key(dev.AppKey)); err != nil { return nil, err } - resBytes, err := resPHY.MarshalBinary() + var resBytes []byte + resBytes, err = resPHY.MarshalBinary() if err != nil { return nil, err } diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 73a3913b2..8b98056c7 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -8,10 +8,12 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/handler" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -60,6 +62,7 @@ func TestHandleActivation(t *testing.T) { a := New(t) h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestHandleActivation")}, applications: application.NewApplicationStore(), devices: device.NewDeviceStore(), } From 711d616964dfe5e176e3ea76b527c8d888babf36 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 16 Jun 2016 22:17:35 +0200 Subject: [PATCH 1518/2266] Don't register uplink twice for joins --- core/router/uplink.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/core/router/uplink.go b/core/router/uplink.go index 056535df2..9d23c359b 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -19,12 +19,6 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess } }() - gateway := r.getGateway(gatewayEUI) - gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) - gateway.Utilization.AddRx(uplink) - - downlinkOptions := r.buildDownlinkOptions(uplink, false, gateway) - // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(uplink.Payload) @@ -32,8 +26,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess return err } - switch phyPayload.MHDR.MType { - case lorawan.JoinRequest: + if phyPayload.MHDR.MType == lorawan.JoinRequest { joinRequestPayload, ok := phyPayload.MACPayload.(*lorawan.JoinRequestPayload) if !ok { return errors.New("Join Request message does not contain a join payload.") @@ -52,10 +45,6 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess GatewayMetadata: uplink.GatewayMetadata, }) return err - case lorawan.UnconfirmedDataUp, lorawan.ConfirmedDataUp: - // Just continue handling uplink - default: - return errors.New("ttn/router: Unhandled message type") } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) @@ -66,6 +55,12 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess ctx = ctx.WithField("DevAddr", devAddr) + gateway := r.getGateway(gatewayEUI) + gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) + gateway.Utilization.AddRx(uplink) + + downlinkOptions := r.buildDownlinkOptions(uplink, false, gateway) + // Find Broker brokers, err := r.brokerDiscovery.Discover(devAddr) if err != nil { From 0e0cf7aa06c82b09921a0c614bce3066db0031ce Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 17 Jun 2016 09:59:42 +0200 Subject: [PATCH 1519/2266] Also sync gateway schedule on Activations --- core/router/activation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/activation.go b/core/router/activation.go index 386b0c03c..5de5bcbdc 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -34,8 +34,8 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De } // Only for LoRaWAN + gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) gateway.Utilization.AddRx(uplink) - downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) // Find Broker From 4f72564486dfccf70a65d69a44621e70abe89b8a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 17 Jun 2016 10:00:04 +0200 Subject: [PATCH 1520/2266] Prefer RX2 over RX1 for SF9+ [EU] --- core/router/downlink.go | 73 ++++++++++++++++-------------- core/router/downlink_test.go | 88 +++++++++++++++++++++++------------- 2 files changed, 95 insertions(+), 66 deletions(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index e7cfb3dfc..6c6fddf54 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -168,38 +168,6 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo return // Invalid packet, probably won't happen if the gateway is just doing its job } - // Configuration for RX1 - { - uplinkChannel, err := band.GetChannel(int(uplink.GatewayMetadata.Frequency), uplinkDRIndex) - if err == nil { - downlinkChannel := band.DownlinkChannels[band.GetRX1Channel(uplinkChannel)] - downlinkDRIndex, err := band.GetRX1DataRateForOffset(uplinkDRIndex, 0) - if err == nil { - dataRate, _ := types.ConvertDataRate(band.DataRates[downlinkDRIndex]) - delay := band.ReceiveDelay1 - if isActivation { - delay = band.JoinAcceptDelay1 - } - rx1 := &pb_broker.DownlinkOption{ - GatewayEui: &gateway.EUI, - ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ - Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa - DataRate: dataRate.String(), // This is default - CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx - }}}, - GatewayConfig: &pb_gateway.TxConfiguration{ - Timestamp: uplink.GatewayMetadata.Timestamp + uint32(delay/1000), - RfChain: 0, - PolarizationInversion: true, - Frequency: uint64(downlinkChannel.Frequency), - Power: int32(band.DefaultTXPower), - }, - } - options = append(options, rx1) - } - } - } - // Configuration for RX2 { power := int32(band.DefaultTXPower) @@ -233,6 +201,38 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo options = append(options, rx2) } + // Configuration for RX1 + { + uplinkChannel, err := band.GetChannel(int(uplink.GatewayMetadata.Frequency), uplinkDRIndex) + if err == nil { + downlinkChannel := band.DownlinkChannels[band.GetRX1Channel(uplinkChannel)] + downlinkDRIndex, err := band.GetRX1DataRateForOffset(uplinkDRIndex, 0) + if err == nil { + dataRate, _ := types.ConvertDataRate(band.DataRates[downlinkDRIndex]) + delay := band.ReceiveDelay1 + if isActivation { + delay = band.JoinAcceptDelay1 + } + rx1 := &pb_broker.DownlinkOption{ + GatewayEui: &gateway.EUI, + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa + DataRate: dataRate.String(), // This is default + CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx + }}}, + GatewayConfig: &pb_gateway.TxConfiguration{ + Timestamp: uplink.GatewayMetadata.Timestamp + uint32(delay/1000), + RfChain: 0, + PolarizationInversion: true, + Frequency: uint64(downlinkChannel.Frequency), + Power: int32(band.DefaultTXPower), + }, + } + options = append(options, rx1) + } + } + } + computeDownlinkScores(gateway, uplink, options) for _, option := range options { @@ -287,14 +287,14 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o utilizationScore := 0.0 // Between 0 and 40 (lower is better) will be over 100 if forbidden { // Avoid gateways that do more Rx - utilizationScore += math.Min(gatewayRx*50, 20) // 40% utilization = 20 (max) + utilizationScore += math.Min(gatewayRx*50, 20) / 2 // 40% utilization = 10 (max) // Avoid busy channels freq := option.GatewayConfig.Frequency channelRx, channelTx := gateway.Utilization.GetChannel(freq) - utilizationScore += math.Min((channelTx+channelRx)*200, 20) // 10% utilization = 20 (max) + utilizationScore += math.Min((channelTx+channelRx)*200, 20) / 2 // 10% utilization = 10 (max) - // Enforce European Duty Cycle + // European Duty Cycle if region == "EU_863_870" { var duty float64 switch { @@ -314,6 +314,9 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o if channelTx > duty { utilizationScore += 100 // Transmissions on this frequency are forbidden } + if duty > 0 { + utilizationScore += math.Min(time.Seconds()/duty/100, 20) // Impact on duty-cycle (in order to prefer RX2 for SF9BW125) + } } } diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 6ae9e8c53..d971461f5 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -1,6 +1,7 @@ package router import ( + "fmt" "sync" "testing" "time" @@ -122,34 +123,34 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].Score, ShouldBeLessThan, options[1].Score) + a.So(options[1].Score, ShouldBeLessThan, options[0].Score) // Check Delay - a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 1000100) - a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 2000100) + a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 1000100) + a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 2000100) // Check Frequency - a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 868100000) - a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 869525000) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 868100000) + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 869525000) // Check Power - a.So(options[0].GatewayConfig.Power, ShouldEqual, 14) - a.So(options[1].GatewayConfig.Power, ShouldEqual, 27) + a.So(options[1].GatewayConfig.Power, ShouldEqual, 14) + a.So(options[0].GatewayConfig.Power, ShouldEqual, 27) // Check Data Rate - a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW125") - a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF9BW125") + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW125") + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF9BW125") // Check Coding Rate - a.So(options[0].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") a.So(options[1].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") + a.So(options[0].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") // And for joins we want a different delay (both RX1 and RX2) and DataRate (RX2) gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() options = r.buildDownlinkOptions(up, true, gtw) - a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 5000100) - a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 6000100) - a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") + a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 5000100) + a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 6000100) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") } func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { @@ -179,7 +180,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { up.GatewayMetadata.Frequency = freq options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].GatewayConfig.Frequency, ShouldEqual, freq) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, freq) } // Unsupported frequencies use only RX2 for downlink @@ -204,7 +205,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { up.GatewayMetadata.Frequency = upFreq options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].GatewayConfig.Frequency, ShouldEqual, downFreq) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, downFreq) } // Unsupported frequencies use only RX2 for downlink @@ -229,7 +230,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { up.GatewayMetadata.Frequency = upFreq options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].GatewayConfig.Frequency, ShouldEqual, downFreq) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, downFreq) } } @@ -254,7 +255,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up.ProtocolMetadata.GetLorawan().DataRate = dr options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) } gtw = newReferenceGateway("US_902_928") @@ -265,7 +266,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") // Supported datarates use RX1 (on the same datarate) for downlink ttnUSDataRates := map[string]string{ @@ -280,7 +281,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up.ProtocolMetadata.GetLorawan().DataRate = drUp options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) } gtw = newReferenceGateway("AU_915_928") @@ -291,7 +292,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up.ProtocolMetadata.GetLorawan().DataRate = "SF8BW500" options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF7BW500") // Supported datarates use RX1 (on the same datarate) for downlink ttnAUDataRates := map[string]string{ @@ -306,7 +307,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { up.ProtocolMetadata.GetLorawan().DataRate = drUp options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) - a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) } } @@ -315,27 +316,27 @@ func TestComputeDownlinkScores(t *testing.T) { a := New(t) r := &router{} gtw := newReferenceGateway("EU_863_870") - refScore := r.buildDownlinkOptions(newReferenceUplink(), false, gtw)[0].Score + refScore := r.buildDownlinkOptions(newReferenceUplink(), false, gtw)[1].Score // Lower RSSI -> worse score testSubject := newReferenceUplink() testSubject.GatewayMetadata.Rssi = -80.0 testSubjectgtw := newReferenceGateway("EU_863_870") - testSubjectScore := r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + testSubjectScore := r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Lower SNR -> worse score testSubject = newReferenceUplink() testSubject.GatewayMetadata.Snr = 2.0 testSubjectgtw = newReferenceGateway("EU_863_870") - testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Slower DataRate -> worse score testSubject = newReferenceUplink() - testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF10BW125" + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF8BW125" testSubjectgtw = newReferenceGateway("EU_863_870") - testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[0].Score + testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Gateway used for Rx -> worse score @@ -346,8 +347,8 @@ func TestComputeDownlinkScores(t *testing.T) { testSubjectgtw = newReferenceGateway("EU_863_870") testSubjectgtw.Utilization.AddRx(newReferenceUplink()) testSubjectgtw.Utilization.Tick() - testSubject1Score := r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score - testSubject2Score := r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score + testSubject1Score := r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[1].Score + testSubject2Score := r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[1].Score a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway a.So(testSubject2Score, ShouldBeGreaterThan, refScore) // Because of Rx in the gateway a.So(testSubject1Score, ShouldBeGreaterThan, testSubject2Score) // Because of Rx on the same channel @@ -363,7 +364,7 @@ func TestComputeDownlinkScores(t *testing.T) { a.So(options, ShouldHaveLength, 1) // RX1 Removed a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 869300000) - // European Duty-cycle + // European Duty-cycle Enforcement testSubject = newReferenceUplink() testSubjectgtw = newReferenceGateway("EU_863_870") for i := 0; i < 5; i++ { @@ -374,14 +375,39 @@ func TestComputeDownlinkScores(t *testing.T) { a.So(options, ShouldHaveLength, 1) // RX1 Removed a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 868100000) + fmt.Println() + + // European Duty-cycle Preferences - Prefer RX1 for low SF + testSubject = newReferenceUplink() + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF7BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + a.So(options[1].Score, ShouldBeLessThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF8BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + a.So(options[1].Score, ShouldBeLessThan, options[0].Score) + + // European Duty-cycle Preferences - Prefer RX2 for high SF + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF9BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF10BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF11BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF12BW125" + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) + // Scheduling Conflicts testSubject1 = newReferenceUplink() testSubject2 = newReferenceUplink() testSubject2.GatewayMetadata.Timestamp = 2000000 testSubjectgtw = newReferenceGateway("EU_863_870") testSubjectgtw.Schedule.GetOption(1000100, 50000) - testSubject1Score = r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[0].Score - testSubject2Score = r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[0].Score + testSubject1Score = r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[1].Score + testSubject2Score = r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[1].Score a.So(testSubject1Score, ShouldBeGreaterThan, refScore) // Scheduling conflict with RX1 a.So(testSubject2Score, ShouldEqual, refScore) // No scheduling conflicts } From 2afca55821480d7e0fed4ebea75313ea4dbdeeab Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 17 Jun 2016 10:49:50 +0200 Subject: [PATCH 1521/2266] Log the number of downlink options --- core/router/uplink.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/router/uplink.go b/core/router/uplink.go index 9d23c359b..260380d55 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -61,6 +61,8 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess downlinkOptions := r.buildDownlinkOptions(uplink, false, gateway) + ctx = ctx.WithField("DownlinkOptions", len(downlinkOptions)) + // Find Broker brokers, err := r.brokerDiscovery.Discover(devAddr) if err != nil { From 7e8a4b9e823ffc2718196a4c42229be46118f1f4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 17 Jun 2016 10:50:12 +0200 Subject: [PATCH 1522/2266] Implement GoStringer for debugging --- core/router/gateway/schedule.go | 11 +++++++++++ core/router/gateway/utilization.go | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index a5cb0dd5e..07b514431 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -2,6 +2,7 @@ package gateway import ( "errors" + "fmt" "sync" "sync/atomic" "time" @@ -13,6 +14,7 @@ import ( // Schedule is used to schedule downlink transmissions type Schedule interface { + fmt.GoStringer // Synchronize the schedule with the gateway timestamp (in microseconds) Sync(timestamp uint32) // Get an "option" on a transmission slot at timestamp for the maximum duration of length (both in microseconds) @@ -49,6 +51,15 @@ type schedule struct { downlink chan *router_pb.DownlinkMessage } +func (s *schedule) GoString() (str string) { + s.RLock() + defer s.RUnlock() + for _, item := range s.items { + str += fmt.Sprintf("%s at %s\n", item.id, item.time) + } + return +} + // TODO: Make configurable var Deadline = 200 * time.Millisecond diff --git a/core/router/gateway/utilization.go b/core/router/gateway/utilization.go index 07268dfc5..bd408c950 100644 --- a/core/router/gateway/utilization.go +++ b/core/router/gateway/utilization.go @@ -1,6 +1,7 @@ package gateway import ( + "fmt" "sync" "time" @@ -12,6 +13,7 @@ import ( // Utilization manages the utilization of a gateway and its channels // It is based on an exponentially weighted moving average over one minute type Utilization interface { + fmt.GoStringer // AddRx updates the utilization for receiving an uplink message AddRx(uplink *pb_router.UplinkMessage) error // AddRx updates the utilization for transmitting a downlink message @@ -43,6 +45,20 @@ type utilization struct { channelTxLock sync.RWMutex } +func (u *utilization) GoString() (str string) { + str += fmt.Sprintf("Rx %5.2f ", u.overallRx.Rate()/1000) + for ch, r := range u.channelRx { + str += fmt.Sprintf("(%d:%5.2f) ", ch, r.Rate()/1000) + } + str += "\n" + str += fmt.Sprintf("Tx %5.2f ", u.overallTx.Rate()/1000) + for ch, r := range u.channelTx { + str += fmt.Sprintf("(%d:%5.2f) ", ch, r.Rate()/1000) + } + str += "\n" + return +} + func (u *utilization) AddRx(uplink *pb_router.UplinkMessage) error { var t time.Duration var err error From 838f492722a8b7a6d748f884b920830b032b97a2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 17 Jun 2016 10:58:45 +0200 Subject: [PATCH 1523/2266] Prepare for next version of Account Server --- core/component.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/component.go b/core/component.go index a9c95440c..95d261c99 100644 --- a/core/component.go +++ b/core/component.go @@ -106,9 +106,18 @@ func (c *Component) UpdateTokenKey() error { } +// TTNClaims contains the claims that are set by the TTN Token Issuer +type TTNClaims struct { + jwt.StandardClaims + Client string `json:"client"` + Scopes []string `json:"scope"` + Apps map[string][]string `json:"apps,omitempty"` +} + // ValidateToken verifies an OAuth Bearer token -func (c *Component) ValidateToken(token string) (claims map[string]interface{}, err error) { - parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { +func (c *Component) ValidateToken(token string) (*TTNClaims, error) { + ttnClaims := &TTNClaims{} + parsed, err := jwt.ParseWithClaims(token, ttnClaims, func(token *jwt.Token) (interface{}, error) { if c.TokenKeyProvider == nil { return nil, errors.New("No token provider configured") } @@ -127,7 +136,7 @@ func (c *Component) ValidateToken(token string) (claims map[string]interface{}, if !parsed.Valid { return nil, errors.New("The token is not valid or is expired") } - return parsed.Claims, nil + return ttnClaims, nil } func (c *Component) ServerOptions() []grpc.ServerOption { From 8764631e28bcf6f9af6d09df8fee38f971a77e7c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 20 Jun 2016 10:49:16 +0200 Subject: [PATCH 1524/2266] Add logging to gateway schedule --- core/router/activation_test.go | 4 ++- core/router/downlink.go | 1 - core/router/downlink_test.go | 46 ++++++++++++++-------------- core/router/gateway/gateway.go | 9 ++++-- core/router/gateway/gateway_test.go | 3 +- core/router/gateway/schedule.go | 11 +++++-- core/router/gateway/schedule_test.go | 7 +++-- core/router/router.go | 2 +- core/router/uplink_test.go | 4 +-- 9 files changed, 50 insertions(+), 37 deletions(-) diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 4a35e9d91..19a716200 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -14,12 +14,14 @@ import ( func TestHandleActivation(t *testing.T) { a := New(t) + gatewayEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + r := &router{ Component: &core.Component{ Ctx: GetLogger(t, "TestHandleActivation"), }, gateways: map[types.GatewayEUI]*gateway.Gateway{ - types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}: newReferenceGateway("EU_863_870"), + gatewayEUI: newReferenceGateway(t, "EU_863_870"), }, brokerDiscovery: &mockBrokerDiscovery{}, } diff --git a/core/router/downlink.go b/core/router/downlink.go index 6c6fddf54..c30ce25ba 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -71,7 +71,6 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { ctx.WithError(err).Warn("Could not schedule Downlink") return err } - ctx.Debug("Schedule Downlink") return nil } diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index d971461f5..fdd700127 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -115,12 +115,12 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { // If something is incorrect, it just returns an empty list up := &pb.UplinkMessage{} - gtw := gateway.NewGateway(types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) + gtw := gateway.NewGateway(GetLogger(t, "TestUplinkBuildDownlinkOptions"), types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldBeEmpty) // The reference gateway and uplink work as expected - gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() + gtw, up = newReferenceGateway(t, "EU_863_870"), newReferenceUplink() options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 2) a.So(options[1].Score, ShouldBeLessThan, options[0].Score) @@ -146,7 +146,7 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { a.So(options[0].ProtocolConfig.GetLorawan().CodingRate, ShouldEqual, "4/5") // And for joins we want a different delay (both RX1 and RX2) and DataRate (RX2) - gtw, up = newReferenceGateway("EU_863_870"), newReferenceUplink() + gtw, up = newReferenceGateway(t, "EU_863_870"), newReferenceUplink() options = r.buildDownlinkOptions(up, true, gtw) a.So(options[1].GatewayConfig.Timestamp, ShouldEqual, 5000100) a.So(options[0].GatewayConfig.Timestamp, ShouldEqual, 6000100) @@ -159,7 +159,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { r := &router{} // Unsupported frequencies use only RX2 for downlink - gtw, up := newReferenceGateway("EU_863_870"), newReferenceUplink() + gtw, up := newReferenceGateway(t, "EU_863_870"), newReferenceUplink() up.GatewayMetadata.Frequency = 869300000 options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 1) @@ -184,7 +184,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { } // Unsupported frequencies use only RX2 for downlink - gtw, up = newReferenceGateway("US_902_928"), newReferenceUplink() + gtw, up = newReferenceGateway(t, "US_902_928"), newReferenceUplink() up.GatewayMetadata.Frequency = 923300000 options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 1) @@ -209,7 +209,7 @@ func TestUplinkBuildDownlinkOptionsFrequencies(t *testing.T) { } // Unsupported frequencies use only RX2 for downlink - gtw, up = newReferenceGateway("AU_915_928"), newReferenceUplink() + gtw, up = newReferenceGateway(t, "AU_915_928"), newReferenceUplink() up.GatewayMetadata.Frequency = 923300000 options = r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldHaveLength, 1) @@ -239,7 +239,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { r := &router{} - gtw := newReferenceGateway("EU_863_870") + gtw := newReferenceGateway(t, "EU_863_870") // Supported datarates use RX1 (on the same datarate) for downlink ttnEUDataRates := []string{ @@ -258,7 +258,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) } - gtw = newReferenceGateway("US_902_928") + gtw = newReferenceGateway(t, "US_902_928") // Test 500kHz channel up := newReferenceUplink() @@ -284,7 +284,7 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) } - gtw = newReferenceGateway("AU_915_928") + gtw = newReferenceGateway(t, "AU_915_928") // Test 500kHz channel up = newReferenceUplink() @@ -315,27 +315,27 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { func TestComputeDownlinkScores(t *testing.T) { a := New(t) r := &router{} - gtw := newReferenceGateway("EU_863_870") + gtw := newReferenceGateway(t, "EU_863_870") refScore := r.buildDownlinkOptions(newReferenceUplink(), false, gtw)[1].Score // Lower RSSI -> worse score testSubject := newReferenceUplink() testSubject.GatewayMetadata.Rssi = -80.0 - testSubjectgtw := newReferenceGateway("EU_863_870") + testSubjectgtw := newReferenceGateway(t, "EU_863_870") testSubjectScore := r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Lower SNR -> worse score testSubject = newReferenceUplink() testSubject.GatewayMetadata.Snr = 2.0 - testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw = newReferenceGateway(t, "EU_863_870") testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) // Slower DataRate -> worse score testSubject = newReferenceUplink() testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF8BW125" - testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw = newReferenceGateway(t, "EU_863_870") testSubjectScore = r.buildDownlinkOptions(testSubject, false, testSubjectgtw)[1].Score a.So(testSubjectScore, ShouldBeGreaterThan, refScore) @@ -344,7 +344,7 @@ func TestComputeDownlinkScores(t *testing.T) { testSubject2 := newReferenceUplink() testSubject2.GatewayMetadata.Timestamp = 10000000 testSubject2.GatewayMetadata.Frequency = 868500000 - testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw = newReferenceGateway(t, "EU_863_870") testSubjectgtw.Utilization.AddRx(newReferenceUplink()) testSubjectgtw.Utilization.Tick() testSubject1Score := r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[1].Score @@ -359,14 +359,14 @@ func TestComputeDownlinkScores(t *testing.T) { // changes the frequency plan. testSubject = newReferenceUplink() testSubject.GatewayMetadata.Frequency = 869300000 - testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw = newReferenceGateway(t, "EU_863_870") options := r.buildDownlinkOptions(testSubject, false, testSubjectgtw) a.So(options, ShouldHaveLength, 1) // RX1 Removed a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 869300000) // European Duty-cycle Enforcement testSubject = newReferenceUplink() - testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw = newReferenceGateway(t, "EU_863_870") for i := 0; i < 5; i++ { testSubjectgtw.Utilization.AddTx(newReferenceDownlink()) } @@ -380,31 +380,31 @@ func TestComputeDownlinkScores(t *testing.T) { // European Duty-cycle Preferences - Prefer RX1 for low SF testSubject = newReferenceUplink() testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF7BW125" - options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) a.So(options[1].Score, ShouldBeLessThan, options[0].Score) testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF8BW125" - options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) a.So(options[1].Score, ShouldBeLessThan, options[0].Score) // European Duty-cycle Preferences - Prefer RX2 for high SF testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF9BW125" - options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF10BW125" - options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF11BW125" - options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF12BW125" - options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway("EU_863_870")) + options = r.buildDownlinkOptions(testSubject, false, newReferenceGateway(t, "EU_863_870")) a.So(options[1].Score, ShouldBeGreaterThan, options[0].Score) // Scheduling Conflicts testSubject1 = newReferenceUplink() testSubject2 = newReferenceUplink() testSubject2.GatewayMetadata.Timestamp = 2000000 - testSubjectgtw = newReferenceGateway("EU_863_870") + testSubjectgtw = newReferenceGateway(t, "EU_863_870") testSubjectgtw.Schedule.GetOption(1000100, 50000) testSubject1Score = r.buildDownlinkOptions(testSubject1, false, testSubjectgtw)[1].Score testSubject2Score = r.buildDownlinkOptions(testSubject2, false, testSubjectgtw)[1].Score diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index ac00e67b4..f3533a6d5 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -1,14 +1,17 @@ package gateway -import "github.com/TheThingsNetwork/ttn/core/types" +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) // NewGateway creates a new in-memory Gateway structure -func NewGateway(eui types.GatewayEUI) *Gateway { +func NewGateway(ctx log.Interface, eui types.GatewayEUI) *Gateway { return &Gateway{ EUI: eui, Status: NewStatusStore(), Utilization: NewUtilization(), - Schedule: NewSchedule(), + Schedule: NewSchedule(ctx.WithField("GatewayEUI", eui)), } } diff --git a/core/router/gateway/gateway_test.go b/core/router/gateway/gateway_test.go index 28c41b009..ed8615170 100644 --- a/core/router/gateway/gateway_test.go +++ b/core/router/gateway/gateway_test.go @@ -4,11 +4,12 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestNewGateway(t *testing.T) { a := New(t) - gtw := NewGateway(types.GatewayEUI{1, 2, 3, 4, 5, 6, 7}) + gtw := NewGateway(GetLogger(t, "TestNewGateway"), types.GatewayEUI{1, 2, 3, 4, 5, 6, 7}) a.So(gtw, ShouldNotBeNil) } diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 07b514431..d3c6191cd 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -10,6 +10,7 @@ import ( router_pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/TheThingsNetwork/ttn/utils/toa" + "github.com/apex/log" ) // Schedule is used to schedule downlink transmissions @@ -28,8 +29,9 @@ type Schedule interface { } // NewSchedule creates a new Schedule -func NewSchedule() Schedule { +func NewSchedule(ctx log.Interface) Schedule { return &schedule{ + ctx: ctx, items: make(map[string]*scheduledItem), } } @@ -45,6 +47,7 @@ type scheduledItem struct { type schedule struct { sync.RWMutex + ctx log.Interface active bool offset int64 items map[string]*scheduledItem @@ -139,6 +142,8 @@ 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) + s.Lock() defer s.Unlock() if item, ok := s.items[id]; ok { @@ -156,6 +161,7 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro // Schedule transmission before the Deadline go func() { waitTime := item.time.Sub(time.Now().Add(Deadline)) + ctx.WithField("Remaining", waitTime).Debug("Schedule Downlink") <-time.After(waitTime) if s.downlink != nil { s.downlink <- item.payload @@ -163,9 +169,10 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro }() } else if s.downlink != nil { // Immediately send it + ctx.WithField("Overdue", time.Now().Add(Deadline).Sub(item.time)).Debug("Send Late Downlink") s.downlink <- item.payload } else { - // We can not send it + ctx.Debug("Unable to send Downlink") } return nil diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index fe565527f..b0f9da093 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -6,6 +6,7 @@ import ( "time" router_pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -88,7 +89,7 @@ func TestScheduleGetConflicts(t *testing.T) { func TestScheduleGetOption(t *testing.T) { a := New(t) - s := NewSchedule().(*schedule) + s := NewSchedule(nil).(*schedule) s.Sync(0) _, conflicts := s.GetOption(100, 100) @@ -99,7 +100,7 @@ func TestScheduleGetOption(t *testing.T) { func TestScheduleSchedule(t *testing.T) { a := New(t) - s := NewSchedule().(*schedule) + s := NewSchedule(GetLogger(t, "TestScheduleSchedule")).(*schedule) s.Sync(0) @@ -116,7 +117,7 @@ func TestScheduleSchedule(t *testing.T) { func TestScheduleSubscribe(t *testing.T) { a := New(t) - s := NewSchedule().(*schedule) + s := NewSchedule(GetLogger(t, "TestScheduleSubscribe")).(*schedule) s.Sync(0) Deadline = 1 // Extremely short deadline diff --git a/core/router/router.go b/core/router/router.go index 61d093ae7..0458af735 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -98,7 +98,7 @@ func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { r.gatewaysLock.Lock() defer r.gatewaysLock.Unlock() if _, ok := r.gateways[eui]; !ok { - r.gateways[eui] = gateway.NewGateway(eui) + r.gateways[eui] = gateway.NewGateway(r.Ctx, eui) } return r.gateways[eui] } diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index 57993d0e4..08cf3511f 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -17,8 +17,8 @@ import ( ) // newReferenceGateway returns a default gateway -func newReferenceGateway(region string) *gateway.Gateway { - gtw := gateway.NewGateway(types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) +func newReferenceGateway(t *testing.T, region string) *gateway.Gateway { + gtw := gateway.NewGateway(GetLogger(t, "ReferenceGateway"), types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) gtw.Status.Update(&pb_gateway.Status{ Region: region, }) From eded26e6ce30983bd76131b15a8c5f820d9de980 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 20 Jun 2016 14:40:25 +0200 Subject: [PATCH 1525/2266] Add List method to stores --- core/broker/application/store.go | 31 +++++++++++++++++++++++ core/broker/application/store_test.go | 19 +++++++++----- core/handler/application/store.go | 31 +++++++++++++++++++++++ core/handler/application/store_test.go | 25 ++++++++++++------- core/handler/device/store.go | 33 +++++++++++++++++++++++++ core/handler/device/store_test.go | 7 +++++- core/networkserver/device/store.go | 33 +++++++++++++++++++++++++ core/networkserver/device/store_test.go | 7 +++++- 8 files changed, 169 insertions(+), 17 deletions(-) diff --git a/core/broker/application/store.go b/core/broker/application/store.go index e171f9c7d..67aabec77 100644 --- a/core/broker/application/store.go +++ b/core/broker/application/store.go @@ -16,6 +16,8 @@ var ( // Store is used to store application configurations type Store interface { + // List all applications + List() ([]*Application, error) // Get the full information about an application Get(appEUI types.AppEUI) (*Application, error) // Set the given fields of an application. If fields empty, it sets all fields. @@ -37,6 +39,14 @@ type applicationStore struct { applications map[types.AppEUI]*Application } +func (s *applicationStore) List() ([]*Application, error) { + apps := make([]*Application, 0, len(s.applications)) + for _, application := range s.applications { + apps = append(apps, application) + } + return apps, nil +} + func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { if app, ok := s.applications[appEUI]; ok { return app, nil @@ -68,6 +78,27 @@ type redisApplicationStore struct { client *redis.Client } +func (s *redisApplicationStore) List() ([]*Application, error) { + var apps []*Application + keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisApplicationPrefix)).Result() + if err != nil { + return nil, err + } + for _, key := range keys { + res, err := s.client.HGetAllMap(key).Result() + if err != nil { + return nil, err + } + application := &Application{} + err = application.FromStringStringMap(res) + if err != nil { + return nil, err + } + apps = append(apps, application) + } + return apps, nil +} + func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() if err != nil { diff --git a/core/broker/application/store_test.go b/core/broker/application/store_test.go index 734b4637b..d1c57aef9 100644 --- a/core/broker/application/store_test.go +++ b/core/broker/application/store_test.go @@ -13,7 +13,7 @@ func getRedisClient() *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set - DB: 0, // use default DB + DB: 1, // use default DB }) } @@ -29,9 +29,11 @@ func TestApplicationStore(t *testing.T) { t.Logf("Testing %s store", name) + appEUI := types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1} + // Non-existing App err := s.Set(&Application{ - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: appEUI, HandlerID: "handler1ID", HandlerNetAddress: "handler1NetAddress:1234", }) @@ -39,7 +41,7 @@ func TestApplicationStore(t *testing.T) { // Existing App err = s.Set(&Application{ - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: appEUI, HandlerID: "handler1ID", HandlerNetAddress: "handler1NetAddress2:1234", }) @@ -49,15 +51,20 @@ func TestApplicationStore(t *testing.T) { a.So(err, ShouldNotBeNil) a.So(app, ShouldBeNil) - app, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + app, err = s.Get(appEUI) a.So(err, ShouldBeNil) a.So(app, ShouldNotBeNil) a.So(app.HandlerNetAddress, ShouldEqual, "handler1NetAddress2:1234") - err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + // List + apps, err := s.List() + a.So(err, ShouldBeNil) + a.So(apps, ShouldHaveLength, 1) + + err = s.Delete(appEUI) a.So(err, ShouldBeNil) - app, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + app, err = s.Get(appEUI) a.So(err, ShouldNotBeNil) a.So(app, ShouldBeNil) } diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 3fb4f1bac..97386d757 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -16,6 +16,8 @@ var ( // Store is used to store application configurations type Store interface { + // List all applications + List() ([]*Application, error) // Get the full information about a application Get(appEUI types.AppEUI) (*Application, error) // Set the given fields of a application. If fields empty, it sets all fields. @@ -37,6 +39,14 @@ type applicationStore struct { applications map[types.AppEUI]*Application } +func (s *applicationStore) List() ([]*Application, error) { + apps := make([]*Application, 0, len(s.applications)) + for _, application := range s.applications { + apps = append(apps, application) + } + return apps, nil +} + func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { if app, ok := s.applications[appEUI]; ok { return app, nil @@ -68,6 +78,27 @@ type redisApplicationStore struct { client *redis.Client } +func (s *redisApplicationStore) List() ([]*Application, error) { + var apps []*Application + keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisApplicationPrefix)).Result() + if err != nil { + return nil, err + } + for _, key := range keys { + res, err := s.client.HGetAllMap(key).Result() + if err != nil { + return nil, err + } + application := &Application{} + err = application.FromStringStringMap(res) + if err != nil { + return nil, err + } + apps = append(apps, application) + } + return apps, nil +} + func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() if err != nil { diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go index 71ac1fbd8..4a0a67b4a 100644 --- a/core/handler/application/store_test.go +++ b/core/handler/application/store_test.go @@ -13,7 +13,7 @@ func getRedisClient() *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set - DB: 0, // use default DB + DB: 1, // use default DB }) } @@ -29,29 +29,36 @@ func TestApplicationStore(t *testing.T) { t.Logf("Testing %s store", name) + appEUI := types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1} + // Get non-existing - dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + app, err := s.Get(appEUI) a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) + a.So(app, ShouldBeNil) // Create err = s.Set(&Application{ - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: appEUI, }) a.So(err, ShouldBeNil) // Get existing - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + app, err = s.Get(appEUI) + a.So(err, ShouldBeNil) + a.So(app, ShouldNotBeNil) + + // List + apps, err := s.List() a.So(err, ShouldBeNil) - a.So(dev, ShouldNotBeNil) + a.So(apps, ShouldHaveLength, 1) // Delete - err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + err = s.Delete(appEUI) a.So(err, ShouldBeNil) // Get deleted - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}) + app, err = s.Get(appEUI) a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) + a.So(app, ShouldBeNil) } } diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 65b75c9fe..5a6d291a1 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -16,6 +16,8 @@ var ( // Store is used to store device configurations type Store interface { + // List all devices + List() ([]*Device, error) // Get the full information about a device Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) // Set the given fields of a device. If fields empty, it sets all fields. @@ -37,6 +39,16 @@ type deviceStore struct { devices map[types.AppEUI]map[types.DevEUI]*Device } +func (s *deviceStore) List() ([]*Device, error) { + devices := make([]*Device, 0, len(s.devices)) + for _, app := range s.devices { + for _, device := range app { + devices = append(devices, device) + } + } + return devices, nil +} + func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { if app, ok := s.devices[appEUI]; ok { if dev, ok := app[devEUI]; ok { @@ -76,6 +88,27 @@ type redisDeviceStore struct { client *redis.Client } +func (s *redisDeviceStore) List() ([]*Device, error) { + var devices []*Device + keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() + if err != nil { + return nil, err + } + for _, key := range keys { + res, err := s.client.HGetAllMap(key).Result() + if err != nil { + return nil, err + } + device := &Device{} + err = device.FromStringStringMap(res) + if err != nil { + return nil, err + } + devices = append(devices, device) + } + return devices, nil +} + func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() if err != nil { diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go index aaeb1c7cb..2edd7fd28 100644 --- a/core/handler/device/store_test.go +++ b/core/handler/device/store_test.go @@ -13,7 +13,7 @@ func getRedisClient() *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set - DB: 0, // use default DB + DB: 1, // use default DB }) } @@ -55,6 +55,11 @@ func TestDeviceStore(t *testing.T) { }) a.So(err, ShouldBeNil) + // List + devices, err := s.List() + a.So(err, ShouldBeNil) + a.So(devices, ShouldHaveLength, 2) + // Delete err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) a.So(err, ShouldBeNil) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index b3c5ee6bf..0acb04af0 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -17,6 +17,8 @@ var ( // Store is used to store device configurations type Store interface { + // List all devices + List() ([]*Device, error) // Get the full information about a device Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) // Get a list of devices matching the DevAddr @@ -44,6 +46,16 @@ type deviceStore struct { byAddress map[types.DevAddr][]*Device } +func (s *deviceStore) List() ([]*Device, error) { + devices := make([]*Device, 0, len(s.devices)) + for _, app := range s.devices { + for _, device := range app { + devices = append(devices, device) + } + } + return devices, nil +} + func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { if app, ok := s.devices[appEUI]; ok { if dev, ok := app[devEUI]; ok { @@ -149,6 +161,27 @@ type redisDeviceStore struct { client *redis.Client } +func (s *redisDeviceStore) List() ([]*Device, error) { + var devices []*Device + keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() + if err != nil { + return nil, err + } + for _, key := range keys { + res, err := s.client.HGetAllMap(key).Result() + if err != nil { + return nil, err + } + device := &Device{} + err = device.FromStringStringMap(res) + if err != nil { + return nil, err + } + devices = append(devices, device) + } + return devices, nil +} + func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() if err != nil { diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go index 61d09d171..209ee2df7 100644 --- a/core/networkserver/device/store_test.go +++ b/core/networkserver/device/store_test.go @@ -13,7 +13,7 @@ func getRedisClient() *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set - DB: 0, // use default DB + DB: 1, // use default DB }) } @@ -92,6 +92,11 @@ func TestDeviceStore(t *testing.T) { a.So(err, ShouldBeNil) a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 3}) + // List + devices, err := s.List() + a.So(err, ShouldBeNil) + a.So(devices, ShouldHaveLength, 2) + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) a.So(err, ShouldBeNil) From 02f82e39a78ce83b1a801579aed08c06487fce8a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 20 Jun 2016 14:51:05 +0200 Subject: [PATCH 1526/2266] Timing of test cases --- core/broker/activation_test.go | 10 +++++----- core/broker/server_test.go | 2 ++ core/broker/uplink_test.go | 8 ++++---- core/handler/mqtt_test.go | 2 +- core/router/gateway/schedule_test.go | 8 ++++---- 5 files changed, 16 insertions(+), 14 deletions(-) diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index d4f08699b..44c1d6485 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -62,7 +62,7 @@ func TestHandleActivation(t *testing.T) { func TestDeduplicateActivation(t *testing.T) { a := New(t) - d := NewDeduplicator(10 * time.Millisecond).(*deduplicator) + d := NewDeduplicator(20 * time.Millisecond).(*deduplicator) payload := []byte{0x01, 0x02, 0x03} protocolMetadata := &protocol.RxMetadata{} @@ -80,14 +80,14 @@ func TestDeduplicateActivation(t *testing.T) { wg.Done() }() - <-time.After(5 * time.Millisecond) + <-time.After(10 * time.Millisecond) a.So(b.deduplicateActivation(activation2), ShouldBeNil) a.So(b.deduplicateActivation(activation3), ShouldBeNil) - wg.Wait() - - <-time.After(20 * time.Millisecond) + <-time.After(50 * time.Millisecond) a.So(b.deduplicateActivation(activation4), ShouldNotBeNil) + + wg.Wait() } diff --git a/core/broker/server_test.go b/core/broker/server_test.go index cb5af7203..1c26daf2a 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -156,6 +156,8 @@ func TestPublishRPC(t *testing.T) { a.So(err, ShouldBeNil) a.So(ack, ShouldNotBeNil) + <-time.After(10 * time.Millisecond) + a.So(len(dlch), ShouldEqual, 1) } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index fe0f21686..2173b1985 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -136,7 +136,7 @@ func TestHandleUplink(t *testing.T) { func TestDeduplicateUplink(t *testing.T) { a := New(t) - d := NewDeduplicator(10 * time.Millisecond).(*deduplicator) + d := NewDeduplicator(20 * time.Millisecond).(*deduplicator) payload := []byte{0x01, 0x02, 0x03} protocolMetadata := &protocol.RxMetadata{} @@ -155,14 +155,14 @@ func TestDeduplicateUplink(t *testing.T) { wg.Done() }() - <-time.After(5 * time.Millisecond) + <-time.After(10 * time.Millisecond) a.So(b.deduplicateUplink(uplink2), ShouldBeNil) a.So(b.deduplicateUplink(uplink3), ShouldBeNil) - <-time.After(10 * time.Millisecond) + <-time.After(50 * time.Millisecond) - a.So(b.deduplicateUplink(uplink4), ShouldBeNil) + a.So(b.deduplicateUplink(uplink4), ShouldNotBeNil) wg.Wait() } diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 1d5273758..a3cc0a75c 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -34,7 +34,7 @@ func TestHandleMQTT(t *testing.T) { c.PublishDownlink(appEUI, devEUI, mqtt.DownlinkMessage{ Payload: []byte{0xAA, 0xBC}, }).Wait() - <-time.After(10 * time.Millisecond) + <-time.After(50 * time.Millisecond) dev, _ := h.devices.Get(appEUI, devEUI) a.So(dev.NextDownlink, ShouldNotBeNil) diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index b0f9da093..3df1c8e4c 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -119,7 +119,7 @@ func TestScheduleSubscribe(t *testing.T) { a := New(t) s := NewSchedule(GetLogger(t, "TestScheduleSubscribe")).(*schedule) s.Sync(0) - Deadline = 1 // Extremely short deadline + Deadline = 1 * time.Millisecond // Very short deadline downlink1 := &router_pb.DownlinkMessage{Payload: []byte{1}} downlink2 := &router_pb.DownlinkMessage{Payload: []byte{2}} @@ -140,11 +140,11 @@ func TestScheduleSubscribe(t *testing.T) { } }() - id, _ := s.GetOption(300, 50) + id, _ := s.GetOption(30000, 50) s.Schedule(id, downlink1) - id, _ = s.GetOption(200, 50) + id, _ = s.GetOption(20000, 50) s.Schedule(id, downlink2) - id, _ = s.GetOption(400, 50) + id, _ = s.GetOption(40000, 50) s.Schedule(id, downlink3) go func() { From d6fdd7e3502d1fedd8983c4d86ae3e216520952b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 20 Jun 2016 15:27:38 +0200 Subject: [PATCH 1527/2266] Fix Go Vet warnings --- core/broker/server.go | 8 +++++--- core/handler/convert_lorawan_test.go | 21 +++++++++++---------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/core/broker/server.go b/core/broker/server.go index 413d7c868..0f7521b93 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -15,12 +15,14 @@ type brokerRPC struct { broker Broker } +var grpcErrf = grpc.Errorf // To make go vet stop complaining + func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { md, ok := metadata.FromContext(ctx) // TODO: Check OK id, ok := md["id"] if !ok || len(id) < 1 { - err = grpc.Errorf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"id\" in context") + err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"id\" in context") return } callerID = id[0] @@ -29,12 +31,12 @@ func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { } token, ok := md["token"] if !ok || len(token) < 1 { - err = grpc.Errorf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"token\" in context") + err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"token\" in context") return } if token[0] != "token" { // TODO: Validate Token - err = grpc.Errorf(codes.Unauthenticated, "ttn/broker: Caller not authorized") + err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller not authorized") return } diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index 426e89d2f..9c7ba17f3 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -14,12 +14,13 @@ import ( . "github.com/smartystreets/assertions" ) +var testDevEUI = types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} +var testAppEUI = types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { - devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} - appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} ttnUp := &pb_broker.DeduplicatedUplinkMessage{ - DevEui: &devEUI, - AppEui: &appEUI, + DevEui: &testDevEUI, + AppEui: &testAppEUI, Payload: payload, ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ Lorawan: &pb_lorawan.Metadata{ @@ -38,8 +39,8 @@ func TestConvertFromLoRaWAN(t *testing.T) { Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, } h.devices.Set(&device.Device{ - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: testDevEUI, + AppEUI: testAppEUI, }) ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x46, 0x55, 0x23, 0xf4, 0xf8, 0x45}) err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) @@ -50,8 +51,8 @@ func TestConvertFromLoRaWAN(t *testing.T) { func buildLorawanDownlink(payload []byte) (*mqtt.DownlinkMessage, *pb_broker.DownlinkMessage) { appDown := &mqtt.DownlinkMessage{ - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: testDevEUI, + AppEUI: testAppEUI, Payload: []byte{0xaa, 0xbc}, } ttnDown := &pb_broker.DownlinkMessage{ @@ -74,8 +75,8 @@ func TestConvertToLoRaWAN(t *testing.T) { Component: &core.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, } h.devices.Set(&device.Device{ - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8}, + DevEUI: testDevEUI, + AppEUI: testAppEUI, }) appDown, ttnDown := buildLorawanDownlink([]byte{0xaa, 0xbc}) err := h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) From ad748bac44411ac0f851164c94ed2310a05930f8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 22 Jun 2016 08:53:47 +0200 Subject: [PATCH 1528/2266] OTAA devices must register in the networkserver --- core/networkserver/device/store.go | 25 ++++++++--------- core/networkserver/device/store_test.go | 19 ++++++++++++- core/networkserver/networkserver.go | 10 +++++++ core/networkserver/networkserver_test.go | 34 +++++++++++++++++++++++- 4 files changed, 74 insertions(+), 14 deletions(-) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 0acb04af0..19c09fa4b 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -93,7 +93,7 @@ func (s *deviceStore) Set(new *Device, fields ...string) error { s.devices[new.AppEUI] = map[types.DevEUI]*Device{new.DevEUI: new} } - if !new.DevAddr.IsEmpty() { + if !new.DevAddr.IsEmpty() && !new.NwkSKey.IsEmpty() { if devices, ok := s.byAddress[new.DevAddr]; ok { var exists bool for _, candidate := range devices { @@ -115,12 +115,7 @@ func (s *deviceStore) Set(new *Device, fields ...string) error { func (s *deviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { dev, err := s.Get(appEUI, devEUI) - if err == ErrNotFound { - dev = &Device{ - AppEUI: appEUI, - DevEUI: devEUI, - } - } else if err != nil { + if err != nil { return err } dev.DevAddr = devAddr @@ -262,7 +257,7 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { } s.client.HMSetMap(key, dmap) - if !new.DevAddr.IsEmpty() { + if !new.DevAddr.IsEmpty() && !new.NwkSKey.IsEmpty() { err := s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, new.DevAddr), key).Err() if err != nil { return err @@ -274,7 +269,15 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) - var dmap map[string]string + + // Find existing device + exists, err := s.client.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return ErrNotFound + } // Check for old DevAddr if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { @@ -289,8 +292,6 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de // Update Device dev := &Device{ - AppEUI: appEUI, - DevEUI: devEUI, DevAddr: devAddr, NwkSKey: nwkSKey, FCntUp: 0, @@ -298,7 +299,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de } // Don't touch Utilization and Options - dmap, err := dev.ToStringStringMap("dev_eui", "app_eui", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") + dmap, err := dev.ToStringStringMap("dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") // Register Device err = s.client.HMSetMap(key, dmap).Err() diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go index 209ee2df7..32d9e4e17 100644 --- a/core/networkserver/device/store_test.go +++ b/core/networkserver/device/store_test.go @@ -34,6 +34,7 @@ func TestDeviceStore(t *testing.T) { DevAddr: types.DevAddr{0, 0, 0, 1}, DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, }) a.So(err, ShouldBeNil) @@ -42,6 +43,7 @@ func TestDeviceStore(t *testing.T) { DevAddr: types.DevAddr{0, 0, 0, 1}, DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, }) a.So(err, ShouldBeNil) @@ -57,6 +59,7 @@ func TestDeviceStore(t *testing.T) { DevAddr: types.DevAddr{0, 0, 0, 3}, DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, }) a.So(err, ShouldBeNil) @@ -71,6 +74,7 @@ func TestDeviceStore(t *testing.T) { DevAddr: types.DevAddr{0, 0, 0, 3}, DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2}, }) res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) @@ -123,13 +127,26 @@ func TestDeviceActivate(t *testing.T) { t.Logf("Testing %s store", name) - // Activate a device for the first time + // Device not registered err := s.Activate( types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevAddr{0, 0, 1, 1}, types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, ) + a.So(err, ShouldNotBeNil) + + // Device registered + s.Set(&Device{ + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + }) + err = s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 1}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + ) a.So(err, ShouldBeNil) // It should register the device diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index c118b083c..6a3848045 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -85,6 +85,7 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) dev := &pb.DevicesResponse_Device{ AppEui: &device.AppEUI, + AppId: device.AppID, DevEui: &device.DevEUI, NwkSKey: &device.NwkSKey, StoredFCnt: device.FCntUp, @@ -109,6 +110,15 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes } func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { + if activation.AppEui == nil || activation.DevEui == nil { + return nil, errors.New("ttn/networkserver: Activation missing AppEUI or DevEUI") + } + dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) + if err != nil { + return nil, err + } + activation.AppId = dev.AppID + // Build activation metadata if not present if meta := activation.GetActivationMetadata(); meta == nil { activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 192dcd1c6..4a2db3db1 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -75,6 +75,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: getDevAddr(1, 2, 3, 4), AppEUI: types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), DevEUI: types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, FCntUp: 5, }) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ @@ -106,6 +107,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: getDevAddr(5, 6, 7, 8), AppEUI: types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), DevEUI: types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), + NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, FCntUp: 5, Options: device.Options{ DisableFCntCheck: true, @@ -124,6 +126,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: getDevAddr(2, 2, 3, 4), AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, FCntUp: 5 + (2 << 16), Options: device.Options{ Uses32BitFCnt: true, @@ -141,6 +144,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: devAddr3, AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, FCntUp: (2 << 16) - 1, Options: device.Options{ Uses32BitFCnt: true, @@ -157,7 +161,17 @@ func TestHandleGetDevices(t *testing.T) { func TestHandlePrepareActivation(t *testing.T) { a := New(t) - ns := &networkServer{netID: [3]byte{0x00, 0x00, 0x13}, prefix: [4]byte{0x26, 0x00, 0x00, 0x00}, prefixLength: 7} + ns := &networkServer{ + netID: [3]byte{0x00, 0x00, 0x13}, + prefix: [4]byte{0x26, 0x00, 0x00, 0x00}, + prefixLength: 7, + devices: device.NewDeviceStore(), + } + + appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) + + // Device not registered resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ @@ -166,6 +180,20 @@ func TestHandlePrepareActivation(t *testing.T) { }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, }) + a.So(err, ShouldNotBeNil) + + // Device registered + ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI}) + resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + DevEui: &devEUI, + AppEui: &appEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) a.So(err, ShouldBeNil) devAddr := resp.ActivationMetadata.GetLorawan().DevAddr a.So(devAddr.IsEmpty(), ShouldBeFalse) @@ -186,6 +214,10 @@ func TestHandleActivate(t *testing.T) { ns := &networkServer{ devices: device.NewDeviceStore(), } + ns.devices.Set(&device.Device{ + AppEUI: types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), + DevEUI: types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), + }) _, err := ns.HandleActivate(&pb_handler.DeviceActivationResponse{}) a.So(err, ShouldNotBeNil) From 56020609532a23eab4f231ffecfefbc784fd063f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 22 Jun 2016 15:50:04 +0200 Subject: [PATCH 1529/2266] Move from AppEUIs to AppIDs --- api/broker/broker.pb.go | 219 +++++++--- api/broker/broker.proto | 2 + api/discovery/discovery.pb.go | 64 +-- api/discovery/discovery.proto | 3 + api/handler/handler.pb.go | 88 +++- api/handler/handler.proto | 1 + api/networkserver/networkserver.pb.go | 430 ++++++++++--------- api/networkserver/networkserver.proto | 20 +- core/broker/activation.go | 24 +- core/broker/activation_test.go | 21 +- core/broker/application/application.go | 78 ---- core/broker/application/application_test.go | 37 -- core/broker/application/store.go | 140 ------ core/broker/application/store_test.go | 71 --- core/broker/broker.go | 7 +- core/broker/broker_test.go | 26 +- core/broker/uplink.go | 19 +- core/broker/uplink_test.go | 15 +- core/discovery/handler_discovery.go | 114 +++++ core/discovery/handler_discovery_test.go | 107 +++++ core/handler/activation.go | 21 +- core/handler/activation_test.go | 6 +- core/handler/application/application.go | 36 +- core/handler/application/application_test.go | 19 +- core/handler/application/store.go | 30 +- core/handler/application/store_test.go | 13 +- core/handler/convert_fields.go | 2 +- core/handler/convert_fields_test.go | 12 +- core/handler/device/device.go | 6 + core/handler/device/device_test.go | 2 + core/handler/handler.go | 17 +- core/handler/uplink_test.go | 4 +- core/networkserver/device/device.go | 24 +- core/networkserver/device/device_test.go | 4 + core/networkserver/networkserver.go | 4 +- mqtt/types.go | 3 + 36 files changed, 897 insertions(+), 792 deletions(-) delete mode 100644 core/broker/application/application.go delete mode 100644 core/broker/application/application_test.go delete mode 100644 core/broker/application/store.go delete mode 100644 core/broker/application/store_test.go create mode 100644 core/discovery/handler_discovery.go create mode 100644 core/discovery/handler_discovery_test.go diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 5e0b7cb1c..e430e43d3 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -160,6 +160,7 @@ type DeduplicatedUplinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ServerTime int64 `protobuf:"varint,23,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"` @@ -241,6 +242,7 @@ type DeduplicatedDeviceActivationRequest struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` @@ -1152,6 +1154,12 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { } i += n13 } + if len(m.AppId) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } if m.ProtocolMetadata != nil { data[i] = 0xaa i++ @@ -1335,6 +1343,12 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error } i += n22 } + if len(m.AppId) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } if m.ProtocolMetadata != nil { data[i] = 0xaa i++ @@ -1778,6 +1792,10 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() n += 2 + l + sovBroker(uint64(l)) @@ -1849,6 +1867,10 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() n += 2 + l + sovBroker(uint64(l)) @@ -2849,6 +2871,35 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) @@ -3385,6 +3436,35 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) @@ -4367,75 +4447,76 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1117 bytes of a gzipped FileDescriptorProto + // 1134 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4d, 0x6f, 0xe3, 0x44, - 0x18, 0x26, 0xcd, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xf4, 0x23, 0x6e, 0xa8, 0xda, 0x62, 0x10, 0x2a, - 0x1f, 0x9b, 0xb0, 0x41, 0x65, 0x55, 0x21, 0x58, 0xa5, 0xdb, 0x55, 0x59, 0xa4, 0x2c, 0x2b, 0x6f, + 0x18, 0x26, 0xc9, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xb4, 0x69, 0xdc, 0x50, 0xb5, 0xc5, 0x20, 0x54, + 0x3e, 0x36, 0x61, 0x83, 0xca, 0xaa, 0x42, 0xb0, 0x4a, 0xb7, 0xab, 0x52, 0xa4, 0x2c, 0x2b, 0x6f, 0x7a, 0xe1, 0x12, 0x39, 0xf6, 0x34, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x6e, 0xff, 0x03, 0x12, - 0x17, 0x0e, 0x1c, 0xb8, 0xf0, 0x4b, 0x10, 0xe2, 0xc2, 0x91, 0x33, 0x07, 0x84, 0xe0, 0xc2, 0xcf, - 0x60, 0x3c, 0x9e, 0xf1, 0x47, 0x9a, 0x6c, 0xbb, 0xcb, 0x72, 0x40, 0xea, 0xc1, 0x8a, 0xe7, 0xfd, - 0x78, 0x3c, 0x7e, 0xde, 0xf7, 0x19, 0xbf, 0x81, 0x7b, 0x43, 0xc2, 0x46, 0xc1, 0xa0, 0x69, 0xb9, - 0xa7, 0xad, 0xde, 0x08, 0xf7, 0x46, 0xc4, 0x19, 0xd2, 0xc7, 0x98, 0x9d, 0xbb, 0xfe, 0x49, 0x8b, - 0x31, 0xa7, 0x65, 0x7a, 0xa4, 0x35, 0xf0, 0xdd, 0x13, 0xec, 0xcb, 0x9f, 0xa6, 0xe7, 0xbb, 0xcc, - 0x45, 0x85, 0x68, 0xd5, 0xb8, 0x93, 0x02, 0x18, 0xba, 0x43, 0xb7, 0x25, 0xdc, 0x83, 0xe0, 0x58, - 0xac, 0xc4, 0x42, 0xdc, 0x45, 0x69, 0x99, 0xf0, 0x99, 0xcf, 0xe3, 0x97, 0x0c, 0xff, 0xf8, 0x3a, - 0xe1, 0x22, 0xd4, 0x72, 0xc7, 0xf1, 0x8d, 0x4c, 0xde, 0xbb, 0x4e, 0xf2, 0xd0, 0x64, 0xf8, 0xdc, - 0xbc, 0x50, 0xbf, 0x51, 0xaa, 0xfe, 0xf3, 0x1c, 0x54, 0x0e, 0xdc, 0x73, 0x67, 0x4c, 0x9c, 0x93, - 0x2f, 0x3c, 0x46, 0x5c, 0x07, 0x6d, 0x02, 0x10, 0x1b, 0x3b, 0x8c, 0x1c, 0x13, 0xec, 0x6b, 0xb9, - 0xed, 0xdc, 0xce, 0xa2, 0x91, 0xb2, 0xa0, 0x2f, 0xa1, 0x28, 0x31, 0xfa, 0x38, 0x20, 0xda, 0x1c, - 0x0f, 0x28, 0xed, 0xef, 0xfd, 0xf6, 0xfb, 0xd6, 0xee, 0x55, 0xdb, 0xb0, 0x5c, 0x1f, 0xb7, 0xd8, - 0x85, 0x87, 0x69, 0xf3, 0x30, 0x42, 0x78, 0x78, 0xf4, 0xc8, 0x00, 0x89, 0xf6, 0x30, 0x20, 0x68, - 0x05, 0x6e, 0xd3, 0x30, 0x4a, 0xcb, 0x73, 0xd4, 0xb2, 0x11, 0x2d, 0x50, 0x03, 0x16, 0x6c, 0x6c, - 0xda, 0x7c, 0x8f, 0x58, 0xbb, 0xc5, 0x1d, 0x79, 0x23, 0x5e, 0xa3, 0x7d, 0xa8, 0x2a, 0x36, 0xfa, - 0x96, 0xeb, 0x1c, 0x93, 0xa1, 0x76, 0x9b, 0x87, 0x14, 0xdb, 0xeb, 0xcd, 0x98, 0xa5, 0xde, 0xb3, - 0x07, 0xc2, 0x13, 0xf8, 0x66, 0xf8, 0x86, 0x46, 0x45, 0x79, 0x22, 0x33, 0xba, 0x0f, 0x15, 0xf5, - 0x46, 0x12, 0xa2, 0x20, 0x20, 0xb4, 0xa6, 0x22, 0x6b, 0x12, 0xa1, 0x2c, 0x1d, 0x91, 0x55, 0xff, - 0x26, 0x0f, 0xe5, 0x23, 0x2f, 0xe4, 0xb0, 0x8b, 0x29, 0x35, 0x87, 0x18, 0x69, 0x30, 0xef, 0x99, - 0x17, 0x63, 0xd7, 0xb4, 0x05, 0x83, 0x25, 0x43, 0x2d, 0xd1, 0x63, 0x98, 0xb7, 0xf1, 0x99, 0xa0, - 0xae, 0x28, 0xa8, 0xdb, 0xe5, 0xd4, 0xdd, 0x7d, 0x01, 0xea, 0x0e, 0xf0, 0x59, 0x48, 0x5b, 0x81, - 0xa3, 0x84, 0x94, 0x71, 0x3c, 0xd3, 0xf3, 0x04, 0x5e, 0xe9, 0xa5, 0xf0, 0x3a, 0x9e, 0x27, 0xf0, - 0x38, 0x4a, 0x88, 0xd7, 0x81, 0xa5, 0x98, 0xd0, 0x53, 0xcc, 0x4c, 0xdb, 0x64, 0xa6, 0xb6, 0x2a, - 0xf8, 0x58, 0x49, 0x28, 0x35, 0x9e, 0x75, 0xa5, 0xcf, 0xa8, 0x29, 0xa3, 0xb2, 0xa0, 0x4f, 0xa1, - 0xa6, 0xf8, 0x8c, 0x11, 0xd6, 0x04, 0xc2, 0x72, 0xcc, 0x68, 0x0a, 0xa0, 0x2a, 0x6d, 0x71, 0x7e, - 0x07, 0x6a, 0xb6, 0xec, 0xc9, 0xbe, 0x2b, 0x9a, 0x92, 0x6a, 0x5b, 0xdb, 0x79, 0x9e, 0xbf, 0xd6, - 0x94, 0xda, 0xcc, 0xf6, 0xac, 0x51, 0xb5, 0x33, 0x6b, 0xaa, 0x7f, 0x3d, 0x07, 0x55, 0x15, 0xf3, - 0xff, 0xaf, 0xc9, 0x7d, 0xa8, 0x4e, 0x10, 0x22, 0x2b, 0x32, 0x8b, 0x8f, 0x4a, 0x96, 0x0f, 0x3d, - 0x00, 0x8d, 0x6f, 0x91, 0x58, 0xb8, 0x63, 0x31, 0x72, 0x16, 0xf5, 0x30, 0xa6, 0x1e, 0x67, 0xea, - 0x79, 0xb4, 0x4c, 0x79, 0x6c, 0xf1, 0x85, 0x1e, 0xfb, 0x63, 0x1e, 0xd6, 0x0f, 0xb0, 0x1d, 0x70, - 0x69, 0x58, 0xbc, 0xc6, 0xf6, 0x8d, 0x46, 0xae, 0xd0, 0x48, 0xfe, 0xda, 0x1a, 0xd9, 0x82, 0x22, - 0xc5, 0xfe, 0x19, 0xf6, 0xfb, 0x8c, 0x9c, 0x62, 0xad, 0x2e, 0x8e, 0x45, 0x88, 0x4c, 0x3d, 0x6e, - 0x41, 0x07, 0xb0, 0xe4, 0xcb, 0x12, 0xf7, 0x19, 0x3e, 0xf5, 0xc6, 0x1c, 0x80, 0xab, 0x28, 0xdc, - 0x63, 0x7d, 0xb2, 0x7c, 0xb2, 0x22, 0x46, 0x4d, 0x65, 0xf4, 0x64, 0x82, 0xfe, 0x77, 0x1e, 0xea, - 0x97, 0x3b, 0xe7, 0xab, 0x00, 0x53, 0x76, 0x53, 0xbf, 0x7f, 0x73, 0xc6, 0x75, 0x61, 0xd9, 0x8c, - 0x19, 0x4d, 0x20, 0xea, 0x02, 0x62, 0x23, 0xd9, 0x44, 0x42, 0x7b, 0x8c, 0x85, 0xcc, 0x4b, 0xb6, - 0x57, 0x71, 0x64, 0xfe, 0x70, 0x0b, 0xde, 0x4c, 0x8b, 0xf5, 0xa6, 0xec, 0xff, 0x81, 0x6c, 0x5f, - 0x71, 0xd9, 0x27, 0x4e, 0x01, 0xed, 0xd2, 0x29, 0xd0, 0x9d, 0x7d, 0x0a, 0x6c, 0xc7, 0x8d, 0x31, - 0xe3, 0xcb, 0x30, 0xe5, 0x38, 0x40, 0x50, 0x7b, 0x1a, 0x0c, 0xa8, 0xe5, 0x93, 0x01, 0x96, 0xfd, - 0xa0, 0xaf, 0xc2, 0x32, 0xe7, 0x59, 0x34, 0x4d, 0xd8, 0x47, 0xca, 0x7c, 0x17, 0x56, 0xb2, 0x66, - 0xf9, 0xb9, 0x59, 0x87, 0x05, 0x59, 0x54, 0xca, 0xfb, 0x27, 0xcf, 0x87, 0xcb, 0xf9, 0xa8, 0x3c, - 0x54, 0xdf, 0x85, 0x86, 0x81, 0x87, 0x84, 0x32, 0xec, 0xa7, 0x52, 0x55, 0xdf, 0xd5, 0x93, 0x6e, - 0x88, 0x86, 0x52, 0x59, 0x56, 0xfd, 0x1e, 0x6c, 0x1c, 0x39, 0xfe, 0x4b, 0x24, 0x56, 0xa1, 0xfc, - 0x94, 0x99, 0x2c, 0x88, 0xf7, 0xfc, 0x53, 0x1e, 0x0a, 0x91, 0x05, 0xe9, 0x50, 0x08, 0xc4, 0xd7, - 0x4a, 0xe4, 0x14, 0xdb, 0xd0, 0x0c, 0x87, 0x75, 0x83, 0x93, 0x40, 0x0d, 0xe9, 0x41, 0x2d, 0x28, - 0x47, 0x77, 0xfd, 0xc0, 0x21, 0x1c, 0x41, 0xcc, 0xc2, 0xd9, 0xd0, 0x52, 0x14, 0x70, 0x24, 0xfc, - 0xe8, 0x6d, 0x3e, 0xc8, 0x4a, 0xd5, 0xc9, 0x2f, 0x69, 0x3a, 0x36, 0xf6, 0xa1, 0xf7, 0xa1, 0x98, - 0x14, 0x9b, 0xca, 0x16, 0x4d, 0x87, 0xa6, 0xdd, 0x68, 0x0f, 0x52, 0xad, 0x41, 0xd5, 0x5e, 0xd6, - 0x2e, 0x25, 0x2d, 0xa5, 0xa2, 0xe4, 0x86, 0x3e, 0x81, 0x95, 0x74, 0xaa, 0x69, 0x59, 0xd8, 0xe3, - 0xd2, 0x97, 0xfd, 0x98, 0x4e, 0x4e, 0xb5, 0x2d, 0xed, 0xc8, 0x30, 0xf4, 0x11, 0x94, 0xed, 0xf8, - 0xc4, 0x08, 0xc7, 0x83, 0xa8, 0xb3, 0x6a, 0x22, 0xef, 0x09, 0xf6, 0xad, 0xf0, 0x4f, 0xc3, 0x98, - 0x67, 0x67, 0xc3, 0xd0, 0x7b, 0xb0, 0xc4, 0x07, 0x6d, 0x07, 0x5b, 0x1c, 0xa4, 0xef, 0xbb, 0x01, - 0xaf, 0x1b, 0xd5, 0xde, 0x11, 0x23, 0x7f, 0x2d, 0x76, 0x18, 0x91, 0x1d, 0xdd, 0x01, 0x94, 0x04, - 0x8f, 0x4c, 0xc7, 0x1e, 0x87, 0xd1, 0xef, 0x8a, 0xe8, 0x04, 0xe6, 0x33, 0xe9, 0x68, 0x7f, 0x3b, - 0x07, 0x85, 0x7d, 0xd1, 0xd8, 0x7c, 0x7e, 0x59, 0xec, 0x50, 0xea, 0x5a, 0x84, 0xbf, 0x01, 0x5a, - 0x55, 0xed, 0x9e, 0x19, 0x42, 0x1a, 0xb3, 0xbe, 0x85, 0x3b, 0xb9, 0x0f, 0x72, 0xe8, 0x73, 0x58, - 0x8c, 0xdb, 0x1d, 0x69, 0x2a, 0x72, 0x52, 0x01, 0x8d, 0x37, 0x12, 0x25, 0xcd, 0x98, 0x75, 0x38, - 0x56, 0x13, 0xe6, 0x9f, 0x04, 0x83, 0x31, 0xa1, 0x23, 0x34, 0xeb, 0x99, 0x8d, 0x05, 0x41, 0x5c, - 0xc7, 0x3a, 0xd9, 0xc9, 0x71, 0xe5, 0x2e, 0x48, 0x49, 0x62, 0xb4, 0x35, 0x5b, 0xaa, 0xd1, 0x0e, - 0xae, 0xd4, 0x72, 0xfb, 0xfb, 0x39, 0x28, 0x47, 0xb4, 0x74, 0x4d, 0x87, 0x3f, 0xcb, 0x47, 0x8f, - 0xa0, 0x94, 0x16, 0x28, 0x7a, 0x5d, 0x61, 0x4c, 0x51, 0x73, 0x63, 0x63, 0xba, 0x53, 0x6a, 0xfa, - 0x01, 0x2c, 0x4f, 0x11, 0x2e, 0xd2, 0x55, 0xd2, 0x6c, 0x55, 0x27, 0xaf, 0x8c, 0x0e, 0x61, 0x75, - 0xaa, 0x8c, 0xd1, 0x5b, 0x71, 0xe5, 0x9e, 0xa3, 0xf2, 0x14, 0x50, 0x1b, 0x16, 0x0f, 0x31, 0x93, - 0x3a, 0x8e, 0xcb, 0x9e, 0x51, 0x7a, 0xa3, 0x92, 0x35, 0xef, 0xd7, 0x7e, 0xf9, 0x73, 0x33, 0xf7, - 0x2b, 0xbf, 0xfe, 0xe0, 0xd7, 0x77, 0x7f, 0x6d, 0xbe, 0x36, 0x28, 0x88, 0xb3, 0xf8, 0xc3, 0x7f, - 0x02, 0x00, 0x00, 0xff, 0xff, 0xb7, 0xa4, 0x23, 0xb1, 0x39, 0x10, 0x00, 0x00, + 0x17, 0x0e, 0x1c, 0xf8, 0x21, 0x5c, 0x11, 0x17, 0x8e, 0x9c, 0x38, 0x70, 0x40, 0x08, 0x2e, 0xfc, + 0x0c, 0xc6, 0xe3, 0xf1, 0x57, 0x1a, 0xef, 0x76, 0x97, 0x45, 0x82, 0x15, 0x07, 0x2b, 0x9e, 0xf7, + 0xe3, 0xf1, 0xf8, 0x79, 0xdf, 0xe7, 0xf5, 0x04, 0xee, 0x8c, 0x09, 0x9b, 0x78, 0xa3, 0xb6, 0x61, + 0x9f, 0x75, 0x06, 0x13, 0x3c, 0x98, 0x10, 0x6b, 0x4c, 0x1f, 0x60, 0x76, 0x61, 0xbb, 0xa7, 0x1d, + 0xc6, 0xac, 0x8e, 0xee, 0x90, 0xce, 0xc8, 0xb5, 0x4f, 0xb1, 0x2b, 0x7f, 0xda, 0x8e, 0x6b, 0x33, + 0x1b, 0x15, 0x83, 0x55, 0xeb, 0x56, 0x02, 0x60, 0x6c, 0x8f, 0xed, 0x8e, 0x70, 0x8f, 0xbc, 0x13, + 0xb1, 0x12, 0x0b, 0x71, 0x17, 0xa4, 0xa5, 0xc2, 0x33, 0x9f, 0xc7, 0x2f, 0x19, 0xfe, 0xe1, 0x75, + 0xc2, 0x45, 0xa8, 0x61, 0x4f, 0xa3, 0x1b, 0x99, 0xbc, 0x77, 0x9d, 0xe4, 0xb1, 0xce, 0xf0, 0x85, + 0x7e, 0x19, 0xfe, 0x06, 0xa9, 0xea, 0x0f, 0x79, 0xa8, 0x1e, 0xd8, 0x17, 0xd6, 0x94, 0x58, 0xa7, + 0x9f, 0x39, 0x8c, 0xd8, 0x16, 0xda, 0x04, 0x20, 0x26, 0xb6, 0x18, 0x39, 0x21, 0xd8, 0x55, 0x72, + 0xdb, 0xb9, 0x9d, 0x25, 0x2d, 0x61, 0x41, 0x9f, 0x43, 0x49, 0x62, 0x0c, 0xb1, 0x47, 0x94, 0x3c, + 0x0f, 0x28, 0xef, 0xef, 0xfd, 0xf2, 0xeb, 0xd6, 0xee, 0xd3, 0xb6, 0x61, 0xd8, 0x2e, 0xee, 0xb0, + 0x4b, 0x07, 0xd3, 0xf6, 0x61, 0x80, 0x70, 0xff, 0xf8, 0x48, 0x03, 0x89, 0x76, 0xdf, 0x23, 0x68, + 0x15, 0x6e, 0x52, 0x3f, 0x4a, 0x29, 0x70, 0xd4, 0x8a, 0x16, 0x2c, 0x50, 0x0b, 0x16, 0x4d, 0xac, + 0x9b, 0x7c, 0x8f, 0x58, 0xb9, 0xc1, 0x1d, 0x05, 0x2d, 0x5a, 0xa3, 0x7d, 0xa8, 0x85, 0x6c, 0x0c, + 0x0d, 0xdb, 0x3a, 0x21, 0x63, 0xe5, 0x26, 0x0f, 0x29, 0x75, 0xd7, 0xdb, 0x11, 0x4b, 0x83, 0xc7, + 0xf7, 0x84, 0xc7, 0x73, 0x75, 0xff, 0x0d, 0xb5, 0x6a, 0xe8, 0x09, 0xcc, 0xe8, 0x2e, 0x54, 0xc3, + 0x37, 0x92, 0x10, 0x45, 0x01, 0xa1, 0xb4, 0x43, 0xb2, 0x66, 0x11, 0x2a, 0xd2, 0x11, 0x58, 0xd5, + 0xaf, 0x0a, 0x50, 0x39, 0x76, 0x7c, 0x0e, 0xfb, 0x98, 0x52, 0x7d, 0x8c, 0x91, 0x02, 0x0b, 0x8e, + 0x7e, 0x39, 0xb5, 0x75, 0x53, 0x30, 0x58, 0xd6, 0xc2, 0x25, 0x7a, 0x00, 0x0b, 0x26, 0x3e, 0x17, + 0xd4, 0x95, 0x04, 0x75, 0xbb, 0x9c, 0xba, 0xdb, 0xcf, 0x40, 0xdd, 0x01, 0x3e, 0xf7, 0x69, 0x2b, + 0x72, 0x14, 0x9f, 0x32, 0x8e, 0xa7, 0x3b, 0x8e, 0xc0, 0x2b, 0x3f, 0x17, 0x5e, 0xcf, 0x71, 0x04, + 0x1e, 0x47, 0xf1, 0xf1, 0x7a, 0xb0, 0x1c, 0x11, 0x7a, 0x86, 0x99, 0x6e, 0xea, 0x4c, 0x57, 0x1a, + 0x82, 0x8f, 0xd5, 0x98, 0x52, 0xed, 0x71, 0x5f, 0xfa, 0xb4, 0x7a, 0x68, 0x0c, 0x2d, 0xe8, 0x63, + 0xa8, 0x87, 0x7c, 0x46, 0x08, 0x6b, 0x02, 0x61, 0x25, 0x62, 0x34, 0x01, 0x50, 0x93, 0xb6, 0x28, + 0xbf, 0x07, 0x75, 0x53, 0xf6, 0xe4, 0xd0, 0x16, 0x4d, 0x49, 0x95, 0xad, 0xed, 0x02, 0xcf, 0x5f, + 0x6b, 0x4b, 0x6d, 0xa6, 0x7b, 0x56, 0xab, 0x99, 0xa9, 0x35, 0x55, 0xbf, 0xcc, 0x43, 0x2d, 0x8c, + 0xf9, 0xef, 0xd7, 0xe4, 0x2e, 0xd4, 0x66, 0x08, 0x91, 0x15, 0xc9, 0xe2, 0xa3, 0x9a, 0xe6, 0x43, + 0xf5, 0x40, 0xe1, 0x5b, 0x24, 0x06, 0xee, 0x19, 0x8c, 0x9c, 0x07, 0x3d, 0x8c, 0xa9, 0xc3, 0x99, + 0x7a, 0x12, 0x2d, 0x73, 0x1e, 0x5b, 0x7a, 0xa6, 0xc7, 0xfe, 0x5c, 0x80, 0xf5, 0x03, 0x6c, 0x7a, + 0x5c, 0x1a, 0x06, 0xaf, 0xb1, 0xf9, 0xb2, 0x68, 0xa4, 0x01, 0xfe, 0xdd, 0x90, 0x98, 0x4a, 0x45, + 0x8c, 0xc7, 0x9b, 0x7c, 0x75, 0x64, 0xfe, 0x73, 0xd2, 0x29, 0x5c, 0x5b, 0x3a, 0x5b, 0x50, 0xa2, + 0xd8, 0x3d, 0xc7, 0xee, 0x90, 0x91, 0x33, 0xac, 0x34, 0xc5, 0xb4, 0x84, 0xc0, 0x34, 0xe0, 0x16, + 0x74, 0x00, 0xcb, 0xae, 0xac, 0xfc, 0x90, 0xe1, 0x33, 0x67, 0xca, 0x01, 0xb8, 0xb8, 0xfc, 0x3d, + 0x36, 0x67, 0xab, 0x2a, 0x0b, 0xa5, 0xd5, 0xc3, 0x8c, 0x81, 0x4c, 0x50, 0xff, 0x2c, 0x40, 0xf3, + 0x6a, 0x43, 0x7d, 0xe1, 0x61, 0xca, 0xfe, 0x1f, 0x7d, 0x7f, 0x67, 0xf4, 0xf5, 0x61, 0x45, 0x8f, + 0x18, 0x8d, 0x21, 0x9a, 0x02, 0x62, 0x23, 0xde, 0x44, 0x4c, 0x7b, 0x84, 0x85, 0xf4, 0x2b, 0xb6, + 0x17, 0x31, 0x49, 0xbf, 0xbb, 0x01, 0xaf, 0x27, 0x35, 0xfc, 0xf2, 0x95, 0xfd, 0xdf, 0xab, 0xe6, + 0x17, 0xdc, 0x0d, 0x33, 0xc3, 0x41, 0xb9, 0x32, 0x1c, 0xfa, 0xd9, 0xc3, 0x61, 0x3b, 0xea, 0x97, + 0x8c, 0xef, 0xc8, 0x9c, 0x29, 0x81, 0xa0, 0xfe, 0xc8, 0x1b, 0x51, 0xc3, 0x25, 0x23, 0x2c, 0xdb, + 0x44, 0x6d, 0xc0, 0x0a, 0xa7, 0x5f, 0xf4, 0x92, 0xdf, 0x5e, 0xa1, 0xf9, 0x36, 0xac, 0xa6, 0xcd, + 0xf2, 0xe3, 0xb4, 0x0e, 0x8b, 0xb2, 0xd6, 0x94, 0xb7, 0x55, 0x81, 0x57, 0x67, 0x21, 0xa8, 0x1a, + 0x55, 0x77, 0xa1, 0xa5, 0xe1, 0x31, 0xa1, 0x0c, 0xbb, 0x89, 0xd4, 0xb0, 0x1d, 0x9b, 0x71, 0x93, + 0x04, 0x47, 0x58, 0x59, 0x6d, 0xf5, 0x0e, 0x6c, 0x1c, 0x5b, 0xee, 0x73, 0x24, 0xd6, 0xa0, 0xf2, + 0x88, 0xe9, 0xcc, 0x8b, 0xf6, 0xfc, 0x7d, 0x01, 0x8a, 0x81, 0x05, 0xa9, 0x50, 0xf4, 0xc4, 0xb7, + 0x4d, 0xe4, 0x94, 0xba, 0xd0, 0xf6, 0x8f, 0xf6, 0x1a, 0x27, 0x81, 0x6a, 0xd2, 0x83, 0x3a, 0x50, + 0x09, 0xee, 0x86, 0x9e, 0x45, 0x38, 0x82, 0x38, 0x39, 0xa7, 0x43, 0xcb, 0x41, 0xc0, 0xb1, 0xf0, + 0xa3, 0x37, 0xf9, 0xb1, 0x57, 0x8a, 0x51, 0x7e, 0x77, 0x93, 0xb1, 0x91, 0x0f, 0xbd, 0x0b, 0xa5, + 0xb8, 0xd8, 0x54, 0xb6, 0x68, 0x32, 0x34, 0xe9, 0x46, 0x7b, 0x90, 0x68, 0x0d, 0x1a, 0xee, 0x65, + 0xed, 0x4a, 0xd2, 0x72, 0x22, 0x4a, 0x6e, 0xe8, 0x23, 0x58, 0x4d, 0xa6, 0xea, 0x86, 0x81, 0x1d, + 0x3e, 0x11, 0x64, 0x3f, 0x26, 0x93, 0x13, 0x6d, 0x4b, 0x7b, 0x32, 0x0c, 0x7d, 0x00, 0x15, 0x33, + 0x1a, 0x24, 0xfe, 0x61, 0x22, 0xe8, 0xac, 0xba, 0xc8, 0x7b, 0x88, 0x5d, 0xc3, 0xff, 0x8b, 0x31, + 0xe5, 0xd9, 0xe9, 0x30, 0xf4, 0x0e, 0x2c, 0xf3, 0x63, 0xb9, 0x85, 0x0d, 0x0e, 0x32, 0x74, 0x6d, + 0x8f, 0xd7, 0x8d, 0x2a, 0x6f, 0x89, 0x3f, 0x08, 0xf5, 0xc8, 0xa1, 0x05, 0x76, 0x74, 0x0b, 0x50, + 0x1c, 0x3c, 0xd1, 0x2d, 0x73, 0xea, 0x47, 0xbf, 0x2d, 0xa2, 0x63, 0x98, 0x4f, 0xa4, 0xa3, 0xfb, + 0x75, 0x1e, 0x8a, 0xfb, 0xa2, 0xb1, 0xf9, 0x69, 0x67, 0xa9, 0x47, 0xa9, 0x6d, 0x10, 0xfe, 0x06, + 0xa8, 0x11, 0xb6, 0x7b, 0xea, 0xc8, 0xd2, 0xca, 0xfa, 0x44, 0xee, 0xe4, 0xde, 0xcb, 0xa1, 0x4f, + 0x61, 0x29, 0x6a, 0x77, 0xa4, 0x84, 0x91, 0xb3, 0x0a, 0x68, 0xbd, 0x16, 0x2b, 0x29, 0xe3, 0x64, + 0xc4, 0xb1, 0xda, 0xb0, 0xf0, 0xd0, 0x1b, 0x4d, 0x09, 0x9d, 0xa0, 0xac, 0x67, 0xb6, 0x16, 0x05, + 0x71, 0x3d, 0xe3, 0x74, 0x27, 0xc7, 0x95, 0xbb, 0x28, 0x25, 0x89, 0xd1, 0x56, 0xb6, 0x54, 0x83, + 0x1d, 0x3c, 0x55, 0xcb, 0xdd, 0x6f, 0xf3, 0x50, 0x09, 0x68, 0xe9, 0xeb, 0x16, 0x7f, 0x96, 0x8b, + 0x8e, 0xa0, 0x9c, 0x14, 0x28, 0x7a, 0x35, 0xc4, 0x98, 0xa3, 0xe6, 0xd6, 0xc6, 0x7c, 0xa7, 0xd4, + 0xf4, 0x3d, 0x58, 0x99, 0x23, 0x5c, 0xa4, 0x86, 0x49, 0xd9, 0xaa, 0x8e, 0x5f, 0x19, 0x1d, 0x42, + 0x63, 0xae, 0x8c, 0xd1, 0x1b, 0x51, 0xe5, 0x9e, 0xa0, 0xf2, 0x04, 0x50, 0x17, 0x96, 0x0e, 0x31, + 0x93, 0x3a, 0x8e, 0xca, 0x9e, 0x52, 0x7a, 0xab, 0x9a, 0x36, 0xef, 0xd7, 0x7f, 0xfc, 0x7d, 0x33, + 0xf7, 0x13, 0xbf, 0x7e, 0xe3, 0xd7, 0x37, 0x7f, 0x6c, 0xbe, 0x32, 0x2a, 0x8a, 0x59, 0xfc, 0xfe, + 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x40, 0x6b, 0x27, 0xc9, 0x67, 0x10, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index ed60d1f41..5e05a5473 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -47,6 +47,7 @@ message DeduplicatedUplinkMessage { bytes payload = 1; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; int64 server_time = 23; @@ -69,6 +70,7 @@ message DeduplicatedDeviceActivationRequest { bytes payload = 1; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; protocol.ActivationMetadata activation_metadata = 23; diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 5edfc07f4..c5ef5ab94 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -45,15 +45,19 @@ const ( // The value for PREFIX consists of 1 byte denoting the number of bits, // followed by the prefix and enough trailing bits to fill 4 octets Metadata_PREFIX Metadata_Key = 1 + // The value for APP_ID are the bytes of the AppID string + Metadata_APP_ID Metadata_Key = 3 ) var Metadata_Key_name = map[int32]string{ 0: "OTHER", 1: "PREFIX", + 3: "APP_ID", } var Metadata_Key_value = map[string]int32{ "OTHER": 0, "PREFIX": 1, + "APP_ID": 3, } func (x Metadata_Key) String() string { @@ -1349,35 +1353,35 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 470 bytes of a gzipped FileDescriptorProto + // 479 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x93, 0x4f, 0x6e, 0xd3, 0x40, - 0x14, 0xc6, 0xeb, 0x98, 0x54, 0xf6, 0x4b, 0x94, 0x46, 0x0f, 0x2a, 0xac, 0x80, 0x4a, 0xf0, 0x86, - 0xb2, 0xa8, 0x2d, 0xa5, 0x6c, 0x58, 0x20, 0x14, 0xa0, 0x14, 0x84, 0x5a, 0x90, 0x15, 0x21, 0x76, - 0x95, 0x6b, 0x3f, 0x25, 0xa3, 0x90, 0x99, 0xe0, 0x99, 0x04, 0xf9, 0x26, 0x9c, 0x85, 0x13, 0xb0, - 0xe4, 0x08, 0x08, 0xae, 0xc0, 0x01, 0x18, 0xff, 0x8b, 0x2d, 0x5a, 0x50, 0x16, 0x23, 0xcd, 0x7c, - 0xf3, 0x7b, 0xdf, 0x7b, 0xf9, 0x26, 0x86, 0x27, 0x53, 0xa6, 0x66, 0xab, 0x4b, 0x2f, 0x12, 0x0b, - 0x7f, 0x32, 0xa3, 0xc9, 0x8c, 0xf1, 0xa9, 0x3c, 0x27, 0xf5, 0x59, 0x24, 0x73, 0x5f, 0x29, 0xee, - 0x87, 0x4b, 0xe6, 0xc7, 0x4c, 0x46, 0x62, 0x4d, 0x49, 0x5a, 0xef, 0xbc, 0x65, 0x22, 0x94, 0x40, - 0x7b, 0x23, 0x0c, 0x8e, 0xb6, 0x71, 0xd2, 0xab, 0xa8, 0x74, 0x19, 0x58, 0x67, 0xa4, 0xc2, 0x38, - 0x54, 0x21, 0x3e, 0x04, 0x73, 0x4e, 0xa9, 0x63, 0x0c, 0x8d, 0xc3, 0xde, 0xe8, 0xb6, 0x57, 0x37, - 0xa9, 0x08, 0xef, 0x0d, 0xa5, 0x41, 0xc6, 0xe0, 0x2d, 0x68, 0xaf, 0xc3, 0x8f, 0x2b, 0x72, 0x5a, - 0x1a, 0xee, 0x06, 0xc5, 0xc1, 0xbd, 0x0b, 0xa6, 0x26, 0xd0, 0x86, 0xf6, 0xdb, 0xc9, 0xab, 0x93, - 0xa0, 0xbf, 0x83, 0x00, 0xbb, 0xef, 0x82, 0x93, 0x97, 0xaf, 0x3f, 0xf4, 0x0d, 0xf7, 0xb7, 0x01, - 0xdd, 0x31, 0xe7, 0x62, 0xc5, 0x23, 0x5a, 0x10, 0x57, 0xd8, 0x83, 0x16, 0x8b, 0xf3, 0x76, 0x76, - 0xa0, 0x77, 0x99, 0xa9, 0x12, 0x73, 0xe2, 0xb9, 0xa9, 0x1d, 0x14, 0x07, 0x1c, 0x42, 0x27, 0x26, - 0x19, 0x25, 0x6c, 0xa9, 0x98, 0xe0, 0x8e, 0x99, 0xdf, 0x35, 0x25, 0xbc, 0x0f, 0x5d, 0x49, 0xc9, - 0x9a, 0x45, 0x74, 0xc1, 0xc3, 0x05, 0x39, 0x37, 0x0a, 0xa4, 0xd4, 0xce, 0xb5, 0x84, 0x0f, 0x60, - 0xaf, 0x42, 0xf4, 0x2f, 0x92, 0x99, 0x51, 0x3b, 0xa7, 0x7a, 0xa5, 0xfc, 0xbe, 0x50, 0xf1, 0x1e, - 0x74, 0x38, 0xa9, 0x8b, 0x30, 0x8e, 0x13, 0x92, 0xd2, 0xe9, 0xe4, 0x10, 0x68, 0x69, 0x5c, 0x28, - 0xe8, 0x83, 0xb5, 0x28, 0xe3, 0x70, 0xf6, 0x87, 0xe6, 0x61, 0x67, 0x74, 0xf3, 0x9a, 0xa4, 0x82, - 0x0d, 0xe4, 0x3e, 0x82, 0xbd, 0x17, 0xe5, 0x7d, 0x40, 0x9f, 0x56, 0x24, 0xd5, 0x95, 0x81, 0x8d, - 0x2b, 0x03, 0xbb, 0x4f, 0x01, 0x4e, 0x49, 0x6d, 0x5f, 0x50, 0x86, 0xd9, 0xd2, 0x13, 0xe5, 0x61, - 0xba, 0xa7, 0xd0, 0xaf, 0xdb, 0xca, 0xa5, 0xe0, 0x92, 0xf0, 0x18, 0xac, 0xb2, 0x44, 0x6a, 0x8b, - 0x6c, 0xf6, 0xe6, 0x2b, 0x37, 0xdf, 0x26, 0xd8, 0x80, 0xa3, 0xaf, 0x06, 0xd8, 0x95, 0x53, 0x8a, - 0x47, 0x60, 0x55, 0x1c, 0xfe, 0xab, 0x78, 0x60, 0x79, 0xd9, 0x1f, 0x6c, 0x1c, 0xcd, 0xf1, 0x39, - 0x58, 0x55, 0x2d, 0x0e, 0x1a, 0xf8, 0x5f, 0x89, 0x0c, 0xee, 0x5c, 0x7b, 0x57, 0x8e, 0xfd, 0x18, - 0x4c, 0x9d, 0x05, 0xee, 0x37, 0x98, 0x3a, 0x9b, 0xff, 0x96, 0x8e, 0xb0, 0x4e, 0x21, 0x3d, 0x0b, - 0x79, 0x38, 0xa5, 0xe4, 0x59, 0xff, 0xdb, 0xcf, 0x03, 0xe3, 0xbb, 0x5e, 0x3f, 0xf4, 0xfa, 0xf2, - 0xeb, 0x60, 0xe7, 0x72, 0x37, 0xff, 0x16, 0x8e, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0x15, 0x47, - 0xb9, 0x40, 0x86, 0x03, 0x00, 0x00, + 0x14, 0xc6, 0xeb, 0x98, 0x54, 0xf6, 0x4b, 0x94, 0x5a, 0x0f, 0x2a, 0xac, 0x20, 0x95, 0xe0, 0x0d, + 0x61, 0x51, 0x5b, 0x4a, 0xd9, 0xb0, 0x40, 0x28, 0xd0, 0x52, 0x2a, 0xd4, 0x12, 0x59, 0x11, 0x62, + 0x17, 0xb9, 0xf6, 0x53, 0x62, 0x85, 0x8c, 0x83, 0x67, 0x1c, 0xe4, 0x9b, 0x70, 0x16, 0x4e, 0xc0, + 0x92, 0x23, 0x20, 0xb8, 0x02, 0x07, 0x60, 0xfc, 0x2f, 0xb6, 0x68, 0x41, 0x59, 0x8c, 0x34, 0xf3, + 0xcd, 0xef, 0x7b, 0xef, 0xe5, 0x9b, 0x18, 0x9e, 0xcf, 0x43, 0xb1, 0x48, 0xae, 0x6d, 0x3f, 0x5a, + 0x39, 0xd3, 0x05, 0x4d, 0x17, 0x21, 0x9b, 0xf3, 0x2b, 0x12, 0x9f, 0xa3, 0x78, 0xe9, 0x08, 0xc1, + 0x1c, 0x6f, 0x1d, 0x3a, 0x41, 0xc8, 0xfd, 0x68, 0x43, 0x71, 0x5a, 0xef, 0xec, 0x75, 0x1c, 0x89, + 0x08, 0xf5, 0xad, 0xd0, 0x3f, 0xde, 0xa5, 0x92, 0x5c, 0x85, 0xd3, 0x4a, 0x40, 0xbb, 0x24, 0xe1, + 0x05, 0x9e, 0xf0, 0xf0, 0x09, 0xa8, 0x4b, 0x4a, 0x4d, 0x65, 0xa0, 0x0c, 0x7b, 0xa3, 0xfb, 0x76, + 0xdd, 0xa4, 0x22, 0xec, 0xb7, 0x94, 0xba, 0x19, 0x83, 0xf7, 0xa0, 0xbd, 0xf1, 0x3e, 0x26, 0x64, + 0xb6, 0x24, 0xdc, 0x75, 0x8b, 0x83, 0x35, 0x04, 0x55, 0x12, 0xa8, 0x43, 0xfb, 0xdd, 0xf4, 0xcd, + 0x99, 0x6b, 0xec, 0x21, 0xc0, 0xfe, 0xc4, 0x3d, 0x7b, 0x7d, 0xf1, 0xc1, 0x50, 0xb2, 0xfd, 0x78, + 0x32, 0x99, 0x5d, 0x9c, 0x1a, 0xaa, 0xf5, 0x5b, 0x81, 0xee, 0x98, 0xb1, 0x28, 0x61, 0x3e, 0xad, + 0x88, 0x09, 0xec, 0x41, 0x2b, 0x0c, 0xf2, 0xd6, 0xba, 0x2b, 0x77, 0x59, 0x03, 0x11, 0x2d, 0x89, + 0xe5, 0x0d, 0x74, 0xb7, 0x38, 0xe0, 0x00, 0x3a, 0x01, 0x71, 0x3f, 0x0e, 0xd7, 0x22, 0x8c, 0x98, + 0xa9, 0xe6, 0x77, 0x4d, 0x09, 0x1f, 0x41, 0x97, 0x53, 0xbc, 0x09, 0x7d, 0x9a, 0x31, 0x6f, 0x45, + 0xe6, 0x9d, 0x02, 0x29, 0xb5, 0x2b, 0x29, 0xe1, 0x63, 0x38, 0xa8, 0x10, 0xf9, 0xeb, 0x78, 0x56, + 0xa8, 0x9d, 0x53, 0xbd, 0x52, 0x7e, 0x5f, 0xa8, 0xf8, 0x10, 0x3a, 0x8c, 0xc4, 0xcc, 0x0b, 0x82, + 0x98, 0x38, 0x37, 0x3b, 0x39, 0x04, 0x52, 0x1a, 0x17, 0x0a, 0x3a, 0xa0, 0xad, 0xca, 0x68, 0xcc, + 0xc3, 0x81, 0x3a, 0xec, 0x8c, 0xee, 0xde, 0x92, 0x9a, 0xbb, 0x85, 0xac, 0xa7, 0x70, 0x70, 0x5a, + 0xde, 0xbb, 0xf4, 0x29, 0x21, 0x2e, 0x6e, 0x0c, 0xac, 0xdc, 0x18, 0xd8, 0x7a, 0x01, 0x70, 0x4e, + 0x62, 0x77, 0x43, 0x19, 0x66, 0x4b, 0x4e, 0x94, 0x87, 0x69, 0x9d, 0x83, 0x51, 0xb7, 0xe5, 0xeb, + 0x88, 0x71, 0xc2, 0x13, 0xd0, 0x4a, 0x0b, 0x97, 0x25, 0xb2, 0xd9, 0x9b, 0x2f, 0xde, 0x7c, 0x1b, + 0x77, 0x0b, 0x8e, 0xbe, 0x2a, 0xa0, 0x57, 0x95, 0x52, 0x3c, 0x06, 0xad, 0xe2, 0xf0, 0x5f, 0xe6, + 0xbe, 0x66, 0x67, 0x7f, 0xb6, 0xb1, 0xbf, 0xc4, 0x57, 0xa0, 0x55, 0x5e, 0xec, 0x37, 0xf0, 0xbf, + 0x12, 0xe9, 0x3f, 0xb8, 0xf5, 0xae, 0x1c, 0xfb, 0x19, 0xa8, 0x32, 0x0b, 0x3c, 0x6c, 0x30, 0x75, + 0x36, 0xff, 0xb5, 0x8e, 0xb0, 0x4e, 0x21, 0xbd, 0xf4, 0x98, 0x37, 0xa7, 0xf8, 0xa5, 0xf1, 0xed, + 0xe7, 0x91, 0xf2, 0x5d, 0xae, 0x1f, 0x72, 0x7d, 0xf9, 0x75, 0xb4, 0x77, 0xbd, 0x9f, 0x7f, 0x17, + 0x27, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x71, 0x0b, 0x82, 0xa6, 0x92, 0x03, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index ab742c8b5..7d2d2f5df 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -11,6 +11,9 @@ message Metadata { // The value for PREFIX consists of 1 byte denoting the number of bits, // followed by the prefix and enough trailing bits to fill 4 octets PREFIX = 1; + + // The value for APP_ID are the bytes of the AppID string + APP_ID = 3; } Key key = 1; bytes value = 2; diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 8c463b7ec..7c08bbb28 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -40,6 +40,7 @@ const _ = proto.ProtoPackageIsVersion2 type DeviceActivationResponse struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + AppId string `protobuf:"bytes,2,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` DownlinkOption *broker.DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` } @@ -242,6 +243,12 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { i = encodeVarintHandler(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if len(m.AppId) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } if m.DownlinkOption != nil { data[i] = 0x5a i++ @@ -337,6 +344,10 @@ func (m *DeviceActivationResponse) Size() (n int) { if l > 0 { n += 1 + l + sovHandler(uint64(l)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } if m.DownlinkOption != nil { l = m.DownlinkOption.Size() n += 1 + l + sovHandler(uint64(l)) @@ -433,6 +444,35 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) @@ -726,27 +766,29 @@ var ( ) var fileDescriptorHandler = []byte{ - // 349 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0xcf, 0x4a, 0xf3, 0x40, - 0x10, 0xff, 0x72, 0x69, 0xfb, 0x6d, 0xbf, 0xaf, 0x95, 0x15, 0x6a, 0x08, 0x52, 0xb4, 0x27, 0x41, - 0x4c, 0xa0, 0x0a, 0x22, 0x1e, 0x44, 0x29, 0xea, 0xa5, 0x0a, 0xb1, 0x27, 0x2f, 0x65, 0x9b, 0x1d, - 0x9a, 0xa5, 0xe9, 0x6e, 0x4c, 0x26, 0x2d, 0xbe, 0x89, 0x0f, 0xe4, 0xc1, 0xa3, 0x8f, 0x20, 0xfa, - 0x22, 0xae, 0xc9, 0x36, 0x52, 0x8b, 0xd0, 0xc3, 0xb0, 0x3b, 0xb3, 0xbf, 0x7f, 0xcc, 0x92, 0x93, - 0xb1, 0xc0, 0x30, 0x1b, 0xb9, 0x81, 0x9a, 0x7a, 0x83, 0x10, 0x06, 0xa1, 0x90, 0xe3, 0xf4, 0x06, - 0x70, 0xae, 0x92, 0x89, 0x87, 0x28, 0x3d, 0x16, 0x0b, 0x2f, 0x64, 0x92, 0x47, 0x90, 0x2c, 0x4e, - 0x37, 0x4e, 0x14, 0x2a, 0x5a, 0x35, 0xad, 0x73, 0xb0, 0x8e, 0x86, 0xae, 0x82, 0xe7, 0x1c, 0xaf, - 0x03, 0x1f, 0x25, 0x6a, 0xa2, 0x1d, 0x8b, 0xc3, 0x10, 0x4f, 0xd7, 0x21, 0xe6, 0xd0, 0x40, 0x45, - 0xe5, 0xa5, 0x20, 0x77, 0x9e, 0x2d, 0x62, 0xf7, 0x60, 0x26, 0x02, 0x38, 0x0f, 0x50, 0xcc, 0x18, - 0x0a, 0x25, 0x7d, 0x48, 0x63, 0x25, 0x53, 0xa0, 0x36, 0xa9, 0xc6, 0xec, 0x31, 0x52, 0x8c, 0xdb, - 0xd6, 0x8e, 0xb5, 0xf7, 0xcf, 0x5f, 0xb4, 0xf4, 0x8c, 0x34, 0xb9, 0x9a, 0xcb, 0x48, 0xc8, 0xc9, - 0x50, 0xc5, 0x5f, 0x24, 0xbb, 0xae, 0x11, 0xf5, 0x6e, 0xcb, 0x35, 0xd9, 0x7a, 0xe6, 0xf9, 0x36, - 0x7f, 0xf5, 0x1b, 0x7c, 0xa9, 0xa7, 0x7d, 0xb2, 0xc9, 0x4a, 0xc3, 0xe1, 0x14, 0x90, 0x71, 0x86, - 0xcc, 0xde, 0xca, 0x45, 0xb6, 0xdd, 0x32, 0xe5, 0x77, 0xaa, 0xbe, 0xc1, 0xf8, 0x94, 0xad, 0xcc, - 0x3a, 0x4d, 0xf2, 0xff, 0x0e, 0x19, 0x66, 0xa9, 0x0f, 0x0f, 0x19, 0xa4, 0xd8, 0xa9, 0x91, 0x4a, - 0x31, 0xe8, 0x02, 0xa9, 0x5e, 0x17, 0x3f, 0x42, 0xef, 0x49, 0xcd, 0xe8, 0x01, 0xdd, 0x2f, 0x83, - 0x02, 0xcf, 0xe2, 0x48, 0x04, 0x7a, 0xc8, 0x57, 0x37, 0x91, 0xab, 0x39, 0xbb, 0xee, 0xe2, 0x8f, - 0x7f, 0xdb, 0x55, 0xf7, 0x92, 0x34, 0x8c, 0x4d, 0x9f, 0x49, 0x36, 0xd6, 0x6e, 0x47, 0xe4, 0xef, - 0x15, 0x60, 0x91, 0x82, 0xb6, 0x4a, 0x85, 0xa5, 0x9c, 0x4e, 0xf3, 0xc7, 0xfc, 0x62, 0xe3, 0xe5, - 0xbd, 0x6d, 0xbd, 0xea, 0x7a, 0xd3, 0xf5, 0xf4, 0xd1, 0xfe, 0x33, 0xaa, 0xe4, 0xcb, 0x38, 0xfc, - 0x0c, 0x00, 0x00, 0xff, 0xff, 0xf5, 0xc0, 0x30, 0xa7, 0x94, 0x02, 0x00, 0x00, + // 369 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x4d, 0x4b, 0xeb, 0x40, + 0x14, 0x7d, 0x79, 0xf0, 0xfa, 0x31, 0x7d, 0xaf, 0x7d, 0x8c, 0x58, 0x43, 0x90, 0xa2, 0x5d, 0x09, + 0x62, 0x02, 0x55, 0x10, 0x71, 0x21, 0x4a, 0xf1, 0x63, 0x51, 0x85, 0xd8, 0x95, 0x9b, 0x32, 0xcd, + 0x5c, 0x9a, 0xa1, 0xe9, 0xcc, 0x98, 0x4c, 0x5a, 0xfc, 0x27, 0xfe, 0x24, 0x97, 0xee, 0xdd, 0x88, + 0xfe, 0x11, 0xc7, 0x64, 0x1a, 0xa9, 0x45, 0xe8, 0xe2, 0x32, 0x73, 0xef, 0xb9, 0xe7, 0xdc, 0xc3, + 0xbd, 0xe8, 0x68, 0xc4, 0x54, 0x98, 0x0e, 0xdd, 0x40, 0x4c, 0xbc, 0x7e, 0x08, 0xfd, 0x90, 0xf1, + 0x51, 0x72, 0x0d, 0x6a, 0x26, 0xe2, 0xb1, 0xa7, 0x14, 0xf7, 0x88, 0x64, 0x5e, 0x48, 0x38, 0x8d, + 0x20, 0x9e, 0xbf, 0xae, 0x8c, 0x85, 0x12, 0xb8, 0x6c, 0x52, 0x67, 0x6f, 0x15, 0x0d, 0x1d, 0x39, + 0xcf, 0x39, 0x5c, 0xa5, 0x7d, 0x18, 0x8b, 0xb1, 0x9e, 0x98, 0x3f, 0x86, 0x78, 0xbc, 0x0a, 0x31, + 0x6b, 0x0d, 0x44, 0x54, 0x7c, 0x72, 0x72, 0xfb, 0xc5, 0x42, 0x76, 0x17, 0xa6, 0x2c, 0x80, 0xd3, + 0x40, 0xb1, 0x29, 0x51, 0x4c, 0x70, 0x1f, 0x12, 0x29, 0x78, 0x02, 0xd8, 0x46, 0x65, 0x49, 0x1e, + 0x22, 0x41, 0xa8, 0x6d, 0x6d, 0x59, 0x3b, 0x7f, 0xfd, 0x79, 0x8a, 0xd7, 0x51, 0x89, 0x48, 0x39, + 0x60, 0xd4, 0xfe, 0xad, 0x81, 0xaa, 0xff, 0x47, 0x67, 0x57, 0x14, 0x9f, 0xa0, 0x06, 0x15, 0x33, + 0x1e, 0x31, 0x3e, 0x1e, 0x08, 0xf9, 0xa9, 0x65, 0xd7, 0x34, 0x5e, 0xeb, 0x34, 0x5d, 0x63, 0xb9, + 0x6b, 0xe0, 0x9b, 0x0c, 0xf5, 0xeb, 0x74, 0x21, 0xc7, 0x3d, 0xb4, 0x46, 0x0a, 0x1f, 0x83, 0x09, + 0x28, 0x42, 0x89, 0x22, 0xf6, 0x46, 0x26, 0xb2, 0xe9, 0x16, 0xe6, 0xbf, 0xcc, 0xf6, 0x4c, 0x8f, + 0x8f, 0xc9, 0x52, 0xad, 0xdd, 0x40, 0xff, 0x6e, 0x15, 0x51, 0x69, 0xe2, 0xc3, 0x7d, 0x0a, 0x89, + 0x6a, 0x57, 0x50, 0x29, 0x2f, 0x74, 0x00, 0x95, 0x2f, 0xf3, 0x43, 0xe1, 0x3b, 0x54, 0x31, 0x7a, + 0x80, 0x77, 0x0b, 0xa3, 0x40, 0x53, 0x19, 0xb1, 0x40, 0x17, 0xe9, 0xf2, 0x82, 0x32, 0x35, 0x67, + 0xdb, 0x9d, 0x9f, 0xfe, 0xa7, 0x15, 0x76, 0xce, 0x51, 0xdd, 0x8c, 0xe9, 0x11, 0x4e, 0x46, 0x7a, + 0xda, 0x01, 0xaa, 0x5e, 0x80, 0xca, 0x5d, 0xe0, 0x66, 0xa1, 0xb0, 0xe0, 0xd3, 0x69, 0x7c, 0xab, + 0x9f, 0xfd, 0x7f, 0x7a, 0x6b, 0x59, 0xcf, 0x3a, 0x5e, 0x75, 0x3c, 0xbe, 0xb7, 0x7e, 0x0d, 0x4b, + 0xd9, 0x32, 0xf6, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x34, 0xb4, 0xf6, 0xbb, 0xab, 0x02, 0x00, + 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 5b9facabd..cf2c35694 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -8,6 +8,7 @@ package handler; message DeviceActivationResponse { bytes payload = 1; + string app_id = 2; broker.DownlinkOption downlink_option = 11; protocol.ActivationMetadata activation_metadata = 23; } diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 34bbf4f10..dccd9048d 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -10,6 +10,7 @@ It has these top-level messages: DevicesRequest + Device DevicesResponse RegisterDeviceRequest StatusRequest @@ -53,39 +54,38 @@ func (m *DevicesRequest) String() string { return proto.CompactTextSt func (*DevicesRequest) ProtoMessage() {} func (*DevicesRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{0} } +type Device struct { + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + StoredFCnt uint32 `protobuf:"varint,4,opt,name=stored_f_cnt,json=storedFCnt,proto3" json:"stored_f_cnt,omitempty"` + FullFCnt uint32 `protobuf:"varint,5,opt,name=full_f_cnt,json=fullFCnt,proto3" json:"full_f_cnt,omitempty"` + AppId string `protobuf:"bytes,6,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} +func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1} } + type DevicesResponse struct { - Results []*DevicesResponse_Device `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` + Results []*Device `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` } func (m *DevicesResponse) Reset() { *m = DevicesResponse{} } func (m *DevicesResponse) String() string { return proto.CompactTextString(m) } func (*DevicesResponse) ProtoMessage() {} -func (*DevicesResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1} } +func (*DevicesResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{2} } -func (m *DevicesResponse) GetResults() []*DevicesResponse_Device { +func (m *DevicesResponse) GetResults() []*Device { if m != nil { return m.Results } return nil } -type DevicesResponse_Device struct { - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - StoredFCnt uint32 `protobuf:"varint,4,opt,name=stored_f_cnt,json=storedFCnt,proto3" json:"stored_f_cnt,omitempty"` - FullFCnt uint32 `protobuf:"varint,5,opt,name=full_f_cnt,json=fullFCnt,proto3" json:"full_f_cnt,omitempty"` - DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` - Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` -} - -func (m *DevicesResponse_Device) Reset() { *m = DevicesResponse_Device{} } -func (m *DevicesResponse_Device) String() string { return proto.CompactTextString(m) } -func (*DevicesResponse_Device) ProtoMessage() {} -func (*DevicesResponse_Device) Descriptor() ([]byte, []int) { - return fileDescriptorNetworkserver, []int{1, 0} -} - // message RegisterDeviceRequest is used to register a device in the // NetworkServer type RegisterDeviceRequest struct { @@ -95,7 +95,7 @@ func (m *RegisterDeviceRequest) Reset() { *m = RegisterDeviceRequest{} } func (m *RegisterDeviceRequest) String() string { return proto.CompactTextString(m) } func (*RegisterDeviceRequest) ProtoMessage() {} func (*RegisterDeviceRequest) Descriptor() ([]byte, []int) { - return fileDescriptorNetworkserver, []int{2} + return fileDescriptorNetworkserver, []int{3} } // message StatusRequest is used to request the status of this NetworkServer @@ -105,7 +105,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{3} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } // message Status is the response to the StatusRequest type Status struct { @@ -116,7 +116,7 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{5} } func (m *Status) GetDevicesPerAddress() *api.Percentiles { if m != nil { @@ -127,8 +127,8 @@ func (m *Status) GetDevicesPerAddress() *api.Percentiles { func init() { proto.RegisterType((*DevicesRequest)(nil), "networkserver.DevicesRequest") + proto.RegisterType((*Device)(nil), "networkserver.Device") proto.RegisterType((*DevicesResponse)(nil), "networkserver.DevicesResponse") - proto.RegisterType((*DevicesResponse_Device)(nil), "networkserver.DevicesResponse.Device") proto.RegisterType((*RegisterDeviceRequest)(nil), "networkserver.RegisterDeviceRequest") proto.RegisterType((*StatusRequest)(nil), "networkserver.StatusRequest") proto.RegisterType((*Status)(nil), "networkserver.Status") @@ -476,7 +476,7 @@ func (m *DevicesRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DevicesResponse) Marshal() (data []byte, err error) { +func (m *Device) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -486,37 +486,7 @@ func (m *DevicesResponse) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *DevicesResponse) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Results) > 0 { - for _, msg := range m.Results { - data[i] = 0xa - i++ - i = encodeVarintNetworkserver(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *DevicesResponse_Device) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { +func (m *Device) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -561,6 +531,12 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintNetworkserver(data, i, uint64(m.FullFCnt)) } + if len(m.AppId) > 0 { + data[i] = 0x32 + i++ + i = encodeVarintNetworkserver(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } if m.DisableFCntCheck { data[i] = 0x58 i++ @@ -584,6 +560,36 @@ func (m *DevicesResponse_Device) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *DevicesResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DevicesResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Results) > 0 { + for _, msg := range m.Results { + data[i] = 0xa + i++ + i = encodeVarintNetworkserver(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func (m *RegisterDeviceRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -688,19 +694,7 @@ func (m *DevicesRequest) Size() (n int) { return n } -func (m *DevicesResponse) Size() (n int) { - var l int - _ = l - if len(m.Results) > 0 { - for _, e := range m.Results { - l = e.Size() - n += 1 + l + sovNetworkserver(uint64(l)) - } - } - return n -} - -func (m *DevicesResponse_Device) Size() (n int) { +func (m *Device) Size() (n int) { var l int _ = l if m.AppEui != nil { @@ -721,6 +715,10 @@ func (m *DevicesResponse_Device) Size() (n int) { if m.FullFCnt != 0 { n += 1 + sovNetworkserver(uint64(m.FullFCnt)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovNetworkserver(uint64(l)) + } if m.DisableFCntCheck { n += 2 } @@ -730,6 +728,18 @@ func (m *DevicesResponse_Device) Size() (n int) { return n } +func (m *DevicesResponse) Size() (n int) { + var l int + _ = l + if len(m.Results) > 0 { + for _, e := range m.Results { + l = e.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + } + return n +} + func (m *RegisterDeviceRequest) Size() (n int) { var l int _ = l @@ -866,88 +876,7 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { } return nil } -func (m *DevicesResponse) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: DevicesResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: DevicesResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthNetworkserver - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Results = append(m.Results, &DevicesResponse_Device{}) - if err := m.Results[len(m.Results)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthNetworkserver - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *DevicesResponse_Device) Unmarshal(data []byte) error { +func (m *Device) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1110,6 +1039,35 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { break } } + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field DisableFCntCheck", wireType) @@ -1171,6 +1129,87 @@ func (m *DevicesResponse_Device) Unmarshal(data []byte) error { } return nil } +func (m *DevicesResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevicesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevicesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Results", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Results = append(m.Results, &Device{}) + if err := m.Results[len(m.Results)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipNetworkserver(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthNetworkserver + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *RegisterDeviceRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -1460,49 +1499,50 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 696 bytes of a gzipped FileDescriptorProto + // 705 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x4e, 0x13, 0x41, - 0x14, 0xb6, 0x14, 0xda, 0x32, 0x50, 0x28, 0x83, 0x84, 0xa6, 0x41, 0xc4, 0x46, 0x23, 0x89, 0x61, - 0x1b, 0x4b, 0xd4, 0x98, 0x98, 0x48, 0xf9, 0xd1, 0x28, 0x81, 0xe0, 0x02, 0xd7, 0x9b, 0xed, 0xee, - 0x69, 0x3b, 0xd9, 0x65, 0x76, 0x9d, 0x99, 0x6d, 0xe5, 0x3d, 0xbc, 0x30, 0xbe, 0x82, 0x2f, 0xe2, - 0xa5, 0xd7, 0x5e, 0x18, 0xa3, 0x57, 0xbe, 0x85, 0xb3, 0x33, 0x53, 0x74, 0x11, 0xe4, 0xe7, 0x62, - 0xd3, 0x99, 0xef, 0x7c, 0xe7, 0x9b, 0x33, 0xe7, 0x7c, 0x53, 0xb4, 0xd5, 0x25, 0xa2, 0x97, 0xb4, - 0x2d, 0x2f, 0x3a, 0x6a, 0x1c, 0xf4, 0xe0, 0xa0, 0x47, 0x68, 0x97, 0xef, 0x82, 0x18, 0x44, 0x2c, - 0x68, 0x08, 0x41, 0x1b, 0x6e, 0x4c, 0x1a, 0x54, 0xef, 0x39, 0xb0, 0x3e, 0xb0, 0xec, 0xce, 0x8a, - 0x59, 0x24, 0x22, 0x5c, 0xce, 0x80, 0xb5, 0x95, 0xbf, 0x54, 0xbb, 0x51, 0x37, 0x6a, 0x28, 0x56, - 0x3b, 0xe9, 0xa8, 0x9d, 0xda, 0xa8, 0x95, 0xce, 0xce, 0xd0, 0xcf, 0x2d, 0x42, 0x7e, 0x86, 0xfe, - 0xe4, 0x32, 0xf4, 0x36, 0x8b, 0x02, 0x59, 0xac, 0xfe, 0x31, 0x89, 0x4f, 0x2f, 0x93, 0xd8, 0x73, - 0xa9, 0x1f, 0xca, 0x4c, 0xf3, 0xab, 0x53, 0xeb, 0xef, 0xd0, 0xd4, 0x26, 0xf4, 0x89, 0x07, 0xdc, - 0x86, 0xb7, 0x09, 0x70, 0x81, 0xdf, 0xa0, 0x92, 0x0f, 0x7d, 0xc7, 0xf5, 0x7d, 0x56, 0xcd, 0x2d, - 0xe5, 0x96, 0x27, 0xd7, 0x1f, 0x7f, 0xfd, 0x76, 0xbb, 0x79, 0xd1, 0x11, 0x5e, 0xc4, 0xa0, 0x21, - 0x8e, 0x63, 0xe0, 0x96, 0x14, 0x6c, 0xc9, 0x6c, 0xbb, 0xe8, 0xeb, 0x05, 0x9e, 0x45, 0x63, 0x1d, - 0xc7, 0xa3, 0xa2, 0x3a, 0x22, 0xf5, 0xca, 0xf6, 0x68, 0x67, 0x83, 0x8a, 0xfa, 0xaf, 0x3c, 0x9a, - 0x3e, 0x39, 0x9a, 0xc7, 0x11, 0xe5, 0x80, 0x9f, 0xa3, 0x22, 0x03, 0x9e, 0x84, 0x82, 0xcb, 0xa3, - 0xf3, 0xcb, 0x13, 0xcd, 0x7b, 0x56, 0x76, 0x2a, 0xa7, 0x12, 0xcc, 0xde, 0x1e, 0x66, 0xd5, 0xde, - 0xe7, 0x51, 0x41, 0x63, 0x78, 0x17, 0x15, 0xdd, 0x38, 0x76, 0x20, 0x21, 0xe6, 0x1a, 0x8f, 0xe4, - 0x35, 0x1e, 0x5e, 0xe1, 0x1a, 0xad, 0x38, 0xde, 0x3a, 0x7c, 0x65, 0x17, 0xa4, 0xca, 0x56, 0x42, - 0x52, 0xbd, 0xb4, 0x2f, 0xa9, 0xde, 0xc8, 0xb5, 0xf4, 0x64, 0x5d, 0x4a, 0x4f, 0xaa, 0xa4, 0x7a, - 0x36, 0x1a, 0xa7, 0x83, 0xc0, 0xe1, 0x4e, 0x00, 0xc7, 0xd5, 0xfc, 0xb5, 0x1a, 0xbd, 0x3b, 0x08, - 0xf6, 0xb7, 0xe1, 0xd8, 0x2e, 0x52, 0xbd, 0xc0, 0x4b, 0x68, 0x92, 0x0b, 0x19, 0xf7, 0x1d, 0xdd, - 0xef, 0x51, 0xd5, 0x6f, 0xa4, 0xb1, 0x17, 0xb2, 0xeb, 0x78, 0x01, 0xa1, 0x4e, 0x12, 0x86, 0x26, - 0x3e, 0xa6, 0xe2, 0xa5, 0x14, 0x51, 0xd1, 0x15, 0x34, 0xeb, 0x13, 0xee, 0xb6, 0x43, 0xd0, 0x04, - 0xc7, 0xeb, 0x81, 0x17, 0x54, 0x27, 0x24, 0xad, 0x64, 0x57, 0x4c, 0x28, 0x65, 0x6e, 0xa4, 0x38, - 0xbe, 0x8f, 0x2a, 0x09, 0x07, 0xbe, 0xda, 0x74, 0xda, 0x44, 0x18, 0xc9, 0x49, 0xc5, 0x2d, 0x6b, - 0x7c, 0x9d, 0x88, 0x94, 0x5d, 0x9f, 0x47, 0x73, 0x36, 0x74, 0x09, 0x17, 0xc0, 0xcc, 0xc4, 0xb4, - 0xd9, 0xea, 0xd3, 0xa8, 0xbc, 0x2f, 0x5c, 0x91, 0x0c, 0xdd, 0x57, 0x7f, 0x8d, 0x0a, 0x1a, 0xc0, - 0x6b, 0xb2, 0x16, 0x3d, 0x6d, 0x27, 0x06, 0xa6, 0xfc, 0x08, 0x9c, 0xab, 0x59, 0x4e, 0x34, 0x2b, - 0x56, 0xfa, 0x6c, 0xf6, 0x80, 0x79, 0x40, 0x05, 0x09, 0xa5, 0x23, 0x66, 0x0c, 0x59, 0x62, 0x2d, - 0x4d, 0x6d, 0x7e, 0xca, 0xa3, 0xb2, 0xe9, 0xdc, 0xbe, 0xb2, 0x0f, 0xde, 0x46, 0xe8, 0x25, 0x08, - 0x63, 0x22, 0x7c, 0xeb, 0x3c, 0x73, 0xa9, 0x52, 0x6a, 0x8b, 0xff, 0xf7, 0x1e, 0x3e, 0x42, 0x33, - 0x7b, 0x0c, 0x62, 0x97, 0x41, 0xcb, 0x13, 0xa4, 0xef, 0x0a, 0x12, 0x51, 0xfc, 0xc0, 0x32, 0x2f, - 0x73, 0x13, 0xfc, 0x24, 0x0e, 0x89, 0xe7, 0x0a, 0xf0, 0x75, 0xe6, 0x1f, 0xd6, 0xf0, 0x84, 0xab, - 0x90, 0xf1, 0x1e, 0x2a, 0x19, 0x10, 0xf0, 0x1d, 0x6b, 0xf8, 0x8a, 0xff, 0x65, 0xeb, 0xea, 0x6a, - 0x17, 0x53, 0xa4, 0xa3, 0x0b, 0x87, 0xf2, 0x54, 0x1a, 0x48, 0xbd, 0x33, 0x0a, 0xd1, 0xb1, 0x1d, - 0xd9, 0x49, 0xb7, 0x9b, 0xea, 0x5d, 0x44, 0xc1, 0xcf, 0x50, 0x69, 0x33, 0x1a, 0x50, 0xa5, 0x38, - 0x7f, 0x42, 0x37, 0xc8, 0x50, 0xe7, 0xbc, 0x40, 0xf3, 0x63, 0x0e, 0xdd, 0xcc, 0x4c, 0x6b, 0xc7, - 0xa5, 0x12, 0x67, 0xd2, 0x08, 0x53, 0x59, 0xf3, 0xe0, 0xbb, 0xa7, 0x26, 0x73, 0xa6, 0xb7, 0x6a, - 0x25, 0xe5, 0x91, 0x96, 0xf4, 0xe9, 0x1a, 0x1a, 0x97, 0x63, 0x37, 0xbe, 0x5a, 0x38, 0x95, 0x9c, - 0xf1, 0x5f, 0x6d, 0xee, 0xcc, 0xe8, 0x7a, 0xe5, 0xf3, 0x8f, 0xc5, 0xdc, 0x17, 0xf9, 0x7d, 0x97, - 0xdf, 0x87, 0x9f, 0x8b, 0x37, 0xda, 0x05, 0xf5, 0xff, 0xb9, 0xfa, 0x3b, 0x00, 0x00, 0xff, 0xff, - 0x6b, 0xba, 0xbc, 0xa2, 0x69, 0x06, 0x00, 0x00, + 0x14, 0xb6, 0xfc, 0xb4, 0x65, 0xa0, 0x50, 0x06, 0x1b, 0x9a, 0x06, 0x11, 0x1b, 0x13, 0x49, 0x0c, + 0x6d, 0x2c, 0x51, 0x63, 0xe2, 0x05, 0x2d, 0xa0, 0x41, 0x02, 0xc1, 0x05, 0xae, 0x37, 0xdb, 0xdd, + 0xd3, 0x76, 0xd2, 0x32, 0xbb, 0xce, 0xcc, 0xb6, 0xf2, 0x26, 0xc6, 0x57, 0xf0, 0x45, 0xbc, 0xf4, + 0xda, 0x0b, 0x63, 0xf4, 0x45, 0x3c, 0x3b, 0x33, 0x45, 0x8b, 0x25, 0xfc, 0x5c, 0x6c, 0x3a, 0x73, + 0xbe, 0xef, 0x7c, 0xe7, 0xcc, 0x99, 0x6f, 0x4a, 0x76, 0xdb, 0x4c, 0x75, 0xe2, 0x66, 0xc5, 0x0f, + 0xcf, 0xaa, 0x27, 0x1d, 0x38, 0xe9, 0x30, 0xde, 0x96, 0x87, 0xa0, 0x06, 0xa1, 0xe8, 0x56, 0x95, + 0xe2, 0x55, 0x2f, 0x62, 0x55, 0x6e, 0xf6, 0x12, 0x44, 0x1f, 0xc4, 0xe8, 0xae, 0x12, 0x89, 0x50, + 0x85, 0x34, 0x37, 0x12, 0x2c, 0x6d, 0xfc, 0xa3, 0xda, 0x0e, 0xdb, 0x61, 0x55, 0xb3, 0x9a, 0x71, + 0x4b, 0xef, 0xf4, 0x46, 0xaf, 0x4c, 0xf6, 0x08, 0xfd, 0xca, 0x26, 0xf0, 0xb3, 0xf4, 0x97, 0x37, + 0xa1, 0x37, 0x45, 0xd8, 0xc5, 0x66, 0xcd, 0x8f, 0x4d, 0x7c, 0x75, 0x93, 0xc4, 0x8e, 0xc7, 0x83, + 0x1e, 0x66, 0xda, 0x5f, 0x93, 0x5a, 0xfe, 0x48, 0xe6, 0x77, 0xa0, 0xcf, 0x7c, 0x90, 0x0e, 0x7c, + 0x88, 0x41, 0x2a, 0xfa, 0x9e, 0x64, 0x03, 0xe8, 0xbb, 0x5e, 0x10, 0x88, 0x62, 0x6a, 0x2d, 0xb5, + 0x3e, 0xd7, 0x78, 0xf1, 0xfd, 0xc7, 0xc3, 0xda, 0x75, 0x25, 0xfc, 0x50, 0x40, 0x55, 0x9d, 0x47, + 0x20, 0x2b, 0x28, 0x58, 0xc7, 0x6c, 0x27, 0x13, 0x98, 0x05, 0x5d, 0x22, 0xd3, 0x2d, 0xd7, 0xe7, + 0xaa, 0x38, 0x81, 0x7a, 0x39, 0x67, 0xaa, 0xb5, 0xcd, 0x55, 0xf9, 0xcb, 0x24, 0x49, 0x9b, 0xd2, + 0xf4, 0x90, 0x64, 0xbc, 0x28, 0x72, 0x21, 0x66, 0xb6, 0xe2, 0x73, 0xac, 0xf8, 0xec, 0x16, 0x15, + 0xeb, 0x51, 0xb4, 0x7b, 0xba, 0xe7, 0xa4, 0x51, 0x65, 0x37, 0x66, 0x89, 0x5e, 0x72, 0x84, 0x44, + 0x6f, 0xe2, 0x4e, 0x7a, 0xd8, 0x97, 0xd6, 0x43, 0x95, 0x44, 0xcf, 0x21, 0x33, 0x7c, 0xd0, 0x75, + 0xa5, 0xdb, 0x85, 0xf3, 0xe2, 0xe4, 0x9d, 0x66, 0x72, 0x38, 0xe8, 0x1e, 0xef, 0xc3, 0xb9, 0x93, + 0xe1, 0x66, 0x41, 0xd7, 0xc8, 0x9c, 0x54, 0x88, 0x07, 0xae, 0x19, 0xcd, 0x94, 0x1e, 0x0d, 0x31, + 0xb1, 0x37, 0x38, 0x20, 0xba, 0x42, 0x48, 0x2b, 0xee, 0xf5, 0x2c, 0x3e, 0xad, 0xf1, 0x6c, 0x12, + 0xd1, 0x68, 0x81, 0x24, 0xa7, 0x75, 0x59, 0x50, 0x4c, 0x23, 0x32, 0xe3, 0x4c, 0xe3, 0x6e, 0x2f, + 0xa0, 0x1b, 0x64, 0x29, 0x60, 0xd2, 0x6b, 0xf6, 0xc0, 0xe4, 0xb9, 0x7e, 0x07, 0xfc, 0x6e, 0x71, + 0x16, 0x39, 0x59, 0x27, 0x6f, 0xa1, 0x44, 0x60, 0x3b, 0x89, 0xd3, 0x27, 0x24, 0x1f, 0x4b, 0x90, + 0x9b, 0x35, 0xb7, 0xc9, 0x94, 0xad, 0x34, 0xa7, 0xb9, 0x39, 0x13, 0x6f, 0x30, 0x95, 0xb0, 0xcb, + 0x0d, 0xb2, 0x70, 0xe1, 0x13, 0x19, 0x85, 0x5c, 0x02, 0xad, 0x92, 0x8c, 0x00, 0x19, 0xf7, 0x94, + 0xc4, 0x5b, 0x9b, 0x5c, 0x9f, 0xad, 0x15, 0x2a, 0xa3, 0x4f, 0xc8, 0x24, 0x38, 0x43, 0x56, 0x79, + 0x99, 0x14, 0x1c, 0x68, 0x33, 0xa9, 0x40, 0x58, 0xc8, 0x58, 0xae, 0xbc, 0x40, 0x72, 0xc7, 0xca, + 0x53, 0xf1, 0xd0, 0x83, 0xe5, 0x77, 0x24, 0x6d, 0x02, 0x74, 0x0b, 0xcf, 0x63, 0xea, 0xba, 0x11, + 0x08, 0xed, 0x4a, 0x90, 0x52, 0xdb, 0x64, 0xb6, 0x96, 0xaf, 0x24, 0x8f, 0xe7, 0x08, 0x84, 0x0f, + 0x5c, 0xb1, 0x1e, 0xf6, 0xb6, 0x68, 0xc9, 0x18, 0xab, 0x1b, 0x6a, 0x0d, 0x7d, 0x96, 0xb3, 0x97, + 0x72, 0xac, 0xfb, 0xa2, 0xfb, 0x84, 0xbc, 0x05, 0x65, 0x8f, 0x43, 0x1f, 0x8c, 0xed, 0x7a, 0xd8, + 0x4a, 0x69, 0xf5, 0x2a, 0xd8, 0x4e, 0xe1, 0x8c, 0x2c, 0x1e, 0x09, 0x88, 0x3c, 0x01, 0x75, 0x5f, + 0xb1, 0xbe, 0xa7, 0x58, 0xc8, 0xe9, 0xd3, 0x8a, 0x7d, 0x9f, 0x3b, 0x10, 0xc4, 0x51, 0x8f, 0xf9, + 0x9e, 0x82, 0xc0, 0x64, 0xfe, 0x65, 0x0d, 0x2b, 0xdc, 0x86, 0x4c, 0x8f, 0x48, 0xd6, 0x06, 0x81, + 0x3e, 0xaa, 0x0c, 0xdf, 0xf2, 0xff, 0x6c, 0xd3, 0x5d, 0xe9, 0x7a, 0x0a, 0x3e, 0x96, 0xf4, 0x29, + 0x56, 0xe5, 0x5d, 0xd4, 0x1b, 0xd3, 0x88, 0xc1, 0x0e, 0x70, 0x92, 0x5e, 0x3b, 0xd1, 0xbb, 0x8e, + 0x42, 0x5f, 0x93, 0xec, 0x4e, 0x38, 0xe0, 0x5a, 0x71, 0xf9, 0x82, 0x6e, 0x23, 0x43, 0x9d, 0xab, + 0x80, 0xda, 0xe7, 0x14, 0xb9, 0x3f, 0x72, 0x5b, 0x07, 0x1e, 0xc7, 0xb8, 0x40, 0x23, 0xcc, 0x8f, + 0x9a, 0x87, 0x3e, 0xbe, 0x74, 0x33, 0x63, 0xbd, 0x55, 0xca, 0x6a, 0x8f, 0xd4, 0xd1, 0xeb, 0x5b, + 0x64, 0x06, 0xaf, 0xdd, 0xfa, 0x6a, 0xe5, 0x52, 0xf2, 0x88, 0xff, 0x4a, 0x85, 0xb1, 0x68, 0x23, + 0xff, 0xf5, 0xd7, 0x6a, 0xea, 0x1b, 0x7e, 0x3f, 0xf1, 0xfb, 0xf4, 0x7b, 0xf5, 0x5e, 0x33, 0xad, + 0xff, 0x45, 0x37, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xac, 0xbc, 0x1b, 0x87, 0x6f, 0x06, 0x00, + 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 05713b76b..1b373c6a1 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -14,16 +14,18 @@ message DevicesRequest { uint32 f_cnt = 2; } +message Device { + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + uint32 stored_f_cnt = 4; + uint32 full_f_cnt = 5; + string app_id = 6; + bool disable_f_cnt_check = 11; + bool uses32_bit_f_cnt = 12; +} + message DevicesResponse { - message Device { - bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; - bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; - uint32 stored_f_cnt = 4; - uint32 full_f_cnt = 5; - bool disable_f_cnt_check = 11; - bool uses32_bit_f_cnt = 12; - } repeated Device results = 1; } diff --git a/core/broker/activation.go b/core/broker/activation.go index 40678f0b5..ca0a74de6 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -10,9 +10,9 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/apex/log" ) @@ -74,22 +74,32 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D return nil, err } + ctx = ctx.WithField("AppID", deduplicatedActivationRequest.AppId) + // Find Handler (based on AppEUI) - var application *application.Application - application, err = b.applications.Get(*base.AppEui) + var announcements []*pb_discovery.Announcement + announcements, err = b.handlerDiscovery.ForAppID(deduplicatedActivationRequest.AppId) if err != nil { return nil, err } + if len(announcements) == 0 { + err = errors.New("ttn/broker: No Handler found") + return nil, err + } + if len(announcements) > 1 { + err = errors.New("ttn/broker: Multiple Handlers found for same AppID") + return nil, err + } + handler := announcements[0] - ctx = ctx.WithField("HandlerID", application.HandlerID) - ctx.Debug("Forward Activation") + ctx.WithField("HandlerID", handler.Id).Debug("Forward Activation") var conn *grpc.ClientConn - conn, err = grpc.Dial(application.HandlerNetAddress, api.DialOptions...) + conn, err = grpc.Dial(handler.NetAddress, api.DialOptions...) + defer conn.Close() if err != nil { return nil, err } - defer conn.Close() client := pb_handler.NewHandlerClient(conn) var handlerResponse *pb_handler.DeviceActivationResponse handlerResponse, err = client.Activate(b.Component.GetContext(), deduplicatedActivationRequest) diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index 44c1d6485..73c3d9fea 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -9,7 +9,6 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/protocol" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -26,12 +25,11 @@ func TestHandleActivation(t *testing.T) { Component: &core.Component{ Ctx: GetLogger(t, "TestHandleActivation"), }, + handlerDiscovery: &mockHandlerDiscovery{}, activationDeduplicator: NewDeduplicator(10 * time.Millisecond), - applications: application.NewApplicationStore(), - ns: &mockNetworkServer{}, + ns: &mockNetworkServer{}, } - // Non-existing Application res, err := b.HandleActivation(&pb.DeviceActivationRequest{ Payload: []byte{}, DevEui: &devEUI, @@ -42,21 +40,6 @@ func TestHandleActivation(t *testing.T) { a.So(err, ShouldNotBeNil) a.So(res, ShouldBeNil) - // Non-existing Broker - b.applications.Set(&application.Application{ - AppEUI: appEUI, - HandlerNetAddress: "localhost:1234", - }) - res, err = b.HandleActivation(&pb.DeviceActivationRequest{ - Payload: []byte{}, - DevEui: &devEUI, - AppEui: &appEUI, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, - ProtocolMetadata: &protocol.RxMetadata{}, - }) - a.So(err, ShouldNotBeNil) - a.So(res, ShouldBeNil) - // TODO: Integration test with Handler } diff --git a/core/broker/application/application.go b/core/broker/application/application.go deleted file mode 100644 index f640e1449..000000000 --- a/core/broker/application/application.go +++ /dev/null @@ -1,78 +0,0 @@ -package application - -import ( - "fmt" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -// Application contains the state of an application -type Application struct { - AppEUI types.AppEUI - HandlerID string - HandlerNetAddress string -} - -// ApplicationProperties contains all properties of an Application that can be stored in Redis. -var ApplicationProperties = []string{ - "app_eui", - "handler_id", - "handler_net_address", -} - -// ToStringStringMap converts the given properties of Application to a -// map[string]string for storage in Redis. -func (app *Application) ToStringStringMap(properties ...string) (map[string]string, error) { - output := make(map[string]string) - for _, p := range properties { - property, err := app.formatProperty(p) - if err != nil { - return output, err - } - if property != "" { - output[p] = property - } - } - return output, nil -} - -// FromStringStringMap imports known values from the input to a Application. -func (app *Application) FromStringStringMap(input map[string]string) error { - for k, v := range input { - app.parseProperty(k, v) - } - return nil -} - -func (app *Application) formatProperty(property string) (formatted string, err error) { - switch property { - case "app_eui": - formatted = app.AppEUI.String() - case "handler_id": - formatted = app.HandlerID - case "handler_net_address": - formatted = app.HandlerNetAddress - default: - err = fmt.Errorf("Property %s does not exist in Application", property) - } - return -} - -func (app *Application) parseProperty(property string, value string) error { - if value == "" { - return nil - } - switch property { - case "app_eui": - val, err := types.ParseAppEUI(value) - if err != nil { - return err - } - app.AppEUI = val - case "handler_id": - app.HandlerID = value - case "handler_net_address": - app.HandlerNetAddress = value - } - return nil -} diff --git a/core/broker/application/application_test.go b/core/broker/application/application_test.go deleted file mode 100644 index ce10648e5..000000000 --- a/core/broker/application/application_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package application - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/smartystreets/assertions" -) - -func getTestApplication() (application *Application, dmap map[string]string) { - return &Application{ - AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, - HandlerID: "handlerID", - HandlerNetAddress: "handlerNetAddress:1234", - }, map[string]string{ - "app_eui": "0807060504030201", - "handler_id": "handlerID", - "handler_net_address": "handlerNetAddress:1234", - } -} - -func TestToStringMap(t *testing.T) { - a := New(t) - application, expected := getTestApplication() - dmap, err := application.ToStringStringMap(ApplicationProperties...) - a.So(err, ShouldBeNil) - a.So(dmap, ShouldResemble, expected) -} - -func TestFromStringMap(t *testing.T) { - a := New(t) - application := &Application{} - expected, dmap := getTestApplication() - err := application.FromStringStringMap(dmap) - a.So(err, ShouldBeNil) - a.So(application, ShouldResemble, expected) -} diff --git a/core/broker/application/store.go b/core/broker/application/store.go deleted file mode 100644 index 67aabec77..000000000 --- a/core/broker/application/store.go +++ /dev/null @@ -1,140 +0,0 @@ -package application - -import ( - "errors" - "fmt" - - "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -var ( - // ErrNotFound is returned when an application was not found - ErrNotFound = errors.New("ttn/broker: Application not found") -) - -// Store is used to store application configurations -type Store interface { - // List all applications - List() ([]*Application, error) - // Get the full information about an application - Get(appEUI types.AppEUI) (*Application, error) - // Set the given fields of an application. If fields empty, it sets all fields. - Set(application *Application, fields ...string) error - // Delete an application - Delete(types.AppEUI) error -} - -// NewApplicationStore creates a new in-memory Application store -func NewApplicationStore() Store { - return &applicationStore{ - applications: make(map[types.AppEUI]*Application), - } -} - -// applicationStore is an in-memory Application store. It should only be used for testing -// purposes. Use the redisApplicationStore for actual deployments. -type applicationStore struct { - applications map[types.AppEUI]*Application -} - -func (s *applicationStore) List() ([]*Application, error) { - apps := make([]*Application, 0, len(s.applications)) - for _, application := range s.applications { - apps = append(apps, application) - } - return apps, nil -} - -func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { - if app, ok := s.applications[appEUI]; ok { - return app, nil - } - return nil, ErrNotFound -} - -func (s *applicationStore) Set(new *Application, fields ...string) error { - // NOTE: We don't care about fields for testing - s.applications[new.AppEUI] = new - return nil -} - -func (s *applicationStore) Delete(appEUI types.AppEUI) error { - delete(s.applications, appEUI) - return nil -} - -// NewRedisApplicationStore creates a new Redis-based status store -func NewRedisApplicationStore(client *redis.Client) Store { - return &redisApplicationStore{ - client: client, - } -} - -const redisApplicationPrefix = "broker:application" - -type redisApplicationStore struct { - client *redis.Client -} - -func (s *redisApplicationStore) List() ([]*Application, error) { - var apps []*Application - keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisApplicationPrefix)).Result() - if err != nil { - return nil, err - } - for _, key := range keys { - res, err := s.client.HGetAllMap(key).Result() - if err != nil { - return nil, err - } - application := &Application{} - err = application.FromStringStringMap(res) - if err != nil { - return nil, err - } - apps = append(apps, application) - } - return apps, nil -} - -func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() - if err != nil { - if err == redis.Nil { - return nil, ErrNotFound - } - return nil, err - } else if len(res) == 0 { - return nil, ErrNotFound - } - application := &Application{} - err = application.FromStringStringMap(res) - if err != nil { - return nil, err - } - return application, nil -} - -func (s *redisApplicationStore) Set(new *Application, fields ...string) error { - if len(fields) == 0 { - fields = ApplicationProperties - } - key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppEUI) - dmap, err := new.ToStringStringMap(fields...) - if err != nil { - return err - } - s.client.HMSetMap(key, dmap) - return nil -} - -func (s *redisApplicationStore) Delete(appEUI types.AppEUI) error { - key := fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI) - err := s.client.Del(key).Err() - if err != nil { - return err - } - return nil -} diff --git a/core/broker/application/store_test.go b/core/broker/application/store_test.go deleted file mode 100644 index d1c57aef9..000000000 --- a/core/broker/application/store_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package application - -import ( - "testing" - - "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/smartystreets/assertions" -) - -func getRedisClient() *redis.Client { - return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Password: "", // no password set - DB: 1, // use default DB - }) -} - -func TestApplicationStore(t *testing.T) { - a := New(t) - - stores := map[string]Store{ - "local": NewApplicationStore(), - "redis": NewRedisApplicationStore(getRedisClient()), - } - - for name, s := range stores { - - t.Logf("Testing %s store", name) - - appEUI := types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1} - - // Non-existing App - err := s.Set(&Application{ - AppEUI: appEUI, - HandlerID: "handler1ID", - HandlerNetAddress: "handler1NetAddress:1234", - }) - a.So(err, ShouldBeNil) - - // Existing App - err = s.Set(&Application{ - AppEUI: appEUI, - HandlerID: "handler1ID", - HandlerNetAddress: "handler1NetAddress2:1234", - }) - a.So(err, ShouldBeNil) - - app, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 2}) - a.So(err, ShouldNotBeNil) - a.So(app, ShouldBeNil) - - app, err = s.Get(appEUI) - a.So(err, ShouldBeNil) - a.So(app, ShouldNotBeNil) - a.So(app.HandlerNetAddress, ShouldEqual, "handler1NetAddress2:1234") - - // List - apps, err := s.List() - a.So(err, ShouldBeNil) - a.So(apps, ShouldHaveLength, 1) - - err = s.Delete(appEUI) - a.So(err, ShouldBeNil) - - app, err = s.Get(appEUI) - a.So(err, ShouldNotBeNil) - a.So(app, ShouldBeNil) - } -} diff --git a/core/broker/broker.go b/core/broker/broker.go index b8fa33ffb..cedf599a3 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -13,7 +13,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/broker/application" + "github.com/TheThingsNetwork/ttn/core/discovery" ) type Broker interface { @@ -33,7 +33,6 @@ func NewRedisBroker(client *redis.Client, networkserver string, timeout time.Dur return &broker{ routers: make(map[string]chan *pb.DownlinkMessage), handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), - applications: application.NewRedisApplicationStore(client), uplinkDeduplicator: NewDeduplicator(timeout), activationDeduplicator: NewDeduplicator(timeout), nsAddr: networkserver, @@ -44,9 +43,9 @@ type broker struct { *core.Component routers map[string]chan *pb.DownlinkMessage routersLock sync.RWMutex + handlerDiscovery discovery.HandlerDiscovery handlers map[string]chan *pb.DeduplicatedUplinkMessage handlersLock sync.RWMutex - applications application.Store nsAddr string ns networkserver.NetworkServerClient uplinkDeduplicator Deduplicator @@ -63,6 +62,8 @@ func (b *broker) Init(c *core.Component) error { if err != nil { return err } + b.handlerDiscovery = discovery.NewHandlerDiscovery(b.Component) + b.handlerDiscovery.All() // Update cache conn, err := grpc.Dial(b.nsAddr, api.DialOptions...) if err != nil { return err diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go index b11ece57e..3c2d84c6a 100644 --- a/core/broker/broker_test.go +++ b/core/broker/broker_test.go @@ -1,10 +1,12 @@ package broker import ( + "errors" "sync" "testing" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" . "github.com/smartystreets/assertions" @@ -12,8 +14,30 @@ import ( "google.golang.org/grpc" ) +type mockHandlerDiscovery struct { + a *pb_discovery.Announcement +} + +func (d *mockHandlerDiscovery) ForAppID(appID string) (a []*pb_discovery.Announcement, err error) { + return d.All() +} + +func (d *mockHandlerDiscovery) Get(id string) (a *pb_discovery.Announcement, err error) { + if d.a == nil { + return nil, errors.New("Not found") + } + return d.a, nil +} + +func (d *mockHandlerDiscovery) All() (a []*pb_discovery.Announcement, err error) { + if d.a == nil { + return []*pb_discovery.Announcement{}, nil + } + return []*pb_discovery.Announcement{d.a}, nil +} + type mockNetworkServer struct { - devices []*pb_networkserver.DevicesResponse_Device + devices []*pb_networkserver.Device } func (s *mockNetworkServer) GetDevices(ctx context.Context, req *pb_networkserver.DevicesRequest, options ...grpc.CallOption) (*pb_networkserver.DevicesResponse, error) { diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 239364585..12e1a96c6 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -8,10 +8,10 @@ import ( "time" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" - "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -78,7 +78,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) // Find AppEUI/DevEUI through MIC check - var device *pb_networkserver.DevicesResponse_Device + var device *pb_networkserver.Device for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) if candidate.Uses32BitFCnt { @@ -100,6 +100,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { ctx = ctx.WithFields(log.Fields{ "DevEUI": device.DevEui, "AppEUI": device.AppEui, + "AppID": device.AppId, "FCnt": device.FullFCnt, }) @@ -147,16 +148,20 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } - var application *application.Application - application, err = b.applications.Get(*device.AppEui) + var announcements []*pb_discovery.Announcement + announcements, err = b.handlerDiscovery.ForAppID(device.AppId) if err != nil { return err } - - ctx = ctx.WithField("HandlerID", application.HandlerID) + if len(announcements) == 0 { + return errors.New("ttn/broker: No Handlers") + } + if len(announcements) > 1 { + return errors.New("ttn/broker: Can't forward to multiple Handlers") + } var handler chan<- *pb.DeduplicatedUplinkMessage - handler, err = b.getHandler(application.HandlerID) + handler, err = b.getHandler(announcements[0].Id) if err != nil { return err } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 2173b1985..60c4558c5 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -6,12 +6,12 @@ import ( "time" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/broker/application" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" @@ -27,7 +27,7 @@ func TestHandleUplink(t *testing.T) { }, uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ - devices: []*pb_networkserver.DevicesResponse_Device{}, + devices: []*pb_networkserver.Device{}, }, } @@ -67,6 +67,7 @@ func TestHandleUplink(t *testing.T) { devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "AppID-1" nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} // Add devices @@ -77,18 +78,20 @@ func TestHandleUplink(t *testing.T) { handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ - devices: []*pb_networkserver.DevicesResponse_Device{ - &pb_networkserver.DevicesResponse_Device{ + devices: []*pb_networkserver.Device{ + &pb_networkserver.Device{ DevEui: &devEUI, AppEui: &appEUI, + AppId: appID, NwkSKey: &nwkSKey, StoredFCnt: 3, }, }, }, - applications: application.NewApplicationStore(), + handlerDiscovery: &mockHandlerDiscovery{ + &pb_discovery.Announcement{Id: "handlerID"}, + }, } - b.applications.Set(&application.Application{AppEUI: appEUI, HandlerID: "handlerID"}) b.handlers["handlerID"] = make(chan *pb.DeduplicatedUplinkMessage, 10) // Device doesn't match diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go new file mode 100644 index 000000000..90361b818 --- /dev/null +++ b/core/discovery/handler_discovery.go @@ -0,0 +1,114 @@ +package discovery + +import ( + "errors" + "sync" + "time" + + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" +) + +// HandlerCacheTime indicates how long the HandlerDiscovery should cache the services +var HandlerCacheTime = 30 * time.Minute + +// HandlerDiscovery is used as a client to discover Handlers +type HandlerDiscovery interface { + ForAppID(appID string) ([]*pb.Announcement, error) + Get(id string) (*pb.Announcement, error) + All() ([]*pb.Announcement, error) +} + +type handlerDiscovery struct { + component *core.Component + cache []*pb.Announcement + byID map[string]*pb.Announcement + byAppID map[string][]*pb.Announcement + cacheLock sync.RWMutex + cacheValidUntil time.Time +} + +// NewHandlerDiscovery returns a new HandlerDiscovery on top of the given gRPC connection +func NewHandlerDiscovery(component *core.Component) HandlerDiscovery { + return &handlerDiscovery{component: component} +} + +func (d *handlerDiscovery) refreshCache() error { + // Connect to the server + conn, err := grpc.Dial(d.component.DiscoveryServer, api.DialOptions...) + if err != nil { + return err + } + defer conn.Close() + client := pb.NewDiscoveryClient(conn) + res, err := client.Discover(d.component.GetContext(), &pb.DiscoverRequest{ServiceName: "handler"}) + if err != nil { + return err + } + // TODO: validate response + d.cacheLock.Lock() + defer d.cacheLock.Unlock() + d.cacheValidUntil = time.Now().Add(HandlerCacheTime) + d.cache = res.Services + d.updateLookups() + return nil +} + +func (d *handlerDiscovery) update() { + if time.Now().After(d.cacheValidUntil) { + d.cacheValidUntil = time.Now().Add(10 * time.Second) + go d.refreshCache() + } +} + +func (d *handlerDiscovery) updateLookups() { + d.byID = map[string]*pb.Announcement{} + d.byAppID = map[string][]*pb.Announcement{} + for _, service := range d.cache { + d.byID[service.Id] = service + for _, meta := range service.Metadata { + switch meta.Key { + case pb.Metadata_APP_ID: + announcedID := string(meta.Value) + if forID, ok := d.byAppID[announcedID]; ok { + d.byAppID[announcedID] = append(forID, service) + } else { + d.byAppID[announcedID] = []*pb.Announcement{service} + } + } + } + } +} + +func (d *handlerDiscovery) All() (announcements []*pb.Announcement, err error) { + d.cacheLock.Lock() + defer d.cacheLock.Unlock() + d.update() + announcements = d.cache + return +} + +func (d *handlerDiscovery) Get(id string) (*pb.Announcement, error) { + d.cacheLock.Lock() + defer d.cacheLock.Unlock() + d.update() + match, ok := d.byID[id] + if !ok { + return nil, errors.New("ttn/discovery: Not found") + } + return match, nil +} + +func (d *handlerDiscovery) ForAppID(appID string) ([]*pb.Announcement, error) { + d.cacheLock.Lock() + defer d.cacheLock.Unlock() + d.update() + matches, ok := d.byAppID[appID] + if !ok { + return []*pb.Announcement{}, nil + } + return matches, nil +} diff --git a/core/discovery/handler_discovery_test.go b/core/discovery/handler_discovery_test.go new file mode 100644 index 000000000..eb484492e --- /dev/null +++ b/core/discovery/handler_discovery_test.go @@ -0,0 +1,107 @@ +package discovery + +import ( + "fmt" + "testing" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/smartystreets/assertions" +) + +func buildTestHandlerDiscoveryClient(port uint) *handlerDiscovery { + discovery := NewHandlerDiscovery(&core.Component{DiscoveryServer: fmt.Sprintf("localhost:%d", port)}).(*handlerDiscovery) + discovery.refreshCache() + return discovery +} + +func TestHandlerDiscovery(t *testing.T) { + a := New(t) + + // Handler1 owns one AppEUI + handler1 := &pb.Announcement{ServiceName: "handler", Id: "handler1", Token: "handler1", NetAddress: "localhost1:1881", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-2")}, + }, + } + + // Handler2 has two AppEUIs + handler2 := &pb.Announcement{ServiceName: "handler", Id: "handler2", Token: "handler2", NetAddress: "localhost2:1881", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}, + }, + } + + d := &handlerDiscovery{ + cacheValidUntil: time.Now().Add(10 * time.Minute), + cache: []*pb.Announcement{handler1, handler2}, + } + d.updateLookups() + + announcement, err := d.Get("handler1") + a.So(err, ShouldBeNil) + a.So(announcement, ShouldEqual, handler1) + + results, err := d.All() + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldContain, handler1) + a.So(results, ShouldContain, handler2) + + results, err = d.ForAppID("AppID-1") + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldNotContain, handler1) + a.So(results, ShouldContain, handler2) + + results, err = d.ForAppID("AppID-2") + a.So(err, ShouldBeNil) + a.So(results, ShouldNotBeEmpty) + a.So(results, ShouldContain, handler1) + a.So(results, ShouldNotContain, handler2) + + results, err = d.ForAppID("AppID-3") + a.So(err, ShouldBeNil) + a.So(results, ShouldBeEmpty) +} + +func TestHandlerDiscoveryCache(t *testing.T) { + a := New(t) + + port := randomPort() + + discoveryServer, _ := buildMockDiscoveryServer(port) + + handler := &pb.Announcement{ServiceName: "handler", Token: "handler", NetAddress: "localhost1:1881", + Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}}, + } + + d := &handlerDiscovery{ + component: &core.Component{ + DiscoveryServer: fmt.Sprintf("localhost:%d", port), + }, + cacheValidUntil: time.Now().Add(-1 * time.Minute), + cache: []*pb.Announcement{handler}, + } + d.updateLookups() + + // It should return the cached handler initially + results, err := d.ForAppID("AppID-1") + a.So(err, ShouldBeNil) + a.So(results, ShouldContain, handler) + + // It should still return the cached handler + results, err = d.ForAppID("AppID-1") + a.So(err, ShouldBeNil) + a.So(results, ShouldContain, handler) + + <-time.After(20 * time.Millisecond) + + // It should return the refreshed (empty) handler list + results, err = d.ForAppID("AppID-1") + a.So(err, ShouldBeNil) + a.So(results, ShouldBeEmpty) + + a.So(discoveryServer.discover, ShouldEqual, 1) +} diff --git a/core/handler/activation.go b/core/handler/activation.go index 9a050a0cf..47d04a4e5 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -5,7 +5,6 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/core/types" @@ -27,6 +26,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv ctx := h.Ctx.WithFields(log.Fields{ "DevEUI": devEUI, "AppEUI": appEUI, + "AppID": activation.AppId, }) var err error defer func() { @@ -37,23 +37,9 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // Find Device var dev *device.Device - dev, err = h.devices.Get(*activation.AppEui, *activation.DevEui) + dev, err = h.devices.Get(appEUI, devEUI) if err != nil { - // Find application - var app *application.Application - app, err = h.applications.Get(*activation.AppEui) - if err != nil { - return nil, err - } - if app.DefaultAppKey.IsEmpty() { - return nil, errors.New("ttn/handler: Device not found") - } - // Use Default AppKey - dev = &device.Device{ - AppEUI: *activation.AppEui, - DevEUI: *activation.DevEui, - AppKey: app.DefaultAppKey, - } + return nil, err } // Check for LoRaWAN @@ -174,6 +160,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv Payload: resBytes, DownlinkOption: activation.ResponseTemplate.DownlinkOption, ActivationMetadata: metadata, + AppId: dev.AppID, } return res, nil diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 8b98056c7..2443e8b70 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -70,18 +70,18 @@ func TestHandleActivation(t *testing.T) { var wg sync.WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "AppID-1" devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} unknownDevEUI := types.DevEUI{8, 7, 6, 5, 4, 3, 2, 1} appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} - defaultAppKey := [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8} h.applications.Set(&application.Application{ - AppEUI: appEUI, - DefaultAppKey: defaultAppKey, + AppID: appID, }) h.devices.Set(&device.Device{ + AppID: appID, AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, diff --git a/core/handler/application/application.go b/core/handler/application/application.go index 2871054ca..c13a62a45 100644 --- a/core/handler/application/application.go +++ b/core/handler/application/application.go @@ -1,15 +1,10 @@ package application -import ( - "fmt" - - "github.com/TheThingsNetwork/ttn/core/types" -) +import "fmt" // Application contains the state of an application type Application struct { - AppEUI types.AppEUI - DefaultAppKey types.AppKey + AppID string // Decoder is a JavaScript function that accepts the payload as byte array and // returns an object containing the decoded values Decoder string @@ -23,8 +18,7 @@ type Application struct { // ApplicationProperties contains all properties of a Application that can be stored in Redis. var ApplicationProperties = []string{ - "app_eui", - "default_app_key", + "app_id", "decoder", "converter", "validator", @@ -56,10 +50,8 @@ func (application *Application) FromStringStringMap(input map[string]string) err func (application *Application) formatProperty(property string) (formatted string, err error) { switch property { - case "app_eui": - formatted = application.AppEUI.String() - case "default_app_key": - formatted = application.DefaultAppKey.String() + case "app_id": + formatted = application.AppID case "decoder": formatted = application.Decoder case "converter": @@ -77,22 +69,8 @@ func (application *Application) parseProperty(property string, value string) err return nil } switch property { - case "app_eui": - val, err := types.ParseAppEUI(value) - if err != nil { - return err - } - if !val.IsEmpty() { - application.AppEUI = val - } - case "default_app_key": - val, err := types.ParseAppKey(value) - if err != nil { - return err - } - if !val.IsEmpty() { - application.DefaultAppKey = val - } + case "app_id": + application.AppID = value case "decoder": application.Decoder = value case "converter": diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go index 73c8cd240..8cc194a65 100644 --- a/core/handler/application/application_test.go +++ b/core/handler/application/application_test.go @@ -3,23 +3,20 @@ package application import ( "testing" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) func getTestApplication() (application *Application, dmap map[string]string) { return &Application{ - AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, - DefaultAppKey: types.AppKey{8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1}, - Decoder: `function (payload) { return { size: payload.length; } }`, - Converter: `function (data) { return data; }`, - Validator: `function (data) { return data.size % 2 == 0; }`, + AppID: "AppID-1", + Decoder: `function (payload) { return { size: payload.length; } }`, + Converter: `function (data) { return data; }`, + Validator: `function (data) { return data.size % 2 == 0; }`, }, map[string]string{ - "app_eui": "0807060504030201", - "default_app_key": "08070605040302010807060504030201", - "decoder": `function (payload) { return { size: payload.length; } }`, - "converter": `function (data) { return data; }`, - "validator": `function (data) { return data.size % 2 == 0; }`, + "app_id": "AppID-1", + "decoder": `function (payload) { return { size: payload.length; } }`, + "converter": `function (data) { return data; }`, + "validator": `function (data) { return data.size % 2 == 0; }`, } } diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 97386d757..4c5bedf69 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -5,8 +5,6 @@ import ( "fmt" "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/types" ) var ( @@ -19,24 +17,24 @@ type Store interface { // List all applications List() ([]*Application, error) // Get the full information about a application - Get(appEUI types.AppEUI) (*Application, error) + Get(appID string) (*Application, error) // Set the given fields of a application. If fields empty, it sets all fields. Set(application *Application, fields ...string) error // Delete a application - Delete(types.AppEUI) error + Delete(appid string) error } // NewApplicationStore creates a new in-memory Application store func NewApplicationStore() Store { return &applicationStore{ - applications: make(map[types.AppEUI]*Application), + applications: make(map[string]*Application), } } // applicationStore is an in-memory Application store. It should only be used for testing // purposes. Use the redisApplicationStore for actual deployments. type applicationStore struct { - applications map[types.AppEUI]*Application + applications map[string]*Application } func (s *applicationStore) List() ([]*Application, error) { @@ -47,8 +45,8 @@ func (s *applicationStore) List() ([]*Application, error) { return apps, nil } -func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { - if app, ok := s.applications[appEUI]; ok { +func (s *applicationStore) Get(appID string) (*Application, error) { + if app, ok := s.applications[appID]; ok { return app, nil } return nil, ErrNotFound @@ -56,12 +54,12 @@ func (s *applicationStore) Get(appEUI types.AppEUI) (*Application, error) { func (s *applicationStore) Set(new *Application, fields ...string) error { // NOTE: We don't care about fields for testing - s.applications[new.AppEUI] = new + s.applications[new.AppID] = new return nil } -func (s *applicationStore) Delete(appEUI types.AppEUI) error { - delete(s.applications, appEUI) +func (s *applicationStore) Delete(appID string) error { + delete(s.applications, appID) return nil } @@ -99,8 +97,8 @@ func (s *redisApplicationStore) List() ([]*Application, error) { return apps, nil } -func (s *redisApplicationStore) Get(appEUI types.AppEUI) (*Application, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI)).Result() +func (s *redisApplicationStore) Get(appID string) (*Application, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appID)).Result() if err != nil { if err == redis.Nil { return nil, ErrNotFound @@ -122,7 +120,7 @@ func (s *redisApplicationStore) Set(new *Application, fields ...string) error { fields = ApplicationProperties } - key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppEUI) + key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppID) dmap, err := new.ToStringStringMap(fields...) if err != nil { return err @@ -135,8 +133,8 @@ func (s *redisApplicationStore) Set(new *Application, fields ...string) error { return nil } -func (s *redisApplicationStore) Delete(appEUI types.AppEUI) error { - key := fmt.Sprintf("%s:%s", redisApplicationPrefix, appEUI) +func (s *redisApplicationStore) Delete(appID string) error { + key := fmt.Sprintf("%s:%s", redisApplicationPrefix, appID) err := s.client.Del(key).Err() if err != nil { return err diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go index 4a0a67b4a..279d951c6 100644 --- a/core/handler/application/store_test.go +++ b/core/handler/application/store_test.go @@ -5,7 +5,6 @@ import ( "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) @@ -29,21 +28,21 @@ func TestApplicationStore(t *testing.T) { t.Logf("Testing %s store", name) - appEUI := types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1} + appID := "AppID-1" // Get non-existing - app, err := s.Get(appEUI) + app, err := s.Get(appID) a.So(err, ShouldNotBeNil) a.So(app, ShouldBeNil) // Create err = s.Set(&Application{ - AppEUI: appEUI, + AppID: appID, }) a.So(err, ShouldBeNil) // Get existing - app, err = s.Get(appEUI) + app, err = s.Get(appID) a.So(err, ShouldBeNil) a.So(app, ShouldNotBeNil) @@ -53,11 +52,11 @@ func TestApplicationStore(t *testing.T) { a.So(apps, ShouldHaveLength, 1) // Delete - err = s.Delete(appEUI) + err = s.Delete(appID) a.So(err, ShouldBeNil) // Get deleted - app, err = s.Get(appEUI) + app, err = s.Get(appID) a.So(err, ShouldNotBeNil) a.So(app, ShouldBeNil) } diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index d36bcd010..064daedbe 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -16,7 +16,7 @@ import ( // ConvertFields converts the payload to fields using payload functions func (h *handler) ConvertFields(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { // Find Application - app, err := h.applications.Get(appUp.AppEUI) + app, err := h.applications.Get(ttnUp.AppId) if err != nil { return nil // Do not process if application not found } diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 8a76fe389..63536c723 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -17,7 +17,9 @@ import ( func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { appEUI, _ := types.ParseAppEUI("0102030405060708") - ttnUp := &pb_broker.DeduplicatedUplinkMessage{} + ttnUp := &pb_broker.DeduplicatedUplinkMessage{ + AppId: "AppID-1", + } appUp := &mqtt.UplinkMessage{ FPort: 1, AppEUI: appEUI, @@ -28,7 +30,7 @@ func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.Uplink func TestConvertFields(t *testing.T) { a := New(t) - appEUI, _ := types.ParseAppEUI("0102030405060708") + appID := "AppID-1" h := &handler{ applications: application.NewApplicationStore(), @@ -42,7 +44,7 @@ func TestConvertFields(t *testing.T) { // Normal flow h.applications.Set(&application.Application{ - AppEUI: appEUI, + AppID: appID, Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, }) ttnUp, appUp = buildConversionUplink() @@ -55,7 +57,7 @@ func TestConvertFields(t *testing.T) { // Invalidate data h.applications.Set(&application.Application{ - AppEUI: appEUI, + AppID: appID, Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, Validator: `function(data) { return false; }`, }) @@ -66,7 +68,7 @@ func TestConvertFields(t *testing.T) { // Function error h.applications.Set(&application.Application{ - AppEUI: appEUI, + AppID: appID, Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, Converter: `function(data) { throw "expected"; }`, }) diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 50df11d9c..ce9394996 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -22,6 +22,7 @@ type AppNonce [3]byte type Device struct { DevEUI types.DevEUI AppEUI types.AppEUI + AppID string DevAddr types.DevAddr AppKey types.AppKey UsedDevNonces []DevNonce @@ -35,6 +36,7 @@ type Device struct { var DeviceProperties = []string{ "dev_eui", "app_eui", + "app_id", "dev_addr", "app_key", "nwk_s_key", @@ -74,6 +76,8 @@ func (device *Device) formatProperty(property string) (formatted string, err err formatted = device.DevEUI.String() case "app_eui": formatted = device.AppEUI.String() + case "app_id": + formatted = device.AppID case "dev_addr": formatted = device.DevAddr.String() case "app_key": @@ -121,6 +125,8 @@ func (device *Device) parseProperty(property string, value string) error { return err } device.AppEUI = val + case "app_id": + device.AppID = value case "dev_addr": val, err := types.ParseDevAddr(value) if err != nil { diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index 994c8d59c..9e7b54ff8 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -12,6 +12,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { return &Device{ DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + AppID: "AppID-1", DevAddr: types.DevAddr{1, 2, 3, 4}, AppKey: types.AppKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, NwkSKey: types.NwkSKey{1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8}, @@ -21,6 +22,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { }, map[string]string{ "dev_eui": "0102030405060708", "app_eui": "0807060504030201", + "app_id": "AppID-1", "dev_addr": "01020304", "app_key": "00010002000300040005000600070008", "nwk_s_key": "01010102010301040105010601070108", diff --git a/core/handler/handler.go b/core/handler/handler.go index 34166df49..2087b5dd4 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -10,6 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" @@ -66,6 +67,21 @@ func (h *handler) Init(c *core.Component) error { if err != nil { return err } + + h.applications.Set(&application.Application{ + AppID: "htdvisser", + }) + + apps, err := h.applications.List() + if err != nil { + return err + } + for _, app := range apps { + h.Component.Identity.Metadata = append(h.Component.Identity.Metadata, &pb_discovery.Metadata{ + Key: pb_discovery.Metadata_APP_ID, Value: []byte(app.AppID), + }) + } + err = h.Component.Announce() if err != nil { return err @@ -75,7 +91,6 @@ func (h *handler) Init(c *core.Component) error { for _, broker := range h.mqttBrokers { brokers = append(brokers, fmt.Sprintf("tcp://%s", broker)) } - err = h.HandleMQTT(h.mqttUsername, h.mqttPassword, brokers...) if err != nil { return err diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index 43ef98165..d9a196212 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -21,6 +21,7 @@ func TestHandleUplink(t *testing.T) { var err error var wg sync.WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "AppID-1" devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleUplink")}, @@ -28,11 +29,12 @@ func TestHandleUplink(t *testing.T) { applications: application.NewApplicationStore(), } h.devices.Set(&device.Device{ + AppID: appID, AppEUI: appEUI, DevEUI: devEUI, }) h.applications.Set(&application.Application{ - AppEUI: appEUI, + AppID: appID, }) h.mqttUp = make(chan *mqtt.UplinkMessage) h.downlink = make(chan *pb_broker.DownlinkMessage) diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 385478022..6ed3ff801 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -1,6 +1,7 @@ package device import ( + "encoding/json" "fmt" "time" @@ -10,14 +11,15 @@ import ( // Options for the specified device type Options struct { - DisableFCntCheck bool // Disable Frame counter check (insecure) - Uses32BitFCnt bool // Use 32-bit Frame counters + DisableFCntCheck bool `json:"disable_fcnt_check,omitemtpy"` // Disable Frame counter check (insecure) + Uses32BitFCnt bool `json:"uses_32_bit_fcnt,omitemtpy"` // Use 32-bit Frame counters } // Device contains the state of a device type Device struct { DevEUI types.DevEUI AppEUI types.AppEUI + AppID string DevAddr types.DevAddr NwkSKey types.NwkSKey FCntUp uint32 @@ -31,6 +33,7 @@ type Device struct { var DeviceProperties = []string{ "dev_eui", "app_eui", + "app_id", "dev_addr", "nwk_s_key", "f_cnt_up", @@ -70,6 +73,8 @@ func (device *Device) formatProperty(property string) (formatted string, err err formatted = device.DevEUI.String() case "app_eui": formatted = device.AppEUI.String() + case "app_id": + formatted = device.AppID case "dev_addr": formatted = device.DevAddr.String() case "nwk_s_key": @@ -81,7 +86,11 @@ func (device *Device) formatProperty(property string) (formatted string, err err case "last_seen": formatted = device.LastSeen.UTC().Format(time.RFC3339Nano) case "options": - // TODO + data, err := json.Marshal(device.Options) + if err != nil { + return "", err + } + formatted = string(data) case "utilization": // TODO default: @@ -107,6 +116,8 @@ func (device *Device) parseProperty(property string, value string) error { return err } device.AppEUI = val + case "app_id": + device.AppID = value case "dev_addr": val, err := types.ParseDevAddr(value) if err != nil { @@ -138,7 +149,12 @@ func (device *Device) parseProperty(property string, value string) error { } device.LastSeen = val case "options": - // TODO + var options Options + err := json.Unmarshal([]byte(value), &options) + if err != nil { + return err + } + device.Options = options case "utilization": // TODO } diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go index 906c08e84..0f0e4ad7c 100644 --- a/core/networkserver/device/device_test.go +++ b/core/networkserver/device/device_test.go @@ -12,19 +12,23 @@ func getTestDevice() (device *Device, dmap map[string]string) { return &Device{ DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + AppID: "TestApp-1", DevAddr: types.DevAddr{1, 2, 3, 4}, NwkSKey: types.NwkSKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, FCntUp: 42, FCntDown: 24, LastSeen: time.Unix(0, 0).UTC(), + Options: Options{}, }, map[string]string{ "dev_eui": "0102030405060708", "app_eui": "0807060504030201", + "app_id": "TestApp-1", "dev_addr": "01020304", "nwk_s_key": "00010002000300040005000600070008", "f_cnt_up": "42", "f_cnt_down": "24", "last_seen": "1970-01-01T00:00:00Z", + "options": `{"disable_fcnt_check":false,"uses_32_bit_fcnt":false}`, } } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 6a3848045..ddf68cb55 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -78,12 +78,12 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes // Return all devices with DevAddr with FCnt <= fCnt or Security off res := &pb.DevicesResponse{ - Results: make([]*pb.DevicesResponse_Device, 0, len(devices)), + Results: make([]*pb.Device, 0, len(devices)), } for _, device := range devices { fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) - dev := &pb.DevicesResponse_Device{ + dev := &pb.Device{ AppEui: &device.AppEUI, AppId: device.AppID, DevEui: &device.DevEUI, diff --git a/mqtt/types.go b/mqtt/types.go index a3d04ddc4..0ce26a30e 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -23,6 +23,7 @@ type Metadata struct { // UplinkMessage represents an application-layer uplink message type UplinkMessage struct { + AppID string `json:"app_id,omitempty"` AppEUI types.AppEUI `json:"app_eui,omitempty"` DevEUI types.DevEUI `json:"dev_eui,omitempty"` Payload []byte `json:"payload,omitempty"` @@ -34,6 +35,7 @@ type UplinkMessage struct { // DownlinkMessage represents an application-layer downlink message type DownlinkMessage struct { + AppID string `json:"app_id,omitempty"` AppEUI types.AppEUI `json:"app_eui,omitempty"` DevEUI types.DevEUI `json:"dev_eui,omitempty"` Payload []byte `json:"payload,omitempty"` @@ -44,6 +46,7 @@ type DownlinkMessage struct { // Activation are used to notify application of a device activation type Activation struct { + AppID string `json:"app_id,omitempty"` AppEUI types.AppEUI `json:"app_eui,omitempty"` DevEUI types.DevEUI `json:"dev_eui,omitempty"` Metadata []Metadata `json:"metadata"` From 646bfc71325555d7aadd1c002894aaeba704dfbc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 22 Jun 2016 17:54:44 +0200 Subject: [PATCH 1530/2266] Move Device from networkserver to lorawan --- api/broker/broker.pb.go | 246 ++---- api/broker/broker.proto | 9 - api/networkserver/networkserver.pb.go | 607 ++------------- api/networkserver/networkserver.proto | 22 +- api/protocol/lorawan/device.pb.go | 1020 +++++++++++++++++++++++++ api/protocol/lorawan/device.proto | 32 + api/protocol/lorawan/lorawan.pb.go | 20 - core/broker/broker.go | 7 +- core/broker/broker_test.go | 3 +- core/broker/uplink.go | 12 +- core/broker/uplink_test.go | 19 +- core/networkserver/networkserver.go | 8 +- 12 files changed, 1191 insertions(+), 814 deletions(-) create mode 100644 api/protocol/lorawan/device.pb.go create mode 100644 api/protocol/lorawan/device.proto diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index e430e43d3..6beb49c74 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -710,12 +710,6 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ // Client API for BrokerManager service type BrokerManagerClient interface { - // Network operator lists all Applications on this Broker - Applications(ctx context.Context, in *ApplicationsRequest, opts ...grpc.CallOption) (*ApplicationsResponse, error) - // Application owner registers Application with Broker Manager - RegisterApplication(ctx context.Context, in *RegisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) - // Application owner unregisters Application with Broker Manager - UnregisterApplication(ctx context.Context, in *UnregisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) // Network operator requests Broker status GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } @@ -728,33 +722,6 @@ func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { return &brokerManagerClient{cc} } -func (c *brokerManagerClient) Applications(ctx context.Context, in *ApplicationsRequest, opts ...grpc.CallOption) (*ApplicationsResponse, error) { - out := new(ApplicationsResponse) - err := grpc.Invoke(ctx, "/broker.BrokerManager/Applications", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *brokerManagerClient) RegisterApplication(ctx context.Context, in *RegisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) - err := grpc.Invoke(ctx, "/broker.BrokerManager/RegisterApplication", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *brokerManagerClient) UnregisterApplication(ctx context.Context, in *UnregisterApplicationRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) - err := grpc.Invoke(ctx, "/broker.BrokerManager/UnregisterApplication", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { out := new(Status) err := grpc.Invoke(ctx, "/broker.BrokerManager/GetStatus", in, out, c.cc, opts...) @@ -767,12 +734,6 @@ func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, // Server API for BrokerManager service type BrokerManagerServer interface { - // Network operator lists all Applications on this Broker - Applications(context.Context, *ApplicationsRequest) (*ApplicationsResponse, error) - // Application owner registers Application with Broker Manager - RegisterApplication(context.Context, *RegisterApplicationRequest) (*api.Ack, error) - // Application owner unregisters Application with Broker Manager - UnregisterApplication(context.Context, *UnregisterApplicationRequest) (*api.Ack, error) // Network operator requests Broker status GetStatus(context.Context, *StatusRequest) (*Status, error) } @@ -781,60 +742,6 @@ func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { s.RegisterService(&_BrokerManager_serviceDesc, srv) } -func _BrokerManager_Applications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(ApplicationsRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerManagerServer).Applications(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/broker.BrokerManager/Applications", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).Applications(ctx, req.(*ApplicationsRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _BrokerManager_RegisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RegisterApplicationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerManagerServer).RegisterApplication(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/broker.BrokerManager/RegisterApplication", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).RegisterApplication(ctx, req.(*RegisterApplicationRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _BrokerManager_UnregisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UnregisterApplicationRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(BrokerManagerServer).UnregisterApplication(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/broker.BrokerManager/UnregisterApplication", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(BrokerManagerServer).UnregisterApplication(ctx, req.(*UnregisterApplicationRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _BrokerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { @@ -857,18 +764,6 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "broker.BrokerManager", HandlerType: (*BrokerManagerServer)(nil), Methods: []grpc.MethodDesc{ - { - MethodName: "Applications", - Handler: _BrokerManager_Applications_Handler, - }, - { - MethodName: "RegisterApplication", - Handler: _BrokerManager_RegisterApplication_Handler, - }, - { - MethodName: "UnregisterApplication", - Handler: _BrokerManager_UnregisterApplication_Handler, - }, { MethodName: "GetStatus", Handler: _BrokerManager_GetStatus_Handler, @@ -4447,76 +4342,73 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1134 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4d, 0x6f, 0xe3, 0x44, - 0x18, 0x26, 0xc9, 0x6e, 0xda, 0xbe, 0xf9, 0xec, 0xb4, 0x69, 0xdc, 0x50, 0xb5, 0xc5, 0x20, 0x54, - 0x3e, 0x36, 0x61, 0x83, 0xca, 0xaa, 0x42, 0xb0, 0x4a, 0xb7, 0xab, 0x52, 0xa4, 0x2c, 0x2b, 0x6f, - 0x7a, 0xe1, 0x12, 0x39, 0xf6, 0x34, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x6e, 0xff, 0x03, 0x12, - 0x17, 0x0e, 0x1c, 0xf8, 0x21, 0x5c, 0x11, 0x17, 0x8e, 0x9c, 0x38, 0x70, 0x40, 0x08, 0x2e, 0xfc, - 0x0c, 0xc6, 0xe3, 0xf1, 0x57, 0x1a, 0xef, 0x76, 0x97, 0x45, 0x82, 0x15, 0x07, 0x2b, 0x9e, 0xf7, - 0xe3, 0xf1, 0xf8, 0x79, 0xdf, 0xe7, 0xf5, 0x04, 0xee, 0x8c, 0x09, 0x9b, 0x78, 0xa3, 0xb6, 0x61, - 0x9f, 0x75, 0x06, 0x13, 0x3c, 0x98, 0x10, 0x6b, 0x4c, 0x1f, 0x60, 0x76, 0x61, 0xbb, 0xa7, 0x1d, - 0xc6, 0xac, 0x8e, 0xee, 0x90, 0xce, 0xc8, 0xb5, 0x4f, 0xb1, 0x2b, 0x7f, 0xda, 0x8e, 0x6b, 0x33, - 0x1b, 0x15, 0x83, 0x55, 0xeb, 0x56, 0x02, 0x60, 0x6c, 0x8f, 0xed, 0x8e, 0x70, 0x8f, 0xbc, 0x13, - 0xb1, 0x12, 0x0b, 0x71, 0x17, 0xa4, 0xa5, 0xc2, 0x33, 0x9f, 0xc7, 0x2f, 0x19, 0xfe, 0xe1, 0x75, - 0xc2, 0x45, 0xa8, 0x61, 0x4f, 0xa3, 0x1b, 0x99, 0xbc, 0x77, 0x9d, 0xe4, 0xb1, 0xce, 0xf0, 0x85, - 0x7e, 0x19, 0xfe, 0x06, 0xa9, 0xea, 0x0f, 0x79, 0xa8, 0x1e, 0xd8, 0x17, 0xd6, 0x94, 0x58, 0xa7, - 0x9f, 0x39, 0x8c, 0xd8, 0x16, 0xda, 0x04, 0x20, 0x26, 0xb6, 0x18, 0x39, 0x21, 0xd8, 0x55, 0x72, - 0xdb, 0xb9, 0x9d, 0x25, 0x2d, 0x61, 0x41, 0x9f, 0x43, 0x49, 0x62, 0x0c, 0xb1, 0x47, 0x94, 0x3c, - 0x0f, 0x28, 0xef, 0xef, 0xfd, 0xf2, 0xeb, 0xd6, 0xee, 0xd3, 0xb6, 0x61, 0xd8, 0x2e, 0xee, 0xb0, - 0x4b, 0x07, 0xd3, 0xf6, 0x61, 0x80, 0x70, 0xff, 0xf8, 0x48, 0x03, 0x89, 0x76, 0xdf, 0x23, 0x68, - 0x15, 0x6e, 0x52, 0x3f, 0x4a, 0x29, 0x70, 0xd4, 0x8a, 0x16, 0x2c, 0x50, 0x0b, 0x16, 0x4d, 0xac, - 0x9b, 0x7c, 0x8f, 0x58, 0xb9, 0xc1, 0x1d, 0x05, 0x2d, 0x5a, 0xa3, 0x7d, 0xa8, 0x85, 0x6c, 0x0c, - 0x0d, 0xdb, 0x3a, 0x21, 0x63, 0xe5, 0x26, 0x0f, 0x29, 0x75, 0xd7, 0xdb, 0x11, 0x4b, 0x83, 0xc7, - 0xf7, 0x84, 0xc7, 0x73, 0x75, 0xff, 0x0d, 0xb5, 0x6a, 0xe8, 0x09, 0xcc, 0xe8, 0x2e, 0x54, 0xc3, - 0x37, 0x92, 0x10, 0x45, 0x01, 0xa1, 0xb4, 0x43, 0xb2, 0x66, 0x11, 0x2a, 0xd2, 0x11, 0x58, 0xd5, - 0xaf, 0x0a, 0x50, 0x39, 0x76, 0x7c, 0x0e, 0xfb, 0x98, 0x52, 0x7d, 0x8c, 0x91, 0x02, 0x0b, 0x8e, - 0x7e, 0x39, 0xb5, 0x75, 0x53, 0x30, 0x58, 0xd6, 0xc2, 0x25, 0x7a, 0x00, 0x0b, 0x26, 0x3e, 0x17, - 0xd4, 0x95, 0x04, 0x75, 0xbb, 0x9c, 0xba, 0xdb, 0xcf, 0x40, 0xdd, 0x01, 0x3e, 0xf7, 0x69, 0x2b, - 0x72, 0x14, 0x9f, 0x32, 0x8e, 0xa7, 0x3b, 0x8e, 0xc0, 0x2b, 0x3f, 0x17, 0x5e, 0xcf, 0x71, 0x04, - 0x1e, 0x47, 0xf1, 0xf1, 0x7a, 0xb0, 0x1c, 0x11, 0x7a, 0x86, 0x99, 0x6e, 0xea, 0x4c, 0x57, 0x1a, - 0x82, 0x8f, 0xd5, 0x98, 0x52, 0xed, 0x71, 0x5f, 0xfa, 0xb4, 0x7a, 0x68, 0x0c, 0x2d, 0xe8, 0x63, - 0xa8, 0x87, 0x7c, 0x46, 0x08, 0x6b, 0x02, 0x61, 0x25, 0x62, 0x34, 0x01, 0x50, 0x93, 0xb6, 0x28, - 0xbf, 0x07, 0x75, 0x53, 0xf6, 0xe4, 0xd0, 0x16, 0x4d, 0x49, 0x95, 0xad, 0xed, 0x02, 0xcf, 0x5f, - 0x6b, 0x4b, 0x6d, 0xa6, 0x7b, 0x56, 0xab, 0x99, 0xa9, 0x35, 0x55, 0xbf, 0xcc, 0x43, 0x2d, 0x8c, - 0xf9, 0xef, 0xd7, 0xe4, 0x2e, 0xd4, 0x66, 0x08, 0x91, 0x15, 0xc9, 0xe2, 0xa3, 0x9a, 0xe6, 0x43, - 0xf5, 0x40, 0xe1, 0x5b, 0x24, 0x06, 0xee, 0x19, 0x8c, 0x9c, 0x07, 0x3d, 0x8c, 0xa9, 0xc3, 0x99, - 0x7a, 0x12, 0x2d, 0x73, 0x1e, 0x5b, 0x7a, 0xa6, 0xc7, 0xfe, 0x5c, 0x80, 0xf5, 0x03, 0x6c, 0x7a, - 0x5c, 0x1a, 0x06, 0xaf, 0xb1, 0xf9, 0xb2, 0x68, 0xa4, 0x01, 0xfe, 0xdd, 0x90, 0x98, 0x4a, 0x45, - 0x8c, 0xc7, 0x9b, 0x7c, 0x75, 0x64, 0xfe, 0x73, 0xd2, 0x29, 0x5c, 0x5b, 0x3a, 0x5b, 0x50, 0xa2, - 0xd8, 0x3d, 0xc7, 0xee, 0x90, 0x91, 0x33, 0xac, 0x34, 0xc5, 0xb4, 0x84, 0xc0, 0x34, 0xe0, 0x16, - 0x74, 0x00, 0xcb, 0xae, 0xac, 0xfc, 0x90, 0xe1, 0x33, 0x67, 0xca, 0x01, 0xb8, 0xb8, 0xfc, 0x3d, - 0x36, 0x67, 0xab, 0x2a, 0x0b, 0xa5, 0xd5, 0xc3, 0x8c, 0x81, 0x4c, 0x50, 0xff, 0x2c, 0x40, 0xf3, - 0x6a, 0x43, 0x7d, 0xe1, 0x61, 0xca, 0xfe, 0x1f, 0x7d, 0x7f, 0x67, 0xf4, 0xf5, 0x61, 0x45, 0x8f, - 0x18, 0x8d, 0x21, 0x9a, 0x02, 0x62, 0x23, 0xde, 0x44, 0x4c, 0x7b, 0x84, 0x85, 0xf4, 0x2b, 0xb6, - 0x17, 0x31, 0x49, 0xbf, 0xbb, 0x01, 0xaf, 0x27, 0x35, 0xfc, 0xf2, 0x95, 0xfd, 0xdf, 0xab, 0xe6, - 0x17, 0xdc, 0x0d, 0x33, 0xc3, 0x41, 0xb9, 0x32, 0x1c, 0xfa, 0xd9, 0xc3, 0x61, 0x3b, 0xea, 0x97, - 0x8c, 0xef, 0xc8, 0x9c, 0x29, 0x81, 0xa0, 0xfe, 0xc8, 0x1b, 0x51, 0xc3, 0x25, 0x23, 0x2c, 0xdb, - 0x44, 0x6d, 0xc0, 0x0a, 0xa7, 0x5f, 0xf4, 0x92, 0xdf, 0x5e, 0xa1, 0xf9, 0x36, 0xac, 0xa6, 0xcd, - 0xf2, 0xe3, 0xb4, 0x0e, 0x8b, 0xb2, 0xd6, 0x94, 0xb7, 0x55, 0x81, 0x57, 0x67, 0x21, 0xa8, 0x1a, - 0x55, 0x77, 0xa1, 0xa5, 0xe1, 0x31, 0xa1, 0x0c, 0xbb, 0x89, 0xd4, 0xb0, 0x1d, 0x9b, 0x71, 0x93, - 0x04, 0x47, 0x58, 0x59, 0x6d, 0xf5, 0x0e, 0x6c, 0x1c, 0x5b, 0xee, 0x73, 0x24, 0xd6, 0xa0, 0xf2, - 0x88, 0xe9, 0xcc, 0x8b, 0xf6, 0xfc, 0x7d, 0x01, 0x8a, 0x81, 0x05, 0xa9, 0x50, 0xf4, 0xc4, 0xb7, - 0x4d, 0xe4, 0x94, 0xba, 0xd0, 0xf6, 0x8f, 0xf6, 0x1a, 0x27, 0x81, 0x6a, 0xd2, 0x83, 0x3a, 0x50, - 0x09, 0xee, 0x86, 0x9e, 0x45, 0x38, 0x82, 0x38, 0x39, 0xa7, 0x43, 0xcb, 0x41, 0xc0, 0xb1, 0xf0, - 0xa3, 0x37, 0xf9, 0xb1, 0x57, 0x8a, 0x51, 0x7e, 0x77, 0x93, 0xb1, 0x91, 0x0f, 0xbd, 0x0b, 0xa5, - 0xb8, 0xd8, 0x54, 0xb6, 0x68, 0x32, 0x34, 0xe9, 0x46, 0x7b, 0x90, 0x68, 0x0d, 0x1a, 0xee, 0x65, - 0xed, 0x4a, 0xd2, 0x72, 0x22, 0x4a, 0x6e, 0xe8, 0x23, 0x58, 0x4d, 0xa6, 0xea, 0x86, 0x81, 0x1d, - 0x3e, 0x11, 0x64, 0x3f, 0x26, 0x93, 0x13, 0x6d, 0x4b, 0x7b, 0x32, 0x0c, 0x7d, 0x00, 0x15, 0x33, - 0x1a, 0x24, 0xfe, 0x61, 0x22, 0xe8, 0xac, 0xba, 0xc8, 0x7b, 0x88, 0x5d, 0xc3, 0xff, 0x8b, 0x31, - 0xe5, 0xd9, 0xe9, 0x30, 0xf4, 0x0e, 0x2c, 0xf3, 0x63, 0xb9, 0x85, 0x0d, 0x0e, 0x32, 0x74, 0x6d, - 0x8f, 0xd7, 0x8d, 0x2a, 0x6f, 0x89, 0x3f, 0x08, 0xf5, 0xc8, 0xa1, 0x05, 0x76, 0x74, 0x0b, 0x50, - 0x1c, 0x3c, 0xd1, 0x2d, 0x73, 0xea, 0x47, 0xbf, 0x2d, 0xa2, 0x63, 0x98, 0x4f, 0xa4, 0xa3, 0xfb, - 0x75, 0x1e, 0x8a, 0xfb, 0xa2, 0xb1, 0xf9, 0x69, 0x67, 0xa9, 0x47, 0xa9, 0x6d, 0x10, 0xfe, 0x06, - 0xa8, 0x11, 0xb6, 0x7b, 0xea, 0xc8, 0xd2, 0xca, 0xfa, 0x44, 0xee, 0xe4, 0xde, 0xcb, 0xa1, 0x4f, - 0x61, 0x29, 0x6a, 0x77, 0xa4, 0x84, 0x91, 0xb3, 0x0a, 0x68, 0xbd, 0x16, 0x2b, 0x29, 0xe3, 0x64, - 0xc4, 0xb1, 0xda, 0xb0, 0xf0, 0xd0, 0x1b, 0x4d, 0x09, 0x9d, 0xa0, 0xac, 0x67, 0xb6, 0x16, 0x05, - 0x71, 0x3d, 0xe3, 0x74, 0x27, 0xc7, 0x95, 0xbb, 0x28, 0x25, 0x89, 0xd1, 0x56, 0xb6, 0x54, 0x83, - 0x1d, 0x3c, 0x55, 0xcb, 0xdd, 0x6f, 0xf3, 0x50, 0x09, 0x68, 0xe9, 0xeb, 0x16, 0x7f, 0x96, 0x8b, - 0x8e, 0xa0, 0x9c, 0x14, 0x28, 0x7a, 0x35, 0xc4, 0x98, 0xa3, 0xe6, 0xd6, 0xc6, 0x7c, 0xa7, 0xd4, - 0xf4, 0x3d, 0x58, 0x99, 0x23, 0x5c, 0xa4, 0x86, 0x49, 0xd9, 0xaa, 0x8e, 0x5f, 0x19, 0x1d, 0x42, - 0x63, 0xae, 0x8c, 0xd1, 0x1b, 0x51, 0xe5, 0x9e, 0xa0, 0xf2, 0x04, 0x50, 0x17, 0x96, 0x0e, 0x31, - 0x93, 0x3a, 0x8e, 0xca, 0x9e, 0x52, 0x7a, 0xab, 0x9a, 0x36, 0xef, 0xd7, 0x7f, 0xfc, 0x7d, 0x33, - 0xf7, 0x13, 0xbf, 0x7e, 0xe3, 0xd7, 0x37, 0x7f, 0x6c, 0xbe, 0x32, 0x2a, 0x8a, 0x59, 0xfc, 0xfe, - 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x40, 0x6b, 0x27, 0xc9, 0x67, 0x10, 0x00, 0x00, + // 1088 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0xcd, 0x6f, 0xe3, 0x44, + 0x14, 0x27, 0xcd, 0x36, 0x6d, 0x5f, 0x9a, 0x8f, 0x4e, 0x3f, 0xe2, 0x46, 0xab, 0xb6, 0x04, 0x09, + 0x95, 0x8f, 0x4d, 0xd8, 0xa0, 0xb2, 0xaa, 0x10, 0xac, 0xd2, 0xed, 0x6a, 0x59, 0xa4, 0x2c, 0x2b, + 0x6f, 0x7a, 0xe1, 0x12, 0x39, 0xf6, 0x34, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x6e, 0xff, 0x07, + 0x24, 0x2e, 0x1c, 0xf8, 0x53, 0xb8, 0x22, 0x2e, 0x1c, 0x39, 0x71, 0xe0, 0x80, 0x10, 0x5c, 0xf8, + 0x33, 0x78, 0x9e, 0x19, 0x3b, 0x76, 0xba, 0x61, 0xbb, 0xcb, 0x22, 0x41, 0xc5, 0xc1, 0x8a, 0xe7, + 0x7d, 0xfc, 0x3c, 0xfe, 0xbd, 0xdf, 0x7b, 0x9e, 0xc0, 0x9d, 0x21, 0x13, 0xa3, 0x70, 0xd0, 0xb4, + 0xbd, 0xd3, 0x56, 0x6f, 0x44, 0x7b, 0x23, 0xe6, 0x0e, 0xf9, 0x23, 0x2a, 0xce, 0xbd, 0xe0, 0xa4, + 0x25, 0x84, 0xdb, 0xb2, 0x7c, 0xd6, 0x1a, 0x04, 0xde, 0x09, 0x0d, 0xf4, 0x4f, 0xd3, 0x0f, 0x3c, + 0xe1, 0x91, 0x82, 0x5a, 0xd5, 0x6f, 0xa5, 0x00, 0x86, 0xde, 0xd0, 0x6b, 0x49, 0xf7, 0x20, 0x3c, + 0x96, 0x2b, 0xb9, 0x90, 0x77, 0x2a, 0x2d, 0x13, 0x3e, 0xf3, 0x79, 0x78, 0xe9, 0xf0, 0x0f, 0xaf, + 0x12, 0x2e, 0x43, 0x6d, 0x6f, 0x9c, 0xdc, 0xe8, 0xe4, 0xfd, 0xab, 0x24, 0x0f, 0x2d, 0x41, 0xcf, + 0xad, 0x8b, 0xf8, 0x57, 0xa5, 0x36, 0xbe, 0x9f, 0x83, 0xf2, 0xa1, 0x77, 0xee, 0x8e, 0x99, 0x7b, + 0xf2, 0x99, 0x2f, 0x98, 0xe7, 0x92, 0x2d, 0x00, 0xe6, 0x50, 0x57, 0xb0, 0x63, 0x46, 0x03, 0x23, + 0xb7, 0x93, 0xdb, 0x5d, 0x32, 0x53, 0x16, 0xf2, 0x39, 0x14, 0x35, 0x46, 0x9f, 0x86, 0xcc, 0x98, + 0xc3, 0x80, 0xe5, 0x83, 0xfd, 0x9f, 0x7f, 0xd9, 0xde, 0x7b, 0xde, 0x36, 0x6c, 0x2f, 0xa0, 0x2d, + 0x71, 0xe1, 0x53, 0xde, 0x7c, 0xa0, 0x10, 0xee, 0x1f, 0x3d, 0x34, 0x41, 0xa3, 0xdd, 0x0f, 0x19, + 0x59, 0x83, 0x79, 0x1e, 0x45, 0x19, 0x79, 0x44, 0x2d, 0x99, 0x6a, 0x41, 0xea, 0xb0, 0xe8, 0x50, + 0xcb, 0xc1, 0x3d, 0x52, 0xe3, 0x06, 0x3a, 0xf2, 0x66, 0xb2, 0x26, 0x07, 0x50, 0x89, 0xd9, 0xe8, + 0xdb, 0x9e, 0x7b, 0xcc, 0x86, 0xc6, 0x3c, 0x86, 0x14, 0xdb, 0x9b, 0xcd, 0x84, 0xa5, 0xde, 0xd3, + 0x7b, 0xd2, 0x13, 0x06, 0x56, 0xf4, 0x86, 0x66, 0x39, 0xf6, 0x28, 0x33, 0xb9, 0x0b, 0xe5, 0xf8, + 0x8d, 0x34, 0x44, 0x41, 0x42, 0x18, 0xcd, 0x98, 0xac, 0x69, 0x84, 0x92, 0x76, 0x28, 0x6b, 0xe3, + 0xab, 0x3c, 0x94, 0x8e, 0xfc, 0x88, 0xc3, 0x2e, 0xe5, 0xdc, 0x1a, 0x52, 0x62, 0xc0, 0x82, 0x6f, + 0x5d, 0x8c, 0x3d, 0xcb, 0x91, 0x0c, 0x2e, 0x9b, 0xf1, 0x92, 0x3c, 0x82, 0x05, 0x87, 0x9e, 0x49, + 0xea, 0x8a, 0x92, 0xba, 0x3d, 0xa4, 0xee, 0xf6, 0x0b, 0x50, 0x77, 0x48, 0xcf, 0x22, 0xda, 0x0a, + 0x88, 0x12, 0x51, 0x86, 0x78, 0x96, 0xef, 0x4b, 0xbc, 0xe5, 0x97, 0xc2, 0xeb, 0xf8, 0xbe, 0xc4, + 0x43, 0x94, 0x08, 0xaf, 0x03, 0x2b, 0x09, 0xa1, 0xa7, 0x54, 0x58, 0x8e, 0x25, 0x2c, 0x63, 0x5d, + 0xf2, 0xb1, 0x36, 0xa1, 0xd4, 0x7c, 0xda, 0xd5, 0x3e, 0xb3, 0x1a, 0x1b, 0x63, 0x0b, 0xf9, 0x18, + 0xaa, 0x31, 0x9f, 0x09, 0xc2, 0x86, 0x44, 0x58, 0x4d, 0x18, 0x4d, 0x01, 0x54, 0xb4, 0x2d, 0xc9, + 0xef, 0x40, 0xd5, 0xd1, 0x9a, 0xec, 0x7b, 0x52, 0x94, 0xdc, 0xd8, 0xde, 0xc9, 0x63, 0xfe, 0x46, + 0x53, 0xf7, 0x66, 0x56, 0xb3, 0x66, 0xc5, 0xc9, 0xac, 0x79, 0xe3, 0xcb, 0x39, 0xa8, 0xc4, 0x31, + 0xff, 0xfd, 0x9a, 0xdc, 0x85, 0xca, 0x14, 0x21, 0xba, 0x22, 0xb3, 0xf8, 0x28, 0x67, 0xf9, 0x68, + 0x84, 0x60, 0xe0, 0x16, 0x99, 0x4d, 0x3b, 0xb6, 0x60, 0x67, 0x4a, 0xc3, 0x94, 0xfb, 0xc8, 0xd4, + 0x5f, 0xd1, 0xf2, 0x8c, 0xc7, 0x16, 0x5f, 0xe8, 0xb1, 0x3f, 0xe5, 0x61, 0xf3, 0x90, 0x3a, 0x21, + 0xb6, 0x86, 0x8d, 0x35, 0x76, 0xae, 0x4b, 0x8f, 0xac, 0x43, 0x74, 0xd7, 0x67, 0x8e, 0x51, 0x92, + 0xe3, 0x71, 0x1e, 0x57, 0x0f, 0x9d, 0x7f, 0xae, 0x75, 0xf2, 0x57, 0x6e, 0x9d, 0x6d, 0x28, 0x72, + 0x1a, 0x9c, 0xd1, 0xa0, 0x2f, 0xd8, 0x29, 0x35, 0x6a, 0x72, 0x5a, 0x82, 0x32, 0xf5, 0xd0, 0x42, + 0x0e, 0x61, 0x25, 0xd0, 0x95, 0xef, 0x0b, 0x7a, 0xea, 0x8f, 0x11, 0x00, 0x9b, 0x2b, 0xda, 0x63, + 0x6d, 0xba, 0xaa, 0xba, 0x50, 0x66, 0x35, 0xce, 0xe8, 0xe9, 0x84, 0xc6, 0x1f, 0x79, 0xa8, 0x5d, + 0x16, 0xd4, 0x17, 0x21, 0xe5, 0xe2, 0xff, 0xd1, 0xf7, 0x77, 0x46, 0x5f, 0x17, 0x56, 0xad, 0x84, + 0xd1, 0x09, 0x44, 0x4d, 0x42, 0xdc, 0x9c, 0x6c, 0x62, 0x42, 0x7b, 0x82, 0x45, 0xac, 0x4b, 0xb6, + 0x57, 0x31, 0x49, 0xbf, 0xbd, 0x01, 0x6f, 0xa4, 0x7b, 0xf8, 0xfa, 0x95, 0xfd, 0xdf, 0xdb, 0xcd, + 0xaf, 0x58, 0x0d, 0x53, 0xc3, 0xc1, 0xb8, 0x34, 0x1c, 0xba, 0xb3, 0x87, 0xc3, 0x4e, 0xa2, 0x97, + 0x19, 0xdf, 0x91, 0x67, 0x4c, 0x09, 0x02, 0xd5, 0x27, 0xe1, 0x80, 0xdb, 0x01, 0x1b, 0x50, 0x2d, + 0x93, 0xc6, 0x3a, 0xac, 0x22, 0xfd, 0x52, 0x4b, 0x91, 0xbc, 0x62, 0xf3, 0x6d, 0x58, 0xcb, 0x9a, + 0xf5, 0xc7, 0x69, 0x13, 0x16, 0x75, 0xad, 0x39, 0xca, 0x2a, 0x8f, 0xd5, 0x59, 0x50, 0x55, 0xe3, + 0x8d, 0x3d, 0xa8, 0x9b, 0x74, 0xc8, 0xb8, 0xa0, 0x41, 0x2a, 0x35, 0x96, 0x63, 0x6d, 0x22, 0x12, + 0x75, 0x84, 0xd5, 0xd5, 0x6e, 0xdc, 0x81, 0x9b, 0x47, 0x6e, 0xf0, 0x12, 0x89, 0x15, 0x28, 0x3d, + 0x11, 0x96, 0x08, 0x93, 0x3d, 0x7f, 0x97, 0x87, 0x82, 0xb2, 0x90, 0x06, 0x14, 0x42, 0xf9, 0x6d, + 0x93, 0x39, 0xc5, 0x36, 0x34, 0xa3, 0xa3, 0xbd, 0x89, 0x24, 0x70, 0x53, 0x7b, 0x48, 0x0b, 0x4a, + 0xea, 0xae, 0x1f, 0xba, 0x0c, 0x11, 0xe4, 0xc9, 0x39, 0x1b, 0xba, 0xac, 0x02, 0x8e, 0xa4, 0x9f, + 0xbc, 0x89, 0xc7, 0x5e, 0xdd, 0x8c, 0xfa, 0xbb, 0x9b, 0x8e, 0x4d, 0x7c, 0xe4, 0x5d, 0x28, 0x4e, + 0x8a, 0xcd, 0xb5, 0x44, 0xd3, 0xa1, 0x69, 0x37, 0xd9, 0x87, 0x94, 0x34, 0x78, 0xbc, 0x97, 0x8d, + 0x4b, 0x49, 0x2b, 0xa9, 0x28, 0xbd, 0xa1, 0x8f, 0x60, 0x2d, 0x9d, 0x6a, 0xd9, 0x36, 0xf5, 0x71, + 0x22, 0x68, 0x3d, 0xa6, 0x93, 0x53, 0xb2, 0xe5, 0x1d, 0x1d, 0x46, 0x3e, 0x80, 0x92, 0x93, 0x0c, + 0x92, 0xe8, 0x30, 0xa1, 0x94, 0x55, 0x95, 0x79, 0x8f, 0x69, 0x60, 0x47, 0x7f, 0x31, 0xc6, 0x98, + 0x9d, 0x0d, 0x23, 0xef, 0xc0, 0x0a, 0x1e, 0xcb, 0x5d, 0x6a, 0x23, 0x48, 0x3f, 0xf0, 0x42, 0xac, + 0x1b, 0x37, 0xde, 0x92, 0x7f, 0x10, 0xaa, 0x89, 0xc3, 0x54, 0x76, 0x72, 0x0b, 0xc8, 0x24, 0x78, + 0x64, 0xb9, 0xce, 0x38, 0x8a, 0x7e, 0x5b, 0x46, 0x4f, 0x60, 0x3e, 0xd1, 0x8e, 0xf6, 0xd7, 0x73, + 0x50, 0x38, 0x90, 0xc2, 0xc6, 0xd3, 0xce, 0x52, 0x87, 0x73, 0xcf, 0x66, 0xf8, 0x06, 0x64, 0x3d, + 0x96, 0x7b, 0xe6, 0xc8, 0x52, 0x9f, 0xf5, 0x89, 0xdc, 0xcd, 0xbd, 0x97, 0x23, 0x9f, 0xc2, 0x52, + 0x22, 0x77, 0x62, 0xc4, 0x91, 0xd3, 0x1d, 0x50, 0x7f, 0x7d, 0xd2, 0x49, 0x33, 0x4e, 0x46, 0x88, + 0xd5, 0x84, 0x85, 0xc7, 0xe1, 0x60, 0xcc, 0xf8, 0x88, 0xcc, 0x7a, 0x66, 0x7d, 0x51, 0x12, 0xd7, + 0xb1, 0x4f, 0x76, 0x73, 0xd8, 0xb9, 0x8b, 0xba, 0x25, 0x29, 0xd9, 0x9e, 0xdd, 0xaa, 0x6a, 0x07, + 0xcf, 0xed, 0xe5, 0xf6, 0x3d, 0x28, 0x29, 0x56, 0xba, 0x96, 0x8b, 0x8f, 0x0a, 0x48, 0x1b, 0x96, + 0x1e, 0x50, 0xa1, 0xd5, 0x9e, 0x90, 0x93, 0xe9, 0x87, 0x7a, 0x39, 0x6b, 0x3e, 0xa8, 0xfe, 0xf0, + 0xdb, 0x56, 0xee, 0x47, 0xbc, 0x7e, 0xc5, 0xeb, 0x9b, 0xdf, 0xb7, 0x5e, 0x1b, 0x14, 0xe4, 0xc4, + 0x7a, 0xff, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x73, 0x68, 0xd7, 0xce, 0x8d, 0x0f, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 5e05a5473..603c486cf 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -141,15 +141,6 @@ message Status { // The BrokerManager service provides configuration and monitoring functionality service BrokerManager { - // Network operator lists all Applications on this Broker - rpc Applications(ApplicationsRequest) returns (ApplicationsResponse); - - // Application owner registers Application with Broker Manager - rpc RegisterApplication(RegisterApplicationRequest) returns (api.Ack); - - // Application owner unregisters Application with Broker Manager - rpc UnregisterApplication(UnregisterApplicationRequest) returns (api.Ack); - // Network operator requests Broker status rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index dccd9048d..ecb5ccb3e 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -10,9 +10,7 @@ It has these top-level messages: DevicesRequest - Device DevicesResponse - RegisterDeviceRequest StatusRequest Status */ @@ -23,6 +21,7 @@ import fmt "fmt" import math "math" import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" +import lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" import broker "github.com/TheThingsNetwork/ttn/api/broker" import handler "github.com/TheThingsNetwork/ttn/api/handler" @@ -54,50 +53,22 @@ func (m *DevicesRequest) String() string { return proto.CompactTextSt func (*DevicesRequest) ProtoMessage() {} func (*DevicesRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{0} } -type Device struct { - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,3,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - StoredFCnt uint32 `protobuf:"varint,4,opt,name=stored_f_cnt,json=storedFCnt,proto3" json:"stored_f_cnt,omitempty"` - FullFCnt uint32 `protobuf:"varint,5,opt,name=full_f_cnt,json=fullFCnt,proto3" json:"full_f_cnt,omitempty"` - AppId string `protobuf:"bytes,6,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` - DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` - Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` -} - -func (m *Device) Reset() { *m = Device{} } -func (m *Device) String() string { return proto.CompactTextString(m) } -func (*Device) ProtoMessage() {} -func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1} } - type DevicesResponse struct { - Results []*Device `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` + Results []*lorawan.Device `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"` } func (m *DevicesResponse) Reset() { *m = DevicesResponse{} } func (m *DevicesResponse) String() string { return proto.CompactTextString(m) } func (*DevicesResponse) ProtoMessage() {} -func (*DevicesResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{2} } +func (*DevicesResponse) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{1} } -func (m *DevicesResponse) GetResults() []*Device { +func (m *DevicesResponse) GetResults() []*lorawan.Device { if m != nil { return m.Results } return nil } -// message RegisterDeviceRequest is used to register a device in the -// NetworkServer -type RegisterDeviceRequest struct { -} - -func (m *RegisterDeviceRequest) Reset() { *m = RegisterDeviceRequest{} } -func (m *RegisterDeviceRequest) String() string { return proto.CompactTextString(m) } -func (*RegisterDeviceRequest) ProtoMessage() {} -func (*RegisterDeviceRequest) Descriptor() ([]byte, []int) { - return fileDescriptorNetworkserver, []int{3} -} - // message StatusRequest is used to request the status of this NetworkServer type StatusRequest struct { } @@ -105,7 +76,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{4} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{2} } // message Status is the response to the StatusRequest type Status struct { @@ -116,7 +87,7 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{5} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{3} } func (m *Status) GetDevicesPerAddress() *api.Percentiles { if m != nil { @@ -127,9 +98,7 @@ func (m *Status) GetDevicesPerAddress() *api.Percentiles { func init() { proto.RegisterType((*DevicesRequest)(nil), "networkserver.DevicesRequest") - proto.RegisterType((*Device)(nil), "networkserver.Device") proto.RegisterType((*DevicesResponse)(nil), "networkserver.DevicesResponse") - proto.RegisterType((*RegisterDeviceRequest)(nil), "networkserver.RegisterDeviceRequest") proto.RegisterType((*StatusRequest)(nil), "networkserver.StatusRequest") proto.RegisterType((*Status)(nil), "networkserver.Status") } @@ -350,7 +319,6 @@ var _NetworkServer_serviceDesc = grpc.ServiceDesc{ // Client API for NetworkServerManager service type NetworkServerManagerClient interface { - RegisterDevice(ctx context.Context, in *RegisterDeviceRequest, opts ...grpc.CallOption) (*api.Ack, error) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } @@ -362,15 +330,6 @@ func NewNetworkServerManagerClient(cc *grpc.ClientConn) NetworkServerManagerClie return &networkServerManagerClient{cc} } -func (c *networkServerManagerClient) RegisterDevice(ctx context.Context, in *RegisterDeviceRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) - err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/RegisterDevice", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *networkServerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { out := new(Status) err := grpc.Invoke(ctx, "/networkserver.NetworkServerManager/GetStatus", in, out, c.cc, opts...) @@ -383,7 +342,6 @@ func (c *networkServerManagerClient) GetStatus(ctx context.Context, in *StatusRe // Server API for NetworkServerManager service type NetworkServerManagerServer interface { - RegisterDevice(context.Context, *RegisterDeviceRequest) (*api.Ack, error) GetStatus(context.Context, *StatusRequest) (*Status, error) } @@ -391,24 +349,6 @@ func RegisterNetworkServerManagerServer(s *grpc.Server, srv NetworkServerManager s.RegisterService(&_NetworkServerManager_serviceDesc, srv) } -func _NetworkServerManager_RegisterDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RegisterDeviceRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(NetworkServerManagerServer).RegisterDevice(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/networkserver.NetworkServerManager/RegisterDevice", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(NetworkServerManagerServer).RegisterDevice(ctx, req.(*RegisterDeviceRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _NetworkServerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { @@ -431,10 +371,6 @@ var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "networkserver.NetworkServerManager", HandlerType: (*NetworkServerManagerServer)(nil), Methods: []grpc.MethodDesc{ - { - MethodName: "RegisterDevice", - Handler: _NetworkServerManager_RegisterDevice_Handler, - }, { MethodName: "GetStatus", Handler: _NetworkServerManager_GetStatus_Handler, @@ -476,90 +412,6 @@ func (m *DevicesRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Device) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *Device) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.AppEui != nil { - data[i] = 0xa - i++ - i = encodeVarintNetworkserver(data, i, uint64(m.AppEui.Size())) - n2, err := m.AppEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n2 - } - if m.DevEui != nil { - data[i] = 0x12 - i++ - i = encodeVarintNetworkserver(data, i, uint64(m.DevEui.Size())) - n3, err := m.DevEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } - if m.NwkSKey != nil { - data[i] = 0x1a - i++ - i = encodeVarintNetworkserver(data, i, uint64(m.NwkSKey.Size())) - n4, err := m.NwkSKey.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n4 - } - if m.StoredFCnt != 0 { - data[i] = 0x20 - i++ - i = encodeVarintNetworkserver(data, i, uint64(m.StoredFCnt)) - } - if m.FullFCnt != 0 { - data[i] = 0x28 - i++ - i = encodeVarintNetworkserver(data, i, uint64(m.FullFCnt)) - } - if len(m.AppId) > 0 { - data[i] = 0x32 - i++ - i = encodeVarintNetworkserver(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) - } - if m.DisableFCntCheck { - data[i] = 0x58 - i++ - if m.DisableFCntCheck { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.Uses32BitFCnt { - data[i] = 0x60 - i++ - if m.Uses32BitFCnt { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - return i, nil -} - func (m *DevicesResponse) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -590,24 +442,6 @@ func (m *DevicesResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *RegisterDeviceRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *RegisterDeviceRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - func (m *StatusRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -645,11 +479,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintNetworkserver(data, i, uint64(m.DevicesPerAddress.Size())) - n5, err := m.DevicesPerAddress.MarshalTo(data[i:]) + n2, err := m.DevicesPerAddress.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n2 } return i, nil } @@ -694,40 +528,6 @@ func (m *DevicesRequest) Size() (n int) { return n } -func (m *Device) Size() (n int) { - var l int - _ = l - if m.AppEui != nil { - l = m.AppEui.Size() - n += 1 + l + sovNetworkserver(uint64(l)) - } - if m.DevEui != nil { - l = m.DevEui.Size() - n += 1 + l + sovNetworkserver(uint64(l)) - } - if m.NwkSKey != nil { - l = m.NwkSKey.Size() - n += 1 + l + sovNetworkserver(uint64(l)) - } - if m.StoredFCnt != 0 { - n += 1 + sovNetworkserver(uint64(m.StoredFCnt)) - } - if m.FullFCnt != 0 { - n += 1 + sovNetworkserver(uint64(m.FullFCnt)) - } - l = len(m.AppId) - if l > 0 { - n += 1 + l + sovNetworkserver(uint64(l)) - } - if m.DisableFCntCheck { - n += 2 - } - if m.Uses32BitFCnt { - n += 2 - } - return n -} - func (m *DevicesResponse) Size() (n int) { var l int _ = l @@ -740,12 +540,6 @@ func (m *DevicesResponse) Size() (n int) { return n } -func (m *RegisterDeviceRequest) Size() (n int) { - var l int - _ = l - return n -} - func (m *StatusRequest) Size() (n int) { var l int _ = l @@ -876,259 +670,6 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { } return nil } -func (m *Device) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Device: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthNetworkserver - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.AppEUI - m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthNetworkserver - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.DevEUI - m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthNetworkserver - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey - m.NwkSKey = &v - if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field StoredFCnt", wireType) - } - m.StoredFCnt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.StoredFCnt |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 5: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FullFCnt", wireType) - } - m.FullFCnt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FullFCnt |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 6: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthNetworkserver - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppId = string(data[iNdEx:postIndex]) - iNdEx = postIndex - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field DisableFCntCheck", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.DisableFCntCheck = bool(v != 0) - case 12: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Uses32BitFCnt", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.Uses32BitFCnt = bool(v != 0) - default: - iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthNetworkserver - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *DevicesResponse) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -1184,7 +725,7 @@ func (m *DevicesResponse) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Results = append(m.Results, &Device{}) + m.Results = append(m.Results, &lorawan.Device{}) if err := m.Results[len(m.Results)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { return err } @@ -1210,56 +751,6 @@ func (m *DevicesResponse) Unmarshal(data []byte) error { } return nil } -func (m *RegisterDeviceRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowNetworkserver - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RegisterDeviceRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RegisterDeviceRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthNetworkserver - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *StatusRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -1499,50 +990,38 @@ var ( ) var fileDescriptorNetworkserver = []byte{ - // 705 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xdd, 0x4e, 0x13, 0x41, - 0x14, 0xb6, 0xfc, 0xb4, 0x65, 0xa0, 0x50, 0x06, 0x1b, 0x9a, 0x06, 0x11, 0x1b, 0x13, 0x49, 0x0c, - 0x6d, 0x2c, 0x51, 0x63, 0xe2, 0x05, 0x2d, 0xa0, 0x41, 0x02, 0xc1, 0x05, 0xae, 0x37, 0xdb, 0xdd, - 0xd3, 0x76, 0xd2, 0x32, 0xbb, 0xce, 0xcc, 0xb6, 0xf2, 0x26, 0xc6, 0x57, 0xf0, 0x45, 0xbc, 0xf4, - 0xda, 0x0b, 0x63, 0xf4, 0x45, 0x3c, 0x3b, 0x33, 0x45, 0x8b, 0x25, 0xfc, 0x5c, 0x6c, 0x3a, 0x73, - 0xbe, 0xef, 0x7c, 0xe7, 0xcc, 0x99, 0x6f, 0x4a, 0x76, 0xdb, 0x4c, 0x75, 0xe2, 0x66, 0xc5, 0x0f, - 0xcf, 0xaa, 0x27, 0x1d, 0x38, 0xe9, 0x30, 0xde, 0x96, 0x87, 0xa0, 0x06, 0xa1, 0xe8, 0x56, 0x95, - 0xe2, 0x55, 0x2f, 0x62, 0x55, 0x6e, 0xf6, 0x12, 0x44, 0x1f, 0xc4, 0xe8, 0xae, 0x12, 0x89, 0x50, - 0x85, 0x34, 0x37, 0x12, 0x2c, 0x6d, 0xfc, 0xa3, 0xda, 0x0e, 0xdb, 0x61, 0x55, 0xb3, 0x9a, 0x71, - 0x4b, 0xef, 0xf4, 0x46, 0xaf, 0x4c, 0xf6, 0x08, 0xfd, 0xca, 0x26, 0xf0, 0xb3, 0xf4, 0x97, 0x37, - 0xa1, 0x37, 0x45, 0xd8, 0xc5, 0x66, 0xcd, 0x8f, 0x4d, 0x7c, 0x75, 0x93, 0xc4, 0x8e, 0xc7, 0x83, - 0x1e, 0x66, 0xda, 0x5f, 0x93, 0x5a, 0xfe, 0x48, 0xe6, 0x77, 0xa0, 0xcf, 0x7c, 0x90, 0x0e, 0x7c, - 0x88, 0x41, 0x2a, 0xfa, 0x9e, 0x64, 0x03, 0xe8, 0xbb, 0x5e, 0x10, 0x88, 0x62, 0x6a, 0x2d, 0xb5, - 0x3e, 0xd7, 0x78, 0xf1, 0xfd, 0xc7, 0xc3, 0xda, 0x75, 0x25, 0xfc, 0x50, 0x40, 0x55, 0x9d, 0x47, - 0x20, 0x2b, 0x28, 0x58, 0xc7, 0x6c, 0x27, 0x13, 0x98, 0x05, 0x5d, 0x22, 0xd3, 0x2d, 0xd7, 0xe7, - 0xaa, 0x38, 0x81, 0x7a, 0x39, 0x67, 0xaa, 0xb5, 0xcd, 0x55, 0xf9, 0xcb, 0x24, 0x49, 0x9b, 0xd2, - 0xf4, 0x90, 0x64, 0xbc, 0x28, 0x72, 0x21, 0x66, 0xb6, 0xe2, 0x73, 0xac, 0xf8, 0xec, 0x16, 0x15, - 0xeb, 0x51, 0xb4, 0x7b, 0xba, 0xe7, 0xa4, 0x51, 0x65, 0x37, 0x66, 0x89, 0x5e, 0x72, 0x84, 0x44, - 0x6f, 0xe2, 0x4e, 0x7a, 0xd8, 0x97, 0xd6, 0x43, 0x95, 0x44, 0xcf, 0x21, 0x33, 0x7c, 0xd0, 0x75, - 0xa5, 0xdb, 0x85, 0xf3, 0xe2, 0xe4, 0x9d, 0x66, 0x72, 0x38, 0xe8, 0x1e, 0xef, 0xc3, 0xb9, 0x93, - 0xe1, 0x66, 0x41, 0xd7, 0xc8, 0x9c, 0x54, 0x88, 0x07, 0xae, 0x19, 0xcd, 0x94, 0x1e, 0x0d, 0x31, - 0xb1, 0x37, 0x38, 0x20, 0xba, 0x42, 0x48, 0x2b, 0xee, 0xf5, 0x2c, 0x3e, 0xad, 0xf1, 0x6c, 0x12, - 0xd1, 0x68, 0x81, 0x24, 0xa7, 0x75, 0x59, 0x50, 0x4c, 0x23, 0x32, 0xe3, 0x4c, 0xe3, 0x6e, 0x2f, - 0xa0, 0x1b, 0x64, 0x29, 0x60, 0xd2, 0x6b, 0xf6, 0xc0, 0xe4, 0xb9, 0x7e, 0x07, 0xfc, 0x6e, 0x71, - 0x16, 0x39, 0x59, 0x27, 0x6f, 0xa1, 0x44, 0x60, 0x3b, 0x89, 0xd3, 0x27, 0x24, 0x1f, 0x4b, 0x90, - 0x9b, 0x35, 0xb7, 0xc9, 0x94, 0xad, 0x34, 0xa7, 0xb9, 0x39, 0x13, 0x6f, 0x30, 0x95, 0xb0, 0xcb, - 0x0d, 0xb2, 0x70, 0xe1, 0x13, 0x19, 0x85, 0x5c, 0x02, 0xad, 0x92, 0x8c, 0x00, 0x19, 0xf7, 0x94, - 0xc4, 0x5b, 0x9b, 0x5c, 0x9f, 0xad, 0x15, 0x2a, 0xa3, 0x4f, 0xc8, 0x24, 0x38, 0x43, 0x56, 0x79, - 0x99, 0x14, 0x1c, 0x68, 0x33, 0xa9, 0x40, 0x58, 0xc8, 0x58, 0xae, 0xbc, 0x40, 0x72, 0xc7, 0xca, - 0x53, 0xf1, 0xd0, 0x83, 0xe5, 0x77, 0x24, 0x6d, 0x02, 0x74, 0x0b, 0xcf, 0x63, 0xea, 0xba, 0x11, - 0x08, 0xed, 0x4a, 0x90, 0x52, 0xdb, 0x64, 0xb6, 0x96, 0xaf, 0x24, 0x8f, 0xe7, 0x08, 0x84, 0x0f, - 0x5c, 0xb1, 0x1e, 0xf6, 0xb6, 0x68, 0xc9, 0x18, 0xab, 0x1b, 0x6a, 0x0d, 0x7d, 0x96, 0xb3, 0x97, - 0x72, 0xac, 0xfb, 0xa2, 0xfb, 0x84, 0xbc, 0x05, 0x65, 0x8f, 0x43, 0x1f, 0x8c, 0xed, 0x7a, 0xd8, - 0x4a, 0x69, 0xf5, 0x2a, 0xd8, 0x4e, 0xe1, 0x8c, 0x2c, 0x1e, 0x09, 0x88, 0x3c, 0x01, 0x75, 0x5f, - 0xb1, 0xbe, 0xa7, 0x58, 0xc8, 0xe9, 0xd3, 0x8a, 0x7d, 0x9f, 0x3b, 0x10, 0xc4, 0x51, 0x8f, 0xf9, - 0x9e, 0x82, 0xc0, 0x64, 0xfe, 0x65, 0x0d, 0x2b, 0xdc, 0x86, 0x4c, 0x8f, 0x48, 0xd6, 0x06, 0x81, - 0x3e, 0xaa, 0x0c, 0xdf, 0xf2, 0xff, 0x6c, 0xd3, 0x5d, 0xe9, 0x7a, 0x0a, 0x3e, 0x96, 0xf4, 0x29, - 0x56, 0xe5, 0x5d, 0xd4, 0x1b, 0xd3, 0x88, 0xc1, 0x0e, 0x70, 0x92, 0x5e, 0x3b, 0xd1, 0xbb, 0x8e, - 0x42, 0x5f, 0x93, 0xec, 0x4e, 0x38, 0xe0, 0x5a, 0x71, 0xf9, 0x82, 0x6e, 0x23, 0x43, 0x9d, 0xab, - 0x80, 0xda, 0xe7, 0x14, 0xb9, 0x3f, 0x72, 0x5b, 0x07, 0x1e, 0xc7, 0xb8, 0x40, 0x23, 0xcc, 0x8f, - 0x9a, 0x87, 0x3e, 0xbe, 0x74, 0x33, 0x63, 0xbd, 0x55, 0xca, 0x6a, 0x8f, 0xd4, 0xd1, 0xeb, 0x5b, - 0x64, 0x06, 0xaf, 0xdd, 0xfa, 0x6a, 0xe5, 0x52, 0xf2, 0x88, 0xff, 0x4a, 0x85, 0xb1, 0x68, 0x23, - 0xff, 0xf5, 0xd7, 0x6a, 0xea, 0x1b, 0x7e, 0x3f, 0xf1, 0xfb, 0xf4, 0x7b, 0xf5, 0x5e, 0x33, 0xad, - 0xff, 0x45, 0x37, 0xff, 0x04, 0x00, 0x00, 0xff, 0xff, 0xac, 0xbc, 0x1b, 0x87, 0x6f, 0x06, 0x00, - 0x00, + // 519 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0xa6, 0x0c, 0xba, 0xe2, 0x51, 0xba, 0x79, 0x20, 0xa6, 0x0a, 0x0a, 0xf4, 0x6a, 0x08, 0x91, + 0x48, 0x41, 0x02, 0x21, 0xed, 0x82, 0x8e, 0x22, 0x24, 0xd0, 0xa6, 0xd2, 0x0d, 0x89, 0xbb, 0xca, + 0x4d, 0x4e, 0xd3, 0xa8, 0x99, 0x1d, 0x6c, 0xa7, 0x85, 0x37, 0xe1, 0x1d, 0x78, 0x11, 0x2e, 0xb9, + 0xe6, 0x02, 0x21, 0x78, 0x11, 0xce, 0x6c, 0x67, 0x10, 0x68, 0xd5, 0xee, 0xc2, 0x8a, 0x7d, 0xce, + 0x77, 0xbe, 0xf3, 0x9d, 0x9f, 0x90, 0x17, 0x71, 0xa2, 0xc7, 0xf9, 0xd0, 0x0b, 0xc5, 0x89, 0x7f, + 0x3c, 0x86, 0xe3, 0x71, 0xc2, 0x63, 0x75, 0x08, 0x7a, 0x26, 0xe4, 0xc4, 0xd7, 0x9a, 0xfb, 0x2c, + 0x4b, 0x7c, 0x6e, 0xdf, 0x0a, 0xe4, 0x14, 0x64, 0xf9, 0xe5, 0x65, 0x52, 0x68, 0x41, 0xeb, 0x25, + 0x63, 0xf3, 0xe1, 0x5f, 0xac, 0xb1, 0x88, 0x85, 0x6f, 0x50, 0xc3, 0x7c, 0x64, 0x5e, 0xe6, 0x61, + 0x6e, 0x36, 0xba, 0x04, 0x5f, 0x28, 0x02, 0x8f, 0x83, 0x77, 0x56, 0x81, 0x1b, 0x68, 0x28, 0x52, + 0x3f, 0x15, 0x92, 0xcd, 0x18, 0xf7, 0x23, 0x98, 0x26, 0x21, 0x38, 0x8a, 0x27, 0xab, 0x50, 0x0c, + 0xa5, 0x98, 0x60, 0xbd, 0xf6, 0xe3, 0x02, 0x9f, 0xae, 0x12, 0x38, 0x66, 0x3c, 0x4a, 0x31, 0xd2, + 0x7d, 0x6d, 0x68, 0xfb, 0x03, 0xb9, 0xd6, 0x35, 0x1a, 0x54, 0x1f, 0xde, 0xe7, 0xa0, 0x34, 0x7d, + 0x43, 0x6a, 0xa8, 0x6a, 0xc0, 0xa2, 0x48, 0xee, 0x54, 0xee, 0x56, 0x76, 0xaf, 0xee, 0x3f, 0xfe, + 0xf6, 0xfd, 0x4e, 0xb0, 0x2c, 0x45, 0x28, 0x24, 0xf8, 0xfa, 0x63, 0x06, 0xca, 0x43, 0xc2, 0x0e, + 0x46, 0xf7, 0xd7, 0x23, 0x7b, 0xa1, 0xdb, 0xe4, 0xf2, 0x68, 0x10, 0x72, 0xbd, 0x73, 0x11, 0xf9, + 0xea, 0xfd, 0x4b, 0xa3, 0xe7, 0x5c, 0xb7, 0xf7, 0x48, 0xe3, 0x2c, 0xb3, 0xca, 0x04, 0x57, 0x40, + 0xef, 0x93, 0x75, 0x09, 0x2a, 0x4f, 0xb5, 0xc2, 0xcc, 0x6b, 0xbb, 0x1b, 0x41, 0xc3, 0x73, 0x8d, + 0xf2, 0x2c, 0xb4, 0x5f, 0xf8, 0xdb, 0x0d, 0x52, 0x3f, 0xd2, 0x4c, 0xe7, 0x85, 0xec, 0xf6, 0x2b, + 0x52, 0xb5, 0x06, 0xfa, 0x8c, 0x6c, 0xdb, 0xb6, 0xaa, 0x41, 0x06, 0xd2, 0x14, 0x02, 0x4a, 0x99, + 0x5a, 0x36, 0x82, 0x4d, 0xef, 0x74, 0x64, 0x3d, 0x90, 0x21, 0x70, 0x9d, 0xa4, 0x98, 0x7c, 0xcb, + 0x81, 0xd1, 0xd6, 0xb1, 0xd0, 0xe0, 0xf3, 0x1a, 0xa9, 0xbb, 0xda, 0x8e, 0xcc, 0xee, 0xd0, 0xd7, + 0x84, 0xbc, 0x04, 0xed, 0xf4, 0xd2, 0xdb, 0x5e, 0x79, 0xdd, 0xca, 0x1d, 0x6c, 0xb6, 0x16, 0xb9, + 0x5d, 0x99, 0x27, 0x64, 0xab, 0x27, 0x21, 0x63, 0x12, 0x3a, 0xa1, 0x4e, 0xa6, 0x4c, 0x27, 0x82, + 0xd3, 0x07, 0x9e, 0x1b, 0x69, 0x17, 0xa2, 0x3c, 0x4b, 0x93, 0x90, 0x69, 0x88, 0x6c, 0xe4, 0x1f, + 0x54, 0x91, 0xe1, 0x3c, 0x60, 0xda, 0x23, 0x35, 0x67, 0x04, 0x7a, 0xcf, 0x2b, 0xc6, 0xff, 0x3f, + 0xda, 0xaa, 0x6b, 0x2e, 0x87, 0xd0, 0x43, 0x52, 0x7d, 0x8b, 0x59, 0xf9, 0x04, 0xf9, 0xe6, 0x08, + 0xb1, 0xbe, 0x03, 0xec, 0x24, 0x8b, 0x4f, 0xf9, 0x96, 0x41, 0xe8, 0x1e, 0xa9, 0x75, 0xc5, 0x8c, + 0x1b, 0xc6, 0x9b, 0x67, 0x70, 0x67, 0x29, 0x78, 0x16, 0x39, 0x82, 0x77, 0xe4, 0x7a, 0x69, 0x58, + 0x07, 0x8c, 0xa3, 0x59, 0xe2, 0x1e, 0x5c, 0xc1, 0x99, 0xb9, 0xa5, 0xb8, 0xf5, 0xcf, 0x4c, 0x4a, + 0xcb, 0xd3, 0xbc, 0x31, 0xd7, 0xbb, 0xbf, 0xf9, 0xe5, 0x67, 0xab, 0xf2, 0x15, 0xcf, 0x0f, 0x3c, + 0x9f, 0x7e, 0xb5, 0x2e, 0x0c, 0xab, 0xe6, 0xaf, 0x79, 0xf4, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x70, + 0xbc, 0x3e, 0xb2, 0xa2, 0x04, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 1b373c6a1..3b5f7dfaf 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -3,10 +3,10 @@ syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; - package networkserver; message DevicesRequest { @@ -14,19 +14,8 @@ message DevicesRequest { uint32 f_cnt = 2; } -message Device { - bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; - bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - bytes nwk_s_key = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; - uint32 stored_f_cnt = 4; - uint32 full_f_cnt = 5; - string app_id = 6; - bool disable_f_cnt_check = 11; - bool uses32_bit_f_cnt = 12; -} - message DevicesResponse { - repeated Device results = 1; + repeated lorawan.Device results = 1; } service NetworkServer { @@ -46,12 +35,6 @@ service NetworkServer { rpc Downlink(broker.DownlinkMessage) returns (broker.DownlinkMessage); } -// message RegisterDeviceRequest is used to register a device in the -// NetworkServer -message RegisterDeviceRequest { - // TODO -} - // message StatusRequest is used to request the status of this NetworkServer message StatusRequest {} @@ -64,6 +47,5 @@ message Status { // The NetworkServerManager service provides configuration and monitoring // functionality service NetworkServerManager { - rpc RegisterDevice(RegisterDeviceRequest) returns (api.Ack); rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go new file mode 100644 index 000000000..620ad9919 --- /dev/null +++ b/api/protocol/lorawan/device.pb.go @@ -0,0 +1,1020 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto +// DO NOT EDIT! + +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto + + It has these top-level messages: + DeviceIdentifier + Device + Metadata + TxConfiguration + ActivationMetadata + PHYPayload + MHdr + MACPayload + FHdr + FCtrl +*/ +package lorawan + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" +import api "github.com/TheThingsNetwork/ttn/api" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +const _ = proto.ProtoPackageIsVersion2 + +type DeviceIdentifier struct { + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` +} + +func (m *DeviceIdentifier) Reset() { *m = DeviceIdentifier{} } +func (m *DeviceIdentifier) String() string { return proto.CompactTextString(m) } +func (*DeviceIdentifier) ProtoMessage() {} +func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorDevice, []int{0} } + +type Device struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,2,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,3,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,4,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,5,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + FCntUp uint32 `protobuf:"varint,6,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` + FCntDown uint32 `protobuf:"varint,7,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` + // Options + DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} +func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorDevice, []int{1} } + +func init() { + proto.RegisterType((*DeviceIdentifier)(nil), "lorawan.DeviceIdentifier") + proto.RegisterType((*Device)(nil), "lorawan.Device") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion3 + +// Client API for DeviceManager service + +type DeviceManagerClient interface { + GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) +} + +type deviceManagerClient struct { + cc *grpc.ClientConn +} + +func NewDeviceManagerClient(cc *grpc.ClientConn) DeviceManagerClient { + return &deviceManagerClient{cc} +} + +func (c *deviceManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) { + out := new(Device) + err := grpc.Invoke(ctx, "/lorawan.DeviceManager/GetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/lorawan.DeviceManager/SetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/lorawan.DeviceManager/DeleteDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for DeviceManager service + +type DeviceManagerServer interface { + GetDevice(context.Context, *DeviceIdentifier) (*Device, error) + SetDevice(context.Context, *Device) (*api.Ack, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*api.Ack, error) +} + +func RegisterDeviceManagerServer(s *grpc.Server, srv DeviceManagerServer) { + s.RegisterService(&_DeviceManager_serviceDesc, srv) +} + +func _DeviceManager_GetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeviceManagerServer).GetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DeviceManager/GetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeviceManagerServer).GetDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeviceManager_SetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Device) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeviceManagerServer).SetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DeviceManager/SetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeviceManagerServer).SetDevice(ctx, req.(*Device)) + } + return interceptor(ctx, in, info, handler) +} + +func _DeviceManager_DeleteDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DeviceManagerServer).DeleteDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DeviceManager/DeleteDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DeviceManagerServer).DeleteDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +var _DeviceManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lorawan.DeviceManager", + HandlerType: (*DeviceManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetDevice", + Handler: _DeviceManager_GetDevice_Handler, + }, + { + MethodName: "SetDevice", + Handler: _DeviceManager_SetDevice_Handler, + }, + { + MethodName: "DeleteDevice", + Handler: _DeviceManager_DeleteDevice_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + +func (m *DeviceIdentifier) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceIdentifier) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.AppEui != nil { + data[i] = 0xa + i++ + i = encodeVarintDevice(data, i, uint64(m.AppEui.Size())) + n1, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.DevEui != nil { + data[i] = 0x12 + i++ + i = encodeVarintDevice(data, i, uint64(m.DevEui.Size())) + n2, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if len(m.AppId) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintDevice(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + return i, nil +} + +func (m *Device) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Device) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintDevice(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if m.AppEui != nil { + data[i] = 0x12 + i++ + i = encodeVarintDevice(data, i, uint64(m.AppEui.Size())) + n3, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.DevEui != nil { + data[i] = 0x1a + i++ + i = encodeVarintDevice(data, i, uint64(m.DevEui.Size())) + n4, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.DevAddr != nil { + data[i] = 0x22 + i++ + i = encodeVarintDevice(data, i, uint64(m.DevAddr.Size())) + n5, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.NwkSKey != nil { + data[i] = 0x2a + i++ + i = encodeVarintDevice(data, i, uint64(m.NwkSKey.Size())) + n6, err := m.NwkSKey.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.FCntUp != 0 { + data[i] = 0x30 + i++ + i = encodeVarintDevice(data, i, uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + data[i] = 0x38 + i++ + i = encodeVarintDevice(data, i, uint64(m.FCntDown)) + } + if m.DisableFCntCheck { + data[i] = 0x58 + i++ + if m.DisableFCntCheck { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.Uses32BitFCnt { + data[i] = 0x60 + i++ + if m.Uses32BitFCnt { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + return i, nil +} + +func encodeFixed64Device(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Device(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintDevice(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *DeviceIdentifier) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + return n +} + +func (m *Device) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.NwkSKey != nil { + l = m.NwkSKey.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.FCntUp != 0 { + n += 1 + sovDevice(uint64(m.FCntUp)) + } + if m.FCntDown != 0 { + n += 1 + sovDevice(uint64(m.FCntDown)) + } + if m.DisableFCntCheck { + n += 2 + } + if m.Uses32BitFCnt { + n += 2 + } + return n +} + +func sovDevice(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozDevice(x uint64) (n int) { + return sovDevice(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *DeviceIdentifier) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceIdentifier: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDevice(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey + m.NwkSKey = &v + if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) + } + m.FCntUp = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCntUp |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) + } + m.FCntDown = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCntDown |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field DisableFCntCheck", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.DisableFCntCheck = bool(v != 0) + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Uses32BitFCnt", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Uses32BitFCnt = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipDevice(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDevice + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDevice(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthDevice + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDevice + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipDevice(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthDevice = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDevice = fmt.Errorf("proto: integer overflow") +) + +var fileDescriptorDevice = []byte{ + // 480 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x93, 0xdf, 0x8a, 0xd3, 0x40, + 0x14, 0xc6, 0xcd, 0xd6, 0x4d, 0xd3, 0xb1, 0xc5, 0x12, 0x11, 0x62, 0x91, 0x55, 0xf6, 0x42, 0xbd, + 0xd9, 0x04, 0x5b, 0xd4, 0xeb, 0x76, 0xbb, 0x4a, 0x11, 0x0b, 0x66, 0x77, 0xaf, 0x43, 0xfe, 0x9c, + 0xa6, 0x43, 0xea, 0xcc, 0x90, 0x4c, 0x0c, 0x7d, 0x13, 0x5f, 0xc0, 0x77, 0xd1, 0x3b, 0xaf, 0xbd, + 0x10, 0xd1, 0x77, 0xf0, 0xda, 0x33, 0x33, 0x65, 0xa9, 0x05, 0x59, 0x76, 0xd9, 0x8b, 0x81, 0x39, + 0xe7, 0x7c, 0xdf, 0x6f, 0xe6, 0x30, 0x73, 0xc8, 0x38, 0xa7, 0x72, 0x59, 0x27, 0x7e, 0xca, 0x3f, + 0x04, 0x67, 0x4b, 0x38, 0x5b, 0x52, 0x96, 0x57, 0x73, 0x90, 0x0d, 0x2f, 0x8b, 0x40, 0x4a, 0x16, + 0xc4, 0x82, 0x06, 0xa2, 0xe4, 0x92, 0xa7, 0x7c, 0x15, 0xac, 0x78, 0x19, 0x37, 0x31, 0x0b, 0x32, + 0xf8, 0x48, 0x53, 0xf0, 0x75, 0xde, 0x6d, 0x6f, 0xb2, 0x83, 0xa3, 0x2d, 0x56, 0xce, 0x73, 0x6e, + 0x7c, 0x49, 0xbd, 0xd0, 0x91, 0x0e, 0xf4, 0xce, 0xf8, 0xfe, 0x91, 0xff, 0xf7, 0x68, 0x5c, 0x46, + 0x7e, 0xf8, 0xd5, 0x22, 0xfd, 0xa9, 0x3e, 0x77, 0x96, 0x01, 0x93, 0x74, 0x41, 0xa1, 0x74, 0xe7, + 0xa4, 0x1d, 0x0b, 0x11, 0x41, 0x4d, 0x3d, 0xeb, 0xb1, 0xf5, 0xac, 0x3b, 0x79, 0xf1, 0xfd, 0xc7, + 0xa3, 0xe7, 0x97, 0x81, 0x53, 0x5e, 0x42, 0x20, 0xd7, 0x02, 0x2a, 0x7f, 0x2c, 0xc4, 0xc9, 0xf9, + 0x2c, 0xb4, 0x91, 0x72, 0x52, 0x53, 0xc5, 0xc3, 0xde, 0x34, 0x6f, 0xef, 0x5a, 0x3c, 0xbc, 0xa1, + 0xe6, 0x21, 0x45, 0xf1, 0xee, 0x13, 0x45, 0x8e, 0x68, 0xe6, 0xb5, 0x10, 0xd7, 0x09, 0xf7, 0x31, + 0x9a, 0x65, 0x87, 0x7f, 0x5a, 0xc4, 0x36, 0xbd, 0x6c, 0x29, 0xac, 0x2d, 0xc5, 0x76, 0x63, 0x7b, + 0x37, 0xdc, 0x58, 0xeb, 0x26, 0x1a, 0x7b, 0x4f, 0x1c, 0xc5, 0x8b, 0xb3, 0xac, 0xf4, 0x6e, 0x6b, + 0xe0, 0x4b, 0x04, 0x0e, 0xaf, 0x06, 0x1c, 0xa3, 0x3b, 0x54, 0xf7, 0x52, 0x1b, 0x37, 0x24, 0x1d, + 0xd6, 0x14, 0x51, 0x15, 0x15, 0xb0, 0xf6, 0xf6, 0xaf, 0xc5, 0x9c, 0x37, 0xc5, 0xe9, 0x5b, 0x58, + 0x87, 0x6d, 0x66, 0x36, 0xae, 0x47, 0x9c, 0x45, 0x94, 0x32, 0x19, 0xd5, 0xc2, 0xb3, 0x11, 0xd9, + 0x0b, 0xed, 0xc5, 0x31, 0x93, 0xe7, 0xc2, 0x7d, 0x48, 0x88, 0xa9, 0x64, 0xbc, 0x61, 0x5e, 0x5b, + 0xd7, 0x1c, 0x55, 0x9b, 0x62, 0xec, 0x1e, 0x91, 0x7b, 0x19, 0xad, 0xe2, 0x64, 0x05, 0x91, 0x51, + 0xa5, 0x4b, 0x48, 0x0b, 0xef, 0x0e, 0xca, 0x9c, 0xb0, 0xbf, 0x29, 0xbd, 0x46, 0xf5, 0xb1, 0xca, + 0xbb, 0x4f, 0x49, 0xbf, 0xae, 0xa0, 0x1a, 0x0d, 0xa3, 0x84, 0x4a, 0xe3, 0xf0, 0xba, 0x5a, 0xdb, + 0x33, 0xf9, 0x09, 0x95, 0x4a, 0x3d, 0xfc, 0x6c, 0x91, 0x9e, 0x79, 0xf8, 0x77, 0x31, 0x8b, 0x73, + 0xfc, 0xc1, 0xaf, 0x48, 0xe7, 0x0d, 0xc8, 0xcd, 0x67, 0x78, 0xe0, 0x6f, 0x66, 0xc9, 0xdf, 0xfd, + 0xe9, 0x83, 0xbb, 0x3b, 0x25, 0xf7, 0x09, 0xe9, 0x9c, 0x5e, 0x18, 0x77, 0xab, 0x03, 0xc7, 0x57, + 0x93, 0x33, 0xc6, 0xbb, 0x8d, 0x48, 0x77, 0x0a, 0x2b, 0x90, 0x70, 0xf9, 0x19, 0x17, 0xa6, 0x49, + 0xff, 0xcb, 0xaf, 0x03, 0xeb, 0x1b, 0xae, 0x9f, 0xb8, 0x3e, 0xfd, 0x3e, 0xb8, 0x95, 0xd8, 0x7a, + 0x0a, 0x47, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x45, 0x17, 0xc4, 0xb2, 0x31, 0x04, 0x00, 0x00, +} diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto new file mode 100644 index 000000000..394e0af9e --- /dev/null +++ b/api/protocol/lorawan/device.proto @@ -0,0 +1,32 @@ +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "github.com/TheThingsNetwork/ttn/api/api.proto"; + +package lorawan; + +message DeviceIdentifier { + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + string app_id = 3; +} + +message Device { + string app_id = 1; + bytes app_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes dev_addr = 4 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + bytes nwk_s_key = 5 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + uint32 f_cnt_up = 6; + uint32 f_cnt_down = 7; + + // Options + bool disable_f_cnt_check = 11; + bool uses32_bit_f_cnt = 12; +} + +service DeviceManager { + rpc GetDevice(DeviceIdentifier) returns (Device); + rpc SetDevice(Device) returns (api.Ack); + rpc DeleteDevice(DeviceIdentifier) returns (api.Ack); +} \ No newline at end of file diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 5e2214a92..4b758cfb1 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -2,22 +2,6 @@ // source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto // DO NOT EDIT! -/* - Package lorawan is a generated protocol buffer package. - - It is generated from these files: - github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto - - It has these top-level messages: - Metadata - TxConfiguration - ActivationMetadata - PHYPayload - MHdr - MACPayload - FHdr - FCtrl -*/ package lorawan import proto "github.com/golang/protobuf/proto" @@ -34,10 +18,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 - type Modulation int32 const ( diff --git a/core/broker/broker.go b/core/broker/broker.go index cedf599a3..3aa801690 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" ) @@ -48,6 +49,7 @@ type broker struct { handlersLock sync.RWMutex nsAddr string ns networkserver.NetworkServerClient + nsManager pb_lorawan.DeviceManagerClient uplinkDeduplicator Deduplicator activationDeduplicator Deduplicator } @@ -68,9 +70,8 @@ func (b *broker) Init(c *core.Component) error { if err != nil { return err } - client := networkserver.NewNetworkServerClient(conn) - b.ns = client - + b.ns = networkserver.NewNetworkServerClient(conn) + b.nsManager = pb_lorawan.NewDeviceManagerClient(conn) return nil } diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go index 3c2d84c6a..804b4c267 100644 --- a/core/broker/broker_test.go +++ b/core/broker/broker_test.go @@ -9,6 +9,7 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" . "github.com/smartystreets/assertions" "golang.org/x/net/context" "google.golang.org/grpc" @@ -37,7 +38,7 @@ func (d *mockHandlerDiscovery) All() (a []*pb_discovery.Announcement, err error) } type mockNetworkServer struct { - devices []*pb_networkserver.Device + devices []*pb_lorawan.Device } func (s *mockNetworkServer) GetDevices(ctx context.Context, req *pb_networkserver.DevicesRequest, options ...grpc.CallOption) (*pb_networkserver.DevicesResponse, error) { diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 12e1a96c6..702dcf84d 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -11,7 +11,8 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" - pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -78,11 +79,11 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) // Find AppEUI/DevEUI through MIC check - var device *pb_networkserver.Device + var device *pb_lorawan.Device for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) if candidate.Uses32BitFCnt { - macPayload.FHDR.FCnt = candidate.FullFCnt + macPayload.FHDR.FCnt = fcnt.GetFull(macPayload.FHDR.FCnt, uint16(candidate.FCntUp)) } ok, err = phyPayload.ValidateMIC(nwkSKey) if err != nil { @@ -101,19 +102,18 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { "DevEUI": device.DevEui, "AppEUI": device.AppEui, "AppID": device.AppId, - "FCnt": device.FullFCnt, }) if device.DisableFCntCheck { // TODO: Add warning to message? - } else if macPayload.FHDR.FCnt <= device.StoredFCnt || macPayload.FHDR.FCnt-device.StoredFCnt > maxFCntGap { + } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { // Replay attack or FCnt gap too big err = ErrInvalidFCnt return err } // Add FCnt to Metadata (because it's not marshaled in lorawan payload) - base.ProtocolMetadata.GetLorawan().FCnt = device.FullFCnt + base.ProtocolMetadata.GetLorawan().FCnt = macPayload.FHDR.FCnt // Collect GatewayMetadata and DownlinkOptions var gatewayMetadata []*gateway.RxMetadata diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 60c4558c5..4d228827b 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -8,7 +8,6 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" - pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" @@ -27,7 +26,7 @@ func TestHandleUplink(t *testing.T) { }, uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ - devices: []*pb_networkserver.Device{}, + devices: []*pb_lorawan.Device{}, }, } @@ -78,13 +77,13 @@ func TestHandleUplink(t *testing.T) { handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ - devices: []*pb_networkserver.Device{ - &pb_networkserver.Device{ - DevEui: &devEUI, - AppEui: &appEUI, - AppId: appID, - NwkSKey: &nwkSKey, - StoredFCnt: 3, + devices: []*pb_lorawan.Device{ + &pb_lorawan.Device{ + DevEui: &devEUI, + AppEui: &appEUI, + AppId: appID, + NwkSKey: &nwkSKey, + FCntUp: 3, }, }, }, @@ -127,7 +126,7 @@ func TestHandleUplink(t *testing.T) { // OK FCnt b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) - b.ns.(*mockNetworkServer).devices[0].StoredFCnt = 0 + b.ns.(*mockNetworkServer).devices[0].FCntUp = 0 b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = false err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index ddf68cb55..85424097b 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -10,6 +10,7 @@ import ( pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/networkserver/device" @@ -78,18 +79,17 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes // Return all devices with DevAddr with FCnt <= fCnt or Security off res := &pb.DevicesResponse{ - Results: make([]*pb.Device, 0, len(devices)), + Results: make([]*pb_lorawan.Device, 0, len(devices)), } for _, device := range devices { fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) - dev := &pb.Device{ + dev := &pb_lorawan.Device{ AppEui: &device.AppEUI, AppId: device.AppID, DevEui: &device.DevEUI, NwkSKey: &device.NwkSKey, - StoredFCnt: device.FCntUp, - FullFCnt: fullFCnt, + FCntUp: device.FCntUp, Uses32BitFCnt: device.Options.Uses32BitFCnt, DisableFCntCheck: device.Options.DisableFCntCheck, } From b1ee42ae5baeaf44b5ff9e4d9e7b2119f7c885cb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 11:38:20 +0200 Subject: [PATCH 1531/2266] Address race in random package --- core/handler/activation.go | 2 +- core/networkserver/networkserver.go | 2 +- core/router/gateway/schedule.go | 8 +- utils/random/random.go | 129 ++++++++++++++++++++++++---- 4 files changed, 117 insertions(+), 24 deletions(-) diff --git a/core/handler/activation.go b/core/handler/activation.go index 47d04a4e5..2c7e9c196 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -108,7 +108,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // NOTE: As DevNonces are only 2 bytes, we will start rejecting those before we run out of AppNonces. // It might just take some time to get one we didn't use yet... alreadyUsed = false - copy(appNonce[:], random.Bytes(3)) + copy(appNonce[:], random.New().Bytes(3)) for _, usedNonce := range dev.UsedAppNonces { if usedNonce == appNonce { alreadyUsed = true diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 85424097b..4edcc430c 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -136,7 +136,7 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat // Generate random DevAddr with prefix var devAddr types.DevAddr - copy(devAddr[:], random.Bytes(4)) + copy(devAddr[:], random.New().Bytes(4)) devAddr = devAddr.WithPrefix(types.DevAddr(n.prefix), n.prefixLength) // Set the DevAddr in the Activation Metadata diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index d3c6191cd..eca1f544b 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -31,8 +31,9 @@ type Schedule interface { // NewSchedule creates a new Schedule func NewSchedule(ctx log.Interface) Schedule { return &schedule{ - ctx: ctx, - items: make(map[string]*scheduledItem), + ctx: ctx, + items: make(map[string]*scheduledItem), + random: random.New(), } } @@ -47,6 +48,7 @@ type scheduledItem struct { type schedule struct { sync.RWMutex + random random.TTNRandom ctx log.Interface active bool offset int64 @@ -115,7 +117,7 @@ func (s *schedule) Sync(timestamp uint32) { // see interface func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) { - id = random.String(32) + id = s.random.String(32) score = s.getConflicts(timestamp, length) item := &scheduledItem{ id: id, diff --git a/utils/random/random.go b/utils/random/random.go index 57e8bbf93..298044e28 100644 --- a/utils/random/random.go +++ b/utils/random/random.go @@ -8,6 +8,7 @@ import ( "fmt" "math" "math/rand" + "sync" "time" ) @@ -20,15 +21,28 @@ const ( letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) -var src = rand.New(rand.NewSource(time.Now().UnixNano())) +type TTNRandom struct { + sync.Mutex + src *rand.Rand +} + +func New() TTNRandom { + return TTNRandom{ + src: rand.New(rand.NewSource(time.Now().UnixNano())), + } +} + +var global = New() // String returns random string of length n -func String(n int) string { +func (r TTNRandom) String(n int) string { + r.Lock() + defer r.Unlock() b := make([]byte, n) // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! - for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; { + for i, cache, remain := n-1, r.src.Int63(), letterIdxMax; i >= 0; { if remain == 0 { - cache, remain = src.Int63(), letterIdxMax + cache, remain = r.src.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] @@ -42,29 +56,60 @@ func String(n int) string { } // Token generate a random 2-bytes token -func Token() []byte { +func (r TTNRandom) Token() []byte { + r.Lock() + defer r.Unlock() b := make([]byte, 4) - binary.BigEndian.PutUint32(b, src.Uint32()) + binary.BigEndian.PutUint32(b, r.src.Uint32()) return b[0:2] } // Rssi generates RSSI signal between -120 < rssi < 0 -func Rssi() int32 { +func (r TTNRandom) Rssi() int32 { + r.Lock() + defer r.Unlock() // Generate RSSI. Tend towards generating great signal strength. - x := float64(src.Int31()) * float64(2e-9) + x := float64(r.src.Int31()) * float64(2e-9) return int32(-1.6 * math.Exp(x)) } +var euFreqs = []float32{ + 868.1, + 868.3, + 868.5, + 868.8, + 867.1, + 867.3, + 867.5, + 867.7, + 867.9, +} + +var usFreqs = []float32{ + 903.9, + 904.1, + 904.3, + 904.5, + 904.7, + 904.9, + 905.1, + 905.3, + 904.6, +} + // Freq generates a frequency between 865.0 and 870.0 Mhz -func Freq() float32 { - // EU 865-870MHz - return float32(src.Float64()*5 + 865.0) +func (r TTNRandom) Freq() float32 { + r.Lock() + defer r.Unlock() + return usFreqs[r.src.Intn(len(usFreqs))] } // Datr generates Datr for instance: SF4BW125 -func Datr() string { +func (r TTNRandom) Datr() string { + r.Lock() + defer r.Unlock() // Spread Factor from 12 to 7 - sf := 12 - src.Intn(7) + sf := 12 - r.src.Intn(7) var bw int if sf == 6 { // DR6 -> SF7@250Khz @@ -77,20 +122,66 @@ func Datr() string { } // Codr generates Codr for instance: 4/6 -func Codr() string { - d := src.Intn(4) + 5 +func (r TTNRandom) Codr() string { + r.Lock() + defer r.Unlock() + d := r.src.Intn(4) + 5 return fmt.Sprintf("4/%d", d) } // Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise -func Lsnr() float32 { - x := float64(src.Int31()) * float64(2e-9) +func (r TTNRandom) Lsnr() float32 { + r.Lock() + defer r.Unlock() + x := float64(r.src.Int31()) * float64(2e-9) return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) } // Bytes generates a random byte slice of length n -func Bytes(n int) []byte { +func (r TTNRandom) Bytes(n int) []byte { + r.Lock() + defer r.Unlock() p := make([]byte, n) - src.Read(p) + r.src.Read(p) return p } + +// String returns random string of length n +func String(n int) string { + return global.String(n) +} + +// Token generate a random 2-bytes token +func Token() []byte { + return global.Token() +} + +// Rssi generates RSSI signal between -120 < rssi < 0 +func Rssi() int32 { + return global.Rssi() +} + +// Freq generates a frequency between 865.0 and 870.0 Mhz +func Freq() float32 { + return global.Freq() +} + +// Datr generates Datr for instance: SF4BW125 +func Datr() string { + return global.Datr() +} + +// Codr generates Codr for instance: 4/6 +func Codr() string { + return global.Codr() +} + +// Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise +func Lsnr() float32 { + return global.Lsnr() +} + +// Bytes generates a random byte slice of length n +func Bytes(n int) []byte { + return global.Bytes(n) +} From 0e14a9141772f3dbc5d6cab6f079d0402afbab1a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 11:39:30 +0200 Subject: [PATCH 1532/2266] Router schedule deadline handling --- core/router/downlink_test.go | 8 +++++-- core/router/gateway/schedule.go | 41 ++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index fdd700127..3e2a61f3d 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -73,6 +73,11 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { } eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + gateway.Deadline = 1 * time.Millisecond + gtw := r.getGateway(eui) + gtw.Schedule.Sync(0) + id, _ := gtw.Schedule.GetOption(5000, 10*1000) + ch, err := r.SubscribeDownlink(eui) a.So(err, ShouldBeNil) @@ -88,7 +93,6 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { wg.Done() }() - id, _ := r.getGateway(eui).Schedule.GetOption(0, 10*1000) r.HandleDownlink(&pb_broker.DownlinkMessage{ Payload: []byte{0x02}, DownlinkOption: &pb_broker.DownlinkOption{ @@ -100,7 +104,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { }) // Wait for the downlink to arrive - <-time.After(5 * time.Millisecond) + <-time.After(10 * time.Millisecond) err = r.UnsubscribeDownlink(eui) a.So(err, ShouldBeNil) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index eca1f544b..f10b3f72b 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -38,12 +38,12 @@ func NewSchedule(ctx log.Interface) Schedule { } type scheduledItem struct { - id string - time time.Time - timestamp uint32 - length uint32 - score uint - payload *router_pb.DownlinkMessage + id string + deadlineAt time.Time + timestamp uint32 + length uint32 + score uint + payload *router_pb.DownlinkMessage } type schedule struct { @@ -60,7 +60,7 @@ func (s *schedule) GoString() (str string) { s.RLock() defer s.RUnlock() for _, item := range s.items { - str += fmt.Sprintf("%s at %s\n", item.id, item.time) + str += fmt.Sprintf("%s at %s\n", item.id, item.deadlineAt) } return } @@ -120,11 +120,11 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score id = s.random.String(32) score = s.getConflicts(timestamp, length) item := &scheduledItem{ - id: id, - time: s.realtime(timestamp), - timestamp: timestamp, - length: length, - score: score, + id: id, + deadlineAt: s.realtime(timestamp).Add(-1 * Deadline), + timestamp: timestamp, + length: length, + score: score, } s.Lock() defer s.Unlock() @@ -159,10 +159,10 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro item.length = uint32(time / 1000) } - if time.Now().Add(Deadline).Before(item.time) { + if time.Now().Before(item.deadlineAt) { // Schedule transmission before the Deadline go func() { - waitTime := item.time.Sub(time.Now().Add(Deadline)) + waitTime := item.deadlineAt.Sub(time.Now()) ctx.WithField("Remaining", waitTime).Debug("Schedule Downlink") <-time.After(waitTime) if s.downlink != nil { @@ -170,11 +170,16 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro } }() } else if s.downlink != nil { - // Immediately send it - ctx.WithField("Overdue", time.Now().Add(Deadline).Sub(item.time)).Debug("Send Late Downlink") - s.downlink <- item.payload + overdue := time.Now().Sub(item.deadlineAt) + if overdue < Deadline { + // Immediately send it + ctx.WithField("Overdue", overdue).Warn("Send Late Downlink") + s.downlink <- item.payload + } else { + ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink") + } } else { - ctx.Debug("Unable to send Downlink") + ctx.Warn("Unable to send Downlink") } return nil From 7450e59ae2f32376d56a09c65a4c9a8c74e3529a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 11:52:21 +0200 Subject: [PATCH 1533/2266] Add Management Functions --- api/broker/broker.pb.go | 342 ++++++++++++---- api/broker/broker.proto | 7 + api/handler/handler.pb.go | 556 +++++++++++++++++++++++++-- api/handler/handler.proto | 17 + api/protocol/lorawan/device.pb.go | 168 ++++++-- api/protocol/lorawan/device.proto | 6 +- cmd/broker.go | 1 + cmd/handler.go | 1 + cmd/networkserver.go | 1 + core/broker/broker.go | 1 + core/broker/manager_server.go | 55 +++ core/component.go | 43 ++- core/discovery/handler_discovery.go | 21 +- core/handler/handler.go | 40 +- core/handler/manager_server.go | 254 ++++++++++++ core/networkserver/manager_server.go | 113 ++++++ core/networkserver/networkserver.go | 2 + core/router/router.go | 2 + 18 files changed, 1483 insertions(+), 147 deletions(-) create mode 100644 core/broker/manager_server.go create mode 100644 core/handler/manager_server.go create mode 100644 core/networkserver/manager_server.go diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 6beb49c74..77eeb5c4d 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -23,6 +23,7 @@ UnregisterApplicationRequest StatusRequest Status + ApplicationHandlerRegistration */ package broker @@ -418,6 +419,18 @@ func (m *Status) GetDeduplication() *api.Percentiles { return nil } +type ApplicationHandlerRegistration struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + HandlerId string `protobuf:"bytes,2,opt,name=handler_id,json=handlerId,proto3" json:"handler_id,omitempty"` +} + +func (m *ApplicationHandlerRegistration) Reset() { *m = ApplicationHandlerRegistration{} } +func (m *ApplicationHandlerRegistration) String() string { return proto.CompactTextString(m) } +func (*ApplicationHandlerRegistration) ProtoMessage() {} +func (*ApplicationHandlerRegistration) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{14} +} + func init() { proto.RegisterType((*DownlinkOption)(nil), "broker.DownlinkOption") proto.RegisterType((*UplinkMessage)(nil), "broker.UplinkMessage") @@ -433,6 +446,7 @@ func init() { proto.RegisterType((*UnregisterApplicationRequest)(nil), "broker.UnregisterApplicationRequest") proto.RegisterType((*StatusRequest)(nil), "broker.StatusRequest") proto.RegisterType((*Status)(nil), "broker.Status") + proto.RegisterType((*ApplicationHandlerRegistration)(nil), "broker.ApplicationHandlerRegistration") } // Reference imports to suppress errors if they are not otherwise used. @@ -710,6 +724,8 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ // Client API for BrokerManager service type BrokerManagerClient interface { + // Handler announces new application to Broker + RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*api.Ack, error) // Network operator requests Broker status GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } @@ -722,6 +738,15 @@ func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { return &brokerManagerClient{cc} } +func (c *brokerManagerClient) RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/broker.BrokerManager/RegisterApplicationHandler", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { out := new(Status) err := grpc.Invoke(ctx, "/broker.BrokerManager/GetStatus", in, out, c.cc, opts...) @@ -734,6 +759,8 @@ func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, // Server API for BrokerManager service type BrokerManagerServer interface { + // Handler announces new application to Broker + RegisterApplicationHandler(context.Context, *ApplicationHandlerRegistration) (*api.Ack, error) // Network operator requests Broker status GetStatus(context.Context, *StatusRequest) (*Status, error) } @@ -742,6 +769,24 @@ func RegisterBrokerManagerServer(s *grpc.Server, srv BrokerManagerServer) { s.RegisterService(&_BrokerManager_serviceDesc, srv) } +func _BrokerManager_RegisterApplicationHandler_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationHandlerRegistration) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(BrokerManagerServer).RegisterApplicationHandler(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/broker.BrokerManager/RegisterApplicationHandler", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(BrokerManagerServer).RegisterApplicationHandler(ctx, req.(*ApplicationHandlerRegistration)) + } + return interceptor(ctx, in, info, handler) +} + func _BrokerManager_GetStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(StatusRequest) if err := dec(in); err != nil { @@ -764,6 +809,10 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "broker.BrokerManager", HandlerType: (*BrokerManagerServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterApplicationHandler", + Handler: _BrokerManager_RegisterApplicationHandler_Handler, + }, { MethodName: "GetStatus", Handler: _BrokerManager_GetStatus_Handler, @@ -1549,6 +1598,36 @@ func (m *Status) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *ApplicationHandlerRegistration) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ApplicationHandlerRegistration) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.HandlerId) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.HandlerId))) + i += copy(data[i:], m.HandlerId) + } + return i, nil +} + func encodeFixed64Broker(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -1880,6 +1959,20 @@ func (m *Status) Size() (n int) { return n } +func (m *ApplicationHandlerRegistration) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.HandlerId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + func sovBroker(x uint64) (n int) { for { n++ @@ -4236,6 +4329,114 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } +func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationHandlerRegistration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationHandlerRegistration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field HandlerId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.HandlerId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipBroker(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -4342,73 +4543,76 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1088 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0xcd, 0x6f, 0xe3, 0x44, - 0x14, 0x27, 0xcd, 0x36, 0x6d, 0x5f, 0x9a, 0x8f, 0x4e, 0x3f, 0xe2, 0x46, 0xab, 0xb6, 0x04, 0x09, - 0x95, 0x8f, 0x4d, 0xd8, 0xa0, 0xb2, 0xaa, 0x10, 0xac, 0xd2, 0xed, 0x6a, 0x59, 0xa4, 0x2c, 0x2b, - 0x6f, 0x7a, 0xe1, 0x12, 0x39, 0xf6, 0x34, 0x19, 0x35, 0xb5, 0x8d, 0x67, 0xdc, 0x6e, 0xff, 0x07, - 0x24, 0x2e, 0x1c, 0xf8, 0x53, 0xb8, 0x22, 0x2e, 0x1c, 0x39, 0x71, 0xe0, 0x80, 0x10, 0x5c, 0xf8, - 0x33, 0x78, 0x9e, 0x19, 0x3b, 0x76, 0xba, 0x61, 0xbb, 0xcb, 0x22, 0x41, 0xc5, 0xc1, 0x8a, 0xe7, - 0x7d, 0xfc, 0x3c, 0xfe, 0xbd, 0xdf, 0x7b, 0x9e, 0xc0, 0x9d, 0x21, 0x13, 0xa3, 0x70, 0xd0, 0xb4, - 0xbd, 0xd3, 0x56, 0x6f, 0x44, 0x7b, 0x23, 0xe6, 0x0e, 0xf9, 0x23, 0x2a, 0xce, 0xbd, 0xe0, 0xa4, - 0x25, 0x84, 0xdb, 0xb2, 0x7c, 0xd6, 0x1a, 0x04, 0xde, 0x09, 0x0d, 0xf4, 0x4f, 0xd3, 0x0f, 0x3c, - 0xe1, 0x91, 0x82, 0x5a, 0xd5, 0x6f, 0xa5, 0x00, 0x86, 0xde, 0xd0, 0x6b, 0x49, 0xf7, 0x20, 0x3c, - 0x96, 0x2b, 0xb9, 0x90, 0x77, 0x2a, 0x2d, 0x13, 0x3e, 0xf3, 0x79, 0x78, 0xe9, 0xf0, 0x0f, 0xaf, - 0x12, 0x2e, 0x43, 0x6d, 0x6f, 0x9c, 0xdc, 0xe8, 0xe4, 0xfd, 0xab, 0x24, 0x0f, 0x2d, 0x41, 0xcf, - 0xad, 0x8b, 0xf8, 0x57, 0xa5, 0x36, 0xbe, 0x9f, 0x83, 0xf2, 0xa1, 0x77, 0xee, 0x8e, 0x99, 0x7b, - 0xf2, 0x99, 0x2f, 0x98, 0xe7, 0x92, 0x2d, 0x00, 0xe6, 0x50, 0x57, 0xb0, 0x63, 0x46, 0x03, 0x23, - 0xb7, 0x93, 0xdb, 0x5d, 0x32, 0x53, 0x16, 0xf2, 0x39, 0x14, 0x35, 0x46, 0x9f, 0x86, 0xcc, 0x98, - 0xc3, 0x80, 0xe5, 0x83, 0xfd, 0x9f, 0x7f, 0xd9, 0xde, 0x7b, 0xde, 0x36, 0x6c, 0x2f, 0xa0, 0x2d, - 0x71, 0xe1, 0x53, 0xde, 0x7c, 0xa0, 0x10, 0xee, 0x1f, 0x3d, 0x34, 0x41, 0xa3, 0xdd, 0x0f, 0x19, - 0x59, 0x83, 0x79, 0x1e, 0x45, 0x19, 0x79, 0x44, 0x2d, 0x99, 0x6a, 0x41, 0xea, 0xb0, 0xe8, 0x50, - 0xcb, 0xc1, 0x3d, 0x52, 0xe3, 0x06, 0x3a, 0xf2, 0x66, 0xb2, 0x26, 0x07, 0x50, 0x89, 0xd9, 0xe8, - 0xdb, 0x9e, 0x7b, 0xcc, 0x86, 0xc6, 0x3c, 0x86, 0x14, 0xdb, 0x9b, 0xcd, 0x84, 0xa5, 0xde, 0xd3, - 0x7b, 0xd2, 0x13, 0x06, 0x56, 0xf4, 0x86, 0x66, 0x39, 0xf6, 0x28, 0x33, 0xb9, 0x0b, 0xe5, 0xf8, - 0x8d, 0x34, 0x44, 0x41, 0x42, 0x18, 0xcd, 0x98, 0xac, 0x69, 0x84, 0x92, 0x76, 0x28, 0x6b, 0xe3, - 0xab, 0x3c, 0x94, 0x8e, 0xfc, 0x88, 0xc3, 0x2e, 0xe5, 0xdc, 0x1a, 0x52, 0x62, 0xc0, 0x82, 0x6f, - 0x5d, 0x8c, 0x3d, 0xcb, 0x91, 0x0c, 0x2e, 0x9b, 0xf1, 0x92, 0x3c, 0x82, 0x05, 0x87, 0x9e, 0x49, - 0xea, 0x8a, 0x92, 0xba, 0x3d, 0xa4, 0xee, 0xf6, 0x0b, 0x50, 0x77, 0x48, 0xcf, 0x22, 0xda, 0x0a, - 0x88, 0x12, 0x51, 0x86, 0x78, 0x96, 0xef, 0x4b, 0xbc, 0xe5, 0x97, 0xc2, 0xeb, 0xf8, 0xbe, 0xc4, - 0x43, 0x94, 0x08, 0xaf, 0x03, 0x2b, 0x09, 0xa1, 0xa7, 0x54, 0x58, 0x8e, 0x25, 0x2c, 0x63, 0x5d, - 0xf2, 0xb1, 0x36, 0xa1, 0xd4, 0x7c, 0xda, 0xd5, 0x3e, 0xb3, 0x1a, 0x1b, 0x63, 0x0b, 0xf9, 0x18, - 0xaa, 0x31, 0x9f, 0x09, 0xc2, 0x86, 0x44, 0x58, 0x4d, 0x18, 0x4d, 0x01, 0x54, 0xb4, 0x2d, 0xc9, - 0xef, 0x40, 0xd5, 0xd1, 0x9a, 0xec, 0x7b, 0x52, 0x94, 0xdc, 0xd8, 0xde, 0xc9, 0x63, 0xfe, 0x46, - 0x53, 0xf7, 0x66, 0x56, 0xb3, 0x66, 0xc5, 0xc9, 0xac, 0x79, 0xe3, 0xcb, 0x39, 0xa8, 0xc4, 0x31, - 0xff, 0xfd, 0x9a, 0xdc, 0x85, 0xca, 0x14, 0x21, 0xba, 0x22, 0xb3, 0xf8, 0x28, 0x67, 0xf9, 0x68, - 0x84, 0x60, 0xe0, 0x16, 0x99, 0x4d, 0x3b, 0xb6, 0x60, 0x67, 0x4a, 0xc3, 0x94, 0xfb, 0xc8, 0xd4, - 0x5f, 0xd1, 0xf2, 0x8c, 0xc7, 0x16, 0x5f, 0xe8, 0xb1, 0x3f, 0xe5, 0x61, 0xf3, 0x90, 0x3a, 0x21, - 0xb6, 0x86, 0x8d, 0x35, 0x76, 0xae, 0x4b, 0x8f, 0xac, 0x43, 0x74, 0xd7, 0x67, 0x8e, 0x51, 0x92, - 0xe3, 0x71, 0x1e, 0x57, 0x0f, 0x9d, 0x7f, 0xae, 0x75, 0xf2, 0x57, 0x6e, 0x9d, 0x6d, 0x28, 0x72, - 0x1a, 0x9c, 0xd1, 0xa0, 0x2f, 0xd8, 0x29, 0x35, 0x6a, 0x72, 0x5a, 0x82, 0x32, 0xf5, 0xd0, 0x42, - 0x0e, 0x61, 0x25, 0xd0, 0x95, 0xef, 0x0b, 0x7a, 0xea, 0x8f, 0x11, 0x00, 0x9b, 0x2b, 0xda, 0x63, - 0x6d, 0xba, 0xaa, 0xba, 0x50, 0x66, 0x35, 0xce, 0xe8, 0xe9, 0x84, 0xc6, 0x1f, 0x79, 0xa8, 0x5d, - 0x16, 0xd4, 0x17, 0x21, 0xe5, 0xe2, 0xff, 0xd1, 0xf7, 0x77, 0x46, 0x5f, 0x17, 0x56, 0xad, 0x84, - 0xd1, 0x09, 0x44, 0x4d, 0x42, 0xdc, 0x9c, 0x6c, 0x62, 0x42, 0x7b, 0x82, 0x45, 0xac, 0x4b, 0xb6, - 0x57, 0x31, 0x49, 0xbf, 0xbd, 0x01, 0x6f, 0xa4, 0x7b, 0xf8, 0xfa, 0x95, 0xfd, 0xdf, 0xdb, 0xcd, - 0xaf, 0x58, 0x0d, 0x53, 0xc3, 0xc1, 0xb8, 0x34, 0x1c, 0xba, 0xb3, 0x87, 0xc3, 0x4e, 0xa2, 0x97, - 0x19, 0xdf, 0x91, 0x67, 0x4c, 0x09, 0x02, 0xd5, 0x27, 0xe1, 0x80, 0xdb, 0x01, 0x1b, 0x50, 0x2d, - 0x93, 0xc6, 0x3a, 0xac, 0x22, 0xfd, 0x52, 0x4b, 0x91, 0xbc, 0x62, 0xf3, 0x6d, 0x58, 0xcb, 0x9a, - 0xf5, 0xc7, 0x69, 0x13, 0x16, 0x75, 0xad, 0x39, 0xca, 0x2a, 0x8f, 0xd5, 0x59, 0x50, 0x55, 0xe3, - 0x8d, 0x3d, 0xa8, 0x9b, 0x74, 0xc8, 0xb8, 0xa0, 0x41, 0x2a, 0x35, 0x96, 0x63, 0x6d, 0x22, 0x12, - 0x75, 0x84, 0xd5, 0xd5, 0x6e, 0xdc, 0x81, 0x9b, 0x47, 0x6e, 0xf0, 0x12, 0x89, 0x15, 0x28, 0x3d, - 0x11, 0x96, 0x08, 0x93, 0x3d, 0x7f, 0x97, 0x87, 0x82, 0xb2, 0x90, 0x06, 0x14, 0x42, 0xf9, 0x6d, - 0x93, 0x39, 0xc5, 0x36, 0x34, 0xa3, 0xa3, 0xbd, 0x89, 0x24, 0x70, 0x53, 0x7b, 0x48, 0x0b, 0x4a, - 0xea, 0xae, 0x1f, 0xba, 0x0c, 0x11, 0xe4, 0xc9, 0x39, 0x1b, 0xba, 0xac, 0x02, 0x8e, 0xa4, 0x9f, - 0xbc, 0x89, 0xc7, 0x5e, 0xdd, 0x8c, 0xfa, 0xbb, 0x9b, 0x8e, 0x4d, 0x7c, 0xe4, 0x5d, 0x28, 0x4e, - 0x8a, 0xcd, 0xb5, 0x44, 0xd3, 0xa1, 0x69, 0x37, 0xd9, 0x87, 0x94, 0x34, 0x78, 0xbc, 0x97, 0x8d, - 0x4b, 0x49, 0x2b, 0xa9, 0x28, 0xbd, 0xa1, 0x8f, 0x60, 0x2d, 0x9d, 0x6a, 0xd9, 0x36, 0xf5, 0x71, - 0x22, 0x68, 0x3d, 0xa6, 0x93, 0x53, 0xb2, 0xe5, 0x1d, 0x1d, 0x46, 0x3e, 0x80, 0x92, 0x93, 0x0c, - 0x92, 0xe8, 0x30, 0xa1, 0x94, 0x55, 0x95, 0x79, 0x8f, 0x69, 0x60, 0x47, 0x7f, 0x31, 0xc6, 0x98, - 0x9d, 0x0d, 0x23, 0xef, 0xc0, 0x0a, 0x1e, 0xcb, 0x5d, 0x6a, 0x23, 0x48, 0x3f, 0xf0, 0x42, 0xac, - 0x1b, 0x37, 0xde, 0x92, 0x7f, 0x10, 0xaa, 0x89, 0xc3, 0x54, 0x76, 0x72, 0x0b, 0xc8, 0x24, 0x78, - 0x64, 0xb9, 0xce, 0x38, 0x8a, 0x7e, 0x5b, 0x46, 0x4f, 0x60, 0x3e, 0xd1, 0x8e, 0xf6, 0xd7, 0x73, - 0x50, 0x38, 0x90, 0xc2, 0xc6, 0xd3, 0xce, 0x52, 0x87, 0x73, 0xcf, 0x66, 0xf8, 0x06, 0x64, 0x3d, - 0x96, 0x7b, 0xe6, 0xc8, 0x52, 0x9f, 0xf5, 0x89, 0xdc, 0xcd, 0xbd, 0x97, 0x23, 0x9f, 0xc2, 0x52, - 0x22, 0x77, 0x62, 0xc4, 0x91, 0xd3, 0x1d, 0x50, 0x7f, 0x7d, 0xd2, 0x49, 0x33, 0x4e, 0x46, 0x88, - 0xd5, 0x84, 0x85, 0xc7, 0xe1, 0x60, 0xcc, 0xf8, 0x88, 0xcc, 0x7a, 0x66, 0x7d, 0x51, 0x12, 0xd7, - 0xb1, 0x4f, 0x76, 0x73, 0xd8, 0xb9, 0x8b, 0xba, 0x25, 0x29, 0xd9, 0x9e, 0xdd, 0xaa, 0x6a, 0x07, - 0xcf, 0xed, 0xe5, 0xf6, 0x3d, 0x28, 0x29, 0x56, 0xba, 0x96, 0x8b, 0x8f, 0x0a, 0x48, 0x1b, 0x96, - 0x1e, 0x50, 0xa1, 0xd5, 0x9e, 0x90, 0x93, 0xe9, 0x87, 0x7a, 0x39, 0x6b, 0x3e, 0xa8, 0xfe, 0xf0, - 0xdb, 0x56, 0xee, 0x47, 0xbc, 0x7e, 0xc5, 0xeb, 0x9b, 0xdf, 0xb7, 0x5e, 0x1b, 0x14, 0xe4, 0xc4, - 0x7a, 0xff, 0xcf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x73, 0x68, 0xd7, 0xce, 0x8d, 0x0f, 0x00, 0x00, + // 1135 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4d, 0x6f, 0x1b, 0x45, + 0x18, 0xc6, 0x76, 0xe3, 0xc4, 0xaf, 0xe3, 0x8f, 0x4c, 0x3e, 0xbc, 0xb1, 0x4a, 0x12, 0x16, 0xa9, + 0x0a, 0x1f, 0xb5, 0xa9, 0x51, 0xa8, 0x22, 0x04, 0x95, 0x43, 0xaa, 0x12, 0x24, 0x97, 0x6a, 0xeb, + 0x70, 0xe0, 0x62, 0xad, 0x77, 0x27, 0xf6, 0x28, 0xce, 0xee, 0xb2, 0x33, 0x9b, 0x34, 0xff, 0x01, + 0x89, 0x03, 0x1c, 0xf8, 0x29, 0x5c, 0x11, 0x17, 0x8e, 0x9c, 0x38, 0x70, 0x40, 0x08, 0x2e, 0xfc, + 0x0c, 0x66, 0x67, 0x66, 0xbf, 0x9c, 0xb8, 0x4d, 0x4b, 0x91, 0x20, 0xe2, 0x60, 0x79, 0xe7, 0xfd, + 0x78, 0x76, 0xfc, 0xbc, 0xcf, 0xfb, 0xce, 0x18, 0xee, 0x8e, 0x08, 0x1b, 0x07, 0xc3, 0x96, 0xe5, + 0x9e, 0xb4, 0xfb, 0x63, 0xdc, 0x1f, 0x13, 0x67, 0x44, 0x1f, 0x62, 0x76, 0xe6, 0xfa, 0xc7, 0x6d, + 0xc6, 0x9c, 0xb6, 0xe9, 0x91, 0xf6, 0xd0, 0x77, 0x8f, 0xb1, 0xaf, 0xbe, 0x5a, 0x9e, 0xef, 0x32, + 0x17, 0x15, 0xe5, 0xaa, 0x79, 0x3b, 0x05, 0x30, 0x72, 0x47, 0x6e, 0x5b, 0xb8, 0x87, 0xc1, 0x91, + 0x58, 0x89, 0x85, 0x78, 0x92, 0x69, 0x99, 0xf0, 0x99, 0xef, 0xe3, 0x1f, 0x15, 0xfe, 0xfe, 0x55, + 0xc2, 0x45, 0xa8, 0xe5, 0x4e, 0xe2, 0x07, 0x95, 0xbc, 0x7b, 0x95, 0xe4, 0x91, 0xc9, 0xf0, 0x99, + 0x79, 0x1e, 0x7d, 0xcb, 0x54, 0xfd, 0x87, 0x3c, 0x54, 0xf7, 0xdd, 0x33, 0x67, 0x42, 0x9c, 0xe3, + 0x4f, 0x3d, 0x46, 0x5c, 0x07, 0x6d, 0x00, 0x10, 0x1b, 0x3b, 0x8c, 0x1c, 0x11, 0xec, 0x6b, 0xb9, + 0xad, 0xdc, 0x76, 0xc9, 0x48, 0x59, 0xd0, 0xe7, 0x50, 0x56, 0x18, 0x03, 0x1c, 0x10, 0x2d, 0xcf, + 0x03, 0x16, 0xf7, 0x76, 0x7f, 0xf9, 0x75, 0x73, 0xe7, 0x59, 0xdb, 0xb0, 0x5c, 0x1f, 0xb7, 0xd9, + 0xb9, 0x87, 0x69, 0xeb, 0x81, 0x44, 0xb8, 0x7f, 0x78, 0x60, 0x80, 0x42, 0xbb, 0x1f, 0x10, 0xb4, + 0x02, 0x73, 0x34, 0x8c, 0xd2, 0x0a, 0x1c, 0xb5, 0x62, 0xc8, 0x05, 0x6a, 0xc2, 0x82, 0x8d, 0x4d, + 0x9b, 0xef, 0x11, 0x6b, 0x37, 0xb8, 0xa3, 0x60, 0xc4, 0x6b, 0xb4, 0x07, 0xb5, 0x88, 0x8d, 0x81, + 0xe5, 0x3a, 0x47, 0x64, 0xa4, 0xcd, 0xf1, 0x90, 0x72, 0x67, 0xbd, 0x15, 0xb3, 0xd4, 0x7f, 0xf2, + 0x91, 0xf0, 0x04, 0xbe, 0x19, 0xfe, 0x42, 0xa3, 0x1a, 0x79, 0xa4, 0x19, 0xdd, 0x83, 0x6a, 0xf4, + 0x8b, 0x14, 0x44, 0x51, 0x40, 0x68, 0xad, 0x88, 0xac, 0x69, 0x84, 0x8a, 0x72, 0x48, 0xab, 0xfe, + 0x55, 0x01, 0x2a, 0x87, 0x5e, 0xc8, 0x61, 0x0f, 0x53, 0x6a, 0x8e, 0x30, 0xd2, 0x60, 0xde, 0x33, + 0xcf, 0x27, 0xae, 0x69, 0x0b, 0x06, 0x17, 0x8d, 0x68, 0x89, 0x1e, 0xc2, 0xbc, 0x8d, 0x4f, 0x05, + 0x75, 0x65, 0x41, 0xdd, 0x0e, 0xa7, 0xee, 0xce, 0x73, 0x50, 0xb7, 0x8f, 0x4f, 0x43, 0xda, 0x8a, + 0x1c, 0x25, 0xa4, 0x8c, 0xe3, 0x99, 0x9e, 0x27, 0xf0, 0x16, 0x5f, 0x08, 0xaf, 0xeb, 0x79, 0x02, + 0x8f, 0xa3, 0x84, 0x78, 0x5d, 0x58, 0x8a, 0x09, 0x3d, 0xc1, 0xcc, 0xb4, 0x4d, 0x66, 0x6a, 0xab, + 0x82, 0x8f, 0x95, 0x84, 0x52, 0xe3, 0x49, 0x4f, 0xf9, 0x8c, 0x7a, 0x64, 0x8c, 0x2c, 0xe8, 0x43, + 0xa8, 0x47, 0x7c, 0xc6, 0x08, 0x6b, 0x02, 0x61, 0x39, 0x66, 0x34, 0x05, 0x50, 0x53, 0xb6, 0x38, + 0xbf, 0x0b, 0x75, 0x5b, 0x69, 0x72, 0xe0, 0x0a, 0x51, 0x52, 0x6d, 0x73, 0xab, 0xc0, 0xf3, 0xd7, + 0x5a, 0xaa, 0x37, 0xb3, 0x9a, 0x35, 0x6a, 0x76, 0x66, 0x4d, 0xf5, 0x2f, 0xf3, 0x50, 0x8b, 0x62, + 0xfe, 0xfb, 0x35, 0xb9, 0x07, 0xb5, 0x29, 0x42, 0x54, 0x45, 0x66, 0xf1, 0x51, 0xcd, 0xf2, 0xa1, + 0x07, 0xa0, 0xf1, 0x2d, 0x12, 0x0b, 0x77, 0x2d, 0x46, 0x4e, 0xa5, 0x86, 0x31, 0xf5, 0x38, 0x53, + 0x4f, 0xa3, 0xe5, 0x92, 0xd7, 0x96, 0x9f, 0xeb, 0xb5, 0x3f, 0x17, 0x60, 0x7d, 0x1f, 0xdb, 0x01, + 0x6f, 0x0d, 0x8b, 0xd7, 0xd8, 0xbe, 0x2e, 0x3d, 0xb2, 0x0a, 0xe1, 0xd3, 0x80, 0xd8, 0x5a, 0x45, + 0x8c, 0xc7, 0x39, 0xbe, 0x3a, 0xb0, 0xff, 0xb9, 0xd6, 0x29, 0x5c, 0xb9, 0x75, 0x36, 0xa1, 0x4c, + 0xb1, 0x7f, 0x8a, 0xfd, 0x01, 0x23, 0x27, 0x58, 0x6b, 0x88, 0x69, 0x09, 0xd2, 0xd4, 0xe7, 0x16, + 0xb4, 0x0f, 0x4b, 0xbe, 0xaa, 0xfc, 0x80, 0xe1, 0x13, 0x6f, 0xc2, 0x01, 0x78, 0x73, 0x85, 0x7b, + 0x6c, 0x4c, 0x57, 0x55, 0x15, 0xca, 0xa8, 0x47, 0x19, 0x7d, 0x95, 0xa0, 0xff, 0x59, 0x80, 0xc6, + 0x45, 0x41, 0x7d, 0x11, 0x60, 0xca, 0xfe, 0x1f, 0x7d, 0x7f, 0x67, 0xf4, 0xf5, 0x60, 0xd9, 0x8c, + 0x19, 0x4d, 0x20, 0x1a, 0x02, 0xe2, 0x66, 0xb2, 0x89, 0x84, 0xf6, 0x18, 0x0b, 0x99, 0x17, 0x6c, + 0x2f, 0x63, 0x92, 0x7e, 0x77, 0x03, 0x5e, 0x4f, 0xf7, 0xf0, 0xf5, 0x2b, 0xfb, 0xbf, 0xb7, 0x9b, + 0x5f, 0xb2, 0x1a, 0xa6, 0x86, 0x83, 0x76, 0x61, 0x38, 0xf4, 0x66, 0x0f, 0x87, 0xad, 0x58, 0x2f, + 0x33, 0xce, 0x91, 0x4b, 0xa6, 0x04, 0x82, 0xfa, 0xe3, 0x60, 0x48, 0x2d, 0x9f, 0x0c, 0xb1, 0x92, + 0x89, 0xbe, 0x0a, 0xcb, 0x9c, 0x7e, 0xa1, 0xa5, 0x50, 0x5e, 0x91, 0xf9, 0x0e, 0xac, 0x64, 0xcd, + 0xea, 0x70, 0x5a, 0x87, 0x05, 0x55, 0x6b, 0xca, 0x65, 0x55, 0xe0, 0xd5, 0x99, 0x97, 0x55, 0xa3, + 0xfa, 0x0e, 0x34, 0x0d, 0x3c, 0x22, 0x94, 0x61, 0x3f, 0x95, 0x1a, 0xc9, 0xb1, 0x91, 0x88, 0x44, + 0x5e, 0x61, 0x55, 0xb5, 0xf5, 0xbb, 0x70, 0xf3, 0xd0, 0xf1, 0x5f, 0x20, 0xb1, 0x06, 0x95, 0xc7, + 0xcc, 0x64, 0x41, 0xbc, 0xe7, 0xef, 0x0b, 0x50, 0x94, 0x16, 0xa4, 0x43, 0x31, 0x10, 0x67, 0x9b, + 0xc8, 0x29, 0x77, 0xa0, 0x15, 0x5e, 0xed, 0x0d, 0x4e, 0x02, 0x35, 0x94, 0x07, 0xb5, 0xa1, 0x22, + 0x9f, 0x06, 0x81, 0x43, 0x38, 0x82, 0xb8, 0x39, 0x67, 0x43, 0x17, 0x65, 0xc0, 0xa1, 0xf0, 0xa3, + 0x5b, 0xfc, 0xda, 0xab, 0x9a, 0x51, 0x9d, 0xbb, 0xe9, 0xd8, 0xd8, 0x87, 0xde, 0x86, 0x72, 0x52, + 0x6c, 0xaa, 0x24, 0x9a, 0x0e, 0x4d, 0xbb, 0xd1, 0x2e, 0xa4, 0xa4, 0x41, 0xa3, 0xbd, 0xac, 0x5d, + 0x48, 0x5a, 0x4a, 0x45, 0xa9, 0x0d, 0x7d, 0x00, 0x2b, 0xe9, 0x54, 0xd3, 0xb2, 0xb0, 0xc7, 0x27, + 0x82, 0xd2, 0x63, 0x3a, 0x39, 0x25, 0x5b, 0xda, 0x55, 0x61, 0xe8, 0x3d, 0xa8, 0xd8, 0xf1, 0x20, + 0x09, 0x2f, 0x13, 0x52, 0x59, 0x75, 0x91, 0xf7, 0x08, 0xfb, 0x56, 0xf8, 0x17, 0x63, 0xc2, 0xb3, + 0xb3, 0x61, 0xe8, 0x2d, 0x58, 0xe2, 0xd7, 0x72, 0x07, 0x5b, 0x1c, 0x64, 0xe0, 0xbb, 0x01, 0xaf, + 0x1b, 0xd5, 0xde, 0x10, 0x7f, 0x10, 0xea, 0xb1, 0xc3, 0x90, 0x76, 0x74, 0x1b, 0x50, 0x12, 0x3c, + 0x36, 0x1d, 0x7b, 0x12, 0x46, 0xbf, 0x29, 0xa2, 0x13, 0x98, 0x8f, 0x95, 0x43, 0xff, 0x0c, 0x36, + 0x52, 0x1a, 0x50, 0x66, 0x29, 0x2b, 0x79, 0xd5, 0x4f, 0x4d, 0x87, 0x5c, 0x7a, 0x3a, 0xbc, 0x0a, + 0xa0, 0xd0, 0x43, 0x57, 0x5e, 0xb8, 0x4a, 0xca, 0x72, 0x60, 0x77, 0xbe, 0xc9, 0x43, 0x71, 0x4f, + 0x34, 0x0c, 0xbf, 0x45, 0x95, 0xba, 0x94, 0xba, 0x16, 0xe1, 0xcc, 0xa0, 0xd5, 0xa8, 0x8d, 0x32, + 0x57, 0xa1, 0xe6, 0xac, 0xa3, 0x77, 0x3b, 0xf7, 0x4e, 0x0e, 0x7d, 0x02, 0xa5, 0xb8, 0x8d, 0x90, + 0x16, 0x45, 0x4e, 0x77, 0x56, 0xf3, 0xb5, 0xa4, 0x43, 0x67, 0xdc, 0xb8, 0x38, 0x56, 0x0b, 0xe6, + 0x1f, 0x05, 0xc3, 0x09, 0xa1, 0x63, 0x34, 0xeb, 0x9d, 0xcd, 0x05, 0x51, 0x90, 0xae, 0x75, 0xbc, + 0x9d, 0xe3, 0x13, 0x61, 0x41, 0xb5, 0x3a, 0x46, 0x9b, 0xb3, 0x47, 0x80, 0xdc, 0xc1, 0x33, 0x67, + 0x44, 0xe7, 0xeb, 0x1c, 0x54, 0x24, 0x2d, 0x3d, 0xd3, 0xe1, 0xef, 0xf2, 0xf9, 0x30, 0xbf, 0xac, + 0x8b, 0x55, 0x21, 0xd0, 0xad, 0x08, 0xf1, 0xe9, 0x45, 0x4a, 0xb6, 0x8c, 0x3a, 0x50, 0x7a, 0x80, + 0x99, 0x6a, 0xcb, 0x98, 0xed, 0x4c, 0xe3, 0x36, 0xab, 0x59, 0xf3, 0x5e, 0xfd, 0xc7, 0xdf, 0x37, + 0x72, 0x3f, 0xf1, 0xcf, 0x6f, 0xfc, 0xf3, 0xed, 0x1f, 0x1b, 0xaf, 0x0c, 0x8b, 0x62, 0xb4, 0xbe, + 0xfb, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7e, 0x77, 0xcc, 0x37, 0x36, 0x10, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 603c486cf..85e51dcd4 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -139,8 +139,15 @@ message Status { uint32 connected_handlers = 42; } +message ApplicationHandlerRegistration { + string app_id = 1; + string handler_id = 2; +} + // The BrokerManager service provides configuration and monitoring functionality service BrokerManager { + // Handler announces new application to Broker + rpc RegisterApplicationHandler(ApplicationHandlerRegistration) returns (api.Ack); // Network operator requests Broker status rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 7c08bbb28..9c18a26bd 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -12,13 +12,15 @@ DeviceActivationResponse StatusRequest Status + ApplicationIdentifier + Application */ package handler import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import _ "github.com/TheThingsNetwork/ttn/api" +import api "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" @@ -82,10 +84,33 @@ func (m *Status) String() string { return proto.CompactTextString(m) func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{2} } +type ApplicationIdentifier struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` +} + +func (m *ApplicationIdentifier) Reset() { *m = ApplicationIdentifier{} } +func (m *ApplicationIdentifier) String() string { return proto.CompactTextString(m) } +func (*ApplicationIdentifier) ProtoMessage() {} +func (*ApplicationIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } + +type Application struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + Decoder string `protobuf:"bytes,2,opt,name=decoder,proto3" json:"decoder,omitempty"` + Converter string `protobuf:"bytes,3,opt,name=converter,proto3" json:"converter,omitempty"` + Validator string `protobuf:"bytes,4,opt,name=validator,proto3" json:"validator,omitempty"` +} + +func (m *Application) Reset() { *m = Application{} } +func (m *Application) String() string { return proto.CompactTextString(m) } +func (*Application) ProtoMessage() {} +func (*Application) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } + func init() { proto.RegisterType((*DeviceActivationResponse)(nil), "handler.DeviceActivationResponse") proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") proto.RegisterType((*Status)(nil), "handler.Status") + proto.RegisterType((*ApplicationIdentifier)(nil), "handler.ApplicationIdentifier") + proto.RegisterType((*Application)(nil), "handler.Application") } // Reference imports to suppress errors if they are not otherwise used. @@ -159,6 +184,135 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ Streams: []grpc.StreamDesc{}, } +// Client API for ApplicationManager service + +type ApplicationManagerClient interface { + GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) + SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*api.Ack, error) + DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) +} + +type applicationManagerClient struct { + cc *grpc.ClientConn +} + +func NewApplicationManagerClient(cc *grpc.ClientConn) ApplicationManagerClient { + return &applicationManagerClient{cc} +} + +func (c *applicationManagerClient) GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) { + out := new(Application) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/SetApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DeleteApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for ApplicationManager service + +type ApplicationManagerServer interface { + GetApplication(context.Context, *ApplicationIdentifier) (*Application, error) + SetApplication(context.Context, *Application) (*api.Ack, error) + DeleteApplication(context.Context, *ApplicationIdentifier) (*api.Ack, error) +} + +func RegisterApplicationManagerServer(s *grpc.Server, srv ApplicationManagerServer) { + s.RegisterService(&_ApplicationManager_serviceDesc, srv) +} + +func _ApplicationManager_GetApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).GetApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/GetApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).GetApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_SetApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Application) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).SetApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/SetApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).SetApplication(ctx, req.(*Application)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DeleteApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DeleteApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DeleteApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DeleteApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "handler.ApplicationManager", + HandlerType: (*ApplicationManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetApplication", + Handler: _ApplicationManager_GetApplication_Handler, + }, + { + MethodName: "SetApplication", + Handler: _ApplicationManager_SetApplication_Handler, + }, + { + MethodName: "DeleteApplication", + Handler: _ApplicationManager_DeleteApplication_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, +} + // Client API for HandlerManager service type HandlerManagerClient interface { @@ -310,6 +464,72 @@ func (m *Status) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *ApplicationIdentifier) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ApplicationIdentifier) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + return i, nil +} + +func (m *Application) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Application) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.Decoder) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Decoder))) + i += copy(data[i:], m.Decoder) + } + if len(m.Converter) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Converter))) + i += copy(data[i:], m.Converter) + } + if len(m.Validator) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Validator))) + i += copy(data[i:], m.Validator) + } + return i, nil +} + func encodeFixed64Handler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -371,6 +591,38 @@ func (m *Status) Size() (n int) { return n } +func (m *ApplicationIdentifier) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *Application) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Decoder) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Converter) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Validator) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + func sovHandler(x uint64) (n int) { for { n++ @@ -660,6 +912,251 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } +func (m *ApplicationIdentifier) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ApplicationIdentifier: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ApplicationIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Application) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Application: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Application: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Decoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Decoder = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Converter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Converter = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Validator", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Validator = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipHandler(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -766,29 +1263,36 @@ var ( ) var fileDescriptorHandler = []byte{ - // 369 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x4d, 0x4b, 0xeb, 0x40, - 0x14, 0x7d, 0x79, 0xf0, 0xfa, 0x31, 0x7d, 0xaf, 0x7d, 0x8c, 0x58, 0x43, 0x90, 0xa2, 0x5d, 0x09, - 0x62, 0x02, 0x55, 0x10, 0x71, 0x21, 0x4a, 0xf1, 0x63, 0x51, 0x85, 0xd8, 0x95, 0x9b, 0x32, 0xcd, - 0x5c, 0x9a, 0xa1, 0xe9, 0xcc, 0x98, 0x4c, 0x5a, 0xfc, 0x27, 0xfe, 0x24, 0x97, 0xee, 0xdd, 0x88, - 0xfe, 0x11, 0xc7, 0x64, 0x1a, 0xa9, 0x45, 0xe8, 0xe2, 0x32, 0x73, 0xef, 0xb9, 0xe7, 0xdc, 0xc3, - 0xbd, 0xe8, 0x68, 0xc4, 0x54, 0x98, 0x0e, 0xdd, 0x40, 0x4c, 0xbc, 0x7e, 0x08, 0xfd, 0x90, 0xf1, - 0x51, 0x72, 0x0d, 0x6a, 0x26, 0xe2, 0xb1, 0xa7, 0x14, 0xf7, 0x88, 0x64, 0x5e, 0x48, 0x38, 0x8d, - 0x20, 0x9e, 0xbf, 0xae, 0x8c, 0x85, 0x12, 0xb8, 0x6c, 0x52, 0x67, 0x6f, 0x15, 0x0d, 0x1d, 0x39, - 0xcf, 0x39, 0x5c, 0xa5, 0x7d, 0x18, 0x8b, 0xb1, 0x9e, 0x98, 0x3f, 0x86, 0x78, 0xbc, 0x0a, 0x31, - 0x6b, 0x0d, 0x44, 0x54, 0x7c, 0x72, 0x72, 0xfb, 0xc5, 0x42, 0x76, 0x17, 0xa6, 0x2c, 0x80, 0xd3, - 0x40, 0xb1, 0x29, 0x51, 0x4c, 0x70, 0x1f, 0x12, 0x29, 0x78, 0x02, 0xd8, 0x46, 0x65, 0x49, 0x1e, - 0x22, 0x41, 0xa8, 0x6d, 0x6d, 0x59, 0x3b, 0x7f, 0xfd, 0x79, 0x8a, 0xd7, 0x51, 0x89, 0x48, 0x39, - 0x60, 0xd4, 0xfe, 0xad, 0x81, 0xaa, 0xff, 0x47, 0x67, 0x57, 0x14, 0x9f, 0xa0, 0x06, 0x15, 0x33, - 0x1e, 0x31, 0x3e, 0x1e, 0x08, 0xf9, 0xa9, 0x65, 0xd7, 0x34, 0x5e, 0xeb, 0x34, 0x5d, 0x63, 0xb9, - 0x6b, 0xe0, 0x9b, 0x0c, 0xf5, 0xeb, 0x74, 0x21, 0xc7, 0x3d, 0xb4, 0x46, 0x0a, 0x1f, 0x83, 0x09, - 0x28, 0x42, 0x89, 0x22, 0xf6, 0x46, 0x26, 0xb2, 0xe9, 0x16, 0xe6, 0xbf, 0xcc, 0xf6, 0x4c, 0x8f, - 0x8f, 0xc9, 0x52, 0xad, 0xdd, 0x40, 0xff, 0x6e, 0x15, 0x51, 0x69, 0xe2, 0xc3, 0x7d, 0x0a, 0x89, - 0x6a, 0x57, 0x50, 0x29, 0x2f, 0x74, 0x00, 0x95, 0x2f, 0xf3, 0x43, 0xe1, 0x3b, 0x54, 0x31, 0x7a, - 0x80, 0x77, 0x0b, 0xa3, 0x40, 0x53, 0x19, 0xb1, 0x40, 0x17, 0xe9, 0xf2, 0x82, 0x32, 0x35, 0x67, - 0xdb, 0x9d, 0x9f, 0xfe, 0xa7, 0x15, 0x76, 0xce, 0x51, 0xdd, 0x8c, 0xe9, 0x11, 0x4e, 0x46, 0x7a, - 0xda, 0x01, 0xaa, 0x5e, 0x80, 0xca, 0x5d, 0xe0, 0x66, 0xa1, 0xb0, 0xe0, 0xd3, 0x69, 0x7c, 0xab, - 0x9f, 0xfd, 0x7f, 0x7a, 0x6b, 0x59, 0xcf, 0x3a, 0x5e, 0x75, 0x3c, 0xbe, 0xb7, 0x7e, 0x0d, 0x4b, - 0xd9, 0x32, 0xf6, 0x3f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x34, 0xb4, 0xf6, 0xbb, 0xab, 0x02, 0x00, - 0x00, + // 491 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0x4b, 0x8b, 0x13, 0x41, + 0x10, 0x76, 0x7c, 0xe4, 0x51, 0xd1, 0x44, 0x5b, 0x77, 0x1d, 0x86, 0x25, 0xe8, 0x9c, 0x04, 0x71, + 0x22, 0x51, 0x10, 0x11, 0x91, 0x48, 0x50, 0xf7, 0x10, 0x85, 0xd9, 0x3d, 0x79, 0x09, 0x9d, 0xe9, + 0x32, 0x69, 0x32, 0xdb, 0xdd, 0xce, 0x74, 0xb2, 0xe8, 0x2f, 0xf1, 0x27, 0x79, 0x11, 0xbc, 0x7b, + 0x11, 0xfd, 0x23, 0x76, 0xe6, 0xd1, 0x3b, 0x71, 0x13, 0xc8, 0x1e, 0x9a, 0x9e, 0xaa, 0xaf, 0x1e, + 0xdf, 0x57, 0x5d, 0x03, 0xcf, 0xa7, 0x5c, 0xcf, 0x16, 0x93, 0x20, 0x92, 0x27, 0xbd, 0xe3, 0x19, + 0x1e, 0xcf, 0xb8, 0x98, 0xa6, 0xef, 0x51, 0x9f, 0xca, 0x64, 0xde, 0xd3, 0x5a, 0xf4, 0xa8, 0xe2, + 0xbd, 0x19, 0x15, 0x2c, 0xc6, 0xa4, 0xbc, 0x03, 0x95, 0x48, 0x2d, 0x49, 0xbd, 0x30, 0xbd, 0x47, + 0xbb, 0xd4, 0x30, 0x27, 0xcf, 0xf3, 0x9e, 0xed, 0x12, 0x3e, 0x49, 0xe4, 0xdc, 0x74, 0xcc, 0xaf, + 0x22, 0xf1, 0xc5, 0x2e, 0x89, 0x59, 0x68, 0x24, 0x63, 0xfb, 0x91, 0x27, 0xfb, 0xbf, 0x1c, 0x70, + 0x87, 0xb8, 0xe4, 0x11, 0x0e, 0x22, 0xcd, 0x97, 0x54, 0x73, 0x29, 0x42, 0x4c, 0x95, 0x14, 0x29, + 0x12, 0x17, 0xea, 0x8a, 0x7e, 0x89, 0x25, 0x65, 0xae, 0x73, 0xcf, 0x79, 0x70, 0x3d, 0x2c, 0x4d, + 0xb2, 0x07, 0x35, 0xaa, 0xd4, 0x98, 0x33, 0xf7, 0xb2, 0x01, 0x9a, 0xe1, 0x35, 0x63, 0x1d, 0x32, + 0xf2, 0x0a, 0x3a, 0x4c, 0x9e, 0x8a, 0x98, 0x8b, 0xf9, 0x58, 0xaa, 0x55, 0x2d, 0xb7, 0x65, 0xf0, + 0x56, 0x7f, 0x3f, 0x28, 0x28, 0x0f, 0x0b, 0xf8, 0x43, 0x86, 0x86, 0x6d, 0xb6, 0x66, 0x93, 0x11, + 0xdc, 0xa6, 0x96, 0xc7, 0xf8, 0x04, 0x35, 0x65, 0x54, 0x53, 0xf7, 0x6e, 0x56, 0xe4, 0x20, 0xb0, + 0xe4, 0xcf, 0xc8, 0x8e, 0x8a, 0x98, 0x90, 0xd0, 0x73, 0x3e, 0xbf, 0x03, 0x37, 0x8e, 0x34, 0xd5, + 0x8b, 0x34, 0xc4, 0xcf, 0x0b, 0x4c, 0xb5, 0xdf, 0x80, 0x5a, 0xee, 0xf0, 0x03, 0xd8, 0x1b, 0x28, + 0x15, 0xf3, 0x28, 0xcb, 0x38, 0x64, 0x28, 0x34, 0xff, 0xc4, 0x31, 0xa9, 0x48, 0x73, 0x2a, 0xd2, + 0xfc, 0xaf, 0xd0, 0xaa, 0xc4, 0x6f, 0x89, 0x5a, 0x4d, 0x8c, 0x61, 0x24, 0x19, 0x26, 0xc5, 0x60, + 0x4a, 0x93, 0x1c, 0x40, 0x33, 0x92, 0x62, 0x89, 0x89, 0x36, 0xd8, 0x95, 0x0c, 0x3b, 0x73, 0xac, + 0xd0, 0x25, 0x8d, 0xb9, 0x21, 0x2d, 0x13, 0xf7, 0x6a, 0x8e, 0x5a, 0x47, 0x1f, 0xa1, 0xfe, 0x2e, + 0x5f, 0x2a, 0xf2, 0x11, 0x1a, 0x85, 0x76, 0x24, 0x0f, 0xed, 0x50, 0x91, 0x2d, 0x72, 0x6a, 0xc8, + 0xce, 0x3f, 0x66, 0xa6, 0xdc, 0xbb, 0x1f, 0x94, 0x6b, 0xba, 0xed, 0xb9, 0xfb, 0x3f, 0x1c, 0x20, + 0x15, 0x8d, 0x23, 0x2a, 0xe8, 0xd4, 0xb4, 0x7c, 0x03, 0xed, 0xb7, 0xa8, 0xab, 0xe2, 0xbb, 0xb6, + 0xd6, 0xc6, 0x11, 0x7a, 0x77, 0x36, 0xe1, 0xe4, 0x31, 0xb4, 0x8f, 0xd6, 0xeb, 0x6c, 0x8c, 0xf3, + 0x1a, 0xc1, 0xea, 0xa7, 0x18, 0x44, 0x73, 0xf2, 0x12, 0x6e, 0x0d, 0x31, 0x46, 0x8d, 0x17, 0x69, + 0x6e, 0xd3, 0xfb, 0x86, 0x78, 0x31, 0xb6, 0x52, 0xca, 0x53, 0x68, 0x1a, 0x29, 0xf9, 0x06, 0x90, + 0x7d, 0x5b, 0x68, 0x6d, 0x47, 0xbc, 0xce, 0x7f, 0xfe, 0xd7, 0x37, 0xbf, 0xff, 0xe9, 0x3a, 0x3f, + 0xcd, 0xf9, 0x6d, 0xce, 0xb7, 0xbf, 0xdd, 0x4b, 0x93, 0x5a, 0xb6, 0x88, 0x4f, 0xfe, 0x05, 0x00, + 0x00, 0xff, 0xff, 0x38, 0xc6, 0xdf, 0x31, 0x27, 0x04, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index cf2c35694..db82a844c 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -24,6 +24,23 @@ message StatusRequest {} // message Status is the response to the StatusRequest message Status {} +message ApplicationIdentifier { + string app_id = 1; +} + +message Application { + string app_id = 1; + string decoder = 2; + string converter = 3; + string validator = 4; +} + +service ApplicationManager { + rpc GetApplication(ApplicationIdentifier) returns (Application); + rpc SetApplication(Application) returns (api.Ack); + rpc DeleteApplication(ApplicationIdentifier) returns (api.Ack); +} + // The HandlerManager service provides configuration and monitoring // functionality service HandlerManager { diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 620ad9919..928d4f784 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -64,8 +64,10 @@ type Device struct { DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,3,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,4,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,5,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - FCntUp uint32 `protobuf:"varint,6,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` - FCntDown uint32 `protobuf:"varint,7,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` + AppSKey *github_com_TheThingsNetwork_ttn_core_types.AppSKey `protobuf:"bytes,6,opt,name=app_s_key,json=appSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppSKey" json:"app_s_key,omitempty"` + AppKey *github_com_TheThingsNetwork_ttn_core_types.AppKey `protobuf:"bytes,7,opt,name=app_key,json=appKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppKey" json:"app_key,omitempty"` + FCntUp uint32 `protobuf:"varint,8,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` + FCntDown uint32 `protobuf:"varint,9,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` // Options DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` @@ -323,13 +325,33 @@ func (m *Device) MarshalTo(data []byte) (int, error) { } i += n6 } + if m.AppSKey != nil { + data[i] = 0x32 + i++ + i = encodeVarintDevice(data, i, uint64(m.AppSKey.Size())) + n7, err := m.AppSKey.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } + if m.AppKey != nil { + data[i] = 0x3a + i++ + i = encodeVarintDevice(data, i, uint64(m.AppKey.Size())) + n8, err := m.AppKey.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } if m.FCntUp != 0 { - data[i] = 0x30 + data[i] = 0x40 i++ i = encodeVarintDevice(data, i, uint64(m.FCntUp)) } if m.FCntDown != 0 { - data[i] = 0x38 + data[i] = 0x48 i++ i = encodeVarintDevice(data, i, uint64(m.FCntDown)) } @@ -424,6 +446,14 @@ func (m *Device) Size() (n int) { l = m.NwkSKey.Size() n += 1 + l + sovDevice(uint64(l)) } + if m.AppSKey != nil { + l = m.AppSKey.Size() + n += 1 + l + sovDevice(uint64(l)) + } + if m.AppKey != nil { + l = m.AppKey.Size() + n += 1 + l + sovDevice(uint64(l)) + } if m.FCntUp != 0 { n += 1 + sovDevice(uint64(m.FCntUp)) } @@ -782,6 +812,70 @@ func (m *Device) Unmarshal(data []byte) error { } iNdEx = postIndex case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppSKey + m.AppSKey = &v + if err := m.AppSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppKey + m.AppKey = &v + if err := m.AppKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) } @@ -800,7 +894,7 @@ func (m *Device) Unmarshal(data []byte) error { break } } - case 7: + case 9: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) } @@ -986,35 +1080,37 @@ var ( ) var fileDescriptorDevice = []byte{ - // 480 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x93, 0xdf, 0x8a, 0xd3, 0x40, - 0x14, 0xc6, 0xcd, 0xd6, 0x4d, 0xd3, 0xb1, 0xc5, 0x12, 0x11, 0x62, 0x91, 0x55, 0xf6, 0x42, 0xbd, - 0xd9, 0x04, 0x5b, 0xd4, 0xeb, 0x76, 0xbb, 0x4a, 0x11, 0x0b, 0x66, 0x77, 0xaf, 0x43, 0xfe, 0x9c, - 0xa6, 0x43, 0xea, 0xcc, 0x90, 0x4c, 0x0c, 0x7d, 0x13, 0x5f, 0xc0, 0x77, 0xd1, 0x3b, 0xaf, 0xbd, - 0x10, 0xd1, 0x77, 0xf0, 0xda, 0x33, 0x33, 0x65, 0xa9, 0x05, 0x59, 0x76, 0xd9, 0x8b, 0x81, 0x39, - 0xe7, 0x7c, 0xdf, 0x6f, 0xe6, 0x30, 0x73, 0xc8, 0x38, 0xa7, 0x72, 0x59, 0x27, 0x7e, 0xca, 0x3f, - 0x04, 0x67, 0x4b, 0x38, 0x5b, 0x52, 0x96, 0x57, 0x73, 0x90, 0x0d, 0x2f, 0x8b, 0x40, 0x4a, 0x16, - 0xc4, 0x82, 0x06, 0xa2, 0xe4, 0x92, 0xa7, 0x7c, 0x15, 0xac, 0x78, 0x19, 0x37, 0x31, 0x0b, 0x32, - 0xf8, 0x48, 0x53, 0xf0, 0x75, 0xde, 0x6d, 0x6f, 0xb2, 0x83, 0xa3, 0x2d, 0x56, 0xce, 0x73, 0x6e, - 0x7c, 0x49, 0xbd, 0xd0, 0x91, 0x0e, 0xf4, 0xce, 0xf8, 0xfe, 0x91, 0xff, 0xf7, 0x68, 0x5c, 0x46, - 0x7e, 0xf8, 0xd5, 0x22, 0xfd, 0xa9, 0x3e, 0x77, 0x96, 0x01, 0x93, 0x74, 0x41, 0xa1, 0x74, 0xe7, - 0xa4, 0x1d, 0x0b, 0x11, 0x41, 0x4d, 0x3d, 0xeb, 0xb1, 0xf5, 0xac, 0x3b, 0x79, 0xf1, 0xfd, 0xc7, - 0xa3, 0xe7, 0x97, 0x81, 0x53, 0x5e, 0x42, 0x20, 0xd7, 0x02, 0x2a, 0x7f, 0x2c, 0xc4, 0xc9, 0xf9, - 0x2c, 0xb4, 0x91, 0x72, 0x52, 0x53, 0xc5, 0xc3, 0xde, 0x34, 0x6f, 0xef, 0x5a, 0x3c, 0xbc, 0xa1, - 0xe6, 0x21, 0x45, 0xf1, 0xee, 0x13, 0x45, 0x8e, 0x68, 0xe6, 0xb5, 0x10, 0xd7, 0x09, 0xf7, 0x31, - 0x9a, 0x65, 0x87, 0x7f, 0x5a, 0xc4, 0x36, 0xbd, 0x6c, 0x29, 0xac, 0x2d, 0xc5, 0x76, 0x63, 0x7b, - 0x37, 0xdc, 0x58, 0xeb, 0x26, 0x1a, 0x7b, 0x4f, 0x1c, 0xc5, 0x8b, 0xb3, 0xac, 0xf4, 0x6e, 0x6b, - 0xe0, 0x4b, 0x04, 0x0e, 0xaf, 0x06, 0x1c, 0xa3, 0x3b, 0x54, 0xf7, 0x52, 0x1b, 0x37, 0x24, 0x1d, - 0xd6, 0x14, 0x51, 0x15, 0x15, 0xb0, 0xf6, 0xf6, 0xaf, 0xc5, 0x9c, 0x37, 0xc5, 0xe9, 0x5b, 0x58, - 0x87, 0x6d, 0x66, 0x36, 0xae, 0x47, 0x9c, 0x45, 0x94, 0x32, 0x19, 0xd5, 0xc2, 0xb3, 0x11, 0xd9, - 0x0b, 0xed, 0xc5, 0x31, 0x93, 0xe7, 0xc2, 0x7d, 0x48, 0x88, 0xa9, 0x64, 0xbc, 0x61, 0x5e, 0x5b, - 0xd7, 0x1c, 0x55, 0x9b, 0x62, 0xec, 0x1e, 0x91, 0x7b, 0x19, 0xad, 0xe2, 0x64, 0x05, 0x91, 0x51, - 0xa5, 0x4b, 0x48, 0x0b, 0xef, 0x0e, 0xca, 0x9c, 0xb0, 0xbf, 0x29, 0xbd, 0x46, 0xf5, 0xb1, 0xca, - 0xbb, 0x4f, 0x49, 0xbf, 0xae, 0xa0, 0x1a, 0x0d, 0xa3, 0x84, 0x4a, 0xe3, 0xf0, 0xba, 0x5a, 0xdb, - 0x33, 0xf9, 0x09, 0x95, 0x4a, 0x3d, 0xfc, 0x6c, 0x91, 0x9e, 0x79, 0xf8, 0x77, 0x31, 0x8b, 0x73, - 0xfc, 0xc1, 0xaf, 0x48, 0xe7, 0x0d, 0xc8, 0xcd, 0x67, 0x78, 0xe0, 0x6f, 0x66, 0xc9, 0xdf, 0xfd, - 0xe9, 0x83, 0xbb, 0x3b, 0x25, 0xf7, 0x09, 0xe9, 0x9c, 0x5e, 0x18, 0x77, 0xab, 0x03, 0xc7, 0x57, - 0x93, 0x33, 0xc6, 0xbb, 0x8d, 0x48, 0x77, 0x0a, 0x2b, 0x90, 0x70, 0xf9, 0x19, 0x17, 0xa6, 0x49, - 0xff, 0xcb, 0xaf, 0x03, 0xeb, 0x1b, 0xae, 0x9f, 0xb8, 0x3e, 0xfd, 0x3e, 0xb8, 0x95, 0xd8, 0x7a, - 0x0a, 0x47, 0x7f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x45, 0x17, 0xc4, 0xb2, 0x31, 0x04, 0x00, 0x00, + // 504 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0xcf, 0x8b, 0xd3, 0x40, + 0x14, 0xc7, 0xcd, 0xae, 0x4d, 0x93, 0xb1, 0xc5, 0x12, 0x11, 0x62, 0x91, 0x55, 0xf6, 0xa0, 0x5e, + 0x36, 0xc1, 0x16, 0xf5, 0xdc, 0x6e, 0x57, 0x29, 0x62, 0xc1, 0xec, 0xee, 0x39, 0xe4, 0xc7, 0x6b, + 0x3a, 0xa4, 0xce, 0x84, 0x64, 0x62, 0xe8, 0x7f, 0x22, 0xde, 0xfd, 0x5f, 0xf4, 0xe6, 0xd9, 0x83, + 0x88, 0xfe, 0x23, 0xbe, 0x99, 0xa9, 0x4b, 0x2d, 0xc8, 0x62, 0xe9, 0x61, 0xe0, 0xfd, 0xf8, 0xbe, + 0xcf, 0xcc, 0xcb, 0xcc, 0x0b, 0x19, 0x65, 0x54, 0x2c, 0xea, 0xd8, 0x4b, 0xf8, 0x3b, 0xff, 0x62, + 0x01, 0x17, 0x0b, 0xca, 0xb2, 0x6a, 0x06, 0xa2, 0xe1, 0x65, 0xee, 0x0b, 0xc1, 0xfc, 0xa8, 0xa0, + 0x7e, 0x51, 0x72, 0xc1, 0x13, 0xbe, 0xf4, 0x97, 0xbc, 0x8c, 0x9a, 0x88, 0xf9, 0x29, 0xbc, 0xa7, + 0x09, 0x78, 0x2a, 0xee, 0xb4, 0xd7, 0xd1, 0xfe, 0xc9, 0x06, 0x2b, 0xe3, 0x19, 0xd7, 0x75, 0x71, + 0x3d, 0x57, 0x9e, 0x72, 0x94, 0xa5, 0xeb, 0xfe, 0x92, 0xff, 0x73, 0x6b, 0x5c, 0x5a, 0x7e, 0xfc, + 0xc5, 0x20, 0xbd, 0x89, 0xda, 0x77, 0x9a, 0x02, 0x13, 0x74, 0x4e, 0xa1, 0x74, 0x66, 0xa4, 0x1d, + 0x15, 0x45, 0x08, 0x35, 0x75, 0x8d, 0x87, 0xc6, 0x93, 0xce, 0xf8, 0xd9, 0xb7, 0xef, 0x0f, 0x9e, + 0x5e, 0x07, 0x4e, 0x78, 0x09, 0xbe, 0x58, 0x15, 0x50, 0x79, 0xa3, 0xa2, 0x38, 0xbb, 0x9c, 0x06, + 0x26, 0x52, 0xce, 0x6a, 0x2a, 0x79, 0xd8, 0x9b, 0xe2, 0x1d, 0xec, 0xc4, 0xc3, 0x13, 0x2a, 0x1e, + 0x52, 0x24, 0xef, 0x2e, 0x91, 0xe4, 0x90, 0xa6, 0xee, 0x21, 0xe2, 0xec, 0xa0, 0x85, 0xde, 0x34, + 0x3d, 0xfe, 0xd8, 0x22, 0xa6, 0xee, 0x65, 0x43, 0x61, 0x6c, 0x28, 0x36, 0x1b, 0x3b, 0xd8, 0x73, + 0x63, 0x87, 0xfb, 0x68, 0xec, 0x2d, 0xb1, 0x24, 0x2f, 0x4a, 0xd3, 0xd2, 0xbd, 0xa9, 0x80, 0xcf, + 0x11, 0x38, 0xf8, 0x3f, 0xe0, 0x08, 0xab, 0x03, 0x79, 0x2e, 0x69, 0x38, 0x01, 0xb1, 0x59, 0x93, + 0x87, 0x55, 0x98, 0xc3, 0xca, 0x6d, 0xed, 0xc4, 0x9c, 0x35, 0xf9, 0xf9, 0x6b, 0x58, 0x05, 0x6d, + 0xa6, 0x0d, 0xc9, 0x94, 0x9f, 0x51, 0x33, 0xcd, 0x9d, 0x98, 0xf8, 0x21, 0x35, 0x33, 0xd2, 0xc6, + 0x9f, 0xab, 0x91, 0xc4, 0xf6, 0xae, 0x57, 0x23, 0x81, 0xf2, 0x6a, 0x24, 0xcf, 0x25, 0xd6, 0x3c, + 0x4c, 0x98, 0x08, 0xeb, 0xc2, 0xb5, 0x10, 0xd8, 0x0d, 0xcc, 0xf9, 0x29, 0x13, 0x97, 0x85, 0x73, + 0x9f, 0x10, 0x9d, 0x49, 0x79, 0xc3, 0x5c, 0x5b, 0xe5, 0x2c, 0x99, 0x9b, 0xa0, 0xef, 0x9c, 0x90, + 0x3b, 0x29, 0xad, 0xa2, 0x78, 0x09, 0xa1, 0x56, 0x25, 0x0b, 0x48, 0x72, 0xf7, 0x16, 0xca, 0xac, + 0xa0, 0xb7, 0x4e, 0xbd, 0x44, 0xf5, 0xa9, 0x8c, 0x3b, 0x8f, 0x49, 0xaf, 0xae, 0xa0, 0x1a, 0x0e, + 0xc2, 0x98, 0x0a, 0x5d, 0xe1, 0x76, 0x94, 0xb6, 0xab, 0xe3, 0x63, 0x2a, 0xa4, 0x7a, 0xf0, 0xc9, + 0x20, 0x5d, 0xfd, 0x38, 0xdf, 0x44, 0x2c, 0xca, 0x70, 0xca, 0x5e, 0x10, 0xfb, 0x15, 0x88, 0xf5, + 0x83, 0xbd, 0xe7, 0xad, 0xe7, 0xdd, 0xdb, 0x9e, 0xc6, 0xfe, 0xed, 0xad, 0x94, 0xf3, 0x88, 0xd8, + 0xe7, 0x57, 0x85, 0xdb, 0xd9, 0xbe, 0xe5, 0xc9, 0xe9, 0x1e, 0xe1, 0xd9, 0x86, 0xa4, 0x33, 0x81, + 0x25, 0x08, 0xb8, 0x7e, 0x8f, 0xab, 0xa2, 0x71, 0xef, 0xf3, 0xcf, 0x23, 0xe3, 0x2b, 0xae, 0x1f, + 0xb8, 0x3e, 0xfc, 0x3a, 0xba, 0x11, 0x9b, 0xea, 0x4f, 0x31, 0xfc, 0x1d, 0x00, 0x00, 0xff, 0xff, + 0x88, 0x65, 0xa1, 0x40, 0xd5, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 394e0af9e..36d112d65 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -17,8 +17,10 @@ message Device { bytes dev_eui = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes dev_addr = 4 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; bytes nwk_s_key = 5 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; - uint32 f_cnt_up = 6; - uint32 f_cnt_down = 7; + bytes app_s_key = 6 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppSKey"]; + bytes app_key = 7 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppKey"]; + uint32 f_cnt_up = 8; + uint32 f_cnt_down = 9; // Options bool disable_f_cnt_check = 11; diff --git a/cmd/broker.go b/cmd/broker.go index bf6e32245..70ddc578a 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -80,6 +80,7 @@ var brokerCmd = &cobra.Command{ // Register and Listen broker.RegisterRPC(grpc) + broker.RegisterManager(grpc) go grpc.Serve(lis) sigChan := make(chan os.Signal) diff --git a/cmd/handler.go b/cmd/handler.go index aa9333bed..33715732d 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -65,6 +65,7 @@ var handlerCmd = &cobra.Command{ // Register and Listen handler.RegisterRPC(grpc) + handler.RegisterManager(grpc) go grpc.Serve(lis) sigChan := make(chan os.Signal) diff --git a/cmd/networkserver.go b/cmd/networkserver.go index b25cef961..ea1f6dd6d 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -70,6 +70,7 @@ var networkserverCmd = &cobra.Command{ // Register and Listen networkserver.RegisterRPC(grpc) + networkserver.RegisterManager(grpc) go grpc.Serve(lis) sigChan := make(chan os.Signal) diff --git a/core/broker/broker.go b/core/broker/broker.go index 3aa801690..50412a762 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -19,6 +19,7 @@ import ( type Broker interface { core.ComponentInterface + core.ManagementInterface HandleUplink(uplink *pb.UplinkMessage) error HandleDownlink(downlink *pb.DownlinkMessage) error diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go new file mode 100644 index 000000000..1d7c9eef3 --- /dev/null +++ b/core/broker/manager_server.go @@ -0,0 +1,55 @@ +package broker + +import ( + "errors" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type brokerManager struct { + *broker +} + +var errf = grpc.Errorf + +func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { + return b.nsManager.GetDevice(ctx, in) +} + +func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*api.Ack, error) { + return b.nsManager.SetDevice(ctx, in) +} + +func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*api.Ack, error) { + return b.nsManager.DeleteDevice(ctx, in) +} + +func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*api.Ack, error) { + claims, err := b.Component.ValidateContext(ctx) + if err != nil { + return nil, err + } + if !claims.CanEditApp(in.AppId) { + return nil, errf(codes.Unauthenticated, "No access to this application") + } + err = b.handlerDiscovery.AddAppID(in.HandlerId, in.AppId) + if err != nil { + return nil, err + } + return &api.Ack{}, nil +} + +func (b *brokerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + return nil, errors.New("Not Implemented") +} + +func (b *broker) RegisterManager(s *grpc.Server) { + server := &brokerManager{b} + pb.RegisterBrokerManagerServer(s, server) + lorawan.RegisterDeviceManagerServer(s, server) +} diff --git a/core/component.go b/core/component.go index 95d261c99..1d376ff92 100644 --- a/core/component.go +++ b/core/component.go @@ -25,6 +25,10 @@ type ComponentInterface interface { Init(c *Component) error } +type ManagementInterface interface { + RegisterManager(s *grpc.Server) +} + // NewComponent creates a new Component func NewComponent(ctx log.Interface, serviceName string, announcedAddress string) *Component { go func() { @@ -114,6 +118,33 @@ type TTNClaims struct { Apps map[string][]string `json:"apps,omitempty"` } +// CanEditApp indicates wheter someone with the claims can manage the given app +func (c *TTNClaims) CanEditApp(appID string) bool { + for id, rights := range c.Apps { + if appID == id { + for _, right := range rights { + if right == "settings" { + return true + } + } + } + } + return false +} + +// ValidateContext gets a token from the context and validates it +func (c *Component) ValidateContext(ctx context.Context) (*TTNClaims, error) { + md, ok := metadata.FromContext(ctx) + if !ok { + return nil, errors.New("ttn: Could not get metadata") + } + token, ok := md["token"] + if !ok || len(token) < 1 { + return nil, errors.New("ttn: Could not get token") + } + return c.ValidateToken(token[0]) +} + // ValidateToken verifies an OAuth Bearer token func (c *Component) ValidateToken(token string) (*TTNClaims, error) { ttnClaims := &TTNClaims{} @@ -128,7 +159,11 @@ func (c *Component) ValidateToken(token string) (*TTNClaims, error) { if k.Algorithm != token.Header["alg"] { return nil, fmt.Errorf("Expected algorithm %v but got %v", k.Algorithm, token.Header["alg"]) } - return []byte(k.Key), nil + key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(k.Key)) + if err != nil { + return nil, err + } + return key, nil }) if err != nil { return nil, fmt.Errorf("Unable to parse token: %s", err.Error()) @@ -192,14 +227,16 @@ func (c *Component) ServerOptions() []grpc.ServerOption { // GetContext returns a context for outgoing RPC requests func (c *Component) GetContext() context.Context { - var id, token string + var id, token, netAddress string if c.Identity != nil { id = c.Identity.Id token = c.Identity.Token + netAddress = c.Identity.NetAddress } md := metadata.Pairs( - "token", token, "id", id, + "token", token, + "net-address", netAddress, ) ctx := metadata.NewContext(context.Background(), md) return ctx diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go index 90361b818..b6676b900 100644 --- a/core/discovery/handler_discovery.go +++ b/core/discovery/handler_discovery.go @@ -13,10 +13,11 @@ import ( ) // HandlerCacheTime indicates how long the HandlerDiscovery should cache the services -var HandlerCacheTime = 30 * time.Minute +var HandlerCacheTime = 5 * time.Minute // HandlerDiscovery is used as a client to discover Handlers type HandlerDiscovery interface { + AddAppID(handlerID, appID string) error ForAppID(appID string) ([]*pb.Announcement, error) Get(id string) (*pb.Announcement, error) All() ([]*pb.Announcement, error) @@ -112,3 +113,21 @@ func (d *handlerDiscovery) ForAppID(appID string) ([]*pb.Announcement, error) { } return matches, nil } + +func (d *handlerDiscovery) AddAppID(handlerID, appID string) error { + d.cacheLock.Lock() + defer d.cacheLock.Unlock() + handler, found := d.byID[handlerID] + if !found { + return errors.New("ttn/discovery: Handler not found") + } + existing, found := d.byAppID[appID] + if found && len(existing) > 0 { + if existing[0].Id == handlerID { + return nil + } + return errors.New("ttn/discovery: AppID already registered") + } + d.byAppID[appID] = []*pb.Announcement{handler} + return nil +} diff --git a/core/handler/handler.go b/core/handler/handler.go index 2087b5dd4..67d86dc5d 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -3,6 +3,7 @@ package handler import ( "fmt" "io" + "sync" "time" "google.golang.org/grpc" @@ -12,6 +13,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" @@ -22,6 +24,7 @@ import ( // Handler component type Handler interface { core.ComponentInterface + core.ManagementInterface HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) @@ -43,12 +46,16 @@ func NewRedisHandler(client *redis.Client, ttnBrokerAddr string, mqttUsername st type handler struct { *core.Component - devices device.Store - applications application.Store + devices device.Store + applications application.Store + applicationIds []string // this is a cache + applicationIdsLock sync.RWMutex - ttnBrokerAddr string - ttnBrokerConn *grpc.ClientConn - ttnBroker pb_broker.BrokerClient + ttnBrokerAddr string + ttnBrokerConn *grpc.ClientConn + ttnBroker pb_broker.BrokerClient + ttnBrokerManager pb_broker.BrokerManagerClient + ttnDeviceManager pb_lorawan.DeviceManagerClient downlink chan *pb_broker.DownlinkMessage @@ -61,6 +68,18 @@ type handler struct { mqttActivation chan *mqtt.Activation } +func (h *handler) announce() error { + h.applicationIdsLock.RLock() + defer h.applicationIdsLock.RUnlock() + h.Component.Identity.Metadata = []*pb_discovery.Metadata{} + for _, id := range h.applicationIds { + h.Identity.Metadata = append(h.Component.Identity.Metadata, &pb_discovery.Metadata{ + Key: pb_discovery.Metadata_APP_ID, Value: []byte(id), + }) + } + return h.Component.Announce() +} + func (h *handler) Init(c *core.Component) error { h.Component = c err := h.Component.UpdateTokenKey() @@ -76,13 +95,12 @@ func (h *handler) Init(c *core.Component) error { if err != nil { return err } + h.applicationIdsLock.Lock() for _, app := range apps { - h.Component.Identity.Metadata = append(h.Component.Identity.Metadata, &pb_discovery.Metadata{ - Key: pb_discovery.Metadata_APP_ID, Value: []byte(app.AppID), - }) + h.applicationIds = append(h.applicationIds, app.AppID) } - - err = h.Component.Announce() + h.applicationIdsLock.Unlock() + err = h.announce() if err != nil { return err } @@ -111,6 +129,8 @@ func (h *handler) associateBroker() error { } h.ttnBrokerConn = conn h.ttnBroker = pb_broker.NewBrokerClient(conn) + h.ttnBrokerManager = pb_broker.NewBrokerManagerClient(conn) + h.ttnDeviceManager = pb_lorawan.NewDeviceManagerClient(conn) ctx := h.GetContext() diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go new file mode 100644 index 000000000..5bd0926bc --- /dev/null +++ b/core/handler/manager_server.go @@ -0,0 +1,254 @@ +package handler + +import ( + "errors" + + "github.com/TheThingsNetwork/ttn/api" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb "github.com/TheThingsNetwork/ttn/api/handler" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type handlerManager struct { + *handler +} + +var errf = grpc.Errorf + +func (h *handlerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { + if in.AppId == "" || in.AppEui == nil || in.DevEui == nil { + return nil, errf(codes.InvalidArgument, "AppID, AppEUI and DevEUI are required") + } + claims, err := h.Component.ValidateContext(ctx) + if err != nil { + return nil, err + } + if !claims.CanEditApp(in.AppId) { + return nil, errf(codes.Unauthenticated, "No access to this device") + } + dev, err := h.devices.Get(*in.AppEui, *in.DevEui) + if err != nil { + return nil, err + } + if !claims.CanEditApp(dev.AppID) { + return nil, errf(codes.Unauthenticated, "No access to this device") + } + return dev, nil +} + +func (h *handlerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { + dev, err := h.getDevice(ctx, in) + if err != nil { + return nil, err + } + + nsDev, err := h.ttnDeviceManager.GetDevice(ctx, in) + if err != nil { + return nil, err + } + + return &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: nsDev.AppEui, + DevEui: nsDev.DevEui, + DevAddr: nsDev.DevAddr, + NwkSKey: nsDev.NwkSKey, + AppSKey: &dev.AppSKey, + AppKey: &dev.AppKey, + FCntUp: nsDev.FCntUp, + FCntDown: nsDev.FCntDown, + DisableFCntCheck: nsDev.DisableFCntCheck, + Uses32BitFCnt: nsDev.Uses32BitFCnt, + }, nil +} + +func (h *handlerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*api.Ack, error) { + _, err := h.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppId: in.AppId, AppEui: in.AppEui, DevEui: in.DevEui}) + if err != nil && err != device.ErrNotFound { + return nil, err + } + + _, err = h.ttnDeviceManager.SetDevice(ctx, &pb_lorawan.Device{ + AppId: in.AppId, + AppEui: in.AppEui, + DevEui: in.DevEui, + DevAddr: in.DevAddr, + NwkSKey: in.NwkSKey, + FCntUp: in.FCntUp, + FCntDown: in.FCntDown, + DisableFCntCheck: in.DisableFCntCheck, + Uses32BitFCnt: in.Uses32BitFCnt, + }) + if err != nil { + return nil, err + } + + updated := &device.Device{ + AppID: in.AppId, + AppEUI: *in.AppEui, + DevEUI: *in.DevEui, + } + + if in.DevAddr != nil && in.NwkSKey != nil && in.AppSKey != nil { + updated.DevAddr = *in.DevAddr + updated.NwkSKey = *in.NwkSKey + updated.AppSKey = *in.AppSKey + } + + if in.AppKey != nil { + updated.AppKey = *in.AppKey + } + + err = h.devices.Set(updated) + if err != nil { + return nil, err + } + + return &api.Ack{}, nil +} + +func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*api.Ack, error) { + _, err := h.getDevice(ctx, in) + if err != nil { + return nil, err + } + _, err = h.ttnDeviceManager.DeleteDevice(ctx, in) + if err != nil { + return nil, err + } + err = h.devices.Delete(*in.AppEui, *in.DevEui) + if err != nil { + return nil, err + } + return &api.Ack{}, nil +} + +func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*application.Application, error) { + if in.AppId == "" { + return nil, errf(codes.InvalidArgument, "AppID is required") + } + claims, err := h.Component.ValidateContext(ctx) + if err != nil { + return nil, err + } + if !claims.CanEditApp(in.AppId) { + return nil, errf(codes.Unauthenticated, "No access to this application") + } + app, err := h.applications.Get(in.AppId) + if err != nil { + return nil, err + } + if !claims.CanEditApp(app.AppID) { + return nil, errf(codes.Unauthenticated, "No access to this application") + } + return app, nil +} + +func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { + claims, err := h.Component.ValidateContext(ctx) + if err != nil { + return nil, err + } + if !claims.CanEditApp(in.AppId) { + return nil, errf(codes.Unauthenticated, "No access to this application") + } + + app, err := h.applications.Get(in.AppId) + if err != nil { + return nil, err + } + return &pb.Application{ + AppId: app.AppID, + Decoder: app.Decoder, + Converter: app.Converter, + Validator: app.Validator, + }, nil +} + +func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*api.Ack, error) { + app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) + if err != nil && err != application.ErrNotFound { + return nil, err + } + + err = h.applications.Set(&application.Application{ + AppID: in.AppId, + Decoder: in.Decoder, + Converter: in.Converter, + Validator: in.Validator, + }) + if err != nil { + return nil, err + } + + if app == nil { + // Add this application ID to the cache if needed + h.applicationIdsLock.Lock() + var alreadyInCache bool + for _, id := range h.applicationIds { + if id == in.AppId { + alreadyInCache = true + break + } + } + if !alreadyInCache { + h.applicationIds = append(h.applicationIds, in.AppId) + } + h.applicationIdsLock.Unlock() + + // If we had to add it, we also have to announce it to the Discovery and Broker + if !alreadyInCache { + h.announce() + _, err := h.ttnBrokerManager.RegisterApplicationHandler(ctx, &pb_broker.ApplicationHandlerRegistration{ + AppId: in.AppId, + HandlerId: h.Identity.Id, + }) + if err != nil { + h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") + } + } + } + + return &api.Ack{}, nil +} + +func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*api.Ack, error) { + _, err := h.getApplication(ctx, in) + if err != nil { + return nil, err + } + + err = h.applications.Delete(in.AppId) + if err != nil { + return nil, err + } + + // Remove this application ID from the cache + h.applicationIdsLock.Lock() + newApplicationIDList := make([]string, 0, len(h.applicationIds)) + for _, id := range h.applicationIds { + if id != in.AppId { + newApplicationIDList = append(newApplicationIDList, id) + } + } + h.applicationIds = newApplicationIDList + h.applicationIdsLock.Unlock() + + return &api.Ack{}, nil +} + +func (h *handlerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + return nil, errors.New("Not Implemented") +} + +func (b *handler) RegisterManager(s *grpc.Server) { + server := &handlerManager{b} + pb.RegisterHandlerManagerServer(s, server) + pb.RegisterApplicationManagerServer(s, server) + pb_lorawan.RegisterDeviceManagerServer(s, server) +} diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go new file mode 100644 index 000000000..f491e185a --- /dev/null +++ b/core/networkserver/manager_server.go @@ -0,0 +1,113 @@ +package networkserver + +import ( + "errors" + + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type networkServerManager struct { + *networkServer +} + +var errf = grpc.Errorf + +func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { + if in.AppId == "" || in.AppEui == nil || in.DevEui == nil { + return nil, errf(codes.InvalidArgument, "AppID, AppEUI and DevEUI are required") + } + claims, err := n.Component.ValidateContext(ctx) + if err != nil { + return nil, err + } + if !claims.CanEditApp(in.AppId) { + return nil, errf(codes.Unauthenticated, "No access to this device") + } + dev, err := n.devices.Get(*in.AppEui, *in.DevEui) + if err != nil { + return nil, err + } + if !claims.CanEditApp(dev.AppID) { + return nil, errf(codes.Unauthenticated, "No access to this device") + } + return dev, nil +} + +func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { + dev, err := n.getDevice(ctx, in) + if err != nil { + return nil, err + } + + return &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: &dev.AppEUI, + DevEui: &dev.DevEUI, + DevAddr: &dev.DevAddr, + NwkSKey: &dev.NwkSKey, + FCntUp: dev.FCntUp, + FCntDown: dev.FCntDown, + DisableFCntCheck: dev.Options.DisableFCntCheck, + Uses32BitFCnt: dev.Options.Uses32BitFCnt, + }, nil +} + +func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*api.Ack, error) { + _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppId: in.AppId, AppEui: in.AppEui, DevEui: in.DevEui}) + if err != nil && err != device.ErrNotFound { + return nil, err + } + + updated := &device.Device{ + AppID: in.AppId, + AppEUI: *in.AppEui, + DevEUI: *in.DevEui, + FCntUp: in.FCntUp, + FCntDown: in.FCntDown, + Options: device.Options{ + DisableFCntCheck: in.DisableFCntCheck, + Uses32BitFCnt: in.Uses32BitFCnt, + }, + } + + if in.NwkSKey != nil && in.DevAddr != nil { + updated.DevAddr = *in.DevAddr + updated.NwkSKey = *in.NwkSKey + } + + err = n.devices.Set(updated) + if err != nil { + return nil, err + } + + return &api.Ack{}, nil +} + +func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*api.Ack, error) { + _, err := n.getDevice(ctx, in) + if err != nil { + return nil, err + } + err = n.devices.Delete(*in.AppEui, *in.DevEui) + if err != nil { + return nil, err + } + return &api.Ack{}, nil +} + +func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + return nil, errors.New("Not Implemented") +} + +// RegisterManager registers this networkserver as a NetworkServerManagerServer (github.com/TheThingsNetwork/ttn/api/networkserver) +func (n *networkServer) RegisterManager(s *grpc.Server) { + server := &networkServerManager{n} + pb.RegisterNetworkServerManagerServer(s, server) + pb_lorawan.RegisterDeviceManagerServer(s, server) +} diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 4edcc430c..4a759e4ef 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -22,6 +22,8 @@ import ( // NetworkServer implements LoRaWAN-specific functionality for TTN type NetworkServer interface { core.ComponentInterface + core.ManagementInterface + UsePrefix(prefixBytes []byte, length int) error HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error) HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) diff --git a/core/router/router.go b/core/router/router.go index 0458af735..e289caf50 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -21,6 +21,8 @@ import ( // Router component type Router interface { core.ComponentInterface + core.ManagementInterface + // Handle a status message from a gateway HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error // Handle an uplink message from a gateway From 3b1dcb1e7d6900b354d0a39ec1014daf16c88172 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 11:52:33 +0200 Subject: [PATCH 1534/2266] Error handling in Broker --- core/broker/uplink.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 702dcf84d..ab3a737f0 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -46,7 +46,8 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { base := duplicates[0] if base.ProtocolMetadata.GetLorawan() == nil { - return errors.New("ttn/broker: Can not handle uplink from non-LoRaWAN device") + err = errors.New("ttn/broker: Can not handle uplink from non-LoRaWAN device") + return err } // LoRaWAN: Unmarshal @@ -106,6 +107,8 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { if device.DisableFCntCheck { // TODO: Add warning to message? + } else if device.FCntUp == 0 { + } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { // Replay attack or FCnt gap too big err = ErrInvalidFCnt @@ -154,10 +157,12 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } if len(announcements) == 0 { - return errors.New("ttn/broker: No Handlers") + err = errors.New("ttn/broker: No Handlers") + return err } if len(announcements) > 1 { - return errors.New("ttn/broker: Can't forward to multiple Handlers") + err = errors.New("ttn/broker: Can't forward to multiple Handlers") + return err } var handler chan<- *pb.DeduplicatedUplinkMessage From 5f6adeac31bc84cccee68d00b2c3f5b80d667e43 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 11:53:49 +0200 Subject: [PATCH 1535/2266] Change Handler-Broker Connection --- core/handler/handler.go | 49 +++++++++++++++-------------------------- 1 file changed, 18 insertions(+), 31 deletions(-) diff --git a/core/handler/handler.go b/core/handler/handler.go index 67d86dc5d..2978f15e9 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -2,12 +2,10 @@ package handler import ( "fmt" - "io" "sync" "time" "google.golang.org/grpc" - "google.golang.org/grpc/codes" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" @@ -134,51 +132,40 @@ func (h *handler) associateBroker() error { ctx := h.GetContext() - upStream, err := h.ttnBroker.Subscribe(ctx, &pb_broker.SubscribeRequest{}) - if err != nil { - return err - } - h.downlink = make(chan *pb_broker.DownlinkMessage) - downStream, err := h.ttnBroker.Publish(ctx) - if err != nil { - return err - } go func() { for { - in, err := upStream.Recv() - if err != nil && (err == io.EOF || - grpc.Code(err) == codes.Canceled || - grpc.Code(err) == codes.Internal || - grpc.Code(err) == codes.Unauthenticated) { - h.Ctx.Fatalf("ttn/handler: Stopping Broker subscribe: %s", err) // TODO: Restart - break - } + upStream, err := h.ttnBroker.Subscribe(ctx, &pb_broker.SubscribeRequest{}) if err != nil { - h.Ctx.Warnf("ttn/handler: Error in Broker subscribe: %s", err) <-time.After(api.Backoff) continue } - go h.HandleUplink(in) + for { + in, err := upStream.Recv() + if err != nil { + h.Ctx.Errorf("ttn/handler: Error in Broker subscribe: %s", err) + break + } + go h.HandleUplink(in) + } } }() go func() { - for downlink := range h.downlink { - err := downStream.Send(downlink) - if err != nil && (err == io.EOF || - grpc.Code(err) == codes.Canceled || - grpc.Code(err) == codes.Internal || - grpc.Code(err) == codes.Unauthenticated) { - h.Ctx.Fatalf("ttn/handler: Stopping Broker publish: %s", err) // TODO: Restart - break - } + for { + downStream, err := h.ttnBroker.Publish(ctx) if err != nil { - h.Ctx.Warnf("ttn/handler: Error in Broker publish: %s", err) <-time.After(api.Backoff) continue } + for downlink := range h.downlink { + err := downStream.Send(downlink) + if err != nil { + h.Ctx.Errorf("ttn/handler: Error in Broker publish: %s", err) + break + } + } } }() From 82fe27182f87ef7a3dbda80b2b9cf63b17bf6a15 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 11:54:12 +0200 Subject: [PATCH 1536/2266] Handler MQTT publish Timeouts --- core/handler/mqtt.go | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index a40ffb702..7f34e5f19 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -1,11 +1,19 @@ package handler import ( + "time" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) +// MQTTTimeout indicates how long we should wait for an MQTT publish +var MQTTTimeout = 2 * time.Second + +// MQTTBufferSize indicates the size for uplink channel buffers +var MQTTBufferSize = 10 + func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) error { h.mqttClient = mqtt.NewClient(h.Ctx, "ttnhdl", username, password, mqttBrokers...) @@ -14,8 +22,8 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e return err } - h.mqttUp = make(chan *mqtt.UplinkMessage) - h.mqttActivation = make(chan *mqtt.Activation) + h.mqttUp = make(chan *mqtt.UplinkMessage, MQTTBufferSize) + h.mqttActivation = make(chan *mqtt.Activation, MQTTBufferSize) token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, msg mqtt.DownlinkMessage) { down := &msg @@ -36,9 +44,12 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e }).Debug("Publish Uplink") token := h.mqttClient.PublishUplink(up.AppEUI, up.DevEUI, *up) go func() { - token.Wait() - if token.Error() != nil { - h.Ctx.WithError(token.Error()).Warn("Could not publish Uplink") + if token.WaitTimeout(MQTTTimeout) { + if token.Error() != nil { + h.Ctx.WithError(token.Error()).Warn("Could not publish Uplink") + } + } else { + h.Ctx.Warn("Uplink publish timeout") } }() } @@ -48,9 +59,12 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e for activation := range h.mqttActivation { token := h.mqttClient.PublishActivation(activation.AppEUI, activation.DevEUI, *activation) go func() { - token.Wait() - if token.Error() != nil { - h.Ctx.WithError(token.Error()).Warn("Could not publish Activation") + if token.WaitTimeout(MQTTTimeout) { + if token.Error() != nil { + h.Ctx.WithError(token.Error()).Warn("Could not publish Activation") + } + } else { + h.Ctx.Warn("Activation publish timeout") } }() } From 1645198c3dcb3b124cbb753a346b1659da32d3aa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 12:09:50 +0200 Subject: [PATCH 1537/2266] Router Manager --- api/router/router.pb.go | 271 ++++++++++++--------------------- api/router/router.proto | 13 +- core/router/activation.go | 2 + core/router/gateway/gateway.go | 3 + core/router/gateway_status.go | 3 + core/router/manager_server.go | 45 ++++++ core/router/uplink.go | 2 + 7 files changed, 153 insertions(+), 186 deletions(-) create mode 100644 core/router/manager_server.go diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 0cb79ddcb..45a860a8b 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -195,9 +195,9 @@ func (m *GatewayStatusRequest) String() string { return proto.Compact func (*GatewayStatusRequest) ProtoMessage() {} func (*GatewayStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{9} } -// message GatewayStatusResponse is the response to the GatewayStatusRequest type GatewayStatusResponse struct { - LastStatus *gateway.Status `protobuf:"bytes,1,opt,name=last_status,json=lastStatus" json:"last_status,omitempty"` + LastSeen int64 `protobuf:"varint,1,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` + Status *gateway.Status `protobuf:"bytes,2,opt,name=status" json:"status,omitempty"` } func (m *GatewayStatusResponse) Reset() { *m = GatewayStatusResponse{} } @@ -205,9 +205,9 @@ func (m *GatewayStatusResponse) String() string { return proto.Compac func (*GatewayStatusResponse) ProtoMessage() {} func (*GatewayStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{10} } -func (m *GatewayStatusResponse) GetLastStatus() *gateway.Status { +func (m *GatewayStatusResponse) GetStatus() *gateway.Status { if m != nil { - return m.LastStatus + return m.Status } return nil } @@ -571,12 +571,6 @@ var _Router_serviceDesc = grpc.ServiceDesc{ // Client API for RouterManager service type RouterManagerClient interface { - // Network operator requests list of Gateways from Router Manager - Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error) - // Gateway owner or network operator registers Gateway with Router Manager - RegisterGateway(ctx context.Context, in *RegisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) - // Gateway owner or network operator unregisters Gateway with Router Manager - UnregisterGateway(ctx context.Context, in *UnregisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) // Gateway owner or network operator requests Gateway status from Router Manager GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) // Network operator requests Router status @@ -591,33 +585,6 @@ func NewRouterManagerClient(cc *grpc.ClientConn) RouterManagerClient { return &routerManagerClient{cc} } -func (c *routerManagerClient) Gateways(ctx context.Context, in *GatewaysRequest, opts ...grpc.CallOption) (*GatewaysResponse, error) { - out := new(GatewaysResponse) - err := grpc.Invoke(ctx, "/router.RouterManager/Gateways", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *routerManagerClient) RegisterGateway(ctx context.Context, in *RegisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) - err := grpc.Invoke(ctx, "/router.RouterManager/RegisterGateway", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *routerManagerClient) UnregisterGateway(ctx context.Context, in *UnregisterGatewayRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) - err := grpc.Invoke(ctx, "/router.RouterManager/UnregisterGateway", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - func (c *routerManagerClient) GatewayStatus(ctx context.Context, in *GatewayStatusRequest, opts ...grpc.CallOption) (*GatewayStatusResponse, error) { out := new(GatewayStatusResponse) err := grpc.Invoke(ctx, "/router.RouterManager/GatewayStatus", in, out, c.cc, opts...) @@ -639,12 +606,6 @@ func (c *routerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, // Server API for RouterManager service type RouterManagerServer interface { - // Network operator requests list of Gateways from Router Manager - Gateways(context.Context, *GatewaysRequest) (*GatewaysResponse, error) - // Gateway owner or network operator registers Gateway with Router Manager - RegisterGateway(context.Context, *RegisterGatewayRequest) (*api.Ack, error) - // Gateway owner or network operator unregisters Gateway with Router Manager - UnregisterGateway(context.Context, *UnregisterGatewayRequest) (*api.Ack, error) // Gateway owner or network operator requests Gateway status from Router Manager GatewayStatus(context.Context, *GatewayStatusRequest) (*GatewayStatusResponse, error) // Network operator requests Router status @@ -655,60 +616,6 @@ func RegisterRouterManagerServer(s *grpc.Server, srv RouterManagerServer) { s.RegisterService(&_RouterManager_serviceDesc, srv) } -func _RouterManager_Gateways_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GatewaysRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterManagerServer).Gateways(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/router.RouterManager/Gateways", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterManagerServer).Gateways(ctx, req.(*GatewaysRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _RouterManager_RegisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RegisterGatewayRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterManagerServer).RegisterGateway(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/router.RouterManager/RegisterGateway", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterManagerServer).RegisterGateway(ctx, req.(*RegisterGatewayRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func _RouterManager_UnregisterGateway_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(UnregisterGatewayRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RouterManagerServer).UnregisterGateway(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/router.RouterManager/UnregisterGateway", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(RouterManagerServer).UnregisterGateway(ctx, req.(*UnregisterGatewayRequest)) - } - return interceptor(ctx, in, info, handler) -} - func _RouterManager_GatewayStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GatewayStatusRequest) if err := dec(in); err != nil { @@ -749,18 +656,6 @@ var _RouterManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "router.RouterManager", HandlerType: (*RouterManagerServer)(nil), Methods: []grpc.MethodDesc{ - { - MethodName: "Gateways", - Handler: _RouterManager_Gateways_Handler, - }, - { - MethodName: "RegisterGateway", - Handler: _RouterManager_RegisterGateway_Handler, - }, - { - MethodName: "UnregisterGateway", - Handler: _RouterManager_UnregisterGateway_Handler, - }, { MethodName: "GatewayStatus", Handler: _RouterManager_GatewayStatus_Handler, @@ -1112,11 +1007,16 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.LastStatus != nil { - data[i] = 0xa + if m.LastSeen != 0 { + data[i] = 0x8 + i++ + i = encodeVarintRouter(data, i, uint64(m.LastSeen)) + } + if m.Status != nil { + data[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.LastStatus.Size())) - n12, err := m.LastStatus.MarshalTo(data[i:]) + i = encodeVarintRouter(data, i, uint64(m.Status.Size())) + n12, err := m.Status.MarshalTo(data[i:]) if err != nil { return 0, err } @@ -1388,8 +1288,11 @@ func (m *GatewayStatusRequest) Size() (n int) { func (m *GatewayStatusResponse) Size() (n int) { var l int _ = l - if m.LastStatus != nil { - l = m.LastStatus.Size() + if m.LastSeen != 0 { + n += 1 + sovRouter(uint64(m.LastSeen)) + } + if m.Status != nil { + l = m.Status.Size() n += 1 + l + sovRouter(uint64(l)) } return n @@ -2462,8 +2365,27 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastSeen", wireType) + } + m.LastSeen = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.LastSeen |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field LastStatus", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Status", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2487,10 +2409,10 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.LastStatus == nil { - m.LastStatus = &gateway.Status{} + if m.Status == nil { + m.Status = &gateway.Status{} } - if err := m.LastStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Status.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2943,60 +2865,59 @@ var ( ) var fileDescriptorRouter = []byte{ - // 876 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x55, 0x4f, 0x4f, 0xeb, 0x46, - 0x10, 0xaf, 0x69, 0x15, 0xc2, 0x24, 0x21, 0xc9, 0x92, 0x80, 0x1b, 0xb5, 0x80, 0x7c, 0x68, 0xe9, - 0x1f, 0x12, 0x48, 0x85, 0x2a, 0x84, 0xfa, 0x27, 0x14, 0x84, 0x90, 0x1a, 0x54, 0x19, 0xb8, 0xf4, - 0x12, 0x6d, 0x9c, 0xc5, 0x58, 0x09, 0xb6, 0xeb, 0x5d, 0x07, 0xf8, 0x26, 0xfd, 0x10, 0xfd, 0x20, - 0x3d, 0x54, 0x55, 0x4f, 0x3d, 0xf4, 0x50, 0x55, 0xed, 0x57, 0xe8, 0xa5, 0xb7, 0x6e, 0xd6, 0xbb, - 0x8e, 0xed, 0x90, 0xf7, 0xe0, 0xbd, 0x27, 0x0e, 0x96, 0x77, 0x67, 0x7e, 0xfb, 0x9b, 0x99, 0x9d, - 0xd9, 0x19, 0xf8, 0xdc, 0x76, 0xd8, 0x75, 0xd8, 0x6f, 0x5a, 0xde, 0x4d, 0xeb, 0xe2, 0x9a, 0x5c, - 0x5c, 0x3b, 0xae, 0x4d, 0xcf, 0x08, 0xbb, 0xf5, 0x82, 0x61, 0x8b, 0x31, 0xb7, 0x85, 0x7d, 0xa7, - 0x15, 0x78, 0x21, 0x23, 0x81, 0xfc, 0x35, 0xfd, 0xc0, 0x63, 0x1e, 0xca, 0x45, 0xbb, 0xc6, 0x76, - 0x82, 0xc0, 0xf6, 0x6c, 0xaf, 0x25, 0xd4, 0xfd, 0xf0, 0x4a, 0xec, 0xc4, 0x46, 0xac, 0xa2, 0x63, - 0x29, 0xf8, 0x5c, 0x7b, 0xfc, 0x93, 0xf0, 0x83, 0xc7, 0xc0, 0x05, 0xd4, 0xf2, 0x46, 0xf1, 0x42, - 0x1e, 0xde, 0x7f, 0xcc, 0x61, 0x1b, 0x33, 0x72, 0x8b, 0xef, 0xd5, 0x3f, 0x3a, 0x6a, 0x20, 0xa8, - 0x9c, 0x87, 0x7d, 0x6a, 0x05, 0x4e, 0x9f, 0x98, 0xe4, 0x87, 0x90, 0x50, 0x66, 0xfc, 0xa4, 0x41, - 0xe9, 0xd2, 0x1f, 0x39, 0xee, 0xb0, 0x4b, 0x28, 0xc5, 0x36, 0x41, 0x3a, 0x2c, 0xfa, 0xf8, 0x7e, - 0xe4, 0xe1, 0x81, 0xae, 0x6d, 0x6a, 0x5b, 0x45, 0x53, 0x6d, 0x51, 0x07, 0xaa, 0xca, 0x99, 0xde, - 0x0d, 0x61, 0x78, 0x80, 0x19, 0xd6, 0x0b, 0x1c, 0x53, 0x68, 0xd7, 0x9a, 0xb1, 0x9b, 0xe6, 0x5d, - 0x57, 0xea, 0xcc, 0x8a, 0x12, 0x2a, 0x09, 0xfa, 0x12, 0x2a, 0xd2, 0xa7, 0x29, 0x43, 0x51, 0x30, - 0xac, 0x34, 0x95, 0xb3, 0x09, 0x82, 0xb2, 0x94, 0x29, 0x81, 0xf1, 0x8b, 0x06, 0xe5, 0x23, 0xef, - 0xd6, 0x7d, 0x9c, 0xc3, 0xdf, 0xc1, 0x6a, 0xec, 0xb0, 0xe5, 0xb9, 0x57, 0x8e, 0x1d, 0x06, 0x98, - 0x39, 0x9e, 0x2b, 0xbd, 0x7e, 0x77, 0xea, 0xf5, 0xc5, 0xdd, 0x37, 0x49, 0x80, 0x59, 0x57, 0x9a, - 0x94, 0x18, 0x75, 0xa1, 0xae, 0xfc, 0x4f, 0x13, 0x46, 0x41, 0xe8, 0x71, 0x10, 0x59, 0xbe, 0x9a, - 0x54, 0xa4, 0xa4, 0xc6, 0xef, 0x0b, 0xb0, 0x76, 0x44, 0xc6, 0x8e, 0x45, 0x3a, 0x16, 0x73, 0xc6, - 0x11, 0x34, 0xca, 0xcc, 0x0b, 0xc2, 0x3a, 0x83, 0xc5, 0x01, 0x19, 0xf7, 0x48, 0xe8, 0x88, 0x38, - 0x8a, 0x87, 0x7b, 0x7f, 0xfc, 0xb9, 0xb1, 0xfb, 0xb2, 0xba, 0xb0, 0xbc, 0x80, 0xb4, 0xd8, 0xbd, - 0x4f, 0x68, 0x93, 0x9b, 0x3c, 0xbe, 0x3c, 0x35, 0x73, 0x9c, 0xe5, 0x38, 0x74, 0x26, 0x7c, 0xd8, - 0xf7, 0x05, 0x5f, 0xf1, 0x95, 0xf8, 0x3a, 0xbe, 0x2f, 0xf8, 0x38, 0xcb, 0x84, 0xef, 0xc1, 0x3a, - 0xa9, 0xbf, 0x76, 0x9d, 0xac, 0x3e, 0xa1, 0x4e, 0x1a, 0xa0, 0xcf, 0xde, 0x2b, 0xf5, 0x3d, 0x97, - 0x12, 0xa3, 0x0a, 0xe5, 0x93, 0x08, 0x4e, 0xd5, 0x2b, 0x70, 0xa1, 0x32, 0x15, 0x45, 0x30, 0xf4, - 0x3d, 0x14, 0x94, 0x0b, 0xce, 0x80, 0xf2, 0x1c, 0xbc, 0xcd, 0x6f, 0x66, 0x9f, 0xdf, 0xcc, 0xde, - 0x13, 0x6e, 0x46, 0xb2, 0x4e, 0x6e, 0x07, 0x24, 0xdb, 0xe9, 0x80, 0x1a, 0x0c, 0x56, 0x4d, 0x62, - 0x3b, 0x94, 0xf7, 0x1a, 0x89, 0x50, 0x59, 0x4f, 0x58, 0x9d, 0xe4, 0x43, 0x64, 0xfe, 0x4d, 0x58, - 0xe5, 0x79, 0x31, 0xc6, 0xa0, 0x5f, 0xba, 0xc1, 0xf3, 0xdb, 0x0d, 0xa0, 0x26, 0x35, 0xe7, 0x0c, - 0xb3, 0x90, 0x3e, 0x87, 0xcd, 0x53, 0xa8, 0x67, 0x6c, 0xca, 0xb4, 0xee, 0x40, 0x61, 0x84, 0x29, - 0xeb, 0x51, 0x21, 0x16, 0x46, 0x0b, 0xed, 0x72, 0x5c, 0x54, 0x12, 0x0d, 0x13, 0x4c, 0xb4, 0x36, - 0xca, 0x50, 0x4a, 0xf9, 0x6d, 0xfc, 0xb7, 0x00, 0xb9, 0x48, 0x82, 0x76, 0x61, 0x59, 0x85, 0x90, - 0x22, 0x84, 0xe6, 0xa4, 0xdd, 0x9b, 0x5c, 0x45, 0xcd, 0x92, 0x9d, 0x74, 0x04, 0x7d, 0x08, 0x65, - 0x3c, 0x29, 0x4a, 0xd2, 0x93, 0x72, 0xaa, 0xbf, 0xc3, 0xcf, 0x94, 0xcc, 0xe5, 0x48, 0xac, 0x0a, - 0x11, 0x19, 0x90, 0x0b, 0x45, 0x67, 0x96, 0xdd, 0x2a, 0xc9, 0x29, 0x35, 0xe8, 0x03, 0xc8, 0x0f, - 0x64, 0x3b, 0x94, 0x2f, 0x2c, 0x89, 0x8a, 0x75, 0xe8, 0x53, 0x28, 0xe0, 0xf8, 0x25, 0x50, 0x7d, - 0x63, 0x06, 0x9a, 0x54, 0xa3, 0x2f, 0xa0, 0x96, 0xd8, 0xf6, 0xb0, 0x65, 0x11, 0x9f, 0x91, 0x81, - 0xbe, 0x39, 0x73, 0x6c, 0x25, 0x81, 0xeb, 0x48, 0x18, 0xda, 0x06, 0xc4, 0x9b, 0xa3, 0x4b, 0x2c, - 0xbe, 0x99, 0x06, 0xf9, 0x91, 0x08, 0xb2, 0x1a, 0x6b, 0xe2, 0x38, 0x3f, 0x81, 0xa9, 0xb0, 0xd7, - 0x0f, 0xbc, 0x21, 0x09, 0xa8, 0xfe, 0xb1, 0x40, 0x57, 0x62, 0xc5, 0x61, 0x24, 0x6f, 0xff, 0xab, - 0x41, 0xce, 0x14, 0x43, 0x9a, 0xc7, 0x54, 0x4a, 0xa5, 0x18, 0x65, 0xb3, 0xd8, 0xc8, 0x0b, 0x4f, - 0x3b, 0xd6, 0x70, 0x4b, 0xe3, 0x56, 0x72, 0xd1, 0x9c, 0x43, 0xf5, 0xa6, 0x9c, 0xf9, 0xa9, 0xb9, - 0x97, 0x02, 0x7f, 0x0d, 0x4b, 0xf1, 0xa4, 0x44, 0xba, 0xc2, 0x67, 0x87, 0x67, 0x63, 0x4d, 0x69, - 0x32, 0x23, 0x69, 0x47, 0xe3, 0x83, 0x22, 0x2f, 0x5b, 0x0f, 0x41, 0x1b, 0x31, 0xec, 0xe1, 0x56, - 0xdf, 0xd8, 0x9c, 0x0f, 0x88, 0xaa, 0xb6, 0xfd, 0xeb, 0x02, 0x94, 0xa2, 0xb0, 0xbb, 0xd8, 0xe5, - 0x16, 0x02, 0x9e, 0xa3, 0x7c, 0x7c, 0x83, 0xb1, 0x1f, 0x99, 0xbe, 0xd6, 0xd0, 0x67, 0x15, 0xf2, - 0x19, 0x1c, 0x40, 0x39, 0xd3, 0x81, 0xd0, 0xba, 0x02, 0x3f, 0xdc, 0x9a, 0xa6, 0x17, 0x84, 0xbe, - 0x82, 0xea, 0x4c, 0x23, 0x41, 0x71, 0x10, 0xf3, 0x7a, 0x4c, 0x82, 0xe0, 0xdb, 0x6c, 0xea, 0xde, - 0xcb, 0x38, 0x9a, 0x7a, 0x70, 0x8d, 0xf7, 0xe7, 0x68, 0x65, 0x2c, 0x6d, 0x58, 0x3a, 0x21, 0xf2, - 0xb5, 0x4e, 0xb3, 0x9b, 0xa6, 0x58, 0x4e, 0x8b, 0x0f, 0x2b, 0x3f, 0xff, 0xbd, 0xae, 0xfd, 0xc6, - 0xbf, 0xbf, 0xf8, 0xf7, 0xe3, 0x3f, 0xeb, 0x6f, 0xf5, 0x73, 0x62, 0x08, 0x7d, 0xf6, 0x7f, 0x00, - 0x00, 0x00, 0xff, 0xff, 0x6d, 0x1f, 0x93, 0x28, 0x3d, 0x0a, 0x00, 0x00, + // 853 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x4f, 0x4f, 0x1b, 0x47, + 0x14, 0xef, 0x42, 0xb5, 0x98, 0x67, 0x1b, 0x9b, 0xc1, 0x86, 0xad, 0xdb, 0x02, 0xda, 0x43, 0x4b, + 0xff, 0x60, 0x17, 0x57, 0xa8, 0x42, 0x55, 0xab, 0x9a, 0x82, 0x10, 0x52, 0x8d, 0xaa, 0x05, 0x2e, + 0x95, 0x2a, 0x6b, 0xbc, 0x1e, 0x96, 0x95, 0xcd, 0xee, 0x76, 0x67, 0xd6, 0xc0, 0xc7, 0xe8, 0x2d, + 0x1f, 0x22, 0x1f, 0x24, 0x87, 0x1c, 0x72, 0xca, 0x21, 0x87, 0x28, 0x4a, 0xbe, 0x42, 0x2e, 0xb9, + 0x65, 0x3c, 0x3b, 0xb3, 0xde, 0x35, 0x90, 0x40, 0x12, 0x71, 0x18, 0x79, 0xe7, 0xbd, 0xdf, 0xfc, + 0xde, 0x7b, 0xf3, 0xde, 0xbc, 0x67, 0xf8, 0xc5, 0x71, 0xd9, 0x69, 0xd4, 0xad, 0xdb, 0xfe, 0x59, + 0xe3, 0xe8, 0x94, 0x1c, 0x9d, 0xba, 0x9e, 0x43, 0x0f, 0x08, 0x3b, 0xf7, 0xc3, 0x7e, 0x83, 0x31, + 0xaf, 0x81, 0x03, 0xb7, 0x11, 0xfa, 0x11, 0x23, 0xa1, 0xfc, 0xa9, 0x07, 0xa1, 0xcf, 0x7c, 0xa4, + 0xc7, 0xbb, 0xda, 0x7a, 0x8a, 0xc0, 0xf1, 0x1d, 0xbf, 0x21, 0xd4, 0xdd, 0xe8, 0x44, 0xec, 0xc4, + 0x46, 0x7c, 0xc5, 0xc7, 0x32, 0xf0, 0x1b, 0xed, 0xf1, 0x25, 0xe1, 0xbf, 0xde, 0x06, 0x2e, 0xa0, + 0xb6, 0x3f, 0x48, 0x3e, 0xe4, 0xe1, 0xad, 0xdb, 0x1c, 0x76, 0x30, 0x23, 0xe7, 0xf8, 0x52, 0xfd, + 0xc6, 0x47, 0x4d, 0x04, 0xe5, 0xc3, 0xa8, 0x4b, 0xed, 0xd0, 0xed, 0x12, 0x8b, 0xfc, 0x17, 0x11, + 0xca, 0xcc, 0x87, 0x1a, 0x14, 0x8f, 0x83, 0x81, 0xeb, 0xf5, 0xdb, 0x84, 0x52, 0xec, 0x10, 0x64, + 0xc0, 0x4c, 0x80, 0x2f, 0x07, 0x3e, 0xee, 0x19, 0xda, 0xaa, 0xb6, 0x56, 0xb0, 0xd4, 0x16, 0xb5, + 0x60, 0x5e, 0x39, 0xd3, 0x39, 0x23, 0x0c, 0xf7, 0x30, 0xc3, 0x46, 0x9e, 0x63, 0xf2, 0xcd, 0x4a, + 0x3d, 0x71, 0xd3, 0xba, 0x68, 0x4b, 0x9d, 0x55, 0x56, 0x42, 0x25, 0x41, 0xbf, 0x43, 0x59, 0xfa, + 0x34, 0x66, 0x28, 0x08, 0x86, 0x85, 0xba, 0x72, 0x36, 0x45, 0x50, 0x92, 0x32, 0x25, 0x30, 0x1f, + 0x6b, 0x50, 0xda, 0xf1, 0xcf, 0xbd, 0xdb, 0x39, 0xfc, 0x37, 0x2c, 0x26, 0x0e, 0xdb, 0xbe, 0x77, + 0xe2, 0x3a, 0x51, 0x88, 0x99, 0xeb, 0x7b, 0xd2, 0xeb, 0x2f, 0xc6, 0x5e, 0x1f, 0x5d, 0xfc, 0x99, + 0x06, 0x58, 0x55, 0xa5, 0xc9, 0x88, 0x51, 0x1b, 0xaa, 0xca, 0xff, 0x2c, 0x61, 0x1c, 0x84, 0x91, + 0x04, 0x31, 0xc9, 0x57, 0x91, 0x8a, 0x8c, 0xd4, 0x7c, 0x3a, 0x05, 0x4b, 0x3b, 0x64, 0xe8, 0xda, + 0xa4, 0x65, 0x33, 0x77, 0x18, 0x43, 0xe3, 0xcc, 0xbc, 0x23, 0xac, 0x03, 0x98, 0xe9, 0x91, 0x61, + 0x87, 0x44, 0xae, 0x88, 0xa3, 0xb0, 0xbd, 0xf9, 0xec, 0xf9, 0xca, 0xc6, 0xfb, 0xea, 0xc2, 0xf6, + 0x43, 0xd2, 0x60, 0x97, 0x01, 0xa1, 0x75, 0x6e, 0x72, 0xf7, 0x78, 0xdf, 0xd2, 0x39, 0xcb, 0x6e, + 0xe4, 0x8e, 0xf8, 0x70, 0x10, 0x08, 0xbe, 0xc2, 0x07, 0xf1, 0xb5, 0x82, 0x40, 0xf0, 0x71, 0x96, + 0x11, 0xdf, 0xb5, 0x75, 0x52, 0xfd, 0xe8, 0x3a, 0x59, 0xbc, 0x43, 0x9d, 0xd4, 0xc0, 0xb8, 0x7a, + 0xaf, 0x34, 0xf0, 0x3d, 0x4a, 0xcc, 0x79, 0x28, 0xed, 0xc5, 0x70, 0xaa, 0x5e, 0x81, 0x07, 0xe5, + 0xb1, 0x28, 0x86, 0xa1, 0x7f, 0x20, 0xaf, 0x5c, 0x70, 0x7b, 0x94, 0xe7, 0x60, 0x9a, 0xdf, 0xcc, + 0x16, 0xbf, 0x99, 0xcd, 0x3b, 0xdc, 0x8c, 0x64, 0x1d, 0xdd, 0x0e, 0x48, 0xb6, 0xfd, 0x1e, 0x35, + 0x19, 0x2c, 0x5a, 0xc4, 0x71, 0x29, 0xef, 0x35, 0x12, 0xa1, 0xb2, 0x9e, 0xb2, 0x3a, 0xca, 0x87, + 0xc8, 0xfc, 0xa7, 0xb0, 0xca, 0xf3, 0x62, 0x0e, 0xc1, 0x38, 0xf6, 0xc2, 0xfb, 0xb7, 0x1b, 0x42, + 0x45, 0x6a, 0x0e, 0x19, 0x66, 0x11, 0xbd, 0x0f, 0x9b, 0xff, 0x42, 0x75, 0xc2, 0xa6, 0x4c, 0xeb, + 0x97, 0x30, 0x3b, 0xc0, 0x94, 0x75, 0x28, 0x21, 0x9e, 0x30, 0x39, 0x6d, 0xe5, 0x46, 0x82, 0x43, + 0xbe, 0x47, 0xdf, 0x82, 0x4e, 0x05, 0xdc, 0x98, 0x12, 0xc5, 0x56, 0x4a, 0x8a, 0x4d, 0xb2, 0x48, + 0xb5, 0x59, 0x82, 0x62, 0x26, 0x16, 0xf3, 0xcd, 0x14, 0xe8, 0xb1, 0x04, 0x6d, 0xc0, 0x9c, 0x0a, + 0x4b, 0x92, 0x69, 0x82, 0x0c, 0xea, 0xa3, 0x11, 0x60, 0x71, 0x15, 0xb5, 0x8a, 0x4e, 0xda, 0x39, + 0x6e, 0xb7, 0x84, 0x47, 0x85, 0x4a, 0x3a, 0x52, 0x4e, 0x8d, 0xcf, 0xf9, 0x99, 0xa2, 0x35, 0x17, + 0x8b, 0x55, 0x71, 0x22, 0x13, 0xf4, 0x48, 0x74, 0x6b, 0xd9, 0xc1, 0xd2, 0x9c, 0x52, 0x83, 0xbe, + 0x81, 0x5c, 0x4f, 0xb6, 0x48, 0xf9, 0xea, 0xd2, 0xa8, 0x44, 0x87, 0x7e, 0x84, 0x3c, 0x4e, 0x5e, + 0x07, 0x35, 0x56, 0xae, 0x40, 0xd3, 0x6a, 0xf4, 0x1b, 0x54, 0x52, 0xdb, 0x0e, 0xb6, 0x6d, 0x12, + 0x30, 0xd2, 0x33, 0x56, 0xaf, 0x1c, 0x5b, 0x48, 0xe1, 0x5a, 0x12, 0x86, 0xd6, 0x01, 0xf1, 0x86, + 0xe9, 0x11, 0x9b, 0x6f, 0xc6, 0x41, 0x7e, 0x27, 0x82, 0x9c, 0x4f, 0x34, 0x49, 0x9c, 0x3f, 0xc0, + 0x58, 0xd8, 0xe9, 0x86, 0x7e, 0x9f, 0x84, 0xd4, 0xf8, 0x5e, 0xa0, 0xcb, 0x89, 0x62, 0x3b, 0x96, + 0x37, 0x5f, 0x6b, 0xa0, 0x5b, 0x62, 0x70, 0xf3, 0x98, 0x8a, 0x99, 0xb4, 0xa3, 0xc9, 0x0c, 0xd6, + 0x72, 0xc2, 0xd3, 0x96, 0xdd, 0x5f, 0xd3, 0xb8, 0x15, 0x3d, 0x9e, 0x7d, 0xa8, 0x5a, 0x97, 0xff, + 0x03, 0x32, 0xb3, 0x30, 0x03, 0xfe, 0x03, 0x66, 0x93, 0xe9, 0x89, 0x0c, 0x85, 0x9f, 0x1c, 0xa8, + 0xb5, 0x25, 0xa5, 0x99, 0x18, 0x53, 0x3f, 0x69, 0x7c, 0x78, 0xe4, 0x64, 0x3b, 0x22, 0x68, 0x25, + 0x81, 0x5d, 0xdf, 0xfe, 0x6b, 0xab, 0x37, 0x03, 0xe2, 0x4a, 0x6e, 0xfe, 0xcf, 0x47, 0x77, 0x1c, + 0x76, 0x1b, 0x7b, 0xdc, 0x42, 0x88, 0xfe, 0x9a, 0x8c, 0xfe, 0x2b, 0x45, 0x72, 0xdd, 0xfb, 0xab, + 0x7d, 0x7d, 0x83, 0x56, 0xbe, 0x94, 0x26, 0xcc, 0xee, 0x11, 0x26, 0x99, 0x92, 0x0b, 0xca, 0x52, + 0xcc, 0x65, 0xc5, 0xdb, 0xe5, 0x47, 0x2f, 0x97, 0xb5, 0x27, 0x7c, 0xbd, 0xe0, 0xeb, 0xc1, 0xab, + 0xe5, 0xcf, 0xba, 0xba, 0xe8, 0xed, 0x3f, 0xbf, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x9b, 0xb9, 0x48, + 0xd4, 0x94, 0x09, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index ae3c66f86..d56133f01 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -79,9 +79,9 @@ message GatewayStatusRequest { bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; } -// message GatewayStatusResponse is the response to the GatewayStatusRequest message GatewayStatusResponse { - gateway.Status last_status = 1; + int64 last_seen = 1; + gateway.Status status = 2; } // message StatusRequest is used to request the status of this Router @@ -110,15 +110,6 @@ message Status { // The RouterManager service provides configuration and monitoring functionality service RouterManager { - // Network operator requests list of Gateways from Router Manager - rpc Gateways(GatewaysRequest) returns (GatewaysResponse); - - // Gateway owner or network operator registers Gateway with Router Manager - rpc RegisterGateway(RegisterGatewayRequest) returns (api.Ack); - - // Gateway owner or network operator unregisters Gateway with Router Manager - rpc UnregisterGateway(UnregisterGatewayRequest) returns (api.Ack); - // Gateway owner or network operator requests Gateway status from Router Manager rpc GatewayStatus(GatewayStatusRequest) returns (GatewayStatusResponse); diff --git a/core/router/activation.go b/core/router/activation.go index 5de5bcbdc..9897a93b7 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -3,6 +3,7 @@ package router import ( "errors" "sync" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" @@ -26,6 +27,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De }() gateway := r.getGateway(gatewayEUI) + gateway.LastSeen = time.Now() uplink := &pb.UplinkMessage{ Payload: activation.Payload, diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index f3533a6d5..6a4e48dac 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -1,6 +1,8 @@ package gateway import ( + "time" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) @@ -21,4 +23,5 @@ type Gateway struct { Status StatusStore Utilization Utilization Schedule Schedule + LastSeen time.Time } diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index d9caf879b..83a3681c4 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -1,6 +1,8 @@ package router import ( + "time" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -15,6 +17,7 @@ func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gat }() gateway := r.getGateway(gatewayEUI) + gateway.LastSeen = time.Now() err = gateway.Status.Update(status) if err != nil { return err diff --git a/core/router/manager_server.go b/core/router/manager_server.go new file mode 100644 index 000000000..5d0efd87c --- /dev/null +++ b/core/router/manager_server.go @@ -0,0 +1,45 @@ +package router + +import ( + "errors" + + pb "github.com/TheThingsNetwork/ttn/api/router" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +type routerManager struct { + *router +} + +var errf = grpc.Errorf + +func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusRequest) (*pb.GatewayStatusResponse, error) { + if in.GatewayEui == nil { + return nil, errf(codes.InvalidArgument, "GatewayEUI is required") + } + _, err := r.ValidateContext(ctx) + if err != nil { + return nil, errf(codes.Unauthenticated, "No access") + } + gtw := r.getGateway(*in.GatewayEui) + status, err := gtw.Status.Get() + if err != nil { + return nil, err + } + return &pb.GatewayStatusResponse{ + LastSeen: gtw.LastSeen.UnixNano(), + Status: status, + }, nil +} + +func (r *routerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { + return nil, errors.New("Not Implemented") +} + +// RegisterManager registers this router as a RouterManagerServer (github.com/TheThingsNetwork/ttn/api/router) +func (r *router) RegisterManager(s *grpc.Server) { + server := &routerManager{r} + pb.RegisterRouterManagerServer(s, server) +} diff --git a/core/router/uplink.go b/core/router/uplink.go index 260380d55..521f660f3 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -2,6 +2,7 @@ package router import ( "errors" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/router" @@ -56,6 +57,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess ctx = ctx.WithField("DevAddr", devAddr) gateway := r.getGateway(gatewayEUI) + gateway.LastSeen = time.Now() gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) gateway.Utilization.AddRx(uplink) From b8db54cbc3c51a1c7a94fb547be7fb4aca00bc3a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 30 Jun 2016 12:56:15 +0200 Subject: [PATCH 1538/2266] Bugfix/payload function timeout (#190) * Add helper to run js code with a timeout. * Use the RunUnsafeCode instead of vm.Run for the payload functions. * Add test for payload function timeout. * Reduce timeout of payload functions to 1s * do not console.log in test * Vendored jwt-go@v2.7.0 --- .gitmodules | 3 ++ core/adapters/fields/functions.go | 39 ++++++++++++++++++++++++-- core/adapters/fields/functions_test.go | 23 +++++++++++++++ vendor/github.com/dgrijalva/jwt-go | 1 + 4 files changed, 63 insertions(+), 3 deletions(-) create mode 160000 vendor/github.com/dgrijalva/jwt-go diff --git a/.gitmodules b/.gitmodules index 9b3eea699..cb64b1d47 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "vendor/google.golang.org/grpc"] path = vendor/google.golang.org/grpc url = https://github.com/grpc/grpc-go.git +[submodule "vendor/github.com/dgrijalva/jwt-go"] + path = vendor/github.com/dgrijalva/jwt-go + url = https://github.com/dgrijalva/jwt-go.git diff --git a/core/adapters/fields/functions.go b/core/adapters/fields/functions.go index 626448c40..1fc21c655 100644 --- a/core/adapters/fields/functions.go +++ b/core/adapters/fields/functions.go @@ -6,6 +6,7 @@ package fields import ( "errors" "fmt" + "time" "github.com/robertkrimen/otto" ) @@ -23,6 +24,9 @@ type Functions struct { Validator string } +// timeOut is the maximum allowed time a payload function is allowed to run +var timeOut = time.Second + // Decode decodes the payload using the Decoder function into a map func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { if f.Decoder == "" { @@ -31,7 +35,7 @@ func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { vm := otto.New() vm.Set("payload", payload) - value, err := vm.Run(fmt.Sprintf("(%s)(payload)", f.Decoder)) + value, err := RunUnsafeCode(vm, fmt.Sprintf("(%s)(payload)", f.Decoder), timeOut) if err != nil { return nil, err } @@ -54,7 +58,7 @@ func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{} vm := otto.New() vm.Set("data", data) - value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Converter)) + value, err := RunUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Converter), timeOut) if err != nil { return nil, err } @@ -76,7 +80,7 @@ func (f *Functions) Validate(data map[string]interface{}) (bool, error) { vm := otto.New() vm.Set("data", data) - value, err := vm.Run(fmt.Sprintf("(%s)(data)", f.Validator)) + value, err := RunUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Validator), timeOut) if err != nil { return false, err } @@ -103,3 +107,32 @@ func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error valid, err := f.Validate(converted) return converted, valid, err } + +var timeOutExceeded = errors.New("Code has been running to long") + +func RunUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value otto.Value, err error) { + start := time.Now() + defer func() { + duration := time.Since(start) + if caught := recover(); caught != nil { + if caught == timeOutExceeded { + value = otto.Value{} + err = fmt.Errorf("Interrupted javascript execution after %v", duration) + return + } + // if this is not the our timeout interrupt, raise the panic again + // so someone else can handle it + panic(caught) + } + }() + + vm.Interrupt = make(chan func(), 1) + + go func() { + time.Sleep(timeOut) + vm.Interrupt <- func() { + panic(timeOutExceeded) + } + }() + return vm.Run(code) +} diff --git a/core/adapters/fields/functions_test.go b/core/adapters/fields/functions_test.go index f94ba5ee5..9c30e0e50 100644 --- a/core/adapters/fields/functions_test.go +++ b/core/adapters/fields/functions_test.go @@ -4,7 +4,9 @@ package fields import ( + "errors" "testing" + "time" . "github.com/smartystreets/assertions" ) @@ -152,3 +154,24 @@ func TestProcessInvalidFunction(t *testing.T) { _, _, err = functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) } + +func TestTimeoutExceeded(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode.") + } + + a := New(t) + start := time.Now() + functions := &Functions{ + Decoder: `function(payload){ while (true) { } }`, + } + + go func() { + time.Sleep(4 * time.Second) + panic(errors.New("Payload function was not interrupted")) + }() + + _, _, err := functions.Process([]byte{0}) + a.So(time.Since(start), ShouldAlmostEqual, time.Second, 0.5e9) + a.So(err, ShouldNotBeNil) +} diff --git a/vendor/github.com/dgrijalva/jwt-go b/vendor/github.com/dgrijalva/jwt-go new file mode 160000 index 000000000..268038b36 --- /dev/null +++ b/vendor/github.com/dgrijalva/jwt-go @@ -0,0 +1 @@ +Subproject commit 268038b363c7a8d7306b8e35bf77a1fde4b0c402 From 1f41555f00b86771b53fb489798519fbfb49fe13 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 1 Jul 2016 17:44:07 +0200 Subject: [PATCH 1539/2266] Bugfix/payload fns array panic (#192) * reproduce bug when arrays are returned from payload functions * fix bug where payload functions returning arrays would cause panics * Add test with array for validator as well, just to be pedantic --- core/adapters/fields/functions.go | 13 +++++++++++-- core/adapters/fields/functions_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/core/adapters/fields/functions.go b/core/adapters/fields/functions.go index 1fc21c655..bc68c2570 100644 --- a/core/adapters/fields/functions.go +++ b/core/adapters/fields/functions.go @@ -45,7 +45,11 @@ func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { } v, _ := value.Export() - return v.(map[string]interface{}), nil + m, ok := v.(map[string]interface{}) + if !ok { + return nil, errors.New("Decoder does not return an object") + } + return m, nil } // Convert converts the values in the specified map to a another map using the @@ -68,7 +72,12 @@ func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{} } v, _ := value.Export() - return v.(map[string]interface{}), nil + m, ok := v.(map[string]interface{}) + if !ok { + return nil, errors.New("Decoder does not return an object") + } + + return m, nil } // Validate validates the values in the specified map using the Validator diff --git a/core/adapters/fields/functions_test.go b/core/adapters/fields/functions_test.go index 9c30e0e50..1f5935fc1 100644 --- a/core/adapters/fields/functions_test.go +++ b/core/adapters/fields/functions_test.go @@ -153,6 +153,32 @@ func TestProcessInvalidFunction(t *testing.T) { } _, _, err = functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) + + // Invalid Object (Arrays are Objects too, but don't jive well with + // map[string]interface{}) + functions = &Functions{ + Decoder: `function(payload) { return [1] }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Object (Arrays are Objects too, but don't jive well with + // map[string]interface{}) + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Converter: `function(payload) { return [1] }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) + + // Invalid Object (Arrays are Objects too), this should work error because + // we're expecting a Boolean + functions = &Functions{ + Decoder: `function(payload) { return { temperature: payload[0] } }`, + Validator: `function(payload) { return [1] }`, + } + _, _, err = functions.Process([]byte{40, 110}) + a.So(err, ShouldNotBeNil) } func TestTimeoutExceeded(t *testing.T) { From 20c99dbca3901a984fe68e8be30a3cc359d64804 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 12:31:43 +0200 Subject: [PATCH 1540/2266] =?UTF-8?q?MIT=20License=20ALL=20THE=20FILES=20?= =?UTF-8?q?=F0=9F=92=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api.go | 3 +++ api/api.proto | 5 ++++- api/broker/broker.proto | 3 +++ api/discovery/announcement.go | 3 +++ api/discovery/announcement_test.go | 3 +++ api/discovery/discovery.proto | 3 +++ api/gateway/gateway.proto | 3 +++ api/gateway/status_message.go | 3 +++ api/gateway/status_message_test.go | 3 +++ api/handler/handler.proto | 3 +++ api/networkserver/networkserver.proto | 3 +++ api/noc/noc.proto | 3 +++ api/protocol/lorawan/device.proto | 5 ++++- api/protocol/lorawan/lorawan.proto | 3 +++ api/protocol/protocol.proto | 3 +++ api/router/router.proto | 3 +++ cmd/root_test.go | 3 +++ core/broker/activation.go | 3 +++ core/broker/activation_test.go | 3 +++ core/broker/broker.go | 3 +++ core/broker/broker_test.go | 3 +++ core/broker/deduplicator.go | 3 +++ core/broker/deduplicator_test.go | 3 +++ core/broker/downlink.go | 3 +++ core/broker/downlink_test.go | 3 +++ core/broker/manager_server.go | 3 +++ core/broker/server.go | 3 +++ core/broker/server_test.go | 3 +++ core/broker/uplink.go | 3 +++ core/broker/uplink_test.go | 3 +++ core/component.go | 3 +++ core/discovery/broker_discovery.go | 3 +++ core/discovery/broker_discovery_test.go | 3 +++ core/discovery/discovery.go | 3 +++ core/discovery/discovery_integration_test.go | 3 +++ core/discovery/discovery_test.go | 3 +++ core/discovery/handler_discovery.go | 3 +++ core/discovery/handler_discovery_test.go | 3 +++ core/discovery/server.go | 3 +++ core/discovery/server_test.go | 3 +++ core/fcnt/fcnt.go | 3 +++ core/fcnt/fcnt_test.go | 3 +++ core/handler/activation.go | 3 +++ core/handler/activation_test.go | 3 +++ core/handler/application/application.go | 3 +++ core/handler/application/application_test.go | 3 +++ core/handler/application/store.go | 3 +++ core/handler/application/store_test.go | 3 +++ core/handler/convert_lorawan.go | 3 +++ core/handler/convert_lorawan_test.go | 3 +++ core/handler/convert_metadata.go | 3 +++ core/handler/convert_metadata_test.go | 3 +++ core/handler/device/device.go | 3 +++ core/handler/device/device_test.go | 3 +++ core/handler/device/store.go | 3 +++ core/handler/device/store_test.go | 3 +++ core/handler/downlink.go | 3 +++ core/handler/downlink_test.go | 3 +++ core/handler/handler.go | 3 +++ core/handler/handler_test.go | 3 +++ core/handler/manager_server.go | 3 +++ core/handler/mqtt.go | 3 +++ core/handler/mqtt_test.go | 3 +++ core/handler/server.go | 3 +++ core/handler/server_test.go | 3 +++ core/handler/types.go | 3 +++ core/handler/uplink.go | 3 +++ core/handler/uplink_test.go | 3 +++ core/networkserver/device/device.go | 3 +++ core/networkserver/device/device_test.go | 3 +++ core/networkserver/device/store.go | 3 +++ core/networkserver/device/store_test.go | 3 +++ core/networkserver/device/utilization.go | 3 +++ core/networkserver/manager_server.go | 3 +++ core/networkserver/networkserver.go | 3 +++ core/networkserver/networkserver_test.go | 3 +++ core/networkserver/server.go | 3 +++ core/networkserver/server_test.go | 3 +++ core/protos/collector.proto | 3 +++ core/router/activation.go | 3 +++ core/router/activation_test.go | 3 +++ core/router/downlink.go | 3 +++ core/router/downlink_test.go | 3 +++ core/router/gateway/gateway.go | 3 +++ core/router/gateway/gateway_test.go | 3 +++ core/router/gateway/schedule.go | 3 +++ core/router/gateway/schedule_test.go | 3 +++ core/router/gateway/status.go | 3 +++ core/router/gateway/status_test.go | 3 +++ core/router/gateway/utilization.go | 3 +++ core/router/gateway/utilization_test.go | 3 +++ core/router/gateway_status.go | 3 +++ core/router/gateway_status_test.go | 3 +++ core/router/manager_server.go | 3 +++ core/router/router.go | 3 +++ core/router/router_test.go | 3 +++ core/router/server.go | 3 +++ core/router/server_test.go | 3 +++ core/router/uplink.go | 3 +++ core/router/uplink_test.go | 3 +++ core/storage/redis.go | 3 +++ core/storage/redis_test.go | 3 +++ core/types/data_rate.go | 3 +++ core/types/data_rate_test.go | 3 +++ core/types/dev_addr.go | 3 +++ core/types/dev_addr_test.go | 3 +++ core/types/device_type.go | 3 +++ core/types/device_type_test.go | 3 +++ core/types/eui.go | 3 +++ core/types/eui_test.go | 3 +++ core/types/keys.go | 3 +++ core/types/keys_test.go | 3 +++ core/types/parse_hex.go | 3 +++ mqtt/types.go | 3 +++ utils/cli/handler/cli.go | 3 +++ utils/stats/registry.go | 3 +++ utils/stats/string.go | 3 +++ utils/toa/toa.go | 3 +++ utils/toa/toa_test.go | 3 +++ utils/tokenkey/tokenkey.go | 3 +++ 120 files changed, 362 insertions(+), 2 deletions(-) diff --git a/api/api.go b/api/api.go index d2f0ac59f..f52d101ec 100644 --- a/api/api.go +++ b/api/api.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package api import ( diff --git a/api/api.proto b/api/api.proto index a77d8ba6e..45d6bfdc7 100644 --- a/api/api.proto +++ b/api/api.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; package api; @@ -24,5 +27,5 @@ message Rates { } message ServerMetadata { - + } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 85e51dcd4..c7c011dd9 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go index 18500f967..1b7ce527c 100644 --- a/api/discovery/announcement.go +++ b/api/discovery/announcement.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/api/discovery/announcement_test.go b/api/discovery/announcement_test.go index fd5ebf9c9..3171a0888 100644 --- a/api/discovery/announcement_test.go +++ b/api/discovery/announcement_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 7d2d2f5df..987663355 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 7ada22ee7..f6ed0c9c5 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go index 48980b14e..51c01bbd4 100644 --- a/api/gateway/status_message.go +++ b/api/gateway/status_message.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/api/gateway/status_message_test.go b/api/gateway/status_message_test.go index c30556a80..5a66b3f84 100644 --- a/api/gateway/status_message_test.go +++ b/api/gateway/status_message_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/api/handler/handler.proto b/api/handler/handler.proto index db82a844c..2c0751653 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 3b5f7dfaf..6d32c8f7e 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; diff --git a/api/noc/noc.proto b/api/noc/noc.proto index 989c7d580..a588cffd8 100644 --- a/api/noc/noc.proto +++ b/api/noc/noc.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 36d112d65..7f7de595c 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; @@ -31,4 +34,4 @@ service DeviceManager { rpc GetDevice(DeviceIdentifier) returns (Device); rpc SetDevice(Device) returns (api.Ack); rpc DeleteDevice(DeviceIdentifier) returns (api.Ack); -} \ No newline at end of file +} diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 73aeddb80..298e77516 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; diff --git a/api/protocol/protocol.proto b/api/protocol/protocol.proto index 3bece744d..78defddf3 100644 --- a/api/protocol/protocol.proto +++ b/api/protocol/protocol.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto"; diff --git a/api/router/router.proto b/api/router/router.proto index d56133f01..c7727f8a4 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; diff --git a/cmd/root_test.go b/cmd/root_test.go index 5f559daa4..1f52afd5f 100644 --- a/cmd/root_test.go +++ b/cmd/root_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( diff --git a/core/broker/activation.go b/core/broker/activation.go index ca0a74de6..0af23fa65 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index 73c3d9fea..ea90e0010 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/broker.go b/core/broker/broker.go index 50412a762..dced87f8f 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go index 804b4c267..578262b50 100644 --- a/core/broker/broker_test.go +++ b/core/broker/broker_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/deduplicator.go b/core/broker/deduplicator.go index 26ab1e6d9..ff308fa61 100644 --- a/core/broker/deduplicator.go +++ b/core/broker/deduplicator.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/deduplicator_test.go b/core/broker/deduplicator_test.go index b82acd5c7..b8d58b619 100644 --- a/core/broker/deduplicator_test.go +++ b/core/broker/deduplicator_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/downlink.go b/core/broker/downlink.go index 9887c3f38..cc3fdbfe6 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go index 8180b2e61..d09cb71f7 100644 --- a/core/broker/downlink_test.go +++ b/core/broker/downlink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 1d7c9eef3..a5c400734 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/server.go b/core/broker/server.go index 0f7521b93..31c4ae922 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/server_test.go b/core/broker/server_test.go index 1c26daf2a..8c46630de 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/uplink.go b/core/broker/uplink.go index ab3a737f0..b0e82ee7f 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 4d228827b..40ce4e54c 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package broker import ( diff --git a/core/component.go b/core/component.go index 1d376ff92..653e53945 100644 --- a/core/component.go +++ b/core/component.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package core import ( diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 1405c3ed2..27c7800a5 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index 52138a9b8..39af8c558 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 85778efc8..9d1a80e60 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + // Package discovery implements TTN Service Discovery. package discovery diff --git a/core/discovery/discovery_integration_test.go b/core/discovery/discovery_integration_test.go index 28ea63016..522324eea 100644 --- a/core/discovery/discovery_integration_test.go +++ b/core/discovery/discovery_integration_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 8c09f6d5b..878094fa6 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go index b6676b900..45251f61c 100644 --- a/core/discovery/handler_discovery.go +++ b/core/discovery/handler_discovery.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/handler_discovery_test.go b/core/discovery/handler_discovery_test.go index eb484492e..62ac2d005 100644 --- a/core/discovery/handler_discovery_test.go +++ b/core/discovery/handler_discovery_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/server.go b/core/discovery/server.go index 0f5c597c7..5346ed4d2 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index a7879b096..7a4518b2b 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package discovery import ( diff --git a/core/fcnt/fcnt.go b/core/fcnt/fcnt.go index aba1dd2cf..a2529fab9 100644 --- a/core/fcnt/fcnt.go +++ b/core/fcnt/fcnt.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package fcnt const maxUint16 = (1 << 16) diff --git a/core/fcnt/fcnt_test.go b/core/fcnt/fcnt_test.go index c9193ad9a..33091b34e 100644 --- a/core/fcnt/fcnt_test.go +++ b/core/fcnt/fcnt_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package fcnt import ( diff --git a/core/handler/activation.go b/core/handler/activation.go index 2c7e9c196..a39a45cb3 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 2443e8b70..7ad47c003 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/application/application.go b/core/handler/application/application.go index c13a62a45..a6b5bb9ed 100644 --- a/core/handler/application/application.go +++ b/core/handler/application/application.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package application import "fmt" diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go index 8cc194a65..4bfc47cee 100644 --- a/core/handler/application/application_test.go +++ b/core/handler/application/application_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package application import ( diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 4c5bedf69..75a44b194 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package application import ( diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go index 279d951c6..fb9703aea 100644 --- a/core/handler/application/store_test.go +++ b/core/handler/application/store_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package application import ( diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index 24589ff15..b4d5dfe38 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index 9c7ba17f3..2747882c8 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/convert_metadata.go b/core/handler/convert_metadata.go index e66b9a76e..cfbc0e0c8 100644 --- a/core/handler/convert_metadata.go +++ b/core/handler/convert_metadata.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go index 59e914b4e..e22ef60e5 100644 --- a/core/handler/convert_metadata_test.go +++ b/core/handler/convert_metadata_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/device/device.go b/core/handler/device/device.go index ce9394996..39843d813 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index 9e7b54ff8..adb914b58 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 5a6d291a1..c83907455 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go index 2edd7fd28..043c9305a 100644 --- a/core/handler/device/store_test.go +++ b/core/handler/device/store_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/handler/downlink.go b/core/handler/downlink.go index fec6e2879..e312a437b 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index c363b9401..8490891b8 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/handler.go b/core/handler/handler.go index 2978f15e9..9da6a80a5 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/handler_test.go b/core/handler/handler_test.go index aad4b96e2..0339071bb 100644 --- a/core/handler/handler_test.go +++ b/core/handler/handler_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler // TODO diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 5bd0926bc..5bfbea960 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 7f34e5f19..11c9a684a 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index a3cc0a75c..e9a32553e 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/server.go b/core/handler/server.go index fdc88d4cb..a81a53635 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/server_test.go b/core/handler/server_test.go index aad4b96e2..0339071bb 100644 --- a/core/handler/server_test.go +++ b/core/handler/server_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler // TODO diff --git a/core/handler/types.go b/core/handler/types.go index 197e95809..1ae2027fc 100644 --- a/core/handler/types.go +++ b/core/handler/types.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/uplink.go b/core/handler/uplink.go index 1d6376093..4a9676fb7 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index d9a196212..bee80a6d6 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 6ed3ff801..01b665cf4 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go index 0f0e4ad7c..146a94069 100644 --- a/core/networkserver/device/device_test.go +++ b/core/networkserver/device/device_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 19c09fa4b..98b00a458 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go index 32d9e4e17..ab1f0be57 100644 --- a/core/networkserver/device/store_test.go +++ b/core/networkserver/device/store_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device import ( diff --git a/core/networkserver/device/utilization.go b/core/networkserver/device/utilization.go index ca16ebf21..f1cf1975e 100644 --- a/core/networkserver/device/utilization.go +++ b/core/networkserver/device/utilization.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package device // Utilization of the device diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index f491e185a..4f2950978 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package networkserver import ( diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 4a759e4ef..16d6b5e8d 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package networkserver import ( diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 4a2db3db1..67122560e 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package networkserver import ( diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 4b0acd35a..5df81020c 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package networkserver import ( diff --git a/core/networkserver/server_test.go b/core/networkserver/server_test.go index 20243c814..b4c8f7d55 100644 --- a/core/networkserver/server_test.go +++ b/core/networkserver/server_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package networkserver // TODO: The RPC server is super simple, but we should still test it. diff --git a/core/protos/collector.proto b/core/protos/collector.proto index bb2f1f456..573fef82f 100644 --- a/core/protos/collector.proto +++ b/core/protos/collector.proto @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + syntax = "proto3"; package core; diff --git a/core/router/activation.go b/core/router/activation.go index 9897a93b7..5ea24f42b 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 19a716200..9ce25f420 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/downlink.go b/core/router/downlink.go index c30ce25ba..5772b2f90 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 3e2a61f3d..f9854b0e4 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 6a4e48dac..c3d1eadf6 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/gateway_test.go b/core/router/gateway/gateway_test.go index ed8615170..87427fa46 100644 --- a/core/router/gateway/gateway_test.go +++ b/core/router/gateway/gateway_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index f10b3f72b..b55aa6134 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index 3df1c8e4c..a5c27ecbc 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index 818ba86ff..bc9e49b4a 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go index e20f1532f..6d4827db3 100644 --- a/core/router/gateway/status_test.go +++ b/core/router/gateway/status_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/utilization.go b/core/router/gateway/utilization.go index bd408c950..b6145ff9c 100644 --- a/core/router/gateway/utilization.go +++ b/core/router/gateway/utilization.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway/utilization_test.go b/core/router/gateway/utilization_test.go index 581554061..fae29de5c 100644 --- a/core/router/gateway/utilization_test.go +++ b/core/router/gateway/utilization_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package gateway import ( diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index 83a3681c4..a0d1238ef 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index a5c6497f8..b9b77b48a 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 5d0efd87c..1af4404da 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/router.go b/core/router/router.go index e289caf50..824f3f461 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/router_test.go b/core/router/router_test.go index e3a4fd70a..65e8c5a6b 100644 --- a/core/router/router_test.go +++ b/core/router/router_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import "testing" diff --git a/core/router/server.go b/core/router/server.go index b17dd4b53..0e9dba9a6 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/server_test.go b/core/router/server_test.go index 3038147e1..7c6c30c42 100644 --- a/core/router/server_test.go +++ b/core/router/server_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/uplink.go b/core/router/uplink.go index 521f660f3..d2f88204e 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index 08cf3511f..cfcc7fa7e 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package router import ( diff --git a/core/storage/redis.go b/core/storage/redis.go index a11b7811c..9c0f742fc 100644 --- a/core/storage/redis.go +++ b/core/storage/redis.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package storage import ( diff --git a/core/storage/redis_test.go b/core/storage/redis_test.go index b1e5c5f24..7239318f1 100644 --- a/core/storage/redis_test.go +++ b/core/storage/redis_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package storage import ( diff --git a/core/types/data_rate.go b/core/types/data_rate.go index 58bf8e107..146e6b4c7 100644 --- a/core/types/data_rate.go +++ b/core/types/data_rate.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/data_rate_test.go b/core/types/data_rate_test.go index b60858f12..c013f119f 100644 --- a/core/types/data_rate_test.go +++ b/core/types/data_rate_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index fa5515483..0efc5230f 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index 0a7012789..d631df23f 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/device_type.go b/core/types/device_type.go index a30e9b6a9..2203a0c41 100644 --- a/core/types/device_type.go +++ b/core/types/device_type.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import "errors" diff --git a/core/types/device_type_test.go b/core/types/device_type_test.go index 206b98e0b..14dde2100 100644 --- a/core/types/device_type_test.go +++ b/core/types/device_type_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/eui.go b/core/types/eui.go index 7fed07c22..1d585782a 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/eui_test.go b/core/types/eui_test.go index 3f6862175..536eccddc 100644 --- a/core/types/eui_test.go +++ b/core/types/eui_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/keys.go b/core/types/keys.go index bbdb05dfd..f01847468 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/keys_test.go b/core/types/keys_test.go index ffb433f83..04b442510 100644 --- a/core/types/keys_test.go +++ b/core/types/keys_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/core/types/parse_hex.go b/core/types/parse_hex.go index 2fe945afe..4b07c4526 100644 --- a/core/types/parse_hex.go +++ b/core/types/parse_hex.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package types import ( diff --git a/mqtt/types.go b/mqtt/types.go index 0ce26a30e..668daef71 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package mqtt import "github.com/TheThingsNetwork/ttn/core/types" diff --git a/utils/cli/handler/cli.go b/utils/cli/handler/cli.go index 8d3326a59..10f1567e2 100644 --- a/utils/cli/handler/cli.go +++ b/utils/cli/handler/cli.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package handler import ( diff --git a/utils/stats/registry.go b/utils/stats/registry.go index 7ff38044c..9eaecaaea 100644 --- a/utils/stats/registry.go +++ b/utils/stats/registry.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package stats import ( diff --git a/utils/stats/string.go b/utils/stats/string.go index 2bb03c76f..adb4c612d 100644 --- a/utils/stats/string.go +++ b/utils/stats/string.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package stats import ( diff --git a/utils/toa/toa.go b/utils/toa/toa.go index 380347c65..1a83338e9 100644 --- a/utils/toa/toa.go +++ b/utils/toa/toa.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package toa import ( diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go index 6249476eb..78e18e9ae 100644 --- a/utils/toa/toa_test.go +++ b/utils/toa/toa_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package toa import ( diff --git a/utils/tokenkey/tokenkey.go b/utils/tokenkey/tokenkey.go index 61f511bbb..7f23735ea 100644 --- a/utils/tokenkey/tokenkey.go +++ b/utils/tokenkey/tokenkey.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package tokenkey import ( From a9e1e2fb65e99e54efcaf414c21a15fc443a1e59 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 12:37:51 +0200 Subject: [PATCH 1541/2266] Fix Tests --- core/broker/broker_test.go | 4 ++++ core/networkserver/networkserver_test.go | 10 ++++++---- core/router/gateway/schedule.go | 2 +- utils/random/random.go | 20 ++++++++++---------- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go index 578262b50..8252abb7d 100644 --- a/core/broker/broker_test.go +++ b/core/broker/broker_test.go @@ -40,6 +40,10 @@ func (d *mockHandlerDiscovery) All() (a []*pb_discovery.Announcement, err error) return []*pb_discovery.Announcement{d.a}, nil } +func (d *mockHandlerDiscovery) AddAppID(_, _ string) error { + return nil +} + type mockNetworkServer struct { devices []*pb_lorawan.Device } diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 67122560e..32d0d4bd6 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -64,6 +64,8 @@ func TestHandleGetDevices(t *testing.T) { devices: device.NewDeviceStore(), } + nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} + // No Devices devAddr1 := getDevAddr(1, 2, 3, 4) res, err := ns.HandleGetDevices(&pb.DevicesRequest{ @@ -78,7 +80,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: getDevAddr(1, 2, 3, 4), AppEUI: types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), DevEUI: types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), - NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: nwkSKey, FCntUp: 5, }) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ @@ -110,7 +112,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: getDevAddr(5, 6, 7, 8), AppEUI: types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), DevEUI: types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), - NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: nwkSKey, FCntUp: 5, Options: device.Options{ DisableFCntCheck: true, @@ -129,7 +131,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: getDevAddr(2, 2, 3, 4), AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: nwkSKey, FCntUp: 5 + (2 << 16), Options: device.Options{ Uses32BitFCnt: true, @@ -147,7 +149,7 @@ func TestHandleGetDevices(t *testing.T) { DevAddr: devAddr3, AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - NwkSKey: types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, + NwkSKey: nwkSKey, FCntUp: (2 << 16) - 1, Options: device.Options{ Uses32BitFCnt: true, diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index b55aa6134..7439b5e96 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -51,7 +51,7 @@ type scheduledItem struct { type schedule struct { sync.RWMutex - random random.TTNRandom + random *random.TTNRandom ctx log.Interface active bool offset int64 diff --git a/utils/random/random.go b/utils/random/random.go index 298044e28..eb5f28dd1 100644 --- a/utils/random/random.go +++ b/utils/random/random.go @@ -26,8 +26,8 @@ type TTNRandom struct { src *rand.Rand } -func New() TTNRandom { - return TTNRandom{ +func New() *TTNRandom { + return &TTNRandom{ src: rand.New(rand.NewSource(time.Now().UnixNano())), } } @@ -35,7 +35,7 @@ func New() TTNRandom { var global = New() // String returns random string of length n -func (r TTNRandom) String(n int) string { +func (r *TTNRandom) String(n int) string { r.Lock() defer r.Unlock() b := make([]byte, n) @@ -56,7 +56,7 @@ func (r TTNRandom) String(n int) string { } // Token generate a random 2-bytes token -func (r TTNRandom) Token() []byte { +func (r *TTNRandom) Token() []byte { r.Lock() defer r.Unlock() b := make([]byte, 4) @@ -65,7 +65,7 @@ func (r TTNRandom) Token() []byte { } // Rssi generates RSSI signal between -120 < rssi < 0 -func (r TTNRandom) Rssi() int32 { +func (r *TTNRandom) Rssi() int32 { r.Lock() defer r.Unlock() // Generate RSSI. Tend towards generating great signal strength. @@ -98,14 +98,14 @@ var usFreqs = []float32{ } // Freq generates a frequency between 865.0 and 870.0 Mhz -func (r TTNRandom) Freq() float32 { +func (r *TTNRandom) Freq() float32 { r.Lock() defer r.Unlock() return usFreqs[r.src.Intn(len(usFreqs))] } // Datr generates Datr for instance: SF4BW125 -func (r TTNRandom) Datr() string { +func (r *TTNRandom) Datr() string { r.Lock() defer r.Unlock() // Spread Factor from 12 to 7 @@ -122,7 +122,7 @@ func (r TTNRandom) Datr() string { } // Codr generates Codr for instance: 4/6 -func (r TTNRandom) Codr() string { +func (r *TTNRandom) Codr() string { r.Lock() defer r.Unlock() d := r.src.Intn(4) + 5 @@ -130,7 +130,7 @@ func (r TTNRandom) Codr() string { } // Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise -func (r TTNRandom) Lsnr() float32 { +func (r *TTNRandom) Lsnr() float32 { r.Lock() defer r.Unlock() x := float64(r.src.Int31()) * float64(2e-9) @@ -138,7 +138,7 @@ func (r TTNRandom) Lsnr() float32 { } // Bytes generates a random byte slice of length n -func (r TTNRandom) Bytes(n int) []byte { +func (r *TTNRandom) Bytes(n int) []byte { r.Lock() defer r.Unlock() p := make([]byte, n) From b27c333d11fec55f90c79df8473d1cb92ab2ba51 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 24 Jun 2016 12:40:06 +0200 Subject: [PATCH 1542/2266] Add LastSeen to Devices --- api/protocol/lorawan/device.pb.go | 98 ++++++++++++++++++---------- api/protocol/lorawan/device.proto | 3 + core/handler/manager_server.go | 1 + core/networkserver/device/store.go | 5 +- core/networkserver/manager_server.go | 1 + core/networkserver/networkserver.go | 2 +- 6 files changed, 75 insertions(+), 35 deletions(-) diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 928d4f784..963f3ef09 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -71,6 +71,8 @@ type Device struct { // Options DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + // Other + LastSeen int64 `protobuf:"varint,21,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` } func (m *Device) Reset() { *m = Device{} } @@ -375,6 +377,13 @@ func (m *Device) MarshalTo(data []byte) (int, error) { } i++ } + if m.LastSeen != 0 { + data[i] = 0xa8 + i++ + data[i] = 0x1 + i++ + i = encodeVarintDevice(data, i, uint64(m.LastSeen)) + } return i, nil } @@ -466,6 +475,9 @@ func (m *Device) Size() (n int) { if m.Uses32BitFCnt { n += 2 } + if m.LastSeen != 0 { + n += 2 + sovDevice(uint64(m.LastSeen)) + } return n } @@ -953,6 +965,25 @@ func (m *Device) Unmarshal(data []byte) error { } } m.Uses32BitFCnt = bool(v != 0) + case 21: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field LastSeen", wireType) + } + m.LastSeen = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.LastSeen |= (int64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipDevice(data[iNdEx:]) @@ -1080,37 +1111,38 @@ var ( ) var fileDescriptorDevice = []byte{ - // 504 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0xcf, 0x8b, 0xd3, 0x40, - 0x14, 0xc7, 0xcd, 0xae, 0x4d, 0x93, 0xb1, 0xc5, 0x12, 0x11, 0x62, 0x91, 0x55, 0xf6, 0xa0, 0x5e, - 0x36, 0xc1, 0x16, 0xf5, 0xdc, 0x6e, 0x57, 0x29, 0x62, 0xc1, 0xec, 0xee, 0x39, 0xe4, 0xc7, 0x6b, - 0x3a, 0xa4, 0xce, 0x84, 0x64, 0x62, 0xe8, 0x7f, 0x22, 0xde, 0xfd, 0x5f, 0xf4, 0xe6, 0xd9, 0x83, - 0x88, 0xfe, 0x23, 0xbe, 0x99, 0xa9, 0x4b, 0x2d, 0xc8, 0x62, 0xe9, 0x61, 0xe0, 0xfd, 0xf8, 0xbe, - 0xcf, 0xcc, 0xcb, 0xcc, 0x0b, 0x19, 0x65, 0x54, 0x2c, 0xea, 0xd8, 0x4b, 0xf8, 0x3b, 0xff, 0x62, - 0x01, 0x17, 0x0b, 0xca, 0xb2, 0x6a, 0x06, 0xa2, 0xe1, 0x65, 0xee, 0x0b, 0xc1, 0xfc, 0xa8, 0xa0, - 0x7e, 0x51, 0x72, 0xc1, 0x13, 0xbe, 0xf4, 0x97, 0xbc, 0x8c, 0x9a, 0x88, 0xf9, 0x29, 0xbc, 0xa7, - 0x09, 0x78, 0x2a, 0xee, 0xb4, 0xd7, 0xd1, 0xfe, 0xc9, 0x06, 0x2b, 0xe3, 0x19, 0xd7, 0x75, 0x71, - 0x3d, 0x57, 0x9e, 0x72, 0x94, 0xa5, 0xeb, 0xfe, 0x92, 0xff, 0x73, 0x6b, 0x5c, 0x5a, 0x7e, 0xfc, - 0xc5, 0x20, 0xbd, 0x89, 0xda, 0x77, 0x9a, 0x02, 0x13, 0x74, 0x4e, 0xa1, 0x74, 0x66, 0xa4, 0x1d, - 0x15, 0x45, 0x08, 0x35, 0x75, 0x8d, 0x87, 0xc6, 0x93, 0xce, 0xf8, 0xd9, 0xb7, 0xef, 0x0f, 0x9e, - 0x5e, 0x07, 0x4e, 0x78, 0x09, 0xbe, 0x58, 0x15, 0x50, 0x79, 0xa3, 0xa2, 0x38, 0xbb, 0x9c, 0x06, - 0x26, 0x52, 0xce, 0x6a, 0x2a, 0x79, 0xd8, 0x9b, 0xe2, 0x1d, 0xec, 0xc4, 0xc3, 0x13, 0x2a, 0x1e, - 0x52, 0x24, 0xef, 0x2e, 0x91, 0xe4, 0x90, 0xa6, 0xee, 0x21, 0xe2, 0xec, 0xa0, 0x85, 0xde, 0x34, - 0x3d, 0xfe, 0xd8, 0x22, 0xa6, 0xee, 0x65, 0x43, 0x61, 0x6c, 0x28, 0x36, 0x1b, 0x3b, 0xd8, 0x73, - 0x63, 0x87, 0xfb, 0x68, 0xec, 0x2d, 0xb1, 0x24, 0x2f, 0x4a, 0xd3, 0xd2, 0xbd, 0xa9, 0x80, 0xcf, - 0x11, 0x38, 0xf8, 0x3f, 0xe0, 0x08, 0xab, 0x03, 0x79, 0x2e, 0x69, 0x38, 0x01, 0xb1, 0x59, 0x93, - 0x87, 0x55, 0x98, 0xc3, 0xca, 0x6d, 0xed, 0xc4, 0x9c, 0x35, 0xf9, 0xf9, 0x6b, 0x58, 0x05, 0x6d, - 0xa6, 0x0d, 0xc9, 0x94, 0x9f, 0x51, 0x33, 0xcd, 0x9d, 0x98, 0xf8, 0x21, 0x35, 0x33, 0xd2, 0xc6, - 0x9f, 0xab, 0x91, 0xc4, 0xf6, 0xae, 0x57, 0x23, 0x81, 0xf2, 0x6a, 0x24, 0xcf, 0x25, 0xd6, 0x3c, - 0x4c, 0x98, 0x08, 0xeb, 0xc2, 0xb5, 0x10, 0xd8, 0x0d, 0xcc, 0xf9, 0x29, 0x13, 0x97, 0x85, 0x73, - 0x9f, 0x10, 0x9d, 0x49, 0x79, 0xc3, 0x5c, 0x5b, 0xe5, 0x2c, 0x99, 0x9b, 0xa0, 0xef, 0x9c, 0x90, - 0x3b, 0x29, 0xad, 0xa2, 0x78, 0x09, 0xa1, 0x56, 0x25, 0x0b, 0x48, 0x72, 0xf7, 0x16, 0xca, 0xac, - 0xa0, 0xb7, 0x4e, 0xbd, 0x44, 0xf5, 0xa9, 0x8c, 0x3b, 0x8f, 0x49, 0xaf, 0xae, 0xa0, 0x1a, 0x0e, - 0xc2, 0x98, 0x0a, 0x5d, 0xe1, 0x76, 0x94, 0xb6, 0xab, 0xe3, 0x63, 0x2a, 0xa4, 0x7a, 0xf0, 0xc9, - 0x20, 0x5d, 0xfd, 0x38, 0xdf, 0x44, 0x2c, 0xca, 0x70, 0xca, 0x5e, 0x10, 0xfb, 0x15, 0x88, 0xf5, - 0x83, 0xbd, 0xe7, 0xad, 0xe7, 0xdd, 0xdb, 0x9e, 0xc6, 0xfe, 0xed, 0xad, 0x94, 0xf3, 0x88, 0xd8, - 0xe7, 0x57, 0x85, 0xdb, 0xd9, 0xbe, 0xe5, 0xc9, 0xe9, 0x1e, 0xe1, 0xd9, 0x86, 0xa4, 0x33, 0x81, - 0x25, 0x08, 0xb8, 0x7e, 0x8f, 0xab, 0xa2, 0x71, 0xef, 0xf3, 0xcf, 0x23, 0xe3, 0x2b, 0xae, 0x1f, - 0xb8, 0x3e, 0xfc, 0x3a, 0xba, 0x11, 0x9b, 0xea, 0x4f, 0x31, 0xfc, 0x1d, 0x00, 0x00, 0xff, 0xff, - 0x88, 0x65, 0xa1, 0x40, 0xd5, 0x04, 0x00, 0x00, + // 525 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0xdd, 0x8e, 0xd2, 0x40, + 0x14, 0xc7, 0xed, 0x22, 0xd0, 0x8e, 0x10, 0x49, 0xcd, 0x26, 0x23, 0x9a, 0xd5, 0xec, 0x85, 0x7a, + 0xb3, 0x6d, 0x84, 0xa8, 0xd7, 0xb0, 0xac, 0x86, 0x18, 0x49, 0x2c, 0xbb, 0xd7, 0xcd, 0xd0, 0x1e, + 0x60, 0x02, 0xce, 0x34, 0xed, 0x54, 0xc2, 0x9b, 0xf8, 0x02, 0x3e, 0x83, 0xaf, 0xa0, 0x77, 0x5e, + 0x7b, 0x61, 0x8c, 0xbe, 0x88, 0x67, 0x66, 0x70, 0x83, 0x24, 0x66, 0x23, 0xd9, 0x8b, 0x49, 0xce, + 0xc7, 0xff, 0xfc, 0x66, 0xce, 0x7c, 0x91, 0xde, 0x8c, 0xab, 0x79, 0x39, 0x09, 0x12, 0xf9, 0x2e, + 0x3c, 0x9f, 0xc3, 0xf9, 0x9c, 0x8b, 0x59, 0x31, 0x02, 0xb5, 0x92, 0xf9, 0x22, 0x54, 0x4a, 0x84, + 0x2c, 0xe3, 0x61, 0x96, 0x4b, 0x25, 0x13, 0xb9, 0x0c, 0x97, 0x32, 0x67, 0x2b, 0x26, 0xc2, 0x14, + 0xde, 0xf3, 0x04, 0x02, 0x13, 0xf7, 0xeb, 0x9b, 0x68, 0xfb, 0x64, 0x8b, 0x35, 0x93, 0x33, 0x69, + 0xeb, 0x26, 0xe5, 0xd4, 0x78, 0xc6, 0x31, 0x96, 0xad, 0xfb, 0x4b, 0xfe, 0xcf, 0xa9, 0x71, 0x58, + 0xf9, 0xf1, 0x17, 0x87, 0xb4, 0x06, 0x66, 0xde, 0x61, 0x0a, 0x42, 0xf1, 0x29, 0x87, 0xdc, 0x1f, + 0x91, 0x3a, 0xcb, 0xb2, 0x18, 0x4a, 0x4e, 0x9d, 0x87, 0xce, 0x93, 0x46, 0xff, 0xd9, 0xb7, 0xef, + 0x0f, 0x9e, 0x5e, 0x05, 0x4e, 0x64, 0x0e, 0xa1, 0x5a, 0x67, 0x50, 0x04, 0xbd, 0x2c, 0x3b, 0xbb, + 0x18, 0x46, 0x35, 0xa4, 0x9c, 0x95, 0x5c, 0xf3, 0xb0, 0x37, 0xc3, 0x3b, 0xd8, 0x8b, 0x87, 0x2b, + 0x34, 0x3c, 0xa4, 0x68, 0xde, 0x21, 0xd1, 0xe4, 0x98, 0xa7, 0xb4, 0x82, 0x38, 0x2f, 0xaa, 0xa2, + 0x37, 0x4c, 0x8f, 0x3f, 0x55, 0x49, 0xcd, 0xf6, 0xb2, 0xa5, 0x70, 0xb6, 0x14, 0xdb, 0x8d, 0x1d, + 0x5c, 0x73, 0x63, 0x95, 0xeb, 0x68, 0xec, 0x2d, 0x71, 0x35, 0x8f, 0xa5, 0x69, 0x4e, 0x6f, 0x1a, + 0xe0, 0x73, 0x04, 0x76, 0xfe, 0x0f, 0xd8, 0xc3, 0xea, 0x48, 0xaf, 0x4b, 0x1b, 0x7e, 0x44, 0x3c, + 0xb1, 0x5a, 0xc4, 0x45, 0xbc, 0x80, 0x35, 0xad, 0xee, 0xc5, 0x1c, 0xad, 0x16, 0xe3, 0xd7, 0xb0, + 0x8e, 0xea, 0xc2, 0x1a, 0x9a, 0xa9, 0xb7, 0xd1, 0x32, 0x6b, 0x7b, 0x31, 0x71, 0x23, 0x2d, 0x93, + 0x59, 0xe3, 0xcf, 0xd1, 0x68, 0x62, 0x7d, 0xdf, 0xa3, 0xd1, 0x40, 0x7d, 0x34, 0x9a, 0x47, 0x89, + 0x3b, 0x8d, 0x13, 0xa1, 0xe2, 0x32, 0xa3, 0x2e, 0x02, 0x9b, 0x51, 0x6d, 0x7a, 0x2a, 0xd4, 0x45, + 0xe6, 0xdf, 0x27, 0xc4, 0x66, 0x52, 0xb9, 0x12, 0xd4, 0x33, 0x39, 0x57, 0xe7, 0x06, 0xe8, 0xfb, + 0x27, 0xe4, 0x4e, 0xca, 0x0b, 0x36, 0x59, 0x42, 0x6c, 0x55, 0xc9, 0x1c, 0x92, 0x05, 0xbd, 0x85, + 0x32, 0x37, 0x6a, 0x6d, 0x52, 0x2f, 0x51, 0x7d, 0xaa, 0xe3, 0xfe, 0x63, 0xd2, 0x2a, 0x0b, 0x28, + 0xba, 0x9d, 0x78, 0xc2, 0x95, 0xad, 0xa0, 0x0d, 0xa3, 0x6d, 0xda, 0x78, 0x9f, 0x2b, 0xad, 0xf6, + 0xef, 0x11, 0x6f, 0xc9, 0x0a, 0x15, 0x17, 0x00, 0x82, 0x1e, 0xa2, 0xa2, 0x12, 0xb9, 0x3a, 0x30, + 0x46, 0xbf, 0xf3, 0xd1, 0x21, 0x4d, 0x7b, 0x73, 0xdf, 0x30, 0xc1, 0x66, 0xf8, 0x04, 0x5f, 0x10, + 0xef, 0x15, 0xa8, 0xcd, 0x6d, 0xbe, 0x1b, 0x6c, 0x3e, 0x83, 0x60, 0xf7, 0xa9, 0xb6, 0x6f, 0xef, + 0xa4, 0xfc, 0x47, 0xc4, 0x1b, 0x5f, 0x16, 0xee, 0x66, 0xdb, 0x6e, 0xa0, 0x9f, 0x7e, 0x0f, 0x17, + 0xde, 0x25, 0x8d, 0x01, 0x2c, 0x41, 0xc1, 0xd5, 0x73, 0x5c, 0x16, 0xf5, 0x5b, 0x9f, 0x7f, 0x1e, + 0x39, 0x5f, 0x71, 0xfc, 0xc0, 0xf1, 0xe1, 0xd7, 0xd1, 0x8d, 0x49, 0xcd, 0x7c, 0x23, 0xdd, 0xdf, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x3a, 0xff, 0x36, 0xff, 0xf2, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 7f7de595c..5badc3d32 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -28,6 +28,9 @@ message Device { // Options bool disable_f_cnt_check = 11; bool uses32_bit_f_cnt = 12; + + // Other + int64 last_seen = 21; } service DeviceManager { diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 5bfbea960..26621cee5 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -67,6 +67,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIde FCntDown: nsDev.FCntDown, DisableFCntCheck: nsDev.DisableFCntCheck, Uses32BitFCnt: nsDev.Uses32BitFCnt, + LastSeen: nsDev.LastSeen, }, nil } diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 98b00a458..99d8c05e6 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -7,6 +7,7 @@ import ( "errors" "fmt" "sync" + "time" "gopkg.in/redis.v3" @@ -121,6 +122,7 @@ func (s *deviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr if err != nil { return err } + dev.LastSeen = time.Now() dev.DevAddr = devAddr dev.NwkSKey = nwkSKey dev.FCntUp = 0 @@ -295,6 +297,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de // Update Device dev := &Device{ + LastSeen: time.Now(), DevAddr: devAddr, NwkSKey: nwkSKey, FCntUp: 0, @@ -302,7 +305,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de } // Don't touch Utilization and Options - dmap, err := dev.ToStringStringMap("dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") + dmap, err := dev.ToStringStringMap("last_seen", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") // Register Device err = s.client.HMSetMap(key, dmap).Err() diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 4f2950978..cf3ca79c9 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -58,6 +58,7 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev FCntDown: dev.FCntDown, DisableFCntCheck: dev.Options.DisableFCntCheck, Uses32BitFCnt: dev.Options.Uses32BitFCnt, + LastSeen: dev.LastSeen.UnixNano(), }, nil } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 16d6b5e8d..e6650a871 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -219,7 +219,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag dev.FCntUp = macPayload.FHDR.FCnt } dev.LastSeen = time.Now().UTC() - err = n.devices.Set(dev, "f_cnt_up") + err = n.devices.Set(dev, "f_cnt_up", "last_seen") if err != nil { return nil, err } From fc3076fe6e433cbbcb2138a6e80209e965a13859 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 6 Jul 2016 13:54:59 +0200 Subject: [PATCH 1543/2266] Updated protos --- api/api.pb.go | 2 +- api/broker/broker.pb.go | 6 ++++-- api/discovery/discovery.pb.go | 6 ++++-- api/gateway/gateway.pb.go | 2 +- api/handler/handler.pb.go | 11 +++++++---- api/networkserver/networkserver.pb.go | 8 +++++--- api/noc/noc.pb.go | 3 ++- api/protocol/lorawan/device.pb.go | 5 +++-- api/protocol/protocol.pb.go | 2 +- api/router/router.pb.go | 6 ++++-- core/collector.pb.go | 5 +++-- 11 files changed, 35 insertions(+), 21 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index c2bec7695..1bcd89580 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -29,7 +29,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 // message Ack is used to acknowledge a request, without giving a response. type Ack struct { diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 77eeb5c4d..944a14f62 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -51,7 +51,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type DownlinkOption struct { Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` @@ -719,6 +719,7 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, + Metadata: fileDescriptorBroker, } // Client API for BrokerManager service @@ -818,7 +819,8 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ Handler: _BrokerManager_GetStatus_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorBroker, } func (m *DownlinkOption) Marshal() (data []byte, err error) { diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index c5ef5ab94..8ad9ef7cc 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -36,7 +36,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type Metadata_Key int32 @@ -275,7 +275,8 @@ var _Discovery_serviceDesc = grpc.ServiceDesc{ Handler: _Discovery_Get_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorDiscovery, } // Client API for DiscoveryManager service @@ -305,6 +306,7 @@ var _DiscoveryManager_serviceDesc = grpc.ServiceDesc{ HandlerType: (*DiscoveryManagerServer)(nil), Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorDiscovery, } func (m *Metadata) Marshal() (data []byte, err error) { diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index b4d87a944..893f98bce 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -32,7 +32,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type GPSMetadata struct { Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 9c18a26bd..9b9efc050 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -38,7 +38,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type DeviceActivationResponse struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` @@ -181,7 +181,8 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ Handler: _Handler_Activate_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorHandler, } // Client API for ApplicationManager service @@ -310,7 +311,8 @@ var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ Handler: _ApplicationManager_DeleteApplication_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorHandler, } // Client API for HandlerManager service @@ -373,7 +375,8 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ Handler: _HandlerManager_GetStatus_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorHandler, } func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index ecb5ccb3e..3a31eb2cb 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -41,7 +41,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type DevicesRequest struct { DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` @@ -313,7 +313,8 @@ var _NetworkServer_serviceDesc = grpc.ServiceDesc{ Handler: _NetworkServer_Downlink_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorNetworkserver, } // Client API for NetworkServerManager service @@ -376,7 +377,8 @@ var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ Handler: _NetworkServerManager_GetStatus_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorNetworkserver, } func (m *DevicesRequest) Marshal() (data []byte, err error) { diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index d9a7c41b8..363455151 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -33,7 +33,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 // Reference imports to suppress errors if they are not otherwise used. var _ context.Context @@ -339,6 +339,7 @@ var _Monitoring_serviceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, + Metadata: fileDescriptorNoc, } var fileDescriptorNoc = []byte{ diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 963f3ef09..a9f30cec2 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -45,7 +45,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type DeviceIdentifier struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` @@ -219,7 +219,8 @@ var _DeviceManager_serviceDesc = grpc.ServiceDesc{ Handler: _DeviceManager_DeleteDevice_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorDevice, } func (m *DeviceIdentifier) Marshal() (data []byte, err error) { diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 66a5ab7e2..012c1c75c 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -30,7 +30,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type RxMetadata struct { // Types that are valid to be assigned to Protocol: diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 45a860a8b..34462f7c8 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -49,7 +49,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type SubscribeRequest struct { } @@ -566,6 +566,7 @@ var _Router_serviceDesc = grpc.ServiceDesc{ ServerStreams: true, }, }, + Metadata: fileDescriptorRouter, } // Client API for RouterManager service @@ -665,7 +666,8 @@ var _RouterManager_serviceDesc = grpc.ServiceDesc{ Handler: _RouterManager_GetStatus_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorRouter, } func (m *SubscribeRequest) Marshal() (data []byte, err error) { diff --git a/core/collector.pb.go b/core/collector.pb.go index f9bbdf21b..a6743f5eb 100644 --- a/core/collector.pb.go +++ b/core/collector.pb.go @@ -37,7 +37,7 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion2 +const _ = proto.ProtoPackageIsVersion1 type GetApplicationsCollectorReq struct { } @@ -263,7 +263,8 @@ var _CollectorManager_serviceDesc = grpc.ServiceDesc{ Handler: _CollectorManager_RemoveApplication_Handler, }, }, - Streams: []grpc.StreamDesc{}, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorCollector, } func (m *GetApplicationsCollectorReq) Marshal() (data []byte, err error) { From ee968bf358dde057078062de69dd631056ea3ae3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 7 Jul 2016 08:30:44 +0200 Subject: [PATCH 1544/2266] Retry Redis Connection --- cmd/broker.go | 2 ++ cmd/handler.go | 2 ++ cmd/networkserver.go | 2 ++ cmd/root.go | 22 ++++++++++++++++++++++ 4 files changed, 28 insertions(+) diff --git a/cmd/broker.go b/cmd/broker.go index 70ddc578a..681ee9738 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -45,6 +45,8 @@ var brokerCmd = &cobra.Command{ DB: int64(viper.GetInt("broker.redis-db")), }) + connectRedis(client) + // Component component := core.NewComponent(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) diff --git a/cmd/handler.go b/cmd/handler.go index 33715732d..0037c4f18 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -40,6 +40,8 @@ var handlerCmd = &cobra.Command{ DB: int64(viper.GetInt("handler.redis-db")), }) + connectRedis(client) + // Component component := core.NewComponent(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) diff --git a/cmd/networkserver.go b/cmd/networkserver.go index ea1f6dd6d..995e62d40 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -43,6 +43,8 @@ var networkserverCmd = &cobra.Command{ DB: int64(viper.GetInt("networkserver.redis-db")), }) + connectRedis(client) + // Component component := core.NewComponent(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) diff --git a/cmd/root.go b/cmd/root.go index 46f349252..44475ed14 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -8,6 +8,9 @@ import ( "os" "path" "strings" + "time" + + "gopkg.in/redis.v3" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" @@ -98,3 +101,22 @@ func initConfig() { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } + +var RedisConnectRetries = 10 +var RedisConnectRetryDelay = 1 * time.Second + +func connectRedis(client *redis.Client) error { + var err error + for retries := 0; retries < RedisConnectRetries; retries++ { + _, err = client.Ping().Result() + if err == nil { + break + } + <-time.After(RedisConnectRetryDelay) + } + if err != nil { + client.Close() + return err + } + return nil +} From 3a58e823ba6acda10af33541e076764f3d7f372f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 7 Jul 2016 08:31:22 +0200 Subject: [PATCH 1545/2266] Use Redis DB 1 for testing --- core/components/collector/appStorage_test.go | 6 +++--- core/components/collector/collector_test.go | 2 +- core/router/gateway/status_test.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/components/collector/appStorage_test.go b/core/components/collector/appStorage_test.go index f7020b994..ad22a9536 100644 --- a/core/components/collector/appStorage_test.go +++ b/core/components/collector/appStorage_test.go @@ -11,7 +11,7 @@ import ( ) func createStorage() AppStorage { - storage, err := ConnectRedis("localhost:6379", 0) + storage, err := ConnectRedis("localhost:6379", 1) if err != nil { panic(err) } @@ -21,11 +21,11 @@ func createStorage() AppStorage { func TestConnect(t *testing.T) { a := New(t) - c, err := ConnectRedis("localhost:6379", 0) + c, err := ConnectRedis("localhost:6379", 1) a.So(err, ShouldBeNil) defer c.Close() - _, err = ConnectRedis("", 0) + _, err = ConnectRedis("", 1) a.So(err, ShouldNotBeNil) } diff --git a/core/components/collector/collector_test.go b/core/components/collector/collector_test.go index b706dad8a..fa63e8559 100644 --- a/core/components/collector/collector_test.go +++ b/core/components/collector/collector_test.go @@ -19,7 +19,7 @@ type mockStorage struct { func TestCollection(t *testing.T) { a := New(t) - appStorage, err := ConnectRedis("localhost:6379", 0) + appStorage, err := ConnectRedis("localhost:6379", 1) if err != nil { panic(err) } diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go index 6d4827db3..bc7084943 100644 --- a/core/router/gateway/status_test.go +++ b/core/router/gateway/status_test.go @@ -17,7 +17,7 @@ func getRedisClient() *redis.Client { return redis.NewClient(&redis.Options{ Addr: "localhost:6379", Password: "", // no password set - DB: 0, // use default DB + DB: 1, // use default DB }) } From 783f24c02613597ff5419f308a0a07ac0ef6f14e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 7 Jul 2016 08:32:54 +0200 Subject: [PATCH 1546/2266] Don't clean compiled protos --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index f2e52fe73..c0d4941fc 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,6 @@ clean: [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] ([ -d $(TEMP_COVER_DIR) ] && rm -rf $(TEMP_COVER_DIR)) || [ ! -d $(TEMP_COVER_DIR) ] ([ -f $(COVER_FILE) ] && rm $(COVER_FILE)) || [ ! -d $(COVER_FILE) ] - find ./api -name '*.pb.go' | xargs rm -f build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) From 9f028bd5d66466eded39d3978ea14d8b942c4a08 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 7 Jul 2016 18:05:14 +0200 Subject: [PATCH 1547/2266] Catch and log panics in root cmd --- cmd/root.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/root.go b/cmd/root.go index 44475ed14..45a1828ef 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path" + "runtime" "strings" "time" @@ -43,6 +44,14 @@ var RootCmd = &cobra.Command{ // Execute adds all child commands to the root command sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { + defer func() { + buf := make([]byte, 1<<16) + runtime.Stack(buf, false) + if thePanic := recover(); thePanic != nil && ctx != nil { + ctx.WithField("panic", thePanic).WithField("stack", string(buf)).Fatal("Stopping because of panic") + } + }() + if err := RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) From 02c73e3439290f3cd617324b2b81ef279992af70 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 8 Jul 2016 09:05:19 +0200 Subject: [PATCH 1548/2266] Remove Collector from ttn repo --- cmd/collector.go | 84 -- core/collection/appCollector.go | 77 -- core/collection/appCollector_test.go | 99 -- core/collection/dataStorage.go | 16 - core/collector.pb.go | 1164 ----------------- core/components/collector/appStorage.go | 116 -- core/components/collector/appStorage_test.go | 73 -- core/components/collector/collector.go | 129 -- core/components/collector/collectorManager.go | 75 -- core/components/collector/collector_test.go | 54 - .../components/collector/influxdb/influxdb.go | 54 - core/protos/collector.proto | 38 - 12 files changed, 1979 deletions(-) delete mode 100644 cmd/collector.go delete mode 100644 core/collection/appCollector.go delete mode 100644 core/collection/appCollector_test.go delete mode 100644 core/collection/dataStorage.go delete mode 100644 core/collector.pb.go delete mode 100644 core/components/collector/appStorage.go delete mode 100644 core/components/collector/appStorage_test.go delete mode 100644 core/components/collector/collector.go delete mode 100644 core/components/collector/collectorManager.go delete mode 100644 core/components/collector/collector_test.go delete mode 100644 core/components/collector/influxdb/influxdb.go delete mode 100644 core/protos/collector.proto diff --git a/cmd/collector.go b/cmd/collector.go deleted file mode 100644 index f0d4e22f1..000000000 --- a/cmd/collector.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "fmt" - "os" - "os/signal" - - "github.com/TheThingsNetwork/ttn/core/components/collector" - "github.com/TheThingsNetwork/ttn/core/components/collector/influxdb" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// collectorCmd represents the collector command -var collectorCmd = &cobra.Command{ - Use: "collector", - Short: "The Things Network collector", - Long: `ttn collector starts the Collector component of The Things Network. - -The Collector is responsible for storing uplink packets from the handler for -configured applications. -`, - Run: func(cmd *cobra.Command, args []string) { - ctx.Info("Starting") - - appStorage, err := collector.ConnectRedis(viper.GetString("collector.redis-addr"), int64(viper.GetInt("collector.redis-db"))) - if err != nil { - ctx.WithError(err).Fatal("Failed to connect to Redis") - } - defer appStorage.Close() - - dataStorage, err := influxdb.NewDataStorage(viper.GetString("collector.influxdb-addr"), - viper.GetString("collector.influxdb-username"), viper.GetString("collector.influxdb-password")) - if err != nil { - ctx.WithError(err).Fatal("Failed to connect to InfluxDB") - } - - col := collector.NewCollector(ctx, - appStorage, - viper.GetString("collector.mqtt-broker"), - dataStorage, - fmt.Sprintf("%s:%d", viper.GetString("collector.address"), viper.GetInt("collector.port"))) - collectors, err := col.Start() - if startError, ok := err.(collector.StartError); ok { - ctx.WithError(startError).Warn("Could not start collecting all applications") - } else if err != nil { - ctx.WithError(err).Fatal("Could not start collector") - } - defer col.Stop() - - ctx.Infof("Started %d app collectors", len(collectors)) - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - <-c - }, -} - -func init() { - RootCmd.AddCommand(collectorCmd) - - collectorCmd.Flags().String("address", "0.0.0.0", "The IP address to listen for management") - collectorCmd.Flags().Int("port", 1783, "The port to listen for management") - viper.BindPFlag("collector.address", collectorCmd.Flags().Lookup("address")) - viper.BindPFlag("collector.port", collectorCmd.Flags().Lookup("port")) - - collectorCmd.Flags().String("mqtt-broker", "localhost:1883", "The address of the MQTT broker") - viper.BindPFlag("collector.mqtt-broker", collectorCmd.Flags().Lookup("mqtt-broker")) - - collectorCmd.Flags().String("influxdb-addr", "http://localhost:8086", "The address of InfluxDB") - collectorCmd.Flags().String("influxdb-username", "", "The username for InfluxDB") - collectorCmd.Flags().String("influxdb-password", "", "The password for InfluxDB") - viper.BindPFlag("collector.influxdb-addr", collectorCmd.Flags().Lookup("influxdb-addr")) - viper.BindPFlag("collector.influxdb-username", collectorCmd.Flags().Lookup("influxdb-username")) - viper.BindPFlag("collector.influxdb-password", collectorCmd.Flags().Lookup("influxdb-password")) - - collectorCmd.Flags().String("redis-addr", "localhost:6379", "The address of Redis") - collectorCmd.Flags().Int("redis-db", 0, "The database of Redis") - viper.BindPFlag("collector.redis-addr", collectorCmd.Flags().Lookup("redis-addr")) - viper.BindPFlag("collector.redis-db", collectorCmd.Flags().Lookup("redis-db")) -} diff --git a/core/collection/appCollector.go b/core/collection/appCollector.go deleted file mode 100644 index 7b67d2564..000000000 --- a/core/collection/appCollector.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collection - -import ( - "fmt" - "time" - - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" - "github.com/apex/log" -) - -// AppCollector represents a collector for application data -type AppCollector interface { - Start() error - Stop() -} - -type appCollector struct { - ctx log.Interface - eui types.AppEUI - mqttBroker string - client mqtt.Client - storage DataStorage -} - -// NewMqttAppCollector instantiates a new AppCollector instance using MQTT -func NewMqttAppCollector(ctx log.Interface, mqttBroker string, eui types.AppEUI, key string, storage DataStorage) AppCollector { - return &appCollector{ - ctx: ctx, - eui: eui, - mqttBroker: mqttBroker, - client: mqtt.NewClient(ctx, "collector", eui.String(), key, fmt.Sprintf("tcp://%s", mqttBroker)), - storage: storage, - } -} - -func (c *appCollector) Start() error { - err := c.client.Connect() - if err != nil { - c.ctx.WithError(err).Error("Connect failed") - return err - } - if token := c.client.SubscribeAppUplink(c.eui, c.handleUplink); token.Wait() && token.Error() != nil { - c.ctx.WithError(token.Error()).Error("Failed to subscribe") - return token.Error() - } - c.ctx.WithField("Broker", c.mqttBroker).Info("Subscribed to app uplink packets") - return nil -} - -func (c *appCollector) Stop() { - c.client.Disconnect() -} - -func (c *appCollector) handleUplink(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, req mqtt.UplinkMessage) { - if req.Fields == nil || len(req.Fields) == 0 { - return - } - - ctx := c.ctx.WithField("DevEUI", devEUI) - - t, err := time.Parse(time.RFC3339, req.Metadata[0].ServerTime) - if err != nil { - ctx.WithError(err).Warnf("Invalid time: %v", req.Metadata[0].ServerTime) - return - } - - err = c.storage.Save(appEUI, devEUI, t, req.Fields) - if err != nil { - ctx.WithError(err).Error("Failed to save data") - return - } - ctx.Debug("Saved uplink packet in store") -} diff --git a/core/collection/appCollector_test.go b/core/collection/appCollector_test.go deleted file mode 100644 index 369532417..000000000 --- a/core/collection/appCollector_test.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collection - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" - ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" - "github.com/apex/log" - - . "github.com/smartystreets/assertions" -) - -type mockStorage struct { - entry chan *mockStorageEntry -} - -type mockStorageEntry struct { - devEUI types.DevEUI - fields map[string]interface{} -} - -func createTestCollector(ctx log.Interface, storage DataStorage) AppCollector { - eui, _ := types.ParseAppEUI("8000000000000001") - return NewMqttAppCollector(ctx, "localhost:1883", eui, "", storage) -} - -func TestStart(t *testing.T) { - a := New(t) - - ctx := ttntesting.GetLogger(t, "Collection") - storage := &mockStorage{} - collector := createTestCollector(ctx, storage) - a.So(collector, ShouldNotBeNil) - - err := collector.Start() - defer collector.Stop() - a.So(err, ShouldBeNil) -} - -func TestCollect(t *testing.T) { - a := New(t) - - ctx := ttntesting.GetLogger(t, "Collection") - storage := &mockStorage{ - entry: make(chan *mockStorageEntry), - } - collector := createTestCollector(ctx, storage) - - err := collector.Start() - defer collector.Stop() - a.So(err, ShouldBeNil) - - appEUI, _ := types.ParseAppEUI("8000000000000001") - devEUI, _ := types.ParseDevEUI("1000000000000001") - - client := mqtt.NewClient(ctx, "collector", "", "", "tcp://localhost:1883") - err = client.Connect() - So(err, ShouldBeNil) - defer client.Disconnect() - - req := mqtt.UplinkMessage{ - DevEUI: devEUI, - FCnt: 0, - FPort: 1, - Metadata: []*mqtt.Metadata{&mqtt.Metadata{ServerTime: time.Now().Format(time.RFC3339)}}, - Payload: []byte{0x1, 0x2, 0x3}, - Fields: map[string]interface{}{"size": 3}, - } - if token := client.PublishUplink(appEUI, devEUI, req); token.Wait() && token.Error() != nil { - panic(token.Error()) - } - - var entry *mockStorageEntry - select { - case entry = <-storage.entry: - break - case <-time.After(time.Second): - panic("Timeout") - } - a.So(entry, ShouldNotBeNil) - a.So(entry.devEUI, ShouldResemble, devEUI) - a.So(entry.fields, ShouldHaveLength, 1) - a.So(entry.fields["size"], ShouldEqual, 3) -} - -func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { - s.entry <- &mockStorageEntry{devEUI, fields} - return nil -} - -func (s *mockStorage) Close() error { - s.entry <- nil - return nil -} diff --git a/core/collection/dataStorage.go b/core/collection/dataStorage.go deleted file mode 100644 index 9cd990815..000000000 --- a/core/collection/dataStorage.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collection - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -// DataStorage provides methods to select and save data -type DataStorage interface { - Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error - Close() error -} diff --git a/core/collector.pb.go b/core/collector.pb.go deleted file mode 100644 index a6743f5eb..000000000 --- a/core/collector.pb.go +++ /dev/null @@ -1,1164 +0,0 @@ -// Code generated by protoc-gen-gogo. -// source: collector.proto -// DO NOT EDIT! - -/* -Package core is a generated protocol buffer package. - -It is generated from these files: - collector.proto - -It has these top-level messages: - GetApplicationsCollectorReq - GetApplicationsCollectorRes - CollectorApplication - AddApplicationCollectorReq - AddApplicationCollectorRes - RemoveApplicationCollectorReq - RemoveApplicationCollectorRes -*/ -package core - -import proto "github.com/golang/protobuf/proto" -import fmt "fmt" -import math "math" - -import ( - context "golang.org/x/net/context" - grpc "google.golang.org/grpc" -) - -import io "io" - -// Reference imports to suppress errors if they are not otherwise used. -var _ = proto.Marshal -var _ = fmt.Errorf -var _ = math.Inf - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 - -type GetApplicationsCollectorReq struct { -} - -func (m *GetApplicationsCollectorReq) Reset() { *m = GetApplicationsCollectorReq{} } -func (m *GetApplicationsCollectorReq) String() string { return proto.CompactTextString(m) } -func (*GetApplicationsCollectorReq) ProtoMessage() {} -func (*GetApplicationsCollectorReq) Descriptor() ([]byte, []int) { - return fileDescriptorCollector, []int{0} -} - -type GetApplicationsCollectorRes struct { - Applications []*CollectorApplication `protobuf:"bytes,1,rep,name=Applications,json=applications" json:"Applications,omitempty"` -} - -func (m *GetApplicationsCollectorRes) Reset() { *m = GetApplicationsCollectorRes{} } -func (m *GetApplicationsCollectorRes) String() string { return proto.CompactTextString(m) } -func (*GetApplicationsCollectorRes) ProtoMessage() {} -func (*GetApplicationsCollectorRes) Descriptor() ([]byte, []int) { - return fileDescriptorCollector, []int{1} -} - -func (m *GetApplicationsCollectorRes) GetApplications() []*CollectorApplication { - if m != nil { - return m.Applications - } - return nil -} - -type CollectorApplication struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` -} - -func (m *CollectorApplication) Reset() { *m = CollectorApplication{} } -func (m *CollectorApplication) String() string { return proto.CompactTextString(m) } -func (*CollectorApplication) ProtoMessage() {} -func (*CollectorApplication) Descriptor() ([]byte, []int) { return fileDescriptorCollector, []int{2} } - -type AddApplicationCollectorReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` - AppAccessKey string `protobuf:"bytes,2,opt,name=AppAccessKey,json=appAccessKey,proto3" json:"AppAccessKey,omitempty"` -} - -func (m *AddApplicationCollectorReq) Reset() { *m = AddApplicationCollectorReq{} } -func (m *AddApplicationCollectorReq) String() string { return proto.CompactTextString(m) } -func (*AddApplicationCollectorReq) ProtoMessage() {} -func (*AddApplicationCollectorReq) Descriptor() ([]byte, []int) { - return fileDescriptorCollector, []int{3} -} - -type AddApplicationCollectorRes struct { -} - -func (m *AddApplicationCollectorRes) Reset() { *m = AddApplicationCollectorRes{} } -func (m *AddApplicationCollectorRes) String() string { return proto.CompactTextString(m) } -func (*AddApplicationCollectorRes) ProtoMessage() {} -func (*AddApplicationCollectorRes) Descriptor() ([]byte, []int) { - return fileDescriptorCollector, []int{4} -} - -type RemoveApplicationCollectorReq struct { - AppEUI []byte `protobuf:"bytes,1,opt,name=AppEUI,json=appEUI,proto3" json:"AppEUI,omitempty"` -} - -func (m *RemoveApplicationCollectorReq) Reset() { *m = RemoveApplicationCollectorReq{} } -func (m *RemoveApplicationCollectorReq) String() string { return proto.CompactTextString(m) } -func (*RemoveApplicationCollectorReq) ProtoMessage() {} -func (*RemoveApplicationCollectorReq) Descriptor() ([]byte, []int) { - return fileDescriptorCollector, []int{5} -} - -type RemoveApplicationCollectorRes struct { -} - -func (m *RemoveApplicationCollectorRes) Reset() { *m = RemoveApplicationCollectorRes{} } -func (m *RemoveApplicationCollectorRes) String() string { return proto.CompactTextString(m) } -func (*RemoveApplicationCollectorRes) ProtoMessage() {} -func (*RemoveApplicationCollectorRes) Descriptor() ([]byte, []int) { - return fileDescriptorCollector, []int{6} -} - -func init() { - proto.RegisterType((*GetApplicationsCollectorReq)(nil), "core.GetApplicationsCollectorReq") - proto.RegisterType((*GetApplicationsCollectorRes)(nil), "core.GetApplicationsCollectorRes") - proto.RegisterType((*CollectorApplication)(nil), "core.CollectorApplication") - proto.RegisterType((*AddApplicationCollectorReq)(nil), "core.AddApplicationCollectorReq") - proto.RegisterType((*AddApplicationCollectorRes)(nil), "core.AddApplicationCollectorRes") - proto.RegisterType((*RemoveApplicationCollectorReq)(nil), "core.RemoveApplicationCollectorReq") - proto.RegisterType((*RemoveApplicationCollectorRes)(nil), "core.RemoveApplicationCollectorRes") -} - -// Reference imports to suppress errors if they are not otherwise used. -var _ context.Context -var _ grpc.ClientConn - -// This is a compile-time assertion to ensure that this generated file -// is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 - -// Client API for CollectorManager service - -type CollectorManagerClient interface { - GetApplications(ctx context.Context, in *GetApplicationsCollectorReq, opts ...grpc.CallOption) (*GetApplicationsCollectorRes, error) - AddApplication(ctx context.Context, in *AddApplicationCollectorReq, opts ...grpc.CallOption) (*AddApplicationCollectorRes, error) - RemoveApplication(ctx context.Context, in *RemoveApplicationCollectorReq, opts ...grpc.CallOption) (*RemoveApplicationCollectorRes, error) -} - -type collectorManagerClient struct { - cc *grpc.ClientConn -} - -func NewCollectorManagerClient(cc *grpc.ClientConn) CollectorManagerClient { - return &collectorManagerClient{cc} -} - -func (c *collectorManagerClient) GetApplications(ctx context.Context, in *GetApplicationsCollectorReq, opts ...grpc.CallOption) (*GetApplicationsCollectorRes, error) { - out := new(GetApplicationsCollectorRes) - err := grpc.Invoke(ctx, "/core.CollectorManager/GetApplications", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *collectorManagerClient) AddApplication(ctx context.Context, in *AddApplicationCollectorReq, opts ...grpc.CallOption) (*AddApplicationCollectorRes, error) { - out := new(AddApplicationCollectorRes) - err := grpc.Invoke(ctx, "/core.CollectorManager/AddApplication", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -func (c *collectorManagerClient) RemoveApplication(ctx context.Context, in *RemoveApplicationCollectorReq, opts ...grpc.CallOption) (*RemoveApplicationCollectorRes, error) { - out := new(RemoveApplicationCollectorRes) - err := grpc.Invoke(ctx, "/core.CollectorManager/RemoveApplication", in, out, c.cc, opts...) - if err != nil { - return nil, err - } - return out, nil -} - -// Server API for CollectorManager service - -type CollectorManagerServer interface { - GetApplications(context.Context, *GetApplicationsCollectorReq) (*GetApplicationsCollectorRes, error) - AddApplication(context.Context, *AddApplicationCollectorReq) (*AddApplicationCollectorRes, error) - RemoveApplication(context.Context, *RemoveApplicationCollectorReq) (*RemoveApplicationCollectorRes, error) -} - -func RegisterCollectorManagerServer(s *grpc.Server, srv CollectorManagerServer) { - s.RegisterService(&_CollectorManager_serviceDesc, srv) -} - -func _CollectorManager_GetApplications_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetApplicationsCollectorReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CollectorManagerServer).GetApplications(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.CollectorManager/GetApplications", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CollectorManagerServer).GetApplications(ctx, req.(*GetApplicationsCollectorReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _CollectorManager_AddApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(AddApplicationCollectorReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CollectorManagerServer).AddApplication(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.CollectorManager/AddApplication", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CollectorManagerServer).AddApplication(ctx, req.(*AddApplicationCollectorReq)) - } - return interceptor(ctx, in, info, handler) -} - -func _CollectorManager_RemoveApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(RemoveApplicationCollectorReq) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(CollectorManagerServer).RemoveApplication(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/core.CollectorManager/RemoveApplication", - } - handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(CollectorManagerServer).RemoveApplication(ctx, req.(*RemoveApplicationCollectorReq)) - } - return interceptor(ctx, in, info, handler) -} - -var _CollectorManager_serviceDesc = grpc.ServiceDesc{ - ServiceName: "core.CollectorManager", - HandlerType: (*CollectorManagerServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "GetApplications", - Handler: _CollectorManager_GetApplications_Handler, - }, - { - MethodName: "AddApplication", - Handler: _CollectorManager_AddApplication_Handler, - }, - { - MethodName: "RemoveApplication", - Handler: _CollectorManager_RemoveApplication_Handler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorCollector, -} - -func (m *GetApplicationsCollectorReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GetApplicationsCollectorReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *GetApplicationsCollectorRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GetApplicationsCollectorRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Applications) > 0 { - for _, msg := range m.Applications { - data[i] = 0xa - i++ - i = encodeVarintCollector(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *CollectorApplication) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *CollectorApplication) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintCollector(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - return i, nil -} - -func (m *AddApplicationCollectorReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *AddApplicationCollectorReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintCollector(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - if len(m.AppAccessKey) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintCollector(data, i, uint64(len(m.AppAccessKey))) - i += copy(data[i:], m.AppAccessKey) - } - return i, nil -} - -func (m *AddApplicationCollectorRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *AddApplicationCollectorRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *RemoveApplicationCollectorReq) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *RemoveApplicationCollectorReq) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEUI) > 0 { - data[i] = 0xa - i++ - i = encodeVarintCollector(data, i, uint64(len(m.AppEUI))) - i += copy(data[i:], m.AppEUI) - } - return i, nil -} - -func (m *RemoveApplicationCollectorRes) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *RemoveApplicationCollectorRes) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func encodeFixed64Collector(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Collector(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintCollector(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ - } - data[offset] = uint8(v) - return offset + 1 -} -func (m *GetApplicationsCollectorReq) Size() (n int) { - var l int - _ = l - return n -} - -func (m *GetApplicationsCollectorRes) Size() (n int) { - var l int - _ = l - if len(m.Applications) > 0 { - for _, e := range m.Applications { - l = e.Size() - n += 1 + l + sovCollector(uint64(l)) - } - } - return n -} - -func (m *CollectorApplication) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovCollector(uint64(l)) - } - return n -} - -func (m *AddApplicationCollectorReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovCollector(uint64(l)) - } - l = len(m.AppAccessKey) - if l > 0 { - n += 1 + l + sovCollector(uint64(l)) - } - return n -} - -func (m *AddApplicationCollectorRes) Size() (n int) { - var l int - _ = l - return n -} - -func (m *RemoveApplicationCollectorReq) Size() (n int) { - var l int - _ = l - l = len(m.AppEUI) - if l > 0 { - n += 1 + l + sovCollector(uint64(l)) - } - return n -} - -func (m *RemoveApplicationCollectorRes) Size() (n int) { - var l int - _ = l - return n -} - -func sovCollector(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } - } - return n -} -func sozCollector(x uint64) (n int) { - return sovCollector(uint64((x << 1) ^ uint64((int64(x) >> 63)))) -} -func (m *GetApplicationsCollectorReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetApplicationsCollectorReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetApplicationsCollectorReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GetApplicationsCollectorRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GetApplicationsCollectorRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GetApplicationsCollectorRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Applications", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthCollector - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Applications = append(m.Applications, &CollectorApplication{}) - if err := m.Applications[len(m.Applications)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *CollectorApplication) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: CollectorApplication: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: CollectorApplication: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthCollector - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AddApplicationCollectorReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AddApplicationCollectorReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AddApplicationCollectorReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthCollector - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppAccessKey", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthCollector - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppAccessKey = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *AddApplicationCollectorRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: AddApplicationCollectorRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: AddApplicationCollectorRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RemoveApplicationCollectorReq) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RemoveApplicationCollectorReq: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RemoveApplicationCollectorReq: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEUI", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthCollector - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEUI = append(m.AppEUI[:0], data[iNdEx:postIndex]...) - if m.AppEUI == nil { - m.AppEUI = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RemoveApplicationCollectorRes) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCollector - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RemoveApplicationCollectorRes: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RemoveApplicationCollectorRes: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipCollector(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthCollector - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func skipCollector(data []byte) (n int, err error) { - l := len(data) - iNdEx := 0 - for iNdEx < l { - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCollector - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - wireType := int(wire & 0x7) - switch wireType { - case 0: - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCollector - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - iNdEx++ - if data[iNdEx-1] < 0x80 { - break - } - } - return iNdEx, nil - case 1: - iNdEx += 8 - return iNdEx, nil - case 2: - var length int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCollector - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - length |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - iNdEx += length - if length < 0 { - return 0, ErrInvalidLengthCollector - } - return iNdEx, nil - case 3: - for { - var innerWire uint64 - var start int = iNdEx - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return 0, ErrIntOverflowCollector - } - if iNdEx >= l { - return 0, io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - innerWire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - innerWireType := int(innerWire & 0x7) - if innerWireType == 4 { - break - } - next, err := skipCollector(data[start:]) - if err != nil { - return 0, err - } - iNdEx = start + next - } - return iNdEx, nil - case 4: - return iNdEx, nil - case 5: - iNdEx += 4 - return iNdEx, nil - default: - return 0, fmt.Errorf("proto: illegal wireType %d", wireType) - } - } - panic("unreachable") -} - -var ( - ErrInvalidLengthCollector = fmt.Errorf("proto: negative length found during unmarshaling") - ErrIntOverflowCollector = fmt.Errorf("proto: integer overflow") -) - -var fileDescriptorCollector = []byte{ - // 276 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4f, 0xce, 0xcf, 0xc9, - 0x49, 0x4d, 0x2e, 0xc9, 0x2f, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x49, 0xce, 0x2f, - 0x4a, 0x55, 0x92, 0xe5, 0x92, 0x76, 0x4f, 0x2d, 0x71, 0x2c, 0x28, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, - 0xc9, 0xcc, 0xcf, 0x2b, 0x76, 0x86, 0xa9, 0x0b, 0x4a, 0x2d, 0x54, 0x8a, 0xc5, 0x27, 0x5d, 0x2c, - 0x64, 0xc7, 0xc5, 0x83, 0x2c, 0x27, 0xc1, 0xa8, 0xc0, 0xac, 0xc1, 0x6d, 0x24, 0xa5, 0x07, 0x32, - 0x5a, 0x0f, 0xae, 0x12, 0x49, 0x49, 0x10, 0x4f, 0x22, 0x92, 0x7a, 0x25, 0x3d, 0x2e, 0x11, 0x6c, - 0xaa, 0x84, 0xc4, 0xb8, 0xd8, 0x80, 0x5c, 0xd7, 0x50, 0x4f, 0xa0, 0x89, 0x8c, 0x1a, 0x3c, 0x41, - 0x6c, 0x89, 0x60, 0x9e, 0x52, 0x04, 0x97, 0x94, 0x63, 0x4a, 0x0a, 0x92, 0x4a, 0x64, 0xc7, 0xe2, - 0xd2, 0x25, 0xa4, 0x04, 0x76, 0xa5, 0x63, 0x72, 0x72, 0x6a, 0x71, 0xb1, 0x77, 0x6a, 0xa5, 0x04, - 0x13, 0x50, 0x96, 0x13, 0xec, 0x12, 0xb8, 0x98, 0x92, 0x0c, 0x1e, 0x93, 0x8b, 0x95, 0xcc, 0xb9, - 0x64, 0x83, 0x52, 0x73, 0xf3, 0xcb, 0x52, 0x49, 0xb4, 0x5a, 0x49, 0x1e, 0xbf, 0xc6, 0x62, 0xa3, - 0x05, 0x4c, 0x5c, 0x02, 0x70, 0x01, 0xdf, 0xc4, 0xbc, 0xc4, 0xf4, 0xd4, 0x22, 0xa1, 0x70, 0x2e, - 0x7e, 0xb4, 0x50, 0x17, 0x52, 0x84, 0x84, 0x29, 0x9e, 0xb8, 0x92, 0x22, 0xa8, 0xa4, 0x58, 0x28, - 0x84, 0x8b, 0x0f, 0xd5, 0x97, 0x42, 0x0a, 0x10, 0x4d, 0xb8, 0x43, 0x55, 0x8a, 0x90, 0x8a, 0x62, - 0xa1, 0x58, 0x2e, 0x41, 0x0c, 0x4f, 0x0a, 0x29, 0x43, 0xb4, 0xe1, 0x0d, 0x36, 0x29, 0x22, 0x14, - 0x15, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, - 0x0c, 0x49, 0x6c, 0xe0, 0x14, 0x6c, 0x0c, 0x08, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x8f, 0x82, 0xfe, - 0xd4, 0x02, 0x00, 0x00, -} diff --git a/core/components/collector/appStorage.go b/core/components/collector/appStorage.go deleted file mode 100644 index 4105f9915..000000000 --- a/core/components/collector/appStorage.go +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "fmt" - "time" - - "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -const ( - appsKey = "collector:apps" - appKey = "collector:app:%s" -) - -var ( - // ConnectRetries says how many times the client should retry a failed connection - ConnectRetries = 5 - // ConnectRetryDelay says how long the client should wait between retries - ConnectRetryDelay = time.Second -) - -// AppStorage provides storage for applications -type AppStorage interface { - Add(eui types.AppEUI) error - Remove(eui types.AppEUI) error - SetAccessKey(eui types.AppEUI, key string) error - GetAccessKey(eui types.AppEUI) (string, error) - List() ([]types.AppEUI, error) - Reset() error - Close() error -} - -type redisAppStorage struct { - client *redis.Client -} - -// ConnectRedis connects to Redis using the specified options -func ConnectRedis(addr string, db int64) (AppStorage, error) { - client := redis.NewClient(&redis.Options{ - Addr: addr, - DB: db, - }) - var err error - for retries := 0; retries < ConnectRetries; retries++ { - _, err = client.Ping().Result() - if err == nil { - break - } - <-time.After(ConnectRetryDelay) - } - if err != nil { - client.Close() - return nil, err - } - return &redisAppStorage{client}, nil -} - -func makeKey(eui types.AppEUI) string { - return fmt.Sprintf(appKey, eui.String()) -} - -func (s *redisAppStorage) Add(eui types.AppEUI) error { - return s.client.SAdd(appsKey, eui.String()).Err() -} - -func (s *redisAppStorage) Remove(eui types.AppEUI) error { - err := s.client.SRem(appsKey, eui.String()).Err() - if err != nil { - return err - } - s.client.Del(makeKey(eui)) - return nil -} - -func (s *redisAppStorage) SetAccessKey(eui types.AppEUI, key string) error { - return s.client.HSet(makeKey(eui), "key", key).Err() -} - -func (s *redisAppStorage) GetAccessKey(eui types.AppEUI) (string, error) { - m, err := s.client.HGetAllMap(makeKey(eui)).Result() - if err == redis.Nil { - return "", nil - } else if err != nil { - return "", err - } - return m["key"], nil -} - -func (s *redisAppStorage) List() ([]types.AppEUI, error) { - members, err := s.client.SMembers(appsKey).Result() - if err != nil { - return nil, err - } - euis := make([]types.AppEUI, len(members)) - for i, k := range members { - eui, err := types.ParseAppEUI(k) - if err != nil { - return nil, err - } - euis[i] = eui - } - return euis, nil -} - -func (s *redisAppStorage) Reset() error { - return s.client.FlushDb().Err() -} - -func (s *redisAppStorage) Close() error { - return s.client.Close() -} diff --git a/core/components/collector/appStorage_test.go b/core/components/collector/appStorage_test.go deleted file mode 100644 index ad22a9536..000000000 --- a/core/components/collector/appStorage_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/smartystreets/assertions" -) - -func createStorage() AppStorage { - storage, err := ConnectRedis("localhost:6379", 1) - if err != nil { - panic(err) - } - return storage -} - -func TestConnect(t *testing.T) { - a := New(t) - - c, err := ConnectRedis("localhost:6379", 1) - a.So(err, ShouldBeNil) - defer c.Close() - - _, err = ConnectRedis("", 1) - a.So(err, ShouldNotBeNil) -} - -func TestAccessSetKey(t *testing.T) { - a := New(t) - - eui, _ := types.ParseAppEUI("8000000000000001") - key := "key" - - storage := createStorage() - defer storage.Close() - defer storage.Reset() - - err := storage.SetAccessKey(eui, key) - a.So(err, ShouldBeNil) - - fetchedKey, err := storage.GetAccessKey(eui) - a.So(err, ShouldBeNil) - a.So(fetchedKey, ShouldEqual, key) -} - -func TestList(t *testing.T) { - a := New(t) - - eui1, _ := types.ParseAppEUI("8000000000000001") - eui2, _ := types.ParseAppEUI("8000000000000002") - - storage := createStorage() - defer storage.Close() - defer storage.Reset() - - err := storage.Add(eui1) - a.So(err, ShouldBeNil) - err = storage.Add(eui2) - a.So(err, ShouldBeNil) - apps, err := storage.List() - a.So(err, ShouldBeNil) - a.So(apps, ShouldHaveLength, 2) - - err = storage.Remove(eui1) - a.So(err, ShouldBeNil) - apps, err = storage.List() - a.So(err, ShouldBeNil) - a.So(apps, ShouldHaveLength, 1) -} diff --git a/core/components/collector/collector.go b/core/components/collector/collector.go deleted file mode 100644 index 12d3d3a9c..000000000 --- a/core/components/collector/collector.go +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "errors" - "fmt" - "net" - "strings" - - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/core/collection" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/apex/log" -) - -// Collector collects data from the Handler and stores it -type Collector interface { - Start() (map[types.AppEUI]collection.AppCollector, error) - Stop() - StopApp(eui types.AppEUI) error -} - -type collector struct { - ctx log.Interface - appStorage AppStorage - broker string - apps map[types.AppEUI]collection.AppCollector - dataStorage collection.DataStorage - netAddr string - server *grpc.Server -} - -// NewCollector creates a new collector -func NewCollector(ctx log.Interface, appStorage AppStorage, broker string, dataStorage collection.DataStorage, netAddr string) Collector { - c := &collector{ - ctx: ctx, - appStorage: appStorage, - broker: broker, - apps: map[types.AppEUI]collection.AppCollector{}, - dataStorage: dataStorage, - netAddr: netAddr, - server: grpc.NewServer(), - } - c.RegisterServer(c.server) - return c -} - -// StartError contains errors of starting applications -type StartError struct { - errors map[types.AppEUI]error -} - -func (e StartError) Error() string { - var s string - for eui, err := range e.errors { - s += fmt.Sprintf("%v: %v\n", eui, err) - } - return strings.TrimRight(s, "\n") -} - -func (c *collector) Start() (map[types.AppEUI]collection.AppCollector, error) { - // Start management service - lis, err := net.Listen("tcp", c.netAddr) - if err != nil { - c.ctx.WithError(err).Fatalf("Listen on %s failed", c.netAddr) - } - go c.server.Serve(lis) - - // Get applications - apps, err := c.appStorage.List() - if err != nil { - c.ctx.WithError(err).Error("Failed to get applications") - return nil, err - } - - // Start app collectors - startErrors := make(map[types.AppEUI]error) - for _, appEUI := range apps { - if err := c.startApp(appEUI); err != nil { - c.ctx.WithField("AppEUI", appEUI).WithError(err).Warn("Failed to start app collector; ignoring app") - startErrors[appEUI] = err - } - } - if len(startErrors) > 0 { - return c.apps, StartError{startErrors} - } - return c.apps, nil -} - -func (c *collector) Stop() { - c.server.Stop() - - for _, app := range c.apps { - app.Stop() - } - c.apps = nil -} - -func (c *collector) startApp(eui types.AppEUI) error { - ctx := c.ctx.WithField("AppEUI", eui) - - key, err := c.appStorage.GetAccessKey(eui) - if err != nil { - return err - } else if key == "" { - return errors.New("Not found") - } - - ac := collection.NewMqttAppCollector(ctx, c.broker, eui, key, c.dataStorage) - ctx.Info("Starting app collector") - if err := ac.Start(); err != nil { - return err - } - c.apps[eui] = ac - return nil -} - -func (c *collector) StopApp(eui types.AppEUI) error { - app, ok := c.apps[eui] - if !ok { - return errors.New("Not found") - } - app.Stop() - delete(c.apps, eui) - return nil -} diff --git a/core/components/collector/collectorManager.go b/core/components/collector/collectorManager.go deleted file mode 100644 index 4aeb0ef36..000000000 --- a/core/components/collector/collectorManager.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - "golang.org/x/net/context" - "google.golang.org/grpc" -) - -type collectorManagerServer struct { - collector *collector -} - -func (s *collectorManagerServer) GetApplications(ctx context.Context, req *core.GetApplicationsCollectorReq) (*core.GetApplicationsCollectorRes, error) { - res := new(core.GetApplicationsCollectorRes) - - apps, err := s.collector.appStorage.List() - if err != nil { - return res, err - } - - res.Applications = make([]*core.CollectorApplication, 0, len(apps)) - for _, eui := range apps { - res.Applications = append(res.Applications, &core.CollectorApplication{ - AppEUI: eui.Bytes(), - }) - } - - return res, nil -} - -func (s *collectorManagerServer) AddApplication(ctx context.Context, req *core.AddApplicationCollectorReq) (*core.AddApplicationCollectorRes, error) { - res := new(core.AddApplicationCollectorRes) - - var appEUI types.AppEUI - if err := appEUI.Unmarshal(req.AppEUI); err != nil { - return res, err - } - - if err := s.collector.appStorage.Add(appEUI); err != nil { - return res, err - } - if err := s.collector.appStorage.SetAccessKey(appEUI, req.AppAccessKey); err != nil { - return res, err - } - - s.collector.startApp(appEUI) - - return res, nil -} - -func (s *collectorManagerServer) RemoveApplication(ctx context.Context, req *core.RemoveApplicationCollectorReq) (*core.RemoveApplicationCollectorRes, error) { - res := new(core.RemoveApplicationCollectorRes) - - var appEUI types.AppEUI - if err := appEUI.Unmarshal(req.AppEUI); err != nil { - return res, err - } - - s.collector.StopApp(appEUI) - - if err := s.collector.appStorage.Remove(appEUI); err != nil { - return res, err - } - - return res, nil -} - -func (c *collector) RegisterServer(s *grpc.Server) { - srv := &collectorManagerServer{c} - core.RegisterCollectorManagerServer(s, srv) -} diff --git a/core/components/collector/collector_test.go b/core/components/collector/collector_test.go deleted file mode 100644 index fa63e8559..000000000 --- a/core/components/collector/collector_test.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package collector - -import ( - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/core/types" - ttntesting "github.com/TheThingsNetwork/ttn/utils/testing" - - . "github.com/smartystreets/assertions" -) - -type mockStorage struct { -} - -func TestCollection(t *testing.T) { - a := New(t) - - appStorage, err := ConnectRedis("localhost:6379", 1) - if err != nil { - panic(err) - } - defer appStorage.Close() - defer appStorage.Reset() - - eui, _ := types.ParseAppEUI("8000000000000001") - err = appStorage.Add(eui) - a.So(err, ShouldBeNil) - err = appStorage.SetAccessKey(eui, "secret") - a.So(err, ShouldBeNil) - - collector := NewCollector(ttntesting.GetLogger(t, "Collector"), appStorage, "localhost:1883", &mockStorage{}, "localhost:1783") - collectors, err := collector.Start() - defer collector.Stop() - a.So(err, ShouldBeNil) - a.So(collectors, ShouldHaveLength, 1) - - err = collector.StopApp(eui) - a.So(err, ShouldBeNil) - - err = collector.StopApp(eui) - a.So(err, ShouldNotBeNil) // Not found -} - -func (s *mockStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { - return nil -} - -func (s *mockStorage) Close() error { - return nil -} diff --git a/core/components/collector/influxdb/influxdb.go b/core/components/collector/influxdb/influxdb.go deleted file mode 100644 index 5dd9322a4..000000000 --- a/core/components/collector/influxdb/influxdb.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package influxdb - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core/collection" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/influxdata/influxdb/client/v2" -) - -type influxDBStorage struct { - client client.Client -} - -// NewDataStorage instantiates a new DataStorage for InfluxDB -func NewDataStorage(addr, username, password string) (collection.DataStorage, error) { - c, err := client.NewHTTPClient(client.HTTPConfig{ - Addr: addr, - Username: username, - Password: password, - }) - if err != nil { - return nil, err - } - return &influxDBStorage{c}, nil -} - -func (i *influxDBStorage) Save(appEUI types.AppEUI, devEUI types.DevEUI, t time.Time, fields map[string]interface{}) error { - bp, err := client.NewBatchPoints(client.BatchPointsConfig{ - Database: "packets", - Precision: "us", - }) - if err != nil { - return err - } - - tags := map[string]string{ - "devEUI": devEUI.String(), - } - p, err := client.NewPoint(appEUI.String(), tags, fields, t) - if err != nil { - return err - } - - bp.AddPoint(p) - return i.client.Write(bp) -} - -func (i *influxDBStorage) Close() error { - return i.client.Close() -} diff --git a/core/protos/collector.proto b/core/protos/collector.proto deleted file mode 100644 index 573fef82f..000000000 --- a/core/protos/collector.proto +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -syntax = "proto3"; - -package core; - -message GetApplicationsCollectorReq { -} - -message GetApplicationsCollectorRes { - repeated CollectorApplication Applications = 1; -} - -message CollectorApplication { - bytes AppEUI = 1; -} - -message AddApplicationCollectorReq { - bytes AppEUI = 1; - string AppAccessKey = 2; -} - -message AddApplicationCollectorRes { -} - -message RemoveApplicationCollectorReq { - bytes AppEUI = 1; -} - -message RemoveApplicationCollectorRes { -} - -service CollectorManager { - rpc GetApplications (GetApplicationsCollectorReq) returns (GetApplicationsCollectorRes); - rpc AddApplication (AddApplicationCollectorReq) returns (AddApplicationCollectorRes); - rpc RemoveApplication (RemoveApplicationCollectorReq) returns (RemoveApplicationCollectorRes); -} From 93767e7d299960ed7700d64b8dcd98af80953fac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 12 Jul 2016 16:52:35 +0200 Subject: [PATCH 1549/2266] Update cmd initialization with config logs --- cmd/broker.go | 9 +++++++-- cmd/discovery.go | 8 +++----- cmd/handler.go | 8 ++++++-- cmd/networkserver.go | 8 +++++--- cmd/root.go | 8 +++++++- cmd/router.go | 5 +++-- cmd/version.go | 4 ++-- 7 files changed, 33 insertions(+), 17 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 681ee9738..1eb77a67b 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -32,8 +32,13 @@ var brokerCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), - }).Info("Using Configuration") + "Server": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address"), viper.GetInt("broker.server-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port")), + "NetworkServer": viper.GetString("broker.networkserver-address"), + "DeduplicationDelay": viper.GetString("broker.deduplication-delay"), + "Prefixes": viper.GetStringSlice("broker.prefix"), + "Database": fmt.Sprintf("%s/%d", viper.GetString("broker.redis-address"), viper.GetInt("broker.redis-db")), + }).Info("Initializing Broker") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") diff --git a/cmd/discovery.go b/cmd/discovery.go index b131df854..53d28437d 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -28,9 +28,9 @@ var discoveryCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "server": fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port")), - "database": fmt.Sprintf("%s/%d", viper.GetString("discovery.redis-address"), viper.GetInt("discovery.redis-db")), - }).Info("Using Configuration") + "Server": fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("discovery.redis-address"), viper.GetInt("discovery.redis-db")), + }).Info("Initializing Discovery") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") @@ -80,9 +80,7 @@ func init() { viper.BindPFlag("discovery.redis-db", discoveryCmd.Flags().Lookup("redis-db")) discoveryCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") - discoveryCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") discoveryCmd.Flags().Int("server-port", 1900, "The port for communication") viper.BindPFlag("discovery.server-address", discoveryCmd.Flags().Lookup("server-address")) - viper.BindPFlag("discovery.server-address-announce", discoveryCmd.Flags().Lookup("server-address-announce")) viper.BindPFlag("discovery.server-port", discoveryCmd.Flags().Lookup("server-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index 0037c4f18..0185195f4 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -27,8 +27,12 @@ var handlerCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), - }).Info("Using Configuration") + "Server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("handler.redis-address"), viper.GetInt("handler.redis-db")), + "TTNbroker": viper.GetString("handler.ttn-broker"), + "MQTTbroker": viper.GetString("handler.mqtt-broker"), + }).Info("Initializing Handler") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") diff --git a/cmd/networkserver.go b/cmd/networkserver.go index 995e62d40..a42422f77 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -29,9 +29,11 @@ var networkserverCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "server": fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port")), - "database": fmt.Sprintf("%s/%d", viper.GetString("networkserver.redis-address"), viper.GetInt("networkserver.redis-db")), - }).Info("Using Configuration") + "Server": fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("networkserver.redis-address"), viper.GetInt("networkserver.redis-db")), + "NetID": viper.GetString("networkserver.net-id"), + "Prefix": viper.GetString("networkserver.prefix"), + }).Info("Initializing Network Server") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") diff --git a/cmd/root.go b/cmd/root.go index 45a1828ef..8e26d9e3f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -38,6 +38,12 @@ var RootCmd = &cobra.Command{ Level: logLevel, Handler: cliHandler.New(os.Stdout), } + ctx.WithFields(log.Fields{ + "ComponentID": viper.GetString("id"), + "Description": viper.GetString("description"), + "DiscoveryServer": viper.GetString("discovery-server"), + "AuthServer": viper.GetString("auth-server"), + }).Info("Initializing The Things Network") }, } @@ -72,7 +78,7 @@ func init() { RootCmd.PersistentFlags().String("description", "", "The description of this component") viper.BindPFlag("description", RootCmd.PersistentFlags().Lookup("description")) - RootCmd.PersistentFlags().String("discovery-server", "localhost:1900", "The address of the Discovery server") + RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") diff --git a/cmd/router.go b/cmd/router.go index 130e97b67..a9ffb15b1 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -26,8 +26,9 @@ var routerCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "server": fmt.Sprintf("%s:%d", viper.GetString("router.server-address"), viper.GetInt("router.server-port")), - }).Info("Using Configuration") + "Server": fmt.Sprintf("%s:%d", viper.GetString("router.server-address"), viper.GetInt("router.server-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port")), + }).Info("Initializing Router") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") diff --git a/cmd/version.go b/cmd/version.go index 9a35129a5..64487342b 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -16,8 +16,8 @@ var versionCmd = &cobra.Command{ Long: `ttn version gets the build and version information of ttn`, Run: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "commit": viper.GetString("gitCommit"), - "build date": viper.GetString("buildDate"), + "Commit": viper.GetString("gitCommit"), + "BuildDate": viper.GetString("buildDate"), }).Infof("You are running %s of ttn.", viper.GetString("version")) }, } From 7b9a1ad789e202e8f9a98ed5e37b82669345156a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 12 Jul 2016 17:11:36 +0200 Subject: [PATCH 1550/2266] Optimize number of MIC checks by sorting on FCnt --- core/broker/uplink.go | 19 ++++++++++++++++--- core/broker/uplink_test.go | 8 ++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index b0e82ee7f..f7d871da5 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -82,13 +82,18 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) + // Sort by FCntUp to optimize the number of MIC checks + sort.Sort(ByFCntUp(getDevicesResp.Results)) + // Find AppEUI/DevEUI through MIC check var device *pb_lorawan.Device + var micChecks int for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) if candidate.Uses32BitFCnt { macPayload.FHDR.FCnt = fcnt.GetFull(macPayload.FHDR.FCnt, uint16(candidate.FCntUp)) } + micChecks++ ok, err = phyPayload.ValidateMIC(nwkSKey) if err != nil { return err @@ -103,9 +108,10 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } ctx = ctx.WithFields(log.Fields{ - "DevEUI": device.DevEui, - "AppEUI": device.AppEui, - "AppID": device.AppId, + "MICChecks": micChecks, + "DevEUI": device.DevEui, + "AppEUI": device.AppEui, + "AppID": device.AppId, }) if device.DisableFCntCheck { @@ -198,3 +204,10 @@ func selectBestDownlink(options []*pb.DownlinkOption) *pb.DownlinkOption { sort.Sort(ByScore(options)) return options[0] } + +// ByFCntUp implements sort.Interface for []*pb_lorawan.Device based on FCnt +type ByFCntUp []*pb_lorawan.Device + +func (a ByFCntUp) Len() int { return len(a) } +func (a ByFCntUp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByFCntUp) Less(i, j int) bool { return a[i].FCntUp < a[j].FCntUp } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 40ce4e54c..6b9d39682 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -68,6 +68,7 @@ func TestHandleUplink(t *testing.T) { a.So(err, ShouldEqual, ErrNotFound) devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9} appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} appID := "AppID-1" nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} @@ -81,6 +82,13 @@ func TestHandleUplink(t *testing.T) { uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), ns: &mockNetworkServer{ devices: []*pb_lorawan.Device{ + &pb_lorawan.Device{ + DevEui: &wrongDevEUI, + AppEui: &appEUI, + AppId: appID, + NwkSKey: &nwkSKey, + FCntUp: 4, + }, &pb_lorawan.Device{ DevEui: &devEUI, AppEui: &appEUI, From d979f223d298d68a125718a96f28aa55e2d49ac4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 12 Jul 2016 17:12:41 +0200 Subject: [PATCH 1551/2266] Log uplink metadata in Router --- core/router/uplink.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/router/uplink.go b/core/router/uplink.go index d2f88204e..c902e2073 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -8,6 +8,7 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" @@ -51,6 +52,23 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess return err } + if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { + ctx = ctx.WithField("Modulation", lorawan.Modulation.String()) + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + ctx = ctx.WithField("DataRate", lorawan.DataRate) + } else { + ctx = ctx.WithField("BitRate", lorawan.BitRate) + } + } + + if gateway := uplink.GatewayMetadata; gateway != nil { + ctx = ctx.WithFields(log.Fields{ + "Frequency": gateway.Frequency, + "RSSI": gateway.Rssi, + "SNR": gateway.Snr, + }) + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { return errors.New("Uplink message does not contain a MAC payload.") From 05838af511af77de6c0e5e157a149c96f8577047 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jul 2016 16:30:45 +0200 Subject: [PATCH 1552/2266] Add validation to gRPC calls --- api/api.go | 5 + api/broker/broker.pb.go | 622 ++------------ api/broker/broker.proto | 18 - api/broker/validation.go | 111 +++ api/discovery/validation.go | 1 + api/gateway/gateway.pb.go | 139 +-- api/gateway/gateway.proto | 2 - api/gateway/validation.go | 19 + api/handler/validation.go | 31 + api/networkserver/validation.go | 9 + api/protocol/lorawan/device.pb.go | 5 - api/protocol/lorawan/lorawan.pb.go | 1191 +------------------------- api/protocol/lorawan/lorawan.proto | 32 - api/protocol/lorawan/validation.go | 82 ++ api/protocol/protocol.pb.go | 287 +------ api/protocol/protocol.proto | 7 - api/protocol/validation.go | 34 + api/router/router.pb.go | 628 ++------------ api/router/router.proto | 19 - api/router/validation.go | 40 + core/broker/manager_server.go | 3 + core/broker/server.go | 9 + core/broker/server_test.go | 8 +- core/handler/manager_server.go | 17 +- core/handler/server.go | 6 + core/networkserver/manager_server.go | 4 +- core/networkserver/server.go | 18 + core/router/server.go | 12 + core/router/uplink_test.go | 11 +- 29 files changed, 630 insertions(+), 2740 deletions(-) create mode 100644 api/broker/validation.go create mode 100644 api/discovery/validation.go create mode 100644 api/gateway/validation.go create mode 100644 api/handler/validation.go create mode 100644 api/networkserver/validation.go create mode 100644 api/protocol/lorawan/validation.go create mode 100644 api/protocol/validation.go create mode 100644 api/router/validation.go diff --git a/api/api.go b/api/api.go index f52d101ec..a1ed4db04 100644 --- a/api/api.go +++ b/api/api.go @@ -10,6 +10,11 @@ import ( "google.golang.org/grpc" ) +// Validator interface is used to validate protos +type Validator interface { + Validate() bool +} + // Backoff indicates how long a client should wait between failed requests var Backoff = 1 * time.Second diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 944a14f62..9c525c7a5 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -17,10 +17,6 @@ DeviceActivationRequest DeduplicatedDeviceActivationRequest SubscribeRequest - ApplicationsRequest - ApplicationsResponse - RegisterApplicationRequest - UnregisterApplicationRequest StatusRequest Status ApplicationHandlerRegistration @@ -295,49 +291,6 @@ func (m *SubscribeRequest) String() string { return proto.CompactText func (*SubscribeRequest) ProtoMessage() {} func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{7} } -type ApplicationsRequest struct { -} - -func (m *ApplicationsRequest) Reset() { *m = ApplicationsRequest{} } -func (m *ApplicationsRequest) String() string { return proto.CompactTextString(m) } -func (*ApplicationsRequest) ProtoMessage() {} -func (*ApplicationsRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{8} } - -type ApplicationsResponse struct { - AppEuis []string `protobuf:"bytes,1,rep,name=app_euis,json=appEuis" json:"app_euis,omitempty"` -} - -func (m *ApplicationsResponse) Reset() { *m = ApplicationsResponse{} } -func (m *ApplicationsResponse) String() string { return proto.CompactTextString(m) } -func (*ApplicationsResponse) ProtoMessage() {} -func (*ApplicationsResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{9} } - -// message RegisterApplicationRequest is used to register an application at this -// Broker -type RegisterApplicationRequest struct { - AppEui string `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` -} - -func (m *RegisterApplicationRequest) Reset() { *m = RegisterApplicationRequest{} } -func (m *RegisterApplicationRequest) String() string { return proto.CompactTextString(m) } -func (*RegisterApplicationRequest) ProtoMessage() {} -func (*RegisterApplicationRequest) Descriptor() ([]byte, []int) { - return fileDescriptorBroker, []int{10} -} - -// message UnregisterApplicationRequest is used to unregister an application at -// this Broker -type UnregisterApplicationRequest struct { - AppEui string `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3" json:"app_eui,omitempty"` -} - -func (m *UnregisterApplicationRequest) Reset() { *m = UnregisterApplicationRequest{} } -func (m *UnregisterApplicationRequest) String() string { return proto.CompactTextString(m) } -func (*UnregisterApplicationRequest) ProtoMessage() {} -func (*UnregisterApplicationRequest) Descriptor() ([]byte, []int) { - return fileDescriptorBroker, []int{11} -} - // message StatusRequest is used to request the status of this Broker type StatusRequest struct { } @@ -345,7 +298,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{12} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{8} } // message Status is the response to the StatusRequest type Status struct { @@ -368,7 +321,7 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{13} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{9} } func (m *Status) GetUplink() *api.Rates { if m != nil { @@ -428,7 +381,7 @@ func (m *ApplicationHandlerRegistration) Reset() { *m = ApplicationHandl func (m *ApplicationHandlerRegistration) String() string { return proto.CompactTextString(m) } func (*ApplicationHandlerRegistration) ProtoMessage() {} func (*ApplicationHandlerRegistration) Descriptor() ([]byte, []int) { - return fileDescriptorBroker, []int{14} + return fileDescriptorBroker, []int{10} } func init() { @@ -440,10 +393,6 @@ func init() { proto.RegisterType((*DeviceActivationRequest)(nil), "broker.DeviceActivationRequest") proto.RegisterType((*DeduplicatedDeviceActivationRequest)(nil), "broker.DeduplicatedDeviceActivationRequest") proto.RegisterType((*SubscribeRequest)(nil), "broker.SubscribeRequest") - proto.RegisterType((*ApplicationsRequest)(nil), "broker.ApplicationsRequest") - proto.RegisterType((*ApplicationsResponse)(nil), "broker.ApplicationsResponse") - proto.RegisterType((*RegisterApplicationRequest)(nil), "broker.RegisterApplicationRequest") - proto.RegisterType((*UnregisterApplicationRequest)(nil), "broker.UnregisterApplicationRequest") proto.RegisterType((*StatusRequest)(nil), "broker.StatusRequest") proto.RegisterType((*Status)(nil), "broker.Status") proto.RegisterType((*ApplicationHandlerRegistration)(nil), "broker.ApplicationHandlerRegistration") @@ -1373,105 +1322,6 @@ func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ApplicationsRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ApplicationsRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *ApplicationsResponse) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *ApplicationsResponse) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEuis) > 0 { - for _, s := range m.AppEuis { - data[i] = 0xa - i++ - l = len(s) - for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - data[i] = uint8(l) - i++ - i += copy(data[i:], s) - } - } - return i, nil -} - -func (m *RegisterApplicationRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *RegisterApplicationRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEui) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) - } - return i, nil -} - -func (m *UnregisterApplicationRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UnregisterApplicationRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.AppEui) > 0 { - data[i] = 0xa - i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppEui))) - i += copy(data[i:], m.AppEui) - } - return i, nil -} - func (m *StatusRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -1877,44 +1727,6 @@ func (m *SubscribeRequest) Size() (n int) { return n } -func (m *ApplicationsRequest) Size() (n int) { - var l int - _ = l - return n -} - -func (m *ApplicationsResponse) Size() (n int) { - var l int - _ = l - if len(m.AppEuis) > 0 { - for _, s := range m.AppEuis { - l = len(s) - n += 1 + l + sovBroker(uint64(l)) - } - } - return n -} - -func (m *RegisterApplicationRequest) Size() (n int) { - var l int - _ = l - l = len(m.AppEui) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - return n -} - -func (m *UnregisterApplicationRequest) Size() (n int) { - var l int - _ = l - l = len(m.AppEui) - if l > 0 { - n += 1 + l + sovBroker(uint64(l)) - } - return n -} - func (m *StatusRequest) Size() (n int) { var l int _ = l @@ -3675,293 +3487,6 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { } return nil } -func (m *ApplicationsRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ApplicationsRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ApplicationsRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ApplicationsResponse) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: ApplicationsResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ApplicationsResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEuis", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEuis = append(m.AppEuis, string(data[iNdEx:postIndex])) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RegisterApplicationRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RegisterApplicationRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RegisterApplicationRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEui = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UnregisterApplicationRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UnregisterApplicationRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UnregisterApplicationRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowBroker - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthBroker - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppEui = string(data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthBroker - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *StatusRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -4545,76 +4070,73 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1135 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4d, 0x6f, 0x1b, 0x45, - 0x18, 0xc6, 0x76, 0xe3, 0xc4, 0xaf, 0xe3, 0x8f, 0x4c, 0x3e, 0xbc, 0xb1, 0x4a, 0x12, 0x16, 0xa9, - 0x0a, 0x1f, 0xb5, 0xa9, 0x51, 0xa8, 0x22, 0x04, 0x95, 0x43, 0xaa, 0x12, 0x24, 0x97, 0x6a, 0xeb, - 0x70, 0xe0, 0x62, 0xad, 0x77, 0x27, 0xf6, 0x28, 0xce, 0xee, 0xb2, 0x33, 0x9b, 0x34, 0xff, 0x01, - 0x89, 0x03, 0x1c, 0xf8, 0x29, 0x5c, 0x11, 0x17, 0x8e, 0x9c, 0x38, 0x70, 0x40, 0x08, 0x2e, 0xfc, - 0x0c, 0x66, 0x67, 0x66, 0xbf, 0x9c, 0xb8, 0x4d, 0x4b, 0x91, 0x20, 0xe2, 0x60, 0x79, 0xe7, 0xfd, - 0x78, 0x76, 0xfc, 0xbc, 0xcf, 0xfb, 0xce, 0x18, 0xee, 0x8e, 0x08, 0x1b, 0x07, 0xc3, 0x96, 0xe5, - 0x9e, 0xb4, 0xfb, 0x63, 0xdc, 0x1f, 0x13, 0x67, 0x44, 0x1f, 0x62, 0x76, 0xe6, 0xfa, 0xc7, 0x6d, - 0xc6, 0x9c, 0xb6, 0xe9, 0x91, 0xf6, 0xd0, 0x77, 0x8f, 0xb1, 0xaf, 0xbe, 0x5a, 0x9e, 0xef, 0x32, - 0x17, 0x15, 0xe5, 0xaa, 0x79, 0x3b, 0x05, 0x30, 0x72, 0x47, 0x6e, 0x5b, 0xb8, 0x87, 0xc1, 0x91, - 0x58, 0x89, 0x85, 0x78, 0x92, 0x69, 0x99, 0xf0, 0x99, 0xef, 0xe3, 0x1f, 0x15, 0xfe, 0xfe, 0x55, - 0xc2, 0x45, 0xa8, 0xe5, 0x4e, 0xe2, 0x07, 0x95, 0xbc, 0x7b, 0x95, 0xe4, 0x91, 0xc9, 0xf0, 0x99, - 0x79, 0x1e, 0x7d, 0xcb, 0x54, 0xfd, 0x87, 0x3c, 0x54, 0xf7, 0xdd, 0x33, 0x67, 0x42, 0x9c, 0xe3, - 0x4f, 0x3d, 0x46, 0x5c, 0x07, 0x6d, 0x00, 0x10, 0x1b, 0x3b, 0x8c, 0x1c, 0x11, 0xec, 0x6b, 0xb9, - 0xad, 0xdc, 0x76, 0xc9, 0x48, 0x59, 0xd0, 0xe7, 0x50, 0x56, 0x18, 0x03, 0x1c, 0x10, 0x2d, 0xcf, - 0x03, 0x16, 0xf7, 0x76, 0x7f, 0xf9, 0x75, 0x73, 0xe7, 0x59, 0xdb, 0xb0, 0x5c, 0x1f, 0xb7, 0xd9, - 0xb9, 0x87, 0x69, 0xeb, 0x81, 0x44, 0xb8, 0x7f, 0x78, 0x60, 0x80, 0x42, 0xbb, 0x1f, 0x10, 0xb4, - 0x02, 0x73, 0x34, 0x8c, 0xd2, 0x0a, 0x1c, 0xb5, 0x62, 0xc8, 0x05, 0x6a, 0xc2, 0x82, 0x8d, 0x4d, - 0x9b, 0xef, 0x11, 0x6b, 0x37, 0xb8, 0xa3, 0x60, 0xc4, 0x6b, 0xb4, 0x07, 0xb5, 0x88, 0x8d, 0x81, - 0xe5, 0x3a, 0x47, 0x64, 0xa4, 0xcd, 0xf1, 0x90, 0x72, 0x67, 0xbd, 0x15, 0xb3, 0xd4, 0x7f, 0xf2, - 0x91, 0xf0, 0x04, 0xbe, 0x19, 0xfe, 0x42, 0xa3, 0x1a, 0x79, 0xa4, 0x19, 0xdd, 0x83, 0x6a, 0xf4, - 0x8b, 0x14, 0x44, 0x51, 0x40, 0x68, 0xad, 0x88, 0xac, 0x69, 0x84, 0x8a, 0x72, 0x48, 0xab, 0xfe, - 0x55, 0x01, 0x2a, 0x87, 0x5e, 0xc8, 0x61, 0x0f, 0x53, 0x6a, 0x8e, 0x30, 0xd2, 0x60, 0xde, 0x33, - 0xcf, 0x27, 0xae, 0x69, 0x0b, 0x06, 0x17, 0x8d, 0x68, 0x89, 0x1e, 0xc2, 0xbc, 0x8d, 0x4f, 0x05, - 0x75, 0x65, 0x41, 0xdd, 0x0e, 0xa7, 0xee, 0xce, 0x73, 0x50, 0xb7, 0x8f, 0x4f, 0x43, 0xda, 0x8a, - 0x1c, 0x25, 0xa4, 0x8c, 0xe3, 0x99, 0x9e, 0x27, 0xf0, 0x16, 0x5f, 0x08, 0xaf, 0xeb, 0x79, 0x02, - 0x8f, 0xa3, 0x84, 0x78, 0x5d, 0x58, 0x8a, 0x09, 0x3d, 0xc1, 0xcc, 0xb4, 0x4d, 0x66, 0x6a, 0xab, - 0x82, 0x8f, 0x95, 0x84, 0x52, 0xe3, 0x49, 0x4f, 0xf9, 0x8c, 0x7a, 0x64, 0x8c, 0x2c, 0xe8, 0x43, - 0xa8, 0x47, 0x7c, 0xc6, 0x08, 0x6b, 0x02, 0x61, 0x39, 0x66, 0x34, 0x05, 0x50, 0x53, 0xb6, 0x38, - 0xbf, 0x0b, 0x75, 0x5b, 0x69, 0x72, 0xe0, 0x0a, 0x51, 0x52, 0x6d, 0x73, 0xab, 0xc0, 0xf3, 0xd7, - 0x5a, 0xaa, 0x37, 0xb3, 0x9a, 0x35, 0x6a, 0x76, 0x66, 0x4d, 0xf5, 0x2f, 0xf3, 0x50, 0x8b, 0x62, - 0xfe, 0xfb, 0x35, 0xb9, 0x07, 0xb5, 0x29, 0x42, 0x54, 0x45, 0x66, 0xf1, 0x51, 0xcd, 0xf2, 0xa1, - 0x07, 0xa0, 0xf1, 0x2d, 0x12, 0x0b, 0x77, 0x2d, 0x46, 0x4e, 0xa5, 0x86, 0x31, 0xf5, 0x38, 0x53, - 0x4f, 0xa3, 0xe5, 0x92, 0xd7, 0x96, 0x9f, 0xeb, 0xb5, 0x3f, 0x17, 0x60, 0x7d, 0x1f, 0xdb, 0x01, - 0x6f, 0x0d, 0x8b, 0xd7, 0xd8, 0xbe, 0x2e, 0x3d, 0xb2, 0x0a, 0xe1, 0xd3, 0x80, 0xd8, 0x5a, 0x45, - 0x8c, 0xc7, 0x39, 0xbe, 0x3a, 0xb0, 0xff, 0xb9, 0xd6, 0x29, 0x5c, 0xb9, 0x75, 0x36, 0xa1, 0x4c, - 0xb1, 0x7f, 0x8a, 0xfd, 0x01, 0x23, 0x27, 0x58, 0x6b, 0x88, 0x69, 0x09, 0xd2, 0xd4, 0xe7, 0x16, - 0xb4, 0x0f, 0x4b, 0xbe, 0xaa, 0xfc, 0x80, 0xe1, 0x13, 0x6f, 0xc2, 0x01, 0x78, 0x73, 0x85, 0x7b, - 0x6c, 0x4c, 0x57, 0x55, 0x15, 0xca, 0xa8, 0x47, 0x19, 0x7d, 0x95, 0xa0, 0xff, 0x59, 0x80, 0xc6, - 0x45, 0x41, 0x7d, 0x11, 0x60, 0xca, 0xfe, 0x1f, 0x7d, 0x7f, 0x67, 0xf4, 0xf5, 0x60, 0xd9, 0x8c, - 0x19, 0x4d, 0x20, 0x1a, 0x02, 0xe2, 0x66, 0xb2, 0x89, 0x84, 0xf6, 0x18, 0x0b, 0x99, 0x17, 0x6c, - 0x2f, 0x63, 0x92, 0x7e, 0x77, 0x03, 0x5e, 0x4f, 0xf7, 0xf0, 0xf5, 0x2b, 0xfb, 0xbf, 0xb7, 0x9b, - 0x5f, 0xb2, 0x1a, 0xa6, 0x86, 0x83, 0x76, 0x61, 0x38, 0xf4, 0x66, 0x0f, 0x87, 0xad, 0x58, 0x2f, - 0x33, 0xce, 0x91, 0x4b, 0xa6, 0x04, 0x82, 0xfa, 0xe3, 0x60, 0x48, 0x2d, 0x9f, 0x0c, 0xb1, 0x92, - 0x89, 0xbe, 0x0a, 0xcb, 0x9c, 0x7e, 0xa1, 0xa5, 0x50, 0x5e, 0x91, 0xf9, 0x0e, 0xac, 0x64, 0xcd, - 0xea, 0x70, 0x5a, 0x87, 0x05, 0x55, 0x6b, 0xca, 0x65, 0x55, 0xe0, 0xd5, 0x99, 0x97, 0x55, 0xa3, - 0xfa, 0x0e, 0x34, 0x0d, 0x3c, 0x22, 0x94, 0x61, 0x3f, 0x95, 0x1a, 0xc9, 0xb1, 0x91, 0x88, 0x44, - 0x5e, 0x61, 0x55, 0xb5, 0xf5, 0xbb, 0x70, 0xf3, 0xd0, 0xf1, 0x5f, 0x20, 0xb1, 0x06, 0x95, 0xc7, - 0xcc, 0x64, 0x41, 0xbc, 0xe7, 0xef, 0x0b, 0x50, 0x94, 0x16, 0xa4, 0x43, 0x31, 0x10, 0x67, 0x9b, - 0xc8, 0x29, 0x77, 0xa0, 0x15, 0x5e, 0xed, 0x0d, 0x4e, 0x02, 0x35, 0x94, 0x07, 0xb5, 0xa1, 0x22, - 0x9f, 0x06, 0x81, 0x43, 0x38, 0x82, 0xb8, 0x39, 0x67, 0x43, 0x17, 0x65, 0xc0, 0xa1, 0xf0, 0xa3, - 0x5b, 0xfc, 0xda, 0xab, 0x9a, 0x51, 0x9d, 0xbb, 0xe9, 0xd8, 0xd8, 0x87, 0xde, 0x86, 0x72, 0x52, - 0x6c, 0xaa, 0x24, 0x9a, 0x0e, 0x4d, 0xbb, 0xd1, 0x2e, 0xa4, 0xa4, 0x41, 0xa3, 0xbd, 0xac, 0x5d, - 0x48, 0x5a, 0x4a, 0x45, 0xa9, 0x0d, 0x7d, 0x00, 0x2b, 0xe9, 0x54, 0xd3, 0xb2, 0xb0, 0xc7, 0x27, - 0x82, 0xd2, 0x63, 0x3a, 0x39, 0x25, 0x5b, 0xda, 0x55, 0x61, 0xe8, 0x3d, 0xa8, 0xd8, 0xf1, 0x20, - 0x09, 0x2f, 0x13, 0x52, 0x59, 0x75, 0x91, 0xf7, 0x08, 0xfb, 0x56, 0xf8, 0x17, 0x63, 0xc2, 0xb3, - 0xb3, 0x61, 0xe8, 0x2d, 0x58, 0xe2, 0xd7, 0x72, 0x07, 0x5b, 0x1c, 0x64, 0xe0, 0xbb, 0x01, 0xaf, - 0x1b, 0xd5, 0xde, 0x10, 0x7f, 0x10, 0xea, 0xb1, 0xc3, 0x90, 0x76, 0x74, 0x1b, 0x50, 0x12, 0x3c, - 0x36, 0x1d, 0x7b, 0x12, 0x46, 0xbf, 0x29, 0xa2, 0x13, 0x98, 0x8f, 0x95, 0x43, 0xff, 0x0c, 0x36, - 0x52, 0x1a, 0x50, 0x66, 0x29, 0x2b, 0x79, 0xd5, 0x4f, 0x4d, 0x87, 0x5c, 0x7a, 0x3a, 0xbc, 0x0a, - 0xa0, 0xd0, 0x43, 0x57, 0x5e, 0xb8, 0x4a, 0xca, 0x72, 0x60, 0x77, 0xbe, 0xc9, 0x43, 0x71, 0x4f, - 0x34, 0x0c, 0xbf, 0x45, 0x95, 0xba, 0x94, 0xba, 0x16, 0xe1, 0xcc, 0xa0, 0xd5, 0xa8, 0x8d, 0x32, - 0x57, 0xa1, 0xe6, 0xac, 0xa3, 0x77, 0x3b, 0xf7, 0x4e, 0x0e, 0x7d, 0x02, 0xa5, 0xb8, 0x8d, 0x90, - 0x16, 0x45, 0x4e, 0x77, 0x56, 0xf3, 0xb5, 0xa4, 0x43, 0x67, 0xdc, 0xb8, 0x38, 0x56, 0x0b, 0xe6, - 0x1f, 0x05, 0xc3, 0x09, 0xa1, 0x63, 0x34, 0xeb, 0x9d, 0xcd, 0x05, 0x51, 0x90, 0xae, 0x75, 0xbc, - 0x9d, 0xe3, 0x13, 0x61, 0x41, 0xb5, 0x3a, 0x46, 0x9b, 0xb3, 0x47, 0x80, 0xdc, 0xc1, 0x33, 0x67, - 0x44, 0xe7, 0xeb, 0x1c, 0x54, 0x24, 0x2d, 0x3d, 0xd3, 0xe1, 0xef, 0xf2, 0xf9, 0x30, 0xbf, 0xac, - 0x8b, 0x55, 0x21, 0xd0, 0xad, 0x08, 0xf1, 0xe9, 0x45, 0x4a, 0xb6, 0x8c, 0x3a, 0x50, 0x7a, 0x80, - 0x99, 0x6a, 0xcb, 0x98, 0xed, 0x4c, 0xe3, 0x36, 0xab, 0x59, 0xf3, 0x5e, 0xfd, 0xc7, 0xdf, 0x37, - 0x72, 0x3f, 0xf1, 0xcf, 0x6f, 0xfc, 0xf3, 0xed, 0x1f, 0x1b, 0xaf, 0x0c, 0x8b, 0x62, 0xb4, 0xbe, - 0xfb, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7e, 0x77, 0xcc, 0x37, 0x36, 0x10, 0x00, 0x00, + // 1077 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0x1b, 0x45, + 0x18, 0xc6, 0x76, 0xe3, 0xc4, 0xaf, 0xe3, 0x8f, 0x4c, 0xf3, 0xb1, 0xb5, 0x20, 0x09, 0x46, 0xaa, + 0xc2, 0x47, 0x6d, 0x30, 0x2a, 0x28, 0x42, 0x50, 0x39, 0xa4, 0x2a, 0x41, 0x72, 0xa9, 0xb6, 0x0e, + 0x07, 0x2e, 0xd6, 0x78, 0x77, 0x62, 0x8f, 0xe2, 0xec, 0x2e, 0x3b, 0xb3, 0x4e, 0xf3, 0x1f, 0x90, + 0x38, 0xc0, 0x81, 0x9f, 0xc2, 0x15, 0x71, 0xe1, 0xc8, 0x89, 0x03, 0x07, 0x84, 0xe0, 0xc2, 0xcf, + 0x60, 0x76, 0x66, 0xf6, 0xcb, 0xae, 0xdb, 0x14, 0x8a, 0x04, 0x11, 0x87, 0x95, 0x77, 0xde, 0x8f, + 0x67, 0xc6, 0xcf, 0xfb, 0x3e, 0xef, 0x0e, 0xbc, 0x3b, 0xa2, 0x7c, 0x1c, 0x0c, 0x5b, 0x96, 0x7b, + 0xd6, 0xee, 0x8f, 0x49, 0x7f, 0x4c, 0x9d, 0x11, 0xbb, 0x4f, 0xf8, 0xb9, 0xeb, 0x9f, 0xb6, 0x39, + 0x77, 0xda, 0xd8, 0xa3, 0xed, 0xa1, 0xef, 0x9e, 0x12, 0x5f, 0xff, 0xb4, 0x3c, 0xdf, 0xe5, 0x2e, + 0x2a, 0xaa, 0x55, 0xe3, 0x56, 0x0a, 0x60, 0xe4, 0x8e, 0xdc, 0xb6, 0x74, 0x0f, 0x83, 0x13, 0xb9, + 0x92, 0x0b, 0xf9, 0xa6, 0xd2, 0x32, 0xe1, 0x0b, 0xf7, 0x13, 0x8f, 0x0e, 0x7f, 0xef, 0x32, 0xe1, + 0x32, 0xd4, 0x72, 0x27, 0xf1, 0x8b, 0x4e, 0xde, 0xbf, 0x4c, 0xf2, 0x08, 0x73, 0x72, 0x8e, 0x2f, + 0xa2, 0x5f, 0x95, 0xda, 0xfc, 0x3e, 0x0f, 0xd5, 0x43, 0xf7, 0xdc, 0x99, 0x50, 0xe7, 0xf4, 0x13, + 0x8f, 0x53, 0xd7, 0x41, 0xdb, 0x00, 0xd4, 0x26, 0x0e, 0xa7, 0x27, 0x94, 0xf8, 0x46, 0x6e, 0x37, + 0xb7, 0x57, 0x32, 0x53, 0x16, 0xf4, 0x19, 0x94, 0x35, 0xc6, 0x80, 0x04, 0xd4, 0xc8, 0x8b, 0x80, + 0xd5, 0x83, 0xfd, 0x9f, 0x7f, 0xd9, 0xb9, 0xfd, 0xb4, 0x63, 0x58, 0xae, 0x4f, 0xda, 0xfc, 0xc2, + 0x23, 0xac, 0x75, 0x4f, 0x21, 0xdc, 0x3d, 0x3e, 0x32, 0x41, 0xa3, 0xdd, 0x0d, 0x28, 0x5a, 0x87, + 0x25, 0x16, 0x46, 0x19, 0x05, 0x81, 0x5a, 0x31, 0xd5, 0x02, 0x35, 0x60, 0xc5, 0x26, 0xd8, 0x16, + 0x67, 0x24, 0xc6, 0x35, 0xe1, 0x28, 0x98, 0xf1, 0x1a, 0x1d, 0x40, 0x2d, 0x62, 0x63, 0x60, 0xb9, + 0xce, 0x09, 0x1d, 0x19, 0x4b, 0x22, 0xa4, 0xdc, 0xb9, 0xd1, 0x8a, 0x59, 0xea, 0x3f, 0xfa, 0x50, + 0x7a, 0x02, 0x1f, 0x87, 0xff, 0xd0, 0xac, 0x46, 0x1e, 0x65, 0x46, 0x77, 0xa0, 0x1a, 0xfd, 0x23, + 0x0d, 0x51, 0x94, 0x10, 0x46, 0x2b, 0x22, 0x6b, 0x16, 0xa1, 0xa2, 0x1d, 0xca, 0xda, 0xfc, 0xb2, + 0x00, 0x95, 0x63, 0x2f, 0xe4, 0xb0, 0x47, 0x18, 0xc3, 0x23, 0x82, 0x0c, 0x58, 0xf6, 0xf0, 0xc5, + 0xc4, 0xc5, 0xb6, 0x64, 0x70, 0xd5, 0x8c, 0x96, 0xe8, 0x3e, 0x2c, 0xdb, 0x64, 0x2a, 0xa9, 0x2b, + 0x4b, 0xea, 0x6e, 0x0b, 0xea, 0xde, 0x7a, 0x06, 0xea, 0x0e, 0xc9, 0x34, 0xa4, 0xad, 0x28, 0x50, + 0x42, 0xca, 0x04, 0x1e, 0xf6, 0x3c, 0x89, 0xb7, 0xfa, 0x97, 0xf0, 0xba, 0x9e, 0x27, 0xf1, 0x04, + 0x4a, 0x88, 0xd7, 0x85, 0xb5, 0x98, 0xd0, 0x33, 0xc2, 0xb1, 0x8d, 0x39, 0x36, 0x36, 0x24, 0x1f, + 0xeb, 0x09, 0xa5, 0xe6, 0xa3, 0x9e, 0xf6, 0x99, 0xf5, 0xc8, 0x18, 0x59, 0xd0, 0x07, 0x50, 0x8f, + 0xf8, 0x8c, 0x11, 0x36, 0x25, 0xc2, 0xf5, 0x98, 0xd1, 0x14, 0x40, 0x4d, 0xdb, 0xe2, 0xfc, 0x2e, + 0xd4, 0x6d, 0xdd, 0x93, 0x03, 0x57, 0x36, 0x25, 0x33, 0x76, 0x76, 0x0b, 0x22, 0x7f, 0xb3, 0xa5, + 0xb5, 0x99, 0xed, 0x59, 0xb3, 0x66, 0x67, 0xd6, 0xac, 0xf9, 0x45, 0x1e, 0x6a, 0x51, 0xcc, 0x7f, + 0xbf, 0x26, 0x77, 0xa0, 0x36, 0x43, 0x88, 0xae, 0xc8, 0x22, 0x3e, 0xaa, 0x59, 0x3e, 0x9a, 0x01, + 0x18, 0xe2, 0x88, 0xd4, 0x22, 0x5d, 0x8b, 0xd3, 0xa9, 0xea, 0x61, 0xc2, 0x3c, 0xc1, 0xd4, 0x93, + 0x68, 0x79, 0xcc, 0xb6, 0xe5, 0x67, 0xda, 0xf6, 0xa7, 0x02, 0xdc, 0x38, 0x24, 0x76, 0x20, 0xa4, + 0x61, 0x89, 0x1a, 0xdb, 0x57, 0x45, 0x23, 0x1b, 0x10, 0xbe, 0x0d, 0xa8, 0x6d, 0x54, 0xe4, 0x78, + 0x5c, 0x12, 0xab, 0x23, 0xfb, 0x9f, 0x93, 0x4e, 0xe1, 0xd2, 0xd2, 0xd9, 0x81, 0x32, 0x23, 0xfe, + 0x94, 0xf8, 0x03, 0x4e, 0xcf, 0x88, 0xb1, 0x25, 0xa7, 0x25, 0x28, 0x53, 0x5f, 0x58, 0xd0, 0x21, + 0xac, 0xf9, 0xba, 0xf2, 0x03, 0x4e, 0xce, 0xbc, 0x89, 0x00, 0x10, 0xe2, 0x0a, 0xcf, 0xb8, 0x35, + 0x5b, 0x55, 0x5d, 0x28, 0xb3, 0x1e, 0x65, 0xf4, 0x75, 0x42, 0xf3, 0x8f, 0x02, 0x6c, 0xcd, 0x37, + 0xd4, 0xe7, 0x01, 0x61, 0xfc, 0xff, 0xd1, 0xf7, 0x77, 0x46, 0x5f, 0x0f, 0xae, 0xe3, 0x98, 0xd1, + 0x04, 0x62, 0x4b, 0x42, 0xbc, 0x98, 0x1c, 0x22, 0xa1, 0x3d, 0xc6, 0x42, 0x78, 0xce, 0xf6, 0x3c, + 0x26, 0xe9, 0xb7, 0xd7, 0xe0, 0x95, 0xb4, 0x86, 0xaf, 0x5e, 0xd9, 0xff, 0xbd, 0x6a, 0x7e, 0xce, + 0xdd, 0x30, 0x33, 0x1c, 0x8c, 0xb9, 0xe1, 0xd0, 0x5b, 0x3c, 0x1c, 0x76, 0xe3, 0x7e, 0x59, 0xf0, + 0x1d, 0x79, 0xcc, 0x94, 0x40, 0x50, 0x7f, 0x18, 0x0c, 0x99, 0xe5, 0xd3, 0x21, 0xd1, 0x6d, 0xd2, + 0xac, 0x41, 0xe5, 0x21, 0xc7, 0x3c, 0x60, 0x91, 0xe1, 0xbb, 0x02, 0x14, 0x95, 0x05, 0x35, 0xa1, + 0x18, 0xc8, 0x2f, 0x84, 0xec, 0xa0, 0x72, 0x07, 0x5a, 0xe1, 0x05, 0xd9, 0x14, 0x50, 0xcc, 0xd4, + 0x1e, 0xd4, 0x86, 0x8a, 0x7a, 0x1b, 0x04, 0x0e, 0x15, 0x08, 0xf2, 0xfe, 0x99, 0x0d, 0x5d, 0x55, + 0x01, 0xc7, 0xd2, 0x8f, 0x6e, 0x8a, 0xcb, 0xa3, 0x6e, 0x69, 0xfd, 0xf5, 0x4a, 0xc7, 0xc6, 0x3e, + 0xf4, 0x06, 0x94, 0x13, 0xca, 0x98, 0x2e, 0x74, 0x3a, 0x34, 0xed, 0x46, 0xfb, 0x90, 0x22, 0x98, + 0x45, 0x67, 0xd9, 0x9c, 0x4b, 0x5a, 0x4b, 0x45, 0xe9, 0x03, 0xbd, 0x0f, 0xeb, 0xe9, 0x54, 0x6c, + 0x59, 0xc4, 0x13, 0xba, 0xd2, 0x55, 0x4d, 0x27, 0xa7, 0x8a, 0xcf, 0xba, 0x3a, 0x0c, 0xbd, 0x03, + 0x15, 0x3b, 0x96, 0x63, 0xf8, 0x49, 0x56, 0xf5, 0xa9, 0xcb, 0xbc, 0x07, 0xc4, 0xb7, 0xc2, 0x8b, + 0xfa, 0x44, 0x64, 0x67, 0xc3, 0xd0, 0xeb, 0xb0, 0x26, 0x2e, 0xb7, 0x0e, 0xb1, 0x04, 0xc8, 0xc0, + 0x77, 0x03, 0x4e, 0x7c, 0x66, 0xbc, 0x2a, 0xaf, 0xd9, 0xf5, 0xd8, 0x61, 0x2a, 0x3b, 0xba, 0x05, + 0x28, 0x09, 0x1e, 0x63, 0xc7, 0x9e, 0x84, 0xd1, 0xaf, 0xc9, 0xe8, 0x04, 0xe6, 0x23, 0xed, 0x68, + 0x7e, 0x0a, 0xdb, 0x42, 0x53, 0xd1, 0x56, 0xda, 0x6c, 0x92, 0x11, 0x65, 0x5c, 0x5d, 0x98, 0x53, + 0x1a, 0xcb, 0xa5, 0x35, 0xf6, 0x12, 0x80, 0x46, 0x0f, 0x5d, 0x79, 0xe9, 0x2a, 0x69, 0xcb, 0x91, + 0xdd, 0xf9, 0x3a, 0x0f, 0xc5, 0x03, 0xd9, 0x76, 0xe2, 0x2e, 0x52, 0xea, 0x32, 0xe6, 0x5a, 0x54, + 0x30, 0x83, 0x36, 0xa2, 0x66, 0xcc, 0x5c, 0x28, 0x1a, 0x8b, 0x3e, 0x60, 0x7b, 0xb9, 0x37, 0x73, + 0xe8, 0x63, 0x28, 0xc5, 0xcd, 0x88, 0x8c, 0x28, 0x72, 0xb6, 0x3f, 0x1b, 0x2f, 0x27, 0x7d, 0xbe, + 0xe0, 0xde, 0x22, 0xb0, 0x5a, 0xb0, 0xfc, 0x20, 0x18, 0x4e, 0x28, 0x1b, 0xa3, 0x45, 0x7b, 0x36, + 0x56, 0x64, 0x41, 0xba, 0xd6, 0xe9, 0x5e, 0x4e, 0xe8, 0x6a, 0x45, 0x0b, 0x86, 0xa0, 0x9d, 0xc5, + 0x42, 0x52, 0x27, 0x78, 0xaa, 0xd2, 0x3a, 0x5f, 0xe5, 0xa0, 0xa2, 0x68, 0xe9, 0x61, 0x47, 0xec, + 0xe5, 0x8b, 0x91, 0xd8, 0x50, 0x74, 0x13, 0x7f, 0xbe, 0x10, 0xe8, 0x66, 0x84, 0xf8, 0xe4, 0x22, + 0x25, 0x47, 0x46, 0x1d, 0x28, 0xdd, 0x23, 0x5c, 0xcb, 0x32, 0x66, 0x3b, 0x23, 0xdc, 0x46, 0x35, + 0x6b, 0x3e, 0xa8, 0xff, 0xf0, 0xdb, 0x76, 0xee, 0x47, 0xf1, 0xfc, 0x2a, 0x9e, 0x6f, 0x7e, 0xdf, + 0x7e, 0x61, 0x58, 0x94, 0x03, 0xea, 0xed, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x73, 0x9f, + 0x4f, 0x7c, 0x0f, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index c7c011dd9..9a47ff27a 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -99,24 +99,6 @@ service Broker { rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); } -message ApplicationsRequest {} - -message ApplicationsResponse { - repeated string app_euis = 1; -} - -// message RegisterApplicationRequest is used to register an application at this -// Broker -message RegisterApplicationRequest { - string app_eui = 1; -} - -// message UnregisterApplicationRequest is used to unregister an application at -// this Broker -message UnregisterApplicationRequest { - string app_eui = 1; -} - // message StatusRequest is used to request the status of this Broker message StatusRequest {} diff --git a/api/broker/validation.go b/api/broker/validation.go new file mode 100644 index 000000000..5a9207efd --- /dev/null +++ b/api/broker/validation.go @@ -0,0 +1,111 @@ +package broker + +// Validate implements the api.Validator interface +func (m *DownlinkOption) Validate() bool { + if m.Identifier == "" { + return false + } + if m.GatewayEui == nil || m.GatewayEui.IsEmpty() { + return false + } + if m.ProtocolConfig == nil || !m.ProtocolConfig.Validate() { + return false + } + if m.GatewayConfig == nil || !m.GatewayConfig.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *UplinkMessage) Validate() bool { + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { + return false + } + if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *DownlinkMessage) Validate() bool { + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *DeduplicatedUplinkMessage) Validate() bool { + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.AppId == "" { + return false + } + if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *DeviceActivationRequest) Validate() bool { + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { + return false + } + if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { + return false + } + if m.ActivationMetadata == nil || !m.ActivationMetadata.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *DeduplicatedDeviceActivationRequest) Validate() bool { + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *ApplicationHandlerRegistration) Validate() bool { + if m.AppId == "" { + return false + } + if m.HandlerId == "" { + return false + } + return true +} diff --git a/api/discovery/validation.go b/api/discovery/validation.go new file mode 100644 index 000000000..5844159ae --- /dev/null +++ b/api/discovery/validation.go @@ -0,0 +1 @@ +package discovery diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 893f98bce..1b7c7e4e7 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -71,13 +71,12 @@ func (m *RxMetadata) GetGps() *GPSMetadata { } type TxConfiguration struct { - GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` - Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` - Frequency uint64 `protobuf:"varint,22,opt,name=frequency,proto3" json:"frequency,omitempty"` - Power int32 `protobuf:"varint,23,opt,name=power,proto3" json:"power,omitempty"` - PolarizationInversion bool `protobuf:"varint,31,opt,name=polarization_inversion,json=polarizationInversion,proto3" json:"polarization_inversion,omitempty"` - FrequencyDeviation uint32 `protobuf:"varint,32,opt,name=frequency_deviation,json=frequencyDeviation,proto3" json:"frequency_deviation,omitempty"` + Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Frequency uint64 `protobuf:"varint,22,opt,name=frequency,proto3" json:"frequency,omitempty"` + Power int32 `protobuf:"varint,23,opt,name=power,proto3" json:"power,omitempty"` + PolarizationInversion bool `protobuf:"varint,31,opt,name=polarization_inversion,json=polarizationInversion,proto3" json:"polarization_inversion,omitempty"` + FrequencyDeviation uint32 `protobuf:"varint,32,opt,name=frequency_deviation,json=frequencyDeviation,proto3" json:"frequency_deviation,omitempty"` } func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } @@ -259,16 +258,6 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayEui != nil { - data[i] = 0xa - i++ - i = encodeVarintGateway(data, i, uint64(m.GatewayEui.Size())) - n3, err := m.GatewayEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n3 - } if m.Timestamp != 0 { data[i] = 0x58 i++ @@ -387,11 +376,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n4, err := m.Gps.MarshalTo(data[i:]) + n3, err := m.Gps.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n3 } if m.Rtt != 0 { data[i] = 0xf8 @@ -514,10 +503,6 @@ func (m *RxMetadata) Size() (n int) { func (m *TxConfiguration) Size() (n int) { var l int _ = l - if m.GatewayEui != nil { - l = m.GatewayEui.Size() - n += 1 + l + sovGateway(uint64(l)) - } if m.Timestamp != 0 { n += 1 + sovGateway(uint64(m.Timestamp)) } @@ -988,38 +973,6 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowGateway - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthGateway - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayEui = &v - if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex case 11: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field Timestamp", wireType) @@ -1624,42 +1577,42 @@ var ( var fileDescriptorGateway = []byte{ // 607 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xc4, 0x54, 0xcb, 0x6e, 0x13, 0x31, - 0x14, 0x25, 0x93, 0x3e, 0x9d, 0xa6, 0xad, 0xdc, 0x36, 0x98, 0x0a, 0xb5, 0x25, 0x48, 0xa8, 0xbc, - 0x32, 0x12, 0xa8, 0x8b, 0x6e, 0x5b, 0xaa, 0xaa, 0x0b, 0x28, 0x72, 0xcb, 0x86, 0x4d, 0xe4, 0x4e, - 0x9c, 0x89, 0x95, 0x89, 0x3d, 0x78, 0x3c, 0x4d, 0xca, 0x97, 0xf0, 0x2f, 0xec, 0x58, 0xb1, 0x44, - 0x62, 0xc7, 0x02, 0x21, 0xf8, 0x11, 0xec, 0x3b, 0x8f, 0x4c, 0x91, 0xa0, 0x4b, 0x16, 0xa3, 0xdc, - 0x73, 0xce, 0xb5, 0x3d, 0xf7, 0xf8, 0x64, 0xd0, 0x7e, 0x28, 0xcc, 0x20, 0xbd, 0xe8, 0x04, 0x6a, - 0xe4, 0x9f, 0x0f, 0xf8, 0xf9, 0x40, 0xc8, 0x30, 0x79, 0xc5, 0xcd, 0x58, 0xe9, 0xa1, 0x6f, 0x8c, - 0xf4, 0x59, 0x2c, 0xfc, 0x90, 0x19, 0x3e, 0x66, 0x57, 0xc5, 0x6f, 0x27, 0xd6, 0xca, 0x28, 0x3c, - 0x9f, 0xc3, 0xcd, 0xa7, 0x95, 0x3d, 0x42, 0x15, 0x2a, 0x1f, 0xf4, 0x8b, 0xb4, 0x0f, 0x08, 0x00, - 0x54, 0xd9, 0xba, 0xf6, 0x18, 0x35, 0x8e, 0x5f, 0x9f, 0xbd, 0xe4, 0x86, 0xf5, 0x98, 0x61, 0x18, - 0xa3, 0x19, 0x23, 0x46, 0x9c, 0xd4, 0x76, 0x6a, 0xbb, 0x75, 0x0a, 0x35, 0xde, 0x44, 0x0b, 0x11, - 0x33, 0xc2, 0xa4, 0x3d, 0x4e, 0x3c, 0xcb, 0x7b, 0xb4, 0xc4, 0xf8, 0x2e, 0x5a, 0x8c, 0x94, 0x0c, - 0x33, 0xb1, 0x0e, 0xe2, 0x94, 0x70, 0x2b, 0x59, 0x94, 0xaf, 0x9c, 0xb1, 0xe2, 0x2c, 0x2d, 0x71, - 0xfb, 0xa3, 0x87, 0x10, 0x9d, 0x94, 0x07, 0xbf, 0x45, 0x8d, 0x7c, 0x82, 0x2e, 0x4f, 0x05, 0x9c, - 0xbf, 0x74, 0xb0, 0xff, 0xed, 0xfb, 0xf6, 0xde, 0x4d, 0x9e, 0x04, 0x4a, 0x73, 0xdf, 0x5c, 0xc5, - 0x3c, 0xe9, 0x1c, 0x67, 0x3b, 0x1c, 0xbd, 0x39, 0xa1, 0x28, 0xdf, 0xed, 0x28, 0x15, 0xee, 0x25, - 0xdd, 0x20, 0x89, 0x61, 0xa3, 0x98, 0x34, 0xec, 0xce, 0x4d, 0x3a, 0x25, 0xca, 0x91, 0x97, 0x2a, - 0x23, 0xdf, 0x41, 0x0b, 0xba, 0xdf, 0x0d, 0x06, 0x4c, 0x48, 0xb2, 0x01, 0x0b, 0xe6, 0x75, 0xff, - 0xd0, 0x41, 0x4c, 0xd0, 0xbc, 0xe5, 0xa5, 0xe4, 0x11, 0x69, 0x65, 0x4a, 0x0e, 0xdd, 0x31, 0x7d, - 0xcd, 0xdf, 0xa5, 0x5c, 0x06, 0x57, 0x64, 0xdb, 0x6a, 0x33, 0x74, 0x4a, 0xb8, 0x63, 0x74, 0x92, - 0x08, 0xb2, 0x03, 0x26, 0x41, 0x8d, 0x57, 0x51, 0x3d, 0x91, 0x9a, 0xdc, 0x03, 0xca, 0x95, 0xf8, - 0x01, 0xaa, 0x87, 0x71, 0x42, 0x1e, 0x5a, 0xa6, 0xf1, 0x6c, 0xbd, 0x53, 0xdc, 0x71, 0xe5, 0x8a, - 0xa8, 0x6b, 0x68, 0x7f, 0xf2, 0xd0, 0xca, 0xf9, 0xe4, 0x50, 0xc9, 0xbe, 0x08, 0x53, 0x6d, 0x6f, - 0x43, 0xc9, 0xff, 0x68, 0xe1, 0x3f, 0xec, 0xba, 0x66, 0x4a, 0xeb, 0x4f, 0x53, 0xd6, 0xd1, 0x6c, - 0xac, 0xc6, 0x5c, 0x93, 0xdb, 0x90, 0x8e, 0x0c, 0xe0, 0x3d, 0xd4, 0x8a, 0x55, 0xc4, 0xb4, 0x78, - 0x0f, 0x83, 0x75, 0x85, 0xbc, 0xe4, 0x3a, 0xb1, 0x15, 0xb8, 0xba, 0x40, 0x37, 0xaa, 0xea, 0x49, - 0x21, 0x62, 0x1f, 0xad, 0x95, 0x3b, 0x77, 0x7b, 0xfc, 0x52, 0x80, 0x0e, 0x86, 0x37, 0x29, 0x2e, - 0xa5, 0x17, 0x85, 0xd2, 0xfe, 0xea, 0xa1, 0xb9, 0x33, 0xc3, 0x4c, 0x9a, 0x5c, 0x9f, 0xaf, 0xf6, - 0xb7, 0x88, 0x78, 0x95, 0x88, 0x2c, 0x23, 0x4f, 0x38, 0x2b, 0xea, 0xbb, 0x8b, 0xd4, 0x56, 0x2e, - 0xeb, 0xb1, 0xfd, 0x5b, 0xf4, 0x95, 0x1e, 0x41, 0x94, 0x16, 0x69, 0x89, 0xf1, 0x7d, 0xd4, 0x0c, - 0x94, 0x34, 0x2c, 0x30, 0x5d, 0x3e, 0x62, 0x22, 0x22, 0x4d, 0x68, 0x58, 0xca, 0xc9, 0x23, 0xc7, - 0xe1, 0x1d, 0xd4, 0xe8, 0xf1, 0x24, 0xd0, 0x22, 0x86, 0xd7, 0x5e, 0x86, 0x96, 0x2a, 0x85, 0x5b, - 0x68, 0x4e, 0xf3, 0xd0, 0x89, 0x2b, 0x20, 0xe6, 0xa8, 0x08, 0xcd, 0xc6, 0x0d, 0xa1, 0x71, 0x71, - 0xd3, 0xc6, 0x80, 0x89, 0x4d, 0xea, 0x4a, 0xbc, 0x86, 0x66, 0xf5, 0xc4, 0xfa, 0x0b, 0x81, 0x6b, - 0xda, 0x54, 0x4e, 0x4e, 0x64, 0x4e, 0xaa, 0x21, 0x79, 0x54, 0x90, 0xa7, 0x43, 0x47, 0x1a, 0xe8, - 0x7c, 0x9c, 0x91, 0x26, 0xef, 0x34, 0xd0, 0xf9, 0xa4, 0x20, 0x4f, 0x87, 0x07, 0xab, 0x9f, 0x7f, - 0x6e, 0xd5, 0xbe, 0xd8, 0xe7, 0x87, 0x7d, 0x3e, 0xfc, 0xda, 0xba, 0x75, 0x31, 0x07, 0x9f, 0x9a, - 0xe7, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x87, 0xc4, 0x95, 0x62, 0xdf, 0x04, 0x00, 0x00, + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x54, 0xcd, 0x6e, 0x13, 0x3d, + 0x14, 0xfd, 0x92, 0xf4, 0xd7, 0xe9, 0xb4, 0x95, 0xdb, 0xe6, 0x33, 0x15, 0x6a, 0x4b, 0x90, 0x50, + 0xf9, 0xcb, 0x48, 0xa0, 0x2e, 0xba, 0x6d, 0xa9, 0xaa, 0x2e, 0xa0, 0xc8, 0x2d, 0x1b, 0x36, 0x91, + 0x3b, 0xf1, 0x4c, 0xac, 0x4c, 0xec, 0xc1, 0xe3, 0x69, 0x52, 0x9e, 0x84, 0x77, 0xe1, 0x05, 0x58, + 0x22, 0xb1, 0x63, 0x81, 0x10, 0x48, 0x3c, 0x07, 0xf6, 0x9d, 0x9f, 0x4e, 0x91, 0xa0, 0x8b, 0x51, + 0xee, 0x39, 0xe7, 0xda, 0xd7, 0xf7, 0x9e, 0xab, 0xa0, 0xfd, 0x48, 0x98, 0x61, 0x76, 0xd1, 0x0b, + 0xd4, 0xd8, 0x3f, 0x1f, 0xf2, 0xf3, 0xa1, 0x90, 0x51, 0xfa, 0x8a, 0x9b, 0x89, 0xd2, 0x23, 0xdf, + 0x18, 0xe9, 0xb3, 0x44, 0xf8, 0x11, 0x33, 0x7c, 0xc2, 0xae, 0xca, 0xdf, 0x5e, 0xa2, 0x95, 0x51, + 0x78, 0xbe, 0x80, 0x9b, 0x4f, 0x6b, 0x77, 0x44, 0x2a, 0x52, 0x3e, 0xe8, 0x17, 0x59, 0x08, 0x08, + 0x00, 0x44, 0xf9, 0xb9, 0xee, 0x04, 0xb5, 0x8f, 0x5f, 0x9f, 0xbd, 0xe4, 0x86, 0x0d, 0x98, 0x61, + 0x18, 0xa3, 0x19, 0x23, 0xc6, 0x9c, 0x34, 0x76, 0x1a, 0xbb, 0x2d, 0x0a, 0x31, 0xde, 0x44, 0x0b, + 0x31, 0x33, 0xc2, 0x64, 0x03, 0x4e, 0x9a, 0x96, 0x6f, 0xd2, 0x0a, 0xe3, 0xbb, 0x68, 0x31, 0x56, + 0x32, 0xca, 0xc5, 0x16, 0x88, 0xd7, 0x84, 0x3b, 0xc9, 0xe2, 0xe2, 0xe4, 0x8c, 0x15, 0x67, 0x69, + 0x85, 0xbb, 0x1f, 0x9b, 0x08, 0xd1, 0x69, 0x55, 0xf8, 0x2d, 0x6a, 0x17, 0x1d, 0xf4, 0x79, 0x26, + 0xa0, 0xfe, 0xd2, 0xc1, 0xfe, 0xd7, 0x6f, 0xdb, 0x7b, 0xb7, 0xcd, 0x24, 0x50, 0x9a, 0xfb, 0xe6, + 0x2a, 0xe1, 0x69, 0xef, 0x38, 0xbf, 0xe1, 0xe8, 0xcd, 0x09, 0x45, 0xc5, 0x6d, 0x47, 0x99, 0x70, + 0x8f, 0x74, 0x8d, 0xa4, 0x86, 0x8d, 0x13, 0xd2, 0xb6, 0x37, 0x7b, 0xf4, 0x9a, 0xa8, 0x5a, 0x5e, + 0xaa, 0xb5, 0x7c, 0x07, 0x2d, 0xe8, 0xb0, 0x1f, 0x0c, 0x99, 0x90, 0x64, 0x03, 0x0e, 0xcc, 0xeb, + 0xf0, 0xd0, 0x41, 0x4c, 0xd0, 0xbc, 0xe5, 0xa5, 0xe4, 0x31, 0xe9, 0xe4, 0x4a, 0x01, 0x5d, 0x99, + 0x50, 0xf3, 0x77, 0x19, 0x97, 0xc1, 0x15, 0xd9, 0xb6, 0xda, 0x0c, 0xbd, 0x26, 0x5c, 0x19, 0x9d, + 0xa6, 0x82, 0xec, 0xc0, 0x90, 0x20, 0xc6, 0xab, 0xa8, 0x95, 0x4a, 0x4d, 0xee, 0x01, 0xe5, 0x42, + 0xfc, 0x00, 0xb5, 0xa2, 0x24, 0x25, 0x0f, 0x2d, 0xd3, 0x7e, 0xb6, 0xde, 0x2b, 0x3d, 0xae, 0x59, + 0x44, 0x5d, 0x42, 0xf7, 0x57, 0x03, 0xad, 0x9c, 0x4f, 0x0f, 0x95, 0x0c, 0x45, 0x94, 0x69, 0xeb, + 0x86, 0x92, 0xb7, 0xb4, 0xf9, 0x8f, 0x96, 0x6e, 0x3c, 0xbc, 0xf3, 0xe7, 0xc3, 0xd7, 0xd1, 0x6c, + 0xa2, 0x26, 0x5c, 0x93, 0xff, 0xc1, 0xc1, 0x1c, 0xe0, 0x3d, 0xd4, 0x49, 0x54, 0xcc, 0xb4, 0x78, + 0x0f, 0xc5, 0xfb, 0x42, 0x5e, 0x72, 0x9d, 0xda, 0x08, 0x3a, 0x5f, 0xa0, 0x1b, 0x75, 0xf5, 0xa4, + 0x14, 0xb1, 0x8f, 0xd6, 0xaa, 0x9b, 0xfb, 0x03, 0x7e, 0x29, 0x40, 0x87, 0xa1, 0x78, 0x14, 0x57, + 0xd2, 0x8b, 0x52, 0xe9, 0x7e, 0x69, 0xa2, 0xb9, 0x33, 0xc3, 0x4c, 0x96, 0xde, 0xec, 0xaf, 0xf1, + 0x37, 0x1b, 0x9b, 0x35, 0x1b, 0x97, 0x51, 0x53, 0xb8, 0x51, 0xb4, 0x76, 0x17, 0xa9, 0x8d, 0xdc, + 0x3e, 0x26, 0x76, 0x75, 0x43, 0xa5, 0xc7, 0x60, 0xf7, 0x22, 0xad, 0x30, 0xbe, 0x8f, 0xbc, 0x40, + 0x49, 0xc3, 0x02, 0xd3, 0xe7, 0x63, 0x26, 0x62, 0xe2, 0x41, 0xc2, 0x52, 0x41, 0x1e, 0x39, 0x0e, + 0xef, 0xa0, 0xf6, 0x80, 0xa7, 0x81, 0x16, 0x09, 0x3c, 0x7b, 0x19, 0x52, 0xea, 0x14, 0xee, 0xa0, + 0x39, 0xcd, 0x23, 0x27, 0xae, 0x80, 0x58, 0xa0, 0xd2, 0xd8, 0x8d, 0x5b, 0x8c, 0x75, 0x2b, 0xa1, + 0x8d, 0x81, 0x21, 0x7a, 0xd4, 0x85, 0x78, 0x0d, 0xcd, 0xea, 0xa9, 0x9d, 0x2f, 0x2c, 0x85, 0x67, + 0x37, 0x67, 0x7a, 0x22, 0x0b, 0x52, 0x8d, 0xc8, 0xa3, 0x92, 0x3c, 0x1d, 0x39, 0xd2, 0x40, 0xe6, + 0xe3, 0x9c, 0x34, 0x45, 0xa6, 0x81, 0xcc, 0x27, 0x25, 0x79, 0x3a, 0x3a, 0x58, 0xfd, 0xf4, 0x63, + 0xab, 0xf1, 0xd9, 0x7e, 0xdf, 0xed, 0xf7, 0xe1, 0xe7, 0xd6, 0x7f, 0x17, 0x73, 0xf0, 0x77, 0xf0, + 0xfc, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x59, 0xd6, 0x7d, 0x83, 0x04, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index f6ed0c9c5..0985df425 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -31,8 +31,6 @@ message RxMetadata { } message TxConfiguration { - bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; - uint32 timestamp = 11; uint32 rf_chain = 21; diff --git a/api/gateway/validation.go b/api/gateway/validation.go new file mode 100644 index 000000000..e4a09f65a --- /dev/null +++ b/api/gateway/validation.go @@ -0,0 +1,19 @@ +package gateway + +// Validate implements the api.Validator interface +func (m *RxMetadata) Validate() bool { + if m.GatewayEui == nil || m.GatewayEui.IsEmpty() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration) Validate() bool { + return true +} + +// Validate implements the api.Validator interface +func (m *Status) Validate() bool { + return true +} diff --git a/api/handler/validation.go b/api/handler/validation.go new file mode 100644 index 000000000..627d14671 --- /dev/null +++ b/api/handler/validation.go @@ -0,0 +1,31 @@ +package handler + +// Validate implements the api.Validator interface +func (m *DeviceActivationResponse) Validate() bool { + if m.AppId == "" { + return false + } + if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { + return false + } + if m.ActivationMetadata == nil || !m.ActivationMetadata.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *ApplicationIdentifier) Validate() bool { + if m.AppId == "" { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *Application) Validate() bool { + if m.AppId == "" { + return false + } + return true +} diff --git a/api/networkserver/validation.go b/api/networkserver/validation.go new file mode 100644 index 000000000..0dad88356 --- /dev/null +++ b/api/networkserver/validation.go @@ -0,0 +1,9 @@ +package networkserver + +// Validate implements the api.Validator interface +func (m *DevicesRequest) Validate() bool { + if m.DevAddr == nil || m.DevAddr.IsEmpty() { + return false + } + return true +} diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index a9f30cec2..d11f3b44a 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -15,11 +15,6 @@ Metadata TxConfiguration ActivationMetadata - PHYPayload - MHdr - MACPayload - FHdr - FCtrl */ package lorawan diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 4b758cfb1..df0423de1 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -114,100 +114,10 @@ func (m *ActivationMetadata) String() string { return proto.CompactTe func (*ActivationMetadata) ProtoMessage() {} func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } -type PHYPayload struct { - Mhdr *MHdr `protobuf:"bytes,1,opt,name=mhdr" json:"mhdr,omitempty"` - MACPayload *MACPayload `protobuf:"bytes,2,opt,name=MAC_payload,json=mACPayload" json:"MAC_payload,omitempty"` - Mic []byte `protobuf:"bytes,3,opt,name=mic,proto3" json:"mic,omitempty"` -} - -func (m *PHYPayload) Reset() { *m = PHYPayload{} } -func (m *PHYPayload) String() string { return proto.CompactTextString(m) } -func (*PHYPayload) ProtoMessage() {} -func (*PHYPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } - -func (m *PHYPayload) GetMhdr() *MHdr { - if m != nil { - return m.Mhdr - } - return nil -} - -func (m *PHYPayload) GetMACPayload() *MACPayload { - if m != nil { - return m.MACPayload - } - return nil -} - -type MHdr struct { - MType uint32 `protobuf:"varint,1,opt,name=m_type,json=mType,proto3" json:"m_type,omitempty"` - Major uint32 `protobuf:"varint,2,opt,name=major,proto3" json:"major,omitempty"` -} - -func (m *MHdr) Reset() { *m = MHdr{} } -func (m *MHdr) String() string { return proto.CompactTextString(m) } -func (*MHdr) ProtoMessage() {} -func (*MHdr) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{4} } - -type MACPayload struct { - FHdr *FHdr `protobuf:"bytes,1,opt,name=f_hdr,json=fHdr" json:"f_hdr,omitempty"` - FPort uint32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"` - FRMPayload []byte `protobuf:"bytes,3,opt,name=FRM_payload,json=fRMPayload,proto3" json:"FRM_payload,omitempty"` -} - -func (m *MACPayload) Reset() { *m = MACPayload{} } -func (m *MACPayload) String() string { return proto.CompactTextString(m) } -func (*MACPayload) ProtoMessage() {} -func (*MACPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } - -func (m *MACPayload) GetFHdr() *FHdr { - if m != nil { - return m.FHdr - } - return nil -} - -type FHdr struct { - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` - FCtrl *FCtrl `protobuf:"bytes,2,opt,name=f_ctrl,json=fCtrl" json:"f_ctrl,omitempty"` - FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` - FOpts [][]byte `protobuf:"bytes,4,rep,name=f_opts,json=fOpts" json:"f_opts,omitempty"` -} - -func (m *FHdr) Reset() { *m = FHdr{} } -func (m *FHdr) String() string { return proto.CompactTextString(m) } -func (*FHdr) ProtoMessage() {} -func (*FHdr) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } - -func (m *FHdr) GetFCtrl() *FCtrl { - if m != nil { - return m.FCtrl - } - return nil -} - -type FCtrl struct { - Adr bool `protobuf:"varint,1,opt,name=adr,proto3" json:"adr,omitempty"` - AdrAckReq bool `protobuf:"varint,2,opt,name=adr_ack_req,json=adrAckReq,proto3" json:"adr_ack_req,omitempty"` - Ack bool `protobuf:"varint,3,opt,name=ack,proto3" json:"ack,omitempty"` - FPending bool `protobuf:"varint,4,opt,name=f_pending,json=fPending,proto3" json:"f_pending,omitempty"` - FOptsLen []byte `protobuf:"bytes,5,opt,name=f_opts_len,json=fOptsLen,proto3" json:"f_opts_len,omitempty"` -} - -func (m *FCtrl) Reset() { *m = FCtrl{} } -func (m *FCtrl) String() string { return proto.CompactTextString(m) } -func (*FCtrl) ProtoMessage() {} -func (*FCtrl) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{7} } - func init() { proto.RegisterType((*Metadata)(nil), "lorawan.Metadata") proto.RegisterType((*TxConfiguration)(nil), "lorawan.TxConfiguration") proto.RegisterType((*ActivationMetadata)(nil), "lorawan.ActivationMetadata") - proto.RegisterType((*PHYPayload)(nil), "lorawan.PHYPayload") - proto.RegisterType((*MHdr)(nil), "lorawan.MHdr") - proto.RegisterType((*MACPayload)(nil), "lorawan.MACPayload") - proto.RegisterType((*FHdr)(nil), "lorawan.FHdr") - proto.RegisterType((*FCtrl)(nil), "lorawan.FCtrl") proto.RegisterEnum("lorawan.Modulation", Modulation_name, Modulation_value) proto.RegisterEnum("lorawan.Region", Region_name, Region_value) } @@ -381,232 +291,6 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *PHYPayload) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *PHYPayload) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Mhdr != nil { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(m.Mhdr.Size())) - n5, err := m.Mhdr.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n5 - } - if m.MACPayload != nil { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(m.MACPayload.Size())) - n6, err := m.MACPayload.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n6 - } - if len(m.Mic) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.Mic))) - i += copy(data[i:], m.Mic) - } - return i, nil -} - -func (m *MHdr) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *MHdr) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.MType != 0 { - data[i] = 0x8 - i++ - i = encodeVarintLorawan(data, i, uint64(m.MType)) - } - if m.Major != 0 { - data[i] = 0x10 - i++ - i = encodeVarintLorawan(data, i, uint64(m.Major)) - } - return i, nil -} - -func (m *MACPayload) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *MACPayload) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.FHdr != nil { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(m.FHdr.Size())) - n7, err := m.FHdr.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n7 - } - if m.FPort != 0 { - data[i] = 0x10 - i++ - i = encodeVarintLorawan(data, i, uint64(m.FPort)) - } - if len(m.FRMPayload) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FRMPayload))) - i += copy(data[i:], m.FRMPayload) - } - return i, nil -} - -func (m *FHdr) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *FHdr) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.DevAddr != nil { - data[i] = 0xa - i++ - i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n8, err := m.DevAddr.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n8 - } - if m.FCtrl != nil { - data[i] = 0x12 - i++ - i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) - n9, err := m.FCtrl.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n9 - } - if m.FCnt != 0 { - data[i] = 0x18 - i++ - i = encodeVarintLorawan(data, i, uint64(m.FCnt)) - } - if len(m.FOpts) > 0 { - for _, b := range m.FOpts { - data[i] = 0x22 - i++ - i = encodeVarintLorawan(data, i, uint64(len(b))) - i += copy(data[i:], b) - } - } - return i, nil -} - -func (m *FCtrl) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *FCtrl) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.Adr { - data[i] = 0x8 - i++ - if m.Adr { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.AdrAckReq { - data[i] = 0x10 - i++ - if m.AdrAckReq { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.Ack { - data[i] = 0x18 - i++ - if m.Ack { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if m.FPending { - data[i] = 0x20 - i++ - if m.FPending { - data[i] = 1 - } else { - data[i] = 0 - } - i++ - } - if len(m.FOptsLen) > 0 { - data[i] = 0x2a - i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FOptsLen))) - i += copy(data[i:], m.FOptsLen) - } - return i, nil -} - func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -716,98 +400,6 @@ func (m *ActivationMetadata) Size() (n int) { return n } -func (m *PHYPayload) Size() (n int) { - var l int - _ = l - if m.Mhdr != nil { - l = m.Mhdr.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.MACPayload != nil { - l = m.MACPayload.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - l = len(m.Mic) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *MHdr) Size() (n int) { - var l int - _ = l - if m.MType != 0 { - n += 1 + sovLorawan(uint64(m.MType)) - } - if m.Major != 0 { - n += 1 + sovLorawan(uint64(m.Major)) - } - return n -} - -func (m *MACPayload) Size() (n int) { - var l int - _ = l - if m.FHdr != nil { - l = m.FHdr.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.FPort != 0 { - n += 1 + sovLorawan(uint64(m.FPort)) - } - l = len(m.FRMPayload) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - -func (m *FHdr) Size() (n int) { - var l int - _ = l - if m.DevAddr != nil { - l = m.DevAddr.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.FCtrl != nil { - l = m.FCtrl.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.FCnt != 0 { - n += 1 + sovLorawan(uint64(m.FCnt)) - } - if len(m.FOpts) > 0 { - for _, b := range m.FOpts { - l = len(b) - n += 1 + l + sovLorawan(uint64(l)) - } - } - return n -} - -func (m *FCtrl) Size() (n int) { - var l int - _ = l - if m.Adr { - n += 2 - } - if m.AdrAckReq { - n += 2 - } - if m.Ack { - n += 2 - } - if m.FPending { - n += 2 - } - l = len(m.FOptsLen) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - return n -} - func sovLorawan(x uint64) (n int) { for { n++ @@ -1406,698 +998,6 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } return nil } -func (m *PHYPayload) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: PHYPayload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: PHYPayload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Mhdr", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.Mhdr == nil { - m.Mhdr = &MHdr{} - } - if err := m.Mhdr.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field MACPayload", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.MACPayload == nil { - m.MACPayload = &MACPayload{} - } - if err := m.MACPayload.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Mic", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Mic = append(m.Mic[:0], data[iNdEx:postIndex]...) - if m.Mic == nil { - m.Mic = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MHdr) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MHdr: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MHdr: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) - } - m.MType = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.MType |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) - } - m.Major = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.Major |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *MACPayload) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: MACPayload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: MACPayload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FHdr", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.FHdr == nil { - m.FHdr = &FHdr{} - } - if err := m.FHdr.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) - } - m.FPort = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FPort |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FRMPayload", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FRMPayload = append(m.FRMPayload[:0], data[iNdEx:postIndex]...) - if m.FRMPayload == nil { - m.FRMPayload = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *FHdr) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: FHdr: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: FHdr: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.DevAddr - m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FCtrl", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - if m.FCtrl == nil { - m.FCtrl = &FCtrl{} - } - if err := m.FCtrl.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) - } - m.FCnt = 0 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - case 4: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FOpts", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FOpts = append(m.FOpts, make([]byte, postIndex-iNdEx)) - copy(m.FOpts[len(m.FOpts)-1], data[iNdEx:postIndex]) - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *FCtrl) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: FCtrl: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: FCtrl: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Adr", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.Adr = bool(v != 0) - case 2: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field AdrAckReq", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.AdrAckReq = bool(v != 0) - case 3: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.Ack = bool(v != 0) - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FPending", wireType) - } - var v int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - v |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - m.FPending = bool(v != 0) - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field FOptsLen", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthLorawan - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.FOptsLen = append(m.FOptsLen[:0], data[iNdEx:postIndex]...) - if m.FOptsLen == nil { - m.FOptsLen = []byte{} - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthLorawan - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipLorawan(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -2204,58 +1104,41 @@ var ( ) var fileDescriptorLorawan = []byte{ - // 837 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x55, 0xdd, 0x6e, 0xe3, 0x44, - 0x14, 0x5e, 0x6f, 0xfe, 0xdc, 0x93, 0x4d, 0x37, 0x9a, 0x05, 0x11, 0x7e, 0xd4, 0x82, 0x25, 0xa4, - 0xd5, 0x4a, 0x34, 0x6d, 0xd2, 0x6e, 0xda, 0x4b, 0x37, 0x6d, 0xb5, 0x68, 0x9b, 0xb6, 0x4c, 0xdb, - 0x0b, 0xae, 0x46, 0x53, 0x7b, 0x9c, 0x18, 0x3b, 0x1e, 0xef, 0x64, 0xd2, 0x24, 0x2f, 0xc0, 0x33, - 0xf0, 0x10, 0x70, 0xc7, 0x43, 0x20, 0xae, 0xb8, 0xe6, 0x02, 0x21, 0x78, 0x11, 0xce, 0x8c, 0xd3, - 0xa6, 0x48, 0x48, 0x88, 0x15, 0x37, 0x7b, 0x11, 0x79, 0xbe, 0xf3, 0xf3, 0x9d, 0x6f, 0xce, 0x39, - 0x76, 0xe0, 0x70, 0x18, 0xeb, 0xd1, 0xf4, 0x66, 0x2b, 0x90, 0xe3, 0xf6, 0xd5, 0x48, 0x5c, 0x8d, - 0xe2, 0x6c, 0x38, 0x39, 0x13, 0x7a, 0x26, 0x55, 0xd2, 0xd6, 0x3a, 0x6b, 0xf3, 0x3c, 0x6e, 0xe7, - 0x4a, 0x6a, 0x19, 0xc8, 0xb4, 0x9d, 0x4a, 0xc5, 0x67, 0x3c, 0xbb, 0x7b, 0x6e, 0x59, 0x07, 0xa9, - 0x2d, 0xe1, 0x47, 0x5f, 0x3c, 0x20, 0x1b, 0xca, 0xa1, 0x2c, 0x12, 0x6f, 0xa6, 0x91, 0x45, 0x16, - 0xd8, 0x53, 0x91, 0xe7, 0xfd, 0xe0, 0x80, 0x3b, 0x10, 0x9a, 0x87, 0x5c, 0x73, 0xd2, 0x05, 0x18, - 0xcb, 0x70, 0x9a, 0x72, 0x1d, 0xcb, 0xac, 0x55, 0xff, 0xd4, 0x79, 0xbe, 0xde, 0x79, 0xb6, 0x75, - 0x57, 0x68, 0x70, 0xef, 0xa2, 0x0f, 0xc2, 0xc8, 0xc7, 0xb0, 0x66, 0x92, 0x99, 0xe2, 0x5a, 0xb4, - 0x9e, 0x60, 0xce, 0x1a, 0x75, 0x8d, 0x81, 0x22, 0x26, 0x1f, 0x82, 0x7b, 0x13, 0xeb, 0xc2, 0xd7, - 0x40, 0x5f, 0x83, 0xd6, 0x10, 0x5b, 0xd7, 0x26, 0xd4, 0x03, 0x19, 0xe2, 0x55, 0x0b, 0xef, 0xba, - 0xcd, 0x84, 0xc2, 0x64, 0x03, 0x9e, 0x41, 0x25, 0x62, 0x41, 0xa6, 0x5b, 0x4f, 0x6d, 0x62, 0x39, - 0xea, 0x67, 0xda, 0xfb, 0xd1, 0x81, 0xa7, 0x57, 0xf3, 0xbe, 0xcc, 0xa2, 0x78, 0x38, 0x55, 0x85, - 0x82, 0x77, 0x40, 0xf6, 0xcf, 0x25, 0x20, 0x7e, 0xa0, 0xe3, 0x5b, 0x5b, 0xfc, 0xbe, 0xe1, 0x67, - 0x50, 0xe3, 0x79, 0xce, 0xc4, 0x34, 0x6e, 0x39, 0x18, 0xfd, 0xe4, 0x70, 0xef, 0xd7, 0xdf, 0x36, - 0x77, 0xfe, 0x6d, 0x1d, 0x02, 0xa9, 0x44, 0x5b, 0x2f, 0x72, 0x31, 0xd9, 0xf2, 0xf3, 0xfc, 0xf8, - 0xfa, 0x4b, 0x5a, 0x45, 0x96, 0xe3, 0x69, 0x6c, 0xf8, 0x42, 0x71, 0x6b, 0xf9, 0x1e, 0xbf, 0x15, - 0xdf, 0x91, 0xb8, 0xb5, 0x7c, 0xc8, 0x62, 0xf8, 0xbe, 0x02, 0xd7, 0xf0, 0xf1, 0x30, 0x54, 0xad, - 0x92, 0x25, 0x7c, 0x89, 0x84, 0x9d, 0xff, 0x46, 0xe8, 0x63, 0x36, 0x35, 0xba, 0xcc, 0x81, 0x50, - 0x58, 0xcb, 0x66, 0x09, 0x9b, 0xb0, 0x44, 0x2c, 0x5a, 0xe5, 0xb7, 0xe2, 0x3c, 0x9b, 0x25, 0x97, - 0xaf, 0xc5, 0x82, 0xd6, 0xb2, 0xe2, 0x40, 0x3c, 0x68, 0xa8, 0xf9, 0x0e, 0x0b, 0x15, 0x93, 0x51, - 0x34, 0x11, 0xda, 0xee, 0x40, 0x83, 0xd6, 0xd1, 0x78, 0xa4, 0xce, 0xad, 0x89, 0xbc, 0x0f, 0x55, - 0x35, 0xef, 0x60, 0x8c, 0x1d, 0x76, 0x83, 0x56, 0x10, 0x1d, 0x29, 0x33, 0x69, 0x35, 0x67, 0xa1, - 0x48, 0xf9, 0xe2, 0x6e, 0xd2, 0x6a, 0x7e, 0x64, 0x20, 0xf9, 0x00, 0x6a, 0x41, 0xc4, 0xd2, 0x78, - 0xa2, 0x71, 0xca, 0xa5, 0xe7, 0x65, 0x5a, 0x0d, 0xa2, 0x53, 0x44, 0xde, 0x0c, 0xe0, 0xe2, 0xd5, - 0xd7, 0x17, 0x7c, 0x91, 0x4a, 0x1e, 0x92, 0xcf, 0xa0, 0x3c, 0x1e, 0x21, 0xad, 0x19, 0x60, 0xbd, - 0xd3, 0x58, 0xed, 0xdd, 0x2b, 0xbc, 0xb6, 0x75, 0x91, 0x5d, 0xa8, 0x0f, 0xfc, 0x3e, 0xcb, 0x8b, - 0x0c, 0x3b, 0x9a, 0xfa, 0xc3, 0x0d, 0xf5, 0xfb, 0x4b, 0x32, 0xdc, 0xd0, 0xfb, 0x33, 0x69, 0x42, - 0x69, 0x1c, 0x07, 0x45, 0xdf, 0xa9, 0x39, 0x7a, 0x5d, 0x28, 0x1b, 0x56, 0x73, 0x97, 0x31, 0x33, - 0xbd, 0xb0, 0x45, 0xf1, 0x2e, 0xe3, 0x2b, 0x04, 0xe4, 0x3d, 0xa8, 0x8c, 0xf9, 0x37, 0x52, 0xd9, - 0x02, 0xc6, 0x6a, 0x80, 0x37, 0x02, 0x58, 0x15, 0xc0, 0x56, 0xe1, 0x76, 0xfe, 0x93, 0xdc, 0x13, - 0x2b, 0x37, 0x5a, 0xd2, 0x47, 0x2c, 0x97, 0x4a, 0xdf, 0x11, 0x45, 0x17, 0x08, 0xcc, 0xe6, 0x9f, - 0xd0, 0xc1, 0xfd, 0x2d, 0x0a, 0x5d, 0x10, 0xd1, 0xc1, 0x92, 0xdb, 0xfb, 0xde, 0x81, 0xb2, 0xa1, - 0xf9, 0xdb, 0xda, 0x38, 0xff, 0xcf, 0xda, 0x7c, 0x6e, 0x34, 0x05, 0x5a, 0xa5, 0xcb, 0xee, 0xad, - 0xaf, 0x84, 0xf7, 0xd1, 0x8a, 0x1a, 0xcd, 0x63, 0xf5, 0xf2, 0x95, 0x56, 0x2f, 0x5f, 0x71, 0x1f, - 0x99, 0xeb, 0x09, 0xee, 0x5b, 0x09, 0x35, 0x57, 0xa2, 0x73, 0x04, 0xde, 0xb7, 0x0e, 0x54, 0x6c, - 0xb2, 0xe9, 0x34, 0x5f, 0x4a, 0x75, 0xa9, 0x39, 0x92, 0x0d, 0xa8, 0xe3, 0x83, 0xf1, 0x20, 0x61, - 0x4a, 0xbc, 0xb1, 0x35, 0x5d, 0xba, 0x86, 0x26, 0x3f, 0x48, 0xa8, 0x78, 0x63, 0x33, 0x82, 0xc4, - 0x56, 0x31, 0x19, 0x41, 0x62, 0xbe, 0x27, 0xd8, 0x34, 0x91, 0x99, 0xef, 0x80, 0xdd, 0x6b, 0x97, - 0xba, 0xd1, 0x45, 0x81, 0xc9, 0x27, 0x00, 0x85, 0x02, 0x96, 0x8a, 0xac, 0x55, 0xb1, 0x9d, 0x73, - 0xad, 0x8a, 0x53, 0x91, 0xbd, 0xd8, 0xc4, 0x09, 0xad, 0x3e, 0x4c, 0x2e, 0x94, 0x4f, 0xcf, 0xa9, - 0xdf, 0x7c, 0x44, 0x6a, 0x50, 0x3a, 0xb9, 0x7c, 0xdd, 0x74, 0x5e, 0x84, 0x50, 0xa5, 0x62, 0x68, - 0x9c, 0xeb, 0x00, 0xc7, 0xd7, 0x6c, 0xff, 0x65, 0x97, 0xed, 0xf7, 0xb6, 0x31, 0x04, 0xf1, 0xf5, - 0x25, 0x3b, 0xd8, 0xee, 0xb0, 0x83, 0xce, 0x7e, 0xd3, 0x31, 0xb8, 0x7f, 0xc6, 0x7a, 0xbd, 0x03, - 0xd6, 0xdb, 0xef, 0x35, 0x1f, 0x13, 0x80, 0x2a, 0xc6, 0xef, 0x76, 0xbb, 0xcd, 0x92, 0xf1, 0xf9, - 0xd7, 0xec, 0x60, 0x67, 0xcf, 0xc6, 0x96, 0x97, 0xb1, 0xbb, 0xbd, 0x6d, 0xb6, 0xb7, 0xb3, 0xdd, - 0xac, 0x1c, 0x36, 0x7f, 0xfa, 0x63, 0xc3, 0xf9, 0x05, 0x7f, 0xbf, 0xe3, 0xef, 0xbb, 0x3f, 0x37, - 0x1e, 0xdd, 0x54, 0xed, 0x7f, 0x44, 0xf7, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0xd5, 0x1e, 0x03, - 0xe2, 0xa1, 0x06, 0x00, 0x00, + // 566 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x53, 0xcf, 0x4e, 0xdb, 0x30, + 0x18, 0x27, 0xb4, 0x24, 0xe5, 0x83, 0x42, 0x64, 0x34, 0x2d, 0xdb, 0x24, 0x98, 0x38, 0x21, 0xa4, + 0x35, 0xfd, 0x03, 0xa4, 0x3d, 0x16, 0xca, 0xa4, 0x09, 0x28, 0x9a, 0xa1, 0x67, 0x2b, 0x4d, 0x9c, + 0x10, 0xb5, 0xc4, 0x91, 0xeb, 0xd0, 0xf6, 0x4d, 0xf6, 0x12, 0xbb, 0xed, 0x21, 0xa6, 0x9d, 0x76, + 0xde, 0x61, 0x9a, 0xb6, 0x17, 0x99, 0xed, 0x50, 0xc6, 0x6d, 0x1a, 0xb7, 0x1d, 0xac, 0xf8, 0xf7, + 0xfd, 0xfe, 0xd8, 0xc9, 0xf7, 0x05, 0x8e, 0xe3, 0x44, 0xdc, 0xe4, 0xc3, 0x5a, 0xc0, 0x6e, 0xdd, + 0xeb, 0x1b, 0x7a, 0x7d, 0x93, 0xa4, 0xf1, 0xa4, 0x4f, 0xc5, 0x94, 0xf1, 0x91, 0x2b, 0x44, 0xea, + 0xfa, 0x59, 0xe2, 0x66, 0x9c, 0x09, 0x16, 0xb0, 0xb1, 0x3b, 0x66, 0xdc, 0x9f, 0xfa, 0xe9, 0xe2, + 0x59, 0xd3, 0x04, 0xb2, 0xee, 0xe1, 0xcb, 0x37, 0x8f, 0xc2, 0x62, 0x16, 0xb3, 0xc2, 0x38, 0xcc, + 0x23, 0x8d, 0x34, 0xd0, 0xbb, 0xc2, 0xb7, 0xfb, 0xd1, 0x80, 0xca, 0x05, 0x15, 0x7e, 0xe8, 0x0b, + 0x1f, 0xb5, 0x00, 0x6e, 0x59, 0x98, 0x8f, 0x7d, 0x91, 0xb0, 0xd4, 0x59, 0x7b, 0x6d, 0xec, 0x6d, + 0x34, 0xb7, 0x6a, 0x8b, 0x83, 0x2e, 0x1e, 0x28, 0xfc, 0x48, 0x86, 0x5e, 0xc1, 0xaa, 0x32, 0x13, + 0xee, 0x0b, 0xea, 0xac, 0x4b, 0xcf, 0x2a, 0xae, 0xa8, 0x02, 0x96, 0x18, 0xbd, 0x80, 0xca, 0x30, + 0x11, 0x05, 0x57, 0x95, 0x5c, 0x15, 0x5b, 0x12, 0x6b, 0x6a, 0x07, 0xd6, 0x02, 0x16, 0xca, 0x57, + 0x2d, 0xd8, 0x0d, 0xed, 0x84, 0xa2, 0xa4, 0x05, 0x5b, 0xb0, 0x12, 0x91, 0x20, 0x15, 0xce, 0xa6, + 0x36, 0x96, 0xa3, 0x93, 0x54, 0xec, 0x7e, 0x32, 0x60, 0xf3, 0x7a, 0x76, 0xc2, 0xd2, 0x28, 0x89, + 0x73, 0x5e, 0xdc, 0xe0, 0x3f, 0xb8, 0xf6, 0x97, 0x12, 0xa0, 0x6e, 0x20, 0x92, 0x3b, 0x7d, 0xf8, + 0xc3, 0x07, 0xef, 0x83, 0xe5, 0x67, 0x19, 0xa1, 0x79, 0xe2, 0x18, 0x52, 0xbd, 0x7e, 0x7c, 0xf8, + 0xed, 0xfb, 0x4e, 0xe3, 0x6f, 0xe3, 0x10, 0x30, 0x4e, 0x5d, 0x31, 0xcf, 0xe8, 0xa4, 0xd6, 0xcd, + 0xb2, 0xd3, 0xc1, 0x3b, 0x6c, 0xca, 0x94, 0xd3, 0x3c, 0x51, 0x79, 0x21, 0xbd, 0xd3, 0x79, 0xcb, + 0x4f, 0xca, 0xeb, 0xd1, 0x3b, 0x9d, 0x27, 0x53, 0x54, 0xde, 0x7b, 0xa8, 0xa8, 0x3c, 0x3f, 0x0c, + 0xb9, 0x53, 0xd2, 0x81, 0x47, 0x32, 0xb0, 0xf9, 0x6f, 0x81, 0x5d, 0xe9, 0xc6, 0xea, 0x5e, 0x6a, + 0x83, 0x30, 0xac, 0xa6, 0xd3, 0x11, 0x99, 0x90, 0x11, 0x9d, 0x3b, 0xe5, 0x27, 0x65, 0xf6, 0xa7, + 0xa3, 0xab, 0x33, 0x3a, 0xc7, 0x56, 0x5a, 0x6c, 0xd0, 0x2e, 0x54, 0xf9, 0xac, 0x41, 0x42, 0x4e, + 0x58, 0x14, 0x4d, 0xa8, 0xd0, 0x33, 0x50, 0xc5, 0x6b, 0xb2, 0xd8, 0xe3, 0x97, 0xba, 0x84, 0x9e, + 0x81, 0xc9, 0x67, 0x4d, 0xa9, 0xd1, 0xcd, 0xae, 0xe2, 0x15, 0x89, 0x7a, 0x5c, 0x75, 0x9a, 0xcf, + 0x48, 0x48, 0xc7, 0xfe, 0x7c, 0xd1, 0x69, 0x3e, 0xeb, 0x29, 0x88, 0x9e, 0x83, 0x15, 0x44, 0x64, + 0x9c, 0x4c, 0x84, 0xec, 0x72, 0x69, 0xaf, 0x8c, 0xcd, 0x20, 0x3a, 0x97, 0x68, 0x7f, 0x07, 0xe0, + 0xcf, 0x50, 0xa1, 0x0a, 0x94, 0xcf, 0x2f, 0x71, 0xd7, 0x5e, 0x42, 0x16, 0x94, 0xde, 0x5e, 0x9d, + 0xd9, 0xc6, 0x7e, 0x08, 0x26, 0xa6, 0xb1, 0x22, 0x37, 0x00, 0x4e, 0x07, 0xa4, 0x7d, 0xd4, 0x22, + 0x6d, 0xaf, 0x2e, 0x25, 0x12, 0x0f, 0xae, 0x48, 0xa7, 0xde, 0x24, 0x9d, 0x66, 0xdb, 0x36, 0x14, + 0x3e, 0xe9, 0x13, 0xcf, 0xeb, 0x10, 0xaf, 0xed, 0xd9, 0xcb, 0x08, 0xc0, 0x94, 0xfa, 0x83, 0x56, + 0xcb, 0x2e, 0x29, 0xae, 0x3b, 0x20, 0x9d, 0xc6, 0xa1, 0xd6, 0x96, 0xef, 0xb5, 0x07, 0x5e, 0x9d, + 0x1c, 0x36, 0xea, 0xf6, 0xca, 0xb1, 0xfd, 0xf9, 0xe7, 0xb6, 0xf1, 0x55, 0xae, 0x1f, 0x72, 0x7d, + 0xf8, 0xb5, 0xbd, 0x34, 0x34, 0xf5, 0x3f, 0xdd, 0xfa, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xf7, + 0x61, 0x93, 0x51, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 298e77516..8d605b9b2 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -42,38 +42,6 @@ message ActivationMetadata { repeated uint64 cf_list = 14; } -message PHYPayload { - MHdr mhdr = 1; - MACPayload MAC_payload = 2; - bytes mic = 3; -} - -message MHdr { - uint32 m_type = 1; - uint32 major = 2; -} - -message MACPayload { - FHdr f_hdr = 1; - uint32 f_port = 2; - bytes FRM_payload = 3; -} - -message FHdr { - bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; - FCtrl f_ctrl = 2; - uint32 f_cnt = 3; - repeated bytes f_opts = 4; -} - -message FCtrl { - bool adr = 1; - bool adr_ack_req = 2; - bool ack = 3; - bool f_pending = 4; - bytes f_opts_len = 5; -} - enum Region { EU_863_870 = 0; US_902_928 = 1; diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go new file mode 100644 index 000000000..7eb28d459 --- /dev/null +++ b/api/protocol/lorawan/validation.go @@ -0,0 +1,82 @@ +package lorawan + +// Validate implements the api.Validator interface +func (m *DeviceIdentifier) Validate() bool { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppId == "" { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *Device) Validate() bool { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppId == "" { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *Metadata) Validate() bool { + switch m.Modulation { + case Modulation_LORA: + if m.DataRate == "" { + return false + } + case Modulation_FSK: + if m.BitRate == 0 { + return false + } + } + if m.CodingRate == "" { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration) Validate() bool { + switch m.Modulation { + case Modulation_LORA: + if m.DataRate == "" { + return false + } + case Modulation_FSK: + if m.BitRate == 0 { + return false + } + } + if m.CodingRate == "" { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *ActivationMetadata) Validate() bool { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.DevAddr == nil || m.DevAddr.IsEmpty() { + return false + } + if m.NwkSKey == nil || m.NwkSKey.IsEmpty() { + return false + } + return true +} diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 012c1c75c..284da36b1 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -12,7 +12,6 @@ RxMetadata TxConfiguration ActivationMetadata - Payload */ package protocol @@ -308,104 +307,10 @@ func _ActivationMetadata_OneofSizer(msg proto.Message) (n int) { return n } -type Payload struct { - Data []byte `protobuf:"bytes,1,opt,name=data,proto3" json:"data,omitempty"` - // Types that are valid to be assigned to Protocol: - // *Payload_Lorawan - Protocol isPayload_Protocol `protobuf_oneof:"protocol"` -} - -func (m *Payload) Reset() { *m = Payload{} } -func (m *Payload) String() string { return proto.CompactTextString(m) } -func (*Payload) ProtoMessage() {} -func (*Payload) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{3} } - -type isPayload_Protocol interface { - isPayload_Protocol() - MarshalTo([]byte) (int, error) - Size() int -} - -type Payload_Lorawan struct { - Lorawan *lorawan.PHYPayload `protobuf:"bytes,2,opt,name=lorawan,oneof"` -} - -func (*Payload_Lorawan) isPayload_Protocol() {} - -func (m *Payload) GetProtocol() isPayload_Protocol { - if m != nil { - return m.Protocol - } - return nil -} - -func (m *Payload) GetLorawan() *lorawan.PHYPayload { - if x, ok := m.GetProtocol().(*Payload_Lorawan); ok { - return x.Lorawan - } - return nil -} - -// XXX_OneofFuncs is for the internal use of the proto package. -func (*Payload) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { - return _Payload_OneofMarshaler, _Payload_OneofUnmarshaler, _Payload_OneofSizer, []interface{}{ - (*Payload_Lorawan)(nil), - } -} - -func _Payload_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { - m := msg.(*Payload) - // protocol - switch x := m.Protocol.(type) { - case *Payload_Lorawan: - _ = b.EncodeVarint(2<<3 | proto.WireBytes) - if err := b.EncodeMessage(x.Lorawan); err != nil { - return err - } - case nil: - default: - return fmt.Errorf("Payload.Protocol has unexpected type %T", x) - } - return nil -} - -func _Payload_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { - m := msg.(*Payload) - switch tag { - case 2: // protocol.lorawan - if wire != proto.WireBytes { - return true, proto.ErrInternalBadWireType - } - msg := new(lorawan.PHYPayload) - err := b.DecodeMessage(msg) - m.Protocol = &Payload_Lorawan{msg} - return true, err - default: - return false, nil - } -} - -func _Payload_OneofSizer(msg proto.Message) (n int) { - m := msg.(*Payload) - // protocol - switch x := m.Protocol.(type) { - case *Payload_Lorawan: - s := proto.Size(x.Lorawan) - n += proto.SizeVarint(2<<3 | proto.WireBytes) - n += proto.SizeVarint(uint64(s)) - n += s - case nil: - default: - panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) - } - return n -} - func init() { proto.RegisterType((*RxMetadata)(nil), "protocol.RxMetadata") proto.RegisterType((*TxConfiguration)(nil), "protocol.TxConfiguration") proto.RegisterType((*ActivationMetadata)(nil), "protocol.ActivationMetadata") - proto.RegisterType((*Payload)(nil), "protocol.Payload") } func (m *RxMetadata) Marshal() (data []byte, err error) { size := m.Size() @@ -524,51 +429,6 @@ func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *Payload) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *Payload) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.Data) > 0 { - data[i] = 0xa - i++ - i = encodeVarintProtocol(data, i, uint64(len(m.Data))) - i += copy(data[i:], m.Data) - } - if m.Protocol != nil { - nn7, err := m.Protocol.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += nn7 - } - return i, nil -} - -func (m *Payload_Lorawan) MarshalTo(data []byte) (int, error) { - i := 0 - if m.Lorawan != nil { - data[i] = 0x12 - i++ - i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) - n8, err := m.Lorawan.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n8 - } - return i, nil -} func encodeFixed64Protocol(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -650,28 +510,6 @@ func (m *ActivationMetadata_Lorawan) Size() (n int) { } return n } -func (m *Payload) Size() (n int) { - var l int - _ = l - l = len(m.Data) - if l > 0 { - n += 1 + l + sovProtocol(uint64(l)) - } - if m.Protocol != nil { - n += m.Protocol.Size() - } - return n -} - -func (m *Payload_Lorawan) Size() (n int) { - var l int - _ = l - if m.Lorawan != nil { - l = m.Lorawan.Size() - n += 1 + l + sovProtocol(uint64(l)) - } - return n -} func sovProtocol(x uint64) (n int) { for { @@ -932,119 +770,6 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } return nil } -func (m *Payload) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProtocol - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Payload: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Payload: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Data", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProtocol - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthProtocol - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Data = append(m.Data[:0], data[iNdEx:postIndex]...) - if m.Data == nil { - m.Data = []byte{} - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) - } - var msglen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowProtocol - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - msglen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if msglen < 0 { - return ErrInvalidLengthProtocol - } - postIndex := iNdEx + msglen - if postIndex > l { - return io.ErrUnexpectedEOF - } - v := &lorawan.PHYPayload{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - m.Protocol = &Payload_Lorawan{v} - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipProtocol(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthProtocol - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func skipProtocol(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -1151,7 +876,7 @@ var ( ) var fileDescriptorProtocol = []byte{ - // 251 bytes of a gzipped FileDescriptorProto + // 211 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, @@ -1162,10 +887,8 @@ var fileDescriptorProtocol = []byte{ 0x8d, 0x07, 0x43, 0x10, 0x4c, 0x8d, 0x13, 0x17, 0x17, 0xdc, 0x31, 0x4a, 0xc1, 0x5c, 0xfc, 0x21, 0x15, 0xce, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0xa5, 0x45, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x42, 0x26, 0xe8, 0xa6, 0x49, 0xc0, 0x4d, 0x43, 0x53, 0x8a, 0xcb, 0xd0, 0x48, 0x2e, 0x21, 0xc7, 0xe4, 0x92, - 0xcc, 0x32, 0xb0, 0x22, 0xb8, 0x2b, 0xcd, 0xd1, 0xcd, 0x95, 0x86, 0x9b, 0x8b, 0xa9, 0x1a, 0x97, - 0xd1, 0x51, 0x5c, 0xec, 0x01, 0x89, 0x95, 0x39, 0xf9, 0x89, 0x29, 0x42, 0x42, 0x5c, 0x2c, 0x20, - 0x95, 0x60, 0xc3, 0x78, 0x82, 0xc0, 0x6c, 0x21, 0x7d, 0x84, 0x1d, 0x4c, 0x60, 0x3b, 0x84, 0xe1, - 0x76, 0x04, 0x78, 0x44, 0x42, 0x75, 0xe2, 0x30, 0xdb, 0x49, 0xe0, 0xc4, 0x23, 0x39, 0xc6, 0x0b, - 0x40, 0xfc, 0x00, 0x88, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xcb, 0x19, 0x03, 0x02, 0x00, - 0x00, 0xff, 0xff, 0xca, 0x90, 0x37, 0x29, 0xfa, 0x01, 0x00, 0x00, + 0xcc, 0x32, 0xb0, 0x22, 0xb8, 0x2b, 0xcd, 0xd1, 0xcd, 0x95, 0x86, 0x9b, 0x8b, 0xa9, 0x1a, 0x87, + 0xd1, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x00, 0xe2, 0x07, 0x40, 0x3c, 0xe3, 0xb1, 0x1c, + 0x43, 0x12, 0x1b, 0x58, 0xce, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x86, 0x56, 0x63, 0x9b, 0x9e, + 0x01, 0x00, 0x00, } diff --git a/api/protocol/protocol.proto b/api/protocol/protocol.proto index 78defddf3..d10cacfe6 100644 --- a/api/protocol/protocol.proto +++ b/api/protocol/protocol.proto @@ -24,10 +24,3 @@ message ActivationMetadata { lorawan.ActivationMetadata lorawan = 1; } } - -message Payload { - bytes data = 1; - oneof protocol { - lorawan.PHYPayload lorawan = 2; - } -} diff --git a/api/protocol/validation.go b/api/protocol/validation.go new file mode 100644 index 000000000..713438cb6 --- /dev/null +++ b/api/protocol/validation.go @@ -0,0 +1,34 @@ +package protocol + +// Validate implements the api.Validator interface +func (m *RxMetadata) Validate() bool { + switch { + case m.GetLorawan() != nil: + if !m.GetLorawan().Validate() { + return false + } + } + return true +} + +// Validate implements the api.Validator interface +func (m *TxConfiguration) Validate() bool { + switch { + case m.GetLorawan() != nil: + if !m.GetLorawan().Validate() { + return false + } + } + return true +} + +// Validate implements the api.Validator interface +func (m *ActivationMetadata) Validate() bool { + switch { + case m.GetLorawan() != nil: + if !m.GetLorawan().Validate() { + return false + } + } + return true +} diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 34462f7c8..26d485c61 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -14,10 +14,6 @@ DownlinkMessage DeviceActivationRequest DeviceActivationResponse - GatewaysRequest - GatewaysResponse - RegisterGatewayRequest - UnregisterGatewayRequest GatewayStatusRequest GatewayStatusResponse StatusRequest @@ -144,46 +140,6 @@ func (m *DeviceActivationResponse) String() string { return proto.Com func (*DeviceActivationResponse) ProtoMessage() {} func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{4} } -// message GatewaysRequest is used to list all Gateways on this Router -type GatewaysRequest struct { -} - -func (m *GatewaysRequest) Reset() { *m = GatewaysRequest{} } -func (m *GatewaysRequest) String() string { return proto.CompactTextString(m) } -func (*GatewaysRequest) ProtoMessage() {} -func (*GatewaysRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{5} } - -// message GatewaysResponse is the response to the GatewaysRequest -type GatewaysResponse struct { - GatewayIds []github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,rep,name=gateway_ids,json=gatewayIds,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_ids,omitempty"` -} - -func (m *GatewaysResponse) Reset() { *m = GatewaysResponse{} } -func (m *GatewaysResponse) String() string { return proto.CompactTextString(m) } -func (*GatewaysResponse) ProtoMessage() {} -func (*GatewaysResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{6} } - -// message RegisterGatewayRequest is used to register a Gateway with this Router -type RegisterGatewayRequest struct { - GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` -} - -func (m *RegisterGatewayRequest) Reset() { *m = RegisterGatewayRequest{} } -func (m *RegisterGatewayRequest) String() string { return proto.CompactTextString(m) } -func (*RegisterGatewayRequest) ProtoMessage() {} -func (*RegisterGatewayRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{7} } - -// message UnregisterGatewayRequest is used to unregister a Gateway from this -// Router -type UnregisterGatewayRequest struct { - GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` -} - -func (m *UnregisterGatewayRequest) Reset() { *m = UnregisterGatewayRequest{} } -func (m *UnregisterGatewayRequest) String() string { return proto.CompactTextString(m) } -func (*UnregisterGatewayRequest) ProtoMessage() {} -func (*UnregisterGatewayRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{8} } - // message GatewayStatusRequest is used to request the status of a gateway from // this Router type GatewayStatusRequest struct { @@ -193,7 +149,7 @@ type GatewayStatusRequest struct { func (m *GatewayStatusRequest) Reset() { *m = GatewayStatusRequest{} } func (m *GatewayStatusRequest) String() string { return proto.CompactTextString(m) } func (*GatewayStatusRequest) ProtoMessage() {} -func (*GatewayStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{9} } +func (*GatewayStatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{5} } type GatewayStatusResponse struct { LastSeen int64 `protobuf:"varint,1,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` @@ -203,7 +159,7 @@ type GatewayStatusResponse struct { func (m *GatewayStatusResponse) Reset() { *m = GatewayStatusResponse{} } func (m *GatewayStatusResponse) String() string { return proto.CompactTextString(m) } func (*GatewayStatusResponse) ProtoMessage() {} -func (*GatewayStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{10} } +func (*GatewayStatusResponse) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{6} } func (m *GatewayStatusResponse) GetStatus() *gateway.Status { if m != nil { @@ -219,7 +175,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{11} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{7} } // message Status is the response to the StatusRequest type Status struct { @@ -241,7 +197,7 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{12} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{8} } func (m *Status) GetGatewayStatus() *api.Rates { if m != nil { @@ -284,10 +240,6 @@ func init() { proto.RegisterType((*DownlinkMessage)(nil), "router.DownlinkMessage") proto.RegisterType((*DeviceActivationRequest)(nil), "router.DeviceActivationRequest") proto.RegisterType((*DeviceActivationResponse)(nil), "router.DeviceActivationResponse") - proto.RegisterType((*GatewaysRequest)(nil), "router.GatewaysRequest") - proto.RegisterType((*GatewaysResponse)(nil), "router.GatewaysResponse") - proto.RegisterType((*RegisterGatewayRequest)(nil), "router.RegisterGatewayRequest") - proto.RegisterType((*UnregisterGatewayRequest)(nil), "router.UnregisterGatewayRequest") proto.RegisterType((*GatewayStatusRequest)(nil), "router.GatewayStatusRequest") proto.RegisterType((*GatewayStatusResponse)(nil), "router.GatewayStatusResponse") proto.RegisterType((*StatusRequest)(nil), "router.StatusRequest") @@ -862,110 +814,6 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *GatewaysRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GatewaysRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - -func (m *GatewaysResponse) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *GatewaysResponse) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if len(m.GatewayIds) > 0 { - for _, msg := range m.GatewayIds { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *RegisterGatewayRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *RegisterGatewayRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.GatewayEui != nil { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n9, err := m.GatewayEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n9 - } - return i, nil -} - -func (m *UnregisterGatewayRequest) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *UnregisterGatewayRequest) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - if m.GatewayEui != nil { - data[i] = 0xa - i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n10, err := m.GatewayEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n10 - } - return i, nil -} - func (m *GatewayStatusRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -985,11 +833,11 @@ func (m *GatewayStatusRequest) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n11, err := m.GatewayEui.MarshalTo(data[i:]) + n9, err := m.GatewayEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n9 } return i, nil } @@ -1018,11 +866,11 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintRouter(data, i, uint64(m.Status.Size())) - n12, err := m.Status.MarshalTo(data[i:]) + n10, err := m.Status.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n10 } return i, nil } @@ -1064,11 +912,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n13, err := m.GatewayStatus.MarshalTo(data[i:]) + n11, err := m.GatewayStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n11 } if m.ActiveGateways != 0 { data[i] = 0x20 @@ -1079,11 +927,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n14, err := m.Uplink.MarshalTo(data[i:]) + n12, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n12 } if m.Downlink != nil { data[i] = 0xaa @@ -1091,11 +939,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n15, err := m.Downlink.MarshalTo(data[i:]) + n13, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n13 } if m.Activations != nil { data[i] = 0xfa @@ -1103,11 +951,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n16, err := m.Activations.MarshalTo(data[i:]) + n14, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n14 } if m.ActivationsAccepted != nil { data[i] = 0x82 @@ -1115,11 +963,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x2 i++ i = encodeVarintRouter(data, i, uint64(m.ActivationsAccepted.Size())) - n17, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n15, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n15 } if m.ConnectedGateways != 0 { data[i] = 0xc8 @@ -1239,44 +1087,6 @@ func (m *DeviceActivationResponse) Size() (n int) { return n } -func (m *GatewaysRequest) Size() (n int) { - var l int - _ = l - return n -} - -func (m *GatewaysResponse) Size() (n int) { - var l int - _ = l - if len(m.GatewayIds) > 0 { - for _, e := range m.GatewayIds { - l = e.Size() - n += 1 + l + sovRouter(uint64(l)) - } - } - return n -} - -func (m *RegisterGatewayRequest) Size() (n int) { - var l int - _ = l - if m.GatewayEui != nil { - l = m.GatewayEui.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - -func (m *UnregisterGatewayRequest) Size() (n int) { - var l int - _ = l - if m.GatewayEui != nil { - l = m.GatewayEui.Size() - n += 1 + l + sovRouter(uint64(l)) - } - return n -} - func (m *GatewayStatusRequest) Size() (n int) { var l int _ = l @@ -1959,302 +1769,6 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { } return nil } -func (m *GatewaysRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GatewaysRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GatewaysRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *GatewaysResponse) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: GatewaysResponse: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: GatewaysResponse: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayIds", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayIds = append(m.GatewayIds, v) - if err := m.GatewayIds[len(m.GatewayIds)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *RegisterGatewayRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: RegisterGatewayRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: RegisterGatewayRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayEui = &v - if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *UnregisterGatewayRequest) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: UnregisterGatewayRequest: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: UnregisterGatewayRequest: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowRouter - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - byteLen |= (int(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthRouter - } - postIndex := iNdEx + byteLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayEui = &v - if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthRouter - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *GatewayStatusRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -2867,59 +2381,55 @@ var ( ) var fileDescriptorRouter = []byte{ - // 853 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xbc, 0x56, 0x4f, 0x4f, 0x1b, 0x47, - 0x14, 0xef, 0x42, 0xb5, 0x98, 0x67, 0x1b, 0x9b, 0xc1, 0x86, 0xad, 0xdb, 0x02, 0xda, 0x43, 0x4b, - 0xff, 0x60, 0x17, 0x57, 0xa8, 0x42, 0x55, 0xab, 0x9a, 0x82, 0x10, 0x52, 0x8d, 0xaa, 0x05, 0x2e, - 0x95, 0x2a, 0x6b, 0xbc, 0x1e, 0x96, 0x95, 0xcd, 0xee, 0x76, 0x67, 0xd6, 0xc0, 0xc7, 0xe8, 0x2d, - 0x1f, 0x22, 0x1f, 0x24, 0x87, 0x1c, 0x72, 0xca, 0x21, 0x87, 0x28, 0x4a, 0xbe, 0x42, 0x2e, 0xb9, - 0x65, 0x3c, 0x3b, 0xb3, 0xde, 0x35, 0x90, 0x40, 0x12, 0x71, 0x18, 0x79, 0xe7, 0xbd, 0xdf, 0xfc, - 0xde, 0x7b, 0xf3, 0xde, 0xbc, 0x67, 0xf8, 0xc5, 0x71, 0xd9, 0x69, 0xd4, 0xad, 0xdb, 0xfe, 0x59, - 0xe3, 0xe8, 0x94, 0x1c, 0x9d, 0xba, 0x9e, 0x43, 0x0f, 0x08, 0x3b, 0xf7, 0xc3, 0x7e, 0x83, 0x31, - 0xaf, 0x81, 0x03, 0xb7, 0x11, 0xfa, 0x11, 0x23, 0xa1, 0xfc, 0xa9, 0x07, 0xa1, 0xcf, 0x7c, 0xa4, - 0xc7, 0xbb, 0xda, 0x7a, 0x8a, 0xc0, 0xf1, 0x1d, 0xbf, 0x21, 0xd4, 0xdd, 0xe8, 0x44, 0xec, 0xc4, - 0x46, 0x7c, 0xc5, 0xc7, 0x32, 0xf0, 0x1b, 0xed, 0xf1, 0x25, 0xe1, 0xbf, 0xde, 0x06, 0x2e, 0xa0, - 0xb6, 0x3f, 0x48, 0x3e, 0xe4, 0xe1, 0xad, 0xdb, 0x1c, 0x76, 0x30, 0x23, 0xe7, 0xf8, 0x52, 0xfd, - 0xc6, 0x47, 0x4d, 0x04, 0xe5, 0xc3, 0xa8, 0x4b, 0xed, 0xd0, 0xed, 0x12, 0x8b, 0xfc, 0x17, 0x11, - 0xca, 0xcc, 0x87, 0x1a, 0x14, 0x8f, 0x83, 0x81, 0xeb, 0xf5, 0xdb, 0x84, 0x52, 0xec, 0x10, 0x64, - 0xc0, 0x4c, 0x80, 0x2f, 0x07, 0x3e, 0xee, 0x19, 0xda, 0xaa, 0xb6, 0x56, 0xb0, 0xd4, 0x16, 0xb5, - 0x60, 0x5e, 0x39, 0xd3, 0x39, 0x23, 0x0c, 0xf7, 0x30, 0xc3, 0x46, 0x9e, 0x63, 0xf2, 0xcd, 0x4a, - 0x3d, 0x71, 0xd3, 0xba, 0x68, 0x4b, 0x9d, 0x55, 0x56, 0x42, 0x25, 0x41, 0xbf, 0x43, 0x59, 0xfa, - 0x34, 0x66, 0x28, 0x08, 0x86, 0x85, 0xba, 0x72, 0x36, 0x45, 0x50, 0x92, 0x32, 0x25, 0x30, 0x1f, - 0x6b, 0x50, 0xda, 0xf1, 0xcf, 0xbd, 0xdb, 0x39, 0xfc, 0x37, 0x2c, 0x26, 0x0e, 0xdb, 0xbe, 0x77, - 0xe2, 0x3a, 0x51, 0x88, 0x99, 0xeb, 0x7b, 0xd2, 0xeb, 0x2f, 0xc6, 0x5e, 0x1f, 0x5d, 0xfc, 0x99, - 0x06, 0x58, 0x55, 0xa5, 0xc9, 0x88, 0x51, 0x1b, 0xaa, 0xca, 0xff, 0x2c, 0x61, 0x1c, 0x84, 0x91, - 0x04, 0x31, 0xc9, 0x57, 0x91, 0x8a, 0x8c, 0xd4, 0x7c, 0x3a, 0x05, 0x4b, 0x3b, 0x64, 0xe8, 0xda, - 0xa4, 0x65, 0x33, 0x77, 0x18, 0x43, 0xe3, 0xcc, 0xbc, 0x23, 0xac, 0x03, 0x98, 0xe9, 0x91, 0x61, - 0x87, 0x44, 0xae, 0x88, 0xa3, 0xb0, 0xbd, 0xf9, 0xec, 0xf9, 0xca, 0xc6, 0xfb, 0xea, 0xc2, 0xf6, - 0x43, 0xd2, 0x60, 0x97, 0x01, 0xa1, 0x75, 0x6e, 0x72, 0xf7, 0x78, 0xdf, 0xd2, 0x39, 0xcb, 0x6e, - 0xe4, 0x8e, 0xf8, 0x70, 0x10, 0x08, 0xbe, 0xc2, 0x07, 0xf1, 0xb5, 0x82, 0x40, 0xf0, 0x71, 0x96, - 0x11, 0xdf, 0xb5, 0x75, 0x52, 0xfd, 0xe8, 0x3a, 0x59, 0xbc, 0x43, 0x9d, 0xd4, 0xc0, 0xb8, 0x7a, - 0xaf, 0x34, 0xf0, 0x3d, 0x4a, 0xcc, 0x79, 0x28, 0xed, 0xc5, 0x70, 0xaa, 0x5e, 0x81, 0x07, 0xe5, - 0xb1, 0x28, 0x86, 0xa1, 0x7f, 0x20, 0xaf, 0x5c, 0x70, 0x7b, 0x94, 0xe7, 0x60, 0x9a, 0xdf, 0xcc, - 0x16, 0xbf, 0x99, 0xcd, 0x3b, 0xdc, 0x8c, 0x64, 0x1d, 0xdd, 0x0e, 0x48, 0xb6, 0xfd, 0x1e, 0x35, - 0x19, 0x2c, 0x5a, 0xc4, 0x71, 0x29, 0xef, 0x35, 0x12, 0xa1, 0xb2, 0x9e, 0xb2, 0x3a, 0xca, 0x87, - 0xc8, 0xfc, 0xa7, 0xb0, 0xca, 0xf3, 0x62, 0x0e, 0xc1, 0x38, 0xf6, 0xc2, 0xfb, 0xb7, 0x1b, 0x42, - 0x45, 0x6a, 0x0e, 0x19, 0x66, 0x11, 0xbd, 0x0f, 0x9b, 0xff, 0x42, 0x75, 0xc2, 0xa6, 0x4c, 0xeb, - 0x97, 0x30, 0x3b, 0xc0, 0x94, 0x75, 0x28, 0x21, 0x9e, 0x30, 0x39, 0x6d, 0xe5, 0x46, 0x82, 0x43, - 0xbe, 0x47, 0xdf, 0x82, 0x4e, 0x05, 0xdc, 0x98, 0x12, 0xc5, 0x56, 0x4a, 0x8a, 0x4d, 0xb2, 0x48, - 0xb5, 0x59, 0x82, 0x62, 0x26, 0x16, 0xf3, 0xcd, 0x14, 0xe8, 0xb1, 0x04, 0x6d, 0xc0, 0x9c, 0x0a, - 0x4b, 0x92, 0x69, 0x82, 0x0c, 0xea, 0xa3, 0x11, 0x60, 0x71, 0x15, 0xb5, 0x8a, 0x4e, 0xda, 0x39, - 0x6e, 0xb7, 0x84, 0x47, 0x85, 0x4a, 0x3a, 0x52, 0x4e, 0x8d, 0xcf, 0xf9, 0x99, 0xa2, 0x35, 0x17, - 0x8b, 0x55, 0x71, 0x22, 0x13, 0xf4, 0x48, 0x74, 0x6b, 0xd9, 0xc1, 0xd2, 0x9c, 0x52, 0x83, 0xbe, - 0x81, 0x5c, 0x4f, 0xb6, 0x48, 0xf9, 0xea, 0xd2, 0xa8, 0x44, 0x87, 0x7e, 0x84, 0x3c, 0x4e, 0x5e, - 0x07, 0x35, 0x56, 0xae, 0x40, 0xd3, 0x6a, 0xf4, 0x1b, 0x54, 0x52, 0xdb, 0x0e, 0xb6, 0x6d, 0x12, - 0x30, 0xd2, 0x33, 0x56, 0xaf, 0x1c, 0x5b, 0x48, 0xe1, 0x5a, 0x12, 0x86, 0xd6, 0x01, 0xf1, 0x86, - 0xe9, 0x11, 0x9b, 0x6f, 0xc6, 0x41, 0x7e, 0x27, 0x82, 0x9c, 0x4f, 0x34, 0x49, 0x9c, 0x3f, 0xc0, - 0x58, 0xd8, 0xe9, 0x86, 0x7e, 0x9f, 0x84, 0xd4, 0xf8, 0x5e, 0xa0, 0xcb, 0x89, 0x62, 0x3b, 0x96, - 0x37, 0x5f, 0x6b, 0xa0, 0x5b, 0x62, 0x70, 0xf3, 0x98, 0x8a, 0x99, 0xb4, 0xa3, 0xc9, 0x0c, 0xd6, - 0x72, 0xc2, 0xd3, 0x96, 0xdd, 0x5f, 0xd3, 0xb8, 0x15, 0x3d, 0x9e, 0x7d, 0xa8, 0x5a, 0x97, 0xff, - 0x03, 0x32, 0xb3, 0x30, 0x03, 0xfe, 0x03, 0x66, 0x93, 0xe9, 0x89, 0x0c, 0x85, 0x9f, 0x1c, 0xa8, - 0xb5, 0x25, 0xa5, 0x99, 0x18, 0x53, 0x3f, 0x69, 0x7c, 0x78, 0xe4, 0x64, 0x3b, 0x22, 0x68, 0x25, - 0x81, 0x5d, 0xdf, 0xfe, 0x6b, 0xab, 0x37, 0x03, 0xe2, 0x4a, 0x6e, 0xfe, 0xcf, 0x47, 0x77, 0x1c, - 0x76, 0x1b, 0x7b, 0xdc, 0x42, 0x88, 0xfe, 0x9a, 0x8c, 0xfe, 0x2b, 0x45, 0x72, 0xdd, 0xfb, 0xab, - 0x7d, 0x7d, 0x83, 0x56, 0xbe, 0x94, 0x26, 0xcc, 0xee, 0x11, 0x26, 0x99, 0x92, 0x0b, 0xca, 0x52, - 0xcc, 0x65, 0xc5, 0xdb, 0xe5, 0x47, 0x2f, 0x97, 0xb5, 0x27, 0x7c, 0xbd, 0xe0, 0xeb, 0xc1, 0xab, - 0xe5, 0xcf, 0xba, 0xba, 0xe8, 0xed, 0x3f, 0xbf, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x9b, 0xb9, 0x48, - 0xd4, 0x94, 0x09, 0x00, 0x00, + // 797 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x05, 0xb9, 0xe9, 0x24, 0x69, 0xd2, 0x6d, 0xd2, 0x9a, 0x00, 0x6d, 0xe5, 0x03, 0x94, + 0x9f, 0x26, 0x34, 0xa8, 0x42, 0x15, 0x02, 0x91, 0xd0, 0xaa, 0x42, 0x22, 0x15, 0x72, 0xdb, 0x0b, + 0x12, 0x8a, 0x36, 0xce, 0xd6, 0xb5, 0x92, 0xda, 0xc6, 0xbb, 0xee, 0xcf, 0x63, 0x70, 0xe3, 0x21, + 0x78, 0x10, 0x0e, 0x1c, 0x38, 0x71, 0xe0, 0x80, 0x10, 0xbc, 0x02, 0x17, 0x6e, 0x6c, 0xd6, 0x6b, + 0xc7, 0x4e, 0x5b, 0x68, 0xe1, 0x60, 0xd9, 0x33, 0xf3, 0xed, 0x37, 0xf3, 0xcd, 0xee, 0x8e, 0xe1, + 0xa1, 0x65, 0xb3, 0xbd, 0xa0, 0x53, 0x35, 0xdd, 0xfd, 0xda, 0xf6, 0x1e, 0xd9, 0xde, 0xb3, 0x1d, + 0x8b, 0x6e, 0x12, 0x76, 0xe8, 0xfa, 0xbd, 0x1a, 0x63, 0x4e, 0x0d, 0x7b, 0x76, 0xcd, 0x77, 0x03, + 0x46, 0x7c, 0xf9, 0xaa, 0x7a, 0xbe, 0xcb, 0x5c, 0xa4, 0x86, 0x56, 0x65, 0x29, 0x41, 0x60, 0xb9, + 0x96, 0x5b, 0x13, 0xe1, 0x4e, 0xb0, 0x2b, 0x2c, 0x61, 0x88, 0xaf, 0x70, 0x59, 0x0a, 0x7e, 0x66, + 0x3e, 0xfe, 0x48, 0xf8, 0xa3, 0xf3, 0xc0, 0x05, 0xd4, 0x74, 0xfb, 0xf1, 0x87, 0x5c, 0xbc, 0x7a, + 0x9e, 0xc5, 0x16, 0x66, 0xe4, 0x10, 0x1f, 0x47, 0xef, 0x70, 0xa9, 0x8e, 0xa0, 0xb8, 0x15, 0x74, + 0xa8, 0xe9, 0xdb, 0x1d, 0x62, 0x90, 0x37, 0x01, 0xa1, 0x4c, 0x7f, 0xaf, 0x40, 0x7e, 0xc7, 0xeb, + 0xdb, 0x4e, 0xaf, 0x45, 0x28, 0xc5, 0x16, 0x41, 0x1a, 0x8c, 0x7b, 0xf8, 0xb8, 0xef, 0xe2, 0xae, + 0xa6, 0x2c, 0x28, 0x8b, 0x39, 0x23, 0x32, 0x51, 0x03, 0xa6, 0xa2, 0x62, 0xda, 0xfb, 0x84, 0xe1, + 0x2e, 0x66, 0x58, 0xcb, 0x72, 0x4c, 0xb6, 0x5e, 0xaa, 0xc6, 0x65, 0x1a, 0x47, 0x2d, 0x19, 0x33, + 0x8a, 0x91, 0x33, 0xf2, 0xa0, 0x27, 0x50, 0x94, 0x35, 0x0d, 0x19, 0x72, 0x82, 0x61, 0xba, 0x1a, + 0x15, 0x9b, 0x20, 0x28, 0x48, 0x5f, 0xe4, 0xd0, 0x3f, 0x2a, 0x50, 0x58, 0x73, 0x0f, 0x9d, 0xf3, + 0x15, 0xfc, 0x12, 0x66, 0xe2, 0x82, 0x4d, 0xd7, 0xd9, 0xb5, 0xad, 0xc0, 0xc7, 0xcc, 0x76, 0x1d, + 0x59, 0xf5, 0xd5, 0x61, 0xd5, 0xdb, 0x47, 0xcf, 0x92, 0x00, 0xa3, 0x1c, 0x45, 0x52, 0x6e, 0xd4, + 0x82, 0x72, 0x54, 0x7f, 0x9a, 0x30, 0x14, 0xa1, 0xc5, 0x22, 0x46, 0xf9, 0x4a, 0x32, 0x90, 0xf2, + 0xea, 0x9f, 0xc7, 0x60, 0x76, 0x8d, 0x1c, 0xd8, 0x26, 0x69, 0x98, 0xcc, 0x3e, 0x08, 0xa1, 0xe1, + 0xce, 0xfc, 0x41, 0xd6, 0x26, 0x8c, 0x77, 0xc9, 0x41, 0x9b, 0x04, 0xb6, 0xd0, 0x91, 0x6b, 0xae, + 0x7c, 0xf9, 0x3a, 0xbf, 0xfc, 0xb7, 0x73, 0x61, 0xba, 0x3e, 0xa9, 0xb1, 0x63, 0x8f, 0xd0, 0x2a, + 0x4f, 0xb9, 0xbe, 0xf3, 0xdc, 0x50, 0x39, 0xcb, 0x7a, 0x60, 0x0f, 0xf8, 0xb0, 0xe7, 0x09, 0xbe, + 0xdc, 0x3f, 0xf1, 0x35, 0x3c, 0x4f, 0xf0, 0x71, 0x96, 0x01, 0xdf, 0xa9, 0xe7, 0xa4, 0xfc, 0xdf, + 0xe7, 0x64, 0xe6, 0x02, 0xe7, 0xa4, 0x02, 0xda, 0xc9, 0xbe, 0x52, 0xcf, 0x75, 0x28, 0xd1, 0x7d, + 0x28, 0x6d, 0x84, 0xf0, 0x2d, 0x86, 0x59, 0x40, 0xa3, 0x86, 0xbf, 0x82, 0x6c, 0x94, 0x73, 0xd0, + 0x0a, 0xd1, 0xf4, 0xe6, 0x2a, 0x6f, 0xc5, 0xca, 0x05, 0x5a, 0x21, 0x99, 0x07, 0xed, 0x00, 0xc9, + 0xc6, 0x5b, 0xa2, 0xbf, 0x86, 0xf2, 0x48, 0xce, 0xb0, 0x18, 0x74, 0x0d, 0x26, 0xfa, 0x98, 0xb2, + 0x36, 0x25, 0xc4, 0x11, 0x29, 0x2f, 0x1b, 0x99, 0x81, 0x63, 0x8b, 0xdb, 0xe8, 0x16, 0xa8, 0x54, + 0xc0, 0xb5, 0x31, 0xa1, 0xbd, 0x10, 0x6b, 0x97, 0x2c, 0x32, 0xac, 0x17, 0x20, 0x9f, 0xd2, 0xa2, + 0xff, 0x1a, 0x03, 0x35, 0xf4, 0xa0, 0x65, 0x98, 0x8c, 0x64, 0x49, 0x32, 0x45, 0x90, 0x41, 0x75, + 0x30, 0x91, 0x0c, 0x1e, 0xa2, 0x46, 0xde, 0x4a, 0x16, 0xc7, 0xf3, 0x16, 0xf0, 0xa0, 0x6f, 0xa4, + 0x2d, 0xfd, 0x54, 0xbb, 0xc2, 0xd7, 0xe4, 0x8d, 0xc9, 0xd0, 0x2d, 0xa5, 0x50, 0xa4, 0x83, 0x1a, + 0x88, 0xe1, 0x21, 0x2f, 0x54, 0x92, 0x53, 0x46, 0xd0, 0x4d, 0xc8, 0x74, 0xe5, 0x8d, 0x95, 0x87, + 0x20, 0x89, 0x8a, 0x63, 0xe8, 0x1e, 0x64, 0x71, 0xbc, 0x59, 0x54, 0x9b, 0x3f, 0x01, 0x4d, 0x86, + 0xd1, 0x63, 0x28, 0x25, 0xcc, 0x36, 0x36, 0x4d, 0xe2, 0x31, 0xd2, 0xd5, 0x16, 0x4e, 0x2c, 0x9b, + 0x4e, 0xe0, 0x1a, 0x12, 0x86, 0x96, 0x00, 0xf1, 0xfb, 0xeb, 0x10, 0x93, 0x1b, 0x43, 0x91, 0xb7, + 0x85, 0xc8, 0xa9, 0x38, 0x12, 0xeb, 0xbc, 0x0b, 0x43, 0x67, 0xbb, 0xe3, 0xbb, 0x3d, 0xe2, 0x53, + 0xed, 0x8e, 0x40, 0x17, 0xe3, 0x40, 0x33, 0xf4, 0xd7, 0x7f, 0x2a, 0xa0, 0x1a, 0xe2, 0x3f, 0xc2, + 0x35, 0xe5, 0x53, 0xdb, 0x8e, 0x46, 0x77, 0xb0, 0x92, 0x11, 0x95, 0x36, 0xcc, 0xde, 0xa2, 0xc2, + 0xb3, 0xa8, 0xe1, 0x28, 0x46, 0xe5, 0xaa, 0xfc, 0x2d, 0xa5, 0x46, 0x73, 0x0a, 0xfc, 0x14, 0x26, + 0xe2, 0x61, 0x8e, 0xb4, 0x08, 0x3f, 0x3a, 0xdf, 0x2b, 0xb3, 0x51, 0x64, 0x64, 0x6a, 0xde, 0x57, + 0xf8, 0x2c, 0xcb, 0xc8, 0xdb, 0x41, 0xd0, 0x7c, 0x0c, 0x3b, 0x7d, 0x1a, 0x55, 0x16, 0xce, 0x06, + 0x84, 0x27, 0xb9, 0xfe, 0x96, 0xff, 0x49, 0x42, 0xd9, 0x2d, 0xec, 0xf0, 0x0c, 0x3e, 0x7a, 0x31, + 0xaa, 0xfe, 0x7a, 0x44, 0x72, 0xda, 0xfd, 0xab, 0xdc, 0x38, 0x23, 0x2a, 0x6f, 0x4a, 0x1d, 0x26, + 0x36, 0x08, 0x93, 0x4c, 0x71, 0x83, 0xd2, 0x14, 0x93, 0x69, 0x77, 0xb3, 0xf8, 0xe1, 0xfb, 0x9c, + 0xf2, 0x89, 0x3f, 0xdf, 0xf8, 0xf3, 0xee, 0xc7, 0xdc, 0xa5, 0x8e, 0x2a, 0x46, 0xcd, 0x83, 0xdf, + 0x01, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xce, 0x3a, 0x9a, 0x23, 0x08, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index c7727f8a4..23061c318 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -57,25 +57,6 @@ service Router { rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); } -// message GatewaysRequest is used to list all Gateways on this Router -message GatewaysRequest {} - -// message GatewaysResponse is the response to the GatewaysRequest -message GatewaysResponse { - repeated bytes gateway_ids = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; -} - -// message RegisterGatewayRequest is used to register a Gateway with this Router -message RegisterGatewayRequest { - bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; -} - -// message UnregisterGatewayRequest is used to unregister a Gateway from this -// Router -message UnregisterGatewayRequest { - bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; -} - // message GatewayStatusRequest is used to request the status of a gateway from // this Router message GatewayStatusRequest { diff --git a/api/router/validation.go b/api/router/validation.go new file mode 100644 index 000000000..3b4e29993 --- /dev/null +++ b/api/router/validation.go @@ -0,0 +1,40 @@ +package router + +// Validate implements the api.Validator interface +func (m *UplinkMessage) Validate() bool { + if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { + return false + } + if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *DownlinkMessage) Validate() bool { + if m.ProtocolConfiguration == nil || !m.ProtocolConfiguration.Validate() { + return false + } + if m.GatewayConfiguration == nil || !m.GatewayConfiguration.Validate() { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *DeviceActivationRequest) Validate() bool { + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { + return false + } + if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { + return false + } + return true +} diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index a5c400734..9ed26d8f8 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -37,6 +37,9 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if err != nil { return nil, err } + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Handler Registration") + } if !claims.CanEditApp(in.AppId) { return nil, errf(codes.Unauthenticated, "No access to this application") } diff --git a/core/broker/server.go b/core/broker/server.go index 31c4ae922..e5666c4bb 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -82,6 +82,9 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { if err != nil { return err } + if !uplink.Validate() { + return grpcErrf(codes.InvalidArgument, "Invalid Uplink") + } go b.broker.HandleUplink(uplink) } } @@ -126,6 +129,9 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { if err != nil { return err } + if !downlink.Validate() { + return grpcErrf(codes.InvalidArgument, "Invalid Downlink") + } // TODO: Validate that this handler can publish downlink for the application _ = handlerID go b.broker.HandleDownlink(downlink) @@ -137,6 +143,9 @@ func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques if err != nil { return nil, err } + if !req.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + } return b.broker.HandleActivation(req) } diff --git a/core/broker/server_test.go b/core/broker/server_test.go index 8c46630de..eb45028af 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -12,6 +12,8 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -127,6 +129,7 @@ func TestPublishRPC(t *testing.T) { appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} + gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} port := randomPort() b, s := buildTestBrokerServer(t, port) @@ -152,7 +155,10 @@ func TestPublishRPC(t *testing.T) { DevEui: &devEUI, AppEui: &appEUI, DownlinkOption: &pb.DownlinkOption{ - Identifier: "routerID:scheduleID", + Identifier: "routerID:scheduleID", + GatewayConfig: &pb_gateway.TxConfiguration{}, + ProtocolConfig: &pb_protocol.TxConfiguration{}, + GatewayEui: >wEUI, }, }) ack, err := stream.CloseAndRecv() diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 26621cee5..cb6830406 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -24,8 +24,8 @@ type handlerManager struct { var errf = grpc.Errorf func (h *handlerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { - if in.AppId == "" || in.AppEui == nil || in.DevEui == nil { - return nil, errf(codes.InvalidArgument, "AppID, AppEUI and DevEUI are required") + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } claims, err := h.Component.ValidateContext(ctx) if err != nil { @@ -133,8 +133,8 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.Device } func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*application.Application, error) { - if in.AppId == "" { - return nil, errf(codes.InvalidArgument, "AppID is required") + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") } claims, err := h.Component.ValidateContext(ctx) if err != nil { @@ -154,18 +154,11 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI } func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { - claims, err := h.Component.ValidateContext(ctx) + app, err := h.getApplication(ctx, in) if err != nil { return nil, err } - if !claims.CanEditApp(in.AppId) { - return nil, errf(codes.Unauthenticated, "No access to this application") - } - app, err := h.applications.Get(in.AppId) - if err != nil { - return nil, err - } return &pb.Application{ AppId: app.AppID, Decoder: app.Decoder, diff --git a/core/handler/server.go b/core/handler/server.go index a81a53635..cbd581512 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -10,6 +10,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/handler" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -17,6 +18,8 @@ type handlerRPC struct { handler Handler } +var grpcErrf = grpc.Errorf // To make go vet stop complaining + func validateBrokerFromMetadata(ctx context.Context) (err error) { md, ok := metadata.FromContext(ctx) // TODO: Check OK @@ -47,6 +50,9 @@ func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.Dedupli if err != nil { return nil, err } + if !activation.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + } return h.handler.HandleActivation(activation) } diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index cf3ca79c9..04dcb310d 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -22,8 +22,8 @@ type networkServerManager struct { var errf = grpc.Errorf func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { - if in.AppId == "" || in.AppEui == nil || in.DevEui == nil { - return nil, errf(codes.InvalidArgument, "AppID, AppEUI and DevEUI are required") + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } claims, err := n.Component.ValidateContext(ctx) if err != nil { diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 5df81020c..f7a54ca64 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -11,6 +11,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/networkserver" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -18,6 +19,8 @@ type networkServerRPC struct { networkServer NetworkServer } +var grpcErrf = grpc.Errorf // To make go vet stop complaining + func validateBrokerFromMetadata(ctx context.Context) (err error) { md, ok := metadata.FromContext(ctx) // TODO: Check OK @@ -48,6 +51,9 @@ func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesReques if err != nil { return nil, err } + if !req.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Devices Request") + } return s.networkServer.HandleGetDevices(req) } @@ -56,6 +62,9 @@ func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *br if err != nil { return nil, err } + if !activation.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + } return s.networkServer.HandlePrepareActivation(activation) } @@ -64,6 +73,9 @@ func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.Dev if err != nil { return nil, err } + if !activation.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + } return s.networkServer.HandleActivate(activation) } @@ -72,6 +84,9 @@ func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.Deduplica if err != nil { return nil, err } + if !message.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Uplink") + } return s.networkServer.HandleUplink(message) } @@ -80,6 +95,9 @@ func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.Downlin if err != nil { return nil, err } + if !message.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Downlink") + } return s.networkServer.HandleDownlink(message) } diff --git a/core/router/server.go b/core/router/server.go index 0e9dba9a6..4888fcd96 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -19,6 +20,8 @@ type routerRPC struct { router Router } +var grpcErrf = grpc.Errorf // To make go vet stop complaining + func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, err error) { md, ok := metadata.FromContext(ctx) // TODO: Check OK @@ -56,6 +59,9 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if err == io.EOF { return stream.SendAndClose(&api.Ack{}) } + if !status.Validate() { + return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") + } if err != nil { return err } @@ -77,6 +83,9 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if err != nil { return err } + if !uplink.Validate() { + return grpcErrf(codes.InvalidArgument, "Invalid Uplink") + } go r.router.HandleUplink(gatewayEUI, uplink) } } @@ -113,6 +122,9 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques if err != nil { return nil, err } + if !req.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + } return r.router.HandleActivation(gatewayEUI, req) } diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index cfcc7fa7e..c394d52e1 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -30,6 +30,8 @@ func newReferenceGateway(t *testing.T, region string) *gateway.Gateway { // newReferenceUplink returns a default uplink message func newReferenceUplink() *pb.UplinkMessage { + gtwEUI := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataUp, @@ -51,10 +53,11 @@ func newReferenceUplink() *pb.UplinkMessage { Modulation: pb_lorawan.Modulation_LORA, }}}, GatewayMetadata: &pb_gateway.RxMetadata{ - Timestamp: 100, - Frequency: 868100000, - Rssi: -25.0, - Snr: 5.0, + GatewayEui: >wEUI, + Timestamp: 100, + Frequency: 868100000, + Rssi: -25.0, + Snr: 5.0, }, } return up From 10deb8f5fb872f76b4ecc41a7e01190e622ce1f2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jul 2016 16:31:52 +0200 Subject: [PATCH 1553/2266] Add extra checks to Handler Activation --- core/handler/activation.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/core/handler/activation.go b/core/handler/activation.go index a39a45cb3..1ed777243 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -38,6 +38,11 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } }() + if activation.ResponseTemplate == nil { + err = errors.New("ttn/handler: No Downlink Available") + return nil, err + } + // Find Device var dev *device.Device dev, err = h.devices.Get(appEUI, devEUI) @@ -45,6 +50,11 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv return nil, err } + if dev.AppKey.IsEmpty() { + err = errors.New("ttn/handler: Can not activate device without AppKey") + return nil, err + } + // Check for LoRaWAN if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { err = errors.New("ttn/handler: Can not activate non-LoRaWAN device") From f2e6796173cc26d6a325d5bc90a5a48871754457 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jul 2016 16:33:46 +0200 Subject: [PATCH 1554/2266] Pipeline Redis calls in NS List and GetWithAddress --- core/networkserver/device/store.go | 78 ++++++++++++++++++------------ 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 99d8c05e6..a1dd06d88 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -6,7 +6,6 @@ package device import ( "errors" "fmt" - "sync" "time" "gopkg.in/redis.v3" @@ -162,23 +161,39 @@ type redisDeviceStore struct { } func (s *redisDeviceStore) List() ([]*Device, error) { - var devices []*Device keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() if err != nil { return nil, err } + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - res, err := s.client.HGetAllMap(key).Result() - if err != nil { - return nil, err - } - device := &Device{} - err = device.FromStringStringMap(res) - if err != nil { - return nil, err + cmds[key] = s.client.HGetAllMap(key) + } + + // Execute pipeline + _, err = pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + devices := make([]*Device, 0, len(keys)) + for _, cmd := range cmds { + dmap, err := cmd.Result() + if err == nil { + device := &Device{} + err := device.FromStringStringMap(dmap) + if err == nil { + devices = append(devices, device) + } } - devices = append(devices, device) } + return devices, nil } @@ -206,33 +221,32 @@ func (s *redisDeviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, err return nil, err } - // TODO: If someone finds a nice way to do this more efficiently, please submit a PR! + pipe := s.client.Pipeline() + defer pipe.Close() - var wg sync.WaitGroup - responses := make(chan *Device) + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - wg.Add(1) - go func(key string) { - dmap, err := s.client.HGetAllMap(key).Result() - if err == nil { - device := &Device{} - err := device.FromStringStringMap(dmap) - if err == nil { - responses <- device - } - } - wg.Done() - }(key) + cmds[key] = s.client.HGetAllMap(key) } - go func() { - wg.Wait() - close(responses) - }() + // Execute pipeline + _, err = pipe.Exec() + if err != nil { + return nil, err + } + // Get all results from pipeline devices := make([]*Device, 0, len(keys)) - for res := range responses { - devices = append(devices, res) + for _, cmd := range cmds { + dmap, err := cmd.Result() + if err == nil { + device := &Device{} + err := device.FromStringStringMap(dmap) + if err == nil { + devices = append(devices, device) + } + } } return devices, nil From 50642cc41b92fc4c8af1047a329ddfdab3a47651 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jul 2016 16:48:45 +0200 Subject: [PATCH 1555/2266] Pipeline Redis calls in Handler List --- core/handler/device/store.go | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/core/handler/device/store.go b/core/handler/device/store.go index c83907455..01b420a07 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -92,23 +92,39 @@ type redisDeviceStore struct { } func (s *redisDeviceStore) List() ([]*Device, error) { - var devices []*Device keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() if err != nil { return nil, err } + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - res, err := s.client.HGetAllMap(key).Result() - if err != nil { - return nil, err - } - device := &Device{} - err = device.FromStringStringMap(res) - if err != nil { - return nil, err + cmds[key] = s.client.HGetAllMap(key) + } + + // Execute pipeline + _, err = pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + devices := make([]*Device, 0, len(keys)) + for _, cmd := range cmds { + dmap, err := cmd.Result() + if err == nil { + device := &Device{} + err := device.FromStringStringMap(dmap) + if err == nil { + devices = append(devices, device) + } } - devices = append(devices, device) } + return devices, nil } From 4f595bdacac42b453f2cc1aeeb5b7daa852d577a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 13 Jul 2016 16:49:29 +0200 Subject: [PATCH 1556/2266] Add DevID to handler device --- core/handler/device/device.go | 6 ++++++ core/handler/device/device_test.go | 2 ++ 2 files changed, 8 insertions(+) diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 39843d813..d7a79ae6c 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -26,6 +26,7 @@ type Device struct { DevEUI types.DevEUI AppEUI types.AppEUI AppID string + DevID string DevAddr types.DevAddr AppKey types.AppKey UsedDevNonces []DevNonce @@ -40,6 +41,7 @@ var DeviceProperties = []string{ "dev_eui", "app_eui", "app_id", + "dev_id", "dev_addr", "app_key", "nwk_s_key", @@ -81,6 +83,8 @@ func (device *Device) formatProperty(property string) (formatted string, err err formatted = device.AppEUI.String() case "app_id": formatted = device.AppID + case "dev_id": + formatted = device.DevID case "dev_addr": formatted = device.DevAddr.String() case "app_key": @@ -130,6 +134,8 @@ func (device *Device) parseProperty(property string, value string) error { device.AppEUI = val case "app_id": device.AppID = value + case "dev_id": + device.DevID = value case "dev_addr": val, err := types.ParseDevAddr(value) if err != nil { diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index adb914b58..c88b6e365 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -16,6 +16,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, AppID: "AppID-1", + DevID: "DevID-1", DevAddr: types.DevAddr{1, 2, 3, 4}, AppKey: types.AppKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, NwkSKey: types.NwkSKey{1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8}, @@ -26,6 +27,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { "dev_eui": "0102030405060708", "app_eui": "0807060504030201", "app_id": "AppID-1", + "dev_id": "DevID-1", "dev_addr": "01020304", "app_key": "00010002000300040005000600070008", "nwk_s_key": "01010102010301040105010601070108", From 6e5893c4ba8927fb99c570bfe13eac3228b20e32 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jul 2016 14:48:27 +0200 Subject: [PATCH 1557/2266] Use AppID and DevID instead of EUIs --- api/api.go | 8 + api/broker/broker.pb.go | 218 ++++-- api/broker/broker.proto | 2 + api/handler/handler.pb.go | 824 ++++++++++++++++++++++- api/handler/handler.proto | 22 + api/handler/validation.go | 27 + api/protocol/lorawan/device.pb.go | 254 +++---- api/protocol/lorawan/device.proto | 20 +- api/protocol/lorawan/validation.go | 3 - api/protocol/validation.go | 23 +- core/broker/activation.go | 5 +- core/broker/server_test.go | 3 +- core/broker/uplink.go | 1 + core/handler/activation.go | 3 +- core/handler/activation_test.go | 6 +- core/handler/convert_fields.go | 2 +- core/handler/convert_fields_test.go | 20 +- core/handler/convert_lorawan.go | 21 +- core/handler/convert_lorawan_test.go | 24 +- core/handler/device/device_test.go | 12 +- core/handler/device/store.go | 75 ++- core/handler/device/store_test.go | 26 +- core/handler/downlink.go | 12 +- core/handler/downlink_test.go | 41 +- core/handler/manager_server.go | 143 ++-- core/handler/mqtt.go | 22 +- core/handler/mqtt_test.go | 33 +- core/handler/uplink.go | 26 +- core/handler/uplink_test.go | 24 +- core/networkserver/device/device.go | 6 + core/networkserver/device/device_test.go | 10 +- core/networkserver/manager_server.go | 11 +- core/networkserver/networkserver.go | 2 + mqtt/client.go | 73 +- mqtt/client_test.go | 178 +---- mqtt/topics.go | 45 +- mqtt/topics_test.go | 37 +- mqtt/types.go | 16 +- 38 files changed, 1582 insertions(+), 696 deletions(-) diff --git a/api/api.go b/api/api.go index a1ed4db04..8ce66b833 100644 --- a/api/api.go +++ b/api/api.go @@ -15,6 +15,14 @@ type Validator interface { Validate() bool } +// Validate the given object if it implements the Validator interface +func Validate(in interface{}) bool { + if v, ok := in.(Validator); ok { + return v.Validate() + } + return true +} + // Backoff indicates how long a client should wait between failed requests var Backoff = 1 * time.Second diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 9c525c7a5..5b2979a56 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -158,6 +158,7 @@ type DeduplicatedUplinkMessage struct { DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ServerTime int64 `protobuf:"varint,23,opt,name=server_time,json=serverTime,proto3" json:"server_time,omitempty"` @@ -240,6 +241,7 @@ type DeduplicatedDeviceActivationRequest struct { DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata []*gateway.RxMetadata `protobuf:"bytes,22,rep,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` @@ -1055,6 +1057,12 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.AppId))) i += copy(data[i:], m.AppId) } + if len(m.DevId) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } if m.ProtocolMetadata != nil { data[i] = 0xaa i++ @@ -1244,6 +1252,12 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error i = encodeVarintBroker(data, i, uint64(len(m.AppId))) i += copy(data[i:], m.AppId) } + if len(m.DevId) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } if m.ProtocolMetadata != nil { data[i] = 0xaa i++ @@ -1622,6 +1636,10 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() n += 2 + l + sovBroker(uint64(l)) @@ -1697,6 +1715,10 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() n += 2 + l + sovBroker(uint64(l)) @@ -2702,6 +2724,35 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { } m.AppId = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) @@ -3267,6 +3318,35 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { } m.AppId = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) @@ -4070,73 +4150,73 @@ var ( ) var fileDescriptorBroker = []byte{ - // 1077 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0x1b, 0x45, - 0x18, 0xc6, 0x76, 0xe3, 0xc4, 0xaf, 0xe3, 0x8f, 0x4c, 0xf3, 0xb1, 0xb5, 0x20, 0x09, 0x46, 0xaa, - 0xc2, 0x47, 0x6d, 0x30, 0x2a, 0x28, 0x42, 0x50, 0x39, 0xa4, 0x2a, 0x41, 0x72, 0xa9, 0xb6, 0x0e, - 0x07, 0x2e, 0xd6, 0x78, 0x77, 0x62, 0x8f, 0xe2, 0xec, 0x2e, 0x3b, 0xb3, 0x4e, 0xf3, 0x1f, 0x90, - 0x38, 0xc0, 0x81, 0x9f, 0xc2, 0x15, 0x71, 0xe1, 0xc8, 0x89, 0x03, 0x07, 0x84, 0xe0, 0xc2, 0xcf, - 0x60, 0x76, 0x66, 0xf6, 0xcb, 0xae, 0xdb, 0x14, 0x8a, 0x04, 0x11, 0x87, 0x95, 0x77, 0xde, 0x8f, - 0x67, 0xc6, 0xcf, 0xfb, 0x3e, 0xef, 0x0e, 0xbc, 0x3b, 0xa2, 0x7c, 0x1c, 0x0c, 0x5b, 0x96, 0x7b, - 0xd6, 0xee, 0x8f, 0x49, 0x7f, 0x4c, 0x9d, 0x11, 0xbb, 0x4f, 0xf8, 0xb9, 0xeb, 0x9f, 0xb6, 0x39, - 0x77, 0xda, 0xd8, 0xa3, 0xed, 0xa1, 0xef, 0x9e, 0x12, 0x5f, 0xff, 0xb4, 0x3c, 0xdf, 0xe5, 0x2e, - 0x2a, 0xaa, 0x55, 0xe3, 0x56, 0x0a, 0x60, 0xe4, 0x8e, 0xdc, 0xb6, 0x74, 0x0f, 0x83, 0x13, 0xb9, - 0x92, 0x0b, 0xf9, 0xa6, 0xd2, 0x32, 0xe1, 0x0b, 0xf7, 0x13, 0x8f, 0x0e, 0x7f, 0xef, 0x32, 0xe1, - 0x32, 0xd4, 0x72, 0x27, 0xf1, 0x8b, 0x4e, 0xde, 0xbf, 0x4c, 0xf2, 0x08, 0x73, 0x72, 0x8e, 0x2f, - 0xa2, 0x5f, 0x95, 0xda, 0xfc, 0x3e, 0x0f, 0xd5, 0x43, 0xf7, 0xdc, 0x99, 0x50, 0xe7, 0xf4, 0x13, - 0x8f, 0x53, 0xd7, 0x41, 0xdb, 0x00, 0xd4, 0x26, 0x0e, 0xa7, 0x27, 0x94, 0xf8, 0x46, 0x6e, 0x37, - 0xb7, 0x57, 0x32, 0x53, 0x16, 0xf4, 0x19, 0x94, 0x35, 0xc6, 0x80, 0x04, 0xd4, 0xc8, 0x8b, 0x80, - 0xd5, 0x83, 0xfd, 0x9f, 0x7f, 0xd9, 0xb9, 0xfd, 0xb4, 0x63, 0x58, 0xae, 0x4f, 0xda, 0xfc, 0xc2, - 0x23, 0xac, 0x75, 0x4f, 0x21, 0xdc, 0x3d, 0x3e, 0x32, 0x41, 0xa3, 0xdd, 0x0d, 0x28, 0x5a, 0x87, - 0x25, 0x16, 0x46, 0x19, 0x05, 0x81, 0x5a, 0x31, 0xd5, 0x02, 0x35, 0x60, 0xc5, 0x26, 0xd8, 0x16, - 0x67, 0x24, 0xc6, 0x35, 0xe1, 0x28, 0x98, 0xf1, 0x1a, 0x1d, 0x40, 0x2d, 0x62, 0x63, 0x60, 0xb9, - 0xce, 0x09, 0x1d, 0x19, 0x4b, 0x22, 0xa4, 0xdc, 0xb9, 0xd1, 0x8a, 0x59, 0xea, 0x3f, 0xfa, 0x50, - 0x7a, 0x02, 0x1f, 0x87, 0xff, 0xd0, 0xac, 0x46, 0x1e, 0x65, 0x46, 0x77, 0xa0, 0x1a, 0xfd, 0x23, - 0x0d, 0x51, 0x94, 0x10, 0x46, 0x2b, 0x22, 0x6b, 0x16, 0xa1, 0xa2, 0x1d, 0xca, 0xda, 0xfc, 0xb2, - 0x00, 0x95, 0x63, 0x2f, 0xe4, 0xb0, 0x47, 0x18, 0xc3, 0x23, 0x82, 0x0c, 0x58, 0xf6, 0xf0, 0xc5, - 0xc4, 0xc5, 0xb6, 0x64, 0x70, 0xd5, 0x8c, 0x96, 0xe8, 0x3e, 0x2c, 0xdb, 0x64, 0x2a, 0xa9, 0x2b, - 0x4b, 0xea, 0x6e, 0x0b, 0xea, 0xde, 0x7a, 0x06, 0xea, 0x0e, 0xc9, 0x34, 0xa4, 0xad, 0x28, 0x50, - 0x42, 0xca, 0x04, 0x1e, 0xf6, 0x3c, 0x89, 0xb7, 0xfa, 0x97, 0xf0, 0xba, 0x9e, 0x27, 0xf1, 0x04, - 0x4a, 0x88, 0xd7, 0x85, 0xb5, 0x98, 0xd0, 0x33, 0xc2, 0xb1, 0x8d, 0x39, 0x36, 0x36, 0x24, 0x1f, - 0xeb, 0x09, 0xa5, 0xe6, 0xa3, 0x9e, 0xf6, 0x99, 0xf5, 0xc8, 0x18, 0x59, 0xd0, 0x07, 0x50, 0x8f, - 0xf8, 0x8c, 0x11, 0x36, 0x25, 0xc2, 0xf5, 0x98, 0xd1, 0x14, 0x40, 0x4d, 0xdb, 0xe2, 0xfc, 0x2e, - 0xd4, 0x6d, 0xdd, 0x93, 0x03, 0x57, 0x36, 0x25, 0x33, 0x76, 0x76, 0x0b, 0x22, 0x7f, 0xb3, 0xa5, - 0xb5, 0x99, 0xed, 0x59, 0xb3, 0x66, 0x67, 0xd6, 0xac, 0xf9, 0x45, 0x1e, 0x6a, 0x51, 0xcc, 0x7f, - 0xbf, 0x26, 0x77, 0xa0, 0x36, 0x43, 0x88, 0xae, 0xc8, 0x22, 0x3e, 0xaa, 0x59, 0x3e, 0x9a, 0x01, - 0x18, 0xe2, 0x88, 0xd4, 0x22, 0x5d, 0x8b, 0xd3, 0xa9, 0xea, 0x61, 0xc2, 0x3c, 0xc1, 0xd4, 0x93, - 0x68, 0x79, 0xcc, 0xb6, 0xe5, 0x67, 0xda, 0xf6, 0xa7, 0x02, 0xdc, 0x38, 0x24, 0x76, 0x20, 0xa4, - 0x61, 0x89, 0x1a, 0xdb, 0x57, 0x45, 0x23, 0x1b, 0x10, 0xbe, 0x0d, 0xa8, 0x6d, 0x54, 0xe4, 0x78, - 0x5c, 0x12, 0xab, 0x23, 0xfb, 0x9f, 0x93, 0x4e, 0xe1, 0xd2, 0xd2, 0xd9, 0x81, 0x32, 0x23, 0xfe, - 0x94, 0xf8, 0x03, 0x4e, 0xcf, 0x88, 0xb1, 0x25, 0xa7, 0x25, 0x28, 0x53, 0x5f, 0x58, 0xd0, 0x21, - 0xac, 0xf9, 0xba, 0xf2, 0x03, 0x4e, 0xce, 0xbc, 0x89, 0x00, 0x10, 0xe2, 0x0a, 0xcf, 0xb8, 0x35, - 0x5b, 0x55, 0x5d, 0x28, 0xb3, 0x1e, 0x65, 0xf4, 0x75, 0x42, 0xf3, 0x8f, 0x02, 0x6c, 0xcd, 0x37, - 0xd4, 0xe7, 0x01, 0x61, 0xfc, 0xff, 0xd1, 0xf7, 0x77, 0x46, 0x5f, 0x0f, 0xae, 0xe3, 0x98, 0xd1, - 0x04, 0x62, 0x4b, 0x42, 0xbc, 0x98, 0x1c, 0x22, 0xa1, 0x3d, 0xc6, 0x42, 0x78, 0xce, 0xf6, 0x3c, - 0x26, 0xe9, 0xb7, 0xd7, 0xe0, 0x95, 0xb4, 0x86, 0xaf, 0x5e, 0xd9, 0xff, 0xbd, 0x6a, 0x7e, 0xce, - 0xdd, 0x30, 0x33, 0x1c, 0x8c, 0xb9, 0xe1, 0xd0, 0x5b, 0x3c, 0x1c, 0x76, 0xe3, 0x7e, 0x59, 0xf0, - 0x1d, 0x79, 0xcc, 0x94, 0x40, 0x50, 0x7f, 0x18, 0x0c, 0x99, 0xe5, 0xd3, 0x21, 0xd1, 0x6d, 0xd2, - 0xac, 0x41, 0xe5, 0x21, 0xc7, 0x3c, 0x60, 0x91, 0xe1, 0xbb, 0x02, 0x14, 0x95, 0x05, 0x35, 0xa1, - 0x18, 0xc8, 0x2f, 0x84, 0xec, 0xa0, 0x72, 0x07, 0x5a, 0xe1, 0x05, 0xd9, 0x14, 0x50, 0xcc, 0xd4, - 0x1e, 0xd4, 0x86, 0x8a, 0x7a, 0x1b, 0x04, 0x0e, 0x15, 0x08, 0xf2, 0xfe, 0x99, 0x0d, 0x5d, 0x55, - 0x01, 0xc7, 0xd2, 0x8f, 0x6e, 0x8a, 0xcb, 0xa3, 0x6e, 0x69, 0xfd, 0xf5, 0x4a, 0xc7, 0xc6, 0x3e, - 0xf4, 0x06, 0x94, 0x13, 0xca, 0x98, 0x2e, 0x74, 0x3a, 0x34, 0xed, 0x46, 0xfb, 0x90, 0x22, 0x98, - 0x45, 0x67, 0xd9, 0x9c, 0x4b, 0x5a, 0x4b, 0x45, 0xe9, 0x03, 0xbd, 0x0f, 0xeb, 0xe9, 0x54, 0x6c, - 0x59, 0xc4, 0x13, 0xba, 0xd2, 0x55, 0x4d, 0x27, 0xa7, 0x8a, 0xcf, 0xba, 0x3a, 0x0c, 0xbd, 0x03, - 0x15, 0x3b, 0x96, 0x63, 0xf8, 0x49, 0x56, 0xf5, 0xa9, 0xcb, 0xbc, 0x07, 0xc4, 0xb7, 0xc2, 0x8b, - 0xfa, 0x44, 0x64, 0x67, 0xc3, 0xd0, 0xeb, 0xb0, 0x26, 0x2e, 0xb7, 0x0e, 0xb1, 0x04, 0xc8, 0xc0, - 0x77, 0x03, 0x4e, 0x7c, 0x66, 0xbc, 0x2a, 0xaf, 0xd9, 0xf5, 0xd8, 0x61, 0x2a, 0x3b, 0xba, 0x05, - 0x28, 0x09, 0x1e, 0x63, 0xc7, 0x9e, 0x84, 0xd1, 0xaf, 0xc9, 0xe8, 0x04, 0xe6, 0x23, 0xed, 0x68, - 0x7e, 0x0a, 0xdb, 0x42, 0x53, 0xd1, 0x56, 0xda, 0x6c, 0x92, 0x11, 0x65, 0x5c, 0x5d, 0x98, 0x53, - 0x1a, 0xcb, 0xa5, 0x35, 0xf6, 0x12, 0x80, 0x46, 0x0f, 0x5d, 0x79, 0xe9, 0x2a, 0x69, 0xcb, 0x91, - 0xdd, 0xf9, 0x3a, 0x0f, 0xc5, 0x03, 0xd9, 0x76, 0xe2, 0x2e, 0x52, 0xea, 0x32, 0xe6, 0x5a, 0x54, - 0x30, 0x83, 0x36, 0xa2, 0x66, 0xcc, 0x5c, 0x28, 0x1a, 0x8b, 0x3e, 0x60, 0x7b, 0xb9, 0x37, 0x73, - 0xe8, 0x63, 0x28, 0xc5, 0xcd, 0x88, 0x8c, 0x28, 0x72, 0xb6, 0x3f, 0x1b, 0x2f, 0x27, 0x7d, 0xbe, - 0xe0, 0xde, 0x22, 0xb0, 0x5a, 0xb0, 0xfc, 0x20, 0x18, 0x4e, 0x28, 0x1b, 0xa3, 0x45, 0x7b, 0x36, - 0x56, 0x64, 0x41, 0xba, 0xd6, 0xe9, 0x5e, 0x4e, 0xe8, 0x6a, 0x45, 0x0b, 0x86, 0xa0, 0x9d, 0xc5, - 0x42, 0x52, 0x27, 0x78, 0xaa, 0xd2, 0x3a, 0x5f, 0xe5, 0xa0, 0xa2, 0x68, 0xe9, 0x61, 0x47, 0xec, - 0xe5, 0x8b, 0x91, 0xd8, 0x50, 0x74, 0x13, 0x7f, 0xbe, 0x10, 0xe8, 0x66, 0x84, 0xf8, 0xe4, 0x22, - 0x25, 0x47, 0x46, 0x1d, 0x28, 0xdd, 0x23, 0x5c, 0xcb, 0x32, 0x66, 0x3b, 0x23, 0xdc, 0x46, 0x35, - 0x6b, 0x3e, 0xa8, 0xff, 0xf0, 0xdb, 0x76, 0xee, 0x47, 0xf1, 0xfc, 0x2a, 0x9e, 0x6f, 0x7e, 0xdf, - 0x7e, 0x61, 0x58, 0x94, 0x03, 0xea, 0xed, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x8e, 0x73, 0x9f, - 0x4f, 0x7c, 0x0f, 0x00, 0x00, + // 1086 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xcd, 0x6f, 0x1b, 0x45, + 0x14, 0xc7, 0x76, 0xeb, 0xc4, 0xcf, 0xf1, 0x47, 0xa6, 0xf9, 0xd8, 0x5a, 0x90, 0x04, 0x23, 0x55, + 0xe1, 0xa3, 0x36, 0x18, 0x15, 0x14, 0x21, 0xa8, 0x1c, 0x52, 0x95, 0x20, 0xb9, 0x54, 0x5b, 0x87, + 0x03, 0x17, 0x6b, 0xbd, 0x3b, 0xb1, 0x47, 0x71, 0x76, 0x97, 0x9d, 0xd9, 0xa4, 0xf9, 0x1f, 0x90, + 0x38, 0xc0, 0x81, 0x7f, 0x07, 0x71, 0xe1, 0x06, 0x67, 0x0e, 0x08, 0xc1, 0x85, 0x0b, 0xff, 0x03, + 0x6f, 0x67, 0x66, 0xd7, 0xbb, 0x76, 0xdd, 0xa6, 0x50, 0x0e, 0xad, 0x38, 0xac, 0xbc, 0xf3, 0x3e, + 0x7e, 0xf3, 0xfc, 0x7b, 0x1f, 0xfb, 0xe0, 0xfd, 0x11, 0x13, 0xe3, 0x70, 0xd8, 0xb2, 0xbd, 0xd3, + 0x76, 0x7f, 0x4c, 0xfb, 0x63, 0xe6, 0x8e, 0xf8, 0x3d, 0x2a, 0xce, 0xbd, 0xe0, 0xa4, 0x2d, 0x84, + 0xdb, 0xb6, 0x7c, 0xd6, 0x1e, 0x06, 0xde, 0x09, 0x0d, 0xf4, 0x4f, 0xcb, 0x0f, 0x3c, 0xe1, 0x91, + 0xa2, 0x3a, 0x35, 0x6e, 0xa6, 0x00, 0x46, 0xde, 0xc8, 0x6b, 0x4b, 0xf5, 0x30, 0x3c, 0x96, 0x27, + 0x79, 0x90, 0x6f, 0xca, 0x2d, 0x63, 0xbe, 0xf0, 0x3e, 0x7c, 0xb4, 0xf9, 0x07, 0x97, 0x31, 0x97, + 0xa6, 0xb6, 0x37, 0x49, 0x5e, 0xb4, 0xf3, 0xde, 0x65, 0x9c, 0x47, 0x96, 0xa0, 0xe7, 0xd6, 0x45, + 0xfc, 0xab, 0x5c, 0x9b, 0x3f, 0xe4, 0xa1, 0x7a, 0xe0, 0x9d, 0xbb, 0x13, 0xe6, 0x9e, 0x7c, 0xe6, + 0x0b, 0xe6, 0xb9, 0x64, 0x0b, 0x80, 0x39, 0xd4, 0x15, 0xec, 0x98, 0xd1, 0xc0, 0xc8, 0xed, 0xe4, + 0x76, 0x4b, 0x66, 0x4a, 0x42, 0xbe, 0x80, 0xb2, 0xc6, 0x18, 0xd0, 0x90, 0x19, 0x79, 0x34, 0x58, + 0xd9, 0xdf, 0xfb, 0xe5, 0xd7, 0xed, 0x5b, 0x4f, 0x0a, 0xc3, 0xf6, 0x02, 0xda, 0x16, 0x17, 0x3e, + 0xe5, 0xad, 0xbb, 0x0a, 0xe1, 0xce, 0xd1, 0xa1, 0x09, 0x1a, 0xed, 0x4e, 0xc8, 0xc8, 0x1a, 0x5c, + 0xe5, 0x91, 0x95, 0x51, 0x40, 0xd4, 0x8a, 0xa9, 0x0e, 0xa4, 0x01, 0xcb, 0x0e, 0xb5, 0x1c, 0x8c, + 0x91, 0x1a, 0x57, 0x50, 0x51, 0x30, 0x93, 0x33, 0xd9, 0x87, 0x5a, 0xcc, 0xc6, 0xc0, 0xf6, 0xdc, + 0x63, 0x36, 0x32, 0xae, 0xa2, 0x49, 0xb9, 0x73, 0xbd, 0x95, 0xb0, 0xd4, 0x7f, 0xf8, 0xb1, 0xd4, + 0x84, 0x81, 0x15, 0xfd, 0x43, 0xb3, 0x1a, 0x6b, 0x94, 0x98, 0xdc, 0x86, 0x6a, 0xfc, 0x8f, 0x34, + 0x44, 0x51, 0x42, 0x18, 0xad, 0x98, 0xac, 0x59, 0x84, 0x8a, 0x56, 0x28, 0x69, 0xf3, 0xeb, 0x02, + 0x54, 0x8e, 0xfc, 0x88, 0xc3, 0x1e, 0xe5, 0xdc, 0x1a, 0x51, 0x62, 0xc0, 0x92, 0x6f, 0x5d, 0x4c, + 0x3c, 0xcb, 0x91, 0x0c, 0xae, 0x98, 0xf1, 0x91, 0xdc, 0x83, 0x25, 0x87, 0x9e, 0x49, 0xea, 0xca, + 0x92, 0xba, 0x5b, 0x48, 0xdd, 0x3b, 0x4f, 0x41, 0xdd, 0x01, 0x3d, 0x8b, 0x68, 0x2b, 0x22, 0x4a, + 0x44, 0x19, 0xe2, 0x59, 0xbe, 0x2f, 0xf1, 0x56, 0xfe, 0x11, 0x5e, 0xd7, 0xf7, 0x25, 0x1e, 0xa2, + 0x44, 0x78, 0x5d, 0x58, 0x4d, 0x08, 0x3d, 0xa5, 0xc2, 0x72, 0x2c, 0x61, 0x19, 0xeb, 0x92, 0x8f, + 0xb5, 0x29, 0xa5, 0xe6, 0xc3, 0x9e, 0xd6, 0x99, 0xf5, 0x58, 0x18, 0x4b, 0xc8, 0x47, 0x50, 0x8f, + 0xf9, 0x4c, 0x10, 0x36, 0x24, 0xc2, 0xb5, 0x84, 0xd1, 0x14, 0x40, 0x4d, 0xcb, 0x12, 0xff, 0x2e, + 0xd4, 0x1d, 0x5d, 0x93, 0x03, 0x4f, 0x16, 0x25, 0x37, 0xb6, 0x77, 0x0a, 0xe8, 0xbf, 0xd1, 0xd2, + 0xbd, 0x99, 0xad, 0x59, 0xb3, 0xe6, 0x64, 0xce, 0xbc, 0xf9, 0x55, 0x1e, 0x6a, 0xb1, 0xcd, 0xf3, + 0x9f, 0x93, 0xdb, 0x50, 0x9b, 0x21, 0x44, 0x67, 0x64, 0x11, 0x1f, 0xd5, 0x2c, 0x1f, 0xcd, 0x10, + 0x0c, 0x0c, 0x91, 0xd9, 0xb4, 0x6b, 0x0b, 0x76, 0xa6, 0x6a, 0x98, 0x72, 0x1f, 0x99, 0x7a, 0x1c, + 0x2d, 0x8f, 0xb8, 0xb6, 0xfc, 0x54, 0xd7, 0xfe, 0x55, 0x80, 0xeb, 0x07, 0xd4, 0x09, 0xb1, 0x35, + 0x6c, 0xcc, 0xb1, 0xf3, 0xa2, 0xf4, 0xc8, 0x3a, 0x44, 0x6f, 0x03, 0xe6, 0x18, 0x15, 0x39, 0x1e, + 0xaf, 0xe2, 0xe9, 0xd0, 0x89, 0xc4, 0x51, 0xd8, 0x28, 0xae, 0x2a, 0x31, 0x9e, 0x50, 0xfc, 0x9f, + 0x75, 0x54, 0xe1, 0xd2, 0x1d, 0xb5, 0x0d, 0x65, 0x4e, 0x83, 0x33, 0x1a, 0x0c, 0x04, 0x3b, 0xa5, + 0xc6, 0xa6, 0x1c, 0xa2, 0xa0, 0x44, 0x7d, 0x94, 0x90, 0x03, 0x58, 0x0d, 0x74, 0x41, 0x0c, 0x04, + 0x3d, 0xf5, 0x27, 0x08, 0x80, 0x3d, 0x17, 0xc5, 0xb8, 0x39, 0x9b, 0x6c, 0x9d, 0x3f, 0xb3, 0x1e, + 0x7b, 0xf4, 0xb5, 0x43, 0xf3, 0xcf, 0x02, 0x6c, 0xce, 0xd7, 0xd9, 0x97, 0x21, 0xe5, 0xe2, 0xff, + 0x89, 0xf8, 0x6f, 0x26, 0x62, 0x0f, 0xae, 0x59, 0x09, 0xa3, 0x53, 0x88, 0x4d, 0x09, 0xf1, 0xf2, + 0x34, 0x88, 0x29, 0xed, 0x09, 0x16, 0xb1, 0xe6, 0x64, 0xcf, 0x62, 0xc0, 0xfe, 0x74, 0x05, 0x5e, + 0x4b, 0xb7, 0xf6, 0x8b, 0x97, 0xf6, 0xe7, 0xae, 0xc9, 0x9f, 0x71, 0x91, 0xcc, 0xcc, 0x0c, 0x63, + 0x6e, 0x66, 0xf4, 0x16, 0xcf, 0x8c, 0x9d, 0xa4, 0x8c, 0x16, 0x7c, 0x75, 0x1e, 0x31, 0x3c, 0x08, + 0xd4, 0x1f, 0x84, 0x43, 0x6e, 0x07, 0x6c, 0x48, 0x75, 0xf5, 0x34, 0x6b, 0x50, 0x79, 0x20, 0x2c, + 0x11, 0xf2, 0x58, 0xf0, 0x7d, 0x01, 0x8a, 0x4a, 0x42, 0x9a, 0x50, 0x0c, 0xe5, 0xf7, 0x44, 0x16, + 0x56, 0xb9, 0x03, 0xad, 0x68, 0x9d, 0x36, 0x11, 0x8a, 0x9b, 0x5a, 0x43, 0xda, 0x50, 0x51, 0x6f, + 0x83, 0xd0, 0x65, 0x88, 0x20, 0xb7, 0xd5, 0xac, 0xe9, 0x8a, 0x32, 0x38, 0x92, 0x7a, 0x72, 0x03, + 0x57, 0x4d, 0x5d, 0xe9, 0xfa, 0x5b, 0x97, 0xb6, 0x4d, 0x74, 0xe4, 0x2d, 0x28, 0x4f, 0x29, 0xe3, + 0x3a, 0xd1, 0x69, 0xd3, 0xb4, 0x9a, 0xec, 0x41, 0x8a, 0x60, 0x1e, 0xc7, 0xb2, 0x31, 0xe7, 0xb4, + 0x9a, 0xb2, 0xd2, 0x01, 0x7d, 0x08, 0x6b, 0x69, 0x57, 0xcb, 0xb6, 0xa9, 0x8f, 0xed, 0xa6, 0xb3, + 0x9a, 0x76, 0x4e, 0x25, 0x9f, 0x77, 0xb5, 0x19, 0x79, 0x0f, 0x2a, 0x4e, 0xd2, 0xa5, 0xd1, 0x07, + 0x5c, 0xe5, 0xa7, 0x2e, 0xfd, 0xee, 0xd3, 0xc0, 0x8e, 0xd6, 0xfa, 0x09, 0x7a, 0x67, 0xcd, 0xc8, + 0x9b, 0xb0, 0x8a, 0xab, 0xb0, 0x4b, 0x6d, 0x04, 0x19, 0x04, 0x5e, 0x28, 0x68, 0xc0, 0x8d, 0xd7, + 0xe5, 0x52, 0x5e, 0x4f, 0x14, 0xa6, 0x92, 0x93, 0x9b, 0x40, 0xa6, 0xc6, 0x63, 0xcb, 0x75, 0x26, + 0x91, 0xf5, 0x1b, 0xd2, 0x7a, 0x0a, 0xf3, 0x89, 0x56, 0x34, 0x3f, 0x87, 0x2d, 0x6c, 0xb5, 0xf8, + 0x2a, 0x2d, 0x36, 0xe9, 0x88, 0x71, 0xa1, 0xd6, 0xeb, 0x54, 0xeb, 0xe5, 0xd2, 0xad, 0xf7, 0x0a, + 0x80, 0x46, 0x8f, 0x54, 0x79, 0xa9, 0x2a, 0x69, 0xc9, 0xa1, 0xd3, 0xf9, 0x36, 0x0f, 0xc5, 0x7d, + 0x59, 0x76, 0xb8, 0xb9, 0x94, 0xba, 0x9c, 0x7b, 0x36, 0x43, 0x66, 0xc8, 0x7a, 0x5c, 0x8c, 0x99, + 0xf5, 0xa3, 0xb1, 0xe8, 0xbb, 0xb6, 0x9b, 0x7b, 0x3b, 0x47, 0x3e, 0x85, 0x52, 0x52, 0x8c, 0xc4, + 0x88, 0x2d, 0x67, 0xeb, 0xb3, 0xf1, 0xea, 0xb4, 0xce, 0x17, 0x6c, 0x39, 0x88, 0xd5, 0x82, 0xa5, + 0xfb, 0xe1, 0x70, 0xc2, 0xf8, 0x98, 0x2c, 0xba, 0xb3, 0xb1, 0x2c, 0x13, 0xd2, 0xb5, 0x4f, 0x76, + 0x73, 0xd8, 0x57, 0xcb, 0xba, 0x61, 0x28, 0xd9, 0x5e, 0xdc, 0x48, 0x2a, 0x82, 0x27, 0x76, 0x5a, + 0xe7, 0x9b, 0x1c, 0x54, 0x14, 0x2d, 0x3d, 0xcb, 0xc5, 0xbb, 0x02, 0x9c, 0x94, 0x0d, 0x45, 0x37, + 0x0d, 0xe6, 0x13, 0x41, 0x6e, 0xc4, 0x88, 0x8f, 0x4f, 0xd2, 0x34, 0x64, 0xd2, 0x81, 0xd2, 0x5d, + 0x2a, 0x74, 0x5b, 0x26, 0x6c, 0x67, 0x1a, 0xb7, 0x51, 0xcd, 0x8a, 0xf7, 0xeb, 0x3f, 0xfe, 0xbe, + 0x95, 0xfb, 0x19, 0x9f, 0xdf, 0xf0, 0xf9, 0xee, 0x8f, 0xad, 0x97, 0x86, 0x45, 0x39, 0xa0, 0xde, + 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x54, 0xb6, 0xf0, 0x3b, 0xaa, 0x0f, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 9a47ff27a..5060d30fc 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -51,6 +51,7 @@ message DeduplicatedUplinkMessage { bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; string app_id = 13; + string dev_id = 14; protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; int64 server_time = 23; @@ -74,6 +75,7 @@ message DeduplicatedDeviceActivationRequest { bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; string app_id = 13; + string dev_id = 14; protocol.RxMetadata protocol_metadata = 21; repeated gateway.RxMetadata gateway_metadata = 22; protocol.ActivationMetadata activation_metadata = 23; diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 9b9efc050..b23b59cf4 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -14,6 +14,9 @@ Status ApplicationIdentifier Application + DeviceIdentifier + Device + DeviceList */ package handler @@ -23,6 +26,7 @@ import math "math" import api "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" +import lorawan1 "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" import ( context "golang.org/x/net/context" @@ -105,12 +109,135 @@ func (m *Application) String() string { return proto.CompactTextStrin func (*Application) ProtoMessage() {} func (*Application) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{4} } +type DeviceIdentifier struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,2,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` +} + +func (m *DeviceIdentifier) Reset() { *m = DeviceIdentifier{} } +func (m *DeviceIdentifier) String() string { return proto.CompactTextString(m) } +func (*DeviceIdentifier) ProtoMessage() {} +func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } + +type Device struct { + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,2,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + // Types that are valid to be assigned to Device: + // *Device_LorawanDevice + Device isDevice_Device `protobuf_oneof:"device"` +} + +func (m *Device) Reset() { *m = Device{} } +func (m *Device) String() string { return proto.CompactTextString(m) } +func (*Device) ProtoMessage() {} +func (*Device) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{6} } + +type isDevice_Device interface { + isDevice_Device() + MarshalTo([]byte) (int, error) + Size() int +} + +type Device_LorawanDevice struct { + LorawanDevice *lorawan1.Device `protobuf:"bytes,3,opt,name=lorawan_device,json=lorawanDevice,oneof"` +} + +func (*Device_LorawanDevice) isDevice_Device() {} + +func (m *Device) GetDevice() isDevice_Device { + if m != nil { + return m.Device + } + return nil +} + +func (m *Device) GetLorawanDevice() *lorawan1.Device { + if x, ok := m.GetDevice().(*Device_LorawanDevice); ok { + return x.LorawanDevice + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Device) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Device_OneofMarshaler, _Device_OneofUnmarshaler, _Device_OneofSizer, []interface{}{ + (*Device_LorawanDevice)(nil), + } +} + +func _Device_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Device) + // device + switch x := m.Device.(type) { + case *Device_LorawanDevice: + _ = b.EncodeVarint(3<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.LorawanDevice); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Device.Device has unexpected type %T", x) + } + return nil +} + +func _Device_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Device) + switch tag { + case 3: // device.lorawan_device + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan1.Device) + err := b.DecodeMessage(msg) + m.Device = &Device_LorawanDevice{msg} + return true, err + default: + return false, nil + } +} + +func _Device_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Device) + // device + switch x := m.Device.(type) { + case *Device_LorawanDevice: + s := proto.Size(x.LorawanDevice) + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type DeviceList struct { + Devices []*Device `protobuf:"bytes,1,rep,name=devices" json:"devices,omitempty"` +} + +func (m *DeviceList) Reset() { *m = DeviceList{} } +func (m *DeviceList) String() string { return proto.CompactTextString(m) } +func (*DeviceList) ProtoMessage() {} +func (*DeviceList) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{7} } + +func (m *DeviceList) GetDevices() []*Device { + if m != nil { + return m.Devices + } + return nil +} + func init() { proto.RegisterType((*DeviceActivationResponse)(nil), "handler.DeviceActivationResponse") proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") proto.RegisterType((*Status)(nil), "handler.Status") proto.RegisterType((*ApplicationIdentifier)(nil), "handler.ApplicationIdentifier") proto.RegisterType((*Application)(nil), "handler.Application") + proto.RegisterType((*DeviceIdentifier)(nil), "handler.DeviceIdentifier") + proto.RegisterType((*Device)(nil), "handler.Device") + proto.RegisterType((*DeviceList)(nil), "handler.DeviceList") } // Reference imports to suppress errors if they are not otherwise used. @@ -191,6 +318,10 @@ type ApplicationManagerClient interface { GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*api.Ack, error) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) + GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) + GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) } type applicationManagerClient struct { @@ -228,12 +359,52 @@ func (c *applicationManagerClient) DeleteApplication(ctx context.Context, in *Ap return out, nil } +func (c *applicationManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) { + out := new(Device) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/SetDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DeleteDevice", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) { + out := new(DeviceList) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetDevicesForApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for ApplicationManager service type ApplicationManagerServer interface { GetApplication(context.Context, *ApplicationIdentifier) (*Application, error) SetApplication(context.Context, *Application) (*api.Ack, error) DeleteApplication(context.Context, *ApplicationIdentifier) (*api.Ack, error) + GetDevice(context.Context, *DeviceIdentifier) (*Device, error) + SetDevice(context.Context, *Device) (*api.Ack, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*api.Ack, error) + GetDevicesForApplication(context.Context, *ApplicationIdentifier) (*DeviceList, error) } func RegisterApplicationManagerServer(s *grpc.Server, srv ApplicationManagerServer) { @@ -294,6 +465,78 @@ func _ApplicationManager_DeleteApplication_Handler(srv interface{}, ctx context. return interceptor(ctx, in, info, handler) } +func _ApplicationManager_GetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).GetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/GetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).GetDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_SetDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(Device) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).SetDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/SetDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).SetDevice(ctx, req.(*Device)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DeleteDevice_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeviceIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DeleteDevice(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DeleteDevice", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DeleteDevice(ctx, req.(*DeviceIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_GetDevicesForApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).GetDevicesForApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/GetDevicesForApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).GetDevicesForApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "handler.ApplicationManager", HandlerType: (*ApplicationManagerServer)(nil), @@ -310,6 +553,22 @@ var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ MethodName: "DeleteApplication", Handler: _ApplicationManager_DeleteApplication_Handler, }, + { + MethodName: "GetDevice", + Handler: _ApplicationManager_GetDevice_Handler, + }, + { + MethodName: "SetDevice", + Handler: _ApplicationManager_SetDevice_Handler, + }, + { + MethodName: "DeleteDevice", + Handler: _ApplicationManager_DeleteDevice_Handler, + }, + { + MethodName: "GetDevicesForApplication", + Handler: _ApplicationManager_GetDevicesForApplication_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: fileDescriptorHandler, @@ -533,6 +792,117 @@ func (m *Application) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *DeviceIdentifier) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceIdentifier) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.DevId) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } + return i, nil +} + +func (m *Device) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *Device) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.AppId) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.DevId) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } + if m.Device != nil { + nn3, err := m.Device.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn3 + } + return i, nil +} + +func (m *Device_LorawanDevice) MarshalTo(data []byte) (int, error) { + i := 0 + if m.LorawanDevice != nil { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(m.LorawanDevice.Size())) + n4, err := m.LorawanDevice.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + return i, nil +} +func (m *DeviceList) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DeviceList) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Devices) > 0 { + for _, msg := range m.Devices { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + func encodeFixed64Handler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -626,6 +996,58 @@ func (m *Application) Size() (n int) { return n } +func (m *DeviceIdentifier) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *Device) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.Device != nil { + n += m.Device.Size() + } + return n +} + +func (m *Device_LorawanDevice) Size() (n int) { + var l int + _ = l + if m.LorawanDevice != nil { + l = m.LorawanDevice.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} +func (m *DeviceList) Size() (n int) { + var l int + _ = l + if len(m.Devices) > 0 { + for _, e := range m.Devices { + l = e.Size() + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + func sovHandler(x uint64) (n int) { for { n++ @@ -1160,6 +1582,335 @@ func (m *Application) Unmarshal(data []byte) error { } return nil } +func (m *DeviceIdentifier) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceIdentifier: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceIdentifier: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Device) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Device: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Device: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LorawanDevice", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan1.Device{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Device = &Device_LorawanDevice{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DeviceList) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DeviceList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DeviceList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Devices", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Devices = append(m.Devices, &Device{}) + if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipHandler(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -1266,36 +2017,45 @@ var ( ) var fileDescriptorHandler = []byte{ - // 491 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0x4b, 0x8b, 0x13, 0x41, - 0x10, 0x76, 0x7c, 0xe4, 0x51, 0xd1, 0x44, 0x5b, 0x77, 0x1d, 0x86, 0x25, 0xe8, 0x9c, 0x04, 0x71, - 0x22, 0x51, 0x10, 0x11, 0x91, 0x48, 0x50, 0xf7, 0x10, 0x85, 0xd9, 0x3d, 0x79, 0x09, 0x9d, 0xe9, - 0x32, 0x69, 0x32, 0xdb, 0xdd, 0xce, 0x74, 0xb2, 0xe8, 0x2f, 0xf1, 0x27, 0x79, 0x11, 0xbc, 0x7b, - 0x11, 0xfd, 0x23, 0x76, 0xe6, 0xd1, 0x3b, 0x71, 0x13, 0xc8, 0x1e, 0x9a, 0x9e, 0xaa, 0xaf, 0x1e, - 0xdf, 0x57, 0x5d, 0x03, 0xcf, 0xa7, 0x5c, 0xcf, 0x16, 0x93, 0x20, 0x92, 0x27, 0xbd, 0xe3, 0x19, - 0x1e, 0xcf, 0xb8, 0x98, 0xa6, 0xef, 0x51, 0x9f, 0xca, 0x64, 0xde, 0xd3, 0x5a, 0xf4, 0xa8, 0xe2, - 0xbd, 0x19, 0x15, 0x2c, 0xc6, 0xa4, 0xbc, 0x03, 0x95, 0x48, 0x2d, 0x49, 0xbd, 0x30, 0xbd, 0x47, - 0xbb, 0xd4, 0x30, 0x27, 0xcf, 0xf3, 0x9e, 0xed, 0x12, 0x3e, 0x49, 0xe4, 0xdc, 0x74, 0xcc, 0xaf, - 0x22, 0xf1, 0xc5, 0x2e, 0x89, 0x59, 0x68, 0x24, 0x63, 0xfb, 0x91, 0x27, 0xfb, 0xbf, 0x1c, 0x70, - 0x87, 0xb8, 0xe4, 0x11, 0x0e, 0x22, 0xcd, 0x97, 0x54, 0x73, 0x29, 0x42, 0x4c, 0x95, 0x14, 0x29, - 0x12, 0x17, 0xea, 0x8a, 0x7e, 0x89, 0x25, 0x65, 0xae, 0x73, 0xcf, 0x79, 0x70, 0x3d, 0x2c, 0x4d, - 0xb2, 0x07, 0x35, 0xaa, 0xd4, 0x98, 0x33, 0xf7, 0xb2, 0x01, 0x9a, 0xe1, 0x35, 0x63, 0x1d, 0x32, - 0xf2, 0x0a, 0x3a, 0x4c, 0x9e, 0x8a, 0x98, 0x8b, 0xf9, 0x58, 0xaa, 0x55, 0x2d, 0xb7, 0x65, 0xf0, - 0x56, 0x7f, 0x3f, 0x28, 0x28, 0x0f, 0x0b, 0xf8, 0x43, 0x86, 0x86, 0x6d, 0xb6, 0x66, 0x93, 0x11, - 0xdc, 0xa6, 0x96, 0xc7, 0xf8, 0x04, 0x35, 0x65, 0x54, 0x53, 0xf7, 0x6e, 0x56, 0xe4, 0x20, 0xb0, - 0xe4, 0xcf, 0xc8, 0x8e, 0x8a, 0x98, 0x90, 0xd0, 0x73, 0x3e, 0xbf, 0x03, 0x37, 0x8e, 0x34, 0xd5, - 0x8b, 0x34, 0xc4, 0xcf, 0x0b, 0x4c, 0xb5, 0xdf, 0x80, 0x5a, 0xee, 0xf0, 0x03, 0xd8, 0x1b, 0x28, - 0x15, 0xf3, 0x28, 0xcb, 0x38, 0x64, 0x28, 0x34, 0xff, 0xc4, 0x31, 0xa9, 0x48, 0x73, 0x2a, 0xd2, - 0xfc, 0xaf, 0xd0, 0xaa, 0xc4, 0x6f, 0x89, 0x5a, 0x4d, 0x8c, 0x61, 0x24, 0x19, 0x26, 0xc5, 0x60, - 0x4a, 0x93, 0x1c, 0x40, 0x33, 0x92, 0x62, 0x89, 0x89, 0x36, 0xd8, 0x95, 0x0c, 0x3b, 0x73, 0xac, - 0xd0, 0x25, 0x8d, 0xb9, 0x21, 0x2d, 0x13, 0xf7, 0x6a, 0x8e, 0x5a, 0x47, 0x1f, 0xa1, 0xfe, 0x2e, - 0x5f, 0x2a, 0xf2, 0x11, 0x1a, 0x85, 0x76, 0x24, 0x0f, 0xed, 0x50, 0x91, 0x2d, 0x72, 0x6a, 0xc8, - 0xce, 0x3f, 0x66, 0xa6, 0xdc, 0xbb, 0x1f, 0x94, 0x6b, 0xba, 0xed, 0xb9, 0xfb, 0x3f, 0x1c, 0x20, - 0x15, 0x8d, 0x23, 0x2a, 0xe8, 0xd4, 0xb4, 0x7c, 0x03, 0xed, 0xb7, 0xa8, 0xab, 0xe2, 0xbb, 0xb6, - 0xd6, 0xc6, 0x11, 0x7a, 0x77, 0x36, 0xe1, 0xe4, 0x31, 0xb4, 0x8f, 0xd6, 0xeb, 0x6c, 0x8c, 0xf3, - 0x1a, 0xc1, 0xea, 0xa7, 0x18, 0x44, 0x73, 0xf2, 0x12, 0x6e, 0x0d, 0x31, 0x46, 0x8d, 0x17, 0x69, - 0x6e, 0xd3, 0xfb, 0x86, 0x78, 0x31, 0xb6, 0x52, 0xca, 0x53, 0x68, 0x1a, 0x29, 0xf9, 0x06, 0x90, - 0x7d, 0x5b, 0x68, 0x6d, 0x47, 0xbc, 0xce, 0x7f, 0xfe, 0xd7, 0x37, 0xbf, 0xff, 0xe9, 0x3a, 0x3f, - 0xcd, 0xf9, 0x6d, 0xce, 0xb7, 0xbf, 0xdd, 0x4b, 0x93, 0x5a, 0xb6, 0x88, 0x4f, 0xfe, 0x05, 0x00, - 0x00, 0xff, 0xff, 0x38, 0xc6, 0xdf, 0x31, 0x27, 0x04, 0x00, 0x00, + // 633 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0x5e, 0x19, 0x74, 0xed, 0xe9, 0xd6, 0x0d, 0x8f, 0x8d, 0x50, 0x4d, 0x13, 0xe4, 0x02, 0x81, + 0x10, 0x29, 0xea, 0x90, 0x06, 0x42, 0x08, 0x3a, 0x4d, 0x63, 0x93, 0x18, 0x93, 0xd2, 0x5d, 0x71, + 0x53, 0xb9, 0xb1, 0x69, 0xad, 0x66, 0x71, 0x48, 0xdc, 0x56, 0xf0, 0x24, 0x3c, 0x08, 0x0f, 0xc1, + 0x25, 0xf7, 0xdc, 0x20, 0x78, 0x11, 0x1c, 0xdb, 0x49, 0xd3, 0x1f, 0xb4, 0xf6, 0x22, 0x4a, 0xce, + 0xf9, 0xce, 0x77, 0x7e, 0xfc, 0x9d, 0x18, 0x5e, 0x76, 0x99, 0xe8, 0x0d, 0x3a, 0x8e, 0xc7, 0xaf, + 0xea, 0x97, 0x3d, 0x7a, 0xd9, 0x63, 0x41, 0x37, 0xfe, 0x40, 0xc5, 0x88, 0x47, 0xfd, 0xba, 0x10, + 0x41, 0x1d, 0x87, 0xac, 0xde, 0xc3, 0x01, 0xf1, 0x69, 0x94, 0xbe, 0x9d, 0x30, 0xe2, 0x82, 0xa3, + 0x35, 0x63, 0xd6, 0x9e, 0x2e, 0x92, 0x43, 0x3e, 0x9a, 0x57, 0x3b, 0x5c, 0x24, 0xbc, 0x13, 0xf1, + 0xbe, 0xac, 0xa8, 0x5f, 0x86, 0xf8, 0x6a, 0x11, 0xa2, 0x0a, 0xf5, 0xb8, 0x9f, 0x7d, 0x18, 0x72, + 0x73, 0x29, 0xb2, 0xcf, 0x23, 0x3c, 0xc2, 0x41, 0x9d, 0xd0, 0x21, 0xf3, 0xa8, 0x4e, 0x61, 0xff, + 0x2a, 0x80, 0x75, 0xac, 0x1c, 0x4d, 0x4f, 0xb0, 0x21, 0x16, 0x8c, 0x07, 0x2e, 0x8d, 0x43, 0x1e, + 0xc4, 0x14, 0x59, 0xb0, 0x16, 0xe2, 0x2f, 0x3e, 0xc7, 0xc4, 0x2a, 0xdc, 0x2f, 0x3c, 0x5a, 0x77, + 0x53, 0x13, 0xed, 0x40, 0x11, 0x87, 0x61, 0x9b, 0x11, 0xeb, 0x86, 0x04, 0xca, 0xee, 0x2d, 0x69, + 0x9d, 0x11, 0xf4, 0x06, 0x36, 0x09, 0x1f, 0x05, 0x3e, 0x0b, 0xfa, 0x6d, 0x1e, 0x26, 0xb9, 0xac, + 0x8a, 0xc4, 0x2b, 0x8d, 0x5d, 0xc7, 0x4c, 0x7d, 0x6c, 0xe0, 0x0b, 0x85, 0xba, 0x55, 0x32, 0x61, + 0xa3, 0x73, 0xd8, 0xc6, 0x59, 0x1f, 0xed, 0x2b, 0x2a, 0x30, 0xc1, 0x02, 0x5b, 0x77, 0x55, 0x92, + 0x3d, 0x27, 0x9b, 0x7f, 0xdc, 0xec, 0xb9, 0x89, 0x71, 0x11, 0x9e, 0xf1, 0xd9, 0x9b, 0xb0, 0xd1, + 0x12, 0x58, 0x0c, 0x62, 0x97, 0x7e, 0x1e, 0xd0, 0x58, 0xd8, 0x25, 0x28, 0x6a, 0x87, 0xed, 0xc0, + 0x4e, 0x33, 0x0c, 0x7d, 0xe6, 0x29, 0xc6, 0x19, 0xa1, 0x81, 0x60, 0x9f, 0x18, 0x8d, 0x72, 0xa3, + 0x15, 0x72, 0xa3, 0xd9, 0x5f, 0xa1, 0x92, 0x8b, 0xff, 0x4f, 0x54, 0x72, 0x62, 0x84, 0x7a, 0x9c, + 0xd0, 0xc8, 0x1c, 0x4c, 0x6a, 0xa2, 0x3d, 0x28, 0x7b, 0x3c, 0x18, 0xd2, 0x48, 0x48, 0x6c, 0x55, + 0x61, 0x63, 0x47, 0x82, 0x0e, 0xb1, 0xcf, 0x64, 0xd3, 0x3c, 0xb2, 0x6e, 0x6a, 0x34, 0x73, 0xd8, + 0x6f, 0x61, 0x4b, 0x6b, 0x74, 0x6d, 0x9b, 0x89, 0x5b, 0xea, 0x9b, 0x13, 0x46, 0x5a, 0xaa, 0xfb, + 0xa2, 0xce, 0xb0, 0x1c, 0x0f, 0xbd, 0x80, 0xaa, 0x59, 0x9b, 0xb6, 0x5e, 0x1b, 0xd5, 0x7a, 0xa5, + 0xb1, 0xe9, 0x18, 0xb7, 0xa3, 0xd3, 0x9e, 0xae, 0xb8, 0x1b, 0xc6, 0xa3, 0x1d, 0x47, 0x25, 0x95, + 0x50, 0x7e, 0xd9, 0x87, 0x00, 0xda, 0xf7, 0x9e, 0xc5, 0x02, 0x3d, 0x4e, 0x4e, 0x28, 0xb1, 0x62, + 0xd9, 0xc0, 0xaa, 0x4a, 0x95, 0xfe, 0x82, 0x3a, 0xca, 0x4d, 0xf1, 0x06, 0x85, 0xb5, 0x53, 0x0d, + 0xa1, 0x8f, 0x50, 0x32, 0x92, 0x53, 0xf4, 0x24, 0xdb, 0x25, 0x4a, 0x06, 0x5a, 0x11, 0x4a, 0x66, + 0x77, 0x58, 0x09, 0x5e, 0x7b, 0x30, 0x95, 0x7d, 0x76, 0xcb, 0x1b, 0xdf, 0x57, 0x01, 0xe5, 0xa4, + 0x3d, 0xc7, 0x01, 0xee, 0xca, 0x92, 0x27, 0x50, 0x7d, 0x47, 0x45, 0x5e, 0xf3, 0xfd, 0x2c, 0xd7, + 0xdc, 0xcd, 0xa9, 0xdd, 0x99, 0x87, 0xa3, 0x67, 0x50, 0x6d, 0x4d, 0xe6, 0x99, 0x1b, 0x57, 0x2b, + 0x39, 0xc9, 0x75, 0xd2, 0xf4, 0xfa, 0xe8, 0x35, 0xdc, 0x3e, 0xa6, 0x3e, 0x15, 0x74, 0x99, 0xe2, + 0x63, 0xfa, 0x21, 0x94, 0x65, 0xe3, 0x46, 0xee, 0x7b, 0x53, 0xf3, 0xe7, 0x18, 0xd3, 0x07, 0x8f, + 0x1e, 0x42, 0xb9, 0x95, 0x11, 0xa7, 0xd1, 0x5c, 0x81, 0x03, 0x58, 0xd7, 0xfd, 0x5d, 0x5f, 0x63, + 0x4c, 0xba, 0x00, 0x2b, 0xeb, 0x2a, 0x3e, 0xe1, 0xd1, 0x32, 0xb3, 0x6d, 0x4f, 0x15, 0x48, 0x16, + 0xa9, 0x21, 0xf5, 0x31, 0xdb, 0x91, 0x2a, 0xf6, 0x5c, 0x0d, 0xae, 0xff, 0x6f, 0xb4, 0x9b, 0x71, + 0x26, 0x6e, 0x80, 0xdc, 0xd4, 0xda, 0x7f, 0xb4, 0xf5, 0xe3, 0xcf, 0x7e, 0xe1, 0xa7, 0x7c, 0x7e, + 0xcb, 0xe7, 0xdb, 0xdf, 0xfd, 0x95, 0x4e, 0x51, 0x5d, 0x33, 0x07, 0xff, 0x02, 0x00, 0x00, 0xff, + 0xff, 0x2d, 0x69, 0xa7, 0x71, 0x48, 0x06, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 2c0751653..89e961983 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -6,6 +6,7 @@ syntax = "proto3"; import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; +import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto"; package handler; @@ -38,10 +39,31 @@ message Application { string validator = 4; } +message DeviceIdentifier { + string app_id = 1; + string dev_id = 2; +} + +message Device { + string app_id = 1; + string dev_id = 2; + oneof device { + lorawan.Device lorawan_device = 3; + } +} + +message DeviceList { + repeated Device devices = 1; +} + service ApplicationManager { rpc GetApplication(ApplicationIdentifier) returns (Application); rpc SetApplication(Application) returns (api.Ack); rpc DeleteApplication(ApplicationIdentifier) returns (api.Ack); + rpc GetDevice(DeviceIdentifier) returns (Device); + rpc SetDevice(Device) returns (api.Ack); + rpc DeleteDevice(DeviceIdentifier) returns (api.Ack); + rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList); } // The HandlerManager service provides configuration and monitoring diff --git a/api/handler/validation.go b/api/handler/validation.go index 627d14671..822d2730b 100644 --- a/api/handler/validation.go +++ b/api/handler/validation.go @@ -1,5 +1,7 @@ package handler +import "github.com/TheThingsNetwork/ttn/api" + // Validate implements the api.Validator interface func (m *DeviceActivationResponse) Validate() bool { if m.AppId == "" { @@ -29,3 +31,28 @@ func (m *Application) Validate() bool { } return true } + +// Validate implements the api.Validator interface +func (m *DeviceIdentifier) Validate() bool { + if m.AppId == "" { + return false + } + if m.DevId == "" { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *Device) Validate() bool { + if m.AppId == "" { + return false + } + if m.DevId == "" { + return false + } + if m.Device == nil || !api.Validate(m.Device) { + return false + } + return true +} diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index d11f3b44a..22bf6cc59 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -45,7 +45,6 @@ const _ = proto.ProtoPackageIsVersion1 type DeviceIdentifier struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` } func (m *DeviceIdentifier) Reset() { *m = DeviceIdentifier{} } @@ -54,15 +53,16 @@ func (*DeviceIdentifier) ProtoMessage() {} func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorDevice, []int{0} } type Device struct { - AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,2,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,3,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,4,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,5,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - AppSKey *github_com_TheThingsNetwork_ttn_core_types.AppSKey `protobuf:"bytes,6,opt,name=app_s_key,json=appSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppSKey" json:"app_s_key,omitempty"` - AppKey *github_com_TheThingsNetwork_ttn_core_types.AppKey `protobuf:"bytes,7,opt,name=app_key,json=appKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppKey" json:"app_key,omitempty"` - FCntUp uint32 `protobuf:"varint,8,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` - FCntDown uint32 `protobuf:"varint,9,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,4,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,5,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,6,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + AppSKey *github_com_TheThingsNetwork_ttn_core_types.AppSKey `protobuf:"bytes,7,opt,name=app_s_key,json=appSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppSKey" json:"app_s_key,omitempty"` + AppKey *github_com_TheThingsNetwork_ttn_core_types.AppKey `protobuf:"bytes,8,opt,name=app_key,json=appKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppKey" json:"app_key,omitempty"` + FCntUp uint32 `protobuf:"varint,9,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` + FCntDown uint32 `protobuf:"varint,10,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` // Options DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` @@ -253,12 +253,6 @@ func (m *DeviceIdentifier) MarshalTo(data []byte) (int, error) { } i += n2 } - if len(m.AppId) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintDevice(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) - } return i, nil } @@ -277,14 +271,8 @@ func (m *Device) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.AppId) > 0 { - data[i] = 0xa - i++ - i = encodeVarintDevice(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) - } if m.AppEui != nil { - data[i] = 0x12 + data[i] = 0xa i++ i = encodeVarintDevice(data, i, uint64(m.AppEui.Size())) n3, err := m.AppEui.MarshalTo(data[i:]) @@ -294,7 +282,7 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += n3 } if m.DevEui != nil { - data[i] = 0x1a + data[i] = 0x12 i++ i = encodeVarintDevice(data, i, uint64(m.DevEui.Size())) n4, err := m.DevEui.MarshalTo(data[i:]) @@ -303,9 +291,21 @@ func (m *Device) MarshalTo(data []byte) (int, error) { } i += n4 } - if m.DevAddr != nil { + if len(m.AppId) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintDevice(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.DevId) > 0 { data[i] = 0x22 i++ + i = encodeVarintDevice(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } + if m.DevAddr != nil { + data[i] = 0x2a + i++ i = encodeVarintDevice(data, i, uint64(m.DevAddr.Size())) n5, err := m.DevAddr.MarshalTo(data[i:]) if err != nil { @@ -314,7 +314,7 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += n5 } if m.NwkSKey != nil { - data[i] = 0x2a + data[i] = 0x32 i++ i = encodeVarintDevice(data, i, uint64(m.NwkSKey.Size())) n6, err := m.NwkSKey.MarshalTo(data[i:]) @@ -324,7 +324,7 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += n6 } if m.AppSKey != nil { - data[i] = 0x32 + data[i] = 0x3a i++ i = encodeVarintDevice(data, i, uint64(m.AppSKey.Size())) n7, err := m.AppSKey.MarshalTo(data[i:]) @@ -334,7 +334,7 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += n7 } if m.AppKey != nil { - data[i] = 0x3a + data[i] = 0x42 i++ i = encodeVarintDevice(data, i, uint64(m.AppKey.Size())) n8, err := m.AppKey.MarshalTo(data[i:]) @@ -344,12 +344,12 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += n8 } if m.FCntUp != 0 { - data[i] = 0x40 + data[i] = 0x48 i++ i = encodeVarintDevice(data, i, uint64(m.FCntUp)) } if m.FCntDown != 0 { - data[i] = 0x48 + data[i] = 0x50 i++ i = encodeVarintDevice(data, i, uint64(m.FCntDown)) } @@ -421,20 +421,12 @@ func (m *DeviceIdentifier) Size() (n int) { l = m.DevEui.Size() n += 1 + l + sovDevice(uint64(l)) } - l = len(m.AppId) - if l > 0 { - n += 1 + l + sovDevice(uint64(l)) - } return n } func (m *Device) Size() (n int) { var l int _ = l - l = len(m.AppId) - if l > 0 { - n += 1 + l + sovDevice(uint64(l)) - } if m.AppEui != nil { l = m.AppEui.Size() n += 1 + l + sovDevice(uint64(l)) @@ -443,6 +435,14 @@ func (m *Device) Size() (n int) { l = m.DevEui.Size() n += 1 + l + sovDevice(uint64(l)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } if m.DevAddr != nil { l = m.DevAddr.Size() n += 1 + l + sovDevice(uint64(l)) @@ -583,35 +583,6 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 3: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowDevice - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return ErrInvalidLengthDevice - } - postIndex := iNdEx + intStringLen - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.AppId = string(data[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipDevice(data[iNdEx:]) @@ -664,9 +635,9 @@ func (m *Device) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowDevice @@ -676,24 +647,27 @@ func (m *Device) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthDevice } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -717,17 +691,17 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.AppEUI - m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) } - var byteLen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowDevice @@ -737,25 +711,51 @@ func (m *Device) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthDevice } - postIndex := iNdEx + byteLen + postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.DevEUI - m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } + m.AppId = string(data[iNdEx:postIndex]) iNdEx = postIndex case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 5: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } @@ -787,7 +787,7 @@ func (m *Device) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 5: + case 6: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) } @@ -819,7 +819,7 @@ func (m *Device) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 6: + case 7: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppSKey", wireType) } @@ -851,7 +851,7 @@ func (m *Device) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 7: + case 8: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field AppKey", wireType) } @@ -883,7 +883,7 @@ func (m *Device) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 8: + case 9: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field FCntUp", wireType) } @@ -902,7 +902,7 @@ func (m *Device) Unmarshal(data []byte) error { break } } - case 9: + case 10: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field FCntDown", wireType) } @@ -1107,38 +1107,38 @@ var ( ) var fileDescriptorDevice = []byte{ - // 525 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0xdd, 0x8e, 0xd2, 0x40, - 0x14, 0xc7, 0xed, 0x22, 0xd0, 0x8e, 0x10, 0x49, 0xcd, 0x26, 0x23, 0x9a, 0xd5, 0xec, 0x85, 0x7a, - 0xb3, 0x6d, 0x84, 0xa8, 0xd7, 0xb0, 0xac, 0x86, 0x18, 0x49, 0x2c, 0xbb, 0xd7, 0xcd, 0xd0, 0x1e, - 0x60, 0x02, 0xce, 0x34, 0xed, 0x54, 0xc2, 0x9b, 0xf8, 0x02, 0x3e, 0x83, 0xaf, 0xa0, 0x77, 0x5e, - 0x7b, 0x61, 0x8c, 0xbe, 0x88, 0x67, 0x66, 0x70, 0x83, 0x24, 0x66, 0x23, 0xd9, 0x8b, 0x49, 0xce, - 0xc7, 0xff, 0xfc, 0x66, 0xce, 0x7c, 0x91, 0xde, 0x8c, 0xab, 0x79, 0x39, 0x09, 0x12, 0xf9, 0x2e, - 0x3c, 0x9f, 0xc3, 0xf9, 0x9c, 0x8b, 0x59, 0x31, 0x02, 0xb5, 0x92, 0xf9, 0x22, 0x54, 0x4a, 0x84, - 0x2c, 0xe3, 0x61, 0x96, 0x4b, 0x25, 0x13, 0xb9, 0x0c, 0x97, 0x32, 0x67, 0x2b, 0x26, 0xc2, 0x14, - 0xde, 0xf3, 0x04, 0x02, 0x13, 0xf7, 0xeb, 0x9b, 0x68, 0xfb, 0x64, 0x8b, 0x35, 0x93, 0x33, 0x69, - 0xeb, 0x26, 0xe5, 0xd4, 0x78, 0xc6, 0x31, 0x96, 0xad, 0xfb, 0x4b, 0xfe, 0xcf, 0xa9, 0x71, 0x58, - 0xf9, 0xf1, 0x17, 0x87, 0xb4, 0x06, 0x66, 0xde, 0x61, 0x0a, 0x42, 0xf1, 0x29, 0x87, 0xdc, 0x1f, - 0x91, 0x3a, 0xcb, 0xb2, 0x18, 0x4a, 0x4e, 0x9d, 0x87, 0xce, 0x93, 0x46, 0xff, 0xd9, 0xb7, 0xef, - 0x0f, 0x9e, 0x5e, 0x05, 0x4e, 0x64, 0x0e, 0xa1, 0x5a, 0x67, 0x50, 0x04, 0xbd, 0x2c, 0x3b, 0xbb, - 0x18, 0x46, 0x35, 0xa4, 0x9c, 0x95, 0x5c, 0xf3, 0xb0, 0x37, 0xc3, 0x3b, 0xd8, 0x8b, 0x87, 0x2b, - 0x34, 0x3c, 0xa4, 0x68, 0xde, 0x21, 0xd1, 0xe4, 0x98, 0xa7, 0xb4, 0x82, 0x38, 0x2f, 0xaa, 0xa2, - 0x37, 0x4c, 0x8f, 0x3f, 0x55, 0x49, 0xcd, 0xf6, 0xb2, 0xa5, 0x70, 0xb6, 0x14, 0xdb, 0x8d, 0x1d, - 0x5c, 0x73, 0x63, 0x95, 0xeb, 0x68, 0xec, 0x2d, 0x71, 0x35, 0x8f, 0xa5, 0x69, 0x4e, 0x6f, 0x1a, - 0xe0, 0x73, 0x04, 0x76, 0xfe, 0x0f, 0xd8, 0xc3, 0xea, 0x48, 0xaf, 0x4b, 0x1b, 0x7e, 0x44, 0x3c, - 0xb1, 0x5a, 0xc4, 0x45, 0xbc, 0x80, 0x35, 0xad, 0xee, 0xc5, 0x1c, 0xad, 0x16, 0xe3, 0xd7, 0xb0, - 0x8e, 0xea, 0xc2, 0x1a, 0x9a, 0xa9, 0xb7, 0xd1, 0x32, 0x6b, 0x7b, 0x31, 0x71, 0x23, 0x2d, 0x93, - 0x59, 0xe3, 0xcf, 0xd1, 0x68, 0x62, 0x7d, 0xdf, 0xa3, 0xd1, 0x40, 0x7d, 0x34, 0x9a, 0x47, 0x89, - 0x3b, 0x8d, 0x13, 0xa1, 0xe2, 0x32, 0xa3, 0x2e, 0x02, 0x9b, 0x51, 0x6d, 0x7a, 0x2a, 0xd4, 0x45, - 0xe6, 0xdf, 0x27, 0xc4, 0x66, 0x52, 0xb9, 0x12, 0xd4, 0x33, 0x39, 0x57, 0xe7, 0x06, 0xe8, 0xfb, - 0x27, 0xe4, 0x4e, 0xca, 0x0b, 0x36, 0x59, 0x42, 0x6c, 0x55, 0xc9, 0x1c, 0x92, 0x05, 0xbd, 0x85, - 0x32, 0x37, 0x6a, 0x6d, 0x52, 0x2f, 0x51, 0x7d, 0xaa, 0xe3, 0xfe, 0x63, 0xd2, 0x2a, 0x0b, 0x28, - 0xba, 0x9d, 0x78, 0xc2, 0x95, 0xad, 0xa0, 0x0d, 0xa3, 0x6d, 0xda, 0x78, 0x9f, 0x2b, 0xad, 0xf6, - 0xef, 0x11, 0x6f, 0xc9, 0x0a, 0x15, 0x17, 0x00, 0x82, 0x1e, 0xa2, 0xa2, 0x12, 0xb9, 0x3a, 0x30, - 0x46, 0xbf, 0xf3, 0xd1, 0x21, 0x4d, 0x7b, 0x73, 0xdf, 0x30, 0xc1, 0x66, 0xf8, 0x04, 0x5f, 0x10, - 0xef, 0x15, 0xa8, 0xcd, 0x6d, 0xbe, 0x1b, 0x6c, 0x3e, 0x83, 0x60, 0xf7, 0xa9, 0xb6, 0x6f, 0xef, - 0xa4, 0xfc, 0x47, 0xc4, 0x1b, 0x5f, 0x16, 0xee, 0x66, 0xdb, 0x6e, 0xa0, 0x9f, 0x7e, 0x0f, 0x17, - 0xde, 0x25, 0x8d, 0x01, 0x2c, 0x41, 0xc1, 0xd5, 0x73, 0x5c, 0x16, 0xf5, 0x5b, 0x9f, 0x7f, 0x1e, - 0x39, 0x5f, 0x71, 0xfc, 0xc0, 0xf1, 0xe1, 0xd7, 0xd1, 0x8d, 0x49, 0xcd, 0x7c, 0x23, 0xdd, 0xdf, - 0x01, 0x00, 0x00, 0xff, 0xff, 0x3a, 0xff, 0x36, 0xff, 0xf2, 0x04, 0x00, 0x00, + // 527 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xc1, 0x8e, 0xd3, 0x3c, + 0x10, 0xc7, 0xbf, 0x7e, 0x4b, 0xdb, 0xc4, 0xb4, 0xa2, 0x0a, 0x5a, 0xc9, 0x14, 0xb4, 0xa0, 0x3d, + 0x00, 0x97, 0x4d, 0x44, 0x2b, 0xe0, 0xdc, 0x6e, 0x17, 0x54, 0x21, 0x2a, 0x91, 0xee, 0x9e, 0x23, + 0x37, 0x99, 0xb6, 0x56, 0x82, 0x1d, 0x25, 0x0e, 0x51, 0xdf, 0x84, 0x17, 0xe0, 0x05, 0x78, 0x0a, + 0x4e, 0x88, 0x33, 0x07, 0x84, 0xe0, 0x45, 0x18, 0xdb, 0x65, 0x05, 0x95, 0xd0, 0x8a, 0x9e, 0x38, + 0x58, 0xf2, 0xfc, 0xe7, 0x3f, 0xbf, 0x19, 0x27, 0x71, 0xc8, 0x68, 0xc5, 0xd5, 0xba, 0x5a, 0xf8, + 0xb1, 0x7c, 0x1d, 0x9c, 0xaf, 0xe1, 0x7c, 0xcd, 0xc5, 0xaa, 0x9c, 0x81, 0xaa, 0x65, 0x91, 0x06, + 0x4a, 0x89, 0x80, 0xe5, 0x3c, 0xc8, 0x0b, 0xa9, 0x64, 0x2c, 0xb3, 0x20, 0x93, 0x05, 0xab, 0x99, + 0x08, 0x12, 0x78, 0xc3, 0x63, 0xf0, 0x8d, 0xee, 0xb5, 0xb7, 0x6a, 0xff, 0xe4, 0x17, 0xd6, 0x4a, + 0xae, 0xa4, 0xad, 0x5b, 0x54, 0x4b, 0x13, 0x99, 0xc0, 0xec, 0x6c, 0xdd, 0x6f, 0xf6, 0x3f, 0xb6, + 0xc6, 0x65, 0xed, 0xc7, 0xef, 0x1b, 0xa4, 0x37, 0x31, 0x7d, 0xa7, 0x09, 0x08, 0xc5, 0x97, 0x1c, + 0x0a, 0x6f, 0x46, 0xda, 0x2c, 0xcf, 0x23, 0xa8, 0x38, 0x6d, 0xdc, 0x6b, 0x3c, 0xec, 0x8c, 0x1f, + 0x7f, 0xfe, 0x72, 0xf7, 0xd1, 0x55, 0xe0, 0x58, 0x16, 0x10, 0xa8, 0x4d, 0x0e, 0xa5, 0x3f, 0xca, + 0xf3, 0xb3, 0x8b, 0x69, 0xd8, 0x42, 0xca, 0x59, 0xc5, 0x35, 0x0f, 0xcf, 0x66, 0x78, 0xff, 0xef, + 0xc5, 0xc3, 0x09, 0x0d, 0x0f, 0x29, 0xc8, 0x3b, 0xfe, 0xd8, 0x24, 0x2d, 0x3b, 0xf4, 0xbf, 0x3e, + 0xaa, 0x77, 0x48, 0x34, 0x39, 0xe2, 0x09, 0x3d, 0x40, 0x9c, 0x1b, 0x36, 0x31, 0x9a, 0x26, 0x5a, + 0xd6, 0x6d, 0x50, 0xbe, 0x66, 0x65, 0x8c, 0x50, 0x7e, 0x45, 0x1c, 0x2d, 0xb3, 0x24, 0x29, 0x68, + 0xd3, 0xb4, 0x7f, 0x82, 0xed, 0x07, 0x7f, 0xd7, 0x7e, 0x84, 0xd5, 0xa1, 0x3e, 0x85, 0xde, 0x78, + 0x21, 0x71, 0x45, 0x9d, 0x46, 0x65, 0x94, 0xc2, 0x86, 0xb6, 0xf6, 0x62, 0xce, 0xea, 0x74, 0xfe, + 0x02, 0x36, 0x61, 0x5b, 0xd8, 0x8d, 0x66, 0xea, 0x43, 0x59, 0x66, 0x7b, 0x2f, 0x26, 0x3e, 0x76, + 0xcb, 0x64, 0x76, 0xf3, 0xf3, 0x45, 0x6a, 0xa2, 0xb3, 0xef, 0x8b, 0xd4, 0x40, 0xfd, 0xb8, 0x35, + 0x8f, 0x12, 0x67, 0x19, 0xc5, 0x42, 0x45, 0x55, 0x4e, 0x5d, 0x04, 0x76, 0xc3, 0xd6, 0xf2, 0x54, + 0xa8, 0x8b, 0xdc, 0xbb, 0x43, 0x88, 0xcd, 0x24, 0xb2, 0x16, 0x94, 0x98, 0x9c, 0xa3, 0x73, 0x13, + 0x8c, 0xbd, 0x13, 0x72, 0x33, 0xe1, 0x25, 0x5b, 0x64, 0x10, 0x59, 0x57, 0xbc, 0x86, 0x38, 0xa5, + 0xd7, 0xd1, 0xe6, 0x84, 0xbd, 0x6d, 0xea, 0x19, 0xba, 0x4f, 0xb5, 0xee, 0x3d, 0x20, 0xbd, 0xaa, + 0x84, 0x72, 0x38, 0x88, 0x16, 0x5c, 0xd9, 0x0a, 0xda, 0x31, 0xde, 0xae, 0xd5, 0xc7, 0x5c, 0x69, + 0xb7, 0x77, 0x9b, 0xb8, 0x19, 0x2b, 0x55, 0x54, 0x02, 0x08, 0x7a, 0x88, 0x8e, 0x83, 0xd0, 0xd1, + 0xc2, 0x1c, 0xe3, 0xc1, 0xbb, 0x06, 0xe9, 0xda, 0x0f, 0xfa, 0x25, 0x13, 0x6c, 0x85, 0x57, 0xf0, + 0x29, 0x71, 0x9f, 0x83, 0xda, 0x7e, 0xe4, 0xb7, 0xfc, 0xed, 0xcf, 0xc0, 0xdf, 0xbd, 0xaa, 0xfd, + 0x1b, 0x3b, 0x29, 0xef, 0x3e, 0x71, 0xe7, 0x97, 0x85, 0xbb, 0xd9, 0xbe, 0xe3, 0xeb, 0xab, 0x3f, + 0xc2, 0xc1, 0x87, 0xa4, 0x33, 0x81, 0x0c, 0x14, 0x5c, 0xdd, 0xe3, 0xb2, 0x68, 0xdc, 0xfb, 0xf0, + 0xed, 0xa8, 0xf1, 0x09, 0xd7, 0x57, 0x5c, 0x6f, 0xbf, 0x1f, 0xfd, 0xb7, 0x68, 0x99, 0xdf, 0xc8, + 0xf0, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x5a, 0xf8, 0xc1, 0xf2, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 5badc3d32..9bffe676d 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -11,19 +11,19 @@ package lorawan; message DeviceIdentifier { bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - string app_id = 3; } message Device { - string app_id = 1; - bytes app_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; - bytes dev_eui = 3 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - bytes dev_addr = 4 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; - bytes nwk_s_key = 5 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; - bytes app_s_key = 6 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppSKey"]; - bytes app_key = 7 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppKey"]; - uint32 f_cnt_up = 8; - uint32 f_cnt_down = 9; + bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + string app_id = 3; + string dev_id = 4; + bytes dev_addr = 5 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + bytes nwk_s_key = 6 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + bytes app_s_key = 7 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppSKey"]; + bytes app_key = 8 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppKey"]; + uint32 f_cnt_up = 9; + uint32 f_cnt_down = 10; // Options bool disable_f_cnt_check = 11; diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 7eb28d459..06a7f373f 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -8,9 +8,6 @@ func (m *DeviceIdentifier) Validate() bool { if m.DevEui == nil || m.DevEui.IsEmpty() { return false } - if m.AppId == "" { - return false - } return true } diff --git a/api/protocol/validation.go b/api/protocol/validation.go index 713438cb6..4455a9cc2 100644 --- a/api/protocol/validation.go +++ b/api/protocol/validation.go @@ -1,34 +1,27 @@ package protocol +import "github.com/TheThingsNetwork/ttn/api" + // Validate implements the api.Validator interface func (m *RxMetadata) Validate() bool { - switch { - case m.GetLorawan() != nil: - if !m.GetLorawan().Validate() { - return false - } + if m.Protocol == nil || !api.Validate(m.Protocol) { + return false } return true } // Validate implements the api.Validator interface func (m *TxConfiguration) Validate() bool { - switch { - case m.GetLorawan() != nil: - if !m.GetLorawan().Validate() { - return false - } + if m.Protocol == nil || !api.Validate(m.Protocol) { + return false } return true } // Validate implements the api.Validator interface func (m *ActivationMetadata) Validate() bool { - switch { - case m.GetLorawan() != nil: - if !m.GetLorawan().Validate() { - return false - } + if m.Protocol == nil || !api.Validate(m.Protocol) { + return false } return true } diff --git a/core/broker/activation.go b/core/broker/activation.go index 0af23fa65..46e98c5e2 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -77,7 +77,10 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D return nil, err } - ctx = ctx.WithField("AppID", deduplicatedActivationRequest.AppId) + ctx = ctx.WithFields(log.Fields{ + "AppID": deduplicatedActivationRequest.AppId, + "DevID": deduplicatedActivationRequest.DevId, + }) // Find Handler (based on AppEUI) var announcements []*pb_discovery.Announcement diff --git a/core/broker/server_test.go b/core/broker/server_test.go index eb45028af..1ee20d773 100644 --- a/core/broker/server_test.go +++ b/core/broker/server_test.go @@ -14,6 +14,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -157,7 +158,7 @@ func TestPublishRPC(t *testing.T) { DownlinkOption: &pb.DownlinkOption{ Identifier: "routerID:scheduleID", GatewayConfig: &pb_gateway.TxConfiguration{}, - ProtocolConfig: &pb_protocol.TxConfiguration{}, + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}}, GatewayEui: >wEUI, }, }) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index f7d871da5..b28e56166 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -112,6 +112,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { "DevEUI": device.DevEui, "AppEUI": device.AppEui, "AppID": device.AppId, + "DevID": device.DevId, }) if device.DisableFCntCheck { diff --git a/core/handler/activation.go b/core/handler/activation.go index 1ed777243..8fa1b18ed 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -30,6 +30,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv "DevEUI": devEUI, "AppEUI": appEUI, "AppID": activation.AppId, + "DevID": activation.DevId, }) var err error defer func() { @@ -45,7 +46,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // Find Device var dev *device.Device - dev, err = h.devices.Get(appEUI, devEUI) + dev, err = h.devices.Get(activation.AppId, activation.DevId) if err != nil { return nil, err } diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 7ad47c003..db1805c36 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -49,7 +49,9 @@ func doTestHandleActivation(h *handler, appEUI types.AppEUI, devEUI types.DevEUI return h.HandleActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ Payload: requestBytes, AppEui: &appEUI, + AppId: appEUI.String(), DevEui: &devEUI, + DevId: devEUI.String(), ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ DevAddr: &devAddr, @@ -73,8 +75,9 @@ func TestHandleActivation(t *testing.T) { var wg sync.WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - appID := "AppID-1" + appID := appEUI.String() devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + devID := devEUI.String() unknownDevEUI := types.DevEUI{8, 7, 6, 5, 4, 3, 2, 1} appKey := [16]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16} @@ -85,6 +88,7 @@ func TestHandleActivation(t *testing.T) { h.devices.Set(&device.Device{ AppID: appID, + DevID: devID, AppEUI: appEUI, DevEUI: devEUI, AppKey: appKey, diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 6d08a9a73..763008ca1 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -56,7 +56,7 @@ type Functions struct { } // timeOut is the maximum allowed time a payload function is allowed to run -var timeOut = time.Second +var timeOut = 100 * time.Millisecond // Decode decodes the payload using the Decoder function into a map func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 3ccf6284c..ea21b016c 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -4,7 +4,6 @@ package handler import ( - "errors" "testing" "time" @@ -12,19 +11,19 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/core/handler/application" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { - appEUI, _ := types.ParseAppEUI("0102030405060708") ttnUp := &pb_broker.DeduplicatedUplinkMessage{ AppId: "AppID-1", + DevId: "DevID-1", } appUp := &mqtt.UplinkMessage{ FPort: 1, - AppEUI: appEUI, + AppID: "AppID-1", + DevID: "DevID-1", Payload: []byte{0x08, 0x70}, } return ttnUp, appUp @@ -257,16 +256,19 @@ func TestTimeoutExceeded(t *testing.T) { a := New(t) start := time.Now() + functions := &Functions{ Decoder: `function(payload){ while (true) { } }`, } + interrupted := make(chan bool, 2) go func() { - time.Sleep(4 * time.Second) - panic(errors.New("Payload function was not interrupted")) + _, _, err := functions.Process([]byte{0}) + a.So(time.Since(start), ShouldAlmostEqual, 100*time.Millisecond, 0.5e9) + a.So(err, ShouldNotBeNil) + interrupted <- true }() - _, _, err := functions.Process([]byte{0}) - a.So(time.Since(start), ShouldAlmostEqual, time.Second, 0.5e9) - a.So(err, ShouldNotBeNil) + <-time.After(200 * time.Millisecond) + a.So(interrupted, ShouldHaveLength, 1) } diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index b4d5dfe38..ae7c0dad5 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -5,6 +5,7 @@ package handler import ( "errors" + "fmt" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/mqtt" @@ -15,7 +16,7 @@ import ( func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { // Find Device - dev, err := h.devices.Get(*ttnUp.AppEui, *ttnUp.DevEui) + dev, err := h.devices.Get(ttnUp.AppId, ttnUp.DevId) if err != nil { return err } @@ -38,6 +39,16 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt appUp.FCnt = macPayload.FHDR.FCnt + // LoRaWAN: Validate MIC + ok, err = phyPayload.ValidateMIC(lorawan.AES128Key(dev.NwkSKey)) + if err != nil { + return err + } + if !ok { + fmt.Printf("Invalid MIC for %X / NwkSKey %s", ttnUp.Payload, dev.NwkSKey) + return errors.New("ttn/handler: Invalid MIC") + } + ctx = ctx.WithField("FCnt", appUp.FCnt) // LoRaWAN: Decrypt @@ -63,7 +74,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { // Find Device - dev, err := h.devices.Get(appDown.AppEUI, appDown.DevEUI) + dev, err := h.devices.Get(appDown.AppID, appDown.DevID) if err != nil { return err } @@ -105,6 +116,12 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMess return err } + // Set MIC + err = phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) + if err != nil { + return err + } + // Marshal phyPayloadBytes, err := phyPayload.MarshalBinary() if err != nil { diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index 2747882c8..7e3d18646 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -11,19 +11,15 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/device" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) -var testDevEUI = types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} -var testAppEUI = types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { ttnUp := &pb_broker.DeduplicatedUplinkMessage{ - DevEui: &testDevEUI, - AppEui: &testAppEUI, + DevId: "devid", + AppId: "appid", Payload: payload, ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ Lorawan: &pb_lorawan.Metadata{ @@ -42,8 +38,8 @@ func TestConvertFromLoRaWAN(t *testing.T) { Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, } h.devices.Set(&device.Device{ - DevEUI: testDevEUI, - AppEUI: testAppEUI, + DevID: "devid", + AppID: "appid", }) ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x46, 0x55, 0x23, 0xf4, 0xf8, 0x45}) err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) @@ -54,8 +50,8 @@ func TestConvertFromLoRaWAN(t *testing.T) { func buildLorawanDownlink(payload []byte) (*mqtt.DownlinkMessage, *pb_broker.DownlinkMessage) { appDown := &mqtt.DownlinkMessage{ - DevEUI: testDevEUI, - AppEUI: testAppEUI, + DevID: "devid", + AppID: "appid", Payload: []byte{0xaa, 0xbc}, } ttnDown := &pb_broker.DownlinkMessage{ @@ -78,17 +74,17 @@ func TestConvertToLoRaWAN(t *testing.T) { Component: &core.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, } h.devices.Set(&device.Device{ - DevEUI: testDevEUI, - AppEUI: testAppEUI, + DevID: "devid", + AppID: "appid", }) appDown, ttnDown := buildLorawanDownlink([]byte{0xaa, 0xbc}) err := h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0xa1, 0x33, 0x00, 0x00, 0x00, 0x00}) + a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0xa1, 0x33, 0x68, 0x0A, 0x08, 0xBD}) appDown, ttnDown = buildLorawanDownlink([]byte{0xaa, 0xbc}) appDown.FPort = 8 err = h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x08, 0xa1, 0x33, 0x00, 0x00, 0x00, 0x00}) + a.So(ttnDown.Payload, ShouldResemble, []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x08, 0xa1, 0x33, 0x41, 0xA9, 0xFA, 0x03}) } diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index c88b6e365..7d890cc68 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -13,14 +13,14 @@ import ( func getTestDevice() (device *Device, dmap map[string]string) { return &Device{ - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + DevEUI: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + AppEUI: types.AppEUI([8]byte{8, 7, 6, 5, 4, 3, 2, 1}), AppID: "AppID-1", DevID: "DevID-1", - DevAddr: types.DevAddr{1, 2, 3, 4}, - AppKey: types.AppKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, - NwkSKey: types.NwkSKey{1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8}, - AppSKey: types.AppSKey{2, 1, 2, 2, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 8}, + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + AppKey: types.AppKey([16]byte{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}), + NwkSKey: types.NwkSKey([16]byte{1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8}), + AppSKey: types.AppSKey([16]byte{2, 1, 2, 2, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 8}), UsedDevNonces: []DevNonce{DevNonce{1, 2}, DevNonce{3, 4}}, UsedAppNonces: []AppNonce{AppNonce{1, 2, 3}, AppNonce{4, 5, 6}}, }, map[string]string{ diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 01b420a07..ebfc61024 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -8,8 +8,6 @@ import ( "fmt" "gopkg.in/redis.v3" - - "github.com/TheThingsNetwork/ttn/core/types" ) var ( @@ -21,25 +19,27 @@ var ( type Store interface { // List all devices List() ([]*Device, error) + // ListForApp lists all devices for an app + ListForApp(appID string) ([]*Device, error) // Get the full information about a device - Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) + Get(appID, devID string) (*Device, error) // Set the given fields of a device. If fields empty, it sets all fields. Set(device *Device, fields ...string) error // Delete a device - Delete(types.AppEUI, types.DevEUI) error + Delete(appID, devID string) error } // NewDeviceStore creates a new in-memory Device store func NewDeviceStore() Store { return &deviceStore{ - devices: make(map[types.AppEUI]map[types.DevEUI]*Device), + devices: make(map[string]map[string]*Device), } } // deviceStore is an in-memory Device store. It should only be used for testing // purposes. Use the redisDeviceStore for actual deployments. type deviceStore struct { - devices map[types.AppEUI]map[types.DevEUI]*Device + devices map[string]map[string]*Device } func (s *deviceStore) List() ([]*Device, error) { @@ -52,9 +52,19 @@ func (s *deviceStore) List() ([]*Device, error) { return devices, nil } -func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { - if app, ok := s.devices[appEUI]; ok { - if dev, ok := app[devEUI]; ok { +func (s *deviceStore) ListForApp(appID string) ([]*Device, error) { + devices := make([]*Device, 0, len(s.devices)) + if app, ok := s.devices[appID]; ok { + for _, device := range app { + devices = append(devices, device) + } + } + return devices, nil +} + +func (s *deviceStore) Get(appID, devID string) (*Device, error) { + if app, ok := s.devices[appID]; ok { + if dev, ok := app[devID]; ok { return dev, nil } } @@ -63,17 +73,17 @@ func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, er func (s *deviceStore) Set(new *Device, fields ...string) error { // NOTE: We don't care about fields for testing - if app, ok := s.devices[new.AppEUI]; ok { - app[new.DevEUI] = new + if app, ok := s.devices[new.AppID]; ok { + app[new.DevID] = new } else { - s.devices[new.AppEUI] = map[types.DevEUI]*Device{new.DevEUI: new} + s.devices[new.AppID] = map[string]*Device{new.DevID: new} } return nil } -func (s *deviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { - if app, ok := s.devices[appEUI]; ok { - delete(app, devEUI) +func (s *deviceStore) Delete(appID, devID string) error { + if app, ok := s.devices[appID]; ok { + delete(app, devID) } return nil } @@ -91,12 +101,7 @@ type redisDeviceStore struct { client *redis.Client } -func (s *redisDeviceStore) List() ([]*Device, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() - if err != nil { - return nil, err - } - +func (s *redisDeviceStore) getForKeys(keys []string) ([]*Device, error) { pipe := s.client.Pipeline() defer pipe.Close() @@ -107,7 +112,7 @@ func (s *redisDeviceStore) List() ([]*Device, error) { } // Execute pipeline - _, err = pipe.Exec() + _, err := pipe.Exec() if err != nil { return nil, err } @@ -128,8 +133,24 @@ func (s *redisDeviceStore) List() ([]*Device, error) { return devices, nil } -func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() +func (s *redisDeviceStore) List() ([]*Device, error) { + keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() + if err != nil { + return nil, err + } + return s.getForKeys(keys) +} + +func (s *redisDeviceStore) ListForApp(appID string) ([]*Device, error) { + keys, err := s.client.Keys(fmt.Sprintf("%s:%s:*", redisDevicePrefix, appID)).Result() + if err != nil { + return nil, err + } + return s.getForKeys(keys) +} + +func (s *redisDeviceStore) Get(appID, devID string) (*Device, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appID, devID)).Result() if err != nil { if err == redis.Nil { return nil, ErrNotFound @@ -151,7 +172,7 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { fields = DeviceProperties } - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppEUI, new.DevEUI) + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppID, new.DevID) dmap, err := new.ToStringStringMap(fields...) if err != nil { return err @@ -167,8 +188,8 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { return nil } -func (s *redisDeviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) +func (s *redisDeviceStore) Delete(appID, devID string) error { + key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appID, devID) err := s.client.Del(key).Err() if err != nil { return err diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go index 043c9305a..8df62f25d 100644 --- a/core/handler/device/store_test.go +++ b/core/handler/device/store_test.go @@ -33,28 +33,32 @@ func TestDeviceStore(t *testing.T) { t.Logf("Testing %s store", name) // Get non-existing - dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + dev, err := s.Get("AppID-1", "DevID-1") a.So(err, ShouldNotBeNil) a.So(dev, ShouldBeNil) // Create err = s.Set(&Device{ - DevAddr: types.DevAddr{0, 0, 0, 1}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + DevAddr: types.DevAddr([4]byte{0, 0, 0, 1}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppID: "AppID-1", + DevID: "DevID-1", }) a.So(err, ShouldBeNil) // Get existing - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + dev, err = s.Get("AppID-1", "DevID-1") a.So(err, ShouldBeNil) a.So(dev, ShouldNotBeNil) // Create extra err = s.Set(&Device{ - DevAddr: types.DevAddr{0, 0, 0, 2}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + DevAddr: types.DevAddr([4]byte{0, 0, 0, 2}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppID: "AppID-1", + DevID: "DevID-2", }) a.So(err, ShouldBeNil) @@ -64,16 +68,16 @@ func TestDeviceStore(t *testing.T) { a.So(devices, ShouldHaveLength, 2) // Delete - err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + err = s.Delete("AppID-1", "DevID-1") a.So(err, ShouldBeNil) // Get deleted - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + dev, err = s.Get("AppID-1", "DevID-1") a.So(err, ShouldNotBeNil) a.So(dev, ShouldBeNil) // Cleanup - s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + s.Delete("AppID-1", "DevID-2") } } diff --git a/core/handler/downlink.go b/core/handler/downlink.go index e312a437b..47b687752 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -11,8 +11,8 @@ import ( func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { ctx := h.Ctx.WithFields(log.Fields{ - "DevEUI": appDownlink.DevEUI, - "AppEUI": appDownlink.AppEUI, + "DevID": appDownlink.DevID, + "AppID": appDownlink.AppID, }) var err error defer func() { @@ -21,7 +21,7 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { } }() - dev, err := h.devices.Get(appDownlink.AppEUI, appDownlink.DevEUI) + dev, err := h.devices.Get(appDownlink.AppID, appDownlink.DevID) if err != nil { return err } @@ -38,8 +38,10 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb_broker.DownlinkMessage) error { ctx := h.Ctx.WithFields(log.Fields{ - "DevEUI": appDownlink.DevEUI, - "AppEUI": appDownlink.AppEUI, + "DevID": appDownlink.AppID, + "AppID": appDownlink.DevID, + "DevEUI": downlink.AppEui, + "AppEUI": downlink.DevEui, }) var err error defer func() { diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 8490891b8..4b369e011 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -17,24 +17,24 @@ import ( func TestEnqueueDownlink(t *testing.T) { a := New(t) - appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "app1" + devID := "dev1" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, devices: device.NewDeviceStore(), } err := h.EnqueueDownlink(&mqtt.DownlinkMessage{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, }) a.So(err, ShouldNotBeNil) h.devices.Set(&device.Device{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, }) err = h.EnqueueDownlink(&mqtt.DownlinkMessage{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, Fields: map[string]interface{}{ "string": "hello!", "int": 42, @@ -42,34 +42,37 @@ func TestEnqueueDownlink(t *testing.T) { }, }) a.So(err, ShouldBeNil) - dev, _ := h.devices.Get(appEUI, devEUI) + dev, _ := h.devices.Get(appID, devID) a.So(dev.NextDownlink, ShouldNotBeEmpty) a.So(dev.NextDownlink.Fields, ShouldHaveLength, 3) } func TestHandleDownlink(t *testing.T) { a := New(t) - appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "app2" + devID := "dev2" + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, devices: device.NewDeviceStore(), } + err := h.HandleDownlink(&mqtt.DownlinkMessage{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, }) a.So(err, ShouldNotBeNil) h.devices.Set(&device.Device{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, }) err = h.HandleDownlink(&mqtt.DownlinkMessage{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, @@ -82,8 +85,8 @@ func TestHandleDownlink(t *testing.T) { a.So(dl.Payload, ShouldNotBeEmpty) }() err = h.HandleDownlink(&mqtt.DownlinkMessage{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, Payload: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index cb6830406..3d7bb127f 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -23,7 +23,7 @@ type handlerManager struct { var errf = grpc.Errorf -func (h *handlerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { +func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) (*device.Device, error) { if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } @@ -34,7 +34,7 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIde if !claims.CanEditApp(in.AppId) { return nil, errf(codes.Unauthenticated, "No access to this device") } - dev, err := h.devices.Get(*in.AppEui, *in.DevEui) + dev, err := h.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err } @@ -44,68 +44,87 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIde return dev, nil } -func (h *handlerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { +func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { dev, err := h.getDevice(ctx, in) if err != nil { return nil, err } - nsDev, err := h.ttnDeviceManager.GetDevice(ctx, in) + nsDev, err := h.ttnDeviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ + AppEui: &dev.AppEUI, + DevEui: &dev.DevEUI, + }) if err != nil { return nil, err } - return &pb_lorawan.Device{ - AppId: dev.AppID, - AppEui: nsDev.AppEui, - DevEui: nsDev.DevEui, - DevAddr: nsDev.DevAddr, - NwkSKey: nsDev.NwkSKey, - AppSKey: &dev.AppSKey, - AppKey: &dev.AppKey, - FCntUp: nsDev.FCntUp, - FCntDown: nsDev.FCntDown, - DisableFCntCheck: nsDev.DisableFCntCheck, - Uses32BitFCnt: nsDev.Uses32BitFCnt, - LastSeen: nsDev.LastSeen, + return &pb.Device{ + AppId: dev.AppID, + DevId: dev.DevID, + Device: &pb.Device_LorawanDevice{LorawanDevice: &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: nsDev.AppEui, + DevId: dev.DevID, + DevEui: nsDev.DevEui, + DevAddr: nsDev.DevAddr, + NwkSKey: nsDev.NwkSKey, + AppSKey: &dev.AppSKey, + AppKey: &dev.AppKey, + FCntUp: nsDev.FCntUp, + FCntDown: nsDev.FCntDown, + DisableFCntCheck: nsDev.DisableFCntCheck, + Uses32BitFCnt: nsDev.Uses32BitFCnt, + LastSeen: nsDev.LastSeen, + }}, }, nil } -func (h *handlerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*api.Ack, error) { - _, err := h.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppId: in.AppId, AppEui: in.AppEui, DevEui: in.DevEui}) +func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack, error) { + _, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) if err != nil && err != device.ErrNotFound { return nil, err } + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Device") + } + + updated := &device.Device{ + AppID: in.AppId, + DevID: in.DevId, + } + + lorawan := in.GetLorawanDevice() + if lorawan == nil { + err = grpcErrf(codes.InvalidArgument, "No LoRaWAN Device") + } + _, err = h.ttnDeviceManager.SetDevice(ctx, &pb_lorawan.Device{ AppId: in.AppId, - AppEui: in.AppEui, - DevEui: in.DevEui, - DevAddr: in.DevAddr, - NwkSKey: in.NwkSKey, - FCntUp: in.FCntUp, - FCntDown: in.FCntDown, - DisableFCntCheck: in.DisableFCntCheck, - Uses32BitFCnt: in.Uses32BitFCnt, + AppEui: lorawan.AppEui, + DevEui: lorawan.DevEui, + DevAddr: lorawan.DevAddr, + NwkSKey: lorawan.NwkSKey, + FCntUp: lorawan.FCntUp, + FCntDown: lorawan.FCntDown, + DisableFCntCheck: lorawan.DisableFCntCheck, + Uses32BitFCnt: lorawan.Uses32BitFCnt, }) if err != nil { return nil, err } - updated := &device.Device{ - AppID: in.AppId, - AppEUI: *in.AppEui, - DevEUI: *in.DevEui, - } + updated.AppEUI = *lorawan.AppEui + updated.DevEUI = *lorawan.DevEui - if in.DevAddr != nil && in.NwkSKey != nil && in.AppSKey != nil { - updated.DevAddr = *in.DevAddr - updated.NwkSKey = *in.NwkSKey - updated.AppSKey = *in.AppSKey + if lorawan.DevAddr != nil && lorawan.NwkSKey != nil && lorawan.AppSKey != nil { + updated.DevAddr = *lorawan.DevAddr + updated.NwkSKey = *lorawan.NwkSKey + updated.AppSKey = *lorawan.AppSKey } - if in.AppKey != nil { - updated.AppKey = *in.AppKey + if lorawan.AppKey != nil { + updated.AppKey = *lorawan.AppKey } err = h.devices.Set(updated) @@ -116,22 +135,57 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) ( return &api.Ack{}, nil } -func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*api.Ack, error) { - _, err := h.getDevice(ctx, in) +func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*api.Ack, error) { + dev, err := h.getDevice(ctx, in) if err != nil { return nil, err } - _, err = h.ttnDeviceManager.DeleteDevice(ctx, in) + _, err = h.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { return nil, err } - err = h.devices.Delete(*in.AppEui, *in.DevEui) + err = h.devices.Delete(in.AppId, in.DevId) if err != nil { return nil, err } return &api.Ack{}, nil } +func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.DeviceList, error) { + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") + } + claims, err := h.Component.ValidateContext(ctx) + if err != nil { + return nil, err + } + if !claims.CanEditApp(in.AppId) { + return nil, errf(codes.Unauthenticated, "No access to this application") + } + devices, err := h.devices.ListForApp(in.AppId) + if err != nil { + return nil, err + } + res := &pb.DeviceList{Devices: []*pb.Device{}} + for _, dev := range devices { + res.Devices = append(res.Devices, &pb.Device{ + AppId: dev.AppID, + DevId: dev.DevID, + Device: &pb.Device_LorawanDevice{LorawanDevice: &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: &dev.AppEUI, + DevId: dev.DevID, + DevEui: &dev.DevEUI, + DevAddr: &dev.DevAddr, + NwkSKey: &dev.NwkSKey, + AppSKey: &dev.AppSKey, + AppKey: &dev.AppKey, + }}, + }) + } + return res, nil +} + func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*application.Application, error) { if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") @@ -173,6 +227,10 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) return nil, err } + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Application") + } + err = h.applications.Set(&application.Application{ AppID: in.AppId, Decoder: in.Decoder, @@ -247,5 +305,4 @@ func (b *handler) RegisterManager(s *grpc.Server) { server := &handlerManager{b} pb.RegisterHandlerManagerServer(s, server) pb.RegisterApplicationManagerServer(s, server) - pb_lorawan.RegisterDeviceManagerServer(s, server) } diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 11c9a684a..921811edc 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -6,7 +6,6 @@ package handler import ( "time" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) @@ -28,10 +27,10 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e h.mqttUp = make(chan *mqtt.UplinkMessage, MQTTBufferSize) h.mqttActivation = make(chan *mqtt.Activation, MQTTBufferSize) - token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, msg mqtt.DownlinkMessage) { + token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appID string, devID string, msg mqtt.DownlinkMessage) { down := &msg - down.DevEUI = devEUI - down.AppEUI = appEUI + down.DevID = devID + down.AppID = appID go h.EnqueueDownlink(down) }) token.Wait() @@ -42,10 +41,10 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e go func() { for up := range h.mqttUp { h.Ctx.WithFields(log.Fields{ - "DevEUI": up.DevEUI, - "AppEUI": up.AppEUI, + "DevID": up.DevID, + "AppID": up.AppID, }).Debug("Publish Uplink") - token := h.mqttClient.PublishUplink(up.AppEUI, up.DevEUI, *up) + token := h.mqttClient.PublishUplink(up.AppID, up.DevID, *up) go func() { if token.WaitTimeout(MQTTTimeout) { if token.Error() != nil { @@ -60,7 +59,14 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e go func() { for activation := range h.mqttActivation { - token := h.mqttClient.PublishActivation(activation.AppEUI, activation.DevEUI, *activation) + h.Ctx.WithFields(log.Fields{ + "DevID": activation.DevID, + "AppID": activation.AppID, + "DevEUI": activation.DevEUI, + "AppEUI": activation.AppEUI, + "DevAddr": activation.DevAddr, + }).Debug("Publish Activation") + token := h.mqttClient.PublishActivation(activation.AppID, activation.DevID, *activation) go func() { if token.WaitTimeout(MQTTTimeout) { if token.Error() != nil { diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index e9a32553e..2afa5c411 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -10,7 +10,6 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/device" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -21,50 +20,50 @@ func TestHandleMQTT(t *testing.T) { var wg sync.WaitGroup c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + appID := "app1" + devID := "dev1" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, devices: device.NewDeviceStore(), } h.devices.Set(&device.Device{ - AppEUI: appEUI, - DevEUI: devEUI, + AppID: appID, + DevID: devID, }) err := h.HandleMQTT("", "", "tcp://localhost:1883") a.So(err, ShouldBeNil) - c.PublishDownlink(appEUI, devEUI, mqtt.DownlinkMessage{ + c.PublishDownlink(appID, devID, mqtt.DownlinkMessage{ Payload: []byte{0xAA, 0xBC}, }).Wait() <-time.After(50 * time.Millisecond) - dev, _ := h.devices.Get(appEUI, devEUI) + dev, _ := h.devices.Get(appID, devID) a.So(dev.NextDownlink, ShouldNotBeNil) wg.Add(1) - c.SubscribeUplink(func(client mqtt.Client, r_appEUI types.AppEUI, r_devEUI types.DevEUI, req mqtt.UplinkMessage) { - a.So(r_appEUI, ShouldEqual, appEUI) - a.So(r_devEUI, ShouldEqual, devEUI) + c.SubscribeUplink(func(client mqtt.Client, r_appID string, r_devID string, req mqtt.UplinkMessage) { + a.So(r_appID, ShouldEqual, appID) + a.So(r_devID, ShouldEqual, devID) a.So(req.Payload, ShouldResemble, []byte{0xAA, 0xBC}) wg.Done() }) h.mqttUp <- &mqtt.UplinkMessage{ - DevEUI: devEUI, - AppEUI: appEUI, + DevID: devID, + AppID: appID, Payload: []byte{0xAA, 0xBC}, } wg.Add(1) - c.SubscribeActivations(func(client mqtt.Client, r_appEUI types.AppEUI, r_devEUI types.DevEUI, req mqtt.Activation) { - a.So(r_appEUI, ShouldEqual, appEUI) - a.So(r_devEUI, ShouldEqual, devEUI) + c.SubscribeActivations(func(client mqtt.Client, r_appID string, r_devID string, req mqtt.Activation) { + a.So(r_appID, ShouldEqual, appID) + a.So(r_devID, ShouldEqual, devID) wg.Done() }) h.mqttActivation <- &mqtt.Activation{ - DevEUI: devEUI, - AppEUI: appEUI, + DevID: devID, + AppID: appID, } wg.Wait() diff --git a/core/handler/uplink.go b/core/handler/uplink.go index 4a9676fb7..1fddbd80b 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -4,10 +4,10 @@ package handler import ( + "fmt" "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) @@ -15,18 +15,9 @@ import ( var ResponseDeadline = 100 * time.Millisecond func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error { - var appEUI types.AppEUI - if uplink.AppEui != nil { - appEUI = *uplink.AppEui - } - var devEUI types.DevEUI - if uplink.DevEui != nil { - devEUI = *uplink.DevEui - } - ctx := h.Ctx.WithFields(log.Fields{ - "DevEUI": devEUI, - "AppEUI": appEUI, + "AppID": uplink.AppId, + "DevID": uplink.DevId, }) var err error defer func() { @@ -37,8 +28,8 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro // Build AppUplink appUplink := &mqtt.UplinkMessage{ - DevEUI: devEUI, - AppEUI: appEUI, + AppID: uplink.AppId, + DevID: uplink.DevId, } // Get Uplink Processors @@ -57,6 +48,7 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro err = nil return nil } else if err != nil { + fmt.Println(err) return err } } @@ -68,7 +60,7 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro // Find Device and scheduled downlink var appDownlink mqtt.DownlinkMessage - dev, err := h.devices.Get(appEUI, devEUI) + dev, err := h.devices.Get(uplink.AppId, uplink.DevId) if err != nil { return err } @@ -83,8 +75,8 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro // Prepare Downlink downlink := uplink.ResponseTemplate - appDownlink.AppEUI = appEUI - appDownlink.DevEUI = devEUI + appDownlink.AppID = uplink.AppId + appDownlink.DevID = uplink.DevId // Handle Downlink err = h.HandleDownlink(&appDownlink, downlink) diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index bee80a6d6..cdc9b481e 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -23,9 +23,10 @@ func TestHandleUplink(t *testing.T) { a := New(t) var err error var wg sync.WaitGroup - appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - appID := "AppID-1" - devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + appID := "appid" + devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + devID := "devid" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleUplink")}, devices: device.NewDeviceStore(), @@ -33,6 +34,7 @@ func TestHandleUplink(t *testing.T) { } h.devices.Set(&device.Device{ AppID: appID, + DevID: devID, AppEUI: appEUI, DevEUI: devEUI, }) @@ -42,11 +44,13 @@ func TestHandleUplink(t *testing.T) { h.mqttUp = make(chan *mqtt.UplinkMessage) h.downlink = make(chan *pb_broker.DownlinkMessage) - uplink, _ := buildLorawanUplink([]byte{0x80, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x9C, 0x15, 0x7C, 0xEB, 0x09, 0x80}) - downlinkEmpty := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00} - downlinkACK := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x20, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00} - downlinkMAC := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x05, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00} - expected := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00} + uplink, _ := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0xDA, 0x23, 0x99, 0x61, 0xD4}) + + downlinkEmpty := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x21, 0xEA, 0x8B, 0x0E} + downlinkACK := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x20, 0x00, 0x00, 0x0A, 0x3B, 0x3F, 0x77, 0x0B} + downlinkMAC := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x05, 0x00, 0x00, 0x03, 0x30, 0x00, 0x00, 0x00, 0x0A, 0x4D, 0x11, 0x55, 0x01} + expected := []byte{0x60, 0x04, 0x03, 0x02, 0x01, 0x00, 0x00, 0x00, 0x0A, 0x66, 0xE6, 0x1D, 0x49, 0x82, 0x84} + downlink := &pb_broker.DownlinkMessage{ DownlinkOption: &pb_broker.DownlinkOption{ ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ @@ -110,6 +114,8 @@ func TestHandleUplink(t *testing.T) { // Test Uplink, Data downlink needed h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, AppEUI: appEUI, DevEUI: devEUI, NextDownlink: &mqtt.DownlinkMessage{ @@ -131,6 +137,6 @@ func TestHandleUplink(t *testing.T) { a.So(err, ShouldBeNil) wg.Wait() - dev, _ := h.devices.Get(appEUI, devEUI) + dev, _ := h.devices.Get(appID, devID) a.So(dev.NextDownlink, ShouldBeNil) } diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 01b665cf4..8ab4d1b13 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -23,6 +23,7 @@ type Device struct { DevEUI types.DevEUI AppEUI types.AppEUI AppID string + DevID string DevAddr types.DevAddr NwkSKey types.NwkSKey FCntUp uint32 @@ -37,6 +38,7 @@ var DeviceProperties = []string{ "dev_eui", "app_eui", "app_id", + "dev_id", "dev_addr", "nwk_s_key", "f_cnt_up", @@ -78,6 +80,8 @@ func (device *Device) formatProperty(property string) (formatted string, err err formatted = device.AppEUI.String() case "app_id": formatted = device.AppID + case "dev_id": + formatted = device.DevID case "dev_addr": formatted = device.DevAddr.String() case "nwk_s_key": @@ -121,6 +125,8 @@ func (device *Device) parseProperty(property string, value string) error { device.AppEUI = val case "app_id": device.AppID = value + case "dev_id": + device.DevID = value case "dev_addr": val, err := types.ParseDevAddr(value) if err != nil { diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go index 146a94069..862802421 100644 --- a/core/networkserver/device/device_test.go +++ b/core/networkserver/device/device_test.go @@ -13,11 +13,12 @@ import ( func getTestDevice() (device *Device, dmap map[string]string) { return &Device{ - DevEUI: types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8}, - AppEUI: types.AppEUI{8, 7, 6, 5, 4, 3, 2, 1}, + DevEUI: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + AppEUI: types.AppEUI([8]byte{8, 7, 6, 5, 4, 3, 2, 1}), AppID: "TestApp-1", - DevAddr: types.DevAddr{1, 2, 3, 4}, - NwkSKey: types.NwkSKey{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}, + DevID: "TestDev-1", + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + NwkSKey: types.NwkSKey([16]byte{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}), FCntUp: 42, FCntDown: 24, LastSeen: time.Unix(0, 0).UTC(), @@ -26,6 +27,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { "dev_eui": "0102030405060708", "app_eui": "0807060504030201", "app_id": "TestApp-1", + "dev_id": "TestDev-1", "dev_addr": "01020304", "nwk_s_key": "00010002000300040005000600070008", "f_cnt_up": "42", diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 04dcb310d..d54cf1bc5 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -29,9 +29,6 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev if err != nil { return nil, err } - if !claims.CanEditApp(in.AppId) { - return nil, errf(codes.Unauthenticated, "No access to this device") - } dev, err := n.devices.Get(*in.AppEui, *in.DevEui) if err != nil { return nil, err @@ -51,6 +48,7 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev return &pb_lorawan.Device{ AppId: dev.AppID, AppEui: &dev.AppEUI, + DevId: dev.DevID, DevEui: &dev.DevEUI, DevAddr: &dev.DevAddr, NwkSKey: &dev.NwkSKey, @@ -63,14 +61,19 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev } func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*api.Ack, error) { - _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppId: in.AppId, AppEui: in.AppEui, DevEui: in.DevEui}) + _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) if err != nil && err != device.ErrNotFound { return nil, err } + if !in.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") + } + updated := &device.Device{ AppID: in.AppId, AppEUI: *in.AppEui, + DevID: in.DevId, DevEUI: *in.DevEui, FCntUp: in.FCntUp, FCntDown: in.FCntDown, diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index e6650a871..e21c55469 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -93,6 +93,7 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes AppEui: &device.AppEUI, AppId: device.AppID, DevEui: &device.DevEUI, + DevId: device.DevID, NwkSKey: &device.NwkSKey, FCntUp: device.FCntUp, Uses32BitFCnt: device.Options.Uses32BitFCnt, @@ -123,6 +124,7 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat return nil, err } activation.AppId = dev.AppID + activation.DevId = dev.DevID // Build activation metadata if not present if meta := activation.GetActivationMetadata(); meta == nil { diff --git a/mqtt/client.go b/mqtt/client.go index 92afdd476..07a32d1bf 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -8,7 +8,6 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" MQTT "github.com/eclipse/paho.mqtt.golang" @@ -24,21 +23,21 @@ type Client interface { IsConnected() bool // Uplink pub/sub - PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, payload UplinkMessage) Token - SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types.DevEUI, handler UplinkHandler) Token - SubscribeAppUplink(appEUI types.AppEUI, handler UplinkHandler) Token + PublishUplink(appID string, devID string, payload UplinkMessage) Token + SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token + SubscribeAppUplink(appID string, handler UplinkHandler) Token SubscribeUplink(handler UplinkHandler) Token // Downlink pub/sub - PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, payload DownlinkMessage) Token - SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI types.DevEUI, handler DownlinkHandler) Token - SubscribeAppDownlink(appEUI types.AppEUI, handler DownlinkHandler) Token + PublishDownlink(appID string, devID string, payload DownlinkMessage) Token + SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token + SubscribeAppDownlink(appID string, handler DownlinkHandler) Token SubscribeDownlink(handler DownlinkHandler) Token // Activation pub/sub - PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, payload Activation) Token - SubscribeDeviceActivations(appEUI types.AppEUI, devEUI types.DevEUI, handler ActivationHandler) Token - SubscribeAppActivations(appEUI types.AppEUI, handler ActivationHandler) Token + PublishActivation(appID string, devID string, payload Activation) Token + SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token + SubscribeAppActivations(appID string, handler ActivationHandler) Token SubscribeActivations(handler ActivationHandler) Token } @@ -67,9 +66,9 @@ func (t *simpleToken) Error() error { return t.err } -type UplinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) -type DownlinkHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) -type ActivationHandler func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) +type UplinkHandler func(client Client, appID string, devID string, req UplinkMessage) +type DownlinkHandler func(client Client, appID string, devID string, req DownlinkMessage) +type ActivationHandler func(client Client, appID string, devID string, req Activation) type defaultClient struct { mqtt MQTT.Client @@ -153,8 +152,8 @@ func (c *defaultClient) IsConnected() bool { return c.mqtt.IsConnected() } -func (c *defaultClient) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, dataUp UplinkMessage) Token { - topic := DeviceTopic{appEUI, devEUI, Uplink} +func (c *defaultClient) PublishUplink(appID string, devID string, dataUp UplinkMessage) Token { + topic := DeviceTopic{appID, devID, Uplink} msg, err := json.Marshal(dataUp) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -162,8 +161,8 @@ func (c *defaultClient) PublishUplink(appEUI types.AppEUI, devEUI types.DevEUI, return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types.DevEUI, handler UplinkHandler) Token { - topic := DeviceTopic{appEUI, devEUI, Uplink} +func (c *defaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { + topic := DeviceTopic{appID, devID, Uplink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) @@ -182,20 +181,20 @@ func (c *defaultClient) SubscribeDeviceUplink(appEUI types.AppEUI, devEUI types. } // Call the uplink handler - handler(c, topic.AppEUI, topic.DevEUI, *dataUp) + handler(c, topic.AppID, topic.DevID, *dataUp) }) } -func (c *defaultClient) SubscribeAppUplink(appEUI types.AppEUI, handler UplinkHandler) Token { - return c.SubscribeDeviceUplink(appEUI, types.DevEUI{}, handler) +func (c *defaultClient) SubscribeAppUplink(appID string, handler UplinkHandler) Token { + return c.SubscribeDeviceUplink(appID, "", handler) } func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { - return c.SubscribeDeviceUplink(types.AppEUI{}, types.DevEUI{}, handler) + return c.SubscribeDeviceUplink("", "", handler) } -func (c *defaultClient) PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI, dataDown DownlinkMessage) Token { - topic := DeviceTopic{appEUI, devEUI, Downlink} +func (c *defaultClient) PublishDownlink(appID string, devID string, dataDown DownlinkMessage) Token { + topic := DeviceTopic{appID, devID, Downlink} msg, err := json.Marshal(dataDown) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -203,8 +202,8 @@ func (c *defaultClient) PublishDownlink(appEUI types.AppEUI, devEUI types.DevEUI return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI types.DevEUI, handler DownlinkHandler) Token { - topic := DeviceTopic{appEUI, devEUI, Downlink} +func (c *defaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { + topic := DeviceTopic{appID, devID, Downlink} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) @@ -222,20 +221,20 @@ func (c *defaultClient) SubscribeDeviceDownlink(appEUI types.AppEUI, devEUI type } // Call the Downlink handler - handler(c, topic.AppEUI, topic.DevEUI, *dataDown) + handler(c, topic.AppID, topic.DevID, *dataDown) }) } -func (c *defaultClient) SubscribeAppDownlink(appEUI types.AppEUI, handler DownlinkHandler) Token { - return c.SubscribeDeviceDownlink(appEUI, types.DevEUI{}, handler) +func (c *defaultClient) SubscribeAppDownlink(appID string, handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink(appID, "", handler) } func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { - return c.SubscribeDeviceDownlink(types.AppEUI{}, types.DevEUI{}, handler) + return c.SubscribeDeviceDownlink("", "", handler) } -func (c *defaultClient) PublishActivation(appEUI types.AppEUI, devEUI types.DevEUI, activation Activation) Token { - topic := DeviceTopic{appEUI, devEUI, Activations} +func (c *defaultClient) PublishActivation(appID string, devID string, activation Activation) Token { + topic := DeviceTopic{appID, devID, Activations} msg, err := json.Marshal(activation) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -243,8 +242,8 @@ func (c *defaultClient) PublishActivation(appEUI types.AppEUI, devEUI types.DevE return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceActivations(appEUI types.AppEUI, devEUI types.DevEUI, handler ActivationHandler) Token { - topic := DeviceTopic{appEUI, devEUI, Activations} +func (c *defaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { + topic := DeviceTopic{appID, devID, Activations} return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) @@ -262,14 +261,14 @@ func (c *defaultClient) SubscribeDeviceActivations(appEUI types.AppEUI, devEUI t } // Call the Activation handler - handler(c, topic.AppEUI, topic.DevEUI, *activation) + handler(c, topic.AppID, topic.DevID, *activation) }) } -func (c *defaultClient) SubscribeAppActivations(appEUI types.AppEUI, handler ActivationHandler) Token { - return c.SubscribeDeviceActivations(appEUI, types.DevEUI{}, handler) +func (c *defaultClient) SubscribeAppActivations(appID string, handler ActivationHandler) Token { + return c.SubscribeDeviceActivations(appID, "", handler) } func (c *defaultClient) SubscribeActivations(handler ActivationHandler) Token { - return c.SubscribeDeviceActivations(types.AppEUI{}, types.DevEUI{}, handler) + return c.SubscribeDeviceActivations("", "", handler) } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index f69010190..e0d280061 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -9,7 +9,6 @@ import ( "testing" "time" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -102,12 +101,11 @@ func TestPublishUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.EUI64{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} dataUp := UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, } - token := c.PublishUplink(types.AppEUI(eui), types.DevEUI(eui), dataUp) + token := c.PublishUplink("someid", "someid", dataUp) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -118,9 +116,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.EUI64{0x02, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - token := c.SubscribeDeviceUplink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { + token := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { }) token.Wait() @@ -133,9 +129,7 @@ func TestSubscribeAppUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.AppEUI{0x03, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - token := c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { + token := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { }) token.Wait() @@ -148,7 +142,7 @@ func TestSubscribeUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeUplink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { + token := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { }) token.Wait() @@ -161,21 +155,18 @@ func TestPubSubUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{0x04, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - var wg sync.WaitGroup wg.Add(1) - c.SubscribeDeviceUplink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { - a.So(appEUI, ShouldResemble, appEUI) - a.So(devEUI, ShouldResemble, devEUI) + c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { + a.So(appID, ShouldResemble, "app1") + a.So(devID, ShouldResemble, "dev1") wg.Done() }).Wait() - c.PublishUplink(appEUI, devEUI, UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink("app1", "dev1", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } @@ -185,49 +176,22 @@ func TestPubSubAppUplink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{0x05, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI1 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - devEUI2 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} - var wg sync.WaitGroup wg.Add(2) - c.SubscribeAppUplink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { - a.So(appEUI, ShouldResemble, appEUI) + c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { + a.So(appID, ShouldResemble, "app2") a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }).Wait() - c.PublishUplink(appEUI, devEUI1, UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - c.PublishUplink(appEUI, devEUI2, UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink("app2", "dev1", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink("app2", "dev2", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } -func TestInvalidUplink(t *testing.T) { - ctx := GetLogger(t, "TestInvalidUplink") - - c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") - c.Connect() - - eui := types.AppEUI{0x06, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - c.SubscribeAppUplink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req UplinkMessage) { - t.Error("Did not expect any message") - }).Wait() - - // Invalid Topic - c.(*defaultClient).mqtt.Publish("0602030405060708/devices/x/up", QoS, false, []byte{0x00}).Wait() - - // Invalid Payload - c.(*defaultClient).mqtt.Publish("0602030405060708/devices/0602030405060708/up", QoS, false, []byte{0x00}).Wait() - - <-time.After(50 * time.Millisecond) - - ctx.Info("This test should have printed two warnings.") -} - // Downlink pub/sub func TestPublishDownlink(t *testing.T) { @@ -235,12 +199,11 @@ func TestPublishDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.EUI64{0x01, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} dataDown := DownlinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, } - token := c.PublishDownlink(types.AppEUI(eui), types.DevEUI(eui), dataDown) + token := c.PublishDownlink("someid", "someid", dataDown) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -251,9 +214,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.EUI64{0x02, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - token := c.SubscribeDeviceDownlink(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { + token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req DownlinkMessage) { }) token.Wait() @@ -266,9 +227,7 @@ func TestSubscribeAppDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.AppEUI{0x03, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - token := c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { + token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req DownlinkMessage) { }) token.Wait() @@ -281,7 +240,7 @@ func TestSubscribeDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeDownlink(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { + token := c.SubscribeDownlink(func(client Client, appID string, devID string, req DownlinkMessage) { }) token.Wait() @@ -294,21 +253,18 @@ func TestPubSubDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{0x04, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - var wg sync.WaitGroup wg.Add(1) - c.SubscribeDeviceDownlink(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { - a.So(appEUI, ShouldResemble, appEUI) - a.So(devEUI, ShouldResemble, devEUI) + c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req DownlinkMessage) { + a.So(appID, ShouldResemble, "app3") + a.So(devID, ShouldResemble, "dev3") wg.Done() }).Wait() - c.PublishDownlink(appEUI, devEUI, DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink("app3", "dev3", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } @@ -318,49 +274,22 @@ func TestPubSubAppDownlink(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{0x05, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI1 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - devEUI2 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} - var wg sync.WaitGroup wg.Add(2) - c.SubscribeAppDownlink(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { - a.So(appEUI, ShouldResemble, appEUI) + c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { + a.So(appID, ShouldResemble, "app4") a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }).Wait() - c.PublishDownlink(appEUI, devEUI1, DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - c.PublishDownlink(appEUI, devEUI2, DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink("app4", "dev1", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink("app4", "dev2", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() } -func TestInvalidDownlink(t *testing.T) { - ctx := GetLogger(t, "TestInvalidDownlink") - - c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") - c.Connect() - - eui := types.AppEUI{0x06, 0x03, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - c.SubscribeAppDownlink(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req DownlinkMessage) { - t.Error("Did not expect any message") - }).Wait() - - // Invalid Topic - c.(*defaultClient).mqtt.Publish("0603030405060708/devices/x/down", QoS, false, []byte{0x00}).Wait() - - // Invalid Payload - c.(*defaultClient).mqtt.Publish("0603030405060708/devices/0602030405060708/down", QoS, false, []byte{0x00}).Wait() - - <-time.After(50 * time.Millisecond) - - ctx.Info("This test should have printed two warnings.") -} - // Activations pub/sub func TestPublishActivations(t *testing.T) { @@ -368,10 +297,9 @@ func TestPublishActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.EUI64{0x01, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} dataActivations := Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}} - token := c.PublishActivation(types.AppEUI(eui), types.DevEUI(eui), dataActivations) + token := c.PublishActivation("someid", "someid", dataActivations) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -382,9 +310,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.EUI64{0x02, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - token := c.SubscribeDeviceActivations(types.AppEUI(eui), types.DevEUI(eui), func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { + token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req Activation) { }) token.Wait() @@ -397,9 +323,7 @@ func TestSubscribeAppActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - eui := types.AppEUI{0x03, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - token := c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { + token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req Activation) { }) token.Wait() @@ -412,7 +336,7 @@ func TestSubscribeActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - token := c.SubscribeActivations(func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { + token := c.SubscribeActivations(func(client Client, appID string, devID string, req Activation) { }) token.Wait() @@ -425,21 +349,18 @@ func TestPubSubActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{0x04, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - var wg sync.WaitGroup wg.Add(1) - c.SubscribeDeviceActivations(appEUI, devEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { - a.So(appEUI, ShouldResemble, appEUI) - a.So(devEUI, ShouldResemble, devEUI) + c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req Activation) { + a.So(appID, ShouldResemble, "app5") + a.So(devID, ShouldResemble, "dev1") wg.Done() }).Wait() - c.PublishActivation(appEUI, devEUI, Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation("app5", "dev1", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() wg.Wait() } @@ -449,45 +370,18 @@ func TestPubSubAppActivations(t *testing.T) { c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() - appEUI := types.AppEUI{0x05, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - devEUI1 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01} - devEUI2 := types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x02} - var wg sync.WaitGroup wg.Add(2) - c.SubscribeAppActivations(appEUI, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { - a.So(appEUI, ShouldResemble, appEUI) + c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { + a.So(appID, ShouldResemble, "app6") a.So(req.Metadata[0].DataRate, ShouldEqual, "SF7BW125") wg.Done() }).Wait() - c.PublishActivation(appEUI, devEUI1, Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() - c.PublishActivation(appEUI, devEUI2, Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation("app6", "dev1", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation("app6", "dev2", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() wg.Wait() } - -func TestInvalidActivations(t *testing.T) { - ctx := GetLogger(t, "TestInvalidActivations") - - c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") - c.Connect() - - eui := types.AppEUI{0x06, 0x04, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08} - - c.SubscribeAppActivations(eui, func(client Client, appEUI types.AppEUI, devEUI types.DevEUI, req Activation) { - t.Error("Did not expect any message") - }).Wait() - - // Invalid Topic - c.(*defaultClient).mqtt.Publish("0604030405060708/devices/x/activations", QoS, false, []byte{0x00}).Wait() - - // Invalid Payload - c.(*defaultClient).mqtt.Publish("0604030405060708/devices/0602030405060708/activations", QoS, false, []byte{0x00}).Wait() - - <-time.After(50 * time.Millisecond) - - ctx.Info("This test should have printed two warnings.") -} diff --git a/mqtt/topics.go b/mqtt/topics.go index 296355da6..e7b492a9a 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -5,10 +5,7 @@ package mqtt import ( "fmt" - "reflect" "regexp" - - "github.com/TheThingsNetwork/ttn/core/types" ) // DeviceTopicType represents the type of a device topic @@ -26,48 +23,40 @@ const ( const wildcard = "+" // DeviceTopic represents an MQTT topic for application devices -// If the DevEUI is an empty []byte{}, it is considered to be a wildcard type DeviceTopic struct { - AppEUI types.AppEUI - DevEUI types.DevEUI - Type DeviceTopicType + AppID string + DevID string + Type DeviceTopicType } // ParseDeviceTopic parses an MQTT device topic string to a DeviceTopic struct func ParseDeviceTopic(topic string) (*DeviceTopic, error) { - var err error - pattern := regexp.MustCompile("([0-9A-F]{16}|\\+)/(devices)/([0-9A-F]{16}|\\+)/(activations|up|down)") + pattern := regexp.MustCompile("^([[:alnum:]](?:[_-]?[[:alnum:]]){1,35}|\\+)/(devices)/([[:alnum:]](?:[_-]?[[:alnum:]]){1,35}|\\+)/(activations|up|down)$") matches := pattern.FindStringSubmatch(topic) if len(matches) < 4 { return nil, fmt.Errorf("Invalid topic format") } - var appEUI types.AppEUI + var appID string if matches[1] != wildcard { - appEUI, err = types.ParseAppEUI(matches[1]) - if err != nil { - return nil, err - } + appID = matches[1] } - var devEUI types.DevEUI - if matches[1] != wildcard { - devEUI, err = types.ParseDevEUI(matches[3]) - if err != nil { - return nil, err - } + var devID string + if matches[3] != wildcard { + devID = matches[3] } topicType := DeviceTopicType(matches[4]) - return &DeviceTopic{appEUI, devEUI, topicType}, nil + return &DeviceTopic{appID, devID, topicType}, nil } // String implements the Stringer interface func (t DeviceTopic) String() string { - appEUI := wildcard - if !reflect.DeepEqual(t.AppEUI, types.AppEUI{}) { - appEUI = t.AppEUI.String() + appID := wildcard + if t.AppID != "" { + appID = t.AppID } - devEUI := wildcard - if !reflect.DeepEqual(t.DevEUI, types.DevEUI{}) { - devEUI = t.DevEUI.String() + devID := wildcard + if t.DevID != "" { + devID = t.DevID } - return fmt.Sprintf("%s/%s/%s/%s", appEUI, "devices", devEUI, t.Type) + return fmt.Sprintf("%s/%s/%s/%s", appID, "devices", devID, t.Type) } diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index f6c913ff3..cdeffe616 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -6,19 +6,18 @@ package mqtt import ( "testing" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) func TestParseDeviceTopic(t *testing.T) { a := New(t) - topic := "0102030405060708/devices/0807060504030201/up" + topic := "AppID-1/devices/DevID-1/up" expected := &DeviceTopic{ - AppEUI: types.AppEUI{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, - DevEUI: types.DevEUI{0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01}, - Type: Uplink, + AppID: "AppID-1", + DevID: "DevID-1", + Type: Uplink, } got, err := ParseDeviceTopic(topic) @@ -30,28 +29,16 @@ func TestParseDeviceTopic(t *testing.T) { func TestParseDeviceTopicInvalid(t *testing.T) { a := New(t) - _, err := ParseDeviceTopic("000000000000000a/devices/0807060504030201/up") // AppEUI contains lowercase hex chars + _, err := ParseDeviceTopic("AppID:Invalid/devices/dev/up") a.So(err, ShouldNotBeNil) - _, err = ParseDeviceTopic("01020304050607/devices/0807060504030201/up") // AppEUI too short + _, err = ParseDeviceTopic("AppID-1/devices/DevID:Invalid/up") // DevEUI contains lowercase hex chars a.So(err, ShouldNotBeNil) - _, err = ParseDeviceTopic("abcdefghijklmnop/devices/08070605040302/up") // AppEUI contains invalid characters + _, err = ParseDeviceTopic("AppID-1/fridges/DevID-1/up") // We don't support fridges (at least, not specifically fridges) a.So(err, ShouldNotBeNil) - _, err = ParseDeviceTopic("0102030405060708/devices/000000000000000a/up") // DevEUI contains lowercase hex chars - a.So(err, ShouldNotBeNil) - - _, err = ParseDeviceTopic("0102030405060708/devices/08070605040302/up") // DevEUI too short - a.So(err, ShouldNotBeNil) - - _, err = ParseDeviceTopic("0102030405060708/devices/abcdefghijklmnop/up") // DevEUI contains invalid characters - a.So(err, ShouldNotBeNil) - - _, err = ParseDeviceTopic("0102030405060708/fridges/0102030405060708/up") // We don't support fridges (at least, not specifically fridges) - a.So(err, ShouldNotBeNil) - - _, err = ParseDeviceTopic("0102030405060708/devices/0102030405060708/emotions") // Devices usually don't publish emotions + _, err = ParseDeviceTopic("AppID-1/devices/DevID-1/emotions") // Devices usually don't publish emotions a.So(err, ShouldNotBeNil) } @@ -59,12 +46,12 @@ func TestTopicString(t *testing.T) { a := New(t) topic := &DeviceTopic{ - AppEUI: types.AppEUI{0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0}, - DevEUI: types.DevEUI{0x28, 0x27, 0x26, 0x25, 0x24, 0x23, 0x22, 0x21}, - Type: Downlink, + AppID: "AppID-1", + DevID: "DevID-1", + Type: Downlink, } - expected := "123456789ABCDEF0/devices/2827262524232221/down" + expected := "AppID-1/devices/DevID-1/down" got := topic.String() diff --git a/mqtt/types.go b/mqtt/types.go index 668daef71..a4cf255d8 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -27,8 +27,7 @@ type Metadata struct { // UplinkMessage represents an application-layer uplink message type UplinkMessage struct { AppID string `json:"app_id,omitempty"` - AppEUI types.AppEUI `json:"app_eui,omitempty"` - DevEUI types.DevEUI `json:"dev_eui,omitempty"` + DevID string `json:"dev_id,omitempty"` Payload []byte `json:"payload,omitempty"` FPort uint8 `json:"port,omitempty"` Fields map[string]interface{} `json:"fields,omitempty"` @@ -39,8 +38,7 @@ type UplinkMessage struct { // DownlinkMessage represents an application-layer downlink message type DownlinkMessage struct { AppID string `json:"app_id,omitempty"` - AppEUI types.AppEUI `json:"app_eui,omitempty"` - DevEUI types.DevEUI `json:"dev_eui,omitempty"` + DevID string `json:"dev_id,omitempty"` Payload []byte `json:"payload,omitempty"` FPort uint8 `json:"port,omitempty"` Fields map[string]interface{} `json:"fields,omitempty"` @@ -49,8 +47,10 @@ type DownlinkMessage struct { // Activation are used to notify application of a device activation type Activation struct { - AppID string `json:"app_id,omitempty"` - AppEUI types.AppEUI `json:"app_eui,omitempty"` - DevEUI types.DevEUI `json:"dev_eui,omitempty"` - Metadata []Metadata `json:"metadata"` + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + AppEUI types.AppEUI `json:"app_eui,omitempty"` + DevEUI types.DevEUI `json:"dev_eui,omitempty"` + DevAddr types.DevAddr `json:"dev_addr,omitempty"` + Metadata []Metadata `json:"metadata"` } From 82bcab30a804ead3f05121e27d519d4d235def45 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 14 Jul 2016 15:53:25 +0200 Subject: [PATCH 1558/2266] Check validity of AppIDs and DevIDs --- api/broker/validation.go | 6 ++++-- api/handler/validation.go | 14 +++++++------- api/protocol/lorawan/validation.go | 7 ++++++- api/validation.go | 10 ++++++++++ 4 files changed, 27 insertions(+), 10 deletions(-) create mode 100644 api/validation.go diff --git a/api/broker/validation.go b/api/broker/validation.go index 5a9207efd..6e15ba9c0 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -1,5 +1,7 @@ package broker +import "github.com/TheThingsNetwork/ttn/api" + // Validate implements the api.Validator interface func (m *DownlinkOption) Validate() bool { if m.Identifier == "" { @@ -56,7 +58,7 @@ func (m *DeduplicatedUplinkMessage) Validate() bool { if m.AppEui == nil || m.AppEui.IsEmpty() { return false } - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { @@ -101,7 +103,7 @@ func (m *DeduplicatedDeviceActivationRequest) Validate() bool { // Validate implements the api.Validator interface func (m *ApplicationHandlerRegistration) Validate() bool { - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } if m.HandlerId == "" { diff --git a/api/handler/validation.go b/api/handler/validation.go index 822d2730b..1619c17e5 100644 --- a/api/handler/validation.go +++ b/api/handler/validation.go @@ -4,7 +4,7 @@ import "github.com/TheThingsNetwork/ttn/api" // Validate implements the api.Validator interface func (m *DeviceActivationResponse) Validate() bool { - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { @@ -18,7 +18,7 @@ func (m *DeviceActivationResponse) Validate() bool { // Validate implements the api.Validator interface func (m *ApplicationIdentifier) Validate() bool { - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } return true @@ -26,7 +26,7 @@ func (m *ApplicationIdentifier) Validate() bool { // Validate implements the api.Validator interface func (m *Application) Validate() bool { - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } return true @@ -34,10 +34,10 @@ func (m *Application) Validate() bool { // Validate implements the api.Validator interface func (m *DeviceIdentifier) Validate() bool { - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } - if m.DevId == "" { + if m.DevId == "" || !api.ValidID(m.DevId) { return false } return true @@ -45,10 +45,10 @@ func (m *DeviceIdentifier) Validate() bool { // Validate implements the api.Validator interface func (m *Device) Validate() bool { - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } - if m.DevId == "" { + if m.DevId == "" || !api.ValidID(m.DevId) { return false } if m.Device == nil || !api.Validate(m.Device) { diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 06a7f373f..4f298ea07 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -1,5 +1,7 @@ package lorawan +import "github.com/TheThingsNetwork/ttn/api" + // Validate implements the api.Validator interface func (m *DeviceIdentifier) Validate() bool { if m.AppEui == nil || m.AppEui.IsEmpty() { @@ -19,7 +21,10 @@ func (m *Device) Validate() bool { if m.DevEui == nil || m.DevEui.IsEmpty() { return false } - if m.AppId == "" { + if m.AppId == "" || !api.ValidID(m.AppId) { + return false + } + if m.DevId == "" || !api.ValidID(m.DevId) { return false } return true diff --git a/api/validation.go b/api/validation.go new file mode 100644 index 000000000..7ef0aaec2 --- /dev/null +++ b/api/validation.go @@ -0,0 +1,10 @@ +package api + +import "regexp" + +var idRegexp = regexp.MustCompile("^[[:alnum:]](?:[_-]?[[:alnum:]]){1,35}$") + +// ValidID returns true if the given ID is a valid application or device ID +func ValidID(id string) bool { + return idRegexp.Match([]byte(id)) +} From d2368e4ac04c0c0f43a273e0dafe2b8dc057ec93 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jul 2016 11:48:48 +0200 Subject: [PATCH 1559/2266] Give devices with DisableFCntCheck lower priority --- core/broker/uplink.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index b28e56166..2b3b20002 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -209,6 +209,12 @@ func selectBestDownlink(options []*pb.DownlinkOption) *pb.DownlinkOption { // ByFCntUp implements sort.Interface for []*pb_lorawan.Device based on FCnt type ByFCntUp []*pb_lorawan.Device -func (a ByFCntUp) Len() int { return len(a) } -func (a ByFCntUp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByFCntUp) Less(i, j int) bool { return a[i].FCntUp < a[j].FCntUp } +func (a ByFCntUp) Len() int { return len(a) } +func (a ByFCntUp) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByFCntUp) Less(i, j int) bool { + // Devices that disable the FCnt check have low priority + if a[i].DisableFCntCheck { + return 2*int(a[i].FCntUp)+100 < int(a[j].FCntUp) + } + return int(a[i].FCntUp) < int(a[j].FCntUp) +} From 35df2b5ec9eeec83c0461f3b1a149883e64a18b6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jul 2016 11:50:46 +0200 Subject: [PATCH 1560/2266] Changes in Device management (handler) - Delete old device from NS when AppEUI/DevEUI changed - Include DevID in NS registration - Delete devices on Application delete --- core/handler/manager_server.go | 79 +++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 3d7bb127f..6464572ee 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -80,7 +80,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) } func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack, error) { - _, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) + dev, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) if err != nil && err != device.ErrNotFound { return nil, err } @@ -89,18 +89,49 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack return nil, grpcErrf(codes.InvalidArgument, "Invalid Device") } + lorawan := in.GetLorawanDevice() + if lorawan == nil { + return nil, grpcErrf(codes.InvalidArgument, "No LoRaWAN Device") + } + + if dev != nil { // When this is an update + if dev.AppEUI != *lorawan.AppEui || dev.DevEUI != *lorawan.DevEui { + // If the AppEUI or DevEUI is changed, we should remove the device from the NetworkServer and re-add it later + _, err = h.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{ + AppEui: &dev.AppEUI, + DevEui: &dev.DevEUI, + }) + if err != nil { + return nil, err + } + } + } else { // When this is a create + + } + updated := &device.Device{ - AppID: in.AppId, - DevID: in.DevId, + AppID: in.AppId, + DevID: in.DevId, + AppEUI: *lorawan.AppEui, + DevEUI: *lorawan.DevEui, } - lorawan := in.GetLorawanDevice() - if lorawan == nil { - err = grpcErrf(codes.InvalidArgument, "No LoRaWAN Device") + if lorawan.DevAddr != nil { + updated.DevAddr = *lorawan.DevAddr + } + if lorawan.NwkSKey != nil { + updated.NwkSKey = *lorawan.NwkSKey + } + if lorawan.AppSKey != nil { + updated.AppSKey = *lorawan.AppSKey + } + if lorawan.AppKey != nil { + updated.AppKey = *lorawan.AppKey } - _, err = h.ttnDeviceManager.SetDevice(ctx, &pb_lorawan.Device{ + nsUpdated := &pb_lorawan.Device{ AppId: in.AppId, + DevId: in.DevId, AppEui: lorawan.AppEui, DevEui: lorawan.DevEui, DevAddr: lorawan.DevAddr, @@ -109,22 +140,11 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack FCntDown: lorawan.FCntDown, DisableFCntCheck: lorawan.DisableFCntCheck, Uses32BitFCnt: lorawan.Uses32BitFCnt, - }) - if err != nil { - return nil, err } - updated.AppEUI = *lorawan.AppEui - updated.DevEUI = *lorawan.DevEui - - if lorawan.DevAddr != nil && lorawan.NwkSKey != nil && lorawan.AppSKey != nil { - updated.DevAddr = *lorawan.DevAddr - updated.NwkSKey = *lorawan.NwkSKey - updated.AppSKey = *lorawan.AppSKey - } - - if lorawan.AppKey != nil { - updated.AppKey = *lorawan.AppKey + _, err = h.ttnDeviceManager.SetDevice(ctx, nsUpdated) + if err != nil { + return nil, err } err = h.devices.Set(updated) @@ -278,6 +298,23 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati return nil, err } + // Get and delete all devices for this application + devices, err := h.devices.ListForApp(in.AppId) + if err != nil { + return nil, err + } + for _, dev := range devices { + _, err = h.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + if err != nil { + return nil, err + } + err = h.devices.Delete(dev.AppID, dev.DevID) + if err != nil { + return nil, err + } + } + + // Delete the Application err = h.applications.Delete(in.AppId) if err != nil { return nil, err From a231b1de4fa0ccd89dbc3f5e22293f5b1f31be1d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jul 2016 14:51:04 +0200 Subject: [PATCH 1561/2266] UpdatedAt and LastSeen for devices --- core/handler/application/application.go | 18 +++++++++++++++++- core/handler/application/store.go | 2 ++ core/handler/device/device.go | 13 +++++++++++++ core/handler/device/store.go | 2 ++ core/networkserver/device/device.go | 16 +++++++++++++++- core/networkserver/device/store.go | 14 ++++++++------ core/networkserver/manager_server.go | 8 +++++++- core/networkserver/networkserver.go | 2 +- 8 files changed, 65 insertions(+), 10 deletions(-) diff --git a/core/handler/application/application.go b/core/handler/application/application.go index a6b5bb9ed..1524ea14f 100644 --- a/core/handler/application/application.go +++ b/core/handler/application/application.go @@ -3,7 +3,10 @@ package application -import "fmt" +import ( + "fmt" + "time" +) // Application contains the state of an application type Application struct { @@ -17,6 +20,8 @@ type Application struct { // Validator is a JavaScript function that validates the data is converted by // Converter and returns a boolean value indicating the validity of the data Validator string + + UpdatedAt time.Time } // ApplicationProperties contains all properties of a Application that can be stored in Redis. @@ -25,6 +30,7 @@ var ApplicationProperties = []string{ "decoder", "converter", "validator", + "updated_at", } // ToStringStringMap converts the given properties of an Application to a @@ -61,6 +67,10 @@ func (application *Application) formatProperty(property string) (formatted strin formatted = application.Converter case "validator": formatted = application.Validator + case "updated_at": + if !application.UpdatedAt.IsZero() { + formatted = application.UpdatedAt.UTC().Format(time.RFC3339Nano) + } default: err = fmt.Errorf("Property %s does not exist in Application", property) } @@ -80,6 +90,12 @@ func (application *Application) parseProperty(property string, value string) err application.Converter = value case "validator": application.Validator = value + case "updated_at": + val, err := time.Parse(time.RFC3339Nano, value) + if err != nil { + return err + } + application.UpdatedAt = val } return nil } diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 75a44b194..993fee849 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -6,6 +6,7 @@ package application import ( "errors" "fmt" + "time" "gopkg.in/redis.v3" ) @@ -124,6 +125,7 @@ func (s *redisApplicationStore) Set(new *Application, fields ...string) error { } key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppID) + new.UpdatedAt = time.Now() dmap, err := new.ToStringStringMap(fields...) if err != nil { return err diff --git a/core/handler/device/device.go b/core/handler/device/device.go index d7a79ae6c..0782f882d 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -7,6 +7,7 @@ import ( "encoding/json" "fmt" "strings" + "time" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" @@ -34,6 +35,7 @@ type Device struct { NwkSKey types.NwkSKey AppSKey types.AppSKey NextDownlink *mqtt.DownlinkMessage + UpdatedAt time.Time } // DeviceProperties contains all properties of a Device that can be stored in Redis. @@ -49,6 +51,7 @@ var DeviceProperties = []string{ "used_dev_nonces", "used_app_nonces", "next_downlink", + "updated_at", } // ToStringStringMap converts the given properties of Device to a @@ -109,6 +112,10 @@ func (device *Device) formatProperty(property string) (formatted string, err err var jsonBytes []byte jsonBytes, err = json.Marshal(device.NextDownlink) formatted = string(jsonBytes) + case "updated_at": + if !device.UpdatedAt.IsZero() { + formatted = device.UpdatedAt.UTC().Format(time.RFC3339Nano) + } default: err = fmt.Errorf("Property %s does not exist in Device", property) } @@ -195,6 +202,12 @@ func (device *Device) parseProperty(property string, value string) error { } device.NextDownlink = nextDownlink } + case "updated_at": + val, err := time.Parse(time.RFC3339Nano, value) + if err != nil { + return err + } + device.UpdatedAt = val } return nil } diff --git a/core/handler/device/store.go b/core/handler/device/store.go index ebfc61024..cd8a5a281 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -6,6 +6,7 @@ package device import ( "errors" "fmt" + "time" "gopkg.in/redis.v3" ) @@ -173,6 +174,7 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { } key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppID, new.DevID) + new.UpdatedAt = time.Now() dmap, err := new.ToStringStringMap(fields...) if err != nil { return err diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 8ab4d1b13..a82533984 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -31,6 +31,7 @@ type Device struct { LastSeen time.Time Options Options Utilization Utilization + UpdatedAt time.Time } // DeviceProperties contains all properties of a Device that can be stored in Redis. @@ -46,6 +47,7 @@ var DeviceProperties = []string{ "last_seen", "options", "utilization", + "updated_at", } // ToStringStringMap converts the given properties of Device to a @@ -91,7 +93,9 @@ func (device *Device) formatProperty(property string) (formatted string, err err case "f_cnt_down": formatted = storage.FormatUint32(device.FCntDown) case "last_seen": - formatted = device.LastSeen.UTC().Format(time.RFC3339Nano) + if !device.LastSeen.IsZero() { + formatted = device.LastSeen.UTC().Format(time.RFC3339Nano) + } case "options": data, err := json.Marshal(device.Options) if err != nil { @@ -100,6 +104,10 @@ func (device *Device) formatProperty(property string) (formatted string, err err formatted = string(data) case "utilization": // TODO + case "updated_at": + if !device.UpdatedAt.IsZero() { + formatted = device.UpdatedAt.UTC().Format(time.RFC3339Nano) + } default: err = fmt.Errorf("Property %s does not exist in Status", property) } @@ -166,6 +174,12 @@ func (device *Device) parseProperty(property string, value string) error { device.Options = options case "utilization": // TODO + case "updated_at": + val, err := time.Parse(time.RFC3339Nano, value) + if err != nil { + return err + } + device.UpdatedAt = val } return nil } diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index a1dd06d88..d71d2a108 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -270,6 +270,7 @@ func (s *redisDeviceStore) Set(new *Device, fields ...string) error { } } + new.UpdatedAt = time.Now() dmap, err := new.ToStringStringMap(fields...) if err != nil { return err @@ -311,15 +312,16 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de // Update Device dev := &Device{ - LastSeen: time.Now(), - DevAddr: devAddr, - NwkSKey: nwkSKey, - FCntUp: 0, - FCntDown: 0, + LastSeen: time.Now(), + UpdatedAt: time.Now(), + DevAddr: devAddr, + NwkSKey: nwkSKey, + FCntUp: 0, + FCntDown: 0, } // Don't touch Utilization and Options - dmap, err := dev.ToStringStringMap("last_seen", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") + dmap, err := dev.ToStringStringMap("last_seen", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down", "updated_at") // Register Device err = s.client.HMSetMap(key, dmap).Err() diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index d54cf1bc5..89791ea1a 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -5,6 +5,7 @@ package networkserver import ( "errors" + "time" "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/networkserver" @@ -45,6 +46,11 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev return nil, err } + lastSeen := time.Unix(0, 0) + if !dev.LastSeen.IsZero() { + lastSeen = dev.LastSeen + } + return &pb_lorawan.Device{ AppId: dev.AppID, AppEui: &dev.AppEUI, @@ -56,7 +62,7 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev FCntDown: dev.FCntDown, DisableFCntCheck: dev.Options.DisableFCntCheck, Uses32BitFCnt: dev.Options.Uses32BitFCnt, - LastSeen: dev.LastSeen.UnixNano(), + LastSeen: lastSeen.UnixNano(), }, nil } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index e21c55469..1d8027e60 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -220,7 +220,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } else { dev.FCntUp = macPayload.FHDR.FCnt } - dev.LastSeen = time.Now().UTC() + dev.LastSeen = time.Now() err = n.devices.Set(dev, "f_cnt_up", "last_seen") if err != nil { return nil, err From 6ecbe3f09e529d2c1bba9990eaf00110116fb5ba Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jul 2016 18:33:40 +0200 Subject: [PATCH 1562/2266] Empty types render as empty string --- core/types/dev_addr.go | 3 +++ core/types/eui.go | 3 +++ core/types/keys.go | 3 +++ core/types/parse_hex.go | 4 ++++ 4 files changed, 13 insertions(+) diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 0efc5230f..f1e36654e 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -31,6 +31,9 @@ func (addr DevAddr) Bytes() []byte { // String implements the Stringer interface. func (addr DevAddr) String() string { + if addr.IsEmpty() { + return "" + } return strings.ToUpper(hex.EncodeToString(addr.Bytes())) } diff --git a/core/types/eui.go b/core/types/eui.go index 1d585782a..0bf12c40a 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -37,6 +37,9 @@ func (eui EUI64) Bytes() []byte { } func (eui EUI64) String() string { + if eui.IsEmpty() { + return "" + } return strings.ToUpper(hex.EncodeToString(eui.Bytes())) } diff --git a/core/types/keys.go b/core/types/keys.go index f01847468..142149c27 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -38,6 +38,9 @@ func (key AES128Key) Bytes() []byte { // String implements the Stringer interface. func (key AES128Key) String() string { + if key.IsEmpty() { + return "" + } return strings.ToUpper(hex.EncodeToString(key.Bytes())) } diff --git a/core/types/parse_hex.go b/core/types/parse_hex.go index 4b07c4526..4820b53e2 100644 --- a/core/types/parse_hex.go +++ b/core/types/parse_hex.go @@ -11,6 +11,10 @@ import ( // ParseHEX parses a string "input" to a byteslice with length "length". func ParseHEX(input string, length int) ([]byte, error) { + if input == "" { + return make([]byte, length), nil + } + pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) if err != nil { return nil, fmt.Errorf("Invalid pattern") From 15a3237b09e92e23bec2a5de81fc3514e0ed0db9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jul 2016 19:38:23 +0200 Subject: [PATCH 1563/2266] Allow un-setting properties in Redis --- api/discovery/announcement.go | 4 +--- core/handler/application/application.go | 4 +--- core/handler/application/application_test.go | 9 ++++---- core/handler/device/device.go | 4 +--- core/handler/device/device_test.go | 1 + core/networkserver/device/device.go | 5 ++--- core/networkserver/device/device_test.go | 22 +++++++++++--------- 7 files changed, 23 insertions(+), 26 deletions(-) diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go index 1b7ce527c..f949be9e9 100644 --- a/api/discovery/announcement.go +++ b/api/discovery/announcement.go @@ -29,9 +29,7 @@ func (announcement *Announcement) ToStringStringMap(properties ...string) (map[s if err != nil { return output, err } - if property != "" { - output[p] = property - } + output[p] = property } return output, nil } diff --git a/core/handler/application/application.go b/core/handler/application/application.go index 1524ea14f..ed0cf377c 100644 --- a/core/handler/application/application.go +++ b/core/handler/application/application.go @@ -42,9 +42,7 @@ func (application *Application) ToStringStringMap(properties ...string) (map[str if err != nil { return output, err } - if property != "" { - output[p] = property - } + output[p] = property } return output, nil } diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go index 4bfc47cee..c3a2e50e7 100644 --- a/core/handler/application/application_test.go +++ b/core/handler/application/application_test.go @@ -16,10 +16,11 @@ func getTestApplication() (application *Application, dmap map[string]string) { Converter: `function (data) { return data; }`, Validator: `function (data) { return data.size % 2 == 0; }`, }, map[string]string{ - "app_id": "AppID-1", - "decoder": `function (payload) { return { size: payload.length; } }`, - "converter": `function (data) { return data; }`, - "validator": `function (data) { return data.size % 2 == 0; }`, + "app_id": "AppID-1", + "decoder": `function (payload) { return { size: payload.length; } }`, + "converter": `function (data) { return data; }`, + "validator": `function (data) { return data.size % 2 == 0; }`, + "updated_at": "", } } diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 0782f882d..8b694f6b3 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -63,9 +63,7 @@ func (device *Device) ToStringStringMap(properties ...string) (map[string]string if err != nil { return output, err } - if property != "" { - output[p] = property - } + output[p] = property } return output, nil } diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index 7d890cc68..10245cf0b 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -35,6 +35,7 @@ func getTestDevice() (device *Device, dmap map[string]string) { "used_dev_nonces": "0102,0304", "used_app_nonces": "010203,040506", "next_downlink": "null", + "updated_at": "", } } diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index a82533984..6f4d5bdf8 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -59,9 +59,8 @@ func (device *Device) ToStringStringMap(properties ...string) (map[string]string if err != nil { return output, err } - if property != "" { - output[p] = property - } + output[p] = property + } return output, nil } diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go index 862802421..86cee2a5a 100644 --- a/core/networkserver/device/device_test.go +++ b/core/networkserver/device/device_test.go @@ -24,16 +24,18 @@ func getTestDevice() (device *Device, dmap map[string]string) { LastSeen: time.Unix(0, 0).UTC(), Options: Options{}, }, map[string]string{ - "dev_eui": "0102030405060708", - "app_eui": "0807060504030201", - "app_id": "TestApp-1", - "dev_id": "TestDev-1", - "dev_addr": "01020304", - "nwk_s_key": "00010002000300040005000600070008", - "f_cnt_up": "42", - "f_cnt_down": "24", - "last_seen": "1970-01-01T00:00:00Z", - "options": `{"disable_fcnt_check":false,"uses_32_bit_fcnt":false}`, + "dev_eui": "0102030405060708", + "app_eui": "0807060504030201", + "app_id": "TestApp-1", + "dev_id": "TestDev-1", + "dev_addr": "01020304", + "nwk_s_key": "00010002000300040005000600070008", + "f_cnt_up": "42", + "f_cnt_down": "24", + "last_seen": "1970-01-01T00:00:00Z", + "options": `{"disable_fcnt_check":false,"uses_32_bit_fcnt":false}`, + "utilization": "", + "updated_at": "", } } From 6c40f0623691e23622a2d07f408bac6df721d602 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 15 Jul 2016 19:39:01 +0200 Subject: [PATCH 1564/2266] Implement wrapper around HandlerManagerClient --- api/handler/manager_client.go | 112 ++++++++++++++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 api/handler/manager_client.go diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go new file mode 100644 index 000000000..3daca1155 --- /dev/null +++ b/api/handler/manager_client.go @@ -0,0 +1,112 @@ +package handler + +import ( + "fmt" + "sync" + + "github.com/TheThingsNetwork/ttn/api" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// ManagerClient is used to manage applications and devices on a handler +type ManagerClient struct { + sync.RWMutex + conn *grpc.ClientConn + context context.Context + applicationManagerClient ApplicationManagerClient +} + +// NewManagerClient returns a new ManagerClient for a handler at the given address that accepts the given access token +func NewManagerClient(address string, accessToken string) (*ManagerClient, error) { + conn, err := grpc.Dial(address, api.DialOptions...) + if err != nil { + return nil, fmt.Errorf("Could not connect to NetworkServer: %s", err.Error()) + } + applicationManagerClient := NewApplicationManagerClient(conn) + md := metadata.Pairs( + "token", accessToken, + ) + manageContext := metadata.NewContext(context.Background(), md) + return &ManagerClient{ + conn: conn, + context: manageContext, + applicationManagerClient: applicationManagerClient, + }, nil +} + +// UpdateAccessToken updates the access token that is used for running commands +func (h *ManagerClient) UpdateAccessToken(accessToken string) { + h.Lock() + defer h.Unlock() + md := metadata.Pairs( + "token", accessToken, + ) + h.context = metadata.NewContext(context.Background(), md) +} + +// GetApplication retrieves an application from the Handler +func (h *ManagerClient) GetApplication(appID string) (*Application, error) { + h.RLock() + defer h.RUnlock() + return h.applicationManagerClient.GetApplication(h.context, &ApplicationIdentifier{AppId: appID}) +} + +// SetApplication sets an application on the Handler +func (h *ManagerClient) SetApplication(in *Application) error { + h.RLock() + defer h.RUnlock() + _, err := h.applicationManagerClient.SetApplication(h.context, in) + return err +} + +// DeleteApplication deletes an application and all its devices from the Handler +func (h *ManagerClient) DeleteApplication(appID string) error { + h.RLock() + defer h.RUnlock() + _, err := h.applicationManagerClient.DeleteApplication(h.context, &ApplicationIdentifier{AppId: appID}) + return err +} + +// GetDevice retrieves a device from the Handler +func (h *ManagerClient) GetDevice(appID string, devID string) (*Device, error) { + h.RLock() + defer h.RUnlock() + return h.applicationManagerClient.GetDevice(h.context, &DeviceIdentifier{AppId: appID, DevId: devID}) +} + +// SetDevice sets a device on the Handler +func (h *ManagerClient) SetDevice(in *Device) error { + h.RLock() + defer h.RUnlock() + _, err := h.applicationManagerClient.SetDevice(h.context, in) + return err +} + +// DeleteDevice deletes a device from the Handler +func (h *ManagerClient) DeleteDevice(appID string, devID string) error { + h.RLock() + defer h.RUnlock() + _, err := h.applicationManagerClient.DeleteDevice(h.context, &DeviceIdentifier{AppId: appID, DevId: devID}) + return err +} + +// GetDevicesForApplication retrieves all devices for an application from the Handler +func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Device, err error) { + h.RLock() + defer h.RUnlock() + res, err := h.applicationManagerClient.GetDevicesForApplication(h.context, &ApplicationIdentifier{AppId: appID}) + if err != nil { + return nil, err + } + for _, dev := range res.Devices { + devices = append(devices, dev) + } + return +} + +// Close closes the client +func (h *ManagerClient) Close() error { + return h.conn.Close() +} From bc9438ee34fe0e9892f748a96dbb76508e3f72bf Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 18 Jul 2016 13:37:40 +0200 Subject: [PATCH 1565/2266] add Access Key type --- core/types/access_keys.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 core/types/access_keys.go diff --git a/core/types/access_keys.go b/core/types/access_keys.go new file mode 100644 index 000000000..e4c3977c9 --- /dev/null +++ b/core/types/access_keys.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +type Right string + +type AccessKey struct { + // The friendly name of the access key + Name string `json:"name"` + + // The hard-to-guess access key + Key string `json:"key"` + + // The rights associated with the key + Rights []Right `json:"rights"` +} + +// HasRight checks if an AccessKey has a certain right +func (k *AccessKey) HasRight(right Right) bool { + for _, r := range k.Rights { + if r == right { + return true + } + } + return false +} From 58b3f65c8ba5b15f81b52afd6069408188a8acd5 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 18 Jul 2016 13:39:52 +0200 Subject: [PATCH 1566/2266] add Application and Collaborators type --- core/account/types.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 core/account/types.go diff --git a/core/account/types.go b/core/account/types.go new file mode 100644 index 000000000..38750852f --- /dev/null +++ b/core/account/types.go @@ -0,0 +1,26 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package application + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// Application represents an application on The Things Network +type Application struct { + ID string `json:"id,required"` + Name string `json:"name"` + EUIs []types.AppEUI `json:"euis,omitempty"` + AccessKeys []types.AccessKey `json:"access_keys,omitempty"` + Created time.Date `json:"created,omitempty"` + Collaborators []Collaborator `json:"collaborators,omitempty"` +} + +// Collaborator is a user that has rights to a certain application +type Collaborator struct { + Username string `json:"username"` + Rights []types.Right `json:"rights"` +} From 11f8bb60ef8bc456775a6b77348d33f967ce2f5d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 18 Jul 2016 13:47:30 +0200 Subject: [PATCH 1567/2266] add API skeleton --- core/account/account.go | 76 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 core/account/account.go diff --git a/core/account/account.go b/core/account/account.go new file mode 100644 index 000000000..990b474a8 --- /dev/null +++ b/core/account/account.go @@ -0,0 +1,76 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// Account is a proxy to an account on the account server +type Account struct { + accessToken string +} + +// New creates a new Account with the default Client +func New(accessToken string) *Account { + return &Account{ + accessToken: accessToken, + } +} + +// ListApplications list all applications +func (a *Account) ListApplications(ctx log.Interface) (apps []Application, err error) { + panic("ListApplications not implemented") +} + +// GetApplication gets a specific application from the account server +func (a *Account) FindApplication(ctx log.Interface, appID string) (Application, error) { + panic("FindApplication not implemented") +} + +// CreateApplication creates a new application on the account server +func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (Application, error) { + panic("CreateApplication not implemented") +} + +// DeleteApplication deletes an application +func (a *Account) DeleteAppliction(ctx log.Interface, appID string) error { + panic("DeleteApplication not implemented") +} + +// Grant adds a collaborator to the application +func (a *Account) Grant(ctx log.Interface, appID string, username string, rights []Right) error { + panic("Grant not implemented") +} + +// Retract removes rights from a collaborator of the application +func (a *Account) Retract(ctx log.Interface, appID string, username string, rights []Right) error { + panic("Retract not implemented") +} + +// AddAccessKey +func (a *Account) AddAccessKey(ctx log.Interface, appID string, key AccessKey) error { + panic("AddAccessKey not implemented") +} + +// RemoveAccessKey +func (a *Account) RemoveAccessKey(ctx log.Interface, appID string, key AccessKey) error { + panic("RemoveAccessKey not implemented") +} + +// ChangeName +func (a *Account) ChangeName(ctx log.Interface, appID string, name string) error { + panic("ChangeName not implemented") +} + +// AddEUI +func (a *Account) AddEUI(ctx log.Interface, appID string, eui types.AppEUI) error { + panic("AddEUI not implemented") +} + +// RemoveEUI +func (a *Account) RemoveEUI(ctx log.Interface, appID string, eui types.AppEUI) error { + panic("RemoveEUI not implemented") +} From 53c66c97acd3dab30e25bcd9ff09c3db6ee6e8b2 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 18 Jul 2016 14:04:16 +0200 Subject: [PATCH 1568/2266] start implementing ListApplications, FindApplication and CreateApplication --- core/account/account.go | 75 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 67 insertions(+), 8 deletions(-) diff --git a/core/account/account.go b/core/account/account.go index 990b474a8..5c08d63c1 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -4,35 +4,94 @@ package account import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/TheThingsNetwork/ttn/core/account/util" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) // Account is a proxy to an account on the account server type Account struct { + // server is the server where the account lives + server string + + // accessToken is the accessToken that gives this client the + // right to act on behalf of the account accessToken string } -// New creates a new Account with the default Client -func New(accessToken string) *Account { +// New creates a new Account for the given server and accessToken +func New(server string, accessToken string) *Account { return &Account{ + server: server, accessToken: accessToken, } } // ListApplications list all applications -func (a *Account) ListApplications(ctx log.Interface) (apps []Application, err error) { - panic("ListApplications not implemented") +func (a *Account) ListApplications() (apps []Application, err error) { + resp, err := util.GET(a.server, a.accessToken, "/applications") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return nil, err + } + + return apps, nil } // GetApplication gets a specific application from the account server -func (a *Account) FindApplication(ctx log.Interface, appID string) (Application, error) { - panic("FindApplication not implemented") +func (a *Account) FindApplication(appID string) (app Application, err error) { + resp, err := util.GET(a.server, a.accessToken, fmt.Printf("/applications/%s", appID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return app, fmt.Errorf("Application with id '%s' does not exist", appID) + } + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return app, err + } + + return app, nil +} + +type createApplicationReq struct { + Name string `json:"name"` + AppID string `json:"id"` + EUIS []types.AppEUI `json:"euis"` } // CreateApplication creates a new application on the account server -func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (Application, error) { - panic("CreateApplication not implemented") +func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (app Application, err error) { + body := createApplicationReq{ + Name: name, + AppID: appID, + EUIs: EUIs, + } + + resp, err := util.POST(a.server, a.accessToken, "/applications", body) + if resp.StatusCode != http.StatusCreated { + return app, fmt.Errorf("Could not create application: %s", resp.Status) + } + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return app, err + } + + return app, nil } // DeleteApplication deletes an application From 0a1bbc04b63dd535f84fa5f64aad42838c843a07 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 18 Jul 2016 17:34:09 +0100 Subject: [PATCH 1569/2266] Update ttnctl --- ttnctl/cmd/applications.go | 244 +---------- ttnctl/cmd/applicationsPayloadFunctions.go | 207 --------- ttnctl/cmd/applications_pf.go | 69 +++ ttnctl/cmd/applications_pf_set.go | 128 ++++++ ttnctl/cmd/device.go | 464 --------------------- ttnctl/cmd/devices.go | 17 + ttnctl/cmd/devices_create.go | 108 +++++ ttnctl/cmd/devices_delete.go | 61 +++ ttnctl/cmd/devices_info.go | 144 +++++++ ttnctl/cmd/devices_list.go | 71 ++++ ttnctl/cmd/devices_personalize.go | 111 +++++ ttnctl/cmd/devices_set.go | 146 +++++++ ttnctl/cmd/downlink.go | 72 ---- ttnctl/cmd/join.go | 217 ---------- ttnctl/cmd/root.go | 32 +- ttnctl/cmd/root_test.go | 12 - ttnctl/cmd/subscribe.go | 90 ---- ttnctl/cmd/uplink.go | 261 ------------ ttnctl/cmd/version.go | 4 +- ttnctl/util/managers.go | 16 - 20 files changed, 867 insertions(+), 1607 deletions(-) delete mode 100644 ttnctl/cmd/applicationsPayloadFunctions.go create mode 100644 ttnctl/cmd/applications_pf.go create mode 100644 ttnctl/cmd/applications_pf_set.go delete mode 100644 ttnctl/cmd/device.go create mode 100644 ttnctl/cmd/devices.go create mode 100644 ttnctl/cmd/devices_create.go create mode 100644 ttnctl/cmd/devices_delete.go create mode 100644 ttnctl/cmd/devices_info.go create mode 100644 ttnctl/cmd/devices_list.go create mode 100644 ttnctl/cmd/devices_personalize.go create mode 100644 ttnctl/cmd/devices_set.go delete mode 100644 ttnctl/cmd/downlink.go delete mode 100644 ttnctl/cmd/join.go delete mode 100644 ttnctl/cmd/root_test.go delete mode 100644 ttnctl/cmd/subscribe.go delete mode 100644 ttnctl/cmd/uplink.go delete mode 100644 ttnctl/util/managers.go diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 64a45109d..9e9e58790 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -3,251 +3,15 @@ package cmd -import ( - "fmt" - "io/ioutil" - "net/http" - "net/url" - "path" - "strings" +import "github.com/spf13/cobra" - "gopkg.in/yaml.v2" - - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/apex/log" - "github.com/gosuri/uitable" - "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// applicationsCmd represents the applications command +// applicationsCmd is the entrypoint for handlerctl var applicationsCmd = &cobra.Command{ Use: "applications", - Short: "Show applications", - Long: `ttnctl applications retrieves the applications of the logged on user.`, - Run: func(cmd *cobra.Command, args []string) { - apps, err := util.GetApplications(ctx) - if err != nil { - ctx.WithError(err).Fatal("Failed to get applications") - } - - ctx.Infof("Found %d application(s)", len(apps)) - table := uitable.New() - table.MaxColWidth = 70 - table.AddRow("EUI", "Name", "Owner", "Access Keys", "Valid") - for _, app := range apps { - table.AddRow(app.EUI, app.Name, app.Owner, strings.Join(app.AccessKeys, ", "), app.Valid) - } - - fmt.Println(table) - }, -} - -var applicationsCreateCmd = &cobra.Command{ - Use: "create [name]", - Short: "Create a new application", - Long: `ttnctl applications create creates a new application.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.Help() - return - } - - server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications", server) - values := url.Values{ - "name": {args[0]}, - } - req, err := util.NewRequestWithAuth(server, "POST", uri, strings.NewReader(values.Encode())) - if err != nil { - ctx.WithError(err).Fatal("Failed to create authenticated request") - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - ctx.WithError(err).Fatal("Failed to create application") - } - if resp.StatusCode != http.StatusCreated { - ctx.Fatalf("Failed to create application: %s", resp.Status) - } - - ctx.Info("Application created successfully") - - // We need to refresh the token to add the new application to the set of - // claims - _, err = util.RefreshToken(server) - if err != nil { - log.WithError(err).Warn("Failed to refresh token. Please login") - } - }, -} - -var applicationsDeleteCmd = &cobra.Command{ - Use: "delete [eui]", - Short: "Delete an application", - Long: `ttnctl application delete deletes an existing application.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.Help() - return - } - - appEUI, err := types.ParseAppEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } - - server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications/%s", server, appEUI) - req, err := util.NewRequestWithAuth(server, "DELETE", uri, nil) - if err != nil { - ctx.WithError(err).Fatal("Failed to create authenticated request") - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - ctx.WithError(err).Fatal("Failed to delete application") - } - if resp.StatusCode != http.StatusOK { - ctx.Fatalf("Failed to delete application: %s", resp.Status) - } - - ctx.Info("Application deleted successfully") - - // We need to refresh the token to remove the application from the set of - // claims - _, err = util.RefreshToken(server) - if err != nil { - log.WithError(err).Warn("Failed to refresh token. Please login") - } - }, -} - -var applicationsAuthorizeCmd = &cobra.Command{ - Use: "authorize [eui] [e-mail]", - Short: "Authorize a user for the application", - Long: `ttnctl applications authorize lets you authorize a user for an application.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 2 { - cmd.Help() - return - } - - appEUI, err := types.ParseAppEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } - - server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications/%s/authorize", server, appEUI) - values := url.Values{ - "email": {args[1]}, - } - req, err := util.NewRequestWithAuth(server, "PUT", uri, strings.NewReader(values.Encode())) - if err != nil { - ctx.WithError(err).Fatal("Failed to create authenticated request") - } - - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - ctx.WithError(err).Fatal("Failed to authorize user") - } - if resp.StatusCode != http.StatusOK { - ctx.Fatalf("Failed to authorize user: %s", resp.Status) - } - - ctx.Info("User authorized successfully") - }, -} - -var applicationsUseCmd = &cobra.Command{ - Use: "use [eui]", - Short: "Set an application as active", - Long: `ttnctl applications use marks an application as the currently active application in ttnctl.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.Help() - return - } - - appEUI, err := types.ParseAppEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } - - // check AppEUI provided is owned by user - apps, err := util.GetApplications(ctx) - if err != nil { - ctx.WithError(err).Fatal("Failed to get applications") - } - - var found bool - for _, app := range apps { - if app.EUI == appEUI { - found = true - break - } - } - - if !found { - ctx.Fatalf("%s not found in registered applications", appEUI) - } - - // Determine config file - cFile := viper.ConfigFileUsed() - if cFile == "" { - dir, err := homedir.Dir() - if err != nil { - ctx.WithError(err).Fatal("Could not get homedir") - } - expanded, err := homedir.Expand(dir) - if err != nil { - ctx.WithError(err).Fatal("Could not get homedir") - } - cFile = path.Join(expanded, ".ttnctl.yaml") - } - - c := make(map[string]interface{}) - - // Read config file - bytes, err := ioutil.ReadFile(cFile) - if err == nil { - err = yaml.Unmarshal(bytes, &c) - } - if err != nil { - ctx.Warnf("Could not read configuration file, will just create a new one") - } - - // Update app - c["app-eui"] = appEUI - - // Write config file - d, err := yaml.Marshal(&c) - if err != nil { - ctx.Fatal("Could not generate configiguration file contents") - } - err = ioutil.WriteFile(cFile, d, 0644) - if err != nil { - ctx.WithError(err).Fatal("Could not write configiguration file") - } - - ctx.Infof("You are now using application %s.", appEUI) - }, + Short: "Manage applications", + Long: `ttnctl applications can be used to manage applications.`, } func init() { RootCmd.AddCommand(applicationsCmd) - applicationsCmd.AddCommand(applicationsCreateCmd) - applicationsCmd.AddCommand(applicationsDeleteCmd) - applicationsCmd.AddCommand(applicationsAuthorizeCmd) - applicationsCmd.AddCommand(applicationsUseCmd) } diff --git a/ttnctl/cmd/applicationsPayloadFunctions.go b/ttnctl/cmd/applicationsPayloadFunctions.go deleted file mode 100644 index b113496f6..000000000 --- a/ttnctl/cmd/applicationsPayloadFunctions.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "fmt" - "io/ioutil" - - "golang.org/x/net/context" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// applicationsPayloadFunctionsCmd represents the applicationsPayloadFunctions command -var applicationsPayloadFunctionsCmd = &cobra.Command{ - Use: "pf", - Short: "Show the payload functions", - Long: `ttnctl applications pf shows the payload functions for decoding, -converting and validating binary payload. -`, - Run: func(cmd *cobra.Command, args []string) { - appEUI := util.GetAppEUI(ctx) - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - manager := util.GetHandlerManager(ctx) - res, err := manager.GetPayloadFunctions(context.Background(), &core.GetPayloadFunctionsReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not get payload functions") - } - - ctx.Info("Has decoder function") - fmt.Println(res.Decoder) - - if res.Converter != "" { - ctx.Info("Has converter function") - fmt.Println(res.Converter) - } - - if res.Validator != "" { - ctx.Info("Has validator function") - fmt.Println(res.Validator) - } else { - ctx.Warn("Does not have validator function") - } - }, -} - -// applicationsSetPayloadFunctionsCmd represents the applicationsSetPayloadFunctions command -var applicationsSetPayloadFunctionsCmd = &cobra.Command{ - Use: "set [decoder.js]", - Short: "Set payload functions", - Long: `ttnctl applications pf set sets the decoder, converter and validator -function by loading the specified files containing JavaScript code. -`, - Run: func(cmd *cobra.Command, args []string) { - appEUI := util.GetAppEUI(ctx) - - if len(args) == 0 { - cmd.Help() - return - } - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - decoder, err := ioutil.ReadFile(args[0]) - if err != nil { - ctx.WithError(err).Fatal("Read decoder file failed") - } - - var converter []byte - converterFile, err := cmd.Flags().GetString("converter") - if converterFile != "" { - converter, err = ioutil.ReadFile(converterFile) - if err != nil { - ctx.WithError(err).Fatal("Read converter file failed") - } - } - - var validator []byte - validatorFile, err := cmd.Flags().GetString("validator") - if validatorFile != "" { - validator, err = ioutil.ReadFile(validatorFile) - if err != nil { - ctx.WithError(err).Fatal("Read validator file failed") - } - } - - manager := util.GetHandlerManager(ctx) - _, err = manager.SetPayloadFunctions(context.Background(), &core.SetPayloadFunctionsReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - Decoder: string(decoder), - Converter: string(converter), - Validator: string(validator), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not set payload functions") - } - ctx.Info("Successfully set payload functions") - }, -} - -// applicationsTestPayloadFunctionsCmd represents the applicationsTestPayloadFunctions command -var applicationsTestPayloadFunctionsCmd = &cobra.Command{ - Use: "test [payload] [decoder.js]", - Short: "Test the payload functions", - Long: `ttnctl applications pf test sends the specified payload functions to -the Handler, as well as a payload to test them on and returns the fields and validation result. -`, - Run: func(cmd *cobra.Command, args []string) { - appEUI := util.GetAppEUI(ctx) - - if len(args) <= 1 { - cmd.Help() - return - } - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - payload, err := types.ParseHEX(args[0], len(args[0])/2) - if err != nil { - ctx.WithError(err).Fatal("Invalid payload") - } - - decoder, err := ioutil.ReadFile(args[1]) - if err != nil { - ctx.WithError(err).Fatal("Read decoder file failed") - } - - var converter []byte - converterFile, err := cmd.Flags().GetString("converter") - if converterFile != "" { - converter, err = ioutil.ReadFile(converterFile) - if err != nil { - ctx.WithError(err).Fatal("Read converter file failed") - } - } - - var validator []byte - validatorFile, err := cmd.Flags().GetString("validator") - if validatorFile != "" { - validator, err = ioutil.ReadFile(validatorFile) - if err != nil { - ctx.WithError(err).Fatal("Read validator file failed") - } - } - - manager := util.GetHandlerManager(ctx) - res, err := manager.TestPayloadFunctions(context.Background(), &core.TestPayloadFunctionsReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - Payload: payload, - Decoder: string(decoder), - Converter: string(converter), - Validator: string(validator), - }) - if err != nil { - ctx.WithError(err).Fatal("Test payload functions failed") - } - - if res.Valid { - ctx.Info("Valid payload") - } else { - ctx.Warn("Invalid payload") - } - fmt.Printf("JSON: %s\n", res.Fields) - }, -} - -func init() { - applicationsCmd.AddCommand(applicationsPayloadFunctionsCmd) - applicationsPayloadFunctionsCmd.AddCommand(applicationsSetPayloadFunctionsCmd) - applicationsPayloadFunctionsCmd.AddCommand(applicationsTestPayloadFunctionsCmd) - - applicationsSetPayloadFunctionsCmd.Flags().StringP("converter", "c", "", "Converter function") - applicationsSetPayloadFunctionsCmd.Flags().StringP("validator", "v", "", "Validator function") - - applicationsTestPayloadFunctionsCmd.Flags().StringP("converter", "c", "", "Converter function") - applicationsTestPayloadFunctionsCmd.Flags().StringP("validator", "v", "", "Validator function") -} diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go new file mode 100644 index 000000000..2f6fe0d65 --- /dev/null +++ b/ttnctl/cmd/applications_pf.go @@ -0,0 +1,69 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// applicationsPayloadFunctionsCmd represents the applicationsPayloadFunctions command +var applicationsPayloadFunctionsCmd = &cobra.Command{ + Use: "pf", + Short: "Show the payload functions", + Long: `ttnctl applications pf shows the payload functions for decoding, +converting and validating binary payload. +`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + app, err := manager.GetApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not get application.") + } + + ctx.Info("Found Application") + + if app.Decoder != "" { + ctx.Info("Decoder function") + fmt.Println(app.Decoder) + } + + if app.Converter != "" { + ctx.Info("Converter function") + fmt.Println(app.Converter) + } + + if app.Validator != "" { + ctx.Info("Validator function") + fmt.Println(app.Validator) + } + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsPayloadFunctionsCmd) + applicationsPayloadFunctionsCmd.AddCommand(applicationsPayloadFunctionsSetCmd) +} diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go new file mode 100644 index 000000000..73a2dfd8b --- /dev/null +++ b/ttnctl/cmd/applications_pf_set.go @@ -0,0 +1,128 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// applicationsPayloadFunctionsSetCmd represents the `applications pf set` command +var applicationsPayloadFunctionsSetCmd = &cobra.Command{ + Use: "set [decoder/converter/validator] [file.js]", + Short: "Set payload functions of an application", + Long: `ttnctl pf set can be used to get or set payload functions of an application. +The functions are read from the supplied file or from STDIN.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + app, err := manager.GetApplication(appID) + if err != nil && strings.Contains(err.Error(), "not found") { + app = &handler.Application{AppId: appID} + } else if err != nil { + ctx.WithError(err).Fatal("Could not get existing application.") + } + + if len(args) == 0 { + cmd.Usage() + return + } + + function := args[0] + + if len(args) == 2 { + content, err := ioutil.ReadFile(args[1]) + if err != nil { + ctx.WithError(err).Fatal("Could not read function file") + } + switch function { + case "decoder": + app.Decoder = string(content) + case "converter": + app.Converter = string(content) + case "validator": + app.Validator = string(content) + default: + ctx.Fatalf("Function %s does not exist", function) + } + } else { + switch function { + case "decoder": + fmt.Println(`function (bytes) { + // bytes is of type Buffer. + + // todo: return an object + return { + payload: bytes, + }; +} +########## Write your Decoder here and end with Ctrl+D (EOF):`) + app.Decoder = readFunction() + case "converter": + fmt.Println(`function (val) { + // val is the output of the decoder function. + + // todo: return an object + return val; +} +########## Write your Converter here and end with Ctrl+D (EOF):`) + app.Converter = readFunction() + case "validator": + fmt.Println(`function (val) { + // val is the output of the converter function. + + // todo: return a boolean + return true; +} +########## Write your Validator here and end with Ctrl+D (EOF):`) + app.Validator = readFunction() + default: + ctx.Fatalf("Function %s does not exist", function) + } + } + + err = manager.SetApplication(app) + if err != nil { + ctx.WithError(err).Fatal("Could not update application") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Updated application") + }, +} + +func readFunction() string { + content, err := ioutil.ReadAll(os.Stdin) + if err != nil { + ctx.WithError(err).Fatal("Could not read function from STDIN.") + } + return strings.TrimSpace(string(content)) +} + +func init() { + applicationsCmd.AddCommand(applicationsPayloadFunctionsSetCmd) +} diff --git a/ttnctl/cmd/device.go b/ttnctl/cmd/device.go deleted file mode 100644 index a83fc9219..000000000 --- a/ttnctl/cmd/device.go +++ /dev/null @@ -1,464 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "bytes" - "fmt" - "reflect" - "strings" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/apex/log" - "github.com/gosuri/uitable" - "github.com/spf13/cobra" - "github.com/spf13/viper" - "golang.org/x/net/context" -) - -const emptyCell = "-" - -var defaultKey = []byte{0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C} - -// devicesCmd represents the `devices` command -var devicesCmd = &cobra.Command{ - Use: "devices", - Short: "Manage devices on the Handler", - Long: `ttnctl devices retrieves a list of devices that your application -registered on the Handler.`, - Run: func(cmd *cobra.Command, args []string) { - - appEUI := util.GetAppEUI(ctx) - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - manager := util.GetHandlerManager(ctx) - defaultDevice, err := manager.GetDefaultDevice(context.Background(), &core.GetDefaultDeviceReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - }) - if err != nil { - // TODO: Check reason; not found is OK here - defaultDevice = nil - } - if defaultDevice != nil { - ctx.Warn("Application activates new devices with default AppKey") - fmt.Printf("Default AppKey: %X\n", defaultDevice.AppKey) - fmt.Printf(" {%s}\n", cStyle(defaultDevice.AppKey, msbf)) - } else { - ctx.Info("Application does not activate new devices with default AppKey") - } - - devices, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not get device list") - } - - ctx.Infof("Found %d personalized devices (ABP)", len(devices.ABP)) - - table := uitable.New() - table.MaxColWidth = 70 - table.AddRow("DevAddr", "FCntUp", "FCntDown", "Flags") - for _, device := range devices.ABP { - devAddr := fmt.Sprintf("%X", device.DevAddr) - var flags string - if (device.Flags & core.RelaxFcntCheck) != 0 { - flags = "relax-fcnt" - } - if flags == "" { - flags = "-" - } - table.AddRow(devAddr, device.FCntUp, device.FCntDown, strings.TrimLeft(flags, ",")) - } - - fmt.Println() - fmt.Println(table) - fmt.Println() - - ctx.Infof("Found %d dynamic devices (OTAA)", len(devices.OTAA)) - table = uitable.New() - table.MaxColWidth = 40 - table.AddRow("DevEUI", "DevAddr", "FCntUp", "FCntDown") - for _, device := range devices.OTAA { - devEUI := fmt.Sprintf("%X", device.DevEUI) - devAddr := fmt.Sprintf("%X", device.DevAddr) - table.AddRow(devEUI, devAddr, device.FCntUp, device.FCntDown) - } - - fmt.Println() - fmt.Println(table) - fmt.Println() - - ctx.Info("Run 'ttnctl devices info [DevAddr|DevEUI]' for more information about a specific device") - }, -} - -const ( - msbf = true - lsbf = false -) - -// devicesInfoCmd represents the `devices info` command -var devicesInfoCmd = &cobra.Command{ - Use: "info [DevAddr|DevEUI]", - Short: "Show device information", - Long: `ttnctl devices info shows information about a specific device.`, - Run: func(cmd *cobra.Command, args []string) { - appEUI := util.GetAppEUI(ctx) - - if len(args) != 1 { - ctx.Fatal("Missing DevAddr or DevEUI") - } - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - manager := util.GetHandlerManager(ctx) - res, err := manager.ListDevices(context.Background(), &core.ListDevicesHandlerReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not get device list") - } - - lmic, _ := cmd.Flags().GetBool("lmic") - - if devEUI, err := types.ParseDevEUI(args[0]); err == nil { - for _, device := range res.OTAA { - if bytes.Equal(device.DevEUI, devEUI.Bytes()) { - fmt.Println("Dynamic device:") - - fmt.Println() - - // LMiC decided to use LSBF for AppEUI and call it ArtEUI - if lmic { - fmt.Printf(" AppEUI: %s (sometimes called ArtEUI)\n", appEUI) - fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(appEUI.Bytes(), lsbf)) - } else { - fmt.Printf(" AppEUI: %s\n", appEUI) - fmt.Printf(" {%s}\n", cStyle(appEUI.Bytes(), msbf)) - } - - fmt.Println() - fmt.Printf(" DevEUI: %X\n", device.DevEUI) - // LMiC decided to use LSBF for DevEUI - if lmic { - fmt.Printf(" {%s} (Note: LSBF)\n", cStyle(device.DevEUI, lsbf)) - } else { - fmt.Printf(" {%s}\n", cStyle(device.DevEUI, msbf)) - } - - fmt.Println() - // LMiC decided to rename AppKey to DevKey - if lmic { - fmt.Printf(" AppKey: %X (sometimes called DevKey)\n", device.AppKey) - } else { - fmt.Printf(" AppKey: %X\n", device.AppKey) - } - fmt.Printf(" {%s}\n", cStyle(device.AppKey, msbf)) - - if len(device.DevAddr) != 0 { - fmt.Println() - fmt.Println(" Activated with the following parameters:") - - fmt.Println() - fmt.Printf(" DevAddr: %X\n", device.DevAddr) - fmt.Printf(" {%s}\n", cStyle(device.DevAddr, msbf)) - - fmt.Println() - fmt.Printf(" NwkSKey: %X\n", device.NwkSKey) - fmt.Printf(" {%s}\n", cStyle(device.NwkSKey, msbf)) - - fmt.Println() - // LMiC decided to rename AppSKey to ArtSKey - if lmic { - fmt.Printf(" AppSKey: %X (sometimes called ArtSKey)\n", device.AppSKey) - } else { - fmt.Printf(" AppSKey: %X\n", device.AppSKey) - } - fmt.Printf(" {%s}\n", cStyle(device.AppSKey, msbf)) - - fmt.Println() - fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) - } else { - fmt.Println() - fmt.Println(" Not yet activated") - } - - return - } - } - } - - if devAddr, err := types.ParseDevAddr(args[0]); err == nil { - for _, device := range res.ABP { - if bytes.Equal(device.DevAddr, devAddr.Bytes()) { - fmt.Println("Personalized device:") - - fmt.Println() - fmt.Printf(" DevAddr: %X\n", device.DevAddr) - fmt.Printf(" {%s}\n", cStyle(device.DevAddr, msbf)) - - fmt.Println() - fmt.Printf(" NwkSKey: %X\n", device.NwkSKey) - fmt.Printf(" {%s}\n", cStyle(device.NwkSKey, msbf)) - - fmt.Println() - // LMiC decided to rename AppSKey to ArtSKey - if lmic { - fmt.Printf(" AppSKey: %X (sometimes called ArtSKey)\n", device.AppSKey) - } else { - fmt.Printf(" AppSKey: %X\n", device.AppSKey) - } - fmt.Printf(" {%s}\n", cStyle(device.AppSKey, msbf)) - - fmt.Println() - fmt.Printf(" FCntUp: %d\n FCntDn: %d\n", device.FCntUp, device.FCntDown) - fmt.Println() - var flags string - if (device.Flags & core.RelaxFcntCheck) != 0 { - flags = "relax-fcnt" - } - if flags == "" { - flags = "-" - } - fmt.Printf(" Flags: %s\n", strings.TrimLeft(flags, ",")) - return - } - } - } else { - ctx.Fatal("Invalid DevAddr or DevEUI") - } - - ctx.Info("Device not found") - - }, -} - -func cStyle(bytes []byte, msbf bool) (output string) { - if !msbf { - bytes = reverse(bytes) - } - for i, b := range bytes { - if i != 0 { - output += ", " - } - output += fmt.Sprintf("0x%02X", b) - } - return -} - -// reverse is used to convert between MSB-first and LSB-first -func reverse(in []byte) (out []byte) { - for i := len(in) - 1; i >= 0; i-- { - out = append(out, in[i]) - } - return -} - -// devicesRegisterCmd represents the `device register` command -var devicesRegisterCmd = &cobra.Command{ - Use: "register [DevEUI] [AppKey]", - Short: "Create or Update registrations on the Handler", - Long: `ttnctl devices register creates or updates an OTAA registration on -the Handler`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) == 0 { - cmd.Help() - return - } - - appEUI := util.GetAppEUI(ctx) - - devEUI, err := types.ParseDevEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevEUI: %s", err) - } - - var appKey types.AppKey - if len(args) >= 2 { - appKey, err = types.ParseAppKey(args[1]) - if err != nil { - ctx.Fatalf("Invalid AppKey: %s", err) - } - } else { - ctx.Info("Generating random AppKey...") - copy(appKey[:], random.Bytes(16)) - } - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - manager := util.GetHandlerManager(ctx) - res, err := manager.UpsertOTAA(context.Background(), &core.UpsertOTAAHandlerReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - DevEUI: devEUI.Bytes(), - AppKey: appKey.Bytes(), - }) - if err != nil || res == nil { - ctx.WithError(err).Fatal("Could not register device") - } - ctx.WithFields(log.Fields{ - "DevEUI": devEUI, - "AppKey": appKey, - }).Info("Registered device") - }, -} - -const netID = 0x13 - -// devicesRegisterPersonalizedCmd represents the `device register personalized` command -var devicesRegisterPersonalizedCmd = &cobra.Command{ - Use: "personalized [DevAddr] [NwkSKey] [AppSKey]", - Short: "Create or update ABP registrations on the Handler", - Long: `ttnctl devices register personalized creates or updates an ABP -registration on the Handler`, - Run: func(cmd *cobra.Command, args []string) { - var devAddr types.DevAddr - var err error - - if len(args) >= 1 { - devAddr, err = types.ParseDevAddr(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevAddr: %s", err) - } - } else { - ctx.Info("Generating random DevAddr...") - copy(devAddr[:], random.Bytes(4)) - devAddr[0] = (netID << 1) | (devAddr[0] & 1) - } - - appEUI := util.GetAppEUI(ctx) - - var nwkSKey types.NwkSKey - var appSKey types.AppSKey - if len(args) >= 3 { - nwkSKey, err = types.ParseNwkSKey(args[1]) - if err != nil { - ctx.Fatalf("Invalid NwkSKey: %s", err) - } - appSKey, err = types.ParseAppSKey(args[2]) - if err != nil { - ctx.Fatalf("Invalid AppSKey: %s", err) - } - if reflect.DeepEqual(nwkSKey.Bytes(), defaultKey) || reflect.DeepEqual(appSKey.Bytes(), defaultKey) { - ctx.Warn("You are using default keys, any attacker can read your data or attack your device's connectivity.") - } - } else { - ctx.Info("Generating random NwkSKey and AppSKey...") - copy(nwkSKey[:], random.Bytes(16)) - copy(appSKey[:], random.Bytes(16)) - } - - var flags uint32 - if value, _ := cmd.Flags().GetBool("relax-fcnt"); value { - flags |= core.RelaxFcntCheck - ctx.Warn("You are disabling frame counter checks. Your device is not protected against replay-attacks.") - } - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - manager := util.GetHandlerManager(ctx) - res, err := manager.UpsertABP(context.Background(), &core.UpsertABPHandlerReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - DevAddr: devAddr.Bytes(), - AppSKey: appSKey.Bytes(), - NwkSKey: nwkSKey.Bytes(), - Flags: flags, - }) - if err != nil || res == nil { - ctx.WithError(err).Fatal("Could not register device") - } - ctx.WithFields(log.Fields{ - "DevAddr": devAddr, - "NwkSKey": nwkSKey, - "AppSKey": appSKey, - "Flags": flags, - }).Info("Registered personalized device") - }, -} - -// devicesRegisterDefaultCmd represents the `device register` command -var devicesRegisterDefaultCmd = &cobra.Command{ - Use: "default [AppKey]", - Short: "Create or update default OTAA registrations on the Handler", - Long: `ttnctl devices register default creates or updates OTAA registrations -on the Handler that have not been explicitly registered using ttnctl devices -register [DevEUI] [AppKey]`, - Run: func(cmd *cobra.Command, args []string) { - appEUI := util.GetAppEUI(ctx) - - var appKey types.AppKey - var err error - if len(args) >= 1 { - appKey, err = types.ParseAppKey(args[0]) - if err != nil { - ctx.Fatalf("Invalid AppKey: %s", err) - } - } else { - ctx.Info("Generating random AppKey...") - copy(appKey[:], random.Bytes(16)) - } - - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found. Please login") - } - - manager := util.GetHandlerManager(ctx) - res, err := manager.SetDefaultDevice(context.Background(), &core.SetDefaultDeviceReq{ - Token: auth.AccessToken, - AppEUI: appEUI.Bytes(), - AppKey: appKey.Bytes(), - }) - if err != nil || res == nil { - ctx.WithError(err).Fatal("Could not set default device settings") - } - ctx.Info("Ok") - }, -} - -func init() { - RootCmd.AddCommand(devicesCmd) - devicesCmd.AddCommand(devicesRegisterCmd) - devicesCmd.AddCommand(devicesInfoCmd) - devicesInfoCmd.Flags().Bool("lmic", false, "Print info for LMiC") - devicesRegisterCmd.AddCommand(devicesRegisterPersonalizedCmd) - devicesRegisterCmd.AddCommand(devicesRegisterDefaultCmd) - devicesRegisterPersonalizedCmd.Flags().Bool("relax-fcnt", false, "Allow frame counter to reset (insecure)") -} diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go new file mode 100644 index 000000000..d405920fe --- /dev/null +++ b/ttnctl/cmd/devices.go @@ -0,0 +1,17 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import "github.com/spf13/cobra" + +// devicesCmd is the entrypoint for handlerctl +var devicesCmd = &cobra.Command{ + Use: "devices", + Short: "Manage devices", + Long: `ttnctl devices can be used to manage devices.`, +} + +func init() { + RootCmd.AddCommand(devicesCmd) +} diff --git a/ttnctl/cmd/devices_create.go b/ttnctl/cmd/devices_create.go new file mode 100644 index 000000000..7d5d3a137 --- /dev/null +++ b/ttnctl/cmd/devices_create.go @@ -0,0 +1,108 @@ +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// devicesCreateCmd represents the `device create` command +var devicesCreateCmd = &cobra.Command{ + Use: "create [Device ID] [DevEUI] [AppKey]", + Short: "Create a new device", + Long: `ttnctl devices create can be used to create a new device.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + if len(args) == 0 { + ctx.Fatalf("Device ID is required") + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + var appEUI types.AppEUI + if suppliedAppEUI := viper.GetString("app-eui"); suppliedAppEUI != "" { + appEUI, err = types.ParseAppEUI(suppliedAppEUI) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + } else { + ctx.Fatal("Missing AppEUI. You should run ttnctl applications use [AppID] [AppEUI]") + } + + var devEUI types.DevEUI + if len(args) > 1 { + devEUI, err = types.ParseDevEUI(args[1]) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + } else { + ctx.Info("Generating random DevEUI...") + copy(devEUI[1:], random.Bytes(7)) + } + + var appKey types.AppKey + if len(args) > 2 { + appKey, err = types.ParseAppKey(args[2]) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + } else { + ctx.Info("Generating random AppKey...") + copy(appKey[:], random.Bytes(16)) + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + err = manager.SetDevice(&handler.Device{ + AppId: appID, + DevId: devID, + Device: &handler.Device_LorawanDevice{LorawanDevice: &lorawan.Device{ + AppId: appID, + DevId: devID, + AppEui: &appEUI, + DevEui: &devEUI, + AppKey: &appKey, + }}, + }) + if err != nil { + ctx.WithError(err).Fatal("Could not create Device") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + "AppEUI": appEUI, + "DevEUI": devEUI, + "AppKey": appKey, + }).Info("Created device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesCreateCmd) +} diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go new file mode 100644 index 000000000..e93a8b1bc --- /dev/null +++ b/ttnctl/cmd/devices_delete.go @@ -0,0 +1,61 @@ +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// devicesDeleteCmd represents the `device delete` command +var devicesDeleteCmd = &cobra.Command{ + Use: "delete [Device ID]", + Short: "Delete a device", + Long: `ttnctl devices delete can be used to delete a device.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + if len(args) == 0 { + cmd.Usage() + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + err = manager.DeleteDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not delete device.") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + }).Info("Deleted device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesDeleteCmd) +} diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go new file mode 100644 index 000000000..6f33d5f31 --- /dev/null +++ b/ttnctl/cmd/devices_info.go @@ -0,0 +1,144 @@ +package cmd + +import ( + "fmt" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// devicesInfoCmd represents the `device info` command +var devicesInfoCmd = &cobra.Command{ + Use: "info [Device ID]", + Short: "Get information about a device", + Long: `ttnctl devices info can be used to get information about a device.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + if len(args) == 0 { + cmd.Usage() + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + dev, err := manager.GetDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not get existing device.") + } + + byteFormat, _ := cmd.Flags().GetString("format") + + ctx.Info("Found device") + + fmt.Println() + + fmt.Printf(" Application ID: %s\n", dev.AppId) + fmt.Printf(" Device ID: %s\n", dev.DevId) + if lorawan := dev.GetLorawanDevice(); lorawan != nil { + lastSeen := "never" + if lorawan.LastSeen > 0 { + lastSeen = fmt.Sprintf("%s", time.Unix(0, 0).Add(time.Duration(lorawan.LastSeen))) + } + + fmt.Printf(" Last Seen: %s\n", lastSeen) + fmt.Println() + fmt.Println(" LoRaWAN Info:") + fmt.Println() + fmt.Printf(" AppEUI: %s\n", formatBytes(lorawan.AppEui, byteFormat)) + fmt.Printf(" DevEUI: %s\n", formatBytes(lorawan.DevEui, byteFormat)) + fmt.Printf(" DevAddr: %s\n", formatBytes(lorawan.DevAddr, byteFormat)) + fmt.Printf(" AppKey: %s\n", formatBytes(lorawan.AppKey, byteFormat)) + fmt.Printf(" AppSKey: %s\n", formatBytes(lorawan.AppSKey, byteFormat)) + fmt.Printf(" NwkSKey: %s\n", formatBytes(lorawan.NwkSKey, byteFormat)) + + fmt.Printf(" FCntUp: %d\n", lorawan.FCntUp) + fmt.Printf(" FCntDown: %d\n", lorawan.FCntDown) + options := []string{} + if lorawan.DisableFCntCheck { + options = append(options, "DisableFCntCheck") + } + if lorawan.Uses32BitFCnt { + options = append(options, "Uses32BitFCnt") + } + fmt.Printf(" Options: %s\n", strings.Join(options, ", ")) + } + + }, +} + +type formattableBytes interface { + IsEmpty() bool + Bytes() []byte +} + +func formatBytes(toPrint interface{}, format string) string { + if i, ok := toPrint.(formattableBytes); ok { + if i.IsEmpty() { + return "" + } + switch format { + case "msb": + return cStyle(i.Bytes(), true) + " (msb first)" + case "lsb": + return cStyle(i.Bytes(), false) + " (lsb first)" + case "hex": + return fmt.Sprintf("%X", i.Bytes()) + } + } + return fmt.Sprintf("%s", toPrint) +} + +// cStyle prints the byte slice in C-Style +func cStyle(bytes []byte, msbf bool) string { + output := "{" + if !msbf { + bytes = reverse(bytes) + } + for i, b := range bytes { + if i != 0 { + output += ", " + } + output += fmt.Sprintf("0x%02X", b) + } + output += "}" + return output +} + +// reverse is used to convert between MSB-first and LSB-first +func reverse(in []byte) (out []byte) { + for i := len(in) - 1; i >= 0; i-- { + out = append(out, in[i]) + } + return +} + +func init() { + devicesCmd.AddCommand(devicesInfoCmd) + devicesInfoCmd.Flags().String("format", "hex", "Formatting: hex/msb/lsb") +} diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go new file mode 100644 index 000000000..c7f1ccc51 --- /dev/null +++ b/ttnctl/cmd/devices_list.go @@ -0,0 +1,71 @@ +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// devicesListCmd represents the `device list` command +var devicesListCmd = &cobra.Command{ + Use: "list", + Short: "List al devices for the current application", + Long: `ttnctl devices list can be used to list all devices for the current application.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + devices, err := manager.GetDevicesForApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not get devices.") + } + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("DevID", "AppEUI", "DevEUI", "DevAddr", "Up/Down") + for _, dev := range devices { + if lorawan := dev.GetLorawanDevice(); lorawan != nil { + devAddr := lorawan.DevAddr + if devAddr.IsEmpty() { + devAddr = nil + } + table.AddRow(dev.DevId, lorawan.AppEui, lorawan.DevEui, devAddr, fmt.Sprintf("%d/%d", lorawan.FCntUp, lorawan.FCntDown)) + } else { + table.AddRow(dev.DevId) + } + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Listed %d devices", len(devices)) + }, +} + +func init() { + devicesCmd.AddCommand(devicesListCmd) +} diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go new file mode 100644 index 000000000..ef9b493b2 --- /dev/null +++ b/ttnctl/cmd/devices_personalize.go @@ -0,0 +1,111 @@ +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// devicesPersonalizeCmd represents the `device personalize` command +var devicesPersonalizeCmd = &cobra.Command{ + Use: "personalize [Device ID] [DevAddr] [NwkSKey] [AppSKey]", + Short: "Personalize a device", + Long: `ttnctl devices personalize can be used to personalize a device (ABP).`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + if len(args) == 0 { + cmd.Usage() + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + var devAddr types.DevAddr + if len(args) > 1 { + devAddr, err = types.ParseDevAddr(args[1]) + if err != nil { + ctx.Fatalf("Invalid DevAddr: %s", err) + } + } else { + ctx.Info("Generating random DevAddr...") + copy(devAddr[:], random.Bytes(8)) + devAddr[0] = (0x13 << 1) | (devAddr[0] & 1) // Use the TTN netID + } + + var nwkSKey types.NwkSKey + if len(args) > 2 { + nwkSKey, err = types.ParseNwkSKey(args[2]) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + } else { + ctx.Info("Generating random NwkSKey...") + copy(nwkSKey[:], random.Bytes(16)) + } + + var appSKey types.AppSKey + if len(args) > 3 { + appSKey, err = types.ParseAppSKey(args[3]) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + } else { + ctx.Info("Generating random AppSKey...") + copy(appSKey[:], random.Bytes(16)) + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + dev, err := manager.GetDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not get existing device.") + } + + dev.GetLorawanDevice().DevAddr = &devAddr + dev.GetLorawanDevice().NwkSKey = &nwkSKey + dev.GetLorawanDevice().AppSKey = &appSKey + dev.GetLorawanDevice().FCntUp = 0 + dev.GetLorawanDevice().FCntDown = 0 + + err = manager.SetDevice(dev) + if err != nil { + ctx.WithError(err).Fatal("Could not update Device") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + "DevAddr": devAddr, + "NwkSKey": nwkSKey, + "AppSKey": appSKey, + }).Info("Personalized device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesPersonalizeCmd) +} diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go new file mode 100644 index 000000000..19a33ea38 --- /dev/null +++ b/ttnctl/cmd/devices_set.go @@ -0,0 +1,146 @@ +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// devicesSetCmd represents the `device set` command +var devicesSetCmd = &cobra.Command{ + Use: "set [Device ID]", + Short: "Set properties of a device", + Long: `ttnctl devices set can be used to set properties of a device.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + if len(args) == 0 { + cmd.Usage() + return + } + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + dev, err := manager.GetDevice(appID, devID) + if err != nil { + ctx.WithError(err).Fatal("Could not get existing device.") + } + + // Do all updates + + if in, err := cmd.Flags().GetString("app-eui"); err == nil && in != "" { + appEUI, err := types.ParseAppEUI(in) + if err != nil { + ctx.Fatalf("Invalid AppEUI: %s", err) + } + dev.GetLorawanDevice().AppEui = &appEUI + } + + if in, err := cmd.Flags().GetString("dev-eui"); err == nil && in != "" { + devEUI, err := types.ParseDevEUI(in) + if err != nil { + ctx.Fatalf("Invalid DevEUI: %s", err) + } + dev.GetLorawanDevice().DevEui = &devEUI + } + + if in, err := cmd.Flags().GetString("dev-addr"); err == nil && in != "" { + devAddr, err := types.ParseDevAddr(in) + if err != nil { + ctx.Fatalf("Invalid DevAddr: %s", err) + } + dev.GetLorawanDevice().DevAddr = &devAddr + } + + if in, err := cmd.Flags().GetString("nwk-s-key"); err == nil && in != "" { + key, err := types.ParseNwkSKey(in) + if err != nil { + ctx.Fatalf("Invalid NwkSKey: %s", err) + } + dev.GetLorawanDevice().NwkSKey = &key + } + + if in, err := cmd.Flags().GetString("app-s-key"); err == nil && in != "" { + key, err := types.ParseAppSKey(in) + if err != nil { + ctx.Fatalf("Invalid AppSKey: %s", err) + } + dev.GetLorawanDevice().AppSKey = &key + } + + if in, err := cmd.Flags().GetString("app-key"); err == nil && in != "" { + key, err := types.ParseAppKey(in) + if err != nil { + ctx.Fatalf("Invalid AppKey: %s", err) + } + dev.GetLorawanDevice().AppKey = &key + } + + if in, err := cmd.Flags().GetInt("fcnt-up"); err == nil && in != -1 { + dev.GetLorawanDevice().FCntUp = uint32(in) + } + + if in, err := cmd.Flags().GetInt("fcnt-down"); err == nil && in != -1 { + dev.GetLorawanDevice().FCntDown = uint32(in) + } + + if in, err := cmd.Flags().GetBool("disable-fcnt-check"); err == nil { + dev.GetLorawanDevice().DisableFCntCheck = in + } + + if in, err := cmd.Flags().GetBool("32-bit-fcnt"); err == nil { + dev.GetLorawanDevice().Uses32BitFCnt = in + } + + err = manager.SetDevice(dev) + if err != nil { + ctx.WithError(err).Fatal("Could not update Device") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + "DevID": devID, + }).Info("Updated device") + }, +} + +func init() { + devicesCmd.AddCommand(devicesSetCmd) + + devicesSetCmd.Flags().String("app-eui", "", "Set AppEUI") + devicesSetCmd.Flags().String("dev-eui", "", "Set DevEUI") + devicesSetCmd.Flags().String("dev-addr", "", "Set DevAddr") + devicesSetCmd.Flags().String("nwk-s-key", "", "Set NwkSKey") + devicesSetCmd.Flags().String("app-s-key", "", "Set AppSKey") + devicesSetCmd.Flags().String("app-key", "", "Set AppKey") + + devicesSetCmd.Flags().Int("fcnt-up", -1, "Set FCnt Up") + devicesSetCmd.Flags().Int("fcnt-down", -1, "Set FCnt Down") + + devicesSetCmd.Flags().Bool("disable-fcnt-check", false, "Disable FCnt check") + devicesSetCmd.Flags().Bool("32-bit-fcnt", false, "Use 32 bit FCnt") +} diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go deleted file mode 100644 index e9fe64d3c..000000000 --- a/ttnctl/cmd/downlink.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/spf13/cobra" -) - -// downlinkCmd represents the `downlink` command -var downlinkCmd = &cobra.Command{ - Use: "downlink [DevEUI] [Payload] [TTL]", - Short: "Send downlink messages to the network", - Long: `ttnctl downlink sends a downlink message to the network - -The DevEUI should be an 8-byte long hex-encoded string (16 chars), the Payload -is a hex-encoded string and the TTL defines the time-to-live of this downlink, -formatted as "1h2m" for one hour and two minutes. The default TTL is one hour.`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 2 { - ctx.Fatal("Insufficient arguments") - } - if len(args) < 3 { - args = append(args, "1h") - } - - appEUI := util.GetAppEUI(ctx) - - devEUI, err := types.ParseDevEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevEUI: %s", err) - } - - var payload []byte - if plain, _ := cmd.Flags().GetBool("plain"); plain { - ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") - payload = []byte(args[1]) - } else { - payload, err = types.ParseHEX(args[1], len(args[1])/2) - if err != nil { - ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") - } - } - - dataDown := core.DataDownAppReq{ - Payload: payload, - TTL: args[2], - } - - if err != nil { - ctx.WithError(err).Fatal("Unable to create downlink payload") - } - - client := util.ConnectMQTTClient(ctx) - defer client.Disconnect() - - token := client.PublishDownlink(appEUI, devEUI, dataDown) - - if token.Wait(); token.Error() != nil { - ctx.WithError(token.Error()).Fatal("Could not publish downlink") - } - ctx.Info("Scheduled downlink") - }, -} - -func init() { - RootCmd.AddCommand(downlinkCmd) - downlinkCmd.Flags().Bool("plain", false, "send payload as plain-text") -} diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go deleted file mode 100644 index 10ebd0fc6..000000000 --- a/ttnctl/cmd/join.go +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "encoding/base64" - "net" - "strings" - "time" - - "github.com/TheThingsNetwork/ttn/core/otaa" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/brocaar/lorawan" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// joinCmd represents the `join-request` command -var joinCmd = &cobra.Command{ - Use: "join [DevEUI] [AppKey]", - Short: "Send a join requests to the network", - Long: `ttnctl join sends a join request to the network`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 2 { - ctx.Fatalf("Insufficient arguments") - } - - // Parse parameters - devEUI, err := types.ParseDevEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevEUI: %s", err) - } - - appKey, err := types.ParseAppKey(args[1]) - if err != nil { - ctx.Fatalf("Invalid appKey: %s", err) - } - - appEUI := util.GetAppEUI(ctx) - - // Generate a DevNonce - var devNonce [2]byte - copy(devNonce[:], random.Token()) - - // Lorawan Payload - joinPayload := lorawan.JoinRequestPayload{ - AppEUI: lorawan.EUI64(appEUI), - DevEUI: lorawan.EUI64(devEUI), - DevNonce: devNonce, - } - phyPayload := &lorawan.PHYPayload{} - phyPayload.MHDR = lorawan.MHDR{ - MType: lorawan.JoinRequest, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = &joinPayload - if err := phyPayload.SetMIC(lorawan.AES128Key(appKey)); err != nil { - ctx.Fatalf("Unable to set MIC: %s", err) - } - - addr, err := net.ResolveUDPAddr("udp", viper.GetString("ttn-router")) - if err != nil { - ctx.Fatalf("Couldn't resolve UDP address: %s", err) - } - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - ctx.Fatalf("Couldn't Dial UDP connection: %s", err) - } - - // Handle downlink - chdown := make(chan bool) - go func() { - // Get PullAck - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - ctx.Fatalf("Error receiving udp datagram: %s", err) - } - pkt := new(semtech.Packet) - if err := pkt.UnmarshalBinary(buf[:n]); err != nil { - ctx.Fatalf("Invalid udp response: %s", err) - } - ctx.Infof("Received PullAck: %s", pkt) - - // Get Ack - buf = make([]byte, 1024) - n, err = conn.Read(buf) - if err != nil { - ctx.Fatalf("Error receiving udp datagram: %s", err) - } - pkt = new(semtech.Packet) - if err := pkt.UnmarshalBinary(buf[:n]); err != nil { - ctx.Fatalf("Invalid udp response: %s", err) - } - ctx.Infof("Received Ack: %s", pkt) - - // Get Downlink, if any - buf = make([]byte, 1024) - n, err = conn.Read(buf) - if err != nil { - ctx.Fatalf("Error receiving udp datagram: %s", err) - } - pkt = new(semtech.Packet) - if err = pkt.UnmarshalBinary(buf[:n]); err != nil { - ctx.Fatalf("Invalid udp response: %s", err) - } - ctx.Infof("Received Downlink: %s", pkt) - defer func() { chdown <- true }() - - if pkt.Payload == nil || pkt.Payload.TXPK == nil || pkt.Payload.TXPK.Data == nil { - ctx.Fatalf("No payload available in downlink response") - } - - data, err := base64.RawStdEncoding.DecodeString(*pkt.Payload.TXPK.Data) - if err != nil { - ctx.Fatalf("Unable to decode data payload: %s", err) - } - - payload := &lorawan.PHYPayload{} - if err := payload.UnmarshalBinary(data); err != nil { - ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) - } - - if err := payload.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)); err != nil { - ctx.Fatalf("Unable to decrypt MACPayload: %s", err) - } - - joinAccept, ok := payload.MACPayload.(*lorawan.JoinAcceptPayload) - if !ok { - ctx.Fatalf("Unable to retrieve LoRaWAN Join-Accept Payload") - } - - // Generate Session keys - appSKey, nwkSKey, err := otaa.CalculateSessionKeys(appKey, joinAccept.AppNonce, joinAccept.NetID, devNonce) - if err != nil { - ctx.Fatal("Unable to compute session keys") - } - - ctx.Info("Network Joined.") - ctx.Infof("Device Address: %X", joinAccept.DevAddr[:]) - ctx.Infof("Network Session Key: %X", nwkSKey) - ctx.Infof("Application Session Key: %X", appSKey) - ctx.Infof("Available Frequencies: %v", joinAccept.CFList) - }() - - // PULL_DATA Packet - - pullPacket := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PULL_DATA, - } - pullData, err := pullPacket.MarshalBinary() - if err != nil { - ctx.Fatal("Unable to construct pull_data") - } - - // Router Packet - data, err := phyPayload.MarshalBinary() - if err != nil { - ctx.Fatalf("Couldn't construct LoRaWAN physical payload: %s", err) - } - encoded := strings.Trim(base64.StdEncoding.EncodeToString(data), "=") - payload := semtech.Packet{ - Identifier: semtech.PUSH_DATA, - Token: random.Token(), - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Version: semtech.VERSION, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Rssi: pointer.Int32(random.Rssi()), - Lsnr: pointer.Float32(random.Lsnr()), - Freq: pointer.Float32(random.Freq()), - Datr: pointer.String(random.Datr()), - Codr: pointer.String(random.Codr()), - Modu: pointer.String("LoRa"), - Tmst: pointer.Uint32(1), - Data: &encoded, - }, - }, - }, - } - - ctx.Infof("Sending packet: %s", payload.String()) - - data, err = payload.MarshalBinary() - if err != nil { - ctx.Fatalf("Unable to construct framepayload: %v", data) - } - - _, err = conn.Write(pullData) - if err != nil { - ctx.Fatal("Unable to send pull_data") - } - - _, err = conn.Write(data) - if err != nil { - ctx.Fatal("Unable to send payload") - } - - select { - case <-chdown: - case <-time.After(2 * time.Second): - } - }, -} - -func init() { - RootCmd.AddCommand(joinCmd) -} diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 5dafe6225..d52ef1bf1 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -22,30 +22,7 @@ var ctx log.Interface var RootCmd = &cobra.Command{ Use: "ttnctl", Short: "Control The Things Network from the command line", - Long: `ttnctl controls The Things Network from the command line. - -Quick start guide: - 1. Create an account: - $ ttnctl user create [Your Email] - 2. Sign in: - $ ttnctl user login [Your Email] - 3. Create an application: - $ ttnctl applications create [Application Name] - 4. List your applications: - $ ttnctl applications - 5. Choose an application to use from now on: - $ ttnctl applications use [EUI] - 6. Create a new device: - $ ttnctl devices register [DevEUI] [AppKey] - 7. List your devices: - $ ttnctl devices - 8. Get info about a specific device: - $ ttnctl devices info [DevEUI] - 9. Subscribe to incoming messages from this device: - $ ttnctl subscribe [DevEUI] - 10. Schedule downlink to this device: - $ ttnctl downlink [DevEUI] [Hex-encoded Payload] - `, + Long: `ttnctl controls The Things Network from the command line.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { var logLevel = log.InfoLevel if viper.GetBool("debug") { @@ -72,15 +49,18 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") - RootCmd.PersistentFlags().String("ttn-router", "staging.thethingsnetwork.org:1700", "The net address of the TTN Router") + RootCmd.PersistentFlags().String("ttn-router", "staging.thethingsnetwork.org:1901", "The address of the TTN Router") viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router")) - RootCmd.PersistentFlags().String("ttn-handler", "staging.thethingsnetwork.org:1782", "The net address of the TTN Handler") + RootCmd.PersistentFlags().String("ttn-handler", "staging.thethingsnetwork.org:1782", "The address of the TTN Handler") viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) RootCmd.PersistentFlags().String("mqtt-broker", "staging.thethingsnetwork.org:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) + RootCmd.PersistentFlags().String("app-id", "", "The app ID to use") + viper.BindPFlag("app-id", RootCmd.PersistentFlags().Lookup("app-id")) + RootCmd.PersistentFlags().String("app-eui", "", "The app EUI to use") viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) diff --git a/ttnctl/cmd/root_test.go b/ttnctl/cmd/root_test.go deleted file mode 100644 index 5f559daa4..000000000 --- a/ttnctl/cmd/root_test.go +++ /dev/null @@ -1,12 +0,0 @@ -package cmd - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestRootCmd(t *testing.T) { - a := New(t) - a.So(RootCmd.IsAvailableCommand(), ShouldBeTrue) -} diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go deleted file mode 100644 index 59efe1947..000000000 --- a/ttnctl/cmd/subscribe.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "os" - "os/signal" - "regexp" - - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/spf13/cobra" -) - -var subscribeCmd = &cobra.Command{ - Use: "subscribe [DevEUI]", - Short: "Subscribe to uplink messages from a device", - Long: `ttnctl subscribe prints out uplink messages from a device as they -arrive. - -The optional DevEUI argument can be used to only receive messages from a -specific device. By default you will receive messages from all devices of your -application.`, - Run: func(cmd *cobra.Command, args []string) { - - appEUI := util.GetAppEUI(ctx) - - var devEUI types.DevEUI - if len(args) > 0 { - devEUI, err := types.ParseDevEUI(args[0]) - if err != nil { - ctx.Fatalf("Invalid DevEUI: %s", err) - } - ctx.Infof("Subscribing uplink messages from device %s", devEUI) - } else { - ctx.Infof("Subscribing to uplink messages from all devices in application %s", appEUI) - } - - client := util.ConnectMQTTClient(ctx) - - token := client.SubscribeDeviceUplink(appEUI, devEUI, func(client mqtt.Client, appEUI types.AppEUI, devEUI types.DevEUI, dataUp core.DataUpAppReq) { - ctx := ctx.WithField("DevEUI", devEUI) - - // TODO: Find out what Metadata people want to see here - - if plain, _ := cmd.Flags().GetBool("plain"); plain { - unprintable, _ := regexp.Compile(`[^[:print:]]`) - if unprintable.Match(dataUp.Payload) { - ctx.WithField("warning", "payload contains unprintable characters").Infof("%X", dataUp.Payload) - } else { - ctx.Infof("%s", dataUp.Payload) - } - } else { - ctx.Infof("%X", dataUp.Payload) - } - - if l := len(dataUp.Payload); l > 20 { - ctx.Warnf("Your payload has a size of %d bytes. We recommend to send no more than 20 bytes.", l) - } - - // TODO: Add warnings for airtime / duty-cycle / fair-use - }) - - if token.Wait(); token.Error() != nil { - ctx.WithError(token.Error()).Fatal("Could not subscribe") - } - ctx.Info("Subscribed. Waiting for messages...") - - if plain, _ := cmd.Flags().GetBool("plain"); plain { - ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") - } - - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - - // Block until a signal is received. - <-c - - client.Disconnect() - - }, -} - -func init() { - RootCmd.AddCommand(subscribeCmd) - subscribeCmd.Flags().Bool("plain", false, "parse payload as plain-text") -} diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go deleted file mode 100644 index 70f1e7fd7..000000000 --- a/ttnctl/cmd/uplink.go +++ /dev/null @@ -1,261 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package cmd - -import ( - "encoding/base64" - "net" - "regexp" - "strconv" - "strings" - "time" - - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/semtech" - "github.com/TheThingsNetwork/ttn/utils/pointer" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/brocaar/lorawan" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// uplinkCmd represents the `uplink` command -var uplinkCmd = &cobra.Command{ - Use: "uplink [ShouldConfirm] [DevAddr] [NwkSKey] [AppSKey] [Payload] [FCnt]", - Short: "Send uplink messages to the network", - Long: `ttnctl uplink sends an uplink message to the network`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 6 { - ctx.Fatalf("Insufficient arguments") - } - - // Parse parameters - var mtype lorawan.MType - switch args[0] { - case "yes": - fallthrough - case "true": - mtype = lorawan.ConfirmedDataUp - default: - mtype = lorawan.UnconfirmedDataUp - } - - devAddr, err := types.ParseDevAddr(args[1]) - if err != nil { - ctx.Fatalf("Invalid DevAddr: %s", err) - } - - nwkSKey, err := types.ParseNwkSKey(args[2]) - if err != nil { - ctx.Fatalf("Invalid NwkSKey: %s", err) - } - - appSKey, err := types.ParseAppSKey(args[3]) - if err != nil { - ctx.Fatalf("Invalid appSKey: %s", err) - } - - fcnt, err := strconv.ParseInt(args[5], 10, 64) - if err != nil { - ctx.Fatalf("Invalid FCnt: %s", err) - } - - // Lorawan Payload - macPayload := &lorawan.MACPayload{} - macPayload.FHDR = lorawan.FHDR{ - DevAddr: lorawan.DevAddr(devAddr), - FCnt: uint32(fcnt), - } - macPayload.FPort = pointer.Uint8(1) - if plain, _ := cmd.Flags().GetBool("plain"); plain { - ctx.Warn("Sending data as plain text is bad practice. We recommend to transmit data in a binary format.") - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: []byte(args[4])}} - } else { - payload, err := types.ParseHEX(args[4], len(args[4])/2) - if err != nil { - ctx.Fatalf("Invalid hexadecimal payload. If you are trying to send a plain-text payload, use the --plain flag.") - } - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: payload}} - } - phyPayload := &lorawan.PHYPayload{} - phyPayload.MHDR = lorawan.MHDR{ - MType: mtype, - Major: lorawan.LoRaWANR1, - } - phyPayload.MACPayload = macPayload - if err := phyPayload.EncryptFRMPayload(lorawan.AES128Key(appSKey)); err != nil { - ctx.Fatalf("Unable to encrypt frame payload: %s", err) - } - if err := phyPayload.SetMIC(lorawan.AES128Key(nwkSKey)); err != nil { - ctx.Fatalf("Unable to set MIC: %s", err) - } - - addr, err := net.ResolveUDPAddr("udp", viper.GetString("ttn-router")) - if err != nil { - ctx.Fatalf("Couldn't resolve UDP address: %s", err) - } - conn, err := net.DialUDP("udp", nil, addr) - if err != nil { - ctx.Fatalf("Couldn't Dial UDP connection: %s", err) - } - - // Handle downlink - chdown := make(chan bool) - go func() { - // Get PullAck - buf := make([]byte, 1024) - n, err := conn.Read(buf) - if err != nil { - ctx.Fatalf("Error receiving udp datagram: %s", err) - } - pkt := new(semtech.Packet) - if err := pkt.UnmarshalBinary(buf[:n]); err != nil { - ctx.Fatalf("Invalid udp response: %s", err) - } - ctx.Infof("Received PullAck: %s", pkt) - - // Get Ack - buf = make([]byte, 1024) - n, err = conn.Read(buf) - if err != nil { - ctx.Fatalf("Error receiving udp datagram: %s", err) - } - pkt = new(semtech.Packet) - if err := pkt.UnmarshalBinary(buf[:n]); err != nil { - ctx.Fatalf("Invalid udp response: %s", err) - } - ctx.Infof("Received Ack: %s", pkt) - - // Get Downlink, if any - buf = make([]byte, 1024) - n, err = conn.Read(buf) - if err != nil { - ctx.Fatalf("Error receiving udp datagram: %s", err) - } - pkt = new(semtech.Packet) - if err = pkt.UnmarshalBinary(buf[:n]); err != nil { - ctx.Fatalf("Invalid udp response: %s", err) - } - ctx.Infof("Received Downlink: %s", pkt) - defer func() { chdown <- true }() - - if pkt.Payload == nil || pkt.Payload.TXPK == nil || pkt.Payload.TXPK.Data == nil { - ctx.Fatalf("No payload available in downlink response") - } - - data, err := base64.RawStdEncoding.DecodeString(*pkt.Payload.TXPK.Data) - if err != nil { - ctx.Fatalf("Unable to decode data payload: %s", err) - } - - payload := &lorawan.PHYPayload{} - if err := payload.UnmarshalBinary(data); err != nil { - ctx.Fatalf("Unable to retrieve LoRaWAN PhyPayload: %s", err) - } - - micOK, _ := payload.ValidateMIC(lorawan.AES128Key(nwkSKey)) - if !micOK { - ctx.Warn("MIC check failed.") - } - - macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) - if !ok || len(macPayload.FRMPayload) > 1 { - ctx.Fatalf("Unable to retrieve LoRaWAN MACPayload") - } - ctx.Infof("Frame counter: %d", macPayload.FHDR.FCnt) - if len(macPayload.FRMPayload) > 0 { - decrypted, err := lorawan.EncryptFRMPayload( - lorawan.AES128Key(appSKey), - false, - lorawan.DevAddr(devAddr), - macPayload.FHDR.FCnt, - macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes, - ) - if err != nil { - ctx.Fatalf("Unable to decrypt MACPayload: %s", err) - } - if plain, _ := cmd.Flags().GetBool("plain"); plain { - unprintable, _ := regexp.Compile(`[^[:print:]]`) - if unprintable.Match(decrypted) { - ctx.WithField("warning", "payload contains unprintable characters").Infof("Decrypted Payload: %X", decrypted) - } else { - ctx.Infof("%s", decrypted) - } - } else { - ctx.Infof("Decrypted Payload: %X", decrypted) - } - } else { - ctx.Infof("The frame payload was empty.") - } - }() - - // PULL_DATA Packet - - pullPacket := semtech.Packet{ - Version: semtech.VERSION, - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Token: []byte{1, 2}, - Identifier: semtech.PULL_DATA, - } - pullData, err := pullPacket.MarshalBinary() - if err != nil { - ctx.Fatal("Unable to construct pull_data") - } - - // Router Packet - data, err := phyPayload.MarshalBinary() - if err != nil { - ctx.Fatalf("Couldn't construct LoRaWAN physical payload: %s", err) - } - encoded := strings.Trim(base64.StdEncoding.EncodeToString(data), "=") - payload := semtech.Packet{ - Identifier: semtech.PUSH_DATA, - Token: random.Token(), - GatewayId: []byte{1, 2, 3, 4, 5, 6, 7, 8}, - Version: semtech.VERSION, - Payload: &semtech.Payload{ - RXPK: []semtech.RXPK{ - { - Rssi: pointer.Int32(random.Rssi()), - Lsnr: pointer.Float32(random.Lsnr()), - Freq: pointer.Float32(random.Freq()), - Datr: pointer.String(random.Datr()), - Codr: pointer.String(random.Codr()), - Modu: pointer.String("LoRa"), - Tmst: pointer.Uint32(1), - Time: pointer.Time(time.Now().UTC()), - Data: &encoded, - }, - }, - }, - } - - ctx.Infof("Sending packet: %s", payload.String()) - - data, err = payload.MarshalBinary() - if err != nil { - ctx.Fatalf("Unable to construct framepayload: %v", data) - } - - _, err = conn.Write(pullData) - if err != nil { - ctx.Fatal("Unable to send pull_data") - } - - _, err = conn.Write(data) - if err != nil { - ctx.Fatal("Unable to send payload") - } - - select { - case <-chdown: - case <-time.After(2 * time.Second): - } - }, -} - -func init() { - RootCmd.AddCommand(uplinkCmd) - uplinkCmd.Flags().Bool("plain", false, "send payload as plain-text") -} diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index 178527026..80a11895f 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -16,8 +16,8 @@ var versionCmd = &cobra.Command{ Long: `ttnctl version gets the build and version information of ttnctl`, Run: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "commit": viper.GetString("gitCommit"), - "build date": viper.GetString("buildDate"), + "Commit": viper.GetString("gitCommit"), + "BuildDate": viper.GetString("buildDate"), }).Infof("You are running %s of ttnctl.", viper.GetString("version")) }, } diff --git a/ttnctl/util/managers.go b/ttnctl/util/managers.go deleted file mode 100644 index ee2e1d302..000000000 --- a/ttnctl/util/managers.go +++ /dev/null @@ -1,16 +0,0 @@ -package util - -import ( - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/components/handler" - "github.com/apex/log" - "github.com/spf13/viper" -) - -func GetHandlerManager(ctx log.Interface) core.AuthHandlerClient { - cli, err := handler.NewClient(viper.GetString("ttn-handler")) - if err != nil { - ctx.Fatalf("Could not connect: %v", err) - } - return cli -} From f97a5f7398c541247396d3b8368ae62563e7e5cc Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 10:28:53 +0200 Subject: [PATCH 1570/2266] move application specific code to applications.go --- core/account/account.go | 113 --------------------------------- core/account/applications.go | 117 +++++++++++++++++++++++++++++++++++ 2 files changed, 117 insertions(+), 113 deletions(-) create mode 100644 core/account/applications.go diff --git a/core/account/account.go b/core/account/account.go index 5c08d63c1..53168733c 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -3,16 +3,6 @@ package account -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/TheThingsNetwork/ttn/core/account/util" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/apex/log" -) - // Account is a proxy to an account on the account server type Account struct { // server is the server where the account lives @@ -30,106 +20,3 @@ func New(server string, accessToken string) *Account { accessToken: accessToken, } } - -// ListApplications list all applications -func (a *Account) ListApplications() (apps []Application, err error) { - resp, err := util.GET(a.server, a.accessToken, "/applications") - if err != nil { - return nil, err - } - defer resp.Body.Close() - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return nil, err - } - - return apps, nil -} - -// GetApplication gets a specific application from the account server -func (a *Account) FindApplication(appID string) (app Application, err error) { - resp, err := util.GET(a.server, a.accessToken, fmt.Printf("/applications/%s", appID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return app, fmt.Errorf("Application with id '%s' does not exist", appID) - } - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return app, err - } - - return app, nil -} - -type createApplicationReq struct { - Name string `json:"name"` - AppID string `json:"id"` - EUIS []types.AppEUI `json:"euis"` -} - -// CreateApplication creates a new application on the account server -func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (app Application, err error) { - body := createApplicationReq{ - Name: name, - AppID: appID, - EUIs: EUIs, - } - - resp, err := util.POST(a.server, a.accessToken, "/applications", body) - if resp.StatusCode != http.StatusCreated { - return app, fmt.Errorf("Could not create application: %s", resp.Status) - } - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return app, err - } - - return app, nil -} - -// DeleteApplication deletes an application -func (a *Account) DeleteAppliction(ctx log.Interface, appID string) error { - panic("DeleteApplication not implemented") -} - -// Grant adds a collaborator to the application -func (a *Account) Grant(ctx log.Interface, appID string, username string, rights []Right) error { - panic("Grant not implemented") -} - -// Retract removes rights from a collaborator of the application -func (a *Account) Retract(ctx log.Interface, appID string, username string, rights []Right) error { - panic("Retract not implemented") -} - -// AddAccessKey -func (a *Account) AddAccessKey(ctx log.Interface, appID string, key AccessKey) error { - panic("AddAccessKey not implemented") -} - -// RemoveAccessKey -func (a *Account) RemoveAccessKey(ctx log.Interface, appID string, key AccessKey) error { - panic("RemoveAccessKey not implemented") -} - -// ChangeName -func (a *Account) ChangeName(ctx log.Interface, appID string, name string) error { - panic("ChangeName not implemented") -} - -// AddEUI -func (a *Account) AddEUI(ctx log.Interface, appID string, eui types.AppEUI) error { - panic("AddEUI not implemented") -} - -// RemoveEUI -func (a *Account) RemoveEUI(ctx log.Interface, appID string, eui types.AppEUI) error { - panic("RemoveEUI not implemented") -} diff --git a/core/account/applications.go b/core/account/applications.go new file mode 100644 index 000000000..2be446a8f --- /dev/null +++ b/core/account/applications.go @@ -0,0 +1,117 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/TheThingsNetwork/ttn/core/account/util" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// ListApplications list all applications +func (a *Account) ListApplications() (apps []Application, err error) { + resp, err := util.GET(a.server, a.accessToken, "/applications") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return nil, err + } + + return apps, nil +} + +// GetApplication gets a specific application from the account server +func (a *Account) FindApplication(appID string) (app Application, err error) { + resp, err := util.GET(a.server, a.accessToken, fmt.Printf("/applications/%s", appID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return app, fmt.Errorf("Application with id '%s' does not exist", appID) + } + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return app, err + } + + return app, nil +} + +type createApplicationReq struct { + Name string `json:"name"` + AppID string `json:"id"` + EUIS []types.AppEUI `json:"euis"` +} + +// CreateApplication creates a new application on the account server +func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (app Application, err error) { + body := createApplicationReq{ + Name: name, + AppID: appID, + EUIs: EUIs, + } + + resp, err := util.POST(a.server, a.accessToken, "/applications", body) + if resp.StatusCode != http.StatusCreated { + return app, fmt.Errorf("Could not create application: %s", resp.Status) + } + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return app, err + } + + return app, nil +} + +// DeleteApplication deletes an application +func (a *Account) DeleteAppliction(ctx log.Interface, appID string) error { + panic("DeleteApplication not implemented") +} + +// Grant adds a collaborator to the application +func (a *Account) Grant(ctx log.Interface, appID string, username string, rights []Right) error { + panic("Grant not implemented") +} + +// Retract removes rights from a collaborator of the application +func (a *Account) Retract(ctx log.Interface, appID string, username string, rights []Right) error { + panic("Retract not implemented") +} + +// AddAccessKey +func (a *Account) AddAccessKey(ctx log.Interface, appID string, key AccessKey) error { + panic("AddAccessKey not implemented") +} + +// RemoveAccessKey +func (a *Account) RemoveAccessKey(ctx log.Interface, appID string, key AccessKey) error { + panic("RemoveAccessKey not implemented") +} + +// ChangeName +func (a *Account) ChangeName(ctx log.Interface, appID string, name string) error { + panic("ChangeName not implemented") +} + +// AddEUI +func (a *Account) AddEUI(ctx log.Interface, appID string, eui types.AppEUI) error { + panic("AddEUI not implemented") +} + +// RemoveEUI +func (a *Account) RemoveEUI(ctx log.Interface, appID string, eui types.AppEUI) error { + panic("RemoveEUI not implemented") +} From 15ce500441eaea9f554325d8b2ee8f455e539556 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 10:30:57 +0200 Subject: [PATCH 1571/2266] add http helper functions --- core/account/util/client.go | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 core/account/util/client.go diff --git a/core/account/util/client.go b/core/account/util/client.go new file mode 100644 index 000000000..5e567ed67 --- /dev/null +++ b/core/account/util/client.go @@ -0,0 +1,97 @@ +package util + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" +) + +// NewRequest creates a new http.Request that has authorization set up +func newRequest(server string, accessToken string, method string, URI string, body io.Reader) (*http.Request, error) { + URL := fmt.Sprint("%s%s", server, URI) + req, err := http.NewRequest(method, URL, body) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", fmt.Sprintf("bearer %s", accessToken)) + req.Header.Set("Accept", "application/json") + + return req, nil +} + +// GET does a get request to the account server +func GET(server, accessToken, URI string) (*http.Response, error) { + req, err := c.NewRequest(server, accessToken, "GET", URI, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} + +// DELETE does a delete request to the account server +func DELETE(server, accessToken, URI string) (*http.Response, error) { + req, err := c.NewRequest(server, accessToken, "DELETE", URI, nil) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} + +// POST creates an HTTP Post request to the specified server, with the body +// encoded as JSON +func POST(server, accessToken, URI string, body interface{}) (*http.Response, error) { + buf := new(bytes.Buffer) + encoder := json.NewEncoder(buf) + encoder.Encode(body) + if err != nil { + return nil, err + } + req, err := c.NewRequest(server, accessToken, "POST", URI, buf) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} + +// POST creates an HTTP Put request to the specified server, with the body +// encoded as JSON +func PUT(server, accessToken, URI string, body interface{}) (*http.Response, error) { + buf := new(bytes.Buffer) + encoder := json.NewEncoder(buf) + err := encoder.Encode(body) + if err != nil { + return nil, err + } + req, err := c.NewRequest(server, accessToken, "PUT", URI, buf) + if err != nil { + return nil, err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, err + } + return resp, nil +} From 0d91714b78634f09f5b4438861023fd46bc5aff7 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 10:33:58 +0200 Subject: [PATCH 1572/2266] remove ctx.Interface from the API, jsut return the error --- core/account/applications.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index 2be446a8f..77acdeb48 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -10,7 +10,6 @@ import ( "github.com/TheThingsNetwork/ttn/core/account/util" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/apex/log" ) // ListApplications list all applications @@ -56,7 +55,7 @@ type createApplicationReq struct { } // CreateApplication creates a new application on the account server -func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (app Application, err error) { +func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppEUI) (app Application, err error) { body := createApplicationReq{ Name: name, AppID: appID, @@ -77,41 +76,41 @@ func (a *Account) CreateApplication(ctx log.Interface, appID string, name string } // DeleteApplication deletes an application -func (a *Account) DeleteAppliction(ctx log.Interface, appID string) error { +func (a *Account) DeleteAppliction(appID string) error { panic("DeleteApplication not implemented") } // Grant adds a collaborator to the application -func (a *Account) Grant(ctx log.Interface, appID string, username string, rights []Right) error { +func (a *Account) Grant(appID string, username string, rights []Right) error { panic("Grant not implemented") } // Retract removes rights from a collaborator of the application -func (a *Account) Retract(ctx log.Interface, appID string, username string, rights []Right) error { +func (a *Account) Retract(appID string, username string, rights []Right) error { panic("Retract not implemented") } // AddAccessKey -func (a *Account) AddAccessKey(ctx log.Interface, appID string, key AccessKey) error { +func (a *Account) AddAccessKey(appID string, key AccessKey) error { panic("AddAccessKey not implemented") } // RemoveAccessKey -func (a *Account) RemoveAccessKey(ctx log.Interface, appID string, key AccessKey) error { +func (a *Account) RemoveAccessKey(appID string, key AccessKey) error { panic("RemoveAccessKey not implemented") } // ChangeName -func (a *Account) ChangeName(ctx log.Interface, appID string, name string) error { +func (a *Account) ChangeName(appID string, name string) error { panic("ChangeName not implemented") } // AddEUI -func (a *Account) AddEUI(ctx log.Interface, appID string, eui types.AppEUI) error { +func (a *Account) AddEUI(appID string, eui types.AppEUI) error { panic("AddEUI not implemented") } // RemoveEUI -func (a *Account) RemoveEUI(ctx log.Interface, appID string, eui types.AppEUI) error { +func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { panic("RemoveEUI not implemented") } From 39c5aacf90417097dcc2f0111046056b02377aa5 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 10:35:16 +0200 Subject: [PATCH 1573/2266] correct status code check --- core/account/applications.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/account/applications.go b/core/account/applications.go index 77acdeb48..bfa91bc66 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -36,7 +36,7 @@ func (a *Account) FindApplication(appID string) (app Application, err error) { } defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { + if resp.StatusCode == http.StatusNotFound { return app, fmt.Errorf("Application with id '%s' does not exist", appID) } From d424e9fd43e8d974cdd7451b9c09634d16810ec1 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 12:16:22 +0200 Subject: [PATCH 1574/2266] extract http stuff completely into util, and add test --- core/account/util/client.go | 97 ------------------------------ core/account/util/http.go | 107 +++++++++++++++++++++++++++++++++ core/account/util/http_test.go | 84 ++++++++++++++++++++++++++ 3 files changed, 191 insertions(+), 97 deletions(-) delete mode 100644 core/account/util/client.go create mode 100644 core/account/util/http.go create mode 100644 core/account/util/http_test.go diff --git a/core/account/util/client.go b/core/account/util/client.go deleted file mode 100644 index 5e567ed67..000000000 --- a/core/account/util/client.go +++ /dev/null @@ -1,97 +0,0 @@ -package util - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" -) - -// NewRequest creates a new http.Request that has authorization set up -func newRequest(server string, accessToken string, method string, URI string, body io.Reader) (*http.Request, error) { - URL := fmt.Sprint("%s%s", server, URI) - req, err := http.NewRequest(method, URL, body) - if err != nil { - return nil, err - } - - req.Header.Set("Authorization", fmt.Sprintf("bearer %s", accessToken)) - req.Header.Set("Accept", "application/json") - - return req, nil -} - -// GET does a get request to the account server -func GET(server, accessToken, URI string) (*http.Response, error) { - req, err := c.NewRequest(server, accessToken, "GET", URI, nil) - if err != nil { - return nil, err - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - return resp, nil -} - -// DELETE does a delete request to the account server -func DELETE(server, accessToken, URI string) (*http.Response, error) { - req, err := c.NewRequest(server, accessToken, "DELETE", URI, nil) - if err != nil { - return nil, err - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - return resp, nil -} - -// POST creates an HTTP Post request to the specified server, with the body -// encoded as JSON -func POST(server, accessToken, URI string, body interface{}) (*http.Response, error) { - buf := new(bytes.Buffer) - encoder := json.NewEncoder(buf) - encoder.Encode(body) - if err != nil { - return nil, err - } - req, err := c.NewRequest(server, accessToken, "POST", URI, buf) - if err != nil { - return nil, err - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - return resp, nil -} - -// POST creates an HTTP Put request to the specified server, with the body -// encoded as JSON -func PUT(server, accessToken, URI string, body interface{}) (*http.Response, error) { - buf := new(bytes.Buffer) - encoder := json.NewEncoder(buf) - err := encoder.Encode(body) - if err != nil { - return nil, err - } - req, err := c.NewRequest(server, accessToken, "PUT", URI, buf) - if err != nil { - return nil, err - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - return resp, nil -} diff --git a/core/account/util/http.go b/core/account/util/http.go new file mode 100644 index 000000000..7f25441fa --- /dev/null +++ b/core/account/util/http.go @@ -0,0 +1,107 @@ +package util + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + "gopkg.in/validator.v2" +) + +type HTTPError struct { + Code int + Message string +} + +func (e HTTPError) Error() string { + return e.Message +} + +// NewRequest creates a new http.Request that has authorization set up +func newRequest(server string, accessToken string, method string, URI string, body io.Reader) (*http.Request, error) { + URL := fmt.Sprintf("%s%s", server, URI) + req, err := http.NewRequest(method, URL, body) + if err != nil { + return nil, err + } + + req.Header.Set("Authorization", fmt.Sprintf("bearer %s", accessToken)) + req.Header.Set("Accept", "application/json") + + return req, nil +} + +func performRequest(server, accessToken, method, URI string, body, res interface{}) (err error) { + var req *http.Request + + if body != nil { + // body is not nil, so serialize it and pass it in the request + buf := new(bytes.Buffer) + encoder := json.NewEncoder(buf) + err = encoder.Encode(body) + if err != nil { + return err + } + req, err = newRequest(server, accessToken, method, URI, buf) + } else { + // body is nil so create a nil request + req, err = newRequest(server, accessToken, method, URI, nil) + } + + // + if err != nil { + return err + } + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return HTTPError{ + Code: resp.StatusCode, + Message: resp.Status, + } + } + + if res != nil { + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(res); err != nil { + return err + } + + if err = validator.Validate(res); err != nil { + // return first error + return fmt.Errorf("Got a bad response from server: %s", err) + } + } + + return nil +} + +// GET does a get request to the account server +func GET(server, accessToken, URI string, res interface{}) error { + return performRequest(server, accessToken, "GET", URI, nil, res) +} + +// DELETE does a delete request to the account server +func DELETE(server, accessToken, URI string) error { + return performRequest(server, accessToken, "DELETE", URI, nil, nil) +} + +// POST creates an HTTP Post request to the specified server, with the body +// encoded as JSON +func POST(server, accessToken, URI string, body, res interface{}) error { + return performRequest(server, accessToken, "POST", URI, body, res) +} + +// POST creates an HTTP Put request to the specified server, with the body +// encoded as JSON +func PUT(server, accessToken, URI string, body, res interface{}) error { + return performRequest(server, accessToken, "PUT", URI, body, res) +} diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go new file mode 100644 index 000000000..f2f9caf2f --- /dev/null +++ b/core/account/util/http_test.go @@ -0,0 +1,84 @@ +package util + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + . "github.com/smartystreets/assertions" +) + +type OKResp struct { + OK string `json:"ok"` +} + +type FooResp struct { + Foo string `json:"foo" validate:"nonzero"` +} + +func OKHandler(a *Assertion, method string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.So(r.RequestURI, ShouldEqual, "/foo") + a.So(r.Method, ShouldEqual, method) + resp := OKResp{ + OK: "ok", + } + w.WriteHeader(http.StatusOK) + encoder := json.NewEncoder(w) + encoder.Encode(&resp) + }) +} + +func FooHandler(a *Assertion, method string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.So(r.RequestURI, ShouldEqual, "/foo") + a.So(r.Method, ShouldEqual, method) + resp := FooResp{ + Foo: "ok", + } + w.WriteHeader(http.StatusOK) + encoder := json.NewEncoder(w) + encoder.Encode(&resp) + }) +} + +func TestGET(t *testing.T) { + a := New(t) + server := httptest.NewServer(OKHandler(a, "GET")) + defer server.Close() + + var resp OKResp + err := GET(server.URL, "ok", "/foo", &resp) + a.So(err, ShouldBeNil) + a.So(resp.OK, ShouldEqual, "ok") +} + +func TestGETDropResponse(t *testing.T) { + a := New(t) + server := httptest.NewServer(OKHandler(a, "GET")) + defer server.Close() + + err := GET(server.URL, "ok", "/foo", nil) + a.So(err, ShouldBeNil) +} + +func TestGETWrongResponseType(t *testing.T) { + a := New(t) + server := httptest.NewServer(OKHandler(a, "GET")) + defer server.Close() + + var resp FooResp + err := GET(server.URL, "ok", "/foo", &resp) + a.So(err, ShouldNotBeNil) +} + +func TestGETWrongResponseTypeIgnore(t *testing.T) { + a := New(t) + server := httptest.NewServer(FooHandler(a, "GET")) + defer server.Close() + + var resp OKResp + err := GET(server.URL, "ok", "/foo", &resp) + a.So(err, ShouldBeNil) +} From 8daca4e7882b0da3a1276f329e8ce23538e3ae18 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 12:16:34 +0200 Subject: [PATCH 1575/2266] use new http funcs --- core/account/applications.go | 59 +++++++----------------------------- 1 file changed, 11 insertions(+), 48 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index bfa91bc66..f82208ddf 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -4,54 +4,26 @@ package account import ( - "encoding/json" - "fmt" - "net/http" - "github.com/TheThingsNetwork/ttn/core/account/util" "github.com/TheThingsNetwork/ttn/core/types" ) // ListApplications list all applications func (a *Account) ListApplications() (apps []Application, err error) { - resp, err := util.GET(a.server, a.accessToken, "/applications") - if err != nil { - return nil, err - } - defer resp.Body.Close() - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return nil, err - } - - return apps, nil + err = util.GET(a.server, a.accessToken, "/applications", &apps) + return apps, err } // GetApplication gets a specific application from the account server func (a *Account) FindApplication(appID string) (app Application, err error) { - resp, err := util.GET(a.server, a.accessToken, fmt.Printf("/applications/%s", appID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - if resp.StatusCode == http.StatusNotFound { - return app, fmt.Errorf("Application with id '%s' does not exist", appID) - } - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return app, err - } - - return app, nil + err = util.GET(a.server, a.accessToken, "/applications", &app) + return app, err } type createApplicationReq struct { Name string `json:"name"` AppID string `json:"id"` - EUIS []types.AppEUI `json:"euis"` + EUIs []types.AppEUI `json:"euis"` } // CreateApplication creates a new application on the account server @@ -62,17 +34,8 @@ func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppE EUIs: EUIs, } - resp, err := util.POST(a.server, a.accessToken, "/applications", body) - if resp.StatusCode != http.StatusCreated { - return app, fmt.Errorf("Could not create application: %s", resp.Status) - } - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return app, err - } - - return app, nil + err = util.POST(a.server, a.accessToken, "/applications", &body, &app) + return app, err } // DeleteApplication deletes an application @@ -81,22 +44,22 @@ func (a *Account) DeleteAppliction(appID string) error { } // Grant adds a collaborator to the application -func (a *Account) Grant(appID string, username string, rights []Right) error { +func (a *Account) Grant(appID string, username string, rights []types.Right) error { panic("Grant not implemented") } // Retract removes rights from a collaborator of the application -func (a *Account) Retract(appID string, username string, rights []Right) error { +func (a *Account) Retract(appID string, username string, rights []types.Right) error { panic("Retract not implemented") } // AddAccessKey -func (a *Account) AddAccessKey(appID string, key AccessKey) error { +func (a *Account) AddAccessKey(appID string, key types.AccessKey) error { panic("AddAccessKey not implemented") } // RemoveAccessKey -func (a *Account) RemoveAccessKey(appID string, key AccessKey) error { +func (a *Account) RemoveAccessKey(appID string, key types.AccessKey) error { panic("RemoveAccessKey not implemented") } From f1e51140f1d2fbefd84b12453416626c1fe69abd Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 12:16:53 +0200 Subject: [PATCH 1576/2266] add validate fields to the application types --- core/account/types.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/account/types.go b/core/account/types.go index 38750852f..1389454d3 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package application +package account import ( "time" @@ -11,16 +11,16 @@ import ( // Application represents an application on The Things Network type Application struct { - ID string `json:"id,required"` - Name string `json:"name"` + ID string `json:"id" validate:"nonzero"` + Name string `json:"name" validate:"nonzero"` EUIs []types.AppEUI `json:"euis,omitempty"` AccessKeys []types.AccessKey `json:"access_keys,omitempty"` - Created time.Date `json:"created,omitempty"` + Created time.Time `json:"created,omitempty"` Collaborators []Collaborator `json:"collaborators,omitempty"` } // Collaborator is a user that has rights to a certain application type Collaborator struct { - Username string `json:"username"` - Rights []types.Right `json:"rights"` + Username string `json:"username" validate:"nonzero"` + Rights []types.Right `json:"rights" validate:"nonzero"` } From 3107a333401584d7ed31d94daba075a2434663cf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 19 Jul 2016 11:44:25 +0100 Subject: [PATCH 1577/2266] Update discovery server - Remove "token" from announcement. We'll verify a JWT similar to what's done in the networkserver and handler. --- api/discovery/announcement.go | 5 - api/discovery/announcement_test.go | 2 - api/discovery/discovery.pb.go | 557 +++++++++++++------ api/discovery/discovery.proto | 34 +- api/discovery/validation.go | 15 + core/component.go | 6 +- core/discovery/announcement/store.go | 191 +++++++ core/discovery/announcement/store_test.go | 81 +++ core/discovery/broker_discovery.go | 2 +- core/discovery/broker_discovery_test.go | 8 +- core/discovery/discovery.go | 185 ++---- core/discovery/discovery_integration_test.go | 25 +- core/discovery/discovery_test.go | 134 +++-- core/discovery/handler_discovery.go | 2 +- core/discovery/handler_discovery_test.go | 6 +- core/discovery/server.go | 67 ++- core/discovery/server_test.go | 18 +- 17 files changed, 962 insertions(+), 376 deletions(-) create mode 100644 core/discovery/announcement/store.go create mode 100644 core/discovery/announcement/store_test.go diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go index f949be9e9..a217ba24e 100644 --- a/api/discovery/announcement.go +++ b/api/discovery/announcement.go @@ -12,7 +12,6 @@ import ( // be stored in Redis. var AnnouncementProperties = []string{ "id", - "token", "description", "service_name", "service_version", @@ -46,8 +45,6 @@ func (announcement *Announcement) formatProperty(property string) (formatted str switch property { case "id": formatted = announcement.Id - case "token": - formatted = announcement.Token case "description": formatted = announcement.Description case "service_name": @@ -75,8 +72,6 @@ func (announcement *Announcement) parseProperty(property string, value string) e switch property { case "id": announcement.Id = value - case "token": - announcement.Token = value case "description": announcement.Description = value case "service_name": diff --git a/api/discovery/announcement_test.go b/api/discovery/announcement_test.go index 3171a0888..076c79ba1 100644 --- a/api/discovery/announcement_test.go +++ b/api/discovery/announcement_test.go @@ -12,7 +12,6 @@ import ( func getTestAnnouncement() (announcement *Announcement, dmap map[string]string) { return &Announcement{ Id: "abcdef", - Token: "ghijkl", Description: "Test Description", ServiceName: "router", ServiceVersion: "1.0-preview build abcdef", @@ -29,7 +28,6 @@ func getTestAnnouncement() (announcement *Announcement, dmap map[string]string) }, }, map[string]string{ "id": "abcdef", - "token": "ghijkl", "description": "Test Description", "service_name": "router", "service_version": "1.0-preview build abcdef", diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 8ad9ef7cc..a6f0dafa6 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -11,9 +11,10 @@ It has these top-level messages: Metadata Announcement - DiscoverRequest + GetAllRequest GetRequest - DiscoverResponse + MetadataRequest + AnnouncementsResponse */ package discovery @@ -45,19 +46,25 @@ const ( // The value for PREFIX consists of 1 byte denoting the number of bits, // followed by the prefix and enough trailing bits to fill 4 octets Metadata_PREFIX Metadata_Key = 1 - // The value for APP_ID are the bytes of the AppID string + // APP_EUI is used for announcing join handlers. + // The value for APP_EUI is the byte slice of the AppEUI string + Metadata_APP_EUI Metadata_Key = 2 + // APP_ID is used for announcing regular handlers + // The value for APP_ID is the byte slice of the AppID string Metadata_APP_ID Metadata_Key = 3 ) var Metadata_Key_name = map[int32]string{ 0: "OTHER", 1: "PREFIX", + 2: "APP_EUI", 3: "APP_ID", } var Metadata_Key_value = map[string]int32{ - "OTHER": 0, - "PREFIX": 1, - "APP_ID": 3, + "OTHER": 0, + "PREFIX": 1, + "APP_EUI": 2, + "APP_ID": 3, } func (x Metadata_Key) String() string { @@ -77,10 +84,9 @@ func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, type Announcement struct { Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Token string `protobuf:"bytes,2,opt,name=token,proto3" json:"token,omitempty"` - Description string `protobuf:"bytes,3,opt,name=description,proto3" json:"description,omitempty"` - ServiceName string `protobuf:"bytes,4,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - ServiceVersion string `protobuf:"bytes,5,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + ServiceVersion string `protobuf:"bytes,3,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } @@ -97,18 +103,18 @@ func (m *Announcement) GetMetadata() []*Metadata { return nil } -type DiscoverRequest struct { +type GetAllRequest struct { ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` } -func (m *DiscoverRequest) Reset() { *m = DiscoverRequest{} } -func (m *DiscoverRequest) String() string { return proto.CompactTextString(m) } -func (*DiscoverRequest) ProtoMessage() {} -func (*DiscoverRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } +func (m *GetAllRequest) Reset() { *m = GetAllRequest{} } +func (m *GetAllRequest) String() string { return proto.CompactTextString(m) } +func (*GetAllRequest) ProtoMessage() {} +func (*GetAllRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } type GetRequest struct { - ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - Id []string `protobuf:"bytes,2,rep,name=id" json:"id,omitempty"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` } func (m *GetRequest) Reset() { *m = GetRequest{} } @@ -116,16 +122,34 @@ func (m *GetRequest) String() string { return proto.CompactTextString func (*GetRequest) ProtoMessage() {} func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{3} } -type DiscoverResponse struct { +type MetadataRequest struct { + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + Metadata *Metadata `protobuf:"bytes,11,opt,name=metadata" json:"metadata,omitempty"` +} + +func (m *MetadataRequest) Reset() { *m = MetadataRequest{} } +func (m *MetadataRequest) String() string { return proto.CompactTextString(m) } +func (*MetadataRequest) ProtoMessage() {} +func (*MetadataRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{4} } + +func (m *MetadataRequest) GetMetadata() *Metadata { + if m != nil { + return m.Metadata + } + return nil +} + +type AnnouncementsResponse struct { Services []*Announcement `protobuf:"bytes,1,rep,name=services" json:"services,omitempty"` } -func (m *DiscoverResponse) Reset() { *m = DiscoverResponse{} } -func (m *DiscoverResponse) String() string { return proto.CompactTextString(m) } -func (*DiscoverResponse) ProtoMessage() {} -func (*DiscoverResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{4} } +func (m *AnnouncementsResponse) Reset() { *m = AnnouncementsResponse{} } +func (m *AnnouncementsResponse) String() string { return proto.CompactTextString(m) } +func (*AnnouncementsResponse) ProtoMessage() {} +func (*AnnouncementsResponse) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{5} } -func (m *DiscoverResponse) GetServices() []*Announcement { +func (m *AnnouncementsResponse) GetServices() []*Announcement { if m != nil { return m.Services } @@ -135,9 +159,10 @@ func (m *DiscoverResponse) GetServices() []*Announcement { func init() { proto.RegisterType((*Metadata)(nil), "discovery.Metadata") proto.RegisterType((*Announcement)(nil), "discovery.Announcement") - proto.RegisterType((*DiscoverRequest)(nil), "discovery.DiscoverRequest") + proto.RegisterType((*GetAllRequest)(nil), "discovery.GetAllRequest") proto.RegisterType((*GetRequest)(nil), "discovery.GetRequest") - proto.RegisterType((*DiscoverResponse)(nil), "discovery.DiscoverResponse") + proto.RegisterType((*MetadataRequest)(nil), "discovery.MetadataRequest") + proto.RegisterType((*AnnouncementsResponse)(nil), "discovery.AnnouncementsResponse") proto.RegisterEnum("discovery.Metadata_Key", Metadata_Key_name, Metadata_Key_value) } @@ -153,8 +178,10 @@ const _ = grpc.SupportPackageIsVersion3 type DiscoveryClient interface { Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*api.Ack, error) - Discover(ctx context.Context, in *DiscoverRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) - Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) + GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) + Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) + AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) + DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) } type discoveryClient struct { @@ -174,17 +201,17 @@ func (c *discoveryClient) Announce(ctx context.Context, in *Announcement, opts . return out, nil } -func (c *discoveryClient) Discover(ctx context.Context, in *DiscoverRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) { - out := new(DiscoverResponse) - err := grpc.Invoke(ctx, "/discovery.Discovery/Discover", in, out, c.cc, opts...) +func (c *discoveryClient) GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) { + out := new(AnnouncementsResponse) + err := grpc.Invoke(ctx, "/discovery.Discovery/GetAll", in, out, c.cc, opts...) if err != nil { return nil, err } return out, nil } -func (c *discoveryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*DiscoverResponse, error) { - out := new(DiscoverResponse) +func (c *discoveryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) { + out := new(Announcement) err := grpc.Invoke(ctx, "/discovery.Discovery/Get", in, out, c.cc, opts...) if err != nil { return nil, err @@ -192,12 +219,32 @@ func (c *discoveryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc. return out, nil } +func (c *discoveryClient) AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/discovery.Discovery/AddMetadata", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/discovery.Discovery/DeleteMetadata", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for Discovery service type DiscoveryServer interface { Announce(context.Context, *Announcement) (*api.Ack, error) - Discover(context.Context, *DiscoverRequest) (*DiscoverResponse, error) - Get(context.Context, *GetRequest) (*DiscoverResponse, error) + GetAll(context.Context, *GetAllRequest) (*AnnouncementsResponse, error) + Get(context.Context, *GetRequest) (*Announcement, error) + AddMetadata(context.Context, *MetadataRequest) (*api.Ack, error) + DeleteMetadata(context.Context, *MetadataRequest) (*api.Ack, error) } func RegisterDiscoveryServer(s *grpc.Server, srv DiscoveryServer) { @@ -222,20 +269,20 @@ func _Discovery_Announce_Handler(srv interface{}, ctx context.Context, dec func( return interceptor(ctx, in, info, handler) } -func _Discovery_Discover_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(DiscoverRequest) +func _Discovery_GetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetAllRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(DiscoveryServer).Discover(ctx, in) + return srv.(DiscoveryServer).GetAll(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/discovery.Discovery/Discover", + FullMethod: "/discovery.Discovery/GetAll", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(DiscoveryServer).Discover(ctx, req.(*DiscoverRequest)) + return srv.(DiscoveryServer).GetAll(ctx, req.(*GetAllRequest)) } return interceptor(ctx, in, info, handler) } @@ -258,6 +305,42 @@ func _Discovery_Get_Handler(srv interface{}, ctx context.Context, dec func(inter return interceptor(ctx, in, info, handler) } +func _Discovery_AddMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).AddMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/AddMetadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).AddMetadata(ctx, req.(*MetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Discovery_DeleteMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MetadataRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DiscoveryServer).DeleteMetadata(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/discovery.Discovery/DeleteMetadata", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DiscoveryServer).DeleteMetadata(ctx, req.(*MetadataRequest)) + } + return interceptor(ctx, in, info, handler) +} + var _Discovery_serviceDesc = grpc.ServiceDesc{ ServiceName: "discovery.Discovery", HandlerType: (*DiscoveryServer)(nil), @@ -267,13 +350,21 @@ var _Discovery_serviceDesc = grpc.ServiceDesc{ Handler: _Discovery_Announce_Handler, }, { - MethodName: "Discover", - Handler: _Discovery_Discover_Handler, + MethodName: "GetAll", + Handler: _Discovery_GetAll_Handler, }, { MethodName: "Get", Handler: _Discovery_Get_Handler, }, + { + MethodName: "AddMetadata", + Handler: _Discovery_AddMetadata_Handler, + }, + { + MethodName: "DeleteMetadata", + Handler: _Discovery_DeleteMetadata_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: fileDescriptorDiscovery, @@ -359,30 +450,24 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) i += copy(data[i:], m.Id) } - if len(m.Token) > 0 { - data[i] = 0x12 - i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Token))) - i += copy(data[i:], m.Token) - } - if len(m.Description) > 0 { - data[i] = 0x1a - i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) - i += copy(data[i:], m.Description) - } if len(m.ServiceName) > 0 { - data[i] = 0x22 + data[i] = 0x12 i++ i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) i += copy(data[i:], m.ServiceName) } if len(m.ServiceVersion) > 0 { - data[i] = 0x2a + data[i] = 0x1a i++ i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceVersion))) i += copy(data[i:], m.ServiceVersion) } + if len(m.Description) > 0 { + data[i] = 0x22 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) + i += copy(data[i:], m.Description) + } if len(m.NetAddress) > 0 { data[i] = 0x5a i++ @@ -406,7 +491,7 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DiscoverRequest) Marshal() (data []byte, err error) { +func (m *GetAllRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -416,7 +501,7 @@ func (m *DiscoverRequest) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *DiscoverRequest) MarshalTo(data []byte) (int, error) { +func (m *GetAllRequest) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -445,31 +530,62 @@ func (m *GetRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if len(m.ServiceName) > 0 { + if len(m.Id) > 0 { data[i] = 0xa i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) + i += copy(data[i:], m.Id) + } + if len(m.ServiceName) > 0 { + data[i] = 0x12 + i++ i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) i += copy(data[i:], m.ServiceName) } + return i, nil +} + +func (m *MetadataRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *MetadataRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l if len(m.Id) > 0 { - for _, s := range m.Id { - data[i] = 0x12 - i++ - l = len(s) - for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ - } - data[i] = uint8(l) - i++ - i += copy(data[i:], s) + data[i] = 0xa + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) + i += copy(data[i:], m.Id) + } + if len(m.ServiceName) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) + i += copy(data[i:], m.ServiceName) + } + if m.Metadata != nil { + data[i] = 0x5a + i++ + i = encodeVarintDiscovery(data, i, uint64(m.Metadata.Size())) + n1, err := m.Metadata.MarshalTo(data[i:]) + if err != nil { + return 0, err } + i += n1 } return i, nil } -func (m *DiscoverResponse) Marshal() (data []byte, err error) { +func (m *AnnouncementsResponse) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -479,7 +595,7 @@ func (m *DiscoverResponse) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *DiscoverResponse) MarshalTo(data []byte) (int, error) { +func (m *AnnouncementsResponse) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -546,19 +662,15 @@ func (m *Announcement) Size() (n int) { if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.Token) - if l > 0 { - n += 1 + l + sovDiscovery(uint64(l)) - } - l = len(m.Description) + l = len(m.ServiceName) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.ServiceName) + l = len(m.ServiceVersion) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - l = len(m.ServiceVersion) + l = len(m.Description) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } @@ -575,7 +687,7 @@ func (m *Announcement) Size() (n int) { return n } -func (m *DiscoverRequest) Size() (n int) { +func (m *GetAllRequest) Size() (n int) { var l int _ = l l = len(m.ServiceName) @@ -588,20 +700,36 @@ func (m *DiscoverRequest) Size() (n int) { func (m *GetRequest) Size() (n int) { var l int _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } l = len(m.ServiceName) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } - if len(m.Id) > 0 { - for _, s := range m.Id { - l = len(s) - n += 1 + l + sovDiscovery(uint64(l)) - } + return n +} + +func (m *MetadataRequest) Size() (n int) { + var l int + _ = l + l = len(m.Id) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.ServiceName) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + if m.Metadata != nil { + l = m.Metadata.Size() + n += 1 + l + sovDiscovery(uint64(l)) } return n } -func (m *DiscoverResponse) Size() (n int) { +func (m *AnnouncementsResponse) Size() (n int) { var l int _ = l if len(m.Services) > 0 { @@ -786,7 +914,7 @@ func (m *Announcement) Unmarshal(data []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Token", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -811,11 +939,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Token = string(data[iNdEx:postIndex]) + m.ServiceName = string(data[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ServiceVersion", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -840,11 +968,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Description = string(data[iNdEx:postIndex]) + m.ServiceVersion = string(data[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Description", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -869,11 +997,11 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.Description = string(data[iNdEx:postIndex]) iNdEx = postIndex - case 5: + case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServiceVersion", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -898,13 +1026,13 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceVersion = string(data[iNdEx:postIndex]) + m.NetAddress = string(data[iNdEx:postIndex]) iNdEx = postIndex - case 11: + case 21: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowDiscovery @@ -914,26 +1042,78 @@ func (m *Announcement) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthDiscovery } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.NetAddress = string(data[iNdEx:postIndex]) + m.Metadata = append(m.Metadata, &Metadata{}) + if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 21: + default: + iNdEx = preIndex + skippy, err := skipDiscovery(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDiscovery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *GetAllRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: GetAllRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GetAllRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowDiscovery @@ -943,22 +1123,20 @@ func (m *Announcement) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthDiscovery } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } - m.Metadata = append(m.Metadata, &Metadata{}) - if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } + m.ServiceName = string(data[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -981,7 +1159,7 @@ func (m *Announcement) Unmarshal(data []byte) error { } return nil } -func (m *DiscoverRequest) Unmarshal(data []byte) error { +func (m *GetRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1004,13 +1182,42 @@ func (m *DiscoverRequest) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DiscoverRequest: wiretype end group for non-group") + return fmt.Errorf("proto: GetRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DiscoverRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: GetRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Id = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) } @@ -1060,7 +1267,7 @@ func (m *DiscoverRequest) Unmarshal(data []byte) error { } return nil } -func (m *GetRequest) Unmarshal(data []byte) error { +func (m *MetadataRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1083,15 +1290,15 @@ func (m *GetRequest) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: GetRequest: wiretype end group for non-group") + return fmt.Errorf("proto: MetadataRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: GetRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MetadataRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1116,11 +1323,11 @@ func (m *GetRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.Id = string(data[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Id", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ServiceName", wireType) } var stringLen uint64 for shift := uint(0); ; shift += 7 { @@ -1145,7 +1352,40 @@ func (m *GetRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = append(m.Id, string(data[iNdEx:postIndex])) + m.ServiceName = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Metadata == nil { + m.Metadata = &Metadata{} + } + if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -1168,7 +1408,7 @@ func (m *GetRequest) Unmarshal(data []byte) error { } return nil } -func (m *DiscoverResponse) Unmarshal(data []byte) error { +func (m *AnnouncementsResponse) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -1191,10 +1431,10 @@ func (m *DiscoverResponse) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: DiscoverResponse: wiretype end group for non-group") + return fmt.Errorf("proto: AnnouncementsResponse: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: DiscoverResponse: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: AnnouncementsResponse: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1355,35 +1595,38 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 479 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x93, 0x4f, 0x6e, 0xd3, 0x40, - 0x14, 0xc6, 0xeb, 0x98, 0x54, 0xf6, 0x4b, 0x94, 0x5a, 0x0f, 0x2a, 0xac, 0x20, 0x95, 0xe0, 0x0d, - 0x61, 0x51, 0x5b, 0x4a, 0xd9, 0xb0, 0x40, 0x28, 0xd0, 0x52, 0x2a, 0xd4, 0x12, 0x59, 0x11, 0x62, - 0x17, 0xb9, 0xf6, 0x53, 0x62, 0x85, 0x8c, 0x83, 0x67, 0x1c, 0xe4, 0x9b, 0x70, 0x16, 0x4e, 0xc0, - 0x92, 0x23, 0x20, 0xb8, 0x02, 0x07, 0x60, 0xfc, 0x2f, 0xb6, 0x68, 0x41, 0x59, 0x8c, 0x34, 0xf3, - 0xcd, 0xef, 0x7b, 0xef, 0xe5, 0x9b, 0x18, 0x9e, 0xcf, 0x43, 0xb1, 0x48, 0xae, 0x6d, 0x3f, 0x5a, - 0x39, 0xd3, 0x05, 0x4d, 0x17, 0x21, 0x9b, 0xf3, 0x2b, 0x12, 0x9f, 0xa3, 0x78, 0xe9, 0x08, 0xc1, - 0x1c, 0x6f, 0x1d, 0x3a, 0x41, 0xc8, 0xfd, 0x68, 0x43, 0x71, 0x5a, 0xef, 0xec, 0x75, 0x1c, 0x89, - 0x08, 0xf5, 0xad, 0xd0, 0x3f, 0xde, 0xa5, 0x92, 0x5c, 0x85, 0xd3, 0x4a, 0x40, 0xbb, 0x24, 0xe1, - 0x05, 0x9e, 0xf0, 0xf0, 0x09, 0xa8, 0x4b, 0x4a, 0x4d, 0x65, 0xa0, 0x0c, 0x7b, 0xa3, 0xfb, 0x76, - 0xdd, 0xa4, 0x22, 0xec, 0xb7, 0x94, 0xba, 0x19, 0x83, 0xf7, 0xa0, 0xbd, 0xf1, 0x3e, 0x26, 0x64, - 0xb6, 0x24, 0xdc, 0x75, 0x8b, 0x83, 0x35, 0x04, 0x55, 0x12, 0xa8, 0x43, 0xfb, 0xdd, 0xf4, 0xcd, - 0x99, 0x6b, 0xec, 0x21, 0xc0, 0xfe, 0xc4, 0x3d, 0x7b, 0x7d, 0xf1, 0xc1, 0x50, 0xb2, 0xfd, 0x78, - 0x32, 0x99, 0x5d, 0x9c, 0x1a, 0xaa, 0xf5, 0x5b, 0x81, 0xee, 0x98, 0xb1, 0x28, 0x61, 0x3e, 0xad, - 0x88, 0x09, 0xec, 0x41, 0x2b, 0x0c, 0xf2, 0xd6, 0xba, 0x2b, 0x77, 0x59, 0x03, 0x11, 0x2d, 0x89, - 0xe5, 0x0d, 0x74, 0xb7, 0x38, 0xe0, 0x00, 0x3a, 0x01, 0x71, 0x3f, 0x0e, 0xd7, 0x22, 0x8c, 0x98, - 0xa9, 0xe6, 0x77, 0x4d, 0x09, 0x1f, 0x41, 0x97, 0x53, 0xbc, 0x09, 0x7d, 0x9a, 0x31, 0x6f, 0x45, - 0xe6, 0x9d, 0x02, 0x29, 0xb5, 0x2b, 0x29, 0xe1, 0x63, 0x38, 0xa8, 0x10, 0xf9, 0xeb, 0x78, 0x56, - 0xa8, 0x9d, 0x53, 0xbd, 0x52, 0x7e, 0x5f, 0xa8, 0xf8, 0x10, 0x3a, 0x8c, 0xc4, 0xcc, 0x0b, 0x82, - 0x98, 0x38, 0x37, 0x3b, 0x39, 0x04, 0x52, 0x1a, 0x17, 0x0a, 0x3a, 0xa0, 0xad, 0xca, 0x68, 0xcc, - 0xc3, 0x81, 0x3a, 0xec, 0x8c, 0xee, 0xde, 0x92, 0x9a, 0xbb, 0x85, 0xac, 0xa7, 0x70, 0x70, 0x5a, - 0xde, 0xbb, 0xf4, 0x29, 0x21, 0x2e, 0x6e, 0x0c, 0xac, 0xdc, 0x18, 0xd8, 0x7a, 0x01, 0x70, 0x4e, - 0x62, 0x77, 0x43, 0x19, 0x66, 0x4b, 0x4e, 0x94, 0x87, 0x69, 0x9d, 0x83, 0x51, 0xb7, 0xe5, 0xeb, - 0x88, 0x71, 0xc2, 0x13, 0xd0, 0x4a, 0x0b, 0x97, 0x25, 0xb2, 0xd9, 0x9b, 0x2f, 0xde, 0x7c, 0x1b, - 0x77, 0x0b, 0x8e, 0xbe, 0x2a, 0xa0, 0x57, 0x95, 0x52, 0x3c, 0x06, 0xad, 0xe2, 0xf0, 0x5f, 0xe6, - 0xbe, 0x66, 0x67, 0x7f, 0xb6, 0xb1, 0xbf, 0xc4, 0x57, 0xa0, 0x55, 0x5e, 0xec, 0x37, 0xf0, 0xbf, - 0x12, 0xe9, 0x3f, 0xb8, 0xf5, 0xae, 0x1c, 0xfb, 0x19, 0xa8, 0x32, 0x0b, 0x3c, 0x6c, 0x30, 0x75, - 0x36, 0xff, 0xb5, 0x8e, 0xb0, 0x4e, 0x21, 0xbd, 0xf4, 0x98, 0x37, 0xa7, 0xf8, 0xa5, 0xf1, 0xed, - 0xe7, 0x91, 0xf2, 0x5d, 0xae, 0x1f, 0x72, 0x7d, 0xf9, 0x75, 0xb4, 0x77, 0xbd, 0x9f, 0x7f, 0x17, - 0x27, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0x71, 0x0b, 0x82, 0xa6, 0x92, 0x03, 0x00, 0x00, + // 518 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0xad, 0x93, 0xaf, 0xf9, 0xe2, 0xeb, 0x90, 0x5a, 0x03, 0x11, 0x56, 0x16, 0x25, 0x78, 0x43, + 0x59, 0xd4, 0x96, 0x5c, 0xc1, 0x0e, 0xa1, 0xa0, 0x04, 0x88, 0xa0, 0x25, 0x1a, 0x05, 0xc4, 0x2e, + 0x72, 0xed, 0xab, 0xc4, 0x4a, 0x32, 0x0e, 0x9e, 0x71, 0x50, 0xb6, 0x3c, 0x05, 0x0f, 0xc0, 0xc3, + 0xb0, 0xe4, 0x09, 0x10, 0x82, 0x17, 0x61, 0x1c, 0xff, 0xc4, 0x51, 0x1b, 0x41, 0x59, 0x8c, 0xe4, + 0x39, 0x3e, 0xe7, 0xce, 0xb9, 0x67, 0xee, 0xc0, 0x93, 0x49, 0x20, 0xa6, 0xf1, 0xa5, 0xe5, 0x85, + 0x0b, 0x7b, 0x34, 0xc5, 0xd1, 0x34, 0x60, 0x13, 0x7e, 0x81, 0xe2, 0x63, 0x18, 0xcd, 0x6c, 0x21, + 0x98, 0xed, 0x2e, 0x03, 0xdb, 0x0f, 0xb8, 0x17, 0xae, 0x30, 0x5a, 0x6f, 0xbf, 0xac, 0x65, 0x14, + 0x8a, 0x90, 0xa8, 0x05, 0xd0, 0x3e, 0xfd, 0x9b, 0x4a, 0x72, 0xa5, 0x4a, 0xf3, 0x93, 0x02, 0xf5, + 0x73, 0x14, 0xae, 0xef, 0x0a, 0x97, 0x3c, 0x84, 0xea, 0x0c, 0xd7, 0x86, 0xd2, 0x51, 0x4e, 0x9a, + 0xce, 0x5d, 0x6b, 0x7b, 0x4a, 0xce, 0xb0, 0x5e, 0xe1, 0x9a, 0x26, 0x1c, 0x72, 0x07, 0x0e, 0x57, + 0xee, 0x3c, 0x46, 0xa3, 0x22, 0xc9, 0x0d, 0x9a, 0x6e, 0xcc, 0x47, 0x50, 0x95, 0x0c, 0xa2, 0xc2, + 0xe1, 0x9b, 0xd1, 0xcb, 0x3e, 0xd5, 0x0f, 0x08, 0x40, 0x6d, 0x48, 0xfb, 0xcf, 0x07, 0xef, 0x75, + 0x85, 0x68, 0xf0, 0x7f, 0x77, 0x38, 0x1c, 0xf7, 0xdf, 0x0e, 0xf4, 0x4a, 0xf2, 0x23, 0xd9, 0x0c, + 0x7a, 0x7a, 0xd5, 0xfc, 0xae, 0x40, 0xa3, 0xcb, 0x58, 0x18, 0x33, 0x0f, 0x17, 0xc8, 0x04, 0x69, + 0x42, 0x25, 0xf0, 0x37, 0x3e, 0x54, 0x2a, 0xbf, 0xc8, 0x7d, 0x68, 0x70, 0x8c, 0x56, 0x81, 0x87, + 0x63, 0xe6, 0x2e, 0xd2, 0x43, 0x55, 0xaa, 0x65, 0xd8, 0x85, 0x84, 0xc8, 0x03, 0x38, 0xca, 0x29, + 0xd2, 0x32, 0x0f, 0x42, 0x66, 0x54, 0x37, 0xac, 0x66, 0x06, 0xbf, 0x4b, 0x51, 0xd2, 0x01, 0xcd, + 0x47, 0xee, 0x45, 0xc1, 0x52, 0x24, 0xa4, 0xff, 0xd2, 0x52, 0x25, 0x88, 0xdc, 0x03, 0x8d, 0xa1, + 0x18, 0xbb, 0xbe, 0x1f, 0x21, 0xe7, 0x86, 0xb6, 0x61, 0x80, 0x84, 0xba, 0x29, 0x42, 0x6c, 0xa8, + 0x2f, 0xb2, 0x44, 0x8c, 0x56, 0xa7, 0x7a, 0xa2, 0x39, 0xb7, 0xaf, 0x09, 0x8b, 0x16, 0x24, 0xd3, + 0x81, 0x5b, 0x2f, 0xa4, 0x7c, 0x3e, 0xa7, 0xf8, 0x21, 0x46, 0x2e, 0xae, 0x34, 0xa4, 0x5c, 0x69, + 0xc8, 0x7c, 0x0a, 0x20, 0x35, 0xb9, 0xe0, 0xe6, 0x89, 0x98, 0x31, 0x1c, 0x15, 0x56, 0xfe, 0xb9, + 0xca, 0x4e, 0xaf, 0x49, 0x12, 0x7f, 0xec, 0xf5, 0x35, 0xb4, 0xca, 0x77, 0xc9, 0x29, 0xf2, 0x65, + 0xc8, 0x38, 0x92, 0x33, 0xa8, 0x67, 0x85, 0xb9, 0xb4, 0x90, 0xa4, 0x56, 0x1e, 0xb1, 0xb2, 0x86, + 0x16, 0x44, 0xe7, 0x4b, 0x05, 0xd4, 0x5e, 0x4e, 0x22, 0xa7, 0x50, 0xcf, 0x79, 0x64, 0x9f, 0xb8, + 0x5d, 0xb7, 0x92, 0xf1, 0xee, 0x7a, 0x33, 0xd2, 0x83, 0x5a, 0x1a, 0x3b, 0x31, 0x4a, 0xe4, 0x9d, + 0x9b, 0x68, 0x77, 0xf6, 0x94, 0xd9, 0xfa, 0x96, 0x43, 0x2d, 0x25, 0xa4, 0xb5, 0x5b, 0x22, 0xd7, + 0xef, 0xb3, 0x21, 0xdb, 0xd5, 0xe4, 0xbc, 0x14, 0x6f, 0xab, 0x7d, 0x5d, 0x6a, 0x59, 0x8d, 0xad, + 0xe3, 0xc7, 0xd0, 0xec, 0xe1, 0x1c, 0x05, 0xde, 0x4c, 0xe7, 0x10, 0xd0, 0x8b, 0x94, 0xce, 0x5d, + 0xe6, 0x4e, 0x30, 0x7a, 0xa6, 0x7f, 0xfd, 0x79, 0xac, 0x7c, 0x93, 0xeb, 0x87, 0x5c, 0x9f, 0x7f, + 0x1d, 0x1f, 0x5c, 0xd6, 0x36, 0x6f, 0xfe, 0xec, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xeb, + 0xd6, 0x1d, 0x6e, 0x04, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 987663355..bb0137e8d 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -15,7 +15,12 @@ message Metadata { // followed by the prefix and enough trailing bits to fill 4 octets PREFIX = 1; - // The value for APP_ID are the bytes of the AppID string + // APP_EUI is used for announcing join handlers. + // The value for APP_EUI is the byte slice of the AppEUI string + APP_EUI = 2; + + // APP_ID is used for announcing regular handlers + // The value for APP_ID is the byte slice of the AppID string APP_ID = 3; } Key key = 1; @@ -24,31 +29,38 @@ message Metadata { message Announcement { string id = 1; - string token = 2; - string description = 3; - string service_name = 4; - string service_version = 5; + string service_name = 2; + string service_version = 3; + string description = 4; string net_address = 11; repeated Metadata metadata = 21; } -message DiscoverRequest { +message GetAllRequest { string service_name = 1; } message GetRequest { - string service_name = 1; - repeated string id = 2; + string id = 1; + string service_name = 2; +} + +message MetadataRequest { + string id = 1; + string service_name = 2; + Metadata metadata = 11; } -message DiscoverResponse { +message AnnouncementsResponse { repeated Announcement services = 1; } service Discovery { rpc Announce(Announcement) returns (api.Ack); - rpc Discover(DiscoverRequest) returns (DiscoverResponse); - rpc Get(GetRequest) returns (DiscoverResponse); + rpc GetAll(GetAllRequest) returns (AnnouncementsResponse); + rpc Get(GetRequest) returns (Announcement); + rpc AddMetadata(MetadataRequest) returns (api.Ack); + rpc DeleteMetadata(MetadataRequest) returns (api.Ack); } // The DiscoveryManager service provides configuration and monitoring functionality diff --git a/api/discovery/validation.go b/api/discovery/validation.go index 5844159ae..43d096f64 100644 --- a/api/discovery/validation.go +++ b/api/discovery/validation.go @@ -1 +1,16 @@ package discovery + +import "github.com/TheThingsNetwork/ttn/api" + +// Validate implements the api.Validator interface +func (m *Announcement) Validate() bool { + if m.Id == "" || !api.ValidID(m.Id) { + return false + } + switch m.ServiceName { + case "router", "broker", "handler": + default: + return false + } + return true +} diff --git a/core/component.go b/core/component.go index 653e53945..cdae15c46 100644 --- a/core/component.go +++ b/core/component.go @@ -26,6 +26,7 @@ import ( type ComponentInterface interface { RegisterRPC(s *grpc.Server) Init(c *Component) error + ValidateContext(ctx context.Context) (*TTNClaims, error) } type ManagementInterface interface { @@ -49,11 +50,11 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string Ctx: ctx, Identity: &pb_discovery.Announcement{ Id: viper.GetString("id"), - Token: viper.GetString("token"), Description: viper.GetString("description"), ServiceName: serviceName, NetAddress: announcedAddress, }, + AccessToken: viper.GetString("token"), DiscoveryServer: viper.GetString("discovery-server"), TokenKeyProvider: tokenkey.NewHTTPProvider( fmt.Sprintf("%s/key", viper.GetString("auth-server")), @@ -67,6 +68,7 @@ type Component struct { Identity *pb_discovery.Announcement DiscoveryServer string Ctx log.Interface + AccessToken string TokenKeyProvider tokenkey.Provider } @@ -233,7 +235,7 @@ func (c *Component) GetContext() context.Context { var id, token, netAddress string if c.Identity != nil { id = c.Identity.Id - token = c.Identity.Token + token = c.AccessToken netAddress = c.Identity.NetAddress } md := metadata.Pairs( diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go new file mode 100644 index 000000000..b31ba3377 --- /dev/null +++ b/core/discovery/announcement/store.go @@ -0,0 +1,191 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "errors" + "fmt" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "gopkg.in/redis.v3" +) + +var ( + // ErrNotFound is returned when a announcement was not found + ErrNotFound = errors.New("ttn/discovery: Announcement not found") +) + +// Store is used to store announcement configurations +type Store interface { + // List all announcements + List() ([]*pb.Announcement, error) + // List all announcements for a service + ListService(serviceName string) ([]*pb.Announcement, error) + // Get the full announcement + Get(serviceName, serviceID string) (*pb.Announcement, error) + // Set the announcement. + Set(announcement *pb.Announcement) error + // Delete a announcement + Delete(serviceName, serviceID string) error +} + +// NewAnnouncementStore creates a new in-memory Announcement store +func NewAnnouncementStore() Store { + return &announcementStore{ + announcements: make(map[string]map[string]*pb.Announcement), + } +} + +// announcementStore is an in-memory Announcement store. It should only be used for testing +// purposes. Use the redisAnnouncementStore for actual deployments. +type announcementStore struct { + announcements map[string]map[string]*pb.Announcement +} + +func (s *announcementStore) List() ([]*pb.Announcement, error) { + announcements := make([]*pb.Announcement, 0, len(s.announcements)) + for _, service := range s.announcements { + for _, announcement := range service { + announcements = append(announcements, announcement) + } + } + return announcements, nil +} + +func (s *announcementStore) ListService(serviceName string) ([]*pb.Announcement, error) { + if service, ok := s.announcements[serviceName]; ok { + announcements := make([]*pb.Announcement, 0, len(s.announcements)) + for _, announcement := range service { + announcements = append(announcements, announcement) + } + return announcements, nil + } + return []*pb.Announcement{}, nil +} + +func (s *announcementStore) Get(serviceName, serviceID string) (*pb.Announcement, error) { + if service, ok := s.announcements[serviceName]; ok { + if announcement, ok := service[serviceID]; ok { + return announcement, nil + } + } + return nil, ErrNotFound +} + +func (s *announcementStore) Set(new *pb.Announcement) error { + if _, ok := s.announcements[new.ServiceName]; !ok { + s.announcements[new.ServiceName] = map[string]*pb.Announcement{} + } + s.announcements[new.ServiceName][new.Id] = new + return nil +} + +func (s *announcementStore) Delete(serviceName, serviceID string) error { + if service, ok := s.announcements[serviceName]; ok { + delete(service, serviceID) + } + return nil +} + +// NewRedisAnnouncementStore creates a new Redis-based status store +func NewRedisAnnouncementStore(client *redis.Client) Store { + return &redisAnnouncementStore{ + client: client, + } +} + +const redisAnnouncementPrefix = "discovery:announcement" + +type redisAnnouncementStore struct { + client *redis.Client +} + +func (s *redisAnnouncementStore) getForKeys(keys []string) ([]*pb.Announcement, error) { + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) + for _, key := range keys { + cmds[key] = s.client.HGetAllMap(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + announcements := make([]*pb.Announcement, 0, len(keys)) + for _, cmd := range cmds { + dmap, err := cmd.Result() + if err == nil { + announcement := &pb.Announcement{} + err := announcement.FromStringStringMap(dmap) + if err == nil { + announcements = append(announcements, announcement) + } + } + } + + return announcements, nil +} + +func (s *redisAnnouncementStore) List() ([]*pb.Announcement, error) { + keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisAnnouncementPrefix)).Result() + if err != nil { + return nil, err + } + return s.getForKeys(keys) +} + +func (s *redisAnnouncementStore) ListService(serviceName string) ([]*pb.Announcement, error) { + keys, err := s.client.Keys(fmt.Sprintf("%s:%s:*", redisAnnouncementPrefix, serviceName)).Result() + if err != nil { + return nil, err + } + return s.getForKeys(keys) +} + +func (s *redisAnnouncementStore) Get(serviceName, serviceID string) (*pb.Announcement, error) { + res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, serviceID)).Result() + if err != nil { + if err == redis.Nil { + return nil, ErrNotFound + } + return nil, err + } else if len(res) == 0 { + return nil, ErrNotFound + } + announcement := &pb.Announcement{} + err = announcement.FromStringStringMap(res) + if err != nil { + return nil, err + } + return announcement, nil +} + +func (s *redisAnnouncementStore) Set(new *pb.Announcement) error { + key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, new.ServiceName, new.Id) + dmap, err := new.ToStringStringMap(pb.AnnouncementProperties...) + if err != nil { + return err + } + err = s.client.HMSetMap(key, dmap).Err() + if err != nil { + return err + } + + return nil +} + +func (s *redisAnnouncementStore) Delete(serviceName, serviceID string) error { + key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, serviceID) + err := s.client.Del(key).Err() + if err != nil { + return err + } + return nil +} diff --git a/core/discovery/announcement/store_test.go b/core/discovery/announcement/store_test.go new file mode 100644 index 000000000..c46abce8b --- /dev/null +++ b/core/discovery/announcement/store_test.go @@ -0,0 +1,81 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + . "github.com/smartystreets/assertions" + "gopkg.in/redis.v3" +) + +func getRedisClient() *redis.Client { + return redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + Password: "", // no password set + DB: 1, // use default DB + }) +} + +func TestAnnouncementStore(t *testing.T) { + a := New(t) + + stores := map[string]Store{ + "local": NewAnnouncementStore(), + "redis": NewRedisAnnouncementStore(getRedisClient()), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Get non-existing + dev, err := s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&pb.Announcement{ + ServiceName: "router", + Id: "router1", + }) + a.So(err, ShouldBeNil) + + // Get existing + dev, err = s.Get("router", "router1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + // Create extra + err = s.Set(&pb.Announcement{ + ServiceName: "broker", + Id: "broker1", + }) + a.So(err, ShouldBeNil) + + // List + announcements, err := s.List() + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 2) + + // List + announcements, err = s.ListService("router") + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 1) + + // Delete + err = s.Delete("router", "router1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Cleanup + s.Delete("broker", "broker1") + } + +} diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 27c7800a5..4bb405ade 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -44,7 +44,7 @@ func (d *brokerDiscovery) refreshCache() error { } defer conn.Close() client := pb.NewDiscoveryClient(conn) - res, err := client.Discover(d.component.GetContext(), &pb.DiscoverRequest{ServiceName: "broker"}) + res, err := client.GetAll(d.component.GetContext(), &pb.GetAllRequest{ServiceName: "broker"}) if err != nil { return err } diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index 39af8c558..268fe660f 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -24,21 +24,21 @@ func TestBrokerDiscovery(t *testing.T) { a := New(t) // Broker1 has a prefix with all DevAddrs - broker1 := &pb.Announcement{ServiceName: "broker", Token: "broker1", NetAddress: "localhost1:1881", + broker1 := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0, 0x00, 0x00, 0x00, 0x00}}, }, } // Broker2 has one DevAddr prefix - broker2 := &pb.Announcement{ServiceName: "broker", Token: "broker2", NetAddress: "localhost2:1881", + broker2 := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost2:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, }, } // Broker3 has multiple DevAddr prefixes - broker3 := &pb.Announcement{ServiceName: "broker", Token: "broker3", NetAddress: "localhost3:1881", + broker3 := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost3:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{16, 0x02, 0x03, 0x00, 0x00}}, }, @@ -85,7 +85,7 @@ func TestBrokerDiscoveryCache(t *testing.T) { discoveryServer, _ := buildMockDiscoveryServer(port) - broker := &pb.Announcement{ServiceName: "broker", Token: "broker", NetAddress: "localhost1:1881", + broker := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00}}}, } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 9d1a80e60..575cf915d 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -5,29 +5,29 @@ package discovery import ( - "errors" - "fmt" - "strings" - "sync" + "bytes" "gopkg.in/redis.v3" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/discovery/announcement" ) // Discovery specifies the interface for the TTN Service Discovery component type Discovery interface { core.ComponentInterface Announce(announcement *pb.Announcement) error - Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) + GetAll(serviceName string) ([]*pb.Announcement, error) + Get(serviceName string, id string) (*pb.Announcement, error) + AddMetadata(serviceName string, id string, metadata *pb.Metadata) error + DeleteMetadata(serviceName string, id string, metadata *pb.Metadata) error } // discovery is a reference implementation for a TTN Service Discovery component. type discovery struct { *core.Component - services map[string]map[string]*pb.Announcement - sync.RWMutex + services announcement.Store } func (d *discovery) Init(c *core.Component) error { @@ -39,150 +39,81 @@ func (d *discovery) Init(c *core.Component) error { return nil } -func (d *discovery) Announce(announcement *pb.Announcement) error { - d.Lock() - defer d.Unlock() - - // Get the list - services, ok := d.services[announcement.ServiceName] - if !ok { - services = map[string]*pb.Announcement{} - d.services[announcement.ServiceName] = services +func (d *discovery) Announce(in *pb.Announcement) error { + existing, err := d.services.Get(in.ServiceName, in.Id) + if err == announcement.ErrNotFound { + // Not found; create new + existing = &pb.Announcement{} + } else if err != nil { + return err } + in.Metadata = existing.Metadata + return d.services.Set(in) +} - // Find an existing announcement - service, ok := services[announcement.Id] - if ok { - if announcement.Token == service.Token { - *service = *announcement - } else { - return errors.New("ttn/core: Invalid token") - } - } else { - services[announcement.Id] = announcement +func (d *discovery) Get(serviceName string, id string) (*pb.Announcement, error) { + service, err := d.services.Get(serviceName, id) + if err != nil { + return nil, err } - - return nil + serviceCopy := *service + return &serviceCopy, nil } -func (d *discovery) Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) { - d.RLock() - defer d.RUnlock() - - // Get the list - services, ok := d.services[serviceName] - if !ok { - return []*pb.Announcement{}, nil +func (d *discovery) GetAll(serviceName string) ([]*pb.Announcement, error) { + services, err := d.services.ListService(serviceName) + if err != nil { + return nil, err } - - // Traverse the list - announcements := make([]*pb.Announcement, 0, len(services)) + serviceCopies := make([]*pb.Announcement, 0, len(services)) for _, service := range services { serviceCopy := *service - serviceCopy.Token = "" - if len(ids) == 0 { - announcements = append(announcements, &serviceCopy) - } else { - for _, id := range ids { - if service.Id == id { - announcements = append(announcements, &serviceCopy) - break - } - } - } - } - return announcements, nil -} - -// NewRedisDiscovery creates a new Redis-based discovery service -func NewRedisDiscovery(client *redis.Client) Discovery { - return &redisDiscovery{ - client: client, + serviceCopies = append(serviceCopies, &serviceCopy) } + return serviceCopies, nil } -const redisAnnouncementPrefix = "service" - -type redisDiscovery struct { - *core.Component - client *redis.Client -} - -func (d *redisDiscovery) Init(c *core.Component) error { - d.Component = c - err := d.Component.UpdateTokenKey() +func (d *discovery) AddMetadata(serviceName string, id string, in *pb.Metadata) error { + existing, err := d.services.Get(serviceName, id) if err != nil { return err } - return nil -} - -func (d *redisDiscovery) Announce(announcement *pb.Announcement) error { - key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, announcement.ServiceName, announcement.Id) - - if token, err := d.client.HGet(key, "token").Result(); err == nil && token != announcement.Token { - return errors.New("ttn/core: Invalid token") + // Skip if already existing + for _, md := range existing.Metadata { + if md.Key == in.Key && bytes.Equal(md.Value, in.Value) { + return nil + } } + existing.Metadata = append(existing.Metadata, in) + return d.services.Set(existing) +} - dmap, err := announcement.ToStringStringMap(pb.AnnouncementProperties...) +func (d *discovery) DeleteMetadata(serviceName string, id string, in *pb.Metadata) error { + existing, err := d.services.Get(serviceName, id) if err != nil { return err } - - return d.client.HMSetMap(key, dmap).Err() -} - -func (d *redisDiscovery) Discover(serviceName string, ids ...string) ([]*pb.Announcement, error) { - announcements := []*pb.Announcement{} - if len(ids) == 0 { - keys, err := d.client.Keys(fmt.Sprintf("%s:%s:*", redisAnnouncementPrefix, serviceName)).Result() - if err != nil { - return nil, err - } - for _, key := range keys { - if parts := strings.Split(key, ":"); len(parts) == 3 { - ids = append(ids, parts[2]) - } + newMeta := make([]*pb.Metadata, 0, len(existing.Metadata)) + for _, md := range existing.Metadata { + if md.Key == in.Key && bytes.Equal(md.Value, in.Value) { + continue } + newMeta = append(newMeta, md) } + existing.Metadata = newMeta + return d.services.Set(existing) +} - var wg sync.WaitGroup - wg.Add(len(ids)) - results := make(chan *pb.Announcement) - go func() { - wg.Wait() - close(results) - }() - for _, id := range ids { - go func(id string) { - res, err := d.Get(serviceName, id) - if err == nil && res != nil { - results <- res - } - wg.Done() - }(id) - } - - for res := range results { - announcements = append(announcements, res) +// NewDiscovery creates a new memory-based discovery service +func NewDiscovery(client *redis.Client) Discovery { + return &discovery{ + services: announcement.NewAnnouncementStore(), } - - return announcements, nil } -func (d *redisDiscovery) Get(serviceName string, id string) (*pb.Announcement, error) { - key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, id) - res, err := d.client.HGetAllMap(key).Result() - if err != nil { - return nil, err - } else if len(res) == 0 { - return nil, redis.Nil // This might be a bug in redis package - } - announcement := &pb.Announcement{} - err = announcement.FromStringStringMap(res) - if err != nil { - return nil, err +// NewRedisDiscovery creates a new Redis-based discovery service +func NewRedisDiscovery(client *redis.Client) Discovery { + return &discovery{ + services: announcement.NewRedisAnnouncementStore(client), } - announcement.Token = "" - return announcement, nil } diff --git a/core/discovery/discovery_integration_test.go b/core/discovery/discovery_integration_test.go index 522324eea..c6835e823 100644 --- a/core/discovery/discovery_integration_test.go +++ b/core/discovery/discovery_integration_test.go @@ -7,6 +7,7 @@ import ( "testing" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) @@ -19,19 +20,21 @@ func TestIntegrationBrokerDiscovery(t *testing.T) { discoveryServer, s := buildTestDiscoveryServer(port) defer s.Stop() - discoveryServer.services = map[string]map[string]*pb.Announcement{ - "broker": map[string]*pb.Announcement{ - "broker1": &pb.Announcement{Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, - }}, - "broker2": &pb.Announcement{Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x02, 0x00, 0x00, 0x00}}, - }}, + discoveryServer.services = announcement.NewAnnouncementStore() + discoveryServer.services.Set(&pb.Announcement{ + Id: "broker1", + ServiceName: "broker", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, }, - "other": map[string]*pb.Announcement{ - "other": &pb.Announcement{}, + }) + discoveryServer.services.Set(&pb.Announcement{ + Id: "broker2", + ServiceName: "broker", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x02, 0x00, 0x00, 0x00}}, }, - } + }) discoveryClient := buildTestBrokerDiscoveryClient(port) diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 878094fa6..4d40db43b 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -9,6 +9,7 @@ import ( "gopkg.in/redis.v3" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/discovery/announcement" . "github.com/smartystreets/assertions" ) @@ -24,14 +25,14 @@ func TestDiscoveryAnnounce(t *testing.T) { a := New(t) localDiscovery := &discovery{ - services: map[string]map[string]*pb.Announcement{}, + services: announcement.NewAnnouncementStore(), } client := getRedisClient(1) redisDiscovery := NewRedisDiscovery(client) defer func() { - client.Del("service:broker:broker1.1") - client.Del("service:broker:broker1.2") + client.Del("discovery:announcement:broker:broker1.1") + client.Del("discovery:announcement:broker:broker1.2") }() discoveries := map[string]Discovery{ @@ -40,9 +41,8 @@ func TestDiscoveryAnnounce(t *testing.T) { } for name, d := range discoveries { - broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", Token: "abcd", NetAddress: "current address"} - broker1aNoToken := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "attacker address"} - broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", Token: "abcd", NetAddress: "updated address"} + broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "current address"} + broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "updated address"} broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker1.2", NetAddress: "other address"} t.Logf("Testing %s\n", name) @@ -50,10 +50,7 @@ func TestDiscoveryAnnounce(t *testing.T) { err := d.Announce(broker1a) a.So(err, ShouldBeNil) - err = d.Announce(broker1aNoToken) - a.So(err, ShouldNotBeNil) - - services, err := d.Discover("broker") + services, err := d.GetAll("broker") a.So(err, ShouldBeNil) a.So(services, ShouldHaveLength, 1) a.So(services[0].NetAddress, ShouldEqual, "current address") @@ -61,7 +58,7 @@ func TestDiscoveryAnnounce(t *testing.T) { err = d.Announce(broker1b) a.So(err, ShouldBeNil) - services, err = d.Discover("broker") + services, err = d.GetAll("broker") a.So(err, ShouldBeNil) a.So(services, ShouldHaveLength, 1) a.So(services[0].NetAddress, ShouldEqual, "updated address") @@ -69,7 +66,7 @@ func TestDiscoveryAnnounce(t *testing.T) { err = d.Announce(broker2) a.So(err, ShouldBeNil) - services, err = d.Discover("broker") + services, err = d.GetAll("broker") a.So(err, ShouldBeNil) a.So(services, ShouldHaveLength, 2) } @@ -79,28 +76,21 @@ func TestDiscoveryAnnounce(t *testing.T) { func TestDiscoveryDiscover(t *testing.T) { a := New(t) - router := &pb.Announcement{ServiceName: "router", Id: "router2.0", Token: "abcd"} + router := &pb.Announcement{ServiceName: "router", Id: "router2.0"} broker1 := &pb.Announcement{ServiceName: "broker", Id: "broker2.1"} broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker2.2"} - localDiscovery := &discovery{ - services: map[string]map[string]*pb.Announcement{ - "router": map[string]*pb.Announcement{ - "router": router, - }, - "broker": map[string]*pb.Announcement{ - "broker1": broker1, - "broker2": broker2, - }, - }, - } + localDiscovery := &discovery{services: announcement.NewAnnouncementStore()} + localDiscovery.services.Set(router) + localDiscovery.services.Set(broker1) + localDiscovery.services.Set(broker2) client := getRedisClient(2) redisDiscovery := NewRedisDiscovery(client) defer func() { - client.Del("service:router:router2.0") - client.Del("service:broker:broker2.1") - client.Del("service:broker:broker2.2") + client.Del("discovery:announcement:router:router2.0") + client.Del("discovery:announcement:broker:broker2.1") + client.Del("discovery:announcement:broker:broker2.2") }() // This depends on the previous test to pass @@ -116,19 +106,101 @@ func TestDiscoveryDiscover(t *testing.T) { for name, d := range discoveries { t.Logf("Testing %s\n", name) - services, err := d.Discover("random") + services, err := d.GetAll("random") a.So(err, ShouldBeNil) a.So(services, ShouldBeEmpty) - services, err = d.Discover("router") + services, err = d.GetAll("router") a.So(err, ShouldBeNil) a.So(services, ShouldHaveLength, 1) a.So(services[0].Id, ShouldEqual, router.Id) - a.So(services[0].Token, ShouldBeEmpty) - services, err = d.Discover("broker") + services, err = d.GetAll("broker") a.So(err, ShouldBeNil) a.So(services, ShouldHaveLength, 2) } } + +func TestDiscoveryMetadata(t *testing.T) { + a := New(t) + + localDiscovery := &discovery{ + services: announcement.NewAnnouncementStore(), + } + + client := getRedisClient(1) + redisDiscovery := NewRedisDiscovery(client) + defer func() { + client.Del("discovery:announcement:broker:broker3") + }() + + discoveries := map[string]Discovery{ + "local": localDiscovery, + "redis": redisDiscovery, + } + + for name, d := range discoveries { + broker := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-1"), + }}} + + t.Logf("Testing %s\n", name) + + // Announce should not change metadata + err := d.Announce(broker) + a.So(err, ShouldBeNil) + service, err := d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + + // AddMetadata should add one + err = d.AddMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // AddMetadata again should not add one + err = d.AddMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // DeleteMetadata for non-existing should not delete one + err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-3"), + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // Announce should not change metadata + err = d.Announce(broker) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // DeleteMetadata should delete one + err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + + } + +} diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go index 45251f61c..49b4053db 100644 --- a/core/discovery/handler_discovery.go +++ b/core/discovery/handler_discovery.go @@ -48,7 +48,7 @@ func (d *handlerDiscovery) refreshCache() error { } defer conn.Close() client := pb.NewDiscoveryClient(conn) - res, err := client.Discover(d.component.GetContext(), &pb.DiscoverRequest{ServiceName: "handler"}) + res, err := client.GetAll(d.component.GetContext(), &pb.GetAllRequest{ServiceName: "handler"}) if err != nil { return err } diff --git a/core/discovery/handler_discovery_test.go b/core/discovery/handler_discovery_test.go index 62ac2d005..1f570c8ab 100644 --- a/core/discovery/handler_discovery_test.go +++ b/core/discovery/handler_discovery_test.go @@ -23,14 +23,14 @@ func TestHandlerDiscovery(t *testing.T) { a := New(t) // Handler1 owns one AppEUI - handler1 := &pb.Announcement{ServiceName: "handler", Id: "handler1", Token: "handler1", NetAddress: "localhost1:1881", + handler1 := &pb.Announcement{ServiceName: "handler", Id: "handler1", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-2")}, }, } // Handler2 has two AppEUIs - handler2 := &pb.Announcement{ServiceName: "handler", Id: "handler2", Token: "handler2", NetAddress: "localhost2:1881", + handler2 := &pb.Announcement{ServiceName: "handler", Id: "handler2", NetAddress: "localhost2:1881", Metadata: []*pb.Metadata{ &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}, }, @@ -76,7 +76,7 @@ func TestHandlerDiscoveryCache(t *testing.T) { discoveryServer, _ := buildMockDiscoveryServer(port) - handler := &pb.Announcement{ServiceName: "handler", Token: "handler", NetAddress: "localhost1:1881", + handler := &pb.Announcement{ServiceName: "handler", NetAddress: "localhost1:1881", Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}}, } diff --git a/core/discovery/server.go b/core/discovery/server.go index 5346ed4d2..7b1254741 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -4,6 +4,8 @@ package discovery import ( + "errors" + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "golang.org/x/net/context" @@ -15,41 +17,76 @@ type discoveryServer struct { } func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { - err := d.discovery.Announce(announcement) + claims, err := d.discovery.ValidateContext(ctx) + if err != nil { + return nil, err + } + _ = claims // TODO: Check the claims for the announced Component ID + announcementCopy := *announcement + announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement + err = d.discovery.Announce(&announcementCopy) if err != nil { return nil, err } return &api.Ack{}, nil } -func (d *discoveryServer) Discover(ctx context.Context, req *pb.DiscoverRequest) (*pb.DiscoverResponse, error) { - services, err := d.discovery.Discover(req.ServiceName) +func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { + claims, err := d.discovery.ValidateContext(ctx) if err != nil { return nil, err } - return &pb.DiscoverResponse{ - Services: services, - }, nil + switch in.Metadata.Key { + case pb.Metadata_PREFIX: + // Allow announcing any PREFIX + case pb.Metadata_APP_EUI: + // TODO: Check the claims for the announced APP_EUI + return nil, errors.New("ttn/discovery: Can not announce AppEUIs at this time") + case pb.Metadata_APP_ID: + if !claims.CanEditApp(string(in.Metadata.Value)) { + return nil, errors.New("ttn/discovery: No access to this application") + } + } + err = d.discovery.AddMetadata(in.ServiceName, in.Id, in.Metadata) + if err != nil { + return nil, err + } + return &api.Ack{}, nil } -func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.DiscoverResponse, error) { - services, err := d.discovery.Discover(req.ServiceName, req.Id...) +func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { + claims, err := d.discovery.ValidateContext(ctx) if err != nil { return nil, err } - return &pb.DiscoverResponse{ + _ = claims // TODO: Check the claims for the announced Component ID + err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) + if err != nil { + return nil, err + } + return &api.Ack{}, nil +} + +func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { + services, err := d.discovery.GetAll(req.ServiceName) + if err != nil { + return nil, err + } + return &pb.AnnouncementsResponse{ Services: services, }, nil } -// RegisterRPC registers the local discovery with a gRPC server -func (d *discovery) RegisterRPC(s *grpc.Server) { - server := &discoveryServer{d} - pb.RegisterDiscoveryServer(s, server) +func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { + service, err := d.discovery.Get(req.ServiceName, req.Id) + if err != nil { + return nil, err + } + return service, nil } -// RegisterRPC registers the Redis-based discovery with a gRPC server -func (d *redisDiscovery) RegisterRPC(s *grpc.Server) { +// RegisterRPC registers the local discovery with a gRPC server +func (d *discovery) RegisterRPC(s *grpc.Server) { server := &discoveryServer{d} pb.RegisterDiscoveryServer(s, server) } diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index 7a4518b2b..1d0fe5d81 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -57,17 +57,23 @@ func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Ann <-time.After(5 * time.Millisecond) return &api.Ack{}, nil } -func (d *mockDiscoveryServer) Discover(ctx context.Context, req *pb.DiscoverRequest) (*pb.DiscoverResponse, error) { +func (d *mockDiscoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { d.discover++ <-time.After(5 * time.Millisecond) - return &pb.DiscoverResponse{ + return &pb.AnnouncementsResponse{ Services: []*pb.Announcement{}, }, nil } -func (d *mockDiscoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.DiscoverResponse, error) { +func (d *mockDiscoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { d.get++ <-time.After(5 * time.Millisecond) - return &pb.DiscoverResponse{ - Services: []*pb.Announcement{}, - }, nil + return &pb.Announcement{}, nil +} +func (d *mockDiscoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { + <-time.After(5 * time.Millisecond) + return &api.Ack{}, nil +} +func (d *mockDiscoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { + <-time.After(5 * time.Millisecond) + return &api.Ack{}, nil } From 05dd85f44faf01e9bfb469b85d3e1de4f34f6e11 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 12:22:29 +0200 Subject: [PATCH 1578/2266] use the more popular govalidator over govalidate --- core/account/account.go.bk | 141 +++++++++++++++++++++++++++++++++ core/account/main.go | 4 + core/account/types.go | 8 +- core/account/util/http.go | 7 +- core/account/util/http_test.go | 2 +- 5 files changed, 153 insertions(+), 9 deletions(-) create mode 100644 core/account/account.go.bk create mode 100644 core/account/main.go diff --git a/core/account/account.go.bk b/core/account/account.go.bk new file mode 100644 index 000000000..17ce53d2e --- /dev/null +++ b/core/account/account.go.bk @@ -0,0 +1,141 @@ +package account + +import ( + "encoding/json" + "fmt" + + "github.com/TheThingsNetwork/ttn/core/account/client" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" +) + +// Account is a proxy to an account on the account server +type Account struct { + client *client.Client + accessToken string +} + +// New creates a new Account with the default HTTPClient +func New(accessToken string) Account { + client := &client.New() + return NewWithClient(accessToken, client) +} + +// NewWithClient creates a new Account with a custom client +func NewWithClient(accessToken string, client *client.Client) Account { + return Account{ + client: client, + accessToken: accessToken, + } +} + +// ListApplications list all applications +func (a *Account) ListApplications(ctx log.Interface) (apps []Application, err error) { + resp, err := a.client.Get(a.accessToken, "/applications") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return nil, err + } + + return apps +} + +// GetApplication gets a specific application from the account server +func (a *Account) GetApplication(ctx log.Interface, appID string) (Application, error) { + resp, err := a.client.Get(a.accessToken, fmt.Sprintf("/applications/%s", appID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return nil, err + } + + return apps +} + +type createApplicationReq struct { + ID string `json:"id"` + Name string `json:"name"` + EUIs []types.AppEUI `json:"euis,omitempty"` +} + +// CreateApplication creates a new application on the account server +func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (Application, error) { + body := createApplicationReq{ + ID: appID, + Name: name, + EUIs: EUIs, + } + + resp, err := a.client.Post(a.accessToken, fmt.Sprintf("/applications", body)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return nil, err + } + + return apps +} + +// DeleteApplication deletes an application +func (a *Account) DeleteAppliction(ctx log.Interface, appID string) error { + resp, err := a.client.Delete(a.accessToken, fmt.Sprintf("/applications/%s", appID)) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&apps); err != nil { + return nil, err + } + + return apps +} + +// Grant adds a collaborator to the application +func (a *Account) Grant(ctx log.Interface, appID string, username string, rights []Right) error { + panic("Grant not implemented") +} + +// Retract removes rights from a collaborator of the application +func (a *Account) Retract(ctx log.Interface, appID string, username string, rights []Right) error { + panic("Retract not implemented") +} + +// AddAccessKey +func (a *Account) AddAccessKey(ctx log.Interface, appID string, key AccessKey) error { + panic("AddAccessKey not implemented") +} + +// RemoveAccessKey +func (a *Account) RemoveAccessKey(ctx log.Interface, appID string, key AccessKey) error { + panic("RemoveAccessKey not implemented") +} + +// ChangeName +func (a *Account) ChangeName(ctx log.Interface, appID string, name string) error { + panic("ChangeName not implemented") +} + +// AddEUI +func (a *Account) AddEUI(ctx log.Interface, appID string, eui types.AppEUI) error { + panic("AddEUI not implemented") +} + +// RemoveEUI +func (a *Account) RemoveEUI(ctx log.Interface, appID string, eui types.AppEUI) error { + panic("RemoveEUI not implemented") +} diff --git a/core/account/main.go b/core/account/main.go new file mode 100644 index 000000000..0abd0facd --- /dev/null +++ b/core/account/main.go @@ -0,0 +1,4 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account diff --git a/core/account/types.go b/core/account/types.go index 1389454d3..0de22aaaa 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -11,8 +11,8 @@ import ( // Application represents an application on The Things Network type Application struct { - ID string `json:"id" validate:"nonzero"` - Name string `json:"name" validate:"nonzero"` + ID string `json:"id" valid:"required"` + Name string `json:"name" valid:"required"` EUIs []types.AppEUI `json:"euis,omitempty"` AccessKeys []types.AccessKey `json:"access_keys,omitempty"` Created time.Time `json:"created,omitempty"` @@ -21,6 +21,6 @@ type Application struct { // Collaborator is a user that has rights to a certain application type Collaborator struct { - Username string `json:"username" validate:"nonzero"` - Rights []types.Right `json:"rights" validate:"nonzero"` + Username string `json:"username" valid:"required"` + Rights []types.Right `json:"rights" valid:"required"` } diff --git a/core/account/util/http.go b/core/account/util/http.go index 7f25441fa..d358937a3 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -7,7 +7,7 @@ import ( "io" "net/http" - "gopkg.in/validator.v2" + "github.com/asaskevich/govalidator" ) type HTTPError struct { @@ -75,9 +75,8 @@ func performRequest(server, accessToken, method, URI string, body, res interface return err } - if err = validator.Validate(res); err != nil { - // return first error - return fmt.Errorf("Got a bad response from server: %s", err) + if _, err := govalidator.ValidateStruct(res); err != nil { + return fmt.Errorf("Got an illegal response from server: %s", err) } } diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index f2f9caf2f..23bb980dd 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -14,7 +14,7 @@ type OKResp struct { } type FooResp struct { - Foo string `json:"foo" validate:"nonzero"` + Foo string `json:"foo" valid:"required"` } func OKHandler(a *Assertion, method string) http.HandlerFunc { From 4c84ae006fc10053d1d440958e5e267f8c6fff3a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 19 Jul 2016 14:48:38 +0100 Subject: [PATCH 1579/2266] Update MQTT Client for #203 - Change QoS to 0x00 - Set Clean Session to false - Keep track of subscriptions - Re-subscribe on reconnect - Add Unsubscribe funcs Closes #203 --- mqtt/client.go | 85 ++++++++++++++++++++++++++++++++++++++------- mqtt/client_test.go | 63 +++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 13 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 07a32d1bf..79ee32fc2 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -13,7 +13,7 @@ import ( MQTT "github.com/eclipse/paho.mqtt.golang" ) -const QoS = 0x02 +const QoS = 0x00 // Client connects to the MQTT server and can publish/subscribe on uplink, downlink and activations from devices type Client interface { @@ -27,18 +27,27 @@ type Client interface { SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token SubscribeAppUplink(appID string, handler UplinkHandler) Token SubscribeUplink(handler UplinkHandler) Token + UnsubscribeDeviceUplink(appID string, devID string) Token + UnsubscribeAppUplink(appID string) Token + UnsubscribeUplink() Token // Downlink pub/sub PublishDownlink(appID string, devID string, payload DownlinkMessage) Token SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token SubscribeAppDownlink(appID string, handler DownlinkHandler) Token SubscribeDownlink(handler DownlinkHandler) Token + UnsubscribeDeviceDownlink(appID string, devID string) Token + UnsubscribeAppDownlink(appID string) Token + UnsubscribeDownlink() Token // Activation pub/sub PublishActivation(appID string, devID string, payload Activation) Token SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token SubscribeAppActivations(appID string, handler ActivationHandler) Token SubscribeActivations(handler ActivationHandler) Token + UnsubscribeDeviceActivations(appID string, devID string) Token + UnsubscribeAppActivations(appID string) Token + UnsubscribeActivations() Token } type Token interface { @@ -71,8 +80,9 @@ type DownlinkHandler func(client Client, appID string, devID string, req Downlin type ActivationHandler func(client Client, appID string, devID string, req Activation) type defaultClient struct { - mqtt MQTT.Client - ctx log.Interface + mqtt MQTT.Client + ctx log.Interface + subscriptions map[string]MQTT.MessageHandler } func NewClient(ctx log.Interface, id, username, password string, brokers ...string) Client { @@ -90,9 +100,7 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri mqttOpts.SetKeepAlive(30 * time.Second) mqttOpts.SetPingTimeout(10 * time.Second) - // Usually this setting should not be used together with random ClientIDs, but - // we configured The Things Network's MQTT servers to handle this correctly. - mqttOpts.SetCleanSession(false) + mqttOpts.SetCleanSession(true) mqttOpts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) { ctx.WithField("message", msg).Warn("Received unhandled message") @@ -102,14 +110,20 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri ctx.WithError(err).Warn("Disconnected, reconnecting...") }) + ttnClient := &defaultClient{ + mqtt: MQTT.NewClient(mqttOpts), + ctx: ctx, + subscriptions: make(map[string]MQTT.MessageHandler), + } + mqttOpts.SetOnConnectHandler(func(client MQTT.Client) { ctx.Debug("Connected to MQTT") + for topic, handler := range ttnClient.subscriptions { + ttnClient.subscribe(topic, handler) // re-subscribe + } }) - return &defaultClient{ - mqtt: MQTT.NewClient(mqttOpts), - ctx: ctx, - } + return ttnClient } var ( @@ -140,6 +154,16 @@ func (c *defaultClient) Connect() error { return nil } +func (c *defaultClient) subscribe(topic string, handler MQTT.MessageHandler) Token { + c.subscriptions[topic] = handler + return c.mqtt.Subscribe(topic, QoS, handler) +} + +func (c *defaultClient) unsubscribe(topic string) Token { + delete(c.subscriptions, topic) + return c.mqtt.Unsubscribe(topic) +} + func (c *defaultClient) Disconnect() { if !c.mqtt.IsConnected() { return @@ -163,7 +187,7 @@ func (c *defaultClient) PublishUplink(appID string, devID string, dataUp UplinkM func (c *defaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { topic := DeviceTopic{appID, devID, Uplink} - return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { @@ -193,6 +217,17 @@ func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { return c.SubscribeDeviceUplink("", "", handler) } +func (c *defaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, Uplink} + return c.unsubscribe(topic.String()) +} +func (c *defaultClient) UnsubscribeAppUplink(appID string) Token { + return c.UnsubscribeDeviceUplink(appID, "") +} +func (c *defaultClient) UnsubscribeUplink() Token { + return c.UnsubscribeDeviceUplink("", "") +} + func (c *defaultClient) PublishDownlink(appID string, devID string, dataDown DownlinkMessage) Token { topic := DeviceTopic{appID, devID, Downlink} msg, err := json.Marshal(dataDown) @@ -204,7 +239,7 @@ func (c *defaultClient) PublishDownlink(appID string, devID string, dataDown Dow func (c *defaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { topic := DeviceTopic{appID, devID, Downlink} - return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { @@ -233,6 +268,17 @@ func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { return c.SubscribeDeviceDownlink("", "", handler) } +func (c *defaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, Downlink} + return c.unsubscribe(topic.String()) +} +func (c *defaultClient) UnsubscribeAppDownlink(appID string) Token { + return c.UnsubscribeDeviceDownlink(appID, "") +} +func (c *defaultClient) UnsubscribeDownlink() Token { + return c.UnsubscribeDeviceDownlink("", "") +} + func (c *defaultClient) PublishActivation(appID string, devID string, activation Activation) Token { topic := DeviceTopic{appID, devID, Activations} msg, err := json.Marshal(activation) @@ -244,7 +290,7 @@ func (c *defaultClient) PublishActivation(appID string, devID string, activation func (c *defaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { topic := DeviceTopic{appID, devID, Activations} - return c.mqtt.Subscribe(topic.String(), QoS, func(mqtt MQTT.Client, msg MQTT.Message) { + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { @@ -272,3 +318,16 @@ func (c *defaultClient) SubscribeAppActivations(appID string, handler Activation func (c *defaultClient) SubscribeActivations(handler ActivationHandler) Token { return c.SubscribeDeviceActivations("", "", handler) } + +func (c *defaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, Activations} + return c.unsubscribe(topic.String()) +} + +func (c *defaultClient) UnsubscribeAppActivations(appID string) Token { + return c.UnsubscribeDeviceActivations(appID, "") +} + +func (c *defaultClient) UnsubscribeActivations() Token { + return c.UnsubscribeDeviceActivations("", "") +} diff --git a/mqtt/client_test.go b/mqtt/client_test.go index e0d280061..a4e760ca2 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -35,10 +35,12 @@ func TestConnect(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") err := c.Connect() + defer c.Disconnect() a.So(err, ShouldBeNil) // Connecting while already connected should not change anything err = c.Connect() + defer c.Disconnect() a.So(err, ShouldBeNil) } @@ -48,6 +50,7 @@ func TestConnectInvalidAddress(t *testing.T) { ConnectRetryDelay = 50 * time.Millisecond c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 err := c.Connect() + defer c.Disconnect() a.So(err, ShouldNotBeNil) } @@ -62,6 +65,7 @@ func TestIsConnected(t *testing.T) { a.So(c.IsConnected(), ShouldBeFalse) c.Connect() + defer c.Disconnect() a.So(c.IsConnected(), ShouldBeTrue) } @@ -75,6 +79,7 @@ func TestDisconnect(t *testing.T) { a.So(c.IsConnected(), ShouldBeFalse) c.Connect() + defer c.Disconnect() c.Disconnect() a.So(c.IsConnected(), ShouldBeFalse) @@ -85,6 +90,7 @@ func TestRandomTopicPublish(t *testing.T) { c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() c.(*defaultClient).mqtt.Subscribe("randomtopic", QoS, nil).Wait() c.(*defaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}).Wait() @@ -100,6 +106,7 @@ func TestPublishUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() dataUp := UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, @@ -115,12 +122,16 @@ func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeDeviceUplink("someid", "someid") + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -128,12 +139,16 @@ func TestSubscribeAppUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeAppUplink("someid") + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -141,12 +156,16 @@ func TestSubscribeUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeUplink() + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -154,6 +173,7 @@ func TestPubSubUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() var wg sync.WaitGroup @@ -169,12 +189,15 @@ func TestPubSubUplink(t *testing.T) { c.PublishUplink("app1", "dev1", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() + + c.UnsubscribeDeviceUplink("app1", "dev1").Wait() } func TestPubSubAppUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() var wg sync.WaitGroup @@ -190,6 +213,8 @@ func TestPubSubAppUplink(t *testing.T) { c.PublishUplink("app2", "dev2", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() + + c.UnsubscribeAppUplink("app1").Wait() } // Downlink pub/sub @@ -198,6 +223,7 @@ func TestPublishDownlink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() dataDown := DownlinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, @@ -213,12 +239,16 @@ func TestSubscribeDeviceDownlink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req DownlinkMessage) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeDeviceDownlink("someid", "someid") + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -226,12 +256,16 @@ func TestSubscribeAppDownlink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req DownlinkMessage) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeAppDownlink("someid") + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -239,12 +273,16 @@ func TestSubscribeDownlink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeDownlink(func(client Client, appID string, devID string, req DownlinkMessage) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeDownlink() + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -252,6 +290,7 @@ func TestPubSubDownlink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() var wg sync.WaitGroup @@ -267,12 +306,15 @@ func TestPubSubDownlink(t *testing.T) { c.PublishDownlink("app3", "dev3", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() + + c.UnsubscribeDeviceDownlink("app3", "dev3").Wait() } func TestPubSubAppDownlink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() var wg sync.WaitGroup @@ -288,6 +330,8 @@ func TestPubSubAppDownlink(t *testing.T) { c.PublishDownlink("app4", "dev2", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() wg.Wait() + + c.UnsubscribeAppDownlink("app3").Wait() } // Activations pub/sub @@ -296,6 +340,7 @@ func TestPublishActivations(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() dataActivations := Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}} @@ -309,12 +354,16 @@ func TestSubscribeDeviceActivations(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req Activation) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeDeviceActivations("someid", "someid") + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -322,12 +371,16 @@ func TestSubscribeAppActivations(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req Activation) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeAppActivations("someid") + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -335,12 +388,16 @@ func TestSubscribeActivations(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() token := c.SubscribeActivations(func(client Client, appID string, devID string, req Activation) { }) token.Wait() + a.So(token.Error(), ShouldBeNil) + token = c.UnsubscribeActivations() + token.Wait() a.So(token.Error(), ShouldBeNil) } @@ -348,6 +405,7 @@ func TestPubSubActivations(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() var wg sync.WaitGroup @@ -363,12 +421,15 @@ func TestPubSubActivations(t *testing.T) { c.PublishActivation("app5", "dev1", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() wg.Wait() + + c.UnsubscribeDeviceActivations("app5", "dev1") } func TestPubSubAppActivations(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") c.Connect() + defer c.Disconnect() var wg sync.WaitGroup @@ -384,4 +445,6 @@ func TestPubSubAppActivations(t *testing.T) { c.PublishActivation("app6", "dev2", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() wg.Wait() + + c.UnsubscribeAppActivations("app6") } From d11a090a6328529281eaecfe6d1fa8a0c457b33b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:17:05 +0200 Subject: [PATCH 1580/2266] check status code for error ranges --- core/account/util/http.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index d358937a3..d8a195ac1 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -50,7 +50,6 @@ func performRequest(server, accessToken, method, URI string, body, res interface req, err = newRequest(server, accessToken, method, URI, nil) } - // if err != nil { return err } @@ -61,13 +60,18 @@ func performRequest(server, accessToken, method, URI string, body, res interface return err } - if resp.StatusCode != http.StatusOK { + if resp.StatusCode >= 400 { return HTTPError{ Code: resp.StatusCode, Message: resp.Status, } } + if resp.StatusCode >= 300 { + // 307 is resolved automatically, 301, 302, 304 cannot be + return fmt.Errorf("Unexpected redirection to %s", resp.Header.Get("Location")) + } + if res != nil { defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) From d2c3771d427609e8b728b4fa90b7aa514f8dd834 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:17:18 +0200 Subject: [PATCH 1581/2266] test server-side redirection --- core/account/util/http_test.go | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index 23bb980dd..c5c248a19 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -43,6 +43,23 @@ func FooHandler(a *Assertion, method string) http.HandlerFunc { }) } +func RedirectHandler(a *Assertion, method string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == "/foo" { + w.Header().Set("Location", "/bar") + w.WriteHeader(307) + } else { + a.So(r.RequestURI, ShouldEqual, "/bar") + resp := FooResp{ + Foo: "ok", + } + w.WriteHeader(http.StatusOK) + encoder := json.NewEncoder(w) + encoder.Encode(&resp) + } + }) +} + func TestGET(t *testing.T) { a := New(t) server := httptest.NewServer(OKHandler(a, "GET")) @@ -82,3 +99,13 @@ func TestGETWrongResponseTypeIgnore(t *testing.T) { err := GET(server.URL, "ok", "/foo", &resp) a.So(err, ShouldBeNil) } + +func TestGETRedirect(t *testing.T) { + a := New(t) + server := httptest.NewServer(RedirectHandler(a, "GET")) + defer server.Close() + + var resp OKResp + err := GET(server.URL, "ok", "/foo", &resp) + a.So(err, ShouldBeNil) +} From 113bd969f8c0d8bb58694662e9b8a25654567a2f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:33:13 +0200 Subject: [PATCH 1582/2266] add PATCH --- core/account/util/http.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index d8a195ac1..c142b6996 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -87,7 +87,7 @@ func performRequest(server, accessToken, method, URI string, body, res interface return nil } -// GET does a get request to the account server +// GET does a get request to the account server, decoding the result into the object pointed to byres func GET(server, accessToken, URI string, res interface{}) error { return performRequest(server, accessToken, "GET", URI, nil, res) } @@ -98,13 +98,19 @@ func DELETE(server, accessToken, URI string) error { } // POST creates an HTTP Post request to the specified server, with the body -// encoded as JSON +// encoded as JSON, decoding the result into the object pointed to byres func POST(server, accessToken, URI string, body, res interface{}) error { return performRequest(server, accessToken, "POST", URI, body, res) } // POST creates an HTTP Put request to the specified server, with the body -// encoded as JSON +// encoded as JSON, decoding the result into the object pointed to byres func PUT(server, accessToken, URI string, body, res interface{}) error { return performRequest(server, accessToken, "PUT", URI, body, res) } + +// PATCH creates an HTTP Patch request to the specified server, with the body +// encoded as JSON, decoding the result into the object pointed to byres +func PATCH(server, accessToken, URI string, body, res interface{}) error { + return performRequest(server, accessToken, "POST", URI, body, res) +} From a220f9c0c9640977b765d0b63fb426fc91c2255b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:33:59 +0200 Subject: [PATCH 1583/2266] implement all api functions --- core/account/applications.go | 46 ++++++++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index f82208ddf..3fd076cd4 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -4,6 +4,8 @@ package account import ( + "fmt" + "github.com/TheThingsNetwork/ttn/core/account/util" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -16,7 +18,7 @@ func (a *Account) ListApplications() (apps []Application, err error) { // GetApplication gets a specific application from the account server func (a *Account) FindApplication(appID string) (app Application, err error) { - err = util.GET(a.server, a.accessToken, "/applications", &app) + err = util.GET(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), &app) return app, err } @@ -40,40 +42,58 @@ func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppE // DeleteApplication deletes an application func (a *Account) DeleteAppliction(appID string) error { - panic("DeleteApplication not implemented") + return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID)) } // Grant adds a collaborator to the application func (a *Account) Grant(appID string, username string, rights []types.Right) error { - panic("Grant not implemented") + return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), rights, nil) } // Retract removes rights from a collaborator of the application -func (a *Account) Retract(appID string, username string, rights []types.Right) error { - panic("Retract not implemented") +func (a *Account) Retract(appID string, username string) error { + return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username)) +} + +type addAccessKeyReq struct { + Name string `json:"name" valid:"required"` + Rights []types.Right `json:"rights" valid:"required"` } // AddAccessKey -func (a *Account) AddAccessKey(appID string, key types.AccessKey) error { - panic("AddAccessKey not implemented") +func (a *Account) AddAccessKey(appID string, name string, rights []types.Right) (key types.AccessKey, err error) { + body := addAccessKeyReq{ + Name: name, + Rights: rights, + } + util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys", appID), body, &key) + return key, err } // RemoveAccessKey -func (a *Account) RemoveAccessKey(appID string, key types.AccessKey) error { - panic("RemoveAccessKey not implemented") +func (a *Account) RemoveAccessKey(appID string, name string) error { + return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys/%s", appID, name)) +} + +type editAppReq struct { + Name string `json:"name,omitempty"` } // ChangeName -func (a *Account) ChangeName(appID string, name string) error { - panic("ChangeName not implemented") +func (a *Account) ChangeName(appID string, name string) (app Application, err error) { + body := editAppReq{ + Name: name, + } + err = util.PATCH(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), body, &app) + return app, err } // AddEUI func (a *Account) AddEUI(appID string, eui types.AppEUI) error { - panic("AddEUI not implemented") + return util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) } // RemoveEUI func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { - panic("RemoveEUI not implemented") + return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) } From e84687d6dc4f52ab91847ae857bcb0cdaae74686 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:37:59 +0200 Subject: [PATCH 1584/2266] add validation to request types --- core/account/applications.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index 3fd076cd4..c9597f9ec 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -23,9 +23,9 @@ func (a *Account) FindApplication(appID string) (app Application, err error) { } type createApplicationReq struct { - Name string `json:"name"` - AppID string `json:"id"` - EUIs []types.AppEUI `json:"euis"` + Name string `json:"name" valid:"required"` + AppID string `json:"id" valid:"required"` + EUIs []types.AppEUI `json:"euis" valid:"required"` } // CreateApplication creates a new application on the account server @@ -56,7 +56,7 @@ func (a *Account) Retract(appID string, username string) error { } type addAccessKeyReq struct { - Name string `json:"name" valid:"required"` + Name string `json:"name" valid:"required"` Rights []types.Right `json:"rights" valid:"required"` } From 837d7204ab1e1669a21a75575c7b45205718882f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:44:34 +0200 Subject: [PATCH 1585/2266] better comments --- core/account/applications.go | 17 +++++++++-------- core/account/util/http.go | 9 ++++++++- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index c9597f9ec..2c09dfe76 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -16,7 +16,7 @@ func (a *Account) ListApplications() (apps []Application, err error) { return apps, err } -// GetApplication gets a specific application from the account server +// FindApplication gets a specific application from the account server func (a *Account) FindApplication(appID string) (app Application, err error) { err = util.GET(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), &app) return app, err @@ -41,7 +41,7 @@ func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppE } // DeleteApplication deletes an application -func (a *Account) DeleteAppliction(appID string) error { +func (a *Account) DeleteApplication(appID string) error { return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID)) } @@ -60,17 +60,18 @@ type addAccessKeyReq struct { Rights []types.Right `json:"rights" valid:"required"` } -// AddAccessKey +// AddAccessKey adds an access key to the application with the specified name +// and rights func (a *Account) AddAccessKey(appID string, name string, rights []types.Right) (key types.AccessKey, err error) { body := addAccessKeyReq{ Name: name, Rights: rights, } - util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys", appID), body, &key) + err = util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys", appID), body, &key) return key, err } -// RemoveAccessKey +// RemoveAccessKey removes the specified access key from the application func (a *Account) RemoveAccessKey(appID string, name string) error { return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys/%s", appID, name)) } @@ -79,7 +80,7 @@ type editAppReq struct { Name string `json:"name,omitempty"` } -// ChangeName +// ChangeName changes the application name func (a *Account) ChangeName(appID string, name string) (app Application, err error) { body := editAppReq{ Name: name, @@ -88,12 +89,12 @@ func (a *Account) ChangeName(appID string, name string) (app Application, err er return app, err } -// AddEUI +// AddEUI adds an EUI to the applications list of EUIs func (a *Account) AddEUI(appID string, eui types.AppEUI) error { return util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) } -// RemoveEUI +// RemoveEUI removes the specified EUI from the application func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) } diff --git a/core/account/util/http.go b/core/account/util/http.go index c142b6996..704ee47ef 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -10,6 +10,9 @@ import ( "github.com/asaskevich/govalidator" ) +// HTTPError represents an error coming over HTTP, +// it is not an error with executing the request itself, it is +// an error the server is flaggin to the client. type HTTPError struct { Code int Message string @@ -38,6 +41,10 @@ func performRequest(server, accessToken, method, URI string, body, res interface if body != nil { // body is not nil, so serialize it and pass it in the request + if _, err = govalidator.ValidateStruct(body); err != nil { + return fmt.Errorf("Got an illegal request body: %s", err) + } + buf := new(bytes.Buffer) encoder := json.NewEncoder(buf) err = encoder.Encode(body) @@ -103,7 +110,7 @@ func POST(server, accessToken, URI string, body, res interface{}) error { return performRequest(server, accessToken, "POST", URI, body, res) } -// POST creates an HTTP Put request to the specified server, with the body +// PUT creates an HTTP Put request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres func PUT(server, accessToken, URI string, body, res interface{}) error { return performRequest(server, accessToken, "PUT", URI, body, res) From a01124ff4388165fbee886f1b6dcd6c1ea0724cd Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:47:16 +0200 Subject: [PATCH 1586/2266] rm old code file --- core/account/account.go.bk | 141 ------------------------------------- 1 file changed, 141 deletions(-) delete mode 100644 core/account/account.go.bk diff --git a/core/account/account.go.bk b/core/account/account.go.bk deleted file mode 100644 index 17ce53d2e..000000000 --- a/core/account/account.go.bk +++ /dev/null @@ -1,141 +0,0 @@ -package account - -import ( - "encoding/json" - "fmt" - - "github.com/TheThingsNetwork/ttn/core/account/client" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/apex/log" -) - -// Account is a proxy to an account on the account server -type Account struct { - client *client.Client - accessToken string -} - -// New creates a new Account with the default HTTPClient -func New(accessToken string) Account { - client := &client.New() - return NewWithClient(accessToken, client) -} - -// NewWithClient creates a new Account with a custom client -func NewWithClient(accessToken string, client *client.Client) Account { - return Account{ - client: client, - accessToken: accessToken, - } -} - -// ListApplications list all applications -func (a *Account) ListApplications(ctx log.Interface) (apps []Application, err error) { - resp, err := a.client.Get(a.accessToken, "/applications") - if err != nil { - return nil, err - } - defer resp.Body.Close() - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return nil, err - } - - return apps -} - -// GetApplication gets a specific application from the account server -func (a *Account) GetApplication(ctx log.Interface, appID string) (Application, error) { - resp, err := a.client.Get(a.accessToken, fmt.Sprintf("/applications/%s", appID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return nil, err - } - - return apps -} - -type createApplicationReq struct { - ID string `json:"id"` - Name string `json:"name"` - EUIs []types.AppEUI `json:"euis,omitempty"` -} - -// CreateApplication creates a new application on the account server -func (a *Account) CreateApplication(ctx log.Interface, appID string, name string, EUIs []types.AppEUI) (Application, error) { - body := createApplicationReq{ - ID: appID, - Name: name, - EUIs: EUIs, - } - - resp, err := a.client.Post(a.accessToken, fmt.Sprintf("/applications", body)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return nil, err - } - - return apps -} - -// DeleteApplication deletes an application -func (a *Account) DeleteAppliction(ctx log.Interface, appID string) error { - resp, err := a.client.Delete(a.accessToken, fmt.Sprintf("/applications/%s", appID)) - if err != nil { - return nil, err - } - defer resp.Body.Close() - - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&apps); err != nil { - return nil, err - } - - return apps -} - -// Grant adds a collaborator to the application -func (a *Account) Grant(ctx log.Interface, appID string, username string, rights []Right) error { - panic("Grant not implemented") -} - -// Retract removes rights from a collaborator of the application -func (a *Account) Retract(ctx log.Interface, appID string, username string, rights []Right) error { - panic("Retract not implemented") -} - -// AddAccessKey -func (a *Account) AddAccessKey(ctx log.Interface, appID string, key AccessKey) error { - panic("AddAccessKey not implemented") -} - -// RemoveAccessKey -func (a *Account) RemoveAccessKey(ctx log.Interface, appID string, key AccessKey) error { - panic("RemoveAccessKey not implemented") -} - -// ChangeName -func (a *Account) ChangeName(ctx log.Interface, appID string, name string) error { - panic("ChangeName not implemented") -} - -// AddEUI -func (a *Account) AddEUI(ctx log.Interface, appID string, eui types.AppEUI) error { - panic("AddEUI not implemented") -} - -// RemoveEUI -func (a *Account) RemoveEUI(ctx log.Interface, appID string, eui types.AppEUI) error { - panic("RemoveEUI not implemented") -} From 3966ba112c40f005093dfe611f48a244765d8fb3 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 19 Jul 2016 17:48:26 +0200 Subject: [PATCH 1587/2266] add licensing in util/http --- core/account/util/http.go | 3 +++ core/account/util/http_test.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/core/account/util/http.go b/core/account/util/http.go index 704ee47ef..51e410852 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package util import ( diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index c5c248a19..f26a66d72 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package util import ( From e4dd91e1c898a559934ff7fbe241c2899da00673 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:20:14 +0200 Subject: [PATCH 1588/2266] need to handle 307 manually --- core/account/util/http.go | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index 51e410852..9bcd74460 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -13,6 +13,10 @@ import ( "github.com/asaskevich/govalidator" ) +var ( + MaxRedirects = 5 +) + // HTTPError represents an error coming over HTTP, // it is not an error with executing the request itself, it is // an error the server is flaggin to the client. @@ -39,7 +43,7 @@ func newRequest(server string, accessToken string, method string, URI string, bo return req, nil } -func performRequest(server, accessToken, method, URI string, body, res interface{}) (err error) { +func performRequest(server, accessToken, method, URI string, body, res interface{}, redirects int) (err error) { var req *http.Request if body != nil { @@ -77,8 +81,16 @@ func performRequest(server, accessToken, method, URI string, body, res interface } } + if resp.StatusCode == 307 { + if redirects > 0 { + location := resp.Header.Get("Location") + return performRequest(server, accessToken, method, location, body, res, redirects-1) + } + return fmt.Errorf("Reached maximum number of redirects") + } + if resp.StatusCode >= 300 { - // 307 is resolved automatically, 301, 302, 304 cannot be + // 307 is handled, 301, 302, 304 cannot be return fmt.Errorf("Unexpected redirection to %s", resp.Header.Get("Location")) } @@ -99,28 +111,28 @@ func performRequest(server, accessToken, method, URI string, body, res interface // GET does a get request to the account server, decoding the result into the object pointed to byres func GET(server, accessToken, URI string, res interface{}) error { - return performRequest(server, accessToken, "GET", URI, nil, res) + return performRequest(server, accessToken, "GET", URI, nil, res, MaxRedirects) } // DELETE does a delete request to the account server func DELETE(server, accessToken, URI string) error { - return performRequest(server, accessToken, "DELETE", URI, nil, nil) + return performRequest(server, accessToken, "DELETE", URI, nil, nil, MaxRedirects) } // POST creates an HTTP Post request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres func POST(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "POST", URI, body, res) + return performRequest(server, accessToken, "POST", URI, body, res, MaxRedirects) } // PUT creates an HTTP Put request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres func PUT(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "PUT", URI, body, res) + return performRequest(server, accessToken, "PUT", URI, body, res, MaxRedirects) } // PATCH creates an HTTP Patch request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres func PATCH(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "POST", URI, body, res) + return performRequest(server, accessToken, "POST", URI, body, res, MaxRedirects) } From dba99fa627a9069d7f698a104bb3612959795a41 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:21:00 +0200 Subject: [PATCH 1589/2266] add tests for HTTP PUT --- core/account/util/http_test.go | 63 ++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index f26a66d72..2c8eb50bd 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -5,6 +5,7 @@ package util import ( "encoding/json" + "io/ioutil" "net/http" "net/http/httptest" "testing" @@ -63,6 +64,18 @@ func RedirectHandler(a *Assertion, method string) http.HandlerFunc { }) } +func EchoHandler(a *Assertion, method string) http.HandlerFunc { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + a.So(r.RequestURI, ShouldEqual, "/foo") + a.So(r.Method, ShouldEqual, method) + w.WriteHeader(http.StatusOK) + defer r.Body.Close() + body, err := ioutil.ReadAll(r.Body) + a.So(err, ShouldBeNil) + w.Write(body) + }) +} + func TestGET(t *testing.T) { a := New(t) server := httptest.NewServer(OKHandler(a, "GET")) @@ -83,7 +96,7 @@ func TestGETDropResponse(t *testing.T) { a.So(err, ShouldBeNil) } -func TestGETWrongResponseType(t *testing.T) { +func TestGETIllegalResponse(t *testing.T) { a := New(t) server := httptest.NewServer(OKHandler(a, "GET")) defer server.Close() @@ -93,7 +106,7 @@ func TestGETWrongResponseType(t *testing.T) { a.So(err, ShouldNotBeNil) } -func TestGETWrongResponseTypeIgnore(t *testing.T) { +func TestGETIllegalResponseIgnore(t *testing.T) { a := New(t) server := httptest.NewServer(FooHandler(a, "GET")) defer server.Close() @@ -112,3 +125,49 @@ func TestGETRedirect(t *testing.T) { err := GET(server.URL, "ok", "/foo", &resp) a.So(err, ShouldBeNil) } + +func TestPUT(t *testing.T) { + a := New(t) + server := httptest.NewServer(EchoHandler(a, "PUT")) + defer server.Close() + + var resp FooResp + body := FooResp{ + Foo: "ok", + } + err := PUT(server.URL, "ok", "/foo", body, &resp) + a.So(err, ShouldBeNil) + a.So(resp.Foo, ShouldEqual, body.Foo) +} + +func TestPUTIllegalRequest(t *testing.T) { + a := New(t) + server := httptest.NewServer(EchoHandler(a, "PUT")) + defer server.Close() + + var resp FooResp + body := FooResp{} + err := PUT(server.URL, "ok", "/foo", body, &resp) + a.So(err, ShouldNotBeNil) +} + +func TestPUTIllegalResponse(t *testing.T) { + a := New(t) + server := httptest.NewServer(OKHandler(a, "PUT")) + defer server.Close() + + var resp FooResp + err := PUT(server.URL, "ok", "/foo", nil, &resp) + a.So(err, ShouldNotBeNil) +} + +func TestPUTRedirect(t *testing.T) { + a := New(t) + server := httptest.NewServer(RedirectHandler(a, "PUT")) + defer server.Close() + + var resp FooResp + err := PUT(server.URL, "ok", "/foo", nil, &resp) + a.So(err, ShouldBeNil) + a.So(resp.Foo, ShouldEqual, "ok") +} From 612837c085037f875decba250ad55ed44b114a72 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:31:24 +0200 Subject: [PATCH 1590/2266] use same request headers on redirection --- core/account/util/http.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index 9bcd74460..6e7541621 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -6,6 +6,7 @@ package util import ( "bytes" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -29,6 +30,18 @@ func (e HTTPError) Error() string { return e.Message } +// checkRedirect implements this clients redirection policy +func checkRedirect(req *http.Request, via []*http.Request) error { + if len(via) > MaxRedirects { + return errors.New("Maximum number of redirects reached") + } + + // use the same headers as before + req.Header.Set("Authorization", via[len(via)-1].Header.Get("Authorization")) + req.Header.Set("Accept", "application/json") + return nil +} + // NewRequest creates a new http.Request that has authorization set up func newRequest(server string, accessToken string, method string, URI string, body io.Reader) (*http.Request, error) { URL := fmt.Sprintf("%s%s", server, URI) @@ -68,7 +81,9 @@ func performRequest(server, accessToken, method, URI string, body, res interface return err } - client := &http.Client{} + client := &http.Client{ + CheckRedirect: checkRedirect, + } resp, err := client.Do(req) if err != nil { return err @@ -91,7 +106,7 @@ func performRequest(server, accessToken, method, URI string, body, res interface if resp.StatusCode >= 300 { // 307 is handled, 301, 302, 304 cannot be - return fmt.Errorf("Unexpected redirection to %s", resp.Header.Get("Location")) + return fmt.Errorf("Unexpected %v redirection to %s", resp.StatusCode, resp.Header.Get("Location")) } if res != nil { From 1da13a885db8ad72d550dba51994090692540dda Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:31:35 +0200 Subject: [PATCH 1591/2266] test PUT redirection --- core/account/util/http_test.go | 49 ++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index 2c8eb50bd..9775237c7 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -13,8 +13,13 @@ import ( . "github.com/smartystreets/assertions" ) +var ( + url = "/foo" + token = "token" +) + type OKResp struct { - OK string `json:"ok"` + OK string `json:token` } type FooResp struct { @@ -23,10 +28,11 @@ type FooResp struct { func OKHandler(a *Assertion, method string) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, "/foo") + a.So(r.RequestURI, ShouldEqual, url) a.So(r.Method, ShouldEqual, method) + a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) resp := OKResp{ - OK: "ok", + OK: token, } w.WriteHeader(http.StatusOK) encoder := json.NewEncoder(w) @@ -36,10 +42,11 @@ func OKHandler(a *Assertion, method string) http.HandlerFunc { func FooHandler(a *Assertion, method string) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, "/foo") + a.So(r.RequestURI, ShouldEqual, url) a.So(r.Method, ShouldEqual, method) + a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) resp := FooResp{ - Foo: "ok", + Foo: token, } w.WriteHeader(http.StatusOK) encoder := json.NewEncoder(w) @@ -49,13 +56,14 @@ func FooHandler(a *Assertion, method string) http.HandlerFunc { func RedirectHandler(a *Assertion, method string) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.RequestURI == "/foo" { + a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) + if r.RequestURI == url { w.Header().Set("Location", "/bar") w.WriteHeader(307) } else { a.So(r.RequestURI, ShouldEqual, "/bar") resp := FooResp{ - Foo: "ok", + Foo: token, } w.WriteHeader(http.StatusOK) encoder := json.NewEncoder(w) @@ -66,8 +74,9 @@ func RedirectHandler(a *Assertion, method string) http.HandlerFunc { func EchoHandler(a *Assertion, method string) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, "/foo") + a.So(r.RequestURI, ShouldEqual, url) a.So(r.Method, ShouldEqual, method) + a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) w.WriteHeader(http.StatusOK) defer r.Body.Close() body, err := ioutil.ReadAll(r.Body) @@ -82,9 +91,9 @@ func TestGET(t *testing.T) { defer server.Close() var resp OKResp - err := GET(server.URL, "ok", "/foo", &resp) + err := GET(server.URL, token, url, &resp) a.So(err, ShouldBeNil) - a.So(resp.OK, ShouldEqual, "ok") + a.So(resp.OK, ShouldEqual, token) } func TestGETDropResponse(t *testing.T) { @@ -92,7 +101,7 @@ func TestGETDropResponse(t *testing.T) { server := httptest.NewServer(OKHandler(a, "GET")) defer server.Close() - err := GET(server.URL, "ok", "/foo", nil) + err := GET(server.URL, token, url, nil) a.So(err, ShouldBeNil) } @@ -102,7 +111,7 @@ func TestGETIllegalResponse(t *testing.T) { defer server.Close() var resp FooResp - err := GET(server.URL, "ok", "/foo", &resp) + err := GET(server.URL, token, url, &resp) a.So(err, ShouldNotBeNil) } @@ -112,7 +121,7 @@ func TestGETIllegalResponseIgnore(t *testing.T) { defer server.Close() var resp OKResp - err := GET(server.URL, "ok", "/foo", &resp) + err := GET(server.URL, token, url, &resp) a.So(err, ShouldBeNil) } @@ -122,7 +131,7 @@ func TestGETRedirect(t *testing.T) { defer server.Close() var resp OKResp - err := GET(server.URL, "ok", "/foo", &resp) + err := GET(server.URL, token, url, &resp) a.So(err, ShouldBeNil) } @@ -133,9 +142,9 @@ func TestPUT(t *testing.T) { var resp FooResp body := FooResp{ - Foo: "ok", + Foo: token, } - err := PUT(server.URL, "ok", "/foo", body, &resp) + err := PUT(server.URL, token, url, body, &resp) a.So(err, ShouldBeNil) a.So(resp.Foo, ShouldEqual, body.Foo) } @@ -147,7 +156,7 @@ func TestPUTIllegalRequest(t *testing.T) { var resp FooResp body := FooResp{} - err := PUT(server.URL, "ok", "/foo", body, &resp) + err := PUT(server.URL, token, url, body, &resp) a.So(err, ShouldNotBeNil) } @@ -157,7 +166,7 @@ func TestPUTIllegalResponse(t *testing.T) { defer server.Close() var resp FooResp - err := PUT(server.URL, "ok", "/foo", nil, &resp) + err := PUT(server.URL, token, url, nil, &resp) a.So(err, ShouldNotBeNil) } @@ -167,7 +176,7 @@ func TestPUTRedirect(t *testing.T) { defer server.Close() var resp FooResp - err := PUT(server.URL, "ok", "/foo", nil, &resp) + err := PUT(server.URL, token, url, nil, &resp) a.So(err, ShouldBeNil) - a.So(resp.Foo, ShouldEqual, "ok") + a.So(resp.Foo, ShouldEqual, token) } From 5449fbfd2539cdf4b3c3ff49797c12bea36a5863 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:33:57 +0200 Subject: [PATCH 1592/2266] add tests for POST and DELETE --- core/account/util/http_test.go | 64 ++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index 9775237c7..95c6a5993 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -180,3 +180,67 @@ func TestPUTRedirect(t *testing.T) { a.So(err, ShouldBeNil) a.So(resp.Foo, ShouldEqual, token) } + +func TestPOST(t *testing.T) { + a := New(t) + server := httptest.NewServer(EchoHandler(a, "POST")) + defer server.Close() + + var resp FooResp + body := FooResp{ + Foo: token, + } + err := POST(server.URL, token, url, body, &resp) + a.So(err, ShouldBeNil) + a.So(resp.Foo, ShouldEqual, body.Foo) +} + +func TestPOSTIllegalRequest(t *testing.T) { + a := New(t) + server := httptest.NewServer(EchoHandler(a, "POST")) + defer server.Close() + + var resp FooResp + body := FooResp{} + err := POST(server.URL, token, url, body, &resp) + a.So(err, ShouldNotBeNil) +} + +func TestPOSTIllegalResponse(t *testing.T) { + a := New(t) + server := httptest.NewServer(OKHandler(a, "POST")) + defer server.Close() + + var resp FooResp + err := POST(server.URL, token, url, nil, &resp) + a.So(err, ShouldNotBeNil) +} + +func TestPOSTRedirect(t *testing.T) { + a := New(t) + server := httptest.NewServer(RedirectHandler(a, "POST")) + defer server.Close() + + var resp FooResp + err := POST(server.URL, token, url, nil, &resp) + a.So(err, ShouldBeNil) + a.So(resp.Foo, ShouldEqual, token) +} + +func TestDELETE(t *testing.T) { + a := New(t) + server := httptest.NewServer(OKHandler(a, "DELETE")) + defer server.Close() + + err := DELETE(server.URL, token, url) + a.So(err, ShouldBeNil) +} + +func TestDELETERedirect(t *testing.T) { + a := New(t) + server := httptest.NewServer(RedirectHandler(a, "DELETE")) + defer server.Close() + + err := DELETE(server.URL, token, url) + a.So(err, ShouldBeNil) +} From 2773859229900d67202c8c0213f4d7f617bf7459 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:58:02 +0200 Subject: [PATCH 1593/2266] add test for access keys --- core/types/access_keys_test.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 core/types/access_keys_test.go diff --git a/core/types/access_keys_test.go b/core/types/access_keys_test.go new file mode 100644 index 000000000..2cf1dbf8a --- /dev/null +++ b/core/types/access_keys_test.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + s "github.com/smartystreets/assertions" +) + +func TestAccessKeysRights(t *testing.T) { + a := s.New(t) + c := AccessKey{ + Name: "name", + Key: "123456", + Rights: []Right{ + Right("right"), + }, + } + + a.So(c.HasRight(Right("right")), s.ShouldBeTrue) + a.So(c.HasRight(Right("foo")), s.ShouldBeFalse) +} From 29af19c234283a700f5ff53e1ec7b310fe9b259b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 11:58:34 +0200 Subject: [PATCH 1594/2266] add HasRight for collaborator and test it --- core/account/types.go | 9 +++++++++ core/account/types_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 core/account/types_test.go diff --git a/core/account/types.go b/core/account/types.go index 0de22aaaa..97bce4876 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -24,3 +24,12 @@ type Collaborator struct { Username string `json:"username" valid:"required"` Rights []types.Right `json:"rights" valid:"required"` } + +func (c *Collaborator) HasRight(right types.Right) bool { + for _, r := range c.Rights { + if r == right { + return true + } + } + return false +} diff --git a/core/account/types_test.go b/core/account/types_test.go new file mode 100644 index 000000000..a0bd4787c --- /dev/null +++ b/core/account/types_test.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + s "github.com/smartystreets/assertions" +) + +func TestCollaboratorRights(t *testing.T) { + a := s.New(t) + c := Collaborator{ + Username: "username", + Rights: []types.Right{ + types.Right("right"), + }, + } + + a.So(c.HasRight(types.Right("right")), s.ShouldBeTrue) + a.So(c.HasRight(types.Right("foo")), s.ShouldBeFalse) +} From cd83c3ac7a7b4e66f7722845d25896f0a685ddf4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 11:08:34 +0100 Subject: [PATCH 1595/2266] fixed spf13/cobra breaking changes --- ttnctl/cmd/applications_pf_set.go | 4 ++-- ttnctl/cmd/devices_delete.go | 2 +- ttnctl/cmd/devices_info.go | 2 +- ttnctl/cmd/devices_personalize.go | 2 +- ttnctl/cmd/devices_set.go | 2 +- ttnctl/cmd/user.go | 4 ++-- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 73a2dfd8b..33e8910f8 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -15,7 +15,7 @@ import ( // applicationsPayloadFunctionsSetCmd represents the `applications pf set` command var applicationsPayloadFunctionsSetCmd = &cobra.Command{ - Use: "set [decoder/converter/validator] [file.js]", + Use: "pf set [decoder/converter/validator] [file.js]", Short: "Set payload functions of an application", Long: `ttnctl pf set can be used to get or set payload functions of an application. The functions are read from the supplied file or from STDIN.`, @@ -47,7 +47,7 @@ The functions are read from the supplied file or from STDIN.`, } if len(args) == 0 { - cmd.Usage() + cmd.UsageFunc()(cmd) return } diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go index e93a8b1bc..fe046e52b 100644 --- a/ttnctl/cmd/devices_delete.go +++ b/ttnctl/cmd/devices_delete.go @@ -25,7 +25,7 @@ var devicesDeleteCmd = &cobra.Command{ } if len(args) == 0 { - cmd.Usage() + cmd.UsageFunc()(cmd) return } diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index 6f33d5f31..cbcfd7e3e 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -28,7 +28,7 @@ var devicesInfoCmd = &cobra.Command{ } if len(args) == 0 { - cmd.Usage() + cmd.UsageFunc()(cmd) return } diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index ef9b493b2..3a3d533bf 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -27,7 +27,7 @@ var devicesPersonalizeCmd = &cobra.Command{ } if len(args) == 0 { - cmd.Usage() + cmd.UsageFunc()(cmd) return } diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index 19a33ea38..13b91516d 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -26,7 +26,7 @@ var devicesSetCmd = &cobra.Command{ } if len(args) == 0 { - cmd.Usage() + cmd.UsageFunc()(cmd) return } diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 5fe36957f..27716b71d 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -42,7 +42,7 @@ var userCreateCmd = &cobra.Command{ Long: `ttnctl user create allows you to create a new user`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { - cmd.Help() + cmd.UsageFunc()(cmd) return } @@ -95,7 +95,7 @@ var userLoginCmd = &cobra.Command{ Long: `ttnctl user login allows you to login`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { - cmd.Help() + cmd.UsageFunc()(cmd) return } From 492d27bb645917b6d2b7e92aa4b1957a775488d6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 11:09:50 +0100 Subject: [PATCH 1596/2266] Check metadata ok in gRPC servers --- core/broker/server.go | 6 +++++- core/handler/server.go | 5 ++++- core/networkserver/server.go | 5 ++++- core/router/server.go | 5 ++++- 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/core/broker/server.go b/core/broker/server.go index e5666c4bb..99e212a57 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -4,6 +4,7 @@ package broker import ( + "errors" "io" pb_api "github.com/TheThingsNetwork/ttn/api" @@ -22,7 +23,10 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { md, ok := metadata.FromContext(ctx) - // TODO: Check OK + if !ok { + err = errors.New("ttn: Could not get metadata") + return + } id, ok := md["id"] if !ok || len(id) < 1 { err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"id\" in context") diff --git a/core/handler/server.go b/core/handler/server.go index cbd581512..b2edd5880 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -22,7 +22,10 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func validateBrokerFromMetadata(ctx context.Context) (err error) { md, ok := metadata.FromContext(ctx) - // TODO: Check OK + if !ok { + err = errors.New("ttn: Could not get metadata") + return + } id, ok := md["id"] if !ok || len(id) < 1 { err = errors.New("ttn/handler: Broker did not provide \"id\" in context") diff --git a/core/networkserver/server.go b/core/networkserver/server.go index f7a54ca64..324801806 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -23,7 +23,10 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func validateBrokerFromMetadata(ctx context.Context) (err error) { md, ok := metadata.FromContext(ctx) - // TODO: Check OK + if !ok { + err = errors.New("ttn: Could not get metadata") + return + } id, ok := md["id"] if !ok || len(id) < 1 { err = errors.New("ttn/networkserver: Broker did not provide \"id\" in context") diff --git a/core/router/server.go b/core/router/server.go index 4888fcd96..d81a48747 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -24,7 +24,10 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, err error) { md, ok := metadata.FromContext(ctx) - // TODO: Check OK + if !ok { + err = errors.New("ttn: Could not get metadata") + return + } euiString, ok := md["gateway_eui"] if !ok || len(euiString) < 1 { err = errors.New("ttn/router: Gateway did not provide \"gateway_eui\" in context") From 6849f85576f057ec7b2ab0f79ea110825ccd46e9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 11:11:06 +0100 Subject: [PATCH 1597/2266] Periodic Gateway Schedule Cleanup --- core/router/gateway/schedule.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 7439b5e96..7b6f2cf0d 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -33,11 +33,30 @@ type Schedule interface { // NewSchedule creates a new Schedule func NewSchedule(ctx log.Interface) Schedule { - return &schedule{ + s := &schedule{ ctx: ctx, items: make(map[string]*scheduledItem), random: random.New(), } + go func() { + for { + s.RLock() + numItems := len(s.items) + s.RUnlock() + if numItems > 0 { + s.Lock() + for id, item := range s.items { + // Delete the item if we are more than 2 seconds after the deadline + if time.Now().After(item.deadlineAt.Add(2 * time.Second)) { + delete(s.items, id) + } + } + s.Unlock() + } + <-time.After(10 * time.Second) + } + }() + return s } type scheduledItem struct { @@ -68,6 +87,7 @@ func (s *schedule) GoString() (str string) { return } +// Deadline for sending a downlink back to the gateway // TODO: Make configurable var Deadline = 200 * time.Millisecond @@ -132,16 +152,6 @@ func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score s.Lock() defer s.Unlock() s.items[id] = item - - // Schedule deletion after the option expires - // TODO: Periodically clean up instead of this goroutine - go func() { - <-time.After(10 * time.Second) - s.Lock() - defer s.Unlock() - delete(s.items, id) - }() - return id, score } From 467cbda56890ab34a6c874b609b13d0326038fb1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 11:12:00 +0100 Subject: [PATCH 1598/2266] Log request durations --- core/component.go | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/core/component.go b/core/component.go index cdae15c46..e56701091 100644 --- a/core/component.go +++ b/core/component.go @@ -194,12 +194,20 @@ func (c *Component) ServerOptions() []grpc.ServerOption { peerID = id[0] } } - c.Ctx.WithFields(log.Fields{ + logCtx := c.Ctx.WithFields(log.Fields{ "CallerID": peerID, "CallerIP": peerAddr, "Method": info.FullMethod, - }).Debug("Handle Request") - return handler(ctx, req) + }) + t := time.Now() + iface, err := handler(ctx, req) + logCtx = logCtx.WithField("Time", time.Now().Sub(t)) + if err != nil { + logCtx.WithError(err).Warn("Could not handle Request") + } else { + logCtx.Debug("Handled Request") + } + return iface, err } stream := func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { From ede4902fc527233d8fee4ef8442a0ee5b665a36c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 11:16:21 +0100 Subject: [PATCH 1599/2266] Update Discovery mechanism --- api/handler/handler.pb.go | 116 +++++++++++++++-------- api/handler/handler.proto | 1 + api/handler/manager_client.go | 8 ++ cmd/broker.go | 7 +- cmd/discovery.go | 7 +- cmd/handler.go | 7 +- cmd/networkserver.go | 5 +- cmd/router.go | 7 +- core/broker/activation.go | 6 +- core/broker/downlink.go | 2 +- core/broker/uplink.go | 4 +- core/component.go | 41 ++++---- core/discovery/broker_discovery.go | 12 +-- core/discovery/broker_discovery_test.go | 18 +++- core/discovery/handler_discovery.go | 12 +-- core/discovery/handler_discovery_test.go | 18 +++- core/handler/handler.go | 33 +------ core/handler/manager_server.go | 90 ++++++++++-------- core/router/activation.go | 2 +- core/router/router.go | 2 +- ttnctl/cmd/applications_register.go | 49 ++++++++++ 21 files changed, 277 insertions(+), 170 deletions(-) create mode 100644 ttnctl/cmd/applications_register.go diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index b23b59cf4..2f7642b69 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -315,6 +315,7 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ // Client API for ApplicationManager service type ApplicationManagerClient interface { + RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*api.Ack, error) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) @@ -332,6 +333,15 @@ func NewApplicationManagerClient(cc *grpc.ClientConn) ApplicationManagerClient { return &applicationManagerClient{cc} } +func (c *applicationManagerClient) RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { + out := new(api.Ack) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/RegisterApplication", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *applicationManagerClient) GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) { out := new(Application) err := grpc.Invoke(ctx, "/handler.ApplicationManager/GetApplication", in, out, c.cc, opts...) @@ -398,6 +408,7 @@ func (c *applicationManagerClient) GetDevicesForApplication(ctx context.Context, // Server API for ApplicationManager service type ApplicationManagerServer interface { + RegisterApplication(context.Context, *ApplicationIdentifier) (*api.Ack, error) GetApplication(context.Context, *ApplicationIdentifier) (*Application, error) SetApplication(context.Context, *Application) (*api.Ack, error) DeleteApplication(context.Context, *ApplicationIdentifier) (*api.Ack, error) @@ -411,6 +422,24 @@ func RegisterApplicationManagerServer(s *grpc.Server, srv ApplicationManagerServ s.RegisterService(&_ApplicationManager_serviceDesc, srv) } +func _ApplicationManager_RegisterApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ApplicationIdentifier) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).RegisterApplication(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/RegisterApplication", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).RegisterApplication(ctx, req.(*ApplicationIdentifier)) + } + return interceptor(ctx, in, info, handler) +} + func _ApplicationManager_GetApplication_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ApplicationIdentifier) if err := dec(in); err != nil { @@ -541,6 +570,10 @@ var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "handler.ApplicationManager", HandlerType: (*ApplicationManagerServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "RegisterApplication", + Handler: _ApplicationManager_RegisterApplication_Handler, + }, { MethodName: "GetApplication", Handler: _ApplicationManager_GetApplication_Handler, @@ -2017,45 +2050,46 @@ var ( ) var fileDescriptorHandler = []byte{ - // 633 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xdd, 0x6e, 0xd3, 0x30, - 0x14, 0x5e, 0x19, 0x74, 0xed, 0xe9, 0xd6, 0x0d, 0x8f, 0x8d, 0x50, 0x4d, 0x13, 0xe4, 0x02, 0x81, - 0x10, 0x29, 0xea, 0x90, 0x06, 0x42, 0x08, 0x3a, 0x4d, 0x63, 0x93, 0x18, 0x93, 0xd2, 0x5d, 0x71, - 0x53, 0xb9, 0xb1, 0x69, 0xad, 0x66, 0x71, 0x48, 0xdc, 0x56, 0xf0, 0x24, 0x3c, 0x08, 0x0f, 0xc1, - 0x25, 0xf7, 0xdc, 0x20, 0x78, 0x11, 0x1c, 0xdb, 0x49, 0xd3, 0x1f, 0xb4, 0xf6, 0x22, 0x4a, 0xce, - 0xf9, 0xce, 0x77, 0x7e, 0xfc, 0x9d, 0x18, 0x5e, 0x76, 0x99, 0xe8, 0x0d, 0x3a, 0x8e, 0xc7, 0xaf, - 0xea, 0x97, 0x3d, 0x7a, 0xd9, 0x63, 0x41, 0x37, 0xfe, 0x40, 0xc5, 0x88, 0x47, 0xfd, 0xba, 0x10, - 0x41, 0x1d, 0x87, 0xac, 0xde, 0xc3, 0x01, 0xf1, 0x69, 0x94, 0xbe, 0x9d, 0x30, 0xe2, 0x82, 0xa3, - 0x35, 0x63, 0xd6, 0x9e, 0x2e, 0x92, 0x43, 0x3e, 0x9a, 0x57, 0x3b, 0x5c, 0x24, 0xbc, 0x13, 0xf1, - 0xbe, 0xac, 0xa8, 0x5f, 0x86, 0xf8, 0x6a, 0x11, 0xa2, 0x0a, 0xf5, 0xb8, 0x9f, 0x7d, 0x18, 0x72, - 0x73, 0x29, 0xb2, 0xcf, 0x23, 0x3c, 0xc2, 0x41, 0x9d, 0xd0, 0x21, 0xf3, 0xa8, 0x4e, 0x61, 0xff, - 0x2a, 0x80, 0x75, 0xac, 0x1c, 0x4d, 0x4f, 0xb0, 0x21, 0x16, 0x8c, 0x07, 0x2e, 0x8d, 0x43, 0x1e, - 0xc4, 0x14, 0x59, 0xb0, 0x16, 0xe2, 0x2f, 0x3e, 0xc7, 0xc4, 0x2a, 0xdc, 0x2f, 0x3c, 0x5a, 0x77, - 0x53, 0x13, 0xed, 0x40, 0x11, 0x87, 0x61, 0x9b, 0x11, 0xeb, 0x86, 0x04, 0xca, 0xee, 0x2d, 0x69, - 0x9d, 0x11, 0xf4, 0x06, 0x36, 0x09, 0x1f, 0x05, 0x3e, 0x0b, 0xfa, 0x6d, 0x1e, 0x26, 0xb9, 0xac, - 0x8a, 0xc4, 0x2b, 0x8d, 0x5d, 0xc7, 0x4c, 0x7d, 0x6c, 0xe0, 0x0b, 0x85, 0xba, 0x55, 0x32, 0x61, - 0xa3, 0x73, 0xd8, 0xc6, 0x59, 0x1f, 0xed, 0x2b, 0x2a, 0x30, 0xc1, 0x02, 0x5b, 0x77, 0x55, 0x92, - 0x3d, 0x27, 0x9b, 0x7f, 0xdc, 0xec, 0xb9, 0x89, 0x71, 0x11, 0x9e, 0xf1, 0xd9, 0x9b, 0xb0, 0xd1, - 0x12, 0x58, 0x0c, 0x62, 0x97, 0x7e, 0x1e, 0xd0, 0x58, 0xd8, 0x25, 0x28, 0x6a, 0x87, 0xed, 0xc0, - 0x4e, 0x33, 0x0c, 0x7d, 0xe6, 0x29, 0xc6, 0x19, 0xa1, 0x81, 0x60, 0x9f, 0x18, 0x8d, 0x72, 0xa3, - 0x15, 0x72, 0xa3, 0xd9, 0x5f, 0xa1, 0x92, 0x8b, 0xff, 0x4f, 0x54, 0x72, 0x62, 0x84, 0x7a, 0x9c, - 0xd0, 0xc8, 0x1c, 0x4c, 0x6a, 0xa2, 0x3d, 0x28, 0x7b, 0x3c, 0x18, 0xd2, 0x48, 0x48, 0x6c, 0x55, - 0x61, 0x63, 0x47, 0x82, 0x0e, 0xb1, 0xcf, 0x64, 0xd3, 0x3c, 0xb2, 0x6e, 0x6a, 0x34, 0x73, 0xd8, - 0x6f, 0x61, 0x4b, 0x6b, 0x74, 0x6d, 0x9b, 0x89, 0x5b, 0xea, 0x9b, 0x13, 0x46, 0x5a, 0xaa, 0xfb, - 0xa2, 0xce, 0xb0, 0x1c, 0x0f, 0xbd, 0x80, 0xaa, 0x59, 0x9b, 0xb6, 0x5e, 0x1b, 0xd5, 0x7a, 0xa5, - 0xb1, 0xe9, 0x18, 0xb7, 0xa3, 0xd3, 0x9e, 0xae, 0xb8, 0x1b, 0xc6, 0xa3, 0x1d, 0x47, 0x25, 0x95, - 0x50, 0x7e, 0xd9, 0x87, 0x00, 0xda, 0xf7, 0x9e, 0xc5, 0x02, 0x3d, 0x4e, 0x4e, 0x28, 0xb1, 0x62, - 0xd9, 0xc0, 0xaa, 0x4a, 0x95, 0xfe, 0x82, 0x3a, 0xca, 0x4d, 0xf1, 0x06, 0x85, 0xb5, 0x53, 0x0d, - 0xa1, 0x8f, 0x50, 0x32, 0x92, 0x53, 0xf4, 0x24, 0xdb, 0x25, 0x4a, 0x06, 0x5a, 0x11, 0x4a, 0x66, - 0x77, 0x58, 0x09, 0x5e, 0x7b, 0x30, 0x95, 0x7d, 0x76, 0xcb, 0x1b, 0xdf, 0x57, 0x01, 0xe5, 0xa4, - 0x3d, 0xc7, 0x01, 0xee, 0xca, 0x92, 0x27, 0x50, 0x7d, 0x47, 0x45, 0x5e, 0xf3, 0xfd, 0x2c, 0xd7, - 0xdc, 0xcd, 0xa9, 0xdd, 0x99, 0x87, 0xa3, 0x67, 0x50, 0x6d, 0x4d, 0xe6, 0x99, 0x1b, 0x57, 0x2b, - 0x39, 0xc9, 0x75, 0xd2, 0xf4, 0xfa, 0xe8, 0x35, 0xdc, 0x3e, 0xa6, 0x3e, 0x15, 0x74, 0x99, 0xe2, - 0x63, 0xfa, 0x21, 0x94, 0x65, 0xe3, 0x46, 0xee, 0x7b, 0x53, 0xf3, 0xe7, 0x18, 0xd3, 0x07, 0x8f, - 0x1e, 0x42, 0xb9, 0x95, 0x11, 0xa7, 0xd1, 0x5c, 0x81, 0x03, 0x58, 0xd7, 0xfd, 0x5d, 0x5f, 0x63, - 0x4c, 0xba, 0x00, 0x2b, 0xeb, 0x2a, 0x3e, 0xe1, 0xd1, 0x32, 0xb3, 0x6d, 0x4f, 0x15, 0x48, 0x16, - 0xa9, 0x21, 0xf5, 0x31, 0xdb, 0x91, 0x2a, 0xf6, 0x5c, 0x0d, 0xae, 0xff, 0x6f, 0xb4, 0x9b, 0x71, - 0x26, 0x6e, 0x80, 0xdc, 0xd4, 0xda, 0x7f, 0xb4, 0xf5, 0xe3, 0xcf, 0x7e, 0xe1, 0xa7, 0x7c, 0x7e, - 0xcb, 0xe7, 0xdb, 0xdf, 0xfd, 0x95, 0x4e, 0x51, 0x5d, 0x33, 0x07, 0xff, 0x02, 0x00, 0x00, 0xff, - 0xff, 0x2d, 0x69, 0xa7, 0x71, 0x48, 0x06, 0x00, 0x00, + // 642 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x41, 0x4f, 0x13, 0x41, + 0x14, 0xa6, 0xa2, 0xa5, 0x7d, 0x85, 0x82, 0x83, 0xe0, 0xda, 0x10, 0xa2, 0x7b, 0x30, 0x1a, 0xe3, + 0xd6, 0x14, 0x13, 0x34, 0xc6, 0x60, 0x09, 0x41, 0x48, 0x44, 0x92, 0x85, 0x93, 0x97, 0x66, 0xd8, + 0x79, 0xb6, 0x93, 0x2e, 0x3b, 0xeb, 0xee, 0xb4, 0x44, 0x7f, 0x89, 0x3f, 0xc9, 0xa3, 0x77, 0x2f, + 0x46, 0x7f, 0x83, 0x77, 0x67, 0x67, 0xa6, 0xcb, 0x52, 0x30, 0xb4, 0x1e, 0x26, 0xdd, 0xf7, 0xbe, + 0xf7, 0xbd, 0xf9, 0x66, 0xbe, 0xd7, 0x81, 0x97, 0x5d, 0x2e, 0x7b, 0x83, 0x13, 0x2f, 0x10, 0xa7, + 0xcd, 0xe3, 0x1e, 0x1e, 0xf7, 0x78, 0xd4, 0x4d, 0xdf, 0xa3, 0x3c, 0x13, 0x49, 0xbf, 0x29, 0x65, + 0xd4, 0xa4, 0x31, 0x6f, 0xf6, 0x68, 0xc4, 0x42, 0x4c, 0x46, 0xbf, 0x5e, 0x9c, 0x08, 0x29, 0xc8, + 0x9c, 0x0d, 0x1b, 0x4f, 0x27, 0xe9, 0xa1, 0x96, 0xe1, 0x35, 0x36, 0x27, 0x29, 0x3f, 0x49, 0x44, + 0x5f, 0xed, 0x68, 0x7e, 0x2c, 0xf1, 0xd5, 0x24, 0x44, 0x5d, 0x1a, 0x88, 0x30, 0xff, 0xb0, 0xe4, + 0xf6, 0x54, 0xe4, 0x50, 0x24, 0xf4, 0x8c, 0x46, 0x4d, 0x86, 0x43, 0x1e, 0xa0, 0x69, 0xe1, 0xfe, + 0x28, 0x81, 0xb3, 0xa3, 0x13, 0xed, 0x40, 0xf2, 0x21, 0x95, 0x5c, 0x44, 0x3e, 0xa6, 0xb1, 0x88, + 0x52, 0x24, 0x0e, 0xcc, 0xc5, 0xf4, 0x73, 0x28, 0x28, 0x73, 0x4a, 0xf7, 0x4b, 0x8f, 0xe6, 0xfd, + 0x51, 0x48, 0x56, 0xa0, 0x4c, 0xe3, 0xb8, 0xc3, 0x99, 0x73, 0x43, 0x01, 0x55, 0xff, 0x96, 0x8a, + 0xf6, 0x19, 0xd9, 0x82, 0x45, 0x26, 0xce, 0xa2, 0x90, 0x47, 0xfd, 0x8e, 0x88, 0xb3, 0x5e, 0x4e, + 0x4d, 0xe1, 0xb5, 0xd6, 0xaa, 0x67, 0x4f, 0xbd, 0x63, 0xe1, 0x43, 0x8d, 0xfa, 0x75, 0x76, 0x21, + 0x26, 0x07, 0xb0, 0x4c, 0x73, 0x1d, 0x9d, 0x53, 0x94, 0x94, 0x51, 0x49, 0x9d, 0xbb, 0xba, 0xc9, + 0x9a, 0x97, 0x9f, 0xff, 0x5c, 0xec, 0x81, 0xad, 0xf1, 0x09, 0xbd, 0x94, 0x73, 0x17, 0x61, 0xe1, + 0x48, 0x52, 0x39, 0x48, 0x7d, 0xfc, 0x34, 0xc0, 0x54, 0xba, 0x15, 0x28, 0x9b, 0x84, 0xeb, 0xc1, + 0x4a, 0x3b, 0x8e, 0x43, 0x1e, 0x68, 0xc6, 0x3e, 0xc3, 0x48, 0xf2, 0x8f, 0x1c, 0x93, 0xc2, 0xd1, + 0x4a, 0x85, 0xa3, 0xb9, 0x5f, 0xa0, 0x56, 0xa8, 0xff, 0x47, 0x55, 0x76, 0x63, 0x0c, 0x03, 0xc1, + 0x30, 0xb1, 0x17, 0x33, 0x0a, 0xc9, 0x1a, 0x54, 0x03, 0x11, 0x0d, 0x31, 0x91, 0x0a, 0x9b, 0xd5, + 0xd8, 0x79, 0x22, 0x43, 0x87, 0x34, 0xe4, 0x4a, 0xb4, 0x48, 0x9c, 0x9b, 0x06, 0xcd, 0x13, 0xee, + 0x1b, 0x58, 0x32, 0x1e, 0x5d, 0x2b, 0x33, 0x4b, 0x2b, 0x7f, 0x0b, 0xc6, 0xa8, 0x48, 0xab, 0x2f, + 0x9b, 0x0e, 0xd3, 0xf1, 0xc8, 0x0b, 0xa8, 0xdb, 0xb1, 0xe9, 0x98, 0xb1, 0xd1, 0xd2, 0x6b, 0xad, + 0x45, 0xcf, 0xa6, 0x3d, 0xd3, 0x76, 0x6f, 0xc6, 0x5f, 0xb0, 0x19, 0x93, 0xd8, 0xae, 0xe8, 0x86, + 0xea, 0xcb, 0xdd, 0x04, 0x30, 0xb9, 0x77, 0x3c, 0x95, 0xe4, 0x71, 0x76, 0x43, 0x59, 0x94, 0x2a, + 0x01, 0xb3, 0xba, 0xd5, 0xe8, 0x2f, 0x68, 0xaa, 0xfc, 0x11, 0xde, 0x42, 0x98, 0xdb, 0x33, 0x10, + 0xf9, 0x00, 0x15, 0x6b, 0x39, 0x92, 0x27, 0xf9, 0x2c, 0x21, 0x1b, 0x18, 0x47, 0x90, 0x5d, 0x9e, + 0x61, 0x6d, 0x78, 0xe3, 0xc1, 0x58, 0xf7, 0xcb, 0x53, 0xde, 0xfa, 0x33, 0x0b, 0xa4, 0x60, 0xed, + 0x01, 0x8d, 0x68, 0x57, 0x6d, 0xb9, 0x05, 0xcb, 0x3e, 0x76, 0x95, 0x64, 0x4c, 0x8a, 0xc6, 0xaf, + 0xe7, 0x0d, 0xaf, 0x1c, 0x9f, 0x46, 0xc5, 0xcb, 0x5e, 0x85, 0x76, 0xd0, 0x27, 0xbb, 0x50, 0x7f, + 0x8b, 0x72, 0x1a, 0xee, 0x9d, 0xab, 0x70, 0xf2, 0x0c, 0xea, 0x47, 0x17, 0xfb, 0x5c, 0x59, 0x57, + 0xd8, 0xf9, 0x35, 0xdc, 0xde, 0xc1, 0x10, 0x25, 0xfe, 0x9f, 0xf0, 0x4d, 0xa8, 0x2a, 0xe1, 0x76, + 0x5e, 0xee, 0x8d, 0x5d, 0x60, 0x81, 0x31, 0xee, 0x1c, 0x79, 0x08, 0xd5, 0xa3, 0x9c, 0x38, 0x8e, + 0x16, 0x36, 0xd8, 0x80, 0x79, 0xa3, 0xef, 0xfa, 0x3d, 0xce, 0x49, 0x87, 0xe0, 0xe4, 0xaa, 0xd2, + 0x5d, 0x31, 0x95, 0x29, 0xcb, 0x63, 0x1b, 0x64, 0x93, 0xd8, 0x52, 0xfe, 0xd8, 0xf1, 0x1a, 0x59, + 0xfe, 0x5c, 0x1f, 0xdc, 0x3c, 0x10, 0x64, 0x35, 0xe7, 0x5c, 0x78, 0x42, 0x0a, 0xa7, 0x36, 0xf9, + 0xed, 0xa5, 0x6f, 0xbf, 0xd6, 0x4b, 0xdf, 0xd5, 0xfa, 0xa9, 0xd6, 0xd7, 0xdf, 0xeb, 0x33, 0x27, + 0x65, 0xfd, 0x4e, 0x6d, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x5f, 0x1e, 0xbd, 0x89, 0x06, + 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 89e961983..0777c8581 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -57,6 +57,7 @@ message DeviceList { } service ApplicationManager { + rpc RegisterApplication(ApplicationIdentifier) returns (api.Ack); rpc GetApplication(ApplicationIdentifier) returns (Application); rpc SetApplication(Application) returns (api.Ack); rpc DeleteApplication(ApplicationIdentifier) returns (api.Ack); diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index 3daca1155..6328e83c6 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -61,6 +61,14 @@ func (h *ManagerClient) SetApplication(in *Application) error { return err } +// RegisterApplication registers an application on the Handler +func (h *ManagerClient) RegisterApplication(appID string) error { + h.RLock() + defer h.RUnlock() + _, err := h.applicationManagerClient.RegisterApplication(h.context, &ApplicationIdentifier{AppId: appID}) + return err +} + // DeleteApplication deletes an application and all its devices from the Handler func (h *ManagerClient) DeleteApplication(appID string) error { h.RLock() diff --git a/cmd/broker.go b/cmd/broker.go index 1eb77a67b..77cf1ec20 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -53,7 +53,10 @@ var brokerCmd = &cobra.Command{ connectRedis(client) // Component - component := core.NewComponent(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) + component, err := core.NewComponent(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } component.Identity.Metadata = []*pb_discovery.Metadata{} for _, prefixString := range viper.GetStringSlice("broker.prefix") { @@ -73,7 +76,7 @@ var brokerCmd = &cobra.Command{ viper.GetString("broker.networkserver-address"), time.Duration(viper.GetInt("broker.deduplication-delay"))*time.Millisecond, ) - err := broker.Init(component) + err = broker.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize broker") } diff --git a/cmd/discovery.go b/cmd/discovery.go index 53d28437d..b4ba55425 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -43,11 +43,14 @@ var discoveryCmd = &cobra.Command{ }) // Component - component := core.NewComponent(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) + component, err := core.NewComponent(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } // Discovery Server discovery := discovery.NewRedisDiscovery(client) - err := discovery.Init(component) + err = discovery.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize discovery") } diff --git a/cmd/handler.go b/cmd/handler.go index 0185195f4..c00c58cd8 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -47,7 +47,10 @@ var handlerCmd = &cobra.Command{ connectRedis(client) // Component - component := core.NewComponent(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) + component, err := core.NewComponent(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } // Broker handler := handler.NewRedisHandler( @@ -57,7 +60,7 @@ var handlerCmd = &cobra.Command{ viper.GetString("handler.mqtt-password"), viper.GetString("handler.mqtt-broker"), ) - err := handler.Init(component) + err = handler.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize handler") } diff --git a/cmd/networkserver.go b/cmd/networkserver.go index a42422f77..7c4cbbc01 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -48,7 +48,10 @@ var networkserverCmd = &cobra.Command{ connectRedis(client) // Component - component := core.NewComponent(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) + component, err := core.NewComponent(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } // networkserver Server networkserver := networkserver.NewRedisNetworkServer(client, viper.GetInt("networkserver.net-id")) diff --git a/cmd/router.go b/cmd/router.go index a9ffb15b1..483a46299 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -34,11 +34,14 @@ var routerCmd = &cobra.Command{ ctx.Info("Starting") // Component - component := core.NewComponent(ctx, "router", fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port"))) + component, err := core.NewComponent(ctx, "router", fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port"))) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize component") + } // Router router := router.NewRouter() - err := router.Init(component) + err = router.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize router") } diff --git a/core/broker/activation.go b/core/broker/activation.go index 46e98c5e2..4b34b289d 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -72,7 +72,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } // Send Activate to NS - deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(), deduplicatedActivationRequest) + deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(false), deduplicatedActivationRequest) if err != nil { return nil, err } @@ -108,12 +108,12 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } client := pb_handler.NewHandlerClient(conn) var handlerResponse *pb_handler.DeviceActivationResponse - handlerResponse, err = client.Activate(b.Component.GetContext(), deduplicatedActivationRequest) + handlerResponse, err = client.Activate(b.Component.GetContext(false), deduplicatedActivationRequest) if err != nil { return nil, err } - handlerResponse, err = b.ns.Activate(b.Component.GetContext(), handlerResponse) + handlerResponse, err = b.ns.Activate(b.Component.GetContext(false), handlerResponse) if err != nil { return nil, err } diff --git a/core/broker/downlink.go b/core/broker/downlink.go index cc3fdbfe6..51da5a9f1 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -30,7 +30,7 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { } }() - downlink, err = b.ns.Downlink(b.Component.GetContext(), downlink) + downlink, err = b.ns.Downlink(b.Component.GetContext(false), downlink) if err != nil { return err } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 2b3b20002..a0cbd2a2e 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -69,7 +69,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { devAddr := types.DevAddr(macPayload.FHDR.DevAddr) ctx = ctx.WithField("DevAddr", devAddr) var getDevicesResp *networkserver.DevicesResponse - getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(), &networkserver.DevicesRequest{ + getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(false), &networkserver.DevicesRequest{ DevAddr: &devAddr, FCnt: macPayload.FHDR.FCnt, }) @@ -156,7 +156,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } // Pass Uplink through NS - deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(), deduplicatedUplink) + deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(false), deduplicatedUplink) if err != nil { return err } diff --git a/core/component.go b/core/component.go index e56701091..7e87340f4 100644 --- a/core/component.go +++ b/core/component.go @@ -34,7 +34,7 @@ type ManagementInterface interface { } // NewComponent creates a new Component -func NewComponent(ctx log.Interface, serviceName string, announcedAddress string) *Component { +func NewComponent(ctx log.Interface, serviceName string, announcedAddress string) (*Component, error) { go func() { memstats := new(runtime.MemStats) for range time.Tick(time.Minute) { @@ -46,6 +46,15 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } }() + var discovery pb_discovery.DiscoveryClient + if serviceName != "discovery" { + discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock())...) + if err != nil { + return nil, err + } + discovery = pb_discovery.NewDiscoveryClient(discoveryConn) + } + return &Component{ Ctx: ctx, Identity: &pb_discovery.Announcement{ @@ -54,19 +63,19 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string ServiceName: serviceName, NetAddress: announcedAddress, }, - AccessToken: viper.GetString("token"), - DiscoveryServer: viper.GetString("discovery-server"), + AccessToken: viper.GetString("token"), + Discovery: discovery, TokenKeyProvider: tokenkey.NewHTTPProvider( fmt.Sprintf("%s/key", viper.GetString("auth-server")), viper.GetString("oauth2-keyfile"), ), - } + }, nil } // Component contains the common attributes for all TTN components type Component struct { Identity *pb_discovery.Announcement - DiscoveryServer string + Discovery pb_discovery.DiscoveryClient Ctx log.Interface AccessToken string TokenKeyProvider tokenkey.Provider @@ -74,21 +83,11 @@ type Component struct { // Announce the component to TTN discovery func (c *Component) Announce() error { - if c.DiscoveryServer == "" { - return errors.New("ttn: No discovery server configured") - } - if c.Identity.Id == "" { return errors.New("ttn: No ID configured") } - conn, err := grpc.Dial(c.DiscoveryServer, append(api.DialOptions, grpc.WithBlock())...) - if err != nil { - return err - } - defer conn.Close() - client := pb_discovery.NewDiscoveryClient(conn) - _, err = client.Announce(c.GetContext(), c.Identity) + _, err := c.Discovery.Announce(c.GetContext(true), c.Identity) if err != nil { return fmt.Errorf("ttn: Failed to announce this component to TTN discovery: %s", err.Error()) } @@ -239,14 +238,18 @@ func (c *Component) ServerOptions() []grpc.ServerOption { } // GetContext returns a context for outgoing RPC requests -func (c *Component) GetContext() context.Context { - var id, token, netAddress string +func (c *Component) GetContext(includeAccessToken bool) context.Context { + var serviceName, id, token, netAddress string if c.Identity != nil { + serviceName = c.Identity.ServiceName id = c.Identity.Id - token = c.AccessToken + if includeAccessToken { + token = c.AccessToken + } netAddress = c.Identity.NetAddress } md := metadata.Pairs( + "service-name", serviceName, "id", id, "token", token, "net-address", netAddress, diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 4bb405ade..964a30885 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -7,9 +7,6 @@ import ( "sync" "time" - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" @@ -37,14 +34,7 @@ func NewBrokerDiscovery(component *core.Component) BrokerDiscovery { } func (d *brokerDiscovery) refreshCache() error { - // Connect to the server - conn, err := grpc.Dial(d.component.DiscoveryServer, api.DialOptions...) - if err != nil { - return err - } - defer conn.Close() - client := pb.NewDiscoveryClient(conn) - res, err := client.GetAll(d.component.GetContext(), &pb.GetAllRequest{ServiceName: "broker"}) + res, err := d.component.Discovery.GetAll(d.component.GetContext(false), &pb.GetAllRequest{ServiceName: "broker"}) if err != nil { return err } diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index 268fe660f..472238dec 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -8,6 +8,9 @@ import ( "testing" "time" + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" @@ -15,7 +18,12 @@ import ( ) func buildTestBrokerDiscoveryClient(port uint) *brokerDiscovery { - discovery := NewBrokerDiscovery(&core.Component{DiscoveryServer: fmt.Sprintf("localhost:%d", port)}).(*brokerDiscovery) + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + if err != nil { + panic(err) + } + client := pb.NewDiscoveryClient(conn) + discovery := NewBrokerDiscovery(&core.Component{Discovery: client}).(*brokerDiscovery) discovery.refreshCache() return discovery } @@ -89,9 +97,15 @@ func TestBrokerDiscoveryCache(t *testing.T) { Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00}}}, } + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + if err != nil { + panic(err) + } + client := pb.NewDiscoveryClient(conn) + d := &brokerDiscovery{ component: &core.Component{ - DiscoveryServer: fmt.Sprintf("localhost:%d", port), + Discovery: client, }, cacheValidUntil: time.Now().Add(-1 * time.Minute), cache: []*pb.Announcement{broker}, diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go index 49b4053db..73a931e22 100644 --- a/core/discovery/handler_discovery.go +++ b/core/discovery/handler_discovery.go @@ -8,9 +8,6 @@ import ( "sync" "time" - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" ) @@ -41,14 +38,7 @@ func NewHandlerDiscovery(component *core.Component) HandlerDiscovery { } func (d *handlerDiscovery) refreshCache() error { - // Connect to the server - conn, err := grpc.Dial(d.component.DiscoveryServer, api.DialOptions...) - if err != nil { - return err - } - defer conn.Close() - client := pb.NewDiscoveryClient(conn) - res, err := client.GetAll(d.component.GetContext(), &pb.GetAllRequest{ServiceName: "handler"}) + res, err := d.component.Discovery.GetAll(d.component.GetContext(false), &pb.GetAllRequest{ServiceName: "handler"}) if err != nil { return err } diff --git a/core/discovery/handler_discovery_test.go b/core/discovery/handler_discovery_test.go index 1f570c8ab..bd75c4ef5 100644 --- a/core/discovery/handler_discovery_test.go +++ b/core/discovery/handler_discovery_test.go @@ -8,13 +8,21 @@ import ( "testing" "time" + "google.golang.org/grpc" + + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" . "github.com/smartystreets/assertions" ) func buildTestHandlerDiscoveryClient(port uint) *handlerDiscovery { - discovery := NewHandlerDiscovery(&core.Component{DiscoveryServer: fmt.Sprintf("localhost:%d", port)}).(*handlerDiscovery) + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + if err != nil { + panic(err) + } + client := pb.NewDiscoveryClient(conn) + discovery := NewHandlerDiscovery(&core.Component{Discovery: client}).(*handlerDiscovery) discovery.refreshCache() return discovery } @@ -80,9 +88,15 @@ func TestHandlerDiscoveryCache(t *testing.T) { Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}}, } + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + if err != nil { + panic(err) + } + client := pb.NewDiscoveryClient(conn) + d := &handlerDiscovery{ component: &core.Component{ - DiscoveryServer: fmt.Sprintf("localhost:%d", port), + Discovery: client, }, cacheValidUntil: time.Now().Add(-1 * time.Minute), cache: []*pb.Announcement{handler}, diff --git a/core/handler/handler.go b/core/handler/handler.go index 9da6a80a5..7304e3284 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -5,14 +5,12 @@ package handler import ( "fmt" - "sync" "time" "google.golang.org/grpc" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" @@ -47,10 +45,8 @@ func NewRedisHandler(client *redis.Client, ttnBrokerAddr string, mqttUsername st type handler struct { *core.Component - devices device.Store - applications application.Store - applicationIds []string // this is a cache - applicationIdsLock sync.RWMutex + devices device.Store + applications application.Store ttnBrokerAddr string ttnBrokerConn *grpc.ClientConn @@ -69,18 +65,6 @@ type handler struct { mqttActivation chan *mqtt.Activation } -func (h *handler) announce() error { - h.applicationIdsLock.RLock() - defer h.applicationIdsLock.RUnlock() - h.Component.Identity.Metadata = []*pb_discovery.Metadata{} - for _, id := range h.applicationIds { - h.Identity.Metadata = append(h.Component.Identity.Metadata, &pb_discovery.Metadata{ - Key: pb_discovery.Metadata_APP_ID, Value: []byte(id), - }) - } - return h.Component.Announce() -} - func (h *handler) Init(c *core.Component) error { h.Component = c err := h.Component.UpdateTokenKey() @@ -92,16 +76,7 @@ func (h *handler) Init(c *core.Component) error { AppID: "htdvisser", }) - apps, err := h.applications.List() - if err != nil { - return err - } - h.applicationIdsLock.Lock() - for _, app := range apps { - h.applicationIds = append(h.applicationIds, app.AppID) - } - h.applicationIdsLock.Unlock() - err = h.announce() + err = h.Announce() if err != nil { return err } @@ -133,7 +108,7 @@ func (h *handler) associateBroker() error { h.ttnBrokerManager = pb_broker.NewBrokerManagerClient(conn) h.ttnDeviceManager = pb_lorawan.NewDeviceManagerClient(conn) - ctx := h.GetContext() + ctx := h.GetContext(false) h.downlink = make(chan *pb_broker.DownlinkMessage) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 6464572ee..65eba9188 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -8,6 +8,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/handler/application" @@ -241,11 +242,51 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI }, nil } -func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*api.Ack, error) { +func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*api.Ack, error) { app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) if err != nil && err != application.ErrNotFound { return nil, err } + if app != nil { + return nil, errf(codes.InvalidArgument, "Application already registered") + } + + err = h.applications.Set(&application.Application{ + AppID: in.AppId, + }) + if err != nil { + return nil, err + } + + _, err = h.Discovery.AddMetadata(ctx, &pb_discovery.MetadataRequest{ + ServiceName: h.Identity.ServiceName, + Id: h.Identity.Id, + Metadata: &pb_discovery.Metadata{ + Key: pb_discovery.Metadata_APP_ID, + Value: []byte(in.AppId), + }, + }) + if err != nil { + h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Discovery") + } + + _, err = h.ttnBrokerManager.RegisterApplicationHandler(ctx, &pb_broker.ApplicationHandlerRegistration{ + AppId: in.AppId, + HandlerId: h.Identity.Id, + }) + if err != nil { + h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") + } + + return &api.Ack{}, nil + +} + +func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*api.Ack, error) { + _, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) + if err != nil { + return nil, err + } if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application") @@ -261,34 +302,6 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) return nil, err } - if app == nil { - // Add this application ID to the cache if needed - h.applicationIdsLock.Lock() - var alreadyInCache bool - for _, id := range h.applicationIds { - if id == in.AppId { - alreadyInCache = true - break - } - } - if !alreadyInCache { - h.applicationIds = append(h.applicationIds, in.AppId) - } - h.applicationIdsLock.Unlock() - - // If we had to add it, we also have to announce it to the Discovery and Broker - if !alreadyInCache { - h.announce() - _, err := h.ttnBrokerManager.RegisterApplicationHandler(ctx, &pb_broker.ApplicationHandlerRegistration{ - AppId: in.AppId, - HandlerId: h.Identity.Id, - }) - if err != nil { - h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") - } - } - } - return &api.Ack{}, nil } @@ -320,16 +333,17 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati return nil, err } - // Remove this application ID from the cache - h.applicationIdsLock.Lock() - newApplicationIDList := make([]string, 0, len(h.applicationIds)) - for _, id := range h.applicationIds { - if id != in.AppId { - newApplicationIDList = append(newApplicationIDList, id) - } + _, err = h.Discovery.DeleteMetadata(ctx, &pb_discovery.MetadataRequest{ + ServiceName: h.Identity.ServiceName, + Id: h.Identity.Id, + Metadata: &pb_discovery.Metadata{ + Key: pb_discovery.Metadata_APP_ID, + Value: []byte(in.AppId), + }, + }) + if err != nil { + h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not unregister Application from Discovery") } - h.applicationIds = newApplicationIDList - h.applicationIdsLock.Unlock() return &api.Ack{}, nil } diff --git a/core/router/activation.go b/core/router/activation.go index 5ea24f42b..4d9dac69c 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -104,7 +104,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Do async request wg.Add(1) go func() { - res, err := broker.client.Activate(r.Component.GetContext(), request) + res, err := broker.client.Activate(r.Component.GetContext(false), request) if err == nil && res != nil { responses <- res } diff --git a/core/router/router.go b/core/router/router.go index 824f3f461..173126394 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -129,7 +129,7 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { } client := pb_broker.NewBrokerClient(conn) - association, err := client.Associate(r.Component.GetContext()) + association, err := client.Associate(r.Component.GetContext(false)) if err != nil { return nil, err } diff --git a/ttnctl/cmd/applications_register.go b/ttnctl/cmd/applications_register.go new file mode 100644 index 000000000..a7bba65e7 --- /dev/null +++ b/ttnctl/cmd/applications_register.go @@ -0,0 +1,49 @@ +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// applicationsRegisterCmd represents the `applications register` command +var applicationsRegisterCmd = &cobra.Command{ + Use: "register", + Short: "Register this application with the handler", + Long: `ttnctl register can be used to register this application with the handler.`, + Run: func(cmd *cobra.Command, args []string) { + + auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) + if err != nil { + ctx.WithError(err).Fatal("Failed to load authentication") + } + if auth == nil { + ctx.Fatal("No authentication found, please login") + } + + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") + } + + manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler client") + } + + err = manager.RegisterApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not register application") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Registered application") + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsRegisterCmd) +} From 73471510e85b67dd143e083ce1a4f73f241b920d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 11:18:26 +0100 Subject: [PATCH 1600/2266] Fix validation errors --- api/broker/validation.go | 9 +++------ core/broker/uplink.go | 2 ++ 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/api/broker/validation.go b/api/broker/validation.go index 6e15ba9c0..533e36f47 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -21,12 +21,6 @@ func (m *DownlinkOption) Validate() bool { // Validate implements the api.Validator interface func (m *UplinkMessage) Validate() bool { - if m.DevEui == nil || m.DevEui.IsEmpty() { - return false - } - if m.AppEui == nil || m.AppEui.IsEmpty() { - return false - } if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { return false } @@ -58,6 +52,9 @@ func (m *DeduplicatedUplinkMessage) Validate() bool { if m.AppEui == nil || m.AppEui.IsEmpty() { return false } + if m.DevId == "" || !api.ValidID(m.DevId) { + return false + } if m.AppId == "" || !api.ValidID(m.AppId) { return false } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index a0cbd2a2e..4b5065761 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -148,7 +148,9 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { deduplicatedUplink := &pb.DeduplicatedUplinkMessage{ Payload: base.Payload, DevEui: device.DevEui, + DevId: device.DevId, AppEui: device.AppEui, + AppId: device.AppId, ProtocolMetadata: base.ProtocolMetadata, GatewayMetadata: gatewayMetadata, ServerTime: time.UnixNano(), From 6a741b579dc975f4be64394fa4a649afa98db9d0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 20 Jul 2016 12:07:48 +0100 Subject: [PATCH 1601/2266] Log durations of handling uplink/activation/downlink --- core/broker/activation.go | 5 +++-- core/broker/downlink.go | 6 ++++-- core/broker/uplink.go | 5 +++-- core/component.go | 6 +++--- core/handler/activation.go | 4 ++++ core/handler/downlink.go | 11 ++++++++--- core/handler/uplink.go | 3 +++ core/router/activation.go | 4 +++- core/router/gateway/schedule.go | 2 +- core/router/gateway_status.go | 3 +++ core/router/uplink.go | 4 +++- 11 files changed, 38 insertions(+), 15 deletions(-) diff --git a/core/broker/activation.go b/core/broker/activation.go index 4b34b289d..c5001bbb6 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -26,9 +26,12 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D "DevEUI": *activation.DevEui, }) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle activation") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() @@ -123,8 +126,6 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D DownlinkOption: handlerResponse.DownlinkOption, } - ctx.Debug("Successful Activation") - return deviceActivationResponse, nil } diff --git a/core/broker/downlink.go b/core/broker/downlink.go index 51da5a9f1..0eb9f7138 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -6,6 +6,7 @@ package broker import ( "errors" "strings" + "time" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/apex/log" @@ -24,9 +25,12 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { "AppEUI": *downlink.AppEui, }) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle downlink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled downlink") } }() @@ -49,8 +53,6 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { return err } - ctx.Debug("Send Downlink") - router <- downlink return nil diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 4b5065761..79823f854 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -32,9 +32,12 @@ var ( func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { ctx := b.Ctx.WithField("GatewayEUI", *uplink.GatewayMetadata.GatewayEui) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle uplink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") } }() @@ -183,8 +186,6 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } - ctx.Debug("Forward Uplink") - handler <- deduplicatedUplink return nil diff --git a/core/component.go b/core/component.go index 7e87340f4..115d643ae 100644 --- a/core/component.go +++ b/core/component.go @@ -200,11 +200,11 @@ func (c *Component) ServerOptions() []grpc.ServerOption { }) t := time.Now() iface, err := handler(ctx, req) - logCtx = logCtx.WithField("Time", time.Now().Sub(t)) + logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) if err != nil { logCtx.WithError(err).Warn("Could not handle Request") } else { - logCtx.Debug("Handled Request") + logCtx.Info("Handled request") } return iface, err } @@ -227,7 +227,7 @@ func (c *Component) ServerOptions() []grpc.ServerOption { "CallerID": peerID, "CallerIP": peerAddr, "Method": info.FullMethod, - }).Debug("Start Stream") + }).Info("Start stream") return handler(srv, stream) } diff --git a/core/handler/activation.go b/core/handler/activation.go index 8fa1b18ed..0b9556460 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -5,6 +5,7 @@ package handler import ( "errors" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -33,9 +34,12 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv "DevID": activation.DevId, }) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle activation") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 47b687752..22b61b24e 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -4,7 +4,10 @@ package handler import ( + "time" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) @@ -15,13 +18,17 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { "AppID": appDownlink.AppID, }) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not enqueue downlink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Debug("Enqueued downlink") } }() - dev, err := h.devices.Get(appDownlink.AppID, appDownlink.DevID) + var dev *device.Device + dev, err = h.devices.Get(appDownlink.AppID, appDownlink.DevID) if err != nil { return err } @@ -31,8 +38,6 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { return err } - ctx.Debug("Enqueue Downlink") - return nil } diff --git a/core/handler/uplink.go b/core/handler/uplink.go index 1fddbd80b..bb57fbc8c 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -20,9 +20,12 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro "DevID": uplink.DevId, }) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle uplink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") } }() diff --git a/core/router/activation.go b/core/router/activation.go index 4d9dac69c..d15c1f569 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -23,9 +23,12 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De "DevEUI": *activation.DevEui, }) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle activation") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() @@ -90,7 +93,6 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De } ctx = ctx.WithField("NumBrokers", len(brokers)) - ctx.Debug("Forward Activation") // Forward to all brokers and collect responses var wg sync.WaitGroup diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 7b6f2cf0d..f9a86ccd5 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -176,7 +176,7 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro // Schedule transmission before the Deadline go func() { waitTime := item.deadlineAt.Sub(time.Now()) - ctx.WithField("Remaining", waitTime).Debug("Schedule Downlink") + ctx.WithField("Remaining", waitTime).Info("Scheduled downlink") <-time.After(waitTime) if s.downlink != nil { s.downlink <- item.payload diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index a0d1238ef..48321b870 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -13,9 +13,12 @@ import ( func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error { ctx := r.Ctx.WithField("GatewayEUI", gatewayEUI) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle gateway status") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled gateway status") } }() diff --git a/core/router/uplink.go b/core/router/uplink.go index c902e2073..0d5d36df5 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -18,9 +18,12 @@ import ( func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error { ctx := r.Ctx.WithField("GatewayEUI", gatewayEUI) var err error + start := time.Now() defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle uplink") + } else { + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") } }() @@ -93,7 +96,6 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess } ctx = ctx.WithField("NumBrokers", len(brokers)) - ctx.Debug("Forward Uplink") // Forward to all brokers for _, broker := range brokers { From 6d3d425df6af64658cbdfec10b867ec77cb39f4d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 20 Jul 2016 13:27:34 +0200 Subject: [PATCH 1602/2266] lint --- core/account/types.go | 1 + core/account/util/http.go | 2 ++ core/account/util/http_test.go | 2 +- core/types/access_keys.go | 3 +++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/account/types.go b/core/account/types.go index 97bce4876..eff173bb6 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -25,6 +25,7 @@ type Collaborator struct { Rights []types.Right `json:"rights" valid:"required"` } +// HasRight checks if the collaborator has a specific right func (c *Collaborator) HasRight(right types.Right) bool { for _, r := range c.Rights { if r == right { diff --git a/core/account/util/http.go b/core/account/util/http.go index 6e7541621..f1fed38ac 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -15,6 +15,8 @@ import ( ) var ( + // MaxRedirects specifies the maximum number of redirects an HTTP + // request should be able to make MaxRedirects = 5 ) diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index 95c6a5993..9905ae751 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -19,7 +19,7 @@ var ( ) type OKResp struct { - OK string `json:token` + OK string `json:"token"` } type FooResp struct { diff --git a/core/types/access_keys.go b/core/types/access_keys.go index e4c3977c9..6e47182d3 100644 --- a/core/types/access_keys.go +++ b/core/types/access_keys.go @@ -3,8 +3,11 @@ package types +// Right is the type that represents a right to do something on TTN type Right string +// AccessKey is an access key that gives someone the right to do something with +// an application type AccessKey struct { // The friendly name of the access key Name string `json:"name"` From 27eafba16bdf6def1005edd3df06338db7fb2f3f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 21 Jul 2016 16:16:56 +0100 Subject: [PATCH 1603/2266] Fix gateway schedule test --- core/router/gateway/schedule.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index f9a86ccd5..828da7039 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -40,6 +40,7 @@ func NewSchedule(ctx log.Interface) Schedule { } go func() { for { + <-time.After(10 * time.Second) s.RLock() numItems := len(s.items) s.RUnlock() @@ -53,7 +54,6 @@ func NewSchedule(ctx log.Interface) Schedule { } s.Unlock() } - <-time.After(10 * time.Second) } }() return s From b66194e1186a368490e6cebde472dff6594e8a6e Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 21 Jul 2016 17:50:39 +0200 Subject: [PATCH 1604/2266] add validate function that can validate a whole range of types --- core/account/util/validate.go | 42 +++++++++++++++++ core/account/util/validate_test.go | 72 ++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) create mode 100644 core/account/util/validate.go create mode 100644 core/account/util/validate_test.go diff --git a/core/account/util/validate.go b/core/account/util/validate.go new file mode 100644 index 000000000..82dd6b11d --- /dev/null +++ b/core/account/util/validate.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + "reflect" + + "github.com/asaskevich/govalidator" +) + +func validateSlice(val interface{}) error { + s := reflect.ValueOf(val) + + for i := 0; i < s.Len(); i++ { + if err := Validate(s.Index(i).Interface()); err != nil { + return fmt.Errorf("In slice at index %v: %s", i, err) + } + } + return nil +} + +func Validate(val interface{}) error { + switch reflect.TypeOf(val).Kind() { + case reflect.Slice: + // validate a slice + err := validateSlice(val) + return err + case reflect.Ptr: + // try to get the valu from the ptr + v := reflect.ValueOf(val).Elem() + if v.CanInterface() { + return Validate(v.Interface()) + } + case reflect.Struct: + // when it is a struct jsut validate + _, err := govalidator.ValidateStruct(val) + return err + } + return nil +} diff --git a/core/account/util/validate_test.go b/core/account/util/validate_test.go new file mode 100644 index 000000000..1c9b73140 --- /dev/null +++ b/core/account/util/validate_test.go @@ -0,0 +1,72 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +type Foo struct { + Foo string `valid:"required"` +} + +var ( + validFoo = Foo{ + Foo: "Hi", + } + + invalidFoo = Foo{} + + validFoos = []Foo{ + validFoo, + validFoo, + } + + invalidFoos = []Foo{ + validFoo, + invalidFoo, + } +) + +func TestValidateStruct(t *testing.T) { + a := New(t) + + err := Validate(validFoo) + a.So(err, ShouldBeNil) + + err = Validate(invalidFoo) + a.So(err, ShouldNotBeNil) +} + +func TestValidateSlice(t *testing.T) { + a := New(t) + + err := Validate(validFoos) + a.So(err, ShouldBeNil) + + err = Validate(invalidFoos) + a.So(err, ShouldNotBeNil) +} + +func TestValidatePtr(t *testing.T) { + a := New(t) + + err := Validate(&validFoo) + a.So(err, ShouldBeNil) + + err = Validate(&invalidFoo) + a.So(err, ShouldNotBeNil) +} + +func TestValidatePtrSlice(t *testing.T) { + a := New(t) + + err := Validate(&validFoos) + a.So(err, ShouldBeNil) + + err = Validate(&invalidFoos) + a.So(err, ShouldNotBeNil) +} From b4810157c93ce7fdc00b1d06063fc1b115a2a55b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 21 Jul 2016 17:51:01 +0200 Subject: [PATCH 1605/2266] use more general validate function --- core/account/util/http.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index f1fed38ac..691e95823 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -10,8 +10,6 @@ import ( "fmt" "io" "net/http" - - "github.com/asaskevich/govalidator" ) var ( @@ -63,7 +61,7 @@ func performRequest(server, accessToken, method, URI string, body, res interface if body != nil { // body is not nil, so serialize it and pass it in the request - if _, err = govalidator.ValidateStruct(body); err != nil { + if err = Validate(body); err != nil { return fmt.Errorf("Got an illegal request body: %s", err) } @@ -118,7 +116,7 @@ func performRequest(server, accessToken, method, URI string, body, res interface return err } - if _, err := govalidator.ValidateStruct(res); err != nil { + if err := Validate(res); err != nil { return fmt.Errorf("Got an illegal response from server: %s", err) } } From 88d09857b21a78252c4ffeebc1c663e26c43b991 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 21 Jul 2016 18:21:51 +0200 Subject: [PATCH 1606/2266] set content-type --- core/account/util/http.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/account/util/http.go b/core/account/util/http.go index 691e95823..371fb8df9 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -39,6 +39,7 @@ func checkRedirect(req *http.Request, via []*http.Request) error { // use the same headers as before req.Header.Set("Authorization", via[len(via)-1].Header.Get("Authorization")) req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") return nil } @@ -52,6 +53,7 @@ func newRequest(server string, accessToken string, method string, URI string, bo req.Header.Set("Authorization", fmt.Sprintf("bearer %s", accessToken)) req.Header.Set("Accept", "application/json") + req.Header.Set("Content-Type", "application/json") return req, nil } From 48401e340d6c30c41daf1d4dfa55ab657ff4fcbb Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 21 Jul 2016 18:22:12 +0200 Subject: [PATCH 1607/2266] EUIs are not mandatory --- core/account/applications.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/account/applications.go b/core/account/applications.go index 2c09dfe76..779196769 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -25,7 +25,7 @@ func (a *Account) FindApplication(appID string) (app Application, err error) { type createApplicationReq struct { Name string `json:"name" valid:"required"` AppID string `json:"id" valid:"required"` - EUIs []types.AppEUI `json:"euis" valid:"required"` + EUIs []types.AppEUI `json:"euis"` } // CreateApplication creates a new application on the account server From fbaea92a06539c87d63236152df661f1c74fc4f5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 21 Jul 2016 18:33:35 +0100 Subject: [PATCH 1608/2266] Implement gRPC security (TLS and JWT) --- api/api.go | 31 +++- api/discovery/announcement.go | 10 ++ api/discovery/announcement_test.go | 2 + api/discovery/dial.go | 13 ++ api/discovery/discovery.pb.go | 150 ++++++++++++---- api/discovery/discovery.proto | 2 + api/handler/manager_client.go | 83 ++++----- cmd/broker.go | 13 ++ cmd/genkeys.go | 33 ++++ cmd/handler.go | 2 +- cmd/root.go | 6 + core/broker/activation.go | 9 +- core/broker/broker.go | 9 +- core/broker/downlink.go | 2 +- core/broker/manager_server.go | 2 +- core/broker/server.go | 39 +---- core/broker/server_test.go | 176 ------------------- core/broker/uplink.go | 4 +- core/component.go | 138 ++++++++++++--- core/discovery/broker_discovery.go | 2 +- core/discovery/broker_discovery_test.go | 4 +- core/discovery/handler_discovery.go | 2 +- core/discovery/handler_discovery_test.go | 4 +- core/discovery/server.go | 6 +- core/handler/handler.go | 24 +-- core/handler/manager_server.go | 6 +- core/handler/server.go | 33 +--- core/handler/server_test.go | 6 - core/networkserver/manager_server.go | 2 +- core/networkserver/server.go | 56 +++--- core/networkserver/server_test.go | 6 - core/router/activation.go | 2 +- core/router/manager_server.go | 2 +- core/router/router.go | 19 +- core/router/server_test.go | 212 ----------------------- utils/security/generate_keys.go | 96 ++++++++++ utils/security/jwt.go | 55 ++++++ utils/security/load_keys.go | 22 +++ utils/security/security_test.go | 1 + 39 files changed, 641 insertions(+), 643 deletions(-) create mode 100644 api/discovery/dial.go create mode 100644 cmd/genkeys.go delete mode 100644 core/broker/server_test.go delete mode 100644 core/handler/server_test.go delete mode 100644 core/networkserver/server_test.go delete mode 100644 core/router/server_test.go create mode 100644 utils/security/generate_keys.go create mode 100644 utils/security/jwt.go create mode 100644 utils/security/load_keys.go create mode 100644 utils/security/security_test.go diff --git a/api/api.go b/api/api.go index 8ce66b833..931acb784 100644 --- a/api/api.go +++ b/api/api.go @@ -4,10 +4,13 @@ package api import ( + "crypto/tls" + "crypto/x509" "net" "time" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" ) // Validator interface is used to validate protos @@ -30,12 +33,36 @@ var Backoff = 1 * time.Second var KeepAlive = 10 * time.Second // DialOptions to use in TTN gRPC -// TODO: disable insecure connections var DialOptions = []grpc.DialOption{ - grpc.WithInsecure(), WithKeepAliveDialer(), } +// DialWithCert dials the address using the given TLS root cert +func DialWithCert(address string, cert string) (*grpc.ClientConn, error) { + var tlsConfig *tls.Config + + if cert != "" { + roots := x509.NewCertPool() + ok := roots.AppendCertsFromPEM([]byte(cert)) + if !ok { + panic("failed to parse root certificate") + } + tlsConfig = &tls.Config{RootCAs: roots} + } + + opts := DialOptions + if tlsConfig != nil { + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + } else { + opts = append(opts, grpc.WithInsecure()) + } + + return grpc.Dial( + address, + opts..., + ) +} + // WithKeepAliveDialer creates a dialer with the configured KeepAlive time func WithKeepAliveDialer() grpc.DialOption { return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go index a217ba24e..ed491c75c 100644 --- a/api/discovery/announcement.go +++ b/api/discovery/announcement.go @@ -16,6 +16,8 @@ var AnnouncementProperties = []string{ "service_name", "service_version", "net_address", + "public_key", + "certificate", "metadata", } @@ -53,6 +55,10 @@ func (announcement *Announcement) formatProperty(property string) (formatted str formatted = announcement.ServiceVersion case "net_address": formatted = announcement.NetAddress + case "public_key": + formatted = announcement.PublicKey + case "certificate": + formatted = announcement.Certificate case "metadata": json, err := json.Marshal(announcement.Metadata) if err != nil { @@ -80,6 +86,10 @@ func (announcement *Announcement) parseProperty(property string, value string) e announcement.ServiceVersion = value case "net_address": announcement.NetAddress = value + case "public_key": + announcement.PublicKey = value + case "certificate": + announcement.Certificate = value case "metadata": metadata := []*Metadata{} err := json.Unmarshal([]byte(value), &metadata) diff --git a/api/discovery/announcement_test.go b/api/discovery/announcement_test.go index 076c79ba1..9c2aadc31 100644 --- a/api/discovery/announcement_test.go +++ b/api/discovery/announcement_test.go @@ -32,6 +32,8 @@ func getTestAnnouncement() (announcement *Announcement, dmap map[string]string) "service_name": "router", "service_version": "1.0-preview build abcdef", "net_address": "localhost:1234", + "public_key": "", + "certificate": "", "metadata": `[{"key":1,"value":"Mzg="},{"key":1,"value":"Mzk="}]`, } } diff --git a/api/discovery/dial.go b/api/discovery/dial.go new file mode 100644 index 000000000..59b37773b --- /dev/null +++ b/api/discovery/dial.go @@ -0,0 +1,13 @@ +package discovery + +import ( + "strings" + + "github.com/TheThingsNetwork/ttn/api" + "google.golang.org/grpc" +) + +// Dial dials the component represented by this Announcement +func (a *Announcement) Dial() (*grpc.ClientConn, error) { + return api.DialWithCert(strings.Split(a.NetAddress, ",")[0], a.Certificate) +} diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index a6f0dafa6..71b526236 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -88,6 +88,8 @@ type Announcement struct { ServiceVersion string `protobuf:"bytes,3,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` + PublicKey string `protobuf:"bytes,12,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + Certificate string `protobuf:"bytes,13,opt,name=certificate,proto3" json:"certificate,omitempty"` Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } @@ -474,6 +476,18 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { i = encodeVarintDiscovery(data, i, uint64(len(m.NetAddress))) i += copy(data[i:], m.NetAddress) } + if len(m.PublicKey) > 0 { + data[i] = 0x62 + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.PublicKey))) + i += copy(data[i:], m.PublicKey) + } + if len(m.Certificate) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Certificate))) + i += copy(data[i:], m.Certificate) + } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { data[i] = 0xaa @@ -678,6 +692,14 @@ func (m *Announcement) Size() (n int) { if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } + l = len(m.PublicKey) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + l = len(m.Certificate) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } if len(m.Metadata) > 0 { for _, e := range m.Metadata { l = e.Size() @@ -1028,6 +1050,64 @@ func (m *Announcement) Unmarshal(data []byte) error { } m.NetAddress = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PublicKey", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PublicKey = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Certificate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Certificate = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) @@ -1595,38 +1675,40 @@ var ( ) var fileDescriptorDiscovery = []byte{ - // 518 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x53, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0xad, 0x93, 0xaf, 0xf9, 0xe2, 0xeb, 0x90, 0x5a, 0x03, 0x11, 0x56, 0x16, 0x25, 0x78, 0x43, - 0x59, 0xd4, 0x96, 0x5c, 0xc1, 0x0e, 0xa1, 0xa0, 0x04, 0x88, 0xa0, 0x25, 0x1a, 0x05, 0xc4, 0x2e, - 0x72, 0xed, 0xab, 0xc4, 0x4a, 0x32, 0x0e, 0x9e, 0x71, 0x50, 0xb6, 0x3c, 0x05, 0x0f, 0xc0, 0xc3, - 0xb0, 0xe4, 0x09, 0x10, 0x82, 0x17, 0x61, 0x1c, 0xff, 0xc4, 0x51, 0x1b, 0x41, 0x59, 0x8c, 0xe4, - 0x39, 0x3e, 0xe7, 0xce, 0xb9, 0x67, 0xee, 0xc0, 0x93, 0x49, 0x20, 0xa6, 0xf1, 0xa5, 0xe5, 0x85, - 0x0b, 0x7b, 0x34, 0xc5, 0xd1, 0x34, 0x60, 0x13, 0x7e, 0x81, 0xe2, 0x63, 0x18, 0xcd, 0x6c, 0x21, - 0x98, 0xed, 0x2e, 0x03, 0xdb, 0x0f, 0xb8, 0x17, 0xae, 0x30, 0x5a, 0x6f, 0xbf, 0xac, 0x65, 0x14, - 0x8a, 0x90, 0xa8, 0x05, 0xd0, 0x3e, 0xfd, 0x9b, 0x4a, 0x72, 0xa5, 0x4a, 0xf3, 0x93, 0x02, 0xf5, - 0x73, 0x14, 0xae, 0xef, 0x0a, 0x97, 0x3c, 0x84, 0xea, 0x0c, 0xd7, 0x86, 0xd2, 0x51, 0x4e, 0x9a, - 0xce, 0x5d, 0x6b, 0x7b, 0x4a, 0xce, 0xb0, 0x5e, 0xe1, 0x9a, 0x26, 0x1c, 0x72, 0x07, 0x0e, 0x57, - 0xee, 0x3c, 0x46, 0xa3, 0x22, 0xc9, 0x0d, 0x9a, 0x6e, 0xcc, 0x47, 0x50, 0x95, 0x0c, 0xa2, 0xc2, - 0xe1, 0x9b, 0xd1, 0xcb, 0x3e, 0xd5, 0x0f, 0x08, 0x40, 0x6d, 0x48, 0xfb, 0xcf, 0x07, 0xef, 0x75, - 0x85, 0x68, 0xf0, 0x7f, 0x77, 0x38, 0x1c, 0xf7, 0xdf, 0x0e, 0xf4, 0x4a, 0xf2, 0x23, 0xd9, 0x0c, - 0x7a, 0x7a, 0xd5, 0xfc, 0xae, 0x40, 0xa3, 0xcb, 0x58, 0x18, 0x33, 0x0f, 0x17, 0xc8, 0x04, 0x69, - 0x42, 0x25, 0xf0, 0x37, 0x3e, 0x54, 0x2a, 0xbf, 0xc8, 0x7d, 0x68, 0x70, 0x8c, 0x56, 0x81, 0x87, - 0x63, 0xe6, 0x2e, 0xd2, 0x43, 0x55, 0xaa, 0x65, 0xd8, 0x85, 0x84, 0xc8, 0x03, 0x38, 0xca, 0x29, - 0xd2, 0x32, 0x0f, 0x42, 0x66, 0x54, 0x37, 0xac, 0x66, 0x06, 0xbf, 0x4b, 0x51, 0xd2, 0x01, 0xcd, - 0x47, 0xee, 0x45, 0xc1, 0x52, 0x24, 0xa4, 0xff, 0xd2, 0x52, 0x25, 0x88, 0xdc, 0x03, 0x8d, 0xa1, - 0x18, 0xbb, 0xbe, 0x1f, 0x21, 0xe7, 0x86, 0xb6, 0x61, 0x80, 0x84, 0xba, 0x29, 0x42, 0x6c, 0xa8, - 0x2f, 0xb2, 0x44, 0x8c, 0x56, 0xa7, 0x7a, 0xa2, 0x39, 0xb7, 0xaf, 0x09, 0x8b, 0x16, 0x24, 0xd3, - 0x81, 0x5b, 0x2f, 0xa4, 0x7c, 0x3e, 0xa7, 0xf8, 0x21, 0x46, 0x2e, 0xae, 0x34, 0xa4, 0x5c, 0x69, - 0xc8, 0x7c, 0x0a, 0x20, 0x35, 0xb9, 0xe0, 0xe6, 0x89, 0x98, 0x31, 0x1c, 0x15, 0x56, 0xfe, 0xb9, - 0xca, 0x4e, 0xaf, 0x49, 0x12, 0x7f, 0xec, 0xf5, 0x35, 0xb4, 0xca, 0x77, 0xc9, 0x29, 0xf2, 0x65, - 0xc8, 0x38, 0x92, 0x33, 0xa8, 0x67, 0x85, 0xb9, 0xb4, 0x90, 0xa4, 0x56, 0x1e, 0xb1, 0xb2, 0x86, - 0x16, 0x44, 0xe7, 0x4b, 0x05, 0xd4, 0x5e, 0x4e, 0x22, 0xa7, 0x50, 0xcf, 0x79, 0x64, 0x9f, 0xb8, - 0x5d, 0xb7, 0x92, 0xf1, 0xee, 0x7a, 0x33, 0xd2, 0x83, 0x5a, 0x1a, 0x3b, 0x31, 0x4a, 0xe4, 0x9d, - 0x9b, 0x68, 0x77, 0xf6, 0x94, 0xd9, 0xfa, 0x96, 0x43, 0x2d, 0x25, 0xa4, 0xb5, 0x5b, 0x22, 0xd7, - 0xef, 0xb3, 0x21, 0xdb, 0xd5, 0xe4, 0xbc, 0x14, 0x6f, 0xab, 0x7d, 0x5d, 0x6a, 0x59, 0x8d, 0xad, - 0xe3, 0xc7, 0xd0, 0xec, 0xe1, 0x1c, 0x05, 0xde, 0x4c, 0xe7, 0x10, 0xd0, 0x8b, 0x94, 0xce, 0x5d, - 0xe6, 0x4e, 0x30, 0x7a, 0xa6, 0x7f, 0xfd, 0x79, 0xac, 0x7c, 0x93, 0xeb, 0x87, 0x5c, 0x9f, 0x7f, - 0x1d, 0x1f, 0x5c, 0xd6, 0x36, 0x6f, 0xfe, 0xec, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xeb, - 0xd6, 0x1d, 0x6e, 0x04, 0x00, 0x00, + // 550 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x5d, 0x8f, 0xd2, 0x40, + 0x14, 0xdd, 0x82, 0xbb, 0xd2, 0x5b, 0x96, 0x6d, 0x46, 0x89, 0x0d, 0x89, 0x2b, 0xf6, 0xc5, 0xf5, + 0x61, 0x21, 0x61, 0xa3, 0x6f, 0xc6, 0x60, 0x40, 0x25, 0xba, 0x2b, 0x69, 0xd0, 0xf8, 0x46, 0x86, + 0xf6, 0x0a, 0x13, 0x60, 0x8a, 0x9d, 0x29, 0x86, 0x57, 0x7f, 0x85, 0x6f, 0xbe, 0xf8, 0x63, 0x7c, + 0xf4, 0x27, 0x18, 0xfd, 0x23, 0x4e, 0xe9, 0x07, 0x25, 0xbb, 0x44, 0xd7, 0x87, 0x49, 0x3a, 0x67, + 0xce, 0xb9, 0xf7, 0xdc, 0xc3, 0x0c, 0xf0, 0x64, 0xcc, 0xe4, 0x24, 0x1c, 0x35, 0x5c, 0x7f, 0xde, + 0x1c, 0x4c, 0x70, 0x30, 0x61, 0x7c, 0x2c, 0x2e, 0x50, 0x7e, 0xf2, 0x83, 0x69, 0x53, 0x4a, 0xde, + 0xa4, 0x0b, 0xd6, 0xf4, 0x98, 0x70, 0xfd, 0x25, 0x06, 0xab, 0xcd, 0x57, 0x63, 0x11, 0xf8, 0xd2, + 0x27, 0x7a, 0x06, 0xd4, 0x4e, 0xff, 0xa5, 0x92, 0x5a, 0xb1, 0xd2, 0xfe, 0xac, 0x41, 0xe9, 0x1c, + 0x25, 0xf5, 0xa8, 0xa4, 0xe4, 0x21, 0x14, 0xa7, 0xb8, 0xb2, 0xb4, 0xba, 0x76, 0x52, 0x69, 0xdd, + 0x69, 0x6c, 0xba, 0xa4, 0x8c, 0xc6, 0x2b, 0x5c, 0x39, 0x11, 0x87, 0xdc, 0x86, 0xfd, 0x25, 0x9d, + 0x85, 0x68, 0x15, 0x14, 0xb9, 0xec, 0xc4, 0x1b, 0xfb, 0x11, 0x14, 0x15, 0x83, 0xe8, 0xb0, 0xff, + 0x66, 0xf0, 0xb2, 0xeb, 0x98, 0x7b, 0x04, 0xe0, 0xa0, 0xef, 0x74, 0x9f, 0xf7, 0xde, 0x9b, 0x1a, + 0x31, 0xe0, 0x66, 0xbb, 0xdf, 0x1f, 0x76, 0xdf, 0xf6, 0xcc, 0x42, 0x74, 0x10, 0x6d, 0x7a, 0x1d, + 0xb3, 0x68, 0x7f, 0x2d, 0x40, 0xb9, 0xcd, 0xb9, 0x1f, 0x72, 0x17, 0xe7, 0xc8, 0x25, 0xa9, 0x40, + 0x81, 0x79, 0x6b, 0x1f, 0xba, 0xa3, 0xbe, 0xc8, 0x7d, 0x28, 0x0b, 0x0c, 0x96, 0xcc, 0xc5, 0x21, + 0xa7, 0xf3, 0xb8, 0xa9, 0xee, 0x18, 0x09, 0x76, 0xa1, 0x20, 0xf2, 0x00, 0x8e, 0x52, 0x8a, 0xb2, + 0x2c, 0x98, 0xcf, 0xad, 0xe2, 0x9a, 0x55, 0x49, 0xe0, 0x77, 0x31, 0x4a, 0xea, 0x60, 0x78, 0x28, + 0xdc, 0x80, 0x2d, 0x64, 0x44, 0xba, 0x11, 0x97, 0xca, 0x41, 0xe4, 0x1e, 0x18, 0x1c, 0xe5, 0x90, + 0x7a, 0x5e, 0x80, 0x42, 0x58, 0xc6, 0x9a, 0x01, 0x0a, 0x6a, 0xc7, 0x08, 0xb9, 0x0b, 0xb0, 0x08, + 0x47, 0x33, 0xe6, 0x0e, 0xa3, 0xb8, 0xca, 0xeb, 0x73, 0x3d, 0x46, 0xa2, 0xf1, 0x55, 0x07, 0x17, + 0x03, 0xc9, 0x3e, 0x30, 0x97, 0x4a, 0xb4, 0x0e, 0xe3, 0x0e, 0x39, 0x88, 0x34, 0xa1, 0x34, 0x4f, + 0x22, 0xb5, 0xaa, 0xf5, 0xe2, 0x89, 0xd1, 0xba, 0x75, 0x45, 0xda, 0x4e, 0x46, 0xb2, 0x5b, 0x70, + 0xf8, 0x42, 0xf5, 0x9f, 0xcd, 0x1c, 0xfc, 0x18, 0xa2, 0x90, 0x97, 0x12, 0xd1, 0x2e, 0x25, 0x62, + 0x3f, 0x05, 0x50, 0x9a, 0x54, 0x70, 0xfd, 0x48, 0xed, 0x10, 0x8e, 0x32, 0x2b, 0xff, 0x5d, 0x65, + 0x6b, 0xd6, 0x28, 0xca, 0xbf, 0xce, 0xfa, 0x1a, 0xaa, 0xf9, 0xcb, 0x20, 0x1c, 0x14, 0x0b, 0x9f, + 0x0b, 0x24, 0x67, 0x50, 0x4a, 0x0a, 0x0b, 0x65, 0x21, 0x4a, 0x2d, 0x7f, 0x47, 0xf3, 0x1a, 0x27, + 0x23, 0xb6, 0xbe, 0x15, 0x40, 0xef, 0xa4, 0x24, 0x72, 0x0a, 0xa5, 0x94, 0x47, 0x76, 0x89, 0x6b, + 0xa5, 0x46, 0xf4, 0x3e, 0xda, 0xee, 0x94, 0x74, 0xe0, 0x20, 0x8e, 0x9d, 0x58, 0x39, 0xf2, 0xd6, + 0x2f, 0x51, 0xab, 0xef, 0x28, 0xb3, 0xf1, 0xad, 0x5e, 0x85, 0x92, 0x90, 0xea, 0x76, 0x89, 0x54, + 0xbf, 0xcb, 0x86, 0x1a, 0xd7, 0x50, 0x17, 0x2e, 0x7b, 0x9c, 0xb5, 0xab, 0x52, 0x4b, 0x6a, 0x6c, + 0x1c, 0x3f, 0x86, 0x4a, 0x07, 0x67, 0x28, 0xf1, 0x7a, 0xba, 0x16, 0x01, 0x33, 0x4b, 0xe9, 0x9c, + 0x72, 0x3a, 0xc6, 0xe0, 0x99, 0xf9, 0xfd, 0xd7, 0xb1, 0xf6, 0x43, 0xad, 0x9f, 0x6a, 0x7d, 0xf9, + 0x7d, 0xbc, 0x37, 0x3a, 0x58, 0xff, 0x69, 0x9c, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x28, + 0x1d, 0x06, 0xaf, 0x04, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index bb0137e8d..8e6804879 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -33,6 +33,8 @@ message Announcement { string service_version = 3; string description = 4; string net_address = 11; + string public_key = 12; + string certificate = 13; repeated Metadata metadata = 21; } diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index 6328e83c6..af444de01 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -1,10 +1,10 @@ package handler import ( - "fmt" + "os" + "os/user" "sync" - "github.com/TheThingsNetwork/ttn/api" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -13,98 +13,99 @@ import ( // ManagerClient is used to manage applications and devices on a handler type ManagerClient struct { sync.RWMutex + id string + accessToken string conn *grpc.ClientConn - context context.Context applicationManagerClient ApplicationManagerClient } -// NewManagerClient returns a new ManagerClient for a handler at the given address that accepts the given access token -func NewManagerClient(address string, accessToken string) (*ManagerClient, error) { - conn, err := grpc.Dial(address, api.DialOptions...) - if err != nil { - return nil, fmt.Errorf("Could not connect to NetworkServer: %s", err.Error()) - } +// NewManagerClient returns a new ManagerClient for a handler on the given conn that accepts the given access token +func NewManagerClient(conn *grpc.ClientConn, accessToken string) (*ManagerClient, error) { applicationManagerClient := NewApplicationManagerClient(conn) - md := metadata.Pairs( - "token", accessToken, - ) - manageContext := metadata.NewContext(context.Background(), md) + + id := "client" + if user, err := user.Current(); err == nil { + id += "-" + user.Username + } + if hostname, err := os.Hostname(); err == nil { + id += "@" + hostname + } + return &ManagerClient{ - conn: conn, - context: manageContext, + id: id, + accessToken: accessToken, + conn: conn, applicationManagerClient: applicationManagerClient, }, nil } +// SetID sets the ID of this client +func (h *ManagerClient) SetID(id string) { + h.Lock() + defer h.Unlock() + h.id = id +} + // UpdateAccessToken updates the access token that is used for running commands func (h *ManagerClient) UpdateAccessToken(accessToken string) { h.Lock() defer h.Unlock() + h.accessToken = accessToken +} + +func (h *ManagerClient) getContext() context.Context { + h.RLock() + defer h.RUnlock() md := metadata.Pairs( - "token", accessToken, + "id", h.id, + "token", h.accessToken, ) - h.context = metadata.NewContext(context.Background(), md) + return metadata.NewContext(context.Background(), md) } // GetApplication retrieves an application from the Handler func (h *ManagerClient) GetApplication(appID string) (*Application, error) { - h.RLock() - defer h.RUnlock() - return h.applicationManagerClient.GetApplication(h.context, &ApplicationIdentifier{AppId: appID}) + return h.applicationManagerClient.GetApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) } // SetApplication sets an application on the Handler func (h *ManagerClient) SetApplication(in *Application) error { - h.RLock() - defer h.RUnlock() - _, err := h.applicationManagerClient.SetApplication(h.context, in) + _, err := h.applicationManagerClient.SetApplication(h.getContext(), in) return err } // RegisterApplication registers an application on the Handler func (h *ManagerClient) RegisterApplication(appID string) error { - h.RLock() - defer h.RUnlock() - _, err := h.applicationManagerClient.RegisterApplication(h.context, &ApplicationIdentifier{AppId: appID}) + _, err := h.applicationManagerClient.RegisterApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) return err } // DeleteApplication deletes an application and all its devices from the Handler func (h *ManagerClient) DeleteApplication(appID string) error { - h.RLock() - defer h.RUnlock() - _, err := h.applicationManagerClient.DeleteApplication(h.context, &ApplicationIdentifier{AppId: appID}) + _, err := h.applicationManagerClient.DeleteApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) return err } // GetDevice retrieves a device from the Handler func (h *ManagerClient) GetDevice(appID string, devID string) (*Device, error) { - h.RLock() - defer h.RUnlock() - return h.applicationManagerClient.GetDevice(h.context, &DeviceIdentifier{AppId: appID, DevId: devID}) + return h.applicationManagerClient.GetDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) } // SetDevice sets a device on the Handler func (h *ManagerClient) SetDevice(in *Device) error { - h.RLock() - defer h.RUnlock() - _, err := h.applicationManagerClient.SetDevice(h.context, in) + _, err := h.applicationManagerClient.SetDevice(h.getContext(), in) return err } // DeleteDevice deletes a device from the Handler func (h *ManagerClient) DeleteDevice(appID string, devID string) error { - h.RLock() - defer h.RUnlock() - _, err := h.applicationManagerClient.DeleteDevice(h.context, &DeviceIdentifier{AppId: appID, DevId: devID}) + _, err := h.applicationManagerClient.DeleteDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) return err } // GetDevicesForApplication retrieves all devices for an application from the Handler func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Device, err error) { - h.RLock() - defer h.RUnlock() - res, err := h.applicationManagerClient.GetDevicesForApplication(h.context, &ApplicationIdentifier{AppId: appID}) + res, err := h.applicationManagerClient.GetDevicesForApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) if err != nil { return nil, err } diff --git a/cmd/broker.go b/cmd/broker.go index 77cf1ec20..0c01bdc65 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "io/ioutil" "net" "os" "os/signal" @@ -70,10 +71,20 @@ var brokerCmd = &cobra.Command{ }) } + var nsCert string + if nsCertFile := viper.GetString("broker.networkserver-cert"); nsCertFile != "" { + contents, err := ioutil.ReadFile(nsCertFile) + if err != nil { + ctx.WithError(err).Fatal("Could not get Networkserver certificate") + } + nsCert = string(contents) + } + // Broker broker := broker.NewRedisBroker( client, viper.GetString("broker.networkserver-address"), + nsCert, time.Duration(viper.GetInt("broker.deduplication-delay"))*time.Millisecond, ) err = broker.Init(component) @@ -111,6 +122,8 @@ func init() { brokerCmd.Flags().String("networkserver-address", "localhost:1903", "Networkserver host and port") viper.BindPFlag("broker.networkserver-address", brokerCmd.Flags().Lookup("networkserver-address")) + brokerCmd.Flags().String("networkserver-cert", "", "Networkserver certificate to use") + viper.BindPFlag("broker.networkserver-cert", brokerCmd.Flags().Lookup("networkserver-cert")) brokerCmd.Flags().StringSlice("prefix", []string{"26000000/24"}, "LoRaWAN DevAddr prefix to announce") viper.BindPFlag("broker.prefix", brokerCmd.Flags().Lookup("prefix")) diff --git a/cmd/genkeys.go b/cmd/genkeys.go new file mode 100644 index 000000000..3b998f04e --- /dev/null +++ b/cmd/genkeys.go @@ -0,0 +1,33 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/utils/security" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// genkeysCmd represents the genkeys command +func genkeysCmd(component string) *cobra.Command { + return &cobra.Command{ + Use: "genkeys", + Short: "Generate keys and certificate", + Long: `ttn genkeys generates keys and a TLS certificate for this component`, + Run: func(cmd *cobra.Command, args []string) { + err := security.GenerateKeys(viper.GetString("key-dir"), viper.GetString(component+".server-address-announce")) + if err != nil { + ctx.WithError(err).Fatal("Could not generate keys") + } + ctx.WithField("TLSDir", viper.GetString("key-dir")).Info("Done") + }, + } +} + +func init() { + routerCmd.AddCommand(genkeysCmd("router")) + brokerCmd.AddCommand(genkeysCmd("broker")) + handlerCmd.AddCommand(genkeysCmd("handler")) + networkserverCmd.AddCommand(genkeysCmd("networkserver")) +} diff --git a/cmd/handler.go b/cmd/handler.go index c00c58cd8..8058c4bab 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -94,7 +94,7 @@ func init() { handlerCmd.Flags().Int("redis-db", 0, "Redis database") viper.BindPFlag("handler.redis-db", handlerCmd.Flags().Lookup("redis-db")) - handlerCmd.Flags().String("ttn-broker", "localhost:1902", "TTN broker host and port") + handlerCmd.Flags().String("ttn-broker", "dev", "The ID of the TTN Broker as announced in the Discovery server") viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "MQTT broker host and port") diff --git a/cmd/root.go b/cmd/root.go index 8e26d9e3f..117684737 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -95,6 +95,12 @@ func init() { RootCmd.PersistentFlags().String("oauth2-keyfile", defaultOAuth2KeyFile, "The OAuth 2.0 public key") viper.BindPFlag("oauth2-keyfile", RootCmd.PersistentFlags().Lookup("oauth2-keyfile")) + + RootCmd.PersistentFlags().Bool("tls", false, "Use TLS") + viper.BindPFlag("tls", RootCmd.PersistentFlags().Lookup("tls")) + + RootCmd.PersistentFlags().String("key-dir", path.Clean(dir+"/.ttn/"), "The directory where public/private keys are stored") + viper.BindPFlag("key-dir", RootCmd.PersistentFlags().Lookup("key-dir")) } // initConfig reads in config file and ENV variables if set. diff --git a/core/broker/activation.go b/core/broker/activation.go index c5001bbb6..941dcdff5 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -11,7 +11,6 @@ import ( "google.golang.org/grpc" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" @@ -75,7 +74,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } // Send Activate to NS - deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(false), deduplicatedActivationRequest) + deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { return nil, err } @@ -104,19 +103,19 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D ctx.WithField("HandlerID", handler.Id).Debug("Forward Activation") var conn *grpc.ClientConn - conn, err = grpc.Dial(handler.NetAddress, api.DialOptions...) + conn, err = handler.Dial() defer conn.Close() if err != nil { return nil, err } client := pb_handler.NewHandlerClient(conn) var handlerResponse *pb_handler.DeviceActivationResponse - handlerResponse, err = client.Activate(b.Component.GetContext(false), deduplicatedActivationRequest) + handlerResponse, err = client.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { return nil, err } - handlerResponse, err = b.ns.Activate(b.Component.GetContext(false), handlerResponse) + handlerResponse, err = b.ns.Activate(b.Component.GetContext(""), handlerResponse) if err != nil { return nil, err } diff --git a/core/broker/broker.go b/core/broker/broker.go index dced87f8f..175408b6b 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -8,12 +8,11 @@ import ( "sync" "time" - "google.golang.org/grpc" - "gopkg.in/redis.v3" "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" @@ -34,13 +33,14 @@ type Broker interface { DeactivateHandler(id string) error } -func NewRedisBroker(client *redis.Client, networkserver string, timeout time.Duration) Broker { +func NewRedisBroker(client *redis.Client, networkserver string, networkserverCert string, timeout time.Duration) Broker { return &broker{ routers: make(map[string]chan *pb.DownlinkMessage), handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), uplinkDeduplicator: NewDeduplicator(timeout), activationDeduplicator: NewDeduplicator(timeout), nsAddr: networkserver, + nsCert: networkserverCert, } } @@ -52,6 +52,7 @@ type broker struct { handlers map[string]chan *pb.DeduplicatedUplinkMessage handlersLock sync.RWMutex nsAddr string + nsCert string ns networkserver.NetworkServerClient nsManager pb_lorawan.DeviceManagerClient uplinkDeduplicator Deduplicator @@ -70,7 +71,7 @@ func (b *broker) Init(c *core.Component) error { } b.handlerDiscovery = discovery.NewHandlerDiscovery(b.Component) b.handlerDiscovery.All() // Update cache - conn, err := grpc.Dial(b.nsAddr, api.DialOptions...) + conn, err := api.DialWithCert(b.nsAddr, b.nsCert) if err != nil { return err } diff --git a/core/broker/downlink.go b/core/broker/downlink.go index 0eb9f7138..a87b3bb52 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -34,7 +34,7 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { } }() - downlink, err = b.ns.Downlink(b.Component.GetContext(false), downlink) + downlink, err = b.ns.Downlink(b.Component.GetContext(""), downlink) if err != nil { return err } diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 9ed26d8f8..a14898019 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -33,7 +33,7 @@ func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIden } func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*api.Ack, error) { - claims, err := b.Component.ValidateContext(ctx) + claims, err := b.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } diff --git a/core/broker/server.go b/core/broker/server.go index 99e212a57..c2c1f22be 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -4,7 +4,6 @@ package broker import ( - "errors" "io" pb_api "github.com/TheThingsNetwork/ttn/api" @@ -12,7 +11,6 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" ) type brokerRPC struct { @@ -21,37 +19,8 @@ type brokerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining -func getCallerFromMetadata(ctx context.Context) (callerID string, err error) { - md, ok := metadata.FromContext(ctx) - if !ok { - err = errors.New("ttn: Could not get metadata") - return - } - id, ok := md["id"] - if !ok || len(id) < 1 { - err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"id\" in context") - return - } - callerID = id[0] - if err != nil { - return - } - token, ok := md["token"] - if !ok || len(token) < 1 { - err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller did not provide \"token\" in context") - return - } - if token[0] != "token" { - // TODO: Validate Token - err = grpcErrf(codes.Unauthenticated, "ttn/broker: Caller not authorized") - return - } - - return -} - func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { - routerID, err := getCallerFromMetadata(stream.Context()) + routerID, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { return err } @@ -94,7 +63,7 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { } func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_SubscribeServer) error { - handlerID, err := getCallerFromMetadata(stream.Context()) + handlerID, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { return err } @@ -121,7 +90,7 @@ func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_Subscri } func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { - handlerID, err := getCallerFromMetadata(stream.Context()) + handlerID, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { return err } @@ -143,7 +112,7 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { } func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { - _, err = getCallerFromMetadata(ctx) + _, err = b.broker.ValidateNetworkContext(ctx) if err != nil { return nil, err } diff --git a/core/broker/server_test.go b/core/broker/server_test.go deleted file mode 100644 index 1ee20d773..000000000 --- a/core/broker/server_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package broker - -import ( - "fmt" - "math/rand" - "net" - "testing" - "time" - - "github.com/TheThingsNetwork/ttn/api" - pb "github.com/TheThingsNetwork/ttn/api/broker" - pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" - pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" - . "github.com/smartystreets/assertions" - "golang.org/x/net/context" - - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" -) - -func randomPort() uint { - rand.Seed(time.Now().UnixNano()) - port := rand.Intn(5000) + 5000 - return uint(port) -} - -func buildTestBrokerServer(t *testing.T, port uint) (*broker, *grpc.Server) { - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - panic(err) - } - b := &broker{ - Component: &core.Component{ - Ctx: GetLogger(t, "TestBrokerServer"), - }, - routers: make(map[string]chan *pb.DownlinkMessage), - handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), - ns: &mockNetworkServer{}, - uplinkDeduplicator: NewDeduplicator(300 * time.Millisecond), - activationDeduplicator: NewDeduplicator(1000 * time.Millisecond), - } - s := grpc.NewServer() - b.RegisterRPC(s) - go s.Serve(lis) - return b, s -} - -func TestAssociateRPC(t *testing.T) { - a := New(t) - - port := randomPort() - b, s := buildTestBrokerServer(t, port) - defer s.Stop() - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewBrokerClient(conn) - md := metadata.Pairs( - "token", "token", - "id", "RouterID", - ) - ctx := metadata.NewContext(context.Background(), md) - - stream, err := client.Associate(ctx) - a.So(err, ShouldBeNil) - - <-time.After(5 * time.Millisecond) - - a.So(b.routers, ShouldNotBeEmpty) - - err = stream.CloseSend() - a.So(err, ShouldBeNil) - - <-time.After(5 * time.Millisecond) - - a.So(b.routers, ShouldBeEmpty) - -} - -func TestSubscribeRPC(t *testing.T) { - a := New(t) - - port := randomPort() - b, s := buildTestBrokerServer(t, port) - defer s.Stop() - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewBrokerClient(conn) - md := metadata.Pairs( - "token", "token", - "id", "HandlerID", - ) - ctx := metadata.NewContext(context.Background(), md) - - stream, err := client.Subscribe(ctx, &pb.SubscribeRequest{}) - a.So(err, ShouldBeNil) - - <-time.After(5 * time.Millisecond) - - a.So(b.handlers, ShouldNotBeEmpty) - - err = stream.CloseSend() - a.So(err, ShouldBeNil) - - err = conn.Close() - a.So(err, ShouldBeNil) - - <-time.After(5 * time.Millisecond) - - a.So(b.handlers, ShouldBeEmpty) - -} - -func TestPublishRPC(t *testing.T) { - a := New(t) - - appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} - devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} - gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} - - port := randomPort() - b, s := buildTestBrokerServer(t, port) - defer s.Stop() - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewBrokerClient(conn) - md := metadata.Pairs( - "token", "token", - "id", "HandlerID", - ) - ctx := metadata.NewContext(context.Background(), md) - - dlch := make(chan *pb.DownlinkMessage, 2) - b.routers["routerID"] = dlch - - stream, _ := client.Publish(ctx) - stream.Send(&pb.DownlinkMessage{ - DevEui: &devEUI, - AppEui: &appEUI, - DownlinkOption: &pb.DownlinkOption{ - Identifier: "routerID:scheduleID", - GatewayConfig: &pb_gateway.TxConfiguration{}, - ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}}, - GatewayEui: >wEUI, - }, - }) - ack, err := stream.CloseAndRecv() - a.So(err, ShouldBeNil) - a.So(ack, ShouldNotBeNil) - - <-time.After(10 * time.Millisecond) - - a.So(len(dlch), ShouldEqual, 1) -} - -func TestActivate(t *testing.T) { - // TODO -} diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 79823f854..5d5afd810 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -72,7 +72,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { devAddr := types.DevAddr(macPayload.FHDR.DevAddr) ctx = ctx.WithField("DevAddr", devAddr) var getDevicesResp *networkserver.DevicesResponse - getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(false), &networkserver.DevicesRequest{ + getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(""), &networkserver.DevicesRequest{ DevAddr: &devAddr, FCnt: macPayload.FHDR.FCnt, }) @@ -161,7 +161,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } // Pass Uplink through NS - deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(false), deduplicatedUplink) + deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(""), deduplicatedUplink) if err != nil { return err } diff --git a/core/component.go b/core/component.go index 115d643ae..61e453d25 100644 --- a/core/component.go +++ b/core/component.go @@ -4,6 +4,7 @@ package core import ( + "crypto/tls" "errors" "fmt" "runtime" @@ -11,11 +12,13 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/security" "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/dgrijalva/jwt-go" @@ -26,7 +29,8 @@ import ( type ComponentInterface interface { RegisterRPC(s *grpc.Server) Init(c *Component) error - ValidateContext(ctx context.Context) (*TTNClaims, error) + ValidateNetworkContext(ctx context.Context) (string, error) + ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) } type ManagementInterface interface { @@ -48,14 +52,14 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string var discovery pb_discovery.DiscoveryClient if serviceName != "discovery" { - discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock())...) + discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) if err != nil { return nil, err } discovery = pb_discovery.NewDiscoveryClient(discoveryConn) } - return &Component{ + component := &Component{ Ctx: ctx, Identity: &pb_discovery.Announcement{ Id: viper.GetString("id"), @@ -69,7 +73,23 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string fmt.Sprintf("%s/key", viper.GetString("auth-server")), viper.GetString("oauth2-keyfile"), ), - }, nil + } + + if pub, priv, cert, err := security.LoadKeys(viper.GetString("key-dir")); err == nil { + component.Identity.PublicKey = string(pub) + component.privateKey = string(priv) + + if viper.GetBool("tls") { + component.Identity.Certificate = string(cert) + cer, err := tls.X509KeyPair(cert, priv) + if err != nil { + return nil, err + } + component.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} + } + } + + return component, nil } // Component contains the common attributes for all TTN components @@ -78,16 +98,25 @@ type Component struct { Discovery pb_discovery.DiscoveryClient Ctx log.Interface AccessToken string + privateKey string + tlsConfig *tls.Config TokenKeyProvider tokenkey.Provider } +// Discover is used to discover another component +func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { + return c.Discovery.Get(c.GetContext(""), &pb_discovery.GetRequest{ + ServiceName: serviceName, + Id: id, + }) +} + // Announce the component to TTN discovery func (c *Component) Announce() error { if c.Identity.Id == "" { return errors.New("ttn: No ID configured") } - - _, err := c.Discovery.Announce(c.GetContext(true), c.Identity) + _, err := c.Discovery.Announce(c.GetContext(c.AccessToken), c.Identity) if err != nil { return fmt.Errorf("ttn: Failed to announce this component to TTN discovery: %s", err.Error()) } @@ -136,8 +165,68 @@ func (c *TTNClaims) CanEditApp(appID string) bool { return false } -// ValidateContext gets a token from the context and validates it -func (c *Component) ValidateContext(ctx context.Context) (*TTNClaims, error) { +// ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) +func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID string, err error) { + defer func() { + if err != nil { + time.Sleep(time.Second) + } + }() + + md, ok := metadata.FromContext(ctx) + if !ok { + err = errors.New("ttn: Could not get metadata") + return + } + var id, serviceName, token string + if ids, ok := md["id"]; ok && len(ids) == 1 { + id = ids[0] + } + if id == "" { + err = errors.New("ttn: Could not get id") + return + } + if serviceNames, ok := md["service-name"]; ok && len(serviceNames) == 1 { + serviceName = serviceNames[0] + } + if serviceName == "" { + err = errors.New("ttn: Could not get service name") + return + } + if tokens, ok := md["token"]; ok && len(tokens) == 1 { + token = tokens[0] + } + + var announcement *pb_discovery.Announcement + announcement, err = c.Discover(serviceName, id) + if err != nil { + return + } + + if announcement.PublicKey == "" { + return id, nil + } + + if token == "" { + err = errors.New("ttn: Could not get token") + return + } + + var claims *jwt.StandardClaims + claims, err = security.ValidateJWT(token, []byte(announcement.PublicKey)) + if err != nil { + return + } + if claims.Subject != id { + err = errors.New("The token was issued for a different component ID") + return + } + + return id, nil +} + +// ValidateTTNAuthContext gets a token from the context and validates it +func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) { md, ok := metadata.FromContext(ctx) if !ok { return nil, errors.New("ttn: Could not get metadata") @@ -146,13 +235,8 @@ func (c *Component) ValidateContext(ctx context.Context) (*TTNClaims, error) { if !ok || len(token) < 1 { return nil, errors.New("ttn: Could not get token") } - return c.ValidateToken(token[0]) -} - -// ValidateToken verifies an OAuth Bearer token -func (c *Component) ValidateToken(token string) (*TTNClaims, error) { ttnClaims := &TTNClaims{} - parsed, err := jwt.ParseWithClaims(token, ttnClaims, func(token *jwt.Token) (interface{}, error) { + parsed, err := jwt.ParseWithClaims(token[0], ttnClaims, func(token *jwt.Token) (interface{}, error) { if c.TokenKeyProvider == nil { return nil, errors.New("No token provider configured") } @@ -231,20 +315,34 @@ func (c *Component) ServerOptions() []grpc.ServerOption { return handler(srv, stream) } - return []grpc.ServerOption{ + opts := []grpc.ServerOption{ grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary)), grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream)), } + + if c.tlsConfig != nil { + opts = append(opts, grpc.Creds(credentials.NewTLS(c.tlsConfig))) + } + + return opts +} + +// BuildJWT builds a short-lived JSON Web Token for this component +func (c *Component) BuildJWT() (string, error) { + if c.privateKey != "" { + return security.BuildJWT(c.Identity.Id, 10*time.Second, []byte(c.privateKey)) + } + return "", nil } -// GetContext returns a context for outgoing RPC requests -func (c *Component) GetContext(includeAccessToken bool) context.Context { - var serviceName, id, token, netAddress string +// GetContext returns a context for outgoing RPC request. If token is "", this function will generate a short lived token from the component +func (c *Component) GetContext(token string) context.Context { + var serviceName, id, netAddress string if c.Identity != nil { serviceName = c.Identity.ServiceName id = c.Identity.Id - if includeAccessToken { - token = c.AccessToken + if token == "" { + token, _ = c.BuildJWT() } netAddress = c.Identity.NetAddress } diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 964a30885..9cf478177 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -34,7 +34,7 @@ func NewBrokerDiscovery(component *core.Component) BrokerDiscovery { } func (d *brokerDiscovery) refreshCache() error { - res, err := d.component.Discovery.GetAll(d.component.GetContext(false), &pb.GetAllRequest{ServiceName: "broker"}) + res, err := d.component.Discovery.GetAll(d.component.GetContext(""), &pb.GetAllRequest{ServiceName: "broker"}) if err != nil { return err } diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go index 472238dec..a707dbaeb 100644 --- a/core/discovery/broker_discovery_test.go +++ b/core/discovery/broker_discovery_test.go @@ -18,7 +18,7 @@ import ( ) func buildTestBrokerDiscoveryClient(port uint) *brokerDiscovery { - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) if err != nil { panic(err) } @@ -97,7 +97,7 @@ func TestBrokerDiscoveryCache(t *testing.T) { Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00}}}, } - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) if err != nil { panic(err) } diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go index 73a931e22..4e49035c5 100644 --- a/core/discovery/handler_discovery.go +++ b/core/discovery/handler_discovery.go @@ -38,7 +38,7 @@ func NewHandlerDiscovery(component *core.Component) HandlerDiscovery { } func (d *handlerDiscovery) refreshCache() error { - res, err := d.component.Discovery.GetAll(d.component.GetContext(false), &pb.GetAllRequest{ServiceName: "handler"}) + res, err := d.component.Discovery.GetAll(d.component.GetContext(""), &pb.GetAllRequest{ServiceName: "handler"}) if err != nil { return err } diff --git a/core/discovery/handler_discovery_test.go b/core/discovery/handler_discovery_test.go index bd75c4ef5..cfb3b9ade 100644 --- a/core/discovery/handler_discovery_test.go +++ b/core/discovery/handler_discovery_test.go @@ -17,7 +17,7 @@ import ( ) func buildTestHandlerDiscoveryClient(port uint) *handlerDiscovery { - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) if err != nil { panic(err) } @@ -88,7 +88,7 @@ func TestHandlerDiscoveryCache(t *testing.T) { Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}}, } - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithBlock())...) + conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) if err != nil { panic(err) } diff --git a/core/discovery/server.go b/core/discovery/server.go index 7b1254741..f54fdf3e7 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -17,7 +17,7 @@ type discoveryServer struct { } func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { - claims, err := d.discovery.ValidateContext(ctx) + claims, err := d.discovery.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } @@ -32,7 +32,7 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc } func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { - claims, err := d.discovery.ValidateContext(ctx) + claims, err := d.discovery.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } @@ -55,7 +55,7 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques } func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { - claims, err := d.discovery.ValidateContext(ctx) + claims, err := d.discovery.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } diff --git a/core/handler/handler.go b/core/handler/handler.go index 7304e3284..51b5953d5 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -31,14 +31,14 @@ type Handler interface { } // NewRedisHandler creates a new Redis-backed Handler -func NewRedisHandler(client *redis.Client, ttnBrokerAddr string, mqttUsername string, mqttPassword string, mqttBrokers ...string) Handler { +func NewRedisHandler(client *redis.Client, ttnBrokerID string, mqttUsername string, mqttPassword string, mqttBrokers ...string) Handler { return &handler{ - devices: device.NewRedisDeviceStore(client), - applications: application.NewRedisApplicationStore(client), - ttnBrokerAddr: ttnBrokerAddr, - mqttUsername: mqttUsername, - mqttPassword: mqttPassword, - mqttBrokers: mqttBrokers, + devices: device.NewRedisDeviceStore(client), + applications: application.NewRedisApplicationStore(client), + ttnBrokerID: ttnBrokerID, + mqttUsername: mqttUsername, + mqttPassword: mqttPassword, + mqttBrokers: mqttBrokers, } } @@ -48,7 +48,7 @@ type handler struct { devices device.Store applications application.Store - ttnBrokerAddr string + ttnBrokerID string ttnBrokerConn *grpc.ClientConn ttnBroker pb_broker.BrokerClient ttnBrokerManager pb_broker.BrokerManagerClient @@ -99,7 +99,11 @@ func (h *handler) Init(c *core.Component) error { } func (h *handler) associateBroker() error { - conn, err := grpc.Dial(h.ttnBrokerAddr, api.DialOptions...) + broker, err := h.Discover("broker", h.ttnBrokerID) + if err != nil { + return err + } + conn, err := broker.Dial() if err != nil { return err } @@ -108,7 +112,7 @@ func (h *handler) associateBroker() error { h.ttnBrokerManager = pb_broker.NewBrokerManagerClient(conn) h.ttnDeviceManager = pb_lorawan.NewDeviceManagerClient(conn) - ctx := h.GetContext(false) + ctx := h.GetContext("") h.downlink = make(chan *pb_broker.DownlinkMessage) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 65eba9188..fada63e9b 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -28,7 +28,7 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } - claims, err := h.Component.ValidateContext(ctx) + claims, err := h.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } @@ -176,7 +176,7 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") } - claims, err := h.Component.ValidateContext(ctx) + claims, err := h.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } @@ -211,7 +211,7 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") } - claims, err := h.Component.ValidateContext(ctx) + claims, err := h.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } diff --git a/core/handler/server.go b/core/handler/server.go index b2edd5880..e4b4ebc5a 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -4,14 +4,11 @@ package handler import ( - "errors" - pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" ) type handlerRPC struct { @@ -20,36 +17,8 @@ type handlerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining -func validateBrokerFromMetadata(ctx context.Context) (err error) { - md, ok := metadata.FromContext(ctx) - if !ok { - err = errors.New("ttn: Could not get metadata") - return - } - id, ok := md["id"] - if !ok || len(id) < 1 { - err = errors.New("ttn/handler: Broker did not provide \"id\" in context") - return - } - if err != nil { - return - } - token, ok := md["token"] - if !ok || len(token) < 1 { - err = errors.New("ttn/handler: Broker did not provide \"token\" in context") - return - } - if token[0] != "token" { - // TODO: Validate Token - err = errors.New("ttn/handler: Broker not authorized") - return - } - - return -} - func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { - err := validateBrokerFromMetadata(ctx) + _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { return nil, err } diff --git a/core/handler/server_test.go b/core/handler/server_test.go deleted file mode 100644 index 0339071bb..000000000 --- a/core/handler/server_test.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -// TODO diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 89791ea1a..e9848829f 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -26,7 +26,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } - claims, err := n.Component.ValidateContext(ctx) + claims, err := n.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 324801806..75f89896a 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -9,6 +9,8 @@ import ( "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/utils/security" + "github.com/dgrijalva/jwt-go" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -21,37 +23,37 @@ type networkServerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining -func validateBrokerFromMetadata(ctx context.Context) (err error) { +func (s *networkServerRPC) ValidateContext(ctx context.Context) error { md, ok := metadata.FromContext(ctx) if !ok { - err = errors.New("ttn: Could not get metadata") - return + return errors.New("ttn: Could not get metadata") } - id, ok := md["id"] - if !ok || len(id) < 1 { - err = errors.New("ttn/networkserver: Broker did not provide \"id\" in context") - return + var id, token string + if ids, ok := md["id"]; ok && len(ids) == 1 { + id = ids[0] } - if err != nil { - return + if id == "" { + return errors.New("ttn: Could not get id") } - token, ok := md["token"] - if !ok || len(token) < 1 { - err = errors.New("ttn/networkserver: Broker did not provide \"token\" in context") - return + if tokens, ok := md["token"]; ok && len(tokens) == 1 { + token = tokens[0] } - if token[0] != "token" { - // TODO: Validate Token - err = errors.New("ttn/networkserver: Broker not authorized") - return + if token == "" { + return errors.New("ttn: Could not get token") } - - return + var claims *jwt.StandardClaims + claims, err := security.ValidateJWT(token, []byte(s.networkServer.(*networkServer).Identity.PublicKey)) + if err != nil { + return err + } + if claims.Subject != id { + return errors.New("The token was issued for a different component ID") + } + return nil } func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesRequest) (*pb.DevicesResponse, error) { - err := validateBrokerFromMetadata(ctx) - if err != nil { + if err := s.ValidateContext(ctx); err != nil { return nil, err } if !req.Validate() { @@ -61,8 +63,7 @@ func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesReques } func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { - err := validateBrokerFromMetadata(ctx) - if err != nil { + if err := s.ValidateContext(ctx); err != nil { return nil, err } if !activation.Validate() { @@ -72,8 +73,7 @@ func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *br } func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { - err := validateBrokerFromMetadata(ctx) - if err != nil { + if err := s.ValidateContext(ctx); err != nil { return nil, err } if !activation.Validate() { @@ -83,8 +83,7 @@ func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.Dev } func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { - err := validateBrokerFromMetadata(ctx) - if err != nil { + if err := s.ValidateContext(ctx); err != nil { return nil, err } if !message.Validate() { @@ -94,8 +93,7 @@ func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.Deduplica } func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { - err := validateBrokerFromMetadata(ctx) - if err != nil { + if err := s.ValidateContext(ctx); err != nil { return nil, err } if !message.Validate() { diff --git a/core/networkserver/server_test.go b/core/networkserver/server_test.go deleted file mode 100644 index b4c8f7d55..000000000 --- a/core/networkserver/server_test.go +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package networkserver - -// TODO: The RPC server is super simple, but we should still test it. diff --git a/core/router/activation.go b/core/router/activation.go index d15c1f569..06651511c 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -106,7 +106,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Do async request wg.Add(1) go func() { - res, err := broker.client.Activate(r.Component.GetContext(false), request) + res, err := broker.client.Activate(r.Component.GetContext(""), request) if err == nil && res != nil { responses <- res } diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 1af4404da..a5ac630f2 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -22,7 +22,7 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR if in.GatewayEui == nil { return nil, errf(codes.InvalidArgument, "GatewayEUI is required") } - _, err := r.ValidateContext(ctx) + _, err := r.ValidateTTNAuthContext(ctx) if err != nil { return nil, errf(codes.Unauthenticated, "No access") } diff --git a/core/router/router.go b/core/router/router.go index 173126394..89cc98b91 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -8,9 +8,6 @@ import ( "sync" "time" - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -110,10 +107,10 @@ func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { // getBroker gets or creates a broker association and returns the broker // the first time it also starts a goroutine that receives downlink from the broker -func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { +func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*broker, error) { // We're going to be optimistic and guess that the broker is already active r.brokersLock.RLock() - brk, ok := r.brokers[req.NetAddress] + brk, ok := r.brokers[brokerAnnouncement.Id] r.brokersLock.RUnlock() if ok { return brk, nil @@ -121,15 +118,15 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { // If it doesn't we still have to lock r.brokersLock.Lock() defer r.brokersLock.Unlock() - if _, ok := r.brokers[req.NetAddress]; !ok { + if _, ok := r.brokers[brokerAnnouncement.Id]; !ok { // Connect to the server - conn, err := grpc.Dial(req.NetAddress, api.DialOptions...) + conn, err := brokerAnnouncement.Dial() if err != nil { return nil, err } client := pb_broker.NewBrokerClient(conn) - association, err := client.Associate(r.Component.GetContext(false)) + association, err := client.Associate(r.Component.GetContext("")) if err != nil { return nil, err } @@ -150,12 +147,12 @@ func (r *router) getBroker(req *pb_discovery.Announcement) (*broker, error) { conn.Close() r.brokersLock.Lock() defer r.brokersLock.Unlock() - delete(r.brokers, req.NetAddress) + delete(r.brokers, brokerAnnouncement.Id) }() - r.brokers[req.NetAddress] = &broker{ + r.brokers[brokerAnnouncement.Id] = &broker{ client: client, association: association, } } - return r.brokers[req.NetAddress], nil + return r.brokers[brokerAnnouncement.Id], nil } diff --git a/core/router/server_test.go b/core/router/server_test.go deleted file mode 100644 index 7c6c30c42..000000000 --- a/core/router/server_test.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "fmt" - "math/rand" - "net" - "sync" - "testing" - "time" - - "golang.org/x/net/context" - - "google.golang.org/grpc" - "google.golang.org/grpc/metadata" - - "github.com/TheThingsNetwork/ttn/api" - pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/router/gateway" - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" - . "github.com/smartystreets/assertions" -) - -func randomPort() uint { - rand.Seed(time.Now().UnixNano()) - port := rand.Intn(5000) + 5000 - return uint(port) -} - -func buildTestRouterServer(t *testing.T, port uint) (*router, *grpc.Server) { - lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) - if err != nil { - panic(err) - } - r := &router{ - Component: &core.Component{ - Ctx: GetLogger(t, "TestRouterServer"), - }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, - brokerDiscovery: &mockBrokerDiscovery{}, - } - s := grpc.NewServer() - r.RegisterRPC(s) - go s.Serve(lis) - return r, s -} - -func TestGatewayStatusRPC(t *testing.T) { - a := New(t) - - port := randomPort() - r, s := buildTestRouterServer(t, port) - defer s.Stop() - - eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewRouterClient(conn) - md := metadata.Pairs( - "token", "token", - "gateway_eui", eui.String(), - ) - ctx := metadata.NewContext(context.Background(), md) - stream, err := client.GatewayStatus(ctx) - if err != nil { - panic(err) - } - statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} - stream.Send(statusMessage) - ack, err := stream.CloseAndRecv() - a.So(err, ShouldBeNil) - a.So(ack, ShouldResemble, &api.Ack{}) - - <-time.After(5 * time.Millisecond) - - status, err := r.getGateway(eui).Status.Get() - a.So(err, ShouldBeNil) - a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, *statusMessage) -} - -func TestUplinkRPC(t *testing.T) { - a := New(t) - - port := randomPort() - r, s := buildTestRouterServer(t, port) - defer s.Stop() - - eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewRouterClient(conn) - md := metadata.Pairs( - "token", "token", - "gateway_eui", eui.String(), - ) - ctx := metadata.NewContext(context.Background(), md) - stream, err := client.Uplink(ctx) - if err != nil { - panic(err) - } - stream.Send(newReferenceUplink()) - ack, err := stream.CloseAndRecv() - a.So(err, ShouldBeNil) - a.So(ack, ShouldResemble, &api.Ack{}) - - <-time.After(5 * time.Millisecond) - - utilization := r.getGateway(eui).Utilization - utilization.Tick() - rx, _ := utilization.Get() - a.So(rx, ShouldBeGreaterThan, 0) -} - -func TestSubscribeRPC(t *testing.T) { - a := New(t) - - port := randomPort() - r, s := buildTestRouterServer(t, port) - defer s.Stop() - - eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewRouterClient(conn) - md := metadata.Pairs( - "token", "token", - "gateway_eui", eui.String(), - ) - ctx := metadata.NewContext(context.Background(), md) - - stream, err := client.Subscribe(ctx, &pb.SubscribeRequest{}) - a.So(err, ShouldBeNil) - - downlink := &pb.DownlinkMessage{Payload: []byte{1}} - - var wg sync.WaitGroup - go func() { - dl, err := stream.Recv() - a.So(err, ShouldBeNil) - a.So(*dl, ShouldResemble, *downlink) - wg.Done() - }() - - wg.Add(1) - schedule := r.getGateway(eui).Schedule - gateway.Deadline = 1 // Extremely short deadline - schedule.Sync(0) - id, _ := schedule.GetOption(300, 50) - schedule.Schedule(id, downlink) - - wg.Wait() -} - -func TestActivateRPC(t *testing.T) { - a := New(t) - - port := randomPort() - r, s := buildTestRouterServer(t, port) - defer s.Stop() - - eui := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} - appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), api.DialOptions...) - if err != nil { - panic(err) - } - defer conn.Close() - client := pb.NewRouterClient(conn) - md := metadata.Pairs( - "token", "token", - "gateway_eui", eui.String(), - ) - ctx := metadata.NewContext(context.Background(), md) - uplink := newReferenceUplink() - activation := &pb.DeviceActivationRequest{ - Payload: []byte{}, - ProtocolMetadata: uplink.ProtocolMetadata, - GatewayMetadata: uplink.GatewayMetadata, - AppEui: &appEUI, - DevEui: &devEUI, - } - res, err := client.Activate(ctx, activation) - a.So(res, ShouldBeNil) - a.So(err, ShouldNotBeNil) - - <-time.After(5 * time.Millisecond) - - utilization := r.getGateway(eui).Utilization - utilization.Tick() - rx, _ := utilization.Get() - a.So(rx, ShouldBeGreaterThan, 0) -} diff --git a/utils/security/generate_keys.go b/utils/security/generate_keys.go new file mode 100644 index 000000000..73a03e612 --- /dev/null +++ b/utils/security/generate_keys.go @@ -0,0 +1,96 @@ +package security + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "io/ioutil" + "math/big" + "net" + "path/filepath" + "time" +) + +var ( + validFor = 365 * 24 * time.Hour +) + +func GenerateKeys(location string, hostnames ...string) error { + // Generate private key + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + privBytes, err := x509.MarshalECPrivateKey(key) + if err != nil { + return err + } + privPEM := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: privBytes, + }) + pubBytes, err := x509.MarshalPKIXPublicKey(key.Public()) + if err != nil { + return err + } + pubPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubBytes, + }) + + // Build Certificate + notBefore := time.Now() + notAfter := notBefore.Add(validFor) + serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) + serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) + if err != nil { + return err + } + template := x509.Certificate{ + SerialNumber: serialNumber, + Subject: pkix.Name{ + Organization: []string{"The Things Network"}, + }, + IsCA: true, + NotBefore: notBefore, + NotAfter: notAfter, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + BasicConstraintsValid: true, + } + for _, h := range hostnames { + if ip := net.ParseIP(h); ip != nil { + template.IPAddresses = append(template.IPAddresses, ip) + } else { + template.DNSNames = append(template.DNSNames, h) + } + } + + // Generate certificate + certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key) + if err != nil { + return err + } + certPEM := pem.EncodeToMemory(&pem.Block{ + Type: "CERTIFICATE", + Bytes: certBytes, + }) + + err = ioutil.WriteFile(filepath.Clean(location+"/server.pub"), pubPEM, 0644) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Clean(location+"/server.key"), privPEM, 0600) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Clean(location+"/server.cert"), certPEM, 0644) + if err != nil { + return err + } + + return nil +} diff --git a/utils/security/jwt.go b/utils/security/jwt.go new file mode 100644 index 000000000..2b192fa89 --- /dev/null +++ b/utils/security/jwt.go @@ -0,0 +1,55 @@ +package security + +import ( + "crypto/ecdsa" + "errors" + "fmt" + "time" + + "github.com/dgrijalva/jwt-go" +) + +// BuildJWT builds a JSON Web Token for the given subject and ttl, and signs it with the given private key +func BuildJWT(subject string, ttl time.Duration, privateKey []byte) (token string, err error) { + claims := jwt.StandardClaims{ + Subject: subject, + IssuedAt: time.Now().Unix(), + NotBefore: time.Now().Unix(), + } + if ttl > 0 { + claims.ExpiresAt = time.Now().Add(ttl).Unix() + } + tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + var key *ecdsa.PrivateKey + key, err = jwt.ParseECPrivateKeyFromPEM(privateKey) + if err != nil { + return + } + token, err = tokenBuilder.SignedString(key) + if err != nil { + return + } + return +} + +// ValidateJWT validates a JSON Web Token with the given public key +func ValidateJWT(token string, publicKey []byte) (*jwt.StandardClaims, error) { + claims := &jwt.StandardClaims{} + parsed, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + key, err := jwt.ParseECPublicKeyFromPEM(publicKey) + if err != nil { + return nil, err + } + return key, nil + }) + if err != nil { + return nil, fmt.Errorf("Unable to parse token: %s", err.Error()) + } + if !parsed.Valid { + return nil, errors.New("The token is not valid or is expired") + } + return claims, nil +} diff --git a/utils/security/load_keys.go b/utils/security/load_keys.go new file mode 100644 index 000000000..74b78f13c --- /dev/null +++ b/utils/security/load_keys.go @@ -0,0 +1,22 @@ +package security + +import ( + "io/ioutil" + "path/filepath" +) + +func LoadKeys(location string) (pubKey, privKey, cert []byte, err error) { + pubKey, err = ioutil.ReadFile(filepath.Clean(location + "/server.pub")) + if err != nil { + return + } + privKey, err = ioutil.ReadFile(filepath.Clean(location + "/server.key")) + if err != nil { + return + } + cert, err = ioutil.ReadFile(filepath.Clean(location + "/server.cert")) + if err != nil { + return + } + return +} diff --git a/utils/security/security_test.go b/utils/security/security_test.go new file mode 100644 index 000000000..3c8a4b4bc --- /dev/null +++ b/utils/security/security_test.go @@ -0,0 +1 @@ +package security From 33bf4b285a58b4b244c90755826989bb80e8eb81 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 22 Jul 2016 10:05:43 +0200 Subject: [PATCH 1609/2266] use account server error messages if they exist --- core/account/util/http.go | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index 371fb8df9..3388cd718 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -22,8 +22,8 @@ var ( // it is not an error with executing the request itself, it is // an error the server is flaggin to the client. type HTTPError struct { - Code int - Message string + Code int `json:"code"` + Message string `json:"error"` } func (e HTTPError) Error() string { @@ -92,10 +92,29 @@ func performRequest(server, accessToken, method, URI string, body, res interface } if resp.StatusCode >= 400 { - return HTTPError{ - Code: resp.StatusCode, - Message: resp.Status, + + var herr HTTPError + defer resp.Body.Close() + decoder := json.NewDecoder(resp.Body) + if err := decoder.Decode(&herr); err != nil { + // could not decode body as error, just return http error + return HTTPError{ + Code: resp.StatusCode, + Message: resp.Status[4:], + } + } + + // fill in blank code + if herr.Code == 0 { + herr.Code = resp.StatusCode + } + + // fill in blank message + if herr.Message == "" { + herr.Message = resp.Status[4:] } + + return herr } if resp.StatusCode == 307 { From 7f8cb05ae3e5fd8876cf36321c95c66ae71f070d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 22 Jul 2016 11:25:34 +0200 Subject: [PATCH 1610/2266] fix errors against api --- core/account/applications.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index 779196769..fadf61d29 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -45,9 +45,16 @@ func (a *Account) DeleteApplication(appID string) error { return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID)) } +type grantReq struct { + Rights []types.Right `json:"rights"` +} + // Grant adds a collaborator to the application func (a *Account) Grant(appID string, username string, rights []types.Right) error { - return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), rights, nil) + req := grantReq{ + Rights: rights, + } + return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), req, nil) } // Retract removes rights from a collaborator of the application @@ -91,7 +98,7 @@ func (a *Account) ChangeName(appID string, name string) (app Application, err er // AddEUI adds an EUI to the applications list of EUIs func (a *Account) AddEUI(appID string, eui types.AppEUI) error { - return util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) + return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) } // RemoveEUI removes the specified EUI from the application From ed0d339862ec1d98bd2fe1e75255199004d8163c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 22 Jul 2016 15:20:07 +0200 Subject: [PATCH 1611/2266] make account totally oauth ready --- core/account/account.go | 77 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/core/account/account.go b/core/account/account.go index 53168733c..50f448a3a 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -3,20 +3,87 @@ package account +import ( + "github.com/TheThingsNetwork/ttn/core/account/util" + "golang.org/x/oauth2" +) + // Account is a proxy to an account on the account server type Account struct { // server is the server where the account lives server string - // accessToken is the accessToken that gives this client the - // right to act on behalf of the account - accessToken string + // login is the login strategy used by the account to log in + tokenSource oauth2.TokenSource } // New creates a new Account for the given server and accessToken -func New(server string, accessToken string) *Account { +func New(server string, source oauth2.TokenSource) *Account { return &Account{ server: server, - accessToken: accessToken, + tokenSource: source, + } +} + +// Token returns the last valid accessToken/refreshToken pair +// and should be used to store the latest token after doing things +// with an Account. If you fail to do this, the token might have refreshed +// and restoring your last copy will return an invalid token +func (a *Account) Token() (*oauth2.Token, error) { + return a.tokenSource.Token() +} + +// AccessToken returns a valid access token for the account, +// refreshing it if necessary +func (a *Account) AccessToken() (accessToken string, err error) { + token, err := a.Token() + if err != nil { + return accessToken, err + } + return token.AccessToken, nil +} + +func (a *Account) get(URI string, res interface{}) error { + accessToken, err := a.AccessToken() + if err != nil { + return err + } + + return util.GET(a.server, accessToken, URI, res) +} + +func (a *Account) put(URI string, body, res interface{}) error { + accessToken, err := a.AccessToken() + if err != nil { + return err } + + return util.PUT(a.server, accessToken, URI, body, res) +} + +func (a *Account) post(URI string, body, res interface{}) error { + accessToken, err := a.AccessToken() + if err != nil { + return err + } + + return util.POST(a.server, accessToken, URI, body, res) +} + +func (a *Account) patch(URI string, body, res interface{}) error { + accessToken, err := a.AccessToken() + if err != nil { + return err + } + + return util.PATCH(a.server, accessToken, URI, body, res) +} + +func (a *Account) del(URI string) error { + accessToken, err := a.AccessToken() + if err != nil { + return err + } + + return util.DELETE(a.server, accessToken, URI) } From cbc20459eab90cac1490341e281fbd5c1ef6977e Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 22 Jul 2016 15:20:54 +0200 Subject: [PATCH 1612/2266] move to account specific requests --- core/account/applications.go | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/core/account/applications.go b/core/account/applications.go index fadf61d29..2ad941980 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -6,19 +6,18 @@ package account import ( "fmt" - "github.com/TheThingsNetwork/ttn/core/account/util" "github.com/TheThingsNetwork/ttn/core/types" ) // ListApplications list all applications func (a *Account) ListApplications() (apps []Application, err error) { - err = util.GET(a.server, a.accessToken, "/applications", &apps) + err = a.get("/applications", &apps) return apps, err } // FindApplication gets a specific application from the account server func (a *Account) FindApplication(appID string) (app Application, err error) { - err = util.GET(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), &app) + err = a.get(fmt.Sprintf("/applications/%s", appID), &app) return app, err } @@ -36,13 +35,13 @@ func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppE EUIs: EUIs, } - err = util.POST(a.server, a.accessToken, "/applications", &body, &app) + err = a.post("/applications", &body, &app) return app, err } // DeleteApplication deletes an application func (a *Account) DeleteApplication(appID string) error { - return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID)) + return a.del(fmt.Sprintf("/applications/%s", appID)) } type grantReq struct { @@ -54,12 +53,12 @@ func (a *Account) Grant(appID string, username string, rights []types.Right) err req := grantReq{ Rights: rights, } - return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), req, nil) + return a.put(fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), req, nil) } // Retract removes rights from a collaborator of the application func (a *Account) Retract(appID string, username string) error { - return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/collaborators/%s", appID, username)) + return a.del(fmt.Sprintf("/applications/%s/collaborators/%s", appID, username)) } type addAccessKeyReq struct { @@ -74,13 +73,13 @@ func (a *Account) AddAccessKey(appID string, name string, rights []types.Right) Name: name, Rights: rights, } - err = util.POST(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys", appID), body, &key) + err = a.post(fmt.Sprintf("/applications/%s/access-keys", appID), body, &key) return key, err } // RemoveAccessKey removes the specified access key from the application func (a *Account) RemoveAccessKey(appID string, name string) error { - return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/access-keys/%s", appID, name)) + return a.del(fmt.Sprintf("/applications/%s/access-keys/%s", appID, name)) } type editAppReq struct { @@ -92,16 +91,16 @@ func (a *Account) ChangeName(appID string, name string) (app Application, err er body := editAppReq{ Name: name, } - err = util.PATCH(a.server, a.accessToken, fmt.Sprintf("/applications/%s", appID), body, &app) + err = a.patch(fmt.Sprintf("/applications/%s", appID), body, &app) return app, err } // AddEUI adds an EUI to the applications list of EUIs func (a *Account) AddEUI(appID string, eui types.AppEUI) error { - return util.PUT(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) + return a.put(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) } // RemoveEUI removes the specified EUI from the application func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { - return util.DELETE(a.server, a.accessToken, fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) + return a.del(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) } From e55db85ebedc18962ed41839cbe734222c154bac Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 22 Jul 2016 15:21:10 +0200 Subject: [PATCH 1613/2266] add helper to create oauth config --- core/account/util/oauth.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 core/account/util/oauth.go diff --git a/core/account/util/oauth.go b/core/account/util/oauth.go new file mode 100644 index 000000000..28767ec39 --- /dev/null +++ b/core/account/util/oauth.go @@ -0,0 +1,26 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + + "golang.org/x/oauth2" +) + +// MakeConfig creates an oauth.Config object based on the necessary parameters, +// redirectURL can be left empty if not needed +func MakeConfig(server string, clientID string, clientSecret string, redirectURL string) oauth2.Config { + endpoint := oauth2.Endpoint{ + TokenURL: fmt.Sprintf("%s/users/token", server), + AuthURL: fmt.Sprintf("%s/users/authorize", server), + } + + return oauth2.Config{ + ClientID: "ttnctl", + ClientSecret: "", + Endpoint: endpoint, + RedirectURL: redirectURL, + } +} From b0cab93a0e11c36ec4569a91ebd7fb474e887044 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 22 Jul 2016 15:23:01 +0200 Subject: [PATCH 1614/2266] add comment on Validate --- core/account/util/validate.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/account/util/validate.go b/core/account/util/validate.go index 82dd6b11d..5b3b57a66 100644 --- a/core/account/util/validate.go +++ b/core/account/util/validate.go @@ -21,6 +21,8 @@ func validateSlice(val interface{}) error { return nil } +// Validate recursivly validates most structures using govalidator +// struct tags. It currently works for slices, structs and pointers. func Validate(val interface{}) error { switch reflect.TypeOf(val).Kind() { case reflect.Slice: From a71bee26e1b9402711e2215f4727e5405e364e22 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 11:24:40 +0200 Subject: [PATCH 1615/2266] PATCH should use PATCH --- core/account/util/http.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index 3388cd718..5ba743dcf 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -170,5 +170,5 @@ func PUT(server, accessToken, URI string, body, res interface{}) error { // PATCH creates an HTTP Patch request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres func PATCH(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "POST", URI, body, res, MaxRedirects) + return performRequest(server, accessToken, "PATCH", URI, body, res, MaxRedirects) } From 63e7243c6e135e8ab5fcca0e2001c4e68da58d16 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 11:28:26 +0200 Subject: [PATCH 1616/2266] guard against nil tokenSource --- core/account/account.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/account/account.go b/core/account/account.go index 50f448a3a..0c7cd4bc4 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -4,6 +4,8 @@ package account import ( + "fmt" + "github.com/TheThingsNetwork/ttn/core/account/util" "golang.org/x/oauth2" ) @@ -30,6 +32,9 @@ func New(server string, source oauth2.TokenSource) *Account { // with an Account. If you fail to do this, the token might have refreshed // and restoring your last copy will return an invalid token func (a *Account) Token() (*oauth2.Token, error) { + if a.tokenSource == nil { + return nil, fmt.Errorf("Could not get credentials for account") + } return a.tokenSource.Token() } From 26c85477ba2295a7013bff29124a2def63999828 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 11:28:41 +0200 Subject: [PATCH 1617/2266] add constructor for static tokens --- core/account/account.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/account/account.go b/core/account/account.go index 0c7cd4bc4..1765b50db 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -27,6 +27,19 @@ func New(server string, source oauth2.TokenSource) *Account { } } +// WithAccessToken creates a new Account that just has an accessToken, +// which it cannot refresh itself. This is useful if you need to manage +// the token refreshes outside of the Account. +func WithAccessToken(server, accessToken string) *Account { + source := oauth2.StaticTokenSource(&oauth2.Token{ + AccessToken: accessToken, + }) + return &Account{ + server: server, + tokenSource: source, + } +} + // Token returns the last valid accessToken/refreshToken pair // and should be used to store the latest token after doing things // with an Account. If you fail to do this, the token might have refreshed From a44f63278c8c027605e9f66b78743dabfc3edd57 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 11:29:11 +0200 Subject: [PATCH 1618/2266] add user types --- core/account/types.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/account/types.go b/core/account/types.go index eff173bb6..76a59f36e 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -34,3 +34,16 @@ func (c *Collaborator) HasRight(right types.Right) bool { } return false } + +// Profile represents the profile of a user +type Profile struct { + Username string `json:"username"` + Email string `json:"email"` + Name *Name `json:"name"` +} + +// Name represents the full name of a user +type Name struct { + First string `json:"first"` + Last string `json:"last"` +} From acf79016166121654776915c951473b44a21e2ba Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 11:31:27 +0200 Subject: [PATCH 1619/2266] add registration and profile editing --- core/account/users.go | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 core/account/users.go diff --git a/core/account/users.go b/core/account/users.go new file mode 100644 index 000000000..1501c2aa3 --- /dev/null +++ b/core/account/users.go @@ -0,0 +1,78 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/core/account/util" +) + +type registerUserReq struct { + Username string `json:"username,omitempty"` + Email string `json:"email"` + Password string `json:"password"` +} + +// RegisterUser registers a new user with the specified username, email and +// password on the specified account server +func RegisterUser(server, username, email, password string) error { + user := registerUserReq{ + Username: username, + Email: email, + Password: password, + } + + err := util.POST(server, "", "/api/users", user, nil) + if err != nil { + return fmt.Errorf("Could not register user: %s", err) + } + return nil +} + +// Profile gets the user profile of the user that is logged in +func (a *Account) Profile() (user Profile, err error) { + err = a.get("/api/users/me", &user) + if err != nil { + return user, fmt.Errorf("Could not get user profile: %s", err) + } + + return user, nil +} + +type nameReq struct { + First string `json:"first,omitemtpy"` + Last string `json:"last,omitempty"` +} + +type editProfileReq struct { + Username string `json:"username,omitempty"` + Email string `json:"email,omitempty"` + Name *Name `json:"name,omitempty"` +} + +// EditProfile edits the users profile. You can change only +// part of the profile (only the name, for instance) by +// omitting the other fields from the passed in Profile struct. +func (a *Account) EditProfile(profile Profile) error { + var edits editProfileReq + + if profile.Username != "" { + edits.Username = profile.Username + } + + if profile.Email != "" { + edits.Email = profile.Email + } + + if profile.Name != nil { + edits.Name = profile.Name + } + + err := a.patch("/api/users/me", edits, nil) + if err != nil { + return fmt.Errorf("Could not update profile: %s", err) + } + return err +} From cbb73c24884bc4e4f8244a7b44adc1cb27c5f937 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 11:40:55 +0200 Subject: [PATCH 1620/2266] add constructor that logs in user with username and password --- core/account/account.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/account/account.go b/core/account/account.go index 1765b50db..8e8c2c5eb 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/TheThingsNetwork/ttn/core/account/util" + "golang.org/x/net/context" "golang.org/x/oauth2" ) @@ -40,6 +41,22 @@ func WithAccessToken(server, accessToken string) *Account { } } +// WithCredentials creates a new Account that logs in to +// the specified account server using the specified username and password. +func WithCredentials(server, clientID, clientSecret, username, password string) (*Account, error) { + config := util.MakeConfig(server, clientID, clientSecret, "") + token, err := config.PasswordCredentialsToken(context.TODO(), username, password) + if err != nil { + return nil, err + } + + source := config.TokenSource(context.TODO(), token) + return &Account{ + server: server, + tokenSource: source, + }, nil +} + // Token returns the last valid accessToken/refreshToken pair // and should be used to store the latest token after doing things // with an Account. If you fail to do this, the token might have refreshed From d5174d3863cb2b42ad6bfced72a80a7cbac777c7 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 13:42:52 +0200 Subject: [PATCH 1621/2266] remove token management from Account --- core/account/account.go | 111 ++++++---------------------------------- 1 file changed, 17 insertions(+), 94 deletions(-) diff --git a/core/account/account.go b/core/account/account.go index 8e8c2c5eb..77d305a34 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -3,122 +3,45 @@ package account -import ( - "fmt" +import "github.com/TheThingsNetwork/ttn/core/account/util" - "github.com/TheThingsNetwork/ttn/core/account/util" - "golang.org/x/net/context" - "golang.org/x/oauth2" -) - -// Account is a proxy to an account on the account server +// Account is a client to an account server type Account struct { - // server is the server where the account lives - server string - - // login is the login strategy used by the account to log in - tokenSource oauth2.TokenSource -} - -// New creates a new Account for the given server and accessToken -func New(server string, source oauth2.TokenSource) *Account { - return &Account{ - server: server, - tokenSource: source, - } + server string + accessToken string } -// WithAccessToken creates a new Account that just has an accessToken, -// which it cannot refresh itself. This is useful if you need to manage -// the token refreshes outside of the Account. -func WithAccessToken(server, accessToken string) *Account { - source := oauth2.StaticTokenSource(&oauth2.Token{ - AccessToken: accessToken, - }) +// New creates a new accoun client that will use the +// accessToken to make requests to the specified account server +func New(server, accessToken string) *Account { return &Account{ server: server, - tokenSource: source, + accessToken: accessToken, } } -// WithCredentials creates a new Account that logs in to -// the specified account server using the specified username and password. -func WithCredentials(server, clientID, clientSecret, username, password string) (*Account, error) { - config := util.MakeConfig(server, clientID, clientSecret, "") - token, err := config.PasswordCredentialsToken(context.TODO(), username, password) - if err != nil { - return nil, err - } - - source := config.TokenSource(context.TODO(), token) - return &Account{ - server: server, - tokenSource: source, - }, nil -} - -// Token returns the last valid accessToken/refreshToken pair -// and should be used to store the latest token after doing things -// with an Account. If you fail to do this, the token might have refreshed -// and restoring your last copy will return an invalid token -func (a *Account) Token() (*oauth2.Token, error) { - if a.tokenSource == nil { - return nil, fmt.Errorf("Could not get credentials for account") - } - return a.tokenSource.Token() -} - -// AccessToken returns a valid access token for the account, -// refreshing it if necessary -func (a *Account) AccessToken() (accessToken string, err error) { - token, err := a.Token() - if err != nil { - return accessToken, err - } - return token.AccessToken, nil +// SetToken changes the accessToken the account client uses to +// makes requests. +func (a *Account) SetToken(accessToken string) { + a.accessToken = accessToken } func (a *Account) get(URI string, res interface{}) error { - accessToken, err := a.AccessToken() - if err != nil { - return err - } - - return util.GET(a.server, accessToken, URI, res) + return util.GET(a.server, a.accessToken, URI, res) } func (a *Account) put(URI string, body, res interface{}) error { - accessToken, err := a.AccessToken() - if err != nil { - return err - } - - return util.PUT(a.server, accessToken, URI, body, res) + return util.PUT(a.server, a.accessToken, URI, body, res) } func (a *Account) post(URI string, body, res interface{}) error { - accessToken, err := a.AccessToken() - if err != nil { - return err - } - - return util.POST(a.server, accessToken, URI, body, res) + return util.POST(a.server, a.accessToken, URI, body, res) } func (a *Account) patch(URI string, body, res interface{}) error { - accessToken, err := a.AccessToken() - if err != nil { - return err - } - - return util.PATCH(a.server, accessToken, URI, body, res) + return util.PATCH(a.server, a.accessToken, URI, body, res) } func (a *Account) del(URI string) error { - accessToken, err := a.AccessToken() - if err != nil { - return err - } - - return util.DELETE(a.server, accessToken, URI) + return util.DELETE(a.server, a.accessToken, URI) } From dfb8819df123f84f128e84a021cb122d59406c5a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 25 Jul 2016 11:44:36 +0100 Subject: [PATCH 1622/2266] Remove double appID and devID from MQTT Publish --- core/handler/mqtt.go | 4 +-- core/handler/mqtt_test.go | 4 ++- mqtt/client.go | 18 +++++----- mqtt/client_test.go | 70 +++++++++++++++++++++++++++++++-------- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 921811edc..6cd4e8e65 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -44,7 +44,7 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e "DevID": up.DevID, "AppID": up.AppID, }).Debug("Publish Uplink") - token := h.mqttClient.PublishUplink(up.AppID, up.DevID, *up) + token := h.mqttClient.PublishUplink(*up) go func() { if token.WaitTimeout(MQTTTimeout) { if token.Error() != nil { @@ -66,7 +66,7 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e "AppEUI": activation.AppEUI, "DevAddr": activation.DevAddr, }).Debug("Publish Activation") - token := h.mqttClient.PublishActivation(activation.AppID, activation.DevID, *activation) + token := h.mqttClient.PublishActivation(*activation) go func() { if token.WaitTimeout(MQTTTimeout) { if token.Error() != nil { diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 2afa5c411..27dc272f4 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -33,7 +33,9 @@ func TestHandleMQTT(t *testing.T) { err := h.HandleMQTT("", "", "tcp://localhost:1883") a.So(err, ShouldBeNil) - c.PublishDownlink(appID, devID, mqtt.DownlinkMessage{ + c.PublishDownlink(mqtt.DownlinkMessage{ + AppID: appID, + DevID: devID, Payload: []byte{0xAA, 0xBC}, }).Wait() <-time.After(50 * time.Millisecond) diff --git a/mqtt/client.go b/mqtt/client.go index 79ee32fc2..c5a22e69f 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -23,7 +23,7 @@ type Client interface { IsConnected() bool // Uplink pub/sub - PublishUplink(appID string, devID string, payload UplinkMessage) Token + PublishUplink(payload UplinkMessage) Token SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token SubscribeAppUplink(appID string, handler UplinkHandler) Token SubscribeUplink(handler UplinkHandler) Token @@ -32,7 +32,7 @@ type Client interface { UnsubscribeUplink() Token // Downlink pub/sub - PublishDownlink(appID string, devID string, payload DownlinkMessage) Token + PublishDownlink(payload DownlinkMessage) Token SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token SubscribeAppDownlink(appID string, handler DownlinkHandler) Token SubscribeDownlink(handler DownlinkHandler) Token @@ -41,7 +41,7 @@ type Client interface { UnsubscribeDownlink() Token // Activation pub/sub - PublishActivation(appID string, devID string, payload Activation) Token + PublishActivation(payload Activation) Token SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token SubscribeAppActivations(appID string, handler ActivationHandler) Token SubscribeActivations(handler ActivationHandler) Token @@ -176,8 +176,8 @@ func (c *defaultClient) IsConnected() bool { return c.mqtt.IsConnected() } -func (c *defaultClient) PublishUplink(appID string, devID string, dataUp UplinkMessage) Token { - topic := DeviceTopic{appID, devID, Uplink} +func (c *defaultClient) PublishUplink(dataUp UplinkMessage) Token { + topic := DeviceTopic{dataUp.AppID, dataUp.DevID, Uplink} msg, err := json.Marshal(dataUp) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -228,8 +228,8 @@ func (c *defaultClient) UnsubscribeUplink() Token { return c.UnsubscribeDeviceUplink("", "") } -func (c *defaultClient) PublishDownlink(appID string, devID string, dataDown DownlinkMessage) Token { - topic := DeviceTopic{appID, devID, Downlink} +func (c *defaultClient) PublishDownlink(dataDown DownlinkMessage) Token { + topic := DeviceTopic{dataDown.AppID, dataDown.DevID, Downlink} msg, err := json.Marshal(dataDown) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -279,8 +279,8 @@ func (c *defaultClient) UnsubscribeDownlink() Token { return c.UnsubscribeDeviceDownlink("", "") } -func (c *defaultClient) PublishActivation(appID string, devID string, activation Activation) Token { - topic := DeviceTopic{appID, devID, Activations} +func (c *defaultClient) PublishActivation(activation Activation) Token { + topic := DeviceTopic{activation.AppID, activation.DevID, Activations} msg, err := json.Marshal(activation) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} diff --git a/mqtt/client_test.go b/mqtt/client_test.go index a4e760ca2..171a5f699 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -109,10 +109,12 @@ func TestPublishUplink(t *testing.T) { defer c.Disconnect() dataUp := UplinkMessage{ + AppID: "someid", + DevID: "someid", Payload: []byte{0x01, 0x02, 0x03, 0x04}, } - token := c.PublishUplink("someid", "someid", dataUp) + token := c.PublishUplink(dataUp) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -186,7 +188,11 @@ func TestPubSubUplink(t *testing.T) { wg.Done() }).Wait() - c.PublishUplink("app1", "dev1", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink(UplinkMessage{ + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app1", + DevID: "dev1", + }).Wait() wg.Wait() @@ -209,8 +215,16 @@ func TestPubSubAppUplink(t *testing.T) { wg.Done() }).Wait() - c.PublishUplink("app2", "dev1", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - c.PublishUplink("app2", "dev2", UplinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishUplink(UplinkMessage{ + AppID: "app2", + DevID: "dev1", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }).Wait() + c.PublishUplink(UplinkMessage{ + AppID: "app2", + DevID: "dev2", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }).Wait() wg.Wait() @@ -226,10 +240,12 @@ func TestPublishDownlink(t *testing.T) { defer c.Disconnect() dataDown := DownlinkMessage{ + AppID: "someid", + DevID: "someid", Payload: []byte{0x01, 0x02, 0x03, 0x04}, } - token := c.PublishDownlink("someid", "someid", dataDown) + token := c.PublishDownlink(dataDown) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -303,7 +319,11 @@ func TestPubSubDownlink(t *testing.T) { wg.Done() }).Wait() - c.PublishDownlink("app3", "dev3", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink(DownlinkMessage{ + AppID: "app3", + DevID: "dev3", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }).Wait() wg.Wait() @@ -326,8 +346,16 @@ func TestPubSubAppDownlink(t *testing.T) { wg.Done() }).Wait() - c.PublishDownlink("app4", "dev1", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() - c.PublishDownlink("app4", "dev2", DownlinkMessage{Payload: []byte{0x01, 0x02, 0x03, 0x04}}).Wait() + c.PublishDownlink(DownlinkMessage{ + AppID: "app4", + DevID: "dev1", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }).Wait() + c.PublishDownlink(DownlinkMessage{ + AppID: "app4", + DevID: "dev2", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }).Wait() wg.Wait() @@ -342,9 +370,13 @@ func TestPublishActivations(t *testing.T) { c.Connect() defer c.Disconnect() - dataActivations := Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}} + dataActivations := Activation{ + AppID: "someid", + DevID: "someid", + Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + } - token := c.PublishActivation("someid", "someid", dataActivations) + token := c.PublishActivation(dataActivations) token.Wait() a.So(token.Error(), ShouldBeNil) @@ -418,7 +450,11 @@ func TestPubSubActivations(t *testing.T) { wg.Done() }).Wait() - c.PublishActivation("app5", "dev1", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation(Activation{ + AppID: "app5", + DevID: "dev1", + Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + }).Wait() wg.Wait() @@ -441,8 +477,16 @@ func TestPubSubAppActivations(t *testing.T) { wg.Done() }).Wait() - c.PublishActivation("app6", "dev1", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() - c.PublishActivation("app6", "dev2", Activation{Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}}).Wait() + c.PublishActivation(Activation{ + AppID: "app6", + DevID: "dev1", + Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + }).Wait() + c.PublishActivation(Activation{ + AppID: "app6", + DevID: "dev2", + Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + }).Wait() wg.Wait() From a3a5595fda14971f281e47b5a20696a3c81f1bad Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 25 Jul 2016 12:46:21 +0100 Subject: [PATCH 1623/2266] Check for downlink availability in Router --- core/router/activation.go | 5 +++++ core/router/gateway/schedule.go | 7 ++++++- core/router/uplink.go | 7 ++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/router/activation.go b/core/router/activation.go index 06651511c..68eca669f 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -44,6 +44,11 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Only for LoRaWAN gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) gateway.Utilization.AddRx(uplink) + + if !gateway.Schedule.IsActive() { + return nil, errors.New("Gateway not available for response") + } + downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) // Find Broker diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 828da7039..f5cfb2198 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -27,6 +27,8 @@ type Schedule interface { Schedule(id string, downlink *router_pb.DownlinkMessage) error // Subscribe to downlink messages Subscribe() <-chan *router_pb.DownlinkMessage + // Whether the gateway has active downlink + IsActive() bool // Stop the subscription Stop() } @@ -72,7 +74,6 @@ type schedule struct { sync.RWMutex random *random.TTNRandom ctx log.Interface - active bool offset int64 items map[string]*scheduledItem downlink chan *router_pb.DownlinkMessage @@ -212,3 +213,7 @@ func (s *schedule) Subscribe() <-chan *router_pb.DownlinkMessage { s.downlink = make(chan *router_pb.DownlinkMessage) return s.downlink } + +func (s *schedule) IsActive() bool { + return s.downlink != nil +} diff --git a/core/router/uplink.go b/core/router/uplink.go index 0d5d36df5..5b19459e0 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -85,7 +85,12 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) gateway.Utilization.AddRx(uplink) - downlinkOptions := r.buildDownlinkOptions(uplink, false, gateway) + var downlinkOptions []*pb_broker.DownlinkOption + if gateway.Schedule.IsActive() { + downlinkOptions = r.buildDownlinkOptions(uplink, false, gateway) + } else { + ctx.Warn("Gateway not active") + } ctx = ctx.WithField("DownlinkOptions", len(downlinkOptions)) From 1d66e9b6642d72998b34624485f78aa046e7cc70 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 25 Jul 2016 12:47:01 +0100 Subject: [PATCH 1624/2266] FPort handling and small cleanup in Handler --- core/handler/convert_lorawan.go | 4 ++++ core/handler/downlink.go | 3 +++ core/networkserver/networkserver.go | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index ae7c0dad5..b7ffca2d0 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -9,6 +9,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -106,6 +107,9 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMess // Set Payload if len(appDown.Payload) > 0 { macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.Payload}} + if macPayload.FPort == nil || *macPayload.FPort == 0 { + macPayload.FPort = pointer.Uint8(1) + } } else { macPayload.FRMPayload = []lorawan.Payload{} } diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 22b61b24e..ceb8f025f 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -32,6 +32,9 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { if err != nil { return err } + // Clear redundant fields + appDownlink.AppID = "" + appDownlink.DevID = "" dev.NextDownlink = appDownlink err = h.devices.Set(dev, "next_downlink") if err != nil { diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 1d8027e60..b030ff8ea 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -255,7 +255,6 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag }, FCnt: dev.FCntDown, }, - FPort: macPayload.FPort, }, } phyBytes, err := phy.MarshalBinary() From b065daafd7555368d7246793e5feb59606aa08c5 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 17:23:37 +0200 Subject: [PATCH 1625/2266] add methods for component api --- core/account/components.go | 91 ++++++++++++++++++++++++++++++++++++++ core/account/types.go | 7 +++ 2 files changed, 98 insertions(+) create mode 100644 core/account/components.go diff --git a/core/account/components.go b/core/account/components.go new file mode 100644 index 000000000..db24668f8 --- /dev/null +++ b/core/account/components.go @@ -0,0 +1,91 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account + +import ( + "fmt" + "time" +) + +// ListComponents lists all of the users components +func (a *Account) ListComponents() (components []Component, err error) { + err = a.get("/api/components", &components) + return components, err +} + +// FindComponent finds a comonent of the specified type with the specified id +func (a *Account) FindComponent(typ, id string) (component []Component, err error) { + err = a.get(fmt.Sprintf("/api/components/%s/%s", typ, id), &component) + return component, err +} + +// FindBroker finds a broker with the specified id +func (a *Account) FindBroker(id string) (component []Component, err error) { + return a.FindComponent("broker", id) +} + +// FindRouter finds a router with the specified id +func (a *Account) FindRouter(id string) (component []Component, err error) { + return a.FindComponent("router", id) +} + +// FindHandler finds a handler with the specified id +func (a *Account) FindHandler(id string) (component []Component, err error) { + return a.FindComponent("handler", id) +} + +type createComponentReq struct { + ID string `json:"id" valid:"required"` +} + +// CreateComponent creates a component with the specified type and id +func (a *Account) CreateComponent(typ, id string) error { + body := createComponentReq{ + ID: id, + } + return a.post(fmt.Sprintf("/api/components/%s", typ), body, nil) +} + +// CreateBroker creates a broker with the specified id +func (a *Account) CreateBroker(id string) error { + return a.CreateComponent("broker", id) +} + +// CreateRouter creates a Router with the specified id +func (a *Account) CreateRouter(id string) error { + return a.CreateComponent("router", id) +} + +// CreateHandler creates a handler with the specified id +func (a *Account) CreateHandler(id string) error { + return a.CreateComponent("handler", id) +} + +type componentTokenRes struct { + Token string `json:"token"` + Expires time.Time `json:"expires"` +} + +// ComponentToken fetches a token for the component with the given +// type and id +func (a *Account) ComponentToken(typ, id string) (token string, err error) { + var res componentTokenRes + err = a.get(fmt.Sprintf("/api/components/%s/%s/token", typ, id), &res) + return res.Token, err +} + +// BrokerToken gets the specified brokers token +func (a *Account) BrokerToken(id string) (token string, err error) { + return a.ComponentToken("broker", id) +} + +// RouterToken gets the specified routers token +func (a *Account) RouterToken(id string) (token string, err error) { + return a.ComponentToken("router", id) +} + +// HandlerToken gets the specified handlers token +func (a *Account) HandlerToken(id string) (token string, err error) { + return a.ComponentToken("handler", id) +} diff --git a/core/account/types.go b/core/account/types.go index 76a59f36e..319217901 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -47,3 +47,10 @@ type Name struct { First string `json:"first"` Last string `json:"last"` } + +// Component represents a component on the newtork +type Component struct { + Type string `json:"type"` + ID string `json:"id"` + Created time.Time `json:"created,omitempty"` +} From 03e19de88736b69922914d0f9d6e4104ada03f6c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 17:24:52 +0200 Subject: [PATCH 1626/2266] implement Stringer for Name --- core/account/types.go | 5 +++++ core/account/types_test.go | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/core/account/types.go b/core/account/types.go index 319217901..66d21cc4b 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -54,3 +54,8 @@ type Component struct { ID string `json:"id"` Created time.Time `json:"created,omitempty"` } + +// String implements the Stringer interface for Name +func (n *Name) String() string { + return n.First + " " + n.Last +} diff --git a/core/account/types_test.go b/core/account/types_test.go index a0bd4787c..85f775131 100644 --- a/core/account/types_test.go +++ b/core/account/types_test.go @@ -22,3 +22,13 @@ func TestCollaboratorRights(t *testing.T) { a.So(c.HasRight(types.Right("right")), s.ShouldBeTrue) a.So(c.HasRight(types.Right("foo")), s.ShouldBeFalse) } + +func TestNameString(t *testing.T) { + a := s.New(t) + name := Name{ + First: "John", + Last: "Doe", + } + + a.So(name.String(), s.ShouldEqual, "John Doe") +} From 0cb6dd93a96ca656ad1279700b88ef3c327a4dde Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 25 Jul 2016 17:29:57 +0200 Subject: [PATCH 1627/2266] implement stringer for Right --- core/types/access_keys.go | 4 ++++ core/types/access_keys_test.go | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/core/types/access_keys.go b/core/types/access_keys.go index 6e47182d3..f4501e26a 100644 --- a/core/types/access_keys.go +++ b/core/types/access_keys.go @@ -28,3 +28,7 @@ func (k *AccessKey) HasRight(right Right) bool { } return false } + +func (r *Right) String() string { + return string(*r) +} diff --git a/core/types/access_keys_test.go b/core/types/access_keys_test.go index 2cf1dbf8a..af61a08fd 100644 --- a/core/types/access_keys_test.go +++ b/core/types/access_keys_test.go @@ -22,3 +22,10 @@ func TestAccessKeysRights(t *testing.T) { a.So(c.HasRight(Right("right")), s.ShouldBeTrue) a.So(c.HasRight(Right("foo")), s.ShouldBeFalse) } + +func TestRightString(t *testing.T) { + a := s.New(t) + + right := Right("foo") + a.So(right.String(), s.ShouldEqual, "foo") +} From 8be5752de3ec7f95c84e9f5b1f2b8f060bcadd36 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jul 2016 11:01:01 +0100 Subject: [PATCH 1628/2266] Cleanup - Remove warning from Router uplink - Remove test app from Handler --- core/handler/handler.go | 4 ---- core/router/uplink.go | 2 -- 2 files changed, 6 deletions(-) diff --git a/core/handler/handler.go b/core/handler/handler.go index 51b5953d5..6cb2a086f 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -72,10 +72,6 @@ func (h *handler) Init(c *core.Component) error { return err } - h.applications.Set(&application.Application{ - AppID: "htdvisser", - }) - err = h.Announce() if err != nil { return err diff --git a/core/router/uplink.go b/core/router/uplink.go index 5b19459e0..fc2380462 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -88,8 +88,6 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess var downlinkOptions []*pb_broker.DownlinkOption if gateway.Schedule.IsActive() { downlinkOptions = r.buildDownlinkOptions(uplink, false, gateway) - } else { - ctx.Warn("Gateway not active") } ctx = ctx.WithField("DownlinkOptions", len(downlinkOptions)) From 4f6e1b9f7519c7a81db3a33f923ccff3e7e5d400 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jul 2016 11:06:20 +0100 Subject: [PATCH 1629/2266] Register DevAddrs in Discovery, NetworkServer Token Auth --- cmd/broker.go | 24 ++----------- cmd/broker_register_prefix.go | 65 ++++++++++++++++++++++++++++++++++ cmd/networkserver.go | 2 +- cmd/networkserver_authorize.go | 48 +++++++++++++++++++++++++ core/broker/activation.go | 4 +-- core/broker/broker.go | 14 +++++--- core/broker/downlink.go | 2 +- core/broker/uplink.go | 4 +-- 8 files changed, 132 insertions(+), 31 deletions(-) create mode 100644 cmd/broker_register_prefix.go create mode 100644 cmd/networkserver_authorize.go diff --git a/cmd/broker.go b/cmd/broker.go index 0c01bdc65..708188b7d 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -16,11 +16,8 @@ import ( "google.golang.org/grpc" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -37,7 +34,6 @@ var brokerCmd = &cobra.Command{ "Announce": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port")), "NetworkServer": viper.GetString("broker.networkserver-address"), "DeduplicationDelay": viper.GetString("broker.deduplication-delay"), - "Prefixes": viper.GetStringSlice("broker.prefix"), "Database": fmt.Sprintf("%s/%d", viper.GetString("broker.redis-address"), viper.GetInt("broker.redis-db")), }).Info("Initializing Broker") }, @@ -59,18 +55,6 @@ var brokerCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not initialize component") } - component.Identity.Metadata = []*pb_discovery.Metadata{} - for _, prefixString := range viper.GetStringSlice("broker.prefix") { - prefix, length, err := types.ParseDevAddrPrefix(prefixString) - if err != nil { - ctx.WithError(err).Fatal("Could not initialize broker") - } - component.Identity.Metadata = append(component.Identity.Metadata, &pb_discovery.Metadata{ - Key: pb_discovery.Metadata_PREFIX, - Value: []byte{byte(length), prefix[0], prefix[1], prefix[2], prefix[3]}, - }) - } - var nsCert string if nsCertFile := viper.GetString("broker.networkserver-cert"); nsCertFile != "" { contents, err := ioutil.ReadFile(nsCertFile) @@ -83,10 +67,9 @@ var brokerCmd = &cobra.Command{ // Broker broker := broker.NewRedisBroker( client, - viper.GetString("broker.networkserver-address"), - nsCert, time.Duration(viper.GetInt("broker.deduplication-delay"))*time.Millisecond, ) + broker.SetNetworkServer(viper.GetString("broker.networkserver-address"), nsCert, viper.GetString("broker.networkserver-token")) err = broker.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize broker") @@ -124,9 +107,8 @@ func init() { viper.BindPFlag("broker.networkserver-address", brokerCmd.Flags().Lookup("networkserver-address")) brokerCmd.Flags().String("networkserver-cert", "", "Networkserver certificate to use") viper.BindPFlag("broker.networkserver-cert", brokerCmd.Flags().Lookup("networkserver-cert")) - - brokerCmd.Flags().StringSlice("prefix", []string{"26000000/24"}, "LoRaWAN DevAddr prefix to announce") - viper.BindPFlag("broker.prefix", brokerCmd.Flags().Lookup("prefix")) + brokerCmd.Flags().String("networkserver-token", "", "Networkserver token to use") + viper.BindPFlag("broker.networkserver-token", brokerCmd.Flags().Lookup("networkserver-token")) brokerCmd.Flags().Int("deduplication-delay", 200, "Deduplication delay (in ms)") viper.BindPFlag("broker.deduplication-delay", brokerCmd.Flags().Lookup("deduplication-delay")) diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go new file mode 100644 index 000000000..9b00a87a8 --- /dev/null +++ b/cmd/broker_register_prefix.go @@ -0,0 +1,65 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// brokerRegisterPrefixCmd represents the secure command +var brokerRegisterPrefixCmd = &cobra.Command{ + Use: "register-prefix [prefix ...]", + Short: "Register a prefix to this Broker", + Long: `ttn broker register prefix registers a prefix to this Broker`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.UsageFunc()(cmd) + } + + conn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithInsecure())...) + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Discovery server") + } + client := discovery.NewDiscoveryClient(conn) + + md := metadata.Pairs( + "service-name", "broker", + "id", viper.GetString("id"), + "token", viper.GetString("token"), + ) + dscContext := metadata.NewContext(context.Background(), md) + + for _, prefixString := range args { + ctx := ctx.WithField("Prefix", prefixString) + prefix, length, err := types.ParseDevAddrPrefix(prefixString) + if err != nil { + ctx.WithError(err).Error("Could not register prefix") + continue + } + _, err = client.AddMetadata(dscContext, &discovery.MetadataRequest{ + ServiceName: "broker", + Id: viper.GetString("id"), + Metadata: &discovery.Metadata{ + Key: discovery.Metadata_PREFIX, + Value: []byte{byte(length), prefix[0], prefix[1], prefix[2], prefix[3]}, + }, + }) + if err != nil { + ctx.WithError(err).Error("Could not register prefix") + } + ctx.Info("Registered prefix") + } + }, +} + +func init() { + brokerCmd.AddCommand(brokerRegisterPrefixCmd) +} diff --git a/cmd/networkserver.go b/cmd/networkserver.go index 7c4cbbc01..37c7ccf1d 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -99,7 +99,7 @@ func init() { networkserverCmd.Flags().Int("net-id", 19, "LoRaWAN NetID") viper.BindPFlag("networkserver.net-id", networkserverCmd.Flags().Lookup("net-id")) - networkserverCmd.Flags().String("prefix", "26000000/24", "LoRaWAN DevAddr Prefix that should be used for issuing device addresses") + networkserverCmd.Flags().String("prefix", "26002000/20", "LoRaWAN DevAddr Prefix that should be used for issuing device addresses") viper.BindPFlag("networkserver.prefix", networkserverCmd.Flags().Lookup("prefix")) networkserverCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") diff --git a/cmd/networkserver_authorize.go b/cmd/networkserver_authorize.go new file mode 100644 index 000000000..8e861c3fd --- /dev/null +++ b/cmd/networkserver_authorize.go @@ -0,0 +1,48 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/utils/security" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// networkserverAuthorizeCmd represents the secure command +var networkserverAuthorizeCmd = &cobra.Command{ + Use: "authorize [id]", + Short: "Generate a token that Brokers should use to connect", + Long: `ttn networkserver authorize generates a token that Brokers should use to connect`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + } + + _, priv, _, err := security.LoadKeys(viper.GetString("key-dir")) + if err != nil { + ctx.WithError(err).Fatal("Could not load security keys") + } + ttl, err := cmd.Flags().GetInt("valid") + if err != nil { + ctx.WithError(err).Fatal("Could not read TTL") + } + token, err := security.BuildJWT(args[0], time.Duration(ttl)*time.Hour*24, priv) + if err != nil { + ctx.WithError(err).Fatal("Could not generate a JWT") + } + + ctx.WithField("ID", args[0]).Info("Generated NS token") + fmt.Println() + fmt.Println(token) + fmt.Println() + }, +} + +func init() { + networkserverCmd.AddCommand(networkserverAuthorizeCmd) + networkserverAuthorizeCmd.Flags().Int("valid", 0, "The number of days the token is valid") +} diff --git a/core/broker/activation.go b/core/broker/activation.go index 941dcdff5..a1b81110d 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -74,7 +74,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D } // Send Activate to NS - deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(""), deduplicatedActivationRequest) + deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) if err != nil { return nil, err } @@ -115,7 +115,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D return nil, err } - handlerResponse, err = b.ns.Activate(b.Component.GetContext(""), handlerResponse) + handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) if err != nil { return nil, err } diff --git a/core/broker/broker.go b/core/broker/broker.go index 175408b6b..b72420f3a 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -12,7 +12,6 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" @@ -23,6 +22,8 @@ type Broker interface { core.ComponentInterface core.ManagementInterface + SetNetworkServer(addr, cert, token string) + HandleUplink(uplink *pb.UplinkMessage) error HandleDownlink(downlink *pb.DownlinkMessage) error HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) @@ -33,17 +34,21 @@ type Broker interface { DeactivateHandler(id string) error } -func NewRedisBroker(client *redis.Client, networkserver string, networkserverCert string, timeout time.Duration) Broker { +func NewRedisBroker(client *redis.Client, timeout time.Duration) Broker { return &broker{ routers: make(map[string]chan *pb.DownlinkMessage), handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), uplinkDeduplicator: NewDeduplicator(timeout), activationDeduplicator: NewDeduplicator(timeout), - nsAddr: networkserver, - nsCert: networkserverCert, } } +func (b *broker) SetNetworkServer(addr, cert, token string) { + b.nsAddr = addr + b.nsCert = cert + b.nsToken = token +} + type broker struct { *core.Component routers map[string]chan *pb.DownlinkMessage @@ -53,6 +58,7 @@ type broker struct { handlersLock sync.RWMutex nsAddr string nsCert string + nsToken string ns networkserver.NetworkServerClient nsManager pb_lorawan.DeviceManagerClient uplinkDeduplicator Deduplicator diff --git a/core/broker/downlink.go b/core/broker/downlink.go index a87b3bb52..152d902a1 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -34,7 +34,7 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { } }() - downlink, err = b.ns.Downlink(b.Component.GetContext(""), downlink) + downlink, err = b.ns.Downlink(b.Component.GetContext(b.nsToken), downlink) if err != nil { return err } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 5d5afd810..7e7f150b6 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -72,7 +72,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { devAddr := types.DevAddr(macPayload.FHDR.DevAddr) ctx = ctx.WithField("DevAddr", devAddr) var getDevicesResp *networkserver.DevicesResponse - getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(""), &networkserver.DevicesRequest{ + getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(b.nsToken), &networkserver.DevicesRequest{ DevAddr: &devAddr, FCnt: macPayload.FHDR.FCnt, }) @@ -161,7 +161,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } // Pass Uplink through NS - deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(""), deduplicatedUplink) + deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(b.nsToken), deduplicatedUplink) if err != nil { return err } From 20e10ea8f24649f59ad88125c2f47f408f1f8740 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jul 2016 11:06:45 +0100 Subject: [PATCH 1630/2266] Update Makefile - Remove compilation of old protos - Add install --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c0d4941fc..bd4154a58 100644 --- a/Makefile +++ b/Makefile @@ -51,7 +51,6 @@ proto-deps: $(GOCMD) get -v github.com/gogo/protobuf/protoc-gen-gofast proto: - find core/protos -name '*.proto' | xargs protoc --gofast_out=plugins=grpc:./core -I=core/protos @$(PROTOC)/api/*.proto @$(PROTOC)/api/protocol/protocol.proto @$(PROTOC)/api/protocol/**/*.proto @@ -92,6 +91,9 @@ clean: build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) +install: + $(GOCMD) install -a -installsuffix cgo ${LDFLAGS} . ./ttnctl + docker: TARGET_PLATFORM = linux-amd64 docker: clean $(RELEASE_DIR)/$(ttnbin) docker build -t thethingsnetwork/ttn -f Dockerfile.local . From 5a701dba2d1b894ce73d63ba96cffb3988a71502 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jul 2016 11:07:35 +0100 Subject: [PATCH 1631/2266] Update TTNCTL --- ttnctl/cmd/applications_create.go | 68 ++++++++ ttnctl/cmd/applications_delete.go | 40 +++++ ttnctl/cmd/applications_list.go | 52 ++++++ ttnctl/cmd/applications_pf.go | 21 +-- ttnctl/cmd/applications_pf_set.go | 27 +-- ttnctl/cmd/applications_register.go | 26 +-- ttnctl/cmd/applications_select.go | 106 ++++++++++++ ttnctl/cmd/applications_unregister.go | 44 +++++ ttnctl/cmd/devices.go | 17 +- ttnctl/cmd/devices_create.go | 34 +--- ttnctl/cmd/devices_delete.go | 31 ++-- ttnctl/cmd/devices_info.go | 24 +-- ttnctl/cmd/devices_list.go | 24 +-- ttnctl/cmd/devices_personalize.go | 28 +--- ttnctl/cmd/devices_set.go | 24 +-- ttnctl/cmd/downlink.go | 54 ++++++ ttnctl/cmd/root.go | 42 ++++- ttnctl/cmd/subscribe.go | 60 +++++++ ttnctl/cmd/uplink.go | 131 +++++++++++++++ ttnctl/cmd/user.go | 130 ++++----------- ttnctl/cmd/user_login.go | 37 +++++ ttnctl/cmd/user_logout.go | 27 +++ ttnctl/cmd/user_register.go | 41 +++++ ttnctl/util/account.go | 123 ++++++++++++++ ttnctl/util/applications.go | 60 ------- ttnctl/util/auth.go | 229 -------------------------- ttnctl/util/auth_test.go | 185 --------------------- ttnctl/util/config.go | 107 ++++++++++++ ttnctl/util/context.go | 35 ++++ ttnctl/util/discovery.go | 21 +++ ttnctl/util/handler.go | 38 +++++ ttnctl/util/lorawan.go | 88 ++++++++++ ttnctl/util/mqtt.go | 54 ++++-- ttnctl/util/router.go | 27 +++ ttnctl/util/uplink_metadata.go | 31 ++++ 35 files changed, 1317 insertions(+), 769 deletions(-) create mode 100644 ttnctl/cmd/applications_create.go create mode 100644 ttnctl/cmd/applications_delete.go create mode 100644 ttnctl/cmd/applications_list.go create mode 100644 ttnctl/cmd/applications_select.go create mode 100644 ttnctl/cmd/applications_unregister.go create mode 100644 ttnctl/cmd/downlink.go create mode 100644 ttnctl/cmd/subscribe.go create mode 100644 ttnctl/cmd/uplink.go create mode 100644 ttnctl/cmd/user_login.go create mode 100644 ttnctl/cmd/user_logout.go create mode 100644 ttnctl/cmd/user_register.go create mode 100644 ttnctl/util/account.go delete mode 100644 ttnctl/util/applications.go delete mode 100644 ttnctl/util/auth.go delete mode 100644 ttnctl/util/auth_test.go create mode 100644 ttnctl/util/config.go create mode 100644 ttnctl/util/context.go create mode 100644 ttnctl/util/discovery.go create mode 100644 ttnctl/util/handler.go create mode 100644 ttnctl/util/lorawan.go create mode 100644 ttnctl/util/router.go create mode 100644 ttnctl/util/uplink_metadata.go diff --git a/ttnctl/cmd/applications_create.go b/ttnctl/cmd/applications_create.go new file mode 100644 index 000000000..17116aace --- /dev/null +++ b/ttnctl/cmd/applications_create.go @@ -0,0 +1,68 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +// applicationsCreateCmd is the entrypoint for handlerctl +var applicationsCreateCmd = &cobra.Command{ + Use: "create [AppID] [Description]", + Short: "Create a new application", + Long: `ttnctl applications create can be used to create a new application.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + var euis []types.AppEUI + euiStrings, err := cmd.Flags().GetStringSlice("app-eui") + if err != nil { + ctx.WithError(err).Fatal("Could not read app-eui options") + } + for _, euiString := range euiStrings { + eui, err := types.ParseAppEUI(euiString) + if err != nil { + ctx.WithError(err).Fatal("Could not read app-eui") + } + euis = append(euis, eui) + } + + account := util.GetAccount(ctx) + + app, err := account.CreateApplication(args[0], args[1], euis) + if err != nil { + ctx.WithError(err).Fatal("Could not create application") + } + + util.ForceRefreshToken(ctx) + + ctx.Info("Created Application") + + skipSelect, _ := cmd.Flags().GetBool("skip-select") + if !skipSelect { + err = util.SetConfig(map[string]interface{}{ + "app-id": app.ID, + "app-eui": app.EUIs[0].String(), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not update configuration") + } + } + + ctx.Info("Selected Current Application") + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsCreateCmd) + applicationsCreateCmd.Flags().StringSlice("app-eui", []string{}, "LoRaWAN AppEUI to register with application") + applicationsCreateCmd.Flags().Bool("skip-select", false, "Do not select this application (also adds --skip-register)") + applicationsCreateCmd.Flags().Bool("skip-register", false, "Do not register application with the Handler") +} diff --git a/ttnctl/cmd/applications_delete.go b/ttnctl/cmd/applications_delete.go new file mode 100644 index 000000000..b53a28313 --- /dev/null +++ b/ttnctl/cmd/applications_delete.go @@ -0,0 +1,40 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +// applicationsDeleteCmd represents the deletes command +var applicationsDeleteCmd = &cobra.Command{ + Use: "delete", + Short: "Delete an application", + Long: `ttnctl devices delete can be used to delete an application.`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + appID := util.GetAppID(ctx) + + if !confirm(fmt.Sprintf("Are you sure you want to delete application %s?", appID)) { + ctx.Info("Not doing anything") + return + } + + err := account.DeleteApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not delete application") + } + util.ForceRefreshToken(ctx) + + ctx.Info("Deleted Application") + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsDeleteCmd) +} diff --git a/ttnctl/cmd/applications_list.go b/ttnctl/cmd/applications_list.go new file mode 100644 index 000000000..188694d7b --- /dev/null +++ b/ttnctl/cmd/applications_list.go @@ -0,0 +1,52 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +// applicationsListCmd is the entrypoint for handlerctl +var applicationsListCmd = &cobra.Command{ + Use: "list", + Short: "list applications", + Long: `ttnctl applications list can be used to list applications.`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + + apps, err := account.ListApplications() + if err != nil { + ctx.WithError(err).Fatal("Could not list applications") + } + + switch len(apps) { + case 0: + ctx.Info("You don't have any applications") + return + case 1: + ctx.Info("Found one application:") + default: + ctx.Infof("Found %d applications:", len(apps)) + } + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "ID", "Description", "EUIs", "Access Keys", "Collaborators") + for i, app := range apps { + table.AddRow(i+1, app.ID, app.Name, len(app.EUIs), len(app.AccessKeys), len(app.Collaborators)) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsListCmd) +} diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index 2f6fe0d65..fdf717439 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -6,10 +6,8 @@ package cmd import ( "fmt" - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // applicationsPayloadFunctionsCmd represents the applicationsPayloadFunctions command @@ -21,23 +19,10 @@ converting and validating binary payload. `, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } + appID := util.GetAppID(ctx) - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } - - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() app, err := manager.GetApplication(appID) if err != nil { diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 33e8910f8..996cd87ad 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( @@ -10,34 +13,20 @@ import ( "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // applicationsPayloadFunctionsSetCmd represents the `applications pf set` command var applicationsPayloadFunctionsSetCmd = &cobra.Command{ - Use: "pf set [decoder/converter/validator] [file.js]", + Use: "set [decoder/converter/validator] [file.js]", Short: "Set payload functions of an application", Long: `ttnctl pf set can be used to get or set payload functions of an application. The functions are read from the supplied file or from STDIN.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } - - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() app, err := manager.GetApplication(appID) if err != nil && strings.Contains(err.Error(), "not found") { @@ -124,5 +113,5 @@ func readFunction() string { } func init() { - applicationsCmd.AddCommand(applicationsPayloadFunctionsSetCmd) + applicationsPayloadFunctionsCmd.AddCommand(applicationsPayloadFunctionsSetCmd) } diff --git a/ttnctl/cmd/applications_register.go b/ttnctl/cmd/applications_register.go index a7bba65e7..f71f6eb16 100644 --- a/ttnctl/cmd/applications_register.go +++ b/ttnctl/cmd/applications_register.go @@ -1,11 +1,12 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // applicationsRegisterCmd represents the `applications register` command @@ -15,25 +16,12 @@ var applicationsRegisterCmd = &cobra.Command{ Long: `ttnctl register can be used to register this application with the handler.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } - - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() - err = manager.RegisterApplication(appID) + err := manager.RegisterApplication(appID) if err != nil { ctx.WithError(err).Fatal("Could not register application") } diff --git a/ttnctl/cmd/applications_select.go b/ttnctl/cmd/applications_select.go new file mode 100644 index 000000000..511150b97 --- /dev/null +++ b/ttnctl/cmd/applications_select.go @@ -0,0 +1,106 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +// applicationsSelectCmd is the entrypoint for handlerctl +var applicationsSelectCmd = &cobra.Command{ + Use: "select", + Short: "select the application to use", + Long: `ttnctl applications select can be used to select the application to use in next commands.`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + + apps, err := account.ListApplications() + if err != nil { + ctx.WithError(err).Fatal("Could not select applications") + } + + var appIdx int + var euiIdx int + + switch len(apps) { + case 0: + ctx.Info("You don't have any applications, not doing anything.") + return + case 1: + ctx.Infof("Found one application \"%s\", selecting that one.", apps[0].ID) + default: + ctx.Infof("Found %d applications:", len(apps)) + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "ID", "Description") + for i, app := range apps { + table.AddRow(i+1, app.ID, app.Name) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + fmt.Println("Which one do you want to use?") + fmt.Printf("Enter the number (1 - %d) > ", len(apps)) + fmt.Scanf("%d", &appIdx) + appIdx-- + } + + if appIdx < 0 || appIdx >= len(apps) { + ctx.Fatal("Invalid choice for application") + } + app := apps[appIdx] + + switch len(app.EUIs) { + case 0: + ctx.Info("You don't have any EUIs in your application") + case 1: + ctx.Infof("Found one EUI \"%s\", selecting that one.", app.EUIs[0]) + default: + ctx.Infof("Found %d EUIs", len(app.EUIs)) + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "EUI") + for i, eui := range app.EUIs { + table.AddRow(i+1, eui) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + fmt.Println("Which one do you want to use?") + fmt.Printf("Enter the number (1 - %d) > ", len(app.EUIs)) + fmt.Scanf("%d", &euiIdx) + euiIdx-- + } + + if euiIdx < 0 || euiIdx >= len(apps) { + ctx.Fatal("Invalid choice for EUI") + } + eui := app.EUIs[euiIdx] + + err = util.SetConfig(map[string]interface{}{ + "app-id": app.ID, + "app-eui": eui.String(), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not update configuration") + } + + ctx.Info("Updated configuration") + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsSelectCmd) +} diff --git a/ttnctl/cmd/applications_unregister.go b/ttnctl/cmd/applications_unregister.go new file mode 100644 index 000000000..d4059b798 --- /dev/null +++ b/ttnctl/cmd/applications_unregister.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +// applicationsUnregisterCmd represents the `applications unregister` command +var applicationsUnregisterCmd = &cobra.Command{ + Use: "unregister", + Short: "Unregister this application from the handler", + Long: `ttnctl unregister can be used to unregister this application from the handler.`, + Run: func(cmd *cobra.Command, args []string) { + + appID := util.GetAppID(ctx) + + if !confirm(fmt.Sprintf("Are you sure you want to unregister application %s?", appID)) { + ctx.Info("Not doing anything") + return + } + + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() + + err := manager.DeleteApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not unregister application") + } + + ctx.WithFields(log.Fields{ + "AppID": appID, + }).Infof("Unregistered application") + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsUnregisterCmd) +} diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go index d405920fe..060f4b4fd 100644 --- a/ttnctl/cmd/devices.go +++ b/ttnctl/cmd/devices.go @@ -3,15 +3,30 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/apex/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) // devicesCmd is the entrypoint for handlerctl var devicesCmd = &cobra.Command{ Use: "devices", Short: "Manage devices", Long: `ttnctl devices can be used to manage devices.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + ctx.WithFields(log.Fields{ + "AppID": viper.GetString("app-id"), + "AppEUI": viper.GetString("app-eui"), + }).Info("Using Application") + }, } func init() { RootCmd.AddCommand(devicesCmd) + devicesCmd.PersistentFlags().String("app-id", "", "The app ID to use") + viper.BindPFlag("app-id", devicesCmd.PersistentFlags().Lookup("app-id")) + devicesCmd.PersistentFlags().String("app-eui", "", "The app EUI to use") + viper.BindPFlag("app-eui", devicesCmd.PersistentFlags().Lookup("app-eui")) } diff --git a/ttnctl/cmd/devices_create.go b/ttnctl/cmd/devices_create.go index 7d5d3a137..d9b408d28 100644 --- a/ttnctl/cmd/devices_create.go +++ b/ttnctl/cmd/devices_create.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( @@ -9,7 +12,6 @@ import ( "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // devicesCreateCmd represents the `device create` command @@ -19,13 +21,7 @@ var devicesCreateCmd = &cobra.Command{ Long: `ttnctl devices create can be used to create a new device.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } + var err error if len(args) == 0 { ctx.Fatalf("Device ID is required") @@ -36,20 +32,8 @@ var devicesCreateCmd = &cobra.Command{ ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs } - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } - - var appEUI types.AppEUI - if suppliedAppEUI := viper.GetString("app-eui"); suppliedAppEUI != "" { - appEUI, err = types.ParseAppEUI(suppliedAppEUI) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } - } else { - ctx.Fatal("Missing AppEUI. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) + appEUI := util.GetAppEUI(ctx) var devEUI types.DevEUI if len(args) > 1 { @@ -73,10 +57,8 @@ var devicesCreateCmd = &cobra.Command{ copy(appKey[:], random.Bytes(16)) } - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() err = manager.SetDevice(&handler.Device{ AppId: appID, diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go index fe046e52b..23f5c54f2 100644 --- a/ttnctl/cmd/devices_delete.go +++ b/ttnctl/cmd/devices_delete.go @@ -1,12 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( + "fmt" + "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // devicesDeleteCmd represents the `device delete` command @@ -16,14 +19,6 @@ var devicesDeleteCmd = &cobra.Command{ Long: `ttnctl devices delete can be used to delete a device.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } - if len(args) == 0 { cmd.UsageFunc()(cmd) return @@ -34,17 +29,17 @@ var devicesDeleteCmd = &cobra.Command{ ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs } - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") + if !confirm(fmt.Sprintf("Are you sure you want to delete device %s from application %s?", devID, appID)) { + ctx.Info("Not doing anything") + return } - err = manager.DeleteDevice(appID, devID) + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() + + err := manager.DeleteDevice(appID, devID) if err != nil { ctx.WithError(err).Fatal("Could not delete device.") } diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index cbcfd7e3e..5aacc6a08 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( @@ -6,10 +9,8 @@ import ( "time" "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // devicesInfoCmd represents the `device info` command @@ -19,14 +20,6 @@ var devicesInfoCmd = &cobra.Command{ Long: `ttnctl devices info can be used to get information about a device.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } - if len(args) == 0 { cmd.UsageFunc()(cmd) return @@ -37,15 +30,10 @@ var devicesInfoCmd = &cobra.Command{ ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs } - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() dev, err := manager.GetDevice(appID, devID) if err != nil { diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index c7f1ccc51..2e93cf344 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -1,14 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( "fmt" - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/gosuri/uitable" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // devicesListCmd represents the `device list` command @@ -18,23 +19,10 @@ var devicesListCmd = &cobra.Command{ Long: `ttnctl devices list can be used to list all devices for the current application.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } - - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() devices, err := manager.GetDevicesForApplication(appID) if err != nil { diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index 3a3d533bf..b06eea3fb 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -1,14 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // devicesPersonalizeCmd represents the `device personalize` command @@ -18,13 +19,7 @@ var devicesPersonalizeCmd = &cobra.Command{ Long: `ttnctl devices personalize can be used to personalize a device (ABP).`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } + var err error if len(args) == 0 { cmd.UsageFunc()(cmd) @@ -36,10 +31,7 @@ var devicesPersonalizeCmd = &cobra.Command{ ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs } - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) var devAddr types.DevAddr if len(args) > 1 { @@ -49,8 +41,8 @@ var devicesPersonalizeCmd = &cobra.Command{ } } else { ctx.Info("Generating random DevAddr...") - copy(devAddr[:], random.Bytes(8)) - devAddr[0] = (0x13 << 1) | (devAddr[0] & 1) // Use the TTN netID + copy(devAddr[:], random.Bytes(4)) + devAddr = devAddr.WithPrefix(types.DevAddr([4]byte{0x26, 0x00, 0x10, 0x00}), 20) } var nwkSKey types.NwkSKey @@ -75,10 +67,8 @@ var devicesPersonalizeCmd = &cobra.Command{ copy(appSKey[:], random.Bytes(16)) } - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() dev, err := manager.GetDevice(appID, devID) if err != nil { diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index 13b91516d..b60697b01 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -1,13 +1,14 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package cmd import ( "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // devicesSetCmd represents the `device set` command @@ -17,14 +18,6 @@ var devicesSetCmd = &cobra.Command{ Long: `ttnctl devices set can be used to set properties of a device.`, Run: func(cmd *cobra.Command, args []string) { - auth, err := util.LoadAuth(viper.GetString("ttn-account-server")) - if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication") - } - if auth == nil { - ctx.Fatal("No authentication found, please login") - } - if len(args) == 0 { cmd.UsageFunc()(cmd) return @@ -35,15 +28,10 @@ var devicesSetCmd = &cobra.Command{ ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs } - appID := viper.GetString("app-id") - if appID == "" { - ctx.Fatal("Missing AppID. You should run ttnctl applications use [AppID] [AppEUI]") - } + appID := util.GetAppID(ctx) - manager, err := handler.NewManagerClient(viper.GetString("ttn-handler"), auth.AccessToken) - if err != nil { - ctx.WithError(err).Fatal("Could not create Handler client") - } + conn, manager := util.GetHandlerManager(ctx) + defer conn.Close() dev, err := manager.GetDevice(appID, devID) if err != nil { diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go new file mode 100644 index 000000000..43acf0abb --- /dev/null +++ b/ttnctl/cmd/downlink.go @@ -0,0 +1,54 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +// downlinkCmd represents the `downlink` command +var downlinkCmd = &cobra.Command{ + Use: "downlink [DevID] [Payload]", + Short: "Send a downlink message to a device", + Long: `ttnctl downlink can be used to send a downlink message to a device.`, + Run: func(cmd *cobra.Command, args []string) { + client := util.GetMQTT(ctx) + defer client.Disconnect() + + appID := util.GetAppID(ctx) + ctx = ctx.WithField("AppID", appID) + + devID := args[0] + if !api.ValidID(devID) { + ctx.Fatalf("Invalid Device ID") // TODO: Add link to wiki explaining device IDs + } + ctx = ctx.WithField("DevID", devID) + + payload, err := types.ParseHEX(args[1], len(args[1])/2) + if err != nil { + ctx.WithError(err).Fatal("Invalid Payload") + } + + token := client.PublishDownlink(mqtt.DownlinkMessage{ + AppID: appID, + DevID: devID, + Payload: payload, + }) + token.Wait() + if token.Error() != nil { + ctx.WithError(token.Error()).Fatal("Could not enqueue downlink") + } + + ctx.Info("Enqueued downlink") + + }, +} + +func init() { + RootCmd.AddCommand(downlinkCmd) +} diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index d52ef1bf1..d6af3ad61 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -49,25 +49,51 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") - RootCmd.PersistentFlags().String("ttn-router", "staging.thethingsnetwork.org:1901", "The address of the TTN Router") + RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") + viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) + + RootCmd.PersistentFlags().String("ttn-router", "dev", "The ID of the TTN Router as announced in the Discovery server") viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router")) - RootCmd.PersistentFlags().String("ttn-handler", "staging.thethingsnetwork.org:1782", "The address of the TTN Handler") + RootCmd.PersistentFlags().String("ttn-handler", "dev", "The ID of the TTN Handler as announced in the Discovery server") viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) RootCmd.PersistentFlags().String("mqtt-broker", "staging.thethingsnetwork.org:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) - RootCmd.PersistentFlags().String("app-id", "", "The app ID to use") - viper.BindPFlag("app-id", RootCmd.PersistentFlags().Lookup("app-id")) - - RootCmd.PersistentFlags().String("app-eui", "", "The app EUI to use") - viper.BindPFlag("app-eui", RootCmd.PersistentFlags().Lookup("app-eui")) - RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) } +func printKV(key, t interface{}) { + var val string + switch t := t.(type) { + case []byte: + val = fmt.Sprintf("%X", t) + default: + val = fmt.Sprintf("%v", t) + } + + if val != "" { + fmt.Printf("%20s: %s\n", key, val) + } +} + +func confirm(prompt string) bool { + fmt.Println(prompt) + fmt.Print("> ") + var answer string + fmt.Scanf("%s", &answer) + switch strings.ToLower(answer) { + case "yes", "y": + return true + case "no", "n", "": + return false + default: + return confirm(prompt) + } +} + // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go new file mode 100644 index 000000000..8ad22c59a --- /dev/null +++ b/ttnctl/cmd/subscribe.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +// subscribeCmd represents the `subscribe` command +var subscribeCmd = &cobra.Command{ + Use: "subscribe", + Short: "Subscribe to events for this application", + Long: `ttnctl subscribe can be used to subscribe to events for this application.`, + Run: func(cmd *cobra.Command, args []string) { + client := util.GetMQTT(ctx) + defer client.Disconnect() + + client.SubscribeActivations(func(client mqtt.Client, appID string, devID string, req mqtt.Activation) { + ctx.Info("Activation") + printKV("AppID", appID) + printKV("DevID", devID) + printKV("AppEUI", req.AppEUI) + printKV("DevEUI", req.DevEUI) + printKV("DevAddr", req.DevAddr) + fmt.Println() + }) + + client.SubscribeUplink(func(client mqtt.Client, appID string, devID string, req mqtt.UplinkMessage) { + ctx.Info("Uplink Message") + printKV("AppID", appID) + printKV("DevID", devID) + printKV("Port", req.FPort) + printKV("FCnt", req.FCnt) + printKV("Payload (hex)", req.Payload) + if len(req.Fields) > 0 { + ctx.Info("Decoded fields") + for k, v := range req.Fields { + printKV(k, v) + } + } + fmt.Println() + }) + + sigChan := make(chan os.Signal) + signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) + ctx.WithField("signal", <-sigChan).Info("signal received") + }, +} + +func init() { + RootCmd.AddCommand(subscribeCmd) +} diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go new file mode 100644 index 000000000..826793e57 --- /dev/null +++ b/ttnctl/cmd/uplink.go @@ -0,0 +1,131 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "strconv" + "time" + + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/apex/log" + "github.com/spf13/cobra" +) + +// uplinkCmd represents the `uplink` command +var uplinkCmd = &cobra.Command{ + Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload]", + Short: "Simulate an uplink message to the network", + Long: `ttnctl uplink simulates an uplink message to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 5 { + cmd.UsageFunc()(cmd) + return + } + + devAddr, err := types.ParseDevAddr(args[0]) + if err != nil { + ctx.WithError(err).Fatal("Invalid DevAddr") + } + + nwkSKey, err := types.ParseNwkSKey(args[1]) + if err != nil { + ctx.WithError(err).Fatal("Invalid NwkSKey") + } + + appSKey, err := types.ParseAppSKey(args[2]) + if err != nil { + ctx.WithError(err).Fatal("Invalid AppSKey") + } + + fCnt, err := strconv.Atoi(args[3]) + if err != nil { + ctx.WithError(err).Fatal("Invalid FCnt") + } + + payload, err := types.ParseHEX(args[4], len(args[4])/2) + if err != nil { + ctx.WithError(err).Fatal("Invalid Payload") + } + + withDownlink, _ := cmd.Flags().GetBool("downlink") + + confirmed, _ := cmd.Flags().GetBool("confirmed") + if confirmed { + withDownlink = true + } + + rtrConn, rtrClient := util.GetRouter(ctx) + defer rtrConn.Close() + + md := metadata.Pairs( + "token", "token", + "gateway_eui", "0102030405060708", + ) + gatewayContext := metadata.NewContext(context.Background(), md) + + downlink := make(chan *router.DownlinkMessage) + if withDownlink { + downlinkStream, err := rtrClient.Subscribe(gatewayContext, &router.SubscribeRequest{}) + if err != nil { + ctx.WithError(err).Fatal("Could not start downlink stream") + } + time.Sleep(100 * time.Millisecond) + go func() { + if downlinkMessage, err := downlinkStream.Recv(); err == nil { + downlink <- downlinkMessage + } + }() + } + + uplink, err := rtrClient.Uplink(gatewayContext) + if err != nil { + ctx.WithError(err).Fatal("Could not start uplink stream") + } + + m := &util.Message{} + m.SetDevice(devAddr, nwkSKey, appSKey) + m.SetMessage(confirmed, fCnt, payload) + bytes := m.Bytes() + + err = uplink.Send(&router.UplinkMessage{ + Payload: bytes, + GatewayMetadata: util.GetGatewayMetadata(types.GatewayEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), 868100000), + ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not send uplink to Router") + } + + time.Sleep(100 * time.Millisecond) + + ctx.Info("Sent uplink to Router") + + if withDownlink { + select { + case downlinkMessage := <-downlink: + if err := m.Unmarshal(downlinkMessage.Payload); err != nil { + ctx.WithError(err).Fatal("Could not unmarshal downlink") + } + ctx.WithFields(log.Fields{ + "Payload": m.Payload, + "FCnt": m.FCnt, + "FPort": m.FPort, + }).Info("Received Downlink") + case <-time.After(2 * time.Second): + ctx.Info("Did not receive downlink") + } + } + }, +} + +func init() { + RootCmd.AddCommand(uplinkCmd) + uplinkCmd.Flags().Bool("downlink", false, "Also start downlink (unstable)") + uplinkCmd.Flags().Bool("confirmed", false, "Use confirmed uplink (this also sets --downlink)") +} diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 27716b71d..3a1dc13c6 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -4,135 +4,65 @@ package cmd import ( - "bytes" + "encoding/json" "fmt" - "io/ioutil" - "net/http" - "net/url" "strings" + "time" "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/howeyc/gopass" + "github.com/dgrijalva/jwt-go" "github.com/spf13/cobra" - "github.com/spf13/viper" ) // userCmd represents the users command var userCmd = &cobra.Command{ Use: "user", Short: "Show the current user", - Long: `ttnctl user shows the current logged on user`, + Long: `ttnctl user shows the current logged on user's profile`, Run: func(cmd *cobra.Command, args []string) { - t, err := util.LoadAuth(viper.GetString("ttn-account-server")) + account := util.GetAccount(ctx) + profile, err := account.Profile() if err != nil { - ctx.WithError(err).Fatal("Failed to load authentication token") - } - if t == nil { - ctx.Warn("No login found. Please login with ttnctl user login [e-mail]") - return - } - - ctx.Infof("Logged on as %s", t.Email) - }, -} - -var userCreateCmd = &cobra.Command{ - Use: "create [e-mail]", - Short: "Create a new user", - Long: `ttnctl user create allows you to create a new user`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.UsageFunc()(cmd) - return - } - - email := args[0] - fmt.Print("Password: ") - password, err := gopass.GetPasswd() - if err != nil { - ctx.Fatal(err.Error()) - } - fmt.Print("Confirm password: ") - password2, err := gopass.GetPasswd() - if err != nil { - ctx.Fatal(err.Error()) - } - if !bytes.Equal(password, password2) { - ctx.Fatal("Passwords do not match") - } - - uri := fmt.Sprintf("%s/register", viper.GetString("ttn-account-server")) - values := url.Values{ - "email": {email}, - "password": {string(password)}, + ctx.WithError(err).Fatal("Could not get user profile") } - req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) - if err != nil { - ctx.WithError(err).Fatalf("Failed to create request") - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Accept", "application/json") + ctx.Info("Found user profile:") + fmt.Println() + printKV("Username", profile.Username) + printKV("Name", profile.Name) + printKV("Email", profile.Email) + fmt.Println() - client := &http.Client{} - res, err := client.Do(req) + token, err := util.GetTokenSource(ctx).Token() if err != nil { - ctx.WithError(err).Fatal("Registration failed") + ctx.WithError(err).Fatal("Could not get access token") } - - if res.StatusCode != http.StatusCreated { - buf, _ := ioutil.ReadAll(res.Body) - ctx.Fatalf("Registration failed: %s (%v)", res.Status, string(buf)) + tokenParts := strings.Split(token.AccessToken, ".") + if len(tokenParts) != 3 { + ctx.Fatal("Invalid access token") } - - ctx.Info("User created") - }, -} - -var userLoginCmd = &cobra.Command{ - Use: "login [e-mail]", - Short: "Login", - Long: `ttnctl user login allows you to login`, - Run: func(cmd *cobra.Command, args []string) { - if len(args) < 1 { - cmd.UsageFunc()(cmd) - return - } - - email := args[0] - fmt.Print("Password: ") - password, err := gopass.GetPasswd() + segment, err := jwt.DecodeSegment(tokenParts[1]) if err != nil { - ctx.Fatal(err.Error()) + ctx.WithError(err).Fatal("Could not decode access token") } - - server := viper.GetString("ttn-account-server") - _, err = util.Login(server, email, string(password)) + var claims util.AccountClaims + err = json.Unmarshal(segment, &claims) if err != nil { - ctx.Info(fmt.Sprintf("Visit %s to register or to retrieve your account credentials.", server)) - ctx.WithError(err).Fatal("Failed to login") + ctx.WithError(err).Fatal("Could not unmarshal access token") } - ctx.Infof("Logged in as %s and persisted token in %s", email, util.AuthsFileName) - }, -} - -var userLogoutCmd = &cobra.Command{ - Use: "logout", - Short: "Logout the current user", - Long: `ttnctl user logout logs out the current user`, - Run: func(cmd *cobra.Command, args []string) { - if err := util.Logout(viper.GetString("ttn-account-server")); err != nil { - ctx.WithError(err).Fatal("Failed to log out") + if claims.ExpiresAt != 0 { + expires := time.Unix(claims.ExpiresAt, 0) + if expires.After(time.Now()) { + ctx.Infof("Login credentials valid until %s", expires.Format(time.Stamp)) + } else { + ctx.Warnf("Login credentials expired %s", expires.Format(time.Stamp)) + } } - ctx.Info("Logged out") }, } func init() { RootCmd.AddCommand(userCmd) - userCmd.AddCommand(userCreateCmd) - userCmd.AddCommand(userLoginCmd) - userCmd.AddCommand(userLogoutCmd) } diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go new file mode 100644 index 000000000..61eb688f4 --- /dev/null +++ b/ttnctl/cmd/user_login.go @@ -0,0 +1,37 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/howeyc/gopass" + "github.com/spf13/cobra" +) + +var userLoginCmd = &cobra.Command{ + Use: "login [e-mail]", + Short: "Login", + Long: `ttnctl user login allows you to login to the account server`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 1 { + cmd.UsageFunc()(cmd) + return + } + + email := args[0] + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal(err.Error()) + } + + util.Login(ctx, email, string(password)) + }, +} + +func init() { + userCmd.AddCommand(userLoginCmd) +} diff --git a/ttnctl/cmd/user_logout.go b/ttnctl/cmd/user_logout.go new file mode 100644 index 000000000..26b4c82ed --- /dev/null +++ b/ttnctl/cmd/user_logout.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var userLogoutCmd = &cobra.Command{ + Use: "logout", + Short: "Logout the current user", + Long: `ttnctl user logout logs out the current user`, + Run: func(cmd *cobra.Command, args []string) { + err := util.SetConfig(map[string]interface{}{ + "oauth2-token": "", + }) + if err != nil { + ctx.WithError(err).Fatal("Could not delete credentials") + } + }, +} + +func init() { + userCmd.AddCommand(userLogoutCmd) +} diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go new file mode 100644 index 000000000..b3d9f61df --- /dev/null +++ b/ttnctl/cmd/user_register.go @@ -0,0 +1,41 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/core/account" + "github.com/howeyc/gopass" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var userRegisterCmd = &cobra.Command{ + Use: "register [username] [e-mail]", + Short: "Register", + Long: `ttnctl user register allows you to register a new user in the account server`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) < 2 { + cmd.UsageFunc()(cmd) + return + } + username := args[0] + email := args[1] + fmt.Print("Password: ") + password, err := gopass.GetPasswd() + if err != nil { + ctx.Fatal(err.Error()) + } + err = account.RegisterUser(viper.GetString("ttn-account-server"), username, email, string(password)) + if err != nil { + ctx.WithError(err).Fatal("Could not register user") + } + ctx.Info("Registered user") + }, +} + +func init() { + userCmd.AddCommand(userRegisterCmd) +} diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go new file mode 100644 index 000000000..b2041a17c --- /dev/null +++ b/ttnctl/util/account.go @@ -0,0 +1,123 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "encoding/json" + "time" + + "github.com/TheThingsNetwork/ttn/core/account" + accountUtil "github.com/TheThingsNetwork/ttn/core/account/util" + "github.com/apex/log" + "github.com/dgrijalva/jwt-go" + "github.com/spf13/viper" + "golang.org/x/net/context" + "golang.org/x/oauth2" +) + +// AccountClaims are extracted from the access token +type AccountClaims struct { + jwt.StandardClaims + Username string `json:"username"` + Name struct { + First string `json:"first"` + Last string `json:"last"` + } `json:"name"` + Email string `json:"email"` + Client string `json:"client"` + Scopes []string `json:"scope"` + Apps map[string][]string `json:"apps,omitempty"` +} + +var tokenSource oauth2.TokenSource + +func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { + config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") + return config.TokenSource(context.Background(), token) +} + +func getStoredToken(ctx log.Interface) *oauth2.Token { + tokenString := viper.GetString("oauth2-token") + if tokenString == "" { + ctx.Fatal("No account information found. Please login with ttnctl user login [e-mail]") + } + token := &oauth2.Token{} + err := json.Unmarshal([]byte(tokenString), token) + if err != nil { + ctx.Fatal("Account information invalid. Please login with ttnctl user login [e-mail]") + } + return token +} + +func saveToken(ctx log.Interface, token *oauth2.Token) { + tokenBytes, err := json.Marshal(token) + if err != nil { + ctx.WithError(err).Fatal("Could not save access token") + } + if viper.GetString("oauth2-token") != string(tokenBytes) { + err = SetConfig(map[string]interface{}{ + "oauth2-token": string(tokenBytes), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not save access token") + } + } +} + +type ttnctlTokenSource struct { + ctx log.Interface + source oauth2.TokenSource +} + +func (s *ttnctlTokenSource) Token() (*oauth2.Token, error) { + token, err := s.source.Token() + if err != nil { + return nil, err + } + saveToken(s.ctx, token) + return token, nil +} + +// ForceRefreshToken forces a refresh of the access token +func ForceRefreshToken(ctx log.Interface) { + tokenSource := GetTokenSource(ctx).(*ttnctlTokenSource) + token, err := tokenSource.Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get access token") + } + token.Expiry = time.Now().Add(-1 * time.Second) + tokenSource.source = oauth2.ReuseTokenSource(token, getAccountServerTokenSource(token)) + tokenSource.Token() +} + +// GetTokenSource builds a new oauth2.TokenSource that uses the ttnctl config to store the token +func GetTokenSource(ctx log.Interface) oauth2.TokenSource { + if tokenSource != nil { + return tokenSource + } + token := getStoredToken(ctx) + source := oauth2.ReuseTokenSource(token, getAccountServerTokenSource(token)) + tokenSource = &ttnctlTokenSource{ctx, source} + return tokenSource +} + +// GetAccount gets a new Account server client for ttnctl +func GetAccount(ctx log.Interface) *account.Account { + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get access token") + } + return account.New(viper.GetString("ttn-account-server"), token.AccessToken) +} + +// Login does a login to the Account server with the given username and password +func Login(ctx log.Interface, username, password string) { + config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") + token, err := config.PasswordCredentialsToken(context.Background(), username, password) + if err != nil { + ctx.WithError(err).Fatal("Login failed") + } + saveToken(ctx, token) + ctx.Info("Login successful") +} diff --git a/ttnctl/util/applications.go b/ttnctl/util/applications.go deleted file mode 100644 index c756ab0f5..000000000 --- a/ttnctl/util/applications.go +++ /dev/null @@ -1,60 +0,0 @@ -package util - -import ( - "encoding/json" - "fmt" - "net/http" - - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/apex/log" - "github.com/spf13/viper" -) - -type App struct { - EUI types.AppEUI `json:"eui"` // TODO: Change to []string - Name string `json:"name"` - Owner string `json:"owner"` - AccessKeys []string `json:"accessKeys"` - Valid bool `json:"valid"` -} - -func GetAppEUI(ctx log.Interface) types.AppEUI { - if viper.GetString("app-eui") == "" { - ctx.Fatal("AppEUI not set. You probably want to run 'ttnctl applications use [appEUI]' to do this.") - } - - appEUI, err := types.ParseAppEUI(viper.GetString("app-eui")) - if err != nil { - ctx.Fatalf("Invalid AppEUI: %s", err) - } - - return appEUI -} - -func GetApplications(ctx log.Interface) ([]*App, error) { - server := viper.GetString("ttn-account-server") - uri := fmt.Sprintf("%s/applications", server) - req, err := NewRequestWithAuth(server, "GET", uri, nil) - if err != nil { - ctx.WithError(err).Fatal("Failed to create authenticated request") - } - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - ctx.WithError(err).Fatal("Failed to get applications") - } - if resp.StatusCode != http.StatusOK { - ctx.Fatalf("Failed to get applications: %s", resp.Status) - } - - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - var apps []*App - err = decoder.Decode(&apps) - if err != nil { - ctx.WithError(err).Fatal("Failed to read applications") - } - - return apps, nil -} diff --git a/ttnctl/util/auth.go b/ttnctl/util/auth.go deleted file mode 100644 index c4aa5c7b3..000000000 --- a/ttnctl/util/auth.go +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "path" - "strings" - "time" - - "github.com/mitchellh/go-homedir" -) - -const authsFilePerm = 0600 - -// AuthsFileName is where the authentication tokens are stored. Defaults to -// $HOME/.ttnctl/auths.json -var AuthsFileName string - -// Auth represents an authentication token -type Auth struct { - AccessToken string `json:"access_token"` - RefreshToken string `json:"refresh_token"` - Email string `json:"email"` - Expires time.Time `json:"expires"` -} - -type auths struct { - Auths map[string]*Auth `json:"auths"` -} - -type token struct { - AccessToken string `json:"access_token,omitempty"` - RefreshToken string `json:"refresh_token,omitempty"` - Error string `json:"error,omitempty"` - ErrorDescription string `json:"error_description,omitempty"` - ExpiresIn int `json:"expires_in,omitempty"` -} - -func init() { - dir, err := homedir.Dir() - if err != nil { - panic(err) - } - expanded, err := homedir.Expand(dir) - if err != nil { - panic(err) - } - AuthsFileName = path.Join(expanded, ".ttnctl/auths.json") -} - -// Login attemps to login using the specified credentials on the server -func Login(server, email, password string) (*Auth, error) { - values := url.Values{ - "grant_type": {"password"}, - "username": {email}, - "password": {password}, - } - return newToken(server, email, values) -} - -// Logout deletes the token for the specified server -func Logout(server string) error { - a, err := loadAuths() - if err != nil { - return err - } - delete(a.Auths, server) - if err := saveAuths(a); err != nil { - return err - } - return nil -} - -// LoadAuth loads the authentication token for the specified server and attempts -// to refresh the token if it has been expired -func LoadAuth(server string) (*Auth, error) { - a, err := loadAuths() - if err != nil { - return nil, err - } - auth, ok := a.Auths[server] - if !ok { - return nil, nil - } - if time.Now().UTC().After(auth.Expires) { - return refreshToken(server, auth) - } - return auth, nil -} - -// NewRequestWithAuth creates a new HTTP request and adds the access token of -// the authenticated user as bearer token -func NewRequestWithAuth(server, method, urlStr string, body io.Reader) (*http.Request, error) { - auth, err := LoadAuth(server) - if err != nil { - return nil, err - } - if auth == nil { - return nil, errors.New("No authentication token found. Please login") - } - req, err := http.NewRequest(method, urlStr, body) - if err != nil { - return nil, err - } - req.Header.Set("Authorization", fmt.Sprintf("bearer %s", auth.AccessToken)) - req.Header.Set("Accept", "application/json") - return req, nil -} - -// RefreshToken refreshes the current token -func RefreshToken(server string) (*Auth, error) { - auth, err := LoadAuth(server) - if err != nil { - return nil, err - } - return refreshToken(server, auth) -} - -func refreshToken(server string, auth *Auth) (*Auth, error) { - values := url.Values{ - "grant_type": {"refresh_token"}, - "refresh_token": {auth.RefreshToken}, - } - return newToken(server, auth.Email, values) -} - -func newToken(server, email string, values url.Values) (*Auth, error) { - uri := fmt.Sprintf("%s/users/token", server) - req, err := http.NewRequest("POST", uri, strings.NewReader(values.Encode())) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "application/x-www-form-urlencoded") - req.SetBasicAuth("ttnctl", "") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - var t token - if err := decoder.Decode(&t); err != nil { - return nil, err - } - - if resp.StatusCode == http.StatusUnauthorized { - return nil, errors.New("Incorrect username or password.") - } - - if resp.StatusCode != http.StatusOK { - if t.Error != "" { - return nil, errors.New(t.ErrorDescription) - } - return nil, errors.New(resp.Status) - } - - expires := time.Now().UTC().Add(time.Duration(t.ExpiresIn) * time.Second) - auth, err := saveAuth(server, email, t.AccessToken, t.RefreshToken, expires) - if err != nil { - return nil, err - } - - return auth, nil -} - -// saveAuth saves the authentication token for the specified server and e-mail -func saveAuth(server, email, accessToken, refreshToken string, expires time.Time) (*Auth, error) { - a, err := loadAuths() - // Ignore error - just create new structure - if err != nil || a == nil { - a = &auths{} - } - - // Initialize the map if not exists and add the token - auth := &Auth{accessToken, refreshToken, email, expires} - a.Auths[server] = auth - if err := saveAuths(a); err != nil { - return nil, err - } - - return auth, nil -} - -// loadAuths loads the authentication tokens. This function returns an empty -// structure if the file does not exist. -func loadAuths() (*auths, error) { - if _, err := os.Stat(AuthsFileName); os.IsNotExist(err) { - return &auths{make(map[string]*Auth)}, nil - } - buff, err := ioutil.ReadFile(AuthsFileName) - if err != nil { - return nil, err - } - var a auths - if err := json.Unmarshal(buff, &a); err != nil { - return nil, err - } - if a.Auths == nil { - a.Auths = make(map[string]*Auth) - } - return &a, nil -} - -func saveAuths(a *auths) error { - // Marshal and write to disk - buff, err := json.Marshal(&a) - if err != nil { - return err - } - if err := os.MkdirAll(path.Dir(AuthsFileName), 0755); err != nil { - return err - } - if err := ioutil.WriteFile(AuthsFileName, buff, authsFilePerm); err != nil { - return err - } - return nil -} diff --git a/ttnctl/util/auth_test.go b/ttnctl/util/auth_test.go deleted file mode 100644 index 58a34bf88..000000000 --- a/ttnctl/util/auth_test.go +++ /dev/null @@ -1,185 +0,0 @@ -package util - -import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "testing" - "time" - - . "github.com/smartystreets/assertions" -) - -func newTokenServer(a *Assertion) *httptest.Server { - return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, "/users/token") - a.So(r.Method, ShouldEqual, "POST") - - username, password, ok := r.BasicAuth() - a.So(ok, ShouldBeTrue) - a.So(username, ShouldEqual, "ttnctl") - a.So(password, ShouldEqual, "") - - grantType := r.FormValue("grant_type") - if grantType == "password" { - handleNewToken(a, w, r) - } else if grantType == "refresh_token" { - handleRefreshToken(a, w, r) - } else { - w.WriteHeader(http.StatusNotFound) - } - })) -} - -func handleNewToken(a *Assertion, w http.ResponseWriter, r *http.Request) { - var resp token - if r.FormValue("username") == "jantje@test.org" && r.FormValue("password") == "secret" { - resp = token{ - AccessToken: "123", - RefreshToken: "ABC", - ExpiresIn: 3600, - } - w.WriteHeader(http.StatusOK) - } else { - resp = token{ - Error: "invalid_credentials", - ErrorDescription: "Invalid credentials", - } - w.WriteHeader(http.StatusForbidden) - } - - encoder := json.NewEncoder(w) - err := encoder.Encode(&resp) - a.So(err, ShouldBeNil) -} - -func handleRefreshToken(a *Assertion, w http.ResponseWriter, r *http.Request) { - var resp token - if r.FormValue("refresh_token") == "ABC" { - resp = token{ - AccessToken: "456", - RefreshToken: "DEF", - ExpiresIn: 3600, - } - w.WriteHeader(http.StatusOK) - } else { - resp = token{ - Error: "invalid_grant", - ErrorDescription: "Refresh token not found", - } - w.WriteHeader(http.StatusBadRequest) - } - - encoder := json.NewEncoder(w) - err := encoder.Encode(&resp) - a.So(err, ShouldBeNil) -} - -func TestLogin(t *testing.T) { - a := New(t) - server := newTokenServer(a) - defer server.Close() - - _, err := Login(server.URL, "pietje@test.org", "secret") - a.So(err, ShouldNotBeNil) - a.So(err.Error(), ShouldEqual, "Invalid credentials") - - loginAuth, err := Login(server.URL, "jantje@test.org", "secret") - a.So(err, ShouldBeNil) - a.So(loginAuth, ShouldNotBeNil) - a.So(loginAuth.AccessToken, ShouldEqual, "123") - a.So(loginAuth.RefreshToken, ShouldEqual, "ABC") - a.So(loginAuth.Email, ShouldEqual, "jantje@test.org") - - loadedAuth, err := LoadAuth(server.URL) - a.So(err, ShouldBeNil) - a.So(loadedAuth, ShouldNotBeNil) - a.So(loginAuth, ShouldResemble, loadedAuth) - - // Check if we get this token on the HTTP request - req, err := NewRequestWithAuth(server.URL, "GET", "http://external", nil) - a.So(err, ShouldBeNil) - a.So(req, ShouldNotBeNil) - a.So(req.Header.Get("Authorization"), ShouldEqual, fmt.Sprintf("bearer %s", loadedAuth.AccessToken)) - - Logout(server.URL) -} - -func TestLogout(t *testing.T) { - a := New(t) - server := newTokenServer(a) - defer server.Close() - - // Make sure we're not logged on - err := Logout(server.URL) - a.So(err, ShouldBeNil) - loadedAuth, err := LoadAuth(server.URL) - a.So(err, ShouldBeNil) - a.So(loadedAuth, ShouldBeNil) - - // Login - loginAuth, err := Login(server.URL, "jantje@test.org", "secret") - a.So(err, ShouldBeNil) - a.So(loginAuth, ShouldNotBeNil) - - // Logout - err = Logout(server.URL) - a.So(err, ShouldBeNil) - loadedAuth, err = LoadAuth(server.URL) - a.So(err, ShouldBeNil) - a.So(loadedAuth, ShouldBeNil) - - // Make sure that we can't make an HTTP request - _, err = NewRequestWithAuth(server.URL, "GET", "http://external", nil) - a.So(err, ShouldNotBeNil) -} - -func TestLoadWithRefresh(t *testing.T) { - a := New(t) - server := newTokenServer(a) - defer server.Close() - - // Make sure we're not logged on - err := Logout(server.URL) - a.So(err, ShouldBeNil) - - // Save an expired token - expires := time.Now().Add(time.Duration(-1) * time.Hour) - savedAuth, err := saveAuth(server.URL, "jantje@test.org", "123", "ABC", expires) - a.So(err, ShouldBeNil) - - // Refresh the token - loadedAuth, err := LoadAuth(server.URL) - a.So(err, ShouldBeNil) - a.So(loadedAuth, ShouldNotBeNil) - a.So(savedAuth, ShouldNotResemble, loadedAuth) - a.So(loadedAuth.AccessToken, ShouldEqual, "456") - a.So(loadedAuth.RefreshToken, ShouldEqual, "DEF") - a.So(loadedAuth.Email, ShouldEqual, "jantje@test.org") - - Logout(server.URL) -} - -func TestLoadWithInvalidRefresh(t *testing.T) { - a := New(t) - server := newTokenServer(a) - defer server.Close() - - // Make sure we're not logged on - err := Logout(server.URL) - a.So(err, ShouldBeNil) - - // Save an expired token - expires := time.Now().Add(time.Duration(-1) * time.Hour) - _, err = saveAuth(server.URL, "pietje@test.org", "987", "ZYX", expires) - a.So(err, ShouldBeNil) - - // Refresh the token - loadedAuth, err := LoadAuth(server.URL) - a.So(err, ShouldNotBeNil) - a.So(err.Error(), ShouldEqual, "Refresh token not found") - a.So(loadedAuth, ShouldBeNil) - - Logout(server.URL) -} diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go new file mode 100644 index 000000000..f4e1f6e69 --- /dev/null +++ b/ttnctl/util/config.go @@ -0,0 +1,107 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + "io/ioutil" + "path" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/apex/log" + "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" + "gopkg.in/yaml.v2" +) + +func getConfigLocation() (string, error) { + cFile := viper.ConfigFileUsed() + if cFile == "" { + dir, err := homedir.Dir() + if err != nil { + return "", fmt.Errorf("Could not get homedir: %s", err.Error()) + } + expanded, err := homedir.Expand(dir) + if err != nil { + return "", fmt.Errorf("Could not get homedir: %s", err.Error()) + } + cFile = path.Join(expanded, ".ttnctl.yaml") + } + return cFile, nil +} + +// ReadConfig reads the config file +func ReadConfig() (map[string]interface{}, error) { + cFile, err := getConfigLocation() + if err != nil { + return nil, err + } + + c := make(map[string]interface{}) + + // Read config file + bytes, err := ioutil.ReadFile(cFile) + if err == nil { + err = yaml.Unmarshal(bytes, &c) + } + if err != nil { + return nil, fmt.Errorf("Could not read configuration file: %s", err.Error()) + } + + return c, nil +} + +// WriteConfigFile writes the config file +func WriteConfigFile(data map[string]interface{}) error { + cFile, err := getConfigLocation() + if err != nil { + return err + } + + // Write config file + d, err := yaml.Marshal(&data) + if err != nil { + return fmt.Errorf("Could not generate configiguration file contents: %s", err.Error()) + } + err = ioutil.WriteFile(cFile, d, 0644) + if err != nil { + return fmt.Errorf("Could not write configiguration file: %s", err.Error()) + } + + return nil +} + +// SetConfig sets the specified fields in the config file. +func SetConfig(data map[string]interface{}) error { + config, err := ReadConfig() + if err != nil { + return err + } + for key, value := range data { + config[key] = value + } + return WriteConfigFile(config) +} + +// GetAppEUI returns the AppEUI that must be set in the command options or config +func GetAppEUI(ctx log.Interface) types.AppEUI { + appEUIString := viper.GetString("app-eui") + if appEUIString == "" { + ctx.Fatal("Missing AppEUI. You should select an application to use with \"ttnctl applications select\"") + } + eui, err := types.ParseAppEUI(appEUIString) + if err != nil { + ctx.WithError(err).Fatal("Invalid AppEUI") + } + return eui +} + +// GetAppID returns the AppID that must be set in the command options or config +func GetAppID(ctx log.Interface) string { + appID := viper.GetString("app-id") + if appID == "" { + ctx.Fatal("Missing AppID. You should select an application to use with \"ttnctl applications select\"") + } + return appID +} diff --git a/ttnctl/util/context.go b/ttnctl/util/context.go new file mode 100644 index 000000000..bf081616d --- /dev/null +++ b/ttnctl/util/context.go @@ -0,0 +1,35 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "os" + "os/user" + + "github.com/apex/log" + + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +// GetContext returns a new context +func GetContext(ctx log.Interface) context.Context { + id := "client" + if user, err := user.Current(); err == nil { + id += "-" + user.Username + } + if hostname, err := os.Hostname(); err == nil { + id += "@" + hostname + } + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get token") + } + md := metadata.Pairs( + "id", id, + "service-name", "ttnctl", + "token", token.AccessToken, + ) + return metadata.NewContext(context.Background(), md) +} diff --git a/ttnctl/util/discovery.go b/ttnctl/util/discovery.go new file mode 100644 index 000000000..415819a37 --- /dev/null +++ b/ttnctl/util/discovery.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/apex/log" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// GetDiscovery gets the Discovery client for ttnctl +func GetDiscovery(ctx log.Interface) (*grpc.ClientConn, discovery.DiscoveryClient) { + conn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithInsecure())...) + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Discovery server") + } + return conn, discovery.NewDiscoveryClient(conn) +} diff --git a/ttnctl/util/handler.go b/ttnctl/util/handler.go new file mode 100644 index 000000000..cb31a56ab --- /dev/null +++ b/ttnctl/util/handler.go @@ -0,0 +1,38 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/apex/log" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// GetHandlerManager gets a new HandlerManager for ttnctl +func GetHandlerManager(ctx log.Interface) (*grpc.ClientConn, *handler.ManagerClient) { + dscConn, client := GetDiscovery(ctx) + defer dscConn.Close() + handlerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ + ServiceName: "handler", + Id: viper.GetString("ttn-handler"), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not find Handler") + } + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get token") + } + hdlConn, err := handlerAnnouncement.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Handler") + } + managerClient, err := handler.NewManagerClient(hdlConn, token.AccessToken) + if err != nil { + ctx.WithError(err).Fatal("Could not create Handler Manager") + } + return hdlConn, managerClient +} diff --git a/ttnctl/util/lorawan.go b/ttnctl/util/lorawan.go new file mode 100644 index 000000000..38ab0e076 --- /dev/null +++ b/ttnctl/util/lorawan.go @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "errors" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/pointer" + "github.com/brocaar/lorawan" +) + +// Message struct is used to construct an uplink message +type Message struct { + devAddr types.DevAddr + nwkSKey types.NwkSKey + appSKey types.AppSKey + FCnt int + FPort int + confirmed bool + Payload []byte +} + +// SetDevice with the LoRaWAN options +func (m *Message) SetDevice(devAddr types.DevAddr, nwkSKey types.NwkSKey, appSKey types.AppSKey) { + m.devAddr = devAddr + m.nwkSKey = nwkSKey + m.appSKey = appSKey +} + +// SetMessage with some options +func (m *Message) SetMessage(confirmed bool, fCnt int, payload []byte) { + m.confirmed = confirmed + m.FCnt = fCnt + m.Payload = payload +} + +// Bytes returns the bytes +func (m *Message) Bytes() []byte { + if m.FPort == 0 { + m.FPort = 1 + } + macPayload := &lorawan.MACPayload{} + macPayload.FHDR = lorawan.FHDR{ + DevAddr: lorawan.DevAddr(m.devAddr), + FCnt: uint32(m.FCnt), + } + macPayload.FPort = pointer.Uint8(uint8(m.FPort)) + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: m.Payload}} + phyPayload := &lorawan.PHYPayload{} + phyPayload.MHDR = lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + } + if m.confirmed { + phyPayload.MHDR.MType = lorawan.ConfirmedDataUp + } + phyPayload.MACPayload = macPayload + phyPayload.EncryptFRMPayload(lorawan.AES128Key(m.appSKey)) + phyPayload.SetMIC(lorawan.AES128Key(m.nwkSKey)) + uplinkBytes, _ := phyPayload.MarshalBinary() + return uplinkBytes +} + +// Unmarshal a byte slice into a Message +func (m *Message) Unmarshal(bytes []byte) error { + payload := &lorawan.PHYPayload{} + payload.UnmarshalBinary(bytes) + if micOK, _ := payload.ValidateMIC(lorawan.AES128Key(m.nwkSKey)); !micOK { + return errors.New("Invalid MIC") + } + macPayload, ok := payload.MACPayload.(*lorawan.MACPayload) + if !ok { + return errors.New("No MACPayload") + } + m.FCnt = int(macPayload.FHDR.FCnt) + m.FPort = -1 + if macPayload.FPort != nil { + m.FPort = int(*macPayload.FPort) + } + m.Payload = []byte{} + if len(macPayload.FRMPayload) > 0 { + payload.DecryptFRMPayload(lorawan.AES128Key(m.appSKey)) + m.Payload = macPayload.FRMPayload[0].(*lorawan.DataPayload).Bytes + } + return nil +} diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 3e4ae0fc1..bdd65eedf 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -5,34 +5,62 @@ package util import ( "fmt" + "strings" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" + "github.com/gosuri/uitable" "github.com/spf13/viper" ) -// ConnectMQTTClient connects a new MQTT clients with the specified credentials -func ConnectMQTTClient(ctx log.Interface) mqtt.Client { - appEUI := GetAppEUI(ctx) +// GetMQTT connects a new MQTT clients with the specified credentials +func GetMQTT(ctx log.Interface) mqtt.Client { + appID := GetAppID(ctx) - apps, err := GetApplications(ctx) + account := GetAccount(ctx) + app, err := account.FindApplication(appID) if err != nil { - ctx.WithError(err).Fatal("Failed to get applications") + ctx.WithError(err).Fatal("Failed to get application") } - var app *App - for _, a := range apps { - if a.EUI == appEUI { - app = a + var keyIdx int + switch len(app.AccessKeys) { + case 0: + ctx.Fatal("Can not connect to MQTT. Your application does not have any access keys.") + case 1: + default: + ctx.Infof("Found %d access keys for your application:", len(app.AccessKeys)) + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Name", "Rights") + for i, key := range app.AccessKeys { + rightStrings := make([]string, 0, len(key.Rights)) + for _, i := range key.Rights { + rightStrings = append(rightStrings, string(i)) + } + table.AddRow(i+1, key.Name, strings.Join(rightStrings, ",")) } + + fmt.Println() + fmt.Println(table) + fmt.Println() + + fmt.Println("Which one do you want to use?") + fmt.Printf("Enter the number (1 - %d) > ", len(app.AccessKeys)) + fmt.Scanf("%d", &keyIdx) + keyIdx-- } - if app == nil { - ctx.Fatal("Application not found") + + if keyIdx < 0 || keyIdx >= len(app.AccessKeys) { + ctx.Fatal("Invalid choice for access key") } + key := app.AccessKeys[keyIdx] + + ctx = ctx.WithField("Username", appID).WithField("Password", key.Key) broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) - // Don't care about which access key here - client := mqtt.NewClient(ctx, "ttnctl", app.EUI.String(), app.AccessKeys[0], broker) + client := mqtt.NewClient(ctx, "ttnctl", appID, key.Key, broker) if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go new file mode 100644 index 000000000..c559e0947 --- /dev/null +++ b/ttnctl/util/router.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/apex/log" + "github.com/spf13/viper" + "google.golang.org/grpc" +) + +// GetRouter starts a connection with the router +func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { + dscConn, client := GetDiscovery(ctx) + defer dscConn.Close() + routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ + ServiceName: "router", + Id: viper.GetString("ttn-router"), + }) + rtrConn, err := routerAnnouncement.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Router") + } + return rtrConn, router.NewRouterClient(rtrConn) +} diff --git a/ttnctl/util/uplink_metadata.go b/ttnctl/util/uplink_metadata.go new file mode 100644 index 000000000..39ce1ed6a --- /dev/null +++ b/ttnctl/util/uplink_metadata.go @@ -0,0 +1,31 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" +) + +// GetProtocolMetadata returns protocol metadata for the given datarate +func GetProtocolMetadata(dataRate string) *protocol.RxMetadata { + return &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &lorawan.Metadata{ + CodingRate: "4/5", + DataRate: dataRate, + Modulation: lorawan.Modulation_LORA, + }}} +} + +// GetGatewayMetadata returns gateway metadata for the given gateway EUI and frequency +func GetGatewayMetadata(eui types.GatewayEUI, freq uint64) *gateway.RxMetadata { + return &gateway.RxMetadata{ + GatewayEui: &eui, + Timestamp: 0, + Frequency: freq, + Rssi: -25.0, + Snr: 5.0, + } +} From 35f5171389d5859cd1d10adeccf92eadfc4b86c1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jul 2016 11:19:10 +0100 Subject: [PATCH 1632/2266] ttnctl updates - Add fPort to downlink - Don't print MQTT password - Explain to people how a yes/no question should be answered --- ttnctl/cmd/downlink.go | 4 ++++ ttnctl/cmd/root.go | 1 + ttnctl/util/mqtt.go | 2 -- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 43acf0abb..73a0a26cc 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -34,9 +34,12 @@ var downlinkCmd = &cobra.Command{ ctx.WithError(err).Fatal("Invalid Payload") } + fPort, _ := cmd.Flags().GetInt("fport") + token := client.PublishDownlink(mqtt.DownlinkMessage{ AppID: appID, DevID: devID, + FPort: uint8(fPort), Payload: payload, }) token.Wait() @@ -51,4 +54,5 @@ var downlinkCmd = &cobra.Command{ func init() { RootCmd.AddCommand(downlinkCmd) + downlinkCmd.Flags().Int("fport", 1, "FPort for downlink") } diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index d6af3ad61..7b87deee1 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -90,6 +90,7 @@ func confirm(prompt string) bool { case "no", "n", "": return false default: + fmt.Println("When you make up your mind, please answer yes or no.") return confirm(prompt) } } diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index bdd65eedf..f13f61fd6 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -57,8 +57,6 @@ func GetMQTT(ctx log.Interface) mqtt.Client { } key := app.AccessKeys[keyIdx] - ctx = ctx.WithField("Username", appID).WithField("Password", key.Key) - broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) client := mqtt.NewClient(ctx, "ttnctl", appID, key.Key, broker) From 8c8833844c151383ea5eb651feb040ede421f6f2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 26 Jul 2016 11:24:45 +0100 Subject: [PATCH 1633/2266] Fix Handler MQTT test --- core/handler/mqtt_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 27dc272f4..527bb7bde 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -20,8 +20,8 @@ func TestHandleMQTT(t *testing.T) { var wg sync.WaitGroup c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", "tcp://localhost:1883") c.Connect() - appID := "app1" - devID := "dev1" + appID := "handler-mqtt-app1" + devID := "handler-mqtt-dev1" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, devices: device.NewDeviceStore(), @@ -43,7 +43,7 @@ func TestHandleMQTT(t *testing.T) { a.So(dev.NextDownlink, ShouldNotBeNil) wg.Add(1) - c.SubscribeUplink(func(client mqtt.Client, r_appID string, r_devID string, req mqtt.UplinkMessage) { + c.SubscribeDeviceUplink(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req mqtt.UplinkMessage) { a.So(r_appID, ShouldEqual, appID) a.So(r_devID, ShouldEqual, devID) a.So(req.Payload, ShouldResemble, []byte{0xAA, 0xBC}) @@ -57,7 +57,7 @@ func TestHandleMQTT(t *testing.T) { } wg.Add(1) - c.SubscribeActivations(func(client mqtt.Client, r_appID string, r_devID string, req mqtt.Activation) { + c.SubscribeDeviceActivations(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req mqtt.Activation) { a.So(r_appID, ShouldEqual, appID) a.So(r_devID, ShouldEqual, devID) wg.Done() From 31b2488db2073b3decf1eca6cf699ceda0db713a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 27 Jul 2016 11:25:23 +0100 Subject: [PATCH 1634/2266] Don't dial components that can't be dialed --- api/discovery/dial.go | 4 ++++ ttnctl/util/router.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/api/discovery/dial.go b/api/discovery/dial.go index 59b37773b..5cb237a89 100644 --- a/api/discovery/dial.go +++ b/api/discovery/dial.go @@ -1,6 +1,7 @@ package discovery import ( + "errors" "strings" "github.com/TheThingsNetwork/ttn/api" @@ -9,5 +10,8 @@ import ( // Dial dials the component represented by this Announcement func (a *Announcement) Dial() (*grpc.ClientConn, error) { + if a.NetAddress == "" { + return nil, errors.New("Can not dial this component") + } return api.DialWithCert(strings.Split(a.NetAddress, ",")[0], a.Certificate) } diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index c559e0947..910ec1705 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -19,6 +19,9 @@ func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { ServiceName: "router", Id: viper.GetString("ttn-router"), }) + if err != nil { + ctx.WithError(err).Fatal("Could not get Router from Discovery") + } rtrConn, err := routerAnnouncement.Dial() if err != nil { ctx.WithError(err).Fatal("Could not connect to Router") From f662e320152351ea17ee8d6be9b5bfd732909d9c Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 27 Jul 2016 15:56:10 +0100 Subject: [PATCH 1635/2266] Removed Procfile --- Procfile | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 Procfile diff --git a/Procfile b/Procfile deleted file mode 100644 index 0ba7b1431..000000000 --- a/Procfile +++ /dev/null @@ -1,3 +0,0 @@ -ro: go run main.go router -br: go run main.go broker -ha: go run main.go handler From 0f3f7369d70456ba32d73d049ac02fc29fa2bff0 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 28 Jul 2016 11:28:44 +0100 Subject: [PATCH 1636/2266] Fixed typo in short name --- ttnctl/cmd/applications_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications_list.go b/ttnctl/cmd/applications_list.go index 188694d7b..f2a1ef1c2 100644 --- a/ttnctl/cmd/applications_list.go +++ b/ttnctl/cmd/applications_list.go @@ -14,7 +14,7 @@ import ( // applicationsListCmd is the entrypoint for handlerctl var applicationsListCmd = &cobra.Command{ Use: "list", - Short: "list applications", + Short: "List applications", Long: `ttnctl applications list can be used to list applications.`, Run: func(cmd *cobra.Command, args []string) { account := util.GetAccount(ctx) From fabdab173613aaf5ebd2ae1a016d1e801b1a5433 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jul 2016 15:53:30 +0100 Subject: [PATCH 1637/2266] Add ttnctl applications info cmd --- ttnctl/cmd/applications_info.go | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 ttnctl/cmd/applications_info.go diff --git a/ttnctl/cmd/applications_info.go b/ttnctl/cmd/applications_info.go new file mode 100644 index 000000000..c6f8e75de --- /dev/null +++ b/ttnctl/cmd/applications_info.go @@ -0,0 +1,77 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var applicationsInfoCmd = &cobra.Command{ + Use: "info [AppID]", + Short: "Get information about an application", + Long: `ttnctl applications info can be used to info applications.`, + Run: func(cmd *cobra.Command, args []string) { + account := util.GetAccount(ctx) + + var appID string + if len(args) == 1 { + appID = args[0] + } else { + appID = util.GetAppID(ctx) + } + + app, err := account.FindApplication(appID) + if err != nil { + ctx.WithError(err).Fatal("Could not find application") + } + + ctx.Info("Found application") + + fmt.Println() + + fmt.Printf("AppID: %s\n", app.ID) + fmt.Printf("Name: %s\n", app.Name) + + fmt.Println("EUIs:") + for _, eui := range app.EUIs { + fmt.Printf(" - %s\n", eui) + } + fmt.Println() + fmt.Println("Access Keys:") + for _, key := range app.AccessKeys { + fmt.Printf(" - Name: %s\n", key.Name) + fmt.Printf(" Key: %s\n", key.Key) + fmt.Print(" Rights: ") + for i, right := range key.Rights { + if i != 0 { + fmt.Print(", ") + } + fmt.Print(right) + } + fmt.Println() + } + fmt.Println() + fmt.Println("Collaborators:") + for _, collaborator := range app.Collaborators { + fmt.Printf(" - Name: %s\n", collaborator.Username) + fmt.Print(" Rights: ") + for i, right := range collaborator.Rights { + if i != 0 { + fmt.Print(", ") + } + fmt.Print(right) + } + fmt.Println() + } + fmt.Println() + + }, +} + +func init() { + applicationsCmd.AddCommand(applicationsInfoCmd) +} From 99336446bc80f07fa86e53ce41b4fdf46acec3ad Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jul 2016 16:19:04 +0100 Subject: [PATCH 1638/2266] Move auth server token to .ttn --- cmd/root.go | 13 ++++++------- core/component.go | 3 ++- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 117684737..76d47f4e6 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,18 +84,17 @@ func init() { RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) - var defaultOAuth2KeyFile string dir, err := homedir.Dir() if err == nil { - expanded, err := homedir.Expand(dir) - if err == nil { - defaultOAuth2KeyFile = path.Join(expanded, ".ttn/oauth2-token.pub") + dir, _ = homedir.Expand(dir) + } + if dir == "" { + dir, err = os.Getwd() + if err != nil { + panic(err) } } - RootCmd.PersistentFlags().String("oauth2-keyfile", defaultOAuth2KeyFile, "The OAuth 2.0 public key") - viper.BindPFlag("oauth2-keyfile", RootCmd.PersistentFlags().Lookup("oauth2-keyfile")) - RootCmd.PersistentFlags().Bool("tls", false, "Use TLS") viper.BindPFlag("tls", RootCmd.PersistentFlags().Lookup("tls")) diff --git a/core/component.go b/core/component.go index 61e453d25..9832cc59f 100644 --- a/core/component.go +++ b/core/component.go @@ -7,6 +7,7 @@ import ( "crypto/tls" "errors" "fmt" + "path" "runtime" "time" @@ -71,7 +72,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string Discovery: discovery, TokenKeyProvider: tokenkey.NewHTTPProvider( fmt.Sprintf("%s/key", viper.GetString("auth-server")), - viper.GetString("oauth2-keyfile"), + path.Join(viper.GetString("key-dir"), "/auth-server.pub"), ), } From 32153d2be96ab9ad31e0e18deab532efc2e21935 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jul 2016 16:19:28 +0100 Subject: [PATCH 1639/2266] Check token sub and type in discovery --- core/component.go | 1 + core/discovery/server.go | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/core/component.go b/core/component.go index 9832cc59f..65dcaa40c 100644 --- a/core/component.go +++ b/core/component.go @@ -147,6 +147,7 @@ func (c *Component) UpdateTokenKey() error { // TTNClaims contains the claims that are set by the TTN Token Issuer type TTNClaims struct { jwt.StandardClaims + Type string `json:"type"` Client string `json:"client"` Scopes []string `json:"scope"` Apps map[string][]string `json:"apps,omitempty"` diff --git a/core/discovery/server.go b/core/discovery/server.go index f54fdf3e7..ebd5c69ea 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -21,7 +21,12 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc if err != nil { return nil, err } - _ = claims // TODO: Check the claims for the announced Component ID + if claims.Subject != announcement.Id { + return nil, errors.New("ttn/discovery: Token subject does not correspond with announcement ID") + } + if claims.Type != announcement.ServiceName { + return nil, errors.New("ttn/discovery: Token type does not correspond with announcement service type") + } announcementCopy := *announcement announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement err = d.discovery.Announce(&announcementCopy) @@ -36,6 +41,12 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques if err != nil { return nil, err } + if claims.Subject != in.Id { + return nil, errors.New("ttn/discovery: Token subject does not correspond with announcement ID") + } + if claims.Type != in.ServiceName { + return nil, errors.New("ttn/discovery: Token type does not correspond with announcement service type") + } switch in.Metadata.Key { case pb.Metadata_PREFIX: // Allow announcing any PREFIX @@ -59,7 +70,12 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq if err != nil { return nil, err } - _ = claims // TODO: Check the claims for the announced Component ID + if claims.Subject != in.Id { + return nil, errors.New("ttn/discovery: Token subject does not correspond with announcement ID") + } + if claims.Type != in.ServiceName { + return nil, errors.New("ttn/discovery: Token type does not correspond with announcement service type") + } err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { return nil, err From 4dc45ba84a93b00ef32e5c66a0fb5ae605518bbd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jul 2016 16:44:24 +0100 Subject: [PATCH 1640/2266] Rename viper "token" to "auth-token" --- cmd/broker_register_prefix.go | 2 +- cmd/root.go | 6 +++--- core/component.go | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index 9b00a87a8..21ea9167c 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -33,7 +33,7 @@ var brokerRegisterPrefixCmd = &cobra.Command{ md := metadata.Pairs( "service-name", "broker", "id", viper.GetString("id"), - "token", viper.GetString("token"), + "token", viper.GetString("auth-token"), ) dscContext := metadata.NewContext(context.Background(), md) diff --git a/cmd/root.go b/cmd/root.go index 76d47f4e6..b1b8ba9a0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -72,9 +72,6 @@ func init() { RootCmd.PersistentFlags().String("id", "", "The id of this component") viper.BindPFlag("id", RootCmd.PersistentFlags().Lookup("id")) - RootCmd.PersistentFlags().String("token", "", "The auth token this component should use") - viper.BindPFlag("token", RootCmd.PersistentFlags().Lookup("token")) - RootCmd.PersistentFlags().String("description", "", "The description of this component") viper.BindPFlag("description", RootCmd.PersistentFlags().Lookup("description")) @@ -84,6 +81,9 @@ func init() { RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) + RootCmd.PersistentFlags().String("auth-token", "", "The auth token signed JWT from the auth-server") + viper.BindPFlag("auth-token", RootCmd.PersistentFlags().Lookup("auth-token")) + dir, err := homedir.Dir() if err == nil { dir, _ = homedir.Expand(dir) diff --git a/core/component.go b/core/component.go index 65dcaa40c..799ca8395 100644 --- a/core/component.go +++ b/core/component.go @@ -68,7 +68,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string ServiceName: serviceName, NetAddress: announcedAddress, }, - AccessToken: viper.GetString("token"), + AccessToken: viper.GetString("auth-token"), Discovery: discovery, TokenKeyProvider: tokenkey.NewHTTPProvider( fmt.Sprintf("%s/key", viper.GetString("auth-server")), From 43999239dbbe58353f822d3025b7a1e253eb217d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 28 Jul 2016 16:44:34 +0100 Subject: [PATCH 1641/2266] Remove Redis from Broker --- cmd/broker.go | 22 ++-------------------- core/broker/broker.go | 4 +--- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/cmd/broker.go b/cmd/broker.go index 708188b7d..09a400e57 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -12,8 +12,6 @@ import ( "syscall" "time" - "gopkg.in/redis.v3" - "google.golang.org/grpc" "github.com/TheThingsNetwork/ttn/core" @@ -34,21 +32,11 @@ var brokerCmd = &cobra.Command{ "Announce": fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port")), "NetworkServer": viper.GetString("broker.networkserver-address"), "DeduplicationDelay": viper.GetString("broker.deduplication-delay"), - "Database": fmt.Sprintf("%s/%d", viper.GetString("broker.redis-address"), viper.GetInt("broker.redis-db")), }).Info("Initializing Broker") }, Run: func(cmd *cobra.Command, args []string) { ctx.Info("Starting") - // Redis Client - client := redis.NewClient(&redis.Options{ - Addr: viper.GetString("broker.redis-address"), - Password: "", // no password set - DB: int64(viper.GetInt("broker.redis-db")), - }) - - connectRedis(client) - // Component component, err := core.NewComponent(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) if err != nil { @@ -65,9 +53,8 @@ var brokerCmd = &cobra.Command{ } // Broker - broker := broker.NewRedisBroker( - client, - time.Duration(viper.GetInt("broker.deduplication-delay"))*time.Millisecond, + broker := broker.NewBroker( + time.Duration(viper.GetInt("broker.deduplication-delay")) * time.Millisecond, ) broker.SetNetworkServer(viper.GetString("broker.networkserver-address"), nsCert, viper.GetString("broker.networkserver-token")) err = broker.Init(component) @@ -98,11 +85,6 @@ var brokerCmd = &cobra.Command{ func init() { RootCmd.AddCommand(brokerCmd) - brokerCmd.Flags().String("redis-address", "localhost:6379", "Redis host and port") - viper.BindPFlag("broker.redis-address", brokerCmd.Flags().Lookup("redis-address")) - brokerCmd.Flags().Int("redis-db", 0, "Redis database") - viper.BindPFlag("broker.redis-db", brokerCmd.Flags().Lookup("redis-db")) - brokerCmd.Flags().String("networkserver-address", "localhost:1903", "Networkserver host and port") viper.BindPFlag("broker.networkserver-address", brokerCmd.Flags().Lookup("networkserver-address")) brokerCmd.Flags().String("networkserver-cert", "", "Networkserver certificate to use") diff --git a/core/broker/broker.go b/core/broker/broker.go index b72420f3a..c4e0fc92c 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -8,8 +8,6 @@ import ( "sync" "time" - "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/networkserver" @@ -34,7 +32,7 @@ type Broker interface { DeactivateHandler(id string) error } -func NewRedisBroker(client *redis.Client, timeout time.Duration) Broker { +func NewBroker(timeout time.Duration) Broker { return &broker{ routers: make(map[string]chan *pb.DownlinkMessage), handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), From 0ca7c8b2c065125da1a5a912d46b0a2ec9eaef2e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 11:05:30 +0100 Subject: [PATCH 1642/2266] Add editorconfig --- .editorconfig | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..b03a99de8 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 + +[*.go] +indent_style = tab +indent_size = 4 + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab +indent_size = 2 From a748a03cb0d8e6f95624c12393be68c904c01775 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 14:36:52 +0100 Subject: [PATCH 1643/2266] Fix rights check in Discovery metadata --- core/discovery/server.go | 73 +++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 27 deletions(-) diff --git a/core/discovery/server.go b/core/discovery/server.go index ebd5c69ea..ac483c346 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -5,6 +5,7 @@ package discovery import ( "errors" + "fmt" "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" @@ -16,16 +17,57 @@ type discoveryServer struct { discovery Discovery } +func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { + claims, err := d.discovery.ValidateTTNAuthContext(ctx) + if err != nil { + return err + } + switch in.Metadata.Key { + case pb.Metadata_PREFIX: + if in.ServiceName != "broker" { + return errors.New("ttn/discovery: Announcement service type should be \"broker\"") + } + if claims.Type != in.ServiceName { + return fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) + } + if claims.Subject != in.Id { + return fmt.Errorf("ttn/discovery: Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + } + // TODO: Check if this PREFIX can be announced + case pb.Metadata_APP_EUI: + if in.ServiceName != "handler" { + return errors.New("ttn/discovery: Announcement service type should be \"handler\"") + } + if claims.Type != in.ServiceName { + return fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) + } + if claims.Subject != in.Id { + return fmt.Errorf("ttn/discovery: Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + } + // TODO: Check if this APP_EUI can be announced + return errors.New("ttn/discovery: Can not announce AppEUIs at this time") + case pb.Metadata_APP_ID: + if in.ServiceName != "handler" { + return errors.New("ttn/discovery: Announcement service type should be \"handler\"") + } + // When announcing APP_ID, token is user token that contains apps + if !claims.CanEditApp(string(in.Metadata.Value)) { + return errors.New("ttn/discovery: No access to this application") + } + } + return nil +} + func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { claims, err := d.discovery.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if claims.Subject != announcement.Id { - return nil, errors.New("ttn/discovery: Token subject does not correspond with announcement ID") + return nil, fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.Id) } if claims.Type != announcement.ServiceName { - return nil, errors.New("ttn/discovery: Token type does not correspond with announcement service type") + return nil, fmt.Errorf("ttn/discovery: Token type %s does not correspond with announcement service type %s", claims.Type, announcement.ServiceName) } announcementCopy := *announcement announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement @@ -37,27 +79,10 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc } func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { - claims, err := d.discovery.ValidateTTNAuthContext(ctx) + err := d.checkMetadataEditRights(ctx, in) if err != nil { return nil, err } - if claims.Subject != in.Id { - return nil, errors.New("ttn/discovery: Token subject does not correspond with announcement ID") - } - if claims.Type != in.ServiceName { - return nil, errors.New("ttn/discovery: Token type does not correspond with announcement service type") - } - switch in.Metadata.Key { - case pb.Metadata_PREFIX: - // Allow announcing any PREFIX - case pb.Metadata_APP_EUI: - // TODO: Check the claims for the announced APP_EUI - return nil, errors.New("ttn/discovery: Can not announce AppEUIs at this time") - case pb.Metadata_APP_ID: - if !claims.CanEditApp(string(in.Metadata.Value)) { - return nil, errors.New("ttn/discovery: No access to this application") - } - } err = d.discovery.AddMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { return nil, err @@ -66,16 +91,10 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques } func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { - claims, err := d.discovery.ValidateTTNAuthContext(ctx) + err := d.checkMetadataEditRights(ctx, in) if err != nil { return nil, err } - if claims.Subject != in.Id { - return nil, errors.New("ttn/discovery: Token subject does not correspond with announcement ID") - } - if claims.Type != in.ServiceName { - return nil, errors.New("ttn/discovery: Token type does not correspond with announcement service type") - } err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { return nil, err From ac7f20fd3b4251ad73d6e3941245c9b4d3b2f383 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 14:37:13 +0100 Subject: [PATCH 1644/2266] Update Makefile --- Makefile | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index bd4154a58..c84b9f20c 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps update-deps test-deps dev-deps proto test fmt vet cover build docker package +.PHONY: all clean deps update-deps test-deps proto-deps dev-deps cover-deps proto test fmt vet cover coveralls build install docker package all: clean deps build package @@ -44,12 +44,16 @@ update-deps: test-deps: $(GOCMD) get -d -v $(TEST_DEPS) -dev-deps: - $(GOCMD) get -v github.com/ddollar/forego - proto-deps: $(GOCMD) get -v github.com/gogo/protobuf/protoc-gen-gofast +dev-deps: update-deps proto-deps test-deps + $(GOCMD) get -v github.com/ddollar/forego + +cover-deps: + if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi + $(GOCMD) get github.com/mattn/goveralls + proto: @$(PROTOC)/api/*.proto @$(PROTOC)/api/protocol/protocol.proto @@ -62,10 +66,6 @@ proto: @$(PROTOC)/api/discovery/discovery.proto @$(PROTOC)/api/noc/noc.proto -cover-deps: - if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi - $(GOCMD) get github.com/mattn/goveralls - test: $(select_pkgs) | xargs $(GOCMD) test From d0631d73fa8a1d55b0d9dbb5341e20b628c5a2f5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 15:47:36 +0100 Subject: [PATCH 1645/2266] More logging for ttnctl subscribe cmd --- ttnctl/cmd/subscribe.go | 14 ++++++++++++-- ttnctl/util/mqtt.go | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 8ad22c59a..1e11c6604 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -23,7 +23,7 @@ var subscribeCmd = &cobra.Command{ client := util.GetMQTT(ctx) defer client.Disconnect() - client.SubscribeActivations(func(client mqtt.Client, appID string, devID string, req mqtt.Activation) { + token := client.SubscribeActivations(func(client mqtt.Client, appID string, devID string, req mqtt.Activation) { ctx.Info("Activation") printKV("AppID", appID) printKV("DevID", devID) @@ -32,8 +32,13 @@ var subscribeCmd = &cobra.Command{ printKV("DevAddr", req.DevAddr) fmt.Println() }) + token.Wait() + if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe to activations") + } + ctx.Info("Subscribed to activations") - client.SubscribeUplink(func(client mqtt.Client, appID string, devID string, req mqtt.UplinkMessage) { + token = client.SubscribeUplink(func(client mqtt.Client, appID string, devID string, req mqtt.UplinkMessage) { ctx.Info("Uplink Message") printKV("AppID", appID) printKV("DevID", devID) @@ -48,6 +53,11 @@ var subscribeCmd = &cobra.Command{ } fmt.Println() }) + token.Wait() + if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe to uplink") + } + ctx.Info("Subscribed to uplink") sigChan := make(chan os.Signal) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index f13f61fd6..a5d59c0ad 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -60,6 +60,8 @@ func GetMQTT(ctx log.Interface) mqtt.Client { broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) client := mqtt.NewClient(ctx, "ttnctl", appID, key.Key, broker) + ctx.WithField("MQTT Broker", broker).Info("Connecting to MQTT...") + if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } From 4511ababcd04a0793a73584b5a7d7d3aad982a8e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 15:48:32 +0100 Subject: [PATCH 1646/2266] Add MQTT OnConnectHandler before making client --- mqtt/client.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index c5a22e69f..0c4bf9db5 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -106,23 +106,31 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri ctx.WithField("message", msg).Warn("Received unhandled message") }) + var reconnecting bool + mqttOpts.SetConnectionLostHandler(func(client MQTT.Client, err error) { ctx.WithError(err).Warn("Disconnected, reconnecting...") + reconnecting = true }) ttnClient := &defaultClient{ - mqtt: MQTT.NewClient(mqttOpts), ctx: ctx, subscriptions: make(map[string]MQTT.MessageHandler), } mqttOpts.SetOnConnectHandler(func(client MQTT.Client) { - ctx.Debug("Connected to MQTT") - for topic, handler := range ttnClient.subscriptions { - ttnClient.subscribe(topic, handler) // re-subscribe + ctx.Info("Connected to MQTT") + if reconnecting { + for topic, handler := range ttnClient.subscriptions { + ctx.Infof("Re-subscribing to %s", topic) + ttnClient.subscribe(topic, handler) + } + reconnecting = false } }) + ttnClient.mqtt = MQTT.NewClient(mqttOpts) + return ttnClient } From 2c1acf631b560d0f1968baa52112a1ad1c7b6371 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 15:52:56 +0100 Subject: [PATCH 1647/2266] Add new Development Environment and update README - Add .env with all config for dev-env - Add Procfile for running locally - Add docker-compose.yml for runnin in Docker - Update README.md with info about all this --- .env/.gitignore | 5 +++ .env/broker/dev.yml | 11 ++++++ .env/broker/networkserver.cert | 1 + .env/broker/server.cert | 11 ++++++ .env/broker/server.key | 5 +++ .env/broker/server.pub | 4 ++ .env/discovery/auth-server.pub | 1 + .env/discovery/dev.yml | 6 +++ .env/handler/dev.yml | 7 ++++ .env/handler/server.cert | 11 ++++++ .env/handler/server.key | 5 +++ .env/handler/server.pub | 4 ++ .env/networkserver/dev.yml | 4 ++ .env/networkserver/server.cert | 11 ++++++ .env/networkserver/server.key | 5 +++ .env/networkserver/server.pub | 4 ++ .env/router/dev.yml | 7 ++++ .env/router/server.cert | 11 ++++++ .env/router/server.key | 5 +++ .env/router/server.pub | 4 ++ .env/ttnctl.yaml.dev-example | 4 ++ Procfile | 5 +++ README.md | 52 ++++++++++++++++--------- docker-compose.yml | 70 ++++++++++++++++++++++++++++++++++ 24 files changed, 235 insertions(+), 18 deletions(-) create mode 100644 .env/.gitignore create mode 100644 .env/broker/dev.yml create mode 120000 .env/broker/networkserver.cert create mode 100644 .env/broker/server.cert create mode 100644 .env/broker/server.key create mode 100644 .env/broker/server.pub create mode 100644 .env/discovery/auth-server.pub create mode 100644 .env/discovery/dev.yml create mode 100644 .env/handler/dev.yml create mode 100644 .env/handler/server.cert create mode 100644 .env/handler/server.key create mode 100644 .env/handler/server.pub create mode 100644 .env/networkserver/dev.yml create mode 100644 .env/networkserver/server.cert create mode 100644 .env/networkserver/server.key create mode 100644 .env/networkserver/server.pub create mode 100644 .env/router/dev.yml create mode 100644 .env/router/server.cert create mode 100644 .env/router/server.key create mode 100644 .env/router/server.pub create mode 100644 .env/ttnctl.yaml.dev-example create mode 100644 Procfile create mode 100644 docker-compose.yml diff --git a/.env/.gitignore b/.env/.gitignore new file mode 100644 index 000000000..a11d543d3 --- /dev/null +++ b/.env/.gitignore @@ -0,0 +1,5 @@ +router/auth-server.pub +broker/auth-server.pub +networkserver/auth-server.pub +handler/auth-server.pub +redis/ diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml new file mode 100644 index 000000000..6f7b73f91 --- /dev/null +++ b/.env/broker/dev.yml @@ -0,0 +1,11 @@ +id: dev +debug: true +discovery-server: "localhost:1900" +tls: true +key-dir: "./.env/broker/" + +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkZXYiLCJ0eXBlIjoiYnJva2VyIiwiaWF0IjoxNDY5NzkyNTc2fQ.BPY3MIeXA0-mzOW9Y-Zef9ME03oz13glerh-1HtkiS0_d7vkReJfG3KaWIvISbETql8ooErphP0-LE0xcCvPlqRRBPM-fPhy2DTyMSVsNPmaABC2WtWvqIsVjoPCqhu3TRzBQkvf9CE6o7F3wXQCOHQzcLXF3EsKUqSK8_p1dpFYsrVdx6V0gylzi5Ne3z5oEqKmUc8L5j14fXpwUOUMIF7awfrbs0E2h5JpvyN6cN__OX5do7o-Icm6WB4sRZzGSIONmwH4BEeqiTb-BM3FYGfPEdeiGl1W4A2-v_m8iU28L28y5Z9b_iPnUufb0K3ffXP7MkRPNRl5G1LpafofOQ + +broker: + networkserver-cert: ./.env/broker/networkserver.cert + networkserver-token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Njk3OTQzNTksIm5iZiI6MTQ2OTc5NDM1OSwic3ViIjoiZGV2In0.Xj1AFEAblzFLdS5cUiDrG773EqHazARvGdkTHFoPa_c0XGhdKSrevVpTNkzvcXeTQTWnTQrg1t98atNIk7F13Q diff --git a/.env/broker/networkserver.cert b/.env/broker/networkserver.cert new file mode 120000 index 000000000..b425d24e6 --- /dev/null +++ b/.env/broker/networkserver.cert @@ -0,0 +1 @@ +../networkserver/server.cert \ No newline at end of file diff --git a/.env/broker/server.cert b/.env/broker/server.cert new file mode 100644 index 000000000..b5fd01ef3 --- /dev/null +++ b/.env/broker/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATSgAwIBAgIQFzR5quaK8FbcvukuVI94uDAKBggqhkjOPQQDAjAdMRsw +GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswHhcNMTYwNzI5MTIxMTU2WhcNMTcw +NzI5MTIxMTU2WjAdMRswGQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAARKJiC1vanAuqbOtVYPwSM8D6oSseAOeUra5dHi +Xtp72d59+kgaEfa6Zgp0KSaes+D2fxE+RJ4G6v7DYbKgWOGto1YwVDAOBgNVHQ8B +Af8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAc +BgNVHREEFTATgglsb2NhbGhvc3SCBmJyb2tlcjAKBggqhkjOPQQDAgNIADBFAiAU +fiL3mhXOFbG9T3QVv2lH7H58pnhdrJmIBN1n6qvDXgIhAPgpd2ZCkJ04GHQyTuoU +v75tKrSlLiJw62QdY+s93uSh +-----END CERTIFICATE----- diff --git a/.env/broker/server.key b/.env/broker/server.key new file mode 100644 index 000000000..4e4caebda --- /dev/null +++ b/.env/broker/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBugiJ1PxyuQto5UGUqnT3rSPhwAUzcpZvREmbSpATQioAoGCCqGSM49 +AwEHoUQDQgAESiYgtb2pwLqmzrVWD8EjPA+qErHgDnlK2uXR4l7ae9neffpIGhH2 +umYKdCkmnrPg9n8RPkSeBur+w2GyoFjhrQ== +-----END EC PRIVATE KEY----- diff --git a/.env/broker/server.pub b/.env/broker/server.pub new file mode 100644 index 000000000..5a298cda9 --- /dev/null +++ b/.env/broker/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAESiYgtb2pwLqmzrVWD8EjPA+qErHg +DnlK2uXR4l7ae9neffpIGhH2umYKdCkmnrPg9n8RPkSeBur+w2GyoFjhrQ== +-----END PUBLIC KEY----- diff --git a/.env/discovery/auth-server.pub b/.env/discovery/auth-server.pub new file mode 100644 index 000000000..52cb60569 --- /dev/null +++ b/.env/discovery/auth-server.pub @@ -0,0 +1 @@ +{"algorithm":"RS256","key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxNptNCOwLZM5SFcUI/l\npqoN2YofNAFN4jNyv1L1qHb+6V82+pRA5QnGJCAOo3bcx3Rra170hJfJUE4f3YxC\n4OaCFflzIwZcG5gaSl25Bm4ISO/y8HbOUtDFl8djkvcQEDu1fndKy7Hfmvghn+Rn\nV68YLfXocmjM1XFD6aiRQhWDU4Pkdhso+hMd56qPziLo0X+Ebt9GuDj3VsfxDQz/\nli7o2RLj7IRfX+mMn1K5OFOsSm0TuMxhaIhpxizoh3XkW7oe6H66uvoEVkI/0rBe\nKeWzmJIt+gGbRnZnqb3tevXnSYKh9aQb/o5VYfC6x6xBpXA2yIQQILheNHuwn7t/\naQIDAQAB\n-----END PUBLIC KEY-----\n"} \ No newline at end of file diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml new file mode 100644 index 000000000..8e537ff22 --- /dev/null +++ b/.env/discovery/dev.yml @@ -0,0 +1,6 @@ +id: dev +debug: true +discovery-server: "localhost:1900" +key-dir: "./.env/discovery/" +# We use a non-existing server, so that the discovery just grabs the auth-server.pub form the key-dir +auth-server: "http://localhost:5000" diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml new file mode 100644 index 000000000..647501f03 --- /dev/null +++ b/.env/handler/dev.yml @@ -0,0 +1,7 @@ +id: dev +debug: true +discovery-server: "localhost:1900" +tls: true +key-dir: "./.env/handler/" + +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkZXYiLCJ0eXBlIjoiaGFuZGxlciIsImlhdCI6MTQ2OTc5MjU3OH0.KM579aO-zSGCms87dEeovUS6M3P45Lks_Ctch5xcmpOwF2HjhQqCU7F476OxAaS70OJ2owpBF9Py_ZwcxaxGcnyx4FVW9V3nSd4aXmUQ4sIwP9zVut9xNB-3gelttvP6MRLl2EoUcGF-5nIEP-AanhVH8jyC_AFMXafyNMJAOE0M3gSlprTcm80h8jRRRjD8fhac_JMvfJfqjS41pw5SKo8csTJVql5-zuAE0hiCY_CREiHJLY8BmX8bqNgQFZr3QlqeJMxcy29kKuZYCi_jYSnCh8LjD6ebOxx99mIcniGk6hbVQNmARns-RZpwPLFOIdsPwxGKIzPD6Liato4kXw diff --git a/.env/handler/server.cert b/.env/handler/server.cert new file mode 100644 index 000000000..b52c6d1e1 --- /dev/null +++ b/.env/handler/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBkDCCATagAwIBAgIRANJJ3rP0cNrdrouE2GCkkaYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIwNloXDTE3 +MDcyOTEyMTIwNlowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEiXbWvyYjOMP4ebTYtVvdIsBwS+U3laWltR7V +ox4+kQWcGLLEg+suI9SRZyKK+frhw9JPKbVNIgEv/S50YKfMEaNXMFUwDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0RBBYwFIIJbG9jYWxob3N0ggdoYW5kbGVyMAoGCCqGSM49BAMCA0gAMEUC +IQDbNJPUIfKZ/1CkTF3+ukl64l3fn613hnMiqAJYO7yz7QIgTAwlr3vkLquSQZUO +yraf7CGvuvulKs4S8sd8im6Bdgs= +-----END CERTIFICATE----- diff --git a/.env/handler/server.key b/.env/handler/server.key new file mode 100644 index 000000000..8326b70ef --- /dev/null +++ b/.env/handler/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIBuY3Ugapx0M58eCNcpQJF4YMYdTvC0h9NYDRl9xu12CoAoGCCqGSM49 +AwEHoUQDQgAEiXbWvyYjOMP4ebTYtVvdIsBwS+U3laWltR7Vox4+kQWcGLLEg+su +I9SRZyKK+frhw9JPKbVNIgEv/S50YKfMEQ== +-----END EC PRIVATE KEY----- diff --git a/.env/handler/server.pub b/.env/handler/server.pub new file mode 100644 index 000000000..a92fc1f6c --- /dev/null +++ b/.env/handler/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEiXbWvyYjOMP4ebTYtVvdIsBwS+U3 +laWltR7Vox4+kQWcGLLEg+suI9SRZyKK+frhw9JPKbVNIgEv/S50YKfMEQ== +-----END PUBLIC KEY----- diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml new file mode 100644 index 000000000..d9137acdc --- /dev/null +++ b/.env/networkserver/dev.yml @@ -0,0 +1,4 @@ +debug: true +discovery-server: "localhost:1900" +tls: true +key-dir: "./.env/networkserver/" diff --git a/.env/networkserver/server.cert b/.env/networkserver/server.cert new file mode 100644 index 000000000..b04453e85 --- /dev/null +++ b/.env/networkserver/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBljCCATygAwIBAgIRAOs3B7qZNgejMf+laNHTRuYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIxNVoXDTE3 +MDcyOTEyMTIxNVowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx +3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMqNdMFswDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +IwYDVR0RBBwwGoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVyMAoGCCqGSM49BAMC +A0gAMEUCIBrRl5a2PX+fn68Uefq15Cn1C1XE6NGVmI+HvmP1sA1JAiEA0L4WgKdo +HcUc8PnKlUUgN9nLVx98W9Sb2TvOaldspVE= +-----END CERTIFICATE----- diff --git a/.env/networkserver/server.key b/.env/networkserver/server.key new file mode 100644 index 000000000..e43c0d345 --- /dev/null +++ b/.env/networkserver/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEINI0PaT8sx614/iBnqZ3Z7TpS+lpVjoqXNYq7yfOagD6oAoGCCqGSM49 +AwEHoUQDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx3JSyWm19DW5dihzP +FrB0Ezu+lak91rTEaon9WNcVibhFNG5wMg== +-----END EC PRIVATE KEY----- diff --git a/.env/networkserver/server.pub b/.env/networkserver/server.pub new file mode 100644 index 000000000..6ead1f00e --- /dev/null +++ b/.env/networkserver/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCX +Z60mbgdx3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMg== +-----END PUBLIC KEY----- diff --git a/.env/router/dev.yml b/.env/router/dev.yml new file mode 100644 index 000000000..06b371ead --- /dev/null +++ b/.env/router/dev.yml @@ -0,0 +1,7 @@ +id: dev +debug: true +discovery-server: "localhost:1900" +tls: true +key-dir: "./.env/router/" + +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkZXYiLCJ0eXBlIjoicm91dGVyIiwiaWF0IjoxNDY5NzkyNTczfQ.nIH2eqv8F7Rmd0v2WLQJrK8jZT-3-ONka_FxuTdu0rQbNyKyOzXjr10Y9hG0CmS2Fn4nM37P7gsjyEhiCYVfMg7cRwAKSPEzC_0caW3dTQV0CgQ6S-sNGVD_ElBPHhrW11moetPpL1G5eJAoB4FtksREmdNikofuQ3z2F-LK5u7NaHiZd_EqOp6K5fedwMzRoXIJl9gthZLnXee0ShgHxzqWR-oS0fl-fw48IJ_aLhDngWi9ECloseDAfO31kzEA5l_wgsic6xBpC2y-bItj_MbT_84pqWc1NdtFlQNsOtZwzRw0w27K9PafTEOLSaVjGWy_FEckbsmUpq3_UYeDJw diff --git a/.env/router/server.cert b/.env/router/server.cert new file mode 100644 index 000000000..fdc967e17 --- /dev/null +++ b/.env/router/server.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATSgAwIBAgIQS/57LijDDb+PB9+WltoR1DAKBggqhkjOPQQDAjAdMRsw +GQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswHhcNMTYwNzI5MTIxMTQ2WhcNMTcw +NzI5MTIxMTQ2WjAdMRswGQYDVQQKExJUaGUgVGhpbmdzIE5ldHdvcmswWTATBgcq +hkjOPQIBBggqhkjOPQMBBwNCAAQrcb9XbpbPrXWn8Qh8kRNxzt+Y3BpxyVgRkeST +30VcppXAv83B64oqklFFTr9BmOSsSXY1iKxcDUV+25TEkuCro1YwVDAOBgNVHQ8B +Af8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUwAwEB/zAc +BgNVHREEFTATgglsb2NhbGhvc3SCBnJvdXRlcjAKBggqhkjOPQQDAgNIADBFAiEA +sI4vft9oNO2iT5The9qOzgnM5UxIc/XPrQhpKMgELTwCIFn9pkIsZ0jeeb99uBdS +4MhSRxk4jgkBaWDPjCznaHVm +-----END CERTIFICATE----- diff --git a/.env/router/server.key b/.env/router/server.key new file mode 100644 index 000000000..620fc8d30 --- /dev/null +++ b/.env/router/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIEWdn/Bs36aFUWfoswii0ASziiwZW2ctu9/1zn3sAR9JoAoGCCqGSM49 +AwEHoUQDQgAEK3G/V26Wz611p/EIfJETcc7fmNwacclYEZHkk99FXKaVwL/NweuK +KpJRRU6/QZjkrEl2NYisXA1FftuUxJLgqw== +-----END EC PRIVATE KEY----- diff --git a/.env/router/server.pub b/.env/router/server.pub new file mode 100644 index 000000000..6fb4f09d0 --- /dev/null +++ b/.env/router/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEK3G/V26Wz611p/EIfJETcc7fmNwa +cclYEZHkk99FXKaVwL/NweuKKpJRRU6/QZjkrEl2NYisXA1FftuUxJLgqw== +-----END PUBLIC KEY----- diff --git a/.env/ttnctl.yaml.dev-example b/.env/ttnctl.yaml.dev-example new file mode 100644 index 000000000..30a6b0522 --- /dev/null +++ b/.env/ttnctl.yaml.dev-example @@ -0,0 +1,4 @@ +discovery-server: localhost:1900 +mqtt-broker: localhost:1883 +ttn-handler: dev +ttn-router: dev diff --git a/Procfile b/Procfile new file mode 100644 index 000000000..54e453c42 --- /dev/null +++ b/Procfile @@ -0,0 +1,5 @@ +rtr: ttn router --config ./.env/router/dev.yml +dsc: ttn discovery --config ./.env/discovery/dev.yml +ns: ttn networkserver --config ./.env/networkserver/dev.yml +brk: ttn broker --config ./.env/broker/dev.yml +hdl: ttn handler --config ./.env/handler/dev.yml diff --git a/README.md b/README.md index 8f165160a..43021674d 100644 --- a/README.md +++ b/README.md @@ -5,38 +5,54 @@ The Things Network ![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) - The Things Network is a global open crowdsourced Internet of Things data network. -## Status - -This repository **will become** version 1.0 of The Things Network. It is **under heavy development** and currently it's APIs and code are not yet stable (pre 1.0). - ## Getting Started With The Things Network When you get started with The Things Network, you'll probably have some questions. Here are some things you can do to find the answer to them: -- Register on the [forum](http://forum.thethingsnetwork.org) -- Join [Slack](https://slack.thethingsnetwork.org) -- Sign up for the [newsletter](http://thethingsnetwork.org/#team) (click the button that says *Join our team*) +- Check out our [website](https://www.thethingsnetwork.org/) and see how [others get started](https://www.thethingsnetwork.org/labs/group/getting-started-with-the-things-network) +- Register on the [forum](http://forum.thethingsnetwork.org) and search around +- Join [Slack](https://slack.thethingsnetwork.org) and ask us what you want to know - Read background information on the [wiki](http://thethingsnetwork.org/wiki) -- Send an email to @johanstokking (johan@thethingsnetwork.org) -## Development Setup +## Prepare your Development Environment + +1. Make sure you have [Go](https://golang.org), [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) installed on your development machine. If you're on a Mac, just run `brew install go mosquitto redis`. +2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) +3. Install the [protobuf compiler](https://github.com/google/protobuf/releases) + +## Set up The Things Network's backend for Development + +1. Fork this repository +2. Clone your fork: `git clone --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` +3. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` +4. Install the dependencies for development: `make dev-deps` +5. Run the tests: `make test` + +## Build, install and run The Things Network's backend locally + +1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` +2. Run `make install` +3. Run `forego start` +4. First time only (or when Redis is flushed): + * Run `ttn broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` + * Restart the backend + +## Build and run The Things Network's backend in Docker -1. Make sure you have [Go](https://golang.org), [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) installed. -2. Fork this repository -3. Clone the fork: `git clone --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` -4. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` -5. Get some Go dependencies: `make deps test-deps` -6. _Optional:_ install [protocol compiler](https://github.com/google/protobuf) and `make proto-deps` -7. Run the tests: `make test` +1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` +2. Run `make docker` +3. Run `docker-compose up` +4. First time only (or when Redis is flushed): + * Run `docker-compose run broker broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` + * Restart the backend ## Contributing Source code for The Things Network is MIT licensed. We encourage users to make contributions on [Github](https://github.com/TheThingsNetwork/ttn) and to participate in discussions on [Slack](https://slack.thethingsnetwork.org). -If you find bugs or documentation mistakes, please check [open issues](https://github.com/TheThingsNetwork/ttn/issues) before [creating a new issue](https://github.com/TheThingsNetwork/ttn/issues/new). Please be specific and give a detailed description of the issue. Explain the steps to reproduce the problem. If you're able to fix the issue yourself, please help the community by forking the repository and submitting a pull request with your fix. +If you encounter any problems, please check [open issues](https://github.com/TheThingsNetwork/ttn/issues) before [creating a new issue](https://github.com/TheThingsNetwork/ttn/issues/new). Please be specific and give a detailed description of the issue. Explain the steps to reproduce the problem. If you're able to fix the issue yourself, please help the community by forking the repository and submitting a pull request with your fix. For contributing a feature, please open an issue that explains what you're working on. Work in your own fork of the repository and submit a pull request when you're done. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..891739f24 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,70 @@ +version: '2' +services: + redis: + image: redis + command: redis-server --appendonly yes + ports: + - "6379:6379" + volumes: + - ./.env/redis:/data + mosquitto: + image: ansi/mosquitto + ports: + - "1883:1883" + discovery: + image: thethingsnetwork/ttn + working_dir: /root + command: discovery --config ./.env/discovery/dev.yml --auth-server "http://192.168.1.29:5000" + environment: + TTN_DISCOVERY_REDIS_ADDRESS: redis:6379 + ports: + - "1900:1900" + volumes: + - "./.env/:/root/.env/" + router: + image: thethingsnetwork/ttn + working_dir: /root + command: router --config ./.env/router/dev.yml --auth-server "http://192.168.1.29:5000" + environment: + TTN_DISCOVERY_SERVER: discovery:1900 + TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE: router + ports: + - "1901:1901" + volumes: + - "./.env/:/root/.env/" + broker: + image: thethingsnetwork/ttn + working_dir: /root + command: broker --config ./.env/broker/dev.yml --auth-server "http://192.168.1.29:5000" + environment: + TTN_DISCOVERY_SERVER: discovery:1900 + TTN_BROKER_SERVER_ADDRESS_ANNOUNCE: broker + TTN_BROKER_NETWORKSERVER_ADDRESS: networkserver:1903 + ports: + - "1902:1902" + volumes: + - "./.env/:/root/.env/" + networkserver: + image: thethingsnetwork/ttn + working_dir: /root + command: networkserver --config ./.env/networkserver/dev.yml --auth-server "http://192.168.1.29:5000" + environment: + TTN_DISCOVERY_SERVER: discovery:1900 + TTN_NETWORKSERVER_REDIS_ADDRESS: redis:6379 + ports: + - "1903:1903" + volumes: + - "./.env/:/root/.env/" + handler: + image: thethingsnetwork/ttn + working_dir: /root + command: handler --config ./.env/handler/dev.yml --auth-server "http://192.168.1.29:5000" + environment: + TTN_DISCOVERY_SERVER: discovery:1900 + TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE: handler + TTN_HANDLER_REDIS_ADDRESS: redis:6379 + TTN_HANDLER_MQTT_BROKER: mosquitto:1883 + ports: + - "1904:1904" + volumes: + - "./.env/:/root/.env/" From 460d35589bccbd7331a4157533dec7da65780568 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 16:09:54 +0100 Subject: [PATCH 1648/2266] Update test coverage pkgs in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c84b9f20c..5e0b7b31c 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .Imports "\n"}}' ./...) | uni TEST_DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .TestImports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` select_pkgs = $(GOCMD) list ./... | grep -vE 'vendor|ttnctl' -coverage_pkgs = $(GOCMD) list ./... | grep -E 'core' | grep -vE 'core$$|mocks$$' +coverage_pkgs = $(GOCMD) list ./... | grep -vE 'ttn/api|ttn/cmd|ttn/vendor|ttn/ttnctl' RELEASE_DIR ?= release COVER_FILE = coverage.out From ade09876337decd028fafbf63534f3334816daca Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 17:44:35 +0100 Subject: [PATCH 1649/2266] Update docker-compose and README --- README.md | 10 +++++++--- docker-compose.yml | 10 +++++----- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 43021674d..15db4e915 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ When you get started with The Things Network, you'll probably have some question 4. Install the dependencies for development: `make dev-deps` 5. Run the tests: `make test` +**NOTE:** From now on you should run all commands from the `$GOPATH/src/github.com/TheThingsNetwork/ttn` directory. + ## Build, install and run The Things Network's backend locally 1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` @@ -42,9 +44,11 @@ When you get started with The Things Network, you'll probably have some question ## Build and run The Things Network's backend in Docker 1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` -2. Run `make docker` -3. Run `docker-compose up` -4. First time only (or when Redis is flushed): +2. Add the following line to your `/etc/hosts` file: + `127.0.0.1 router handler` +3. Run `make install docker` +4. Run `docker-compose up` +5. First time only (or when Redis is flushed): * Run `docker-compose run broker broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` * Restart the backend diff --git a/docker-compose.yml b/docker-compose.yml index 891739f24..64da768eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,7 @@ services: discovery: image: thethingsnetwork/ttn working_dir: /root - command: discovery --config ./.env/discovery/dev.yml --auth-server "http://192.168.1.29:5000" + command: discovery --config ./.env/discovery/dev.yml environment: TTN_DISCOVERY_REDIS_ADDRESS: redis:6379 ports: @@ -24,7 +24,7 @@ services: router: image: thethingsnetwork/ttn working_dir: /root - command: router --config ./.env/router/dev.yml --auth-server "http://192.168.1.29:5000" + command: router --config ./.env/router/dev.yml environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE: router @@ -35,7 +35,7 @@ services: broker: image: thethingsnetwork/ttn working_dir: /root - command: broker --config ./.env/broker/dev.yml --auth-server "http://192.168.1.29:5000" + command: broker --config ./.env/broker/dev.yml environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_BROKER_SERVER_ADDRESS_ANNOUNCE: broker @@ -47,7 +47,7 @@ services: networkserver: image: thethingsnetwork/ttn working_dir: /root - command: networkserver --config ./.env/networkserver/dev.yml --auth-server "http://192.168.1.29:5000" + command: networkserver --config ./.env/networkserver/dev.yml environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_NETWORKSERVER_REDIS_ADDRESS: redis:6379 @@ -58,7 +58,7 @@ services: handler: image: thethingsnetwork/ttn working_dir: /root - command: handler --config ./.env/handler/dev.yml --auth-server "http://192.168.1.29:5000" + command: handler --config ./.env/handler/dev.yml environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE: handler From c4dcb41bd1aa5bdaa030c289091678b08c970be2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 17:44:57 +0100 Subject: [PATCH 1650/2266] Remove double applications pf set cmd and add "no converter function" msg --- ttnctl/cmd/applications_pf.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index fdf717439..05f8ebae7 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -34,21 +34,26 @@ converting and validating binary payload. if app.Decoder != "" { ctx.Info("Decoder function") fmt.Println(app.Decoder) + } else { + ctx.Info("No decoder function") } if app.Converter != "" { ctx.Info("Converter function") fmt.Println(app.Converter) + } else { + ctx.Info("No converter function") } if app.Validator != "" { ctx.Info("Validator function") fmt.Println(app.Validator) + } else { + ctx.Info("No validator function") } }, } func init() { applicationsCmd.AddCommand(applicationsPayloadFunctionsCmd) - applicationsPayloadFunctionsCmd.AddCommand(applicationsPayloadFunctionsSetCmd) } From 22e8aae996d957154e069d18bfc9f3d58113417b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 29 Jul 2016 17:51:53 +0100 Subject: [PATCH 1651/2266] Don't publish AppID and DevID in MQTT payload These guys are already in the topic --- mqtt/client.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/mqtt/client.go b/mqtt/client.go index 0c4bf9db5..836f66401 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -186,6 +186,8 @@ func (c *defaultClient) IsConnected() bool { func (c *defaultClient) PublishUplink(dataUp UplinkMessage) Token { topic := DeviceTopic{dataUp.AppID, dataUp.DevID, Uplink} + dataUp.AppID = "" + dataUp.DevID = "" msg, err := json.Marshal(dataUp) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -206,6 +208,8 @@ func (c *defaultClient) SubscribeDeviceUplink(appID string, devID string, handle // Unmarshal the payload dataUp := &UplinkMessage{} err = json.Unmarshal(msg.Payload(), dataUp) + dataUp.AppID = topic.AppID + dataUp.DevID = topic.DevID if err != nil { c.ctx.WithError(err).Warn("Could not unmarshal uplink") @@ -238,6 +242,8 @@ func (c *defaultClient) UnsubscribeUplink() Token { func (c *defaultClient) PublishDownlink(dataDown DownlinkMessage) Token { topic := DeviceTopic{dataDown.AppID, dataDown.DevID, Downlink} + dataDown.AppID = "" + dataDown.DevID = "" msg, err := json.Marshal(dataDown) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -262,6 +268,8 @@ func (c *defaultClient) SubscribeDeviceDownlink(appID string, devID string, hand c.ctx.WithError(err).Warn("Could not unmarshal Downlink") return } + dataDown.AppID = topic.AppID + dataDown.DevID = topic.DevID // Call the Downlink handler handler(c, topic.AppID, topic.DevID, *dataDown) @@ -289,6 +297,8 @@ func (c *defaultClient) UnsubscribeDownlink() Token { func (c *defaultClient) PublishActivation(activation Activation) Token { topic := DeviceTopic{activation.AppID, activation.DevID, Activations} + activation.AppID = "" + activation.DevID = "" msg, err := json.Marshal(activation) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -313,6 +323,8 @@ func (c *defaultClient) SubscribeDeviceActivations(appID string, devID string, h c.ctx.WithError(err).Warn("Could not unmarshal Activation") return } + activation.AppID = topic.AppID + activation.DevID = topic.DevID // Call the Activation handler handler(c, topic.AppID, topic.DevID, *activation) From 2653aa6fa9635122fbfc383e45ac12d0199e59f4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 1 Aug 2016 16:59:52 +0200 Subject: [PATCH 1652/2266] Explicitly mention `protoc` in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 15db4e915..67aaf5bdc 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org), [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) installed on your development machine. If you're on a Mac, just run `brew install go mosquitto redis`. 2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) -3. Install the [protobuf compiler](https://github.com/google/protobuf/releases) +3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) ## Set up The Things Network's backend for Development From e5739af77356ba0b385d03ee9fc8b5c14487220d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 1 Aug 2016 17:58:07 +0200 Subject: [PATCH 1653/2266] New MQTT structure --- core/handler/convert_metadata.go | 51 +++++----- core/handler/convert_metadata_test.go | 10 +- core/handler/device/device_test.go | 4 +- mqtt/README.md | 131 ++++++++++++++++++++++++++ mqtt/client.go | 95 +++++++++++++------ mqtt/client_test.go | 63 ++++++++++--- mqtt/types.go | 105 +++++++++++++++------ mqtt/types_test.go | 35 +++++++ 8 files changed, 392 insertions(+), 102 deletions(-) create mode 100644 mqtt/README.md create mode 100644 mqtt/types_test.go diff --git a/core/handler/convert_metadata.go b/core/handler/convert_metadata.go index cfbc0e0c8..9ed256090 100644 --- a/core/handler/convert_metadata.go +++ b/core/handler/convert_metadata.go @@ -4,8 +4,6 @@ package handler import ( - "time" - pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" @@ -13,43 +11,44 @@ import ( // ConvertMetadata converts the protobuf matadata to application metadata func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { - ctx = ctx.WithField("NumGateways", len(ttnUp.GatewayMetadata)) // Transform Metadata - metadata := make([]*mqtt.Metadata, 0, len(ttnUp.GatewayMetadata)) - for _, in := range ttnUp.GatewayMetadata { - out := &mqtt.Metadata{} + appUp.Metadata.Time = mqtt.BuildTime(ttnUp.ServerTime) + if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan != nil { + appUp.Metadata.Modulation = lorawan.Modulation.String() + appUp.Metadata.DataRate = lorawan.DataRate + appUp.Metadata.Bitrate = lorawan.BitRate + appUp.Metadata.CodingRate = lorawan.CodingRate + } - out.ServerTime = time.Unix(0, 0).Add(time.Duration(ttnUp.ServerTime)).UTC().Format(time.RFC3339Nano) + // Transform Gateway Metadata + appUp.Metadata.Gateways = make([]mqtt.GatewayMetadata, 0, len(ttnUp.GatewayMetadata)) + for i, in := range ttnUp.GatewayMetadata { - if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan != nil { - out.DataRate = lorawan.DataRate - out.CodingRate = lorawan.CodingRate - out.Modulation = lorawan.Modulation.String() + // Same for all gateways, take first one + if i == 0 { + appUp.Metadata.Frequency = float32(float64(in.Frequency) / 1000000) } - if in.GatewayEui != nil { - out.GatewayEUI = *in.GatewayEui + gatewayMetadata := mqtt.GatewayMetadata{ + EUI: *in.GatewayEui, + Timestamp: in.Timestamp, + Time: mqtt.BuildTime(in.Time), + Channel: in.Channel, + RFChain: in.RfChain, + RSSI: in.Rssi, + SNR: in.Snr, } - out.Timestamp = in.Timestamp - out.Time = time.Unix(0, 0).Add(time.Duration(in.Time)).UTC().Format(time.RFC3339Nano) - out.Channel = in.Channel - out.RFChain = in.RfChain - out.Frequency = float32(float64(in.Frequency) / 1000000) - out.Rssi = in.Rssi - out.Lsnr = in.Snr if gps := in.GetGps(); gps != nil { - out.Altitude = gps.Altitude - out.Longitude = gps.Longitude - out.Latitude = gps.Latitude + gatewayMetadata.Altitude = gps.Altitude + gatewayMetadata.Longitude = gps.Longitude + gatewayMetadata.Latitude = gps.Latitude } - metadata = append(metadata, out) + appUp.Metadata.Gateways = append(appUp.Metadata.Gateways, gatewayMetadata) } - appUp.Metadata = metadata - return nil } diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go index e22ef60e5..8ff55cbc1 100644 --- a/core/handler/convert_metadata_test.go +++ b/core/handler/convert_metadata_test.go @@ -5,6 +5,7 @@ package handler import ( "testing" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -41,7 +42,7 @@ func TestConvertMetadata(t *testing.T) { err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Metadata, ShouldHaveLength, 2) + a.So(appUp.Metadata.Gateways, ShouldHaveLength, 2) ttnUp.ProtocolMetadata = &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ Lorawan: &pb_lorawan.Metadata{ @@ -51,8 +52,7 @@ func TestConvertMetadata(t *testing.T) { err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Metadata[0].DataRate, ShouldEqual, "SF7BW125") - a.So(appUp.Metadata[1].DataRate, ShouldEqual, "SF7BW125") + a.So(appUp.Metadata.DataRate, ShouldEqual, "SF7BW125") ttnUp.GatewayMetadata[0].Time = 1465831736000000000 ttnUp.GatewayMetadata[0].Gps = &pb_gateway.GPSMetadata{ @@ -61,7 +61,7 @@ func TestConvertMetadata(t *testing.T) { err = h.ConvertMetadata(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Metadata[0].Latitude, ShouldEqual, 42) - a.So(appUp.Metadata[0].Time, ShouldEqual, "2016-06-13T15:28:56Z") + a.So(appUp.Metadata.Gateways[0].Latitude, ShouldEqual, 42) + a.So(time.Time(appUp.Metadata.Gateways[0].Time).UTC(), ShouldResemble, time.Date(2016, 06, 13, 15, 28, 56, 0, time.UTC)) } diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index 10245cf0b..0f8fda285 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -69,10 +69,10 @@ func TestNextDownlink(t *testing.T) { } formatted, err := dev.formatProperty("next_downlink") a.So(err, ShouldBeNil) - a.So(formatted, ShouldContainSubstring, `"fields":{"bool":true,"int":42,"string":"hello!"}`) + a.So(formatted, ShouldContainSubstring, `"payload_fields":{"bool":true,"int":42,"string":"hello!"}`) dev = &Device{} - err = dev.parseProperty("next_downlink", `{"fields":{"bool":true,"int":42,"string":"hello!"}}`) + err = dev.parseProperty("next_downlink", `{"payload_fields":{"bool":true,"int":42,"string":"hello!"}}`) a.So(err, ShouldBeNil) a.So(dev.NextDownlink.Fields, ShouldNotBeNil) a.So(dev.NextDownlink.Fields["bool"], ShouldBeTrue) diff --git a/mqtt/README.md b/mqtt/README.md new file mode 100644 index 000000000..d51d6f957 --- /dev/null +++ b/mqtt/README.md @@ -0,0 +1,131 @@ +# The Things Network MQTT + +This package contains the code that is used to publish and subscribe to MQTT. +This README describes the topics and messages that are used + +## Uplink Messages + +**Topic:** `/devices//up` + +**Message:** + +```js +{ + "port": 1, // LoRaWAN FPort + "counter": 2, // LoRaWAN frame counter + "payload_raw": "AQIDBA==", // Base64 encoded payload: [0x01, 0x02, 0x03, 0x04] + "payload_fields": {}, // Object containing the results from the payload functions - left out when empty + "metadata": { + "time": "1970-01-01T00:00:00Z", // Time when the server received the message + "frequency": 868.1, // Frequency at which the message was sent + "modulation": "LORA", // Modulation that was used - currently only LORA. In the future we will support FSK as well + "data_rate": "SF7BW125", // Data rate that was used - if LORA modulation + "bit_rate": 50000, // Bit rate that was used - if FSK modulation + "coding_rate": "4/5", // Coding rate that was used + "gateways": [ + { + "eui": "0102030405060708", // EUI of the gateway + "timestamp": 12345, // Timestamp when the gateway received the message + "time": "1970-01-01T00:00:00Z", // Time when the gateway received the message - left out when gateway does not have synchronized time + "channel": 0, // Channel where the gateway received the message + "rssi": -25, // Signal strength of the received message + "snr": 5, // Signal to noise ratio of the received message + "rf_chain": 0, // RF chain where the gateway received the message + }, + //...more if received by more gateways... + ] + } +} +``` + +Note: Some values may be omitted if they are `null`, `""` or `0`. + +**Usage (Mosquitto):** `mosquitto_sub -d -t 'my-app-id/devices/my-dev-id/up'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req UplinkMessage) { + // Do something with the uplink message +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe") +} +``` + +## Downlink Messages + +**Topic:** `/devices//down` + +**Message:** + +```js +{ + "port": 1, // LoRaWAN FPort + "payload_raw": "AQIDBA==", // Base64 encoded payload: [0x01, 0x02, 0x03, 0x04] +} +``` + +**Usage (Mosquitto):** `mosquitto_pub -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_raw":"AQIDBA=="}'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.PublishDownlink(DownlinkMessage{ + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not publish") +} +``` + +## Device Activations + +**Topic:** `/devices//activations` + +**Message:** + +```js +{ + "app_eui": "0102030405060708", // EUI of the application + "dev_eui": "0102030405060708", // EUI of the device + "dev_addr": "26001716", // Assigned address of the device + "metadata": { + // Same as with Uplink Message + } +} +``` + +**Usage (Mosquitto):** `mosquitto_sub -d -t 'my-app-id/devices/my-dev-id/activations'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.SubscribeDeviceActivations("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req Activation) { + // Do something with the activation +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not subscribe") +} +``` diff --git a/mqtt/client.go b/mqtt/client.go index 836f66401..537418883 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -50,9 +50,13 @@ type Client interface { UnsubscribeActivations() Token } +// Token is returned on asyncronous functions type Token interface { + // Wait for the function to finish Wait() bool + // Wait for the function to finish or return false after a certain time WaitTimeout(time.Duration) bool + // The error associated with the result of the function (nil if everything okay) Error() error } @@ -75,16 +79,23 @@ func (t *simpleToken) Error() error { return t.err } +// UplinkHandler is called for uplink messages type UplinkHandler func(client Client, appID string, devID string, req UplinkMessage) + +// DownlinkHandler is called for downlink messages type DownlinkHandler func(client Client, appID string, devID string, req DownlinkMessage) + +// ActivationHandler is called for activations type ActivationHandler func(client Client, appID string, devID string, req Activation) -type defaultClient struct { +// DefaultClient is the default MQTT client for The Things Network +type DefaultClient struct { mqtt MQTT.Client ctx log.Interface subscriptions map[string]MQTT.MessageHandler } +// NewClient creates a new DefaultClient func NewClient(ctx log.Interface, id, username, password string, brokers ...string) Client { mqttOpts := MQTT.NewClientOptions() @@ -113,7 +124,7 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri reconnecting = true }) - ttnClient := &defaultClient{ + ttnClient := &DefaultClient{ ctx: ctx, subscriptions: make(map[string]MQTT.MessageHandler), } @@ -141,7 +152,8 @@ var ( ConnectRetryDelay = time.Second ) -func (c *defaultClient) Connect() error { +// Connect to the MQTT broker. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +func (c *DefaultClient) Connect() error { if c.mqtt.IsConnected() { return nil } @@ -162,17 +174,18 @@ func (c *defaultClient) Connect() error { return nil } -func (c *defaultClient) subscribe(topic string, handler MQTT.MessageHandler) Token { +func (c *DefaultClient) subscribe(topic string, handler MQTT.MessageHandler) Token { c.subscriptions[topic] = handler return c.mqtt.Subscribe(topic, QoS, handler) } -func (c *defaultClient) unsubscribe(topic string) Token { +func (c *DefaultClient) unsubscribe(topic string) Token { delete(c.subscriptions, topic) return c.mqtt.Unsubscribe(topic) } -func (c *defaultClient) Disconnect() { +// Disconnect from the MQTT broker +func (c *DefaultClient) Disconnect() { if !c.mqtt.IsConnected() { return } @@ -180,11 +193,13 @@ func (c *defaultClient) Disconnect() { c.mqtt.Disconnect(25) } -func (c *defaultClient) IsConnected() bool { +// IsConnected returns true if there is a connection to the MQTT broker +func (c *DefaultClient) IsConnected() bool { return c.mqtt.IsConnected() } -func (c *defaultClient) PublishUplink(dataUp UplinkMessage) Token { +// PublishUplink publishes an uplink message to the MQTT broker +func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { topic := DeviceTopic{dataUp.AppID, dataUp.DevID, Uplink} dataUp.AppID = "" dataUp.DevID = "" @@ -195,7 +210,8 @@ func (c *defaultClient) PublishUplink(dataUp UplinkMessage) Token { return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { +// SubscribeDeviceUplink subscribes to all uplink messages for the given application and device +func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { topic := DeviceTopic{appID, devID, Uplink} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -221,26 +237,34 @@ func (c *defaultClient) SubscribeDeviceUplink(appID string, devID string, handle }) } -func (c *defaultClient) SubscribeAppUplink(appID string, handler UplinkHandler) Token { +// SubscribeAppUplink subscribes to all uplink messages for the given application +func (c *DefaultClient) SubscribeAppUplink(appID string, handler UplinkHandler) Token { return c.SubscribeDeviceUplink(appID, "", handler) } -func (c *defaultClient) SubscribeUplink(handler UplinkHandler) Token { +// SubscribeUplink subscribes to all uplink messages that the current user has access to +func (c *DefaultClient) SubscribeUplink(handler UplinkHandler) Token { return c.SubscribeDeviceUplink("", "", handler) } -func (c *defaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { +// UnsubscribeDeviceUplink unsubscribes from the uplink messages for the given application and device +func (c *DefaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { topic := DeviceTopic{appID, devID, Uplink} return c.unsubscribe(topic.String()) } -func (c *defaultClient) UnsubscribeAppUplink(appID string) Token { + +// UnsubscribeAppUplink unsubscribes from the uplink messages for the given application +func (c *DefaultClient) UnsubscribeAppUplink(appID string) Token { return c.UnsubscribeDeviceUplink(appID, "") } -func (c *defaultClient) UnsubscribeUplink() Token { + +// UnsubscribeUplink unsubscribes from the uplink messages that the current user has access to +func (c *DefaultClient) UnsubscribeUplink() Token { return c.UnsubscribeDeviceUplink("", "") } -func (c *defaultClient) PublishDownlink(dataDown DownlinkMessage) Token { +// PublishDownlink publishes a downlink message +func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { topic := DeviceTopic{dataDown.AppID, dataDown.DevID, Downlink} dataDown.AppID = "" dataDown.DevID = "" @@ -251,7 +275,8 @@ func (c *defaultClient) PublishDownlink(dataDown DownlinkMessage) Token { return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { +// SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device +func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { topic := DeviceTopic{appID, devID, Downlink} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -276,26 +301,34 @@ func (c *defaultClient) SubscribeDeviceDownlink(appID string, devID string, hand }) } -func (c *defaultClient) SubscribeAppDownlink(appID string, handler DownlinkHandler) Token { +// SubscribeAppDownlink subscribes to all downlink messages for the given application +func (c *DefaultClient) SubscribeAppDownlink(appID string, handler DownlinkHandler) Token { return c.SubscribeDeviceDownlink(appID, "", handler) } -func (c *defaultClient) SubscribeDownlink(handler DownlinkHandler) Token { +// SubscribeDownlink subscribes to all downlink messages that the current user has access to +func (c *DefaultClient) SubscribeDownlink(handler DownlinkHandler) Token { return c.SubscribeDeviceDownlink("", "", handler) } -func (c *defaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { +// UnsubscribeDeviceDownlink unsubscribes from the downlink messages for the given application and device +func (c *DefaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { topic := DeviceTopic{appID, devID, Downlink} return c.unsubscribe(topic.String()) } -func (c *defaultClient) UnsubscribeAppDownlink(appID string) Token { + +// UnsubscribeAppDownlink unsubscribes from the downlink messages for the given application +func (c *DefaultClient) UnsubscribeAppDownlink(appID string) Token { return c.UnsubscribeDeviceDownlink(appID, "") } -func (c *defaultClient) UnsubscribeDownlink() Token { + +// UnsubscribeDownlink unsubscribes from the downlink messages that the current user has access to +func (c *DefaultClient) UnsubscribeDownlink() Token { return c.UnsubscribeDeviceDownlink("", "") } -func (c *defaultClient) PublishActivation(activation Activation) Token { +// PublishActivation publishes an activation +func (c *DefaultClient) PublishActivation(activation Activation) Token { topic := DeviceTopic{activation.AppID, activation.DevID, Activations} activation.AppID = "" activation.DevID = "" @@ -306,7 +339,8 @@ func (c *defaultClient) PublishActivation(activation Activation) Token { return c.mqtt.Publish(topic.String(), QoS, false, msg) } -func (c *defaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { +// SubscribeDeviceActivations subscribes to all activations for the given application and device +func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { topic := DeviceTopic{appID, devID, Activations} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic @@ -331,23 +365,28 @@ func (c *defaultClient) SubscribeDeviceActivations(appID string, devID string, h }) } -func (c *defaultClient) SubscribeAppActivations(appID string, handler ActivationHandler) Token { +// SubscribeAppActivations subscribes to all activations for the given application +func (c *DefaultClient) SubscribeAppActivations(appID string, handler ActivationHandler) Token { return c.SubscribeDeviceActivations(appID, "", handler) } -func (c *defaultClient) SubscribeActivations(handler ActivationHandler) Token { +// SubscribeActivations subscribes to all activations that the current user has access to +func (c *DefaultClient) SubscribeActivations(handler ActivationHandler) Token { return c.SubscribeDeviceActivations("", "", handler) } -func (c *defaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { +// UnsubscribeDeviceActivations unsubscribes from the activations for the given application and device +func (c *DefaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { topic := DeviceTopic{appID, devID, Activations} return c.unsubscribe(topic.String()) } -func (c *defaultClient) UnsubscribeAppActivations(appID string) Token { +// UnsubscribeAppActivations unsubscribes from the activations for the given application +func (c *DefaultClient) UnsubscribeAppActivations(appID string) Token { return c.UnsubscribeDeviceActivations(appID, "") } -func (c *defaultClient) UnsubscribeActivations() Token { +// UnsubscribeActivations unsubscribes from the activations that the current user has access to +func (c *DefaultClient) UnsubscribeActivations() Token { return c.UnsubscribeDeviceActivations("", "") } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 171a5f699..37e0ea89b 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -10,6 +10,7 @@ import ( "time" . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/apex/log" . "github.com/smartystreets/assertions" ) @@ -28,7 +29,7 @@ func TestToken(t *testing.T) { func TestNewClient(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") - a.So(c.(*defaultClient).mqtt, ShouldNotBeNil) + a.So(c.(*DefaultClient).mqtt, ShouldNotBeNil) } func TestConnect(t *testing.T) { @@ -92,8 +93,8 @@ func TestRandomTopicPublish(t *testing.T) { c.Connect() defer c.Disconnect() - c.(*defaultClient).mqtt.Subscribe("randomtopic", QoS, nil).Wait() - c.(*defaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}).Wait() + c.(*DefaultClient).mqtt.Subscribe("randomtopic", QoS, nil).Wait() + c.(*DefaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}).Wait() <-time.After(50 * time.Millisecond) @@ -177,15 +178,13 @@ func TestPubSubUplink(t *testing.T) { c.Connect() defer c.Disconnect() - var wg sync.WaitGroup - - wg.Add(1) + waitChan := make(chan bool, 1) c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { a.So(appID, ShouldResemble, "app1") a.So(devID, ShouldResemble, "dev1") - wg.Done() + waitChan <- true }).Wait() c.PublishUplink(UplinkMessage{ @@ -194,7 +193,11 @@ func TestPubSubUplink(t *testing.T) { DevID: "dev1", }).Wait() - wg.Wait() + select { + case <-waitChan: + case <-time.After(1 * time.Second): + panic("Did not receive Uplink") + } c.UnsubscribeDeviceUplink("app1", "dev1").Wait() } @@ -373,7 +376,7 @@ func TestPublishActivations(t *testing.T) { dataActivations := Activation{ AppID: "someid", DevID: "someid", - Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + Metadata: Metadata{DataRate: "SF7BW125"}, } token := c.PublishActivation(dataActivations) @@ -453,7 +456,7 @@ func TestPubSubActivations(t *testing.T) { c.PublishActivation(Activation{ AppID: "app5", DevID: "dev1", - Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + Metadata: Metadata{DataRate: "SF7BW125"}, }).Wait() wg.Wait() @@ -473,22 +476,56 @@ func TestPubSubAppActivations(t *testing.T) { c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { a.So(appID, ShouldResemble, "app6") - a.So(req.Metadata[0].DataRate, ShouldEqual, "SF7BW125") + a.So(req.Metadata.DataRate, ShouldEqual, "SF7BW125") wg.Done() }).Wait() c.PublishActivation(Activation{ AppID: "app6", DevID: "dev1", - Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + Metadata: Metadata{DataRate: "SF7BW125"}, }).Wait() c.PublishActivation(Activation{ AppID: "app6", DevID: "dev2", - Metadata: []Metadata{Metadata{DataRate: "SF7BW125"}}, + Metadata: Metadata{DataRate: "SF7BW125"}, }).Wait() wg.Wait() c.UnsubscribeAppActivations("app6") } + +func ExampleNewClient() { + ctx := log.WithField("Example", "NewClient") + exampleClient := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") + err := exampleClient.Connect() + if err != nil { + ctx.WithError(err).Fatal("Could not connect") + } +} + +var exampleClient Client + +func ExampleDefaultClient_SubscribeDeviceUplink() { + token := exampleClient.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req UplinkMessage) { + // Do something with the message + }) + token.Wait() + if err := token.Error(); err != nil { + panic(err) + } +} + +func ExampleDefaultClient_PublishDownlink() { + token := exampleClient.PublishDownlink(DownlinkMessage{ + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }) + token.Wait() + if err := token.Error(); err != nil { + panic(err) + } +} diff --git a/mqtt/types.go b/mqtt/types.go index a4cf255d8..7fe4498a8 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -3,54 +3,103 @@ package mqtt -import "github.com/TheThingsNetwork/ttn/core/types" +import ( + "time" -// Metadata contains the metadata that is passed up to the application + "github.com/TheThingsNetwork/ttn/core/types" +) + +// JSONTime is a time.Time that marshals to/from RFC3339Nano format +type JSONTime time.Time + +// MarshalText implements the encoding.TextMarshaler interface +func (t JSONTime) MarshalText() ([]byte, error) { + if time.Time(t).IsZero() || time.Time(t).Unix() == 0 { + return []byte{}, nil + } + stamp := time.Time(t).UTC().Format(time.RFC3339Nano) + return []byte(stamp), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (t *JSONTime) UnmarshalText(text []byte) error { + if len(text) == 0 { + *t = JSONTime{} + return nil + } + time, err := time.Parse(time.RFC3339Nano, string(text)) + if err != nil { + return err + } + *t = JSONTime(time) + return nil +} + +// BuildTime builds a new JSONTime +func BuildTime(unixNano int64) JSONTime { + if unixNano == 0 { + return JSONTime{} + } + return JSONTime(time.Unix(0, 0).Add(time.Duration(unixNano)).UTC()) +} + +// LocationMetadata contains GPS coordinates +type LocationMetadata struct { + Altitude int32 `json:"altitude,omitempty"` + Longitude float32 `json:"longitude,omitempty"` + Latitude float32 `json:"latitude,omitempty"` +} + +// GatewayMetadata contains metadata for each gateway that received a message +type GatewayMetadata struct { + EUI types.GatewayEUI `json:"eui,omitempty"` + Timestamp uint32 `json:"timestamp,omitempty"` + Time JSONTime `json:"time,omitempty"` + Channel uint32 `json:"channel,omitempty"` + RSSI float32 `json:"rssi,omitempty"` + SNR float32 `json:"snr,omitempty"` + RFChain uint32 `json:"rf_chain,omitempty"` + LocationMetadata +} + +// Metadata contains metadata of a message type Metadata struct { - Frequency float32 `json:"frequency"` - DataRate string `json:"datarate"` - CodingRate string `json:"codingrate"` - Timestamp uint32 `json:"gateway_timestamp"` - Time string `json:"gateway_time,omitempty"` - ServerTime string `json:"server_time"` - Channel uint32 `json:"channel"` - Rssi float32 `json:"rssi"` - Lsnr float32 `json:"lsnr"` - RFChain uint32 `json:"rfchain"` - Modulation string `json:"modulation"` - GatewayEUI types.GatewayEUI `json:"gateway_eui"` - Altitude int32 `json:"altitude"` - Longitude float32 `json:"longitude"` - Latitude float32 `json:"latitude"` + Time JSONTime `json:"time,omitempty,omitempty"` + Frequency float32 `json:"frequency,omitempty"` + Modulation string `json:"modulation,omitempty"` + DataRate string `json:"data_rate,omitempty"` + Bitrate uint32 `json:"bit_rate,omitempty"` + CodingRate string `json:"coding_rate,omitempty"` + Gateways []GatewayMetadata `json:"gateways,omitempty"` + LocationMetadata } // UplinkMessage represents an application-layer uplink message type UplinkMessage struct { AppID string `json:"app_id,omitempty"` DevID string `json:"dev_id,omitempty"` - Payload []byte `json:"payload,omitempty"` - FPort uint8 `json:"port,omitempty"` - Fields map[string]interface{} `json:"fields,omitempty"` - FCnt uint32 `json:"counter,omitempty"` - Metadata []*Metadata `json:"metadata,omitempty"` + FPort uint8 `json:"port"` + FCnt uint32 `json:"counter"` + Payload []byte `json:"payload_raw"` + Fields map[string]interface{} `json:"payload_fields,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` } // DownlinkMessage represents an application-layer downlink message type DownlinkMessage struct { AppID string `json:"app_id,omitempty"` DevID string `json:"dev_id,omitempty"` - Payload []byte `json:"payload,omitempty"` - FPort uint8 `json:"port,omitempty"` - Fields map[string]interface{} `json:"fields,omitempty"` - TTL string `json:"ttl,omitempty"` + FPort uint8 `json:"port"` + Payload []byte `json:"payload_raw,omitempty"` + Fields map[string]interface{} `json:"payload_fields,omitempty"` } -// Activation are used to notify application of a device activation +// Activation messages are used to notify application of a device activation type Activation struct { AppID string `json:"app_id,omitempty"` DevID string `json:"dev_id,omitempty"` AppEUI types.AppEUI `json:"app_eui,omitempty"` DevEUI types.DevEUI `json:"dev_eui,omitempty"` DevAddr types.DevAddr `json:"dev_addr,omitempty"` - Metadata []Metadata `json:"metadata"` + Metadata Metadata `json:"metadata,omitempty"` } diff --git a/mqtt/types_test.go b/mqtt/types_test.go new file mode 100644 index 000000000..1897808e6 --- /dev/null +++ b/mqtt/types_test.go @@ -0,0 +1,35 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +func TestJSONTime(t *testing.T) { + a := New(t) + + a.So(BuildTime(0), ShouldResemble, JSONTime(time.Time{})) + + data, err := json.Marshal(JSONTime{}) + a.So(err, ShouldBeNil) + a.So(string(data), ShouldResemble, `""`) + + data, err = json.Marshal(BuildTime(0)) + a.So(err, ShouldBeNil) + a.So(string(data), ShouldResemble, `""`) + + data, err = json.Marshal(BuildTime(1465831736000000000)) + a.So(err, ShouldBeNil) + a.So(string(data), ShouldResemble, `"2016-06-13T15:28:56Z"`) + + var time JSONTime + err = json.Unmarshal([]byte(`"2016-06-13T15:28:56Z"`), &time) + a.So(err, ShouldBeNil) + a.So(time, ShouldResemble, BuildTime(1465831736000000000)) +} From b4a79eb82dae5047ed78e374beda76672621c257 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 1 Aug 2016 18:29:33 +0200 Subject: [PATCH 1654/2266] Mosquitto and Redis should be RUNNING --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 67aaf5bdc..ff2d8be7a 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,14 @@ When you get started with The Things Network, you'll probably have some question ## Prepare your Development Environment -1. Make sure you have [Go](https://golang.org), [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) installed on your development machine. If you're on a Mac, just run `brew install go mosquitto redis`. +1. Make sure you have [Go](https://golang.org) installed. 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. Make sure you have [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) **installed** and **running**. + If you're on a Mac, just run: + * `brew install go mosquitto redis` + * `brew services start mosquitto` + * `brew services start redis` ## Set up The Things Network's backend for Development From 4fc4ef91ba12b00ac629375bbccfaccdadeed962 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 1 Aug 2016 18:29:57 +0200 Subject: [PATCH 1655/2266] Use the staging account server for development --- .env/.gitignore | 5 +---- .env/broker/dev.yml | 1 + .env/discovery/auth-server.pub | 1 - .env/discovery/dev.yml | 3 +-- .env/handler/dev.yml | 1 + .env/networkserver/dev.yml | 1 + .env/router/dev.yml | 1 + .env/ttnctl.yaml.dev-example | 1 + ttnctl/cmd/user_register.go | 1 + 9 files changed, 8 insertions(+), 7 deletions(-) delete mode 100644 .env/discovery/auth-server.pub diff --git a/.env/.gitignore b/.env/.gitignore index a11d543d3..75ae1aa83 100644 --- a/.env/.gitignore +++ b/.env/.gitignore @@ -1,5 +1,2 @@ -router/auth-server.pub -broker/auth-server.pub -networkserver/auth-server.pub -handler/auth-server.pub +*/auth-server.pub redis/ diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index 6f7b73f91..1e693152d 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -1,6 +1,7 @@ id: dev debug: true discovery-server: "localhost:1900" +auth-server: https://staging.account.thethingsnetwork.org tls: true key-dir: "./.env/broker/" diff --git a/.env/discovery/auth-server.pub b/.env/discovery/auth-server.pub deleted file mode 100644 index 52cb60569..000000000 --- a/.env/discovery/auth-server.pub +++ /dev/null @@ -1 +0,0 @@ -{"algorithm":"RS256","key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzxNptNCOwLZM5SFcUI/l\npqoN2YofNAFN4jNyv1L1qHb+6V82+pRA5QnGJCAOo3bcx3Rra170hJfJUE4f3YxC\n4OaCFflzIwZcG5gaSl25Bm4ISO/y8HbOUtDFl8djkvcQEDu1fndKy7Hfmvghn+Rn\nV68YLfXocmjM1XFD6aiRQhWDU4Pkdhso+hMd56qPziLo0X+Ebt9GuDj3VsfxDQz/\nli7o2RLj7IRfX+mMn1K5OFOsSm0TuMxhaIhpxizoh3XkW7oe6H66uvoEVkI/0rBe\nKeWzmJIt+gGbRnZnqb3tevXnSYKh9aQb/o5VYfC6x6xBpXA2yIQQILheNHuwn7t/\naQIDAQAB\n-----END PUBLIC KEY-----\n"} \ No newline at end of file diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index 8e537ff22..e88f03ba6 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -1,6 +1,5 @@ id: dev debug: true discovery-server: "localhost:1900" +auth-server: https://staging.account.thethingsnetwork.org key-dir: "./.env/discovery/" -# We use a non-existing server, so that the discovery just grabs the auth-server.pub form the key-dir -auth-server: "http://localhost:5000" diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 647501f03..574f5fbb7 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -1,6 +1,7 @@ id: dev debug: true discovery-server: "localhost:1900" +auth-server: https://staging.account.thethingsnetwork.org tls: true key-dir: "./.env/handler/" diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index d9137acdc..10429b96c 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -1,4 +1,5 @@ debug: true discovery-server: "localhost:1900" +auth-server: https://staging.account.thethingsnetwork.org tls: true key-dir: "./.env/networkserver/" diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 06b371ead..e201cc840 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -1,6 +1,7 @@ id: dev debug: true discovery-server: "localhost:1900" +auth-server: https://staging.account.thethingsnetwork.org tls: true key-dir: "./.env/router/" diff --git a/.env/ttnctl.yaml.dev-example b/.env/ttnctl.yaml.dev-example index 30a6b0522..c8a80dc0c 100644 --- a/.env/ttnctl.yaml.dev-example +++ b/.env/ttnctl.yaml.dev-example @@ -2,3 +2,4 @@ discovery-server: localhost:1900 mqtt-broker: localhost:1883 ttn-handler: dev ttn-router: dev +ttn-account-server: https://staging.account.thethingsnetwork.org diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go index b3d9f61df..a7ec90943 100644 --- a/ttnctl/cmd/user_register.go +++ b/ttnctl/cmd/user_register.go @@ -33,6 +33,7 @@ var userRegisterCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not register user") } ctx.Info("Registered user") + ctx.Warn("You might have to verify your email before you can login") }, } From 242ec0cb880a9086836a8dda55de622044717ff8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 1 Aug 2016 19:21:41 +0200 Subject: [PATCH 1656/2266] Clarify ttnctl configuration --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff2d8be7a..06c97acef 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ When you get started with The Things Network, you'll probably have some question ## Build, install and run The Things Network's backend locally -1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` +1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` by copying that file to `~/.ttnctl.yaml`. 2. Run `make install` 3. Run `forego start` 4. First time only (or when Redis is flushed): @@ -48,7 +48,7 @@ When you get started with The Things Network, you'll probably have some question ## Build and run The Things Network's backend in Docker -1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` +1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` by copying that file to `~/.ttnctl.yaml`. 2. Add the following line to your `/etc/hosts` file: `127.0.0.1 router handler` 3. Run `make install docker` From c39e823763cca7885501878bb867179c409703bd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 11:05:27 +0200 Subject: [PATCH 1657/2266] [ttnctl] Create new config file if it doesn't exist --- ttnctl/util/account.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index b2041a17c..2e263e2ae 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -56,9 +56,12 @@ func saveToken(ctx log.Interface, token *oauth2.Token) { ctx.WithError(err).Fatal("Could not save access token") } if viper.GetString("oauth2-token") != string(tokenBytes) { - err = SetConfig(map[string]interface{}{ - "oauth2-token": string(tokenBytes), - }) + config, _ := ReadConfig() + if config == nil { + config = map[string]interface{}{} + } + config["oauth2-token"] = string(tokenBytes) + err = WriteConfigFile(config) if err != nil { ctx.WithError(err).Fatal("Could not save access token") } From 9a78d49a3e5beefb0bf1bdf30c07792fe3e218be Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 11:43:31 +0200 Subject: [PATCH 1658/2266] Add ttnctl README --- ttnctl/README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 ttnctl/README.md diff --git a/ttnctl/README.md b/ttnctl/README.md new file mode 100644 index 000000000..4377fc3fe --- /dev/null +++ b/ttnctl/README.md @@ -0,0 +1,78 @@ +# The Things Network Control Utility - `ttnctl` + +This document is a simple guide to `ttnctl`. + +## Configuration + +Configuration is done with: + +* Command line arguments +* Environment variables +* Configuration file + +The following configuration options can be set: + +| CLI flag / yaml key | Environment Var | Description | +|-----------------------|-----------------------------|--------------| +| `app-id` | `TTNCTL_APP_ID` | The application ID that should be used | +| `app-eui` | `TTNCTL_APP_EUI` | The LoRaWAN AppEUI that should be used | +| `debug` | `TTNCTL_DEBUG` | Print debug logs | +| `discovery-server` | `TTNCTL_DISCOVERY_SERVER` | The address and port of the discovery server | +| `ttn-router` | `TTNCTL_TTN_ROUTER` | The id of the router | +| `ttn-handler` | `TTNCTL_TTN_HANDLER` | The id of the handler | +| `mqtt-broker` | `TTNCTL_MQTT_BROKER` | The address and port of the MQTT broker | +| `ttn-account-server` | `TTNCTL_TTN_ACCOUNT_SERVER` | The protocol, address (and port) of the account server | + +**Configuration for Development:** Copy `../.env/ttnctl.yaml.dev-example` to `~/.ttnctl.yaml` + +## Command Options + +The arguments and flags for each command are shown when executing a command with the `--help` flag. + +## Getting Started + +* Create an account: `ttnctl user register [username] [e-mail]` + * Note: You might have to verify your email before you can login. +* Login: `ttnctl user login [e-mail]` +* List your applications: `ttnctl applications list` +* Create a new application: `ttnctl applications create [AppID] [Description]` +* Select the application you want to use from now on: `ttnctl applications select` +* Register the application with the Handler: `ttnctl applications register` +* List the devices in your application: `ttnctl devices list` +* Create a new device: `ttnctl devices create [Device ID]` +* Get info about the device: `ttnctl devices info [Device ID]` +* Personalize the device (optional): `ttnctl devices personalize [Device ID]` +* Set the next downlink for a device: `ttnctl downlink [Device ID] [Payload]` +* Subscribe to messages from your devices: `ttnctl subscribe` +* Get payload functions for your application: `ttnctl applications pf` +* Set payload functions for your application: `ttnctl applications pf set [decoder/converter/validator]` + +## List of commands + +``` +ttnctl +|-- user + |-- register + |-- login + |-- logout +|-- applications + |-- create + |-- delete + |-- list + |-- select + |-- info + |-- register + |-- unregister + |-- pf + |-- set +|-- devices + |-- create + |-- delete + |-- list + |-- info + |-- set + |-- personalize +|-- downlink +|-- subscribe +|-- version +``` From 4d16b58a5034d58382de9b4f393c043508f4b1d7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 16:31:43 +0200 Subject: [PATCH 1659/2266] Check Redis connection in Discovery --- cmd/discovery.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/discovery.go b/cmd/discovery.go index b4ba55425..0c02e0896 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -42,6 +42,8 @@ var discoveryCmd = &cobra.Command{ DB: int64(viper.GetInt("discovery.redis-db")), }) + connectRedis(client) + // Component component, err := core.NewComponent(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) if err != nil { From e381b44604bfb749e7241b6aef840440a24be6ec Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 16:32:15 +0200 Subject: [PATCH 1660/2266] Don't wait more than 1 second in MQTT connection attempt --- mqtt/client.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 537418883..d33840bb1 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -159,17 +159,17 @@ func (c *DefaultClient) Connect() error { } var err error for retries := 0; retries < ConnectRetries; retries++ { - c.ctx.Debug("Connecting to MQTT...") token := c.mqtt.Connect() - token.Wait() + finished := token.WaitTimeout(1 * time.Second) err = token.Error() - if err == nil { + if finished && err == nil { break } + c.ctx.WithError(err).Warn("Could not connect to MQTT Broker. Retrying...") <-time.After(ConnectRetryDelay) } if err != nil { - return fmt.Errorf("Could not connect: %s", err) + return fmt.Errorf("Could not connect to MQTT Broker: %s", err) } return nil } From 79266df430b1f3ff42ce8dd914c3c6620072e10f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 17:21:04 +0200 Subject: [PATCH 1661/2266] Log warning on Redis reconnect --- cmd/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/root.go b/cmd/root.go index b1b8ba9a0..9f5e7efdb 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -132,6 +132,7 @@ func connectRedis(client *redis.Client) error { if err == nil { break } + ctx.WithError(err).Warn("Could not connect to Redis. Retrying...") <-time.After(RedisConnectRetryDelay) } if err != nil { From adf543730071d476f1892a528d861b818abb71f2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 17:23:26 +0200 Subject: [PATCH 1662/2266] Add health server --- cmd/root.go | 3 +++ core/broker/broker.go | 1 + core/component.go | 39 +++++++++++++++++++++++++++++ core/discovery/discovery.go | 1 + core/handler/handler.go | 2 ++ core/networkserver/networkserver.go | 1 + core/router/router.go | 1 + 7 files changed, 48 insertions(+) diff --git a/cmd/root.go b/cmd/root.go index 9f5e7efdb..aadf1a8bc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,6 +84,9 @@ func init() { RootCmd.PersistentFlags().String("auth-token", "", "The auth token signed JWT from the auth-server") viper.BindPFlag("auth-token", RootCmd.PersistentFlags().Lookup("auth-token")) + RootCmd.PersistentFlags().Int("health-port", 0, "The port number where the health server should be started") + viper.BindPFlag("health-port", RootCmd.PersistentFlags().Lookup("health-port")) + dir, err := homedir.Dir() if err == nil { dir, _ = homedir.Expand(dir) diff --git a/core/broker/broker.go b/core/broker/broker.go index c4e0fc92c..38dfc742e 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -81,6 +81,7 @@ func (b *broker) Init(c *core.Component) error { } b.ns = networkserver.NewNetworkServerClient(conn) b.nsManager = pb_lorawan.NewDeviceManagerClient(conn) + b.Component.SetStatus(core.StatusHealthy) return nil } diff --git a/core/component.go b/core/component.go index 799ca8395..93d4c9a5a 100644 --- a/core/component.go +++ b/core/component.go @@ -7,8 +7,10 @@ import ( "crypto/tls" "errors" "fmt" + "net/http" "path" "runtime" + "sync/atomic" "time" "golang.org/x/net/context" @@ -90,9 +92,35 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } } + if healthPort := viper.GetInt("health-port"); healthPort > 0 { + http.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { + switch component.GetStatus() { + case StatusHealthy: + w.WriteHeader(200) + w.Write([]byte("Status is HEALTHY")) + return + case StatusUnhealthy: + w.WriteHeader(503) + w.Write([]byte("Status is UNHEALTHY")) + return + } + }) + http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) + } + return component, nil } +// Status indicates the health status of this component +type Status int + +const ( + // StatusHealthy indicates a healthy component + StatusHealthy Status = iota + // StatusUnhealthy indicates an unhealthy component + StatusUnhealthy +) + // Component contains the common attributes for all TTN components type Component struct { Identity *pb_discovery.Announcement @@ -102,6 +130,17 @@ type Component struct { privateKey string tlsConfig *tls.Config TokenKeyProvider tokenkey.Provider + status int64 +} + +// GetStatus gets the health status of the component +func (c *Component) GetStatus() Status { + return Status(atomic.LoadInt64(&c.status)) +} + +// SetStatus sets the health status of the component +func (c *Component) SetStatus(status Status) { + atomic.StoreInt64(&c.status, int64(status)) } // Discover is used to discover another component diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 575cf915d..331483104 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -36,6 +36,7 @@ func (d *discovery) Init(c *core.Component) error { if err != nil { return err } + d.Component.SetStatus(core.StatusHealthy) return nil } diff --git a/core/handler/handler.go b/core/handler/handler.go index 6cb2a086f..1927ef47f 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -91,6 +91,8 @@ func (h *handler) Init(c *core.Component) error { return err } + h.Component.SetStatus(core.StatusHealthy) + return nil } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index b030ff8ea..2880c2078 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -72,6 +72,7 @@ func (n *networkServer) Init(c *core.Component) error { if err != nil { return err } + n.Component.SetStatus(core.StatusHealthy) return nil } diff --git a/core/router/router.go b/core/router/router.go index 89cc98b91..d55636a5b 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -84,6 +84,7 @@ func (r *router) Init(c *core.Component) error { r.tickGateways() } }() + r.Component.SetStatus(core.StatusHealthy) return nil } From ee8ecb23d518044841aa0f832beb9649cb6b41d1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 17:49:15 +0200 Subject: [PATCH 1663/2266] Timeout MQTT tests --- core/handler/mqtt_test.go | 5 ++--- mqtt/client_test.go | 21 ++++++++++----------- utils/testing/testing.go | 26 ++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 14 deletions(-) diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 527bb7bde..01d86ffec 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -4,7 +4,6 @@ package handler import ( - "sync" "testing" "time" @@ -17,7 +16,7 @@ import ( func TestHandleMQTT(t *testing.T) { a := New(t) - var wg sync.WaitGroup + var wg WaitGroup c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", "tcp://localhost:1883") c.Connect() appID := "handler-mqtt-app1" @@ -68,5 +67,5 @@ func TestHandleMQTT(t *testing.T) { AppID: appID, } - wg.Wait() + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 37e0ea89b..e15eb021a 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -5,7 +5,6 @@ package mqtt import ( "fmt" - "sync" "testing" "time" @@ -208,7 +207,7 @@ func TestPubSubAppUplink(t *testing.T) { c.Connect() defer c.Disconnect() - var wg sync.WaitGroup + var wg WaitGroup wg.Add(2) @@ -229,7 +228,7 @@ func TestPubSubAppUplink(t *testing.T) { Payload: []byte{0x01, 0x02, 0x03, 0x04}, }).Wait() - wg.Wait() + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) c.UnsubscribeAppUplink("app1").Wait() } @@ -311,7 +310,7 @@ func TestPubSubDownlink(t *testing.T) { c.Connect() defer c.Disconnect() - var wg sync.WaitGroup + var wg WaitGroup wg.Add(1) @@ -328,7 +327,7 @@ func TestPubSubDownlink(t *testing.T) { Payload: []byte{0x01, 0x02, 0x03, 0x04}, }).Wait() - wg.Wait() + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) c.UnsubscribeDeviceDownlink("app3", "dev3").Wait() } @@ -339,7 +338,7 @@ func TestPubSubAppDownlink(t *testing.T) { c.Connect() defer c.Disconnect() - var wg sync.WaitGroup + var wg WaitGroup wg.Add(2) @@ -360,7 +359,7 @@ func TestPubSubAppDownlink(t *testing.T) { Payload: []byte{0x01, 0x02, 0x03, 0x04}, }).Wait() - wg.Wait() + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) c.UnsubscribeAppDownlink("app3").Wait() } @@ -442,7 +441,7 @@ func TestPubSubActivations(t *testing.T) { c.Connect() defer c.Disconnect() - var wg sync.WaitGroup + var wg WaitGroup wg.Add(1) @@ -459,7 +458,7 @@ func TestPubSubActivations(t *testing.T) { Metadata: Metadata{DataRate: "SF7BW125"}, }).Wait() - wg.Wait() + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) c.UnsubscribeDeviceActivations("app5", "dev1") } @@ -470,7 +469,7 @@ func TestPubSubAppActivations(t *testing.T) { c.Connect() defer c.Disconnect() - var wg sync.WaitGroup + var wg WaitGroup wg.Add(2) @@ -491,7 +490,7 @@ func TestPubSubAppActivations(t *testing.T) { Metadata: Metadata{DataRate: "SF7BW125"}, }).Wait() - wg.Wait() + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) c.UnsubscribeAppActivations("app6") } diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 2ce75c75a..973c5915c 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -6,7 +6,11 @@ package testing import ( + "errors" + "fmt" + "sync" "testing" + "time" "github.com/apex/log" ) @@ -18,3 +22,25 @@ func GetLogger(t *testing.T, tag string) log.Interface { } return logger.WithField("tag", tag) } + +// WaitGroup is an extension of sync.WaitGroup with a WaitFor function for testing +type WaitGroup struct { + sync.WaitGroup +} + +// WaitFor waits for the specified duration +func (wg *WaitGroup) WaitFor(d time.Duration) error { + waitChan := make(chan bool) + go func() { + wg.Wait() + fmt.Println("WG DONE") + waitChan <- true + close(waitChan) + }() + select { + case <-waitChan: + return nil + case <-time.After(d): + return errors.New("Wait timeout expired") + } +} From 39263f55f9dcbd389c66175164b8a02df524caa3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rom=C4=81ns=20Volosatovs?= Date: Tue, 2 Aug 2016 17:58:40 +0200 Subject: [PATCH 1664/2266] Gateway status command implementation (#212) * ttnctl/util: GetRouterManager function as implemented by @htdvisser * ttnctl/cmd/root.go: Register RouterManager service * ttnctl/cmd/: `gateway` placeholder * ttnctl/cmd/: `gateway status` command implementation --- cmd/router.go | 1 + ttnctl/cmd/gateway.go | 19 +++++++++ ttnctl/cmd/gateway_status.go | 75 ++++++++++++++++++++++++++++++++++++ ttnctl/util/router.go | 18 +++++++++ 4 files changed, 113 insertions(+) create mode 100644 ttnctl/cmd/gateway.go create mode 100644 ttnctl/cmd/gateway_status.go diff --git a/cmd/router.go b/cmd/router.go index 483a46299..7ab190156 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -55,6 +55,7 @@ var routerCmd = &cobra.Command{ // Register and Listen router.RegisterRPC(grpc) + router.RegisterManager(grpc) go grpc.Serve(lis) sigChan := make(chan os.Signal) diff --git a/ttnctl/cmd/gateway.go b/ttnctl/cmd/gateway.go new file mode 100644 index 000000000..c5228d46b --- /dev/null +++ b/ttnctl/cmd/gateway.go @@ -0,0 +1,19 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import "github.com/spf13/cobra" + +var gatewayCmd = &cobra.Command{ + Use: "gateway", + Short: "Manage gateways", + Long: `ttnctl applications can be used to manage gateways.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + }, +} + +func init() { + RootCmd.AddCommand(gatewayCmd) +} diff --git a/ttnctl/cmd/gateway_status.go b/ttnctl/cmd/gateway_status.go new file mode 100644 index 000000000..018e21cf9 --- /dev/null +++ b/ttnctl/cmd/gateway_status.go @@ -0,0 +1,75 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewayStatusCmd = &cobra.Command{ + Use: "status [GatewayEUI]", + Short: "Get status of a gateway", + Long: `ttnctl gateway status can be used to get status of gateways.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + euiString := args[0] + ctx = ctx.WithField("Gateway EUI", euiString) + + eui, err := types.ParseGatewayEUI(euiString) + if err != nil { + ctx.WithError(err).Fatal("Invalid Gateway EUI") + } + + conn, manager := util.GetRouterManager(ctx) + defer conn.Close() + + resp, err := manager.GatewayStatus(util.GetContext(ctx), &router.GatewayStatusRequest{ + GatewayEui: &eui, + }) + if err != nil { + ctx.WithError(err).Fatal("Could not get status of gateway.") + } + + ctx.Infof("Received status") + fmt.Println() + printKV("Last seen", time.Unix(0, resp.LastSeen)) + printKV("Timestamp", time.Duration(resp.Status.Timestamp)) + if t := resp.Status.Time; t != 0 { + printKV("Reported time", time.Unix(0, t)) + } + printKV("Description", resp.Status.Description) + printKV("Platform", resp.Status.Platform) + printKV("Contact email", resp.Status.ContactEmail) + printKV("Region", resp.Status.Region) + printKV("GPS coordinates", func() interface{} { + if gps := resp.Status.Gps; gps != nil && !(gps.Latitude == 0 && gps.Longitude == 0) { + return fmt.Sprintf("(%.6f %.6f)", gps.Latitude, gps.Longitude) + } + return "not available" + }()) + printKV("Rtt", func() interface{} { + if t := resp.Status.Rtt; t != 0 { + return time.Duration(t) + } + return "not available" + }()) + printKV("Rx", fmt.Sprintf("(in: %d; ok: %d)", resp.Status.RxIn, resp.Status.RxOk)) + printKV("Tx", fmt.Sprintf("(in: %d; ok: %d)", resp.Status.TxIn, resp.Status.TxOk)) + fmt.Println() + }, +} + +func init() { + gatewayCmd.AddCommand(gatewayStatusCmd) +} diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index 910ec1705..9427c39f0 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -28,3 +28,21 @@ func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { } return rtrConn, router.NewRouterClient(rtrConn) } + +// GetRouterManager starts a management connection with the router +func GetRouterManager(ctx log.Interface) (*grpc.ClientConn, router.RouterManagerClient) { + dscConn, client := GetDiscovery(ctx) + defer dscConn.Close() + routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ + ServiceName: "router", + Id: viper.GetString("ttn-router"), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not get Router from Discovery") + } + rtrConn, err := routerAnnouncement.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not connect to Router") + } + return rtrConn, router.NewRouterManagerClient(rtrConn) +} From bc2c36c5abe7c7507aa06416a9eec44aaa44dff1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 2 Aug 2016 18:02:22 +0200 Subject: [PATCH 1665/2266] =?UTF-8?q?Add=20Roma=CC=84ns=20to=20AUTHORS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AUTHORS | 1 + ttnctl/AUTHORS | 1 + 2 files changed, 2 insertions(+) diff --git a/AUTHORS b/AUTHORS index 7371c433f..3bdfaf13c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -11,3 +11,4 @@ Hylke Visser Johan Stokking Matthias Benkort +Romans Volosatovs diff --git a/ttnctl/AUTHORS b/ttnctl/AUTHORS index 6177a203c..624e22ba1 100644 --- a/ttnctl/AUTHORS +++ b/ttnctl/AUTHORS @@ -10,3 +10,4 @@ Hylke Visser Johan Stokking +Romans Volosatovs From 25da1253451bb17d2528d551423f3d853981dc59 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 1 Aug 2016 17:32:36 +0200 Subject: [PATCH 1666/2266] add account.EditPassword --- core/account/users.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/account/users.go b/core/account/users.go index 1501c2aa3..e94e170bb 100644 --- a/core/account/users.go +++ b/core/account/users.go @@ -76,3 +76,23 @@ func (a *Account) EditProfile(profile Profile) error { } return err } + +type editPasswordReq struct { + OldPassword string `json:"old_password"` + Password string `json:"password"` +} + +// EditPassword edits the users password, it requires the old password +// to be given. +func (a *Account) EditPassword(oldPassword, newPassword string) error { + edits := editPasswordReq{ + OldPassword: oldPassword, + Password: newPassword, + } + + err := a.patch("/api/users/me", edits, nil) + if err != nil { + return fmt.Errorf("Could change not password: %s", err) + } + return nil +} From 3760f1100b9ac38ea72f351fd21e7130edb41b8c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 3 Aug 2016 14:04:27 +0200 Subject: [PATCH 1667/2266] Testing, CI and CD --- .drone.sec | 1 - .drone.yml | 46 ------------ .gitlab-ci.yml | 86 +++++++++++++++++++++++ .travis.yml | 13 ++-- Dockerfile | 14 +--- Dockerfile.local | 5 -- Makefile | 2 +- core/discovery/announcement/store_test.go | 8 ++- core/discovery/discovery_test.go | 10 ++- core/handler/application/store_test.go | 8 ++- core/handler/device/store_test.go | 8 ++- core/handler/mqtt_test.go | 15 ++-- core/networkserver/device/store_test.go | 8 ++- core/router/gateway/status_test.go | 8 ++- coverage.sh | 11 --- mqtt/client_test.go | 56 +++++++++------ utils/testing/testing.go | 2 - 17 files changed, 181 insertions(+), 120 deletions(-) delete mode 100644 .drone.sec delete mode 100644 .drone.yml create mode 100644 .gitlab-ci.yml delete mode 100644 Dockerfile.local delete mode 100755 coverage.sh diff --git a/.drone.sec b/.drone.sec deleted file mode 100644 index 7a6f90106..000000000 --- a/.drone.sec +++ /dev/null @@ -1 +0,0 @@ -eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkExMjhHQ00ifQ.n9QUGATHW2RFFSgvadl1JZ80jbdqiCM7AyrulN1R7MOlB1c8S7Z29Ioc3f3HGx3eBiFokb0uCKDOnSt3J2eUGvhYHXNxn7C18gHOIhA_vS4fMistcPgX68a-Rietp0DkdA5S1WLQvOLDEpkmt_wltDVkApQ8ROLQQKHPsQCltt16C5jou6QrDjCadiiY8zHLInAvVVwi7jIpL98nhz50YK_CZfB7H4pvWVSfFqZiSt-XoN6mCfzn44-hoB-WDz4UdHmgEnxa_CktpKPXc9PtMod73yhpBJf87vUHounfcJJJwK1fSjNhJlc7bHli8Sfp0dDSJZSOe5m4iZc9bJC9Bg.xQj0AkcaSwPQkO3K.FC5xexkTbNUW89TFveVqLhrhZ3Uh9HKUgawzp3Ogypx0f5Glw7ELFuTUcRrNDArWZuLxChLGcKoKFNke4Jlc4T39zKmNaV_hgqS1Dt5Uv9MT_j8JZi1OTLa1UwxLOBJqAbK9azcwhV9FPb2X46ts-yZsuCyMqe2gBCN_zFYVw4UCGFjwR81QwiKR-7jFm4sklpIxcrztBNeNhmpmHs8VE25Ya1dy5wyvWwhFlI9FTm01DPDsG2xOZcbvvOO9N9lAAkuvnGJyM-JZIbKnf-Fxs9MORLoqcngumfAKxjwUI4dDBKFmWLxakIDsvBdUBp1JtIzI2vwg71m1oBgvN0DXzfA7_Z55u7fJpkShOct74ewlytmWwAZ_1BSaP45CPJ7hAkr5XQM1Vn-Jozudmw24VBbC2kOm2RrlPZRvni35eOc4PJ4njoTutLLMTs3XcvdIhTeKdEwZp1-_3Ex059Im6t8PPYcx.0rqytqBzBFPMRKkkGrubGg \ No newline at end of file diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index 82f3bd339..000000000 --- a/.drone.yml +++ /dev/null @@ -1,46 +0,0 @@ -clone: - recursive: true - -matrix: - TARGET_PLATFORM: - - linux-386 - - linux-amd64 - - linux-arm - - darwin-amd64 - - windows-386 - - windows-amd64 - -compose: - mqtt: - image: ansi/mosquitto - -build: - image: htdvisser/ttnbuild - pull: true - environment: - - RELEASE_DIR=release/branch/$$BRANCH - commands: - # ttnbuild already contains dependencies, so we copy them: - - rsync -a /go/src/ /drone/src/ - - make deps - - make build package - when: - branch: [master, develop] - -publish: - azure_storage: - image: htdvisser/drone-azure-storage - account_key: $$AZURE_STORAGE_KEY - storage_account: ttnreleases - container: release - source: release/ - when: - branch: [master, develop] - - dockerhub: - repo: thethingsnetwork/ttn - token: $$DOCKER_TOKEN - when: - branch: master - matrix: - TARGET_PLATFORM: linux-amd64 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 000000000..03e3be2b5 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,86 @@ +stages: + - deps + - test + - build + - package + +variables: + CONTAINER_NAME: thethingsnetwork/ttn + +cache: + key: "$CI_BUILD_REF_NAME/go_src" + paths: + - go_src/ + - vendor/ + +before_script: + - git submodule update --init + - rm -rf $GOPATH/src + - "if [ -d go_src ]; then mv go_src $GOPATH/src; fi" + - mkdir -p $GOPATH/src/github.com/TheThingsNetwork/ttn + - cp -R . $GOPATH/src/github.com/TheThingsNetwork/ttn + +after_script: + - rm -rf $GOPATH/src/github.com/TheThingsNetwork/ttn + - cp -R $GOPATH/src go_src + +deps: + stage: deps + image: golang:latest + script: + - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - TARGET_PLATFORM=linux-386 make deps + - TARGET_PLATFORM=linux-amd64 make deps + - TARGET_PLATFORM=linux-arm make deps + - TARGET_PLATFORM=darwin-amd64 make deps + - TARGET_PLATFORM=windows-386 make deps + - TARGET_PLATFORM=windows-amd64 make deps + - make test-deps cover-deps + - popd + +tests: + stage: test + image: golang:latest + services: + - ansi/mosquitto + - redis + variables: + REDIS_HOST: redis + MQTT_HOST: ansi-mosquitto + script: + - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - make test + - popd + +binaries: + stage: build + image: golang:latest + script: + - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - TARGET_PLATFORM=linux-386 make build + - TARGET_PLATFORM=linux-amd64 make build + - TARGET_PLATFORM=linux-arm make build + - TARGET_PLATFORM=darwin-amd64 make build + - TARGET_PLATFORM=windows-386 make build + - TARGET_PLATFORM=windows-amd64 make build + - popd + - mkdir release + - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/release/* release/ + artifacts: + paths: + - release/ + +container: + stage: package + image: docker:git + services: + - "docker:dind" + script: + - cd $GOPATH/src/github.com/TheThingsNetwork/ttn + - docker build -t ttn . + - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" registry.gitlab.com + - docker tag ttn registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME + - docker push registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME + - docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_PASSWORD" + - docker tag ttn $CONTAINER_NAME:$CI_BUILD_REF_NAME + - docker push $CONTAINER_NAME:$CI_BUILD_REF_NAME diff --git a/.travis.yml b/.travis.yml index daa41f286..9b5fc1475 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,23 +2,20 @@ language: go sudo: required +services: + - docker + go: - 1.6 -before_install: - - sudo apt-get update - install: - - sudo apt-get install mosquitto - make deps - make test-deps - make cover-deps before_script: - - mosquitto -p 1883 1>/dev/null 2>/dev/null & - -services: - - redis-server + - docker run -d -p 127.0.0.1:6379:6379 redis + - docker run -d -p 127.0.0.1:1883:1883 ansi/mosquitto script: - make test diff --git a/Dockerfile b/Dockerfile index d2573ab7d..03efc6148 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,5 @@ FROM alpine - RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* - -RUN apk --update add curl tar \ - && mkdir /ttnsrc \ - && curl -sL -o /ttnsrc/ttn-linux-amd64.tar.gz "https://ttnreleases.blob.core.windows.net/release/src/github.com/TheThingsNetwork/ttn/release/branch/master/ttn-linux-amd64.tar.gz" \ - && tar -xf /ttnsrc/ttn-linux-amd64.tar.gz -C /ttnsrc \ - && mv /ttnsrc/ttn-linux-amd64 /usr/local/bin/ttn \ - && chmod 755 /usr/local/bin/ttn \ - && rm -rf /ttnsrc \ - && apk del curl tar \ - && rm -rf /var/cache/apk/* - +ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn +RUN chmod 755 /usr/local/bin/ttn ENTRYPOINT ["/usr/local/bin/ttn"] diff --git a/Dockerfile.local b/Dockerfile.local deleted file mode 100644 index 03efc6148..000000000 --- a/Dockerfile.local +++ /dev/null @@ -1,5 +0,0 @@ -FROM alpine -RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* -ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn -RUN chmod 755 /usr/local/bin/ttn -ENTRYPOINT ["/usr/local/bin/ttn"] diff --git a/Makefile b/Makefile index 5e0b7b31c..1cdea14d4 100644 --- a/Makefile +++ b/Makefile @@ -96,7 +96,7 @@ install: docker: TARGET_PLATFORM = linux-amd64 docker: clean $(RELEASE_DIR)/$(ttnbin) - docker build -t thethingsnetwork/ttn -f Dockerfile.local . + docker build -t thethingsnetwork/ttn -f Dockerfile . package: $(RELEASE_DIR)/$(ttnpkg).zip $(RELEASE_DIR)/$(ttnpkg).tar.gz $(RELEASE_DIR)/$(ttnpkg).tar.xz $(RELEASE_DIR)/$(ttnctlpkg).zip $(RELEASE_DIR)/$(ttnctlpkg).tar.gz $(RELEASE_DIR)/$(ttnctlpkg).tar.xz diff --git a/core/discovery/announcement/store_test.go b/core/discovery/announcement/store_test.go index c46abce8b..3e2dff724 100644 --- a/core/discovery/announcement/store_test.go +++ b/core/discovery/announcement/store_test.go @@ -4,6 +4,8 @@ package announcement import ( + "fmt" + "os" "testing" pb "github.com/TheThingsNetwork/ttn/api/discovery" @@ -12,8 +14,12 @@ import ( ) func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", + Addr: fmt.Sprintf("%s:6379", host), Password: "", // no password set DB: 1, // use default DB }) diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 4d40db43b..66f63f672 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -4,6 +4,8 @@ package discovery import ( + "fmt" + "os" "testing" "gopkg.in/redis.v3" @@ -14,10 +16,14 @@ import ( ) func getRedisClient(db int64) *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", + Addr: fmt.Sprintf("%s:6379", host), Password: "", // no password set - DB: db, // use default DB + DB: db, }) } diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go index fb9703aea..8180ee2f9 100644 --- a/core/handler/application/store_test.go +++ b/core/handler/application/store_test.go @@ -4,6 +4,8 @@ package application import ( + "fmt" + "os" "testing" "gopkg.in/redis.v3" @@ -12,8 +14,12 @@ import ( ) func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", + Addr: fmt.Sprintf("%s:6379", host), Password: "", // no password set DB: 1, // use default DB }) diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go index 8df62f25d..7736b2bf4 100644 --- a/core/handler/device/store_test.go +++ b/core/handler/device/store_test.go @@ -4,6 +4,8 @@ package device import ( + "fmt" + "os" "testing" "gopkg.in/redis.v3" @@ -13,8 +15,12 @@ import ( ) func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", + Addr: fmt.Sprintf("%s:6379", host), Password: "", // no password set DB: 1, // use default DB }) diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 01d86ffec..c93334e27 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -4,6 +4,8 @@ package handler import ( + "fmt" + "os" "testing" "time" @@ -15,9 +17,14 @@ import ( ) func TestHandleMQTT(t *testing.T) { + host := os.Getenv("MQTT_HOST") + if host == "" { + host = "localhost" + } + a := New(t) var wg WaitGroup - c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", "tcp://localhost:1883") + c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() appID := "handler-mqtt-app1" devID := "handler-mqtt-dev1" @@ -29,7 +36,7 @@ func TestHandleMQTT(t *testing.T) { AppID: appID, DevID: devID, }) - err := h.HandleMQTT("", "", "tcp://localhost:1883") + err := h.HandleMQTT("", "", fmt.Sprintf("tcp://%s:1883", host)) a.So(err, ShouldBeNil) c.PublishDownlink(mqtt.DownlinkMessage{ @@ -47,7 +54,7 @@ func TestHandleMQTT(t *testing.T) { a.So(r_devID, ShouldEqual, devID) a.So(req.Payload, ShouldResemble, []byte{0xAA, 0xBC}) wg.Done() - }) + }).Wait() h.mqttUp <- &mqtt.UplinkMessage{ DevID: devID, @@ -60,7 +67,7 @@ func TestHandleMQTT(t *testing.T) { a.So(r_appID, ShouldEqual, appID) a.So(r_devID, ShouldEqual, devID) wg.Done() - }) + }).Wait() h.mqttActivation <- &mqtt.Activation{ DevID: devID, diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go index ab1f0be57..c0263ad00 100644 --- a/core/networkserver/device/store_test.go +++ b/core/networkserver/device/store_test.go @@ -4,6 +4,8 @@ package device import ( + "fmt" + "os" "testing" "gopkg.in/redis.v3" @@ -13,8 +15,12 @@ import ( ) func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", + Addr: fmt.Sprintf("%s:6379", host), Password: "", // no password set DB: 1, // use default DB }) diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go index bc7084943..6ba59bfc6 100644 --- a/core/router/gateway/status_test.go +++ b/core/router/gateway/status_test.go @@ -4,6 +4,8 @@ package gateway import ( + "fmt" + "os" "testing" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -14,8 +16,12 @@ import ( ) func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } return redis.NewClient(&redis.Options{ - Addr: "localhost:6379", + Addr: fmt.Sprintf("%s:6379", host), Password: "", // no password set DB: 1, // use default DB }) diff --git a/coverage.sh b/coverage.sh deleted file mode 100755 index fe1cbdceb..000000000 --- a/coverage.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -mkdir .cover -for pkg in $(go list ./... | grep -E 'core' | grep -vE 'core$|mocks$') -do - profile=".cover/$(echo $pkg | grep -oE 'ttn/.*' | sed 's/\///g').cover" - go test -cover -coverprofile=$profile $pkg -done -echo "mode: set" > coverage.out && cat .cover/*.cover | grep -v mode: | sort -r | \ -awk '{if($1 != last) {print $0;last=$1}}' >> coverage.out -rm -r .cover diff --git a/mqtt/client_test.go b/mqtt/client_test.go index e15eb021a..5396f1ea8 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -5,6 +5,7 @@ package mqtt import ( "fmt" + "os" "testing" "time" @@ -13,6 +14,15 @@ import ( . "github.com/smartystreets/assertions" ) +var host string + +func init() { + host = os.Getenv("MQTT_HOST") + if host == "" { + host = "localhost" + } +} + func TestToken(t *testing.T) { a := New(t) @@ -27,13 +37,13 @@ func TestToken(t *testing.T) { func TestNewClient(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) a.So(c.(*DefaultClient).mqtt, ShouldNotBeNil) } func TestConnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) err := c.Connect() defer c.Disconnect() a.So(err, ShouldBeNil) @@ -60,7 +70,7 @@ func TestConnectInvalidCredentials(t *testing.T) { func TestIsConnected(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) a.So(c.IsConnected(), ShouldBeFalse) @@ -72,7 +82,7 @@ func TestIsConnected(t *testing.T) { func TestDisconnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) // Disconnecting when not connected should not change anything c.Disconnect() @@ -88,7 +98,7 @@ func TestDisconnect(t *testing.T) { func TestRandomTopicPublish(t *testing.T) { ctx := GetLogger(t, "TestRandomTopicPublish") - c := NewClient(ctx, "test", "", "", "tcp://localhost:1883") + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -104,7 +114,7 @@ func TestRandomTopicPublish(t *testing.T) { func TestPublishUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -122,7 +132,7 @@ func TestPublishUplink(t *testing.T) { func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -139,7 +149,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { func TestSubscribeAppUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -156,7 +166,7 @@ func TestSubscribeAppUplink(t *testing.T) { func TestSubscribeUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -173,7 +183,7 @@ func TestSubscribeUplink(t *testing.T) { func TestPubSubUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -203,7 +213,7 @@ func TestPubSubUplink(t *testing.T) { func TestPubSubAppUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -237,7 +247,7 @@ func TestPubSubAppUplink(t *testing.T) { func TestPublishDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -255,7 +265,7 @@ func TestPublishDownlink(t *testing.T) { func TestSubscribeDeviceDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -272,7 +282,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { func TestSubscribeAppDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -289,7 +299,7 @@ func TestSubscribeAppDownlink(t *testing.T) { func TestSubscribeDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -306,7 +316,7 @@ func TestSubscribeDownlink(t *testing.T) { func TestPubSubDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -334,7 +344,7 @@ func TestPubSubDownlink(t *testing.T) { func TestPubSubAppDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -368,7 +378,7 @@ func TestPubSubAppDownlink(t *testing.T) { func TestPublishActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -386,7 +396,7 @@ func TestPublishActivations(t *testing.T) { func TestSubscribeDeviceActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -403,7 +413,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { func TestSubscribeAppActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -420,7 +430,7 @@ func TestSubscribeAppActivations(t *testing.T) { func TestSubscribeActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -437,7 +447,7 @@ func TestSubscribeActivations(t *testing.T) { func TestPubSubActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() @@ -465,7 +475,7 @@ func TestPubSubActivations(t *testing.T) { func TestPubSubAppActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:1883") + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() diff --git a/utils/testing/testing.go b/utils/testing/testing.go index 973c5915c..24824ddae 100644 --- a/utils/testing/testing.go +++ b/utils/testing/testing.go @@ -7,7 +7,6 @@ package testing import ( "errors" - "fmt" "sync" "testing" "time" @@ -33,7 +32,6 @@ func (wg *WaitGroup) WaitFor(d time.Duration) error { waitChan := make(chan bool) go func() { wg.Wait() - fmt.Println("WG DONE") waitChan <- true close(waitChan) }() From fd597e58a138936aa52fc0c5f8ead026840f3560 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 3 Aug 2016 15:48:04 +0200 Subject: [PATCH 1668/2266] Health server: ListenAndServe in your own goroutine --- core/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/component.go b/core/component.go index 93d4c9a5a..e142d8bd0 100644 --- a/core/component.go +++ b/core/component.go @@ -105,7 +105,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string return } }) - http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) + go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) } return component, nil From 11cd5436395eb8087d7fbcfc28b92cac67eb8a9f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 3 Aug 2016 16:53:37 +0200 Subject: [PATCH 1669/2266] Fix nil pointer when token expired --- mqtt/client.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/mqtt/client.go b/mqtt/client.go index d33840bb1..f17cf9832 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -161,8 +161,12 @@ func (c *DefaultClient) Connect() error { for retries := 0; retries < ConnectRetries; retries++ { token := c.mqtt.Connect() finished := token.WaitTimeout(1 * time.Second) + if !finished { + c.ctx.Warn("MQTT connection took longer than expected...") + token.Wait() + } err = token.Error() - if finished && err == nil { + if err == nil { break } c.ctx.WithError(err).Warn("Could not connect to MQTT Broker. Retrying...") From 4a8be46aa98fd71509fca5c72066bd98dd693499 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 4 Aug 2016 11:54:22 +0200 Subject: [PATCH 1670/2266] Reconnect gRPC streams with correct context --- core/handler/handler.go | 6 ++-- core/router/router.go | 69 +++++++++++++++++++++++++++++------------ core/router/uplink.go | 4 +-- 3 files changed, 54 insertions(+), 25 deletions(-) diff --git a/core/handler/handler.go b/core/handler/handler.go index 1927ef47f..f90267a8a 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -110,13 +110,11 @@ func (h *handler) associateBroker() error { h.ttnBrokerManager = pb_broker.NewBrokerManagerClient(conn) h.ttnDeviceManager = pb_lorawan.NewDeviceManagerClient(conn) - ctx := h.GetContext("") - h.downlink = make(chan *pb_broker.DownlinkMessage) go func() { for { - upStream, err := h.ttnBroker.Subscribe(ctx, &pb_broker.SubscribeRequest{}) + upStream, err := h.ttnBroker.Subscribe(h.GetContext(""), &pb_broker.SubscribeRequest{}) if err != nil { <-time.After(api.Backoff) continue @@ -134,7 +132,7 @@ func (h *handler) associateBroker() error { go func() { for { - downStream, err := h.ttnBroker.Publish(ctx) + downStream, err := h.ttnBroker.Publish(h.GetContext("")) if err != nil { <-time.After(api.Backoff) continue diff --git a/core/router/router.go b/core/router/router.go index d55636a5b..411aab69f 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -4,10 +4,10 @@ package router import ( - "io" "sync" "time" + "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -38,8 +38,9 @@ type Router interface { } type broker struct { - client pb_broker.BrokerClient - association pb_broker.Broker_AssociateClient + client pb_broker.BrokerClient + uplink chan *pb_broker.UplinkMessage + downlink chan *pb_broker.DownlinkMessage } // NewRouter creates a new Router @@ -120,6 +121,7 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok r.brokersLock.Lock() defer r.brokersLock.Unlock() if _, ok := r.brokers[brokerAnnouncement.Id]; !ok { + // Connect to the server conn, err := brokerAnnouncement.Dial() if err != nil { @@ -127,33 +129,62 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok } client := pb_broker.NewBrokerClient(conn) - association, err := client.Associate(r.Component.GetContext("")) - if err != nil { - return nil, err + brk := &broker{ + client: client, + uplink: make(chan *pb_broker.UplinkMessage), + downlink: make(chan *pb_broker.DownlinkMessage), } - // Start a goroutine that receives and processes downlink + go func() { + errors := 0 for { - downlink, err := association.Recv() - if err == io.EOF { - association.CloseSend() - break - } + association, err := client.Associate(r.Component.GetContext("")) if err != nil { - break + errors++ + <-time.After(api.Backoff) + if errors > 10 { + break + } + continue + } + + errChan := make(chan error) + + go func() { + for { + downlink, err := association.Recv() + if err != nil { + errChan <- err + return + } + brk.downlink <- downlink + } + }() + + associationLoop: + for { + select { + case err := <-errChan: + r.Ctx.Errorf("ttn/router: Error in Broker associate: %s", err) + break associationLoop + case uplink := <-brk.uplink: + err := association.Send(uplink) + if err != nil { + errChan <- err + } + case downlink := <-brk.downlink: + go r.HandleDownlink(downlink) + } } - go r.HandleDownlink(downlink) + } - // When the loop is broken: close connection and unregister broker. conn.Close() r.brokersLock.Lock() defer r.brokersLock.Unlock() delete(r.brokers, brokerAnnouncement.Id) }() - r.brokers[brokerAnnouncement.Id] = &broker{ - client: client, - association: association, - } + + r.brokers[brokerAnnouncement.Id] = brk } return r.brokers[brokerAnnouncement.Id], nil } diff --git a/core/router/uplink.go b/core/router/uplink.go index fc2380462..d2a73c6f2 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -106,12 +106,12 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess if err != nil { continue } - broker.association.Send(&pb_broker.UplinkMessage{ + broker.uplink <- &pb_broker.UplinkMessage{ Payload: uplink.Payload, ProtocolMetadata: uplink.ProtocolMetadata, GatewayMetadata: uplink.GatewayMetadata, DownlinkOptions: downlinkOptions, - }) + } } return nil From fa8b6397044af0ace4d008ace0612a81d675ad8e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 4 Aug 2016 13:43:34 +0200 Subject: [PATCH 1671/2266] Add file-logging to cmd/root.go --- cmd/root.go | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index aadf1a8bc..6f03b3a08 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "path" + "path/filepath" "runtime" "strings" "time" @@ -15,6 +16,9 @@ import ( cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/apex/log" + jsonHandler "github.com/apex/log/handlers/json" + levelHandler "github.com/apex/log/handlers/level" + multiHandler "github.com/apex/log/handlers/multi" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -22,6 +26,8 @@ import ( var cfgFile string +var logFile *os.File + var ctx log.Interface // RootCmd is executed when ttn is executed without a subcommand @@ -34,9 +40,26 @@ var RootCmd = &cobra.Command{ if viper.GetBool("debug") { logLevel = log.DebugLevel } + + var logHandlers []log.Handler + logHandlers = append(logHandlers, levelHandler.New(cliHandler.New(os.Stdout), logLevel)) + + if logFileLocation := viper.GetString("log-file"); logFileLocation != "" { + absLogFileLocation, err := filepath.Abs(logFileLocation) + if err != nil { + panic(err) + } + logFile, err = os.OpenFile(absLogFileLocation, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644) + if err != nil { + panic(err) + } + if err == nil { + logHandlers = append(logHandlers, levelHandler.New(jsonHandler.New(logFile), logLevel)) + } + } + ctx = &log.Logger{ - Level: logLevel, - Handler: cliHandler.New(os.Stdout), + Handler: multiHandler.New(logHandlers...), } ctx.WithFields(log.Fields{ "ComponentID": viper.GetString("id"), @@ -45,6 +68,11 @@ var RootCmd = &cobra.Command{ "AuthServer": viper.GetString("auth-server"), }).Info("Initializing The Things Network") }, + PersistentPostRun: func(cmd *cobra.Command, args []string) { + if logFile != nil { + logFile.Close() + } + }, } // Execute adds all child commands to the root command sets flags appropriately. @@ -103,6 +131,9 @@ func init() { RootCmd.PersistentFlags().String("key-dir", path.Clean(dir+"/.ttn/"), "The directory where public/private keys are stored") viper.BindPFlag("key-dir", RootCmd.PersistentFlags().Lookup("key-dir")) + + RootCmd.PersistentFlags().String("log-file", "", "Location of the log file") + viper.BindPFlag("log-file", RootCmd.PersistentFlags().Lookup("log-file")) } // initConfig reads in config file and ENV variables if set. From 5d00eb95c85d22c05f589ad8236326013cecbb73 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 4 Aug 2016 16:39:14 +0200 Subject: [PATCH 1672/2266] core/router/server.go: fix GatewayStatus 'if' order (#217) --- core/router/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index d81a48747..8979b8f04 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -62,12 +62,12 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if err == io.EOF { return stream.SendAndClose(&api.Ack{}) } - if !status.Validate() { - return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") - } if err != nil { return err } + if !status.Validate() { + return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") + } go r.router.HandleGatewayStatus(gatewayEUI, status) } } From fc79c41feee3abb05b08aaf04bca667b48dff360 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 5 Aug 2016 16:06:27 +0200 Subject: [PATCH 1673/2266] Implement Discovery KV Store --- core/discovery/kv/store.go | 142 ++++++++++++++++++++++++++++++++ core/discovery/kv/store_test.go | 75 +++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 core/discovery/kv/store.go create mode 100644 core/discovery/kv/store_test.go diff --git a/core/discovery/kv/store.go b/core/discovery/kv/store.go new file mode 100644 index 000000000..221a96322 --- /dev/null +++ b/core/discovery/kv/store.go @@ -0,0 +1,142 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package kv + +import ( + "errors" + "fmt" + + "gopkg.in/redis.v3" +) + +var ( + // ErrNotFound is returned when an Key was not found + ErrNotFound = errors.New("ttn/discovery: Key not found") +) + +// Store is a simple String/String Key-Value store +type Store interface { + // List all items in the store + List() (map[string]string, error) + // Get the full HandlerID for the AppID + Get(key string) (value string, err error) + // Set the mapping. + Set(key, value string) error + // Delete a mapping + Delete(key string) error +} + +// NewKVStore creates a new in-memory Key-Value store +func NewKVStore() Store { + return &kvStore{ + data: make(map[string]string), + } +} + +// kvStore is an in-memory Key-Value store. It should only be used for testing +// purposes. Use the redisKVStore for actual deployments. +type kvStore struct { + data map[string]string +} + +func (s *kvStore) List() (map[string]string, error) { + return s.data, nil +} + +func (s *kvStore) Get(key string) (string, error) { + if value, ok := s.data[key]; ok { + return value, nil + } + return "", ErrNotFound +} + +func (s *kvStore) Set(key, value string) error { + s.data[key] = value + return nil +} + +func (s *kvStore) Delete(key string) error { + delete(s.data, key) + return nil +} + +const redisKVPrefix = "discovery:" + +// NewRedisKVStore creates a new Redis-based Key-Value store +func NewRedisKVStore(client *redis.Client, prefix string) Store { + return &redisKVStore{ + client: client, + prefix: redisKVPrefix + prefix, + } +} + +type redisKVStore struct { + prefix string + client *redis.Client +} + +func (s *redisKVStore) getForKeys(keys []string) (map[string]string, error) { + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringCmd) + for _, key := range keys { + cmds[key] = s.client.Get(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + data := make(map[string]string) + for key, cmd := range cmds { + res, err := cmd.Result() + if err == nil { + data[key] = res + } + } + + return data, nil +} + +func (s *redisKVStore) List() (map[string]string, error) { + keys, err := s.client.Keys(fmt.Sprintf("%s:*", s.prefix)).Result() + if err != nil { + return nil, err + } + return s.getForKeys(keys) +} + +func (s *redisKVStore) Get(key string) (string, error) { + res, err := s.client.Get(fmt.Sprintf("%s:%s", s.prefix, key)).Result() + if err != nil { + if err == redis.Nil { + return "", ErrNotFound + } + return "", err + } else if res == "" { + return "", ErrNotFound + } + return res, nil +} + +func (s *redisKVStore) Set(key, value string) error { + err := s.client.Set(fmt.Sprintf("%s:%s", s.prefix, key), value, 0).Err() + if err != nil { + return err + } + return nil +} + +func (s *redisKVStore) Delete(key string) error { + err := s.client.Del(fmt.Sprintf("%s:%s", s.prefix, key)).Err() + if err != nil { + return err + } + return nil +} diff --git a/core/discovery/kv/store_test.go b/core/discovery/kv/store_test.go new file mode 100644 index 000000000..8d1a2a504 --- /dev/null +++ b/core/discovery/kv/store_test.go @@ -0,0 +1,75 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package kv + +import ( + "fmt" + "os" + "testing" + + . "github.com/smartystreets/assertions" + "gopkg.in/redis.v3" +) + +func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: 1, // use default DB + }) +} + +func TestAnnouncementStore(t *testing.T) { + a := New(t) + + stores := map[string]Store{ + "local": NewKVStore(), + "redis": NewRedisKVStore(getRedisClient(), "test"), + } + + for name, s := range stores { + + t.Logf("Testing %s store", name) + + // Get non-existing + res, err := s.Get("some-key") + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeEmpty) + + // Create + err = s.Set("some-key", "some-value") + a.So(err, ShouldBeNil) + + // Get existing + res, err = s.Get("some-key") + a.So(err, ShouldBeNil) + a.So(res, ShouldEqual, "some-value") + + // Create extra + err = s.Set("other-key", "other-value") + a.So(err, ShouldBeNil) + + // List + resps, err := s.List() + a.So(err, ShouldBeNil) + a.So(resps, ShouldHaveLength, 2) + + // Delete + err = s.Delete("other-key") + a.So(err, ShouldBeNil) + + // Get deleted + res, err = s.Get("other-key") + a.So(err, ShouldNotBeNil) + a.So(res, ShouldBeEmpty) + + // Cleanup + s.Delete("some-key") + } + +} From adf370ab807d4343ab4305534d768d1812c48121 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 8 Aug 2016 12:15:48 +0200 Subject: [PATCH 1674/2266] Delete old appID announcement when adding new one --- core/discovery/discovery.go | 31 ++++++++++++++++++++++++++++++- core/discovery/discovery_test.go | 28 ++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 331483104..cadc067c8 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -12,6 +12,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" + "github.com/TheThingsNetwork/ttn/core/discovery/kv" ) // Discovery specifies the interface for the TTN Service Discovery component @@ -28,6 +29,7 @@ type Discovery interface { type discovery struct { *core.Component services announcement.Store + appIDs kv.Store } func (d *discovery) Init(c *core.Component) error { @@ -85,8 +87,33 @@ func (d *discovery) AddMetadata(serviceName string, id string, in *pb.Metadata) return nil } } + + // Pre-update + switch in.Key { + case pb.Metadata_APP_ID: + existingHandler, err := d.appIDs.Get(string(in.Value)) + if err == nil { + d.DeleteMetadata("handler", existingHandler, in) + } + } + + // Update existing.Metadata = append(existing.Metadata, in) - return d.services.Set(existing) + err = d.services.Set(existing) + if err != nil { + return err + } + + // Post-update + switch in.Key { + case pb.Metadata_APP_ID: + err := d.appIDs.Set(string(in.Value), id) + if err != nil { + return err + } + } + + return nil } func (d *discovery) DeleteMetadata(serviceName string, id string, in *pb.Metadata) error { @@ -109,6 +136,7 @@ func (d *discovery) DeleteMetadata(serviceName string, id string, in *pb.Metadat func NewDiscovery(client *redis.Client) Discovery { return &discovery{ services: announcement.NewAnnouncementStore(), + appIDs: kv.NewKVStore(), } } @@ -116,5 +144,6 @@ func NewDiscovery(client *redis.Client) Discovery { func NewRedisDiscovery(client *redis.Client) Discovery { return &discovery{ services: announcement.NewRedisAnnouncementStore(client), + appIDs: kv.NewRedisKVStore(client, "app-id"), } } diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 66f63f672..8ef721c37 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -12,6 +12,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" + "github.com/TheThingsNetwork/ttn/core/discovery/kv" . "github.com/smartystreets/assertions" ) @@ -32,6 +33,7 @@ func TestDiscoveryAnnounce(t *testing.T) { localDiscovery := &discovery{ services: announcement.NewAnnouncementStore(), + appIDs: kv.NewKVStore(), } client := getRedisClient(1) @@ -86,7 +88,11 @@ func TestDiscoveryDiscover(t *testing.T) { broker1 := &pb.Announcement{ServiceName: "broker", Id: "broker2.1"} broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker2.2"} - localDiscovery := &discovery{services: announcement.NewAnnouncementStore()} + localDiscovery := &discovery{ + services: announcement.NewAnnouncementStore(), + appIDs: kv.NewKVStore(), + } + localDiscovery.services.Set(router) localDiscovery.services.Set(broker1) localDiscovery.services.Set(broker2) @@ -133,12 +139,15 @@ func TestDiscoveryMetadata(t *testing.T) { localDiscovery := &discovery{ services: announcement.NewAnnouncementStore(), + appIDs: kv.NewKVStore(), } client := getRedisClient(1) redisDiscovery := NewRedisDiscovery(client) defer func() { client.Del("discovery:announcement:broker:broker3") + client.Del("discovery:announcement:broker:broker4") + client.Del("discovery:app-id:app-id-2") }() discoveries := map[string]Discovery{ @@ -147,20 +156,26 @@ func TestDiscoveryMetadata(t *testing.T) { } for name, d := range discoveries { - broker := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ + broker3 := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ Key: pb.Metadata_APP_ID, Value: []byte("app-id-1"), }}} + broker4 := &pb.Announcement{ServiceName: "broker", Id: "broker4", Metadata: []*pb.Metadata{&pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }}} t.Logf("Testing %s\n", name) // Announce should not change metadata - err := d.Announce(broker) + err := d.Announce(broker3) a.So(err, ShouldBeNil) service, err := d.Get("broker", "broker3") a.So(err, ShouldBeNil) a.So(service.Metadata, ShouldHaveLength, 0) + d.Announce(broker4) + // AddMetadata should add one err = d.AddMetadata("broker", "broker3", &pb.Metadata{ Key: pb.Metadata_APP_ID, @@ -171,6 +186,11 @@ func TestDiscoveryMetadata(t *testing.T) { a.So(err, ShouldBeNil) a.So(service.Metadata, ShouldHaveLength, 1) + // And should remove it from the other broker + service, err = d.Get("broker", "broker4") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + // AddMetadata again should not add one err = d.AddMetadata("broker", "broker3", &pb.Metadata{ Key: pb.Metadata_APP_ID, @@ -191,7 +211,7 @@ func TestDiscoveryMetadata(t *testing.T) { a.So(service.Metadata, ShouldHaveLength, 1) // Announce should not change metadata - err = d.Announce(broker) + err = d.Announce(broker3) a.So(err, ShouldBeNil) service, err = d.Get("broker", "broker3") a.So(err, ShouldBeNil) From 5f6f0b820fb3f3b83ef0bfd5009c0f83230a98a7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 8 Aug 2016 17:20:32 +0200 Subject: [PATCH 1675/2266] Supply additional names/IPs to genkeys cmd --- cmd/genkeys.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/genkeys.go b/cmd/genkeys.go index 3b998f04e..0f717485d 100644 --- a/cmd/genkeys.go +++ b/cmd/genkeys.go @@ -16,7 +16,10 @@ func genkeysCmd(component string) *cobra.Command { Short: "Generate keys and certificate", Long: `ttn genkeys generates keys and a TLS certificate for this component`, Run: func(cmd *cobra.Command, args []string) { - err := security.GenerateKeys(viper.GetString("key-dir"), viper.GetString(component+".server-address-announce")) + var names []string + names = append(names, viper.GetString(component+".server-address-announce")) + names = append(names, args...) + err := security.GenerateKeys(viper.GetString("key-dir"), names...) if err != nil { ctx.WithError(err).Fatal("Could not generate keys") } From 963541484c15add0df63db754e795ade1a368498 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 11 Aug 2016 11:15:53 +0100 Subject: [PATCH 1676/2266] log in using client code instead of username and password --- ttnctl/cmd/user_login.go | 17 ++++------------- ttnctl/util/account.go | 4 ++-- 2 files changed, 6 insertions(+), 15 deletions(-) diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index 61eb688f4..2b098642f 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -4,31 +4,22 @@ package cmd import ( - "fmt" - "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/howeyc/gopass" "github.com/spf13/cobra" ) var userLoginCmd = &cobra.Command{ - Use: "login [e-mail]", + Use: "login [client code]", Short: "Login", - Long: `ttnctl user login allows you to login to the account server`, + Long: `ttnctl user login allows you to login to the account server.`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { cmd.UsageFunc()(cmd) return } - email := args[0] - fmt.Print("Password: ") - password, err := gopass.GetPasswd() - if err != nil { - ctx.Fatal(err.Error()) - } - - util.Login(ctx, email, string(password)) + code := args[0] + util.Login(ctx, code) }, } diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 2e263e2ae..921ea5d9d 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -115,9 +115,9 @@ func GetAccount(ctx log.Interface) *account.Account { } // Login does a login to the Account server with the given username and password -func Login(ctx log.Interface, username, password string) { +func Login(ctx log.Interface, code string) { config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") - token, err := config.PasswordCredentialsToken(context.Background(), username, password) + token, err := config.Exchange(context.Background(), code) if err != nil { ctx.WithError(err).Fatal("Login failed") } From 7d8afca6f434312f0cbc4c5967b71b29c5f589f4 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 11 Aug 2016 11:18:34 +0100 Subject: [PATCH 1677/2266] update docs to reflect client code --- ttnctl/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ttnctl/README.md b/ttnctl/README.md index 4377fc3fe..e2399fbaa 100644 --- a/ttnctl/README.md +++ b/ttnctl/README.md @@ -33,7 +33,9 @@ The arguments and flags for each command are shown when executing a command with * Create an account: `ttnctl user register [username] [e-mail]` * Note: You might have to verify your email before you can login. -* Login: `ttnctl user login [e-mail]` +* Get a client access code on the account server by clicking *ttnctl access + code* on the home page. +* Login with the client code you received `ttnctl user login [client code]` * List your applications: `ttnctl applications list` * Create a new application: `ttnctl applications create [AppID] [Description]` * Select the application you want to use from now on: `ttnctl applications select` From f82a8c09a0874db12326639336a812196871cd38 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 11 Aug 2016 11:47:01 +0100 Subject: [PATCH 1678/2266] show username and email on login --- ttnctl/cmd/user_login.go | 15 ++++++++++++++- ttnctl/util/account.go | 6 +++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index 2b098642f..a0420dd6c 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -4,6 +4,8 @@ package cmd import ( + "fmt" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) @@ -19,7 +21,18 @@ var userLoginCmd = &cobra.Command{ } code := args[0] - util.Login(ctx, code) + err := util.Login(ctx, code) + if err != nil { + ctx.WithError(err).Fatal("Login failed") + } + + account := util.GetAccount(ctx) + profile, err := account.Profile() + if err != nil { + ctx.WithError(err).Fatal("Could not get user profile") + } + + ctx.Info(fmt.Sprintf("Successfully logged in as %s (%s)", profile.Username, profile.Email)) }, } diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 921ea5d9d..beccb3c8f 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -115,12 +115,12 @@ func GetAccount(ctx log.Interface) *account.Account { } // Login does a login to the Account server with the given username and password -func Login(ctx log.Interface, code string) { +func Login(ctx log.Interface, code string) error { config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") token, err := config.Exchange(context.Background(), code) if err != nil { - ctx.WithError(err).Fatal("Login failed") + return err } saveToken(ctx, token) - ctx.Info("Login successful") + return nil } From 0a58429e7a423ee32df0975bca15a9ddf6c11e52 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 12 Aug 2016 10:24:36 +0100 Subject: [PATCH 1679/2266] do not use hardcoded (wrong) values in MakeConfig --- core/account/util/oauth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/account/util/oauth.go b/core/account/util/oauth.go index 28767ec39..fec35aede 100644 --- a/core/account/util/oauth.go +++ b/core/account/util/oauth.go @@ -18,8 +18,8 @@ func MakeConfig(server string, clientID string, clientSecret string, redirectURL } return oauth2.Config{ - ClientID: "ttnctl", - ClientSecret: "", + ClientID: clientID, + ClientSecret: clientSecret, Endpoint: endpoint, RedirectURL: redirectURL, } From ef05a7827505abce8b060fa908d250a8b90f504f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 15 Aug 2016 09:55:00 +0100 Subject: [PATCH 1680/2266] fix bug where logging in with ttnctl would always give error --- ttnctl/cmd/user_login.go | 9 +++++---- ttnctl/util/account.go | 6 +++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index a0420dd6c..b96517dba 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -6,8 +6,10 @@ package cmd import ( "fmt" + "github.com/TheThingsNetwork/ttn/core/account" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var userLoginCmd = &cobra.Command{ @@ -21,17 +23,16 @@ var userLoginCmd = &cobra.Command{ } code := args[0] - err := util.Login(ctx, code) + token, err := util.Login(ctx, code) if err != nil { ctx.WithError(err).Fatal("Login failed") } - account := util.GetAccount(ctx) - profile, err := account.Profile() + acc := account.New(viper.GetString("ttn-account-server"), token.AccessToken) + profile, err := acc.Profile() if err != nil { ctx.WithError(err).Fatal("Could not get user profile") } - ctx.Info(fmt.Sprintf("Successfully logged in as %s (%s)", profile.Username, profile.Email)) }, } diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index beccb3c8f..ddcd4630b 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -115,12 +115,12 @@ func GetAccount(ctx log.Interface) *account.Account { } // Login does a login to the Account server with the given username and password -func Login(ctx log.Interface, code string) error { +func Login(ctx log.Interface, code string) (*oauth2.Token, error) { config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") token, err := config.Exchange(context.Background(), code) if err != nil { - return err + return nil, err } saveToken(ctx, token) - return nil + return token, nil } From d004bac8365cf30ed51155134862c723ddc6e4b6 Mon Sep 17 00:00:00 2001 From: Johan Stokking Date: Mon, 15 Aug 2016 15:43:56 +0100 Subject: [PATCH 1681/2266] Fix for reading configuration file by config parameter (#219) * Fix for reading configuration file by config parameter * Print correct config file used --- cmd/root.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 6f03b3a08..48cb6d323 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -138,20 +138,24 @@ func init() { // initConfig reads in config file and ENV variables if set. func initConfig() { - if cfgFile != "" { // enable ability to specify config file via flag - viper.SetConfigFile(cfgFile) - } - + viper.SetConfigType("yaml") viper.SetConfigName(".ttn") // name of config file (without extension) viper.AddConfigPath("$HOME") // adding home directory as first search path viper.SetEnvPrefix("ttn") // set environment prefix viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() // read in environment variables that match + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + viper.BindEnv("debug") // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Error when reading config file:", err) + } else if err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } From c9aac0a4f09de669336c385f1b71dc3ce9360825 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 15 Aug 2016 16:55:20 +0100 Subject: [PATCH 1682/2266] Fix for reading configuration file by config parameter in ttnctl (#220) --- ttnctl/cmd/root.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 7b87deee1..5b83b4d98 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -97,18 +97,22 @@ func confirm(prompt string) bool { // initConfig reads in config file and ENV variables if set. func initConfig() { - if cfgFile != "" { - viper.SetConfigFile(cfgFile) - } - + viper.SetConfigType("yaml") viper.SetConfigName(".ttnctl") viper.AddConfigPath("$HOME") viper.SetEnvPrefix("ttnctl") // set environment prefix viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() + if cfgFile != "" { // enable ability to specify config file via flag + viper.SetConfigFile(cfgFile) + } + // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil && viper.GetBool("debug") { + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Error when reading config file:", err) + } else if err == nil && viper.GetBool("debug") { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } From 918890086f6c41512ae4f5310e24fc871d20a258 Mon Sep 17 00:00:00 2001 From: Antoine Rondelet Date: Wed, 17 Aug 2016 17:37:56 +0100 Subject: [PATCH 1683/2266] Update protos for encoder (#224) --- api/api.pb.go | 6 +- api/broker/broker.pb.go | 8 +- api/discovery/discovery.pb.go | 8 +- api/gateway/gateway.pb.go | 8 +- api/handler/handler.pb.go | 132 +++++++++++++++++--------- api/handler/handler.proto | 1 + api/networkserver/networkserver.pb.go | 8 +- api/noc/noc.pb.go | 8 +- api/protocol/lorawan/device.pb.go | 8 +- api/protocol/lorawan/lorawan.pb.go | 4 + api/protocol/protocol.pb.go | 8 +- api/router/router.pb.go | 8 +- 12 files changed, 155 insertions(+), 52 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index 1bcd89580..2cb077199 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -29,7 +29,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // message Ack is used to acknowledge a request, without giving a response. type Ack struct { @@ -787,6 +789,8 @@ var ( ErrIntOverflowApi = fmt.Errorf("proto: integer overflow") ) +func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } + var fileDescriptorApi = []byte{ // 263 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 5b2979a56..22268f25d 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -47,7 +47,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DownlinkOption struct { Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` @@ -4149,6 +4151,10 @@ var ( ErrIntOverflowBroker = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/broker/broker.proto", fileDescriptorBroker) +} + var fileDescriptorBroker = []byte{ // 1086 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xcd, 0x6f, 0x1b, 0x45, diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 71b526236..d436bc388 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -37,7 +37,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type Metadata_Key int32 @@ -1674,6 +1676,10 @@ var ( ErrIntOverflowDiscovery = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto", fileDescriptorDiscovery) +} + var fileDescriptorDiscovery = []byte{ // 550 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x5d, 0x8f, 0xd2, 0x40, diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 1b7c7e4e7..627689e93 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -32,7 +32,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type GPSMetadata struct { Time int64 `protobuf:"varint,1,opt,name=time,proto3" json:"time,omitempty"` @@ -1575,6 +1577,10 @@ var ( ErrIntOverflowGateway = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto", fileDescriptorGateway) +} + var fileDescriptorGateway = []byte{ // 607 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x54, 0xcd, 0x6e, 0x13, 0x3d, diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 2f7642b69..8dbe8d9f9 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -42,7 +42,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DeviceActivationResponse struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` @@ -102,6 +104,7 @@ type Application struct { Decoder string `protobuf:"bytes,2,opt,name=decoder,proto3" json:"decoder,omitempty"` Converter string `protobuf:"bytes,3,opt,name=converter,proto3" json:"converter,omitempty"` Validator string `protobuf:"bytes,4,opt,name=validator,proto3" json:"validator,omitempty"` + Encoder string `protobuf:"bytes,5,opt,name=encoder,proto3" json:"encoder,omitempty"` } func (m *Application) Reset() { *m = Application{} } @@ -822,6 +825,12 @@ func (m *Application) MarshalTo(data []byte) (int, error) { i = encodeVarintHandler(data, i, uint64(len(m.Validator))) i += copy(data[i:], m.Validator) } + if len(m.Encoder) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Encoder))) + i += copy(data[i:], m.Encoder) + } return i, nil } @@ -1026,6 +1035,10 @@ func (m *Application) Size() (n int) { if l > 0 { n += 1 + l + sovHandler(uint64(l)) } + l = len(m.Encoder) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } return n } @@ -1594,6 +1607,35 @@ func (m *Application) Unmarshal(data []byte) error { } m.Validator = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Encoder", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Encoder = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -2049,47 +2091,51 @@ var ( ErrIntOverflowHandler = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/handler/handler.proto", fileDescriptorHandler) +} + var fileDescriptorHandler = []byte{ - // 642 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x41, 0x4f, 0x13, 0x41, - 0x14, 0xa6, 0xa2, 0xa5, 0x7d, 0x85, 0x82, 0x83, 0xe0, 0xda, 0x10, 0xa2, 0x7b, 0x30, 0x1a, 0xe3, - 0xd6, 0x14, 0x13, 0x34, 0xc6, 0x60, 0x09, 0x41, 0x48, 0x44, 0x92, 0x85, 0x93, 0x97, 0x66, 0xd8, - 0x79, 0xb6, 0x93, 0x2e, 0x3b, 0xeb, 0xee, 0xb4, 0x44, 0x7f, 0x89, 0x3f, 0xc9, 0xa3, 0x77, 0x2f, - 0x46, 0x7f, 0x83, 0x77, 0x67, 0x67, 0xa6, 0xcb, 0x52, 0x30, 0xb4, 0x1e, 0x26, 0xdd, 0xf7, 0xbe, - 0xf7, 0xbd, 0xf9, 0x66, 0xbe, 0xd7, 0x81, 0x97, 0x5d, 0x2e, 0x7b, 0x83, 0x13, 0x2f, 0x10, 0xa7, - 0xcd, 0xe3, 0x1e, 0x1e, 0xf7, 0x78, 0xd4, 0x4d, 0xdf, 0xa3, 0x3c, 0x13, 0x49, 0xbf, 0x29, 0x65, - 0xd4, 0xa4, 0x31, 0x6f, 0xf6, 0x68, 0xc4, 0x42, 0x4c, 0x46, 0xbf, 0x5e, 0x9c, 0x08, 0x29, 0xc8, - 0x9c, 0x0d, 0x1b, 0x4f, 0x27, 0xe9, 0xa1, 0x96, 0xe1, 0x35, 0x36, 0x27, 0x29, 0x3f, 0x49, 0x44, - 0x5f, 0xed, 0x68, 0x7e, 0x2c, 0xf1, 0xd5, 0x24, 0x44, 0x5d, 0x1a, 0x88, 0x30, 0xff, 0xb0, 0xe4, - 0xf6, 0x54, 0xe4, 0x50, 0x24, 0xf4, 0x8c, 0x46, 0x4d, 0x86, 0x43, 0x1e, 0xa0, 0x69, 0xe1, 0xfe, - 0x28, 0x81, 0xb3, 0xa3, 0x13, 0xed, 0x40, 0xf2, 0x21, 0x95, 0x5c, 0x44, 0x3e, 0xa6, 0xb1, 0x88, - 0x52, 0x24, 0x0e, 0xcc, 0xc5, 0xf4, 0x73, 0x28, 0x28, 0x73, 0x4a, 0xf7, 0x4b, 0x8f, 0xe6, 0xfd, - 0x51, 0x48, 0x56, 0xa0, 0x4c, 0xe3, 0xb8, 0xc3, 0x99, 0x73, 0x43, 0x01, 0x55, 0xff, 0x96, 0x8a, - 0xf6, 0x19, 0xd9, 0x82, 0x45, 0x26, 0xce, 0xa2, 0x90, 0x47, 0xfd, 0x8e, 0x88, 0xb3, 0x5e, 0x4e, - 0x4d, 0xe1, 0xb5, 0xd6, 0xaa, 0x67, 0x4f, 0xbd, 0x63, 0xe1, 0x43, 0x8d, 0xfa, 0x75, 0x76, 0x21, - 0x26, 0x07, 0xb0, 0x4c, 0x73, 0x1d, 0x9d, 0x53, 0x94, 0x94, 0x51, 0x49, 0x9d, 0xbb, 0xba, 0xc9, - 0x9a, 0x97, 0x9f, 0xff, 0x5c, 0xec, 0x81, 0xad, 0xf1, 0x09, 0xbd, 0x94, 0x73, 0x17, 0x61, 0xe1, - 0x48, 0x52, 0x39, 0x48, 0x7d, 0xfc, 0x34, 0xc0, 0x54, 0xba, 0x15, 0x28, 0x9b, 0x84, 0xeb, 0xc1, - 0x4a, 0x3b, 0x8e, 0x43, 0x1e, 0x68, 0xc6, 0x3e, 0xc3, 0x48, 0xf2, 0x8f, 0x1c, 0x93, 0xc2, 0xd1, - 0x4a, 0x85, 0xa3, 0xb9, 0x5f, 0xa0, 0x56, 0xa8, 0xff, 0x47, 0x55, 0x76, 0x63, 0x0c, 0x03, 0xc1, - 0x30, 0xb1, 0x17, 0x33, 0x0a, 0xc9, 0x1a, 0x54, 0x03, 0x11, 0x0d, 0x31, 0x91, 0x0a, 0x9b, 0xd5, - 0xd8, 0x79, 0x22, 0x43, 0x87, 0x34, 0xe4, 0x4a, 0xb4, 0x48, 0x9c, 0x9b, 0x06, 0xcd, 0x13, 0xee, - 0x1b, 0x58, 0x32, 0x1e, 0x5d, 0x2b, 0x33, 0x4b, 0x2b, 0x7f, 0x0b, 0xc6, 0xa8, 0x48, 0xab, 0x2f, - 0x9b, 0x0e, 0xd3, 0xf1, 0xc8, 0x0b, 0xa8, 0xdb, 0xb1, 0xe9, 0x98, 0xb1, 0xd1, 0xd2, 0x6b, 0xad, - 0x45, 0xcf, 0xa6, 0x3d, 0xd3, 0x76, 0x6f, 0xc6, 0x5f, 0xb0, 0x19, 0x93, 0xd8, 0xae, 0xe8, 0x86, - 0xea, 0xcb, 0xdd, 0x04, 0x30, 0xb9, 0x77, 0x3c, 0x95, 0xe4, 0x71, 0x76, 0x43, 0x59, 0x94, 0x2a, - 0x01, 0xb3, 0xba, 0xd5, 0xe8, 0x2f, 0x68, 0xaa, 0xfc, 0x11, 0xde, 0x42, 0x98, 0xdb, 0x33, 0x10, - 0xf9, 0x00, 0x15, 0x6b, 0x39, 0x92, 0x27, 0xf9, 0x2c, 0x21, 0x1b, 0x18, 0x47, 0x90, 0x5d, 0x9e, - 0x61, 0x6d, 0x78, 0xe3, 0xc1, 0x58, 0xf7, 0xcb, 0x53, 0xde, 0xfa, 0x33, 0x0b, 0xa4, 0x60, 0xed, - 0x01, 0x8d, 0x68, 0x57, 0x6d, 0xb9, 0x05, 0xcb, 0x3e, 0x76, 0x95, 0x64, 0x4c, 0x8a, 0xc6, 0xaf, - 0xe7, 0x0d, 0xaf, 0x1c, 0x9f, 0x46, 0xc5, 0xcb, 0x5e, 0x85, 0x76, 0xd0, 0x27, 0xbb, 0x50, 0x7f, - 0x8b, 0x72, 0x1a, 0xee, 0x9d, 0xab, 0x70, 0xf2, 0x0c, 0xea, 0x47, 0x17, 0xfb, 0x5c, 0x59, 0x57, - 0xd8, 0xf9, 0x35, 0xdc, 0xde, 0xc1, 0x10, 0x25, 0xfe, 0x9f, 0xf0, 0x4d, 0xa8, 0x2a, 0xe1, 0x76, - 0x5e, 0xee, 0x8d, 0x5d, 0x60, 0x81, 0x31, 0xee, 0x1c, 0x79, 0x08, 0xd5, 0xa3, 0x9c, 0x38, 0x8e, - 0x16, 0x36, 0xd8, 0x80, 0x79, 0xa3, 0xef, 0xfa, 0x3d, 0xce, 0x49, 0x87, 0xe0, 0xe4, 0xaa, 0xd2, - 0x5d, 0x31, 0x95, 0x29, 0xcb, 0x63, 0x1b, 0x64, 0x93, 0xd8, 0x52, 0xfe, 0xd8, 0xf1, 0x1a, 0x59, - 0xfe, 0x5c, 0x1f, 0xdc, 0x3c, 0x10, 0x64, 0x35, 0xe7, 0x5c, 0x78, 0x42, 0x0a, 0xa7, 0x36, 0xf9, - 0xed, 0xa5, 0x6f, 0xbf, 0xd6, 0x4b, 0xdf, 0xd5, 0xfa, 0xa9, 0xd6, 0xd7, 0xdf, 0xeb, 0x33, 0x27, - 0x65, 0xfd, 0x4e, 0x6d, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xf5, 0x5f, 0x1e, 0xbd, 0x89, 0x06, - 0x00, 0x00, + // 655 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x51, 0x4f, 0x13, 0x41, + 0x10, 0xa6, 0x22, 0xa5, 0x9d, 0x42, 0xc1, 0x45, 0xf0, 0x6c, 0x08, 0xd1, 0x7b, 0x30, 0x1a, 0xe3, + 0xd5, 0x14, 0x13, 0x34, 0xc6, 0x60, 0x09, 0x41, 0x48, 0x44, 0x92, 0x83, 0x27, 0x5f, 0x9a, 0xe5, + 0x76, 0x6c, 0x37, 0x3d, 0x6e, 0xcf, 0xbb, 0x6d, 0x89, 0xfe, 0x0e, 0x1f, 0xfc, 0x49, 0x3e, 0xfa, + 0xee, 0x8b, 0xd1, 0xdf, 0xe0, 0xbb, 0x7b, 0xbb, 0xdb, 0xe3, 0x28, 0x18, 0xa8, 0x0f, 0x97, 0xbb, + 0x99, 0x6f, 0xbe, 0x6f, 0x66, 0x76, 0xe6, 0x16, 0x5e, 0x74, 0xb9, 0xec, 0x0d, 0x8e, 0xbd, 0x40, + 0x9c, 0x34, 0x8f, 0x7a, 0x78, 0xd4, 0xe3, 0x51, 0x37, 0x7d, 0x87, 0xf2, 0x54, 0x24, 0xfd, 0xa6, + 0x94, 0x51, 0x93, 0xc6, 0xbc, 0xd9, 0xa3, 0x11, 0x0b, 0x31, 0x19, 0xbd, 0xbd, 0x38, 0x11, 0x52, + 0x90, 0x59, 0x6b, 0x36, 0x9e, 0x5c, 0x47, 0x43, 0x3d, 0x86, 0xd7, 0xd8, 0xb8, 0x4e, 0xf8, 0x71, + 0x22, 0xfa, 0x2a, 0xa3, 0x79, 0x59, 0xe2, 0xcb, 0xeb, 0x10, 0x75, 0x68, 0x20, 0xc2, 0xfc, 0xc3, + 0x92, 0xdb, 0x13, 0x91, 0x43, 0x91, 0xd0, 0x53, 0x1a, 0x35, 0x19, 0x0e, 0x79, 0x80, 0x46, 0xc2, + 0xfd, 0x51, 0x02, 0x67, 0x5b, 0x3b, 0xda, 0x81, 0xe4, 0x43, 0x2a, 0xb9, 0x88, 0x7c, 0x4c, 0x63, + 0x11, 0xa5, 0x48, 0x1c, 0x98, 0x8d, 0xe9, 0xa7, 0x50, 0x50, 0xe6, 0x94, 0xee, 0x95, 0x1e, 0xce, + 0xf9, 0x23, 0x93, 0x2c, 0x43, 0x99, 0xc6, 0x71, 0x87, 0x33, 0xe7, 0x86, 0x02, 0xaa, 0xfe, 0x8c, + 0xb2, 0xf6, 0x18, 0xd9, 0x84, 0x05, 0x26, 0x4e, 0xa3, 0x90, 0x47, 0xfd, 0x8e, 0x88, 0x33, 0x2d, + 0xa7, 0xa6, 0xf0, 0x5a, 0x6b, 0xc5, 0xb3, 0x5d, 0x6f, 0x5b, 0xf8, 0x40, 0xa3, 0x7e, 0x9d, 0x9d, + 0xb3, 0xc9, 0x3e, 0x2c, 0xd1, 0xbc, 0x8e, 0xce, 0x09, 0x4a, 0xca, 0xa8, 0xa4, 0xce, 0x1d, 0x2d, + 0xb2, 0xea, 0xe5, 0xfd, 0x9f, 0x15, 0xbb, 0x6f, 0x63, 0x7c, 0x42, 0x2f, 0xf8, 0xdc, 0x05, 0x98, + 0x3f, 0x94, 0x54, 0x0e, 0x52, 0x1f, 0x3f, 0x0e, 0x30, 0x95, 0x6e, 0x05, 0xca, 0xc6, 0xe1, 0x7a, + 0xb0, 0xdc, 0x8e, 0xe3, 0x90, 0x07, 0x9a, 0xb1, 0xc7, 0x30, 0x92, 0xfc, 0x03, 0xc7, 0xa4, 0xd0, + 0x5a, 0xa9, 0xd0, 0x9a, 0xfb, 0xa5, 0x04, 0xb5, 0x02, 0xe1, 0x1f, 0x61, 0xd9, 0x91, 0x31, 0x0c, + 0x04, 0xc3, 0xc4, 0x9e, 0xcc, 0xc8, 0x24, 0xab, 0x50, 0x0d, 0x44, 0x34, 0xc4, 0x44, 0x2a, 0x6c, + 0x5a, 0x63, 0x67, 0x8e, 0x0c, 0x1d, 0xd2, 0x90, 0xab, 0xaa, 0x45, 0xe2, 0xdc, 0x34, 0x68, 0xee, + 0xc8, 0x54, 0x31, 0x32, 0xaa, 0x33, 0x46, 0xd5, 0x9a, 0xee, 0x6b, 0x58, 0x34, 0xe3, 0xbb, 0xb2, + 0x83, 0xcc, 0xad, 0x46, 0x5f, 0x98, 0x99, 0xb2, 0x54, 0x63, 0x9f, 0xa1, 0x6c, 0x14, 0x26, 0xe3, + 0x91, 0xe7, 0x50, 0xb7, 0x1b, 0xd5, 0x31, 0x1b, 0xa5, 0x9b, 0xaa, 0xb5, 0x16, 0x3c, 0xeb, 0xf6, + 0x8c, 0xec, 0xee, 0x94, 0x3f, 0x6f, 0x3d, 0xc6, 0xb1, 0x55, 0xd1, 0x82, 0xea, 0xcb, 0xdd, 0x00, + 0x30, 0xbe, 0xb7, 0x3c, 0x95, 0xe4, 0x51, 0x76, 0x76, 0x99, 0x95, 0xaa, 0x02, 0xa6, 0xb5, 0xd4, + 0xe8, 0xef, 0x34, 0x51, 0xfe, 0x08, 0x6f, 0x21, 0xcc, 0xee, 0x1a, 0x88, 0xbc, 0x87, 0x8a, 0xdd, + 0x06, 0x24, 0x8f, 0xf3, 0x35, 0x43, 0x36, 0x30, 0xb3, 0x42, 0x76, 0x71, 0xbd, 0xf5, 0x2e, 0x34, + 0xee, 0x8f, 0xa9, 0x5f, 0xfc, 0x01, 0x5a, 0x7f, 0xa6, 0x81, 0x14, 0x86, 0xbe, 0x4f, 0x23, 0xda, + 0x55, 0x29, 0x37, 0x61, 0xc9, 0xc7, 0xae, 0x2a, 0x19, 0x93, 0xe2, 0x4a, 0xac, 0xe5, 0x82, 0x97, + 0x6e, 0x56, 0xa3, 0xe2, 0x65, 0x17, 0x46, 0x3b, 0xe8, 0x93, 0x1d, 0xa8, 0xbf, 0x41, 0x39, 0x09, + 0xf7, 0xf6, 0x65, 0x38, 0x79, 0x0a, 0xf5, 0xc3, 0xf3, 0x3a, 0x97, 0xc6, 0x15, 0x32, 0xbf, 0x82, + 0x5b, 0xdb, 0x18, 0xa2, 0xc4, 0xff, 0x2b, 0x7c, 0x03, 0xaa, 0xaa, 0x70, 0xbb, 0x2f, 0x77, 0xc7, + 0x0e, 0xb0, 0xc0, 0x18, 0x9f, 0x1c, 0x79, 0x00, 0xd5, 0xc3, 0x9c, 0x38, 0x8e, 0x16, 0x12, 0xac, + 0xc3, 0x9c, 0xa9, 0xef, 0xea, 0x1c, 0x67, 0xa4, 0x03, 0x70, 0xf2, 0xaa, 0xd2, 0x1d, 0x31, 0xd1, + 0x50, 0x96, 0xc6, 0x12, 0x64, 0x9b, 0xd8, 0x52, 0xf3, 0xb1, 0xeb, 0x35, 0x1a, 0xf9, 0x33, 0xdd, + 0xb8, 0xb9, 0x3b, 0xc8, 0x4a, 0xce, 0x39, 0x77, 0xbb, 0x14, 0xba, 0x36, 0xfe, 0xad, 0xc5, 0x6f, + 0xbf, 0xd6, 0x4a, 0xdf, 0xd5, 0xf3, 0x53, 0x3d, 0x5f, 0x7f, 0xaf, 0x4d, 0x1d, 0x97, 0xf5, 0x15, + 0xb6, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x57, 0xba, 0xac, 0xa4, 0x06, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 0777c8581..fbf70f36a 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -37,6 +37,7 @@ message Application { string decoder = 2; string converter = 3; string validator = 4; + string encoder = 5; } message DeviceIdentifier { diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 3a31eb2cb..2b7f76108 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -41,7 +41,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DevicesRequest struct { DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` @@ -991,6 +993,10 @@ var ( ErrIntOverflowNetworkserver = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto", fileDescriptorNetworkserver) +} + var fileDescriptorNetworkserver = []byte{ // 519 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xdd, 0x6e, 0xd3, 0x30, diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 363455151..3488e62d6 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -33,7 +33,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package // Reference imports to suppress errors if they are not otherwise used. var _ context.Context @@ -342,6 +344,10 @@ var _Monitoring_serviceDesc = grpc.ServiceDesc{ Metadata: fileDescriptorNoc, } +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/noc/noc.proto", fileDescriptorNoc) +} + var fileDescriptorNoc = []byte{ // 237 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 22bf6cc59..1c4a1c8ec 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -40,7 +40,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DeviceIdentifier struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` @@ -1106,6 +1108,10 @@ var ( ErrIntOverflowDevice = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto", fileDescriptorDevice) +} + var fileDescriptorDevice = []byte{ // 527 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xc1, 0x8e, 0xd3, 0x3c, diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index df0423de1..47b5902f9 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -1103,6 +1103,10 @@ var ( ErrIntOverflowLorawan = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto", fileDescriptorLorawan) +} + var fileDescriptorLorawan = []byte{ // 566 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x53, 0xcf, 0x4e, 0xdb, 0x30, diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 284da36b1..bc217ac63 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -29,7 +29,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type RxMetadata struct { // Types that are valid to be assigned to Protocol: @@ -875,6 +877,10 @@ var ( ErrIntOverflowProtocol = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto", fileDescriptorProtocol) +} + var fileDescriptorProtocol = []byte{ // 211 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 26d485c61..9c2cc98e7 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -45,7 +45,9 @@ var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. -const _ = proto.ProtoPackageIsVersion1 +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type SubscribeRequest struct { } @@ -2380,6 +2382,10 @@ var ( ErrIntOverflowRouter = fmt.Errorf("proto: integer overflow") ) +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/router/router.proto", fileDescriptorRouter) +} + var fileDescriptorRouter = []byte{ // 797 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0xcd, 0x6e, 0xd3, 0x40, From 07f59b0009401311d624c1bff2d1f764b8f0b599 Mon Sep 17 00:00:00 2001 From: Antoine Rondelet Date: Thu, 18 Aug 2016 17:51:37 +0100 Subject: [PATCH 1684/2266] Feature/pf encoder (#223) Introduced downlink payload functions with encoder to process structured data from JSON to binary data to emit to the node --- core/handler/application/application.go | 8 + core/handler/application/application_test.go | 2 + core/handler/convert_fields.go | 118 +++++++++++-- core/handler/convert_fields_test.go | 165 ++++++++++++++++--- core/handler/downlink.go | 2 + core/handler/downlink_test.go | 60 ++++++- core/handler/manager_server.go | 2 + core/handler/uplink.go | 2 +- ttnctl/cmd/applications_pf.go | 7 + ttnctl/cmd/applications_pf_set.go | 14 +- ttnctl/cmd/downlink.go | 63 +++++-- 11 files changed, 383 insertions(+), 60 deletions(-) diff --git a/core/handler/application/application.go b/core/handler/application/application.go index ed0cf377c..2617ee508 100644 --- a/core/handler/application/application.go +++ b/core/handler/application/application.go @@ -20,6 +20,9 @@ type Application struct { // Validator is a JavaScript function that validates the data is converted by // Converter and returns a boolean value indicating the validity of the data Validator string + // Encoder is a JavaScript function that encode the data send on Downlink messages + // Returns an object containing the converted values in []byte + Encoder string UpdatedAt time.Time } @@ -30,6 +33,7 @@ var ApplicationProperties = []string{ "decoder", "converter", "validator", + "encoder", "updated_at", } @@ -65,6 +69,8 @@ func (application *Application) formatProperty(property string) (formatted strin formatted = application.Converter case "validator": formatted = application.Validator + case "encoder": + formatted = application.Encoder case "updated_at": if !application.UpdatedAt.IsZero() { formatted = application.UpdatedAt.UTC().Format(time.RFC3339Nano) @@ -88,6 +94,8 @@ func (application *Application) parseProperty(property string, value string) err application.Converter = value case "validator": application.Validator = value + case "encoder": + application.Encoder = value case "updated_at": val, err := time.Parse(time.RFC3339Nano, value) if err != nil { diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go index c3a2e50e7..acd356e36 100644 --- a/core/handler/application/application_test.go +++ b/core/handler/application/application_test.go @@ -15,11 +15,13 @@ func getTestApplication() (application *Application, dmap map[string]string) { Decoder: `function (payload) { return { size: payload.length; } }`, Converter: `function (data) { return data; }`, Validator: `function (data) { return data.size % 2 == 0; }`, + Encoder: `function (payload){return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]; }`, }, map[string]string{ "app_id": "AppID-1", "decoder": `function (payload) { return { size: payload.length; } }`, "converter": `function (data) { return data; }`, "validator": `function (data) { return data.size % 2 == 0; }`, + "encoder": `function (payload){return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]; }`, "updated_at": "", } } diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 763008ca1..dcc73a6a8 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -14,15 +14,15 @@ import ( "github.com/robertkrimen/otto" ) -// ConvertFields converts the payload to fields using payload functions -func (h *handler) ConvertFields(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { +// ConvertFieldsUp converts the payload to fields using payload functions +func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { // Find Application app, err := h.applications.Get(ttnUp.AppId) if err != nil { return nil // Do not process if application not found } - functions := &Functions{ + functions := &UplinkFunctions{ Decoder: app.Decoder, Converter: app.Converter, Validator: app.Validator, @@ -42,8 +42,8 @@ func (h *handler) ConvertFields(ctx log.Interface, ttnUp *pb_broker.Deduplicated return nil } -// Functions decodes, converts and validates payload using JavaScript functions -type Functions struct { +// UplinkFunctions decodes, converts and validates payload using JavaScript functions +type UplinkFunctions struct { // Decoder is a JavaScript function that accepts the payload as byte array and // returns an object containing the decoded values Decoder string @@ -59,14 +59,14 @@ type Functions struct { var timeOut = 100 * time.Millisecond // Decode decodes the payload using the Decoder function into a map -func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { +func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) { if f.Decoder == "" { return nil, errors.New("Decoder function not set") } vm := otto.New() vm.Set("payload", payload) - value, err := RunUnsafeCode(vm, fmt.Sprintf("(%s)(payload)", f.Decoder), timeOut) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload)", f.Decoder), timeOut) if err != nil { return nil, err } @@ -86,14 +86,14 @@ func (f *Functions) Decode(payload []byte) (map[string]interface{}, error) { // Convert converts the values in the specified map to a another map using the // Converter function. If the Converter function is not set, this function // returns the data as-is -func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{}, error) { +func (f *UplinkFunctions) Convert(data map[string]interface{}) (map[string]interface{}, error) { if f.Converter == "" { return data, nil } vm := otto.New() vm.Set("data", data) - value, err := RunUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Converter), timeOut) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Converter), timeOut) if err != nil { return nil, err } @@ -113,14 +113,14 @@ func (f *Functions) Convert(data map[string]interface{}) (map[string]interface{} // Validate validates the values in the specified map using the Validator // function. If the Validator function is not set, this function returns true -func (f *Functions) Validate(data map[string]interface{}) (bool, error) { +func (f *UplinkFunctions) Validate(data map[string]interface{}) (bool, error) { if f.Validator == "" { return true, nil } vm := otto.New() vm.Set("data", data) - value, err := RunUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Validator), timeOut) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Validator), timeOut) if err != nil { return false, err } @@ -133,7 +133,7 @@ func (f *Functions) Validate(data map[string]interface{}) (bool, error) { } // Process decodes the specified payload, converts it and test the validity -func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error) { +func (f *UplinkFunctions) Process(payload []byte) (map[string]interface{}, bool, error) { decoded, err := f.Decode(payload) if err != nil { return nil, false, err @@ -148,14 +148,14 @@ func (f *Functions) Process(payload []byte) (map[string]interface{}, bool, error return converted, valid, err } -var timeOutExceeded = errors.New("Code has been running to long") +var errTimeOutExceeded = errors.New("Code has been running to long") -func RunUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value otto.Value, err error) { +func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value otto.Value, err error) { start := time.Now() defer func() { duration := time.Since(start) if caught := recover(); caught != nil { - if caught == timeOutExceeded { + if caught == errTimeOutExceeded { value = otto.Value{} err = fmt.Errorf("Interrupted javascript execution after %v", duration) return @@ -171,8 +171,94 @@ func RunUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value ott go func() { time.Sleep(timeOut) vm.Interrupt <- func() { - panic(timeOutExceeded) + panic(errTimeOutExceeded) } }() return vm.Run(code) } + +// DownlinkFunctions encodes payload using JavaScript functions +type DownlinkFunctions struct { + // Encoder is a JavaScript function that accepts the payload as JSON and + // returns an array of bytes + Encoder string +} + +// Encode encodes the map into a byte slice using the encoder payload function +// If no encoder function is set, this function returns an array. +func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, error) { + if f.Encoder == "" { + return nil, errors.New("Encoder function not set") + } + + vm := otto.New() + vm.Set("payload", payload) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload)", f.Encoder), timeOut) + if err != nil { + return nil, err + } + + if !value.IsObject() { + return nil, errors.New("Encoder does not return an object") + } + + v, err := value.Export() + if err != nil { + return nil, err + } + + m, ok := v.([]int64) + if !ok { + return nil, errors.New("Encoder should return an Array of numbers") + } + + res := make([]byte, len(m)) + for i, v := range m { + if v < 0 || v > 255 { + return nil, errors.New("Numbers in array should be between 0 and 255") + } + res[i] = byte(v) + } + + return res, nil +} + +// Process encode the specified field, converts it into a valid payload +func (f *DownlinkFunctions) Process(payload map[string]interface{}) ([]byte, bool, error) { + encoded, err := f.Encode(payload) + if err != nil { + return nil, false, err + } + + return encoded, true, nil +} + +// ConvertFieldsDown converts the fields into a payload +func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { + if appDown.Fields == nil { + return nil + } + + if appDown.Payload != nil { + return errors.New("Both Fields and Payload provided") + } + + app, err := h.applications.Get(appDown.AppID) + if err != nil { + return nil + } + + functions := &DownlinkFunctions{ + Encoder: app.Encoder, + } + + message, _, err := functions.Process(appDown.Fields) + if err != nil { + return err + } + + appDown.Payload = message + ttnDown.Payload = message + + return nil +} diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index ea21b016c..5f99f36a9 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/core/handler/application" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -29,7 +30,7 @@ func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.Uplink return ttnUp, appUp } -func TestConvertFields(t *testing.T) { +func TestConvertFieldsUp(t *testing.T) { a := New(t) appID := "AppID-1" @@ -39,7 +40,7 @@ func TestConvertFields(t *testing.T) { // No functions ttnUp, appUp := buildConversionUplink() - err := h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + err := h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.Fields, ShouldBeEmpty) @@ -49,7 +50,7 @@ func TestConvertFields(t *testing.T) { Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, }) ttnUp, appUp = buildConversionUplink() - err = h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.Fields, ShouldResemble, map[string]interface{}{ @@ -63,7 +64,7 @@ func TestConvertFields(t *testing.T) { Validator: `function(data) { return false; }`, }) ttnUp, appUp = buildConversionUplink() - err = h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldNotBeNil) a.So(appUp.Fields, ShouldBeEmpty) @@ -74,7 +75,7 @@ func TestConvertFields(t *testing.T) { Converter: `function(data) { throw "expected"; }`, }) ttnUp, appUp = buildConversionUplink() - err = h.ConvertFields(GetLogger(t, "TestConvertFields"), ttnUp, appUp) + err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.Fields, ShouldBeEmpty) } @@ -82,7 +83,7 @@ func TestConvertFields(t *testing.T) { func TestDecode(t *testing.T) { a := New(t) - functions := &Functions{ + functions := &UplinkFunctions{ Decoder: `function(payload) { return { value: (payload[0] << 8) | payload[1] @@ -102,7 +103,7 @@ func TestDecode(t *testing.T) { func TestConvert(t *testing.T) { a := New(t) - withFunction := &Functions{ + withFunction := &UplinkFunctions{ Converter: `function(data) { return { celcius: data.temperature * 2 @@ -113,7 +114,7 @@ func TestConvert(t *testing.T) { a.So(err, ShouldBeNil) a.So(data["celcius"], ShouldEqual, 22) - withoutFunction := &Functions{} + withoutFunction := &UplinkFunctions{} data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) a.So(err, ShouldBeNil) a.So(data["temperature"], ShouldEqual, 11) @@ -122,7 +123,7 @@ func TestConvert(t *testing.T) { func TestValidate(t *testing.T) { a := New(t) - withFunction := &Functions{ + withFunction := &UplinkFunctions{ Validator: `function(data) { return data.temperature < 20; }`, @@ -134,16 +135,16 @@ func TestValidate(t *testing.T) { a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) - withoutFunction := &Functions{} + withoutFunction := &UplinkFunctions{} valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) a.So(err, ShouldBeNil) a.So(valid, ShouldBeTrue) } -func TestProcess(t *testing.T) { +func TestProcessUplink(t *testing.T) { a := New(t) - functions := &Functions{ + functions := &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0], @@ -166,32 +167,32 @@ func TestProcess(t *testing.T) { a.So(data["humidity"], ShouldEqual, 110) } -func TestProcessInvalidFunction(t *testing.T) { +func TestProcessInvalidUplinkFunction(t *testing.T) { a := New(t) // Empty Function - functions := &Functions{ + functions := &UplinkFunctions{ Decoder: ``, } _, _, err := functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) // Invalid Function - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `this is not valid JavaScript`, } _, _, err = functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) // Invalid return - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return "Hello" }`, } _, _, err = functions.Process([]byte{40, 110}) a.So(err, ShouldNotBeNil) // Invalid Function - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `this is not valid JavaScript`, } @@ -199,7 +200,7 @@ func TestProcessInvalidFunction(t *testing.T) { a.So(err, ShouldNotBeNil) // Invalid Return - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `function(data) { return "Hello" }`, } @@ -207,7 +208,7 @@ func TestProcessInvalidFunction(t *testing.T) { a.So(err, ShouldNotBeNil) // Invalid Function - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `this is not valid JavaScript`, } @@ -215,7 +216,7 @@ func TestProcessInvalidFunction(t *testing.T) { a.So(err, ShouldNotBeNil) // Invalid Return - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `function(data) { return "Hello" }`, } @@ -224,7 +225,7 @@ func TestProcessInvalidFunction(t *testing.T) { // Invalid Object (Arrays are Objects too, but don't jive well with // map[string]interface{}) - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return [1] }`, } _, _, err = functions.Process([]byte{40, 110}) @@ -232,7 +233,7 @@ func TestProcessInvalidFunction(t *testing.T) { // Invalid Object (Arrays are Objects too, but don't jive well with // map[string]interface{}) - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `function(payload) { return [1] }`, } @@ -241,7 +242,7 @@ func TestProcessInvalidFunction(t *testing.T) { // Invalid Object (Arrays are Objects too), this should work error because // we're expecting a Boolean - functions = &Functions{ + functions = &UplinkFunctions{ Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `function(payload) { return [1] }`, } @@ -257,7 +258,7 @@ func TestTimeoutExceeded(t *testing.T) { a := New(t) start := time.Now() - functions := &Functions{ + functions := &UplinkFunctions{ Decoder: `function(payload){ while (true) { } }`, } @@ -272,3 +273,119 @@ func TestTimeoutExceeded(t *testing.T) { <-time.After(200 * time.Millisecond) a.So(interrupted, ShouldHaveLength, 1) } + +func TestEncode(t *testing.T) { + a := New(t) + + // This function return an array of bytes (random) + functions := &DownlinkFunctions{ + Encoder: `function test(payload){ + return [ 1, 2, 3, 4, 5, 6, 7 ] + }`, + } + + // The payload is a JSON structure + payload := map[string]interface{}{"temperature": 11} + + m, err := functions.Encode(payload) + a.So(err, ShouldBeNil) + + a.So(m, ShouldHaveLength, 7) + a.So(m, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) +} + +func buildConversionDownlink() (*pb_broker.DownlinkMessage, *mqtt.DownlinkMessage) { + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + ttnDown := &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + } + appDown := &mqtt.DownlinkMessage{ + FPort: 1, + AppID: "AppID-1", + DevID: "DevID-1", + Fields: map[string]interface{}{"temperature": 30}, + // We want to "build" the payload with the content of the fields + } + return ttnDown, appDown +} + +func TestConvertFieldsDown(t *testing.T) { + a := New(t) + appID := "AppID-1" + + h := &handler{ + applications: application.NewApplicationStore(), + } + + // Case1: No Encoder + ttnDown, appDown := buildConversionDownlink() + err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(ttnDown.Payload, ShouldBeEmpty) + + // Case2: Normal flow with Encoder + h.applications.Set(&application.Application{ + AppID: appID, + // Encoder takes JSON fields as argument and return the payload as []byte + Encoder: `function test(payload){ + return [ 1, 2, 3, 4, 5, 6, 7 ] + }`, + }) + ttnDown, appDown = buildConversionDownlink() + err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(ttnDown.Payload, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) +} + +func TestProcessDownlinkInvalidFunction(t *testing.T) { + a := New(t) + + // Empty Function + functions := &DownlinkFunctions{ + Encoder: ``, + } + _, _, err := functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) + + // Invalid Function + functions = &DownlinkFunctions{ + Encoder: `this is not valid JavaScript`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function(payload) { return "Hello" }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function(payload) { return [ 100, 2256, 7 ] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function(payload) { return [0, -1, "blablabla"] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) + + // Invalid return + functions = &DownlinkFunctions{ + Encoder: `function(payload) { + return { + temperature: payload[0], + humidity: payload[1] + } +} }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) +} diff --git a/core/handler/downlink.go b/core/handler/downlink.go index ceb8f025f..6ee2f84b0 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -51,6 +51,7 @@ func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb "DevEUI": downlink.AppEui, "AppEUI": downlink.DevEui, }) + var err error defer func() { if err != nil { @@ -60,6 +61,7 @@ func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb // Get Processors processors := []DownlinkProcessor{ + h.ConvertFieldsDown, h.ConvertToLoRaWAN, } diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 4b369e011..34a17fd55 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -4,10 +4,12 @@ package handler import ( + "sync" "testing" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" @@ -49,16 +51,20 @@ func TestEnqueueDownlink(t *testing.T) { func TestHandleDownlink(t *testing.T) { a := New(t) + var err error + var wg sync.WaitGroup appID := "app2" devID := "dev2" appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, - devices: device.NewDeviceStore(), + Component: &core.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, + devices: device.NewDeviceStore(), + applications: application.NewApplicationStore(), + downlink: make(chan *pb_broker.DownlinkMessage), } - - err := h.HandleDownlink(&mqtt.DownlinkMessage{ + // Neither payload nor Fields provided : ERROR + err = h.HandleDownlink(&mqtt.DownlinkMessage{ AppID: appID, DevID: devID, }, &pb_broker.DownlinkMessage{ @@ -66,6 +72,7 @@ func TestHandleDownlink(t *testing.T) { DevEui: &devEUI, }) a.So(err, ShouldNotBeNil) + h.devices.Set(&device.Device{ AppID: appID, DevID: devID, @@ -79,10 +86,13 @@ func TestHandleDownlink(t *testing.T) { Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldBeNil) - h.downlink = make(chan *pb_broker.DownlinkMessage) + + // Payload provided + wg.Add(1) go func() { dl := <-h.downlink a.So(dl.Payload, ShouldNotBeEmpty) + wg.Done() }() err = h.HandleDownlink(&mqtt.DownlinkMessage{ AppID: appID, @@ -94,4 +104,44 @@ func TestHandleDownlink(t *testing.T) { Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldBeNil) + wg.Wait() + + // Both Payload and Fields provided + h.applications.Set(&application.Application{ + AppID: appID, + Encoder: `function (payload){ + return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0] + }`, + }) + jsonFields := map[string]interface{}{"temperature": 11} + err = h.HandleDownlink(&mqtt.DownlinkMessage{ + FPort: 1, + AppID: appID, + DevID: devID, + Fields: jsonFields, + Payload: []byte{0xAA, 0xBC}, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + }) + a.So(err, ShouldNotBeNil) + + // JSON Fields provided + wg.Add(1) + go func() { + dl := <-h.downlink + a.So(dl.Payload, ShouldNotBeEmpty) + wg.Done() + }() + err = h.HandleDownlink(&mqtt.DownlinkMessage{ + FPort: 1, + AppID: appID, + DevID: devID, + Fields: jsonFields, + }, &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + }) + a.So(err, ShouldBeNil) + wg.Wait() } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index fada63e9b..03dcb9ee2 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -239,6 +239,7 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI Decoder: app.Decoder, Converter: app.Converter, Validator: app.Validator, + Encoder: app.Encoder, }, nil } @@ -297,6 +298,7 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) Decoder: in.Decoder, Converter: in.Converter, Validator: in.Validator, + Encoder: in.Encoder, }) if err != nil { return nil, err diff --git a/core/handler/uplink.go b/core/handler/uplink.go index bb57fbc8c..f69085b62 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -39,7 +39,7 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro processors := []UplinkProcessor{ h.ConvertFromLoRaWAN, h.ConvertMetadata, - h.ConvertFields, + h.ConvertFieldsUp, } ctx.WithField("NumProcessors", len(processors)).Debug("Running Uplink Processors") diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index 05f8ebae7..86bdb4d91 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -51,6 +51,13 @@ converting and validating binary payload. } else { ctx.Info("No validator function") } + + if app.Encoder != "" { + ctx.Info("Encoder function") + fmt.Println(app.Encoder) + } else { + ctx.Info("No encoder function") + } }, } diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 996cd87ad..ce04b7b07 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -15,9 +15,8 @@ import ( "github.com/spf13/cobra" ) -// applicationsPayloadFunctionsSetCmd represents the `applications pf set` command var applicationsPayloadFunctionsSetCmd = &cobra.Command{ - Use: "set [decoder/converter/validator] [file.js]", + Use: "set [decoder/converter/validator/encoder] [file.js]", Short: "Set payload functions of an application", Long: `ttnctl pf set can be used to get or set payload functions of an application. The functions are read from the supplied file or from STDIN.`, @@ -54,6 +53,8 @@ The functions are read from the supplied file or from STDIN.`, app.Converter = string(content) case "validator": app.Validator = string(content) + case "encoder": + app.Encoder = string(content) default: ctx.Fatalf("Function %s does not exist", function) } @@ -88,6 +89,15 @@ The functions are read from the supplied file or from STDIN.`, } ########## Write your Validator here and end with Ctrl+D (EOF):`) app.Validator = readFunction() + case "encoder": + fmt.Println(`function (val) { + // val is the output of the encoder function. + + // todo: return an array of numbers + return return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]; +} +########## Write your Encoder here and end with Ctrl+D (EOF):`) + app.Encoder = readFunction() default: ctx.Fatalf("Function %s does not exist", function) } diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 73a0a26cc..38aee22b6 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -4,6 +4,8 @@ package cmd import ( + "encoding/json" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" @@ -11,7 +13,6 @@ import ( "github.com/spf13/cobra" ) -// downlinkCmd represents the `downlink` command var downlinkCmd = &cobra.Command{ Use: "downlink [DevID] [Payload]", Short: "Send a downlink message to a device", @@ -20,6 +21,12 @@ var downlinkCmd = &cobra.Command{ client := util.GetMQTT(ctx) defer client.Disconnect() + if len(args) < 2 { + ctx.Info("Not enough arguments. Please, provide a devId and a Payload") + cmd.UsageFunc()(cmd) + return + } + appID := util.GetAppID(ctx) ctx = ctx.WithField("AppID", appID) @@ -29,30 +36,62 @@ var downlinkCmd = &cobra.Command{ } ctx = ctx.WithField("DevID", devID) - payload, err := types.ParseHEX(args[1], len(args[1])/2) + jsonflag, err := cmd.Flags().GetBool("json") + if err != nil { - ctx.WithError(err).Fatal("Invalid Payload") + ctx.WithError(err).Fatal("Failed to read json flag") } - fPort, _ := cmd.Flags().GetInt("fport") + fPort, err := cmd.Flags().GetInt("fport") + + if err != nil { + ctx.WithError(err).Fatal("Failed to read fport flag") + } + + message := mqtt.DownlinkMessage{ + AppID: appID, + DevID: devID, + FPort: uint8(fPort), + } - token := client.PublishDownlink(mqtt.DownlinkMessage{ - AppID: appID, - DevID: devID, - FPort: uint8(fPort), - Payload: payload, - }) + if args[1] == "" { + ctx.Info("Invalid command") + cmd.UsageFunc()(cmd) + return + } + + if jsonflag { + // Valid payload provided + json flag + _, err := types.ParseHEX(args[1], len(args[1])/2) + if err == nil { + ctx.WithError(err).Fatal("You are providing a valid payload using the --json flag.") + } + + err = json.Unmarshal([]byte(args[1]), &message.Fields) + + if err != nil { + ctx.WithError(err).Fatal("Invalid json string") + return + } + } else { // Payload provided + payload, err := types.ParseHEX(args[1], len(args[1])/2) + if err != nil { + ctx.WithError(err).Fatal("Invalid Payload") + } + + message.Payload = payload + } + token := client.PublishDownlink(message) token.Wait() if token.Error() != nil { ctx.WithError(token.Error()).Fatal("Could not enqueue downlink") } - ctx.Info("Enqueued downlink") - }, } func init() { RootCmd.AddCommand(downlinkCmd) downlinkCmd.Flags().Int("fport", 1, "FPort for downlink") + downlinkCmd.Flags().Bool("json", false, "Send json to the handler (MQTT)") } From af8c0b073914f7c8381db4fc949fce132da421f1 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 18 Aug 2016 17:56:36 +0100 Subject: [PATCH 1685/2266] Clarified ttnctl user output --- ttnctl/cmd/downlink.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 38aee22b6..23a5fc151 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -64,7 +64,7 @@ var downlinkCmd = &cobra.Command{ // Valid payload provided + json flag _, err := types.ParseHEX(args[1], len(args[1])/2) if err == nil { - ctx.WithError(err).Fatal("You are providing a valid payload using the --json flag.") + ctx.WithError(err).Fatal("You are providing a valid HEX payload while sending payload in JSON.") } err = json.Unmarshal([]byte(args[1]), &message.Fields) @@ -93,5 +93,5 @@ var downlinkCmd = &cobra.Command{ func init() { RootCmd.AddCommand(downlinkCmd) downlinkCmd.Flags().Int("fport", 1, "FPort for downlink") - downlinkCmd.Flags().Bool("json", false, "Send json to the handler (MQTT)") + downlinkCmd.Flags().Bool("json", false, "Provide the payload as JSON") } From 654a548e5af9da4f94471f5e0eb8ead1d9ff6b2e Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 31 Aug 2016 17:38:26 +0200 Subject: [PATCH 1686/2266] Bugfix/encoder types (#225) * Add test to reproduce int handling problem * Use reflection to determine the type of the return Encoder array * Allow floating point types * Add missing types * Fix typo in comments --- core/handler/convert_fields.go | 52 +++++++++++++++++++++++++---- core/handler/convert_fields_test.go | 14 ++++++++ 2 files changed, 59 insertions(+), 7 deletions(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index dcc73a6a8..55808dd98 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -6,6 +6,7 @@ package handler import ( "errors" "fmt" + "reflect" "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" @@ -207,17 +208,54 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro return nil, err } - m, ok := v.([]int64) - if !ok { - return nil, errors.New("Encoder should return an Array of numbers") + if reflect.TypeOf(v).Kind() != reflect.Slice { + return nil, errors.New("Encoder does not return an array") } - res := make([]byte, len(m)) - for i, v := range m { - if v < 0 || v > 255 { + s := reflect.ValueOf(v) + l := s.Len() + + res := make([]byte, l) + + var n int64 + for i := 0; i < l; i++ { + el := s.Index(i).Interface() + + // type switch does not have fallthrough so we need + // to check every element individually + switch t := el.(type) { + case byte: + n = int64(t) + case int: + n = int64(t) + case int32: + n = int64(t) + case uint32: + n = int64(t) + case int64: + n = int64(t) + case uint64: + n = int64(t) + case float32: + n = int64(t) + if float32(n) != t { + return nil, errors.New("Encoder should return an Array of integer numbers") + } + case float64: + n = int64(t) + if float64(n) != t { + return nil, errors.New("Encoder should return an Array of integer numbers") + } + default: + fmt.Printf("VAL %v TYPE %T\n", el, el) + return nil, errors.New("Encoder should return an Array of integer numbers") + } + + if n < 0 || n > 255 { return nil, errors.New("Numbers in array should be between 0 and 255") } - res[i] = byte(v) + + res[i] = byte(n) } return res, nil diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 5f99f36a9..d90bd98f6 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -292,6 +292,13 @@ func TestEncode(t *testing.T) { a.So(m, ShouldHaveLength, 7) a.So(m, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) + + // Return int type + functions = &DownlinkFunctions{ + Encoder: `function(payload) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldBeNil) } func buildConversionDownlink() (*pb_broker.DownlinkMessage, *mqtt.DownlinkMessage) { @@ -333,6 +340,7 @@ func TestConvertFieldsDown(t *testing.T) { return [ 1, 2, 3, 4, 5, 6, 7 ] }`, }) + ttnDown, appDown = buildConversionDownlink() err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) @@ -388,4 +396,10 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { } _, _, err = functions.Process(map[string]interface{}{"key": 11}) a.So(err, ShouldNotBeNil) + + functions = &DownlinkFunctions{ + Encoder: `function(payload) { return [ 1, 1.5 ] }`, + } + _, _, err = functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldNotBeNil) } From 9ffd63db131510024592ce2868bdd7b6742895f2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 2 Sep 2016 10:23:24 +0200 Subject: [PATCH 1687/2266] Return error on subscribing to TLS MQTT --- ttnctl/util/mqtt.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index a5d59c0ad..390344e58 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -57,7 +57,12 @@ func GetMQTT(ctx log.Interface) mqtt.Client { } key := app.AccessKeys[keyIdx] - broker := fmt.Sprintf("tcp://%s", viper.GetString("mqtt-broker")) + mqttProto := "tcp" + if strings.HasSuffix(viper.GetString("mqtt-broker"), ":8883") { + mqttProto = "ssl" + ctx.Fatal("TLS connections are not yet supported by ttnctl") + } + broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-broker")) client := mqtt.NewClient(ctx, "ttnctl", appID, key.Key, broker) ctx.WithField("MQTT Broker", broker).Info("Connecting to MQTT...") From 13a949479c985d60babb002ed5d2693d4f3cf5bc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 2 Sep 2016 11:51:26 +0200 Subject: [PATCH 1688/2266] Separate manager servers from components Resolves #227 --- core/broker/manager_server.go | 12 +++--- core/handler/manager_server.go | 60 ++++++++++++++-------------- core/networkserver/manager_server.go | 10 ++--- core/router/manager_server.go | 6 +-- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index a14898019..cad0c10a1 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -15,25 +15,25 @@ import ( ) type brokerManager struct { - *broker + broker *broker } var errf = grpc.Errorf func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { - return b.nsManager.GetDevice(ctx, in) + return b.broker.nsManager.GetDevice(ctx, in) } func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*api.Ack, error) { - return b.nsManager.SetDevice(ctx, in) + return b.broker.nsManager.SetDevice(ctx, in) } func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*api.Ack, error) { - return b.nsManager.DeleteDevice(ctx, in) + return b.broker.nsManager.DeleteDevice(ctx, in) } func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*api.Ack, error) { - claims, err := b.Component.ValidateTTNAuthContext(ctx) + claims, err := b.broker.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } @@ -43,7 +43,7 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if !claims.CanEditApp(in.AppId) { return nil, errf(codes.Unauthenticated, "No access to this application") } - err = b.handlerDiscovery.AddAppID(in.HandlerId, in.AppId) + err = b.broker.handlerDiscovery.AddAppID(in.HandlerId, in.AppId) if err != nil { return nil, err } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 03dcb9ee2..5b7b43a49 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -19,7 +19,7 @@ import ( ) type handlerManager struct { - *handler + handler *handler } var errf = grpc.Errorf @@ -28,14 +28,14 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } - claims, err := h.Component.ValidateTTNAuthContext(ctx) + claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { return nil, errf(codes.Unauthenticated, "No access to this device") } - dev, err := h.devices.Get(in.AppId, in.DevId) + dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err } @@ -51,7 +51,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) return nil, err } - nsDev, err := h.ttnDeviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ + nsDev, err := h.handler.ttnDeviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ AppEui: &dev.AppEUI, DevEui: &dev.DevEUI, }) @@ -98,7 +98,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack if dev != nil { // When this is an update if dev.AppEUI != *lorawan.AppEui || dev.DevEUI != *lorawan.DevEui { // If the AppEUI or DevEUI is changed, we should remove the device from the NetworkServer and re-add it later - _, err = h.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{ + _, err = h.handler.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{ AppEui: &dev.AppEUI, DevEui: &dev.DevEUI, }) @@ -143,12 +143,12 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack Uses32BitFCnt: lorawan.Uses32BitFCnt, } - _, err = h.ttnDeviceManager.SetDevice(ctx, nsUpdated) + _, err = h.handler.ttnDeviceManager.SetDevice(ctx, nsUpdated) if err != nil { return nil, err } - err = h.devices.Set(updated) + err = h.handler.devices.Set(updated) if err != nil { return nil, err } @@ -161,11 +161,11 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifi if err != nil { return nil, err } - _, err = h.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + _, err = h.handler.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { return nil, err } - err = h.devices.Delete(in.AppId, in.DevId) + err = h.handler.devices.Delete(in.AppId, in.DevId) if err != nil { return nil, err } @@ -176,14 +176,14 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") } - claims, err := h.Component.ValidateTTNAuthContext(ctx) + claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { return nil, errf(codes.Unauthenticated, "No access to this application") } - devices, err := h.devices.ListForApp(in.AppId) + devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { return nil, err } @@ -211,14 +211,14 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") } - claims, err := h.Component.ValidateTTNAuthContext(ctx) + claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { return nil, errf(codes.Unauthenticated, "No access to this application") } - app, err := h.applications.Get(in.AppId) + app, err := h.handler.applications.Get(in.AppId) if err != nil { return nil, err } @@ -252,31 +252,31 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica return nil, errf(codes.InvalidArgument, "Application already registered") } - err = h.applications.Set(&application.Application{ + err = h.handler.applications.Set(&application.Application{ AppID: in.AppId, }) if err != nil { return nil, err } - _, err = h.Discovery.AddMetadata(ctx, &pb_discovery.MetadataRequest{ - ServiceName: h.Identity.ServiceName, - Id: h.Identity.Id, + _, err = h.handler.Discovery.AddMetadata(ctx, &pb_discovery.MetadataRequest{ + ServiceName: h.handler.Identity.ServiceName, + Id: h.handler.Identity.Id, Metadata: &pb_discovery.Metadata{ Key: pb_discovery.Metadata_APP_ID, Value: []byte(in.AppId), }, }) if err != nil { - h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Discovery") + h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Discovery") } - _, err = h.ttnBrokerManager.RegisterApplicationHandler(ctx, &pb_broker.ApplicationHandlerRegistration{ + _, err = h.handler.ttnBrokerManager.RegisterApplicationHandler(ctx, &pb_broker.ApplicationHandlerRegistration{ AppId: in.AppId, - HandlerId: h.Identity.Id, + HandlerId: h.handler.Identity.Id, }) if err != nil { - h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") + h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") } return &api.Ack{}, nil @@ -293,7 +293,7 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) return nil, grpcErrf(codes.InvalidArgument, "Invalid Application") } - err = h.applications.Set(&application.Application{ + err = h.handler.applications.Set(&application.Application{ AppID: in.AppId, Decoder: in.Decoder, Converter: in.Converter, @@ -314,37 +314,37 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati } // Get and delete all devices for this application - devices, err := h.devices.ListForApp(in.AppId) + devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { return nil, err } for _, dev := range devices { - _, err = h.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + _, err = h.handler.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { return nil, err } - err = h.devices.Delete(dev.AppID, dev.DevID) + err = h.handler.devices.Delete(dev.AppID, dev.DevID) if err != nil { return nil, err } } // Delete the Application - err = h.applications.Delete(in.AppId) + err = h.handler.applications.Delete(in.AppId) if err != nil { return nil, err } - _, err = h.Discovery.DeleteMetadata(ctx, &pb_discovery.MetadataRequest{ - ServiceName: h.Identity.ServiceName, - Id: h.Identity.Id, + _, err = h.handler.Discovery.DeleteMetadata(ctx, &pb_discovery.MetadataRequest{ + ServiceName: h.handler.Identity.ServiceName, + Id: h.handler.Identity.Id, Metadata: &pb_discovery.Metadata{ Key: pb_discovery.Metadata_APP_ID, Value: []byte(in.AppId), }, }) if err != nil { - h.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not unregister Application from Discovery") + h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not unregister Application from Discovery") } return &api.Ack{}, nil diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index e9848829f..3b1536e5c 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -17,7 +17,7 @@ import ( ) type networkServerManager struct { - *networkServer + networkServer *networkServer } var errf = grpc.Errorf @@ -26,11 +26,11 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } - claims, err := n.Component.ValidateTTNAuthContext(ctx) + claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } - dev, err := n.devices.Get(*in.AppEui, *in.DevEui) + dev, err := n.networkServer.devices.Get(*in.AppEui, *in.DevEui) if err != nil { return nil, err } @@ -94,7 +94,7 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev updated.NwkSKey = *in.NwkSKey } - err = n.devices.Set(updated) + err = n.networkServer.devices.Set(updated) if err != nil { return nil, err } @@ -107,7 +107,7 @@ func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan. if err != nil { return nil, err } - err = n.devices.Delete(*in.AppEui, *in.DevEui) + err = n.networkServer.devices.Delete(*in.AppEui, *in.DevEui) if err != nil { return nil, err } diff --git a/core/router/manager_server.go b/core/router/manager_server.go index a5ac630f2..3995dcb68 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -13,7 +13,7 @@ import ( ) type routerManager struct { - *router + router *router } var errf = grpc.Errorf @@ -22,11 +22,11 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR if in.GatewayEui == nil { return nil, errf(codes.InvalidArgument, "GatewayEUI is required") } - _, err := r.ValidateTTNAuthContext(ctx) + _, err := r.router.ValidateTTNAuthContext(ctx) if err != nil { return nil, errf(codes.Unauthenticated, "No access") } - gtw := r.getGateway(*in.GatewayEui) + gtw := r.router.getGateway(*in.GatewayEui) status, err := gtw.Status.Get() if err != nil { return nil, err From ae8ba2515a378c2c0c30c38525c09c0014e592dc Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 2 Sep 2016 12:04:59 +0200 Subject: [PATCH 1689/2266] Feature/dry run (#226) * Add protos for handler dry-run uplink and downlink * Implement and test dry run functions in handler manager * Implement and test dry run in mhandler manager * Add client dry-run methods * Avoid nil pointer panic, and test for it * Move dry run methods to handlerManager --- api/api.pb.go | 30 +- api/broker/broker.pb.go | 139 ++-- api/discovery/discovery.pb.go | 72 +-- api/gateway/gateway.pb.go | 79 +-- api/handler/handler.pb.go | 886 ++++++++++++++++++++++++-- api/handler/handler.proto | 23 + api/handler/manager_client.go | 33 + api/networkserver/networkserver.pb.go | 68 +- api/noc/noc.pb.go | 27 +- api/protocol/lorawan/device.pb.go | 69 +- api/protocol/lorawan/lorawan.pb.go | 75 +-- api/protocol/protocol.pb.go | 8 +- api/router/router.pb.go | 103 +-- core/handler/dry_run.go | 93 +++ core/handler/dry_run_test.go | 199 ++++++ 15 files changed, 1530 insertions(+), 374 deletions(-) create mode 100644 core/handler/dry_run.go create mode 100644 core/handler/dry_run_test.go diff --git a/api/api.pb.go b/api/api.pb.go index 2cb077199..431e780f9 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -792,22 +792,22 @@ var ( func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 263 bytes of a gzipped FileDescriptorProto + // 268 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0x04, - 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x66, 0x20, 0x53, 0x89, 0x95, 0x8b, 0xd9, 0x31, - 0x39, 0x5b, 0xe9, 0x2c, 0x13, 0x17, 0x77, 0x40, 0x6a, 0x51, 0x72, 0x6a, 0x5e, 0x49, 0x66, 0x4e, - 0x6a, 0xb1, 0x90, 0x02, 0x17, 0x77, 0x01, 0x9c, 0x6b, 0x28, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0x14, - 0x84, 0x2c, 0x84, 0xaa, 0xc2, 0x54, 0x82, 0x09, 0x5d, 0x85, 0xa9, 0x90, 0x12, 0x17, 0x0f, 0x92, - 0x06, 0x03, 0x09, 0x66, 0xb0, 0x12, 0x14, 0x31, 0x54, 0x35, 0x46, 0xa6, 0x12, 0x2c, 0xe8, 0x6a, - 0x8c, 0xd0, 0xcc, 0x31, 0x35, 0x90, 0x60, 0x45, 0x57, 0x63, 0x8a, 0x66, 0x8e, 0xb9, 0xa9, 0x04, - 0x1b, 0xba, 0x1a, 0x73, 0x34, 0x73, 0x2c, 0x0d, 0x24, 0xd8, 0xd1, 0xd5, 0x58, 0xa2, 0x99, 0x63, - 0x69, 0x2a, 0xc1, 0x81, 0xa1, 0x06, 0xdd, 0x1c, 0x4b, 0x09, 0x4e, 0x0c, 0x35, 0x96, 0x4a, 0xde, - 0x5c, 0xac, 0x41, 0x89, 0x25, 0xc0, 0x80, 0x14, 0xe1, 0x62, 0x2d, 0x02, 0x32, 0x60, 0x41, 0x08, - 0xe1, 0xc0, 0x44, 0x61, 0xc1, 0x06, 0xe1, 0x08, 0x89, 0x71, 0xb1, 0x81, 0xa5, 0x4d, 0xa1, 0x41, - 0x05, 0xe5, 0x29, 0x09, 0x70, 0xf1, 0x05, 0xa7, 0x16, 0x95, 0xa5, 0x16, 0xf9, 0xa6, 0x96, 0x24, - 0xa6, 0x24, 0x96, 0x24, 0x3a, 0x09, 0x9c, 0x78, 0x24, 0xc7, 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, - 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0xe0, 0x38, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xd1, - 0xa3, 0x30, 0x03, 0x04, 0x02, 0x00, 0x00, + 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xc4, 0x82, 0x4c, 0x25, 0x56, 0x2e, 0x66, + 0xc7, 0xe4, 0x6c, 0xa5, 0xb3, 0x4c, 0x5c, 0xdc, 0x01, 0xa9, 0x45, 0xc9, 0xa9, 0x79, 0x25, 0x99, + 0x39, 0xa9, 0xc5, 0x42, 0x0a, 0x5c, 0xdc, 0x05, 0x70, 0xae, 0xa1, 0x04, 0xa3, 0x02, 0xa3, 0x06, + 0x53, 0x10, 0xb2, 0x10, 0xaa, 0x0a, 0x53, 0x09, 0x26, 0x74, 0x15, 0xa6, 0x42, 0x4a, 0x5c, 0x3c, + 0x48, 0x1a, 0x0c, 0x24, 0x98, 0xc1, 0x4a, 0x50, 0xc4, 0x50, 0xd5, 0x18, 0x99, 0x4a, 0xb0, 0xa0, + 0xab, 0x31, 0x42, 0x33, 0xc7, 0xd4, 0x40, 0x82, 0x15, 0x5d, 0x8d, 0x29, 0x9a, 0x39, 0xe6, 0xa6, + 0x12, 0x6c, 0xe8, 0x6a, 0xcc, 0xd1, 0xcc, 0xb1, 0x34, 0x90, 0x60, 0x47, 0x57, 0x63, 0x89, 0x66, + 0x8e, 0xa5, 0xa9, 0x04, 0x07, 0x86, 0x1a, 0x74, 0x73, 0x2c, 0x25, 0x38, 0x31, 0xd4, 0x58, 0x2a, + 0x79, 0x73, 0xb1, 0x06, 0x25, 0x96, 0xa4, 0x16, 0x0b, 0x89, 0x70, 0xb1, 0x16, 0x25, 0x96, 0xc0, + 0x83, 0x10, 0xc2, 0x81, 0x89, 0xc2, 0x82, 0x0d, 0xc2, 0x11, 0x12, 0xe3, 0x62, 0x03, 0x4b, 0x9b, + 0x42, 0x83, 0x0a, 0xca, 0x53, 0x12, 0xe0, 0xe2, 0x0b, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0xf2, 0x4d, + 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x74, 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, + 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xc7, 0xa9, 0x31, 0x20, + 0x00, 0x00, 0xff, 0xff, 0xd1, 0xa3, 0x30, 0x03, 0x04, 0x02, 0x00, 0x00, } diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 22268f25d..c58b1a59e 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -4156,73 +4156,74 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1086 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0xcd, 0x6f, 0x1b, 0x45, - 0x14, 0xc7, 0x76, 0xeb, 0xc4, 0xcf, 0xf1, 0x47, 0xa6, 0xf9, 0xd8, 0x5a, 0x90, 0x04, 0x23, 0x55, - 0xe1, 0xa3, 0x36, 0x18, 0x15, 0x14, 0x21, 0xa8, 0x1c, 0x52, 0x95, 0x20, 0xb9, 0x54, 0x5b, 0x87, - 0x03, 0x17, 0x6b, 0xbd, 0x3b, 0xb1, 0x47, 0x71, 0x76, 0x97, 0x9d, 0xd9, 0xa4, 0xf9, 0x1f, 0x90, - 0x38, 0xc0, 0x81, 0x7f, 0x07, 0x71, 0xe1, 0x06, 0x67, 0x0e, 0x08, 0xc1, 0x85, 0x0b, 0xff, 0x03, - 0x6f, 0x67, 0x66, 0xd7, 0xbb, 0x76, 0xdd, 0xa6, 0x50, 0x0e, 0xad, 0x38, 0xac, 0xbc, 0xf3, 0x3e, - 0x7e, 0xf3, 0xfc, 0x7b, 0x1f, 0xfb, 0xe0, 0xfd, 0x11, 0x13, 0xe3, 0x70, 0xd8, 0xb2, 0xbd, 0xd3, - 0x76, 0x7f, 0x4c, 0xfb, 0x63, 0xe6, 0x8e, 0xf8, 0x3d, 0x2a, 0xce, 0xbd, 0xe0, 0xa4, 0x2d, 0x84, - 0xdb, 0xb6, 0x7c, 0xd6, 0x1e, 0x06, 0xde, 0x09, 0x0d, 0xf4, 0x4f, 0xcb, 0x0f, 0x3c, 0xe1, 0x91, - 0xa2, 0x3a, 0x35, 0x6e, 0xa6, 0x00, 0x46, 0xde, 0xc8, 0x6b, 0x4b, 0xf5, 0x30, 0x3c, 0x96, 0x27, - 0x79, 0x90, 0x6f, 0xca, 0x2d, 0x63, 0xbe, 0xf0, 0x3e, 0x7c, 0xb4, 0xf9, 0x07, 0x97, 0x31, 0x97, - 0xa6, 0xb6, 0x37, 0x49, 0x5e, 0xb4, 0xf3, 0xde, 0x65, 0x9c, 0x47, 0x96, 0xa0, 0xe7, 0xd6, 0x45, - 0xfc, 0xab, 0x5c, 0x9b, 0x3f, 0xe4, 0xa1, 0x7a, 0xe0, 0x9d, 0xbb, 0x13, 0xe6, 0x9e, 0x7c, 0xe6, - 0x0b, 0xe6, 0xb9, 0x64, 0x0b, 0x80, 0x39, 0xd4, 0x15, 0xec, 0x98, 0xd1, 0xc0, 0xc8, 0xed, 0xe4, - 0x76, 0x4b, 0x66, 0x4a, 0x42, 0xbe, 0x80, 0xb2, 0xc6, 0x18, 0xd0, 0x90, 0x19, 0x79, 0x34, 0x58, - 0xd9, 0xdf, 0xfb, 0xe5, 0xd7, 0xed, 0x5b, 0x4f, 0x0a, 0xc3, 0xf6, 0x02, 0xda, 0x16, 0x17, 0x3e, - 0xe5, 0xad, 0xbb, 0x0a, 0xe1, 0xce, 0xd1, 0xa1, 0x09, 0x1a, 0xed, 0x4e, 0xc8, 0xc8, 0x1a, 0x5c, - 0xe5, 0x91, 0x95, 0x51, 0x40, 0xd4, 0x8a, 0xa9, 0x0e, 0xa4, 0x01, 0xcb, 0x0e, 0xb5, 0x1c, 0x8c, - 0x91, 0x1a, 0x57, 0x50, 0x51, 0x30, 0x93, 0x33, 0xd9, 0x87, 0x5a, 0xcc, 0xc6, 0xc0, 0xf6, 0xdc, - 0x63, 0x36, 0x32, 0xae, 0xa2, 0x49, 0xb9, 0x73, 0xbd, 0x95, 0xb0, 0xd4, 0x7f, 0xf8, 0xb1, 0xd4, - 0x84, 0x81, 0x15, 0xfd, 0x43, 0xb3, 0x1a, 0x6b, 0x94, 0x98, 0xdc, 0x86, 0x6a, 0xfc, 0x8f, 0x34, - 0x44, 0x51, 0x42, 0x18, 0xad, 0x98, 0xac, 0x59, 0x84, 0x8a, 0x56, 0x28, 0x69, 0xf3, 0xeb, 0x02, - 0x54, 0x8e, 0xfc, 0x88, 0xc3, 0x1e, 0xe5, 0xdc, 0x1a, 0x51, 0x62, 0xc0, 0x92, 0x6f, 0x5d, 0x4c, - 0x3c, 0xcb, 0x91, 0x0c, 0xae, 0x98, 0xf1, 0x91, 0xdc, 0x83, 0x25, 0x87, 0x9e, 0x49, 0xea, 0xca, - 0x92, 0xba, 0x5b, 0x48, 0xdd, 0x3b, 0x4f, 0x41, 0xdd, 0x01, 0x3d, 0x8b, 0x68, 0x2b, 0x22, 0x4a, - 0x44, 0x19, 0xe2, 0x59, 0xbe, 0x2f, 0xf1, 0x56, 0xfe, 0x11, 0x5e, 0xd7, 0xf7, 0x25, 0x1e, 0xa2, - 0x44, 0x78, 0x5d, 0x58, 0x4d, 0x08, 0x3d, 0xa5, 0xc2, 0x72, 0x2c, 0x61, 0x19, 0xeb, 0x92, 0x8f, - 0xb5, 0x29, 0xa5, 0xe6, 0xc3, 0x9e, 0xd6, 0x99, 0xf5, 0x58, 0x18, 0x4b, 0xc8, 0x47, 0x50, 0x8f, - 0xf9, 0x4c, 0x10, 0x36, 0x24, 0xc2, 0xb5, 0x84, 0xd1, 0x14, 0x40, 0x4d, 0xcb, 0x12, 0xff, 0x2e, - 0xd4, 0x1d, 0x5d, 0x93, 0x03, 0x4f, 0x16, 0x25, 0x37, 0xb6, 0x77, 0x0a, 0xe8, 0xbf, 0xd1, 0xd2, - 0xbd, 0x99, 0xad, 0x59, 0xb3, 0xe6, 0x64, 0xce, 0xbc, 0xf9, 0x55, 0x1e, 0x6a, 0xb1, 0xcd, 0xf3, - 0x9f, 0x93, 0xdb, 0x50, 0x9b, 0x21, 0x44, 0x67, 0x64, 0x11, 0x1f, 0xd5, 0x2c, 0x1f, 0xcd, 0x10, - 0x0c, 0x0c, 0x91, 0xd9, 0xb4, 0x6b, 0x0b, 0x76, 0xa6, 0x6a, 0x98, 0x72, 0x1f, 0x99, 0x7a, 0x1c, - 0x2d, 0x8f, 0xb8, 0xb6, 0xfc, 0x54, 0xd7, 0xfe, 0x55, 0x80, 0xeb, 0x07, 0xd4, 0x09, 0xb1, 0x35, - 0x6c, 0xcc, 0xb1, 0xf3, 0xa2, 0xf4, 0xc8, 0x3a, 0x44, 0x6f, 0x03, 0xe6, 0x18, 0x15, 0x39, 0x1e, - 0xaf, 0xe2, 0xe9, 0xd0, 0x89, 0xc4, 0x51, 0xd8, 0x28, 0xae, 0x2a, 0x31, 0x9e, 0x50, 0xfc, 0x9f, - 0x75, 0x54, 0xe1, 0xd2, 0x1d, 0xb5, 0x0d, 0x65, 0x4e, 0x83, 0x33, 0x1a, 0x0c, 0x04, 0x3b, 0xa5, - 0xc6, 0xa6, 0x1c, 0xa2, 0xa0, 0x44, 0x7d, 0x94, 0x90, 0x03, 0x58, 0x0d, 0x74, 0x41, 0x0c, 0x04, - 0x3d, 0xf5, 0x27, 0x08, 0x80, 0x3d, 0x17, 0xc5, 0xb8, 0x39, 0x9b, 0x6c, 0x9d, 0x3f, 0xb3, 0x1e, - 0x7b, 0xf4, 0xb5, 0x43, 0xf3, 0xcf, 0x02, 0x6c, 0xce, 0xd7, 0xd9, 0x97, 0x21, 0xe5, 0xe2, 0xff, - 0x89, 0xf8, 0x6f, 0x26, 0x62, 0x0f, 0xae, 0x59, 0x09, 0xa3, 0x53, 0x88, 0x4d, 0x09, 0xf1, 0xf2, - 0x34, 0x88, 0x29, 0xed, 0x09, 0x16, 0xb1, 0xe6, 0x64, 0xcf, 0x62, 0xc0, 0xfe, 0x74, 0x05, 0x5e, - 0x4b, 0xb7, 0xf6, 0x8b, 0x97, 0xf6, 0xe7, 0xae, 0xc9, 0x9f, 0x71, 0x91, 0xcc, 0xcc, 0x0c, 0x63, - 0x6e, 0x66, 0xf4, 0x16, 0xcf, 0x8c, 0x9d, 0xa4, 0x8c, 0x16, 0x7c, 0x75, 0x1e, 0x31, 0x3c, 0x08, - 0xd4, 0x1f, 0x84, 0x43, 0x6e, 0x07, 0x6c, 0x48, 0x75, 0xf5, 0x34, 0x6b, 0x50, 0x79, 0x20, 0x2c, - 0x11, 0xf2, 0x58, 0xf0, 0x7d, 0x01, 0x8a, 0x4a, 0x42, 0x9a, 0x50, 0x0c, 0xe5, 0xf7, 0x44, 0x16, - 0x56, 0xb9, 0x03, 0xad, 0x68, 0x9d, 0x36, 0x11, 0x8a, 0x9b, 0x5a, 0x43, 0xda, 0x50, 0x51, 0x6f, - 0x83, 0xd0, 0x65, 0x88, 0x20, 0xb7, 0xd5, 0xac, 0xe9, 0x8a, 0x32, 0x38, 0x92, 0x7a, 0x72, 0x03, - 0x57, 0x4d, 0x5d, 0xe9, 0xfa, 0x5b, 0x97, 0xb6, 0x4d, 0x74, 0xe4, 0x2d, 0x28, 0x4f, 0x29, 0xe3, - 0x3a, 0xd1, 0x69, 0xd3, 0xb4, 0x9a, 0xec, 0x41, 0x8a, 0x60, 0x1e, 0xc7, 0xb2, 0x31, 0xe7, 0xb4, - 0x9a, 0xb2, 0xd2, 0x01, 0x7d, 0x08, 0x6b, 0x69, 0x57, 0xcb, 0xb6, 0xa9, 0x8f, 0xed, 0xa6, 0xb3, - 0x9a, 0x76, 0x4e, 0x25, 0x9f, 0x77, 0xb5, 0x19, 0x79, 0x0f, 0x2a, 0x4e, 0xd2, 0xa5, 0xd1, 0x07, - 0x5c, 0xe5, 0xa7, 0x2e, 0xfd, 0xee, 0xd3, 0xc0, 0x8e, 0xd6, 0xfa, 0x09, 0x7a, 0x67, 0xcd, 0xc8, - 0x9b, 0xb0, 0x8a, 0xab, 0xb0, 0x4b, 0x6d, 0x04, 0x19, 0x04, 0x5e, 0x28, 0x68, 0xc0, 0x8d, 0xd7, - 0xe5, 0x52, 0x5e, 0x4f, 0x14, 0xa6, 0x92, 0x93, 0x9b, 0x40, 0xa6, 0xc6, 0x63, 0xcb, 0x75, 0x26, - 0x91, 0xf5, 0x1b, 0xd2, 0x7a, 0x0a, 0xf3, 0x89, 0x56, 0x34, 0x3f, 0x87, 0x2d, 0x6c, 0xb5, 0xf8, - 0x2a, 0x2d, 0x36, 0xe9, 0x88, 0x71, 0xa1, 0xd6, 0xeb, 0x54, 0xeb, 0xe5, 0xd2, 0xad, 0xf7, 0x0a, - 0x80, 0x46, 0x8f, 0x54, 0x79, 0xa9, 0x2a, 0x69, 0xc9, 0xa1, 0xd3, 0xf9, 0x36, 0x0f, 0xc5, 0x7d, - 0x59, 0x76, 0xb8, 0xb9, 0x94, 0xba, 0x9c, 0x7b, 0x36, 0x43, 0x66, 0xc8, 0x7a, 0x5c, 0x8c, 0x99, - 0xf5, 0xa3, 0xb1, 0xe8, 0xbb, 0xb6, 0x9b, 0x7b, 0x3b, 0x47, 0x3e, 0x85, 0x52, 0x52, 0x8c, 0xc4, - 0x88, 0x2d, 0x67, 0xeb, 0xb3, 0xf1, 0xea, 0xb4, 0xce, 0x17, 0x6c, 0x39, 0x88, 0xd5, 0x82, 0xa5, - 0xfb, 0xe1, 0x70, 0xc2, 0xf8, 0x98, 0x2c, 0xba, 0xb3, 0xb1, 0x2c, 0x13, 0xd2, 0xb5, 0x4f, 0x76, - 0x73, 0xd8, 0x57, 0xcb, 0xba, 0x61, 0x28, 0xd9, 0x5e, 0xdc, 0x48, 0x2a, 0x82, 0x27, 0x76, 0x5a, - 0xe7, 0x9b, 0x1c, 0x54, 0x14, 0x2d, 0x3d, 0xcb, 0xc5, 0xbb, 0x02, 0x9c, 0x94, 0x0d, 0x45, 0x37, - 0x0d, 0xe6, 0x13, 0x41, 0x6e, 0xc4, 0x88, 0x8f, 0x4f, 0xd2, 0x34, 0x64, 0xd2, 0x81, 0xd2, 0x5d, - 0x2a, 0x74, 0x5b, 0x26, 0x6c, 0x67, 0x1a, 0xb7, 0x51, 0xcd, 0x8a, 0xf7, 0xeb, 0x3f, 0xfe, 0xbe, - 0x95, 0xfb, 0x19, 0x9f, 0xdf, 0xf0, 0xf9, 0xee, 0x8f, 0xad, 0x97, 0x86, 0x45, 0x39, 0xa0, 0xde, - 0xfd, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x54, 0xb6, 0xf0, 0x3b, 0xaa, 0x0f, 0x00, 0x00, + // 1099 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0x1b, 0xc5, + 0x1b, 0xff, 0x6f, 0xdc, 0x38, 0xc9, 0xe3, 0xf8, 0x25, 0xd3, 0xbc, 0x6c, 0xad, 0x3f, 0xb6, 0x31, + 0x52, 0x65, 0x5e, 0x6a, 0x17, 0xa3, 0x82, 0x22, 0x04, 0x91, 0x43, 0xaa, 0x12, 0x24, 0x87, 0x6a, + 0x9b, 0x70, 0xe0, 0x62, 0x8d, 0x77, 0x9e, 0x38, 0xa3, 0x38, 0xbb, 0xdb, 0x9d, 0x59, 0xa7, 0xf9, + 0x0e, 0x48, 0x1c, 0xe0, 0xc0, 0xd7, 0x41, 0x5c, 0xb8, 0xc1, 0xb9, 0x87, 0x0a, 0x85, 0x0b, 0x17, + 0xbe, 0x03, 0xda, 0xd9, 0xd9, 0xf5, 0xda, 0xae, 0xdb, 0x14, 0xca, 0xa1, 0x15, 0x27, 0xef, 0x3c, + 0xcf, 0xef, 0xf9, 0xcd, 0xf8, 0xf7, 0xbc, 0xcc, 0xc0, 0x47, 0x03, 0x2e, 0x4f, 0x82, 0x7e, 0xd3, + 0x76, 0xcf, 0x5a, 0x87, 0x27, 0x78, 0x78, 0xc2, 0x9d, 0x81, 0x38, 0x40, 0x79, 0xee, 0xfa, 0xa7, + 0x2d, 0x29, 0x9d, 0x16, 0xf5, 0x78, 0xab, 0xef, 0xbb, 0xa7, 0xe8, 0xeb, 0x9f, 0xa6, 0xe7, 0xbb, + 0xd2, 0x25, 0xd9, 0x68, 0x55, 0xbe, 0x95, 0x22, 0x18, 0xb8, 0x03, 0xb7, 0xa5, 0xdc, 0xfd, 0xe0, + 0x58, 0xad, 0xd4, 0x42, 0x7d, 0x45, 0x61, 0x13, 0xf0, 0xb9, 0xfb, 0x51, 0x8f, 0x6b, 0xf8, 0xc7, + 0x57, 0x81, 0x2b, 0xa8, 0xed, 0x0e, 0x93, 0x0f, 0x1d, 0xbc, 0x7d, 0x95, 0xe0, 0x01, 0x95, 0x78, + 0x4e, 0x2f, 0xe2, 0xdf, 0x28, 0xb4, 0xfe, 0xd3, 0x02, 0x14, 0xf6, 0xdc, 0x73, 0x67, 0xc8, 0x9d, + 0xd3, 0x2f, 0x3d, 0xc9, 0x5d, 0x87, 0x54, 0x00, 0x38, 0x43, 0x47, 0xf2, 0x63, 0x8e, 0xbe, 0x69, + 0xd4, 0x8c, 0xc6, 0x8a, 0x95, 0xb2, 0x90, 0xaf, 0x21, 0xa7, 0x39, 0x7a, 0x18, 0x70, 0x73, 0xa1, + 0x66, 0x34, 0x56, 0x77, 0xb7, 0x1f, 0x3f, 0xa9, 0xde, 0x79, 0xde, 0x31, 0x6c, 0xd7, 0xc7, 0x96, + 0xbc, 0xf0, 0x50, 0x34, 0xef, 0x45, 0x0c, 0x77, 0x8f, 0xf6, 0x2d, 0xd0, 0x6c, 0x77, 0x03, 0x4e, + 0xd6, 0x61, 0x51, 0x84, 0x28, 0x33, 0x53, 0x33, 0x1a, 0x79, 0x2b, 0x5a, 0x90, 0x32, 0x2c, 0x33, + 0xa4, 0x6c, 0xc8, 0x1d, 0x34, 0xaf, 0xd5, 0x8c, 0x46, 0xc6, 0x4a, 0xd6, 0x64, 0x17, 0x8a, 0xb1, + 0x1a, 0x3d, 0xdb, 0x75, 0x8e, 0xf9, 0xc0, 0x5c, 0xac, 0x19, 0x8d, 0x5c, 0xfb, 0x46, 0x33, 0x51, + 0xe9, 0xf0, 0xd1, 0x67, 0xca, 0x13, 0xf8, 0x34, 0xfc, 0x87, 0x56, 0x21, 0xf6, 0x44, 0x66, 0xb2, + 0x03, 0x85, 0xf8, 0x1f, 0x69, 0x8a, 0xac, 0xa2, 0x30, 0x9b, 0xb1, 0x58, 0xd3, 0x0c, 0x79, 0xed, + 0x88, 0xac, 0xf5, 0x6f, 0x33, 0x90, 0x3f, 0xf2, 0x42, 0x0d, 0xbb, 0x28, 0x04, 0x1d, 0x20, 0x31, + 0x61, 0xc9, 0xa3, 0x17, 0x43, 0x97, 0x32, 0xa5, 0xe0, 0xaa, 0x15, 0x2f, 0xc9, 0x01, 0x2c, 0x31, + 0x1c, 0x29, 0xe9, 0x72, 0x4a, 0xba, 0x3b, 0x8f, 0x9f, 0x54, 0xdf, 0x7f, 0x01, 0xe9, 0xf6, 0x70, + 0x14, 0xca, 0x96, 0x65, 0x38, 0x0a, 0x25, 0x3b, 0x80, 0x25, 0xea, 0x79, 0x8a, 0x6f, 0xf5, 0x6f, + 0xf1, 0x75, 0x3c, 0x4f, 0xf1, 0x51, 0xcf, 0x0b, 0xf9, 0x3a, 0xb0, 0x96, 0x08, 0x7a, 0x86, 0x92, + 0x32, 0x2a, 0xa9, 0xb9, 0xa1, 0xf4, 0x58, 0x1f, 0x4b, 0x6a, 0x3d, 0xea, 0x6a, 0x9f, 0x55, 0x8a, + 0x8d, 0xb1, 0x85, 0x7c, 0x0a, 0xa5, 0x58, 0xcf, 0x84, 0x61, 0x53, 0x31, 0x5c, 0x4f, 0x14, 0x4d, + 0x11, 0x14, 0xb5, 0x2d, 0x89, 0xef, 0x40, 0x89, 0xe9, 0x9a, 0xec, 0xb9, 0xaa, 0x28, 0x85, 0x59, + 0xad, 0x65, 0x1a, 0xb9, 0xf6, 0x66, 0x53, 0xf7, 0xe6, 0x64, 0xcd, 0x5a, 0x45, 0x36, 0xb1, 0x16, + 0xf5, 0x6f, 0x16, 0xa0, 0x18, 0x63, 0x5e, 0xfd, 0x9c, 0xec, 0x40, 0x71, 0x4a, 0x10, 0x9d, 0x91, + 0x79, 0x7a, 0x14, 0x26, 0xf5, 0xa8, 0x07, 0x60, 0xee, 0xe1, 0x88, 0xdb, 0xd8, 0xb1, 0x25, 0x1f, + 0x45, 0x35, 0x8c, 0xc2, 0x73, 0x1d, 0xf1, 0x2c, 0x59, 0x9e, 0xb2, 0x6d, 0xee, 0x85, 0xb6, 0xfd, + 0x33, 0x03, 0x37, 0xf6, 0x90, 0x05, 0xde, 0x90, 0xdb, 0x54, 0x22, 0x7b, 0x5d, 0x7a, 0x64, 0x03, + 0xc2, 0xaf, 0x1e, 0x67, 0x66, 0x5e, 0x8d, 0xc7, 0x45, 0xea, 0x79, 0xfb, 0x2c, 0x34, 0x87, 0xc7, + 0xe6, 0xcc, 0x2c, 0x44, 0x66, 0x86, 0xa3, 0x7d, 0xf6, 0xef, 0x75, 0x54, 0xe6, 0xca, 0x1d, 0x55, + 0x85, 0x9c, 0x40, 0x7f, 0x84, 0x7e, 0x4f, 0xf2, 0x33, 0x34, 0xb7, 0xd4, 0x10, 0x85, 0xc8, 0x74, + 0xc8, 0xcf, 0x90, 0xec, 0xc1, 0x9a, 0xaf, 0x0b, 0xa2, 0x27, 0xf1, 0xcc, 0x1b, 0x52, 0x89, 0x66, + 0x55, 0x9d, 0x71, 0x6b, 0x3a, 0xd9, 0x3a, 0x7f, 0x56, 0x29, 0x8e, 0x38, 0xd4, 0x01, 0xf5, 0x3f, + 0x32, 0xb0, 0x35, 0x5b, 0x67, 0x0f, 0x03, 0x14, 0xf2, 0xbf, 0x89, 0xf8, 0x4f, 0x26, 0x62, 0x17, + 0xae, 0xd3, 0x44, 0xd1, 0x31, 0xc5, 0x96, 0xa2, 0xf8, 0xff, 0xf8, 0x10, 0x63, 0xd9, 0x13, 0x2e, + 0x42, 0x67, 0x6c, 0x2f, 0x63, 0xc0, 0xfe, 0x72, 0x0d, 0xde, 0x4a, 0xb7, 0xf6, 0xeb, 0x97, 0xf6, + 0x57, 0xae, 0xc9, 0x5f, 0x72, 0x91, 0x4c, 0xcd, 0x0c, 0x73, 0x66, 0x66, 0x74, 0xe7, 0xcf, 0x8c, + 0x5a, 0x52, 0x46, 0x73, 0x6e, 0x9d, 0xa7, 0x0c, 0x0f, 0x02, 0xa5, 0x07, 0x41, 0x5f, 0xd8, 0x3e, + 0xef, 0xa3, 0xae, 0x9e, 0x7a, 0x11, 0xf2, 0x0f, 0x24, 0x95, 0x81, 0x88, 0x0d, 0x3f, 0x66, 0x20, + 0x1b, 0x59, 0x48, 0x1d, 0xb2, 0x81, 0xba, 0x4f, 0x54, 0x61, 0xe5, 0xda, 0xd0, 0x0c, 0x9f, 0xd3, + 0x16, 0x95, 0x28, 0x2c, 0xed, 0x21, 0x2d, 0xc8, 0x47, 0x5f, 0xbd, 0xc0, 0xe1, 0x0f, 0x03, 0x54, + 0xaf, 0xd5, 0x49, 0xe8, 0x6a, 0x04, 0x38, 0x52, 0x7e, 0x72, 0x13, 0x96, 0xe3, 0x4a, 0xd7, 0x77, + 0x5d, 0x1a, 0x9b, 0xf8, 0xc8, 0x7b, 0x90, 0x1b, 0x4b, 0x26, 0x74, 0xa2, 0xd3, 0xd0, 0xb4, 0x9b, + 0x6c, 0x43, 0x4a, 0x60, 0x11, 0x9f, 0x65, 0x73, 0x26, 0x68, 0x2d, 0x85, 0xd2, 0x07, 0xfa, 0x04, + 0xd6, 0xd3, 0xa1, 0xd4, 0xb6, 0xd1, 0x93, 0xc8, 0x74, 0x56, 0xd3, 0xc1, 0xa9, 0xe4, 0x8b, 0x8e, + 0x86, 0x91, 0x0f, 0x21, 0xcf, 0x92, 0x2e, 0x0d, 0x2f, 0xf0, 0x28, 0x3f, 0x25, 0x15, 0x77, 0x1f, + 0x7d, 0x3b, 0x7c, 0xd6, 0x0f, 0x51, 0x58, 0x93, 0x30, 0xf2, 0x2e, 0xac, 0xd9, 0xae, 0xe3, 0xa0, + 0x2d, 0x91, 0xf5, 0x7c, 0x37, 0x90, 0xe8, 0x0b, 0xf3, 0x6d, 0xf5, 0x28, 0x2f, 0x25, 0x0e, 0x2b, + 0xb2, 0x93, 0x5b, 0x40, 0xc6, 0xe0, 0x13, 0xea, 0xb0, 0x61, 0x88, 0x7e, 0x47, 0xa1, 0xc7, 0x34, + 0x9f, 0x6b, 0x47, 0xfd, 0x2b, 0xa8, 0x74, 0xbc, 0x64, 0x2b, 0x6d, 0xb6, 0x70, 0xc0, 0x85, 0x8c, + 0x9e, 0xd7, 0xa9, 0xd6, 0x33, 0xd2, 0xad, 0xf7, 0x06, 0x80, 0x66, 0x0f, 0x5d, 0x0b, 0xca, 0xb5, + 0xa2, 0x2d, 0xfb, 0xac, 0xfd, 0xfd, 0x02, 0x64, 0x77, 0x55, 0xd9, 0x91, 0x1d, 0x58, 0xe9, 0x08, + 0xe1, 0xda, 0x9c, 0x4a, 0x24, 0x1b, 0x71, 0x31, 0x4e, 0x3c, 0x3f, 0xca, 0xf3, 0xee, 0xb5, 0x86, + 0x71, 0xdb, 0x20, 0x5f, 0xc0, 0x4a, 0x52, 0x8c, 0xc4, 0x8c, 0x91, 0xd3, 0xf5, 0x59, 0x7e, 0x73, + 0x5c, 0xe7, 0x73, 0x5e, 0x39, 0xb7, 0x0d, 0xd2, 0x84, 0xa5, 0xfb, 0x41, 0x7f, 0xc8, 0xc5, 0x09, + 0x99, 0xb7, 0x67, 0x79, 0x59, 0x25, 0xa4, 0x63, 0x9f, 0x36, 0x0c, 0xd2, 0x85, 0x65, 0xdd, 0x30, + 0x48, 0xaa, 0xf3, 0x1b, 0x29, 0x3a, 0xc1, 0x73, 0x3b, 0xad, 0xfd, 0x9d, 0x01, 0xf9, 0x48, 0x96, + 0x2e, 0x75, 0xe8, 0x00, 0x7d, 0x72, 0x00, 0xe5, 0x48, 0x6e, 0xf4, 0x67, 0x13, 0x41, 0x6e, 0xc6, + 0x8c, 0xcf, 0x4e, 0xd2, 0xf8, 0xc8, 0xa4, 0x0d, 0x2b, 0xf7, 0x50, 0xea, 0xb6, 0x4c, 0xd4, 0x9e, + 0x68, 0xdc, 0x72, 0x61, 0xd2, 0xbc, 0x5b, 0xfa, 0xf9, 0xb2, 0x62, 0xfc, 0x7a, 0x59, 0x31, 0x7e, + 0xbb, 0xac, 0x18, 0x3f, 0xfc, 0x5e, 0xf9, 0x5f, 0x3f, 0xab, 0x06, 0xd4, 0x07, 0x7f, 0x05, 0x00, + 0x00, 0xff, 0xff, 0x54, 0xb6, 0xf0, 0x3b, 0xaa, 0x0f, 0x00, 0x00, } diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index d436bc388..7e3106557 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -1681,40 +1681,40 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 550 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x5d, 0x8f, 0xd2, 0x40, - 0x14, 0xdd, 0x82, 0xbb, 0xd2, 0x5b, 0x96, 0x6d, 0x46, 0x89, 0x0d, 0x89, 0x2b, 0xf6, 0xc5, 0xf5, - 0x61, 0x21, 0x61, 0xa3, 0x6f, 0xc6, 0x60, 0x40, 0x25, 0xba, 0x2b, 0x69, 0xd0, 0xf8, 0x46, 0x86, - 0xf6, 0x0a, 0x13, 0x60, 0x8a, 0x9d, 0x29, 0x86, 0x57, 0x7f, 0x85, 0x6f, 0xbe, 0xf8, 0x63, 0x7c, - 0xf4, 0x27, 0x18, 0xfd, 0x23, 0x4e, 0xe9, 0x07, 0x25, 0xbb, 0x44, 0xd7, 0x87, 0x49, 0x3a, 0x67, - 0xce, 0xb9, 0xf7, 0xdc, 0xc3, 0x0c, 0xf0, 0x64, 0xcc, 0xe4, 0x24, 0x1c, 0x35, 0x5c, 0x7f, 0xde, - 0x1c, 0x4c, 0x70, 0x30, 0x61, 0x7c, 0x2c, 0x2e, 0x50, 0x7e, 0xf2, 0x83, 0x69, 0x53, 0x4a, 0xde, - 0xa4, 0x0b, 0xd6, 0xf4, 0x98, 0x70, 0xfd, 0x25, 0x06, 0xab, 0xcd, 0x57, 0x63, 0x11, 0xf8, 0xd2, - 0x27, 0x7a, 0x06, 0xd4, 0x4e, 0xff, 0xa5, 0x92, 0x5a, 0xb1, 0xd2, 0xfe, 0xac, 0x41, 0xe9, 0x1c, - 0x25, 0xf5, 0xa8, 0xa4, 0xe4, 0x21, 0x14, 0xa7, 0xb8, 0xb2, 0xb4, 0xba, 0x76, 0x52, 0x69, 0xdd, - 0x69, 0x6c, 0xba, 0xa4, 0x8c, 0xc6, 0x2b, 0x5c, 0x39, 0x11, 0x87, 0xdc, 0x86, 0xfd, 0x25, 0x9d, - 0x85, 0x68, 0x15, 0x14, 0xb9, 0xec, 0xc4, 0x1b, 0xfb, 0x11, 0x14, 0x15, 0x83, 0xe8, 0xb0, 0xff, - 0x66, 0xf0, 0xb2, 0xeb, 0x98, 0x7b, 0x04, 0xe0, 0xa0, 0xef, 0x74, 0x9f, 0xf7, 0xde, 0x9b, 0x1a, - 0x31, 0xe0, 0x66, 0xbb, 0xdf, 0x1f, 0x76, 0xdf, 0xf6, 0xcc, 0x42, 0x74, 0x10, 0x6d, 0x7a, 0x1d, - 0xb3, 0x68, 0x7f, 0x2d, 0x40, 0xb9, 0xcd, 0xb9, 0x1f, 0x72, 0x17, 0xe7, 0xc8, 0x25, 0xa9, 0x40, - 0x81, 0x79, 0x6b, 0x1f, 0xba, 0xa3, 0xbe, 0xc8, 0x7d, 0x28, 0x0b, 0x0c, 0x96, 0xcc, 0xc5, 0x21, - 0xa7, 0xf3, 0xb8, 0xa9, 0xee, 0x18, 0x09, 0x76, 0xa1, 0x20, 0xf2, 0x00, 0x8e, 0x52, 0x8a, 0xb2, - 0x2c, 0x98, 0xcf, 0xad, 0xe2, 0x9a, 0x55, 0x49, 0xe0, 0x77, 0x31, 0x4a, 0xea, 0x60, 0x78, 0x28, - 0xdc, 0x80, 0x2d, 0x64, 0x44, 0xba, 0x11, 0x97, 0xca, 0x41, 0xe4, 0x1e, 0x18, 0x1c, 0xe5, 0x90, - 0x7a, 0x5e, 0x80, 0x42, 0x58, 0xc6, 0x9a, 0x01, 0x0a, 0x6a, 0xc7, 0x08, 0xb9, 0x0b, 0xb0, 0x08, - 0x47, 0x33, 0xe6, 0x0e, 0xa3, 0xb8, 0xca, 0xeb, 0x73, 0x3d, 0x46, 0xa2, 0xf1, 0x55, 0x07, 0x17, - 0x03, 0xc9, 0x3e, 0x30, 0x97, 0x4a, 0xb4, 0x0e, 0xe3, 0x0e, 0x39, 0x88, 0x34, 0xa1, 0x34, 0x4f, - 0x22, 0xb5, 0xaa, 0xf5, 0xe2, 0x89, 0xd1, 0xba, 0x75, 0x45, 0xda, 0x4e, 0x46, 0xb2, 0x5b, 0x70, - 0xf8, 0x42, 0xf5, 0x9f, 0xcd, 0x1c, 0xfc, 0x18, 0xa2, 0x90, 0x97, 0x12, 0xd1, 0x2e, 0x25, 0x62, - 0x3f, 0x05, 0x50, 0x9a, 0x54, 0x70, 0xfd, 0x48, 0xed, 0x10, 0x8e, 0x32, 0x2b, 0xff, 0x5d, 0x65, - 0x6b, 0xd6, 0x28, 0xca, 0xbf, 0xce, 0xfa, 0x1a, 0xaa, 0xf9, 0xcb, 0x20, 0x1c, 0x14, 0x0b, 0x9f, - 0x0b, 0x24, 0x67, 0x50, 0x4a, 0x0a, 0x0b, 0x65, 0x21, 0x4a, 0x2d, 0x7f, 0x47, 0xf3, 0x1a, 0x27, - 0x23, 0xb6, 0xbe, 0x15, 0x40, 0xef, 0xa4, 0x24, 0x72, 0x0a, 0xa5, 0x94, 0x47, 0x76, 0x89, 0x6b, - 0xa5, 0x46, 0xf4, 0x3e, 0xda, 0xee, 0x94, 0x74, 0xe0, 0x20, 0x8e, 0x9d, 0x58, 0x39, 0xf2, 0xd6, - 0x2f, 0x51, 0xab, 0xef, 0x28, 0xb3, 0xf1, 0xad, 0x5e, 0x85, 0x92, 0x90, 0xea, 0x76, 0x89, 0x54, - 0xbf, 0xcb, 0x86, 0x1a, 0xd7, 0x50, 0x17, 0x2e, 0x7b, 0x9c, 0xb5, 0xab, 0x52, 0x4b, 0x6a, 0x6c, - 0x1c, 0x3f, 0x86, 0x4a, 0x07, 0x67, 0x28, 0xf1, 0x7a, 0xba, 0x16, 0x01, 0x33, 0x4b, 0xe9, 0x9c, - 0x72, 0x3a, 0xc6, 0xe0, 0x99, 0xf9, 0xfd, 0xd7, 0xb1, 0xf6, 0x43, 0xad, 0x9f, 0x6a, 0x7d, 0xf9, - 0x7d, 0xbc, 0x37, 0x3a, 0x58, 0xff, 0x69, 0x9c, 0xfd, 0x09, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x28, - 0x1d, 0x06, 0xaf, 0x04, 0x00, 0x00, + // 556 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xc1, 0x6e, 0xd3, 0x40, + 0x10, 0xad, 0x6d, 0x5a, 0xe2, 0x71, 0x9a, 0x5a, 0x0b, 0x15, 0x56, 0x24, 0x82, 0xf1, 0x85, 0x72, + 0x68, 0x22, 0xa5, 0x2a, 0x37, 0x84, 0x8c, 0x52, 0x4a, 0x55, 0x5a, 0x2a, 0xab, 0x20, 0x6e, 0xd1, + 0xd6, 0x1e, 0x92, 0x55, 0x92, 0xb5, 0xf1, 0xae, 0x83, 0x72, 0xe5, 0x2b, 0xb8, 0x71, 0xe1, 0x63, + 0x38, 0xf2, 0x09, 0x28, 0xfc, 0x08, 0xb2, 0x1d, 0x3b, 0x8e, 0xda, 0x08, 0xca, 0xcd, 0xfb, 0xf6, + 0xbd, 0x37, 0x33, 0xcf, 0xbb, 0x0b, 0xcf, 0x07, 0x4c, 0x0e, 0x93, 0xab, 0xb6, 0x1f, 0x4e, 0x3a, + 0x97, 0x43, 0xbc, 0x1c, 0x32, 0x3e, 0x10, 0xe7, 0x28, 0x3f, 0x87, 0xf1, 0xa8, 0x23, 0x25, 0xef, + 0xd0, 0x88, 0x75, 0x02, 0x26, 0xfc, 0x70, 0x8a, 0xf1, 0x6c, 0xf9, 0xd5, 0x8e, 0xe2, 0x50, 0x86, + 0x44, 0x2f, 0x81, 0xe6, 0xfe, 0xbf, 0x38, 0xd1, 0x88, 0xe5, 0x4a, 0xe7, 0x8b, 0x02, 0xb5, 0x33, + 0x94, 0x34, 0xa0, 0x92, 0x92, 0xa7, 0xa0, 0x8d, 0x70, 0x66, 0x29, 0xb6, 0xb2, 0xd7, 0xe8, 0x3e, + 0x68, 0x2f, 0xab, 0x14, 0x8c, 0xf6, 0x29, 0xce, 0xbc, 0x94, 0x43, 0xee, 0xc3, 0xe6, 0x94, 0x8e, + 0x13, 0xb4, 0x54, 0x5b, 0xd9, 0xab, 0x7b, 0xf9, 0xc2, 0x39, 0x04, 0xed, 0x14, 0x67, 0x44, 0x87, + 0xcd, 0xb7, 0x97, 0xaf, 0x8f, 0x3c, 0x73, 0x83, 0x00, 0x6c, 0x5d, 0x78, 0x47, 0xaf, 0x4e, 0x3e, + 0x98, 0x0a, 0x31, 0xe0, 0xae, 0x7b, 0x71, 0xd1, 0x3f, 0x7a, 0x77, 0x62, 0xaa, 0xe9, 0x46, 0xba, + 0x38, 0xe9, 0x99, 0x9a, 0xf3, 0x4d, 0x85, 0xba, 0xcb, 0x79, 0x98, 0x70, 0x1f, 0x27, 0xc8, 0x25, + 0x69, 0x80, 0xca, 0x82, 0xac, 0x0f, 0xdd, 0x53, 0x59, 0x40, 0x1e, 0x43, 0x5d, 0x60, 0x3c, 0x65, + 0x3e, 0xf6, 0x39, 0x9d, 0xe4, 0x45, 0x75, 0xcf, 0x58, 0x60, 0xe7, 0x74, 0x82, 0xe4, 0x09, 0xec, + 0x14, 0x94, 0x29, 0xc6, 0x82, 0x85, 0xdc, 0xd2, 0x32, 0x56, 0x63, 0x01, 0xbf, 0xcf, 0x51, 0x62, + 0x83, 0x11, 0xa0, 0xf0, 0x63, 0x16, 0xc9, 0x94, 0x74, 0x27, 0xb7, 0xaa, 0x40, 0xe4, 0x11, 0x18, + 0x1c, 0x65, 0x9f, 0x06, 0x41, 0x8c, 0x42, 0x58, 0x46, 0xc6, 0x00, 0x8e, 0xd2, 0xcd, 0x11, 0xf2, + 0x10, 0x20, 0x4a, 0xae, 0xc6, 0xcc, 0xef, 0xa7, 0x71, 0xd5, 0xb3, 0x7d, 0x3d, 0x47, 0xd2, 0xf1, + 0x6d, 0x30, 0x7c, 0x8c, 0x25, 0xfb, 0xc8, 0x7c, 0x2a, 0xd1, 0xda, 0xce, 0x2b, 0x54, 0x20, 0xd2, + 0x81, 0xda, 0x64, 0x11, 0xa9, 0xb5, 0x6b, 0x6b, 0x7b, 0x46, 0xf7, 0xde, 0x0d, 0x69, 0x7b, 0x25, + 0xc9, 0xe9, 0xc2, 0xf6, 0x31, 0x4a, 0x77, 0x3c, 0xf6, 0xf0, 0x53, 0x82, 0x42, 0x5e, 0x4b, 0x44, + 0xb9, 0x96, 0x88, 0xf3, 0x02, 0xe0, 0x18, 0x65, 0x21, 0xb8, 0x7d, 0xa4, 0x4e, 0x02, 0x3b, 0x65, + 0x2b, 0xff, 0xed, 0xb2, 0x32, 0x6b, 0x1a, 0xe5, 0x5f, 0x67, 0x7d, 0x03, 0xbb, 0xd5, 0xc3, 0x20, + 0x3c, 0x14, 0x51, 0xc8, 0x05, 0x92, 0x03, 0xa8, 0x2d, 0x8c, 0x85, 0xa5, 0x64, 0xa9, 0x55, 0xcf, + 0x68, 0x55, 0xe3, 0x95, 0xc4, 0xee, 0x77, 0x15, 0xf4, 0x5e, 0x41, 0x22, 0xfb, 0x50, 0x2b, 0x78, + 0x64, 0x9d, 0xb8, 0x59, 0x6b, 0xa7, 0xf7, 0xc3, 0xf5, 0x47, 0xa4, 0x07, 0x5b, 0x79, 0xec, 0xc4, + 0xaa, 0x90, 0x57, 0xfe, 0x44, 0xd3, 0x5e, 0x63, 0xb3, 0xec, 0xfb, 0x10, 0xb4, 0x63, 0x94, 0x64, + 0x77, 0xd5, 0xa2, 0xd0, 0xaf, 0x6b, 0x83, 0x1c, 0x80, 0xe1, 0x06, 0x41, 0x79, 0x39, 0x9b, 0x37, + 0xa5, 0xb6, 0xf0, 0x58, 0x76, 0xfc, 0x0c, 0x1a, 0x3d, 0x1c, 0xa3, 0xc4, 0xdb, 0xe9, 0xba, 0x04, + 0xcc, 0x32, 0xa5, 0x33, 0xca, 0xe9, 0x00, 0xe3, 0x97, 0xe6, 0x8f, 0x79, 0x4b, 0xf9, 0x39, 0x6f, + 0x29, 0xbf, 0xe6, 0x2d, 0xe5, 0xeb, 0xef, 0xd6, 0xc6, 0xd5, 0x56, 0xf6, 0x68, 0x1c, 0xfc, 0x09, + 0x00, 0x00, 0xff, 0xff, 0x2c, 0x28, 0x1d, 0x06, 0xaf, 0x04, 0x00, 0x00, } diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 627689e93..2f7fbc0bf 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -1582,43 +1582,44 @@ func init() { } var fileDescriptorGateway = []byte{ - // 607 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x54, 0xcd, 0x6e, 0x13, 0x3d, - 0x14, 0xfd, 0x92, 0xf4, 0xd7, 0xe9, 0xb4, 0x95, 0xdb, 0xe6, 0x33, 0x15, 0x6a, 0x4b, 0x90, 0x50, - 0xf9, 0xcb, 0x48, 0xa0, 0x2e, 0xba, 0x6d, 0xa9, 0xaa, 0x2e, 0xa0, 0xc8, 0x2d, 0x1b, 0x36, 0x91, - 0x3b, 0xf1, 0x4c, 0xac, 0x4c, 0xec, 0xc1, 0xe3, 0x69, 0x52, 0x9e, 0x84, 0x77, 0xe1, 0x05, 0x58, - 0x22, 0xb1, 0x63, 0x81, 0x10, 0x48, 0x3c, 0x07, 0xf6, 0x9d, 0x9f, 0x4e, 0x91, 0xa0, 0x8b, 0x51, - 0xee, 0x39, 0xe7, 0xda, 0xd7, 0xf7, 0x9e, 0xab, 0xa0, 0xfd, 0x48, 0x98, 0x61, 0x76, 0xd1, 0x0b, - 0xd4, 0xd8, 0x3f, 0x1f, 0xf2, 0xf3, 0xa1, 0x90, 0x51, 0xfa, 0x8a, 0x9b, 0x89, 0xd2, 0x23, 0xdf, - 0x18, 0xe9, 0xb3, 0x44, 0xf8, 0x11, 0x33, 0x7c, 0xc2, 0xae, 0xca, 0xdf, 0x5e, 0xa2, 0x95, 0x51, - 0x78, 0xbe, 0x80, 0x9b, 0x4f, 0x6b, 0x77, 0x44, 0x2a, 0x52, 0x3e, 0xe8, 0x17, 0x59, 0x08, 0x08, - 0x00, 0x44, 0xf9, 0xb9, 0xee, 0x04, 0xb5, 0x8f, 0x5f, 0x9f, 0xbd, 0xe4, 0x86, 0x0d, 0x98, 0x61, - 0x18, 0xa3, 0x19, 0x23, 0xc6, 0x9c, 0x34, 0x76, 0x1a, 0xbb, 0x2d, 0x0a, 0x31, 0xde, 0x44, 0x0b, - 0x31, 0x33, 0xc2, 0x64, 0x03, 0x4e, 0x9a, 0x96, 0x6f, 0xd2, 0x0a, 0xe3, 0xbb, 0x68, 0x31, 0x56, - 0x32, 0xca, 0xc5, 0x16, 0x88, 0xd7, 0x84, 0x3b, 0xc9, 0xe2, 0xe2, 0xe4, 0x8c, 0x15, 0x67, 0x69, - 0x85, 0xbb, 0x1f, 0x9b, 0x08, 0xd1, 0x69, 0x55, 0xf8, 0x2d, 0x6a, 0x17, 0x1d, 0xf4, 0x79, 0x26, - 0xa0, 0xfe, 0xd2, 0xc1, 0xfe, 0xd7, 0x6f, 0xdb, 0x7b, 0xb7, 0xcd, 0x24, 0x50, 0x9a, 0xfb, 0xe6, - 0x2a, 0xe1, 0x69, 0xef, 0x38, 0xbf, 0xe1, 0xe8, 0xcd, 0x09, 0x45, 0xc5, 0x6d, 0x47, 0x99, 0x70, - 0x8f, 0x74, 0x8d, 0xa4, 0x86, 0x8d, 0x13, 0xd2, 0xb6, 0x37, 0x7b, 0xf4, 0x9a, 0xa8, 0x5a, 0x5e, - 0xaa, 0xb5, 0x7c, 0x07, 0x2d, 0xe8, 0xb0, 0x1f, 0x0c, 0x99, 0x90, 0x64, 0x03, 0x0e, 0xcc, 0xeb, - 0xf0, 0xd0, 0x41, 0x4c, 0xd0, 0xbc, 0xe5, 0xa5, 0xe4, 0x31, 0xe9, 0xe4, 0x4a, 0x01, 0x5d, 0x99, - 0x50, 0xf3, 0x77, 0x19, 0x97, 0xc1, 0x15, 0xd9, 0xb6, 0xda, 0x0c, 0xbd, 0x26, 0x5c, 0x19, 0x9d, - 0xa6, 0x82, 0xec, 0xc0, 0x90, 0x20, 0xc6, 0xab, 0xa8, 0x95, 0x4a, 0x4d, 0xee, 0x01, 0xe5, 0x42, - 0xfc, 0x00, 0xb5, 0xa2, 0x24, 0x25, 0x0f, 0x2d, 0xd3, 0x7e, 0xb6, 0xde, 0x2b, 0x3d, 0xae, 0x59, - 0x44, 0x5d, 0x42, 0xf7, 0x57, 0x03, 0xad, 0x9c, 0x4f, 0x0f, 0x95, 0x0c, 0x45, 0x94, 0x69, 0xeb, - 0x86, 0x92, 0xb7, 0xb4, 0xf9, 0x8f, 0x96, 0x6e, 0x3c, 0xbc, 0xf3, 0xe7, 0xc3, 0xd7, 0xd1, 0x6c, - 0xa2, 0x26, 0x5c, 0x93, 0xff, 0xc1, 0xc1, 0x1c, 0xe0, 0x3d, 0xd4, 0x49, 0x54, 0xcc, 0xb4, 0x78, - 0x0f, 0xc5, 0xfb, 0x42, 0x5e, 0x72, 0x9d, 0xda, 0x08, 0x3a, 0x5f, 0xa0, 0x1b, 0x75, 0xf5, 0xa4, - 0x14, 0xb1, 0x8f, 0xd6, 0xaa, 0x9b, 0xfb, 0x03, 0x7e, 0x29, 0x40, 0x87, 0xa1, 0x78, 0x14, 0x57, - 0xd2, 0x8b, 0x52, 0xe9, 0x7e, 0x69, 0xa2, 0xb9, 0x33, 0xc3, 0x4c, 0x96, 0xde, 0xec, 0xaf, 0xf1, - 0x37, 0x1b, 0x9b, 0x35, 0x1b, 0x97, 0x51, 0x53, 0xb8, 0x51, 0xb4, 0x76, 0x17, 0xa9, 0x8d, 0xdc, - 0x3e, 0x26, 0x76, 0x75, 0x43, 0xa5, 0xc7, 0x60, 0xf7, 0x22, 0xad, 0x30, 0xbe, 0x8f, 0xbc, 0x40, - 0x49, 0xc3, 0x02, 0xd3, 0xe7, 0x63, 0x26, 0x62, 0xe2, 0x41, 0xc2, 0x52, 0x41, 0x1e, 0x39, 0x0e, - 0xef, 0xa0, 0xf6, 0x80, 0xa7, 0x81, 0x16, 0x09, 0x3c, 0x7b, 0x19, 0x52, 0xea, 0x14, 0xee, 0xa0, - 0x39, 0xcd, 0x23, 0x27, 0xae, 0x80, 0x58, 0xa0, 0xd2, 0xd8, 0x8d, 0x5b, 0x8c, 0x75, 0x2b, 0xa1, - 0x8d, 0x81, 0x21, 0x7a, 0xd4, 0x85, 0x78, 0x0d, 0xcd, 0xea, 0xa9, 0x9d, 0x2f, 0x2c, 0x85, 0x67, - 0x37, 0x67, 0x7a, 0x22, 0x0b, 0x52, 0x8d, 0xc8, 0xa3, 0x92, 0x3c, 0x1d, 0x39, 0xd2, 0x40, 0xe6, - 0xe3, 0x9c, 0x34, 0x45, 0xa6, 0x81, 0xcc, 0x27, 0x25, 0x79, 0x3a, 0x3a, 0x58, 0xfd, 0xf4, 0x63, - 0xab, 0xf1, 0xd9, 0x7e, 0xdf, 0xed, 0xf7, 0xe1, 0xe7, 0xd6, 0x7f, 0x17, 0x73, 0xf0, 0x77, 0xf0, - 0xfc, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x59, 0xd6, 0x7d, 0x83, 0x04, 0x00, 0x00, + // 611 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x54, 0x4d, 0x6f, 0xd3, 0x30, + 0x18, 0x26, 0x69, 0xf7, 0xe5, 0xae, 0xdb, 0xe4, 0x6d, 0xc5, 0x4c, 0xa8, 0x0b, 0x45, 0x42, 0xe1, + 0xab, 0x91, 0x40, 0x3b, 0xec, 0xba, 0x31, 0x4d, 0x3b, 0xc0, 0x90, 0x37, 0x2e, 0x5c, 0x2a, 0x2f, + 0x75, 0x53, 0xab, 0xa9, 0x1d, 0x1c, 0x67, 0xed, 0xf8, 0x25, 0xfc, 0x17, 0xfe, 0x00, 0x47, 0x24, + 0x6e, 0x1c, 0x10, 0x1a, 0x12, 0xbf, 0x03, 0xf9, 0x6d, 0x92, 0x65, 0x48, 0xd0, 0x53, 0xde, 0xe7, + 0x79, 0x5e, 0xfb, 0xfd, 0x78, 0xac, 0xa0, 0xfd, 0x48, 0x98, 0x61, 0x76, 0xd1, 0x0d, 0xd5, 0x38, + 0x38, 0x1f, 0xf2, 0xf3, 0xa1, 0x90, 0x51, 0xfa, 0x86, 0x9b, 0x89, 0xd2, 0xa3, 0xc0, 0x18, 0x19, + 0xb0, 0x44, 0x04, 0x11, 0x33, 0x7c, 0xc2, 0xae, 0x8a, 0x6f, 0x37, 0xd1, 0xca, 0x28, 0xbc, 0x94, + 0xc3, 0x9d, 0xe7, 0x95, 0x3b, 0x22, 0x15, 0xa9, 0x00, 0xf4, 0x8b, 0x6c, 0x00, 0x08, 0x00, 0x44, + 0xb3, 0x73, 0x9d, 0x09, 0x6a, 0x1c, 0xbf, 0x3d, 0x7b, 0xcd, 0x0d, 0xeb, 0x33, 0xc3, 0x30, 0x46, + 0x75, 0x23, 0xc6, 0x9c, 0x38, 0x9e, 0xe3, 0xd7, 0x28, 0xc4, 0x78, 0x07, 0x2d, 0xc7, 0xcc, 0x08, + 0x93, 0xf5, 0x39, 0x71, 0x3d, 0xc7, 0x77, 0x69, 0x89, 0xf1, 0x7d, 0xb4, 0x12, 0x2b, 0x19, 0xcd, + 0xc4, 0x1a, 0x88, 0x37, 0x84, 0x3d, 0xc9, 0xe2, 0xfc, 0x64, 0xdd, 0x73, 0xfc, 0x05, 0x5a, 0xe2, + 0xce, 0x67, 0x17, 0x21, 0x3a, 0x2d, 0x0b, 0xbf, 0x47, 0x8d, 0x7c, 0x82, 0x1e, 0xcf, 0x04, 0xd4, + 0x5f, 0x3d, 0xd8, 0xff, 0xfe, 0x63, 0x77, 0x6f, 0xde, 0x4e, 0x42, 0xa5, 0x79, 0x60, 0xae, 0x12, + 0x9e, 0x76, 0x8f, 0x67, 0x37, 0x1c, 0xbd, 0x3b, 0xa1, 0x28, 0xbf, 0xed, 0x28, 0x13, 0xb6, 0x49, + 0x3b, 0x48, 0x6a, 0xd8, 0x38, 0x21, 0x0d, 0xcf, 0xf1, 0x9b, 0xf4, 0x86, 0x28, 0x47, 0x5e, 0xad, + 0x8c, 0x7c, 0x0f, 0x2d, 0xeb, 0x41, 0x2f, 0x1c, 0x32, 0x21, 0xc9, 0x36, 0x1c, 0x58, 0xd2, 0x83, + 0x43, 0x0b, 0x31, 0x41, 0x4b, 0xe1, 0x90, 0x49, 0xc9, 0x63, 0xd2, 0x9a, 0x29, 0x39, 0xb4, 0x65, + 0x06, 0x9a, 0x7f, 0xc8, 0xb8, 0x0c, 0xaf, 0xc8, 0xae, 0xe7, 0xf8, 0x75, 0x7a, 0x43, 0xd8, 0x32, + 0x3a, 0x4d, 0x05, 0xf1, 0x60, 0x49, 0x10, 0xe3, 0x0d, 0x54, 0x4b, 0xa5, 0x26, 0x0f, 0x80, 0xb2, + 0x21, 0x7e, 0x84, 0x6a, 0x51, 0x92, 0x92, 0xc7, 0x9e, 0xe3, 0x37, 0x5e, 0x6c, 0x75, 0x0b, 0x8f, + 0x2b, 0x16, 0x51, 0x9b, 0xd0, 0xf9, 0xed, 0xa0, 0xf5, 0xf3, 0xe9, 0xa1, 0x92, 0x03, 0x11, 0x65, + 0x9a, 0x19, 0xa1, 0xe4, 0x9c, 0x31, 0xff, 0x33, 0xd2, 0xad, 0xc6, 0x5b, 0x7f, 0x37, 0xbe, 0x85, + 0x16, 0x12, 0x35, 0xe1, 0x9a, 0xdc, 0x05, 0x07, 0x67, 0x00, 0xef, 0xa1, 0x56, 0xa2, 0x62, 0xa6, + 0xc5, 0x47, 0x28, 0xde, 0x13, 0xf2, 0x92, 0xeb, 0x54, 0x28, 0x09, 0x93, 0x2f, 0xd3, 0xed, 0xaa, + 0x7a, 0x52, 0x88, 0x38, 0x40, 0x9b, 0xe5, 0xcd, 0xbd, 0x3e, 0xbf, 0x14, 0xa0, 0xc3, 0x52, 0x9a, + 0x14, 0x97, 0xd2, 0xab, 0x42, 0xe9, 0x7c, 0x73, 0xd1, 0xe2, 0x99, 0x61, 0x26, 0x4b, 0x6f, 0xcf, + 0xe7, 0xfc, 0xcb, 0x46, 0xb7, 0x62, 0xe3, 0x1a, 0x72, 0x85, 0x5d, 0x45, 0xcd, 0x5f, 0xa1, 0xae, + 0x48, 0xec, 0x7b, 0x4c, 0x62, 0x66, 0x06, 0x4a, 0x8f, 0xc1, 0xee, 0x15, 0x5a, 0x62, 0xfc, 0x10, + 0x35, 0x43, 0x25, 0x0d, 0x0b, 0x4d, 0x8f, 0x8f, 0x99, 0x88, 0x49, 0x13, 0x12, 0x56, 0x73, 0xf2, + 0xc8, 0x72, 0xd8, 0x43, 0x8d, 0x3e, 0x4f, 0x43, 0x2d, 0x12, 0x68, 0x7b, 0x0d, 0x52, 0xaa, 0x14, + 0x6e, 0xa1, 0x45, 0xcd, 0x23, 0x2b, 0xae, 0x83, 0x98, 0xa3, 0xc2, 0xd8, 0xed, 0x39, 0xc6, 0xda, + 0x27, 0xa1, 0x8d, 0x81, 0x25, 0x36, 0xa9, 0x0d, 0xf1, 0x26, 0x5a, 0xd0, 0xd3, 0x9e, 0x90, 0xf0, + 0x28, 0x9a, 0xb4, 0xae, 0xa7, 0x27, 0x32, 0x27, 0xd5, 0x88, 0x3c, 0x29, 0xc8, 0xd3, 0x91, 0x25, + 0x0d, 0x64, 0x3e, 0x9d, 0x91, 0x26, 0xcf, 0x34, 0x90, 0xf9, 0xac, 0x20, 0x4f, 0x47, 0x07, 0x1b, + 0x5f, 0xae, 0xdb, 0xce, 0xd7, 0xeb, 0xb6, 0xf3, 0xf3, 0xba, 0xed, 0x7c, 0xfa, 0xd5, 0xbe, 0x73, + 0xb1, 0x08, 0xbf, 0x83, 0x97, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x59, 0xd6, 0x7d, 0x83, + 0x04, 0x00, 0x00, } diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 8dbe8d9f9..16af323df 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -17,6 +17,10 @@ DeviceIdentifier Device DeviceList + DryDownlinkMessage + DryUplinkMessage + DryUplinkResult + DryDownlinkResult */ package handler @@ -232,6 +236,61 @@ func (m *DeviceList) GetDevices() []*Device { return nil } +type DryDownlinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + App *Application `protobuf:"bytes,3,opt,name=app" json:"app,omitempty"` +} + +func (m *DryDownlinkMessage) Reset() { *m = DryDownlinkMessage{} } +func (m *DryDownlinkMessage) String() string { return proto.CompactTextString(m) } +func (*DryDownlinkMessage) ProtoMessage() {} +func (*DryDownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{8} } + +func (m *DryDownlinkMessage) GetApp() *Application { + if m != nil { + return m.App + } + return nil +} + +type DryUplinkMessage struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + App *Application `protobuf:"bytes,2,opt,name=app" json:"app,omitempty"` +} + +func (m *DryUplinkMessage) Reset() { *m = DryUplinkMessage{} } +func (m *DryUplinkMessage) String() string { return proto.CompactTextString(m) } +func (*DryUplinkMessage) ProtoMessage() {} +func (*DryUplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{9} } + +func (m *DryUplinkMessage) GetApp() *Application { + if m != nil { + return m.App + } + return nil +} + +type DryUplinkResult struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` +} + +func (m *DryUplinkResult) Reset() { *m = DryUplinkResult{} } +func (m *DryUplinkResult) String() string { return proto.CompactTextString(m) } +func (*DryUplinkResult) ProtoMessage() {} +func (*DryUplinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{10} } + +type DryDownlinkResult struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (m *DryDownlinkResult) Reset() { *m = DryDownlinkResult{} } +func (m *DryDownlinkResult) String() string { return proto.CompactTextString(m) } +func (*DryDownlinkResult) ProtoMessage() {} +func (*DryDownlinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{11} } + func init() { proto.RegisterType((*DeviceActivationResponse)(nil), "handler.DeviceActivationResponse") proto.RegisterType((*StatusRequest)(nil), "handler.StatusRequest") @@ -241,6 +300,10 @@ func init() { proto.RegisterType((*DeviceIdentifier)(nil), "handler.DeviceIdentifier") proto.RegisterType((*Device)(nil), "handler.Device") proto.RegisterType((*DeviceList)(nil), "handler.DeviceList") + proto.RegisterType((*DryDownlinkMessage)(nil), "handler.DryDownlinkMessage") + proto.RegisterType((*DryUplinkMessage)(nil), "handler.DryUplinkMessage") + proto.RegisterType((*DryUplinkResult)(nil), "handler.DryUplinkResult") + proto.RegisterType((*DryDownlinkResult)(nil), "handler.DryDownlinkResult") } // Reference imports to suppress errors if they are not otherwise used. @@ -326,6 +389,8 @@ type ApplicationManagerClient interface { SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) + DryDownlink(ctx context.Context, in *DryDownlinkMessage, opts ...grpc.CallOption) (*DryDownlinkResult, error) + DryUplink(ctx context.Context, in *DryUplinkMessage, opts ...grpc.CallOption) (*DryUplinkResult, error) } type applicationManagerClient struct { @@ -408,6 +473,24 @@ func (c *applicationManagerClient) GetDevicesForApplication(ctx context.Context, return out, nil } +func (c *applicationManagerClient) DryDownlink(ctx context.Context, in *DryDownlinkMessage, opts ...grpc.CallOption) (*DryDownlinkResult, error) { + out := new(DryDownlinkResult) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DryDownlink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *applicationManagerClient) DryUplink(ctx context.Context, in *DryUplinkMessage, opts ...grpc.CallOption) (*DryUplinkResult, error) { + out := new(DryUplinkResult) + err := grpc.Invoke(ctx, "/handler.ApplicationManager/DryUplink", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // Server API for ApplicationManager service type ApplicationManagerServer interface { @@ -419,6 +502,8 @@ type ApplicationManagerServer interface { SetDevice(context.Context, *Device) (*api.Ack, error) DeleteDevice(context.Context, *DeviceIdentifier) (*api.Ack, error) GetDevicesForApplication(context.Context, *ApplicationIdentifier) (*DeviceList, error) + DryDownlink(context.Context, *DryDownlinkMessage) (*DryDownlinkResult, error) + DryUplink(context.Context, *DryUplinkMessage) (*DryUplinkResult, error) } func RegisterApplicationManagerServer(s *grpc.Server, srv ApplicationManagerServer) { @@ -569,6 +654,42 @@ func _ApplicationManager_GetDevicesForApplication_Handler(srv interface{}, ctx c return interceptor(ctx, in, info, handler) } +func _ApplicationManager_DryDownlink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DryDownlinkMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DryDownlink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DryDownlink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DryDownlink(ctx, req.(*DryDownlinkMessage)) + } + return interceptor(ctx, in, info, handler) +} + +func _ApplicationManager_DryUplink_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DryUplinkMessage) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ApplicationManagerServer).DryUplink(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.ApplicationManager/DryUplink", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ApplicationManagerServer).DryUplink(ctx, req.(*DryUplinkMessage)) + } + return interceptor(ctx, in, info, handler) +} + var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ ServiceName: "handler.ApplicationManager", HandlerType: (*ApplicationManagerServer)(nil), @@ -605,6 +726,14 @@ var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ MethodName: "GetDevicesForApplication", Handler: _ApplicationManager_GetDevicesForApplication_Handler, }, + { + MethodName: "DryDownlink", + Handler: _ApplicationManager_DryDownlink_Handler, + }, + { + MethodName: "DryUplink", + Handler: _ApplicationManager_DryUplink_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: fileDescriptorHandler, @@ -945,6 +1074,144 @@ func (m *DeviceList) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *DryDownlinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DryDownlinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if len(m.Fields) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Fields))) + i += copy(data[i:], m.Fields) + } + if m.App != nil { + data[i] = 0x1a + i++ + i = encodeVarintHandler(data, i, uint64(m.App.Size())) + n5, err := m.App.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + return i, nil +} + +func (m *DryUplinkMessage) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DryUplinkMessage) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.App != nil { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(m.App.Size())) + n6, err := m.App.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + return i, nil +} + +func (m *DryUplinkResult) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DryUplinkResult) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if len(m.Fields) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Fields))) + i += copy(data[i:], m.Fields) + } + if m.Valid { + data[i] = 0x18 + i++ + if m.Valid { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + return i, nil +} + +func (m *DryDownlinkResult) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DryDownlinkResult) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + return i, nil +} + func encodeFixed64Handler(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -1094,6 +1361,65 @@ func (m *DeviceList) Size() (n int) { return n } +func (m *DryDownlinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Fields) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.App != nil { + l = m.App.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *DryUplinkMessage) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.App != nil { + l = m.App.Size() + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + +func (m *DryUplinkResult) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + l = len(m.Fields) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if m.Valid { + n += 2 + } + return n +} + +func (m *DryDownlinkResult) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + return n +} + func sovHandler(x uint64) (n int) { for { n++ @@ -1986,6 +2312,474 @@ func (m *DeviceList) Unmarshal(data []byte) error { } return nil } +func (m *DryDownlinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryDownlinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryDownlinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field App", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.App == nil { + m.App = &Application{} + } + if err := m.App.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryUplinkMessage) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryUplinkMessage: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryUplinkMessage: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field App", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.App == nil { + m.App = &Application{} + } + if err := m.App.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryUplinkResult) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryUplinkResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryUplinkResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Valid", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Valid = bool(v != 0) + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DryDownlinkResult) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DryDownlinkResult: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DryDownlinkResult: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipHandler(data []byte) (n int, err error) { l := len(data) iNdEx := 0 @@ -2096,46 +2890,54 @@ func init() { } var fileDescriptorHandler = []byte{ - // 655 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0x51, 0x4f, 0x13, 0x41, - 0x10, 0xa6, 0x22, 0xa5, 0x9d, 0x42, 0xc1, 0x45, 0xf0, 0x6c, 0x08, 0xd1, 0x7b, 0x30, 0x1a, 0xe3, - 0xd5, 0x14, 0x13, 0x34, 0xc6, 0x60, 0x09, 0x41, 0x48, 0x44, 0x92, 0x83, 0x27, 0x5f, 0x9a, 0xe5, - 0x76, 0x6c, 0x37, 0x3d, 0x6e, 0xcf, 0xbb, 0x6d, 0x89, 0xfe, 0x0e, 0x1f, 0xfc, 0x49, 0x3e, 0xfa, - 0xee, 0x8b, 0xd1, 0xdf, 0xe0, 0xbb, 0x7b, 0xbb, 0xdb, 0xe3, 0x28, 0x18, 0xa8, 0x0f, 0x97, 0xbb, - 0x99, 0x6f, 0xbe, 0x6f, 0x66, 0x76, 0xe6, 0x16, 0x5e, 0x74, 0xb9, 0xec, 0x0d, 0x8e, 0xbd, 0x40, - 0x9c, 0x34, 0x8f, 0x7a, 0x78, 0xd4, 0xe3, 0x51, 0x37, 0x7d, 0x87, 0xf2, 0x54, 0x24, 0xfd, 0xa6, - 0x94, 0x51, 0x93, 0xc6, 0xbc, 0xd9, 0xa3, 0x11, 0x0b, 0x31, 0x19, 0xbd, 0xbd, 0x38, 0x11, 0x52, - 0x90, 0x59, 0x6b, 0x36, 0x9e, 0x5c, 0x47, 0x43, 0x3d, 0x86, 0xd7, 0xd8, 0xb8, 0x4e, 0xf8, 0x71, - 0x22, 0xfa, 0x2a, 0xa3, 0x79, 0x59, 0xe2, 0xcb, 0xeb, 0x10, 0x75, 0x68, 0x20, 0xc2, 0xfc, 0xc3, - 0x92, 0xdb, 0x13, 0x91, 0x43, 0x91, 0xd0, 0x53, 0x1a, 0x35, 0x19, 0x0e, 0x79, 0x80, 0x46, 0xc2, - 0xfd, 0x51, 0x02, 0x67, 0x5b, 0x3b, 0xda, 0x81, 0xe4, 0x43, 0x2a, 0xb9, 0x88, 0x7c, 0x4c, 0x63, - 0x11, 0xa5, 0x48, 0x1c, 0x98, 0x8d, 0xe9, 0xa7, 0x50, 0x50, 0xe6, 0x94, 0xee, 0x95, 0x1e, 0xce, - 0xf9, 0x23, 0x93, 0x2c, 0x43, 0x99, 0xc6, 0x71, 0x87, 0x33, 0xe7, 0x86, 0x02, 0xaa, 0xfe, 0x8c, - 0xb2, 0xf6, 0x18, 0xd9, 0x84, 0x05, 0x26, 0x4e, 0xa3, 0x90, 0x47, 0xfd, 0x8e, 0x88, 0x33, 0x2d, - 0xa7, 0xa6, 0xf0, 0x5a, 0x6b, 0xc5, 0xb3, 0x5d, 0x6f, 0x5b, 0xf8, 0x40, 0xa3, 0x7e, 0x9d, 0x9d, - 0xb3, 0xc9, 0x3e, 0x2c, 0xd1, 0xbc, 0x8e, 0xce, 0x09, 0x4a, 0xca, 0xa8, 0xa4, 0xce, 0x1d, 0x2d, - 0xb2, 0xea, 0xe5, 0xfd, 0x9f, 0x15, 0xbb, 0x6f, 0x63, 0x7c, 0x42, 0x2f, 0xf8, 0xdc, 0x05, 0x98, - 0x3f, 0x94, 0x54, 0x0e, 0x52, 0x1f, 0x3f, 0x0e, 0x30, 0x95, 0x6e, 0x05, 0xca, 0xc6, 0xe1, 0x7a, - 0xb0, 0xdc, 0x8e, 0xe3, 0x90, 0x07, 0x9a, 0xb1, 0xc7, 0x30, 0x92, 0xfc, 0x03, 0xc7, 0xa4, 0xd0, - 0x5a, 0xa9, 0xd0, 0x9a, 0xfb, 0xa5, 0x04, 0xb5, 0x02, 0xe1, 0x1f, 0x61, 0xd9, 0x91, 0x31, 0x0c, - 0x04, 0xc3, 0xc4, 0x9e, 0xcc, 0xc8, 0x24, 0xab, 0x50, 0x0d, 0x44, 0x34, 0xc4, 0x44, 0x2a, 0x6c, - 0x5a, 0x63, 0x67, 0x8e, 0x0c, 0x1d, 0xd2, 0x90, 0xab, 0xaa, 0x45, 0xe2, 0xdc, 0x34, 0x68, 0xee, - 0xc8, 0x54, 0x31, 0x32, 0xaa, 0x33, 0x46, 0xd5, 0x9a, 0xee, 0x6b, 0x58, 0x34, 0xe3, 0xbb, 0xb2, - 0x83, 0xcc, 0xad, 0x46, 0x5f, 0x98, 0x99, 0xb2, 0x54, 0x63, 0x9f, 0xa1, 0x6c, 0x14, 0x26, 0xe3, - 0x91, 0xe7, 0x50, 0xb7, 0x1b, 0xd5, 0x31, 0x1b, 0xa5, 0x9b, 0xaa, 0xb5, 0x16, 0x3c, 0xeb, 0xf6, - 0x8c, 0xec, 0xee, 0x94, 0x3f, 0x6f, 0x3d, 0xc6, 0xb1, 0x55, 0xd1, 0x82, 0xea, 0xcb, 0xdd, 0x00, - 0x30, 0xbe, 0xb7, 0x3c, 0x95, 0xe4, 0x51, 0x76, 0x76, 0x99, 0x95, 0xaa, 0x02, 0xa6, 0xb5, 0xd4, - 0xe8, 0xef, 0x34, 0x51, 0xfe, 0x08, 0x6f, 0x21, 0xcc, 0xee, 0x1a, 0x88, 0xbc, 0x87, 0x8a, 0xdd, - 0x06, 0x24, 0x8f, 0xf3, 0x35, 0x43, 0x36, 0x30, 0xb3, 0x42, 0x76, 0x71, 0xbd, 0xf5, 0x2e, 0x34, - 0xee, 0x8f, 0xa9, 0x5f, 0xfc, 0x01, 0x5a, 0x7f, 0xa6, 0x81, 0x14, 0x86, 0xbe, 0x4f, 0x23, 0xda, - 0x55, 0x29, 0x37, 0x61, 0xc9, 0xc7, 0xae, 0x2a, 0x19, 0x93, 0xe2, 0x4a, 0xac, 0xe5, 0x82, 0x97, - 0x6e, 0x56, 0xa3, 0xe2, 0x65, 0x17, 0x46, 0x3b, 0xe8, 0x93, 0x1d, 0xa8, 0xbf, 0x41, 0x39, 0x09, - 0xf7, 0xf6, 0x65, 0x38, 0x79, 0x0a, 0xf5, 0xc3, 0xf3, 0x3a, 0x97, 0xc6, 0x15, 0x32, 0xbf, 0x82, - 0x5b, 0xdb, 0x18, 0xa2, 0xc4, 0xff, 0x2b, 0x7c, 0x03, 0xaa, 0xaa, 0x70, 0xbb, 0x2f, 0x77, 0xc7, - 0x0e, 0xb0, 0xc0, 0x18, 0x9f, 0x1c, 0x79, 0x00, 0xd5, 0xc3, 0x9c, 0x38, 0x8e, 0x16, 0x12, 0xac, - 0xc3, 0x9c, 0xa9, 0xef, 0xea, 0x1c, 0x67, 0xa4, 0x03, 0x70, 0xf2, 0xaa, 0xd2, 0x1d, 0x31, 0xd1, - 0x50, 0x96, 0xc6, 0x12, 0x64, 0x9b, 0xd8, 0x52, 0xf3, 0xb1, 0xeb, 0x35, 0x1a, 0xf9, 0x33, 0xdd, - 0xb8, 0xb9, 0x3b, 0xc8, 0x4a, 0xce, 0x39, 0x77, 0xbb, 0x14, 0xba, 0x36, 0xfe, 0xad, 0xc5, 0x6f, - 0xbf, 0xd6, 0x4a, 0xdf, 0xd5, 0xf3, 0x53, 0x3d, 0x5f, 0x7f, 0xaf, 0x4d, 0x1d, 0x97, 0xf5, 0x15, - 0xb6, 0xfe, 0x37, 0x00, 0x00, 0xff, 0xff, 0xf7, 0x57, 0xba, 0xac, 0xa4, 0x06, 0x00, 0x00, + // 781 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xea, 0x46, + 0x14, 0xbe, 0xbe, 0xb9, 0x21, 0x70, 0xb8, 0x17, 0x92, 0xc9, 0x4f, 0x5d, 0x1a, 0x21, 0xea, 0x45, + 0x94, 0xaa, 0x8a, 0xa9, 0x48, 0xa5, 0xb4, 0xaa, 0xaa, 0x84, 0x08, 0x91, 0x44, 0x2a, 0x8d, 0xe4, + 0xa4, 0x8b, 0x76, 0x83, 0x26, 0x9e, 0x13, 0x18, 0xe1, 0x78, 0x5c, 0x7b, 0x20, 0x4a, 0x9f, 0xa3, + 0x8b, 0x3e, 0x52, 0x97, 0xdd, 0x77, 0x53, 0xa5, 0x0f, 0xd2, 0xca, 0x9e, 0xc1, 0x38, 0x40, 0x0b, + 0x64, 0x05, 0xe7, 0xe7, 0xfb, 0xce, 0xbf, 0x07, 0xbe, 0xee, 0x71, 0xd9, 0x1f, 0xde, 0xd9, 0xae, + 0x78, 0xa8, 0xdf, 0xf6, 0xf1, 0xb6, 0xcf, 0xfd, 0x5e, 0xf4, 0x3d, 0xca, 0x47, 0x11, 0x0e, 0xea, + 0x52, 0xfa, 0x75, 0x1a, 0xf0, 0x7a, 0x9f, 0xfa, 0xcc, 0xc3, 0x70, 0xfc, 0x6b, 0x07, 0xa1, 0x90, + 0x82, 0x6c, 0x68, 0xb1, 0x72, 0xb4, 0x0c, 0x07, 0x0d, 0xb8, 0xc2, 0x55, 0x4e, 0x96, 0x71, 0xbf, + 0x0b, 0xc5, 0x00, 0x43, 0xfd, 0xa3, 0x81, 0xdf, 0x2c, 0x03, 0x4c, 0x5c, 0x5d, 0xe1, 0xa5, 0x7f, + 0x34, 0xb8, 0xb9, 0x12, 0xd8, 0x13, 0x21, 0x7d, 0xa4, 0x7e, 0x9d, 0xe1, 0x88, 0xbb, 0xa8, 0x28, + 0xac, 0x3f, 0x0d, 0x30, 0x5b, 0x89, 0xa2, 0xe9, 0x4a, 0x3e, 0xa2, 0x92, 0x0b, 0xdf, 0xc1, 0x28, + 0x10, 0x7e, 0x84, 0xc4, 0x84, 0x8d, 0x80, 0x3e, 0x79, 0x82, 0x32, 0xd3, 0xa8, 0x19, 0x87, 0xef, + 0x9d, 0xb1, 0x48, 0x76, 0x21, 0x47, 0x83, 0xa0, 0xcb, 0x99, 0xf9, 0xb6, 0x66, 0x1c, 0x16, 0x9c, + 0x75, 0x1a, 0x04, 0x57, 0x8c, 0x9c, 0x42, 0x99, 0x89, 0x47, 0xdf, 0xe3, 0xfe, 0xa0, 0x2b, 0x82, + 0x98, 0xcb, 0x2c, 0xd6, 0x8c, 0xc3, 0x62, 0x63, 0xcf, 0xd6, 0x55, 0xb7, 0xb4, 0xf9, 0x3a, 0xb1, + 0x3a, 0x25, 0xf6, 0x42, 0x26, 0x1d, 0xd8, 0xa6, 0x69, 0x1e, 0xdd, 0x07, 0x94, 0x94, 0x51, 0x49, + 0xcd, 0x8f, 0x12, 0x92, 0x7d, 0x3b, 0xad, 0x7f, 0x92, 0x6c, 0x47, 0xfb, 0x38, 0x84, 0xce, 0xe8, + 0xac, 0x32, 0x7c, 0xb8, 0x91, 0x54, 0x0e, 0x23, 0x07, 0x7f, 0x1e, 0x62, 0x24, 0xad, 0x3c, 0xe4, + 0x94, 0xc2, 0xb2, 0x61, 0xb7, 0x19, 0x04, 0x1e, 0x77, 0x13, 0xc4, 0x15, 0x43, 0x5f, 0xf2, 0x7b, + 0x8e, 0x61, 0xa6, 0x34, 0x23, 0x53, 0x9a, 0xf5, 0xab, 0x01, 0xc5, 0x0c, 0xe0, 0x3f, 0xdc, 0xe2, + 0x96, 0x31, 0x74, 0x05, 0xc3, 0x50, 0x77, 0x66, 0x2c, 0x92, 0x7d, 0x28, 0xb8, 0xc2, 0x1f, 0x61, + 0x28, 0x31, 0x34, 0xd7, 0x12, 0xdb, 0x44, 0x11, 0x5b, 0x47, 0xd4, 0xe3, 0x8c, 0x4a, 0x11, 0x9a, + 0xef, 0x94, 0x35, 0x55, 0xc4, 0xac, 0xe8, 0x2b, 0xd6, 0x75, 0xc5, 0xaa, 0x45, 0xeb, 0x0c, 0x36, + 0xd5, 0xf8, 0x16, 0x56, 0x10, 0xab, 0x19, 0x8e, 0x32, 0x33, 0x63, 0x38, 0xba, 0x62, 0xd6, 0x2f, + 0x90, 0x53, 0x0c, 0xab, 0xe1, 0xc8, 0x57, 0x50, 0xd2, 0x1b, 0xd5, 0x55, 0x1b, 0x95, 0x14, 0x55, + 0x6c, 0x94, 0x6d, 0xad, 0xb6, 0x15, 0xed, 0xe5, 0x1b, 0xe7, 0x83, 0xd6, 0x28, 0xc5, 0x79, 0x3e, + 0x21, 0xe4, 0x2e, 0x5a, 0x27, 0x00, 0x4a, 0xf7, 0x1d, 0x8f, 0x24, 0xf9, 0x2c, 0xee, 0x5d, 0x2c, + 0x45, 0xa6, 0x51, 0x5b, 0x4b, 0xa8, 0xc6, 0xd7, 0xa9, 0xbc, 0x9c, 0xb1, 0xdd, 0xf2, 0x81, 0xb4, + 0xc2, 0xa7, 0xf1, 0x32, 0x75, 0x30, 0x8a, 0x68, 0xef, 0xff, 0xf6, 0x75, 0x0f, 0x72, 0xf7, 0x1c, + 0x3d, 0x16, 0xe9, 0x1a, 0xb4, 0x44, 0x0e, 0x60, 0x8d, 0x06, 0x81, 0xce, 0x7c, 0x27, 0x0d, 0x97, + 0x19, 0xb4, 0x13, 0x3b, 0x58, 0xb7, 0xb0, 0xd9, 0x0a, 0x9f, 0x7e, 0x08, 0x96, 0x8b, 0xa6, 0x59, + 0xdf, 0x2e, 0x62, 0xfd, 0x11, 0xca, 0x29, 0xab, 0x83, 0xd1, 0xd0, 0x93, 0xaf, 0x28, 0x61, 0x07, + 0xd6, 0x93, 0x45, 0x49, 0x8a, 0xc8, 0x3b, 0x4a, 0xb0, 0x8e, 0x60, 0x2b, 0xd3, 0xa0, 0x45, 0xe4, + 0x0d, 0x84, 0x8d, 0x4b, 0x95, 0x25, 0xf9, 0x09, 0xf2, 0xfa, 0xba, 0x90, 0x7c, 0x9e, 0x9e, 0x2d, + 0xb2, 0xa1, 0x4a, 0x1e, 0xd9, 0xec, 0xe7, 0x22, 0xb9, 0xad, 0xca, 0xa7, 0x53, 0xd3, 0x9a, 0xfd, + 0xa0, 0x34, 0xfe, 0x79, 0x07, 0x24, 0xd3, 0x85, 0x0e, 0xf5, 0x69, 0x0f, 0x43, 0x72, 0x0a, 0xdb, + 0x0e, 0xf6, 0x78, 0x24, 0x31, 0xcc, 0x9e, 0x58, 0x75, 0x5e, 0xe7, 0x26, 0x7b, 0x5e, 0xc9, 0xdb, + 0xf1, 0x07, 0xb8, 0xe9, 0x0e, 0x48, 0x1b, 0x4a, 0x17, 0x28, 0x57, 0xc1, 0xce, 0x9d, 0x0a, 0xf9, + 0x02, 0x4a, 0x37, 0x2f, 0x79, 0xe6, 0xfa, 0x65, 0x22, 0x7f, 0x0b, 0x5b, 0x2d, 0xf4, 0x50, 0xe2, + 0xeb, 0x12, 0x3f, 0x81, 0xc2, 0x05, 0x4a, 0x7d, 0x7f, 0x1f, 0x4f, 0x35, 0x30, 0x83, 0x98, 0xbe, + 0x04, 0x72, 0x00, 0x85, 0x9b, 0x14, 0x38, 0x6d, 0xcd, 0x04, 0x38, 0x86, 0xf7, 0x2a, 0xbf, 0xc5, + 0x31, 0x26, 0xa0, 0x6b, 0x30, 0xd3, 0xac, 0xa2, 0xb6, 0x58, 0x69, 0x28, 0xdb, 0x53, 0x01, 0x92, + 0xcb, 0x6e, 0x43, 0x31, 0xb3, 0x8d, 0xe4, 0x93, 0x89, 0xcf, 0xcc, 0x11, 0x57, 0x2a, 0xf3, 0x8c, + 0x7a, 0x81, 0xcf, 0xa0, 0x90, 0x1e, 0x4c, 0xb6, 0x94, 0xa9, 0xd3, 0xac, 0x98, 0xb3, 0x26, 0xc5, + 0xd0, 0x68, 0x43, 0x49, 0x2f, 0xfa, 0x78, 0xf9, 0xbe, 0x4c, 0x46, 0xa0, 0x5e, 0x05, 0xb2, 0x97, + 0x02, 0x5f, 0xbc, 0x1b, 0x99, 0xfe, 0x2b, 0xfd, 0xf9, 0xe6, 0xef, 0xcf, 0x55, 0xe3, 0x8f, 0xe7, + 0xaa, 0xf1, 0xd7, 0x73, 0xd5, 0xf8, 0xed, 0xef, 0xea, 0x9b, 0xbb, 0x5c, 0xf2, 0x38, 0x1d, 0xff, + 0x1b, 0x00, 0x00, 0xff, 0xff, 0x5c, 0x52, 0xb5, 0xba, 0x7e, 0x08, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index fbf70f36a..0371c8925 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -57,6 +57,27 @@ message DeviceList { repeated Device devices = 1; } +message DryDownlinkMessage { + bytes payload = 1; + string fields = 2; + Application app = 3; +} + +message DryUplinkMessage { + bytes payload = 1; + Application app = 2; +} + +message DryUplinkResult { + bytes payload = 1; + string fields = 2; + bool valid = 3; +} + +message DryDownlinkResult { + bytes payload = 1; +} + service ApplicationManager { rpc RegisterApplication(ApplicationIdentifier) returns (api.Ack); rpc GetApplication(ApplicationIdentifier) returns (Application); @@ -66,6 +87,8 @@ service ApplicationManager { rpc SetDevice(Device) returns (api.Ack); rpc DeleteDevice(DeviceIdentifier) returns (api.Ack); rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList); + rpc DryDownlink(DryDownlinkMessage) returns (DryDownlinkResult); + rpc DryUplink(DryUplinkMessage) returns (DryUplinkResult); } // The HandlerManager service provides configuration and monitoring diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index af444de01..658cde567 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -1,6 +1,7 @@ package handler import ( + "encoding/json" "os" "os/user" "sync" @@ -115,6 +116,38 @@ func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Devic return } +// DryUplink transforms the uplink payload with the payload functions provided +// in the app.. +func (h *ManagerClient) DryUplink(payload []byte, app *Application) (*DryUplinkResult, error) { + return h.applicationManagerClient.DryUplink(h.getContext(), &DryUplinkMessage{ + App: app, + Payload: payload, + }) +} + +// DryDownlinkWithPayload transforms the downlink payload with the payload functions +// provided in app. +func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application) (*DryDownlinkResult, error) { + return h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ + App: app, + Payload: payload, + }) +} + +// DryDownlinkWithPayload transforms the downlink fields with the payload functions +// provided in app. +func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app *Application) (*DryDownlinkResult, error) { + marshalled, err := json.Marshal(fields) + if err != nil { + return nil, err + } + + return h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ + App: app, + Fields: string(marshalled), + }) +} + // Close closes the client func (h *ManagerClient) Close() error { return h.conn.Close() diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 2b7f76108..74c4b3ad5 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -998,38 +998,38 @@ func init() { } var fileDescriptorNetworkserver = []byte{ - // 519 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x53, 0xdd, 0x6e, 0xd3, 0x30, - 0x14, 0xa6, 0x0c, 0xba, 0xe2, 0x51, 0xba, 0x79, 0x20, 0xa6, 0x0a, 0x0a, 0xf4, 0x6a, 0x08, 0x91, - 0x48, 0x41, 0x02, 0x21, 0xed, 0x82, 0x8e, 0x22, 0x24, 0xd0, 0xa6, 0xd2, 0x0d, 0x89, 0xbb, 0xca, - 0x4d, 0x4e, 0xd3, 0xa8, 0x99, 0x1d, 0x6c, 0xa7, 0x85, 0x37, 0xe1, 0x1d, 0x78, 0x11, 0x2e, 0xb9, - 0xe6, 0x02, 0x21, 0x78, 0x11, 0xce, 0x6c, 0x67, 0x10, 0x68, 0xd5, 0xee, 0xc2, 0x8a, 0x7d, 0xce, - 0x77, 0xbe, 0xf3, 0x9d, 0x9f, 0x90, 0x17, 0x71, 0xa2, 0xc7, 0xf9, 0xd0, 0x0b, 0xc5, 0x89, 0x7f, - 0x3c, 0x86, 0xe3, 0x71, 0xc2, 0x63, 0x75, 0x08, 0x7a, 0x26, 0xe4, 0xc4, 0xd7, 0x9a, 0xfb, 0x2c, - 0x4b, 0x7c, 0x6e, 0xdf, 0x0a, 0xe4, 0x14, 0x64, 0xf9, 0xe5, 0x65, 0x52, 0x68, 0x41, 0xeb, 0x25, - 0x63, 0xf3, 0xe1, 0x5f, 0xac, 0xb1, 0x88, 0x85, 0x6f, 0x50, 0xc3, 0x7c, 0x64, 0x5e, 0xe6, 0x61, - 0x6e, 0x36, 0xba, 0x04, 0x5f, 0x28, 0x02, 0x8f, 0x83, 0x77, 0x56, 0x81, 0x1b, 0x68, 0x28, 0x52, - 0x3f, 0x15, 0x92, 0xcd, 0x18, 0xf7, 0x23, 0x98, 0x26, 0x21, 0x38, 0x8a, 0x27, 0xab, 0x50, 0x0c, - 0xa5, 0x98, 0x60, 0xbd, 0xf6, 0xe3, 0x02, 0x9f, 0xae, 0x12, 0x38, 0x66, 0x3c, 0x4a, 0x31, 0xd2, - 0x7d, 0x6d, 0x68, 0xfb, 0x03, 0xb9, 0xd6, 0x35, 0x1a, 0x54, 0x1f, 0xde, 0xe7, 0xa0, 0x34, 0x7d, - 0x43, 0x6a, 0xa8, 0x6a, 0xc0, 0xa2, 0x48, 0xee, 0x54, 0xee, 0x56, 0x76, 0xaf, 0xee, 0x3f, 0xfe, - 0xf6, 0xfd, 0x4e, 0xb0, 0x2c, 0x45, 0x28, 0x24, 0xf8, 0xfa, 0x63, 0x06, 0xca, 0x43, 0xc2, 0x0e, - 0x46, 0xf7, 0xd7, 0x23, 0x7b, 0xa1, 0xdb, 0xe4, 0xf2, 0x68, 0x10, 0x72, 0xbd, 0x73, 0x11, 0xf9, - 0xea, 0xfd, 0x4b, 0xa3, 0xe7, 0x5c, 0xb7, 0xf7, 0x48, 0xe3, 0x2c, 0xb3, 0xca, 0x04, 0x57, 0x40, - 0xef, 0x93, 0x75, 0x09, 0x2a, 0x4f, 0xb5, 0xc2, 0xcc, 0x6b, 0xbb, 0x1b, 0x41, 0xc3, 0x73, 0x8d, - 0xf2, 0x2c, 0xb4, 0x5f, 0xf8, 0xdb, 0x0d, 0x52, 0x3f, 0xd2, 0x4c, 0xe7, 0x85, 0xec, 0xf6, 0x2b, - 0x52, 0xb5, 0x06, 0xfa, 0x8c, 0x6c, 0xdb, 0xb6, 0xaa, 0x41, 0x06, 0xd2, 0x14, 0x02, 0x4a, 0x99, - 0x5a, 0x36, 0x82, 0x4d, 0xef, 0x74, 0x64, 0x3d, 0x90, 0x21, 0x70, 0x9d, 0xa4, 0x98, 0x7c, 0xcb, - 0x81, 0xd1, 0xd6, 0xb1, 0xd0, 0xe0, 0xf3, 0x1a, 0xa9, 0xbb, 0xda, 0x8e, 0xcc, 0xee, 0xd0, 0xd7, - 0x84, 0xbc, 0x04, 0xed, 0xf4, 0xd2, 0xdb, 0x5e, 0x79, 0xdd, 0xca, 0x1d, 0x6c, 0xb6, 0x16, 0xb9, - 0x5d, 0x99, 0x27, 0x64, 0xab, 0x27, 0x21, 0x63, 0x12, 0x3a, 0xa1, 0x4e, 0xa6, 0x4c, 0x27, 0x82, - 0xd3, 0x07, 0x9e, 0x1b, 0x69, 0x17, 0xa2, 0x3c, 0x4b, 0x93, 0x90, 0x69, 0x88, 0x6c, 0xe4, 0x1f, - 0x54, 0x91, 0xe1, 0x3c, 0x60, 0xda, 0x23, 0x35, 0x67, 0x04, 0x7a, 0xcf, 0x2b, 0xc6, 0xff, 0x3f, - 0xda, 0xaa, 0x6b, 0x2e, 0x87, 0xd0, 0x43, 0x52, 0x7d, 0x8b, 0x59, 0xf9, 0x04, 0xf9, 0xe6, 0x08, - 0xb1, 0xbe, 0x03, 0xec, 0x24, 0x8b, 0x4f, 0xf9, 0x96, 0x41, 0xe8, 0x1e, 0xa9, 0x75, 0xc5, 0x8c, - 0x1b, 0xc6, 0x9b, 0x67, 0x70, 0x67, 0x29, 0x78, 0x16, 0x39, 0x82, 0x77, 0xe4, 0x7a, 0x69, 0x58, - 0x07, 0x8c, 0xa3, 0x59, 0xe2, 0x1e, 0x5c, 0xc1, 0x99, 0xb9, 0xa5, 0xb8, 0xf5, 0xcf, 0x4c, 0x4a, - 0xcb, 0xd3, 0xbc, 0x31, 0xd7, 0xbb, 0xbf, 0xf9, 0xe5, 0x67, 0xab, 0xf2, 0x15, 0xcf, 0x0f, 0x3c, - 0x9f, 0x7e, 0xb5, 0x2e, 0x0c, 0xab, 0xe6, 0xaf, 0x79, 0xf4, 0x3b, 0x00, 0x00, 0xff, 0xff, 0x70, - 0xbc, 0x3e, 0xb2, 0xa2, 0x04, 0x00, 0x00, + // 524 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x93, 0x51, 0x6f, 0xd3, 0x30, + 0x10, 0xc7, 0x09, 0x83, 0xad, 0x78, 0x94, 0x6e, 0x1e, 0x88, 0xaa, 0x82, 0xd2, 0xf5, 0xa9, 0x08, + 0x91, 0x48, 0x45, 0x02, 0x21, 0xed, 0x61, 0x1d, 0x45, 0x93, 0x40, 0x9b, 0x4a, 0x36, 0x24, 0xde, + 0x2a, 0x37, 0xbe, 0xa6, 0x56, 0x33, 0x3b, 0xd8, 0x4e, 0x0a, 0xdf, 0x84, 0xef, 0xc0, 0x17, 0xe1, + 0x91, 0x67, 0x1e, 0x10, 0x2a, 0x5f, 0x04, 0xcd, 0x76, 0x06, 0x81, 0x55, 0x2d, 0x4f, 0xf1, 0xdd, + 0xfd, 0xee, 0xfc, 0x3f, 0xdf, 0x05, 0xbd, 0x8c, 0x99, 0x9e, 0x64, 0x23, 0x3f, 0x12, 0x67, 0xc1, + 0xe9, 0x04, 0x4e, 0x27, 0x8c, 0xc7, 0xea, 0x18, 0xf4, 0x4c, 0xc8, 0x69, 0xa0, 0x35, 0x0f, 0x48, + 0xca, 0x02, 0x6e, 0x6d, 0x05, 0x32, 0x07, 0x59, 0xb6, 0xfc, 0x54, 0x0a, 0x2d, 0x70, 0xb5, 0xe4, + 0x6c, 0x3c, 0xfe, 0xa3, 0x6a, 0x2c, 0x62, 0x11, 0x18, 0x6a, 0x94, 0x8d, 0x8d, 0x65, 0x0c, 0x73, + 0xb2, 0xd9, 0x25, 0x7c, 0xa1, 0x08, 0x92, 0x32, 0x87, 0xf7, 0x56, 0xc1, 0x0d, 0x1a, 0x89, 0x24, + 0x48, 0x84, 0x24, 0x33, 0xc2, 0x03, 0x0a, 0x39, 0x8b, 0xc0, 0x95, 0x78, 0xb6, 0x4a, 0x89, 0x91, + 0x14, 0x53, 0x90, 0xee, 0xe3, 0x12, 0x9f, 0xaf, 0x92, 0x38, 0x21, 0x9c, 0x26, 0x20, 0x8b, 0xaf, + 0x4d, 0x6d, 0x7f, 0x40, 0xb7, 0xfa, 0x46, 0x83, 0x0a, 0xe1, 0x7d, 0x06, 0x4a, 0xe3, 0x37, 0xa8, + 0x42, 0x21, 0x1f, 0x12, 0x4a, 0x65, 0xdd, 0x6b, 0x79, 0x9d, 0x9b, 0x07, 0x4f, 0xbf, 0x7d, 0x7f, + 0xd0, 0x5d, 0x76, 0x45, 0x24, 0x24, 0x04, 0xfa, 0x63, 0x0a, 0xca, 0xef, 0x43, 0xde, 0xa3, 0x54, + 0x86, 0x1b, 0xd4, 0x1e, 0xf0, 0x0e, 0xba, 0x3e, 0x1e, 0x46, 0x5c, 0xd7, 0xaf, 0xb6, 0xbc, 0x4e, + 0x35, 0xbc, 0x36, 0x7e, 0xc1, 0x75, 0x7b, 0x0f, 0xd5, 0x2e, 0x6e, 0x56, 0xa9, 0xe0, 0x0a, 0xf0, + 0x43, 0xb4, 0x21, 0x41, 0x65, 0x89, 0x56, 0x75, 0xaf, 0xb5, 0xd6, 0xd9, 0xec, 0xd6, 0x7c, 0xf7, + 0x50, 0xbe, 0x45, 0xc3, 0x22, 0xde, 0xae, 0xa1, 0xea, 0x89, 0x26, 0x3a, 0x2b, 0x64, 0xb7, 0x5f, + 0xa1, 0x75, 0xeb, 0xc0, 0xfb, 0x68, 0xc7, 0x3e, 0xab, 0x1a, 0xa6, 0x20, 0x4d, 0x23, 0xa0, 0x94, + 0xe9, 0x65, 0xb3, 0xbb, 0xe5, 0x9f, 0x8f, 0x6c, 0x00, 0x32, 0x02, 0xae, 0x59, 0x02, 0x2a, 0xdc, + 0x76, 0xf0, 0x00, 0x64, 0xcf, 0xa2, 0xdd, 0xcf, 0x6b, 0xa8, 0xea, 0x7a, 0x3b, 0x31, 0xbb, 0x83, + 0x5f, 0x23, 0x74, 0x08, 0xda, 0xe9, 0xc5, 0xf7, 0xfd, 0xf2, 0xba, 0x95, 0x5f, 0xb0, 0xd1, 0x5c, + 0x14, 0x76, 0x6d, 0x9e, 0xa1, 0xed, 0x81, 0x84, 0x94, 0x48, 0xe8, 0x45, 0x9a, 0xe5, 0x44, 0x33, + 0xc1, 0xf1, 0x23, 0xdf, 0x8d, 0xb4, 0x0f, 0x34, 0x4b, 0x13, 0x16, 0x11, 0x0d, 0xd4, 0x66, 0xfe, + 0xa6, 0x8a, 0x1b, 0xfe, 0x07, 0xc6, 0x03, 0x54, 0x71, 0x4e, 0xc0, 0xbb, 0x7e, 0x31, 0xfe, 0x7f, + 0x69, 0xab, 0xae, 0xb1, 0x1c, 0xc1, 0xc7, 0x68, 0xfd, 0x6d, 0x9a, 0x30, 0x3e, 0xc5, 0xbb, 0x97, + 0x09, 0xb1, 0xb1, 0x23, 0x50, 0x8a, 0xc4, 0xe7, 0xf5, 0x96, 0x21, 0x78, 0x0f, 0x55, 0xfa, 0x62, + 0xc6, 0x4d, 0xc5, 0xbb, 0x17, 0xb8, 0xf3, 0x14, 0x75, 0x16, 0x05, 0xba, 0xef, 0xd0, 0xed, 0xd2, + 0xb0, 0x8e, 0x08, 0x27, 0x31, 0x48, 0xbc, 0x8f, 0x6e, 0x1c, 0x82, 0x76, 0x4b, 0x71, 0xef, 0xaf, + 0x99, 0x94, 0x96, 0xa7, 0x71, 0xe7, 0xd2, 0xe8, 0xc1, 0xd6, 0x97, 0x79, 0xd3, 0xfb, 0x3a, 0x6f, + 0x7a, 0x3f, 0xe6, 0x4d, 0xef, 0xd3, 0xcf, 0xe6, 0x95, 0xd1, 0xba, 0xf9, 0x6b, 0x9e, 0xfc, 0x0a, + 0x00, 0x00, 0xff, 0xff, 0x70, 0xbc, 0x3e, 0xb2, 0xa2, 0x04, 0x00, 0x00, } diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 3488e62d6..2c7d86141 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -349,20 +349,21 @@ func init() { } var fileDescriptorNoc = []byte{ - // 237 bytes of a gzipped FileDescriptorProto + // 245 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, - 0xcf, 0xcb, 0x4f, 0x06, 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0x66, 0x20, 0x53, 0x4a, - 0x97, 0x18, 0x7d, 0x40, 0x0c, 0xd1, 0x23, 0x65, 0x49, 0x8c, 0xf2, 0xf4, 0xc4, 0x92, 0xd4, 0xf2, - 0xc4, 0x4a, 0x18, 0x0d, 0xd5, 0x6a, 0x4e, 0x8c, 0xd6, 0xa2, 0xfc, 0xd2, 0x92, 0xd4, 0x22, 0x28, - 0x45, 0x8a, 0xc6, 0xa4, 0xa2, 0xfc, 0x6c, 0xa0, 0x46, 0x08, 0x45, 0x8a, 0x63, 0x33, 0x12, 0xf3, - 0x52, 0x72, 0x80, 0x3a, 0xa1, 0x34, 0x44, 0xab, 0xd1, 0x01, 0x46, 0x2e, 0x2e, 0xdf, 0xfc, 0xbc, - 0xcc, 0x92, 0xfc, 0x22, 0xa0, 0x26, 0x21, 0x1d, 0x2e, 0x5e, 0x77, 0x88, 0x67, 0x82, 0x4b, 0x12, - 0x4b, 0x4a, 0x8b, 0x85, 0xf8, 0xf5, 0x60, 0x9e, 0x83, 0x08, 0x48, 0x71, 0xe8, 0x81, 0x02, 0xc9, - 0x31, 0x39, 0x5b, 0x83, 0x51, 0x48, 0x8b, 0x8b, 0x27, 0x08, 0xec, 0x01, 0xa8, 0x62, 0x3e, 0x3d, - 0xa8, 0x7f, 0xb0, 0xab, 0x75, 0x02, 0xbb, 0x19, 0xae, 0x16, 0xea, 0x05, 0x2c, 0x6a, 0x81, 0xae, - 0xf0, 0x80, 0xb8, 0x12, 0xee, 0x0a, 0x98, 0xab, 0x31, 0x55, 0x3b, 0x09, 0x9c, 0x78, 0x24, 0xc7, - 0x78, 0x01, 0x88, 0x1f, 0x00, 0xf1, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0xbf, 0x19, 0x03, - 0x02, 0x00, 0x00, 0xff, 0xff, 0x34, 0xb1, 0xcc, 0x36, 0x2c, 0x02, 0x00, 0x00, + 0xcf, 0xcb, 0x4f, 0x06, 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xbc, 0xfc, 0x64, + 0x29, 0x5d, 0x62, 0xf4, 0x25, 0x16, 0x64, 0x42, 0xf4, 0x48, 0x59, 0x12, 0xa3, 0x3c, 0x3d, 0xb1, + 0x24, 0xb5, 0x3c, 0xb1, 0x12, 0x46, 0x43, 0xb5, 0x9a, 0x13, 0xa3, 0xb5, 0x28, 0xbf, 0xb4, 0x24, + 0xb5, 0x08, 0x4a, 0x91, 0xa2, 0x31, 0xa9, 0x28, 0x3f, 0x3b, 0xb5, 0x08, 0x4a, 0x91, 0xe2, 0xd8, + 0x8c, 0xc4, 0xbc, 0x94, 0x9c, 0xd4, 0x22, 0x18, 0x0d, 0xd1, 0x6a, 0x74, 0x80, 0x91, 0x8b, 0xcb, + 0x37, 0x3f, 0x2f, 0xb3, 0x24, 0xbf, 0x28, 0x33, 0x2f, 0x5d, 0x48, 0x87, 0x8b, 0xd7, 0x1d, 0xe2, + 0x99, 0xe0, 0x92, 0xc4, 0x92, 0xd2, 0x62, 0x21, 0x7e, 0x3d, 0x98, 0xe7, 0x20, 0x02, 0x52, 0x1c, + 0x7a, 0xa0, 0x40, 0x72, 0x4c, 0xce, 0xd6, 0x60, 0x14, 0xd2, 0xe2, 0xe2, 0x09, 0x02, 0x7b, 0x00, + 0xaa, 0x98, 0x4f, 0x0f, 0xea, 0x1f, 0xec, 0x6a, 0x9d, 0xc0, 0x6e, 0x86, 0xab, 0x85, 0x7a, 0x01, + 0x8b, 0x5a, 0x1d, 0x2e, 0x5e, 0x0f, 0x88, 0x2b, 0xe1, 0xae, 0x80, 0xb9, 0x1a, 0x53, 0xb5, 0x93, + 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe3, 0xb1, + 0x1c, 0x43, 0x12, 0x1b, 0xd8, 0x6f, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x34, 0xb1, 0xcc, + 0x36, 0x2c, 0x02, 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 1c4a1c8ec..3fd54758a 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -1113,38 +1113,39 @@ func init() { } var fileDescriptorDevice = []byte{ - // 527 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xc1, 0x8e, 0xd3, 0x3c, - 0x10, 0xc7, 0xbf, 0x7e, 0x4b, 0xdb, 0xc4, 0xb4, 0xa2, 0x0a, 0x5a, 0xc9, 0x14, 0xb4, 0xa0, 0x3d, - 0x00, 0x97, 0x4d, 0x44, 0x2b, 0xe0, 0xdc, 0x6e, 0x17, 0x54, 0x21, 0x2a, 0x91, 0xee, 0x9e, 0x23, - 0x37, 0x99, 0xb6, 0x56, 0x82, 0x1d, 0x25, 0x0e, 0x51, 0xdf, 0x84, 0x17, 0xe0, 0x05, 0x78, 0x0a, - 0x4e, 0x88, 0x33, 0x07, 0x84, 0xe0, 0x45, 0x18, 0xdb, 0x65, 0x05, 0x95, 0xd0, 0x8a, 0x9e, 0x38, - 0x58, 0xf2, 0xfc, 0xe7, 0x3f, 0xbf, 0x19, 0x27, 0x71, 0xc8, 0x68, 0xc5, 0xd5, 0xba, 0x5a, 0xf8, - 0xb1, 0x7c, 0x1d, 0x9c, 0xaf, 0xe1, 0x7c, 0xcd, 0xc5, 0xaa, 0x9c, 0x81, 0xaa, 0x65, 0x91, 0x06, - 0x4a, 0x89, 0x80, 0xe5, 0x3c, 0xc8, 0x0b, 0xa9, 0x64, 0x2c, 0xb3, 0x20, 0x93, 0x05, 0xab, 0x99, - 0x08, 0x12, 0x78, 0xc3, 0x63, 0xf0, 0x8d, 0xee, 0xb5, 0xb7, 0x6a, 0xff, 0xe4, 0x17, 0xd6, 0x4a, - 0xae, 0xa4, 0xad, 0x5b, 0x54, 0x4b, 0x13, 0x99, 0xc0, 0xec, 0x6c, 0xdd, 0x6f, 0xf6, 0x3f, 0xb6, - 0xc6, 0x65, 0xed, 0xc7, 0xef, 0x1b, 0xa4, 0x37, 0x31, 0x7d, 0xa7, 0x09, 0x08, 0xc5, 0x97, 0x1c, - 0x0a, 0x6f, 0x46, 0xda, 0x2c, 0xcf, 0x23, 0xa8, 0x38, 0x6d, 0xdc, 0x6b, 0x3c, 0xec, 0x8c, 0x1f, - 0x7f, 0xfe, 0x72, 0xf7, 0xd1, 0x55, 0xe0, 0x58, 0x16, 0x10, 0xa8, 0x4d, 0x0e, 0xa5, 0x3f, 0xca, - 0xf3, 0xb3, 0x8b, 0x69, 0xd8, 0x42, 0xca, 0x59, 0xc5, 0x35, 0x0f, 0xcf, 0x66, 0x78, 0xff, 0xef, - 0xc5, 0xc3, 0x09, 0x0d, 0x0f, 0x29, 0xc8, 0x3b, 0xfe, 0xd8, 0x24, 0x2d, 0x3b, 0xf4, 0xbf, 0x3e, - 0xaa, 0x77, 0x48, 0x34, 0x39, 0xe2, 0x09, 0x3d, 0x40, 0x9c, 0x1b, 0x36, 0x31, 0x9a, 0x26, 0x5a, - 0xd6, 0x6d, 0x50, 0xbe, 0x66, 0x65, 0x8c, 0x50, 0x7e, 0x45, 0x1c, 0x2d, 0xb3, 0x24, 0x29, 0x68, - 0xd3, 0xb4, 0x7f, 0x82, 0xed, 0x07, 0x7f, 0xd7, 0x7e, 0x84, 0xd5, 0xa1, 0x3e, 0x85, 0xde, 0x78, - 0x21, 0x71, 0x45, 0x9d, 0x46, 0x65, 0x94, 0xc2, 0x86, 0xb6, 0xf6, 0x62, 0xce, 0xea, 0x74, 0xfe, - 0x02, 0x36, 0x61, 0x5b, 0xd8, 0x8d, 0x66, 0xea, 0x43, 0x59, 0x66, 0x7b, 0x2f, 0x26, 0x3e, 0x76, - 0xcb, 0x64, 0x76, 0xf3, 0xf3, 0x45, 0x6a, 0xa2, 0xb3, 0xef, 0x8b, 0xd4, 0x40, 0xfd, 0xb8, 0x35, - 0x8f, 0x12, 0x67, 0x19, 0xc5, 0x42, 0x45, 0x55, 0x4e, 0x5d, 0x04, 0x76, 0xc3, 0xd6, 0xf2, 0x54, - 0xa8, 0x8b, 0xdc, 0xbb, 0x43, 0x88, 0xcd, 0x24, 0xb2, 0x16, 0x94, 0x98, 0x9c, 0xa3, 0x73, 0x13, - 0x8c, 0xbd, 0x13, 0x72, 0x33, 0xe1, 0x25, 0x5b, 0x64, 0x10, 0x59, 0x57, 0xbc, 0x86, 0x38, 0xa5, - 0xd7, 0xd1, 0xe6, 0x84, 0xbd, 0x6d, 0xea, 0x19, 0xba, 0x4f, 0xb5, 0xee, 0x3d, 0x20, 0xbd, 0xaa, - 0x84, 0x72, 0x38, 0x88, 0x16, 0x5c, 0xd9, 0x0a, 0xda, 0x31, 0xde, 0xae, 0xd5, 0xc7, 0x5c, 0x69, - 0xb7, 0x77, 0x9b, 0xb8, 0x19, 0x2b, 0x55, 0x54, 0x02, 0x08, 0x7a, 0x88, 0x8e, 0x83, 0xd0, 0xd1, - 0xc2, 0x1c, 0xe3, 0xc1, 0xbb, 0x06, 0xe9, 0xda, 0x0f, 0xfa, 0x25, 0x13, 0x6c, 0x85, 0x57, 0xf0, - 0x29, 0x71, 0x9f, 0x83, 0xda, 0x7e, 0xe4, 0xb7, 0xfc, 0xed, 0xcf, 0xc0, 0xdf, 0xbd, 0xaa, 0xfd, - 0x1b, 0x3b, 0x29, 0xef, 0x3e, 0x71, 0xe7, 0x97, 0x85, 0xbb, 0xd9, 0xbe, 0xe3, 0xeb, 0xab, 0x3f, - 0xc2, 0xc1, 0x87, 0xa4, 0x33, 0x81, 0x0c, 0x14, 0x5c, 0xdd, 0xe3, 0xb2, 0x68, 0xdc, 0xfb, 0xf0, - 0xed, 0xa8, 0xf1, 0x09, 0xd7, 0x57, 0x5c, 0x6f, 0xbf, 0x1f, 0xfd, 0xb7, 0x68, 0x99, 0xdf, 0xc8, - 0xf0, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0x5a, 0xf8, 0xc1, 0xf2, 0x04, 0x00, 0x00, + // 537 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xdd, 0x6e, 0xd3, 0x30, + 0x14, 0xc7, 0x09, 0x63, 0x6d, 0x62, 0x36, 0x51, 0x19, 0x4d, 0x0a, 0x05, 0x75, 0xd5, 0x2e, 0xa0, + 0x37, 0x4b, 0x44, 0x2b, 0xe0, 0xba, 0x5f, 0xa0, 0x0a, 0x51, 0x89, 0x74, 0xbb, 0x8e, 0xdc, 0xf8, + 0xb4, 0xb5, 0x52, 0x6c, 0x2b, 0x71, 0x1a, 0xf5, 0x4d, 0x78, 0x01, 0x5e, 0x80, 0xa7, 0xe0, 0x0a, + 0x71, 0xbd, 0x8b, 0x09, 0x95, 0x17, 0x41, 0xb6, 0xcb, 0x04, 0x95, 0xd0, 0x44, 0xaf, 0xb8, 0x3b, + 0xfe, 0x9f, 0x73, 0x7e, 0x7f, 0x7f, 0xa3, 0xee, 0x9c, 0xa9, 0x45, 0x31, 0x0d, 0x12, 0xf1, 0x21, + 0xbc, 0x58, 0xc0, 0xc5, 0x82, 0xf1, 0x79, 0x3e, 0x06, 0x55, 0x8a, 0x2c, 0x0d, 0x95, 0xe2, 0x21, + 0x91, 0x2c, 0x94, 0x99, 0x50, 0x22, 0x11, 0xcb, 0x70, 0x29, 0x32, 0x52, 0x12, 0x1e, 0x52, 0x58, + 0xb1, 0x04, 0x02, 0xa3, 0xe3, 0xea, 0x56, 0xad, 0x9f, 0xff, 0xc6, 0x9a, 0x8b, 0xb9, 0xb0, 0x7d, + 0xd3, 0x62, 0x66, 0x46, 0x66, 0x60, 0x22, 0xdb, 0xf7, 0x47, 0xf9, 0x5f, 0xad, 0x89, 0x64, 0xb6, + 0xfc, 0xec, 0xb3, 0x83, 0x6a, 0x03, 0xe3, 0x3b, 0xa2, 0xc0, 0x15, 0x9b, 0x31, 0xc8, 0xf0, 0x18, + 0x55, 0x89, 0x94, 0x31, 0x14, 0xcc, 0x77, 0x9a, 0x4e, 0xeb, 0xa8, 0xf7, 0xe2, 0xea, 0xfa, 0xf4, + 0xf9, 0x6d, 0xe0, 0x44, 0x64, 0x10, 0xaa, 0xb5, 0x84, 0x3c, 0xe8, 0x4a, 0x39, 0xbc, 0x1c, 0x45, + 0x15, 0x22, 0xe5, 0xb0, 0x60, 0x9a, 0x47, 0x61, 0x65, 0x78, 0x77, 0xf7, 0xe2, 0x0d, 0x60, 0x65, + 0x78, 0x14, 0x56, 0xc3, 0x82, 0x9d, 0x7d, 0x3d, 0x44, 0x15, 0x3b, 0xe9, 0xff, 0x7d, 0xaa, 0xf8, + 0x04, 0x69, 0x72, 0xcc, 0xa8, 0x7f, 0xd0, 0x74, 0x5a, 0x5e, 0x74, 0x48, 0xa4, 0x1c, 0x51, 0x2d, + 0x6b, 0x1b, 0x46, 0xfd, 0x7b, 0x56, 0xa6, 0xb0, 0x1a, 0x51, 0xfc, 0x1e, 0xb9, 0x5a, 0x26, 0x94, + 0x66, 0xfe, 0xa1, 0xb1, 0x7f, 0x79, 0x75, 0x7d, 0xda, 0xfe, 0x37, 0xfb, 0x2e, 0xa5, 0x59, 0xa4, + 0x57, 0xa1, 0x03, 0x1c, 0x21, 0x8f, 0x97, 0x69, 0x9c, 0xc7, 0x29, 0xac, 0xfd, 0xca, 0x5e, 0xcc, + 0x71, 0x99, 0x4e, 0xde, 0xc2, 0x3a, 0xaa, 0x72, 0x1b, 0x68, 0xa6, 0x5e, 0x94, 0x65, 0x56, 0xf7, + 0x62, 0x76, 0xa5, 0xb4, 0x4c, 0x62, 0x83, 0x5f, 0x07, 0xa9, 0x89, 0xee, 0xbe, 0x07, 0xa9, 0x81, + 0x7a, 0xbb, 0x35, 0xcf, 0x47, 0xee, 0x2c, 0x4e, 0xb8, 0x8a, 0x0b, 0xe9, 0x7b, 0x4d, 0xa7, 0x75, + 0x1c, 0x55, 0x66, 0x7d, 0xae, 0x2e, 0x25, 0x7e, 0x82, 0x90, 0xcd, 0x50, 0x51, 0x72, 0x1f, 0x99, + 0x9c, 0xab, 0x73, 0x03, 0x51, 0x72, 0x7c, 0x8e, 0x1e, 0x52, 0x96, 0x93, 0xe9, 0x12, 0x62, 0x5b, + 0x95, 0x2c, 0x20, 0x49, 0xfd, 0xfb, 0x4d, 0xa7, 0xe5, 0x46, 0xb5, 0x6d, 0xea, 0x75, 0x9f, 0xab, + 0xbe, 0xd6, 0xf1, 0x33, 0x54, 0x2b, 0x72, 0xc8, 0x3b, 0xed, 0x78, 0xca, 0x94, 0xed, 0xf0, 0x8f, + 0x4c, 0xed, 0xb1, 0xd5, 0x7b, 0x4c, 0xe9, 0x6a, 0xfc, 0x18, 0x79, 0x4b, 0x92, 0xab, 0x38, 0x07, + 0xe0, 0xfe, 0x49, 0xd3, 0x69, 0x1d, 0x44, 0xae, 0x16, 0x26, 0x00, 0xbc, 0xfd, 0xc9, 0x41, 0xc7, + 0xf6, 0x42, 0xbf, 0x23, 0x9c, 0xcc, 0x21, 0xc3, 0xaf, 0x90, 0xf7, 0x06, 0xd4, 0xf6, 0x92, 0x3f, + 0x0a, 0xb6, 0x9f, 0x41, 0xb0, 0xfb, 0x54, 0xeb, 0x0f, 0x76, 0x52, 0xf8, 0x29, 0xf2, 0x26, 0x37, + 0x8d, 0xbb, 0xd9, 0xba, 0x1b, 0xe8, 0xa7, 0xdf, 0x4d, 0x52, 0xdc, 0x41, 0x47, 0x03, 0x58, 0x82, + 0x82, 0xdb, 0x3d, 0x6e, 0x9a, 0x7a, 0xb5, 0x2f, 0x9b, 0x86, 0xf3, 0x6d, 0xd3, 0x70, 0xbe, 0x6f, + 0x1a, 0xce, 0xc7, 0x1f, 0x8d, 0x3b, 0xd3, 0x8a, 0xf9, 0x46, 0x3a, 0x3f, 0x03, 0x00, 0x00, 0xff, + 0xff, 0xff, 0x5a, 0xf8, 0xc1, 0xf2, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 47b5902f9..c6f883c4a 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -1108,41 +1108,42 @@ func init() { } var fileDescriptorLorawan = []byte{ - // 566 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x53, 0xcf, 0x4e, 0xdb, 0x30, - 0x18, 0x27, 0xb4, 0x24, 0xe5, 0x83, 0x42, 0x64, 0x34, 0x2d, 0xdb, 0x24, 0x98, 0x38, 0x21, 0xa4, - 0x35, 0xfd, 0x03, 0xa4, 0x3d, 0x16, 0xca, 0xa4, 0x09, 0x28, 0x9a, 0xa1, 0x67, 0x2b, 0x4d, 0x9c, - 0x10, 0xb5, 0xc4, 0x91, 0xeb, 0xd0, 0xf6, 0x4d, 0xf6, 0x12, 0xbb, 0xed, 0x21, 0xa6, 0x9d, 0x76, - 0xde, 0x61, 0x9a, 0xb6, 0x17, 0x99, 0xed, 0x50, 0xc6, 0x6d, 0x1a, 0xb7, 0x1d, 0xac, 0xf8, 0xf7, - 0xfd, 0xfe, 0xd8, 0xc9, 0xf7, 0x05, 0x8e, 0xe3, 0x44, 0xdc, 0xe4, 0xc3, 0x5a, 0xc0, 0x6e, 0xdd, - 0xeb, 0x1b, 0x7a, 0x7d, 0x93, 0xa4, 0xf1, 0xa4, 0x4f, 0xc5, 0x94, 0xf1, 0x91, 0x2b, 0x44, 0xea, - 0xfa, 0x59, 0xe2, 0x66, 0x9c, 0x09, 0x16, 0xb0, 0xb1, 0x3b, 0x66, 0xdc, 0x9f, 0xfa, 0xe9, 0xe2, - 0x59, 0xd3, 0x04, 0xb2, 0xee, 0xe1, 0xcb, 0x37, 0x8f, 0xc2, 0x62, 0x16, 0xb3, 0xc2, 0x38, 0xcc, - 0x23, 0x8d, 0x34, 0xd0, 0xbb, 0xc2, 0xb7, 0xfb, 0xd1, 0x80, 0xca, 0x05, 0x15, 0x7e, 0xe8, 0x0b, - 0x1f, 0xb5, 0x00, 0x6e, 0x59, 0x98, 0x8f, 0x7d, 0x91, 0xb0, 0xd4, 0x59, 0x7b, 0x6d, 0xec, 0x6d, - 0x34, 0xb7, 0x6a, 0x8b, 0x83, 0x2e, 0x1e, 0x28, 0xfc, 0x48, 0x86, 0x5e, 0xc1, 0xaa, 0x32, 0x13, - 0xee, 0x0b, 0xea, 0xac, 0x4b, 0xcf, 0x2a, 0xae, 0xa8, 0x02, 0x96, 0x18, 0xbd, 0x80, 0xca, 0x30, - 0x11, 0x05, 0x57, 0x95, 0x5c, 0x15, 0x5b, 0x12, 0x6b, 0x6a, 0x07, 0xd6, 0x02, 0x16, 0xca, 0x57, - 0x2d, 0xd8, 0x0d, 0xed, 0x84, 0xa2, 0xa4, 0x05, 0x5b, 0xb0, 0x12, 0x91, 0x20, 0x15, 0xce, 0xa6, - 0x36, 0x96, 0xa3, 0x93, 0x54, 0xec, 0x7e, 0x32, 0x60, 0xf3, 0x7a, 0x76, 0xc2, 0xd2, 0x28, 0x89, - 0x73, 0x5e, 0xdc, 0xe0, 0x3f, 0xb8, 0xf6, 0x97, 0x12, 0xa0, 0x6e, 0x20, 0x92, 0x3b, 0x7d, 0xf8, - 0xc3, 0x07, 0xef, 0x83, 0xe5, 0x67, 0x19, 0xa1, 0x79, 0xe2, 0x18, 0x52, 0xbd, 0x7e, 0x7c, 0xf8, - 0xed, 0xfb, 0x4e, 0xe3, 0x6f, 0xe3, 0x10, 0x30, 0x4e, 0x5d, 0x31, 0xcf, 0xe8, 0xa4, 0xd6, 0xcd, - 0xb2, 0xd3, 0xc1, 0x3b, 0x6c, 0xca, 0x94, 0xd3, 0x3c, 0x51, 0x79, 0x21, 0xbd, 0xd3, 0x79, 0xcb, - 0x4f, 0xca, 0xeb, 0xd1, 0x3b, 0x9d, 0x27, 0x53, 0x54, 0xde, 0x7b, 0xa8, 0xa8, 0x3c, 0x3f, 0x0c, - 0xb9, 0x53, 0xd2, 0x81, 0x47, 0x32, 0xb0, 0xf9, 0x6f, 0x81, 0x5d, 0xe9, 0xc6, 0xea, 0x5e, 0x6a, - 0x83, 0x30, 0xac, 0xa6, 0xd3, 0x11, 0x99, 0x90, 0x11, 0x9d, 0x3b, 0xe5, 0x27, 0x65, 0xf6, 0xa7, - 0xa3, 0xab, 0x33, 0x3a, 0xc7, 0x56, 0x5a, 0x6c, 0xd0, 0x2e, 0x54, 0xf9, 0xac, 0x41, 0x42, 0x4e, - 0x58, 0x14, 0x4d, 0xa8, 0xd0, 0x33, 0x50, 0xc5, 0x6b, 0xb2, 0xd8, 0xe3, 0x97, 0xba, 0x84, 0x9e, - 0x81, 0xc9, 0x67, 0x4d, 0xa9, 0xd1, 0xcd, 0xae, 0xe2, 0x15, 0x89, 0x7a, 0x5c, 0x75, 0x9a, 0xcf, - 0x48, 0x48, 0xc7, 0xfe, 0x7c, 0xd1, 0x69, 0x3e, 0xeb, 0x29, 0x88, 0x9e, 0x83, 0x15, 0x44, 0x64, - 0x9c, 0x4c, 0x84, 0xec, 0x72, 0x69, 0xaf, 0x8c, 0xcd, 0x20, 0x3a, 0x97, 0x68, 0x7f, 0x07, 0xe0, - 0xcf, 0x50, 0xa1, 0x0a, 0x94, 0xcf, 0x2f, 0x71, 0xd7, 0x5e, 0x42, 0x16, 0x94, 0xde, 0x5e, 0x9d, - 0xd9, 0xc6, 0x7e, 0x08, 0x26, 0xa6, 0xb1, 0x22, 0x37, 0x00, 0x4e, 0x07, 0xa4, 0x7d, 0xd4, 0x22, - 0x6d, 0xaf, 0x2e, 0x25, 0x12, 0x0f, 0xae, 0x48, 0xa7, 0xde, 0x24, 0x9d, 0x66, 0xdb, 0x36, 0x14, - 0x3e, 0xe9, 0x13, 0xcf, 0xeb, 0x10, 0xaf, 0xed, 0xd9, 0xcb, 0x08, 0xc0, 0x94, 0xfa, 0x83, 0x56, - 0xcb, 0x2e, 0x29, 0xae, 0x3b, 0x20, 0x9d, 0xc6, 0xa1, 0xd6, 0x96, 0xef, 0xb5, 0x07, 0x5e, 0x9d, - 0x1c, 0x36, 0xea, 0xf6, 0xca, 0xb1, 0xfd, 0xf9, 0xe7, 0xb6, 0xf1, 0x55, 0xae, 0x1f, 0x72, 0x7d, - 0xf8, 0xb5, 0xbd, 0x34, 0x34, 0xf5, 0x3f, 0xdd, 0xfa, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xf7, - 0x61, 0x93, 0x51, 0x04, 0x00, 0x00, + // 580 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x93, 0xcf, 0x4f, 0x1a, 0x41, + 0x14, 0xc7, 0x5d, 0x41, 0xc0, 0xa7, 0xe0, 0x66, 0x4d, 0xd3, 0x6d, 0x9b, 0x00, 0xf1, 0x44, 0x4c, + 0xca, 0x4f, 0x15, 0x38, 0xa2, 0xd0, 0xa4, 0x51, 0x31, 0x1d, 0xe5, 0x3c, 0x19, 0x76, 0x66, 0xd7, + 0x09, 0xb8, 0xb3, 0x19, 0x06, 0x58, 0xfe, 0x93, 0xfe, 0x13, 0xbd, 0xf5, 0x8f, 0x68, 0x7a, 0xea, + 0xd9, 0x83, 0x69, 0xec, 0x3f, 0xd2, 0xcc, 0xac, 0x5a, 0x6f, 0x4d, 0xbd, 0xf5, 0xc4, 0xfb, 0xbe, + 0xef, 0x7b, 0x9f, 0x79, 0xe1, 0xbd, 0x85, 0xe3, 0x80, 0xab, 0xeb, 0xf9, 0xb8, 0xea, 0x89, 0x9b, + 0xda, 0xd5, 0x35, 0xbb, 0xba, 0xe6, 0x61, 0x30, 0x1b, 0x32, 0xb5, 0x14, 0x72, 0x52, 0x53, 0x2a, + 0xac, 0x91, 0x88, 0xd7, 0x22, 0x29, 0x94, 0xf0, 0xc4, 0xb4, 0x36, 0x15, 0x92, 0x2c, 0x49, 0xf8, + 0xf8, 0x5b, 0x35, 0x86, 0x93, 0x7d, 0x90, 0x6f, 0xdf, 0x3f, 0x83, 0x05, 0x22, 0x10, 0x49, 0xe3, + 0x78, 0xee, 0x1b, 0x65, 0x84, 0x89, 0x92, 0xbe, 0xbd, 0x2f, 0x16, 0xe4, 0xce, 0x99, 0x22, 0x94, + 0x28, 0xe2, 0xb4, 0x00, 0x6e, 0x04, 0x9d, 0x4f, 0x89, 0xe2, 0x22, 0x74, 0xb7, 0xca, 0x56, 0xa5, + 0xd0, 0xdc, 0xad, 0x3e, 0x3e, 0x74, 0xfe, 0x64, 0xa1, 0x67, 0x65, 0xce, 0x3b, 0xd8, 0xd4, 0xcd, + 0x58, 0x12, 0xc5, 0xdc, 0xed, 0xb2, 0x55, 0xd9, 0x44, 0x39, 0x9d, 0x40, 0x44, 0x31, 0xe7, 0x0d, + 0xe4, 0xc6, 0x5c, 0x25, 0x5e, 0xbe, 0x6c, 0x55, 0xf2, 0x28, 0x3b, 0xe6, 0xca, 0x58, 0x25, 0xd8, + 0xf2, 0x04, 0xe5, 0x61, 0x90, 0xb8, 0x05, 0xd3, 0x09, 0x49, 0xca, 0x14, 0xec, 0xc2, 0x86, 0x8f, + 0xbd, 0x50, 0xb9, 0x3b, 0xa6, 0x31, 0xed, 0x9f, 0x84, 0x6a, 0xef, 0xab, 0x05, 0x3b, 0x57, 0xf1, + 0x89, 0x08, 0x7d, 0x1e, 0xcc, 0x65, 0x32, 0xc1, 0x7f, 0x30, 0xf6, 0xf7, 0x14, 0x38, 0x3d, 0x4f, + 0xf1, 0x85, 0x79, 0xfc, 0xe9, 0x0f, 0x1f, 0x42, 0x96, 0x44, 0x11, 0x66, 0x73, 0xee, 0x5a, 0x65, + 0xab, 0xb2, 0x7d, 0x7c, 0x78, 0x7b, 0x57, 0x6a, 0xfc, 0xed, 0x1c, 0x3c, 0x21, 0x59, 0x4d, 0xad, + 0x22, 0x36, 0xab, 0xf6, 0xa2, 0x68, 0x30, 0xfa, 0x88, 0x32, 0x24, 0x8a, 0x06, 0x73, 0xae, 0x79, + 0x94, 0x2d, 0x0c, 0x6f, 0xfd, 0x45, 0xbc, 0x3e, 0x5b, 0x18, 0x1e, 0x65, 0x0b, 0xcd, 0xfb, 0x04, + 0x39, 0xcd, 0x23, 0x94, 0x4a, 0x37, 0x65, 0x80, 0x47, 0xb7, 0x77, 0xa5, 0xe6, 0xbf, 0x01, 0x7b, + 0x94, 0x4a, 0xa4, 0xe7, 0xd2, 0x81, 0x83, 0x60, 0x33, 0x5c, 0x4e, 0xf0, 0x0c, 0x4f, 0xd8, 0xca, + 0x4d, 0xbf, 0x88, 0x39, 0x5c, 0x4e, 0x2e, 0x4f, 0xd9, 0x0a, 0x65, 0xc3, 0x24, 0x70, 0xf6, 0x20, + 0x2f, 0xe3, 0x06, 0xa6, 0x12, 0x0b, 0xdf, 0x9f, 0x31, 0x65, 0x6e, 0x20, 0x8f, 0xb6, 0x64, 0xdc, + 0xe8, 0xcb, 0x0b, 0x93, 0x72, 0x5e, 0x41, 0x46, 0xc6, 0x4d, 0x4c, 0xa5, 0x59, 0x76, 0x1e, 0x6d, + 0xc8, 0xb8, 0xd9, 0x97, 0x7a, 0xd3, 0x32, 0xc6, 0x94, 0x4d, 0xc9, 0xea, 0x71, 0xd3, 0x32, 0xee, + 0x6b, 0xe9, 0xbc, 0x86, 0xac, 0xe7, 0xe3, 0x29, 0x9f, 0x29, 0xb7, 0x50, 0x4e, 0x55, 0xd2, 0x28, + 0xe3, 0xf9, 0x67, 0x7c, 0xa6, 0xf6, 0x4b, 0x00, 0x7f, 0x8e, 0xca, 0xc9, 0x41, 0xfa, 0xec, 0x02, + 0xf5, 0xec, 0x35, 0x27, 0x0b, 0xa9, 0x0f, 0x97, 0xa7, 0xb6, 0xb5, 0x4f, 0x21, 0x83, 0x58, 0xa0, + 0xcd, 0x02, 0xc0, 0x60, 0x84, 0x3b, 0x47, 0x2d, 0xdc, 0x69, 0xd7, 0xed, 0x35, 0xad, 0x47, 0x97, + 0xb8, 0x5b, 0x6f, 0xe2, 0x6e, 0xb3, 0x63, 0x5b, 0x5a, 0x9f, 0x0c, 0x71, 0xbb, 0xdd, 0xc5, 0xed, + 0x4e, 0xdb, 0x5e, 0x77, 0x00, 0x32, 0x83, 0x11, 0x3e, 0x68, 0xb5, 0xec, 0x94, 0xf6, 0x7a, 0x23, + 0xdc, 0x6d, 0x1c, 0x9a, 0xda, 0xf4, 0x43, 0xed, 0x41, 0xbb, 0x8e, 0x0f, 0x1b, 0x75, 0x7b, 0xe3, + 0xd8, 0xfe, 0x76, 0x5f, 0xb4, 0x7e, 0xdc, 0x17, 0xad, 0x9f, 0xf7, 0x45, 0xeb, 0xf3, 0xaf, 0xe2, + 0xda, 0x38, 0x63, 0xbe, 0xe9, 0xd6, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xf7, 0x61, 0x93, + 0x51, 0x04, 0x00, 0x00, } diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index bc217ac63..077ad4fa8 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -882,7 +882,7 @@ func init() { } var fileDescriptorProtocol = []byte{ - // 211 bytes of a gzipped FileDescriptorProto + // 215 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, @@ -894,7 +894,7 @@ var fileDescriptorProtocol = []byte{ 0x15, 0xce, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0xa5, 0x45, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x42, 0x26, 0xe8, 0xa6, 0x49, 0xc0, 0x4d, 0x43, 0x53, 0x8a, 0xcb, 0xd0, 0x48, 0x2e, 0x21, 0xc7, 0xe4, 0x92, 0xcc, 0x32, 0xb0, 0x22, 0xb8, 0x2b, 0xcd, 0xd1, 0xcd, 0x95, 0x86, 0x9b, 0x8b, 0xa9, 0x1a, 0x87, - 0xd1, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x00, 0xe2, 0x07, 0x40, 0x3c, 0xe3, 0xb1, 0x1c, - 0x43, 0x12, 0x1b, 0x58, 0xce, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x86, 0x56, 0x63, 0x9b, 0x9e, - 0x01, 0x00, 0x00, + 0xd1, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, + 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x39, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x86, + 0x56, 0x63, 0x9b, 0x9e, 0x01, 0x00, 0x00, } diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 9c2cc98e7..d6f644247 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -2387,55 +2387,56 @@ func init() { } var fileDescriptorRouter = []byte{ - // 797 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x05, 0xb9, 0xe9, 0x24, 0x69, 0xd2, 0x6d, 0xd2, 0x9a, 0x00, 0x6d, 0xe5, 0x03, 0x94, - 0x9f, 0x26, 0x34, 0xa8, 0x42, 0x15, 0x02, 0x91, 0xd0, 0xaa, 0x42, 0x22, 0x15, 0x72, 0xdb, 0x0b, - 0x12, 0x8a, 0x36, 0xce, 0xd6, 0xb5, 0x92, 0xda, 0xc6, 0xbb, 0xee, 0xcf, 0x63, 0x70, 0xe3, 0x21, - 0x78, 0x10, 0x0e, 0x1c, 0x38, 0x71, 0xe0, 0x80, 0x10, 0xbc, 0x02, 0x17, 0x6e, 0x6c, 0xd6, 0x6b, - 0xc7, 0x4e, 0x5b, 0x68, 0xe1, 0x60, 0xd9, 0x33, 0xf3, 0xed, 0x37, 0xf3, 0xcd, 0xee, 0x8e, 0xe1, - 0xa1, 0x65, 0xb3, 0xbd, 0xa0, 0x53, 0x35, 0xdd, 0xfd, 0xda, 0xf6, 0x1e, 0xd9, 0xde, 0xb3, 0x1d, - 0x8b, 0x6e, 0x12, 0x76, 0xe8, 0xfa, 0xbd, 0x1a, 0x63, 0x4e, 0x0d, 0x7b, 0x76, 0xcd, 0x77, 0x03, - 0x46, 0x7c, 0xf9, 0xaa, 0x7a, 0xbe, 0xcb, 0x5c, 0xa4, 0x86, 0x56, 0x65, 0x29, 0x41, 0x60, 0xb9, - 0x96, 0x5b, 0x13, 0xe1, 0x4e, 0xb0, 0x2b, 0x2c, 0x61, 0x88, 0xaf, 0x70, 0x59, 0x0a, 0x7e, 0x66, - 0x3e, 0xfe, 0x48, 0xf8, 0xa3, 0xf3, 0xc0, 0x05, 0xd4, 0x74, 0xfb, 0xf1, 0x87, 0x5c, 0xbc, 0x7a, - 0x9e, 0xc5, 0x16, 0x66, 0xe4, 0x10, 0x1f, 0x47, 0xef, 0x70, 0xa9, 0x8e, 0xa0, 0xb8, 0x15, 0x74, - 0xa8, 0xe9, 0xdb, 0x1d, 0x62, 0x90, 0x37, 0x01, 0xa1, 0x4c, 0x7f, 0xaf, 0x40, 0x7e, 0xc7, 0xeb, - 0xdb, 0x4e, 0xaf, 0x45, 0x28, 0xc5, 0x16, 0x41, 0x1a, 0x8c, 0x7b, 0xf8, 0xb8, 0xef, 0xe2, 0xae, - 0xa6, 0x2c, 0x28, 0x8b, 0x39, 0x23, 0x32, 0x51, 0x03, 0xa6, 0xa2, 0x62, 0xda, 0xfb, 0x84, 0xe1, - 0x2e, 0x66, 0x58, 0xcb, 0x72, 0x4c, 0xb6, 0x5e, 0xaa, 0xc6, 0x65, 0x1a, 0x47, 0x2d, 0x19, 0x33, - 0x8a, 0x91, 0x33, 0xf2, 0xa0, 0x27, 0x50, 0x94, 0x35, 0x0d, 0x19, 0x72, 0x82, 0x61, 0xba, 0x1a, - 0x15, 0x9b, 0x20, 0x28, 0x48, 0x5f, 0xe4, 0xd0, 0x3f, 0x2a, 0x50, 0x58, 0x73, 0x0f, 0x9d, 0xf3, - 0x15, 0xfc, 0x12, 0x66, 0xe2, 0x82, 0x4d, 0xd7, 0xd9, 0xb5, 0xad, 0xc0, 0xc7, 0xcc, 0x76, 0x1d, - 0x59, 0xf5, 0xd5, 0x61, 0xd5, 0xdb, 0x47, 0xcf, 0x92, 0x00, 0xa3, 0x1c, 0x45, 0x52, 0x6e, 0xd4, - 0x82, 0x72, 0x54, 0x7f, 0x9a, 0x30, 0x14, 0xa1, 0xc5, 0x22, 0x46, 0xf9, 0x4a, 0x32, 0x90, 0xf2, - 0xea, 0x9f, 0xc7, 0x60, 0x76, 0x8d, 0x1c, 0xd8, 0x26, 0x69, 0x98, 0xcc, 0x3e, 0x08, 0xa1, 0xe1, - 0xce, 0xfc, 0x41, 0xd6, 0x26, 0x8c, 0x77, 0xc9, 0x41, 0x9b, 0x04, 0xb6, 0xd0, 0x91, 0x6b, 0xae, - 0x7c, 0xf9, 0x3a, 0xbf, 0xfc, 0xb7, 0x73, 0x61, 0xba, 0x3e, 0xa9, 0xb1, 0x63, 0x8f, 0xd0, 0x2a, - 0x4f, 0xb9, 0xbe, 0xf3, 0xdc, 0x50, 0x39, 0xcb, 0x7a, 0x60, 0x0f, 0xf8, 0xb0, 0xe7, 0x09, 0xbe, - 0xdc, 0x3f, 0xf1, 0x35, 0x3c, 0x4f, 0xf0, 0x71, 0x96, 0x01, 0xdf, 0xa9, 0xe7, 0xa4, 0xfc, 0xdf, - 0xe7, 0x64, 0xe6, 0x02, 0xe7, 0xa4, 0x02, 0xda, 0xc9, 0xbe, 0x52, 0xcf, 0x75, 0x28, 0xd1, 0x7d, - 0x28, 0x6d, 0x84, 0xf0, 0x2d, 0x86, 0x59, 0x40, 0xa3, 0x86, 0xbf, 0x82, 0x6c, 0x94, 0x73, 0xd0, - 0x0a, 0xd1, 0xf4, 0xe6, 0x2a, 0x6f, 0xc5, 0xca, 0x05, 0x5a, 0x21, 0x99, 0x07, 0xed, 0x00, 0xc9, - 0xc6, 0x5b, 0xa2, 0xbf, 0x86, 0xf2, 0x48, 0xce, 0xb0, 0x18, 0x74, 0x0d, 0x26, 0xfa, 0x98, 0xb2, - 0x36, 0x25, 0xc4, 0x11, 0x29, 0x2f, 0x1b, 0x99, 0x81, 0x63, 0x8b, 0xdb, 0xe8, 0x16, 0xa8, 0x54, - 0xc0, 0xb5, 0x31, 0xa1, 0xbd, 0x10, 0x6b, 0x97, 0x2c, 0x32, 0xac, 0x17, 0x20, 0x9f, 0xd2, 0xa2, - 0xff, 0x1a, 0x03, 0x35, 0xf4, 0xa0, 0x65, 0x98, 0x8c, 0x64, 0x49, 0x32, 0x45, 0x90, 0x41, 0x75, - 0x30, 0x91, 0x0c, 0x1e, 0xa2, 0x46, 0xde, 0x4a, 0x16, 0xc7, 0xf3, 0x16, 0xf0, 0xa0, 0x6f, 0xa4, - 0x2d, 0xfd, 0x54, 0xbb, 0xc2, 0xd7, 0xe4, 0x8d, 0xc9, 0xd0, 0x2d, 0xa5, 0x50, 0xa4, 0x83, 0x1a, - 0x88, 0xe1, 0x21, 0x2f, 0x54, 0x92, 0x53, 0x46, 0xd0, 0x4d, 0xc8, 0x74, 0xe5, 0x8d, 0x95, 0x87, - 0x20, 0x89, 0x8a, 0x63, 0xe8, 0x1e, 0x64, 0x71, 0xbc, 0x59, 0x54, 0x9b, 0x3f, 0x01, 0x4d, 0x86, - 0xd1, 0x63, 0x28, 0x25, 0xcc, 0x36, 0x36, 0x4d, 0xe2, 0x31, 0xd2, 0xd5, 0x16, 0x4e, 0x2c, 0x9b, - 0x4e, 0xe0, 0x1a, 0x12, 0x86, 0x96, 0x00, 0xf1, 0xfb, 0xeb, 0x10, 0x93, 0x1b, 0x43, 0x91, 0xb7, - 0x85, 0xc8, 0xa9, 0x38, 0x12, 0xeb, 0xbc, 0x0b, 0x43, 0x67, 0xbb, 0xe3, 0xbb, 0x3d, 0xe2, 0x53, - 0xed, 0x8e, 0x40, 0x17, 0xe3, 0x40, 0x33, 0xf4, 0xd7, 0x7f, 0x2a, 0xa0, 0x1a, 0xe2, 0x3f, 0xc2, - 0x35, 0xe5, 0x53, 0xdb, 0x8e, 0x46, 0x77, 0xb0, 0x92, 0x11, 0x95, 0x36, 0xcc, 0xde, 0xa2, 0xc2, - 0xb3, 0xa8, 0xe1, 0x28, 0x46, 0xe5, 0xaa, 0xfc, 0x2d, 0xa5, 0x46, 0x73, 0x0a, 0xfc, 0x14, 0x26, - 0xe2, 0x61, 0x8e, 0xb4, 0x08, 0x3f, 0x3a, 0xdf, 0x2b, 0xb3, 0x51, 0x64, 0x64, 0x6a, 0xde, 0x57, - 0xf8, 0x2c, 0xcb, 0xc8, 0xdb, 0x41, 0xd0, 0x7c, 0x0c, 0x3b, 0x7d, 0x1a, 0x55, 0x16, 0xce, 0x06, - 0x84, 0x27, 0xb9, 0xfe, 0x96, 0xff, 0x49, 0x42, 0xd9, 0x2d, 0xec, 0xf0, 0x0c, 0x3e, 0x7a, 0x31, - 0xaa, 0xfe, 0x7a, 0x44, 0x72, 0xda, 0xfd, 0xab, 0xdc, 0x38, 0x23, 0x2a, 0x6f, 0x4a, 0x1d, 0x26, - 0x36, 0x08, 0x93, 0x4c, 0x71, 0x83, 0xd2, 0x14, 0x93, 0x69, 0x77, 0xb3, 0xf8, 0xe1, 0xfb, 0x9c, - 0xf2, 0x89, 0x3f, 0xdf, 0xf8, 0xf3, 0xee, 0xc7, 0xdc, 0xa5, 0x8e, 0x2a, 0x46, 0xcd, 0x83, 0xdf, - 0x01, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xce, 0x3a, 0x9a, 0x23, 0x08, 0x00, 0x00, + // 806 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0xc1, 0x6e, 0xeb, 0x44, + 0x14, 0xc5, 0x05, 0xf9, 0xa5, 0x37, 0x49, 0x93, 0x37, 0x2f, 0x79, 0x35, 0x01, 0x92, 0xc8, 0x0b, + 0x1a, 0x68, 0x9b, 0xd0, 0xa0, 0x0a, 0x55, 0x08, 0x44, 0x42, 0xab, 0x0a, 0x89, 0x54, 0xc8, 0x6d, + 0x37, 0x48, 0x28, 0x9a, 0x38, 0xb7, 0xae, 0x95, 0xd4, 0x63, 0x3c, 0xe3, 0xb4, 0xfd, 0x0c, 0x76, + 0x7c, 0x04, 0x1f, 0xc2, 0x82, 0x05, 0x2b, 0x16, 0x5d, 0x54, 0xa8, 0xfc, 0x02, 0x1b, 0x76, 0x28, + 0xe3, 0xb1, 0x63, 0xa7, 0x2d, 0xb4, 0xb0, 0x4a, 0xe6, 0xdc, 0x33, 0x67, 0xee, 0xb9, 0x73, 0xe7, + 0x1a, 0x3e, 0x71, 0x5c, 0x71, 0x1e, 0x8e, 0xda, 0x36, 0xbb, 0xe8, 0x9c, 0x9c, 0xe3, 0xc9, 0xb9, + 0xeb, 0x39, 0xfc, 0x08, 0xc5, 0x25, 0x0b, 0x26, 0x1d, 0x21, 0xbc, 0x0e, 0xf5, 0xdd, 0x4e, 0xc0, + 0x42, 0x81, 0x81, 0xfa, 0x69, 0xfb, 0x01, 0x13, 0x8c, 0xe8, 0xd1, 0xaa, 0xb6, 0x9d, 0x12, 0x70, + 0x98, 0xc3, 0x3a, 0x32, 0x3c, 0x0a, 0xcf, 0xe4, 0x4a, 0x2e, 0xe4, 0xbf, 0x68, 0x5b, 0x86, 0xfe, + 0xe8, 0x79, 0xd4, 0x77, 0x15, 0xfd, 0xd3, 0xa7, 0xd0, 0x25, 0xd5, 0x66, 0xd3, 0xe4, 0x8f, 0xda, + 0xbc, 0xf7, 0x94, 0xcd, 0x0e, 0x15, 0x78, 0x49, 0xaf, 0xe3, 0xdf, 0x68, 0xab, 0x49, 0xa0, 0x7c, + 0x1c, 0x8e, 0xb8, 0x1d, 0xb8, 0x23, 0xb4, 0xf0, 0xfb, 0x10, 0xb9, 0x30, 0x7f, 0xd2, 0xa0, 0x78, + 0xea, 0x4f, 0x5d, 0x6f, 0x32, 0x40, 0xce, 0xa9, 0x83, 0xc4, 0x80, 0x17, 0x3e, 0xbd, 0x9e, 0x32, + 0x3a, 0x36, 0xb4, 0xa6, 0xd6, 0x2a, 0x58, 0xf1, 0x92, 0xf4, 0xe0, 0x65, 0x9c, 0xcc, 0xf0, 0x02, + 0x05, 0x1d, 0x53, 0x41, 0x8d, 0x7c, 0x53, 0x6b, 0xe5, 0xbb, 0x95, 0x76, 0x92, 0xa6, 0x75, 0x35, + 0x50, 0x31, 0xab, 0x1c, 0x83, 0x31, 0x42, 0x3e, 0x87, 0xb2, 0xca, 0x69, 0xa1, 0x50, 0x90, 0x0a, + 0xaf, 0xda, 0x71, 0xb2, 0x29, 0x81, 0x92, 0xc2, 0x62, 0xc0, 0xfc, 0x45, 0x83, 0xd2, 0x3e, 0xbb, + 0xf4, 0x9e, 0x96, 0xf0, 0x37, 0xf0, 0x3a, 0x49, 0xd8, 0x66, 0xde, 0x99, 0xeb, 0x84, 0x01, 0x15, + 0x2e, 0xf3, 0x54, 0xd6, 0x6f, 0x2f, 0xb2, 0x3e, 0xb9, 0xfa, 0x32, 0x4d, 0xb0, 0xaa, 0x71, 0x24, + 0x03, 0x93, 0x01, 0x54, 0xe3, 0xfc, 0xb3, 0x82, 0x91, 0x09, 0x23, 0x31, 0xb1, 0xac, 0x57, 0x51, + 0x81, 0x0c, 0x6a, 0xfe, 0xb6, 0x02, 0xeb, 0xfb, 0x38, 0x73, 0x6d, 0xec, 0xd9, 0xc2, 0x9d, 0x45, + 0xd4, 0xe8, 0x66, 0xfe, 0xc1, 0xd6, 0x11, 0xbc, 0x18, 0xe3, 0x6c, 0x88, 0xa1, 0x2b, 0x7d, 0x14, + 0xfa, 0xbb, 0x37, 0xb7, 0x8d, 0x9d, 0x7f, 0xeb, 0x0b, 0x9b, 0x05, 0xd8, 0x11, 0xd7, 0x3e, 0xf2, + 0xf6, 0x3e, 0xce, 0x0e, 0x4e, 0xbf, 0xb2, 0xf4, 0x31, 0xce, 0x0e, 0x42, 0x77, 0xae, 0x47, 0x7d, + 0x5f, 0xea, 0x15, 0xfe, 0x93, 0x5e, 0xcf, 0xf7, 0xa5, 0x1e, 0xf5, 0xfd, 0xb9, 0xde, 0x83, 0x7d, + 0x52, 0xfd, 0xdf, 0x7d, 0xf2, 0xfa, 0x19, 0x7d, 0x52, 0x03, 0xe3, 0x7e, 0x5d, 0xb9, 0xcf, 0x3c, + 0x8e, 0x66, 0x00, 0x95, 0xc3, 0x88, 0x7e, 0x2c, 0xa8, 0x08, 0x79, 0x5c, 0xf0, 0x6f, 0x21, 0x1f, + 0x9f, 0x39, 0x2f, 0x85, 0x2c, 0x7a, 0x7f, 0xef, 0xe6, 0xb6, 0xb1, 0xfb, 0x8c, 0x52, 0x28, 0xe5, + 0x79, 0x39, 0x40, 0xa9, 0x1d, 0x84, 0xae, 0xf9, 0x1d, 0x54, 0x97, 0xce, 0x8c, 0x92, 0x21, 0xef, + 0xc0, 0xea, 0x94, 0x72, 0x31, 0xe4, 0x88, 0x9e, 0x3c, 0xf2, 0x4d, 0x2b, 0x37, 0x07, 0x8e, 0x11, + 0x3d, 0xb2, 0x01, 0x3a, 0x97, 0x74, 0x63, 0x45, 0x7a, 0x2f, 0x25, 0xde, 0x95, 0x8a, 0x0a, 0x9b, + 0x25, 0x28, 0x66, 0xbc, 0x98, 0x7f, 0xad, 0x80, 0x1e, 0x21, 0x64, 0x07, 0xd6, 0x62, 0x5b, 0x4a, + 0x4c, 0x93, 0x62, 0xd0, 0x9e, 0x4f, 0x24, 0x8b, 0x0a, 0xe4, 0x56, 0xd1, 0x49, 0x27, 0x47, 0x36, + 0xa0, 0x44, 0xe7, 0x75, 0xc3, 0xa1, 0xc2, 0xb9, 0xf1, 0x56, 0x53, 0x6b, 0x15, 0xad, 0xb5, 0x08, + 0x56, 0x56, 0x38, 0x31, 0x41, 0x0f, 0xe5, 0xf0, 0x50, 0x0f, 0x2a, 0xad, 0xa9, 0x22, 0xe4, 0x7d, + 0xc8, 0x8d, 0xd5, 0x8b, 0x55, 0x4d, 0x90, 0x66, 0x25, 0x31, 0xb2, 0x05, 0x79, 0x9a, 0x5c, 0x16, + 0x37, 0x1a, 0xf7, 0xa8, 0xe9, 0x30, 0xf9, 0x0c, 0x2a, 0xa9, 0xe5, 0x90, 0xda, 0x36, 0xfa, 0x02, + 0xc7, 0x46, 0xf3, 0xde, 0xb6, 0x57, 0x29, 0x5e, 0x4f, 0xd1, 0xc8, 0x36, 0x10, 0x9b, 0x79, 0x1e, + 0xda, 0x02, 0xc7, 0x0b, 0x93, 0x1f, 0x48, 0x93, 0x2f, 0x93, 0x48, 0xe2, 0x73, 0x13, 0x16, 0xe0, + 0x70, 0x14, 0xb0, 0x09, 0x06, 0xdc, 0xf8, 0x50, 0xb2, 0xcb, 0x49, 0xa0, 0x1f, 0xe1, 0xdd, 0x3f, + 0x35, 0xd0, 0x2d, 0xf9, 0x1d, 0x21, 0x5b, 0x50, 0xcc, 0x5c, 0x3b, 0x59, 0xbe, 0xc1, 0x5a, 0x4e, + 0x66, 0xda, 0xb3, 0x27, 0x2d, 0x8d, 0x6c, 0x82, 0x1e, 0x8d, 0x62, 0x52, 0x6d, 0xab, 0xcf, 0x52, + 0x66, 0x34, 0x67, 0xc8, 0x5f, 0xc0, 0x6a, 0x32, 0xcc, 0x89, 0x11, 0xf3, 0x97, 0xe7, 0x7b, 0x6d, + 0x3d, 0x8e, 0x2c, 0x4d, 0xcd, 0x8f, 0x34, 0x32, 0x80, 0x9c, 0x7a, 0x1d, 0x48, 0x1a, 0x09, 0xed, + 0xe1, 0x69, 0x54, 0x6b, 0x3e, 0x4e, 0x88, 0x3a, 0xb9, 0xfb, 0x83, 0x06, 0xc5, 0xc8, 0xf6, 0x80, + 0x7a, 0xd4, 0xc1, 0x80, 0x7c, 0xbd, 0xec, 0xfe, 0xdd, 0x58, 0xe4, 0xa1, 0xf7, 0x57, 0x7b, 0xef, + 0x91, 0xa8, 0x7a, 0x29, 0x5d, 0x58, 0x3d, 0x44, 0xa1, 0x94, 0x92, 0x02, 0x65, 0x25, 0xd6, 0xb2, + 0x70, 0xbf, 0xfc, 0xf3, 0x5d, 0x5d, 0xfb, 0xf5, 0xae, 0xae, 0xfd, 0x7e, 0x57, 0xd7, 0x7e, 0xfc, + 0xa3, 0xfe, 0xc6, 0x48, 0x97, 0xa3, 0xe6, 0xe3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xce, + 0x3a, 0x9a, 0x23, 0x08, 0x00, 0x00, } diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go new file mode 100644 index 000000000..3e36da008 --- /dev/null +++ b/core/handler/dry_run.go @@ -0,0 +1,93 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "encoding/json" + "errors" + "fmt" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + "golang.org/x/net/context" +) + +// DryUplink converts the uplink message payload by running the payload +// functions that are provided in the DryUplinkMessage, without actually going to the network. +// This is helpful for testing the payload functions without having to save them. +func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) (*pb.DryUplinkResult, error) { + app := in.App + + flds := "" + valid := true + if app != nil && app.Decoder != "" { + functions := &UplinkFunctions{ + Decoder: app.Decoder, + Converter: app.Converter, + Validator: app.Validator, + } + + fields, val, err := functions.Process(in.Payload) + if err != nil { + return nil, err + } + + valid = val + + marshalled, err := json.Marshal(fields) + if err != nil { + return nil, err + } + + flds = string(marshalled) + } + + return &pb.DryUplinkResult{ + Payload: in.Payload, + Fields: flds, + Valid: valid, + }, nil +} + +// DryDownlink converts the downlink message payload by running the payload +// functions that are provided in the DryDownlinkMessage, without actually going to the network. +// This is helpful for testing the payload functions without having to save them. +func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMessage) (*pb.DryDownlinkResult, error) { + app := in.App + + if in.Payload != nil { + if in.Fields != "" { + return nil, errors.New("Both fields and payload provided on downlink message") + } + return &pb.DryDownlinkResult{ + Payload: in.Payload, + }, nil + } + + if in.Fields == "" { + return nil, errors.New("Neither fields or payload provided on downlink message") + } + + if app == nil || app.Encoder == "" { + return nil, errors.New("No encoder specified") + } + + functions := &DownlinkFunctions{ + Encoder: app.Encoder, + } + + var parsed map[string]interface{} + err := json.Unmarshal([]byte(in.Fields), &parsed) + if err != nil { + return nil, fmt.Errorf("Could not parse fields: %s", err) + } + + payload, _, err := functions.Process(parsed) + if err != nil { + return nil, err + } + + return &pb.DryDownlinkResult{ + Payload: payload, + }, nil +} diff --git a/core/handler/dry_run_test.go b/core/handler/dry_run_test.go new file mode 100644 index 000000000..acc4fb1fb --- /dev/null +++ b/core/handler/dry_run_test.go @@ -0,0 +1,199 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + "golang.org/x/net/context" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/application" + . "github.com/smartystreets/assertions" +) + +type countingStore struct { + store application.Store + counts map[string]int +} + +func newCountingStore(store application.Store) *countingStore { + return &countingStore{ + store: store, + } +} + +func (s *countingStore) inc(name string) { + val, ok := s.counts[name] + if !ok { + val = 0 + } + s.counts[name] = val + 1 +} + +func (s *countingStore) Count(name string) int { + val, ok := s.counts[name] + if !ok { + val = 0 + } + return val +} + +func (s *countingStore) List() ([]*application.Application, error) { + s.inc("list") + return s.store.List() +} + +func (s *countingStore) Get(appID string) (*application.Application, error) { + s.inc("get") + return s.store.Get(appID) +} + +func (s *countingStore) Set(app *application.Application, fields ...string) error { + s.inc("set") + return s.store.Set(app, fields...) +} + +func (s *countingStore) Delete(appID string) error { + s.inc("delete") + return s.store.Delete(appID) +} + +func TestDryUplinkFields(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewApplicationStore()) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + dryUplinkMessage := &pb.DryUplinkMessage{ + Payload: []byte{11, 22, 33}, + App: &pb.Application{ + Decoder: `function (bytes) { return { length: bytes.length }}`, + Converter: `function (obj) { return obj }`, + Validator: `function (bytes) { return true; }`, + }, + } + + res, err := m.DryUplink(context.TODO(), dryUplinkMessage) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) + a.So(res.Fields, ShouldEqual, `{"length":3}`) + a.So(res.Valid, ShouldBeTrue) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryUplinkEmptyApp(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewApplicationStore()) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + dryUplinkMessage := &pb.DryUplinkMessage{ + Payload: []byte{11, 22, 33}, + } + + res, err := m.DryUplink(context.TODO(), dryUplinkMessage) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) + a.So(res.Fields, ShouldEqual, "") + a.So(res.Valid, ShouldBeTrue) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryDownlinkFields(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewApplicationStore()) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Fields: `{ "foo": [ 1, 2, 3 ] }`, + App: &pb.Application{ + Encoder: `function (fields) { return fields.foo }`, + }, + } + + res, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, []byte{1, 2, 3}) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryDownlinkPayload(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewApplicationStore()) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Payload: []byte{0x1, 0x2, 0x3}, + App: &pb.Application{ + Encoder: `function (fields) { return fields.foo }`, + }, + } + + res, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldBeNil) + + a.So(res.Payload, ShouldResemble, []byte{0x1, 0x2, 0x3}) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} + +func TestDryDownlinkEmptyApp(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewApplicationStore()) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Fields: `{ "foo": [ 1, 2, 3 ] }`, + } + + _, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldNotBeNil) + + // make sure no calls to app store were made + a.So(store.Count("list"), ShouldEqual, 0) + a.So(store.Count("get"), ShouldEqual, 0) + a.So(store.Count("set"), ShouldEqual, 0) + a.So(store.Count("delete"), ShouldEqual, 0) +} From 49ab4204c8a1a86c668ccba44a214ffd49f717f0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 2 Sep 2016 15:11:22 +0200 Subject: [PATCH 1690/2266] Add support for multiple auth servers - Also improve caching in TokenKey Provider --- .env/.gitignore | 2 +- .env/broker/dev.yml | 6 ++-- .env/discovery/dev.yml | 4 ++- .env/handler/dev.yml | 6 ++-- .env/networkserver/dev.yml | 4 ++- .env/router/dev.yml | 6 ++-- cmd/root.go | 9 +++--- core/component.go | 13 ++++---- core/discovery/server.go | 15 +++++++++- utils/tokenkey/tokenkey.go | 61 +++++++++++++++++++++++++++----------- 10 files changed, 87 insertions(+), 39 deletions(-) diff --git a/.env/.gitignore b/.env/.gitignore index 75ae1aa83..8f0792831 100644 --- a/.env/.gitignore +++ b/.env/.gitignore @@ -1,2 +1,2 @@ -*/auth-server.pub +*/auth-*.pub redis/ diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index 1e693152d..b5663da66 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -1,11 +1,13 @@ id: dev debug: true discovery-server: "localhost:1900" -auth-server: https://staging.account.thethingsnetwork.org +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-staging: "https://staging.account.thethingsnetwork.org" tls: true key-dir: "./.env/broker/" -auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkZXYiLCJ0eXBlIjoiYnJva2VyIiwiaWF0IjoxNDY5NzkyNTc2fQ.BPY3MIeXA0-mzOW9Y-Zef9ME03oz13glerh-1HtkiS0_d7vkReJfG3KaWIvISbETql8ooErphP0-LE0xcCvPlqRRBPM-fPhy2DTyMSVsNPmaABC2WtWvqIsVjoPCqhu3TRzBQkvf9CE6o7F3wXQCOHQzcLXF3EsKUqSK8_p1dpFYsrVdx6V0gylzi5Ne3z5oEqKmUc8L5j14fXpwUOUMIF7awfrbs0E2h5JpvyN6cN__OX5do7o-Icm6WB4sRZzGSIONmwH4BEeqiTb-BM3FYGfPEdeiGl1W4A2-v_m8iU28L28y5Z9b_iPnUufb0K3ffXP7MkRPNRl5G1LpafofOQ +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1zdGFnaW5nIiwic3ViIjoiZGV2IiwidHlwZSI6ImJyb2tlciIsImlhdCI6MTQ3MjgxNzk1MX0.m3sMJDaXcsAxROlCOqbidv5cQf1iau99N5aIC9D4rH9lCzl__Z6sVkMOqDrdDska1lxuKlkr7dQEDvU5ujUmOSKC3riMZFtOnvA_7NQAR8Q1i6JxHVg3OKpml513nbOKynPn8Vb2oEN0S1yyfkLepbMfAuJKzxPUL92vZcKX6GgyV51nnfYZp-HksqkaFK7zm2wbQQ_xqouLZ_mibRdMyryFfv5mDnZ3K1LCHXcv_FanoMA8hvjKq3pKwflI2vJPSNGmwftPL0oG7t4MBXkx3IqthxhyymyYbukcC1UBtwtvo3Now2cbbfwatBPAEqUkvcXe-LK4YW1QsYB0xs8iOQ broker: networkserver-cert: ./.env/broker/networkserver.cert diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index e88f03ba6..141da5c35 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -1,5 +1,7 @@ id: dev debug: true discovery-server: "localhost:1900" -auth-server: https://staging.account.thethingsnetwork.org +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-staging: "https://staging.account.thethingsnetwork.org" key-dir: "./.env/discovery/" diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 574f5fbb7..2541329c6 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -1,8 +1,10 @@ id: dev debug: true discovery-server: "localhost:1900" -auth-server: https://staging.account.thethingsnetwork.org +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-staging: "https://staging.account.thethingsnetwork.org" tls: true key-dir: "./.env/handler/" -auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkZXYiLCJ0eXBlIjoiaGFuZGxlciIsImlhdCI6MTQ2OTc5MjU3OH0.KM579aO-zSGCms87dEeovUS6M3P45Lks_Ctch5xcmpOwF2HjhQqCU7F476OxAaS70OJ2owpBF9Py_ZwcxaxGcnyx4FVW9V3nSd4aXmUQ4sIwP9zVut9xNB-3gelttvP6MRLl2EoUcGF-5nIEP-AanhVH8jyC_AFMXafyNMJAOE0M3gSlprTcm80h8jRRRjD8fhac_JMvfJfqjS41pw5SKo8csTJVql5-zuAE0hiCY_CREiHJLY8BmX8bqNgQFZr3QlqeJMxcy29kKuZYCi_jYSnCh8LjD6ebOxx99mIcniGk6hbVQNmARns-RZpwPLFOIdsPwxGKIzPD6Liato4kXw +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1zdGFnaW5nIiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzI4MTc5NTJ9.XyaTta7R66bfaCS4rzNYayzejE7nP7CcleksPyk71LsDkyMetFE_dJ-EP1Lp6Lys7yBILO4ax5tfuswBwvXg2sOIBIT30SPuTaJ62HidU6We_DQHBvHMSkdSOMnk3WNJ5rWhogga1rSHiKnJH56CqdcmT-9vwVFqIeU6ZCTawdDjRGjgQCn81PGlnCW7GVb3fLgpD1A_feFbGxBXVYSbRWA-rHGccyI9kWU5xxWyZwlYpl9_zT8j4mD8CZzffbgsPzag6aspE52fHgPRqz6rYzgZpXS7o6MUOEsjcWiOwp2ManvvnVA0mM_G65jdqMz1rPjdxt19QnQ8V59QtButGQ diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index 10429b96c..b53933306 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -1,5 +1,7 @@ debug: true discovery-server: "localhost:1900" -auth-server: https://staging.account.thethingsnetwork.org +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-staging: "https://staging.account.thethingsnetwork.org" tls: true key-dir: "./.env/networkserver/" diff --git a/.env/router/dev.yml b/.env/router/dev.yml index e201cc840..4bcbddc01 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -1,8 +1,10 @@ id: dev debug: true discovery-server: "localhost:1900" -auth-server: https://staging.account.thethingsnetwork.org +auth-servers: + ttn-account: "https://account.thethingsnetwork.org" + ttn-account-staging: "https://staging.account.thethingsnetwork.org" tls: true key-dir: "./.env/router/" -auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJkZXYiLCJ0eXBlIjoicm91dGVyIiwiaWF0IjoxNDY5NzkyNTczfQ.nIH2eqv8F7Rmd0v2WLQJrK8jZT-3-ONka_FxuTdu0rQbNyKyOzXjr10Y9hG0CmS2Fn4nM37P7gsjyEhiCYVfMg7cRwAKSPEzC_0caW3dTQV0CgQ6S-sNGVD_ElBPHhrW11moetPpL1G5eJAoB4FtksREmdNikofuQ3z2F-LK5u7NaHiZd_EqOp6K5fedwMzRoXIJl9gthZLnXee0ShgHxzqWR-oS0fl-fw48IJ_aLhDngWi9ECloseDAfO31kzEA5l_wgsic6xBpC2y-bItj_MbT_84pqWc1NdtFlQNsOtZwzRw0w27K9PafTEOLSaVjGWy_FEckbsmUpq3_UYeDJw +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1zdGFnaW5nIiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3MjgxNzk1MX0.Rjv2QHMAdhrZpc7Nf1HdUG1EoZqURr-PKWZaU7Xz7p30FMBxDXUDUtDbcILKbjstlczVOdHGQnAOf-rwzeIrXJ5051KopcDYcmpRwqJQHnKmzFClw5gs-1uAYSnwcEvpsGaFDS8Q7qc6X0m-5lFnFeHAtgVPa80joAw6QBPaTwW3KHvoY_a2htjk8tUQhVlL-wTCnnPMeUGIYJwHFIRXZW3Wk32XLWwF2w8fimVFgfr8yBQMLucbQ-CNEqcuC8tU8H46kPKCugadY6enmaOVVgeGFK1s9OEm4oHhYMJkE0agY7FZBPVEyl9bVxpCRor4H60MebA5U7xQ9tQQlP2ndQ diff --git a/cmd/root.go b/cmd/root.go index 48cb6d323..e55537a65 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -65,7 +65,7 @@ var RootCmd = &cobra.Command{ "ComponentID": viper.GetString("id"), "Description": viper.GetString("description"), "DiscoveryServer": viper.GetString("discovery-server"), - "AuthServer": viper.GetString("auth-server"), + "AuthServers": viper.GetStringMapString("auth-servers"), }).Info("Initializing The Things Network") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { @@ -106,10 +106,11 @@ func init() { RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) - RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") - viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) + viper.SetDefault("auth-servers", map[string]string{ + "ttn-account": "https://account.thethingsnetwork.org", + }) - RootCmd.PersistentFlags().String("auth-token", "", "The auth token signed JWT from the auth-server") + RootCmd.PersistentFlags().String("auth-token", "", "The JWT token to be used for the discovery server") viper.BindPFlag("auth-token", RootCmd.PersistentFlags().Lookup("auth-token")) RootCmd.PersistentFlags().Int("health-port", 0, "The port number where the health server should be started") diff --git a/core/component.go b/core/component.go index e142d8bd0..25c03895b 100644 --- a/core/component.go +++ b/core/component.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "net/http" - "path" "runtime" "sync/atomic" "time" @@ -73,8 +72,8 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string AccessToken: viper.GetString("auth-token"), Discovery: discovery, TokenKeyProvider: tokenkey.NewHTTPProvider( - fmt.Sprintf("%s/key", viper.GetString("auth-server")), - path.Join(viper.GetString("key-dir"), "/auth-server.pub"), + viper.GetStringMapString("auth-servers"), + viper.GetString("key-dir"), ), } @@ -172,11 +171,11 @@ func (c *Component) UpdateTokenKey() error { } // Set up Auth Server Token Validation - tokenKey, err := c.TokenKeyProvider.Get(true) + err := c.TokenKeyProvider.Update() if err != nil { - c.Ctx.Warnf("ttn: Failed to refresh public key for token validation: %s", err.Error()) + c.Ctx.Warnf("ttn: Failed to refresh public keys for token validation: %s", err.Error()) } else { - c.Ctx.Infof("ttn: Got public key for token validation (%v)", tokenKey.Algorithm) + c.Ctx.Info("ttn: Got public keys for token validation") } return nil @@ -281,7 +280,7 @@ func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, err if c.TokenKeyProvider == nil { return nil, errors.New("No token provider configured") } - k, err := c.TokenKeyProvider.Get(false) + k, err := c.TokenKeyProvider.Get(ttnClaims.Issuer, false) if err != nil { return nil, err } diff --git a/core/discovery/server.go b/core/discovery/server.go index ac483c346..089c419cf 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -14,7 +14,7 @@ import ( ) type discoveryServer struct { - discovery Discovery + discovery *discovery } func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { @@ -27,6 +27,10 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me if in.ServiceName != "broker" { return errors.New("ttn/discovery: Announcement service type should be \"broker\"") } + // Only allow prefix announcements if token is issued by the official ttn account server (or if in dev mode) + if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { + return fmt.Errorf("ttn/discovery: Token issuer %s should be ttn-account", claims.Issuer) + } if claims.Type != in.ServiceName { return fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) } @@ -38,6 +42,10 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me if in.ServiceName != "handler" { return errors.New("ttn/discovery: Announcement service type should be \"handler\"") } + // Only allow eui announcements if token is issued by the official ttn account server (or if in dev mode) + if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { + return fmt.Errorf("ttn/discovery: Token issuer %s should be ttn-account", claims.Issuer) + } if claims.Type != in.ServiceName { return fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) } @@ -50,6 +58,7 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me if in.ServiceName != "handler" { return errors.New("ttn/discovery: Announcement service type should be \"handler\"") } + // Allow APP_ID announcements from all trusted auth servers // When announcing APP_ID, token is user token that contains apps if !claims.CanEditApp(string(in.Metadata.Value)) { return errors.New("ttn/discovery: No access to this application") @@ -63,6 +72,10 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc if err != nil { return nil, err } + // Only allow announcements if token is issued by the official ttn account server (or if in dev mode) + if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { + return nil, fmt.Errorf("ttn/discovery: Token issuer %s should be ttn-account", claims.Issuer) + } if claims.Subject != announcement.Id { return nil, fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.Id) } diff --git a/utils/tokenkey/tokenkey.go b/utils/tokenkey/tokenkey.go index 7f23735ea..26fda5bc9 100644 --- a/utils/tokenkey/tokenkey.go +++ b/utils/tokenkey/tokenkey.go @@ -9,6 +9,7 @@ import ( "fmt" "io/ioutil" "net/http" + "path" ) // K is the data returned by the token key provider @@ -19,42 +20,56 @@ type K struct { // Provider represents a provider of the token key type Provider interface { - fmt.Stringer - Get(renew bool) (*K, error) + Get(server string, renew bool) (*K, error) + Update() error } type httpProvider struct { - url string - cacheFile string + servers map[string]string + cache map[string][]byte + cacheLocation string } // NewHTTPProvider returns a new Provider that fetches the key from a HTTP // resource -func NewHTTPProvider(url, cacheFile string) Provider { - return &httpProvider{url, cacheFile} +func NewHTTPProvider(servers map[string]string, cacheLocation string) Provider { + return &httpProvider{servers, map[string][]byte{}, cacheLocation} } -func (p *httpProvider) String() string { - return p.url -} - -func (p *httpProvider) Get(renew bool) (*K, error) { +func (p *httpProvider) Get(server string, renew bool) (*K, error) { var data []byte - // Try to read from cache - cached, err := ioutil.ReadFile(p.cacheFile) - if err == nil { + url, ok := p.servers[server] + if !ok { + return nil, fmt.Errorf("Auth server %s not registered", server) + } + + cacheFile := path.Join(p.cacheLocation, fmt.Sprintf("auth-%s.pub", server)) + + // Try to read from memory + cached, ok := p.cache[server] + if ok { data = cached } + if data == nil { + // Try to read from cache file + cached, err := ioutil.ReadFile(cacheFile) + if err == nil { + p.cache[server] = cached + data = cached + } + } + // Fetch token if there's a renew or if there's no key cached if renew || data == nil { - fetched, err := p.fetch() + fetched, err := p.fetch(fmt.Sprintf("%s/key", url)) if err == nil { data = fetched // Don't care about errors here. It's better to retrieve keys all the time // because they can't be cached than not to be able to verify a token - ioutil.WriteFile(p.cacheFile, data, 0644) + ioutil.WriteFile(cacheFile, data, 0644) + p.cache[server] = data } else if data == nil { return nil, err // We don't have a key here } @@ -68,8 +83,18 @@ func (p *httpProvider) Get(renew bool) (*K, error) { return &key, nil } -func (p *httpProvider) fetch() ([]byte, error) { - resp, err := http.Get(p.url) +func (p *httpProvider) Update() error { + for server := range p.servers { + _, err := p.Get(server, true) + if err != nil { + return err + } + } + return nil +} + +func (p *httpProvider) fetch(url string) ([]byte, error) { + resp, err := http.Get(url) if err != nil { return nil, err } From d885c2038dc28f848b36954cafec36db7232eac7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 5 Sep 2016 11:40:45 +0200 Subject: [PATCH 1691/2266] Add Elasticsearch log handler --- cmd/root.go | 29 ++++- utils/elasticsearch/handler/elasticsearch.go | 106 +++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 utils/elasticsearch/handler/elasticsearch.go diff --git a/cmd/root.go b/cmd/root.go index e55537a65..166cfe7e3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "net/http" "os" "path" "path/filepath" @@ -15,6 +16,7 @@ import ( "gopkg.in/redis.v3" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" + esHandler "github.com/TheThingsNetwork/ttn/utils/elasticsearch/handler" "github.com/apex/log" jsonHandler "github.com/apex/log/handlers/json" levelHandler "github.com/apex/log/handlers/level" @@ -22,6 +24,7 @@ import ( "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" + "github.com/tj/go-elastic" ) var cfgFile string @@ -42,7 +45,10 @@ var RootCmd = &cobra.Command{ } var logHandlers []log.Handler - logHandlers = append(logHandlers, levelHandler.New(cliHandler.New(os.Stdout), logLevel)) + + if !viper.GetBool("no-cli-logs") { + logHandlers = append(logHandlers, levelHandler.New(cliHandler.New(os.Stdout), logLevel)) + } if logFileLocation := viper.GetString("log-file"); logFileLocation != "" { absLogFileLocation, err := filepath.Abs(logFileLocation) @@ -58,6 +64,18 @@ var RootCmd = &cobra.Command{ } } + if esServer := viper.GetString("elasticsearch"); esServer != "" { + esClient := elastic.New(esServer) + esClient.HTTPClient = &http.Client{ + Timeout: 5 * time.Second, + } + logHandlers = append(logHandlers, levelHandler.New(esHandler.New(&esHandler.Config{ + Client: esClient, + Prefix: cmd.Name(), + BufferSize: 10, + }), logLevel)) + } + ctx = &log.Logger{ Handler: multiHandler.New(logHandlers...), } @@ -133,8 +151,14 @@ func init() { RootCmd.PersistentFlags().String("key-dir", path.Clean(dir+"/.ttn/"), "The directory where public/private keys are stored") viper.BindPFlag("key-dir", RootCmd.PersistentFlags().Lookup("key-dir")) + RootCmd.PersistentFlags().Bool("no-cli-logs", false, "Disable CLI logs") + viper.BindPFlag("no-cli-logs", RootCmd.PersistentFlags().Lookup("no-cli-logs")) + RootCmd.PersistentFlags().String("log-file", "", "Location of the log file") viper.BindPFlag("log-file", RootCmd.PersistentFlags().Lookup("log-file")) + + RootCmd.PersistentFlags().String("elasticsearch", "", "Location of Elasticsearch server for logging") + viper.BindPFlag("elasticsearch", RootCmd.PersistentFlags().Lookup("elasticsearch")) } // initConfig reads in config file and ENV variables if set. @@ -161,7 +185,10 @@ func initConfig() { } } +// RedisConnectRetries indicates how many times the Redis connection should be retried var RedisConnectRetries = 10 + +// RedisConnectRetryDelay indicates the time between Redis connection retries var RedisConnectRetryDelay = 1 * time.Second func connectRedis(client *redis.Client) error { diff --git a/utils/elasticsearch/handler/elasticsearch.go b/utils/elasticsearch/handler/elasticsearch.go new file mode 100644 index 000000000..1fadf0a7e --- /dev/null +++ b/utils/elasticsearch/handler/elasticsearch.go @@ -0,0 +1,106 @@ +// Package elasticsearch implements an Elasticsearch batch handler. +package elasticsearch + +import ( + "fmt" + "io" + stdlog "log" + "os" + "sync" + "time" + + "github.com/apex/log" + "github.com/tj/go-elastic/batch" +) + +// Elasticsearch interface. +type Elasticsearch interface { + Bulk(io.Reader) error +} + +// Config for handler. +type Config struct { + BufferSize int // BufferSize is the number of logs to buffer before flush (default: 100) + Client Elasticsearch // Client for ES + Prefix string // Prefix for the index - The index will be prefix-YY-MM-DD (default: logs) + Hostname string // Hostname to add to the logs +} + +// defaults applies defaults to the config. +func (c *Config) defaults() { + if c.BufferSize == 0 { + c.BufferSize = 100 + } + if c.Prefix == "" { + c.Prefix = "logs" + } + if c.Hostname == "" { + c.Hostname, _ = os.Hostname() + } +} + +// Handler implementation. +type Handler struct { + *Config + + mu sync.Mutex + batch *batch.Batch +} + +// indexName returns the index for the configured +func (h *Handler) indexName() string { + return fmt.Sprintf("%s-%s", h.Config.Prefix, time.Now().Format("2006.01.02")) +} + +// New handler with BufferSize +func New(config *Config) *Handler { + config.defaults() + return &Handler{ + Config: config, + } +} + +// HandleLog implements log.Handler. +func (h *Handler) HandleLog(e *log.Entry) error { + h.mu.Lock() + defer h.mu.Unlock() + + if h.batch == nil { + h.batch = &batch.Batch{ + Elastic: h.Client, + Index: h.indexName(), + Type: "log", + } + } + + // Map fields + for k, v := range e.Fields { + switch t := v.(type) { + case []byte: // Convert []byte to HEX-string + e.Fields[k] = fmt.Sprintf("%X", t) + } + } + + e.Timestamp = e.Timestamp.UTC() + + if h.Hostname != "" { + e.Fields["hostname"] = h.Hostname + } + + h.batch.Add(e) + + if h.batch.Size() >= h.BufferSize { + go h.flush(h.batch) + h.batch = nil + } + + return nil +} + +// flush the given `batch` asynchronously. +func (h *Handler) flush(batch *batch.Batch) { + size := batch.Size() + if err := batch.Flush(); err != nil { + stdlog.Printf("log/elastic: failed to flush %d logs: %s", size, err) + } +} From c88b67aa8072acb9022ba1bd16c72d9e3ce0400d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Sep 2016 16:02:28 +0200 Subject: [PATCH 1692/2266] Fix MQTT messages for Activations --- core/handler/activation.go | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/core/handler/activation.go b/core/handler/activation.go index 0b9556460..608dcc97f 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -18,6 +18,20 @@ import ( "github.com/brocaar/lorawan" ) +func (h *handler) getActivationMetadata(ctx log.Interface, activation *pb_broker.DeduplicatedDeviceActivationRequest) (mqtt.Metadata, error) { + ttnUp := &pb_broker.DeduplicatedUplinkMessage{ + ProtocolMetadata: activation.ProtocolMetadata, + GatewayMetadata: activation.GatewayMetadata, + ServerTime: activation.ServerTime, + } + mqttUp := &mqtt.UplinkMessage{} + err := h.ConvertMetadata(ctx, ttnUp, mqttUp) + if err != nil { + return mqtt.Metadata{}, err + } + return mqttUp.Metadata, nil +} + func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { var appEUI types.AppEUI if activation.AppEui != nil { @@ -98,12 +112,6 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv ctx.Debug("Accepting Join Request") - // Publish Activation - h.mqttActivation <- &mqtt.Activation{ - AppEUI: *activation.AppEui, - DevEUI: *activation.DevEui, - } - // Prepare Device Activation Response var resPHY lorawan.PHYPayload if err = resPHY.UnmarshalBinary(activation.ResponseTemplate.Payload); err != nil { @@ -120,6 +128,17 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } resPHY.MACPayload = joinAccept + // Publish Activation + mqttMetadata, _ := h.getActivationMetadata(ctx, activation) + h.mqttActivation <- &mqtt.Activation{ + AppEUI: *activation.AppEui, + DevEUI: *activation.DevEui, + AppID: activation.AppId, + DevID: activation.DevId, + DevAddr: types.DevAddr(joinAccept.DevAddr), + Metadata: mqttMetadata, + } + // Generate random AppNonce var appNonce device.AppNonce for { From 47fe8dc126a5877a6a595198c1dd0858676a47b6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Sep 2016 16:10:53 +0200 Subject: [PATCH 1693/2266] Give access to all funcs in Rand --- utils/random/random.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/utils/random/random.go b/utils/random/random.go index eb5f28dd1..4d7d389d5 100644 --- a/utils/random/random.go +++ b/utils/random/random.go @@ -23,12 +23,12 @@ const ( type TTNRandom struct { sync.Mutex - src *rand.Rand + *rand.Rand } func New() *TTNRandom { return &TTNRandom{ - src: rand.New(rand.NewSource(time.Now().UnixNano())), + Rand: rand.New(rand.NewSource(time.Now().UnixNano())), } } @@ -39,10 +39,10 @@ func (r *TTNRandom) String(n int) string { r.Lock() defer r.Unlock() b := make([]byte, n) - // A src.Int63() generates 63 random bits, enough for letterIdxMax characters! - for i, cache, remain := n-1, r.src.Int63(), letterIdxMax; i >= 0; { + // A Int63() generates 63 random bits, enough for letterIdxMax characters! + for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; { if remain == 0 { - cache, remain = r.src.Int63(), letterIdxMax + cache, remain = r.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] @@ -60,7 +60,7 @@ func (r *TTNRandom) Token() []byte { r.Lock() defer r.Unlock() b := make([]byte, 4) - binary.BigEndian.PutUint32(b, r.src.Uint32()) + binary.BigEndian.PutUint32(b, r.Uint32()) return b[0:2] } @@ -69,7 +69,7 @@ func (r *TTNRandom) Rssi() int32 { r.Lock() defer r.Unlock() // Generate RSSI. Tend towards generating great signal strength. - x := float64(r.src.Int31()) * float64(2e-9) + x := float64(r.Int31()) * float64(2e-9) return int32(-1.6 * math.Exp(x)) } @@ -101,7 +101,7 @@ var usFreqs = []float32{ func (r *TTNRandom) Freq() float32 { r.Lock() defer r.Unlock() - return usFreqs[r.src.Intn(len(usFreqs))] + return usFreqs[r.Intn(len(usFreqs))] } // Datr generates Datr for instance: SF4BW125 @@ -109,7 +109,7 @@ func (r *TTNRandom) Datr() string { r.Lock() defer r.Unlock() // Spread Factor from 12 to 7 - sf := 12 - r.src.Intn(7) + sf := 12 - r.Intn(7) var bw int if sf == 6 { // DR6 -> SF7@250Khz @@ -125,7 +125,7 @@ func (r *TTNRandom) Datr() string { func (r *TTNRandom) Codr() string { r.Lock() defer r.Unlock() - d := r.src.Intn(4) + 5 + d := r.Intn(4) + 5 return fmt.Sprintf("4/%d", d) } @@ -133,7 +133,7 @@ func (r *TTNRandom) Codr() string { func (r *TTNRandom) Lsnr() float32 { r.Lock() defer r.Unlock() - x := float64(r.src.Int31()) * float64(2e-9) + x := float64(r.Int31()) * float64(2e-9) return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) } @@ -142,7 +142,7 @@ func (r *TTNRandom) Bytes(n int) []byte { r.Lock() defer r.Unlock() p := make([]byte, n) - r.src.Read(p) + r.Read(p) return p } From 38661b155a47846b93ab14aba3b9552a80ee49c3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Sep 2016 16:25:31 +0200 Subject: [PATCH 1694/2266] Request DevAddrs from NS for ABP Resolves #229 for backend and ttnctl part --- api/handler/manager_client.go | 15 + api/protocol/lorawan/device.pb.go | 121 ++- api/protocol/lorawan/device.proto | 1 + api/protocol/lorawan/device_address.pb.go | 971 ++++++++++++++++++++++ api/protocol/lorawan/device_address.proto | 32 + cmd/broker_register_prefix.go | 4 +- cmd/networkserver.go | 35 +- core/broker/broker.go | 58 +- core/broker/manager_server.go | 26 +- core/discovery/broker_discovery.go | 14 +- core/handler/device/device.go | 6 - core/handler/handler.go | 3 - core/handler/manager_server.go | 61 +- core/networkserver/device/device.go | 5 +- core/networkserver/manager_server.go | 29 +- core/networkserver/networkserver.go | 84 +- core/networkserver/networkserver_test.go | 36 +- core/types/dev_addr.go | 75 +- core/types/dev_addr_test.go | 54 +- ttnctl/cmd/devices_personalize.go | 33 +- ttnctl/cmd/devices_set.go | 1 + 21 files changed, 1487 insertions(+), 177 deletions(-) create mode 100644 api/protocol/lorawan/device_address.pb.go create mode 100644 api/protocol/lorawan/device_address.proto diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index 658cde567..f3d987599 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -6,6 +6,9 @@ import ( "os/user" "sync" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -116,6 +119,18 @@ func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Devic return } +// GetDevAddr requests a random device address with the given constraints +func (h *ManagerClient) GetDevAddr(constraints ...string) (types.DevAddr, error) { + devAddrManager := lorawan.NewDevAddrManagerClient(h.conn) + resp, err := devAddrManager.GetDevAddr(h.getContext(), &lorawan.DevAddrRequest{ + Usage: constraints, + }) + if err != nil { + return types.DevAddr{}, err + } + return *resp.DevAddr, nil +} + // DryUplink transforms the uplink payload with the payload functions provided // in the app.. func (h *ManagerClient) DryUplink(payload []byte, app *Application) (*DryUplinkResult, error) { diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 3fd54758a..875f39526 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -7,11 +7,16 @@ It is generated from these files: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto It has these top-level messages: DeviceIdentifier Device + PrefixesRequest + PrefixesResponse + DevAddrRequest + DevAddrResponse Metadata TxConfiguration ActivationMetadata @@ -66,8 +71,9 @@ type Device struct { FCntUp uint32 `protobuf:"varint,9,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` FCntDown uint32 `protobuf:"varint,10,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` // Options - DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` - Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + ActivationConstraints string `protobuf:"bytes,13,opt,name=activation_constraints,json=activationConstraints,proto3" json:"activation_constraints,omitempty"` // Other LastSeen int64 `protobuf:"varint,21,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` } @@ -375,6 +381,12 @@ func (m *Device) MarshalTo(data []byte) (int, error) { } i++ } + if len(m.ActivationConstraints) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintDevice(data, i, uint64(len(m.ActivationConstraints))) + i += copy(data[i:], m.ActivationConstraints) + } if m.LastSeen != 0 { data[i] = 0xa8 i++ @@ -473,6 +485,10 @@ func (m *Device) Size() (n int) { if m.Uses32BitFCnt { n += 2 } + l = len(m.ActivationConstraints) + if l > 0 { + n += 1 + l + sovDevice(uint64(l)) + } if m.LastSeen != 0 { n += 2 + sovDevice(uint64(m.LastSeen)) } @@ -963,6 +979,35 @@ func (m *Device) Unmarshal(data []byte) error { } } m.Uses32BitFCnt = bool(v != 0) + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationConstraints", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDevice + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDevice + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ActivationConstraints = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field LastSeen", wireType) @@ -1113,39 +1158,41 @@ func init() { } var fileDescriptorDevice = []byte{ - // 537 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xdd, 0x6e, 0xd3, 0x30, - 0x14, 0xc7, 0x09, 0x63, 0x6d, 0x62, 0x36, 0x51, 0x19, 0x4d, 0x0a, 0x05, 0x75, 0xd5, 0x2e, 0xa0, - 0x37, 0x4b, 0x44, 0x2b, 0xe0, 0xba, 0x5f, 0xa0, 0x0a, 0x51, 0x89, 0x74, 0xbb, 0x8e, 0xdc, 0xf8, - 0xb4, 0xb5, 0x52, 0x6c, 0x2b, 0x71, 0x1a, 0xf5, 0x4d, 0x78, 0x01, 0x5e, 0x80, 0xa7, 0xe0, 0x0a, - 0x71, 0xbd, 0x8b, 0x09, 0x95, 0x17, 0x41, 0xb6, 0xcb, 0x04, 0x95, 0xd0, 0x44, 0xaf, 0xb8, 0x3b, - 0xfe, 0x9f, 0x73, 0x7e, 0x7f, 0x7f, 0xa3, 0xee, 0x9c, 0xa9, 0x45, 0x31, 0x0d, 0x12, 0xf1, 0x21, - 0xbc, 0x58, 0xc0, 0xc5, 0x82, 0xf1, 0x79, 0x3e, 0x06, 0x55, 0x8a, 0x2c, 0x0d, 0x95, 0xe2, 0x21, - 0x91, 0x2c, 0x94, 0x99, 0x50, 0x22, 0x11, 0xcb, 0x70, 0x29, 0x32, 0x52, 0x12, 0x1e, 0x52, 0x58, - 0xb1, 0x04, 0x02, 0xa3, 0xe3, 0xea, 0x56, 0xad, 0x9f, 0xff, 0xc6, 0x9a, 0x8b, 0xb9, 0xb0, 0x7d, - 0xd3, 0x62, 0x66, 0x46, 0x66, 0x60, 0x22, 0xdb, 0xf7, 0x47, 0xf9, 0x5f, 0xad, 0x89, 0x64, 0xb6, - 0xfc, 0xec, 0xb3, 0x83, 0x6a, 0x03, 0xe3, 0x3b, 0xa2, 0xc0, 0x15, 0x9b, 0x31, 0xc8, 0xf0, 0x18, - 0x55, 0x89, 0x94, 0x31, 0x14, 0xcc, 0x77, 0x9a, 0x4e, 0xeb, 0xa8, 0xf7, 0xe2, 0xea, 0xfa, 0xf4, - 0xf9, 0x6d, 0xe0, 0x44, 0x64, 0x10, 0xaa, 0xb5, 0x84, 0x3c, 0xe8, 0x4a, 0x39, 0xbc, 0x1c, 0x45, - 0x15, 0x22, 0xe5, 0xb0, 0x60, 0x9a, 0x47, 0x61, 0x65, 0x78, 0x77, 0xf7, 0xe2, 0x0d, 0x60, 0x65, - 0x78, 0x14, 0x56, 0xc3, 0x82, 0x9d, 0x7d, 0x3d, 0x44, 0x15, 0x3b, 0xe9, 0xff, 0x7d, 0xaa, 0xf8, - 0x04, 0x69, 0x72, 0xcc, 0xa8, 0x7f, 0xd0, 0x74, 0x5a, 0x5e, 0x74, 0x48, 0xa4, 0x1c, 0x51, 0x2d, - 0x6b, 0x1b, 0x46, 0xfd, 0x7b, 0x56, 0xa6, 0xb0, 0x1a, 0x51, 0xfc, 0x1e, 0xb9, 0x5a, 0x26, 0x94, - 0x66, 0xfe, 0xa1, 0xb1, 0x7f, 0x79, 0x75, 0x7d, 0xda, 0xfe, 0x37, 0xfb, 0x2e, 0xa5, 0x59, 0xa4, - 0x57, 0xa1, 0x03, 0x1c, 0x21, 0x8f, 0x97, 0x69, 0x9c, 0xc7, 0x29, 0xac, 0xfd, 0xca, 0x5e, 0xcc, - 0x71, 0x99, 0x4e, 0xde, 0xc2, 0x3a, 0xaa, 0x72, 0x1b, 0x68, 0xa6, 0x5e, 0x94, 0x65, 0x56, 0xf7, - 0x62, 0x76, 0xa5, 0xb4, 0x4c, 0x62, 0x83, 0x5f, 0x07, 0xa9, 0x89, 0xee, 0xbe, 0x07, 0xa9, 0x81, - 0x7a, 0xbb, 0x35, 0xcf, 0x47, 0xee, 0x2c, 0x4e, 0xb8, 0x8a, 0x0b, 0xe9, 0x7b, 0x4d, 0xa7, 0x75, - 0x1c, 0x55, 0x66, 0x7d, 0xae, 0x2e, 0x25, 0x7e, 0x82, 0x90, 0xcd, 0x50, 0x51, 0x72, 0x1f, 0x99, - 0x9c, 0xab, 0x73, 0x03, 0x51, 0x72, 0x7c, 0x8e, 0x1e, 0x52, 0x96, 0x93, 0xe9, 0x12, 0x62, 0x5b, - 0x95, 0x2c, 0x20, 0x49, 0xfd, 0xfb, 0x4d, 0xa7, 0xe5, 0x46, 0xb5, 0x6d, 0xea, 0x75, 0x9f, 0xab, - 0xbe, 0xd6, 0xf1, 0x33, 0x54, 0x2b, 0x72, 0xc8, 0x3b, 0xed, 0x78, 0xca, 0x94, 0xed, 0xf0, 0x8f, - 0x4c, 0xed, 0xb1, 0xd5, 0x7b, 0x4c, 0xe9, 0x6a, 0xfc, 0x18, 0x79, 0x4b, 0x92, 0xab, 0x38, 0x07, - 0xe0, 0xfe, 0x49, 0xd3, 0x69, 0x1d, 0x44, 0xae, 0x16, 0x26, 0x00, 0xbc, 0xfd, 0xc9, 0x41, 0xc7, - 0xf6, 0x42, 0xbf, 0x23, 0x9c, 0xcc, 0x21, 0xc3, 0xaf, 0x90, 0xf7, 0x06, 0xd4, 0xf6, 0x92, 0x3f, - 0x0a, 0xb6, 0x9f, 0x41, 0xb0, 0xfb, 0x54, 0xeb, 0x0f, 0x76, 0x52, 0xf8, 0x29, 0xf2, 0x26, 0x37, - 0x8d, 0xbb, 0xd9, 0xba, 0x1b, 0xe8, 0xa7, 0xdf, 0x4d, 0x52, 0xdc, 0x41, 0x47, 0x03, 0x58, 0x82, - 0x82, 0xdb, 0x3d, 0x6e, 0x9a, 0x7a, 0xb5, 0x2f, 0x9b, 0x86, 0xf3, 0x6d, 0xd3, 0x70, 0xbe, 0x6f, - 0x1a, 0xce, 0xc7, 0x1f, 0x8d, 0x3b, 0xd3, 0x8a, 0xf9, 0x46, 0x3a, 0x3f, 0x03, 0x00, 0x00, 0xff, - 0xff, 0xff, 0x5a, 0xf8, 0xc1, 0xf2, 0x04, 0x00, 0x00, + // 568 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xc1, 0x6e, 0xd3, 0x30, + 0x18, 0xc7, 0x09, 0x63, 0x6d, 0x62, 0x56, 0x51, 0x19, 0x0d, 0x99, 0x81, 0xba, 0x6a, 0x07, 0xe8, + 0x65, 0x89, 0xd8, 0x34, 0x38, 0x77, 0xed, 0x40, 0x15, 0x62, 0x12, 0xd9, 0x76, 0x8e, 0xdc, 0xf8, + 0x6b, 0x6b, 0xa5, 0xd8, 0x56, 0xe2, 0x24, 0xda, 0x03, 0xf0, 0x0e, 0xbc, 0x00, 0x2f, 0xc0, 0x53, + 0x70, 0xe4, 0xbc, 0xc3, 0x84, 0xc6, 0x8b, 0x20, 0xdb, 0x65, 0x83, 0x4a, 0x68, 0xa2, 0x27, 0x6e, + 0x9f, 0xff, 0xff, 0xef, 0xfb, 0x7d, 0x76, 0x1c, 0x7f, 0xa8, 0x3f, 0xe5, 0x7a, 0x56, 0x8e, 0xc3, + 0x54, 0x7e, 0x88, 0x4e, 0x67, 0x70, 0x3a, 0xe3, 0x62, 0x5a, 0x1c, 0x83, 0xae, 0x65, 0x9e, 0x45, + 0x5a, 0x8b, 0x88, 0x2a, 0x1e, 0xa9, 0x5c, 0x6a, 0x99, 0xca, 0x79, 0x34, 0x97, 0x39, 0xad, 0xa9, + 0x88, 0x18, 0x54, 0x3c, 0x85, 0xd0, 0xea, 0xb8, 0xb9, 0x50, 0xb7, 0x76, 0x7f, 0x63, 0x4d, 0xe5, + 0x54, 0xba, 0xba, 0x71, 0x39, 0xb1, 0x2b, 0xbb, 0xb0, 0x91, 0xab, 0xfb, 0x23, 0xfd, 0xaf, 0xad, + 0xa9, 0xe2, 0x2e, 0x7d, 0xe7, 0x8b, 0x87, 0xda, 0x43, 0xdb, 0x77, 0xc4, 0x40, 0x68, 0x3e, 0xe1, + 0x90, 0xe3, 0x63, 0xd4, 0xa4, 0x4a, 0x25, 0x50, 0x72, 0xe2, 0x75, 0xbd, 0xde, 0xc6, 0xe1, 0xc1, + 0xc5, 0xe5, 0xf6, 0x8b, 0xdb, 0xc0, 0xa9, 0xcc, 0x21, 0xd2, 0xe7, 0x0a, 0x8a, 0xb0, 0xaf, 0xd4, + 0xd1, 0xd9, 0x28, 0x6e, 0x50, 0xa5, 0x8e, 0x4a, 0x6e, 0x78, 0x0c, 0x2a, 0xcb, 0xbb, 0xbb, 0x12, + 0x6f, 0x08, 0x95, 0xe5, 0x31, 0xa8, 0x8e, 0x4a, 0xbe, 0xf3, 0xb1, 0x81, 0x1a, 0x6e, 0xd3, 0xff, + 0xfb, 0x56, 0xf1, 0x26, 0x32, 0xe4, 0x84, 0x33, 0xb2, 0xd6, 0xf5, 0x7a, 0x41, 0xbc, 0x4e, 0x95, + 0x1a, 0x31, 0x23, 0x9b, 0x36, 0x9c, 0x91, 0x7b, 0x4e, 0x66, 0x50, 0x8d, 0x18, 0x7e, 0x8f, 0x7c, + 0x23, 0x53, 0xc6, 0x72, 0xb2, 0x6e, 0xdb, 0xbf, 0xbc, 0xb8, 0xdc, 0xde, 0xfb, 0xb7, 0xf6, 0x7d, + 0xc6, 0xf2, 0xd8, 0x9c, 0xc2, 0x04, 0x38, 0x46, 0x81, 0xa8, 0xb3, 0xa4, 0x48, 0x32, 0x38, 0x27, + 0x8d, 0x95, 0x98, 0xc7, 0x75, 0x76, 0xf2, 0x16, 0xce, 0xe3, 0xa6, 0x70, 0x81, 0x61, 0x9a, 0x43, + 0x39, 0x66, 0x73, 0x25, 0x66, 0x5f, 0x29, 0xc7, 0xa4, 0x2e, 0xf8, 0x75, 0x91, 0x86, 0xe8, 0xaf, + 0x7a, 0x91, 0x06, 0x68, 0x3e, 0xb7, 0xe1, 0x11, 0xe4, 0x4f, 0x92, 0x54, 0xe8, 0xa4, 0x54, 0x24, + 0xe8, 0x7a, 0xbd, 0x56, 0xdc, 0x98, 0x0c, 0x84, 0x3e, 0x53, 0xf8, 0x29, 0x42, 0xce, 0x61, 0xb2, + 0x16, 0x04, 0x59, 0xcf, 0x37, 0xde, 0x50, 0xd6, 0x02, 0xef, 0xa2, 0x87, 0x8c, 0x17, 0x74, 0x3c, + 0x87, 0xc4, 0x65, 0xa5, 0x33, 0x48, 0x33, 0x72, 0xbf, 0xeb, 0xf5, 0xfc, 0xb8, 0xbd, 0xb0, 0x5e, + 0x0f, 0x84, 0x1e, 0x18, 0x1d, 0x3f, 0x47, 0xed, 0xb2, 0x80, 0x62, 0x7f, 0x2f, 0x19, 0x73, 0xed, + 0x2a, 0xc8, 0x86, 0xcd, 0x6d, 0x39, 0xfd, 0x90, 0x6b, 0x93, 0x8d, 0x0f, 0xd0, 0x23, 0x9a, 0x6a, + 0x5e, 0x51, 0xcd, 0xa5, 0x48, 0x52, 0x29, 0x0a, 0x9d, 0x53, 0x2e, 0x74, 0x41, 0x5a, 0xf6, 0x0f, + 0xd8, 0xbc, 0x71, 0x07, 0x37, 0x26, 0x7e, 0x82, 0x82, 0x39, 0x2d, 0x74, 0x52, 0x00, 0x08, 0xb2, + 0xd9, 0xf5, 0x7a, 0x6b, 0xb1, 0x6f, 0x84, 0x13, 0x00, 0xb1, 0xf7, 0xd9, 0x43, 0x2d, 0xf7, 0x0e, + 0xde, 0x51, 0x41, 0xa7, 0x90, 0xe3, 0x57, 0x28, 0x78, 0x03, 0x7a, 0xf1, 0x36, 0x1e, 0x87, 0x8b, + 0x19, 0x12, 0x2e, 0xbf, 0xf0, 0xad, 0x07, 0x4b, 0x16, 0x7e, 0x86, 0x82, 0x93, 0xeb, 0xc2, 0x65, + 0x77, 0xcb, 0x0f, 0xcd, 0xc4, 0xe8, 0xa7, 0x19, 0xde, 0x47, 0x1b, 0x43, 0x98, 0x83, 0x86, 0xdb, + 0x7b, 0x5c, 0x17, 0x1d, 0xb6, 0xbf, 0x5e, 0x75, 0xbc, 0x6f, 0x57, 0x1d, 0xef, 0xfb, 0x55, 0xc7, + 0xfb, 0xf4, 0xa3, 0x73, 0x67, 0xdc, 0xb0, 0xd3, 0x67, 0xff, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, + 0xd5, 0xda, 0x65, 0xf2, 0x29, 0x05, 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 9bffe676d..802e317df 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -28,6 +28,7 @@ message Device { // Options bool disable_f_cnt_check = 11; bool uses32_bit_f_cnt = 12; + string activation_constraints = 13; // Other int64 last_seen = 21; diff --git a/api/protocol/lorawan/device_address.pb.go b/api/protocol/lorawan/device_address.pb.go new file mode 100644 index 000000000..fab991ec0 --- /dev/null +++ b/api/protocol/lorawan/device_address.pb.go @@ -0,0 +1,971 @@ +// Code generated by protoc-gen-gogo. +// source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto +// DO NOT EDIT! + +package lorawan + +import proto "github.com/golang/protobuf/proto" +import fmt "fmt" +import math "math" +import _ "github.com/gogo/protobuf/gogoproto" + +import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" + +import ( + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +import io "io" + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +type PrefixesRequest struct { +} + +func (m *PrefixesRequest) Reset() { *m = PrefixesRequest{} } +func (m *PrefixesRequest) String() string { return proto.CompactTextString(m) } +func (*PrefixesRequest) ProtoMessage() {} +func (*PrefixesRequest) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{0} } + +type PrefixesResponse struct { + Prefixes []*PrefixesResponse_PrefixMapping `protobuf:"bytes,1,rep,name=prefixes" json:"prefixes,omitempty"` +} + +func (m *PrefixesResponse) Reset() { *m = PrefixesResponse{} } +func (m *PrefixesResponse) String() string { return proto.CompactTextString(m) } +func (*PrefixesResponse) ProtoMessage() {} +func (*PrefixesResponse) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{1} } + +func (m *PrefixesResponse) GetPrefixes() []*PrefixesResponse_PrefixMapping { + if m != nil { + return m.Prefixes + } + return nil +} + +type PrefixesResponse_PrefixMapping struct { + Prefix string `protobuf:"bytes,1,opt,name=prefix,proto3" json:"prefix,omitempty"` + Usage []string `protobuf:"bytes,2,rep,name=usage" json:"usage,omitempty"` +} + +func (m *PrefixesResponse_PrefixMapping) Reset() { *m = PrefixesResponse_PrefixMapping{} } +func (m *PrefixesResponse_PrefixMapping) String() string { return proto.CompactTextString(m) } +func (*PrefixesResponse_PrefixMapping) ProtoMessage() {} +func (*PrefixesResponse_PrefixMapping) Descriptor() ([]byte, []int) { + return fileDescriptorDeviceAddress, []int{1, 0} +} + +type DevAddrRequest struct { + Usage []string `protobuf:"bytes,1,rep,name=usage" json:"usage,omitempty"` +} + +func (m *DevAddrRequest) Reset() { *m = DevAddrRequest{} } +func (m *DevAddrRequest) String() string { return proto.CompactTextString(m) } +func (*DevAddrRequest) ProtoMessage() {} +func (*DevAddrRequest) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{2} } + +type DevAddrResponse struct { + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` +} + +func (m *DevAddrResponse) Reset() { *m = DevAddrResponse{} } +func (m *DevAddrResponse) String() string { return proto.CompactTextString(m) } +func (*DevAddrResponse) ProtoMessage() {} +func (*DevAddrResponse) Descriptor() ([]byte, []int) { return fileDescriptorDeviceAddress, []int{3} } + +func init() { + proto.RegisterType((*PrefixesRequest)(nil), "lorawan.PrefixesRequest") + proto.RegisterType((*PrefixesResponse)(nil), "lorawan.PrefixesResponse") + proto.RegisterType((*PrefixesResponse_PrefixMapping)(nil), "lorawan.PrefixesResponse.PrefixMapping") + proto.RegisterType((*DevAddrRequest)(nil), "lorawan.DevAddrRequest") + proto.RegisterType((*DevAddrResponse)(nil), "lorawan.DevAddrResponse") +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion3 + +// Client API for DevAddrManager service + +type DevAddrManagerClient interface { + GetPrefixes(ctx context.Context, in *PrefixesRequest, opts ...grpc.CallOption) (*PrefixesResponse, error) + GetDevAddr(ctx context.Context, in *DevAddrRequest, opts ...grpc.CallOption) (*DevAddrResponse, error) +} + +type devAddrManagerClient struct { + cc *grpc.ClientConn +} + +func NewDevAddrManagerClient(cc *grpc.ClientConn) DevAddrManagerClient { + return &devAddrManagerClient{cc} +} + +func (c *devAddrManagerClient) GetPrefixes(ctx context.Context, in *PrefixesRequest, opts ...grpc.CallOption) (*PrefixesResponse, error) { + out := new(PrefixesResponse) + err := grpc.Invoke(ctx, "/lorawan.DevAddrManager/GetPrefixes", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *devAddrManagerClient) GetDevAddr(ctx context.Context, in *DevAddrRequest, opts ...grpc.CallOption) (*DevAddrResponse, error) { + out := new(DevAddrResponse) + err := grpc.Invoke(ctx, "/lorawan.DevAddrManager/GetDevAddr", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// Server API for DevAddrManager service + +type DevAddrManagerServer interface { + GetPrefixes(context.Context, *PrefixesRequest) (*PrefixesResponse, error) + GetDevAddr(context.Context, *DevAddrRequest) (*DevAddrResponse, error) +} + +func RegisterDevAddrManagerServer(s *grpc.Server, srv DevAddrManagerServer) { + s.RegisterService(&_DevAddrManager_serviceDesc, srv) +} + +func _DevAddrManager_GetPrefixes_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(PrefixesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DevAddrManagerServer).GetPrefixes(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DevAddrManager/GetPrefixes", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DevAddrManagerServer).GetPrefixes(ctx, req.(*PrefixesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _DevAddrManager_GetDevAddr_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DevAddrRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(DevAddrManagerServer).GetDevAddr(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lorawan.DevAddrManager/GetDevAddr", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(DevAddrManagerServer).GetDevAddr(ctx, req.(*DevAddrRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _DevAddrManager_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lorawan.DevAddrManager", + HandlerType: (*DevAddrManagerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GetPrefixes", + Handler: _DevAddrManager_GetPrefixes_Handler, + }, + { + MethodName: "GetDevAddr", + Handler: _DevAddrManager_GetDevAddr_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: fileDescriptorDeviceAddress, +} + +func (m *PrefixesRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *PrefixesRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *PrefixesResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *PrefixesResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Prefixes) > 0 { + for _, msg := range m.Prefixes { + data[i] = 0xa + i++ + i = encodeVarintDeviceAddress(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *PrefixesResponse_PrefixMapping) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *PrefixesResponse_PrefixMapping) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Prefix) > 0 { + data[i] = 0xa + i++ + i = encodeVarintDeviceAddress(data, i, uint64(len(m.Prefix))) + i += copy(data[i:], m.Prefix) + } + if len(m.Usage) > 0 { + for _, s := range m.Usage { + data[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + return i, nil +} + +func (m *DevAddrRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DevAddrRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Usage) > 0 { + for _, s := range m.Usage { + data[i] = 0xa + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + return i, nil +} + +func (m *DevAddrResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DevAddrResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.DevAddr != nil { + data[i] = 0xa + i++ + i = encodeVarintDeviceAddress(data, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + return i, nil +} + +func encodeFixed64DeviceAddress(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32DeviceAddress(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintDeviceAddress(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *PrefixesRequest) Size() (n int) { + var l int + _ = l + return n +} + +func (m *PrefixesResponse) Size() (n int) { + var l int + _ = l + if len(m.Prefixes) > 0 { + for _, e := range m.Prefixes { + l = e.Size() + n += 1 + l + sovDeviceAddress(uint64(l)) + } + } + return n +} + +func (m *PrefixesResponse_PrefixMapping) Size() (n int) { + var l int + _ = l + l = len(m.Prefix) + if l > 0 { + n += 1 + l + sovDeviceAddress(uint64(l)) + } + if len(m.Usage) > 0 { + for _, s := range m.Usage { + l = len(s) + n += 1 + l + sovDeviceAddress(uint64(l)) + } + } + return n +} + +func (m *DevAddrRequest) Size() (n int) { + var l int + _ = l + if len(m.Usage) > 0 { + for _, s := range m.Usage { + l = len(s) + n += 1 + l + sovDeviceAddress(uint64(l)) + } + } + return n +} + +func (m *DevAddrResponse) Size() (n int) { + var l int + _ = l + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovDeviceAddress(uint64(l)) + } + return n +} + +func sovDeviceAddress(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozDeviceAddress(x uint64) (n int) { + return sovDeviceAddress(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *PrefixesRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrefixesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrefixesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PrefixesResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrefixesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrefixesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prefixes", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prefixes = append(m.Prefixes, &PrefixesResponse_PrefixMapping{}) + if err := m.Prefixes[len(m.Prefixes)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: PrefixMapping: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PrefixMapping: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Prefix", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Prefix = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Usage = append(m.Usage, string(data[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevAddrRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevAddrRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevAddrRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Usage", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Usage = append(m.Usage, string(data[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DevAddrResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DevAddrResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DevAddrResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthDeviceAddress + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipDeviceAddress(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthDeviceAddress + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipDeviceAddress(data []byte) (n int, err error) { + l := len(data) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if data[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + iNdEx += length + if length < 0 { + return 0, ErrInvalidLengthDeviceAddress + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowDeviceAddress + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipDeviceAddress(data[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthDeviceAddress = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowDeviceAddress = fmt.Errorf("proto: integer overflow") +) + +func init() { + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto", fileDescriptorDeviceAddress) +} + +var fileDescriptorDeviceAddress = []byte{ + // 361 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x51, 0x4d, 0x4f, 0xe3, 0x30, + 0x14, 0x5c, 0x6f, 0xb5, 0xfd, 0x70, 0x77, 0xb7, 0x5d, 0x6b, 0xb5, 0x1b, 0x72, 0x08, 0x55, 0x0f, + 0xd0, 0x0b, 0x89, 0x54, 0x24, 0x6e, 0x08, 0x51, 0x90, 0x2a, 0x0e, 0x45, 0x10, 0xf5, 0x8e, 0xdc, + 0xf8, 0x35, 0x8d, 0x28, 0x71, 0xb0, 0x9d, 0x16, 0x7e, 0x08, 0x88, 0x9f, 0xc4, 0x91, 0x33, 0x07, + 0x84, 0xca, 0x1f, 0x41, 0x72, 0xdc, 0xb4, 0xe5, 0x43, 0xdc, 0x3c, 0x6f, 0xe6, 0xcd, 0x9b, 0x4c, + 0xf0, 0x51, 0x18, 0xa9, 0x51, 0x3a, 0x70, 0x03, 0x7e, 0xe1, 0xf5, 0x47, 0xd0, 0x1f, 0x45, 0x71, + 0x28, 0x8f, 0x41, 0x4d, 0xb9, 0x38, 0xf7, 0x94, 0x8a, 0x3d, 0x9a, 0x44, 0x5e, 0x22, 0xb8, 0xe2, + 0x01, 0x1f, 0x7b, 0x63, 0x2e, 0xe8, 0x94, 0xc6, 0x1e, 0x83, 0x49, 0x14, 0xc0, 0x19, 0x65, 0x4c, + 0x80, 0x94, 0xae, 0xe6, 0x49, 0xc9, 0xb0, 0xf6, 0xd6, 0x92, 0x67, 0xc8, 0x43, 0x9e, 0xed, 0x0f, + 0xd2, 0xa1, 0x46, 0x1a, 0xe8, 0x57, 0xb6, 0xd7, 0xfc, 0x83, 0x6b, 0x27, 0x02, 0x86, 0xd1, 0x15, + 0x48, 0x1f, 0x2e, 0x53, 0x90, 0xaa, 0x79, 0x8b, 0x70, 0x7d, 0x31, 0x93, 0x09, 0x8f, 0x25, 0x90, + 0x03, 0x5c, 0x4e, 0xcc, 0xcc, 0x42, 0x8d, 0x42, 0xab, 0xda, 0xde, 0x74, 0xcd, 0x49, 0xf7, 0xad, + 0xd8, 0x0c, 0x7a, 0x34, 0x49, 0xa2, 0x38, 0xf4, 0xf3, 0x45, 0x7b, 0x17, 0xff, 0x5a, 0xa1, 0xc8, + 0x3f, 0x5c, 0xcc, 0x48, 0x0b, 0x35, 0x50, 0xab, 0xe2, 0x1b, 0x44, 0xfe, 0xe2, 0x1f, 0xa9, 0xa4, + 0x21, 0x58, 0xdf, 0x1b, 0x85, 0x56, 0xc5, 0xcf, 0x40, 0x73, 0x03, 0xff, 0x3e, 0x84, 0xc9, 0x3e, + 0x63, 0xc2, 0x44, 0x5d, 0xe8, 0xd0, 0xb2, 0x8e, 0xe1, 0x5a, 0xae, 0x33, 0xf1, 0x4f, 0x71, 0x99, + 0xc1, 0x44, 0x77, 0xa6, 0x4f, 0xfd, 0xec, 0xec, 0x3c, 0x3e, 0xad, 0xb7, 0xbf, 0xea, 0x3f, 0xe0, + 0x02, 0x3c, 0x75, 0x9d, 0x80, 0x74, 0xe7, 0x8e, 0x25, 0x96, 0x3d, 0xda, 0x37, 0x28, 0x8f, 0xd3, + 0xa3, 0x31, 0x0d, 0x41, 0x90, 0x0e, 0xae, 0x76, 0x41, 0xcd, 0xeb, 0x20, 0xd6, 0x07, 0x0d, 0xe9, + 0xdc, 0xf6, 0xda, 0xa7, 0xdd, 0x91, 0x3d, 0x8c, 0xbb, 0xa0, 0x8c, 0x31, 0xf9, 0x9f, 0x0b, 0x57, + 0xbf, 0xdc, 0xb6, 0xde, 0x13, 0x99, 0x41, 0xa7, 0x7e, 0x3f, 0x73, 0xd0, 0xc3, 0xcc, 0x41, 0xcf, + 0x33, 0x07, 0xdd, 0xbd, 0x38, 0xdf, 0x06, 0x45, 0xfd, 0xab, 0xb7, 0x5f, 0x03, 0x00, 0x00, 0xff, + 0xff, 0x4c, 0x06, 0x93, 0x24, 0x6f, 0x02, 0x00, 0x00, +} diff --git a/api/protocol/lorawan/device_address.proto b/api/protocol/lorawan/device_address.proto new file mode 100644 index 000000000..e2f320319 --- /dev/null +++ b/api/protocol/lorawan/device_address.proto @@ -0,0 +1,32 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +syntax = "proto3"; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; + +package lorawan; + +message PrefixesRequest {} + +message PrefixesResponse { + message PrefixMapping { + string prefix = 1; + repeated string usage = 2; + } + + repeated PrefixMapping prefixes = 1; +} + +message DevAddrRequest { + repeated string usage = 1; +} + +message DevAddrResponse { + bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; +} + +service DevAddrManager { + rpc GetPrefixes(PrefixesRequest) returns (PrefixesResponse); + rpc GetDevAddr(DevAddrRequest) returns (DevAddrResponse); +} diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index 21ea9167c..e25df8853 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -39,7 +39,7 @@ var brokerRegisterPrefixCmd = &cobra.Command{ for _, prefixString := range args { ctx := ctx.WithField("Prefix", prefixString) - prefix, length, err := types.ParseDevAddrPrefix(prefixString) + prefix, err := types.ParseDevAddrPrefix(prefixString) if err != nil { ctx.WithError(err).Error("Could not register prefix") continue @@ -49,7 +49,7 @@ var brokerRegisterPrefixCmd = &cobra.Command{ Id: viper.GetString("id"), Metadata: &discovery.Metadata{ Key: discovery.Metadata_PREFIX, - Value: []byte{byte(length), prefix[0], prefix[1], prefix[2], prefix[3]}, + Value: []byte{byte(prefix.Length), prefix.DevAddr[0], prefix.DevAddr[1], prefix.DevAddr[2], prefix.DevAddr[3]}, }, }) if err != nil { diff --git a/cmd/networkserver.go b/cmd/networkserver.go index 37c7ccf1d..2da7d1f17 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -8,18 +8,17 @@ import ( "net" "os" "os/signal" + "strings" "syscall" - "google.golang.org/grpc" - - "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/networkserver" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc" + "gopkg.in/redis.v3" ) // networkserverCmd represents the networkserver command @@ -32,7 +31,6 @@ var networkserverCmd = &cobra.Command{ "Server": fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address"), viper.GetInt("networkserver.server-port")), "Database": fmt.Sprintf("%s/%d", viper.GetString("networkserver.redis-address"), viper.GetInt("networkserver.redis-db")), "NetID": viper.GetString("networkserver.net-id"), - "Prefix": viper.GetString("networkserver.prefix"), }).Info("Initializing Network Server") }, Run: func(cmd *cobra.Command, args []string) { @@ -55,14 +53,22 @@ var networkserverCmd = &cobra.Command{ // networkserver Server networkserver := networkserver.NewRedisNetworkServer(client, viper.GetInt("networkserver.net-id")) - prefix, length, err := types.ParseDevAddrPrefix(viper.GetString("networkserver.prefix")) - if err != nil { - ctx.WithError(err).Fatal("Could not initialize networkserver") - } - err = networkserver.UsePrefix(prefix[:], length) - if err != nil { - ctx.WithError(err).Fatal("Could not initialize networkserver") + + // Register Prefixes + for prefix, usage := range viper.GetStringMapString("networkserver.prefixes") { + prefix, err := types.ParseDevAddrPrefix(prefix) + if err != nil { + ctx.WithError(err).Warn("Could not use DevAddr Prefix. Skipping.") + continue + } + err = networkserver.UsePrefix(prefix, strings.Split(usage, ",")) + if err != nil { + ctx.WithError(err).Fatal("Could not initialize networkserver") + continue + } + ctx.Infof("Using DevAddr prefix %s (%v)", prefix, usage) } + err = networkserver.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize networkserver") @@ -99,8 +105,9 @@ func init() { networkserverCmd.Flags().Int("net-id", 19, "LoRaWAN NetID") viper.BindPFlag("networkserver.net-id", networkserverCmd.Flags().Lookup("net-id")) - networkserverCmd.Flags().String("prefix", "26002000/20", "LoRaWAN DevAddr Prefix that should be used for issuing device addresses") - viper.BindPFlag("networkserver.prefix", networkserverCmd.Flags().Lookup("prefix")) + viper.SetDefault("networkserver.prefixes", map[string]string{ + "26000000/20": "otaa,abp,world,local,private,testing", + }) networkserverCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") networkserverCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") diff --git a/core/broker/broker.go b/core/broker/broker.go index 38dfc742e..8fa45896f 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -5,15 +5,20 @@ package broker import ( "errors" + "strings" "sync" "time" + "google.golang.org/grpc" + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" + "github.com/TheThingsNetwork/ttn/core/types" ) type Broker interface { @@ -57,12 +62,60 @@ type broker struct { nsAddr string nsCert string nsToken string + nsConn *grpc.ClientConn ns networkserver.NetworkServerClient - nsManager pb_lorawan.DeviceManagerClient uplinkDeduplicator Deduplicator activationDeduplicator Deduplicator } +func (b *broker) checkPrefixAnnouncements() error { + // Get prefixes from NS + nsPrefixes := map[types.DevAddrPrefix]string{} + devAddrClient := pb_lorawan.NewDevAddrManagerClient(b.nsConn) + resp, err := devAddrClient.GetPrefixes(b.GetContext(""), &pb_lorawan.PrefixesRequest{}) + if err != nil { + return err + } + for _, mapping := range resp.Prefixes { + prefix, err := types.ParseDevAddrPrefix(mapping.Prefix) + if err != nil { + continue + } + nsPrefixes[prefix] = strings.Join(mapping.Usage, ",") + } + + // Get self from Discovery + var announcedPrefixes []types.DevAddrPrefix + self, err := b.Component.Discover("broker", b.Component.Identity.Id) + if err != nil { + return err + } + for _, meta := range self.Metadata { + if meta.Key == pb_discovery.Metadata_PREFIX && len(meta.Value) == 5 { + var prefix types.DevAddrPrefix + copy(prefix.DevAddr[:], meta.Value[1:]) + prefix.Length = int(meta.Value[0]) + announcedPrefixes = append(announcedPrefixes, prefix) + } + } + +nextPrefix: + for nsPrefix, usage := range nsPrefixes { + if !strings.Contains(usage, "world") && !strings.Contains(usage, "local") { + continue + } + for _, announcedPrefix := range announcedPrefixes { + if nsPrefix.DevAddr == announcedPrefix.DevAddr && nsPrefix.Length == announcedPrefix.Length { + b.Ctx.WithField("NSPrefix", nsPrefix).WithField("DPrefix", announcedPrefix).Info("Prefix found in Discovery") + continue nextPrefix + } + } + b.Ctx.WithField("Prefix", nsPrefix).Warn("Prefix not announced in Discovery") + } + + return nil +} + func (b *broker) Init(c *core.Component) error { b.Component = c err := b.Component.UpdateTokenKey() @@ -79,8 +132,9 @@ func (b *broker) Init(c *core.Component) error { if err != nil { return err } + b.nsConn = conn b.ns = networkserver.NewNetworkServerClient(conn) - b.nsManager = pb_lorawan.NewDeviceManagerClient(conn) + b.checkPrefixAnnouncements() b.Component.SetStatus(core.StatusHealthy) return nil } diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index cad0c10a1..2bb77ac1f 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -9,27 +9,30 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) type brokerManager struct { - broker *broker + broker *broker + deviceManager pb_lorawan.DeviceManagerClient + devAddrManager pb_lorawan.DevAddrManagerClient } var errf = grpc.Errorf func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { - return b.broker.nsManager.GetDevice(ctx, in) + return b.deviceManager.GetDevice(ctx, in) } func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*api.Ack, error) { - return b.broker.nsManager.SetDevice(ctx, in) + return b.deviceManager.SetDevice(ctx, in) } func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*api.Ack, error) { - return b.broker.nsManager.DeleteDevice(ctx, in) + return b.deviceManager.DeleteDevice(ctx, in) } func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*api.Ack, error) { @@ -50,12 +53,25 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A return &api.Ack{}, nil } +func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesRequest) (*lorawan.PrefixesResponse, error) { + return b.devAddrManager.GetPrefixes(ctx, in) +} + +func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrRequest) (*lorawan.DevAddrResponse, error) { + return b.devAddrManager.GetDevAddr(ctx, in) +} + func (b *brokerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { return nil, errors.New("Not Implemented") } func (b *broker) RegisterManager(s *grpc.Server) { - server := &brokerManager{b} + server := &brokerManager{ + broker: b, + deviceManager: pb_lorawan.NewDeviceManagerClient(b.nsConn), + devAddrManager: pb_lorawan.NewDevAddrManagerClient(b.nsConn), + } pb.RegisterBrokerManagerServer(s, server) lorawan.RegisterDeviceManagerServer(s, server) + lorawan.RegisterDevAddrManagerServer(s, server) } diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 9cf478177..5e0111020 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -69,11 +69,15 @@ func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, e matches := []*pb.Announcement{} for _, service := range d.cache { for _, meta := range service.Metadata { - var prefix types.DevAddr - copy(prefix[:], meta.Value[1:]) - if meta.Key == pb.Metadata_PREFIX && len(meta.Value) == 5 && devAddr.HasPrefix(prefix, int(meta.Value[0])) { - matches = append(matches, service) - break + if meta.Key == pb.Metadata_PREFIX && len(meta.Value) == 5 { + var prefix types.DevAddrPrefix + copy(prefix.DevAddr[:], meta.Value[1:]) + prefix.Length = int(meta.Value[0]) + + if devAddr.HasPrefix(prefix) { + matches = append(matches, service) + break + } } } } diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 8b694f6b3..760a31815 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -13,12 +13,6 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" ) -// Options for the specified device -type Options struct { - DisableFCntCheck bool // Disable Frame counter check (insecure) - Uses32BitFCnt bool // Use 32-bit Frame counters -} - type DevNonce [2]byte type AppNonce [3]byte diff --git a/core/handler/handler.go b/core/handler/handler.go index f90267a8a..39ce930eb 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -12,7 +12,6 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" - pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" @@ -52,7 +51,6 @@ type handler struct { ttnBrokerConn *grpc.ClientConn ttnBroker pb_broker.BrokerClient ttnBrokerManager pb_broker.BrokerManagerClient - ttnDeviceManager pb_lorawan.DeviceManagerClient downlink chan *pb_broker.DownlinkMessage @@ -108,7 +106,6 @@ func (h *handler) associateBroker() error { h.ttnBrokerConn = conn h.ttnBroker = pb_broker.NewBrokerClient(conn) h.ttnBrokerManager = pb_broker.NewBrokerManagerClient(conn) - h.ttnDeviceManager = pb_lorawan.NewDeviceManagerClient(conn) h.downlink = make(chan *pb_broker.DownlinkMessage) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 5b7b43a49..cea55c199 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -19,7 +19,9 @@ import ( ) type handlerManager struct { - handler *handler + handler *handler + deviceManager pb_lorawan.DeviceManagerClient + devAddrManager pb_lorawan.DevAddrManagerClient } var errf = grpc.Errorf @@ -51,7 +53,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) return nil, err } - nsDev, err := h.handler.ttnDeviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ + nsDev, err := h.deviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ AppEui: &dev.AppEUI, DevEui: &dev.DevEUI, }) @@ -98,7 +100,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack if dev != nil { // When this is an update if dev.AppEUI != *lorawan.AppEui || dev.DevEUI != *lorawan.DevEui { // If the AppEUI or DevEUI is changed, we should remove the device from the NetworkServer and re-add it later - _, err = h.handler.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{ + _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{ AppEui: &dev.AppEUI, DevEui: &dev.DevEUI, }) @@ -131,19 +133,25 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack } nsUpdated := &pb_lorawan.Device{ - AppId: in.AppId, - DevId: in.DevId, - AppEui: lorawan.AppEui, - DevEui: lorawan.DevEui, - DevAddr: lorawan.DevAddr, - NwkSKey: lorawan.NwkSKey, - FCntUp: lorawan.FCntUp, - FCntDown: lorawan.FCntDown, - DisableFCntCheck: lorawan.DisableFCntCheck, - Uses32BitFCnt: lorawan.Uses32BitFCnt, - } - - _, err = h.handler.ttnDeviceManager.SetDevice(ctx, nsUpdated) + AppId: in.AppId, + DevId: in.DevId, + AppEui: lorawan.AppEui, + DevEui: lorawan.DevEui, + DevAddr: lorawan.DevAddr, + NwkSKey: lorawan.NwkSKey, + FCntUp: lorawan.FCntUp, + FCntDown: lorawan.FCntDown, + DisableFCntCheck: lorawan.DisableFCntCheck, + Uses32BitFCnt: lorawan.Uses32BitFCnt, + ActivationConstraints: lorawan.ActivationConstraints, + } + + // Devices are activated locally by default + if nsUpdated.ActivationConstraints == "" { + nsUpdated.ActivationConstraints = "local" + } + + _, err = h.deviceManager.SetDevice(ctx, nsUpdated) if err != nil { return nil, err } @@ -161,7 +169,7 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifi if err != nil { return nil, err } - _, err = h.handler.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { return nil, err } @@ -319,7 +327,7 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati return nil, err } for _, dev := range devices { - _, err = h.handler.ttnDeviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) + _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { return nil, err } @@ -350,12 +358,25 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati return &api.Ack{}, nil } +func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { + return h.devAddrManager.GetPrefixes(ctx, in) +} + +func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { + return h.devAddrManager.GetDevAddr(ctx, in) +} + func (h *handlerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { return nil, errors.New("Not Implemented") } -func (b *handler) RegisterManager(s *grpc.Server) { - server := &handlerManager{b} +func (h *handler) RegisterManager(s *grpc.Server) { + server := &handlerManager{ + handler: h, + deviceManager: pb_lorawan.NewDeviceManagerClient(h.ttnBrokerConn), + devAddrManager: pb_lorawan.NewDevAddrManagerClient(h.ttnBrokerConn), + } pb.RegisterHandlerManagerServer(s, server) pb.RegisterApplicationManagerServer(s, server) + pb_lorawan.RegisterDevAddrManagerServer(s, server) } diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 6f4d5bdf8..9bf092a8e 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -14,8 +14,9 @@ import ( // Options for the specified device type Options struct { - DisableFCntCheck bool `json:"disable_fcnt_check,omitemtpy"` // Disable Frame counter check (insecure) - Uses32BitFCnt bool `json:"uses_32_bit_fcnt,omitemtpy"` // Use 32-bit Frame counters + ActivationConstraints string `json:"activation_constraints,omitempty"` // Activation Constraints (public/local/private) + DisableFCntCheck bool `json:"disable_fcnt_check,omitemtpy"` // Disable Frame counter check (insecure) + Uses32BitFCnt bool `json:"uses_32_bit_fcnt,omitemtpy"` // Use 32-bit Frame counters } // Device contains the state of a device diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 3b1536e5c..284d9cacd 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -84,8 +84,9 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev FCntUp: in.FCntUp, FCntDown: in.FCntDown, Options: device.Options{ - DisableFCntCheck: in.DisableFCntCheck, - Uses32BitFCnt: in.Uses32BitFCnt, + DisableFCntCheck: in.DisableFCntCheck, + Uses32BitFCnt: in.Uses32BitFCnt, + ActivationConstraints: in.ActivationConstraints, }, } @@ -114,6 +115,29 @@ func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan. return &api.Ack{}, nil } +func (n *networkServerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { + var mapping []*pb_lorawan.PrefixesResponse_PrefixMapping + for prefix, usage := range n.networkServer.prefixes { + mapping = append(mapping, &pb_lorawan.PrefixesResponse_PrefixMapping{ + Prefix: prefix.String(), + Usage: usage, + }) + } + return &pb_lorawan.PrefixesResponse{ + Prefixes: mapping, + }, nil +} + +func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { + devAddr, err := n.networkServer.getDevAddr(in.Usage...) + if err != nil { + return nil, err + } + return &pb_lorawan.DevAddrResponse{ + DevAddr: &devAddr, + }, nil +} + func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { return nil, errors.New("Not Implemented") } @@ -123,4 +147,5 @@ func (n *networkServer) RegisterManager(s *grpc.Server) { server := &networkServerManager{n} pb.RegisterNetworkServerManagerServer(s, server) pb_lorawan.RegisterDeviceManagerServer(s, server) + pb_lorawan.RegisterDevAddrManagerServer(s, server) } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 2880c2078..995d7ce5f 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -5,6 +5,8 @@ package networkserver import ( "errors" + "fmt" + "strings" "time" "gopkg.in/redis.v3" @@ -27,7 +29,9 @@ type NetworkServer interface { core.ComponentInterface core.ManagementInterface - UsePrefix(prefixBytes []byte, length int) error + UsePrefix(prefix types.DevAddrPrefix, usage []string) error + GetPrefixesFor(requiredUsages ...string) []types.DevAddrPrefix + HandleGetDevices(*pb.DevicesRequest) (*pb.DevicesResponse, error) HandlePrepareActivation(*pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) HandleActivate(*pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) @@ -38,34 +42,49 @@ type NetworkServer interface { // NewRedisNetworkServer creates a new Redis-backed NetworkServer func NewRedisNetworkServer(client *redis.Client, netID int) NetworkServer { ns := &networkServer{ - devices: device.NewRedisDeviceStore(client), + devices: device.NewRedisDeviceStore(client), + prefixes: map[types.DevAddrPrefix][]string{}, } ns.netID = [3]byte{byte(netID >> 16), byte(netID >> 8), byte(netID)} - ns.prefix = [4]byte{ns.netID[2] << 1, 0, 0, 0} - ns.prefixLength = 7 return ns } type networkServer struct { *core.Component - devices device.Store - netID [3]byte - prefix [4]byte - prefixLength int + devices device.Store + netID [3]byte + prefixes map[types.DevAddrPrefix][]string } -func (n *networkServer) UsePrefix(prefixBytes []byte, length int) error { - if length < 7 { +func (n *networkServer) UsePrefix(prefix types.DevAddrPrefix, usage []string) error { + if prefix.Length < 7 { return errors.New("ttn/networkserver: Invalid prefix length") } - if prefixBytes[0]>>1 != n.netID[2] { + if prefix.DevAddr[0]>>1 != n.netID[2] { return errors.New("ttn/networkserver: Invalid prefix") } - copy(n.prefix[:], prefixBytes) - n.prefixLength = length + n.prefixes[prefix] = usage return nil } +func (n *networkServer) GetPrefixesFor(requiredUsages ...string) []types.DevAddrPrefix { + var suitablePrefixes []types.DevAddrPrefix + for prefix, offeredUsages := range n.prefixes { + matches := 0 + for _, requiredUsage := range requiredUsages { + for _, offeredUsage := range offeredUsages { + if offeredUsage == requiredUsage { + matches++ + } + } + } + if matches == len(requiredUsages) { + suitablePrefixes = append(suitablePrefixes, prefix) + } + } + return suitablePrefixes +} + func (n *networkServer) Init(c *core.Component) error { n.Component = c err := n.Component.UpdateTokenKey() @@ -116,6 +135,29 @@ func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesRes return res, nil } +func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) { + // Instantiate a new random source + random := random.New() + + // Generate random DevAddr bytes + var devAddr types.DevAddr + copy(devAddr[:], random.Bytes(4)) + + // Get a random prefix that matches the constraints + prefixes := n.GetPrefixesFor(constraints...) + if len(prefixes) == 0 { + return types.DevAddr{}, fmt.Errorf("ttn/networkserver: No DevAddr prefixes available for constraints %v", constraints) + } + + // Select a prefix + prefix := prefixes[random.Intn(len(prefixes))] + + // Apply the prefix + devAddr = devAddr.WithPrefix(prefix) + + return devAddr, nil +} + func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { if activation.AppEui == nil || activation.DevEui == nil { return nil, errors.New("ttn/networkserver: Activation missing AppEUI or DevEUI") @@ -127,6 +169,13 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat activation.AppId = dev.AppID activation.DevId = dev.DevID + // Get activation constraints (for DevAddr prefix selection) + activationConstraints := strings.Split(dev.Options.ActivationConstraints, ",") + if len(activationConstraints) == 1 && activationConstraints[0] == "" { + activationConstraints = []string{} + } + activationConstraints = append(activationConstraints, "otaa") + // Build activation metadata if not present if meta := activation.GetActivationMetadata(); meta == nil { activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} @@ -142,10 +191,11 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat } lorawanMeta := activation.ActivationMetadata.GetLorawan() - // Generate random DevAddr with prefix - var devAddr types.DevAddr - copy(devAddr[:], random.New().Bytes(4)) - devAddr = devAddr.WithPrefix(types.DevAddr(n.prefix), n.prefixLength) + // Get a random device address + devAddr, err := n.getDevAddr(activationConstraints...) + if err != nil { + return nil, err + } // Set the DevAddr in the Activation Metadata lorawanMeta.DevAddr = &devAddr diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 32d0d4bd6..ed20171ae 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -51,10 +51,10 @@ func TestUsePrefix(t *testing.T) { var client redis.Client ns := NewRedisNetworkServer(&client, 19) - a.So(ns.UsePrefix([]byte{}, 0), ShouldNotBeNil) - a.So(ns.UsePrefix([]byte{0x14}, 7), ShouldNotBeNil) - a.So(ns.UsePrefix([]byte{0x26}, 7), ShouldBeNil) - a.So(ns.(*networkServer).prefix, ShouldEqual, [4]byte{0x26, 0x00, 0x00, 0x00}) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr{0, 0, 0, 0}, Length: 0}, []string{"otaa"}), ShouldNotBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr{0x14, 0, 0, 0}, Length: 7}, []string{"otaa"}), ShouldNotBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr{0x26, 0, 0, 0}, Length: 7}, []string{"otaa"}), ShouldBeNil) + a.So(ns.(*networkServer).prefixes, ShouldHaveLength, 1) } func TestHandleGetDevices(t *testing.T) { @@ -167,10 +167,14 @@ func TestHandleGetDevices(t *testing.T) { func TestHandlePrepareActivation(t *testing.T) { a := New(t) ns := &networkServer{ - netID: [3]byte{0x00, 0x00, 0x13}, - prefix: [4]byte{0x26, 0x00, 0x00, 0x00}, - prefixLength: 7, - devices: device.NewDeviceStore(), + netID: [3]byte{0x00, 0x00, 0x13}, + prefixes: map[types.DevAddrPrefix][]string{ + types.DevAddrPrefix{DevAddr: [4]byte{0x26, 0x00, 0x00, 0x00}, Length: 7}: []string{ + "otaa", + "local", + }, + }, + devices: device.NewDeviceStore(), } appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) @@ -187,6 +191,22 @@ func TestHandlePrepareActivation(t *testing.T) { }) a.So(err, ShouldNotBeNil) + // Constrained Device + ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ + ActivationConstraints: "private", + }}) + resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + DevEui: &devEUI, + AppEui: &appEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldNotBeNil) + // Device registered ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI}) resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index f1e36654e..90c657930 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -6,6 +6,7 @@ package types import ( "encoding/hex" "errors" + "fmt" "regexp" "strconv" "strings" @@ -99,41 +100,73 @@ func (addr DevAddr) IsEmpty() bool { return addr == empty } +// DevAddrPrefix is a DevAddr with a prefix length +type DevAddrPrefix struct { + DevAddr DevAddr + Length int +} + +// ParseDevAddrPrefix parses a DevAddr in prefix notation (01020304/24) to a prefix +func ParseDevAddrPrefix(prefixString string) (prefix DevAddrPrefix, err error) { + pattern := regexp.MustCompile("([[:xdigit:]]{8})/([[:digit:]]+)") + matches := pattern.FindStringSubmatch(prefixString) + if len(matches) != 3 { + err = errors.New("Invalid Prefix") + return + } + addr, _ := ParseDevAddr(matches[1]) // errors handled in regexp + prefix.Length, _ = strconv.Atoi(matches[2]) // errors handled in regexp + prefix.DevAddr = addr.Mask(prefix.Length) + return +} + +// String implements the fmt.Stringer interface +func (prefix DevAddrPrefix) String() string { + var addr string + if prefix.DevAddr.IsEmpty() { + addr = "00000000" + } else { + addr = prefix.DevAddr.String() + } + return fmt.Sprintf("%s/%d", addr, prefix.Length) +} + +// MarshalText implements the TextMarshaler interface. +func (prefix DevAddrPrefix) MarshalText() ([]byte, error) { + return []byte(prefix.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (prefix *DevAddrPrefix) UnmarshalText(data []byte) error { + parsed, err := ParseDevAddrPrefix(string(data)) + if err != nil { + return err + } + *prefix = DevAddrPrefix(parsed) + return nil +} + // Mask returns a copy of the DevAddr with only the first "bits" bits func (addr DevAddr) Mask(bits int) (masked DevAddr) { - return empty.WithPrefix(addr, bits) + return empty.WithPrefix(DevAddrPrefix{addr, bits}) } // WithPrefix returns the DevAddr, but with the first length bits replaced by the Prefix -func (addr DevAddr) WithPrefix(prefix DevAddr, length int) (prefixed DevAddr) { - k := uint(length) +func (addr DevAddr) WithPrefix(prefix DevAddrPrefix) (prefixed DevAddr) { + k := uint(prefix.Length) for i := 0; i < 4; i++ { if k >= 8 { - prefixed[i] = prefix[i] & 0xff + prefixed[i] = prefix.DevAddr[i] & 0xff k -= 8 continue } - prefixed[i] = (prefix[i] & ^byte(0xff>>k)) | (addr[i] & byte(0xff>>k)) + prefixed[i] = (prefix.DevAddr[i] & ^byte(0xff>>k)) | (addr[i] & byte(0xff>>k)) k = 0 } return } // HasPrefix returns true if the DevAddr has a prefix of given length -func (addr DevAddr) HasPrefix(prefix DevAddr, length int) bool { - return addr.Mask(length) == prefix.Mask(length) -} - -// ParseDevAddrPrefix parses a DevAddr in prefix notation (01020304/24) to a prefix (01020300) and length (24) -func ParseDevAddrPrefix(prefix string) (addr DevAddr, length int, err error) { - pattern := regexp.MustCompile("([[:xdigit:]]{8})/([[:digit:]]+)") - matches := pattern.FindStringSubmatch(prefix) - if len(matches) != 3 { - err = errors.New("Invalid Prefix") - return - } - addr, _ = ParseDevAddr(matches[1]) // errors handled in regexp - length, _ = strconv.Atoi(matches[2]) // errors handled in regexp - addr = addr.Mask(length) - return +func (addr DevAddr) HasPrefix(prefix DevAddrPrefix) bool { + return addr.Mask(prefix.Length) == prefix.DevAddr.Mask(prefix.Length) } diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index d631df23f..4dcdd315a 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -84,36 +84,54 @@ func TestDevAddrWithPrefix(t *testing.T) { a := New(t) addr := DevAddr{0xAA, 0xAA, 0xAA, 0xAA} prefix := DevAddr{0x55, 0x55, 0x55, 0x55} - a.So(addr.WithPrefix(prefix, 4), ShouldEqual, DevAddr{0x5A, 0xAA, 0xAA, 0xAA}) - a.So(addr.WithPrefix(prefix, 8), ShouldEqual, DevAddr{0x55, 0xAA, 0xAA, 0xAA}) - a.So(addr.WithPrefix(prefix, 12), ShouldEqual, DevAddr{0x55, 0x5A, 0xAA, 0xAA}) - a.So(addr.WithPrefix(prefix, 16), ShouldEqual, DevAddr{0x55, 0x55, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 4}), ShouldEqual, DevAddr{0x5A, 0xAA, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 8}), ShouldEqual, DevAddr{0x55, 0xAA, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 12}), ShouldEqual, DevAddr{0x55, 0x5A, 0xAA, 0xAA}) + a.So(addr.WithPrefix(DevAddrPrefix{prefix, 16}), ShouldEqual, DevAddr{0x55, 0x55, 0xAA, 0xAA}) } func TestDevAddrHasPrefix(t *testing.T) { a := New(t) addr := DevAddr{1, 2, 3, 4} - a.So(addr.HasPrefix(DevAddr{0, 0, 0, 0}, 0), ShouldBeTrue) - a.So(addr.HasPrefix(DevAddr{1, 2, 3, 0}, 24), ShouldBeTrue) - a.So(addr.HasPrefix(DevAddr{2, 2, 3, 4}, 31), ShouldBeFalse) - a.So(addr.HasPrefix(DevAddr{1, 1, 3, 4}, 31), ShouldBeFalse) - a.So(addr.HasPrefix(DevAddr{1, 1, 1, 1}, 15), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{0, 0, 0, 0}, 0}), ShouldBeTrue) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{1, 2, 3, 0}, 24}), ShouldBeTrue) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{2, 2, 3, 4}, 31}), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{1, 1, 3, 4}, 31}), ShouldBeFalse) + a.So(addr.HasPrefix(DevAddrPrefix{DevAddr{1, 1, 1, 1}, 15}), ShouldBeFalse) } func TestParseDevAddrPrefix(t *testing.T) { a := New(t) - addr, length, err := ParseDevAddrPrefix("XYZ") + prefix, err := ParseDevAddrPrefix("XYZ") a.So(err, ShouldNotBeNil) - addr, length, err = ParseDevAddrPrefix("00/bla") + prefix, err = ParseDevAddrPrefix("00/bla") a.So(err, ShouldNotBeNil) - addr, length, err = ParseDevAddrPrefix("00/1") + prefix, err = ParseDevAddrPrefix("00/1") a.So(err, ShouldNotBeNil) - addr, length, err = ParseDevAddrPrefix("01020304/1") + prefix, err = ParseDevAddrPrefix("01020304/1") a.So(err, ShouldBeNil) - a.So(addr, ShouldEqual, DevAddr{0, 0, 0, 0}) - a.So(length, ShouldEqual, 1) - addr, length, err = ParseDevAddrPrefix("ff020304/1") + a.So(prefix.DevAddr, ShouldEqual, DevAddr{0, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) + prefix, err = ParseDevAddrPrefix("ff020304/1") a.So(err, ShouldBeNil) - a.So(addr, ShouldEqual, DevAddr{128, 0, 0, 0}) - a.So(length, ShouldEqual, 1) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) +} + +func TestMarshalUnmarshalTextDevAddrPrefix(t *testing.T) { + a := New(t) + var prefix DevAddrPrefix + + txt, err := prefix.MarshalText() + a.So(err, ShouldBeNil) + a.So(string(txt), ShouldEqual, "00000000/0") + + err = prefix.UnmarshalText([]byte("ff556677/1")) + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) + + txt, err = prefix.MarshalText() + a.So(err, ShouldBeNil) + a.So(string(txt), ShouldEqual, "80000000/1") } diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index b06eea3fb..555a1bc34 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -4,6 +4,8 @@ package cmd import ( + "strings" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" @@ -14,7 +16,7 @@ import ( // devicesPersonalizeCmd represents the `device personalize` command var devicesPersonalizeCmd = &cobra.Command{ - Use: "personalize [Device ID] [DevAddr] [NwkSKey] [AppSKey]", + Use: "personalize [Device ID] [NwkSKey] [AppSKey]", Short: "Personalize a device", Long: `ttnctl devices personalize can be used to personalize a device (ABP).`, Run: func(cmd *cobra.Command, args []string) { @@ -33,20 +35,8 @@ var devicesPersonalizeCmd = &cobra.Command{ appID := util.GetAppID(ctx) - var devAddr types.DevAddr - if len(args) > 1 { - devAddr, err = types.ParseDevAddr(args[1]) - if err != nil { - ctx.Fatalf("Invalid DevAddr: %s", err) - } - } else { - ctx.Info("Generating random DevAddr...") - copy(devAddr[:], random.Bytes(4)) - devAddr = devAddr.WithPrefix(types.DevAddr([4]byte{0x26, 0x00, 0x10, 0x00}), 20) - } - var nwkSKey types.NwkSKey - if len(args) > 2 { + if len(args) > 1 { nwkSKey, err = types.ParseNwkSKey(args[2]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) @@ -57,7 +47,7 @@ var devicesPersonalizeCmd = &cobra.Command{ } var appSKey types.AppSKey - if len(args) > 3 { + if len(args) > 2 { appSKey, err = types.ParseAppSKey(args[3]) if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) @@ -75,6 +65,19 @@ var devicesPersonalizeCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not get existing device.") } + ctx.Info("Requesting DevAddr for device...") + + var constraints []string + if lorawan := dev.GetLorawanDevice(); lorawan != nil && lorawan.ActivationConstraints != "" { + constraints = strings.Split(lorawan.ActivationConstraints, ",") + } + constraints = append(constraints, "abp") + + devAddr, err := manager.GetDevAddr(constraints...) + if err != nil { + ctx.WithError(err).Fatal("Could not request device address") + } + dev.GetLorawanDevice().DevAddr = &devAddr dev.GetLorawanDevice().NwkSKey = &nwkSKey dev.GetLorawanDevice().AppSKey = &appSKey diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index b60697b01..9ee183b3b 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -61,6 +61,7 @@ var devicesSetCmd = &cobra.Command{ if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } + ctx.Warn("Using a DevAddr that was not issued by the NetworkServer could break connectivity") dev.GetLorawanDevice().DevAddr = &devAddr } From ffd8de9c59e3ec2e185c1a032ab279b9cb89cc29 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 7 Sep 2016 07:42:54 +0200 Subject: [PATCH 1695/2266] Fix go vet errors --- core/networkserver/networkserver_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index ed20171ae..fe96e5c1d 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -51,9 +51,9 @@ func TestUsePrefix(t *testing.T) { var client redis.Client ns := NewRedisNetworkServer(&client, 19) - a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr{0, 0, 0, 0}, Length: 0}, []string{"otaa"}), ShouldNotBeNil) - a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr{0x14, 0, 0, 0}, Length: 7}, []string{"otaa"}), ShouldNotBeNil) - a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr{0x26, 0, 0, 0}, Length: 7}, []string{"otaa"}), ShouldBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0, 0, 0, 0}), Length: 0}, []string{"otaa"}), ShouldNotBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0x14, 0, 0, 0}), Length: 7}, []string{"otaa"}), ShouldNotBeNil) + a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0x26, 0, 0, 0}), Length: 7}, []string{"otaa"}), ShouldBeNil) a.So(ns.(*networkServer).prefixes, ShouldHaveLength, 1) } From 1124286dcb7275017e908820f2ac80ba2769264f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 7 Sep 2016 16:27:41 +0200 Subject: [PATCH 1696/2266] Structured Error Handling --- api/handler/manager_client.go | 49 +++++--- core/broker/activation.go | 19 ++-- core/broker/broker.go | 20 ++-- core/broker/downlink.go | 7 +- core/broker/manager_server.go | 44 +++++--- core/broker/server.go | 19 ++-- core/broker/uplink.go | 28 ++--- core/broker/uplink_test.go | 6 +- core/component.go | 52 +++++---- core/discovery/announcement/store.go | 13 +-- core/discovery/broker_discovery.go | 3 +- core/discovery/discovery.go | 5 +- core/discovery/handler_discovery.go | 11 +- core/discovery/kv/store.go | 13 +-- core/discovery/server.go | 49 ++++---- core/errors.go | 161 +++++++++++++++++++++++++++ core/handler/activation.go | 17 +-- core/handler/application/store.go | 13 +-- core/handler/convert_fields.go | 36 +++--- core/handler/convert_lorawan.go | 20 ++-- core/handler/device/store.go | 13 +-- core/handler/dry_run.go | 11 +- core/handler/handler.go | 9 +- core/handler/manager_server.go | 84 +++++++------- core/handler/server.go | 9 +- core/networkserver/device/store.go | 18 +-- core/networkserver/manager_server.go | 25 ++--- core/networkserver/networkserver.go | 24 ++-- core/networkserver/server.go | 51 ++++++--- core/otaa/session_keys.go | 4 +- core/router/activation.go | 6 +- core/router/downlink.go | 12 +- core/router/gateway/schedule.go | 4 +- core/router/manager_server.go | 6 +- core/router/router.go | 2 +- core/router/server.go | 10 +- core/router/uplink.go | 6 +- ttnctl/cmd/gateway_status.go | 3 +- ttnctl/cmd/root.go | 9 ++ ttnctl/util/handler.go | 5 +- ttnctl/util/router.go | 11 +- utils/logging/grpc.go | 73 ++++++++++++ 42 files changed, 645 insertions(+), 335 deletions(-) create mode 100644 core/errors.go create mode 100644 utils/logging/grpc.go diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index f3d987599..0d7093b82 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -7,8 +7,9 @@ import ( "sync" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" - + "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -69,49 +70,57 @@ func (h *ManagerClient) getContext() context.Context { // GetApplication retrieves an application from the Handler func (h *ManagerClient) GetApplication(appID string) (*Application, error) { - return h.applicationManagerClient.GetApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) + res, err := h.applicationManagerClient.GetApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) + if err != nil { + return nil, errors.Wrap(core.FromGRPCError(err), "Could not get application from Handler") + } + return res, nil } // SetApplication sets an application on the Handler func (h *ManagerClient) SetApplication(in *Application) error { _, err := h.applicationManagerClient.SetApplication(h.getContext(), in) - return err + return errors.Wrap(core.FromGRPCError(err), "Could not set application on Handler") } // RegisterApplication registers an application on the Handler func (h *ManagerClient) RegisterApplication(appID string) error { _, err := h.applicationManagerClient.RegisterApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) - return err + return errors.Wrap(core.FromGRPCError(err), "Could not register application on Handler") } // DeleteApplication deletes an application and all its devices from the Handler func (h *ManagerClient) DeleteApplication(appID string) error { _, err := h.applicationManagerClient.DeleteApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) - return err + return errors.Wrap(core.FromGRPCError(err), "Could not delete application from Handler") } // GetDevice retrieves a device from the Handler func (h *ManagerClient) GetDevice(appID string, devID string) (*Device, error) { - return h.applicationManagerClient.GetDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) + res, err := h.applicationManagerClient.GetDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) + if err != nil { + return nil, errors.Wrap(core.FromGRPCError(err), "Could not get device from Handler") + } + return res, nil } // SetDevice sets a device on the Handler func (h *ManagerClient) SetDevice(in *Device) error { _, err := h.applicationManagerClient.SetDevice(h.getContext(), in) - return err + return errors.Wrap(core.FromGRPCError(err), "Could not set device on Handler") } // DeleteDevice deletes a device from the Handler func (h *ManagerClient) DeleteDevice(appID string, devID string) error { _, err := h.applicationManagerClient.DeleteDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) - return err + return errors.Wrap(core.FromGRPCError(err), "Could not delete device from Handler") } // GetDevicesForApplication retrieves all devices for an application from the Handler func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Device, err error) { res, err := h.applicationManagerClient.GetDevicesForApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) if err != nil { - return nil, err + return nil, errors.Wrap(core.FromGRPCError(err), "Could not get devices for application from Handler") } for _, dev := range res.Devices { devices = append(devices, dev) @@ -126,7 +135,7 @@ func (h *ManagerClient) GetDevAddr(constraints ...string) (types.DevAddr, error) Usage: constraints, }) if err != nil { - return types.DevAddr{}, err + return types.DevAddr{}, errors.Wrap(core.FromGRPCError(err), "Could not get DevAddr from Handler") } return *resp.DevAddr, nil } @@ -134,22 +143,30 @@ func (h *ManagerClient) GetDevAddr(constraints ...string) (types.DevAddr, error) // DryUplink transforms the uplink payload with the payload functions provided // in the app.. func (h *ManagerClient) DryUplink(payload []byte, app *Application) (*DryUplinkResult, error) { - return h.applicationManagerClient.DryUplink(h.getContext(), &DryUplinkMessage{ + res, err := h.applicationManagerClient.DryUplink(h.getContext(), &DryUplinkMessage{ App: app, Payload: payload, }) + if err != nil { + return nil, errors.Wrap(core.FromGRPCError(err), "Could not dry-run uplink on Handler") + } + return res, nil } // DryDownlinkWithPayload transforms the downlink payload with the payload functions // provided in app. func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application) (*DryDownlinkResult, error) { - return h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ + res, err := h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ App: app, Payload: payload, }) + if err != nil { + return nil, errors.Wrap(core.FromGRPCError(err), "Could not dry-run downlink with payload on Handler") + } + return res, nil } -// DryDownlinkWithPayload transforms the downlink fields with the payload functions +// DryDownlinkWithFields transforms the downlink fields with the payload functions // provided in app. func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app *Application) (*DryDownlinkResult, error) { marshalled, err := json.Marshal(fields) @@ -157,10 +174,14 @@ func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app return nil, err } - return h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ + res, err := h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ App: app, Fields: string(marshalled), }) + if err != nil { + return nil, errors.Wrap(core.FromGRPCError(err), "Could not dry-run downlink with fields on Handler") + } + return res, nil } // Close closes the client diff --git a/core/broker/activation.go b/core/broker/activation.go index a1b81110d..55e5958c0 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -6,16 +6,17 @@ package broker import ( "crypto/md5" "encoding/hex" - "errors" + "fmt" "time" - "google.golang.org/grpc" - pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" + "github.com/pkg/errors" + "google.golang.org/grpc" ) func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { @@ -39,7 +40,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) if len(duplicates) == 0 { - err = errors.New("ttn/broker: No duplicates") + err = core.NewErrInternal("No duplicates") return nil, err } @@ -76,7 +77,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // Send Activate to NS deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) if err != nil { - return nil, err + return nil, errors.Wrap(core.FromGRPCError(err), "NetworkServer refused to prepare activation") } ctx = ctx.WithFields(log.Fields{ @@ -91,11 +92,11 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D return nil, err } if len(announcements) == 0 { - err = errors.New("ttn/broker: No Handler found") + err = core.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) return nil, err } if len(announcements) > 1 { - err = errors.New("ttn/broker: Multiple Handlers found for same AppID") + err = core.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", deduplicatedActivationRequest.AppId)) return nil, err } handler := announcements[0] @@ -112,12 +113,12 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D var handlerResponse *pb_handler.DeviceActivationResponse handlerResponse, err = client.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { - return nil, err + return nil, errors.Wrap(core.FromGRPCError(err), "Handler refused activation") } handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) if err != nil { - return nil, err + return nil, errors.Wrap(core.FromGRPCError(err), "NetworkServer refused activation") } deviceActivationResponse = &pb.DeviceActivationResponse{ diff --git a/core/broker/broker.go b/core/broker/broker.go index 8fa45896f..1b0a09eee 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -4,13 +4,11 @@ package broker import ( - "errors" + "fmt" "strings" "sync" "time" - "google.golang.org/grpc" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" @@ -19,6 +17,8 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/pkg/errors" + "google.golang.org/grpc" ) type Broker interface { @@ -74,7 +74,7 @@ func (b *broker) checkPrefixAnnouncements() error { devAddrClient := pb_lorawan.NewDevAddrManagerClient(b.nsConn) resp, err := devAddrClient.GetPrefixes(b.GetContext(""), &pb_lorawan.PrefixesRequest{}) if err != nil { - return err + return errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return prefixes") } for _, mapping := range resp.Prefixes { prefix, err := types.ParseDevAddrPrefix(mapping.Prefix) @@ -143,7 +143,7 @@ func (b *broker) ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) { b.routersLock.Lock() defer b.routersLock.Unlock() if existing, ok := b.routers[id]; ok { - return existing, errors.New("Router already active") + return existing, core.NewErrInternal(fmt.Sprintf("Router %s already active", id)) } b.routers[id] = make(chan *pb.DownlinkMessage) return b.routers[id], nil @@ -157,7 +157,7 @@ func (b *broker) DeactivateRouter(id string) error { delete(b.routers, id) return nil } - return errors.New("Router not active") + return core.NewErrInternal(fmt.Sprintf("Router %s not active", id)) } func (b *broker) getRouter(id string) (chan<- *pb.DownlinkMessage, error) { @@ -166,14 +166,14 @@ func (b *broker) getRouter(id string) (chan<- *pb.DownlinkMessage, error) { if router, ok := b.routers[id]; ok { return router, nil } - return nil, errors.New("Router not active") + return nil, core.NewErrInternal(fmt.Sprintf("Router %s not active", id)) } func (b *broker) ActivateHandler(id string) (<-chan *pb.DeduplicatedUplinkMessage, error) { b.handlersLock.Lock() defer b.handlersLock.Unlock() if existing, ok := b.handlers[id]; ok { - return existing, errors.New("Handler already active") + return existing, core.NewErrInternal(fmt.Sprintf("Handler %s already active", id)) } b.handlers[id] = make(chan *pb.DeduplicatedUplinkMessage) return b.handlers[id], nil @@ -187,7 +187,7 @@ func (b *broker) DeactivateHandler(id string) error { delete(b.handlers, id) return nil } - return errors.New("Handler not active") + return core.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) } func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, error) { @@ -196,5 +196,5 @@ func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, er if handler, ok := b.handlers[id]; ok { return handler, nil } - return nil, errors.New("Handler not active") + return nil, core.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) } diff --git a/core/broker/downlink.go b/core/broker/downlink.go index 152d902a1..b6f6aae88 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -4,12 +4,13 @@ package broker import ( - "errors" "strings" "time" pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" + "github.com/pkg/errors" ) // ByScore is used to sort a list of DownlinkOptions based on Score @@ -36,14 +37,14 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { downlink, err = b.ns.Downlink(b.Component.GetContext(b.nsToken), downlink) if err != nil { - return err + return errors.Wrap(core.FromGRPCError(err), "NetworkServer did not handle downlink") } var routerID string if id := strings.Split(downlink.DownlinkOption.Identifier, ":"); len(id) == 2 { routerID = id[0] } else { - return errors.New("ttn/broker: Invalid downlink option") + return core.NewErrInvalidArgument("DownlinkOption Identifier", "invalid format") } ctx = ctx.WithField("RouterID", routerID) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 2bb77ac1f..8ae60a67a 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -4,12 +4,12 @@ package broker import ( - "errors" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" + "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -21,48 +21,66 @@ type brokerManager struct { devAddrManager pb_lorawan.DevAddrManagerClient } -var errf = grpc.Errorf - func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { - return b.deviceManager.GetDevice(ctx, in) + res, err := b.deviceManager.GetDevice(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return device")) + } + return res, nil } func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*api.Ack, error) { - return b.deviceManager.SetDevice(ctx, in) + res, err := b.deviceManager.SetDevice(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not set device")) + } + return res, nil } func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*api.Ack, error) { - return b.deviceManager.DeleteDevice(ctx, in) + res, err := b.deviceManager.DeleteDevice(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not delete device")) + } + return res, nil } func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*api.Ack, error) { claims, err := b.broker.Component.ValidateTTNAuthContext(ctx) if err != nil { - return nil, err + return nil, core.BuildGRPCError(core.FromGRPCError(err)) } if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Handler Registration") } if !claims.CanEditApp(in.AppId) { - return nil, errf(codes.Unauthenticated, "No access to this application") + return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } err = b.broker.handlerDiscovery.AddAppID(in.HandlerId, in.AppId) if err != nil { - return nil, err + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Discovery did not add appID")) } return &api.Ack{}, nil } func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesRequest) (*lorawan.PrefixesResponse, error) { - return b.devAddrManager.GetPrefixes(ctx, in) + res, err := b.devAddrManager.GetPrefixes(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return prefixes")) + } + return res, nil } func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrRequest) (*lorawan.DevAddrResponse, error) { - return b.devAddrManager.GetDevAddr(ctx, in) + res, err := b.devAddrManager.GetDevAddr(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return DevAddr")) + } + return res, nil } func (b *brokerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, errors.New("Not Implemented") + return nil, grpcErrf(codes.Unimplemented, "Not Implemented") } func (b *broker) RegisterManager(s *grpc.Server) { diff --git a/core/broker/server.go b/core/broker/server.go index c2c1f22be..79fff7a11 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -8,6 +8,7 @@ import ( pb_api "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -22,11 +23,11 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { routerID, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { - return err + return core.BuildGRPCError(err) } downlinkChannel, err := b.broker.ActivateRouter(routerID) if err != nil { - return err + return core.BuildGRPCError(err) } defer b.broker.DeactivateRouter(routerID) go func() { @@ -65,11 +66,11 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_SubscribeServer) error { handlerID, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { - return err + return core.BuildGRPCError(err) } uplinkChannel, err := b.broker.ActivateHandler(handlerID) if err != nil { - return err + return core.BuildGRPCError(err) } defer b.broker.DeactivateHandler(handlerID) for { @@ -92,7 +93,7 @@ func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_Subscri func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { handlerID, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { - return err + return core.BuildGRPCError(err) } for { downlink, err := stream.Recv() @@ -114,12 +115,16 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { _, err = b.broker.ValidateNetworkContext(ctx) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return b.broker.HandleActivation(req) + res, err = b.broker.HandleActivation(req) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return } func (b *broker) RegisterRPC(s *grpc.Server) { diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 7e7f150b6..0312ab49f 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -6,7 +6,7 @@ package broker import ( "crypto/md5" "encoding/hex" - "errors" + "fmt" "sort" "time" @@ -15,20 +15,16 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/brocaar/lorawan" + "github.com/pkg/errors" ) const maxFCntGap = 16384 -var ( - ErrNotFound = errors.New("ttn/broker: Device not found") - ErrNoMatch = errors.New("ttn/broker: No matching device") - ErrInvalidFCnt = errors.New("ttn/broker: Invalid Frame Counter") -) - func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { ctx := b.Ctx.WithField("GatewayEUI", *uplink.GatewayMetadata.GatewayEui) var err error @@ -52,7 +48,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { base := duplicates[0] if base.ProtocolMetadata.GetLorawan() == nil { - err = errors.New("ttn/broker: Can not handle uplink from non-LoRaWAN device") + err = core.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") return err } @@ -64,7 +60,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - err = errors.New("Uplink message does not contain a MAC payload.") + err = core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") return err } @@ -77,10 +73,10 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { FCnt: macPayload.FHDR.FCnt, }) if err != nil { - return err + return core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return devices")) } if len(getDevicesResp.Results) == 0 { - err = ErrNotFound + err = core.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) return err } ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) @@ -107,7 +103,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } } if device == nil { - err = ErrNoMatch + err = core.NewErrNotFound("device that validates MIC") return err } ctx = ctx.WithFields(log.Fields{ @@ -124,7 +120,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { // Replay attack or FCnt gap too big - err = ErrInvalidFCnt + err = core.NewErrNotFound("device with matching FCnt") return err } @@ -163,7 +159,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { // Pass Uplink through NS deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(b.nsToken), deduplicatedUplink) if err != nil { - return err + return core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not handle uplink")) } var announcements []*pb_discovery.Announcement @@ -172,11 +168,11 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } if len(announcements) == 0 { - err = errors.New("ttn/broker: No Handlers") + err = core.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) return err } if len(announcements) > 1 { - err = errors.New("ttn/broker: Can't forward to multiple Handlers") + err = core.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) return err } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 6b9d39682..d522347f5 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -65,7 +65,7 @@ func TestHandleUplink(t *testing.T) { GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) - a.So(err, ShouldEqual, ErrNotFound) + a.So(err, ShouldHaveSameTypeAs, &core.ErrNotFound{}) devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9} @@ -111,7 +111,7 @@ func TestHandleUplink(t *testing.T) { GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) - a.So(err, ShouldEqual, ErrNoMatch) + a.So(err, ShouldHaveSameTypeAs, &core.ErrNotFound{}) phy.SetMIC(lorawan.AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) bytes, _ = phy.MarshalBinary() @@ -123,7 +123,7 @@ func TestHandleUplink(t *testing.T) { GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) - a.So(err, ShouldEqual, ErrInvalidFCnt) + a.So(err, ShouldHaveSameTypeAs, &core.ErrNotFound{}) // Disable FCnt Check b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) diff --git a/core/component.go b/core/component.go index 25c03895b..0e8523d5d 100644 --- a/core/component.go +++ b/core/component.go @@ -5,27 +5,28 @@ package core import ( "crypto/tls" - "errors" "fmt" "net/http" "runtime" "sync/atomic" "time" - "golang.org/x/net/context" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" - "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/TheThingsNetwork/ttn/utils/security" "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/dgrijalva/jwt-go" "github.com/mwitkow/go-grpc-middleware" + "github.com/pkg/errors" "github.com/spf13/viper" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" ) type ComponentInterface interface { @@ -52,6 +53,8 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } }() + grpclog.SetLogger(logging.NewGRPCLogger(ctx)) + var discovery pb_discovery.DiscoveryClient if serviceName != "discovery" { discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) @@ -144,20 +147,24 @@ func (c *Component) SetStatus(status Status) { // Discover is used to discover another component func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { - return c.Discovery.Get(c.GetContext(""), &pb_discovery.GetRequest{ + res, err := c.Discovery.Get(c.GetContext(""), &pb_discovery.GetRequest{ ServiceName: serviceName, Id: id, }) + if err != nil { + return nil, errors.Wrapf(FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) + } + return res, nil } // Announce the component to TTN discovery func (c *Component) Announce() error { if c.Identity.Id == "" { - return errors.New("ttn: No ID configured") + return NewErrInvalidArgument("Component ID", "can not be empty") } _, err := c.Discovery.Announce(c.GetContext(c.AccessToken), c.Identity) if err != nil { - return fmt.Errorf("ttn: Failed to announce this component to TTN discovery: %s", err.Error()) + return errors.Wrapf(FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) } c.Ctx.Info("ttn: Announced to TTN discovery") @@ -167,7 +174,7 @@ func (c *Component) Announce() error { // UpdateTokenKey updates the OAuth Bearer token key func (c *Component) UpdateTokenKey() error { if c.TokenKeyProvider == nil { - return errors.New("ttn: No public key provider configured for token validation") + return NewErrInternal("No public key provider configured for token validation") } // Set up Auth Server Token Validation @@ -215,7 +222,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str md, ok := metadata.FromContext(ctx) if !ok { - err = errors.New("ttn: Could not get metadata") + err = NewErrInternal("Could not get metadata from context") return } var id, serviceName, token string @@ -223,14 +230,14 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str id = ids[0] } if id == "" { - err = errors.New("ttn: Could not get id") + err = NewErrInvalidArgument("Metadata", "id missing") return } if serviceNames, ok := md["service-name"]; ok && len(serviceNames) == 1 { serviceName = serviceNames[0] } if serviceName == "" { - err = errors.New("ttn: Could not get service name") + err = NewErrInvalidArgument("Metadata", "service-name missing") return } if tokens, ok := md["token"]; ok && len(tokens) == 1 { @@ -248,7 +255,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str } if token == "" { - err = errors.New("ttn: Could not get token") + err = NewErrInvalidArgument("Metadata", "token missing") return } @@ -258,7 +265,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str return } if claims.Subject != id { - err = errors.New("The token was issued for a different component ID") + err = NewErrInvalidArgument("Metadata", "token was issued for a different component id") return } @@ -269,23 +276,23 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) { md, ok := metadata.FromContext(ctx) if !ok { - return nil, errors.New("ttn: Could not get metadata") + return nil, NewErrInternal("Could not get metadata from context") } token, ok := md["token"] if !ok || len(token) < 1 { - return nil, errors.New("ttn: Could not get token") + return nil, NewErrInvalidArgument("Metadata", "token missing") } ttnClaims := &TTNClaims{} parsed, err := jwt.ParseWithClaims(token[0], ttnClaims, func(token *jwt.Token) (interface{}, error) { if c.TokenKeyProvider == nil { - return nil, errors.New("No token provider configured") + return nil, NewErrInternal("No token provider configured") } k, err := c.TokenKeyProvider.Get(ttnClaims.Issuer, false) if err != nil { return nil, err } if k.Algorithm != token.Header["alg"] { - return nil, fmt.Errorf("Expected algorithm %v but got %v", k.Algorithm, token.Header["alg"]) + return nil, NewErrInvalidArgument("Token", fmt.Sprintf("expected algorithm %v but got %v", k.Algorithm, token.Header["alg"])) } key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(k.Key)) if err != nil { @@ -294,10 +301,10 @@ func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, err return key, nil }) if err != nil { - return nil, fmt.Errorf("Unable to parse token: %s", err.Error()) + return nil, NewErrInvalidArgument("Token", fmt.Sprintf("unable to parse token: %s", err.Error())) } if !parsed.Valid { - return nil, errors.New("The token is not valid or is expired") + return nil, NewErrInvalidArgument("Token", "not valid or expired") } return ttnClaims, nil } @@ -326,6 +333,7 @@ func (c *Component) ServerOptions() []grpc.ServerOption { iface, err := handler(ctx, req) logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) if err != nil { + err := FromGRPCError(err) logCtx.WithError(err).Warn("Could not handle Request") } else { logCtx.Info("Handled request") diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go index b31ba3377..119699565 100644 --- a/core/discovery/announcement/store.go +++ b/core/discovery/announcement/store.go @@ -4,18 +4,13 @@ package announcement import ( - "errors" "fmt" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" "gopkg.in/redis.v3" ) -var ( - // ErrNotFound is returned when a announcement was not found - ErrNotFound = errors.New("ttn/discovery: Announcement not found") -) - // Store is used to store announcement configurations type Store interface { // List all announcements @@ -70,7 +65,7 @@ func (s *announcementStore) Get(serviceName, serviceID string) (*pb.Announcement return announcement, nil } } - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) } func (s *announcementStore) Set(new *pb.Announcement) error { @@ -153,11 +148,11 @@ func (s *redisAnnouncementStore) Get(serviceName, serviceID string) (*pb.Announc res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, serviceID)).Result() if err != nil { if err == redis.Nil { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) } return nil, err } else if len(res) == 0 { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) } announcement := &pb.Announcement{} err = announcement.FromStringStringMap(res) diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go index 5e0111020..4ed63d7c6 100644 --- a/core/discovery/broker_discovery.go +++ b/core/discovery/broker_discovery.go @@ -10,6 +10,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/pkg/errors" ) // BrokerCacheTime indicates how long the BrokerDiscovery should cache the services @@ -36,7 +37,7 @@ func NewBrokerDiscovery(component *core.Component) BrokerDiscovery { func (d *brokerDiscovery) refreshCache() error { res, err := d.component.Discovery.GetAll(d.component.GetContext(""), &pb.GetAllRequest{ServiceName: "broker"}) if err != nil { - return err + return errors.Wrap(core.FromGRPCError(err), "Failed to refresh brokers from Discovery") } // TODO: validate response d.cacheLock.Lock() diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index cadc067c8..454c37425 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -7,12 +7,11 @@ package discovery import ( "bytes" - "gopkg.in/redis.v3" - pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/TheThingsNetwork/ttn/core/discovery/kv" + "gopkg.in/redis.v3" ) // Discovery specifies the interface for the TTN Service Discovery component @@ -44,7 +43,7 @@ func (d *discovery) Init(c *core.Component) error { func (d *discovery) Announce(in *pb.Announcement) error { existing, err := d.services.Get(in.ServiceName, in.Id) - if err == announcement.ErrNotFound { + if core.GetErrType(err) == core.NotFound { // Not found; create new existing = &pb.Announcement{} } else if err != nil { diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go index 4e49035c5..c89f9b555 100644 --- a/core/discovery/handler_discovery.go +++ b/core/discovery/handler_discovery.go @@ -4,12 +4,13 @@ package discovery import ( - "errors" + "fmt" "sync" "time" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" + "github.com/pkg/errors" ) // HandlerCacheTime indicates how long the HandlerDiscovery should cache the services @@ -40,7 +41,7 @@ func NewHandlerDiscovery(component *core.Component) HandlerDiscovery { func (d *handlerDiscovery) refreshCache() error { res, err := d.component.Discovery.GetAll(d.component.GetContext(""), &pb.GetAllRequest{ServiceName: "handler"}) if err != nil { - return err + return errors.Wrap(core.FromGRPCError(err), "Failed to refresh handlers from Discovery") } // TODO: validate response d.cacheLock.Lock() @@ -91,7 +92,7 @@ func (d *handlerDiscovery) Get(id string) (*pb.Announcement, error) { d.update() match, ok := d.byID[id] if !ok { - return nil, errors.New("ttn/discovery: Not found") + return nil, core.NewErrNotFound(fmt.Sprintf("handler/%s", id)) } return match, nil } @@ -112,14 +113,14 @@ func (d *handlerDiscovery) AddAppID(handlerID, appID string) error { defer d.cacheLock.Unlock() handler, found := d.byID[handlerID] if !found { - return errors.New("ttn/discovery: Handler not found") + return core.NewErrNotFound(fmt.Sprintf("handler/%s", handlerID)) } existing, found := d.byAppID[appID] if found && len(existing) > 0 { if existing[0].Id == handlerID { return nil } - return errors.New("ttn/discovery: AppID already registered") + return core.NewErrAlreadyExists(fmt.Sprintf("AppID %s already registered", appID)) } d.byAppID[appID] = []*pb.Announcement{handler} return nil diff --git a/core/discovery/kv/store.go b/core/discovery/kv/store.go index 221a96322..42603986c 100644 --- a/core/discovery/kv/store.go +++ b/core/discovery/kv/store.go @@ -4,17 +4,12 @@ package kv import ( - "errors" "fmt" + "github.com/TheThingsNetwork/ttn/core" "gopkg.in/redis.v3" ) -var ( - // ErrNotFound is returned when an Key was not found - ErrNotFound = errors.New("ttn/discovery: Key not found") -) - // Store is a simple String/String Key-Value store type Store interface { // List all items in the store @@ -48,7 +43,7 @@ func (s *kvStore) Get(key string) (string, error) { if value, ok := s.data[key]; ok { return value, nil } - return "", ErrNotFound + return "", core.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) } func (s *kvStore) Set(key, value string) error { @@ -116,11 +111,11 @@ func (s *redisKVStore) Get(key string) (string, error) { res, err := s.client.Get(fmt.Sprintf("%s:%s", s.prefix, key)).Result() if err != nil { if err == redis.Nil { - return "", ErrNotFound + return "", core.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) } return "", err } else if res == "" { - return "", ErrNotFound + return "", core.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) } return res, nil } diff --git a/core/discovery/server.go b/core/discovery/server.go index 089c419cf..889049788 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -4,19 +4,24 @@ package discovery import ( - "errors" - "fmt" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" "golang.org/x/net/context" "google.golang.org/grpc" + "google.golang.org/grpc/codes" ) type discoveryServer struct { discovery *discovery } +var grpcErrf = grpc.Errorf // To make go vet stop complaining + +func errPermissionDeniedf(format string, args ...string) error { + return grpcErrf(codes.PermissionDenied, "Discovery:"+format, args) +} + func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { claims, err := d.discovery.ValidateTTNAuthContext(ctx) if err != nil { @@ -25,43 +30,43 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me switch in.Metadata.Key { case pb.Metadata_PREFIX: if in.ServiceName != "broker" { - return errors.New("ttn/discovery: Announcement service type should be \"broker\"") + return errPermissionDeniedf("Announcement service type should be \"broker\"") } // Only allow prefix announcements if token is issued by the official ttn account server (or if in dev mode) if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return fmt.Errorf("ttn/discovery: Token issuer %s should be ttn-account", claims.Issuer) + return errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) } if claims.Type != in.ServiceName { - return fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) + return errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) } if claims.Subject != in.Id { - return fmt.Errorf("ttn/discovery: Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) } // TODO: Check if this PREFIX can be announced case pb.Metadata_APP_EUI: if in.ServiceName != "handler" { - return errors.New("ttn/discovery: Announcement service type should be \"handler\"") + return errPermissionDeniedf("Announcement service type should be \"handler\"") } // Only allow eui announcements if token is issued by the official ttn account server (or if in dev mode) if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return fmt.Errorf("ttn/discovery: Token issuer %s should be ttn-account", claims.Issuer) + return errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) } if claims.Type != in.ServiceName { - return fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) + return errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) } if claims.Subject != in.Id { - return fmt.Errorf("ttn/discovery: Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) } // TODO: Check if this APP_EUI can be announced - return errors.New("ttn/discovery: Can not announce AppEUIs at this time") + return errPermissionDeniedf("Can not announce AppEUIs at this time") case pb.Metadata_APP_ID: if in.ServiceName != "handler" { - return errors.New("ttn/discovery: Announcement service type should be \"handler\"") + return errPermissionDeniedf("Announcement service type should be \"handler\"") } // Allow APP_ID announcements from all trusted auth servers // When announcing APP_ID, token is user token that contains apps if !claims.CanEditApp(string(in.Metadata.Value)) { - return errors.New("ttn/discovery: No access to this application") + return errPermissionDeniedf("No access to this application") } } return nil @@ -74,19 +79,19 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc } // Only allow announcements if token is issued by the official ttn account server (or if in dev mode) if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return nil, fmt.Errorf("ttn/discovery: Token issuer %s should be ttn-account", claims.Issuer) + return nil, errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) } if claims.Subject != announcement.Id { - return nil, fmt.Errorf("ttn/discovery: Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.Id) + return nil, errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.Id) } if claims.Type != announcement.ServiceName { - return nil, fmt.Errorf("ttn/discovery: Token type %s does not correspond with announcement service type %s", claims.Type, announcement.ServiceName) + return nil, errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, announcement.ServiceName) } announcementCopy := *announcement announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement err = d.discovery.Announce(&announcementCopy) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil } @@ -98,7 +103,7 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques } err = d.discovery.AddMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil } @@ -110,7 +115,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq } err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil } @@ -118,7 +123,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { services, err := d.discovery.GetAll(req.ServiceName) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &pb.AnnouncementsResponse{ Services: services, @@ -128,7 +133,7 @@ func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*p func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { service, err := d.discovery.Get(req.ServiceName, req.Id) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return service, nil } diff --git a/core/errors.go b/core/errors.go new file mode 100644 index 000000000..550b84240 --- /dev/null +++ b/core/errors.go @@ -0,0 +1,161 @@ +package core + +import ( + "fmt" + "strings" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + + "github.com/pkg/errors" +) + +type ErrType string + +// These constants represent error types +const ( + AlreadyExists ErrType = "already exists" + Internal ErrType = "internal" + InvalidArgument ErrType = "invalid argument" + NotFound ErrType = "not found" + OutOfRange ErrType = "out of range" + PermissionDenied ErrType = "permission denied" + Unknown ErrType = "unknown" +) + +// GetErrType returns the type of err +func GetErrType(err error) ErrType { + switch errors.Cause(err).(type) { + case *ErrAlreadyExists: + return AlreadyExists + case *ErrInternal: + return Internal + case *ErrInvalidArgument: + return InvalidArgument + case *ErrNotFound: + return NotFound + case *ErrPermissionDenied: + return PermissionDenied + } + return Unknown +} + +var grpcErrf = grpc.Errorf + +// BuildGRPCError returns the error with a GRPC code +func BuildGRPCError(err error) error { + code := codes.Unknown + switch errors.Cause(err).(type) { + case *ErrAlreadyExists: + code = codes.AlreadyExists + case *ErrInternal: + code = codes.Internal + case *ErrInvalidArgument: + code = codes.InvalidArgument + case *ErrNotFound: + code = codes.NotFound + case *ErrPermissionDenied: + code = codes.PermissionDenied + } + return grpcErrf(code, fmt.Sprintf("%+v", err)) +} + +// FromGRPCError creates a regular error with the same type as the gRPC error +func FromGRPCError(err error) error { + if err == nil { + return nil + } + code := grpc.Code(err) + desc := grpc.ErrorDesc(err) + switch code { + case codes.AlreadyExists: + return NewErrAlreadyExists(strings.TrimSuffix(desc, " already exists")) + case codes.Internal: + return NewErrInternal(strings.TrimPrefix(desc, "Internal error: ")) + case codes.InvalidArgument: + return NewErrInvalidArgument("Argument", desc) + case codes.NotFound: + return NewErrNotFound(strings.TrimSuffix(desc, " not found")) + case codes.PermissionDenied: + return NewErrPermissionDenied(strings.TrimPrefix(desc, "permission denied: ")) + case codes.Unknown: // This also includes all non-gRPC errors + return errors.New(err.Error()) + } + return NewErrInternal(fmt.Sprintf("[%s] %s", code, desc)) +} + +// NewErrAlreadyExists returns a new ErrAlreadyExists for the given entitiy +func NewErrAlreadyExists(entity string) error { + return &ErrAlreadyExists{entity: entity} +} + +// ErrAlreadyExists indicates that an entity already exists +type ErrAlreadyExists struct { + entity string +} + +// Error implements the error interface +func (err ErrAlreadyExists) Error() string { + return fmt.Sprintf("%s already exists", err.entity) +} + +// NewErrInternal returns a new ErrInternal with the given message +func NewErrInternal(message string) error { + return &ErrInternal{message: message} +} + +// ErrInternal indicates that an internal error occured +type ErrInternal struct { + message string +} + +// Error implements the error interface +func (err ErrInternal) Error() string { + return fmt.Sprintf("Internal error: %s", err.message) +} + +// NewErrInvalidArgument returns a new ErrInvalidArgument for the given entitiy +func NewErrInvalidArgument(argument string, reason string) error { + return &ErrInvalidArgument{argument: argument, reason: reason} +} + +// ErrInvalidArgument indicates that an argument was invalid +type ErrInvalidArgument struct { + argument string + reason string +} + +// Error implements the error interface +func (err ErrInvalidArgument) Error() string { + return fmt.Sprintf("%s not valid: %s", err.argument, err.reason) +} + +// NewErrNotFound returns a new ErrNotFound for the given entitiy +func NewErrNotFound(entity string) error { + return &ErrNotFound{entity: entity} +} + +// ErrNotFound indicates that an entity was not found +type ErrNotFound struct { + entity string +} + +// Error implements the error interface +func (err ErrNotFound) Error() string { + return fmt.Sprintf("%s not found", err.entity) +} + +// NewErrPermissionDenied returns a new ErrPermissionDenied with the given reason +func NewErrPermissionDenied(reason string) error { + return &ErrPermissionDenied{reason: reason} +} + +// ErrPermissionDenied indicates that permissions were not sufficient +type ErrPermissionDenied struct { + reason string +} + +// Error implements the error interface +func (err ErrPermissionDenied) Error() string { + return fmt.Sprintf("permission denied: %s", err.reason) +} diff --git a/core/handler/activation.go b/core/handler/activation.go index 608dcc97f..c702379df 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -4,11 +4,12 @@ package handler import ( - "errors" + "fmt" "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/core/types" @@ -58,7 +59,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv }() if activation.ResponseTemplate == nil { - err = errors.New("ttn/handler: No Downlink Available") + err = core.NewErrInternal("No downlink available") return nil, err } @@ -70,13 +71,13 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } if dev.AppKey.IsEmpty() { - err = errors.New("ttn/handler: Can not activate device without AppKey") + err = core.NewErrNotFound(fmt.Sprintf("AppKey for device %s", activation.DevId)) return nil, err } // Check for LoRaWAN if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - err = errors.New("ttn/handler: Can not activate non-LoRaWAN device") + err = core.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") return nil, err } @@ -87,13 +88,13 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } reqMAC, ok := reqPHY.MACPayload.(*lorawan.JoinRequestPayload) if !ok { - err = errors.New("MACPayload must be a *JoinRequestPayload") + err = core.NewErrInvalidArgument("Activation", "does not contain a JoinRequestPayload") return nil, err } // Validate MIC if ok, err = reqPHY.ValidateMIC(lorawan.AES128Key(dev.AppKey)); err != nil || !ok { - err = errors.New("ttn/handler: Invalid MIC") + err = core.NewErrNotFound("MIC does not match device") return nil, err } @@ -106,7 +107,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } } if alreadyUsed { - err = errors.New("ttn/handler: DevNonce already used") + err = core.NewErrInvalidArgument("Activation DevNonce", "already used") return nil, err } @@ -119,7 +120,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } resMAC, ok := resPHY.MACPayload.(*lorawan.DataPayload) if !ok { - err = errors.New("MACPayload must be a *DataPayload") + err = core.NewErrInvalidArgument("Activation ResponseTemplate", "MACPayload must be a *DataPayload") return nil, err } joinAccept := &lorawan.JoinAcceptPayload{} diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 993fee849..129956e1b 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -4,18 +4,13 @@ package application import ( - "errors" "fmt" "time" + "github.com/TheThingsNetwork/ttn/core" "gopkg.in/redis.v3" ) -var ( - // ErrNotFound is returned when a application was not found - ErrNotFound = errors.New("ttn/handler: Application not found") -) - // Store is used to store application configurations type Store interface { // List all applications @@ -53,7 +48,7 @@ func (s *applicationStore) Get(appID string) (*Application, error) { if app, ok := s.applications[appID]; ok { return app, nil } - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s", appID)) } func (s *applicationStore) Set(new *Application, fields ...string) error { @@ -105,11 +100,11 @@ func (s *redisApplicationStore) Get(appID string) (*Application, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appID)).Result() if err != nil { if err == redis.Nil { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s", appID)) } return nil, err } else if len(res) == 0 { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s", appID)) } application := &Application{} err = application.FromStringStringMap(res) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 55808dd98..966280566 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -4,12 +4,12 @@ package handler import ( - "errors" "fmt" "reflect" "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" "github.com/robertkrimen/otto" @@ -35,7 +35,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat } if !valid { - return errors.New("ttn/handler: The processed payload is not valid") + return core.NewErrInvalidArgument("Payload", "payload validator function returned false") } appUp.Fields = fields @@ -62,7 +62,7 @@ var timeOut = 100 * time.Millisecond // Decode decodes the payload using the Decoder function into a map func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) { if f.Decoder == "" { - return nil, errors.New("Decoder function not set") + return nil, core.NewErrInternal("Decoder function not set") } vm := otto.New() @@ -73,13 +73,13 @@ func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) } if !value.IsObject() { - return nil, errors.New("Decoder does not return an object") + return nil, core.NewErrInvalidArgument("Decoder", "does not return an object") } v, _ := value.Export() m, ok := v.(map[string]interface{}) if !ok { - return nil, errors.New("Decoder does not return an object") + return nil, core.NewErrInvalidArgument("Decoder", "does not return an object") } return m, nil } @@ -100,13 +100,13 @@ func (f *UplinkFunctions) Convert(data map[string]interface{}) (map[string]inter } if !value.IsObject() { - return nil, errors.New("Converter does not return an object") + return nil, core.NewErrInvalidArgument("Converter", "does not return an object") } v, _ := value.Export() m, ok := v.(map[string]interface{}) if !ok { - return nil, errors.New("Decoder does not return an object") + return nil, core.NewErrInvalidArgument("Converter", "does not return an object") } return m, nil @@ -127,7 +127,7 @@ func (f *UplinkFunctions) Validate(data map[string]interface{}) (bool, error) { } if !value.IsBoolean() { - return false, errors.New("Validator does not return a boolean") + return false, core.NewErrInvalidArgument("Validator", "does not return a boolean") } return value.ToBoolean() @@ -149,7 +149,7 @@ func (f *UplinkFunctions) Process(payload []byte) (map[string]interface{}, bool, return converted, valid, err } -var errTimeOutExceeded = errors.New("Code has been running to long") +var errTimeOutExceeded = core.NewErrInternal("Code has been running to long") func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value otto.Value, err error) { start := time.Now() @@ -158,7 +158,7 @@ func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value ott if caught := recover(); caught != nil { if caught == errTimeOutExceeded { value = otto.Value{} - err = fmt.Errorf("Interrupted javascript execution after %v", duration) + err = core.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) return } // if this is not the our timeout interrupt, raise the panic again @@ -189,7 +189,7 @@ type DownlinkFunctions struct { // If no encoder function is set, this function returns an array. func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, error) { if f.Encoder == "" { - return nil, errors.New("Encoder function not set") + return nil, core.NewErrInternal("Encoder function not set") } vm := otto.New() @@ -200,7 +200,7 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro } if !value.IsObject() { - return nil, errors.New("Encoder does not return an object") + return nil, core.NewErrInvalidArgument("Encoder", "does not return an object") } v, err := value.Export() @@ -209,7 +209,7 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro } if reflect.TypeOf(v).Kind() != reflect.Slice { - return nil, errors.New("Encoder does not return an array") + return nil, core.NewErrInvalidArgument("Encoder", "does not return an Array") } s := reflect.ValueOf(v) @@ -239,20 +239,20 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro case float32: n = int64(t) if float32(n) != t { - return nil, errors.New("Encoder should return an Array of integer numbers") + return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } case float64: n = int64(t) if float64(n) != t { - return nil, errors.New("Encoder should return an Array of integer numbers") + return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } default: fmt.Printf("VAL %v TYPE %T\n", el, el) - return nil, errors.New("Encoder should return an Array of integer numbers") + return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } if n < 0 || n > 255 { - return nil, errors.New("Numbers in array should be between 0 and 255") + return nil, core.NewErrInvalidArgument("Encoder Output", "Numbers in Array should be between 0 and 255") } res[i] = byte(n) @@ -278,7 +278,7 @@ func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMes } if appDown.Payload != nil { - return errors.New("Both Fields and Payload provided") + return core.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") } app, err := h.applications.Get(appDown.AppID) diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index b7ffca2d0..028231205 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -4,14 +4,11 @@ package handler import ( - "errors" - "fmt" - pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" - "github.com/brocaar/lorawan" ) @@ -24,7 +21,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli // Check for LoRaWAN if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil { - return errors.New("ttn/handler: Could not handle uplink for non-LoRaWAN device") + return core.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") } // LoRaWAN: Unmarshal Uplink @@ -35,7 +32,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return errors.New("Uplink message does not contain a MAC payload.") + return core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt appUp.FCnt = macPayload.FHDR.FCnt @@ -46,8 +43,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli return err } if !ok { - fmt.Printf("Invalid MIC for %X / NwkSKey %s", ttnUp.Payload, dev.NwkSKey) - return errors.New("ttn/handler: Invalid MIC") + return core.NewErrNotFound("device that validates MIC") } ctx = ctx.WithField("FCnt", appUp.FCnt) @@ -57,17 +53,17 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli appUp.FPort = *macPayload.FPort ctx = ctx.WithField("FCnt", appUp.FPort) if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil { - return errors.New("ttn/handler: Could not decrypt payload") + return core.NewErrInternal("Could not decrypt payload") } if len(macPayload.FRMPayload) == 1 { payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) if !ok { - return errors.New("FRMPayload must be of type *lorawan.DataPayload") + return core.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload") } appUp.Payload = payload.Bytes } } else { - return errors.New("ttn/handler: Could not get frame payload") + return core.NewErrInvalidArgument("Uplink MACPayload", "could not get frame payload") } return nil @@ -88,7 +84,7 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMess } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return errors.New("Downlink message does not contain a MAC payload.") + return core.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } if ttnDown.DownlinkOption != nil { macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt diff --git a/core/handler/device/store.go b/core/handler/device/store.go index cd8a5a281..b55cba7be 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -4,18 +4,13 @@ package device import ( - "errors" "fmt" "time" + "github.com/TheThingsNetwork/ttn/core" "gopkg.in/redis.v3" ) -var ( - // ErrNotFound is returned when a device was not found - ErrNotFound = errors.New("ttn/handler: Device not found") -) - // Store is used to store device configurations type Store interface { // List all devices @@ -69,7 +64,7 @@ func (s *deviceStore) Get(appID, devID string) (*Device, error) { return dev, nil } } - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } func (s *deviceStore) Set(new *Device, fields ...string) error { @@ -154,11 +149,11 @@ func (s *redisDeviceStore) Get(appID, devID string) (*Device, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appID, devID)).Result() if err != nil { if err == redis.Nil { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } return nil, err } else if len(res) == 0 { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } device := &Device{} err = device.FromStringStringMap(res) diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go index 3e36da008..ac8c85853 100644 --- a/core/handler/dry_run.go +++ b/core/handler/dry_run.go @@ -5,10 +5,9 @@ package handler import ( "encoding/json" - "errors" - "fmt" pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core" "golang.org/x/net/context" ) @@ -57,7 +56,7 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess if in.Payload != nil { if in.Fields != "" { - return nil, errors.New("Both fields and payload provided on downlink message") + return nil, core.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") } return &pb.DryDownlinkResult{ Payload: in.Payload, @@ -65,11 +64,11 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess } if in.Fields == "" { - return nil, errors.New("Neither fields or payload provided on downlink message") + return nil, core.NewErrInvalidArgument("Downlink", "Neither Fields nor Payload provided") } if app == nil || app.Encoder == "" { - return nil, errors.New("No encoder specified") + return nil, core.NewErrInvalidArgument("Encoder", "Not specified") } functions := &DownlinkFunctions{ @@ -79,7 +78,7 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess var parsed map[string]interface{} err := json.Unmarshal([]byte(in.Fields), &parsed) if err != nil { - return nil, fmt.Errorf("Could not parse fields: %s", err) + return nil, core.NewErrInvalidArgument("Fields", err.Error()) } payload, _, err := functions.Process(parsed) diff --git a/core/handler/handler.go b/core/handler/handler.go index 39ce930eb..afe4f2fc8 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -7,8 +7,6 @@ import ( "fmt" "time" - "google.golang.org/grpc" - "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -16,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/mqtt" + "google.golang.org/grpc" "gopkg.in/redis.v3" ) @@ -113,13 +112,14 @@ func (h *handler) associateBroker() error { for { upStream, err := h.ttnBroker.Subscribe(h.GetContext(""), &pb_broker.SubscribeRequest{}) if err != nil { + h.Ctx.WithError(core.FromGRPCError(err)).Error("Could not start Broker subscribe stream") <-time.After(api.Backoff) continue } for { in, err := upStream.Recv() if err != nil { - h.Ctx.Errorf("ttn/handler: Error in Broker subscribe: %s", err) + h.Ctx.WithError(core.FromGRPCError(err)).Error("Error in Broker subscribe stream") break } go h.HandleUplink(in) @@ -131,13 +131,14 @@ func (h *handler) associateBroker() error { for { downStream, err := h.ttnBroker.Publish(h.GetContext("")) if err != nil { + h.Ctx.WithError(core.FromGRPCError(err)).Error("Could not start Broker publish stream") <-time.After(api.Backoff) continue } for downlink := range h.downlink { err := downStream.Send(downlink) if err != nil { - h.Ctx.Errorf("ttn/handler: Error in Broker publish: %s", err) + h.Ctx.WithError(core.FromGRPCError(err)).Error("Error in Broker publish stream") break } } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index cea55c199..8f5c2883b 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -4,15 +4,17 @@ package handler import ( - "errors" + "fmt" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -24,25 +26,23 @@ type handlerManager struct { devAddrManager pb_lorawan.DevAddrManagerClient } -var errf = grpc.Errorf - func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) (*device.Device, error) { if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") + return nil, core.NewErrInvalidArgument("Device Identifier", "validation failed") } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { - return nil, errf(codes.Unauthenticated, "No access to this device") + return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err } if !claims.CanEditApp(dev.AppID) { - return nil, errf(codes.Unauthenticated, "No access to this device") + return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return dev, nil } @@ -50,7 +50,7 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { dev, err := h.getDevice(ctx, in) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } nsDev, err := h.deviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ @@ -58,7 +58,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) DevEui: &dev.DevEUI, }) if err != nil { - return nil, err + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not return device")) } return &pb.Device{ @@ -84,8 +84,8 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack, error) { dev, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) - if err != nil && err != device.ErrNotFound { - return nil, err + if err != nil && core.GetErrType(err) != core.NotFound { + return nil, core.BuildGRPCError(err) } if !in.Validate() { @@ -105,7 +105,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack DevEui: &dev.DevEUI, }) if err != nil { - return nil, err + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not delete device")) } } } else { // When this is a create @@ -153,12 +153,12 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack _, err = h.deviceManager.SetDevice(ctx, nsUpdated) if err != nil { - return nil, err + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not set device")) } err = h.handler.devices.Set(updated) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil @@ -167,15 +167,15 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*api.Ack, error) { dev, err := h.getDevice(ctx, in) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { - return nil, err + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not delete device")) } err = h.handler.devices.Delete(in.AppId, in.DevId) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil } @@ -186,14 +186,14 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !claims.CanEditApp(in.AppId) { - return nil, errf(codes.Unauthenticated, "No access to this application") + return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } res := &pb.DeviceList{Devices: []*pb.Device{}} for _, dev := range devices { @@ -217,21 +217,21 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*application.Application, error) { if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") + return nil, core.NewErrInvalidArgument("Application Identifier", "validation failed") } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { - return nil, errf(codes.Unauthenticated, "No access to this application") + return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } app, err := h.handler.applications.Get(in.AppId) if err != nil { return nil, err } if !claims.CanEditApp(app.AppID) { - return nil, errf(codes.Unauthenticated, "No access to this application") + return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return app, nil } @@ -239,7 +239,7 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { app, err := h.getApplication(ctx, in) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &pb.Application{ @@ -253,18 +253,18 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*api.Ack, error) { app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) - if err != nil && err != application.ErrNotFound { - return nil, err + if err != nil && core.GetErrType(err) != core.NotFound { + return nil, core.BuildGRPCError(err) } if app != nil { - return nil, errf(codes.InvalidArgument, "Application already registered") + return nil, grpcErrf(codes.AlreadyExists, "Application already registered") } err = h.handler.applications.Set(&application.Application{ AppID: in.AppId, }) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } _, err = h.handler.Discovery.AddMetadata(ctx, &pb_discovery.MetadataRequest{ @@ -294,7 +294,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*api.Ack, error) { _, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !in.Validate() { @@ -309,7 +309,7 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) Encoder: in.Encoder, }) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil @@ -318,29 +318,29 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*api.Ack, error) { _, err := h.getApplication(ctx, in) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } // Get and delete all devices for this application devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } for _, dev := range devices { _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { - return nil, err + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not delete device")) } err = h.handler.devices.Delete(dev.AppID, dev.DevID) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } } // Delete the Application err = h.handler.applications.Delete(in.AppId) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } _, err = h.handler.Discovery.DeleteMetadata(ctx, &pb_discovery.MetadataRequest{ @@ -352,22 +352,30 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati }, }) if err != nil { - h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not unregister Application from Discovery") + h.handler.Ctx.WithField("AppID", in.AppId).WithError(core.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") } return &api.Ack{}, nil } func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { - return h.devAddrManager.GetPrefixes(ctx, in) + res, err := h.devAddrManager.GetPrefixes(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not return prefixes")) + } + return res, nil } func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { - return h.devAddrManager.GetDevAddr(ctx, in) + res, err := h.devAddrManager.GetDevAddr(ctx, in) + if err != nil { + return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not return DevAddr")) + } + return res, nil } func (h *handlerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, errors.New("Not Implemented") + return nil, grpcErrf(codes.Unimplemented, "Not Implemented") } func (h *handler) RegisterManager(s *grpc.Server) { diff --git a/core/handler/server.go b/core/handler/server.go index e4b4ebc5a..d5f2ecad2 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -6,6 +6,7 @@ package handler import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -20,12 +21,16 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !activation.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return h.handler.HandleActivation(activation) + res, err := h.handler.HandleActivation(activation) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil } // RegisterRPC registers this handler as a HandlerServer (github.com/TheThingsNetwork/ttn/api/handler) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index d71d2a108..865ac82b3 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -4,18 +4,12 @@ package device import ( - "errors" "fmt" "time" - "gopkg.in/redis.v3" - + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" -) - -var ( - // ErrNotFound is returned when a device was not found - ErrNotFound = errors.New("ttn/networkserver: Device not found") + "gopkg.in/redis.v3" ) // Store is used to store device configurations @@ -65,7 +59,7 @@ func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, er return dev, nil } } - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } func (s *deviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, error) { @@ -201,11 +195,11 @@ func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Devic res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() if err != nil { if err == redis.Nil { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } return nil, err } else if len(res) == 0 { - return nil, ErrNotFound + return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } device := &Device{} err = device.FromStringStringMap(res) @@ -296,7 +290,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de return err } if !exists { - return ErrNotFound + return core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } // Check for old DevAddr diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 284d9cacd..99d551427 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -4,12 +4,13 @@ package networkserver import ( - "errors" + "fmt" "time" "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "golang.org/x/net/context" "google.golang.org/grpc" @@ -20,11 +21,9 @@ type networkServerManager struct { networkServer *networkServer } -var errf = grpc.Errorf - func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") + return nil, core.NewErrInvalidArgument("Device Identifier", "validation failed") } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) if err != nil { @@ -35,7 +34,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev return nil, err } if !claims.CanEditApp(dev.AppID) { - return nil, errf(codes.Unauthenticated, "No access to this device") + return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) } return dev, nil } @@ -43,7 +42,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { dev, err := n.getDevice(ctx, in) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } lastSeen := time.Unix(0, 0) @@ -68,8 +67,8 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*api.Ack, error) { _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) - if err != nil && err != device.ErrNotFound { - return nil, err + if err != nil && core.GetErrType(err) != core.NotFound { + return nil, core.BuildGRPCError(err) } if !in.Validate() { @@ -97,7 +96,7 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev err = n.networkServer.devices.Set(updated) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil @@ -106,11 +105,11 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*api.Ack, error) { _, err := n.getDevice(ctx, in) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } err = n.networkServer.devices.Delete(*in.AppEui, *in.DevEui) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &api.Ack{}, nil } @@ -131,7 +130,7 @@ func (n *networkServerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.P func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { devAddr, err := n.networkServer.getDevAddr(in.Usage...) if err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } return &pb_lorawan.DevAddrResponse{ DevAddr: &devAddr, @@ -139,7 +138,7 @@ func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.De } func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, errors.New("Not Implemented") + return nil, grpcErrf(codes.Unimplemented, "Not Implemented") } // RegisterManager registers this networkserver as a NetworkServerManagerServer (github.com/TheThingsNetwork/ttn/api/networkserver) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 995d7ce5f..abf29f938 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -4,13 +4,10 @@ package networkserver import ( - "errors" "fmt" "strings" "time" - "gopkg.in/redis.v3" - pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" @@ -22,6 +19,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" + "gopkg.in/redis.v3" ) // NetworkServer implements LoRaWAN-specific functionality for TTN @@ -58,10 +56,10 @@ type networkServer struct { func (n *networkServer) UsePrefix(prefix types.DevAddrPrefix, usage []string) error { if prefix.Length < 7 { - return errors.New("ttn/networkserver: Invalid prefix length") + return core.NewErrInvalidArgument("Prefix", "invalid length") } if prefix.DevAddr[0]>>1 != n.netID[2] { - return errors.New("ttn/networkserver: Invalid prefix") + return core.NewErrInvalidArgument("Prefix", "invalid netID") } n.prefixes[prefix] = usage return nil @@ -146,7 +144,7 @@ func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) // Get a random prefix that matches the constraints prefixes := n.GetPrefixesFor(constraints...) if len(prefixes) == 0 { - return types.DevAddr{}, fmt.Errorf("ttn/networkserver: No DevAddr prefixes available for constraints %v", constraints) + return types.DevAddr{}, core.NewErrNotFound(fmt.Sprintf("DevAddr prefix with constraints %v", constraints)) } // Select a prefix @@ -160,7 +158,7 @@ func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { if activation.AppEui == nil || activation.DevEui == nil { - return nil, errors.New("ttn/networkserver: Activation missing AppEUI or DevEUI") + return nil, core.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") } dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) if err != nil { @@ -182,12 +180,12 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat } // Build lorawan metadata if not present if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - return nil, errors.New("ttn/networkserver: Can only handle LoRaWAN activations") + return nil, core.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") } // Build response template if not present if pld := activation.GetResponseTemplate(); pld == nil { - return nil, errors.New("ttn/networkserver: Activation does not contain a response template") + return nil, core.NewErrInvalidArgument("Activation", "missing response template") } lorawanMeta := activation.ActivationMetadata.GetLorawan() @@ -234,11 +232,11 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) { meta := activation.GetActivationMetadata() if meta == nil { - return nil, errors.New("ttn/networkserver: invalid ActivationMetadata") + return nil, core.NewErrInvalidArgument("Activation", "missing ActivationMetadata") } lorawan := meta.GetLorawan() if lorawan == nil { - return nil, errors.New("ttn/networkserver: invalid LoRaWAN ActivationMetadata") + return nil, core.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") } err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) if err != nil { @@ -262,7 +260,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload") + return nil, core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) @@ -335,7 +333,7 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return nil, errors.New("ttn/networkserver: LoRaWAN message does not contain a MACPayload") + return nil, core.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } // Set DevAddr diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 75f89896a..50dbb7711 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -4,11 +4,10 @@ package networkserver import ( - "errors" - "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/security" "github.com/dgrijalva/jwt-go" "golang.org/x/net/context" @@ -26,20 +25,20 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func (s *networkServerRPC) ValidateContext(ctx context.Context) error { md, ok := metadata.FromContext(ctx) if !ok { - return errors.New("ttn: Could not get metadata") + return core.NewErrInternal("Could not get metadata from context") } var id, token string if ids, ok := md["id"]; ok && len(ids) == 1 { id = ids[0] } if id == "" { - return errors.New("ttn: Could not get id") + return core.NewErrInvalidArgument("Metadata", "id missing") } if tokens, ok := md["token"]; ok && len(tokens) == 1 { token = tokens[0] } if token == "" { - return errors.New("ttn: Could not get token") + return core.NewErrInvalidArgument("Metadata", "token missing") } var claims *jwt.StandardClaims claims, err := security.ValidateJWT(token, []byte(s.networkServer.(*networkServer).Identity.PublicKey)) @@ -47,59 +46,79 @@ func (s *networkServerRPC) ValidateContext(ctx context.Context) error { return err } if claims.Subject != id { - return errors.New("The token was issued for a different component ID") + return core.NewErrInvalidArgument("Metadata", "token was issued for a different component id") } return nil } func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesRequest) (*pb.DevicesResponse, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Devices Request") } - return s.networkServer.HandleGetDevices(req) + res, err := s.networkServer.HandleGetDevices(req) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil } func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !activation.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return s.networkServer.HandlePrepareActivation(activation) + res, err := s.networkServer.HandlePrepareActivation(activation) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil } func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !activation.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return s.networkServer.HandleActivate(activation) + res, err := s.networkServer.HandleActivate(activation) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil } func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !message.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Uplink") } - return s.networkServer.HandleUplink(message) + res, err := s.networkServer.HandleUplink(message) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil } func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, err + return nil, core.BuildGRPCError(err) } if !message.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Downlink") } - return s.networkServer.HandleDownlink(message) + res, err := s.networkServer.HandleDownlink(message) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil } // RegisterRPC registers this networkserver as a NetworkServerServer (github.com/TheThingsNetwork/ttn/api/networkserver) diff --git a/core/otaa/session_keys.go b/core/otaa/session_keys.go index 7c4fc95a1..cf2105bca 100644 --- a/core/otaa/session_keys.go +++ b/core/otaa/session_keys.go @@ -5,8 +5,8 @@ package otaa import ( "crypto/aes" - "errors" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -22,7 +22,7 @@ func CalculateSessionKeys(appKey types.AppKey, appNonce [3]byte, netID [3]byte, block, err := aes.NewCipher(appKey[:]) if err != nil || block.BlockSize() != 16 { - err = errors.New("Unable to create cipher to generate keys") + err = core.NewErrInternal("Unable to create cipher to generate keys") return } diff --git a/core/router/activation.go b/core/router/activation.go index 68eca669f..ad18cd617 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -5,6 +5,7 @@ package router import ( "errors" + "fmt" "sync" "time" @@ -12,6 +13,7 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) @@ -46,7 +48,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De gateway.Utilization.AddRx(uplink) if !gateway.Schedule.IsActive() { - return nil, errors.New("Gateway not available for response") + return nil, core.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) } downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) @@ -146,7 +148,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De // Activation not accepted by any broker if !gotFirst { ctx.Debug("Activation not accepted at this gateway") - return nil, errors.New("ttn/router: Activation not accepted at this Gateway") + return nil, errors.New("Activation not accepted at this Gateway") } // Activation accepted by (at least one) broker diff --git a/core/router/downlink.go b/core/router/downlink.go index 5772b2f90..a9e54b664 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -4,7 +4,6 @@ package router import ( - "errors" "fmt" "math" "strings" @@ -14,6 +13,7 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/toa" @@ -41,7 +41,7 @@ func (r *router) SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.Down }() return toGateway, nil } - return nil, errors.New("ttn/router: Gateway downlink not available") + return nil, core.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) } func (r *router) UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error { @@ -105,15 +105,15 @@ func getBand(region string) (band *lora.Band, err error) { case "US_902_928": b, err = lora.GetConfig(lora.US_902_928) case "CN_779_787": - err = errors.New("ttn/router: China 779-787 MHz band not supported") + err = core.NewErrInternal("China 779-787 MHz band not supported") case "EU_433": - err = errors.New("ttn/router: Europe 433 MHz band not supported") + err = core.NewErrInternal("Europe 433 MHz band not supported") case "AU_915_928": b, err = lora.GetConfig(lora.AU_915_928) case "CN_470_510": - err = errors.New("ttn/router: China 470-510 MHz band not supported") + err = core.NewErrInternal("China 470-510 MHz band not supported") default: - err = errors.New("ttn/router: Unknown band") + err = core.NewErrInvalidArgument("Frequency Band", "unknown") } if err != nil { return diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index f5cfb2198..5db5e0f3b 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -4,13 +4,13 @@ package gateway import ( - "errors" "fmt" "sync" "sync/atomic" "time" router_pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/TheThingsNetwork/ttn/utils/toa" "github.com/apex/log" @@ -198,7 +198,7 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro return nil } - return errors.New("ID not found") + return core.NewErrNotFound(id) } func (s *schedule) Stop() { diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 3995dcb68..f067c42f2 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -4,8 +4,6 @@ package router import ( - "errors" - pb "github.com/TheThingsNetwork/ttn/api/router" "golang.org/x/net/context" "google.golang.org/grpc" @@ -24,7 +22,7 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR } _, err := r.router.ValidateTTNAuthContext(ctx) if err != nil { - return nil, errf(codes.Unauthenticated, "No access") + return nil, errf(codes.PermissionDenied, "No access") } gtw := r.router.getGateway(*in.GatewayEui) status, err := gtw.Status.Get() @@ -38,7 +36,7 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR } func (r *routerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, errors.New("Not Implemented") + return nil, grpcErrf(codes.Unimplemented, "Not Implemented") } // RegisterManager registers this router as a RouterManagerServer (github.com/TheThingsNetwork/ttn/api/router) diff --git a/core/router/router.go b/core/router/router.go index 411aab69f..9c193cda4 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -165,7 +165,7 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok for { select { case err := <-errChan: - r.Ctx.Errorf("ttn/router: Error in Broker associate: %s", err) + r.Ctx.WithError(core.FromGRPCError(err)).Error("Error in Broker associate") break associationLoop case uplink := <-brk.uplink: err := association.Send(uplink) diff --git a/core/router/server.go b/core/router/server.go index 8979b8f04..41841fdeb 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -4,11 +4,11 @@ package router import ( - "errors" "io" api "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "golang.org/x/net/context" "google.golang.org/grpc" @@ -25,12 +25,12 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, err error) { md, ok := metadata.FromContext(ctx) if !ok { - err = errors.New("ttn: Could not get metadata") + err = core.NewErrInternal("Could not get metadata from context") return } euiString, ok := md["gateway_eui"] if !ok || len(euiString) < 1 { - err = errors.New("ttn/router: Gateway did not provide \"gateway_eui\" in context") + err = core.NewErrInvalidArgument("Metadata", "gateway_eui missing") return } gatewayEUI, err = types.ParseGatewayEUI(euiString[0]) @@ -39,12 +39,12 @@ func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, e } token, ok := md["token"] if !ok || len(token) < 1 { - err = errors.New("ttn/router: Gateway did not provide \"token\" in context") + err = core.NewErrInvalidArgument("Metadata", "token missing") return } if token[0] != "token" { // TODO: Validate Token - err = errors.New("ttn/router: Gateway not authorized") + err = core.NewErrPermissionDenied("Gateway token not authorized") return } diff --git a/core/router/uplink.go b/core/router/uplink.go index d2a73c6f2..c70aa443e 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -4,12 +4,12 @@ package router import ( - "errors" "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -37,7 +37,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess if phyPayload.MHDR.MType == lorawan.JoinRequest { joinRequestPayload, ok := phyPayload.MACPayload.(*lorawan.JoinRequestPayload) if !ok { - return errors.New("Join Request message does not contain a join payload.") + return core.NewErrInvalidArgument("Join Request", "does not contain a JoinRequest payload") } devEUI := types.DevEUI(joinRequestPayload.DevEUI) appEUI := types.AppEUI(joinRequestPayload.AppEUI) @@ -74,7 +74,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return errors.New("Uplink message does not contain a MAC payload.") + return core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } devAddr := types.DevAddr(macPayload.FHDR.DevAddr) diff --git a/ttnctl/cmd/gateway_status.go b/ttnctl/cmd/gateway_status.go index 018e21cf9..d9a8107ad 100644 --- a/ttnctl/cmd/gateway_status.go +++ b/ttnctl/cmd/gateway_status.go @@ -8,6 +8,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" @@ -38,7 +39,7 @@ var gatewayStatusCmd = &cobra.Command{ GatewayEui: &eui, }) if err != nil { - ctx.WithError(err).Fatal("Could not get status of gateway.") + ctx.WithError(core.FromGRPCError(err)).Fatal("Could not get status of gateway.") } ctx.Infof("Received status") diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 5b83b4d98..0e3151990 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -7,8 +7,14 @@ import ( "fmt" "os" "strings" + "time" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" + + "github.com/TheThingsNetwork/ttn/api" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" + "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -32,6 +38,9 @@ var RootCmd = &cobra.Command{ Level: logLevel, Handler: cliHandler.New(os.Stdout), } + api.DialOptions = append(api.DialOptions, grpc.WithBlock()) + api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) + grpclog.SetLogger(logging.NewGRPCLogger(ctx)) }, } diff --git a/ttnctl/util/handler.go b/ttnctl/util/handler.go index cb31a56ab..abccb12d6 100644 --- a/ttnctl/util/handler.go +++ b/ttnctl/util/handler.go @@ -6,6 +6,7 @@ package util import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" "github.com/spf13/viper" "google.golang.org/grpc" @@ -13,6 +14,7 @@ import ( // GetHandlerManager gets a new HandlerManager for ttnctl func GetHandlerManager(ctx log.Interface) (*grpc.ClientConn, *handler.ManagerClient) { + ctx.WithField("Handler", viper.GetString("ttn-handler")).Info("Discovering Handler...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() handlerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ @@ -20,12 +22,13 @@ func GetHandlerManager(ctx log.Interface) (*grpc.ClientConn, *handler.ManagerCli Id: viper.GetString("ttn-handler"), }) if err != nil { - ctx.WithError(err).Fatal("Could not find Handler") + ctx.WithError(core.FromGRPCError(err)).Fatal("Could not find Handler") } token, err := GetTokenSource(ctx).Token() if err != nil { ctx.WithError(err).Fatal("Could not get token") } + ctx.WithField("Handler", handlerAnnouncement.NetAddress).Info("Connecting with Handler...") hdlConn, err := handlerAnnouncement.Dial() if err != nil { ctx.WithError(err).Fatal("Could not connect to Handler") diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index 9427c39f0..1e947e421 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -6,6 +6,7 @@ package util import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" "github.com/spf13/viper" "google.golang.org/grpc" @@ -13,6 +14,7 @@ import ( // GetRouter starts a connection with the router func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { + ctx.Info("Discovering Router...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ @@ -20,17 +22,20 @@ func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { Id: viper.GetString("ttn-router"), }) if err != nil { - ctx.WithError(err).Fatal("Could not get Router from Discovery") + ctx.WithError(core.FromGRPCError(err)).Fatal("Could not get Router from Discovery") } + ctx.Info("Connecting with Router...") rtrConn, err := routerAnnouncement.Dial() if err != nil { ctx.WithError(err).Fatal("Could not connect to Router") } + ctx.Info("Connected to Router") return rtrConn, router.NewRouterClient(rtrConn) } // GetRouterManager starts a management connection with the router func GetRouterManager(ctx log.Interface) (*grpc.ClientConn, router.RouterManagerClient) { + ctx.Info("Discovering Router...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ @@ -38,11 +43,13 @@ func GetRouterManager(ctx log.Interface) (*grpc.ClientConn, router.RouterManager Id: viper.GetString("ttn-router"), }) if err != nil { - ctx.WithError(err).Fatal("Could not get Router from Discovery") + ctx.WithError(core.FromGRPCError(err)).Fatal("Could not get Router from Discovery") } + ctx.Info("Connecting with Router...") rtrConn, err := routerAnnouncement.Dial() if err != nil { ctx.WithError(err).Fatal("Could not connect to Router") } + ctx.Info("Connected to Router") return rtrConn, router.NewRouterManagerClient(rtrConn) } diff --git a/utils/logging/grpc.go b/utils/logging/grpc.go new file mode 100644 index 000000000..9f6c0db58 --- /dev/null +++ b/utils/logging/grpc.go @@ -0,0 +1,73 @@ +package logging + +import ( + "fmt" + "strings" + + "google.golang.org/grpc/grpclog" + + "github.com/apex/log" +) + +// NewGRPCLogger wraps the ctx and returns a grpclog.Logger +func NewGRPCLogger(ctx log.Interface) grpclog.Logger { + return &gRPCLogger{ctx} +} + +// gRPCLogger implements the grpc/grpclog.Logger interface +type gRPCLogger struct { + ctx log.Interface +} + +var filteredLogs = []string{ + "failed to complete security handshake", +} + +func (l *gRPCLogger) shouldLog(log string) bool { + for _, filter := range filteredLogs { + if strings.Contains(log, filter) { + return false + } + } + return true +} + +// Fatal implements the grpc/grpclog.Logger interface +func (l *gRPCLogger) Fatal(args ...interface{}) { + l.fatalln(fmt.Sprint(args...)) +} + +// Fatalf implements the grpc/grpclog.Logger interface +func (l *gRPCLogger) Fatalf(format string, args ...interface{}) { + l.fatalln(fmt.Sprintf(format, args...)) +} + +// Fatalln implements the grpc/grpclog.Logger interface +func (l *gRPCLogger) Fatalln(args ...interface{}) { + l.fatalln(fmt.Sprint(args...)) +} + +func (l *gRPCLogger) fatalln(in string) { + l.ctx.Error(in) +} + +// Print implements the grpc/grpclog.Logger interface +func (l *gRPCLogger) Print(args ...interface{}) { + l.println(fmt.Sprint(args...)) +} + +// Printf implements the grpc/grpclog.Logger interface +func (l *gRPCLogger) Printf(format string, args ...interface{}) { + l.println(fmt.Sprintf(format, args...)) +} + +// Println implements the grpc/grpclog.Logger interface +func (l *gRPCLogger) Println(args ...interface{}) { + l.println(fmt.Sprint(args...)) +} + +func (l *gRPCLogger) println(in string) { + if l.shouldLog(in) { + l.ctx.Debug(in) + } +} From 86314a6131b3cf91255e81f9b68629dcbf78aab2 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 7 Sep 2016 16:58:24 +0200 Subject: [PATCH 1697/2266] Add GenerateEUI to applications (#232) --- core/account/applications.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/account/applications.go b/core/account/applications.go index 2ad941980..8835ec407 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -100,6 +100,20 @@ func (a *Account) AddEUI(appID string, eui types.AppEUI) error { return a.put(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) } +type genEUIRes struct { + EUI types.AppEUI `json:"eui"` +} + +// GenerateEUI creates a new EUI for the application +func (a *Account) GenerateEUI(appID string) (*types.AppEUI, error) { + var res genEUIRes + err := a.post(fmt.Sprintf("/applications/%s/euis", appID), nil, &res) + if err != nil { + return nil, err + } + return &res.EUI, nil +} + // RemoveEUI removes the specified EUI from the application func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { return a.del(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) From 37f54d1d8384c284085f379963298d9a2da70285 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 7 Sep 2016 17:10:09 +0200 Subject: [PATCH 1698/2266] Use Empty type instead of api.Ack Resolves #230 for ttn part --- api/api.pb.go | 121 +++----------------- api/api.proto | 3 - api/broker/broker.pb.go | 161 ++++++++++++++------------- api/broker/broker.proto | 6 +- api/discovery/discovery.pb.go | 99 ++++++++-------- api/discovery/discovery.proto | 8 +- api/handler/handler.pb.go | 144 ++++++++++++------------ api/handler/handler.proto | 12 +- api/noc/noc.pb.go | 77 ++++++------- api/noc/noc.proto | 10 +- api/protocol/lorawan/device.pb.go | 91 +++++++-------- api/protocol/lorawan/device.proto | 6 +- api/router/router.pb.go | 126 ++++++++++----------- api/router/router.proto | 6 +- core/broker/manager_server.go | 10 +- core/broker/server.go | 4 +- core/discovery/server.go | 14 +-- core/discovery/server_test.go | 14 +-- core/handler/manager_server.go | 22 ++-- core/networkserver/manager_server.go | 10 +- core/router/server.go | 6 +- 21 files changed, 436 insertions(+), 514 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index 431e780f9..db814c7ea 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -9,7 +9,6 @@ github.com/TheThingsNetwork/ttn/api/api.proto It has these top-level messages: - Ack Percentiles Rates ServerMetadata @@ -33,15 +32,6 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package -// message Ack is used to acknowledge a request, without giving a response. -type Ack struct { -} - -func (m *Ack) Reset() { *m = Ack{} } -func (m *Ack) String() string { return proto.CompactTextString(m) } -func (*Ack) ProtoMessage() {} -func (*Ack) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{0} } - type Percentiles struct { Percentile1 float32 `protobuf:"fixed32,1,opt,name=percentile1,proto3" json:"percentile1,omitempty"` Percentile5 float32 `protobuf:"fixed32,2,opt,name=percentile5,proto3" json:"percentile5,omitempty"` @@ -57,7 +47,7 @@ type Percentiles struct { func (m *Percentiles) Reset() { *m = Percentiles{} } func (m *Percentiles) String() string { return proto.CompactTextString(m) } func (*Percentiles) ProtoMessage() {} -func (*Percentiles) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} } +func (*Percentiles) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{0} } type Rates struct { Rate1 float32 `protobuf:"fixed32,1,opt,name=rate1,proto3" json:"rate1,omitempty"` @@ -68,7 +58,7 @@ type Rates struct { func (m *Rates) Reset() { *m = Rates{} } func (m *Rates) String() string { return proto.CompactTextString(m) } func (*Rates) ProtoMessage() {} -func (*Rates) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} } +func (*Rates) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} } type ServerMetadata struct { } @@ -76,32 +66,13 @@ type ServerMetadata struct { func (m *ServerMetadata) Reset() { *m = ServerMetadata{} } func (m *ServerMetadata) String() string { return proto.CompactTextString(m) } func (*ServerMetadata) ProtoMessage() {} -func (*ServerMetadata) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3} } +func (*ServerMetadata) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} } func init() { - proto.RegisterType((*Ack)(nil), "api.Ack") proto.RegisterType((*Percentiles)(nil), "api.Percentiles") proto.RegisterType((*Rates)(nil), "api.Rates") proto.RegisterType((*ServerMetadata)(nil), "api.ServerMetadata") } -func (m *Ack) Marshal() (data []byte, err error) { - size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) - if err != nil { - return nil, err - } - return data[:n], nil -} - -func (m *Ack) MarshalTo(data []byte) (int, error) { - var i int - _ = i - var l int - _ = l - return i, nil -} - func (m *Percentiles) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -243,12 +214,6 @@ func encodeVarintApi(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } -func (m *Ack) Size() (n int) { - var l int - _ = l - return n -} - func (m *Percentiles) Size() (n int) { var l int _ = l @@ -316,56 +281,6 @@ func sovApi(x uint64) (n int) { func sozApi(x uint64) (n int) { return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Ack) Unmarshal(data []byte) error { - l := len(data) - iNdEx := 0 - for iNdEx < l { - preIndex := iNdEx - var wire uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowApi - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := data[iNdEx] - iNdEx++ - wire |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break - } - } - fieldNum := int32(wire >> 3) - wireType := int(wire & 0x7) - if wireType == 4 { - return fmt.Errorf("proto: Ack: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Ack: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - default: - iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthApi - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} func (m *Percentiles) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -792,22 +707,22 @@ var ( func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 268 bytes of a gzipped FileDescriptorProto + // 261 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0x04, - 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xc4, 0x82, 0x4c, 0x25, 0x56, 0x2e, 0x66, - 0xc7, 0xe4, 0x6c, 0xa5, 0xb3, 0x4c, 0x5c, 0xdc, 0x01, 0xa9, 0x45, 0xc9, 0xa9, 0x79, 0x25, 0x99, - 0x39, 0xa9, 0xc5, 0x42, 0x0a, 0x5c, 0xdc, 0x05, 0x70, 0xae, 0xa1, 0x04, 0xa3, 0x02, 0xa3, 0x06, - 0x53, 0x10, 0xb2, 0x10, 0xaa, 0x0a, 0x53, 0x09, 0x26, 0x74, 0x15, 0xa6, 0x42, 0x4a, 0x5c, 0x3c, - 0x48, 0x1a, 0x0c, 0x24, 0x98, 0xc1, 0x4a, 0x50, 0xc4, 0x50, 0xd5, 0x18, 0x99, 0x4a, 0xb0, 0xa0, - 0xab, 0x31, 0x42, 0x33, 0xc7, 0xd4, 0x40, 0x82, 0x15, 0x5d, 0x8d, 0x29, 0x9a, 0x39, 0xe6, 0xa6, - 0x12, 0x6c, 0xe8, 0x6a, 0xcc, 0xd1, 0xcc, 0xb1, 0x34, 0x90, 0x60, 0x47, 0x57, 0x63, 0x89, 0x66, - 0x8e, 0xa5, 0xa9, 0x04, 0x07, 0x86, 0x1a, 0x74, 0x73, 0x2c, 0x25, 0x38, 0x31, 0xd4, 0x58, 0x2a, - 0x79, 0x73, 0xb1, 0x06, 0x25, 0x96, 0xa4, 0x16, 0x0b, 0x89, 0x70, 0xb1, 0x16, 0x25, 0x96, 0xc0, - 0x83, 0x10, 0xc2, 0x81, 0x89, 0xc2, 0x82, 0x0d, 0xc2, 0x11, 0x12, 0xe3, 0x62, 0x03, 0x4b, 0x9b, - 0x42, 0x83, 0x0a, 0xca, 0x53, 0x12, 0xe0, 0xe2, 0x0b, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0xf2, 0x4d, - 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x74, 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, - 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, 0x3c, 0x96, 0x63, 0x48, 0x62, 0x03, 0xc7, 0xa9, 0x31, 0x20, - 0x00, 0x00, 0xff, 0xff, 0xd1, 0xa3, 0x30, 0x03, 0x04, 0x02, 0x00, 0x00, + 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xc4, 0x82, 0x4c, 0xa5, 0xb3, 0x4c, 0x5c, + 0xdc, 0x01, 0xa9, 0x45, 0xc9, 0xa9, 0x79, 0x25, 0x99, 0x39, 0xa9, 0xc5, 0x42, 0x0a, 0x5c, 0xdc, + 0x05, 0x70, 0xae, 0xa1, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x53, 0x10, 0xb2, 0x10, 0xaa, 0x0a, 0x53, + 0x09, 0x26, 0x74, 0x15, 0xa6, 0x42, 0x4a, 0x5c, 0x3c, 0x48, 0x1a, 0x0c, 0x24, 0x98, 0xc1, 0x4a, + 0x50, 0xc4, 0x50, 0xd5, 0x18, 0x99, 0x4a, 0xb0, 0xa0, 0xab, 0x31, 0x42, 0x33, 0xc7, 0xd4, 0x40, + 0x82, 0x15, 0x5d, 0x8d, 0x29, 0x9a, 0x39, 0xe6, 0xa6, 0x12, 0x6c, 0xe8, 0x6a, 0xcc, 0xd1, 0xcc, + 0xb1, 0x34, 0x90, 0x60, 0x47, 0x57, 0x63, 0x89, 0x66, 0x8e, 0xa5, 0xa9, 0x04, 0x07, 0x86, 0x1a, + 0x74, 0x73, 0x2c, 0x25, 0x38, 0x31, 0xd4, 0x58, 0x2a, 0x79, 0x73, 0xb1, 0x06, 0x25, 0x96, 0xa4, + 0x16, 0x0b, 0x89, 0x70, 0xb1, 0x16, 0x25, 0x96, 0xc0, 0x83, 0x10, 0xc2, 0x81, 0x89, 0xc2, 0x82, + 0x0d, 0xc2, 0x11, 0x12, 0xe3, 0x62, 0x03, 0x4b, 0x9b, 0x42, 0x83, 0x0a, 0xca, 0x53, 0x12, 0xe0, + 0xe2, 0x0b, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0xf2, 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x74, + 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, 0x3c, + 0x96, 0x63, 0x48, 0x62, 0x03, 0x47, 0xa6, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfb, 0x1b, + 0x5d, 0xfd, 0x01, 0x00, 0x00, } diff --git a/api/api.proto b/api/api.proto index 45d6bfdc7..59c22df14 100644 --- a/api/api.proto +++ b/api/api.proto @@ -5,9 +5,6 @@ syntax = "proto3"; package api; -// message Ack is used to acknowledge a request, without giving a response. -message Ack {} - message Percentiles { float percentile1 = 1; float percentile5 = 2; diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index c58b1a59e..c89d643c7 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -26,6 +26,7 @@ package broker import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" @@ -505,7 +506,7 @@ func (c *brokerClient) Publish(ctx context.Context, opts ...grpc.CallOption) (Br type Broker_PublishClient interface { Send(*DownlinkMessage) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -517,11 +518,11 @@ func (x *brokerPublishClient) Send(m *DownlinkMessage) error { return x.ClientStream.SendMsg(m) } -func (x *brokerPublishClient) CloseAndRecv() (*api.Ack, error) { +func (x *brokerPublishClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -606,7 +607,7 @@ func _Broker_Publish_Handler(srv interface{}, stream grpc.ServerStream) error { } type Broker_PublishServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*DownlinkMessage, error) grpc.ServerStream } @@ -615,7 +616,7 @@ type brokerPublishServer struct { grpc.ServerStream } -func (x *brokerPublishServer) SendAndClose(m *api.Ack) error { +func (x *brokerPublishServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -679,7 +680,7 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ type BrokerManagerClient interface { // Handler announces new application to Broker - RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*api.Ack, error) + RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*google_protobuf.Empty, error) // Network operator requests Broker status GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) } @@ -692,8 +693,8 @@ func NewBrokerManagerClient(cc *grpc.ClientConn) BrokerManagerClient { return &brokerManagerClient{cc} } -func (c *brokerManagerClient) RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *brokerManagerClient) RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/broker.BrokerManager/RegisterApplicationHandler", in, out, c.cc, opts...) if err != nil { return nil, err @@ -714,7 +715,7 @@ func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, type BrokerManagerServer interface { // Handler announces new application to Broker - RegisterApplicationHandler(context.Context, *ApplicationHandlerRegistration) (*api.Ack, error) + RegisterApplicationHandler(context.Context, *ApplicationHandlerRegistration) (*google_protobuf.Empty, error) // Network operator requests Broker status GetStatus(context.Context, *StatusRequest) (*Status, error) } @@ -4156,74 +4157,76 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1099 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x56, 0x4d, 0x6f, 0x1b, 0xc5, - 0x1b, 0xff, 0x6f, 0xdc, 0x38, 0xc9, 0xe3, 0xf8, 0x25, 0xd3, 0xbc, 0x6c, 0xad, 0x3f, 0xb6, 0x31, - 0x52, 0x65, 0x5e, 0x6a, 0x17, 0xa3, 0x82, 0x22, 0x04, 0x91, 0x43, 0xaa, 0x12, 0x24, 0x87, 0x6a, - 0x9b, 0x70, 0xe0, 0x62, 0x8d, 0x77, 0x9e, 0x38, 0xa3, 0x38, 0xbb, 0xdb, 0x9d, 0x59, 0xa7, 0xf9, - 0x0e, 0x48, 0x1c, 0xe0, 0xc0, 0xd7, 0x41, 0x5c, 0xb8, 0xc1, 0xb9, 0x87, 0x0a, 0x85, 0x0b, 0x17, - 0xbe, 0x03, 0xda, 0xd9, 0xd9, 0xf5, 0xda, 0xae, 0xdb, 0x14, 0xca, 0xa1, 0x15, 0x27, 0xef, 0x3c, - 0xcf, 0xef, 0xf9, 0xcd, 0xf8, 0xf7, 0xbc, 0xcc, 0xc0, 0x47, 0x03, 0x2e, 0x4f, 0x82, 0x7e, 0xd3, - 0x76, 0xcf, 0x5a, 0x87, 0x27, 0x78, 0x78, 0xc2, 0x9d, 0x81, 0x38, 0x40, 0x79, 0xee, 0xfa, 0xa7, - 0x2d, 0x29, 0x9d, 0x16, 0xf5, 0x78, 0xab, 0xef, 0xbb, 0xa7, 0xe8, 0xeb, 0x9f, 0xa6, 0xe7, 0xbb, - 0xd2, 0x25, 0xd9, 0x68, 0x55, 0xbe, 0x95, 0x22, 0x18, 0xb8, 0x03, 0xb7, 0xa5, 0xdc, 0xfd, 0xe0, - 0x58, 0xad, 0xd4, 0x42, 0x7d, 0x45, 0x61, 0x13, 0xf0, 0xb9, 0xfb, 0x51, 0x8f, 0x6b, 0xf8, 0xc7, - 0x57, 0x81, 0x2b, 0xa8, 0xed, 0x0e, 0x93, 0x0f, 0x1d, 0xbc, 0x7d, 0x95, 0xe0, 0x01, 0x95, 0x78, - 0x4e, 0x2f, 0xe2, 0xdf, 0x28, 0xb4, 0xfe, 0xd3, 0x02, 0x14, 0xf6, 0xdc, 0x73, 0x67, 0xc8, 0x9d, - 0xd3, 0x2f, 0x3d, 0xc9, 0x5d, 0x87, 0x54, 0x00, 0x38, 0x43, 0x47, 0xf2, 0x63, 0x8e, 0xbe, 0x69, - 0xd4, 0x8c, 0xc6, 0x8a, 0x95, 0xb2, 0x90, 0xaf, 0x21, 0xa7, 0x39, 0x7a, 0x18, 0x70, 0x73, 0xa1, - 0x66, 0x34, 0x56, 0x77, 0xb7, 0x1f, 0x3f, 0xa9, 0xde, 0x79, 0xde, 0x31, 0x6c, 0xd7, 0xc7, 0x96, - 0xbc, 0xf0, 0x50, 0x34, 0xef, 0x45, 0x0c, 0x77, 0x8f, 0xf6, 0x2d, 0xd0, 0x6c, 0x77, 0x03, 0x4e, - 0xd6, 0x61, 0x51, 0x84, 0x28, 0x33, 0x53, 0x33, 0x1a, 0x79, 0x2b, 0x5a, 0x90, 0x32, 0x2c, 0x33, - 0xa4, 0x6c, 0xc8, 0x1d, 0x34, 0xaf, 0xd5, 0x8c, 0x46, 0xc6, 0x4a, 0xd6, 0x64, 0x17, 0x8a, 0xb1, - 0x1a, 0x3d, 0xdb, 0x75, 0x8e, 0xf9, 0xc0, 0x5c, 0xac, 0x19, 0x8d, 0x5c, 0xfb, 0x46, 0x33, 0x51, - 0xe9, 0xf0, 0xd1, 0x67, 0xca, 0x13, 0xf8, 0x34, 0xfc, 0x87, 0x56, 0x21, 0xf6, 0x44, 0x66, 0xb2, - 0x03, 0x85, 0xf8, 0x1f, 0x69, 0x8a, 0xac, 0xa2, 0x30, 0x9b, 0xb1, 0x58, 0xd3, 0x0c, 0x79, 0xed, - 0x88, 0xac, 0xf5, 0x6f, 0x33, 0x90, 0x3f, 0xf2, 0x42, 0x0d, 0xbb, 0x28, 0x04, 0x1d, 0x20, 0x31, - 0x61, 0xc9, 0xa3, 0x17, 0x43, 0x97, 0x32, 0xa5, 0xe0, 0xaa, 0x15, 0x2f, 0xc9, 0x01, 0x2c, 0x31, - 0x1c, 0x29, 0xe9, 0x72, 0x4a, 0xba, 0x3b, 0x8f, 0x9f, 0x54, 0xdf, 0x7f, 0x01, 0xe9, 0xf6, 0x70, - 0x14, 0xca, 0x96, 0x65, 0x38, 0x0a, 0x25, 0x3b, 0x80, 0x25, 0xea, 0x79, 0x8a, 0x6f, 0xf5, 0x6f, - 0xf1, 0x75, 0x3c, 0x4f, 0xf1, 0x51, 0xcf, 0x0b, 0xf9, 0x3a, 0xb0, 0x96, 0x08, 0x7a, 0x86, 0x92, - 0x32, 0x2a, 0xa9, 0xb9, 0xa1, 0xf4, 0x58, 0x1f, 0x4b, 0x6a, 0x3d, 0xea, 0x6a, 0x9f, 0x55, 0x8a, - 0x8d, 0xb1, 0x85, 0x7c, 0x0a, 0xa5, 0x58, 0xcf, 0x84, 0x61, 0x53, 0x31, 0x5c, 0x4f, 0x14, 0x4d, - 0x11, 0x14, 0xb5, 0x2d, 0x89, 0xef, 0x40, 0x89, 0xe9, 0x9a, 0xec, 0xb9, 0xaa, 0x28, 0x85, 0x59, - 0xad, 0x65, 0x1a, 0xb9, 0xf6, 0x66, 0x53, 0xf7, 0xe6, 0x64, 0xcd, 0x5a, 0x45, 0x36, 0xb1, 0x16, - 0xf5, 0x6f, 0x16, 0xa0, 0x18, 0x63, 0x5e, 0xfd, 0x9c, 0xec, 0x40, 0x71, 0x4a, 0x10, 0x9d, 0x91, - 0x79, 0x7a, 0x14, 0x26, 0xf5, 0xa8, 0x07, 0x60, 0xee, 0xe1, 0x88, 0xdb, 0xd8, 0xb1, 0x25, 0x1f, - 0x45, 0x35, 0x8c, 0xc2, 0x73, 0x1d, 0xf1, 0x2c, 0x59, 0x9e, 0xb2, 0x6d, 0xee, 0x85, 0xb6, 0xfd, - 0x33, 0x03, 0x37, 0xf6, 0x90, 0x05, 0xde, 0x90, 0xdb, 0x54, 0x22, 0x7b, 0x5d, 0x7a, 0x64, 0x03, - 0xc2, 0xaf, 0x1e, 0x67, 0x66, 0x5e, 0x8d, 0xc7, 0x45, 0xea, 0x79, 0xfb, 0x2c, 0x34, 0x87, 0xc7, - 0xe6, 0xcc, 0x2c, 0x44, 0x66, 0x86, 0xa3, 0x7d, 0xf6, 0xef, 0x75, 0x54, 0xe6, 0xca, 0x1d, 0x55, - 0x85, 0x9c, 0x40, 0x7f, 0x84, 0x7e, 0x4f, 0xf2, 0x33, 0x34, 0xb7, 0xd4, 0x10, 0x85, 0xc8, 0x74, - 0xc8, 0xcf, 0x90, 0xec, 0xc1, 0x9a, 0xaf, 0x0b, 0xa2, 0x27, 0xf1, 0xcc, 0x1b, 0x52, 0x89, 0x66, - 0x55, 0x9d, 0x71, 0x6b, 0x3a, 0xd9, 0x3a, 0x7f, 0x56, 0x29, 0x8e, 0x38, 0xd4, 0x01, 0xf5, 0x3f, - 0x32, 0xb0, 0x35, 0x5b, 0x67, 0x0f, 0x03, 0x14, 0xf2, 0xbf, 0x89, 0xf8, 0x4f, 0x26, 0x62, 0x17, - 0xae, 0xd3, 0x44, 0xd1, 0x31, 0xc5, 0x96, 0xa2, 0xf8, 0xff, 0xf8, 0x10, 0x63, 0xd9, 0x13, 0x2e, - 0x42, 0x67, 0x6c, 0x2f, 0x63, 0xc0, 0xfe, 0x72, 0x0d, 0xde, 0x4a, 0xb7, 0xf6, 0xeb, 0x97, 0xf6, - 0x57, 0xae, 0xc9, 0x5f, 0x72, 0x91, 0x4c, 0xcd, 0x0c, 0x73, 0x66, 0x66, 0x74, 0xe7, 0xcf, 0x8c, - 0x5a, 0x52, 0x46, 0x73, 0x6e, 0x9d, 0xa7, 0x0c, 0x0f, 0x02, 0xa5, 0x07, 0x41, 0x5f, 0xd8, 0x3e, - 0xef, 0xa3, 0xae, 0x9e, 0x7a, 0x11, 0xf2, 0x0f, 0x24, 0x95, 0x81, 0x88, 0x0d, 0x3f, 0x66, 0x20, - 0x1b, 0x59, 0x48, 0x1d, 0xb2, 0x81, 0xba, 0x4f, 0x54, 0x61, 0xe5, 0xda, 0xd0, 0x0c, 0x9f, 0xd3, - 0x16, 0x95, 0x28, 0x2c, 0xed, 0x21, 0x2d, 0xc8, 0x47, 0x5f, 0xbd, 0xc0, 0xe1, 0x0f, 0x03, 0x54, - 0xaf, 0xd5, 0x49, 0xe8, 0x6a, 0x04, 0x38, 0x52, 0x7e, 0x72, 0x13, 0x96, 0xe3, 0x4a, 0xd7, 0x77, - 0x5d, 0x1a, 0x9b, 0xf8, 0xc8, 0x7b, 0x90, 0x1b, 0x4b, 0x26, 0x74, 0xa2, 0xd3, 0xd0, 0xb4, 0x9b, - 0x6c, 0x43, 0x4a, 0x60, 0x11, 0x9f, 0x65, 0x73, 0x26, 0x68, 0x2d, 0x85, 0xd2, 0x07, 0xfa, 0x04, - 0xd6, 0xd3, 0xa1, 0xd4, 0xb6, 0xd1, 0x93, 0xc8, 0x74, 0x56, 0xd3, 0xc1, 0xa9, 0xe4, 0x8b, 0x8e, - 0x86, 0x91, 0x0f, 0x21, 0xcf, 0x92, 0x2e, 0x0d, 0x2f, 0xf0, 0x28, 0x3f, 0x25, 0x15, 0x77, 0x1f, - 0x7d, 0x3b, 0x7c, 0xd6, 0x0f, 0x51, 0x58, 0x93, 0x30, 0xf2, 0x2e, 0xac, 0xd9, 0xae, 0xe3, 0xa0, - 0x2d, 0x91, 0xf5, 0x7c, 0x37, 0x90, 0xe8, 0x0b, 0xf3, 0x6d, 0xf5, 0x28, 0x2f, 0x25, 0x0e, 0x2b, - 0xb2, 0x93, 0x5b, 0x40, 0xc6, 0xe0, 0x13, 0xea, 0xb0, 0x61, 0x88, 0x7e, 0x47, 0xa1, 0xc7, 0x34, - 0x9f, 0x6b, 0x47, 0xfd, 0x2b, 0xa8, 0x74, 0xbc, 0x64, 0x2b, 0x6d, 0xb6, 0x70, 0xc0, 0x85, 0x8c, - 0x9e, 0xd7, 0xa9, 0xd6, 0x33, 0xd2, 0xad, 0xf7, 0x06, 0x80, 0x66, 0x0f, 0x5d, 0x0b, 0xca, 0xb5, - 0xa2, 0x2d, 0xfb, 0xac, 0xfd, 0xfd, 0x02, 0x64, 0x77, 0x55, 0xd9, 0x91, 0x1d, 0x58, 0xe9, 0x08, - 0xe1, 0xda, 0x9c, 0x4a, 0x24, 0x1b, 0x71, 0x31, 0x4e, 0x3c, 0x3f, 0xca, 0xf3, 0xee, 0xb5, 0x86, - 0x71, 0xdb, 0x20, 0x5f, 0xc0, 0x4a, 0x52, 0x8c, 0xc4, 0x8c, 0x91, 0xd3, 0xf5, 0x59, 0x7e, 0x73, - 0x5c, 0xe7, 0x73, 0x5e, 0x39, 0xb7, 0x0d, 0xd2, 0x84, 0xa5, 0xfb, 0x41, 0x7f, 0xc8, 0xc5, 0x09, - 0x99, 0xb7, 0x67, 0x79, 0x59, 0x25, 0xa4, 0x63, 0x9f, 0x36, 0x0c, 0xd2, 0x85, 0x65, 0xdd, 0x30, - 0x48, 0xaa, 0xf3, 0x1b, 0x29, 0x3a, 0xc1, 0x73, 0x3b, 0xad, 0xfd, 0x9d, 0x01, 0xf9, 0x48, 0x96, - 0x2e, 0x75, 0xe8, 0x00, 0x7d, 0x72, 0x00, 0xe5, 0x48, 0x6e, 0xf4, 0x67, 0x13, 0x41, 0x6e, 0xc6, - 0x8c, 0xcf, 0x4e, 0xd2, 0xf8, 0xc8, 0xa4, 0x0d, 0x2b, 0xf7, 0x50, 0xea, 0xb6, 0x4c, 0xd4, 0x9e, - 0x68, 0xdc, 0x72, 0x61, 0xd2, 0xbc, 0x5b, 0xfa, 0xf9, 0xb2, 0x62, 0xfc, 0x7a, 0x59, 0x31, 0x7e, - 0xbb, 0xac, 0x18, 0x3f, 0xfc, 0x5e, 0xf9, 0x5f, 0x3f, 0xab, 0x06, 0xd4, 0x07, 0x7f, 0x05, 0x00, - 0x00, 0xff, 0xff, 0x54, 0xb6, 0xf0, 0x3b, 0xaa, 0x0f, 0x00, 0x00, + // 1121 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0xcd, 0x6f, 0x1b, 0x45, + 0x14, 0x67, 0xe3, 0xc6, 0x49, 0x9e, 0xe3, 0x8f, 0x4c, 0xf3, 0xb1, 0x35, 0x60, 0x1b, 0x23, 0x55, + 0xe6, 0xa3, 0x76, 0x31, 0x2a, 0x28, 0xe2, 0x23, 0x72, 0x48, 0x54, 0x82, 0xe4, 0x50, 0x6d, 0x13, + 0x0e, 0x08, 0xc9, 0x1a, 0xef, 0xbe, 0x6c, 0x46, 0xb1, 0x77, 0xb7, 0x3b, 0xb3, 0x4e, 0xf3, 0x3f, + 0x20, 0x71, 0xe5, 0x0a, 0xff, 0x09, 0xe2, 0xc2, 0x0d, 0xce, 0x3d, 0x54, 0x28, 0x5c, 0xb8, 0xf0, + 0x3f, 0xa0, 0x9d, 0x9d, 0x5d, 0xaf, 0xe3, 0xb8, 0x4d, 0xa1, 0x1c, 0x5a, 0x71, 0xf2, 0xce, 0x7b, + 0xbf, 0xf7, 0xdb, 0xf1, 0xef, 0x7d, 0xcc, 0x2c, 0x7c, 0x68, 0x33, 0x71, 0x1c, 0xf4, 0x9b, 0xa6, + 0x3b, 0x6c, 0x1d, 0x1c, 0xe3, 0xc1, 0x31, 0x73, 0x6c, 0xbe, 0x8f, 0xe2, 0xd4, 0xf5, 0x4f, 0x5a, + 0x42, 0x38, 0x2d, 0xea, 0xb1, 0x56, 0xdf, 0x77, 0x4f, 0xd0, 0x57, 0x3f, 0x4d, 0xcf, 0x77, 0x85, + 0x4b, 0xb2, 0xd1, 0xaa, 0xfc, 0xaa, 0xed, 0xba, 0xf6, 0x00, 0x5b, 0xd2, 0xda, 0x0f, 0x8e, 0x5a, + 0x38, 0xf4, 0xc4, 0x59, 0x04, 0x2a, 0xdf, 0x4a, 0xb1, 0xdb, 0xae, 0xed, 0x8e, 0x51, 0xe1, 0x4a, + 0x2e, 0xe4, 0xd3, 0x25, 0xf0, 0x99, 0x9b, 0xa1, 0x1e, 0x53, 0xf0, 0x8f, 0xae, 0x02, 0x97, 0x50, + 0xd3, 0x1d, 0x24, 0x0f, 0x2a, 0x78, 0xf3, 0x2a, 0xc1, 0x36, 0x15, 0x78, 0x4a, 0xcf, 0xe2, 0xdf, + 0x28, 0xb4, 0xfe, 0xf3, 0x1c, 0x14, 0x76, 0xdc, 0x53, 0x67, 0xc0, 0x9c, 0x93, 0x2f, 0x3d, 0xc1, + 0x5c, 0x87, 0x54, 0x00, 0x98, 0x85, 0x8e, 0x60, 0x47, 0x0c, 0x7d, 0x5d, 0xab, 0x69, 0x8d, 0x25, + 0x23, 0x65, 0x21, 0x5f, 0x43, 0x4e, 0x71, 0xf4, 0x30, 0x60, 0xfa, 0x5c, 0x4d, 0x6b, 0x2c, 0x6f, + 0x6f, 0x3e, 0x7a, 0x5c, 0xbd, 0xf3, 0xb4, 0x6d, 0x98, 0xae, 0x8f, 0x2d, 0x71, 0xe6, 0x21, 0x6f, + 0xde, 0x8d, 0x18, 0x76, 0x0f, 0xf7, 0x0c, 0x50, 0x6c, 0xbb, 0x01, 0x23, 0xab, 0x30, 0xcf, 0x43, + 0x94, 0x9e, 0xa9, 0x69, 0x8d, 0xbc, 0x11, 0x2d, 0x48, 0x19, 0x16, 0x2d, 0xa4, 0xd6, 0x80, 0x39, + 0xa8, 0x5f, 0xab, 0x69, 0x8d, 0x8c, 0x91, 0xac, 0xc9, 0x36, 0x14, 0x63, 0x35, 0x7a, 0xa6, 0xeb, + 0x1c, 0x31, 0x5b, 0x9f, 0xaf, 0x69, 0x8d, 0x5c, 0xfb, 0x46, 0x33, 0x51, 0xe9, 0xe0, 0xe1, 0x67, + 0xd2, 0x13, 0xf8, 0x34, 0xfc, 0x87, 0x46, 0x21, 0xf6, 0x44, 0x66, 0xb2, 0x05, 0x85, 0xf8, 0x1f, + 0x29, 0x8a, 0xac, 0xa4, 0xd0, 0x9b, 0xb1, 0x58, 0x17, 0x19, 0xf2, 0xca, 0x11, 0x59, 0xeb, 0xdf, + 0x65, 0x20, 0x7f, 0xe8, 0x85, 0x1a, 0x76, 0x91, 0x73, 0x6a, 0x23, 0xd1, 0x61, 0xc1, 0xa3, 0x67, + 0x03, 0x97, 0x5a, 0x52, 0xc1, 0x65, 0x23, 0x5e, 0x92, 0x7d, 0x58, 0xb0, 0x70, 0x24, 0xa5, 0xcb, + 0x49, 0xe9, 0xee, 0x3c, 0x7a, 0x5c, 0x7d, 0xef, 0x19, 0xa4, 0xdb, 0xc1, 0x51, 0x28, 0x5b, 0xd6, + 0xc2, 0x51, 0x28, 0xd9, 0x3e, 0x2c, 0x50, 0xcf, 0x93, 0x7c, 0xcb, 0xff, 0x88, 0xaf, 0xe3, 0x79, + 0x92, 0x8f, 0x7a, 0x5e, 0xc8, 0xd7, 0x81, 0x95, 0x44, 0xd0, 0x21, 0x0a, 0x6a, 0x51, 0x41, 0xf5, + 0x35, 0xa9, 0xc7, 0xea, 0x58, 0x52, 0xe3, 0x61, 0x57, 0xf9, 0x8c, 0x52, 0x6c, 0x8c, 0x2d, 0xe4, + 0x53, 0x28, 0xc5, 0x7a, 0x26, 0x0c, 0xeb, 0x92, 0xe1, 0x7a, 0xa2, 0x68, 0x8a, 0xa0, 0xa8, 0x6c, + 0x49, 0x7c, 0x07, 0x4a, 0x96, 0xaa, 0xc9, 0x9e, 0x2b, 0x8b, 0x92, 0xeb, 0xd5, 0x5a, 0xa6, 0x91, + 0x6b, 0xaf, 0x37, 0x55, 0xe3, 0x4e, 0xd6, 0xac, 0x51, 0xb4, 0x26, 0xd6, 0xbc, 0xfe, 0xed, 0x1c, + 0x14, 0x63, 0xcc, 0x8b, 0x9f, 0x93, 0x2d, 0x28, 0x5e, 0x10, 0x44, 0x65, 0x64, 0x96, 0x1e, 0x85, + 0x49, 0x3d, 0xea, 0x01, 0xe8, 0x3b, 0x38, 0x62, 0x26, 0x76, 0x4c, 0xc1, 0x46, 0x51, 0x0d, 0x23, + 0xf7, 0x5c, 0x87, 0x3f, 0x49, 0x96, 0x4b, 0x5e, 0x9b, 0x7b, 0xa6, 0xd7, 0xfe, 0x95, 0x81, 0x1b, + 0x3b, 0x68, 0x05, 0xde, 0x80, 0x99, 0x54, 0xa0, 0xf5, 0xb2, 0xf4, 0xc8, 0x1a, 0x84, 0x4f, 0x3d, + 0x66, 0xe9, 0x79, 0x39, 0x1e, 0xe7, 0xa9, 0xe7, 0xed, 0x59, 0xa1, 0x39, 0xdc, 0x36, 0xb3, 0xf4, + 0x42, 0x64, 0xb6, 0x70, 0xb4, 0x67, 0xfd, 0x77, 0x1d, 0x95, 0xb9, 0x72, 0x47, 0x55, 0x21, 0xc7, + 0xd1, 0x1f, 0xa1, 0xdf, 0x13, 0x6c, 0x88, 0xfa, 0x86, 0x1c, 0xa2, 0x10, 0x99, 0x0e, 0xd8, 0x10, + 0xc9, 0x0e, 0xac, 0xf8, 0xaa, 0x20, 0x7a, 0x02, 0x87, 0xde, 0x80, 0x0a, 0xd4, 0xab, 0x72, 0x8f, + 0x1b, 0x17, 0x93, 0xad, 0xf2, 0x67, 0x94, 0xe2, 0x88, 0x03, 0x15, 0x50, 0xff, 0x33, 0x03, 0x1b, + 0xd3, 0x75, 0xf6, 0x20, 0x40, 0x2e, 0xfe, 0x9f, 0x88, 0xff, 0x66, 0x22, 0x76, 0xe1, 0x3a, 0x4d, + 0x14, 0x1d, 0x53, 0x6c, 0x48, 0x8a, 0xd7, 0xc6, 0x9b, 0x18, 0xcb, 0x9e, 0x70, 0x11, 0x3a, 0x65, + 0x7b, 0x1e, 0x03, 0xf6, 0xd7, 0x6b, 0xf0, 0x66, 0xba, 0xb5, 0x5f, 0xbe, 0xb4, 0xbf, 0x70, 0x4d, + 0xfe, 0x9c, 0x8b, 0xe4, 0xc2, 0xcc, 0xd0, 0xa7, 0x66, 0x46, 0x77, 0xf6, 0xcc, 0xa8, 0x25, 0x65, + 0x34, 0xe3, 0xd4, 0xb9, 0x64, 0x78, 0x10, 0x28, 0xdd, 0x0f, 0xfa, 0xdc, 0xf4, 0x59, 0x1f, 0x55, + 0xf5, 0xd4, 0x8b, 0x90, 0xbf, 0x2f, 0xa8, 0x08, 0x78, 0x6c, 0xf8, 0x29, 0x03, 0xd9, 0xc8, 0x42, + 0xea, 0x90, 0x0d, 0xe4, 0x79, 0x22, 0x0b, 0x2b, 0xd7, 0x86, 0x66, 0x78, 0x9d, 0x36, 0xa8, 0x40, + 0x6e, 0x28, 0x0f, 0x69, 0x41, 0x3e, 0x7a, 0xea, 0x05, 0x0e, 0x7b, 0x10, 0xa0, 0xbc, 0xad, 0x4e, + 0x42, 0x97, 0x23, 0xc0, 0xa1, 0xf4, 0x93, 0x9b, 0xb0, 0x18, 0x57, 0xba, 0x3a, 0xeb, 0xd2, 0xd8, + 0xc4, 0x47, 0xde, 0x85, 0xdc, 0x58, 0x32, 0xae, 0x12, 0x9d, 0x86, 0xa6, 0xdd, 0x64, 0x13, 0x52, + 0x02, 0xf3, 0x78, 0x2f, 0xeb, 0x53, 0x41, 0x2b, 0x29, 0x94, 0xda, 0xd0, 0x27, 0xb0, 0x9a, 0x0e, + 0xa5, 0xa6, 0x89, 0x9e, 0x40, 0x4b, 0x65, 0x35, 0x1d, 0x9c, 0x4a, 0x3e, 0xef, 0x28, 0x18, 0xf9, + 0x00, 0xf2, 0x56, 0xd2, 0xa5, 0xe1, 0x01, 0x1e, 0xe5, 0xa7, 0x24, 0xe3, 0xee, 0xa1, 0x6f, 0x86, + 0xd7, 0xfa, 0x01, 0x72, 0x63, 0x12, 0x46, 0xde, 0x81, 0x15, 0xd3, 0x75, 0x1c, 0x34, 0x05, 0x5a, + 0x3d, 0xdf, 0x0d, 0x04, 0xfa, 0x5c, 0x7f, 0x4b, 0x5e, 0xca, 0x4b, 0x89, 0xc3, 0x88, 0xec, 0xe4, + 0x16, 0x90, 0x31, 0xf8, 0x98, 0x3a, 0xd6, 0x20, 0x44, 0xbf, 0x2d, 0xd1, 0x63, 0x9a, 0xcf, 0x95, + 0xa3, 0xfe, 0x15, 0x54, 0x3a, 0x5e, 0xf2, 0x2a, 0x65, 0x36, 0xd0, 0x66, 0x5c, 0x44, 0xd7, 0xeb, + 0x54, 0xeb, 0x69, 0xe9, 0xd6, 0x7b, 0x1d, 0x40, 0xb1, 0x87, 0xae, 0x39, 0xe9, 0x5a, 0x52, 0x96, + 0x3d, 0xab, 0xfd, 0xe3, 0x1c, 0x64, 0xb7, 0x65, 0xd9, 0x91, 0x2d, 0x58, 0xea, 0x70, 0xee, 0x9a, + 0x8c, 0x0a, 0x24, 0x6b, 0x71, 0x31, 0x4e, 0x5c, 0x3f, 0xca, 0xb3, 0xce, 0xb5, 0x86, 0x76, 0x5b, + 0x23, 0x5f, 0xc0, 0x52, 0x52, 0x8c, 0x44, 0x8f, 0x91, 0x17, 0xeb, 0xb3, 0xfc, 0xc6, 0xb8, 0xce, + 0x67, 0xdc, 0x72, 0x6e, 0x6b, 0xe4, 0x63, 0x58, 0xb8, 0x17, 0xf4, 0x07, 0x8c, 0x1f, 0x93, 0x59, + 0xef, 0x2c, 0xaf, 0x37, 0xa3, 0x6f, 0xcf, 0x66, 0xfc, 0x55, 0xd9, 0xdc, 0x0d, 0xbf, 0x3d, 0x1b, + 0x1a, 0xe9, 0xc2, 0xa2, 0x6a, 0x1f, 0x24, 0xd5, 0xd9, 0x6d, 0x15, 0xed, 0xe7, 0xa9, 0x7d, 0xd7, + 0xfe, 0x41, 0x83, 0x7c, 0x24, 0x52, 0x97, 0x3a, 0xd4, 0x46, 0x9f, 0x7c, 0x03, 0xe5, 0x48, 0x7c, + 0xf4, 0xa7, 0xd3, 0x42, 0x6e, 0xc6, 0x8c, 0x4f, 0x4e, 0xd9, 0xac, 0x3f, 0x40, 0xda, 0xb0, 0x74, + 0x17, 0x85, 0x6a, 0xd9, 0x24, 0x13, 0x13, 0x4d, 0x5d, 0x2e, 0x4c, 0x9a, 0xb7, 0x4b, 0xbf, 0x9c, + 0x57, 0xb4, 0xdf, 0xce, 0x2b, 0xda, 0xef, 0xe7, 0x15, 0xed, 0xfb, 0x3f, 0x2a, 0xaf, 0xf4, 0xb3, + 0x92, 0xf5, 0xfd, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x80, 0xe5, 0x67, 0xe3, 0x0f, 0x00, + 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 5060d30fc..3042115f8 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -3,8 +3,8 @@ syntax = "proto3"; +import "google/protobuf/empty.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; - import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; @@ -95,7 +95,7 @@ service Broker { rpc Subscribe(SubscribeRequest) returns (stream DeduplicatedUplinkMessage); // Handler initiates downlink stream. - rpc Publish(stream DownlinkMessage) returns (api.Ack); + rpc Publish(stream DownlinkMessage) returns (google.protobuf.Empty); // Router requests device activation rpc Activate(DeviceActivationRequest) returns (DeviceActivationResponse); @@ -134,7 +134,7 @@ message ApplicationHandlerRegistration { // The BrokerManager service provides configuration and monitoring functionality service BrokerManager { // Handler announces new application to Broker - rpc RegisterApplicationHandler(ApplicationHandlerRegistration) returns (api.Ack); + rpc RegisterApplicationHandler(ApplicationHandlerRegistration) returns (google.protobuf.Empty); // Network operator requests Broker status rpc GetStatus(StatusRequest) returns (Status); } diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 7e3106557..a33f59014 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -21,7 +21,7 @@ package discovery import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import api "github.com/TheThingsNetwork/ttn/api" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import ( context "golang.org/x/net/context" @@ -181,11 +181,11 @@ const _ = grpc.SupportPackageIsVersion3 // Client API for Discovery service type DiscoveryClient interface { - Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*api.Ack, error) + Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) - AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) - DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) + AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) } type discoveryClient struct { @@ -196,8 +196,8 @@ func NewDiscoveryClient(cc *grpc.ClientConn) DiscoveryClient { return &discoveryClient{cc} } -func (c *discoveryClient) Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *discoveryClient) Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/discovery.Discovery/Announce", in, out, c.cc, opts...) if err != nil { return nil, err @@ -223,8 +223,8 @@ func (c *discoveryClient) Get(ctx context.Context, in *GetRequest, opts ...grpc. return out, nil } -func (c *discoveryClient) AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *discoveryClient) AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/discovery.Discovery/AddMetadata", in, out, c.cc, opts...) if err != nil { return nil, err @@ -232,8 +232,8 @@ func (c *discoveryClient) AddMetadata(ctx context.Context, in *MetadataRequest, return out, nil } -func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/discovery.Discovery/DeleteMetadata", in, out, c.cc, opts...) if err != nil { return nil, err @@ -244,11 +244,11 @@ func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataReques // Server API for Discovery service type DiscoveryServer interface { - Announce(context.Context, *Announcement) (*api.Ack, error) + Announce(context.Context, *Announcement) (*google_protobuf.Empty, error) GetAll(context.Context, *GetAllRequest) (*AnnouncementsResponse, error) Get(context.Context, *GetRequest) (*Announcement, error) - AddMetadata(context.Context, *MetadataRequest) (*api.Ack, error) - DeleteMetadata(context.Context, *MetadataRequest) (*api.Ack, error) + AddMetadata(context.Context, *MetadataRequest) (*google_protobuf.Empty, error) + DeleteMetadata(context.Context, *MetadataRequest) (*google_protobuf.Empty, error) } func RegisterDiscoveryServer(s *grpc.Server, srv DiscoveryServer) { @@ -1681,40 +1681,41 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 556 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x54, 0xc1, 0x6e, 0xd3, 0x40, - 0x10, 0xad, 0x6d, 0x5a, 0xe2, 0x71, 0x9a, 0x5a, 0x0b, 0x15, 0x56, 0x24, 0x82, 0xf1, 0x85, 0x72, - 0x68, 0x22, 0xa5, 0x2a, 0x37, 0x84, 0x8c, 0x52, 0x4a, 0x55, 0x5a, 0x2a, 0xab, 0x20, 0x6e, 0xd1, - 0xd6, 0x1e, 0x92, 0x55, 0x92, 0xb5, 0xf1, 0xae, 0x83, 0x72, 0xe5, 0x2b, 0xb8, 0x71, 0xe1, 0x63, - 0x38, 0xf2, 0x09, 0x28, 0xfc, 0x08, 0xb2, 0x1d, 0x3b, 0x8e, 0xda, 0x08, 0xca, 0xcd, 0xfb, 0xf6, - 0xbd, 0x37, 0x33, 0xcf, 0xbb, 0x0b, 0xcf, 0x07, 0x4c, 0x0e, 0x93, 0xab, 0xb6, 0x1f, 0x4e, 0x3a, - 0x97, 0x43, 0xbc, 0x1c, 0x32, 0x3e, 0x10, 0xe7, 0x28, 0x3f, 0x87, 0xf1, 0xa8, 0x23, 0x25, 0xef, - 0xd0, 0x88, 0x75, 0x02, 0x26, 0xfc, 0x70, 0x8a, 0xf1, 0x6c, 0xf9, 0xd5, 0x8e, 0xe2, 0x50, 0x86, - 0x44, 0x2f, 0x81, 0xe6, 0xfe, 0xbf, 0x38, 0xd1, 0x88, 0xe5, 0x4a, 0xe7, 0x8b, 0x02, 0xb5, 0x33, - 0x94, 0x34, 0xa0, 0x92, 0x92, 0xa7, 0xa0, 0x8d, 0x70, 0x66, 0x29, 0xb6, 0xb2, 0xd7, 0xe8, 0x3e, - 0x68, 0x2f, 0xab, 0x14, 0x8c, 0xf6, 0x29, 0xce, 0xbc, 0x94, 0x43, 0xee, 0xc3, 0xe6, 0x94, 0x8e, - 0x13, 0xb4, 0x54, 0x5b, 0xd9, 0xab, 0x7b, 0xf9, 0xc2, 0x39, 0x04, 0xed, 0x14, 0x67, 0x44, 0x87, - 0xcd, 0xb7, 0x97, 0xaf, 0x8f, 0x3c, 0x73, 0x83, 0x00, 0x6c, 0x5d, 0x78, 0x47, 0xaf, 0x4e, 0x3e, - 0x98, 0x0a, 0x31, 0xe0, 0xae, 0x7b, 0x71, 0xd1, 0x3f, 0x7a, 0x77, 0x62, 0xaa, 0xe9, 0x46, 0xba, - 0x38, 0xe9, 0x99, 0x9a, 0xf3, 0x4d, 0x85, 0xba, 0xcb, 0x79, 0x98, 0x70, 0x1f, 0x27, 0xc8, 0x25, - 0x69, 0x80, 0xca, 0x82, 0xac, 0x0f, 0xdd, 0x53, 0x59, 0x40, 0x1e, 0x43, 0x5d, 0x60, 0x3c, 0x65, - 0x3e, 0xf6, 0x39, 0x9d, 0xe4, 0x45, 0x75, 0xcf, 0x58, 0x60, 0xe7, 0x74, 0x82, 0xe4, 0x09, 0xec, - 0x14, 0x94, 0x29, 0xc6, 0x82, 0x85, 0xdc, 0xd2, 0x32, 0x56, 0x63, 0x01, 0xbf, 0xcf, 0x51, 0x62, - 0x83, 0x11, 0xa0, 0xf0, 0x63, 0x16, 0xc9, 0x94, 0x74, 0x27, 0xb7, 0xaa, 0x40, 0xe4, 0x11, 0x18, - 0x1c, 0x65, 0x9f, 0x06, 0x41, 0x8c, 0x42, 0x58, 0x46, 0xc6, 0x00, 0x8e, 0xd2, 0xcd, 0x11, 0xf2, - 0x10, 0x20, 0x4a, 0xae, 0xc6, 0xcc, 0xef, 0xa7, 0x71, 0xd5, 0xb3, 0x7d, 0x3d, 0x47, 0xd2, 0xf1, - 0x6d, 0x30, 0x7c, 0x8c, 0x25, 0xfb, 0xc8, 0x7c, 0x2a, 0xd1, 0xda, 0xce, 0x2b, 0x54, 0x20, 0xd2, - 0x81, 0xda, 0x64, 0x11, 0xa9, 0xb5, 0x6b, 0x6b, 0x7b, 0x46, 0xf7, 0xde, 0x0d, 0x69, 0x7b, 0x25, - 0xc9, 0xe9, 0xc2, 0xf6, 0x31, 0x4a, 0x77, 0x3c, 0xf6, 0xf0, 0x53, 0x82, 0x42, 0x5e, 0x4b, 0x44, - 0xb9, 0x96, 0x88, 0xf3, 0x02, 0xe0, 0x18, 0x65, 0x21, 0xb8, 0x7d, 0xa4, 0x4e, 0x02, 0x3b, 0x65, - 0x2b, 0xff, 0xed, 0xb2, 0x32, 0x6b, 0x1a, 0xe5, 0x5f, 0x67, 0x7d, 0x03, 0xbb, 0xd5, 0xc3, 0x20, - 0x3c, 0x14, 0x51, 0xc8, 0x05, 0x92, 0x03, 0xa8, 0x2d, 0x8c, 0x85, 0xa5, 0x64, 0xa9, 0x55, 0xcf, - 0x68, 0x55, 0xe3, 0x95, 0xc4, 0xee, 0x77, 0x15, 0xf4, 0x5e, 0x41, 0x22, 0xfb, 0x50, 0x2b, 0x78, - 0x64, 0x9d, 0xb8, 0x59, 0x6b, 0xa7, 0xf7, 0xc3, 0xf5, 0x47, 0xa4, 0x07, 0x5b, 0x79, 0xec, 0xc4, - 0xaa, 0x90, 0x57, 0xfe, 0x44, 0xd3, 0x5e, 0x63, 0xb3, 0xec, 0xfb, 0x10, 0xb4, 0x63, 0x94, 0x64, - 0x77, 0xd5, 0xa2, 0xd0, 0xaf, 0x6b, 0x83, 0x1c, 0x80, 0xe1, 0x06, 0x41, 0x79, 0x39, 0x9b, 0x37, - 0xa5, 0xb6, 0xf0, 0x58, 0x76, 0xfc, 0x0c, 0x1a, 0x3d, 0x1c, 0xa3, 0xc4, 0xdb, 0xe9, 0xba, 0x04, - 0xcc, 0x32, 0xa5, 0x33, 0xca, 0xe9, 0x00, 0xe3, 0x97, 0xe6, 0x8f, 0x79, 0x4b, 0xf9, 0x39, 0x6f, - 0x29, 0xbf, 0xe6, 0x2d, 0xe5, 0xeb, 0xef, 0xd6, 0xc6, 0xd5, 0x56, 0xf6, 0x68, 0x1c, 0xfc, 0x09, - 0x00, 0x00, 0xff, 0xff, 0x2c, 0x28, 0x1d, 0x06, 0xaf, 0x04, 0x00, 0x00, + // 566 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x53, 0x4d, 0x6f, 0xd3, 0x40, + 0x10, 0xad, 0x13, 0x5a, 0xe2, 0x71, 0x9a, 0x5a, 0x0b, 0x05, 0x2b, 0x88, 0x60, 0x7c, 0xa1, 0x5c, + 0x6c, 0x29, 0x55, 0x4f, 0x08, 0x21, 0xa3, 0x84, 0x52, 0x95, 0x96, 0xca, 0x2a, 0x88, 0x5b, 0xe4, + 0xd8, 0xd3, 0x74, 0x55, 0x7b, 0x6d, 0xbc, 0xeb, 0xa0, 0x5c, 0xf9, 0x15, 0xdc, 0xf8, 0x3b, 0x9c, + 0x10, 0x3f, 0x01, 0x95, 0x3f, 0x82, 0xfc, 0x59, 0x47, 0x6d, 0x84, 0x80, 0x9b, 0xfd, 0xe6, 0xbd, + 0xb7, 0x33, 0x6f, 0x77, 0xe0, 0xf9, 0x8c, 0x8a, 0xf3, 0x74, 0x6a, 0x7a, 0x51, 0x68, 0x9d, 0x9e, + 0xe3, 0xe9, 0x39, 0x65, 0x33, 0x7e, 0x8c, 0xe2, 0x53, 0x94, 0x5c, 0x58, 0x42, 0x30, 0xcb, 0x8d, + 0xa9, 0xe5, 0x53, 0xee, 0x45, 0x73, 0x4c, 0x16, 0x57, 0x5f, 0x66, 0x9c, 0x44, 0x22, 0x22, 0x72, + 0x0d, 0xf4, 0x1f, 0xcc, 0xa2, 0x68, 0x16, 0xa0, 0x95, 0x17, 0xa6, 0xe9, 0x99, 0x85, 0x61, 0x2c, + 0x4a, 0x9e, 0xf1, 0x59, 0x82, 0xce, 0x11, 0x0a, 0xd7, 0x77, 0x85, 0x4b, 0x9e, 0x42, 0xfb, 0x02, + 0x17, 0x9a, 0xa4, 0x4b, 0x3b, 0xbd, 0xe1, 0x7d, 0xf3, 0xca, 0xb3, 0x62, 0x98, 0x87, 0xb8, 0x70, + 0x32, 0x0e, 0xb9, 0x0b, 0xeb, 0x73, 0x37, 0x48, 0x51, 0x6b, 0xe9, 0xd2, 0x4e, 0xd7, 0x29, 0x7e, + 0x8c, 0x3d, 0x68, 0x1f, 0xe2, 0x82, 0xc8, 0xb0, 0xfe, 0xf6, 0xf4, 0xf5, 0xd8, 0x51, 0xd7, 0x08, + 0xc0, 0xc6, 0x89, 0x33, 0x7e, 0x75, 0xf0, 0x41, 0x95, 0x88, 0x02, 0xb7, 0xed, 0x93, 0x93, 0xc9, + 0xf8, 0xdd, 0x81, 0xda, 0xca, 0x0a, 0xd9, 0xcf, 0xc1, 0x48, 0x6d, 0x1b, 0x5f, 0x5b, 0xd0, 0xb5, + 0x19, 0x8b, 0x52, 0xe6, 0x61, 0x88, 0x4c, 0x90, 0x1e, 0xb4, 0xa8, 0x9f, 0xf7, 0x21, 0x3b, 0x2d, + 0xea, 0x93, 0xc7, 0xd0, 0xe5, 0x98, 0xcc, 0xa9, 0x87, 0x13, 0xe6, 0x86, 0xc5, 0xa1, 0xb2, 0xa3, + 0x94, 0xd8, 0xb1, 0x1b, 0x22, 0x79, 0x02, 0x5b, 0x15, 0x65, 0x8e, 0x09, 0xa7, 0x11, 0xd3, 0xda, + 0x39, 0xab, 0x57, 0xc2, 0xef, 0x0b, 0x94, 0xe8, 0xa0, 0xf8, 0xc8, 0xbd, 0x84, 0xc6, 0x22, 0x23, + 0xdd, 0x2a, 0xac, 0x1a, 0x10, 0x79, 0x04, 0x0a, 0x43, 0x31, 0x71, 0x7d, 0x3f, 0x41, 0xce, 0x35, + 0x25, 0x67, 0x00, 0x43, 0x61, 0x17, 0x08, 0x79, 0x08, 0x10, 0xa7, 0xd3, 0x80, 0x7a, 0x93, 0x2c, + 0xae, 0x6e, 0x5e, 0x97, 0x0b, 0x24, 0x1b, 0x5f, 0x07, 0xc5, 0xc3, 0x44, 0xd0, 0x33, 0xea, 0xb9, + 0x02, 0xb5, 0xcd, 0xe2, 0x84, 0x06, 0x44, 0x2c, 0xe8, 0x84, 0x65, 0xa4, 0xda, 0xb6, 0xde, 0xde, + 0x51, 0x86, 0x77, 0x6e, 0x48, 0xdb, 0xa9, 0x49, 0xc6, 0x10, 0x36, 0xf7, 0x51, 0xd8, 0x41, 0xe0, + 0xe0, 0xc7, 0x14, 0xb9, 0xb8, 0x96, 0x88, 0x74, 0x2d, 0x11, 0xe3, 0x05, 0xc0, 0x3e, 0x8a, 0x4a, + 0xf0, 0xf7, 0x91, 0x1a, 0x29, 0x6c, 0xd5, 0xad, 0xfc, 0xb3, 0xcb, 0xd2, 0xac, 0x59, 0x94, 0x7f, + 0x9c, 0xf5, 0x0d, 0x6c, 0x37, 0x1f, 0x03, 0x77, 0x90, 0xc7, 0x11, 0xe3, 0x48, 0x76, 0xa1, 0x53, + 0x1a, 0x73, 0x4d, 0xca, 0x53, 0x6b, 0xbe, 0xd1, 0xa6, 0xc6, 0xa9, 0x89, 0xc3, 0xef, 0x2d, 0x90, + 0x47, 0x15, 0x89, 0x3c, 0x83, 0x4e, 0xc5, 0x23, 0xab, 0xc4, 0xfd, 0x7b, 0x66, 0xb1, 0x31, 0x66, + 0xb5, 0x31, 0xe6, 0x38, 0xdb, 0x18, 0x32, 0x82, 0x8d, 0xe2, 0x12, 0x88, 0xd6, 0x90, 0x2e, 0xdd, + 0x4b, 0x5f, 0x5f, 0x61, 0x7a, 0x35, 0xc5, 0x1e, 0xb4, 0xf7, 0x51, 0x90, 0xed, 0x65, 0x8b, 0x4a, + 0xbf, 0xaa, 0x29, 0x62, 0x83, 0x62, 0xfb, 0x7e, 0xbd, 0xaa, 0xfd, 0x9b, 0x32, 0x2c, 0x3d, 0x56, + 0xf7, 0xdf, 0x1b, 0x61, 0x80, 0x02, 0xff, 0xc7, 0x65, 0x48, 0x40, 0xad, 0xf3, 0x3c, 0x72, 0x99, + 0x3b, 0xc3, 0xe4, 0xa5, 0xfa, 0xed, 0x72, 0x20, 0xfd, 0xb8, 0x1c, 0x48, 0x3f, 0x2f, 0x07, 0xd2, + 0x97, 0x5f, 0x83, 0xb5, 0xe9, 0x46, 0xae, 0xda, 0xfd, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x11, 0x16, + 0x9b, 0xee, 0xc7, 0x04, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 8e6804879..d260e64cf 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -3,7 +3,7 @@ syntax = "proto3"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "google/protobuf/empty.proto"; package discovery; @@ -58,11 +58,11 @@ message AnnouncementsResponse { } service Discovery { - rpc Announce(Announcement) returns (api.Ack); + rpc Announce(Announcement) returns (google.protobuf.Empty); rpc GetAll(GetAllRequest) returns (AnnouncementsResponse); rpc Get(GetRequest) returns (Announcement); - rpc AddMetadata(MetadataRequest) returns (api.Ack); - rpc DeleteMetadata(MetadataRequest) returns (api.Ack); + rpc AddMetadata(MetadataRequest) returns (google.protobuf.Empty); + rpc DeleteMetadata(MetadataRequest) returns (google.protobuf.Empty); } // The DiscoveryManager service provides configuration and monitoring functionality diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 16af323df..b7283b531 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -27,7 +27,7 @@ package handler import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import api "github.com/TheThingsNetwork/ttn/api" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import broker "github.com/TheThingsNetwork/ttn/api/broker" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" import lorawan1 "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" @@ -381,13 +381,13 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ // Client API for ApplicationManager service type ApplicationManagerClient interface { - RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) + RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) - SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*api.Ack, error) - DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) + SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) - SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) - DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) DryDownlink(ctx context.Context, in *DryDownlinkMessage, opts ...grpc.CallOption) (*DryDownlinkResult, error) DryUplink(ctx context.Context, in *DryUplinkMessage, opts ...grpc.CallOption) (*DryUplinkResult, error) @@ -401,8 +401,8 @@ func NewApplicationManagerClient(cc *grpc.ClientConn) ApplicationManagerClient { return &applicationManagerClient{cc} } -func (c *applicationManagerClient) RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *applicationManagerClient) RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/handler.ApplicationManager/RegisterApplication", in, out, c.cc, opts...) if err != nil { return nil, err @@ -419,8 +419,8 @@ func (c *applicationManagerClient) GetApplication(ctx context.Context, in *Appli return out, nil } -func (c *applicationManagerClient) SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *applicationManagerClient) SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/handler.ApplicationManager/SetApplication", in, out, c.cc, opts...) if err != nil { return nil, err @@ -428,8 +428,8 @@ func (c *applicationManagerClient) SetApplication(ctx context.Context, in *Appli return out, nil } -func (c *applicationManagerClient) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *applicationManagerClient) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/handler.ApplicationManager/DeleteApplication", in, out, c.cc, opts...) if err != nil { return nil, err @@ -446,8 +446,8 @@ func (c *applicationManagerClient) GetDevice(ctx context.Context, in *DeviceIden return out, nil } -func (c *applicationManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *applicationManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/handler.ApplicationManager/SetDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -455,8 +455,8 @@ func (c *applicationManagerClient) SetDevice(ctx context.Context, in *Device, op return out, nil } -func (c *applicationManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *applicationManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/handler.ApplicationManager/DeleteDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -494,13 +494,13 @@ func (c *applicationManagerClient) DryUplink(ctx context.Context, in *DryUplinkM // Server API for ApplicationManager service type ApplicationManagerServer interface { - RegisterApplication(context.Context, *ApplicationIdentifier) (*api.Ack, error) + RegisterApplication(context.Context, *ApplicationIdentifier) (*google_protobuf.Empty, error) GetApplication(context.Context, *ApplicationIdentifier) (*Application, error) - SetApplication(context.Context, *Application) (*api.Ack, error) - DeleteApplication(context.Context, *ApplicationIdentifier) (*api.Ack, error) + SetApplication(context.Context, *Application) (*google_protobuf.Empty, error) + DeleteApplication(context.Context, *ApplicationIdentifier) (*google_protobuf.Empty, error) GetDevice(context.Context, *DeviceIdentifier) (*Device, error) - SetDevice(context.Context, *Device) (*api.Ack, error) - DeleteDevice(context.Context, *DeviceIdentifier) (*api.Ack, error) + SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) GetDevicesForApplication(context.Context, *ApplicationIdentifier) (*DeviceList, error) DryDownlink(context.Context, *DryDownlinkMessage) (*DryDownlinkResult, error) DryUplink(context.Context, *DryUplinkMessage) (*DryUplinkResult, error) @@ -2890,54 +2890,56 @@ func init() { } var fileDescriptorHandler = []byte{ - // 781 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x55, 0xcd, 0x6e, 0xea, 0x46, - 0x14, 0xbe, 0xbe, 0xb9, 0x21, 0x70, 0xb8, 0x17, 0x92, 0xc9, 0x4f, 0x5d, 0x1a, 0x21, 0xea, 0x45, - 0x94, 0xaa, 0x8a, 0xa9, 0x48, 0xa5, 0xb4, 0xaa, 0xaa, 0x84, 0x08, 0x91, 0x44, 0x2a, 0x8d, 0xe4, - 0xa4, 0x8b, 0x76, 0x83, 0x26, 0x9e, 0x13, 0x18, 0xe1, 0x78, 0x5c, 0x7b, 0x20, 0x4a, 0x9f, 0xa3, - 0x8b, 0x3e, 0x52, 0x97, 0xdd, 0x77, 0x53, 0xa5, 0x0f, 0xd2, 0xca, 0x9e, 0xc1, 0x38, 0x40, 0x0b, - 0x64, 0x05, 0xe7, 0xe7, 0xfb, 0xce, 0xbf, 0x07, 0xbe, 0xee, 0x71, 0xd9, 0x1f, 0xde, 0xd9, 0xae, - 0x78, 0xa8, 0xdf, 0xf6, 0xf1, 0xb6, 0xcf, 0xfd, 0x5e, 0xf4, 0x3d, 0xca, 0x47, 0x11, 0x0e, 0xea, - 0x52, 0xfa, 0x75, 0x1a, 0xf0, 0x7a, 0x9f, 0xfa, 0xcc, 0xc3, 0x70, 0xfc, 0x6b, 0x07, 0xa1, 0x90, - 0x82, 0x6c, 0x68, 0xb1, 0x72, 0xb4, 0x0c, 0x07, 0x0d, 0xb8, 0xc2, 0x55, 0x4e, 0x96, 0x71, 0xbf, - 0x0b, 0xc5, 0x00, 0x43, 0xfd, 0xa3, 0x81, 0xdf, 0x2c, 0x03, 0x4c, 0x5c, 0x5d, 0xe1, 0xa5, 0x7f, - 0x34, 0xb8, 0xb9, 0x12, 0xd8, 0x13, 0x21, 0x7d, 0xa4, 0x7e, 0x9d, 0xe1, 0x88, 0xbb, 0xa8, 0x28, - 0xac, 0x3f, 0x0d, 0x30, 0x5b, 0x89, 0xa2, 0xe9, 0x4a, 0x3e, 0xa2, 0x92, 0x0b, 0xdf, 0xc1, 0x28, - 0x10, 0x7e, 0x84, 0xc4, 0x84, 0x8d, 0x80, 0x3e, 0x79, 0x82, 0x32, 0xd3, 0xa8, 0x19, 0x87, 0xef, - 0x9d, 0xb1, 0x48, 0x76, 0x21, 0x47, 0x83, 0xa0, 0xcb, 0x99, 0xf9, 0xb6, 0x66, 0x1c, 0x16, 0x9c, - 0x75, 0x1a, 0x04, 0x57, 0x8c, 0x9c, 0x42, 0x99, 0x89, 0x47, 0xdf, 0xe3, 0xfe, 0xa0, 0x2b, 0x82, - 0x98, 0xcb, 0x2c, 0xd6, 0x8c, 0xc3, 0x62, 0x63, 0xcf, 0xd6, 0x55, 0xb7, 0xb4, 0xf9, 0x3a, 0xb1, - 0x3a, 0x25, 0xf6, 0x42, 0x26, 0x1d, 0xd8, 0xa6, 0x69, 0x1e, 0xdd, 0x07, 0x94, 0x94, 0x51, 0x49, - 0xcd, 0x8f, 0x12, 0x92, 0x7d, 0x3b, 0xad, 0x7f, 0x92, 0x6c, 0x47, 0xfb, 0x38, 0x84, 0xce, 0xe8, - 0xac, 0x32, 0x7c, 0xb8, 0x91, 0x54, 0x0e, 0x23, 0x07, 0x7f, 0x1e, 0x62, 0x24, 0xad, 0x3c, 0xe4, - 0x94, 0xc2, 0xb2, 0x61, 0xb7, 0x19, 0x04, 0x1e, 0x77, 0x13, 0xc4, 0x15, 0x43, 0x5f, 0xf2, 0x7b, - 0x8e, 0x61, 0xa6, 0x34, 0x23, 0x53, 0x9a, 0xf5, 0xab, 0x01, 0xc5, 0x0c, 0xe0, 0x3f, 0xdc, 0xe2, - 0x96, 0x31, 0x74, 0x05, 0xc3, 0x50, 0x77, 0x66, 0x2c, 0x92, 0x7d, 0x28, 0xb8, 0xc2, 0x1f, 0x61, - 0x28, 0x31, 0x34, 0xd7, 0x12, 0xdb, 0x44, 0x11, 0x5b, 0x47, 0xd4, 0xe3, 0x8c, 0x4a, 0x11, 0x9a, - 0xef, 0x94, 0x35, 0x55, 0xc4, 0xac, 0xe8, 0x2b, 0xd6, 0x75, 0xc5, 0xaa, 0x45, 0xeb, 0x0c, 0x36, - 0xd5, 0xf8, 0x16, 0x56, 0x10, 0xab, 0x19, 0x8e, 0x32, 0x33, 0x63, 0x38, 0xba, 0x62, 0xd6, 0x2f, - 0x90, 0x53, 0x0c, 0xab, 0xe1, 0xc8, 0x57, 0x50, 0xd2, 0x1b, 0xd5, 0x55, 0x1b, 0x95, 0x14, 0x55, - 0x6c, 0x94, 0x6d, 0xad, 0xb6, 0x15, 0xed, 0xe5, 0x1b, 0xe7, 0x83, 0xd6, 0x28, 0xc5, 0x79, 0x3e, - 0x21, 0xe4, 0x2e, 0x5a, 0x27, 0x00, 0x4a, 0xf7, 0x1d, 0x8f, 0x24, 0xf9, 0x2c, 0xee, 0x5d, 0x2c, - 0x45, 0xa6, 0x51, 0x5b, 0x4b, 0xa8, 0xc6, 0xd7, 0xa9, 0xbc, 0x9c, 0xb1, 0xdd, 0xf2, 0x81, 0xb4, - 0xc2, 0xa7, 0xf1, 0x32, 0x75, 0x30, 0x8a, 0x68, 0xef, 0xff, 0xf6, 0x75, 0x0f, 0x72, 0xf7, 0x1c, - 0x3d, 0x16, 0xe9, 0x1a, 0xb4, 0x44, 0x0e, 0x60, 0x8d, 0x06, 0x81, 0xce, 0x7c, 0x27, 0x0d, 0x97, - 0x19, 0xb4, 0x13, 0x3b, 0x58, 0xb7, 0xb0, 0xd9, 0x0a, 0x9f, 0x7e, 0x08, 0x96, 0x8b, 0xa6, 0x59, - 0xdf, 0x2e, 0x62, 0xfd, 0x11, 0xca, 0x29, 0xab, 0x83, 0xd1, 0xd0, 0x93, 0xaf, 0x28, 0x61, 0x07, - 0xd6, 0x93, 0x45, 0x49, 0x8a, 0xc8, 0x3b, 0x4a, 0xb0, 0x8e, 0x60, 0x2b, 0xd3, 0xa0, 0x45, 0xe4, - 0x0d, 0x84, 0x8d, 0x4b, 0x95, 0x25, 0xf9, 0x09, 0xf2, 0xfa, 0xba, 0x90, 0x7c, 0x9e, 0x9e, 0x2d, - 0xb2, 0xa1, 0x4a, 0x1e, 0xd9, 0xec, 0xe7, 0x22, 0xb9, 0xad, 0xca, 0xa7, 0x53, 0xd3, 0x9a, 0xfd, - 0xa0, 0x34, 0xfe, 0x79, 0x07, 0x24, 0xd3, 0x85, 0x0e, 0xf5, 0x69, 0x0f, 0x43, 0x72, 0x0a, 0xdb, - 0x0e, 0xf6, 0x78, 0x24, 0x31, 0xcc, 0x9e, 0x58, 0x75, 0x5e, 0xe7, 0x26, 0x7b, 0x5e, 0xc9, 0xdb, - 0xf1, 0x07, 0xb8, 0xe9, 0x0e, 0x48, 0x1b, 0x4a, 0x17, 0x28, 0x57, 0xc1, 0xce, 0x9d, 0x0a, 0xf9, - 0x02, 0x4a, 0x37, 0x2f, 0x79, 0xe6, 0xfa, 0x65, 0x22, 0x7f, 0x0b, 0x5b, 0x2d, 0xf4, 0x50, 0xe2, - 0xeb, 0x12, 0x3f, 0x81, 0xc2, 0x05, 0x4a, 0x7d, 0x7f, 0x1f, 0x4f, 0x35, 0x30, 0x83, 0x98, 0xbe, - 0x04, 0x72, 0x00, 0x85, 0x9b, 0x14, 0x38, 0x6d, 0xcd, 0x04, 0x38, 0x86, 0xf7, 0x2a, 0xbf, 0xc5, - 0x31, 0x26, 0xa0, 0x6b, 0x30, 0xd3, 0xac, 0xa2, 0xb6, 0x58, 0x69, 0x28, 0xdb, 0x53, 0x01, 0x92, - 0xcb, 0x6e, 0x43, 0x31, 0xb3, 0x8d, 0xe4, 0x93, 0x89, 0xcf, 0xcc, 0x11, 0x57, 0x2a, 0xf3, 0x8c, - 0x7a, 0x81, 0xcf, 0xa0, 0x90, 0x1e, 0x4c, 0xb6, 0x94, 0xa9, 0xd3, 0xac, 0x98, 0xb3, 0x26, 0xc5, - 0xd0, 0x68, 0x43, 0x49, 0x2f, 0xfa, 0x78, 0xf9, 0xbe, 0x4c, 0x46, 0xa0, 0x5e, 0x05, 0xb2, 0x97, - 0x02, 0x5f, 0xbc, 0x1b, 0x99, 0xfe, 0x2b, 0xfd, 0xf9, 0xe6, 0xef, 0xcf, 0x55, 0xe3, 0x8f, 0xe7, - 0xaa, 0xf1, 0xd7, 0x73, 0xd5, 0xf8, 0xed, 0xef, 0xea, 0x9b, 0xbb, 0x5c, 0xf2, 0x38, 0x1d, 0xff, - 0x1b, 0x00, 0x00, 0xff, 0xff, 0x5c, 0x52, 0xb5, 0xba, 0x7e, 0x08, 0x00, 0x00, + // 801 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0xef, 0x36, 0xc4, 0x89, 0x9f, 0x5b, 0x3b, 0x9d, 0xb6, 0x66, 0x71, 0x2b, 0xcb, 0xec, 0x01, + 0x05, 0x21, 0xd6, 0x92, 0xa9, 0x14, 0x10, 0x12, 0xd4, 0x95, 0x71, 0x1b, 0x81, 0xa9, 0xb4, 0x09, + 0x07, 0xb8, 0x58, 0xe3, 0x9d, 0x17, 0x7b, 0x94, 0xcd, 0xce, 0xb2, 0x3b, 0x76, 0x64, 0x3e, 0x07, + 0x07, 0xbe, 0x10, 0x12, 0x47, 0xee, 0x5c, 0x50, 0xf8, 0x22, 0x68, 0x67, 0x66, 0xd7, 0x1b, 0xff, + 0xc1, 0x31, 0xea, 0xc9, 0x7e, 0xff, 0x7e, 0xf3, 0xfe, 0xfd, 0xde, 0xc2, 0x17, 0x63, 0x2e, 0x27, + 0xd3, 0x91, 0xeb, 0x8b, 0xab, 0xf6, 0xf9, 0x04, 0xcf, 0x27, 0x3c, 0x1c, 0x27, 0xdf, 0xa3, 0xbc, + 0x16, 0xf1, 0x65, 0x5b, 0xca, 0xb0, 0x4d, 0x23, 0xde, 0x9e, 0xd0, 0x90, 0x05, 0x18, 0x67, 0xbf, + 0x6e, 0x14, 0x0b, 0x29, 0xc8, 0x81, 0x11, 0x1b, 0xcf, 0xc6, 0x42, 0x8c, 0x03, 0x6c, 0x2b, 0xf5, + 0x68, 0x7a, 0xd1, 0xc6, 0xab, 0x48, 0xce, 0xb5, 0x57, 0xe3, 0xe4, 0x2e, 0x0f, 0x8c, 0x62, 0x71, + 0x89, 0xb1, 0xf9, 0x31, 0x81, 0x5f, 0xde, 0x25, 0x50, 0xb9, 0xfa, 0x22, 0xc8, 0xff, 0x98, 0xe0, + 0xee, 0x4e, 0xc1, 0x81, 0x88, 0xe9, 0x35, 0x0d, 0xdb, 0x0c, 0x67, 0xdc, 0x47, 0x0d, 0xe1, 0xfc, + 0x65, 0x81, 0xdd, 0x53, 0x8a, 0xae, 0x2f, 0xf9, 0x8c, 0x4a, 0x2e, 0x42, 0x0f, 0x93, 0x48, 0x84, + 0x09, 0x12, 0x1b, 0x0e, 0x22, 0x3a, 0x0f, 0x04, 0x65, 0xb6, 0xd5, 0xb2, 0x8e, 0x1f, 0x78, 0x99, + 0x48, 0x9e, 0x42, 0x89, 0x46, 0xd1, 0x90, 0x33, 0xfb, 0x7e, 0xcb, 0x3a, 0x2e, 0x7b, 0xfb, 0x34, + 0x8a, 0x4e, 0x19, 0xf9, 0x1a, 0x6a, 0x4c, 0x5c, 0x87, 0x01, 0x0f, 0x2f, 0x87, 0x22, 0x4a, 0xb1, + 0xec, 0x4a, 0xcb, 0x3a, 0xae, 0x74, 0xea, 0xae, 0xa9, 0xba, 0x67, 0xcc, 0x6f, 0x95, 0xd5, 0xab, + 0xb2, 0x5b, 0x32, 0x19, 0xc0, 0x63, 0x9a, 0xe7, 0x31, 0xbc, 0x42, 0x49, 0x19, 0x95, 0xd4, 0x7e, + 0x5f, 0x81, 0x3c, 0x77, 0xf3, 0xfa, 0x17, 0xc9, 0x0e, 0x8c, 0x8f, 0x47, 0xe8, 0x8a, 0xce, 0xa9, + 0xc1, 0xc3, 0x33, 0x49, 0xe5, 0x34, 0xf1, 0xf0, 0xe7, 0x29, 0x26, 0xd2, 0x39, 0x84, 0x92, 0x56, + 0x38, 0x2e, 0x3c, 0xed, 0x46, 0x51, 0xc0, 0x7d, 0x15, 0x71, 0xca, 0x30, 0x94, 0xfc, 0x82, 0x63, + 0x5c, 0x28, 0xcd, 0x2a, 0x94, 0xe6, 0xfc, 0x6a, 0x41, 0xa5, 0x10, 0xb0, 0xc1, 0x2d, 0x6d, 0x19, + 0x43, 0x5f, 0x30, 0x8c, 0x4d, 0x67, 0x32, 0x91, 0x3c, 0x87, 0xb2, 0x2f, 0xc2, 0x19, 0xc6, 0x12, + 0x63, 0x7b, 0x4f, 0xd9, 0x16, 0x8a, 0xd4, 0x3a, 0xa3, 0x01, 0x67, 0x54, 0x8a, 0xd8, 0x7e, 0x4f, + 0x5b, 0x73, 0x45, 0x8a, 0x8a, 0xa1, 0x46, 0xdd, 0xd7, 0xa8, 0x46, 0x74, 0x5e, 0xc2, 0x91, 0x1e, + 0xdf, 0xd6, 0x0a, 0x52, 0x35, 0xc3, 0x59, 0x61, 0x66, 0x0c, 0x67, 0xa7, 0xcc, 0xf9, 0x05, 0x4a, + 0x1a, 0x61, 0xb7, 0x38, 0xf2, 0x39, 0x54, 0xcd, 0x46, 0x0d, 0xf5, 0x46, 0xa9, 0xa2, 0x2a, 0x9d, + 0x9a, 0x6b, 0xd4, 0xae, 0x86, 0x7d, 0x73, 0xcf, 0x7b, 0x68, 0x34, 0x5a, 0xf1, 0xea, 0x50, 0x01, + 0x72, 0x1f, 0x9d, 0x13, 0x00, 0xad, 0xfb, 0x8e, 0x27, 0x92, 0x7c, 0x9c, 0xf6, 0x2e, 0x95, 0x12, + 0xdb, 0x6a, 0xed, 0x29, 0xa8, 0x8c, 0x8b, 0xda, 0xcb, 0xcb, 0xec, 0x4e, 0x08, 0xa4, 0x17, 0xcf, + 0xb3, 0x65, 0x1a, 0x60, 0x92, 0xd0, 0xf1, 0x7f, 0xed, 0x6b, 0x1d, 0x4a, 0x17, 0x1c, 0x03, 0x96, + 0x98, 0x1a, 0x8c, 0x44, 0x3e, 0x82, 0x3d, 0x1a, 0x45, 0x26, 0xf3, 0x27, 0xf9, 0x73, 0x85, 0x41, + 0x7b, 0xa9, 0x83, 0x73, 0x0e, 0x47, 0xbd, 0x78, 0xfe, 0x43, 0x74, 0xb7, 0xd7, 0x0c, 0xea, 0xfd, + 0x6d, 0xa8, 0x3f, 0x42, 0x2d, 0x47, 0xf5, 0x30, 0x99, 0x06, 0xf2, 0x7f, 0x94, 0xf0, 0x04, 0xf6, + 0xd5, 0xa2, 0xa8, 0x22, 0x0e, 0x3d, 0x2d, 0x38, 0x9f, 0xc2, 0xa3, 0x42, 0x83, 0xb6, 0x81, 0x77, + 0x10, 0x0e, 0xde, 0xe8, 0x2c, 0xc9, 0x4f, 0x70, 0x68, 0xd8, 0x85, 0xe4, 0x93, 0x9c, 0xb6, 0xc8, + 0xa6, 0x3a, 0x79, 0x64, 0xab, 0xe7, 0x42, 0x71, 0xab, 0xf1, 0xe1, 0xd2, 0xb4, 0x56, 0x0f, 0x4a, + 0xe7, 0xf7, 0x7d, 0x20, 0x85, 0x2e, 0x0c, 0x68, 0x48, 0xc7, 0x18, 0xa7, 0xac, 0xf7, 0x70, 0xcc, + 0x13, 0x89, 0x71, 0x91, 0x62, 0xcd, 0x75, 0x9d, 0x5b, 0xec, 0x79, 0xa3, 0xee, 0xea, 0x93, 0xec, + 0x66, 0x27, 0xd9, 0xfd, 0x26, 0x3d, 0xc9, 0xa4, 0x0f, 0xd5, 0xd7, 0x28, 0x77, 0x41, 0x5a, 0x3b, + 0x23, 0xf2, 0x15, 0x54, 0xcf, 0x6e, 0xe3, 0xac, 0xf5, 0xdb, 0x98, 0xc7, 0xb7, 0xf0, 0xa8, 0x87, + 0x01, 0x4a, 0x7c, 0x17, 0x45, 0x9d, 0x40, 0xf9, 0x35, 0x4a, 0xc3, 0xd4, 0x0f, 0x96, 0x5a, 0x5d, + 0x88, 0x5f, 0xe6, 0x0c, 0x79, 0x01, 0xe5, 0xb3, 0x3c, 0x70, 0xd9, 0xba, 0xf1, 0xb9, 0x2e, 0x3c, + 0xd0, 0xb9, 0x6f, 0x7f, 0x71, 0x13, 0xc4, 0x5b, 0xb0, 0xf3, 0x8c, 0x93, 0xbe, 0xd8, 0x69, 0xb4, + 0x8f, 0x97, 0x9e, 0x53, 0xf7, 0xa1, 0x0f, 0x95, 0xc2, 0x4e, 0x93, 0x67, 0x0b, 0x9f, 0x95, 0x53, + 0xd0, 0x68, 0xac, 0x33, 0x1a, 0x1a, 0xbc, 0x84, 0x72, 0x4e, 0xbb, 0x62, 0x61, 0x4b, 0x04, 0x6f, + 0xd8, 0xab, 0x26, 0x8d, 0xd0, 0xe9, 0x43, 0xd5, 0xd0, 0x25, 0x5b, 0xe1, 0x17, 0x6a, 0x3c, 0xfa, + 0xdb, 0x42, 0xea, 0x79, 0xe0, 0xad, 0xaf, 0x4f, 0x61, 0x36, 0x5a, 0xff, 0xea, 0xe8, 0x8f, 0x9b, + 0xa6, 0xf5, 0xe7, 0x4d, 0xd3, 0xfa, 0xfb, 0xa6, 0x69, 0xfd, 0xf6, 0x4f, 0xf3, 0xde, 0xa8, 0xa4, + 0x9a, 0xf8, 0xd9, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x55, 0xb5, 0x55, 0x22, 0xb2, 0x08, 0x00, + 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 0371c8925..cf62f9d62 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -3,7 +3,7 @@ syntax = "proto3"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "google/protobuf/empty.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto"; @@ -79,13 +79,13 @@ message DryDownlinkResult { } service ApplicationManager { - rpc RegisterApplication(ApplicationIdentifier) returns (api.Ack); + rpc RegisterApplication(ApplicationIdentifier) returns (google.protobuf.Empty); rpc GetApplication(ApplicationIdentifier) returns (Application); - rpc SetApplication(Application) returns (api.Ack); - rpc DeleteApplication(ApplicationIdentifier) returns (api.Ack); + rpc SetApplication(Application) returns (google.protobuf.Empty); + rpc DeleteApplication(ApplicationIdentifier) returns (google.protobuf.Empty); rpc GetDevice(DeviceIdentifier) returns (Device); - rpc SetDevice(Device) returns (api.Ack); - rpc DeleteDevice(DeviceIdentifier) returns (api.Ack); + rpc SetDevice(Device) returns (google.protobuf.Empty); + rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty); rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList); rpc DryDownlink(DryDownlinkMessage) returns (DryDownlinkResult); rpc DryUplink(DryUplinkMessage) returns (DryUplinkResult); diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 2c7d86141..ed7280340 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -15,7 +15,7 @@ package noc import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import api "github.com/TheThingsNetwork/ttn/api" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import gateway "github.com/TheThingsNetwork/ttn/api/gateway" import router "github.com/TheThingsNetwork/ttn/api/router" import broker "github.com/TheThingsNetwork/ttn/api/broker" @@ -73,7 +73,7 @@ func (c *monitoringClient) GatewayStatus(ctx context.Context, opts ...grpc.CallO type Monitoring_GatewayStatusClient interface { Send(*gateway.Status) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -85,11 +85,11 @@ func (x *monitoringGatewayStatusClient) Send(m *gateway.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringGatewayStatusClient) CloseAndRecv() (*api.Ack, error) { +func (x *monitoringGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -107,7 +107,7 @@ func (c *monitoringClient) RouterStatus(ctx context.Context, opts ...grpc.CallOp type Monitoring_RouterStatusClient interface { Send(*router.Status) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -119,11 +119,11 @@ func (x *monitoringRouterStatusClient) Send(m *router.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringRouterStatusClient) CloseAndRecv() (*api.Ack, error) { +func (x *monitoringRouterStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -141,7 +141,7 @@ func (c *monitoringClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOp type Monitoring_BrokerStatusClient interface { Send(*broker.Status) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -153,11 +153,11 @@ func (x *monitoringBrokerStatusClient) Send(m *broker.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringBrokerStatusClient) CloseAndRecv() (*api.Ack, error) { +func (x *monitoringBrokerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -175,7 +175,7 @@ func (c *monitoringClient) HandlerStatus(ctx context.Context, opts ...grpc.CallO type Monitoring_HandlerStatusClient interface { Send(*handler.Status) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -187,11 +187,11 @@ func (x *monitoringHandlerStatusClient) Send(m *handler.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringHandlerStatusClient) CloseAndRecv() (*api.Ack, error) { +func (x *monitoringHandlerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -216,7 +216,7 @@ func _Monitoring_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream } type Monitoring_GatewayStatusServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*gateway.Status, error) grpc.ServerStream } @@ -225,7 +225,7 @@ type monitoringGatewayStatusServer struct { grpc.ServerStream } -func (x *monitoringGatewayStatusServer) SendAndClose(m *api.Ack) error { +func (x *monitoringGatewayStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -242,7 +242,7 @@ func _Monitoring_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) } type Monitoring_RouterStatusServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*router.Status, error) grpc.ServerStream } @@ -251,7 +251,7 @@ type monitoringRouterStatusServer struct { grpc.ServerStream } -func (x *monitoringRouterStatusServer) SendAndClose(m *api.Ack) error { +func (x *monitoringRouterStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -268,7 +268,7 @@ func _Monitoring_BrokerStatus_Handler(srv interface{}, stream grpc.ServerStream) } type Monitoring_BrokerStatusServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*broker.Status, error) grpc.ServerStream } @@ -277,7 +277,7 @@ type monitoringBrokerStatusServer struct { grpc.ServerStream } -func (x *monitoringBrokerStatusServer) SendAndClose(m *api.Ack) error { +func (x *monitoringBrokerStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -294,7 +294,7 @@ func _Monitoring_HandlerStatus_Handler(srv interface{}, stream grpc.ServerStream } type Monitoring_HandlerStatusServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*handler.Status, error) grpc.ServerStream } @@ -303,7 +303,7 @@ type monitoringHandlerStatusServer struct { grpc.ServerStream } -func (x *monitoringHandlerStatusServer) SendAndClose(m *api.Ack) error { +func (x *monitoringHandlerStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -349,21 +349,22 @@ func init() { } var fileDescriptorNoc = []byte{ - // 245 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, - 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, - 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, - 0xcf, 0xcb, 0x4f, 0x06, 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xbc, 0xfc, 0x64, - 0x29, 0x5d, 0x62, 0xf4, 0x25, 0x16, 0x64, 0x42, 0xf4, 0x48, 0x59, 0x12, 0xa3, 0x3c, 0x3d, 0xb1, - 0x24, 0xb5, 0x3c, 0xb1, 0x12, 0x46, 0x43, 0xb5, 0x9a, 0x13, 0xa3, 0xb5, 0x28, 0xbf, 0xb4, 0x24, - 0xb5, 0x08, 0x4a, 0x91, 0xa2, 0x31, 0xa9, 0x28, 0x3f, 0x3b, 0xb5, 0x08, 0x4a, 0x91, 0xe2, 0xd8, - 0x8c, 0xc4, 0xbc, 0x94, 0x9c, 0xd4, 0x22, 0x18, 0x0d, 0xd1, 0x6a, 0x74, 0x80, 0x91, 0x8b, 0xcb, - 0x37, 0x3f, 0x2f, 0xb3, 0x24, 0xbf, 0x28, 0x33, 0x2f, 0x5d, 0x48, 0x87, 0x8b, 0xd7, 0x1d, 0xe2, - 0x99, 0xe0, 0x92, 0xc4, 0x92, 0xd2, 0x62, 0x21, 0x7e, 0x3d, 0x98, 0xe7, 0x20, 0x02, 0x52, 0x1c, - 0x7a, 0xa0, 0x40, 0x72, 0x4c, 0xce, 0xd6, 0x60, 0x14, 0xd2, 0xe2, 0xe2, 0x09, 0x02, 0x7b, 0x00, - 0xaa, 0x98, 0x4f, 0x0f, 0xea, 0x1f, 0xec, 0x6a, 0x9d, 0xc0, 0x6e, 0x86, 0xab, 0x85, 0x7a, 0x01, - 0x8b, 0x5a, 0x1d, 0x2e, 0x5e, 0x0f, 0x88, 0x2b, 0xe1, 0xae, 0x80, 0xb9, 0x1a, 0x53, 0xb5, 0x93, - 0xc0, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe3, 0xb1, - 0x1c, 0x43, 0x12, 0x1b, 0xd8, 0x6f, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x34, 0xb1, 0xcc, - 0x36, 0x2c, 0x02, 0x00, 0x00, + // 261 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x90, 0xb1, 0x4e, 0xc3, 0x30, + 0x10, 0x86, 0xb1, 0x90, 0x18, 0x2c, 0x0a, 0x28, 0x03, 0x43, 0x90, 0x32, 0x33, 0xd9, 0x02, 0x06, + 0xa0, 0x63, 0x25, 0x04, 0x0b, 0x0c, 0xd0, 0x17, 0x70, 0xc2, 0xe1, 0x58, 0x6d, 0x7d, 0xd1, 0xf5, + 0xa2, 0xaa, 0x6f, 0xc2, 0x23, 0x31, 0xf2, 0x08, 0x28, 0xbc, 0x04, 0x23, 0x6a, 0x6c, 0x77, 0x6d, + 0x3a, 0x58, 0xbf, 0x74, 0xba, 0xcf, 0xba, 0xef, 0x97, 0x57, 0xd6, 0x71, 0xdd, 0x96, 0xaa, 0xc2, + 0x85, 0x9e, 0xd6, 0x30, 0xad, 0x9d, 0xb7, 0xcb, 0x17, 0xe0, 0x15, 0xd2, 0x4c, 0x33, 0x7b, 0x6d, + 0x1a, 0xa7, 0x3d, 0x56, 0x9b, 0xa7, 0x1a, 0x42, 0xc6, 0xec, 0xd0, 0x63, 0x95, 0x5f, 0x58, 0x44, + 0x3b, 0x07, 0xdd, 0x8f, 0xca, 0xf6, 0x43, 0xc3, 0xa2, 0xe1, 0x75, 0xd8, 0xc8, 0xef, 0x87, 0x7c, + 0x6a, 0x0d, 0xc3, 0xca, 0xac, 0x53, 0x46, 0xf4, 0x76, 0x08, 0x4a, 0xd8, 0x32, 0x50, 0x8c, 0x7d, + 0xc0, 0x92, 0x70, 0x06, 0x14, 0x63, 0x9f, 0x63, 0x6b, 0xe3, 0xdf, 0xe7, 0x40, 0x29, 0x03, 0x7a, + 0xfd, 0x27, 0xa4, 0x7c, 0x46, 0xef, 0x18, 0xc9, 0x79, 0x9b, 0x8d, 0xe5, 0xe8, 0x31, 0xc8, 0xbc, + 0xb1, 0xe1, 0x76, 0x99, 0x9d, 0xaa, 0x24, 0x17, 0x06, 0xf9, 0xb9, 0x0a, 0xb5, 0xa9, 0x54, 0x9b, + 0x7a, 0xd8, 0xd4, 0x76, 0x29, 0xb2, 0x3b, 0x79, 0xfc, 0xda, 0xeb, 0x44, 0xf4, 0x44, 0x45, 0xbb, + 0x21, 0xe4, 0xa4, 0xf7, 0xd9, 0x92, 0x51, 0x6f, 0x27, 0x39, 0x96, 0xa3, 0xa7, 0xe0, 0xb3, 0xbd, + 0x37, 0xf9, 0xed, 0x62, 0x27, 0x67, 0x5f, 0x5d, 0x21, 0xbe, 0xbb, 0x42, 0xfc, 0x74, 0x85, 0xf8, + 0xfc, 0x2d, 0x0e, 0xca, 0xa3, 0x7e, 0xe7, 0xe6, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x73, 0xf2, 0xf6, + 0xc0, 0x52, 0x02, 0x00, 0x00, } diff --git a/api/noc/noc.proto b/api/noc/noc.proto index a588cffd8..b78b49b02 100644 --- a/api/noc/noc.proto +++ b/api/noc/noc.proto @@ -3,7 +3,7 @@ syntax = "proto3"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; +import "google/protobuf/empty.proto"; import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; import "github.com/TheThingsNetwork/ttn/api/router/router.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; @@ -12,8 +12,8 @@ import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; package noc; service Monitoring { - rpc GatewayStatus(stream gateway.Status) returns (api.Ack); - rpc RouterStatus(stream router.Status) returns (api.Ack); - rpc BrokerStatus(stream broker.Status) returns (api.Ack); - rpc HandlerStatus(stream handler.Status) returns (api.Ack); + rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); + rpc RouterStatus(stream router.Status) returns (google.protobuf.Empty); + rpc BrokerStatus(stream broker.Status) returns (google.protobuf.Empty); + rpc HandlerStatus(stream handler.Status) returns (google.protobuf.Empty); } diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 875f39526..27128067d 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -26,8 +26,8 @@ package lorawan import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import _ "github.com/gogo/protobuf/gogoproto" -import api "github.com/TheThingsNetwork/ttn/api" import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" @@ -100,8 +100,8 @@ const _ = grpc.SupportPackageIsVersion3 type DeviceManagerClient interface { GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) - SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) - DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) } type deviceManagerClient struct { @@ -121,8 +121,8 @@ func (c *deviceManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifie return out, nil } -func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/lorawan.DeviceManager/SetDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -130,8 +130,8 @@ func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts .. return out, nil } -func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*api.Ack, error) { - out := new(api.Ack) +func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/lorawan.DeviceManager/DeleteDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -143,8 +143,8 @@ func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdenti type DeviceManagerServer interface { GetDevice(context.Context, *DeviceIdentifier) (*Device, error) - SetDevice(context.Context, *Device) (*api.Ack, error) - DeleteDevice(context.Context, *DeviceIdentifier) (*api.Ack, error) + SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) } func RegisterDeviceManagerServer(s *grpc.Server, srv DeviceManagerServer) { @@ -1158,41 +1158,42 @@ func init() { } var fileDescriptorDevice = []byte{ - // 568 bytes of a gzipped FileDescriptorProto + // 578 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xc1, 0x6e, 0xd3, 0x30, - 0x18, 0xc7, 0x09, 0x63, 0x6d, 0x62, 0x56, 0x51, 0x19, 0x0d, 0x99, 0x81, 0xba, 0x6a, 0x07, 0xe8, - 0x65, 0x89, 0xd8, 0x34, 0x38, 0x77, 0xed, 0x40, 0x15, 0x62, 0x12, 0xd9, 0x76, 0x8e, 0xdc, 0xf8, - 0x6b, 0x6b, 0xa5, 0xd8, 0x56, 0xe2, 0x24, 0xda, 0x03, 0xf0, 0x0e, 0xbc, 0x00, 0x2f, 0xc0, 0x53, - 0x70, 0xe4, 0xbc, 0xc3, 0x84, 0xc6, 0x8b, 0x20, 0xdb, 0x65, 0x83, 0x4a, 0x68, 0xa2, 0x27, 0x6e, - 0x9f, 0xff, 0xff, 0xef, 0xfb, 0x7d, 0x76, 0x1c, 0x7f, 0xa8, 0x3f, 0xe5, 0x7a, 0x56, 0x8e, 0xc3, - 0x54, 0x7e, 0x88, 0x4e, 0x67, 0x70, 0x3a, 0xe3, 0x62, 0x5a, 0x1c, 0x83, 0xae, 0x65, 0x9e, 0x45, - 0x5a, 0x8b, 0x88, 0x2a, 0x1e, 0xa9, 0x5c, 0x6a, 0x99, 0xca, 0x79, 0x34, 0x97, 0x39, 0xad, 0xa9, - 0x88, 0x18, 0x54, 0x3c, 0x85, 0xd0, 0xea, 0xb8, 0xb9, 0x50, 0xb7, 0x76, 0x7f, 0x63, 0x4d, 0xe5, - 0x54, 0xba, 0xba, 0x71, 0x39, 0xb1, 0x2b, 0xbb, 0xb0, 0x91, 0xab, 0xfb, 0x23, 0xfd, 0xaf, 0xad, - 0xa9, 0xe2, 0x2e, 0x7d, 0xe7, 0x8b, 0x87, 0xda, 0x43, 0xdb, 0x77, 0xc4, 0x40, 0x68, 0x3e, 0xe1, - 0x90, 0xe3, 0x63, 0xd4, 0xa4, 0x4a, 0x25, 0x50, 0x72, 0xe2, 0x75, 0xbd, 0xde, 0xc6, 0xe1, 0xc1, - 0xc5, 0xe5, 0xf6, 0x8b, 0xdb, 0xc0, 0xa9, 0xcc, 0x21, 0xd2, 0xe7, 0x0a, 0x8a, 0xb0, 0xaf, 0xd4, - 0xd1, 0xd9, 0x28, 0x6e, 0x50, 0xa5, 0x8e, 0x4a, 0x6e, 0x78, 0x0c, 0x2a, 0xcb, 0xbb, 0xbb, 0x12, - 0x6f, 0x08, 0x95, 0xe5, 0x31, 0xa8, 0x8e, 0x4a, 0xbe, 0xf3, 0xb1, 0x81, 0x1a, 0x6e, 0xd3, 0xff, - 0xfb, 0x56, 0xf1, 0x26, 0x32, 0xe4, 0x84, 0x33, 0xb2, 0xd6, 0xf5, 0x7a, 0x41, 0xbc, 0x4e, 0x95, - 0x1a, 0x31, 0x23, 0x9b, 0x36, 0x9c, 0x91, 0x7b, 0x4e, 0x66, 0x50, 0x8d, 0x18, 0x7e, 0x8f, 0x7c, - 0x23, 0x53, 0xc6, 0x72, 0xb2, 0x6e, 0xdb, 0xbf, 0xbc, 0xb8, 0xdc, 0xde, 0xfb, 0xb7, 0xf6, 0x7d, - 0xc6, 0xf2, 0xd8, 0x9c, 0xc2, 0x04, 0x38, 0x46, 0x81, 0xa8, 0xb3, 0xa4, 0x48, 0x32, 0x38, 0x27, - 0x8d, 0x95, 0x98, 0xc7, 0x75, 0x76, 0xf2, 0x16, 0xce, 0xe3, 0xa6, 0x70, 0x81, 0x61, 0x9a, 0x43, - 0x39, 0x66, 0x73, 0x25, 0x66, 0x5f, 0x29, 0xc7, 0xa4, 0x2e, 0xf8, 0x75, 0x91, 0x86, 0xe8, 0xaf, - 0x7a, 0x91, 0x06, 0x68, 0x3e, 0xb7, 0xe1, 0x11, 0xe4, 0x4f, 0x92, 0x54, 0xe8, 0xa4, 0x54, 0x24, - 0xe8, 0x7a, 0xbd, 0x56, 0xdc, 0x98, 0x0c, 0x84, 0x3e, 0x53, 0xf8, 0x29, 0x42, 0xce, 0x61, 0xb2, - 0x16, 0x04, 0x59, 0xcf, 0x37, 0xde, 0x50, 0xd6, 0x02, 0xef, 0xa2, 0x87, 0x8c, 0x17, 0x74, 0x3c, - 0x87, 0xc4, 0x65, 0xa5, 0x33, 0x48, 0x33, 0x72, 0xbf, 0xeb, 0xf5, 0xfc, 0xb8, 0xbd, 0xb0, 0x5e, - 0x0f, 0x84, 0x1e, 0x18, 0x1d, 0x3f, 0x47, 0xed, 0xb2, 0x80, 0x62, 0x7f, 0x2f, 0x19, 0x73, 0xed, - 0x2a, 0xc8, 0x86, 0xcd, 0x6d, 0x39, 0xfd, 0x90, 0x6b, 0x93, 0x8d, 0x0f, 0xd0, 0x23, 0x9a, 0x6a, - 0x5e, 0x51, 0xcd, 0xa5, 0x48, 0x52, 0x29, 0x0a, 0x9d, 0x53, 0x2e, 0x74, 0x41, 0x5a, 0xf6, 0x0f, - 0xd8, 0xbc, 0x71, 0x07, 0x37, 0x26, 0x7e, 0x82, 0x82, 0x39, 0x2d, 0x74, 0x52, 0x00, 0x08, 0xb2, - 0xd9, 0xf5, 0x7a, 0x6b, 0xb1, 0x6f, 0x84, 0x13, 0x00, 0xb1, 0xf7, 0xd9, 0x43, 0x2d, 0xf7, 0x0e, - 0xde, 0x51, 0x41, 0xa7, 0x90, 0xe3, 0x57, 0x28, 0x78, 0x03, 0x7a, 0xf1, 0x36, 0x1e, 0x87, 0x8b, - 0x19, 0x12, 0x2e, 0xbf, 0xf0, 0xad, 0x07, 0x4b, 0x16, 0x7e, 0x86, 0x82, 0x93, 0xeb, 0xc2, 0x65, - 0x77, 0xcb, 0x0f, 0xcd, 0xc4, 0xe8, 0xa7, 0x19, 0xde, 0x47, 0x1b, 0x43, 0x98, 0x83, 0x86, 0xdb, - 0x7b, 0x5c, 0x17, 0x1d, 0xb6, 0xbf, 0x5e, 0x75, 0xbc, 0x6f, 0x57, 0x1d, 0xef, 0xfb, 0x55, 0xc7, - 0xfb, 0xf4, 0xa3, 0x73, 0x67, 0xdc, 0xb0, 0xd3, 0x67, 0xff, 0x67, 0x00, 0x00, 0x00, 0xff, 0xff, - 0xd5, 0xda, 0x65, 0xf2, 0x29, 0x05, 0x00, 0x00, + 0x18, 0xc7, 0x09, 0x63, 0x6d, 0x63, 0x56, 0x51, 0x19, 0x6d, 0x32, 0x1d, 0xea, 0xaa, 0x5d, 0xe8, + 0x65, 0x89, 0xd8, 0x18, 0x9c, 0xbb, 0xb6, 0xa0, 0x0a, 0x31, 0x89, 0x6c, 0x3b, 0x47, 0x6e, 0xfc, + 0x35, 0xb5, 0x9a, 0xd9, 0x56, 0xe2, 0x24, 0xea, 0x03, 0xf0, 0x0e, 0x3c, 0x07, 0x6f, 0xc0, 0x8d, + 0x23, 0xe7, 0x1d, 0x26, 0x34, 0x5e, 0x04, 0x39, 0xee, 0x18, 0xaa, 0x84, 0x26, 0x7a, 0xe2, 0xf6, + 0xe5, 0xff, 0xff, 0xfb, 0xf7, 0xd9, 0x75, 0xfd, 0xa1, 0x7e, 0xcc, 0xf5, 0x2c, 0x9f, 0x78, 0x91, + 0xbc, 0xf4, 0xcf, 0x67, 0x70, 0x3e, 0xe3, 0x22, 0xce, 0x4e, 0x41, 0x97, 0x32, 0x9d, 0xfb, 0x5a, + 0x0b, 0x9f, 0x2a, 0xee, 0xab, 0x54, 0x6a, 0x19, 0xc9, 0xc4, 0x4f, 0x64, 0x4a, 0x4b, 0x2a, 0x7c, + 0x06, 0x05, 0x8f, 0xc0, 0xab, 0x74, 0x5c, 0x5f, 0xaa, 0xed, 0xdd, 0x58, 0xca, 0x38, 0x01, 0x1b, + 0x9f, 0xe4, 0x53, 0x1f, 0x2e, 0x95, 0x5e, 0xd8, 0x54, 0xfb, 0xe0, 0x8f, 0x46, 0xb1, 0x8c, 0xe5, + 0x5d, 0xca, 0x7c, 0x55, 0x1f, 0x55, 0x65, 0xe3, 0xfb, 0x5f, 0x1c, 0xd4, 0x1a, 0x56, 0x5d, 0xc6, + 0x0c, 0x84, 0xe6, 0x53, 0x0e, 0x29, 0x3e, 0x45, 0x75, 0xaa, 0x54, 0x08, 0x39, 0x27, 0x4e, 0xd7, + 0xe9, 0x6d, 0x9d, 0x1c, 0x5f, 0x5d, 0xef, 0xbd, 0xbc, 0xef, 0x04, 0x91, 0x4c, 0xc1, 0xd7, 0x0b, + 0x05, 0x99, 0xd7, 0x57, 0x6a, 0x74, 0x31, 0x0e, 0x6a, 0x54, 0xa9, 0x51, 0xce, 0x0d, 0x8f, 0x41, + 0x51, 0xf1, 0x1e, 0xae, 0xc5, 0x1b, 0x42, 0x51, 0xf1, 0x18, 0x14, 0xa3, 0x9c, 0xef, 0x7f, 0xaa, + 0xa1, 0x9a, 0xdd, 0xf4, 0xff, 0xbe, 0x55, 0xbc, 0x8d, 0x0c, 0x39, 0xe4, 0x8c, 0x6c, 0x74, 0x9d, + 0x9e, 0x1b, 0x6c, 0x52, 0xa5, 0xc6, 0xcc, 0xc8, 0xa6, 0x0d, 0x67, 0xe4, 0x91, 0x95, 0x19, 0x14, + 0x63, 0x86, 0x3f, 0xa2, 0x86, 0x91, 0x29, 0x63, 0x29, 0xd9, 0xac, 0xda, 0xbf, 0xbe, 0xba, 0xde, + 0x3b, 0xfc, 0xb7, 0xf6, 0x7d, 0xc6, 0xd2, 0xc0, 0x9c, 0xc2, 0x14, 0x38, 0x40, 0xae, 0x28, 0xe7, + 0x61, 0x16, 0xce, 0x61, 0x41, 0x6a, 0x6b, 0x31, 0x4f, 0xcb, 0xf9, 0xd9, 0x7b, 0x58, 0x04, 0x75, + 0x61, 0x0b, 0xc3, 0x34, 0x87, 0xb2, 0xcc, 0xfa, 0x5a, 0xcc, 0xbe, 0x52, 0x96, 0x49, 0x6d, 0x71, + 0x7b, 0x91, 0x86, 0xd8, 0x58, 0xf7, 0x22, 0x0d, 0xd0, 0xfc, 0xdc, 0x86, 0x47, 0x50, 0x63, 0x1a, + 0x46, 0x42, 0x87, 0xb9, 0x22, 0x6e, 0xd7, 0xe9, 0x35, 0x83, 0xda, 0x74, 0x20, 0xf4, 0x85, 0xc2, + 0xcf, 0x11, 0xb2, 0x0e, 0x93, 0xa5, 0x20, 0xa8, 0xf2, 0x1a, 0xc6, 0x1b, 0xca, 0x52, 0xe0, 0x03, + 0xf4, 0x94, 0xf1, 0x8c, 0x4e, 0x12, 0x08, 0x6d, 0x2a, 0x9a, 0x41, 0x34, 0x27, 0x8f, 0xbb, 0x4e, + 0xaf, 0x11, 0xb4, 0x96, 0xd6, 0xdb, 0x81, 0xd0, 0x03, 0xa3, 0xe3, 0x17, 0xa8, 0x95, 0x67, 0x90, + 0x1d, 0x1d, 0x86, 0x13, 0xae, 0xed, 0x0a, 0xb2, 0x55, 0x65, 0x9b, 0x56, 0x3f, 0xe1, 0xda, 0xa4, + 0xf1, 0x31, 0xda, 0xa1, 0x91, 0xe6, 0x05, 0xd5, 0x5c, 0x8a, 0x30, 0x92, 0x22, 0xd3, 0x29, 0xe5, + 0x42, 0x67, 0xa4, 0x59, 0xfd, 0x03, 0xb6, 0xef, 0xdc, 0xc1, 0x9d, 0x89, 0x77, 0x91, 0x9b, 0xd0, + 0x4c, 0x87, 0x19, 0x80, 0x20, 0xdb, 0x5d, 0xa7, 0xb7, 0x11, 0x34, 0x8c, 0x70, 0x06, 0x20, 0x0e, + 0xbf, 0x3a, 0xa8, 0x69, 0xdf, 0xc1, 0x07, 0x2a, 0x68, 0x0c, 0x29, 0x7e, 0x83, 0xdc, 0x77, 0xa0, + 0x97, 0x6f, 0xe3, 0x99, 0xb7, 0x9c, 0x18, 0xde, 0xea, 0x0b, 0x6f, 0x3f, 0x59, 0xb1, 0xf0, 0x2b, + 0xe4, 0x9e, 0xfd, 0x5e, 0xb8, 0xea, 0xb6, 0x77, 0x3c, 0x3b, 0x72, 0xbc, 0xdb, 0x61, 0xe2, 0x8d, + 0xcc, 0xc8, 0xc1, 0x7d, 0xb4, 0x35, 0x84, 0x04, 0x34, 0xdc, 0xdf, 0xf1, 0x2f, 0x88, 0x93, 0xd6, + 0xb7, 0x9b, 0x8e, 0xf3, 0xfd, 0xa6, 0xe3, 0xfc, 0xb8, 0xe9, 0x38, 0x9f, 0x7f, 0x76, 0x1e, 0x4c, + 0x6a, 0x55, 0xe2, 0xe8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4d, 0x74, 0x98, 0x46, 0x33, 0x05, + 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 802e317df..384925419 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -3,8 +3,8 @@ syntax = "proto3"; +import "google/protobuf/empty.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; package lorawan; @@ -36,6 +36,6 @@ message Device { service DeviceManager { rpc GetDevice(DeviceIdentifier) returns (Device); - rpc SetDevice(Device) returns (api.Ack); - rpc DeleteDevice(DeviceIdentifier) returns (api.Ack); + rpc SetDevice(Device) returns (google.protobuf.Empty); + rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty); } diff --git a/api/router/router.pb.go b/api/router/router.pb.go index d6f644247..b5033a954 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -24,6 +24,7 @@ package router import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import _ "github.com/gogo/protobuf/gogoproto" import api "github.com/TheThingsNetwork/ttn/api" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" @@ -288,7 +289,7 @@ func (c *routerClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOptio type Router_GatewayStatusClient interface { Send(*gateway.Status) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -300,11 +301,11 @@ func (x *routerGatewayStatusClient) Send(m *gateway.Status) error { return x.ClientStream.SendMsg(m) } -func (x *routerGatewayStatusClient) CloseAndRecv() (*api.Ack, error) { +func (x *routerGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -322,7 +323,7 @@ func (c *routerClient) Uplink(ctx context.Context, opts ...grpc.CallOption) (Rou type Router_UplinkClient interface { Send(*UplinkMessage) error - CloseAndRecv() (*api.Ack, error) + CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } @@ -334,11 +335,11 @@ func (x *routerUplinkClient) Send(m *UplinkMessage) error { return x.ClientStream.SendMsg(m) } -func (x *routerUplinkClient) CloseAndRecv() (*api.Ack, error) { +func (x *routerUplinkClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(api.Ack) + m := new(google_protobuf.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -408,7 +409,7 @@ func _Router_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) er } type Router_GatewayStatusServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*gateway.Status, error) grpc.ServerStream } @@ -417,7 +418,7 @@ type routerGatewayStatusServer struct { grpc.ServerStream } -func (x *routerGatewayStatusServer) SendAndClose(m *api.Ack) error { +func (x *routerGatewayStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -434,7 +435,7 @@ func _Router_Uplink_Handler(srv interface{}, stream grpc.ServerStream) error { } type Router_UplinkServer interface { - SendAndClose(*api.Ack) error + SendAndClose(*google_protobuf.Empty) error Recv() (*UplinkMessage, error) grpc.ServerStream } @@ -443,7 +444,7 @@ type routerUplinkServer struct { grpc.ServerStream } -func (x *routerUplinkServer) SendAndClose(m *api.Ack) error { +func (x *routerUplinkServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } @@ -2387,56 +2388,57 @@ func init() { } var fileDescriptorRouter = []byte{ - // 806 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0xc1, 0x6e, 0xeb, 0x44, - 0x14, 0xc5, 0x05, 0xf9, 0xa5, 0x37, 0x49, 0x93, 0x37, 0x2f, 0x79, 0x35, 0x01, 0x92, 0xc8, 0x0b, - 0x1a, 0x68, 0x9b, 0xd0, 0xa0, 0x0a, 0x55, 0x08, 0x44, 0x42, 0xab, 0x0a, 0x89, 0x54, 0xc8, 0x6d, - 0x37, 0x48, 0x28, 0x9a, 0x38, 0xb7, 0xae, 0x95, 0xd4, 0x63, 0x3c, 0xe3, 0xb4, 0xfd, 0x0c, 0x76, - 0x7c, 0x04, 0x1f, 0xc2, 0x82, 0x05, 0x2b, 0x16, 0x5d, 0x54, 0xa8, 0xfc, 0x02, 0x1b, 0x76, 0x28, - 0xe3, 0xb1, 0x63, 0xa7, 0x2d, 0xb4, 0xb0, 0x4a, 0xe6, 0xdc, 0x33, 0x67, 0xee, 0xb9, 0x73, 0xe7, - 0x1a, 0x3e, 0x71, 0x5c, 0x71, 0x1e, 0x8e, 0xda, 0x36, 0xbb, 0xe8, 0x9c, 0x9c, 0xe3, 0xc9, 0xb9, - 0xeb, 0x39, 0xfc, 0x08, 0xc5, 0x25, 0x0b, 0x26, 0x1d, 0x21, 0xbc, 0x0e, 0xf5, 0xdd, 0x4e, 0xc0, - 0x42, 0x81, 0x81, 0xfa, 0x69, 0xfb, 0x01, 0x13, 0x8c, 0xe8, 0xd1, 0xaa, 0xb6, 0x9d, 0x12, 0x70, - 0x98, 0xc3, 0x3a, 0x32, 0x3c, 0x0a, 0xcf, 0xe4, 0x4a, 0x2e, 0xe4, 0xbf, 0x68, 0x5b, 0x86, 0xfe, - 0xe8, 0x79, 0xd4, 0x77, 0x15, 0xfd, 0xd3, 0xa7, 0xd0, 0x25, 0xd5, 0x66, 0xd3, 0xe4, 0x8f, 0xda, - 0xbc, 0xf7, 0x94, 0xcd, 0x0e, 0x15, 0x78, 0x49, 0xaf, 0xe3, 0xdf, 0x68, 0xab, 0x49, 0xa0, 0x7c, - 0x1c, 0x8e, 0xb8, 0x1d, 0xb8, 0x23, 0xb4, 0xf0, 0xfb, 0x10, 0xb9, 0x30, 0x7f, 0xd2, 0xa0, 0x78, - 0xea, 0x4f, 0x5d, 0x6f, 0x32, 0x40, 0xce, 0xa9, 0x83, 0xc4, 0x80, 0x17, 0x3e, 0xbd, 0x9e, 0x32, - 0x3a, 0x36, 0xb4, 0xa6, 0xd6, 0x2a, 0x58, 0xf1, 0x92, 0xf4, 0xe0, 0x65, 0x9c, 0xcc, 0xf0, 0x02, - 0x05, 0x1d, 0x53, 0x41, 0x8d, 0x7c, 0x53, 0x6b, 0xe5, 0xbb, 0x95, 0x76, 0x92, 0xa6, 0x75, 0x35, - 0x50, 0x31, 0xab, 0x1c, 0x83, 0x31, 0x42, 0x3e, 0x87, 0xb2, 0xca, 0x69, 0xa1, 0x50, 0x90, 0x0a, - 0xaf, 0xda, 0x71, 0xb2, 0x29, 0x81, 0x92, 0xc2, 0x62, 0xc0, 0xfc, 0x45, 0x83, 0xd2, 0x3e, 0xbb, - 0xf4, 0x9e, 0x96, 0xf0, 0x37, 0xf0, 0x3a, 0x49, 0xd8, 0x66, 0xde, 0x99, 0xeb, 0x84, 0x01, 0x15, - 0x2e, 0xf3, 0x54, 0xd6, 0x6f, 0x2f, 0xb2, 0x3e, 0xb9, 0xfa, 0x32, 0x4d, 0xb0, 0xaa, 0x71, 0x24, - 0x03, 0x93, 0x01, 0x54, 0xe3, 0xfc, 0xb3, 0x82, 0x91, 0x09, 0x23, 0x31, 0xb1, 0xac, 0x57, 0x51, - 0x81, 0x0c, 0x6a, 0xfe, 0xb6, 0x02, 0xeb, 0xfb, 0x38, 0x73, 0x6d, 0xec, 0xd9, 0xc2, 0x9d, 0x45, - 0xd4, 0xe8, 0x66, 0xfe, 0xc1, 0xd6, 0x11, 0xbc, 0x18, 0xe3, 0x6c, 0x88, 0xa1, 0x2b, 0x7d, 0x14, - 0xfa, 0xbb, 0x37, 0xb7, 0x8d, 0x9d, 0x7f, 0xeb, 0x0b, 0x9b, 0x05, 0xd8, 0x11, 0xd7, 0x3e, 0xf2, - 0xf6, 0x3e, 0xce, 0x0e, 0x4e, 0xbf, 0xb2, 0xf4, 0x31, 0xce, 0x0e, 0x42, 0x77, 0xae, 0x47, 0x7d, - 0x5f, 0xea, 0x15, 0xfe, 0x93, 0x5e, 0xcf, 0xf7, 0xa5, 0x1e, 0xf5, 0xfd, 0xb9, 0xde, 0x83, 0x7d, - 0x52, 0xfd, 0xdf, 0x7d, 0xf2, 0xfa, 0x19, 0x7d, 0x52, 0x03, 0xe3, 0x7e, 0x5d, 0xb9, 0xcf, 0x3c, - 0x8e, 0x66, 0x00, 0x95, 0xc3, 0x88, 0x7e, 0x2c, 0xa8, 0x08, 0x79, 0x5c, 0xf0, 0x6f, 0x21, 0x1f, - 0x9f, 0x39, 0x2f, 0x85, 0x2c, 0x7a, 0x7f, 0xef, 0xe6, 0xb6, 0xb1, 0xfb, 0x8c, 0x52, 0x28, 0xe5, - 0x79, 0x39, 0x40, 0xa9, 0x1d, 0x84, 0xae, 0xf9, 0x1d, 0x54, 0x97, 0xce, 0x8c, 0x92, 0x21, 0xef, - 0xc0, 0xea, 0x94, 0x72, 0x31, 0xe4, 0x88, 0x9e, 0x3c, 0xf2, 0x4d, 0x2b, 0x37, 0x07, 0x8e, 0x11, - 0x3d, 0xb2, 0x01, 0x3a, 0x97, 0x74, 0x63, 0x45, 0x7a, 0x2f, 0x25, 0xde, 0x95, 0x8a, 0x0a, 0x9b, - 0x25, 0x28, 0x66, 0xbc, 0x98, 0x7f, 0xad, 0x80, 0x1e, 0x21, 0x64, 0x07, 0xd6, 0x62, 0x5b, 0x4a, - 0x4c, 0x93, 0x62, 0xd0, 0x9e, 0x4f, 0x24, 0x8b, 0x0a, 0xe4, 0x56, 0xd1, 0x49, 0x27, 0x47, 0x36, - 0xa0, 0x44, 0xe7, 0x75, 0xc3, 0xa1, 0xc2, 0xb9, 0xf1, 0x56, 0x53, 0x6b, 0x15, 0xad, 0xb5, 0x08, - 0x56, 0x56, 0x38, 0x31, 0x41, 0x0f, 0xe5, 0xf0, 0x50, 0x0f, 0x2a, 0xad, 0xa9, 0x22, 0xe4, 0x7d, - 0xc8, 0x8d, 0xd5, 0x8b, 0x55, 0x4d, 0x90, 0x66, 0x25, 0x31, 0xb2, 0x05, 0x79, 0x9a, 0x5c, 0x16, - 0x37, 0x1a, 0xf7, 0xa8, 0xe9, 0x30, 0xf9, 0x0c, 0x2a, 0xa9, 0xe5, 0x90, 0xda, 0x36, 0xfa, 0x02, - 0xc7, 0x46, 0xf3, 0xde, 0xb6, 0x57, 0x29, 0x5e, 0x4f, 0xd1, 0xc8, 0x36, 0x10, 0x9b, 0x79, 0x1e, - 0xda, 0x02, 0xc7, 0x0b, 0x93, 0x1f, 0x48, 0x93, 0x2f, 0x93, 0x48, 0xe2, 0x73, 0x13, 0x16, 0xe0, - 0x70, 0x14, 0xb0, 0x09, 0x06, 0xdc, 0xf8, 0x50, 0xb2, 0xcb, 0x49, 0xa0, 0x1f, 0xe1, 0xdd, 0x3f, - 0x35, 0xd0, 0x2d, 0xf9, 0x1d, 0x21, 0x5b, 0x50, 0xcc, 0x5c, 0x3b, 0x59, 0xbe, 0xc1, 0x5a, 0x4e, - 0x66, 0xda, 0xb3, 0x27, 0x2d, 0x8d, 0x6c, 0x82, 0x1e, 0x8d, 0x62, 0x52, 0x6d, 0xab, 0xcf, 0x52, - 0x66, 0x34, 0x67, 0xc8, 0x5f, 0xc0, 0x6a, 0x32, 0xcc, 0x89, 0x11, 0xf3, 0x97, 0xe7, 0x7b, 0x6d, - 0x3d, 0x8e, 0x2c, 0x4d, 0xcd, 0x8f, 0x34, 0x32, 0x80, 0x9c, 0x7a, 0x1d, 0x48, 0x1a, 0x09, 0xed, - 0xe1, 0x69, 0x54, 0x6b, 0x3e, 0x4e, 0x88, 0x3a, 0xb9, 0xfb, 0x83, 0x06, 0xc5, 0xc8, 0xf6, 0x80, - 0x7a, 0xd4, 0xc1, 0x80, 0x7c, 0xbd, 0xec, 0xfe, 0xdd, 0x58, 0xe4, 0xa1, 0xf7, 0x57, 0x7b, 0xef, - 0x91, 0xa8, 0x7a, 0x29, 0x5d, 0x58, 0x3d, 0x44, 0xa1, 0x94, 0x92, 0x02, 0x65, 0x25, 0xd6, 0xb2, - 0x70, 0xbf, 0xfc, 0xf3, 0x5d, 0x5d, 0xfb, 0xf5, 0xae, 0xae, 0xfd, 0x7e, 0x57, 0xd7, 0x7e, 0xfc, - 0xa3, 0xfe, 0xc6, 0x48, 0x97, 0xa3, 0xe6, 0xe3, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x5b, 0xce, - 0x3a, 0x9a, 0x23, 0x08, 0x00, 0x00, + // 829 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0xc7, 0x05, 0x79, 0xdb, 0xd7, 0xa6, 0xc9, 0xce, 0x36, 0x5d, 0x93, 0x85, 0x24, 0xf2, 0x81, + 0x0d, 0x7f, 0xd6, 0x61, 0x83, 0x56, 0xa8, 0x20, 0x10, 0x09, 0x8d, 0x56, 0x48, 0x64, 0x85, 0xdc, + 0xee, 0x05, 0x09, 0x45, 0x13, 0xe7, 0xad, 0x6b, 0x35, 0xf5, 0x18, 0xcf, 0x38, 0xdd, 0x7c, 0x0b, + 0xb8, 0xf1, 0x21, 0xf8, 0x20, 0x1c, 0x38, 0x70, 0xe2, 0xb0, 0x87, 0x15, 0x2a, 0x9f, 0x82, 0x1b, + 0xf2, 0x78, 0xc6, 0xb1, 0xd3, 0x74, 0x69, 0xd9, 0x93, 0x3d, 0xbf, 0xf7, 0x9b, 0xdf, 0xbc, 0xdf, + 0x9b, 0x99, 0x37, 0xf0, 0xa9, 0x1f, 0x88, 0x93, 0x64, 0xe2, 0x78, 0xec, 0xac, 0x7b, 0x7c, 0x82, + 0xc7, 0x27, 0x41, 0xe8, 0xf3, 0x27, 0x28, 0xce, 0x59, 0x7c, 0xda, 0x15, 0x22, 0xec, 0xd2, 0x28, + 0xe8, 0xc6, 0x2c, 0x11, 0x18, 0xab, 0x8f, 0x13, 0xc5, 0x4c, 0x30, 0x62, 0x66, 0xa3, 0xc6, 0x3d, + 0x9f, 0x31, 0x7f, 0x86, 0x5d, 0x89, 0x4e, 0x92, 0x67, 0x5d, 0x3c, 0x8b, 0xc4, 0x22, 0x23, 0x35, + 0x1e, 0x14, 0xd4, 0x7d, 0xe6, 0xb3, 0x25, 0x2b, 0x1d, 0xc9, 0x81, 0xfc, 0x5b, 0x43, 0xbf, 0x32, + 0x19, 0x1a, 0x05, 0x8a, 0xfe, 0xf9, 0x75, 0xe8, 0x92, 0xea, 0xb1, 0x59, 0xfe, 0xa3, 0x26, 0x1f, + 0x5c, 0x67, 0xb2, 0x4f, 0x05, 0x9e, 0xd3, 0x85, 0xfe, 0x66, 0x53, 0x6d, 0x02, 0xb5, 0xa3, 0x64, + 0xc2, 0xbd, 0x38, 0x98, 0xa0, 0x8b, 0x3f, 0x26, 0xc8, 0x85, 0xfd, 0xab, 0x01, 0x95, 0xa7, 0xd1, + 0x2c, 0x08, 0x4f, 0x47, 0xc8, 0x39, 0xf5, 0x91, 0x58, 0x70, 0x2b, 0xa2, 0x8b, 0x19, 0xa3, 0x53, + 0xcb, 0x68, 0x1b, 0x9d, 0x1d, 0x57, 0x0f, 0x49, 0x1f, 0x6e, 0xeb, 0x64, 0xc6, 0x67, 0x28, 0xe8, + 0x94, 0x0a, 0x6a, 0x6d, 0xb7, 0x8d, 0xce, 0x76, 0x6f, 0xcf, 0xc9, 0xd3, 0x74, 0x9f, 0x8f, 0x54, + 0xcc, 0xad, 0x69, 0x50, 0x23, 0xe4, 0x4b, 0xa8, 0xa9, 0x9c, 0x96, 0x0a, 0x3b, 0x52, 0xe1, 0x8e, + 0xa3, 0x93, 0x2d, 0x08, 0x54, 0x15, 0xa6, 0x01, 0xfb, 0x77, 0x03, 0xaa, 0x87, 0xec, 0x3c, 0xbc, + 0x5e, 0xc2, 0xdf, 0xc1, 0x7e, 0x9e, 0xb0, 0xc7, 0xc2, 0x67, 0x81, 0x9f, 0xc4, 0x54, 0x04, 0x2c, + 0x54, 0x59, 0xbf, 0xbd, 0xcc, 0xfa, 0xf8, 0xf9, 0xd7, 0x45, 0x82, 0x5b, 0xd7, 0x91, 0x12, 0x4c, + 0x46, 0x50, 0xd7, 0xf9, 0x97, 0x05, 0x33, 0x13, 0x56, 0x6e, 0x62, 0x55, 0x6f, 0x4f, 0x05, 0x4a, + 0xa8, 0xfd, 0xe7, 0x06, 0xdc, 0x3d, 0xc4, 0x79, 0xe0, 0x61, 0xdf, 0x13, 0xc1, 0x3c, 0xa3, 0x66, + 0x3b, 0xf3, 0x0a, 0x5b, 0x4f, 0xe0, 0xd6, 0x14, 0xe7, 0x63, 0x4c, 0x02, 0xe9, 0x63, 0x67, 0xf0, + 0xe8, 0xc5, 0xcb, 0xd6, 0xc3, 0xff, 0x3a, 0x17, 0x1e, 0x8b, 0xb1, 0x2b, 0x16, 0x11, 0x72, 0xe7, + 0x10, 0xe7, 0xc3, 0xa7, 0xdf, 0xb8, 0xe6, 0x14, 0xe7, 0xc3, 0x24, 0x48, 0xf5, 0x68, 0x14, 0x49, + 0xbd, 0x9d, 0xff, 0xa5, 0xd7, 0x8f, 0x22, 0xa9, 0x47, 0xa3, 0x28, 0xd5, 0x5b, 0x7b, 0x4e, 0xea, + 0xaf, 0x7d, 0x4e, 0xf6, 0x6f, 0x70, 0x4e, 0x1a, 0x60, 0x5d, 0xae, 0x2b, 0x8f, 0x58, 0xc8, 0xd1, + 0x8e, 0x61, 0xef, 0x71, 0x46, 0x3f, 0x12, 0x54, 0x24, 0x5c, 0x17, 0xfc, 0x7b, 0xd8, 0xd6, 0x6b, + 0xa6, 0xa5, 0x90, 0x45, 0x1f, 0x1c, 0xbc, 0x78, 0xd9, 0x7a, 0x74, 0x83, 0x52, 0x28, 0xe5, 0xb4, + 0x1c, 0xa0, 0xd4, 0x86, 0x49, 0x60, 0xff, 0x00, 0xf5, 0x95, 0x35, 0xb3, 0x64, 0xc8, 0x3d, 0xd8, + 0x9a, 0x51, 0x2e, 0xc6, 0x1c, 0x31, 0x94, 0x4b, 0xbe, 0xe9, 0x6e, 0xa6, 0xc0, 0x11, 0x62, 0x48, + 0xee, 0x83, 0xc9, 0x25, 0xdd, 0xda, 0x90, 0xde, 0xab, 0xb9, 0x77, 0xa5, 0xa2, 0xc2, 0x76, 0x15, + 0x2a, 0x25, 0x2f, 0xf6, 0x3f, 0x1b, 0x60, 0x66, 0x08, 0x79, 0x08, 0xbb, 0xda, 0x96, 0x12, 0x33, + 0xa4, 0x18, 0x38, 0x69, 0x47, 0x72, 0xa9, 0x40, 0xee, 0x56, 0xfc, 0x62, 0x72, 0xe4, 0x3e, 0x54, + 0x69, 0x5a, 0x37, 0x1c, 0x2b, 0x9c, 0x5b, 0x6f, 0xb5, 0x8d, 0x4e, 0xc5, 0xdd, 0xcd, 0x60, 0x65, + 0x85, 0x13, 0x1b, 0xcc, 0x44, 0x36, 0x0f, 0x75, 0xa1, 0x8a, 0x9a, 0x2a, 0x42, 0xde, 0x83, 0xcd, + 0xa9, 0xba, 0xb1, 0xea, 0x10, 0x14, 0x59, 0x79, 0x8c, 0x7c, 0x04, 0xdb, 0x34, 0xdf, 0x2c, 0x6e, + 0xb5, 0x2e, 0x51, 0x8b, 0x61, 0xf2, 0x05, 0xec, 0x15, 0x86, 0x63, 0xea, 0x79, 0x18, 0x09, 0x9c, + 0x5a, 0xed, 0x4b, 0xd3, 0xee, 0x14, 0x78, 0x7d, 0x45, 0x23, 0x0f, 0x80, 0x78, 0x2c, 0x0c, 0xd1, + 0x13, 0x38, 0x5d, 0x9a, 0x7c, 0x5f, 0x9a, 0xbc, 0x9d, 0x47, 0x72, 0x9f, 0x1f, 0xc2, 0x12, 0x1c, + 0x4f, 0x62, 0x76, 0x8a, 0x31, 0xb7, 0x3e, 0x90, 0xec, 0x5a, 0x1e, 0x18, 0x64, 0x78, 0xef, 0xa7, + 0x0d, 0x30, 0x5d, 0xf9, 0xc8, 0x90, 0xcf, 0xa0, 0x52, 0xda, 0x76, 0xb2, 0xba, 0x83, 0x8d, 0x7d, + 0x27, 0x7b, 0x87, 0x1c, 0xfd, 0xc2, 0x38, 0xc3, 0xf4, 0x1d, 0xea, 0x18, 0xe4, 0x00, 0xcc, 0xac, + 0x31, 0x93, 0xba, 0xa3, 0x5e, 0xb0, 0x52, 0xa3, 0x7e, 0xc5, 0xd4, 0xaf, 0x60, 0x2b, 0x6f, 0xf4, + 0xc4, 0xd2, 0xb3, 0x57, 0x7b, 0x7f, 0xe3, 0xae, 0x8e, 0xac, 0x74, 0xd4, 0x8f, 0x0d, 0x32, 0x82, + 0x4d, 0x75, 0x73, 0x90, 0xb4, 0x72, 0xda, 0xfa, 0x4e, 0xd5, 0x68, 0x5f, 0x4d, 0xc8, 0x4e, 0x79, + 0xef, 0x67, 0x03, 0x2a, 0x59, 0x49, 0x46, 0x34, 0xa4, 0x3e, 0xc6, 0xe4, 0xdb, 0xd5, 0xca, 0xbc, + 0xa3, 0x45, 0xd6, 0xdd, 0xcd, 0xc6, 0xbb, 0x57, 0x44, 0xd5, 0x2d, 0xea, 0xc1, 0xd6, 0x63, 0x14, + 0x4a, 0x29, 0x2f, 0x57, 0x59, 0x62, 0xb7, 0x0c, 0x0f, 0x6a, 0xbf, 0x5d, 0x34, 0x8d, 0x3f, 0x2e, + 0x9a, 0xc6, 0x5f, 0x17, 0x4d, 0xe3, 0x97, 0xbf, 0x9b, 0x6f, 0x4c, 0x4c, 0x59, 0xc8, 0x4f, 0xfe, + 0x0d, 0x00, 0x00, 0xff, 0xff, 0x68, 0x58, 0x58, 0x45, 0x5c, 0x08, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index 23061c318..c33ed19e2 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -3,8 +3,8 @@ syntax = "proto3"; +import "google/protobuf/empty.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; - import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; @@ -45,10 +45,10 @@ message DeviceActivationResponse { // The Router service provides pure network functionality service Router { // Gateway streams status messages to Router - rpc GatewayStatus(stream gateway.Status) returns (api.Ack); + rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); // Gateway streams uplink messages to Router - rpc Uplink(stream UplinkMessage) returns (api.Ack); + rpc Uplink(stream UplinkMessage) returns (google.protobuf.Empty); // Gateway subscribes to downlink messages from Router rpc Subscribe(SubscribeRequest) returns (stream DownlinkMessage); diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 8ae60a67a..246b99ea7 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -4,11 +4,11 @@ package broker import ( - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" + "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" @@ -29,7 +29,7 @@ func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentif return res, nil } -func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*api.Ack, error) { +func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*empty.Empty, error) { res, err := b.deviceManager.SetDevice(ctx, in) if err != nil { return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not set device")) @@ -37,7 +37,7 @@ func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*api return res, nil } -func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*api.Ack, error) { +func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*empty.Empty, error) { res, err := b.deviceManager.DeleteDevice(ctx, in) if err != nil { return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not delete device")) @@ -45,7 +45,7 @@ func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIden return res, nil } -func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*api.Ack, error) { +func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*empty.Empty, error) { claims, err := b.broker.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, core.BuildGRPCError(core.FromGRPCError(err)) @@ -60,7 +60,7 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if err != nil { return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Discovery did not add appID")) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesRequest) (*lorawan.PrefixesResponse, error) { diff --git a/core/broker/server.go b/core/broker/server.go index 79fff7a11..b42ffdcce 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -6,9 +6,9 @@ package broker import ( "io" - pb_api "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/core" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -98,7 +98,7 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { for { downlink, err := stream.Recv() if err == io.EOF { - return stream.SendAndClose(&pb_api.Ack{}) + return stream.SendAndClose(&empty.Empty{}) } if err != nil { return err diff --git a/core/discovery/server.go b/core/discovery/server.go index 889049788..1c4e1769d 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -4,9 +4,9 @@ package discovery import ( - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -72,7 +72,7 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me return nil } -func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { +func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*empty.Empty, error) { claims, err := d.discovery.ValidateTTNAuthContext(ctx) if err != nil { return nil, err @@ -93,10 +93,10 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc if err != nil { return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { +func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { err := d.checkMetadataEditRights(ctx, in) if err != nil { return nil, err @@ -105,10 +105,10 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques if err != nil { return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { +func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { err := d.checkMetadataEditRights(ctx, in) if err != nil { return nil, err @@ -117,7 +117,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq if err != nil { return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index 1d0fe5d81..2eb5b1ce1 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -9,8 +9,8 @@ import ( "net" "time" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" ) @@ -52,10 +52,10 @@ type mockDiscoveryServer struct { get uint } -func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*api.Ack, error) { +func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Announcement) (*empty.Empty, error) { d.announce++ <-time.After(5 * time.Millisecond) - return &api.Ack{}, nil + return &empty.Empty{}, nil } func (d *mockDiscoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { d.discover++ @@ -69,11 +69,11 @@ func (d *mockDiscoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb. <-time.After(5 * time.Millisecond) return &pb.Announcement{}, nil } -func (d *mockDiscoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { +func (d *mockDiscoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { <-time.After(5 * time.Millisecond) - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (d *mockDiscoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*api.Ack, error) { +func (d *mockDiscoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataRequest) (*empty.Empty, error) { <-time.After(5 * time.Millisecond) - return &api.Ack{}, nil + return &empty.Empty{}, nil } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 8f5c2883b..cf9b9d0fd 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -6,7 +6,6 @@ package handler import ( "fmt" - "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -14,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/golang/protobuf/ptypes/empty" "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" @@ -82,7 +82,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) }, nil } -func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack, error) { +func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { dev, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) if err != nil && core.GetErrType(err) != core.NotFound { return nil, core.BuildGRPCError(err) @@ -161,10 +161,10 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*api.Ack return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*api.Ack, error) { +func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { dev, err := h.getDevice(ctx, in) if err != nil { return nil, core.BuildGRPCError(err) @@ -177,7 +177,7 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifi if err != nil { return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.DeviceList, error) { @@ -251,7 +251,7 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI }, nil } -func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*api.Ack, error) { +func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) if err != nil && core.GetErrType(err) != core.NotFound { return nil, core.BuildGRPCError(err) @@ -287,11 +287,11 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Broker") } - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*api.Ack, error) { +func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { _, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) if err != nil { return nil, core.BuildGRPCError(err) @@ -312,10 +312,10 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*api.Ack, error) { +func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { _, err := h.getApplication(ctx, in) if err != nil { return nil, core.BuildGRPCError(err) @@ -355,7 +355,7 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati h.handler.Ctx.WithField("AppID", in.AppId).WithError(core.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") } - return &api.Ack{}, nil + return &empty.Empty{}, nil } func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 99d551427..32ae1d4c9 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -7,11 +7,11 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -65,7 +65,7 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev }, nil } -func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*api.Ack, error) { +func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*empty.Empty, error) { _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) if err != nil && core.GetErrType(err) != core.NotFound { return nil, core.BuildGRPCError(err) @@ -99,10 +99,10 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } -func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*api.Ack, error) { +func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*empty.Empty, error) { _, err := n.getDevice(ctx, in) if err != nil { return nil, core.BuildGRPCError(err) @@ -111,7 +111,7 @@ func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan. if err != nil { return nil, core.BuildGRPCError(err) } - return &api.Ack{}, nil + return &empty.Empty{}, nil } func (n *networkServerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { diff --git a/core/router/server.go b/core/router/server.go index 41841fdeb..477cb9aa8 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -6,10 +6,10 @@ package router import ( "io" - api "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -60,7 +60,7 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { for { status, err := stream.Recv() if err == io.EOF { - return stream.SendAndClose(&api.Ack{}) + return stream.SendAndClose(&empty.Empty{}) } if err != nil { return err @@ -81,7 +81,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { for { uplink, err := stream.Recv() if err == io.EOF { - return stream.SendAndClose(&api.Ack{}) + return stream.SendAndClose(&empty.Empty{}) } if err != nil { return err From 7f0e5b941cf989775869d968e46970355e6653f7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Sep 2016 14:33:32 +0200 Subject: [PATCH 1699/2266] Add MIC challenge between Broker and Handler for Activations --- api/broker/broker.pb.go | 805 ++++++++++++++++++++++++---- api/broker/broker.proto | 18 +- api/broker/validation.go | 11 + api/handler/handler.pb.go | 138 +++-- api/handler/handler.proto | 1 + core/broker/activation.go | 111 +++- core/broker/server.go | 38 +- core/component.go | 10 +- core/errors.go | 5 +- core/handler/activation.go | 36 ++ core/handler/handler.go | 1 + core/handler/server.go | 15 + core/networkserver/networkserver.go | 6 + 13 files changed, 1003 insertions(+), 192 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index c89d643c7..9c03a5965 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -16,6 +16,8 @@ DeduplicatedUplinkMessage DeviceActivationRequest DeduplicatedDeviceActivationRequest + ActivationChallengeRequest + ActivationChallengeResponse SubscribeRequest StatusRequest Status @@ -83,9 +85,11 @@ func (m *DownlinkOption) GetGatewayConfig() *gateway.TxConfiguration { // received from the Router type UplinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - // NOTE: For LoRaWAN, the Router doesn't know the DevEUI and AppEUI + // NOTE: For LoRaWAN, the Router doesn't know the DevEUI/ID and AppEUI/ID DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` DownlinkOptions []*DownlinkOption `protobuf:"bytes,31,rep,name=downlink_options,json=downlinkOptions" json:"downlink_options,omitempty"` @@ -122,6 +126,8 @@ type DownlinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` DownlinkOption *DownlinkOption `protobuf:"bytes,21,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` } @@ -287,6 +293,30 @@ func (m *DeduplicatedDeviceActivationRequest) GetResponseTemplate() *DeviceActiv return nil } +type ActivationChallengeRequest struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + DevId string `protobuf:"bytes,14,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` +} + +func (m *ActivationChallengeRequest) Reset() { *m = ActivationChallengeRequest{} } +func (m *ActivationChallengeRequest) String() string { return proto.CompactTextString(m) } +func (*ActivationChallengeRequest) ProtoMessage() {} +func (*ActivationChallengeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{7} } + +type ActivationChallengeResponse struct { + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (m *ActivationChallengeResponse) Reset() { *m = ActivationChallengeResponse{} } +func (m *ActivationChallengeResponse) String() string { return proto.CompactTextString(m) } +func (*ActivationChallengeResponse) ProtoMessage() {} +func (*ActivationChallengeResponse) Descriptor() ([]byte, []int) { + return fileDescriptorBroker, []int{8} +} + // message SubscribeRequest is used by a Handler to subscribe to uplink messages type SubscribeRequest struct { } @@ -294,7 +324,7 @@ type SubscribeRequest struct { func (m *SubscribeRequest) Reset() { *m = SubscribeRequest{} } func (m *SubscribeRequest) String() string { return proto.CompactTextString(m) } func (*SubscribeRequest) ProtoMessage() {} -func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{7} } +func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{9} } // message StatusRequest is used to request the status of this Broker type StatusRequest struct { @@ -303,7 +333,7 @@ type StatusRequest struct { func (m *StatusRequest) Reset() { *m = StatusRequest{} } func (m *StatusRequest) String() string { return proto.CompactTextString(m) } func (*StatusRequest) ProtoMessage() {} -func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{8} } +func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{10} } // message Status is the response to the StatusRequest type Status struct { @@ -326,7 +356,7 @@ type Status struct { func (m *Status) Reset() { *m = Status{} } func (m *Status) String() string { return proto.CompactTextString(m) } func (*Status) ProtoMessage() {} -func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{9} } +func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{11} } func (m *Status) GetUplink() *api.Rates { if m != nil { @@ -386,7 +416,7 @@ func (m *ApplicationHandlerRegistration) Reset() { *m = ApplicationHandl func (m *ApplicationHandlerRegistration) String() string { return proto.CompactTextString(m) } func (*ApplicationHandlerRegistration) ProtoMessage() {} func (*ApplicationHandlerRegistration) Descriptor() ([]byte, []int) { - return fileDescriptorBroker, []int{10} + return fileDescriptorBroker, []int{12} } func init() { @@ -397,6 +427,8 @@ func init() { proto.RegisterType((*DeduplicatedUplinkMessage)(nil), "broker.DeduplicatedUplinkMessage") proto.RegisterType((*DeviceActivationRequest)(nil), "broker.DeviceActivationRequest") proto.RegisterType((*DeduplicatedDeviceActivationRequest)(nil), "broker.DeduplicatedDeviceActivationRequest") + proto.RegisterType((*ActivationChallengeRequest)(nil), "broker.ActivationChallengeRequest") + proto.RegisterType((*ActivationChallengeResponse)(nil), "broker.ActivationChallengeResponse") proto.RegisterType((*SubscribeRequest)(nil), "broker.SubscribeRequest") proto.RegisterType((*StatusRequest)(nil), "broker.StatusRequest") proto.RegisterType((*Status)(nil), "broker.Status") @@ -882,6 +914,18 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { } i += n5 } + if len(m.AppId) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.DevId) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } if m.ProtocolMetadata != nil { data[i] = 0xaa i++ @@ -964,6 +1008,18 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { } i += n9 } + if len(m.AppId) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.DevId) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } if m.DownlinkOption != nil { data[i] = 0xaa i++ @@ -1321,6 +1377,86 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error return i, nil } +func (m *ActivationChallengeRequest) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ActivationChallengeRequest) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + if m.DevEui != nil { + data[i] = 0x5a + i++ + i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) + n26, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n26 + } + if m.AppEui != nil { + data[i] = 0x62 + i++ + i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) + n27, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n27 + } + if len(m.AppId) > 0 { + data[i] = 0x6a + i++ + i = encodeVarintBroker(data, i, uint64(len(m.AppId))) + i += copy(data[i:], m.AppId) + } + if len(m.DevId) > 0 { + data[i] = 0x72 + i++ + i = encodeVarintBroker(data, i, uint64(len(m.DevId))) + i += copy(data[i:], m.DevId) + } + return i, nil +} + +func (m *ActivationChallengeResponse) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ActivationChallengeResponse) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Payload) > 0 { + data[i] = 0xa + i++ + i = encodeVarintBroker(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + return i, nil +} + func (m *SubscribeRequest) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -1376,31 +1512,31 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n26, err := m.Uplink.MarshalTo(data[i:]) + n28, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n28 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n27, err := m.UplinkUnique.MarshalTo(data[i:]) + n29, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n27 + i += n29 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n28, err := m.Downlink.MarshalTo(data[i:]) + n30, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n28 + i += n30 } if m.Activations != nil { data[i] = 0xaa @@ -1408,11 +1544,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n29, err := m.Activations.MarshalTo(data[i:]) + n31, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n29 + i += n31 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1420,11 +1556,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n30, err := m.ActivationsUnique.MarshalTo(data[i:]) + n32, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n30 + i += n32 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1432,11 +1568,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n31, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n33, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n31 + i += n33 } if m.Deduplication != nil { data[i] = 0xfa @@ -1444,11 +1580,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n32, err := m.Deduplication.MarshalTo(data[i:]) + n34, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n32 + i += n34 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1567,6 +1703,14 @@ func (m *UplinkMessage) Size() (n int) { l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() n += 2 + l + sovBroker(uint64(l)) @@ -1599,6 +1743,14 @@ func (m *DownlinkMessage) Size() (n int) { l = m.AppEui.Size() n += 1 + l + sovBroker(uint64(l)) } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } if m.DownlinkOption != nil { l = m.DownlinkOption.Size() n += 2 + l + sovBroker(uint64(l)) @@ -1746,6 +1898,42 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { return n } +func (m *ActivationChallengeRequest) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.AppId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + l = len(m.DevId) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + +func (m *ActivationChallengeResponse) Size() (n int) { + var l int + _ = l + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovBroker(uint64(l)) + } + return n +} + func (m *SubscribeRequest) Size() (n int) { var l int _ = l @@ -2164,6 +2352,64 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) @@ -2406,11 +2652,11 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 21: + case 13: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) } - var msglen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBroker @@ -2420,30 +2666,88 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - msglen |= (int(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } - if msglen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthBroker } - postIndex := iNdEx + msglen + postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } - if m.DownlinkOption == nil { - m.DownlinkOption = &DownlinkOption{} - } - if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } + m.AppId = string(data[iNdEx:postIndex]) iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) - if err != nil { - return err + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 21: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.DownlinkOption == nil { + m.DownlinkOption = &DownlinkOption{} + } + if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err } if skippy < 0 { return ErrInvalidLengthBroker @@ -3520,6 +3824,290 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { } return nil } +func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationChallengeRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationChallengeRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AppId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DevId = string(data[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationChallengeResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationChallengeResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipBroker(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthBroker + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *SubscribeRequest) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -4157,76 +4745,77 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1121 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0xcd, 0x6f, 0x1b, 0x45, - 0x14, 0x67, 0xe3, 0xc6, 0x49, 0x9e, 0xe3, 0x8f, 0x4c, 0xf3, 0xb1, 0x35, 0x60, 0x1b, 0x23, 0x55, - 0xe6, 0xa3, 0x76, 0x31, 0x2a, 0x28, 0xe2, 0x23, 0x72, 0x48, 0x54, 0x82, 0xe4, 0x50, 0x6d, 0x13, - 0x0e, 0x08, 0xc9, 0x1a, 0xef, 0xbe, 0x6c, 0x46, 0xb1, 0x77, 0xb7, 0x3b, 0xb3, 0x4e, 0xf3, 0x3f, - 0x20, 0x71, 0xe5, 0x0a, 0xff, 0x09, 0xe2, 0xc2, 0x0d, 0xce, 0x3d, 0x54, 0x28, 0x5c, 0xb8, 0xf0, - 0x3f, 0xa0, 0x9d, 0x9d, 0x5d, 0xaf, 0xe3, 0xb8, 0x4d, 0xa1, 0x1c, 0x5a, 0x71, 0xf2, 0xce, 0x7b, - 0xbf, 0xf7, 0xdb, 0xf1, 0xef, 0x7d, 0xcc, 0x2c, 0x7c, 0x68, 0x33, 0x71, 0x1c, 0xf4, 0x9b, 0xa6, - 0x3b, 0x6c, 0x1d, 0x1c, 0xe3, 0xc1, 0x31, 0x73, 0x6c, 0xbe, 0x8f, 0xe2, 0xd4, 0xf5, 0x4f, 0x5a, - 0x42, 0x38, 0x2d, 0xea, 0xb1, 0x56, 0xdf, 0x77, 0x4f, 0xd0, 0x57, 0x3f, 0x4d, 0xcf, 0x77, 0x85, - 0x4b, 0xb2, 0xd1, 0xaa, 0xfc, 0xaa, 0xed, 0xba, 0xf6, 0x00, 0x5b, 0xd2, 0xda, 0x0f, 0x8e, 0x5a, - 0x38, 0xf4, 0xc4, 0x59, 0x04, 0x2a, 0xdf, 0x4a, 0xb1, 0xdb, 0xae, 0xed, 0x8e, 0x51, 0xe1, 0x4a, - 0x2e, 0xe4, 0xd3, 0x25, 0xf0, 0x99, 0x9b, 0xa1, 0x1e, 0x53, 0xf0, 0x8f, 0xae, 0x02, 0x97, 0x50, - 0xd3, 0x1d, 0x24, 0x0f, 0x2a, 0x78, 0xf3, 0x2a, 0xc1, 0x36, 0x15, 0x78, 0x4a, 0xcf, 0xe2, 0xdf, - 0x28, 0xb4, 0xfe, 0xf3, 0x1c, 0x14, 0x76, 0xdc, 0x53, 0x67, 0xc0, 0x9c, 0x93, 0x2f, 0x3d, 0xc1, - 0x5c, 0x87, 0x54, 0x00, 0x98, 0x85, 0x8e, 0x60, 0x47, 0x0c, 0x7d, 0x5d, 0xab, 0x69, 0x8d, 0x25, - 0x23, 0x65, 0x21, 0x5f, 0x43, 0x4e, 0x71, 0xf4, 0x30, 0x60, 0xfa, 0x5c, 0x4d, 0x6b, 0x2c, 0x6f, - 0x6f, 0x3e, 0x7a, 0x5c, 0xbd, 0xf3, 0xb4, 0x6d, 0x98, 0xae, 0x8f, 0x2d, 0x71, 0xe6, 0x21, 0x6f, - 0xde, 0x8d, 0x18, 0x76, 0x0f, 0xf7, 0x0c, 0x50, 0x6c, 0xbb, 0x01, 0x23, 0xab, 0x30, 0xcf, 0x43, - 0x94, 0x9e, 0xa9, 0x69, 0x8d, 0xbc, 0x11, 0x2d, 0x48, 0x19, 0x16, 0x2d, 0xa4, 0xd6, 0x80, 0x39, - 0xa8, 0x5f, 0xab, 0x69, 0x8d, 0x8c, 0x91, 0xac, 0xc9, 0x36, 0x14, 0x63, 0x35, 0x7a, 0xa6, 0xeb, - 0x1c, 0x31, 0x5b, 0x9f, 0xaf, 0x69, 0x8d, 0x5c, 0xfb, 0x46, 0x33, 0x51, 0xe9, 0xe0, 0xe1, 0x67, - 0xd2, 0x13, 0xf8, 0x34, 0xfc, 0x87, 0x46, 0x21, 0xf6, 0x44, 0x66, 0xb2, 0x05, 0x85, 0xf8, 0x1f, - 0x29, 0x8a, 0xac, 0xa4, 0xd0, 0x9b, 0xb1, 0x58, 0x17, 0x19, 0xf2, 0xca, 0x11, 0x59, 0xeb, 0xdf, - 0x65, 0x20, 0x7f, 0xe8, 0x85, 0x1a, 0x76, 0x91, 0x73, 0x6a, 0x23, 0xd1, 0x61, 0xc1, 0xa3, 0x67, - 0x03, 0x97, 0x5a, 0x52, 0xc1, 0x65, 0x23, 0x5e, 0x92, 0x7d, 0x58, 0xb0, 0x70, 0x24, 0xa5, 0xcb, - 0x49, 0xe9, 0xee, 0x3c, 0x7a, 0x5c, 0x7d, 0xef, 0x19, 0xa4, 0xdb, 0xc1, 0x51, 0x28, 0x5b, 0xd6, - 0xc2, 0x51, 0x28, 0xd9, 0x3e, 0x2c, 0x50, 0xcf, 0x93, 0x7c, 0xcb, 0xff, 0x88, 0xaf, 0xe3, 0x79, - 0x92, 0x8f, 0x7a, 0x5e, 0xc8, 0xd7, 0x81, 0x95, 0x44, 0xd0, 0x21, 0x0a, 0x6a, 0x51, 0x41, 0xf5, - 0x35, 0xa9, 0xc7, 0xea, 0x58, 0x52, 0xe3, 0x61, 0x57, 0xf9, 0x8c, 0x52, 0x6c, 0x8c, 0x2d, 0xe4, - 0x53, 0x28, 0xc5, 0x7a, 0x26, 0x0c, 0xeb, 0x92, 0xe1, 0x7a, 0xa2, 0x68, 0x8a, 0xa0, 0xa8, 0x6c, - 0x49, 0x7c, 0x07, 0x4a, 0x96, 0xaa, 0xc9, 0x9e, 0x2b, 0x8b, 0x92, 0xeb, 0xd5, 0x5a, 0xa6, 0x91, - 0x6b, 0xaf, 0x37, 0x55, 0xe3, 0x4e, 0xd6, 0xac, 0x51, 0xb4, 0x26, 0xd6, 0xbc, 0xfe, 0xed, 0x1c, - 0x14, 0x63, 0xcc, 0x8b, 0x9f, 0x93, 0x2d, 0x28, 0x5e, 0x10, 0x44, 0x65, 0x64, 0x96, 0x1e, 0x85, - 0x49, 0x3d, 0xea, 0x01, 0xe8, 0x3b, 0x38, 0x62, 0x26, 0x76, 0x4c, 0xc1, 0x46, 0x51, 0x0d, 0x23, - 0xf7, 0x5c, 0x87, 0x3f, 0x49, 0x96, 0x4b, 0x5e, 0x9b, 0x7b, 0xa6, 0xd7, 0xfe, 0x95, 0x81, 0x1b, - 0x3b, 0x68, 0x05, 0xde, 0x80, 0x99, 0x54, 0xa0, 0xf5, 0xb2, 0xf4, 0xc8, 0x1a, 0x84, 0x4f, 0x3d, - 0x66, 0xe9, 0x79, 0x39, 0x1e, 0xe7, 0xa9, 0xe7, 0xed, 0x59, 0xa1, 0x39, 0xdc, 0x36, 0xb3, 0xf4, - 0x42, 0x64, 0xb6, 0x70, 0xb4, 0x67, 0xfd, 0x77, 0x1d, 0x95, 0xb9, 0x72, 0x47, 0x55, 0x21, 0xc7, - 0xd1, 0x1f, 0xa1, 0xdf, 0x13, 0x6c, 0x88, 0xfa, 0x86, 0x1c, 0xa2, 0x10, 0x99, 0x0e, 0xd8, 0x10, - 0xc9, 0x0e, 0xac, 0xf8, 0xaa, 0x20, 0x7a, 0x02, 0x87, 0xde, 0x80, 0x0a, 0xd4, 0xab, 0x72, 0x8f, - 0x1b, 0x17, 0x93, 0xad, 0xf2, 0x67, 0x94, 0xe2, 0x88, 0x03, 0x15, 0x50, 0xff, 0x33, 0x03, 0x1b, - 0xd3, 0x75, 0xf6, 0x20, 0x40, 0x2e, 0xfe, 0x9f, 0x88, 0xff, 0x66, 0x22, 0x76, 0xe1, 0x3a, 0x4d, - 0x14, 0x1d, 0x53, 0x6c, 0x48, 0x8a, 0xd7, 0xc6, 0x9b, 0x18, 0xcb, 0x9e, 0x70, 0x11, 0x3a, 0x65, - 0x7b, 0x1e, 0x03, 0xf6, 0xd7, 0x6b, 0xf0, 0x66, 0xba, 0xb5, 0x5f, 0xbe, 0xb4, 0xbf, 0x70, 0x4d, - 0xfe, 0x9c, 0x8b, 0xe4, 0xc2, 0xcc, 0xd0, 0xa7, 0x66, 0x46, 0x77, 0xf6, 0xcc, 0xa8, 0x25, 0x65, - 0x34, 0xe3, 0xd4, 0xb9, 0x64, 0x78, 0x10, 0x28, 0xdd, 0x0f, 0xfa, 0xdc, 0xf4, 0x59, 0x1f, 0x55, - 0xf5, 0xd4, 0x8b, 0x90, 0xbf, 0x2f, 0xa8, 0x08, 0x78, 0x6c, 0xf8, 0x29, 0x03, 0xd9, 0xc8, 0x42, - 0xea, 0x90, 0x0d, 0xe4, 0x79, 0x22, 0x0b, 0x2b, 0xd7, 0x86, 0x66, 0x78, 0x9d, 0x36, 0xa8, 0x40, - 0x6e, 0x28, 0x0f, 0x69, 0x41, 0x3e, 0x7a, 0xea, 0x05, 0x0e, 0x7b, 0x10, 0xa0, 0xbc, 0xad, 0x4e, - 0x42, 0x97, 0x23, 0xc0, 0xa1, 0xf4, 0x93, 0x9b, 0xb0, 0x18, 0x57, 0xba, 0x3a, 0xeb, 0xd2, 0xd8, - 0xc4, 0x47, 0xde, 0x85, 0xdc, 0x58, 0x32, 0xae, 0x12, 0x9d, 0x86, 0xa6, 0xdd, 0x64, 0x13, 0x52, - 0x02, 0xf3, 0x78, 0x2f, 0xeb, 0x53, 0x41, 0x2b, 0x29, 0x94, 0xda, 0xd0, 0x27, 0xb0, 0x9a, 0x0e, - 0xa5, 0xa6, 0x89, 0x9e, 0x40, 0x4b, 0x65, 0x35, 0x1d, 0x9c, 0x4a, 0x3e, 0xef, 0x28, 0x18, 0xf9, - 0x00, 0xf2, 0x56, 0xd2, 0xa5, 0xe1, 0x01, 0x1e, 0xe5, 0xa7, 0x24, 0xe3, 0xee, 0xa1, 0x6f, 0x86, - 0xd7, 0xfa, 0x01, 0x72, 0x63, 0x12, 0x46, 0xde, 0x81, 0x15, 0xd3, 0x75, 0x1c, 0x34, 0x05, 0x5a, - 0x3d, 0xdf, 0x0d, 0x04, 0xfa, 0x5c, 0x7f, 0x4b, 0x5e, 0xca, 0x4b, 0x89, 0xc3, 0x88, 0xec, 0xe4, - 0x16, 0x90, 0x31, 0xf8, 0x98, 0x3a, 0xd6, 0x20, 0x44, 0xbf, 0x2d, 0xd1, 0x63, 0x9a, 0xcf, 0x95, - 0xa3, 0xfe, 0x15, 0x54, 0x3a, 0x5e, 0xf2, 0x2a, 0x65, 0x36, 0xd0, 0x66, 0x5c, 0x44, 0xd7, 0xeb, - 0x54, 0xeb, 0x69, 0xe9, 0xd6, 0x7b, 0x1d, 0x40, 0xb1, 0x87, 0xae, 0x39, 0xe9, 0x5a, 0x52, 0x96, - 0x3d, 0xab, 0xfd, 0xe3, 0x1c, 0x64, 0xb7, 0x65, 0xd9, 0x91, 0x2d, 0x58, 0xea, 0x70, 0xee, 0x9a, - 0x8c, 0x0a, 0x24, 0x6b, 0x71, 0x31, 0x4e, 0x5c, 0x3f, 0xca, 0xb3, 0xce, 0xb5, 0x86, 0x76, 0x5b, - 0x23, 0x5f, 0xc0, 0x52, 0x52, 0x8c, 0x44, 0x8f, 0x91, 0x17, 0xeb, 0xb3, 0xfc, 0xc6, 0xb8, 0xce, - 0x67, 0xdc, 0x72, 0x6e, 0x6b, 0xe4, 0x63, 0x58, 0xb8, 0x17, 0xf4, 0x07, 0x8c, 0x1f, 0x93, 0x59, - 0xef, 0x2c, 0xaf, 0x37, 0xa3, 0x6f, 0xcf, 0x66, 0xfc, 0x55, 0xd9, 0xdc, 0x0d, 0xbf, 0x3d, 0x1b, - 0x1a, 0xe9, 0xc2, 0xa2, 0x6a, 0x1f, 0x24, 0xd5, 0xd9, 0x6d, 0x15, 0xed, 0xe7, 0xa9, 0x7d, 0xd7, - 0xfe, 0x41, 0x83, 0x7c, 0x24, 0x52, 0x97, 0x3a, 0xd4, 0x46, 0x9f, 0x7c, 0x03, 0xe5, 0x48, 0x7c, - 0xf4, 0xa7, 0xd3, 0x42, 0x6e, 0xc6, 0x8c, 0x4f, 0x4e, 0xd9, 0xac, 0x3f, 0x40, 0xda, 0xb0, 0x74, - 0x17, 0x85, 0x6a, 0xd9, 0x24, 0x13, 0x13, 0x4d, 0x5d, 0x2e, 0x4c, 0x9a, 0xb7, 0x4b, 0xbf, 0x9c, - 0x57, 0xb4, 0xdf, 0xce, 0x2b, 0xda, 0xef, 0xe7, 0x15, 0xed, 0xfb, 0x3f, 0x2a, 0xaf, 0xf4, 0xb3, - 0x92, 0xf5, 0xfd, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x80, 0xe5, 0x67, 0xe3, 0x0f, 0x00, - 0x00, + // 1151 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x58, 0x4b, 0x6f, 0xdb, 0x46, + 0x10, 0x2e, 0xa5, 0x44, 0x8e, 0x47, 0xd6, 0xc3, 0x1b, 0x3f, 0x18, 0xa5, 0x95, 0x54, 0x15, 0x08, + 0xd4, 0x47, 0xa4, 0x54, 0x45, 0x1a, 0x18, 0x7d, 0x18, 0x72, 0x6c, 0xa4, 0x2e, 0x20, 0x37, 0x60, + 0xec, 0x1e, 0x8a, 0x02, 0xc2, 0x8a, 0x1c, 0x53, 0x0b, 0x53, 0x24, 0x43, 0x2e, 0xe5, 0xf8, 0xde, + 0x1f, 0xd1, 0x6b, 0xfb, 0x33, 0x0a, 0xf4, 0x50, 0xf4, 0xd2, 0x5b, 0x7b, 0xce, 0x21, 0x28, 0xdc, + 0x4b, 0x2f, 0xfd, 0x0f, 0x05, 0x97, 0x4b, 0x8a, 0xb2, 0xad, 0xd8, 0x29, 0x7c, 0x68, 0x9a, 0x9e, + 0xc4, 0x9d, 0xf9, 0xe6, 0xe3, 0xf2, 0x9b, 0x07, 0xb9, 0x82, 0x7b, 0x26, 0xe3, 0xc3, 0x60, 0xd0, + 0xd2, 0x9d, 0x51, 0x7b, 0x77, 0x88, 0xbb, 0x43, 0x66, 0x9b, 0xfe, 0x0e, 0xf2, 0x43, 0xc7, 0x3b, + 0x68, 0x73, 0x6e, 0xb7, 0xa9, 0xcb, 0xda, 0x03, 0xcf, 0x39, 0x40, 0x4f, 0xfe, 0xb4, 0x5c, 0xcf, + 0xe1, 0x0e, 0xc9, 0x45, 0xab, 0xca, 0x4d, 0xd3, 0x71, 0x4c, 0x0b, 0xdb, 0xc2, 0x3a, 0x08, 0xf6, + 0xdb, 0x38, 0x72, 0xf9, 0x51, 0x04, 0xaa, 0xdc, 0x4e, 0xb1, 0x9b, 0x8e, 0xe9, 0x4c, 0x50, 0xe1, + 0x4a, 0x2c, 0xc4, 0xd5, 0x19, 0xf0, 0x99, 0x9b, 0xa1, 0x2e, 0x93, 0xf0, 0x8f, 0x2e, 0x02, 0x17, + 0x50, 0xdd, 0xb1, 0x92, 0x0b, 0x19, 0xbc, 0x76, 0x91, 0x60, 0x93, 0x72, 0x3c, 0xa4, 0x47, 0xf1, + 0x6f, 0x14, 0xda, 0xf8, 0x39, 0x03, 0xc5, 0x4d, 0xe7, 0xd0, 0xb6, 0x98, 0x7d, 0xf0, 0x85, 0xcb, + 0x99, 0x63, 0x93, 0x2a, 0x00, 0x33, 0xd0, 0xe6, 0x6c, 0x9f, 0xa1, 0xa7, 0x2a, 0x75, 0xa5, 0x39, + 0xaf, 0xa5, 0x2c, 0xe4, 0x2b, 0xc8, 0x4b, 0x8e, 0x3e, 0x06, 0x4c, 0xcd, 0xd4, 0x95, 0xe6, 0xc2, + 0xc6, 0xda, 0xd3, 0x67, 0xb5, 0xbb, 0xe7, 0x6d, 0x43, 0x77, 0x3c, 0x6c, 0xf3, 0x23, 0x17, 0xfd, + 0xd6, 0x83, 0x88, 0x61, 0x6b, 0x6f, 0x5b, 0x03, 0xc9, 0xb6, 0x15, 0x30, 0xb2, 0x04, 0x57, 0xfd, + 0x10, 0xa5, 0x66, 0xeb, 0x4a, 0xb3, 0xa0, 0x45, 0x0b, 0x52, 0x81, 0x6b, 0x06, 0x52, 0xc3, 0x62, + 0x36, 0xaa, 0x57, 0xea, 0x4a, 0x33, 0xab, 0x25, 0x6b, 0xb2, 0x01, 0xa5, 0x58, 0x8d, 0xbe, 0xee, + 0xd8, 0xfb, 0xcc, 0x54, 0xaf, 0xd6, 0x95, 0x66, 0xbe, 0x73, 0xa3, 0x95, 0xa8, 0xb4, 0xfb, 0xe4, + 0xbe, 0xf0, 0x04, 0x1e, 0x0d, 0x9f, 0x50, 0x2b, 0xc6, 0x9e, 0xc8, 0x4c, 0xd6, 0xa1, 0x18, 0x3f, + 0x91, 0xa4, 0xc8, 0x09, 0x0a, 0xb5, 0x15, 0x8b, 0x75, 0x92, 0xa1, 0x20, 0x1d, 0x91, 0xb5, 0xf1, + 0x63, 0x16, 0x0a, 0x7b, 0x6e, 0xa8, 0x61, 0x0f, 0x7d, 0x9f, 0x9a, 0x48, 0x54, 0x98, 0x73, 0xe9, + 0x91, 0xe5, 0x50, 0x43, 0x28, 0xb8, 0xa0, 0xc5, 0x4b, 0xb2, 0x03, 0x73, 0x06, 0x8e, 0x85, 0x74, + 0x79, 0x21, 0xdd, 0xdd, 0xa7, 0xcf, 0x6a, 0xef, 0xbf, 0x80, 0x74, 0x9b, 0x38, 0x0e, 0x65, 0xcb, + 0x19, 0x38, 0x0e, 0x25, 0xdb, 0x81, 0x39, 0xea, 0xba, 0x82, 0x6f, 0xe1, 0x1f, 0xf1, 0x75, 0x5d, + 0x57, 0xf0, 0x51, 0xd7, 0x0d, 0xf9, 0x96, 0x21, 0xbc, 0xea, 0x33, 0x43, 0x2d, 0x88, 0xd4, 0x5f, + 0xa5, 0xae, 0xbb, 0x6d, 0x84, 0xe6, 0x70, 0xdb, 0xcc, 0x50, 0x8b, 0x91, 0xd9, 0xc0, 0xf1, 0xb6, + 0x41, 0xba, 0xb0, 0x98, 0xc8, 0x3f, 0x42, 0x4e, 0x0d, 0xca, 0xa9, 0xba, 0x2c, 0xd4, 0x5b, 0x9a, + 0x24, 0x40, 0x7b, 0xd2, 0x93, 0x3e, 0xad, 0x1c, 0x1b, 0x63, 0x0b, 0xf9, 0x14, 0xca, 0xb1, 0xfa, + 0x09, 0xc3, 0x8a, 0x60, 0xb8, 0x9e, 0xe8, 0x9f, 0x22, 0x28, 0x49, 0x5b, 0x12, 0xdf, 0x85, 0xb2, + 0x21, 0x2b, 0xb8, 0xef, 0x88, 0x12, 0xf6, 0xd5, 0x5a, 0x3d, 0xdb, 0xcc, 0x77, 0x56, 0x5a, 0xb2, + 0xcd, 0xa7, 0x2b, 0x5c, 0x2b, 0x19, 0x53, 0x6b, 0xbf, 0xf1, 0x43, 0x06, 0x4a, 0x31, 0xe6, 0x55, + 0xcb, 0xe0, 0x3a, 0x94, 0x4e, 0xc8, 0x27, 0xf3, 0x37, 0x4b, 0xbd, 0xe2, 0xb4, 0x7a, 0x8d, 0x00, + 0xd4, 0x4d, 0x1c, 0x33, 0x1d, 0xbb, 0x3a, 0x67, 0xe3, 0xa8, 0x3f, 0xd0, 0x77, 0x1d, 0xdb, 0x7f, + 0x9e, 0x88, 0x67, 0xdc, 0x36, 0xff, 0x42, 0xb7, 0xfd, 0x2b, 0x0b, 0x37, 0x36, 0xd1, 0x08, 0x5c, + 0x8b, 0xe9, 0x94, 0xa3, 0xf1, 0x7f, 0xff, 0x5d, 0x6a, 0xff, 0x65, 0x2f, 0xdc, 0x7f, 0x35, 0xc8, + 0xfb, 0xe8, 0x8d, 0xd1, 0xeb, 0x73, 0x36, 0x42, 0x75, 0x55, 0x0c, 0x68, 0x88, 0x4c, 0xbb, 0x6c, + 0x84, 0x64, 0x13, 0x16, 0x3d, 0x59, 0x10, 0x7d, 0x8e, 0x23, 0xd7, 0xa2, 0x1c, 0xd5, 0x9a, 0xd8, + 0xe3, 0xea, 0xc9, 0x64, 0xcb, 0xfc, 0x69, 0xe5, 0x38, 0x62, 0x57, 0x06, 0x34, 0xfe, 0xcc, 0xc2, + 0xea, 0xe9, 0x3a, 0x7b, 0x1c, 0xa0, 0xcf, 0x5f, 0xe2, 0x6c, 0xff, 0x0b, 0xe6, 0x67, 0x0f, 0xae, + 0xd3, 0x44, 0xd1, 0x09, 0xc5, 0xaa, 0xa0, 0x78, 0x7d, 0xb2, 0x89, 0x89, 0xec, 0x09, 0x17, 0xa1, + 0xa7, 0x6c, 0x97, 0x31, 0x8e, 0x7f, 0xbd, 0x02, 0x6f, 0xa5, 0x5b, 0xfb, 0xbf, 0x97, 0xf6, 0x97, + 0xae, 0xc9, 0x2f, 0xb9, 0x48, 0x4e, 0xcc, 0x0c, 0xf5, 0xd4, 0xcc, 0xe8, 0xcd, 0x9e, 0x19, 0xf5, + 0xa4, 0x8c, 0x66, 0xbc, 0x75, 0xce, 0x18, 0x1e, 0xdf, 0x64, 0xa0, 0x32, 0x01, 0xde, 0x1f, 0x52, + 0xcb, 0x42, 0xdb, 0xc4, 0x57, 0xac, 0x90, 0x1a, 0xf7, 0xe0, 0xe6, 0x99, 0x2a, 0x9c, 0xf7, 0xb6, + 0x6e, 0x10, 0x28, 0x3f, 0x0a, 0x06, 0xbe, 0xee, 0xb1, 0x41, 0x2c, 0x5a, 0xa3, 0x04, 0x85, 0x47, + 0x9c, 0xf2, 0xc0, 0x8f, 0x0d, 0x3f, 0x65, 0x21, 0x17, 0x59, 0x48, 0x03, 0x72, 0x81, 0x78, 0x1f, + 0x0b, 0xa2, 0x7c, 0x07, 0x5a, 0xe1, 0x51, 0x47, 0xa3, 0x1c, 0x7d, 0x4d, 0x7a, 0x48, 0x1b, 0x0a, + 0xd1, 0x55, 0x3f, 0xb0, 0xd9, 0xe3, 0x00, 0xc5, 0x49, 0x62, 0x1a, 0xba, 0x10, 0x01, 0xf6, 0x84, + 0x9f, 0xdc, 0x82, 0x6b, 0xf1, 0xa4, 0x90, 0xdf, 0x0a, 0x69, 0x6c, 0xe2, 0x23, 0xef, 0x41, 0x7e, + 0x52, 0x72, 0xbe, 0x6c, 0x94, 0x34, 0x34, 0xed, 0x26, 0x6b, 0x90, 0x2a, 0x50, 0x3f, 0xde, 0xcb, + 0xca, 0xa9, 0xa0, 0xc5, 0x14, 0x4a, 0x6e, 0xe8, 0x13, 0x58, 0x4a, 0x87, 0x52, 0x5d, 0x47, 0x97, + 0xa3, 0x21, 0xbb, 0x22, 0x1d, 0x9c, 0x6a, 0x1e, 0xbf, 0x2b, 0x61, 0xe4, 0x43, 0x28, 0x18, 0xc9, + 0x94, 0x0b, 0x3f, 0x80, 0xa2, 0xfa, 0x2e, 0x8b, 0xb8, 0x87, 0xe8, 0xe9, 0xe1, 0x91, 0xcb, 0x42, + 0x5f, 0x9b, 0x86, 0x91, 0x77, 0x61, 0x51, 0x77, 0x6c, 0x1b, 0x75, 0x8e, 0x46, 0xdf, 0x73, 0x02, + 0x8e, 0x9e, 0xaf, 0xbe, 0x2d, 0x0e, 0x4c, 0xe5, 0xc4, 0xa1, 0x45, 0x76, 0x72, 0x1b, 0xc8, 0x04, + 0x3c, 0xa4, 0xb6, 0x61, 0x85, 0xe8, 0x77, 0x04, 0x7a, 0x42, 0xf3, 0x99, 0x74, 0x34, 0xbe, 0x84, + 0x6a, 0xd7, 0x4d, 0x6e, 0x25, 0xcd, 0x1a, 0x9a, 0xcc, 0xe7, 0xd1, 0xd1, 0x27, 0x55, 0x71, 0x4a, + 0xba, 0xe2, 0xde, 0x00, 0x90, 0xec, 0xa1, 0x2b, 0x23, 0x5c, 0xf3, 0xd2, 0xb2, 0x6d, 0x74, 0xbe, + 0xcf, 0x40, 0x6e, 0x43, 0xb4, 0x2d, 0x59, 0x87, 0xf9, 0xae, 0xef, 0x3b, 0x3a, 0xa3, 0x1c, 0xc9, + 0x72, 0xdc, 0xcc, 0x53, 0x9f, 0x6f, 0x95, 0x59, 0xdf, 0x05, 0x4d, 0xe5, 0x8e, 0x42, 0x3e, 0x87, + 0xf9, 0xa4, 0x18, 0x89, 0x1a, 0x23, 0x4f, 0xd6, 0x67, 0xe5, 0xcd, 0xc9, 0x9c, 0x98, 0xf1, 0x95, + 0x78, 0x47, 0x21, 0x1f, 0xc3, 0xdc, 0xc3, 0x60, 0x60, 0x31, 0x7f, 0x48, 0x66, 0xdd, 0xb3, 0xb2, + 0xd2, 0x8a, 0xfe, 0x17, 0x68, 0xc5, 0x27, 0xfe, 0xd6, 0xd6, 0xc8, 0xe5, 0x47, 0x4d, 0x85, 0xf4, + 0xe0, 0x9a, 0xec, 0x27, 0x24, 0xb5, 0xd9, 0x63, 0x29, 0xda, 0xcf, 0xb9, 0x73, 0xab, 0xf3, 0x9d, + 0x02, 0x85, 0x48, 0xa4, 0x1e, 0xb5, 0xa9, 0x89, 0x1e, 0xf9, 0x1a, 0x2a, 0x91, 0xf8, 0xe8, 0x9d, + 0x4e, 0x0b, 0xb9, 0x15, 0x33, 0x3e, 0x3f, 0x65, 0xb3, 0x1e, 0x80, 0x74, 0x60, 0xfe, 0x01, 0x72, + 0xd9, 0xb2, 0x49, 0x26, 0xa6, 0x9a, 0xba, 0x52, 0x9c, 0x36, 0x6f, 0x94, 0x7f, 0x39, 0xae, 0x2a, + 0xbf, 0x1d, 0x57, 0x95, 0xdf, 0x8f, 0xab, 0xca, 0xb7, 0x7f, 0x54, 0x5f, 0x1b, 0xe4, 0x04, 0xeb, + 0x07, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x85, 0xd6, 0xce, 0x7f, 0x11, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 3042115f8..f6d23531f 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -23,9 +23,11 @@ message DownlinkOption { // received from the Router message UplinkMessage { bytes payload = 1; - // NOTE: For LoRaWAN, the Router doesn't know the DevEUI and AppEUI + // NOTE: For LoRaWAN, the Router doesn't know the DevEUI/ID and AppEUI/ID bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; protocol.RxMetadata protocol_metadata = 21; gateway.RxMetadata gateway_metadata = 22; repeated DownlinkOption downlink_options = 31; @@ -36,6 +38,8 @@ message DownlinkMessage { bytes payload = 1; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; DownlinkOption downlink_option = 21; } @@ -83,6 +87,18 @@ message DeduplicatedDeviceActivationRequest { DeviceActivationResponse response_template = 31; } +message ActivationChallengeRequest { + bytes payload = 1; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; +} + +message ActivationChallengeResponse { + bytes payload = 1; +} + // message SubscribeRequest is used by a Handler to subscribe to uplink messages message SubscribeRequest {} diff --git a/api/broker/validation.go b/api/broker/validation.go index 533e36f47..4c1d720dc 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -98,6 +98,17 @@ func (m *DeduplicatedDeviceActivationRequest) Validate() bool { return true } +// Validate implements the api.Validator interface +func (m *ActivationChallengeRequest) Validate() bool { + if m.DevEui == nil || m.DevEui.IsEmpty() { + return false + } + if m.AppEui == nil || m.AppEui.IsEmpty() { + return false + } + return true +} + // Validate implements the api.Validator interface func (m *ApplicationHandlerRegistration) Validate() bool { if m.AppId == "" || !api.ValidID(m.AppId) { diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index b7283b531..64651f224 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -317,6 +317,7 @@ const _ = grpc.SupportPackageIsVersion3 // Client API for Handler service type HandlerClient interface { + ActivationChallenge(ctx context.Context, in *broker.ActivationChallengeRequest, opts ...grpc.CallOption) (*broker.ActivationChallengeResponse, error) Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) } @@ -328,6 +329,15 @@ func NewHandlerClient(cc *grpc.ClientConn) HandlerClient { return &handlerClient{cc} } +func (c *handlerClient) ActivationChallenge(ctx context.Context, in *broker.ActivationChallengeRequest, opts ...grpc.CallOption) (*broker.ActivationChallengeResponse, error) { + out := new(broker.ActivationChallengeResponse) + err := grpc.Invoke(ctx, "/handler.Handler/ActivationChallenge", in, out, c.cc, opts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) { out := new(DeviceActivationResponse) err := grpc.Invoke(ctx, "/handler.Handler/Activate", in, out, c.cc, opts...) @@ -340,6 +350,7 @@ func (c *handlerClient) Activate(ctx context.Context, in *broker.DeduplicatedDev // Server API for Handler service type HandlerServer interface { + ActivationChallenge(context.Context, *broker.ActivationChallengeRequest) (*broker.ActivationChallengeResponse, error) Activate(context.Context, *broker.DeduplicatedDeviceActivationRequest) (*DeviceActivationResponse, error) } @@ -347,6 +358,24 @@ func RegisterHandlerServer(s *grpc.Server, srv HandlerServer) { s.RegisterService(&_Handler_serviceDesc, srv) } +func _Handler_ActivationChallenge_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(broker.ActivationChallengeRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(HandlerServer).ActivationChallenge(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/handler.Handler/ActivationChallenge", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(HandlerServer).ActivationChallenge(ctx, req.(*broker.ActivationChallengeRequest)) + } + return interceptor(ctx, in, info, handler) +} + func _Handler_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(broker.DeduplicatedDeviceActivationRequest) if err := dec(in); err != nil { @@ -369,6 +398,10 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ ServiceName: "handler.Handler", HandlerType: (*HandlerServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "ActivationChallenge", + Handler: _Handler_ActivationChallenge_Handler, + }, { MethodName: "Activate", Handler: _Handler_Activate_Handler, @@ -2890,56 +2923,57 @@ func init() { } var fileDescriptorHandler = []byte{ - // 801 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0xef, 0x36, 0xc4, 0x89, 0x9f, 0x5b, 0x3b, 0x9d, 0xb6, 0x66, 0x71, 0x2b, 0xcb, 0xec, 0x01, - 0x05, 0x21, 0xd6, 0x92, 0xa9, 0x14, 0x10, 0x12, 0xd4, 0x95, 0x71, 0x1b, 0x81, 0xa9, 0xb4, 0x09, - 0x07, 0xb8, 0x58, 0xe3, 0x9d, 0x17, 0x7b, 0x94, 0xcd, 0xce, 0xb2, 0x3b, 0x76, 0x64, 0x3e, 0x07, - 0x07, 0xbe, 0x10, 0x12, 0x47, 0xee, 0x5c, 0x50, 0xf8, 0x22, 0x68, 0x67, 0x66, 0xd7, 0x1b, 0xff, - 0xc1, 0x31, 0xea, 0xc9, 0x7e, 0xff, 0x7e, 0xf3, 0xfe, 0xfd, 0xde, 0xc2, 0x17, 0x63, 0x2e, 0x27, - 0xd3, 0x91, 0xeb, 0x8b, 0xab, 0xf6, 0xf9, 0x04, 0xcf, 0x27, 0x3c, 0x1c, 0x27, 0xdf, 0xa3, 0xbc, - 0x16, 0xf1, 0x65, 0x5b, 0xca, 0xb0, 0x4d, 0x23, 0xde, 0x9e, 0xd0, 0x90, 0x05, 0x18, 0x67, 0xbf, - 0x6e, 0x14, 0x0b, 0x29, 0xc8, 0x81, 0x11, 0x1b, 0xcf, 0xc6, 0x42, 0x8c, 0x03, 0x6c, 0x2b, 0xf5, - 0x68, 0x7a, 0xd1, 0xc6, 0xab, 0x48, 0xce, 0xb5, 0x57, 0xe3, 0xe4, 0x2e, 0x0f, 0x8c, 0x62, 0x71, - 0x89, 0xb1, 0xf9, 0x31, 0x81, 0x5f, 0xde, 0x25, 0x50, 0xb9, 0xfa, 0x22, 0xc8, 0xff, 0x98, 0xe0, - 0xee, 0x4e, 0xc1, 0x81, 0x88, 0xe9, 0x35, 0x0d, 0xdb, 0x0c, 0x67, 0xdc, 0x47, 0x0d, 0xe1, 0xfc, - 0x65, 0x81, 0xdd, 0x53, 0x8a, 0xae, 0x2f, 0xf9, 0x8c, 0x4a, 0x2e, 0x42, 0x0f, 0x93, 0x48, 0x84, - 0x09, 0x12, 0x1b, 0x0e, 0x22, 0x3a, 0x0f, 0x04, 0x65, 0xb6, 0xd5, 0xb2, 0x8e, 0x1f, 0x78, 0x99, - 0x48, 0x9e, 0x42, 0x89, 0x46, 0xd1, 0x90, 0x33, 0xfb, 0x7e, 0xcb, 0x3a, 0x2e, 0x7b, 0xfb, 0x34, - 0x8a, 0x4e, 0x19, 0xf9, 0x1a, 0x6a, 0x4c, 0x5c, 0x87, 0x01, 0x0f, 0x2f, 0x87, 0x22, 0x4a, 0xb1, - 0xec, 0x4a, 0xcb, 0x3a, 0xae, 0x74, 0xea, 0xae, 0xa9, 0xba, 0x67, 0xcc, 0x6f, 0x95, 0xd5, 0xab, - 0xb2, 0x5b, 0x32, 0x19, 0xc0, 0x63, 0x9a, 0xe7, 0x31, 0xbc, 0x42, 0x49, 0x19, 0x95, 0xd4, 0x7e, - 0x5f, 0x81, 0x3c, 0x77, 0xf3, 0xfa, 0x17, 0xc9, 0x0e, 0x8c, 0x8f, 0x47, 0xe8, 0x8a, 0xce, 0xa9, - 0xc1, 0xc3, 0x33, 0x49, 0xe5, 0x34, 0xf1, 0xf0, 0xe7, 0x29, 0x26, 0xd2, 0x39, 0x84, 0x92, 0x56, - 0x38, 0x2e, 0x3c, 0xed, 0x46, 0x51, 0xc0, 0x7d, 0x15, 0x71, 0xca, 0x30, 0x94, 0xfc, 0x82, 0x63, - 0x5c, 0x28, 0xcd, 0x2a, 0x94, 0xe6, 0xfc, 0x6a, 0x41, 0xa5, 0x10, 0xb0, 0xc1, 0x2d, 0x6d, 0x19, - 0x43, 0x5f, 0x30, 0x8c, 0x4d, 0x67, 0x32, 0x91, 0x3c, 0x87, 0xb2, 0x2f, 0xc2, 0x19, 0xc6, 0x12, - 0x63, 0x7b, 0x4f, 0xd9, 0x16, 0x8a, 0xd4, 0x3a, 0xa3, 0x01, 0x67, 0x54, 0x8a, 0xd8, 0x7e, 0x4f, - 0x5b, 0x73, 0x45, 0x8a, 0x8a, 0xa1, 0x46, 0xdd, 0xd7, 0xa8, 0x46, 0x74, 0x5e, 0xc2, 0x91, 0x1e, - 0xdf, 0xd6, 0x0a, 0x52, 0x35, 0xc3, 0x59, 0x61, 0x66, 0x0c, 0x67, 0xa7, 0xcc, 0xf9, 0x05, 0x4a, - 0x1a, 0x61, 0xb7, 0x38, 0xf2, 0x39, 0x54, 0xcd, 0x46, 0x0d, 0xf5, 0x46, 0xa9, 0xa2, 0x2a, 0x9d, - 0x9a, 0x6b, 0xd4, 0xae, 0x86, 0x7d, 0x73, 0xcf, 0x7b, 0x68, 0x34, 0x5a, 0xf1, 0xea, 0x50, 0x01, - 0x72, 0x1f, 0x9d, 0x13, 0x00, 0xad, 0xfb, 0x8e, 0x27, 0x92, 0x7c, 0x9c, 0xf6, 0x2e, 0x95, 0x12, - 0xdb, 0x6a, 0xed, 0x29, 0xa8, 0x8c, 0x8b, 0xda, 0xcb, 0xcb, 0xec, 0x4e, 0x08, 0xa4, 0x17, 0xcf, - 0xb3, 0x65, 0x1a, 0x60, 0x92, 0xd0, 0xf1, 0x7f, 0xed, 0x6b, 0x1d, 0x4a, 0x17, 0x1c, 0x03, 0x96, - 0x98, 0x1a, 0x8c, 0x44, 0x3e, 0x82, 0x3d, 0x1a, 0x45, 0x26, 0xf3, 0x27, 0xf9, 0x73, 0x85, 0x41, - 0x7b, 0xa9, 0x83, 0x73, 0x0e, 0x47, 0xbd, 0x78, 0xfe, 0x43, 0x74, 0xb7, 0xd7, 0x0c, 0xea, 0xfd, - 0x6d, 0xa8, 0x3f, 0x42, 0x2d, 0x47, 0xf5, 0x30, 0x99, 0x06, 0xf2, 0x7f, 0x94, 0xf0, 0x04, 0xf6, - 0xd5, 0xa2, 0xa8, 0x22, 0x0e, 0x3d, 0x2d, 0x38, 0x9f, 0xc2, 0xa3, 0x42, 0x83, 0xb6, 0x81, 0x77, - 0x10, 0x0e, 0xde, 0xe8, 0x2c, 0xc9, 0x4f, 0x70, 0x68, 0xd8, 0x85, 0xe4, 0x93, 0x9c, 0xb6, 0xc8, - 0xa6, 0x3a, 0x79, 0x64, 0xab, 0xe7, 0x42, 0x71, 0xab, 0xf1, 0xe1, 0xd2, 0xb4, 0x56, 0x0f, 0x4a, - 0xe7, 0xf7, 0x7d, 0x20, 0x85, 0x2e, 0x0c, 0x68, 0x48, 0xc7, 0x18, 0xa7, 0xac, 0xf7, 0x70, 0xcc, - 0x13, 0x89, 0x71, 0x91, 0x62, 0xcd, 0x75, 0x9d, 0x5b, 0xec, 0x79, 0xa3, 0xee, 0xea, 0x93, 0xec, - 0x66, 0x27, 0xd9, 0xfd, 0x26, 0x3d, 0xc9, 0xa4, 0x0f, 0xd5, 0xd7, 0x28, 0x77, 0x41, 0x5a, 0x3b, - 0x23, 0xf2, 0x15, 0x54, 0xcf, 0x6e, 0xe3, 0xac, 0xf5, 0xdb, 0x98, 0xc7, 0xb7, 0xf0, 0xa8, 0x87, - 0x01, 0x4a, 0x7c, 0x17, 0x45, 0x9d, 0x40, 0xf9, 0x35, 0x4a, 0xc3, 0xd4, 0x0f, 0x96, 0x5a, 0x5d, - 0x88, 0x5f, 0xe6, 0x0c, 0x79, 0x01, 0xe5, 0xb3, 0x3c, 0x70, 0xd9, 0xba, 0xf1, 0xb9, 0x2e, 0x3c, - 0xd0, 0xb9, 0x6f, 0x7f, 0x71, 0x13, 0xc4, 0x5b, 0xb0, 0xf3, 0x8c, 0x93, 0xbe, 0xd8, 0x69, 0xb4, - 0x8f, 0x97, 0x9e, 0x53, 0xf7, 0xa1, 0x0f, 0x95, 0xc2, 0x4e, 0x93, 0x67, 0x0b, 0x9f, 0x95, 0x53, - 0xd0, 0x68, 0xac, 0x33, 0x1a, 0x1a, 0xbc, 0x84, 0x72, 0x4e, 0xbb, 0x62, 0x61, 0x4b, 0x04, 0x6f, - 0xd8, 0xab, 0x26, 0x8d, 0xd0, 0xe9, 0x43, 0xd5, 0xd0, 0x25, 0x5b, 0xe1, 0x17, 0x6a, 0x3c, 0xfa, - 0xdb, 0x42, 0xea, 0x79, 0xe0, 0xad, 0xaf, 0x4f, 0x61, 0x36, 0x5a, 0xff, 0xea, 0xe8, 0x8f, 0x9b, - 0xa6, 0xf5, 0xe7, 0x4d, 0xd3, 0xfa, 0xfb, 0xa6, 0x69, 0xfd, 0xf6, 0x4f, 0xf3, 0xde, 0xa8, 0xa4, - 0x9a, 0xf8, 0xd9, 0xbf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x55, 0xb5, 0x55, 0x22, 0xb2, 0x08, 0x00, - 0x00, + // 827 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0x5d, 0x6f, 0xdb, 0x54, + 0x18, 0x9e, 0x57, 0x9a, 0x36, 0x6f, 0xb6, 0xa4, 0x3b, 0xdd, 0x82, 0xc9, 0xa6, 0x28, 0x18, 0x09, + 0x15, 0x21, 0x1c, 0x29, 0x4c, 0x2a, 0x08, 0x09, 0x96, 0x11, 0xb2, 0x55, 0x10, 0x26, 0xb9, 0xe5, + 0x02, 0x2e, 0x88, 0x4e, 0x7c, 0xde, 0x3a, 0x47, 0x75, 0x7d, 0x8c, 0x7d, 0x92, 0x2a, 0xfc, 0x0e, + 0x2e, 0xf8, 0x43, 0x93, 0xb8, 0xe4, 0x9e, 0x1b, 0x54, 0xfe, 0x08, 0xb2, 0xcf, 0xb1, 0xe3, 0xe6, + 0xa3, 0x69, 0x10, 0x57, 0xc9, 0xfb, 0xf5, 0x9c, 0xf7, 0xf3, 0x31, 0x7c, 0xee, 0x71, 0x39, 0x9e, + 0x8c, 0x6c, 0x57, 0x5c, 0xb6, 0xcf, 0xc6, 0x78, 0x36, 0xe6, 0x81, 0x17, 0x7f, 0x8f, 0xf2, 0x4a, + 0x44, 0x17, 0x6d, 0x29, 0x83, 0x36, 0x0d, 0x79, 0x7b, 0x4c, 0x03, 0xe6, 0x63, 0x94, 0xfd, 0xda, + 0x61, 0x24, 0xa4, 0x20, 0x7b, 0x5a, 0x6c, 0x3c, 0xf5, 0x84, 0xf0, 0x7c, 0x6c, 0xa7, 0xea, 0xd1, + 0xe4, 0xbc, 0x8d, 0x97, 0xa1, 0x9c, 0x29, 0xaf, 0xc6, 0xf1, 0x5d, 0x1e, 0x18, 0x45, 0xe2, 0x02, + 0x23, 0xfd, 0xa3, 0x03, 0xbf, 0xb8, 0x4b, 0x60, 0xea, 0xea, 0x0a, 0x3f, 0xff, 0xa3, 0x83, 0xbb, + 0x5b, 0x05, 0xfb, 0x22, 0xa2, 0x57, 0x34, 0x68, 0x33, 0x9c, 0x72, 0x17, 0x15, 0x84, 0xf5, 0x97, + 0x01, 0x66, 0x2f, 0x55, 0x74, 0x5d, 0xc9, 0xa7, 0x54, 0x72, 0x11, 0x38, 0x18, 0x87, 0x22, 0x88, + 0x91, 0x98, 0xb0, 0x17, 0xd2, 0x99, 0x2f, 0x28, 0x33, 0x8d, 0x96, 0x71, 0xf4, 0xc0, 0xc9, 0x44, + 0xf2, 0x04, 0x4a, 0x34, 0x0c, 0x87, 0x9c, 0x99, 0xf7, 0x5b, 0xc6, 0x51, 0xd9, 0xd9, 0xa5, 0x61, + 0x78, 0xc2, 0xc8, 0x57, 0x50, 0x63, 0xe2, 0x2a, 0xf0, 0x79, 0x70, 0x31, 0x14, 0x61, 0x82, 0x65, + 0x56, 0x5a, 0xc6, 0x51, 0xa5, 0x53, 0xb7, 0x75, 0xd5, 0x3d, 0x6d, 0x7e, 0x93, 0x5a, 0x9d, 0x2a, + 0xbb, 0x21, 0x93, 0x01, 0x1c, 0xd2, 0x3c, 0x8f, 0xe1, 0x25, 0x4a, 0xca, 0xa8, 0xa4, 0xe6, 0xbb, + 0x29, 0xc8, 0x33, 0x3b, 0xaf, 0x7f, 0x9e, 0xec, 0x40, 0xfb, 0x38, 0x84, 0x2e, 0xe9, 0xac, 0x1a, + 0x3c, 0x3c, 0x95, 0x54, 0x4e, 0x62, 0x07, 0x7f, 0x99, 0x60, 0x2c, 0xad, 0x7d, 0x28, 0x29, 0x85, + 0x65, 0xc3, 0x93, 0x6e, 0x18, 0xfa, 0xdc, 0x4d, 0x23, 0x4e, 0x18, 0x06, 0x92, 0x9f, 0x73, 0x8c, + 0x0a, 0xa5, 0x19, 0x85, 0xd2, 0xac, 0xdf, 0x0c, 0xa8, 0x14, 0x02, 0xd6, 0xb8, 0x25, 0x2d, 0x63, + 0xe8, 0x0a, 0x86, 0x91, 0xee, 0x4c, 0x26, 0x92, 0x67, 0x50, 0x76, 0x45, 0x30, 0xc5, 0x48, 0x62, + 0x64, 0xee, 0xa4, 0xb6, 0xb9, 0x22, 0xb1, 0x4e, 0xa9, 0xcf, 0x19, 0x95, 0x22, 0x32, 0xdf, 0x51, + 0xd6, 0x5c, 0x91, 0xa0, 0x62, 0xa0, 0x50, 0x77, 0x15, 0xaa, 0x16, 0xad, 0x17, 0x70, 0xa0, 0xc6, + 0xb7, 0xb1, 0x82, 0x44, 0xcd, 0x70, 0x5a, 0x98, 0x19, 0xc3, 0xe9, 0x09, 0xb3, 0x7e, 0x85, 0x92, + 0x42, 0xd8, 0x2e, 0x8e, 0x7c, 0x06, 0x55, 0xbd, 0x51, 0x43, 0xb5, 0x51, 0x69, 0x51, 0x95, 0x4e, + 0xcd, 0xd6, 0x6a, 0x5b, 0xc1, 0xbe, 0xbe, 0xe7, 0x3c, 0xd4, 0x1a, 0xa5, 0x78, 0xb9, 0x9f, 0x02, + 0x72, 0x17, 0xad, 0x63, 0x00, 0xa5, 0xfb, 0x8e, 0xc7, 0x92, 0x7c, 0x94, 0xf4, 0x2e, 0x91, 0x62, + 0xd3, 0x68, 0xed, 0xa4, 0x50, 0xd9, 0x2d, 0x2a, 0x2f, 0x27, 0xb3, 0x5b, 0x01, 0x90, 0x5e, 0x34, + 0xcb, 0x96, 0x69, 0x80, 0x71, 0x4c, 0xbd, 0xdb, 0xf6, 0xb5, 0x0e, 0xa5, 0x73, 0x8e, 0x3e, 0x8b, + 0x75, 0x0d, 0x5a, 0x22, 0x1f, 0xc2, 0x0e, 0x0d, 0x43, 0x9d, 0xf9, 0xe3, 0xfc, 0xb9, 0xc2, 0xa0, + 0x9d, 0xc4, 0xc1, 0x3a, 0x83, 0x83, 0x5e, 0x34, 0xfb, 0x21, 0xbc, 0xdb, 0x6b, 0x1a, 0xf5, 0xfe, + 0x26, 0xd4, 0x1f, 0xa1, 0x96, 0xa3, 0x3a, 0x18, 0x4f, 0x7c, 0xf9, 0x1f, 0x4a, 0x78, 0x0c, 0xbb, + 0xe9, 0xa2, 0xa4, 0x45, 0xec, 0x3b, 0x4a, 0xb0, 0x3e, 0x81, 0x47, 0x85, 0x06, 0x6d, 0x02, 0xef, + 0xbc, 0x35, 0x60, 0xef, 0xb5, 0x4a, 0x93, 0xfc, 0x0c, 0x87, 0xf3, 0xf3, 0xfa, 0x7a, 0x4c, 0x7d, + 0x1f, 0x03, 0x0f, 0x89, 0x95, 0x9d, 0xf0, 0x0a, 0xa3, 0x3e, 0xaf, 0xc6, 0x07, 0xb7, 0xfa, 0x68, + 0x56, 0xf9, 0x09, 0xf6, 0xb5, 0x19, 0xc9, 0xc7, 0x39, 0x2f, 0x20, 0x9b, 0xa8, 0xee, 0x20, 0x5b, + 0xe6, 0x23, 0x85, 0xfe, 0xfe, 0xc2, 0x3a, 0x2c, 0x33, 0x56, 0xe7, 0xed, 0x2e, 0x90, 0x42, 0x9b, + 0x07, 0x34, 0xa0, 0x1e, 0x46, 0x09, 0xad, 0x38, 0xe8, 0xf1, 0x58, 0x62, 0x54, 0xbc, 0xe1, 0xe6, + 0xaa, 0xd1, 0xcc, 0x0f, 0xa9, 0x51, 0xb7, 0x15, 0xe7, 0xdb, 0x19, 0xe7, 0xdb, 0xdf, 0x24, 0x9c, + 0x4f, 0xfa, 0x50, 0x7d, 0x85, 0x72, 0x1b, 0xa4, 0x95, 0x4b, 0x40, 0xbe, 0x84, 0xea, 0xe9, 0x4d, + 0x9c, 0x95, 0x7e, 0x6b, 0xf3, 0xf8, 0x16, 0x1e, 0xf5, 0xd0, 0x47, 0x89, 0xff, 0x47, 0x51, 0xc7, + 0x50, 0x7e, 0x85, 0x52, 0x53, 0xc1, 0x7b, 0x0b, 0xad, 0x2e, 0xc4, 0x2f, 0x1e, 0x25, 0x79, 0x0e, + 0xe5, 0xd3, 0x3c, 0x70, 0xd1, 0xba, 0xf6, 0xb9, 0x2e, 0x3c, 0x50, 0xb9, 0x6f, 0x7e, 0x71, 0x1d, + 0xc4, 0x1b, 0x30, 0xf3, 0x8c, 0xe3, 0xbe, 0xd8, 0x6a, 0xb4, 0x87, 0x0b, 0xcf, 0xa5, 0x04, 0xd4, + 0x87, 0x4a, 0xe1, 0x68, 0xc8, 0xd3, 0xb9, 0xcf, 0x12, 0xd7, 0x34, 0x1a, 0xab, 0x8c, 0xfa, 0xce, + 0x5e, 0x40, 0x39, 0xbf, 0xeb, 0x62, 0x61, 0x0b, 0x0c, 0xd2, 0x30, 0x97, 0x4d, 0x0a, 0xa1, 0xd3, + 0x87, 0xaa, 0x3e, 0xc7, 0x6c, 0x85, 0x9f, 0xa7, 0xe3, 0x51, 0x1f, 0x2f, 0x52, 0xcf, 0x03, 0x6f, + 0x7c, 0xde, 0x0a, 0xb3, 0x51, 0xfa, 0x97, 0x07, 0x7f, 0x5c, 0x37, 0x8d, 0x3f, 0xaf, 0x9b, 0xc6, + 0xdf, 0xd7, 0x4d, 0xe3, 0xf7, 0x7f, 0x9a, 0xf7, 0x46, 0xa5, 0xb4, 0x89, 0x9f, 0xfe, 0x1b, 0x00, + 0x00, 0xff, 0xff, 0xaf, 0x9b, 0xbd, 0xd5, 0x13, 0x09, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index cf62f9d62..97e0bcbde 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -19,6 +19,7 @@ message DeviceActivationResponse { // The Handler service provides pure network functionality service Handler { + rpc ActivationChallenge(broker.ActivationChallengeRequest) returns (broker.ActivationChallengeResponse); rpc Activate(broker.DeduplicatedDeviceActivationRequest) returns (DeviceActivationResponse); } diff --git a/core/broker/activation.go b/core/broker/activation.go index 55e5958c0..118384ebc 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -7,6 +7,7 @@ import ( "crypto/md5" "encoding/hex" "fmt" + "sync" "time" pb "github.com/TheThingsNetwork/ttn/api/broker" @@ -15,10 +16,16 @@ import ( pb_handler "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core" "github.com/apex/log" + "github.com/brocaar/lorawan" "github.com/pkg/errors" - "google.golang.org/grpc" ) +type challengeResponseWithHandler struct { + handler *pb_discovery.Announcement + client pb_handler.HandlerClient + response *pb.ActivationChallengeResponse +} + func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { ctx := b.Ctx.WithFields(log.Fields{ "GatewayEUI": *activation.GatewayMetadata.GatewayEui, @@ -77,7 +84,8 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // Send Activate to NS deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "NetworkServer refused to prepare activation") + err = errors.Wrap(core.FromGRPCError(err), "NetworkServer refused to prepare activation") + return nil, err } ctx = ctx.WithFields(log.Fields{ @@ -95,30 +103,105 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D err = core.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) return nil, err } - if len(announcements) > 1 { - err = core.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", deduplicatedActivationRequest.AppId)) + + ctx = ctx.WithField("NumHandlers", len(announcements)) + + // LoRaWAN: Unmarshal and prepare version without MIC + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(deduplicatedActivationRequest.Payload) + if err != nil { + return nil, err + } + correctMIC := phyPayload.MIC + phyPayload.MIC = [4]byte{0, 0, 0, 0} + phyPayloadWithoutMIC, err := phyPayload.MarshalBinary() + if err != nil { return nil, err } - handler := announcements[0] - ctx.WithField("HandlerID", handler.Id).Debug("Forward Activation") + // Build Challenge + challenge := &pb.ActivationChallengeRequest{ + Payload: phyPayloadWithoutMIC, + AppId: deduplicatedActivationRequest.AppId, + DevId: deduplicatedActivationRequest.DevId, + AppEui: deduplicatedActivationRequest.AppEui, + DevEui: deduplicatedActivationRequest.DevEui, + } - var conn *grpc.ClientConn - conn, err = handler.Dial() - defer conn.Close() - if err != nil { + // Send Challenge to all handlers and collect responses + var wg sync.WaitGroup + responses := make(chan *challengeResponseWithHandler, len(announcements)) + for _, announcement := range announcements { + conn, err := announcement.Dial() + if err != nil { + ctx.WithError(err).Warn("Could not dial handler for Activation") + continue + } + client := pb_handler.NewHandlerClient(conn) + + // Do async request + wg.Add(1) + go func(announcement *pb_discovery.Announcement) { + res, err := client.ActivationChallenge(b.Component.GetContext(""), challenge) + if err == nil && res != nil { + responses <- &challengeResponseWithHandler{ + handler: announcement, + client: client, + response: res, + } + } + wg.Done() + }(announcement) + } + + // Make sure to close channel when all requests are done + go func() { + wg.Wait() + close(responses) + }() + + var gotFirst bool + var joinHandler *pb_discovery.Announcement + var joinHandlerClient pb_handler.HandlerClient + for res := range responses { + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(res.response.Payload) + if err != nil { + continue + } + if phyPayload.MIC != correctMIC { + continue + } + + if gotFirst { + ctx.Warn("Duplicate Activation Response") + } else { + gotFirst = true + joinHandler = res.handler + joinHandlerClient = res.client + } + } + + // Activation not accepted by any broker + if !gotFirst { + ctx.Debug("Activation not accepted by any Handler") + err = errors.New("Activation not accepted by any Handler") return nil, err } - client := pb_handler.NewHandlerClient(conn) + + ctx.WithField("HandlerID", joinHandler.Id).Debug("Forward Activation") + var handlerResponse *pb_handler.DeviceActivationResponse - handlerResponse, err = client.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) + handlerResponse, err = joinHandlerClient.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Handler refused activation") + err = errors.Wrap(core.FromGRPCError(err), "Handler refused activation") + return nil, err } handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "NetworkServer refused activation") + err = errors.Wrap(core.FromGRPCError(err), "NetworkServer refused activation") + return nil, err } deviceActivationResponse = &pb.DeviceActivationResponse{ diff --git a/core/broker/server.go b/core/broker/server.go index b42ffdcce..e8bd2272b 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -7,6 +7,7 @@ import ( "io" pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" @@ -15,21 +16,21 @@ import ( ) type brokerRPC struct { - broker Broker + broker *broker } var grpcErrf = grpc.Errorf // To make go vet stop complaining func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { - routerID, err := b.broker.ValidateNetworkContext(stream.Context()) + router, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { return core.BuildGRPCError(err) } - downlinkChannel, err := b.broker.ActivateRouter(routerID) + downlinkChannel, err := b.broker.ActivateRouter(router.Id) if err != nil { return core.BuildGRPCError(err) } - defer b.broker.DeactivateRouter(routerID) + defer b.broker.DeactivateRouter(router.Id) go func() { for { if downlinkChannel == nil { @@ -64,15 +65,15 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { } func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_SubscribeServer) error { - handlerID, err := b.broker.ValidateNetworkContext(stream.Context()) + handler, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { return core.BuildGRPCError(err) } - uplinkChannel, err := b.broker.ActivateHandler(handlerID) + uplinkChannel, err := b.broker.ActivateHandler(handler.Id) if err != nil { return core.BuildGRPCError(err) } - defer b.broker.DeactivateHandler(handlerID) + defer b.broker.DeactivateHandler(handler.Id) for { if uplinkChannel == nil { return nil @@ -91,7 +92,7 @@ func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_Subscri } func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { - handlerID, err := b.broker.ValidateNetworkContext(stream.Context()) + handler, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { return core.BuildGRPCError(err) } @@ -106,9 +107,24 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { if !downlink.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Downlink") } - // TODO: Validate that this handler can publish downlink for the application - _ = handlerID - go b.broker.HandleDownlink(downlink) + go func(downlink *pb.DownlinkMessage) { + // Get latest Handler metadata + handler, err := b.broker.Component.Discover("handler", handler.Id) + if err != nil { + return + } + // Check if this Handler can publish for this AppId + for _, meta := range handler.Metadata { + switch meta.Key { + case pb_discovery.Metadata_APP_ID: + announcedID := string(meta.Value) + if announcedID == downlink.AppId { + b.broker.HandleDownlink(downlink) + return + } + } + } + }(downlink) } } diff --git a/core/component.go b/core/component.go index 0e8523d5d..50c89b1a8 100644 --- a/core/component.go +++ b/core/component.go @@ -32,7 +32,7 @@ import ( type ComponentInterface interface { RegisterRPC(s *grpc.Server) Init(c *Component) error - ValidateNetworkContext(ctx context.Context) (string, error) + ValidateNetworkContext(ctx context.Context) (*pb_discovery.Announcement, error) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) } @@ -213,7 +213,7 @@ func (c *TTNClaims) CanEditApp(appID string) bool { } // ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) -func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID string, err error) { +func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_discovery.Announcement, err error) { defer func() { if err != nil { time.Sleep(time.Second) @@ -251,7 +251,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str } if announcement.PublicKey == "" { - return id, nil + return announcement, nil } if token == "" { @@ -269,7 +269,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (componentID str return } - return id, nil + return announcement, nil } // ValidateTTNAuthContext gets a token from the context and validates it @@ -334,7 +334,7 @@ func (c *Component) ServerOptions() []grpc.ServerOption { logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) if err != nil { err := FromGRPCError(err) - logCtx.WithError(err).Warn("Could not handle Request") + logCtx.WithField("error", err.Error()).WithField("err-type", fmt.Sprintf("%T", err)).WithField("err-err-type", fmt.Sprintf("%T", err.Error())).Warn("Could not handle Request") } else { logCtx.Info("Handled request") } diff --git a/core/errors.go b/core/errors.go index 550b84240..d1cdb48d0 100644 --- a/core/errors.go +++ b/core/errors.go @@ -57,7 +57,7 @@ func BuildGRPCError(err error) error { case *ErrPermissionDenied: code = codes.PermissionDenied } - return grpcErrf(code, fmt.Sprintf("%+v", err)) + return grpcErrf(code, err.Error()) } // FromGRPCError creates a regular error with the same type as the gRPC error @@ -73,6 +73,9 @@ func FromGRPCError(err error) error { case codes.Internal: return NewErrInternal(strings.TrimPrefix(desc, "Internal error: ")) case codes.InvalidArgument: + if split := strings.Split(desc, " not valid: "); len(split) == 2 { + return NewErrInvalidArgument(split[0], split[1]) + } return NewErrInvalidArgument("Argument", desc) case codes.NotFound: return NewErrNotFound(strings.TrimSuffix(desc, " not found")) diff --git a/core/handler/activation.go b/core/handler/activation.go index c702379df..ce6933939 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -33,6 +33,42 @@ func (h *handler) getActivationMetadata(ctx log.Interface, activation *pb_broker return mqttUp.Metadata, nil } +func (h *handler) HandleActivationChallenge(challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { + + // Find Device + dev, err := h.devices.Get(challenge.AppId, challenge.DevId) + if err != nil { + return nil, err + } + + if dev.AppKey.IsEmpty() { + err = core.NewErrNotFound(fmt.Sprintf("AppKey for device %s", challenge.DevId)) + return nil, err + } + + // Unmarshal LoRaWAN + var reqPHY lorawan.PHYPayload + if err = reqPHY.UnmarshalBinary(challenge.Payload); err != nil { + return nil, err + } + + // Set MIC + if err := reqPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { + err = core.NewErrNotFound("Could not set MIC") + return nil, err + } + + // Marshal + bytes, err := reqPHY.MarshalBinary() + if err != nil { + return nil, err + } + + return &pb_broker.ActivationChallengeResponse{ + Payload: bytes, + }, nil +} + func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { var appEUI types.AppEUI if activation.AppEui != nil { diff --git a/core/handler/handler.go b/core/handler/handler.go index afe4f2fc8..ff8be6ead 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -24,6 +24,7 @@ type Handler interface { core.ManagementInterface HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error + HandleActivationChallenge(challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error } diff --git a/core/handler/server.go b/core/handler/server.go index d5f2ecad2..6d003c2a5 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -18,6 +18,21 @@ type handlerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining +func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { + _, err := h.handler.ValidateNetworkContext(ctx) + if err != nil { + return nil, core.BuildGRPCError(err) + } + if !challenge.Validate() { + return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + } + res, err := h.handler.HandleActivationChallenge(challenge) + if err != nil { + return nil, core.BuildGRPCError(err) + } + return res, nil +} + func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index abf29f938..33d9e9010 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -281,6 +281,8 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } message.ResponseTemplate.AppEui = message.AppEui message.ResponseTemplate.DevEui = message.DevEui + message.ResponseTemplate.AppId = message.AppId + message.ResponseTemplate.DevId = message.DevId // Add Full FCnt (avoiding nil pointer panics) if option := message.ResponseTemplate.DownlinkOption; option != nil { @@ -325,6 +327,10 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ return nil, err } + if dev.AppID != message.AppId || dev.DevID != message.DevId { + return nil, core.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") + } + // Unmarshal LoRaWAN Payload var phyPayload lorawan.PHYPayload err = phyPayload.UnmarshalBinary(message.Payload) From d39fcecad99da21e85dd5769709ac3c822c0a0da Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 8 Sep 2016 14:53:39 +0200 Subject: [PATCH 1700/2266] Feature/check app rights (#235) * Add helper to fetch rights of an access key * Use authentication strategies to differ between accessToken and accessKey (and Public) * Implement AppRights in terms of Account methods * s -> strategy --- core/account/account.go | 43 +++++++++++++++++++----------- core/account/applications.go | 11 ++++++++ core/account/auth/auth.go | 48 ++++++++++++++++++++++++++++++++++ core/account/users.go | 3 ++- core/account/util/http.go | 36 +++++++++++++------------ core/account/util/http_test.go | 36 +++++++++++++------------ 6 files changed, 128 insertions(+), 49 deletions(-) create mode 100644 core/account/auth/auth.go diff --git a/core/account/account.go b/core/account/account.go index 77d305a34..3b4c6e2ec 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -3,45 +3,58 @@ package account -import "github.com/TheThingsNetwork/ttn/core/account/util" +import ( + "github.com/TheThingsNetwork/ttn/core/account/auth" + "github.com/TheThingsNetwork/ttn/core/account/util" +) // Account is a client to an account server type Account struct { - server string - accessToken string + server string + auth auth.Strategy } -// New creates a new accoun client that will use the +// New creates a new account client that will use the // accessToken to make requests to the specified account server func New(server, accessToken string) *Account { return &Account{ - server: server, - accessToken: accessToken, + server: server, + auth: auth.AccessToken(accessToken), } } -// SetToken changes the accessToken the account client uses to -// makes requests. -func (a *Account) SetToken(accessToken string) { - a.accessToken = accessToken +// NewWithKey creates an account client that uses an accessKey to +// authenticate +func NewWithKey(server, accessKey string) *Account { + return &Account{ + server: server, + auth: auth.AccessKey(accessKey), + } +} + +func NewWithPublic(server string) *Account { + return &Account{ + server: server, + auth: auth.Public, + } } func (a *Account) get(URI string, res interface{}) error { - return util.GET(a.server, a.accessToken, URI, res) + return util.GET(a.server, a.auth, URI, res) } func (a *Account) put(URI string, body, res interface{}) error { - return util.PUT(a.server, a.accessToken, URI, body, res) + return util.PUT(a.server, a.auth, URI, body, res) } func (a *Account) post(URI string, body, res interface{}) error { - return util.POST(a.server, a.accessToken, URI, body, res) + return util.POST(a.server, a.auth, URI, body, res) } func (a *Account) patch(URI string, body, res interface{}) error { - return util.PATCH(a.server, a.accessToken, URI, body, res) + return util.PATCH(a.server, a.auth, URI, body, res) } func (a *Account) del(URI string) error { - return util.DELETE(a.server, a.accessToken, URI) + return util.DELETE(a.server, a.auth, URI) } diff --git a/core/account/applications.go b/core/account/applications.go index 8835ec407..61e36d8b3 100644 --- a/core/account/applications.go +++ b/core/account/applications.go @@ -118,3 +118,14 @@ func (a *Account) GenerateEUI(appID string) (*types.AppEUI, error) { func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { return a.del(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) } + +// AppRights returns the rights the current account client has to a certain +// application +func (a *Account) AppRights(appID string) (rights []types.Right, err error) { + err = a.get(fmt.Sprintf("/applications/%s/rights", appID), &rights) + if err != nil { + return nil, err + } + + return rights, nil +} diff --git a/core/account/auth/auth.go b/core/account/auth/auth.go new file mode 100644 index 000000000..1e2ae5f73 --- /dev/null +++ b/core/account/auth/auth.go @@ -0,0 +1,48 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package auth + +import ( + "fmt" + "net/http" +) + +type Strategy interface { + DecorateRequest(*http.Request) +} + +type accessToken struct { + accessToken string +} + +func (a *accessToken) DecorateRequest(req *http.Request) { + req.Header.Set("Authorization", fmt.Sprintf("bearer %s", a.accessToken)) +} + +func AccessToken(s string) *accessToken { + return &accessToken{ + accessToken: s, + } +} + +type accessKey struct { + accessKey string +} + +func (a *accessKey) DecorateRequest(req *http.Request) { + req.Header.Set("Authorization", fmt.Sprintf("key %s", a.accessKey)) +} + +func AccessKey(s string) *accessKey { + return &accessKey{ + accessKey: s, + } +} + +type public struct{} + +func (p *public) DecorateRequest(req *http.Request) { +} + +var Public = &public{} diff --git a/core/account/users.go b/core/account/users.go index e94e170bb..65cb1a6c2 100644 --- a/core/account/users.go +++ b/core/account/users.go @@ -6,6 +6,7 @@ package account import ( "fmt" + "github.com/TheThingsNetwork/ttn/core/account/auth" "github.com/TheThingsNetwork/ttn/core/account/util" ) @@ -24,7 +25,7 @@ func RegisterUser(server, username, email, password string) error { Password: password, } - err := util.POST(server, "", "/api/users", user, nil) + err := util.POST(server, auth.Public, "/api/users", user, nil) if err != nil { return fmt.Errorf("Could not register user: %s", err) } diff --git a/core/account/util/http.go b/core/account/util/http.go index 5ba743dcf..471942ad5 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -10,6 +10,8 @@ import ( "fmt" "io" "net/http" + + "github.com/TheThingsNetwork/ttn/core/account/auth" ) var ( @@ -44,21 +46,20 @@ func checkRedirect(req *http.Request, via []*http.Request) error { } // NewRequest creates a new http.Request that has authorization set up -func newRequest(server string, accessToken string, method string, URI string, body io.Reader) (*http.Request, error) { +func newRequest(server, method string, URI string, body io.Reader) (*http.Request, error) { URL := fmt.Sprintf("%s%s", server, URI) req, err := http.NewRequest(method, URL, body) if err != nil { return nil, err } - req.Header.Set("Authorization", fmt.Sprintf("bearer %s", accessToken)) req.Header.Set("Accept", "application/json") req.Header.Set("Content-Type", "application/json") return req, nil } -func performRequest(server, accessToken, method, URI string, body, res interface{}, redirects int) (err error) { +func performRequest(server string, strategy auth.Strategy, method, URI string, body, res interface{}, redirects int) (err error) { var req *http.Request if body != nil { @@ -73,12 +74,15 @@ func performRequest(server, accessToken, method, URI string, body, res interface if err != nil { return err } - req, err = newRequest(server, accessToken, method, URI, buf) + req, err = newRequest(server, method, URI, buf) } else { // body is nil so create a nil request - req, err = newRequest(server, accessToken, method, URI, nil) + req, err = newRequest(server, method, URI, nil) } + // decorate the request + strategy.DecorateRequest(req) + if err != nil { return err } @@ -120,7 +124,7 @@ func performRequest(server, accessToken, method, URI string, body, res interface if resp.StatusCode == 307 { if redirects > 0 { location := resp.Header.Get("Location") - return performRequest(server, accessToken, method, location, body, res, redirects-1) + return performRequest(server, strategy, method, location, body, res, redirects-1) } return fmt.Errorf("Reached maximum number of redirects") } @@ -146,29 +150,29 @@ func performRequest(server, accessToken, method, URI string, body, res interface } // GET does a get request to the account server, decoding the result into the object pointed to byres -func GET(server, accessToken, URI string, res interface{}) error { - return performRequest(server, accessToken, "GET", URI, nil, res, MaxRedirects) +func GET(server string, strategy auth.Strategy, URI string, res interface{}) error { + return performRequest(server, strategy, "GET", URI, nil, res, MaxRedirects) } // DELETE does a delete request to the account server -func DELETE(server, accessToken, URI string) error { - return performRequest(server, accessToken, "DELETE", URI, nil, nil, MaxRedirects) +func DELETE(server string, strategy auth.Strategy, URI string) error { + return performRequest(server, strategy, "DELETE", URI, nil, nil, MaxRedirects) } // POST creates an HTTP Post request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres -func POST(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "POST", URI, body, res, MaxRedirects) +func POST(server string, strategy auth.Strategy, URI string, body, res interface{}) error { + return performRequest(server, strategy, "POST", URI, body, res, MaxRedirects) } // PUT creates an HTTP Put request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres -func PUT(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "PUT", URI, body, res, MaxRedirects) +func PUT(server string, strategy auth.Strategy, URI string, body, res interface{}) error { + return performRequest(server, strategy, "PUT", URI, body, res, MaxRedirects) } // PATCH creates an HTTP Patch request to the specified server, with the body // encoded as JSON, decoding the result into the object pointed to byres -func PATCH(server, accessToken, URI string, body, res interface{}) error { - return performRequest(server, accessToken, "PATCH", URI, body, res, MaxRedirects) +func PATCH(server string, strategy auth.Strategy, URI string, body, res interface{}) error { + return performRequest(server, strategy, "PATCH", URI, body, res, MaxRedirects) } diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go index 9905ae751..0dcc1c492 100644 --- a/core/account/util/http_test.go +++ b/core/account/util/http_test.go @@ -10,12 +10,14 @@ import ( "net/http/httptest" "testing" + "github.com/TheThingsNetwork/ttn/core/account/auth" . "github.com/smartystreets/assertions" ) var ( - url = "/foo" - token = "token" + url = "/foo" + token = "token" + tokenStrategy = auth.AccessToken(token) ) type OKResp struct { @@ -91,7 +93,7 @@ func TestGET(t *testing.T) { defer server.Close() var resp OKResp - err := GET(server.URL, token, url, &resp) + err := GET(server.URL, tokenStrategy, url, &resp) a.So(err, ShouldBeNil) a.So(resp.OK, ShouldEqual, token) } @@ -101,7 +103,7 @@ func TestGETDropResponse(t *testing.T) { server := httptest.NewServer(OKHandler(a, "GET")) defer server.Close() - err := GET(server.URL, token, url, nil) + err := GET(server.URL, tokenStrategy, url, nil) a.So(err, ShouldBeNil) } @@ -111,7 +113,7 @@ func TestGETIllegalResponse(t *testing.T) { defer server.Close() var resp FooResp - err := GET(server.URL, token, url, &resp) + err := GET(server.URL, tokenStrategy, url, &resp) a.So(err, ShouldNotBeNil) } @@ -121,7 +123,7 @@ func TestGETIllegalResponseIgnore(t *testing.T) { defer server.Close() var resp OKResp - err := GET(server.URL, token, url, &resp) + err := GET(server.URL, tokenStrategy, url, &resp) a.So(err, ShouldBeNil) } @@ -131,7 +133,7 @@ func TestGETRedirect(t *testing.T) { defer server.Close() var resp OKResp - err := GET(server.URL, token, url, &resp) + err := GET(server.URL, tokenStrategy, url, &resp) a.So(err, ShouldBeNil) } @@ -144,7 +146,7 @@ func TestPUT(t *testing.T) { body := FooResp{ Foo: token, } - err := PUT(server.URL, token, url, body, &resp) + err := PUT(server.URL, tokenStrategy, url, body, &resp) a.So(err, ShouldBeNil) a.So(resp.Foo, ShouldEqual, body.Foo) } @@ -156,7 +158,7 @@ func TestPUTIllegalRequest(t *testing.T) { var resp FooResp body := FooResp{} - err := PUT(server.URL, token, url, body, &resp) + err := PUT(server.URL, tokenStrategy, url, body, &resp) a.So(err, ShouldNotBeNil) } @@ -166,7 +168,7 @@ func TestPUTIllegalResponse(t *testing.T) { defer server.Close() var resp FooResp - err := PUT(server.URL, token, url, nil, &resp) + err := PUT(server.URL, tokenStrategy, url, nil, &resp) a.So(err, ShouldNotBeNil) } @@ -176,7 +178,7 @@ func TestPUTRedirect(t *testing.T) { defer server.Close() var resp FooResp - err := PUT(server.URL, token, url, nil, &resp) + err := PUT(server.URL, tokenStrategy, url, nil, &resp) a.So(err, ShouldBeNil) a.So(resp.Foo, ShouldEqual, token) } @@ -190,7 +192,7 @@ func TestPOST(t *testing.T) { body := FooResp{ Foo: token, } - err := POST(server.URL, token, url, body, &resp) + err := POST(server.URL, tokenStrategy, url, body, &resp) a.So(err, ShouldBeNil) a.So(resp.Foo, ShouldEqual, body.Foo) } @@ -202,7 +204,7 @@ func TestPOSTIllegalRequest(t *testing.T) { var resp FooResp body := FooResp{} - err := POST(server.URL, token, url, body, &resp) + err := POST(server.URL, tokenStrategy, url, body, &resp) a.So(err, ShouldNotBeNil) } @@ -212,7 +214,7 @@ func TestPOSTIllegalResponse(t *testing.T) { defer server.Close() var resp FooResp - err := POST(server.URL, token, url, nil, &resp) + err := POST(server.URL, tokenStrategy, url, nil, &resp) a.So(err, ShouldNotBeNil) } @@ -222,7 +224,7 @@ func TestPOSTRedirect(t *testing.T) { defer server.Close() var resp FooResp - err := POST(server.URL, token, url, nil, &resp) + err := POST(server.URL, tokenStrategy, url, nil, &resp) a.So(err, ShouldBeNil) a.So(resp.Foo, ShouldEqual, token) } @@ -232,7 +234,7 @@ func TestDELETE(t *testing.T) { server := httptest.NewServer(OKHandler(a, "DELETE")) defer server.Close() - err := DELETE(server.URL, token, url) + err := DELETE(server.URL, tokenStrategy, url) a.So(err, ShouldBeNil) } @@ -241,6 +243,6 @@ func TestDELETERedirect(t *testing.T) { server := httptest.NewServer(RedirectHandler(a, "DELETE")) defer server.Close() - err := DELETE(server.URL, token, url) + err := DELETE(server.URL, tokenStrategy, url) a.So(err, ShouldBeNil) } From cea4e5e7c57ec70fa23d12035f334379f7067b9c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 12 Sep 2016 15:29:02 +0200 Subject: [PATCH 1701/2266] Add extra types to pf Encode (#240) * Add test to reprocude uint8 case * Fix the test by adding missing type to the type switch * Remove fmt.Printf --- core/handler/convert_fields.go | 7 ++++++- core/handler/convert_fields_test.go | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 966280566..263afc5b2 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -228,6 +228,12 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro n = int64(t) case int: n = int64(t) + case int8: + n = int64(t) + case int16: + n = int64(t) + case uint16: + n = int64(t) case int32: n = int64(t) case uint32: @@ -247,7 +253,6 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } default: - fmt.Printf("VAL %v TYPE %T\n", el, el) return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index d90bd98f6..657ad90f4 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -4,6 +4,7 @@ package handler import ( + "fmt" "testing" "time" @@ -403,3 +404,20 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { _, _, err = functions.Process(map[string]interface{}{"key": 11}) a.So(err, ShouldNotBeNil) } + +func TestEncodeCharCode(t *testing.T) { + a := New(t) + + // return arr of charcodes + functions := &DownlinkFunctions{ + Encoder: `function Encoder(obj) { + return "Hi".split('').map(function(char) { + return char.charCodeAt(); + }); + }`, + } + val, _, err := functions.Process(map[string]interface{}{"key": 11}) + a.So(err, ShouldBeNil) + + fmt.Println("VALUE", val) +} From be817f74edda83983a6b963870b39231006df5af Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 12 Sep 2016 08:10:36 +0200 Subject: [PATCH 1702/2266] Add version number to Discovery Announcement --- cmd/version.go | 2 +- core/component.go | 9 +++++---- main.go | 2 +- ttnctl/cmd/version.go | 2 +- ttnctl/main.go | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index 64487342b..6109c41e4 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -18,7 +18,7 @@ var versionCmd = &cobra.Command{ ctx.WithFields(log.Fields{ "Commit": viper.GetString("gitCommit"), "BuildDate": viper.GetString("buildDate"), - }).Infof("You are running %s of ttn.", viper.GetString("version")) + }).Infof("You are running version %s of ttn.", viper.GetString("version")) }, } diff --git a/core/component.go b/core/component.go index 50c89b1a8..574693379 100644 --- a/core/component.go +++ b/core/component.go @@ -67,10 +67,11 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string component := &Component{ Ctx: ctx, Identity: &pb_discovery.Announcement{ - Id: viper.GetString("id"), - Description: viper.GetString("description"), - ServiceName: serviceName, - NetAddress: announcedAddress, + Id: viper.GetString("id"), + Description: viper.GetString("description"), + ServiceName: serviceName, + ServiceVersion: fmt.Sprintf("%s-%s (built %s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), + NetAddress: announcedAddress, }, AccessToken: viper.GetString("auth-token"), Discovery: discovery, diff --git a/main.go b/main.go index 263751043..c90c1c45a 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,7 @@ var ( ) func main() { - viper.Set("version", "v1-staging") + viper.Set("version", "2.0.0-dev") viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index 80a11895f..4081fc43c 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -18,7 +18,7 @@ var versionCmd = &cobra.Command{ ctx.WithFields(log.Fields{ "Commit": viper.GetString("gitCommit"), "BuildDate": viper.GetString("buildDate"), - }).Infof("You are running %s of ttnctl.", viper.GetString("version")) + }).Infof("You are running version %s of ttnctl.", viper.GetString("version")) }, } diff --git a/ttnctl/main.go b/ttnctl/main.go index c9be40b96..ec96f5a69 100644 --- a/ttnctl/main.go +++ b/ttnctl/main.go @@ -14,7 +14,7 @@ var ( ) func main() { - viper.Set("version", "v1-staging") + viper.Set("version", "2.0.0-dev") viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() From 30e37c412ae4481b9244acd74e5358ba55d94632 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 12 Sep 2016 17:07:15 +0200 Subject: [PATCH 1703/2266] Empty AppKey in ttnctl devices personalize CC: @FokkeZB --- ttnctl/cmd/devices_personalize.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index 555a1bc34..b06f26a8f 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -78,6 +78,8 @@ var devicesPersonalizeCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not request device address") } + var emptyAppKey types.AppKey + dev.GetLorawanDevice().AppKey = &emptyAppKey dev.GetLorawanDevice().DevAddr = &devAddr dev.GetLorawanDevice().NwkSKey = &nwkSKey dev.GetLorawanDevice().AppSKey = &appSKey From c392310d998c4976efd2a1dcfa620473c9d0cec9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 11:43:59 +0200 Subject: [PATCH 1704/2266] Publish uplink Fields to MQTT --- core/handler/mqtt.go | 20 ++++++-- core/handler/mqtt_test.go | 3 ++ mqtt/README.md | 26 ++++++++++ mqtt/client.go | 105 ++++++++++++++++++++++++++++++++++---- mqtt/client_test.go | 66 ++++++++++++++++++++++++ mqtt/topics.go | 15 ++++-- mqtt/topics_test.go | 26 +++++----- 7 files changed, 232 insertions(+), 29 deletions(-) diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 6cd4e8e65..423dcf359 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -44,16 +44,28 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e "DevID": up.DevID, "AppID": up.AppID, }).Debug("Publish Uplink") - token := h.mqttClient.PublishUplink(*up) + upToken := h.mqttClient.PublishUplink(*up) go func() { - if token.WaitTimeout(MQTTTimeout) { - if token.Error() != nil { - h.Ctx.WithError(token.Error()).Warn("Could not publish Uplink") + if upToken.WaitTimeout(MQTTTimeout) { + if upToken.Error() != nil { + h.Ctx.WithError(upToken.Error()).Warn("Could not publish Uplink") } } else { h.Ctx.Warn("Uplink publish timeout") } }() + if len(up.Fields) > 0 { + fieldsToken := h.mqttClient.PublishUplinkFields(up.AppID, up.DevID, up.Fields) + go func() { + if fieldsToken.WaitTimeout(MQTTTimeout) { + if fieldsToken.Error() != nil { + h.Ctx.WithError(fieldsToken.Error()).Warn("Could not publish Uplink Fields") + } + } else { + h.Ctx.Warn("Uplink Fields publish timeout") + } + }() + } } }() diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index c93334e27..e8bf94bae 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -60,6 +60,9 @@ func TestHandleMQTT(t *testing.T) { DevID: devID, AppID: appID, Payload: []byte{0xAA, 0xBC}, + Fields: map[string]interface{}{ + "field": "value", + }, } wg.Add(1) diff --git a/mqtt/README.md b/mqtt/README.md index d51d6f957..b50ce1c78 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -59,6 +59,32 @@ if err := token.Error(); err != nil { } ``` +### Uplink Fields + +Each uplink field will be published to its own topic `my-app-id/devices/my-dev-id/up/`. The payload will be a string with the value in a JSON-style encoding. + +If your fields look like the following: + +```js +{ + "water": true, + "analog": [0, 255, 500, 1000], + "gps": { + "lat": 52.3736735, + "lon": 4.886663 + }, + "text": "why are you using text?" +} +``` + +you will see this on MQTT: + +* `my-app-id/devices/my-dev-id/up/water`: `true` +* `my-app-id/devices/my-dev-id/up/analog`: `[0, 255, 500, 1000]` +* `my-app-id/devices/my-dev-id/up/gps/lat`: `52.3736735` +* `my-app-id/devices/my-dev-id/up/gps/lon`: `4.886663` +* `my-app-id/devices/my-dev-id/up/text`: `"why are you using text?"` + ## Downlink Messages **Topic:** `/devices//down` diff --git a/mqtt/client.go b/mqtt/client.go index f17cf9832..93eb21241 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -6,6 +6,7 @@ package mqtt import ( "encoding/json" "fmt" + "sync" "time" "github.com/TheThingsNetwork/ttn/utils/random" @@ -24,6 +25,7 @@ type Client interface { // Uplink pub/sub PublishUplink(payload UplinkMessage) Token + PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token SubscribeAppUplink(appID string, handler UplinkHandler) Token SubscribeUplink(handler UplinkHandler) Token @@ -79,6 +81,52 @@ func (t *simpleToken) Error() error { return t.err } +type token struct { + sync.RWMutex + complete chan bool + ready bool + err error +} + +func newToken() *token { + return &token{ + complete: make(chan bool), + } +} + +func (t *token) Wait() bool { + t.Lock() + defer t.Unlock() + if !t.ready { + <-t.complete + t.ready = true + } + return t.ready +} + +func (t *token) WaitTimeout(d time.Duration) bool { + t.Lock() + defer t.Unlock() + if !t.ready { + select { + case <-t.complete: + t.ready = true + case <-time.After(d): + } + } + return t.ready +} + +func (t *token) flowComplete() { + close(t.complete) +} + +func (t *token) Error() error { + t.RLock() + defer t.RUnlock() + return t.err +} + // UplinkHandler is called for uplink messages type UplinkHandler func(client Client, appID string, devID string, req UplinkMessage) @@ -204,7 +252,7 @@ func (c *DefaultClient) IsConnected() bool { // PublishUplink publishes an uplink message to the MQTT broker func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { - topic := DeviceTopic{dataUp.AppID, dataUp.DevID, Uplink} + topic := DeviceTopic{dataUp.AppID, dataUp.DevID, Uplink, ""} dataUp.AppID = "" dataUp.DevID = "" msg, err := json.Marshal(dataUp) @@ -214,9 +262,48 @@ func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { return c.mqtt.Publish(topic.String(), QoS, false, msg) } +// PublishUplinkFields publishes uplink fields to MQTT +func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token { + flattenedFields := make(map[string]interface{}) + flatten("", "/", fields, flattenedFields) + tokens := make([]Token, 0, len(flattenedFields)) + for field, value := range flattenedFields { + topic := DeviceTopic{appID, devID, Uplink, field} + pld, _ := json.Marshal(value) + token := c.mqtt.Publish(topic.String(), QoS, false, pld) + tokens = append(tokens, token) + } + t := newToken() + go func() { + for _, token := range tokens { + token.Wait() + if token.Error() != nil { + fmt.Println(token.Error()) + t.err = token.Error() + } + } + t.complete <- true + }() + return t +} + +func flatten(prefix, sep string, in, out map[string]interface{}) { + for k, v := range in { + key := prefix + sep + k + if prefix == "" { + key = k + } + if next, ok := v.(map[string]interface{}); ok { + flatten(key, sep, next, out) + } else { + out[key] = v + } + } +} + // SubscribeDeviceUplink subscribes to all uplink messages for the given application and device func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { - topic := DeviceTopic{appID, devID, Uplink} + topic := DeviceTopic{appID, devID, Uplink, ""} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) @@ -253,7 +340,7 @@ func (c *DefaultClient) SubscribeUplink(handler UplinkHandler) Token { // UnsubscribeDeviceUplink unsubscribes from the uplink messages for the given application and device func (c *DefaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { - topic := DeviceTopic{appID, devID, Uplink} + topic := DeviceTopic{appID, devID, Uplink, ""} return c.unsubscribe(topic.String()) } @@ -269,7 +356,7 @@ func (c *DefaultClient) UnsubscribeUplink() Token { // PublishDownlink publishes a downlink message func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { - topic := DeviceTopic{dataDown.AppID, dataDown.DevID, Downlink} + topic := DeviceTopic{dataDown.AppID, dataDown.DevID, Downlink, ""} dataDown.AppID = "" dataDown.DevID = "" msg, err := json.Marshal(dataDown) @@ -281,7 +368,7 @@ func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { // SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { - topic := DeviceTopic{appID, devID, Downlink} + topic := DeviceTopic{appID, devID, Downlink, ""} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) @@ -317,7 +404,7 @@ func (c *DefaultClient) SubscribeDownlink(handler DownlinkHandler) Token { // UnsubscribeDeviceDownlink unsubscribes from the downlink messages for the given application and device func (c *DefaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { - topic := DeviceTopic{appID, devID, Downlink} + topic := DeviceTopic{appID, devID, Downlink, ""} return c.unsubscribe(topic.String()) } @@ -333,7 +420,7 @@ func (c *DefaultClient) UnsubscribeDownlink() Token { // PublishActivation publishes an activation func (c *DefaultClient) PublishActivation(activation Activation) Token { - topic := DeviceTopic{activation.AppID, activation.DevID, Activations} + topic := DeviceTopic{activation.AppID, activation.DevID, Activations, ""} activation.AppID = "" activation.DevID = "" msg, err := json.Marshal(activation) @@ -345,7 +432,7 @@ func (c *DefaultClient) PublishActivation(activation Activation) Token { // SubscribeDeviceActivations subscribes to all activations for the given application and device func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { - topic := DeviceTopic{appID, devID, Activations} + topic := DeviceTopic{appID, devID, Activations, ""} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) @@ -381,7 +468,7 @@ func (c *DefaultClient) SubscribeActivations(handler ActivationHandler) Token { // UnsubscribeDeviceActivations unsubscribes from the activations for the given application and device func (c *DefaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { - topic := DeviceTopic{appID, devID, Activations} + topic := DeviceTopic{appID, devID, Activations, ""} return c.unsubscribe(topic.String()) } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 5396f1ea8..83c0d686b 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -6,11 +6,13 @@ package mqtt import ( "fmt" "os" + "strings" "testing" "time" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" + MQTT "github.com/eclipse/paho.mqtt.golang" . "github.com/smartystreets/assertions" ) @@ -130,6 +132,70 @@ func TestPublishUplink(t *testing.T) { a.So(token.Error(), ShouldBeNil) } +func TestPublishUplinkFields(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + waitChan := make(chan bool, 1) + expected := 8 + c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { + + switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { + case "battery": + a.So(string(msg.Payload()), ShouldEqual, "90") + case "sensors/color": + a.So(string(msg.Payload()), ShouldEqual, `"blue"`) + case "sensors/people": + a.So(string(msg.Payload()), ShouldEqual, `["bob","alice"]`) + case "sensors/water": + a.So(string(msg.Payload()), ShouldEqual, "true") + case "sensors/analog": + a.So(string(msg.Payload()), ShouldEqual, `[0,255,500,1000]`) + case "sensors/history/today": + a.So(string(msg.Payload()), ShouldEqual, `"not yet"`) + case "sensors/history/yesterday": + a.So(string(msg.Payload()), ShouldEqual, `"absolutely"`) + case "gps": + a.So(string(msg.Payload()), ShouldEqual, "[52.3736735,4.886663]") + default: + t.Errorf("Should not have received message on topic %s", msg.Topic()) + t.Fail() + } + + expected-- + if expected == 0 { + waitChan <- true + } + }).Wait() + + fields := map[string]interface{}{ + "battery": 90, + "sensors": map[string]interface{}{ + "color": "blue", + "people": []string{"bob", "alice"}, + "water": true, + "analog": []int{0, 255, 500, 1000}, + "history": map[string]interface{}{ + "today": "not yet", + "yesterday": "absolutely", + }, + }, + "gps": []float64{52.3736735, 4.886663}, + } + + token := c.PublishUplinkFields("fields-app", "fields-dev", fields) + token.Wait() + a.So(token.Error(), ShouldBeNil) + + select { + case <-waitChan: + case <-time.After(1 * time.Second): + panic("Did not receive fields") + } +} + func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) diff --git a/mqtt/topics.go b/mqtt/topics.go index e7b492a9a..3357e5582 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -27,11 +27,12 @@ type DeviceTopic struct { AppID string DevID string Type DeviceTopicType + Field string } // ParseDeviceTopic parses an MQTT device topic string to a DeviceTopic struct func ParseDeviceTopic(topic string) (*DeviceTopic, error) { - pattern := regexp.MustCompile("^([[:alnum:]](?:[_-]?[[:alnum:]]){1,35}|\\+)/(devices)/([[:alnum:]](?:[_-]?[[:alnum:]]){1,35}|\\+)/(activations|up|down)$") + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(devices)/([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(activations|up|down)([0-9a-z/]+)?$") matches := pattern.FindStringSubmatch(topic) if len(matches) < 4 { return nil, fmt.Errorf("Invalid topic format") @@ -45,7 +46,11 @@ func ParseDeviceTopic(topic string) (*DeviceTopic, error) { devID = matches[3] } topicType := DeviceTopicType(matches[4]) - return &DeviceTopic{appID, devID, topicType}, nil + deviceTopic := &DeviceTopic{appID, devID, topicType, ""} + if topicType == Uplink && len(matches) > 4 { + deviceTopic.Field = matches[5] + } + return deviceTopic, nil } // String implements the Stringer interface @@ -58,5 +63,9 @@ func (t DeviceTopic) String() string { if t.DevID != "" { devID = t.DevID } - return fmt.Sprintf("%s/%s/%s/%s", appID, "devices", devID, t.Type) + topic := fmt.Sprintf("%s/%s/%s/%s", appID, "devices", devID, t.Type) + if t.Type == Uplink && t.Field != "" { + topic += "/" + t.Field + } + return topic } diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index cdeffe616..a53dc090d 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -12,11 +12,11 @@ import ( func TestParseDeviceTopic(t *testing.T) { a := New(t) - topic := "AppID-1/devices/DevID-1/up" + topic := "appid-1/devices/devid-1/up" expected := &DeviceTopic{ - AppID: "AppID-1", - DevID: "DevID-1", + AppID: "appid-1", + DevID: "devid-1", Type: Uplink, } @@ -29,16 +29,16 @@ func TestParseDeviceTopic(t *testing.T) { func TestParseDeviceTopicInvalid(t *testing.T) { a := New(t) - _, err := ParseDeviceTopic("AppID:Invalid/devices/dev/up") + _, err := ParseDeviceTopic("appid:Invalid/devices/dev/up") a.So(err, ShouldNotBeNil) - _, err = ParseDeviceTopic("AppID-1/devices/DevID:Invalid/up") // DevEUI contains lowercase hex chars + _, err = ParseDeviceTopic("appid-1/devices/devid:Invalid/up") // DevEUI contains lowercase hex chars a.So(err, ShouldNotBeNil) - _, err = ParseDeviceTopic("AppID-1/fridges/DevID-1/up") // We don't support fridges (at least, not specifically fridges) + _, err = ParseDeviceTopic("appid-1/fridges/devid-1/up") // We don't support fridges (at least, not specifically fridges) a.So(err, ShouldNotBeNil) - _, err = ParseDeviceTopic("AppID-1/devices/DevID-1/emotions") // Devices usually don't publish emotions + _, err = ParseDeviceTopic("appid-1/devices/devid-1/emotions") // Devices usually don't publish emotions a.So(err, ShouldNotBeNil) } @@ -46,12 +46,12 @@ func TestTopicString(t *testing.T) { a := New(t) topic := &DeviceTopic{ - AppID: "AppID-1", - DevID: "DevID-1", + AppID: "appid-1", + DevID: "devid-1", Type: Downlink, } - expected := "AppID-1/devices/DevID-1/down" + expected := "appid-1/devices/devid-1/down" got := topic.String() @@ -63,9 +63,9 @@ func TestTopicParseAndString(t *testing.T) { expectedList := []string{ // Uppercase (not lowercase) - "0102030405060708/devices/ABCDABCD12345678/up", - "0102030405060708/devices/ABCDABCD12345678/down", - "0102030405060708/devices/ABCDABCD12345678/activations", + "0102030405060708/devices/abcdabcd12345678/up", + "0102030405060708/devices/abcdabcd12345678/down", + "0102030405060708/devices/abcdabcd12345678/activations", // Numbers "0102030405060708/devices/0000000012345678/up", "0102030405060708/devices/0000000012345678/down", From 5822a76af5643c1b815b01cfbd42dff7fe7283dd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 11:48:22 +0200 Subject: [PATCH 1705/2266] Use token flowComplete instead of channel access --- mqtt/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt/client.go b/mqtt/client.go index 93eb21241..aab5f6241 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -282,7 +282,7 @@ func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields m t.err = token.Error() } } - t.complete <- true + t.flowComplete() }() return t } From 9955a22f37dcc1fa93ec898c1d892281b1df5c3c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 13:38:33 +0200 Subject: [PATCH 1706/2266] Use lowercase AppIDs and DevIDs --- api/validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/validation.go b/api/validation.go index 7ef0aaec2..74bca11e9 100644 --- a/api/validation.go +++ b/api/validation.go @@ -2,7 +2,7 @@ package api import "regexp" -var idRegexp = regexp.MustCompile("^[[:alnum:]](?:[_-]?[[:alnum:]]){1,35}$") +var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") // ValidID returns true if the given ID is a valid application or device ID func ValidID(id string) bool { From 986b4b53e793f0ec9c5765e9e8deb20143c3ceca Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 14:17:33 +0200 Subject: [PATCH 1707/2266] Re-implement Discovery Client --- Makefile | 16 +- api/discovery/announcement.go | 25 ++ api/discovery/client.go | 218 ++++++++++++++++ api/discovery/client_mock.go | 115 +++++++++ api/networkserver/networkserver_mock.go | 258 +++++++++++++++++++ check_upstream.sh | 12 + core/broker/activation.go | 2 +- core/broker/activation_test.go | 44 ++-- core/broker/broker.go | 5 +- core/broker/manager_server.go | 7 +- core/broker/uplink.go | 2 +- core/broker/uplink_test.go | 86 ++++--- core/broker/util_test.go | 44 ++++ core/component.go | 34 ++- core/discovery/broker_discovery.go | 87 ------- core/discovery/broker_discovery_test.go | 132 ---------- core/discovery/discovery_integration_test.go | 44 ---- core/discovery/handler_discovery.go | 127 --------- core/discovery/handler_discovery_test.go | 124 --------- core/discovery/server.go | 10 +- core/handler/manager_server.go | 23 +- core/router/activation.go | 2 +- core/router/activation_test.go | 1 - core/router/downlink_test.go | 6 +- core/router/router.go | 13 +- core/router/uplink.go | 2 +- core/router/uplink_test.go | 20 +- core/router/util_test.go | 37 +++ utils/security/jwt.go | 2 +- 29 files changed, 838 insertions(+), 660 deletions(-) create mode 100644 api/discovery/client.go create mode 100644 api/discovery/client_mock.go create mode 100644 api/networkserver/networkserver_mock.go create mode 100755 check_upstream.sh create mode 100644 core/broker/util_test.go delete mode 100644 core/discovery/broker_discovery.go delete mode 100644 core/discovery/broker_discovery_test.go delete mode 100644 core/discovery/discovery_integration_test.go delete mode 100644 core/discovery/handler_discovery.go delete mode 100644 core/discovery/handler_discovery_test.go create mode 100644 core/router/util_test.go diff --git a/Makefile b/Makefile index 1cdea14d4..515310edc 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps update-deps test-deps proto-deps dev-deps cover-deps proto test fmt vet cover coveralls build install docker package +.PHONY: all clean deps update-deps test-deps dev-deps cover-deps proto test fmt vet cover coveralls build install docker package all: clean deps build package @@ -44,11 +44,11 @@ update-deps: test-deps: $(GOCMD) get -d -v $(TEST_DEPS) -proto-deps: - $(GOCMD) get -v github.com/gogo/protobuf/protoc-gen-gofast - -dev-deps: update-deps proto-deps test-deps - $(GOCMD) get -v github.com/ddollar/forego +dev-deps: update-deps test-deps + $(GOCMD) get -u -v github.com/gogo/protobuf/protoc-gen-gofast + $(GOCMD) get -u -v github.com/golang/mock/gomock + $(GOCMD) get -u -v github.com/golang/mock/mockgen + $(GOCMD) get -u -v github.com/ddollar/forego cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi @@ -66,6 +66,10 @@ proto: @$(PROTOC)/api/discovery/discovery.proto @$(PROTOC)/api/noc/noc.proto +mocks: + mockgen -source=./api/networkserver/networkserver.pb.go -package networkserver NetworkServerClient > api/networkserver/networkserver_mock.go + mockgen -source=./api/discovery/client.go -package discovery NetworkServerClient > api/discovery/client_mock.go + test: $(select_pkgs) | xargs $(GOCMD) test diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go index ed491c75c..c43bd38dc 100644 --- a/api/discovery/announcement.go +++ b/api/discovery/announcement.go @@ -4,6 +4,7 @@ package discovery import ( + "bytes" "encoding/json" "fmt" ) @@ -102,3 +103,27 @@ func (announcement *Announcement) parseProperty(property string, value string) e } return nil } + +// AddMetadata adds metadata to the announcement if it doesn't exist +func (announcement *Announcement) AddMetadata(key Metadata_Key, value []byte) { + for _, meta := range announcement.Metadata { + if meta.Key == key && bytes.Equal(value, meta.Value) { + return + } + } + announcement.Metadata = append(announcement.Metadata, &Metadata{ + Key: key, + Value: value, + }) +} + +// DeleteMetadata deletes metadata from the announcement if it exists +func (announcement *Announcement) DeleteMetadata(key Metadata_Key, value []byte) { + newMeta := make([]*Metadata, 0, len(announcement.Metadata)) + for _, meta := range announcement.Metadata { + if !(meta.Key == key && bytes.Equal(value, meta.Value)) { + newMeta = append(newMeta, meta) + } + } + announcement.Metadata = newMeta +} diff --git a/api/discovery/client.go b/api/discovery/client.go new file mode 100644 index 000000000..0d613c110 --- /dev/null +++ b/api/discovery/client.go @@ -0,0 +1,218 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package discovery + +// Client is used to manage applications and devices on a handler +import ( + "fmt" + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/bluele/gcache" + + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/metadata" +) + +// CacheSize indicates the number of components that are cached +var CacheSize = 1000 + +// CacheExpiration indicates the time a cached item is valid +var CacheExpiration = 5 * time.Minute + +type Client interface { + Announce(token string) error + GetAll(serviceName string) ([]*Announcement, error) + Get(serviceName, id string) (*Announcement, error) + AddMetadata(key Metadata_Key, value []byte, token string) error + DeleteMetadata(key Metadata_Key, value []byte, token string) error + GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func(value []byte) bool) ([]*Announcement, error) + GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) + GetAllHandlersForAppID(appID string) ([]*Announcement, error) +} + +// NewClient returns a new Client +func NewClient(conn *grpc.ClientConn, announcement *Announcement, tokenFunc func() string) Client { + client := &DefaultClient{ + lists: make(map[string][]*Announcement), + listsUpdated: make(map[string]time.Time), + self: announcement, + tokenFunc: tokenFunc, + conn: conn, + client: NewDiscoveryClient(conn), + } + client.cache = gcache. + New(CacheSize). + Expiration(CacheExpiration). + ARC(). + LoaderFunc(func(k interface{}) (interface{}, error) { + key, ok := k.(cacheKey) + if !ok { + return nil, fmt.Errorf("wrong type for cacheKey: %T", k) + } + return client.get(key.serviceName, key.id) + }). + Build() + return client +} + +// DefaultClient is a wrapper around DiscoveryClient +type DefaultClient struct { + sync.Mutex + cache gcache.Cache + listsUpdated map[string]time.Time + lists map[string][]*Announcement + self *Announcement + tokenFunc func() string + conn *grpc.ClientConn + client DiscoveryClient +} + +type cacheKey struct { + serviceName string + id string +} + +func (c *DefaultClient) getContext(token string) context.Context { + if token == "" { + token = c.tokenFunc() + } + md := metadata.Pairs( + "service-name", c.self.ServiceName, + "id", c.self.Id, + "token", token, + "net-address", c.self.NetAddress, + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} + +func (c *DefaultClient) get(serviceName, id string) (*Announcement, error) { + res, err := c.client.Get(c.getContext(""), &GetRequest{ + ServiceName: serviceName, + Id: id, + }) + if err != nil { + return nil, err + } + return res, nil +} + +func (c *DefaultClient) getAll(serviceName string) ([]*Announcement, error) { + res, err := c.client.GetAll(c.getContext(""), &GetAllRequest{ServiceName: serviceName}) + if err != nil { + return nil, err + } + c.lists[serviceName] = res.Services + c.listsUpdated[serviceName] = time.Now() + for _, announcement := range res.Services { + c.cache.Set(&cacheKey{serviceName: announcement.ServiceName, id: announcement.Id}, announcement) + } + return res.Services, nil +} + +// Announce announces the configured announcement to the discovery server +func (c *DefaultClient) Announce(token string) error { + _, err := c.client.Announce(c.getContext(token), c.self) + return err +} + +// GetAll returns all services of the given service type +func (c *DefaultClient) GetAll(serviceName string) ([]*Announcement, error) { + c.Lock() + defer c.Unlock() + + // If list initialized, return cached version + if list, ok := c.lists[serviceName]; ok && len(list) > 0 { + // And update if expired + if c.listsUpdated[serviceName].Add(CacheExpiration).After(time.Now()) { + go func() { + c.Lock() + defer c.Unlock() + c.getAll(serviceName) + }() + } + return list, nil + } + + // If list not initialized, do request + return c.getAll(serviceName) +} + +// Get returns the (cached) service annoucement for the given service type and id +func (c *DefaultClient) Get(serviceName, id string) (*Announcement, error) { + res, err := c.cache.Get(cacheKey{serviceName, id}) + if err != nil { + return nil, err + } + return res.(*Announcement), nil +} + +// AddMetadata publishes metadata for the current component to the Discovery server +func (c *DefaultClient) AddMetadata(key Metadata_Key, value []byte, token string) error { + _, err := c.client.AddMetadata(c.getContext(token), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{ + Key: key, + Value: value, + }, + }) + return err +} + +// DeleteMetadata deletes metadata for the current component from the Discovery server +func (c *DefaultClient) DeleteMetadata(key Metadata_Key, value []byte, token string) error { + _, err := c.client.DeleteMetadata(c.getContext(token), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{ + Key: key, + Value: value, + }, + }) + return err +} + +// GetAllForMetadata returns all annoucements of given type that contain given metadata and match the given function +func (c *DefaultClient) GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func(value []byte) bool) ([]*Announcement, error) { + announcements, err := c.GetAll(serviceName) + if err != nil { + return nil, err + } + res := make([]*Announcement, 0, len(announcements)) +nextAnnouncement: + for _, announcement := range announcements { + for _, meta := range announcement.Metadata { + if meta.Key == key && matchFunc(meta.Value) { + res = append(res, announcement) + continue nextAnnouncement + } + } + } + return res, nil +} + +// GetAllBrokersForDevAddr returns all brokers that can handle the given DevAddr +func (c *DefaultClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) { + return c.GetAllForMetadata("broker", Metadata_PREFIX, func(value []byte) bool { + if len(value) != 5 { + return false + } + var prefix types.DevAddrPrefix + copy(prefix.DevAddr[:], value[1:]) + prefix.Length = int(value[0]) + return devAddr.HasPrefix(prefix) + }) +} + +// GetAllHandlersForAppID returns all handlers that can handle the given AppID +func (c *DefaultClient) GetAllHandlersForAppID(appID string) ([]*Announcement, error) { + return c.GetAllForMetadata("handler", Metadata_APP_ID, func(value []byte) bool { + return string(value) == appID + }) +} diff --git a/api/discovery/client_mock.go b/api/discovery/client_mock.go new file mode 100644 index 000000000..066c17aff --- /dev/null +++ b/api/discovery/client_mock.go @@ -0,0 +1,115 @@ +// Automatically generated by MockGen. DO NOT EDIT! +// Source: ./api/discovery/client.go + +package discovery + +import ( + types "github.com/TheThingsNetwork/ttn/core/types" + gomock "github.com/golang/mock/gomock" +) + +// Mock of Client interface +type MockClient struct { + ctrl *gomock.Controller + recorder *_MockClientRecorder +} + +// Recorder for MockClient (not exported) +type _MockClientRecorder struct { + mock *MockClient +} + +func NewMockClient(ctrl *gomock.Controller) *MockClient { + mock := &MockClient{ctrl: ctrl} + mock.recorder = &_MockClientRecorder{mock} + return mock +} + +func (_m *MockClient) EXPECT() *_MockClientRecorder { + return _m.recorder +} + +func (_m *MockClient) Announce(token string) error { + ret := _m.ctrl.Call(_m, "Announce", token) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) Announce(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Announce", arg0) +} + +func (_m *MockClient) GetAll(serviceName string) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAll", serviceName) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAll(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAll", arg0) +} + +func (_m *MockClient) Get(serviceName string, id string) (*Announcement, error) { + ret := _m.ctrl.Call(_m, "Get", serviceName, id) + ret0, _ := ret[0].(*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) Get(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Get", arg0, arg1) +} + +func (_m *MockClient) AddMetadata(key Metadata_Key, value []byte, token string) error { + ret := _m.ctrl.Call(_m, "AddMetadata", key, value, token) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) AddMetadata(arg0, arg1, arg2 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "AddMetadata", arg0, arg1, arg2) +} + +func (_m *MockClient) DeleteMetadata(key Metadata_Key, value []byte, token string) error { + ret := _m.ctrl.Call(_m, "DeleteMetadata", key, value, token) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) DeleteMetadata(arg0, arg1, arg2 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "DeleteMetadata", arg0, arg1, arg2) +} + +func (_m *MockClient) GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func([]byte) bool) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAllForMetadata", serviceName, key, matchFunc) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAllForMetadata(arg0, arg1, arg2 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllForMetadata", arg0, arg1, arg2) +} + +func (_m *MockClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAllBrokersForDevAddr", devAddr) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAllBrokersForDevAddr(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllBrokersForDevAddr", arg0) +} + +func (_m *MockClient) GetAllHandlersForAppID(appID string) ([]*Announcement, error) { + ret := _m.ctrl.Call(_m, "GetAllHandlersForAppID", appID) + ret0, _ := ret[0].([]*Announcement) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockClientRecorder) GetAllHandlersForAppID(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllHandlersForAppID", arg0) +} diff --git a/api/networkserver/networkserver_mock.go b/api/networkserver/networkserver_mock.go new file mode 100644 index 000000000..1d38ef7f8 --- /dev/null +++ b/api/networkserver/networkserver_mock.go @@ -0,0 +1,258 @@ +// Automatically generated by MockGen. DO NOT EDIT! +// Source: ./api/networkserver/networkserver.pb.go + +package networkserver + +import ( + broker "github.com/TheThingsNetwork/ttn/api/broker" + handler "github.com/TheThingsNetwork/ttn/api/handler" + gomock "github.com/golang/mock/gomock" + context "golang.org/x/net/context" + grpc "google.golang.org/grpc" +) + +// Mock of NetworkServerClient interface +type MockNetworkServerClient struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerClientRecorder +} + +// Recorder for MockNetworkServerClient (not exported) +type _MockNetworkServerClientRecorder struct { + mock *MockNetworkServerClient +} + +func NewMockNetworkServerClient(ctrl *gomock.Controller) *MockNetworkServerClient { + mock := &MockNetworkServerClient{ctrl: ctrl} + mock.recorder = &_MockNetworkServerClientRecorder{mock} + return mock +} + +func (_m *MockNetworkServerClient) EXPECT() *_MockNetworkServerClientRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerClient) GetDevices(ctx context.Context, in *DevicesRequest, opts ...grpc.CallOption) (*DevicesResponse, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "GetDevices", _s...) + ret0, _ := ret[0].(*DevicesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) GetDevices(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetDevices", _s...) +} + +func (_m *MockNetworkServerClient) PrepareActivation(ctx context.Context, in *broker.DeduplicatedDeviceActivationRequest, opts ...grpc.CallOption) (*broker.DeduplicatedDeviceActivationRequest, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "PrepareActivation", _s...) + ret0, _ := ret[0].(*broker.DeduplicatedDeviceActivationRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) PrepareActivation(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "PrepareActivation", _s...) +} + +func (_m *MockNetworkServerClient) Activate(ctx context.Context, in *handler.DeviceActivationResponse, opts ...grpc.CallOption) (*handler.DeviceActivationResponse, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "Activate", _s...) + ret0, _ := ret[0].(*handler.DeviceActivationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) Activate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "Activate", _s...) +} + +func (_m *MockNetworkServerClient) Uplink(ctx context.Context, in *broker.DeduplicatedUplinkMessage, opts ...grpc.CallOption) (*broker.DeduplicatedUplinkMessage, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "Uplink", _s...) + ret0, _ := ret[0].(*broker.DeduplicatedUplinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) Uplink(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "Uplink", _s...) +} + +func (_m *MockNetworkServerClient) Downlink(ctx context.Context, in *broker.DownlinkMessage, opts ...grpc.CallOption) (*broker.DownlinkMessage, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "Downlink", _s...) + ret0, _ := ret[0].(*broker.DownlinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerClientRecorder) Downlink(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "Downlink", _s...) +} + +// Mock of NetworkServerServer interface +type MockNetworkServerServer struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerServerRecorder +} + +// Recorder for MockNetworkServerServer (not exported) +type _MockNetworkServerServerRecorder struct { + mock *MockNetworkServerServer +} + +func NewMockNetworkServerServer(ctrl *gomock.Controller) *MockNetworkServerServer { + mock := &MockNetworkServerServer{ctrl: ctrl} + mock.recorder = &_MockNetworkServerServerRecorder{mock} + return mock +} + +func (_m *MockNetworkServerServer) EXPECT() *_MockNetworkServerServerRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerServer) GetDevices(_param0 context.Context, _param1 *DevicesRequest) (*DevicesResponse, error) { + ret := _m.ctrl.Call(_m, "GetDevices", _param0, _param1) + ret0, _ := ret[0].(*DevicesResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) GetDevices(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetDevices", arg0, arg1) +} + +func (_m *MockNetworkServerServer) PrepareActivation(_param0 context.Context, _param1 *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { + ret := _m.ctrl.Call(_m, "PrepareActivation", _param0, _param1) + ret0, _ := ret[0].(*broker.DeduplicatedDeviceActivationRequest) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) PrepareActivation(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "PrepareActivation", arg0, arg1) +} + +func (_m *MockNetworkServerServer) Activate(_param0 context.Context, _param1 *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { + ret := _m.ctrl.Call(_m, "Activate", _param0, _param1) + ret0, _ := ret[0].(*handler.DeviceActivationResponse) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) Activate(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Activate", arg0, arg1) +} + +func (_m *MockNetworkServerServer) Uplink(_param0 context.Context, _param1 *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { + ret := _m.ctrl.Call(_m, "Uplink", _param0, _param1) + ret0, _ := ret[0].(*broker.DeduplicatedUplinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) Uplink(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Uplink", arg0, arg1) +} + +func (_m *MockNetworkServerServer) Downlink(_param0 context.Context, _param1 *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { + ret := _m.ctrl.Call(_m, "Downlink", _param0, _param1) + ret0, _ := ret[0].(*broker.DownlinkMessage) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerServerRecorder) Downlink(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Downlink", arg0, arg1) +} + +// Mock of NetworkServerManagerClient interface +type MockNetworkServerManagerClient struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerManagerClientRecorder +} + +// Recorder for MockNetworkServerManagerClient (not exported) +type _MockNetworkServerManagerClientRecorder struct { + mock *MockNetworkServerManagerClient +} + +func NewMockNetworkServerManagerClient(ctrl *gomock.Controller) *MockNetworkServerManagerClient { + mock := &MockNetworkServerManagerClient{ctrl: ctrl} + mock.recorder = &_MockNetworkServerManagerClientRecorder{mock} + return mock +} + +func (_m *MockNetworkServerManagerClient) EXPECT() *_MockNetworkServerManagerClientRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) { + _s := []interface{}{ctx, in} + for _, _x := range opts { + _s = append(_s, _x) + } + ret := _m.ctrl.Call(_m, "GetStatus", _s...) + ret0, _ := ret[0].(*Status) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerManagerClientRecorder) GetStatus(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call { + _s := append([]interface{}{arg0, arg1}, arg2...) + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetStatus", _s...) +} + +// Mock of NetworkServerManagerServer interface +type MockNetworkServerManagerServer struct { + ctrl *gomock.Controller + recorder *_MockNetworkServerManagerServerRecorder +} + +// Recorder for MockNetworkServerManagerServer (not exported) +type _MockNetworkServerManagerServerRecorder struct { + mock *MockNetworkServerManagerServer +} + +func NewMockNetworkServerManagerServer(ctrl *gomock.Controller) *MockNetworkServerManagerServer { + mock := &MockNetworkServerManagerServer{ctrl: ctrl} + mock.recorder = &_MockNetworkServerManagerServerRecorder{mock} + return mock +} + +func (_m *MockNetworkServerManagerServer) EXPECT() *_MockNetworkServerManagerServerRecorder { + return _m.recorder +} + +func (_m *MockNetworkServerManagerServer) GetStatus(_param0 context.Context, _param1 *StatusRequest) (*Status, error) { + ret := _m.ctrl.Call(_m, "GetStatus", _param0, _param1) + ret0, _ := ret[0].(*Status) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +func (_mr *_MockNetworkServerManagerServerRecorder) GetStatus(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "GetStatus", arg0, arg1) +} diff --git a/check_upstream.sh b/check_upstream.sh new file mode 100755 index 000000000..9bf4ad434 --- /dev/null +++ b/check_upstream.sh @@ -0,0 +1,12 @@ +#!/bin/bash +branch=$(git rev-parse --abbrev-ref HEAD) +for remote in $(git remote) +do + status=$(git rev-list --left-right --count HEAD...$remote/$branch 2>/dev/null) + (( !$? )) || status="0 0" + IFS=' ' read -ra changes <<< "$status" + up=${changes[0]} + down=${changes[1]} + if [[ $up -eq 0 ]] && [[ $down -eq 0 ]]; then continue; fi + echo "Remote $remote: ⬆️ ${changes[0]} ⬇️ ${changes[1]}" +done diff --git a/core/broker/activation.go b/core/broker/activation.go index 118384ebc..2cae2ef77 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -95,7 +95,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // Find Handler (based on AppEUI) var announcements []*pb_discovery.Announcement - announcements, err = b.handlerDiscovery.ForAppID(deduplicatedActivationRequest.AppId) + announcements, err = b.Discovery.GetAllHandlersForAppID(deduplicatedActivationRequest.AppId) if err != nil { return nil, err } diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index ea90e0010..175536643 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -8,12 +8,12 @@ import ( "testing" "time" - pb "github.com/TheThingsNetwork/ttn/api/broker" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/protocol" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" . "github.com/smartystreets/assertions" ) @@ -24,16 +24,21 @@ func TestHandleActivation(t *testing.T) { devEUI := types.DevEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) appEUI := types.AppEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) - b := &broker{ - Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleActivation"), + b := getTestBroker(t) + b.ns.EXPECT().PrepareActivation(gomock.Any(), gomock.Any()).Return(&pb_broker.DeduplicatedDeviceActivationRequest{ + Payload: []byte{}, + DevEui: &devEUI, + AppEui: &appEUI, + AppId: "appid", + DevId: "devid", + GatewayMetadata: []*gateway.RxMetadata{ + &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, }, - handlerDiscovery: &mockHandlerDiscovery{}, - activationDeduplicator: NewDeduplicator(10 * time.Millisecond), - ns: &mockNetworkServer{}, - } + ProtocolMetadata: &protocol.RxMetadata{}, + }, nil) + b.discovery.EXPECT().GetAllHandlersForAppID("appid").Return([]*pb_discovery.Announcement{}, nil) - res, err := b.HandleActivation(&pb.DeviceActivationRequest{ + res, err := b.HandleActivation(&pb_broker.DeviceActivationRequest{ Payload: []byte{}, DevEui: &devEUI, AppEui: &appEUI, @@ -43,26 +48,29 @@ func TestHandleActivation(t *testing.T) { a.So(err, ShouldNotBeNil) a.So(res, ShouldBeNil) + b.ctrl.Finish() + // TODO: Integration test with Handler } func TestDeduplicateActivation(t *testing.T) { a := New(t) - d := NewDeduplicator(20 * time.Millisecond).(*deduplicator) payload := []byte{0x01, 0x02, 0x03} protocolMetadata := &protocol.RxMetadata{} - activation1 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, ProtocolMetadata: protocolMetadata} - activation2 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 3.4}, ProtocolMetadata: protocolMetadata} - activation3 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} - activation4 := &pb.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} + activation1 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2}, ProtocolMetadata: protocolMetadata} + activation2 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 3.4}, ProtocolMetadata: protocolMetadata} + activation3 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} + activation4 := &pb_broker.DeviceActivationRequest{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} + + b := getTestBroker(t) + b.activationDeduplicator = NewDeduplicator(20 * time.Millisecond).(*deduplicator) - b := &broker{activationDeduplicator: d} var wg sync.WaitGroup wg.Add(1) go func() { res := b.deduplicateActivation(activation1) - a.So(res, ShouldResemble, []*pb.DeviceActivationRequest{activation1, activation2, activation3}) + a.So(res, ShouldResemble, []*pb_broker.DeviceActivationRequest{activation1, activation2, activation3}) wg.Done() }() diff --git a/core/broker/broker.go b/core/broker/broker.go index 1b0a09eee..3eb987283 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -15,7 +15,6 @@ import ( "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/discovery" "github.com/TheThingsNetwork/ttn/core/types" "github.com/pkg/errors" "google.golang.org/grpc" @@ -56,7 +55,6 @@ type broker struct { *core.Component routers map[string]chan *pb.DownlinkMessage routersLock sync.RWMutex - handlerDiscovery discovery.HandlerDiscovery handlers map[string]chan *pb.DeduplicatedUplinkMessage handlersLock sync.RWMutex nsAddr string @@ -126,8 +124,7 @@ func (b *broker) Init(c *core.Component) error { if err != nil { return err } - b.handlerDiscovery = discovery.NewHandlerDiscovery(b.Component) - b.handlerDiscovery.All() // Update cache + b.Discovery.GetAll("handler") // Update cache conn, err := api.DialWithCert(b.nsAddr, b.nsCert) if err != nil { return err diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 246b99ea7..ff19a2224 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -5,6 +5,7 @@ package broker import ( pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" @@ -56,10 +57,12 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if !claims.CanEditApp(in.AppId) { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } - err = b.broker.handlerDiscovery.AddAppID(in.HandlerId, in.AppId) + // Add Handler in local cache + handler, err := b.broker.Discovery.Get("handler", in.HandlerId) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Discovery did not add appID")) + return nil, grpcErrf(codes.Internal, "Could not get Handler Announcement") } + handler.AddMetadata(discovery.Metadata_APP_ID, []byte(in.AppId)) return &empty.Empty{}, nil } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 0312ab49f..b4bdbb418 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -163,7 +163,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } var announcements []*pb_discovery.Announcement - announcements, err = b.handlerDiscovery.ForAppID(device.AppId) + announcements, err = b.Discovery.GetAllHandlersForAppID(device.AppId) if err != nil { return err } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index d522347f5..9abaef9e7 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -11,27 +11,20 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" + "github.com/golang/mock/gomock" . "github.com/smartystreets/assertions" ) func TestHandleUplink(t *testing.T) { a := New(t) - b := &broker{ - Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleUplink"), - }, - uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), - ns: &mockNetworkServer{ - devices: []*pb_lorawan.Device{}, - }, - } + b := getTestBroker(t) gtwEUI := types.GatewayEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) @@ -60,6 +53,9 @@ func TestHandleUplink(t *testing.T) { // Device not found b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(&pb_networkserver.DevicesResponse{ + Results: []*pb_lorawan.Device{}, + }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, @@ -70,42 +66,34 @@ func TestHandleUplink(t *testing.T) { devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9} appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} - appID := "AppID-1" + appID := "appid-1" nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} // Add devices - b = &broker{ - Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleUplink"), - }, - handlers: make(map[string]chan *pb.DeduplicatedUplinkMessage), - uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), - ns: &mockNetworkServer{ - devices: []*pb_lorawan.Device{ - &pb_lorawan.Device{ - DevEui: &wrongDevEUI, - AppEui: &appEUI, - AppId: appID, - NwkSKey: &nwkSKey, - FCntUp: 4, - }, - &pb_lorawan.Device{ - DevEui: &devEUI, - AppEui: &appEUI, - AppId: appID, - NwkSKey: &nwkSKey, - FCntUp: 3, - }, + b = getTestBroker(t) + nsResponse := &pb_networkserver.DevicesResponse{ + Results: []*pb_lorawan.Device{ + &pb_lorawan.Device{ + DevEui: &wrongDevEUI, + AppEui: &appEUI, + AppId: appID, + NwkSKey: &nwkSKey, + FCntUp: 4, + }, + &pb_lorawan.Device{ + DevEui: &devEUI, + AppEui: &appEUI, + AppId: appID, + NwkSKey: &nwkSKey, + FCntUp: 3, }, - }, - handlerDiscovery: &mockHandlerDiscovery{ - &pb_discovery.Announcement{Id: "handlerID"}, }, } b.handlers["handlerID"] = make(chan *pb.DeduplicatedUplinkMessage, 10) // Device doesn't match b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, @@ -118,6 +106,7 @@ func TestHandleUplink(t *testing.T) { // Wrong FCnt b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, @@ -127,7 +116,14 @@ func TestHandleUplink(t *testing.T) { // Disable FCnt Check b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) - b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = true + nsResponse.Results[0].DisableFCntCheck = true + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) + b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any()) + b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{ + &pb_discovery.Announcement{ + Id: "handlerID", + }, + }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, @@ -137,8 +133,15 @@ func TestHandleUplink(t *testing.T) { // OK FCnt b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) - b.ns.(*mockNetworkServer).devices[0].FCntUp = 0 - b.ns.(*mockNetworkServer).devices[0].DisableFCntCheck = false + nsResponse.Results[0].FCntUp = 0 + nsResponse.Results[0].DisableFCntCheck = false + b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) + b.ns.EXPECT().Uplink(gomock.Any(), gomock.Any()) + b.discovery.EXPECT().GetAllHandlersForAppID("appid-1").Return([]*pb_discovery.Announcement{ + &pb_discovery.Announcement{ + Id: "handlerID", + }, + }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, @@ -149,7 +152,6 @@ func TestHandleUplink(t *testing.T) { func TestDeduplicateUplink(t *testing.T) { a := New(t) - d := NewDeduplicator(20 * time.Millisecond).(*deduplicator) payload := []byte{0x01, 0x02, 0x03} protocolMetadata := &protocol.RxMetadata{} @@ -158,7 +160,9 @@ func TestDeduplicateUplink(t *testing.T) { uplink3 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 5.6}, ProtocolMetadata: protocolMetadata} uplink4 := &pb.UplinkMessage{Payload: payload, GatewayMetadata: &gateway.RxMetadata{Snr: 7.8}, ProtocolMetadata: protocolMetadata} - b := &broker{uplinkDeduplicator: d} + b := getTestBroker(t) + b.uplinkDeduplicator = NewDeduplicator(20 * time.Millisecond).(*deduplicator) + var wg sync.WaitGroup wg.Add(1) go func() { diff --git a/core/broker/util_test.go b/core/broker/util_test.go new file mode 100644 index 000000000..831b75ee1 --- /dev/null +++ b/core/broker/util_test.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" +) + +type testBroker struct { + *broker + ctrl *gomock.Controller + discovery *pb_discovery.MockClient + ns *pb_networkserver.MockNetworkServerClient +} + +func getTestBroker(t *testing.T) *testBroker { + ctrl := gomock.NewController(t) + discovery := pb_discovery.NewMockClient(ctrl) + ns := pb_networkserver.NewMockNetworkServerClient(ctrl) + return &testBroker{ + broker: &broker{ + Component: &core.Component{ + Discovery: discovery, + Ctx: GetLogger(t, "TestBroker"), + }, + handlers: make(map[string]chan *pb_broker.DeduplicatedUplinkMessage), + activationDeduplicator: NewDeduplicator(10 * time.Millisecond), + uplinkDeduplicator: NewDeduplicator(10 * time.Millisecond), + ns: ns, + }, + ns: ns, + ctrl: ctrl, + discovery: discovery, + } +} diff --git a/core/component.go b/core/component.go index 574693379..65efc9f52 100644 --- a/core/component.go +++ b/core/component.go @@ -55,15 +55,6 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string grpclog.SetLogger(logging.NewGRPCLogger(ctx)) - var discovery pb_discovery.DiscoveryClient - if serviceName != "discovery" { - discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) - if err != nil { - return nil, err - } - discovery = pb_discovery.NewDiscoveryClient(discoveryConn) - } - component := &Component{ Ctx: ctx, Identity: &pb_discovery.Announcement{ @@ -74,13 +65,23 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string NetAddress: announcedAddress, }, AccessToken: viper.GetString("auth-token"), - Discovery: discovery, TokenKeyProvider: tokenkey.NewHTTPProvider( viper.GetStringMapString("auth-servers"), viper.GetString("key-dir"), ), } + if serviceName != "discovery" { + discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) + if err != nil { + return nil, err + } + component.Discovery = pb_discovery.NewClient(discoveryConn, component.Identity, func() string { + token, _ := component.BuildJWT() + return token + }) + } + if pub, priv, cert, err := security.LoadKeys(viper.GetString("key-dir")); err == nil { component.Identity.PublicKey = string(pub) component.privateKey = string(priv) @@ -127,7 +128,7 @@ const ( // Component contains the common attributes for all TTN components type Component struct { Identity *pb_discovery.Announcement - Discovery pb_discovery.DiscoveryClient + Discovery pb_discovery.Client Ctx log.Interface AccessToken string privateKey string @@ -148,10 +149,7 @@ func (c *Component) SetStatus(status Status) { // Discover is used to discover another component func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { - res, err := c.Discovery.Get(c.GetContext(""), &pb_discovery.GetRequest{ - ServiceName: serviceName, - Id: id, - }) + res, err := c.Discovery.Get(serviceName, id) if err != nil { return nil, errors.Wrapf(FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) } @@ -163,7 +161,7 @@ func (c *Component) Announce() error { if c.Identity.Id == "" { return NewErrInvalidArgument("Component ID", "can not be empty") } - _, err := c.Discovery.Announce(c.GetContext(c.AccessToken), c.Identity) + err := c.Discovery.Announce(c.AccessToken) if err != nil { return errors.Wrapf(FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) } @@ -265,8 +263,8 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d if err != nil { return } - if claims.Subject != id { - err = NewErrInvalidArgument("Metadata", "token was issued for a different component id") + if claims.Issuer != id { + err = NewErrInvalidArgument("Metadata", "token was issued by different component id") return } diff --git a/core/discovery/broker_discovery.go b/core/discovery/broker_discovery.go deleted file mode 100644 index 4ed63d7c6..000000000 --- a/core/discovery/broker_discovery.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import ( - "sync" - "time" - - pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/pkg/errors" -) - -// BrokerCacheTime indicates how long the BrokerDiscovery should cache the services -var BrokerCacheTime = 30 * time.Minute - -// BrokerDiscovery is used as a client to discover Brokers -type BrokerDiscovery interface { - Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) - All() ([]*pb.Announcement, error) -} - -type brokerDiscovery struct { - component *core.Component - cache []*pb.Announcement - cacheLock sync.RWMutex - cacheValidUntil time.Time -} - -// NewBrokerDiscovery returns a new BrokerDiscovery on top of the given gRPC connection -func NewBrokerDiscovery(component *core.Component) BrokerDiscovery { - return &brokerDiscovery{component: component} -} - -func (d *brokerDiscovery) refreshCache() error { - res, err := d.component.Discovery.GetAll(d.component.GetContext(""), &pb.GetAllRequest{ServiceName: "broker"}) - if err != nil { - return errors.Wrap(core.FromGRPCError(err), "Failed to refresh brokers from Discovery") - } - // TODO: validate response - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - d.cacheValidUntil = time.Now().Add(BrokerCacheTime) - d.cache = res.Services - return nil -} - -func (d *brokerDiscovery) All() (announcements []*pb.Announcement, err error) { - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - if time.Now().After(d.cacheValidUntil) { - d.cacheValidUntil = time.Now().Add(10 * time.Second) - go d.refreshCache() - } - announcements = d.cache - return -} - -func (d *brokerDiscovery) Discover(devAddr types.DevAddr) ([]*pb.Announcement, error) { - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - - if time.Now().After(d.cacheValidUntil) { - d.cacheValidUntil = time.Now().Add(10 * time.Second) - go d.refreshCache() - } - - matches := []*pb.Announcement{} - for _, service := range d.cache { - for _, meta := range service.Metadata { - if meta.Key == pb.Metadata_PREFIX && len(meta.Value) == 5 { - var prefix types.DevAddrPrefix - copy(prefix.DevAddr[:], meta.Value[1:]) - prefix.Length = int(meta.Value[0]) - - if devAddr.HasPrefix(prefix) { - matches = append(matches, service) - break - } - } - } - } - - return matches, nil -} diff --git a/core/discovery/broker_discovery_test.go b/core/discovery/broker_discovery_test.go deleted file mode 100644 index a707dbaeb..000000000 --- a/core/discovery/broker_discovery_test.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import ( - "fmt" - "testing" - "time" - - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/api" - pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/smartystreets/assertions" -) - -func buildTestBrokerDiscoveryClient(port uint) *brokerDiscovery { - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) - if err != nil { - panic(err) - } - client := pb.NewDiscoveryClient(conn) - discovery := NewBrokerDiscovery(&core.Component{Discovery: client}).(*brokerDiscovery) - discovery.refreshCache() - return discovery -} - -func TestBrokerDiscovery(t *testing.T) { - a := New(t) - - // Broker1 has a prefix with all DevAddrs - broker1 := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost1:1881", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0, 0x00, 0x00, 0x00, 0x00}}, - }, - } - - // Broker2 has one DevAddr prefix - broker2 := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost2:1881", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, - }, - } - - // Broker3 has multiple DevAddr prefixes - broker3 := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost3:1881", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{16, 0x02, 0x03, 0x00, 0x00}}, - }, - } - - d := &brokerDiscovery{ - cacheValidUntil: time.Now().Add(10 * time.Minute), - cache: []*pb.Announcement{broker1, broker2, broker3}, - } - - results, err := d.All() - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldContain, broker1) - a.So(results, ShouldContain, broker2) - a.So(results, ShouldContain, broker3) - - results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldContain, broker1) - a.So(results, ShouldContain, broker2) - a.So(results, ShouldNotContain, broker3) - - results, err = d.Discover(types.DevAddr{0x02, 0x03, 0x04, 0x05}) - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldContain, broker1) - a.So(results, ShouldNotContain, broker2) - a.So(results, ShouldContain, broker3) - - results, err = d.Discover(types.DevAddr{0x04, 0x05, 0x06, 0x07}) - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldContain, broker1) - a.So(results, ShouldNotContain, broker2) - a.So(results, ShouldNotContain, broker3) -} - -func TestBrokerDiscoveryCache(t *testing.T) { - a := New(t) - - port := randomPort() - - discoveryServer, _ := buildMockDiscoveryServer(port) - - broker := &pb.Announcement{ServiceName: "broker", NetAddress: "localhost1:1881", - Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0x00, 0x00, 0x00, 0x00, 0x00}}}, - } - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) - if err != nil { - panic(err) - } - client := pb.NewDiscoveryClient(conn) - - d := &brokerDiscovery{ - component: &core.Component{ - Discovery: client, - }, - cacheValidUntil: time.Now().Add(-1 * time.Minute), - cache: []*pb.Announcement{broker}, - } - - // It should return the cached broker initially - results, err := d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) - a.So(err, ShouldBeNil) - a.So(results, ShouldContain, broker) - - // It should still return the cached broker - results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) - a.So(err, ShouldBeNil) - a.So(results, ShouldContain, broker) - - <-time.After(20 * time.Millisecond) - - // It should return the refreshed (empty) broker list - results, err = d.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) - a.So(err, ShouldBeNil) - a.So(results, ShouldBeEmpty) - - a.So(discoveryServer.discover, ShouldEqual, 1) -} diff --git a/core/discovery/discovery_integration_test.go b/core/discovery/discovery_integration_test.go deleted file mode 100644 index c6835e823..000000000 --- a/core/discovery/discovery_integration_test.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import ( - "testing" - - pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core/discovery/announcement" - "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/smartystreets/assertions" -) - -func TestIntegrationBrokerDiscovery(t *testing.T) { - a := New(t) - - port := randomPort() - - discoveryServer, s := buildTestDiscoveryServer(port) - defer s.Stop() - - discoveryServer.services = announcement.NewAnnouncementStore() - discoveryServer.services.Set(&pb.Announcement{ - Id: "broker1", - ServiceName: "broker", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x01, 0x00, 0x00, 0x00}}, - }, - }) - discoveryServer.services.Set(&pb.Announcement{ - Id: "broker2", - ServiceName: "broker", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{8, 0x02, 0x00, 0x00, 0x00}}, - }, - }) - - discoveryClient := buildTestBrokerDiscoveryClient(port) - - brokers, err := discoveryClient.Discover(types.DevAddr{0x01, 0x02, 0x03, 0x04}) - a.So(err, ShouldBeNil) - a.So(brokers, ShouldHaveLength, 1) -} diff --git a/core/discovery/handler_discovery.go b/core/discovery/handler_discovery.go deleted file mode 100644 index c89f9b555..000000000 --- a/core/discovery/handler_discovery.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import ( - "fmt" - "sync" - "time" - - pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" - "github.com/pkg/errors" -) - -// HandlerCacheTime indicates how long the HandlerDiscovery should cache the services -var HandlerCacheTime = 5 * time.Minute - -// HandlerDiscovery is used as a client to discover Handlers -type HandlerDiscovery interface { - AddAppID(handlerID, appID string) error - ForAppID(appID string) ([]*pb.Announcement, error) - Get(id string) (*pb.Announcement, error) - All() ([]*pb.Announcement, error) -} - -type handlerDiscovery struct { - component *core.Component - cache []*pb.Announcement - byID map[string]*pb.Announcement - byAppID map[string][]*pb.Announcement - cacheLock sync.RWMutex - cacheValidUntil time.Time -} - -// NewHandlerDiscovery returns a new HandlerDiscovery on top of the given gRPC connection -func NewHandlerDiscovery(component *core.Component) HandlerDiscovery { - return &handlerDiscovery{component: component} -} - -func (d *handlerDiscovery) refreshCache() error { - res, err := d.component.Discovery.GetAll(d.component.GetContext(""), &pb.GetAllRequest{ServiceName: "handler"}) - if err != nil { - return errors.Wrap(core.FromGRPCError(err), "Failed to refresh handlers from Discovery") - } - // TODO: validate response - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - d.cacheValidUntil = time.Now().Add(HandlerCacheTime) - d.cache = res.Services - d.updateLookups() - return nil -} - -func (d *handlerDiscovery) update() { - if time.Now().After(d.cacheValidUntil) { - d.cacheValidUntil = time.Now().Add(10 * time.Second) - go d.refreshCache() - } -} - -func (d *handlerDiscovery) updateLookups() { - d.byID = map[string]*pb.Announcement{} - d.byAppID = map[string][]*pb.Announcement{} - for _, service := range d.cache { - d.byID[service.Id] = service - for _, meta := range service.Metadata { - switch meta.Key { - case pb.Metadata_APP_ID: - announcedID := string(meta.Value) - if forID, ok := d.byAppID[announcedID]; ok { - d.byAppID[announcedID] = append(forID, service) - } else { - d.byAppID[announcedID] = []*pb.Announcement{service} - } - } - } - } -} - -func (d *handlerDiscovery) All() (announcements []*pb.Announcement, err error) { - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - d.update() - announcements = d.cache - return -} - -func (d *handlerDiscovery) Get(id string) (*pb.Announcement, error) { - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - d.update() - match, ok := d.byID[id] - if !ok { - return nil, core.NewErrNotFound(fmt.Sprintf("handler/%s", id)) - } - return match, nil -} - -func (d *handlerDiscovery) ForAppID(appID string) ([]*pb.Announcement, error) { - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - d.update() - matches, ok := d.byAppID[appID] - if !ok { - return []*pb.Announcement{}, nil - } - return matches, nil -} - -func (d *handlerDiscovery) AddAppID(handlerID, appID string) error { - d.cacheLock.Lock() - defer d.cacheLock.Unlock() - handler, found := d.byID[handlerID] - if !found { - return core.NewErrNotFound(fmt.Sprintf("handler/%s", handlerID)) - } - existing, found := d.byAppID[appID] - if found && len(existing) > 0 { - if existing[0].Id == handlerID { - return nil - } - return core.NewErrAlreadyExists(fmt.Sprintf("AppID %s already registered", appID)) - } - d.byAppID[appID] = []*pb.Announcement{handler} - return nil -} diff --git a/core/discovery/handler_discovery_test.go b/core/discovery/handler_discovery_test.go deleted file mode 100644 index cfb3b9ade..000000000 --- a/core/discovery/handler_discovery_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import ( - "fmt" - "testing" - "time" - - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/api" - pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" - . "github.com/smartystreets/assertions" -) - -func buildTestHandlerDiscoveryClient(port uint) *handlerDiscovery { - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) - if err != nil { - panic(err) - } - client := pb.NewDiscoveryClient(conn) - discovery := NewHandlerDiscovery(&core.Component{Discovery: client}).(*handlerDiscovery) - discovery.refreshCache() - return discovery -} - -func TestHandlerDiscovery(t *testing.T) { - a := New(t) - - // Handler1 owns one AppEUI - handler1 := &pb.Announcement{ServiceName: "handler", Id: "handler1", NetAddress: "localhost1:1881", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-2")}, - }, - } - - // Handler2 has two AppEUIs - handler2 := &pb.Announcement{ServiceName: "handler", Id: "handler2", NetAddress: "localhost2:1881", - Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}, - }, - } - - d := &handlerDiscovery{ - cacheValidUntil: time.Now().Add(10 * time.Minute), - cache: []*pb.Announcement{handler1, handler2}, - } - d.updateLookups() - - announcement, err := d.Get("handler1") - a.So(err, ShouldBeNil) - a.So(announcement, ShouldEqual, handler1) - - results, err := d.All() - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldContain, handler1) - a.So(results, ShouldContain, handler2) - - results, err = d.ForAppID("AppID-1") - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldNotContain, handler1) - a.So(results, ShouldContain, handler2) - - results, err = d.ForAppID("AppID-2") - a.So(err, ShouldBeNil) - a.So(results, ShouldNotBeEmpty) - a.So(results, ShouldContain, handler1) - a.So(results, ShouldNotContain, handler2) - - results, err = d.ForAppID("AppID-3") - a.So(err, ShouldBeNil) - a.So(results, ShouldBeEmpty) -} - -func TestHandlerDiscoveryCache(t *testing.T) { - a := New(t) - - port := randomPort() - - discoveryServer, _ := buildMockDiscoveryServer(port) - - handler := &pb.Announcement{ServiceName: "handler", NetAddress: "localhost1:1881", - Metadata: []*pb.Metadata{&pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID-1")}}, - } - - conn, err := grpc.Dial(fmt.Sprintf("localhost:%d", port), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) - if err != nil { - panic(err) - } - client := pb.NewDiscoveryClient(conn) - - d := &handlerDiscovery{ - component: &core.Component{ - Discovery: client, - }, - cacheValidUntil: time.Now().Add(-1 * time.Minute), - cache: []*pb.Announcement{handler}, - } - d.updateLookups() - - // It should return the cached handler initially - results, err := d.ForAppID("AppID-1") - a.So(err, ShouldBeNil) - a.So(results, ShouldContain, handler) - - // It should still return the cached handler - results, err = d.ForAppID("AppID-1") - a.So(err, ShouldBeNil) - a.So(results, ShouldContain, handler) - - <-time.After(20 * time.Millisecond) - - // It should return the refreshed (empty) handler list - results, err = d.ForAppID("AppID-1") - a.So(err, ShouldBeNil) - a.So(results, ShouldBeEmpty) - - a.So(discoveryServer.discover, ShouldEqual, 1) -} diff --git a/core/discovery/server.go b/core/discovery/server.go index 1c4e1769d..9a9dccb80 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -34,13 +34,13 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me } // Only allow prefix announcements if token is issued by the official ttn account server (or if in dev mode) if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) + return errPermissionDeniedf("Token issuer \"%s\" should be \"ttn-account\"", claims.Issuer) } if claims.Type != in.ServiceName { - return errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) + return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) } if claims.Subject != in.Id { - return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.Id) } // TODO: Check if this PREFIX can be announced case pb.Metadata_APP_EUI: @@ -52,10 +52,10 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me return errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) } if claims.Type != in.ServiceName { - return errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, in.Id) + return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) } if claims.Subject != in.Id { - return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) + return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.Id) } // TODO: Check if this APP_EUI can be announced return errPermissionDeniedf("Can not announce AppEUIs at this time") diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index cf9b9d0fd..dde0f20b0 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -18,6 +18,7 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" ) type handlerManager struct { @@ -267,14 +268,9 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica return nil, core.BuildGRPCError(err) } - _, err = h.handler.Discovery.AddMetadata(ctx, &pb_discovery.MetadataRequest{ - ServiceName: h.handler.Identity.ServiceName, - Id: h.handler.Identity.Id, - Metadata: &pb_discovery.Metadata{ - Key: pb_discovery.Metadata_APP_ID, - Value: []byte(in.AppId), - }, - }) + md, _ := metadata.FromContext(ctx) + token, _ := md["token"] + err = h.handler.Discovery.AddMetadata(pb_discovery.Metadata_APP_ID, []byte(in.AppId), token[0]) if err != nil { h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Discovery") } @@ -343,14 +339,9 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati return nil, core.BuildGRPCError(err) } - _, err = h.handler.Discovery.DeleteMetadata(ctx, &pb_discovery.MetadataRequest{ - ServiceName: h.handler.Identity.ServiceName, - Id: h.handler.Identity.Id, - Metadata: &pb_discovery.Metadata{ - Key: pb_discovery.Metadata_APP_ID, - Value: []byte(in.AppId), - }, - }) + md, _ := metadata.FromContext(ctx) + token, _ := md["token"] + err = h.handler.Discovery.DeleteMetadata(pb_discovery.Metadata_APP_ID, []byte(in.AppId), token[0]) if err != nil { h.handler.Ctx.WithField("AppID", in.AppId).WithError(core.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") } diff --git a/core/router/activation.go b/core/router/activation.go index ad18cd617..604d9ec4c 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -54,7 +54,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) // Find Broker - brokers, err := r.brokerDiscovery.All() + brokers, err := r.Discovery.GetAll("broker") if err != nil { return nil, err } diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 9ce25f420..dd234a4bf 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -26,7 +26,6 @@ func TestHandleActivation(t *testing.T) { gateways: map[types.GatewayEUI]*gateway.Gateway{ gatewayEUI: newReferenceGateway(t, "EU_863_870"), }, - brokerDiscovery: &mockBrokerDiscovery{}, } appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index f9854b0e4..ce41f745b 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -45,8 +45,7 @@ func TestHandleDownlink(t *testing.T) { Component: &core.Component{ Ctx: GetLogger(t, "TestHandleDownlink"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, - brokerDiscovery: &mockBrokerDiscovery{}, + gateways: map[types.GatewayEUI]*gateway.Gateway{}, } eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} @@ -71,8 +70,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { Component: &core.Component{ Ctx: GetLogger(t, "TestSubscribeUnsubscribeDownlink"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, - brokerDiscovery: &mockBrokerDiscovery{}, + gateways: map[types.GatewayEUI]*gateway.Gateway{}, } eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} diff --git a/core/router/router.go b/core/router/router.go index 9c193cda4..7336c4f6e 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -13,7 +13,6 @@ import ( pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/discovery" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -53,11 +52,10 @@ func NewRouter() Router { type router struct { *core.Component - gateways map[types.GatewayEUI]*gateway.Gateway - gatewaysLock sync.RWMutex - brokerDiscovery discovery.BrokerDiscovery - brokers map[string]*broker - brokersLock sync.RWMutex + gateways map[types.GatewayEUI]*gateway.Gateway + gatewaysLock sync.RWMutex + brokers map[string]*broker + brokersLock sync.RWMutex } func (r *router) tickGateways() { @@ -78,8 +76,7 @@ func (r *router) Init(c *core.Component) error { if err != nil { return err } - r.brokerDiscovery = discovery.NewBrokerDiscovery(r.Component) - r.brokerDiscovery.All() // Update cache + r.Discovery.GetAll("broker") // Update cache go func() { for range time.Tick(5 * time.Second) { r.tickGateways() diff --git a/core/router/uplink.go b/core/router/uplink.go index c70aa443e..48b7959a6 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -93,7 +93,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess ctx = ctx.WithField("DownlinkOptions", len(downlinkOptions)) // Find Broker - brokers, err := r.brokerDiscovery.Discover(devAddr) + brokers, err := r.Discovery.GetAllBrokersForDevAddr(devAddr) if err != nil { return err } diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index c394d52e1..8b302b4a8 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -11,7 +11,6 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -63,26 +62,11 @@ func newReferenceUplink() *pb.UplinkMessage { return up } -type mockBrokerDiscovery struct{} - -func (d *mockBrokerDiscovery) Discover(devAddr types.DevAddr) ([]*discovery.Announcement, error) { - return []*discovery.Announcement{}, nil -} - -func (d *mockBrokerDiscovery) All() ([]*discovery.Announcement, error) { - return []*discovery.Announcement{}, nil -} - func TestHandleUplink(t *testing.T) { a := New(t) - r := &router{ - Component: &core.Component{ - Ctx: GetLogger(t, "TestHandleUplink"), - }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, - brokerDiscovery: &mockBrokerDiscovery{}, - } + r := getTestRouter(t) + r.discovery.EXPECT().GetAllBrokersForDevAddr(types.DevAddr([4]byte{1, 2, 3, 4})).Return([]*discovery.Announcement{}, nil) uplink := newReferenceUplink() gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} diff --git a/core/router/util_test.go b/core/router/util_test.go new file mode 100644 index 000000000..faa5b5b95 --- /dev/null +++ b/core/router/util_test.go @@ -0,0 +1,37 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/router/gateway" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" +) + +type testRouter struct { + *router + ctrl *gomock.Controller + discovery *discovery.MockClient +} + +func getTestRouter(t *testing.T) *testRouter { + ctrl := gomock.NewController(t) + discovery := discovery.NewMockClient(ctrl) + return &testRouter{ + router: &router{ + Component: &core.Component{ + Discovery: discovery, + Ctx: GetLogger(t, "TestRouter"), + }, + gateways: map[types.GatewayEUI]*gateway.Gateway{}, + }, + ctrl: ctrl, + discovery: discovery, + } +} diff --git a/utils/security/jwt.go b/utils/security/jwt.go index 2b192fa89..993a4be0f 100644 --- a/utils/security/jwt.go +++ b/utils/security/jwt.go @@ -12,7 +12,7 @@ import ( // BuildJWT builds a JSON Web Token for the given subject and ttl, and signs it with the given private key func BuildJWT(subject string, ttl time.Duration, privateKey []byte) (token string, err error) { claims := jwt.StandardClaims{ - Subject: subject, + Issuer: subject, IssuedAt: time.Now().Unix(), NotBefore: time.Now().Unix(), } From 55e8f6eea96c6bb8602eaaa7f1d171510d937a97 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 14:30:10 +0200 Subject: [PATCH 1708/2266] Fix discovery cache expiration --- api/discovery/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/discovery/client.go b/api/discovery/client.go index 0d613c110..d456771a3 100644 --- a/api/discovery/client.go +++ b/api/discovery/client.go @@ -129,7 +129,7 @@ func (c *DefaultClient) GetAll(serviceName string) ([]*Announcement, error) { // If list initialized, return cached version if list, ok := c.lists[serviceName]; ok && len(list) > 0 { // And update if expired - if c.listsUpdated[serviceName].Add(CacheExpiration).After(time.Now()) { + if c.listsUpdated[serviceName].Add(CacheExpiration).Before(time.Now()) { go func() { c.Lock() defer c.Unlock() From d53cdabec377e3edcb244261361e135fc72197c1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 16:28:17 +0200 Subject: [PATCH 1709/2266] Fix Downlink Field Conversion --- core/handler/convert_fields.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 263afc5b2..e2547d95f 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -301,7 +301,6 @@ func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMes } appDown.Payload = message - ttnDown.Payload = message return nil } From 6f1ef72bac78ab32f72ddd049c7ca59638f67418 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 16:58:42 +0200 Subject: [PATCH 1710/2266] Fix test for d53cdabe --- core/handler/convert_fields_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 657ad90f4..c5209561b 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -331,7 +331,7 @@ func TestConvertFieldsDown(t *testing.T) { ttnDown, appDown := buildConversionDownlink() err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(ttnDown.Payload, ShouldBeEmpty) + a.So(appDown.Payload, ShouldBeEmpty) // Case2: Normal flow with Encoder h.applications.Set(&application.Application{ @@ -345,7 +345,7 @@ func TestConvertFieldsDown(t *testing.T) { ttnDown, appDown = buildConversionDownlink() err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(ttnDown.Payload, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) + a.So(appDown.Payload, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) } func TestProcessDownlinkInvalidFunction(t *testing.T) { From a9319df768908c2c07cf6d64efdc7986ba2a9f72 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Sep 2016 17:14:30 +0200 Subject: [PATCH 1711/2266] Fix the other test for d53cdabe --- core/handler/downlink_test.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 34a17fd55..9f5203053 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -4,8 +4,8 @@ package handler import ( - "sync" "testing" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/core" @@ -52,7 +52,7 @@ func TestEnqueueDownlink(t *testing.T) { func TestHandleDownlink(t *testing.T) { a := New(t) var err error - var wg sync.WaitGroup + var wg WaitGroup appID := "app2" devID := "dev2" appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) @@ -104,7 +104,7 @@ func TestHandleDownlink(t *testing.T) { Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(100 * time.Millisecond) // Both Payload and Fields provided h.applications.Set(&application.Application{ @@ -121,8 +121,9 @@ func TestHandleDownlink(t *testing.T) { Fields: jsonFields, Payload: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldNotBeNil) @@ -139,9 +140,10 @@ func TestHandleDownlink(t *testing.T) { DevID: devID, Fields: jsonFields, }, &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, }) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(100 * time.Millisecond) } From 6c27eb04514997f01165a74b326bfe42ebd8927e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 10:48:31 +0200 Subject: [PATCH 1712/2266] Update vendor submodules --- .gitmodules | 55 +++++++++++++++++++- Makefile | 1 + core/router/downlink.go | 2 +- vendor/github.com/asaskevich/govalidator | 1 + vendor/github.com/bluele/gcache | 1 + vendor/github.com/brocaar/lorawan | 2 +- vendor/github.com/dgrijalva/jwt-go | 2 +- vendor/github.com/eclipse/paho.mqtt.golang | 1 + vendor/github.com/golang/mock | 1 + vendor/github.com/gosuri/uitable | 1 + vendor/github.com/howeyc/gopass | 1 + vendor/github.com/jacobsa/crypto | 1 + vendor/github.com/mitchellh/go-homedir | 1 + vendor/github.com/mwitkow/go-grpc-middleware | 1 + vendor/github.com/pkg/errors | 1 + vendor/github.com/rcrowley/go-metrics | 1 + vendor/github.com/robertkrimen/otto | 1 + vendor/github.com/spf13/cobra | 1 + vendor/github.com/spf13/viper | 1 + vendor/github.com/tj/go-elastic | 1 + vendor/gopkg.in/redis.v3 | 1 + vendor/gopkg.in/yaml.v2 | 1 + 22 files changed, 74 insertions(+), 5 deletions(-) create mode 160000 vendor/github.com/asaskevich/govalidator create mode 160000 vendor/github.com/bluele/gcache create mode 160000 vendor/github.com/eclipse/paho.mqtt.golang create mode 160000 vendor/github.com/golang/mock create mode 160000 vendor/github.com/gosuri/uitable create mode 160000 vendor/github.com/howeyc/gopass create mode 160000 vendor/github.com/jacobsa/crypto create mode 160000 vendor/github.com/mitchellh/go-homedir create mode 160000 vendor/github.com/mwitkow/go-grpc-middleware create mode 160000 vendor/github.com/pkg/errors create mode 160000 vendor/github.com/rcrowley/go-metrics create mode 160000 vendor/github.com/robertkrimen/otto create mode 160000 vendor/github.com/spf13/cobra create mode 160000 vendor/github.com/spf13/viper create mode 160000 vendor/github.com/tj/go-elastic create mode 160000 vendor/gopkg.in/redis.v3 create mode 160000 vendor/gopkg.in/yaml.v2 diff --git a/.gitmodules b/.gitmodules index de2fc6150..684a928dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,57 @@ +[submodule "vendor/github.com/asaskevich/govalidator"] + path = vendor/github.com/asaskevich/govalidator + url = https://github.com/asaskevich/govalidator +[submodule "vendor/github.com/bluele/gcache"] + path = vendor/github.com/bluele/gcache + url = https://github.com/bluele/gcache [submodule "vendor/github.com/brocaar/lorawan"] path = vendor/github.com/brocaar/lorawan - url = https://github.com/brocaar/lorawan.git + url = https://github.com/brocaar/lorawan [submodule "vendor/github.com/dgrijalva/jwt-go"] path = vendor/github.com/dgrijalva/jwt-go - url = https://github.com/dgrijalva/jwt-go.git + url = https://github.com/dgrijalva/jwt-go +[submodule "vendor/github.com/eclipse/paho.mqtt.golang"] + path = vendor/github.com/eclipse/paho.mqtt.golang + url = https://github.com/eclipse/paho.mqtt.golang +[submodule "vendor/github.com/golang/mock"] + path = vendor/github.com/golang/mock + url = https://github.com/golang/mock +[submodule "vendor/github.com/gosuri/uitable"] + path = vendor/github.com/gosuri/uitable + url = https://github.com/gosuri/uitable +[submodule "vendor/github.com/howeyc/gopass"] + path = vendor/github.com/howeyc/gopass + url = https://github.com/howeyc/gopass +[submodule "vendor/github.com/jacobsa/crypto"] + path = vendor/github.com/jacobsa/crypto + url = https://github.com/jacobsa/crypto +[submodule "vendor/github.com/mitchellh/go-homedir"] + path = vendor/github.com/mitchellh/go-homedir + url = https://github.com/mitchellh/go-homedir +[submodule "vendor/github.com/mwitkow/go-grpc-middleware"] + path = vendor/github.com/mwitkow/go-grpc-middleware + url = https://github.com/mwitkow/go-grpc-middleware +[submodule "vendor/github.com/pkg/errors"] + path = vendor/github.com/pkg/errors + url = https://github.com/pkg/errors +[submodule "vendor/github.com/rcrowley/go-metrics"] + path = vendor/github.com/rcrowley/go-metrics + url = https://github.com/rcrowley/go-metrics +[submodule "vendor/github.com/robertkrimen/otto"] + path = vendor/github.com/robertkrimen/otto + url = https://github.com/robertkrimen/otto +[submodule "vendor/github.com/spf13/cobra"] + path = vendor/github.com/spf13/cobra + url = https://github.com/spf13/cobra +[submodule "vendor/github.com/spf13/viper"] + path = vendor/github.com/spf13/viper + url = https://github.com/spf13/viper +[submodule "vendor/github.com/tj/go-elastic"] + path = vendor/github.com/tj/go-elastic + url = https://github.com/tj/go-elastic +[submodule "vendor/gopkg.in/redis.v3"] + path = vendor/gopkg.in/redis.v3 + url = https://gopkg.in/redis.v3 +[submodule "vendor/gopkg.in/yaml.v2"] + path = vendor/gopkg.in/yaml.v2 + url = https://gopkg.in/yaml.v2 diff --git a/Makefile b/Makefile index 515310edc..c897d5918 100644 --- a/Makefile +++ b/Makefile @@ -49,6 +49,7 @@ dev-deps: update-deps test-deps $(GOCMD) get -u -v github.com/golang/mock/gomock $(GOCMD) get -u -v github.com/golang/mock/mockgen $(GOCMD) get -u -v github.com/ddollar/forego + $(GOCMD) get -u -v github.com/kovetskiy/manul cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi diff --git a/core/router/downlink.go b/core/router/downlink.go index a9e54b664..a3783ee9f 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -205,7 +205,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo // Configuration for RX1 { - uplinkChannel, err := band.GetChannel(int(uplink.GatewayMetadata.Frequency), uplinkDRIndex) + uplinkChannel, err := band.GetChannel(int(uplink.GatewayMetadata.Frequency), nil) if err == nil { downlinkChannel := band.DownlinkChannels[band.GetRX1Channel(uplinkChannel)] downlinkDRIndex, err := band.GetRX1DataRateForOffset(uplinkDRIndex, 0) diff --git a/vendor/github.com/asaskevich/govalidator b/vendor/github.com/asaskevich/govalidator new file mode 160000 index 000000000..593d64559 --- /dev/null +++ b/vendor/github.com/asaskevich/govalidator @@ -0,0 +1 @@ +Subproject commit 593d64559f7600f29581a3ee42177f5dbded27a9 diff --git a/vendor/github.com/bluele/gcache b/vendor/github.com/bluele/gcache new file mode 160000 index 000000000..69623c269 --- /dev/null +++ b/vendor/github.com/bluele/gcache @@ -0,0 +1 @@ +Subproject commit 69623c269a10cc02d8f770133ca36357c0877d81 diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan index bd4dcf15f..8e810fc7a 160000 --- a/vendor/github.com/brocaar/lorawan +++ b/vendor/github.com/brocaar/lorawan @@ -1 +1 @@ -Subproject commit bd4dcf15f7cde1e8c4639b1aa7143aed268aa0af +Subproject commit 8e810fc7af6148d4514c9d873e5f921e9178a9c1 diff --git a/vendor/github.com/dgrijalva/jwt-go b/vendor/github.com/dgrijalva/jwt-go index d2709f9f1..24c63f565 160000 --- a/vendor/github.com/dgrijalva/jwt-go +++ b/vendor/github.com/dgrijalva/jwt-go @@ -1 +1 @@ -Subproject commit d2709f9f1f31ebcda9651b03077758c1f3a0018c +Subproject commit 24c63f56522a87ec5339cc3567883f1039378fdb diff --git a/vendor/github.com/eclipse/paho.mqtt.golang b/vendor/github.com/eclipse/paho.mqtt.golang new file mode 160000 index 000000000..b0f0cce24 --- /dev/null +++ b/vendor/github.com/eclipse/paho.mqtt.golang @@ -0,0 +1 @@ +Subproject commit b0f0cce24f138f7a1d034f55069883c7dcb6a9d7 diff --git a/vendor/github.com/golang/mock b/vendor/github.com/golang/mock new file mode 160000 index 000000000..bd3c8e81b --- /dev/null +++ b/vendor/github.com/golang/mock @@ -0,0 +1 @@ +Subproject commit bd3c8e81be01eef76d4b503f5e687d2d1354d2d9 diff --git a/vendor/github.com/gosuri/uitable b/vendor/github.com/gosuri/uitable new file mode 160000 index 000000000..36ee7e946 --- /dev/null +++ b/vendor/github.com/gosuri/uitable @@ -0,0 +1 @@ +Subproject commit 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 diff --git a/vendor/github.com/howeyc/gopass b/vendor/github.com/howeyc/gopass new file mode 160000 index 000000000..26c6e1184 --- /dev/null +++ b/vendor/github.com/howeyc/gopass @@ -0,0 +1 @@ +Subproject commit 26c6e1184fd5255fa5f5289d0b789a4819c203a4 diff --git a/vendor/github.com/jacobsa/crypto b/vendor/github.com/jacobsa/crypto new file mode 160000 index 000000000..42daa9d04 --- /dev/null +++ b/vendor/github.com/jacobsa/crypto @@ -0,0 +1 @@ +Subproject commit 42daa9d04c68a12aca47231abd9fb7f8aac27ef7 diff --git a/vendor/github.com/mitchellh/go-homedir b/vendor/github.com/mitchellh/go-homedir new file mode 160000 index 000000000..756f7b183 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir @@ -0,0 +1 @@ +Subproject commit 756f7b183b7ab78acdbbee5c7f392838ed459dda diff --git a/vendor/github.com/mwitkow/go-grpc-middleware b/vendor/github.com/mwitkow/go-grpc-middleware new file mode 160000 index 000000000..0664ec3f4 --- /dev/null +++ b/vendor/github.com/mwitkow/go-grpc-middleware @@ -0,0 +1 @@ +Subproject commit 0664ec3f4e6e08d02f2bf6a62be6900c0b82ea69 diff --git a/vendor/github.com/pkg/errors b/vendor/github.com/pkg/errors new file mode 160000 index 000000000..17b591df3 --- /dev/null +++ b/vendor/github.com/pkg/errors @@ -0,0 +1 @@ +Subproject commit 17b591df37844cde689f4d5813e5cea0927d8dd2 diff --git a/vendor/github.com/rcrowley/go-metrics b/vendor/github.com/rcrowley/go-metrics new file mode 160000 index 000000000..6ee5318c7 --- /dev/null +++ b/vendor/github.com/rcrowley/go-metrics @@ -0,0 +1 @@ +Subproject commit 6ee5318c779434e3545a375bde303fd197e0dde2 diff --git a/vendor/github.com/robertkrimen/otto b/vendor/github.com/robertkrimen/otto new file mode 160000 index 000000000..7d9cbc2be --- /dev/null +++ b/vendor/github.com/robertkrimen/otto @@ -0,0 +1 @@ +Subproject commit 7d9cbc2befca39869eb0e5bcb0f44c0692c2f8ff diff --git a/vendor/github.com/spf13/cobra b/vendor/github.com/spf13/cobra new file mode 160000 index 000000000..9c28e4bbd --- /dev/null +++ b/vendor/github.com/spf13/cobra @@ -0,0 +1 @@ +Subproject commit 9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744 diff --git a/vendor/github.com/spf13/viper b/vendor/github.com/spf13/viper new file mode 160000 index 000000000..16990631d --- /dev/null +++ b/vendor/github.com/spf13/viper @@ -0,0 +1 @@ +Subproject commit 16990631d4aa7e38f73dbbbf37fa13e67c648531 diff --git a/vendor/github.com/tj/go-elastic b/vendor/github.com/tj/go-elastic new file mode 160000 index 000000000..9a9a2a21e --- /dev/null +++ b/vendor/github.com/tj/go-elastic @@ -0,0 +1 @@ +Subproject commit 9a9a2a21e071e6e38f236740c3b650e7316ae67e diff --git a/vendor/gopkg.in/redis.v3 b/vendor/gopkg.in/redis.v3 new file mode 160000 index 000000000..b5e368500 --- /dev/null +++ b/vendor/gopkg.in/redis.v3 @@ -0,0 +1 @@ +Subproject commit b5e368500d0a508ef8f16e9c2d4025a8a46bcc29 diff --git a/vendor/gopkg.in/yaml.v2 b/vendor/gopkg.in/yaml.v2 new file mode 160000 index 000000000..31c299268 --- /dev/null +++ b/vendor/gopkg.in/yaml.v2 @@ -0,0 +1 @@ +Subproject commit 31c299268d302dd0aa9a0dcf765a3d58971ac83f From 6a534c8887731f9d680306c9323b5536ca4ba095 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 12:07:56 +0200 Subject: [PATCH 1713/2266] Update Makefile --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index c897d5918..65d95cbd2 100644 --- a/Makefile +++ b/Makefile @@ -15,12 +15,12 @@ BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` LDFLAGS = -ldflags "-w -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" -DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .Imports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` -TEST_DEPS = `comm -23 <(sort <($(GOCMD) list -f '{{join .TestImports "\n"}}' ./...) | uniq) <($(GOCMD) list std) | grep -v TheThingsNetwork` - -select_pkgs = $(GOCMD) list ./... | grep -vE 'vendor|ttnctl' +select_pkgs = $(GOCMD) list ./... | grep -vE 'ttn/vendor|ttn/ttnctl' coverage_pkgs = $(GOCMD) list ./... | grep -vE 'ttn/api|ttn/cmd|ttn/vendor|ttn/ttnctl' +DEPS = `comm -23 <($(GOCMD) list -f '{{join .Deps "\n"}}' . | grep -vE 'github.com/TheThingsNetwork/ttn' | sort | uniq) <($(GOCMD) list std)` +TEST_DEPS = `comm -23 <($(select_pkgs) | xargs $(GOCMD) list -f '{{join .TestImports "\n"}}' | grep -vE 'github.com/TheThingsNetwork/ttn' | sort | uniq) <($(GOCMD) list std)` + RELEASE_DIR ?= release COVER_FILE = coverage.out TEMP_COVER_DIR ?= .cover From bc1226af70e63a580f29bd75aefd4958f0d61e7a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 12:42:48 +0200 Subject: [PATCH 1714/2266] Vendor golang.org/x/oauth2 --- .gitmodules | 3 +++ vendor/golang.org/x/oauth2 | 1 + 2 files changed, 4 insertions(+) create mode 160000 vendor/golang.org/x/oauth2 diff --git a/.gitmodules b/.gitmodules index 684a928dd..b3b1e81ef 100644 --- a/.gitmodules +++ b/.gitmodules @@ -55,3 +55,6 @@ [submodule "vendor/gopkg.in/yaml.v2"] path = vendor/gopkg.in/yaml.v2 url = https://gopkg.in/yaml.v2 +[submodule "vendor/golang.org/x/oauth2"] + path = vendor/golang.org/x/oauth2 + url = https://go.googlesource.com/oauth2 diff --git a/vendor/golang.org/x/oauth2 b/vendor/golang.org/x/oauth2 new file mode 160000 index 000000000..3c3a985cb --- /dev/null +++ b/vendor/golang.org/x/oauth2 @@ -0,0 +1 @@ +Subproject commit 3c3a985cb79f52a3190fbc056984415ca6763d01 From 1abe547aae9ec889420099cd619bfd276279ab93 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 13:04:11 +0200 Subject: [PATCH 1715/2266] Remove manul from dev-deps --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 65d95cbd2..f5a1ac137 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,6 @@ dev-deps: update-deps test-deps $(GOCMD) get -u -v github.com/golang/mock/gomock $(GOCMD) get -u -v github.com/golang/mock/mockgen $(GOCMD) get -u -v github.com/ddollar/forego - $(GOCMD) get -u -v github.com/kovetskiy/manul cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi From d1f30bae141550fe74f61858e50443a513cb2637 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 13:05:37 +0200 Subject: [PATCH 1716/2266] Make payload function placeholders consistent with Dashboard --- ttnctl/cmd/applications_pf_set.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index ce04b7b07..508e7939b 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -61,9 +61,9 @@ The functions are read from the supplied file or from STDIN.`, } else { switch function { case "decoder": - fmt.Println(`function (bytes) { + fmt.Println(`function Decoder(bytes) { + // Here you can decode the payload into json. // bytes is of type Buffer. - // todo: return an object return { payload: bytes, @@ -72,29 +72,29 @@ The functions are read from the supplied file or from STDIN.`, ########## Write your Decoder here and end with Ctrl+D (EOF):`) app.Decoder = readFunction() case "converter": - fmt.Println(`function (val) { + fmt.Println(`function Converter(val) { + // Here you can combine the json values into a more meaningful value. // val is the output of the decoder function. - // todo: return an object return val; } ########## Write your Converter here and end with Ctrl+D (EOF):`) app.Converter = readFunction() case "validator": - fmt.Println(`function (val) { + fmt.Println(`function Validator(val) { + // This function defines which values will be propagated. // val is the output of the converter function. - // todo: return a boolean return true; } ########## Write your Validator here and end with Ctrl+D (EOF):`) app.Validator = readFunction() case "encoder": - fmt.Println(`function (val) { - // val is the output of the encoder function. - - // todo: return an array of numbers - return return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]; + fmt.Println(`function Encoder(obj) { + // The encoder encodes application data (a JS object) + // into a binary payload that is sent to devices. + // todo: return an array of numbers representing the payload + return [ 0x1 ]; } ########## Write your Encoder here and end with Ctrl+D (EOF):`) app.Encoder = readFunction() From fa141655bc8ad61f61afd616d18ede4f29b1cac6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 15:25:04 +0200 Subject: [PATCH 1717/2266] Add Brewfile --- Brewfile | 6 ++++++ README.md | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 Brewfile diff --git a/Brewfile b/Brewfile new file mode 100644 index 000000000..e8907f25f --- /dev/null +++ b/Brewfile @@ -0,0 +1,6 @@ +tap 'homebrew/bundle' + +brew 'go' +brew 'mosquitto', service_restart: true +brew 'protobuf' +brew 'redis', service_restart: true diff --git a/README.md b/README.md index 06c97acef..d5a4a3474 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ When you get started with The Things Network, you'll probably have some question 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) 4. Make sure you have [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) **installed** and **running**. If you're on a Mac, just run: - * `brew install go mosquitto redis` + * `brew bundle` * `brew services start mosquitto` * `brew services start redis` From 9dac8f24ed76e9787251189f10f39b70056cb8f3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Sep 2016 16:33:50 +0200 Subject: [PATCH 1718/2266] Also publish parent objects in MQTT fields --- mqtt/client.go | 3 +-- mqtt/client_test.go | 4 ++++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index aab5f6241..ac8c43394 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -293,10 +293,9 @@ func flatten(prefix, sep string, in, out map[string]interface{}) { if prefix == "" { key = k } + out[key] = v if next, ok := v.(map[string]interface{}); ok { flatten(key, sep, next, out) - } else { - out[key] = v } } } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 83c0d686b..2f4857e93 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -145,6 +145,8 @@ func TestPublishUplinkFields(t *testing.T) { switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { case "battery": a.So(string(msg.Payload()), ShouldEqual, "90") + case "sensors": + a.So(string(msg.Payload()), ShouldContainSubstring, `people":["`) case "sensors/color": a.So(string(msg.Payload()), ShouldEqual, `"blue"`) case "sensors/people": @@ -153,6 +155,8 @@ func TestPublishUplinkFields(t *testing.T) { a.So(string(msg.Payload()), ShouldEqual, "true") case "sensors/analog": a.So(string(msg.Payload()), ShouldEqual, `[0,255,500,1000]`) + case "sensors/history": + a.So(string(msg.Payload()), ShouldContainSubstring, `today":"`) case "sensors/history/today": a.So(string(msg.Payload()), ShouldEqual, `"not yet"`) case "sensors/history/yesterday": From 6bbda9c62affea64325644868a400703b7dcfe49 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 14 Sep 2016 21:43:24 +0200 Subject: [PATCH 1719/2266] Update readme for new parent field topic (#243) * Update readme for new parent field topic * Remove spaces --- mqtt/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/mqtt/README.md b/mqtt/README.md index b50ce1c78..fe6d4fdff 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -81,6 +81,7 @@ you will see this on MQTT: * `my-app-id/devices/my-dev-id/up/water`: `true` * `my-app-id/devices/my-dev-id/up/analog`: `[0, 255, 500, 1000]` +* `my-app-id/devices/my-dev-id/up/gps`: `{"lat":52.3736735,"lon":4.886663}` * `my-app-id/devices/my-dev-id/up/gps/lat`: `52.3736735` * `my-app-id/devices/my-dev-id/up/gps/lon`: `4.886663` * `my-app-id/devices/my-dev-id/up/text`: `"why are you using text?"` From 5bf2dfb9f368cea043a0c8444a32e418b35dc5bf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 15 Sep 2016 13:37:32 +0200 Subject: [PATCH 1720/2266] Move core errors to separate package --- api/discovery/dial.go | 2 +- api/handler/manager_client.go | 27 ++++++----- core/account/util/http.go | 2 +- core/broker/activation.go | 13 +++--- core/broker/broker.go | 16 +++---- core/broker/broker_test.go | 2 +- core/broker/downlink.go | 7 ++- core/broker/manager_server.go | 15 +++---- core/broker/server.go | 16 +++---- core/broker/uplink.go | 21 +++++---- core/broker/uplink_test.go | 8 ++-- core/component.go | 34 +++++++------- core/discovery/announcement/store.go | 8 ++-- core/discovery/discovery.go | 3 +- core/discovery/kv/store.go | 9 ++-- core/discovery/server.go | 12 ++--- core/handler/activation.go | 20 ++++----- core/handler/application/store.go | 8 ++-- core/handler/convert_fields.go | 36 +++++++-------- core/handler/convert_lorawan.go | 16 +++---- core/handler/device/store.go | 8 ++-- core/handler/dry_run.go | 10 ++--- core/handler/handler.go | 9 ++-- core/handler/manager_server.go | 67 ++++++++++++++-------------- core/handler/server.go | 10 ++--- core/handler/types.go | 2 +- core/networkserver/device/store.go | 10 ++--- core/networkserver/manager_server.go | 20 ++++----- core/networkserver/networkserver.go | 23 +++++----- core/networkserver/server.go | 30 ++++++------- core/otaa/session_keys.go | 4 +- core/router/activation.go | 5 +-- core/router/downlink.go | 12 ++--- core/router/gateway/schedule.go | 4 +- core/router/router.go | 9 ++-- core/router/server.go | 10 ++--- core/router/uplink.go | 6 +-- core/types/data_rate.go | 2 +- core/types/dev_addr.go | 2 +- core/types/device_type.go | 2 +- core/types/eui.go | 2 +- core/types/keys.go | 2 +- ttnctl/cmd/gateway_status.go | 4 +- ttnctl/util/handler.go | 4 +- ttnctl/util/lorawan.go | 2 +- ttnctl/util/router.go | 6 +-- {core => utils/errors}/errors.go | 28 +++++++++--- 47 files changed, 292 insertions(+), 276 deletions(-) rename {core => utils/errors}/errors.go (85%) diff --git a/api/discovery/dial.go b/api/discovery/dial.go index 5cb237a89..2f66e4888 100644 --- a/api/discovery/dial.go +++ b/api/discovery/dial.go @@ -1,7 +1,7 @@ package discovery import ( - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "strings" "github.com/TheThingsNetwork/ttn/api" diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index 0d7093b82..20d05c075 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -7,9 +7,8 @@ import ( "sync" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/pkg/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/metadata" @@ -72,7 +71,7 @@ func (h *ManagerClient) getContext() context.Context { func (h *ManagerClient) GetApplication(appID string) (*Application, error) { res, err := h.applicationManagerClient.GetApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Could not get application from Handler") + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not get application from Handler") } return res, nil } @@ -80,26 +79,26 @@ func (h *ManagerClient) GetApplication(appID string) (*Application, error) { // SetApplication sets an application on the Handler func (h *ManagerClient) SetApplication(in *Application) error { _, err := h.applicationManagerClient.SetApplication(h.getContext(), in) - return errors.Wrap(core.FromGRPCError(err), "Could not set application on Handler") + return errors.Wrap(errors.FromGRPCError(err), "Could not set application on Handler") } // RegisterApplication registers an application on the Handler func (h *ManagerClient) RegisterApplication(appID string) error { _, err := h.applicationManagerClient.RegisterApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) - return errors.Wrap(core.FromGRPCError(err), "Could not register application on Handler") + return errors.Wrap(errors.FromGRPCError(err), "Could not register application on Handler") } // DeleteApplication deletes an application and all its devices from the Handler func (h *ManagerClient) DeleteApplication(appID string) error { _, err := h.applicationManagerClient.DeleteApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) - return errors.Wrap(core.FromGRPCError(err), "Could not delete application from Handler") + return errors.Wrap(errors.FromGRPCError(err), "Could not delete application from Handler") } // GetDevice retrieves a device from the Handler func (h *ManagerClient) GetDevice(appID string, devID string) (*Device, error) { res, err := h.applicationManagerClient.GetDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Could not get device from Handler") + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not get device from Handler") } return res, nil } @@ -107,20 +106,20 @@ func (h *ManagerClient) GetDevice(appID string, devID string) (*Device, error) { // SetDevice sets a device on the Handler func (h *ManagerClient) SetDevice(in *Device) error { _, err := h.applicationManagerClient.SetDevice(h.getContext(), in) - return errors.Wrap(core.FromGRPCError(err), "Could not set device on Handler") + return errors.Wrap(errors.FromGRPCError(err), "Could not set device on Handler") } // DeleteDevice deletes a device from the Handler func (h *ManagerClient) DeleteDevice(appID string, devID string) error { _, err := h.applicationManagerClient.DeleteDevice(h.getContext(), &DeviceIdentifier{AppId: appID, DevId: devID}) - return errors.Wrap(core.FromGRPCError(err), "Could not delete device from Handler") + return errors.Wrap(errors.FromGRPCError(err), "Could not delete device from Handler") } // GetDevicesForApplication retrieves all devices for an application from the Handler func (h *ManagerClient) GetDevicesForApplication(appID string) (devices []*Device, err error) { res, err := h.applicationManagerClient.GetDevicesForApplication(h.getContext(), &ApplicationIdentifier{AppId: appID}) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Could not get devices for application from Handler") + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not get devices for application from Handler") } for _, dev := range res.Devices { devices = append(devices, dev) @@ -135,7 +134,7 @@ func (h *ManagerClient) GetDevAddr(constraints ...string) (types.DevAddr, error) Usage: constraints, }) if err != nil { - return types.DevAddr{}, errors.Wrap(core.FromGRPCError(err), "Could not get DevAddr from Handler") + return types.DevAddr{}, errors.Wrap(errors.FromGRPCError(err), "Could not get DevAddr from Handler") } return *resp.DevAddr, nil } @@ -148,7 +147,7 @@ func (h *ManagerClient) DryUplink(payload []byte, app *Application) (*DryUplinkR Payload: payload, }) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Could not dry-run uplink on Handler") + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run uplink on Handler") } return res, nil } @@ -161,7 +160,7 @@ func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application) Payload: payload, }) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Could not dry-run downlink with payload on Handler") + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run downlink with payload on Handler") } return res, nil } @@ -179,7 +178,7 @@ func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app Fields: string(marshalled), }) if err != nil { - return nil, errors.Wrap(core.FromGRPCError(err), "Could not dry-run downlink with fields on Handler") + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run downlink with fields on Handler") } return res, nil } diff --git a/core/account/util/http.go b/core/account/util/http.go index 471942ad5..c7b40c3f3 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -6,7 +6,7 @@ package util import ( "bytes" "encoding/json" - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "fmt" "io" "net/http" diff --git a/core/broker/activation.go b/core/broker/activation.go index 2cae2ef77..b9682d297 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -14,10 +14,9 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/brocaar/lorawan" - "github.com/pkg/errors" ) type challengeResponseWithHandler struct { @@ -47,7 +46,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) if len(duplicates) == 0 { - err = core.NewErrInternal("No duplicates") + err = errors.NewErrInternal("No duplicates") return nil, err } @@ -84,7 +83,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // Send Activate to NS deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) if err != nil { - err = errors.Wrap(core.FromGRPCError(err), "NetworkServer refused to prepare activation") + err = errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused to prepare activation") return nil, err } @@ -100,7 +99,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D return nil, err } if len(announcements) == 0 { - err = core.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) + err = errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) return nil, err } @@ -194,13 +193,13 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D var handlerResponse *pb_handler.DeviceActivationResponse handlerResponse, err = joinHandlerClient.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { - err = errors.Wrap(core.FromGRPCError(err), "Handler refused activation") + err = errors.Wrap(errors.FromGRPCError(err), "Handler refused activation") return nil, err } handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) if err != nil { - err = errors.Wrap(core.FromGRPCError(err), "NetworkServer refused activation") + err = errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused activation") return nil, err } diff --git a/core/broker/broker.go b/core/broker/broker.go index 3eb987283..0e6c321fb 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -16,7 +16,7 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/pkg/errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc" ) @@ -72,7 +72,7 @@ func (b *broker) checkPrefixAnnouncements() error { devAddrClient := pb_lorawan.NewDevAddrManagerClient(b.nsConn) resp, err := devAddrClient.GetPrefixes(b.GetContext(""), &pb_lorawan.PrefixesRequest{}) if err != nil { - return errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return prefixes") + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return prefixes") } for _, mapping := range resp.Prefixes { prefix, err := types.ParseDevAddrPrefix(mapping.Prefix) @@ -140,7 +140,7 @@ func (b *broker) ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) { b.routersLock.Lock() defer b.routersLock.Unlock() if existing, ok := b.routers[id]; ok { - return existing, core.NewErrInternal(fmt.Sprintf("Router %s already active", id)) + return existing, errors.NewErrInternal(fmt.Sprintf("Router %s already active", id)) } b.routers[id] = make(chan *pb.DownlinkMessage) return b.routers[id], nil @@ -154,7 +154,7 @@ func (b *broker) DeactivateRouter(id string) error { delete(b.routers, id) return nil } - return core.NewErrInternal(fmt.Sprintf("Router %s not active", id)) + return errors.NewErrInternal(fmt.Sprintf("Router %s not active", id)) } func (b *broker) getRouter(id string) (chan<- *pb.DownlinkMessage, error) { @@ -163,14 +163,14 @@ func (b *broker) getRouter(id string) (chan<- *pb.DownlinkMessage, error) { if router, ok := b.routers[id]; ok { return router, nil } - return nil, core.NewErrInternal(fmt.Sprintf("Router %s not active", id)) + return nil, errors.NewErrInternal(fmt.Sprintf("Router %s not active", id)) } func (b *broker) ActivateHandler(id string) (<-chan *pb.DeduplicatedUplinkMessage, error) { b.handlersLock.Lock() defer b.handlersLock.Unlock() if existing, ok := b.handlers[id]; ok { - return existing, core.NewErrInternal(fmt.Sprintf("Handler %s already active", id)) + return existing, errors.NewErrInternal(fmt.Sprintf("Handler %s already active", id)) } b.handlers[id] = make(chan *pb.DeduplicatedUplinkMessage) return b.handlers[id], nil @@ -184,7 +184,7 @@ func (b *broker) DeactivateHandler(id string) error { delete(b.handlers, id) return nil } - return core.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) + return errors.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) } func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, error) { @@ -193,5 +193,5 @@ func (b *broker) getHandler(id string) (chan<- *pb.DeduplicatedUplinkMessage, er if handler, ok := b.handlers[id]; ok { return handler, nil } - return nil, core.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) + return nil, errors.NewErrInternal(fmt.Sprintf("Handler %s not active", id)) } diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go index 8252abb7d..749af2c14 100644 --- a/core/broker/broker_test.go +++ b/core/broker/broker_test.go @@ -4,7 +4,7 @@ package broker import ( - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "sync" "testing" diff --git a/core/broker/downlink.go b/core/broker/downlink.go index b6f6aae88..324e4526b 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -8,9 +8,8 @@ import ( "time" pb "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" - "github.com/pkg/errors" ) // ByScore is used to sort a list of DownlinkOptions based on Score @@ -37,14 +36,14 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { downlink, err = b.ns.Downlink(b.Component.GetContext(b.nsToken), downlink) if err != nil { - return errors.Wrap(core.FromGRPCError(err), "NetworkServer did not handle downlink") + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle downlink") } var routerID string if id := strings.Split(downlink.DownlinkOption.Identifier, ":"); len(id) == 2 { routerID = id[0] } else { - return core.NewErrInvalidArgument("DownlinkOption Identifier", "invalid format") + return errors.NewErrInvalidArgument("DownlinkOption Identifier", "invalid format") } ctx = ctx.WithField("RouterID", routerID) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index ff19a2224..ec2ba4533 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -8,9 +8,8 @@ import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -25,7 +24,7 @@ type brokerManager struct { func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { res, err := b.deviceManager.GetDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return device")) } return res, nil } @@ -33,7 +32,7 @@ func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentif func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*empty.Empty, error) { res, err := b.deviceManager.SetDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not set device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not set device")) } return res, nil } @@ -41,7 +40,7 @@ func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*emp func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*empty.Empty, error) { res, err := b.deviceManager.DeleteDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not delete device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not delete device")) } return res, nil } @@ -49,7 +48,7 @@ func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIden func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*empty.Empty, error) { claims, err := b.broker.Component.ValidateTTNAuthContext(ctx) if err != nil { - return nil, core.BuildGRPCError(core.FromGRPCError(err)) + return nil, errors.BuildGRPCError(errors.FromGRPCError(err)) } if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Handler Registration") @@ -69,7 +68,7 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesRequest) (*lorawan.PrefixesResponse, error) { res, err := b.devAddrManager.GetPrefixes(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return prefixes")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return prefixes")) } return res, nil } @@ -77,7 +76,7 @@ func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesReq func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrRequest) (*lorawan.DevAddrResponse, error) { res, err := b.devAddrManager.GetDevAddr(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return DevAddr")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return DevAddr")) } return res, nil } diff --git a/core/broker/server.go b/core/broker/server.go index e8bd2272b..376611af1 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -8,7 +8,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" @@ -24,11 +24,11 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { router, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { - return core.BuildGRPCError(err) + return errors.BuildGRPCError(err) } downlinkChannel, err := b.broker.ActivateRouter(router.Id) if err != nil { - return core.BuildGRPCError(err) + return errors.BuildGRPCError(err) } defer b.broker.DeactivateRouter(router.Id) go func() { @@ -67,11 +67,11 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_SubscribeServer) error { handler, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { - return core.BuildGRPCError(err) + return errors.BuildGRPCError(err) } uplinkChannel, err := b.broker.ActivateHandler(handler.Id) if err != nil { - return core.BuildGRPCError(err) + return errors.BuildGRPCError(err) } defer b.broker.DeactivateHandler(handler.Id) for { @@ -94,7 +94,7 @@ func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_Subscri func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { handler, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { - return core.BuildGRPCError(err) + return errors.BuildGRPCError(err) } for { downlink, err := stream.Recv() @@ -131,14 +131,14 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { _, err = b.broker.ValidateNetworkContext(ctx) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } res, err = b.broker.HandleActivation(req) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index b4bdbb418..d150d863e 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -15,12 +15,11 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/brocaar/lorawan" - "github.com/pkg/errors" ) const maxFCntGap = 16384 @@ -48,7 +47,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { base := duplicates[0] if base.ProtocolMetadata.GetLorawan() == nil { - err = core.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") + err = errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") return err } @@ -60,7 +59,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - err = core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + err = errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") return err } @@ -73,10 +72,10 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { FCnt: macPayload.FHDR.FCnt, }) if err != nil { - return core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not return devices")) + return errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices")) } if len(getDevicesResp.Results) == 0 { - err = core.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) + err = errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) return err } ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) @@ -103,7 +102,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } } if device == nil { - err = core.NewErrNotFound("device that validates MIC") + err = errors.NewErrNotFound("device that validates MIC") return err } ctx = ctx.WithFields(log.Fields{ @@ -120,7 +119,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { // Replay attack or FCnt gap too big - err = core.NewErrNotFound("device with matching FCnt") + err = errors.NewErrNotFound("device with matching FCnt") return err } @@ -159,7 +158,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { // Pass Uplink through NS deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(b.nsToken), deduplicatedUplink) if err != nil { - return core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "NetworkServer did not handle uplink")) + return errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle uplink")) } var announcements []*pb_discovery.Announcement @@ -168,11 +167,11 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } if len(announcements) == 0 { - err = core.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) + err = errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) return err } if len(announcements) > 1 { - err = core.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) + err = errors.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) return err } diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 9abaef9e7..1c209402e 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -14,8 +14,8 @@ import ( pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/brocaar/lorawan" "github.com/golang/mock/gomock" . "github.com/smartystreets/assertions" @@ -61,7 +61,7 @@ func TestHandleUplink(t *testing.T) { GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) - a.So(err, ShouldHaveSameTypeAs, &core.ErrNotFound{}) + a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) devEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 8} wrongDevEUI := types.DevEUI{1, 2, 3, 4, 5, 6, 7, 9} @@ -99,7 +99,7 @@ func TestHandleUplink(t *testing.T) { GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) - a.So(err, ShouldHaveSameTypeAs, &core.ErrNotFound{}) + a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) phy.SetMIC(lorawan.AES128Key{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}) bytes, _ = phy.MarshalBinary() @@ -112,7 +112,7 @@ func TestHandleUplink(t *testing.T) { GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) - a.So(err, ShouldHaveSameTypeAs, &core.ErrNotFound{}) + a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) // Disable FCnt Check b.uplinkDeduplicator = NewDeduplicator(10 * time.Millisecond) diff --git a/core/component.go b/core/component.go index 65efc9f52..f958e4988 100644 --- a/core/component.go +++ b/core/component.go @@ -13,13 +13,13 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/TheThingsNetwork/ttn/utils/security" "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/dgrijalva/jwt-go" "github.com/mwitkow/go-grpc-middleware" - "github.com/pkg/errors" "github.com/spf13/viper" "golang.org/x/net/context" "google.golang.org/grpc" @@ -151,7 +151,7 @@ func (c *Component) SetStatus(status Status) { func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { res, err := c.Discovery.Get(serviceName, id) if err != nil { - return nil, errors.Wrapf(FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) + return nil, errors.Wrapf(errors.FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) } return res, nil } @@ -159,11 +159,11 @@ func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement // Announce the component to TTN discovery func (c *Component) Announce() error { if c.Identity.Id == "" { - return NewErrInvalidArgument("Component ID", "can not be empty") + return errors.NewErrInvalidArgument("Component ID", "can not be empty") } err := c.Discovery.Announce(c.AccessToken) if err != nil { - return errors.Wrapf(FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) + return errors.Wrapf(errors.FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) } c.Ctx.Info("ttn: Announced to TTN discovery") @@ -173,7 +173,7 @@ func (c *Component) Announce() error { // UpdateTokenKey updates the OAuth Bearer token key func (c *Component) UpdateTokenKey() error { if c.TokenKeyProvider == nil { - return NewErrInternal("No public key provider configured for token validation") + return errors.NewErrInternal("No public key provider configured for token validation") } // Set up Auth Server Token Validation @@ -221,7 +221,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d md, ok := metadata.FromContext(ctx) if !ok { - err = NewErrInternal("Could not get metadata from context") + err = errors.NewErrInternal("Could not get metadata from context") return } var id, serviceName, token string @@ -229,14 +229,14 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d id = ids[0] } if id == "" { - err = NewErrInvalidArgument("Metadata", "id missing") + err = errors.NewErrInvalidArgument("Metadata", "id missing") return } if serviceNames, ok := md["service-name"]; ok && len(serviceNames) == 1 { serviceName = serviceNames[0] } if serviceName == "" { - err = NewErrInvalidArgument("Metadata", "service-name missing") + err = errors.NewErrInvalidArgument("Metadata", "service-name missing") return } if tokens, ok := md["token"]; ok && len(tokens) == 1 { @@ -254,7 +254,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d } if token == "" { - err = NewErrInvalidArgument("Metadata", "token missing") + err = errors.NewErrInvalidArgument("Metadata", "token missing") return } @@ -264,7 +264,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d return } if claims.Issuer != id { - err = NewErrInvalidArgument("Metadata", "token was issued by different component id") + err = errors.NewErrInvalidArgument("Metadata", "token was issued by different component id") return } @@ -275,23 +275,23 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) { md, ok := metadata.FromContext(ctx) if !ok { - return nil, NewErrInternal("Could not get metadata from context") + return nil, errors.NewErrInternal("Could not get metadata from context") } token, ok := md["token"] if !ok || len(token) < 1 { - return nil, NewErrInvalidArgument("Metadata", "token missing") + return nil, errors.NewErrInvalidArgument("Metadata", "token missing") } ttnClaims := &TTNClaims{} parsed, err := jwt.ParseWithClaims(token[0], ttnClaims, func(token *jwt.Token) (interface{}, error) { if c.TokenKeyProvider == nil { - return nil, NewErrInternal("No token provider configured") + return nil, errors.NewErrInternal("No token provider configured") } k, err := c.TokenKeyProvider.Get(ttnClaims.Issuer, false) if err != nil { return nil, err } if k.Algorithm != token.Header["alg"] { - return nil, NewErrInvalidArgument("Token", fmt.Sprintf("expected algorithm %v but got %v", k.Algorithm, token.Header["alg"])) + return nil, errors.NewErrInvalidArgument("Token", fmt.Sprintf("expected algorithm %v but got %v", k.Algorithm, token.Header["alg"])) } key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(k.Key)) if err != nil { @@ -300,10 +300,10 @@ func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, err return key, nil }) if err != nil { - return nil, NewErrInvalidArgument("Token", fmt.Sprintf("unable to parse token: %s", err.Error())) + return nil, errors.NewErrInvalidArgument("Token", fmt.Sprintf("unable to parse token: %s", err.Error())) } if !parsed.Valid { - return nil, NewErrInvalidArgument("Token", "not valid or expired") + return nil, errors.NewErrInvalidArgument("Token", "not valid or expired") } return ttnClaims, nil } @@ -332,7 +332,7 @@ func (c *Component) ServerOptions() []grpc.ServerOption { iface, err := handler(ctx, req) logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) if err != nil { - err := FromGRPCError(err) + err := errors.FromGRPCError(err) logCtx.WithField("error", err.Error()).WithField("err-type", fmt.Sprintf("%T", err)).WithField("err-err-type", fmt.Sprintf("%T", err.Error())).Warn("Could not handle Request") } else { logCtx.Info("Handled request") diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go index 119699565..c091616b7 100644 --- a/core/discovery/announcement/store.go +++ b/core/discovery/announcement/store.go @@ -7,7 +7,7 @@ import ( "fmt" pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "gopkg.in/redis.v3" ) @@ -65,7 +65,7 @@ func (s *announcementStore) Get(serviceName, serviceID string) (*pb.Announcement return announcement, nil } } - return nil, core.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) } func (s *announcementStore) Set(new *pb.Announcement) error { @@ -148,11 +148,11 @@ func (s *redisAnnouncementStore) Get(serviceName, serviceID string) (*pb.Announc res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, serviceID)).Result() if err != nil { if err == redis.Nil { - return nil, core.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) } return nil, err } else if len(res) == 0 { - return nil, core.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) } announcement := &pb.Announcement{} err = announcement.FromStringStringMap(res) diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 454c37425..e9b0ed468 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/TheThingsNetwork/ttn/core/discovery/kv" + "github.com/TheThingsNetwork/ttn/utils/errors" "gopkg.in/redis.v3" ) @@ -43,7 +44,7 @@ func (d *discovery) Init(c *core.Component) error { func (d *discovery) Announce(in *pb.Announcement) error { existing, err := d.services.Get(in.ServiceName, in.Id) - if core.GetErrType(err) == core.NotFound { + if errors.GetErrType(err) == errors.NotFound { // Not found; create new existing = &pb.Announcement{} } else if err != nil { diff --git a/core/discovery/kv/store.go b/core/discovery/kv/store.go index 42603986c..3344390a7 100644 --- a/core/discovery/kv/store.go +++ b/core/discovery/kv/store.go @@ -6,7 +6,8 @@ package kv import ( "fmt" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" + "gopkg.in/redis.v3" ) @@ -43,7 +44,7 @@ func (s *kvStore) Get(key string) (string, error) { if value, ok := s.data[key]; ok { return value, nil } - return "", core.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) + return "", errors.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) } func (s *kvStore) Set(key, value string) error { @@ -111,11 +112,11 @@ func (s *redisKVStore) Get(key string) (string, error) { res, err := s.client.Get(fmt.Sprintf("%s:%s", s.prefix, key)).Result() if err != nil { if err == redis.Nil { - return "", core.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) + return "", errors.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) } return "", err } else if res == "" { - return "", core.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) + return "", errors.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) } return res, nil } diff --git a/core/discovery/server.go b/core/discovery/server.go index 9a9dccb80..d628c3af4 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -5,7 +5,7 @@ package discovery import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" @@ -91,7 +91,7 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement err = d.discovery.Announce(&announcementCopy) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil } @@ -103,7 +103,7 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques } err = d.discovery.AddMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil } @@ -115,7 +115,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq } err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil } @@ -123,7 +123,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { services, err := d.discovery.GetAll(req.ServiceName) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &pb.AnnouncementsResponse{ Services: services, @@ -133,7 +133,7 @@ func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*p func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { service, err := d.discovery.Get(req.ServiceName, req.Id) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return service, nil } diff --git a/core/handler/activation.go b/core/handler/activation.go index ce6933939..7fffae53c 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -9,11 +9,11 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -42,7 +42,7 @@ func (h *handler) HandleActivationChallenge(challenge *pb_broker.ActivationChall } if dev.AppKey.IsEmpty() { - err = core.NewErrNotFound(fmt.Sprintf("AppKey for device %s", challenge.DevId)) + err = errors.NewErrNotFound(fmt.Sprintf("AppKey for device %s", challenge.DevId)) return nil, err } @@ -54,7 +54,7 @@ func (h *handler) HandleActivationChallenge(challenge *pb_broker.ActivationChall // Set MIC if err := reqPHY.SetMIC(lorawan.AES128Key(dev.AppKey)); err != nil { - err = core.NewErrNotFound("Could not set MIC") + err = errors.NewErrNotFound("Could not set MIC") return nil, err } @@ -95,7 +95,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv }() if activation.ResponseTemplate == nil { - err = core.NewErrInternal("No downlink available") + err = errors.NewErrInternal("No downlink available") return nil, err } @@ -107,13 +107,13 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } if dev.AppKey.IsEmpty() { - err = core.NewErrNotFound(fmt.Sprintf("AppKey for device %s", activation.DevId)) + err = errors.NewErrNotFound(fmt.Sprintf("AppKey for device %s", activation.DevId)) return nil, err } // Check for LoRaWAN if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - err = core.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") + err = errors.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") return nil, err } @@ -124,13 +124,13 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } reqMAC, ok := reqPHY.MACPayload.(*lorawan.JoinRequestPayload) if !ok { - err = core.NewErrInvalidArgument("Activation", "does not contain a JoinRequestPayload") + err = errors.NewErrInvalidArgument("Activation", "does not contain a JoinRequestPayload") return nil, err } // Validate MIC if ok, err = reqPHY.ValidateMIC(lorawan.AES128Key(dev.AppKey)); err != nil || !ok { - err = core.NewErrNotFound("MIC does not match device") + err = errors.NewErrNotFound("MIC does not match device") return nil, err } @@ -143,7 +143,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } } if alreadyUsed { - err = core.NewErrInvalidArgument("Activation DevNonce", "already used") + err = errors.NewErrInvalidArgument("Activation DevNonce", "already used") return nil, err } @@ -156,7 +156,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } resMAC, ok := resPHY.MACPayload.(*lorawan.DataPayload) if !ok { - err = core.NewErrInvalidArgument("Activation ResponseTemplate", "MACPayload must be a *DataPayload") + err = errors.NewErrInvalidArgument("Activation ResponseTemplate", "MACPayload must be a *DataPayload") return nil, err } joinAccept := &lorawan.JoinAcceptPayload{} diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 129956e1b..07c058cac 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "gopkg.in/redis.v3" ) @@ -48,7 +48,7 @@ func (s *applicationStore) Get(appID string) (*Application, error) { if app, ok := s.applications[appID]; ok { return app, nil } - return nil, core.NewErrNotFound(fmt.Sprintf("%s", appID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s", appID)) } func (s *applicationStore) Set(new *Application, fields ...string) error { @@ -100,11 +100,11 @@ func (s *redisApplicationStore) Get(appID string) (*Application, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appID)).Result() if err != nil { if err == redis.Nil { - return nil, core.NewErrNotFound(fmt.Sprintf("%s", appID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s", appID)) } return nil, err } else if len(res) == 0 { - return nil, core.NewErrNotFound(fmt.Sprintf("%s", appID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s", appID)) } application := &Application{} err = application.FromStringStringMap(res) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index e2547d95f..5d56644f7 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -9,8 +9,8 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/robertkrimen/otto" ) @@ -35,7 +35,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat } if !valid { - return core.NewErrInvalidArgument("Payload", "payload validator function returned false") + return errors.NewErrInvalidArgument("Payload", "payload validator function returned false") } appUp.Fields = fields @@ -62,7 +62,7 @@ var timeOut = 100 * time.Millisecond // Decode decodes the payload using the Decoder function into a map func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) { if f.Decoder == "" { - return nil, core.NewErrInternal("Decoder function not set") + return nil, errors.NewErrInternal("Decoder function not set") } vm := otto.New() @@ -73,13 +73,13 @@ func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) } if !value.IsObject() { - return nil, core.NewErrInvalidArgument("Decoder", "does not return an object") + return nil, errors.NewErrInvalidArgument("Decoder", "does not return an object") } v, _ := value.Export() m, ok := v.(map[string]interface{}) if !ok { - return nil, core.NewErrInvalidArgument("Decoder", "does not return an object") + return nil, errors.NewErrInvalidArgument("Decoder", "does not return an object") } return m, nil } @@ -100,13 +100,13 @@ func (f *UplinkFunctions) Convert(data map[string]interface{}) (map[string]inter } if !value.IsObject() { - return nil, core.NewErrInvalidArgument("Converter", "does not return an object") + return nil, errors.NewErrInvalidArgument("Converter", "does not return an object") } v, _ := value.Export() m, ok := v.(map[string]interface{}) if !ok { - return nil, core.NewErrInvalidArgument("Converter", "does not return an object") + return nil, errors.NewErrInvalidArgument("Converter", "does not return an object") } return m, nil @@ -127,7 +127,7 @@ func (f *UplinkFunctions) Validate(data map[string]interface{}) (bool, error) { } if !value.IsBoolean() { - return false, core.NewErrInvalidArgument("Validator", "does not return a boolean") + return false, errors.NewErrInvalidArgument("Validator", "does not return a boolean") } return value.ToBoolean() @@ -149,7 +149,7 @@ func (f *UplinkFunctions) Process(payload []byte) (map[string]interface{}, bool, return converted, valid, err } -var errTimeOutExceeded = core.NewErrInternal("Code has been running to long") +var errTimeOutExceeded = errors.NewErrInternal("Code has been running to long") func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value otto.Value, err error) { start := time.Now() @@ -158,7 +158,7 @@ func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value ott if caught := recover(); caught != nil { if caught == errTimeOutExceeded { value = otto.Value{} - err = core.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) + err = errors.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) return } // if this is not the our timeout interrupt, raise the panic again @@ -189,7 +189,7 @@ type DownlinkFunctions struct { // If no encoder function is set, this function returns an array. func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, error) { if f.Encoder == "" { - return nil, core.NewErrInternal("Encoder function not set") + return nil, errors.NewErrInternal("Encoder function not set") } vm := otto.New() @@ -200,7 +200,7 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro } if !value.IsObject() { - return nil, core.NewErrInvalidArgument("Encoder", "does not return an object") + return nil, errors.NewErrInvalidArgument("Encoder", "does not return an object") } v, err := value.Export() @@ -209,7 +209,7 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro } if reflect.TypeOf(v).Kind() != reflect.Slice { - return nil, core.NewErrInvalidArgument("Encoder", "does not return an Array") + return nil, errors.NewErrInvalidArgument("Encoder", "does not return an Array") } s := reflect.ValueOf(v) @@ -245,19 +245,19 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro case float32: n = int64(t) if float32(n) != t { - return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") + return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } case float64: n = int64(t) if float64(n) != t { - return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") + return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } default: - return nil, core.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") + return nil, errors.NewErrInvalidArgument("Encoder", "should return an Array of integer numbers") } if n < 0 || n > 255 { - return nil, core.NewErrInvalidArgument("Encoder Output", "Numbers in Array should be between 0 and 255") + return nil, errors.NewErrInvalidArgument("Encoder Output", "Numbers in Array should be between 0 and 255") } res[i] = byte(n) @@ -283,7 +283,7 @@ func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMes } if appDown.Payload != nil { - return core.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") + return errors.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") } app, err := h.applications.Get(appDown.AppID) diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index 028231205..f3c573b3a 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -5,8 +5,8 @@ package handler import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" "github.com/brocaar/lorawan" @@ -21,7 +21,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli // Check for LoRaWAN if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan == nil { - return core.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") + return errors.NewErrInvalidArgument("Activation", "does not contain LoRaWAN metadata") } // LoRaWAN: Unmarshal Uplink @@ -32,7 +32,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt appUp.FCnt = macPayload.FHDR.FCnt @@ -43,7 +43,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli return err } if !ok { - return core.NewErrNotFound("device that validates MIC") + return errors.NewErrNotFound("device that validates MIC") } ctx = ctx.WithField("FCnt", appUp.FCnt) @@ -53,17 +53,17 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli appUp.FPort = *macPayload.FPort ctx = ctx.WithField("FCnt", appUp.FPort) if err := phyPayload.DecryptFRMPayload(lorawan.AES128Key(dev.AppSKey)); err != nil { - return core.NewErrInternal("Could not decrypt payload") + return errors.NewErrInternal("Could not decrypt payload") } if len(macPayload.FRMPayload) == 1 { payload, ok := macPayload.FRMPayload[0].(*lorawan.DataPayload) if !ok { - return core.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload") + return errors.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload") } appUp.Payload = payload.Bytes } } else { - return core.NewErrInvalidArgument("Uplink MACPayload", "could not get frame payload") + return errors.NewErrInvalidArgument("Uplink MACPayload", "could not get frame payload") } return nil @@ -84,7 +84,7 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMess } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return core.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") + return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } if ttnDown.DownlinkOption != nil { macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt diff --git a/core/handler/device/store.go b/core/handler/device/store.go index b55cba7be..6d228c35c 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -7,7 +7,7 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "gopkg.in/redis.v3" ) @@ -64,7 +64,7 @@ func (s *deviceStore) Get(appID, devID string) (*Device, error) { return dev, nil } } - return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } func (s *deviceStore) Set(new *Device, fields ...string) error { @@ -149,11 +149,11 @@ func (s *redisDeviceStore) Get(appID, devID string) (*Device, error) { res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appID, devID)).Result() if err != nil { if err == redis.Nil { - return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } return nil, err } else if len(res) == 0 { - return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } device := &Device{} err = device.FromStringStringMap(res) diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go index ac8c85853..7fb4c520c 100644 --- a/core/handler/dry_run.go +++ b/core/handler/dry_run.go @@ -7,7 +7,7 @@ import ( "encoding/json" pb "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" ) @@ -56,7 +56,7 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess if in.Payload != nil { if in.Fields != "" { - return nil, core.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") + return nil, errors.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") } return &pb.DryDownlinkResult{ Payload: in.Payload, @@ -64,11 +64,11 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess } if in.Fields == "" { - return nil, core.NewErrInvalidArgument("Downlink", "Neither Fields nor Payload provided") + return nil, errors.NewErrInvalidArgument("Downlink", "Neither Fields nor Payload provided") } if app == nil || app.Encoder == "" { - return nil, core.NewErrInvalidArgument("Encoder", "Not specified") + return nil, errors.NewErrInvalidArgument("Encoder", "Not specified") } functions := &DownlinkFunctions{ @@ -78,7 +78,7 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess var parsed map[string]interface{} err := json.Unmarshal([]byte(in.Fields), &parsed) if err != nil { - return nil, core.NewErrInvalidArgument("Fields", err.Error()) + return nil, errors.NewErrInvalidArgument("Fields", err.Error()) } payload, _, err := functions.Process(parsed) diff --git a/core/handler/handler.go b/core/handler/handler.go index ff8be6ead..316743135 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -14,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc" "gopkg.in/redis.v3" ) @@ -113,14 +114,14 @@ func (h *handler) associateBroker() error { for { upStream, err := h.ttnBroker.Subscribe(h.GetContext(""), &pb_broker.SubscribeRequest{}) if err != nil { - h.Ctx.WithError(core.FromGRPCError(err)).Error("Could not start Broker subscribe stream") + h.Ctx.WithError(errors.FromGRPCError(err)).Error("Could not start Broker subscribe stream") <-time.After(api.Backoff) continue } for { in, err := upStream.Recv() if err != nil { - h.Ctx.WithError(core.FromGRPCError(err)).Error("Error in Broker subscribe stream") + h.Ctx.WithError(errors.FromGRPCError(err)).Error("Error in Broker subscribe stream") break } go h.HandleUplink(in) @@ -132,14 +133,14 @@ func (h *handler) associateBroker() error { for { downStream, err := h.ttnBroker.Publish(h.GetContext("")) if err != nil { - h.Ctx.WithError(core.FromGRPCError(err)).Error("Could not start Broker publish stream") + h.Ctx.WithError(errors.FromGRPCError(err)).Error("Could not start Broker publish stream") <-time.After(api.Backoff) continue } for downlink := range h.downlink { err := downStream.Send(downlink) if err != nil { - h.Ctx.WithError(core.FromGRPCError(err)).Error("Error in Broker publish stream") + h.Ctx.WithError(errors.FromGRPCError(err)).Error("Error in Broker publish stream") break } } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index dde0f20b0..4a1280d8e 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -10,11 +10,10 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "github.com/pkg/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -29,21 +28,21 @@ type handlerManager struct { func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) (*device.Device, error) { if !in.Validate() { - return nil, core.NewErrInvalidArgument("Device Identifier", "validation failed") + return nil, errors.NewErrInvalidArgument("Device Identifier", "validation failed") } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { - return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err } if !claims.CanEditApp(dev.AppID) { - return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return dev, nil } @@ -51,7 +50,7 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { dev, err := h.getDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } nsDev, err := h.deviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ @@ -59,7 +58,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) DevEui: &dev.DevEUI, }) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not return device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not return device")) } return &pb.Device{ @@ -85,8 +84,8 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { dev, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) - if err != nil && core.GetErrType(err) != core.NotFound { - return nil, core.BuildGRPCError(err) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, errors.BuildGRPCError(err) } if !in.Validate() { @@ -106,7 +105,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E DevEui: &dev.DevEUI, }) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not delete device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) } } } else { // When this is a create @@ -154,12 +153,12 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E _, err = h.deviceManager.SetDevice(ctx, nsUpdated) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not set device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not set device")) } err = h.handler.devices.Set(updated) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil @@ -168,15 +167,15 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { dev, err := h.getDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not delete device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) } err = h.handler.devices.Delete(in.AppId, in.DevId) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil } @@ -187,14 +186,14 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !claims.CanEditApp(in.AppId) { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } res := &pb.DeviceList{Devices: []*pb.Device{}} for _, dev := range devices { @@ -218,21 +217,21 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*application.Application, error) { if !in.Validate() { - return nil, core.NewErrInvalidArgument("Application Identifier", "validation failed") + return nil, errors.NewErrInvalidArgument("Application Identifier", "validation failed") } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { return nil, err } if !claims.CanEditApp(in.AppId) { - return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } app, err := h.handler.applications.Get(in.AppId) if err != nil { return nil, err } if !claims.CanEditApp(app.AppID) { - return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return app, nil } @@ -240,7 +239,7 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { app, err := h.getApplication(ctx, in) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &pb.Application{ @@ -254,8 +253,8 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) - if err != nil && core.GetErrType(err) != core.NotFound { - return nil, core.BuildGRPCError(err) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, errors.BuildGRPCError(err) } if app != nil { return nil, grpcErrf(codes.AlreadyExists, "Application already registered") @@ -265,7 +264,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica AppID: in.AppId, }) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } md, _ := metadata.FromContext(ctx) @@ -290,7 +289,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { _, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !in.Validate() { @@ -305,7 +304,7 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) Encoder: in.Encoder, }) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil @@ -314,36 +313,36 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { _, err := h.getApplication(ctx, in) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } // Get and delete all devices for this application devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } for _, dev := range devices { _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not delete device")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) } err = h.handler.devices.Delete(dev.AppID, dev.DevID) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } } // Delete the Application err = h.handler.applications.Delete(in.AppId) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } md, _ := metadata.FromContext(ctx) token, _ := md["token"] err = h.handler.Discovery.DeleteMetadata(pb_discovery.Metadata_APP_ID, []byte(in.AppId), token[0]) if err != nil { - h.handler.Ctx.WithField("AppID", in.AppId).WithError(core.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") + h.handler.Ctx.WithField("AppID", in.AppId).WithError(errors.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") } return &empty.Empty{}, nil @@ -352,7 +351,7 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { res, err := h.devAddrManager.GetPrefixes(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not return prefixes")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not return prefixes")) } return res, nil } @@ -360,7 +359,7 @@ func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.Prefixe func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { res, err := h.devAddrManager.GetDevAddr(ctx, in) if err != nil { - return nil, core.BuildGRPCError(errors.Wrap(core.FromGRPCError(err), "Broker did not return DevAddr")) + return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not return DevAddr")) } return res, nil } diff --git a/core/handler/server.go b/core/handler/server.go index 6d003c2a5..21d37c455 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -6,7 +6,7 @@ package handler import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -21,14 +21,14 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !challenge.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } res, err := h.handler.HandleActivationChallenge(challenge) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } @@ -36,14 +36,14 @@ func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_brok func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !activation.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } res, err := h.handler.HandleActivation(activation) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } diff --git a/core/handler/types.go b/core/handler/types.go index 1ae2027fc..d468545a5 100644 --- a/core/handler/types.go +++ b/core/handler/types.go @@ -4,7 +4,7 @@ package handler import ( - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/mqtt" diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 865ac82b3..ff0916729 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -7,8 +7,8 @@ import ( "fmt" "time" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "gopkg.in/redis.v3" ) @@ -59,7 +59,7 @@ func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, er return dev, nil } } - return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } func (s *deviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, error) { @@ -195,11 +195,11 @@ func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Devic res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() if err != nil { if err == redis.Nil { - return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } return nil, err } else if len(res) == 0 { - return nil, core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) + return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } device := &Device{} err = device.FromStringStringMap(res) @@ -290,7 +290,7 @@ func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, de return err } if !exists { - return core.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) + return errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } // Check for old DevAddr diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 32ae1d4c9..8fce89a5c 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -9,8 +9,8 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" @@ -23,7 +23,7 @@ type networkServerManager struct { func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { if !in.Validate() { - return nil, core.NewErrInvalidArgument("Device Identifier", "validation failed") + return nil, errors.NewErrInvalidArgument("Device Identifier", "validation failed") } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) if err != nil { @@ -34,7 +34,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev return nil, err } if !claims.CanEditApp(dev.AppID) { - return nil, core.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) } return dev, nil } @@ -42,7 +42,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { dev, err := n.getDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } lastSeen := time.Unix(0, 0) @@ -67,8 +67,8 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*empty.Empty, error) { _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) - if err != nil && core.GetErrType(err) != core.NotFound { - return nil, core.BuildGRPCError(err) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, errors.BuildGRPCError(err) } if !in.Validate() { @@ -96,7 +96,7 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev err = n.networkServer.devices.Set(updated) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil @@ -105,11 +105,11 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*empty.Empty, error) { _, err := n.getDevice(ctx, in) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } err = n.networkServer.devices.Delete(*in.AppEui, *in.DevEui) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &empty.Empty{}, nil } @@ -130,7 +130,7 @@ func (n *networkServerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.P func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { devAddr, err := n.networkServer.getDevAddr(in.Usage...) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return &pb_lorawan.DevAddrResponse{ DevAddr: &devAddr, diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 33d9e9010..e6540838f 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -17,6 +17,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/brocaar/lorawan" "gopkg.in/redis.v3" @@ -56,10 +57,10 @@ type networkServer struct { func (n *networkServer) UsePrefix(prefix types.DevAddrPrefix, usage []string) error { if prefix.Length < 7 { - return core.NewErrInvalidArgument("Prefix", "invalid length") + return errors.NewErrInvalidArgument("Prefix", "invalid length") } if prefix.DevAddr[0]>>1 != n.netID[2] { - return core.NewErrInvalidArgument("Prefix", "invalid netID") + return errors.NewErrInvalidArgument("Prefix", "invalid netID") } n.prefixes[prefix] = usage return nil @@ -144,7 +145,7 @@ func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) // Get a random prefix that matches the constraints prefixes := n.GetPrefixesFor(constraints...) if len(prefixes) == 0 { - return types.DevAddr{}, core.NewErrNotFound(fmt.Sprintf("DevAddr prefix with constraints %v", constraints)) + return types.DevAddr{}, errors.NewErrNotFound(fmt.Sprintf("DevAddr prefix with constraints %v", constraints)) } // Select a prefix @@ -158,7 +159,7 @@ func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { if activation.AppEui == nil || activation.DevEui == nil { - return nil, core.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") + return nil, errors.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") } dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) if err != nil { @@ -180,12 +181,12 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat } // Build lorawan metadata if not present if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - return nil, core.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") + return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") } // Build response template if not present if pld := activation.GetResponseTemplate(); pld == nil { - return nil, core.NewErrInvalidArgument("Activation", "missing response template") + return nil, errors.NewErrInvalidArgument("Activation", "missing response template") } lorawanMeta := activation.ActivationMetadata.GetLorawan() @@ -232,11 +233,11 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) { meta := activation.GetActivationMetadata() if meta == nil { - return nil, core.NewErrInvalidArgument("Activation", "missing ActivationMetadata") + return nil, errors.NewErrInvalidArgument("Activation", "missing ActivationMetadata") } lorawan := meta.GetLorawan() if lorawan == nil { - return nil, core.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") + return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") } err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) if err != nil { @@ -260,7 +261,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return nil, core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + return nil, errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) @@ -328,7 +329,7 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ } if dev.AppID != message.AppId || dev.DevID != message.DevId { - return nil, core.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") + return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") } // Unmarshal LoRaWAN Payload @@ -339,7 +340,7 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return nil, core.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") + return nil, errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } // Set DevAddr diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 50dbb7711..31401d03a 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -7,7 +7,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/security" "github.com/dgrijalva/jwt-go" "golang.org/x/net/context" @@ -25,20 +25,20 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func (s *networkServerRPC) ValidateContext(ctx context.Context) error { md, ok := metadata.FromContext(ctx) if !ok { - return core.NewErrInternal("Could not get metadata from context") + return errors.NewErrInternal("Could not get metadata from context") } var id, token string if ids, ok := md["id"]; ok && len(ids) == 1 { id = ids[0] } if id == "" { - return core.NewErrInvalidArgument("Metadata", "id missing") + return errors.NewErrInvalidArgument("Metadata", "id missing") } if tokens, ok := md["token"]; ok && len(tokens) == 1 { token = tokens[0] } if token == "" { - return core.NewErrInvalidArgument("Metadata", "token missing") + return errors.NewErrInvalidArgument("Metadata", "token missing") } var claims *jwt.StandardClaims claims, err := security.ValidateJWT(token, []byte(s.networkServer.(*networkServer).Identity.PublicKey)) @@ -46,77 +46,77 @@ func (s *networkServerRPC) ValidateContext(ctx context.Context) error { return err } if claims.Subject != id { - return core.NewErrInvalidArgument("Metadata", "token was issued for a different component id") + return errors.NewErrInvalidArgument("Metadata", "token was issued for a different component id") } return nil } func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesRequest) (*pb.DevicesResponse, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Devices Request") } res, err := s.networkServer.HandleGetDevices(req) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !activation.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } res, err := s.networkServer.HandlePrepareActivation(activation) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !activation.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } res, err := s.networkServer.HandleActivate(activation) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !message.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Uplink") } res, err := s.networkServer.HandleUplink(message) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } if !message.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Downlink") } res, err := s.networkServer.HandleDownlink(message) if err != nil { - return nil, core.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } return res, nil } diff --git a/core/otaa/session_keys.go b/core/otaa/session_keys.go index cf2105bca..b137b6a70 100644 --- a/core/otaa/session_keys.go +++ b/core/otaa/session_keys.go @@ -6,8 +6,8 @@ package otaa import ( "crypto/aes" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" ) // CalculateSessionKeys calculates the AppSKey and NwkSKey @@ -22,7 +22,7 @@ func CalculateSessionKeys(appKey types.AppKey, appNonce [3]byte, netID [3]byte, block, err := aes.NewCipher(appKey[:]) if err != nil || block.BlockSize() != 16 { - err = core.NewErrInternal("Unable to create cipher to generate keys") + err = errors.NewErrInternal("Unable to create cipher to generate keys") return } diff --git a/core/router/activation.go b/core/router/activation.go index 604d9ec4c..7a7fb2ccd 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -4,7 +4,6 @@ package router import ( - "errors" "fmt" "sync" "time" @@ -13,8 +12,8 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -48,7 +47,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De gateway.Utilization.AddRx(uplink) if !gateway.Schedule.IsActive() { - return nil, core.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) + return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) } downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) diff --git a/core/router/downlink.go b/core/router/downlink.go index a3783ee9f..704dcaef9 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -13,9 +13,9 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/toa" "github.com/apex/log" lora "github.com/brocaar/lorawan/band" @@ -41,7 +41,7 @@ func (r *router) SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.Down }() return toGateway, nil } - return nil, core.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) + return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) } func (r *router) UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error { @@ -105,15 +105,15 @@ func getBand(region string) (band *lora.Band, err error) { case "US_902_928": b, err = lora.GetConfig(lora.US_902_928) case "CN_779_787": - err = core.NewErrInternal("China 779-787 MHz band not supported") + err = errors.NewErrInternal("China 779-787 MHz band not supported") case "EU_433": - err = core.NewErrInternal("Europe 433 MHz band not supported") + err = errors.NewErrInternal("Europe 433 MHz band not supported") case "AU_915_928": b, err = lora.GetConfig(lora.AU_915_928) case "CN_470_510": - err = core.NewErrInternal("China 470-510 MHz band not supported") + err = errors.NewErrInternal("China 470-510 MHz band not supported") default: - err = core.NewErrInvalidArgument("Frequency Band", "unknown") + err = errors.NewErrInvalidArgument("Frequency Band", "unknown") } if err != nil { return diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 5db5e0f3b..79d5ab3a4 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -10,7 +10,7 @@ import ( "time" router_pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/TheThingsNetwork/ttn/utils/toa" "github.com/apex/log" @@ -198,7 +198,7 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro return nil } - return core.NewErrNotFound(id) + return errors.NewErrNotFound(id) } func (s *schedule) Stop() { diff --git a/core/router/router.go b/core/router/router.go index 7336c4f6e..bb2a325ba 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -15,6 +15,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" ) // Router component @@ -133,13 +134,13 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok } go func() { - errors := 0 + numErrs := 0 for { association, err := client.Associate(r.Component.GetContext("")) if err != nil { - errors++ + numErrs++ <-time.After(api.Backoff) - if errors > 10 { + if numErrs > 10 { break } continue @@ -162,7 +163,7 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok for { select { case err := <-errChan: - r.Ctx.WithError(core.FromGRPCError(err)).Error("Error in Broker associate") + r.Ctx.WithError(errors.FromGRPCError(err)).Error("Error in Broker associate") break associationLoop case uplink := <-brk.uplink: err := association.Send(uplink) diff --git a/core/router/server.go b/core/router/server.go index 477cb9aa8..c8bcccd16 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -7,8 +7,8 @@ import ( "io" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" "google.golang.org/grpc" @@ -25,12 +25,12 @@ var grpcErrf = grpc.Errorf // To make go vet stop complaining func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, err error) { md, ok := metadata.FromContext(ctx) if !ok { - err = core.NewErrInternal("Could not get metadata from context") + err = errors.NewErrInternal("Could not get metadata from context") return } euiString, ok := md["gateway_eui"] if !ok || len(euiString) < 1 { - err = core.NewErrInvalidArgument("Metadata", "gateway_eui missing") + err = errors.NewErrInvalidArgument("Metadata", "gateway_eui missing") return } gatewayEUI, err = types.ParseGatewayEUI(euiString[0]) @@ -39,12 +39,12 @@ func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, e } token, ok := md["token"] if !ok || len(token) < 1 { - err = core.NewErrInvalidArgument("Metadata", "token missing") + err = errors.NewErrInvalidArgument("Metadata", "token missing") return } if token[0] != "token" { // TODO: Validate Token - err = core.NewErrPermissionDenied("Gateway token not authorized") + err = errors.NewErrPermissionDenied("Gateway token not authorized") return } diff --git a/core/router/uplink.go b/core/router/uplink.go index 48b7959a6..371c86abf 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -9,8 +9,8 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/brocaar/lorawan" ) @@ -37,7 +37,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess if phyPayload.MHDR.MType == lorawan.JoinRequest { joinRequestPayload, ok := phyPayload.MACPayload.(*lorawan.JoinRequestPayload) if !ok { - return core.NewErrInvalidArgument("Join Request", "does not contain a JoinRequest payload") + return errors.NewErrInvalidArgument("Join Request", "does not contain a JoinRequest payload") } devEUI := types.DevEUI(joinRequestPayload.DevEUI) appEUI := types.AppEUI(joinRequestPayload.AppEUI) @@ -74,7 +74,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - return core.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } devAddr := types.DevAddr(macPayload.FHDR.DevAddr) diff --git a/core/types/data_rate.go b/core/types/data_rate.go index 146e6b4c7..3d3490d92 100644 --- a/core/types/data_rate.go +++ b/core/types/data_rate.go @@ -4,7 +4,7 @@ package types import ( - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "fmt" "regexp" "strconv" diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 90c657930..fca9c6b1c 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -5,7 +5,7 @@ package types import ( "encoding/hex" - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "fmt" "regexp" "strconv" diff --git a/core/types/device_type.go b/core/types/device_type.go index 2203a0c41..2ac4ba84d 100644 --- a/core/types/device_type.go +++ b/core/types/device_type.go @@ -3,7 +3,7 @@ package types -import "errors" +import "github.com/TheThingsNetwork/ttn/utils/errors" // DeviceType is the type of a LoRaWAN device. type DeviceType byte diff --git a/core/types/eui.go b/core/types/eui.go index 0bf12c40a..38285c5b8 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -5,7 +5,7 @@ package types import ( "encoding/hex" - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "strings" ) diff --git a/core/types/keys.go b/core/types/keys.go index 142149c27..a34b11ebb 100644 --- a/core/types/keys.go +++ b/core/types/keys.go @@ -5,7 +5,7 @@ package types import ( "encoding/hex" - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "strings" ) diff --git a/ttnctl/cmd/gateway_status.go b/ttnctl/cmd/gateway_status.go index d9a8107ad..ff57f7bd5 100644 --- a/ttnctl/cmd/gateway_status.go +++ b/ttnctl/cmd/gateway_status.go @@ -8,9 +8,9 @@ import ( "time" "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/spf13/cobra" ) @@ -39,7 +39,7 @@ var gatewayStatusCmd = &cobra.Command{ GatewayEui: &eui, }) if err != nil { - ctx.WithError(core.FromGRPCError(err)).Fatal("Could not get status of gateway.") + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get status of gateway.") } ctx.Infof("Received status") diff --git a/ttnctl/util/handler.go b/ttnctl/util/handler.go index abccb12d6..b29a5ea14 100644 --- a/ttnctl/util/handler.go +++ b/ttnctl/util/handler.go @@ -6,7 +6,7 @@ package util import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/spf13/viper" "google.golang.org/grpc" @@ -22,7 +22,7 @@ func GetHandlerManager(ctx log.Interface) (*grpc.ClientConn, *handler.ManagerCli Id: viper.GetString("ttn-handler"), }) if err != nil { - ctx.WithError(core.FromGRPCError(err)).Fatal("Could not find Handler") + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not find Handler") } token, err := GetTokenSource(ctx).Token() if err != nil { diff --git a/ttnctl/util/lorawan.go b/ttnctl/util/lorawan.go index 38ab0e076..e15e42dbd 100644 --- a/ttnctl/util/lorawan.go +++ b/ttnctl/util/lorawan.go @@ -4,7 +4,7 @@ package util import ( - "errors" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/pointer" diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index 1e947e421..557cce419 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -6,7 +6,7 @@ package util import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/spf13/viper" "google.golang.org/grpc" @@ -22,7 +22,7 @@ func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { Id: viper.GetString("ttn-router"), }) if err != nil { - ctx.WithError(core.FromGRPCError(err)).Fatal("Could not get Router from Discovery") + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") } ctx.Info("Connecting with Router...") rtrConn, err := routerAnnouncement.Dial() @@ -43,7 +43,7 @@ func GetRouterManager(ctx log.Interface) (*grpc.ClientConn, router.RouterManager Id: viper.GetString("ttn-router"), }) if err != nil { - ctx.WithError(core.FromGRPCError(err)).Fatal("Could not get Router from Discovery") + ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") } ctx.Info("Connecting with Router...") rtrConn, err := routerAnnouncement.Dial() diff --git a/core/errors.go b/utils/errors/errors.go similarity index 85% rename from core/errors.go rename to utils/errors/errors.go index d1cdb48d0..d118a327e 100644 --- a/core/errors.go +++ b/utils/errors/errors.go @@ -1,4 +1,4 @@ -package core +package errors import ( "fmt" @@ -7,7 +7,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/codes" - "github.com/pkg/errors" + errs "github.com/pkg/errors" ) type ErrType string @@ -25,7 +25,7 @@ const ( // GetErrType returns the type of err func GetErrType(err error) ErrType { - switch errors.Cause(err).(type) { + switch errs.Cause(err).(type) { case *ErrAlreadyExists: return AlreadyExists case *ErrInternal: @@ -45,7 +45,7 @@ var grpcErrf = grpc.Errorf // BuildGRPCError returns the error with a GRPC code func BuildGRPCError(err error) error { code := codes.Unknown - switch errors.Cause(err).(type) { + switch errs.Cause(err).(type) { case *ErrAlreadyExists: code = codes.AlreadyExists case *ErrInternal: @@ -82,7 +82,7 @@ func FromGRPCError(err error) error { case codes.PermissionDenied: return NewErrPermissionDenied(strings.TrimPrefix(desc, "permission denied: ")) case codes.Unknown: // This also includes all non-gRPC errors - return errors.New(err.Error()) + return errs.New(err.Error()) } return NewErrInternal(fmt.Sprintf("[%s] %s", code, desc)) } @@ -162,3 +162,21 @@ type ErrPermissionDenied struct { func (err ErrPermissionDenied) Error() string { return fmt.Sprintf("permission denied: %s", err.reason) } + +// Wrapf returns an error annotating err with the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + return errs.Wrapf(err, format, args...) +} + +// Wrap returns an error annotating err with message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + return errs.Wrap(err, message) +} + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return errs.New(message) +} From 4c890fb64f3320e8aa033d160c3bb42cf99776d6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 15 Sep 2016 13:40:14 +0200 Subject: [PATCH 1721/2266] Don't need to log err type --- core/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/component.go b/core/component.go index f958e4988..f1e348810 100644 --- a/core/component.go +++ b/core/component.go @@ -333,7 +333,7 @@ func (c *Component) ServerOptions() []grpc.ServerOption { logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) if err != nil { err := errors.FromGRPCError(err) - logCtx.WithField("error", err.Error()).WithField("err-type", fmt.Sprintf("%T", err)).WithField("err-err-type", fmt.Sprintf("%T", err.Error())).Warn("Could not handle Request") + logCtx.WithField("error", err.Error()).Warn("Could not handle Request") } else { logCtx.Info("Handled request") } From b33f44795f30eb68ef465b9c855087c088d172b0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 15 Sep 2016 13:47:31 +0200 Subject: [PATCH 1722/2266] Run gofmt Because a find-replace doesn't do that --- core/account/util/http.go | 2 +- core/types/data_rate.go | 2 +- core/types/dev_addr.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/account/util/http.go b/core/account/util/http.go index c7b40c3f3..437ddc74a 100644 --- a/core/account/util/http.go +++ b/core/account/util/http.go @@ -6,12 +6,12 @@ package util import ( "bytes" "encoding/json" - "github.com/TheThingsNetwork/ttn/utils/errors" "fmt" "io" "net/http" "github.com/TheThingsNetwork/ttn/core/account/auth" + "github.com/TheThingsNetwork/ttn/utils/errors" ) var ( diff --git a/core/types/data_rate.go b/core/types/data_rate.go index 3d3490d92..786e497f6 100644 --- a/core/types/data_rate.go +++ b/core/types/data_rate.go @@ -4,8 +4,8 @@ package types import ( - "github.com/TheThingsNetwork/ttn/utils/errors" "fmt" + "github.com/TheThingsNetwork/ttn/utils/errors" "regexp" "strconv" diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index fca9c6b1c..899850bd8 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -5,8 +5,8 @@ package types import ( "encoding/hex" - "github.com/TheThingsNetwork/ttn/utils/errors" "fmt" + "github.com/TheThingsNetwork/ttn/utils/errors" "regexp" "strconv" "strings" From d134828ae429f0ac795dc10be2e49fa37c36de7b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 12 Aug 2016 10:07:18 +0100 Subject: [PATCH 1723/2266] implement gateway api client --- core/account/gateways.go | 94 ++++++++++++++++++++++++++++++++++++++++ core/account/types.go | 30 +++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 core/account/gateways.go diff --git a/core/account/gateways.go b/core/account/gateways.go new file mode 100644 index 000000000..c2ce9bdbb --- /dev/null +++ b/core/account/gateways.go @@ -0,0 +1,94 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package account + +import ( + "errors" + "fmt" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// ListApplications list all applications +func (a *Account) ListGateways() (gateways []Gateway, err error) { + err = a.get("/gateways", &gateways) + return gateways, err +} + +// FindGateway returns the information about a specific gateay +func (a *Account) FindGateway(gatewayID string) (gateway Gateway, err error) { + err = a.get(fmt.Sprintf("/gateways/%s", gatewayID), &gateway) + return gateway, err +} + +// NewGateway is used as a paramater to CreateGateway to allow for optional +// arguments +type NewGateway struct { + // ID is the ID of the new gateway (required) + ID string `json:"id"` + + // Country is the country code of the new gateway (required) + Country string `json:"country"` + + // EUI is the EUI of the new gateway + EUI string `json:"eui,omitemtpy"` + + // Location is the location of the new gateway + Location *Location `json:"location,omitempty"` +} + +// CreateGateway registers a new gateway on the account server +func (a *Account) CreateGateway(opts *NewGateway) (gateway Gateway, err error) { + if opts.ID == "" { + return gateway, errors.New("Cannot create gateway: no ID given") + } + + if opts.Country == "" { + return gateway, errors.New("Cannot create gateway: no Country given") + } + + err = a.post("/applications", &opts, &gateway) + return gateway, err +} + +// DeleteGateway removes a gateway from the account server +func (a *Account) DeleteGateway(gatewayID string) error { + return a.del(fmt.Sprintf("/gateways/%s", gatewayID)) +} + +// Grant grants rights to a collaborator of the gateway +func (a *Account) GrantGatewayRights(gatewayID string, username string, rights []types.Right) error { + req := grantReq{ + Rights: rights, + } + return a.put(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username), req, nil) +} + +// Retract removes rights from a collaborator of the gateway +func (a *Account) RetractGatewayRights(gatewayID string, username string) error { + return a.del(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username)) +} + +type editGatewayReq struct { + Owner string `json:"owner,omitempty"` + PublicRights []types.Right `json:"public_rights,omitempty"` +} + +// TransferOwnership transfers the owenership of the gateway to another user +func (a *Account) TransferOwnership(gatewayID, username string) error { + req := &editGatewayReq{ + Owner: username, + } + + return a.patch(fmt.Sprintf("/gateways/%s", gatewayID), req, nil) +} + +// SetPublicRights changes the publicily visible rights of the gateway +func (a *Account) SetPublicRights(gatewayID string, rights []types.Right) error { + req := &editGatewayReq{ + PublicRights: rights, + } + + return a.patch(fmt.Sprintf("/gateways/%s", gatewayID), req, nil) +} diff --git a/core/account/types.go b/core/account/types.go index 66d21cc4b..086446b1d 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -59,3 +59,33 @@ type Component struct { func (n *Name) String() string { return n.First + " " + n.Last } + +// Gateway represents a gateway on the account server +type Gateway struct { + // ID is the id of the gateway + ID string `json:"id"` + + // EUI is the eui of the gateway + EUI []types.GatewayEUI `json:"eui,omitempty"` + + // Location is the location of the gateway + Location *Location `json:"location,omitempty"` + + // Country is the country code where the gateway is located + Country string `json:"country"` + + // Activated denotes wether or not the gateway has been activated yet + Activated bool `json:"activated"` + + // Owner is the user that owns the gateway + Owner string `json:"owner,omitempty"` + + // PublicRights are the rights that are publicly available for the gateway + PublicRights []types.Right `json:"public_rights"` +} + +// Location represents a geo location +type Location struct { + Lng float64 `json:"lng"` + Lat float64 `json:"lat"` +} From 01c6087e3f0f657cd9e4e91c6209dd8ddac77daa Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 7 Sep 2016 17:36:17 +0200 Subject: [PATCH 1724/2266] Update gateway type for new gateway enpoint --- core/account/types.go | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/core/account/types.go b/core/account/types.go index 086446b1d..cf5282fcd 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -62,29 +62,18 @@ func (n *Name) String() string { // Gateway represents a gateway on the account server type Gateway struct { - // ID is the id of the gateway - ID string `json:"id"` - - // EUI is the eui of the gateway - EUI []types.GatewayEUI `json:"eui,omitempty"` - - // Location is the location of the gateway - Location *Location `json:"location,omitempty"` - - // Country is the country code where the gateway is located - Country string `json:"country"` - - // Activated denotes wether or not the gateway has been activated yet - Activated bool `json:"activated"` - - // Owner is the user that owns the gateway - Owner string `json:"owner,omitempty"` - - // PublicRights are the rights that are publicly available for the gateway - PublicRights []types.Right `json:"public_rights"` + ID string `json:"id" valid:"required"` + EUI types.GatewayEUI `json:"eui" valid:"required"` + Activated bool `json:"activated"` + FrequencyPlan string `json:"frequency_plan"` + FrequencyPlanURL string `json:"frequency_plan_url"` + PublicLocation bool `json:"location_public"` + StatusPublic bool `json:"status_public"` + Location *Location `json:"location"` + Collaborators []Collaborator `json:"collaborator"` + Key string `json:"key"` } -// Location represents a geo location type Location struct { Lng float64 `json:"lng"` Lat float64 `json:"lat"` From 0a1fe141a91e6c02fc47d9556c0a65545f9be411 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 14 Sep 2016 14:47:08 +0200 Subject: [PATCH 1725/2266] Update NewGateway struct --- core/account/gateways.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/account/gateways.go b/core/account/gateways.go index c2ce9bdbb..1e0badd8d 100644 --- a/core/account/gateways.go +++ b/core/account/gateways.go @@ -29,7 +29,7 @@ type NewGateway struct { ID string `json:"id"` // Country is the country code of the new gateway (required) - Country string `json:"country"` + FrequencyPlan string `json:"frequency_plan"` // EUI is the EUI of the new gateway EUI string `json:"eui,omitemtpy"` @@ -44,11 +44,11 @@ func (a *Account) CreateGateway(opts *NewGateway) (gateway Gateway, err error) { return gateway, errors.New("Cannot create gateway: no ID given") } - if opts.Country == "" { - return gateway, errors.New("Cannot create gateway: no Country given") + if opts.FrequencyPlan == "" { + return gateway, errors.New("Cannot create gateway: no FrequencyPlan given") } - err = a.post("/applications", &opts, &gateway) + err = a.post("/gateways", &opts, &gateway) return gateway, err } From 9d8c5dc44d6e2800f6acfd4337b31add439955f4 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 14 Sep 2016 14:47:29 +0200 Subject: [PATCH 1726/2266] Update deps --- vendor/github.com/brocaar/lorawan | 2 +- vendor/github.com/dgrijalva/jwt-go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan index 8e810fc7a..bd4dcf15f 160000 --- a/vendor/github.com/brocaar/lorawan +++ b/vendor/github.com/brocaar/lorawan @@ -1 +1 @@ -Subproject commit 8e810fc7af6148d4514c9d873e5f921e9178a9c1 +Subproject commit bd4dcf15f7cde1e8c4639b1aa7143aed268aa0af diff --git a/vendor/github.com/dgrijalva/jwt-go b/vendor/github.com/dgrijalva/jwt-go index 24c63f565..d2709f9f1 160000 --- a/vendor/github.com/dgrijalva/jwt-go +++ b/vendor/github.com/dgrijalva/jwt-go @@ -1 +1 @@ -Subproject commit 24c63f56522a87ec5339cc3567883f1039378fdb +Subproject commit d2709f9f1f31ebcda9651b03077758c1f3a0018c From c500ae5ea9b14e4184c36bfb6a9c01f6b1f87e4a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 14 Sep 2016 15:13:03 +0200 Subject: [PATCH 1727/2266] Add Gateways field to TTNClaims --- core/component.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/component.go b/core/component.go index f1e348810..bfa35f090 100644 --- a/core/component.go +++ b/core/component.go @@ -191,10 +191,11 @@ func (c *Component) UpdateTokenKey() error { // TTNClaims contains the claims that are set by the TTN Token Issuer type TTNClaims struct { jwt.StandardClaims - Type string `json:"type"` - Client string `json:"client"` - Scopes []string `json:"scope"` - Apps map[string][]string `json:"apps,omitempty"` + Type string `json:"type"` + Client string `json:"client"` + Scopes []string `json:"scope"` + Apps map[string][]string `json:"apps,omitempty"` + Gateways map[string][]string `json:"gateways,omitempty"` } // CanEditApp indicates wheter someone with the claims can manage the given app From 7d9ab184e57b15376f86053bfb01b456ed181739 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 14 Sep 2016 15:26:31 +0200 Subject: [PATCH 1728/2266] Improve gateway edit api --- core/account/gateways.go | 42 ++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/core/account/gateways.go b/core/account/gateways.go index 1e0badd8d..75b7ae0eb 100644 --- a/core/account/gateways.go +++ b/core/account/gateways.go @@ -70,25 +70,43 @@ func (a *Account) RetractGatewayRights(gatewayID string, username string) error return a.del(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username)) } -type editGatewayReq struct { - Owner string `json:"owner,omitempty"` - PublicRights []types.Right `json:"public_rights,omitempty"` +type GatewayEdits struct { + Owner string `json:"owner,omitempty"` + PublicRights []types.Right `json:"public_rights,omitempty"` + FrequencyPlan string `json:"frequency_plan,omitempty"` + Location *Location `json:"location,omitempty"` +} + +// EditGateway edits the fields of a gateway +func (a *Account) EditGateway(gatewayID string, edits GatewayEdits) (gateway Gateway, err error) { + err = a.patch(fmt.Sprintf("/gateways/%s", gatewayID), edits, &gateway) + return gateway, err } // TransferOwnership transfers the owenership of the gateway to another user -func (a *Account) TransferOwnership(gatewayID, username string) error { - req := &editGatewayReq{ +func (a *Account) TransferOwnership(gatewayID, username string) (Gateway, error) { + return a.EditGateway(gatewayID, GatewayEdits{ Owner: username, - } - - return a.patch(fmt.Sprintf("/gateways/%s", gatewayID), req, nil) + }) } // SetPublicRights changes the publicily visible rights of the gateway -func (a *Account) SetPublicRights(gatewayID string, rights []types.Right) error { - req := &editGatewayReq{ +func (a *Account) SetPublicRights(gatewayID string, rights []types.Right) (Gateway, error) { + return a.EditGateway(gatewayID, GatewayEdits{ PublicRights: rights, - } + }) +} + +// ChangeFrequencyPlan changes the requency plan of a gateway +func (a *Account) ChangeFrequencyPlan(gatewayID, plan string) (Gateway, error) { + return a.EditGateway(gatewayID, GatewayEdits{ + FrequencyPlan: plan, + }) +} - return a.patch(fmt.Sprintf("/gateways/%s", gatewayID), req, nil) +// ChangeLocation changes teh location of the gateway +func (a *Account) ChangeLocation(gatewayID string, location Location) (Gateway, error) { + return a.EditGateway(gatewayID, GatewayEdits{ + Location: &location, + }) } From 0411ece06414d515a724d0d923e037c58591d86a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 16 Sep 2016 11:39:27 +0200 Subject: [PATCH 1729/2266] Update submodules --- vendor/github.com/brocaar/lorawan | 2 +- vendor/github.com/dgrijalva/jwt-go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan index bd4dcf15f..8e810fc7a 160000 --- a/vendor/github.com/brocaar/lorawan +++ b/vendor/github.com/brocaar/lorawan @@ -1 +1 @@ -Subproject commit bd4dcf15f7cde1e8c4639b1aa7143aed268aa0af +Subproject commit 8e810fc7af6148d4514c9d873e5f921e9178a9c1 diff --git a/vendor/github.com/dgrijalva/jwt-go b/vendor/github.com/dgrijalva/jwt-go index d2709f9f1..24c63f565 160000 --- a/vendor/github.com/dgrijalva/jwt-go +++ b/vendor/github.com/dgrijalva/jwt-go @@ -1 +1 @@ -Subproject commit d2709f9f1f31ebcda9651b03077758c1f3a0018c +Subproject commit 24c63f56522a87ec5339cc3567883f1039378fdb From 1cefcef8ba109b3da007a6fd39180dc7cf176dd4 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 16 Sep 2016 14:17:31 +0200 Subject: [PATCH 1730/2266] Be more consistent with Gateway field names --- core/account/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/account/types.go b/core/account/types.go index cf5282fcd..cdfb56646 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -67,7 +67,7 @@ type Gateway struct { Activated bool `json:"activated"` FrequencyPlan string `json:"frequency_plan"` FrequencyPlanURL string `json:"frequency_plan_url"` - PublicLocation bool `json:"location_public"` + LocationPublic bool `json:"location_public"` StatusPublic bool `json:"status_public"` Location *Location `json:"location"` Collaborators []Collaborator `json:"collaborator"` From f194dcd7cb856e234a02c383ea4ca1ee3440288d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 16 Sep 2016 14:20:41 +0200 Subject: [PATCH 1731/2266] Do not shorten field names in Location struct --- core/account/types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/account/types.go b/core/account/types.go index cdfb56646..a54f75cd7 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -75,6 +75,6 @@ type Gateway struct { } type Location struct { - Lng float64 `json:"lng"` - Lat float64 `json:"lat"` + Longitude float64 `json:"lng"` + Latitude float64 `json:"lat"` } From 941ccb0d1b2a6a2d42b09d30a7e39785559fa1cc Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 16 Sep 2016 14:29:49 +0200 Subject: [PATCH 1732/2266] Build simplify ChangeLocation api --- core/account/gateways.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/account/gateways.go b/core/account/gateways.go index 75b7ae0eb..061ff2ec2 100644 --- a/core/account/gateways.go +++ b/core/account/gateways.go @@ -104,9 +104,12 @@ func (a *Account) ChangeFrequencyPlan(gatewayID, plan string) (Gateway, error) { }) } -// ChangeLocation changes teh location of the gateway -func (a *Account) ChangeLocation(gatewayID string, location Location) (Gateway, error) { +// ChangeLocation changes the location of the gateway +func (a *Account) ChangeLocation(gatewayID string, latitude, longitude float64) (Gateway, error) { return a.EditGateway(gatewayID, GatewayEdits{ - Location: &location, + Location: &Location{ + Longitude: longitude, + Latitude: latitude, + }, }) } From ef26a48138c771f91c9d9bab673895790b5b8c5a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 19 Sep 2016 08:35:30 +0200 Subject: [PATCH 1733/2266] Add some func comments to PR#246 --- core/account/account.go | 1 + core/account/gateways.go | 7 ++++--- core/account/types.go | 20 ++++++++++---------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/core/account/account.go b/core/account/account.go index 3b4c6e2ec..538f3b8e3 100644 --- a/core/account/account.go +++ b/core/account/account.go @@ -32,6 +32,7 @@ func NewWithKey(server, accessKey string) *Account { } } +// NewWithPublic creates an account client that does not use authentication func NewWithPublic(server string) *Account { return &Account{ server: server, diff --git a/core/account/gateways.go b/core/account/gateways.go index 061ff2ec2..6aef32b17 100644 --- a/core/account/gateways.go +++ b/core/account/gateways.go @@ -10,7 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -// ListApplications list all applications +// ListGateways list all gateways func (a *Account) ListGateways() (gateways []Gateway, err error) { err = a.get("/gateways", &gateways) return gateways, err @@ -57,7 +57,7 @@ func (a *Account) DeleteGateway(gatewayID string) error { return a.del(fmt.Sprintf("/gateways/%s", gatewayID)) } -// Grant grants rights to a collaborator of the gateway +// GrantGatewayRights grants rights to a collaborator of the gateway func (a *Account) GrantGatewayRights(gatewayID string, username string, rights []types.Right) error { req := grantReq{ Rights: rights, @@ -65,11 +65,12 @@ func (a *Account) GrantGatewayRights(gatewayID string, username string, rights [ return a.put(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username), req, nil) } -// Retract removes rights from a collaborator of the gateway +// RetractGatewayRights removes rights from a collaborator of the gateway func (a *Account) RetractGatewayRights(gatewayID string, username string) error { return a.del(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username)) } +// GatewayEdits contains editable fields of gateways type GatewayEdits struct { Owner string `json:"owner,omitempty"` PublicRights []types.Right `json:"public_rights,omitempty"` diff --git a/core/account/types.go b/core/account/types.go index a54f75cd7..0baeef3f5 100644 --- a/core/account/types.go +++ b/core/account/types.go @@ -62,18 +62,18 @@ func (n *Name) String() string { // Gateway represents a gateway on the account server type Gateway struct { - ID string `json:"id" valid:"required"` - EUI types.GatewayEUI `json:"eui" valid:"required"` - Activated bool `json:"activated"` - FrequencyPlan string `json:"frequency_plan"` - FrequencyPlanURL string `json:"frequency_plan_url"` - LocationPublic bool `json:"location_public"` - StatusPublic bool `json:"status_public"` - Location *Location `json:"location"` - Collaborators []Collaborator `json:"collaborator"` - Key string `json:"key"` + ID string `json:"id" valid:"required"` + Activated bool `json:"activated"` + FrequencyPlan string `json:"frequency_plan"` + FrequencyPlanURL string `json:"frequency_plan_url"` + LocationPublic bool `json:"location_public"` + StatusPublic bool `json:"status_public"` + Location *Location `json:"location"` + Collaborators []Collaborator `json:"collaborator"` + Key string `json:"key"` } +// Location is the GPS location of a gateway type Location struct { Longitude float64 `json:"lng"` Latitude float64 `json:"lat"` From 9ea92a92cb7625d3dbe7fb03014d24ffef0bab5a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 19 Sep 2016 09:19:38 +0200 Subject: [PATCH 1734/2266] Cleanup - Remove DeviceType type --- core/types/device_type.go | 109 --------------------------------- core/types/device_type_test.go | 85 ------------------------- 2 files changed, 194 deletions(-) delete mode 100644 core/types/device_type.go delete mode 100644 core/types/device_type_test.go diff --git a/core/types/device_type.go b/core/types/device_type.go deleted file mode 100644 index 2ac4ba84d..000000000 --- a/core/types/device_type.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package types - -import "github.com/TheThingsNetwork/ttn/utils/errors" - -// DeviceType is the type of a LoRaWAN device. -type DeviceType byte - -const ( - // ABP is a LoRaWAN device that is activated by personalization. - ABP DeviceType = iota - // OTAA is an over-the-air activated LoRaWAN device. - OTAA -) - -// ParseDeviceType parses a string to a DeviceType. -func ParseDeviceType(input string) (devType DeviceType, err error) { - switch input { - case "ABP": - devType = ABP - case "OTAA": - devType = OTAA - default: - err = errors.New("ttn/core: Invalid DeviceType") - } - return -} - -// String implements the Stringer interface. -func (devType DeviceType) String() string { - switch devType { - case ABP: - return "ABP" - case OTAA: - return "OTAA" - } - return "" -} - -// GoString implements the GoStringer interface. -func (devType DeviceType) GoString() string { - return devType.String() -} - -// MarshalText implements the TextMarshaler interface. -func (devType DeviceType) MarshalText() ([]byte, error) { - str := devType.String() - if str == "" { - return nil, errors.New("ttn/core: Invalid DeviceType") - } - return []byte(devType.String()), nil -} - -// UnmarshalText implements the TextUnmarshaler interface. -func (devType *DeviceType) UnmarshalText(data []byte) error { - parsed, err := ParseDeviceType(string(data)) - if err != nil { - return err - } - *devType = DeviceType(parsed) - return nil -} - -// MarshalBinary implements the BinaryMarshaler interface. -func (devType DeviceType) MarshalBinary() ([]byte, error) { - switch devType { - case ABP, OTAA: - return []byte{byte(devType)}, nil - default: - return nil, errors.New("ttn/core: Invalid DeviceType") - } -} - -// UnmarshalBinary implements the BinaryUnmarshaler interface. -func (devType *DeviceType) UnmarshalBinary(data []byte) error { - if len(data) != 1 { - return errors.New("ttn/core: Invalid length for DeviceType") - } - switch data[0] { - case byte(ABP), byte(OTAA): - *devType = DeviceType(data[0]) - default: - return errors.New("ttn/core: Invalid DeviceType") - } - return nil -} - -// MarshalTo is used by Protobuf -func (devType *DeviceType) MarshalTo(b []byte) (int, error) { - copy(b, []byte(devType.String())) - return len(devType.String()), nil -} - -// Size is used by Protobuf -func (devType *DeviceType) Size() int { - return len(devType.String()) -} - -// Marshal implements the Marshaler interface. -func (devType DeviceType) Marshal() ([]byte, error) { - return devType.MarshalBinary() -} - -// Unmarshal implements the Unmarshaler interface. -func (devType *DeviceType) Unmarshal(data []byte) error { - return devType.UnmarshalBinary(data) -} diff --git a/core/types/device_type_test.go b/core/types/device_type_test.go deleted file mode 100644 index 14dde2100..000000000 --- a/core/types/device_type_test.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package types - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestDeviceType(t *testing.T) { - a := New(t) - - // Setup - abp := ABP - otaa := OTAA - abpStr := "ABP" - otaaStr := "OTAA" - abpBin := []byte{0x00} - otaaBin := []byte{0x01} - - // String - a.So(abp.String(), ShouldEqual, abpStr) - a.So(otaa.String(), ShouldEqual, otaaStr) - - // MarshalText - mtOut, err := abp.MarshalText() - a.So(err, ShouldBeNil) - a.So(mtOut, ShouldResemble, []byte(abpStr)) - mtOut, err = otaa.MarshalText() - a.So(err, ShouldBeNil) - a.So(mtOut, ShouldResemble, []byte(otaaStr)) - - // MarshalBinary - mbOut, err := abp.MarshalBinary() - a.So(err, ShouldBeNil) - a.So(mbOut, ShouldResemble, abpBin) - mbOut, err = otaa.MarshalBinary() - a.So(err, ShouldBeNil) - a.So(mbOut, ShouldResemble, otaaBin) - - // Marshal - mOut, err := abp.Marshal() - a.So(err, ShouldBeNil) - a.So(mOut, ShouldResemble, abpBin) - mOut, err = otaa.Marshal() - a.So(err, ShouldBeNil) - a.So(mOut, ShouldResemble, otaaBin) - - // Parse - pOut, err := ParseDeviceType(abpStr) - a.So(err, ShouldBeNil) - a.So(pOut, ShouldEqual, abp) - pOut, err = ParseDeviceType(otaaStr) - a.So(err, ShouldBeNil) - a.So(pOut, ShouldEqual, otaa) - - // UnmarshalText - utOut := ABP - err = utOut.UnmarshalText([]byte(otaaStr)) - a.So(err, ShouldBeNil) - a.So(utOut, ShouldEqual, otaa) - err = utOut.UnmarshalText([]byte(abpStr)) - a.So(err, ShouldBeNil) - a.So(utOut, ShouldEqual, abp) - - // UnmarshalBinary - ubOut := ABP - err = ubOut.UnmarshalBinary(otaaBin) - a.So(err, ShouldBeNil) - a.So(ubOut, ShouldEqual, otaa) - err = ubOut.UnmarshalBinary(abpBin) - a.So(err, ShouldBeNil) - a.So(ubOut, ShouldEqual, abp) - - // Unmarshal - uOut := ABP - err = uOut.Unmarshal(otaaBin) - a.So(err, ShouldBeNil) - a.So(uOut, ShouldEqual, otaa) - err = uOut.Unmarshal(abpBin) - a.So(err, ShouldBeNil) - a.So(uOut, ShouldEqual, abp) -} From e3592bdd519ae2bdbb59c6191c36fe103744abe8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 19 Sep 2016 09:24:44 +0200 Subject: [PATCH 1735/2266] Migrate from GatewayEUIs to GatewayIDs --- api/broker/broker.pb.go | 320 +++++++++++++------------- api/broker/broker.proto | 2 +- api/broker/validation.go | 2 +- api/gateway/gateway.pb.go | 137 +++++------ api/gateway/gateway.proto | 2 +- api/gateway/validation.go | 2 +- api/router/router.pb.go | 162 +++++++------ api/router/router.proto | 2 +- core/broker/activation.go | 6 +- core/broker/activation_test.go | 6 +- core/broker/uplink.go | 2 +- core/broker/uplink_test.go | 14 +- core/handler/convert_metadata.go | 2 +- core/handler/convert_metadata_test.go | 7 +- core/handler/types.go | 1 + core/handler/uplink.go | 1 + core/router/activation.go | 13 +- core/router/activation_test.go | 12 +- core/router/downlink.go | 21 +- core/router/downlink_test.go | 23 +- core/router/gateway/gateway.go | 9 +- core/router/gateway/gateway_test.go | 3 +- core/router/gateway/status.go | 5 +- core/router/gateway/status_test.go | 9 +- core/router/gateway_status.go | 7 +- core/router/gateway_status_test.go | 9 +- core/router/manager_server.go | 6 +- core/router/router.go | 25 +- core/router/server.go | 37 ++- core/router/uplink.go | 8 +- core/router/uplink_test.go | 20 +- core/router/util_test.go | 3 +- core/types/eui.go | 89 +------ core/types/eui_test.go | 58 ----- mqtt/README.md | 2 +- mqtt/types.go | 14 +- ttnctl/cmd/gateway_status.go | 16 +- ttnctl/cmd/uplink.go | 4 +- ttnctl/util/uplink_metadata.go | 15 +- 39 files changed, 445 insertions(+), 631 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 9c03a5965..972a31702 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -55,12 +55,12 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DownlinkOption struct { - Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` - GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,2,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` - Score uint32 `protobuf:"varint,3,opt,name=score,proto3" json:"score,omitempty"` - Deadline int64 `protobuf:"varint,4,opt,name=deadline,proto3" json:"deadline,omitempty"` - ProtocolConfig *protocol.TxConfiguration `protobuf:"bytes,5,opt,name=protocol_config,json=protocolConfig" json:"protocol_config,omitempty"` - GatewayConfig *gateway.TxConfiguration `protobuf:"bytes,6,opt,name=gateway_config,json=gatewayConfig" json:"gateway_config,omitempty"` + Identifier string `protobuf:"bytes,1,opt,name=identifier,proto3" json:"identifier,omitempty"` + GatewayId string `protobuf:"bytes,2,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + Score uint32 `protobuf:"varint,3,opt,name=score,proto3" json:"score,omitempty"` + Deadline int64 `protobuf:"varint,4,opt,name=deadline,proto3" json:"deadline,omitempty"` + ProtocolConfig *protocol.TxConfiguration `protobuf:"bytes,5,opt,name=protocol_config,json=protocolConfig" json:"protocol_config,omitempty"` + GatewayConfig *gateway.TxConfiguration `protobuf:"bytes,6,opt,name=gateway_config,json=gatewayConfig" json:"gateway_config,omitempty"` } func (m *DownlinkOption) Reset() { *m = DownlinkOption{} } @@ -830,15 +830,11 @@ func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Identifier))) i += copy(data[i:], m.Identifier) } - if m.GatewayEui != nil { + if len(m.GatewayId) > 0 { data[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.GatewayEui.Size())) - n1, err := m.GatewayEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 + i = encodeVarintBroker(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) } if m.Score != 0 { data[i] = 0x18 @@ -854,21 +850,21 @@ func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { data[i] = 0x2a i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolConfig.Size())) - n2, err := m.ProtocolConfig.MarshalTo(data[i:]) + n1, err := m.ProtocolConfig.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n1 } if m.GatewayConfig != nil { data[i] = 0x32 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayConfig.Size())) - n3, err := m.GatewayConfig.MarshalTo(data[i:]) + n2, err := m.GatewayConfig.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n2 } return i, nil } @@ -898,21 +894,21 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n4, err := m.DevEui.MarshalTo(data[i:]) + n3, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n3 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n5, err := m.AppEui.MarshalTo(data[i:]) + n4, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n4 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -932,11 +928,11 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n6, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n5, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n5 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -944,11 +940,11 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n7, err := m.GatewayMetadata.MarshalTo(data[i:]) + n6, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n6 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -992,21 +988,21 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n8, err := m.DevEui.MarshalTo(data[i:]) + n7, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n7 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n9, err := m.AppEui.MarshalTo(data[i:]) + n8, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n8 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1026,11 +1022,11 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n10, err := m.DownlinkOption.MarshalTo(data[i:]) + n9, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n9 } return i, nil } @@ -1060,11 +1056,11 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n11, err := m.DownlinkOption.MarshalTo(data[i:]) + n10, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n10 } return i, nil } @@ -1094,21 +1090,21 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n12, err := m.DevEui.MarshalTo(data[i:]) + n11, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n11 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n13, err := m.AppEui.MarshalTo(data[i:]) + n12, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n12 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1128,11 +1124,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n14, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n13, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n13 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1161,11 +1157,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n15, err := m.ResponseTemplate.MarshalTo(data[i:]) + n14, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n14 } return i, nil } @@ -1195,21 +1191,21 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n16, err := m.DevEui.MarshalTo(data[i:]) + n15, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n15 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n17, err := m.AppEui.MarshalTo(data[i:]) + n16, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n16 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1217,11 +1213,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n18, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n17, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n17 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1229,11 +1225,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n19, err := m.GatewayMetadata.MarshalTo(data[i:]) + n18, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n18 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1241,11 +1237,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n20, err := m.ActivationMetadata.MarshalTo(data[i:]) + n19, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n20 + i += n19 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -1289,21 +1285,21 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n21, err := m.DevEui.MarshalTo(data[i:]) + n20, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n21 + i += n20 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n22, err := m.AppEui.MarshalTo(data[i:]) + n21, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n22 + i += n21 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1323,11 +1319,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n23, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n22, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n23 + i += n22 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1349,11 +1345,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n24, err := m.ActivationMetadata.MarshalTo(data[i:]) + n23, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n24 + i += n23 } if m.ServerTime != 0 { data[i] = 0xc0 @@ -1368,11 +1364,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n25, err := m.ResponseTemplate.MarshalTo(data[i:]) + n24, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n25 + i += n24 } return i, nil } @@ -1402,21 +1398,21 @@ func (m *ActivationChallengeRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n26, err := m.DevEui.MarshalTo(data[i:]) + n25, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n25 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n27, err := m.AppEui.MarshalTo(data[i:]) + n26, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n27 + i += n26 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1512,31 +1508,31 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n28, err := m.Uplink.MarshalTo(data[i:]) + n27, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n28 + i += n27 } if m.UplinkUnique != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n29, err := m.UplinkUnique.MarshalTo(data[i:]) + n28, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n29 + i += n28 } if m.Downlink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n30, err := m.Downlink.MarshalTo(data[i:]) + n29, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n30 + i += n29 } if m.Activations != nil { data[i] = 0xaa @@ -1544,11 +1540,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n31, err := m.Activations.MarshalTo(data[i:]) + n30, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n31 + i += n30 } if m.ActivationsUnique != nil { data[i] = 0xb2 @@ -1556,11 +1552,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n32, err := m.ActivationsUnique.MarshalTo(data[i:]) + n31, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n32 + i += n31 } if m.ActivationsAccepted != nil { data[i] = 0xba @@ -1568,11 +1564,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n33, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n32, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n33 + i += n32 } if m.Deduplication != nil { data[i] = 0xfa @@ -1580,11 +1576,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n34, err := m.Deduplication.MarshalTo(data[i:]) + n33, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n34 + i += n33 } if m.ConnectedRouters != 0 { data[i] = 0xc8 @@ -1667,8 +1663,8 @@ func (m *DownlinkOption) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } - if m.GatewayEui != nil { - l = m.GatewayEui.Size() + l = len(m.GatewayId) + if l > 0 { n += 1 + l + sovBroker(uint64(l)) } if m.Score != 0 { @@ -2073,9 +2069,9 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) } - var byteLen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowBroker @@ -2085,23 +2081,20 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthBroker } - postIndex := iNdEx + byteLen + postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayEui = &v - if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } + m.GatewayId = string(data[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 0 { @@ -4745,77 +4738,76 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1151 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x58, 0x4b, 0x6f, 0xdb, 0x46, - 0x10, 0x2e, 0xa5, 0x44, 0x8e, 0x47, 0xd6, 0xc3, 0x1b, 0x3f, 0x18, 0xa5, 0x95, 0x54, 0x15, 0x08, - 0xd4, 0x47, 0xa4, 0x54, 0x45, 0x1a, 0x18, 0x7d, 0x18, 0x72, 0x6c, 0xa4, 0x2e, 0x20, 0x37, 0x60, - 0xec, 0x1e, 0x8a, 0x02, 0xc2, 0x8a, 0x1c, 0x53, 0x0b, 0x53, 0x24, 0x43, 0x2e, 0xe5, 0xf8, 0xde, - 0x1f, 0xd1, 0x6b, 0xfb, 0x33, 0x0a, 0xf4, 0x50, 0xf4, 0xd2, 0x5b, 0x7b, 0xce, 0x21, 0x28, 0xdc, - 0x4b, 0x2f, 0xfd, 0x0f, 0x05, 0x97, 0x4b, 0x8a, 0xb2, 0xad, 0xd8, 0x29, 0x7c, 0x68, 0x9a, 0x9e, - 0xc4, 0x9d, 0xf9, 0xe6, 0xe3, 0xf2, 0x9b, 0x07, 0xb9, 0x82, 0x7b, 0x26, 0xe3, 0xc3, 0x60, 0xd0, - 0xd2, 0x9d, 0x51, 0x7b, 0x77, 0x88, 0xbb, 0x43, 0x66, 0x9b, 0xfe, 0x0e, 0xf2, 0x43, 0xc7, 0x3b, - 0x68, 0x73, 0x6e, 0xb7, 0xa9, 0xcb, 0xda, 0x03, 0xcf, 0x39, 0x40, 0x4f, 0xfe, 0xb4, 0x5c, 0xcf, - 0xe1, 0x0e, 0xc9, 0x45, 0xab, 0xca, 0x4d, 0xd3, 0x71, 0x4c, 0x0b, 0xdb, 0xc2, 0x3a, 0x08, 0xf6, - 0xdb, 0x38, 0x72, 0xf9, 0x51, 0x04, 0xaa, 0xdc, 0x4e, 0xb1, 0x9b, 0x8e, 0xe9, 0x4c, 0x50, 0xe1, - 0x4a, 0x2c, 0xc4, 0xd5, 0x19, 0xf0, 0x99, 0x9b, 0xa1, 0x2e, 0x93, 0xf0, 0x8f, 0x2e, 0x02, 0x17, - 0x50, 0xdd, 0xb1, 0x92, 0x0b, 0x19, 0xbc, 0x76, 0x91, 0x60, 0x93, 0x72, 0x3c, 0xa4, 0x47, 0xf1, - 0x6f, 0x14, 0xda, 0xf8, 0x39, 0x03, 0xc5, 0x4d, 0xe7, 0xd0, 0xb6, 0x98, 0x7d, 0xf0, 0x85, 0xcb, - 0x99, 0x63, 0x93, 0x2a, 0x00, 0x33, 0xd0, 0xe6, 0x6c, 0x9f, 0xa1, 0xa7, 0x2a, 0x75, 0xa5, 0x39, - 0xaf, 0xa5, 0x2c, 0xe4, 0x2b, 0xc8, 0x4b, 0x8e, 0x3e, 0x06, 0x4c, 0xcd, 0xd4, 0x95, 0xe6, 0xc2, - 0xc6, 0xda, 0xd3, 0x67, 0xb5, 0xbb, 0xe7, 0x6d, 0x43, 0x77, 0x3c, 0x6c, 0xf3, 0x23, 0x17, 0xfd, - 0xd6, 0x83, 0x88, 0x61, 0x6b, 0x6f, 0x5b, 0x03, 0xc9, 0xb6, 0x15, 0x30, 0xb2, 0x04, 0x57, 0xfd, - 0x10, 0xa5, 0x66, 0xeb, 0x4a, 0xb3, 0xa0, 0x45, 0x0b, 0x52, 0x81, 0x6b, 0x06, 0x52, 0xc3, 0x62, - 0x36, 0xaa, 0x57, 0xea, 0x4a, 0x33, 0xab, 0x25, 0x6b, 0xb2, 0x01, 0xa5, 0x58, 0x8d, 0xbe, 0xee, - 0xd8, 0xfb, 0xcc, 0x54, 0xaf, 0xd6, 0x95, 0x66, 0xbe, 0x73, 0xa3, 0x95, 0xa8, 0xb4, 0xfb, 0xe4, - 0xbe, 0xf0, 0x04, 0x1e, 0x0d, 0x9f, 0x50, 0x2b, 0xc6, 0x9e, 0xc8, 0x4c, 0xd6, 0xa1, 0x18, 0x3f, - 0x91, 0xa4, 0xc8, 0x09, 0x0a, 0xb5, 0x15, 0x8b, 0x75, 0x92, 0xa1, 0x20, 0x1d, 0x91, 0xb5, 0xf1, - 0x63, 0x16, 0x0a, 0x7b, 0x6e, 0xa8, 0x61, 0x0f, 0x7d, 0x9f, 0x9a, 0x48, 0x54, 0x98, 0x73, 0xe9, - 0x91, 0xe5, 0x50, 0x43, 0x28, 0xb8, 0xa0, 0xc5, 0x4b, 0xb2, 0x03, 0x73, 0x06, 0x8e, 0x85, 0x74, - 0x79, 0x21, 0xdd, 0xdd, 0xa7, 0xcf, 0x6a, 0xef, 0xbf, 0x80, 0x74, 0x9b, 0x38, 0x0e, 0x65, 0xcb, - 0x19, 0x38, 0x0e, 0x25, 0xdb, 0x81, 0x39, 0xea, 0xba, 0x82, 0x6f, 0xe1, 0x1f, 0xf1, 0x75, 0x5d, - 0x57, 0xf0, 0x51, 0xd7, 0x0d, 0xf9, 0x96, 0x21, 0xbc, 0xea, 0x33, 0x43, 0x2d, 0x88, 0xd4, 0x5f, - 0xa5, 0xae, 0xbb, 0x6d, 0x84, 0xe6, 0x70, 0xdb, 0xcc, 0x50, 0x8b, 0x91, 0xd9, 0xc0, 0xf1, 0xb6, - 0x41, 0xba, 0xb0, 0x98, 0xc8, 0x3f, 0x42, 0x4e, 0x0d, 0xca, 0xa9, 0xba, 0x2c, 0xd4, 0x5b, 0x9a, - 0x24, 0x40, 0x7b, 0xd2, 0x93, 0x3e, 0xad, 0x1c, 0x1b, 0x63, 0x0b, 0xf9, 0x14, 0xca, 0xb1, 0xfa, - 0x09, 0xc3, 0x8a, 0x60, 0xb8, 0x9e, 0xe8, 0x9f, 0x22, 0x28, 0x49, 0x5b, 0x12, 0xdf, 0x85, 0xb2, - 0x21, 0x2b, 0xb8, 0xef, 0x88, 0x12, 0xf6, 0xd5, 0x5a, 0x3d, 0xdb, 0xcc, 0x77, 0x56, 0x5a, 0xb2, - 0xcd, 0xa7, 0x2b, 0x5c, 0x2b, 0x19, 0x53, 0x6b, 0xbf, 0xf1, 0x43, 0x06, 0x4a, 0x31, 0xe6, 0x55, - 0xcb, 0xe0, 0x3a, 0x94, 0x4e, 0xc8, 0x27, 0xf3, 0x37, 0x4b, 0xbd, 0xe2, 0xb4, 0x7a, 0x8d, 0x00, - 0xd4, 0x4d, 0x1c, 0x33, 0x1d, 0xbb, 0x3a, 0x67, 0xe3, 0xa8, 0x3f, 0xd0, 0x77, 0x1d, 0xdb, 0x7f, - 0x9e, 0x88, 0x67, 0xdc, 0x36, 0xff, 0x42, 0xb7, 0xfd, 0x2b, 0x0b, 0x37, 0x36, 0xd1, 0x08, 0x5c, - 0x8b, 0xe9, 0x94, 0xa3, 0xf1, 0x7f, 0xff, 0x5d, 0x6a, 0xff, 0x65, 0x2f, 0xdc, 0x7f, 0x35, 0xc8, - 0xfb, 0xe8, 0x8d, 0xd1, 0xeb, 0x73, 0x36, 0x42, 0x75, 0x55, 0x0c, 0x68, 0x88, 0x4c, 0xbb, 0x6c, - 0x84, 0x64, 0x13, 0x16, 0x3d, 0x59, 0x10, 0x7d, 0x8e, 0x23, 0xd7, 0xa2, 0x1c, 0xd5, 0x9a, 0xd8, - 0xe3, 0xea, 0xc9, 0x64, 0xcb, 0xfc, 0x69, 0xe5, 0x38, 0x62, 0x57, 0x06, 0x34, 0xfe, 0xcc, 0xc2, - 0xea, 0xe9, 0x3a, 0x7b, 0x1c, 0xa0, 0xcf, 0x5f, 0xe2, 0x6c, 0xff, 0x0b, 0xe6, 0x67, 0x0f, 0xae, - 0xd3, 0x44, 0xd1, 0x09, 0xc5, 0xaa, 0xa0, 0x78, 0x7d, 0xb2, 0x89, 0x89, 0xec, 0x09, 0x17, 0xa1, - 0xa7, 0x6c, 0x97, 0x31, 0x8e, 0x7f, 0xbd, 0x02, 0x6f, 0xa5, 0x5b, 0xfb, 0xbf, 0x97, 0xf6, 0x97, - 0xae, 0xc9, 0x2f, 0xb9, 0x48, 0x4e, 0xcc, 0x0c, 0xf5, 0xd4, 0xcc, 0xe8, 0xcd, 0x9e, 0x19, 0xf5, - 0xa4, 0x8c, 0x66, 0xbc, 0x75, 0xce, 0x18, 0x1e, 0xdf, 0x64, 0xa0, 0x32, 0x01, 0xde, 0x1f, 0x52, - 0xcb, 0x42, 0xdb, 0xc4, 0x57, 0xac, 0x90, 0x1a, 0xf7, 0xe0, 0xe6, 0x99, 0x2a, 0x9c, 0xf7, 0xb6, - 0x6e, 0x10, 0x28, 0x3f, 0x0a, 0x06, 0xbe, 0xee, 0xb1, 0x41, 0x2c, 0x5a, 0xa3, 0x04, 0x85, 0x47, - 0x9c, 0xf2, 0xc0, 0x8f, 0x0d, 0x3f, 0x65, 0x21, 0x17, 0x59, 0x48, 0x03, 0x72, 0x81, 0x78, 0x1f, - 0x0b, 0xa2, 0x7c, 0x07, 0x5a, 0xe1, 0x51, 0x47, 0xa3, 0x1c, 0x7d, 0x4d, 0x7a, 0x48, 0x1b, 0x0a, - 0xd1, 0x55, 0x3f, 0xb0, 0xd9, 0xe3, 0x00, 0xc5, 0x49, 0x62, 0x1a, 0xba, 0x10, 0x01, 0xf6, 0x84, - 0x9f, 0xdc, 0x82, 0x6b, 0xf1, 0xa4, 0x90, 0xdf, 0x0a, 0x69, 0x6c, 0xe2, 0x23, 0xef, 0x41, 0x7e, - 0x52, 0x72, 0xbe, 0x6c, 0x94, 0x34, 0x34, 0xed, 0x26, 0x6b, 0x90, 0x2a, 0x50, 0x3f, 0xde, 0xcb, - 0xca, 0xa9, 0xa0, 0xc5, 0x14, 0x4a, 0x6e, 0xe8, 0x13, 0x58, 0x4a, 0x87, 0x52, 0x5d, 0x47, 0x97, - 0xa3, 0x21, 0xbb, 0x22, 0x1d, 0x9c, 0x6a, 0x1e, 0xbf, 0x2b, 0x61, 0xe4, 0x43, 0x28, 0x18, 0xc9, - 0x94, 0x0b, 0x3f, 0x80, 0xa2, 0xfa, 0x2e, 0x8b, 0xb8, 0x87, 0xe8, 0xe9, 0xe1, 0x91, 0xcb, 0x42, - 0x5f, 0x9b, 0x86, 0x91, 0x77, 0x61, 0x51, 0x77, 0x6c, 0x1b, 0x75, 0x8e, 0x46, 0xdf, 0x73, 0x02, - 0x8e, 0x9e, 0xaf, 0xbe, 0x2d, 0x0e, 0x4c, 0xe5, 0xc4, 0xa1, 0x45, 0x76, 0x72, 0x1b, 0xc8, 0x04, - 0x3c, 0xa4, 0xb6, 0x61, 0x85, 0xe8, 0x77, 0x04, 0x7a, 0x42, 0xf3, 0x99, 0x74, 0x34, 0xbe, 0x84, - 0x6a, 0xd7, 0x4d, 0x6e, 0x25, 0xcd, 0x1a, 0x9a, 0xcc, 0xe7, 0xd1, 0xd1, 0x27, 0x55, 0x71, 0x4a, - 0xba, 0xe2, 0xde, 0x00, 0x90, 0xec, 0xa1, 0x2b, 0x23, 0x5c, 0xf3, 0xd2, 0xb2, 0x6d, 0x74, 0xbe, - 0xcf, 0x40, 0x6e, 0x43, 0xb4, 0x2d, 0x59, 0x87, 0xf9, 0xae, 0xef, 0x3b, 0x3a, 0xa3, 0x1c, 0xc9, - 0x72, 0xdc, 0xcc, 0x53, 0x9f, 0x6f, 0x95, 0x59, 0xdf, 0x05, 0x4d, 0xe5, 0x8e, 0x42, 0x3e, 0x87, - 0xf9, 0xa4, 0x18, 0x89, 0x1a, 0x23, 0x4f, 0xd6, 0x67, 0xe5, 0xcd, 0xc9, 0x9c, 0x98, 0xf1, 0x95, - 0x78, 0x47, 0x21, 0x1f, 0xc3, 0xdc, 0xc3, 0x60, 0x60, 0x31, 0x7f, 0x48, 0x66, 0xdd, 0xb3, 0xb2, - 0xd2, 0x8a, 0xfe, 0x17, 0x68, 0xc5, 0x27, 0xfe, 0xd6, 0xd6, 0xc8, 0xe5, 0x47, 0x4d, 0x85, 0xf4, - 0xe0, 0x9a, 0xec, 0x27, 0x24, 0xb5, 0xd9, 0x63, 0x29, 0xda, 0xcf, 0xb9, 0x73, 0xab, 0xf3, 0x9d, - 0x02, 0x85, 0x48, 0xa4, 0x1e, 0xb5, 0xa9, 0x89, 0x1e, 0xf9, 0x1a, 0x2a, 0x91, 0xf8, 0xe8, 0x9d, - 0x4e, 0x0b, 0xb9, 0x15, 0x33, 0x3e, 0x3f, 0x65, 0xb3, 0x1e, 0x80, 0x74, 0x60, 0xfe, 0x01, 0x72, - 0xd9, 0xb2, 0x49, 0x26, 0xa6, 0x9a, 0xba, 0x52, 0x9c, 0x36, 0x6f, 0x94, 0x7f, 0x39, 0xae, 0x2a, - 0xbf, 0x1d, 0x57, 0x95, 0xdf, 0x8f, 0xab, 0xca, 0xb7, 0x7f, 0x54, 0x5f, 0x1b, 0xe4, 0x04, 0xeb, - 0x07, 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x6c, 0x85, 0xd6, 0xce, 0x7f, 0x11, 0x00, 0x00, + // 1131 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0xcb, 0x6e, 0xdb, 0x46, + 0x17, 0xfe, 0x69, 0x25, 0xb2, 0x7d, 0x64, 0x5d, 0x3c, 0xf1, 0x85, 0x51, 0xfe, 0xc8, 0xaa, 0x0a, + 0x04, 0xea, 0x25, 0x52, 0xaa, 0xa2, 0x0d, 0x82, 0x5e, 0x0c, 0x39, 0x36, 0x5a, 0x17, 0x50, 0x1a, + 0x30, 0x76, 0x57, 0x05, 0x84, 0x11, 0x79, 0x4c, 0x0d, 0x4c, 0x91, 0x0c, 0x67, 0x28, 0xc7, 0xfb, + 0xa2, 0xcf, 0xd0, 0x6d, 0xfb, 0x18, 0x05, 0xba, 0xe8, 0xae, 0xbb, 0x76, 0xdd, 0x45, 0x51, 0xb8, + 0x9b, 0x6e, 0xfa, 0x0e, 0x05, 0x87, 0x43, 0x8a, 0xb2, 0xad, 0xd8, 0x2d, 0xbc, 0x68, 0x9a, 0xae, + 0xc4, 0xf9, 0xce, 0x77, 0x3e, 0x0e, 0xcf, 0x4d, 0x33, 0x70, 0xdf, 0x66, 0x62, 0x18, 0x0e, 0x5a, + 0xa6, 0x37, 0x6a, 0xef, 0x0d, 0x71, 0x6f, 0xc8, 0x5c, 0x9b, 0x3f, 0x42, 0x71, 0xe4, 0x05, 0x87, + 0x6d, 0x21, 0xdc, 0x36, 0xf5, 0x59, 0x7b, 0x10, 0x78, 0x87, 0x18, 0xa8, 0x9f, 0x96, 0x1f, 0x78, + 0xc2, 0x23, 0xf9, 0x78, 0x55, 0xbd, 0x65, 0x7b, 0x9e, 0xed, 0x60, 0x5b, 0xa2, 0x83, 0xf0, 0xa0, + 0x8d, 0x23, 0x5f, 0x1c, 0xc7, 0xa4, 0xea, 0xdd, 0x8c, 0xba, 0xed, 0xd9, 0xde, 0x84, 0x15, 0xad, + 0xe4, 0x42, 0x3e, 0x9d, 0x43, 0x9f, 0xb9, 0x19, 0xea, 0x33, 0x45, 0x7f, 0xef, 0x32, 0x74, 0x49, + 0x35, 0x3d, 0x27, 0x7d, 0x50, 0xce, 0x0f, 0x2e, 0xe3, 0x6c, 0x53, 0x81, 0x47, 0xf4, 0x38, 0xf9, + 0x8d, 0x5d, 0x1b, 0x5f, 0xce, 0x41, 0x69, 0xdb, 0x3b, 0x72, 0x1d, 0xe6, 0x1e, 0x7e, 0xea, 0x0b, + 0xe6, 0xb9, 0xa4, 0x06, 0xc0, 0x2c, 0x74, 0x05, 0x3b, 0x60, 0x18, 0xe8, 0x5a, 0x5d, 0x6b, 0x2e, + 0x1a, 0x19, 0x84, 0xdc, 0x06, 0x50, 0x1a, 0x7d, 0x66, 0xe9, 0x73, 0xd2, 0xbe, 0xa8, 0x90, 0x5d, + 0x8b, 0xac, 0xc0, 0x75, 0x6e, 0x7a, 0x01, 0xea, 0xb9, 0xba, 0xd6, 0x2c, 0x1a, 0xf1, 0x82, 0x54, + 0x61, 0xc1, 0x42, 0x6a, 0x39, 0xcc, 0x45, 0xfd, 0x5a, 0x5d, 0x6b, 0xe6, 0x8c, 0x74, 0x4d, 0xb6, + 0xa0, 0x9c, 0x7c, 0x50, 0xdf, 0xf4, 0xdc, 0x03, 0x66, 0xeb, 0xd7, 0xeb, 0x5a, 0xb3, 0xd0, 0xb9, + 0xd9, 0x4a, 0x3f, 0x74, 0xef, 0xd9, 0x43, 0x69, 0x09, 0x03, 0x1a, 0x6d, 0xd2, 0x28, 0x25, 0x96, + 0x18, 0x26, 0x9b, 0x50, 0x4a, 0x36, 0xa5, 0x24, 0xf2, 0x52, 0x42, 0x6f, 0x25, 0xdf, 0x7b, 0x5a, + 0xa1, 0xa8, 0x0c, 0x31, 0xda, 0xf8, 0x2e, 0x07, 0xc5, 0x7d, 0x3f, 0x0a, 0x43, 0x0f, 0x39, 0xa7, + 0x36, 0x12, 0x1d, 0xe6, 0x7d, 0x7a, 0xec, 0x78, 0xd4, 0x92, 0x41, 0x58, 0x32, 0x92, 0x25, 0x79, + 0x04, 0xf3, 0x16, 0x8e, 0xfb, 0x18, 0x32, 0xbd, 0x10, 0x59, 0xb6, 0xde, 0xf9, 0xf9, 0x97, 0x8d, + 0xb7, 0x2e, 0x4a, 0x42, 0x14, 0x87, 0xb6, 0x38, 0xf6, 0x91, 0xb7, 0xb6, 0x71, 0xbc, 0xb3, 0xbf, + 0x6b, 0xe4, 0x2d, 0x1c, 0xef, 0x84, 0x2c, 0xd2, 0xa3, 0xbe, 0x2f, 0xf5, 0x96, 0xfe, 0x96, 0x5e, + 0xd7, 0xf7, 0xa5, 0x1e, 0xf5, 0xfd, 0x48, 0x6f, 0x15, 0xa2, 0xa7, 0x28, 0x3b, 0x45, 0x99, 0x9d, + 0xeb, 0xd4, 0xf7, 0x77, 0xad, 0x08, 0x8e, 0xb6, 0xcd, 0x2c, 0xbd, 0x14, 0xc3, 0x16, 0x8e, 0x77, + 0x2d, 0xd2, 0x85, 0xe5, 0x34, 0xfc, 0x23, 0x14, 0xd4, 0xa2, 0x82, 0xea, 0xab, 0x32, 0x7a, 0x2b, + 0x93, 0x04, 0x18, 0xcf, 0x7a, 0xca, 0x66, 0x54, 0x12, 0x30, 0x41, 0xc8, 0x87, 0x50, 0x49, 0xa2, + 0x9f, 0x2a, 0xac, 0x49, 0x85, 0x1b, 0x69, 0xfc, 0x33, 0x02, 0x65, 0x85, 0xa5, 0xfe, 0x5d, 0xa8, + 0x58, 0xaa, 0x08, 0xfb, 0x9e, 0xac, 0x42, 0xae, 0x6f, 0xd4, 0x73, 0xcd, 0x42, 0x67, 0xad, 0xa5, + 0x3a, 0x75, 0xba, 0x48, 0x8d, 0xb2, 0x35, 0xb5, 0xe6, 0x8d, 0x6f, 0xe7, 0xa0, 0x9c, 0x70, 0x5e, + 0xb6, 0x0c, 0x6e, 0x42, 0xf9, 0x54, 0xf8, 0x54, 0xfe, 0x66, 0x45, 0xaf, 0x34, 0x1d, 0xbd, 0x46, + 0x08, 0xfa, 0x36, 0x8e, 0x99, 0x89, 0x5d, 0x53, 0xb0, 0x71, 0xdc, 0x1f, 0xc8, 0x7d, 0xcf, 0xe5, + 0xcf, 0x0b, 0xe2, 0x39, 0xaf, 0x2d, 0xfc, 0xa5, 0xd7, 0xfe, 0x91, 0x83, 0x9b, 0xdb, 0x68, 0x85, + 0xbe, 0xc3, 0x4c, 0x2a, 0xd0, 0xfa, 0xaf, 0xff, 0xae, 0xb4, 0xff, 0x72, 0x97, 0xee, 0xbf, 0x0d, + 0x28, 0x70, 0x0c, 0xc6, 0x18, 0xf4, 0x05, 0x1b, 0xa1, 0xbe, 0x2e, 0x07, 0x34, 0xc4, 0xd0, 0x1e, + 0x1b, 0x21, 0xd9, 0x86, 0xe5, 0x40, 0x15, 0x44, 0x5f, 0xe0, 0xc8, 0x77, 0xa8, 0x40, 0x7d, 0x43, + 0xee, 0x71, 0xfd, 0x74, 0xb2, 0x55, 0xfe, 0x8c, 0x4a, 0xe2, 0xb1, 0xa7, 0x1c, 0x1a, 0xbf, 0xe7, + 0x60, 0xfd, 0x6c, 0x9d, 0x3d, 0x0d, 0x91, 0x8b, 0x17, 0x38, 0xdb, 0xff, 0x80, 0xf9, 0xd9, 0x83, + 0x1b, 0x34, 0x8d, 0xe8, 0x44, 0x62, 0x5d, 0x4a, 0xfc, 0x7f, 0xb2, 0x89, 0x49, 0xd8, 0x53, 0x2d, + 0x42, 0xcf, 0x60, 0x57, 0x31, 0x8e, 0x7f, 0xbc, 0x06, 0xaf, 0x66, 0x5b, 0xfb, 0xdf, 0x97, 0xf6, + 0x17, 0xae, 0xc9, 0xaf, 0xb8, 0x48, 0x4e, 0xcd, 0x0c, 0xfd, 0xcc, 0xcc, 0xe8, 0xcd, 0x9e, 0x19, + 0xf5, 0xb4, 0x8c, 0x66, 0xfc, 0xeb, 0x9c, 0x33, 0x3c, 0xbe, 0x98, 0x83, 0xea, 0x84, 0xf8, 0x70, + 0x48, 0x1d, 0x07, 0x5d, 0x1b, 0x5f, 0xb2, 0x42, 0x6a, 0xdc, 0x87, 0x5b, 0xe7, 0x46, 0xe1, 0xa2, + 0x7f, 0xeb, 0x06, 0x81, 0xca, 0x93, 0x70, 0xc0, 0xcd, 0x80, 0x0d, 0x92, 0xa0, 0x35, 0xca, 0x50, + 0x7c, 0x22, 0xa8, 0x08, 0x79, 0x02, 0x7c, 0x9f, 0x83, 0x7c, 0x8c, 0x90, 0x06, 0xe4, 0x43, 0xf9, + 0x7f, 0x2c, 0x85, 0x0a, 0x1d, 0x68, 0x45, 0xb7, 0x15, 0x83, 0x0a, 0xe4, 0x86, 0xb2, 0x90, 0x36, + 0x14, 0xe3, 0xa7, 0x7e, 0xe8, 0xb2, 0xa7, 0x21, 0xca, 0xdb, 0xc0, 0x34, 0x75, 0x29, 0x26, 0xec, + 0x4b, 0x3b, 0xb9, 0x03, 0x0b, 0xc9, 0xa4, 0x50, 0x67, 0x85, 0x2c, 0x37, 0xb5, 0x91, 0x37, 0xa1, + 0x30, 0x29, 0x39, 0xae, 0x1a, 0x25, 0x4b, 0xcd, 0x9a, 0xc9, 0x03, 0xc8, 0x14, 0x28, 0x4f, 0xf6, + 0xb2, 0x76, 0xc6, 0x69, 0x39, 0xc3, 0x52, 0x1b, 0xfa, 0x00, 0x56, 0xb2, 0xae, 0xd4, 0x34, 0xd1, + 0x17, 0x68, 0xa9, 0xae, 0xc8, 0x3a, 0x67, 0x9a, 0x87, 0x77, 0x15, 0x8d, 0xbc, 0x0b, 0x45, 0x2b, + 0x9d, 0x72, 0xd1, 0x01, 0x28, 0xae, 0xef, 0x8a, 0xf4, 0x7b, 0x8c, 0x81, 0x19, 0xdd, 0x9a, 0x1c, + 0xe4, 0xc6, 0x34, 0x8d, 0xbc, 0x01, 0xcb, 0xa6, 0xe7, 0xba, 0x68, 0x0a, 0xb4, 0xfa, 0x81, 0x17, + 0x0a, 0x0c, 0xb8, 0xfe, 0x9a, 0xbc, 0x30, 0x55, 0x52, 0x83, 0x11, 0xe3, 0xe4, 0x2e, 0x90, 0x09, + 0x79, 0x48, 0x5d, 0xcb, 0x89, 0xd8, 0xaf, 0x4b, 0xf6, 0x44, 0xe6, 0x63, 0x65, 0x68, 0x7c, 0x06, + 0xb5, 0xae, 0x9f, 0xbe, 0x4a, 0xc1, 0x06, 0xda, 0x8c, 0x8b, 0xf8, 0xea, 0x93, 0xa9, 0x38, 0x2d, + 0x5b, 0x71, 0xb7, 0x01, 0x94, 0x7a, 0xe6, 0x62, 0xa7, 0x90, 0x5d, 0xab, 0xf3, 0xcd, 0x1c, 0xe4, + 0xb7, 0x64, 0xdb, 0x92, 0x4d, 0x58, 0xec, 0x72, 0xee, 0x99, 0x8c, 0x0a, 0x24, 0xab, 0x49, 0x33, + 0x4f, 0x1d, 0xdf, 0xaa, 0xb3, 0xce, 0x05, 0x4d, 0xed, 0x9e, 0x46, 0x3e, 0x81, 0xc5, 0xb4, 0x18, + 0x89, 0x9e, 0x30, 0x4f, 0xd7, 0x67, 0xf5, 0x95, 0xc9, 0x9c, 0x98, 0x71, 0x4a, 0xbc, 0xa7, 0x91, + 0xf7, 0x61, 0xfe, 0x71, 0x38, 0x70, 0x18, 0x1f, 0x92, 0x59, 0xef, 0xac, 0xae, 0xb5, 0xe2, 0xab, + 0x7d, 0x2b, 0xb9, 0xb4, 0xb7, 0x76, 0xa2, 0xab, 0x7d, 0x53, 0x23, 0x3d, 0x58, 0x50, 0xfd, 0x84, + 0x64, 0x63, 0xf6, 0x58, 0x8a, 0xf7, 0x73, 0xe1, 0xdc, 0xea, 0x7c, 0xad, 0x41, 0x31, 0x0e, 0x52, + 0x8f, 0xba, 0xd4, 0xc6, 0x80, 0x7c, 0x0e, 0xd5, 0x38, 0xf8, 0x18, 0x9c, 0x4d, 0x0b, 0xb9, 0x93, + 0x28, 0x3e, 0x3f, 0x65, 0xb3, 0x3e, 0x80, 0x74, 0x60, 0xf1, 0x23, 0x14, 0xaa, 0x65, 0xd3, 0x4c, + 0x4c, 0x35, 0x75, 0xb5, 0x34, 0x0d, 0x6f, 0x55, 0x7e, 0x38, 0xa9, 0x69, 0x3f, 0x9d, 0xd4, 0xb4, + 0x5f, 0x4f, 0x6a, 0xda, 0x57, 0xbf, 0xd5, 0xfe, 0x37, 0xc8, 0x4b, 0xd5, 0xb7, 0xff, 0x0c, 0x00, + 0x00, 0xff, 0xff, 0x64, 0x08, 0xa4, 0x66, 0x42, 0x11, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index f6d23531f..6b1b6074e 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -13,7 +13,7 @@ package broker; message DownlinkOption { string identifier = 1; - bytes gateway_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; + string gateway_id = 2; uint32 score = 3; // lower is better, 0 is best int64 deadline = 4; // deadline time at server represented as the number of nanoseconds elapsed since January 1, 1970 UTC protocol.TxConfiguration protocol_config = 5; diff --git a/api/broker/validation.go b/api/broker/validation.go index 4c1d720dc..b6b4fbaf0 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -7,7 +7,7 @@ func (m *DownlinkOption) Validate() bool { if m.Identifier == "" { return false } - if m.GatewayEui == nil || m.GatewayEui.IsEmpty() { + if m.GatewayId == "" { return false } if m.ProtocolConfig == nil || !m.ProtocolConfig.Validate() { diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 2f7fbc0bf..fb0ef7936 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -21,8 +21,6 @@ import fmt "fmt" import math "math" import _ "github.com/gogo/protobuf/gogoproto" -import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" - import io "io" // Reference imports to suppress errors if they are not otherwise used. @@ -49,15 +47,15 @@ func (*GPSMetadata) ProtoMessage() {} func (*GPSMetadata) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{0} } type RxMetadata struct { - GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` - Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Time int64 `protobuf:"varint,12,opt,name=time,proto3" json:"time,omitempty"` - RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` - Channel uint32 `protobuf:"varint,22,opt,name=channel,proto3" json:"channel,omitempty"` - Frequency uint64 `protobuf:"varint,31,opt,name=frequency,proto3" json:"frequency,omitempty"` - Rssi float32 `protobuf:"fixed32,32,opt,name=rssi,proto3" json:"rssi,omitempty"` - Snr float32 `protobuf:"fixed32,33,opt,name=snr,proto3" json:"snr,omitempty"` - Gps *GPSMetadata `protobuf:"bytes,41,opt,name=gps" json:"gps,omitempty"` + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` + Timestamp uint32 `protobuf:"varint,11,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Time int64 `protobuf:"varint,12,opt,name=time,proto3" json:"time,omitempty"` + RfChain uint32 `protobuf:"varint,21,opt,name=rf_chain,json=rfChain,proto3" json:"rf_chain,omitempty"` + Channel uint32 `protobuf:"varint,22,opt,name=channel,proto3" json:"channel,omitempty"` + Frequency uint64 `protobuf:"varint,31,opt,name=frequency,proto3" json:"frequency,omitempty"` + Rssi float32 `protobuf:"fixed32,32,opt,name=rssi,proto3" json:"rssi,omitempty"` + Snr float32 `protobuf:"fixed32,33,opt,name=snr,proto3" json:"snr,omitempty"` + Gps *GPSMetadata `protobuf:"bytes,41,opt,name=gps" json:"gps,omitempty"` } func (m *RxMetadata) Reset() { *m = RxMetadata{} } @@ -175,15 +173,11 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayEui != nil { + if len(m.GatewayId) > 0 { data[i] = 0xa i++ - i = encodeVarintGateway(data, i, uint64(m.GatewayEui.Size())) - n1, err := m.GatewayEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n1 + i = encodeVarintGateway(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) } if m.Timestamp != 0 { data[i] = 0x58 @@ -236,11 +230,11 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { data[i] = 0x2 i++ i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n2, err := m.Gps.MarshalTo(data[i:]) + n1, err := m.Gps.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n1 } return i, nil } @@ -378,11 +372,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n3, err := m.Gps.MarshalTo(data[i:]) + n2, err := m.Gps.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n2 } if m.Rtt != 0 { data[i] = 0xf8 @@ -470,8 +464,8 @@ func (m *GPSMetadata) Size() (n int) { func (m *RxMetadata) Size() (n int) { var l int _ = l - if m.GatewayEui != nil { - l = m.GatewayEui.Size() + l = len(m.GatewayId) + if l > 0 { n += 1 + l + sovGateway(uint64(l)) } if m.Timestamp != 0 { @@ -739,9 +733,9 @@ func (m *RxMetadata) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) } - var byteLen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowGateway @@ -751,23 +745,20 @@ func (m *RxMetadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthGateway } - postIndex := iNdEx + byteLen + postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayEui = &v - if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } + m.GatewayId = string(data[iNdEx:postIndex]) iNdEx = postIndex case 11: if wireType != 0 { @@ -1582,44 +1573,42 @@ func init() { } var fileDescriptorGateway = []byte{ - // 611 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x54, 0x4d, 0x6f, 0xd3, 0x30, - 0x18, 0x26, 0x69, 0xf7, 0xe5, 0xae, 0xdb, 0xe4, 0x6d, 0xc5, 0x4c, 0xa8, 0x0b, 0x45, 0x42, 0xe1, - 0xab, 0x91, 0x40, 0x3b, 0xec, 0xba, 0x31, 0x4d, 0x3b, 0xc0, 0x90, 0x37, 0x2e, 0x5c, 0x2a, 0x2f, - 0x75, 0x53, 0xab, 0xa9, 0x1d, 0x1c, 0x67, 0xed, 0xf8, 0x25, 0xfc, 0x17, 0xfe, 0x00, 0x47, 0x24, - 0x6e, 0x1c, 0x10, 0x1a, 0x12, 0xbf, 0x03, 0xf9, 0x6d, 0x92, 0x65, 0x48, 0xd0, 0x53, 0xde, 0xe7, - 0x79, 0x5e, 0xfb, 0xfd, 0x78, 0xac, 0xa0, 0xfd, 0x48, 0x98, 0x61, 0x76, 0xd1, 0x0d, 0xd5, 0x38, - 0x38, 0x1f, 0xf2, 0xf3, 0xa1, 0x90, 0x51, 0xfa, 0x86, 0x9b, 0x89, 0xd2, 0xa3, 0xc0, 0x18, 0x19, - 0xb0, 0x44, 0x04, 0x11, 0x33, 0x7c, 0xc2, 0xae, 0x8a, 0x6f, 0x37, 0xd1, 0xca, 0x28, 0xbc, 0x94, - 0xc3, 0x9d, 0xe7, 0x95, 0x3b, 0x22, 0x15, 0xa9, 0x00, 0xf4, 0x8b, 0x6c, 0x00, 0x08, 0x00, 0x44, - 0xb3, 0x73, 0x9d, 0x09, 0x6a, 0x1c, 0xbf, 0x3d, 0x7b, 0xcd, 0x0d, 0xeb, 0x33, 0xc3, 0x30, 0x46, - 0x75, 0x23, 0xc6, 0x9c, 0x38, 0x9e, 0xe3, 0xd7, 0x28, 0xc4, 0x78, 0x07, 0x2d, 0xc7, 0xcc, 0x08, - 0x93, 0xf5, 0x39, 0x71, 0x3d, 0xc7, 0x77, 0x69, 0x89, 0xf1, 0x7d, 0xb4, 0x12, 0x2b, 0x19, 0xcd, - 0xc4, 0x1a, 0x88, 0x37, 0x84, 0x3d, 0xc9, 0xe2, 0xfc, 0x64, 0xdd, 0x73, 0xfc, 0x05, 0x5a, 0xe2, - 0xce, 0x67, 0x17, 0x21, 0x3a, 0x2d, 0x0b, 0xbf, 0x47, 0x8d, 0x7c, 0x82, 0x1e, 0xcf, 0x04, 0xd4, - 0x5f, 0x3d, 0xd8, 0xff, 0xfe, 0x63, 0x77, 0x6f, 0xde, 0x4e, 0x42, 0xa5, 0x79, 0x60, 0xae, 0x12, - 0x9e, 0x76, 0x8f, 0x67, 0x37, 0x1c, 0xbd, 0x3b, 0xa1, 0x28, 0xbf, 0xed, 0x28, 0x13, 0xb6, 0x49, - 0x3b, 0x48, 0x6a, 0xd8, 0x38, 0x21, 0x0d, 0xcf, 0xf1, 0x9b, 0xf4, 0x86, 0x28, 0x47, 0x5e, 0xad, - 0x8c, 0x7c, 0x0f, 0x2d, 0xeb, 0x41, 0x2f, 0x1c, 0x32, 0x21, 0xc9, 0x36, 0x1c, 0x58, 0xd2, 0x83, - 0x43, 0x0b, 0x31, 0x41, 0x4b, 0xe1, 0x90, 0x49, 0xc9, 0x63, 0xd2, 0x9a, 0x29, 0x39, 0xb4, 0x65, - 0x06, 0x9a, 0x7f, 0xc8, 0xb8, 0x0c, 0xaf, 0xc8, 0xae, 0xe7, 0xf8, 0x75, 0x7a, 0x43, 0xd8, 0x32, - 0x3a, 0x4d, 0x05, 0xf1, 0x60, 0x49, 0x10, 0xe3, 0x0d, 0x54, 0x4b, 0xa5, 0x26, 0x0f, 0x80, 0xb2, - 0x21, 0x7e, 0x84, 0x6a, 0x51, 0x92, 0x92, 0xc7, 0x9e, 0xe3, 0x37, 0x5e, 0x6c, 0x75, 0x0b, 0x8f, - 0x2b, 0x16, 0x51, 0x9b, 0xd0, 0xf9, 0xed, 0xa0, 0xf5, 0xf3, 0xe9, 0xa1, 0x92, 0x03, 0x11, 0x65, - 0x9a, 0x19, 0xa1, 0xe4, 0x9c, 0x31, 0xff, 0x33, 0xd2, 0xad, 0xc6, 0x5b, 0x7f, 0x37, 0xbe, 0x85, - 0x16, 0x12, 0x35, 0xe1, 0x9a, 0xdc, 0x05, 0x07, 0x67, 0x00, 0xef, 0xa1, 0x56, 0xa2, 0x62, 0xa6, - 0xc5, 0x47, 0x28, 0xde, 0x13, 0xf2, 0x92, 0xeb, 0x54, 0x28, 0x09, 0x93, 0x2f, 0xd3, 0xed, 0xaa, - 0x7a, 0x52, 0x88, 0x38, 0x40, 0x9b, 0xe5, 0xcd, 0xbd, 0x3e, 0xbf, 0x14, 0xa0, 0xc3, 0x52, 0x9a, - 0x14, 0x97, 0xd2, 0xab, 0x42, 0xe9, 0x7c, 0x73, 0xd1, 0xe2, 0x99, 0x61, 0x26, 0x4b, 0x6f, 0xcf, - 0xe7, 0xfc, 0xcb, 0x46, 0xb7, 0x62, 0xe3, 0x1a, 0x72, 0x85, 0x5d, 0x45, 0xcd, 0x5f, 0xa1, 0xae, - 0x48, 0xec, 0x7b, 0x4c, 0x62, 0x66, 0x06, 0x4a, 0x8f, 0xc1, 0xee, 0x15, 0x5a, 0x62, 0xfc, 0x10, - 0x35, 0x43, 0x25, 0x0d, 0x0b, 0x4d, 0x8f, 0x8f, 0x99, 0x88, 0x49, 0x13, 0x12, 0x56, 0x73, 0xf2, - 0xc8, 0x72, 0xd8, 0x43, 0x8d, 0x3e, 0x4f, 0x43, 0x2d, 0x12, 0x68, 0x7b, 0x0d, 0x52, 0xaa, 0x14, - 0x6e, 0xa1, 0x45, 0xcd, 0x23, 0x2b, 0xae, 0x83, 0x98, 0xa3, 0xc2, 0xd8, 0xed, 0x39, 0xc6, 0xda, - 0x27, 0xa1, 0x8d, 0x81, 0x25, 0x36, 0xa9, 0x0d, 0xf1, 0x26, 0x5a, 0xd0, 0xd3, 0x9e, 0x90, 0xf0, - 0x28, 0x9a, 0xb4, 0xae, 0xa7, 0x27, 0x32, 0x27, 0xd5, 0x88, 0x3c, 0x29, 0xc8, 0xd3, 0x91, 0x25, - 0x0d, 0x64, 0x3e, 0x9d, 0x91, 0x26, 0xcf, 0x34, 0x90, 0xf9, 0xac, 0x20, 0x4f, 0x47, 0x07, 0x1b, - 0x5f, 0xae, 0xdb, 0xce, 0xd7, 0xeb, 0xb6, 0xf3, 0xf3, 0xba, 0xed, 0x7c, 0xfa, 0xd5, 0xbe, 0x73, - 0xb1, 0x08, 0xbf, 0x83, 0x97, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbe, 0x59, 0xd6, 0x7d, 0x83, - 0x04, 0x00, 0x00, + // 582 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x5d, 0x6f, 0xd3, 0x3c, + 0x14, 0x7e, 0xdd, 0x76, 0x1f, 0x75, 0xd7, 0x6d, 0xf2, 0xb6, 0xbe, 0x66, 0x82, 0x12, 0x8a, 0x84, + 0xc2, 0xd7, 0x2a, 0x81, 0xb8, 0xe0, 0x96, 0x81, 0xd0, 0x2e, 0x60, 0xc8, 0xdb, 0x7d, 0xe4, 0x25, + 0x4e, 0x6a, 0x35, 0xb5, 0x83, 0xe3, 0xac, 0x1d, 0xbf, 0x84, 0x9f, 0xc4, 0x25, 0x12, 0x7f, 0x00, + 0x0d, 0x89, 0x9f, 0x81, 0x90, 0x4f, 0x93, 0x2c, 0x43, 0x82, 0x5d, 0xf5, 0x3c, 0xcf, 0x73, 0x4e, + 0xce, 0xc7, 0xe3, 0xe2, 0x97, 0x89, 0xb4, 0x93, 0xe2, 0xec, 0x20, 0xd4, 0xb3, 0xf1, 0xe9, 0x44, + 0x9c, 0x4e, 0xa4, 0x4a, 0xf2, 0xf7, 0xc2, 0xce, 0xb5, 0x99, 0x8e, 0xad, 0x55, 0x63, 0x9e, 0xc9, + 0x71, 0xc2, 0xad, 0x98, 0xf3, 0x8b, 0xea, 0xf7, 0x20, 0x33, 0xda, 0x6a, 0xb2, 0x56, 0xc2, 0xfd, + 0xa7, 0x8d, 0x6f, 0x24, 0x3a, 0xd1, 0x63, 0xd0, 0xcf, 0x8a, 0x18, 0x10, 0x00, 0x88, 0x96, 0x75, + 0xa3, 0x39, 0xee, 0xbd, 0xfd, 0x70, 0xf2, 0x4e, 0x58, 0x1e, 0x71, 0xcb, 0x09, 0xc1, 0x1d, 0x2b, + 0x67, 0x82, 0x22, 0x0f, 0xf9, 0x6d, 0x06, 0x31, 0xd9, 0xc7, 0xeb, 0x29, 0xb7, 0xd2, 0x16, 0x91, + 0xa0, 0x2d, 0x0f, 0xf9, 0x2d, 0x56, 0x63, 0x72, 0x1b, 0x77, 0x53, 0xad, 0x92, 0xa5, 0xd8, 0x06, + 0xf1, 0x8a, 0x70, 0x95, 0x3c, 0x2d, 0x2b, 0x3b, 0x1e, 0xf2, 0x57, 0x58, 0x8d, 0x47, 0xbf, 0x10, + 0xc6, 0x6c, 0x51, 0x37, 0xbe, 0x83, 0x71, 0xb9, 0x41, 0x20, 0x23, 0x68, 0xdf, 0x65, 0xdd, 0x92, + 0x39, 0x8a, 0x5c, 0x1f, 0x37, 0x4b, 0x6e, 0xf9, 0x2c, 0xa3, 0x3d, 0x0f, 0xf9, 0x7d, 0x76, 0x45, + 0xd4, 0x53, 0x6f, 0x34, 0xa6, 0xbe, 0x85, 0xd7, 0x4d, 0x1c, 0x84, 0x13, 0x2e, 0x15, 0xdd, 0x83, + 0x82, 0x35, 0x13, 0x1f, 0x3a, 0x48, 0x28, 0x5e, 0x0b, 0x27, 0x5c, 0x29, 0x91, 0xd2, 0xc1, 0x52, + 0x29, 0xa1, 0x6b, 0x13, 0x1b, 0xf1, 0xb1, 0x10, 0x2a, 0xbc, 0xa0, 0x77, 0x3d, 0xe4, 0x77, 0xd8, + 0x15, 0xe1, 0xda, 0x98, 0x3c, 0x97, 0xd4, 0x83, 0x3d, 0x21, 0x26, 0xdb, 0xb8, 0x9d, 0x2b, 0x43, + 0xef, 0x01, 0xe5, 0x42, 0xf2, 0x00, 0xb7, 0x93, 0x2c, 0xa7, 0x0f, 0x3d, 0xe4, 0xf7, 0x9e, 0xed, + 0x1e, 0x54, 0x36, 0x35, 0xae, 0xcc, 0x5c, 0xc2, 0xe8, 0x27, 0xc2, 0x5b, 0xa7, 0x8b, 0x43, 0xad, + 0x62, 0x99, 0x14, 0x86, 0x5b, 0xa9, 0xd5, 0x0d, 0x6b, 0xfe, 0x63, 0xa5, 0x6b, 0x83, 0x0f, 0xfe, + 0x1c, 0x7c, 0x17, 0xaf, 0x64, 0x7a, 0x2e, 0x0c, 0xfd, 0x1f, 0x4c, 0x58, 0x02, 0xf2, 0x02, 0x0f, + 0x32, 0x9d, 0x72, 0x23, 0x3f, 0x41, 0xf3, 0x40, 0xaa, 0x73, 0x61, 0x72, 0xa9, 0x15, 0x6c, 0xbe, + 0xce, 0xf6, 0x9a, 0xea, 0x51, 0x25, 0x92, 0x31, 0xde, 0xa9, 0xbf, 0x1c, 0x44, 0xe2, 0x5c, 0x82, + 0x0e, 0x47, 0xe9, 0x33, 0x52, 0x4b, 0xaf, 0x2b, 0x65, 0xf4, 0xad, 0x85, 0x57, 0x4f, 0x2c, 0xb7, + 0x45, 0x7e, 0x7d, 0x3f, 0xf4, 0x37, 0x1b, 0x5b, 0x0d, 0x1b, 0x37, 0x71, 0x4b, 0xba, 0x53, 0xb4, + 0xfd, 0x2e, 0x6b, 0xc9, 0xcc, 0x3d, 0xa9, 0x2c, 0xe5, 0x36, 0xd6, 0x66, 0x06, 0x76, 0x77, 0x59, + 0x8d, 0xc9, 0x7d, 0xdc, 0x0f, 0xb5, 0xb2, 0x3c, 0xb4, 0x81, 0x98, 0x71, 0x99, 0xd2, 0x3e, 0x24, + 0x6c, 0x94, 0xe4, 0x1b, 0xc7, 0x11, 0x0f, 0xf7, 0x22, 0x91, 0x87, 0x46, 0x66, 0x30, 0xf6, 0x26, + 0xa4, 0x34, 0x29, 0x32, 0xc0, 0xab, 0x46, 0x24, 0x4e, 0xdc, 0x02, 0xb1, 0x44, 0x95, 0xb1, 0x7b, + 0x37, 0x18, 0xeb, 0x9e, 0x84, 0xb1, 0x16, 0x8e, 0xd8, 0x67, 0x2e, 0x24, 0x3b, 0x78, 0xc5, 0x2c, + 0x02, 0xa9, 0xe0, 0x51, 0xf4, 0x59, 0xc7, 0x2c, 0x8e, 0x54, 0x49, 0xea, 0x29, 0x7d, 0x54, 0x91, + 0xc7, 0x53, 0x47, 0x5a, 0xc8, 0x7c, 0xbc, 0x24, 0x6d, 0x99, 0x69, 0x21, 0xf3, 0x49, 0x45, 0x1e, + 0x4f, 0x5f, 0x6d, 0x7f, 0xb9, 0x1c, 0xa2, 0xaf, 0x97, 0x43, 0xf4, 0xfd, 0x72, 0x88, 0x3e, 0xff, + 0x18, 0xfe, 0x77, 0xb6, 0x0a, 0xff, 0xe8, 0xe7, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x31, 0xae, + 0xe0, 0x33, 0x46, 0x04, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 0985df425..7d69921b0 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -15,7 +15,7 @@ message GPSMetadata { } message RxMetadata { - bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; + string gateway_id = 1; uint32 timestamp = 11; int64 time = 12; diff --git a/api/gateway/validation.go b/api/gateway/validation.go index e4a09f65a..b030ccc00 100644 --- a/api/gateway/validation.go +++ b/api/gateway/validation.go @@ -2,7 +2,7 @@ package gateway // Validate implements the api.Validator interface func (m *RxMetadata) Validate() bool { - if m.GatewayEui == nil || m.GatewayEui.IsEmpty() { + if m.GatewayId == "" { return false } return true diff --git a/api/router/router.pb.go b/api/router/router.pb.go index b5033a954..a9d70b4ba 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -146,7 +146,7 @@ func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescr // message GatewayStatusRequest is used to request the status of a gateway from // this Router type GatewayStatusRequest struct { - GatewayEui *github_com_TheThingsNetwork_ttn_core_types.GatewayEUI `protobuf:"bytes,1,opt,name=gateway_eui,json=gatewayEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.GatewayEUI" json:"gateway_eui,omitempty"` + GatewayId string `protobuf:"bytes,1,opt,name=gateway_id,json=gatewayId,proto3" json:"gateway_id,omitempty"` } func (m *GatewayStatusRequest) Reset() { *m = GatewayStatusRequest{} } @@ -832,15 +832,11 @@ func (m *GatewayStatusRequest) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayEui != nil { + if len(m.GatewayId) > 0 { data[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayEui.Size())) - n9, err := m.GatewayEui.MarshalTo(data[i:]) - if err != nil { - return 0, err - } - i += n9 + i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) + i += copy(data[i:], m.GatewayId) } return i, nil } @@ -869,11 +865,11 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintRouter(data, i, uint64(m.Status.Size())) - n10, err := m.Status.MarshalTo(data[i:]) + n9, err := m.Status.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n9 } return i, nil } @@ -915,11 +911,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n11, err := m.GatewayStatus.MarshalTo(data[i:]) + n10, err := m.GatewayStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n10 } if m.ActiveGateways != 0 { data[i] = 0x20 @@ -930,11 +926,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n12, err := m.Uplink.MarshalTo(data[i:]) + n11, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n11 } if m.Downlink != nil { data[i] = 0xaa @@ -942,11 +938,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n13, err := m.Downlink.MarshalTo(data[i:]) + n12, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n12 } if m.Activations != nil { data[i] = 0xfa @@ -954,11 +950,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n14, err := m.Activations.MarshalTo(data[i:]) + n13, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n13 } if m.ActivationsAccepted != nil { data[i] = 0x82 @@ -966,11 +962,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x2 i++ i = encodeVarintRouter(data, i, uint64(m.ActivationsAccepted.Size())) - n15, err := m.ActivationsAccepted.MarshalTo(data[i:]) + n14, err := m.ActivationsAccepted.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n14 } if m.ConnectedGateways != 0 { data[i] = 0xc8 @@ -1093,8 +1089,8 @@ func (m *DeviceActivationResponse) Size() (n int) { func (m *GatewayStatusRequest) Size() (n int) { var l int _ = l - if m.GatewayEui != nil { - l = m.GatewayEui.Size() + l = len(m.GatewayId) + if l > 0 { n += 1 + l + sovRouter(uint64(l)) } return n @@ -1803,9 +1799,9 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayId", wireType) } - var byteLen int + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -1815,23 +1811,20 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - byteLen |= (int(b) & 0x7F) << shift + stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { break } } - if byteLen < 0 { + intStringLen := int(stringLen) + if intStringLen < 0 { return ErrInvalidLengthRouter } - postIndex := iNdEx + byteLen + postIndex := iNdEx + intStringLen if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.GatewayEUI - m.GatewayEui = &v - if err := m.GatewayEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err - } + m.GatewayId = string(data[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex @@ -2388,57 +2381,56 @@ func init() { } var fileDescriptorRouter = []byte{ - // 829 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0xc7, 0x05, 0x79, 0xdb, 0xd7, 0xa6, 0xc9, 0xce, 0x36, 0x5d, 0x93, 0x85, 0x24, 0xf2, 0x81, - 0x0d, 0x7f, 0xd6, 0x61, 0x83, 0x56, 0xa8, 0x20, 0x10, 0x09, 0x8d, 0x56, 0x48, 0x64, 0x85, 0xdc, - 0xee, 0x05, 0x09, 0x45, 0x13, 0xe7, 0xad, 0x6b, 0x35, 0xf5, 0x18, 0xcf, 0x38, 0xdd, 0x7c, 0x0b, - 0xb8, 0xf1, 0x21, 0xf8, 0x20, 0x1c, 0x38, 0x70, 0xe2, 0xb0, 0x87, 0x15, 0x2a, 0x9f, 0x82, 0x1b, - 0xf2, 0x78, 0xc6, 0xb1, 0xd3, 0x74, 0x69, 0xd9, 0x93, 0x3d, 0xbf, 0xf7, 0x9b, 0xdf, 0xbc, 0xdf, - 0x9b, 0x99, 0x37, 0xf0, 0xa9, 0x1f, 0x88, 0x93, 0x64, 0xe2, 0x78, 0xec, 0xac, 0x7b, 0x7c, 0x82, - 0xc7, 0x27, 0x41, 0xe8, 0xf3, 0x27, 0x28, 0xce, 0x59, 0x7c, 0xda, 0x15, 0x22, 0xec, 0xd2, 0x28, - 0xe8, 0xc6, 0x2c, 0x11, 0x18, 0xab, 0x8f, 0x13, 0xc5, 0x4c, 0x30, 0x62, 0x66, 0xa3, 0xc6, 0x3d, - 0x9f, 0x31, 0x7f, 0x86, 0x5d, 0x89, 0x4e, 0x92, 0x67, 0x5d, 0x3c, 0x8b, 0xc4, 0x22, 0x23, 0x35, - 0x1e, 0x14, 0xd4, 0x7d, 0xe6, 0xb3, 0x25, 0x2b, 0x1d, 0xc9, 0x81, 0xfc, 0x5b, 0x43, 0xbf, 0x32, - 0x19, 0x1a, 0x05, 0x8a, 0xfe, 0xf9, 0x75, 0xe8, 0x92, 0xea, 0xb1, 0x59, 0xfe, 0xa3, 0x26, 0x1f, - 0x5c, 0x67, 0xb2, 0x4f, 0x05, 0x9e, 0xd3, 0x85, 0xfe, 0x66, 0x53, 0x6d, 0x02, 0xb5, 0xa3, 0x64, - 0xc2, 0xbd, 0x38, 0x98, 0xa0, 0x8b, 0x3f, 0x26, 0xc8, 0x85, 0xfd, 0xab, 0x01, 0x95, 0xa7, 0xd1, - 0x2c, 0x08, 0x4f, 0x47, 0xc8, 0x39, 0xf5, 0x91, 0x58, 0x70, 0x2b, 0xa2, 0x8b, 0x19, 0xa3, 0x53, - 0xcb, 0x68, 0x1b, 0x9d, 0x1d, 0x57, 0x0f, 0x49, 0x1f, 0x6e, 0xeb, 0x64, 0xc6, 0x67, 0x28, 0xe8, - 0x94, 0x0a, 0x6a, 0x6d, 0xb7, 0x8d, 0xce, 0x76, 0x6f, 0xcf, 0xc9, 0xd3, 0x74, 0x9f, 0x8f, 0x54, - 0xcc, 0xad, 0x69, 0x50, 0x23, 0xe4, 0x4b, 0xa8, 0xa9, 0x9c, 0x96, 0x0a, 0x3b, 0x52, 0xe1, 0x8e, - 0xa3, 0x93, 0x2d, 0x08, 0x54, 0x15, 0xa6, 0x01, 0xfb, 0x77, 0x03, 0xaa, 0x87, 0xec, 0x3c, 0xbc, - 0x5e, 0xc2, 0xdf, 0xc1, 0x7e, 0x9e, 0xb0, 0xc7, 0xc2, 0x67, 0x81, 0x9f, 0xc4, 0x54, 0x04, 0x2c, - 0x54, 0x59, 0xbf, 0xbd, 0xcc, 0xfa, 0xf8, 0xf9, 0xd7, 0x45, 0x82, 0x5b, 0xd7, 0x91, 0x12, 0x4c, - 0x46, 0x50, 0xd7, 0xf9, 0x97, 0x05, 0x33, 0x13, 0x56, 0x6e, 0x62, 0x55, 0x6f, 0x4f, 0x05, 0x4a, - 0xa8, 0xfd, 0xe7, 0x06, 0xdc, 0x3d, 0xc4, 0x79, 0xe0, 0x61, 0xdf, 0x13, 0xc1, 0x3c, 0xa3, 0x66, - 0x3b, 0xf3, 0x0a, 0x5b, 0x4f, 0xe0, 0xd6, 0x14, 0xe7, 0x63, 0x4c, 0x02, 0xe9, 0x63, 0x67, 0xf0, - 0xe8, 0xc5, 0xcb, 0xd6, 0xc3, 0xff, 0x3a, 0x17, 0x1e, 0x8b, 0xb1, 0x2b, 0x16, 0x11, 0x72, 0xe7, - 0x10, 0xe7, 0xc3, 0xa7, 0xdf, 0xb8, 0xe6, 0x14, 0xe7, 0xc3, 0x24, 0x48, 0xf5, 0x68, 0x14, 0x49, - 0xbd, 0x9d, 0xff, 0xa5, 0xd7, 0x8f, 0x22, 0xa9, 0x47, 0xa3, 0x28, 0xd5, 0x5b, 0x7b, 0x4e, 0xea, - 0xaf, 0x7d, 0x4e, 0xf6, 0x6f, 0x70, 0x4e, 0x1a, 0x60, 0x5d, 0xae, 0x2b, 0x8f, 0x58, 0xc8, 0xd1, - 0x8e, 0x61, 0xef, 0x71, 0x46, 0x3f, 0x12, 0x54, 0x24, 0x5c, 0x17, 0xfc, 0x7b, 0xd8, 0xd6, 0x6b, - 0xa6, 0xa5, 0x90, 0x45, 0x1f, 0x1c, 0xbc, 0x78, 0xd9, 0x7a, 0x74, 0x83, 0x52, 0x28, 0xe5, 0xb4, - 0x1c, 0xa0, 0xd4, 0x86, 0x49, 0x60, 0xff, 0x00, 0xf5, 0x95, 0x35, 0xb3, 0x64, 0xc8, 0x3d, 0xd8, - 0x9a, 0x51, 0x2e, 0xc6, 0x1c, 0x31, 0x94, 0x4b, 0xbe, 0xe9, 0x6e, 0xa6, 0xc0, 0x11, 0x62, 0x48, - 0xee, 0x83, 0xc9, 0x25, 0xdd, 0xda, 0x90, 0xde, 0xab, 0xb9, 0x77, 0xa5, 0xa2, 0xc2, 0x76, 0x15, - 0x2a, 0x25, 0x2f, 0xf6, 0x3f, 0x1b, 0x60, 0x66, 0x08, 0x79, 0x08, 0xbb, 0xda, 0x96, 0x12, 0x33, - 0xa4, 0x18, 0x38, 0x69, 0x47, 0x72, 0xa9, 0x40, 0xee, 0x56, 0xfc, 0x62, 0x72, 0xe4, 0x3e, 0x54, - 0x69, 0x5a, 0x37, 0x1c, 0x2b, 0x9c, 0x5b, 0x6f, 0xb5, 0x8d, 0x4e, 0xc5, 0xdd, 0xcd, 0x60, 0x65, - 0x85, 0x13, 0x1b, 0xcc, 0x44, 0x36, 0x0f, 0x75, 0xa1, 0x8a, 0x9a, 0x2a, 0x42, 0xde, 0x83, 0xcd, - 0xa9, 0xba, 0xb1, 0xea, 0x10, 0x14, 0x59, 0x79, 0x8c, 0x7c, 0x04, 0xdb, 0x34, 0xdf, 0x2c, 0x6e, - 0xb5, 0x2e, 0x51, 0x8b, 0x61, 0xf2, 0x05, 0xec, 0x15, 0x86, 0x63, 0xea, 0x79, 0x18, 0x09, 0x9c, - 0x5a, 0xed, 0x4b, 0xd3, 0xee, 0x14, 0x78, 0x7d, 0x45, 0x23, 0x0f, 0x80, 0x78, 0x2c, 0x0c, 0xd1, - 0x13, 0x38, 0x5d, 0x9a, 0x7c, 0x5f, 0x9a, 0xbc, 0x9d, 0x47, 0x72, 0x9f, 0x1f, 0xc2, 0x12, 0x1c, - 0x4f, 0x62, 0x76, 0x8a, 0x31, 0xb7, 0x3e, 0x90, 0xec, 0x5a, 0x1e, 0x18, 0x64, 0x78, 0xef, 0xa7, - 0x0d, 0x30, 0x5d, 0xf9, 0xc8, 0x90, 0xcf, 0xa0, 0x52, 0xda, 0x76, 0xb2, 0xba, 0x83, 0x8d, 0x7d, - 0x27, 0x7b, 0x87, 0x1c, 0xfd, 0xc2, 0x38, 0xc3, 0xf4, 0x1d, 0xea, 0x18, 0xe4, 0x00, 0xcc, 0xac, - 0x31, 0x93, 0xba, 0xa3, 0x5e, 0xb0, 0x52, 0xa3, 0x7e, 0xc5, 0xd4, 0xaf, 0x60, 0x2b, 0x6f, 0xf4, - 0xc4, 0xd2, 0xb3, 0x57, 0x7b, 0x7f, 0xe3, 0xae, 0x8e, 0xac, 0x74, 0xd4, 0x8f, 0x0d, 0x32, 0x82, - 0x4d, 0x75, 0x73, 0x90, 0xb4, 0x72, 0xda, 0xfa, 0x4e, 0xd5, 0x68, 0x5f, 0x4d, 0xc8, 0x4e, 0x79, - 0xef, 0x67, 0x03, 0x2a, 0x59, 0x49, 0x46, 0x34, 0xa4, 0x3e, 0xc6, 0xe4, 0xdb, 0xd5, 0xca, 0xbc, - 0xa3, 0x45, 0xd6, 0xdd, 0xcd, 0xc6, 0xbb, 0x57, 0x44, 0xd5, 0x2d, 0xea, 0xc1, 0xd6, 0x63, 0x14, - 0x4a, 0x29, 0x2f, 0x57, 0x59, 0x62, 0xb7, 0x0c, 0x0f, 0x6a, 0xbf, 0x5d, 0x34, 0x8d, 0x3f, 0x2e, - 0x9a, 0xc6, 0x5f, 0x17, 0x4d, 0xe3, 0x97, 0xbf, 0x9b, 0x6f, 0x4c, 0x4c, 0x59, 0xc8, 0x4f, 0xfe, - 0x0d, 0x00, 0x00, 0xff, 0xff, 0x68, 0x58, 0x58, 0x45, 0x5c, 0x08, 0x00, 0x00, + // 813 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0x67, 0x03, 0xda, 0xc6, 0x2f, 0x71, 0xec, 0x4e, 0xe3, 0x74, 0x71, 0x69, 0x62, 0xed, 0x81, + 0x9a, 0x3f, 0x5d, 0x53, 0xa3, 0x0a, 0x15, 0x04, 0xc2, 0x21, 0x51, 0x55, 0x09, 0x57, 0x68, 0x93, + 0x1e, 0x91, 0x35, 0xde, 0x7d, 0xdd, 0xac, 0xe2, 0xec, 0x2c, 0x3b, 0xb3, 0x4e, 0xfd, 0x2d, 0xe0, + 0xc6, 0x87, 0xe0, 0x83, 0x70, 0xe0, 0xc0, 0x89, 0x03, 0x07, 0x84, 0xc2, 0xa7, 0xe0, 0x86, 0x3c, + 0x7f, 0xd6, 0xbb, 0x8e, 0x13, 0x02, 0x3d, 0x79, 0xe7, 0xf7, 0x7e, 0xef, 0xe7, 0xdf, 0x7b, 0x33, + 0xf3, 0x06, 0x3e, 0x89, 0x62, 0x71, 0x92, 0x8f, 0xbd, 0x80, 0x9d, 0xf5, 0x8e, 0x4f, 0xf0, 0xf8, + 0x24, 0x4e, 0x22, 0xfe, 0x1c, 0xc5, 0x39, 0xcb, 0x4e, 0x7b, 0x42, 0x24, 0x3d, 0x9a, 0xc6, 0xbd, + 0x8c, 0xe5, 0x02, 0x33, 0xfd, 0xe3, 0xa5, 0x19, 0x13, 0x8c, 0xd8, 0x6a, 0xd5, 0xbe, 0x17, 0x31, + 0x16, 0x4d, 0xb0, 0x27, 0xd1, 0x71, 0xfe, 0xb2, 0x87, 0x67, 0xa9, 0x98, 0x29, 0x52, 0xfb, 0x61, + 0x49, 0x3d, 0x62, 0x11, 0x5b, 0xb0, 0xe6, 0x2b, 0xb9, 0x90, 0x5f, 0x2b, 0xe8, 0x57, 0x9a, 0xa1, + 0x69, 0xac, 0xe9, 0x9f, 0xdd, 0x84, 0x2e, 0xa9, 0x01, 0x9b, 0x14, 0x1f, 0x3a, 0xf9, 0xc9, 0x4d, + 0x92, 0x23, 0x2a, 0xf0, 0x9c, 0xce, 0xcc, 0xaf, 0x4a, 0x75, 0x09, 0x34, 0x8f, 0xf2, 0x31, 0x0f, + 0xb2, 0x78, 0x8c, 0x3e, 0x7e, 0x97, 0x23, 0x17, 0xee, 0x4f, 0x16, 0xd4, 0x5f, 0xa4, 0x93, 0x38, + 0x39, 0x1d, 0x22, 0xe7, 0x34, 0x42, 0xe2, 0xc0, 0xad, 0x94, 0xce, 0x26, 0x8c, 0x86, 0x8e, 0xd5, + 0xb1, 0xba, 0x9b, 0xbe, 0x59, 0x92, 0x01, 0xdc, 0x36, 0x66, 0x46, 0x67, 0x28, 0x68, 0x48, 0x05, + 0x75, 0x36, 0x3a, 0x56, 0x77, 0xa3, 0xbf, 0xed, 0x15, 0x36, 0xfd, 0x57, 0x43, 0x1d, 0xf3, 0x9b, + 0x06, 0x34, 0x08, 0xf9, 0x02, 0x9a, 0xda, 0xd3, 0x42, 0x61, 0x53, 0x2a, 0xdc, 0xf1, 0x8c, 0xd9, + 0x92, 0x40, 0x43, 0x63, 0x06, 0x70, 0x7f, 0xb1, 0xa0, 0x71, 0xc0, 0xce, 0x93, 0x9b, 0x19, 0xfe, + 0x06, 0x76, 0x0a, 0xc3, 0x01, 0x4b, 0x5e, 0xc6, 0x51, 0x9e, 0x51, 0x11, 0xb3, 0x44, 0xbb, 0x7e, + 0x7b, 0xe1, 0xfa, 0xf8, 0xd5, 0x57, 0x65, 0x82, 0xdf, 0x32, 0x91, 0x0a, 0x4c, 0x86, 0xd0, 0x32, + 0xfe, 0xab, 0x82, 0xaa, 0x08, 0xa7, 0x28, 0x62, 0x59, 0x6f, 0x5b, 0x07, 0x2a, 0xa8, 0xfb, 0xdb, + 0x1a, 0xdc, 0x3d, 0xc0, 0x69, 0x1c, 0xe0, 0x20, 0x10, 0xf1, 0x54, 0x51, 0xd5, 0xce, 0x5c, 0x53, + 0xd6, 0x73, 0xb8, 0x15, 0xe2, 0x74, 0x84, 0x79, 0x2c, 0xeb, 0xd8, 0xdc, 0x7f, 0xfc, 0xfb, 0x1f, + 0x7b, 0x8f, 0xfe, 0xed, 0x5c, 0x04, 0x2c, 0xc3, 0x9e, 0x98, 0xa5, 0xc8, 0xbd, 0x03, 0x9c, 0x1e, + 0xbe, 0x78, 0xe6, 0xdb, 0x21, 0x4e, 0x0f, 0xf3, 0x78, 0xae, 0x47, 0xd3, 0x54, 0xea, 0x6d, 0xfe, + 0x2f, 0xbd, 0x41, 0x9a, 0x4a, 0x3d, 0x9a, 0xa6, 0x73, 0xbd, 0x95, 0xe7, 0xa4, 0xf5, 0xda, 0xe7, + 0x64, 0xe7, 0x3f, 0x9c, 0x93, 0x36, 0x38, 0x97, 0xfb, 0xca, 0x53, 0x96, 0x70, 0x74, 0x1f, 0xc3, + 0xf6, 0x53, 0x45, 0x3f, 0x12, 0x54, 0xe4, 0xdc, 0x34, 0xfc, 0x3e, 0x80, 0xf9, 0xcf, 0x58, 0xf5, + 0xbc, 0xe6, 0xd7, 0x34, 0xf2, 0x2c, 0x74, 0xbf, 0x85, 0xd6, 0x52, 0x9a, 0xd2, 0x23, 0xf7, 0xa0, + 0x36, 0xa1, 0x5c, 0x8c, 0x38, 0x62, 0x22, 0xd3, 0xde, 0xf4, 0xd7, 0xe7, 0xc0, 0x11, 0x62, 0x42, + 0x1e, 0x80, 0xcd, 0x25, 0xdd, 0x59, 0x93, 0xf6, 0x1b, 0x85, 0x7d, 0xad, 0xa2, 0xc3, 0x6e, 0x03, + 0xea, 0x15, 0x3b, 0xee, 0xdf, 0x6b, 0x60, 0x2b, 0x84, 0x3c, 0x82, 0x2d, 0xe3, 0x4c, 0x8b, 0x59, + 0x52, 0x0c, 0xbc, 0xf9, 0x50, 0xf1, 0xa9, 0x40, 0xee, 0xd7, 0xa3, 0xb2, 0x39, 0xf2, 0x00, 0x1a, + 0x74, 0x5e, 0x3a, 0x8e, 0x34, 0xce, 0x9d, 0xb7, 0x3a, 0x56, 0xb7, 0xee, 0x6f, 0x29, 0x58, 0x97, + 0xc2, 0x89, 0x0b, 0x76, 0x2e, 0xef, 0xbf, 0xbe, 0x13, 0x65, 0x4d, 0x1d, 0x21, 0xef, 0xc2, 0x7a, + 0xa8, 0x2f, 0x9d, 0xde, 0xc7, 0x32, 0xab, 0x88, 0x91, 0x0f, 0x61, 0x83, 0x16, 0xfd, 0xe6, 0xce, + 0xde, 0x25, 0x6a, 0x39, 0x4c, 0x3e, 0x87, 0xed, 0xd2, 0x72, 0x44, 0x83, 0x00, 0x53, 0x81, 0xa1, + 0xd3, 0xb9, 0x94, 0x76, 0xa7, 0xc4, 0x1b, 0x68, 0x1a, 0x79, 0x08, 0x24, 0x60, 0x49, 0x82, 0x81, + 0xc0, 0x70, 0x51, 0xe4, 0x7b, 0xb2, 0xc8, 0xdb, 0x45, 0xa4, 0xa8, 0xf3, 0x03, 0x58, 0x80, 0xa3, + 0x71, 0xc6, 0x4e, 0x31, 0xe3, 0xce, 0xfb, 0x92, 0xdd, 0x2c, 0x02, 0xfb, 0x0a, 0xef, 0x7f, 0xbf, + 0x06, 0xb6, 0x2f, 0xdf, 0x09, 0xf2, 0x29, 0xd4, 0x2b, 0xdb, 0x4e, 0x96, 0x77, 0xb0, 0xbd, 0xe3, + 0xa9, 0xa7, 0xc4, 0x33, 0x8f, 0x84, 0x77, 0x38, 0x7f, 0x4a, 0xba, 0x16, 0x79, 0x02, 0xb6, 0x9a, + 0xad, 0xa4, 0xe5, 0xe9, 0x47, 0xa8, 0x32, 0x6b, 0xaf, 0x49, 0xfd, 0x12, 0x6a, 0xc5, 0xac, 0x26, + 0x8e, 0xc9, 0x5e, 0x1e, 0xdf, 0xed, 0xbb, 0x26, 0xb2, 0x34, 0x14, 0x3f, 0xb2, 0xc8, 0x10, 0xd6, + 0xf5, 0xe1, 0x47, 0xb2, 0x57, 0xd0, 0x56, 0x0f, 0x9b, 0x76, 0xe7, 0x6a, 0x82, 0x3a, 0xe5, 0xfd, + 0x1f, 0x2c, 0xa8, 0xab, 0x96, 0x0c, 0x69, 0x42, 0x23, 0xcc, 0xc8, 0xd7, 0xcb, 0x9d, 0x79, 0xc7, + 0x88, 0xac, 0xba, 0x5e, 0xed, 0xfb, 0x57, 0x44, 0xf5, 0x2d, 0xea, 0x43, 0xed, 0x29, 0x0a, 0xad, + 0x54, 0xb4, 0xab, 0x2a, 0xb1, 0x55, 0x85, 0xf7, 0x9b, 0x3f, 0x5f, 0xec, 0x5a, 0xbf, 0x5e, 0xec, + 0x5a, 0x7f, 0x5e, 0xec, 0x5a, 0x3f, 0xfe, 0xb5, 0xfb, 0xc6, 0xd8, 0x96, 0x8d, 0xfc, 0xf8, 0x9f, + 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x16, 0x0d, 0xe5, 0x1f, 0x08, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index c33ed19e2..3060c43d7 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -60,7 +60,7 @@ service Router { // message GatewayStatusRequest is used to request the status of a gateway from // this Router message GatewayStatusRequest { - bytes gateway_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.GatewayEUI"]; + string gateway_id = 1; } message GatewayStatusResponse { diff --git a/core/broker/activation.go b/core/broker/activation.go index b9682d297..3bd0d6f52 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -27,9 +27,9 @@ type challengeResponseWithHandler struct { func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { ctx := b.Ctx.WithFields(log.Fields{ - "GatewayEUI": *activation.GatewayMetadata.GatewayEui, - "AppEUI": *activation.AppEui, - "DevEUI": *activation.DevEui, + "GatewayID": activation.GatewayMetadata.GatewayId, + "AppEUI": *activation.AppEui, + "DevEUI": *activation.DevEui, }) var err error start := time.Now() diff --git a/core/broker/activation_test.go b/core/broker/activation_test.go index 175536643..ee8475f15 100644 --- a/core/broker/activation_test.go +++ b/core/broker/activation_test.go @@ -20,7 +20,7 @@ import ( func TestHandleActivation(t *testing.T) { a := New(t) - gtwEUI := types.GatewayEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + gtwID := "eui-0102030405060708" devEUI := types.DevEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) appEUI := types.AppEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) @@ -32,7 +32,7 @@ func TestHandleActivation(t *testing.T) { AppId: "appid", DevId: "devid", GatewayMetadata: []*gateway.RxMetadata{ - &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, }, ProtocolMetadata: &protocol.RxMetadata{}, }, nil) @@ -42,7 +42,7 @@ func TestHandleActivation(t *testing.T) { Payload: []byte{}, DevEui: &devEUI, AppEui: &appEUI, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{}, }) a.So(err, ShouldNotBeNil) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index d150d863e..a1d6a3d78 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -25,7 +25,7 @@ import ( const maxFCntGap = 16384 func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { - ctx := b.Ctx.WithField("GatewayEUI", *uplink.GatewayMetadata.GatewayEui) + ctx := b.Ctx.WithField("GatewayID", uplink.GatewayMetadata.GatewayId) var err error start := time.Now() defer func() { diff --git a/core/broker/uplink_test.go b/core/broker/uplink_test.go index 1c209402e..fc4fea464 100644 --- a/core/broker/uplink_test.go +++ b/core/broker/uplink_test.go @@ -26,12 +26,12 @@ func TestHandleUplink(t *testing.T) { b := getTestBroker(t) - gtwEUI := types.GatewayEUI([8]byte{0, 1, 2, 3, 4, 5, 6, 7}) + gtwID := "eui-0102030405060708" // Invalid Payload err := b.HandleUplink(&pb.UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03}, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{}, }) a.So(err, ShouldNotBeNil) @@ -58,7 +58,7 @@ func TestHandleUplink(t *testing.T) { }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) @@ -96,7 +96,7 @@ func TestHandleUplink(t *testing.T) { b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) @@ -109,7 +109,7 @@ func TestHandleUplink(t *testing.T) { b.ns.EXPECT().GetDevices(gomock.Any(), gomock.Any()).Return(nsResponse, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldHaveSameTypeAs, &errors.ErrNotFound{}) @@ -126,7 +126,7 @@ func TestHandleUplink(t *testing.T) { }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) @@ -144,7 +144,7 @@ func TestHandleUplink(t *testing.T) { }, nil) err = b.HandleUplink(&pb.UplinkMessage{ Payload: bytes, - GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayEui: >wEUI}, + GatewayMetadata: &gateway.RxMetadata{Snr: 1.2, GatewayId: gtwID}, ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}}, }) a.So(err, ShouldBeNil) diff --git a/core/handler/convert_metadata.go b/core/handler/convert_metadata.go index 9ed256090..910b137d3 100644 --- a/core/handler/convert_metadata.go +++ b/core/handler/convert_metadata.go @@ -32,7 +32,7 @@ func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.Deduplicat } gatewayMetadata := mqtt.GatewayMetadata{ - EUI: *in.GatewayEui, + GtwID: in.GatewayId, Timestamp: in.Timestamp, Time: mqtt.BuildTime(in.Time), Channel: in.Channel, diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go index 8ff55cbc1..8ed35faba 100644 --- a/core/handler/convert_metadata_test.go +++ b/core/handler/convert_metadata_test.go @@ -12,7 +12,6 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -30,13 +29,13 @@ func TestConvertMetadata(t *testing.T) { err := h.ConvertMetadata(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) - gtwEUI := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + gtwID := "eui-0102030405060708" ttnUp.GatewayMetadata = []*pb_gateway.RxMetadata{ &pb_gateway.RxMetadata{ - GatewayEui: >wEUI, + GatewayId: gtwID, }, &pb_gateway.RxMetadata{ - GatewayEui: >wEUI, + GatewayId: gtwID, }, } diff --git a/core/handler/types.go b/core/handler/types.go index d468545a5..02ad2c60a 100644 --- a/core/handler/types.go +++ b/core/handler/types.go @@ -17,4 +17,5 @@ type UplinkProcessor func(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplink // DownlinkProcessor processes an application-layer downlink message to a downlik protobuf type DownlinkProcessor func(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error +// ErrNotNeeded indicates that the processing of a message should be aborted var ErrNotNeeded = errors.New("Further processing not needed") diff --git a/core/handler/uplink.go b/core/handler/uplink.go index f69085b62..476981c76 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -12,6 +12,7 @@ import ( "github.com/apex/log" ) +// ResponseDeadline indicates how long var ResponseDeadline = 100 * time.Millisecond func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error { diff --git a/core/router/activation.go b/core/router/activation.go index 7a7fb2ccd..65f026638 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -12,16 +12,15 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) -func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { +func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { ctx := r.Ctx.WithFields(log.Fields{ - "GatewayEUI": gatewayEUI, - "AppEUI": *activation.AppEui, - "DevEUI": *activation.DevEui, + "GatewayID": gatewayID, + "AppEUI": *activation.AppEui, + "DevEUI": *activation.DevEui, }) var err error start := time.Now() @@ -33,7 +32,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De } }() - gateway := r.getGateway(gatewayEUI) + gateway := r.getGateway(gatewayID) gateway.LastSeen = time.Now() uplink := &pb.UplinkMessage{ @@ -47,7 +46,7 @@ func (r *router) HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.De gateway.Utilization.AddRx(uplink) if !gateway.Schedule.IsActive() { - return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) + return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayID)) } downlinkOptions := r.buildDownlinkOptions(uplink, true, gateway) diff --git a/core/router/activation_test.go b/core/router/activation_test.go index dd234a4bf..168d805cc 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -17,14 +17,14 @@ import ( func TestHandleActivation(t *testing.T) { a := New(t) - gatewayEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + gtwID := "eui-0102030405060708" r := &router{ Component: &core.Component{ Ctx: GetLogger(t, "TestHandleActivation"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{ - gatewayEUI: newReferenceGateway(t, "EU_863_870"), + gateways: map[string]*gateway.Gateway{ + gtwID: newReferenceGateway(t, "EU_863_870"), }, } @@ -39,12 +39,12 @@ func TestHandleActivation(t *testing.T) { AppEui: &appEUI, DevEui: &devEUI, } - gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} - res, err := r.HandleActivation(gtwEUI, activation) + res, err := r.HandleActivation(gtwID, activation) a.So(res, ShouldBeNil) a.So(err, ShouldNotBeNil) - utilization := r.getGateway(gtwEUI).Utilization + + utilization := r.getGateway(gtwID).Utilization utilization.Tick() rx, _ := utilization.Get() a.So(rx, ShouldBeGreaterThan, 0) diff --git a/core/router/downlink.go b/core/router/downlink.go index 704dcaef9..2a2bcd374 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -21,12 +21,12 @@ import ( lora "github.com/brocaar/lorawan/band" ) -func (r *router) SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.DownlinkMessage, error) { +func (r *router) SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage, error) { ctx := r.Ctx.WithFields(log.Fields{ - "GatewayEUI": gatewayEUI, + "GatewayID": gatewayID, }) - gateway := r.getGateway(gatewayEUI) + gateway := r.getGateway(gatewayID) if fromSchedule := gateway.Schedule.Subscribe(); fromSchedule != nil { toGateway := make(chan *pb.DownlinkMessage) go func() { @@ -41,21 +41,20 @@ func (r *router) SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.Down }() return toGateway, nil } - return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayEUI)) + return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayID)) } -func (r *router) UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error { - r.getGateway(gatewayEUI).Schedule.Stop() +func (r *router) UnsubscribeDownlink(gatewayID string) error { + r.getGateway(gatewayID).Schedule.Stop() return nil } func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { option := downlink.DownlinkOption ctx := r.Ctx.WithFields(log.Fields{ - "GatewayEUI": *option.GatewayEui, + "GatewayID": option.GatewayId, }) - - gateway := r.getGateway(*option.GatewayEui) + gateway := r.getGateway(option.GatewayId) downlinkMessage := &pb.DownlinkMessage{ Payload: downlink.Payload, @@ -186,7 +185,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo delay = band.JoinAcceptDelay2 } rx2 := &pb_broker.DownlinkOption{ - GatewayEui: &gateway.EUI, + GatewayId: gateway.ID, ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa DataRate: dataRate.String(), // This is default @@ -216,7 +215,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo delay = band.JoinAcceptDelay1 } rx1 := &pb_broker.DownlinkOption{ - GatewayEui: &gateway.EUI, + GatewayId: gateway.ID, ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa DataRate: dataRate.String(), // This is default diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index ce41f745b..a0c75868b 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -16,7 +16,6 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -45,15 +44,15 @@ func TestHandleDownlink(t *testing.T) { Component: &core.Component{ Ctx: GetLogger(t, "TestHandleDownlink"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, + gateways: map[string]*gateway.Gateway{}, } - eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} - id, _ := r.getGateway(eui).Schedule.GetOption(0, 10*1000) + gtwID := "eui-0102030405060708" + id, _ := r.getGateway(gtwID).Schedule.GetOption(0, 10*1000) err := r.HandleDownlink(&pb_broker.DownlinkMessage{ Payload: []byte{}, DownlinkOption: &pb_broker.DownlinkOption{ - GatewayEui: &eui, + GatewayId: gtwID, Identifier: id, ProtocolConfig: &pb_protocol.TxConfiguration{}, GatewayConfig: &pb_gateway.TxConfiguration{}, @@ -70,16 +69,16 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { Component: &core.Component{ Ctx: GetLogger(t, "TestSubscribeUnsubscribeDownlink"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, + gateways: map[string]*gateway.Gateway{}, } - eui := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + gtwID := "eui-0102030405060708" gateway.Deadline = 1 * time.Millisecond - gtw := r.getGateway(eui) + gtw := r.getGateway(gtwID) gtw.Schedule.Sync(0) id, _ := gtw.Schedule.GetOption(5000, 10*1000) - ch, err := r.SubscribeDownlink(eui) + ch, err := r.SubscribeDownlink(gtwID) a.So(err, ShouldBeNil) var wg sync.WaitGroup @@ -97,7 +96,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { r.HandleDownlink(&pb_broker.DownlinkMessage{ Payload: []byte{0x02}, DownlinkOption: &pb_broker.DownlinkOption{ - GatewayEui: &eui, + GatewayId: gtwID, Identifier: id, ProtocolConfig: &pb_protocol.TxConfiguration{}, GatewayConfig: &pb_gateway.TxConfiguration{}, @@ -107,7 +106,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { // Wait for the downlink to arrive <-time.After(10 * time.Millisecond) - err = r.UnsubscribeDownlink(eui) + err = r.UnsubscribeDownlink(gtwID) a.So(err, ShouldBeNil) wg.Wait() @@ -120,7 +119,7 @@ func TestUplinkBuildDownlinkOptions(t *testing.T) { // If something is incorrect, it just returns an empty list up := &pb.UplinkMessage{} - gtw := gateway.NewGateway(GetLogger(t, "TestUplinkBuildDownlinkOptions"), types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) + gtw := gateway.NewGateway(GetLogger(t, "TestUplinkBuildDownlinkOptions"), "eui-0102030405060708") options := r.buildDownlinkOptions(up, false, gtw) a.So(options, ShouldBeEmpty) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index c3d1eadf6..aa39584cc 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -6,23 +6,22 @@ package gateway import ( "time" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) // NewGateway creates a new in-memory Gateway structure -func NewGateway(ctx log.Interface, eui types.GatewayEUI) *Gateway { +func NewGateway(ctx log.Interface, id string) *Gateway { return &Gateway{ - EUI: eui, + ID: id, Status: NewStatusStore(), Utilization: NewUtilization(), - Schedule: NewSchedule(ctx.WithField("GatewayEUI", eui)), + Schedule: NewSchedule(ctx.WithField("GatewayID", id)), } } // Gateway contains the state of a gateway type Gateway struct { - EUI types.GatewayEUI + ID string Status StatusStore Utilization Utilization Schedule Schedule diff --git a/core/router/gateway/gateway_test.go b/core/router/gateway/gateway_test.go index 87427fa46..8593e2cc2 100644 --- a/core/router/gateway/gateway_test.go +++ b/core/router/gateway/gateway_test.go @@ -6,13 +6,12 @@ package gateway import ( "testing" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestNewGateway(t *testing.T) { a := New(t) - gtw := NewGateway(GetLogger(t, "TestNewGateway"), types.GatewayEUI{1, 2, 3, 4, 5, 6, 7}) + gtw := NewGateway(GetLogger(t, "TestNewGateway"), "eui-0102030405060708") a.So(gtw, ShouldNotBeNil) } diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index bc9e49b4a..9e59923b8 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -8,7 +8,6 @@ import ( "sync" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - "github.com/TheThingsNetwork/ttn/core/types" "gopkg.in/redis.v3" ) @@ -47,10 +46,10 @@ func (s *statusStore) Get() (*pb_gateway.Status, error) { } // NewRedisStatusStore creates a new Redis-based status store -func NewRedisStatusStore(client *redis.Client, eui types.GatewayEUI) StatusStore { +func NewRedisStatusStore(client *redis.Client, id string) StatusStore { return &redisStatusStore{ client: client, - key: fmt.Sprintf("router:gateway:%s", eui), + key: fmt.Sprintf("router:gateway:%s", id), } } diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go index 6ba59bfc6..336c26797 100644 --- a/core/router/gateway/status_test.go +++ b/core/router/gateway/status_test.go @@ -9,7 +9,6 @@ import ( "testing" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" "gopkg.in/redis.v3" @@ -30,8 +29,8 @@ func getRedisClient() *redis.Client { func TestNewGatewayStatusStore(t *testing.T) { a := New(t) client := getRedisClient() - eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 0} - store := NewRedisStatusStore(client, eui) + id := "0000000000000000" + store := NewRedisStatusStore(client, id) a.So(store, ShouldNotBeNil) store = NewStatusStore() a.So(store, ShouldNotBeNil) @@ -61,9 +60,9 @@ func TestStatusGetUpsert(t *testing.T) { func TestRedisStatusGetUpsert(t *testing.T) { a := New(t) - eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 1} + id := "0000000000000001" client := getRedisClient() - store := NewRedisStatusStore(client, eui) + store := NewRedisStatusStore(client, id) // Cleanup before and after client.Del(store.(*redisStatusStore).key) diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index 48321b870..740cfb663 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -7,11 +7,10 @@ import ( "time" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - "github.com/TheThingsNetwork/ttn/core/types" ) -func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error { - ctx := r.Ctx.WithField("GatewayEUI", gatewayEUI) +func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) error { + ctx := r.Ctx.WithField("GatewayID", gatewayID) var err error start := time.Now() defer func() { @@ -22,7 +21,7 @@ func (r *router) HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gat } }() - gateway := r.getGateway(gatewayEUI) + gateway := r.getGateway(gatewayID) gateway.LastSeen = time.Now() err = gateway.Status.Update(status) if err != nil { diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index b9b77b48a..2356aa825 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -9,29 +9,28 @@ import ( pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestHandleGatewayStatus(t *testing.T) { a := New(t) - eui := types.GatewayEUI{0, 0, 0, 0, 0, 0, 0, 2} + gtwID := "eui-0102030405060708" router := &router{ Component: &core.Component{ Ctx: GetLogger(t, "TestHandleGatewayStatus"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, + gateways: map[string]*gateway.Gateway{}, } // Handle statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} - err := router.HandleGatewayStatus(eui, statusMessage) + err := router.HandleGatewayStatus(gtwID, statusMessage) a.So(err, ShouldBeNil) // Check storage - status, err := router.getGateway(eui).Status.Get() + status, err := router.getGateway(gtwID).Status.Get() a.So(err, ShouldBeNil) a.So(status, ShouldNotBeNil) a.So(*status, ShouldResemble, *statusMessage) diff --git a/core/router/manager_server.go b/core/router/manager_server.go index f067c42f2..2c66f755d 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -17,14 +17,14 @@ type routerManager struct { var errf = grpc.Errorf func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusRequest) (*pb.GatewayStatusResponse, error) { - if in.GatewayEui == nil { - return nil, errf(codes.InvalidArgument, "GatewayEUI is required") + if in.GatewayId == "" { + return nil, errf(codes.InvalidArgument, "GatewayID is required") } _, err := r.router.ValidateTTNAuthContext(ctx) if err != nil { return nil, errf(codes.PermissionDenied, "No access") } - gtw := r.router.getGateway(*in.GatewayEui) + gtw := r.router.getGateway(in.GatewayId) status, err := gtw.Status.Get() if err != nil { return nil, err diff --git a/core/router/router.go b/core/router/router.go index bb2a325ba..db648ceee 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -14,7 +14,6 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" ) @@ -24,17 +23,17 @@ type Router interface { core.ManagementInterface // Handle a status message from a gateway - HandleGatewayStatus(gatewayEUI types.GatewayEUI, status *pb_gateway.Status) error + HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) error // Handle an uplink message from a gateway - HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error + HandleUplink(gatewayID string, uplink *pb.UplinkMessage) error // Handle a downlink message HandleDownlink(message *pb_broker.DownlinkMessage) error // Subscribe to downlink messages - SubscribeDownlink(gatewayEUI types.GatewayEUI) (<-chan *pb.DownlinkMessage, error) + SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage, error) // Unsubscribe from downlink messages - UnsubscribeDownlink(gatewayEUI types.GatewayEUI) error + UnsubscribeDownlink(gatewayID string) error // Handle a device activation - HandleActivation(gatewayEUI types.GatewayEUI, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) + HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) } type broker struct { @@ -46,14 +45,14 @@ type broker struct { // NewRouter creates a new Router func NewRouter() Router { return &router{ - gateways: make(map[types.GatewayEUI]*gateway.Gateway), + gateways: make(map[string]*gateway.Gateway), brokers: make(map[string]*broker), } } type router struct { *core.Component - gateways map[types.GatewayEUI]*gateway.Gateway + gateways map[string]*gateway.Gateway gatewaysLock sync.RWMutex brokers map[string]*broker brokersLock sync.RWMutex @@ -88,10 +87,10 @@ func (r *router) Init(c *core.Component) error { } // getGateway gets or creates a Gateway -func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { +func (r *router) getGateway(id string) *gateway.Gateway { // We're going to be optimistic and guess that the gateway is already active r.gatewaysLock.RLock() - gtw, ok := r.gateways[eui] + gtw, ok := r.gateways[id] r.gatewaysLock.RUnlock() if ok { return gtw @@ -99,10 +98,10 @@ func (r *router) getGateway(eui types.GatewayEUI) *gateway.Gateway { // If it doesn't we still have to lock r.gatewaysLock.Lock() defer r.gatewaysLock.Unlock() - if _, ok := r.gateways[eui]; !ok { - r.gateways[eui] = gateway.NewGateway(r.Ctx, eui) + if _, ok := r.gateways[id]; !ok { + r.gateways[id] = gateway.NewGateway(r.Ctx, id) } - return r.gateways[eui] + return r.gateways[id] } // getBroker gets or creates a broker association and returns the broker diff --git a/core/router/server.go b/core/router/server.go index c8bcccd16..eb29f2c80 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -7,7 +7,6 @@ import ( "io" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" @@ -22,21 +21,19 @@ type routerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining -func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, err error) { +func getGatewayFromMetadata(ctx context.Context) (gatewayID string, err error) { md, ok := metadata.FromContext(ctx) if !ok { err = errors.NewErrInternal("Could not get metadata from context") return } - euiString, ok := md["gateway_eui"] - if !ok || len(euiString) < 1 { - err = errors.NewErrInvalidArgument("Metadata", "gateway_eui missing") - return - } - gatewayEUI, err = types.ParseGatewayEUI(euiString[0]) - if err != nil { + id, ok := md["id"] + if !ok || len(id) < 1 { + err = errors.NewErrInvalidArgument("Metadata", "id missing") return } + gatewayID = id[0] + token, ok := md["token"] if !ok || len(token) < 1 { err = errors.NewErrInvalidArgument("Metadata", "token missing") @@ -53,9 +50,9 @@ func getGatewayFromMetadata(ctx context.Context) (gatewayEUI types.GatewayEUI, e // GatewayStatus implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { - gatewayEUI, err := getGatewayFromMetadata(stream.Context()) + gatewayID, err := getGatewayFromMetadata(stream.Context()) if err != nil { - return err + return errors.BuildGRPCError(err) } for { status, err := stream.Recv() @@ -68,15 +65,15 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if !status.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") } - go r.router.HandleGatewayStatus(gatewayEUI, status) + go r.router.HandleGatewayStatus(gatewayID, status) } } // Uplink implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { - gatewayEUI, err := getGatewayFromMetadata(stream.Context()) + gatewayID, err := getGatewayFromMetadata(stream.Context()) if err != nil { - return err + return errors.BuildGRPCError(err) } for { uplink, err := stream.Recv() @@ -89,21 +86,21 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if !uplink.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Uplink") } - go r.router.HandleUplink(gatewayEUI, uplink) + go r.router.HandleUplink(gatewayID, uplink) } } // Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { - gatewayEUI, err := getGatewayFromMetadata(stream.Context()) + gatewayID, err := getGatewayFromMetadata(stream.Context()) if err != nil { return err } - downlinkChannel, err := r.router.SubscribeDownlink(gatewayEUI) + downlinkChannel, err := r.router.SubscribeDownlink(gatewayID) if err != nil { return err } - defer r.router.UnsubscribeDownlink(gatewayEUI) + defer r.router.UnsubscribeDownlink(gatewayID) for { if downlinkChannel == nil { return nil @@ -121,14 +118,14 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { - gatewayEUI, err := getGatewayFromMetadata(ctx) + gatewayID, err := getGatewayFromMetadata(ctx) if err != nil { return nil, err } if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return r.router.HandleActivation(gatewayEUI, req) + return r.router.HandleActivation(gatewayID, req) } // RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) diff --git a/core/router/uplink.go b/core/router/uplink.go index 371c86abf..95a43d009 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -15,8 +15,8 @@ import ( "github.com/brocaar/lorawan" ) -func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMessage) error { - ctx := r.Ctx.WithField("GatewayEUI", gatewayEUI) +func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) error { + ctx := r.Ctx.WithField("GatewayID", gatewayID) var err error start := time.Now() defer func() { @@ -45,7 +45,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess "DevEUI": devEUI, "AppEUI": appEUI, }).Debug("Handle Uplink as Activation") - _, err := r.HandleActivation(gatewayEUI, &pb.DeviceActivationRequest{ + _, err := r.HandleActivation(gatewayID, &pb.DeviceActivationRequest{ Payload: uplink.Payload, DevEui: &devEUI, AppEui: &appEUI, @@ -80,7 +80,7 @@ func (r *router) HandleUplink(gatewayEUI types.GatewayEUI, uplink *pb.UplinkMess ctx = ctx.WithField("DevAddr", devAddr) - gateway := r.getGateway(gatewayEUI) + gateway := r.getGateway(gatewayID) gateway.LastSeen = time.Now() gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) gateway.Utilization.AddRx(uplink) diff --git a/core/router/uplink_test.go b/core/router/uplink_test.go index 8b302b4a8..34a12956c 100644 --- a/core/router/uplink_test.go +++ b/core/router/uplink_test.go @@ -20,7 +20,7 @@ import ( // newReferenceGateway returns a default gateway func newReferenceGateway(t *testing.T, region string) *gateway.Gateway { - gtw := gateway.NewGateway(GetLogger(t, "ReferenceGateway"), types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7}) + gtw := gateway.NewGateway(GetLogger(t, "ReferenceGateway"), "eui-0102030405060708") gtw.Status.Update(&pb_gateway.Status{ Region: region, }) @@ -29,7 +29,7 @@ func newReferenceGateway(t *testing.T, region string) *gateway.Gateway { // newReferenceUplink returns a default uplink message func newReferenceUplink() *pb.UplinkMessage { - gtwEUI := types.GatewayEUI{1, 2, 3, 4, 5, 6, 7, 8} + gtwID := "eui-0102030405060708" phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ @@ -52,11 +52,11 @@ func newReferenceUplink() *pb.UplinkMessage { Modulation: pb_lorawan.Modulation_LORA, }}}, GatewayMetadata: &pb_gateway.RxMetadata{ - GatewayEui: >wEUI, - Timestamp: 100, - Frequency: 868100000, - Rssi: -25.0, - Snr: 5.0, + GatewayId: gtwID, + Timestamp: 100, + Frequency: 868100000, + Rssi: -25.0, + Snr: 5.0, }, } return up @@ -69,11 +69,11 @@ func TestHandleUplink(t *testing.T) { r.discovery.EXPECT().GetAllBrokersForDevAddr(types.DevAddr([4]byte{1, 2, 3, 4})).Return([]*discovery.Announcement{}, nil) uplink := newReferenceUplink() - gtwEUI := types.GatewayEUI{0, 1, 2, 3, 4, 5, 6, 7} + gtwID := "eui-0102030405060708" - err := r.HandleUplink(gtwEUI, uplink) + err := r.HandleUplink(gtwID, uplink) a.So(err, ShouldBeNil) - utilization := r.getGateway(gtwEUI).Utilization + utilization := r.getGateway(gtwID).Utilization utilization.Tick() rx, _ := utilization.Get() a.So(rx, ShouldBeGreaterThan, 0) diff --git a/core/router/util_test.go b/core/router/util_test.go index faa5b5b95..2a134f572 100644 --- a/core/router/util_test.go +++ b/core/router/util_test.go @@ -9,7 +9,6 @@ import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/golang/mock/gomock" ) @@ -29,7 +28,7 @@ func getTestRouter(t *testing.T) *testRouter { Discovery: discovery, Ctx: GetLogger(t, "TestRouter"), }, - gateways: map[types.GatewayEUI]*gateway.Gateway{}, + gateways: map[string]*gateway.Gateway{}, }, ctrl: ctrl, discovery: discovery, diff --git a/core/types/eui.go b/core/types/eui.go index 38285c5b8..61a9a9717 100644 --- a/core/types/eui.go +++ b/core/types/eui.go @@ -5,8 +5,9 @@ package types import ( "encoding/hex" - "github.com/TheThingsNetwork/ttn/utils/errors" "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" ) // EUI64 is used for AppEUIs and DevEUIs. @@ -18,9 +19,6 @@ type AppEUI EUI64 // DevEUI is a unique identifier for devices. type DevEUI EUI64 -// GatewayEUI is a unique identifier for devices. -type GatewayEUI EUI64 - // ParseEUI64 parses a 64-bit hex-encoded string to an EUI64. func ParseEUI64(input string) (eui EUI64, err error) { bytes, err := ParseHEX(input, 8) @@ -257,85 +255,6 @@ func (eui *DevEUI) Unmarshal(data []byte) error { return eui.UnmarshalBinary(data) } -// ParseGatewayEUI parses a 64-bit hex-encoded string to an GatewayEUI -func ParseGatewayEUI(input string) (eui GatewayEUI, err error) { - eui64, err := ParseEUI64(input) - if err != nil { - return - } - eui = GatewayEUI(eui64) - return -} - -// Bytes returns the GatewayEUI as a byte slice -func (eui GatewayEUI) Bytes() []byte { - return EUI64(eui).Bytes() -} - -// String implements the Stringer interface. -func (eui GatewayEUI) String() string { - return EUI64(eui).String() -} - -// GoString implements the GoStringer interface. -func (eui GatewayEUI) GoString() string { - return eui.String() -} - -// MarshalText implements the TextMarshaler interface. -func (eui GatewayEUI) MarshalText() ([]byte, error) { - return EUI64(eui).MarshalText() -} - -// UnmarshalText implements the TextUnmarshaler interface. -func (eui *GatewayEUI) UnmarshalText(data []byte) error { - e := EUI64(*eui) - err := e.UnmarshalText(data) - if err != nil { - return err - } - *eui = GatewayEUI(e) - return nil -} - -// MarshalBinary implements the BinaryMarshaler interface. -func (eui GatewayEUI) MarshalBinary() ([]byte, error) { - return EUI64(eui).MarshalBinary() -} - -// UnmarshalBinary implements the BinaryUnmarshaler interface. -func (eui *GatewayEUI) UnmarshalBinary(data []byte) error { - e := EUI64(*eui) - err := e.UnmarshalBinary(data) - if err != nil { - return err - } - *eui = GatewayEUI(e) - return nil -} - -// MarshalTo is used by Protobuf -func (eui *GatewayEUI) MarshalTo(b []byte) (int, error) { - copy(b, eui.Bytes()) - return 8, nil -} - -// Size is used by Protobuf -func (eui *GatewayEUI) Size() int { - return 8 -} - -// Marshal implements the Marshaler interface. -func (eui GatewayEUI) Marshal() ([]byte, error) { - return eui.MarshalBinary() -} - -// Unmarshal implements the Unmarshaler interface. -func (eui *GatewayEUI) Unmarshal(data []byte) error { - *eui = [8]byte{} // Reset the receiver - return eui.UnmarshalBinary(data) -} - var emptyEUI64 EUI64 func (eui EUI64) IsEmpty() bool { @@ -349,7 +268,3 @@ func (eui DevEUI) IsEmpty() bool { func (eui AppEUI) IsEmpty() bool { return EUI64(eui).IsEmpty() } - -func (eui GatewayEUI) IsEmpty() bool { - return EUI64(eui).IsEmpty() -} diff --git a/core/types/eui_test.go b/core/types/eui_test.go index 536eccddc..4890acc80 100644 --- a/core/types/eui_test.go +++ b/core/types/eui_test.go @@ -182,61 +182,3 @@ func TestDevEUI(t *testing.T) { a.So(empty.IsEmpty(), ShouldEqual, true) a.So(eui.IsEmpty(), ShouldEqual, false) } - -func TestGatewayEUI(t *testing.T) { - a := New(t) - - // Setup - eui := GatewayEUI{1, 2, 3, 4, 252, 253, 254, 255} - str := "01020304FCFDFEFF" - bin := []byte{0x01, 0x02, 0x03, 0x04, 0xfc, 0xfd, 0xfe, 0xff} - - // Bytes - a.So(eui.Bytes(), ShouldResemble, bin) - - // String - a.So(eui.String(), ShouldEqual, str) - - // MarshalText - mtOut, err := eui.MarshalText() - a.So(err, ShouldBeNil) - a.So(mtOut, ShouldResemble, []byte(str)) - - // MarshalBinary - mbOut, err := eui.MarshalBinary() - a.So(err, ShouldBeNil) - a.So(mbOut, ShouldResemble, bin) - - // Marshal - mOut, err := eui.Marshal() - a.So(err, ShouldBeNil) - a.So(mOut, ShouldResemble, bin) - - // Parse - pOut, err := ParseGatewayEUI(str) - a.So(err, ShouldBeNil) - a.So(pOut, ShouldEqual, eui) - - // UnmarshalText - utOut := &GatewayEUI{} - err = utOut.UnmarshalText([]byte(str)) - a.So(err, ShouldBeNil) - a.So(*utOut, ShouldEqual, eui) - - // UnmarshalBinary - ubOut := &GatewayEUI{} - err = ubOut.UnmarshalBinary(bin) - a.So(err, ShouldBeNil) - a.So(*ubOut, ShouldEqual, eui) - - // Unmarshal - uOut := &GatewayEUI{} - err = uOut.Unmarshal(bin) - a.So(err, ShouldBeNil) - a.So(*uOut, ShouldEqual, eui) - - // IsEmpty - var empty GatewayEUI - a.So(empty.IsEmpty(), ShouldEqual, true) - a.So(eui.IsEmpty(), ShouldEqual, false) -} diff --git a/mqtt/README.md b/mqtt/README.md index fe6d4fdff..86bddadc2 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -24,7 +24,7 @@ This README describes the topics and messages that are used "coding_rate": "4/5", // Coding rate that was used "gateways": [ { - "eui": "0102030405060708", // EUI of the gateway + "id": "ttn-herengracht-ams", // EUI of the gateway "timestamp": 12345, // Timestamp when the gateway received the message "time": "1970-01-01T00:00:00Z", // Time when the gateway received the message - left out when gateway does not have synchronized time "channel": 0, // Channel where the gateway received the message diff --git a/mqtt/types.go b/mqtt/types.go index 7fe4498a8..c09570d8d 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -52,13 +52,13 @@ type LocationMetadata struct { // GatewayMetadata contains metadata for each gateway that received a message type GatewayMetadata struct { - EUI types.GatewayEUI `json:"eui,omitempty"` - Timestamp uint32 `json:"timestamp,omitempty"` - Time JSONTime `json:"time,omitempty"` - Channel uint32 `json:"channel,omitempty"` - RSSI float32 `json:"rssi,omitempty"` - SNR float32 `json:"snr,omitempty"` - RFChain uint32 `json:"rf_chain,omitempty"` + GtwID string `json:"gtw_id,omitempty"` + Timestamp uint32 `json:"timestamp,omitempty"` + Time JSONTime `json:"time,omitempty"` + Channel uint32 `json:"channel,omitempty"` + RSSI float32 `json:"rssi,omitempty"` + SNR float32 `json:"snr,omitempty"` + RFChain uint32 `json:"rf_chain,omitempty"` LocationMetadata } diff --git a/ttnctl/cmd/gateway_status.go b/ttnctl/cmd/gateway_status.go index ff57f7bd5..509d50703 100644 --- a/ttnctl/cmd/gateway_status.go +++ b/ttnctl/cmd/gateway_status.go @@ -7,15 +7,15 @@ import ( "fmt" "time" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/spf13/cobra" ) var gatewayStatusCmd = &cobra.Command{ - Use: "status [GatewayEUI]", + Use: "status [gatewayID]", Short: "Get status of a gateway", Long: `ttnctl gateway status can be used to get status of gateways.`, Run: func(cmd *cobra.Command, args []string) { @@ -24,19 +24,17 @@ var gatewayStatusCmd = &cobra.Command{ return } - euiString := args[0] - ctx = ctx.WithField("Gateway EUI", euiString) - - eui, err := types.ParseGatewayEUI(euiString) - if err != nil { - ctx.WithError(err).Fatal("Invalid Gateway EUI") + gtwID := args[0] + if !api.ValidID(gtwID) { + ctx.Fatal("Invalid Gateway ID") } + ctx = ctx.WithField("GatewayID", gtwID) conn, manager := util.GetRouterManager(ctx) defer conn.Close() resp, err := manager.GatewayStatus(util.GetContext(ctx), &router.GatewayStatusRequest{ - GatewayEui: &eui, + GatewayId: gtwID, }) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get status of gateway.") diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 826793e57..d347cb226 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -65,7 +65,7 @@ var uplinkCmd = &cobra.Command{ md := metadata.Pairs( "token", "token", - "gateway_eui", "0102030405060708", + "id", "eui-0102030405060708", ) gatewayContext := metadata.NewContext(context.Background(), md) @@ -95,7 +95,7 @@ var uplinkCmd = &cobra.Command{ err = uplink.Send(&router.UplinkMessage{ Payload: bytes, - GatewayMetadata: util.GetGatewayMetadata(types.GatewayEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), 868100000), + GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), }) if err != nil { diff --git a/ttnctl/util/uplink_metadata.go b/ttnctl/util/uplink_metadata.go index 39ce1ed6a..824b549b1 100644 --- a/ttnctl/util/uplink_metadata.go +++ b/ttnctl/util/uplink_metadata.go @@ -7,7 +7,6 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/protocol" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core/types" ) // GetProtocolMetadata returns protocol metadata for the given datarate @@ -19,13 +18,13 @@ func GetProtocolMetadata(dataRate string) *protocol.RxMetadata { }}} } -// GetGatewayMetadata returns gateway metadata for the given gateway EUI and frequency -func GetGatewayMetadata(eui types.GatewayEUI, freq uint64) *gateway.RxMetadata { +// GetGatewayMetadata returns gateway metadata for the given gateway ID and frequency +func GetGatewayMetadata(id string, freq uint64) *gateway.RxMetadata { return &gateway.RxMetadata{ - GatewayEui: &eui, - Timestamp: 0, - Frequency: freq, - Rssi: -25.0, - Snr: 5.0, + GatewayId: id, + Timestamp: 0, + Frequency: freq, + Rssi: -25.0, + Snr: 5.0, } } From 408a01f15812f0cea32380c70816ec59e8ce6a91 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Mon, 19 Sep 2016 11:00:50 +0200 Subject: [PATCH 1736/2266] Update placeholders https://github.com/TheThingsIndustries/dashboard/pull/68#issuecomment-247533214 --- ttnctl/cmd/applications_pf_set.go | 35 ++++++++++++++++--------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 508e7939b..8128a05bd 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -62,39 +62,40 @@ The functions are read from the supplied file or from STDIN.`, switch function { case "decoder": fmt.Println(`function Decoder(bytes) { - // Here you can decode the payload into json. - // bytes is of type Buffer. - // todo: return an object + // Decode an uplink message from + // a Buffer of bytes to an object. + return { - payload: bytes, + isLightOn: bytes[0] }; } ########## Write your Decoder here and end with Ctrl+D (EOF):`) app.Decoder = readFunction() case "converter": - fmt.Println(`function Converter(val) { - // Here you can combine the json values into a more meaningful value. - // val is the output of the decoder function. - // todo: return an object - return val; + fmt.Println(`function Converter(decodedObj) { + // Modify the decoded uplink message. + + decodedObj.isLightOn = !!decodedObj.isLightOn; + + return decodedObj; } ########## Write your Converter here and end with Ctrl+D (EOF):`) app.Converter = readFunction() case "validator": - fmt.Println(`function Validator(val) { - // This function defines which values will be propagated. - // val is the output of the converter function. - // todo: return a boolean + fmt.Println(`function Validator(convertedObj) { + // Return false if the decoded and converted uplink + // message is invalid and should be dropped. + return true; } ########## Write your Validator here and end with Ctrl+D (EOF):`) app.Validator = readFunction() case "encoder": fmt.Println(`function Encoder(obj) { - // The encoder encodes application data (a JS object) - // into a binary payload that is sent to devices. - // todo: return an array of numbers representing the payload - return [ 0x1 ]; + // Convert uplink messages sent as object to + // an array of bytes. + + return [ obj.turnLightOn ? 1 : 0 ]; } ########## Write your Encoder here and end with Ctrl+D (EOF):`) app.Encoder = readFunction() From 61d202291e0aa8a4e26eb96c10f266511ca94e48 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Mon, 19 Sep 2016 14:14:24 +0200 Subject: [PATCH 1737/2266] Edit title and add auth info --- mqtt/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/mqtt/README.md b/mqtt/README.md index 86bddadc2..338b4371e 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -1,7 +1,10 @@ -# The Things Network MQTT +# API Reference -This package contains the code that is used to publish and subscribe to MQTT. -This README describes the topics and messages that are used +* Host: `.thethings.network` +* Port: `1883` +* TLS: Not yet available +* Username: Application ID +* Password: Application Access Key ## Uplink Messages From e97e332d130b09c7955378ff8836164c335fc306 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Mon, 19 Sep 2016 14:09:40 +0200 Subject: [PATCH 1738/2266] Add downlink fields to MQTT readme --- mqtt/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/mqtt/README.md b/mqtt/README.md index 338b4371e..4290e3810 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -124,6 +124,23 @@ if err := token.Error(); err != nil { } ``` +### Downlink Fields + +Instead of `payload_raw` you can also use `payload_fields` with an object of fields. This requires the application to be configured with an Encoder Payload Function which encodes the fields into a Buffer. + +**Message:** + +```js +{ + "port": 1, // LoRaWAN FPort + "payload_fields": { + "led": true + } +} +``` + +**Usage (Mosquitto):** `mosquitto_pub -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_fields":{"led":true}}'` + ## Device Activations **Topic:** `/devices//activations` From 4b58cefa2452545770188bb7426a3590d7c5665b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 19 Sep 2016 14:33:00 +0200 Subject: [PATCH 1739/2266] Add Go example to MQTT README Also update server in examples --- mqtt/README.md | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/mqtt/README.md b/mqtt/README.md index 4290e3810..238cfbdd9 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -43,13 +43,13 @@ Note: Some values may be omitted if they are `null`, `""` or `0`. -**Usage (Mosquitto):** `mosquitto_sub -d -t 'my-app-id/devices/my-dev-id/up'` +**Usage (Mosquitto):** `mosquitto_sub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/up'` **Usage (Go client):** ```go ctx := log.WithField("Example", "Go Client") -client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } @@ -102,13 +102,13 @@ you will see this on MQTT: } ``` -**Usage (Mosquitto):** `mosquitto_pub -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_raw":"AQIDBA=="}'` +**Usage (Mosquitto):** `mosquitto_pub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_raw":"AQIDBA=="}'` **Usage (Go client):** ```go ctx := log.WithField("Example", "Go Client") -client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } @@ -139,7 +139,29 @@ Instead of `payload_raw` you can also use `payload_fields` with an object of fie } ``` -**Usage (Mosquitto):** `mosquitto_pub -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_fields":{"led":true}}'` +**Usage (Mosquitto):** `mosquitto_pub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/down' -m '{"port":1,"payload_fields":{"led":true}}'` + +**Usage (Go client):** + +```go +ctx := log.WithField("Example", "Go Client") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") +if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") +} +token := client.PublishDownlink(DownlinkMessage{ + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + Fields: map[string]interface{}{ + "led": true, + }, +}) +token.Wait() +if err := token.Error(); err != nil { + ctx.WithError(err).Fatal("Could not publish") +} +``` ## Device Activations @@ -158,13 +180,13 @@ Instead of `payload_raw` you can also use `payload_fields` with an object of fie } ``` -**Usage (Mosquitto):** `mosquitto_sub -d -t 'my-app-id/devices/my-dev-id/activations'` +**Usage (Mosquitto):** `mosquitto_sub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/activations'` **Usage (Go client):** ```go ctx := log.WithField("Example", "Go Client") -client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") +client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".thethings.network:1883") if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } From dddab6a0ddd1f3d10d2e620fed774b733a5ec102 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 23 Aug 2016 19:26:48 +0200 Subject: [PATCH 1740/2266] noc proto: GatewayUplink --- api/noc/noc.pb.go | 256 ++++++++++++++-------- api/noc/noc.proto | 4 +- api/protocol/lorawan/device.pb.go | 43 +--- api/protocol/lorawan/device_address.pb.go | 25 +++ 4 files changed, 199 insertions(+), 129 deletions(-) diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index ed7280340..78cb863fc 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -45,47 +45,48 @@ var _ grpc.ClientConn // is compatible with the grpc package it is being compiled against. const _ = grpc.SupportPackageIsVersion3 -// Client API for Monitoring service +// Client API for Monitor service -type MonitoringClient interface { - GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_GatewayStatusClient, error) - RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_RouterStatusClient, error) - BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_BrokerStatusClient, error) - HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_HandlerStatusClient, error) +type MonitorClient interface { + GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) + GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) + RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) + BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) + HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) } -type monitoringClient struct { +type monitorClient struct { cc *grpc.ClientConn } -func NewMonitoringClient(cc *grpc.ClientConn) MonitoringClient { - return &monitoringClient{cc} +func NewMonitorClient(cc *grpc.ClientConn) MonitorClient { + return &monitorClient{cc} } -func (c *monitoringClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_GatewayStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[0], c.cc, "/noc.Monitoring/GatewayStatus", opts...) +func (c *monitorClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/noc.Monitor/GatewayStatus", opts...) if err != nil { return nil, err } - x := &monitoringGatewayStatusClient{stream} + x := &monitorGatewayStatusClient{stream} return x, nil } -type Monitoring_GatewayStatusClient interface { +type Monitor_GatewayStatusClient interface { Send(*gateway.Status) error CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } -type monitoringGatewayStatusClient struct { +type monitorGatewayStatusClient struct { grpc.ClientStream } -func (x *monitoringGatewayStatusClient) Send(m *gateway.Status) error { +func (x *monitorGatewayStatusClient) Send(m *gateway.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } @@ -96,30 +97,64 @@ func (x *monitoringGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, return m, nil } -func (c *monitoringClient) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_RouterStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[1], c.cc, "/noc.Monitoring/RouterStatus", opts...) +func (c *monitorClient) GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[1], c.cc, "/noc.Monitor/GatewayUplink", opts...) if err != nil { return nil, err } - x := &monitoringRouterStatusClient{stream} + x := &monitorGatewayUplinkClient{stream} return x, nil } -type Monitoring_RouterStatusClient interface { +type Monitor_GatewayUplinkClient interface { + Send(*router.UplinkMessage) error + CloseAndRecv() (*google_protobuf.Empty, error) + grpc.ClientStream +} + +type monitorGatewayUplinkClient struct { + grpc.ClientStream +} + +func (x *monitorGatewayUplinkClient) Send(m *router.UplinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorGatewayUplinkClient) CloseAndRecv() (*google_protobuf.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func (c *monitorClient) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[2], c.cc, "/noc.Monitor/RouterStatus", opts...) + if err != nil { + return nil, err + } + x := &monitorRouterStatusClient{stream} + return x, nil +} + +type Monitor_RouterStatusClient interface { Send(*router.Status) error CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } -type monitoringRouterStatusClient struct { +type monitorRouterStatusClient struct { grpc.ClientStream } -func (x *monitoringRouterStatusClient) Send(m *router.Status) error { +func (x *monitorRouterStatusClient) Send(m *router.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringRouterStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorRouterStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } @@ -130,30 +165,30 @@ func (x *monitoringRouterStatusClient) CloseAndRecv() (*google_protobuf.Empty, e return m, nil } -func (c *monitoringClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_BrokerStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[2], c.cc, "/noc.Monitoring/BrokerStatus", opts...) +func (c *monitorClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[3], c.cc, "/noc.Monitor/BrokerStatus", opts...) if err != nil { return nil, err } - x := &monitoringBrokerStatusClient{stream} + x := &monitorBrokerStatusClient{stream} return x, nil } -type Monitoring_BrokerStatusClient interface { +type Monitor_BrokerStatusClient interface { Send(*broker.Status) error CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } -type monitoringBrokerStatusClient struct { +type monitorBrokerStatusClient struct { grpc.ClientStream } -func (x *monitoringBrokerStatusClient) Send(m *broker.Status) error { +func (x *monitorBrokerStatusClient) Send(m *broker.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringBrokerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorBrokerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } @@ -164,30 +199,30 @@ func (x *monitoringBrokerStatusClient) CloseAndRecv() (*google_protobuf.Empty, e return m, nil } -func (c *monitoringClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitoring_HandlerStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitoring_serviceDesc.Streams[3], c.cc, "/noc.Monitoring/HandlerStatus", opts...) +func (c *monitorClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[4], c.cc, "/noc.Monitor/HandlerStatus", opts...) if err != nil { return nil, err } - x := &monitoringHandlerStatusClient{stream} + x := &monitorHandlerStatusClient{stream} return x, nil } -type Monitoring_HandlerStatusClient interface { +type Monitor_HandlerStatusClient interface { Send(*handler.Status) error CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } -type monitoringHandlerStatusClient struct { +type monitorHandlerStatusClient struct { grpc.ClientStream } -func (x *monitoringHandlerStatusClient) Send(m *handler.Status) error { +func (x *monitorHandlerStatusClient) Send(m *handler.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitoringHandlerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorHandlerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } @@ -198,38 +233,39 @@ func (x *monitoringHandlerStatusClient) CloseAndRecv() (*google_protobuf.Empty, return m, nil } -// Server API for Monitoring service +// Server API for Monitor service -type MonitoringServer interface { - GatewayStatus(Monitoring_GatewayStatusServer) error - RouterStatus(Monitoring_RouterStatusServer) error - BrokerStatus(Monitoring_BrokerStatusServer) error - HandlerStatus(Monitoring_HandlerStatusServer) error +type MonitorServer interface { + GatewayStatus(Monitor_GatewayStatusServer) error + GatewayUplink(Monitor_GatewayUplinkServer) error + RouterStatus(Monitor_RouterStatusServer) error + BrokerStatus(Monitor_BrokerStatusServer) error + HandlerStatus(Monitor_HandlerStatusServer) error } -func RegisterMonitoringServer(s *grpc.Server, srv MonitoringServer) { - s.RegisterService(&_Monitoring_serviceDesc, srv) +func RegisterMonitorServer(s *grpc.Server, srv MonitorServer) { + s.RegisterService(&_Monitor_serviceDesc, srv) } -func _Monitoring_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(MonitoringServer).GatewayStatus(&monitoringGatewayStatusServer{stream}) +func _Monitor_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayStatus(&monitorGatewayStatusServer{stream}) } -type Monitoring_GatewayStatusServer interface { +type Monitor_GatewayStatusServer interface { SendAndClose(*google_protobuf.Empty) error Recv() (*gateway.Status, error) grpc.ServerStream } -type monitoringGatewayStatusServer struct { +type monitorGatewayStatusServer struct { grpc.ServerStream } -func (x *monitoringGatewayStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorGatewayStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } -func (x *monitoringGatewayStatusServer) Recv() (*gateway.Status, error) { +func (x *monitorGatewayStatusServer) Recv() (*gateway.Status, error) { m := new(gateway.Status) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err @@ -237,25 +273,51 @@ func (x *monitoringGatewayStatusServer) Recv() (*gateway.Status, error) { return m, nil } -func _Monitoring_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(MonitoringServer).RouterStatus(&monitoringRouterStatusServer{stream}) +func _Monitor_GatewayUplink_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayUplink(&monitorGatewayUplinkServer{stream}) } -type Monitoring_RouterStatusServer interface { +type Monitor_GatewayUplinkServer interface { + SendAndClose(*google_protobuf.Empty) error + Recv() (*router.UplinkMessage, error) + grpc.ServerStream +} + +type monitorGatewayUplinkServer struct { + grpc.ServerStream +} + +func (x *monitorGatewayUplinkServer) SendAndClose(m *google_protobuf.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorGatewayUplinkServer) Recv() (*router.UplinkMessage, error) { + m := new(router.UplinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + +func _Monitor_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).RouterStatus(&monitorRouterStatusServer{stream}) +} + +type Monitor_RouterStatusServer interface { SendAndClose(*google_protobuf.Empty) error Recv() (*router.Status, error) grpc.ServerStream } -type monitoringRouterStatusServer struct { +type monitorRouterStatusServer struct { grpc.ServerStream } -func (x *monitoringRouterStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorRouterStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } -func (x *monitoringRouterStatusServer) Recv() (*router.Status, error) { +func (x *monitorRouterStatusServer) Recv() (*router.Status, error) { m := new(router.Status) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err @@ -263,25 +325,25 @@ func (x *monitoringRouterStatusServer) Recv() (*router.Status, error) { return m, nil } -func _Monitoring_BrokerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(MonitoringServer).BrokerStatus(&monitoringBrokerStatusServer{stream}) +func _Monitor_BrokerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).BrokerStatus(&monitorBrokerStatusServer{stream}) } -type Monitoring_BrokerStatusServer interface { +type Monitor_BrokerStatusServer interface { SendAndClose(*google_protobuf.Empty) error Recv() (*broker.Status, error) grpc.ServerStream } -type monitoringBrokerStatusServer struct { +type monitorBrokerStatusServer struct { grpc.ServerStream } -func (x *monitoringBrokerStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorBrokerStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } -func (x *monitoringBrokerStatusServer) Recv() (*broker.Status, error) { +func (x *monitorBrokerStatusServer) Recv() (*broker.Status, error) { m := new(broker.Status) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err @@ -289,25 +351,25 @@ func (x *monitoringBrokerStatusServer) Recv() (*broker.Status, error) { return m, nil } -func _Monitoring_HandlerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(MonitoringServer).HandlerStatus(&monitoringHandlerStatusServer{stream}) +func _Monitor_HandlerStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).HandlerStatus(&monitorHandlerStatusServer{stream}) } -type Monitoring_HandlerStatusServer interface { +type Monitor_HandlerStatusServer interface { SendAndClose(*google_protobuf.Empty) error Recv() (*handler.Status, error) grpc.ServerStream } -type monitoringHandlerStatusServer struct { +type monitorHandlerStatusServer struct { grpc.ServerStream } -func (x *monitoringHandlerStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorHandlerStatusServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } -func (x *monitoringHandlerStatusServer) Recv() (*handler.Status, error) { +func (x *monitorHandlerStatusServer) Recv() (*handler.Status, error) { m := new(handler.Status) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err @@ -315,29 +377,34 @@ func (x *monitoringHandlerStatusServer) Recv() (*handler.Status, error) { return m, nil } -var _Monitoring_serviceDesc = grpc.ServiceDesc{ - ServiceName: "noc.Monitoring", - HandlerType: (*MonitoringServer)(nil), +var _Monitor_serviceDesc = grpc.ServiceDesc{ + ServiceName: "noc.Monitor", + HandlerType: (*MonitorServer)(nil), Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ { StreamName: "GatewayStatus", - Handler: _Monitoring_GatewayStatus_Handler, + Handler: _Monitor_GatewayStatus_Handler, + ClientStreams: true, + }, + { + StreamName: "GatewayUplink", + Handler: _Monitor_GatewayUplink_Handler, ClientStreams: true, }, { StreamName: "RouterStatus", - Handler: _Monitoring_RouterStatus_Handler, + Handler: _Monitor_RouterStatus_Handler, ClientStreams: true, }, { StreamName: "BrokerStatus", - Handler: _Monitoring_BrokerStatus_Handler, + Handler: _Monitor_BrokerStatus_Handler, ClientStreams: true, }, { StreamName: "HandlerStatus", - Handler: _Monitoring_HandlerStatus_Handler, + Handler: _Monitor_HandlerStatus_Handler, ClientStreams: true, }, }, @@ -349,22 +416,23 @@ func init() { } var fileDescriptorNoc = []byte{ - // 261 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x90, 0xb1, 0x4e, 0xc3, 0x30, - 0x10, 0x86, 0xb1, 0x90, 0x18, 0x2c, 0x0a, 0x28, 0x03, 0x43, 0x90, 0x32, 0x33, 0xd9, 0x02, 0x06, - 0xa0, 0x63, 0x25, 0x04, 0x0b, 0x0c, 0xd0, 0x17, 0x70, 0xc2, 0xe1, 0x58, 0x6d, 0x7d, 0xd1, 0xf5, - 0xa2, 0xaa, 0x6f, 0xc2, 0x23, 0x31, 0xf2, 0x08, 0x28, 0xbc, 0x04, 0x23, 0x6a, 0x6c, 0x77, 0x6d, - 0x3a, 0x58, 0xbf, 0x74, 0xba, 0xcf, 0xba, 0xef, 0x97, 0x57, 0xd6, 0x71, 0xdd, 0x96, 0xaa, 0xc2, - 0x85, 0x9e, 0xd6, 0x30, 0xad, 0x9d, 0xb7, 0xcb, 0x17, 0xe0, 0x15, 0xd2, 0x4c, 0x33, 0x7b, 0x6d, - 0x1a, 0xa7, 0x3d, 0x56, 0x9b, 0xa7, 0x1a, 0x42, 0xc6, 0xec, 0xd0, 0x63, 0x95, 0x5f, 0x58, 0x44, - 0x3b, 0x07, 0xdd, 0x8f, 0xca, 0xf6, 0x43, 0xc3, 0xa2, 0xe1, 0x75, 0xd8, 0xc8, 0xef, 0x87, 0x7c, - 0x6a, 0x0d, 0xc3, 0xca, 0xac, 0x53, 0x46, 0xf4, 0x76, 0x08, 0x4a, 0xd8, 0x32, 0x50, 0x8c, 0x7d, - 0xc0, 0x92, 0x70, 0x06, 0x14, 0x63, 0x9f, 0x63, 0x6b, 0xe3, 0xdf, 0xe7, 0x40, 0x29, 0x03, 0x7a, - 0xfd, 0x27, 0xa4, 0x7c, 0x46, 0xef, 0x18, 0xc9, 0x79, 0x9b, 0x8d, 0xe5, 0xe8, 0x31, 0xc8, 0xbc, - 0xb1, 0xe1, 0x76, 0x99, 0x9d, 0xaa, 0x24, 0x17, 0x06, 0xf9, 0xb9, 0x0a, 0xb5, 0xa9, 0x54, 0x9b, - 0x7a, 0xd8, 0xd4, 0x76, 0x29, 0xb2, 0x3b, 0x79, 0xfc, 0xda, 0xeb, 0x44, 0xf4, 0x44, 0x45, 0xbb, - 0x21, 0xe4, 0xa4, 0xf7, 0xd9, 0x92, 0x51, 0x6f, 0x27, 0x39, 0x96, 0xa3, 0xa7, 0xe0, 0xb3, 0xbd, - 0x37, 0xf9, 0xed, 0x62, 0x27, 0x67, 0x5f, 0x5d, 0x21, 0xbe, 0xbb, 0x42, 0xfc, 0x74, 0x85, 0xf8, - 0xfc, 0x2d, 0x0e, 0xca, 0xa3, 0x7e, 0xe7, 0xe6, 0x3f, 0x00, 0x00, 0xff, 0xff, 0x73, 0xf2, 0xf6, - 0xc0, 0x52, 0x02, 0x00, 0x00, + // 287 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, + 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, + 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, + 0xcf, 0xcb, 0x4f, 0x06, 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xbc, 0xfc, 0x64, + 0x29, 0xe9, 0xf4, 0xfc, 0xfc, 0xf4, 0x9c, 0x54, 0x7d, 0xb0, 0x50, 0x52, 0x69, 0x9a, 0x7e, 0x6a, + 0x6e, 0x41, 0x49, 0x25, 0x44, 0x85, 0x94, 0x25, 0x31, 0x86, 0xa6, 0x27, 0x96, 0xa4, 0x96, 0x27, + 0x56, 0xc2, 0x68, 0xa8, 0x56, 0x73, 0x62, 0xb4, 0x16, 0xe5, 0x97, 0x96, 0xa4, 0x16, 0x41, 0x29, + 0x52, 0x34, 0x26, 0x15, 0xe5, 0x67, 0xa7, 0x16, 0x41, 0x29, 0x52, 0x1c, 0x9b, 0x91, 0x98, 0x97, + 0x92, 0x93, 0x5a, 0x04, 0xa3, 0x21, 0x5a, 0x8d, 0xb6, 0x33, 0x71, 0xb1, 0xfb, 0xe6, 0xe7, 0x65, + 0x96, 0xe4, 0x17, 0x09, 0x59, 0x71, 0xf1, 0xba, 0x43, 0x7c, 0x12, 0x5c, 0x92, 0x58, 0x52, 0x5a, + 0x2c, 0xc4, 0xaf, 0x07, 0xf3, 0x19, 0x44, 0x40, 0x4a, 0x4c, 0x0f, 0x12, 0x66, 0x7a, 0xb0, 0x30, + 0xd3, 0x73, 0x05, 0x85, 0x99, 0x06, 0xa3, 0x90, 0x03, 0x5c, 0x6f, 0x68, 0x41, 0x4e, 0x66, 0x5e, + 0xb6, 0x90, 0xa8, 0x1e, 0xd4, 0x6f, 0x10, 0xbe, 0x6f, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0x2a, 0x1e, + 0x13, 0x2c, 0xb8, 0x78, 0x82, 0xc0, 0x3a, 0xa0, 0x96, 0xf3, 0xc1, 0x0c, 0x20, 0x68, 0xb7, 0x05, + 0x17, 0x8f, 0x13, 0x38, 0x38, 0xe0, 0x3a, 0xa1, 0xa1, 0x43, 0x50, 0xa7, 0x15, 0x17, 0xaf, 0x07, + 0x24, 0x38, 0xe0, 0x3e, 0x86, 0x05, 0x0f, 0x21, 0xbd, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, + 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x35, + 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x64, 0x86, 0xd0, 0xa9, 0x91, 0x02, 0x00, 0x00, } diff --git a/api/noc/noc.proto b/api/noc/noc.proto index b78b49b02..4d86dff61 100644 --- a/api/noc/noc.proto +++ b/api/noc/noc.proto @@ -11,8 +11,10 @@ import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; package noc; -service Monitoring { +service Monitor { rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); + rpc GatewayUplink(stream router.UplinkMessage) returns (google.protobuf.Empty); + rpc RouterStatus(stream router.Status) returns (google.protobuf.Empty); rpc BrokerStatus(stream broker.Status) returns (google.protobuf.Empty); rpc HandlerStatus(stream handler.Status) returns (google.protobuf.Empty); diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index 27128067d..cd59762e2 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -2,31 +2,12 @@ // source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto // DO NOT EDIT! -/* - Package lorawan is a generated protocol buffer package. - - It is generated from these files: - github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto - github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto - github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto - - It has these top-level messages: - DeviceIdentifier - Device - PrefixesRequest - PrefixesResponse - DevAddrRequest - DevAddrResponse - Metadata - TxConfiguration - ActivationMetadata -*/ package lorawan import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" import _ "github.com/gogo/protobuf/gogoproto" import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" @@ -43,12 +24,6 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf -// This is a compile-time assertion to ensure that this generated file -// is compatible with the proto package it is being compiled against. -// A compilation error at this line likely means your copy of the -// proto package needs to be updated. -const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package - type DeviceIdentifier struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` @@ -100,8 +75,8 @@ const _ = grpc.SupportPackageIsVersion3 type DeviceManagerClient interface { GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) - SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) - DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) } type deviceManagerClient struct { @@ -121,8 +96,8 @@ func (c *deviceManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifie return out, nil } -func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { - out := new(google_protobuf.Empty) +func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) err := grpc.Invoke(ctx, "/lorawan.DeviceManager/SetDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -130,8 +105,8 @@ func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts .. return out, nil } -func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { - out := new(google_protobuf.Empty) +func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { + out := new(google_protobuf1.Empty) err := grpc.Invoke(ctx, "/lorawan.DeviceManager/DeleteDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -143,8 +118,8 @@ func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdenti type DeviceManagerServer interface { GetDevice(context.Context, *DeviceIdentifier) (*Device, error) - SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) - DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) + SetDevice(context.Context, *Device) (*google_protobuf1.Empty, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf1.Empty, error) } func RegisterDeviceManagerServer(s *grpc.Server, srv DeviceManagerServer) { diff --git a/api/protocol/lorawan/device_address.pb.go b/api/protocol/lorawan/device_address.pb.go index fab991ec0..f41623e08 100644 --- a/api/protocol/lorawan/device_address.pb.go +++ b/api/protocol/lorawan/device_address.pb.go @@ -2,6 +2,25 @@ // source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto // DO NOT EDIT! +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto + + It has these top-level messages: + PrefixesRequest + PrefixesResponse + DevAddrRequest + DevAddrResponse + DeviceIdentifier + Device + Metadata + TxConfiguration + ActivationMetadata +*/ package lorawan import proto "github.com/golang/protobuf/proto" @@ -23,6 +42,12 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + type PrefixesRequest struct { } From b3d592eabcc4f172e3bce07ac28da6f5e088f3e4 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 23 Aug 2016 20:36:39 +0200 Subject: [PATCH 1741/2266] noc proto: GatewayStatus->GatewayLocation --- api/noc/noc.pb.go | 79 ++++++++++++++++++++++++----------------------- api/noc/noc.proto | 2 +- 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 78cb863fc..336e7a42a 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -48,7 +48,7 @@ const _ = grpc.SupportPackageIsVersion3 // Client API for Monitor service type MonitorClient interface { - GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) + GatewayLocation(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayLocationClient, error) GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) @@ -63,30 +63,30 @@ func NewMonitorClient(cc *grpc.ClientConn) MonitorClient { return &monitorClient{cc} } -func (c *monitorClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/noc.Monitor/GatewayStatus", opts...) +func (c *monitorClient) GatewayLocation(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayLocationClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/noc.Monitor/GatewayLocation", opts...) if err != nil { return nil, err } - x := &monitorGatewayStatusClient{stream} + x := &monitorGatewayLocationClient{stream} return x, nil } -type Monitor_GatewayStatusClient interface { - Send(*gateway.Status) error +type Monitor_GatewayLocationClient interface { + Send(*gateway.GPSMetadata) error CloseAndRecv() (*google_protobuf.Empty, error) grpc.ClientStream } -type monitorGatewayStatusClient struct { +type monitorGatewayLocationClient struct { grpc.ClientStream } -func (x *monitorGatewayStatusClient) Send(m *gateway.Status) error { +func (x *monitorGatewayLocationClient) Send(m *gateway.GPSMetadata) error { return x.ClientStream.SendMsg(m) } -func (x *monitorGatewayStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorGatewayLocationClient) CloseAndRecv() (*google_protobuf.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } @@ -236,7 +236,7 @@ func (x *monitorHandlerStatusClient) CloseAndRecv() (*google_protobuf.Empty, err // Server API for Monitor service type MonitorServer interface { - GatewayStatus(Monitor_GatewayStatusServer) error + GatewayLocation(Monitor_GatewayLocationServer) error GatewayUplink(Monitor_GatewayUplinkServer) error RouterStatus(Monitor_RouterStatusServer) error BrokerStatus(Monitor_BrokerStatusServer) error @@ -247,26 +247,26 @@ func RegisterMonitorServer(s *grpc.Server, srv MonitorServer) { s.RegisterService(&_Monitor_serviceDesc, srv) } -func _Monitor_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(MonitorServer).GatewayStatus(&monitorGatewayStatusServer{stream}) +func _Monitor_GatewayLocation_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayLocation(&monitorGatewayLocationServer{stream}) } -type Monitor_GatewayStatusServer interface { +type Monitor_GatewayLocationServer interface { SendAndClose(*google_protobuf.Empty) error - Recv() (*gateway.Status, error) + Recv() (*gateway.GPSMetadata, error) grpc.ServerStream } -type monitorGatewayStatusServer struct { +type monitorGatewayLocationServer struct { grpc.ServerStream } -func (x *monitorGatewayStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorGatewayLocationServer) SendAndClose(m *google_protobuf.Empty) error { return x.ServerStream.SendMsg(m) } -func (x *monitorGatewayStatusServer) Recv() (*gateway.Status, error) { - m := new(gateway.Status) +func (x *monitorGatewayLocationServer) Recv() (*gateway.GPSMetadata, error) { + m := new(gateway.GPSMetadata) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } @@ -383,8 +383,8 @@ var _Monitor_serviceDesc = grpc.ServiceDesc{ Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ { - StreamName: "GatewayStatus", - Handler: _Monitor_GatewayStatus_Handler, + StreamName: "GatewayLocation", + Handler: _Monitor_GatewayLocation_Handler, ClientStreams: true, }, { @@ -416,23 +416,24 @@ func init() { } var fileDescriptorNoc = []byte{ - // 287 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x32, 0x4c, 0xcf, 0x2c, 0xc9, - 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, - 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, - 0xcf, 0xcb, 0x4f, 0x06, 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xbc, 0xfc, 0x64, - 0x29, 0xe9, 0xf4, 0xfc, 0xfc, 0xf4, 0x9c, 0x54, 0x7d, 0xb0, 0x50, 0x52, 0x69, 0x9a, 0x7e, 0x6a, - 0x6e, 0x41, 0x49, 0x25, 0x44, 0x85, 0x94, 0x25, 0x31, 0x86, 0xa6, 0x27, 0x96, 0xa4, 0x96, 0x27, - 0x56, 0xc2, 0x68, 0xa8, 0x56, 0x73, 0x62, 0xb4, 0x16, 0xe5, 0x97, 0x96, 0xa4, 0x16, 0x41, 0x29, - 0x52, 0x34, 0x26, 0x15, 0xe5, 0x67, 0xa7, 0x16, 0x41, 0x29, 0x52, 0x1c, 0x9b, 0x91, 0x98, 0x97, - 0x92, 0x93, 0x5a, 0x04, 0xa3, 0x21, 0x5a, 0x8d, 0xb6, 0x33, 0x71, 0xb1, 0xfb, 0xe6, 0xe7, 0x65, - 0x96, 0xe4, 0x17, 0x09, 0x59, 0x71, 0xf1, 0xba, 0x43, 0x7c, 0x12, 0x5c, 0x92, 0x58, 0x52, 0x5a, - 0x2c, 0xc4, 0xaf, 0x07, 0xf3, 0x19, 0x44, 0x40, 0x4a, 0x4c, 0x0f, 0x12, 0x66, 0x7a, 0xb0, 0x30, - 0xd3, 0x73, 0x05, 0x85, 0x99, 0x06, 0xa3, 0x90, 0x03, 0x5c, 0x6f, 0x68, 0x41, 0x4e, 0x66, 0x5e, - 0xb6, 0x90, 0xa8, 0x1e, 0xd4, 0x6f, 0x10, 0xbe, 0x6f, 0x6a, 0x71, 0x71, 0x62, 0x7a, 0x2a, 0x1e, - 0x13, 0x2c, 0xb8, 0x78, 0x82, 0xc0, 0x3a, 0xa0, 0x96, 0xf3, 0xc1, 0x0c, 0x20, 0x68, 0xb7, 0x05, - 0x17, 0x8f, 0x13, 0x38, 0x38, 0xe0, 0x3a, 0xa1, 0xa1, 0x43, 0x50, 0xa7, 0x15, 0x17, 0xaf, 0x07, - 0x24, 0x38, 0xe0, 0x3e, 0x86, 0x05, 0x0f, 0x21, 0xbd, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, - 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x35, - 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x64, 0x86, 0xd0, 0xa9, 0x91, 0x02, 0x00, 0x00, + // 299 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x90, 0xcb, 0x4a, 0xc3, 0x40, + 0x14, 0x86, 0x8d, 0x82, 0xc2, 0x60, 0xad, 0x0c, 0xea, 0xa2, 0x42, 0xd6, 0xae, 0x66, 0x50, 0x17, + 0x5e, 0x56, 0x5a, 0x90, 0xba, 0xb0, 0x22, 0xb6, 0x3e, 0xc0, 0x24, 0x1d, 0x27, 0x43, 0xd2, 0x39, + 0x61, 0x72, 0x42, 0xe9, 0x9b, 0xf8, 0x34, 0xae, 0x5d, 0xfa, 0x08, 0x12, 0x5f, 0x44, 0x9a, 0x99, + 0x71, 0xd9, 0xcb, 0x22, 0xfc, 0x9c, 0x70, 0xbe, 0x9f, 0x39, 0x1f, 0x39, 0x57, 0x1a, 0xb3, 0x3a, + 0x61, 0x29, 0x4c, 0xf9, 0x38, 0x93, 0xe3, 0x4c, 0x1b, 0x55, 0x3d, 0x4b, 0x9c, 0x81, 0xcd, 0x39, + 0xa2, 0xe1, 0xa2, 0xd4, 0xdc, 0x40, 0xba, 0xf8, 0x58, 0x69, 0x01, 0x81, 0xee, 0x18, 0x48, 0x7b, + 0xa7, 0x0a, 0x40, 0x15, 0x92, 0xb7, 0xbf, 0x92, 0xfa, 0x9d, 0xcb, 0x69, 0x89, 0x73, 0xb7, 0xd1, + 0xbb, 0x59, 0xa7, 0x54, 0x09, 0x94, 0x33, 0x31, 0x0f, 0xe9, 0xd1, 0xab, 0x75, 0x50, 0x0b, 0x35, + 0x4a, 0xeb, 0x63, 0x13, 0x30, 0xb1, 0x90, 0x4b, 0xeb, 0x63, 0x93, 0xc7, 0x66, 0xc2, 0x4c, 0x0a, + 0x69, 0x43, 0x3a, 0xf4, 0xe2, 0x73, 0x9b, 0xec, 0x0d, 0xc1, 0x68, 0x04, 0x4b, 0xef, 0x49, 0x77, + 0xe0, 0x2e, 0x79, 0x82, 0x54, 0xa0, 0x06, 0x43, 0x8f, 0x58, 0xb8, 0x6d, 0xf0, 0x32, 0x1a, 0x4a, + 0x14, 0x13, 0x81, 0xa2, 0x77, 0xc2, 0x9c, 0x3a, 0x16, 0xd4, 0xb1, 0x87, 0x85, 0xba, 0xb3, 0x88, + 0xde, 0x91, 0x8e, 0xaf, 0x78, 0x2b, 0x0b, 0x6d, 0x72, 0x7a, 0xcc, 0xfc, 0x89, 0x6e, 0x1e, 0xca, + 0xaa, 0x12, 0x4a, 0x2e, 0x69, 0xb8, 0x26, 0xfb, 0xaf, 0x2d, 0x31, 0x42, 0x81, 0x75, 0x45, 0x0f, + 0x42, 0x81, 0x9b, 0x97, 0x93, 0xfd, 0xd6, 0xca, 0x3f, 0xe9, 0x25, 0xad, 0x24, 0x6f, 0x49, 0xe7, + 0xd1, 0x59, 0xf1, 0x68, 0x97, 0x05, 0x4b, 0xab, 0xd8, 0xfe, 0xe1, 0x57, 0x13, 0x47, 0xdf, 0x4d, + 0x1c, 0xfd, 0x34, 0x71, 0xf4, 0xf1, 0x1b, 0x6f, 0x25, 0xbb, 0xed, 0xce, 0xe5, 0x5f, 0x00, 0x00, + 0x00, 0xff, 0xff, 0xf8, 0xba, 0x29, 0x05, 0x98, 0x02, 0x00, 0x00, } diff --git a/api/noc/noc.proto b/api/noc/noc.proto index 4d86dff61..fef635266 100644 --- a/api/noc/noc.proto +++ b/api/noc/noc.proto @@ -12,7 +12,7 @@ import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; package noc; service Monitor { - rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); + rpc GatewayLocation(stream gateway.GPSMetadata) returns (google.protobuf.Empty); rpc GatewayUplink(stream router.UplinkMessage) returns (google.protobuf.Empty); rpc RouterStatus(stream router.Status) returns (google.protobuf.Empty); From f2d2e99ec2e2145d8add7213b8e0d2388f44c310 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 7 Sep 2016 10:24:19 +0200 Subject: [PATCH 1742/2266] api/gateway: IsZero, Validate --- api/gateway/validation.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/gateway/validation.go b/api/gateway/validation.go index b030ccc00..f31e0107e 100644 --- a/api/gateway/validation.go +++ b/api/gateway/validation.go @@ -17,3 +17,12 @@ func (m *TxConfiguration) Validate() bool { func (m *Status) Validate() bool { return true } + +// Validate implements the api.Validator interface +func (m *GPSMetadata) Validate() bool { + return m != nil && !m.IsZero() +} + +func (m GPSMetadata) IsZero() bool { + return m.Latitude == 0 && m.Longitude == 0 +} From 9e61eb7802cdffe1d26e8c33d2bf4622c4e7f2ee Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 7 Sep 2016 10:26:44 +0200 Subject: [PATCH 1743/2266] api/noc: return Empty, UplinkUpdate type --- api/noc/noc.pb.go | 92 +++++++++++++++++++++++------------------------ api/noc/noc.proto | 3 +- 2 files changed, 48 insertions(+), 47 deletions(-) diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 336e7a42a..4eee7cbc0 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -15,11 +15,11 @@ package noc import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import google_protobuf "github.com/golang/protobuf/ptypes/empty" import gateway "github.com/TheThingsNetwork/ttn/api/gateway" import router "github.com/TheThingsNetwork/ttn/api/router" import broker "github.com/TheThingsNetwork/ttn/api/broker" import handler "github.com/TheThingsNetwork/ttn/api/handler" +import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" import ( context "golang.org/x/net/context" @@ -74,7 +74,7 @@ func (c *monitorClient) GatewayLocation(ctx context.Context, opts ...grpc.CallOp type Monitor_GatewayLocationClient interface { Send(*gateway.GPSMetadata) error - CloseAndRecv() (*google_protobuf.Empty, error) + CloseAndRecv() (*google_protobuf1.Empty, error) grpc.ClientStream } @@ -86,11 +86,11 @@ func (x *monitorGatewayLocationClient) Send(m *gateway.GPSMetadata) error { return x.ClientStream.SendMsg(m) } -func (x *monitorGatewayLocationClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorGatewayLocationClient) CloseAndRecv() (*google_protobuf1.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(google_protobuf.Empty) + m := new(google_protobuf1.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -108,7 +108,7 @@ func (c *monitorClient) GatewayUplink(ctx context.Context, opts ...grpc.CallOpti type Monitor_GatewayUplinkClient interface { Send(*router.UplinkMessage) error - CloseAndRecv() (*google_protobuf.Empty, error) + CloseAndRecv() (*google_protobuf1.Empty, error) grpc.ClientStream } @@ -120,11 +120,11 @@ func (x *monitorGatewayUplinkClient) Send(m *router.UplinkMessage) error { return x.ClientStream.SendMsg(m) } -func (x *monitorGatewayUplinkClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorGatewayUplinkClient) CloseAndRecv() (*google_protobuf1.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(google_protobuf.Empty) + m := new(google_protobuf1.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -142,7 +142,7 @@ func (c *monitorClient) RouterStatus(ctx context.Context, opts ...grpc.CallOptio type Monitor_RouterStatusClient interface { Send(*router.Status) error - CloseAndRecv() (*google_protobuf.Empty, error) + CloseAndRecv() (*google_protobuf1.Empty, error) grpc.ClientStream } @@ -154,11 +154,11 @@ func (x *monitorRouterStatusClient) Send(m *router.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitorRouterStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorRouterStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(google_protobuf.Empty) + m := new(google_protobuf1.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -176,7 +176,7 @@ func (c *monitorClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOptio type Monitor_BrokerStatusClient interface { Send(*broker.Status) error - CloseAndRecv() (*google_protobuf.Empty, error) + CloseAndRecv() (*google_protobuf1.Empty, error) grpc.ClientStream } @@ -188,11 +188,11 @@ func (x *monitorBrokerStatusClient) Send(m *broker.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitorBrokerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorBrokerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(google_protobuf.Empty) + m := new(google_protobuf1.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -210,7 +210,7 @@ func (c *monitorClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOpti type Monitor_HandlerStatusClient interface { Send(*handler.Status) error - CloseAndRecv() (*google_protobuf.Empty, error) + CloseAndRecv() (*google_protobuf1.Empty, error) grpc.ClientStream } @@ -222,11 +222,11 @@ func (x *monitorHandlerStatusClient) Send(m *handler.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitorHandlerStatusClient) CloseAndRecv() (*google_protobuf.Empty, error) { +func (x *monitorHandlerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } - m := new(google_protobuf.Empty) + m := new(google_protobuf1.Empty) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } @@ -252,7 +252,7 @@ func _Monitor_GatewayLocation_Handler(srv interface{}, stream grpc.ServerStream) } type Monitor_GatewayLocationServer interface { - SendAndClose(*google_protobuf.Empty) error + SendAndClose(*google_protobuf1.Empty) error Recv() (*gateway.GPSMetadata, error) grpc.ServerStream } @@ -261,7 +261,7 @@ type monitorGatewayLocationServer struct { grpc.ServerStream } -func (x *monitorGatewayLocationServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorGatewayLocationServer) SendAndClose(m *google_protobuf1.Empty) error { return x.ServerStream.SendMsg(m) } @@ -278,7 +278,7 @@ func _Monitor_GatewayUplink_Handler(srv interface{}, stream grpc.ServerStream) e } type Monitor_GatewayUplinkServer interface { - SendAndClose(*google_protobuf.Empty) error + SendAndClose(*google_protobuf1.Empty) error Recv() (*router.UplinkMessage, error) grpc.ServerStream } @@ -287,7 +287,7 @@ type monitorGatewayUplinkServer struct { grpc.ServerStream } -func (x *monitorGatewayUplinkServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorGatewayUplinkServer) SendAndClose(m *google_protobuf1.Empty) error { return x.ServerStream.SendMsg(m) } @@ -304,7 +304,7 @@ func _Monitor_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) er } type Monitor_RouterStatusServer interface { - SendAndClose(*google_protobuf.Empty) error + SendAndClose(*google_protobuf1.Empty) error Recv() (*router.Status, error) grpc.ServerStream } @@ -313,7 +313,7 @@ type monitorRouterStatusServer struct { grpc.ServerStream } -func (x *monitorRouterStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorRouterStatusServer) SendAndClose(m *google_protobuf1.Empty) error { return x.ServerStream.SendMsg(m) } @@ -330,7 +330,7 @@ func _Monitor_BrokerStatus_Handler(srv interface{}, stream grpc.ServerStream) er } type Monitor_BrokerStatusServer interface { - SendAndClose(*google_protobuf.Empty) error + SendAndClose(*google_protobuf1.Empty) error Recv() (*broker.Status, error) grpc.ServerStream } @@ -339,7 +339,7 @@ type monitorBrokerStatusServer struct { grpc.ServerStream } -func (x *monitorBrokerStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorBrokerStatusServer) SendAndClose(m *google_protobuf1.Empty) error { return x.ServerStream.SendMsg(m) } @@ -356,7 +356,7 @@ func _Monitor_HandlerStatus_Handler(srv interface{}, stream grpc.ServerStream) e } type Monitor_HandlerStatusServer interface { - SendAndClose(*google_protobuf.Empty) error + SendAndClose(*google_protobuf1.Empty) error Recv() (*handler.Status, error) grpc.ServerStream } @@ -365,7 +365,7 @@ type monitorHandlerStatusServer struct { grpc.ServerStream } -func (x *monitorHandlerStatusServer) SendAndClose(m *google_protobuf.Empty) error { +func (x *monitorHandlerStatusServer) SendAndClose(m *google_protobuf1.Empty) error { return x.ServerStream.SendMsg(m) } @@ -416,24 +416,24 @@ func init() { } var fileDescriptorNoc = []byte{ - // 299 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x90, 0xcb, 0x4a, 0xc3, 0x40, - 0x14, 0x86, 0x8d, 0x82, 0xc2, 0x60, 0xad, 0x0c, 0xea, 0xa2, 0x42, 0xd6, 0xae, 0x66, 0x50, 0x17, - 0x5e, 0x56, 0x5a, 0x90, 0xba, 0xb0, 0x22, 0xb6, 0x3e, 0xc0, 0x24, 0x1d, 0x27, 0x43, 0xd2, 0x39, - 0x61, 0x72, 0x42, 0xe9, 0x9b, 0xf8, 0x34, 0xae, 0x5d, 0xfa, 0x08, 0x12, 0x5f, 0x44, 0x9a, 0x99, - 0x71, 0xd9, 0xcb, 0x22, 0xfc, 0x9c, 0x70, 0xbe, 0x9f, 0x39, 0x1f, 0x39, 0x57, 0x1a, 0xb3, 0x3a, - 0x61, 0x29, 0x4c, 0xf9, 0x38, 0x93, 0xe3, 0x4c, 0x1b, 0x55, 0x3d, 0x4b, 0x9c, 0x81, 0xcd, 0x39, - 0xa2, 0xe1, 0xa2, 0xd4, 0xdc, 0x40, 0xba, 0xf8, 0x58, 0x69, 0x01, 0x81, 0xee, 0x18, 0x48, 0x7b, - 0xa7, 0x0a, 0x40, 0x15, 0x92, 0xb7, 0xbf, 0x92, 0xfa, 0x9d, 0xcb, 0x69, 0x89, 0x73, 0xb7, 0xd1, - 0xbb, 0x59, 0xa7, 0x54, 0x09, 0x94, 0x33, 0x31, 0x0f, 0xe9, 0xd1, 0xab, 0x75, 0x50, 0x0b, 0x35, - 0x4a, 0xeb, 0x63, 0x13, 0x30, 0xb1, 0x90, 0x4b, 0xeb, 0x63, 0x93, 0xc7, 0x66, 0xc2, 0x4c, 0x0a, - 0x69, 0x43, 0x3a, 0xf4, 0xe2, 0x73, 0x9b, 0xec, 0x0d, 0xc1, 0x68, 0x04, 0x4b, 0xef, 0x49, 0x77, - 0xe0, 0x2e, 0x79, 0x82, 0x54, 0xa0, 0x06, 0x43, 0x8f, 0x58, 0xb8, 0x6d, 0xf0, 0x32, 0x1a, 0x4a, - 0x14, 0x13, 0x81, 0xa2, 0x77, 0xc2, 0x9c, 0x3a, 0x16, 0xd4, 0xb1, 0x87, 0x85, 0xba, 0xb3, 0x88, - 0xde, 0x91, 0x8e, 0xaf, 0x78, 0x2b, 0x0b, 0x6d, 0x72, 0x7a, 0xcc, 0xfc, 0x89, 0x6e, 0x1e, 0xca, - 0xaa, 0x12, 0x4a, 0x2e, 0x69, 0xb8, 0x26, 0xfb, 0xaf, 0x2d, 0x31, 0x42, 0x81, 0x75, 0x45, 0x0f, - 0x42, 0x81, 0x9b, 0x97, 0x93, 0xfd, 0xd6, 0xca, 0x3f, 0xe9, 0x25, 0xad, 0x24, 0x6f, 0x49, 0xe7, - 0xd1, 0x59, 0xf1, 0x68, 0x97, 0x05, 0x4b, 0xab, 0xd8, 0xfe, 0xe1, 0x57, 0x13, 0x47, 0xdf, 0x4d, - 0x1c, 0xfd, 0x34, 0x71, 0xf4, 0xf1, 0x1b, 0x6f, 0x25, 0xbb, 0xed, 0xce, 0xe5, 0x5f, 0x00, 0x00, - 0x00, 0xff, 0xff, 0xf8, 0xba, 0x29, 0x05, 0x98, 0x02, 0x00, 0x00, + // 297 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x90, 0x4f, 0x4b, 0xc3, 0x30, + 0x18, 0xc6, 0xad, 0x82, 0x42, 0x70, 0x4e, 0x82, 0x7a, 0x98, 0xd0, 0xb3, 0xa7, 0x04, 0xf5, 0xe0, + 0x9f, 0x93, 0x0e, 0x64, 0x1e, 0x9c, 0x88, 0x9b, 0x1f, 0x20, 0xed, 0x62, 0x1a, 0xda, 0xe5, 0x2d, + 0xe9, 0x5b, 0x86, 0xdf, 0xc4, 0x4f, 0xe3, 0xd9, 0xa3, 0x1f, 0x41, 0xea, 0x17, 0x91, 0x35, 0x89, + 0xc7, 0xad, 0x1e, 0xca, 0xc3, 0x0b, 0xfd, 0xfd, 0xc8, 0xf3, 0x90, 0x53, 0xa5, 0x31, 0xab, 0x13, + 0x96, 0xc2, 0x9c, 0x4f, 0x33, 0x39, 0xcd, 0xb4, 0x51, 0xd5, 0xa3, 0xc4, 0x05, 0xd8, 0x9c, 0x23, + 0x1a, 0x2e, 0x4a, 0xcd, 0x0d, 0xa4, 0xcb, 0x8f, 0x95, 0x16, 0x10, 0xe8, 0x96, 0x81, 0x74, 0x70, + 0xd5, 0x85, 0x53, 0x02, 0xe5, 0x42, 0xbc, 0x85, 0x74, 0xfc, 0xe0, 0xa2, 0x0b, 0x6a, 0xa1, 0x46, + 0x69, 0x7d, 0xfc, 0x07, 0x4c, 0x2c, 0xe4, 0xd2, 0xfa, 0xf0, 0x60, 0xa7, 0xc7, 0x66, 0xc2, 0xcc, + 0x0a, 0x69, 0x43, 0x7a, 0xf4, 0x58, 0x01, 0xa8, 0x42, 0xf2, 0xf6, 0x4a, 0xea, 0x57, 0x2e, 0xe7, + 0x25, 0xfa, 0x26, 0x67, 0x1f, 0x9b, 0x64, 0x67, 0x0c, 0x46, 0x23, 0x58, 0x7a, 0x4b, 0xfa, 0x23, + 0x57, 0xf3, 0x01, 0x52, 0x81, 0x1a, 0x0c, 0x3d, 0x60, 0xa1, 0xf8, 0xe8, 0x69, 0x32, 0x96, 0x28, + 0x66, 0x02, 0xc5, 0xe0, 0x88, 0x39, 0x25, 0x0b, 0x4a, 0x76, 0xb7, 0x54, 0x9e, 0x44, 0xf4, 0x86, + 0xf4, 0xbc, 0xe2, 0xa5, 0x2c, 0xb4, 0xc9, 0xe9, 0x21, 0xf3, 0xfd, 0xdd, 0x3d, 0x96, 0x55, 0x25, + 0x94, 0x5c, 0x61, 0xb8, 0x24, 0xbb, 0xcf, 0x2d, 0x31, 0x41, 0x81, 0x75, 0x45, 0xf7, 0x82, 0xc0, + 0xdd, 0xab, 0xc9, 0x61, 0x3b, 0xd9, 0x1f, 0xe9, 0x17, 0x5c, 0x4b, 0x5e, 0x93, 0xde, 0xbd, 0x9b, + 0xcc, 0xa3, 0x7d, 0x16, 0x26, 0x5c, 0xc7, 0x0e, 0xf7, 0x3f, 0x9b, 0x38, 0xfa, 0x6a, 0xe2, 0xe8, + 0xbb, 0x89, 0xa3, 0xf7, 0x9f, 0x78, 0x23, 0xd9, 0x6e, 0xff, 0x39, 0xff, 0x0d, 0x00, 0x00, 0xff, + 0xff, 0xd4, 0xfe, 0xf0, 0x46, 0x98, 0x02, 0x00, 0x00, } diff --git a/api/noc/noc.proto b/api/noc/noc.proto index fef635266..d31ebf977 100644 --- a/api/noc/noc.proto +++ b/api/noc/noc.proto @@ -3,12 +3,13 @@ syntax = "proto3"; -import "google/protobuf/empty.proto"; import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; import "github.com/TheThingsNetwork/ttn/api/router/router.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; +import "google/protobuf/empty.proto"; + package noc; service Monitor { From b9bc4ce5e38e6a18245cafac737b106b392295dc Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:43:52 +0200 Subject: [PATCH 1744/2266] core/router/gateway.Gateway: add Token field --- core/router/gateway/gateway.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index aa39584cc..672805c02 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -26,4 +26,5 @@ type Gateway struct { Utilization Utilization Schedule Schedule LastSeen time.Time + Token string } From 0adb547ac24d9056315a8137c585daf4f0e93a4d Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Fri, 16 Sep 2016 09:50:30 +0200 Subject: [PATCH 1745/2266] core/router/server.go: use metadata instead of context, refactoring --- core/router/router.go | 2 + core/router/server.go | 86 +++++++++++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 20 deletions(-) diff --git a/core/router/router.go b/core/router/router.go index db648ceee..b4d5daa58 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -34,6 +34,8 @@ type Router interface { UnsubscribeDownlink(gatewayID string) error // Handle a device activation HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) + + getGateway(gatewayID string) *gateway.Gateway } type broker struct { diff --git a/core/router/server.go b/core/router/server.go index eb29f2c80..26ded0c74 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -21,39 +21,64 @@ type routerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining -func getGatewayFromMetadata(ctx context.Context) (gatewayID string, err error) { - md, ok := metadata.FromContext(ctx) - if !ok { - err = errors.NewErrInternal("Could not get metadata from context") +func metadataFromContext(ctx context.Context) (md metadata.MD, err error) { + var ok bool + if md, ok = metadata.FromContext(ctx); !ok { + return md, errors.NewErrInternal("Could not get metadata from context") + } + return md, nil +} + +func gatewayFromContext(ctx context.Context) (gatewayID string, err error) { + md, err := metadataFromContext(ctx) + if err != nil { return } + + return getGatewayFromMetadata(md) +} + +func gatewayFromMetadata(md metadata.MD) (gatewayID string, err error) { id, ok := md["id"] if !ok || len(id) < 1 { err = errors.NewErrInvalidArgument("Metadata", "id missing") return } - gatewayID = id[0] + return id[0], nil +} +func getTokenFromMetadata(md metadata.MD) (string, error) { token, ok := md["token"] if !ok || len(token) < 1 { - err = errors.NewErrInvalidArgument("Metadata", "token missing") - return + return "", errors.NewErrInvalidArgument("Metadata", "token missing") } + if token[0] != "token" { // TODO: Validate Token - err = errors.NewErrPermissionDenied("Gateway token not authorized") - return + return "", errors.NewErrPermissionDenied("Gateway token not authorized") } - - return + return token[0], nil } // GatewayStatus implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { - gatewayID, err := getGatewayFromMetadata(stream.Context()) + md, err := metadataFromContext(stream.Context()) + + id, err := getGatewayFromMetadata(md) if err != nil { return errors.BuildGRPCError(err) } + + token, err := getTokenFromMetadata(md) + if err != nil { + return err + } + + //TODO Validate token + + //r.router.getGateway(id).Token = token + r.router.getGateway(id).Token = token // FIXME + for { status, err := stream.Recv() if err == io.EOF { @@ -65,16 +90,29 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if !status.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") } - go r.router.HandleGatewayStatus(gatewayID, status) + go r.router.HandleGatewayStatus(id, status) } } // Uplink implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { - gatewayID, err := getGatewayFromMetadata(stream.Context()) + md, err := metadataFromContext(stream.Context()) + + id, err := getGatewayFromMetadata(md) + if err != nil { + return err + } + + token, err := getTokenFromMetadata(md) if err != nil { return errors.BuildGRPCError(err) } + + //TODO Validate token + + //r.router.getGateway(id).Token = token + r.router.getGateway(id).Token = token // FIXME + for { uplink, err := stream.Recv() if err == io.EOF { @@ -86,21 +124,27 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if !uplink.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Uplink") } - go r.router.HandleUplink(gatewayID, uplink) + go r.router.HandleUplink(id, uplink) } } // Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { - gatewayID, err := getGatewayFromMetadata(stream.Context()) + md, err := metadataFromContext(stream.Context()) + + id, err := getGatewayFromMetadata(md) if err != nil { return err } - downlinkChannel, err := r.router.SubscribeDownlink(gatewayID) + + // TODO validate token + + downlinkChannel, err := r.router.SubscribeDownlink(id) if err != nil { return err } - defer r.router.UnsubscribeDownlink(gatewayID) + defer r.router.UnsubscribeDownlink(id) + for { if downlinkChannel == nil { return nil @@ -118,14 +162,16 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { - gatewayID, err := getGatewayFromMetadata(ctx) + md, err := metadataFromContext(ctx) + + id, err := getGatewayFromMetadata(md) if err != nil { return nil, err } if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return r.router.HandleActivation(gatewayID, req) + return r.router.HandleActivation(id, req) } // RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) From 8352fe2890e35bd6e3331a48842dd857dbaa4248 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Fri, 16 Sep 2016 01:22:20 +0200 Subject: [PATCH 1746/2266] getGatewayFromMetadata -> gatewayFromMetadata, tokenFromMetadata --- core/router/server.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index 26ded0c74..f70cc8aa3 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -35,7 +35,7 @@ func gatewayFromContext(ctx context.Context) (gatewayID string, err error) { return } - return getGatewayFromMetadata(md) + return gatewayFromMetadata(md) } func gatewayFromMetadata(md metadata.MD) (gatewayID string, err error) { @@ -47,7 +47,7 @@ func gatewayFromMetadata(md metadata.MD) (gatewayID string, err error) { return id[0], nil } -func getTokenFromMetadata(md metadata.MD) (string, error) { +func tokenFromMetadata(md metadata.MD) (string, error) { token, ok := md["token"] if !ok || len(token) < 1 { return "", errors.NewErrInvalidArgument("Metadata", "token missing") @@ -64,12 +64,12 @@ func getTokenFromMetadata(md metadata.MD) (string, error) { func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { md, err := metadataFromContext(stream.Context()) - id, err := getGatewayFromMetadata(md) + id, err := gatewayFromMetadata(md) if err != nil { return errors.BuildGRPCError(err) } - token, err := getTokenFromMetadata(md) + token, err := tokenFromMetadata(md) if err != nil { return err } @@ -98,12 +98,12 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { md, err := metadataFromContext(stream.Context()) - id, err := getGatewayFromMetadata(md) + id, err := gatewayFromMetadata(md) if err != nil { return err } - token, err := getTokenFromMetadata(md) + token, err := tokenFromMetadata(md) if err != nil { return errors.BuildGRPCError(err) } @@ -132,7 +132,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { md, err := metadataFromContext(stream.Context()) - id, err := getGatewayFromMetadata(md) + id, err := gatewayFromMetadata(md) if err != nil { return err } @@ -164,7 +164,7 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { md, err := metadataFromContext(ctx) - id, err := getGatewayFromMetadata(md) + id, err := gatewayFromMetadata(md) if err != nil { return nil, err } From e22d43f23393de9d9156e54f02f6ca931ea3e3c1 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:34:42 +0200 Subject: [PATCH 1747/2266] noc.proto: GatewayStatus, GatewayDownlink --- api/noc/noc.pb.go | 153 +++++++++++++++++++++++++++++++++------------- api/noc/noc.proto | 3 +- 2 files changed, 112 insertions(+), 44 deletions(-) diff --git a/api/noc/noc.pb.go b/api/noc/noc.pb.go index 4eee7cbc0..85d0977bd 100644 --- a/api/noc/noc.pb.go +++ b/api/noc/noc.pb.go @@ -48,8 +48,9 @@ const _ = grpc.SupportPackageIsVersion3 // Client API for Monitor service type MonitorClient interface { - GatewayLocation(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayLocationClient, error) + GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) + GatewayDownlink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayDownlinkClient, error) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) @@ -63,30 +64,30 @@ func NewMonitorClient(cc *grpc.ClientConn) MonitorClient { return &monitorClient{cc} } -func (c *monitorClient) GatewayLocation(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayLocationClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/noc.Monitor/GatewayLocation", opts...) +func (c *monitorClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/noc.Monitor/GatewayStatus", opts...) if err != nil { return nil, err } - x := &monitorGatewayLocationClient{stream} + x := &monitorGatewayStatusClient{stream} return x, nil } -type Monitor_GatewayLocationClient interface { - Send(*gateway.GPSMetadata) error +type Monitor_GatewayStatusClient interface { + Send(*gateway.Status) error CloseAndRecv() (*google_protobuf1.Empty, error) grpc.ClientStream } -type monitorGatewayLocationClient struct { +type monitorGatewayStatusClient struct { grpc.ClientStream } -func (x *monitorGatewayLocationClient) Send(m *gateway.GPSMetadata) error { +func (x *monitorGatewayStatusClient) Send(m *gateway.Status) error { return x.ClientStream.SendMsg(m) } -func (x *monitorGatewayLocationClient) CloseAndRecv() (*google_protobuf1.Empty, error) { +func (x *monitorGatewayStatusClient) CloseAndRecv() (*google_protobuf1.Empty, error) { if err := x.ClientStream.CloseSend(); err != nil { return nil, err } @@ -131,8 +132,42 @@ func (x *monitorGatewayUplinkClient) CloseAndRecv() (*google_protobuf1.Empty, er return m, nil } +func (c *monitorClient) GatewayDownlink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayDownlinkClient, error) { + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[2], c.cc, "/noc.Monitor/GatewayDownlink", opts...) + if err != nil { + return nil, err + } + x := &monitorGatewayDownlinkClient{stream} + return x, nil +} + +type Monitor_GatewayDownlinkClient interface { + Send(*router.DownlinkMessage) error + CloseAndRecv() (*google_protobuf1.Empty, error) + grpc.ClientStream +} + +type monitorGatewayDownlinkClient struct { + grpc.ClientStream +} + +func (x *monitorGatewayDownlinkClient) Send(m *router.DownlinkMessage) error { + return x.ClientStream.SendMsg(m) +} + +func (x *monitorGatewayDownlinkClient) CloseAndRecv() (*google_protobuf1.Empty, error) { + if err := x.ClientStream.CloseSend(); err != nil { + return nil, err + } + m := new(google_protobuf1.Empty) + if err := x.ClientStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func (c *monitorClient) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[2], c.cc, "/noc.Monitor/RouterStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[3], c.cc, "/noc.Monitor/RouterStatus", opts...) if err != nil { return nil, err } @@ -166,7 +201,7 @@ func (x *monitorRouterStatusClient) CloseAndRecv() (*google_protobuf1.Empty, err } func (c *monitorClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[3], c.cc, "/noc.Monitor/BrokerStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[4], c.cc, "/noc.Monitor/BrokerStatus", opts...) if err != nil { return nil, err } @@ -200,7 +235,7 @@ func (x *monitorBrokerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, err } func (c *monitorClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[4], c.cc, "/noc.Monitor/HandlerStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[5], c.cc, "/noc.Monitor/HandlerStatus", opts...) if err != nil { return nil, err } @@ -236,8 +271,9 @@ func (x *monitorHandlerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, er // Server API for Monitor service type MonitorServer interface { - GatewayLocation(Monitor_GatewayLocationServer) error + GatewayStatus(Monitor_GatewayStatusServer) error GatewayUplink(Monitor_GatewayUplinkServer) error + GatewayDownlink(Monitor_GatewayDownlinkServer) error RouterStatus(Monitor_RouterStatusServer) error BrokerStatus(Monitor_BrokerStatusServer) error HandlerStatus(Monitor_HandlerStatusServer) error @@ -247,26 +283,26 @@ func RegisterMonitorServer(s *grpc.Server, srv MonitorServer) { s.RegisterService(&_Monitor_serviceDesc, srv) } -func _Monitor_GatewayLocation_Handler(srv interface{}, stream grpc.ServerStream) error { - return srv.(MonitorServer).GatewayLocation(&monitorGatewayLocationServer{stream}) +func _Monitor_GatewayStatus_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayStatus(&monitorGatewayStatusServer{stream}) } -type Monitor_GatewayLocationServer interface { +type Monitor_GatewayStatusServer interface { SendAndClose(*google_protobuf1.Empty) error - Recv() (*gateway.GPSMetadata, error) + Recv() (*gateway.Status, error) grpc.ServerStream } -type monitorGatewayLocationServer struct { +type monitorGatewayStatusServer struct { grpc.ServerStream } -func (x *monitorGatewayLocationServer) SendAndClose(m *google_protobuf1.Empty) error { +func (x *monitorGatewayStatusServer) SendAndClose(m *google_protobuf1.Empty) error { return x.ServerStream.SendMsg(m) } -func (x *monitorGatewayLocationServer) Recv() (*gateway.GPSMetadata, error) { - m := new(gateway.GPSMetadata) +func (x *monitorGatewayStatusServer) Recv() (*gateway.Status, error) { + m := new(gateway.Status) if err := x.ServerStream.RecvMsg(m); err != nil { return nil, err } @@ -299,6 +335,32 @@ func (x *monitorGatewayUplinkServer) Recv() (*router.UplinkMessage, error) { return m, nil } +func _Monitor_GatewayDownlink_Handler(srv interface{}, stream grpc.ServerStream) error { + return srv.(MonitorServer).GatewayDownlink(&monitorGatewayDownlinkServer{stream}) +} + +type Monitor_GatewayDownlinkServer interface { + SendAndClose(*google_protobuf1.Empty) error + Recv() (*router.DownlinkMessage, error) + grpc.ServerStream +} + +type monitorGatewayDownlinkServer struct { + grpc.ServerStream +} + +func (x *monitorGatewayDownlinkServer) SendAndClose(m *google_protobuf1.Empty) error { + return x.ServerStream.SendMsg(m) +} + +func (x *monitorGatewayDownlinkServer) Recv() (*router.DownlinkMessage, error) { + m := new(router.DownlinkMessage) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + func _Monitor_RouterStatus_Handler(srv interface{}, stream grpc.ServerStream) error { return srv.(MonitorServer).RouterStatus(&monitorRouterStatusServer{stream}) } @@ -383,8 +445,8 @@ var _Monitor_serviceDesc = grpc.ServiceDesc{ Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ { - StreamName: "GatewayLocation", - Handler: _Monitor_GatewayLocation_Handler, + StreamName: "GatewayStatus", + Handler: _Monitor_GatewayStatus_Handler, ClientStreams: true, }, { @@ -392,6 +454,11 @@ var _Monitor_serviceDesc = grpc.ServiceDesc{ Handler: _Monitor_GatewayUplink_Handler, ClientStreams: true, }, + { + StreamName: "GatewayDownlink", + Handler: _Monitor_GatewayDownlink_Handler, + ClientStreams: true, + }, { StreamName: "RouterStatus", Handler: _Monitor_RouterStatus_Handler, @@ -416,24 +483,24 @@ func init() { } var fileDescriptorNoc = []byte{ - // 297 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x90, 0x4f, 0x4b, 0xc3, 0x30, - 0x18, 0xc6, 0xad, 0x82, 0x42, 0x70, 0x4e, 0x82, 0x7a, 0x98, 0xd0, 0xb3, 0xa7, 0x04, 0xf5, 0xe0, - 0x9f, 0x93, 0x0e, 0x64, 0x1e, 0x9c, 0x88, 0x9b, 0x1f, 0x20, 0xed, 0x62, 0x1a, 0xda, 0xe5, 0x2d, - 0xe9, 0x5b, 0x86, 0xdf, 0xc4, 0x4f, 0xe3, 0xd9, 0xa3, 0x1f, 0x41, 0xea, 0x17, 0x91, 0x35, 0x89, - 0xc7, 0xad, 0x1e, 0xca, 0xc3, 0x0b, 0xfd, 0xfd, 0xc8, 0xf3, 0x90, 0x53, 0xa5, 0x31, 0xab, 0x13, - 0x96, 0xc2, 0x9c, 0x4f, 0x33, 0x39, 0xcd, 0xb4, 0x51, 0xd5, 0xa3, 0xc4, 0x05, 0xd8, 0x9c, 0x23, - 0x1a, 0x2e, 0x4a, 0xcd, 0x0d, 0xa4, 0xcb, 0x8f, 0x95, 0x16, 0x10, 0xe8, 0x96, 0x81, 0x74, 0x70, - 0xd5, 0x85, 0x53, 0x02, 0xe5, 0x42, 0xbc, 0x85, 0x74, 0xfc, 0xe0, 0xa2, 0x0b, 0x6a, 0xa1, 0x46, - 0x69, 0x7d, 0xfc, 0x07, 0x4c, 0x2c, 0xe4, 0xd2, 0xfa, 0xf0, 0x60, 0xa7, 0xc7, 0x66, 0xc2, 0xcc, - 0x0a, 0x69, 0x43, 0x7a, 0xf4, 0x58, 0x01, 0xa8, 0x42, 0xf2, 0xf6, 0x4a, 0xea, 0x57, 0x2e, 0xe7, - 0x25, 0xfa, 0x26, 0x67, 0x1f, 0x9b, 0x64, 0x67, 0x0c, 0x46, 0x23, 0x58, 0x7a, 0x4b, 0xfa, 0x23, - 0x57, 0xf3, 0x01, 0x52, 0x81, 0x1a, 0x0c, 0x3d, 0x60, 0xa1, 0xf8, 0xe8, 0x69, 0x32, 0x96, 0x28, - 0x66, 0x02, 0xc5, 0xe0, 0x88, 0x39, 0x25, 0x0b, 0x4a, 0x76, 0xb7, 0x54, 0x9e, 0x44, 0xf4, 0x86, - 0xf4, 0xbc, 0xe2, 0xa5, 0x2c, 0xb4, 0xc9, 0xe9, 0x21, 0xf3, 0xfd, 0xdd, 0x3d, 0x96, 0x55, 0x25, - 0x94, 0x5c, 0x61, 0xb8, 0x24, 0xbb, 0xcf, 0x2d, 0x31, 0x41, 0x81, 0x75, 0x45, 0xf7, 0x82, 0xc0, - 0xdd, 0xab, 0xc9, 0x61, 0x3b, 0xd9, 0x1f, 0xe9, 0x17, 0x5c, 0x4b, 0x5e, 0x93, 0xde, 0xbd, 0x9b, - 0xcc, 0xa3, 0x7d, 0x16, 0x26, 0x5c, 0xc7, 0x0e, 0xf7, 0x3f, 0x9b, 0x38, 0xfa, 0x6a, 0xe2, 0xe8, - 0xbb, 0x89, 0xa3, 0xf7, 0x9f, 0x78, 0x23, 0xd9, 0x6e, 0xff, 0x39, 0xff, 0x0d, 0x00, 0x00, 0xff, - 0xff, 0xd4, 0xfe, 0xf0, 0x46, 0x98, 0x02, 0x00, 0x00, + // 300 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x91, 0x41, 0x4a, 0xc4, 0x30, + 0x14, 0x86, 0xad, 0x82, 0x42, 0x98, 0x71, 0xa4, 0xa0, 0x42, 0x85, 0xae, 0x5d, 0x25, 0xa8, 0x0b, + 0xc7, 0x59, 0xc9, 0x30, 0xa2, 0x9b, 0x71, 0xa1, 0xe3, 0x01, 0xd2, 0x1a, 0xd3, 0xd2, 0x4e, 0x5e, + 0x49, 0x5f, 0x29, 0xde, 0xc4, 0x23, 0xb9, 0xf4, 0x08, 0x52, 0xcf, 0x21, 0xc8, 0x34, 0x49, 0x97, + 0xb6, 0xb3, 0x28, 0x3f, 0x09, 0xfd, 0x3e, 0xf2, 0xbf, 0x47, 0x2e, 0x64, 0x8a, 0x49, 0x15, 0xd1, + 0x18, 0xd6, 0x6c, 0x95, 0x88, 0x55, 0x92, 0x2a, 0x59, 0x3e, 0x0a, 0xac, 0x41, 0x67, 0x0c, 0x51, + 0x31, 0x5e, 0xa4, 0x4c, 0x41, 0xbc, 0xf9, 0x68, 0xa1, 0x01, 0xc1, 0xdf, 0x53, 0x10, 0x07, 0x37, + 0x43, 0x38, 0xc9, 0x51, 0xd4, 0xfc, 0xdd, 0xa5, 0xe1, 0x83, 0xeb, 0x21, 0xa8, 0x86, 0x0a, 0x85, + 0xb6, 0xb1, 0x0d, 0x18, 0x69, 0xc8, 0x84, 0xb6, 0x61, 0xc1, 0x41, 0x8f, 0x4d, 0xb8, 0x7a, 0xcd, + 0x85, 0x76, 0x69, 0xd1, 0x33, 0x09, 0x20, 0x73, 0xc1, 0xda, 0x53, 0x54, 0xbd, 0x31, 0xb1, 0x2e, + 0xd0, 0x36, 0xb9, 0xfc, 0xdd, 0x25, 0x07, 0x4b, 0x50, 0x29, 0x82, 0xf6, 0x67, 0x64, 0x7c, 0x6f, + 0x6a, 0x3e, 0x23, 0xc7, 0xaa, 0xf4, 0x27, 0xd4, 0xd5, 0x36, 0x17, 0xc1, 0x09, 0x35, 0x2e, 0xea, + 0x5c, 0xf4, 0x6e, 0xe3, 0x3a, 0xf7, 0xfc, 0xdb, 0x8e, 0x7d, 0x29, 0xf2, 0x54, 0x65, 0xfe, 0x31, + 0xb5, 0xc5, 0xcd, 0x79, 0x29, 0xca, 0x92, 0x4b, 0xf1, 0x8f, 0x61, 0x41, 0x26, 0xd6, 0xb0, 0x80, + 0x5a, 0xb5, 0x8e, 0x53, 0xe7, 0x70, 0x37, 0xfd, 0x96, 0x29, 0x19, 0x3d, 0xb5, 0x8c, 0xad, 0x70, + 0xe8, 0x14, 0xbd, 0x0d, 0xa6, 0x64, 0x34, 0x6f, 0x27, 0xde, 0x91, 0x76, 0x01, 0xbd, 0xe4, 0x8c, + 0x8c, 0x1f, 0xcc, 0xc4, 0xbb, 0xb9, 0xb9, 0x0d, 0xf4, 0xb1, 0xf3, 0xa3, 0xcf, 0x26, 0xf4, 0xbe, + 0x9a, 0xd0, 0xfb, 0x6e, 0x42, 0xef, 0xe3, 0x27, 0xdc, 0x89, 0xf6, 0xdb, 0x7f, 0xae, 0xfe, 0x02, + 0x00, 0x00, 0xff, 0xff, 0x26, 0x4e, 0x3b, 0x20, 0xd7, 0x02, 0x00, 0x00, } diff --git a/api/noc/noc.proto b/api/noc/noc.proto index d31ebf977..0e5a33d01 100644 --- a/api/noc/noc.proto +++ b/api/noc/noc.proto @@ -13,8 +13,9 @@ import "google/protobuf/empty.proto"; package noc; service Monitor { - rpc GatewayLocation(stream gateway.GPSMetadata) returns (google.protobuf.Empty); + rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); rpc GatewayUplink(stream router.UplinkMessage) returns (google.protobuf.Empty); + rpc GatewayDownlink(stream router.DownlinkMessage) returns (google.protobuf.Empty); rpc RouterStatus(stream router.Status) returns (google.protobuf.Empty); rpc BrokerStatus(stream broker.Status) returns (google.protobuf.Empty); From 9a8fc1a7f593d50ad2340254033a631b7a10ac04 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:35:51 +0200 Subject: [PATCH 1748/2266] core.Component: Noc field --- core/component.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/component.go b/core/component.go index bfa35f090..9b94190d5 100644 --- a/core/component.go +++ b/core/component.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_noc "github.com/TheThingsNetwork/ttn/api/noc" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/TheThingsNetwork/ttn/utils/security" @@ -129,6 +130,7 @@ const ( type Component struct { Identity *pb_discovery.Announcement Discovery pb_discovery.Client + Noc pb_noc.MonitorClient Ctx log.Interface AccessToken string privateKey string From 27cf5a1d44157cf5a37f54315baba8607218a9a3 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:40:29 +0200 Subject: [PATCH 1749/2266] consistent downlink spelling --- core/router/downlink.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index 2a2bcd374..71b6cbacc 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -30,13 +30,13 @@ func (r *router) SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage if fromSchedule := gateway.Schedule.Subscribe(); fromSchedule != nil { toGateway := make(chan *pb.DownlinkMessage) go func() { - ctx.Debug("Activate Downlink") + ctx.Debug("Activate downlink") for message := range fromSchedule { gateway.Utilization.AddTx(message) - ctx.Debug("Send Downlink") + ctx.Debug("Send downlink") toGateway <- message } - ctx.Debug("Deactivate Downlink") + ctx.Debug("Deactivate downlink") close(toGateway) }() return toGateway, nil From 28ebfa9ab3b7ffcd38cef5c6c2df28b119409373 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:43:02 +0200 Subject: [PATCH 1750/2266] core/router/gateway.Gateway: add Ctx field --- core/router/gateway/gateway.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 672805c02..e4c46a62c 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -11,11 +11,13 @@ import ( // NewGateway creates a new in-memory Gateway structure func NewGateway(ctx log.Interface, id string) *Gateway { + ctx = ctx.WithField("GatewayID", id) return &Gateway{ ID: id, Status: NewStatusStore(), Utilization: NewUtilization(), - Schedule: NewSchedule(ctx.WithField("GatewayID", id)), + Schedule: NewSchedule(ctx), + Ctx: ctx, } } @@ -27,4 +29,6 @@ type Gateway struct { Schedule Schedule LastSeen time.Time Token string + + Ctx log.Interface } From 0f812bfe83a6b3250725a37e3922ec4fd9b73f79 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:46:19 +0200 Subject: [PATCH 1751/2266] core/router/gateway.Gateway: handlers --- core/router/downlink.go | 15 ++------------- core/router/gateway/gateway.go | 25 +++++++++++++++++++++++++ core/router/gateway_status.go | 11 ++--------- core/router/uplink.go | 10 +++++----- 4 files changed, 34 insertions(+), 27 deletions(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index 71b6cbacc..0a563e98a 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -49,12 +49,8 @@ func (r *router) UnsubscribeDownlink(gatewayID string) error { return nil } -func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { +func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) (err error) { option := downlink.DownlinkOption - ctx := r.Ctx.WithFields(log.Fields{ - "GatewayID": option.GatewayId, - }) - gateway := r.getGateway(option.GatewayId) downlinkMessage := &pb.DownlinkMessage{ Payload: downlink.Payload, @@ -66,15 +62,8 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { if r.Component != nil && r.Component.Identity != nil { identifier = strings.TrimPrefix(option.Identifier, fmt.Sprintf("%s:", r.Component.Identity.Id)) } - ctx = ctx.WithField("Identifier", identifier) - - err := gateway.Schedule.Schedule(identifier, downlinkMessage) - if err != nil { - ctx.WithError(err).Warn("Could not schedule Downlink") - return err - } - return nil + return r.getGateway(downlink.DownlinkOption.GatewayId).HandleDownlink(identifier, downlinkMessage) } func guessRegion(frequency uint64) string { diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index e4c46a62c..c0311bbeb 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -6,6 +6,8 @@ package gateway import ( "time" + pb "github.com/TheThingsNetwork/ttn/api/gateway" + pb_router "github.com/TheThingsNetwork/ttn/api/router" "github.com/apex/log" ) @@ -32,3 +34,26 @@ type Gateway struct { Ctx log.Interface } + +func (g *Gateway) updateTimestamp() { + g.LastSeen = time.Now() +} + +func (g *Gateway) HandleStatus(status *pb.Status) (err error) { + g.updateTimestamp() + return g.Status.Update(status) +} + +func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { + g.updateTimestamp() + g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) + return g.Utilization.AddRx(uplink) +} + +func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.DownlinkMessage) (err error) { + ctx := g.Ctx.WithField("Identifier", identifier) + if err = g.Schedule.Schedule(identifier, downlink); err != nil { + ctx.WithError(err).Warn("Could not schedule downlink") + } + return err +} diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index 740cfb663..ad3076c44 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -9,9 +9,8 @@ import ( pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" ) -func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) error { +func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) (err error) { ctx := r.Ctx.WithField("GatewayID", gatewayID) - var err error start := time.Now() defer func() { if err != nil { @@ -21,11 +20,5 @@ func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status } }() - gateway := r.getGateway(gatewayID) - gateway.LastSeen = time.Now() - err = gateway.Status.Update(status) - if err != nil { - return err - } - return nil + return r.getGateway(gatewayID).HandleStatus(status) } diff --git a/core/router/uplink.go b/core/router/uplink.go index 95a43d009..48b7abc24 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -15,9 +15,8 @@ import ( "github.com/brocaar/lorawan" ) -func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) error { +func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err error) { ctx := r.Ctx.WithField("GatewayID", gatewayID) - var err error start := time.Now() defer func() { if err != nil { @@ -81,9 +80,10 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) error ctx = ctx.WithField("DevAddr", devAddr) gateway := r.getGateway(gatewayID) - gateway.LastSeen = time.Now() - gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) - gateway.Utilization.AddRx(uplink) + + if err = gateway.HandleUplink(uplink); err != nil { + return err + } var downlinkOptions []*pb_broker.DownlinkOption if gateway.Schedule.IsActive() { From f3accb1b051a299e10df0a7b06c8e092f14d8894 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 14:48:14 +0200 Subject: [PATCH 1752/2266] core/router/gateway: NOC integration --- core/router/gateway/gateway.go | 42 ++++++++++++++ core/router/gateway/monitor.go | 101 +++++++++++++++++++++++++++++++++ core/router/router.go | 16 +++++- 3 files changed, 156 insertions(+), 3 deletions(-) create mode 100644 core/router/gateway/monitor.go diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index c0311bbeb..2083aafac 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -8,6 +8,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/gateway" pb_router "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -32,6 +33,8 @@ type Gateway struct { LastSeen time.Time Token string + monitor *monitorConn + Ctx log.Interface } @@ -40,17 +43,56 @@ func (g *Gateway) updateTimestamp() { } func (g *Gateway) HandleStatus(status *pb.Status) (err error) { + if g.monitor != nil { + go func() { + cl, err := g.statusMonitor() + if err != nil { + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish status connection to the NOC") + } + + if err = cl.Send(status); err != nil { + g.Ctx.WithError(errors.FromGRPCError(err)).Error("NOC status push failed") + } + }() + } + g.updateTimestamp() return g.Status.Update(status) } func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { + if g.monitor != nil { + go func() { + cl, err := g.uplinkMonitor() + if err != nil { + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish uplink connection to the NOC") + } + + if err = cl.Send(uplink); err != nil { + g.Ctx.WithError(errors.FromGRPCError(err)).Error("NOC uplink push failed") + } + }() + } + g.updateTimestamp() g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) return g.Utilization.AddRx(uplink) } func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.DownlinkMessage) (err error) { + if g.monitor != nil { + go func() { + cl, err := g.downlinkMonitor() + if err != nil { + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish downlink connection to the NOC") + } + + if err = cl.Send(downlink); err != nil { + g.Ctx.WithError(errors.FromGRPCError(err)).Error("NOC downlink push failed") + } + }() + } + ctx := g.Ctx.WithField("Identifier", identifier) if err = g.Schedule.Schedule(identifier, downlink); err != nil { ctx.WithError(err).Warn("Could not schedule downlink") diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go new file mode 100644 index 000000000..155ac9f81 --- /dev/null +++ b/core/router/gateway/monitor.go @@ -0,0 +1,101 @@ +package gateway + +import ( + "context" + "sync" + + pb_noc "github.com/TheThingsNetwork/ttn/api/noc" +) + +func (g Gateway) monitorContext() (ctx context.Context) { + ctx = context.WithValue(context.Background(), "id", g.ID) + ctx = context.WithValue(ctx, "token", g.Token) + return ctx +} + +type monitorConn struct { + client pb_noc.MonitorClient + + uplink struct { + client pb_noc.Monitor_GatewayUplinkClient + sync.Mutex + } + downlink struct { + client pb_noc.Monitor_GatewayDownlinkClient + sync.Mutex + } + status struct { + client pb_noc.Monitor_GatewayStatusClient + sync.Mutex + } +} + +func (g *Gateway) SetMonitor(client pb_noc.MonitorClient) { + g.monitor = &monitorConn{client: client} +} + +func (g *Gateway) uplinkMonitor() (client pb_noc.Monitor_GatewayUplinkClient, err error) { + g.monitor.uplink.Lock() + defer g.monitor.uplink.Unlock() + + if g.monitor.uplink.client != nil { + return g.monitor.uplink.client, nil + } + return g.connectUplinkMonitor() +} + +func (g *Gateway) downlinkMonitor() (client pb_noc.Monitor_GatewayDownlinkClient, err error) { + g.monitor.downlink.Lock() + defer g.monitor.downlink.Unlock() + + if g.monitor.downlink.client != nil { + return g.monitor.downlink.client, nil + } + return g.connectDownlinkMonitor() +} + +func (g *Gateway) statusMonitor() (client pb_noc.Monitor_GatewayStatusClient, err error) { + g.monitor.status.Lock() + defer g.monitor.status.Unlock() + + if g.monitor.status.client != nil { + return g.monitor.status.client, nil + } + return g.connectStatusMonitor() +} + +func (g *Gateway) connectUplinkMonitor() (client pb_noc.Monitor_GatewayUplinkClient, err error) { + if client, err = connectUplinkMonitor(g.monitorContext(), g.monitor.client); err != nil { + return nil, err + } + g.monitor.uplink.client = client + return client, nil +} + +func (g *Gateway) connectDownlinkMonitor() (client pb_noc.Monitor_GatewayDownlinkClient, err error) { + if client, err = connectDownlinkMonitor(g.monitorContext(), g.monitor.client); err != nil { + return nil, err + } + g.monitor.downlink.client = client + return client, nil +} + +func (g *Gateway) connectStatusMonitor() (client pb_noc.Monitor_GatewayStatusClient, err error) { + if client, err = connectStatusMonitor(g.monitorContext(), g.monitor.client); err != nil { + return nil, err + } + g.monitor.status.client = client + return client, nil +} + +func connectDownlinkMonitor(ctx context.Context, client pb_noc.MonitorClient) (pb_noc.Monitor_GatewayDownlinkClient, error) { + return client.GatewayDownlink(ctx) +} + +func connectUplinkMonitor(ctx context.Context, client pb_noc.MonitorClient) (pb_noc.Monitor_GatewayUplinkClient, error) { + return client.GatewayUplink(ctx) +} + +func connectStatusMonitor(ctx context.Context, client pb_noc.MonitorClient) (pb_noc.Monitor_GatewayStatusClient, error) { + return client.GatewayStatus(ctx) +} diff --git a/core/router/router.go b/core/router/router.go index b4d5daa58..cd0ab85c5 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -79,6 +79,7 @@ func (r *router) Init(c *core.Component) error { return err } r.Discovery.GetAll("broker") // Update cache + go func() { for range time.Tick(5 * time.Second) { r.tickGateways() @@ -100,10 +101,19 @@ func (r *router) getGateway(id string) *gateway.Gateway { // If it doesn't we still have to lock r.gatewaysLock.Lock() defer r.gatewaysLock.Unlock() - if _, ok := r.gateways[id]; !ok { - r.gateways[id] = gateway.NewGateway(r.Ctx, id) + + gtw, ok = r.gateways[id] + if !ok { + gtw = gateway.NewGateway(r.Ctx, id) + + if r.Component.Noc != nil { + gtw.SetMonitor(r.Component.Noc) + } + + r.gateways[id] = gtw } - return r.gateways[id] + + return gtw } // getBroker gets or creates a broker association and returns the broker From b8e3a44e989ae3157bbaa95e53afb2afbb4915a9 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 15:53:43 +0200 Subject: [PATCH 1753/2266] core/router/server.go: error handling, token validation --- core/router/server.go | 42 ++++++++++++++++++------------------------ 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index f70cc8aa3..4a0d2b558 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -32,7 +32,12 @@ func metadataFromContext(ctx context.Context) (md metadata.MD, err error) { func gatewayFromContext(ctx context.Context) (gatewayID string, err error) { md, err := metadataFromContext(ctx) if err != nil { - return + return "", err + } + + // validate token + if _, err := tokenFromMetadata(md); err != nil { + return "", err } return gatewayFromMetadata(md) @@ -41,8 +46,7 @@ func gatewayFromContext(ctx context.Context) (gatewayID string, err error) { func gatewayFromMetadata(md metadata.MD) (gatewayID string, err error) { id, ok := md["id"] if !ok || len(id) < 1 { - err = errors.NewErrInvalidArgument("Metadata", "id missing") - return + return "", errors.NewErrInvalidArgument("Metadata", "id missing") } return id[0], nil } @@ -52,8 +56,7 @@ func tokenFromMetadata(md metadata.MD) (string, error) { if !ok || len(token) < 1 { return "", errors.NewErrInvalidArgument("Metadata", "token missing") } - - if token[0] != "token" { + if token != "token" { // TODO: Validate Token return "", errors.NewErrPermissionDenied("Gateway token not authorized") } @@ -71,13 +74,11 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { token, err := tokenFromMetadata(md) if err != nil { - return err + return errors.BuildGRPCError(err) } //TODO Validate token - - //r.router.getGateway(id).Token = token - r.router.getGateway(id).Token = token // FIXME + r.router.getGateway(id).Token = token for { status, err := stream.Recv() @@ -100,7 +101,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { id, err := gatewayFromMetadata(md) if err != nil { - return err + return errors.BuildGRPCError(err) } token, err := tokenFromMetadata(md) @@ -109,9 +110,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { } //TODO Validate token - - //r.router.getGateway(id).Token = token - r.router.getGateway(id).Token = token // FIXME + r.router.getGateway(id).Token = token for { uplink, err := stream.Recv() @@ -130,15 +129,11 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { // Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { - md, err := metadataFromContext(stream.Context()) - - id, err := gatewayFromMetadata(md) + id, err := gatewayFromContext(stream.Context()) if err != nil { - return err + return errors.BuildGRPCError(err) } - // TODO validate token - downlinkChannel, err := r.router.SubscribeDownlink(id) if err != nil { return err @@ -162,16 +157,15 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { - md, err := metadataFromContext(ctx) - - id, err := gatewayFromMetadata(md) + id, err := gatewayFromContext(stream.Context()) if err != nil { - return nil, err + return nil, errors.BuildGRPCError(err) } + if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return r.router.HandleActivation(id, req) + return r.router.HandleActivation(gatewayID, req) } // RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) From 42496f2ac893a9004bc6d3c0adeec0c7bcc61816 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 16:35:26 +0200 Subject: [PATCH 1754/2266] fix context import path --- core/router/gateway/monitor.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 155ac9f81..8dbe66a1a 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -1,9 +1,10 @@ package gateway import ( - "context" "sync" + context "golang.org/x/net/context" + pb_noc "github.com/TheThingsNetwork/ttn/api/noc" ) From eef07a947b5e85a6cc27ecb85ab82f1bc4ef7426 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 17:04:20 +0200 Subject: [PATCH 1755/2266] core/router/gateway/server: token validation --- core/router/server.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index 4a0d2b558..d6aa51ad6 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -35,11 +35,6 @@ func gatewayFromContext(ctx context.Context) (gatewayID string, err error) { return "", err } - // validate token - if _, err := tokenFromMetadata(md); err != nil { - return "", err - } - return gatewayFromMetadata(md) } @@ -56,10 +51,6 @@ func tokenFromMetadata(md metadata.MD) (string, error) { if !ok || len(token) < 1 { return "", errors.NewErrInvalidArgument("Metadata", "token missing") } - if token != "token" { - // TODO: Validate Token - return "", errors.NewErrPermissionDenied("Gateway token not authorized") - } return token[0], nil } @@ -76,6 +67,10 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if err != nil { return errors.BuildGRPCError(err) } + if token != "token" { + // TODO: Validate Token + return errors.NewErrPermissionDenied("Gateway token not authorized") + } //TODO Validate token r.router.getGateway(id).Token = token @@ -108,6 +103,10 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if err != nil { return errors.BuildGRPCError(err) } + if token != "token" { + // TODO: Validate Token + return errors.NewErrPermissionDenied("Gateway token not authorized") + } //TODO Validate token r.router.getGateway(id).Token = token @@ -157,7 +156,7 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { - id, err := gatewayFromContext(stream.Context()) + gatewayID, err := gatewayFromContext(ctx) if err != nil { return nil, errors.BuildGRPCError(err) } From d8142a3c88ef2440c315b0454d19cbb82d50c59c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 17:05:13 +0200 Subject: [PATCH 1756/2266] core/router/gateway: SetToken, Lock --- core/router/gateway/gateway.go | 12 +++++++++++- core/router/server.go | 6 ++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 2083aafac..fe99f7650 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -4,6 +4,7 @@ package gateway import ( + "sync" "time" pb "github.com/TheThingsNetwork/ttn/api/gateway" @@ -31,13 +32,22 @@ type Gateway struct { Utilization Utilization Schedule Schedule LastSeen time.Time - Token string + + Token string + tokenLock sync.Mutex monitor *monitorConn Ctx log.Interface } +func (g *Gateway) SetToken(token string) { + g.tokenLock.Lock() + defer g.tokenLock.Unlock() + + g.Token = token +} + func (g *Gateway) updateTimestamp() { g.LastSeen = time.Now() } diff --git a/core/router/server.go b/core/router/server.go index d6aa51ad6..ffce8d013 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -72,8 +72,7 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { return errors.NewErrPermissionDenied("Gateway token not authorized") } - //TODO Validate token - r.router.getGateway(id).Token = token + r.router.getGateway(id).SetToken(token) for { status, err := stream.Recv() @@ -108,8 +107,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { return errors.NewErrPermissionDenied("Gateway token not authorized") } - //TODO Validate token - r.router.getGateway(id).Token = token + r.router.getGateway(id).SetToken(token) for { uplink, err := stream.Recv() From f6f4409f327276e2d1e639dc6826c1b39641073a Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 17:09:42 +0200 Subject: [PATCH 1757/2266] id -> gatewayID --- core/router/server.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index ffce8d013..c864d7ee5 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -58,7 +58,7 @@ func tokenFromMetadata(md metadata.MD) (string, error) { func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { md, err := metadataFromContext(stream.Context()) - id, err := gatewayFromMetadata(md) + gatewayID, err := gatewayFromMetadata(md) if err != nil { return errors.BuildGRPCError(err) } @@ -72,7 +72,7 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { return errors.NewErrPermissionDenied("Gateway token not authorized") } - r.router.getGateway(id).SetToken(token) + r.router.getGateway(gatewayID).SetToken(token) for { status, err := stream.Recv() @@ -85,7 +85,7 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if !status.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") } - go r.router.HandleGatewayStatus(id, status) + go r.router.HandleGatewayStatus(gatewayID, status) } } @@ -93,7 +93,7 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { md, err := metadataFromContext(stream.Context()) - id, err := gatewayFromMetadata(md) + gatewayID, err := gatewayFromMetadata(md) if err != nil { return errors.BuildGRPCError(err) } @@ -107,7 +107,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { return errors.NewErrPermissionDenied("Gateway token not authorized") } - r.router.getGateway(id).SetToken(token) + r.router.getGateway(gatewayID).SetToken(token) for { uplink, err := stream.Recv() @@ -120,22 +120,22 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if !uplink.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Uplink") } - go r.router.HandleUplink(id, uplink) + go r.router.HandleUplink(gatewayID, uplink) } } // Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { - id, err := gatewayFromContext(stream.Context()) + gatewayID, err := gatewayFromContext(stream.Context()) if err != nil { return errors.BuildGRPCError(err) } - downlinkChannel, err := r.router.SubscribeDownlink(id) + downlinkChannel, err := r.router.SubscribeDownlink(gatewayID) if err != nil { return err } - defer r.router.UnsubscribeDownlink(id) + defer r.router.UnsubscribeDownlink(gatewayID) for { if downlinkChannel == nil { From 81ec2fa1ab645c8c78cc9e12e1b374b8e75ff7e6 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 17:15:12 +0200 Subject: [PATCH 1758/2266] core/component.Noc -> NOC --- core/component.go | 2 +- core/router/router.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/component.go b/core/component.go index 9b94190d5..b48e54a6a 100644 --- a/core/component.go +++ b/core/component.go @@ -130,7 +130,7 @@ const ( type Component struct { Identity *pb_discovery.Announcement Discovery pb_discovery.Client - Noc pb_noc.MonitorClient + NOC pb_noc.MonitorClient Ctx log.Interface AccessToken string privateKey string diff --git a/core/router/router.go b/core/router/router.go index cd0ab85c5..66e656d40 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -106,8 +106,8 @@ func (r *router) getGateway(id string) *gateway.Gateway { if !ok { gtw = gateway.NewGateway(r.Ctx, id) - if r.Component.Noc != nil { - gtw.SetMonitor(r.Component.Noc) + if r.Component.NOC != nil { + gtw.SetMonitor(r.Component.NOC) } r.gateways[id] = gtw From bcdc11dfc6f107adb16b3573e7aea37d0afa9ac3 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 17:22:34 +0200 Subject: [PATCH 1759/2266] core/router/gateway: push to NOC as last step --- core/router/gateway/gateway.go | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index fe99f7650..925add9b3 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -53,6 +53,11 @@ func (g *Gateway) updateTimestamp() { } func (g *Gateway) HandleStatus(status *pb.Status) (err error) { + if err = g.Status.Update(status); err != nil { + return err + } + g.updateTimestamp() + if g.monitor != nil { go func() { cl, err := g.statusMonitor() @@ -65,12 +70,16 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { } }() } - - g.updateTimestamp() - return g.Status.Update(status) + return nil } func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { + if err = g.Utilization.AddRx(uplink); err != nil { + return err + } + g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) + g.updateTimestamp() + if g.monitor != nil { go func() { cl, err := g.uplinkMonitor() @@ -83,13 +92,16 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { } }() } - - g.updateTimestamp() - g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) - return g.Utilization.AddRx(uplink) + return nil } func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.DownlinkMessage) (err error) { + ctx := g.Ctx.WithField("Identifier", identifier) + if err = g.Schedule.Schedule(identifier, downlink); err != nil { + ctx.WithError(err).Warn("Could not schedule downlink") + return err + } + if g.monitor != nil { go func() { cl, err := g.downlinkMonitor() @@ -102,10 +114,5 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink } }() } - - ctx := g.Ctx.WithField("Identifier", identifier) - if err = g.Schedule.Schedule(identifier, downlink); err != nil { - ctx.WithError(err).Warn("Could not schedule downlink") - } - return err + return nil } From cb884722bbadc3c6f964bb65d58e770c8e7047c8 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 19 Sep 2016 21:36:42 +0200 Subject: [PATCH 1760/2266] `go vet` error fix --- core/router/gateway/monitor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 8dbe66a1a..60d7d9faa 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -8,7 +8,7 @@ import ( pb_noc "github.com/TheThingsNetwork/ttn/api/noc" ) -func (g Gateway) monitorContext() (ctx context.Context) { +func (g *Gateway) monitorContext() (ctx context.Context) { ctx = context.WithValue(context.Background(), "id", g.ID) ctx = context.WithValue(ctx, "token", g.Token) return ctx From 3c5829775980898ea1fd9011ca50559828908d45 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 11:06:48 +0200 Subject: [PATCH 1761/2266] Add CLI docs --- Makefile | 6 +- cmd/docs/README.md | 446 +++++++++++ cmd/docs/generate.go | 42 + ttnctl/cmd/applications_create.go | 4 + ttnctl/cmd/applications_info.go | 17 + ttnctl/cmd/applications_list.go | 6 + ttnctl/cmd/applications_pf.go | 15 +- ttnctl/cmd/applications_pf_set.go | 25 +- ttnctl/cmd/applications_register.go | 5 + ttnctl/cmd/applications_select.go | 5 + ttnctl/cmd/applications_unregister.go | 7 + ttnctl/cmd/devices_create.go | 8 + ttnctl/cmd/devices_delete.go | 8 + ttnctl/cmd/devices_info.go | 22 + ttnctl/cmd/devices_list.go | 10 + ttnctl/cmd/devices_personalize.go | 9 + ttnctl/cmd/devices_set.go | 6 + ttnctl/cmd/docs/README.md | 1026 +++++++++++++++++++++++++ ttnctl/cmd/docs/generate.go | 42 + ttnctl/cmd/downlink.go | 10 + ttnctl/cmd/gateway_status.go | 19 +- ttnctl/cmd/root.go | 6 +- ttnctl/cmd/user.go | 9 + ttnctl/cmd/user_login.go | 6 + ttnctl/cmd/user_register.go | 5 + 25 files changed, 1754 insertions(+), 10 deletions(-) create mode 100644 cmd/docs/README.md create mode 100644 cmd/docs/generate.go create mode 100644 ttnctl/cmd/docs/README.md create mode 100644 ttnctl/cmd/docs/generate.go diff --git a/Makefile b/Makefile index f5a1ac137..c9f1a2eb8 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps update-deps test-deps dev-deps cover-deps proto test fmt vet cover coveralls build install docker package +.PHONY: all clean deps update-deps test-deps dev-deps cover-deps proto test fmt vet cover coveralls docs build install docker package all: clean deps build package @@ -93,6 +93,10 @@ clean: ([ -d $(TEMP_COVER_DIR) ] && rm -rf $(TEMP_COVER_DIR)) || [ ! -d $(TEMP_COVER_DIR) ] ([ -f $(COVER_FILE) ] && rm $(COVER_FILE)) || [ ! -d $(COVER_FILE) ] +docs: + cd cmd/docs && HOME='$$HOME' $(GOCMD) run generate.go > README.md + cd ttnctl/cmd/docs && HOME='$$HOME' $(GOCMD) run generate.go > README.md + build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) install: diff --git a/cmd/docs/README.md b/cmd/docs/README.md new file mode 100644 index 000000000..345198bcf --- /dev/null +++ b/cmd/docs/README.md @@ -0,0 +1,446 @@ +## ttn + +The Things Network's backend servers + +### Synopsis + + +ttn launches The Things Network's backend servers + +### Options + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn broker + +The Things Network broker + +### Synopsis + + +The Things Network broker + +``` +ttn broker +``` + +### Options + +``` + --deduplication-delay int Deduplication delay (in ms) (default 200) + --networkserver-address string Networkserver host and port (default "localhost:1903") + --networkserver-cert string Networkserver certificate to use + --networkserver-token string Networkserver token to use + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1902) +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn broker genkeys + +Generate keys and certificate + +### Synopsis + + +ttn genkeys generates keys and a TLS certificate for this component + +``` +ttn broker genkeys +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn broker register-prefix + +Register a prefix to this Broker + +### Synopsis + + +ttn broker register prefix registers a prefix to this Broker + +``` +ttn broker register-prefix [prefix ...] +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn discovery + +The Things Network discovery + +### Synopsis + + +The Things Network discovery + +``` +ttn discovery +``` + +### Options + +``` + --redis-address string Redis server and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-port int The port for communication (default 1900) +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn handler + +The Things Network handler + +### Synopsis + + +The Things Network handler + +``` +ttn handler +``` + +### Options + +``` + --mqtt-broker string MQTT broker host and port (default "localhost:1883") + --mqtt-password string MQTT password + --mqtt-username string MQTT username (default "handler") + --redis-address string Redis host and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1904) + --ttn-broker string The ID of the TTN Broker as announced in the Discovery server (default "dev") +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn handler genkeys + +Generate keys and certificate + +### Synopsis + + +ttn genkeys generates keys and a TLS certificate for this component + +``` +ttn handler genkeys +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn networkserver + +The Things Network networkserver + +### Synopsis + + +The Things Network networkserver + +``` +ttn networkserver +``` + +### Options + +``` + --net-id int LoRaWAN NetID (default 19) + --redis-address string Redis server and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1903) +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn networkserver authorize + +Generate a token that Brokers should use to connect + +### Synopsis + + +ttn networkserver authorize generates a token that Brokers should use to connect + +``` +ttn networkserver authorize [id] +``` + +### Options + +``` + --valid int The number of days the token is valid +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn networkserver genkeys + +Generate keys and certificate + +### Synopsis + + +ttn genkeys generates keys and a TLS certificate for this component + +``` +ttn networkserver genkeys +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn router + +The Things Network router + +### Synopsis + + +The Things Network router + +``` +ttn router +``` + +### Options + +``` + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-address-announce string The public IP address to announce (default "localhost") + --server-port int The port for communication (default 1901) +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn router genkeys + +Generate keys and certificate + +### Synopsis + + +ttn genkeys generates keys and a TLS certificate for this component + +``` +ttn router genkeys +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + +## ttn version + +Get build and version information + +### Synopsis + + +ttn version gets the build and version information of ttn + +``` +ttn version +``` + +### Options inherited from parent commands + +``` + --auth-token string The JWT token to be used for the discovery server + --config string config file (default "$HOME/.ttn.yaml") + --description string The description of this component + --discovery-server 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 + --tls Use TLS +``` + + diff --git a/cmd/docs/generate.go b/cmd/docs/generate.go new file mode 100644 index 000000000..bf0bcc638 --- /dev/null +++ b/cmd/docs/generate.go @@ -0,0 +1,42 @@ +package main + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/cmd" + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +type byName []*cobra.Command + +func (s byName) Len() int { return len(s) } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPath() } + +func main() { + cmds := genCmdList(cmd.RootCmd) + sort.Sort(byName(cmds)) + for _, cmd := range cmds { + var buf bytes.Buffer + doc.GenMarkdownCustom(cmd, &buf, func(s string) string { + return "#" + strings.TrimSuffix(s, ".md") + }) + cleaned := strings.Split(buf.String(), "### SEE ALSO")[0] + fmt.Println(cleaned) + } +} + +func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { + cmds = append(cmds, cmd) + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsHelpCommand() { + continue + } + cmds = append(cmds, genCmdList(c)...) + } + return cmds +} diff --git a/ttnctl/cmd/applications_create.go b/ttnctl/cmd/applications_create.go index 17116aace..369f32227 100644 --- a/ttnctl/cmd/applications_create.go +++ b/ttnctl/cmd/applications_create.go @@ -14,6 +14,10 @@ var applicationsCreateCmd = &cobra.Command{ Use: "create [AppID] [Description]", Short: "Create a new application", Long: `ttnctl applications create can be used to create a new application.`, + Example: `$ ttnctl applications create test "Test application" + INFO Created Application + INFO Selected Current Application +`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { cmd.UsageFunc()(cmd) diff --git a/ttnctl/cmd/applications_info.go b/ttnctl/cmd/applications_info.go index c6f8e75de..8715540b6 100644 --- a/ttnctl/cmd/applications_info.go +++ b/ttnctl/cmd/applications_info.go @@ -14,6 +14,23 @@ var applicationsInfoCmd = &cobra.Command{ Use: "info [AppID]", Short: "Get information about an application", Long: `ttnctl applications info can be used to info applications.`, + Example: `$ ttnctl applications info + INFO Found application + +AppID: test +Name: Test application +EUIs: + - 0000000000000000 + +Access Keys: + - Name: default key + Key: FZYr01cUhdhY1KBiMghUl+/gXyqXhrF6y+1ww7+DzHg= + Rights: messages:up:r, messages:down:w + +Collaborators: + - Name: yourname + Rights: settings, delete, collaborators +`, Run: func(cmd *cobra.Command, args []string) { account := util.GetAccount(ctx) diff --git a/ttnctl/cmd/applications_list.go b/ttnctl/cmd/applications_list.go index f2a1ef1c2..25ba21363 100644 --- a/ttnctl/cmd/applications_list.go +++ b/ttnctl/cmd/applications_list.go @@ -16,6 +16,12 @@ var applicationsListCmd = &cobra.Command{ Use: "list", Short: "List applications", Long: `ttnctl applications list can be used to list applications.`, + Example: `$ ttnctl applications list + INFO Found one application: + + ID Description EUIs Access Keys Collaborators +1 test Test application 1 1 1 +`, Run: func(cmd *cobra.Command, args []string) { account := util.GetAccount(ctx) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index 86bdb4d91..ddb1079b0 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -15,7 +15,20 @@ var applicationsPayloadFunctionsCmd = &cobra.Command{ Use: "pf", Short: "Show the payload functions", Long: `ttnctl applications pf shows the payload functions for decoding, -converting and validating binary payload. +converting and validating binary payload.`, + Example: `$ ttnctl applications pf + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found Application + INFO Decoder function +function Decoder(bytes) { + return { + payload: bytes, + }; +} + INFO No converter function + INFO No validator function + INFO No encoder function `, Run: func(cmd *cobra.Command, args []string) { diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 8128a05bd..c230559bf 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -20,6 +20,25 @@ var applicationsPayloadFunctionsSetCmd = &cobra.Command{ Short: "Set payload functions of an application", Long: `ttnctl pf set can be used to get or set payload functions of an application. The functions are read from the supplied file or from STDIN.`, + Example: `$ ttnctl applications pf set decoder + INFO Discovering Handler... + INFO Connecting with Handler... +function Decoder(bytes) { + // Here you can decode the payload into json. + // bytes is of type Buffer. + // todo: return an object + return { + payload: bytes, + }; +} +########## Write your Decoder here and end with Ctrl+D (EOF): +function Decoder(bytes) { + return { + payload: bytes, + }; +} + INFO Updated application AppID=test +`, Run: func(cmd *cobra.Command, args []string) { appID := util.GetAppID(ctx) @@ -74,8 +93,8 @@ The functions are read from the supplied file or from STDIN.`, case "converter": fmt.Println(`function Converter(decodedObj) { // Modify the decoded uplink message. - - decodedObj.isLightOn = !!decodedObj.isLightOn; + + decodedObj.isLightOn = !!decodedObj.isLightOn; return decodedObj; } @@ -94,7 +113,7 @@ The functions are read from the supplied file or from STDIN.`, fmt.Println(`function Encoder(obj) { // Convert uplink messages sent as object to // an array of bytes. - + return [ obj.turnLightOn ? 1 : 0 ]; } ########## Write your Encoder here and end with Ctrl+D (EOF):`) diff --git a/ttnctl/cmd/applications_register.go b/ttnctl/cmd/applications_register.go index f71f6eb16..acd5f4fed 100644 --- a/ttnctl/cmd/applications_register.go +++ b/ttnctl/cmd/applications_register.go @@ -14,6 +14,11 @@ var applicationsRegisterCmd = &cobra.Command{ Use: "register", Short: "Register this application with the handler", Long: `ttnctl register can be used to register this application with the handler.`, + Example: `$ ttnctl applications register + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered application AppID=test +`, Run: func(cmd *cobra.Command, args []string) { appID := util.GetAppID(ctx) diff --git a/ttnctl/cmd/applications_select.go b/ttnctl/cmd/applications_select.go index 511150b97..3bf573863 100644 --- a/ttnctl/cmd/applications_select.go +++ b/ttnctl/cmd/applications_select.go @@ -16,6 +16,11 @@ var applicationsSelectCmd = &cobra.Command{ Use: "select", Short: "select the application to use", Long: `ttnctl applications select can be used to select the application to use in next commands.`, + Example: `$ ttnctl applications select + INFO Found one application "test", selecting that one. + INFO Found one EUI "0000000000000000", selecting that one. + INFO Updated configuration +`, Run: func(cmd *cobra.Command, args []string) { account := util.GetAccount(ctx) diff --git a/ttnctl/cmd/applications_unregister.go b/ttnctl/cmd/applications_unregister.go index d4059b798..d56a26f19 100644 --- a/ttnctl/cmd/applications_unregister.go +++ b/ttnctl/cmd/applications_unregister.go @@ -16,6 +16,13 @@ var applicationsUnregisterCmd = &cobra.Command{ Use: "unregister", Short: "Unregister this application from the handler", Long: `ttnctl unregister can be used to unregister this application from the handler.`, + Example: `$ ttnctl applications unregister +Are you sure you want to unregister application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Unregistered application AppID=test +`, Run: func(cmd *cobra.Command, args []string) { appID := util.GetAppID(ctx) diff --git a/ttnctl/cmd/devices_create.go b/ttnctl/cmd/devices_create.go index d9b408d28..6a96bac49 100644 --- a/ttnctl/cmd/devices_create.go +++ b/ttnctl/cmd/devices_create.go @@ -19,6 +19,14 @@ var devicesCreateCmd = &cobra.Command{ Use: "create [Device ID] [DevEUI] [AppKey]", Short: "Create a new device", Long: `ttnctl devices create can be used to create a new device.`, + Example: `$ ttnctl devices create test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random DevEUI... + INFO Generating random AppKey... + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Created device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test +`, Run: func(cmd *cobra.Command, args []string) { var err error diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go index 23f5c54f2..50204e4f1 100644 --- a/ttnctl/cmd/devices_delete.go +++ b/ttnctl/cmd/devices_delete.go @@ -17,6 +17,14 @@ var devicesDeleteCmd = &cobra.Command{ Use: "delete [Device ID]", Short: "Delete a device", Long: `ttnctl devices delete can be used to delete a device.`, + Example: `$ ttnctl devices delete test + INFO Using Application AppID=test +Are you sure you want to delete device test from application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Deleted device AppID=test DevID=test +`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index 5aacc6a08..da53a2957 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -18,6 +18,28 @@ var devicesInfoCmd = &cobra.Command{ Use: "info [Device ID]", Short: "Get information about a device", Long: `ttnctl devices info can be used to get information about a device.`, + Example: `$ ttnctl devices info test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found device + + Application ID: test + Device ID: test + Last Seen: never + + LoRaWAN Info: + + AppEUI: 70B3D57EF0000024 + DevEUI: 0001D544B2936FCE + DevAddr: 26001ADA + AppKey: + AppSKey: D8DD37B4B709BA76C6FEC62CAD0CCE51 + NwkSKey: 3382A3066850293421ED8D392B9BF4DF + FCntUp: 0 + FCntDown: 0 + Options: +`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index 2e93cf344..8d46d6d06 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -17,6 +17,16 @@ var devicesListCmd = &cobra.Command{ Use: "list", Short: "List al devices for the current application", Long: `ttnctl devices list can be used to list all devices for the current application.`, + Example: `$ ttnctl devices list + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + +DevID AppEUI DevEUI DevAddr Up/Down +test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 + + INFO Listed 1 devices AppID=test +`, Run: func(cmd *cobra.Command, args []string) { appID := util.GetAppID(ctx) diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index b06f26a8f..0b062e57e 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -19,6 +19,15 @@ var devicesPersonalizeCmd = &cobra.Command{ Use: "personalize [Device ID] [NwkSKey] [AppSKey]", Short: "Personalize a device", Long: `ttnctl devices personalize can be used to personalize a device (ABP).`, + Example: `$ ttnctl devices personalize test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random NwkSKey... + INFO Generating random AppSKey... + INFO Discovering Handler... Handler=ttn-handler-eu + INFO Connecting with Handler... Handler=eu.thethings.network:1904 + INFO Requesting DevAddr for device... + INFO Personalized device AppID=test AppSKey=D8DD37B4B709BA76C6FEC62CAD0CCE51 DevAddr=26001ADA DevID=test NwkSKey=3382A3066850293421ED8D392B9BF4DF +`, Run: func(cmd *cobra.Command, args []string) { var err error diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index 9ee183b3b..f0c59c87a 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -16,6 +16,12 @@ var devicesSetCmd = &cobra.Command{ Use: "set [Device ID]", Short: "Set properties of a device", Long: `ttnctl devices set can be used to set properties of a device.`, + Example: `$ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Updated device AppID=test DevID=test +`, Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md new file mode 100644 index 000000000..fbb112b51 --- /dev/null +++ b/ttnctl/cmd/docs/README.md @@ -0,0 +1,1026 @@ +## ttnctl + +Control The Things Network from the command line + +### Synopsis + + +ttnctl controls The Things Network from the command line. + +### Options + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications + +Manage applications + +### Synopsis + + +ttnctl applications can be used to manage applications. + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications create + +Create a new application + +### Synopsis + + +ttnctl applications create can be used to create a new application. + +``` +ttnctl applications create [AppID] [Description] +``` + +### Examples + +``` +$ ttnctl applications create test "Test application" + INFO Created Application + INFO Selected Current Application + +``` + +### 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) +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications delete + +Delete an application + +### Synopsis + + +ttnctl devices delete can be used to delete an application. + +``` +ttnctl applications delete +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications info + +Get information about an application + +### Synopsis + + +ttnctl applications info can be used to info applications. + +``` +ttnctl applications info [AppID] +``` + +### Examples + +``` +$ ttnctl applications info + INFO Found application + +AppID: test +Name: Test application +EUIs: + - 0000000000000000 + +Access Keys: + - Name: default key + Key: FZYr01cUhdhY1KBiMghUl+/gXyqXhrF6y+1ww7+DzHg= + Rights: messages:up:r, messages:down:w + +Collaborators: + - Name: yourname + Rights: settings, delete, collaborators + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications list + +List applications + +### Synopsis + + +ttnctl applications list can be used to list applications. + +``` +ttnctl applications list +``` + +### Examples + +``` +$ ttnctl applications list + INFO Found one application: + + ID Description EUIs Access Keys Collaborators +1 test Test application 1 1 1 + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications pf + +Show the payload functions + +### Synopsis + + +ttnctl applications pf shows the payload functions for decoding, +converting and validating binary payload. + +``` +ttnctl applications pf +``` + +### Examples + +``` +$ ttnctl applications pf + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found Application + INFO Decoder function +function Decoder(bytes) { + return { + payload: bytes, + }; +} + INFO No converter function + INFO No validator function + INFO No encoder function + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications pf set + +Set payload functions of an application + +### Synopsis + + +ttnctl pf set can be used to get or set payload functions of an application. +The functions are read from the supplied file or from STDIN. + +``` +ttnctl applications pf set [decoder/converter/validator/encoder] [file.js] +``` + +### Examples + +``` +$ ttnctl applications pf set decoder + INFO Discovering Handler... + INFO Connecting with Handler... +function Decoder(bytes) { + // Here you can decode the payload into json. + // bytes is of type Buffer. + // todo: return an object + return { + payload: bytes, + }; +} +########## Write your Decoder here and end with Ctrl+D (EOF): +function Decoder(bytes) { + return { + payload: bytes, + }; +} + INFO Updated application AppID=test + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications register + +Register this application with the handler + +### Synopsis + + +ttnctl register can be used to register this application with the handler. + +``` +ttnctl applications register +``` + +### Examples + +``` +$ ttnctl applications register + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered application AppID=test + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications select + +select the application to use + +### Synopsis + + +ttnctl applications select can be used to select the application to use in next commands. + +``` +ttnctl applications select +``` + +### Examples + +``` +$ ttnctl applications select + INFO Found one application "test", selecting that one. + INFO Found one EUI "0000000000000000", selecting that one. + INFO Updated configuration + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl applications unregister + +Unregister this application from the handler + +### Synopsis + + +ttnctl unregister can be used to unregister this application from the handler. + +``` +ttnctl applications unregister +``` + +### Examples + +``` +$ ttnctl applications unregister +Are you sure you want to unregister application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Unregistered application AppID=test + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices + +Manage devices + +### Synopsis + + +ttnctl devices can be used to manage devices. + +### Options + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices create + +Create a new device + +### Synopsis + + +ttnctl devices create can be used to create a new device. + +``` +ttnctl devices create [Device ID] [DevEUI] [AppKey] +``` + +### Examples + +``` +$ ttnctl devices create test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random DevEUI... + INFO Generating random AppKey... + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Created device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test + +``` + +### Options inherited from parent commands + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices delete + +Delete a device + +### Synopsis + + +ttnctl devices delete can be used to delete a device. + +``` +ttnctl devices delete [Device ID] +``` + +### Examples + +``` +$ ttnctl devices delete test + INFO Using Application AppID=test +Are you sure you want to delete device test from application test? +> yes + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Deleted device AppID=test DevID=test + +``` + +### Options inherited from parent commands + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices info + +Get information about a device + +### Synopsis + + +ttnctl devices info can be used to get information about a device. + +``` +ttnctl devices info [Device ID] +``` + +### Examples + +``` +$ ttnctl devices info test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Found device + + Application ID: test + Device ID: test + Last Seen: never + + LoRaWAN Info: + + AppEUI: 70B3D57EF0000024 + DevEUI: 0001D544B2936FCE + DevAddr: 26001ADA + AppKey: + AppSKey: D8DD37B4B709BA76C6FEC62CAD0CCE51 + NwkSKey: 3382A3066850293421ED8D392B9BF4DF + FCntUp: 0 + FCntDown: 0 + Options: + +``` + +### Options + +``` + --format string Formatting: hex/msb/lsb (default "hex") +``` + +### Options inherited from parent commands + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices list + +List al devices for the current application + +### Synopsis + + +ttnctl devices list can be used to list all devices for the current application. + +``` +ttnctl devices list +``` + +### Examples + +``` +$ ttnctl devices list + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + +DevID AppEUI DevEUI DevAddr Up/Down +test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 + + INFO Listed 1 devices AppID=test + +``` + +### Options inherited from parent commands + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices personalize + +Personalize a device + +### Synopsis + + +ttnctl devices personalize can be used to personalize a device (ABP). + +``` +ttnctl devices personalize [Device ID] [NwkSKey] [AppSKey] +``` + +### Examples + +``` +$ ttnctl devices personalize test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random NwkSKey... + INFO Generating random AppSKey... + INFO Discovering Handler... Handler=ttn-handler-eu + INFO Connecting with Handler... Handler=eu.thethings.network:1904 + INFO Requesting DevAddr for device... + INFO Personalized device AppID=test AppSKey=D8DD37B4B709BA76C6FEC62CAD0CCE51 DevAddr=26001ADA DevID=test NwkSKey=3382A3066850293421ED8D392B9BF4DF + +``` + +### Options inherited from parent commands + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl devices set + +Set properties of a device + +### Synopsis + + +ttnctl devices set can be used to set properties of a device. + +``` +ttnctl devices set [Device ID] +``` + +### Examples + +``` +$ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Updated device AppID=test DevID=test + +``` + +### Options + +``` + --32-bit-fcnt Use 32 bit FCnt + --app-eui string Set AppEUI + --app-key string Set AppKey + --app-s-key string Set AppSKey + --dev-addr string Set DevAddr + --dev-eui string Set DevEUI + --disable-fcnt-check Disable FCnt check + --fcnt-down int Set FCnt Down (default -1) + --fcnt-up int Set FCnt Up (default -1) + --nwk-s-key string Set NwkSKey +``` + +### Options inherited from parent commands + +``` + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl downlink + +Send a downlink message to a device + +### Synopsis + + +ttnctl downlink can be used to send a downlink message to a device. + +``` +ttnctl downlink [DevID] [Payload] +``` + +### Examples + +``` +$ ttnctl downlink test aabc + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test + +$ ttnctl downlink test --json '{"led":"on"}' + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test + +``` + +### Options + +``` + --fport int FPort for downlink (default 1) + --json Provide the payload as JSON +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl gateway + +Manage gateways + +### Synopsis + + +ttnctl applications can be used to manage gateways. + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl gateway status + +Get status of a gateway + +### Synopsis + + +ttnctl gateway status can be used to get status of gateways. + +``` +ttnctl gateway status [gatewayID] +``` + +### Examples + +``` +$ ttnctl gateway status eui-0000024b08060030 + INFO Discovering Router... + INFO Connecting with Router... + INFO Connected to Router + INFO Received status + + Last seen: 2016-09-20 08:25:27.94138808 +0200 CEST + Timestamp: 0 + Reported time: 2016-09-20 08:25:26 +0200 CEST + GPS coordinates: (52.372791 4.900300) + Rtt: not available + Rx: (in: 0; ok: 0) + Tx: (in: 0; ok: 0) + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl subscribe + +Subscribe to events for this application + +### Synopsis + + +ttnctl subscribe can be used to subscribe to events for this application. + +``` +ttnctl subscribe +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl uplink + +Simulate an uplink message to the network + +### Synopsis + + +ttnctl uplink simulates an uplink message to the network + +``` +ttnctl uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload] +``` + +### Options + +``` + --confirmed Use confirmed uplink (this also sets --downlink) + --downlink Also start downlink (unstable) +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl user + +Show the current user + +### Synopsis + + +ttnctl user shows the current logged on user's profile + +``` +ttnctl user +``` + +### Examples + +``` +$ ttnctl user + INFO Found user profile: + + Username: yourname + Name: Your Name + Email: your@email.org + + INFO Login credentials valid until Sep 20 09:04:12 + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl user login + +Login + +### Synopsis + + +ttnctl user login allows you to login to the account server. + +``` +ttnctl user login [client code] +``` + +### Examples + +``` +First get an access code from your TTN Profile by going to +https://account.thethingsnetwork.org and clicking "ttnctl access code". + +$ ttnctl user login 2keK3FTu6e0327cq4ni0wRTMT2mTS-m_FLzFBlNQadwa + INFO Successfully logged in as yourname (your@email.org) + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl user logout + +Logout the current user + +### Synopsis + + +ttnctl user logout logs out the current user + +``` +ttnctl user logout +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl user register + +Register + +### Synopsis + + +ttnctl user register allows you to register a new user in the account server + +``` +ttnctl user register [username] [e-mail] +``` + +### Examples + +``` +$ ttnctl user register yourname your@email.org +Password: + INFO Registered user + WARN You might have to verify your email before you can login + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl version + +Get build and version information + +### Synopsis + + +ttnctl version gets the build and version information of ttnctl + +``` +ttnctl version +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go new file mode 100644 index 000000000..7d832eb69 --- /dev/null +++ b/ttnctl/cmd/docs/generate.go @@ -0,0 +1,42 @@ +package main + +import ( + "bytes" + "fmt" + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/ttnctl/cmd" + "github.com/spf13/cobra" + "github.com/spf13/cobra/doc" +) + +type byName []*cobra.Command + +func (s byName) Len() int { return len(s) } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPath() } + +func main() { + cmds := genCmdList(cmd.RootCmd) + sort.Sort(byName(cmds)) + for _, cmd := range cmds { + var buf bytes.Buffer + doc.GenMarkdownCustom(cmd, &buf, func(s string) string { + return "#" + strings.TrimSuffix(s, ".md") + }) + cleaned := strings.Split(buf.String(), "### SEE ALSO")[0] + fmt.Println(cleaned) + } +} + +func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { + cmds = append(cmds, cmd) + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsHelpCommand() { + continue + } + cmds = append(cmds, genCmdList(c)...) + } + return cmds +} diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 23a5fc151..f7dfe7661 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -17,6 +17,16 @@ var downlinkCmd = &cobra.Command{ Use: "downlink [DevID] [Payload]", Short: "Send a downlink message to a device", Long: `ttnctl downlink can be used to send a downlink message to a device.`, + Example: `$ ttnctl downlink test aabc + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test + +$ ttnctl downlink test --json '{"led":"on"}' + INFO Connecting to MQTT... + INFO Connected to MQTT + INFO Enqueued downlink AppID=test DevID=test +`, Run: func(cmd *cobra.Command, args []string) { client := util.GetMQTT(ctx) defer client.Disconnect() diff --git a/ttnctl/cmd/gateway_status.go b/ttnctl/cmd/gateway_status.go index 509d50703..e081840a0 100644 --- a/ttnctl/cmd/gateway_status.go +++ b/ttnctl/cmd/gateway_status.go @@ -18,6 +18,20 @@ var gatewayStatusCmd = &cobra.Command{ Use: "status [gatewayID]", Short: "Get status of a gateway", Long: `ttnctl gateway status can be used to get status of gateways.`, + Example: `$ ttnctl gateway status eui-0000024b08060030 + INFO Discovering Router... + INFO Connecting with Router... + INFO Connected to Router + INFO Received status + + Last seen: 2016-09-20 08:25:27.94138808 +0200 CEST + Timestamp: 0 + Reported time: 2016-09-20 08:25:26 +0200 CEST + GPS coordinates: (52.372791 4.900300) + Rtt: not available + Rx: (in: 0; ok: 0) + Tx: (in: 0; ok: 0) +`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { cmd.UsageFunc()(cmd) @@ -28,11 +42,12 @@ var gatewayStatusCmd = &cobra.Command{ if !api.ValidID(gtwID) { ctx.Fatal("Invalid Gateway ID") } - ctx = ctx.WithField("GatewayID", gtwID) conn, manager := util.GetRouterManager(ctx) defer conn.Close() + ctx = ctx.WithField("GatewayID", gtwID) + resp, err := manager.GatewayStatus(util.GetContext(ctx), &router.GatewayStatusRequest{ GatewayId: gtwID, }) @@ -43,7 +58,7 @@ var gatewayStatusCmd = &cobra.Command{ ctx.Infof("Received status") fmt.Println() printKV("Last seen", time.Unix(0, resp.LastSeen)) - printKV("Timestamp", time.Duration(resp.Status.Timestamp)) + printKV("Timestamp", resp.Status.Timestamp) if t := resp.Status.Time; t != 0 { printKV("Reported time", time.Unix(0, t)) } diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 0e3151990..bbacdfd22 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -61,13 +61,13 @@ func init() { RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) - RootCmd.PersistentFlags().String("ttn-router", "dev", "The ID of the TTN Router as announced in the Discovery server") + RootCmd.PersistentFlags().String("ttn-router", "ttn-router-eu", "The ID of the TTN Router as announced in the Discovery server") viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router")) - RootCmd.PersistentFlags().String("ttn-handler", "dev", "The ID of the TTN Handler as announced in the Discovery server") + RootCmd.PersistentFlags().String("ttn-handler", "ttn-handler-eu", "The ID of the TTN Handler as announced in the Discovery server") viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) - RootCmd.PersistentFlags().String("mqtt-broker", "staging.thethingsnetwork.org:1883", "The address of the MQTT broker") + RootCmd.PersistentFlags().String("mqtt-broker", "eu.thethings.network:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 3a1dc13c6..1d9903fdc 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -19,6 +19,15 @@ var userCmd = &cobra.Command{ Use: "user", Short: "Show the current user", Long: `ttnctl user shows the current logged on user's profile`, + Example: `$ ttnctl user + INFO Found user profile: + + Username: yourname + Name: Your Name + Email: your@email.org + + INFO Login credentials valid until Sep 20 09:04:12 +`, Run: func(cmd *cobra.Command, args []string) { account := util.GetAccount(ctx) profile, err := account.Profile() diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index b96517dba..044858b1f 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -16,6 +16,12 @@ var userLoginCmd = &cobra.Command{ Use: "login [client code]", Short: "Login", Long: `ttnctl user login allows you to login to the account server.`, + Example: `First get an access code from your TTN Profile by going to +https://account.thethingsnetwork.org and clicking "ttnctl access code". + +$ ttnctl user login 2keK3FTu6e0327cq4ni0wRTMT2mTS-m_FLzFBlNQadwa + INFO Successfully logged in as yourname (your@email.org) +`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 1 { cmd.UsageFunc()(cmd) diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go index a7ec90943..dde731e07 100644 --- a/ttnctl/cmd/user_register.go +++ b/ttnctl/cmd/user_register.go @@ -16,6 +16,11 @@ var userRegisterCmd = &cobra.Command{ Use: "register [username] [e-mail]", Short: "Register", Long: `ttnctl user register allows you to register a new user in the account server`, + Example: `$ ttnctl user register yourname your@email.org +Password: + INFO Registered user + WARN You might have to verify your email before you can login +`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 2 { cmd.UsageFunc()(cmd) From d58c807be51d27fbe1b48649500889b0bb015f5b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 11:15:35 +0200 Subject: [PATCH 1762/2266] Vendor go-md2man --- .gitmodules | 3 +++ vendor/github.com/cpuguy83/go-md2man | 1 + 2 files changed, 4 insertions(+) create mode 160000 vendor/github.com/cpuguy83/go-md2man diff --git a/.gitmodules b/.gitmodules index b3b1e81ef..cc89b9e1c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,3 +58,6 @@ [submodule "vendor/golang.org/x/oauth2"] path = vendor/golang.org/x/oauth2 url = https://go.googlesource.com/oauth2 +[submodule "vendor/github.com/cpuguy83/go-md2man"] + path = vendor/github.com/cpuguy83/go-md2man + url = https://github.com/cpuguy83/go-md2man.git diff --git a/vendor/github.com/cpuguy83/go-md2man b/vendor/github.com/cpuguy83/go-md2man new file mode 160000 index 000000000..a65d4d2de --- /dev/null +++ b/vendor/github.com/cpuguy83/go-md2man @@ -0,0 +1 @@ +Subproject commit a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa From 887cc6d8da0d4b7cc91f2bced60e8f4f7a25029b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 11:22:14 +0200 Subject: [PATCH 1763/2266] Add header to ttnctl docs and remove RootCmd --- ttnctl/cmd/docs/README.md | 22 ++-------------------- ttnctl/cmd/docs/generate.go | 7 +++++++ 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index fbb112b51..cf04385c3 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -1,24 +1,6 @@ -## ttnctl - -Control The Things Network from the command line - -### Synopsis - - -ttnctl controls The Things Network from the command line. - -### Options - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - +# API Reference +Control The Things Network from the command line. ## ttnctl applications Manage applications diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go index 7d832eb69..e35513da7 100644 --- a/ttnctl/cmd/docs/generate.go +++ b/ttnctl/cmd/docs/generate.go @@ -20,7 +20,14 @@ func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPa func main() { cmds := genCmdList(cmd.RootCmd) sort.Sort(byName(cmds)) + fmt.Println(`# API Reference + +Control The Things Network from the command line.`) for _, cmd := range cmds { + if cmd.CommandPath() == "ttnctl" { + continue + } + var buf bytes.Buffer doc.GenMarkdownCustom(cmd, &buf, func(s string) string { return "#" + strings.TrimSuffix(s, ".md") From 8fa01ef997fb8a33fefa54ea81fc044025237064 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 12:08:25 +0200 Subject: [PATCH 1764/2266] Add line break after ttnctl doc header --- ttnctl/cmd/docs/README.md | 1 + ttnctl/cmd/docs/generate.go | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index cf04385c3..6af60a235 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -1,6 +1,7 @@ # API Reference Control The Things Network from the command line. + ## ttnctl applications Manage applications diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go index e35513da7..ef40e9a9a 100644 --- a/ttnctl/cmd/docs/generate.go +++ b/ttnctl/cmd/docs/generate.go @@ -22,7 +22,8 @@ func main() { sort.Sort(byName(cmds)) fmt.Println(`# API Reference -Control The Things Network from the command line.`) +Control The Things Network from the command line. +`) for _, cmd := range cmds { if cmd.CommandPath() == "ttnctl" { continue From 0e1868119d67a50db544864087edd68e55997066 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 13:30:48 +0200 Subject: [PATCH 1765/2266] Return not found if gateway not found for routerManager.GatewayStatus --- core/router/manager_server.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 2c66f755d..fd8c894fa 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -24,7 +24,12 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR if err != nil { return nil, errf(codes.PermissionDenied, "No access") } - gtw := r.router.getGateway(in.GatewayId) + r.router.gatewaysLock.RLock() + gtw, ok := r.router.gateways[in.GatewayId] + r.router.gatewaysLock.RUnlock() + if !ok { + return nil, grpcErrf(codes.NotFound, "Gateway %s not found", in.GatewayId) + } status, err := gtw.Status.Get() if err != nil { return nil, err From 505da42e15fa1f6d70196a2a950844d184f89fb1 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 20 Sep 2016 14:21:00 +0200 Subject: [PATCH 1766/2266] core/router/gateway updateTimestamp -> updateLastSeen --- core/router/gateway/gateway.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 925add9b3..881b04e38 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -48,7 +48,7 @@ func (g *Gateway) SetToken(token string) { g.Token = token } -func (g *Gateway) updateTimestamp() { +func (g *Gateway) updateLastSeen() { g.LastSeen = time.Now() } @@ -56,7 +56,7 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { if err = g.Status.Update(status); err != nil { return err } - g.updateTimestamp() + g.updateLastSeen() if g.monitor != nil { go func() { @@ -78,7 +78,7 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { return err } g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) - g.updateTimestamp() + g.updateLastSeen() if g.monitor != nil { go func() { From 891d41cead1da09275f2968168dd2bc24885addd Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 20 Sep 2016 14:54:08 +0200 Subject: [PATCH 1767/2266] core/router/server.go: refactoring --- core/router/server.go | 83 +++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 47 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index c864d7ee5..68e5a2fe7 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -7,9 +7,10 @@ import ( "io" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + context "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" @@ -21,6 +22,29 @@ type routerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining +func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gateway, err error) { + md, err := metadataFromContext(ctx) + + gatewayID, err := gatewayIDFromMetadata(md) + if err != nil { + return nil, err + } + + token, err := tokenFromMetadata(md) + if err != nil { + return nil, err + } + if token != "token" { + // TODO: Validate Token + return nil, errors.NewErrPermissionDenied("Gateway token not authorized") + } + + gtw = r.router.getGateway(gatewayID) + gtw.SetToken(token) + + return gtw, nil +} + func metadataFromContext(ctx context.Context) (md metadata.MD, err error) { var ok bool if md, ok = metadata.FromContext(ctx); !ok { @@ -29,16 +53,7 @@ func metadataFromContext(ctx context.Context) (md metadata.MD, err error) { return md, nil } -func gatewayFromContext(ctx context.Context) (gatewayID string, err error) { - md, err := metadataFromContext(ctx) - if err != nil { - return "", err - } - - return gatewayFromMetadata(md) -} - -func gatewayFromMetadata(md metadata.MD) (gatewayID string, err error) { +func gatewayIDFromMetadata(md metadata.MD) (gatewayID string, err error) { id, ok := md["id"] if !ok || len(id) < 1 { return "", errors.NewErrInvalidArgument("Metadata", "id missing") @@ -56,24 +71,11 @@ func tokenFromMetadata(md metadata.MD) (string, error) { // GatewayStatus implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { - md, err := metadataFromContext(stream.Context()) - - gatewayID, err := gatewayFromMetadata(md) + gateway, err := r.gatewayFromContext(stream.Context()) if err != nil { return errors.BuildGRPCError(err) } - token, err := tokenFromMetadata(md) - if err != nil { - return errors.BuildGRPCError(err) - } - if token != "token" { - // TODO: Validate Token - return errors.NewErrPermissionDenied("Gateway token not authorized") - } - - r.router.getGateway(gatewayID).SetToken(token) - for { status, err := stream.Recv() if err == io.EOF { @@ -85,30 +87,17 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if !status.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") } - go r.router.HandleGatewayStatus(gatewayID, status) + go r.router.HandleGatewayStatus(gateway.ID, status) } } // Uplink implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { - md, err := metadataFromContext(stream.Context()) - - gatewayID, err := gatewayFromMetadata(md) + gateway, err := r.gatewayFromContext(stream.Context()) if err != nil { return errors.BuildGRPCError(err) } - token, err := tokenFromMetadata(md) - if err != nil { - return errors.BuildGRPCError(err) - } - if token != "token" { - // TODO: Validate Token - return errors.NewErrPermissionDenied("Gateway token not authorized") - } - - r.router.getGateway(gatewayID).SetToken(token) - for { uplink, err := stream.Recv() if err == io.EOF { @@ -120,22 +109,22 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if !uplink.Validate() { return grpcErrf(codes.InvalidArgument, "Invalid Uplink") } - go r.router.HandleUplink(gatewayID, uplink) + go r.router.HandleUplink(gateway.ID, uplink) } } // Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { - gatewayID, err := gatewayFromContext(stream.Context()) + gateway, err := r.gatewayFromContext(stream.Context()) if err != nil { return errors.BuildGRPCError(err) } - downlinkChannel, err := r.router.SubscribeDownlink(gatewayID) + downlinkChannel, err := r.router.SubscribeDownlink(gateway.ID) if err != nil { - return err + return errors.BuildGRPCError(err) } - defer r.router.UnsubscribeDownlink(gatewayID) + defer r.router.UnsubscribeDownlink(gateway.ID) for { if downlinkChannel == nil { @@ -154,7 +143,7 @@ func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_Subscri // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { - gatewayID, err := gatewayFromContext(ctx) + gateway, err := r.gatewayFromContext(ctx) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -162,7 +151,7 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques if !req.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") } - return r.router.HandleActivation(gatewayID, req) + return r.router.HandleActivation(gateway.ID, req) } // RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) From a1cb6b7d6ecbe9c88f8378eadd2f9b25d6d4b018 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 15:14:10 +0200 Subject: [PATCH 1768/2266] Shorten version string --- Makefile | 2 +- core/component.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index c9f1a2eb8..7a0e3f96d 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ GOBUILD = $(GOCMD) build PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn -GIT_COMMIT = `git rev-parse HEAD 2>/dev/null` +GIT_COMMIT = `git rev-parse --short HEAD 2>/dev/null` BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` LDFLAGS = -ldflags "-w -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" diff --git a/core/component.go b/core/component.go index bfa35f090..0faa9ef0e 100644 --- a/core/component.go +++ b/core/component.go @@ -61,7 +61,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string Id: viper.GetString("id"), Description: viper.GetString("description"), ServiceName: serviceName, - ServiceVersion: fmt.Sprintf("%s-%s (built %s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), + ServiceVersion: fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), NetAddress: announcedAddress, }, AccessToken: viper.GetString("auth-token"), From 2bf2c64905be0398de1c9aee9c0d89913959c141 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 20 Sep 2016 15:36:44 +0200 Subject: [PATCH 1769/2266] NOC -> Monitor --- core/component.go | 2 +- core/router/gateway/gateway.go | 12 ++++++------ core/router/router.go | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/component.go b/core/component.go index b48e54a6a..3a3c4c159 100644 --- a/core/component.go +++ b/core/component.go @@ -130,7 +130,7 @@ const ( type Component struct { Identity *pb_discovery.Announcement Discovery pb_discovery.Client - NOC pb_noc.MonitorClient + Monitor pb_noc.MonitorClient Ctx log.Interface AccessToken string privateKey string diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 881b04e38..62fe8a26b 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -62,11 +62,11 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { go func() { cl, err := g.statusMonitor() if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish status connection to the NOC") + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish status connection to the monitor") } if err = cl.Send(status); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("NOC status push failed") + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Monitor status push failed") } }() } @@ -84,11 +84,11 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { go func() { cl, err := g.uplinkMonitor() if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish uplink connection to the NOC") + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish uplink connection to the monitor") } if err = cl.Send(uplink); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("NOC uplink push failed") + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Monitor uplink push failed") } }() } @@ -106,11 +106,11 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink go func() { cl, err := g.downlinkMonitor() if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish downlink connection to the NOC") + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish downlink connection to the monitor") } if err = cl.Send(downlink); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("NOC downlink push failed") + g.Ctx.WithError(errors.FromGRPCError(err)).Error("Monitor downlink push failed") } }() } diff --git a/core/router/router.go b/core/router/router.go index 66e656d40..e18b340d8 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -106,8 +106,8 @@ func (r *router) getGateway(id string) *gateway.Gateway { if !ok { gtw = gateway.NewGateway(r.Ctx, id) - if r.Component.NOC != nil { - gtw.SetMonitor(r.Component.NOC) + if r.Component.Monitor != nil { + gtw.SetMonitor(r.Component.Monitor) } r.gateways[id] = gtw From 023f27b4c57f661397edca5ea1ed12356b87fa48 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 20 Sep 2016 15:50:22 +0200 Subject: [PATCH 1770/2266] Move gRPC connection inside Discovery client --- api/discovery/client.go | 17 +++++++++++++++-- api/discovery/client_mock.go | 10 ++++++++++ core/component.go | 15 +++++++++------ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/api/discovery/client.go b/api/discovery/client.go index d456771a3..43bb4a4a3 100644 --- a/api/discovery/client.go +++ b/api/discovery/client.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/core/types" "github.com/bluele/gcache" @@ -24,6 +25,7 @@ var CacheSize = 1000 // CacheExpiration indicates the time a cached item is valid var CacheExpiration = 5 * time.Minute +// Client is used as the main client to the Discovery server type Client interface { Announce(token string) error GetAll(serviceName string) ([]*Announcement, error) @@ -33,10 +35,15 @@ type Client interface { GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func(value []byte) bool) ([]*Announcement, error) GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) GetAllHandlersForAppID(appID string) ([]*Announcement, error) + Close() error } // NewClient returns a new Client -func NewClient(conn *grpc.ClientConn, announcement *Announcement, tokenFunc func() string) Client { +func NewClient(server string, announcement *Announcement, tokenFunc func() string) (Client, error) { + conn, err := grpc.Dial(server, append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) + if err != nil { + return nil, err + } client := &DefaultClient{ lists: make(map[string][]*Announcement), listsUpdated: make(map[string]time.Time), @@ -57,7 +64,7 @@ func NewClient(conn *grpc.ClientConn, announcement *Announcement, tokenFunc func return client.get(key.serviceName, key.id) }). Build() - return client + return client, nil } // DefaultClient is a wrapper around DiscoveryClient @@ -216,3 +223,9 @@ func (c *DefaultClient) GetAllHandlersForAppID(appID string) ([]*Announcement, e return string(value) == appID }) } + +// Close purges the cache and closes the connection with the Discovery server +func (c *DefaultClient) Close() error { + c.cache.Purge() + return c.conn.Close() +} diff --git a/api/discovery/client_mock.go b/api/discovery/client_mock.go index 066c17aff..a11b4f090 100644 --- a/api/discovery/client_mock.go +++ b/api/discovery/client_mock.go @@ -113,3 +113,13 @@ func (_m *MockClient) GetAllHandlersForAppID(appID string) ([]*Announcement, err func (_mr *_MockClientRecorder) GetAllHandlersForAppID(arg0 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllHandlersForAppID", arg0) } + +func (_m *MockClient) Close() error { + ret := _m.ctrl.Call(_m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) Close() *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "Close") +} diff --git a/core/component.go b/core/component.go index 0faa9ef0e..85d161d86 100644 --- a/core/component.go +++ b/core/component.go @@ -11,7 +11,6 @@ import ( "sync/atomic" "time" - "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" @@ -72,14 +71,18 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } if serviceName != "discovery" { - discoveryConn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) + var err error + component.Discovery, err = pb_discovery.NewClient( + viper.GetString("discovery-server"), + component.Identity, + func() string { + token, _ := component.BuildJWT() + return token + }, + ) if err != nil { return nil, err } - component.Discovery = pb_discovery.NewClient(discoveryConn, component.Identity, func() string { - token, _ := component.BuildJWT() - return token - }) } if pub, priv, cert, err := security.LoadKeys(viper.GetString("key-dir")); err == nil { From 5753b681daafaff0d609cb11186ab12482ef1ade Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 20 Sep 2016 16:22:59 +0200 Subject: [PATCH 1771/2266] Dockerfile: apk `--no-cache` parameter --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 03efc6148..d4183019c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ FROM alpine -RUN apk --update add ca-certificates && rm -rf /var/cache/apk/* +RUN apk --update --no-cache add ca-certificates ADD ./release/ttn-linux-amd64 /usr/local/bin/ttn RUN chmod 755 /usr/local/bin/ttn ENTRYPOINT ["/usr/local/bin/ttn"] From c21c0fceaa2f69a09cf6410fc7c3ad5d740c4ae8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 21 Sep 2016 08:52:46 +0200 Subject: [PATCH 1772/2266] Make MQTT client independent from apex/log --- mqtt/client.go | 35 +++++---- mqtt/client_test.go | 182 +++++++++++++++++++++++++------------------- mqtt/logging.go | 29 +++++++ 3 files changed, 153 insertions(+), 93 deletions(-) create mode 100644 mqtt/logging.go diff --git a/mqtt/client.go b/mqtt/client.go index ac8c43394..00c019326 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -10,10 +10,13 @@ import ( "time" "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/apex/log" MQTT "github.com/eclipse/paho.mqtt.golang" ) +// QoS indicates the MQTT Quality of Service level. +// 0: The broker/client will deliver the message once, with no confirmation. +// 1: The broker/client will deliver the message at least once, with confirmation required. +// 2: The broker/client will deliver the message exactly once by using a four step handshake. const QoS = 0x00 // Client connects to the MQTT server and can publish/subscribe on uplink, downlink and activations from devices @@ -139,12 +142,16 @@ type ActivationHandler func(client Client, appID string, devID string, req Activ // DefaultClient is the default MQTT client for The Things Network type DefaultClient struct { mqtt MQTT.Client - ctx log.Interface + ctx Logger subscriptions map[string]MQTT.MessageHandler } // NewClient creates a new DefaultClient -func NewClient(ctx log.Interface, id, username, password string, brokers ...string) Client { +func NewClient(ctx Logger, id, username, password string, brokers ...string) Client { + if ctx == nil { + ctx = &noopLogger{} + } + mqttOpts := MQTT.NewClientOptions() for _, broker := range brokers { @@ -162,13 +169,13 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri mqttOpts.SetCleanSession(true) mqttOpts.SetDefaultPublishHandler(func(client MQTT.Client, msg MQTT.Message) { - ctx.WithField("message", msg).Warn("Received unhandled message") + ctx.Warnf("Received unhandled message: %v", msg) }) var reconnecting bool mqttOpts.SetConnectionLostHandler(func(client MQTT.Client, err error) { - ctx.WithError(err).Warn("Disconnected, reconnecting...") + ctx.Warnf("Disconnected (%s). Reconnecting...", err.Error()) reconnecting = true }) @@ -181,7 +188,7 @@ func NewClient(ctx log.Interface, id, username, password string, brokers ...stri ctx.Info("Connected to MQTT") if reconnecting { for topic, handler := range ttnClient.subscriptions { - ctx.Infof("Re-subscribing to %s", topic) + ctx.Infof("Re-subscribing to topic: %s", topic) ttnClient.subscribe(topic, handler) } reconnecting = false @@ -217,11 +224,11 @@ func (c *DefaultClient) Connect() error { if err == nil { break } - c.ctx.WithError(err).Warn("Could not connect to MQTT Broker. Retrying...") + c.ctx.Warnf("Could not connect to MQTT Broker (%s). Retrying...", err.Error()) <-time.After(ConnectRetryDelay) } if err != nil { - return fmt.Errorf("Could not connect to MQTT Broker: %s", err) + return fmt.Errorf("Could not connect to MQTT Broker (%s).", err) } return nil } @@ -307,7 +314,7 @@ func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handle // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid uplink topic") + c.ctx.Warnf("Received message on invalid uplink topic: %s", msg.Topic()) return } @@ -318,7 +325,7 @@ func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handle dataUp.DevID = topic.DevID if err != nil { - c.ctx.WithError(err).Warn("Could not unmarshal uplink") + c.ctx.Warnf("Could not unmarshal uplink (%s).", err.Error()) return } @@ -372,7 +379,7 @@ func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, hand // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Downlink topic") + c.ctx.Warnf("Received message on invalid downlink topic: %s", msg.Topic()) return } @@ -380,7 +387,7 @@ func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, hand dataDown := &DownlinkMessage{} err = json.Unmarshal(msg.Payload(), dataDown) if err != nil { - c.ctx.WithError(err).Warn("Could not unmarshal Downlink") + c.ctx.Warnf("Could not unmarshal downlink (%s).", err.Error()) return } dataDown.AppID = topic.AppID @@ -436,7 +443,7 @@ func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, h // Determine the actual topic topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { - c.ctx.WithField("topic", msg.Topic()).WithError(err).Warn("Received message on invalid Activations topic") + c.ctx.Warnf("Received message on invalid activations topic: %s", msg.Topic()) return } @@ -444,7 +451,7 @@ func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, h activation := &Activation{} err = json.Unmarshal(msg.Payload(), activation) if err != nil { - c.ctx.WithError(err).Warn("Could not unmarshal Activation") + c.ctx.Warnf("Could not unmarshal activation (%s).", err.Error()) return } activation.AppID = topic.AppID diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 2f4857e93..71251e47e 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -25,6 +25,12 @@ func init() { } } +func waitForOK(token Token, a *Assertion) { + success := token.WaitTimeout(100 * time.Millisecond) + a.So(success, ShouldBeTrue) + a.So(token.Error(), ShouldBeNil) +} + func TestToken(t *testing.T) { a := New(t) @@ -98,14 +104,17 @@ func TestDisconnect(t *testing.T) { } func TestRandomTopicPublish(t *testing.T) { + a := New(t) ctx := GetLogger(t, "TestRandomTopicPublish") c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) c.Connect() defer c.Disconnect() - c.(*DefaultClient).mqtt.Subscribe("randomtopic", QoS, nil).Wait() - c.(*DefaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}).Wait() + subToken := c.(*DefaultClient).mqtt.Subscribe("randomtopic", QoS, nil) + waitForOK(subToken, a) + pubToken := c.(*DefaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}) + waitForOK(pubToken, a) <-time.After(50 * time.Millisecond) @@ -127,20 +136,20 @@ func TestPublishUplink(t *testing.T) { } token := c.PublishUplink(dataUp) - token.Wait() - - a.So(token.Error(), ShouldBeNil) + waitForOK(token, a) } func TestPublishUplinkFields(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + ctx := GetLogger(t, "Test") + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() defer c.Disconnect() waitChan := make(chan bool, 1) expected := 8 - c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { + subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { case "battery": @@ -172,7 +181,8 @@ func TestPublishUplinkFields(t *testing.T) { if expected == 0 { waitChan <- true } - }).Wait() + }) + waitForOK(subToken, a) fields := map[string]interface{}{ "battery": 90, @@ -189,9 +199,8 @@ func TestPublishUplinkFields(t *testing.T) { "gps": []float64{52.3736735, 4.886663}, } - token := c.PublishUplinkFields("fields-app", "fields-dev", fields) - token.Wait() - a.So(token.Error(), ShouldBeNil) + pubToken := c.PublishUplinkFields("fields-app", "fields-dev", fields) + waitForOK(pubToken, a) select { case <-waitChan: @@ -206,15 +215,13 @@ func TestSubscribeDeviceUplink(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { }) - token.Wait() - a.So(token.Error(), ShouldBeNil) + waitForOK(subToken, a) - token = c.UnsubscribeDeviceUplink("someid", "someid") - token.Wait() - a.So(token.Error(), ShouldBeNil) + unsubToken := c.UnsubscribeDeviceUplink("someid", "someid") + waitForOK(unsubToken, a) } func TestSubscribeAppUplink(t *testing.T) { @@ -223,15 +230,13 @@ func TestSubscribeAppUplink(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { }) - token.Wait() - a.So(token.Error(), ShouldBeNil) + waitForOK(subToken, a) - token = c.UnsubscribeAppUplink("someid") - token.Wait() - a.So(token.Error(), ShouldBeNil) + unsubToken := c.UnsubscribeAppUplink("someid") + waitForOK(unsubToken, a) } func TestSubscribeUplink(t *testing.T) { @@ -240,15 +245,13 @@ func TestSubscribeUplink(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { }) - token.Wait() - a.So(token.Error(), ShouldBeNil) + waitForOK(subToken, a) - token = c.UnsubscribeUplink() - token.Wait() - a.So(token.Error(), ShouldBeNil) + unsubToken := c.UnsubscribeUplink() + waitForOK(unsubToken, a) } func TestPubSubUplink(t *testing.T) { @@ -259,18 +262,20 @@ func TestPubSubUplink(t *testing.T) { waitChan := make(chan bool, 1) - c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { a.So(appID, ShouldResemble, "app1") a.So(devID, ShouldResemble, "dev1") waitChan <- true - }).Wait() + }) + waitForOK(subToken, a) - c.PublishUplink(UplinkMessage{ + pubToken := c.PublishUplink(UplinkMessage{ Payload: []byte{0x01, 0x02, 0x03, 0x04}, AppID: "app1", DevID: "dev1", - }).Wait() + }) + waitForOK(pubToken, a) select { case <-waitChan: @@ -278,7 +283,8 @@ func TestPubSubUplink(t *testing.T) { panic("Did not receive Uplink") } - c.UnsubscribeDeviceUplink("app1", "dev1").Wait() + unsubToken := c.UnsubscribeDeviceUplink("app1", "dev1") + waitForOK(unsubToken, a) } func TestPubSubAppUplink(t *testing.T) { @@ -291,26 +297,30 @@ func TestPubSubAppUplink(t *testing.T) { wg.Add(2) - c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { a.So(appID, ShouldResemble, "app2") a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() - }).Wait() + }) + waitForOK(subToken, a) - c.PublishUplink(UplinkMessage{ + pubToken := c.PublishUplink(UplinkMessage{ AppID: "app2", DevID: "dev1", Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }).Wait() - c.PublishUplink(UplinkMessage{ + }) + waitForOK(pubToken, a) + pubToken = c.PublishUplink(UplinkMessage{ AppID: "app2", DevID: "dev2", Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }).Wait() + }) + waitForOK(pubToken, a) a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - c.UnsubscribeAppUplink("app1").Wait() + unsubToken := c.UnsubscribeAppUplink("app1") + waitForOK(unsubToken, a) } // Downlink pub/sub @@ -328,7 +338,7 @@ func TestPublishDownlink(t *testing.T) { } token := c.PublishDownlink(dataDown) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -342,11 +352,11 @@ func TestSubscribeDeviceDownlink(t *testing.T) { token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req DownlinkMessage) { }) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) token = c.UnsubscribeDeviceDownlink("someid", "someid") - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -359,11 +369,11 @@ func TestSubscribeAppDownlink(t *testing.T) { token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req DownlinkMessage) { }) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) token = c.UnsubscribeAppDownlink("someid") - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -376,11 +386,11 @@ func TestSubscribeDownlink(t *testing.T) { token := c.SubscribeDownlink(func(client Client, appID string, devID string, req DownlinkMessage) { }) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) token = c.UnsubscribeDownlink() - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -394,22 +404,25 @@ func TestPubSubDownlink(t *testing.T) { wg.Add(1) - c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req DownlinkMessage) { + subToken := c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req DownlinkMessage) { a.So(appID, ShouldResemble, "app3") a.So(devID, ShouldResemble, "dev3") wg.Done() - }).Wait() + }) + waitForOK(subToken, a) - c.PublishDownlink(DownlinkMessage{ + pubToken := c.PublishDownlink(DownlinkMessage{ AppID: "app3", DevID: "dev3", Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }).Wait() + }) + waitForOK(pubToken, a) a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - c.UnsubscribeDeviceDownlink("app3", "dev3").Wait() + unsubToken := c.UnsubscribeDeviceDownlink("app3", "dev3") + waitForOK(unsubToken, a) } func TestPubSubAppDownlink(t *testing.T) { @@ -422,26 +435,30 @@ func TestPubSubAppDownlink(t *testing.T) { wg.Add(2) - c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { + subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { a.So(appID, ShouldResemble, "app4") a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() - }).Wait() + }) + waitForOK(subToken, a) - c.PublishDownlink(DownlinkMessage{ + pubToken := c.PublishDownlink(DownlinkMessage{ AppID: "app4", DevID: "dev1", Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }).Wait() - c.PublishDownlink(DownlinkMessage{ + }) + waitForOK(pubToken, a) + pubToken = c.PublishDownlink(DownlinkMessage{ AppID: "app4", DevID: "dev2", Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }).Wait() + }) + waitForOK(pubToken, a) a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - c.UnsubscribeAppDownlink("app3").Wait() + unsubToken := c.UnsubscribeAppDownlink("app3") + waitForOK(unsubToken, a) } // Activations pub/sub @@ -459,7 +476,7 @@ func TestPublishActivations(t *testing.T) { } token := c.PublishActivation(dataActivations) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -473,11 +490,11 @@ func TestSubscribeDeviceActivations(t *testing.T) { token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req Activation) { }) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) token = c.UnsubscribeDeviceActivations("someid", "someid") - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -490,11 +507,11 @@ func TestSubscribeAppActivations(t *testing.T) { token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req Activation) { }) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) token = c.UnsubscribeAppActivations("someid") - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -507,11 +524,11 @@ func TestSubscribeActivations(t *testing.T) { token := c.SubscribeActivations(func(client Client, appID string, devID string, req Activation) { }) - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) token = c.UnsubscribeActivations() - token.Wait() + waitForOK(token, a) a.So(token.Error(), ShouldBeNil) } @@ -525,22 +542,25 @@ func TestPubSubActivations(t *testing.T) { wg.Add(1) - c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req Activation) { + subToken := c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req Activation) { a.So(appID, ShouldResemble, "app5") a.So(devID, ShouldResemble, "dev1") wg.Done() - }).Wait() + }) + waitForOK(subToken, a) - c.PublishActivation(Activation{ + pubToken := c.PublishActivation(Activation{ AppID: "app5", DevID: "dev1", Metadata: Metadata{DataRate: "SF7BW125"}, - }).Wait() + }) + waitForOK(pubToken, a) a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - c.UnsubscribeDeviceActivations("app5", "dev1") + unsubToken := c.UnsubscribeDeviceActivations("app5", "dev1") + waitForOK(unsubToken, a) } func TestPubSubAppActivations(t *testing.T) { @@ -553,26 +573,30 @@ func TestPubSubAppActivations(t *testing.T) { wg.Add(2) - c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { + subToken := c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { a.So(appID, ShouldResemble, "app6") a.So(req.Metadata.DataRate, ShouldEqual, "SF7BW125") wg.Done() - }).Wait() + }) + waitForOK(subToken, a) - c.PublishActivation(Activation{ + pubToken := c.PublishActivation(Activation{ AppID: "app6", DevID: "dev1", Metadata: Metadata{DataRate: "SF7BW125"}, - }).Wait() - c.PublishActivation(Activation{ + }) + waitForOK(pubToken, a) + pubToken = c.PublishActivation(Activation{ AppID: "app6", DevID: "dev2", Metadata: Metadata{DataRate: "SF7BW125"}, - }).Wait() + }) + waitForOK(pubToken, a) a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - c.UnsubscribeAppActivations("app6") + unsubToken := c.UnsubscribeAppActivations("app6") + waitForOK(unsubToken, a) } func ExampleNewClient() { diff --git a/mqtt/logging.go b/mqtt/logging.go new file mode 100644 index 000000000..f53056a12 --- /dev/null +++ b/mqtt/logging.go @@ -0,0 +1,29 @@ +package mqtt + +// Logger used in mqtt package +type Logger interface { + Debug(msg string) + Info(msg string) + Warn(msg string) + Error(msg string) + Fatal(msg string) + Debugf(msg string, v ...interface{}) + Infof(msg string, v ...interface{}) + Warnf(msg string, v ...interface{}) + Errorf(msg string, v ...interface{}) + Fatalf(msg string, v ...interface{}) +} + +// noopLogger just does nothing +type noopLogger struct{} + +func (l noopLogger) Debug(msg string) {} +func (l noopLogger) Info(msg string) {} +func (l noopLogger) Warn(msg string) {} +func (l noopLogger) Error(msg string) {} +func (l noopLogger) Fatal(msg string) {} +func (l noopLogger) Debugf(msg string, v ...interface{}) {} +func (l noopLogger) Infof(msg string, v ...interface{}) {} +func (l noopLogger) Warnf(msg string, v ...interface{}) {} +func (l noopLogger) Errorf(msg string, v ...interface{}) {} +func (l noopLogger) Fatalf(msg string, v ...interface{}) {} From f4939d12881de226a12b345ff469d4a326b6f47f Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 21 Sep 2016 09:03:18 +0200 Subject: [PATCH 1773/2266] core/router/downlink.go: remove named err variable --- core/router/downlink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index 0a563e98a..d396596b9 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -49,7 +49,7 @@ func (r *router) UnsubscribeDownlink(gatewayID string) error { return nil } -func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) (err error) { +func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { option := downlink.DownlinkOption downlinkMessage := &pb.DownlinkMessage{ From 51acb7e6a1fc491128cdf3c1a9e5997fc33f620b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 21 Sep 2016 12:04:18 +0200 Subject: [PATCH 1774/2266] Return error on invalid MQTT FixedHeader --- .gitmodules | 2 +- vendor/github.com/eclipse/paho.mqtt.golang | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index cc89b9e1c..286988361 100644 --- a/.gitmodules +++ b/.gitmodules @@ -12,7 +12,7 @@ url = https://github.com/dgrijalva/jwt-go [submodule "vendor/github.com/eclipse/paho.mqtt.golang"] path = vendor/github.com/eclipse/paho.mqtt.golang - url = https://github.com/eclipse/paho.mqtt.golang + url = https://github.com/htdvisser/paho.mqtt.golang [submodule "vendor/github.com/golang/mock"] path = vendor/github.com/golang/mock url = https://github.com/golang/mock diff --git a/vendor/github.com/eclipse/paho.mqtt.golang b/vendor/github.com/eclipse/paho.mqtt.golang index b0f0cce24..b3d5b9734 160000 --- a/vendor/github.com/eclipse/paho.mqtt.golang +++ b/vendor/github.com/eclipse/paho.mqtt.golang @@ -1 +1 @@ -Subproject commit b0f0cce24f138f7a1d034f55069883c7dcb6a9d7 +Subproject commit b3d5b973455691c8a6b7e9c4f402dc4e7da6a2e2 From 5273c296022bb6cfb7bbe4cd7da711c0e566aac3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 21 Sep 2016 12:12:55 +0200 Subject: [PATCH 1775/2266] Add tests for MQTT token --- mqtt/client_test.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 71251e47e..076934297 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -4,6 +4,7 @@ package mqtt import ( + "errors" "fmt" "os" "strings" @@ -34,6 +35,31 @@ func waitForOK(token Token, a *Assertion) { func TestToken(t *testing.T) { a := New(t) + okToken := newToken() + go func() { + time.Sleep(1 * time.Millisecond) + okToken.flowComplete() + }() + okToken.Wait() + a.So(okToken.Error(), ShouldBeNil) + + failToken := newToken() + go func() { + time.Sleep(1 * time.Millisecond) + failToken.err = errors.New("Err") + failToken.flowComplete() + }() + failToken.Wait() + a.So(failToken.Error(), ShouldNotBeNil) + + timeoutToken := newToken() + timeoutTokenDone := timeoutToken.WaitTimeout(5 * time.Millisecond) + a.So(timeoutTokenDone, ShouldBeFalse) +} + +func TestSimpleToken(t *testing.T) { + a := New(t) + okToken := simpleToken{} okToken.Wait() a.So(okToken.Error(), ShouldBeNil) From 49ef5909d2e9f443f44867c543367ca56e1ff22b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 21 Sep 2016 12:57:32 +0200 Subject: [PATCH 1776/2266] Fix mqtt TestPublishUplinkFields --- mqtt/client_test.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 076934297..31cf3bc99 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -173,10 +173,16 @@ func TestPublishUplinkFields(t *testing.T) { c.Connect() defer c.Disconnect() - waitChan := make(chan bool, 1) - expected := 8 + subChan := make(chan bool) + waitChan := make(chan bool) + go func() { + for i := 10; i > 0; i-- { + <-subChan + } + close(subChan) + waitChan <- true + }() subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { - switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { case "battery": a.So(string(msg.Payload()), ShouldEqual, "90") @@ -202,11 +208,7 @@ func TestPublishUplinkFields(t *testing.T) { t.Errorf("Should not have received message on topic %s", msg.Topic()) t.Fail() } - - expected-- - if expected == 0 { - waitChan <- true - } + subChan <- true }) waitForOK(subToken, a) From c28678e1bbfd3b996d790e746649f7d2cc51dc0a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 21 Sep 2016 15:55:12 +0200 Subject: [PATCH 1777/2266] Split networkserver functionality into separate files --- core/networkserver/activation.go | 129 ++++++++ core/networkserver/activation_test.go | 123 ++++++++ core/networkserver/downlink.go | 59 ++++ core/networkserver/downlink_test.go | 86 ++++++ core/networkserver/get_devices.go | 50 ++++ core/networkserver/get_devices_test.go | 120 ++++++++ core/networkserver/networkserver.go | 285 ------------------ core/networkserver/networkserver_test.go | 365 ----------------------- core/networkserver/uplink.go | 87 ++++++ core/networkserver/uplink_test.go | 87 ++++++ 10 files changed, 741 insertions(+), 650 deletions(-) create mode 100644 core/networkserver/activation.go create mode 100644 core/networkserver/activation_test.go create mode 100644 core/networkserver/downlink.go create mode 100644 core/networkserver/downlink_test.go create mode 100644 core/networkserver/get_devices.go create mode 100644 core/networkserver/get_devices_test.go create mode 100644 core/networkserver/uplink.go create mode 100644 core/networkserver/uplink_test.go diff --git a/core/networkserver/activation.go b/core/networkserver/activation.go new file mode 100644 index 000000000..58243727c --- /dev/null +++ b/core/networkserver/activation.go @@ -0,0 +1,129 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "fmt" + "strings" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/random" + "github.com/brocaar/lorawan" +) + +func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) { + // Instantiate a new random source + random := random.New() + + // Generate random DevAddr bytes + var devAddr types.DevAddr + copy(devAddr[:], random.Bytes(4)) + + // Get a random prefix that matches the constraints + prefixes := n.GetPrefixesFor(constraints...) + if len(prefixes) == 0 { + return types.DevAddr{}, errors.NewErrNotFound(fmt.Sprintf("DevAddr prefix with constraints %v", constraints)) + } + + // Select a prefix + prefix := prefixes[random.Intn(len(prefixes))] + + // Apply the prefix + devAddr = devAddr.WithPrefix(prefix) + + return devAddr, nil +} + +func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { + if activation.AppEui == nil || activation.DevEui == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") + } + dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) + if err != nil { + return nil, err + } + activation.AppId = dev.AppID + activation.DevId = dev.DevID + + // Get activation constraints (for DevAddr prefix selection) + activationConstraints := strings.Split(dev.Options.ActivationConstraints, ",") + if len(activationConstraints) == 1 && activationConstraints[0] == "" { + activationConstraints = []string{} + } + activationConstraints = append(activationConstraints, "otaa") + + // Build activation metadata if not present + if meta := activation.GetActivationMetadata(); meta == nil { + activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} + } + // Build lorawan metadata if not present + if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") + } + + // Build response template if not present + if pld := activation.GetResponseTemplate(); pld == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing response template") + } + lorawanMeta := activation.ActivationMetadata.GetLorawan() + + // Get a random device address + devAddr, err := n.getDevAddr(activationConstraints...) + if err != nil { + return nil, err + } + + // Set the DevAddr in the Activation Metadata + lorawanMeta.DevAddr = &devAddr + + // Build JoinAccept Payload + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.JoinAccept, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.JoinAcceptPayload{ + NetID: n.netID, + DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)}, + RXDelay: uint8(lorawanMeta.RxDelay), + DevAddr: lorawan.DevAddr(devAddr), + }, + } + if len(lorawanMeta.CfList) == 5 { + var cfList lorawan.CFList + for i, cfListItem := range lorawanMeta.CfList { + cfList[i] = uint32(cfListItem) + } + phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList + } + + // Set the Payload + phyBytes, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + activation.ResponseTemplate.Payload = phyBytes + + return activation, nil +} + +func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) { + meta := activation.GetActivationMetadata() + if meta == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing ActivationMetadata") + } + lorawan := meta.GetLorawan() + if lorawan == nil { + return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") + } + err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) + if err != nil { + return nil, err + } + return activation, nil +} diff --git a/core/networkserver/activation_test.go b/core/networkserver/activation_test.go new file mode 100644 index 000000000..f2fa6559b --- /dev/null +++ b/core/networkserver/activation_test.go @@ -0,0 +1,123 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandlePrepareActivation(t *testing.T) { + a := New(t) + ns := &networkServer{ + netID: [3]byte{0x00, 0x00, 0x13}, + prefixes: map[types.DevAddrPrefix][]string{ + types.DevAddrPrefix{DevAddr: [4]byte{0x26, 0x00, 0x00, 0x00}, Length: 7}: []string{ + "otaa", + "local", + }, + }, + devices: device.NewDeviceStore(), + } + + appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) + + // Device not registered + resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldNotBeNil) + + // Constrained Device + ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ + ActivationConstraints: "private", + }}) + resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + DevEui: &devEUI, + AppEui: &appEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldNotBeNil) + + // Device registered + ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI}) + resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ + DevEui: &devEUI, + AppEui: &appEUI, + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + }, + }}, + ResponseTemplate: &pb_broker.DeviceActivationResponse{}, + }) + a.So(err, ShouldBeNil) + devAddr := resp.ActivationMetadata.GetLorawan().DevAddr + a.So(devAddr.IsEmpty(), ShouldBeFalse) + a.So(devAddr[0]&254, ShouldEqual, 19<<1) // 7 MSB should be NetID + + var resPHY lorawan.PHYPayload + resPHY.UnmarshalBinary(resp.ResponseTemplate.Payload) + resMAC, _ := resPHY.MACPayload.(*lorawan.DataPayload) + joinAccept := &lorawan.JoinAcceptPayload{} + joinAccept.UnmarshalBinary(false, resMAC.Bytes) + + a.So(joinAccept.DevAddr[0]&254, ShouldEqual, 19<<1) + a.So(*joinAccept.CFList, ShouldEqual, lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000}) +} + +func TestHandleActivate(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + ns.devices.Set(&device.Device{ + AppEUI: types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), + DevEUI: types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), + }) + + _, err := ns.HandleActivate(&pb_handler.DeviceActivationResponse{}) + a.So(err, ShouldNotBeNil) + + _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ + ActivationMetadata: &pb_protocol.ActivationMetadata{}, + }) + a.So(err, ShouldNotBeNil) + + devAddr := getDevAddr(0, 0, 3, 1) + var nwkSKey types.NwkSKey + copy(nwkSKey[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1}) + appEUI := types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) + devEUI := types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) + _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ + ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ + Lorawan: &pb_lorawan.ActivationMetadata{ + AppEui: &appEUI, + DevEui: &devEUI, + DevAddr: &devAddr, + NwkSKey: &nwkSKey, + }, + }}, + }) + a.So(err, ShouldBeNil) +} diff --git a/core/networkserver/downlink.go b/core/networkserver/downlink.go new file mode 100644 index 000000000..1735e1bd4 --- /dev/null +++ b/core/networkserver/downlink.go @@ -0,0 +1,59 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) { + // Get Device + dev, err := n.devices.Get(*message.AppEui, *message.DevEui) + if err != nil { + return nil, err + } + + if dev.AppID != message.AppId || dev.DevID != message.DevId { + return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") + } + + // Unmarshal LoRaWAN Payload + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(message.Payload) + if err != nil { + return nil, err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") + } + + // Set DevAddr + macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr) + + // FIRST set and THEN increment FCntDown + // TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK + macPayload.FHDR.FCnt = dev.FCntDown + dev.FCntDown++ + err = n.devices.Set(dev, "f_cnt_down") + if err != nil { + return nil, err + } + + // TODO: Maybe we need to add MAC commands on downlink + + // Sign MIC + phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) + + // Update message + bytes, err := phyPayload.MarshalBinary() + if err != nil { + return nil, err + } + message.Payload = bytes + + return message, nil +} diff --git a/core/networkserver/downlink_test.go b/core/networkserver/downlink_test.go new file mode 100644 index 000000000..2de6467a9 --- /dev/null +++ b/core/networkserver/downlink_test.go @@ -0,0 +1,86 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandleDownlink(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devAddr := getDevAddr(1, 2, 3, 4) + + // Device Not Found + message := &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err := ns.HandleDownlink(message) + a.So(err, ShouldNotBeNil) + + ns.devices.Set(&device.Device{ + DevAddr: devAddr, + AppEUI: appEUI, + DevEUI: devEUI, + }) + + // Invalid Payload + message = &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err = ns.HandleDownlink(message) + a.So(err, ShouldNotBeNil) + + fPort := uint8(3) + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FPort: &fPort, + FHDR: lorawan.FHDR{ + FCtrl: lorawan.FCtrl{ + ACK: true, + }, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + message = &pb_broker.DownlinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: bytes, + } + res, err := ns.HandleDownlink(message) + a.So(err, ShouldBeNil) + + var phyPayload lorawan.PHYPayload + phyPayload.UnmarshalBinary(res.Payload) + macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + a.So(*macPayload.FPort, ShouldEqual, 3) + a.So(macPayload.FHDR.DevAddr, ShouldEqual, lorawan.DevAddr{1, 2, 3, 4}) + a.So(macPayload.FHDR.FCnt, ShouldEqual, 0) // The first Frame counter is zero + a.So(phyPayload.MIC, ShouldNotEqual, [4]byte{0, 0, 0, 0}) // MIC should be set, we'll check it with actual examples in the integration test + + dev, _ := ns.devices.Get(appEUI, devEUI) + a.So(dev.FCntDown, ShouldEqual, 1) + +} diff --git a/core/networkserver/get_devices.go b/core/networkserver/get_devices.go new file mode 100644 index 000000000..f9e635a48 --- /dev/null +++ b/core/networkserver/get_devices.go @@ -0,0 +1,50 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/fcnt" +) + +func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { + devices, err := n.devices.GetWithAddress(*req.DevAddr) + if err != nil { + return nil, err + } + + // Return all devices with DevAddr with FCnt <= fCnt or Security off + + res := &pb.DevicesResponse{ + Results: make([]*pb_lorawan.Device, 0, len(devices)), + } + + for _, device := range devices { + fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) + dev := &pb_lorawan.Device{ + AppEui: &device.AppEUI, + AppId: device.AppID, + DevEui: &device.DevEUI, + DevId: device.DevID, + NwkSKey: &device.NwkSKey, + FCntUp: device.FCntUp, + Uses32BitFCnt: device.Options.Uses32BitFCnt, + DisableFCntCheck: device.Options.DisableFCntCheck, + } + if device.Options.DisableFCntCheck { + res.Results = append(res.Results, dev) + continue + } + if device.FCntUp <= req.FCnt { + res.Results = append(res.Results, dev) + continue + } else if device.Options.Uses32BitFCnt && device.FCntUp <= fullFCnt { + res.Results = append(res.Results, dev) + continue + } + } + + return res, nil +} diff --git a/core/networkserver/get_devices_test.go b/core/networkserver/get_devices_test.go new file mode 100644 index 000000000..9c381dfe0 --- /dev/null +++ b/core/networkserver/get_devices_test.go @@ -0,0 +1,120 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestHandleGetDevices(t *testing.T) { + a := New(t) + + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} + + // No Devices + devAddr1 := getDevAddr(1, 2, 3, 4) + res, err := ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr1, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldBeEmpty) + + // Matching Device + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(1, 2, 3, 4), + AppEUI: types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: nwkSKey, + FCntUp: 5, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr1, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // Non-Matching DevAddr + devAddr2 := getDevAddr(5, 6, 7, 8) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr2, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 0) + + // Non-Matching FCnt + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr1, + FCnt: 4, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 0) + + // Non-Matching FCnt, but FCnt Check Disabled + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(5, 6, 7, 8), + AppEUI: types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), + DevEUI: types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), + NwkSKey: nwkSKey, + FCntUp: 5, + Options: device.Options{ + DisableFCntCheck: true, + }, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr2, + FCnt: 4, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // 32 Bit Frame Counter (A) + devAddr3 := getDevAddr(2, 2, 3, 4) + ns.devices.Set(&device.Device{ + DevAddr: getDevAddr(2, 2, 3, 4), + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: nwkSKey, + FCntUp: 5 + (2 << 16), + Options: device.Options{ + Uses32BitFCnt: true, + }, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr3, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + + // 32 Bit Frame Counter (B) + ns.devices.Set(&device.Device{ + DevAddr: devAddr3, + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + NwkSKey: nwkSKey, + FCntUp: (2 << 16) - 1, + Options: device.Options{ + Uses32BitFCnt: true, + }, + }) + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ + DevAddr: &devAddr3, + FCnt: 5, + }) + a.So(err, ShouldBeNil) + a.So(res.Results, ShouldHaveLength, 1) + +} diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index e6540838f..0cc08ac0a 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -4,22 +4,13 @@ package networkserver import ( - "fmt" - "strings" - "time" - pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" - pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" - pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/random" - "github.com/brocaar/lorawan" "gopkg.in/redis.v3" ) @@ -93,279 +84,3 @@ func (n *networkServer) Init(c *core.Component) error { n.Component.SetStatus(core.StatusHealthy) return nil } - -func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { - devices, err := n.devices.GetWithAddress(*req.DevAddr) - if err != nil { - return nil, err - } - - // Return all devices with DevAddr with FCnt <= fCnt or Security off - - res := &pb.DevicesResponse{ - Results: make([]*pb_lorawan.Device, 0, len(devices)), - } - - for _, device := range devices { - fullFCnt := fcnt.GetFull(device.FCntUp, uint16(req.FCnt)) - dev := &pb_lorawan.Device{ - AppEui: &device.AppEUI, - AppId: device.AppID, - DevEui: &device.DevEUI, - DevId: device.DevID, - NwkSKey: &device.NwkSKey, - FCntUp: device.FCntUp, - Uses32BitFCnt: device.Options.Uses32BitFCnt, - DisableFCntCheck: device.Options.DisableFCntCheck, - } - if device.Options.DisableFCntCheck { - res.Results = append(res.Results, dev) - continue - } - if device.FCntUp <= req.FCnt { - res.Results = append(res.Results, dev) - continue - } else if device.Options.Uses32BitFCnt && device.FCntUp <= fullFCnt { - res.Results = append(res.Results, dev) - continue - } - } - - return res, nil -} - -func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) { - // Instantiate a new random source - random := random.New() - - // Generate random DevAddr bytes - var devAddr types.DevAddr - copy(devAddr[:], random.Bytes(4)) - - // Get a random prefix that matches the constraints - prefixes := n.GetPrefixesFor(constraints...) - if len(prefixes) == 0 { - return types.DevAddr{}, errors.NewErrNotFound(fmt.Sprintf("DevAddr prefix with constraints %v", constraints)) - } - - // Select a prefix - prefix := prefixes[random.Intn(len(prefixes))] - - // Apply the prefix - devAddr = devAddr.WithPrefix(prefix) - - return devAddr, nil -} - -func (n *networkServer) HandlePrepareActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb_broker.DeduplicatedDeviceActivationRequest, error) { - if activation.AppEui == nil || activation.DevEui == nil { - return nil, errors.NewErrInvalidArgument("Activation", "missing AppEUI or DevEUI") - } - dev, err := n.devices.Get(*activation.AppEui, *activation.DevEui) - if err != nil { - return nil, err - } - activation.AppId = dev.AppID - activation.DevId = dev.DevID - - // Get activation constraints (for DevAddr prefix selection) - activationConstraints := strings.Split(dev.Options.ActivationConstraints, ",") - if len(activationConstraints) == 1 && activationConstraints[0] == "" { - activationConstraints = []string{} - } - activationConstraints = append(activationConstraints, "otaa") - - // Build activation metadata if not present - if meta := activation.GetActivationMetadata(); meta == nil { - activation.ActivationMetadata = &pb_protocol.ActivationMetadata{} - } - // Build lorawan metadata if not present - if lorawan := activation.ActivationMetadata.GetLorawan(); lorawan == nil { - return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN metadata") - } - - // Build response template if not present - if pld := activation.GetResponseTemplate(); pld == nil { - return nil, errors.NewErrInvalidArgument("Activation", "missing response template") - } - lorawanMeta := activation.ActivationMetadata.GetLorawan() - - // Get a random device address - devAddr, err := n.getDevAddr(activationConstraints...) - if err != nil { - return nil, err - } - - // Set the DevAddr in the Activation Metadata - lorawanMeta.DevAddr = &devAddr - - // Build JoinAccept Payload - phy := lorawan.PHYPayload{ - MHDR: lorawan.MHDR{ - MType: lorawan.JoinAccept, - Major: lorawan.LoRaWANR1, - }, - MACPayload: &lorawan.JoinAcceptPayload{ - NetID: n.netID, - DLSettings: lorawan.DLSettings{RX2DataRate: uint8(lorawanMeta.Rx2Dr), RX1DROffset: uint8(lorawanMeta.Rx1DrOffset)}, - RXDelay: uint8(lorawanMeta.RxDelay), - DevAddr: lorawan.DevAddr(devAddr), - }, - } - if len(lorawanMeta.CfList) == 5 { - var cfList lorawan.CFList - for i, cfListItem := range lorawanMeta.CfList { - cfList[i] = uint32(cfListItem) - } - phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList - } - - // Set the Payload - phyBytes, err := phy.MarshalBinary() - if err != nil { - return nil, err - } - activation.ResponseTemplate.Payload = phyBytes - - return activation, nil -} - -func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationResponse) (*pb_handler.DeviceActivationResponse, error) { - meta := activation.GetActivationMetadata() - if meta == nil { - return nil, errors.NewErrInvalidArgument("Activation", "missing ActivationMetadata") - } - lorawan := meta.GetLorawan() - if lorawan == nil { - return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") - } - err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) - if err != nil { - return nil, err - } - return activation, nil -} - -func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) { - // Get Device - dev, err := n.devices.Get(*message.AppEui, *message.DevEui) - if err != nil { - return nil, err - } - - // Unmarshal LoRaWAN Payload - var phyPayload lorawan.PHYPayload - err = phyPayload.UnmarshalBinary(message.Payload) - if err != nil { - return nil, err - } - macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") - } - - // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) - if lorawan := message.GetProtocolMetadata().GetLorawan(); lorawan != nil { - dev.FCntUp = lorawan.FCnt - } else { - dev.FCntUp = macPayload.FHDR.FCnt - } - dev.LastSeen = time.Now() - err = n.devices.Set(dev, "f_cnt_up", "last_seen") - if err != nil { - return nil, err - } - - // Prepare Downlink - if message.ResponseTemplate == nil { - return message, nil - } - message.ResponseTemplate.AppEui = message.AppEui - message.ResponseTemplate.DevEui = message.DevEui - message.ResponseTemplate.AppId = message.AppId - message.ResponseTemplate.DevId = message.DevId - - // Add Full FCnt (avoiding nil pointer panics) - if option := message.ResponseTemplate.DownlinkOption; option != nil { - if protocol := option.ProtocolConfig; protocol != nil { - if lorawan := protocol.GetLorawan(); lorawan != nil { - lorawan.FCnt = dev.FCntDown - } - } - } - - phy := lorawan.PHYPayload{ - MHDR: lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - }, - MACPayload: &lorawan.MACPayload{ - FHDR: lorawan.FHDR{ - DevAddr: macPayload.FHDR.DevAddr, - FCtrl: lorawan.FCtrl{ - ACK: phyPayload.MHDR.MType == lorawan.ConfirmedDataUp, - }, - FCnt: dev.FCntDown, - }, - }, - } - phyBytes, err := phy.MarshalBinary() - if err != nil { - return nil, err - } - - // TODO: Maybe we need to add MAC commands on downlink - - message.ResponseTemplate.Payload = phyBytes - - return message, nil -} - -func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_broker.DownlinkMessage, error) { - // Get Device - dev, err := n.devices.Get(*message.AppEui, *message.DevEui) - if err != nil { - return nil, err - } - - if dev.AppID != message.AppId || dev.DevID != message.DevId { - return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") - } - - // Unmarshal LoRaWAN Payload - var phyPayload lorawan.PHYPayload - err = phyPayload.UnmarshalBinary(message.Payload) - if err != nil { - return nil, err - } - macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) - if !ok { - return nil, errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") - } - - // Set DevAddr - macPayload.FHDR.DevAddr = lorawan.DevAddr(dev.DevAddr) - - // FIRST set and THEN increment FCntDown - // TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK - macPayload.FHDR.FCnt = dev.FCntDown - dev.FCntDown++ - err = n.devices.Set(dev, "f_cnt_down") - if err != nil { - return nil, err - } - - // TODO: Maybe we need to add MAC commands on downlink - - // Sign MIC - phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) - - // Update message - bytes, err := phyPayload.MarshalBinary() - if err != nil { - return nil, err - } - message.Payload = bytes - - return message, nil -} diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index fe96e5c1d..3194181cb 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -5,19 +5,11 @@ package networkserver import ( "testing" - "time" "gopkg.in/redis.v3" - "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" - pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - pb_handler "github.com/TheThingsNetwork/ttn/api/handler" - pb "github.com/TheThingsNetwork/ttn/api/networkserver" - pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" - pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" ) @@ -56,360 +48,3 @@ func TestUsePrefix(t *testing.T) { a.So(ns.UsePrefix(types.DevAddrPrefix{DevAddr: types.DevAddr([4]byte{0x26, 0, 0, 0}), Length: 7}, []string{"otaa"}), ShouldBeNil) a.So(ns.(*networkServer).prefixes, ShouldHaveLength, 1) } - -func TestHandleGetDevices(t *testing.T) { - a := New(t) - - ns := &networkServer{ - devices: device.NewDeviceStore(), - } - - nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} - - // No Devices - devAddr1 := getDevAddr(1, 2, 3, 4) - res, err := ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr1, - FCnt: 5, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldBeEmpty) - - // Matching Device - ns.devices.Set(&device.Device{ - DevAddr: getDevAddr(1, 2, 3, 4), - AppEUI: types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), - DevEUI: types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), - NwkSKey: nwkSKey, - FCntUp: 5, - }) - res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr1, - FCnt: 5, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldHaveLength, 1) - - // Non-Matching DevAddr - devAddr2 := getDevAddr(5, 6, 7, 8) - res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr2, - FCnt: 5, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldHaveLength, 0) - - // Non-Matching FCnt - res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr1, - FCnt: 4, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldHaveLength, 0) - - // Non-Matching FCnt, but FCnt Check Disabled - ns.devices.Set(&device.Device{ - DevAddr: getDevAddr(5, 6, 7, 8), - AppEUI: types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), - DevEUI: types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), - NwkSKey: nwkSKey, - FCntUp: 5, - Options: device.Options{ - DisableFCntCheck: true, - }, - }) - res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr2, - FCnt: 4, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldHaveLength, 1) - - // 32 Bit Frame Counter (A) - devAddr3 := getDevAddr(2, 2, 3, 4) - ns.devices.Set(&device.Device{ - DevAddr: getDevAddr(2, 2, 3, 4), - AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - NwkSKey: nwkSKey, - FCntUp: 5 + (2 << 16), - Options: device.Options{ - Uses32BitFCnt: true, - }, - }) - res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr3, - FCnt: 5, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldHaveLength, 1) - - // 32 Bit Frame Counter (B) - ns.devices.Set(&device.Device{ - DevAddr: devAddr3, - AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - NwkSKey: nwkSKey, - FCntUp: (2 << 16) - 1, - Options: device.Options{ - Uses32BitFCnt: true, - }, - }) - res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr3, - FCnt: 5, - }) - a.So(err, ShouldBeNil) - a.So(res.Results, ShouldHaveLength, 1) - -} - -func TestHandlePrepareActivation(t *testing.T) { - a := New(t) - ns := &networkServer{ - netID: [3]byte{0x00, 0x00, 0x13}, - prefixes: map[types.DevAddrPrefix][]string{ - types.DevAddrPrefix{DevAddr: [4]byte{0x26, 0x00, 0x00, 0x00}, Length: 7}: []string{ - "otaa", - "local", - }, - }, - devices: device.NewDeviceStore(), - } - - appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) - devEUI := types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) - - // Device not registered - resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ - ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ - Lorawan: &pb_lorawan.ActivationMetadata{ - CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, - }, - }}, - ResponseTemplate: &pb_broker.DeviceActivationResponse{}, - }) - a.So(err, ShouldNotBeNil) - - // Constrained Device - ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ - ActivationConstraints: "private", - }}) - resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ - DevEui: &devEUI, - AppEui: &appEUI, - ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ - Lorawan: &pb_lorawan.ActivationMetadata{ - CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, - }, - }}, - ResponseTemplate: &pb_broker.DeviceActivationResponse{}, - }) - a.So(err, ShouldNotBeNil) - - // Device registered - ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI}) - resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ - DevEui: &devEUI, - AppEui: &appEUI, - ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ - Lorawan: &pb_lorawan.ActivationMetadata{ - CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, - }, - }}, - ResponseTemplate: &pb_broker.DeviceActivationResponse{}, - }) - a.So(err, ShouldBeNil) - devAddr := resp.ActivationMetadata.GetLorawan().DevAddr - a.So(devAddr.IsEmpty(), ShouldBeFalse) - a.So(devAddr[0]&254, ShouldEqual, 19<<1) // 7 MSB should be NetID - - var resPHY lorawan.PHYPayload - resPHY.UnmarshalBinary(resp.ResponseTemplate.Payload) - resMAC, _ := resPHY.MACPayload.(*lorawan.DataPayload) - joinAccept := &lorawan.JoinAcceptPayload{} - joinAccept.UnmarshalBinary(false, resMAC.Bytes) - - a.So(joinAccept.DevAddr[0]&254, ShouldEqual, 19<<1) - a.So(*joinAccept.CFList, ShouldEqual, lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000}) -} - -func TestHandleActivate(t *testing.T) { - a := New(t) - ns := &networkServer{ - devices: device.NewDeviceStore(), - } - ns.devices.Set(&device.Device{ - AppEUI: types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), - DevEUI: types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), - }) - - _, err := ns.HandleActivate(&pb_handler.DeviceActivationResponse{}) - a.So(err, ShouldNotBeNil) - - _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ - ActivationMetadata: &pb_protocol.ActivationMetadata{}, - }) - a.So(err, ShouldNotBeNil) - - devAddr := getDevAddr(0, 0, 3, 1) - var nwkSKey types.NwkSKey - copy(nwkSKey[:], []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1}) - appEUI := types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) - devEUI := types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)) - _, err = ns.HandleActivate(&pb_handler.DeviceActivationResponse{ - ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ - Lorawan: &pb_lorawan.ActivationMetadata{ - AppEui: &appEUI, - DevEui: &devEUI, - DevAddr: &devAddr, - NwkSKey: &nwkSKey, - }, - }}, - }) - a.So(err, ShouldBeNil) -} - -func TestHandleUplink(t *testing.T) { - a := New(t) - ns := &networkServer{ - devices: device.NewDeviceStore(), - } - - appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) - devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) - devAddr := getDevAddr(1, 2, 3, 4) - - // Device Not Found - message := &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: []byte{}, - } - _, err := ns.HandleUplink(message) - a.So(err, ShouldNotBeNil) - - ns.devices.Set(&device.Device{ - DevAddr: devAddr, - AppEUI: appEUI, - DevEUI: devEUI, - }) - - // Invalid Payload - message = &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: []byte{}, - } - _, err = ns.HandleUplink(message) - a.So(err, ShouldNotBeNil) - - phy := lorawan.PHYPayload{ - MHDR: lorawan.MHDR{ - MType: lorawan.UnconfirmedDataUp, - Major: lorawan.LoRaWANR1, - }, - MACPayload: &lorawan.MACPayload{ - FHDR: lorawan.FHDR{ - DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), - FCnt: 1, - }, - }, - } - bytes, _ := phy.MarshalBinary() - - // Valid Uplink - message = &pb_broker.DeduplicatedUplinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: bytes, - ResponseTemplate: &pb_broker.DownlinkMessage{}, - } - res, err := ns.HandleUplink(message) - a.So(err, ShouldBeNil) - a.So(res.ResponseTemplate, ShouldNotBeNil) - - // LoRaWAN: Unmarshal - var phyPayload lorawan.PHYPayload - phyPayload.UnmarshalBinary(res.ResponseTemplate.Payload) - macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) - - a.So([4]byte(macPayload.FHDR.DevAddr), ShouldEqual, [4]byte(devAddr)) - - // Frame Counter should have been updated - dev, _ := ns.devices.Get(appEUI, devEUI) - a.So(dev.FCntUp, ShouldEqual, 1) - a.So(time.Now().Sub(dev.LastSeen), ShouldBeLessThan, 1*time.Second) -} - -func TestHandleDownlink(t *testing.T) { - a := New(t) - ns := &networkServer{ - devices: device.NewDeviceStore(), - } - - appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) - devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) - devAddr := getDevAddr(1, 2, 3, 4) - - // Device Not Found - message := &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: []byte{}, - } - _, err := ns.HandleDownlink(message) - a.So(err, ShouldNotBeNil) - - ns.devices.Set(&device.Device{ - DevAddr: devAddr, - AppEUI: appEUI, - DevEUI: devEUI, - }) - - // Invalid Payload - message = &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: []byte{}, - } - _, err = ns.HandleDownlink(message) - a.So(err, ShouldNotBeNil) - - fPort := uint8(3) - phy := lorawan.PHYPayload{ - MHDR: lorawan.MHDR{ - MType: lorawan.UnconfirmedDataDown, - Major: lorawan.LoRaWANR1, - }, - MACPayload: &lorawan.MACPayload{ - FPort: &fPort, - FHDR: lorawan.FHDR{ - FCtrl: lorawan.FCtrl{ - ACK: true, - }, - }, - }, - } - bytes, _ := phy.MarshalBinary() - - message = &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: bytes, - } - res, err := ns.HandleDownlink(message) - a.So(err, ShouldBeNil) - - var phyPayload lorawan.PHYPayload - phyPayload.UnmarshalBinary(res.Payload) - macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) - a.So(*macPayload.FPort, ShouldEqual, 3) - a.So(macPayload.FHDR.DevAddr, ShouldEqual, lorawan.DevAddr{1, 2, 3, 4}) - a.So(macPayload.FHDR.FCnt, ShouldEqual, 0) // The first Frame counter is zero - a.So(phyPayload.MIC, ShouldNotEqual, [4]byte{0, 0, 0, 0}) // MIC should be set, we'll check it with actual examples in the integration test - - dev, _ := ns.devices.Get(appEUI, devEUI) - a.So(dev.FCntDown, ShouldEqual, 1) - -} diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go new file mode 100644 index 000000000..9e748dd64 --- /dev/null +++ b/core/networkserver/uplink.go @@ -0,0 +1,87 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" +) + +func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessage) (*pb_broker.DeduplicatedUplinkMessage, error) { + // Get Device + dev, err := n.devices.Get(*message.AppEui, *message.DevEui) + if err != nil { + return nil, err + } + + // Unmarshal LoRaWAN Payload + var phyPayload lorawan.PHYPayload + err = phyPayload.UnmarshalBinary(message.Payload) + if err != nil { + return nil, err + } + macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) + if !ok { + return nil, errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") + } + + // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) + if lorawan := message.GetProtocolMetadata().GetLorawan(); lorawan != nil { + dev.FCntUp = lorawan.FCnt + } else { + dev.FCntUp = macPayload.FHDR.FCnt + } + dev.LastSeen = time.Now() + err = n.devices.Set(dev, "f_cnt_up", "last_seen") + if err != nil { + return nil, err + } + + // Prepare Downlink + if message.ResponseTemplate == nil { + return message, nil + } + message.ResponseTemplate.AppEui = message.AppEui + message.ResponseTemplate.DevEui = message.DevEui + message.ResponseTemplate.AppId = message.AppId + message.ResponseTemplate.DevId = message.DevId + + // Add Full FCnt (avoiding nil pointer panics) + if option := message.ResponseTemplate.DownlinkOption; option != nil { + if protocol := option.ProtocolConfig; protocol != nil { + if lorawan := protocol.GetLorawan(); lorawan != nil { + lorawan.FCnt = dev.FCntDown + } + } + } + + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataDown, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: macPayload.FHDR.DevAddr, + FCtrl: lorawan.FCtrl{ + ACK: phyPayload.MHDR.MType == lorawan.ConfirmedDataUp, + }, + FCnt: dev.FCntDown, + }, + }, + } + phyBytes, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + + // TODO: Maybe we need to add MAC commands on downlink + + message.ResponseTemplate.Payload = phyBytes + + return message, nil +} diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go new file mode 100644 index 000000000..99bbdac5b --- /dev/null +++ b/core/networkserver/uplink_test.go @@ -0,0 +1,87 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + "time" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/networkserver/device" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestHandleUplink(t *testing.T) { + a := New(t) + ns := &networkServer{ + devices: device.NewDeviceStore(), + } + + appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) + devAddr := getDevAddr(1, 2, 3, 4) + + // Device Not Found + message := &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err := ns.HandleUplink(message) + a.So(err, ShouldNotBeNil) + + ns.devices.Set(&device.Device{ + DevAddr: devAddr, + AppEUI: appEUI, + DevEUI: devEUI, + }) + + // Invalid Payload + message = &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{}, + } + _, err = ns.HandleUplink(message) + a.So(err, ShouldNotBeNil) + + phy := lorawan.PHYPayload{ + MHDR: lorawan.MHDR{ + MType: lorawan.UnconfirmedDataUp, + Major: lorawan.LoRaWANR1, + }, + MACPayload: &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }, + } + bytes, _ := phy.MarshalBinary() + + // Valid Uplink + message = &pb_broker.DeduplicatedUplinkMessage{ + AppEui: &appEUI, + DevEui: &devEUI, + Payload: bytes, + ResponseTemplate: &pb_broker.DownlinkMessage{}, + } + res, err := ns.HandleUplink(message) + a.So(err, ShouldBeNil) + a.So(res.ResponseTemplate, ShouldNotBeNil) + + // LoRaWAN: Unmarshal + var phyPayload lorawan.PHYPayload + phyPayload.UnmarshalBinary(res.ResponseTemplate.Payload) + macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + + a.So([4]byte(macPayload.FHDR.DevAddr), ShouldEqual, [4]byte(devAddr)) + + // Frame Counter should have been updated + dev, _ := ns.devices.Get(appEUI, devEUI) + a.So(dev.FCntUp, ShouldEqual, 1) + a.So(time.Now().Sub(dev.LastSeen), ShouldBeLessThan, 1*time.Second) +} From 74b13439310f36b08e6ea9c6f6319da30766c0d6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 21 Sep 2016 16:32:44 +0200 Subject: [PATCH 1778/2266] Set ACK on AdrAckReq Refs #265 --- core/networkserver/uplink.go | 34 +++++++++++++++++++++---------- core/networkserver/uplink_test.go | 8 ++++++++ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go index 9e748dd64..499d4a7c2 100644 --- a/core/networkserver/uplink.go +++ b/core/networkserver/uplink.go @@ -59,28 +59,40 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } } + mac := &lorawan.MACPayload{ + FHDR: lorawan.FHDR{ + DevAddr: macPayload.FHDR.DevAddr, + FCnt: dev.FCntDown, + }, + } + phy := lorawan.PHYPayload{ MHDR: lorawan.MHDR{ MType: lorawan.UnconfirmedDataDown, Major: lorawan.LoRaWANR1, }, - MACPayload: &lorawan.MACPayload{ - FHDR: lorawan.FHDR{ - DevAddr: macPayload.FHDR.DevAddr, - FCtrl: lorawan.FCtrl{ - ACK: phyPayload.MHDR.MType == lorawan.ConfirmedDataUp, - }, - FCnt: dev.FCntDown, - }, - }, + MACPayload: mac, + } + + // Confirmed Uplink + if phyPayload.MHDR.MType == lorawan.ConfirmedDataUp { + mac.FHDR.FCtrl.ACK = true } + + // Adaptive DataRate + if macPayload.FHDR.FCtrl.ADR { + if macPayload.FHDR.FCtrl.ADRACKReq { + mac.FHDR.FCtrl.ACK = true + } + } + + // TODO: We might need to add MAC commands on downlink + phyBytes, err := phy.MarshalBinary() if err != nil { return nil, err } - // TODO: Maybe we need to add MAC commands on downlink - message.ResponseTemplate.Payload = phyBytes return message, nil diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go index 99bbdac5b..9ada13d8e 100644 --- a/core/networkserver/uplink_test.go +++ b/core/networkserver/uplink_test.go @@ -57,6 +57,10 @@ func TestHandleUplink(t *testing.T) { FHDR: lorawan.FHDR{ DevAddr: lorawan.DevAddr([4]byte{1, 2, 3, 4}), FCnt: 1, + FCtrl: lorawan.FCtrl{ + ADR: true, + ADRACKReq: true, + }, }, }, } @@ -78,8 +82,12 @@ func TestHandleUplink(t *testing.T) { phyPayload.UnmarshalBinary(res.ResponseTemplate.Payload) macPayload, _ := phyPayload.MACPayload.(*lorawan.MACPayload) + // ResponseTemplate DevAddr should match a.So([4]byte(macPayload.FHDR.DevAddr), ShouldEqual, [4]byte(devAddr)) + // ResponseTemplate should ACK the ADRACKReq + a.So(macPayload.FHDR.FCtrl.ACK, ShouldBeTrue) + // Frame Counter should have been updated dev, _ := ns.devices.Get(appEUI, devEUI) a.So(dev.FCntUp, ShouldEqual, 1) From ca89b9de40c598f958e764aa317d688933c87f2a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 20 Sep 2016 10:31:19 +0200 Subject: [PATCH 1779/2266] Remove EUI and rename Create -> Register Gateway --- core/account/gateways.go | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/core/account/gateways.go b/core/account/gateways.go index 6aef32b17..363162b70 100644 --- a/core/account/gateways.go +++ b/core/account/gateways.go @@ -22,33 +22,34 @@ func (a *Account) FindGateway(gatewayID string) (gateway Gateway, err error) { return gateway, err } -// NewGateway is used as a paramater to CreateGateway to allow for optional -// arguments -type NewGateway struct { +type registerGatewayReq struct { // ID is the ID of the new gateway (required) ID string `json:"id"` // Country is the country code of the new gateway (required) FrequencyPlan string `json:"frequency_plan"` - // EUI is the EUI of the new gateway - EUI string `json:"eui,omitemtpy"` - // Location is the location of the new gateway Location *Location `json:"location,omitempty"` } -// CreateGateway registers a new gateway on the account server -func (a *Account) CreateGateway(opts *NewGateway) (gateway Gateway, err error) { - if opts.ID == "" { +// RegisterGateway registers a new gateway on the account server +func (a *Account) RegisterGateway(gatewayID string, frequencyPlan string, location *Location) (gateway Gateway, err error) { + if gatewayID == "" { return gateway, errors.New("Cannot create gateway: no ID given") } - if opts.FrequencyPlan == "" { + if frequencyPlan == "" { return gateway, errors.New("Cannot create gateway: no FrequencyPlan given") } - err = a.post("/gateways", &opts, &gateway) + req := registerGatewayReq{ + ID: gatewayID, + FrequencyPlan: frequencyPlan, + Location: location, + } + + err = a.post("/gateways", req, &gateway) return gateway, err } From f897d5ee27b15c288f14247e6b058338f19e3db8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 20 Sep 2016 11:08:50 +0200 Subject: [PATCH 1780/2266] Add ttnctl gateway register cmd --- ttnctl/cmd/gateways_register.go | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 ttnctl/cmd/gateways_register.go diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go new file mode 100644 index 000000000..8731ed35f --- /dev/null +++ b/ttnctl/cmd/gateways_register.go @@ -0,0 +1,38 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewayRegisterCmd = &cobra.Command{ + Use: "register [GatewayID] [FrequencyPlan]", + Short: "Register a gateway", + Long: `ttnctl gateway register can be used to register a gateway`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + frequencyPlan := args[1] + + act := util.GetAccount(ctx) + gateway, err := act.RegisterGateway(gatewayID, frequencyPlan, nil) + if err != nil { + ctx.WithError(err).Fatal("Could not register gateway") + } + + util.ForceRefreshToken(ctx) + + ctx.WithField("Gateway ID", gateway.ID).Info("Registered Gateway") + }, +} + +func init() { + gatewayCmd.AddCommand(gatewayRegisterCmd) +} From 77289ffa04b575642d0d4971684a704e789e2808 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 20 Sep 2016 11:11:23 +0200 Subject: [PATCH 1781/2266] ttnctl gateway -> ttnctl gateways (consistent with ttnctl applications) --- ttnctl/cmd/{gateway.go => gateways.go} | 8 ++++---- ttnctl/cmd/gateways_register.go | 6 +++--- ttnctl/cmd/{gateway_status.go => gateways_status.go} | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) rename ttnctl/cmd/{gateway.go => gateways.go} (69%) rename ttnctl/cmd/{gateway_status.go => gateways_status.go} (91%) diff --git a/ttnctl/cmd/gateway.go b/ttnctl/cmd/gateways.go similarity index 69% rename from ttnctl/cmd/gateway.go rename to ttnctl/cmd/gateways.go index c5228d46b..de5df8c98 100644 --- a/ttnctl/cmd/gateway.go +++ b/ttnctl/cmd/gateways.go @@ -5,15 +5,15 @@ package cmd import "github.com/spf13/cobra" -var gatewayCmd = &cobra.Command{ - Use: "gateway", +var gatewaysCmd = &cobra.Command{ + Use: "gateways", Short: "Manage gateways", - Long: `ttnctl applications can be used to manage gateways.`, + Long: `ttnctl gateways can be used to manage gateways.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) }, } func init() { - RootCmd.AddCommand(gatewayCmd) + RootCmd.AddCommand(gatewaysCmd) } diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index 8731ed35f..e998f2c76 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -8,10 +8,10 @@ import ( "github.com/spf13/cobra" ) -var gatewayRegisterCmd = &cobra.Command{ +var gatewaysRegisterCmd = &cobra.Command{ Use: "register [GatewayID] [FrequencyPlan]", Short: "Register a gateway", - Long: `ttnctl gateway register can be used to register a gateway`, + Long: `ttnctl gateways register can be used to register a gateway`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { cmd.UsageFunc()(cmd) @@ -34,5 +34,5 @@ var gatewayRegisterCmd = &cobra.Command{ } func init() { - gatewayCmd.AddCommand(gatewayRegisterCmd) + gatewaysCmd.AddCommand(gatewaysRegisterCmd) } diff --git a/ttnctl/cmd/gateway_status.go b/ttnctl/cmd/gateways_status.go similarity index 91% rename from ttnctl/cmd/gateway_status.go rename to ttnctl/cmd/gateways_status.go index e081840a0..cfc0e03e4 100644 --- a/ttnctl/cmd/gateway_status.go +++ b/ttnctl/cmd/gateways_status.go @@ -14,11 +14,11 @@ import ( "github.com/spf13/cobra" ) -var gatewayStatusCmd = &cobra.Command{ +var gatewaysStatusCmd = &cobra.Command{ Use: "status [gatewayID]", Short: "Get status of a gateway", - Long: `ttnctl gateway status can be used to get status of gateways.`, - Example: `$ ttnctl gateway status eui-0000024b08060030 + Long: `ttnctl gateways status can be used to get status of gateways.`, + Example: `$ ttnctl gateways status eui-0000024b08060030 INFO Discovering Router... INFO Connecting with Router... INFO Connected to Router @@ -85,5 +85,5 @@ var gatewayStatusCmd = &cobra.Command{ } func init() { - gatewayCmd.AddCommand(gatewayStatusCmd) + gatewaysCmd.AddCommand(gatewaysStatusCmd) } From aa5e8fb124eb1492db13fced1c4bc8b32c7d21ed Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 20 Sep 2016 17:33:47 +0200 Subject: [PATCH 1782/2266] fixup! Add ttnctl gateway register cmd --- ttnctl/cmd/gateways_register.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index e998f2c76..88bb42a2e 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -19,6 +19,10 @@ var gatewaysRegisterCmd = &cobra.Command{ } gatewayID := args[0] + if !validID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + frequencyPlan := args[1] act := util.GetAccount(ctx) From 29a06ce1303df313b1c9e7aa52f8961d86ead57d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 20 Sep 2016 17:34:01 +0200 Subject: [PATCH 1783/2266] Add gateway list cmd --- ttnctl/cmd/gateways_list.go | 46 +++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 ttnctl/cmd/gateways_list.go diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go new file mode 100644 index 000000000..f54c5e763 --- /dev/null +++ b/ttnctl/cmd/gateways_list.go @@ -0,0 +1,46 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +var gatewaysListCmd = &cobra.Command{ + Use: "list", + Short: "List your gateways", + Long: `ttnctl gateways list can be used to list the gateways you have access to`, + Run: func(cmd *cobra.Command, args []string) { + act := util.GetAccount(ctx) + gateways, err := act.ListGateways() + if err != nil { + ctx.WithError(err).Fatal("Could not list gateways") + } + + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "ID", "Activated", "Frequency Plan", "Lat", "Lng") + for i, gateway := range gateways { + var lat float64 + var lng float64 + if gateway.Location != nil { + lat = gateway.Location.Latitude + lng = gateway.Location.Longitude + } + table.AddRow(i+1, gateway.ID, gateway.Activated, gateway.FrequencyPlan, lat, lng) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysListCmd) +} From 37ed701a3f8be6e0e42bc42b65db6300308e9104 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 21 Sep 2016 09:46:29 +0200 Subject: [PATCH 1784/2266] fixup! Add ttnctl gateway register cmd --- ttnctl/cmd/gateways_register.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index 88bb42a2e..621cd1bb8 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -4,6 +4,7 @@ package cmd import ( + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) @@ -19,7 +20,7 @@ var gatewaysRegisterCmd = &cobra.Command{ } gatewayID := args[0] - if !validID(gatewayID) { + if !api.ValidID(gatewayID) { ctx.Fatal("Invalid Gateway ID") } From 831c4eda754ae2c16f26a3d256f92fdb1552ea6a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 21 Sep 2016 16:37:36 +0200 Subject: [PATCH 1785/2266] Add ttnctl gateways delete command --- ttnctl/cmd/gateways_delete.go | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 ttnctl/cmd/gateways_delete.go diff --git a/ttnctl/cmd/gateways_delete.go b/ttnctl/cmd/gateways_delete.go new file mode 100644 index 000000000..0d8a3b2c4 --- /dev/null +++ b/ttnctl/cmd/gateways_delete.go @@ -0,0 +1,39 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysDeleteCmd = &cobra.Command{ + Use: "delete [GatewayID]", + Short: "Delete a gateway", + Long: `ttnctl gateways deletw can be used to delete a gateway`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + act := util.GetAccount(ctx) + err := act.DeleteGateway(gatewayID) + if err != nil { + ctx.WithError(err).Fatal("Could not list gateways") + } + + ctx.WithField("Gateway ID", gatewayID).Info("Deleted gateway") + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysDeleteCmd) +} From 99134e1d8e6a356527b3832075706d4fbd951fbe Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 21 Sep 2016 17:07:27 +0200 Subject: [PATCH 1786/2266] Add location parser and add ability to set location on gateways register --- ttnctl/cmd/gateways_register.go | 16 ++++++++++--- ttnctl/util/location.go | 42 +++++++++++++++++++++++++++++++++ ttnctl/util/location_test.go | 25 ++++++++++++++++++++ 3 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 ttnctl/util/location.go create mode 100644 ttnctl/util/location_test.go diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index 621cd1bb8..10a10ad29 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -5,16 +5,17 @@ package cmd import ( "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/account" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) var gatewaysRegisterCmd = &cobra.Command{ - Use: "register [GatewayID] [FrequencyPlan]", + Use: "register [GatewayID] [FrequencyPlan] [Location]", Short: "Register a gateway", Long: `ttnctl gateways register can be used to register a gateway`, Run: func(cmd *cobra.Command, args []string) { - if len(args) != 2 { + if len(args) != 2 && len(args) != 3 { cmd.UsageFunc()(cmd) return } @@ -26,8 +27,17 @@ var gatewaysRegisterCmd = &cobra.Command{ frequencyPlan := args[1] + var err error + var location *account.Location + if len(args) == 3 { + location, err = util.ParseLocation(args[2]) + if err != nil { + ctx.WithError(err).Fatal("Invalid location") + } + } + act := util.GetAccount(ctx) - gateway, err := act.RegisterGateway(gatewayID, frequencyPlan, nil) + gateway, err := act.RegisterGateway(gatewayID, frequencyPlan, location) if err != nil { ctx.WithError(err).Fatal("Could not register gateway") } diff --git a/ttnctl/util/location.go b/ttnctl/util/location.go new file mode 100644 index 000000000..1286a6d18 --- /dev/null +++ b/ttnctl/util/location.go @@ -0,0 +1,42 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "errors" + "strconv" + "strings" + + "github.com/TheThingsNetwork/ttn/core/account" +) + +func ParseLocation(locationStr string) (*account.Location, error) { + parts := strings.Split(locationStr, ":") + if len(parts) != 2 { + return nil, errors.New("Location should be on the : format") + } + + lat, err := strconv.ParseFloat(parts[0], 64) + if err != nil { + return nil, err + } + + if lat < 0 || lat > 90 { + return nil, errors.New("Latitude should be in range [0, 90]") + } + + lng, err := strconv.ParseFloat(parts[1], 64) + if err != nil { + return nil, err + } + + if lng < 0 || lng > 180 { + return nil, errors.New("Longitude should be in range [0, 180]") + } + + return &account.Location{ + Latitude: lat, + Longitude: lng, + }, nil +} diff --git a/ttnctl/util/location_test.go b/ttnctl/util/location_test.go new file mode 100644 index 000000000..2b71bf28c --- /dev/null +++ b/ttnctl/util/location_test.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/account" + s "github.com/smartystreets/assertions" +) + +func TestParseLocation(t *testing.T) { + a := s.New(t) + + str := "10.5:33.4" + loc := &account.Location{ + Latitude: float64(10.5), + Longitude: float64(33.4), + } + parsed, err := ParseLocation(str) + a.So(err, s.ShouldBeNil) + a.So(loc.Latitude, s.ShouldEqual, parsed.Latitude) + a.So(loc.Longitude, s.ShouldEqual, parsed.Longitude) +} From a7608a43c1d5204182539b3ca43dc3c20ead696f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 21 Sep 2016 17:07:38 +0200 Subject: [PATCH 1787/2266] Add gateways edit cmd --- ttnctl/cmd/gateways_edit.go | 66 +++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 ttnctl/cmd/gateways_edit.go diff --git a/ttnctl/cmd/gateways_edit.go b/ttnctl/cmd/gateways_edit.go new file mode 100644 index 000000000..72be6322f --- /dev/null +++ b/ttnctl/cmd/gateways_edit.go @@ -0,0 +1,66 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/core/account" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysEditCmd = &cobra.Command{ + Use: "edit [GatewayID]", + Short: "edit a gateway", + Long: `ttnctl gateways edit can be used to edit settings of a gateway`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + var edits account.GatewayEdits + + frequencyPlan, err := cmd.Flags().GetString("frequency-plan") + if err != nil { + ctx.WithError(err).Fatal("Invalid frequency-plan") + } + + if frequencyPlan != "" { + edits.FrequencyPlan = frequencyPlan + } + + locationStr, err := cmd.Flags().GetString("location") + if err != nil { + ctx.WithError(err).Fatal("Invalid location") + } + + if locationStr != "" { + location, err := util.ParseLocation(locationStr) + if err != nil { + ctx.WithError(err).Fatal("Invalid location") + } + edits.Location = location + } + + act := util.GetAccount(ctx) + _, err = act.EditGateway(gatewayID, edits) + if err != nil { + ctx.WithError(err).Fatal("Failure editing gateway") + } + + ctx.WithField("Gateway ID", gatewayID).Info("Edited gateway") + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysEditCmd) + gatewaysEditCmd.Flags().String("frequency-plan", "", "The frequency plan to use on the gateway") + gatewaysEditCmd.Flags().String("location", "", "The location of the gateway") +} From 94e993d44702e7b898a07e42d40939fc0d968969 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 22 Sep 2016 10:23:08 +0200 Subject: [PATCH 1788/2266] Add examples to ttnctl gateways --- ttnctl/cmd/gateways_delete.go | 5 ++++- ttnctl/cmd/gateways_edit.go | 3 +++ ttnctl/cmd/gateways_list.go | 4 ++++ ttnctl/cmd/gateways_register.go | 3 +++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/gateways_delete.go b/ttnctl/cmd/gateways_delete.go index 0d8a3b2c4..8a686f352 100644 --- a/ttnctl/cmd/gateways_delete.go +++ b/ttnctl/cmd/gateways_delete.go @@ -12,7 +12,10 @@ import ( var gatewaysDeleteCmd = &cobra.Command{ Use: "delete [GatewayID]", Short: "Delete a gateway", - Long: `ttnctl gateways deletw can be used to delete a gateway`, + Long: `ttnctl gateways delete can be used to delete a gateway`, + Example: `$ ttnctl gateways delete test + INFO Deleted gateway Gateway ID=test +`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { cmd.UsageFunc()(cmd) diff --git a/ttnctl/cmd/gateways_edit.go b/ttnctl/cmd/gateways_edit.go index 72be6322f..aa87dc5d9 100644 --- a/ttnctl/cmd/gateways_edit.go +++ b/ttnctl/cmd/gateways_edit.go @@ -14,6 +14,9 @@ var gatewaysEditCmd = &cobra.Command{ Use: "edit [GatewayID]", Short: "edit a gateway", Long: `ttnctl gateways edit can be used to edit settings of a gateway`, + Example: `$ ttnctl gateways edit test --location 52.37403:4.88968 --frequency-plan EU + INFO Edited gateway Gateway ID=test +`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { cmd.UsageFunc()(cmd) diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go index f54c5e763..99b3a6a71 100644 --- a/ttnctl/cmd/gateways_list.go +++ b/ttnctl/cmd/gateways_list.go @@ -15,6 +15,10 @@ var gatewaysListCmd = &cobra.Command{ Use: "list", Short: "List your gateways", Long: `ttnctl gateways list can be used to list the gateways you have access to`, + Example: `$ ttnctl gateways list + ID Activated Frequency Plan Lat Lng +1 test true US 52.3740 4.8896 +`, Run: func(cmd *cobra.Command, args []string) { act := util.GetAccount(ctx) gateways, err := act.ListGateways() diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index 10a10ad29..785025009 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -14,6 +14,9 @@ var gatewaysRegisterCmd = &cobra.Command{ Use: "register [GatewayID] [FrequencyPlan] [Location]", Short: "Register a gateway", Long: `ttnctl gateways register can be used to register a gateway`, + Example: `$ ttnctl gateways register test US 52.37403:4.88968 + INFO Registered gateway Gateway ID=test +`, Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 && len(args) != 3 { cmd.UsageFunc()(cmd) From eef9a7b7782538a3e93658a37fb0bd33f2b63c4f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 22 Sep 2016 10:34:36 +0200 Subject: [PATCH 1789/2266] lat:lng -> lat,lng format --- ttnctl/cmd/gateways_edit.go | 2 +- ttnctl/cmd/gateways_register.go | 2 +- ttnctl/util/location.go | 4 ++-- ttnctl/util/location_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/gateways_edit.go b/ttnctl/cmd/gateways_edit.go index aa87dc5d9..e4cfdef24 100644 --- a/ttnctl/cmd/gateways_edit.go +++ b/ttnctl/cmd/gateways_edit.go @@ -14,7 +14,7 @@ var gatewaysEditCmd = &cobra.Command{ Use: "edit [GatewayID]", Short: "edit a gateway", Long: `ttnctl gateways edit can be used to edit settings of a gateway`, - Example: `$ ttnctl gateways edit test --location 52.37403:4.88968 --frequency-plan EU + Example: `$ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU INFO Edited gateway Gateway ID=test `, Run: func(cmd *cobra.Command, args []string) { diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index 785025009..6af483d60 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -14,7 +14,7 @@ var gatewaysRegisterCmd = &cobra.Command{ Use: "register [GatewayID] [FrequencyPlan] [Location]", Short: "Register a gateway", Long: `ttnctl gateways register can be used to register a gateway`, - Example: `$ ttnctl gateways register test US 52.37403:4.88968 + Example: `$ ttnctl gateways register test US 52.37403,4.88968 INFO Registered gateway Gateway ID=test `, Run: func(cmd *cobra.Command, args []string) { diff --git a/ttnctl/util/location.go b/ttnctl/util/location.go index 1286a6d18..e2d9323b8 100644 --- a/ttnctl/util/location.go +++ b/ttnctl/util/location.go @@ -12,9 +12,9 @@ import ( ) func ParseLocation(locationStr string) (*account.Location, error) { - parts := strings.Split(locationStr, ":") + parts := strings.Split(locationStr, ",") if len(parts) != 2 { - return nil, errors.New("Location should be on the : format") + return nil, errors.New("Location should be on the , format") } lat, err := strconv.ParseFloat(parts[0], 64) diff --git a/ttnctl/util/location_test.go b/ttnctl/util/location_test.go index 2b71bf28c..f021a9239 100644 --- a/ttnctl/util/location_test.go +++ b/ttnctl/util/location_test.go @@ -13,7 +13,7 @@ import ( func TestParseLocation(t *testing.T) { a := s.New(t) - str := "10.5:33.4" + str := "10.5,33.4" loc := &account.Location{ Latitude: float64(10.5), Longitude: float64(33.4), From 1558d94ae85877103149b77992184f4c6443f276 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 22 Sep 2016 10:35:09 +0200 Subject: [PATCH 1790/2266] act -> account --- ttnctl/cmd/gateways_delete.go | 4 ++-- ttnctl/cmd/gateways_list.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/gateways_delete.go b/ttnctl/cmd/gateways_delete.go index 8a686f352..48d1c80ba 100644 --- a/ttnctl/cmd/gateways_delete.go +++ b/ttnctl/cmd/gateways_delete.go @@ -27,8 +27,8 @@ var gatewaysDeleteCmd = &cobra.Command{ ctx.Fatal("Invalid Gateway ID") } - act := util.GetAccount(ctx) - err := act.DeleteGateway(gatewayID) + account := util.GetAccount(ctx) + err := account.DeleteGateway(gatewayID) if err != nil { ctx.WithError(err).Fatal("Could not list gateways") } diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go index 99b3a6a71..a93ea7887 100644 --- a/ttnctl/cmd/gateways_list.go +++ b/ttnctl/cmd/gateways_list.go @@ -20,8 +20,8 @@ var gatewaysListCmd = &cobra.Command{ 1 test true US 52.3740 4.8896 `, Run: func(cmd *cobra.Command, args []string) { - act := util.GetAccount(ctx) - gateways, err := act.ListGateways() + accountt := util.GetAccount(ctx) + gateways, err := account.ListGateways() if err != nil { ctx.WithError(err).Fatal("Could not list gateways") } From 49f314ff439fca58f573c918b9e861acaf00b7c2 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 22 Sep 2016 10:37:07 +0200 Subject: [PATCH 1791/2266] Make gateway ids in examples consistent --- ttnctl/cmd/gateways_status.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/gateways_status.go b/ttnctl/cmd/gateways_status.go index cfc0e03e4..f3913b3bf 100644 --- a/ttnctl/cmd/gateways_status.go +++ b/ttnctl/cmd/gateways_status.go @@ -18,7 +18,7 @@ var gatewaysStatusCmd = &cobra.Command{ Use: "status [gatewayID]", Short: "Get status of a gateway", Long: `ttnctl gateways status can be used to get status of gateways.`, - Example: `$ ttnctl gateways status eui-0000024b08060030 + Example: `$ ttnctl gateways status test INFO Discovering Router... INFO Connecting with Router... INFO Connected to Router From 374382d6c5ed8778f8e4ad7d2c1d49c18a13ffd2 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 22 Sep 2016 10:38:42 +0200 Subject: [PATCH 1792/2266] Correct latittude longitude ranges --- ttnctl/util/location.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/util/location.go b/ttnctl/util/location.go index e2d9323b8..1cff56ed8 100644 --- a/ttnctl/util/location.go +++ b/ttnctl/util/location.go @@ -22,8 +22,8 @@ func ParseLocation(locationStr string) (*account.Location, error) { return nil, err } - if lat < 0 || lat > 90 { - return nil, errors.New("Latitude should be in range [0, 90]") + if lat < -90 || lat > 90 { + return nil, errors.New("Latitude should be in range [90, 90]") } lng, err := strconv.ParseFloat(parts[1], 64) @@ -31,8 +31,8 @@ func ParseLocation(locationStr string) (*account.Location, error) { return nil, err } - if lng < 0 || lng > 180 { - return nil, errors.New("Longitude should be in range [0, 180]") + if lng < -180 || lng > 180 { + return nil, errors.New("Longitude should be in range [-180, 180]") } return &account.Location{ From 8dd0cc0bfab3d5432f5de21cab22258af155a11a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 22 Sep 2016 10:40:15 +0200 Subject: [PATCH 1793/2266] List coordinates as one column --- ttnctl/cmd/gateways_list.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go index a93ea7887..3f628e9ee 100644 --- a/ttnctl/cmd/gateways_list.go +++ b/ttnctl/cmd/gateways_list.go @@ -16,8 +16,8 @@ var gatewaysListCmd = &cobra.Command{ Short: "List your gateways", Long: `ttnctl gateways list can be used to list the gateways you have access to`, Example: `$ ttnctl gateways list - ID Activated Frequency Plan Lat Lng -1 test true US 52.3740 4.8896 + ID Activated Frequency Plan Coordinates +1 test true US (52.3740, 4.8896) `, Run: func(cmd *cobra.Command, args []string) { accountt := util.GetAccount(ctx) @@ -28,7 +28,7 @@ var gatewaysListCmd = &cobra.Command{ table := uitable.New() table.MaxColWidth = 70 - table.AddRow("", "ID", "Activated", "Frequency Plan", "Lat", "Lng") + table.AddRow("", "ID", "Activated", "Frequency Plan", "Coordinates") for i, gateway := range gateways { var lat float64 var lng float64 @@ -36,7 +36,7 @@ var gatewaysListCmd = &cobra.Command{ lat = gateway.Location.Latitude lng = gateway.Location.Longitude } - table.AddRow(i+1, gateway.ID, gateway.Activated, gateway.FrequencyPlan, lat, lng) + table.AddRow(i+1, gateway.ID, gateway.Activated, gateway.FrequencyPlan, fmt.Sprintf("(%f, %f)", lat, lng)) } fmt.Println() From 88e872ad17a77b320401d551f18cba590e4c2a8e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 11:04:17 +0200 Subject: [PATCH 1794/2266] Rename applications/devices create command to add --- ...ications_create.go => applications_add.go} | 25 +++++++++---------- ...{devices_create.go => devices_register.go} | 19 +++++++------- 2 files changed, 21 insertions(+), 23 deletions(-) rename ttnctl/cmd/{applications_create.go => applications_add.go} (60%) rename ttnctl/cmd/{devices_create.go => devices_register.go} (81%) diff --git a/ttnctl/cmd/applications_create.go b/ttnctl/cmd/applications_add.go similarity index 60% rename from ttnctl/cmd/applications_create.go rename to ttnctl/cmd/applications_add.go index 369f32227..80caa05cb 100644 --- a/ttnctl/cmd/applications_create.go +++ b/ttnctl/cmd/applications_add.go @@ -9,13 +9,12 @@ import ( "github.com/spf13/cobra" ) -// applicationsCreateCmd is the entrypoint for handlerctl -var applicationsCreateCmd = &cobra.Command{ - Use: "create [AppID] [Description]", - Short: "Create a new application", - Long: `ttnctl applications create can be used to create a new application.`, - Example: `$ ttnctl applications create test "Test application" - INFO Created Application +var applicationsAddCmd = &cobra.Command{ + Use: "add [AppID] [Description]", + Short: "Add a new application", + Long: `ttnctl applications add can be used to add a new application to your account.`, + Example: `$ ttnctl applications add test "Test application" + INFO Added Application INFO Selected Current Application `, Run: func(cmd *cobra.Command, args []string) { @@ -41,12 +40,12 @@ var applicationsCreateCmd = &cobra.Command{ app, err := account.CreateApplication(args[0], args[1], euis) if err != nil { - ctx.WithError(err).Fatal("Could not create application") + ctx.WithError(err).Fatal("Could not add application") } util.ForceRefreshToken(ctx) - ctx.Info("Created Application") + ctx.Info("Added Application") skipSelect, _ := cmd.Flags().GetBool("skip-select") if !skipSelect { @@ -65,8 +64,8 @@ var applicationsCreateCmd = &cobra.Command{ } func init() { - applicationsCmd.AddCommand(applicationsCreateCmd) - applicationsCreateCmd.Flags().StringSlice("app-eui", []string{}, "LoRaWAN AppEUI to register with application") - applicationsCreateCmd.Flags().Bool("skip-select", false, "Do not select this application (also adds --skip-register)") - applicationsCreateCmd.Flags().Bool("skip-register", false, "Do not register application with the Handler") + applicationsCmd.AddCommand(applicationsAddCmd) + applicationsAddCmd.Flags().StringSlice("app-eui", []string{}, "LoRaWAN AppEUI to register with application") + applicationsAddCmd.Flags().Bool("skip-select", false, "Do not select this application (also adds --skip-register)") + applicationsAddCmd.Flags().Bool("skip-register", false, "Do not register application with the Handler") } diff --git a/ttnctl/cmd/devices_create.go b/ttnctl/cmd/devices_register.go similarity index 81% rename from ttnctl/cmd/devices_create.go rename to ttnctl/cmd/devices_register.go index 6a96bac49..e821ce878 100644 --- a/ttnctl/cmd/devices_create.go +++ b/ttnctl/cmd/devices_register.go @@ -14,18 +14,17 @@ import ( "github.com/spf13/cobra" ) -// devicesCreateCmd represents the `device create` command -var devicesCreateCmd = &cobra.Command{ - Use: "create [Device ID] [DevEUI] [AppKey]", - Short: "Create a new device", - Long: `ttnctl devices create can be used to create a new device.`, - Example: `$ ttnctl devices create test +var devicesRegisterCmd = &cobra.Command{ + Use: "register [Device ID] [DevEUI] [AppKey]", + Short: "Register a new device", + Long: `ttnctl devices register can be used to register a new device.`, + Example: `$ ttnctl devices register test INFO Using Application AppEUI=70B3D57EF0000024 AppID=test INFO Generating random DevEUI... INFO Generating random AppKey... INFO Discovering Handler... INFO Connecting with Handler... - INFO Created device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test + INFO Registered device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test `, Run: func(cmd *cobra.Command, args []string) { @@ -80,7 +79,7 @@ var devicesCreateCmd = &cobra.Command{ }}, }) if err != nil { - ctx.WithError(err).Fatal("Could not create Device") + ctx.WithError(err).Fatal("Could not register Device") } ctx.WithFields(log.Fields{ @@ -89,10 +88,10 @@ var devicesCreateCmd = &cobra.Command{ "AppEUI": appEUI, "DevEUI": devEUI, "AppKey": appKey, - }).Info("Created device") + }).Info("Registered device") }, } func init() { - devicesCmd.AddCommand(devicesCreateCmd) + devicesCmd.AddCommand(devicesRegisterCmd) } From 1fde81b5411d23f8a02ad1f924e7ce36ad1fe2c9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 11:04:53 +0200 Subject: [PATCH 1795/2266] Cleanup ttnctl --- ttnctl/cmd/applications.go | 1 - ttnctl/cmd/applications_delete.go | 1 - ttnctl/cmd/applications_list.go | 1 - ttnctl/cmd/applications_pf.go | 1 - ttnctl/cmd/applications_register.go | 1 - ttnctl/cmd/applications_select.go | 1 - ttnctl/cmd/applications_unregister.go | 1 - ttnctl/cmd/devices.go | 1 - ttnctl/cmd/devices_delete.go | 1 - ttnctl/cmd/devices_info.go | 1 - ttnctl/cmd/devices_list.go | 1 - ttnctl/cmd/devices_personalize.go | 1 - ttnctl/cmd/devices_set.go | 1 - ttnctl/cmd/gateways_list.go | 2 +- ttnctl/cmd/root.go | 2 +- ttnctl/cmd/subscribe.go | 1 - ttnctl/cmd/uplink.go | 1 - ttnctl/cmd/user.go | 1 - ttnctl/cmd/user_login.go | 10 +++++----- ttnctl/cmd/version.go | 1 - ttnctl/util/account.go | 4 ++-- 21 files changed, 9 insertions(+), 26 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 9e9e58790..512e08f78 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -5,7 +5,6 @@ package cmd import "github.com/spf13/cobra" -// applicationsCmd is the entrypoint for handlerctl var applicationsCmd = &cobra.Command{ Use: "applications", Short: "Manage applications", diff --git a/ttnctl/cmd/applications_delete.go b/ttnctl/cmd/applications_delete.go index b53a28313..0a1752c8d 100644 --- a/ttnctl/cmd/applications_delete.go +++ b/ttnctl/cmd/applications_delete.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/cobra" ) -// applicationsDeleteCmd represents the deletes command var applicationsDeleteCmd = &cobra.Command{ Use: "delete", Short: "Delete an application", diff --git a/ttnctl/cmd/applications_list.go b/ttnctl/cmd/applications_list.go index 25ba21363..386d0630b 100644 --- a/ttnctl/cmd/applications_list.go +++ b/ttnctl/cmd/applications_list.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" ) -// applicationsListCmd is the entrypoint for handlerctl var applicationsListCmd = &cobra.Command{ Use: "list", Short: "List applications", diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index ddb1079b0..4fe2ee3ef 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/cobra" ) -// applicationsPayloadFunctionsCmd represents the applicationsPayloadFunctions command var applicationsPayloadFunctionsCmd = &cobra.Command{ Use: "pf", Short: "Show the payload functions", diff --git a/ttnctl/cmd/applications_register.go b/ttnctl/cmd/applications_register.go index acd5f4fed..6e61062cb 100644 --- a/ttnctl/cmd/applications_register.go +++ b/ttnctl/cmd/applications_register.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/cobra" ) -// applicationsRegisterCmd represents the `applications register` command var applicationsRegisterCmd = &cobra.Command{ Use: "register", Short: "Register this application with the handler", diff --git a/ttnctl/cmd/applications_select.go b/ttnctl/cmd/applications_select.go index 3bf573863..496ca9a7b 100644 --- a/ttnctl/cmd/applications_select.go +++ b/ttnctl/cmd/applications_select.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" ) -// applicationsSelectCmd is the entrypoint for handlerctl var applicationsSelectCmd = &cobra.Command{ Use: "select", Short: "select the application to use", diff --git a/ttnctl/cmd/applications_unregister.go b/ttnctl/cmd/applications_unregister.go index d56a26f19..016a0737e 100644 --- a/ttnctl/cmd/applications_unregister.go +++ b/ttnctl/cmd/applications_unregister.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" ) -// applicationsUnregisterCmd represents the `applications unregister` command var applicationsUnregisterCmd = &cobra.Command{ Use: "unregister", Short: "Unregister this application from the handler", diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go index 060f4b4fd..eee6c496e 100644 --- a/ttnctl/cmd/devices.go +++ b/ttnctl/cmd/devices.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/viper" ) -// devicesCmd is the entrypoint for handlerctl var devicesCmd = &cobra.Command{ Use: "devices", Short: "Manage devices", diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go index 50204e4f1..a99df34fa 100644 --- a/ttnctl/cmd/devices_delete.go +++ b/ttnctl/cmd/devices_delete.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" ) -// devicesDeleteCmd represents the `device delete` command var devicesDeleteCmd = &cobra.Command{ Use: "delete [Device ID]", Short: "Delete a device", diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index da53a2957..a2eac3824 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -13,7 +13,6 @@ import ( "github.com/spf13/cobra" ) -// devicesInfoCmd represents the `device info` command var devicesInfoCmd = &cobra.Command{ Use: "info [Device ID]", Short: "Get information about a device", diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index 8d46d6d06..575740d8f 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -12,7 +12,6 @@ import ( "github.com/spf13/cobra" ) -// devicesListCmd represents the `device list` command var devicesListCmd = &cobra.Command{ Use: "list", Short: "List al devices for the current application", diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index 0b062e57e..e9259aa29 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -14,7 +14,6 @@ import ( "github.com/spf13/cobra" ) -// devicesPersonalizeCmd represents the `device personalize` command var devicesPersonalizeCmd = &cobra.Command{ Use: "personalize [Device ID] [NwkSKey] [AppSKey]", Short: "Personalize a device", diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index f0c59c87a..7f433e27e 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -11,7 +11,6 @@ import ( "github.com/spf13/cobra" ) -// devicesSetCmd represents the `device set` command var devicesSetCmd = &cobra.Command{ Use: "set [Device ID]", Short: "Set properties of a device", diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go index 3f628e9ee..5bf0a0b33 100644 --- a/ttnctl/cmd/gateways_list.go +++ b/ttnctl/cmd/gateways_list.go @@ -20,7 +20,7 @@ var gatewaysListCmd = &cobra.Command{ 1 test true US (52.3740, 4.8896) `, Run: func(cmd *cobra.Command, args []string) { - accountt := util.GetAccount(ctx) + account := util.GetAccount(ctx) gateways, err := account.ListGateways() if err != nil { ctx.WithError(err).Fatal("Could not list gateways") diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index bbacdfd22..b7c11955a 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -24,7 +24,7 @@ var cfgFile string var ctx log.Interface -// RootCmd is the entrypoint for handlerctl +// RootCmd is the entrypoint for ttnctl var RootCmd = &cobra.Command{ Use: "ttnctl", Short: "Control The Things Network from the command line", diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 1e11c6604..34d51f94a 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -14,7 +14,6 @@ import ( "github.com/spf13/cobra" ) -// subscribeCmd represents the `subscribe` command var subscribeCmd = &cobra.Command{ Use: "subscribe", Short: "Subscribe to events for this application", diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index d347cb226..22d1e9c10 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -17,7 +17,6 @@ import ( "github.com/spf13/cobra" ) -// uplinkCmd represents the `uplink` command var uplinkCmd = &cobra.Command{ Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload]", Short: "Simulate an uplink message to the network", diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 1d9903fdc..ca30313e8 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -14,7 +14,6 @@ import ( "github.com/spf13/cobra" ) -// userCmd represents the users command var userCmd = &cobra.Command{ Use: "user", Short: "Show the current user", diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index 044858b1f..59b85e4b0 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -13,13 +13,13 @@ import ( ) var userLoginCmd = &cobra.Command{ - Use: "login [client code]", - Short: "Login", - Long: `ttnctl user login allows you to login to the account server.`, - Example: `First get an access code from your TTN Profile by going to + Use: "login [access code]", + Short: "Log in with your TTN account", + Long: `ttnctl user login allows you to log in to your TTN account.`, + Example: `First get an access code from your TTN profile by going to https://account.thethingsnetwork.org and clicking "ttnctl access code". -$ ttnctl user login 2keK3FTu6e0327cq4ni0wRTMT2mTS-m_FLzFBlNQadwa +$ ttnctl user login [paste the access code you requested above] INFO Successfully logged in as yourname (your@email.org) `, Run: func(cmd *cobra.Command, args []string) { diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index 4081fc43c..48e3ef852 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -9,7 +9,6 @@ import ( "github.com/spf13/viper" ) -// versionCmd represents the version command var versionCmd = &cobra.Command{ Use: "version", Short: "Get build and version information", diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index ddcd4630b..245878c57 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -40,12 +40,12 @@ func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { func getStoredToken(ctx log.Interface) *oauth2.Token { tokenString := viper.GetString("oauth2-token") if tokenString == "" { - ctx.Fatal("No account information found. Please login with ttnctl user login [e-mail]") + ctx.Fatal("No account information found. Please login with ttnctl user login [access code]") } token := &oauth2.Token{} err := json.Unmarshal([]byte(tokenString), token) if err != nil { - ctx.Fatal("Account information invalid. Please login with ttnctl user login [e-mail]") + ctx.Fatal("Account information invalid. Please login with ttnctl user login [access code]") } return token } From e65dba825e91f279ebac394dcaff8a1f678cc48b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 11:05:47 +0200 Subject: [PATCH 1796/2266] Change error description for 'Application already exists' --- core/handler/manager_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 4a1280d8e..8230f121e 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -257,7 +257,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica return nil, errors.BuildGRPCError(err) } if app != nil { - return nil, grpcErrf(codes.AlreadyExists, "Application already registered") + return nil, grpcErrf(codes.AlreadyExists, "Application") } err = h.handler.applications.Set(&application.Application{ From 2f8737984fa092c85079cd39a95260482d4a8c9f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 11:10:44 +0200 Subject: [PATCH 1797/2266] Generate ttnctl documentation --- ttnctl/README.md | 62 +--------- ttnctl/cmd/docs/README.md | 254 +++++++++++++++++++++++++++++--------- 2 files changed, 203 insertions(+), 113 deletions(-) diff --git a/ttnctl/README.md b/ttnctl/README.md index e2399fbaa..58f326e41 100644 --- a/ttnctl/README.md +++ b/ttnctl/README.md @@ -1,8 +1,10 @@ # The Things Network Control Utility - `ttnctl` -This document is a simple guide to `ttnctl`. +`ttnctl` can be used to manage The Things Network from the command line. -## Configuration +[Documentation](https://www.thethingsnetwork.org/docs/cli/) + +## Configuration File Configuration is done with: @@ -23,58 +25,6 @@ The following configuration options can be set: | `mqtt-broker` | `TTNCTL_MQTT_BROKER` | The address and port of the MQTT broker | | `ttn-account-server` | `TTNCTL_TTN_ACCOUNT_SERVER` | The protocol, address (and port) of the account server | -**Configuration for Development:** Copy `../.env/ttnctl.yaml.dev-example` to `~/.ttnctl.yaml` - -## Command Options - -The arguments and flags for each command are shown when executing a command with the `--help` flag. +## Development -## Getting Started - -* Create an account: `ttnctl user register [username] [e-mail]` - * Note: You might have to verify your email before you can login. -* Get a client access code on the account server by clicking *ttnctl access - code* on the home page. -* Login with the client code you received `ttnctl user login [client code]` -* List your applications: `ttnctl applications list` -* Create a new application: `ttnctl applications create [AppID] [Description]` -* Select the application you want to use from now on: `ttnctl applications select` -* Register the application with the Handler: `ttnctl applications register` -* List the devices in your application: `ttnctl devices list` -* Create a new device: `ttnctl devices create [Device ID]` -* Get info about the device: `ttnctl devices info [Device ID]` -* Personalize the device (optional): `ttnctl devices personalize [Device ID]` -* Set the next downlink for a device: `ttnctl downlink [Device ID] [Payload]` -* Subscribe to messages from your devices: `ttnctl subscribe` -* Get payload functions for your application: `ttnctl applications pf` -* Set payload functions for your application: `ttnctl applications pf set [decoder/converter/validator]` - -## List of commands - -``` -ttnctl -|-- user - |-- register - |-- login - |-- logout -|-- applications - |-- create - |-- delete - |-- list - |-- select - |-- info - |-- register - |-- unregister - |-- pf - |-- set -|-- devices - |-- create - |-- delete - |-- list - |-- info - |-- set - |-- personalize -|-- downlink -|-- subscribe -|-- version -``` +**Configuration for Development:** Copy `../.env/ttnctl.yaml.dev-example` to `~/.ttnctl.yaml` diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index 6af60a235..e9c41c733 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -23,24 +23,24 @@ ttnctl applications can be used to manage applications. ``` -## ttnctl applications create +## ttnctl applications add -Create a new application +Add a new application ### Synopsis -ttnctl applications create can be used to create a new application. +ttnctl applications add can be used to add a new application to your account. ``` -ttnctl applications create [AppID] [Description] +ttnctl applications add [AppID] [Description] ``` ### Examples ``` -$ ttnctl applications create test "Test application" - INFO Created Application +$ ttnctl applications add test "Test application" + INFO Added Application INFO Selected Current Application ``` @@ -403,46 +403,6 @@ ttnctl devices can be used to manage devices. ``` -## ttnctl devices create - -Create a new device - -### Synopsis - - -ttnctl devices create can be used to create a new device. - -``` -ttnctl devices create [Device ID] [DevEUI] [AppKey] -``` - -### Examples - -``` -$ ttnctl devices create test - INFO Using Application AppEUI=70B3D57EF0000024 AppID=test - INFO Generating random DevEUI... - INFO Generating random AppKey... - INFO Discovering Handler... - INFO Connecting with Handler... - INFO Created device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test - -``` - -### Options inherited from parent commands - -``` - --app-eui string The app EUI to use - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - ## ttnctl devices delete Delete a device @@ -626,6 +586,46 @@ $ ttnctl devices personalize test ``` +## ttnctl devices register + +Register a new device + +### Synopsis + + +ttnctl devices register can be used to register a new device. + +``` +ttnctl devices register [Device ID] [DevEUI] [AppKey] +``` + +### Examples + +``` +$ ttnctl devices register test + INFO Using Application AppEUI=70B3D57EF0000024 AppID=test + INFO Generating random DevEUI... + INFO Generating random AppKey... + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Registered device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test + +``` + +### Options inherited from parent commands + +``` + --app-eui string The app EUI to use + --app-id string The app ID to use + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + ## ttnctl devices set Set properties of a device @@ -725,14 +725,154 @@ $ ttnctl downlink test --json '{"led":"on"}' ``` -## ttnctl gateway +## ttnctl gateways Manage gateways ### Synopsis -ttnctl applications can be used to manage gateways. +ttnctl gateways can be used to manage gateways. + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl gateways delete + +Delete a gateway + +### Synopsis + + +ttnctl gateways delete can be used to delete a gateway + +``` +ttnctl gateways delete [GatewayID] +``` + +### Examples + +``` +$ ttnctl gateways delete test + INFO Deleted gateway Gateway ID=test + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl gateways edit + +edit a gateway + +### Synopsis + + +ttnctl gateways edit can be used to edit settings of a gateway + +``` +ttnctl gateways edit [GatewayID] +``` + +### Examples + +``` +$ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU + INFO Edited gateway Gateway ID=test + +``` + +### Options + +``` + --frequency-plan string The frequency plan to use on the gateway + --location string The location of the gateway +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl gateways list + +List your gateways + +### Synopsis + + +ttnctl gateways list can be used to list the gateways you have access to + +``` +ttnctl gateways list +``` + +### Examples + +``` +$ ttnctl gateways list + ID Activated Frequency Plan Coordinates +1 test true US (52.3740, 4.8896) + +``` + +### Options inherited from parent commands + +``` + --config string config file (default is $HOME/.ttnctl.yaml) + --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") + --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +``` + + +## ttnctl gateways register + +Register a gateway + +### Synopsis + + +ttnctl gateways register can be used to register a gateway + +``` +ttnctl gateways register [GatewayID] [FrequencyPlan] [Location] +``` + +### Examples + +``` +$ ttnctl gateways register test US 52.37403,4.88968 + INFO Registered gateway Gateway ID=test + +``` ### Options inherited from parent commands @@ -746,23 +886,23 @@ ttnctl applications can be used to manage gateways. ``` -## ttnctl gateway status +## ttnctl gateways status Get status of a gateway ### Synopsis -ttnctl gateway status can be used to get status of gateways. +ttnctl gateways status can be used to get status of gateways. ``` -ttnctl gateway status [gatewayID] +ttnctl gateways status [gatewayID] ``` ### Examples ``` -$ ttnctl gateway status eui-0000024b08060030 +$ ttnctl gateways status test INFO Discovering Router... INFO Connecting with Router... INFO Connected to Router @@ -888,24 +1028,24 @@ $ ttnctl user ## ttnctl user login -Login +Log in with your TTN account ### Synopsis -ttnctl user login allows you to login to the account server. +ttnctl user login allows you to log in to your TTN account. ``` -ttnctl user login [client code] +ttnctl user login [access code] ``` ### Examples ``` -First get an access code from your TTN Profile by going to +First get an access code from your TTN profile by going to https://account.thethingsnetwork.org and clicking "ttnctl access code". -$ ttnctl user login 2keK3FTu6e0327cq4ni0wRTMT2mTS-m_FLzFBlNQadwa +$ ttnctl user login [paste the access code you requested above] INFO Successfully logged in as yourname (your@email.org) ``` From 242680ab4c0234e16947b63069f62e16b0cf5c56 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 12:14:28 +0200 Subject: [PATCH 1798/2266] Clone refactor branch, not develop --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5a4a3474..98648eeb7 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ When you get started with The Things Network, you'll probably have some question ## Set up The Things Network's backend for Development 1. Fork this repository -2. Clone your fork: `git clone --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` +2. Clone your fork: `git clone --branch refactor --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` 3. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` 4. Install the dependencies for development: `make dev-deps` 5. Run the tests: `make test` From 9493cf3ee016bf96d74ac6e89dd9e02c878c2832 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 13:55:33 +0200 Subject: [PATCH 1799/2266] Split MQTT into up/down/events --- mqtt/README.md | 4 +- mqtt/activations.go | 61 +++++ mqtt/activations_test.go | 151 ++++++++++++ mqtt/client.go | 253 +------------------- mqtt/client_test.go | 482 --------------------------------------- mqtt/downlink.go | 78 +++++++ mqtt/downlink_test.go | 151 ++++++++++++ mqtt/events.go | 77 +++++++ mqtt/events_test.go | 56 +++++ mqtt/topics.go | 78 +++++-- mqtt/topics_test.go | 15 +- mqtt/uplink.go | 117 ++++++++++ mqtt/uplink_test.go | 219 ++++++++++++++++++ 13 files changed, 998 insertions(+), 744 deletions(-) create mode 100644 mqtt/activations.go create mode 100644 mqtt/activations_test.go create mode 100644 mqtt/downlink.go create mode 100644 mqtt/downlink_test.go create mode 100644 mqtt/events.go create mode 100644 mqtt/events_test.go create mode 100644 mqtt/uplink.go create mode 100644 mqtt/uplink_test.go diff --git a/mqtt/README.md b/mqtt/README.md index 238cfbdd9..e508a7309 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -165,7 +165,7 @@ if err := token.Error(); err != nil { ## Device Activations -**Topic:** `/devices//activations` +**Topic:** `/devices//events/activations` **Message:** @@ -180,7 +180,7 @@ if err := token.Error(); err != nil { } ``` -**Usage (Mosquitto):** `mosquitto_sub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/activations'` +**Usage (Mosquitto):** `mosquitto_sub -h .thethings.network:1883 -d -t 'my-app-id/devices/my-dev-id/events/activations'` **Usage (Go client):** diff --git a/mqtt/activations.go b/mqtt/activations.go new file mode 100644 index 000000000..cd1fdb18b --- /dev/null +++ b/mqtt/activations.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import "encoding/json" + +// ActivationHandler is called for activations +type ActivationHandler func(client Client, appID string, devID string, req Activation) + +// ActivationEvent for MQTT +const ActivationEvent = "activations" + +// PublishActivation publishes an activation +func (c *DefaultClient) PublishActivation(activation Activation) Token { + appID := activation.AppID + devID := activation.DevID + activation.AppID = "" + activation.DevID = "" + return c.PublishDeviceEvent(appID, devID, ActivationEvent, activation) +} + +// SubscribeDeviceActivations subscribes to all activations for the given application and device +func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { + return c.SubscribeDeviceEvents(appID, devID, ActivationEvent, func(_ Client, appID string, devID string, _ string, payload []byte) { + activation := Activation{} + if err := json.Unmarshal(payload, &activation); err != nil { + c.ctx.Warnf("Could not unmarshal activation (%s).", err.Error()) + return + } + activation.AppID = appID + activation.DevID = devID + // Call the Activation handler + handler(c, appID, devID, activation) + }) +} + +// SubscribeAppActivations subscribes to all activations for the given application +func (c *DefaultClient) SubscribeAppActivations(appID string, handler ActivationHandler) Token { + return c.SubscribeDeviceActivations(appID, "", handler) +} + +// SubscribeActivations subscribes to all activations that the current user has access to +func (c *DefaultClient) SubscribeActivations(handler ActivationHandler) Token { + return c.SubscribeDeviceActivations("", "", handler) +} + +// UnsubscribeDeviceActivations unsubscribes from the activations for the given application and device +func (c *DefaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { + return c.UnsubscribeDeviceEvents(appID, devID, ActivationEvent) +} + +// UnsubscribeAppActivations unsubscribes from the activations for the given application +func (c *DefaultClient) UnsubscribeAppActivations(appID string) Token { + return c.UnsubscribeDeviceEvents(appID, "", ActivationEvent) +} + +// UnsubscribeActivations unsubscribes from the activations that the current user has access to +func (c *DefaultClient) UnsubscribeActivations() Token { + return c.UnsubscribeDeviceEvents("", "", ActivationEvent) +} diff --git a/mqtt/activations_test.go b/mqtt/activations_test.go new file mode 100644 index 000000000..bd2186d52 --- /dev/null +++ b/mqtt/activations_test.go @@ -0,0 +1,151 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +// Activations pub/sub + +func TestPublishActivations(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + dataActivations := Activation{ + AppID: "someid", + DevID: "someid", + Metadata: Metadata{DataRate: "SF7BW125"}, + } + + token := c.PublishActivation(dataActivations) + waitForOK(token, a) + + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDeviceActivations(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req Activation) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeDeviceActivations("someid", "someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppActivations(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req Activation) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeAppActivations("someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeActivations(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeActivations(func(client Client, appID string, devID string, req Activation) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeActivations() + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubActivations(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(1) + + subToken := c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req Activation) { + a.So(appID, ShouldResemble, "app5") + a.So(devID, ShouldResemble, "dev1") + + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishActivation(Activation{ + AppID: "app5", + DevID: "dev1", + Metadata: Metadata{DataRate: "SF7BW125"}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeDeviceActivations("app5", "dev1") + waitForOK(unsubToken, a) +} + +func TestPubSubAppActivations(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(2) + + subToken := c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { + a.So(appID, ShouldResemble, "app6") + a.So(req.Metadata.DataRate, ShouldEqual, "SF7BW125") + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishActivation(Activation{ + AppID: "app6", + DevID: "dev1", + Metadata: Metadata{DataRate: "SF7BW125"}, + }) + waitForOK(pubToken, a) + pubToken = c.PublishActivation(Activation{ + AppID: "app6", + DevID: "dev2", + Metadata: Metadata{DataRate: "SF7BW125"}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeAppActivations("app6") + waitForOK(unsubToken, a) +} diff --git a/mqtt/client.go b/mqtt/client.go index 00c019326..0cb68cb99 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -4,7 +4,6 @@ package mqtt import ( - "encoding/json" "fmt" "sync" "time" @@ -45,6 +44,14 @@ type Client interface { UnsubscribeAppDownlink(appID string) Token UnsubscribeDownlink() Token + // Event pub/sub + PublishAppEvent(appID string, eventType string, payload interface{}) Token + PublishDeviceEvent(appID string, devID string, eventType string, payload interface{}) Token + SubscribeAppEvents(appID string, eventType string, handler AppEventHandler) Token + SubscribeDeviceEvents(appID string, devID string, eventType string, handler DeviceEventHandler) Token + UnsubscribeAppEvents(appID string, eventType string) Token + UnsubscribeDeviceEvents(appID string, devID string, eventType string) Token + // Activation pub/sub PublishActivation(payload Activation) Token SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token @@ -130,15 +137,6 @@ func (t *token) Error() error { return t.err } -// UplinkHandler is called for uplink messages -type UplinkHandler func(client Client, appID string, devID string, req UplinkMessage) - -// DownlinkHandler is called for downlink messages -type DownlinkHandler func(client Client, appID string, devID string, req DownlinkMessage) - -// ActivationHandler is called for activations -type ActivationHandler func(client Client, appID string, devID string, req Activation) - // DefaultClient is the default MQTT client for The Things Network type DefaultClient struct { mqtt MQTT.Client @@ -233,6 +231,10 @@ func (c *DefaultClient) Connect() error { return nil } +func (c *DefaultClient) publish(topic string, msg []byte) Token { + return c.mqtt.Publish(topic, QoS, false, msg) +} + func (c *DefaultClient) subscribe(topic string, handler MQTT.MessageHandler) Token { c.subscriptions[topic] = handler return c.mqtt.Subscribe(topic, QoS, handler) @@ -256,234 +258,3 @@ func (c *DefaultClient) Disconnect() { func (c *DefaultClient) IsConnected() bool { return c.mqtt.IsConnected() } - -// PublishUplink publishes an uplink message to the MQTT broker -func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { - topic := DeviceTopic{dataUp.AppID, dataUp.DevID, Uplink, ""} - dataUp.AppID = "" - dataUp.DevID = "" - msg, err := json.Marshal(dataUp) - if err != nil { - return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} - } - return c.mqtt.Publish(topic.String(), QoS, false, msg) -} - -// PublishUplinkFields publishes uplink fields to MQTT -func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token { - flattenedFields := make(map[string]interface{}) - flatten("", "/", fields, flattenedFields) - tokens := make([]Token, 0, len(flattenedFields)) - for field, value := range flattenedFields { - topic := DeviceTopic{appID, devID, Uplink, field} - pld, _ := json.Marshal(value) - token := c.mqtt.Publish(topic.String(), QoS, false, pld) - tokens = append(tokens, token) - } - t := newToken() - go func() { - for _, token := range tokens { - token.Wait() - if token.Error() != nil { - fmt.Println(token.Error()) - t.err = token.Error() - } - } - t.flowComplete() - }() - return t -} - -func flatten(prefix, sep string, in, out map[string]interface{}) { - for k, v := range in { - key := prefix + sep + k - if prefix == "" { - key = k - } - out[key] = v - if next, ok := v.(map[string]interface{}); ok { - flatten(key, sep, next, out) - } - } -} - -// SubscribeDeviceUplink subscribes to all uplink messages for the given application and device -func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { - topic := DeviceTopic{appID, devID, Uplink, ""} - return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { - // Determine the actual topic - topic, err := ParseDeviceTopic(msg.Topic()) - if err != nil { - c.ctx.Warnf("Received message on invalid uplink topic: %s", msg.Topic()) - return - } - - // Unmarshal the payload - dataUp := &UplinkMessage{} - err = json.Unmarshal(msg.Payload(), dataUp) - dataUp.AppID = topic.AppID - dataUp.DevID = topic.DevID - - if err != nil { - c.ctx.Warnf("Could not unmarshal uplink (%s).", err.Error()) - return - } - - // Call the uplink handler - handler(c, topic.AppID, topic.DevID, *dataUp) - }) -} - -// SubscribeAppUplink subscribes to all uplink messages for the given application -func (c *DefaultClient) SubscribeAppUplink(appID string, handler UplinkHandler) Token { - return c.SubscribeDeviceUplink(appID, "", handler) -} - -// SubscribeUplink subscribes to all uplink messages that the current user has access to -func (c *DefaultClient) SubscribeUplink(handler UplinkHandler) Token { - return c.SubscribeDeviceUplink("", "", handler) -} - -// UnsubscribeDeviceUplink unsubscribes from the uplink messages for the given application and device -func (c *DefaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { - topic := DeviceTopic{appID, devID, Uplink, ""} - return c.unsubscribe(topic.String()) -} - -// UnsubscribeAppUplink unsubscribes from the uplink messages for the given application -func (c *DefaultClient) UnsubscribeAppUplink(appID string) Token { - return c.UnsubscribeDeviceUplink(appID, "") -} - -// UnsubscribeUplink unsubscribes from the uplink messages that the current user has access to -func (c *DefaultClient) UnsubscribeUplink() Token { - return c.UnsubscribeDeviceUplink("", "") -} - -// PublishDownlink publishes a downlink message -func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { - topic := DeviceTopic{dataDown.AppID, dataDown.DevID, Downlink, ""} - dataDown.AppID = "" - dataDown.DevID = "" - msg, err := json.Marshal(dataDown) - if err != nil { - return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} - } - return c.mqtt.Publish(topic.String(), QoS, false, msg) -} - -// SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device -func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { - topic := DeviceTopic{appID, devID, Downlink, ""} - return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { - // Determine the actual topic - topic, err := ParseDeviceTopic(msg.Topic()) - if err != nil { - c.ctx.Warnf("Received message on invalid downlink topic: %s", msg.Topic()) - return - } - - // Unmarshal the payload - dataDown := &DownlinkMessage{} - err = json.Unmarshal(msg.Payload(), dataDown) - if err != nil { - c.ctx.Warnf("Could not unmarshal downlink (%s).", err.Error()) - return - } - dataDown.AppID = topic.AppID - dataDown.DevID = topic.DevID - - // Call the Downlink handler - handler(c, topic.AppID, topic.DevID, *dataDown) - }) -} - -// SubscribeAppDownlink subscribes to all downlink messages for the given application -func (c *DefaultClient) SubscribeAppDownlink(appID string, handler DownlinkHandler) Token { - return c.SubscribeDeviceDownlink(appID, "", handler) -} - -// SubscribeDownlink subscribes to all downlink messages that the current user has access to -func (c *DefaultClient) SubscribeDownlink(handler DownlinkHandler) Token { - return c.SubscribeDeviceDownlink("", "", handler) -} - -// UnsubscribeDeviceDownlink unsubscribes from the downlink messages for the given application and device -func (c *DefaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { - topic := DeviceTopic{appID, devID, Downlink, ""} - return c.unsubscribe(topic.String()) -} - -// UnsubscribeAppDownlink unsubscribes from the downlink messages for the given application -func (c *DefaultClient) UnsubscribeAppDownlink(appID string) Token { - return c.UnsubscribeDeviceDownlink(appID, "") -} - -// UnsubscribeDownlink unsubscribes from the downlink messages that the current user has access to -func (c *DefaultClient) UnsubscribeDownlink() Token { - return c.UnsubscribeDeviceDownlink("", "") -} - -// PublishActivation publishes an activation -func (c *DefaultClient) PublishActivation(activation Activation) Token { - topic := DeviceTopic{activation.AppID, activation.DevID, Activations, ""} - activation.AppID = "" - activation.DevID = "" - msg, err := json.Marshal(activation) - if err != nil { - return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} - } - return c.mqtt.Publish(topic.String(), QoS, false, msg) -} - -// SubscribeDeviceActivations subscribes to all activations for the given application and device -func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { - topic := DeviceTopic{appID, devID, Activations, ""} - return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { - // Determine the actual topic - topic, err := ParseDeviceTopic(msg.Topic()) - if err != nil { - c.ctx.Warnf("Received message on invalid activations topic: %s", msg.Topic()) - return - } - - // Unmarshal the payload - activation := &Activation{} - err = json.Unmarshal(msg.Payload(), activation) - if err != nil { - c.ctx.Warnf("Could not unmarshal activation (%s).", err.Error()) - return - } - activation.AppID = topic.AppID - activation.DevID = topic.DevID - - // Call the Activation handler - handler(c, topic.AppID, topic.DevID, *activation) - }) -} - -// SubscribeAppActivations subscribes to all activations for the given application -func (c *DefaultClient) SubscribeAppActivations(appID string, handler ActivationHandler) Token { - return c.SubscribeDeviceActivations(appID, "", handler) -} - -// SubscribeActivations subscribes to all activations that the current user has access to -func (c *DefaultClient) SubscribeActivations(handler ActivationHandler) Token { - return c.SubscribeDeviceActivations("", "", handler) -} - -// UnsubscribeDeviceActivations unsubscribes from the activations for the given application and device -func (c *DefaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { - topic := DeviceTopic{appID, devID, Activations, ""} - return c.unsubscribe(topic.String()) -} - -// UnsubscribeAppActivations unsubscribes from the activations for the given application -func (c *DefaultClient) UnsubscribeAppActivations(appID string) Token { - return c.UnsubscribeDeviceActivations(appID, "") -} - -// UnsubscribeActivations unsubscribes from the activations that the current user has access to -func (c *DefaultClient) UnsubscribeActivations() Token { - return c.UnsubscribeDeviceActivations("", "") -} diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 31cf3bc99..313b9dbca 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -7,13 +7,11 @@ import ( "errors" "fmt" "os" - "strings" "testing" "time" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" - MQTT "github.com/eclipse/paho.mqtt.golang" . "github.com/smartystreets/assertions" ) @@ -147,486 +145,6 @@ func TestRandomTopicPublish(t *testing.T) { ctx.Info("This test should have printed one message.") } -// Uplink pub/sub - -func TestPublishUplink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - dataUp := UplinkMessage{ - AppID: "someid", - DevID: "someid", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - } - - token := c.PublishUplink(dataUp) - waitForOK(token, a) -} - -func TestPublishUplinkFields(t *testing.T) { - a := New(t) - ctx := GetLogger(t, "Test") - c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - - c.Connect() - defer c.Disconnect() - - subChan := make(chan bool) - waitChan := make(chan bool) - go func() { - for i := 10; i > 0; i-- { - <-subChan - } - close(subChan) - waitChan <- true - }() - subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { - switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { - case "battery": - a.So(string(msg.Payload()), ShouldEqual, "90") - case "sensors": - a.So(string(msg.Payload()), ShouldContainSubstring, `people":["`) - case "sensors/color": - a.So(string(msg.Payload()), ShouldEqual, `"blue"`) - case "sensors/people": - a.So(string(msg.Payload()), ShouldEqual, `["bob","alice"]`) - case "sensors/water": - a.So(string(msg.Payload()), ShouldEqual, "true") - case "sensors/analog": - a.So(string(msg.Payload()), ShouldEqual, `[0,255,500,1000]`) - case "sensors/history": - a.So(string(msg.Payload()), ShouldContainSubstring, `today":"`) - case "sensors/history/today": - a.So(string(msg.Payload()), ShouldEqual, `"not yet"`) - case "sensors/history/yesterday": - a.So(string(msg.Payload()), ShouldEqual, `"absolutely"`) - case "gps": - a.So(string(msg.Payload()), ShouldEqual, "[52.3736735,4.886663]") - default: - t.Errorf("Should not have received message on topic %s", msg.Topic()) - t.Fail() - } - subChan <- true - }) - waitForOK(subToken, a) - - fields := map[string]interface{}{ - "battery": 90, - "sensors": map[string]interface{}{ - "color": "blue", - "people": []string{"bob", "alice"}, - "water": true, - "analog": []int{0, 255, 500, 1000}, - "history": map[string]interface{}{ - "today": "not yet", - "yesterday": "absolutely", - }, - }, - "gps": []float64{52.3736735, 4.886663}, - } - - pubToken := c.PublishUplinkFields("fields-app", "fields-dev", fields) - waitForOK(pubToken, a) - - select { - case <-waitChan: - case <-time.After(1 * time.Second): - panic("Did not receive fields") - } -} - -func TestSubscribeDeviceUplink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - subToken := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { - - }) - waitForOK(subToken, a) - - unsubToken := c.UnsubscribeDeviceUplink("someid", "someid") - waitForOK(unsubToken, a) -} - -func TestSubscribeAppUplink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - subToken := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { - - }) - waitForOK(subToken, a) - - unsubToken := c.UnsubscribeAppUplink("someid") - waitForOK(unsubToken, a) -} - -func TestSubscribeUplink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - subToken := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { - - }) - waitForOK(subToken, a) - - unsubToken := c.UnsubscribeUplink() - waitForOK(unsubToken, a) -} - -func TestPubSubUplink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - waitChan := make(chan bool, 1) - - subToken := c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { - a.So(appID, ShouldResemble, "app1") - a.So(devID, ShouldResemble, "dev1") - - waitChan <- true - }) - waitForOK(subToken, a) - - pubToken := c.PublishUplink(UplinkMessage{ - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - AppID: "app1", - DevID: "dev1", - }) - waitForOK(pubToken, a) - - select { - case <-waitChan: - case <-time.After(1 * time.Second): - panic("Did not receive Uplink") - } - - unsubToken := c.UnsubscribeDeviceUplink("app1", "dev1") - waitForOK(unsubToken, a) -} - -func TestPubSubAppUplink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - var wg WaitGroup - - wg.Add(2) - - subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { - a.So(appID, ShouldResemble, "app2") - a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) - wg.Done() - }) - waitForOK(subToken, a) - - pubToken := c.PublishUplink(UplinkMessage{ - AppID: "app2", - DevID: "dev1", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }) - waitForOK(pubToken, a) - pubToken = c.PublishUplink(UplinkMessage{ - AppID: "app2", - DevID: "dev2", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }) - waitForOK(pubToken, a) - - a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - - unsubToken := c.UnsubscribeAppUplink("app1") - waitForOK(unsubToken, a) -} - -// Downlink pub/sub - -func TestPublishDownlink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - dataDown := DownlinkMessage{ - AppID: "someid", - DevID: "someid", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - } - - token := c.PublishDownlink(dataDown) - waitForOK(token, a) - - a.So(token.Error(), ShouldBeNil) -} - -func TestSubscribeDeviceDownlink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req DownlinkMessage) { - - }) - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) - - token = c.UnsubscribeDeviceDownlink("someid", "someid") - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) -} - -func TestSubscribeAppDownlink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req DownlinkMessage) { - - }) - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) - - token = c.UnsubscribeAppDownlink("someid") - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) -} - -func TestSubscribeDownlink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - token := c.SubscribeDownlink(func(client Client, appID string, devID string, req DownlinkMessage) { - - }) - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) - - token = c.UnsubscribeDownlink() - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) -} - -func TestPubSubDownlink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - var wg WaitGroup - - wg.Add(1) - - subToken := c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req DownlinkMessage) { - a.So(appID, ShouldResemble, "app3") - a.So(devID, ShouldResemble, "dev3") - - wg.Done() - }) - waitForOK(subToken, a) - - pubToken := c.PublishDownlink(DownlinkMessage{ - AppID: "app3", - DevID: "dev3", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }) - waitForOK(pubToken, a) - - a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - - unsubToken := c.UnsubscribeDeviceDownlink("app3", "dev3") - waitForOK(unsubToken, a) -} - -func TestPubSubAppDownlink(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - var wg WaitGroup - - wg.Add(2) - - subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { - a.So(appID, ShouldResemble, "app4") - a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) - wg.Done() - }) - waitForOK(subToken, a) - - pubToken := c.PublishDownlink(DownlinkMessage{ - AppID: "app4", - DevID: "dev1", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }) - waitForOK(pubToken, a) - pubToken = c.PublishDownlink(DownlinkMessage{ - AppID: "app4", - DevID: "dev2", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - }) - waitForOK(pubToken, a) - - a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - - unsubToken := c.UnsubscribeAppDownlink("app3") - waitForOK(unsubToken, a) -} - -// Activations pub/sub - -func TestPublishActivations(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - dataActivations := Activation{ - AppID: "someid", - DevID: "someid", - Metadata: Metadata{DataRate: "SF7BW125"}, - } - - token := c.PublishActivation(dataActivations) - waitForOK(token, a) - - a.So(token.Error(), ShouldBeNil) -} - -func TestSubscribeDeviceActivations(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req Activation) { - - }) - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) - - token = c.UnsubscribeDeviceActivations("someid", "someid") - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) -} - -func TestSubscribeAppActivations(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req Activation) { - - }) - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) - - token = c.UnsubscribeAppActivations("someid") - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) -} - -func TestSubscribeActivations(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - token := c.SubscribeActivations(func(client Client, appID string, devID string, req Activation) { - - }) - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) - - token = c.UnsubscribeActivations() - waitForOK(token, a) - a.So(token.Error(), ShouldBeNil) -} - -func TestPubSubActivations(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - var wg WaitGroup - - wg.Add(1) - - subToken := c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req Activation) { - a.So(appID, ShouldResemble, "app5") - a.So(devID, ShouldResemble, "dev1") - - wg.Done() - }) - waitForOK(subToken, a) - - pubToken := c.PublishActivation(Activation{ - AppID: "app5", - DevID: "dev1", - Metadata: Metadata{DataRate: "SF7BW125"}, - }) - waitForOK(pubToken, a) - - a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - - unsubToken := c.UnsubscribeDeviceActivations("app5", "dev1") - waitForOK(unsubToken, a) -} - -func TestPubSubAppActivations(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() - defer c.Disconnect() - - var wg WaitGroup - - wg.Add(2) - - subToken := c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { - a.So(appID, ShouldResemble, "app6") - a.So(req.Metadata.DataRate, ShouldEqual, "SF7BW125") - wg.Done() - }) - waitForOK(subToken, a) - - pubToken := c.PublishActivation(Activation{ - AppID: "app6", - DevID: "dev1", - Metadata: Metadata{DataRate: "SF7BW125"}, - }) - waitForOK(pubToken, a) - pubToken = c.PublishActivation(Activation{ - AppID: "app6", - DevID: "dev2", - Metadata: Metadata{DataRate: "SF7BW125"}, - }) - waitForOK(pubToken, a) - - a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) - - unsubToken := c.UnsubscribeAppActivations("app6") - waitForOK(unsubToken, a) -} - func ExampleNewClient() { ctx := log.WithField("Example", "NewClient") exampleClient := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") diff --git a/mqtt/downlink.go b/mqtt/downlink.go new file mode 100644 index 000000000..3277a76f8 --- /dev/null +++ b/mqtt/downlink.go @@ -0,0 +1,78 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "fmt" + + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// DownlinkHandler is called for downlink messages +type DownlinkHandler func(client Client, appID string, devID string, req DownlinkMessage) + +// PublishDownlink publishes a downlink message +func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { + topic := DeviceTopic{dataDown.AppID, dataDown.DevID, DeviceDownlink, ""} + dataDown.AppID = "" + dataDown.DevID = "" + msg, err := json.Marshal(dataDown) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.mqtt.Publish(topic.String(), QoS, false, msg) +} + +// SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device +func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token { + topic := DeviceTopic{appID, devID, DeviceDownlink, ""} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseDeviceTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid downlink topic: %s", msg.Topic()) + return + } + + // Unmarshal the payload + dataDown := &DownlinkMessage{} + err = json.Unmarshal(msg.Payload(), dataDown) + if err != nil { + c.ctx.Warnf("Could not unmarshal downlink (%s).", err.Error()) + return + } + dataDown.AppID = topic.AppID + dataDown.DevID = topic.DevID + + // Call the Downlink handler + handler(c, topic.AppID, topic.DevID, *dataDown) + }) +} + +// SubscribeAppDownlink subscribes to all downlink messages for the given application +func (c *DefaultClient) SubscribeAppDownlink(appID string, handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink(appID, "", handler) +} + +// SubscribeDownlink subscribes to all downlink messages that the current user has access to +func (c *DefaultClient) SubscribeDownlink(handler DownlinkHandler) Token { + return c.SubscribeDeviceDownlink("", "", handler) +} + +// UnsubscribeDeviceDownlink unsubscribes from the downlink messages for the given application and device +func (c *DefaultClient) UnsubscribeDeviceDownlink(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, DeviceDownlink, ""} + return c.unsubscribe(topic.String()) +} + +// UnsubscribeAppDownlink unsubscribes from the downlink messages for the given application +func (c *DefaultClient) UnsubscribeAppDownlink(appID string) Token { + return c.UnsubscribeDeviceDownlink(appID, "") +} + +// UnsubscribeDownlink unsubscribes from the downlink messages that the current user has access to +func (c *DefaultClient) UnsubscribeDownlink() Token { + return c.UnsubscribeDeviceDownlink("", "") +} diff --git a/mqtt/downlink_test.go b/mqtt/downlink_test.go new file mode 100644 index 000000000..47a46a736 --- /dev/null +++ b/mqtt/downlink_test.go @@ -0,0 +1,151 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +// Downlink pub/sub + +func TestPublishDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + dataDown := DownlinkMessage{ + AppID: "someid", + DevID: "someid", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + } + + token := c.PublishDownlink(dataDown) + waitForOK(token, a) + + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDeviceDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req DownlinkMessage) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeDeviceDownlink("someid", "someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeAppDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req DownlinkMessage) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeAppDownlink("someid") + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + token := c.SubscribeDownlink(func(client Client, appID string, devID string, req DownlinkMessage) { + + }) + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) + + token = c.UnsubscribeDownlink() + waitForOK(token, a) + a.So(token.Error(), ShouldBeNil) +} + +func TestPubSubDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(1) + + subToken := c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req DownlinkMessage) { + a.So(appID, ShouldResemble, "app3") + a.So(devID, ShouldResemble, "dev3") + + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishDownlink(DownlinkMessage{ + AppID: "app3", + DevID: "dev3", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeDeviceDownlink("app3", "dev3") + waitForOK(unsubToken, a) +} + +func TestPubSubAppDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(2) + + subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { + a.So(appID, ShouldResemble, "app4") + a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishDownlink(DownlinkMessage{ + AppID: "app4", + DevID: "dev1", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + pubToken = c.PublishDownlink(DownlinkMessage{ + AppID: "app4", + DevID: "dev2", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeAppDownlink("app3") + waitForOK(unsubToken, a) +} diff --git a/mqtt/events.go b/mqtt/events.go new file mode 100644 index 000000000..5090a4c99 --- /dev/null +++ b/mqtt/events.go @@ -0,0 +1,77 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "fmt" + + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// AppEventHandler is called for events +type AppEventHandler func(client Client, appID string, eventType string, payload []byte) + +// DeviceEventHandler is called for events +type DeviceEventHandler func(client Client, appID string, devID string, eventType string, payload []byte) + +// PublishAppEvent publishes an event to the topic for application events of the given type +// it will marshal the payload to json +func (c *DefaultClient) PublishAppEvent(appID string, eventType string, payload interface{}) Token { + topic := ApplicationTopic{appID, AppEvents, eventType} + msg, err := json.Marshal(payload) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.publish(topic.String(), msg) +} + +// PublishDeviceEvent publishes an event to the topic for device events of the given type +// it will marshal the payload to json +func (c *DefaultClient) PublishDeviceEvent(appID string, devID string, eventType string, payload interface{}) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, eventType} + msg, err := json.Marshal(payload) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.publish(topic.String(), msg) +} + +// SubscribeAppEvents subscribes to events of the given type for the given application +func (c *DefaultClient) SubscribeAppEvents(appID string, eventType string, handler AppEventHandler) Token { + topic := ApplicationTopic{appID, AppEvents, eventType} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + topic, err := ParseApplicationTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid events topic: %s", msg.Topic()) + return + } + handler(c, topic.AppID, topic.Field, msg.Payload()) + }) +} + +// SubscribeDeviceEvents subscribes to events of the given type for the given device +func (c *DefaultClient) SubscribeDeviceEvents(appID string, devID string, eventType string, handler DeviceEventHandler) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, eventType} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + topic, err := ParseDeviceTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid events topic: %s", msg.Topic()) + return + } + handler(c, topic.AppID, topic.DevID, topic.Field, msg.Payload()) + }) +} + +// UnsubscribeAppEvents unsubscribes from the events that were subscribed to by SubscribeAppEvents +func (c *DefaultClient) UnsubscribeAppEvents(appID string, eventType string) Token { + topic := ApplicationTopic{appID, AppEvents, eventType} + return c.unsubscribe(topic.String()) +} + +// UnsubscribeDeviceEvents unsubscribes from the events that were subscribed to by SubscribeDeviceEvents +func (c *DefaultClient) UnsubscribeDeviceEvents(appID string, devID string, eventType string) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, eventType} + return c.unsubscribe(topic.String()) +} diff --git a/mqtt/events_test.go b/mqtt/events_test.go new file mode 100644 index 000000000..7e3509244 --- /dev/null +++ b/mqtt/events_test.go @@ -0,0 +1,56 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestPublishSubscribeAppEvents(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + var wg WaitGroup + wg.Add(1) + subToken := c.SubscribeAppEvents("app-id", "", func(_ Client, appID string, eventType string, payload []byte) { + a.So(appID, ShouldEqual, "app-id") + a.So(eventType, ShouldEqual, "some-event") + a.So(string(payload), ShouldEqual, `"payload"`) + wg.Done() + }) + waitForOK(subToken, a) + pubToken := c.PublishAppEvent("app-id", "some-event", "payload") + waitForOK(pubToken, a) + unsubToken := c.UnsubscribeAppEvents("app-id", "") + waitForOK(unsubToken, a) + wg.WaitFor(100 * time.Millisecond) +} + +func TestPublishSubscribeDeviceEvents(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + var wg WaitGroup + wg.Add(1) + subToken := c.SubscribeDeviceEvents("app-id", "dev-id", "", func(_ Client, appID string, devID string, eventType string, payload []byte) { + a.So(appID, ShouldEqual, "app-id") + a.So(devID, ShouldEqual, "dev-id") + a.So(eventType, ShouldEqual, "some-event") + a.So(string(payload), ShouldEqual, `"payload"`) + wg.Done() + }) + waitForOK(subToken, a) + pubToken := c.PublishDeviceEvent("app-id", "dev-id", "some-event", "payload") + waitForOK(pubToken, a) + unsubToken := c.UnsubscribeDeviceEvents("app-id", "dev-id", "") + waitForOK(unsubToken, a) + wg.WaitFor(100 * time.Millisecond) +} diff --git a/mqtt/topics.go b/mqtt/topics.go index 3357e5582..963cd4c0d 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -6,23 +6,22 @@ package mqtt import ( "fmt" "regexp" + "strings" ) +const wildcard = "+" + // DeviceTopicType represents the type of a device topic type DeviceTopicType string +// Topic types for Devices const ( - // Activations of devices - Activations DeviceTopicType = "activations" - // Uplink data from devices - Uplink DeviceTopicType = "up" - // Downlink data to devices - Downlink DeviceTopicType = "down" + DeviceEvents DeviceTopicType = "events" + DeviceUplink DeviceTopicType = "up" + DeviceDownlink DeviceTopicType = "down" ) -const wildcard = "+" - -// DeviceTopic represents an MQTT topic for application devices +// DeviceTopic represents an MQTT topic for devices type DeviceTopic struct { AppID string DevID string @@ -32,7 +31,7 @@ type DeviceTopic struct { // ParseDeviceTopic parses an MQTT device topic string to a DeviceTopic struct func ParseDeviceTopic(topic string) (*DeviceTopic, error) { - pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(devices)/([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(activations|up|down)([0-9a-z/]+)?$") + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(devices)/([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(events|up|down)([0-9a-z/]+)?$") matches := pattern.FindStringSubmatch(topic) if len(matches) < 4 { return nil, fmt.Errorf("Invalid topic format") @@ -47,8 +46,8 @@ func ParseDeviceTopic(topic string) (*DeviceTopic, error) { } topicType := DeviceTopicType(matches[4]) deviceTopic := &DeviceTopic{appID, devID, topicType, ""} - if topicType == Uplink && len(matches) > 4 { - deviceTopic.Field = matches[5] + if (topicType == DeviceUplink || topicType == DeviceEvents) && len(matches) > 4 { + deviceTopic.Field = strings.Trim(matches[5], "/") } return deviceTopic, nil } @@ -63,8 +62,61 @@ func (t DeviceTopic) String() string { if t.DevID != "" { devID = t.DevID } + if t.Type == DeviceEvents && t.Field == "" { + t.Field = wildcard + } topic := fmt.Sprintf("%s/%s/%s/%s", appID, "devices", devID, t.Type) - if t.Type == Uplink && t.Field != "" { + if (t.Type == DeviceUplink || t.Type == DeviceEvents) && t.Field != "" { + topic += "/" + t.Field + } + return topic +} + +// ApplicationTopicType represents the type of an application topic +type ApplicationTopicType string + +// Topic types for Applications +const ( + AppEvents ApplicationTopicType = "events" +) + +// ApplicationTopic represents an MQTT topic for applications +type ApplicationTopic struct { + AppID string + Type ApplicationTopicType + Field string +} + +// ParseApplicationTopic parses an MQTT device topic string to an ApplicationTopic struct +func ParseApplicationTopic(topic string) (*ApplicationTopic, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(events)([0-9a-z/]+)?$") + matches := pattern.FindStringSubmatch(topic) + if len(matches) < 2 { + return nil, fmt.Errorf("Invalid topic format") + } + var appID string + if matches[1] != wildcard { + appID = matches[1] + } + topicType := ApplicationTopicType(matches[2]) + appTopic := &ApplicationTopic{appID, topicType, ""} + if topicType == AppEvents && len(matches) > 2 { + appTopic.Field = strings.Trim(matches[3], "/") + } + return appTopic, nil +} + +// String implements the Stringer interface +func (t ApplicationTopic) String() string { + appID := wildcard + if t.AppID != "" { + appID = t.AppID + } + if t.Type == AppEvents && t.Field == "" { + t.Field = wildcard + } + topic := fmt.Sprintf("%s/%s", appID, t.Type) + if t.Type == AppEvents && t.Field != "" { topic += "/" + t.Field } return topic diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index a53dc090d..936a3dd04 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -17,7 +17,7 @@ func TestParseDeviceTopic(t *testing.T) { expected := &DeviceTopic{ AppID: "appid-1", DevID: "devid-1", - Type: Uplink, + Type: DeviceUplink, } got, err := ParseDeviceTopic(topic) @@ -48,7 +48,7 @@ func TestTopicString(t *testing.T) { topic := &DeviceTopic{ AppID: "appid-1", DevID: "devid-1", - Type: Downlink, + Type: DeviceDownlink, } expected := "appid-1/devices/devid-1/down" @@ -64,20 +64,23 @@ func TestTopicParseAndString(t *testing.T) { expectedList := []string{ // Uppercase (not lowercase) "0102030405060708/devices/abcdabcd12345678/up", + "0102030405060708/devices/abcdabcd12345678/up/value", "0102030405060708/devices/abcdabcd12345678/down", - "0102030405060708/devices/abcdabcd12345678/activations", + "0102030405060708/devices/abcdabcd12345678/events/activations", // Numbers "0102030405060708/devices/0000000012345678/up", + "0102030405060708/devices/0000000012345678/up/value", "0102030405060708/devices/0000000012345678/down", - "0102030405060708/devices/0000000012345678/activations", + "0102030405060708/devices/0000000012345678/events/activations", // Wildcards "+/devices/+/up", "+/devices/+/down", - "+/devices/+/activations", + "+/devices/+/events/activations", // Not Wildcard "0102030405060708/devices/0100000000000000/up", + "0102030405060708/devices/0100000000000000/up/value", "0102030405060708/devices/0100000000000000/down", - "0102030405060708/devices/0100000000000000/activations", + "0102030405060708/devices/0100000000000000/events/activations", } for _, expected := range expectedList { diff --git a/mqtt/uplink.go b/mqtt/uplink.go new file mode 100644 index 000000000..82ee3a207 --- /dev/null +++ b/mqtt/uplink.go @@ -0,0 +1,117 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "encoding/json" + "fmt" + + MQTT "github.com/eclipse/paho.mqtt.golang" +) + +// UplinkHandler is called for uplink messages +type UplinkHandler func(client Client, appID string, devID string, req UplinkMessage) + +// PublishUplink publishes an uplink message to the MQTT broker +func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { + topic := DeviceTopic{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} + dataUp.AppID = "" + dataUp.DevID = "" + msg, err := json.Marshal(dataUp) + if err != nil { + return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} + } + return c.mqtt.Publish(topic.String(), QoS, false, msg) +} + +// PublishUplinkFields publishes uplink fields to MQTT +func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token { + flattenedFields := make(map[string]interface{}) + flatten("", "/", fields, flattenedFields) + tokens := make([]Token, 0, len(flattenedFields)) + for field, value := range flattenedFields { + topic := DeviceTopic{appID, devID, DeviceUplink, field} + pld, _ := json.Marshal(value) + token := c.mqtt.Publish(topic.String(), QoS, false, pld) + tokens = append(tokens, token) + } + t := newToken() + go func() { + for _, token := range tokens { + token.Wait() + if token.Error() != nil { + fmt.Println(token.Error()) + t.err = token.Error() + } + } + t.flowComplete() + }() + return t +} + +func flatten(prefix, sep string, in, out map[string]interface{}) { + for k, v := range in { + key := prefix + sep + k + if prefix == "" { + key = k + } + out[key] = v + if next, ok := v.(map[string]interface{}); ok { + flatten(key, sep, next, out) + } + } +} + +// SubscribeDeviceUplink subscribes to all uplink messages for the given application and device +func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token { + topic := DeviceTopic{appID, devID, DeviceUplink, ""} + return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { + // Determine the actual topic + topic, err := ParseDeviceTopic(msg.Topic()) + if err != nil { + c.ctx.Warnf("Received message on invalid uplink topic: %s", msg.Topic()) + return + } + + // Unmarshal the payload + dataUp := &UplinkMessage{} + err = json.Unmarshal(msg.Payload(), dataUp) + dataUp.AppID = topic.AppID + dataUp.DevID = topic.DevID + + if err != nil { + c.ctx.Warnf("Could not unmarshal uplink (%s).", err.Error()) + return + } + + // Call the uplink handler + handler(c, topic.AppID, topic.DevID, *dataUp) + }) +} + +// SubscribeAppUplink subscribes to all uplink messages for the given application +func (c *DefaultClient) SubscribeAppUplink(appID string, handler UplinkHandler) Token { + return c.SubscribeDeviceUplink(appID, "", handler) +} + +// SubscribeUplink subscribes to all uplink messages that the current user has access to +func (c *DefaultClient) SubscribeUplink(handler UplinkHandler) Token { + return c.SubscribeDeviceUplink("", "", handler) +} + +// UnsubscribeDeviceUplink unsubscribes from the uplink messages for the given application and device +func (c *DefaultClient) UnsubscribeDeviceUplink(appID string, devID string) Token { + topic := DeviceTopic{appID, devID, DeviceUplink, ""} + return c.unsubscribe(topic.String()) +} + +// UnsubscribeAppUplink unsubscribes from the uplink messages for the given application +func (c *DefaultClient) UnsubscribeAppUplink(appID string) Token { + return c.UnsubscribeDeviceUplink(appID, "") +} + +// UnsubscribeUplink unsubscribes from the uplink messages that the current user has access to +func (c *DefaultClient) UnsubscribeUplink() Token { + return c.UnsubscribeDeviceUplink("", "") +} diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go new file mode 100644 index 000000000..d4cbf45cc --- /dev/null +++ b/mqtt/uplink_test.go @@ -0,0 +1,219 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "fmt" + "strings" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + MQTT "github.com/eclipse/paho.mqtt.golang" + . "github.com/smartystreets/assertions" +) + +// Uplink pub/sub + +func TestPublishUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + dataUp := UplinkMessage{ + AppID: "someid", + DevID: "someid", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + } + + token := c.PublishUplink(dataUp) + waitForOK(token, a) +} + +func TestPublishUplinkFields(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "Test") + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + + c.Connect() + defer c.Disconnect() + + subChan := make(chan bool) + waitChan := make(chan bool) + go func() { + for i := 10; i > 0; i-- { + <-subChan + } + close(subChan) + waitChan <- true + }() + subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { + switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { + case "battery": + a.So(string(msg.Payload()), ShouldEqual, "90") + case "sensors": + a.So(string(msg.Payload()), ShouldContainSubstring, `people":["`) + case "sensors/color": + a.So(string(msg.Payload()), ShouldEqual, `"blue"`) + case "sensors/people": + a.So(string(msg.Payload()), ShouldEqual, `["bob","alice"]`) + case "sensors/water": + a.So(string(msg.Payload()), ShouldEqual, "true") + case "sensors/analog": + a.So(string(msg.Payload()), ShouldEqual, `[0,255,500,1000]`) + case "sensors/history": + a.So(string(msg.Payload()), ShouldContainSubstring, `today":"`) + case "sensors/history/today": + a.So(string(msg.Payload()), ShouldEqual, `"not yet"`) + case "sensors/history/yesterday": + a.So(string(msg.Payload()), ShouldEqual, `"absolutely"`) + case "gps": + a.So(string(msg.Payload()), ShouldEqual, "[52.3736735,4.886663]") + default: + t.Errorf("Should not have received message on topic %s", msg.Topic()) + t.Fail() + } + subChan <- true + }) + waitForOK(subToken, a) + + fields := map[string]interface{}{ + "battery": 90, + "sensors": map[string]interface{}{ + "color": "blue", + "people": []string{"bob", "alice"}, + "water": true, + "analog": []int{0, 255, 500, 1000}, + "history": map[string]interface{}{ + "today": "not yet", + "yesterday": "absolutely", + }, + }, + "gps": []float64{52.3736735, 4.886663}, + } + + pubToken := c.PublishUplinkFields("fields-app", "fields-dev", fields) + waitForOK(pubToken, a) + + select { + case <-waitChan: + case <-time.After(1 * time.Second): + panic("Did not receive fields") + } +} + +func TestSubscribeDeviceUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { + + }) + waitForOK(subToken, a) + + unsubToken := c.UnsubscribeDeviceUplink("someid", "someid") + waitForOK(unsubToken, a) +} + +func TestSubscribeAppUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { + + }) + waitForOK(subToken, a) + + unsubToken := c.UnsubscribeAppUplink("someid") + waitForOK(unsubToken, a) +} + +func TestSubscribeUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + subToken := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { + + }) + waitForOK(subToken, a) + + unsubToken := c.UnsubscribeUplink() + waitForOK(unsubToken, a) +} + +func TestPubSubUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + waitChan := make(chan bool, 1) + + subToken := c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { + a.So(appID, ShouldResemble, "app1") + a.So(devID, ShouldResemble, "dev1") + + waitChan <- true + }) + waitForOK(subToken, a) + + pubToken := c.PublishUplink(UplinkMessage{ + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app1", + DevID: "dev1", + }) + waitForOK(pubToken, a) + + select { + case <-waitChan: + case <-time.After(1 * time.Second): + panic("Did not receive Uplink") + } + + unsubToken := c.UnsubscribeDeviceUplink("app1", "dev1") + waitForOK(unsubToken, a) +} + +func TestPubSubAppUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c.Connect() + defer c.Disconnect() + + var wg WaitGroup + + wg.Add(2) + + subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { + a.So(appID, ShouldResemble, "app2") + a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + wg.Done() + }) + waitForOK(subToken, a) + + pubToken := c.PublishUplink(UplinkMessage{ + AppID: "app2", + DevID: "dev1", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + pubToken = c.PublishUplink(UplinkMessage{ + AppID: "app2", + DevID: "dev2", + Payload: []byte{0x01, 0x02, 0x03, 0x04}, + }) + waitForOK(pubToken, a) + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) + + unsubToken := c.UnsubscribeAppUplink("app1") + waitForOK(unsubToken, a) +} From 37f8ebb3e6f4fb2b8c3f6201efddbe569d39a399 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 15:36:55 +0200 Subject: [PATCH 1800/2266] Publish device events to MQTT - Failed Activations - Failed Uplinks - Failed Downlinks - Scheduled Downlinks - Sent Downlinks - ACKed Downlinks (note that downlinks are still sent as unconfirmed) Closes #159 --- core/handler/activation.go | 28 ++++++++++-------- core/handler/activation_test.go | 1 + core/handler/convert_lorawan.go | 10 +++++++ core/handler/convert_lorawan_test.go | 3 +- core/handler/downlink.go | 43 +++++++++++++++++++++++----- core/handler/downlink_test.go | 2 ++ core/handler/handler.go | 1 + core/handler/mqtt.go | 32 +++++++++++++++++++++ core/handler/uplink.go | 20 +++++++------ core/handler/uplink_test.go | 1 + core/router/activation.go | 3 +- mqtt/README.md | 18 ++++++++++++ 12 files changed, 132 insertions(+), 30 deletions(-) diff --git a/core/handler/activation.go b/core/handler/activation.go index 7fffae53c..d15a58f3d 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -69,7 +69,8 @@ func (h *handler) HandleActivationChallenge(challenge *pb_broker.ActivationChall }, nil } -func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { +func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { + appID, devID := activation.AppId, activation.DevId var appEUI types.AppEUI if activation.AppEui != nil { appEUI = *activation.AppEui @@ -81,13 +82,18 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv ctx := h.Ctx.WithFields(log.Fields{ "DevEUI": devEUI, "AppEUI": appEUI, - "AppID": activation.AppId, - "DevID": activation.DevId, + "AppID": appID, + "DevID": devID, }) - var err error start := time.Now() defer func() { if err != nil { + h.mqttEvent <- &mqttEvent{ + AppID: appID, + DevID: devID, + Type: "activations/errors", + Payload: map[string]string{"error": err.Error()}, + } ctx.WithError(err).Warn("Could not handle activation") } else { ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") @@ -101,13 +107,13 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // Find Device var dev *device.Device - dev, err = h.devices.Get(activation.AppId, activation.DevId) + dev, err = h.devices.Get(appID, devID) if err != nil { return nil, err } if dev.AppKey.IsEmpty() { - err = errors.NewErrNotFound(fmt.Sprintf("AppKey for device %s", activation.DevId)) + err = errors.NewErrNotFound(fmt.Sprintf("AppKey for device %s", devID)) return nil, err } @@ -170,8 +176,8 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv h.mqttActivation <- &mqtt.Activation{ AppEUI: *activation.AppEui, DevEUI: *activation.DevEui, - AppID: activation.AppId, - DevID: activation.DevId, + AppID: appID, + DevID: devID, DevAddr: types.DevAddr(joinAccept.DevAddr), Metadata: mqttMetadata, } @@ -196,9 +202,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv joinAccept.AppNonce = appNonce // Calculate session keys - var appSKey types.AppSKey - var nwkSKey types.NwkSKey - appSKey, nwkSKey, err = otaa.CalculateSessionKeys(dev.AppKey, joinAccept.AppNonce, joinAccept.NetID, reqMAC.DevNonce) + appSKey, nwkSKey, err := otaa.CalculateSessionKeys(dev.AppKey, joinAccept.AppNonce, joinAccept.NetID, reqMAC.DevNonce) if err != nil { return nil, err } @@ -230,7 +234,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv metadata := activation.ActivationMetadata metadata.GetLorawan().NwkSKey = &dev.NwkSKey metadata.GetLorawan().DevAddr = &dev.DevAddr - res := &pb.DeviceActivationResponse{ + res = &pb.DeviceActivationResponse{ Payload: resBytes, DownlinkOption: activation.ResponseTemplate.DownlinkOption, ActivationMetadata: metadata, diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index db1805c36..79e776f9b 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -72,6 +72,7 @@ func TestHandleActivation(t *testing.T) { devices: device.NewDeviceStore(), } h.mqttActivation = make(chan *mqtt.Activation) + h.mqttEvent = make(chan *mqttEvent, 10) var wg sync.WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index f3c573b3a..f43ea9cf6 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -66,6 +66,16 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli return errors.NewErrInvalidArgument("Uplink MACPayload", "could not get frame payload") } + // LoRaWAN: Publish ACKs as events + if macPayload.FHDR.FCtrl.ACK { + h.mqttEvent <- &mqttEvent{ + AppID: appUp.AppID, + DevID: appUp.DevID, + Type: "down/acks", + Payload: map[string]interface{}{}, + } + } + return nil } diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index 7e3d18646..45cdac497 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -36,12 +36,13 @@ func TestConvertFromLoRaWAN(t *testing.T) { h := &handler{ devices: device.NewDeviceStore(), Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, + mqttEvent: make(chan *mqttEvent, 10), } h.devices.Set(&device.Device{ DevID: "devid", AppID: "appid", }) - ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x01, 0x46, 0x55, 0x23, 0xf4, 0xf8, 0x45}) + ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x20, 0x01, 0x00, 0x0A, 0x46, 0x55, 0x96, 0x42, 0x92, 0xF2}) err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.Payload, ShouldResemble, []byte{0xaa, 0xbc}) diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 6ee2f84b0..51c94fa74 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -13,9 +13,11 @@ import ( ) func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { + appID, devID := appDownlink.AppID, appDownlink.DevID + ctx := h.Ctx.WithFields(log.Fields{ - "DevID": appDownlink.DevID, - "AppID": appDownlink.AppID, + "AppID": appID, + "DevID": devID, }) var err error start := time.Now() @@ -28,7 +30,7 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { }() var dev *device.Device - dev, err = h.devices.Get(appDownlink.AppID, appDownlink.DevID) + dev, err = h.devices.Get(appID, devID) if err != nil { return err } @@ -41,20 +43,35 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { return err } + h.mqttEvent <- &mqttEvent{ + AppID: appID, + DevID: devID, + Type: "down/scheduled", + Payload: appDownlink, + } + return nil } func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb_broker.DownlinkMessage) error { + appID, devID := appDownlink.AppID, appDownlink.DevID + ctx := h.Ctx.WithFields(log.Fields{ - "DevID": appDownlink.AppID, - "AppID": appDownlink.DevID, - "DevEUI": downlink.AppEui, - "AppEUI": downlink.DevEui, + "AppID": appID, + "DevID": devID, + "AppEUI": downlink.AppEui, + "DevEUI": downlink.DevEui, }) var err error defer func() { if err != nil { + h.mqttEvent <- &mqttEvent{ + AppID: appID, + DevID: devID, + Type: "down/errors", + Payload: map[string]string{"error": err.Error()}, + } ctx.WithError(err).Warn("Could not handle downlink") } }() @@ -82,5 +99,17 @@ func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb h.downlink <- downlink + appDownlinkCopy := *appDownlink + appDownlinkCopy.AppID = "" + appDownlinkCopy.DevID = "" + appDownlinkCopy.Fields = make(map[string]interface{}) + + h.mqttEvent <- &mqttEvent{ + AppID: appDownlink.AppID, + DevID: appDownlink.DevID, + Type: "down/sent", + Payload: appDownlinkCopy, + } + return nil } diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 9f5203053..9bfc742be 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -24,6 +24,7 @@ func TestEnqueueDownlink(t *testing.T) { h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, devices: device.NewDeviceStore(), + mqttEvent: make(chan *mqttEvent, 10), } err := h.EnqueueDownlink(&mqtt.DownlinkMessage{ AppID: appID, @@ -62,6 +63,7 @@ func TestHandleDownlink(t *testing.T) { devices: device.NewDeviceStore(), applications: application.NewApplicationStore(), downlink: make(chan *pb_broker.DownlinkMessage), + mqttEvent: make(chan *mqttEvent, 10), } // Neither payload nor Fields provided : ERROR err = h.HandleDownlink(&mqtt.DownlinkMessage{ diff --git a/core/handler/handler.go b/core/handler/handler.go index 316743135..bbe5d00bd 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -62,6 +62,7 @@ type handler struct { mqttUp chan *mqtt.UplinkMessage mqttActivation chan *mqtt.Activation + mqttEvent chan *mqttEvent } func (h *handler) Init(c *core.Component) error { diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 423dcf359..4fb953259 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -10,6 +10,13 @@ import ( "github.com/apex/log" ) +type mqttEvent struct { + AppID string + DevID string + Type string + Payload interface{} +} + // MQTTTimeout indicates how long we should wait for an MQTT publish var MQTTTimeout = 2 * time.Second @@ -26,6 +33,7 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e h.mqttUp = make(chan *mqtt.UplinkMessage, MQTTBufferSize) h.mqttActivation = make(chan *mqtt.Activation, MQTTBufferSize) + h.mqttEvent = make(chan *mqttEvent, MQTTBufferSize) token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appID string, devID string, msg mqtt.DownlinkMessage) { down := &msg @@ -91,5 +99,29 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e } }() + go func() { + for event := range h.mqttEvent { + h.Ctx.WithFields(log.Fields{ + "DevID": event.DevID, + "AppID": event.AppID, + }).Debug("Publish Event") + var token mqtt.Token + if event.DevID == "" { + token = h.mqttClient.PublishAppEvent(event.AppID, event.Type, event.Payload) + } else { + token = h.mqttClient.PublishDeviceEvent(event.AppID, event.DevID, event.Type, event.Payload) + } + go func() { + if token.WaitTimeout(MQTTTimeout) { + if token.Error() != nil { + h.Ctx.WithError(token.Error()).Warn("Could not publish Event") + } + } else { + h.Ctx.Warn("Event publish timeout") + } + }() + } + }() + return nil } diff --git a/core/handler/uplink.go b/core/handler/uplink.go index 476981c76..dd0006189 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -4,7 +4,6 @@ package handler import ( - "fmt" "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" @@ -15,15 +14,21 @@ import ( // ResponseDeadline indicates how long var ResponseDeadline = 100 * time.Millisecond -func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error { +func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err error) { + appID, devID := uplink.AppId, uplink.DevId ctx := h.Ctx.WithFields(log.Fields{ - "AppID": uplink.AppId, - "DevID": uplink.DevId, + "AppID": appID, + "DevID": devID, }) - var err error start := time.Now() defer func() { if err != nil { + h.mqttEvent <- &mqttEvent{ + AppID: appID, + DevID: devID, + Type: "up/errors", + Payload: map[string]string{"error": err.Error()}, + } ctx.WithError(err).Warn("Could not handle uplink") } else { ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") @@ -32,8 +37,8 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro // Build AppUplink appUplink := &mqtt.UplinkMessage{ - AppID: uplink.AppId, - DevID: uplink.DevId, + AppID: appID, + DevID: devID, } // Get Uplink Processors @@ -52,7 +57,6 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) erro err = nil return nil } else if err != nil { - fmt.Println(err) return err } } diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index cdc9b481e..55c5e340b 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -42,6 +42,7 @@ func TestHandleUplink(t *testing.T) { AppID: appID, }) h.mqttUp = make(chan *mqtt.UplinkMessage) + h.mqttEvent = make(chan *mqttEvent, 10) h.downlink = make(chan *pb_broker.DownlinkMessage) uplink, _ := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0xDA, 0x23, 0x99, 0x61, 0xD4}) diff --git a/core/router/activation.go b/core/router/activation.go index 65f026638..ec199207c 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -16,13 +16,12 @@ import ( "github.com/apex/log" ) -func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { +func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { ctx := r.Ctx.WithFields(log.Fields{ "GatewayID": gatewayID, "AppEUI": *activation.AppEui, "DevEUI": *activation.DevEui, }) - var err error start := time.Now() defer func() { if err != nil { diff --git a/mqtt/README.md b/mqtt/README.md index e508a7309..00d6148c0 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -198,3 +198,21 @@ if err := token.Error(); err != nil { ctx.WithError(err).Fatal("Could not subscribe") } ``` + +## Device Events + +### Downlink Events + +* Downlink Scheduled: `/devices//events/down/scheduled` (payload: the message - see **Downlink Messages**) +* Downlink Sent: `/devices//events/down/sent` (payload: the message - see **Downlink Messages**) +* Acknowledgements: `/devices//events/ack` (payload: `{}`) + +### Error Events + +The payload of error events is a JSON object with the error's description. + +* Uplink Errors: `/devices//events/up/errors` +* Downlink Errors: `/devices//events/down/errors` +* Activation Errors: `/devices//events/activations/errors` + +Example: `{"error":"Activation DevNonce not valid: already used"}` From 31ebdf7b7f01669d4c9c78c2d3bc341469b0f683 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 15:47:29 +0200 Subject: [PATCH 1801/2266] Fix #250 --- core/broker/uplink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index a1d6a3d78..caca77c00 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -89,7 +89,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) if candidate.Uses32BitFCnt { - macPayload.FHDR.FCnt = fcnt.GetFull(macPayload.FHDR.FCnt, uint16(candidate.FCntUp)) + macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCntUp, uint16(macPayload.FHDR.FCnt)) } micChecks++ ok, err = phyPayload.ValidateMIC(nwkSKey) From 62ae27ef4c5067843e0a461b1339fe895374c8a0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 16:06:48 +0200 Subject: [PATCH 1802/2266] Update AUTHORS --- AUTHORS | 5 ++++- ttnctl/AUTHORS | 13 ------------- ttnctl/LICENSE | 21 --------------------- ttnctl/README.md | 4 ++++ 4 files changed, 8 insertions(+), 35 deletions(-) delete mode 100644 ttnctl/AUTHORS delete mode 100644 ttnctl/LICENSE diff --git a/AUTHORS b/AUTHORS index 3bdfaf13c..9996c61fe 100644 --- a/AUTHORS +++ b/AUTHORS @@ -8,7 +8,10 @@ # # Please keep the list sorted. +Antoine Rondelet +Fokke Zandbergen Hylke Visser Johan Stokking Matthias Benkort -Romans Volosatovs +Roman Volosatovs +Romeo Van Snick diff --git a/ttnctl/AUTHORS b/ttnctl/AUTHORS deleted file mode 100644 index 624e22ba1..000000000 --- a/ttnctl/AUTHORS +++ /dev/null @@ -1,13 +0,0 @@ -# This is the official list of The Things Network authors for copyright purposes. -# -# The copyright owners listed in this document agree to release their work under -# the MIT license that can be found in the LICENSE file. -# -# Names should be added to this file as -# Firstname Lastname -# -# Please keep the list sorted. - -Hylke Visser -Johan Stokking -Romans Volosatovs diff --git a/ttnctl/LICENSE b/ttnctl/LICENSE deleted file mode 100644 index 31c9aa88f..000000000 --- a/ttnctl/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 The Things Network - -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/ttnctl/README.md b/ttnctl/README.md index 58f326e41..ec4b1fbb4 100644 --- a/ttnctl/README.md +++ b/ttnctl/README.md @@ -28,3 +28,7 @@ The following configuration options can be set: ## Development **Configuration for Development:** Copy `../.env/ttnctl.yaml.dev-example` to `~/.ttnctl.yaml` + +## License + +Source code for The Things Network is released under the MIT License, which can be found in the [LICENSE](../LICENSE) file. A list of authors can be found in the [AUTHORS](../AUTHORS) file. From c5c9068729c95d832be697a1b85a3897242b35c8 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 22 Sep 2016 22:20:38 +0200 Subject: [PATCH 1803/2266] Update AUTHORS (#269) --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 9996c61fe..b85c1a113 100644 --- a/AUTHORS +++ b/AUTHORS @@ -13,5 +13,5 @@ Fokke Zandbergen Hylke Visser Johan Stokking Matthias Benkort -Roman Volosatovs +Roman Volosatovs Romeo Van Snick From efe435d77aa3761133d5ec7092574bd4e3aed41e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 16:21:23 +0200 Subject: [PATCH 1804/2266] rename toa.Compute to toa.ComputeLoRa --- core/router/gateway/schedule.go | 2 +- core/router/gateway/utilization.go | 4 ++-- utils/toa/toa.go | 8 ++++---- utils/toa/toa_test.go | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 79d5ab3a4..90cc0a44a 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -165,7 +165,7 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro if item, ok := s.items[id]; ok { item.payload = downlink if lora := downlink.GetProtocolConfiguration().GetLorawan(); lora != nil { - time, _ := toa.Compute( + time, _ := toa.ComputeLoRa( uint(len(downlink.Payload)), lora.DataRate, lora.CodingRate, diff --git a/core/router/gateway/utilization.go b/core/router/gateway/utilization.go index b6145ff9c..8a5749507 100644 --- a/core/router/gateway/utilization.go +++ b/core/router/gateway/utilization.go @@ -66,7 +66,7 @@ func (u *utilization) AddRx(uplink *pb_router.UplinkMessage) error { var t time.Duration var err error if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { - t, err = toa.Compute(uint(len(uplink.Payload)), lorawan.DataRate, lorawan.CodingRate) + t, err = toa.ComputeLoRa(uint(len(uplink.Payload)), lorawan.DataRate, lorawan.CodingRate) if err != nil { return err } @@ -89,7 +89,7 @@ func (u *utilization) AddTx(downlink *pb_router.DownlinkMessage) error { var t time.Duration var err error if lorawan := downlink.ProtocolConfiguration.GetLorawan(); lorawan != nil { - t, err = toa.Compute(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) + t, err = toa.ComputeLoRa(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) if err != nil { return err } diff --git a/utils/toa/toa.go b/utils/toa/toa.go index 1a83338e9..decda4570 100644 --- a/utils/toa/toa.go +++ b/utils/toa/toa.go @@ -11,12 +11,12 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -// Compute the time-on-air given a PHY payload size in bytes, a datr identifier, -// an LoRa coding rate identifier. Note that this function operates on the PHY -// payload size and does not add the LoRaWAN header. +// ComputeLoRa computes the time-on-air given a PHY payload size in bytes, a datr +// identifier and LoRa coding rate identifier. Note that this function operates +// on the PHY payload size and does not add the LoRaWAN header. // // See http://www.semtech.com/images/datasheet/LoraDesignGuide_STD.pdf, page 7 -func Compute(payloadSize uint, datr string, codr string) (time.Duration, error) { +func ComputeLoRa(payloadSize uint, datr string, codr string) (time.Duration, error) { // Determine CR var cr float64 switch codr { diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go index 78e18e9ae..bf298f77d 100644 --- a/utils/toa/toa_test.go +++ b/utils/toa/toa_test.go @@ -10,7 +10,7 @@ import ( . "github.com/smartystreets/assertions" ) -func TestCompute(t *testing.T) { +func TestComputeLoRa(t *testing.T) { a := New(t) var toa time.Duration @@ -26,7 +26,7 @@ func TestCompute(t *testing.T) { "SF12BW125": 991232, } for dr, us := range sfTests { - toa, err = Compute(10, dr, "4/5") + toa, err = ComputeLoRa(10, dr, "4/5") a.So(err, ShouldBeNil) a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) } @@ -38,7 +38,7 @@ func TestCompute(t *testing.T) { "SF7BW500": 10304, } for dr, us := range bwTests { - toa, err = Compute(10, dr, "4/5") + toa, err = ComputeLoRa(10, dr, "4/5") a.So(err, ShouldBeNil) a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) } @@ -51,7 +51,7 @@ func TestCompute(t *testing.T) { "4/8": 53504, } for cr, us := range crTests { - toa, err = Compute(10, "SF7BW125", cr) + toa, err = ComputeLoRa(10, "SF7BW125", cr) a.So(err, ShouldBeNil) a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) } @@ -67,7 +67,7 @@ func TestCompute(t *testing.T) { 19: 51456, } for size, us := range plTests { - toa, err = Compute(size, "SF7BW125", "4/5") + toa, err = ComputeLoRa(size, "SF7BW125", "4/5") a.So(err, ShouldBeNil) a.So(toa, ShouldAlmostEqual, time.Duration(us)*time.Microsecond) } From 03e9285b2b80d2c2d82d3fc98865042812f072de Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 22 Sep 2016 16:34:10 +0200 Subject: [PATCH 1805/2266] Add ComputeFSK to toa package --- core/router/downlink.go | 2 +- utils/toa/toa.go | 10 ++++++++++ utils/toa/toa_test.go | 8 ++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index d396596b9..60852b898 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -256,7 +256,7 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o for _, option := range options { // Calculate max ToA - time, _ := toa.Compute( + time, _ := toa.ComputeLoRa( 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? option.GetProtocolConfig().GetLorawan().DataRate, option.GetProtocolConfig().GetLorawan().CodingRate, diff --git a/utils/toa/toa.go b/utils/toa/toa.go index decda4570..6553ef6f5 100644 --- a/utils/toa/toa.go +++ b/utils/toa/toa.go @@ -53,3 +53,13 @@ func ComputeLoRa(payloadSize uint, datr string, codr string) (time.Duration, err return time.Duration(timeOnAir), nil } + +// ComputeFSK computes the time-on-air given a PHY payload size in bytes and a +// bitrate, Note that this function operates on the PHY payload size and does +// not add the LoRaWAN header. +// +// TODO: (@tftelkamp): Verify this +func ComputeFSK(payloadSize uint, bitrate int) (time.Duration, error) { + tPkt := int(time.Second) * (int(payloadSize) + 5 + 3 + 1 + 2) * 8 / bitrate + return time.Duration(tPkt), nil +} diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go index bf298f77d..7daed8621 100644 --- a/utils/toa/toa_test.go +++ b/utils/toa/toa_test.go @@ -73,3 +73,11 @@ func TestComputeLoRa(t *testing.T) { } } + +// TODO: (@tftelkamp): Verify this +func TestComputeFSK(t *testing.T) { + a := New(t) + toa, err := ComputeFSK(200, 50000) + a.So(err, ShouldBeNil) + a.So(toa, ShouldAlmostEqual, 33760*time.Microsecond) +} From cdaaaf37b301098b4019e8b53fd101bd1274b76f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 23 Sep 2016 09:12:45 +0200 Subject: [PATCH 1806/2266] Add FSK modulation to Router --- core/router/downlink.go | 87 +++++++++++++++++++++++++----- core/router/gateway/schedule.go | 25 ++++++--- core/router/gateway/utilization.go | 29 +++++++--- 3 files changed, 115 insertions(+), 26 deletions(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index 60852b898..fb29b8750 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "strings" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -149,11 +150,25 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo return // We can't handle this region } - dr, err := types.ParseDataRate(lorawanMetadata.DataRate) - if err != nil { - return // Invalid packet, probably won't happen if the gateway is just doing its job + var dataRate lora.DataRate + + // LORA Modulation + if lorawanMetadata.Modulation == pb_lorawan.Modulation_LORA { + dataRate.Modulation = lora.LoRaModulation + dr, err := types.ParseDataRate(lorawanMetadata.DataRate) + if err != nil { + return // Invalid packet, probably won't happen if the gateway is just doing its job + } + dataRate.Bandwidth = int(dr.Bandwidth) + dataRate.SpreadFactor = int(dr.SpreadingFactor) + } + + if lorawanMetadata.Modulation == pb_lorawan.Modulation_FSK { + dataRate.Modulation = lora.FSKModulation + dataRate.BitRate = int(lorawanMetadata.BitRate) } - uplinkDRIndex, err := band.GetDataRate(lora.DataRate{Modulation: lora.LoRaModulation, SpreadFactor: int(dr.SpreadingFactor), Bandwidth: int(dr.Bandwidth)}) + + uplinkDRIndex, err := band.GetDataRate(dataRate) if err != nil { return // Invalid packet, probably won't happen if the gateway is just doing its job } @@ -176,7 +191,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo rx2 := &pb_broker.DownlinkOption{ GatewayId: gateway.ID, ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ - Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa + Modulation: pb_lorawan.Modulation_LORA, // RX2 is always LoRa DataRate: dataRate.String(), // This is default CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx }}}, @@ -198,7 +213,24 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo downlinkChannel := band.DownlinkChannels[band.GetRX1Channel(uplinkChannel)] downlinkDRIndex, err := band.GetRX1DataRateForOffset(uplinkDRIndex, 0) if err == nil { - dataRate, _ := types.ConvertDataRate(band.DataRates[downlinkDRIndex]) + var modulation pb_lorawan.Modulation + var dataRateString string + var bitRate int + var frequencyDeviation int + + dr := band.DataRates[downlinkDRIndex] + if dr.Modulation == lora.LoRaModulation { + modulation = pb_lorawan.Modulation_LORA + dataRate, _ := types.ConvertDataRate(dr) + dataRateString = dataRate.String() + } + + if dr.Modulation == lora.FSKModulation { + modulation = pb_lorawan.Modulation_FSK + bitRate = dr.BitRate + frequencyDeviation = bitRate / 2 + } + delay := band.ReceiveDelay1 if isActivation { delay = band.JoinAcceptDelay1 @@ -206,8 +238,9 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo rx1 := &pb_broker.DownlinkOption{ GatewayId: gateway.ID, ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ - Modulation: pb_lorawan.Modulation_LORA, // We only support LoRa - DataRate: dataRate.String(), // This is default + Modulation: modulation, + DataRate: dataRateString, + BitRate: uint32(bitRate), CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx }}}, GatewayConfig: &pb_gateway.TxConfiguration{ @@ -216,6 +249,7 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo PolarizationInversion: true, Frequency: uint64(downlinkChannel.Frequency), Power: int32(band.DefaultTXPower), + FrequencyDeviation: uint32(frequencyDeviation), }, } options = append(options, rx1) @@ -255,12 +289,37 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o gatewayRx, _ := gateway.Utilization.Get() for _, option := range options { - // Calculate max ToA - time, _ := toa.ComputeLoRa( - 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? - option.GetProtocolConfig().GetLorawan().DataRate, - option.GetProtocolConfig().GetLorawan().CodingRate, - ) + // Invalid if no LoRaWAN + lorawan := option.GetProtocolConfig().GetLorawan() + if lorawan == nil { + option.Score = 1000 + continue + } + + var time time.Duration + + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + // Calculate max ToA + time, _ = toa.ComputeLoRa( + 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? + lorawan.DataRate, + lorawan.CodingRate, + ) + } + + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + // Calculate max ToA + time, _ = toa.ComputeFSK( + 51+13, // Max MACPayload plus LoRaWAN header, TODO: What is the length we should use? + int(lorawan.BitRate), + ) + } + + // Invalid if time is zero + if time == 0 { + option.Score = 1000 + continue + } timeScore := math.Min(time.Seconds()*5, 10) // 2 seconds will be 10 (max) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 90cc0a44a..d89ce9aff 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -9,6 +9,7 @@ import ( "sync/atomic" "time" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" router_pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/random" @@ -164,12 +165,24 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro defer s.Unlock() if item, ok := s.items[id]; ok { item.payload = downlink - if lora := downlink.GetProtocolConfiguration().GetLorawan(); lora != nil { - time, _ := toa.ComputeLoRa( - uint(len(downlink.Payload)), - lora.DataRate, - lora.CodingRate, - ) + + if lorawan := downlink.GetProtocolConfiguration().GetLorawan(); lorawan != nil { + var time time.Duration + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + // Calculate max ToA + time, _ = toa.ComputeLoRa( + uint(len(downlink.Payload)), + lorawan.DataRate, + lorawan.CodingRate, + ) + } + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + // Calculate max ToA + time, _ = toa.ComputeFSK( + uint(len(downlink.Payload)), + int(lorawan.BitRate), + ) + } item.length = uint32(time / 1000) } diff --git a/core/router/gateway/utilization.go b/core/router/gateway/utilization.go index 8a5749507..88bcd8e7c 100644 --- a/core/router/gateway/utilization.go +++ b/core/router/gateway/utilization.go @@ -8,6 +8,7 @@ import ( "sync" "time" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb_router "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/toa" "github.com/rcrowley/go-metrics" @@ -66,9 +67,17 @@ func (u *utilization) AddRx(uplink *pb_router.UplinkMessage) error { var t time.Duration var err error if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { - t, err = toa.ComputeLoRa(uint(len(uplink.Payload)), lorawan.DataRate, lorawan.CodingRate) - if err != nil { - return err + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + t, err = toa.ComputeLoRa(uint(len(uplink.Payload)), lorawan.DataRate, lorawan.CodingRate) + if err != nil { + return err + } + } + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + t, err = toa.ComputeFSK(uint(len(uplink.Payload)), int(lorawan.BitRate)) + if err != nil { + return err + } } } if t == 0 { @@ -89,9 +98,17 @@ func (u *utilization) AddTx(downlink *pb_router.DownlinkMessage) error { var t time.Duration var err error if lorawan := downlink.ProtocolConfiguration.GetLorawan(); lorawan != nil { - t, err = toa.ComputeLoRa(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) - if err != nil { - return err + if lorawan.Modulation == pb_lorawan.Modulation_LORA { + t, err = toa.ComputeLoRa(uint(len(downlink.Payload)), lorawan.DataRate, lorawan.CodingRate) + if err != nil { + return err + } + } + if lorawan.Modulation == pb_lorawan.Modulation_FSK { + t, err = toa.ComputeFSK(uint(len(downlink.Payload)), int(lorawan.BitRate)) + if err != nil { + return err + } } } if t == 0 { From a9772cdc7ef2733b2b2fe28df1fc84be7d333e69 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 23 Sep 2016 16:33:25 +0200 Subject: [PATCH 1807/2266] Version label --- main.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.go b/main.go index c90c1c45a..8aa661707 100644 --- a/main.go +++ b/main.go @@ -9,12 +9,13 @@ import ( ) var ( + version = "2.0.0-dev" gitCommit = "unknown" buildDate = "unknown" ) func main() { - viper.Set("version", "2.0.0-dev") + viper.Set("version", version) viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() From 557c60851e2ae6e94d6105829fb7213ab7d3894c Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 27 Sep 2016 09:49:06 +0200 Subject: [PATCH 1808/2266] Update payload function placeholders As per https://github.com/TheThingsIndustries/dashboard/issues/90#issuecomment-249515032 --- ttnctl/cmd/applications_pf_set.go | 61 ++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index c230559bf..7df63a674 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -24,17 +24,18 @@ The functions are read from the supplied file or from STDIN.`, INFO Discovering Handler... INFO Connecting with Handler... function Decoder(bytes) { - // Here you can decode the payload into json. - // bytes is of type Buffer. - // todo: return an object - return { - payload: bytes, - }; + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; + + decoded.isLightOn = bytes[0]; + + return decoded; } ########## Write your Decoder here and end with Ctrl+D (EOF): function Decoder(bytes) { return { - payload: bytes, + isLightOn: bytes[0] }; } INFO Updated application AppID=test @@ -81,40 +82,56 @@ function Decoder(bytes) { switch function { case "decoder": fmt.Println(`function Decoder(bytes) { - // Decode an uplink message from - // a Buffer of bytes to an object. + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; - return { - isLightOn: bytes[0] - }; + decoded.isLightOn = bytes[0]; + + return decoded; } ########## Write your Decoder here and end with Ctrl+D (EOF):`) app.Decoder = readFunction() case "converter": - fmt.Println(`function Converter(decodedObj) { - // Modify the decoded uplink message. + fmt.Println(`function Converter(decoded) { + // Merge, split or otherwise + // mutate decoded fields. + var converted = decoded; - decodedObj.isLightOn = !!decodedObj.isLightOn; + if (converted.isLightOn === 0) { + converted.isLightOn = false; + } - return decodedObj; + if (converted.isLightOn === 1) { + converted.isLightOn = true; + } + + return converted; } ########## Write your Converter here and end with Ctrl+D (EOF):`) app.Converter = readFunction() case "validator": - fmt.Println(`function Validator(convertedObj) { - // Return false if the decoded and converted uplink + fmt.Println(`function Validator(converted) { + // Return false if the decoded, converted // message is invalid and should be dropped. + if (converted.isLightOn !== true && converted.isLightOn !== false) { + return false; + } + return true; } ########## Write your Validator here and end with Ctrl+D (EOF):`) app.Validator = readFunction() case "encoder": - fmt.Println(`function Encoder(obj) { - // Convert uplink messages sent as object to - // an array of bytes. + fmt.Println(`function Encoder(object) { + // Encode downlink messages sent as + // object to an array or buffer of bytes. + var bytes = []; + + bytes[0] = object.turnLightOn ? 1 : 0; - return [ obj.turnLightOn ? 1 : 0 ]; + return bytes; } ########## Write your Encoder here and end with Ctrl+D (EOF):`) app.Encoder = readFunction() From a8793e90eafd0516fac03c71f3645b574ae1c6aa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 27 Sep 2016 14:32:24 +0200 Subject: [PATCH 1809/2266] Add Router Client --- api/discovery/client.go | 1 - api/logging.go | 41 ++++++ api/router/client.go | 307 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 348 insertions(+), 1 deletion(-) create mode 100644 api/logging.go create mode 100644 api/router/client.go diff --git a/api/discovery/client.go b/api/discovery/client.go index 43bb4a4a3..1689e0c8c 100644 --- a/api/discovery/client.go +++ b/api/discovery/client.go @@ -3,7 +3,6 @@ package discovery -// Client is used to manage applications and devices on a handler import ( "fmt" "sync" diff --git a/api/logging.go b/api/logging.go new file mode 100644 index 000000000..c1c4e45de --- /dev/null +++ b/api/logging.go @@ -0,0 +1,41 @@ +package api + +// Logger used in mqtt package +type Logger interface { + Debug(msg string) + Info(msg string) + Warn(msg string) + Error(msg string) + Fatal(msg string) + Debugf(msg string, v ...interface{}) + Infof(msg string, v ...interface{}) + Warnf(msg string, v ...interface{}) + Errorf(msg string, v ...interface{}) + Fatalf(msg string, v ...interface{}) +} + +var logger Logger = noopLogger{} + +// GetLogger returns the API Logger +func GetLogger() Logger { + return logger +} + +// SetLogger returns the API Logger +func SetLogger(log Logger) { + logger = log +} + +// noopLogger just does nothing +type noopLogger struct{} + +func (l noopLogger) Debug(msg string) {} +func (l noopLogger) Info(msg string) {} +func (l noopLogger) Warn(msg string) {} +func (l noopLogger) Error(msg string) {} +func (l noopLogger) Fatal(msg string) {} +func (l noopLogger) Debugf(msg string, v ...interface{}) {} +func (l noopLogger) Infof(msg string, v ...interface{}) {} +func (l noopLogger) Warnf(msg string, v ...interface{}) {} +func (l noopLogger) Errorf(msg string, v ...interface{}) {} +func (l noopLogger) Fatalf(msg string, v ...interface{}) {} diff --git a/api/router/client.go b/api/router/client.go new file mode 100644 index 000000000..1dbdf92e3 --- /dev/null +++ b/api/router/client.go @@ -0,0 +1,307 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "io" + "sync" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/errors" + + "golang.org/x/net/context" + + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +// GatewayClient is used as the main client for Gateways to communicate with the Router +type GatewayClient interface { + SendGatewayStatus(*gateway.Status) error + SendUplink(*UplinkMessage) error + Subscribe() (<-chan *DownlinkMessage, <-chan error, error) + Unsbscribe() error + Activate(*DeviceActivationRequest) (*DeviceActivationResponse, error) + Close() error +} + +// NewClient returns a new Client +func NewClient(routerAnnouncement *discovery.Announcement) (*Client, error) { + conn, err := routerAnnouncement.Dial() + if err != nil { + return nil, err + } + client := &Client{ + conn: conn, + client: NewRouterClient(conn), + } + return client, nil +} + +// Client is a wrapper around RouterClient +type Client struct { + mutex sync.Mutex + conn *grpc.ClientConn + client RouterClient + gateways []GatewayClient +} + +// ForGateway returns a GatewayClient that is configured for the provided gateway +func (c *Client) ForGateway(gatewayID string, tokenFunc func() string) GatewayClient { + defer c.mutex.Unlock() + c.mutex.Lock() + gatewayClient := NewGatewayClient(c.client, gatewayID, tokenFunc) + c.gateways = append(c.gateways, gatewayClient) + return gatewayClient +} + +// Close purges the cache and closes the connection with the Router +func (c *Client) Close() error { + defer c.mutex.Unlock() + c.mutex.Lock() + for _, gateway := range c.gateways { + gateway.Close() + } + return c.conn.Close() +} + +// NewGatewayClient returns a new GatewayClient +func NewGatewayClient(client RouterClient, gatewayID string, tokenFunc func() string) GatewayClient { + gatewayClient := &gatewayClient{ + client: client, + id: gatewayID, + tokenFunc: tokenFunc, + } + return gatewayClient +} + +type gatewayClient struct { + mutex sync.Mutex + id string + tokenFunc func() string + client RouterClient + gatewayStatus Router_GatewayStatusClient + stopGatewayStatus chan bool + uplink Router_UplinkClient + stopUplink chan bool + downlink Router_SubscribeClient + stopDownlink chan bool +} + +func (c *gatewayClient) getContext() context.Context { + md := metadata.Pairs( + "id", c.id, + "token", c.tokenFunc(), + ) + gatewayContext := metadata.NewContext(context.Background(), md) + return gatewayContext +} + +func (c *gatewayClient) setupGatewayStatus() error { + api.GetLogger().Debugf("Setting up gateway status stream for %s...", c.id) + gatewayStatusClient, err := c.client.GatewayStatus(c.getContext()) + if err != nil { + return err + } + c.gatewayStatus = gatewayStatusClient + c.stopGatewayStatus = make(chan bool) + go func() { + var msg interface{} + for { + select { + case <-c.stopGatewayStatus: + return + default: + if err := gatewayStatusClient.RecvMsg(msg); err != nil { + defer c.mutex.Unlock() + c.mutex.Lock() + api.GetLogger().Warnf("Error in gateway status stream for %s: %s", c.id, err.Error()) + c.teardownGatewayStatus() + return + } + api.GetLogger().Debugf("Received: %v", msg) + } + } + }() + return nil +} + +func (c *gatewayClient) teardownGatewayStatus() { + if c.gatewayStatus != nil { + api.GetLogger().Debugf("Closing gateway status stream for %s...", c.id) + close(c.stopGatewayStatus) + c.gatewayStatus.CloseSend() + c.gatewayStatus = nil + } +} + +func (c *gatewayClient) SendGatewayStatus(status *gateway.Status) error { + defer c.mutex.Unlock() + c.mutex.Lock() + if c.gatewayStatus == nil { + if err := c.setupGatewayStatus(); err != nil { + return errors.FromGRPCError(err) + } + } + if err := c.gatewayStatus.Send(status); err != nil { + if err == io.EOF { + api.GetLogger().Warnf("Could not send gateway status for %s on closed stream", c.id) + c.teardownGatewayStatus() + return errors.FromGRPCError(err) + } + api.GetLogger().Warnf("Error sending gateway status for %s: %s", c.id, err.Error()) + return errors.FromGRPCError(err) + } + return nil +} + +func (c *gatewayClient) setupUplink() error { + api.GetLogger().Debugf("Setting up uplink stream for %s...", c.id) + uplinkClient, err := c.client.Uplink(c.getContext()) + if err != nil { + return err + } + c.uplink = uplinkClient + c.stopUplink = make(chan bool) + go func() { + var msg interface{} + for { + select { + case <-c.stopUplink: + return + default: + if err := uplinkClient.RecvMsg(msg); err != nil { + defer c.mutex.Unlock() + c.mutex.Lock() + api.GetLogger().Warnf("Error in uplink stream for %s: %s", c.id, err.Error()) + c.teardownUplink() + return + } + api.GetLogger().Debugf("Received: %v", msg) + + } + } + }() + return nil +} + +func (c *gatewayClient) teardownUplink() { + if c.uplink != nil { + api.GetLogger().Debugf("Closing uplink stream for %s...", c.id) + close(c.stopUplink) + c.uplink.CloseSend() + c.uplink = nil + } +} + +func (c *gatewayClient) SendUplink(uplink *UplinkMessage) error { + defer c.mutex.Unlock() + c.mutex.Lock() + if c.uplink == nil { + if err := c.setupUplink(); err != nil { + return errors.FromGRPCError(err) + } + } + if err := c.uplink.Send(uplink); err != nil { + if err == io.EOF { + api.GetLogger().Warnf("Could not send uplink for %s on closed stream", c.id) + c.teardownUplink() + return errors.FromGRPCError(err) + } + api.GetLogger().Warnf("Error sending uplink for %s: %s", c.id, err.Error()) + return errors.FromGRPCError(err) + } + return nil +} + +func (c *gatewayClient) setupDownlink() error { + api.GetLogger().Debugf("Setting up downlink stream for %s...", c.id) + ctx, cancel := context.WithCancel(c.getContext()) + downlinkClient, err := c.client.Subscribe(ctx, &SubscribeRequest{}) + if err != nil { + return err + } + c.stopDownlink = make(chan bool) + go func() { + <-c.stopDownlink + cancel() + }() + c.downlink = downlinkClient + return nil +} + +func (c *gatewayClient) teardownDownlink() { + if c.downlink != nil { + api.GetLogger().Debugf("Closing downlink stream for %s...", c.id) + c.downlink.CloseSend() + c.downlink = nil + } +} + +func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, error) { + defer c.mutex.Unlock() + c.mutex.Lock() + if c.downlink == nil { + if err := c.setupDownlink(); err != nil { + return nil, nil, errors.FromGRPCError(err) + } + } + downChan := make(chan *DownlinkMessage) + errChan := make(chan error) + go func() { + defer func() { + close(downChan) + close(errChan) + c.teardownDownlink() + }() + for { + select { + case <-c.stopDownlink: + return + default: + downlink, err := c.downlink.Recv() + if err != nil { + errChan <- errors.FromGRPCError(err) + if grpc.Code(err) == codes.Canceled { + api.GetLogger().Warnf("Downlink stream for %s was canceled", c.id) + return + } + api.GetLogger().Warnf("Error receiving gateway downlink for %s: %s", c.id, err.Error()) + } else { + downChan <- downlink + } + + } + } + }() + return downChan, errChan, nil +} + +func (c *gatewayClient) Unsbscribe() error { + close(c.stopDownlink) + return nil +} + +func (c *gatewayClient) Activate(req *DeviceActivationRequest) (*DeviceActivationResponse, error) { + return c.client.Activate(c.getContext(), req) +} + +func (c *gatewayClient) Close() error { + defer c.mutex.Unlock() + c.mutex.Lock() + if c.gatewayStatus != nil { + c.teardownGatewayStatus() + } + if c.uplink != nil { + c.teardownUplink() + } + if c.downlink != nil { + c.Unsbscribe() + c.teardownDownlink() + } + return nil +} From 213f6a282e3f16db99b697a1cbe4b2bd76527460 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 27 Sep 2016 14:34:55 +0200 Subject: [PATCH 1810/2266] Use Router client in ttnctl --- ttnctl/cmd/root.go | 1 + ttnctl/cmd/uplink.go | 46 +++++++++++++++++++----------------------- ttnctl/util/context.go | 13 ++++++++---- ttnctl/util/router.go | 6 +++--- 4 files changed, 34 insertions(+), 32 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index b7c11955a..cd0014e9e 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -41,6 +41,7 @@ var RootCmd = &cobra.Command{ api.DialOptions = append(api.DialOptions, grpc.WithBlock()) api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) grpclog.SetLogger(logging.NewGRPCLogger(ctx)) + api.SetLogger(ctx) }, } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 22d1e9c10..69de56a1f 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -7,9 +7,6 @@ import ( "strconv" "time" - "golang.org/x/net/context" - "google.golang.org/grpc/metadata" - "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" @@ -59,32 +56,18 @@ var uplinkCmd = &cobra.Command{ withDownlink = true } - rtrConn, rtrClient := util.GetRouter(ctx) - defer rtrConn.Close() + rtrClient := util.GetRouter(ctx) + defer rtrClient.Close() - md := metadata.Pairs( - "token", "token", - "id", "eui-0102030405060708", - ) - gatewayContext := metadata.NewContext(context.Background(), md) + gtwClient := rtrClient.ForGateway(util.GetID(), func() string { return "token" }) - downlink := make(chan *router.DownlinkMessage) + var downlink <-chan *router.DownlinkMessage + var errChan <-chan error if withDownlink { - downlinkStream, err := rtrClient.Subscribe(gatewayContext, &router.SubscribeRequest{}) + downlink, errChan, err = gtwClient.Subscribe() if err != nil { ctx.WithError(err).Fatal("Could not start downlink stream") } - time.Sleep(100 * time.Millisecond) - go func() { - if downlinkMessage, err := downlinkStream.Recv(); err == nil { - downlink <- downlinkMessage - } - }() - } - - uplink, err := rtrClient.Uplink(gatewayContext) - if err != nil { - ctx.WithError(err).Fatal("Could not start uplink stream") } m := &util.Message{} @@ -92,7 +75,18 @@ var uplinkCmd = &cobra.Command{ m.SetMessage(confirmed, fCnt, payload) bytes := m.Bytes() - err = uplink.Send(&router.UplinkMessage{ + err = gtwClient.SendUplink(&router.UplinkMessage{ + Payload: bytes, + GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), + ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), + }) + if err != nil { + ctx.WithError(err).Fatal("Could not send uplink to Router") + } + + time.Sleep(100 * time.Millisecond) + + err = gtwClient.SendUplink(&router.UplinkMessage{ Payload: bytes, GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), @@ -105,8 +99,10 @@ var uplinkCmd = &cobra.Command{ ctx.Info("Sent uplink to Router") - if withDownlink { + if downlink != nil { select { + case err := <-errChan: + ctx.WithError(err).Fatal("Error in downlink") case downlinkMessage := <-downlink: if err := m.Unmarshal(downlinkMessage.Payload); err != nil { ctx.WithError(err).Fatal("Could not unmarshal downlink") diff --git a/ttnctl/util/context.go b/ttnctl/util/context.go index bf081616d..195be4645 100644 --- a/ttnctl/util/context.go +++ b/ttnctl/util/context.go @@ -13,21 +13,26 @@ import ( "google.golang.org/grpc/metadata" ) -// GetContext returns a new context -func GetContext(ctx log.Interface) context.Context { - id := "client" +// GetID retrns the ID of this ttnctl +func GetID() string { + id := "ttnctl" if user, err := user.Current(); err == nil { id += "-" + user.Username } if hostname, err := os.Hostname(); err == nil { id += "@" + hostname } + return id +} + +// GetContext returns a new context +func GetContext(ctx log.Interface, extraPairs ...string) context.Context { token, err := GetTokenSource(ctx).Token() if err != nil { ctx.WithError(err).Fatal("Could not get token") } md := metadata.Pairs( - "id", id, + "id", GetID(), "service-name", "ttnctl", "token", token.AccessToken, ) diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index 557cce419..ca07753e7 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -13,7 +13,7 @@ import ( ) // GetRouter starts a connection with the router -func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { +func GetRouter(ctx log.Interface) *router.Client { ctx.Info("Discovering Router...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() @@ -25,12 +25,12 @@ func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") } ctx.Info("Connecting with Router...") - rtrConn, err := routerAnnouncement.Dial() + router, err := router.NewClient(routerAnnouncement) if err != nil { ctx.WithError(err).Fatal("Could not connect to Router") } ctx.Info("Connected to Router") - return rtrConn, router.NewRouterClient(rtrConn) + return router } // GetRouterManager starts a management connection with the router From 45ad67436ff2183a79b1521ffb714c52442ca0d9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 27 Sep 2016 15:41:17 +0200 Subject: [PATCH 1811/2266] Move mutexes in router client teardown --- api/router/client.go | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/api/router/client.go b/api/router/client.go index 1dbdf92e3..38e710687 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -117,8 +117,6 @@ func (c *gatewayClient) setupGatewayStatus() error { return default: if err := gatewayStatusClient.RecvMsg(msg); err != nil { - defer c.mutex.Unlock() - c.mutex.Lock() api.GetLogger().Warnf("Error in gateway status stream for %s: %s", c.id, err.Error()) c.teardownGatewayStatus() return @@ -131,6 +129,8 @@ func (c *gatewayClient) setupGatewayStatus() error { } func (c *gatewayClient) teardownGatewayStatus() { + defer c.mutex.Unlock() + c.mutex.Lock() if c.gatewayStatus != nil { api.GetLogger().Debugf("Closing gateway status stream for %s...", c.id) close(c.stopGatewayStatus) @@ -150,7 +150,7 @@ func (c *gatewayClient) SendGatewayStatus(status *gateway.Status) error { if err := c.gatewayStatus.Send(status); err != nil { if err == io.EOF { api.GetLogger().Warnf("Could not send gateway status for %s on closed stream", c.id) - c.teardownGatewayStatus() + go c.teardownGatewayStatus() return errors.FromGRPCError(err) } api.GetLogger().Warnf("Error sending gateway status for %s: %s", c.id, err.Error()) @@ -175,8 +175,6 @@ func (c *gatewayClient) setupUplink() error { return default: if err := uplinkClient.RecvMsg(msg); err != nil { - defer c.mutex.Unlock() - c.mutex.Lock() api.GetLogger().Warnf("Error in uplink stream for %s: %s", c.id, err.Error()) c.teardownUplink() return @@ -190,6 +188,8 @@ func (c *gatewayClient) setupUplink() error { } func (c *gatewayClient) teardownUplink() { + defer c.mutex.Unlock() + c.mutex.Lock() if c.uplink != nil { api.GetLogger().Debugf("Closing uplink stream for %s...", c.id) close(c.stopUplink) @@ -209,7 +209,7 @@ func (c *gatewayClient) SendUplink(uplink *UplinkMessage) error { if err := c.uplink.Send(uplink); err != nil { if err == io.EOF { api.GetLogger().Warnf("Could not send uplink for %s on closed stream", c.id) - c.teardownUplink() + go c.teardownUplink() return errors.FromGRPCError(err) } api.GetLogger().Warnf("Error sending uplink for %s: %s", c.id, err.Error()) @@ -235,8 +235,11 @@ func (c *gatewayClient) setupDownlink() error { } func (c *gatewayClient) teardownDownlink() { + defer c.mutex.Unlock() + c.mutex.Lock() if c.downlink != nil { api.GetLogger().Debugf("Closing downlink stream for %s...", c.id) + close(c.stopDownlink) c.downlink.CloseSend() c.downlink = nil } @@ -256,7 +259,6 @@ func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, erro defer func() { close(downChan) close(errChan) - c.teardownDownlink() }() for { select { @@ -265,11 +267,13 @@ func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, erro default: downlink, err := c.downlink.Recv() if err != nil { - errChan <- errors.FromGRPCError(err) if grpc.Code(err) == codes.Canceled { - api.GetLogger().Warnf("Downlink stream for %s was canceled", c.id) + api.GetLogger().Debugf("Downlink stream for %s was canceled", c.id) + errChan <- nil + c.teardownDownlink() return } + errChan <- errors.FromGRPCError(err) api.GetLogger().Warnf("Error receiving gateway downlink for %s: %s", c.id, err.Error()) } else { downChan <- downlink @@ -282,7 +286,7 @@ func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, erro } func (c *gatewayClient) Unsbscribe() error { - close(c.stopDownlink) + c.teardownDownlink() return nil } @@ -291,17 +295,8 @@ func (c *gatewayClient) Activate(req *DeviceActivationRequest) (*DeviceActivatio } func (c *gatewayClient) Close() error { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.gatewayStatus != nil { - c.teardownGatewayStatus() - } - if c.uplink != nil { - c.teardownUplink() - } - if c.downlink != nil { - c.Unsbscribe() - c.teardownDownlink() - } + c.teardownGatewayStatus() + c.teardownUplink() + c.teardownDownlink() return nil } From 65169181bd5bb6d9a1a56cd75383f1641986a533 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 27 Sep 2016 18:16:40 +0200 Subject: [PATCH 1812/2266] api/metadata.go: helper functions for metadata --- api/metadata.go | 37 +++++++++++++++++++++++++++++++++++++ core/router/server.go | 35 +++++++---------------------------- 2 files changed, 44 insertions(+), 28 deletions(-) create mode 100644 api/metadata.go diff --git a/api/metadata.go b/api/metadata.go new file mode 100644 index 000000000..809eaaf24 --- /dev/null +++ b/api/metadata.go @@ -0,0 +1,37 @@ +package api + +import ( + "context" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "google.golang.org/grpc/metadata" +) + +var ErrContext = errors.NewErrInternal("Could not get metadata from context") + +var ErrNoToken = errors.NewErrInvalidArgument("Metadata", "token missing") +var ErrNoID = errors.NewErrInvalidArgument("Metadata", "id missing") + +func MetadataFromContext(ctx context.Context) (md metadata.MD, err error) { + var ok bool + if md, ok = metadata.FromContext(ctx); !ok { + return md, ErrContext + } + return md, nil +} + +func IDFromMetadata(md metadata.MD) (string, error) { + id, ok := md["id"] + if !ok || len(id) == 0 { + return "", ErrNoID + } + return id[0], nil +} + +func TokenFromMetadata(md metadata.MD) (string, error) { + token, ok := md["token"] + if !ok || len(token) == 0 { + return "", ErrNoToken + } + return token[0], nil +} diff --git a/core/router/server.go b/core/router/server.go index 68e5a2fe7..96fa50d6e 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -6,6 +6,7 @@ package router import ( "io" + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -13,7 +14,6 @@ import ( context "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" ) type routerRPC struct { @@ -23,14 +23,17 @@ type routerRPC struct { var grpcErrf = grpc.Errorf // To make go vet stop complaining func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gateway, err error) { - md, err := metadataFromContext(ctx) + md, err := api.MetadataFromContext(ctx) + if err != nil { + return nil, err + } - gatewayID, err := gatewayIDFromMetadata(md) + gatewayID, err := api.IDFromMetadata(md) if err != nil { return nil, err } - token, err := tokenFromMetadata(md) + token, err := api.TokenFromMetadata(md) if err != nil { return nil, err } @@ -45,30 +48,6 @@ func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gatewa return gtw, nil } -func metadataFromContext(ctx context.Context) (md metadata.MD, err error) { - var ok bool - if md, ok = metadata.FromContext(ctx); !ok { - return md, errors.NewErrInternal("Could not get metadata from context") - } - return md, nil -} - -func gatewayIDFromMetadata(md metadata.MD) (gatewayID string, err error) { - id, ok := md["id"] - if !ok || len(id) < 1 { - return "", errors.NewErrInvalidArgument("Metadata", "id missing") - } - return id[0], nil -} - -func tokenFromMetadata(md metadata.MD) (string, error) { - token, ok := md["token"] - if !ok || len(token) < 1 { - return "", errors.NewErrInvalidArgument("Metadata", "token missing") - } - return token[0], nil -} - // GatewayStatus implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { gateway, err := r.gatewayFromContext(stream.Context()) From 4fece30004508f5adb6e0719b9c0ea885b873c6e Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 27 Sep 2016 18:23:21 +0200 Subject: [PATCH 1813/2266] api/metadata.go: consistent style --- api/metadata.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/metadata.go b/api/metadata.go index 809eaaf24..3ef3b3729 100644 --- a/api/metadata.go +++ b/api/metadata.go @@ -12,9 +12,9 @@ var ErrContext = errors.NewErrInternal("Could not get metadata from context") var ErrNoToken = errors.NewErrInvalidArgument("Metadata", "token missing") var ErrNoID = errors.NewErrInvalidArgument("Metadata", "id missing") -func MetadataFromContext(ctx context.Context) (md metadata.MD, err error) { - var ok bool - if md, ok = metadata.FromContext(ctx); !ok { +func MetadataFromContext(ctx context.Context) (metadata.MD, error) { + md, ok := metadata.FromContext(ctx) + if !ok { return md, ErrContext } return md, nil From 67baa407ef3039e3d2048e6608b9c9d49984dd2c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 27 Sep 2016 18:25:12 +0200 Subject: [PATCH 1814/2266] api/metadata.go: fix context import path --- api/metadata.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/metadata.go b/api/metadata.go index 3ef3b3729..f036975c8 100644 --- a/api/metadata.go +++ b/api/metadata.go @@ -1,7 +1,8 @@ package api import ( - "context" + // TODO change to "context", when protoc supports it + context "golang.org/x/net/context" "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc/metadata" From b93d339a0d644c1af7bc015b02785440075adce3 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 28 Sep 2016 08:42:36 +0200 Subject: [PATCH 1815/2266] Comment out PF placeholder logic Closes https://github.com/TheThingsIndustries/dashboard/issues/102 --- ttnctl/cmd/applications_pf.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index 4fe2ee3ef..3e0be6e63 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -21,9 +21,9 @@ converting and validating binary payload.`, INFO Found Application INFO Decoder function function Decoder(bytes) { - return { - payload: bytes, - }; + var decoded = {}; + decoded.led = bytes[0]; + return decoded; } INFO No converter function INFO No validator function From 783e0bb33bbaae0af717e02add2731fe4ffe18a6 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 28 Sep 2016 08:45:42 +0200 Subject: [PATCH 1816/2266] Update setter itself --- ttnctl/cmd/applications_pf_set.go | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 7df63a674..793b2c955 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -28,15 +28,17 @@ function Decoder(bytes) { // (array) of bytes to an object of fields. var decoded = {}; - decoded.isLightOn = bytes[0]; + // decoded.led = bytes[0]; return decoded; } ########## Write your Decoder here and end with Ctrl+D (EOF): function Decoder(bytes) { - return { - isLightOn: bytes[0] - }; + var decoded = {}; + + decoded.led = bytes[0]; + + return decoded; } INFO Updated application AppID=test `, @@ -86,7 +88,7 @@ function Decoder(bytes) { // (array) of bytes to an object of fields. var decoded = {}; - decoded.isLightOn = bytes[0]; + // decoded.led = bytes[0]; return decoded; } @@ -98,13 +100,9 @@ function Decoder(bytes) { // mutate decoded fields. var converted = decoded; - if (converted.isLightOn === 0) { - converted.isLightOn = false; - } - - if (converted.isLightOn === 1) { - converted.isLightOn = true; - } + // if (converted.led === 0 || converted.led === 1) { + // converted.led = Boolean(converted.led); + // } return converted; } @@ -115,9 +113,9 @@ function Decoder(bytes) { // Return false if the decoded, converted // message is invalid and should be dropped. - if (converted.isLightOn !== true && converted.isLightOn !== false) { - return false; - } + // if (typeof converted.led !== 'boolean') { + // return false; + // } return true; } @@ -129,7 +127,7 @@ function Decoder(bytes) { // object to an array or buffer of bytes. var bytes = []; - bytes[0] = object.turnLightOn ? 1 : 0; + // bytes[0] = object.led ? 1 : 0; return bytes; } From 36ed9bf0270fda3f4468310a647142fe67ae9f02 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 28 Sep 2016 11:37:37 +0200 Subject: [PATCH 1817/2266] Migrate Vendors --- .gitignore | 2 + .gitlab-ci.yml | 38 +- .gitmodules | 63 -- .travis.yml | 3 +- Makefile | 28 +- vendor/github.com/asaskevich/govalidator | 1 - vendor/github.com/bluele/gcache | 1 - vendor/github.com/brocaar/lorawan | 1 - vendor/github.com/cpuguy83/go-md2man | 1 - vendor/github.com/dgrijalva/jwt-go | 1 - vendor/github.com/eclipse/paho.mqtt.golang | 1 - vendor/github.com/golang/mock | 1 - vendor/github.com/gosuri/uitable | 1 - vendor/github.com/howeyc/gopass | 1 - vendor/github.com/jacobsa/crypto | 1 - vendor/github.com/mitchellh/go-homedir | 1 - vendor/github.com/mwitkow/go-grpc-middleware | 1 - vendor/github.com/pkg/errors | 1 - vendor/github.com/rcrowley/go-metrics | 1 - vendor/github.com/robertkrimen/otto | 1 - vendor/github.com/spf13/cobra | 1 - vendor/github.com/spf13/viper | 1 - vendor/github.com/tj/go-elastic | 1 - vendor/golang.org/x/oauth2 | 1 - vendor/gopkg.in/redis.v3 | 1 - vendor/gopkg.in/yaml.v2 | 1 - vendor/vendor.json | 655 +++++++++++++++++++ 27 files changed, 692 insertions(+), 118 deletions(-) delete mode 100644 .gitmodules delete mode 160000 vendor/github.com/asaskevich/govalidator delete mode 160000 vendor/github.com/bluele/gcache delete mode 160000 vendor/github.com/brocaar/lorawan delete mode 160000 vendor/github.com/cpuguy83/go-md2man delete mode 160000 vendor/github.com/dgrijalva/jwt-go delete mode 160000 vendor/github.com/eclipse/paho.mqtt.golang delete mode 160000 vendor/github.com/golang/mock delete mode 160000 vendor/github.com/gosuri/uitable delete mode 160000 vendor/github.com/howeyc/gopass delete mode 160000 vendor/github.com/jacobsa/crypto delete mode 160000 vendor/github.com/mitchellh/go-homedir delete mode 160000 vendor/github.com/mwitkow/go-grpc-middleware delete mode 160000 vendor/github.com/pkg/errors delete mode 160000 vendor/github.com/rcrowley/go-metrics delete mode 160000 vendor/github.com/robertkrimen/otto delete mode 160000 vendor/github.com/spf13/cobra delete mode 160000 vendor/github.com/spf13/viper delete mode 160000 vendor/github.com/tj/go-elastic delete mode 160000 vendor/golang.org/x/oauth2 delete mode 160000 vendor/gopkg.in/redis.v3 delete mode 160000 vendor/gopkg.in/yaml.v2 create mode 100644 vendor/vendor.json diff --git a/.gitignore b/.gitignore index 09289adc0..31e503fe1 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,8 @@ _testmain.go *.prof /release +/vendor/*/ +/.cover # Generated databases *.db diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 03e3be2b5..d777c26fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,35 +8,23 @@ variables: CONTAINER_NAME: thethingsnetwork/ttn cache: - key: "$CI_BUILD_REF_NAME/go_src" - paths: - - go_src/ - - vendor/ + key: "$CI_PIPELINE_ID" + paths: + - vendor/ before_script: - - git submodule update --init - rm -rf $GOPATH/src - - "if [ -d go_src ]; then mv go_src $GOPATH/src; fi" - mkdir -p $GOPATH/src/github.com/TheThingsNetwork/ttn - cp -R . $GOPATH/src/github.com/TheThingsNetwork/ttn -after_script: - - rm -rf $GOPATH/src/github.com/TheThingsNetwork/ttn - - cp -R $GOPATH/src go_src - deps: stage: deps image: golang:latest script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - - TARGET_PLATFORM=linux-386 make deps - - TARGET_PLATFORM=linux-amd64 make deps - - TARGET_PLATFORM=linux-arm make deps - - TARGET_PLATFORM=darwin-amd64 make deps - - TARGET_PLATFORM=windows-386 make deps - - TARGET_PLATFORM=windows-amd64 make deps - - make test-deps cover-deps + - make deps - popd + - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/vendor/* vendor/ tests: stage: test @@ -49,6 +37,7 @@ tests: MQTT_HOST: ansi-mosquitto script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - make deps - make test - popd @@ -57,6 +46,7 @@ binaries: image: golang:latest script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn + - make deps - TARGET_PLATFORM=linux-386 make build - TARGET_PLATFORM=linux-amd64 make build - TARGET_PLATFORM=linux-arm make build @@ -70,7 +60,7 @@ binaries: paths: - release/ -container: +gitlab-image: stage: package image: docker:git services: @@ -81,6 +71,18 @@ container: - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" registry.gitlab.com - docker tag ttn registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME - docker push registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME + +dockerhub-image: + only: + - master@thethingsnetwork/ttn + - refactor@thethingsnetwork/ttn + stage: package + image: docker:git + services: + - "docker:dind" + script: + - cd $GOPATH/src/github.com/TheThingsNetwork/ttn + - docker build -t ttn . - docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_PASSWORD" - docker tag ttn $CONTAINER_NAME:$CI_BUILD_REF_NAME - docker push $CONTAINER_NAME:$CI_BUILD_REF_NAME diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 286988361..000000000 --- a/.gitmodules +++ /dev/null @@ -1,63 +0,0 @@ -[submodule "vendor/github.com/asaskevich/govalidator"] - path = vendor/github.com/asaskevich/govalidator - url = https://github.com/asaskevich/govalidator -[submodule "vendor/github.com/bluele/gcache"] - path = vendor/github.com/bluele/gcache - url = https://github.com/bluele/gcache -[submodule "vendor/github.com/brocaar/lorawan"] - path = vendor/github.com/brocaar/lorawan - url = https://github.com/brocaar/lorawan -[submodule "vendor/github.com/dgrijalva/jwt-go"] - path = vendor/github.com/dgrijalva/jwt-go - url = https://github.com/dgrijalva/jwt-go -[submodule "vendor/github.com/eclipse/paho.mqtt.golang"] - path = vendor/github.com/eclipse/paho.mqtt.golang - url = https://github.com/htdvisser/paho.mqtt.golang -[submodule "vendor/github.com/golang/mock"] - path = vendor/github.com/golang/mock - url = https://github.com/golang/mock -[submodule "vendor/github.com/gosuri/uitable"] - path = vendor/github.com/gosuri/uitable - url = https://github.com/gosuri/uitable -[submodule "vendor/github.com/howeyc/gopass"] - path = vendor/github.com/howeyc/gopass - url = https://github.com/howeyc/gopass -[submodule "vendor/github.com/jacobsa/crypto"] - path = vendor/github.com/jacobsa/crypto - url = https://github.com/jacobsa/crypto -[submodule "vendor/github.com/mitchellh/go-homedir"] - path = vendor/github.com/mitchellh/go-homedir - url = https://github.com/mitchellh/go-homedir -[submodule "vendor/github.com/mwitkow/go-grpc-middleware"] - path = vendor/github.com/mwitkow/go-grpc-middleware - url = https://github.com/mwitkow/go-grpc-middleware -[submodule "vendor/github.com/pkg/errors"] - path = vendor/github.com/pkg/errors - url = https://github.com/pkg/errors -[submodule "vendor/github.com/rcrowley/go-metrics"] - path = vendor/github.com/rcrowley/go-metrics - url = https://github.com/rcrowley/go-metrics -[submodule "vendor/github.com/robertkrimen/otto"] - path = vendor/github.com/robertkrimen/otto - url = https://github.com/robertkrimen/otto -[submodule "vendor/github.com/spf13/cobra"] - path = vendor/github.com/spf13/cobra - url = https://github.com/spf13/cobra -[submodule "vendor/github.com/spf13/viper"] - path = vendor/github.com/spf13/viper - url = https://github.com/spf13/viper -[submodule "vendor/github.com/tj/go-elastic"] - path = vendor/github.com/tj/go-elastic - url = https://github.com/tj/go-elastic -[submodule "vendor/gopkg.in/redis.v3"] - path = vendor/gopkg.in/redis.v3 - url = https://gopkg.in/redis.v3 -[submodule "vendor/gopkg.in/yaml.v2"] - path = vendor/gopkg.in/yaml.v2 - url = https://gopkg.in/yaml.v2 -[submodule "vendor/golang.org/x/oauth2"] - path = vendor/golang.org/x/oauth2 - url = https://go.googlesource.com/oauth2 -[submodule "vendor/github.com/cpuguy83/go-md2man"] - path = vendor/github.com/cpuguy83/go-md2man - url = https://github.com/cpuguy83/go-md2man.git diff --git a/.travis.yml b/.travis.yml index 9b5fc1475..fe66db49e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,11 +6,10 @@ services: - docker go: - - 1.6 + - 1.7 install: - make deps - - make test-deps - make cover-deps before_script: diff --git a/Makefile b/Makefile index 7a0e3f96d..d834b034c 100644 --- a/Makefile +++ b/Makefile @@ -15,11 +15,8 @@ BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` LDFLAGS = -ldflags "-w -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" -select_pkgs = $(GOCMD) list ./... | grep -vE 'ttn/vendor|ttn/ttnctl' -coverage_pkgs = $(GOCMD) list ./... | grep -vE 'ttn/api|ttn/cmd|ttn/vendor|ttn/ttnctl' - -DEPS = `comm -23 <($(GOCMD) list -f '{{join .Deps "\n"}}' . | grep -vE 'github.com/TheThingsNetwork/ttn' | sort | uniq) <($(GOCMD) list std)` -TEST_DEPS = `comm -23 <($(select_pkgs) | xargs $(GOCMD) list -f '{{join .TestImports "\n"}}' | grep -vE 'github.com/TheThingsNetwork/ttn' | sort | uniq) <($(GOCMD) list std)` +select_pkgs = govendor list --no-status +local +coverage_pkgs = $(select_pkgs) | grep -vE 'ttn/api|ttn/cmd|ttn/ttnctl' RELEASE_DIR ?= release COVER_FILE = coverage.out @@ -31,20 +28,17 @@ ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) ttnbin = $(ttnpkg)$(GOEXE) ttnctlbin = $(ttnctlpkg)$(GOEXE) -.PHONY: all clean deps update-deps test-deps dev-deps cover-deps proto test fmt vet cover coveralls docs build install docker package +.PHONY: all clean build-deps deps dev-deps cover-deps vendor update-vendor proto test fmt vet cover coveralls docs build install docker package all: clean deps build package -deps: - $(GOCMD) get -d -v $(DEPS) - -update-deps: - $(GOCMD) get -u -d -v $(DEPS) +build-deps: + $(GOCMD) get -u "github.com/kardianos/govendor" -test-deps: - $(GOCMD) get -d -v $(TEST_DEPS) +deps: build-deps + govendor sync -dev-deps: update-deps test-deps +dev-deps: deps $(GOCMD) get -u -v github.com/gogo/protobuf/protoc-gen-gofast $(GOCMD) get -u -v github.com/golang/mock/gomock $(GOCMD) get -u -v github.com/golang/mock/mockgen @@ -54,6 +48,12 @@ cover-deps: if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi $(GOCMD) get github.com/mattn/goveralls +vendor: build-deps + govendor add +external + +update-vendor: build-deps + govendor fetch +external + proto: @$(PROTOC)/api/*.proto @$(PROTOC)/api/protocol/protocol.proto diff --git a/vendor/github.com/asaskevich/govalidator b/vendor/github.com/asaskevich/govalidator deleted file mode 160000 index 593d64559..000000000 --- a/vendor/github.com/asaskevich/govalidator +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 593d64559f7600f29581a3ee42177f5dbded27a9 diff --git a/vendor/github.com/bluele/gcache b/vendor/github.com/bluele/gcache deleted file mode 160000 index 69623c269..000000000 --- a/vendor/github.com/bluele/gcache +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 69623c269a10cc02d8f770133ca36357c0877d81 diff --git a/vendor/github.com/brocaar/lorawan b/vendor/github.com/brocaar/lorawan deleted file mode 160000 index 8e810fc7a..000000000 --- a/vendor/github.com/brocaar/lorawan +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 8e810fc7af6148d4514c9d873e5f921e9178a9c1 diff --git a/vendor/github.com/cpuguy83/go-md2man b/vendor/github.com/cpuguy83/go-md2man deleted file mode 160000 index a65d4d2de..000000000 --- a/vendor/github.com/cpuguy83/go-md2man +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa diff --git a/vendor/github.com/dgrijalva/jwt-go b/vendor/github.com/dgrijalva/jwt-go deleted file mode 160000 index 24c63f565..000000000 --- a/vendor/github.com/dgrijalva/jwt-go +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 24c63f56522a87ec5339cc3567883f1039378fdb diff --git a/vendor/github.com/eclipse/paho.mqtt.golang b/vendor/github.com/eclipse/paho.mqtt.golang deleted file mode 160000 index b3d5b9734..000000000 --- a/vendor/github.com/eclipse/paho.mqtt.golang +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b3d5b973455691c8a6b7e9c4f402dc4e7da6a2e2 diff --git a/vendor/github.com/golang/mock b/vendor/github.com/golang/mock deleted file mode 160000 index bd3c8e81b..000000000 --- a/vendor/github.com/golang/mock +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bd3c8e81be01eef76d4b503f5e687d2d1354d2d9 diff --git a/vendor/github.com/gosuri/uitable b/vendor/github.com/gosuri/uitable deleted file mode 160000 index 36ee7e946..000000000 --- a/vendor/github.com/gosuri/uitable +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 36ee7e946282a3fb1cfecd476ddc9b35d8847e42 diff --git a/vendor/github.com/howeyc/gopass b/vendor/github.com/howeyc/gopass deleted file mode 160000 index 26c6e1184..000000000 --- a/vendor/github.com/howeyc/gopass +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 26c6e1184fd5255fa5f5289d0b789a4819c203a4 diff --git a/vendor/github.com/jacobsa/crypto b/vendor/github.com/jacobsa/crypto deleted file mode 160000 index 42daa9d04..000000000 --- a/vendor/github.com/jacobsa/crypto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 42daa9d04c68a12aca47231abd9fb7f8aac27ef7 diff --git a/vendor/github.com/mitchellh/go-homedir b/vendor/github.com/mitchellh/go-homedir deleted file mode 160000 index 756f7b183..000000000 --- a/vendor/github.com/mitchellh/go-homedir +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 756f7b183b7ab78acdbbee5c7f392838ed459dda diff --git a/vendor/github.com/mwitkow/go-grpc-middleware b/vendor/github.com/mwitkow/go-grpc-middleware deleted file mode 160000 index 0664ec3f4..000000000 --- a/vendor/github.com/mwitkow/go-grpc-middleware +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0664ec3f4e6e08d02f2bf6a62be6900c0b82ea69 diff --git a/vendor/github.com/pkg/errors b/vendor/github.com/pkg/errors deleted file mode 160000 index 17b591df3..000000000 --- a/vendor/github.com/pkg/errors +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 17b591df37844cde689f4d5813e5cea0927d8dd2 diff --git a/vendor/github.com/rcrowley/go-metrics b/vendor/github.com/rcrowley/go-metrics deleted file mode 160000 index 6ee5318c7..000000000 --- a/vendor/github.com/rcrowley/go-metrics +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6ee5318c779434e3545a375bde303fd197e0dde2 diff --git a/vendor/github.com/robertkrimen/otto b/vendor/github.com/robertkrimen/otto deleted file mode 160000 index 7d9cbc2be..000000000 --- a/vendor/github.com/robertkrimen/otto +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7d9cbc2befca39869eb0e5bcb0f44c0692c2f8ff diff --git a/vendor/github.com/spf13/cobra b/vendor/github.com/spf13/cobra deleted file mode 160000 index 9c28e4bbd..000000000 --- a/vendor/github.com/spf13/cobra +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744 diff --git a/vendor/github.com/spf13/viper b/vendor/github.com/spf13/viper deleted file mode 160000 index 16990631d..000000000 --- a/vendor/github.com/spf13/viper +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 16990631d4aa7e38f73dbbbf37fa13e67c648531 diff --git a/vendor/github.com/tj/go-elastic b/vendor/github.com/tj/go-elastic deleted file mode 160000 index 9a9a2a21e..000000000 --- a/vendor/github.com/tj/go-elastic +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9a9a2a21e071e6e38f236740c3b650e7316ae67e diff --git a/vendor/golang.org/x/oauth2 b/vendor/golang.org/x/oauth2 deleted file mode 160000 index 3c3a985cb..000000000 --- a/vendor/golang.org/x/oauth2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3c3a985cb79f52a3190fbc056984415ca6763d01 diff --git a/vendor/gopkg.in/redis.v3 b/vendor/gopkg.in/redis.v3 deleted file mode 160000 index b5e368500..000000000 --- a/vendor/gopkg.in/redis.v3 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit b5e368500d0a508ef8f16e9c2d4025a8a46bcc29 diff --git a/vendor/gopkg.in/yaml.v2 b/vendor/gopkg.in/yaml.v2 deleted file mode 160000 index 31c299268..000000000 --- a/vendor/gopkg.in/yaml.v2 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 31c299268d302dd0aa9a0dcf765a3d58971ac83f diff --git a/vendor/vendor.json b/vendor/vendor.json new file mode 100644 index 000000000..d0b99d0ec --- /dev/null +++ b/vendor/vendor.json @@ -0,0 +1,655 @@ +{ + "comment": "", + "ignore": "test", + "package": [ + { + "path": "-v", + "revision": "" + }, + { + "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", + "path": "github.com/apex/log", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "LjQdQscNb25c2HxbREjmFFOoyx4=", + "path": "github.com/apex/log/handlers/json", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "AHCiF3VnEqmXyZDeH+z/IGsAtnI=", + "path": "github.com/apex/log/handlers/level", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "rxQUkWqruIZKpRzdwqrkxfcZvyw=", + "path": "github.com/apex/log/handlers/multi", + "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", + "revisionTime": "2016-09-05T15:13:04Z" + }, + { + "checksumSHA1": "CDOphCxF7dup+hBBVRpFeQhM9Jw=", + "path": "github.com/asaskevich/govalidator", + "revision": "593d64559f7600f29581a3ee42177f5dbded27a9", + "revisionTime": "2016-07-15T17:06:12Z" + }, + { + "checksumSHA1": "rVZEfQyYQxiivnzH6ZWzcqrbi6o=", + "path": "github.com/bluele/gcache", + "revision": "69623c269a10cc02d8f770133ca36357c0877d81", + "revisionTime": "2016-07-16T13:40:25Z" + }, + { + "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", + "path": "github.com/brocaar/lorawan", + "revision": "5dcb3accd0694379a4db94be75fa721867c59739", + "revisionTime": "2016-09-18T07:42:22Z" + }, + { + "checksumSHA1": "ff324fCIrB7mAfRe47oaPZE5PDM=", + "path": "github.com/brocaar/lorawan/band", + "revision": "5dcb3accd0694379a4db94be75fa721867c59739", + "revisionTime": "2016-09-18T07:42:22Z" + }, + { + "checksumSHA1": "ntacCkWfMT63DaehXLG5FeXWyNM=", + "path": "github.com/cpuguy83/go-md2man/md2man", + "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", + "revisionTime": "2016-09-04T16:08:59Z" + }, + { + "checksumSHA1": "EsJksAKyY2+r3mY+rdNocSCe1wg=", + "path": "github.com/dgrijalva/jwt-go", + "revision": "24c63f56522a87ec5339cc3567883f1039378fdb", + "revisionTime": "2016-08-31T18:35:34Z" + }, + { + "checksumSHA1": "Uouj6ZRJ7qieqrCHp6u1cghopIM=", + "path": "github.com/eclipse/paho.mqtt.golang", + "revision": "b0f0cce24f138f7a1d034f55069883c7dcb6a9d7", + "revisionTime": "2016-09-07T14:31:29Z" + }, + { + "checksumSHA1": "0KKFo4Q90AHUBTqNWH3DKZHWals=", + "path": "github.com/eclipse/paho.mqtt.golang/packets", + "revision": "b0f0cce24f138f7a1d034f55069883c7dcb6a9d7", + "revisionTime": "2016-09-07T14:31:29Z" + }, + { + "checksumSHA1": "xgjI2W3RGiQwNlxsOW2V9fJ9kaM=", + "path": "github.com/fsnotify/fsnotify", + "revision": "f12c6236fe7b5cf6bcf30e5935d08cb079d78334", + "revisionTime": "2016-08-16T05:15:41Z" + }, + { + "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", + "path": "github.com/gogo/protobuf/gogoproto", + "revision": "89f1976ff373a3e549675d2f212c10f98b6c6316", + "revisionTime": "2016-09-26T20:24:12Z" + }, + { + "checksumSHA1": "cCRiVVaOgfOXO73tkNYIXU+lXn4=", + "path": "github.com/gogo/protobuf/proto", + "revision": "89f1976ff373a3e549675d2f212c10f98b6c6316", + "revisionTime": "2016-09-26T20:24:12Z" + }, + { + "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", + "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", + "revision": "89f1976ff373a3e549675d2f212c10f98b6c6316", + "revisionTime": "2016-09-26T20:24:12Z" + }, + { + "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", + "path": "github.com/golang/mock/gomock", + "revision": "bd3c8e81be01eef76d4b503f5e687d2d1354d2d9", + "revisionTime": "2016-01-21T18:51:14Z" + }, + { + "checksumSHA1": "sZSeW3Dc6lUqm1CzMUdu3poimoA=", + "path": "github.com/golang/protobuf/proto", + "revision": "87c000235d3d852c1628dc9490cd21ab36a7d69f", + "revisionTime": "2016-09-26T18:56:24Z" + }, + { + "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", + "path": "github.com/golang/protobuf/ptypes/empty", + "revision": "87c000235d3d852c1628dc9490cd21ab36a7d69f", + "revisionTime": "2016-09-26T18:56:24Z" + }, + { + "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", + "path": "github.com/gosuri/uitable", + "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", + "revisionTime": "2016-04-04T20:39:58Z" + }, + { + "checksumSHA1": "hfL7iFULaUity86NGidQt/AiYyo=", + "path": "github.com/gosuri/uitable/util/strutil", + "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", + "revisionTime": "2016-04-04T20:39:58Z" + }, + { + "checksumSHA1": "xUvDrGeY4HijGKl+W8WSvWl1Evs=", + "path": "github.com/gosuri/uitable/util/wordwrap", + "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", + "revisionTime": "2016-04-04T20:39:58Z" + }, + { + "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", + "path": "github.com/hashicorp/hcl", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "67DfevLBglV52Y2eAuhFc/xQni0=", + "path": "github.com/hashicorp/hcl/hcl/ast", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "l2oQxBsZRwn6eZjf+whXr8c9+8c=", + "path": "github.com/hashicorp/hcl/hcl/parser", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", + "path": "github.com/hashicorp/hcl/hcl/scanner", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", + "path": "github.com/hashicorp/hcl/hcl/strconv", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", + "path": "github.com/hashicorp/hcl/hcl/token", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "jQ45CCc1ed/nlV7bbSnx6z72q1M=", + "path": "github.com/hashicorp/hcl/json/parser", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", + "path": "github.com/hashicorp/hcl/json/scanner", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", + "path": "github.com/hashicorp/hcl/json/token", + "revision": "ef8133da8cda503718a74741312bf50821e6de79", + "revisionTime": "2016-09-16T13:01:00Z" + }, + { + "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", + "path": "github.com/howeyc/gopass", + "revision": "26c6e1184fd5255fa5f5289d0b789a4819c203a4", + "revisionTime": "2016-09-12T12:55:46Z" + }, + { + "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", + "path": "github.com/inconshreveable/mousetrap", + "revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75", + "revisionTime": "2014-10-17T20:07:13Z" + }, + { + "checksumSHA1": "YEZ/qI/rjyPhyyjuGI0NysuKIfA=", + "path": "github.com/jacobsa/crypto/cmac", + "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", + "revisionTime": "2016-04-10T22:58:39Z" + }, + { + "checksumSHA1": "NBvtX91AEKxFLmj8mwwhXEKl6d0=", + "path": "github.com/jacobsa/crypto/common", + "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", + "revisionTime": "2016-04-10T22:58:39Z" + }, + { + "checksumSHA1": "KQhA4EQp4Ldwj9nJZnEURlE6aQw=", + "path": "github.com/kr/fs", + "revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b", + "revisionTime": "2013-11-06T22:25:44Z" + }, + { + "checksumSHA1": "S6PDDQMYaKwLDIP/NsRYb4FRAqQ=", + "path": "github.com/magiconair/properties", + "revision": "0723e352fa358f9322c938cc2dadda874e9151a9", + "revisionTime": "2016-09-08T09:36:58Z" + }, + { + "checksumSHA1": "DdH3xAkzAWJ4B/LGYJyCeRsly2I=", + "path": "github.com/mattn/go-runewidth", + "revision": "d6bea18f789704b5f83375793155289da36a3c7f", + "revisionTime": "2016-03-15T04:07:12Z" + }, + { + "checksumSHA1": "AXacfEchaUqT5RGmPmMXsOWRhv8=", + "path": "github.com/mitchellh/go-homedir", + "revision": "756f7b183b7ab78acdbbee5c7f392838ed459dda", + "revisionTime": "2016-06-21T17:42:43Z" + }, + { + "checksumSHA1": "LUrnGREfnifW4WDMaavmc9MlLI0=", + "path": "github.com/mitchellh/mapstructure", + "revision": "ca63d7c062ee3c9f34db231e352b60012b4fd0c1", + "revisionTime": "2016-08-08T18:12:53Z" + }, + { + "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", + "path": "github.com/mwitkow/go-grpc-middleware", + "revision": "0664ec3f4e6e08d02f2bf6a62be6900c0b82ea69", + "revisionTime": "2016-09-11T13:13:45Z" + }, + { + "checksumSHA1": "8Y05Pz7onrQPcVWW6JStSsYRh6E=", + "path": "github.com/pelletier/go-buffruneio", + "revision": "df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d", + "revisionTime": "2016-01-24T19:35:03Z" + }, + { + "checksumSHA1": "CtI2rBQw2rxHNIU+a0rcAf29Sco=", + "path": "github.com/pelletier/go-toml", + "revision": "45932ad32dfdd20826f5671da37a5f3ce9f26a8d", + "revisionTime": "2016-09-20T07:07:15Z" + }, + { + "checksumSHA1": "Hky3u+8Rqum+wB5BHMj0A8ZmT4g=", + "path": "github.com/pkg/errors", + "revision": "a887431f7f6ef7687b556dbf718d9f351d4858a0", + "revisionTime": "2016-09-16T11:02:12Z" + }, + { + "checksumSHA1": "v6/DDmWObvEsdMvLe+KfuBVSNtg=", + "path": "github.com/pkg/sftp", + "revision": "8197a2e580736b78d704be0fc47b2324c0591a32", + "revisionTime": "2016-09-08T10:00:35Z" + }, + { + "checksumSHA1": "GiX6yRUzizn1C+ckgj1xLFLoz8g=", + "path": "github.com/rcrowley/go-metrics", + "revision": "ab2277b1c5d15c3cba104e9cbddbdfc622df5ad8", + "revisionTime": "2016-09-21T19:52:07Z" + }, + { + "checksumSHA1": "WXYoBfdPvrhxal3Iy0xrP+C7VQY=", + "path": "github.com/robertkrimen/otto", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=", + "path": "github.com/robertkrimen/otto/ast", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "L0KsB2EzTlPgv0iae3q3SukNW7U=", + "path": "github.com/robertkrimen/otto/dbg", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "euDLJKhw4doeTSxjEoezjxYXLzs=", + "path": "github.com/robertkrimen/otto/file", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "LLuLITFO8chqSG0+APJIy5NtOHU=", + "path": "github.com/robertkrimen/otto/parser", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "7J/7NaYRqKhBvZ+dTIutsEoEgFw=", + "path": "github.com/robertkrimen/otto/registry", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "/jMXYuXycBpTqWhRyJ2xsqvHvQI=", + "path": "github.com/robertkrimen/otto/token", + "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", + "revisionTime": "2016-09-19T01:00:42Z" + }, + { + "checksumSHA1": "JdYdBoNxMR+CDqTfs78cQmY8JtI=", + "origin": "github.com/cpuguy83/go-md2man/vendor/github.com/russross/blackfriday", + "path": "github.com/russross/blackfriday", + "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", + "revisionTime": "2016-09-04T16:08:59Z" + }, + { + "checksumSHA1": "XXTR/ftEYSqQn4+Y7wNfiJJmq9U=", + "origin": "github.com/cpuguy83/go-md2man/vendor/github.com/shurcooL/sanitized_anchor_name", + "path": "github.com/shurcooL/sanitized_anchor_name", + "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", + "revisionTime": "2016-09-04T16:08:59Z" + }, + { + "checksumSHA1": "6AYg4fjEvFuAVN3wHakGApjhZAM=", + "path": "github.com/smartystreets/assertions", + "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", + "revisionTime": "2016-07-07T19:03:55Z" + }, + { + "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", + "path": "github.com/smartystreets/assertions/internal/go-render/render", + "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", + "revisionTime": "2016-07-07T19:03:55Z" + }, + { + "checksumSHA1": "SLC6TfV4icQA9l8YJQu8acJYbuo=", + "path": "github.com/smartystreets/assertions/internal/oglematchers", + "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", + "revisionTime": "2016-07-07T19:03:55Z" + }, + { + "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", + "path": "github.com/smartystreets/go-aws-auth", + "revision": "2043e6d0bb7e4c18464a7bba562acbe482e3cabd", + "revisionTime": "2016-07-22T04:48:03Z" + }, + { + "checksumSHA1": "59wTbS4fE2282Q88NrBYImbFGbo=", + "path": "github.com/spf13/afero", + "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", + "revisionTime": "2016-09-19T21:01:14Z" + }, + { + "checksumSHA1": "u6B0SEgZ/TUEfIvF6w/HnFVQbII=", + "path": "github.com/spf13/afero/mem", + "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", + "revisionTime": "2016-09-19T21:01:14Z" + }, + { + "checksumSHA1": "sLyAUiIT7V0DNVp6yBhW4Ms5BEs=", + "path": "github.com/spf13/afero/sftp", + "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", + "revisionTime": "2016-09-19T21:01:14Z" + }, + { + "checksumSHA1": "M4qI7Ul0vf2uj3fF69DvRnwqfd0=", + "path": "github.com/spf13/cast", + "revision": "2580bc98dc0e62908119e4737030cc2fdfc45e4c", + "revisionTime": "2016-09-26T08:42:49Z" + }, + { + "checksumSHA1": "fEGgcE+iSzLkxdrbRj7j/vV7a7E=", + "path": "github.com/spf13/cobra", + "revision": "9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744", + "revisionTime": "2016-08-30T17:49:25Z" + }, + { + "checksumSHA1": "rJRJtv7XT5eDwNlcqNPC+NjXCYE=", + "path": "github.com/spf13/cobra/doc", + "revision": "9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744", + "revisionTime": "2016-08-30T17:49:25Z" + }, + { + "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", + "path": "github.com/spf13/jwalterweatherman", + "revision": "33c24e77fb80341fe7130ee7c594256ff08ccc46", + "revisionTime": "2016-03-01T12:00:06Z" + }, + { + "checksumSHA1": "b1FgvXRMwcr3D1litcrDNxgowBw=", + "path": "github.com/spf13/pflag", + "revision": "c7e63cf4530bcd3ba943729cee0efeff2ebea63f", + "revisionTime": "2016-09-15T15:31:01Z" + }, + { + "checksumSHA1": "c95O+eiZqqbHBDb8oy+RqzUBzRg=", + "path": "github.com/spf13/viper", + "revision": "382f87b929b84ce13e9c8a375a4b217f224e6c65", + "revisionTime": "2016-09-26T15:04:02Z" + }, + { + "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", + "path": "github.com/tj/go-elastic", + "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", + "revisionTime": "2016-06-07T20:24:39Z" + }, + { + "checksumSHA1": "nL4enNHknemOmxcaPTIJCrJc0/I=", + "path": "github.com/tj/go-elastic/aliases", + "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", + "revisionTime": "2016-06-07T20:24:39Z" + }, + { + "checksumSHA1": "SgbyhOvKGvet/Nw70Rxa8d3gLZ0=", + "path": "github.com/tj/go-elastic/batch", + "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", + "revisionTime": "2016-06-07T20:24:39Z" + }, + { + "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", + "path": "golang.org/x/crypto/curve25519", + "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", + "revisionTime": "2016-09-10T18:59:01Z" + }, + { + "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", + "path": "golang.org/x/crypto/ed25519", + "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", + "revisionTime": "2016-09-10T18:59:01Z" + }, + { + "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", + "path": "golang.org/x/crypto/ed25519/internal/edwards25519", + "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", + "revisionTime": "2016-09-10T18:59:01Z" + }, + { + "checksumSHA1": "mtOqF6Q4ldg801EaT6YTv9LP9bM=", + "path": "golang.org/x/crypto/ssh", + "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", + "revisionTime": "2016-09-10T18:59:01Z" + }, + { + "checksumSHA1": "y3a1tOZVwKCqG2yJZvAYycnelyM=", + "path": "golang.org/x/crypto/ssh/terminal", + "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", + "revisionTime": "2016-09-10T18:59:01Z" + }, + { + "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", + "path": "golang.org/x/net/context", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "yIyismrXLh/RQFDeOtTPAGWsTOw=", + "path": "golang.org/x/net/http2", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", + "path": "golang.org/x/net/http2/hpack", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", + "path": "golang.org/x/net/idna", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", + "path": "golang.org/x/net/internal/timeseries", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", + "path": "golang.org/x/net/lex/httplex", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "WpST9lFOHvWrjDVy0GJNqHe+R3E=", + "path": "golang.org/x/net/trace", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "likOl7O0BJntqagR050kkOBuY4o=", + "path": "golang.org/x/net/websocket", + "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", + "revisionTime": "2016-09-24T00:10:04Z" + }, + { + "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", + "path": "golang.org/x/oauth2", + "revision": "3c3a985cb79f52a3190fbc056984415ca6763d01", + "revisionTime": "2016-08-26T23:14:08Z" + }, + { + "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", + "path": "golang.org/x/oauth2/internal", + "revision": "3c3a985cb79f52a3190fbc056984415ca6763d01", + "revisionTime": "2016-08-26T23:14:08Z" + }, + { + "checksumSHA1": "vu7njn8O4vhFtmgGWntwjA2NAbk=", + "path": "golang.org/x/sys/unix", + "revision": "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9", + "revisionTime": "2016-09-14T20:33:16Z" + }, + { + "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", + "path": "golang.org/x/text/transform", + "revision": "a7c023693a94aedd6b6df43ae7526bfe9d2b7d22", + "revisionTime": "2016-09-22T05:42:04Z" + }, + { + "checksumSHA1": "Aj3JSVO324FCjEAGm4ZwmC79bbo=", + "path": "golang.org/x/text/unicode/norm", + "revision": "a7c023693a94aedd6b6df43ae7526bfe9d2b7d22", + "revisionTime": "2016-09-22T05:42:04Z" + }, + { + "checksumSHA1": "iCegvO4MPw6Uf8eMp/LJa1KCfeE=", + "path": "google.golang.org/grpc", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", + "path": "google.golang.org/grpc/codes", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", + "path": "google.golang.org/grpc/credentials", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", + "path": "google.golang.org/grpc/grpclog", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", + "path": "google.golang.org/grpc/internal", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", + "path": "google.golang.org/grpc/metadata", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", + "path": "google.golang.org/grpc/naming", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", + "path": "google.golang.org/grpc/peer", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "Z9sFAvzisTlmBjPMVt8dEGBK2pg=", + "path": "google.golang.org/grpc/transport", + "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", + "revisionTime": "2016-09-28T00:26:54Z" + }, + { + "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", + "path": "gopkg.in/bsm/ratelimit.v1", + "revision": "db14e161995a5177acef654cb0dd785e8ee8bc22", + "revisionTime": "2016-02-20T15:49:07Z" + }, + { + "checksumSHA1": "/EL/UuzIPObHgESjkBMaD4gaXOw=", + "path": "gopkg.in/redis.v3", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "b4Z72l8sq4xuWhR+nM5vbLQIv+o=", + "path": "gopkg.in/redis.v3/internal", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "tNI/mAuqqQqfYU4vuJghUroIts4=", + "path": "gopkg.in/redis.v3/internal/consistenthash", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "JvqkSear94dc+NgofRqBAUmLcN0=", + "path": "gopkg.in/redis.v3/internal/hashtag", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "ROpD4/McQLnnxpPenjA0mEZfTL8=", + "path": "gopkg.in/redis.v3/internal/pool", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", + "path": "gopkg.in/sourcemap.v1", + "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", + "revisionTime": "2016-06-02T08:55:44Z" + }, + { + "checksumSHA1": "DPyTTxwhl5mrDF15nLNtshc0cWs=", + "path": "gopkg.in/sourcemap.v1/base64vlq", + "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", + "revisionTime": "2016-06-02T08:55:44Z" + }, + { + "checksumSHA1": "SPMXWeoFQa5z0pLPmqpcFzyHqQQ=", + "path": "gopkg.in/yaml.v2", + "revision": "31c299268d302dd0aa9a0dcf765a3d58971ac83f", + "revisionTime": "2016-09-12T16:56:03Z" + } + ], + "rootPath": "github.com/TheThingsNetwork/ttn" +} From b1ff842a6e6853d3cd35eb518558310ba098e97f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 28 Sep 2016 14:10:05 +0200 Subject: [PATCH 1818/2266] Show gateway IP in ttnctl gateway status cmd --- ttnctl/cmd/gateways_status.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ttnctl/cmd/gateways_status.go b/ttnctl/cmd/gateways_status.go index f3913b3bf..44433f088 100644 --- a/ttnctl/cmd/gateways_status.go +++ b/ttnctl/cmd/gateways_status.go @@ -5,6 +5,7 @@ package cmd import ( "fmt" + "strings" "time" "github.com/TheThingsNetwork/ttn/api" @@ -66,6 +67,7 @@ var gatewaysStatusCmd = &cobra.Command{ printKV("Platform", resp.Status.Platform) printKV("Contact email", resp.Status.ContactEmail) printKV("Region", resp.Status.Region) + printKV("IP Address", strings.Join(resp.Status.Ip, ", ")) printKV("GPS coordinates", func() interface{} { if gps := resp.Status.Gps; gps != nil && !(gps.Latitude == 0 && gps.Longitude == 0) { return fmt.Sprintf("(%.6f %.6f)", gps.Latitude, gps.Longitude) From b2f9665f9d332d51ff614fffea07e7cf3dbabdd8 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 28 Sep 2016 16:38:52 +0200 Subject: [PATCH 1819/2266] Renamed Payload to PayloadRaw and Fields to PayloadFields, closes #276 --- core/handler/convert_fields.go | 12 ++++++------ core/handler/convert_fields_test.go | 28 ++++++++++++++-------------- core/handler/convert_lorawan.go | 8 ++++---- core/handler/convert_lorawan_test.go | 8 ++++---- core/handler/device/device_test.go | 10 +++++----- core/handler/downlink.go | 2 +- core/handler/downlink_test.go | 28 ++++++++++++++-------------- core/handler/mqtt.go | 4 ++-- core/handler/mqtt_test.go | 16 ++++++++-------- core/handler/uplink_test.go | 2 +- mqtt/client_test.go | 8 ++++---- mqtt/downlink_test.go | 26 +++++++++++++------------- mqtt/types.go | 24 ++++++++++++------------ mqtt/uplink_test.go | 26 +++++++++++++------------- ttnctl/cmd/downlink.go | 4 ++-- ttnctl/cmd/subscribe.go | 6 +++--- 16 files changed, 106 insertions(+), 106 deletions(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 5d56644f7..324237973 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -29,7 +29,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat Validator: app.Validator, } - fields, valid, err := functions.Process(appUp.Payload) + fields, valid, err := functions.Process(appUp.PayloadRaw) if err != nil { return nil // Do not set fields if processing failed } @@ -38,7 +38,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat return errors.NewErrInvalidArgument("Payload", "payload validator function returned false") } - appUp.Fields = fields + appUp.PayloadFields = fields return nil } @@ -278,11 +278,11 @@ func (f *DownlinkFunctions) Process(payload map[string]interface{}) ([]byte, boo // ConvertFieldsDown converts the fields into a payload func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { - if appDown.Fields == nil { + if appDown.PayloadFields == nil { return nil } - if appDown.Payload != nil { + if appDown.PayloadRaw != nil { return errors.NewErrInvalidArgument("Downlink", "Both Fields and Payload provided") } @@ -295,12 +295,12 @@ func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMes Encoder: app.Encoder, } - message, _, err := functions.Process(appDown.Fields) + message, _, err := functions.Process(appDown.PayloadFields) if err != nil { return err } - appDown.Payload = message + appDown.PayloadRaw = message return nil } diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index c5209561b..fc131e6f8 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -23,10 +23,10 @@ func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.Uplink DevId: "DevID-1", } appUp := &mqtt.UplinkMessage{ - FPort: 1, - AppID: "AppID-1", - DevID: "DevID-1", - Payload: []byte{0x08, 0x70}, + FPort: 1, + AppID: "AppID-1", + DevID: "DevID-1", + PayloadRaw: []byte{0x08, 0x70}, } return ttnUp, appUp } @@ -43,7 +43,7 @@ func TestConvertFieldsUp(t *testing.T) { ttnUp, appUp := buildConversionUplink() err := h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Fields, ShouldBeEmpty) + a.So(appUp.PayloadFields, ShouldBeEmpty) // Normal flow h.applications.Set(&application.Application{ @@ -54,7 +54,7 @@ func TestConvertFieldsUp(t *testing.T) { err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Fields, ShouldResemble, map[string]interface{}{ + a.So(appUp.PayloadFields, ShouldResemble, map[string]interface{}{ "temperature": 21.6, }) @@ -67,7 +67,7 @@ func TestConvertFieldsUp(t *testing.T) { ttnUp, appUp = buildConversionUplink() err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldNotBeNil) - a.So(appUp.Fields, ShouldBeEmpty) + a.So(appUp.PayloadFields, ShouldBeEmpty) // Function error h.applications.Set(&application.Application{ @@ -78,7 +78,7 @@ func TestConvertFieldsUp(t *testing.T) { ttnUp, appUp = buildConversionUplink() err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Fields, ShouldBeEmpty) + a.So(appUp.PayloadFields, ShouldBeEmpty) } func TestDecode(t *testing.T) { @@ -310,10 +310,10 @@ func buildConversionDownlink() (*pb_broker.DownlinkMessage, *mqtt.DownlinkMessag DevEui: &devEUI, } appDown := &mqtt.DownlinkMessage{ - FPort: 1, - AppID: "AppID-1", - DevID: "DevID-1", - Fields: map[string]interface{}{"temperature": 30}, + FPort: 1, + AppID: "AppID-1", + DevID: "DevID-1", + PayloadFields: map[string]interface{}{"temperature": 30}, // We want to "build" the payload with the content of the fields } return ttnDown, appDown @@ -331,7 +331,7 @@ func TestConvertFieldsDown(t *testing.T) { ttnDown, appDown := buildConversionDownlink() err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(appDown.Payload, ShouldBeEmpty) + a.So(appDown.PayloadRaw, ShouldBeEmpty) // Case2: Normal flow with Encoder h.applications.Set(&application.Application{ @@ -345,7 +345,7 @@ func TestConvertFieldsDown(t *testing.T) { ttnDown, appDown = buildConversionDownlink() err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(appDown.Payload, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) + a.So(appDown.PayloadRaw, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) } func TestProcessDownlinkInvalidFunction(t *testing.T) { diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index f43ea9cf6..ff6752069 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -60,7 +60,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli if !ok { return errors.NewErrInvalidArgument("Uplink FRMPayload", "must be of type *lorawan.DataPayload") } - appUp.Payload = payload.Bytes + appUp.PayloadRaw = payload.Bytes } } else { return errors.NewErrInvalidArgument("Uplink MACPayload", "could not get frame payload") @@ -101,7 +101,7 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMess } // Abort when downlink not needed - if len(appDown.Payload) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 { + if len(appDown.PayloadRaw) == 0 && !macPayload.FHDR.FCtrl.ACK && len(macPayload.FHDR.FOpts) == 0 { return ErrNotNeeded } @@ -111,8 +111,8 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMess } // Set Payload - if len(appDown.Payload) > 0 { - macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.Payload}} + if len(appDown.PayloadRaw) > 0 { + macPayload.FRMPayload = []lorawan.Payload{&lorawan.DataPayload{Bytes: appDown.PayloadRaw}} if macPayload.FPort == nil || *macPayload.FPort == 0 { macPayload.FPort = pointer.Uint8(1) } diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index 45cdac497..0fc2a0dcc 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -45,15 +45,15 @@ func TestConvertFromLoRaWAN(t *testing.T) { ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x20, 0x01, 0x00, 0x0A, 0x46, 0x55, 0x96, 0x42, 0x92, 0xF2}) err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) - a.So(appUp.Payload, ShouldResemble, []byte{0xaa, 0xbc}) + a.So(appUp.PayloadRaw, ShouldResemble, []byte{0xaa, 0xbc}) a.So(appUp.FCnt, ShouldEqual, 1) } func buildLorawanDownlink(payload []byte) (*mqtt.DownlinkMessage, *pb_broker.DownlinkMessage) { appDown := &mqtt.DownlinkMessage{ - DevID: "devid", - AppID: "appid", - Payload: []byte{0xaa, 0xbc}, + DevID: "devid", + AppID: "appid", + PayloadRaw: []byte{0xaa, 0xbc}, } ttnDown := &pb_broker.DownlinkMessage{ Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index 0f8fda285..067196d11 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -60,7 +60,7 @@ func TestNextDownlink(t *testing.T) { a := New(t) dev := &Device{ NextDownlink: &mqtt.DownlinkMessage{ - Fields: map[string]interface{}{ + PayloadFields: map[string]interface{}{ "string": "hello!", "int": 42, "bool": true, @@ -74,8 +74,8 @@ func TestNextDownlink(t *testing.T) { dev = &Device{} err = dev.parseProperty("next_downlink", `{"payload_fields":{"bool":true,"int":42,"string":"hello!"}}`) a.So(err, ShouldBeNil) - a.So(dev.NextDownlink.Fields, ShouldNotBeNil) - a.So(dev.NextDownlink.Fields["bool"], ShouldBeTrue) - a.So(dev.NextDownlink.Fields["int"], ShouldEqual, 42) - a.So(dev.NextDownlink.Fields["string"], ShouldEqual, "hello!") + a.So(dev.NextDownlink.PayloadFields, ShouldNotBeNil) + a.So(dev.NextDownlink.PayloadFields["bool"], ShouldBeTrue) + a.So(dev.NextDownlink.PayloadFields["int"], ShouldEqual, 42) + a.So(dev.NextDownlink.PayloadFields["string"], ShouldEqual, "hello!") } diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 51c94fa74..03fa8349c 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -102,7 +102,7 @@ func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb appDownlinkCopy := *appDownlink appDownlinkCopy.AppID = "" appDownlinkCopy.DevID = "" - appDownlinkCopy.Fields = make(map[string]interface{}) + appDownlinkCopy.PayloadFields = make(map[string]interface{}) h.mqttEvent <- &mqttEvent{ AppID: appDownlink.AppID, diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 9bfc742be..ac327fef3 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -38,7 +38,7 @@ func TestEnqueueDownlink(t *testing.T) { err = h.EnqueueDownlink(&mqtt.DownlinkMessage{ AppID: appID, DevID: devID, - Fields: map[string]interface{}{ + PayloadFields: map[string]interface{}{ "string": "hello!", "int": 42, "bool": true, @@ -47,7 +47,7 @@ func TestEnqueueDownlink(t *testing.T) { a.So(err, ShouldBeNil) dev, _ := h.devices.Get(appID, devID) a.So(dev.NextDownlink, ShouldNotBeEmpty) - a.So(dev.NextDownlink.Fields, ShouldHaveLength, 3) + a.So(dev.NextDownlink.PayloadFields, ShouldHaveLength, 3) } func TestHandleDownlink(t *testing.T) { @@ -97,9 +97,9 @@ func TestHandleDownlink(t *testing.T) { wg.Done() }() err = h.HandleDownlink(&mqtt.DownlinkMessage{ - AppID: appID, - DevID: devID, - Payload: []byte{0xAA, 0xBC}, + AppID: appID, + DevID: devID, + PayloadRaw: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, @@ -117,11 +117,11 @@ func TestHandleDownlink(t *testing.T) { }) jsonFields := map[string]interface{}{"temperature": 11} err = h.HandleDownlink(&mqtt.DownlinkMessage{ - FPort: 1, - AppID: appID, - DevID: devID, - Fields: jsonFields, - Payload: []byte{0xAA, 0xBC}, + FPort: 1, + AppID: appID, + DevID: devID, + PayloadFields: jsonFields, + PayloadRaw: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, @@ -137,10 +137,10 @@ func TestHandleDownlink(t *testing.T) { wg.Done() }() err = h.HandleDownlink(&mqtt.DownlinkMessage{ - FPort: 1, - AppID: appID, - DevID: devID, - Fields: jsonFields, + FPort: 1, + AppID: appID, + DevID: devID, + PayloadFields: jsonFields, }, &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 4fb953259..2e05db1ad 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -62,8 +62,8 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e h.Ctx.Warn("Uplink publish timeout") } }() - if len(up.Fields) > 0 { - fieldsToken := h.mqttClient.PublishUplinkFields(up.AppID, up.DevID, up.Fields) + if len(up.PayloadFields) > 0 { + fieldsToken := h.mqttClient.PublishUplinkFields(up.AppID, up.DevID, up.PayloadFields) go func() { if fieldsToken.WaitTimeout(MQTTTimeout) { if fieldsToken.Error() != nil { diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index e8bf94bae..d80c94b2b 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -40,9 +40,9 @@ func TestHandleMQTT(t *testing.T) { a.So(err, ShouldBeNil) c.PublishDownlink(mqtt.DownlinkMessage{ - AppID: appID, - DevID: devID, - Payload: []byte{0xAA, 0xBC}, + AppID: appID, + DevID: devID, + PayloadRaw: []byte{0xAA, 0xBC}, }).Wait() <-time.After(50 * time.Millisecond) dev, _ := h.devices.Get(appID, devID) @@ -52,15 +52,15 @@ func TestHandleMQTT(t *testing.T) { c.SubscribeDeviceUplink(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req mqtt.UplinkMessage) { a.So(r_appID, ShouldEqual, appID) a.So(r_devID, ShouldEqual, devID) - a.So(req.Payload, ShouldResemble, []byte{0xAA, 0xBC}) + a.So(req.PayloadRaw, ShouldResemble, []byte{0xAA, 0xBC}) wg.Done() }).Wait() h.mqttUp <- &mqtt.UplinkMessage{ - DevID: devID, - AppID: appID, - Payload: []byte{0xAA, 0xBC}, - Fields: map[string]interface{}{ + DevID: devID, + AppID: appID, + PayloadRaw: []byte{0xAA, 0xBC}, + PayloadFields: map[string]interface{}{ "field": "value", }, } diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index 55c5e340b..aaed87e8d 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -120,7 +120,7 @@ func TestHandleUplink(t *testing.T) { AppEUI: appEUI, DevEUI: devEUI, NextDownlink: &mqtt.DownlinkMessage{ - Payload: []byte{0xaa, 0xbc}, + PayloadRaw: []byte{0xaa, 0xbc}, }, }) wg.Add(2) diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 313b9dbca..cad99acc4 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -168,10 +168,10 @@ func ExampleDefaultClient_SubscribeDeviceUplink() { func ExampleDefaultClient_PublishDownlink() { token := exampleClient.PublishDownlink(DownlinkMessage{ - AppID: "my-app-id", - DevID: "my-dev-id", - FPort: 1, - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "my-app-id", + DevID: "my-dev-id", + FPort: 1, + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) token.Wait() if err := token.Error(); err != nil { diff --git a/mqtt/downlink_test.go b/mqtt/downlink_test.go index 47a46a736..de5ab3b4f 100644 --- a/mqtt/downlink_test.go +++ b/mqtt/downlink_test.go @@ -21,9 +21,9 @@ func TestPublishDownlink(t *testing.T) { defer c.Disconnect() dataDown := DownlinkMessage{ - AppID: "someid", - DevID: "someid", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "someid", + DevID: "someid", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, } token := c.PublishDownlink(dataDown) @@ -102,9 +102,9 @@ func TestPubSubDownlink(t *testing.T) { waitForOK(subToken, a) pubToken := c.PublishDownlink(DownlinkMessage{ - AppID: "app3", - DevID: "dev3", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app3", + DevID: "dev3", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) @@ -126,21 +126,21 @@ func TestPubSubAppDownlink(t *testing.T) { subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { a.So(appID, ShouldResemble, "app4") - a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }) waitForOK(subToken, a) pubToken := c.PublishDownlink(DownlinkMessage{ - AppID: "app4", - DevID: "dev1", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app4", + DevID: "dev1", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) pubToken = c.PublishDownlink(DownlinkMessage{ - AppID: "app4", - DevID: "dev2", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app4", + DevID: "dev2", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) diff --git a/mqtt/types.go b/mqtt/types.go index c09570d8d..4038224d4 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -76,22 +76,22 @@ type Metadata struct { // UplinkMessage represents an application-layer uplink message type UplinkMessage struct { - AppID string `json:"app_id,omitempty"` - DevID string `json:"dev_id,omitempty"` - FPort uint8 `json:"port"` - FCnt uint32 `json:"counter"` - Payload []byte `json:"payload_raw"` - Fields map[string]interface{} `json:"payload_fields,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + FCnt uint32 `json:"counter"` + PayloadRaw []byte `json:"payload_raw"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` } // DownlinkMessage represents an application-layer downlink message type DownlinkMessage struct { - AppID string `json:"app_id,omitempty"` - DevID string `json:"dev_id,omitempty"` - FPort uint8 `json:"port"` - Payload []byte `json:"payload_raw,omitempty"` - Fields map[string]interface{} `json:"payload_fields,omitempty"` + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + PayloadRaw []byte `json:"payload_raw,omitempty"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` } // Activation messages are used to notify application of a device activation diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go index d4cbf45cc..fb5ec91d8 100644 --- a/mqtt/uplink_test.go +++ b/mqtt/uplink_test.go @@ -23,9 +23,9 @@ func TestPublishUplink(t *testing.T) { defer c.Disconnect() dataUp := UplinkMessage{ - AppID: "someid", - DevID: "someid", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "someid", + DevID: "someid", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, } token := c.PublishUplink(dataUp) @@ -166,9 +166,9 @@ func TestPubSubUplink(t *testing.T) { waitForOK(subToken, a) pubToken := c.PublishUplink(UplinkMessage{ - Payload: []byte{0x01, 0x02, 0x03, 0x04}, - AppID: "app1", - DevID: "dev1", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app1", + DevID: "dev1", }) waitForOK(pubToken, a) @@ -194,21 +194,21 @@ func TestPubSubAppUplink(t *testing.T) { subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { a.So(appID, ShouldResemble, "app2") - a.So(req.Payload, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }) waitForOK(subToken, a) pubToken := c.PublishUplink(UplinkMessage{ - AppID: "app2", - DevID: "dev1", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app2", + DevID: "dev1", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) pubToken = c.PublishUplink(UplinkMessage{ - AppID: "app2", - DevID: "dev2", - Payload: []byte{0x01, 0x02, 0x03, 0x04}, + AppID: "app2", + DevID: "dev2", + PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index f7dfe7661..3f71eb4cc 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -77,7 +77,7 @@ $ ttnctl downlink test --json '{"led":"on"}' ctx.WithError(err).Fatal("You are providing a valid HEX payload while sending payload in JSON.") } - err = json.Unmarshal([]byte(args[1]), &message.Fields) + err = json.Unmarshal([]byte(args[1]), &message.PayloadFields) if err != nil { ctx.WithError(err).Fatal("Invalid json string") @@ -89,7 +89,7 @@ $ ttnctl downlink test --json '{"led":"on"}' ctx.WithError(err).Fatal("Invalid Payload") } - message.Payload = payload + message.PayloadRaw = payload } token := client.PublishDownlink(message) token.Wait() diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 34d51f94a..3615ae4cb 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -43,10 +43,10 @@ var subscribeCmd = &cobra.Command{ printKV("DevID", devID) printKV("Port", req.FPort) printKV("FCnt", req.FCnt) - printKV("Payload (hex)", req.Payload) - if len(req.Fields) > 0 { + printKV("Payload (hex)", req.PayloadRaw) + if len(req.PayloadFields) > 0 { ctx.Info("Decoded fields") - for k, v := range req.Fields { + for k, v := range req.PayloadFields { printKV(k, v) } } From 87c9eddf5faae2ee5b997ac56b0f7106c6813eaf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 28 Sep 2016 17:37:49 +0200 Subject: [PATCH 1820/2266] Better error message for double downlink subscribe --- core/router/downlink.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/downlink.go b/core/router/downlink.go index fb29b8750..4b650646c 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -42,7 +42,7 @@ func (r *router) SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage }() return toGateway, nil } - return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayID)) + return nil, errors.NewErrInternal(fmt.Sprintf("Already subscribed to downlink for %s", gatewayID)) } func (r *router) UnsubscribeDownlink(gatewayID string) error { From d9247a336ecd26bbe520b78f81a9090781f0e44e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 28 Sep 2016 18:01:40 +0200 Subject: [PATCH 1821/2266] Teardown stream after downlink error in router client --- api/router/client.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/api/router/client.go b/api/router/client.go index 38e710687..c0ad3247f 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -270,15 +270,14 @@ func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, erro if grpc.Code(err) == codes.Canceled { api.GetLogger().Debugf("Downlink stream for %s was canceled", c.id) errChan <- nil - c.teardownDownlink() - return + } else { + api.GetLogger().Warnf("Error receiving gateway downlink for %s: %s", c.id, err.Error()) + errChan <- errors.FromGRPCError(err) } - errChan <- errors.FromGRPCError(err) - api.GetLogger().Warnf("Error receiving gateway downlink for %s: %s", c.id, err.Error()) - } else { - downChan <- downlink + c.teardownDownlink() + return } - + downChan <- downlink } } }() From 37b761ef0106a33f5f3a967351a3a070e0b27ed0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 28 Sep 2016 18:21:25 +0200 Subject: [PATCH 1822/2266] Fix typo in Router client --- api/router/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/router/client.go b/api/router/client.go index c0ad3247f..2893d9564 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -24,7 +24,7 @@ type GatewayClient interface { SendGatewayStatus(*gateway.Status) error SendUplink(*UplinkMessage) error Subscribe() (<-chan *DownlinkMessage, <-chan error, error) - Unsbscribe() error + Unsubscribe() error Activate(*DeviceActivationRequest) (*DeviceActivationResponse, error) Close() error } @@ -284,7 +284,7 @@ func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, erro return downChan, errChan, nil } -func (c *gatewayClient) Unsbscribe() error { +func (c *gatewayClient) Unsubscribe() error { c.teardownDownlink() return nil } From ce0b7cb05f009499f4f139ac9dd8449c831a37e6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 29 Sep 2016 11:28:56 +0200 Subject: [PATCH 1823/2266] Fix ttnctl devices personalize cmd --- ttnctl/cmd/devices_personalize.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index e9259aa29..63cce36ed 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -45,7 +45,7 @@ var devicesPersonalizeCmd = &cobra.Command{ var nwkSKey types.NwkSKey if len(args) > 1 { - nwkSKey, err = types.ParseNwkSKey(args[2]) + nwkSKey, err = types.ParseNwkSKey(args[1]) if err != nil { ctx.Fatalf("Invalid NwkSKey: %s", err) } @@ -56,7 +56,7 @@ var devicesPersonalizeCmd = &cobra.Command{ var appSKey types.AppSKey if len(args) > 2 { - appSKey, err = types.ParseAppSKey(args[3]) + appSKey, err = types.ParseAppSKey(args[2]) if err != nil { ctx.Fatalf("Invalid AppSKey: %s", err) } From c19b0028d152432f77f8f5ae0d58c0323d90dd3d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 23 Sep 2016 14:45:27 +0200 Subject: [PATCH 1824/2266] Remove code that is moved to go-account-lib --- core/account/account.go | 61 ------- core/account/applications.go | 131 --------------- core/account/auth/auth.go | 48 ------ core/account/components.go | 91 ----------- core/account/gateways.go | 117 -------------- core/account/main.go | 4 - core/account/types.go | 80 ---------- core/account/types_test.go | 34 ---- core/account/users.go | 99 ------------ core/account/util/http.go | 178 --------------------- core/account/util/http_test.go | 248 ----------------------------- core/account/util/oauth.go | 26 --- core/account/util/validate.go | 44 ----- core/account/util/validate_test.go | 72 --------- utils/tokenkey/tokenkey.go | 110 ------------- 15 files changed, 1343 deletions(-) delete mode 100644 core/account/account.go delete mode 100644 core/account/applications.go delete mode 100644 core/account/auth/auth.go delete mode 100644 core/account/components.go delete mode 100644 core/account/gateways.go delete mode 100644 core/account/main.go delete mode 100644 core/account/types.go delete mode 100644 core/account/types_test.go delete mode 100644 core/account/users.go delete mode 100644 core/account/util/http.go delete mode 100644 core/account/util/http_test.go delete mode 100644 core/account/util/oauth.go delete mode 100644 core/account/util/validate.go delete mode 100644 core/account/util/validate_test.go delete mode 100644 utils/tokenkey/tokenkey.go diff --git a/core/account/account.go b/core/account/account.go deleted file mode 100644 index 538f3b8e3..000000000 --- a/core/account/account.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "github.com/TheThingsNetwork/ttn/core/account/auth" - "github.com/TheThingsNetwork/ttn/core/account/util" -) - -// Account is a client to an account server -type Account struct { - server string - auth auth.Strategy -} - -// New creates a new account client that will use the -// accessToken to make requests to the specified account server -func New(server, accessToken string) *Account { - return &Account{ - server: server, - auth: auth.AccessToken(accessToken), - } -} - -// NewWithKey creates an account client that uses an accessKey to -// authenticate -func NewWithKey(server, accessKey string) *Account { - return &Account{ - server: server, - auth: auth.AccessKey(accessKey), - } -} - -// NewWithPublic creates an account client that does not use authentication -func NewWithPublic(server string) *Account { - return &Account{ - server: server, - auth: auth.Public, - } -} - -func (a *Account) get(URI string, res interface{}) error { - return util.GET(a.server, a.auth, URI, res) -} - -func (a *Account) put(URI string, body, res interface{}) error { - return util.PUT(a.server, a.auth, URI, body, res) -} - -func (a *Account) post(URI string, body, res interface{}) error { - return util.POST(a.server, a.auth, URI, body, res) -} - -func (a *Account) patch(URI string, body, res interface{}) error { - return util.PATCH(a.server, a.auth, URI, body, res) -} - -func (a *Account) del(URI string) error { - return util.DELETE(a.server, a.auth, URI) -} diff --git a/core/account/applications.go b/core/account/applications.go deleted file mode 100644 index 61e36d8b3..000000000 --- a/core/account/applications.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "fmt" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -// ListApplications list all applications -func (a *Account) ListApplications() (apps []Application, err error) { - err = a.get("/applications", &apps) - return apps, err -} - -// FindApplication gets a specific application from the account server -func (a *Account) FindApplication(appID string) (app Application, err error) { - err = a.get(fmt.Sprintf("/applications/%s", appID), &app) - return app, err -} - -type createApplicationReq struct { - Name string `json:"name" valid:"required"` - AppID string `json:"id" valid:"required"` - EUIs []types.AppEUI `json:"euis"` -} - -// CreateApplication creates a new application on the account server -func (a *Account) CreateApplication(appID string, name string, EUIs []types.AppEUI) (app Application, err error) { - body := createApplicationReq{ - Name: name, - AppID: appID, - EUIs: EUIs, - } - - err = a.post("/applications", &body, &app) - return app, err -} - -// DeleteApplication deletes an application -func (a *Account) DeleteApplication(appID string) error { - return a.del(fmt.Sprintf("/applications/%s", appID)) -} - -type grantReq struct { - Rights []types.Right `json:"rights"` -} - -// Grant adds a collaborator to the application -func (a *Account) Grant(appID string, username string, rights []types.Right) error { - req := grantReq{ - Rights: rights, - } - return a.put(fmt.Sprintf("/applications/%s/collaborators/%s", appID, username), req, nil) -} - -// Retract removes rights from a collaborator of the application -func (a *Account) Retract(appID string, username string) error { - return a.del(fmt.Sprintf("/applications/%s/collaborators/%s", appID, username)) -} - -type addAccessKeyReq struct { - Name string `json:"name" valid:"required"` - Rights []types.Right `json:"rights" valid:"required"` -} - -// AddAccessKey adds an access key to the application with the specified name -// and rights -func (a *Account) AddAccessKey(appID string, name string, rights []types.Right) (key types.AccessKey, err error) { - body := addAccessKeyReq{ - Name: name, - Rights: rights, - } - err = a.post(fmt.Sprintf("/applications/%s/access-keys", appID), body, &key) - return key, err -} - -// RemoveAccessKey removes the specified access key from the application -func (a *Account) RemoveAccessKey(appID string, name string) error { - return a.del(fmt.Sprintf("/applications/%s/access-keys/%s", appID, name)) -} - -type editAppReq struct { - Name string `json:"name,omitempty"` -} - -// ChangeName changes the application name -func (a *Account) ChangeName(appID string, name string) (app Application, err error) { - body := editAppReq{ - Name: name, - } - err = a.patch(fmt.Sprintf("/applications/%s", appID), body, &app) - return app, err -} - -// AddEUI adds an EUI to the applications list of EUIs -func (a *Account) AddEUI(appID string, eui types.AppEUI) error { - return a.put(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String()), nil, nil) -} - -type genEUIRes struct { - EUI types.AppEUI `json:"eui"` -} - -// GenerateEUI creates a new EUI for the application -func (a *Account) GenerateEUI(appID string) (*types.AppEUI, error) { - var res genEUIRes - err := a.post(fmt.Sprintf("/applications/%s/euis", appID), nil, &res) - if err != nil { - return nil, err - } - return &res.EUI, nil -} - -// RemoveEUI removes the specified EUI from the application -func (a *Account) RemoveEUI(appID string, eui types.AppEUI) error { - return a.del(fmt.Sprintf("/applications/%s/euis/%s", appID, eui.String())) -} - -// AppRights returns the rights the current account client has to a certain -// application -func (a *Account) AppRights(appID string) (rights []types.Right, err error) { - err = a.get(fmt.Sprintf("/applications/%s/rights", appID), &rights) - if err != nil { - return nil, err - } - - return rights, nil -} diff --git a/core/account/auth/auth.go b/core/account/auth/auth.go deleted file mode 100644 index 1e2ae5f73..000000000 --- a/core/account/auth/auth.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package auth - -import ( - "fmt" - "net/http" -) - -type Strategy interface { - DecorateRequest(*http.Request) -} - -type accessToken struct { - accessToken string -} - -func (a *accessToken) DecorateRequest(req *http.Request) { - req.Header.Set("Authorization", fmt.Sprintf("bearer %s", a.accessToken)) -} - -func AccessToken(s string) *accessToken { - return &accessToken{ - accessToken: s, - } -} - -type accessKey struct { - accessKey string -} - -func (a *accessKey) DecorateRequest(req *http.Request) { - req.Header.Set("Authorization", fmt.Sprintf("key %s", a.accessKey)) -} - -func AccessKey(s string) *accessKey { - return &accessKey{ - accessKey: s, - } -} - -type public struct{} - -func (p *public) DecorateRequest(req *http.Request) { -} - -var Public = &public{} diff --git a/core/account/components.go b/core/account/components.go deleted file mode 100644 index db24668f8..000000000 --- a/core/account/components.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "fmt" - "time" -) - -// ListComponents lists all of the users components -func (a *Account) ListComponents() (components []Component, err error) { - err = a.get("/api/components", &components) - return components, err -} - -// FindComponent finds a comonent of the specified type with the specified id -func (a *Account) FindComponent(typ, id string) (component []Component, err error) { - err = a.get(fmt.Sprintf("/api/components/%s/%s", typ, id), &component) - return component, err -} - -// FindBroker finds a broker with the specified id -func (a *Account) FindBroker(id string) (component []Component, err error) { - return a.FindComponent("broker", id) -} - -// FindRouter finds a router with the specified id -func (a *Account) FindRouter(id string) (component []Component, err error) { - return a.FindComponent("router", id) -} - -// FindHandler finds a handler with the specified id -func (a *Account) FindHandler(id string) (component []Component, err error) { - return a.FindComponent("handler", id) -} - -type createComponentReq struct { - ID string `json:"id" valid:"required"` -} - -// CreateComponent creates a component with the specified type and id -func (a *Account) CreateComponent(typ, id string) error { - body := createComponentReq{ - ID: id, - } - return a.post(fmt.Sprintf("/api/components/%s", typ), body, nil) -} - -// CreateBroker creates a broker with the specified id -func (a *Account) CreateBroker(id string) error { - return a.CreateComponent("broker", id) -} - -// CreateRouter creates a Router with the specified id -func (a *Account) CreateRouter(id string) error { - return a.CreateComponent("router", id) -} - -// CreateHandler creates a handler with the specified id -func (a *Account) CreateHandler(id string) error { - return a.CreateComponent("handler", id) -} - -type componentTokenRes struct { - Token string `json:"token"` - Expires time.Time `json:"expires"` -} - -// ComponentToken fetches a token for the component with the given -// type and id -func (a *Account) ComponentToken(typ, id string) (token string, err error) { - var res componentTokenRes - err = a.get(fmt.Sprintf("/api/components/%s/%s/token", typ, id), &res) - return res.Token, err -} - -// BrokerToken gets the specified brokers token -func (a *Account) BrokerToken(id string) (token string, err error) { - return a.ComponentToken("broker", id) -} - -// RouterToken gets the specified routers token -func (a *Account) RouterToken(id string) (token string, err error) { - return a.ComponentToken("router", id) -} - -// HandlerToken gets the specified handlers token -func (a *Account) HandlerToken(id string) (token string, err error) { - return a.ComponentToken("handler", id) -} diff --git a/core/account/gateways.go b/core/account/gateways.go deleted file mode 100644 index 363162b70..000000000 --- a/core/account/gateways.go +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "errors" - "fmt" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -// ListGateways list all gateways -func (a *Account) ListGateways() (gateways []Gateway, err error) { - err = a.get("/gateways", &gateways) - return gateways, err -} - -// FindGateway returns the information about a specific gateay -func (a *Account) FindGateway(gatewayID string) (gateway Gateway, err error) { - err = a.get(fmt.Sprintf("/gateways/%s", gatewayID), &gateway) - return gateway, err -} - -type registerGatewayReq struct { - // ID is the ID of the new gateway (required) - ID string `json:"id"` - - // Country is the country code of the new gateway (required) - FrequencyPlan string `json:"frequency_plan"` - - // Location is the location of the new gateway - Location *Location `json:"location,omitempty"` -} - -// RegisterGateway registers a new gateway on the account server -func (a *Account) RegisterGateway(gatewayID string, frequencyPlan string, location *Location) (gateway Gateway, err error) { - if gatewayID == "" { - return gateway, errors.New("Cannot create gateway: no ID given") - } - - if frequencyPlan == "" { - return gateway, errors.New("Cannot create gateway: no FrequencyPlan given") - } - - req := registerGatewayReq{ - ID: gatewayID, - FrequencyPlan: frequencyPlan, - Location: location, - } - - err = a.post("/gateways", req, &gateway) - return gateway, err -} - -// DeleteGateway removes a gateway from the account server -func (a *Account) DeleteGateway(gatewayID string) error { - return a.del(fmt.Sprintf("/gateways/%s", gatewayID)) -} - -// GrantGatewayRights grants rights to a collaborator of the gateway -func (a *Account) GrantGatewayRights(gatewayID string, username string, rights []types.Right) error { - req := grantReq{ - Rights: rights, - } - return a.put(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username), req, nil) -} - -// RetractGatewayRights removes rights from a collaborator of the gateway -func (a *Account) RetractGatewayRights(gatewayID string, username string) error { - return a.del(fmt.Sprintf("/gateways/%s/collaborators/%s", gatewayID, username)) -} - -// GatewayEdits contains editable fields of gateways -type GatewayEdits struct { - Owner string `json:"owner,omitempty"` - PublicRights []types.Right `json:"public_rights,omitempty"` - FrequencyPlan string `json:"frequency_plan,omitempty"` - Location *Location `json:"location,omitempty"` -} - -// EditGateway edits the fields of a gateway -func (a *Account) EditGateway(gatewayID string, edits GatewayEdits) (gateway Gateway, err error) { - err = a.patch(fmt.Sprintf("/gateways/%s", gatewayID), edits, &gateway) - return gateway, err -} - -// TransferOwnership transfers the owenership of the gateway to another user -func (a *Account) TransferOwnership(gatewayID, username string) (Gateway, error) { - return a.EditGateway(gatewayID, GatewayEdits{ - Owner: username, - }) -} - -// SetPublicRights changes the publicily visible rights of the gateway -func (a *Account) SetPublicRights(gatewayID string, rights []types.Right) (Gateway, error) { - return a.EditGateway(gatewayID, GatewayEdits{ - PublicRights: rights, - }) -} - -// ChangeFrequencyPlan changes the requency plan of a gateway -func (a *Account) ChangeFrequencyPlan(gatewayID, plan string) (Gateway, error) { - return a.EditGateway(gatewayID, GatewayEdits{ - FrequencyPlan: plan, - }) -} - -// ChangeLocation changes the location of the gateway -func (a *Account) ChangeLocation(gatewayID string, latitude, longitude float64) (Gateway, error) { - return a.EditGateway(gatewayID, GatewayEdits{ - Location: &Location{ - Longitude: longitude, - Latitude: latitude, - }, - }) -} diff --git a/core/account/main.go b/core/account/main.go deleted file mode 100644 index 0abd0facd..000000000 --- a/core/account/main.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account diff --git a/core/account/types.go b/core/account/types.go deleted file mode 100644 index 0baeef3f5..000000000 --- a/core/account/types.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -// Application represents an application on The Things Network -type Application struct { - ID string `json:"id" valid:"required"` - Name string `json:"name" valid:"required"` - EUIs []types.AppEUI `json:"euis,omitempty"` - AccessKeys []types.AccessKey `json:"access_keys,omitempty"` - Created time.Time `json:"created,omitempty"` - Collaborators []Collaborator `json:"collaborators,omitempty"` -} - -// Collaborator is a user that has rights to a certain application -type Collaborator struct { - Username string `json:"username" valid:"required"` - Rights []types.Right `json:"rights" valid:"required"` -} - -// HasRight checks if the collaborator has a specific right -func (c *Collaborator) HasRight(right types.Right) bool { - for _, r := range c.Rights { - if r == right { - return true - } - } - return false -} - -// Profile represents the profile of a user -type Profile struct { - Username string `json:"username"` - Email string `json:"email"` - Name *Name `json:"name"` -} - -// Name represents the full name of a user -type Name struct { - First string `json:"first"` - Last string `json:"last"` -} - -// Component represents a component on the newtork -type Component struct { - Type string `json:"type"` - ID string `json:"id"` - Created time.Time `json:"created,omitempty"` -} - -// String implements the Stringer interface for Name -func (n *Name) String() string { - return n.First + " " + n.Last -} - -// Gateway represents a gateway on the account server -type Gateway struct { - ID string `json:"id" valid:"required"` - Activated bool `json:"activated"` - FrequencyPlan string `json:"frequency_plan"` - FrequencyPlanURL string `json:"frequency_plan_url"` - LocationPublic bool `json:"location_public"` - StatusPublic bool `json:"status_public"` - Location *Location `json:"location"` - Collaborators []Collaborator `json:"collaborator"` - Key string `json:"key"` -} - -// Location is the GPS location of a gateway -type Location struct { - Longitude float64 `json:"lng"` - Latitude float64 `json:"lat"` -} diff --git a/core/account/types_test.go b/core/account/types_test.go deleted file mode 100644 index 85f775131..000000000 --- a/core/account/types_test.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "testing" - - "github.com/TheThingsNetwork/ttn/core/types" - s "github.com/smartystreets/assertions" -) - -func TestCollaboratorRights(t *testing.T) { - a := s.New(t) - c := Collaborator{ - Username: "username", - Rights: []types.Right{ - types.Right("right"), - }, - } - - a.So(c.HasRight(types.Right("right")), s.ShouldBeTrue) - a.So(c.HasRight(types.Right("foo")), s.ShouldBeFalse) -} - -func TestNameString(t *testing.T) { - a := s.New(t) - name := Name{ - First: "John", - Last: "Doe", - } - - a.So(name.String(), s.ShouldEqual, "John Doe") -} diff --git a/core/account/users.go b/core/account/users.go deleted file mode 100644 index 65cb1a6c2..000000000 --- a/core/account/users.go +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package account - -import ( - "fmt" - - "github.com/TheThingsNetwork/ttn/core/account/auth" - "github.com/TheThingsNetwork/ttn/core/account/util" -) - -type registerUserReq struct { - Username string `json:"username,omitempty"` - Email string `json:"email"` - Password string `json:"password"` -} - -// RegisterUser registers a new user with the specified username, email and -// password on the specified account server -func RegisterUser(server, username, email, password string) error { - user := registerUserReq{ - Username: username, - Email: email, - Password: password, - } - - err := util.POST(server, auth.Public, "/api/users", user, nil) - if err != nil { - return fmt.Errorf("Could not register user: %s", err) - } - return nil -} - -// Profile gets the user profile of the user that is logged in -func (a *Account) Profile() (user Profile, err error) { - err = a.get("/api/users/me", &user) - if err != nil { - return user, fmt.Errorf("Could not get user profile: %s", err) - } - - return user, nil -} - -type nameReq struct { - First string `json:"first,omitemtpy"` - Last string `json:"last,omitempty"` -} - -type editProfileReq struct { - Username string `json:"username,omitempty"` - Email string `json:"email,omitempty"` - Name *Name `json:"name,omitempty"` -} - -// EditProfile edits the users profile. You can change only -// part of the profile (only the name, for instance) by -// omitting the other fields from the passed in Profile struct. -func (a *Account) EditProfile(profile Profile) error { - var edits editProfileReq - - if profile.Username != "" { - edits.Username = profile.Username - } - - if profile.Email != "" { - edits.Email = profile.Email - } - - if profile.Name != nil { - edits.Name = profile.Name - } - - err := a.patch("/api/users/me", edits, nil) - if err != nil { - return fmt.Errorf("Could not update profile: %s", err) - } - return err -} - -type editPasswordReq struct { - OldPassword string `json:"old_password"` - Password string `json:"password"` -} - -// EditPassword edits the users password, it requires the old password -// to be given. -func (a *Account) EditPassword(oldPassword, newPassword string) error { - edits := editPasswordReq{ - OldPassword: oldPassword, - Password: newPassword, - } - - err := a.patch("/api/users/me", edits, nil) - if err != nil { - return fmt.Errorf("Could change not password: %s", err) - } - return nil -} diff --git a/core/account/util/http.go b/core/account/util/http.go deleted file mode 100644 index 437ddc74a..000000000 --- a/core/account/util/http.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - - "github.com/TheThingsNetwork/ttn/core/account/auth" - "github.com/TheThingsNetwork/ttn/utils/errors" -) - -var ( - // MaxRedirects specifies the maximum number of redirects an HTTP - // request should be able to make - MaxRedirects = 5 -) - -// HTTPError represents an error coming over HTTP, -// it is not an error with executing the request itself, it is -// an error the server is flaggin to the client. -type HTTPError struct { - Code int `json:"code"` - Message string `json:"error"` -} - -func (e HTTPError) Error() string { - return e.Message -} - -// checkRedirect implements this clients redirection policy -func checkRedirect(req *http.Request, via []*http.Request) error { - if len(via) > MaxRedirects { - return errors.New("Maximum number of redirects reached") - } - - // use the same headers as before - req.Header.Set("Authorization", via[len(via)-1].Header.Get("Authorization")) - req.Header.Set("Accept", "application/json") - req.Header.Set("Content-Type", "application/json") - return nil -} - -// NewRequest creates a new http.Request that has authorization set up -func newRequest(server, method string, URI string, body io.Reader) (*http.Request, error) { - URL := fmt.Sprintf("%s%s", server, URI) - req, err := http.NewRequest(method, URL, body) - if err != nil { - return nil, err - } - - req.Header.Set("Accept", "application/json") - req.Header.Set("Content-Type", "application/json") - - return req, nil -} - -func performRequest(server string, strategy auth.Strategy, method, URI string, body, res interface{}, redirects int) (err error) { - var req *http.Request - - if body != nil { - // body is not nil, so serialize it and pass it in the request - if err = Validate(body); err != nil { - return fmt.Errorf("Got an illegal request body: %s", err) - } - - buf := new(bytes.Buffer) - encoder := json.NewEncoder(buf) - err = encoder.Encode(body) - if err != nil { - return err - } - req, err = newRequest(server, method, URI, buf) - } else { - // body is nil so create a nil request - req, err = newRequest(server, method, URI, nil) - } - - // decorate the request - strategy.DecorateRequest(req) - - if err != nil { - return err - } - - client := &http.Client{ - CheckRedirect: checkRedirect, - } - resp, err := client.Do(req) - if err != nil { - return err - } - - if resp.StatusCode >= 400 { - - var herr HTTPError - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(&herr); err != nil { - // could not decode body as error, just return http error - return HTTPError{ - Code: resp.StatusCode, - Message: resp.Status[4:], - } - } - - // fill in blank code - if herr.Code == 0 { - herr.Code = resp.StatusCode - } - - // fill in blank message - if herr.Message == "" { - herr.Message = resp.Status[4:] - } - - return herr - } - - if resp.StatusCode == 307 { - if redirects > 0 { - location := resp.Header.Get("Location") - return performRequest(server, strategy, method, location, body, res, redirects-1) - } - return fmt.Errorf("Reached maximum number of redirects") - } - - if resp.StatusCode >= 300 { - // 307 is handled, 301, 302, 304 cannot be - return fmt.Errorf("Unexpected %v redirection to %s", resp.StatusCode, resp.Header.Get("Location")) - } - - if res != nil { - defer resp.Body.Close() - decoder := json.NewDecoder(resp.Body) - if err := decoder.Decode(res); err != nil { - return err - } - - if err := Validate(res); err != nil { - return fmt.Errorf("Got an illegal response from server: %s", err) - } - } - - return nil -} - -// GET does a get request to the account server, decoding the result into the object pointed to byres -func GET(server string, strategy auth.Strategy, URI string, res interface{}) error { - return performRequest(server, strategy, "GET", URI, nil, res, MaxRedirects) -} - -// DELETE does a delete request to the account server -func DELETE(server string, strategy auth.Strategy, URI string) error { - return performRequest(server, strategy, "DELETE", URI, nil, nil, MaxRedirects) -} - -// POST creates an HTTP Post request to the specified server, with the body -// encoded as JSON, decoding the result into the object pointed to byres -func POST(server string, strategy auth.Strategy, URI string, body, res interface{}) error { - return performRequest(server, strategy, "POST", URI, body, res, MaxRedirects) -} - -// PUT creates an HTTP Put request to the specified server, with the body -// encoded as JSON, decoding the result into the object pointed to byres -func PUT(server string, strategy auth.Strategy, URI string, body, res interface{}) error { - return performRequest(server, strategy, "PUT", URI, body, res, MaxRedirects) -} - -// PATCH creates an HTTP Patch request to the specified server, with the body -// encoded as JSON, decoding the result into the object pointed to byres -func PATCH(server string, strategy auth.Strategy, URI string, body, res interface{}) error { - return performRequest(server, strategy, "PATCH", URI, body, res, MaxRedirects) -} diff --git a/core/account/util/http_test.go b/core/account/util/http_test.go deleted file mode 100644 index 0dcc1c492..000000000 --- a/core/account/util/http_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - - "github.com/TheThingsNetwork/ttn/core/account/auth" - . "github.com/smartystreets/assertions" -) - -var ( - url = "/foo" - token = "token" - tokenStrategy = auth.AccessToken(token) -) - -type OKResp struct { - OK string `json:"token"` -} - -type FooResp struct { - Foo string `json:"foo" valid:"required"` -} - -func OKHandler(a *Assertion, method string) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, url) - a.So(r.Method, ShouldEqual, method) - a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) - resp := OKResp{ - OK: token, - } - w.WriteHeader(http.StatusOK) - encoder := json.NewEncoder(w) - encoder.Encode(&resp) - }) -} - -func FooHandler(a *Assertion, method string) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, url) - a.So(r.Method, ShouldEqual, method) - a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) - resp := FooResp{ - Foo: token, - } - w.WriteHeader(http.StatusOK) - encoder := json.NewEncoder(w) - encoder.Encode(&resp) - }) -} - -func RedirectHandler(a *Assertion, method string) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) - if r.RequestURI == url { - w.Header().Set("Location", "/bar") - w.WriteHeader(307) - } else { - a.So(r.RequestURI, ShouldEqual, "/bar") - resp := FooResp{ - Foo: token, - } - w.WriteHeader(http.StatusOK) - encoder := json.NewEncoder(w) - encoder.Encode(&resp) - } - }) -} - -func EchoHandler(a *Assertion, method string) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - a.So(r.RequestURI, ShouldEqual, url) - a.So(r.Method, ShouldEqual, method) - a.So(r.Header.Get("Authorization"), ShouldEqual, "bearer "+token) - w.WriteHeader(http.StatusOK) - defer r.Body.Close() - body, err := ioutil.ReadAll(r.Body) - a.So(err, ShouldBeNil) - w.Write(body) - }) -} - -func TestGET(t *testing.T) { - a := New(t) - server := httptest.NewServer(OKHandler(a, "GET")) - defer server.Close() - - var resp OKResp - err := GET(server.URL, tokenStrategy, url, &resp) - a.So(err, ShouldBeNil) - a.So(resp.OK, ShouldEqual, token) -} - -func TestGETDropResponse(t *testing.T) { - a := New(t) - server := httptest.NewServer(OKHandler(a, "GET")) - defer server.Close() - - err := GET(server.URL, tokenStrategy, url, nil) - a.So(err, ShouldBeNil) -} - -func TestGETIllegalResponse(t *testing.T) { - a := New(t) - server := httptest.NewServer(OKHandler(a, "GET")) - defer server.Close() - - var resp FooResp - err := GET(server.URL, tokenStrategy, url, &resp) - a.So(err, ShouldNotBeNil) -} - -func TestGETIllegalResponseIgnore(t *testing.T) { - a := New(t) - server := httptest.NewServer(FooHandler(a, "GET")) - defer server.Close() - - var resp OKResp - err := GET(server.URL, tokenStrategy, url, &resp) - a.So(err, ShouldBeNil) -} - -func TestGETRedirect(t *testing.T) { - a := New(t) - server := httptest.NewServer(RedirectHandler(a, "GET")) - defer server.Close() - - var resp OKResp - err := GET(server.URL, tokenStrategy, url, &resp) - a.So(err, ShouldBeNil) -} - -func TestPUT(t *testing.T) { - a := New(t) - server := httptest.NewServer(EchoHandler(a, "PUT")) - defer server.Close() - - var resp FooResp - body := FooResp{ - Foo: token, - } - err := PUT(server.URL, tokenStrategy, url, body, &resp) - a.So(err, ShouldBeNil) - a.So(resp.Foo, ShouldEqual, body.Foo) -} - -func TestPUTIllegalRequest(t *testing.T) { - a := New(t) - server := httptest.NewServer(EchoHandler(a, "PUT")) - defer server.Close() - - var resp FooResp - body := FooResp{} - err := PUT(server.URL, tokenStrategy, url, body, &resp) - a.So(err, ShouldNotBeNil) -} - -func TestPUTIllegalResponse(t *testing.T) { - a := New(t) - server := httptest.NewServer(OKHandler(a, "PUT")) - defer server.Close() - - var resp FooResp - err := PUT(server.URL, tokenStrategy, url, nil, &resp) - a.So(err, ShouldNotBeNil) -} - -func TestPUTRedirect(t *testing.T) { - a := New(t) - server := httptest.NewServer(RedirectHandler(a, "PUT")) - defer server.Close() - - var resp FooResp - err := PUT(server.URL, tokenStrategy, url, nil, &resp) - a.So(err, ShouldBeNil) - a.So(resp.Foo, ShouldEqual, token) -} - -func TestPOST(t *testing.T) { - a := New(t) - server := httptest.NewServer(EchoHandler(a, "POST")) - defer server.Close() - - var resp FooResp - body := FooResp{ - Foo: token, - } - err := POST(server.URL, tokenStrategy, url, body, &resp) - a.So(err, ShouldBeNil) - a.So(resp.Foo, ShouldEqual, body.Foo) -} - -func TestPOSTIllegalRequest(t *testing.T) { - a := New(t) - server := httptest.NewServer(EchoHandler(a, "POST")) - defer server.Close() - - var resp FooResp - body := FooResp{} - err := POST(server.URL, tokenStrategy, url, body, &resp) - a.So(err, ShouldNotBeNil) -} - -func TestPOSTIllegalResponse(t *testing.T) { - a := New(t) - server := httptest.NewServer(OKHandler(a, "POST")) - defer server.Close() - - var resp FooResp - err := POST(server.URL, tokenStrategy, url, nil, &resp) - a.So(err, ShouldNotBeNil) -} - -func TestPOSTRedirect(t *testing.T) { - a := New(t) - server := httptest.NewServer(RedirectHandler(a, "POST")) - defer server.Close() - - var resp FooResp - err := POST(server.URL, tokenStrategy, url, nil, &resp) - a.So(err, ShouldBeNil) - a.So(resp.Foo, ShouldEqual, token) -} - -func TestDELETE(t *testing.T) { - a := New(t) - server := httptest.NewServer(OKHandler(a, "DELETE")) - defer server.Close() - - err := DELETE(server.URL, tokenStrategy, url) - a.So(err, ShouldBeNil) -} - -func TestDELETERedirect(t *testing.T) { - a := New(t) - server := httptest.NewServer(RedirectHandler(a, "DELETE")) - defer server.Close() - - err := DELETE(server.URL, tokenStrategy, url) - a.So(err, ShouldBeNil) -} diff --git a/core/account/util/oauth.go b/core/account/util/oauth.go deleted file mode 100644 index fec35aede..000000000 --- a/core/account/util/oauth.go +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "fmt" - - "golang.org/x/oauth2" -) - -// MakeConfig creates an oauth.Config object based on the necessary parameters, -// redirectURL can be left empty if not needed -func MakeConfig(server string, clientID string, clientSecret string, redirectURL string) oauth2.Config { - endpoint := oauth2.Endpoint{ - TokenURL: fmt.Sprintf("%s/users/token", server), - AuthURL: fmt.Sprintf("%s/users/authorize", server), - } - - return oauth2.Config{ - ClientID: clientID, - ClientSecret: clientSecret, - Endpoint: endpoint, - RedirectURL: redirectURL, - } -} diff --git a/core/account/util/validate.go b/core/account/util/validate.go deleted file mode 100644 index 5b3b57a66..000000000 --- a/core/account/util/validate.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "fmt" - "reflect" - - "github.com/asaskevich/govalidator" -) - -func validateSlice(val interface{}) error { - s := reflect.ValueOf(val) - - for i := 0; i < s.Len(); i++ { - if err := Validate(s.Index(i).Interface()); err != nil { - return fmt.Errorf("In slice at index %v: %s", i, err) - } - } - return nil -} - -// Validate recursivly validates most structures using govalidator -// struct tags. It currently works for slices, structs and pointers. -func Validate(val interface{}) error { - switch reflect.TypeOf(val).Kind() { - case reflect.Slice: - // validate a slice - err := validateSlice(val) - return err - case reflect.Ptr: - // try to get the valu from the ptr - v := reflect.ValueOf(val).Elem() - if v.CanInterface() { - return Validate(v.Interface()) - } - case reflect.Struct: - // when it is a struct jsut validate - _, err := govalidator.ValidateStruct(val) - return err - } - return nil -} diff --git a/core/account/util/validate_test.go b/core/account/util/validate_test.go deleted file mode 100644 index 1c9b73140..000000000 --- a/core/account/util/validate_test.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package util - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -type Foo struct { - Foo string `valid:"required"` -} - -var ( - validFoo = Foo{ - Foo: "Hi", - } - - invalidFoo = Foo{} - - validFoos = []Foo{ - validFoo, - validFoo, - } - - invalidFoos = []Foo{ - validFoo, - invalidFoo, - } -) - -func TestValidateStruct(t *testing.T) { - a := New(t) - - err := Validate(validFoo) - a.So(err, ShouldBeNil) - - err = Validate(invalidFoo) - a.So(err, ShouldNotBeNil) -} - -func TestValidateSlice(t *testing.T) { - a := New(t) - - err := Validate(validFoos) - a.So(err, ShouldBeNil) - - err = Validate(invalidFoos) - a.So(err, ShouldNotBeNil) -} - -func TestValidatePtr(t *testing.T) { - a := New(t) - - err := Validate(&validFoo) - a.So(err, ShouldBeNil) - - err = Validate(&invalidFoo) - a.So(err, ShouldNotBeNil) -} - -func TestValidatePtrSlice(t *testing.T) { - a := New(t) - - err := Validate(&validFoos) - a.So(err, ShouldBeNil) - - err = Validate(&invalidFoos) - a.So(err, ShouldNotBeNil) -} diff --git a/utils/tokenkey/tokenkey.go b/utils/tokenkey/tokenkey.go deleted file mode 100644 index 26fda5bc9..000000000 --- a/utils/tokenkey/tokenkey.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package tokenkey - -import ( - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "net/http" - "path" -) - -// K is the data returned by the token key provider -type K struct { - Algorithm string `json:"algorithm"` - Key string `json:"key"` -} - -// Provider represents a provider of the token key -type Provider interface { - Get(server string, renew bool) (*K, error) - Update() error -} - -type httpProvider struct { - servers map[string]string - cache map[string][]byte - cacheLocation string -} - -// NewHTTPProvider returns a new Provider that fetches the key from a HTTP -// resource -func NewHTTPProvider(servers map[string]string, cacheLocation string) Provider { - return &httpProvider{servers, map[string][]byte{}, cacheLocation} -} - -func (p *httpProvider) Get(server string, renew bool) (*K, error) { - var data []byte - - url, ok := p.servers[server] - if !ok { - return nil, fmt.Errorf("Auth server %s not registered", server) - } - - cacheFile := path.Join(p.cacheLocation, fmt.Sprintf("auth-%s.pub", server)) - - // Try to read from memory - cached, ok := p.cache[server] - if ok { - data = cached - } - - if data == nil { - // Try to read from cache file - cached, err := ioutil.ReadFile(cacheFile) - if err == nil { - p.cache[server] = cached - data = cached - } - } - - // Fetch token if there's a renew or if there's no key cached - if renew || data == nil { - fetched, err := p.fetch(fmt.Sprintf("%s/key", url)) - if err == nil { - data = fetched - // Don't care about errors here. It's better to retrieve keys all the time - // because they can't be cached than not to be able to verify a token - ioutil.WriteFile(cacheFile, data, 0644) - p.cache[server] = data - } else if data == nil { - return nil, err // We don't have a key here - } - } - - var key K - if err := json.Unmarshal(data, &key); err != nil { - return nil, err - } - - return &key, nil -} - -func (p *httpProvider) Update() error { - for server := range p.servers { - _, err := p.Get(server, true) - if err != nil { - return err - } - } - return nil -} - -func (p *httpProvider) fetch(url string) ([]byte, error) { - resp, err := http.Get(url) - if err != nil { - return nil, err - } - if resp.StatusCode != http.StatusOK { - return nil, errors.New(resp.Status) - } - defer resp.Body.Close() - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return data, nil -} From d1ff90bc9a514d0b55843a8f8027b3229057cbb9 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 23 Sep 2016 14:52:25 +0200 Subject: [PATCH 1825/2266] Replace tokenkey by go-account-lib equivalent --- core/component.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/component.go b/core/component.go index 20816f770..a363b97d6 100644 --- a/core/component.go +++ b/core/component.go @@ -11,12 +11,13 @@ import ( "sync/atomic" "time" + "github.com/TheThingsNetwork/go-account-lib/tokenkey" + "github.com/TheThingsNetwork/go-account-lib/tokenkey/cache" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_noc "github.com/TheThingsNetwork/ttn/api/noc" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/TheThingsNetwork/ttn/utils/security" - "github.com/TheThingsNetwork/ttn/utils/tokenkey" "github.com/apex/log" "github.com/dgrijalva/jwt-go" "github.com/mwitkow/go-grpc-middleware" @@ -65,9 +66,9 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string NetAddress: announcedAddress, }, AccessToken: viper.GetString("auth-token"), - TokenKeyProvider: tokenkey.NewHTTPProvider( + TokenKeyProvider: tokenkey.HTTPProvider( viper.GetStringMapString("auth-servers"), - viper.GetString("key-dir"), + cache.WriteTroughCache(viper.GetString("key-dir")), ), } From 327b7de8ba97bb8b20f02cecbf2e9c4328c01042 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 23 Sep 2016 16:14:12 +0200 Subject: [PATCH 1826/2266] Update clients of core/account to use go-account-lib/account --- ttnctl/cmd/gateways_edit.go | 2 +- ttnctl/cmd/gateways_register.go | 2 +- ttnctl/cmd/user_login.go | 2 +- ttnctl/cmd/user_register.go | 2 +- ttnctl/util/account.go | 11 ++++++++--- ttnctl/util/location.go | 2 +- 6 files changed, 13 insertions(+), 8 deletions(-) diff --git a/ttnctl/cmd/gateways_edit.go b/ttnctl/cmd/gateways_edit.go index e4cfdef24..c58714a16 100644 --- a/ttnctl/cmd/gateways_edit.go +++ b/ttnctl/cmd/gateways_edit.go @@ -4,8 +4,8 @@ package cmd import ( + "github.com/TheThingsNetwork/go-account-lib/account" "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/core/account" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) diff --git a/ttnctl/cmd/gateways_register.go b/ttnctl/cmd/gateways_register.go index 6af483d60..7aad010d7 100644 --- a/ttnctl/cmd/gateways_register.go +++ b/ttnctl/cmd/gateways_register.go @@ -4,8 +4,8 @@ package cmd import ( + "github.com/TheThingsNetwork/go-account-lib/account" "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/core/account" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index 59b85e4b0..e1cdf0edd 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -6,7 +6,7 @@ package cmd import ( "fmt" - "github.com/TheThingsNetwork/ttn/core/account" + "github.com/TheThingsNetwork/go-account-lib/account" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go index dde731e07..593f46863 100644 --- a/ttnctl/cmd/user_register.go +++ b/ttnctl/cmd/user_register.go @@ -6,7 +6,7 @@ package cmd import ( "fmt" - "github.com/TheThingsNetwork/ttn/core/account" + "github.com/TheThingsNetwork/go-account-lib/account" "github.com/howeyc/gopass" "github.com/spf13/cobra" "github.com/spf13/viper" diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 245878c57..6dc8feb2e 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -7,8 +7,9 @@ import ( "encoding/json" "time" - "github.com/TheThingsNetwork/ttn/core/account" - accountUtil "github.com/TheThingsNetwork/ttn/core/account/util" + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/go-account-lib/tokens" + accountUtil "github.com/TheThingsNetwork/go-account-lib/util" "github.com/apex/log" "github.com/dgrijalva/jwt-go" "github.com/spf13/viper" @@ -111,7 +112,11 @@ func GetAccount(ctx log.Interface) *account.Account { if err != nil { ctx.WithError(err).Fatal("Could not get access token") } - return account.New(viper.GetString("ttn-account-server"), token.AccessToken) + + server := viper.GetString("ttn-account-server") + manager := tokens.HTTPManager(server, token.AccessToken, tokens.MemoryStore()) + + return account.NewWithManager(server, token.AccessToken, manager) } // Login does a login to the Account server with the given username and password diff --git a/ttnctl/util/location.go b/ttnctl/util/location.go index 1cff56ed8..3c1bf4dbc 100644 --- a/ttnctl/util/location.go +++ b/ttnctl/util/location.go @@ -8,7 +8,7 @@ import ( "strconv" "strings" - "github.com/TheThingsNetwork/ttn/core/account" + "github.com/TheThingsNetwork/go-account-lib/account" ) func ParseLocation(locationStr string) (*account.Location, error) { From a3e6a00d4a9d4b2c1d8629cffcc6e63a3db51b0c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 26 Sep 2016 10:20:19 +0200 Subject: [PATCH 1827/2266] TTNClaims -> claims.Claims --- core/broker/manager_server.go | 2 +- core/component.go | 63 ++++++---------------------- core/discovery/server.go | 2 +- core/handler/manager_server.go | 10 ++--- core/networkserver/manager_server.go | 2 +- 5 files changed, 21 insertions(+), 58 deletions(-) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index ec2ba4533..eb7caac1a 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -53,7 +53,7 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Handler Registration") } - if !claims.CanEditApp(in.AppId) { + if !claims.AppRight(in.AppId, "settings") { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } // Add Handler in local cache diff --git a/core/component.go b/core/component.go index a363b97d6..a0ef338c9 100644 --- a/core/component.go +++ b/core/component.go @@ -11,8 +11,9 @@ import ( "sync/atomic" "time" + "github.com/TheThingsNetwork/go-account-lib/cache" + "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/tokenkey" - "github.com/TheThingsNetwork/go-account-lib/tokenkey/cache" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_noc "github.com/TheThingsNetwork/ttn/api/noc" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -34,7 +35,7 @@ type ComponentInterface interface { RegisterRPC(s *grpc.Server) Init(c *Component) error ValidateNetworkContext(ctx context.Context) (*pb_discovery.Announcement, error) - ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) + ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) } type ManagementInterface interface { @@ -194,30 +195,6 @@ func (c *Component) UpdateTokenKey() error { } -// TTNClaims contains the claims that are set by the TTN Token Issuer -type TTNClaims struct { - jwt.StandardClaims - Type string `json:"type"` - Client string `json:"client"` - Scopes []string `json:"scope"` - Apps map[string][]string `json:"apps,omitempty"` - Gateways map[string][]string `json:"gateways,omitempty"` -} - -// CanEditApp indicates wheter someone with the claims can manage the given app -func (c *TTNClaims) CanEditApp(appID string) bool { - for id, rights := range c.Apps { - if appID == id { - for _, right := range rights { - if right == "settings" { - return true - } - } - } - } - return false -} - // ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_discovery.Announcement, err error) { defer func() { @@ -279,7 +256,7 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d } // ValidateTTNAuthContext gets a token from the context and validates it -func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, error) { +func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) { md, ok := metadata.FromContext(ctx) if !ok { return nil, errors.NewErrInternal("Could not get metadata from context") @@ -288,31 +265,17 @@ func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*TTNClaims, err if !ok || len(token) < 1 { return nil, errors.NewErrInvalidArgument("Metadata", "token missing") } - ttnClaims := &TTNClaims{} - parsed, err := jwt.ParseWithClaims(token[0], ttnClaims, func(token *jwt.Token) (interface{}, error) { - if c.TokenKeyProvider == nil { - return nil, errors.NewErrInternal("No token provider configured") - } - k, err := c.TokenKeyProvider.Get(ttnClaims.Issuer, false) - if err != nil { - return nil, err - } - if k.Algorithm != token.Header["alg"] { - return nil, errors.NewErrInvalidArgument("Token", fmt.Sprintf("expected algorithm %v but got %v", k.Algorithm, token.Header["alg"])) - } - key, err := jwt.ParseRSAPublicKeyFromPEM([]byte(k.Key)) - if err != nil { - return nil, err - } - return key, nil - }) - if err != nil { - return nil, errors.NewErrInvalidArgument("Token", fmt.Sprintf("unable to parse token: %s", err.Error())) + + if c.TokenKeyProvider == nil { + return nil, errors.NewErrInternal("No token provider configured") } - if !parsed.Valid { - return nil, errors.NewErrInvalidArgument("Token", "not valid or expired") + + claims, err := claims.FromToken(c.TokenKeyProvider, token[0]) + if err != nil { + return nil, err } - return ttnClaims, nil + + return claims, nil } func (c *Component) ServerOptions() []grpc.ServerOption { diff --git a/core/discovery/server.go b/core/discovery/server.go index d628c3af4..e3d5ae6f7 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -65,7 +65,7 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me } // Allow APP_ID announcements from all trusted auth servers // When announcing APP_ID, token is user token that contains apps - if !claims.CanEditApp(string(in.Metadata.Value)) { + if !claims.AppRight(string(in.Metadata.Value), "settings") { return errPermissionDeniedf("No access to this application") } } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 8230f121e..5d6681e02 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -34,14 +34,14 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) if err != nil { return nil, err } - if !claims.CanEditApp(in.AppId) { + if !claims.AppRight(in.AppId, "settings") { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err } - if !claims.CanEditApp(dev.AppID) { + if !claims.AppRight(dev.AppID, "settings") { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return dev, nil @@ -188,7 +188,7 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap if err != nil { return nil, errors.BuildGRPCError(err) } - if !claims.CanEditApp(in.AppId) { + if !claims.AppRight(in.AppId, "setttings") { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } devices, err := h.handler.devices.ListForApp(in.AppId) @@ -223,14 +223,14 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI if err != nil { return nil, err } - if !claims.CanEditApp(in.AppId) { + if !claims.AppRight(in.AppId, "settings") { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } app, err := h.handler.applications.Get(in.AppId) if err != nil { return nil, err } - if !claims.CanEditApp(app.AppID) { + if !claims.AppRight(app.AppID, "settings") { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return app, nil diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 8fce89a5c..9178e3f4a 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -33,7 +33,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev if err != nil { return nil, err } - if !claims.CanEditApp(dev.AppID) { + if !claims.AppRight(dev.AppID, "settings") { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) } return dev, nil From 7fa62781aa5f44bfdc8332e4ea61aeb550a788c8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 26 Sep 2016 10:34:03 +0200 Subject: [PATCH 1828/2266] Remove AccountClaims in favor of claims.Claims --- ttnctl/cmd/user.go | 3 ++- ttnctl/util/account.go | 15 --------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index ca30313e8..3712dac16 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/dgrijalva/jwt-go" "github.com/spf13/cobra" @@ -53,7 +54,7 @@ var userCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Could not decode access token") } - var claims util.AccountClaims + var claims claims.Claims err = json.Unmarshal(segment, &claims) if err != nil { ctx.WithError(err).Fatal("Could not unmarshal access token") diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 6dc8feb2e..cf504e04b 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -11,26 +11,11 @@ import ( "github.com/TheThingsNetwork/go-account-lib/tokens" accountUtil "github.com/TheThingsNetwork/go-account-lib/util" "github.com/apex/log" - "github.com/dgrijalva/jwt-go" "github.com/spf13/viper" "golang.org/x/net/context" "golang.org/x/oauth2" ) -// AccountClaims are extracted from the access token -type AccountClaims struct { - jwt.StandardClaims - Username string `json:"username"` - Name struct { - First string `json:"first"` - Last string `json:"last"` - } `json:"name"` - Email string `json:"email"` - Client string `json:"client"` - Scopes []string `json:"scope"` - Apps map[string][]string `json:"apps,omitempty"` -} - var tokenSource oauth2.TokenSource func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { From e396dc719e02b9527a6b0e5ad544d7e220e92f31 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 26 Sep 2016 11:41:27 +0200 Subject: [PATCH 1829/2266] Use claims.FromTokenWithoutValidation --- ttnctl/cmd/user.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index 3712dac16..e4fe4db33 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -4,14 +4,11 @@ package cmd import ( - "encoding/json" "fmt" - "strings" "time" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/dgrijalva/jwt-go" "github.com/spf13/cobra" ) @@ -46,18 +43,10 @@ var userCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Could not get access token") } - tokenParts := strings.Split(token.AccessToken, ".") - if len(tokenParts) != 3 { - ctx.Fatal("Invalid access token") - } - segment, err := jwt.DecodeSegment(tokenParts[1]) - if err != nil { - ctx.WithError(err).Fatal("Could not decode access token") - } - var claims claims.Claims - err = json.Unmarshal(segment, &claims) + + claims, err := claims.FromTokenWithoutValidation(token.AccessToken) if err != nil { - ctx.WithError(err).Fatal("Could not unmarshal access token") + ctx.WithError(err).Fatal("Could not parse token") } if claims.ExpiresAt != 0 { From b0c70faf2b8233f8266f6b943a351ecac6cc50bc Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 26 Sep 2016 12:20:06 +0200 Subject: [PATCH 1830/2266] Store tokens in ~/.ttnctl or $TOKEN_DIR instead of config file --- ttnctl/cmd/root.go | 3 +++ ttnctl/util/account.go | 54 +++++++++++++++++++++++++++++------------- 2 files changed, 41 insertions(+), 16 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index cd0014e9e..557ee692f 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -73,6 +73,9 @@ func init() { RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) + + RootCmd.PersistentFlags().String("token-dir", os.ExpandEnv("$HOME/.ttnctl"), "The location where tokens are stored") + viper.BindPFlag("token-dir", RootCmd.PersistentFlags().Lookup("token-dir")) } func printKV(key, t interface{}) { diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index cf504e04b..9c806be3a 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -5,9 +5,11 @@ package util import ( "encoding/json" + "strings" "time" "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/go-account-lib/cache" "github.com/TheThingsNetwork/go-account-lib/tokens" accountUtil "github.com/TheThingsNetwork/go-account-lib/util" "github.com/apex/log" @@ -18,18 +20,45 @@ import ( var tokenSource oauth2.TokenSource +func tokenName() string { + return viper.GetString("ttn-account-server") +} + +func serverKey() string { + replacer := strings.NewReplacer("https:", "", "http:", "", "/", "", ".", "") + return replacer.Replace(viper.GetString("ttn-account-server")) +} + +func tokenFilename(name string) string { + return serverKey() + ".token" +} + +func derivedTokenFilename(name string) string { + return serverKey() + "." + name + ".token" +} + +// GetCache get's the cache that will store our tokens +func GetTokenCache() cache.Cache { + return cache.FileCacheWithNameFn(viper.GetString("token-dir"), tokenFilename) +} + func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") return config.TokenSource(context.Background(), token) } func getStoredToken(ctx log.Interface) *oauth2.Token { - tokenString := viper.GetString("oauth2-token") - if tokenString == "" { - ctx.Fatal("No account information found. Please login with ttnctl user login [access code]") + tokenCache := GetTokenCache() + data, err := tokenCache.Get(tokenName()) + if err != nil { + ctx.WithError(err).Fatal("Could not read stored token") + } + if data == nil { + ctx.Fatal("No account information found. Please login with ttnctl user login [e-mail]") } + token := &oauth2.Token{} - err := json.Unmarshal([]byte(tokenString), token) + err = json.Unmarshal(data, token) if err != nil { ctx.Fatal("Account information invalid. Please login with ttnctl user login [access code]") } @@ -37,20 +66,13 @@ func getStoredToken(ctx log.Interface) *oauth2.Token { } func saveToken(ctx log.Interface, token *oauth2.Token) { - tokenBytes, err := json.Marshal(token) + data, err := json.Marshal(token) if err != nil { ctx.WithError(err).Fatal("Could not save access token") } - if viper.GetString("oauth2-token") != string(tokenBytes) { - config, _ := ReadConfig() - if config == nil { - config = map[string]interface{}{} - } - config["oauth2-token"] = string(tokenBytes) - err = WriteConfigFile(config) - if err != nil { - ctx.WithError(err).Fatal("Could not save access token") - } + err = GetTokenCache().Set(tokenName(), data) + if err != nil { + ctx.WithError(err).Fatal("Could not save access token") } } @@ -99,7 +121,7 @@ func GetAccount(ctx log.Interface) *account.Account { } server := viper.GetString("ttn-account-server") - manager := tokens.HTTPManager(server, token.AccessToken, tokens.MemoryStore()) + manager := tokens.HTTPManager(server, token.AccessToken, tokens.FileStoreWithNameFn(viper.GetString("token-dir"), derivedTokenFilename)) return account.NewWithManager(server, token.AccessToken, manager) } From 2a1b1c7ec94f58df34f89f428acc4b1b795436ba Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 26 Sep 2016 16:43:04 +0200 Subject: [PATCH 1831/2266] "settings" -> rights.AppSettings --- core/broker/manager_server.go | 3 ++- core/discovery/server.go | 3 ++- core/handler/manager_server.go | 9 +++++---- core/networkserver/manager_server.go | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index eb7caac1a..cf7ba8731 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -4,6 +4,7 @@ package broker import ( + "github.com/TheThingsNetwork/go-account-lib/rights" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" @@ -53,7 +54,7 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Handler Registration") } - if !claims.AppRight(in.AppId, "settings") { + if !claims.AppRight(in.AppId, rights.AppSettings) { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } // Add Handler in local cache diff --git a/core/discovery/server.go b/core/discovery/server.go index e3d5ae6f7..8d711ffa9 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -4,6 +4,7 @@ package discovery import ( + "github.com/TheThingsNetwork/go-account-lib/rights" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" @@ -65,7 +66,7 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me } // Allow APP_ID announcements from all trusted auth servers // When announcing APP_ID, token is user token that contains apps - if !claims.AppRight(string(in.Metadata.Value), "settings") { + if !claims.AppRight(string(in.Metadata.Value), rights.AppSettings) { return errPermissionDeniedf("No access to this application") } } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 5d6681e02..80205c9c6 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -6,6 +6,7 @@ package handler import ( "fmt" + "github.com/TheThingsNetwork/go-account-lib/rights" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -34,14 +35,14 @@ func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) if err != nil { return nil, err } - if !claims.AppRight(in.AppId, "settings") { + if !claims.AppRight(in.AppId, rights.AppSettings) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err } - if !claims.AppRight(dev.AppID, "settings") { + if !claims.AppRight(dev.AppID, rights.AppSettings) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return dev, nil @@ -223,14 +224,14 @@ func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationI if err != nil { return nil, err } - if !claims.AppRight(in.AppId, "settings") { + if !claims.AppRight(in.AppId, rights.AppSettings) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } app, err := h.handler.applications.Get(in.AppId) if err != nil { return nil, err } - if !claims.AppRight(app.AppID, "settings") { + if !claims.AppRight(app.AppID, rights.AppSettings) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) } return app, nil diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 9178e3f4a..591a7fb61 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/TheThingsNetwork/go-account-lib/rights" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/networkserver/device" @@ -33,7 +34,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev if err != nil { return nil, err } - if !claims.AppRight(dev.AppID, "settings") { + if !claims.AppRight(dev.AppID, rights.AppSettings) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) } return dev, nil From 11770b26c5eb19637d55d71ce718d904e4a39f47 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 26 Sep 2016 17:57:27 +0200 Subject: [PATCH 1832/2266] Also use specific token for handler devices --- ttnctl/cmd/applications_pf.go | 2 +- ttnctl/cmd/applications_pf_set.go | 2 +- ttnctl/cmd/applications_register.go | 2 +- ttnctl/cmd/applications_unregister.go | 2 +- ttnctl/cmd/devices_delete.go | 2 +- ttnctl/cmd/devices_info.go | 2 +- ttnctl/cmd/devices_list.go | 2 +- ttnctl/cmd/devices_personalize.go | 2 +- ttnctl/cmd/devices_register.go | 2 +- ttnctl/cmd/devices_set.go | 2 +- ttnctl/util/account.go | 21 ++++++++++++++++++++- ttnctl/util/handler.go | 12 ++++++------ 12 files changed, 36 insertions(+), 17 deletions(-) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index 3e0be6e63..d3b235462 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -33,7 +33,7 @@ function Decoder(bytes) { appID := util.GetAppID(ctx) - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() app, err := manager.GetApplication(appID) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 793b2c955..782926d6f 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -46,7 +46,7 @@ function Decoder(bytes) { appID := util.GetAppID(ctx) - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() app, err := manager.GetApplication(appID) diff --git a/ttnctl/cmd/applications_register.go b/ttnctl/cmd/applications_register.go index 6e61062cb..b4c7b6971 100644 --- a/ttnctl/cmd/applications_register.go +++ b/ttnctl/cmd/applications_register.go @@ -22,7 +22,7 @@ var applicationsRegisterCmd = &cobra.Command{ appID := util.GetAppID(ctx) - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() err := manager.RegisterApplication(appID) diff --git a/ttnctl/cmd/applications_unregister.go b/ttnctl/cmd/applications_unregister.go index 016a0737e..6555f7fb3 100644 --- a/ttnctl/cmd/applications_unregister.go +++ b/ttnctl/cmd/applications_unregister.go @@ -31,7 +31,7 @@ Are you sure you want to unregister application test? return } - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() err := manager.DeleteApplication(appID) diff --git a/ttnctl/cmd/devices_delete.go b/ttnctl/cmd/devices_delete.go index a99df34fa..274843db7 100644 --- a/ttnctl/cmd/devices_delete.go +++ b/ttnctl/cmd/devices_delete.go @@ -43,7 +43,7 @@ Are you sure you want to delete device test from application test? return } - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() err := manager.DeleteDevice(appID, devID) diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index a2eac3824..b8131d873 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -53,7 +53,7 @@ var devicesInfoCmd = &cobra.Command{ appID := util.GetAppID(ctx) - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() dev, err := manager.GetDevice(appID, devID) diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index 575740d8f..59ccb64f9 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -30,7 +30,7 @@ test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 appID := util.GetAppID(ctx) - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() devices, err := manager.GetDevicesForApplication(appID) diff --git a/ttnctl/cmd/devices_personalize.go b/ttnctl/cmd/devices_personalize.go index 63cce36ed..006d48e60 100644 --- a/ttnctl/cmd/devices_personalize.go +++ b/ttnctl/cmd/devices_personalize.go @@ -65,7 +65,7 @@ var devicesPersonalizeCmd = &cobra.Command{ copy(appSKey[:], random.Bytes(16)) } - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() dev, err := manager.GetDevice(appID, devID) diff --git a/ttnctl/cmd/devices_register.go b/ttnctl/cmd/devices_register.go index e821ce878..16f508fb1 100644 --- a/ttnctl/cmd/devices_register.go +++ b/ttnctl/cmd/devices_register.go @@ -64,7 +64,7 @@ var devicesRegisterCmd = &cobra.Command{ copy(appKey[:], random.Bytes(16)) } - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() err = manager.SetDevice(&handler.Device{ diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index 7f433e27e..52ecc2fe9 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -35,7 +35,7 @@ var devicesSetCmd = &cobra.Command{ appID := util.GetAppID(ctx) - conn, manager := util.GetHandlerManager(ctx) + conn, manager := util.GetHandlerManager(ctx, appID) defer conn.Close() dev, err := manager.GetDevice(appID, devID) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 9c806be3a..b8a3bc2ef 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -113,6 +113,11 @@ func GetTokenSource(ctx log.Interface) oauth2.TokenSource { return tokenSource } +func GetTokenManager(accessToken string) tokens.Manager { + server := viper.GetString("ttn-account-server") + return tokens.HTTPManager(server, accessToken, tokens.FileStoreWithNameFn(viper.GetString("token-dir"), derivedTokenFilename)) +} + // GetAccount gets a new Account server client for ttnctl func GetAccount(ctx log.Interface) *account.Account { token, err := GetTokenSource(ctx).Token() @@ -121,7 +126,7 @@ func GetAccount(ctx log.Interface) *account.Account { } server := viper.GetString("ttn-account-server") - manager := tokens.HTTPManager(server, token.AccessToken, tokens.FileStoreWithNameFn(viper.GetString("token-dir"), derivedTokenFilename)) + manager := GetTokenManager(token.AccessToken) return account.NewWithManager(server, token.AccessToken, manager) } @@ -136,3 +141,17 @@ func Login(ctx log.Interface, code string) (*oauth2.Token, error) { saveToken(ctx, token) return token, nil } + +func TokenForScope(ctx log.Interface, scope string) string { + token, err := GetTokenSource(ctx).Token() + if err != nil { + ctx.WithError(err).Fatal("Could not get token") + } + + restricted, err := GetTokenManager(token.AccessToken).TokenForScope(scope) + if err != nil { + ctx.WithError(err).Fatal("Could not get correct rights") + } + + return restricted +} diff --git a/ttnctl/util/handler.go b/ttnctl/util/handler.go index b29a5ea14..8dcafb41c 100644 --- a/ttnctl/util/handler.go +++ b/ttnctl/util/handler.go @@ -4,6 +4,7 @@ package util import ( + "github.com/TheThingsNetwork/go-account-lib/scope" "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -13,7 +14,7 @@ import ( ) // GetHandlerManager gets a new HandlerManager for ttnctl -func GetHandlerManager(ctx log.Interface) (*grpc.ClientConn, *handler.ManagerClient) { +func GetHandlerManager(ctx log.Interface, appID string) (*grpc.ClientConn, *handler.ManagerClient) { ctx.WithField("Handler", viper.GetString("ttn-handler")).Info("Discovering Handler...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() @@ -24,16 +25,15 @@ func GetHandlerManager(ctx log.Interface) (*grpc.ClientConn, *handler.ManagerCli if err != nil { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not find Handler") } - token, err := GetTokenSource(ctx).Token() - if err != nil { - ctx.WithError(err).Fatal("Could not get token") - } + + token := TokenForScope(ctx, scope.App(appID)) + ctx.WithField("Handler", handlerAnnouncement.NetAddress).Info("Connecting with Handler...") hdlConn, err := handlerAnnouncement.Dial() if err != nil { ctx.WithError(err).Fatal("Could not connect to Handler") } - managerClient, err := handler.NewManagerClient(hdlConn, token.AccessToken) + managerClient, err := handler.NewManagerClient(hdlConn, token) if err != nil { ctx.WithError(err).Fatal("Could not create Handler Manager") } From e4339fd3266cc89613d723c3fb42190f66b7c688 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 27 Sep 2016 11:05:24 +0200 Subject: [PATCH 1833/2266] Fix typo and switch to constant --- core/handler/manager_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 80205c9c6..726794b1e 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -189,7 +189,7 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap if err != nil { return nil, errors.BuildGRPCError(err) } - if !claims.AppRight(in.AppId, "setttings") { + if !claims.AppRight(in.AppId, rights.AppSettings) { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") } devices, err := h.handler.devices.ListForApp(in.AppId) From a80660eb8f7787c4264bb71101b1f55af7ef3b8e Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 27 Sep 2016 11:14:45 +0200 Subject: [PATCH 1834/2266] Better error when token is empty --- core/component.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/component.go b/core/component.go index a0ef338c9..50c0b8509 100644 --- a/core/component.go +++ b/core/component.go @@ -270,6 +270,10 @@ func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, return nil, errors.NewErrInternal("No token provider configured") } + if token[0] == "" { + return nil, errors.NewErrInvalidArgument("Metadata", "token is empty") + } + claims, err := claims.FromToken(c.TokenKeyProvider, token[0]) if err != nil { return nil, err From 96db666e5864676823c2e25df1b3309d3f325bf4 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 28 Sep 2016 15:33:49 +0200 Subject: [PATCH 1835/2266] Make tokenkey filename compliant with refactor --- core/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/component.go b/core/component.go index 50c0b8509..76897af89 100644 --- a/core/component.go +++ b/core/component.go @@ -69,7 +69,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string AccessToken: viper.GetString("auth-token"), TokenKeyProvider: tokenkey.HTTPProvider( viper.GetStringMapString("auth-servers"), - cache.WriteTroughCache(viper.GetString("key-dir")), + cache.WriteTroughCacheWithFormat(viper.GetString("key-dir"), "auth-%s.pub"), ), } From b1945ab7c4487270cd0d4d11cd464e4aef40683d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 29 Sep 2016 12:51:24 +0200 Subject: [PATCH 1836/2266] Vendor TheThingsNetwork/go-account-lib --- vendor/vendor.json | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/vendor/vendor.json b/vendor/vendor.json index d0b99d0ec..44b39043d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -6,6 +6,60 @@ "path": "-v", "revision": "" }, + { + "checksumSHA1": "rniS3263DDVGFj6N2g9DQ1T0Qgk=", + "path": "github.com/TheThingsNetwork/go-account-lib/account", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", + "path": "github.com/TheThingsNetwork/go-account-lib/auth", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "PFL4Ax29rfb/zBV3xOD56m/qUCQ=", + "path": "github.com/TheThingsNetwork/go-account-lib/cache", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", + "path": "github.com/TheThingsNetwork/go-account-lib/claims", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", + "path": "github.com/TheThingsNetwork/go-account-lib/rights", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", + "path": "github.com/TheThingsNetwork/go-account-lib/scope", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "M36IFas2KacxiVfNI3McxRzYcGA=", + "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "Mtox7znVD1KQGX40T4wtbH60K/w=", + "path": "github.com/TheThingsNetwork/go-account-lib/tokens", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, + { + "checksumSHA1": "xkcCm3Gn6B23Ca4A34nRJzEAlJo=", + "path": "github.com/TheThingsNetwork/go-account-lib/util", + "revision": "34de518002e1f51593baeabd670f281f4af90506", + "revisionTime": "2016-09-28T13:23:42Z" + }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", "path": "github.com/apex/log", From b71bf861284a581b347914b0b476eda6f8a92830 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 29 Sep 2016 12:51:31 +0200 Subject: [PATCH 1837/2266] Fix location_test --- ttnctl/util/location_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ttnctl/util/location_test.go b/ttnctl/util/location_test.go index f021a9239..37913bca2 100644 --- a/ttnctl/util/location_test.go +++ b/ttnctl/util/location_test.go @@ -6,12 +6,12 @@ package util import ( "testing" - "github.com/TheThingsNetwork/ttn/core/account" - s "github.com/smartystreets/assertions" + "github.com/TheThingsNetwork/go-account-lib/account" + . "github.com/smartystreets/assertions" ) func TestParseLocation(t *testing.T) { - a := s.New(t) + a := New(t) str := "10.5,33.4" loc := &account.Location{ @@ -19,7 +19,7 @@ func TestParseLocation(t *testing.T) { Longitude: float64(33.4), } parsed, err := ParseLocation(str) - a.So(err, s.ShouldBeNil) - a.So(loc.Latitude, s.ShouldEqual, parsed.Latitude) - a.So(loc.Longitude, s.ShouldEqual, parsed.Longitude) + a.So(err, ShouldBeNil) + a.So(loc.Latitude, ShouldEqual, parsed.Latitude) + a.So(loc.Longitude, ShouldEqual, parsed.Longitude) } From 6ef88f521515aae0bad70c25ffe207248499f324 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 29 Sep 2016 14:18:05 +0200 Subject: [PATCH 1838/2266] Update Vendors --- vendor/vendor.json | 118 ++++++++++++++++++++++----------------------- 1 file changed, 57 insertions(+), 61 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 44b39043d..32b82dc61 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2,63 +2,59 @@ "comment": "", "ignore": "test", "package": [ - { - "path": "-v", - "revision": "" - }, { "checksumSHA1": "rniS3263DDVGFj6N2g9DQ1T0Qgk=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "PFL4Ax29rfb/zBV3xOD56m/qUCQ=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "M36IFas2KacxiVfNI3McxRzYcGA=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "Mtox7znVD1KQGX40T4wtbH60K/w=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { - "checksumSHA1": "xkcCm3Gn6B23Ca4A34nRJzEAlJo=", + "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "34de518002e1f51593baeabd670f281f4af90506", - "revisionTime": "2016-09-28T13:23:42Z" + "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", + "revisionTime": "2016-09-29T11:56:12Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", @@ -123,14 +119,14 @@ { "checksumSHA1": "Uouj6ZRJ7qieqrCHp6u1cghopIM=", "path": "github.com/eclipse/paho.mqtt.golang", - "revision": "b0f0cce24f138f7a1d034f55069883c7dcb6a9d7", - "revisionTime": "2016-09-07T14:31:29Z" + "revision": "97b6f6bfc5007d4bc6859ea6ef8995079b60adc8", + "revisionTime": "2016-09-28T12:59:33Z" }, { - "checksumSHA1": "0KKFo4Q90AHUBTqNWH3DKZHWals=", + "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", "path": "github.com/eclipse/paho.mqtt.golang/packets", - "revision": "b0f0cce24f138f7a1d034f55069883c7dcb6a9d7", - "revisionTime": "2016-09-07T14:31:29Z" + "revision": "97b6f6bfc5007d4bc6859ea6ef8995079b60adc8", + "revisionTime": "2016-09-28T12:59:33Z" }, { "checksumSHA1": "xgjI2W3RGiQwNlxsOW2V9fJ9kaM=", @@ -319,10 +315,10 @@ "revisionTime": "2016-09-20T07:07:15Z" }, { - "checksumSHA1": "Hky3u+8Rqum+wB5BHMj0A8ZmT4g=", + "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", "path": "github.com/pkg/errors", - "revision": "a887431f7f6ef7687b556dbf718d9f351d4858a0", - "revisionTime": "2016-09-16T11:02:12Z" + "revision": "645ef00459ed84a119197bfb8d8205042c6df63d", + "revisionTime": "2016-09-29T01:48:01Z" }, { "checksumSHA1": "v6/DDmWObvEsdMvLe+KfuBVSNtg=", @@ -491,80 +487,80 @@ { "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", "path": "golang.org/x/crypto/curve25519", - "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", - "revisionTime": "2016-09-10T18:59:01Z" + "revision": "a20de3fa94e069ec699987416679230d72e030a3", + "revisionTime": "2016-09-23T08:39:13Z" }, { "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "path": "golang.org/x/crypto/ed25519", - "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", - "revisionTime": "2016-09-10T18:59:01Z" + "revision": "a20de3fa94e069ec699987416679230d72e030a3", + "revisionTime": "2016-09-23T08:39:13Z" }, { "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", - "revisionTime": "2016-09-10T18:59:01Z" + "revision": "a20de3fa94e069ec699987416679230d72e030a3", + "revisionTime": "2016-09-23T08:39:13Z" }, { - "checksumSHA1": "mtOqF6Q4ldg801EaT6YTv9LP9bM=", + "checksumSHA1": "ueYA012ftzqjyWYYdlt3H3pkixo=", "path": "golang.org/x/crypto/ssh", - "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", - "revisionTime": "2016-09-10T18:59:01Z" + "revision": "a20de3fa94e069ec699987416679230d72e030a3", + "revisionTime": "2016-09-23T08:39:13Z" }, { "checksumSHA1": "y3a1tOZVwKCqG2yJZvAYycnelyM=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "8e06e8ddd9629eb88639aba897641bff8031f1d3", - "revisionTime": "2016-09-10T18:59:01Z" + "revision": "a20de3fa94e069ec699987416679230d72e030a3", + "revisionTime": "2016-09-23T08:39:13Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "yIyismrXLh/RQFDeOtTPAGWsTOw=", "path": "golang.org/x/net/http2", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "WpST9lFOHvWrjDVy0GJNqHe+R3E=", "path": "golang.org/x/net/trace", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "likOl7O0BJntqagR050kkOBuY4o=", "path": "golang.org/x/net/websocket", - "revision": "f09c4662a0bd6bd8943ac7b4931e185df9471da4", - "revisionTime": "2016-09-24T00:10:04Z" + "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", + "revisionTime": "2016-09-27T14:53:45Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", @@ -699,10 +695,10 @@ "revisionTime": "2016-06-02T08:55:44Z" }, { - "checksumSHA1": "SPMXWeoFQa5z0pLPmqpcFzyHqQQ=", + "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", "path": "gopkg.in/yaml.v2", - "revision": "31c299268d302dd0aa9a0dcf765a3d58971ac83f", - "revisionTime": "2016-09-12T16:56:03Z" + "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", + "revisionTime": "2016-09-28T15:37:09Z" } ], "rootPath": "github.com/TheThingsNetwork/ttn" From 9d8118d96dc2e0b9b87ed30b92c0dd0248666179 Mon Sep 17 00:00:00 2001 From: Bart Vrancken Date: Thu, 29 Sep 2016 16:09:29 +0200 Subject: [PATCH 1839/2266] Update account.go (#278) switch error message from e-mail to access code due to auth change --- ttnctl/util/account.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index b8a3bc2ef..43ba6ed78 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -54,7 +54,7 @@ func getStoredToken(ctx log.Interface) *oauth2.Token { ctx.WithError(err).Fatal("Could not read stored token") } if data == nil { - ctx.Fatal("No account information found. Please login with ttnctl user login [e-mail]") + ctx.Fatal("No account information found. Please login with ttnctl user login [access code]") } token := &oauth2.Token{} From a9af68e0dcb03fd1519f4116d989a57599822d67 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 29 Sep 2016 16:12:01 +0200 Subject: [PATCH 1840/2266] Fix signature --- ttnctl/cmd/gateways_edit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/gateways_edit.go b/ttnctl/cmd/gateways_edit.go index c58714a16..8601bcee2 100644 --- a/ttnctl/cmd/gateways_edit.go +++ b/ttnctl/cmd/gateways_edit.go @@ -53,7 +53,7 @@ var gatewaysEditCmd = &cobra.Command{ } act := util.GetAccount(ctx) - _, err = act.EditGateway(gatewayID, edits) + err = act.EditGateway(gatewayID, edits) if err != nil { ctx.WithError(err).Fatal("Failure editing gateway") } From 8053b904e82ee9d6bf555f33696b70b82445aa79 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 29 Sep 2016 16:14:33 +0200 Subject: [PATCH 1841/2266] Update go-account-lib that has the bugfix --- vendor/vendor.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 32b82dc61..28b3151a8 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,58 +3,58 @@ "ignore": "test", "package": [ { - "checksumSHA1": "rniS3263DDVGFj6N2g9DQ1T0Qgk=", + "checksumSHA1": "waq4N4OCUwSpA4kjU3abu//ms68=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "PFL4Ax29rfb/zBV3xOD56m/qUCQ=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "M36IFas2KacxiVfNI3McxRzYcGA=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "Mtox7znVD1KQGX40T4wtbH60K/w=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "04b26fb148c2039127ce74639f9bba26da1ebcf1", - "revisionTime": "2016-09-29T11:56:12Z" + "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", + "revisionTime": "2016-09-29T14:07:59Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", From 00b8f771df7e47ffb9cfa111e077bea7fa7f42ca Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 29 Sep 2016 16:48:14 +0200 Subject: [PATCH 1842/2266] Store all tokens in one file, so it's easier to remove the old ones --- ttnctl/util/account.go | 32 +++++++++++++++++++++++++------- vendor/vendor.json | 38 +++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 26 deletions(-) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 43ba6ed78..6bb056f1a 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -5,6 +5,8 @@ package util import ( "encoding/json" + "os" + "path" "strings" "time" @@ -20,21 +22,24 @@ import ( var tokenSource oauth2.TokenSource +func tokenFile() string { + return getServerKey() + ".token" +} +func derivedTokenFile() string { + return getServerKey() + ".derived-tokens" +} + func tokenName() string { return viper.GetString("ttn-account-server") } -func serverKey() string { +func getServerKey() string { replacer := strings.NewReplacer("https:", "", "http:", "", "/", "", ".", "") return replacer.Replace(viper.GetString("ttn-account-server")) } func tokenFilename(name string) string { - return serverKey() + ".token" -} - -func derivedTokenFilename(name string) string { - return serverKey() + "." + name + ".token" + return tokenFile() } // GetCache get's the cache that will store our tokens @@ -115,7 +120,7 @@ func GetTokenSource(ctx log.Interface) oauth2.TokenSource { func GetTokenManager(accessToken string) tokens.Manager { server := viper.GetString("ttn-account-server") - return tokens.HTTPManager(server, accessToken, tokens.FileStoreWithNameFn(viper.GetString("token-dir"), derivedTokenFilename)) + return tokens.HTTPManager(server, accessToken, tokens.FileStore(path.Join(viper.GetString("token-dir"), derivedTokenFile()))) } // GetAccount gets a new Account server client for ttnctl @@ -155,3 +160,16 @@ func TokenForScope(ctx log.Interface, scope string) string { return restricted } + +func Logout() error { + err := os.Remove(path.Join(viper.GetString("token-dir"), tokenFile())) + if err != nil { + return err + } + + err = os.Remove(path.Join(viper.GetString("token-dir"), derivedTokenFile())) + if err != nil && !os.IsNotExist(err) { + return err + } + return nil +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 28b3151a8..59b55c2e2 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -5,56 +5,56 @@ { "checksumSHA1": "waq4N4OCUwSpA4kjU3abu//ms68=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "PFL4Ax29rfb/zBV3xOD56m/qUCQ=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "M36IFas2KacxiVfNI3McxRzYcGA=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { - "checksumSHA1": "Mtox7znVD1KQGX40T4wtbH60K/w=", + "checksumSHA1": "id8rTAsVn0DvpHFJmpRSdpEgxaA=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "3de7e0b0810b23f3264fa475fcaee8e259f71910", - "revisionTime": "2016-09-29T14:07:59Z" + "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", + "revisionTime": "2016-09-29T15:26:01Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", From e0b73fedda8f3842e582177c0ec5fc2ef2872b19 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 29 Sep 2016 16:48:32 +0200 Subject: [PATCH 1843/2266] When logging out, remove all stored tokens --- ttnctl/cmd/user_logout.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ttnctl/cmd/user_logout.go b/ttnctl/cmd/user_logout.go index 26b4c82ed..1707b8dbd 100644 --- a/ttnctl/cmd/user_logout.go +++ b/ttnctl/cmd/user_logout.go @@ -13,9 +13,7 @@ var userLogoutCmd = &cobra.Command{ Short: "Logout the current user", Long: `ttnctl user logout logs out the current user`, Run: func(cmd *cobra.Command, args []string) { - err := util.SetConfig(map[string]interface{}{ - "oauth2-token": "", - }) + err := util.Logout() if err != nil { ctx.WithError(err).Fatal("Could not delete credentials") } From 7d24a8ee070de9bfc1b3d8ee2797ed06638abaf1 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 11:26:15 +0200 Subject: [PATCH 1844/2266] Add helpers to find ttnctl config and data directories --- ttnctl/cmd/root.go | 23 ++++++++++++++-------- ttnctl/util/config.go | 46 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 557ee692f..aab539e97 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -13,6 +13,7 @@ import ( "google.golang.org/grpc/grpclog" "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/apex/log" @@ -21,6 +22,8 @@ import ( ) var cfgFile string +var dataDir string +var debug bool var ctx log.Interface @@ -58,6 +61,8 @@ func init() { cobra.OnInitialize(initConfig) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") + RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") + RootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode") RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) @@ -74,8 +79,6 @@ func init() { RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) - RootCmd.PersistentFlags().String("token-dir", os.ExpandEnv("$HOME/.ttnctl"), "The location where tokens are stored") - viper.BindPFlag("token-dir", RootCmd.PersistentFlags().Lookup("token-dir")) } func printKV(key, t interface{}) { @@ -110,17 +113,21 @@ func confirm(prompt string) bool { // initConfig reads in config file and ENV variables if set. func initConfig() { + if cfgFile == "" { + cfgFile = util.GetConfigFile() + } + + if dataDir == "" { + dataDir = util.GetDataDir() + } + viper.SetConfigType("yaml") - viper.SetConfigName(".ttnctl") - viper.AddConfigPath("$HOME") + viper.SetConfigFile(cfgFile) + viper.SetEnvPrefix("ttnctl") // set environment prefix viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() - if cfgFile != "" { // enable ability to specify config file via flag - viper.SetConfigFile(cfgFile) - } - // If a config file is found, read it in. err := viper.ReadInConfig() if err != nil { diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go index f4e1f6e69..3e32b5a02 100644 --- a/ttnctl/util/config.go +++ b/ttnctl/util/config.go @@ -6,6 +6,7 @@ package util import ( "fmt" "io/ioutil" + "os" "path" "github.com/TheThingsNetwork/ttn/core/types" @@ -105,3 +106,48 @@ func GetAppID(ctx log.Interface) string { } return appID } + +// GetConfigFile returns the location of the configuration file. +// It checks the following (in this order): +// the --config flag +// $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set) +// $HOME/.ttnctl.yml +func GetConfigFile() string { + file := viper.GetString("config") + if file != "" { + return file + } + + xdg := os.Getenv("XDG_CONFIG_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl", "config.yml") + } + + return path.Join(os.Getenv("HOME"), ".ttnctl.yml") +} + +// GetDataDir returns the location of the data directory used for +// sotring data. +// It checks the following (in this order): +// the --data flag +// $XDG_DATA_HOME/ttnctl (if $XDG_DATA_HOME is set) +// $XDG_CACHE_HOME/ttnctl (if $XDG_CACHE_HOME is set) +// $HOME/.ttnctl +func GetDataDir() string { + file := viper.GetString("data") + if file != "" { + return file + } + + xdg := os.Getenv("XDG_DATA_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl") + } + + xdg = os.Getenv("XDG_CACHE_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl") + } + + return path.Join(os.Getenv("HOME"), ".ttnctl") +} From 44b241f7f5f3c5acb2256fa317fc5f3aa4d1c1e7 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 11:34:22 +0200 Subject: [PATCH 1845/2266] Use datadir to store tokens --- ttnctl/util/account.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 6bb056f1a..7015c4289 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -44,7 +44,7 @@ func tokenFilename(name string) string { // GetCache get's the cache that will store our tokens func GetTokenCache() cache.Cache { - return cache.FileCacheWithNameFn(viper.GetString("token-dir"), tokenFilename) + return cache.FileCacheWithNameFn(GetDataDir(), tokenFilename) } func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { @@ -120,7 +120,7 @@ func GetTokenSource(ctx log.Interface) oauth2.TokenSource { func GetTokenManager(accessToken string) tokens.Manager { server := viper.GetString("ttn-account-server") - return tokens.HTTPManager(server, accessToken, tokens.FileStore(path.Join(viper.GetString("token-dir"), derivedTokenFile()))) + return tokens.HTTPManager(server, accessToken, tokens.FileStore(path.Join(GetDataDir(), derivedTokenFile()))) } // GetAccount gets a new Account server client for ttnctl From 28afdc219ad929f2eb223e39de065685b7d85b42 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 11:35:00 +0200 Subject: [PATCH 1846/2266] Correclty use debug flag, show config file and data directories when debug flag is enabled --- ttnctl/cmd/root.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index aab539e97..6d64ba216 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -34,13 +34,16 @@ var RootCmd = &cobra.Command{ Long: `ttnctl controls The Things Network from the command line.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { var logLevel = log.InfoLevel - if viper.GetBool("debug") { + if debug { logLevel = log.DebugLevel } ctx = &log.Logger{ Level: logLevel, Handler: cliHandler.New(os.Stdout), } + + ctx.WithField("config file", viper.ConfigFileUsed()).WithField("data dir", dataDir).Debug("Using config") + api.DialOptions = append(api.DialOptions, grpc.WithBlock()) api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) grpclog.SetLogger(logging.NewGRPCLogger(ctx)) From 2596d03feff1d4e2a001f24aa0b69c2c466c5a88 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 11:52:36 +0200 Subject: [PATCH 1847/2266] Bind data flag so that can be read by viper --- ttnctl/cmd/root.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 6d64ba216..b2957db85 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -64,9 +64,11 @@ func init() { cobra.OnInitialize(initConfig) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") - RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") RootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode") + RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") + viper.BindPFlag("data", RootCmd.PersistentFlags().Lookup("data")) + RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) @@ -81,7 +83,6 @@ func init() { RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) - } func printKV(key, t interface{}) { From 8af0965d85bff5e15d3fc1c277a8db7de605e7ad Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 10:59:53 +0200 Subject: [PATCH 1848/2266] Add system and component statistics --- api/api.pb.go | 1250 ++++++++++++++++++++++++- api/api.proto | 36 +- api/broker/broker.pb.go | 371 ++++---- api/broker/broker.proto | 26 +- api/handler/handler.pb.go | 404 ++++++-- api/handler/handler.proto | 10 +- api/networkserver/networkserver.pb.go | 361 ++++++- api/networkserver/networkserver.proto | 10 +- api/router/router.pb.go | 303 +++--- api/router/router.proto | 22 +- api/stats.go | 67 ++ 11 files changed, 2388 insertions(+), 472 deletions(-) create mode 100644 api/stats.go diff --git a/api/api.pb.go b/api/api.pb.go index db814c7ea..e8d7ed582 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -11,7 +11,8 @@ It has these top-level messages: Percentiles Rates - ServerMetadata + SystemStats + ComponentStats */ package api @@ -60,18 +61,128 @@ func (m *Rates) String() string { return proto.CompactTextString(m) } func (*Rates) ProtoMessage() {} func (*Rates) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{1} } -type ServerMetadata struct { +type SystemStats struct { + Load *SystemStats_Loadstats `protobuf:"bytes,1,opt,name=load" json:"load,omitempty"` + Cpu *SystemStats_CPUStats `protobuf:"bytes,2,opt,name=cpu" json:"cpu,omitempty"` + Memory *SystemStats_MemoryStats `protobuf:"bytes,3,opt,name=memory" json:"memory,omitempty"` } -func (m *ServerMetadata) Reset() { *m = ServerMetadata{} } -func (m *ServerMetadata) String() string { return proto.CompactTextString(m) } -func (*ServerMetadata) ProtoMessage() {} -func (*ServerMetadata) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} } +func (m *SystemStats) Reset() { *m = SystemStats{} } +func (m *SystemStats) String() string { return proto.CompactTextString(m) } +func (*SystemStats) ProtoMessage() {} +func (*SystemStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2} } + +func (m *SystemStats) GetLoad() *SystemStats_Loadstats { + if m != nil { + return m.Load + } + return nil +} + +func (m *SystemStats) GetCpu() *SystemStats_CPUStats { + if m != nil { + return m.Cpu + } + return nil +} + +func (m *SystemStats) GetMemory() *SystemStats_MemoryStats { + if m != nil { + return m.Memory + } + return nil +} + +type SystemStats_Loadstats struct { + Load1 float32 `protobuf:"fixed32,1,opt,name=load1,proto3" json:"load1,omitempty"` + Load5 float32 `protobuf:"fixed32,2,opt,name=load5,proto3" json:"load5,omitempty"` + Load15 float32 `protobuf:"fixed32,3,opt,name=load15,proto3" json:"load15,omitempty"` +} + +func (m *SystemStats_Loadstats) Reset() { *m = SystemStats_Loadstats{} } +func (m *SystemStats_Loadstats) String() string { return proto.CompactTextString(m) } +func (*SystemStats_Loadstats) ProtoMessage() {} +func (*SystemStats_Loadstats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 0} } + +type SystemStats_CPUStats struct { + User float32 `protobuf:"fixed32,1,opt,name=user,proto3" json:"user,omitempty"` + System float32 `protobuf:"fixed32,2,opt,name=system,proto3" json:"system,omitempty"` + Idle float32 `protobuf:"fixed32,3,opt,name=idle,proto3" json:"idle,omitempty"` +} + +func (m *SystemStats_CPUStats) Reset() { *m = SystemStats_CPUStats{} } +func (m *SystemStats_CPUStats) String() string { return proto.CompactTextString(m) } +func (*SystemStats_CPUStats) ProtoMessage() {} +func (*SystemStats_CPUStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 1} } + +type SystemStats_MemoryStats struct { + Total uint64 `protobuf:"varint,1,opt,name=total,proto3" json:"total,omitempty"` + Available uint64 `protobuf:"varint,2,opt,name=available,proto3" json:"available,omitempty"` + Used uint64 `protobuf:"varint,3,opt,name=used,proto3" json:"used,omitempty"` +} + +func (m *SystemStats_MemoryStats) Reset() { *m = SystemStats_MemoryStats{} } +func (m *SystemStats_MemoryStats) String() string { return proto.CompactTextString(m) } +func (*SystemStats_MemoryStats) ProtoMessage() {} +func (*SystemStats_MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 2} } + +type ComponentStats struct { + Cpu *ComponentStats_CPUStats `protobuf:"bytes,1,opt,name=cpu" json:"cpu,omitempty"` + Memory *ComponentStats_MemoryStats `protobuf:"bytes,3,opt,name=memory" json:"memory,omitempty"` + Goroutines uint64 `protobuf:"varint,4,opt,name=goroutines,proto3" json:"goroutines,omitempty"` + GcCpuFraction float32 `protobuf:"fixed32,5,opt,name=gc_cpu_fraction,json=gcCpuFraction,proto3" json:"gc_cpu_fraction,omitempty"` +} + +func (m *ComponentStats) Reset() { *m = ComponentStats{} } +func (m *ComponentStats) String() string { return proto.CompactTextString(m) } +func (*ComponentStats) ProtoMessage() {} +func (*ComponentStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3} } + +func (m *ComponentStats) GetCpu() *ComponentStats_CPUStats { + if m != nil { + return m.Cpu + } + return nil +} + +func (m *ComponentStats) GetMemory() *ComponentStats_MemoryStats { + if m != nil { + return m.Memory + } + return nil +} + +type ComponentStats_CPUStats struct { + User float32 `protobuf:"fixed32,1,opt,name=user,proto3" json:"user,omitempty"` + System float32 `protobuf:"fixed32,2,opt,name=system,proto3" json:"system,omitempty"` + Idle float32 `protobuf:"fixed32,3,opt,name=idle,proto3" json:"idle,omitempty"` +} + +func (m *ComponentStats_CPUStats) Reset() { *m = ComponentStats_CPUStats{} } +func (m *ComponentStats_CPUStats) String() string { return proto.CompactTextString(m) } +func (*ComponentStats_CPUStats) ProtoMessage() {} +func (*ComponentStats_CPUStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3, 0} } + +type ComponentStats_MemoryStats struct { + Memory uint64 `protobuf:"varint,1,opt,name=memory,proto3" json:"memory,omitempty"` + Swap uint64 `protobuf:"varint,2,opt,name=swap,proto3" json:"swap,omitempty"` +} + +func (m *ComponentStats_MemoryStats) Reset() { *m = ComponentStats_MemoryStats{} } +func (m *ComponentStats_MemoryStats) String() string { return proto.CompactTextString(m) } +func (*ComponentStats_MemoryStats) ProtoMessage() {} +func (*ComponentStats_MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{3, 1} } func init() { proto.RegisterType((*Percentiles)(nil), "api.Percentiles") proto.RegisterType((*Rates)(nil), "api.Rates") - proto.RegisterType((*ServerMetadata)(nil), "api.ServerMetadata") + proto.RegisterType((*SystemStats)(nil), "api.SystemStats") + proto.RegisterType((*SystemStats_Loadstats)(nil), "api.SystemStats.Loadstats") + proto.RegisterType((*SystemStats_CPUStats)(nil), "api.SystemStats.CPUStats") + proto.RegisterType((*SystemStats_MemoryStats)(nil), "api.SystemStats.MemoryStats") + proto.RegisterType((*ComponentStats)(nil), "api.ComponentStats") + proto.RegisterType((*ComponentStats_CPUStats)(nil), "api.ComponentStats.CPUStats") + proto.RegisterType((*ComponentStats_MemoryStats)(nil), "api.ComponentStats.MemoryStats") } func (m *Percentiles) Marshal() (data []byte, err error) { size := m.Size() @@ -169,7 +280,235 @@ func (m *Rates) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ServerMetadata) Marshal() (data []byte, err error) { +func (m *SystemStats) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SystemStats) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Load != nil { + data[i] = 0xa + i++ + i = encodeVarintApi(data, i, uint64(m.Load.Size())) + n1, err := m.Load.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } + if m.Cpu != nil { + data[i] = 0x12 + i++ + i = encodeVarintApi(data, i, uint64(m.Cpu.Size())) + n2, err := m.Cpu.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.Memory != nil { + data[i] = 0x1a + i++ + i = encodeVarintApi(data, i, uint64(m.Memory.Size())) + n3, err := m.Memory.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *SystemStats_Loadstats) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SystemStats_Loadstats) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Load1 != 0 { + data[i] = 0xd + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Load1)))) + } + if m.Load5 != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Load5)))) + } + if m.Load15 != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Load15)))) + } + return i, nil +} + +func (m *SystemStats_CPUStats) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SystemStats_CPUStats) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.User != 0 { + data[i] = 0xd + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.User)))) + } + if m.System != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.System)))) + } + if m.Idle != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Idle)))) + } + return i, nil +} + +func (m *SystemStats_MemoryStats) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *SystemStats_MemoryStats) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Total != 0 { + data[i] = 0x8 + i++ + i = encodeVarintApi(data, i, uint64(m.Total)) + } + if m.Available != 0 { + data[i] = 0x10 + i++ + i = encodeVarintApi(data, i, uint64(m.Available)) + } + if m.Used != 0 { + data[i] = 0x18 + i++ + i = encodeVarintApi(data, i, uint64(m.Used)) + } + return i, nil +} + +func (m *ComponentStats) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ComponentStats) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Cpu != nil { + data[i] = 0xa + i++ + i = encodeVarintApi(data, i, uint64(m.Cpu.Size())) + n4, err := m.Cpu.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Memory != nil { + data[i] = 0x1a + i++ + i = encodeVarintApi(data, i, uint64(m.Memory.Size())) + n5, err := m.Memory.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Goroutines != 0 { + data[i] = 0x20 + i++ + i = encodeVarintApi(data, i, uint64(m.Goroutines)) + } + if m.GcCpuFraction != 0 { + data[i] = 0x2d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.GcCpuFraction)))) + } + return i, nil +} + +func (m *ComponentStats_CPUStats) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ComponentStats_CPUStats) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.User != 0 { + data[i] = 0xd + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.User)))) + } + if m.System != 0 { + data[i] = 0x15 + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.System)))) + } + if m.Idle != 0 { + data[i] = 0x1d + i++ + i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Idle)))) + } + return i, nil +} + +func (m *ComponentStats_MemoryStats) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -179,11 +518,21 @@ func (m *ServerMetadata) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *ServerMetadata) MarshalTo(data []byte) (int, error) { +func (m *ComponentStats_MemoryStats) MarshalTo(data []byte) (int, error) { var i int _ = i var l int _ = l + if m.Memory != 0 { + data[i] = 0x8 + i++ + i = encodeVarintApi(data, i, uint64(m.Memory)) + } + if m.Swap != 0 { + data[i] = 0x10 + i++ + i = encodeVarintApi(data, i, uint64(m.Swap)) + } return i, nil } @@ -262,9 +611,113 @@ func (m *Rates) Size() (n int) { return n } -func (m *ServerMetadata) Size() (n int) { +func (m *SystemStats) Size() (n int) { + var l int + _ = l + if m.Load != nil { + l = m.Load.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Cpu != nil { + l = m.Cpu.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Memory != nil { + l = m.Memory.Size() + n += 1 + l + sovApi(uint64(l)) + } + return n +} + +func (m *SystemStats_Loadstats) Size() (n int) { + var l int + _ = l + if m.Load1 != 0 { + n += 5 + } + if m.Load5 != 0 { + n += 5 + } + if m.Load15 != 0 { + n += 5 + } + return n +} + +func (m *SystemStats_CPUStats) Size() (n int) { + var l int + _ = l + if m.User != 0 { + n += 5 + } + if m.System != 0 { + n += 5 + } + if m.Idle != 0 { + n += 5 + } + return n +} + +func (m *SystemStats_MemoryStats) Size() (n int) { + var l int + _ = l + if m.Total != 0 { + n += 1 + sovApi(uint64(m.Total)) + } + if m.Available != 0 { + n += 1 + sovApi(uint64(m.Available)) + } + if m.Used != 0 { + n += 1 + sovApi(uint64(m.Used)) + } + return n +} + +func (m *ComponentStats) Size() (n int) { + var l int + _ = l + if m.Cpu != nil { + l = m.Cpu.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Memory != nil { + l = m.Memory.Size() + n += 1 + l + sovApi(uint64(l)) + } + if m.Goroutines != 0 { + n += 1 + sovApi(uint64(m.Goroutines)) + } + if m.GcCpuFraction != 0 { + n += 5 + } + return n +} + +func (m *ComponentStats_CPUStats) Size() (n int) { + var l int + _ = l + if m.User != 0 { + n += 5 + } + if m.System != 0 { + n += 5 + } + if m.Idle != 0 { + n += 5 + } + return n +} + +func (m *ComponentStats_MemoryStats) Size() (n int) { var l int _ = l + if m.Memory != 0 { + n += 1 + sovApi(uint64(m.Memory)) + } + if m.Swap != 0 { + n += 1 + sovApi(uint64(m.Swap)) + } return n } @@ -549,7 +1002,7 @@ func (m *Rates) Unmarshal(data []byte) error { } return nil } -func (m *ServerMetadata) Unmarshal(data []byte) error { +func (m *SystemStats) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -572,12 +1025,731 @@ func (m *ServerMetadata) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ServerMetadata: wiretype end group for non-group") + return fmt.Errorf("proto: SystemStats: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ServerMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: SystemStats: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Load", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Load == nil { + m.Load = &SystemStats_Loadstats{} + } + if err := m.Load.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Cpu", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Cpu == nil { + m.Cpu = &SystemStats_CPUStats{} + } + if err := m.Cpu.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Memory == nil { + m.Memory = &SystemStats_MemoryStats{} + } + if err := m.Memory.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Loadstats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Loadstats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Load1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Load5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load15", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Load15 = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CPUStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CPUStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.User = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.System = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Idle", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Idle = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MemoryStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemoryStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Total", wireType) + } + m.Total = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Total |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Available", wireType) + } + m.Available = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Available |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Used", wireType) + } + m.Used = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Used |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ComponentStats) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ComponentStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ComponentStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Cpu", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Cpu == nil { + m.Cpu = &ComponentStats_CPUStats{} + } + if err := m.Cpu.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthApi + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Memory == nil { + m.Memory = &ComponentStats_MemoryStats{} + } + if err := m.Memory.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Goroutines", wireType) + } + m.Goroutines = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Goroutines |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 5: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field GcCpuFraction", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.GcCpuFraction = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CPUStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CPUStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field User", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.User = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.System = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Idle", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(data[iNdEx-4]) + v |= uint32(data[iNdEx-3]) << 8 + v |= uint32(data[iNdEx-2]) << 16 + v |= uint32(data[iNdEx-1]) << 24 + m.Idle = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipApi(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthApi + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MemoryStats: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MemoryStats: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Memory", wireType) + } + m.Memory = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Memory |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Swap", wireType) + } + m.Swap = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Swap |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipApi(data[iNdEx:]) @@ -707,22 +1879,38 @@ var ( func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 261 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, - 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, - 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0x04, - 0x61, 0xbd, 0x82, 0xa2, 0xfc, 0x92, 0x7c, 0x21, 0xe6, 0xc4, 0x82, 0x4c, 0xa5, 0xb3, 0x4c, 0x5c, - 0xdc, 0x01, 0xa9, 0x45, 0xc9, 0xa9, 0x79, 0x25, 0x99, 0x39, 0xa9, 0xc5, 0x42, 0x0a, 0x5c, 0xdc, - 0x05, 0x70, 0xae, 0xa1, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x53, 0x10, 0xb2, 0x10, 0xaa, 0x0a, 0x53, - 0x09, 0x26, 0x74, 0x15, 0xa6, 0x42, 0x4a, 0x5c, 0x3c, 0x48, 0x1a, 0x0c, 0x24, 0x98, 0xc1, 0x4a, - 0x50, 0xc4, 0x50, 0xd5, 0x18, 0x99, 0x4a, 0xb0, 0xa0, 0xab, 0x31, 0x42, 0x33, 0xc7, 0xd4, 0x40, - 0x82, 0x15, 0x5d, 0x8d, 0x29, 0x9a, 0x39, 0xe6, 0xa6, 0x12, 0x6c, 0xe8, 0x6a, 0xcc, 0xd1, 0xcc, - 0xb1, 0x34, 0x90, 0x60, 0x47, 0x57, 0x63, 0x89, 0x66, 0x8e, 0xa5, 0xa9, 0x04, 0x07, 0x86, 0x1a, - 0x74, 0x73, 0x2c, 0x25, 0x38, 0x31, 0xd4, 0x58, 0x2a, 0x79, 0x73, 0xb1, 0x06, 0x25, 0x96, 0xa4, - 0x16, 0x0b, 0x89, 0x70, 0xb1, 0x16, 0x25, 0x96, 0xc0, 0x83, 0x10, 0xc2, 0x81, 0x89, 0xc2, 0x82, - 0x0d, 0xc2, 0x11, 0x12, 0xe3, 0x62, 0x03, 0x4b, 0x9b, 0x42, 0x83, 0x0a, 0xca, 0x53, 0x12, 0xe0, - 0xe2, 0x0b, 0x4e, 0x2d, 0x2a, 0x4b, 0x2d, 0xf2, 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x74, - 0x12, 0x38, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, 0x3c, - 0x96, 0x63, 0x48, 0x62, 0x03, 0x47, 0xa6, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0xd3, 0xfb, 0x1b, - 0x5d, 0xfd, 0x01, 0x00, 0x00, + // 526 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0xcf, 0x6e, 0xd3, 0x40, + 0x10, 0xc6, 0x71, 0xe2, 0x84, 0x66, 0xcc, 0x3f, 0xad, 0x50, 0x65, 0xa2, 0x2a, 0x54, 0x39, 0x20, + 0x24, 0x44, 0x12, 0x02, 0x56, 0xe4, 0x2b, 0x91, 0x38, 0xf0, 0xb7, 0x72, 0xdb, 0x73, 0xb5, 0x71, + 0x96, 0xc4, 0xc2, 0xf6, 0x5a, 0xde, 0x35, 0x55, 0xdf, 0x83, 0x03, 0x8f, 0xd4, 0x0b, 0x12, 0x8f, + 0x80, 0xc2, 0x8b, 0xa0, 0x1d, 0xaf, 0x8d, 0xbd, 0xe9, 0xb1, 0x87, 0x48, 0xf3, 0xcd, 0xfe, 0xf6, + 0xf3, 0xcc, 0x67, 0x39, 0xf0, 0x72, 0x13, 0xc9, 0x6d, 0xb1, 0x9a, 0x84, 0x3c, 0x99, 0x9e, 0x6d, + 0xd9, 0xd9, 0x36, 0x4a, 0x37, 0xe2, 0x33, 0x93, 0x97, 0x3c, 0xff, 0x36, 0x95, 0x32, 0x9d, 0xd2, + 0x2c, 0x52, 0xbf, 0x49, 0x96, 0x73, 0xc9, 0x49, 0x97, 0x66, 0xd1, 0xf8, 0x57, 0x07, 0x9c, 0x13, + 0x96, 0x87, 0x2c, 0x95, 0x51, 0xcc, 0x04, 0x39, 0x06, 0x27, 0xab, 0xe5, 0x2b, 0xd7, 0x3a, 0xb6, + 0x9e, 0x77, 0x82, 0x66, 0xab, 0x4d, 0x78, 0x6e, 0xc7, 0x24, 0x3c, 0x32, 0x86, 0x7b, 0x8d, 0x0b, + 0x33, 0xb7, 0x8b, 0x48, 0xab, 0xd7, 0x66, 0xe6, 0x9e, 0x6b, 0x9b, 0xcc, 0xdc, 0xf0, 0xf1, 0x66, + 0x6e, 0xcf, 0x64, 0x3c, 0xc3, 0x67, 0xe1, 0xb9, 0x7d, 0x93, 0x59, 0x18, 0x3e, 0xfe, 0xcc, 0xbd, + 0x6b, 0x32, 0xbe, 0xe1, 0xe3, 0x7b, 0xee, 0xc1, 0x1e, 0x63, 0xfa, 0xf8, 0xee, 0x60, 0x8f, 0xf1, + 0xc7, 0x1f, 0xa0, 0x17, 0x50, 0xc9, 0x04, 0x79, 0x0c, 0xbd, 0x9c, 0xca, 0x3a, 0xc2, 0x52, 0x54, + 0xdd, 0x2a, 0xb6, 0x52, 0x90, 0x43, 0xe8, 0xe3, 0xb1, 0xa7, 0xa3, 0xd2, 0x6a, 0xfc, 0xa3, 0x0b, + 0xce, 0xe9, 0x95, 0x90, 0x2c, 0x39, 0x95, 0x54, 0x0a, 0x32, 0x01, 0x3b, 0xe6, 0x74, 0x8d, 0x96, + 0xce, 0x7c, 0x38, 0x51, 0xef, 0xb2, 0x71, 0x3e, 0xf9, 0xc8, 0xe9, 0x5a, 0xa8, 0x2a, 0x40, 0x8e, + 0xbc, 0x80, 0x6e, 0x98, 0x15, 0xf8, 0x2c, 0x67, 0xfe, 0x64, 0x0f, 0x5f, 0x9e, 0x9c, 0x63, 0x11, + 0x28, 0x8a, 0xbc, 0x81, 0x7e, 0xc2, 0x12, 0x9e, 0x5f, 0xe1, 0x10, 0xce, 0xfc, 0x68, 0x8f, 0xff, + 0x84, 0xc7, 0xe5, 0x15, 0xcd, 0x0e, 0xbf, 0xc0, 0xa0, 0x7e, 0xaa, 0xda, 0x4e, 0x3d, 0xb7, 0xde, + 0x19, 0x45, 0xd5, 0xad, 0x77, 0x46, 0xa1, 0x76, 0xc6, 0xe3, 0x7a, 0xe7, 0x52, 0x0d, 0xdf, 0xc3, + 0x41, 0x35, 0x17, 0x21, 0x60, 0x17, 0x82, 0xe5, 0xda, 0x0e, 0x6b, 0x75, 0x4f, 0xe0, 0x4c, 0xda, + 0x4e, 0x2b, 0xc5, 0x46, 0xeb, 0x98, 0x69, 0x37, 0xac, 0x87, 0xe7, 0xe0, 0x34, 0x66, 0x56, 0x83, + 0x48, 0x2e, 0x69, 0x8c, 0x7e, 0x76, 0x50, 0x0a, 0x72, 0x04, 0x03, 0xfa, 0x9d, 0x46, 0x31, 0x5d, + 0xc5, 0x0c, 0x3d, 0xed, 0xe0, 0x7f, 0x43, 0x8f, 0xb0, 0x46, 0x5b, 0x1b, 0x47, 0x58, 0x8f, 0xaf, + 0x3b, 0xf0, 0x60, 0xc9, 0x93, 0x8c, 0xa7, 0x2c, 0x95, 0xd5, 0x9b, 0xc1, 0xa4, 0xad, 0x46, 0x72, + 0x6d, 0xc2, 0x08, 0x7b, 0x61, 0x84, 0xfd, 0xf4, 0xa6, 0x2b, 0x37, 0xe4, 0x4d, 0x46, 0x00, 0x1b, + 0x9e, 0xf3, 0x42, 0x46, 0x29, 0x13, 0xf8, 0xd5, 0xd8, 0x41, 0xa3, 0x43, 0x9e, 0xc1, 0xc3, 0x4d, + 0x78, 0x11, 0x66, 0xc5, 0xc5, 0xd7, 0x9c, 0x86, 0x32, 0xe2, 0xa9, 0xfe, 0x6c, 0xee, 0x6f, 0xc2, + 0x65, 0x56, 0xbc, 0xd3, 0xcd, 0x5b, 0x8d, 0xd9, 0x6f, 0xc7, 0x7c, 0x58, 0xef, 0x56, 0xe6, 0x5c, + 0x8d, 0x4e, 0xc0, 0x16, 0x97, 0x34, 0xd3, 0x19, 0x63, 0xfd, 0xf6, 0xd1, 0xf5, 0x6e, 0x64, 0xfd, + 0xde, 0x8d, 0xac, 0x3f, 0xbb, 0x91, 0xf5, 0xf3, 0xef, 0xe8, 0xce, 0xaa, 0x8f, 0x7f, 0x4e, 0xaf, + 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x03, 0x07, 0xe4, 0xab, 0xcd, 0x04, 0x00, 0x00, } diff --git a/api/api.proto b/api/api.proto index 59c22df14..a3e821c59 100644 --- a/api/api.proto +++ b/api/api.proto @@ -23,6 +23,40 @@ message Rates { float rate15 = 3; } -message ServerMetadata { +message SystemStats { + message Loadstats { + float load1 = 1; + float load5 = 2; + float load15 = 3; + } + Loadstats load = 1; + message CPUStats { + float user = 1; + float system = 2; + float idle = 3; + } + CPUStats cpu = 2; + message MemoryStats { + uint64 total = 1; + uint64 available = 2; + uint64 used = 3; + } + MemoryStats memory = 3; +} + +message ComponentStats { + message CPUStats { + float user = 1; + float system = 2; + float idle = 3; + } + CPUStats cpu = 1; + message MemoryStats { + uint64 memory = 1; + uint64 swap = 2; + } + MemoryStats memory = 3; + uint64 goroutines = 4; + float gc_cpu_fraction = 5; } diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 972a31702..ffc7dcf4a 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -335,22 +335,18 @@ func (m *StatusRequest) String() string { return proto.CompactTextStr func (*StatusRequest) ProtoMessage() {} func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{10} } -// message Status is the response to the StatusRequest type Status struct { - // Uplink - Uplink *api.Rates `protobuf:"bytes,1,opt,name=uplink" json:"uplink,omitempty"` - UplinkUnique *api.Rates `protobuf:"bytes,2,opt,name=uplink_unique,json=uplinkUnique" json:"uplink_unique,omitempty"` - // Downlink - Downlink *api.Rates `protobuf:"bytes,11,opt,name=downlink" json:"downlink,omitempty"` - // Activations - Activations *api.Rates `protobuf:"bytes,21,opt,name=activations" json:"activations,omitempty"` - ActivationsUnique *api.Rates `protobuf:"bytes,22,opt,name=activations_unique,json=activationsUnique" json:"activations_unique,omitempty"` - ActivationsAccepted *api.Rates `protobuf:"bytes,23,opt,name=activations_accepted,json=activationsAccepted" json:"activations_accepted,omitempty"` - // Deduplication histogram percentiles - Deduplication *api.Percentiles `protobuf:"bytes,31,opt,name=deduplication" json:"deduplication,omitempty"` + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + UplinkUnique *api.Rates `protobuf:"bytes,12,opt,name=uplink_unique,json=uplinkUnique" json:"uplink_unique,omitempty"` + Downlink *api.Rates `protobuf:"bytes,13,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,14,opt,name=activations" json:"activations,omitempty"` + ActivationsUnique *api.Rates `protobuf:"bytes,15,opt,name=activations_unique,json=activationsUnique" json:"activations_unique,omitempty"` + Deduplication *api.Percentiles `protobuf:"bytes,16,opt,name=deduplication" json:"deduplication,omitempty"` // Connections - ConnectedRouters uint32 `protobuf:"varint,41,opt,name=connected_routers,json=connectedRouters,proto3" json:"connected_routers,omitempty"` - ConnectedHandlers uint32 `protobuf:"varint,42,opt,name=connected_handlers,json=connectedHandlers,proto3" json:"connected_handlers,omitempty"` + ConnectedRouters uint32 `protobuf:"varint,21,opt,name=connected_routers,json=connectedRouters,proto3" json:"connected_routers,omitempty"` + ConnectedHandlers uint32 `protobuf:"varint,22,opt,name=connected_handlers,json=connectedHandlers,proto3" json:"connected_handlers,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -358,6 +354,20 @@ func (m *Status) String() string { return proto.CompactTextString(m) func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{11} } +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + func (m *Status) GetUplink() *api.Rates { if m != nil { return m.Uplink @@ -393,13 +403,6 @@ func (m *Status) GetActivationsUnique() *api.Rates { return nil } -func (m *Status) GetActivationsAccepted() *api.Rates { - if m != nil { - return m.ActivationsAccepted - } - return nil -} - func (m *Status) GetDeduplication() *api.Percentiles { if m != nil { return m.Deduplication @@ -1504,95 +1507,99 @@ func (m *Status) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.Uplink != nil { + if m.System != nil { data[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n27, err := m.Uplink.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.System.Size())) + n27, err := m.System.MarshalTo(data[i:]) if err != nil { return 0, err } i += n27 } - if m.UplinkUnique != nil { + if m.Component != nil { data[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n28, err := m.UplinkUnique.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.Component.Size())) + n28, err := m.Component.MarshalTo(data[i:]) if err != nil { return 0, err } i += n28 } - if m.Downlink != nil { + if m.Uplink != nil { data[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n29, err := m.Downlink.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) + n29, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } i += n29 } - if m.Activations != nil { - data[i] = 0xaa - i++ - data[i] = 0x1 + if m.UplinkUnique != nil { + data[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n30, err := m.Activations.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) + n30, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } i += n30 } - if m.ActivationsUnique != nil { - data[i] = 0xb2 - i++ - data[i] = 0x1 + if m.Downlink != nil { + data[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n31, err := m.ActivationsUnique.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) + n31, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } i += n31 } - if m.ActivationsAccepted != nil { - data[i] = 0xba - i++ - data[i] = 0x1 + if m.Activations != nil { + data[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(m.ActivationsAccepted.Size())) - n32, err := m.ActivationsAccepted.MarshalTo(data[i:]) + i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) + n32, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } i += n32 } + if m.ActivationsUnique != nil { + data[i] = 0x7a + i++ + i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) + n33, err := m.ActivationsUnique.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n33 + } if m.Deduplication != nil { - data[i] = 0xfa + data[i] = 0x82 i++ data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n33, err := m.Deduplication.MarshalTo(data[i:]) + n34, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n33 + i += n34 } if m.ConnectedRouters != 0 { - data[i] = 0xc8 + data[i] = 0xa8 i++ - data[i] = 0x2 + data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ConnectedRouters)) } if m.ConnectedHandlers != 0 { - data[i] = 0xd0 + data[i] = 0xb0 i++ - data[i] = 0x2 + data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ConnectedHandlers)) } @@ -1945,6 +1952,14 @@ func (m *StatusRequest) Size() (n int) { func (m *Status) Size() (n int) { var l int _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovBroker(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.Uplink != nil { l = m.Uplink.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1959,15 +1974,11 @@ func (m *Status) Size() (n int) { } if m.Activations != nil { l = m.Activations.Size() - n += 2 + l + sovBroker(uint64(l)) + n += 1 + l + sovBroker(uint64(l)) } if m.ActivationsUnique != nil { l = m.ActivationsUnique.Size() - n += 2 + l + sovBroker(uint64(l)) - } - if m.ActivationsAccepted != nil { - l = m.ActivationsAccepted.Size() - n += 2 + l + sovBroker(uint64(l)) + n += 1 + l + sovBroker(uint64(l)) } if m.Deduplication != nil { l = m.Deduplication.Size() @@ -4232,7 +4243,7 @@ func (m *Status) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4256,16 +4267,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Uplink == nil { - m.Uplink = &api.Rates{} + if m.System == nil { + m.System = &api.SystemStats{} } - if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field UplinkUnique", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4289,16 +4300,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.UplinkUnique == nil { - m.UplinkUnique = &api.Rates{} + if m.Component == nil { + m.Component = &api.ComponentStats{} } - if err := m.UplinkUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4322,16 +4333,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Downlink == nil { - m.Downlink = &api.Rates{} + if m.Uplink == nil { + m.Uplink = &api.Rates{} } - if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 21: + case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field UplinkUnique", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4355,16 +4366,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Activations == nil { - m.Activations = &api.Rates{} + if m.UplinkUnique == nil { + m.UplinkUnique = &api.Rates{} } - if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.UplinkUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 22: + case 13: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ActivationsUnique", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4388,16 +4399,49 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ActivationsUnique == nil { - m.ActivationsUnique = &api.Rates{} + if m.Downlink == nil { + m.Downlink = &api.Rates{} } - if err := m.ActivationsUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 23: + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 15: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ActivationsAccepted", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ActivationsUnique", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -4421,14 +4465,14 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ActivationsAccepted == nil { - m.ActivationsAccepted = &api.Rates{} + if m.ActivationsUnique == nil { + m.ActivationsUnique = &api.Rates{} } - if err := m.ActivationsAccepted.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ActivationsUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 31: + case 16: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Deduplication", wireType) } @@ -4461,7 +4505,7 @@ func (m *Status) Unmarshal(data []byte) error { return err } iNdEx = postIndex - case 41: + case 21: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ConnectedRouters", wireType) } @@ -4480,7 +4524,7 @@ func (m *Status) Unmarshal(data []byte) error { break } } - case 42: + case 22: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ConnectedHandlers", wireType) } @@ -4738,76 +4782,77 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1131 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0xcb, 0x6e, 0xdb, 0x46, - 0x17, 0xfe, 0x69, 0x25, 0xb2, 0x7d, 0x64, 0x5d, 0x3c, 0xf1, 0x85, 0x51, 0xfe, 0xc8, 0xaa, 0x0a, - 0x04, 0xea, 0x25, 0x52, 0xaa, 0xa2, 0x0d, 0x82, 0x5e, 0x0c, 0x39, 0x36, 0x5a, 0x17, 0x50, 0x1a, - 0x30, 0x76, 0x57, 0x05, 0x84, 0x11, 0x79, 0x4c, 0x0d, 0x4c, 0x91, 0x0c, 0x67, 0x28, 0xc7, 0xfb, - 0xa2, 0xcf, 0xd0, 0x6d, 0xfb, 0x18, 0x05, 0xba, 0xe8, 0xae, 0xbb, 0x76, 0xdd, 0x45, 0x51, 0xb8, - 0x9b, 0x6e, 0xfa, 0x0e, 0x05, 0x87, 0x43, 0x8a, 0xb2, 0xad, 0xd8, 0x2d, 0xbc, 0x68, 0x9a, 0xae, - 0xc4, 0xf9, 0xce, 0x77, 0x3e, 0x0e, 0xcf, 0x4d, 0x33, 0x70, 0xdf, 0x66, 0x62, 0x18, 0x0e, 0x5a, - 0xa6, 0x37, 0x6a, 0xef, 0x0d, 0x71, 0x6f, 0xc8, 0x5c, 0x9b, 0x3f, 0x42, 0x71, 0xe4, 0x05, 0x87, - 0x6d, 0x21, 0xdc, 0x36, 0xf5, 0x59, 0x7b, 0x10, 0x78, 0x87, 0x18, 0xa8, 0x9f, 0x96, 0x1f, 0x78, - 0xc2, 0x23, 0xf9, 0x78, 0x55, 0xbd, 0x65, 0x7b, 0x9e, 0xed, 0x60, 0x5b, 0xa2, 0x83, 0xf0, 0xa0, - 0x8d, 0x23, 0x5f, 0x1c, 0xc7, 0xa4, 0xea, 0xdd, 0x8c, 0xba, 0xed, 0xd9, 0xde, 0x84, 0x15, 0xad, - 0xe4, 0x42, 0x3e, 0x9d, 0x43, 0x9f, 0xb9, 0x19, 0xea, 0x33, 0x45, 0x7f, 0xef, 0x32, 0x74, 0x49, - 0x35, 0x3d, 0x27, 0x7d, 0x50, 0xce, 0x0f, 0x2e, 0xe3, 0x6c, 0x53, 0x81, 0x47, 0xf4, 0x38, 0xf9, - 0x8d, 0x5d, 0x1b, 0x5f, 0xce, 0x41, 0x69, 0xdb, 0x3b, 0x72, 0x1d, 0xe6, 0x1e, 0x7e, 0xea, 0x0b, - 0xe6, 0xb9, 0xa4, 0x06, 0xc0, 0x2c, 0x74, 0x05, 0x3b, 0x60, 0x18, 0xe8, 0x5a, 0x5d, 0x6b, 0x2e, - 0x1a, 0x19, 0x84, 0xdc, 0x06, 0x50, 0x1a, 0x7d, 0x66, 0xe9, 0x73, 0xd2, 0xbe, 0xa8, 0x90, 0x5d, - 0x8b, 0xac, 0xc0, 0x75, 0x6e, 0x7a, 0x01, 0xea, 0xb9, 0xba, 0xd6, 0x2c, 0x1a, 0xf1, 0x82, 0x54, - 0x61, 0xc1, 0x42, 0x6a, 0x39, 0xcc, 0x45, 0xfd, 0x5a, 0x5d, 0x6b, 0xe6, 0x8c, 0x74, 0x4d, 0xb6, - 0xa0, 0x9c, 0x7c, 0x50, 0xdf, 0xf4, 0xdc, 0x03, 0x66, 0xeb, 0xd7, 0xeb, 0x5a, 0xb3, 0xd0, 0xb9, - 0xd9, 0x4a, 0x3f, 0x74, 0xef, 0xd9, 0x43, 0x69, 0x09, 0x03, 0x1a, 0x6d, 0xd2, 0x28, 0x25, 0x96, - 0x18, 0x26, 0x9b, 0x50, 0x4a, 0x36, 0xa5, 0x24, 0xf2, 0x52, 0x42, 0x6f, 0x25, 0xdf, 0x7b, 0x5a, - 0xa1, 0xa8, 0x0c, 0x31, 0xda, 0xf8, 0x2e, 0x07, 0xc5, 0x7d, 0x3f, 0x0a, 0x43, 0x0f, 0x39, 0xa7, - 0x36, 0x12, 0x1d, 0xe6, 0x7d, 0x7a, 0xec, 0x78, 0xd4, 0x92, 0x41, 0x58, 0x32, 0x92, 0x25, 0x79, - 0x04, 0xf3, 0x16, 0x8e, 0xfb, 0x18, 0x32, 0xbd, 0x10, 0x59, 0xb6, 0xde, 0xf9, 0xf9, 0x97, 0x8d, - 0xb7, 0x2e, 0x4a, 0x42, 0x14, 0x87, 0xb6, 0x38, 0xf6, 0x91, 0xb7, 0xb6, 0x71, 0xbc, 0xb3, 0xbf, - 0x6b, 0xe4, 0x2d, 0x1c, 0xef, 0x84, 0x2c, 0xd2, 0xa3, 0xbe, 0x2f, 0xf5, 0x96, 0xfe, 0x96, 0x5e, - 0xd7, 0xf7, 0xa5, 0x1e, 0xf5, 0xfd, 0x48, 0x6f, 0x15, 0xa2, 0xa7, 0x28, 0x3b, 0x45, 0x99, 0x9d, - 0xeb, 0xd4, 0xf7, 0x77, 0xad, 0x08, 0x8e, 0xb6, 0xcd, 0x2c, 0xbd, 0x14, 0xc3, 0x16, 0x8e, 0x77, - 0x2d, 0xd2, 0x85, 0xe5, 0x34, 0xfc, 0x23, 0x14, 0xd4, 0xa2, 0x82, 0xea, 0xab, 0x32, 0x7a, 0x2b, - 0x93, 0x04, 0x18, 0xcf, 0x7a, 0xca, 0x66, 0x54, 0x12, 0x30, 0x41, 0xc8, 0x87, 0x50, 0x49, 0xa2, - 0x9f, 0x2a, 0xac, 0x49, 0x85, 0x1b, 0x69, 0xfc, 0x33, 0x02, 0x65, 0x85, 0xa5, 0xfe, 0x5d, 0xa8, - 0x58, 0xaa, 0x08, 0xfb, 0x9e, 0xac, 0x42, 0xae, 0x6f, 0xd4, 0x73, 0xcd, 0x42, 0x67, 0xad, 0xa5, - 0x3a, 0x75, 0xba, 0x48, 0x8d, 0xb2, 0x35, 0xb5, 0xe6, 0x8d, 0x6f, 0xe7, 0xa0, 0x9c, 0x70, 0x5e, - 0xb6, 0x0c, 0x6e, 0x42, 0xf9, 0x54, 0xf8, 0x54, 0xfe, 0x66, 0x45, 0xaf, 0x34, 0x1d, 0xbd, 0x46, - 0x08, 0xfa, 0x36, 0x8e, 0x99, 0x89, 0x5d, 0x53, 0xb0, 0x71, 0xdc, 0x1f, 0xc8, 0x7d, 0xcf, 0xe5, - 0xcf, 0x0b, 0xe2, 0x39, 0xaf, 0x2d, 0xfc, 0xa5, 0xd7, 0xfe, 0x91, 0x83, 0x9b, 0xdb, 0x68, 0x85, - 0xbe, 0xc3, 0x4c, 0x2a, 0xd0, 0xfa, 0xaf, 0xff, 0xae, 0xb4, 0xff, 0x72, 0x97, 0xee, 0xbf, 0x0d, - 0x28, 0x70, 0x0c, 0xc6, 0x18, 0xf4, 0x05, 0x1b, 0xa1, 0xbe, 0x2e, 0x07, 0x34, 0xc4, 0xd0, 0x1e, - 0x1b, 0x21, 0xd9, 0x86, 0xe5, 0x40, 0x15, 0x44, 0x5f, 0xe0, 0xc8, 0x77, 0xa8, 0x40, 0x7d, 0x43, - 0xee, 0x71, 0xfd, 0x74, 0xb2, 0x55, 0xfe, 0x8c, 0x4a, 0xe2, 0xb1, 0xa7, 0x1c, 0x1a, 0xbf, 0xe7, - 0x60, 0xfd, 0x6c, 0x9d, 0x3d, 0x0d, 0x91, 0x8b, 0x17, 0x38, 0xdb, 0xff, 0x80, 0xf9, 0xd9, 0x83, - 0x1b, 0x34, 0x8d, 0xe8, 0x44, 0x62, 0x5d, 0x4a, 0xfc, 0x7f, 0xb2, 0x89, 0x49, 0xd8, 0x53, 0x2d, - 0x42, 0xcf, 0x60, 0x57, 0x31, 0x8e, 0x7f, 0xbc, 0x06, 0xaf, 0x66, 0x5b, 0xfb, 0xdf, 0x97, 0xf6, - 0x17, 0xae, 0xc9, 0xaf, 0xb8, 0x48, 0x4e, 0xcd, 0x0c, 0xfd, 0xcc, 0xcc, 0xe8, 0xcd, 0x9e, 0x19, - 0xf5, 0xb4, 0x8c, 0x66, 0xfc, 0xeb, 0x9c, 0x33, 0x3c, 0xbe, 0x98, 0x83, 0xea, 0x84, 0xf8, 0x70, - 0x48, 0x1d, 0x07, 0x5d, 0x1b, 0x5f, 0xb2, 0x42, 0x6a, 0xdc, 0x87, 0x5b, 0xe7, 0x46, 0xe1, 0xa2, - 0x7f, 0xeb, 0x06, 0x81, 0xca, 0x93, 0x70, 0xc0, 0xcd, 0x80, 0x0d, 0x92, 0xa0, 0x35, 0xca, 0x50, - 0x7c, 0x22, 0xa8, 0x08, 0x79, 0x02, 0x7c, 0x9f, 0x83, 0x7c, 0x8c, 0x90, 0x06, 0xe4, 0x43, 0xf9, - 0x7f, 0x2c, 0x85, 0x0a, 0x1d, 0x68, 0x45, 0xb7, 0x15, 0x83, 0x0a, 0xe4, 0x86, 0xb2, 0x90, 0x36, - 0x14, 0xe3, 0xa7, 0x7e, 0xe8, 0xb2, 0xa7, 0x21, 0xca, 0xdb, 0xc0, 0x34, 0x75, 0x29, 0x26, 0xec, - 0x4b, 0x3b, 0xb9, 0x03, 0x0b, 0xc9, 0xa4, 0x50, 0x67, 0x85, 0x2c, 0x37, 0xb5, 0x91, 0x37, 0xa1, - 0x30, 0x29, 0x39, 0xae, 0x1a, 0x25, 0x4b, 0xcd, 0x9a, 0xc9, 0x03, 0xc8, 0x14, 0x28, 0x4f, 0xf6, - 0xb2, 0x76, 0xc6, 0x69, 0x39, 0xc3, 0x52, 0x1b, 0xfa, 0x00, 0x56, 0xb2, 0xae, 0xd4, 0x34, 0xd1, - 0x17, 0x68, 0xa9, 0xae, 0xc8, 0x3a, 0x67, 0x9a, 0x87, 0x77, 0x15, 0x8d, 0xbc, 0x0b, 0x45, 0x2b, - 0x9d, 0x72, 0xd1, 0x01, 0x28, 0xae, 0xef, 0x8a, 0xf4, 0x7b, 0x8c, 0x81, 0x19, 0xdd, 0x9a, 0x1c, - 0xe4, 0xc6, 0x34, 0x8d, 0xbc, 0x01, 0xcb, 0xa6, 0xe7, 0xba, 0x68, 0x0a, 0xb4, 0xfa, 0x81, 0x17, - 0x0a, 0x0c, 0xb8, 0xfe, 0x9a, 0xbc, 0x30, 0x55, 0x52, 0x83, 0x11, 0xe3, 0xe4, 0x2e, 0x90, 0x09, - 0x79, 0x48, 0x5d, 0xcb, 0x89, 0xd8, 0xaf, 0x4b, 0xf6, 0x44, 0xe6, 0x63, 0x65, 0x68, 0x7c, 0x06, - 0xb5, 0xae, 0x9f, 0xbe, 0x4a, 0xc1, 0x06, 0xda, 0x8c, 0x8b, 0xf8, 0xea, 0x93, 0xa9, 0x38, 0x2d, - 0x5b, 0x71, 0xb7, 0x01, 0x94, 0x7a, 0xe6, 0x62, 0xa7, 0x90, 0x5d, 0xab, 0xf3, 0xcd, 0x1c, 0xe4, - 0xb7, 0x64, 0xdb, 0x92, 0x4d, 0x58, 0xec, 0x72, 0xee, 0x99, 0x8c, 0x0a, 0x24, 0xab, 0x49, 0x33, - 0x4f, 0x1d, 0xdf, 0xaa, 0xb3, 0xce, 0x05, 0x4d, 0xed, 0x9e, 0x46, 0x3e, 0x81, 0xc5, 0xb4, 0x18, - 0x89, 0x9e, 0x30, 0x4f, 0xd7, 0x67, 0xf5, 0x95, 0xc9, 0x9c, 0x98, 0x71, 0x4a, 0xbc, 0xa7, 0x91, - 0xf7, 0x61, 0xfe, 0x71, 0x38, 0x70, 0x18, 0x1f, 0x92, 0x59, 0xef, 0xac, 0xae, 0xb5, 0xe2, 0xab, - 0x7d, 0x2b, 0xb9, 0xb4, 0xb7, 0x76, 0xa2, 0xab, 0x7d, 0x53, 0x23, 0x3d, 0x58, 0x50, 0xfd, 0x84, - 0x64, 0x63, 0xf6, 0x58, 0x8a, 0xf7, 0x73, 0xe1, 0xdc, 0xea, 0x7c, 0xad, 0x41, 0x31, 0x0e, 0x52, - 0x8f, 0xba, 0xd4, 0xc6, 0x80, 0x7c, 0x0e, 0xd5, 0x38, 0xf8, 0x18, 0x9c, 0x4d, 0x0b, 0xb9, 0x93, - 0x28, 0x3e, 0x3f, 0x65, 0xb3, 0x3e, 0x80, 0x74, 0x60, 0xf1, 0x23, 0x14, 0xaa, 0x65, 0xd3, 0x4c, - 0x4c, 0x35, 0x75, 0xb5, 0x34, 0x0d, 0x6f, 0x55, 0x7e, 0x38, 0xa9, 0x69, 0x3f, 0x9d, 0xd4, 0xb4, - 0x5f, 0x4f, 0x6a, 0xda, 0x57, 0xbf, 0xd5, 0xfe, 0x37, 0xc8, 0x4b, 0xd5, 0xb7, 0xff, 0x0c, 0x00, - 0x00, 0xff, 0xff, 0x64, 0x08, 0xa4, 0x66, 0x42, 0x11, 0x00, 0x00, + // 1146 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x6f, 0x1b, 0x45, + 0x14, 0x67, 0xe3, 0xc6, 0x89, 0x9f, 0xe3, 0x3f, 0x99, 0x36, 0xc9, 0xd6, 0xa5, 0x8e, 0x31, 0x52, + 0x65, 0x09, 0x6a, 0xb7, 0x46, 0x50, 0x55, 0x20, 0x22, 0xe7, 0x8f, 0x20, 0x48, 0x2e, 0xd5, 0x26, + 0xe1, 0x84, 0x64, 0x8d, 0x77, 0x5f, 0xd6, 0xa3, 0xac, 0x77, 0xb7, 0x3b, 0xb3, 0x4e, 0x73, 0x47, + 0x7c, 0x06, 0xae, 0xf0, 0x31, 0x90, 0xb8, 0x73, 0x83, 0x33, 0x07, 0x40, 0xe1, 0xc2, 0x85, 0xef, + 0x80, 0x76, 0x76, 0x76, 0x6d, 0x27, 0x71, 0x13, 0x50, 0x0e, 0x94, 0xf6, 0xe4, 0x9d, 0xf7, 0x7e, + 0xf3, 0xdb, 0xb7, 0xbf, 0xf7, 0xc7, 0x33, 0xf0, 0xc8, 0x66, 0x62, 0x10, 0xf6, 0x9b, 0xa6, 0x37, + 0x6c, 0xed, 0x0f, 0x70, 0x7f, 0xc0, 0x5c, 0x9b, 0x3f, 0x41, 0x71, 0xec, 0x05, 0x47, 0x2d, 0x21, + 0xdc, 0x16, 0xf5, 0x59, 0xab, 0x1f, 0x78, 0x47, 0x18, 0xa8, 0x9f, 0xa6, 0x1f, 0x78, 0xc2, 0x23, + 0xd9, 0x78, 0x55, 0xb9, 0x63, 0x7b, 0x9e, 0xed, 0x60, 0x4b, 0x5a, 0xfb, 0xe1, 0x61, 0x0b, 0x87, + 0xbe, 0x38, 0x89, 0x41, 0x95, 0xfb, 0x13, 0xec, 0xb6, 0x67, 0x7b, 0x63, 0x54, 0xb4, 0x92, 0x0b, + 0xf9, 0x74, 0x01, 0x7c, 0x66, 0x30, 0xd4, 0x67, 0x0a, 0xfe, 0xe1, 0x55, 0xe0, 0x12, 0x6a, 0x7a, + 0x4e, 0xfa, 0xa0, 0x36, 0x3f, 0xbe, 0xca, 0x66, 0x9b, 0x0a, 0x3c, 0xa6, 0x27, 0xc9, 0x6f, 0xbc, + 0xb5, 0xfe, 0xf5, 0x1c, 0x14, 0xb7, 0xbd, 0x63, 0xd7, 0x61, 0xee, 0xd1, 0xe7, 0xbe, 0x60, 0x9e, + 0x4b, 0xaa, 0x00, 0xcc, 0x42, 0x57, 0xb0, 0x43, 0x86, 0x81, 0xae, 0xd5, 0xb4, 0x46, 0xce, 0x98, + 0xb0, 0x90, 0xbb, 0x00, 0x8a, 0xa3, 0xc7, 0x2c, 0x7d, 0x4e, 0xfa, 0x73, 0xca, 0xb2, 0x6b, 0x91, + 0x5b, 0x30, 0xcf, 0x4d, 0x2f, 0x40, 0x3d, 0x53, 0xd3, 0x1a, 0x05, 0x23, 0x5e, 0x90, 0x0a, 0x2c, + 0x5a, 0x48, 0x2d, 0x87, 0xb9, 0xa8, 0xdf, 0xa8, 0x69, 0x8d, 0x8c, 0x91, 0xae, 0xc9, 0x26, 0x94, + 0x92, 0x0f, 0xea, 0x99, 0x9e, 0x7b, 0xc8, 0x6c, 0x7d, 0xbe, 0xa6, 0x35, 0xf2, 0xed, 0xdb, 0xcd, + 0xf4, 0x43, 0xf7, 0x9f, 0x6f, 0x49, 0x4f, 0x18, 0xd0, 0x28, 0x48, 0xa3, 0x98, 0x78, 0x62, 0x33, + 0xd9, 0x80, 0x62, 0x12, 0x94, 0xa2, 0xc8, 0x4a, 0x0a, 0xbd, 0x99, 0x7c, 0xef, 0x59, 0x86, 0x82, + 0x72, 0xc4, 0xd6, 0xfa, 0x0f, 0x19, 0x28, 0x1c, 0xf8, 0x91, 0x0c, 0x5d, 0xe4, 0x9c, 0xda, 0x48, + 0x74, 0x58, 0xf0, 0xe9, 0x89, 0xe3, 0x51, 0x4b, 0x8a, 0xb0, 0x64, 0x24, 0x4b, 0xf2, 0x04, 0x16, + 0x2c, 0x1c, 0xf5, 0x30, 0x64, 0x7a, 0x3e, 0xf2, 0x6c, 0xbe, 0xff, 0xcb, 0xaf, 0xeb, 0x0f, 0x2f, + 0x4b, 0x42, 0xa4, 0x43, 0x4b, 0x9c, 0xf8, 0xc8, 0x9b, 0xdb, 0x38, 0xda, 0x39, 0xd8, 0x35, 0xb2, + 0x16, 0x8e, 0x76, 0x42, 0x16, 0xf1, 0x51, 0xdf, 0x97, 0x7c, 0x4b, 0xff, 0x8a, 0xaf, 0xe3, 0xfb, + 0x92, 0x8f, 0xfa, 0x7e, 0xc4, 0xb7, 0x02, 0xd1, 0x53, 0x94, 0x9d, 0x82, 0xcc, 0xce, 0x3c, 0xf5, + 0xfd, 0x5d, 0x2b, 0x32, 0x47, 0x61, 0x33, 0x4b, 0x2f, 0xc6, 0x66, 0x0b, 0x47, 0xbb, 0x16, 0xe9, + 0xc0, 0x72, 0x2a, 0xff, 0x10, 0x05, 0xb5, 0xa8, 0xa0, 0xfa, 0x8a, 0x54, 0xef, 0xd6, 0x38, 0x01, + 0xc6, 0xf3, 0xae, 0xf2, 0x19, 0xe5, 0xc4, 0x98, 0x58, 0xc8, 0xc7, 0x50, 0x4e, 0xd4, 0x4f, 0x19, + 0x56, 0x25, 0xc3, 0xcd, 0x54, 0xff, 0x09, 0x82, 0x92, 0xb2, 0xa5, 0xfb, 0x3b, 0x50, 0xb6, 0x54, + 0x11, 0xf6, 0x3c, 0x59, 0x85, 0x5c, 0x5f, 0xaf, 0x65, 0x1a, 0xf9, 0xf6, 0x6a, 0x53, 0x75, 0xea, + 0x74, 0x91, 0x1a, 0x25, 0x6b, 0x6a, 0xcd, 0xeb, 0xdf, 0xcf, 0x41, 0x29, 0xc1, 0xbc, 0x6a, 0x19, + 0xdc, 0x80, 0xd2, 0x19, 0xf9, 0x54, 0xfe, 0x66, 0xa9, 0x57, 0x9c, 0x56, 0xaf, 0x1e, 0x82, 0xbe, + 0x8d, 0x23, 0x66, 0x62, 0xc7, 0x14, 0x6c, 0x14, 0xf7, 0x07, 0x72, 0xdf, 0x73, 0xf9, 0x8b, 0x44, + 0xbc, 0xe0, 0xb5, 0xf9, 0x7f, 0xf4, 0xda, 0xbf, 0x32, 0x70, 0x7b, 0x1b, 0xad, 0xd0, 0x77, 0x98, + 0x49, 0x05, 0x5a, 0xaf, 0xfb, 0xef, 0x5a, 0xfb, 0x2f, 0x73, 0xe5, 0xfe, 0x5b, 0x87, 0x3c, 0xc7, + 0x60, 0x84, 0x41, 0x4f, 0xb0, 0x21, 0xea, 0x6b, 0x72, 0x40, 0x43, 0x6c, 0xda, 0x67, 0x43, 0x24, + 0xdb, 0xb0, 0x1c, 0xa8, 0x82, 0xe8, 0x09, 0x1c, 0xfa, 0x0e, 0x15, 0xa8, 0xaf, 0xcb, 0x18, 0xd7, + 0xce, 0x26, 0x5b, 0xe5, 0xcf, 0x28, 0x27, 0x3b, 0xf6, 0xd5, 0x86, 0xfa, 0x9f, 0x19, 0x58, 0x3b, + 0x5f, 0x67, 0xcf, 0x42, 0xe4, 0xe2, 0x25, 0xce, 0xf6, 0x7f, 0x60, 0x7e, 0x76, 0xe1, 0x26, 0x4d, + 0x15, 0x1d, 0x53, 0xac, 0x49, 0x8a, 0x37, 0xc7, 0x41, 0x8c, 0x65, 0x4f, 0xb9, 0x08, 0x3d, 0x67, + 0xbb, 0x8e, 0x71, 0xfc, 0xd3, 0x0d, 0x78, 0x7b, 0xb2, 0xb5, 0xff, 0x7f, 0x69, 0x7f, 0xe9, 0x9a, + 0xfc, 0x9a, 0x8b, 0xe4, 0xcc, 0xcc, 0xd0, 0xcf, 0xcd, 0x8c, 0xee, 0xec, 0x99, 0x51, 0x4b, 0xcb, + 0x68, 0xc6, 0xbf, 0xce, 0x05, 0xc3, 0xe3, 0xab, 0x39, 0xa8, 0x8c, 0x81, 0x5b, 0x03, 0xea, 0x38, + 0xe8, 0xda, 0xf8, 0x8a, 0x15, 0x52, 0xfd, 0x11, 0xdc, 0xb9, 0x50, 0x85, 0xcb, 0xfe, 0xad, 0xeb, + 0x04, 0xca, 0x7b, 0x61, 0x9f, 0x9b, 0x01, 0xeb, 0x27, 0xa2, 0xd5, 0x4b, 0x50, 0xd8, 0x13, 0x54, + 0x84, 0x3c, 0x31, 0xfc, 0x96, 0x81, 0x6c, 0x6c, 0x21, 0x0d, 0xc8, 0xf2, 0x13, 0x2e, 0x70, 0x28, + 0x89, 0xf2, 0xed, 0x72, 0x33, 0xba, 0xad, 0xec, 0x49, 0x53, 0x04, 0xe1, 0x86, 0xf2, 0x93, 0x87, + 0x90, 0x33, 0xbd, 0xa1, 0xef, 0xb9, 0xe8, 0x0a, 0x79, 0x1f, 0x88, 0x2a, 0x32, 0x02, 0x6f, 0x25, + 0xd6, 0x18, 0x3f, 0x46, 0x91, 0x3a, 0x64, 0x43, 0xf9, 0x67, 0xaf, 0x4e, 0x0c, 0x20, 0xf1, 0x06, + 0x15, 0xc8, 0x0d, 0xe5, 0x21, 0x2d, 0x28, 0xc4, 0x4f, 0xbd, 0xd0, 0x65, 0xcf, 0x42, 0x94, 0x6a, + 0x4f, 0x43, 0x97, 0x62, 0xc0, 0x81, 0xf4, 0x93, 0x7b, 0xb0, 0x98, 0x8c, 0x21, 0x29, 0xe5, 0x34, + 0x36, 0xf5, 0x91, 0x77, 0x21, 0x3f, 0xae, 0x67, 0x2e, 0xe5, 0x9d, 0x86, 0x4e, 0xba, 0xc9, 0x63, + 0x98, 0xa8, 0x7e, 0x9e, 0xc4, 0x52, 0x3a, 0xb7, 0x69, 0x79, 0x02, 0xa5, 0x02, 0xfa, 0x00, 0x0a, + 0x56, 0x3a, 0x03, 0xa3, 0xe3, 0x51, 0x79, 0x42, 0xc9, 0xa7, 0x18, 0x98, 0xd1, 0x9d, 0xca, 0x41, + 0x6e, 0x4c, 0xc3, 0xc8, 0x3b, 0xb0, 0x6c, 0x7a, 0xae, 0x8b, 0xa6, 0x40, 0xab, 0x17, 0x78, 0xa1, + 0xc0, 0x80, 0xcb, 0x61, 0x51, 0x30, 0xca, 0xa9, 0xc3, 0x88, 0xed, 0xe4, 0x3e, 0x90, 0x31, 0x78, + 0x40, 0x5d, 0xcb, 0x89, 0xd0, 0xab, 0x12, 0x3d, 0xa6, 0xf9, 0x54, 0x39, 0xea, 0x5f, 0x40, 0xb5, + 0xe3, 0xa7, 0xaf, 0x52, 0x66, 0x03, 0x6d, 0xc6, 0x45, 0x7c, 0x31, 0x9a, 0xa8, 0x47, 0x6d, 0xb2, + 0x1e, 0xef, 0x02, 0x28, 0xf6, 0x89, 0x6b, 0x9f, 0xb2, 0xec, 0x5a, 0xed, 0xef, 0xe6, 0x20, 0xbb, + 0x29, 0x9b, 0x9a, 0x6c, 0x40, 0xae, 0xc3, 0xb9, 0x67, 0x32, 0x2a, 0x90, 0xac, 0x24, 0xad, 0x3e, + 0x75, 0xb8, 0xab, 0xcc, 0x3a, 0x35, 0x34, 0xb4, 0x07, 0x1a, 0xf9, 0x0c, 0x72, 0x69, 0xa9, 0x12, + 0x3d, 0x41, 0x9e, 0xad, 0xde, 0xca, 0x5b, 0xe3, 0x29, 0x32, 0xe3, 0x0c, 0xf9, 0x40, 0x23, 0x1f, + 0xc1, 0xc2, 0xd3, 0xb0, 0xef, 0x30, 0x3e, 0x20, 0xb3, 0xde, 0x59, 0x59, 0x6d, 0xc6, 0x17, 0xff, + 0x66, 0x72, 0xa5, 0x6f, 0xee, 0x44, 0x17, 0xff, 0x86, 0x46, 0xba, 0xb0, 0xa8, 0xba, 0x0d, 0xc9, + 0xfa, 0xec, 0xa1, 0x15, 0xc7, 0x73, 0xe9, 0x54, 0x6b, 0x7f, 0xab, 0x41, 0x21, 0x16, 0xa9, 0x4b, + 0x5d, 0x6a, 0x63, 0x40, 0xbe, 0x84, 0x4a, 0x2c, 0x3e, 0x06, 0xe7, 0xd3, 0x42, 0xee, 0x25, 0x8c, + 0x2f, 0x4e, 0xd9, 0xac, 0x0f, 0x20, 0x6d, 0xc8, 0x7d, 0x82, 0x42, 0x35, 0x74, 0x9a, 0x89, 0xa9, + 0x96, 0xaf, 0x14, 0xa7, 0xcd, 0x9b, 0xe5, 0x1f, 0x4f, 0xab, 0xda, 0xcf, 0xa7, 0x55, 0xed, 0xf7, + 0xd3, 0xaa, 0xf6, 0xcd, 0x1f, 0xd5, 0x37, 0xfa, 0x59, 0xc9, 0xfa, 0xde, 0xdf, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x92, 0x70, 0x31, 0xe1, 0x60, 0x11, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 6b1b6074e..e3f51d854 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -120,26 +120,20 @@ service Broker { // message StatusRequest is used to request the status of this Broker message StatusRequest {} -// message Status is the response to the StatusRequest message Status { - // Uplink - api.Rates uplink = 1; - api.Rates uplink_unique = 2; + api.SystemStats system = 1; + api.ComponentStats component = 2; - // Downlink - api.Rates downlink = 11; - - // Activations - api.Rates activations = 21; - api.Rates activations_unique = 22; - api.Rates activations_accepted = 23; - - // Deduplication histogram percentiles - api.Percentiles deduplication = 31; + api.Rates uplink = 11; + api.Rates uplink_unique = 12; + api.Rates downlink = 13; + api.Rates activations = 14; + api.Rates activations_unique = 15; + api.Percentiles deduplication = 16; // Connections - uint32 connected_routers = 41; - uint32 connected_handlers = 42; + uint32 connected_routers = 21; + uint32 connected_handlers = 22; } message ApplicationHandlerRegistration { diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 64651f224..7d65d9766 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -28,6 +28,7 @@ import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import api "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" import lorawan1 "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" @@ -87,6 +88,11 @@ func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorHandle // message Status is the response to the StatusRequest type Status struct { + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + Downlink *api.Rates `protobuf:"bytes,12,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,13,opt,name=activations" json:"activations,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -94,6 +100,41 @@ func (m *Status) String() string { return proto.CompactTextString(m) func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{2} } +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + +func (m *Status) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *Status) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *Status) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + type ApplicationIdentifier struct { AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` } @@ -921,6 +962,56 @@ func (m *Status) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l + if m.System != nil { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(m.System.Size())) + n3, err := m.System.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Component != nil { + data[i] = 0x12 + i++ + i = encodeVarintHandler(data, i, uint64(m.Component.Size())) + n4, err := m.Component.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Uplink != nil { + data[i] = 0x5a + i++ + i = encodeVarintHandler(data, i, uint64(m.Uplink.Size())) + n5, err := m.Uplink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Downlink != nil { + data[i] = 0x62 + i++ + i = encodeVarintHandler(data, i, uint64(m.Downlink.Size())) + n6, err := m.Downlink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.Activations != nil { + data[i] = 0x6a + i++ + i = encodeVarintHandler(data, i, uint64(m.Activations.Size())) + n7, err := m.Activations.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } return i, nil } @@ -1054,11 +1145,11 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevId) } if m.Device != nil { - nn3, err := m.Device.MarshalTo(data[i:]) + nn8, err := m.Device.MarshalTo(data[i:]) if err != nil { return 0, err } - i += nn3 + i += nn8 } return i, nil } @@ -1069,11 +1160,11 @@ func (m *Device_LorawanDevice) MarshalTo(data []byte) (int, error) { data[i] = 0x1a i++ i = encodeVarintHandler(data, i, uint64(m.LorawanDevice.Size())) - n4, err := m.LorawanDevice.MarshalTo(data[i:]) + n9, err := m.LorawanDevice.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n9 } return i, nil } @@ -1138,11 +1229,11 @@ func (m *DryDownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1a i++ i = encodeVarintHandler(data, i, uint64(m.App.Size())) - n5, err := m.App.MarshalTo(data[i:]) + n10, err := m.App.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n10 } return i, nil } @@ -1172,11 +1263,11 @@ func (m *DryUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintHandler(data, i, uint64(m.App.Size())) - n6, err := m.App.MarshalTo(data[i:]) + n11, err := m.App.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n11 } return i, nil } @@ -1303,6 +1394,26 @@ func (m *StatusRequest) Size() (n int) { func (m *Status) Size() (n int) { var l int _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovHandler(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 1 + l + sovHandler(uint64(l)) + } return n } @@ -1721,6 +1832,171 @@ func (m *Status) Unmarshal(data []byte) error { return fmt.Errorf("proto: Status: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.System == nil { + m.System = &api.SystemStats{} + } + if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -2923,57 +3199,63 @@ func init() { } var fileDescriptorHandler = []byte{ - // 827 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0x5d, 0x6f, 0xdb, 0x54, - 0x18, 0x9e, 0x57, 0x9a, 0x36, 0x6f, 0xb6, 0xa4, 0x3b, 0xdd, 0x82, 0xc9, 0xa6, 0x28, 0x18, 0x09, - 0x15, 0x21, 0x1c, 0x29, 0x4c, 0x2a, 0x08, 0x09, 0x96, 0x11, 0xb2, 0x55, 0x10, 0x26, 0xb9, 0xe5, - 0x02, 0x2e, 0x88, 0x4e, 0x7c, 0xde, 0x3a, 0x47, 0x75, 0x7d, 0x8c, 0x7d, 0x92, 0x2a, 0xfc, 0x0e, - 0x2e, 0xf8, 0x43, 0x93, 0xb8, 0xe4, 0x9e, 0x1b, 0x54, 0xfe, 0x08, 0xb2, 0xcf, 0xb1, 0xe3, 0xe6, - 0xa3, 0x69, 0x10, 0x57, 0xc9, 0xfb, 0xf5, 0x9c, 0xf7, 0xf3, 0x31, 0x7c, 0xee, 0x71, 0x39, 0x9e, - 0x8c, 0x6c, 0x57, 0x5c, 0xb6, 0xcf, 0xc6, 0x78, 0x36, 0xe6, 0x81, 0x17, 0x7f, 0x8f, 0xf2, 0x4a, - 0x44, 0x17, 0x6d, 0x29, 0x83, 0x36, 0x0d, 0x79, 0x7b, 0x4c, 0x03, 0xe6, 0x63, 0x94, 0xfd, 0xda, - 0x61, 0x24, 0xa4, 0x20, 0x7b, 0x5a, 0x6c, 0x3c, 0xf5, 0x84, 0xf0, 0x7c, 0x6c, 0xa7, 0xea, 0xd1, - 0xe4, 0xbc, 0x8d, 0x97, 0xa1, 0x9c, 0x29, 0xaf, 0xc6, 0xf1, 0x5d, 0x1e, 0x18, 0x45, 0xe2, 0x02, - 0x23, 0xfd, 0xa3, 0x03, 0xbf, 0xb8, 0x4b, 0x60, 0xea, 0xea, 0x0a, 0x3f, 0xff, 0xa3, 0x83, 0xbb, - 0x5b, 0x05, 0xfb, 0x22, 0xa2, 0x57, 0x34, 0x68, 0x33, 0x9c, 0x72, 0x17, 0x15, 0x84, 0xf5, 0x97, - 0x01, 0x66, 0x2f, 0x55, 0x74, 0x5d, 0xc9, 0xa7, 0x54, 0x72, 0x11, 0x38, 0x18, 0x87, 0x22, 0x88, - 0x91, 0x98, 0xb0, 0x17, 0xd2, 0x99, 0x2f, 0x28, 0x33, 0x8d, 0x96, 0x71, 0xf4, 0xc0, 0xc9, 0x44, - 0xf2, 0x04, 0x4a, 0x34, 0x0c, 0x87, 0x9c, 0x99, 0xf7, 0x5b, 0xc6, 0x51, 0xd9, 0xd9, 0xa5, 0x61, - 0x78, 0xc2, 0xc8, 0x57, 0x50, 0x63, 0xe2, 0x2a, 0xf0, 0x79, 0x70, 0x31, 0x14, 0x61, 0x82, 0x65, - 0x56, 0x5a, 0xc6, 0x51, 0xa5, 0x53, 0xb7, 0x75, 0xd5, 0x3d, 0x6d, 0x7e, 0x93, 0x5a, 0x9d, 0x2a, - 0xbb, 0x21, 0x93, 0x01, 0x1c, 0xd2, 0x3c, 0x8f, 0xe1, 0x25, 0x4a, 0xca, 0xa8, 0xa4, 0xe6, 0xbb, - 0x29, 0xc8, 0x33, 0x3b, 0xaf, 0x7f, 0x9e, 0xec, 0x40, 0xfb, 0x38, 0x84, 0x2e, 0xe9, 0xac, 0x1a, - 0x3c, 0x3c, 0x95, 0x54, 0x4e, 0x62, 0x07, 0x7f, 0x99, 0x60, 0x2c, 0xad, 0x7d, 0x28, 0x29, 0x85, - 0x65, 0xc3, 0x93, 0x6e, 0x18, 0xfa, 0xdc, 0x4d, 0x23, 0x4e, 0x18, 0x06, 0x92, 0x9f, 0x73, 0x8c, - 0x0a, 0xa5, 0x19, 0x85, 0xd2, 0xac, 0xdf, 0x0c, 0xa8, 0x14, 0x02, 0xd6, 0xb8, 0x25, 0x2d, 0x63, - 0xe8, 0x0a, 0x86, 0x91, 0xee, 0x4c, 0x26, 0x92, 0x67, 0x50, 0x76, 0x45, 0x30, 0xc5, 0x48, 0x62, - 0x64, 0xee, 0xa4, 0xb6, 0xb9, 0x22, 0xb1, 0x4e, 0xa9, 0xcf, 0x19, 0x95, 0x22, 0x32, 0xdf, 0x51, - 0xd6, 0x5c, 0x91, 0xa0, 0x62, 0xa0, 0x50, 0x77, 0x15, 0xaa, 0x16, 0xad, 0x17, 0x70, 0xa0, 0xc6, - 0xb7, 0xb1, 0x82, 0x44, 0xcd, 0x70, 0x5a, 0x98, 0x19, 0xc3, 0xe9, 0x09, 0xb3, 0x7e, 0x85, 0x92, - 0x42, 0xd8, 0x2e, 0x8e, 0x7c, 0x06, 0x55, 0xbd, 0x51, 0x43, 0xb5, 0x51, 0x69, 0x51, 0x95, 0x4e, - 0xcd, 0xd6, 0x6a, 0x5b, 0xc1, 0xbe, 0xbe, 0xe7, 0x3c, 0xd4, 0x1a, 0xa5, 0x78, 0xb9, 0x9f, 0x02, - 0x72, 0x17, 0xad, 0x63, 0x00, 0xa5, 0xfb, 0x8e, 0xc7, 0x92, 0x7c, 0x94, 0xf4, 0x2e, 0x91, 0x62, - 0xd3, 0x68, 0xed, 0xa4, 0x50, 0xd9, 0x2d, 0x2a, 0x2f, 0x27, 0xb3, 0x5b, 0x01, 0x90, 0x5e, 0x34, - 0xcb, 0x96, 0x69, 0x80, 0x71, 0x4c, 0xbd, 0xdb, 0xf6, 0xb5, 0x0e, 0xa5, 0x73, 0x8e, 0x3e, 0x8b, - 0x75, 0x0d, 0x5a, 0x22, 0x1f, 0xc2, 0x0e, 0x0d, 0x43, 0x9d, 0xf9, 0xe3, 0xfc, 0xb9, 0xc2, 0xa0, - 0x9d, 0xc4, 0xc1, 0x3a, 0x83, 0x83, 0x5e, 0x34, 0xfb, 0x21, 0xbc, 0xdb, 0x6b, 0x1a, 0xf5, 0xfe, - 0x26, 0xd4, 0x1f, 0xa1, 0x96, 0xa3, 0x3a, 0x18, 0x4f, 0x7c, 0xf9, 0x1f, 0x4a, 0x78, 0x0c, 0xbb, - 0xe9, 0xa2, 0xa4, 0x45, 0xec, 0x3b, 0x4a, 0xb0, 0x3e, 0x81, 0x47, 0x85, 0x06, 0x6d, 0x02, 0xef, - 0xbc, 0x35, 0x60, 0xef, 0xb5, 0x4a, 0x93, 0xfc, 0x0c, 0x87, 0xf3, 0xf3, 0xfa, 0x7a, 0x4c, 0x7d, - 0x1f, 0x03, 0x0f, 0x89, 0x95, 0x9d, 0xf0, 0x0a, 0xa3, 0x3e, 0xaf, 0xc6, 0x07, 0xb7, 0xfa, 0x68, - 0x56, 0xf9, 0x09, 0xf6, 0xb5, 0x19, 0xc9, 0xc7, 0x39, 0x2f, 0x20, 0x9b, 0xa8, 0xee, 0x20, 0x5b, - 0xe6, 0x23, 0x85, 0xfe, 0xfe, 0xc2, 0x3a, 0x2c, 0x33, 0x56, 0xe7, 0xed, 0x2e, 0x90, 0x42, 0x9b, - 0x07, 0x34, 0xa0, 0x1e, 0x46, 0x09, 0xad, 0x38, 0xe8, 0xf1, 0x58, 0x62, 0x54, 0xbc, 0xe1, 0xe6, - 0xaa, 0xd1, 0xcc, 0x0f, 0xa9, 0x51, 0xb7, 0x15, 0xe7, 0xdb, 0x19, 0xe7, 0xdb, 0xdf, 0x24, 0x9c, - 0x4f, 0xfa, 0x50, 0x7d, 0x85, 0x72, 0x1b, 0xa4, 0x95, 0x4b, 0x40, 0xbe, 0x84, 0xea, 0xe9, 0x4d, - 0x9c, 0x95, 0x7e, 0x6b, 0xf3, 0xf8, 0x16, 0x1e, 0xf5, 0xd0, 0x47, 0x89, 0xff, 0x47, 0x51, 0xc7, - 0x50, 0x7e, 0x85, 0x52, 0x53, 0xc1, 0x7b, 0x0b, 0xad, 0x2e, 0xc4, 0x2f, 0x1e, 0x25, 0x79, 0x0e, - 0xe5, 0xd3, 0x3c, 0x70, 0xd1, 0xba, 0xf6, 0xb9, 0x2e, 0x3c, 0x50, 0xb9, 0x6f, 0x7e, 0x71, 0x1d, - 0xc4, 0x1b, 0x30, 0xf3, 0x8c, 0xe3, 0xbe, 0xd8, 0x6a, 0xb4, 0x87, 0x0b, 0xcf, 0xa5, 0x04, 0xd4, - 0x87, 0x4a, 0xe1, 0x68, 0xc8, 0xd3, 0xb9, 0xcf, 0x12, 0xd7, 0x34, 0x1a, 0xab, 0x8c, 0xfa, 0xce, - 0x5e, 0x40, 0x39, 0xbf, 0xeb, 0x62, 0x61, 0x0b, 0x0c, 0xd2, 0x30, 0x97, 0x4d, 0x0a, 0xa1, 0xd3, - 0x87, 0xaa, 0x3e, 0xc7, 0x6c, 0x85, 0x9f, 0xa7, 0xe3, 0x51, 0x1f, 0x2f, 0x52, 0xcf, 0x03, 0x6f, - 0x7c, 0xde, 0x0a, 0xb3, 0x51, 0xfa, 0x97, 0x07, 0x7f, 0x5c, 0x37, 0x8d, 0x3f, 0xaf, 0x9b, 0xc6, - 0xdf, 0xd7, 0x4d, 0xe3, 0xf7, 0x7f, 0x9a, 0xf7, 0x46, 0xa5, 0xb4, 0x89, 0x9f, 0xfe, 0x1b, 0x00, - 0x00, 0xff, 0xff, 0xaf, 0x9b, 0xbd, 0xd5, 0x13, 0x09, 0x00, 0x00, + // 921 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0x51, 0x6f, 0x1b, 0x45, + 0x10, 0xee, 0x35, 0xc4, 0x89, 0xc7, 0x89, 0x93, 0x6e, 0xda, 0x70, 0xb8, 0x95, 0x15, 0x0e, 0xa9, + 0x0a, 0x82, 0x9e, 0x85, 0xa9, 0x14, 0x10, 0x12, 0x34, 0xad, 0x49, 0x1b, 0x41, 0xa8, 0x74, 0x09, + 0x0f, 0xf0, 0x40, 0xb4, 0xf1, 0x4e, 0xec, 0x55, 0xce, 0xb7, 0xcb, 0xed, 0xda, 0x91, 0xf9, 0x1d, + 0x3c, 0xf0, 0x87, 0x2a, 0xf1, 0xc8, 0x3b, 0x0f, 0xa0, 0xf0, 0x47, 0xd0, 0xed, 0xee, 0x9d, 0x2f, + 0xb6, 0xd3, 0x24, 0x88, 0xa7, 0xbb, 0x99, 0xf9, 0xe6, 0xdb, 0xfd, 0x76, 0x76, 0x66, 0xe1, 0xf3, + 0x1e, 0xd7, 0xfd, 0xe1, 0x49, 0xd8, 0x15, 0x83, 0xd6, 0x51, 0x1f, 0x8f, 0xfa, 0x3c, 0xe9, 0xa9, + 0xef, 0x50, 0x9f, 0x8b, 0xf4, 0xac, 0xa5, 0x75, 0xd2, 0xa2, 0x92, 0xb7, 0xfa, 0x34, 0x61, 0x31, + 0xa6, 0xf9, 0x37, 0x94, 0xa9, 0xd0, 0x82, 0x2c, 0x39, 0xb3, 0xf1, 0xb0, 0x27, 0x44, 0x2f, 0xc6, + 0x96, 0x71, 0x9f, 0x0c, 0x4f, 0x5b, 0x38, 0x90, 0x7a, 0x6c, 0x51, 0x8d, 0x27, 0x37, 0x59, 0x80, + 0x4a, 0xee, 0xe0, 0x3b, 0x37, 0x81, 0x9f, 0xa4, 0xe2, 0x0c, 0x53, 0xf7, 0x71, 0x89, 0x5f, 0xdc, + 0x24, 0xd1, 0x40, 0xbb, 0x22, 0x2e, 0x7e, 0x5c, 0xf2, 0xee, 0xad, 0x92, 0x63, 0x91, 0xd2, 0x73, + 0x9a, 0xb4, 0x18, 0x8e, 0x78, 0x17, 0x2d, 0x45, 0xf0, 0xa7, 0x07, 0x7e, 0xc7, 0x38, 0x76, 0xbb, + 0x9a, 0x8f, 0xa8, 0xe6, 0x22, 0x89, 0x50, 0x49, 0x91, 0x28, 0x24, 0x3e, 0x2c, 0x49, 0x3a, 0x8e, + 0x05, 0x65, 0xbe, 0xb7, 0xe5, 0x6d, 0xaf, 0x44, 0xb9, 0x49, 0x1e, 0x40, 0x85, 0x4a, 0x79, 0xcc, + 0x99, 0x7f, 0x77, 0xcb, 0xdb, 0xae, 0x46, 0x8b, 0x54, 0xca, 0x7d, 0x46, 0xbe, 0x82, 0x35, 0x26, + 0xce, 0x93, 0x98, 0x27, 0x67, 0xc7, 0x42, 0x66, 0x5c, 0x7e, 0x6d, 0xcb, 0xdb, 0xae, 0xb5, 0x37, + 0x43, 0xa7, 0xba, 0xe3, 0xc2, 0xaf, 0x4d, 0x34, 0xaa, 0xb3, 0x4b, 0x36, 0x39, 0x80, 0x0d, 0x5a, + 0xec, 0xe3, 0x78, 0x80, 0x9a, 0x32, 0xaa, 0xa9, 0xff, 0xae, 0x21, 0x79, 0x14, 0x16, 0xfa, 0x27, + 0x9b, 0x3d, 0x70, 0x98, 0x88, 0xd0, 0x19, 0x5f, 0xb0, 0x06, 0xab, 0x87, 0x9a, 0xea, 0xa1, 0x8a, + 0xf0, 0xe7, 0x21, 0x2a, 0x1d, 0xfc, 0xe5, 0x41, 0xc5, 0x7a, 0xc8, 0x36, 0x54, 0xd4, 0x58, 0x69, + 0x1c, 0x18, 0x6d, 0xb5, 0xf6, 0x7a, 0x98, 0x95, 0xf3, 0xd0, 0xb8, 0x32, 0x88, 0x8a, 0x5c, 0x9c, + 0x7c, 0x02, 0xd5, 0xae, 0x18, 0x48, 0x91, 0x60, 0xa2, 0x8d, 0xde, 0x5a, 0x7b, 0xc3, 0x80, 0x5f, + 0xe4, 0x5e, 0x8b, 0x9f, 0xa0, 0x48, 0x00, 0x95, 0xa1, 0xcc, 0x74, 0x39, 0xfd, 0x60, 0xf0, 0x11, + 0xd5, 0xa8, 0x22, 0x17, 0x21, 0x8f, 0x61, 0x39, 0x57, 0xef, 0xaf, 0xcc, 0xa0, 0x8a, 0x18, 0xf9, + 0x18, 0x6a, 0x13, 0x69, 0xca, 0x5f, 0x9d, 0x81, 0x96, 0xc3, 0x41, 0x08, 0x0f, 0x76, 0xa5, 0x8c, + 0x79, 0xd7, 0xd8, 0xfb, 0x0c, 0x13, 0xcd, 0x4f, 0x39, 0xa6, 0xa5, 0x92, 0x79, 0xa5, 0x92, 0x05, + 0xbf, 0x7a, 0x50, 0x2b, 0x25, 0x5c, 0x01, 0xcb, 0xae, 0x02, 0xc3, 0xae, 0x60, 0x98, 0xba, 0x8a, + 0xe7, 0x26, 0x79, 0x94, 0x9d, 0x4e, 0x32, 0xc2, 0x54, 0x63, 0xea, 0x2f, 0x98, 0xd8, 0xc4, 0x91, + 0x45, 0x47, 0x34, 0xe6, 0x8c, 0x6a, 0x91, 0xfa, 0xef, 0xd8, 0x68, 0xe1, 0xc8, 0x58, 0x31, 0xb1, + 0xac, 0x8b, 0x96, 0xd5, 0x99, 0xc1, 0x33, 0x58, 0xb7, 0xd7, 0xf2, 0x5a, 0x05, 0x99, 0x9b, 0xe1, + 0xa8, 0x74, 0x17, 0x19, 0x8e, 0xf6, 0x59, 0xf0, 0x0b, 0x54, 0x2c, 0xc3, 0xed, 0xf2, 0xc8, 0x67, + 0x50, 0x77, 0x9d, 0x72, 0x6c, 0x3b, 0xc5, 0x88, 0xaa, 0xb5, 0xd7, 0x42, 0xe7, 0x0e, 0x2d, 0xed, + 0xab, 0x3b, 0xd1, 0xaa, 0xf3, 0x58, 0xc7, 0xf3, 0x65, 0x43, 0xc8, 0xbb, 0x18, 0xec, 0x00, 0x58, + 0xdf, 0xb7, 0x5c, 0x69, 0xf2, 0x61, 0x76, 0x76, 0x99, 0xa5, 0x7c, 0x6f, 0x6b, 0xc1, 0x50, 0xe5, + 0x23, 0xc9, 0xa2, 0xa2, 0x3c, 0x1e, 0x24, 0x40, 0x3a, 0xe9, 0x38, 0x6f, 0x92, 0x03, 0x54, 0x8a, + 0xf6, 0xde, 0xd6, 0x87, 0x9b, 0x50, 0x39, 0xe5, 0x18, 0x33, 0xe5, 0x34, 0x38, 0x8b, 0x3c, 0x86, + 0x05, 0x2a, 0xa5, 0xdb, 0xf9, 0xfd, 0x62, 0xb9, 0x52, 0xa1, 0xa3, 0x0c, 0x10, 0x1c, 0xc1, 0x7a, + 0x27, 0x1d, 0x7f, 0x2f, 0x6f, 0xb6, 0x9a, 0x63, 0xbd, 0x7b, 0x1d, 0xeb, 0x0f, 0xb0, 0x56, 0xb0, + 0x46, 0xa8, 0x86, 0xb1, 0xfe, 0x0f, 0x12, 0xee, 0xc3, 0xa2, 0xb9, 0x28, 0x46, 0xc4, 0x72, 0x64, + 0x8d, 0xe0, 0x09, 0xdc, 0x2b, 0x1d, 0xd0, 0x75, 0xe4, 0xed, 0x37, 0x1e, 0x2c, 0xbd, 0xb2, 0xdb, + 0x24, 0x3f, 0xc1, 0xc6, 0x64, 0x6c, 0xbc, 0xe8, 0xd3, 0x38, 0xc6, 0xa4, 0x87, 0x24, 0xc8, 0x47, + 0xd3, 0x9c, 0xa0, 0x1b, 0x1b, 0x8d, 0x0f, 0xde, 0x8a, 0x71, 0xd3, 0xf2, 0x47, 0x58, 0x76, 0x61, + 0x24, 0x1f, 0x15, 0xf3, 0x0e, 0xd9, 0xd0, 0x9e, 0x0e, 0xb2, 0xd9, 0x39, 0x6b, 0xd9, 0xdf, 0x9f, + 0xba, 0x0e, 0xb3, 0x93, 0xb8, 0xfd, 0x66, 0x11, 0x48, 0xe9, 0x98, 0x0f, 0x68, 0x42, 0x7b, 0x98, + 0x66, 0xe3, 0x32, 0xc2, 0x1e, 0x57, 0x1a, 0xd3, 0x72, 0x0f, 0x37, 0xe7, 0x95, 0x66, 0xd2, 0x48, + 0x8d, 0xcd, 0xd0, 0x3e, 0x7d, 0x61, 0xfe, 0xf4, 0x85, 0x5f, 0x67, 0x4f, 0x1f, 0xd9, 0x83, 0xfa, + 0x4b, 0xd4, 0xb7, 0x61, 0x9a, 0x7b, 0x09, 0xc8, 0x97, 0x50, 0x3f, 0xbc, 0xcc, 0x33, 0x17, 0x77, + 0xe5, 0x3e, 0xbe, 0x81, 0x7b, 0x1d, 0x8c, 0x51, 0xe3, 0xff, 0x21, 0x6a, 0x07, 0xaa, 0x2f, 0x51, + 0xbb, 0x51, 0xf0, 0xde, 0xd4, 0x51, 0x97, 0xf2, 0xa7, 0x9b, 0x92, 0x3c, 0x85, 0xea, 0x61, 0x91, + 0x38, 0x1d, 0xbd, 0x72, 0xb9, 0x5d, 0x58, 0xb1, 0x7b, 0xbf, 0x7e, 0xc5, 0xab, 0x28, 0x5e, 0x83, + 0x5f, 0xec, 0x58, 0xed, 0x89, 0x5b, 0x95, 0x76, 0x63, 0x6a, 0x39, 0x33, 0x80, 0xf6, 0xa0, 0x56, + 0x6a, 0x1a, 0xf2, 0x70, 0x82, 0x99, 0x99, 0x35, 0x8d, 0xc6, 0xbc, 0xa0, 0xeb, 0xb3, 0x67, 0x50, + 0x2d, 0xfa, 0xba, 0x2c, 0x6c, 0x6a, 0x82, 0x34, 0xfc, 0xd9, 0x90, 0x65, 0x68, 0xef, 0x41, 0xdd, + 0xb5, 0x63, 0x7e, 0x85, 0x9f, 0x9a, 0xf2, 0xb8, 0x37, 0x79, 0xb3, 0x48, 0xbc, 0xf4, 0x6c, 0x97, + 0x6a, 0x63, 0xfd, 0xcf, 0xd7, 0x7f, 0xbf, 0x68, 0x7a, 0x7f, 0x5c, 0x34, 0xbd, 0xbf, 0x2f, 0x9a, + 0xde, 0x6f, 0xff, 0x34, 0xef, 0x9c, 0x54, 0xcc, 0x21, 0x7e, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, + 0xff, 0xf6, 0xa1, 0x00, 0x96, 0x1a, 0x0a, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 97e0bcbde..4b548482f 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -4,6 +4,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; +import "github.com/TheThingsNetwork/ttn/api/api.proto"; import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto"; @@ -27,7 +28,14 @@ service Handler { message StatusRequest {} // message Status is the response to the StatusRequest -message Status {} +message Status { + api.SystemStats system = 1; + api.ComponentStats component = 2; + + api.Rates uplink = 11; + api.Rates downlink = 12; + api.Rates activations = 13; +} message ApplicationIdentifier { string app_id = 1; diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 74c4b3ad5..2699e88b2 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -82,8 +82,12 @@ func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorNetwor // message Status is the response to the StatusRequest type Status struct { - // GetDevices histogram percentiles - DevicesPerAddress *api.Percentiles `protobuf:"bytes,1,opt,name=devices_per_address,json=devicesPerAddress" json:"devices_per_address,omitempty"` + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` + Downlink *api.Rates `protobuf:"bytes,12,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,13,opt,name=activations" json:"activations,omitempty"` + DevicesPerAddress *api.Percentiles `protobuf:"bytes,21,opt,name=devices_per_address,json=devicesPerAddress" json:"devices_per_address,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -91,6 +95,41 @@ func (m *Status) String() string { return proto.CompactTextString(m) func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorNetworkserver, []int{3} } +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + +func (m *Status) GetUplink() *api.Rates { + if m != nil { + return m.Uplink + } + return nil +} + +func (m *Status) GetDownlink() *api.Rates { + if m != nil { + return m.Downlink + } + return nil +} + +func (m *Status) GetActivations() *api.Rates { + if m != nil { + return m.Activations + } + return nil +} + func (m *Status) GetDevicesPerAddress() *api.Percentiles { if m != nil { return m.DevicesPerAddress @@ -479,16 +518,68 @@ func (m *Status) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.DevicesPerAddress != nil { + if m.System != nil { data[i] = 0xa i++ - i = encodeVarintNetworkserver(data, i, uint64(m.DevicesPerAddress.Size())) - n2, err := m.DevicesPerAddress.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(data, i, uint64(m.System.Size())) + n2, err := m.System.MarshalTo(data[i:]) if err != nil { return 0, err } i += n2 } + if m.Component != nil { + data[i] = 0x12 + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.Component.Size())) + n3, err := m.Component.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } + if m.Uplink != nil { + data[i] = 0x5a + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.Uplink.Size())) + n4, err := m.Uplink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } + if m.Downlink != nil { + data[i] = 0x62 + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.Downlink.Size())) + n5, err := m.Downlink.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 + } + if m.Activations != nil { + data[i] = 0x6a + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.Activations.Size())) + n6, err := m.Activations.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n6 + } + if m.DevicesPerAddress != nil { + data[i] = 0xaa + i++ + data[i] = 0x1 + i++ + i = encodeVarintNetworkserver(data, i, uint64(m.DevicesPerAddress.Size())) + n7, err := m.DevicesPerAddress.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } return i, nil } @@ -553,9 +644,29 @@ func (m *StatusRequest) Size() (n int) { func (m *Status) Size() (n int) { var l int _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Uplink != nil { + l = m.Uplink.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Downlink != nil { + l = m.Downlink.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } + if m.Activations != nil { + l = m.Activations.Size() + n += 1 + l + sovNetworkserver(uint64(l)) + } if m.DevicesPerAddress != nil { l = m.DevicesPerAddress.Size() - n += 1 + l + sovNetworkserver(uint64(l)) + n += 2 + l + sovNetworkserver(uint64(l)) } return n } @@ -835,6 +946,171 @@ func (m *Status) Unmarshal(data []byte) error { } switch fieldNum { case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.System == nil { + m.System = &api.SystemStats{} + } + if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Uplink == nil { + m.Uplink = &api.Rates{} + } + if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Downlink == nil { + m.Downlink = &api.Rates{} + } + if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 13: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowNetworkserver + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthNetworkserver + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Activations == nil { + m.Activations = &api.Rates{} + } + if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevicesPerAddress", wireType) } @@ -998,38 +1274,43 @@ func init() { } var fileDescriptorNetworkserver = []byte{ - // 524 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x93, 0x51, 0x6f, 0xd3, 0x30, - 0x10, 0xc7, 0x09, 0x83, 0xad, 0x78, 0x94, 0x6e, 0x1e, 0x88, 0xaa, 0x82, 0xd2, 0xf5, 0xa9, 0x08, - 0x91, 0x48, 0x45, 0x02, 0x21, 0xed, 0x61, 0x1d, 0x45, 0x93, 0x40, 0x9b, 0x4a, 0x36, 0x24, 0xde, - 0x2a, 0x37, 0xbe, 0xa6, 0x56, 0x33, 0x3b, 0xd8, 0x4e, 0x0a, 0xdf, 0x84, 0xef, 0xc0, 0x17, 0xe1, - 0x91, 0x67, 0x1e, 0x10, 0x2a, 0x5f, 0x04, 0xcd, 0x76, 0x06, 0x81, 0x55, 0x2d, 0x4f, 0xf1, 0xdd, - 0xfd, 0xee, 0xfc, 0x3f, 0xdf, 0x05, 0xbd, 0x8c, 0x99, 0x9e, 0x64, 0x23, 0x3f, 0x12, 0x67, 0xc1, - 0xe9, 0x04, 0x4e, 0x27, 0x8c, 0xc7, 0xea, 0x18, 0xf4, 0x4c, 0xc8, 0x69, 0xa0, 0x35, 0x0f, 0x48, - 0xca, 0x02, 0x6e, 0x6d, 0x05, 0x32, 0x07, 0x59, 0xb6, 0xfc, 0x54, 0x0a, 0x2d, 0x70, 0xb5, 0xe4, - 0x6c, 0x3c, 0xfe, 0xa3, 0x6a, 0x2c, 0x62, 0x11, 0x18, 0x6a, 0x94, 0x8d, 0x8d, 0x65, 0x0c, 0x73, - 0xb2, 0xd9, 0x25, 0x7c, 0xa1, 0x08, 0x92, 0x32, 0x87, 0xf7, 0x56, 0xc1, 0x0d, 0x1a, 0x89, 0x24, - 0x48, 0x84, 0x24, 0x33, 0xc2, 0x03, 0x0a, 0x39, 0x8b, 0xc0, 0x95, 0x78, 0xb6, 0x4a, 0x89, 0x91, - 0x14, 0x53, 0x90, 0xee, 0xe3, 0x12, 0x9f, 0xaf, 0x92, 0x38, 0x21, 0x9c, 0x26, 0x20, 0x8b, 0xaf, - 0x4d, 0x6d, 0x7f, 0x40, 0xb7, 0xfa, 0x46, 0x83, 0x0a, 0xe1, 0x7d, 0x06, 0x4a, 0xe3, 0x37, 0xa8, - 0x42, 0x21, 0x1f, 0x12, 0x4a, 0x65, 0xdd, 0x6b, 0x79, 0x9d, 0x9b, 0x07, 0x4f, 0xbf, 0x7d, 0x7f, - 0xd0, 0x5d, 0x76, 0x45, 0x24, 0x24, 0x04, 0xfa, 0x63, 0x0a, 0xca, 0xef, 0x43, 0xde, 0xa3, 0x54, - 0x86, 0x1b, 0xd4, 0x1e, 0xf0, 0x0e, 0xba, 0x3e, 0x1e, 0x46, 0x5c, 0xd7, 0xaf, 0xb6, 0xbc, 0x4e, - 0x35, 0xbc, 0x36, 0x7e, 0xc1, 0x75, 0x7b, 0x0f, 0xd5, 0x2e, 0x6e, 0x56, 0xa9, 0xe0, 0x0a, 0xf0, - 0x43, 0xb4, 0x21, 0x41, 0x65, 0x89, 0x56, 0x75, 0xaf, 0xb5, 0xd6, 0xd9, 0xec, 0xd6, 0x7c, 0xf7, - 0x50, 0xbe, 0x45, 0xc3, 0x22, 0xde, 0xae, 0xa1, 0xea, 0x89, 0x26, 0x3a, 0x2b, 0x64, 0xb7, 0x5f, - 0xa1, 0x75, 0xeb, 0xc0, 0xfb, 0x68, 0xc7, 0x3e, 0xab, 0x1a, 0xa6, 0x20, 0x4d, 0x23, 0xa0, 0x94, - 0xe9, 0x65, 0xb3, 0xbb, 0xe5, 0x9f, 0x8f, 0x6c, 0x00, 0x32, 0x02, 0xae, 0x59, 0x02, 0x2a, 0xdc, - 0x76, 0xf0, 0x00, 0x64, 0xcf, 0xa2, 0xdd, 0xcf, 0x6b, 0xa8, 0xea, 0x7a, 0x3b, 0x31, 0xbb, 0x83, - 0x5f, 0x23, 0x74, 0x08, 0xda, 0xe9, 0xc5, 0xf7, 0xfd, 0xf2, 0xba, 0x95, 0x5f, 0xb0, 0xd1, 0x5c, - 0x14, 0x76, 0x6d, 0x9e, 0xa1, 0xed, 0x81, 0x84, 0x94, 0x48, 0xe8, 0x45, 0x9a, 0xe5, 0x44, 0x33, - 0xc1, 0xf1, 0x23, 0xdf, 0x8d, 0xb4, 0x0f, 0x34, 0x4b, 0x13, 0x16, 0x11, 0x0d, 0xd4, 0x66, 0xfe, - 0xa6, 0x8a, 0x1b, 0xfe, 0x07, 0xc6, 0x03, 0x54, 0x71, 0x4e, 0xc0, 0xbb, 0x7e, 0x31, 0xfe, 0x7f, - 0x69, 0xab, 0xae, 0xb1, 0x1c, 0xc1, 0xc7, 0x68, 0xfd, 0x6d, 0x9a, 0x30, 0x3e, 0xc5, 0xbb, 0x97, - 0x09, 0xb1, 0xb1, 0x23, 0x50, 0x8a, 0xc4, 0xe7, 0xf5, 0x96, 0x21, 0x78, 0x0f, 0x55, 0xfa, 0x62, - 0xc6, 0x4d, 0xc5, 0xbb, 0x17, 0xb8, 0xf3, 0x14, 0x75, 0x16, 0x05, 0xba, 0xef, 0xd0, 0xed, 0xd2, - 0xb0, 0x8e, 0x08, 0x27, 0x31, 0x48, 0xbc, 0x8f, 0x6e, 0x1c, 0x82, 0x76, 0x4b, 0x71, 0xef, 0xaf, - 0x99, 0x94, 0x96, 0xa7, 0x71, 0xe7, 0xd2, 0xe8, 0xc1, 0xd6, 0x97, 0x79, 0xd3, 0xfb, 0x3a, 0x6f, - 0x7a, 0x3f, 0xe6, 0x4d, 0xef, 0xd3, 0xcf, 0xe6, 0x95, 0xd1, 0xba, 0xf9, 0x6b, 0x9e, 0xfc, 0x0a, - 0x00, 0x00, 0xff, 0xff, 0x70, 0xbc, 0x3e, 0xb2, 0xa2, 0x04, 0x00, 0x00, + // 605 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x94, 0x41, 0x4f, 0x13, 0x41, + 0x14, 0xc7, 0x5d, 0xd0, 0x52, 0x5e, 0xa9, 0xc0, 0x20, 0xb1, 0x69, 0xb4, 0x42, 0x0f, 0xa6, 0x46, + 0xdd, 0x8d, 0x35, 0xd1, 0x98, 0x70, 0xa0, 0x50, 0xc3, 0xc1, 0x40, 0xea, 0x82, 0x89, 0x37, 0x32, + 0xdd, 0x7d, 0x6c, 0x37, 0x6c, 0x67, 0xd6, 0x99, 0xd9, 0x22, 0xdf, 0xc4, 0xab, 0x67, 0xbf, 0x88, + 0x47, 0xcf, 0x1e, 0x8c, 0xc1, 0x2f, 0x62, 0x3a, 0x33, 0x5b, 0x58, 0x81, 0x50, 0x4f, 0xbb, 0xef, + 0xfd, 0x7f, 0xef, 0xcd, 0xeb, 0xfe, 0xdf, 0x14, 0xde, 0x46, 0xb1, 0x1a, 0x64, 0x7d, 0x37, 0xe0, + 0x43, 0xef, 0x60, 0x80, 0x07, 0x83, 0x98, 0x45, 0x72, 0x0f, 0xd5, 0x09, 0x17, 0xc7, 0x9e, 0x52, + 0xcc, 0xa3, 0x69, 0xec, 0x31, 0x13, 0x4b, 0x14, 0x23, 0x14, 0xc5, 0xc8, 0x4d, 0x05, 0x57, 0x9c, + 0x54, 0x0b, 0xc9, 0xfa, 0xf3, 0x0b, 0x5d, 0x23, 0x1e, 0x71, 0x4f, 0x53, 0xfd, 0xec, 0x48, 0x47, + 0x3a, 0xd0, 0x6f, 0xa6, 0xba, 0x80, 0x5f, 0x3b, 0x04, 0x4d, 0x63, 0x8b, 0x77, 0xa6, 0xc1, 0x35, + 0x1a, 0xf0, 0xc4, 0x4b, 0xb8, 0xa0, 0x27, 0x94, 0x79, 0x21, 0x8e, 0xe2, 0x00, 0x6d, 0x8b, 0xd7, + 0xd3, 0xb4, 0xe8, 0x0b, 0x7e, 0x8c, 0xc2, 0x3e, 0x6c, 0xe1, 0x9b, 0x69, 0x0a, 0x07, 0x94, 0x85, + 0x09, 0x8a, 0xfc, 0x69, 0x4a, 0x9b, 0x9f, 0xe1, 0x6e, 0x57, 0xcf, 0x20, 0x7d, 0xfc, 0x94, 0xa1, + 0x54, 0xe4, 0x3d, 0x94, 0x43, 0x1c, 0x1d, 0xd2, 0x30, 0x14, 0x35, 0x67, 0xcd, 0x69, 0x2d, 0x6c, + 0xbd, 0xfa, 0xf9, 0xeb, 0x51, 0xfb, 0xa6, 0x23, 0x02, 0x2e, 0xd0, 0x53, 0xa7, 0x29, 0x4a, 0xb7, + 0x8b, 0xa3, 0x4e, 0x18, 0x0a, 0x7f, 0x2e, 0x34, 0x2f, 0x64, 0x05, 0xee, 0x1c, 0x1d, 0x06, 0x4c, + 0xd5, 0x66, 0xd6, 0x9c, 0x56, 0xd5, 0xbf, 0x7d, 0xb4, 0xcd, 0x54, 0x73, 0x03, 0x16, 0x27, 0x27, + 0xcb, 0x94, 0x33, 0x89, 0xe4, 0x09, 0xcc, 0x09, 0x94, 0x59, 0xa2, 0x64, 0xcd, 0x59, 0x9b, 0x6d, + 0x55, 0xda, 0x8b, 0xae, 0xfd, 0x50, 0xae, 0x41, 0xfd, 0x5c, 0x6f, 0x2e, 0x42, 0x75, 0x5f, 0x51, + 0x95, 0xe5, 0x63, 0x37, 0xbf, 0xce, 0x40, 0xc9, 0x64, 0x48, 0x0b, 0x4a, 0xf2, 0x54, 0x2a, 0x1c, + 0xea, 0xf9, 0x2b, 0xed, 0x25, 0x77, 0x6c, 0xd3, 0xbe, 0x4e, 0x8d, 0x11, 0xe9, 0x5b, 0x9d, 0xbc, + 0x80, 0xf9, 0x80, 0x0f, 0x53, 0xce, 0xd0, 0x0e, 0x57, 0x69, 0xaf, 0x68, 0x78, 0x3b, 0xcf, 0x1a, + 0xfe, 0x9c, 0x22, 0x4d, 0x28, 0x65, 0x69, 0x12, 0xb3, 0xe3, 0x5a, 0x45, 0xf3, 0xa0, 0x79, 0x9f, + 0x2a, 0x94, 0xbe, 0x55, 0xc8, 0x63, 0x28, 0x87, 0xfc, 0x84, 0x69, 0x6a, 0xe1, 0x12, 0x35, 0xd1, + 0xc8, 0x33, 0xa8, 0xd0, 0x40, 0xc5, 0x23, 0xaa, 0x62, 0xce, 0x64, 0xad, 0x7a, 0x09, 0xbd, 0x28, + 0x93, 0x4d, 0x58, 0x31, 0xeb, 0x22, 0x0f, 0x53, 0x14, 0xda, 0x20, 0x94, 0xb2, 0xb6, 0x7a, 0xe1, + 0x37, 0xf6, 0x50, 0x04, 0xc8, 0x54, 0x9c, 0xa0, 0xf4, 0x97, 0x2d, 0xdc, 0x43, 0xd1, 0x31, 0x68, + 0xfb, 0xdb, 0x2c, 0x54, 0xad, 0x67, 0xfb, 0xfa, 0x4e, 0x90, 0x77, 0x00, 0x3b, 0xa8, 0xac, 0x0f, + 0xe4, 0xa1, 0x5b, 0xbc, 0x46, 0xc5, 0xcd, 0xa8, 0x37, 0xae, 0x93, 0xad, 0x7d, 0x43, 0x58, 0xee, + 0x09, 0x4c, 0xa9, 0xc0, 0xce, 0x64, 0x6c, 0xf2, 0xd4, 0xb5, 0xab, 0xda, 0xc5, 0x70, 0xfc, 0x79, + 0x02, 0xaa, 0x30, 0x34, 0x95, 0xe7, 0x54, 0x7e, 0xc2, 0xff, 0xc0, 0xa4, 0x07, 0x65, 0x9b, 0x44, + 0xb2, 0xee, 0xe6, 0x6b, 0x7d, 0x99, 0x36, 0xd3, 0xd5, 0x6f, 0x46, 0xc8, 0x1e, 0x94, 0x3e, 0x18, + 0x07, 0xd7, 0xaf, 0x1a, 0xc4, 0x68, 0xbb, 0x28, 0x25, 0x8d, 0xc6, 0xfd, 0x6e, 0x42, 0xc8, 0x06, + 0x94, 0xbb, 0xb9, 0xd7, 0xf7, 0x27, 0xb8, 0xcd, 0xe4, 0x7d, 0xae, 0x13, 0xda, 0x1f, 0xe1, 0x5e, + 0xc1, 0xac, 0x5d, 0xca, 0x68, 0x84, 0x82, 0x6c, 0xc2, 0xfc, 0x0e, 0x2a, 0xbb, 0xeb, 0x0f, 0xfe, + 0xf1, 0xa4, 0x70, 0x29, 0xea, 0xab, 0x57, 0xaa, 0x5b, 0x4b, 0xdf, 0xcf, 0x1a, 0xce, 0x8f, 0xb3, + 0x86, 0xf3, 0xfb, 0xac, 0xe1, 0x7c, 0xf9, 0xd3, 0xb8, 0xd5, 0x2f, 0xe9, 0x7f, 0x83, 0x97, 0x7f, + 0x03, 0x00, 0x00, 0xff, 0xff, 0x66, 0xb2, 0x91, 0x81, 0x7a, 0x05, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 6d32c8f7e..077c9b29b 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -43,8 +43,14 @@ message StatusRequest {} // message Status is the response to the StatusRequest message Status { - // GetDevices histogram percentiles - api.Percentiles devices_per_address = 1; + api.SystemStats system = 1; + api.ComponentStats component = 2; + + api.Rates uplink = 11; + api.Rates downlink = 12; + api.Rates activations = 13; + + api.Percentiles devices_per_address = 21; } // The NetworkServerManager service provides configuration and monitoring diff --git a/api/router/router.pb.go b/api/router/router.pb.go index a9d70b4ba..846712963 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -182,19 +182,15 @@ func (*StatusRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter // message Status is the response to the StatusRequest type Status struct { - // Gateways - GatewayStatus *api.Rates `protobuf:"bytes,1,opt,name=gateway_status,json=gatewayStatus" json:"gateway_status,omitempty"` - ActiveGateways uint32 `protobuf:"varint,4,opt,name=active_gateways,json=activeGateways,proto3" json:"active_gateways,omitempty"` - // Uplink - Uplink *api.Rates `protobuf:"bytes,11,opt,name=uplink" json:"uplink,omitempty"` - // Downlink - Downlink *api.Rates `protobuf:"bytes,21,opt,name=downlink" json:"downlink,omitempty"` - // Activations - Activations *api.Rates `protobuf:"bytes,31,opt,name=activations" json:"activations,omitempty"` - ActivationsAccepted *api.Rates `protobuf:"bytes,32,opt,name=activations_accepted,json=activationsAccepted" json:"activations_accepted,omitempty"` + System *api.SystemStats `protobuf:"bytes,1,opt,name=system" json:"system,omitempty"` + Component *api.ComponentStats `protobuf:"bytes,2,opt,name=component" json:"component,omitempty"` + GatewayStatus *api.Rates `protobuf:"bytes,11,opt,name=gateway_status,json=gatewayStatus" json:"gateway_status,omitempty"` + Uplink *api.Rates `protobuf:"bytes,12,opt,name=uplink" json:"uplink,omitempty"` + Downlink *api.Rates `protobuf:"bytes,13,opt,name=downlink" json:"downlink,omitempty"` + Activations *api.Rates `protobuf:"bytes,14,opt,name=activations" json:"activations,omitempty"` // Connections - ConnectedGateways uint32 `protobuf:"varint,41,opt,name=connected_gateways,json=connectedGateways,proto3" json:"connected_gateways,omitempty"` - ConnectedBrokers uint32 `protobuf:"varint,42,opt,name=connected_brokers,json=connectedBrokers,proto3" json:"connected_brokers,omitempty"` + ConnectedGateways uint32 `protobuf:"varint,21,opt,name=connected_gateways,json=connectedGateways,proto3" json:"connected_gateways,omitempty"` + ConnectedBrokers uint32 `protobuf:"varint,22,opt,name=connected_brokers,json=connectedBrokers,proto3" json:"connected_brokers,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -202,6 +198,20 @@ func (m *Status) String() string { return proto.CompactTextString(m) func (*Status) ProtoMessage() {} func (*Status) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{8} } +func (m *Status) GetSystem() *api.SystemStats { + if m != nil { + return m.System + } + return nil +} + +func (m *Status) GetComponent() *api.ComponentStats { + if m != nil { + return m.Component + } + return nil +} + func (m *Status) GetGatewayStatus() *api.Rates { if m != nil { return m.GatewayStatus @@ -230,13 +240,6 @@ func (m *Status) GetActivations() *api.Rates { return nil } -func (m *Status) GetActivationsAccepted() *api.Rates { - if m != nil { - return m.ActivationsAccepted - } - return nil -} - func init() { proto.RegisterType((*SubscribeRequest)(nil), "router.SubscribeRequest") proto.RegisterType((*UplinkMessage)(nil), "router.UplinkMessage") @@ -907,78 +910,77 @@ func (m *Status) MarshalTo(data []byte) (int, error) { _ = i var l int _ = l - if m.GatewayStatus != nil { + if m.System != nil { data[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n10, err := m.GatewayStatus.MarshalTo(data[i:]) + i = encodeVarintRouter(data, i, uint64(m.System.Size())) + n10, err := m.System.MarshalTo(data[i:]) if err != nil { return 0, err } i += n10 } - if m.ActiveGateways != 0 { - data[i] = 0x20 - i++ - i = encodeVarintRouter(data, i, uint64(m.ActiveGateways)) - } - if m.Uplink != nil { - data[i] = 0x5a + if m.Component != nil { + data[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n11, err := m.Uplink.MarshalTo(data[i:]) + i = encodeVarintRouter(data, i, uint64(m.Component.Size())) + n11, err := m.Component.MarshalTo(data[i:]) if err != nil { return 0, err } i += n11 } - if m.Downlink != nil { - data[i] = 0xaa - i++ - data[i] = 0x1 + if m.GatewayStatus != nil { + data[i] = 0x5a i++ - i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n12, err := m.Downlink.MarshalTo(data[i:]) + i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) + n12, err := m.GatewayStatus.MarshalTo(data[i:]) if err != nil { return 0, err } i += n12 } - if m.Activations != nil { - data[i] = 0xfa - i++ - data[i] = 0x1 + if m.Uplink != nil { + data[i] = 0x62 i++ - i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n13, err := m.Activations.MarshalTo(data[i:]) + i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) + n13, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } i += n13 } - if m.ActivationsAccepted != nil { - data[i] = 0x82 - i++ - data[i] = 0x2 + if m.Downlink != nil { + data[i] = 0x6a i++ - i = encodeVarintRouter(data, i, uint64(m.ActivationsAccepted.Size())) - n14, err := m.ActivationsAccepted.MarshalTo(data[i:]) + i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) + n14, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } i += n14 } + if m.Activations != nil { + data[i] = 0x72 + i++ + i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) + n15, err := m.Activations.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n15 + } if m.ConnectedGateways != 0 { - data[i] = 0xc8 + data[i] = 0xa8 i++ - data[i] = 0x2 + data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.ConnectedGateways)) } if m.ConnectedBrokers != 0 { - data[i] = 0xd0 + data[i] = 0xb0 i++ - data[i] = 0x2 + data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.ConnectedBrokers)) } @@ -1118,28 +1120,29 @@ func (m *StatusRequest) Size() (n int) { func (m *Status) Size() (n int) { var l int _ = l + if m.System != nil { + l = m.System.Size() + n += 1 + l + sovRouter(uint64(l)) + } + if m.Component != nil { + l = m.Component.Size() + n += 1 + l + sovRouter(uint64(l)) + } if m.GatewayStatus != nil { l = m.GatewayStatus.Size() n += 1 + l + sovRouter(uint64(l)) } - if m.ActiveGateways != 0 { - n += 1 + sovRouter(uint64(m.ActiveGateways)) - } if m.Uplink != nil { l = m.Uplink.Size() n += 1 + l + sovRouter(uint64(l)) } if m.Downlink != nil { l = m.Downlink.Size() - n += 2 + l + sovRouter(uint64(l)) + n += 1 + l + sovRouter(uint64(l)) } if m.Activations != nil { l = m.Activations.Size() - n += 2 + l + sovRouter(uint64(l)) - } - if m.ActivationsAccepted != nil { - l = m.ActivationsAccepted.Size() - n += 2 + l + sovRouter(uint64(l)) + n += 1 + l + sovRouter(uint64(l)) } if m.ConnectedGateways != 0 { n += 2 + sovRouter(uint64(m.ConnectedGateways)) @@ -2030,7 +2033,7 @@ func (m *Status) Unmarshal(data []byte) error { switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field GatewayStatus", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field System", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2054,18 +2057,18 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.GatewayStatus == nil { - m.GatewayStatus = &api.Rates{} + if m.System == nil { + m.System = &api.SystemStats{} } - if err := m.GatewayStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 4: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field ActiveGateways", wireType) + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Component", wireType) } - m.ActiveGateways = 0 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowRouter @@ -2075,14 +2078,28 @@ func (m *Status) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.ActiveGateways |= (uint32(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Component == nil { + m.Component = &api.ComponentStats{} + } + if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field GatewayStatus", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2106,16 +2123,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Uplink == nil { - m.Uplink = &api.Rates{} + if m.GatewayStatus == nil { + m.GatewayStatus = &api.Rates{} } - if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 21: + case 12: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Uplink", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2139,16 +2156,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Downlink == nil { - m.Downlink = &api.Rates{} + if m.Uplink == nil { + m.Uplink = &api.Rates{} } - if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 31: + case 13: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Downlink", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2172,16 +2189,16 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Activations == nil { - m.Activations = &api.Rates{} + if m.Downlink == nil { + m.Downlink = &api.Rates{} } - if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 32: + case 14: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ActivationsAccepted", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Activations", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -2205,14 +2222,14 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.ActivationsAccepted == nil { - m.ActivationsAccepted = &api.Rates{} + if m.Activations == nil { + m.Activations = &api.Rates{} } - if err := m.ActivationsAccepted.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 41: + case 21: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ConnectedGateways", wireType) } @@ -2231,7 +2248,7 @@ func (m *Status) Unmarshal(data []byte) error { break } } - case 42: + case 22: if wireType != 0 { return fmt.Errorf("proto: wrong wireType = %d for field ConnectedBrokers", wireType) } @@ -2381,56 +2398,56 @@ func init() { } var fileDescriptorRouter = []byte{ - // 813 bytes of a gzipped FileDescriptorProto + // 815 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0x67, 0x03, 0xda, 0xc6, 0x2f, 0x71, 0xec, 0x4e, 0xe3, 0x74, 0x71, 0x69, 0x62, 0xed, 0x81, - 0x9a, 0x3f, 0x5d, 0x53, 0xa3, 0x0a, 0x15, 0x04, 0xc2, 0x21, 0x51, 0x55, 0x09, 0x57, 0x68, 0x93, - 0x1e, 0x91, 0x35, 0xde, 0x7d, 0xdd, 0xac, 0xe2, 0xec, 0x2c, 0x3b, 0xb3, 0x4e, 0xfd, 0x2d, 0xe0, - 0xc6, 0x87, 0xe0, 0x83, 0x70, 0xe0, 0xc0, 0x89, 0x03, 0x07, 0x84, 0xc2, 0xa7, 0xe0, 0x86, 0x3c, - 0x7f, 0xd6, 0xbb, 0x8e, 0x13, 0x02, 0x3d, 0x79, 0xe7, 0xf7, 0x7e, 0xef, 0xe7, 0xdf, 0x7b, 0x33, - 0xf3, 0x06, 0x3e, 0x89, 0x62, 0x71, 0x92, 0x8f, 0xbd, 0x80, 0x9d, 0xf5, 0x8e, 0x4f, 0xf0, 0xf8, - 0x24, 0x4e, 0x22, 0xfe, 0x1c, 0xc5, 0x39, 0xcb, 0x4e, 0x7b, 0x42, 0x24, 0x3d, 0x9a, 0xc6, 0xbd, - 0x8c, 0xe5, 0x02, 0x33, 0xfd, 0xe3, 0xa5, 0x19, 0x13, 0x8c, 0xd8, 0x6a, 0xd5, 0xbe, 0x17, 0x31, - 0x16, 0x4d, 0xb0, 0x27, 0xd1, 0x71, 0xfe, 0xb2, 0x87, 0x67, 0xa9, 0x98, 0x29, 0x52, 0xfb, 0x61, - 0x49, 0x3d, 0x62, 0x11, 0x5b, 0xb0, 0xe6, 0x2b, 0xb9, 0x90, 0x5f, 0x2b, 0xe8, 0x57, 0x9a, 0xa1, - 0x69, 0xac, 0xe9, 0x9f, 0xdd, 0x84, 0x2e, 0xa9, 0x01, 0x9b, 0x14, 0x1f, 0x3a, 0xf9, 0xc9, 0x4d, - 0x92, 0x23, 0x2a, 0xf0, 0x9c, 0xce, 0xcc, 0xaf, 0x4a, 0x75, 0x09, 0x34, 0x8f, 0xf2, 0x31, 0x0f, - 0xb2, 0x78, 0x8c, 0x3e, 0x7e, 0x97, 0x23, 0x17, 0xee, 0x4f, 0x16, 0xd4, 0x5f, 0xa4, 0x93, 0x38, - 0x39, 0x1d, 0x22, 0xe7, 0x34, 0x42, 0xe2, 0xc0, 0xad, 0x94, 0xce, 0x26, 0x8c, 0x86, 0x8e, 0xd5, - 0xb1, 0xba, 0x9b, 0xbe, 0x59, 0x92, 0x01, 0xdc, 0x36, 0x66, 0x46, 0x67, 0x28, 0x68, 0x48, 0x05, - 0x75, 0x36, 0x3a, 0x56, 0x77, 0xa3, 0xbf, 0xed, 0x15, 0x36, 0xfd, 0x57, 0x43, 0x1d, 0xf3, 0x9b, - 0x06, 0x34, 0x08, 0xf9, 0x02, 0x9a, 0xda, 0xd3, 0x42, 0x61, 0x53, 0x2a, 0xdc, 0xf1, 0x8c, 0xd9, - 0x92, 0x40, 0x43, 0x63, 0x06, 0x70, 0x7f, 0xb1, 0xa0, 0x71, 0xc0, 0xce, 0x93, 0x9b, 0x19, 0xfe, - 0x06, 0x76, 0x0a, 0xc3, 0x01, 0x4b, 0x5e, 0xc6, 0x51, 0x9e, 0x51, 0x11, 0xb3, 0x44, 0xbb, 0x7e, - 0x7b, 0xe1, 0xfa, 0xf8, 0xd5, 0x57, 0x65, 0x82, 0xdf, 0x32, 0x91, 0x0a, 0x4c, 0x86, 0xd0, 0x32, - 0xfe, 0xab, 0x82, 0xaa, 0x08, 0xa7, 0x28, 0x62, 0x59, 0x6f, 0x5b, 0x07, 0x2a, 0xa8, 0xfb, 0xdb, - 0x1a, 0xdc, 0x3d, 0xc0, 0x69, 0x1c, 0xe0, 0x20, 0x10, 0xf1, 0x54, 0x51, 0xd5, 0xce, 0x5c, 0x53, - 0xd6, 0x73, 0xb8, 0x15, 0xe2, 0x74, 0x84, 0x79, 0x2c, 0xeb, 0xd8, 0xdc, 0x7f, 0xfc, 0xfb, 0x1f, - 0x7b, 0x8f, 0xfe, 0xed, 0x5c, 0x04, 0x2c, 0xc3, 0x9e, 0x98, 0xa5, 0xc8, 0xbd, 0x03, 0x9c, 0x1e, - 0xbe, 0x78, 0xe6, 0xdb, 0x21, 0x4e, 0x0f, 0xf3, 0x78, 0xae, 0x47, 0xd3, 0x54, 0xea, 0x6d, 0xfe, - 0x2f, 0xbd, 0x41, 0x9a, 0x4a, 0x3d, 0x9a, 0xa6, 0x73, 0xbd, 0x95, 0xe7, 0xa4, 0xf5, 0xda, 0xe7, - 0x64, 0xe7, 0x3f, 0x9c, 0x93, 0x36, 0x38, 0x97, 0xfb, 0xca, 0x53, 0x96, 0x70, 0x74, 0x1f, 0xc3, - 0xf6, 0x53, 0x45, 0x3f, 0x12, 0x54, 0xe4, 0xdc, 0x34, 0xfc, 0x3e, 0x80, 0xf9, 0xcf, 0x58, 0xf5, - 0xbc, 0xe6, 0xd7, 0x34, 0xf2, 0x2c, 0x74, 0xbf, 0x85, 0xd6, 0x52, 0x9a, 0xd2, 0x23, 0xf7, 0xa0, - 0x36, 0xa1, 0x5c, 0x8c, 0x38, 0x62, 0x22, 0xd3, 0xde, 0xf4, 0xd7, 0xe7, 0xc0, 0x11, 0x62, 0x42, - 0x1e, 0x80, 0xcd, 0x25, 0xdd, 0x59, 0x93, 0xf6, 0x1b, 0x85, 0x7d, 0xad, 0xa2, 0xc3, 0x6e, 0x03, - 0xea, 0x15, 0x3b, 0xee, 0xdf, 0x6b, 0x60, 0x2b, 0x84, 0x3c, 0x82, 0x2d, 0xe3, 0x4c, 0x8b, 0x59, - 0x52, 0x0c, 0xbc, 0xf9, 0x50, 0xf1, 0xa9, 0x40, 0xee, 0xd7, 0xa3, 0xb2, 0x39, 0xf2, 0x00, 0x1a, - 0x74, 0x5e, 0x3a, 0x8e, 0x34, 0xce, 0x9d, 0xb7, 0x3a, 0x56, 0xb7, 0xee, 0x6f, 0x29, 0x58, 0x97, - 0xc2, 0x89, 0x0b, 0x76, 0x2e, 0xef, 0xbf, 0xbe, 0x13, 0x65, 0x4d, 0x1d, 0x21, 0xef, 0xc2, 0x7a, - 0xa8, 0x2f, 0x9d, 0xde, 0xc7, 0x32, 0xab, 0x88, 0x91, 0x0f, 0x61, 0x83, 0x16, 0xfd, 0xe6, 0xce, - 0xde, 0x25, 0x6a, 0x39, 0x4c, 0x3e, 0x87, 0xed, 0xd2, 0x72, 0x44, 0x83, 0x00, 0x53, 0x81, 0xa1, - 0xd3, 0xb9, 0x94, 0x76, 0xa7, 0xc4, 0x1b, 0x68, 0x1a, 0x79, 0x08, 0x24, 0x60, 0x49, 0x82, 0x81, - 0xc0, 0x70, 0x51, 0xe4, 0x7b, 0xb2, 0xc8, 0xdb, 0x45, 0xa4, 0xa8, 0xf3, 0x03, 0x58, 0x80, 0xa3, - 0x71, 0xc6, 0x4e, 0x31, 0xe3, 0xce, 0xfb, 0x92, 0xdd, 0x2c, 0x02, 0xfb, 0x0a, 0xef, 0x7f, 0xbf, - 0x06, 0xb6, 0x2f, 0xdf, 0x09, 0xf2, 0x29, 0xd4, 0x2b, 0xdb, 0x4e, 0x96, 0x77, 0xb0, 0xbd, 0xe3, - 0xa9, 0xa7, 0xc4, 0x33, 0x8f, 0x84, 0x77, 0x38, 0x7f, 0x4a, 0xba, 0x16, 0x79, 0x02, 0xb6, 0x9a, - 0xad, 0xa4, 0xe5, 0xe9, 0x47, 0xa8, 0x32, 0x6b, 0xaf, 0x49, 0xfd, 0x12, 0x6a, 0xc5, 0xac, 0x26, - 0x8e, 0xc9, 0x5e, 0x1e, 0xdf, 0xed, 0xbb, 0x26, 0xb2, 0x34, 0x14, 0x3f, 0xb2, 0xc8, 0x10, 0xd6, - 0xf5, 0xe1, 0x47, 0xb2, 0x57, 0xd0, 0x56, 0x0f, 0x9b, 0x76, 0xe7, 0x6a, 0x82, 0x3a, 0xe5, 0xfd, - 0x1f, 0x2c, 0xa8, 0xab, 0x96, 0x0c, 0x69, 0x42, 0x23, 0xcc, 0xc8, 0xd7, 0xcb, 0x9d, 0x79, 0xc7, - 0x88, 0xac, 0xba, 0x5e, 0xed, 0xfb, 0x57, 0x44, 0xf5, 0x2d, 0xea, 0x43, 0xed, 0x29, 0x0a, 0xad, - 0x54, 0xb4, 0xab, 0x2a, 0xb1, 0x55, 0x85, 0xf7, 0x9b, 0x3f, 0x5f, 0xec, 0x5a, 0xbf, 0x5e, 0xec, - 0x5a, 0x7f, 0x5e, 0xec, 0x5a, 0x3f, 0xfe, 0xb5, 0xfb, 0xc6, 0xd8, 0x96, 0x8d, 0xfc, 0xf8, 0x9f, - 0x00, 0x00, 0x00, 0xff, 0xff, 0x0b, 0x16, 0x0d, 0xe5, 0x1f, 0x08, 0x00, 0x00, + 0x14, 0x67, 0x83, 0xb4, 0x8d, 0x5f, 0xb2, 0xb1, 0x3b, 0x8d, 0xd3, 0xc5, 0xa5, 0x6e, 0xb4, 0x07, + 0xb0, 0x04, 0x5d, 0x13, 0xa3, 0x0a, 0x15, 0x24, 0x44, 0xd2, 0x44, 0x55, 0x25, 0x5c, 0xa1, 0x75, + 0x7a, 0x44, 0xd6, 0x78, 0xfd, 0xba, 0x59, 0xc5, 0xde, 0x59, 0x76, 0x66, 0x9d, 0xfa, 0x5b, 0xc0, + 0x8d, 0x0f, 0xc1, 0x07, 0xe1, 0xc0, 0x81, 0x13, 0x07, 0x0e, 0x08, 0x85, 0x3b, 0x9f, 0x01, 0xed, + 0xfc, 0x59, 0x7b, 0x1d, 0x27, 0x04, 0x7a, 0xb2, 0xe7, 0xf7, 0x7e, 0xef, 0xb7, 0xbf, 0x79, 0xf3, + 0xe6, 0x0d, 0x7c, 0x16, 0xc5, 0xe2, 0x2c, 0x1f, 0xf9, 0x21, 0x9b, 0x76, 0x4f, 0xcf, 0xf0, 0xf4, + 0x2c, 0x4e, 0x22, 0xfe, 0x12, 0xc5, 0x05, 0xcb, 0xce, 0xbb, 0x42, 0x24, 0x5d, 0x9a, 0xc6, 0xdd, + 0x8c, 0xe5, 0x02, 0x33, 0xfd, 0xe3, 0xa7, 0x19, 0x13, 0x8c, 0xd8, 0x6a, 0xd5, 0x7a, 0x10, 0x31, + 0x16, 0x4d, 0xb0, 0x2b, 0xd1, 0x51, 0xfe, 0xba, 0x8b, 0xd3, 0x54, 0xcc, 0x15, 0xa9, 0xf5, 0x78, + 0x49, 0x3d, 0x62, 0x11, 0x5b, 0xb0, 0x8a, 0x95, 0x5c, 0xc8, 0x7f, 0x6b, 0xe8, 0xd7, 0x9a, 0xa1, + 0x69, 0xac, 0xe9, 0x5f, 0xdc, 0x86, 0x2e, 0xa9, 0x21, 0x9b, 0x94, 0x7f, 0x74, 0xf2, 0xd3, 0xdb, + 0x24, 0x47, 0x54, 0xe0, 0x05, 0x9d, 0x9b, 0x5f, 0x95, 0xea, 0x11, 0x68, 0x0c, 0xf2, 0x11, 0x0f, + 0xb3, 0x78, 0x84, 0x01, 0x7e, 0x97, 0x23, 0x17, 0xde, 0x4f, 0x16, 0x38, 0xaf, 0xd2, 0x49, 0x9c, + 0x9c, 0xf7, 0x91, 0x73, 0x1a, 0x21, 0x71, 0xe1, 0x4e, 0x4a, 0xe7, 0x13, 0x46, 0xc7, 0xae, 0xb5, + 0x6f, 0x75, 0xb6, 0x03, 0xb3, 0x24, 0x87, 0x70, 0xd7, 0x98, 0x19, 0x4e, 0x51, 0xd0, 0x31, 0x15, + 0xd4, 0xdd, 0xda, 0xb7, 0x3a, 0x5b, 0xbd, 0x5d, 0xbf, 0xb4, 0x19, 0xbc, 0xe9, 0xeb, 0x58, 0xd0, + 0x30, 0xa0, 0x41, 0xc8, 0x97, 0xd0, 0xd0, 0x9e, 0x16, 0x0a, 0xdb, 0x52, 0xe1, 0x9e, 0x6f, 0xcc, + 0x2e, 0x09, 0xd4, 0x35, 0x66, 0x00, 0xef, 0x17, 0x0b, 0xea, 0xc7, 0xec, 0x22, 0xb9, 0x9d, 0xe1, + 0x6f, 0x60, 0xaf, 0x34, 0x1c, 0xb2, 0xe4, 0x75, 0x1c, 0xe5, 0x19, 0x15, 0x31, 0x4b, 0xb4, 0xeb, + 0xf7, 0x16, 0xae, 0x4f, 0xdf, 0x3c, 0x5b, 0x26, 0x04, 0x4d, 0x13, 0xa9, 0xc0, 0xa4, 0x0f, 0x4d, + 0xe3, 0xbf, 0x2a, 0xa8, 0x36, 0xe1, 0x96, 0x9b, 0x58, 0xd5, 0xdb, 0xd5, 0x81, 0x0a, 0xea, 0xfd, + 0xb6, 0x01, 0xf7, 0x8f, 0x71, 0x16, 0x87, 0x78, 0x18, 0x8a, 0x78, 0xa6, 0xa8, 0xea, 0x64, 0x6e, + 0xd8, 0xd6, 0x4b, 0xb8, 0x33, 0xc6, 0xd9, 0x10, 0xf3, 0x58, 0xee, 0x63, 0xfb, 0xe8, 0xc9, 0xef, + 0x7f, 0x3c, 0x3a, 0xf8, 0xb7, 0xbe, 0x08, 0x59, 0x86, 0x5d, 0x31, 0x4f, 0x91, 0xfb, 0xc7, 0x38, + 0x3b, 0x79, 0xf5, 0x22, 0xb0, 0xc7, 0x38, 0x3b, 0xc9, 0xe3, 0x42, 0x8f, 0xa6, 0xa9, 0xd4, 0xdb, + 0xfe, 0x5f, 0x7a, 0x87, 0x69, 0x2a, 0xf5, 0x68, 0x9a, 0x16, 0x7a, 0x6b, 0xfb, 0xa4, 0xf9, 0xd6, + 0x7d, 0xb2, 0xf7, 0x1f, 0xfa, 0xa4, 0x05, 0xee, 0xd5, 0xba, 0xf2, 0x94, 0x25, 0x1c, 0xbd, 0x27, + 0xb0, 0xfb, 0x5c, 0xd1, 0x07, 0x82, 0x8a, 0x9c, 0x9b, 0x82, 0x3f, 0x04, 0x30, 0xdf, 0x8c, 0x55, + 0xcd, 0x6b, 0x41, 0x4d, 0x23, 0x2f, 0xc6, 0xde, 0xb7, 0xd0, 0x5c, 0x49, 0x53, 0x7a, 0xe4, 0x01, + 0xd4, 0x26, 0x94, 0x8b, 0x21, 0x47, 0x4c, 0x64, 0xda, 0xbb, 0xc1, 0x66, 0x01, 0x0c, 0x10, 0x13, + 0xf2, 0x21, 0xd8, 0x5c, 0xd2, 0xdd, 0x0d, 0x69, 0xbf, 0x5e, 0xda, 0xd7, 0x2a, 0x3a, 0xec, 0xd5, + 0xc1, 0xa9, 0xd8, 0xf1, 0xfe, 0xde, 0x00, 0x5b, 0x21, 0xa4, 0x03, 0x36, 0x9f, 0x73, 0x81, 0x53, + 0x29, 0xbf, 0xd5, 0x6b, 0xf8, 0xc5, 0x30, 0x19, 0x48, 0xa8, 0xa0, 0x14, 0x2a, 0x72, 0x41, 0x0e, + 0xa0, 0x16, 0xb2, 0x69, 0xca, 0x12, 0x4c, 0x84, 0xfe, 0xe2, 0x3d, 0x49, 0x7e, 0x66, 0x50, 0xc5, + 0x5f, 0xb0, 0xc8, 0x01, 0xec, 0x98, 0x6d, 0x6b, 0xa7, 0xea, 0x72, 0x80, 0xcc, 0x0b, 0xa8, 0x40, + 0x1e, 0x38, 0xd1, 0xf2, 0xce, 0x89, 0x07, 0x76, 0x2e, 0x67, 0x86, 0x6e, 0xfb, 0x65, 0xaa, 0x8e, + 0x90, 0x0f, 0x60, 0x73, 0xac, 0x2f, 0xaa, 0xeb, 0x5c, 0x61, 0x95, 0x31, 0xf2, 0x31, 0x6c, 0xd1, + 0xf2, 0x8c, 0xb8, 0xbb, 0x73, 0x85, 0xba, 0x1c, 0x26, 0x8f, 0x81, 0x84, 0x2c, 0x49, 0x30, 0x14, + 0x38, 0x1e, 0x6a, 0x53, 0x5c, 0xf6, 0x96, 0x13, 0xdc, 0x2d, 0x23, 0xfa, 0x9c, 0x38, 0xf9, 0x08, + 0x16, 0xe0, 0x70, 0x94, 0xb1, 0x73, 0xcc, 0xb8, 0xec, 0x23, 0x27, 0x68, 0x94, 0x81, 0x23, 0x85, + 0xf7, 0xbe, 0xdf, 0x00, 0x3b, 0x90, 0x8f, 0x03, 0xf9, 0x1c, 0x9c, 0xca, 0x59, 0x93, 0xd5, 0x63, + 0x6b, 0xed, 0xf9, 0xea, 0xfd, 0xf0, 0xcd, 0xcb, 0xe0, 0x9f, 0x14, 0xef, 0x47, 0xc7, 0x22, 0x4f, + 0xc1, 0x56, 0x03, 0x95, 0x34, 0x7d, 0xfd, 0xf2, 0x54, 0x06, 0xec, 0x0d, 0xa9, 0x5f, 0x41, 0xad, + 0x1c, 0xd0, 0xc4, 0x35, 0xd9, 0xab, 0x33, 0xbb, 0x75, 0xdf, 0x44, 0x56, 0x26, 0xe1, 0x27, 0x16, + 0xe9, 0xc3, 0xa6, 0xee, 0x78, 0x24, 0x8f, 0x4a, 0xda, 0xfa, 0x09, 0xd3, 0xda, 0xbf, 0x9e, 0xa0, + 0x5a, 0xbb, 0xf7, 0x83, 0x05, 0x8e, 0x2a, 0x49, 0x9f, 0x26, 0x34, 0xc2, 0x8c, 0x7c, 0xbd, 0x5a, + 0x99, 0xf7, 0x8d, 0xc8, 0xba, 0x3b, 0xd5, 0x7a, 0x78, 0x4d, 0x54, 0x5f, 0x9d, 0x1e, 0xd4, 0x9e, + 0xa3, 0xd0, 0x4a, 0x65, 0xb9, 0xaa, 0x12, 0x3b, 0x55, 0xf8, 0xa8, 0xf1, 0xf3, 0x65, 0xdb, 0xfa, + 0xf5, 0xb2, 0x6d, 0xfd, 0x79, 0xd9, 0xb6, 0x7e, 0xfc, 0xab, 0xfd, 0xce, 0xc8, 0x96, 0x85, 0xfc, + 0xf4, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x78, 0xda, 0x25, 0xba, 0x14, 0x08, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index 3060c43d7..7d30a25ef 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -73,23 +73,17 @@ message StatusRequest {} // message Status is the response to the StatusRequest message Status { - // Gateways - api.Rates gateway_status = 1; - uint32 active_gateways = 4; + api.SystemStats system = 1; + api.ComponentStats component = 2; - // Uplink - api.Rates uplink = 11; - - // Downlink - api.Rates downlink = 21; - - // Activations - api.Rates activations = 31; - api.Rates activations_accepted = 32; + api.Rates gateway_status = 11; + api.Rates uplink = 12; + api.Rates downlink = 13; + api.Rates activations = 14; // Connections - uint32 connected_gateways = 41; - uint32 connected_brokers = 42; + uint32 connected_gateways = 21; + uint32 connected_brokers = 22; } // The RouterManager service provides configuration and monitoring functionality diff --git a/api/stats.go b/api/stats.go new file mode 100644 index 000000000..bfe8db8a1 --- /dev/null +++ b/api/stats.go @@ -0,0 +1,67 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package api + +import ( + "os" + "runtime" + + "github.com/shirou/gopsutil/cpu" + "github.com/shirou/gopsutil/load" + "github.com/shirou/gopsutil/mem" + "github.com/shirou/gopsutil/process" +) + +// GetSystemStats gets statistics about the system +func GetSystemStats() *SystemStats { + status := new(SystemStats) + if load, err := load.Avg(); err == nil { + status.Load = &SystemStats_Loadstats{ + Load1: float32(load.Load1), + Load5: float32(load.Load5), + Load15: float32(load.Load15), + } + } + if cpu, err := cpu.Times(false); err == nil && len(cpu) == 1 { + status.Cpu = &SystemStats_CPUStats{ + User: float32(cpu[0].User), + System: float32(cpu[0].System), + Idle: float32(cpu[0].Idle), + } + } + if mem, err := mem.VirtualMemory(); err == nil { + status.Memory = &SystemStats_MemoryStats{ + Total: mem.Total, + Available: mem.Available, + Used: mem.Used, + } + } + return status +} + +// GetComponentStats gets statistics about this component +func GetComponentStats() *ComponentStats { + status := new(ComponentStats) + process, err := process.NewProcess(int32(os.Getpid())) + if err == nil { + if memory, err := process.MemoryInfo(); err == nil { + status.Memory = &ComponentStats_MemoryStats{ + Memory: memory.RSS, + Swap: memory.Swap, + } + } + if cpu, err := process.Times(); err == nil { + status.Cpu = &ComponentStats_CPUStats{ + User: float32(cpu.User), + System: float32(cpu.System), + Idle: float32(cpu.Idle), + } + } + } + status.Goroutines = uint64(runtime.NumGoroutine()) + memstats := new(runtime.MemStats) + runtime.ReadMemStats(memstats) + status.GcCpuFraction = float32(memstats.GCCPUFraction) + return status +} From 25f96609fb16ab8fc9a038817114d39d7d261d62 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 12:16:56 +0200 Subject: [PATCH 1849/2266] Vendor gopsutil --- vendor/vendor.json | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/vendor/vendor.json b/vendor/vendor.json index 59b55c2e2..82f1cc6dc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -381,6 +381,48 @@ "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", "revisionTime": "2016-09-04T16:08:59Z" }, + { + "checksumSHA1": "ToLFWWpwBceipBQRASIxs36Glok=", + "path": "github.com/shirou/gopsutil/cpu", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, + { + "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", + "path": "github.com/shirou/gopsutil/host", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, + { + "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", + "path": "github.com/shirou/gopsutil/internal/common", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, + { + "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", + "path": "github.com/shirou/gopsutil/load", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, + { + "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", + "path": "github.com/shirou/gopsutil/mem", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, + { + "checksumSHA1": "Fa7lUUadHW9402ifjheuSFDkufo=", + "path": "github.com/shirou/gopsutil/net", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, + { + "checksumSHA1": "k1ug9JDvES8wNqxaQcO7qxgpOKc=", + "path": "github.com/shirou/gopsutil/process", + "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", + "revisionTime": "2016-09-22T12:59:23Z" + }, { "checksumSHA1": "XXTR/ftEYSqQn4+Y7wNfiJJmq9U=", "origin": "github.com/cpuguy83/go-md2man/vendor/github.com/shurcooL/sanitized_anchor_name", From 7d949de998a7853cbd31cc8544f97294cb72458a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 13:27:41 +0200 Subject: [PATCH 1850/2266] Store selected appID and appEUI in data dir instead of in the config file --- ttnctl/cmd/applications_add.go | 8 +- ttnctl/cmd/applications_select.go | 9 +- ttnctl/util/config.go | 190 ++++++++++++++++-------------- 3 files changed, 106 insertions(+), 101 deletions(-) diff --git a/ttnctl/cmd/applications_add.go b/ttnctl/cmd/applications_add.go index 80caa05cb..96589a2e1 100644 --- a/ttnctl/cmd/applications_add.go +++ b/ttnctl/cmd/applications_add.go @@ -49,13 +49,7 @@ var applicationsAddCmd = &cobra.Command{ skipSelect, _ := cmd.Flags().GetBool("skip-select") if !skipSelect { - err = util.SetConfig(map[string]interface{}{ - "app-id": app.ID, - "app-eui": app.EUIs[0].String(), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not update configuration") - } + util.SetApp(ctx, app.ID, app.EUIs[0]) } ctx.Info("Selected Current Application") diff --git a/ttnctl/cmd/applications_select.go b/ttnctl/cmd/applications_select.go index 496ca9a7b..5679333c0 100644 --- a/ttnctl/cmd/applications_select.go +++ b/ttnctl/cmd/applications_select.go @@ -92,16 +92,9 @@ var applicationsSelectCmd = &cobra.Command{ } eui := app.EUIs[euiIdx] - err = util.SetConfig(map[string]interface{}{ - "app-id": app.ID, - "app-eui": eui.String(), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not update configuration") - } + util.SetApp(ctx, app.ID, eui) ctx.Info("Updated configuration") - }, } diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go index 3e32b5a02..38e865565 100644 --- a/ttnctl/util/config.go +++ b/ttnctl/util/config.go @@ -9,88 +9,123 @@ import ( "os" "path" + yaml "gopkg.in/yaml.v2" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" - "github.com/mitchellh/go-homedir" "github.com/spf13/viper" - "gopkg.in/yaml.v2" ) -func getConfigLocation() (string, error) { - cFile := viper.ConfigFileUsed() - if cFile == "" { - dir, err := homedir.Dir() - if err != nil { - return "", fmt.Errorf("Could not get homedir: %s", err.Error()) - } - expanded, err := homedir.Expand(dir) - if err != nil { - return "", fmt.Errorf("Could not get homedir: %s", err.Error()) - } - cFile = path.Join(expanded, ".ttnctl.yaml") +const ( + appFilename = "app" + euiKey = "eui" + idKey = "id" +) + +// GetConfigFile returns the location of the configuration file. +// It checks the following (in this order): +// the --config flag +// $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set) +// $HOME/.ttnctl.yml +func GetConfigFile() string { + file := viper.GetString("config") + if file != "" { + return file } - return cFile, nil + + xdg := os.Getenv("XDG_CONFIG_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl", "config.yml") + } + + return path.Join(os.Getenv("HOME"), ".ttnctl.yml") } -// ReadConfig reads the config file -func ReadConfig() (map[string]interface{}, error) { - cFile, err := getConfigLocation() - if err != nil { - return nil, err +// GetDataDir returns the location of the data directory used for +// sotring data. +// It checks the following (in this order): +// the --data flag +// $XDG_DATA_HOME/ttnctl (if $XDG_DATA_HOME is set) +// $XDG_CACHE_HOME/ttnctl (if $XDG_CACHE_HOME is set) +// $HOME/.ttnctl +func GetDataDir() string { + file := viper.GetString("data") + if file != "" { + return file + } + + xdg := os.Getenv("XDG_DATA_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl") + } + + xdg = os.Getenv("XDG_CACHE_HOME") + if xdg != "" { + return path.Join(xdg, "ttnctl") } + return path.Join(os.Getenv("HOME"), ".ttnctl") +} + +func readData(file string) map[string]interface{} { + fullpath := path.Join(GetDataDir(), file) + c := make(map[string]interface{}) // Read config file - bytes, err := ioutil.ReadFile(cFile) - if err == nil { - err = yaml.Unmarshal(bytes, &c) + data, err := ioutil.ReadFile(fullpath) + if err != nil { + return c } + + err = yaml.Unmarshal(data, &c) if err != nil { - return nil, fmt.Errorf("Could not read configuration file: %s", err.Error()) + return c } - return c, nil + return c } -// WriteConfigFile writes the config file -func WriteConfigFile(data map[string]interface{}) error { - cFile, err := getConfigLocation() - if err != nil { - return err - } +func writeData(file string, data map[string]interface{}) error { + fullpath := path.Join(GetDataDir(), file) - // Write config file + // Generate yaml contents d, err := yaml.Marshal(&data) if err != nil { return fmt.Errorf("Could not generate configiguration file contents: %s", err.Error()) } - err = ioutil.WriteFile(cFile, d, 0644) + + // Write to file + err = ioutil.WriteFile(fullpath, d, 0644) if err != nil { - return fmt.Errorf("Could not write configiguration file: %s", err.Error()) + return fmt.Errorf("Could not write configuration file: %s", err.Error()) } return nil } -// SetConfig sets the specified fields in the config file. -func SetConfig(data map[string]interface{}) error { - config, err := ReadConfig() - if err != nil { - return err - } - for key, value := range data { - config[key] = value - } - return WriteConfigFile(config) +func setData(file, key string, data interface{}) error { + config := readData(file) + config[key] = data + return writeData(file, config) } // GetAppEUI returns the AppEUI that must be set in the command options or config func GetAppEUI(ctx log.Interface) types.AppEUI { appEUIString := viper.GetString("app-eui") + if appEUIString == "" { + appData := readData(appFilename) + eui, ok := appData[euiKey].(string) + if !ok { + ctx.Fatal("Invalid AppEUI in config file") + } + appEUIString = eui + } + if appEUIString == "" { ctx.Fatal("Missing AppEUI. You should select an application to use with \"ttnctl applications select\"") } + eui, err := types.ParseAppEUI(appEUIString) if err != nil { ctx.WithError(err).Fatal("Invalid AppEUI") @@ -98,56 +133,39 @@ func GetAppEUI(ctx log.Interface) types.AppEUI { return eui } +// SetApp stores the app EUI preference +func SetAppEUI(ctx log.Interface, appEUI types.AppEUI) { + err := setData(appFilename, euiKey, appEUI.String()) + if err != nil { + ctx.WithError(err).Fatal("Could not save app EUI") + } +} + // GetAppID returns the AppID that must be set in the command options or config func GetAppID(ctx log.Interface) string { appID := viper.GetString("app-id") if appID == "" { - ctx.Fatal("Missing AppID. You should select an application to use with \"ttnctl applications select\"") - } - return appID -} - -// GetConfigFile returns the location of the configuration file. -// It checks the following (in this order): -// the --config flag -// $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set) -// $HOME/.ttnctl.yml -func GetConfigFile() string { - file := viper.GetString("config") - if file != "" { - return file + appData := readData(appFilename) + id, ok := appData[idKey].(string) + if !ok { + ctx.Fatal("Invalid appID in config file.") + } + appID = id } - xdg := os.Getenv("XDG_CONFIG_HOME") - if xdg != "" { - return path.Join(xdg, "ttnctl", "config.yml") + if appID == "" { + ctx.Fatal("Missing AppID. You should select an application to use with \"ttnctl applications select\"") } - - return path.Join(os.Getenv("HOME"), ".ttnctl.yml") + return appID } -// GetDataDir returns the location of the data directory used for -// sotring data. -// It checks the following (in this order): -// the --data flag -// $XDG_DATA_HOME/ttnctl (if $XDG_DATA_HOME is set) -// $XDG_CACHE_HOME/ttnctl (if $XDG_CACHE_HOME is set) -// $HOME/.ttnctl -func GetDataDir() string { - file := viper.GetString("data") - if file != "" { - return file - } - - xdg := os.Getenv("XDG_DATA_HOME") - if xdg != "" { - return path.Join(xdg, "ttnctl") - } - - xdg = os.Getenv("XDG_CACHE_HOME") - if xdg != "" { - return path.Join(xdg, "ttnctl") +// SetApp stores the app ID and app EUI preferences +func SetApp(ctx log.Interface, appID string, appEUI types.AppEUI) { + config := readData(appFilename) + config[idKey] = appID + config[euiKey] = appEUI.String() + err := writeData(appFilename, config) + if err != nil { + ctx.WithError(err).Fatal("Could not save app preference") } - - return path.Join(os.Getenv("HOME"), ".ttnctl") } From 40321bcb2c39bc05499a537db181f750c8080422 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 13:34:13 +0200 Subject: [PATCH 1851/2266] Do not try to read config file if it does not exist --- ttnctl/cmd/root.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index b2957db85..a039213ad 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -133,10 +133,10 @@ func initConfig() { viper.AutomaticEnv() // If a config file is found, read it in. - err := viper.ReadInConfig() - if err != nil { - fmt.Println("Error when reading config file:", err) - } else if err == nil && viper.GetBool("debug") { - fmt.Println("Using config file:", viper.ConfigFileUsed()) + if _, err := os.Stat(cfgFile); err == nil { + err := viper.ReadInConfig() + if err != nil { + fmt.Println("Error when reading config file:", err) + } } } From c85784dcbe81f87e22dcf666104bfafa7a6e16f8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Fri, 30 Sep 2016 13:39:05 +0200 Subject: [PATCH 1852/2266] Show correct app EUI and app ID --- ttnctl/cmd/devices.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go index eee6c496e..cd721875c 100644 --- a/ttnctl/cmd/devices.go +++ b/ttnctl/cmd/devices.go @@ -4,6 +4,7 @@ package cmd import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -16,8 +17,8 @@ var devicesCmd = &cobra.Command{ PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) ctx.WithFields(log.Fields{ - "AppID": viper.GetString("app-id"), - "AppEUI": viper.GetString("app-eui"), + "AppID": util.GetAppID(ctx), + "AppEUI": util.GetAppEUI(ctx), }).Info("Using Application") }, } From 92bd9855aac9e3e78d7675db0e42c903c7d91e38 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 14:03:17 +0200 Subject: [PATCH 1853/2266] Check login before running commands --- ttnctl/cmd/applications.go | 9 ++++++++- ttnctl/cmd/devices.go | 1 + ttnctl/cmd/gateways.go | 6 +++++- ttnctl/cmd/subscribe.go | 2 ++ 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index 512e08f78..b8d14a1b3 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -3,12 +3,19 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) var applicationsCmd = &cobra.Command{ Use: "applications", Short: "Manage applications", Long: `ttnctl applications can be used to manage applications.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) + }, } func init() { diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go index cd721875c..678f07b72 100644 --- a/ttnctl/cmd/devices.go +++ b/ttnctl/cmd/devices.go @@ -16,6 +16,7 @@ var devicesCmd = &cobra.Command{ Long: `ttnctl devices can be used to manage devices.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) ctx.WithFields(log.Fields{ "AppID": util.GetAppID(ctx), "AppEUI": util.GetAppEUI(ctx), diff --git a/ttnctl/cmd/gateways.go b/ttnctl/cmd/gateways.go index de5df8c98..fc3cff1b3 100644 --- a/ttnctl/cmd/gateways.go +++ b/ttnctl/cmd/gateways.go @@ -3,7 +3,10 @@ package cmd -import "github.com/spf13/cobra" +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) var gatewaysCmd = &cobra.Command{ Use: "gateways", @@ -11,6 +14,7 @@ var gatewaysCmd = &cobra.Command{ Long: `ttnctl gateways can be used to manage gateways.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) }, } diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 3615ae4cb..0e7f60f5e 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -19,6 +19,8 @@ var subscribeCmd = &cobra.Command{ Short: "Subscribe to events for this application", Long: `ttnctl subscribe can be used to subscribe to events for this application.`, Run: func(cmd *cobra.Command, args []string) { + util.GetAccount(ctx) + client := util.GetMQTT(ctx) defer client.Disconnect() From 1cae137c661237a95f09a47ed3fea4f9386a43ff Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 14:06:47 +0200 Subject: [PATCH 1854/2266] Hide ttnctl uplink cmd --- ttnctl/cmd/uplink.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 69de56a1f..b52b94229 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -15,9 +15,10 @@ import ( ) var uplinkCmd = &cobra.Command{ - Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload]", - Short: "Simulate an uplink message to the network", - Long: `ttnctl uplink simulates an uplink message to the network`, + Hidden: true, + Use: "uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload]", + Short: "Simulate an uplink message to the network", + Long: `ttnctl uplink simulates an uplink message to the network`, Run: func(cmd *cobra.Command, args []string) { if len(args) < 5 { cmd.UsageFunc()(cmd) From 05bfd50768fa69586dcabc6e9a3a8ecf3e7369f3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 14:07:04 +0200 Subject: [PATCH 1855/2266] Update ttnctl docs --- ttnctl/cmd/docs/README.md | 119 ++++++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 44 deletions(-) diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index e9c41c733..869786ff2 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -15,6 +15,8 @@ ttnctl applications can be used to manage applications. ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -57,6 +59,8 @@ $ ttnctl applications add test "Test application" ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -82,6 +86,8 @@ ttnctl applications delete ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -129,6 +135,8 @@ Collaborators: ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -165,6 +173,8 @@ $ ttnctl applications list ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -196,9 +206,9 @@ $ ttnctl applications pf INFO Found Application INFO Decoder function function Decoder(bytes) { - return { - payload: bytes, - }; + var decoded = {}; + decoded.led = bytes[0]; + return decoded; } INFO No converter function INFO No validator function @@ -210,6 +220,8 @@ function Decoder(bytes) { ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -239,18 +251,21 @@ $ ttnctl applications pf set decoder INFO Discovering Handler... INFO Connecting with Handler... function Decoder(bytes) { - // Here you can decode the payload into json. - // bytes is of type Buffer. - // todo: return an object - return { - payload: bytes, - }; + // Decode an uplink message from a buffer + // (array) of bytes to an object of fields. + var decoded = {}; + + // decoded.led = bytes[0]; + + return decoded; } ########## Write your Decoder here and end with Ctrl+D (EOF): function Decoder(bytes) { - return { - payload: bytes, - }; + var decoded = {}; + + decoded.led = bytes[0]; + + return decoded; } INFO Updated application AppID=test @@ -260,6 +275,8 @@ function Decoder(bytes) { ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -295,6 +312,8 @@ $ ttnctl applications register ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -330,6 +349,8 @@ $ ttnctl applications select ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -367,6 +388,8 @@ Are you sure you want to unregister application test? ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -395,6 +418,8 @@ ttnctl devices can be used to manage devices. ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -435,6 +460,8 @@ Are you sure you want to delete device test from application test? --app-eui string The app EUI to use --app-id string The app ID to use --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -495,6 +522,8 @@ $ ttnctl devices info test --app-eui string The app EUI to use --app-id string The app ID to use --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -537,6 +566,8 @@ test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 --app-eui string The app EUI to use --app-id string The app ID to use --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -578,6 +609,8 @@ $ ttnctl devices personalize test --app-eui string The app EUI to use --app-id string The app ID to use --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -618,6 +651,8 @@ $ ttnctl devices register test --app-eui string The app EUI to use --app-id string The app ID to use --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -670,6 +705,8 @@ $ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 ``` --app-id string The app ID to use --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -717,6 +754,8 @@ $ ttnctl downlink test --json '{"led":"on"}' ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -738,6 +777,8 @@ ttnctl gateways can be used to manage gateways. ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -771,6 +812,8 @@ $ ttnctl gateways delete test ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -811,6 +854,8 @@ $ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -845,6 +890,8 @@ $ ttnctl gateways list ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -878,6 +925,8 @@ $ ttnctl gateways register test US 52.37403,4.88968 ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -922,6 +971,8 @@ $ ttnctl gateways status test ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -947,38 +998,8 @@ ttnctl subscribe ``` --config string config file (default is $HOME/.ttnctl.yaml) - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl uplink - -Simulate an uplink message to the network - -### Synopsis - - -ttnctl uplink simulates an uplink message to the network - -``` -ttnctl uplink [DevAddr] [NwkSKey] [AppSKey] [FCnt] [Payload] -``` - -### Options - -``` - --confirmed Use confirmed uplink (this also sets --downlink) - --downlink Also start downlink (unstable) -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -1018,6 +1039,8 @@ $ ttnctl user ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -1054,6 +1077,8 @@ $ ttnctl user login [paste the access code you requested above] ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -1079,6 +1104,8 @@ ttnctl user logout ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -1114,6 +1141,8 @@ Password: ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") @@ -1139,6 +1168,8 @@ ttnctl version ``` --config string config file (default is $HOME/.ttnctl.yaml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") From e6114911be47bbbee4284e0162095351fa46980d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 16:34:11 +0200 Subject: [PATCH 1856/2266] Add vendors for Windows system stats --- vendor/vendor.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/vendor/vendor.json b/vendor/vendor.json index 82f1cc6dc..339eb7fab 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2,6 +2,12 @@ "comment": "", "ignore": "test", "package": [ + { + "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", + "path": "github.com/StackExchange/wmi", + "revision": "e54cbda6595d7293a7a468ccf9525f6bc8887f99", + "revisionTime": "2016-08-11T21:45:55Z" + }, { "checksumSHA1": "waq4N4OCUwSpA4kjU3abu//ms68=", "path": "github.com/TheThingsNetwork/go-account-lib/account", @@ -134,6 +140,18 @@ "revision": "f12c6236fe7b5cf6bcf30e5935d08cb079d78334", "revisionTime": "2016-08-16T05:15:41Z" }, + { + "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", + "path": "github.com/go-ole/go-ole", + "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", + "revisionTime": "2016-07-29T03:38:29Z" + }, + { + "checksumSHA1": "qLYVTQDhgrVIeZ2KI9eZV51mmug=", + "path": "github.com/go-ole/go-ole/oleutil", + "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", + "revisionTime": "2016-07-29T03:38:29Z" + }, { "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", "path": "github.com/gogo/protobuf/gogoproto", @@ -423,6 +441,12 @@ "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", "revisionTime": "2016-09-22T12:59:23Z" }, + { + "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", + "path": "github.com/shirou/w32", + "revision": "bb4de0191aa41b5507caa14b0650cdbddcd9280b", + "revisionTime": "2016-09-30T03:27:40Z" + }, { "checksumSHA1": "XXTR/ftEYSqQn4+Y7wNfiJJmq9U=", "origin": "github.com/cpuguy83/go-md2man/vendor/github.com/shurcooL/sanitized_anchor_name", From 2c9863e8265d8bf0b3ef18bec04cd1d337cc6ea2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 30 Sep 2016 16:38:36 +0200 Subject: [PATCH 1857/2266] Upload binaries to Azure --- .gitlab-ci.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d777c26fe..8960720ae 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -56,6 +56,7 @@ binaries: - popd - mkdir release - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/release/* release/ + - echo $CI_BUILD_REF > release/commit artifacts: paths: - release/ @@ -86,3 +87,14 @@ dockerhub-image: - docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_PASSWORD" - docker tag ttn $CONTAINER_NAME:$CI_BUILD_REF_NAME - docker push $CONTAINER_NAME:$CI_BUILD_REF_NAME + +azure-binaries: + only: + - master@thethingsnetwork/ttn + - refactor@thethingsnetwork/ttn + stage: package + image: registry.gitlab.com/thethingsindustries/upload + script: + - cd release + - export STORAGE_CONTAINER=release STORAGE_KEY=$AZURE_STORAGE_KEY ZIP=true TGZ=true PREFIX=$CI_BUILD_REF_NAME/ + - upload * From a68fcb9f9858f5fd838ac083802fb38e4d33d506 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 14:41:35 +0200 Subject: [PATCH 1858/2266] Update ttnctl README for #282 --- .env/{ttnctl.yaml.dev-example => ttnctl.yml.dev-example} | 0 ttnctl/README.md | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename .env/{ttnctl.yaml.dev-example => ttnctl.yml.dev-example} (100%) diff --git a/.env/ttnctl.yaml.dev-example b/.env/ttnctl.yml.dev-example similarity index 100% rename from .env/ttnctl.yaml.dev-example rename to .env/ttnctl.yml.dev-example diff --git a/ttnctl/README.md b/ttnctl/README.md index ec4b1fbb4..f6ce92e25 100644 --- a/ttnctl/README.md +++ b/ttnctl/README.md @@ -27,7 +27,7 @@ The following configuration options can be set: ## Development -**Configuration for Development:** Copy `../.env/ttnctl.yaml.dev-example` to `~/.ttnctl.yaml` +**Configuration for Development:** Copy `../.env/ttnctl.yml.dev-example` to `~/.ttnctl.yml` ## License From 7bb60ea3d85e496f21abc1df50d61000b3e2b532 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 14:48:45 +0200 Subject: [PATCH 1859/2266] Add aliases for ttnctl commands --- ttnctl/cmd/applications.go | 7 ++++--- ttnctl/cmd/applications_list.go | 7 ++++--- ttnctl/cmd/devices.go | 7 ++++--- ttnctl/cmd/devices_list.go | 7 ++++--- ttnctl/cmd/gateways.go | 7 ++++--- ttnctl/cmd/gateways_list.go | 7 ++++--- ttnctl/cmd/user.go | 7 ++++--- 7 files changed, 28 insertions(+), 21 deletions(-) diff --git a/ttnctl/cmd/applications.go b/ttnctl/cmd/applications.go index b8d14a1b3..2a6c18f4b 100644 --- a/ttnctl/cmd/applications.go +++ b/ttnctl/cmd/applications.go @@ -9,9 +9,10 @@ import ( ) var applicationsCmd = &cobra.Command{ - Use: "applications", - Short: "Manage applications", - Long: `ttnctl applications can be used to manage applications.`, + Use: "applications", + Aliases: []string{"application"}, + Short: "Manage applications", + Long: `ttnctl applications can be used to manage applications.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) util.GetAccount(ctx) diff --git a/ttnctl/cmd/applications_list.go b/ttnctl/cmd/applications_list.go index 386d0630b..efa4b90b8 100644 --- a/ttnctl/cmd/applications_list.go +++ b/ttnctl/cmd/applications_list.go @@ -12,9 +12,10 @@ import ( ) var applicationsListCmd = &cobra.Command{ - Use: "list", - Short: "List applications", - Long: `ttnctl applications list can be used to list applications.`, + Use: "list", + Aliases: []string{"ls"}, + Short: "List applications", + Long: `ttnctl applications list can be used to list applications.`, Example: `$ ttnctl applications list INFO Found one application: diff --git a/ttnctl/cmd/devices.go b/ttnctl/cmd/devices.go index 678f07b72..e3301ef37 100644 --- a/ttnctl/cmd/devices.go +++ b/ttnctl/cmd/devices.go @@ -11,9 +11,10 @@ import ( ) var devicesCmd = &cobra.Command{ - Use: "devices", - Short: "Manage devices", - Long: `ttnctl devices can be used to manage devices.`, + Use: "devices", + Aliases: []string{"device"}, + Short: "Manage devices", + Long: `ttnctl devices can be used to manage devices.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) util.GetAccount(ctx) diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index 59ccb64f9..22532a4a8 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -13,9 +13,10 @@ import ( ) var devicesListCmd = &cobra.Command{ - Use: "list", - Short: "List al devices for the current application", - Long: `ttnctl devices list can be used to list all devices for the current application.`, + Use: "list", + Aliases: []string{"ls"}, + Short: "List al devices for the current application", + Long: `ttnctl devices list can be used to list all devices for the current application.`, Example: `$ ttnctl devices list INFO Using Application AppID=test INFO Discovering Handler... diff --git a/ttnctl/cmd/gateways.go b/ttnctl/cmd/gateways.go index fc3cff1b3..8ef1574b0 100644 --- a/ttnctl/cmd/gateways.go +++ b/ttnctl/cmd/gateways.go @@ -9,9 +9,10 @@ import ( ) var gatewaysCmd = &cobra.Command{ - Use: "gateways", - Short: "Manage gateways", - Long: `ttnctl gateways can be used to manage gateways.`, + Use: "gateways", + Aliases: []string{"gateway"}, + Short: "Manage gateways", + Long: `ttnctl gateways can be used to manage gateways.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { RootCmd.PersistentPreRun(cmd, args) util.GetAccount(ctx) diff --git a/ttnctl/cmd/gateways_list.go b/ttnctl/cmd/gateways_list.go index 5bf0a0b33..b6b85b9e9 100644 --- a/ttnctl/cmd/gateways_list.go +++ b/ttnctl/cmd/gateways_list.go @@ -12,9 +12,10 @@ import ( ) var gatewaysListCmd = &cobra.Command{ - Use: "list", - Short: "List your gateways", - Long: `ttnctl gateways list can be used to list the gateways you have access to`, + Use: "list", + Aliases: []string{"ls"}, + Short: "List your gateways", + Long: `ttnctl gateways list can be used to list the gateways you have access to`, Example: `$ ttnctl gateways list ID Activated Frequency Plan Coordinates 1 test true US (52.3740, 4.8896) diff --git a/ttnctl/cmd/user.go b/ttnctl/cmd/user.go index e4fe4db33..d8af553ed 100644 --- a/ttnctl/cmd/user.go +++ b/ttnctl/cmd/user.go @@ -13,9 +13,10 @@ import ( ) var userCmd = &cobra.Command{ - Use: "user", - Short: "Show the current user", - Long: `ttnctl user shows the current logged on user's profile`, + Use: "user", + Aliases: []string{"users"}, + Short: "Show the current user", + Long: `ttnctl user shows the current logged on user's profile`, Example: `$ ttnctl user INFO Found user profile: From 0bd351da81fe35a12f5c4c2955c7766754acfa7e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 15:49:51 +0200 Subject: [PATCH 1860/2266] Add git branch to ttn and ttnctl version information --- Makefile | 5 +++-- cmd/version.go | 3 ++- main.go | 2 ++ ttnctl/cmd/version.go | 3 ++- ttnctl/main.go | 5 ++++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d834b034c..e4ea0c67e 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,11 @@ GOBUILD = $(GOCMD) build PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn -GIT_COMMIT = `git rev-parse --short HEAD 2>/dev/null` +GIT_BRANCH = `git rev-parse --abbrev-ref HEAD 2>/dev/null` +GIT_COMMIT = `git rev-parse HEAD 2>/dev/null` BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` -LDFLAGS = -ldflags "-w -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" +LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" select_pkgs = govendor list --no-status +local coverage_pkgs = $(select_pkgs) | grep -vE 'ttn/api|ttn/cmd|ttn/ttnctl' diff --git a/cmd/version.go b/cmd/version.go index 6109c41e4..9c6c74e1c 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -16,7 +16,8 @@ var versionCmd = &cobra.Command{ Long: `ttn version gets the build and version information of ttn`, Run: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "Commit": viper.GetString("gitCommit"), + "Branch": viper.GetString("gitBranch"), + "Commit": viper.GetString("gitCommit")[:7], "BuildDate": viper.GetString("buildDate"), }).Infof("You are running version %s of ttn.", viper.GetString("version")) }, diff --git a/main.go b/main.go index 8aa661707..e0a051fcb 100644 --- a/main.go +++ b/main.go @@ -10,12 +10,14 @@ import ( var ( version = "2.0.0-dev" + gitBranch = "unknown" gitCommit = "unknown" buildDate = "unknown" ) func main() { viper.Set("version", version) + viper.Set("gitBranch", gitBranch) viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index 48e3ef852..b09d06afd 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -15,7 +15,8 @@ var versionCmd = &cobra.Command{ Long: `ttnctl version gets the build and version information of ttnctl`, Run: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "Commit": viper.GetString("gitCommit"), + "Branch": viper.GetString("gitBranch"), + "Commit": viper.GetString("gitCommit")[:7], "BuildDate": viper.GetString("buildDate"), }).Infof("You are running version %s of ttnctl.", viper.GetString("version")) }, diff --git a/ttnctl/main.go b/ttnctl/main.go index ec96f5a69..06f78c670 100644 --- a/ttnctl/main.go +++ b/ttnctl/main.go @@ -9,12 +9,15 @@ import ( ) var ( + version = "2.0.0-dev" + gitBranch = "unknown" gitCommit = "unknown" buildDate = "unknown" ) func main() { - viper.Set("version", "2.0.0-dev") + viper.Set("version", version) + viper.Set("gitBranch", gitBranch) viper.Set("gitCommit", gitCommit) viper.Set("buildDate", buildDate) cmd.Execute() From 614a5130a67c4f01f302ab9d46181ae424e35d84 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 15:51:28 +0200 Subject: [PATCH 1861/2266] Add build info to release/info --- .gitlab-ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8960720ae..d766e049f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -45,6 +45,9 @@ binaries: stage: build image: golang:latest script: + - mkdir release + - echo "date $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> release/info + - echo "commit $CI_BUILD_REF" >> release/info - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps - TARGET_PLATFORM=linux-386 make build @@ -54,9 +57,7 @@ binaries: - TARGET_PLATFORM=windows-386 make build - TARGET_PLATFORM=windows-amd64 make build - popd - - mkdir release - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/release/* release/ - - echo $CI_BUILD_REF > release/commit artifacts: paths: - release/ From 556c2f0d188b3afffe1fd9fc60bef352b0e4fc02 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 16:34:30 +0200 Subject: [PATCH 1862/2266] Use CI build name ref and date in Makefile if available --- .gitlab-ci.yml | 3 ++- Makefile | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d766e049f..a6b822dd5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -46,7 +46,8 @@ binaries: image: golang:latest script: - mkdir release - - echo "date $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> release/info + - export CI_BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + - echo "date $CI_BUILD_DATE" >> release/info - echo "commit $CI_BUILD_REF" >> release/info - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps diff --git a/Makefile b/Makefile index e4ea0c67e..5e6689fc9 100644 --- a/Makefile +++ b/Makefile @@ -10,9 +10,9 @@ GOBUILD = $(GOCMD) build PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn -GIT_BRANCH = `git rev-parse --abbrev-ref HEAD 2>/dev/null` -GIT_COMMIT = `git rev-parse HEAD 2>/dev/null` -BUILD_DATE = `date -u +%Y-%m-%dT%H:%M:%SZ` +GIT_BRANCH = $(or $(CI_BUILD_REF_NAME) ,`git rev-parse --abbrev-ref HEAD 2>/dev/null`) +GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) +BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" From b938766fff51c37c295c55167224e499a40bffcf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 16:50:29 +0200 Subject: [PATCH 1863/2266] Add selfupdate to ttn and ttnctl --- cmd/selfupdate.go | 22 ++++++ ttnctl/cmd/selfupdate.go | 22 ++++++ utils/version/version.go | 163 +++++++++++++++++++++++++++++++++++++++ vendor/vendor.json | 6 ++ 4 files changed, 213 insertions(+) create mode 100644 cmd/selfupdate.go create mode 100644 ttnctl/cmd/selfupdate.go create mode 100644 utils/version/version.go diff --git a/cmd/selfupdate.go b/cmd/selfupdate.go new file mode 100644 index 000000000..b681a36cf --- /dev/null +++ b/cmd/selfupdate.go @@ -0,0 +1,22 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/spf13/cobra" +) + +var selfUpdateCmd = &cobra.Command{ + Use: "selfupdate", + Short: "Update ttn to the latest version", + Long: `ttn selfupdate updates the current ttn to the latest version`, + Run: func(cmd *cobra.Command, args []string) { + version.Selfupdate(ctx, "ttn") + }, +} + +func init() { + RootCmd.AddCommand(selfUpdateCmd) +} diff --git a/ttnctl/cmd/selfupdate.go b/ttnctl/cmd/selfupdate.go new file mode 100644 index 000000000..7c754f965 --- /dev/null +++ b/ttnctl/cmd/selfupdate.go @@ -0,0 +1,22 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/spf13/cobra" +) + +var selfUpdateCmd = &cobra.Command{ + Use: "selfupdate", + Short: "Update ttnctl to the latest version", + Long: `ttnctl selfupdate updates the current ttnctl to the latest version`, + Run: func(cmd *cobra.Command, args []string) { + version.Selfupdate(ctx, "ttnctl") + }, +} + +func init() { + RootCmd.AddCommand(selfUpdateCmd) +} diff --git a/utils/version/version.go b/utils/version/version.go new file mode 100644 index 000000000..f6e3d2b80 --- /dev/null +++ b/utils/version/version.go @@ -0,0 +1,163 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package version + +import ( + "archive/tar" + "bytes" + "compress/gzip" + "errors" + "fmt" + "io" + "io/ioutil" + "net/http" + "os" + "runtime" + "strings" + "time" + + "github.com/apex/log" + "github.com/kardianos/osext" + "github.com/spf13/viper" +) + +// ErrNotFound indicates that a version was not found +var ErrNotFound = errors.New("Not Found") + +// ReleaseHost is where we publish our releases +const ReleaseHost = "ttnreleases.blob.core.windows.net/release" + +// Info contains version information +type Info struct { + Version string + Commit string + Date time.Time +} + +// GetLatestInfo gets information about the latest release for the current branch +func GetLatestInfo() (*Info, error) { + location := fmt.Sprintf("https://%s/%s/info", ReleaseHost, viper.GetString("gitBranch")) + resp, err := http.Get(location) + if err != nil { + return nil, err + } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Status %d was not OK", resp.StatusCode) + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + var info Info + for _, line := range strings.Split(string(body), "\n") { + infoLine := strings.SplitAfterN(line, " ", 2) + if len(infoLine) != 2 { + continue + } + switch strings.TrimSpace(infoLine[0]) { + case "version": + info.Version = infoLine[1] + case "commit": + info.Commit = infoLine[1] + case "date": + if date, err := time.Parse(time.RFC3339, infoLine[1]); err == nil { + info.Date = date + } + default: + fmt.Printf("Can't handle %s", infoLine[0]) + } + } + + return &info, nil +} + +// GetLatest gets the latest release binary +func GetLatest(binary string) ([]byte, error) { + exe := "" + if runtime.GOARCH == "windows" { + exe = ".exe" + } + filename := fmt.Sprintf("%s-%s-%s%s", binary, runtime.GOOS, runtime.GOARCH, exe) + location := fmt.Sprintf("https://%s/%s/%s.tar.gz", ReleaseHost, viper.GetString("gitBranch"), filename) + resp, err := http.Get(location) + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + gr, err := gzip.NewReader(bytes.NewBuffer(body)) + if err != nil { + return nil, err + } + archive, err := ioutil.ReadAll(gr) + if err != nil { + return nil, err + } + tr := tar.NewReader(bytes.NewBuffer(archive)) + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + return nil, err + } + if hdr.Name == filename { + binary, err := ioutil.ReadAll(tr) + if err != nil { + return nil, err + } + return binary, nil + } + } + return nil, ErrNotFound +} + +// Selfupdate runs a self-update for the current binary +func Selfupdate(ctx log.Interface, component string) { + info, err := GetLatestInfo() + if err != nil { + ctx.WithError(err).Fatal("Could not get version information from the server") + } + if date, err := time.Parse(time.RFC3339, viper.GetString("buildDate")); err == nil { + if date.After(info.Date) { + ctx.Infof("Your build is %s newer than the build on the server", date.Sub(info.Date)) + } else { + ctx.Infof("The build on the server is %s newer than yours", info.Date.Sub(date)) + } + } + if viper.GetString("gitCommit") == info.Commit { + ctx.Info("The git commit of the build on the server is the same as yours") + ctx.Info("Not proceeding with the update") + return + } + ctx.Infof("Downloading the latest %s...", component) + binary, err := GetLatest(component) + if err != nil { + ctx.WithError(err).Fatal("Could not download latest binary") + } + filename, err := osext.Executable() + if err != nil { + ctx.WithError(err).Fatal("Could not get path to local binary") + } + stat, err := os.Stat(filename) + if err != nil { + ctx.WithError(err).Fatal("Could not stat local binary") + } + ctx.Info("Replacing local binary...") + if err := ioutil.WriteFile(filename+".new", binary, stat.Mode()); err != nil { + ctx.WithError(err).Fatal("Could not write new binary to filesystem") + } + if err := os.Rename(filename, filename+".old"); err != nil { + ctx.WithError(err).Fatal("Could not rename binary") + } + if err := os.Rename(filename+".new", filename); err != nil { + ctx.WithError(err).Fatal("Could not rename binary") + } + ctx.Infof("Updated %s to the latest version.", component) +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 339eb7fab..a1d1b7c13 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -284,6 +284,12 @@ "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", "revisionTime": "2016-04-10T22:58:39Z" }, + { + "checksumSHA1": "g+afVQQVopBLiLB5pFZp/8s6aBs=", + "path": "github.com/kardianos/osext", + "revision": "c2c54e542fb797ad986b31721e1baedf214ca413", + "revisionTime": "2016-08-11T00:15:26Z" + }, { "checksumSHA1": "KQhA4EQp4Ldwj9nJZnEURlE6aQw=", "path": "github.com/kr/fs", From 641b609715a10e210026d61f9eddf13f296a29eb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 17:28:37 +0200 Subject: [PATCH 1864/2266] Update cli docs Resolves #283 [Skip CI] --- cmd/docs/README.md | 354 ++------------- cmd/docs/generate.go | 51 ++- ttnctl/cmd/docs/README.md | 861 +++++------------------------------- ttnctl/cmd/docs/generate.go | 43 +- 4 files changed, 227 insertions(+), 1082 deletions(-) diff --git a/cmd/docs/README.md b/cmd/docs/README.md index 345198bcf..4380b2d10 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -1,13 +1,8 @@ -## ttn +# API Reference -The Things Network's backend servers +The Things Network's backend servers. -### Synopsis - - -ttn launches The Things Network's backend servers - -### Options +**Options** ``` --auth-token string The JWT token to be used for the discovery server @@ -23,21 +18,13 @@ ttn launches The Things Network's backend servers --tls Use TLS ``` - ## ttn broker -The Things Network broker -### Synopsis +**Usage:** `ttn broker` -The Things Network broker - -``` -ttn broker -``` - -### Options +**Options** ``` --deduplication-delay int Deduplication delay (in ms) (default 200) @@ -48,98 +35,25 @@ ttn broker --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1902) ``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - -## ttn broker genkeys - -Generate keys and certificate - -### Synopsis - +### ttn broker genkeys ttn genkeys generates keys and a TLS certificate for this component -``` -ttn broker genkeys -``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - -## ttn broker register-prefix - -Register a prefix to this Broker - -### Synopsis +**Usage:** `ttn broker genkeys` +### ttn broker register-prefix ttn broker register prefix registers a prefix to this Broker -``` -ttn broker register-prefix [prefix ...] -``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - +**Usage:** `ttn broker register-prefix [prefix ...]` ## ttn discovery -The Things Network discovery - -### Synopsis -The Things Network discovery +**Usage:** `ttn discovery` -``` -ttn discovery -``` - -### Options +**Options** ``` --redis-address string Redis server and port (default "localhost:6379") @@ -147,38 +61,13 @@ ttn discovery --server-address string The IP address to listen for communication (default "0.0.0.0") --server-port int The port for communication (default 1900) ``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - ## ttn handler -The Things Network handler -### Synopsis +**Usage:** `ttn handler` -The Things Network handler - -``` -ttn handler -``` - -### Options +**Options** ``` --mqtt-broker string MQTT broker host and port (default "localhost:1883") @@ -191,68 +80,19 @@ ttn handler --server-port int The port for communication (default 1904) --ttn-broker string The ID of the TTN Broker as announced in the Discovery server (default "dev") ``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - -## ttn handler genkeys - -Generate keys and certificate - -### Synopsis - +### ttn handler genkeys ttn genkeys generates keys and a TLS certificate for this component -``` -ttn handler genkeys -``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - +**Usage:** `ttn handler genkeys` ## ttn networkserver -The Things Network networkserver - -### Synopsis -The Things Network networkserver +**Usage:** `ttn networkserver` -``` -ttn networkserver -``` - -### Options +**Options** ``` --net-id int LoRaWAN NetID (default 19) @@ -262,185 +102,51 @@ ttn networkserver --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1903) ``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - -## ttn networkserver authorize - -Generate a token that Brokers should use to connect - -### Synopsis - +### ttn networkserver authorize ttn networkserver authorize generates a token that Brokers should use to connect -``` -ttn networkserver authorize [id] -``` +**Usage:** `ttn networkserver authorize [id]` -### Options +**Options** ``` --valid int The number of days the token is valid ``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - -## ttn networkserver genkeys - -Generate keys and certificate - -### Synopsis - +### ttn networkserver genkeys ttn genkeys generates keys and a TLS certificate for this component -``` -ttn networkserver genkeys -``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - +**Usage:** `ttn networkserver genkeys` ## ttn router -The Things Network router - -### Synopsis -The Things Network router +**Usage:** `ttn router` -``` -ttn router -``` - -### Options +**Options** ``` --server-address string The IP address to listen for communication (default "0.0.0.0") --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1901) ``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - - -## ttn router genkeys - -Generate keys and certificate - -### Synopsis - +### ttn router genkeys ttn genkeys generates keys and a TLS certificate for this component -``` -ttn router genkeys -``` +**Usage:** `ttn router genkeys` -### Options inherited from parent commands +## ttn selfupdate -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` +ttn selfupdate updates the current ttn to the latest version +**Usage:** `ttn selfupdate` ## ttn version -Get build and version information - -### Synopsis - - ttn version gets the build and version information of ttn -``` -ttn version -``` - -### Options inherited from parent commands - -``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS -``` - +**Usage:** `ttn version` diff --git a/cmd/docs/generate.go b/cmd/docs/generate.go index bf0bcc638..ff3bc60c5 100644 --- a/cmd/docs/generate.go +++ b/cmd/docs/generate.go @@ -8,7 +8,6 @@ import ( "github.com/TheThingsNetwork/ttn/cmd" "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" ) type byName []*cobra.Command @@ -20,13 +19,37 @@ func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPa func main() { cmds := genCmdList(cmd.RootCmd) sort.Sort(byName(cmds)) + fmt.Println(`# API Reference + +The Things Network's backend servers. +`) for _, cmd := range cmds { - var buf bytes.Buffer - doc.GenMarkdownCustom(cmd, &buf, func(s string) string { - return "#" + strings.TrimSuffix(s, ".md") - }) - cleaned := strings.Split(buf.String(), "### SEE ALSO")[0] - fmt.Println(cleaned) + if cmd.CommandPath() == "ttn" { + fmt.Print("**Options**\n\n") + printOptions(cmd) + fmt.Println() + continue + } + + depth := len(strings.Split(cmd.CommandPath(), " ")) + + printHeader(depth, cmd.CommandPath()) + + fmt.Print(cmd.Long, "\n\n") + + if cmd.Runnable() { + fmt.Print("**Usage:** ", "`", cmd.UseLine(), "`", "\n\n") + } + + if cmd.HasLocalFlags() || cmd.HasPersistentFlags() { + fmt.Print("**Options**\n\n") + printOptions(cmd) + } + + if cmd.Example != "" { + fmt.Print("**Example**\n\n") + fmt.Print("```", "\n", cmd.Example, "```", "\n\n") + } } } @@ -40,3 +63,17 @@ func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { } return cmds } + +func printHeader(depth int, header string) { + fmt.Print(strings.Repeat("#", depth), " ", header, "\n\n") +} + +func printOptions(cmd *cobra.Command) { + fmt.Println("```") + var b bytes.Buffer + flags := cmd.NonInheritedFlags() + flags.SetOutput(&b) + flags.PrintDefaults() + fmt.Print(b.String()) + fmt.Println("```") +} diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index 869786ff2..ae6cb43e0 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -2,16 +2,7 @@ Control The Things Network from the command line. -## ttnctl applications - -Manage applications - -### Synopsis - - -ttnctl applications can be used to manage applications. - -### Options inherited from parent commands +**Options** ``` --config string config file (default is $HOME/.ttnctl.yaml) @@ -24,92 +15,44 @@ ttnctl applications can be used to manage applications. --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` +## ttnctl applications -## ttnctl applications add - -Add a new application - -### Synopsis +ttnctl applications can be used to manage applications. +### ttnctl applications add ttnctl applications add can be used to add a new application to your account. -``` -ttnctl applications add [AppID] [Description] -``` - -### Examples - -``` -$ ttnctl applications add test "Test application" - INFO Added Application - INFO Selected Current Application - -``` +**Usage:** `ttnctl applications add [AppID] [Description]` -### Options +**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) ``` - -### Options inherited from parent commands +**Example** ``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +$ ttnctl applications add test "Test application" + INFO Added Application + INFO Selected Current Application ``` - -## ttnctl applications delete - -Delete an application - -### Synopsis - +### ttnctl applications delete ttnctl devices delete can be used to delete an application. -``` -ttnctl applications delete -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl applications info - -Get information about an application - -### Synopsis +**Usage:** `ttnctl applications delete` +### ttnctl applications info ttnctl applications info can be used to info applications. -``` -ttnctl applications info [AppID] -``` +**Usage:** `ttnctl applications info [AppID]` -### Examples +**Example** ``` $ ttnctl applications info @@ -128,37 +71,15 @@ Access Keys: Collaborators: - Name: yourname Rights: settings, delete, collaborators - -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl applications list - -List applications - -### Synopsis - +### ttnctl applications list ttnctl applications list can be used to list applications. -``` -ttnctl applications list -``` +**Usage:** `ttnctl applications list` -### Examples +**Example** ``` $ ttnctl applications list @@ -166,38 +87,16 @@ $ ttnctl applications list ID Description EUIs Access Keys Collaborators 1 test Test application 1 1 1 - -``` - -### Options inherited from parent commands - ``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl applications pf - -Show the payload functions - -### Synopsis +### ttnctl applications pf ttnctl applications pf shows the payload functions for decoding, converting and validating binary payload. -``` -ttnctl applications pf -``` +**Usage:** `ttnctl applications pf` -### Examples +**Example** ``` $ ttnctl applications pf @@ -213,38 +112,16 @@ function Decoder(bytes) { INFO No converter function INFO No validator function INFO No encoder function - ``` -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl applications pf set - -Set payload functions of an application - -### Synopsis - +#### ttnctl applications pf set ttnctl pf set can be used to get or set payload functions of an application. The functions are read from the supplied file or from STDIN. -``` -ttnctl applications pf set [decoder/converter/validator/encoder] [file.js] -``` +**Usage:** `ttnctl applications pf set [decoder/converter/validator/encoder] [file.js]` -### Examples +**Example** ``` $ ttnctl applications pf set decoder @@ -268,111 +145,45 @@ function Decoder(bytes) { return decoded; } INFO Updated application AppID=test - -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl applications register - -Register this application with the handler - -### Synopsis - +### ttnctl applications register ttnctl register can be used to register this application with the handler. -``` -ttnctl applications register -``` +**Usage:** `ttnctl applications register` -### Examples +**Example** ``` $ ttnctl applications register INFO Discovering Handler... INFO Connecting with Handler... INFO Registered application AppID=test - -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl applications select - -select the application to use - -### Synopsis - +### ttnctl applications select ttnctl applications select can be used to select the application to use in next commands. -``` -ttnctl applications select -``` +**Usage:** `ttnctl applications select` -### Examples +**Example** ``` $ ttnctl applications select INFO Found one application "test", selecting that one. INFO Found one EUI "0000000000000000", selecting that one. INFO Updated configuration - -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl applications unregister - -Unregister this application from the handler - -### Synopsis - +### ttnctl applications unregister ttnctl unregister can be used to unregister this application from the handler. -``` -ttnctl applications unregister -``` +**Usage:** `ttnctl applications unregister` -### Examples +**Example** ``` $ ttnctl applications unregister @@ -381,67 +192,31 @@ Are you sure you want to unregister application test? INFO Discovering Handler... INFO Connecting with Handler... INFO Unregistered application AppID=test - ``` -### Options inherited from parent commands +## ttnctl config -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` +ttnctl config gets the config of ttnctl +**Usage:** `ttnctl config` ## ttnctl devices -Manage devices - -### Synopsis - - ttnctl devices can be used to manage devices. -### Options +**Options** ``` --app-eui string The app EUI to use --app-id string The app ID to use ``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl devices delete - -Delete a device - -### Synopsis - +### ttnctl devices delete ttnctl devices delete can be used to delete a device. -``` -ttnctl devices delete [Device ID] -``` +**Usage:** `ttnctl devices delete [Device ID]` -### Examples +**Example** ``` $ ttnctl devices delete test @@ -451,39 +226,20 @@ Are you sure you want to delete device test from application test? INFO Discovering Handler... INFO Connecting with Handler... INFO Deleted device AppID=test DevID=test - -``` - -### Options inherited from parent commands - ``` - --app-eui string The app EUI to use - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - -## ttnctl devices info +### ttnctl devices info -Get information about a device - -### Synopsis +ttnctl devices info can be used to get information about a device. +**Usage:** `ttnctl devices info [Device ID]` -ttnctl devices info can be used to get information about a device. +**Options** ``` -ttnctl devices info [Device ID] + --format string Formatting: hex/msb/lsb (default "hex") ``` - -### Examples +**Example** ``` $ ttnctl devices info test @@ -507,45 +263,15 @@ $ ttnctl devices info test FCntUp: 0 FCntDown: 0 Options: - ``` -### Options - -``` - --format string Formatting: hex/msb/lsb (default "hex") -``` - -### Options inherited from parent commands - -``` - --app-eui string The app EUI to use - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl devices list - -List al devices for the current application - -### Synopsis - +### ttnctl devices list ttnctl devices list can be used to list all devices for the current application. -``` -ttnctl devices list -``` +**Usage:** `ttnctl devices list` -### Examples +**Example** ``` $ ttnctl devices list @@ -557,39 +283,15 @@ DevID AppEUI DevEUI DevAddr Up/Down test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 INFO Listed 1 devices AppID=test - ``` -### Options inherited from parent commands - -``` - --app-eui string The app EUI to use - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl devices personalize - -Personalize a device - -### Synopsis - - -ttnctl devices personalize can be used to personalize a device (ABP). - -``` -ttnctl devices personalize [Device ID] [NwkSKey] [AppSKey] -``` - -### Examples +### ttnctl devices personalize + +ttnctl devices personalize can be used to personalize a device (ABP). + +**Usage:** `ttnctl devices personalize [Device ID] [NwkSKey] [AppSKey]` + +**Example** ``` $ ttnctl devices personalize test @@ -600,39 +302,15 @@ $ ttnctl devices personalize test INFO Connecting with Handler... Handler=eu.thethings.network:1904 INFO Requesting DevAddr for device... INFO Personalized device AppID=test AppSKey=D8DD37B4B709BA76C6FEC62CAD0CCE51 DevAddr=26001ADA DevID=test NwkSKey=3382A3066850293421ED8D392B9BF4DF - -``` - -### Options inherited from parent commands - ``` - --app-eui string The app EUI to use - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl devices register - -Register a new device - -### Synopsis +### ttnctl devices register ttnctl devices register can be used to register a new device. -``` -ttnctl devices register [Device ID] [DevEUI] [AppKey] -``` +**Usage:** `ttnctl devices register [Device ID] [DevEUI] [AppKey]` -### Examples +**Example** ``` $ ttnctl devices register test @@ -642,50 +320,15 @@ $ ttnctl devices register test INFO Discovering Handler... INFO Connecting with Handler... INFO Registered device AppEUI=70B3D57EF0000024 AppID=test AppKey=EBD2E2810A4307263FE5EF78E2EF589D DevEUI=0001D544B2936FCE DevID=test - -``` - -### Options inherited from parent commands - -``` - --app-eui string The app EUI to use - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl devices set - -Set properties of a device - -### Synopsis - +### ttnctl devices set ttnctl devices set can be used to set properties of a device. -``` -ttnctl devices set [Device ID] -``` - -### Examples - -``` -$ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 - INFO Using Application AppID=test - INFO Discovering Handler... - INFO Connecting with Handler... - INFO Updated device AppID=test DevID=test - -``` +**Usage:** `ttnctl devices set [Device ID]` -### Options +**Options** ``` --32-bit-fcnt Use 32 bit FCnt @@ -699,36 +342,41 @@ $ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 --fcnt-up int Set FCnt Up (default -1) --nwk-s-key string Set NwkSKey ``` - -### Options inherited from parent commands +**Example** ``` - --app-id string The app ID to use - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +$ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 + INFO Using Application AppID=test + INFO Discovering Handler... + INFO Connecting with Handler... + INFO Updated device AppID=test DevID=test ``` +## ttnctl discover -## ttnctl downlink +ttnctl discover is used to discover all network components + +**Usage:** `ttnctl discover` -Send a downlink message to a device +### ttnctl discover prefixes -### Synopsis +ttnctl discover prefixes is used to discover all prefixes announced by brokers +**Usage:** `ttnctl discover prefixes` + +## ttnctl downlink ttnctl downlink can be used to send a downlink message to a device. +**Usage:** `ttnctl downlink [DevID] [Payload]` + +**Options** + ``` -ttnctl downlink [DevID] [Payload] + --fport int FPort for downlink (default 1) + --json Provide the payload as JSON ``` - -### Examples +**Example** ``` $ ttnctl downlink test aabc @@ -740,215 +388,78 @@ $ ttnctl downlink test --json '{"led":"on"}' INFO Connecting to MQTT... INFO Connected to MQTT INFO Enqueued downlink AppID=test DevID=test - -``` - -### Options - -``` - --fport int FPort for downlink (default 1) - --json Provide the payload as JSON -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - ## ttnctl gateways -Manage gateways - -### Synopsis - - ttnctl gateways can be used to manage gateways. -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl gateways delete - -Delete a gateway - -### Synopsis - +### ttnctl gateways delete ttnctl gateways delete can be used to delete a gateway -``` -ttnctl gateways delete [GatewayID] -``` +**Usage:** `ttnctl gateways delete [GatewayID]` -### Examples +**Example** ``` $ ttnctl gateways delete test INFO Deleted gateway Gateway ID=test - ``` -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl gateways edit - -edit a gateway - -### Synopsis - +### ttnctl gateways edit ttnctl gateways edit can be used to edit settings of a gateway -``` -ttnctl gateways edit [GatewayID] -``` - -### Examples - -``` -$ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU - INFO Edited gateway Gateway ID=test +**Usage:** `ttnctl gateways edit [GatewayID]` -``` - -### Options +**Options** ``` --frequency-plan string The frequency plan to use on the gateway --location string The location of the gateway ``` - -### Options inherited from parent commands +**Example** ``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") +$ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU + INFO Edited gateway Gateway ID=test ``` - -## ttnctl gateways list - -List your gateways - -### Synopsis - +### ttnctl gateways list ttnctl gateways list can be used to list the gateways you have access to -``` -ttnctl gateways list -``` +**Usage:** `ttnctl gateways list` -### Examples +**Example** ``` $ ttnctl gateways list ID Activated Frequency Plan Coordinates 1 test true US (52.3740, 4.8896) - ``` -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl gateways register - -Register a gateway - -### Synopsis - +### ttnctl gateways register ttnctl gateways register can be used to register a gateway -``` -ttnctl gateways register [GatewayID] [FrequencyPlan] [Location] -``` +**Usage:** `ttnctl gateways register [GatewayID] [FrequencyPlan] [Location]` -### Examples +**Example** ``` $ ttnctl gateways register test US 52.37403,4.88968 INFO Registered gateway Gateway ID=test - ``` -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl gateways status - -Get status of a gateway - -### Synopsis - +### ttnctl gateways status ttnctl gateways status can be used to get status of gateways. -``` -ttnctl gateways status [gatewayID] -``` +**Usage:** `ttnctl gateways status [gatewayID]` -### Examples +**Example** ``` $ ttnctl gateways status test @@ -964,64 +475,27 @@ $ ttnctl gateways status test Rtt: not available Rx: (in: 0; ok: 0) Tx: (in: 0; ok: 0) - ``` -### Options inherited from parent commands +## ttnctl selfupdate -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` +ttnctl selfupdate updates the current ttnctl to the latest version +**Usage:** `ttnctl selfupdate` ## ttnctl subscribe -Subscribe to events for this application - -### Synopsis - - ttnctl subscribe can be used to subscribe to events for this application. -``` -ttnctl subscribe -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - +**Usage:** `ttnctl subscribe` ## ttnctl user -Show the current user - -### Synopsis - - ttnctl user shows the current logged on user's profile -``` -ttnctl user -``` +**Usage:** `ttnctl user` -### Examples +**Example** ``` $ ttnctl user @@ -1032,37 +506,15 @@ $ ttnctl user Email: your@email.org INFO Login credentials valid until Sep 20 09:04:12 - -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl user login - -Log in with your TTN account - -### Synopsis - +### ttnctl user login ttnctl user login allows you to log in to your TTN account. -``` -ttnctl user login [access code] -``` +**Usage:** `ttnctl user login [access code]` -### Examples +**Example** ``` First get an access code from your TTN profile by going to @@ -1070,111 +522,32 @@ https://account.thethingsnetwork.org and clicking "ttnctl access code". $ ttnctl user login [paste the access code you requested above] INFO Successfully logged in as yourname (your@email.org) - -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` - -## ttnctl user logout - -Logout the current user - -### Synopsis - +### ttnctl user logout ttnctl user logout logs out the current user -``` -ttnctl user logout -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - -## ttnctl user register - -Register - -### Synopsis +**Usage:** `ttnctl user logout` +### ttnctl user register ttnctl user register allows you to register a new user in the account server -``` -ttnctl user register [username] [e-mail] -``` +**Usage:** `ttnctl user register [username] [e-mail]` -### Examples +**Example** ``` $ ttnctl user register yourname your@email.org Password: INFO Registered user WARN You might have to verify your email before you can login - ``` -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - - ## ttnctl version -Get build and version information - -### Synopsis - - ttnctl version gets the build and version information of ttnctl -``` -ttnctl version -``` - -### Options inherited from parent commands - -``` - --config string config file (default is $HOME/.ttnctl.yaml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") -``` - +**Usage:** `ttnctl version` diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go index ef40e9a9a..9e7fdbb27 100644 --- a/ttnctl/cmd/docs/generate.go +++ b/ttnctl/cmd/docs/generate.go @@ -8,7 +8,6 @@ import ( "github.com/TheThingsNetwork/ttn/ttnctl/cmd" "github.com/spf13/cobra" - "github.com/spf13/cobra/doc" ) type byName []*cobra.Command @@ -26,15 +25,31 @@ Control The Things Network from the command line. `) for _, cmd := range cmds { if cmd.CommandPath() == "ttnctl" { + fmt.Print("**Options**\n\n") + printOptions(cmd) + fmt.Println() continue } - var buf bytes.Buffer - doc.GenMarkdownCustom(cmd, &buf, func(s string) string { - return "#" + strings.TrimSuffix(s, ".md") - }) - cleaned := strings.Split(buf.String(), "### SEE ALSO")[0] - fmt.Println(cleaned) + depth := len(strings.Split(cmd.CommandPath(), " ")) + + printHeader(depth, cmd.CommandPath()) + + fmt.Print(cmd.Long, "\n\n") + + if cmd.Runnable() { + fmt.Print("**Usage:** ", "`", cmd.UseLine(), "`", "\n\n") + } + + if cmd.HasLocalFlags() || cmd.HasPersistentFlags() { + fmt.Print("**Options**\n\n") + printOptions(cmd) + } + + if cmd.Example != "" { + fmt.Print("**Example**\n\n") + fmt.Print("```", "\n", cmd.Example, "```", "\n\n") + } } } @@ -48,3 +63,17 @@ func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { } return cmds } + +func printHeader(depth int, header string) { + fmt.Print(strings.Repeat("#", depth), " ", header, "\n\n") +} + +func printOptions(cmd *cobra.Command) { + fmt.Println("```") + var b bytes.Buffer + flags := cmd.NonInheritedFlags() + flags.SetOutput(&b) + flags.PrintDefaults() + fmt.Print(b.String()) + fmt.Println("```") +} From 2af4ad8276bc5e2056fa5727763a7d720254ba20 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 17:47:51 +0200 Subject: [PATCH 1865/2266] Make mqtt publish and subscribe QoS configurable --- mqtt/client.go | 9 ++++++--- mqtt/client_test.go | 4 ++-- mqtt/downlink.go | 2 +- mqtt/uplink.go | 4 ++-- mqtt/uplink_test.go | 2 +- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/mqtt/client.go b/mqtt/client.go index 0cb68cb99..10d791206 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -16,7 +16,10 @@ import ( // 0: The broker/client will deliver the message once, with no confirmation. // 1: The broker/client will deliver the message at least once, with confirmation required. // 2: The broker/client will deliver the message exactly once by using a four step handshake. -const QoS = 0x00 +var ( + PublishQoS byte = 0x00 + SubscribeQoS byte = 0x00 +) // Client connects to the MQTT server and can publish/subscribe on uplink, downlink and activations from devices type Client interface { @@ -232,12 +235,12 @@ func (c *DefaultClient) Connect() error { } func (c *DefaultClient) publish(topic string, msg []byte) Token { - return c.mqtt.Publish(topic, QoS, false, msg) + return c.mqtt.Publish(topic, PublishQoS, false, msg) } func (c *DefaultClient) subscribe(topic string, handler MQTT.MessageHandler) Token { c.subscriptions[topic] = handler - return c.mqtt.Subscribe(topic, QoS, handler) + return c.mqtt.Subscribe(topic, SubscribeQoS, handler) } func (c *DefaultClient) unsubscribe(topic string) Token { diff --git a/mqtt/client_test.go b/mqtt/client_test.go index cad99acc4..c5700e53d 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -135,9 +135,9 @@ func TestRandomTopicPublish(t *testing.T) { c.Connect() defer c.Disconnect() - subToken := c.(*DefaultClient).mqtt.Subscribe("randomtopic", QoS, nil) + subToken := c.(*DefaultClient).mqtt.Subscribe("randomtopic", SubscribeQoS, nil) waitForOK(subToken, a) - pubToken := c.(*DefaultClient).mqtt.Publish("randomtopic", QoS, false, []byte{0x00}) + pubToken := c.(*DefaultClient).mqtt.Publish("randomtopic", PublishQoS, false, []byte{0x00}) waitForOK(pubToken, a) <-time.After(50 * time.Millisecond) diff --git a/mqtt/downlink.go b/mqtt/downlink.go index 3277a76f8..9d1b7c8a0 100644 --- a/mqtt/downlink.go +++ b/mqtt/downlink.go @@ -22,7 +22,7 @@ func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} } - return c.mqtt.Publish(topic.String(), QoS, false, msg) + return c.publish(topic.String(), msg) } // SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device diff --git a/mqtt/uplink.go b/mqtt/uplink.go index 82ee3a207..f061c2054 100644 --- a/mqtt/uplink.go +++ b/mqtt/uplink.go @@ -22,7 +22,7 @@ func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} } - return c.mqtt.Publish(topic.String(), QoS, false, msg) + return c.publish(topic.String(), msg) } // PublishUplinkFields publishes uplink fields to MQTT @@ -33,7 +33,7 @@ func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields m for field, value := range flattenedFields { topic := DeviceTopic{appID, devID, DeviceUplink, field} pld, _ := json.Marshal(value) - token := c.mqtt.Publish(topic.String(), QoS, false, pld) + token := c.publish(topic.String(), pld) tokens = append(tokens, token) } t := newToken() diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go index fb5ec91d8..edc49a04d 100644 --- a/mqtt/uplink_test.go +++ b/mqtt/uplink_test.go @@ -49,7 +49,7 @@ func TestPublishUplinkFields(t *testing.T) { close(subChan) waitChan <- true }() - subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", QoS, func(_ MQTT.Client, msg MQTT.Message) { + subToken := c.(*DefaultClient).mqtt.Subscribe("fields-app/devices/fields-dev/up/#", SubscribeQoS, func(_ MQTT.Client, msg MQTT.Message) { switch strings.TrimPrefix(msg.Topic(), "fields-app/devices/fields-dev/up/") { case "battery": a.So(string(msg.Payload()), ShouldEqual, "90") From 733a1c27445f0fcba35164100034f2a8485637f2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 3 Oct 2016 20:25:04 +0200 Subject: [PATCH 1866/2266] Clear join nonces when AppKey is changed Signed-off-by: Hylke Visser --- core/handler/manager_server.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 726794b1e..d29f70529 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -131,6 +131,10 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } if lorawan.AppKey != nil { updated.AppKey = *lorawan.AppKey + if dev != nil && dev.AppKey != *lorawan.AppKey { // When the AppKey of an existing device is changed + updated.UsedAppNonces = []device.AppNonce{} + updated.UsedDevNonces = []device.DevNonce{} + } } nsUpdated := &pb_lorawan.Device{ From d723960c1f8b2a56fd1ac244431accd3401f20a0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 4 Oct 2016 12:29:14 +0200 Subject: [PATCH 1867/2266] Update Makefile --- .gitlab-ci.yml | 12 ++-- .travis.yml | 1 - Makefile | 180 ++++++++++++++++++++++++------------------------- 3 files changed, 93 insertions(+), 100 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index a6b822dd5..674b4c917 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,12 +51,12 @@ binaries: - echo "commit $CI_BUILD_REF" >> release/info - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps - - TARGET_PLATFORM=linux-386 make build - - TARGET_PLATFORM=linux-amd64 make build - - TARGET_PLATFORM=linux-arm make build - - TARGET_PLATFORM=darwin-amd64 make build - - TARGET_PLATFORM=windows-386 make build - - TARGET_PLATFORM=windows-amd64 make build + - GOOS=linux GOARCH=386 make build + - GOOS=linux GOARCH=amd64 make build + - GOOS=linux GOARCH=arm make build + - GOOS=darwin GOARCH=amd64 make build + - GOOS=windows GOARCH=386 make build + - GOOS=windows GOARCH=amd64 make build - popd - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/release/* release/ artifacts: diff --git a/.travis.yml b/.travis.yml index fe66db49e..6e89633bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,5 +22,4 @@ script: - make vet after_success: - - make cover - make coveralls diff --git a/Makefile b/Makefile index 5e6689fc9..a33e869c7 100644 --- a/Makefile +++ b/Makefile @@ -1,134 +1,128 @@ SHELL = bash -export GOOS=$(or $(word 1,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOOS`")) -export GOARCH=$(or $(word 2,$(subst -, ,${TARGET_PLATFORM})), $(shell echo "`go env GOARCH`")) -export GOEXE=$(shell echo "`GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE`") -export CGO_ENABLED=0 - -GOCMD = go -GOBUILD = $(GOCMD) build - -PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn +# Environment GIT_BRANCH = $(or $(CI_BUILD_REF_NAME) ,`git rev-parse --abbrev-ref HEAD 2>/dev/null`) GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) -LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" +# All -select_pkgs = govendor list --no-status +local -coverage_pkgs = $(select_pkgs) | grep -vE 'ttn/api|ttn/cmd|ttn/ttnctl' +.PHONY: all build-deps deps dev-deps protos-clean protos mocks test cover-clean cover-deps cover coveralls fmt vet ttn ttnctl build link docs clean docker -RELEASE_DIR ?= release -COVER_FILE = coverage.out -TEMP_COVER_DIR ?= .cover +all: deps build -ttnpkg = ttn-$(GOOS)-$(GOARCH) -ttnctlpkg = ttnctl-$(GOOS)-$(GOARCH) - -ttnbin = $(ttnpkg)$(GOEXE) -ttnctlbin = $(ttnctlpkg)$(GOEXE) - -.PHONY: all clean build-deps deps dev-deps cover-deps vendor update-vendor proto test fmt vet cover coveralls docs build install docker package - -all: clean deps build package +# Deps build-deps: - $(GOCMD) get -u "github.com/kardianos/govendor" + @command -v govendor > /dev/null || go get "github.com/kardianos/govendor" deps: build-deps govendor sync dev-deps: deps - $(GOCMD) get -u -v github.com/gogo/protobuf/protoc-gen-gofast - $(GOCMD) get -u -v github.com/golang/mock/gomock - $(GOCMD) get -u -v github.com/golang/mock/mockgen - $(GOCMD) get -u -v github.com/ddollar/forego + @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast + @command -v mockgen > /dev/null || go get github.com/golang/mock/mockgen + @command -v forego > /dev/null || go get github.com/ddollar/forego -cover-deps: - if ! $(GOCMD) get github.com/golang/tools/cmd/cover; then $(GOCMD) get golang.org/x/tools/cmd/cover; fi - $(GOCMD) get github.com/mattn/goveralls - -vendor: build-deps - govendor add +external - -update-vendor: build-deps - govendor fetch +external - -proto: - @$(PROTOC)/api/*.proto - @$(PROTOC)/api/protocol/protocol.proto - @$(PROTOC)/api/protocol/**/*.proto - @$(PROTOC)/api/gateway/gateway.proto - @$(PROTOC)/api/router/router.proto - @$(PROTOC)/api/broker/broker.proto - @$(PROTOC)/api/handler/handler.proto - @$(PROTOC)/api/networkserver/networkserver.proto - @$(PROTOC)/api/discovery/discovery.proto - @$(PROTOC)/api/noc/noc.proto +# Protobuf + +PROTO_FILES = $(shell find api -name "*.proto" -and -not -name ".git") +COMPILED_PROTO_FILES = $(patsubst api%.proto, api%.pb.go, $(PROTO_FILES)) +PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn + +protos-clean: + rm -f $(COMPILED_PROTO_FILES) + +protos: $(COMPILED_PROTO_FILES) + +api/%.pb.go: api/%.proto + $(PROTOC)/"$<" + +# Mocks mocks: mockgen -source=./api/networkserver/networkserver.pb.go -package networkserver NetworkServerClient > api/networkserver/networkserver_mock.go - mockgen -source=./api/discovery/client.go -package discovery NetworkServerClient > api/discovery/client_mock.go + mockgen -source=./api/discovery/client.go -package discovery Client > api/discovery/client_mock.go + +# Go Test + +GO_FILES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor") +GO_PACKAGES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor" | sed 's:/[^/]*$$::' | sort | uniq) +GO_TEST_PACKAGES = $(shell find . -name "*_test.go" | grep -vE ".git|.env|vendor" | sed 's:/[^/]*$$::' | sort | uniq) +GO_COVER_PACKAGES = $(shell find . -name "*_test.go" | grep -vE ".git|.env|vendor|ttnctl|cmd|api" | sed 's:/[^/]*$$::' | sort | uniq) + +GO_COVER_FILE ?= coverage.out +GO_COVER_DIR ?= .cover +GO_COVER_FILES = $(patsubst ./%, $(GO_COVER_DIR)/%.out, $(shell echo "$(GO_COVER_PACKAGES)")) -test: - $(select_pkgs) | xargs $(GOCMD) test +test: $(GO_FILES) + go test $(GO_TEST_PACKAGES) + +cover-clean: + rm -rf $(GO_COVER_DIR) $(GO_COVER_FILE) + +cover-deps: + @command -v goveralls > /dev/null || go get github.com/mattn/goveralls + +cover: $(GO_COVER_FILE) + +$(GO_COVER_FILE): cover-clean $(GO_COVER_FILES) + echo "mode: set" > $(GO_COVER_FILE) + cat $(GO_COVER_FILES) | grep -v "mode: set" | sort >> $(GO_COVER_FILE) + +$(GO_COVER_DIR)/%.out: % + @mkdir -p "$(GO_COVER_DIR)/$<" + go test -cover -coverprofile="$@" "./$<" + +coveralls: cover-deps $(GO_COVER_FILE) + goveralls -coverprofile=$(GO_COVER_FILE) -service=travis-ci -repotoken $$COVERALLS_TOKEN fmt: - [[ -z "`$(select_pkgs) | xargs $(GOCMD) fmt | tee -a /dev/stderr`" ]] + [[ -z "`echo "$(GO_PACKAGES)" | xargs go fmt | tee -a /dev/stderr`" ]] vet: - $(select_pkgs) | xargs $(GOCMD) vet + echo $(GO_PACKAGES) | xargs go vet -cover: - mkdir $(TEMP_COVER_DIR) - for pkg in $$($(coverage_pkgs)); do profile="$(TEMP_COVER_DIR)/$$(echo $$pkg | grep -oE 'ttn/.*' | sed 's/\///g').cover"; $(GOCMD) test -cover -coverprofile=$$profile $$pkg; done - echo "mode: set" > $(COVER_FILE) && cat $(TEMP_COVER_DIR)/*.cover | grep -v mode: | sort -r | awk '{if($$1 != last) {print $$0;last=$$1}}' >> $(COVER_FILE) - rm -r $(TEMP_COVER_DIR) +# Go Build -coveralls: - $$GOPATH/bin/goveralls -coverprofile=$(COVER_FILE) -service=travis-ci -repotoken $$COVERALLS_TOKEN - -clean: - [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] - ([ -d $(TEMP_COVER_DIR) ] && rm -rf $(TEMP_COVER_DIR)) || [ ! -d $(TEMP_COVER_DIR) ] - ([ -f $(COVER_FILE) ] && rm $(COVER_FILE)) || [ ! -d $(COVER_FILE) ] +RELEASE_DIR ?= release +GOOS ?= $(shell go env GOOS) +GOARCH ?= $(shell go env GOARCH) +GO = GOOS=$(GOOS) GOARCH=$(GOARCH) go +GOEXE = $(shell $(GO) env GOEXE) -docs: - cd cmd/docs && HOME='$$HOME' $(GOCMD) run generate.go > README.md - cd ttnctl/cmd/docs && HOME='$$HOME' $(GOCMD) run generate.go > README.md +LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" +GOBUILD = CGO_ENABLED=0 $(GO) build -a -installsuffix cgo ${LDFLAGS} -build: $(RELEASE_DIR)/$(ttnbin) $(RELEASE_DIR)/$(ttnctlbin) +ttn: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) -install: - $(GOCMD) install -a -installsuffix cgo ${LDFLAGS} . ./ttnctl +$(RELEASE_DIR)/ttn-%: $(GO_FILES) + $(GOBUILD) -o "$@" ./main.go -docker: TARGET_PLATFORM = linux-amd64 -docker: clean $(RELEASE_DIR)/$(ttnbin) - docker build -t thethingsnetwork/ttn -f Dockerfile . +ttnctl: $(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) -package: $(RELEASE_DIR)/$(ttnpkg).zip $(RELEASE_DIR)/$(ttnpkg).tar.gz $(RELEASE_DIR)/$(ttnpkg).tar.xz $(RELEASE_DIR)/$(ttnctlpkg).zip $(RELEASE_DIR)/$(ttnctlpkg).tar.gz $(RELEASE_DIR)/$(ttnctlpkg).tar.xz +$(RELEASE_DIR)/ttnctl-%: $(GO_FILES) + $(GOBUILD) -o "$@" ./ttnctl/main.go -$(RELEASE_DIR)/$(ttnbin): - $(GOBUILD) -a -installsuffix cgo ${LDFLAGS} -o $(RELEASE_DIR)/$(ttnbin) ./main.go +build: ttn ttnctl -$(RELEASE_DIR)/$(ttnpkg).zip: $(RELEASE_DIR)/$(ttnbin) - cd $(RELEASE_DIR) && zip -q $(ttnpkg).zip $(ttnbin) +GOBIN ?= $(GOPATH)/bin -$(RELEASE_DIR)/$(ttnpkg).tar.gz: $(RELEASE_DIR)/$(ttnbin) - cd $(RELEASE_DIR) && tar -czf $(ttnpkg).tar.gz $(ttnbin) +link: ttn + rm $(GOBIN)/ttn && ln -s $(PWD)/$(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttn + rm $(GOBIN)/ttnctl && ln -s $(PWD)/$(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttnctl -$(RELEASE_DIR)/$(ttnpkg).tar.xz: $(RELEASE_DIR)/$(ttnbin) - cd $(RELEASE_DIR) && tar -cJf $(ttnpkg).tar.xz $(ttnbin) +# Documentation -$(RELEASE_DIR)/$(ttnctlbin): - $(GOBUILD) -a -installsuffix cgo ${LDFLAGS} -o $(RELEASE_DIR)/$(ttnctlbin) ./ttnctl/main.go +docs: + cd cmd/docs && HOME='$$HOME' go run generate.go > README.md + cd ttnctl/cmd/docs && HOME='$$HOME' go run generate.go > README.md -$(RELEASE_DIR)/$(ttnctlpkg).zip: $(RELEASE_DIR)/$(ttnctlbin) - cd $(RELEASE_DIR) && zip -q $(ttnctlpkg).zip $(ttnctlbin) +# Clean -$(RELEASE_DIR)/$(ttnctlpkg).tar.gz: $(RELEASE_DIR)/$(ttnctlbin) - cd $(RELEASE_DIR) && tar -czf $(ttnctlpkg).tar.gz $(ttnctlbin) +clean: + [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] -$(RELEASE_DIR)/$(ttnctlpkg).tar.xz: $(RELEASE_DIR)/$(ttnctlbin) - cd $(RELEASE_DIR) && tar -cJf $(ttnctlpkg).tar.xz $(ttnctlbin) +docker: $(RELEASE_DIR)/ttn-linux-amd64 + docker build -t thethingsnetwork/ttn -f Dockerfile . From 54db85f589d02509c2d640909d06110bfc73e1d4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 4 Oct 2016 13:13:02 +0200 Subject: [PATCH 1868/2266] Update README and Brewfile [Skip CI] --- Brewfile | 4 ++-- README.md | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Brewfile b/Brewfile index e8907f25f..42c88f091 100644 --- a/Brewfile +++ b/Brewfile @@ -1,6 +1,6 @@ tap 'homebrew/bundle' brew 'go' -brew 'mosquitto', service_restart: true +brew 'mosquitto', restart_service: true brew 'protobuf' -brew 'redis', service_restart: true +brew 'redis', restart_service: true diff --git a/README.md b/README.md index 98648eeb7..611ffb52c 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,7 @@ When you get started with The Things Network, you'll probably have some question 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. Make sure you have [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) **installed** and **running**. - If you're on a Mac, just run: - * `brew bundle` - * `brew services start mosquitto` - * `brew services start redis` + If you're on a Mac, just run `brew bundle`. ## Set up The Things Network's backend for Development @@ -39,20 +36,23 @@ When you get started with The Things Network, you'll probably have some question ## Build, install and run The Things Network's backend locally -1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` by copying that file to `~/.ttnctl.yaml`. -2. Run `make install` -3. Run `forego start` +Additional to setting up the backend for development, you should do the following to build, install and run the backend locally. + +1. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. +2. Run `make build` to build both `ttn` and `ttnctl` from source. +2. Run `make link` to link them to `$GOPATH/bin/` (In order to run the commands, you should have `export PATH="$GOPATH/bin:$PATH"` in your profile). +3. Run `forego start` to start all backend services at the same time. Make sure that Redis and Mosquitto **are running** on your machine. 4. First time only (or when Redis is flushed): * Run `ttn broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` - * Restart the backend + * Restart the backend services ## Build and run The Things Network's backend in Docker -1. Configure your `ttnctl` with the settings in `.env/ttnctl.yaml.dev-example` by copying that file to `~/.ttnctl.yaml`. +1. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. 2. Add the following line to your `/etc/hosts` file: `127.0.0.1 router handler` -3. Run `make install docker` -4. Run `docker-compose up` +3. Run `make docker` to build the docker image +4. Run `docker-compose up` to start all backend services in Docker. Make sure that Redis and Mosquitto **are not running** on your local machine. 5. First time only (or when Redis is flushed): * Run `docker-compose run broker broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` * Restart the backend From 6ce1e06b09726b1429dec08f5abed87b940ca7a0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 4 Oct 2016 13:48:23 +0200 Subject: [PATCH 1869/2266] Check version online in version cmd --- ttnctl/cmd/version.go | 46 ++++++++++++++++++++++++++++++++++------ utils/version/version.go | 30 +++++++++++++++++++------- 2 files changed, 62 insertions(+), 14 deletions(-) diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index b09d06afd..8e6da0000 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -4,21 +4,55 @@ package cmd import ( - "github.com/apex/log" + "time" + + "github.com/TheThingsNetwork/ttn/utils/version" "github.com/spf13/cobra" "github.com/spf13/viper" ) +const unknown = "unknown" + var versionCmd = &cobra.Command{ Use: "version", Short: "Get build and version information", Long: `ttnctl version gets the build and version information of ttnctl`, Run: func(cmd *cobra.Command, args []string) { - ctx.WithFields(log.Fields{ - "Branch": viper.GetString("gitBranch"), - "Commit": viper.GetString("gitCommit")[:7], - "BuildDate": viper.GetString("buildDate"), - }).Infof("You are running version %s of ttnctl.", viper.GetString("version")) + ctx = ctx.WithField("Version", viper.GetString("version")) + + gitBranch := viper.GetString("gitBranch") + ctx = ctx.WithField("Branch", gitBranch) + + gitCommit := viper.GetString("gitCommit") + ctx = ctx.WithField("Commit", gitCommit[:7]) + + buildDate := viper.GetString("buildDate") + ctx = ctx.WithField("BuildDate", buildDate) + + if gitBranch == unknown || gitCommit == unknown || buildDate == unknown { + ctx.Warn("This is not an official ttnctl build") + } + + if gitBranch != unknown { + if version, err := version.GetLatestInfo(); err == nil { + if version.Commit == gitCommit { + ctx.Info("This is an up-to-date ttnctl build") + } else { + if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { + if buildDate.Before(version.Date) { + ctx.Warn("This is not an up-to-date ttnctl build") + ctx.Warnf("The newest build is %s newer.", version.Date.Sub(buildDate)) + } else { + ctx.Warn("This is not an official ttnctl build") + } + } + } + } else { + ctx.Warn("Could not get latest version information") + } + } + + ctx.Info("") }, } diff --git a/utils/version/version.go b/utils/version/version.go index f6e3d2b80..b9369670a 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -120,22 +120,36 @@ func GetLatest(binary string) ([]byte, error) { // Selfupdate runs a self-update for the current binary func Selfupdate(ctx log.Interface, component string) { + if viper.GetString("gitBranch") == "unknown" { + ctx.Infof("You are not using an official %s build. Not proceeding with the update", component) + return + } + info, err := GetLatestInfo() if err != nil { ctx.WithError(err).Fatal("Could not get version information from the server") } - if date, err := time.Parse(time.RFC3339, viper.GetString("buildDate")); err == nil { - if date.After(info.Date) { - ctx.Infof("Your build is %s newer than the build on the server", date.Sub(info.Date)) - } else { - ctx.Infof("The build on the server is %s newer than yours", info.Date.Sub(date)) - } - } + if viper.GetString("gitCommit") == info.Commit { ctx.Info("The git commit of the build on the server is the same as yours") ctx.Info("Not proceeding with the update") return } + + if date, err := time.Parse(time.RFC3339, viper.GetString("buildDate")); err == nil { + if date.Equal(info.Date) { + ctx.Infof("You have the latest version of %s", component) + ctx.Info("Nothing to update") + return + } + if date.After(info.Date) { + ctx.Infof("Your build is %s newer than the build on the server", date.Sub(info.Date)) + ctx.Info("Not proceeding with the update") + return + } + ctx.Infof("The build on the server is %s newer than yours", info.Date.Sub(date)) + } + ctx.Infof("Downloading the latest %s...", component) binary, err := GetLatest(component) if err != nil { @@ -159,5 +173,5 @@ func Selfupdate(ctx log.Interface, component string) { if err := os.Rename(filename+".new", filename); err != nil { ctx.WithError(err).Fatal("Could not rename binary") } - ctx.Infof("Updated %s to the latest version.", component) + ctx.Infof("Updated %s to the latest version", component) } From c266fc36b5c24d3f52616b07e007082c4a0422dd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 4 Oct 2016 16:32:10 +0200 Subject: [PATCH 1870/2266] Don't slice gitCommit in version cmd --- cmd/version.go | 47 ++++++++++++++++++++++++++++++++++++------- ttnctl/cmd/version.go | 2 +- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index 9c6c74e1c..2f4531f69 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -4,22 +4,55 @@ package cmd import ( - "github.com/apex/log" + "time" + + "github.com/TheThingsNetwork/ttn/utils/version" "github.com/spf13/cobra" "github.com/spf13/viper" ) -// versionCmd represents the version command +const unknown = "unknown" + var versionCmd = &cobra.Command{ Use: "version", Short: "Get build and version information", Long: `ttn version gets the build and version information of ttn`, Run: func(cmd *cobra.Command, args []string) { - ctx.WithFields(log.Fields{ - "Branch": viper.GetString("gitBranch"), - "Commit": viper.GetString("gitCommit")[:7], - "BuildDate": viper.GetString("buildDate"), - }).Infof("You are running version %s of ttn.", viper.GetString("version")) + ctx = ctx.WithField("Version", viper.GetString("version")) + + gitBranch := viper.GetString("gitBranch") + ctx = ctx.WithField("Branch", gitBranch) + + gitCommit := viper.GetString("gitCommit") + ctx = ctx.WithField("Commit", gitCommit) + + buildDate := viper.GetString("buildDate") + ctx = ctx.WithField("BuildDate", buildDate) + + if gitBranch == unknown || gitCommit == unknown || buildDate == unknown { + ctx.Warn("This is not an official ttn build") + } + + if gitBranch != unknown { + if version, err := version.GetLatestInfo(); err == nil { + if version.Commit == gitCommit { + ctx.Info("This is an up-to-date ttn build") + } else { + if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { + if buildDate.Before(version.Date) { + ctx.Warn("This is not an up-to-date ttn build") + ctx.Warnf("The newest build is %s newer.", version.Date.Sub(buildDate)) + } else { + ctx.Warn("This is not an official ttn build") + } + } + } + } else { + ctx.Warn("Could not get latest version information") + } + } + + ctx.Info("") }, } diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index 8e6da0000..4509b626b 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -24,7 +24,7 @@ var versionCmd = &cobra.Command{ ctx = ctx.WithField("Branch", gitBranch) gitCommit := viper.GetString("gitCommit") - ctx = ctx.WithField("Commit", gitCommit[:7]) + ctx = ctx.WithField("Commit", gitCommit) buildDate := viper.GetString("buildDate") ctx = ctx.WithField("BuildDate", buildDate) From 112958cc119b89c9f8f99e236cfcde8f804d6de4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 4 Oct 2016 17:07:12 +0200 Subject: [PATCH 1871/2266] Exclude selfupdate from Homebrew builds --- cmd/selfupdate.go | 2 ++ ttnctl/cmd/selfupdate.go | 2 ++ 2 files changed, 4 insertions(+) diff --git a/cmd/selfupdate.go b/cmd/selfupdate.go index b681a36cf..ee2c15267 100644 --- a/cmd/selfupdate.go +++ b/cmd/selfupdate.go @@ -1,3 +1,5 @@ +// +build !homebrew + // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. diff --git a/ttnctl/cmd/selfupdate.go b/ttnctl/cmd/selfupdate.go index 7c754f965..c49d46cdf 100644 --- a/ttnctl/cmd/selfupdate.go +++ b/ttnctl/cmd/selfupdate.go @@ -1,3 +1,5 @@ +// +build !homebrew + // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. From 97e9264603dac18333b7f459ed6e6d8319cf9fb4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 5 Oct 2016 09:23:50 +0200 Subject: [PATCH 1872/2266] Update vendor.json --- vendor/vendor.json | 350 ++++++++++++++++++++++++--------------------- 1 file changed, 183 insertions(+), 167 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index a1d1b7c13..a1ee9c8ae 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -11,56 +11,56 @@ { "checksumSHA1": "waq4N4OCUwSpA4kjU3abu//ms68=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { - "checksumSHA1": "PFL4Ax29rfb/zBV3xOD56m/qUCQ=", + "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { - "checksumSHA1": "M36IFas2KacxiVfNI3McxRzYcGA=", + "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { - "checksumSHA1": "id8rTAsVn0DvpHFJmpRSdpEgxaA=", + "checksumSHA1": "ANS1jzUqsv1Q1X0gZNzSe9wY0Fo=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "cb9716b4752192112a4bbde4d362c3757c3c2d38", - "revisionTime": "2016-09-29T15:26:01Z" + "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", + "revisionTime": "2016-09-30T14:52:40Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", @@ -87,10 +87,10 @@ "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "CDOphCxF7dup+hBBVRpFeQhM9Jw=", + "checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", "path": "github.com/asaskevich/govalidator", - "revision": "593d64559f7600f29581a3ee42177f5dbded27a9", - "revisionTime": "2016-07-15T17:06:12Z" + "revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", + "revisionTime": "2016-10-01T16:31:30Z" }, { "checksumSHA1": "rVZEfQyYQxiivnzH6ZWzcqrbi6o=", @@ -101,20 +101,14 @@ { "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", "path": "github.com/brocaar/lorawan", - "revision": "5dcb3accd0694379a4db94be75fa721867c59739", - "revisionTime": "2016-09-18T07:42:22Z" + "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", + "revisionTime": "2016-09-30T18:12:03Z" }, { - "checksumSHA1": "ff324fCIrB7mAfRe47oaPZE5PDM=", + "checksumSHA1": "MCRyJiR3hs/bt++6w5SCcyIYaZE=", "path": "github.com/brocaar/lorawan/band", - "revision": "5dcb3accd0694379a4db94be75fa721867c59739", - "revisionTime": "2016-09-18T07:42:22Z" - }, - { - "checksumSHA1": "ntacCkWfMT63DaehXLG5FeXWyNM=", - "path": "github.com/cpuguy83/go-md2man/md2man", - "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", - "revisionTime": "2016-09-04T16:08:59Z" + "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", + "revisionTime": "2016-09-30T18:12:03Z" }, { "checksumSHA1": "EsJksAKyY2+r3mY+rdNocSCe1wg=", @@ -123,22 +117,22 @@ "revisionTime": "2016-08-31T18:35:34Z" }, { - "checksumSHA1": "Uouj6ZRJ7qieqrCHp6u1cghopIM=", + "checksumSHA1": "PPyNjcW5yfrfXvARE74fh9JBSpo=", "path": "github.com/eclipse/paho.mqtt.golang", - "revision": "97b6f6bfc5007d4bc6859ea6ef8995079b60adc8", - "revisionTime": "2016-09-28T12:59:33Z" + "revision": "5d3aa4073fcbdaf5fcacf2c3f033ca6448e0bd85", + "revisionTime": "2016-10-04T07:53:38Z" }, { "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", "path": "github.com/eclipse/paho.mqtt.golang/packets", - "revision": "97b6f6bfc5007d4bc6859ea6ef8995079b60adc8", - "revisionTime": "2016-09-28T12:59:33Z" + "revision": "5d3aa4073fcbdaf5fcacf2c3f033ca6448e0bd85", + "revisionTime": "2016-10-04T07:53:38Z" }, { - "checksumSHA1": "xgjI2W3RGiQwNlxsOW2V9fJ9kaM=", + "checksumSHA1": "e2o/P8ZZ8Iz+um6/nyLUEg7S2H8=", "path": "github.com/fsnotify/fsnotify", - "revision": "f12c6236fe7b5cf6bcf30e5935d08cb079d78334", - "revisionTime": "2016-08-16T05:15:41Z" + "revision": "944cff21b3baf3ced9a880365682152ba577d348", + "revisionTime": "2016-10-05T04:06:20Z" }, { "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", @@ -155,20 +149,20 @@ { "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", "path": "github.com/gogo/protobuf/gogoproto", - "revision": "89f1976ff373a3e549675d2f212c10f98b6c6316", - "revisionTime": "2016-09-26T20:24:12Z" + "revision": "fdc14ac22689d09f8639e603614593811bc1d81c", + "revisionTime": "2016-10-01T10:27:20Z" }, { - "checksumSHA1": "cCRiVVaOgfOXO73tkNYIXU+lXn4=", + "checksumSHA1": "2eIRbxeMUEF1ZnOanJhXmFR7pFs=", "path": "github.com/gogo/protobuf/proto", - "revision": "89f1976ff373a3e549675d2f212c10f98b6c6316", - "revisionTime": "2016-09-26T20:24:12Z" + "revision": "fdc14ac22689d09f8639e603614593811bc1d81c", + "revisionTime": "2016-10-01T10:27:20Z" }, { "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", - "revision": "89f1976ff373a3e549675d2f212c10f98b6c6316", - "revisionTime": "2016-09-26T20:24:12Z" + "revision": "fdc14ac22689d09f8639e603614593811bc1d81c", + "revisionTime": "2016-10-01T10:27:20Z" }, { "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", @@ -177,16 +171,16 @@ "revisionTime": "2016-01-21T18:51:14Z" }, { - "checksumSHA1": "sZSeW3Dc6lUqm1CzMUdu3poimoA=", + "checksumSHA1": "qjr2SKQanbmna221z0Ce2n0hnDE=", "path": "github.com/golang/protobuf/proto", - "revision": "87c000235d3d852c1628dc9490cd21ab36a7d69f", - "revisionTime": "2016-09-26T18:56:24Z" + "revision": "df1d3ca07d2d07bba352d5b73c4313b4e2a6203e", + "revisionTime": "2016-09-27T20:09:49Z" }, { "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", - "revision": "87c000235d3d852c1628dc9490cd21ab36a7d69f", - "revisionTime": "2016-09-26T18:56:24Z" + "revision": "df1d3ca07d2d07bba352d5b73c4313b4e2a6203e", + "revisionTime": "2016-09-27T20:09:49Z" }, { "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", @@ -263,8 +257,8 @@ { "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", "path": "github.com/howeyc/gopass", - "revision": "26c6e1184fd5255fa5f5289d0b789a4819c203a4", - "revisionTime": "2016-09-12T12:55:46Z" + "revision": "f5387c492211eb133053880d23dfae62aa14123d", + "revisionTime": "2016-10-03T13:09:00Z" }, { "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", @@ -341,14 +335,14 @@ { "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", "path": "github.com/pkg/errors", - "revision": "645ef00459ed84a119197bfb8d8205042c6df63d", - "revisionTime": "2016-09-29T01:48:01Z" + "revision": "839d9e913e063e28dfd0e6c7b7512793e0a48be9", + "revisionTime": "2016-10-02T05:25:12Z" }, { - "checksumSHA1": "v6/DDmWObvEsdMvLe+KfuBVSNtg=", + "checksumSHA1": "k9SlQdp/DTB72G/u4aNecX/fFIg=", "path": "github.com/pkg/sftp", - "revision": "8197a2e580736b78d704be0fc47b2324c0591a32", - "revisionTime": "2016-09-08T10:00:35Z" + "revision": "4d0e916071f68db74f8a73926335f809396d6b42", + "revisionTime": "2016-09-30T22:07:58Z" }, { "checksumSHA1": "GiX6yRUzizn1C+ckgj1xLFLoz8g=", @@ -357,95 +351,88 @@ "revisionTime": "2016-09-21T19:52:07Z" }, { - "checksumSHA1": "WXYoBfdPvrhxal3Iy0xrP+C7VQY=", + "checksumSHA1": "5qwv3yDROEz5ZV8HztOBmQxen8c=", "path": "github.com/robertkrimen/otto", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { "checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=", "path": "github.com/robertkrimen/otto/ast", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { "checksumSHA1": "L0KsB2EzTlPgv0iae3q3SukNW7U=", "path": "github.com/robertkrimen/otto/dbg", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { "checksumSHA1": "euDLJKhw4doeTSxjEoezjxYXLzs=", "path": "github.com/robertkrimen/otto/file", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { "checksumSHA1": "LLuLITFO8chqSG0+APJIy5NtOHU=", "path": "github.com/robertkrimen/otto/parser", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { "checksumSHA1": "7J/7NaYRqKhBvZ+dTIutsEoEgFw=", "path": "github.com/robertkrimen/otto/registry", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { "checksumSHA1": "/jMXYuXycBpTqWhRyJ2xsqvHvQI=", "path": "github.com/robertkrimen/otto/token", - "revision": "3e7ca9f1929a7f90c674390834e6495bcd09836a", - "revisionTime": "2016-09-19T01:00:42Z" - }, - { - "checksumSHA1": "JdYdBoNxMR+CDqTfs78cQmY8JtI=", - "origin": "github.com/cpuguy83/go-md2man/vendor/github.com/russross/blackfriday", - "path": "github.com/russross/blackfriday", - "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", - "revisionTime": "2016-09-04T16:08:59Z" + "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", + "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "ToLFWWpwBceipBQRASIxs36Glok=", + "checksumSHA1": "ys3WakqgBbJBZBQ7FCbxW26eSAw=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", "path": "github.com/shirou/gopsutil/host", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", "path": "github.com/shirou/gopsutil/load", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", "path": "github.com/shirou/gopsutil/mem", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "Fa7lUUadHW9402ifjheuSFDkufo=", "path": "github.com/shirou/gopsutil/net", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "k1ug9JDvES8wNqxaQcO7qxgpOKc=", "path": "github.com/shirou/gopsutil/process", - "revision": "af2b5127ea65bc380b2e07f2f27829aac2640d65", - "revisionTime": "2016-09-22T12:59:23Z" + "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", + "revisionTime": "2016-09-30T12:56:23Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -453,13 +440,6 @@ "revision": "bb4de0191aa41b5507caa14b0650cdbddcd9280b", "revisionTime": "2016-09-30T03:27:40Z" }, - { - "checksumSHA1": "XXTR/ftEYSqQn4+Y7wNfiJJmq9U=", - "origin": "github.com/cpuguy83/go-md2man/vendor/github.com/shurcooL/sanitized_anchor_name", - "path": "github.com/shurcooL/sanitized_anchor_name", - "revision": "a65d4d2de4d5f7c74868dfa9b202a3c8be315aaa", - "revisionTime": "2016-09-04T16:08:59Z" - }, { "checksumSHA1": "6AYg4fjEvFuAVN3wHakGApjhZAM=", "path": "github.com/smartystreets/assertions", @@ -514,12 +494,6 @@ "revision": "9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744", "revisionTime": "2016-08-30T17:49:25Z" }, - { - "checksumSHA1": "rJRJtv7XT5eDwNlcqNPC+NjXCYE=", - "path": "github.com/spf13/cobra/doc", - "revision": "9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744", - "revisionTime": "2016-08-30T17:49:25Z" - }, { "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", "path": "github.com/spf13/jwalterweatherman", @@ -559,80 +533,80 @@ { "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", "path": "golang.org/x/crypto/curve25519", - "revision": "a20de3fa94e069ec699987416679230d72e030a3", - "revisionTime": "2016-09-23T08:39:13Z" + "revision": "7682e7e3945130cf3cde089834664f68afdd1523", + "revisionTime": "2016-10-03T20:54:26Z" }, { "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "path": "golang.org/x/crypto/ed25519", - "revision": "a20de3fa94e069ec699987416679230d72e030a3", - "revisionTime": "2016-09-23T08:39:13Z" + "revision": "7682e7e3945130cf3cde089834664f68afdd1523", + "revisionTime": "2016-10-03T20:54:26Z" }, { "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "a20de3fa94e069ec699987416679230d72e030a3", - "revisionTime": "2016-09-23T08:39:13Z" + "revision": "7682e7e3945130cf3cde089834664f68afdd1523", + "revisionTime": "2016-10-03T20:54:26Z" }, { - "checksumSHA1": "ueYA012ftzqjyWYYdlt3H3pkixo=", + "checksumSHA1": "1LydpuiE3oBdkbYvSdKKwe9lsLs=", "path": "golang.org/x/crypto/ssh", - "revision": "a20de3fa94e069ec699987416679230d72e030a3", - "revisionTime": "2016-09-23T08:39:13Z" + "revision": "7682e7e3945130cf3cde089834664f68afdd1523", + "revisionTime": "2016-10-03T20:54:26Z" }, { - "checksumSHA1": "y3a1tOZVwKCqG2yJZvAYycnelyM=", + "checksumSHA1": "9C4Av3ypK5pi173F76ogJT/d8x4=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "a20de3fa94e069ec699987416679230d72e030a3", - "revisionTime": "2016-09-23T08:39:13Z" + "revision": "7682e7e3945130cf3cde089834664f68afdd1523", + "revisionTime": "2016-10-03T20:54:26Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { - "checksumSHA1": "yIyismrXLh/RQFDeOtTPAGWsTOw=", + "checksumSHA1": "TUt1YBoSgtklP3MKWuOJwXBrkRw=", "path": "golang.org/x/net/http2", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { - "checksumSHA1": "WpST9lFOHvWrjDVy0GJNqHe+R3E=", + "checksumSHA1": "TSgie2Sfk7n2xAssjIGa4JwY7Lk=", "path": "golang.org/x/net/trace", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { - "checksumSHA1": "likOl7O0BJntqagR050kkOBuY4o=", + "checksumSHA1": "sM7kVFxASf7lzSkUI9sF6YClGaA=", "path": "golang.org/x/net/websocket", - "revision": "9f0e377a8a39bb99ff03459d77c135a0042878f0", - "revisionTime": "2016-09-27T14:53:45Z" + "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", + "revisionTime": "2016-10-03T09:22:05Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", @@ -655,68 +629,110 @@ { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "a7c023693a94aedd6b6df43ae7526bfe9d2b7d22", - "revisionTime": "2016-09-22T05:42:04Z" + "revision": "098f51fb687dbaba1f6efabeafbb6461203f9e21", + "revisionTime": "2016-09-28T15:11:56Z" }, { "checksumSHA1": "Aj3JSVO324FCjEAGm4ZwmC79bbo=", "path": "golang.org/x/text/unicode/norm", - "revision": "a7c023693a94aedd6b6df43ae7526bfe9d2b7d22", - "revisionTime": "2016-09-22T05:42:04Z" + "revision": "098f51fb687dbaba1f6efabeafbb6461203f9e21", + "revisionTime": "2016-09-28T15:11:56Z" + }, + { + "checksumSHA1": "Iq/i8Bsr/xrLXTT14mBcyLYOyrY=", + "path": "google.golang.org/appengine/internal", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" + }, + { + "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", + "path": "google.golang.org/appengine/internal/base", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" + }, + { + "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", + "path": "google.golang.org/appengine/internal/datastore", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" + }, + { + "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", + "path": "google.golang.org/appengine/internal/log", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" + }, + { + "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", + "path": "google.golang.org/appengine/internal/remote_api", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" + }, + { + "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", + "path": "google.golang.org/appengine/internal/urlfetch", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" + }, + { + "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", + "path": "google.golang.org/appengine/urlfetch", + "revision": "926995697fa8241be2dc73eb318666e24f44ed51", + "revisionTime": "2016-09-22T22:46:31Z" }, { - "checksumSHA1": "iCegvO4MPw6Uf8eMp/LJa1KCfeE=", + "checksumSHA1": "N0GmUbip6LljIQkixSZnQ7a76Fs=", "path": "google.golang.org/grpc", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", "path": "google.golang.org/grpc/metadata", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { - "checksumSHA1": "Z9sFAvzisTlmBjPMVt8dEGBK2pg=", + "checksumSHA1": "dzbx9oVtSfVgNE3lTl+r5xOXxcw=", "path": "google.golang.org/grpc/transport", - "revision": "6d7caeea7c3bbc318c636d366c82c328e2ea07b3", - "revisionTime": "2016-09-28T00:26:54Z" + "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", + "revisionTime": "2016-10-03T17:23:44Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", From dbd379f330921f4fe6498dd2bad5ad461aabebd9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 5 Oct 2016 11:06:19 +0200 Subject: [PATCH 1873/2266] Add golint to Makefile --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index a33e869c7..d48ef0dbf 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,7 @@ deps: build-deps dev-deps: deps @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast @command -v mockgen > /dev/null || go get github.com/golang/mock/mockgen + @command -v golint > /dev/null || github.com/golang/lint/golint @command -v forego > /dev/null || go get github.com/ddollar/forego # Protobuf @@ -84,6 +85,9 @@ fmt: vet: echo $(GO_PACKAGES) | xargs go vet +lint: + for pkg in `echo $(GO_PACKAGES)`; do golint $$pkg | grep -vE 'mock|.pb.go'; done + # Go Build RELEASE_DIR ?= release From 9f1ea5741740df0625315383b72dc57fce19421b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 5 Oct 2016 11:06:40 +0200 Subject: [PATCH 1874/2266] Infer GOOS and GOARCH from build filename --- Makefile | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index d48ef0dbf..8a5a4d1bb 100644 --- a/Makefile +++ b/Makefile @@ -93,21 +93,23 @@ lint: RELEASE_DIR ?= release GOOS ?= $(shell go env GOOS) GOARCH ?= $(shell go env GOARCH) -GO = GOOS=$(GOOS) GOARCH=$(GOARCH) go -GOEXE = $(shell $(GO) env GOEXE) +GOEXE = $(shell GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE) +splitfilename = $(subst ., ,$(subst -, ,$(subst $(RELEASE_DIR)/,,$1))) +GOOSfromfilename = $(word 2, $(call splitfilename, $1)) +GOARCHfromfilename = $(word 3, $(call splitfilename, $1)) LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" -GOBUILD = CGO_ENABLED=0 $(GO) build -a -installsuffix cgo ${LDFLAGS} +GOBUILD = CGO_ENABLED=0 GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build -a -installsuffix cgo ${LDFLAGS} -o "$@" ttn: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(RELEASE_DIR)/ttn-%: $(GO_FILES) - $(GOBUILD) -o "$@" ./main.go + $(GOBUILD) ./main.go ttnctl: $(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) $(RELEASE_DIR)/ttnctl-%: $(GO_FILES) - $(GOBUILD) -o "$@" ./ttnctl/main.go + $(GOBUILD) ./ttnctl/main.go build: ttn ttnctl @@ -128,5 +130,7 @@ docs: clean: [ -d $(RELEASE_DIR) ] && rm -rf $(RELEASE_DIR) || [ ! -d $(RELEASE_DIR) ] +docker: GOOS=linux +docker: GOARCH=amd64 docker: $(RELEASE_DIR)/ttn-linux-amd64 docker build -t thethingsnetwork/ttn -f Dockerfile . From 813c164ad81b776b64942cf3aa9014520b9358bd Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 5 Oct 2016 11:16:15 +0200 Subject: [PATCH 1875/2266] Add newline after options Or the heading follow it won't be styled --- ttnctl/cmd/docs/generate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go index 9e7fdbb27..ac8d41112 100644 --- a/ttnctl/cmd/docs/generate.go +++ b/ttnctl/cmd/docs/generate.go @@ -76,4 +76,5 @@ func printOptions(cmd *cobra.Command) { flags.PrintDefaults() fmt.Print(b.String()) fmt.Println("```") + fmt.Println("") } From 55f5087f1baa553cc615d762d3d8665fe8c71450 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 5 Oct 2016 12:31:55 +0200 Subject: [PATCH 1876/2266] Add newline after options for ttn as well --- cmd/docs/generate.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/docs/generate.go b/cmd/docs/generate.go index ff3bc60c5..2676eab17 100644 --- a/cmd/docs/generate.go +++ b/cmd/docs/generate.go @@ -76,4 +76,5 @@ func printOptions(cmd *cobra.Command) { flags.PrintDefaults() fmt.Print(b.String()) fmt.Println("```") + fmt.Println("") } From d3f0504ff4641dddd059a03840de6cfa99168a02 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 5 Oct 2016 12:43:04 +0200 Subject: [PATCH 1877/2266] Move docs Generate to separate package --- cmd/docs/generate.go | 68 +------------------------------ ttnctl/cmd/docs/generate.go | 68 +------------------------------ utils/docs/generate.go | 79 +++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 132 deletions(-) create mode 100644 utils/docs/generate.go diff --git a/cmd/docs/generate.go b/cmd/docs/generate.go index 2676eab17..bbaaec1dc 100644 --- a/cmd/docs/generate.go +++ b/cmd/docs/generate.go @@ -1,80 +1,16 @@ package main import ( - "bytes" "fmt" - "sort" - "strings" "github.com/TheThingsNetwork/ttn/cmd" - "github.com/spf13/cobra" + "github.com/TheThingsNetwork/ttn/utils/docs" ) -type byName []*cobra.Command - -func (s byName) Len() int { return len(s) } -func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPath() } - func main() { - cmds := genCmdList(cmd.RootCmd) - sort.Sort(byName(cmds)) fmt.Println(`# API Reference The Things Network's backend servers. `) - for _, cmd := range cmds { - if cmd.CommandPath() == "ttn" { - fmt.Print("**Options**\n\n") - printOptions(cmd) - fmt.Println() - continue - } - - depth := len(strings.Split(cmd.CommandPath(), " ")) - - printHeader(depth, cmd.CommandPath()) - - fmt.Print(cmd.Long, "\n\n") - - if cmd.Runnable() { - fmt.Print("**Usage:** ", "`", cmd.UseLine(), "`", "\n\n") - } - - if cmd.HasLocalFlags() || cmd.HasPersistentFlags() { - fmt.Print("**Options**\n\n") - printOptions(cmd) - } - - if cmd.Example != "" { - fmt.Print("**Example**\n\n") - fmt.Print("```", "\n", cmd.Example, "```", "\n\n") - } - } -} - -func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { - cmds = append(cmds, cmd) - for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c.IsHelpCommand() { - continue - } - cmds = append(cmds, genCmdList(c)...) - } - return cmds -} - -func printHeader(depth int, header string) { - fmt.Print(strings.Repeat("#", depth), " ", header, "\n\n") -} - -func printOptions(cmd *cobra.Command) { - fmt.Println("```") - var b bytes.Buffer - flags := cmd.NonInheritedFlags() - flags.SetOutput(&b) - flags.PrintDefaults() - fmt.Print(b.String()) - fmt.Println("```") - fmt.Println("") + fmt.Print(docs.Generate(cmd.RootCmd)) } diff --git a/ttnctl/cmd/docs/generate.go b/ttnctl/cmd/docs/generate.go index ac8d41112..4541452bf 100644 --- a/ttnctl/cmd/docs/generate.go +++ b/ttnctl/cmd/docs/generate.go @@ -1,80 +1,16 @@ package main import ( - "bytes" "fmt" - "sort" - "strings" "github.com/TheThingsNetwork/ttn/ttnctl/cmd" - "github.com/spf13/cobra" + "github.com/TheThingsNetwork/ttn/utils/docs" ) -type byName []*cobra.Command - -func (s byName) Len() int { return len(s) } -func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPath() } - func main() { - cmds := genCmdList(cmd.RootCmd) - sort.Sort(byName(cmds)) fmt.Println(`# API Reference Control The Things Network from the command line. `) - for _, cmd := range cmds { - if cmd.CommandPath() == "ttnctl" { - fmt.Print("**Options**\n\n") - printOptions(cmd) - fmt.Println() - continue - } - - depth := len(strings.Split(cmd.CommandPath(), " ")) - - printHeader(depth, cmd.CommandPath()) - - fmt.Print(cmd.Long, "\n\n") - - if cmd.Runnable() { - fmt.Print("**Usage:** ", "`", cmd.UseLine(), "`", "\n\n") - } - - if cmd.HasLocalFlags() || cmd.HasPersistentFlags() { - fmt.Print("**Options**\n\n") - printOptions(cmd) - } - - if cmd.Example != "" { - fmt.Print("**Example**\n\n") - fmt.Print("```", "\n", cmd.Example, "```", "\n\n") - } - } -} - -func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { - cmds = append(cmds, cmd) - for _, c := range cmd.Commands() { - if !c.IsAvailableCommand() || c.IsHelpCommand() { - continue - } - cmds = append(cmds, genCmdList(c)...) - } - return cmds -} - -func printHeader(depth int, header string) { - fmt.Print(strings.Repeat("#", depth), " ", header, "\n\n") -} - -func printOptions(cmd *cobra.Command) { - fmt.Println("```") - var b bytes.Buffer - flags := cmd.NonInheritedFlags() - flags.SetOutput(&b) - flags.PrintDefaults() - fmt.Print(b.String()) - fmt.Println("```") - fmt.Println("") + fmt.Print(docs.Generate(cmd.RootCmd)) } diff --git a/utils/docs/generate.go b/utils/docs/generate.go new file mode 100644 index 000000000..30817c15f --- /dev/null +++ b/utils/docs/generate.go @@ -0,0 +1,79 @@ +package docs + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + + "github.com/spf13/cobra" +) + +type byName []*cobra.Command + +func (s byName) Len() int { return len(s) } +func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s byName) Less(i, j int) bool { return s[i].CommandPath() < s[j].CommandPath() } + +// Generate prints API docs for a command +func Generate(cmd *cobra.Command) string { + buf := new(bytes.Buffer) + + cmds := genCmdList(cmd) + sort.Sort(byName(cmds)) + for _, cmd := range cmds { + if len(strings.Split(cmd.CommandPath(), " ")) == 1 { + fmt.Fprint(buf, "**Options**\n\n") + printOptions(buf, cmd) + fmt.Fprintln(buf) + continue + } + + depth := len(strings.Split(cmd.CommandPath(), " ")) + + fmt.Fprint(buf, header(depth, cmd.CommandPath())) + + fmt.Fprint(buf, cmd.Long, "\n\n") + + if cmd.Runnable() { + fmt.Fprint(buf, "**Usage:** ", "`", cmd.UseLine(), "`", "\n\n") + } + + if cmd.HasLocalFlags() || cmd.HasPersistentFlags() { + fmt.Fprint(buf, "**Options**\n\n") + printOptions(buf, cmd) + } + + if cmd.Example != "" { + fmt.Fprint(buf, "**Example**\n\n") + fmt.Fprint(buf, "```", "\n", cmd.Example, "```", "\n\n") + } + } + + return buf.String() +} + +func genCmdList(cmd *cobra.Command) (cmds []*cobra.Command) { + cmds = append(cmds, cmd) + for _, c := range cmd.Commands() { + if !c.IsAvailableCommand() || c.IsHelpCommand() { + continue + } + cmds = append(cmds, genCmdList(c)...) + } + return cmds +} + +func header(depth int, header string) string { + return fmt.Sprint(strings.Repeat("#", depth), " ", header, "\n\n") +} + +func printOptions(w io.Writer, cmd *cobra.Command) { + fmt.Fprintln(w, "```") + flags := cmd.NonInheritedFlags() + flags.SetOutput(w) + flags.PrintDefaults() + fmt.Fprintln(w, "```") + fmt.Fprintln(w, "") +} From 483416deea26ad2a87cf0c3f7e0fccb5becb16a8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 5 Oct 2016 12:43:13 +0200 Subject: [PATCH 1878/2266] Update Docs --- cmd/docs/README.md | 7 +++++++ ttnctl/cmd/docs/README.md | 25 +++++++------------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/cmd/docs/README.md b/cmd/docs/README.md index 4380b2d10..385f8a8ce 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -18,6 +18,7 @@ The Things Network's backend servers. --tls Use TLS ``` + ## ttn broker @@ -35,6 +36,7 @@ The Things Network's backend servers. --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1902) ``` + ### ttn broker genkeys ttn genkeys generates keys and a TLS certificate for this component @@ -61,6 +63,7 @@ ttn broker register prefix registers a prefix to this Broker --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 handler @@ -80,6 +83,7 @@ ttn broker register prefix registers a prefix to this Broker --server-port int The port for communication (default 1904) --ttn-broker string The ID of the TTN Broker as announced in the Discovery server (default "dev") ``` + ### ttn handler genkeys ttn genkeys generates keys and a TLS certificate for this component @@ -102,6 +106,7 @@ ttn genkeys generates keys and a TLS certificate for this component --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1903) ``` + ### ttn networkserver authorize ttn networkserver authorize generates a token that Brokers should use to connect @@ -113,6 +118,7 @@ ttn networkserver authorize generates a token that Brokers should use to connect ``` --valid int The number of days the token is valid ``` + ### ttn networkserver genkeys ttn genkeys generates keys and a TLS certificate for this component @@ -132,6 +138,7 @@ ttn genkeys generates keys and a TLS certificate for this component --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1901) ``` + ### ttn router genkeys ttn genkeys generates keys and a TLS certificate for this component diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index ae6cb43e0..2376842c7 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -15,6 +15,7 @@ Control The Things Network from the command line. --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` + ## ttnctl applications ttnctl applications can be used to manage applications. @@ -32,6 +33,7 @@ ttnctl applications add can be used to add a new application to your account. --skip-register Do not register application with the Handler --skip-select Do not select this application (also adds --skip-register) ``` + **Example** ``` @@ -194,12 +196,6 @@ Are you sure you want to unregister application test? INFO Unregistered application AppID=test ``` -## ttnctl config - -ttnctl config gets the config of ttnctl - -**Usage:** `ttnctl config` - ## ttnctl devices ttnctl devices can be used to manage devices. @@ -210,6 +206,7 @@ ttnctl devices can be used to manage devices. --app-eui string The app EUI to use --app-id string The app ID to use ``` + ### ttnctl devices delete ttnctl devices delete can be used to delete a device. @@ -239,6 +236,7 @@ ttnctl devices info can be used to get information about a device. ``` --format string Formatting: hex/msb/lsb (default "hex") ``` + **Example** ``` @@ -342,6 +340,7 @@ ttnctl devices set can be used to set properties of a device. --fcnt-up int Set FCnt Up (default -1) --nwk-s-key string Set NwkSKey ``` + **Example** ``` @@ -352,18 +351,6 @@ $ ttnctl devices set test --fcnt-up 0 --fcnt-down 0 INFO Updated device AppID=test DevID=test ``` -## ttnctl discover - -ttnctl discover is used to discover all network components - -**Usage:** `ttnctl discover` - -### ttnctl discover prefixes - -ttnctl discover prefixes is used to discover all prefixes announced by brokers - -**Usage:** `ttnctl discover prefixes` - ## ttnctl downlink ttnctl downlink can be used to send a downlink message to a device. @@ -376,6 +363,7 @@ ttnctl downlink can be used to send a downlink message to a device. --fport int FPort for downlink (default 1) --json Provide the payload as JSON ``` + **Example** ``` @@ -419,6 +407,7 @@ ttnctl gateways edit can be used to edit settings of a gateway --frequency-plan string The frequency plan to use on the gateway --location string The location of the gateway ``` + **Example** ``` From 874a086370535d3c2031e6798f8976012e4a7244 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 5 Oct 2016 22:46:35 +0200 Subject: [PATCH 1879/2266] Makefile: `link` use `ln -f` --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8a5a4d1bb..d7f7cf786 100644 --- a/Makefile +++ b/Makefile @@ -116,8 +116,8 @@ build: ttn ttnctl GOBIN ?= $(GOPATH)/bin link: ttn - rm $(GOBIN)/ttn && ln -s $(PWD)/$(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttn - rm $(GOBIN)/ttnctl && ln -s $(PWD)/$(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttnctl + ln -sf $(PWD)/$(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttn + ln -sf $(PWD)/$(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttnctl # Documentation From a0383546e5cd126180634587deaf8f9e8e72e963 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 6 Oct 2016 08:41:46 +0200 Subject: [PATCH 1880/2266] BuildGRPCError should return nil on nil error --- utils/errors/errors.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/errors/errors.go b/utils/errors/errors.go index d118a327e..e0c0e7ac6 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -44,6 +44,9 @@ var grpcErrf = grpc.Errorf // BuildGRPCError returns the error with a GRPC code func BuildGRPCError(err error) error { + if err == nil { + return nil + } code := codes.Unknown switch errs.Cause(err).(type) { case *ErrAlreadyExists: From 80fdbe7f3c5b92bf9e57ea63ba22a28ac0306400 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 6 Oct 2016 11:13:00 +0200 Subject: [PATCH 1881/2266] Recognize AS and SK bands --- api/protocol/lorawan/lorawan.pb.go | 100 ++++++++++++++++++----------- api/protocol/lorawan/lorawan.proto | 2 + core/router/downlink.go | 32 +++++---- 3 files changed, 84 insertions(+), 50 deletions(-) diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index c6f883c4a..994d64594 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -2,6 +2,17 @@ // source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto // DO NOT EDIT! +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto + + It has these top-level messages: + Metadata + TxConfiguration + ActivationMetadata +*/ package lorawan import proto "github.com/golang/protobuf/proto" @@ -18,6 +29,12 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + type Modulation int32 const ( @@ -48,6 +65,8 @@ const ( Region_EU_433 Region = 3 Region_AU_915_928 Region = 4 Region_CN_470_510 Region = 5 + Region_AS_923 Region = 6 + Region_SK_920_923 Region = 7 ) var Region_name = map[int32]string{ @@ -57,6 +76,8 @@ var Region_name = map[int32]string{ 3: "EU_433", 4: "AU_915_928", 5: "CN_470_510", + 6: "AS_923", + 7: "SK_920_923", } var Region_value = map[string]int32{ "EU_863_870": 0, @@ -65,6 +86,8 @@ var Region_value = map[string]int32{ "EU_433": 3, "AU_915_928": 4, "CN_470_510": 5, + "AS_923": 6, + "SK_920_923": 7, } func (x Region) String() string { @@ -1108,42 +1131,43 @@ func init() { } var fileDescriptorLorawan = []byte{ - // 580 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x93, 0xcf, 0x4f, 0x1a, 0x41, - 0x14, 0xc7, 0x5d, 0x41, 0xc0, 0xa7, 0xe0, 0x66, 0x4d, 0xd3, 0x6d, 0x9b, 0x00, 0xf1, 0x44, 0x4c, - 0xca, 0x4f, 0x15, 0x38, 0xa2, 0xd0, 0xa4, 0x51, 0x31, 0x1d, 0xe5, 0x3c, 0x19, 0x76, 0x66, 0xd7, - 0x09, 0xb8, 0xb3, 0x19, 0x06, 0x58, 0xfe, 0x93, 0xfe, 0x13, 0xbd, 0xf5, 0x8f, 0x68, 0x7a, 0xea, - 0xd9, 0x83, 0x69, 0xec, 0x3f, 0xd2, 0xcc, 0xac, 0x5a, 0x6f, 0x4d, 0xbd, 0xf5, 0xc4, 0xfb, 0xbe, - 0xef, 0x7b, 0x9f, 0x79, 0xe1, 0xbd, 0x85, 0xe3, 0x80, 0xab, 0xeb, 0xf9, 0xb8, 0xea, 0x89, 0x9b, - 0xda, 0xd5, 0x35, 0xbb, 0xba, 0xe6, 0x61, 0x30, 0x1b, 0x32, 0xb5, 0x14, 0x72, 0x52, 0x53, 0x2a, - 0xac, 0x91, 0x88, 0xd7, 0x22, 0x29, 0x94, 0xf0, 0xc4, 0xb4, 0x36, 0x15, 0x92, 0x2c, 0x49, 0xf8, - 0xf8, 0x5b, 0x35, 0x86, 0x93, 0x7d, 0x90, 0x6f, 0xdf, 0x3f, 0x83, 0x05, 0x22, 0x10, 0x49, 0xe3, - 0x78, 0xee, 0x1b, 0x65, 0x84, 0x89, 0x92, 0xbe, 0xbd, 0x2f, 0x16, 0xe4, 0xce, 0x99, 0x22, 0x94, - 0x28, 0xe2, 0xb4, 0x00, 0x6e, 0x04, 0x9d, 0x4f, 0x89, 0xe2, 0x22, 0x74, 0xb7, 0xca, 0x56, 0xa5, - 0xd0, 0xdc, 0xad, 0x3e, 0x3e, 0x74, 0xfe, 0x64, 0xa1, 0x67, 0x65, 0xce, 0x3b, 0xd8, 0xd4, 0xcd, - 0x58, 0x12, 0xc5, 0xdc, 0xed, 0xb2, 0x55, 0xd9, 0x44, 0x39, 0x9d, 0x40, 0x44, 0x31, 0xe7, 0x0d, - 0xe4, 0xc6, 0x5c, 0x25, 0x5e, 0xbe, 0x6c, 0x55, 0xf2, 0x28, 0x3b, 0xe6, 0xca, 0x58, 0x25, 0xd8, - 0xf2, 0x04, 0xe5, 0x61, 0x90, 0xb8, 0x05, 0xd3, 0x09, 0x49, 0xca, 0x14, 0xec, 0xc2, 0x86, 0x8f, - 0xbd, 0x50, 0xb9, 0x3b, 0xa6, 0x31, 0xed, 0x9f, 0x84, 0x6a, 0xef, 0xab, 0x05, 0x3b, 0x57, 0xf1, - 0x89, 0x08, 0x7d, 0x1e, 0xcc, 0x65, 0x32, 0xc1, 0x7f, 0x30, 0xf6, 0xf7, 0x14, 0x38, 0x3d, 0x4f, - 0xf1, 0x85, 0x79, 0xfc, 0xe9, 0x0f, 0x1f, 0x42, 0x96, 0x44, 0x11, 0x66, 0x73, 0xee, 0x5a, 0x65, - 0xab, 0xb2, 0x7d, 0x7c, 0x78, 0x7b, 0x57, 0x6a, 0xfc, 0xed, 0x1c, 0x3c, 0x21, 0x59, 0x4d, 0xad, - 0x22, 0x36, 0xab, 0xf6, 0xa2, 0x68, 0x30, 0xfa, 0x88, 0x32, 0x24, 0x8a, 0x06, 0x73, 0xae, 0x79, - 0x94, 0x2d, 0x0c, 0x6f, 0xfd, 0x45, 0xbc, 0x3e, 0x5b, 0x18, 0x1e, 0x65, 0x0b, 0xcd, 0xfb, 0x04, - 0x39, 0xcd, 0x23, 0x94, 0x4a, 0x37, 0x65, 0x80, 0x47, 0xb7, 0x77, 0xa5, 0xe6, 0xbf, 0x01, 0x7b, - 0x94, 0x4a, 0xa4, 0xe7, 0xd2, 0x81, 0x83, 0x60, 0x33, 0x5c, 0x4e, 0xf0, 0x0c, 0x4f, 0xd8, 0xca, - 0x4d, 0xbf, 0x88, 0x39, 0x5c, 0x4e, 0x2e, 0x4f, 0xd9, 0x0a, 0x65, 0xc3, 0x24, 0x70, 0xf6, 0x20, - 0x2f, 0xe3, 0x06, 0xa6, 0x12, 0x0b, 0xdf, 0x9f, 0x31, 0x65, 0x6e, 0x20, 0x8f, 0xb6, 0x64, 0xdc, - 0xe8, 0xcb, 0x0b, 0x93, 0x72, 0x5e, 0x41, 0x46, 0xc6, 0x4d, 0x4c, 0xa5, 0x59, 0x76, 0x1e, 0x6d, - 0xc8, 0xb8, 0xd9, 0x97, 0x7a, 0xd3, 0x32, 0xc6, 0x94, 0x4d, 0xc9, 0xea, 0x71, 0xd3, 0x32, 0xee, - 0x6b, 0xe9, 0xbc, 0x86, 0xac, 0xe7, 0xe3, 0x29, 0x9f, 0x29, 0xb7, 0x50, 0x4e, 0x55, 0xd2, 0x28, - 0xe3, 0xf9, 0x67, 0x7c, 0xa6, 0xf6, 0x4b, 0x00, 0x7f, 0x8e, 0xca, 0xc9, 0x41, 0xfa, 0xec, 0x02, - 0xf5, 0xec, 0x35, 0x27, 0x0b, 0xa9, 0x0f, 0x97, 0xa7, 0xb6, 0xb5, 0x4f, 0x21, 0x83, 0x58, 0xa0, - 0xcd, 0x02, 0xc0, 0x60, 0x84, 0x3b, 0x47, 0x2d, 0xdc, 0x69, 0xd7, 0xed, 0x35, 0xad, 0x47, 0x97, - 0xb8, 0x5b, 0x6f, 0xe2, 0x6e, 0xb3, 0x63, 0x5b, 0x5a, 0x9f, 0x0c, 0x71, 0xbb, 0xdd, 0xc5, 0xed, - 0x4e, 0xdb, 0x5e, 0x77, 0x00, 0x32, 0x83, 0x11, 0x3e, 0x68, 0xb5, 0xec, 0x94, 0xf6, 0x7a, 0x23, - 0xdc, 0x6d, 0x1c, 0x9a, 0xda, 0xf4, 0x43, 0xed, 0x41, 0xbb, 0x8e, 0x0f, 0x1b, 0x75, 0x7b, 0xe3, - 0xd8, 0xfe, 0x76, 0x5f, 0xb4, 0x7e, 0xdc, 0x17, 0xad, 0x9f, 0xf7, 0x45, 0xeb, 0xf3, 0xaf, 0xe2, - 0xda, 0x38, 0x63, 0xbe, 0xe9, 0xd6, 0xef, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0xf7, 0x61, 0x93, - 0x51, 0x04, 0x00, 0x00, + // 601 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x93, 0xcf, 0x6e, 0xda, 0x4e, + 0x10, 0xc7, 0xe3, 0x40, 0x6c, 0x32, 0x09, 0xc4, 0x72, 0xf4, 0xd3, 0xcf, 0x6d, 0x25, 0x40, 0x39, + 0xa1, 0x48, 0xe5, 0x8f, 0x49, 0x02, 0x1c, 0x49, 0xa0, 0x52, 0x95, 0x84, 0xa8, 0x26, 0x9c, 0x57, + 0x8b, 0xbd, 0x76, 0x56, 0x10, 0xaf, 0xb5, 0x2c, 0x60, 0x6e, 0x7d, 0x8c, 0xbe, 0x44, 0x6f, 0x7d, + 0x88, 0xaa, 0xa7, 0x9e, 0x73, 0x88, 0xaa, 0xf4, 0x45, 0xaa, 0x5d, 0x87, 0x34, 0xb7, 0xaa, 0xb9, + 0xf5, 0xc4, 0x7c, 0xe7, 0x3b, 0xf3, 0xd9, 0x11, 0x33, 0x86, 0xd3, 0x90, 0x8a, 0x9b, 0xf9, 0xb8, + 0xea, 0xb1, 0xdb, 0xda, 0xf5, 0x0d, 0xb9, 0xbe, 0xa1, 0x51, 0x38, 0x1b, 0x10, 0xb1, 0x64, 0x7c, + 0x52, 0x13, 0x22, 0xaa, 0xe1, 0x98, 0xd6, 0x62, 0xce, 0x04, 0xf3, 0xd8, 0xb4, 0x36, 0x65, 0x1c, + 0x2f, 0x71, 0xb4, 0xfe, 0xad, 0x2a, 0xc3, 0x32, 0x1e, 0xe5, 0xeb, 0xb7, 0xcf, 0x60, 0x21, 0x0b, + 0x59, 0xda, 0x38, 0x9e, 0x07, 0x4a, 0x29, 0xa1, 0xa2, 0xb4, 0xef, 0xe0, 0xb3, 0x06, 0xb9, 0x4b, + 0x22, 0xb0, 0x8f, 0x05, 0xb6, 0x9a, 0x00, 0xb7, 0xcc, 0x9f, 0x4f, 0xb1, 0xa0, 0x2c, 0xb2, 0x77, + 0xca, 0x5a, 0xa5, 0xe0, 0xec, 0x57, 0xd7, 0x0f, 0x5d, 0x3e, 0x59, 0xee, 0xb3, 0x32, 0xeb, 0x0d, + 0x6c, 0xcb, 0x66, 0xc4, 0xb1, 0x20, 0xf6, 0x6e, 0x59, 0xab, 0x6c, 0xbb, 0x39, 0x99, 0x70, 0xb1, + 0x20, 0xd6, 0x2b, 0xc8, 0x8d, 0xa9, 0x48, 0xbd, 0x7c, 0x59, 0xab, 0xe4, 0x5d, 0x63, 0x4c, 0x85, + 0xb2, 0x4a, 0xb0, 0xe3, 0x31, 0x9f, 0x46, 0x61, 0xea, 0x16, 0x54, 0x27, 0xa4, 0x29, 0x55, 0xb0, + 0x0f, 0x5b, 0x01, 0xf2, 0x22, 0x61, 0xef, 0xa9, 0xc6, 0x6c, 0x70, 0x16, 0x89, 0x83, 0x2f, 0x1a, + 0xec, 0x5d, 0x27, 0x67, 0x2c, 0x0a, 0x68, 0x38, 0xe7, 0xe9, 0x04, 0xff, 0xc0, 0xd8, 0xdf, 0x32, + 0x60, 0x75, 0x3d, 0x41, 0x17, 0xea, 0xf1, 0xa7, 0x3f, 0x7c, 0x00, 0x06, 0x8e, 0x63, 0x44, 0xe6, + 0xd4, 0xd6, 0xca, 0x5a, 0x65, 0xf7, 0xf4, 0xf8, 0xee, 0xbe, 0xd4, 0xf8, 0xd3, 0x39, 0x78, 0x8c, + 0x93, 0x9a, 0x58, 0xc5, 0x64, 0x56, 0xed, 0xc6, 0x71, 0x7f, 0xf4, 0xde, 0xd5, 0x71, 0x1c, 0xf7, + 0xe7, 0x54, 0xf2, 0x7c, 0xb2, 0x50, 0xbc, 0xcd, 0x17, 0xf1, 0x7a, 0x64, 0xa1, 0x78, 0x3e, 0x59, + 0x48, 0xde, 0x07, 0xc8, 0x49, 0x1e, 0xf6, 0x7d, 0x6e, 0x67, 0x14, 0xf0, 0xe4, 0xee, 0xbe, 0xe4, + 0xfc, 0x1d, 0xb0, 0xeb, 0xfb, 0xdc, 0x95, 0x73, 0xc9, 0xc0, 0x72, 0x61, 0x3b, 0x5a, 0x4e, 0xd0, + 0x0c, 0x4d, 0xc8, 0xca, 0xce, 0xbe, 0x88, 0x39, 0x58, 0x4e, 0x86, 0xe7, 0x64, 0xe5, 0x1a, 0x51, + 0x1a, 0x58, 0x07, 0x90, 0xe7, 0x49, 0x03, 0xf9, 0x1c, 0xb1, 0x20, 0x98, 0x11, 0xa1, 0x6e, 0x20, + 0xef, 0xee, 0xf0, 0xa4, 0xd1, 0xe3, 0x57, 0x2a, 0x65, 0xfd, 0x07, 0x3a, 0x4f, 0x1c, 0xe4, 0x73, + 0xb5, 0xec, 0xbc, 0xbb, 0xc5, 0x13, 0xa7, 0xc7, 0xe5, 0xa6, 0x79, 0x82, 0x7c, 0x32, 0xc5, 0xab, + 0xf5, 0xa6, 0x79, 0xd2, 0x93, 0xd2, 0xfa, 0x1f, 0x0c, 0x2f, 0x40, 0x53, 0x3a, 0x13, 0x76, 0xa1, + 0x9c, 0xa9, 0x64, 0x5d, 0xdd, 0x0b, 0x2e, 0xe8, 0x4c, 0x1c, 0x96, 0x00, 0x7e, 0x1f, 0x95, 0x95, + 0x83, 0xec, 0xc5, 0x95, 0xdb, 0x35, 0x37, 0x2c, 0x03, 0x32, 0xef, 0x86, 0xe7, 0xa6, 0x76, 0xf8, + 0x51, 0x03, 0xdd, 0x25, 0xa1, 0x74, 0x0b, 0x00, 0xfd, 0x11, 0x6a, 0x9f, 0x34, 0x51, 0xbb, 0x55, + 0x37, 0x37, 0xa4, 0x1e, 0x0d, 0x51, 0xa7, 0xee, 0xa0, 0x8e, 0xd3, 0x36, 0x35, 0xa9, 0xcf, 0x06, + 0xa8, 0xd5, 0xea, 0xa0, 0x56, 0xbb, 0x65, 0x6e, 0x5a, 0x00, 0x7a, 0x7f, 0x84, 0x8e, 0x9a, 0x4d, + 0x33, 0x23, 0xbd, 0xee, 0x08, 0x75, 0x1a, 0xc7, 0xaa, 0x36, 0xfb, 0x58, 0x7b, 0xd4, 0xaa, 0xa3, + 0xe3, 0x46, 0xdd, 0xdc, 0x92, 0xb5, 0xdd, 0x21, 0xea, 0x38, 0x4d, 0x53, 0x97, 0xde, 0xf0, 0x1c, + 0x75, 0x9c, 0xba, 0xd2, 0xc6, 0xa9, 0xf9, 0xf5, 0xa1, 0xa8, 0x7d, 0x7f, 0x28, 0x6a, 0x3f, 0x1e, + 0x8a, 0xda, 0xa7, 0x9f, 0xc5, 0x8d, 0xb1, 0xae, 0x3e, 0xf8, 0xe6, 0xaf, 0x00, 0x00, 0x00, 0xff, + 0xff, 0x91, 0xac, 0x02, 0x91, 0x6e, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 8d605b9b2..4337a8079 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -49,4 +49,6 @@ enum Region { EU_433 = 3; AU_915_928 = 4; CN_470_510 = 5; + AS_923 = 6; + SK_920_923 = 7; } diff --git a/core/router/downlink.go b/core/router/downlink.go index 4b650646c..4beb01162 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -70,17 +70,21 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { func guessRegion(frequency uint64) string { switch { case frequency >= 863000000 && frequency <= 870000000: - return "EU_863_870" + return pb_lorawan.Region_EU_863_870.String() case frequency >= 902300000 && frequency <= 914900000: - return "US_902_928" + return pb_lorawan.Region_US_902_928.String() case frequency >= 779500000 && frequency <= 786500000: - return "CN_779_787" + return pb_lorawan.Region_CN_779_787.String() case frequency >= 433175000 && frequency <= 434665000: - return "EU_433" + return pb_lorawan.Region_EU_433.String() + case frequency == 923200000 || frequency == 923400000: + return pb_lorawan.Region_AS_923.String() + case frequency >= 920900000 || frequency == 923300000: + return pb_lorawan.Region_SK_920_923.String() case frequency >= 915200000 && frequency <= 927800000: - return "AU_915_928" + return pb_lorawan.Region_AU_915_928.String() case frequency >= 470300000 && frequency <= 489300000: - return "CN_470_510" + return pb_lorawan.Region_CN_470_510.String() } return "" } @@ -89,18 +93,22 @@ func getBand(region string) (band *lora.Band, err error) { var b lora.Band switch region { - case "EU_863_870": + case pb_lorawan.Region_EU_863_870.String(): b, err = lora.GetConfig(lora.EU_863_870) - case "US_902_928": + case pb_lorawan.Region_US_902_928.String(): b, err = lora.GetConfig(lora.US_902_928) - case "CN_779_787": + case pb_lorawan.Region_CN_779_787.String(): err = errors.NewErrInternal("China 779-787 MHz band not supported") - case "EU_433": + case pb_lorawan.Region_EU_433.String(): err = errors.NewErrInternal("Europe 433 MHz band not supported") - case "AU_915_928": + case pb_lorawan.Region_AU_915_928.String(): b, err = lora.GetConfig(lora.AU_915_928) - case "CN_470_510": + case pb_lorawan.Region_CN_470_510.String(): err = errors.NewErrInternal("China 470-510 MHz band not supported") + case pb_lorawan.Region_AS_923.String(): + err = errors.NewErrInternal("Asia 923 MHz band not supported") + case pb_lorawan.Region_SK_920_923.String(): + err = errors.NewErrInternal("South Korea 920-923 MHz band not supported") default: err = errors.NewErrInvalidArgument("Frequency Band", "unknown") } From b64d2488f358d8ea9934054aeaa7ed3e7fd16489 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 11:17:10 +0200 Subject: [PATCH 1882/2266] ttnctl/util.GetConfigFile: check if config exists --- ttnctl/util/config.go | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go index 38e865565..9bb69c1d4 100644 --- a/ttnctl/util/config.go +++ b/ttnctl/util/config.go @@ -28,17 +28,31 @@ const ( // $XDG_CONFIG_HOME/ttnctl/config.yml (if $XDG_CONFIG_HOME is set) // $HOME/.ttnctl.yml func GetConfigFile() string { - file := viper.GetString("config") - if file != "" { - return file - } + flag := viper.GetString("config") xdg := os.Getenv("XDG_CONFIG_HOME") if xdg != "" { - return path.Join(xdg, "ttnctl", "config.yml") + xdg = path.Join(xdg, "ttnctl", "config.yml") + } + + home := os.Getenv("HOME") + if home != "" { + home = path.Join(home, ".ttnctl.yml") + } + + for _, file := range []string{ + flag, + xdg, + home, + } { + if len(file) > 0 { + if _, err := os.Open(file); !os.IsNotExist(err) { + return file + } + } } - return path.Join(os.Getenv("HOME"), ".ttnctl.yml") + return "" } // GetDataDir returns the location of the data directory used for From f9792b378ebe3229e1329500cf55e8009e6aa640 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 6 Oct 2016 11:55:16 +0200 Subject: [PATCH 1883/2266] Add ttnctl components commands --- ttnctl/cmd/components.go | 24 +++++++ ttnctl/cmd/components_add.go | 39 +++++++++++ ttnctl/cmd/components_info.go | 49 +++++++++++++ ttnctl/cmd/components_list.go | 122 +++++++++++++++++++++++++++++++++ ttnctl/cmd/components_token.go | 45 ++++++++++++ 5 files changed, 279 insertions(+) create mode 100644 ttnctl/cmd/components.go create mode 100644 ttnctl/cmd/components_add.go create mode 100644 ttnctl/cmd/components_info.go create mode 100644 ttnctl/cmd/components_list.go create mode 100644 ttnctl/cmd/components_token.go diff --git a/ttnctl/cmd/components.go b/ttnctl/cmd/components.go new file mode 100644 index 000000000..a71688e1f --- /dev/null +++ b/ttnctl/cmd/components.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsCmd = &cobra.Command{ + Use: "components", + Aliases: []string{"component"}, + Short: "Manage network components", + Long: `ttnctl applications can be used to manage network components.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + RootCmd.PersistentPreRun(cmd, args) + util.GetAccount(ctx) + }, +} + +func init() { + RootCmd.AddCommand(componentsCmd) +} diff --git a/ttnctl/cmd/components_add.go b/ttnctl/cmd/components_add.go new file mode 100644 index 000000000..4f3307923 --- /dev/null +++ b/ttnctl/cmd/components_add.go @@ -0,0 +1,39 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsAddCmd = &cobra.Command{ + Use: "add [type] [ComponentID]", + Short: "Add a new network component", + Long: `ttnctl components add can be used to add a new network component.`, + Example: `$ ttnctld components add handler test 146 ! + INFO Added network component id=test type=handler +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + account := util.GetAccount(ctx) + + err := account.CreateComponent(args[0], args[1]) + if err != nil { + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could not add component") + } + + util.ForceRefreshToken(ctx) + + ctx.WithField("type", args[0]).WithField("id", args[1]).Info("Added network component") + }, +} + +func init() { + componentsCmd.AddCommand(componentsAddCmd) +} diff --git a/ttnctl/cmd/components_info.go b/ttnctl/cmd/components_info.go new file mode 100644 index 000000000..ab01e4620 --- /dev/null +++ b/ttnctl/cmd/components_info.go @@ -0,0 +1,49 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsInfoCmd = &cobra.Command{ + Use: "info [type] [ComponentID]", + Short: "Get information about a network component.", + Long: `components info can be used to retreive information about a network component.`, + Example: `$ ttnctl components info handler test + INFO Found network component + +Component ID: test +Type: handler +Created: 2016-10-06 09:52:28.766 +0000 UTC +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + account := util.GetAccount(ctx) + + component, err := account.FindComponent(args[0], args[1]) + if err != nil { + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could not find component") + } + + ctx.Info("Found network component") + + fmt.Println() + fmt.Printf("Component ID: %s\n", component.ID) + fmt.Printf("Type: %s\n", component.Type) + fmt.Printf("Created: %s\n", component.Created) + fmt.Println() + }, +} + +func init() { + componentsCmd.AddCommand(componentsInfoCmd) +} diff --git a/ttnctl/cmd/components_list.go b/ttnctl/cmd/components_list.go new file mode 100644 index 000000000..fcd93b611 --- /dev/null +++ b/ttnctl/cmd/components_list.go @@ -0,0 +1,122 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/gosuri/uitable" + "github.com/spf13/cobra" +) + +func plural(n int, name string) string { + if n == 1 { + return name + } + return fmt.Sprintf("%ss", name) +} + +var componentsListCmd = &cobra.Command{ + Use: "list [type]", + Short: "Get the token for a network component.", + Long: `components token gets a singed token for the component.`, + Example: `$ ttnctld components list 146 ! + INFO Found 0 routers + INFO Found 0 brokers + INFO Found 1 handler + + Type ID +1 handler test +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 1 { + cmd.UsageFunc()(cmd) + return + } + + act := util.GetAccount(ctx) + + components, err := act.ListComponents() + if err != nil { + ctx.WithError(err).Fatal("Could get list network components") + } + + typ := "" + if len(args) == 1 { + typ = args[0] + } + + routers := make([]account.Component, 0) + handlers := make([]account.Component, 0) + brokers := make([]account.Component, 0) + + for _, component := range components { + switch component.Type { + case string(account.Handler): + handlers = append(handlers, component) + case string(account.Broker): + brokers = append(brokers, component) + case string(account.Router): + routers = append(routers, component) + } + } + + if typ == "" || typ == "routers" || typ == "router" { + ctx.Info(fmt.Sprintf("Found %v %s", len(routers), plural(len(routers), "router"))) + + if len(routers) > 0 { + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Type", "ID") + for i, router := range routers { + table.AddRow(i, router.Type, router.ID) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + } + } + + if typ == "" || typ == "brokers" || typ == "broker" { + ctx.Info(fmt.Sprintf("Found %v %s", len(brokers), plural(len(brokers), "broker"))) + + if len(brokers) > 0 { + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Type", "ID") + for i, broker := range brokers { + table.AddRow(i, broker.Type, broker.ID) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + } + } + + if typ == "" || typ == "handlers" || typ == "handler" { + ctx.Info(fmt.Sprintf("Found %v %s", len(handlers), plural(len(handlers), "handler"))) + + if len(handlers) > 0 { + table := uitable.New() + table.MaxColWidth = 70 + table.AddRow("", "Type", "ID") + for i, handler := range handlers { + table.AddRow(i, handler.Type, handler.ID) + } + + fmt.Println() + fmt.Println(table) + fmt.Println() + } + } + }, +} + +func init() { + componentsCmd.AddCommand(componentsListCmd) +} diff --git a/ttnctl/cmd/components_token.go b/ttnctl/cmd/components_token.go new file mode 100644 index 000000000..ef5d0a163 --- /dev/null +++ b/ttnctl/cmd/components_token.go @@ -0,0 +1,45 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var componentsTokenCmd = &cobra.Command{ + Use: "token [type] [ComponentID]", + Short: "Get the token for a network component.", + Long: `components token gets a singed token for the component.`, + Example: `$ ttnctld components token handler test 146 ! + INFO Got component token id=test type=handler + +eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudCIsInN1YiI6InRlc3QxyzJ0eXBlIjoiaGFuZGxlciIsImlhdCI6MTQ3NTc0NzY3MywiZXhwIjoxNDgzNzgyODczfQ.Bf6Gy6xTE2m7fkYSd4WHs3UgRaAEXkox2jjJeaBahVNU365n_wI4_oWX_B3mkMOa1ZL3IB2JagAybo50mTApPtnGiRjDczGjqkkbBiXPcwA8SvmyKTKNkPkrpzGIioq9itjpYDuMJixgLh4gYlK0B_1jkH23ZFoslzn7WfYYe3AKC0JZAhePgQygJ2Zn3w6cGZOqgRvblIIcGynSEqqP3aKyKRhtnwofao-w-jzWqINGvAcMt1iW7JN3hX9yW4IXRicB4_-L0Aaq1sqvRpoh8z9SmpkkE8oBmWqPsUAXTECuoYc4kezjGcDg4YnBfBQtT-itPTfdb8-vq2izxyztsw +`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + account := util.GetAccount(ctx) + + token, err := account.ComponentToken(args[0], args[1]) + if err != nil { + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could get component token") + } + + ctx.WithField("type", args[0]).WithField("id", args[1]).Info("Got component token") + + fmt.Println() + fmt.Println(token) + fmt.Println() + }, +} + +func init() { + componentsCmd.AddCommand(componentsTokenCmd) +} From faecc2e24ef54527da702a30cc38fc2d9f50185f Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 6 Oct 2016 11:55:32 +0200 Subject: [PATCH 1884/2266] Update vendor.json to match new go-account-lib --- vendor/vendor.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index a1ee9c8ae..3b3826043 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,58 +9,58 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "waq4N4OCUwSpA4kjU3abu//ms68=", + "checksumSHA1": "4yiQ3/k0aOGDr1iyjIXwoBu0BKw=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "ANS1jzUqsv1Q1X0gZNzSe9wY0Fo=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "c377c9a91a3c299173f22adb984050ee2dda96b1", - "revisionTime": "2016-09-30T14:52:40Z" + "revision": "4838790e062e94f873817af02a6d08537b9381e1", + "revisionTime": "2016-10-06T09:30:39Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", From b27df7e0868af20436dd64c865173b821e4b946c Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 6 Oct 2016 12:49:47 +0200 Subject: [PATCH 1885/2266] Hide the components command --- ttnctl/cmd/components.go | 1 + 1 file changed, 1 insertion(+) diff --git a/ttnctl/cmd/components.go b/ttnctl/cmd/components.go index a71688e1f..212f81d61 100644 --- a/ttnctl/cmd/components.go +++ b/ttnctl/cmd/components.go @@ -10,6 +10,7 @@ import ( var componentsCmd = &cobra.Command{ Use: "components", + Hidden: true, Aliases: []string{"component"}, Short: "Manage network components", Long: `ttnctl applications can be used to manage network components.`, From 1db1f0df143df4cc9130932dd50173601c95c91e Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:33:57 +0200 Subject: [PATCH 1886/2266] ttnctl/util.GetConfigFile: refactoring --- ttnctl/util/config.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go index 9bb69c1d4..db3f7c010 100644 --- a/ttnctl/util/config.go +++ b/ttnctl/util/config.go @@ -45,8 +45,8 @@ func GetConfigFile() string { xdg, home, } { - if len(file) > 0 { - if _, err := os.Open(file); !os.IsNotExist(err) { + if file != "" { + if _, err := os.Stat(file); err == nil { return file } } From 89e8a139b0e3202b95f452e61a2a14274a38d2c2 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 6 Oct 2016 16:19:30 +0200 Subject: [PATCH 1887/2266] Fix typo's and misspellings --- ttnctl/cmd/components_add.go | 2 +- ttnctl/cmd/components_info.go | 4 ++-- ttnctl/cmd/components_list.go | 2 +- ttnctl/cmd/components_token.go | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ttnctl/cmd/components_add.go b/ttnctl/cmd/components_add.go index 4f3307923..a5162dca6 100644 --- a/ttnctl/cmd/components_add.go +++ b/ttnctl/cmd/components_add.go @@ -9,7 +9,7 @@ import ( ) var componentsAddCmd = &cobra.Command{ - Use: "add [type] [ComponentID]", + Use: "add [Type] [ComponentID]", Short: "Add a new network component", Long: `ttnctl components add can be used to add a new network component.`, Example: `$ ttnctld components add handler test 146 ! diff --git a/ttnctl/cmd/components_info.go b/ttnctl/cmd/components_info.go index ab01e4620..12fa44b95 100644 --- a/ttnctl/cmd/components_info.go +++ b/ttnctl/cmd/components_info.go @@ -11,9 +11,9 @@ import ( ) var componentsInfoCmd = &cobra.Command{ - Use: "info [type] [ComponentID]", + Use: "info [Type] [ComponentID]", Short: "Get information about a network component.", - Long: `components info can be used to retreive information about a network component.`, + Long: `components info can be used to retrieve information about a network component.`, Example: `$ ttnctl components info handler test INFO Found network component diff --git a/ttnctl/cmd/components_list.go b/ttnctl/cmd/components_list.go index fcd93b611..780f1870e 100644 --- a/ttnctl/cmd/components_list.go +++ b/ttnctl/cmd/components_list.go @@ -20,7 +20,7 @@ func plural(n int, name string) string { } var componentsListCmd = &cobra.Command{ - Use: "list [type]", + Use: "list [Type]", Short: "Get the token for a network component.", Long: `components token gets a singed token for the component.`, Example: `$ ttnctld components list 146 ! diff --git a/ttnctl/cmd/components_token.go b/ttnctl/cmd/components_token.go index ef5d0a163..d9fc125ad 100644 --- a/ttnctl/cmd/components_token.go +++ b/ttnctl/cmd/components_token.go @@ -11,9 +11,9 @@ import ( ) var componentsTokenCmd = &cobra.Command{ - Use: "token [type] [ComponentID]", + Use: "token [Type] [ComponentID]", Short: "Get the token for a network component.", - Long: `components token gets a singed token for the component.`, + Long: `components token gets a signed token for the component.`, Example: `$ ttnctld components token handler test 146 ! INFO Got component token id=test type=handler @@ -29,7 +29,7 @@ eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudCIsInN1YiI6InRlc3Q token, err := account.ComponentToken(args[0], args[1]) if err != nil { - ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could get component token") + ctx.WithError(err).WithField("type", args[0]).WithField("id", args[1]).Fatal("Could not get component token") } ctx.WithField("type", args[0]).WithField("id", args[1]).Info("Got component token") From b1526f604dc73bfe5c23ad916d6591da4406efc7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 6 Oct 2016 17:02:00 +0200 Subject: [PATCH 1888/2266] exclude generated files from GO_FILES in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index d7f7cf786..a6145372b 100644 --- a/Makefile +++ b/Makefile @@ -48,7 +48,7 @@ mocks: # Go Test -GO_FILES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor") +GO_FILES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor|.pb.go|_mock.go") GO_PACKAGES = $(shell find . -name "*.go" | grep -vE ".git|.env|vendor" | sed 's:/[^/]*$$::' | sort | uniq) GO_TEST_PACKAGES = $(shell find . -name "*_test.go" | grep -vE ".git|.env|vendor" | sed 's:/[^/]*$$::' | sort | uniq) GO_COVER_PACKAGES = $(shell find . -name "*_test.go" | grep -vE ".git|.env|vendor|ttnctl|cmd|api" | sed 's:/[^/]*$$::' | sort | uniq) From 9dbeacfc141bb7a25fb3d56faa1ff42da8c767e1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 7 Oct 2016 11:08:21 +0200 Subject: [PATCH 1889/2266] Fix networkserver authorize cmd --- cmd/networkserver_authorize.go | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/cmd/networkserver_authorize.go b/cmd/networkserver_authorize.go index 8e861c3fd..faf7bef67 100644 --- a/cmd/networkserver_authorize.go +++ b/cmd/networkserver_authorize.go @@ -8,6 +8,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/utils/security" + jwt "github.com/dgrijalva/jwt-go" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -22,7 +23,7 @@ var networkserverAuthorizeCmd = &cobra.Command{ cmd.UsageFunc()(cmd) } - _, priv, _, err := security.LoadKeys(viper.GetString("key-dir")) + _, privateKey, _, err := security.LoadKeys(viper.GetString("key-dir")) if err != nil { ctx.WithError(err).Fatal("Could not load security keys") } @@ -30,9 +31,23 @@ var networkserverAuthorizeCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Could not read TTL") } - token, err := security.BuildJWT(args[0], time.Duration(ttl)*time.Hour*24, priv) + claims := jwt.StandardClaims{ + Subject: args[0], + Issuer: viper.GetString("id"), + IssuedAt: time.Now().Unix(), + NotBefore: time.Now().Unix(), + } + if ttl > 0 { + claims.ExpiresAt = time.Now().Add(time.Duration(ttl) * time.Hour * 24).Unix() + } + tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + key, err := jwt.ParseECPrivateKeyFromPEM(privateKey) + if err != nil { + ctx.WithError(err).Fatal("Could not parse private kay") + } + token, err := tokenBuilder.SignedString(key) if err != nil { - ctx.WithError(err).Fatal("Could not generate a JWT") + ctx.WithError(err).Fatal("Could not sign JWT") } ctx.WithField("ID", args[0]).Info("Generated NS token") From e740c184e2c721b67870cac364017e6c5fcc95e6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 7 Oct 2016 14:23:15 +0200 Subject: [PATCH 1890/2266] Update README --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 611ffb52c..c402bb0f0 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,11 @@ The Things Network is a global open crowdsourced Internet of Things data network When you get started with The Things Network, you'll probably have some questions. Here are some things you can do to find the answer to them: -- Check out our [website](https://www.thethingsnetwork.org/) and see how [others get started](https://www.thethingsnetwork.org/labs/group/getting-started-with-the-things-network) -- Register on the [forum](http://forum.thethingsnetwork.org) and search around +- Check out our [website](https://www.thethingsnetwork.org/) +- Read the [official documentation](https://www.thethingsnetwork.org/docs/) +- Register on the [forum](https://www.thethingsnetwork.org/forum/) and search around - Join [Slack](https://slack.thethingsnetwork.org) and ask us what you want to know -- Read background information on the [wiki](http://thethingsnetwork.org/wiki) +- Read background information on the [wiki](https://www.thethingsnetwork.org/wiki/) ## Prepare your Development Environment @@ -22,12 +23,12 @@ When you get started with The Things Network, you'll probably have some question 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. Make sure you have [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) **installed** and **running**. - If you're on a Mac, just run `brew bundle`. + If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. Windows users are currently on their own (feel free to contribute a guide for Windows). ## Set up The Things Network's backend for Development 1. Fork this repository -2. Clone your fork: `git clone --branch refactor --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` +2. Clone your fork: `git clone --branch v2-preview --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` 3. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` 4. Install the dependencies for development: `make dev-deps` 5. Run the tests: `make test` From fe20079f4fe0543df74e3a91d252b1d737421fe6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 7 Oct 2016 15:04:40 +0200 Subject: [PATCH 1891/2266] Use Govendor cache --- .gitlab-ci.yml | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 674b4c917..ff339549e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,14 +8,15 @@ variables: CONTAINER_NAME: thethingsnetwork/ttn cache: - key: "$CI_PIPELINE_ID" + key: "$CI_PROJECT_PATH" paths: - - vendor/ + - .govendor before_script: - - rm -rf $GOPATH/src - - mkdir -p $GOPATH/src/github.com/TheThingsNetwork/ttn - - cp -R . $GOPATH/src/github.com/TheThingsNetwork/ttn + - mkdir -p $(pwd)/.govendor + - rm -rf $GOPATH + - mkdir -p $GOPATH/.cache && ln -s $(pwd)/.govendor $GOPATH/.cache/govendor + - mkdir -p $GOPATH/src/github.com/TheThingsNetwork && ln -s $(pwd) $GOPATH/src/github.com/TheThingsNetwork/ttn deps: stage: deps @@ -24,7 +25,6 @@ deps: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps - popd - - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/vendor/* vendor/ tests: stage: test @@ -58,7 +58,6 @@ binaries: - GOOS=windows GOARCH=386 make build - GOOS=windows GOARCH=amd64 make build - popd - - cp -R $GOPATH/src/github.com/TheThingsNetwork/ttn/release/* release/ artifacts: paths: - release/ @@ -69,7 +68,6 @@ gitlab-image: services: - "docker:dind" script: - - cd $GOPATH/src/github.com/TheThingsNetwork/ttn - docker build -t ttn . - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" registry.gitlab.com - docker tag ttn registry.gitlab.com/$CONTAINER_NAME:$CI_BUILD_REF_NAME @@ -77,14 +75,13 @@ gitlab-image: dockerhub-image: only: - - master@thethingsnetwork/ttn - - refactor@thethingsnetwork/ttn + - v1-staging@thethingsnetwork/ttn + - v2-preview@thethingsnetwork/ttn stage: package image: docker:git services: - "docker:dind" script: - - cd $GOPATH/src/github.com/TheThingsNetwork/ttn - docker build -t ttn . - docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_PASSWORD" - docker tag ttn $CONTAINER_NAME:$CI_BUILD_REF_NAME @@ -92,8 +89,8 @@ dockerhub-image: azure-binaries: only: - - master@thethingsnetwork/ttn - - refactor@thethingsnetwork/ttn + - v1-staging@thethingsnetwork/ttn + - v2-preview@thethingsnetwork/ttn stage: package image: registry.gitlab.com/thethingsindustries/upload script: From ca3e2416844cbb4880ab8aba7439487ccd0d4634 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 7 Oct 2016 17:16:34 +0200 Subject: [PATCH 1892/2266] Add go get to Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a6145372b..3d7414394 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ deps: build-deps dev-deps: deps @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast @command -v mockgen > /dev/null || go get github.com/golang/mock/mockgen - @command -v golint > /dev/null || github.com/golang/lint/golint + @command -v golint > /dev/null || go get github.com/golang/lint/golint @command -v forego > /dev/null || go get github.com/ddollar/forego # Protobuf From aaaf64781802df5a582214f44ac72ab962ca425c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 7 Oct 2016 17:29:03 +0200 Subject: [PATCH 1893/2266] Update docker-compose.yml --- docker-compose.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 64da768eb..afa7b6431 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: image: thethingsnetwork/ttn working_dir: /root command: discovery --config ./.env/discovery/dev.yml + depends_on: [ redis ] environment: TTN_DISCOVERY_REDIS_ADDRESS: redis:6379 ports: @@ -25,6 +26,7 @@ services: image: thethingsnetwork/ttn working_dir: /root command: router --config ./.env/router/dev.yml + depends_on: [ discovery ] environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE: router @@ -36,6 +38,7 @@ services: image: thethingsnetwork/ttn working_dir: /root command: broker --config ./.env/broker/dev.yml + depends_on: [ discovery, networkserver ] environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_BROKER_SERVER_ADDRESS_ANNOUNCE: broker @@ -48,6 +51,7 @@ services: image: thethingsnetwork/ttn working_dir: /root command: networkserver --config ./.env/networkserver/dev.yml + depends_on: [ redis ] environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_NETWORKSERVER_REDIS_ADDRESS: redis:6379 @@ -59,6 +63,8 @@ services: image: thethingsnetwork/ttn working_dir: /root command: handler --config ./.env/handler/dev.yml + depends_on: [ discovery ] + depends_on: [ redis ] environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE: handler @@ -68,3 +74,13 @@ services: - "1904:1904" volumes: - "./.env/:/root/.env/" + bridge: + image: thethingsnetwork/lora-gateway-bridge + ports: + - "1700:1700/udp" + restart: always + depends_on: [ router ] + environment: + - UDP_BIND=:1700 + - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_ROUTER=dev From 1fa7f4c6d58aba5786062f3c1c46d01120d6d0d0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 10 Oct 2016 12:45:25 +0200 Subject: [PATCH 1894/2266] Make link should depend on build [Skip CI] --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3d7414394..42e63aee3 100644 --- a/Makefile +++ b/Makefile @@ -115,7 +115,7 @@ build: ttn ttnctl GOBIN ?= $(GOPATH)/bin -link: ttn +link: build ln -sf $(PWD)/$(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttn ln -sf $(PWD)/$(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttnctl From fa8f8a6b1991e32164462db72c2cfba0b4af8efd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 10 Oct 2016 13:48:55 +0200 Subject: [PATCH 1895/2266] Update URL for preview account server --- .env/broker/dev.yml | 3 ++- .env/discovery/dev.yml | 3 ++- .env/handler/dev.yml | 3 ++- .env/networkserver/dev.yml | 3 ++- .env/router/dev.yml | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index b5663da66..99363a562 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -3,7 +3,8 @@ debug: true discovery-server: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" - ttn-account-staging: "https://staging.account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" + ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/broker/" diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index 141da5c35..217a4abb8 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -3,5 +3,6 @@ debug: true discovery-server: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" - ttn-account-staging: "https://staging.account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" + ttn-account-staging: "https://preview.account.thethingsnetwork.org" key-dir: "./.env/discovery/" diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 2541329c6..28c47a515 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -3,7 +3,8 @@ debug: true discovery-server: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" - ttn-account-staging: "https://staging.account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" + ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/handler/" diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index b53933306..3b67552b2 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -2,6 +2,7 @@ debug: true discovery-server: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" - ttn-account-staging: "https://staging.account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" + ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/networkserver/" diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 4bcbddc01..b78d9a76b 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -3,7 +3,8 @@ debug: true discovery-server: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" - ttn-account-staging: "https://staging.account.thethingsnetwork.org" + ttn-account-preview: "https://preview.account.thethingsnetwork.org" + ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/router/" From 345477d18144dba7ad49d88ef6c76918a194a41e Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Fri, 7 Oct 2016 00:10:24 +0200 Subject: [PATCH 1896/2266] api.{ID,Token}FromContext: helper functions --- api/context.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 api/context.go diff --git a/api/context.go b/api/context.go new file mode 100644 index 000000000..1e3eafbd0 --- /dev/null +++ b/api/context.go @@ -0,0 +1,24 @@ +package api + +import ( + context "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +func TokenFromContext(ctx context.Context) (token string, err error) { + var md metadata.MD + if md, err = MetadataFromContext(ctx); err != nil { + return "", err + } + + return TokenFromMetadata(md) +} + +func IDFromContext(ctx context.Context) (token string, err error) { + var md metadata.MD + if md, err = MetadataFromContext(ctx); err != nil { + return "", err + } + + return IDFromMetadata(md) +} From ad946983876e1f4d17d50ca8bd6972fb65197acf Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Fri, 7 Oct 2016 00:11:28 +0200 Subject: [PATCH 1897/2266] api: fix `context` import formatting --- api/context.go | 2 +- api/metadata.go | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/api/context.go b/api/context.go index 1e3eafbd0..aa11f2234 100644 --- a/api/context.go +++ b/api/context.go @@ -1,7 +1,7 @@ package api import ( - context "golang.org/x/net/context" + context "golang.org/x/net/context" //TODO change to "context", when protoc supports it "google.golang.org/grpc/metadata" ) diff --git a/api/metadata.go b/api/metadata.go index f036975c8..84251aa94 100644 --- a/api/metadata.go +++ b/api/metadata.go @@ -1,8 +1,7 @@ package api import ( - // TODO change to "context", when protoc supports it - context "golang.org/x/net/context" + context "golang.org/x/net/context" //TODO change to "context", when protoc supports it "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc/metadata" From 2f1582ed14ab2f2a77e89403ebacc5a735e2cc75 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 10 Oct 2016 16:03:18 +0200 Subject: [PATCH 1898/2266] Split generating keypair and cert for ttn --- cmd/genkeys.go | 41 +++++++++++++++++++-------- cmd/networkserver_authorize.go | 9 ++---- core/component.go | 28 ++++++++++++++----- utils/security/convert_keys.go | 33 ++++++++++++++++++++++ utils/security/generate_keys.go | 49 ++++++++++++++++++--------------- utils/security/load_keys.go | 24 ++++++++++++---- 6 files changed, 132 insertions(+), 52 deletions(-) create mode 100644 utils/security/convert_keys.go diff --git a/cmd/genkeys.go b/cmd/genkeys.go index 0f717485d..51828c818 100644 --- a/cmd/genkeys.go +++ b/cmd/genkeys.go @@ -9,19 +9,31 @@ import ( "github.com/spf13/viper" ) -// genkeysCmd represents the genkeys command -func genkeysCmd(component string) *cobra.Command { +func genKeypairCmd(component string) *cobra.Command { return &cobra.Command{ - Use: "genkeys", - Short: "Generate keys and certificate", - Long: `ttn genkeys generates keys and a TLS certificate for this component`, + Use: "gen-keypair", + Short: "Generate a public/private keypair", + Long: `ttn gen-keypair generates a public/private keypair`, + Run: func(cmd *cobra.Command, args []string) { + if err := security.GenerateKeypair(viper.GetString("key-dir")); err != nil { + ctx.WithError(err).Fatal("Could not generate keypair") + } + ctx.WithField("TLSDir", viper.GetString("key-dir")).Info("Done") + }, + } +} + +func genCertCmd(component string) *cobra.Command { + return &cobra.Command{ + Use: "gen-cert", + Short: "Generate a TLS certificate", + Long: `ttn gen-cert generates a TLS Certificate`, Run: func(cmd *cobra.Command, args []string) { var names []string names = append(names, viper.GetString(component+".server-address-announce")) names = append(names, args...) - err := security.GenerateKeys(viper.GetString("key-dir"), names...) - if err != nil { - ctx.WithError(err).Fatal("Could not generate keys") + if err := security.GenerateCert(viper.GetString("key-dir"), names...); err != nil { + ctx.WithError(err).Fatal("Could not generate certificate") } ctx.WithField("TLSDir", viper.GetString("key-dir")).Info("Done") }, @@ -29,8 +41,13 @@ func genkeysCmd(component string) *cobra.Command { } func init() { - routerCmd.AddCommand(genkeysCmd("router")) - brokerCmd.AddCommand(genkeysCmd("broker")) - handlerCmd.AddCommand(genkeysCmd("handler")) - networkserverCmd.AddCommand(genkeysCmd("networkserver")) + routerCmd.AddCommand(genKeypairCmd("router")) + brokerCmd.AddCommand(genKeypairCmd("broker")) + handlerCmd.AddCommand(genKeypairCmd("handler")) + networkserverCmd.AddCommand(genKeypairCmd("networkserver")) + + routerCmd.AddCommand(genCertCmd("router")) + brokerCmd.AddCommand(genCertCmd("broker")) + handlerCmd.AddCommand(genCertCmd("handler")) + networkserverCmd.AddCommand(genCertCmd("networkserver")) } diff --git a/cmd/networkserver_authorize.go b/cmd/networkserver_authorize.go index faf7bef67..31ad4bd35 100644 --- a/cmd/networkserver_authorize.go +++ b/cmd/networkserver_authorize.go @@ -23,10 +23,11 @@ var networkserverAuthorizeCmd = &cobra.Command{ cmd.UsageFunc()(cmd) } - _, privateKey, _, err := security.LoadKeys(viper.GetString("key-dir")) + privKey, err := security.LoadKeypair(viper.GetString("key-dir")) if err != nil { ctx.WithError(err).Fatal("Could not load security keys") } + ttl, err := cmd.Flags().GetInt("valid") if err != nil { ctx.WithError(err).Fatal("Could not read TTL") @@ -41,11 +42,7 @@ var networkserverAuthorizeCmd = &cobra.Command{ claims.ExpiresAt = time.Now().Add(time.Duration(ttl) * time.Hour * 24).Unix() } tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) - key, err := jwt.ParseECPrivateKeyFromPEM(privateKey) - if err != nil { - ctx.WithError(err).Fatal("Could not parse private kay") - } - token, err := tokenBuilder.SignedString(key) + token, err := tokenBuilder.SignedString(privKey) if err != nil { ctx.WithError(err).Fatal("Could not sign JWT") } diff --git a/core/component.go b/core/component.go index 76897af89..893e40f85 100644 --- a/core/component.go +++ b/core/component.go @@ -4,6 +4,7 @@ package core import ( + "crypto/ecdsa" "crypto/tls" "fmt" "net/http" @@ -88,13 +89,22 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } } - if pub, priv, cert, err := security.LoadKeys(viper.GetString("key-dir")); err == nil { - component.Identity.PublicKey = string(pub) - component.privateKey = string(priv) + if priv, err := security.LoadKeypair(viper.GetString("key-dir")); err == nil { + component.privateKey = priv + + pubPEM, _ := security.PublicPEM(priv) + component.Identity.PublicKey = string(pubPEM) + + privPEM, _ := security.PrivatePEM(priv) if viper.GetBool("tls") { + cert, err := security.LoadCert(viper.GetString("key-dir")) + if err != nil { + return nil, err + } component.Identity.Certificate = string(cert) - cer, err := tls.X509KeyPair(cert, priv) + + cer, err := tls.X509KeyPair(cert, privPEM) if err != nil { return nil, err } @@ -138,7 +148,7 @@ type Component struct { Monitor pb_noc.MonitorClient Ctx log.Interface AccessToken string - privateKey string + privateKey *ecdsa.PrivateKey tlsConfig *tls.Config TokenKeyProvider tokenkey.Provider status int64 @@ -350,8 +360,12 @@ func (c *Component) ServerOptions() []grpc.ServerOption { // BuildJWT builds a short-lived JSON Web Token for this component func (c *Component) BuildJWT() (string, error) { - if c.privateKey != "" { - return security.BuildJWT(c.Identity.Id, 10*time.Second, []byte(c.privateKey)) + if c.privateKey != nil { + privPEM, err := security.PrivatePEM(c.privateKey) + if err != nil { + return "", err + } + return security.BuildJWT(c.Identity.Id, 10*time.Second, privPEM) } return "", nil } diff --git a/utils/security/convert_keys.go b/utils/security/convert_keys.go new file mode 100644 index 000000000..6d98dcb49 --- /dev/null +++ b/utils/security/convert_keys.go @@ -0,0 +1,33 @@ +package security + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" +) + +// PublicPEM returns the PEM-encoded public key +func PublicPEM(key *ecdsa.PrivateKey) ([]byte, error) { + pubBytes, err := x509.MarshalPKIXPublicKey(key.Public()) + if err != nil { + return nil, err + } + pubPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: pubBytes, + }) + return pubPEM, nil +} + +// PrivatePEM returns the PEM-encoded private key +func PrivatePEM(key *ecdsa.PrivateKey) ([]byte, error) { + privBytes, err := x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + privPEM := pem.EncodeToMemory(&pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: privBytes, + }) + return privPEM, nil +} diff --git a/utils/security/generate_keys.go b/utils/security/generate_keys.go index 73a03e612..c0ae16c34 100644 --- a/utils/security/generate_keys.go +++ b/utils/security/generate_keys.go @@ -18,28 +18,41 @@ var ( validFor = 365 * 24 * time.Hour ) -func GenerateKeys(location string, hostnames ...string) error { +// GenerateKeypair generates a new keypair in the given location +func GenerateKeypair(location string) error { // Generate private key key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { return err } - privBytes, err := x509.MarshalECPrivateKey(key) + + privPEM, err := PrivatePEM(key) if err != nil { return err } - privPEM := pem.EncodeToMemory(&pem.Block{ - Type: "EC PRIVATE KEY", - Bytes: privBytes, - }) - pubBytes, err := x509.MarshalPKIXPublicKey(key.Public()) + pubPEM, err := PublicPEM(key) + if err != nil { + return err + } + + err = ioutil.WriteFile(filepath.Clean(location+"/server.pub"), pubPEM, 0644) + if err != nil { + return err + } + err = ioutil.WriteFile(filepath.Clean(location+"/server.key"), privPEM, 0600) + if err != nil { + return err + } + + return nil +} + +// GenerateCert generates a certificate for the given hostnames in the given location +func GenerateCert(location string, hostnames ...string) error { + privKey, err := LoadKeypair(location) if err != nil { return err } - pubPEM := pem.EncodeToMemory(&pem.Block{ - Type: "PUBLIC KEY", - Bytes: pubBytes, - }) // Build Certificate notBefore := time.Now() @@ -57,8 +70,8 @@ func GenerateKeys(location string, hostnames ...string) error { IsCA: true, NotBefore: notBefore, NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, } for _, h := range hostnames { @@ -70,7 +83,7 @@ func GenerateKeys(location string, hostnames ...string) error { } // Generate certificate - certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, key.Public(), key) + certBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, privKey.Public(), privKey) if err != nil { return err } @@ -79,14 +92,6 @@ func GenerateKeys(location string, hostnames ...string) error { Bytes: certBytes, }) - err = ioutil.WriteFile(filepath.Clean(location+"/server.pub"), pubPEM, 0644) - if err != nil { - return err - } - err = ioutil.WriteFile(filepath.Clean(location+"/server.key"), privPEM, 0600) - if err != nil { - return err - } err = ioutil.WriteFile(filepath.Clean(location+"/server.cert"), certPEM, 0644) if err != nil { return err diff --git a/utils/security/load_keys.go b/utils/security/load_keys.go index 74b78f13c..1a558106e 100644 --- a/utils/security/load_keys.go +++ b/utils/security/load_keys.go @@ -1,19 +1,33 @@ package security import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" "io/ioutil" "path/filepath" ) -func LoadKeys(location string) (pubKey, privKey, cert []byte, err error) { - pubKey, err = ioutil.ReadFile(filepath.Clean(location + "/server.pub")) +// LoadKeypair loads the keypair in the given location +func LoadKeypair(location string) (*ecdsa.PrivateKey, error) { + priv, err := ioutil.ReadFile(filepath.Clean(location + "/server.key")) if err != nil { - return + return nil, err + } + privBlock, _ := pem.Decode(priv) + if privBlock == nil { + return nil, errors.New("No private key data found") } - privKey, err = ioutil.ReadFile(filepath.Clean(location + "/server.key")) + privKey, err := x509.ParseECPrivateKey(privBlock.Bytes) if err != nil { - return + return nil, err } + return privKey, nil +} + +// LoadCert loads the certificate in the given location +func LoadCert(location string) (cert []byte, err error) { cert, err = ioutil.ReadFile(filepath.Clean(location + "/server.cert")) if err != nil { return From 5e5e131c9a14529e98798815a398d06075f766f3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 10 Oct 2016 16:06:57 +0200 Subject: [PATCH 1899/2266] Reformat depends_on in docker-compose.yml --- docker-compose.yml | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index afa7b6431..b53917f8c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,7 +15,8 @@ services: image: thethingsnetwork/ttn working_dir: /root command: discovery --config ./.env/discovery/dev.yml - depends_on: [ redis ] + depends_on: + - redis environment: TTN_DISCOVERY_REDIS_ADDRESS: redis:6379 ports: @@ -26,7 +27,8 @@ services: image: thethingsnetwork/ttn working_dir: /root command: router --config ./.env/router/dev.yml - depends_on: [ discovery ] + depends_on: + - discovery environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE: router @@ -38,7 +40,9 @@ services: image: thethingsnetwork/ttn working_dir: /root command: broker --config ./.env/broker/dev.yml - depends_on: [ discovery, networkserver ] + depends_on: + - discovery + - networkserver environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_BROKER_SERVER_ADDRESS_ANNOUNCE: broker @@ -51,7 +55,8 @@ services: image: thethingsnetwork/ttn working_dir: /root command: networkserver --config ./.env/networkserver/dev.yml - depends_on: [ redis ] + depends_on: + - redis environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_NETWORKSERVER_REDIS_ADDRESS: redis:6379 @@ -63,8 +68,10 @@ services: image: thethingsnetwork/ttn working_dir: /root command: handler --config ./.env/handler/dev.yml - depends_on: [ discovery ] - depends_on: [ redis ] + depends_on: + - discovery + - redis + - mosquitto environment: TTN_DISCOVERY_SERVER: discovery:1900 TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE: handler From 13e3c6d08627ba580160165e20b25c00161c4e18 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 10 Oct 2016 16:31:36 +0200 Subject: [PATCH 1900/2266] Implement LinkCheck MAC command --- core/networkserver/mac.go | 39 +++++++++++++++++++++++++++++++ core/networkserver/mac_test.go | 25 ++++++++++++++++++++ core/networkserver/uplink.go | 17 ++++++++++++-- core/networkserver/uplink_test.go | 16 +++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 core/networkserver/mac.go create mode 100644 core/networkserver/mac_test.go diff --git a/core/networkserver/mac.go b/core/networkserver/mac.go new file mode 100644 index 000000000..53d665bd5 --- /dev/null +++ b/core/networkserver/mac.go @@ -0,0 +1,39 @@ +package networkserver + +import ( + "sort" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" +) + +type bySNR []*pb_gateway.RxMetadata + +func (a bySNR) Len() int { return len(a) } +func (a bySNR) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a bySNR) Less(i, j int) bool { return a[i].Snr < a[j].Snr } + +func bestSNR(metadata []*pb_gateway.RxMetadata) float32 { + if len(metadata) == 0 { + return 0 + } + sorted := bySNR(metadata) + sort.Sort(sorted) + return sorted[len(sorted)-1].Snr +} + +var demodulationFloor = map[string]float32{ + "SF7BW125": -7.5, + "SF8BW125": -10, + "SF9BW125": -12.5, + "SF10BW125": -15, + "SF11BW125": -17.5, + "SF12BW125": -20, + "SF7BW250": -4.5, +} + +func linkMargin(dataRate string, snr float32) float32 { + if floor, ok := demodulationFloor[dataRate]; ok { + return snr - floor + } + return 0 +} diff --git a/core/networkserver/mac_test.go b/core/networkserver/mac_test.go new file mode 100644 index 000000000..61af1ff85 --- /dev/null +++ b/core/networkserver/mac_test.go @@ -0,0 +1,25 @@ +package networkserver + +import ( + "testing" + + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + . "github.com/smartystreets/assertions" +) + +func TestBestSNR(t *testing.T) { + a := New(t) + best := bestSNR([]*pb_gateway.RxMetadata{ + &pb_gateway.RxMetadata{Snr: 1}, + &pb_gateway.RxMetadata{Snr: 2}, + &pb_gateway.RxMetadata{Snr: 0}, + &pb_gateway.RxMetadata{Snr: 10}, + &pb_gateway.RxMetadata{Snr: -10}, + }) + a.So(best, ShouldEqual, 10) +} + +func TestLinkMargin(t *testing.T) { + a := New(t) + a.So(linkMargin("SF7BW125", 4.3), ShouldEqual, 11.8) +} diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go index 499d4a7c2..3f5d0b45f 100644 --- a/core/networkserver/uplink.go +++ b/core/networkserver/uplink.go @@ -30,7 +30,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } // Update FCntUp (from metadata if possible, because only 16lsb are marshaled in FHDR) - if lorawan := message.GetProtocolMetadata().GetLorawan(); lorawan != nil { + if lorawan := message.GetProtocolMetadata().GetLorawan(); lorawan != nil && lorawan.FCnt != 0 { dev.FCntUp = lorawan.FCnt } else { dev.FCntUp = macPayload.FHDR.FCnt @@ -86,7 +86,20 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag } } - // TODO: We might need to add MAC commands on downlink + // MAC Commands + for _, cmd := range macPayload.FHDR.FOpts { + switch cmd.CID { + case lorawan.LinkCheckReq: + mac.FHDR.FOpts = append(mac.FHDR.FOpts, lorawan.MACCommand{ + CID: lorawan.LinkCheckAns, + Payload: &lorawan.LinkCheckAnsPayload{ + Margin: uint8(linkMargin(message.GetProtocolMetadata().GetLorawan().DataRate, bestSNR(message.GetGatewayMetadata()))), + GwCnt: uint8(len(message.GatewayMetadata)), + }, + }) + default: + } + } phyBytes, err := phy.MarshalBinary() if err != nil { diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go index 9ada13d8e..d7f946cc3 100644 --- a/core/networkserver/uplink_test.go +++ b/core/networkserver/uplink_test.go @@ -8,6 +8,9 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/brocaar/lorawan" @@ -61,6 +64,9 @@ func TestHandleUplink(t *testing.T) { ADR: true, ADRACKReq: true, }, + FOpts: []lorawan.MACCommand{ + lorawan.MACCommand{CID: lorawan.LinkCheckReq}, + }, }, }, } @@ -72,6 +78,14 @@ func TestHandleUplink(t *testing.T) { DevEui: &devEUI, Payload: bytes, ResponseTemplate: &pb_broker.DownlinkMessage{}, + GatewayMetadata: []*pb_gateway.RxMetadata{ + &pb_gateway.RxMetadata{}, + }, + ProtocolMetadata: &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{ + Lorawan: &pb_lorawan.Metadata{ + DataRate: "SF7BW125", + }, + }}, } res, err := ns.HandleUplink(message) a.So(err, ShouldBeNil) @@ -87,6 +101,8 @@ func TestHandleUplink(t *testing.T) { // ResponseTemplate should ACK the ADRACKReq a.So(macPayload.FHDR.FCtrl.ACK, ShouldBeTrue) + a.So(macPayload.FHDR.FOpts, ShouldHaveLength, 1) + a.So(macPayload.FHDR.FOpts[0].Payload, ShouldResemble, &lorawan.LinkCheckAnsPayload{GwCnt: 1, Margin: 7}) // Frame Counter should have been updated dev, _ := ns.devices.Get(appEUI, devEUI) From eea57b3b4e71606807cbac19ee21729f0bc21f9c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 17:16:20 +0200 Subject: [PATCH 1901/2266] api/context: refactor --- api/context.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/api/context.go b/api/context.go index aa11f2234..04efecec7 100644 --- a/api/context.go +++ b/api/context.go @@ -1,24 +1,19 @@ package api -import ( - context "golang.org/x/net/context" //TODO change to "context", when protoc supports it - "google.golang.org/grpc/metadata" -) +import context "golang.org/x/net/context" func TokenFromContext(ctx context.Context) (token string, err error) { - var md metadata.MD - if md, err = MetadataFromContext(ctx); err != nil { + md, err := MetadataFromContext(ctx) + if err != nil { return "", err } - return TokenFromMetadata(md) } func IDFromContext(ctx context.Context) (token string, err error) { - var md metadata.MD - if md, err = MetadataFromContext(ctx); err != nil { + md, err := MetadataFromContext(ctx) + if err != nil { return "", err } - return IDFromMetadata(md) } From b28c96529a7ac1ce7ac2571867bdfe3e75e458b4 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 10 Oct 2016 17:55:28 +0200 Subject: [PATCH 1902/2266] Fix ttnctl user logout cmd file location --- ttnctl/cmd/user_logout.go | 6 ++++++ ttnctl/util/account.go | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/user_logout.go b/ttnctl/cmd/user_logout.go index 1707b8dbd..a31d5ac61 100644 --- a/ttnctl/cmd/user_logout.go +++ b/ttnctl/cmd/user_logout.go @@ -4,6 +4,8 @@ package cmd import ( + "os" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) @@ -15,6 +17,10 @@ var userLogoutCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { err := util.Logout() if err != nil { + if os.IsNotExist(err) { + ctx.Info("You were not logged in") + return + } ctx.WithError(err).Fatal("Could not delete credentials") } }, diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 7015c4289..b3964e7dd 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -162,12 +162,12 @@ func TokenForScope(ctx log.Interface, scope string) string { } func Logout() error { - err := os.Remove(path.Join(viper.GetString("token-dir"), tokenFile())) + err := os.Remove(path.Join(GetDataDir(), tokenFile())) if err != nil { return err } - err = os.Remove(path.Join(viper.GetString("token-dir"), derivedTokenFile())) + err = os.Remove(path.Join(GetDataDir(), derivedTokenFile())) if err != nil && !os.IsNotExist(err) { return err } From 4cdbb2643a072850d201d914af00ac0add6344da Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 10 Oct 2016 17:59:19 +0200 Subject: [PATCH 1903/2266] Fix preview account server URL in ttnctl.yml dev example --- .env/ttnctl.yml.dev-example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.env/ttnctl.yml.dev-example b/.env/ttnctl.yml.dev-example index c8a80dc0c..b702ddc82 100644 --- a/.env/ttnctl.yml.dev-example +++ b/.env/ttnctl.yml.dev-example @@ -2,4 +2,4 @@ discovery-server: localhost:1900 mqtt-broker: localhost:1883 ttn-handler: dev ttn-router: dev -ttn-account-server: https://staging.account.thethingsnetwork.org +ttn-account-server: https://preview.account.thethingsnetwork.org From 89195207386e957716d07684ce874a190934a398 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 11 Oct 2016 14:51:20 +0200 Subject: [PATCH 1904/2266] Show correct default configuration file --- cmd/root.go | 2 +- ttnctl/cmd/root.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 166cfe7e3..63dc8e34b 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -113,7 +113,7 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yaml\")") + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yml\")") RootCmd.PersistentFlags().String("id", "", "The id of this component") viper.BindPFlag("id", RootCmd.PersistentFlags().Lookup("id")) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index a039213ad..c82689667 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -63,7 +63,7 @@ func Execute() { func init() { cobra.OnInitialize(initConfig) - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yaml)") + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yml)") RootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode") RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") From 421b0c74df0582c09bf865040c5e51807a7dd368 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 14:14:53 +0200 Subject: [PATCH 1905/2266] Only set changed fields in networkserver --- core/networkserver/device/store.go | 31 ++++++++------- core/networkserver/manager_server.go | 57 +++++++++++++++++++++------- 2 files changed, 62 insertions(+), 26 deletions(-) diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index ff0916729..695ad1b34 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -120,7 +120,7 @@ func (s *deviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr dev.NwkSKey = nwkSKey dev.FCntUp = 0 dev.FCntDown = 0 - return s.Set(dev) + return s.Set(dev, "last_seen", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") } func (s *deviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { @@ -249,33 +249,38 @@ func (s *redisDeviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, err func (s *redisDeviceStore) Set(new *Device, fields ...string) error { if len(fields) == 0 { fields = DeviceProperties + } else { + fields = append(fields, "updated_at") } key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppEUI, new.DevEUI) // Check for old DevAddr - if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { - // Delete old DevAddr - if devAddr != "" { - err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() - if err != nil { - return err - } - } + oldDevAddrStr, err := s.client.HGet(key, "dev_addr").Result() + if err != nil && err != redis.Nil { + return err } + oldDevAddr, _ := types.ParseDevAddr(oldDevAddrStr) new.UpdatedAt = time.Now() dmap, err := new.ToStringStringMap(fields...) if err != nil { return err } - s.client.HMSetMap(key, dmap) + if err := s.client.HMSetMap(key, dmap).Err(); err != nil { + return err + } - if !new.DevAddr.IsEmpty() && !new.NwkSKey.IsEmpty() { - err := s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, new.DevAddr), key).Err() - if err != nil { + // Update DevAddr lookup if needed + if new.DevAddr != oldDevAddr { + if err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, oldDevAddr), key).Err(); err != nil && err != redis.Nil { return err } + if !new.DevAddr.IsEmpty() { + if err := s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, new.DevAddr), key).Err(); err != nil { + return err + } + } } return nil diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 591a7fb61..c7f018894 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -67,7 +67,7 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev } func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*empty.Empty, error) { - _, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) + dev, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) if err != nil && errors.GetErrType(err) != errors.NotFound { return nil, errors.BuildGRPCError(err) } @@ -76,26 +76,57 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") } - updated := &device.Device{ - AppID: in.AppId, - AppEUI: *in.AppEui, - DevID: in.DevId, - DevEUI: *in.DevEui, - FCntUp: in.FCntUp, - FCntDown: in.FCntDown, - Options: device.Options{ + if dev == nil { + dev = new(device.Device) + } + fields := []string{} + + if dev.AppID != in.AppId { + dev.AppID = in.AppId + fields = append(fields, "app_id") + } + + if dev.AppEUI != *in.AppEui { + dev.AppEUI = *in.AppEui + fields = append(fields, "app_eui") + } + + if dev.DevID != in.DevId { + dev.DevID = in.DevId + fields = append(fields, "dev_id") + } + + if dev.DevEUI != *in.DevEui { + dev.DevEUI = *in.DevEui + fields = append(fields, "dev_eui") + } + + if dev.FCntUp != in.FCntUp { + dev.FCntUp = in.FCntUp + fields = append(fields, "f_cnt_up") + } + + if dev.FCntDown != in.FCntDown { + dev.FCntDown = in.FCntDown + fields = append(fields, "f_cnt_down") + } + + if dev.Options.DisableFCntCheck != in.DisableFCntCheck || dev.Options.Uses32BitFCnt != in.Uses32BitFCnt || dev.Options.ActivationConstraints != in.ActivationConstraints { + dev.Options = device.Options{ DisableFCntCheck: in.DisableFCntCheck, Uses32BitFCnt: in.Uses32BitFCnt, ActivationConstraints: in.ActivationConstraints, - }, + } + fields = append(fields, "options") } if in.NwkSKey != nil && in.DevAddr != nil { - updated.DevAddr = *in.DevAddr - updated.NwkSKey = *in.NwkSKey + dev.DevAddr = *in.DevAddr + dev.NwkSKey = *in.NwkSKey + fields = append(fields, "dev_addr", "nwk_s_key") } - err = n.networkServer.devices.Set(updated) + err = n.networkServer.devices.Set(dev, fields...) if err != nil { return nil, errors.BuildGRPCError(err) } From a59216a366c07035c08f4f6d7c218d5aa8a37cc4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 14:44:18 +0200 Subject: [PATCH 1906/2266] Also check for rights to new device in networkserver manager --- core/networkserver/manager_server.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index c7f018894..e9bcf67f0 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -73,7 +73,15 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev } if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Device Identifier") + return nil, grpcErrf(codes.InvalidArgument, "Invalid Device") + } + + claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", dev.AppID)) } if dev == nil { From 116b57b867b8aa2b0b9e7ccc530e641f452ec759 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 14:48:31 +0200 Subject: [PATCH 1907/2266] Only set changed fields in handler --- core/handler/application/store.go | 2 + core/handler/device/store.go | 2 + core/handler/manager_server.go | 78 ++++++++++++++++++++++--------- 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 07c058cac..1beeb7be1 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -117,6 +117,8 @@ func (s *redisApplicationStore) Get(appID string) (*Application, error) { func (s *redisApplicationStore) Set(new *Application, fields ...string) error { if len(fields) == 0 { fields = ApplicationProperties + } else { + fields = append(fields, "updated_at") } key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppID) diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 6d228c35c..f47ca05db 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -166,6 +166,8 @@ func (s *redisDeviceStore) Get(appID, devID string) (*Device, error) { func (s *redisDeviceStore) Set(new *Device, fields ...string) error { if len(fields) == 0 { fields = DeviceProperties + } else { + fields = append(fields, "updated_at") } key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppID, new.DevID) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index d29f70529..a510ebd1b 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -110,31 +110,51 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } } } else { // When this is a create + dev = new(device.Device) + } + fields := []string{} + + if dev.AppID != in.AppId { + dev.AppID = in.AppId + fields = append(fields, "app_id") + } + + if dev.AppEUI != *lorawan.AppEui { + dev.AppEUI = *lorawan.AppEui + fields = append(fields, "app_eui") + } + if dev.DevID != in.DevId { + dev.DevID = in.DevId + fields = append(fields, "dev_id") } - updated := &device.Device{ - AppID: in.AppId, - DevID: in.DevId, - AppEUI: *lorawan.AppEui, - DevEUI: *lorawan.DevEui, + if dev.DevEUI != *lorawan.DevEui { + dev.DevEUI = *lorawan.DevEui + fields = append(fields, "dev_eui") } if lorawan.DevAddr != nil { - updated.DevAddr = *lorawan.DevAddr + dev.DevAddr = *lorawan.DevAddr + fields = append(fields, "dev_addr") } if lorawan.NwkSKey != nil { - updated.NwkSKey = *lorawan.NwkSKey + dev.NwkSKey = *lorawan.NwkSKey + fields = append(fields, "nwk_s_key") } if lorawan.AppSKey != nil { - updated.AppSKey = *lorawan.AppSKey + dev.AppSKey = *lorawan.AppSKey + fields = append(fields, "app_s_key") } + if lorawan.AppKey != nil { - updated.AppKey = *lorawan.AppKey - if dev != nil && dev.AppKey != *lorawan.AppKey { // When the AppKey of an existing device is changed - updated.UsedAppNonces = []device.AppNonce{} - updated.UsedDevNonces = []device.DevNonce{} + if dev.AppKey != *lorawan.AppKey { // When the AppKey of an existing device is changed + dev.UsedAppNonces = []device.AppNonce{} + dev.UsedDevNonces = []device.DevNonce{} + fields = append(fields, "used_dev_nonces", "used_app_nonces") } + dev.AppKey = *lorawan.AppKey + fields = append(fields, "app_key") } nsUpdated := &pb_lorawan.Device{ @@ -161,7 +181,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not set device")) } - err = h.handler.devices.Set(updated) + err = h.handler.devices.Set(dev, fields...) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -292,7 +312,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica } func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { - _, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) + app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -301,13 +321,29 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) return nil, grpcErrf(codes.InvalidArgument, "Invalid Application") } - err = h.handler.applications.Set(&application.Application{ - AppID: in.AppId, - Decoder: in.Decoder, - Converter: in.Converter, - Validator: in.Validator, - Encoder: in.Encoder, - }) + fields := []string{} + + if app.Decoder != in.Decoder { + app.Decoder = in.Decoder + fields = append(fields, "decoder") + } + + if app.Converter != in.Converter { + app.Converter = in.Converter + fields = append(fields, "converter") + } + + if app.Validator != in.Validator { + app.Validator = in.Validator + fields = append(fields, "validator") + } + + if app.Encoder != in.Encoder { + app.Encoder = in.Encoder + fields = append(fields, "encoder") + } + + err = h.handler.applications.Set(app) if err != nil { return nil, errors.BuildGRPCError(err) } From e71af804639940561809840bdcf852cbe139d457 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 14:58:55 +0200 Subject: [PATCH 1908/2266] Confirm potentially breaking changes in ttnctl --- ttnctl/cmd/devices_set.go | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index 52ecc2fe9..f22fe6a8d 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -4,6 +4,8 @@ package cmd import ( + "os" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/ttnctl/util" @@ -46,6 +48,13 @@ var devicesSetCmd = &cobra.Command{ // Do all updates if in, err := cmd.Flags().GetString("app-eui"); err == nil && in != "" { + + ctx.Warn("Manually changing the AppEUI of a device might break routing for this device") + if override, _ := cmd.Flags().GetBool("override"); !override { + ctx.Warnf("Use the --override flag if you're really sure you want to do this") + os.Exit(0) + } + appEUI, err := types.ParseAppEUI(in) if err != nil { ctx.Fatalf("Invalid AppEUI: %s", err) @@ -54,6 +63,13 @@ var devicesSetCmd = &cobra.Command{ } if in, err := cmd.Flags().GetString("dev-eui"); err == nil && in != "" { + + ctx.Warn("Manually changing the DevEUI of a device might break routing for this device") + if override, _ := cmd.Flags().GetBool("override"); !override { + ctx.Warnf("Use the --override flag if you're really sure you want to do this") + os.Exit(0) + } + devEUI, err := types.ParseDevEUI(in) if err != nil { ctx.Fatalf("Invalid DevEUI: %s", err) @@ -62,11 +78,17 @@ var devicesSetCmd = &cobra.Command{ } if in, err := cmd.Flags().GetString("dev-addr"); err == nil && in != "" { + + ctx.Warn("Manually changing the DevAddr of a device might break routing for this device") + if override, _ := cmd.Flags().GetBool("override"); !override { + ctx.Warnf("Use the --override flag if you're really sure you want to do this") + os.Exit(0) + } + devAddr, err := types.ParseDevAddr(in) if err != nil { ctx.Fatalf("Invalid DevAddr: %s", err) } - ctx.Warn("Using a DevAddr that was not issued by the NetworkServer could break connectivity") dev.GetLorawanDevice().DevAddr = &devAddr } @@ -125,6 +147,8 @@ var devicesSetCmd = &cobra.Command{ func init() { devicesCmd.AddCommand(devicesSetCmd) + devicesSetCmd.Flags().Bool("override", false, "Override protection against breaking changes") + devicesSetCmd.Flags().String("app-eui", "", "Set AppEUI") devicesSetCmd.Flags().String("dev-eui", "", "Set DevEUI") devicesSetCmd.Flags().String("dev-addr", "", "Set DevAddr") From 217e2a8b4b71adc3039755b3b987ec8803c760cb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 15:06:59 +0200 Subject: [PATCH 1909/2266] Check for devices with same AppEUI/DevEUI before create --- core/handler/manager_server.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index a510ebd1b..f3bc96d5c 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -110,6 +110,15 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } } } else { // When this is a create + existingDevices, err := h.handler.devices.ListForApp(in.AppId) + if err != nil { + return nil, err + } + for _, existingDevice := range existingDevices { + if existingDevice.AppEUI == *lorawan.AppEui && existingDevice.DevEUI == *lorawan.DevEui { + return nil, errors.BuildGRPCError(errors.NewErrAlreadyExists("Device with AppEUI and DevEUI")) + } + } dev = new(device.Device) } fields := []string{} From 27f09b994af6b8c19bf7edeaf80c60861f4ceffc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 15:56:36 +0200 Subject: [PATCH 1910/2266] Don't require setting bool flags in ttnctl devices set Resolves #301 --- ttnctl/cmd/devices_set.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index f22fe6a8d..62bd2d193 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -124,12 +124,20 @@ var devicesSetCmd = &cobra.Command{ dev.GetLorawanDevice().FCntDown = uint32(in) } - if in, err := cmd.Flags().GetBool("disable-fcnt-check"); err == nil { - dev.GetLorawanDevice().DisableFCntCheck = in + if in, err := cmd.Flags().GetBool("enable-fcnt-check"); err == nil && in { + dev.GetLorawanDevice().DisableFCntCheck = false } - if in, err := cmd.Flags().GetBool("32-bit-fcnt"); err == nil { - dev.GetLorawanDevice().Uses32BitFCnt = in + if in, err := cmd.Flags().GetBool("disable-fcnt-check"); err == nil && in { + dev.GetLorawanDevice().DisableFCntCheck = true + } + + if in, err := cmd.Flags().GetBool("32-bit-fcnt"); err == nil && in { + dev.GetLorawanDevice().Uses32BitFCnt = true + } + + if in, err := cmd.Flags().GetBool("16-bit-fcnt"); err == nil && in { + dev.GetLorawanDevice().Uses32BitFCnt = false } err = manager.SetDevice(dev) @@ -160,5 +168,7 @@ func init() { devicesSetCmd.Flags().Int("fcnt-down", -1, "Set FCnt Down") devicesSetCmd.Flags().Bool("disable-fcnt-check", false, "Disable FCnt check") + devicesSetCmd.Flags().Bool("enable-fcnt-check", false, "Enable FCnt check (default)") devicesSetCmd.Flags().Bool("32-bit-fcnt", false, "Use 32 bit FCnt") + devicesSetCmd.Flags().Bool("16-bit-fcnt", false, "Use 16 bit FCnt (default)") } From e83a1772a47a10bd505d10eac9cf0f7baa0003d8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 16:03:29 +0200 Subject: [PATCH 1911/2266] Add warning for Uses32BitFCnt DisableFCntCheck combination --- ttnctl/cmd/devices_info.go | 4 ++++ ttnctl/cmd/devices_set.go | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index b8131d873..36fa0c012 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -96,6 +96,10 @@ var devicesInfoCmd = &cobra.Command{ options = append(options, "Uses32BitFCnt") } fmt.Printf(" Options: %s\n", strings.Join(options, ", ")) + + if lorawan.Uses32BitFCnt && lorawan.DisableFCntCheck { + ctx.Warn("Using both the DisableFCntCheck and the Uses32BitFCnt options might break routing for this device") + } } }, diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index 62bd2d193..ea88732c7 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -140,6 +140,10 @@ var devicesSetCmd = &cobra.Command{ dev.GetLorawanDevice().Uses32BitFCnt = false } + if dev.GetLorawanDevice().Uses32BitFCnt && dev.GetLorawanDevice().DisableFCntCheck { + ctx.Warn("Using both the DisableFCntCheck and the Uses32BitFCnt options might break routing for this device") + } + err = manager.SetDevice(dev) if err != nil { ctx.WithError(err).Fatal("Could not update Device") From e0569b5703b54de18916657486a1eaed7be661df Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 11 Oct 2016 17:19:03 +0200 Subject: [PATCH 1912/2266] Fixes #303 (#304) Convert GoArray to Array --- core/handler/convert_fields.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 324237973..ff9711a27 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -67,7 +67,7 @@ func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) vm := otto.New() vm.Set("payload", payload) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload)", f.Decoder), timeOut) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload.slice(0))", f.Decoder), timeOut) if err != nil { return nil, err } From 495e60c8f71412e8eba5d21f77115573e986acdf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 11 Oct 2016 16:24:21 +0200 Subject: [PATCH 1913/2266] Update MQTT example with new location --- mqtt/client_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt/client_test.go b/mqtt/client_test.go index c5700e53d..f08a5f1c9 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -147,7 +147,7 @@ func TestRandomTopicPublish(t *testing.T) { func ExampleNewClient() { ctx := log.WithField("Example", "NewClient") - exampleClient := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "staging.thethingsnetwork.org:1883") + exampleClient := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "eu.thethings.network:1883") err := exampleClient.Connect() if err != nil { ctx.WithError(err).Fatal("Could not connect") From 73b8929b56213eaad6571b3696ff0481aa9f1e96 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 12 Oct 2016 09:57:45 +0200 Subject: [PATCH 1914/2266] Fix Redis pipelining Thanks for spotting, @rvolosatovs --- core/discovery/announcement/store.go | 2 +- core/discovery/kv/store.go | 2 +- core/handler/application/store.go | 38 ++++++++++++++++++++-------- core/handler/device/store.go | 2 +- core/networkserver/device/store.go | 4 +-- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go index c091616b7..be4efc1ee 100644 --- a/core/discovery/announcement/store.go +++ b/core/discovery/announcement/store.go @@ -103,7 +103,7 @@ func (s *redisAnnouncementStore) getForKeys(keys []string) ([]*pb.Announcement, // Add all commands to pipeline cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - cmds[key] = s.client.HGetAllMap(key) + cmds[key] = pipe.HGetAllMap(key) } // Execute pipeline diff --git a/core/discovery/kv/store.go b/core/discovery/kv/store.go index 3344390a7..5fb49b65f 100644 --- a/core/discovery/kv/store.go +++ b/core/discovery/kv/store.go @@ -79,7 +79,7 @@ func (s *redisKVStore) getForKeys(keys []string) (map[string]string, error) { // Add all commands to pipeline cmds := make(map[string]*redis.StringCmd) for _, key := range keys { - cmds[key] = s.client.Get(key) + cmds[key] = pipe.Get(key) } // Execute pipeline diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 1beeb7be1..b2ad4516d 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -76,24 +76,40 @@ type redisApplicationStore struct { } func (s *redisApplicationStore) List() ([]*Application, error) { - var apps []*Application keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisApplicationPrefix)).Result() if err != nil { return nil, err } + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - res, err := s.client.HGetAllMap(key).Result() - if err != nil { - return nil, err - } - application := &Application{} - err = application.FromStringStringMap(res) - if err != nil { - return nil, err + cmds[key] = pipe.HGetAllMap(key) + } + + // Execute pipeline + _, err = pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + applications := make([]*Application, 0, len(keys)) + for _, cmd := range cmds { + dmap, err := cmd.Result() + if err == nil { + application := &Application{} + err := application.FromStringStringMap(dmap) + if err == nil { + applications = append(applications, application) + } } - apps = append(apps, application) } - return apps, nil + + return applications, nil } func (s *redisApplicationStore) Get(appID string) (*Application, error) { diff --git a/core/handler/device/store.go b/core/handler/device/store.go index f47ca05db..9b73d77fb 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -104,7 +104,7 @@ func (s *redisDeviceStore) getForKeys(keys []string) ([]*Device, error) { // Add all commands to pipeline cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - cmds[key] = s.client.HGetAllMap(key) + cmds[key] = pipe.HGetAllMap(key) } // Execute pipeline diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 695ad1b34..33414c9f6 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -166,7 +166,7 @@ func (s *redisDeviceStore) List() ([]*Device, error) { // Add all commands to pipeline cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - cmds[key] = s.client.HGetAllMap(key) + cmds[key] = pipe.HGetAllMap(key) } // Execute pipeline @@ -221,7 +221,7 @@ func (s *redisDeviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, err // Add all commands to pipeline cmds := make(map[string]*redis.StringStringMapCmd) for _, key := range keys { - cmds[key] = s.client.HGetAllMap(key) + cmds[key] = pipe.HGetAllMap(key) } // Execute pipeline From f93ff159d37c2c868ac1d246472496d5c3380eab Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 12 Oct 2016 10:06:08 +0200 Subject: [PATCH 1915/2266] Don't stop processing uplink that only contains MAC cmds --- core/handler/convert_lorawan.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index ff6752069..878faeb31 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -62,8 +62,6 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli } appUp.PayloadRaw = payload.Bytes } - } else { - return errors.NewErrInvalidArgument("Uplink MACPayload", "could not get frame payload") } // LoRaWAN: Publish ACKs as events From 616646902e6394ff2f5a5f5b73c3247ebccc9758 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 12 Oct 2016 10:06:29 +0200 Subject: [PATCH 1916/2266] Increase gtw schedule deadline --- core/router/gateway/schedule.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index d89ce9aff..9bef11593 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -91,7 +91,7 @@ func (s *schedule) GoString() (str string) { // Deadline for sending a downlink back to the gateway // TODO: Make configurable -var Deadline = 200 * time.Millisecond +var Deadline = 400 * time.Millisecond const uintmax = 1 << 32 From f2f553b32a48282130bcd78cc4ccec952fe60d84 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 12 Oct 2016 11:28:33 +0200 Subject: [PATCH 1917/2266] Sort redis keys when listing --- core/discovery/announcement/store.go | 3 +++ core/discovery/kv/store.go | 3 +++ core/handler/application/store.go | 3 +++ core/handler/device/store.go | 3 +++ core/networkserver/device/store.go | 5 +++++ 5 files changed, 17 insertions(+) diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go index be4efc1ee..9f977f4d5 100644 --- a/core/discovery/announcement/store.go +++ b/core/discovery/announcement/store.go @@ -5,6 +5,7 @@ package announcement import ( "fmt" + "sort" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -97,6 +98,8 @@ type redisAnnouncementStore struct { } func (s *redisAnnouncementStore) getForKeys(keys []string) ([]*pb.Announcement, error) { + sort.Strings(keys) + pipe := s.client.Pipeline() defer pipe.Close() diff --git a/core/discovery/kv/store.go b/core/discovery/kv/store.go index 5fb49b65f..fea6efad1 100644 --- a/core/discovery/kv/store.go +++ b/core/discovery/kv/store.go @@ -5,6 +5,7 @@ package kv import ( "fmt" + "sort" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -73,6 +74,8 @@ type redisKVStore struct { } func (s *redisKVStore) getForKeys(keys []string) (map[string]string, error) { + sort.Strings(keys) + pipe := s.client.Pipeline() defer pipe.Close() diff --git a/core/handler/application/store.go b/core/handler/application/store.go index b2ad4516d..5ca4b747d 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -5,6 +5,7 @@ package application import ( "fmt" + "sort" "time" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -81,6 +82,8 @@ func (s *redisApplicationStore) List() ([]*Application, error) { return nil, err } + sort.Strings(keys) + pipe := s.client.Pipeline() defer pipe.Close() diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 9b73d77fb..7c68c4957 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -5,6 +5,7 @@ package device import ( "fmt" + "sort" "time" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -98,6 +99,8 @@ type redisDeviceStore struct { } func (s *redisDeviceStore) getForKeys(keys []string) ([]*Device, error) { + sort.Strings(keys) + pipe := s.client.Pipeline() defer pipe.Close() diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 33414c9f6..2ba61e8a5 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -5,6 +5,7 @@ package device import ( "fmt" + "sort" "time" "github.com/TheThingsNetwork/ttn/core/types" @@ -160,6 +161,8 @@ func (s *redisDeviceStore) List() ([]*Device, error) { return nil, err } + sort.Strings(keys) + pipe := s.client.Pipeline() defer pipe.Close() @@ -215,6 +218,8 @@ func (s *redisDeviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, err return nil, err } + sort.Strings(keys) + pipe := s.client.Pipeline() defer pipe.Close() From 3779fd37b4f9c498e8c894deafa322a9f29b4739 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 12 Oct 2016 11:53:25 +0200 Subject: [PATCH 1918/2266] Resolve #307 --- mqtt/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt/types.go b/mqtt/types.go index 4038224d4..44598ed85 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -55,7 +55,7 @@ type GatewayMetadata struct { GtwID string `json:"gtw_id,omitempty"` Timestamp uint32 `json:"timestamp,omitempty"` Time JSONTime `json:"time,omitempty"` - Channel uint32 `json:"channel,omitempty"` + Channel uint32 `json:"channel"` RSSI float32 `json:"rssi,omitempty"` SNR float32 `json:"snr,omitempty"` RFChain uint32 `json:"rf_chain,omitempty"` From c60546319f0509b190a1653a3a29c993816bf520 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 12 Oct 2016 16:51:53 +0200 Subject: [PATCH 1919/2266] Return correct config file, even if none were found --- ttnctl/util/config.go | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go index db3f7c010..251a91f7a 100644 --- a/ttnctl/util/config.go +++ b/ttnctl/util/config.go @@ -29,22 +29,19 @@ const ( // $HOME/.ttnctl.yml func GetConfigFile() string { flag := viper.GetString("config") + xdg := path.Join(os.Getenv("XDG_CONFIG_HOME"), "ttnctl", "config.yml") + homeyml := path.Join(os.Getenv("HOME"), ".ttnctl.yml") + homeyaml := path.Join(os.Getenv("HOME"), ".ttnctl.yaml") - xdg := os.Getenv("XDG_CONFIG_HOME") - if xdg != "" { - xdg = path.Join(xdg, "ttnctl", "config.yml") - } - - home := os.Getenv("HOME") - if home != "" { - home = path.Join(home, ".ttnctl.yml") - } - - for _, file := range []string{ + try_files := []string{ flag, xdg, - home, - } { + homeyml, + homeyaml, + } + + // find a file that exists, and use that + for _, file := range try_files { if file != "" { if _, err := os.Stat(file); err == nil { return file @@ -52,7 +49,12 @@ func GetConfigFile() string { } } - return "" + // no file found, set up correct fallback + if os.Getenv("XDG_CONFIG_HOME") != "" { + return xdg + } else { + return homeyml + } } // GetDataDir returns the location of the data directory used for From 17de5d11b3a19c8150304156134c8d9607983410 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 12 Oct 2016 17:49:14 +0200 Subject: [PATCH 1920/2266] Add extra MIC check for devices with DisableFCntCheck --- core/broker/uplink.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index caca77c00..fc83e6a9b 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -89,6 +89,19 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) if candidate.Uses32BitFCnt { + if candidate.DisableFCntCheck { + // We should first check with the 16 bit counter + micChecks++ + ok, err = phyPayload.ValidateMIC(nwkSKey) + if err != nil { + return err + } + if ok { + device = candidate + break + } + } + // Then set the full 32 bit counter and check again macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCntUp, uint16(macPayload.FHDR.FCnt)) } micChecks++ From 2b2caaebdd1cd8e50715726ea53242a204c7db7b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 12 Oct 2016 18:16:39 +0200 Subject: [PATCH 1921/2266] Prevent empty environment variables to lead to files in / --- ttnctl/util/config.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/ttnctl/util/config.go b/ttnctl/util/config.go index 251a91f7a..91d3a53e1 100644 --- a/ttnctl/util/config.go +++ b/ttnctl/util/config.go @@ -29,9 +29,20 @@ const ( // $HOME/.ttnctl.yml func GetConfigFile() string { flag := viper.GetString("config") - xdg := path.Join(os.Getenv("XDG_CONFIG_HOME"), "ttnctl", "config.yml") - homeyml := path.Join(os.Getenv("HOME"), ".ttnctl.yml") - homeyaml := path.Join(os.Getenv("HOME"), ".ttnctl.yaml") + + xdg := os.Getenv("XDG_CONFIG_HOME") + if xdg != "" { + xdg = path.Join(xdg, "ttnctl", "config.yml") + } + + home := os.Getenv("HOME") + homeyml := "" + homeyaml := "" + + if home != "" { + homeyml = path.Join(home, ".ttnctl.yml") + homeyaml = path.Join(home, ".ttnctl.yaml") + } try_files := []string{ flag, From 5c4b69d19a2863a1e5be263416d1fd4666cccc5c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 08:43:27 +0200 Subject: [PATCH 1922/2266] Add extra logging to uplink This should resolve #309 --- core/broker/uplink.go | 69 ++++++++++++++++++--------------- core/handler/convert_lorawan.go | 4 +- core/router/uplink.go | 14 +++++-- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index fc83e6a9b..8d1579cf0 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -24,9 +24,8 @@ import ( const maxFCntGap = 16384 -func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { +func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { ctx := b.Ctx.WithField("GatewayID", uplink.GatewayMetadata.GatewayId) - var err error start := time.Now() defer func() { if err != nil { @@ -44,11 +43,12 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return nil } + ctx = ctx.WithField("Duplicates", len(duplicates)) + base := duplicates[0] if base.ProtocolMetadata.GetLorawan() == nil { - err = errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") - return err + return errors.NewErrInvalidArgument("Uplink", "does not contain LoRaWAN metadata") } // LoRaWAN: Unmarshal @@ -59,13 +59,15 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } macPayload, ok := phyPayload.MACPayload.(*lorawan.MACPayload) if !ok { - err = errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") - return err + return errors.NewErrInvalidArgument("Uplink", "does not contain a MAC payload") } // Request devices from NS devAddr := types.DevAddr(macPayload.FHDR.DevAddr) - ctx = ctx.WithField("DevAddr", devAddr) + ctx = ctx.WithFields(log.Fields{ + "DevAddr": devAddr, + "FCnt": macPayload.FHDR.FCnt, + }) var getDevicesResp *networkserver.DevicesResponse getDevicesResp, err = b.ns.GetDevices(b.Component.GetContext(b.nsToken), &networkserver.DevicesRequest{ DevAddr: &devAddr, @@ -75,8 +77,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices")) } if len(getDevicesResp.Results) == 0 { - err = errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) - return err + return errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) } ctx = ctx.WithField("DevAddrResults", len(getDevicesResp.Results)) @@ -86,11 +87,27 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { // Find AppEUI/DevEUI through MIC check var device *pb_lorawan.Device var micChecks int + var originalFCnt uint32 for _, candidate := range getDevicesResp.Results { nwkSKey := lorawan.AES128Key(*candidate.NwkSKey) + + // First check with the 16 bit counter + micChecks++ + ok, err = phyPayload.ValidateMIC(nwkSKey) + if err != nil { + return err + } + if ok { + device = candidate + break + } + + originalFCnt = macPayload.FHDR.FCnt if candidate.Uses32BitFCnt { - if candidate.DisableFCntCheck { - // We should first check with the 16 bit counter + macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCntUp, uint16(originalFCnt)) + + // If 32 bit counter has different value, perform another MIC check + if macPayload.FHDR.FCnt != originalFCnt { micChecks++ ok, err = phyPayload.ValidateMIC(nwkSKey) if err != nil { @@ -101,22 +118,9 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { break } } - // Then set the full 32 bit counter and check again - macPayload.FHDR.FCnt = fcnt.GetFull(candidate.FCntUp, uint16(macPayload.FHDR.FCnt)) } - micChecks++ - ok, err = phyPayload.ValidateMIC(nwkSKey) - if err != nil { - return err - } - if ok { - device = candidate - break - } - } - if device == nil { - err = errors.NewErrNotFound("device that validates MIC") - return err + + return errors.NewErrNotFound("device that validates MIC") } ctx = ctx.WithFields(log.Fields{ "MICChecks": micChecks, @@ -124,7 +128,11 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { "AppEUI": device.AppEui, "AppID": device.AppId, "DevID": device.DevId, + "FCnt": originalFCnt, }) + if macPayload.FHDR.FCnt != originalFCnt { + ctx = ctx.WithField("RealFCnt", macPayload.FHDR.FCnt) + } if device.DisableFCntCheck { // TODO: Add warning to message? @@ -132,8 +140,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { } else if macPayload.FHDR.FCnt <= device.FCntUp || macPayload.FHDR.FCnt-device.FCntUp > maxFCntGap { // Replay attack or FCnt gap too big - err = errors.NewErrNotFound("device with matching FCnt") - return err + return errors.NewErrNotFound("device with matching FCnt") } // Add FCnt to Metadata (because it's not marshaled in lorawan payload) @@ -180,12 +187,10 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) error { return err } if len(announcements) == 0 { - err = errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) - return err + return errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", device.AppId)) } if len(announcements) > 1 { - err = errors.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) - return err + return errors.NewErrInternal(fmt.Sprintf("Multiple Handlers for AppID %s", device.AppId)) } var handler chan<- *pb.DeduplicatedUplinkMessage diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index 878faeb31..34f92b4c9 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -37,6 +37,8 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli macPayload.FHDR.FCnt = ttnUp.ProtocolMetadata.GetLorawan().FCnt appUp.FCnt = macPayload.FHDR.FCnt + ctx = ctx.WithField("FCnt", appUp.FCnt) + // LoRaWAN: Validate MIC ok, err = phyPayload.ValidateMIC(lorawan.AES128Key(dev.NwkSKey)) if err != nil { @@ -46,8 +48,6 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli return errors.NewErrNotFound("device that validates MIC") } - ctx = ctx.WithField("FCnt", appUp.FCnt) - // LoRaWAN: Decrypt if macPayload.FPort != nil && *macPayload.FPort != 0 && len(macPayload.FRMPayload) == 1 { appUp.FPort = *macPayload.FPort diff --git a/core/router/uplink.go b/core/router/uplink.go index 48b7abc24..0dd079fbd 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -21,8 +21,6 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err e defer func() { if err != nil { ctx.WithError(err).Warn("Could not handle uplink") - } else { - ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") } }() @@ -77,7 +75,10 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err e } devAddr := types.DevAddr(macPayload.FHDR.DevAddr) - ctx = ctx.WithField("DevAddr", devAddr) + ctx = ctx.WithFields(log.Fields{ + "DevAddr": devAddr, + "FCnt": macPayload.FHDR.FCnt, + }) gateway := r.getGateway(gatewayID) @@ -98,6 +99,11 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err e return err } + if len(brokers) == 0 { + ctx.Debug("No brokers to forward message to") + return nil + } + ctx = ctx.WithField("NumBrokers", len(brokers)) // Forward to all brokers @@ -114,5 +120,7 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err e } } + ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") + return nil } From c387789a0bcfd22ebaf5c28a2fb03a3e35b84902 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 09:40:50 +0200 Subject: [PATCH 1923/2266] Make 32 bit FCnt default in ttnctl --- ttnctl/cmd/devices_register.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ttnctl/cmd/devices_register.go b/ttnctl/cmd/devices_register.go index 16f508fb1..4aa97a60b 100644 --- a/ttnctl/cmd/devices_register.go +++ b/ttnctl/cmd/devices_register.go @@ -71,11 +71,12 @@ var devicesRegisterCmd = &cobra.Command{ AppId: appID, DevId: devID, Device: &handler.Device_LorawanDevice{LorawanDevice: &lorawan.Device{ - AppId: appID, - DevId: devID, - AppEui: &appEUI, - DevEui: &devEUI, - AppKey: &appKey, + AppId: appID, + DevId: devID, + AppEui: &appEUI, + DevEui: &devEUI, + AppKey: &appKey, + Uses32BitFCnt: true, }}, }) if err != nil { From a304952292e0b6abe431d0f7ba2bd3837b1cacb1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 09:43:35 +0200 Subject: [PATCH 1924/2266] Remove warning about 32BitFCnt and DisableFCntCheck This is fixed in the backend --- ttnctl/cmd/devices_info.go | 4 ---- ttnctl/cmd/devices_set.go | 8 ++------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index 36fa0c012..b8131d873 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -96,10 +96,6 @@ var devicesInfoCmd = &cobra.Command{ options = append(options, "Uses32BitFCnt") } fmt.Printf(" Options: %s\n", strings.Join(options, ", ")) - - if lorawan.Uses32BitFCnt && lorawan.DisableFCntCheck { - ctx.Warn("Using both the DisableFCntCheck and the Uses32BitFCnt options might break routing for this device") - } } }, diff --git a/ttnctl/cmd/devices_set.go b/ttnctl/cmd/devices_set.go index ea88732c7..1a38c4e92 100644 --- a/ttnctl/cmd/devices_set.go +++ b/ttnctl/cmd/devices_set.go @@ -140,10 +140,6 @@ var devicesSetCmd = &cobra.Command{ dev.GetLorawanDevice().Uses32BitFCnt = false } - if dev.GetLorawanDevice().Uses32BitFCnt && dev.GetLorawanDevice().DisableFCntCheck { - ctx.Warn("Using both the DisableFCntCheck and the Uses32BitFCnt options might break routing for this device") - } - err = manager.SetDevice(dev) if err != nil { ctx.WithError(err).Fatal("Could not update Device") @@ -173,6 +169,6 @@ func init() { devicesSetCmd.Flags().Bool("disable-fcnt-check", false, "Disable FCnt check") devicesSetCmd.Flags().Bool("enable-fcnt-check", false, "Enable FCnt check (default)") - devicesSetCmd.Flags().Bool("32-bit-fcnt", false, "Use 32 bit FCnt") - devicesSetCmd.Flags().Bool("16-bit-fcnt", false, "Use 16 bit FCnt (default)") + devicesSetCmd.Flags().Bool("32-bit-fcnt", false, "Use 32 bit FCnt (default)") + devicesSetCmd.Flags().Bool("16-bit-fcnt", false, "Use 16 bit FCnt") } From 7a14d27ec401296ae066675cab4638d59a342e87 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 09:54:49 +0200 Subject: [PATCH 1925/2266] Be more clear about FCnt options in ttnctl --- ttnctl/cmd/devices_info.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/devices_info.go b/ttnctl/cmd/devices_info.go index b8131d873..44388e04d 100644 --- a/ttnctl/cmd/devices_info.go +++ b/ttnctl/cmd/devices_info.go @@ -90,10 +90,14 @@ var devicesInfoCmd = &cobra.Command{ fmt.Printf(" FCntDown: %d\n", lorawan.FCntDown) options := []string{} if lorawan.DisableFCntCheck { - options = append(options, "DisableFCntCheck") + options = append(options, "FCntCheckDisabled") + } else { + options = append(options, "FCntCheckEnabled") } if lorawan.Uses32BitFCnt { - options = append(options, "Uses32BitFCnt") + options = append(options, "32BitFCnt") + } else { + options = append(options, "16BitFCnt") } fmt.Printf(" Options: %s\n", strings.Join(options, ", ")) } From 56154adb3dd5e4217dc9187a4b95a23711f66803 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 11:35:50 +0200 Subject: [PATCH 1926/2266] Add Github Issue Template --- .github/ISSUE_TEMPLATE.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..9d307762a --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,28 @@ + + +This is a **{bug report/feature request/question/...}** for **{the Backend/ttnctl/the Console/the NOC/an integration}** (**{v1-staging/v2-preview}**). + +- Explain what you want to do +- Explain what steps you took +- Explain what went wrong or what is missing + +## Environment + +- `ttn version` returns: `Commit={...}` +- `ttnctl version` returns: `Commit={...}` +- My application: + - App ID: `{...}` +- My gateway: + - EUI: `{...}` + - Connected to: `router.{...}.thethings.network:1700` +- My node: + - Device ID: `{...}` + - AppEUI: `{...}` + - DevEUI: `{...}` +- MQTT is connected to: `{...}.thethings.network:{1883/8883}` +- I am using the `{...}` library for the `{...}` programming language From 2eacbac1cb96b933a6472934ee0df8d36a22dcf5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 11:50:04 +0200 Subject: [PATCH 1927/2266] Only allow announcing "dev" to discovery if in "dev" mode --- core/discovery/server.go | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/core/discovery/server.go b/core/discovery/server.go index 8d711ffa9..d29994f10 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -78,10 +78,20 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc if err != nil { return nil, err } - // Only allow announcements if token is issued by the official ttn account server (or if in dev mode) - if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return nil, errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) + + // If not in development mode + if d.discovery.Component.Identity.Id != "dev" { + // Tokens must be issued by official ttn account server + if claims.Issuer != "ttn-account" { + return nil, errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) + } + + // Can't announce development components + if claims.Subject == "dev" { + return nil, errPermissionDeniedf("Can't announce development components") + } } + if claims.Subject != announcement.Id { return nil, errPermissionDeniedf("Token subject %s does not correspond with announcement ID %s", claims.Subject, announcement.Id) } From 223056da6d492b409361e11ab64f433fd001b847 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 12:07:01 +0200 Subject: [PATCH 1928/2266] Update token issuer in dev JWTs --- .env/broker/dev.yml | 2 +- .env/handler/dev.yml | 2 +- .env/router/dev.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index 99363a562..cf6779d9e 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -8,7 +8,7 @@ auth-servers: tls: true key-dir: "./.env/broker/" -auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1zdGFnaW5nIiwic3ViIjoiZGV2IiwidHlwZSI6ImJyb2tlciIsImlhdCI6MTQ3MjgxNzk1MX0.m3sMJDaXcsAxROlCOqbidv5cQf1iau99N5aIC9D4rH9lCzl__Z6sVkMOqDrdDska1lxuKlkr7dQEDvU5ujUmOSKC3riMZFtOnvA_7NQAR8Q1i6JxHVg3OKpml513nbOKynPn8Vb2oEN0S1yyfkLepbMfAuJKzxPUL92vZcKX6GgyV51nnfYZp-HksqkaFK7zm2wbQQ_xqouLZ_mibRdMyryFfv5mDnZ3K1LCHXcv_FanoMA8hvjKq3pKwflI2vJPSNGmwftPL0oG7t4MBXkx3IqthxhyymyYbukcC1UBtwtvo3Now2cbbfwatBPAEqUkvcXe-LK4YW1QsYB0xs8iOQ +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImJyb2tlciIsImlhdCI6MTQ3NjQzOTQzOH0.nVH5FuQiN_WUEdntQ-kDocjTxq-UrlKey2Jm6NJmE18tts5K4PDPMzNgF-uenKKHEcDWImlf3k1vcI4LMPUj0sgPxWdyR5PnUNFRIupxdy1k_Q2cSnZcmpX-Mc4KN23JzXtmaDq-Qp7reyEOv7K7HpHwt6Jb_YQHEZEfkU1628LQaybUKJgCIttoUzBV12dFfKnC8tdL_NMfSVCquhITLOj5efXf-0CL6A_4s_vNctnIBFfpdKUeukpnT52B8-c2SCzk36g13n3L-6fc7MRTCaF0D1LlMQasy5Aq39e_1VJP10kH_-luoF8eOFHVFH-r4pLf6_RGz51oocDwt7w9Hw broker: networkserver-cert: ./.env/broker/networkserver.cert diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 28c47a515..06a2ac582 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -8,4 +8,4 @@ auth-servers: tls: true key-dir: "./.env/handler/" -auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1zdGFnaW5nIiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzI4MTc5NTJ9.XyaTta7R66bfaCS4rzNYayzejE7nP7CcleksPyk71LsDkyMetFE_dJ-EP1Lp6Lys7yBILO4ax5tfuswBwvXg2sOIBIT30SPuTaJ62HidU6We_DQHBvHMSkdSOMnk3WNJ5rWhogga1rSHiKnJH56CqdcmT-9vwVFqIeU6ZCTawdDjRGjgQCn81PGlnCW7GVb3fLgpD1A_feFbGxBXVYSbRWA-rHGccyI9kWU5xxWyZwlYpl9_zT8j4mD8CZzffbgsPzag6aspE52fHgPRqz6rYzgZpXS7o6MUOEsjcWiOwp2ManvvnVA0mM_G65jdqMz1rPjdxt19QnQ8V59QtButGQ +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg diff --git a/.env/router/dev.yml b/.env/router/dev.yml index b78d9a76b..28cd21c66 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -8,4 +8,4 @@ auth-servers: tls: true key-dir: "./.env/router/" -auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1zdGFnaW5nIiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3MjgxNzk1MX0.Rjv2QHMAdhrZpc7Nf1HdUG1EoZqURr-PKWZaU7Xz7p30FMBxDXUDUtDbcILKbjstlczVOdHGQnAOf-rwzeIrXJ5051KopcDYcmpRwqJQHnKmzFClw5gs-1uAYSnwcEvpsGaFDS8Q7qc6X0m-5lFnFeHAtgVPa80joAw6QBPaTwW3KHvoY_a2htjk8tUQhVlL-wTCnnPMeUGIYJwHFIRXZW3Wk32XLWwF2w8fimVFgfr8yBQMLucbQ-CNEqcuC8tU8H46kPKCugadY6enmaOVVgeGFK1s9OEm4oHhYMJkE0agY7FZBPVEyl9bVxpCRor4H60MebA5U7xQ9tQQlP2ndQ +auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA From 488b0212e476ba75c03e6ac915e3e9e559ae28e1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 12:13:19 +0200 Subject: [PATCH 1929/2266] Add ttnctl cmds for gateway info and token --- ttnctl/cmd/gateways_info.go | 61 ++++++++++++++++++++++++++++++++++++ ttnctl/cmd/gateways_token.go | 55 ++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 ttnctl/cmd/gateways_info.go create mode 100644 ttnctl/cmd/gateways_token.go diff --git a/ttnctl/cmd/gateways_info.go b/ttnctl/cmd/gateways_info.go new file mode 100644 index 000000000..eee74ee1e --- /dev/null +++ b/ttnctl/cmd/gateways_info.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysInfoCmd = &cobra.Command{ + Use: "info [GatewayID]", + Short: "get info about a gateway", + Long: `ttnctl gateways info can be used to get information about a gateway`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + + account := util.GetAccount(ctx) + + gateway, err := account.FindGateway(gatewayID) + if err != nil { + ctx.WithError(err).WithField("id", gatewayID).Fatal("Could not find gateway") + } + + ctx.Info("Found gateway") + + fmt.Println() + fmt.Printf("Gateway ID: %s\n", gateway.ID) + fmt.Printf("Activated: %v\n", gateway.Activated) + fmt.Printf("Frequency Plan: %s\n", gateway.FrequencyPlan) + locationAccess := "private" + if gateway.LocationPublic { + locationAccess = "public" + } + if gateway.Location != nil { + fmt.Printf("Location Info : (%f, %f) (%s) \n", gateway.Location.Latitude, gateway.Location.Longitude, locationAccess) + } + if gateway.StatusPublic { + fmt.Printf("Status Info: public (see ttnctl gateways status %s)\n", gatewayID) + } else { + fmt.Print("Status Info: private\n") + } + + fmt.Println() + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysInfoCmd) +} diff --git a/ttnctl/cmd/gateways_token.go b/ttnctl/cmd/gateways_token.go new file mode 100644 index 000000000..31f775d46 --- /dev/null +++ b/ttnctl/cmd/gateways_token.go @@ -0,0 +1,55 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var gatewaysTokenCmd = &cobra.Command{ + Use: "token [Type] [gatewayID]", + Hidden: true, + Short: "Get the token for a gateway.", + Long: `gateways token gets a signed token for the gateway.`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 1 { + cmd.UsageFunc()(cmd) + return + } + + gatewayID := args[0] + if !api.ValidID(gatewayID) { + ctx.Fatal("Invalid Gateway ID") + } + ctx = ctx.WithField("id", gatewayID) + + account := util.GetAccount(ctx) + + token, err := account.GetGatewayToken(gatewayID) + if err != nil { + ctx.WithError(err).Fatal("Could not get gateway token") + } + if token.Token == "" { + ctx.Fatal("Gateway token was empty") + } + + ctx.Info("Got gateway token") + + fmt.Println() + fmt.Println(token.Token) + fmt.Println() + if !token.Expires.IsZero() { + fmt.Printf("Expires %s\n", token.Expires) + fmt.Println() + } + }, +} + +func init() { + gatewaysCmd.AddCommand(gatewaysTokenCmd) +} From 97202216efb82d6a03f4f19c3ad3aebfc4e42e73 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 12:14:54 +0200 Subject: [PATCH 1930/2266] Update vendor file for 488b021 --- vendor/vendor.json | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 3b3826043..42bba4a84 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,58 +9,58 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "4yiQ3/k0aOGDr1iyjIXwoBu0BKw=", + "checksumSHA1": "LxBmvOQzHVoY2cdh8vflnzARtNU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { - "checksumSHA1": "CwTMKMV1GuWH0cahU1plR3qLk78=", + "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "ANS1jzUqsv1Q1X0gZNzSe9wY0Fo=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "4838790e062e94f873817af02a6d08537b9381e1", - "revisionTime": "2016-10-06T09:30:39Z" + "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", + "revisionTime": "2016-10-14T09:56:50Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", From d535a09d228bc5bb21e87ed29c6c938dd75bf35a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 13:49:36 +0200 Subject: [PATCH 1931/2266] Only use color if terminal supports it Resolves #312 --- utils/cli/handler/cli.go | 41 ++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/utils/cli/handler/cli.go b/utils/cli/handler/cli.go index 10f1567e2..2db85807e 100644 --- a/utils/cli/handler/cli.go +++ b/utils/cli/handler/cli.go @@ -6,8 +6,9 @@ package handler import ( "fmt" "io" - "runtime" + "os" "sort" + "strings" "sync" "github.com/apex/log" @@ -56,14 +57,34 @@ func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } // Handler implementation. type Handler struct { - mu sync.Mutex - Writer io.Writer + mu sync.Mutex + Writer io.Writer + UseColor bool +} + +// colorTermSubstrings contains a list of substrings that indicate support for terminal colors +var colorTermSubstrings = []string{ + "color", + "xterm", } // New handler. func New(w io.Writer) *Handler { + var useColor bool + if os.Getenv("COLORTERM") != "" { + useColor = true + } + if term := os.Getenv("TERM"); term != "" { + for _, substring := range colorTermSubstrings { + if strings.Contains(term, substring) { + useColor = true + break + } + } + } return &Handler{ - Writer: w, + Writer: w, + UseColor: useColor, } } @@ -83,10 +104,10 @@ func (h *Handler) HandleLog(e *log.Entry) error { h.mu.Lock() defer h.mu.Unlock() - if runtime.GOOS == "windows" { - fmt.Fprintf(h.Writer, "%6s %-40s", level, e.Message) - } else { + if h.UseColor { fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-40s", color, level, e.Message) + } else { + fmt.Fprintf(h.Writer, "%6s %-40s", level, e.Message) } for _, f := range fields { @@ -100,10 +121,10 @@ func (h *Handler) HandleLog(e *log.Entry) error { value = f.Value } - if runtime.GOOS == "windows" { - fmt.Fprintf(h.Writer, " %s=%v", f.Name, value) - } else { + if h.UseColor { fmt.Fprintf(h.Writer, " \033[%dm%s\033[0m=%v", color, f.Name, value) + } else { + fmt.Fprintf(h.Writer, " %s=%v", f.Name, value) } } From cc769d9bebe87e8aece2e982089b46a17744a2dc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 14:04:13 +0200 Subject: [PATCH 1932/2266] Verify Gateway Tokens in Router --- .env/router/dev.yml | 2 ++ cmd/router.go | 2 ++ core/router/server.go | 15 +++++++++++---- ttnctl/cmd/uplink.go | 22 +++++++++++++++++++++- 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 28cd21c66..66c379c5e 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -7,5 +7,7 @@ auth-servers: ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/router/" +router: + skip-verify-gateway-token: true auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA diff --git a/cmd/router.go b/cmd/router.go index 7ab190156..956083f9f 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -71,7 +71,9 @@ func init() { routerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") routerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") routerCmd.Flags().Int("server-port", 1901, "The port for communication") + routerCmd.Flags().Bool("skip-verify-gateway-token", false, "Skip verification of the gateway token") viper.BindPFlag("router.server-address", routerCmd.Flags().Lookup("server-address")) viper.BindPFlag("router.server-address-announce", routerCmd.Flags().Lookup("server-address-announce")) viper.BindPFlag("router.server-port", routerCmd.Flags().Lookup("server-port")) + viper.BindPFlag("router.skip-verify-gateway-token", routerCmd.Flags().Lookup("skip-verify-gateway-token")) } diff --git a/core/router/server.go b/core/router/server.go index 96fa50d6e..4e6edc6a6 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -11,13 +11,14 @@ import ( "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" + "github.com/spf13/viper" context "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) type routerRPC struct { - router Router + router *router } var grpcErrf = grpc.Errorf // To make go vet stop complaining @@ -37,9 +38,15 @@ func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gatewa if err != nil { return nil, err } - if token != "token" { - // TODO: Validate Token - return nil, errors.NewErrPermissionDenied("Gateway token not authorized") + + if !viper.GetBool("router.skip-verify-gateway-token") { + claims, err := r.router.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if claims.Type != "gateway" || claims.Subject != gatewayID { + return nil, errors.NewErrPermissionDenied("Gateway token not authorized") + } } gtw = r.router.getGateway(gatewayID) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index b52b94229..19d840e26 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -60,7 +60,26 @@ var uplinkCmd = &cobra.Command{ rtrClient := util.GetRouter(ctx) defer rtrClient.Close() - gtwClient := rtrClient.ForGateway(util.GetID(), func() string { return "token" }) + gatewayID, _ := cmd.Flags().GetString("gateway-id") + var gatewayToken string + + if gatewayID == "" { + gatewayID = "dev" + // This token is valid for the "dev" gateway: + gatewayToken = `eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImdhdGV3YXkiLCJpYXQiOjE0NzY0Mzk0Mzh9.kEOiLe9j4qRElZOt_bAXmZlva1nV6duIL0MDVa3bx2SEWC3qredaBWXWq4FmV4PKeI_zndovQtOoValH0B_6MW6vXuWL1wYzV6foTH5gQdxmn-iuQ1AmAIYbZeyHl9a-NPqDgkXLwKmo2iB1hUi9wV6HXfIOalguDtGJbmMfJ2tommsgmuNCXd-2zqhStSy8ArpROFXPm7voGDTcgm4hfchr7zhn-Er76R-eJa3RZ1Seo9BsiWrQ0N3VDSuh7ycCakZtkaLD4OTutAemcbzbrNJSOCvvZr8Asn-RmMkjKUdTN4Bgn3qlacIQ9iZikPLT8XyjFkj-8xjs3KAobWg40A` + } else { + account := util.GetAccount(ctx) + token, err := account.GetGatewayToken(gatewayID) + if err != nil { + ctx.WithError(err).Warn("Could not get gateway token") + ctx.Warn("Trying without token. Your message may not be processed by the router") + gatewayToken = "" + } else if token != nil && token.Token != "" { + gatewayToken = token.Token + } + } + + gtwClient := rtrClient.ForGateway(gatewayID, func() string { return gatewayToken }) var downlink <-chan *router.DownlinkMessage var errChan <-chan error @@ -124,4 +143,5 @@ func init() { RootCmd.AddCommand(uplinkCmd) uplinkCmd.Flags().Bool("downlink", false, "Also start downlink (unstable)") uplinkCmd.Flags().Bool("confirmed", false, "Use confirmed uplink (this also sets --downlink)") + uplinkCmd.Flags().String("gateway-id", "", "The ID of the gateway that you are faking (you can only fake gateways that you own)") } From 1bffb7a0669578edcded90ed11520cf19dffe0dc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 14:04:43 +0200 Subject: [PATCH 1933/2266] Only send 1 uplink with ttnctl --- ttnctl/cmd/uplink.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 19d840e26..a6f7c1d74 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -106,17 +106,6 @@ var uplinkCmd = &cobra.Command{ time.Sleep(100 * time.Millisecond) - err = gtwClient.SendUplink(&router.UplinkMessage{ - Payload: bytes, - GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), - ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), - }) - if err != nil { - ctx.WithError(err).Fatal("Could not send uplink to Router") - } - - time.Sleep(100 * time.Millisecond) - ctx.Info("Sent uplink to Router") if downlink != nil { From da203c837824dd37c8769ae1496faa5ca5b53363 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 14 Oct 2016 14:12:53 +0200 Subject: [PATCH 1934/2266] Relax time on short-lived token to allow for inaccurate clocks --- core/component.go | 2 +- utils/security/jwt.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/component.go b/core/component.go index 893e40f85..8c9709b24 100644 --- a/core/component.go +++ b/core/component.go @@ -365,7 +365,7 @@ func (c *Component) BuildJWT() (string, error) { if err != nil { return "", err } - return security.BuildJWT(c.Identity.Id, 10*time.Second, privPEM) + return security.BuildJWT(c.Identity.Id, 20*time.Second, privPEM) } return "", nil } diff --git a/utils/security/jwt.go b/utils/security/jwt.go index 993a4be0f..f650cd579 100644 --- a/utils/security/jwt.go +++ b/utils/security/jwt.go @@ -13,8 +13,8 @@ import ( func BuildJWT(subject string, ttl time.Duration, privateKey []byte) (token string, err error) { claims := jwt.StandardClaims{ Issuer: subject, - IssuedAt: time.Now().Unix(), - NotBefore: time.Now().Unix(), + IssuedAt: time.Now().Add(-20 * time.Second).Unix(), + NotBefore: time.Now().Add(-20 * time.Second).Unix(), } if ttl > 0 { claims.ExpiresAt = time.Now().Add(ttl).Unix() From 3472424960263075ab254abbbcfe98f81cbb5375 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 11:38:48 +0200 Subject: [PATCH 1935/2266] closes #268 --- cmd/root.go | 10 ++++++++++ core/component.go | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/cmd/root.go b/cmd/root.go index 63dc8e34b..a438f6df3 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,6 +84,13 @@ var RootCmd = &cobra.Command{ "Description": viper.GetString("description"), "DiscoveryServer": viper.GetString("discovery-server"), "AuthServers": viper.GetStringMapString("auth-servers"), + + "NocServer": func() string { + if nocAddr := viper.GetString("noc-server"); len(nocAddr) > 0 { + return nocAddr + } + return "undefined" + }(), }).Info("Initializing The Things Network") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { @@ -124,6 +131,9 @@ func init() { RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) + RootCmd.PersistentFlags().String("noc-server", "noc.thethingsnetwork.org:1905", "The address of the Noc server") + viper.BindPFlag("noc-server", RootCmd.PersistentFlags().Lookup("noc-server")) + viper.SetDefault("auth-servers", map[string]string{ "ttn-account": "https://account.thethingsnetwork.org", }) diff --git a/core/component.go b/core/component.go index 8c9709b24..d1a11e93e 100644 --- a/core/component.go +++ b/core/component.go @@ -15,6 +15,7 @@ import ( "github.com/TheThingsNetwork/go-account-lib/cache" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/tokenkey" + "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_noc "github.com/TheThingsNetwork/ttn/api/noc" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -128,6 +129,15 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) } + if nocAddr := viper.GetString("noc-server"); len(nocAddr) > 0 { + conn, err := grpc.Dial(nocAddr, append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) + if err != nil { + return nil, err + } + + component.Monitor = pb_noc.NewMonitorClient(conn) + } + return component, nil } From 7e7fc51f391f2f85bdd016d63b9d2b04678f63d4 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 11:40:09 +0200 Subject: [PATCH 1936/2266] .env: specify `noc-server` --- .env/broker/dev.yml | 1 + .env/discovery/dev.yml | 1 + .env/handler/dev.yml | 1 + .env/networkserver/dev.yml | 1 + .env/router/dev.yml | 1 + 5 files changed, 5 insertions(+) diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index cf6779d9e..fc63152a2 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -13,3 +13,4 @@ auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV broker: networkserver-cert: ./.env/broker/networkserver.cert networkserver-token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Njk3OTQzNTksIm5iZiI6MTQ2OTc5NDM1OSwic3ViIjoiZGV2In0.Xj1AFEAblzFLdS5cUiDrG773EqHazARvGdkTHFoPa_c0XGhdKSrevVpTNkzvcXeTQTWnTQrg1t98atNIk7F13Q +noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index 217a4abb8..4f28700d6 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -6,3 +6,4 @@ auth-servers: ttn-account-preview: "https://preview.account.thethingsnetwork.org" ttn-account-staging: "https://preview.account.thethingsnetwork.org" key-dir: "./.env/discovery/" +noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 06a2ac582..71993f72c 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -9,3 +9,4 @@ tls: true key-dir: "./.env/handler/" auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg +noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index 3b67552b2..73c494b5a 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -6,3 +6,4 @@ auth-servers: ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/networkserver/" +noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 66c379c5e..232ea9cc2 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -11,3 +11,4 @@ router: skip-verify-gateway-token: true auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA +noc-server: noc.thethingsnetwork.org:1905 From deb79af46bf47889ed1b89292a12b2a02c1e66b9 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:02:48 +0200 Subject: [PATCH 1937/2266] Revert ".env: specify `noc-server`" This reverts commit fd9c757e365cf84cb6324223ce5fcc56d020670e. --- .env/broker/dev.yml | 1 - .env/discovery/dev.yml | 1 - .env/networkserver/dev.yml | 1 - 3 files changed, 3 deletions(-) diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index fc63152a2..cf6779d9e 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -13,4 +13,3 @@ auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV broker: networkserver-cert: ./.env/broker/networkserver.cert networkserver-token: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE0Njk3OTQzNTksIm5iZiI6MTQ2OTc5NDM1OSwic3ViIjoiZGV2In0.Xj1AFEAblzFLdS5cUiDrG773EqHazARvGdkTHFoPa_c0XGhdKSrevVpTNkzvcXeTQTWnTQrg1t98atNIk7F13Q -noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index 4f28700d6..217a4abb8 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -6,4 +6,3 @@ auth-servers: ttn-account-preview: "https://preview.account.thethingsnetwork.org" ttn-account-staging: "https://preview.account.thethingsnetwork.org" key-dir: "./.env/discovery/" -noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index 73c494b5a..3b67552b2 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -6,4 +6,3 @@ auth-servers: ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/networkserver/" -noc-server: noc.thethingsnetwork.org:1905 From fa696b70d26a9e805683fe8908d9ce3484c3c4b7 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:12:58 +0200 Subject: [PATCH 1938/2266] core/router/gateway: Warn on NOC push errors --- core/router/gateway/gateway.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 62fe8a26b..a9f511966 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -62,11 +62,11 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { go func() { cl, err := g.statusMonitor() if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish status connection to the monitor") + g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish status connection to the monitor") } if err = cl.Send(status); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Monitor status push failed") + g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor status push failed") } }() } @@ -84,11 +84,11 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { go func() { cl, err := g.uplinkMonitor() if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish uplink connection to the monitor") + g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish uplink connection to the monitor") } if err = cl.Send(uplink); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Monitor uplink push failed") + g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor uplink push failed") } }() } @@ -106,11 +106,11 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink go func() { cl, err := g.downlinkMonitor() if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Failed to establish downlink connection to the monitor") + g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish downlink connection to the monitor") } if err = cl.Send(downlink); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Error("Monitor downlink push failed") + g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor downlink push failed") } }() } From 364b0b625c572e97e9d84456603aa1632980b3c4 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:13:27 +0200 Subject: [PATCH 1939/2266] cmd/root.go: refactoring --- cmd/root.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a438f6df3..583e22478 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,12 +84,11 @@ var RootCmd = &cobra.Command{ "Description": viper.GetString("description"), "DiscoveryServer": viper.GetString("discovery-server"), "AuthServers": viper.GetStringMapString("auth-servers"), - - "NocServer": func() string { - if nocAddr := viper.GetString("noc-server"); len(nocAddr) > 0 { + "NOCServer": func() string { + if nocAddr := viper.GetString("noc-server"); nocAddr != "" { return nocAddr } - return "undefined" + return "" }(), }).Info("Initializing The Things Network") }, From e963fe6c0834a1d8533baf65eb5416db8e82f716 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:14:03 +0200 Subject: [PATCH 1940/2266] cmd/root.go: don't specify default noc address --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 583e22478..4a4fe320c 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -130,7 +130,7 @@ func init() { RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) - RootCmd.PersistentFlags().String("noc-server", "noc.thethingsnetwork.org:1905", "The address of the Noc server") + RootCmd.PersistentFlags().String("noc-server", "", "The address of the Noc server") viper.BindPFlag("noc-server", RootCmd.PersistentFlags().Lookup("noc-server")) viper.SetDefault("auth-servers", map[string]string{ From 3c5fdef6052051fd876bab57e4d9a3b086bce429 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:15:39 +0200 Subject: [PATCH 1941/2266] core/component.go: dont't block on NOC connection --- core/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/component.go b/core/component.go index d1a11e93e..c7b85a496 100644 --- a/core/component.go +++ b/core/component.go @@ -130,7 +130,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } if nocAddr := viper.GetString("noc-server"); len(nocAddr) > 0 { - conn, err := grpc.Dial(nocAddr, append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) + conn, err := grpc.Dial(nocAddr, append(api.DialOptions, grpc.WithInsecure())...) if err != nil { return nil, err } From e27e12cff4f63495bacd16a8c29e9c212fb9a5af Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:22:56 +0200 Subject: [PATCH 1942/2266] core/component.go: refactoring --- core/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/component.go b/core/component.go index c7b85a496..2720bdff1 100644 --- a/core/component.go +++ b/core/component.go @@ -129,7 +129,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) } - if nocAddr := viper.GetString("noc-server"); len(nocAddr) > 0 { + if nocAddr := viper.GetString("noc-server"); nocAddr != "" { conn, err := grpc.Dial(nocAddr, append(api.DialOptions, grpc.WithInsecure())...) if err != nil { return nil, err From 6c32633132195e9e45c90656b213b3648a91bb0a Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 14:24:21 +0200 Subject: [PATCH 1943/2266] cmd/root.go: Noc -> NOC --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index 4a4fe320c..a6bbdf457 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -130,7 +130,7 @@ func init() { RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) - RootCmd.PersistentFlags().String("noc-server", "", "The address of the Noc server") + RootCmd.PersistentFlags().String("noc-server", "", "The address of the NOC server") viper.BindPFlag("noc-server", RootCmd.PersistentFlags().Lookup("noc-server")) viper.SetDefault("auth-servers", map[string]string{ From 44d8303399def80311ce04aedc91e46000c08d38 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 16:09:11 +0200 Subject: [PATCH 1944/2266] cmd/root.go: refactoring --- cmd/root.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a6bbdf457..a9651f1ef 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,12 +84,7 @@ var RootCmd = &cobra.Command{ "Description": viper.GetString("description"), "DiscoveryServer": viper.GetString("discovery-server"), "AuthServers": viper.GetStringMapString("auth-servers"), - "NOCServer": func() string { - if nocAddr := viper.GetString("noc-server"); nocAddr != "" { - return nocAddr - } - return "" - }(), + "NOCServer": viper.GetString("noc-server"), }).Info("Initializing The Things Network") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { From 618f3a2b1f0a9f086570545cb5d586d298603c40 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 6 Oct 2016 17:31:36 +0200 Subject: [PATCH 1945/2266] core/router/gateway: log monitor pushes --- core/router/gateway/gateway.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index a9f511966..05b0a6ae6 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -67,6 +67,8 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { if err = cl.Send(status); err != nil { g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor status push failed") + } else { + g.Ctx.Info("Pushed status to monitor") } }() } @@ -89,6 +91,8 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { if err = cl.Send(uplink); err != nil { g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor uplink push failed") + } else { + g.Ctx.Info("Pushed uplink to monitor") } }() } @@ -111,6 +115,8 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink if err = cl.Send(downlink); err != nil { g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor downlink push failed") + } else { + g.Ctx.Info("Pushed downlink to monitor") } }() } From b87a3090e1d7c3e67724321243a260814d27c2b1 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 15:08:05 +0200 Subject: [PATCH 1946/2266] api/noc.NewClient: NewMonitorClient wrapper --- api/noc/client.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 api/noc/client.go diff --git a/api/noc/client.go b/api/noc/client.go new file mode 100644 index 000000000..2fe28ee3b --- /dev/null +++ b/api/noc/client.go @@ -0,0 +1,17 @@ +package noc + +import ( + "github.com/TheThingsNetwork/ttn/api" + "google.golang.org/grpc" +) + +// NewClient is a wrapper for NewMonitorClient, initializes +// connection to MonitorServer on monitorAddr with default gRPC options +func NewClient(monitorAddr string) (cl MonitorClient, err error) { + var conn *grpc.ClientConn + if conn, err = grpc.Dial(monitorAddr, append(api.DialOptions, grpc.WithInsecure())...); err != nil { + return nil, err + } + + return NewMonitorClient(conn), nil +} From 68a528fb173a14ec2e3c3a05d0013144d3c8b8db Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 14:57:15 +0200 Subject: [PATCH 1947/2266] reimplementation of #261 - Allows to use multiple monitors instead of only one(uses internal hashmap name->client) - Reopens stream, if it is closed - Adds logging features --- core/router/gateway/gateway.go | 52 +++----- core/router/gateway/monitor.go | 233 ++++++++++++++++++++++++--------- core/router/router.go | 4 +- 3 files changed, 187 insertions(+), 102 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 05b0a6ae6..3c745edf2 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -9,7 +9,6 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/gateway" pb_router "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -59,18 +58,11 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { g.updateLastSeen() if g.monitor != nil { - go func() { - cl, err := g.statusMonitor() - if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish status connection to the monitor") - } - - if err = cl.Send(status); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor status push failed") - } else { - g.Ctx.Info("Pushed status to monitor") - } - }() + g.monitor.RLock() + for name := range g.monitor.clients { + go g.pushStatusToMonitor(g.Ctx.WithField("monitor", name), name, status) + } + g.monitor.RUnlock() } return nil } @@ -83,18 +75,11 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { g.updateLastSeen() if g.monitor != nil { - go func() { - cl, err := g.uplinkMonitor() - if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish uplink connection to the monitor") - } - - if err = cl.Send(uplink); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor uplink push failed") - } else { - g.Ctx.Info("Pushed uplink to monitor") - } - }() + g.monitor.RLock() + for name := range g.monitor.clients { + go g.pushUplinkToMonitor(g.Ctx.WithField("monitor", name), name, uplink) + } + g.monitor.RUnlock() } return nil } @@ -107,18 +92,11 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink } if g.monitor != nil { - go func() { - cl, err := g.downlinkMonitor() - if err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish downlink connection to the monitor") - } - - if err = cl.Send(downlink); err != nil { - g.Ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor downlink push failed") - } else { - g.Ctx.Info("Pushed downlink to monitor") - } - }() + g.monitor.RLock() + for name := range g.monitor.clients { + go g.pushDownlinkToMonitor(ctx.WithField("monitor", name), name, downlink) + } + g.monitor.RUnlock() } return nil } diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 60d7d9faa..dc494e5c3 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -1,11 +1,16 @@ package gateway import ( + "io" "sync" context "golang.org/x/net/context" + pb "github.com/TheThingsNetwork/ttn/api/gateway" pb_noc "github.com/TheThingsNetwork/ttn/api/noc" + pb_router "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" ) func (g *Gateway) monitorContext() (ctx context.Context) { @@ -15,88 +20,190 @@ func (g *Gateway) monitorContext() (ctx context.Context) { } type monitorConn struct { - client pb_noc.MonitorClient + clients map[string]pb_noc.MonitorClient + sync.RWMutex - uplink struct { - client pb_noc.Monitor_GatewayUplinkClient - sync.Mutex - } - downlink struct { - client pb_noc.Monitor_GatewayDownlinkClient - sync.Mutex - } status struct { - client pb_noc.Monitor_GatewayStatusClient - sync.Mutex + streams map[string]pb_noc.Monitor_GatewayStatusClient + sync.RWMutex } -} -func (g *Gateway) SetMonitor(client pb_noc.MonitorClient) { - g.monitor = &monitorConn{client: client} -} - -func (g *Gateway) uplinkMonitor() (client pb_noc.Monitor_GatewayUplinkClient, err error) { - g.monitor.uplink.Lock() - defer g.monitor.uplink.Unlock() - - if g.monitor.uplink.client != nil { - return g.monitor.uplink.client, nil + uplink struct { + streams map[string]pb_noc.Monitor_GatewayUplinkClient + sync.RWMutex } - return g.connectUplinkMonitor() -} -func (g *Gateway) downlinkMonitor() (client pb_noc.Monitor_GatewayDownlinkClient, err error) { - g.monitor.downlink.Lock() - defer g.monitor.downlink.Unlock() - - if g.monitor.downlink.client != nil { - return g.monitor.downlink.client, nil + downlink struct { + streams map[string]pb_noc.Monitor_GatewayDownlinkClient + sync.RWMutex } - return g.connectDownlinkMonitor() } -func (g *Gateway) statusMonitor() (client pb_noc.Monitor_GatewayStatusClient, err error) { - g.monitor.status.Lock() - defer g.monitor.status.Unlock() - - if g.monitor.status.client != nil { - return g.monitor.status.client, nil - } - return g.connectStatusMonitor() +func (g *Gateway) SetMonitors(clients map[string]pb_noc.MonitorClient) { + g.monitor = NewMonitorConn(clients) } -func (g *Gateway) connectUplinkMonitor() (client pb_noc.Monitor_GatewayUplinkClient, err error) { - if client, err = connectUplinkMonitor(g.monitorContext(), g.monitor.client); err != nil { - return nil, err - } - g.monitor.uplink.client = client - return client, nil +func NewMonitorConn(clients map[string]pb_noc.MonitorClient) (conn *monitorConn) { + conn = &monitorConn{clients: clients} + conn.uplink.streams = map[string]pb_noc.Monitor_GatewayUplinkClient{} + conn.downlink.streams = map[string]pb_noc.Monitor_GatewayDownlinkClient{} + conn.status.streams = map[string]pb_noc.Monitor_GatewayStatusClient{} + return conn } -func (g *Gateway) connectDownlinkMonitor() (client pb_noc.Monitor_GatewayDownlinkClient, err error) { - if client, err = connectDownlinkMonitor(g.monitorContext(), g.monitor.client); err != nil { - return nil, err +func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb.Status) (err error) { + defer func() { + switch err { + case nil: + ctx.Info("Pushed status to monitor") + + case io.EOF: + ctx.Info("Stream closed, retrying") + g.monitor.status.Lock() + delete(g.monitor.status.streams, name) + g.monitor.status.Unlock() + err = g.pushStatusToMonitor(ctx, name, status) + + default: + ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor status push failed") + } + }() + + g.monitor.status.RLock() + stream, ok := g.monitor.status.streams[name] + g.monitor.status.RUnlock() + + if !ok { + g.monitor.status.Lock() + if _, ok := g.monitor.status.streams[name]; !ok { + g.monitor.RLock() + cl, ok := g.monitor.clients[name] + if !ok { + // Should not happen + return errors.New("Monitor not found") + } + + if stream, err = cl.GatewayStatus(g.monitorContext()); err != nil { + // TODO check if err returned is GRPC error + err = errors.FromGRPCError(err) + ctx.WithError(err).Warn("Failed to open new status stream") + return err + } + ctx.Info("Opened new status stream") + g.monitor.status.streams[name] = stream + + g.monitor.RUnlock() + } + g.monitor.status.Unlock() } - g.monitor.downlink.client = client - return client, nil + + return stream.Send(status) } -func (g *Gateway) connectStatusMonitor() (client pb_noc.Monitor_GatewayStatusClient, err error) { - if client, err = connectStatusMonitor(g.monitorContext(), g.monitor.client); err != nil { - return nil, err +func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb_router.UplinkMessage) (err error) { + defer func() { + switch err { + case nil: + ctx.Info("Pushed uplink to monitor") + + case io.EOF: + ctx.Info("Stream closed, retrying") + g.monitor.uplink.Lock() + delete(g.monitor.uplink.streams, name) + g.monitor.uplink.Unlock() + err = g.pushUplinkToMonitor(ctx, name, uplink) + + default: + ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor uplink push failed") + } + }() + + ctx.Debug("g.monitor.uplink.RLock") + g.monitor.uplink.RLock() + stream, ok := g.monitor.uplink.streams[name] + ctx.Debug("g.monitor.uplink.RUnlock") + g.monitor.uplink.RUnlock() + + if !ok { + ctx.Debug("g.monitor.uplink.Lock") + g.monitor.uplink.Lock() + defer g.monitor.uplink.Unlock() + + if _, ok := g.monitor.uplink.streams[name]; !ok { + ctx.Debug("g.monitor.RLock") + g.monitor.RLock() + defer g.monitor.RUnlock() + + cl, ok := g.monitor.clients[name] + if !ok { + // Should not happen + return errors.New("Monitor not found") + } + + ctx.Debug("cl.GatewayUplink") + if stream, err = cl.GatewayUplink(g.monitorContext()); err != nil { + // TODO check if err returned is GRPC error + err = errors.FromGRPCError(err) + ctx.WithError(err).Warn("Failed to open new uplink stream") + return err + } + ctx.Info("Opened new uplink stream") + g.monitor.uplink.streams[name] = stream + + ctx.Debug("g.monitor.RUnlock") + } + ctx.Debug("g.monitor.uplink.RUnlock") } - g.monitor.status.client = client - return client, nil -} -func connectDownlinkMonitor(ctx context.Context, client pb_noc.MonitorClient) (pb_noc.Monitor_GatewayDownlinkClient, error) { - return client.GatewayDownlink(ctx) + ctx.Debug("stream.Send(uplink)") + return stream.Send(uplink) } -func connectUplinkMonitor(ctx context.Context, client pb_noc.MonitorClient) (pb_noc.Monitor_GatewayUplinkClient, error) { - return client.GatewayUplink(ctx) -} +func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink *pb_router.DownlinkMessage) (err error) { + defer func() { + switch err { + case nil: + ctx.Info("Pushed downlink to monitor") + + case io.EOF: + ctx.Info("Stream closed, retrying") + g.monitor.downlink.Lock() + delete(g.monitor.downlink.streams, name) + g.monitor.downlink.Unlock() + err = g.pushDownlinkToMonitor(ctx, name, downlink) + + default: + ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor downlink push failed") + } + }() + + g.monitor.downlink.RLock() + stream, ok := g.monitor.downlink.streams[name] + g.monitor.downlink.RUnlock() + + if !ok { + g.monitor.downlink.Lock() + if _, ok := g.monitor.downlink.streams[name]; !ok { + g.monitor.RLock() + cl, ok := g.monitor.clients[name] + if !ok { + // Should not happen + return errors.New("Monitor not found") + } + + if stream, err = cl.GatewayDownlink(g.monitorContext()); err != nil { + // TODO check if err returned is GRPC error + err = errors.FromGRPCError(err) + ctx.WithError(err).Warn("Failed to open new downlink stream") + return err + } + ctx.Info("Opened new downlink stream") + g.monitor.downlink.streams[name] = stream + + g.monitor.RUnlock() + } + g.monitor.downlink.Unlock() + } -func connectStatusMonitor(ctx context.Context, client pb_noc.MonitorClient) (pb_noc.Monitor_GatewayStatusClient, error) { - return client.GatewayStatus(ctx) + return stream.Send(downlink) } diff --git a/core/router/router.go b/core/router/router.go index e18b340d8..26897f81e 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -106,8 +106,8 @@ func (r *router) getGateway(id string) *gateway.Gateway { if !ok { gtw = gateway.NewGateway(r.Ctx, id) - if r.Component.Monitor != nil { - gtw.SetMonitor(r.Component.Monitor) + if r.Component.Monitors != nil { + gtw.SetMonitors(r.Component.Monitors) } r.gateways[id] = gtw From 142ef02a227bf40951b90ea6ca61f34d3b641277 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 15:09:06 +0200 Subject: [PATCH 1948/2266] monitor-servers initialization --- cmd/root.go | 5 +---- core/component.go | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index a9651f1ef..9ac0598ee 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -84,7 +84,7 @@ var RootCmd = &cobra.Command{ "Description": viper.GetString("description"), "DiscoveryServer": viper.GetString("discovery-server"), "AuthServers": viper.GetStringMapString("auth-servers"), - "NOCServer": viper.GetString("noc-server"), + "Monitors": viper.GetStringMapString("monitor-servers"), }).Info("Initializing The Things Network") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { @@ -125,9 +125,6 @@ func init() { RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) - RootCmd.PersistentFlags().String("noc-server", "", "The address of the NOC server") - viper.BindPFlag("noc-server", RootCmd.PersistentFlags().Lookup("noc-server")) - viper.SetDefault("auth-servers", map[string]string{ "ttn-account": "https://account.thethingsnetwork.org", }) diff --git a/core/component.go b/core/component.go index 2720bdff1..11f533a05 100644 --- a/core/component.go +++ b/core/component.go @@ -15,7 +15,6 @@ import ( "github.com/TheThingsNetwork/go-account-lib/cache" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/tokenkey" - "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_noc "github.com/TheThingsNetwork/ttn/api/noc" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -129,13 +128,16 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) } - if nocAddr := viper.GetString("noc-server"); nocAddr != "" { - conn, err := grpc.Dial(nocAddr, append(api.DialOptions, grpc.WithInsecure())...) - if err != nil { - return nil, err + if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { + component.Monitors = map[string]pb_noc.MonitorClient{} + for name, addr := range monitors { + var err error + component.Monitors[name], err = pb_noc.NewClient(addr) + if err != nil { + // Assuming grpc.WithBlock() is not set + return nil, err + } } - - component.Monitor = pb_noc.NewMonitorClient(conn) } return component, nil @@ -155,7 +157,7 @@ const ( type Component struct { Identity *pb_discovery.Announcement Discovery pb_discovery.Client - Monitor pb_noc.MonitorClient + Monitors map[string]pb_noc.MonitorClient Ctx log.Interface AccessToken string privateKey *ecdsa.PrivateKey From 8e282ba020c6d654530bfd6f2f08f61e1d341ae8 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 15:21:11 +0200 Subject: [PATCH 1949/2266] gateway monitor: remove debugging statements --- core/router/gateway/monitor.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index dc494e5c3..c0560b9bb 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -118,19 +118,15 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb } }() - ctx.Debug("g.monitor.uplink.RLock") g.monitor.uplink.RLock() stream, ok := g.monitor.uplink.streams[name] - ctx.Debug("g.monitor.uplink.RUnlock") g.monitor.uplink.RUnlock() if !ok { - ctx.Debug("g.monitor.uplink.Lock") g.monitor.uplink.Lock() defer g.monitor.uplink.Unlock() if _, ok := g.monitor.uplink.streams[name]; !ok { - ctx.Debug("g.monitor.RLock") g.monitor.RLock() defer g.monitor.RUnlock() @@ -140,22 +136,17 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb return errors.New("Monitor not found") } - ctx.Debug("cl.GatewayUplink") if stream, err = cl.GatewayUplink(g.monitorContext()); err != nil { // TODO check if err returned is GRPC error err = errors.FromGRPCError(err) ctx.WithError(err).Warn("Failed to open new uplink stream") return err } - ctx.Info("Opened new uplink stream") g.monitor.uplink.streams[name] = stream - ctx.Debug("g.monitor.RUnlock") } - ctx.Debug("g.monitor.uplink.RUnlock") } - ctx.Debug("stream.Send(uplink)") return stream.Send(uplink) } From 9ead9181ff23880ae70aecee7f8a40f36d871fe7 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 15:45:50 +0200 Subject: [PATCH 1950/2266] gateway monitor: pass metadata in context --- core/router/gateway/monitor.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index c0560b9bb..2b53d43c1 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -4,6 +4,8 @@ import ( "io" "sync" + "google.golang.org/grpc/metadata" + context "golang.org/x/net/context" pb "github.com/TheThingsNetwork/ttn/api/gateway" @@ -14,9 +16,10 @@ import ( ) func (g *Gateway) monitorContext() (ctx context.Context) { - ctx = context.WithValue(context.Background(), "id", g.ID) - ctx = context.WithValue(ctx, "token", g.Token) - return ctx + return metadata.NewContext(context.Background(), metadata.Pairs( + "id", g.ID, + "token", g.Token, + )) } type monitorConn struct { From c80151052e1f9e76beb94a78b88b969563e36b0a Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 15:59:04 +0200 Subject: [PATCH 1951/2266] gateway monitor: SetMonitors safe for concurrent use --- core/router/gateway/monitor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 2b53d43c1..98b4379d2 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -43,6 +43,9 @@ type monitorConn struct { } func (g *Gateway) SetMonitors(clients map[string]pb_noc.MonitorClient) { + g.monitor.Lock() + defer g.monitor.Unlock() + g.monitor = NewMonitorConn(clients) } From d7c5d2761feabafb83a94a910c01911dcf5e6231 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 10 Oct 2016 16:28:33 +0200 Subject: [PATCH 1952/2266] gateway monitor: mutex refactoring --- core/router/gateway/gateway.go | 6 ------ core/router/gateway/monitor.go | 17 +---------------- 2 files changed, 1 insertion(+), 22 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 3c745edf2..858632b20 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -58,11 +58,9 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { g.updateLastSeen() if g.monitor != nil { - g.monitor.RLock() for name := range g.monitor.clients { go g.pushStatusToMonitor(g.Ctx.WithField("monitor", name), name, status) } - g.monitor.RUnlock() } return nil } @@ -75,11 +73,9 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { g.updateLastSeen() if g.monitor != nil { - g.monitor.RLock() for name := range g.monitor.clients { go g.pushUplinkToMonitor(g.Ctx.WithField("monitor", name), name, uplink) } - g.monitor.RUnlock() } return nil } @@ -92,11 +88,9 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink } if g.monitor != nil { - g.monitor.RLock() for name := range g.monitor.clients { go g.pushDownlinkToMonitor(ctx.WithField("monitor", name), name, downlink) } - g.monitor.RUnlock() } return nil } diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 98b4379d2..cb46a85fe 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -24,7 +24,6 @@ func (g *Gateway) monitorContext() (ctx context.Context) { type monitorConn struct { clients map[string]pb_noc.MonitorClient - sync.RWMutex status struct { streams map[string]pb_noc.Monitor_GatewayStatusClient @@ -43,9 +42,6 @@ type monitorConn struct { } func (g *Gateway) SetMonitors(clients map[string]pb_noc.MonitorClient) { - g.monitor.Lock() - defer g.monitor.Unlock() - g.monitor = NewMonitorConn(clients) } @@ -82,7 +78,6 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb if !ok { g.monitor.status.Lock() if _, ok := g.monitor.status.streams[name]; !ok { - g.monitor.RLock() cl, ok := g.monitor.clients[name] if !ok { // Should not happen @@ -97,8 +92,6 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb } ctx.Info("Opened new status stream") g.monitor.status.streams[name] = stream - - g.monitor.RUnlock() } g.monitor.status.Unlock() } @@ -130,12 +123,7 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb if !ok { g.monitor.uplink.Lock() - defer g.monitor.uplink.Unlock() - if _, ok := g.monitor.uplink.streams[name]; !ok { - g.monitor.RLock() - defer g.monitor.RUnlock() - cl, ok := g.monitor.clients[name] if !ok { // Should not happen @@ -149,8 +137,8 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb return err } g.monitor.uplink.streams[name] = stream - } + g.monitor.uplink.Unlock() } return stream.Send(uplink) @@ -181,7 +169,6 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink if !ok { g.monitor.downlink.Lock() if _, ok := g.monitor.downlink.streams[name]; !ok { - g.monitor.RLock() cl, ok := g.monitor.clients[name] if !ok { // Should not happen @@ -196,8 +183,6 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink } ctx.Info("Opened new downlink stream") g.monitor.downlink.streams[name] = stream - - g.monitor.RUnlock() } g.monitor.downlink.Unlock() } From 8953e07e53b4247d3702c5dcb7e286d67163b262 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 11 Oct 2016 17:23:05 +0200 Subject: [PATCH 1953/2266] api/noc -> api/monitor --- api/{noc => monitor}/client.go | 2 +- api/{noc/noc.pb.go => monitor/monitor.pb.go} | 56 ++++++++++---------- api/{noc/noc.proto => monitor/monitor.proto} | 2 +- core/component.go | 2 +- core/router/gateway/monitor.go | 2 +- 5 files changed, 32 insertions(+), 32 deletions(-) rename api/{noc => monitor}/client.go (96%) rename api/{noc/noc.pb.go => monitor/monitor.pb.go} (86%) rename api/{noc/noc.proto => monitor/monitor.proto} (98%) diff --git a/api/noc/client.go b/api/monitor/client.go similarity index 96% rename from api/noc/client.go rename to api/monitor/client.go index 2fe28ee3b..2421b8fc8 100644 --- a/api/noc/client.go +++ b/api/monitor/client.go @@ -1,4 +1,4 @@ -package noc +package monitor import ( "github.com/TheThingsNetwork/ttn/api" diff --git a/api/noc/noc.pb.go b/api/monitor/monitor.pb.go similarity index 86% rename from api/noc/noc.pb.go rename to api/monitor/monitor.pb.go index 85d0977bd..e47adfb3e 100644 --- a/api/noc/noc.pb.go +++ b/api/monitor/monitor.pb.go @@ -1,16 +1,16 @@ // Code generated by protoc-gen-gogo. -// source: github.com/TheThingsNetwork/ttn/api/noc/noc.proto +// source: github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto // DO NOT EDIT! /* Package noc is a generated protocol buffer package. It is generated from these files: - github.com/TheThingsNetwork/ttn/api/noc/noc.proto + github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto It has these top-level messages: */ -package noc +package monitor import proto "github.com/golang/protobuf/proto" import fmt "fmt" @@ -475,32 +475,32 @@ var _Monitor_serviceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, - Metadata: fileDescriptorNoc, + Metadata: fileDescriptorMonitor, } func init() { - proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/noc/noc.proto", fileDescriptorNoc) -} - -var fileDescriptorNoc = []byte{ - // 300 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x91, 0x41, 0x4a, 0xc4, 0x30, - 0x14, 0x86, 0xad, 0x82, 0x42, 0x98, 0x71, 0xa4, 0xa0, 0x42, 0x85, 0xae, 0x5d, 0x25, 0xa8, 0x0b, - 0xc7, 0x59, 0xc9, 0x30, 0xa2, 0x9b, 0x71, 0xa1, 0xe3, 0x01, 0xd2, 0x1a, 0xd3, 0xd2, 0x4e, 0x5e, - 0x49, 0x5f, 0x29, 0xde, 0xc4, 0x23, 0xb9, 0xf4, 0x08, 0x52, 0xcf, 0x21, 0xc8, 0x34, 0x49, 0x97, - 0xb6, 0xb3, 0x28, 0x3f, 0x09, 0xfd, 0x3e, 0xf2, 0xbf, 0x47, 0x2e, 0x64, 0x8a, 0x49, 0x15, 0xd1, - 0x18, 0xd6, 0x6c, 0x95, 0x88, 0x55, 0x92, 0x2a, 0x59, 0x3e, 0x0a, 0xac, 0x41, 0x67, 0x0c, 0x51, - 0x31, 0x5e, 0xa4, 0x4c, 0x41, 0xbc, 0xf9, 0x68, 0xa1, 0x01, 0xc1, 0xdf, 0x53, 0x10, 0x07, 0x37, - 0x43, 0x38, 0xc9, 0x51, 0xd4, 0xfc, 0xdd, 0xa5, 0xe1, 0x83, 0xeb, 0x21, 0xa8, 0x86, 0x0a, 0x85, - 0xb6, 0xb1, 0x0d, 0x18, 0x69, 0xc8, 0x84, 0xb6, 0x61, 0xc1, 0x41, 0x8f, 0x4d, 0xb8, 0x7a, 0xcd, - 0x85, 0x76, 0x69, 0xd1, 0x33, 0x09, 0x20, 0x73, 0xc1, 0xda, 0x53, 0x54, 0xbd, 0x31, 0xb1, 0x2e, - 0xd0, 0x36, 0xb9, 0xfc, 0xdd, 0x25, 0x07, 0x4b, 0x50, 0x29, 0x82, 0xf6, 0x67, 0x64, 0x7c, 0x6f, - 0x6a, 0x3e, 0x23, 0xc7, 0xaa, 0xf4, 0x27, 0xd4, 0xd5, 0x36, 0x17, 0xc1, 0x09, 0x35, 0x2e, 0xea, - 0x5c, 0xf4, 0x6e, 0xe3, 0x3a, 0xf7, 0xfc, 0xdb, 0x8e, 0x7d, 0x29, 0xf2, 0x54, 0x65, 0xfe, 0x31, - 0xb5, 0xc5, 0xcd, 0x79, 0x29, 0xca, 0x92, 0x4b, 0xf1, 0x8f, 0x61, 0x41, 0x26, 0xd6, 0xb0, 0x80, - 0x5a, 0xb5, 0x8e, 0x53, 0xe7, 0x70, 0x37, 0xfd, 0x96, 0x29, 0x19, 0x3d, 0xb5, 0x8c, 0xad, 0x70, - 0xe8, 0x14, 0xbd, 0x0d, 0xa6, 0x64, 0x34, 0x6f, 0x27, 0xde, 0x91, 0x76, 0x01, 0xbd, 0xe4, 0x8c, - 0x8c, 0x1f, 0xcc, 0xc4, 0xbb, 0xb9, 0xb9, 0x0d, 0xf4, 0xb1, 0xf3, 0xa3, 0xcf, 0x26, 0xf4, 0xbe, - 0x9a, 0xd0, 0xfb, 0x6e, 0x42, 0xef, 0xe3, 0x27, 0xdc, 0x89, 0xf6, 0xdb, 0x7f, 0xae, 0xfe, 0x02, - 0x00, 0x00, 0xff, 0xff, 0x26, 0x4e, 0x3b, 0x20, 0xd7, 0x02, 0x00, 0x00, + proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto", fileDescriptorMonitor) +} + +var fileDescriptorMonitor = []byte{ + // 299 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x91, 0xcd, 0x4a, 0xf4, 0x30, + 0x18, 0x85, 0xbf, 0x7e, 0x82, 0x42, 0x98, 0x71, 0xa4, 0xa0, 0x42, 0x85, 0xae, 0x5d, 0x25, 0xa0, + 0x0b, 0xc7, 0x59, 0xc9, 0x30, 0xa2, 0x9b, 0x71, 0xa1, 0xe3, 0x05, 0xa4, 0x63, 0x4c, 0x4b, 0xdb, + 0xbc, 0x25, 0x7d, 0x4b, 0xf1, 0x4e, 0xbc, 0x24, 0x97, 0x5e, 0x82, 0xd4, 0xeb, 0x10, 0xc4, 0xfc, + 0x74, 0x69, 0xeb, 0xea, 0x90, 0xd0, 0xe7, 0xa1, 0xe7, 0x84, 0x5c, 0xca, 0x0c, 0xd3, 0x26, 0xa1, + 0x5b, 0x28, 0xd9, 0x26, 0x15, 0x9b, 0x34, 0x53, 0xb2, 0xbe, 0x13, 0xd8, 0x82, 0xce, 0x19, 0xa2, + 0x62, 0xbc, 0xca, 0x58, 0x09, 0x2a, 0x43, 0xd0, 0x3e, 0x69, 0xa5, 0x01, 0x21, 0xdc, 0x51, 0xb0, + 0x8d, 0x46, 0xf1, 0x92, 0xa3, 0x68, 0xf9, 0x8b, 0x4f, 0xcb, 0x47, 0x17, 0x63, 0x50, 0x0d, 0x0d, + 0x0a, 0xed, 0xe2, 0x2f, 0x60, 0xa2, 0x21, 0x17, 0xda, 0x85, 0x03, 0x47, 0xfd, 0x6c, 0xca, 0xd5, + 0x53, 0x21, 0xb4, 0x4f, 0x87, 0x9e, 0x48, 0x00, 0x59, 0x08, 0x66, 0x4e, 0x49, 0xf3, 0xcc, 0x44, + 0x59, 0xa1, 0x6b, 0x72, 0xf6, 0xf5, 0x9f, 0xec, 0xad, 0xed, 0x36, 0xe1, 0x82, 0x4c, 0x6f, 0x6c, + 0xcd, 0x07, 0xe4, 0xd8, 0xd4, 0xe1, 0x8c, 0xfa, 0xda, 0xf6, 0x22, 0x3a, 0xa2, 0xd6, 0x45, 0xbd, + 0x8b, 0x5e, 0xff, 0xb8, 0x4e, 0x83, 0xf0, 0xaa, 0x67, 0x1f, 0xab, 0x22, 0x53, 0x79, 0x78, 0x48, + 0x5d, 0x71, 0x7b, 0x5e, 0x8b, 0xba, 0xe6, 0x52, 0xfc, 0x62, 0x58, 0x91, 0x99, 0x33, 0xac, 0xa0, + 0x55, 0xc6, 0x71, 0xec, 0x1d, 0xfe, 0x66, 0xd8, 0x32, 0x27, 0x93, 0x7b, 0xc3, 0xb8, 0x0a, 0xfb, + 0x5e, 0x31, 0xd8, 0x60, 0x4e, 0x26, 0x4b, 0xb3, 0x78, 0x4f, 0xba, 0x07, 0x18, 0x24, 0x17, 0x64, + 0x7a, 0x6b, 0x17, 0xef, 0x77, 0xf3, 0x2f, 0x30, 0xc4, 0x2e, 0x0f, 0xde, 0xba, 0x38, 0x78, 0xef, + 0xe2, 0xe0, 0xa3, 0x8b, 0x83, 0xd7, 0xcf, 0xf8, 0x5f, 0xb2, 0x6b, 0xbe, 0x39, 0xff, 0x0e, 0x00, + 0x00, 0xff, 0xff, 0xbd, 0x86, 0x73, 0x31, 0xdf, 0x02, 0x00, 0x00, } diff --git a/api/noc/noc.proto b/api/monitor/monitor.proto similarity index 98% rename from api/noc/noc.proto rename to api/monitor/monitor.proto index 0e5a33d01..1e292ad8c 100644 --- a/api/noc/noc.proto +++ b/api/monitor/monitor.proto @@ -10,7 +10,7 @@ import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; import "google/protobuf/empty.proto"; -package noc; +package monitor; service Monitor { rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); diff --git a/core/component.go b/core/component.go index 11f533a05..a53cbf456 100644 --- a/core/component.go +++ b/core/component.go @@ -16,7 +16,7 @@ import ( "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/tokenkey" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" - pb_noc "github.com/TheThingsNetwork/ttn/api/noc" + pb_noc "github.com/TheThingsNetwork/ttn/api/monitor" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/TheThingsNetwork/ttn/utils/security" diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index cb46a85fe..6673635a3 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -9,7 +9,7 @@ import ( context "golang.org/x/net/context" pb "github.com/TheThingsNetwork/ttn/api/gateway" - pb_noc "github.com/TheThingsNetwork/ttn/api/noc" + pb_noc "github.com/TheThingsNetwork/ttn/api/monitor" pb_router "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" From 698c1898fb25215742149ce2011771232b1c6df2 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 11 Oct 2016 18:57:12 +0200 Subject: [PATCH 1954/2266] core/router/gateway/monitor.go: import fix --- core/router/gateway/monitor.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 6673635a3..832766921 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -4,15 +4,13 @@ import ( "io" "sync" - "google.golang.org/grpc/metadata" - - context "golang.org/x/net/context" - pb "github.com/TheThingsNetwork/ttn/api/gateway" pb_noc "github.com/TheThingsNetwork/ttn/api/monitor" pb_router "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" + context "golang.org/x/net/context" + "google.golang.org/grpc/metadata" ) func (g *Gateway) monitorContext() (ctx context.Context) { From 694cf6784f7cd18e778cca3e413930414747a439 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 11 Oct 2016 19:08:25 +0200 Subject: [PATCH 1955/2266] gateway monitor: logging refactor --- core/router/gateway/monitor.go | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 832766921..cfb60da1f 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -55,10 +55,10 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb defer func() { switch err { case nil: - ctx.Info("Pushed status to monitor") + ctx.Debug("Pushed status to monitor") case io.EOF: - ctx.Info("Stream closed, retrying") + ctx.Warn("Stream closed, retrying") g.monitor.status.Lock() delete(g.monitor.status.streams, name) g.monitor.status.Unlock() @@ -82,13 +82,15 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb return errors.New("Monitor not found") } - if stream, err = cl.GatewayStatus(g.monitorContext()); err != nil { + stream, err = cl.GatewayStatus(g.monitorContext()) + if err != nil { // TODO check if err returned is GRPC error err = errors.FromGRPCError(err) ctx.WithError(err).Warn("Failed to open new status stream") return err } - ctx.Info("Opened new status stream") + ctx.Debug("Opened new status stream") + g.monitor.status.streams[name] = stream } g.monitor.status.Unlock() @@ -101,10 +103,10 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb defer func() { switch err { case nil: - ctx.Info("Pushed uplink to monitor") + ctx.Debug("Pushed uplink to monitor") case io.EOF: - ctx.Info("Stream closed, retrying") + ctx.Warn("Stream closed, retrying") g.monitor.uplink.Lock() delete(g.monitor.uplink.streams, name) g.monitor.uplink.Unlock() @@ -128,12 +130,15 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb return errors.New("Monitor not found") } - if stream, err = cl.GatewayUplink(g.monitorContext()); err != nil { + stream, err = cl.GatewayUplink(g.monitorContext()) + if err != nil { // TODO check if err returned is GRPC error err = errors.FromGRPCError(err) ctx.WithError(err).Warn("Failed to open new uplink stream") return err } + ctx.Debug("Opened new uplink stream") + g.monitor.uplink.streams[name] = stream } g.monitor.uplink.Unlock() @@ -146,10 +151,10 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink defer func() { switch err { case nil: - ctx.Info("Pushed downlink to monitor") + ctx.Debug("Pushed downlink to monitor") case io.EOF: - ctx.Info("Stream closed, retrying") + ctx.Warn("Stream closed, retrying") g.monitor.downlink.Lock() delete(g.monitor.downlink.streams, name) g.monitor.downlink.Unlock() @@ -173,13 +178,15 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink return errors.New("Monitor not found") } - if stream, err = cl.GatewayDownlink(g.monitorContext()); err != nil { + stream, err = cl.GatewayDownlink(g.monitorContext()) + if err != nil { // TODO check if err returned is GRPC error err = errors.FromGRPCError(err) ctx.WithError(err).Warn("Failed to open new downlink stream") return err } - ctx.Info("Opened new downlink stream") + ctx.Debug("Opened new downlink stream") + g.monitor.downlink.streams[name] = stream } g.monitor.downlink.Unlock() From 21fdc1411f82095d18c34ef7e001bf0c832d1085 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 11 Oct 2016 19:20:16 +0200 Subject: [PATCH 1956/2266] refactor: initialize using `make` --- core/component.go | 2 +- core/router/gateway/monitor.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/component.go b/core/component.go index a53cbf456..4bbe1cd92 100644 --- a/core/component.go +++ b/core/component.go @@ -129,7 +129,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { - component.Monitors = map[string]pb_noc.MonitorClient{} + component.Monitors = make(map[string]pb_noc.MonitorClient) for name, addr := range monitors { var err error component.Monitors[name], err = pb_noc.NewClient(addr) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index cfb60da1f..050999f17 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -45,9 +45,9 @@ func (g *Gateway) SetMonitors(clients map[string]pb_noc.MonitorClient) { func NewMonitorConn(clients map[string]pb_noc.MonitorClient) (conn *monitorConn) { conn = &monitorConn{clients: clients} - conn.uplink.streams = map[string]pb_noc.Monitor_GatewayUplinkClient{} - conn.downlink.streams = map[string]pb_noc.Monitor_GatewayDownlinkClient{} - conn.status.streams = map[string]pb_noc.Monitor_GatewayStatusClient{} + conn.uplink.streams = make(map[string]pb_noc.Monitor_GatewayUplinkClient) + conn.downlink.streams = make(map[string]pb_noc.Monitor_GatewayDownlinkClient) + conn.status.streams = make(map[string]pb_noc.Monitor_GatewayStatusClient) return conn } From 37c184c194516604cfe45f988a99912f81c53783 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 11 Oct 2016 19:22:45 +0200 Subject: [PATCH 1957/2266] pb_noc -> pb_monitor --- core/component.go | 8 ++++---- core/router/gateway/monitor.go | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/core/component.go b/core/component.go index 4bbe1cd92..d1098909d 100644 --- a/core/component.go +++ b/core/component.go @@ -16,7 +16,7 @@ import ( "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/tokenkey" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" - pb_noc "github.com/TheThingsNetwork/ttn/api/monitor" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/TheThingsNetwork/ttn/utils/security" @@ -129,10 +129,10 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { - component.Monitors = make(map[string]pb_noc.MonitorClient) + component.Monitors = make(map[string]pb_monitor.MonitorClient) for name, addr := range monitors { var err error - component.Monitors[name], err = pb_noc.NewClient(addr) + component.Monitors[name], err = pb_monitor.NewClient(addr) if err != nil { // Assuming grpc.WithBlock() is not set return nil, err @@ -157,7 +157,7 @@ const ( type Component struct { Identity *pb_discovery.Announcement Discovery pb_discovery.Client - Monitors map[string]pb_noc.MonitorClient + Monitors map[string]pb_monitor.MonitorClient Ctx log.Interface AccessToken string privateKey *ecdsa.PrivateKey diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 050999f17..1a18d3848 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -5,7 +5,7 @@ import ( "sync" pb "github.com/TheThingsNetwork/ttn/api/gateway" - pb_noc "github.com/TheThingsNetwork/ttn/api/monitor" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" pb_router "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" @@ -21,33 +21,33 @@ func (g *Gateway) monitorContext() (ctx context.Context) { } type monitorConn struct { - clients map[string]pb_noc.MonitorClient + clients map[string]pb_monitor.MonitorClient status struct { - streams map[string]pb_noc.Monitor_GatewayStatusClient + streams map[string]pb_monitor.Monitor_GatewayStatusClient sync.RWMutex } uplink struct { - streams map[string]pb_noc.Monitor_GatewayUplinkClient + streams map[string]pb_monitor.Monitor_GatewayUplinkClient sync.RWMutex } downlink struct { - streams map[string]pb_noc.Monitor_GatewayDownlinkClient + streams map[string]pb_monitor.Monitor_GatewayDownlinkClient sync.RWMutex } } -func (g *Gateway) SetMonitors(clients map[string]pb_noc.MonitorClient) { +func (g *Gateway) SetMonitors(clients map[string]pb_monitor.MonitorClient) { g.monitor = NewMonitorConn(clients) } -func NewMonitorConn(clients map[string]pb_noc.MonitorClient) (conn *monitorConn) { +func NewMonitorConn(clients map[string]pb_monitor.MonitorClient) (conn *monitorConn) { conn = &monitorConn{clients: clients} - conn.uplink.streams = make(map[string]pb_noc.Monitor_GatewayUplinkClient) - conn.downlink.streams = make(map[string]pb_noc.Monitor_GatewayDownlinkClient) - conn.status.streams = make(map[string]pb_noc.Monitor_GatewayStatusClient) + conn.uplink.streams = make(map[string]pb_monitor.Monitor_GatewayUplinkClient) + conn.downlink.streams = make(map[string]pb_monitor.Monitor_GatewayDownlinkClient) + conn.status.streams = make(map[string]pb_monitor.Monitor_GatewayStatusClient) return conn } From 2c28a6903c353588bbb14fe8a693971f06dbeae5 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 12 Oct 2016 12:28:02 +0200 Subject: [PATCH 1958/2266] gateway monitor: `stream closed` refactor --- core/router/gateway/monitor.go | 75 ++++++++++++++++------------------ 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index 1a18d3848..dff2454d7 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -53,19 +53,10 @@ func NewMonitorConn(clients map[string]pb_monitor.MonitorClient) (conn *monitorC func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb.Status) (err error) { defer func() { - switch err { - case nil: - ctx.Debug("Pushed status to monitor") - - case io.EOF: - ctx.Warn("Stream closed, retrying") - g.monitor.status.Lock() - delete(g.monitor.status.streams, name) - g.monitor.status.Unlock() - err = g.pushStatusToMonitor(ctx, name, status) - - default: + if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor status push failed") + } else { + ctx.Debug("Pushed status to monitor") } }() @@ -96,24 +87,23 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb g.monitor.status.Unlock() } - return stream.Send(status) + if err = stream.Send(status); err == io.EOF { + ctx.Warn("Status stream closed") + g.monitor.status.Lock() + if g.monitor.status.streams[name] == stream { + delete(g.monitor.status.streams, name) + } + g.monitor.status.Unlock() + } + return err } func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb_router.UplinkMessage) (err error) { defer func() { - switch err { - case nil: - ctx.Debug("Pushed uplink to monitor") - - case io.EOF: - ctx.Warn("Stream closed, retrying") - g.monitor.uplink.Lock() - delete(g.monitor.uplink.streams, name) - g.monitor.uplink.Unlock() - err = g.pushUplinkToMonitor(ctx, name, uplink) - - default: + if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor uplink push failed") + } else { + ctx.Debug("Pushed uplink to monitor") } }() @@ -144,24 +134,23 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb g.monitor.uplink.Unlock() } - return stream.Send(uplink) + if err = stream.Send(uplink); err == io.EOF { + ctx.Warn("Uplink stream closed") + g.monitor.uplink.Lock() + if g.monitor.uplink.streams[name] == stream { + delete(g.monitor.uplink.streams, name) + } + g.monitor.uplink.Unlock() + } + return err } func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink *pb_router.DownlinkMessage) (err error) { defer func() { - switch err { - case nil: - ctx.Debug("Pushed downlink to monitor") - - case io.EOF: - ctx.Warn("Stream closed, retrying") - g.monitor.downlink.Lock() - delete(g.monitor.downlink.streams, name) - g.monitor.downlink.Unlock() - err = g.pushDownlinkToMonitor(ctx, name, downlink) - - default: + if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor downlink push failed") + } else { + ctx.Debug("Pushed downlink to monitor") } }() @@ -192,5 +181,13 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink g.monitor.downlink.Unlock() } - return stream.Send(downlink) + if err = stream.Send(downlink); err == io.EOF { + ctx.Warn("Downlink stream closed") + g.monitor.downlink.Lock() + if g.monitor.downlink.streams[name] == stream { + delete(g.monitor.downlink.streams, name) + } + g.monitor.downlink.Unlock() + } + return err } From 64247a15578518da9287383010299535fb640577 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 12 Oct 2016 12:48:32 +0200 Subject: [PATCH 1959/2266] gateway monitor: more explicit logging --- core/router/gateway/monitor.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index dff2454d7..aa9df3f44 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -75,12 +75,10 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb stream, err = cl.GatewayStatus(g.monitorContext()) if err != nil { - // TODO check if err returned is GRPC error - err = errors.FromGRPCError(err) - ctx.WithError(err).Warn("Failed to open new status stream") + ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") return err } - ctx.Debug("Opened new status stream") + ctx.Debug("Opened new monitor status stream") g.monitor.status.streams[name] = stream } @@ -88,7 +86,7 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb } if err = stream.Send(status); err == io.EOF { - ctx.Warn("Status stream closed") + ctx.Warn("Monitor status stream closed") g.monitor.status.Lock() if g.monitor.status.streams[name] == stream { delete(g.monitor.status.streams, name) @@ -122,12 +120,10 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb stream, err = cl.GatewayUplink(g.monitorContext()) if err != nil { - // TODO check if err returned is GRPC error - err = errors.FromGRPCError(err) - ctx.WithError(err).Warn("Failed to open new uplink stream") + ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") return err } - ctx.Debug("Opened new uplink stream") + ctx.Debug("Opened new monitor uplink stream") g.monitor.uplink.streams[name] = stream } @@ -135,7 +131,7 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb } if err = stream.Send(uplink); err == io.EOF { - ctx.Warn("Uplink stream closed") + ctx.Warn("Monitor uplink stream closed") g.monitor.uplink.Lock() if g.monitor.uplink.streams[name] == stream { delete(g.monitor.uplink.streams, name) @@ -169,12 +165,10 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink stream, err = cl.GatewayDownlink(g.monitorContext()) if err != nil { - // TODO check if err returned is GRPC error - err = errors.FromGRPCError(err) - ctx.WithError(err).Warn("Failed to open new downlink stream") + ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") return err } - ctx.Debug("Opened new downlink stream") + ctx.Debug("Opened new monitor downlink stream") g.monitor.downlink.streams[name] = stream } @@ -182,7 +176,7 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink } if err = stream.Send(downlink); err == io.EOF { - ctx.Warn("Downlink stream closed") + ctx.Warn("Monitor downlink stream closed") g.monitor.downlink.Lock() if g.monitor.downlink.streams[name] == stream { delete(g.monitor.downlink.streams, name) From 56dcf9f1d42da6357dbb3520fb9b900c45636bfc Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Wed, 12 Oct 2016 13:34:33 +0200 Subject: [PATCH 1960/2266] gateway monitor: mutex refactor --- core/router/gateway/monitor.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go index aa9df3f44..5c6e343cb 100644 --- a/core/router/gateway/monitor.go +++ b/core/router/gateway/monitor.go @@ -70,12 +70,14 @@ func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb cl, ok := g.monitor.clients[name] if !ok { // Should not happen + g.monitor.status.Unlock() return errors.New("Monitor not found") } stream, err = cl.GatewayStatus(g.monitorContext()) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") + g.monitor.status.Unlock() return err } ctx.Debug("Opened new monitor status stream") @@ -115,12 +117,14 @@ func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb cl, ok := g.monitor.clients[name] if !ok { // Should not happen + g.monitor.uplink.Unlock() return errors.New("Monitor not found") } stream, err = cl.GatewayUplink(g.monitorContext()) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") + g.monitor.uplink.Unlock() return err } ctx.Debug("Opened new monitor uplink stream") @@ -160,12 +164,14 @@ func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink cl, ok := g.monitor.clients[name] if !ok { // Should not happen + g.monitor.downlink.Unlock() return errors.New("Monitor not found") } stream, err = cl.GatewayDownlink(g.monitorContext()) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") + g.monitor.downlink.Unlock() return err } ctx.Debug("Opened new monitor downlink stream") From 2fc588fa77d1a74949e47c2ee70fd3473c6bea20 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Fri, 14 Oct 2016 14:38:41 +0200 Subject: [PATCH 1961/2266] fix rebase "artifacts" --- .env/handler/dev.yml | 1 - .env/router/dev.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 71993f72c..06a2ac582 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -9,4 +9,3 @@ tls: true key-dir: "./.env/handler/" auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg -noc-server: noc.thethingsnetwork.org:1905 diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 232ea9cc2..66c379c5e 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -11,4 +11,3 @@ router: skip-verify-gateway-token: true auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA -noc-server: noc.thethingsnetwork.org:1905 From 86e8d87639a5e7ce76e0203ba086c46dd35afa9a Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Sun, 16 Oct 2016 14:29:21 +0200 Subject: [PATCH 1962/2266] gateway monitor: capitalize log fields --- core/router/gateway/gateway.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 858632b20..3a1d7c55a 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -59,7 +59,7 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { if g.monitor != nil { for name := range g.monitor.clients { - go g.pushStatusToMonitor(g.Ctx.WithField("monitor", name), name, status) + go g.pushStatusToMonitor(g.Ctx.WithField("Monitor", name), name, status) } } return nil @@ -74,7 +74,7 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { if g.monitor != nil { for name := range g.monitor.clients { - go g.pushUplinkToMonitor(g.Ctx.WithField("monitor", name), name, uplink) + go g.pushUplinkToMonitor(g.Ctx.WithField("Monitor", name), name, uplink) } } return nil @@ -89,7 +89,7 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink if g.monitor != nil { for name := range g.monitor.clients { - go g.pushDownlinkToMonitor(ctx.WithField("monitor", name), name, downlink) + go g.pushDownlinkToMonitor(ctx.WithField("Monitor", name), name, downlink) } } return nil From 8f494aadfa4798202b69a7baf6d15131d7c29639 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 07:18:01 +0200 Subject: [PATCH 1963/2266] Better handling of gateway errors --- core/router/server.go | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index 4e6edc6a6..af8d0286d 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -4,8 +4,10 @@ package router import ( + "fmt" "io" + "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/router/gateway" @@ -34,18 +36,25 @@ func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gatewa return nil, err } - token, err := api.TokenFromMetadata(md) - if err != nil { - return nil, err - } + var token string if !viper.GetBool("router.skip-verify-gateway-token") { - claims, err := r.router.ValidateTTNAuthContext(ctx) + token, err = api.TokenFromMetadata(md) if err != nil { return nil, err } + if token == "" { + return nil, errors.NewErrPermissionDenied("No gateway token supplied") + } + if r.router.TokenKeyProvider == nil { + return nil, errors.NewErrInternal("No token provider configured") + } + claims, err := claims.FromToken(r.router.TokenKeyProvider, token) + if err != nil { + return nil, errors.NewErrPermissionDenied(fmt.Sprintf("Gateway token invalid: %s", err.Error())) + } if claims.Type != "gateway" || claims.Subject != gatewayID { - return nil, errors.NewErrPermissionDenied("Gateway token not authorized") + return nil, errors.NewErrPermissionDenied("Gateway token not consistent") } } From b2a2c49ebf43fa4283cf3b31bf1288bb5838fc53 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 08:19:19 +0200 Subject: [PATCH 1964/2266] Include hostnames in docker-compose --- docker-compose.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index b53917f8c..404af661b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,6 +13,7 @@ services: - "1883:1883" discovery: image: thethingsnetwork/ttn + hostname: discovery working_dir: /root command: discovery --config ./.env/discovery/dev.yml depends_on: @@ -25,6 +26,7 @@ services: - "./.env/:/root/.env/" router: image: thethingsnetwork/ttn + hostname: router working_dir: /root command: router --config ./.env/router/dev.yml depends_on: @@ -38,6 +40,7 @@ services: - "./.env/:/root/.env/" broker: image: thethingsnetwork/ttn + hostname: broker working_dir: /root command: broker --config ./.env/broker/dev.yml depends_on: @@ -53,6 +56,7 @@ services: - "./.env/:/root/.env/" networkserver: image: thethingsnetwork/ttn + hostname: networkserver working_dir: /root command: networkserver --config ./.env/networkserver/dev.yml depends_on: @@ -66,6 +70,7 @@ services: - "./.env/:/root/.env/" handler: image: thethingsnetwork/ttn + hostname: handler working_dir: /root command: handler --config ./.env/handler/dev.yml depends_on: @@ -83,6 +88,7 @@ services: - "./.env/:/root/.env/" bridge: image: thethingsnetwork/lora-gateway-bridge + hostname: bridge ports: - "1700:1700/udp" restart: always From f1b2eb17b6d006aa85138d68dcfc550e8e1e57a6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 08:20:11 +0200 Subject: [PATCH 1965/2266] Forward TERM environment to ttn containers --- docker-compose.yml | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 404af661b..89419d854 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,7 +19,8 @@ services: depends_on: - redis environment: - TTN_DISCOVERY_REDIS_ADDRESS: redis:6379 + - TERM + - TTN_DISCOVERY_REDIS_ADDRESS=redis:6379 ports: - "1900:1900" volumes: @@ -32,8 +33,9 @@ services: depends_on: - discovery environment: - TTN_DISCOVERY_SERVER: discovery:1900 - TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE: router + - TERM + - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE=router ports: - "1901:1901" volumes: @@ -47,9 +49,10 @@ services: - discovery - networkserver environment: - TTN_DISCOVERY_SERVER: discovery:1900 - TTN_BROKER_SERVER_ADDRESS_ANNOUNCE: broker - TTN_BROKER_NETWORKSERVER_ADDRESS: networkserver:1903 + - TERM + - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_BROKER_SERVER_ADDRESS_ANNOUNCE=broker + - TTN_BROKER_NETWORKSERVER_ADDRESS=networkserver:1903 ports: - "1902:1902" volumes: @@ -62,8 +65,9 @@ services: depends_on: - redis environment: - TTN_DISCOVERY_SERVER: discovery:1900 - TTN_NETWORKSERVER_REDIS_ADDRESS: redis:6379 + - TERM + - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_NETWORKSERVER_REDIS_ADDRESS=redis:6379 ports: - "1903:1903" volumes: @@ -78,10 +82,11 @@ services: - redis - mosquitto environment: - TTN_DISCOVERY_SERVER: discovery:1900 - TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE: handler - TTN_HANDLER_REDIS_ADDRESS: redis:6379 - TTN_HANDLER_MQTT_BROKER: mosquitto:1883 + - TERM + - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE=handler + - TTN_HANDLER_REDIS_ADDRESS=redis:6379 + - TTN_HANDLER_MQTT_BROKER=mosquitto:1883 ports: - "1904:1904" volumes: From 83bfd315c4d6153e0ba5fba7da8cbd1fd9cf2ffb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 08:30:52 +0200 Subject: [PATCH 1966/2266] Add dev gateway and token to docker-compose --- docker-compose.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 89419d854..71e207206 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -99,6 +99,12 @@ services: restart: always depends_on: [ router ] environment: - - UDP_BIND=:1700 - - TTN_DISCOVERY_SERVER=discovery:1900 - - TTN_ROUTER=dev + UDP_BIND: ":1700" + TTN_DISCOVERY_SERVER: discovery:1900 + TTN_ROUTER: dev + # The following environment variables make that communication for gateway + # "0102030405060708" is forwarded as authenticated communication for + # gateway "dev" + TTN_GATEWAY_EUI: 0102030405060708 + TTN_GATEWAY_ID: dev + TTN_GATEWAY_TOKEN: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImdhdGV3YXkiLCJpYXQiOjE0NzY0Mzk0Mzh9.kEOiLe9j4qRElZOt_bAXmZlva1nV6duIL0MDVa3bx2SEWC3qredaBWXWq4FmV4PKeI_zndovQtOoValH0B_6MW6vXuWL1wYzV6foTH5gQdxmn-iuQ1AmAIYbZeyHl9a-NPqDgkXLwKmo2iB1hUi9wV6HXfIOalguDtGJbmMfJ2tommsgmuNCXd-2zqhStSy8ArpROFXPm7voGDTcgm4hfchr7zhn-Er76R-eJa3RZ1Seo9BsiWrQ0N3VDSuh7ycCakZtkaLD4OTutAemcbzbrNJSOCvvZr8Asn-RmMkjKUdTN4Bgn3qlacIQ9iZikPLT8XyjFkj-8xjs3KAobWg40A From a387701b2f6fbb321ebac2de5c82804a8009f0e4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 10:21:36 +0200 Subject: [PATCH 1967/2266] Add pprof (only compiled when pprof tag present) --- Makefile | 2 +- core/pprof.go | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 core/pprof.go diff --git a/Makefile b/Makefile index 42e63aee3..c4215b059 100644 --- a/Makefile +++ b/Makefile @@ -99,7 +99,7 @@ splitfilename = $(subst ., ,$(subst -, ,$(subst $(RELEASE_DIR)/,,$1))) GOOSfromfilename = $(word 2, $(call splitfilename, $1)) GOARCHfromfilename = $(word 3, $(call splitfilename, $1)) LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" -GOBUILD = CGO_ENABLED=0 GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build -a -installsuffix cgo ${LDFLAGS} -o "$@" +GOBUILD = CGO_ENABLED=0 GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build -a -installsuffix cgo ${LDFLAGS} -tags "${TAGS}" -o "$@" ttn: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) diff --git a/core/pprof.go b/core/pprof.go new file mode 100644 index 000000000..0bd862acb --- /dev/null +++ b/core/pprof.go @@ -0,0 +1,8 @@ +// +build pprof + +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package core + +import _ "net/http/pprof" From ef4ecae4906fd5fffaf46ea906bb94d06b043323 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 10:39:29 +0200 Subject: [PATCH 1968/2266] Disable gRPC Tracing --- core/component.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/component.go b/core/component.go index 8c9709b24..a09dbe498 100644 --- a/core/component.go +++ b/core/component.go @@ -56,6 +56,10 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } }() + // Disable gRPC tracing + // SEE: https://github.com/grpc/grpc-go/issues/695 + grpc.EnableTracing = false + grpclog.SetLogger(logging.NewGRPCLogger(ctx)) component := &Component{ From e1949f13b0cb2452ccaebdcb60b62f51805e4625 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 13:39:53 +0200 Subject: [PATCH 1969/2266] Use global random to save memory --- core/handler/activation.go | 2 +- core/networkserver/activation.go | 3 -- core/router/gateway/schedule.go | 8 ++-- utils/random/random.go | 70 +++++++++++++++++++------------- 4 files changed, 46 insertions(+), 37 deletions(-) diff --git a/core/handler/activation.go b/core/handler/activation.go index d15a58f3d..64d2a6073 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -188,7 +188,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // NOTE: As DevNonces are only 2 bytes, we will start rejecting those before we run out of AppNonces. // It might just take some time to get one we didn't use yet... alreadyUsed = false - copy(appNonce[:], random.New().Bytes(3)) + copy(appNonce[:], random.Bytes(3)) for _, usedNonce := range dev.UsedAppNonces { if usedNonce == appNonce { alreadyUsed = true diff --git a/core/networkserver/activation.go b/core/networkserver/activation.go index 58243727c..33a828636 100644 --- a/core/networkserver/activation.go +++ b/core/networkserver/activation.go @@ -17,9 +17,6 @@ import ( ) func (n *networkServer) getDevAddr(constraints ...string) (types.DevAddr, error) { - // Instantiate a new random source - random := random.New() - // Generate random DevAddr bytes var devAddr types.DevAddr copy(devAddr[:], random.Bytes(4)) diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 9bef11593..93487b15a 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -37,9 +37,8 @@ type Schedule interface { // NewSchedule creates a new Schedule func NewSchedule(ctx log.Interface) Schedule { s := &schedule{ - ctx: ctx, - items: make(map[string]*scheduledItem), - random: random.New(), + ctx: ctx, + items: make(map[string]*scheduledItem), } go func() { for { @@ -73,7 +72,6 @@ type scheduledItem struct { type schedule struct { sync.RWMutex - random *random.TTNRandom ctx log.Interface offset int64 items map[string]*scheduledItem @@ -142,7 +140,7 @@ func (s *schedule) Sync(timestamp uint32) { // see interface func (s *schedule) GetOption(timestamp uint32, length uint32) (id string, score uint) { - id = s.random.String(32) + id = random.String(32) score = s.getConflicts(timestamp, length) item := &scheduledItem{ id: id, diff --git a/utils/random/random.go b/utils/random/random.go index 4d7d389d5..b929ef21e 100644 --- a/utils/random/random.go +++ b/utils/random/random.go @@ -21,28 +21,37 @@ const ( letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits ) +// TTNRandom is used as a wrapper around math/rand type TTNRandom struct { - sync.Mutex - *rand.Rand + mu sync.Mutex + rand *rand.Rand } +// New returns a new TTNRandom, in most cases you can just use the global funcs func New() *TTNRandom { return &TTNRandom{ - Rand: rand.New(rand.NewSource(time.Now().UnixNano())), + rand: rand.New(rand.NewSource(time.Now().UnixNano())), } } var global = New() +// Intn wraps rand.Intn +func (r *TTNRandom) Intn(n int) int { + r.mu.Lock() + defer r.mu.Unlock() + return r.rand.Intn(n) +} + // String returns random string of length n func (r *TTNRandom) String(n int) string { - r.Lock() - defer r.Unlock() + r.mu.Lock() + defer r.mu.Unlock() b := make([]byte, n) // A Int63() generates 63 random bits, enough for letterIdxMax characters! - for i, cache, remain := n-1, r.Int63(), letterIdxMax; i >= 0; { + for i, cache, remain := n-1, r.rand.Int63(), letterIdxMax; i >= 0; { if remain == 0 { - cache, remain = r.Int63(), letterIdxMax + cache, remain = r.rand.Int63(), letterIdxMax } if idx := int(cache & letterIdxMask); idx < len(letterBytes) { b[i] = letterBytes[idx] @@ -57,19 +66,19 @@ func (r *TTNRandom) String(n int) string { // Token generate a random 2-bytes token func (r *TTNRandom) Token() []byte { - r.Lock() - defer r.Unlock() + r.mu.Lock() + defer r.mu.Unlock() b := make([]byte, 4) - binary.BigEndian.PutUint32(b, r.Uint32()) + binary.BigEndian.PutUint32(b, r.rand.Uint32()) return b[0:2] } // Rssi generates RSSI signal between -120 < rssi < 0 func (r *TTNRandom) Rssi() int32 { - r.Lock() - defer r.Unlock() + r.mu.Lock() + defer r.mu.Unlock() // Generate RSSI. Tend towards generating great signal strength. - x := float64(r.Int31()) * float64(2e-9) + x := float64(r.rand.Int31()) * float64(2e-9) return int32(-1.6 * math.Exp(x)) } @@ -99,17 +108,17 @@ var usFreqs = []float32{ // Freq generates a frequency between 865.0 and 870.0 Mhz func (r *TTNRandom) Freq() float32 { - r.Lock() - defer r.Unlock() - return usFreqs[r.Intn(len(usFreqs))] + r.mu.Lock() + defer r.mu.Unlock() + return usFreqs[r.rand.Intn(len(usFreqs))] } // Datr generates Datr for instance: SF4BW125 func (r *TTNRandom) Datr() string { - r.Lock() - defer r.Unlock() + r.mu.Lock() + defer r.mu.Unlock() // Spread Factor from 12 to 7 - sf := 12 - r.Intn(7) + sf := 12 - r.rand.Intn(7) var bw int if sf == 6 { // DR6 -> SF7@250Khz @@ -123,29 +132,34 @@ func (r *TTNRandom) Datr() string { // Codr generates Codr for instance: 4/6 func (r *TTNRandom) Codr() string { - r.Lock() - defer r.Unlock() - d := r.Intn(4) + 5 + r.mu.Lock() + defer r.mu.Unlock() + d := r.rand.Intn(4) + 5 return fmt.Sprintf("4/%d", d) } // Lsnr generates LoRa SNR ratio in db. Tend towards generating good ratio with low noise func (r *TTNRandom) Lsnr() float32 { - r.Lock() - defer r.Unlock() - x := float64(r.Int31()) * float64(2e-9) + r.mu.Lock() + defer r.mu.Unlock() + x := float64(r.rand.Int31()) * float64(2e-9) return float32(math.Floor((-0.1*math.Exp(x)+5.5)*10) / 10) } // Bytes generates a random byte slice of length n func (r *TTNRandom) Bytes(n int) []byte { - r.Lock() - defer r.Unlock() + r.mu.Lock() + defer r.mu.Unlock() p := make([]byte, n) - r.Read(p) + r.rand.Read(p) return p } +// Intn returns random int with max n +func Intn(n int) int { + return global.Intn(n) +} + // String returns random string of length n func String(n int) string { return global.String(n) From e3f9b5e6e69a3d4f95550f92c9e24de31b95b43a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 15:04:48 +0200 Subject: [PATCH 1970/2266] Use TheThingsNetwork/go-utils cli handler --- cmd/root.go | 5 +- ttnctl/cmd/root.go | 7 +- utils/cli/handler/cli.go | 135 --------------------------------------- vendor/vendor.json | 6 ++ 4 files changed, 11 insertions(+), 142 deletions(-) delete mode 100644 utils/cli/handler/cli.go diff --git a/cmd/root.go b/cmd/root.go index 63dc8e34b..c604ef641 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,9 +13,7 @@ import ( "strings" "time" - "gopkg.in/redis.v3" - - cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" + cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" esHandler "github.com/TheThingsNetwork/ttn/utils/elasticsearch/handler" "github.com/apex/log" jsonHandler "github.com/apex/log/handlers/json" @@ -25,6 +23,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tj/go-elastic" + "gopkg.in/redis.v3" ) var cfgFile string diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index c82689667..51a979bd0 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -9,16 +9,15 @@ import ( "strings" "time" - "google.golang.org/grpc" - "google.golang.org/grpc/grpclog" - + cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/ttnctl/util" - cliHandler "github.com/TheThingsNetwork/ttn/utils/cli/handler" "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" ) var cfgFile string diff --git a/utils/cli/handler/cli.go b/utils/cli/handler/cli.go deleted file mode 100644 index 2db85807e..000000000 --- a/utils/cli/handler/cli.go +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "fmt" - "io" - "os" - "sort" - "strings" - "sync" - - "github.com/apex/log" -) - -// colors. -const ( - none = 0 - red = 31 - green = 32 - yellow = 33 - blue = 34 - gray = 90 -) - -// Colors mapping. -var Colors = [...]int{ - log.DebugLevel: gray, - log.InfoLevel: blue, - log.WarnLevel: yellow, - log.ErrorLevel: red, - log.FatalLevel: red, -} - -// Strings mapping. -var Strings = [...]string{ - log.DebugLevel: "DEBUG", - log.InfoLevel: "INFO", - log.WarnLevel: "WARN", - log.ErrorLevel: "ERROR", - log.FatalLevel: "FATAL", -} - -// field used for sorting. -type field struct { - Name string - Value interface{} -} - -// by sorts projects by call count. -type byName []field - -func (a byName) Len() int { return len(a) } -func (a byName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a byName) Less(i, j int) bool { return a[i].Name < a[j].Name } - -// Handler implementation. -type Handler struct { - mu sync.Mutex - Writer io.Writer - UseColor bool -} - -// colorTermSubstrings contains a list of substrings that indicate support for terminal colors -var colorTermSubstrings = []string{ - "color", - "xterm", -} - -// New handler. -func New(w io.Writer) *Handler { - var useColor bool - if os.Getenv("COLORTERM") != "" { - useColor = true - } - if term := os.Getenv("TERM"); term != "" { - for _, substring := range colorTermSubstrings { - if strings.Contains(term, substring) { - useColor = true - break - } - } - } - return &Handler{ - Writer: w, - UseColor: useColor, - } -} - -// HandleLog implements log.Handler. -func (h *Handler) HandleLog(e *log.Entry) error { - color := Colors[e.Level] - level := Strings[e.Level] - - var fields []field - - for k, v := range e.Fields { - fields = append(fields, field{k, v}) - } - - sort.Sort(byName(fields)) - - h.mu.Lock() - defer h.mu.Unlock() - - if h.UseColor { - fmt.Fprintf(h.Writer, "\033[%dm%6s\033[0m %-40s", color, level, e.Message) - } else { - fmt.Fprintf(h.Writer, "%6s %-40s", level, e.Message) - } - - for _, f := range fields { - var value interface{} - switch t := f.Value.(type) { - case []byte: // addresses and EUIs are []byte - value = fmt.Sprintf("%X", t) - case [21]byte: // bundle IDs [21]byte - value = fmt.Sprintf("%X-%X-%X-%X", t[0], t[1:9], t[9:17], t[17:]) - default: - value = f.Value - } - - if h.UseColor { - fmt.Fprintf(h.Writer, " \033[%dm%s\033[0m=%v", color, f.Name, value) - } else { - fmt.Fprintf(h.Writer, " %s=%v", f.Name, value) - } - - } - - fmt.Fprintln(h.Writer) - - return nil -} diff --git a/vendor/vendor.json b/vendor/vendor.json index 42bba4a84..16717ed73 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -62,6 +62,12 @@ "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", "revisionTime": "2016-10-14T09:56:50Z" }, + { + "checksumSHA1": "klPzXuUi+zvLz4ADQcPzlJsepVc=", + "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", + "revision": "ca9398a9d975d5c912f87b4869c3bc82091034df", + "revisionTime": "2016-10-18T13:04:05Z" + }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", "path": "github.com/apex/log", From f93fb725bcf180aa7c264400c624622aa65d64c9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 16:03:10 +0200 Subject: [PATCH 1971/2266] Move otaa and fcnt packages to utils --- core/broker/uplink.go | 2 +- core/handler/activation.go | 2 +- {core => utils}/fcnt/fcnt.go | 0 {core => utils}/fcnt/fcnt_test.go | 0 {core => utils}/otaa/session_keys.go | 0 {core => utils}/otaa/session_keys_test.go | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename {core => utils}/fcnt/fcnt.go (100%) rename {core => utils}/fcnt/fcnt_test.go (100%) rename {core => utils}/otaa/session_keys.go (100%) rename {core => utils}/otaa/session_keys_test.go (100%) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 8d1579cf0..4712a9ce8 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -15,9 +15,9 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core/fcnt" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/fcnt" "github.com/apex/log" "github.com/brocaar/lorawan" ) diff --git a/core/handler/activation.go b/core/handler/activation.go index 64d2a6073..1b8ccdbfb 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -10,10 +10,10 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/handler/device" - "github.com/TheThingsNetwork/ttn/core/otaa" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/otaa" "github.com/TheThingsNetwork/ttn/utils/random" "github.com/apex/log" "github.com/brocaar/lorawan" diff --git a/core/fcnt/fcnt.go b/utils/fcnt/fcnt.go similarity index 100% rename from core/fcnt/fcnt.go rename to utils/fcnt/fcnt.go diff --git a/core/fcnt/fcnt_test.go b/utils/fcnt/fcnt_test.go similarity index 100% rename from core/fcnt/fcnt_test.go rename to utils/fcnt/fcnt_test.go diff --git a/core/otaa/session_keys.go b/utils/otaa/session_keys.go similarity index 100% rename from core/otaa/session_keys.go rename to utils/otaa/session_keys.go diff --git a/core/otaa/session_keys_test.go b/utils/otaa/session_keys_test.go similarity index 100% rename from core/otaa/session_keys_test.go rename to utils/otaa/session_keys_test.go From 22ca9b061ba9fff7bcb53f3976aa64d1286a0ac5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 12:04:49 +0200 Subject: [PATCH 1972/2266] Implement RedisMapStore --- core/storage/{redis.go => conversions.go} | 0 .../{redis_test.go => conversions_test.go} | 0 core/storage/decoder.go | 152 +++++++++++ core/storage/decoder_test.go | 119 +++++++++ core/storage/encoder.go | 98 +++++++ core/storage/encoder_test.go | 99 +++++++ core/storage/redis_map_store.go | 248 ++++++++++++++++++ core/storage/redis_map_store_test.go | 179 +++++++++++++ core/storage/tags.go | 25 ++ core/storage/types.go | 26 ++ core/storage/util.go | 13 + vendor/vendor.json | 48 ++++ 12 files changed, 1007 insertions(+) rename core/storage/{redis.go => conversions.go} (100%) rename core/storage/{redis_test.go => conversions_test.go} (100%) create mode 100644 core/storage/decoder.go create mode 100644 core/storage/decoder_test.go create mode 100644 core/storage/encoder.go create mode 100644 core/storage/encoder_test.go create mode 100644 core/storage/redis_map_store.go create mode 100644 core/storage/redis_map_store_test.go create mode 100644 core/storage/tags.go create mode 100644 core/storage/types.go create mode 100644 core/storage/util.go diff --git a/core/storage/redis.go b/core/storage/conversions.go similarity index 100% rename from core/storage/redis.go rename to core/storage/conversions.go diff --git a/core/storage/redis_test.go b/core/storage/conversions_test.go similarity index 100% rename from core/storage/redis_test.go rename to core/storage/conversions_test.go diff --git a/core/storage/decoder.go b/core/storage/decoder.go new file mode 100644 index 000000000..401c199ed --- /dev/null +++ b/core/storage/decoder.go @@ -0,0 +1,152 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + "strconv" + + "github.com/fatih/structs" +) + +// StringStringMapDecoder is used to decode a map[string]string to a struct +type StringStringMapDecoder func(input map[string]string) (interface{}, error) + +func decodeToType(typ reflect.Kind, value string) interface{} { + switch typ { + case reflect.String: + return value + case reflect.Bool: + v, _ := strconv.ParseBool(value) + return v + case reflect.Int: + v, _ := strconv.ParseInt(value, 10, 64) + return int(v) + case reflect.Int8: + return int8(decodeToType(reflect.Int, value).(int)) + case reflect.Int16: + return int16(decodeToType(reflect.Int, value).(int)) + case reflect.Int32: + return int32(decodeToType(reflect.Int, value).(int)) + case reflect.Int64: + return int64(decodeToType(reflect.Int, value).(int)) + case reflect.Uint: + v, _ := strconv.ParseUint(value, 10, 64) + return uint(v) + case reflect.Uint8: + return uint8(decodeToType(reflect.Uint, value).(uint)) + case reflect.Uint16: + return uint16(decodeToType(reflect.Uint, value).(uint)) + case reflect.Uint32: + return uint32(decodeToType(reflect.Uint, value).(uint)) + case reflect.Uint64: + return uint64(decodeToType(reflect.Uint, value).(uint)) + case reflect.Float64: + v, _ := strconv.ParseFloat(value, 64) + return v + case reflect.Float32: + return float32(decodeToType(reflect.Float64, value).(float64)) + } + return nil +} + +func unmarshalToType(typ reflect.Type, value string) (val interface{}, err error) { + // If we get a pointer in, we'll return a pointer out + if typ.Kind() == reflect.Ptr { + val = reflect.New(typ.Elem()).Interface() + } else { + val = reflect.New(typ).Interface() + } + defer func() { + if err == nil && typ.Kind() != reflect.Ptr { + val = reflect.Indirect(reflect.ValueOf(val)).Interface() + } + }() + + // If we can just assign the value, return the value + if typ.AssignableTo(reflect.TypeOf(value)) { + return value, nil + } + + // Try Unmarshalers + if um, ok := val.(encoding.TextUnmarshaler); ok { + if err = um.UnmarshalText([]byte(value)); err == nil { + return val, nil + } + } + if um, ok := val.(json.Unmarshaler); ok { + if err = um.UnmarshalJSON([]byte(value)); err == nil { + return val, nil + } + } + + // Try conversion + if typ.ConvertibleTo(reflect.TypeOf(value)) { + return reflect.ValueOf(value).Convert(typ).Interface(), nil + } + + // Try JSON + if err = json.Unmarshal([]byte(value), val); err == nil { + return val, nil + } + + // Return error if we have one + if err != nil { + return nil, err + } + + return val, fmt.Errorf("No way to unmarshal \"%s\" to %s", value, typ.Name()) +} + +// buildDefaultStructDecoder is used by the RedisMapStore +func buildDefaultStructDecoder(base interface{}) StringStringMapDecoder { + return func(input map[string]string) (interface{}, error) { + baseType := reflect.TypeOf(base) + + output := reflect.New(baseType).Interface() + s := structs.New(output) + for _, field := range s.Fields() { + if !field.IsExported() { + continue + } + + tagName, _ := parseTag(field.Tag(tagName)) + if tagName == "" || tagName == "-" { + continue + } + if str, ok := input[tagName]; ok { + baseField, _ := baseType.FieldByName(field.Name()) + + var val interface{} + switch field.Kind() { + case reflect.Struct, reflect.Array, reflect.Interface, reflect.Slice, reflect.Ptr: + var err error + val, err = unmarshalToType(baseField.Type, str) + if err != nil { + return nil, err + } + default: + val = decodeToType(field.Kind(), str) + } + + if val == nil { + fmt.Printf("Could not decode %s to %s\n", str, field.Kind()) + continue + } + + if !baseField.Type.AssignableTo(reflect.TypeOf(val)) && baseField.Type.ConvertibleTo(reflect.TypeOf(val)) { + val = reflect.ValueOf(val).Convert(baseField.Type).Interface() + } + + if err := field.Set(val); err != nil { + return nil, err + } + } + } + return output, nil + } +} diff --git a/core/storage/decoder_test.go b/core/storage/decoder_test.go new file mode 100644 index 000000000..9957da3f0 --- /dev/null +++ b/core/storage/decoder_test.go @@ -0,0 +1,119 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding/hex" + "reflect" + "strings" + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestDecodeToType(t *testing.T) { + a := New(t) + a.So(decodeToType(reflect.String, "abc"), ShouldEqual, "abc") + a.So(decodeToType(reflect.Bool, "true"), ShouldEqual, true) + a.So(decodeToType(reflect.Bool, "false"), ShouldEqual, false) + a.So(decodeToType(reflect.Int, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int8, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int16, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int32, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Int64, "-10"), ShouldEqual, -10) + a.So(decodeToType(reflect.Uint, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint8, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint16, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint32, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Uint64, "10"), ShouldEqual, 10) + a.So(decodeToType(reflect.Float64, "12.34"), ShouldEqual, 12.34) + a.So(decodeToType(reflect.Float32, "12.34"), ShouldEqual, 12.34) + a.So(decodeToType(reflect.Struct, "blabla"), ShouldBeNil) +} + +type noIdea struct { + something complex128 +} + +type strtp string + +type testTextUnmarshaler struct { + Data string +} + +func (um *testTextUnmarshaler) UnmarshalText(text []byte) error { + um.Data = string(text) + return nil +} + +type testCustomType [2]byte + +type testJSONUnmarshaler struct { + Customs []testCustomType +} + +type jsonStruct struct { + String string `json:"string"` +} + +func (jum *testJSONUnmarshaler) UnmarshalJSON(text []byte) error { + jum.Customs = []testCustomType{} + txt := strings.Trim(string(text), "[]") + txtlist := strings.Split(txt, ",") + for _, txtitem := range txtlist { + txtitem = strings.Trim(txtitem, `"`) + b, _ := hex.DecodeString(txtitem) + var o testCustomType + copy(o[:], b[:]) + jum.Customs = append(jum.Customs, o) + } + return nil +} + +func TestUnmarshalToType(t *testing.T) { + a := New(t) + + var str string + strOut, err := unmarshalToType(reflect.TypeOf(str), "data") + a.So(err, ShouldBeNil) + a.So(strOut, ShouldEqual, "data") + + var strtp strtp + strtpOut, err := unmarshalToType(reflect.TypeOf(strtp), "data") + a.So(err, ShouldBeNil) + a.So(strtpOut, ShouldEqual, "data") + + var um testTextUnmarshaler + umOut, err := unmarshalToType(reflect.TypeOf(um), "data") + a.So(err, ShouldBeNil) + a.So(umOut.(testTextUnmarshaler), ShouldResemble, testTextUnmarshaler{"data"}) + + var jum testJSONUnmarshaler + jumOut, err := unmarshalToType(reflect.TypeOf(jum), `["abcd","1234","def0"]`) + a.So(err, ShouldBeNil) + a.So(jumOut.(testJSONUnmarshaler), ShouldResemble, testJSONUnmarshaler{[]testCustomType{ + testCustomType{0xab, 0xcd}, + testCustomType{0x12, 0x34}, + testCustomType{0xde, 0xf0}, + }}) + + var js jsonStruct + jsOut, err := unmarshalToType(reflect.TypeOf(js), `{"string": "String"}`) + a.So(err, ShouldBeNil) + a.So(jsOut.(jsonStruct), ShouldResemble, jsonStruct{"String"}) + + _, err = unmarshalToType(reflect.TypeOf(js), `this is no json`) + a.So(err, ShouldNotBeNil) + + var eui types.DevEUI + euiOut, err := unmarshalToType(reflect.TypeOf(eui), "0102abcd0304abcd") + a.So(err, ShouldBeNil) + a.So(euiOut.(types.DevEUI), ShouldEqual, types.DevEUI{0x01, 0x02, 0xab, 0xcd, 0x03, 0x04, 0xab, 0xcd}) + + var euiPtr *types.DevEUI + euiPtrOut, err := unmarshalToType(reflect.TypeOf(euiPtr), "0102abcd0304abcd") + a.So(err, ShouldBeNil) + a.So(euiPtrOut.(*types.DevEUI), ShouldResemble, &types.DevEUI{0x01, 0x02, 0xab, 0xcd, 0x03, 0x04, 0xab, 0xcd}) +} diff --git a/core/storage/encoder.go b/core/storage/encoder.go new file mode 100644 index 000000000..aa0f9f75e --- /dev/null +++ b/core/storage/encoder.go @@ -0,0 +1,98 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "encoding" + "encoding/json" + "fmt" + "reflect" + + "github.com/fatih/structs" +) + +// StringStringMapEncoder encodes the given properties of the input to a map[string]string for storage in Redis +type StringStringMapEncoder func(input interface{}, properties ...string) (map[string]string, error) + +type isZeroer interface { + IsZero() bool +} + +type isEmptier interface { + IsEmpty() bool +} + +func defaultStructEncoder(input interface{}, properties ...string) (map[string]string, error) { + vmap := make(map[string]string) + s := structs.New(input) + s.TagName = tagName + if len(properties) == 0 { + properties = s.Names() + } + for _, field := range s.Fields() { + if !field.IsExported() { + continue + } + + if !stringInSlice(field.Name(), properties) { + continue + } + + tagName, opts := parseTag(field.Tag(tagName)) + if tagName == "" || tagName == "-" { + continue + } + + val := field.Value() + + if opts.Has("omitempty") { + if field.IsZero() { + continue + } + if z, ok := val.(isZeroer); ok && z.IsZero() { + continue + } + if z, ok := val.(isEmptier); ok && z.IsEmpty() { + continue + } + } + + if v, ok := val.(string); ok { + vmap[tagName] = v + continue + } + + if !field.IsZero() { + if m, ok := val.(encoding.TextMarshaler); ok { + txt, err := m.MarshalText() + if err != nil { + return nil, err + } + vmap[tagName] = string(txt) + continue + } + if m, ok := val.(json.Marshaler); ok { + txt, err := m.MarshalJSON() + if err != nil { + return nil, err + } + vmap[tagName] = string(txt) + continue + } + } + + if field.Kind() == reflect.String { + vmap[tagName] = fmt.Sprint(val) + continue + } + + if txt, err := json.Marshal(val); err == nil { + vmap[tagName] = string(txt) + continue + } + + vmap[tagName] = fmt.Sprintf("%v", val) + } + return vmap, nil +} diff --git a/core/storage/encoder_test.go b/core/storage/encoder_test.go new file mode 100644 index 000000000..acfe0b052 --- /dev/null +++ b/core/storage/encoder_test.go @@ -0,0 +1,99 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type strtps []strtp + +type jm struct { + txt string +} + +func (j jm) MarshalJSON() ([]byte, error) { + return []byte("{" + j.txt + "}"), nil +} + +type testStruct struct { + unexported string `redis:"unexported"` + NoRedis string `` + DisRedis string `redis:"-"` + String string `redis:"string"` + Strings []string `redis:"strings"` + EmptyString string `redis:"empty_string,omitempty"` + EmptyStrings []string `redis:"empty_strings,omitempty"` + AppEUI types.AppEUI `redis:"app_eui"` + AppEUIPtr *types.AppEUI `redis:"app_eui_ptr"` + EmptyAppEUI types.AppEUI `redis:"empty_app_eui,omitempty"` + EmptyAppEUIPtr *types.AppEUI `redis:"empty_app_eui_ptr,omitempty"` + Time time.Time `redis:"time"` + TimePtr *time.Time `redis:"time_ptr"` + EmptyTime time.Time `redis:"empty_time,omitempty"` + EmptyTimePtr *time.Time `redis:"empty_time_ptr,omitempty"` + STime Time `redis:"stime"` + STimePtr *Time `redis:"stime_ptr"` + EmptySTime Time `redis:"empty_stime,omitempty"` + EmptySTimePtr *Time `redis:"empty_stime_ptr,omitempty"` + Str strtp `redis:"str"` + Strs []strtp `redis:"strs"` + JM jm `redis:"jm"` + JMs []jm `redis:"jms"` + Int int `redis:"int"` + Uint uint `redis:"uint"` +} + +func TestDefaultStructEncoder(t *testing.T) { + a := New(t) + + var emptyAppEUI types.AppEUI + var appEUI = types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} + var now = time.Now() + var emptyTime time.Time + var stime = Time{now} + var emptySTime Time + + out, err := defaultStructEncoder(&testStruct{ + unexported: "noop", + NoRedis: "noop", + DisRedis: "noop", + String: "string", + Strings: []string{"string1", "string2"}, + AppEUI: appEUI, + AppEUIPtr: &appEUI, + EmptyAppEUIPtr: &emptyAppEUI, + Time: now, + TimePtr: &now, + STime: stime, + STimePtr: &stime, + EmptySTimePtr: &emptySTime, + EmptyTimePtr: &emptyTime, + JM: jm{"cool"}, + }) + a.So(err, ShouldBeNil) + a.So(out, ShouldNotContainKey, "unexported") + a.So(out, ShouldNotContainKey, "empty_string") + a.So(out, ShouldNotContainKey, "empty_strings") + a.So(out, ShouldNotContainKey, "empty_app_eui") + a.So(out, ShouldNotContainKey, "empty_app_eui_ptr") + a.So(out, ShouldNotContainKey, "empty_time") + a.So(out, ShouldNotContainKey, "empty_time_ptr") + a.So(out, ShouldNotContainKey, "empty_stime") + a.So(out, ShouldNotContainKey, "empty_stime_ptr") + a.So(out["string"], ShouldEqual, "string") + a.So(out["strings"], ShouldEqual, `["string1","string2"]`) + a.So(out["jm"], ShouldEqual, "{cool}") + + out, err = defaultStructEncoder(&testStruct{ + String: "noop", + Strings: []string{"string1", "string2"}, + }, "String") + a.So(err, ShouldBeNil) + a.So(out, ShouldNotContainKey, "strings") +} diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go new file mode 100644 index 000000000..fa4bc48ad --- /dev/null +++ b/core/storage/redis_map_store.go @@ -0,0 +1,248 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + redis "gopkg.in/redis.v4" +) + +// ListOptions are options for all list commands +type ListOptions struct { + Limit int + Offset int +} + +// RedisMapStore stores structs as HMaps in Redis +type RedisMapStore struct { + prefix string + client *redis.Client + encoder StringStringMapEncoder + decoder StringStringMapDecoder +} + +// NewRedisMapStore returns a new RedisMapStore that talks to the given Redis client and respects the given prefix +func NewRedisMapStore(client *redis.Client, prefix string) *RedisMapStore { + if !strings.HasSuffix(prefix, ":") { + prefix += ":" + } + return &RedisMapStore{ + client: client, + prefix: prefix, + } +} + +// SetBase sets the base struct for automatically encoding and decoding to and from Redis format +func (s *RedisMapStore) SetBase(base interface{}) { + s.SetEncoder(defaultStructEncoder) + s.SetDecoder(buildDefaultStructDecoder(base)) +} + +// SetEncoder sets the encoder to convert structs to Redis format +func (s *RedisMapStore) SetEncoder(encoder StringStringMapEncoder) { + s.encoder = encoder +} + +// SetDecoder sets the decoder to convert structs from Redis format +func (s *RedisMapStore) SetDecoder(decoder StringStringMapDecoder) { + s.decoder = decoder +} + +// GetAll returns all results for the given keys, prepending the prefix to the keys if necessary +func (s *RedisMapStore) GetAll(keys []string, options *ListOptions) ([]interface{}, error) { + for i, key := range keys { + if !strings.HasPrefix(key, s.prefix) { + keys[i] = s.prefix + key + } + } + + sort.Strings(keys) + + var start int + var end = len(keys) + if options != nil { + if options.Offset >= len(keys) { + return []interface{}{}, nil + } + start = options.Offset + if options.Limit > 0 { + if options.Offset+options.Limit > len(keys) { + options.Limit = len(keys) - options.Offset + } + end = options.Offset + options.Limit + } + } + selectedKeys := keys[start:end] + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringStringMapCmd) + for _, key := range selectedKeys { + cmds[key] = pipe.HGetAll(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + results := make([]interface{}, 0, len(selectedKeys)) + for _, key := range selectedKeys { + if result, err := cmds[key].Result(); err == nil { + if result, err := s.decoder(result); err == nil { + results = append(results, result) + } + } + } + + return results, nil +} + +// List all results matching the selector, prepending the prefix to the selector if necessary +func (s *RedisMapStore) List(selector string, options *ListOptions) ([]interface{}, error) { + if selector == "" { + selector = "*" + } + if !strings.HasPrefix(selector, s.prefix) { + selector = s.prefix + selector + } + keys, err := s.client.Keys(selector).Result() + if err != nil { + return nil, err + } + return s.GetAll(keys, options) +} + +// Get one result, prepending the prefix to the key if necessary +func (s *RedisMapStore) Get(key string) (interface{}, error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + result, err := s.client.HGetAll(key).Result() + if err == redis.Nil || len(result) == 0 { + return nil, errors.NewErrNotFound(key) + } + if err != nil { + return nil, err + } + i, err := s.decoder(result) + if err != nil { + return nil, err + } + return i, nil +} + +// Create a new record, prepending the prefix to the key if necessary, optionally setting only the given properties +func (s *RedisMapStore) Create(key string, value interface{}, properties ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + vmap, err := s.encoder(value, properties...) + if err != nil { + return err + } + if len(vmap) == 0 { + return nil + } + + err = s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if exists { + return errors.NewErrAlreadyExists(key) + } + _, err = tx.MultiExec(func() error { + tx.HMSet(key, vmap) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Update an existing record, prepending the prefix to the key if necessary, optionally setting only the given properties +func (s *RedisMapStore) Update(key string, value interface{}, properties ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + vmap, err := s.encoder(value, properties...) + if err != nil { + return err + } + if len(vmap) == 0 { + return nil + } + + err = s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.MultiExec(func() error { + tx.HMSet(key, vmap) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Delete an existing record, prepending the prefix to the key if necessary +func (s *RedisMapStore) Delete(key string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.MultiExec(func() error { + tx.Del(key) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go new file mode 100644 index 000000000..8f725be71 --- /dev/null +++ b/core/storage/redis_map_store_test.go @@ -0,0 +1,179 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "os" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/smartystreets/assertions" + redis "gopkg.in/redis.v4" +) + +func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: 1, // use default DB + }) +} + +type testRedisStruct struct { + Name string `redis:"name,omitempty"` + UpdatedAt Time `redis:"updated_at,omitempty"` +} + +func TestRedisMapStore(t *testing.T) { + a := New(t) + c := getRedisClient() + s := NewRedisMapStore(c, "test-redis-map-store") + a.So(s, ShouldNotBeNil) + + now := time.Now() + testRedisStructVal := testRedisStruct{ + Name: "My Name", + UpdatedAt: Time{now}, + } + + s.SetBase(testRedisStructVal) + + // Get non-existing + { + res, err := s.Get("test") + a.So(err, ShouldNotBeNil) + a.So(errors.GetErrType(err), ShouldEqual, errors.NotFound) + a.So(res, ShouldBeNil) + } + + // Not Create + { + err := s.Create("test", &testRedisStruct{}) + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-map-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeFalse) + } + + // Create New + { + defer func() { + c.Del("test-redis-map-store:test").Result() + }() + err := s.Create("test", &testRedisStructVal) + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-map-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeTrue) + } + + // Create Existing + { + err := s.Create("test", &testRedisStructVal) + a.So(err, ShouldNotBeNil) + } + + // Get + { + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + a.So(res.(*testRedisStruct).Name, ShouldEqual, "My Name") + a.So(res.(*testRedisStruct).UpdatedAt.Nanosecond(), ShouldEqual, now.Nanosecond()) + } + + for i := 1; i < 10; i++ { + // Create Extra + { + name := fmt.Sprintf("test-%d", i) + defer func() { + c.Del("test-redis-map-store:" + name).Result() + }() + s.Create(name, &testRedisStruct{ + Name: name, + }) + } + } + + // GetAll + { + res, err := s.GetAll([]string{"test"}, nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "My Name") + } + + // List + { + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 10) + a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "My Name") + } + + // List With Options + { + res, _ := s.List("test-*", &ListOptions{Limit: 2}) + a.So(res, ShouldHaveLength, 2) + a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "test-1") + a.So(res[1].(*testRedisStruct).Name, ShouldEqual, "test-2") + + res, _ = s.List("test-*", &ListOptions{Limit: 20}) + a.So(res, ShouldHaveLength, 9) + + res, _ = s.List("test-*", &ListOptions{Offset: 20}) + a.So(res, ShouldHaveLength, 0) + + res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) + a.So(res, ShouldHaveLength, 2) + a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "test-2") + a.So(res[1].(*testRedisStruct).Name, ShouldEqual, "test-3") + + res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) + a.So(res, ShouldHaveLength, 8) + } + + // Update Non-Existing + { + err := s.Update("not-there", &testRedisStructVal) + a.So(err, ShouldNotBeNil) + } + + // Update Existing + { + err := s.Update("test", &testRedisStruct{ + Name: "New Name", + }, "Name") + a.So(err, ShouldBeNil) + + name, err := c.HGet("test-redis-map-store:test", "name").Result() + a.So(err, ShouldBeNil) + a.So(name, ShouldEqual, "New Name") + } + + // Delete Non-Existing + { + err := s.Delete("not-there") + a.So(err, ShouldNotBeNil) + } + + // Delete Existing + { + err := s.Delete("test") + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-map-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeFalse) + } + +} diff --git a/core/storage/tags.go b/core/storage/tags.go new file mode 100644 index 000000000..e7adbdff4 --- /dev/null +++ b/core/storage/tags.go @@ -0,0 +1,25 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import "strings" + +var tagName = "redis" + +type tagOptions []string + +// Has returns true if opt is one of the options +func (t tagOptions) Has(opt string) bool { + for _, opt := range t { + if opt == opt { + return true + } + } + return false +} + +func parseTag(tag string) (string, tagOptions) { + res := strings.Split(tag, ",") + return res[0], res[1:] +} diff --git a/core/storage/types.go b/core/storage/types.go new file mode 100644 index 000000000..060078b90 --- /dev/null +++ b/core/storage/types.go @@ -0,0 +1,26 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import "time" + +// Time is a wrapper around time.Time that marshals and unmarshals to the time.RFC3339Nano format +type Time struct { + time.Time +} + +// MarshalText implements the encoding.TextMarshaler interface +func (t Time) MarshalText() ([]byte, error) { + return []byte(t.Time.UTC().Format(time.RFC3339Nano)), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (t *Time) UnmarshalText(in []byte) error { + parsed, err := time.Parse(time.RFC3339Nano, string(in)) + if err != nil { + return err + } + t.Time = parsed + return nil +} diff --git a/core/storage/util.go b/core/storage/util.go new file mode 100644 index 000000000..0ef949147 --- /dev/null +++ b/core/storage/util.go @@ -0,0 +1,13 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +func stringInSlice(search string, slice []string) bool { + for _, i := range slice { + if i == search { + return true + } + } + return false +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 16717ed73..e9c5360e4 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -134,6 +134,12 @@ "revision": "5d3aa4073fcbdaf5fcacf2c3f033ca6448e0bd85", "revisionTime": "2016-10-04T07:53:38Z" }, + { + "checksumSHA1": "KCWVxG+J8SxHGlGiUghe0KBGsa8=", + "path": "github.com/fatih/structs", + "revision": "dc3312cb1a4513a366c4c9e622ad55c32df12ed3", + "revisionTime": "2016-08-07T23:55:29Z" + }, { "checksumSHA1": "e2o/P8ZZ8Iz+um6/nyLUEg7S2H8=", "path": "github.com/fsnotify/fsnotify", @@ -776,6 +782,48 @@ "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", "revisionTime": "2016-06-27T09:56:34Z" }, + { + "checksumSHA1": "CKEuRia+Qsk0UcWie9f/TxW6zpU=", + "path": "gopkg.in/redis.v4", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, + { + "checksumSHA1": "4HbfnrL1KOz/6nsdBAPeL0oUXzc=", + "path": "gopkg.in/redis.v4/internal", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, + { + "checksumSHA1": "l+1jiOGUyjHP0u0mB+KiT/UHVcY=", + "path": "gopkg.in/redis.v4/internal/consistenthash", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, + { + "checksumSHA1": "XxoAGEMN5ZCJJ3FeDMSTVhvscHg=", + "path": "gopkg.in/redis.v4/internal/errors", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, + { + "checksumSHA1": "6XSNzBg5bzd2TjEfC/q41tPT/Zg=", + "path": "gopkg.in/redis.v4/internal/hashtag", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, + { + "checksumSHA1": "fYkMv2BMIeHKM1VirUzJ7A/jG7w=", + "path": "gopkg.in/redis.v4/internal/pool", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, + { + "checksumSHA1": "BGzuM8ZpMCmTKWnb/3CtrV4OwDU=", + "path": "gopkg.in/redis.v4/internal/proto", + "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", + "revisionTime": "2016-10-02T13:00:31Z" + }, { "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", "path": "gopkg.in/sourcemap.v1", From 67fc17765089bb27cd8921ebd50edf1844253df5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 13:09:02 +0200 Subject: [PATCH 1973/2266] Implement RedisKVStore --- core/storage/redis_kv_store.go | 189 +++++++++++++++++++++++++++ core/storage/redis_kv_store_test.go | 134 +++++++++++++++++++ core/storage/redis_map_store.go | 22 +--- core/storage/redis_map_store_test.go | 14 -- core/storage/util.go | 24 ++++ core/storage/util_test.go | 23 ++++ 6 files changed, 371 insertions(+), 35 deletions(-) create mode 100644 core/storage/redis_kv_store.go create mode 100644 core/storage/redis_kv_store_test.go create mode 100644 core/storage/util_test.go diff --git a/core/storage/redis_kv_store.go b/core/storage/redis_kv_store.go new file mode 100644 index 000000000..aec51a802 --- /dev/null +++ b/core/storage/redis_kv_store.go @@ -0,0 +1,189 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + "gopkg.in/redis.v4" +) + +// RedisKVStore stores arbitrary data in Redis +type RedisKVStore struct { + prefix string + client *redis.Client +} + +// NewRedisKVStore creates a new RedisKVStore +func NewRedisKVStore(client *redis.Client, prefix string) *RedisKVStore { + if !strings.HasSuffix(prefix, ":") { + prefix += ":" + } + return &RedisKVStore{ + client: client, + prefix: prefix, + } +} + +// GetAll returns all results for the given keys, prepending the prefix to the keys if necessary +func (s *RedisKVStore) GetAll(keys []string, options *ListOptions) (map[string]string, error) { + for i, key := range keys { + if !strings.HasPrefix(key, s.prefix) { + keys[i] = s.prefix + key + } + } + + sort.Strings(keys) + + selectedKeys := selectKeys(keys, options) + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringCmd) + for _, key := range selectedKeys { + cmds[key] = pipe.Get(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + data := make(map[string]string) + for key, cmd := range cmds { + res, err := cmd.Result() + if err == nil { + data[strings.TrimPrefix(key, s.prefix)] = res + } + } + + return data, nil +} + +// List all results matching the selector, prepending the prefix to the selector if necessary +func (s *RedisKVStore) List(selector string, options *ListOptions) (map[string]string, error) { + if selector == "" { + selector = "*" + } + if !strings.HasPrefix(selector, s.prefix) { + selector = s.prefix + selector + } + keys, err := s.client.Keys(selector).Result() + if err != nil { + return nil, err + } + return s.GetAll(keys, options) +} + +// Get one result, prepending the prefix to the key if necessary +func (s *RedisKVStore) Get(key string) (string, error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + result, err := s.client.Get(key).Result() + if err == redis.Nil || result == "" { + return "", errors.NewErrNotFound(key) + } + if err != nil { + return "", err + } + return result, nil +} + +// Create a new record, prepending the prefix to the key if necessary +func (s *RedisKVStore) Create(key string, value string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if exists { + return errors.NewErrAlreadyExists(key) + } + _, err = tx.MultiExec(func() error { + tx.Set(key, value, 0) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Update an existing record, prepending the prefix to the key if necessary +func (s *RedisKVStore) Update(key string, value string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.MultiExec(func() error { + tx.Set(key, value, 0) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} + +// Delete an existing record, prepending the prefix to the key if necessary +func (s *RedisKVStore) Delete(key string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + + err := s.client.Watch(func(tx *redis.Tx) error { + exists, err := tx.Exists(key).Result() + if err != nil { + return err + } + if !exists { + return errors.NewErrNotFound(key) + } + _, err = tx.MultiExec(func() error { + tx.Del(key) + return nil + }) + if err != nil { + return err + } + return nil + }, key) + if err != nil { + return err + } + + return nil +} diff --git a/core/storage/redis_kv_store_test.go b/core/storage/redis_kv_store_test.go new file mode 100644 index 000000000..58ce18088 --- /dev/null +++ b/core/storage/redis_kv_store_test.go @@ -0,0 +1,134 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/smartystreets/assertions" +) + +func TestRedisKVStore(t *testing.T) { + a := New(t) + c := getRedisClient() + s := NewRedisKVStore(c, "test-redis-kv-store") + a.So(s, ShouldNotBeNil) + + // Get non-existing + { + _, err := s.Get("test") + a.So(err, ShouldNotBeNil) + a.So(errors.GetErrType(err), ShouldEqual, errors.NotFound) + } + + // Create New + { + defer func() { + c.Del("test-redis-kv-store:test").Result() + }() + err := s.Create("test", "value") + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-kv-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeTrue) + } + + // Create Existing + { + err := s.Create("test", "value") + a.So(err, ShouldNotBeNil) + } + + // Get + { + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldEqual, "value") + } + + for i := 1; i < 10; i++ { + // Create Extra + { + name := fmt.Sprintf("test-%d", i) + defer func() { + c.Del("test-redis-kv-store:" + name).Result() + }() + s.Create(name, name) + } + } + + // GetAll + { + res, err := s.GetAll([]string{"test"}, nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + a.So(res["test"], ShouldEqual, "value") + } + + // List + { + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 10) + a.So(res["test"], ShouldEqual, "value") + } + + // List With Options + { + res, _ := s.List("test-*", &ListOptions{Limit: 2}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-1"], ShouldEqual, "test-1") + a.So(res["test-2"], ShouldEqual, "test-2") + + res, _ = s.List("test-*", &ListOptions{Limit: 20}) + a.So(res, ShouldHaveLength, 9) + + res, _ = s.List("test-*", &ListOptions{Offset: 20}) + a.So(res, ShouldHaveLength, 0) + + res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-2"], ShouldEqual, "test-2") + a.So(res["test-3"], ShouldEqual, "test-3") + + res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) + a.So(res, ShouldHaveLength, 8) + } + + // Update Non-Existing + { + err := s.Update("not-there", "value") + a.So(err, ShouldNotBeNil) + } + + // Update Existing + { + err := s.Update("test", "updated") + a.So(err, ShouldBeNil) + + name, err := c.Get("test-redis-kv-store:test").Result() + a.So(err, ShouldBeNil) + a.So(name, ShouldEqual, "updated") + } + + // Delete Non-Existing + { + err := s.Delete("not-there") + a.So(err, ShouldNotBeNil) + } + + // Delete Existing + { + err := s.Delete("test") + a.So(err, ShouldBeNil) + + exists, err := c.Exists("test-redis-kv-store:test").Result() + a.So(err, ShouldBeNil) + a.So(exists, ShouldBeFalse) + } + +} diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index fa4bc48ad..51e3bfae4 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -12,12 +12,6 @@ import ( redis "gopkg.in/redis.v4" ) -// ListOptions are options for all list commands -type ListOptions struct { - Limit int - Offset int -} - // RedisMapStore stores structs as HMaps in Redis type RedisMapStore struct { prefix string @@ -63,21 +57,7 @@ func (s *RedisMapStore) GetAll(keys []string, options *ListOptions) ([]interface sort.Strings(keys) - var start int - var end = len(keys) - if options != nil { - if options.Offset >= len(keys) { - return []interface{}{}, nil - } - start = options.Offset - if options.Limit > 0 { - if options.Offset+options.Limit > len(keys) { - options.Limit = len(keys) - options.Offset - } - end = options.Offset + options.Limit - } - } - selectedKeys := keys[start:end] + selectedKeys := selectKeys(keys, options) pipe := s.client.Pipeline() defer pipe.Close() diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go index 8f725be71..85c9de584 100644 --- a/core/storage/redis_map_store_test.go +++ b/core/storage/redis_map_store_test.go @@ -5,27 +5,13 @@ package storage import ( "fmt" - "os" "testing" "time" "github.com/TheThingsNetwork/ttn/utils/errors" . "github.com/smartystreets/assertions" - redis "gopkg.in/redis.v4" ) -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB - }) -} - type testRedisStruct struct { Name string `redis:"name,omitempty"` UpdatedAt Time `redis:"updated_at,omitempty"` diff --git a/core/storage/util.go b/core/storage/util.go index 0ef949147..e450d6425 100644 --- a/core/storage/util.go +++ b/core/storage/util.go @@ -3,6 +3,30 @@ package storage +// ListOptions are options for all list commands +type ListOptions struct { + Limit int + Offset int +} + +func selectKeys(keys []string, options *ListOptions) []string { + var start int + var end = len(keys) + if options != nil { + if options.Offset >= len(keys) { + return []string{} + } + start = options.Offset + if options.Limit > 0 { + if options.Offset+options.Limit > len(keys) { + options.Limit = len(keys) - options.Offset + } + end = options.Offset + options.Limit + } + } + return keys[start:end] +} + func stringInSlice(search string, slice []string) bool { for _, i := range slice { if i == search { diff --git a/core/storage/util_test.go b/core/storage/util_test.go new file mode 100644 index 000000000..4dca9f813 --- /dev/null +++ b/core/storage/util_test.go @@ -0,0 +1,23 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "os" + + redis "gopkg.in/redis.v4" +) + +func getRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: 1, // use default DB + }) +} From 014fded47155938fe6876f89086f45718373005a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 13:31:24 +0200 Subject: [PATCH 1974/2266] Implement RedisSetStore --- core/storage/redis_set_store.go | 134 +++++++++++++++++++++++++++ core/storage/redis_set_store_test.go | 123 ++++++++++++++++++++++++ 2 files changed, 257 insertions(+) create mode 100644 core/storage/redis_set_store.go create mode 100644 core/storage/redis_set_store_test.go diff --git a/core/storage/redis_set_store.go b/core/storage/redis_set_store.go new file mode 100644 index 000000000..de74a82f1 --- /dev/null +++ b/core/storage/redis_set_store.go @@ -0,0 +1,134 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "sort" + "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" + + "gopkg.in/redis.v4" +) + +// RedisSetStore stores sets in Redis +type RedisSetStore struct { + prefix string + client *redis.Client +} + +// NewRedisSetStore creates a new RedisSetStore +func NewRedisSetStore(client *redis.Client, prefix string) *RedisSetStore { + if !strings.HasSuffix(prefix, ":") { + prefix += ":" + } + return &RedisSetStore{ + client: client, + prefix: prefix, + } +} + +// GetAll returns all results for the given keys, prepending the prefix to the keys if necessary +func (s *RedisSetStore) GetAll(keys []string, options *ListOptions) (map[string][]string, error) { + for i, key := range keys { + if !strings.HasPrefix(key, s.prefix) { + keys[i] = s.prefix + key + } + } + + sort.Strings(keys) + + selectedKeys := selectKeys(keys, options) + + pipe := s.client.Pipeline() + defer pipe.Close() + + // Add all commands to pipeline + cmds := make(map[string]*redis.StringSliceCmd) + for _, key := range selectedKeys { + cmds[key] = pipe.SMembers(key) + } + + // Execute pipeline + _, err := pipe.Exec() + if err != nil { + return nil, err + } + + // Get all results from pipeline + data := make(map[string][]string) + for key, cmd := range cmds { + res, err := cmd.Result() + if err == nil { + sort.Strings(res) + data[strings.TrimPrefix(key, s.prefix)] = res + } + } + + return data, nil +} + +// List all results matching the selector, prepending the prefix to the selector if necessary +func (s *RedisSetStore) List(selector string, options *ListOptions) (map[string][]string, error) { + if selector == "" { + selector = "*" + } + if !strings.HasPrefix(selector, s.prefix) { + selector = s.prefix + selector + } + keys, err := s.client.Keys(selector).Result() + if err != nil { + return nil, err + } + return s.GetAll(keys, options) +} + +// Get one result, prepending the prefix to the key if necessary +func (s *RedisSetStore) Get(key string) (res []string, err error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + res, err = s.client.SMembers(key).Result() + if err == redis.Nil || len(res) == 0 { + return res, errors.NewErrNotFound(key) + } + sort.Strings(res) + return res, err +} + +// Contains returns wheter the set contains a given value, prepending the prefix to the key if necessary +func (s *RedisSetStore) Contains(key string, value string) (res bool, err error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + res, err = s.client.SIsMember(key, value).Result() + if err == redis.Nil { + return res, errors.NewErrNotFound(key) + } + return res, err +} + +// Add one or more values to the set, prepending the prefix to the key if necessary +func (s *RedisSetStore) Add(key string, values ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + var valuesI []interface{} + for _, v := range values { + valuesI = append(valuesI, v) + } + return s.client.SAdd(key, valuesI...).Err() +} + +// Remove one or more values from the set, prepending the prefix to the key if necessary +func (s *RedisSetStore) Remove(key string, values ...string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + var valuesI []interface{} + for _, v := range values { + valuesI = append(valuesI, v) + } + return s.client.SRem(key, valuesI...).Err() +} diff --git a/core/storage/redis_set_store_test.go b/core/storage/redis_set_store_test.go new file mode 100644 index 000000000..d71be81d2 --- /dev/null +++ b/core/storage/redis_set_store_test.go @@ -0,0 +1,123 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package storage + +import ( + "fmt" + "testing" + + "github.com/TheThingsNetwork/ttn/utils/errors" + . "github.com/smartystreets/assertions" +) + +func TestRedisSetStore(t *testing.T) { + a := New(t) + c := getRedisClient() + s := NewRedisSetStore(c, "test-redis-set-store") + a.So(s, ShouldNotBeNil) + + // Get non-existing + { + _, err := s.Get("test") + a.So(err, ShouldNotBeNil) + a.So(errors.GetErrType(err), ShouldEqual, errors.NotFound) + + contains, err := s.Contains("test", "value") + a.So(err, ShouldBeNil) + a.So(contains, ShouldBeFalse) + } + + defer func() { + c.Del("test-redis-set-store:test").Result() + }() + + // Add + { + err := s.Add("test", "value") + a.So(err, ShouldBeNil) + + contains, err := s.Contains("test", "value") + a.So(err, ShouldBeNil) + a.So(contains, ShouldBeTrue) + + contains, err = s.Contains("test", "not-there") + a.So(err, ShouldBeNil) + a.So(contains, ShouldBeFalse) + } + + // Get + { + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + } + + // Get More + { + s.Add("test", "othervalue") + + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + } + + // Remove + { + s.Remove("test", "othervalue") + + res, err := s.Get("test") + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + } + + for i := 1; i < 10; i++ { + // Create Extra + { + name := fmt.Sprintf("test-%d", i) + defer func() { + c.Del("test-redis-set-store:" + name).Result() + }() + s.Add(name, name) + } + } + + // GetAll + { + res, err := s.GetAll([]string{"test"}, nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + a.So(res["test"], ShouldHaveLength, 1) + } + + // List + { + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 10) + a.So(res["test"], ShouldHaveLength, 1) + } + + // List With Options + { + res, _ := s.List("test-*", &ListOptions{Limit: 2}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-1"], ShouldResemble, []string{"test-1"}) + a.So(res["test-2"], ShouldResemble, []string{"test-2"}) + + res, _ = s.List("test-*", &ListOptions{Limit: 20}) + a.So(res, ShouldHaveLength, 9) + + res, _ = s.List("test-*", &ListOptions{Offset: 20}) + a.So(res, ShouldHaveLength, 0) + + res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) + a.So(res, ShouldHaveLength, 2) + a.So(res["test-2"], ShouldResemble, []string{"test-2"}) + a.So(res["test-3"], ShouldResemble, []string{"test-3"}) + + res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) + a.So(res, ShouldHaveLength, 8) + } + +} From 9a49f550d292c3ff77903496983953892e2fb5aa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 14:13:36 +0200 Subject: [PATCH 1975/2266] Add GetFields to RedisMapStore --- core/storage/redis_map_store.go | 25 +++++++++++++++++++++++++ core/storage/redis_map_store_test.go | 8 ++++++++ 2 files changed, 33 insertions(+) diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index 51e3bfae4..9bded9a2a 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -121,6 +121,31 @@ func (s *RedisMapStore) Get(key string) (interface{}, error) { return i, nil } +// GetFields for a record, prepending the prefix to the key if necessary +func (s *RedisMapStore) GetFields(key string, fields ...string) (interface{}, error) { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + result, err := s.client.HMGet(key, fields...).Result() + if err == redis.Nil { + return nil, errors.NewErrNotFound(key) + } + if err != nil { + return nil, err + } + res := make(map[string]string) + for i, field := range fields { + if str, ok := result[i].(string); ok { + res[field] = str + } + } + i, err := s.decoder(res) + if err != nil { + return nil, err + } + return i, nil +} + // Create a new record, prepending the prefix to the key if necessary, optionally setting only the given properties func (s *RedisMapStore) Create(key string, value interface{}, properties ...string) error { if !strings.HasPrefix(key, s.prefix) { diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go index 85c9de584..5a879c814 100644 --- a/core/storage/redis_map_store_test.go +++ b/core/storage/redis_map_store_test.go @@ -77,6 +77,14 @@ func TestRedisMapStore(t *testing.T) { a.So(res.(*testRedisStruct).UpdatedAt.Nanosecond(), ShouldEqual, now.Nanosecond()) } + // GetFields + { + res, err := s.GetFields("test", "name") + a.So(err, ShouldBeNil) + a.So(res, ShouldNotBeNil) + a.So(res.(*testRedisStruct).Name, ShouldEqual, "My Name") + } + for i := 1; i < 10; i++ { // Create Extra { From 7c11a9da13e74d2b98e88344ff413b1fc0cc24d7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 14:59:31 +0200 Subject: [PATCH 1976/2266] Make StringStringMapDecoder consistent If pointer in -> pointer out If struct in -> struct out --- core/storage/decoder.go | 14 ++++++++++++-- core/storage/redis_map_store_test.go | 22 +++++++++++----------- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/core/storage/decoder.go b/core/storage/decoder.go index 401c199ed..608a009c6 100644 --- a/core/storage/decoder.go +++ b/core/storage/decoder.go @@ -104,10 +104,20 @@ func unmarshalToType(typ reflect.Type, value string) (val interface{}, err error // buildDefaultStructDecoder is used by the RedisMapStore func buildDefaultStructDecoder(base interface{}) StringStringMapDecoder { - return func(input map[string]string) (interface{}, error) { + return func(input map[string]string) (output interface{}, err error) { baseType := reflect.TypeOf(base) + // If we get a pointer in, we'll return a pointer out + if baseType.Kind() == reflect.Ptr { + output = reflect.New(baseType.Elem()).Interface() + } else { + output = reflect.New(baseType).Interface() + } + defer func() { + if err == nil && baseType.Kind() != reflect.Ptr { + output = reflect.Indirect(reflect.ValueOf(output)).Interface() + } + }() - output := reflect.New(baseType).Interface() s := structs.New(output) for _, field := range s.Fields() { if !field.IsExported() { diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go index 5a879c814..9cae33626 100644 --- a/core/storage/redis_map_store_test.go +++ b/core/storage/redis_map_store_test.go @@ -64,7 +64,7 @@ func TestRedisMapStore(t *testing.T) { // Create Existing { - err := s.Create("test", &testRedisStructVal) + err := s.Create("test", testRedisStructVal) a.So(err, ShouldNotBeNil) } @@ -73,8 +73,8 @@ func TestRedisMapStore(t *testing.T) { res, err := s.Get("test") a.So(err, ShouldBeNil) a.So(res, ShouldNotBeNil) - a.So(res.(*testRedisStruct).Name, ShouldEqual, "My Name") - a.So(res.(*testRedisStruct).UpdatedAt.Nanosecond(), ShouldEqual, now.Nanosecond()) + a.So(res.(testRedisStruct).Name, ShouldEqual, "My Name") + a.So(res.(testRedisStruct).UpdatedAt.Nanosecond(), ShouldEqual, now.Nanosecond()) } // GetFields @@ -82,7 +82,7 @@ func TestRedisMapStore(t *testing.T) { res, err := s.GetFields("test", "name") a.So(err, ShouldBeNil) a.So(res, ShouldNotBeNil) - a.So(res.(*testRedisStruct).Name, ShouldEqual, "My Name") + a.So(res.(testRedisStruct).Name, ShouldEqual, "My Name") } for i := 1; i < 10; i++ { @@ -92,7 +92,7 @@ func TestRedisMapStore(t *testing.T) { defer func() { c.Del("test-redis-map-store:" + name).Result() }() - s.Create(name, &testRedisStruct{ + s.Create(name, testRedisStruct{ Name: name, }) } @@ -103,7 +103,7 @@ func TestRedisMapStore(t *testing.T) { res, err := s.GetAll([]string{"test"}, nil) a.So(err, ShouldBeNil) a.So(res, ShouldHaveLength, 1) - a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "My Name") + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "My Name") } // List @@ -111,15 +111,15 @@ func TestRedisMapStore(t *testing.T) { res, err := s.List("", nil) a.So(err, ShouldBeNil) a.So(res, ShouldHaveLength, 10) - a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "My Name") + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "My Name") } // List With Options { res, _ := s.List("test-*", &ListOptions{Limit: 2}) a.So(res, ShouldHaveLength, 2) - a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "test-1") - a.So(res[1].(*testRedisStruct).Name, ShouldEqual, "test-2") + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "test-1") + a.So(res[1].(testRedisStruct).Name, ShouldEqual, "test-2") res, _ = s.List("test-*", &ListOptions{Limit: 20}) a.So(res, ShouldHaveLength, 9) @@ -129,8 +129,8 @@ func TestRedisMapStore(t *testing.T) { res, _ = s.List("test-*", &ListOptions{Limit: 2, Offset: 1}) a.So(res, ShouldHaveLength, 2) - a.So(res[0].(*testRedisStruct).Name, ShouldEqual, "test-2") - a.So(res[1].(*testRedisStruct).Name, ShouldEqual, "test-3") + a.So(res[0].(testRedisStruct).Name, ShouldEqual, "test-2") + a.So(res[1].(testRedisStruct).Name, ShouldEqual, "test-3") res, _ = s.List("test-*", &ListOptions{Limit: 20, Offset: 1}) a.So(res, ShouldHaveLength, 8) From ed7e592069013ca0266f1d06ce3369d9e56aae15 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 15:25:08 +0200 Subject: [PATCH 1977/2266] Don't print a message when val==nil --- core/storage/decoder.go | 1 - 1 file changed, 1 deletion(-) diff --git a/core/storage/decoder.go b/core/storage/decoder.go index 608a009c6..b7e62eb01 100644 --- a/core/storage/decoder.go +++ b/core/storage/decoder.go @@ -144,7 +144,6 @@ func buildDefaultStructDecoder(base interface{}) StringStringMapDecoder { } if val == nil { - fmt.Printf("Could not decode %s to %s\n", str, field.Kind()) continue } From 0c101ec124e06b5c33192555c52fd6f35dcefb33 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 14:39:17 +0200 Subject: [PATCH 1978/2266] Allow specifying struct tag for storage (default redis) --- core/storage/decoder.go | 6 +- core/storage/encoder.go | 108 ++++++++++++++------------- core/storage/encoder_test.go | 4 +- core/storage/redis_map_store.go | 6 +- core/storage/redis_map_store_test.go | 2 +- core/storage/tags.go | 2 +- 6 files changed, 69 insertions(+), 59 deletions(-) diff --git a/core/storage/decoder.go b/core/storage/decoder.go index b7e62eb01..0118d8635 100644 --- a/core/storage/decoder.go +++ b/core/storage/decoder.go @@ -103,7 +103,11 @@ func unmarshalToType(typ reflect.Type, value string) (val interface{}, err error } // buildDefaultStructDecoder is used by the RedisMapStore -func buildDefaultStructDecoder(base interface{}) StringStringMapDecoder { +func buildDefaultStructDecoder(base interface{}, tagName string) StringStringMapDecoder { + if tagName == "" { + tagName = defaultTagName + } + return func(input map[string]string) (output interface{}, err error) { baseType := reflect.TypeOf(base) // If we get a pointer in, we'll return a pointer out diff --git a/core/storage/encoder.go b/core/storage/encoder.go index aa0f9f75e..cbfa4be5c 100644 --- a/core/storage/encoder.go +++ b/core/storage/encoder.go @@ -23,76 +23,82 @@ type isEmptier interface { IsEmpty() bool } -func defaultStructEncoder(input interface{}, properties ...string) (map[string]string, error) { - vmap := make(map[string]string) - s := structs.New(input) - s.TagName = tagName - if len(properties) == 0 { - properties = s.Names() +func buildDefaultStructEncoder(tagName string) StringStringMapEncoder { + if tagName == "" { + tagName = defaultTagName } - for _, field := range s.Fields() { - if !field.IsExported() { - continue - } - - if !stringInSlice(field.Name(), properties) { - continue - } - tagName, opts := parseTag(field.Tag(tagName)) - if tagName == "" || tagName == "-" { - continue + return func(input interface{}, properties ...string) (map[string]string, error) { + vmap := make(map[string]string) + s := structs.New(input) + s.TagName = tagName + if len(properties) == 0 { + properties = s.Names() } - - val := field.Value() - - if opts.Has("omitempty") { - if field.IsZero() { + for _, field := range s.Fields() { + if !field.IsExported() { continue } - if z, ok := val.(isZeroer); ok && z.IsZero() { + + if !stringInSlice(field.Name(), properties) { continue } - if z, ok := val.(isEmptier); ok && z.IsEmpty() { + + tagName, opts := parseTag(field.Tag(tagName)) + if tagName == "" || tagName == "-" { continue } - } - if v, ok := val.(string); ok { - vmap[tagName] = v - continue - } + val := field.Value() - if !field.IsZero() { - if m, ok := val.(encoding.TextMarshaler); ok { - txt, err := m.MarshalText() - if err != nil { - return nil, err + if opts.Has("omitempty") { + if field.IsZero() { + continue } - vmap[tagName] = string(txt) + if z, ok := val.(isZeroer); ok && z.IsZero() { + continue + } + if z, ok := val.(isEmptier); ok && z.IsEmpty() { + continue + } + } + + if v, ok := val.(string); ok { + vmap[tagName] = v continue } - if m, ok := val.(json.Marshaler); ok { - txt, err := m.MarshalJSON() - if err != nil { - return nil, err + + if !field.IsZero() { + if m, ok := val.(encoding.TextMarshaler); ok { + txt, err := m.MarshalText() + if err != nil { + return nil, err + } + vmap[tagName] = string(txt) + continue } - vmap[tagName] = string(txt) + if m, ok := val.(json.Marshaler); ok { + txt, err := m.MarshalJSON() + if err != nil { + return nil, err + } + vmap[tagName] = string(txt) + continue + } + } + + if field.Kind() == reflect.String { + vmap[tagName] = fmt.Sprint(val) continue } - } - if field.Kind() == reflect.String { - vmap[tagName] = fmt.Sprint(val) - continue - } + if txt, err := json.Marshal(val); err == nil { + vmap[tagName] = string(txt) + continue + } - if txt, err := json.Marshal(val); err == nil { - vmap[tagName] = string(txt) - continue + vmap[tagName] = fmt.Sprintf("%v", val) } - - vmap[tagName] = fmt.Sprintf("%v", val) + return vmap, nil } - return vmap, nil } diff --git a/core/storage/encoder_test.go b/core/storage/encoder_test.go index acfe0b052..dde7a5368 100644 --- a/core/storage/encoder_test.go +++ b/core/storage/encoder_test.go @@ -59,7 +59,7 @@ func TestDefaultStructEncoder(t *testing.T) { var stime = Time{now} var emptySTime Time - out, err := defaultStructEncoder(&testStruct{ + out, err := buildDefaultStructEncoder("")(&testStruct{ unexported: "noop", NoRedis: "noop", DisRedis: "noop", @@ -90,7 +90,7 @@ func TestDefaultStructEncoder(t *testing.T) { a.So(out["strings"], ShouldEqual, `["string1","string2"]`) a.So(out["jm"], ShouldEqual, "{cool}") - out, err = defaultStructEncoder(&testStruct{ + out, err = buildDefaultStructEncoder("")(&testStruct{ String: "noop", Strings: []string{"string1", "string2"}, }, "String") diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index 9bded9a2a..b40e17eee 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -32,9 +32,9 @@ func NewRedisMapStore(client *redis.Client, prefix string) *RedisMapStore { } // SetBase sets the base struct for automatically encoding and decoding to and from Redis format -func (s *RedisMapStore) SetBase(base interface{}) { - s.SetEncoder(defaultStructEncoder) - s.SetDecoder(buildDefaultStructDecoder(base)) +func (s *RedisMapStore) SetBase(base interface{}, tagName string) { + s.SetEncoder(buildDefaultStructEncoder(tagName)) + s.SetDecoder(buildDefaultStructDecoder(base, tagName)) } // SetEncoder sets the encoder to convert structs to Redis format diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go index 9cae33626..df0bea368 100644 --- a/core/storage/redis_map_store_test.go +++ b/core/storage/redis_map_store_test.go @@ -29,7 +29,7 @@ func TestRedisMapStore(t *testing.T) { UpdatedAt: Time{now}, } - s.SetBase(testRedisStructVal) + s.SetBase(testRedisStructVal, "") // Get non-existing { diff --git a/core/storage/tags.go b/core/storage/tags.go index e7adbdff4..5444ee01a 100644 --- a/core/storage/tags.go +++ b/core/storage/tags.go @@ -5,7 +5,7 @@ package storage import "strings" -var tagName = "redis" +var defaultTagName = "redis" type tagOptions []string From ffd70ab9622388f4519ea110ab6cedb0f3827879 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 10:54:51 +0200 Subject: [PATCH 1979/2266] Add Delete to RedisSetStore --- core/storage/redis_set_store.go | 8 ++++++++ core/storage/redis_set_store_test.go | 10 ++++++++++ 2 files changed, 18 insertions(+) diff --git a/core/storage/redis_set_store.go b/core/storage/redis_set_store.go index de74a82f1..53e9661de 100644 --- a/core/storage/redis_set_store.go +++ b/core/storage/redis_set_store.go @@ -132,3 +132,11 @@ func (s *RedisSetStore) Remove(key string, values ...string) error { } return s.client.SRem(key, valuesI...).Err() } + +// Delete the entire set +func (s *RedisSetStore) Delete(key string) error { + if !strings.HasPrefix(key, s.prefix) { + key = s.prefix + key + } + return s.client.Del(key).Err() +} diff --git a/core/storage/redis_set_store_test.go b/core/storage/redis_set_store_test.go index d71be81d2..c46970667 100644 --- a/core/storage/redis_set_store_test.go +++ b/core/storage/redis_set_store_test.go @@ -120,4 +120,14 @@ func TestRedisSetStore(t *testing.T) { a.So(res, ShouldHaveLength, 8) } + // Delete + { + err := s.Delete("test-1") + a.So(err, ShouldBeNil) + + res, err := s.List("", nil) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 9) + } + } From e7da415cd5950aef6feca1dec8c2b0890335c2d6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 10:40:55 +0200 Subject: [PATCH 1980/2266] Check for changed fields in RedisMapStore --- core/storage/redis_map_store.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index b40e17eee..64f86f146 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -146,12 +146,23 @@ func (s *RedisMapStore) GetFields(key string, fields ...string) (interface{}, er return i, nil } +// ChangedFielder interface is used to see what fields to update +type ChangedFielder interface { + ChangedFields() []string +} + // Create a new record, prepending the prefix to the key if necessary, optionally setting only the given properties func (s *RedisMapStore) Create(key string, value interface{}, properties ...string) error { if !strings.HasPrefix(key, s.prefix) { key = s.prefix + key } + if len(properties) == 0 { + if i, ok := value.(ChangedFielder); ok { + properties = i.ChangedFields() + } + } + vmap, err := s.encoder(value, properties...) if err != nil { return err @@ -190,6 +201,12 @@ func (s *RedisMapStore) Update(key string, value interface{}, properties ...stri key = s.prefix + key } + if len(properties) == 0 { + if i, ok := value.(ChangedFielder); ok { + properties = i.ChangedFields() + } + } + vmap, err := s.encoder(value, properties...) if err != nil { return err From 31ef07dad45ac99ecca5ec9d148bad2f8ecf8d78 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 13:39:29 +0200 Subject: [PATCH 1981/2266] Respect nil and null in RedisMapStore --- core/storage/decoder.go | 7 ++++++- core/storage/redis_map_store_test.go | 20 ++++++++------------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/core/storage/decoder.go b/core/storage/decoder.go index 0118d8635..7ebbd3f0b 100644 --- a/core/storage/decoder.go +++ b/core/storage/decoder.go @@ -137,7 +137,12 @@ func buildDefaultStructDecoder(base interface{}, tagName string) StringStringMap var val interface{} switch field.Kind() { - case reflect.Struct, reflect.Array, reflect.Interface, reflect.Slice, reflect.Ptr: + case reflect.Ptr: + if str == "" || str == "null" { + continue + } + fallthrough + case reflect.Struct, reflect.Array, reflect.Interface, reflect.Slice: var err error val, err = unmarshalToType(baseField.Type, str) if err != nil { diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go index df0bea368..4ac381f23 100644 --- a/core/storage/redis_map_store_test.go +++ b/core/storage/redis_map_store_test.go @@ -13,8 +13,12 @@ import ( ) type testRedisStruct struct { - Name string `redis:"name,omitempty"` - UpdatedAt Time `redis:"updated_at,omitempty"` + unexported string + Skipped string `redis:"-"` + Name string `redis:"name,omitempty"` + UpdatedAt Time `redis:"updated_at,omitempty"` + Empty *map[string]string `redis:"empty"` + NotEmpty *map[string]string `redis:"not_empty"` } func TestRedisMapStore(t *testing.T) { @@ -24,9 +28,11 @@ func TestRedisMapStore(t *testing.T) { a.So(s, ShouldNotBeNil) now := time.Now() + notEmpty := map[string]string{"ab": "cd"} testRedisStructVal := testRedisStruct{ Name: "My Name", UpdatedAt: Time{now}, + NotEmpty: ¬Empty, } s.SetBase(testRedisStructVal, "") @@ -39,16 +45,6 @@ func TestRedisMapStore(t *testing.T) { a.So(res, ShouldBeNil) } - // Not Create - { - err := s.Create("test", &testRedisStruct{}) - a.So(err, ShouldBeNil) - - exists, err := c.Exists("test-redis-map-store:test").Result() - a.So(err, ShouldBeNil) - a.So(exists, ShouldBeFalse) - } - // Create New { defer func() { From 056ebc5e7579f531f0260f7651043b45ef7f58d4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 13:58:59 +0200 Subject: [PATCH 1982/2266] Add helper for Redis testing --- utils/testing/redis.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 utils/testing/redis.go diff --git a/utils/testing/redis.go b/utils/testing/redis.go new file mode 100644 index 000000000..2fb3fa8d5 --- /dev/null +++ b/utils/testing/redis.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package testing + +import ( + "fmt" + "os" + + redis "gopkg.in/redis.v4" +) + +// GetRedisClient returns a redis client that can be used for testing +func GetRedisClient() *redis.Client { + host := os.Getenv("REDIS_HOST") + if host == "" { + host = "localhost" + } + return redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:6379", host), + Password: "", // no password set + DB: 1, // use default DB + }) +} From 7291b56ae21b73f74de52c324570a8cc31fed6d8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 13 Oct 2016 15:36:08 +0200 Subject: [PATCH 1983/2266] Use RedisMapStore and RedisSetStore in Networkserver --- cmd/networkserver.go | 4 +- core/networkserver/activation_test.go | 31 +- core/networkserver/device/device.go | 185 ++--------- core/networkserver/device/device_test.go | 56 +--- core/networkserver/device/store.go | 372 ++++++----------------- core/networkserver/device/store_test.go | 312 +++++++++---------- core/networkserver/downlink.go | 4 +- core/networkserver/downlink_test.go | 6 +- core/networkserver/get_devices.go | 4 +- core/networkserver/get_devices_test.go | 27 +- core/networkserver/manager_server.go | 52 +--- core/networkserver/networkserver.go | 4 +- core/networkserver/networkserver_test.go | 6 +- core/networkserver/uplink.go | 3 +- core/networkserver/uplink_test.go | 6 +- 15 files changed, 372 insertions(+), 700 deletions(-) diff --git a/cmd/networkserver.go b/cmd/networkserver.go index 2da7d1f17..f5e69a796 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -18,7 +18,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "google.golang.org/grpc" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) // networkserverCmd represents the networkserver command @@ -40,7 +40,7 @@ var networkserverCmd = &cobra.Command{ client := redis.NewClient(&redis.Options{ Addr: viper.GetString("networkserver.redis-address"), Password: "", // no password set - DB: int64(viper.GetInt("networkserver.redis-db")), + DB: viper.GetInt("networkserver.redis-db"), }) connectRedis(client) diff --git a/core/networkserver/activation_test.go b/core/networkserver/activation_test.go index f2fa6559b..5ccd6d716 100644 --- a/core/networkserver/activation_test.go +++ b/core/networkserver/activation_test.go @@ -12,6 +12,7 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -26,7 +27,7 @@ func TestHandlePrepareActivation(t *testing.T) { "local", }, }, - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-prepare-activation"), } appEUI := types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)) @@ -43,10 +44,16 @@ func TestHandlePrepareActivation(t *testing.T) { }) a.So(err, ShouldNotBeNil) - // Constrained Device - ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ + dev := &device.Device{AppEUI: appEUI, DevEUI: devEUI, Options: device.Options{ ActivationConstraints: "private", - }}) + }} + a.So(ns.devices.Set(dev), ShouldBeNil) + + defer func() { + ns.devices.Delete(appEUI, devEUI) + }() + + // Constrained Device resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ DevEui: &devEUI, AppEui: &appEUI, @@ -59,8 +66,11 @@ func TestHandlePrepareActivation(t *testing.T) { }) a.So(err, ShouldNotBeNil) + dev.StartUpdate() + dev.Options = device.Options{} + a.So(ns.devices.Set(dev), ShouldBeNil) + // Device registered - ns.devices.Set(&device.Device{AppEUI: appEUI, DevEUI: devEUI}) resp, err = ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ DevEui: &devEUI, AppEui: &appEUI, @@ -89,12 +99,17 @@ func TestHandlePrepareActivation(t *testing.T) { func TestHandleActivate(t *testing.T) { a := New(t) ns := &networkServer{ - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-activate"), } - ns.devices.Set(&device.Device{ + + dev := &device.Device{ AppEUI: types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), DevEUI: types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), - }) + } + a.So(ns.devices.Set(dev), ShouldBeNil) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), types.DevEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1))) + }() _, err := ns.HandleActivate(&pb_handler.DeviceActivationResponse{}) a.So(err, ShouldNotBeNil) diff --git a/core/networkserver/device/device.go b/core/networkserver/device/device.go index 9bf092a8e..649794b5b 100644 --- a/core/networkserver/device/device.go +++ b/core/networkserver/device/device.go @@ -4,12 +4,11 @@ package device import ( - "encoding/json" - "fmt" + "reflect" "time" - "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/core/types" + "github.com/fatih/structs" ) // Options for the specified device @@ -21,165 +20,45 @@ type Options struct { // Device contains the state of a device type Device struct { - DevEUI types.DevEUI - AppEUI types.AppEUI - AppID string - DevID string - DevAddr types.DevAddr - NwkSKey types.NwkSKey - FCntUp uint32 - FCntDown uint32 - LastSeen time.Time - Options Options - Utilization Utilization - UpdatedAt time.Time -} + old *Device + DevEUI types.DevEUI `redis:"dev_eui"` + AppEUI types.AppEUI `redis:"app_eui"` + AppID string `redis:"app_id"` + DevID string `redis:"dev_id"` + DevAddr types.DevAddr `redis:"dev_addr"` + NwkSKey types.NwkSKey `redis:"nwk_s_key"` + FCntUp uint32 `redis:"f_cnt_up"` + FCntDown uint32 `redis:"f_cnt_down"` + LastSeen time.Time `redis:"last_seen"` + Options Options `redis:"options"` + Utilization Utilization `redis:"utilization"` -// DeviceProperties contains all properties of a Device that can be stored in Redis. -var DeviceProperties = []string{ - "dev_eui", - "app_eui", - "app_id", - "dev_id", - "dev_addr", - "nwk_s_key", - "f_cnt_up", - "f_cnt_down", - "last_seen", - "options", - "utilization", - "updated_at", + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` } -// ToStringStringMap converts the given properties of Device to a -// map[string]string for storage in Redis. -func (device *Device) ToStringStringMap(properties ...string) (map[string]string, error) { - output := make(map[string]string) - for _, p := range properties { - property, err := device.formatProperty(p) - if err != nil { - return output, err - } - output[p] = property - - } - return output, nil +// StartUpdate stores the state of the device +func (d *Device) StartUpdate() { + old := *d + d.old = &old } -// FromStringStringMap imports known values from the input to a Device. -func (device *Device) FromStringStringMap(input map[string]string) error { - for k, v := range input { - device.parseProperty(k, v) +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (d Device) ChangedFields() (changed []string) { + new := structs.New(d) + fields := new.Names() + if d.old == nil { + return fields } - return nil -} + old := structs.New(*d.old) -func (device *Device) formatProperty(property string) (formatted string, err error) { - switch property { - case "dev_eui": - formatted = device.DevEUI.String() - case "app_eui": - formatted = device.AppEUI.String() - case "app_id": - formatted = device.AppID - case "dev_id": - formatted = device.DevID - case "dev_addr": - formatted = device.DevAddr.String() - case "nwk_s_key": - formatted = device.NwkSKey.String() - case "f_cnt_up": - formatted = storage.FormatUint32(device.FCntUp) - case "f_cnt_down": - formatted = storage.FormatUint32(device.FCntDown) - case "last_seen": - if !device.LastSeen.IsZero() { - formatted = device.LastSeen.UTC().Format(time.RFC3339Nano) + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue } - case "options": - data, err := json.Marshal(device.Options) - if err != nil { - return "", err + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) } - formatted = string(data) - case "utilization": - // TODO - case "updated_at": - if !device.UpdatedAt.IsZero() { - formatted = device.UpdatedAt.UTC().Format(time.RFC3339Nano) - } - default: - err = fmt.Errorf("Property %s does not exist in Status", property) } return } - -func (device *Device) parseProperty(property string, value string) error { - if value == "" { - return nil - } - switch property { - case "dev_eui": - val, err := types.ParseDevEUI(value) - if err != nil { - return err - } - device.DevEUI = val - case "app_eui": - val, err := types.ParseAppEUI(value) - if err != nil { - return err - } - device.AppEUI = val - case "app_id": - device.AppID = value - case "dev_id": - device.DevID = value - case "dev_addr": - val, err := types.ParseDevAddr(value) - if err != nil { - return err - } - device.DevAddr = val - case "nwk_s_key": - val, err := types.ParseNwkSKey(value) - if err != nil { - return err - } - device.NwkSKey = val - case "f_cnt_up": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - device.FCntUp = val - case "f_cnt_down": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - device.FCntDown = val - case "last_seen": - val, err := time.Parse(time.RFC3339Nano, value) - if err != nil { - return err - } - device.LastSeen = val - case "options": - var options Options - err := json.Unmarshal([]byte(value), &options) - if err != nil { - return err - } - device.Options = options - case "utilization": - // TODO - case "updated_at": - val, err := time.Parse(time.RFC3339Nano, value) - if err != nil { - return err - } - device.UpdatedAt = val - } - return nil -} diff --git a/core/networkserver/device/device_test.go b/core/networkserver/device/device_test.go index 86cee2a5a..cdaae2951 100644 --- a/core/networkserver/device/device_test.go +++ b/core/networkserver/device/device_test.go @@ -5,53 +5,27 @@ package device import ( "testing" - "time" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" ) -func getTestDevice() (device *Device, dmap map[string]string) { - return &Device{ - DevEUI: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - AppEUI: types.AppEUI([8]byte{8, 7, 6, 5, 4, 3, 2, 1}), - AppID: "TestApp-1", - DevID: "TestDev-1", - DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), - NwkSKey: types.NwkSKey([16]byte{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}), - FCntUp: 42, - FCntDown: 24, - LastSeen: time.Unix(0, 0).UTC(), - Options: Options{}, - }, map[string]string{ - "dev_eui": "0102030405060708", - "app_eui": "0807060504030201", - "app_id": "TestApp-1", - "dev_id": "TestDev-1", - "dev_addr": "01020304", - "nwk_s_key": "00010002000300040005000600070008", - "f_cnt_up": "42", - "f_cnt_down": "24", - "last_seen": "1970-01-01T00:00:00Z", - "options": `{"disable_fcnt_check":false,"uses_32_bit_fcnt":false}`, - "utilization": "", - "updated_at": "", - } -} - -func TestToStringMap(t *testing.T) { +func TestDeviceUpdate(t *testing.T) { a := New(t) - device, expected := getTestDevice() - dmap, err := device.ToStringStringMap(DeviceProperties...) - a.So(err, ShouldBeNil) - a.So(dmap, ShouldResemble, expected) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + a.So(device.old.DevID, ShouldEqual, device.DevID) } -func TestFromStringMap(t *testing.T) { +func TestDeviceChangedFields(t *testing.T) { a := New(t) - device := &Device{} - expected, dmap := getTestDevice() - err := device.FromStringStringMap(dmap) - a.So(err, ShouldBeNil) - a.So(device, ShouldResemble, expected) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + device.DevID = "NewDevID" + + a.So(device.ChangedFields(), ShouldHaveLength, 1) + a.So(device.ChangedFields(), ShouldContain, "DevID") } diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index 2ba61e8a5..ea458c19f 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -5,357 +5,175 @@ package device import ( "fmt" - "sort" "time" + "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) -// Store is used to store device configurations +// Store interface for Devices type Store interface { - // List all devices List() ([]*Device, error) - // Get the full information about a device + ListForAddress(devAddr types.DevAddr) ([]*Device, error) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) - // Get a list of devices matching the DevAddr - GetWithAddress(devAddr types.DevAddr) ([]*Device, error) - // Set the given fields of a device. If fields empty, it sets all fields. - Set(device *Device, fields ...string) error - // Activate a device - Activate(types.AppEUI, types.DevEUI, types.DevAddr, types.NwkSKey) error - // Delete a device - Delete(types.AppEUI, types.DevEUI) error + Set(new *Device, properties ...string) (err error) + Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error + Delete(appEUI types.AppEUI, devEUI types.DevEUI) error } -// NewDeviceStore creates a new in-memory Device store -func NewDeviceStore() Store { - return &deviceStore{ - devices: make(map[types.AppEUI]map[types.DevEUI]*Device), - byAddress: make(map[types.DevAddr][]*Device), - } -} +const defaultRedisPrefix = "ns" -// deviceStore is an in-memory Device store. It should only be used for testing -// purposes. Use the redisDeviceStore for actual deployments. -type deviceStore struct { - devices map[types.AppEUI]map[types.DevEUI]*Device - byAddress map[types.DevAddr][]*Device -} +const redisDevicePrefix = "device" +const redisDevAddrPrefix = "dev_addr" -func (s *deviceStore) List() ([]*Device, error) { - devices := make([]*Device, 0, len(s.devices)) - for _, app := range s.devices { - for _, device := range app { - devices = append(devices, device) - } +// NewRedisDeviceStore creates a new Redis-based status store +func NewRedisDeviceStore(client *redis.Client, prefix string) Store { + if prefix == "" { + prefix = defaultRedisPrefix } - return devices, nil -} + store := storage.NewRedisMapStore(client, prefix+":"+redisDevicePrefix) + store.SetBase(Device{}, "") -func (s *deviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { - if app, ok := s.devices[appEUI]; ok { - if dev, ok := app[devEUI]; ok { - return dev, nil - } + return &RedisDeviceStore{ + store: store, + devAddrIndex: storage.NewRedisSetStore(client, prefix+":"+redisDevAddrPrefix), } - return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } -func (s *deviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, error) { - if devices, ok := s.byAddress[devAddr]; ok { - return devices, nil - } - return []*Device{}, nil +// RedisDeviceStore stores Devices in Redis. +// - Devices are stored as a Hash +// - DevAddr mappings are indexed in a Set +type RedisDeviceStore struct { + store *storage.RedisMapStore + devAddrIndex *storage.RedisSetStore } -func (s *deviceStore) Set(new *Device, fields ...string) error { - // NOTE: We don't care about fields for testing - if app, ok := s.devices[new.AppEUI]; ok { - if old, ok := app[new.DevEUI]; ok { - // DevAddr Updated - if new.DevAddr != old.DevAddr && !old.DevAddr.IsEmpty() { - // Remove the old DevAddr - newList := make([]*Device, 0, len(s.byAddress[old.DevAddr])) - for _, candidate := range s.byAddress[old.DevAddr] { - if candidate.DevEUI != old.DevEUI || candidate.AppEUI != old.AppEUI { - newList = append(newList, candidate) - } - } - s.byAddress[old.DevAddr] = newList - } - } - app[new.DevEUI] = new - } else { - s.devices[new.AppEUI] = map[types.DevEUI]*Device{new.DevEUI: new} - } - - if !new.DevAddr.IsEmpty() && !new.NwkSKey.IsEmpty() { - if devices, ok := s.byAddress[new.DevAddr]; ok { - var exists bool - for _, candidate := range devices { - if candidate.AppEUI == new.AppEUI && candidate.DevEUI == new.DevEUI { - exists = true - break - } - } - if !exists { - s.byAddress[new.DevAddr] = append(devices, new) - } - } else { - s.byAddress[new.DevAddr] = []*Device{new} - } - } - - return nil -} - -func (s *deviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { - dev, err := s.Get(appEUI, devEUI) +// List all Devices +func (s *RedisDeviceStore) List() ([]*Device, error) { + devicesI, err := s.store.List("", nil) if err != nil { - return err + return nil, err } - dev.LastSeen = time.Now() - dev.DevAddr = devAddr - dev.NwkSKey = nwkSKey - dev.FCntUp = 0 - dev.FCntDown = 0 - return s.Set(dev, "last_seen", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down") -} - -func (s *deviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { - if app, ok := s.devices[appEUI]; ok { - if old, ok := app[devEUI]; ok { - delete(app, devEUI) - newList := make([]*Device, 0, len(s.byAddress[old.DevAddr])) - for _, candidate := range s.byAddress[old.DevAddr] { - if candidate.DevEUI != old.DevEUI || candidate.AppEUI != old.AppEUI { - newList = append(newList, candidate) - } - } - s.byAddress[old.DevAddr] = newList + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) } } - - return nil + return devices, nil } -// NewRedisDeviceStore creates a new Redis-based status store -func NewRedisDeviceStore(client *redis.Client) Store { - return &redisDeviceStore{ - client: client, +// ListForAddress lists all devices for a specific DevAddr +func (s *RedisDeviceStore) ListForAddress(devAddr types.DevAddr) ([]*Device, error) { + deviceKeys, err := s.devAddrIndex.Get(devAddr.String()) + if errors.GetErrType(err) == errors.NotFound { + return nil, nil } -} - -const redisDevicePrefix = "ns:device" -const redisDevAddrPrefix = "ns:dev_addr" - -type redisDeviceStore struct { - client *redis.Client -} - -func (s *redisDeviceStore) List() ([]*Device, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() if err != nil { return nil, err } - - sort.Strings(keys) - - pipe := s.client.Pipeline() - defer pipe.Close() - - // Add all commands to pipeline - cmds := make(map[string]*redis.StringStringMapCmd) - for _, key := range keys { - cmds[key] = pipe.HGetAllMap(key) - } - - // Execute pipeline - _, err = pipe.Exec() + devicesI, err := s.store.GetAll(deviceKeys, nil) if err != nil { return nil, err } - - // Get all results from pipeline - devices := make([]*Device, 0, len(keys)) - for _, cmd := range cmds { - dmap, err := cmd.Result() - if err == nil { - device := &Device{} - err := device.FromStringStringMap(dmap) - if err == nil { - devices = append(devices, device) - } + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) } } - return devices, nil } -func (s *redisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI)).Result() +// Get a specific Device +func (s *RedisDeviceStore) Get(appEUI types.AppEUI, devEUI types.DevEUI) (*Device, error) { + deviceI, err := s.store.Get(fmt.Sprintf("%s:%s", appEUI, devEUI)) if err != nil { - if err == redis.Nil { - return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) - } return nil, err - } else if len(res) == 0 { - return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) } - device := &Device{} - err = device.FromStringStringMap(res) - if err != nil { - return nil, err + if device, ok := deviceI.(Device); ok { + return &device, nil } - return device, nil + return nil, errors.New("Database did not return a Device") } -func (s *redisDeviceStore) GetWithAddress(devAddr types.DevAddr) ([]*Device, error) { - keys, err := s.client.SMembers(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr)).Result() - if err != nil { - return nil, err - } - - sort.Strings(keys) - - pipe := s.client.Pipeline() - defer pipe.Close() - - // Add all commands to pipeline - cmds := make(map[string]*redis.StringStringMapCmd) - for _, key := range keys { - cmds[key] = pipe.HGetAllMap(key) - } - - // Execute pipeline - _, err = pipe.Exec() - if err != nil { - return nil, err - } - - // Get all results from pipeline - devices := make([]*Device, 0, len(keys)) - for _, cmd := range cmds { - dmap, err := cmd.Result() - if err == nil { - device := &Device{} - err := device.FromStringStringMap(dmap) - if err == nil { - devices = append(devices, device) +// Set a new Device or update an existing one +func (s *RedisDeviceStore) Set(new *Device, properties ...string) (err error) { + // If this is an update, check if AppEUI, DevEUI and DevAddr are still the same + old := new.old + var addrChanged bool + if old != nil { + addrChanged = new.DevAddr != old.DevAddr || new.DevEUI != old.DevEUI || new.AppEUI != old.AppEUI + if addrChanged { + if err := s.devAddrIndex.Remove(old.DevAddr.String(), fmt.Sprintf("%s:%s", old.AppEUI, old.DevEUI)); err != nil { + return err } } } - return devices, nil -} + now := time.Now() + new.UpdatedAt = now -func (s *redisDeviceStore) Set(new *Device, fields ...string) error { - if len(fields) == 0 { - fields = DeviceProperties + key := fmt.Sprintf("%s:%s", new.AppEUI, new.DevEUI) + if new.old != nil { + err = s.store.Update(key, *new, properties...) } else { - fields = append(fields, "updated_at") + new.CreatedAt = now + err = s.store.Create(key, *new, properties...) } - - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppEUI, new.DevEUI) - - // Check for old DevAddr - oldDevAddrStr, err := s.client.HGet(key, "dev_addr").Result() - if err != nil && err != redis.Nil { - return err - } - oldDevAddr, _ := types.ParseDevAddr(oldDevAddrStr) - - new.UpdatedAt = time.Now() - dmap, err := new.ToStringStringMap(fields...) if err != nil { - return err - } - if err := s.client.HMSetMap(key, dmap).Err(); err != nil { - return err + return } - // Update DevAddr lookup if needed - if new.DevAddr != oldDevAddr { - if err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, oldDevAddr), key).Err(); err != nil && err != redis.Nil { + if (new.old == nil || addrChanged) && !new.DevAddr.IsEmpty() { + if err := s.devAddrIndex.Add(new.DevAddr.String(), key); err != nil { return err } - if !new.DevAddr.IsEmpty() { - if err := s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, new.DevAddr), key).Err(); err != nil { - return err - } - } } return nil } -func (s *redisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) - - // Find existing device - exists, err := s.client.Exists(key).Result() +// Activate a Device +func (s *RedisDeviceStore) Activate(appEUI types.AppEUI, devEUI types.DevEUI, devAddr types.DevAddr, nwkSKey types.NwkSKey) error { + dev, err := s.Get(appEUI, devEUI) if err != nil { return err } - if !exists { - return errors.NewErrNotFound(fmt.Sprintf("%s/%s", appEUI, devEUI)) - } - // Check for old DevAddr - if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { - // Delete old DevAddr - if devAddr != "" { - err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() - if err != nil { - return err - } - } - } + dev.StartUpdate() - // Update Device - dev := &Device{ - LastSeen: time.Now(), - UpdatedAt: time.Now(), - DevAddr: devAddr, - NwkSKey: nwkSKey, - FCntUp: 0, - FCntDown: 0, - } + dev.LastSeen = time.Now() + dev.UpdatedAt = time.Now() + dev.DevAddr = devAddr + dev.NwkSKey = nwkSKey + dev.FCntUp = 0 + dev.FCntDown = 0 - // Don't touch Utilization and Options - dmap, err := dev.ToStringStringMap("last_seen", "dev_addr", "nwk_s_key", "f_cnt_up", "f_cnt_down", "updated_at") + return s.Set(dev) +} - // Register Device - err = s.client.HMSetMap(key, dmap).Err() - if err != nil { - return err - } +// Delete a Device +func (s *RedisDeviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { + key := fmt.Sprintf("%s:%s", appEUI, devEUI) - // Register DevAddr - err = s.client.SAdd(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() + deviceI, err := s.store.GetFields(key, "dev_addr") if err != nil { return err } - return nil -} + device, ok := deviceI.(Device) + if !ok { + errors.New("Database did not return a Device") + } -func (s *redisDeviceStore) Delete(appEUI types.AppEUI, devEUI types.DevEUI) error { - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appEUI, devEUI) - if devAddr, err := s.client.HGet(key, "dev_addr").Result(); err == nil { - // Delete old DevAddr - if devAddr != "" { - err := s.client.SRem(fmt.Sprintf("%s:%s", redisDevAddrPrefix, devAddr), key).Err() - if err != nil { - return err - } + if !device.DevAddr.IsEmpty() { + if err := s.devAddrIndex.Remove(device.DevAddr.String(), key); err != nil { + return err } } - err := s.client.Del(key).Err() - if err != nil { - return err - } - return nil + + return s.store.Delete(key) } diff --git a/core/networkserver/device/store_test.go b/core/networkserver/device/store_test.go index c0263ad00..0d4ba4e58 100644 --- a/core/networkserver/device/store_test.go +++ b/core/networkserver/device/store_test.go @@ -4,198 +4,186 @@ package device import ( - "fmt" - "os" "testing" - "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB - }) -} - func TestDeviceStore(t *testing.T) { a := New(t) - stores := map[string]Store{ - "local": NewDeviceStore(), - "redis": NewRedisDeviceStore(getRedisClient()), - } + NewRedisDeviceStore(GetRedisClient(), "") - for name, s := range stores { + s := NewRedisDeviceStore(GetRedisClient(), "networkserver-test-device-store") - t.Logf("Testing %s store", name) + // Non-existing App + err := s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, + }) + a.So(err, ShouldBeNil) + + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 1}) + + defer func() { + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + }() + + // Existing App + err = s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, + }) + a.So(err, ShouldBeNil) - // Non-existing App - err := s.Set(&Device{ + defer func() { + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + }() + + res, err := s.ListForAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 2}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 0) + + // Existing Device, New DevAddr + err = s.Set(&Device{ + old: &Device{ DevAddr: types.DevAddr{0, 0, 0, 1}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, - NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, - }) - a.So(err, ShouldBeNil) + }, + DevAddr: types.DevAddr{0, 0, 0, 3}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, + }) + a.So(err, ShouldBeNil) + + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) - // Existing App - err = s.Set(&Device{ + s.Set(&Device{ + old: &Device{ DevAddr: types.DevAddr{0, 0, 0, 1}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, - NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, - }) - a.So(err, ShouldBeNil) - - res, err := s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 2) - res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 2}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 0) - - // Existing Device, New DevAddr - err = s.Set(&Device{ - DevAddr: types.DevAddr{0, 0, 0, 3}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}, - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, - NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 1}, - }) - a.So(err, ShouldBeNil) - - res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 3}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 1) - res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 1) - - s.Set(&Device{ - DevAddr: types.DevAddr{0, 0, 0, 3}, DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, - NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2}, - }) - - res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 1}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 0) - res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 3}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 2) - - dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 2}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) - a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) + }, + DevAddr: types.DevAddr{0, 0, 0, 3}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, + NwkSKey: types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 2}, + }) - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 3}) - a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 0) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 2) - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) - a.So(err, ShouldBeNil) - a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 3}) + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 2}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) - // List - devices, err := s.List() - a.So(err, ShouldBeNil) - a.So(devices, ShouldHaveLength, 2) + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 3}) + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) - err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) - a.So(err, ShouldBeNil) + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 0, 3}) - res, err = s.GetWithAddress(types.DevAddr{0, 0, 0, 3}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 1) + // List + devices, err := s.List() + a.So(err, ShouldBeNil) + a.So(devices, ShouldHaveLength, 2) - // Cleanup - s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 2}) - } + err = s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 0, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 0, 1}) + a.So(err, ShouldBeNil) + res, err = s.ListForAddress(types.DevAddr{0, 0, 0, 3}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) } func TestDeviceActivate(t *testing.T) { a := New(t) - stores := map[string]Store{ - "local": NewDeviceStore(), - "redis": NewRedisDeviceStore(getRedisClient()), - } - - for name, s := range stores { - - t.Logf("Testing %s store", name) - - // Device not registered - err := s.Activate( - types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, - types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, - types.DevAddr{0, 0, 1, 1}, - types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, - ) - a.So(err, ShouldNotBeNil) - - // Device registered - s.Set(&Device{ - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, - }) - err = s.Activate( - types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, - types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, - types.DevAddr{0, 0, 1, 1}, - types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, - ) - a.So(err, ShouldBeNil) - - // It should register the device - dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) - a.So(err, ShouldBeNil) - a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 1}) - - // It should register the DevAddr - res, err := s.GetWithAddress(types.DevAddr{0, 0, 1, 1}) - a.So(err, ShouldBeNil) - a.So(res, ShouldHaveLength, 1) - - s.Set(&Device{ - DevAddr: types.DevAddr{0, 0, 1, 1}, - DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, - AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, - FCntUp: 42, - FCntDown: 42, - }) - - // Activate the same device again - err = s.Activate( - types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, - types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, - types.DevAddr{0, 0, 1, 2}, - types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}, - ) - a.So(err, ShouldBeNil) - - // It should reset the DevAddr, NwkSKey and Frame Counters - dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) - a.So(err, ShouldBeNil) - a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 2}) - a.So(dev.NwkSKey, ShouldEqual, types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}) - a.So(dev.FCntUp, ShouldEqual, 0) - a.So(dev.FCntDown, ShouldEqual, 0) - - // Cleanup - s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) - - } + s := NewRedisDeviceStore(GetRedisClient(), "networkserver-test-device-activate") + + // Device not registered + err := s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 1}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + ) + a.So(err, ShouldNotBeNil) + + // Device registered + s.Set(&Device{ + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + }) + err = s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 1}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1}, + ) + a.So(err, ShouldBeNil) + + // It should register the device + dev, err := s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 1}) + + // It should register the DevAddr + res, err := s.ListForAddress(types.DevAddr{0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(res, ShouldHaveLength, 1) + + s.Set(&Device{ + DevAddr: types.DevAddr{0, 0, 1, 1}, + DevEUI: types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + AppEUI: types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + FCntUp: 42, + FCntDown: 42, + }) + + // Activate the same device again + err = s.Activate( + types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}, + types.DevAddr{0, 0, 1, 2}, + types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}, + ) + a.So(err, ShouldBeNil) + + // It should reset the DevAddr, NwkSKey and Frame Counters + dev, err = s.Get(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) + a.So(err, ShouldBeNil) + a.So(dev.DevAddr, ShouldEqual, types.DevAddr{0, 0, 1, 2}) + a.So(dev.NwkSKey, ShouldEqual, types.NwkSKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2}) + a.So(dev.FCntUp, ShouldEqual, 0) + a.So(dev.FCntDown, ShouldEqual, 0) + + // Cleanup + s.Delete(types.AppEUI{0, 0, 0, 0, 0, 0, 1, 1}, types.DevEUI{0, 0, 0, 0, 0, 0, 1, 1}) } diff --git a/core/networkserver/downlink.go b/core/networkserver/downlink.go index 1735e1bd4..50a32e63c 100644 --- a/core/networkserver/downlink.go +++ b/core/networkserver/downlink.go @@ -16,6 +16,8 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ return nil, err } + dev.StartUpdate() + if dev.AppID != message.AppId || dev.DevID != message.DevId { return nil, errors.NewErrInvalidArgument("Downlink", "AppID and DevID do not match AppEUI and DevEUI") } @@ -38,7 +40,7 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ // TODO: For confirmed downlink, FCntDown should be incremented AFTER ACK macPayload.FHDR.FCnt = dev.FCntDown dev.FCntDown++ - err = n.devices.Set(dev, "f_cnt_down") + err = n.devices.Set(dev) if err != nil { return nil, err } diff --git a/core/networkserver/downlink_test.go b/core/networkserver/downlink_test.go index 2de6467a9..044b627de 100644 --- a/core/networkserver/downlink_test.go +++ b/core/networkserver/downlink_test.go @@ -9,6 +9,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -16,7 +17,7 @@ import ( func TestHandleDownlink(t *testing.T) { a := New(t) ns := &networkServer{ - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-downlink"), } appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) @@ -37,6 +38,9 @@ func TestHandleDownlink(t *testing.T) { AppEUI: appEUI, DevEUI: devEUI, }) + defer func() { + ns.devices.Delete(appEUI, devEUI) + }() // Invalid Payload message = &pb_broker.DownlinkMessage{ diff --git a/core/networkserver/get_devices.go b/core/networkserver/get_devices.go index f9e635a48..708b70455 100644 --- a/core/networkserver/get_devices.go +++ b/core/networkserver/get_devices.go @@ -6,11 +6,11 @@ package networkserver import ( pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core/fcnt" + "github.com/TheThingsNetwork/ttn/utils/fcnt" ) func (n *networkServer) HandleGetDevices(req *pb.DevicesRequest) (*pb.DevicesResponse, error) { - devices, err := n.devices.GetWithAddress(*req.DevAddr) + devices, err := n.devices.ListForAddress(*req.DevAddr) if err != nil { return nil, err } diff --git a/core/networkserver/get_devices_test.go b/core/networkserver/get_devices_test.go index 9c381dfe0..0e7159750 100644 --- a/core/networkserver/get_devices_test.go +++ b/core/networkserver/get_devices_test.go @@ -9,6 +9,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/networkserver" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -16,7 +17,7 @@ func TestHandleGetDevices(t *testing.T) { a := New(t) ns := &networkServer{ - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-get-devices"), } nwkSKey := types.NwkSKey{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8} @@ -38,6 +39,10 @@ func TestHandleGetDevices(t *testing.T) { NwkSKey: nwkSKey, FCntUp: 5, }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)), types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8))) + }() + res, err = ns.HandleGetDevices(&pb.DevicesRequest{ DevAddr: &devAddr1, FCnt: 5, @@ -73,6 +78,9 @@ func TestHandleGetDevices(t *testing.T) { DisableFCntCheck: true, }, }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4)), types.DevEUI(getEUI(5, 6, 7, 8, 1, 2, 3, 4))) + }() res, err = ns.HandleGetDevices(&pb.DevicesRequest{ DevAddr: &devAddr2, FCnt: 4, @@ -81,7 +89,6 @@ func TestHandleGetDevices(t *testing.T) { a.So(res.Results, ShouldHaveLength, 1) // 32 Bit Frame Counter (A) - devAddr3 := getDevAddr(2, 2, 3, 4) ns.devices.Set(&device.Device{ DevAddr: getDevAddr(2, 2, 3, 4), AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), @@ -92,6 +99,10 @@ func TestHandleGetDevices(t *testing.T) { Uses32BitFCnt: true, }, }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8))) + }() + devAddr3 := getDevAddr(2, 2, 3, 4) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ DevAddr: &devAddr3, FCnt: 5, @@ -101,17 +112,21 @@ func TestHandleGetDevices(t *testing.T) { // 32 Bit Frame Counter (B) ns.devices.Set(&device.Device{ - DevAddr: devAddr3, - AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), - DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 6, 7, 8)), + DevAddr: getDevAddr(2, 2, 3, 5), + AppEUI: types.AppEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8)), + DevEUI: types.DevEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8)), NwkSKey: nwkSKey, FCntUp: (2 << 16) - 1, Options: device.Options{ Uses32BitFCnt: true, }, }) + defer func() { + ns.devices.Delete(types.AppEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8)), types.DevEUI(getEUI(2, 2, 3, 4, 5, 3, 7, 8))) + }() + devAddr4 := getDevAddr(2, 2, 3, 5) res, err = ns.HandleGetDevices(&pb.DevicesRequest{ - DevAddr: &devAddr3, + DevAddr: &devAddr4, FCnt: 5, }) a.So(err, ShouldBeNil) diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index e9bcf67f0..f12a3dc6b 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -86,55 +86,29 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev if dev == nil { dev = new(device.Device) + } else { + dev.StartUpdate() } - fields := []string{} - if dev.AppID != in.AppId { - dev.AppID = in.AppId - fields = append(fields, "app_id") - } - - if dev.AppEUI != *in.AppEui { - dev.AppEUI = *in.AppEui - fields = append(fields, "app_eui") - } - - if dev.DevID != in.DevId { - dev.DevID = in.DevId - fields = append(fields, "dev_id") - } - - if dev.DevEUI != *in.DevEui { - dev.DevEUI = *in.DevEui - fields = append(fields, "dev_eui") - } - - if dev.FCntUp != in.FCntUp { - dev.FCntUp = in.FCntUp - fields = append(fields, "f_cnt_up") - } - - if dev.FCntDown != in.FCntDown { - dev.FCntDown = in.FCntDown - fields = append(fields, "f_cnt_down") - } + dev.AppID = in.AppId + dev.AppEUI = *in.AppEui + dev.DevID = in.DevId + dev.DevEUI = *in.DevEui + dev.FCntUp = in.FCntUp + dev.FCntDown = in.FCntDown - if dev.Options.DisableFCntCheck != in.DisableFCntCheck || dev.Options.Uses32BitFCnt != in.Uses32BitFCnt || dev.Options.ActivationConstraints != in.ActivationConstraints { - dev.Options = device.Options{ - DisableFCntCheck: in.DisableFCntCheck, - Uses32BitFCnt: in.Uses32BitFCnt, - ActivationConstraints: in.ActivationConstraints, - } - fields = append(fields, "options") + dev.Options = device.Options{ + DisableFCntCheck: in.DisableFCntCheck, + Uses32BitFCnt: in.Uses32BitFCnt, + ActivationConstraints: in.ActivationConstraints, } if in.NwkSKey != nil && in.DevAddr != nil { dev.DevAddr = *in.DevAddr dev.NwkSKey = *in.NwkSKey - fields = append(fields, "dev_addr", "nwk_s_key") } - err = n.networkServer.devices.Set(dev, fields...) + err = n.networkServer.devices.Set(dev) if err != nil { return nil, errors.BuildGRPCError(err) } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 0cc08ac0a..2410236ce 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -11,7 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) // NetworkServer implements LoRaWAN-specific functionality for TTN @@ -32,7 +32,7 @@ type NetworkServer interface { // NewRedisNetworkServer creates a new Redis-backed NetworkServer func NewRedisNetworkServer(client *redis.Client, netID int) NetworkServer { ns := &networkServer{ - devices: device.NewRedisDeviceStore(client), + devices: device.NewRedisDeviceStore(client, "ns"), prefixes: map[types.DevAddrPrefix][]string{}, } ns.netID = [3]byte{byte(netID >> 16), byte(netID >> 8), byte(netID)} diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 3194181cb..7ab378e60 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -6,11 +6,9 @@ package networkserver import ( "testing" - "gopkg.in/redis.v3" - - . "github.com/smartystreets/assertions" - "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" + "gopkg.in/redis.v4" ) func getDevAddr(bytes ...byte) (addr types.DevAddr) { diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go index 3f5d0b45f..838949818 100644 --- a/core/networkserver/uplink.go +++ b/core/networkserver/uplink.go @@ -17,6 +17,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag if err != nil { return nil, err } + dev.StartUpdate() // Unmarshal LoRaWAN Payload var phyPayload lorawan.PHYPayload @@ -36,7 +37,7 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag dev.FCntUp = macPayload.FHDR.FCnt } dev.LastSeen = time.Now() - err = n.devices.Set(dev, "f_cnt_up", "last_seen") + err = n.devices.Set(dev) if err != nil { return nil, err } diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go index d7f946cc3..7c99af309 100644 --- a/core/networkserver/uplink_test.go +++ b/core/networkserver/uplink_test.go @@ -13,6 +13,7 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" ) @@ -20,7 +21,7 @@ import ( func TestHandleUplink(t *testing.T) { a := New(t) ns := &networkServer{ - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-uplink"), } appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) @@ -41,6 +42,9 @@ func TestHandleUplink(t *testing.T) { AppEUI: appEUI, DevEUI: devEUI, }) + defer func() { + ns.devices.Delete(appEUI, devEUI) + }() // Invalid Payload message = &pb_broker.DeduplicatedUplinkMessage{ From bd85ee0fc1d0c055b6314246ab2664083b21a8d0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 17 Oct 2016 14:19:50 +0200 Subject: [PATCH 1984/2266] Use Redis in Handler --- cmd/handler.go | 4 +- core/handler/activation.go | 3 +- core/handler/activation_test.go | 18 +- core/handler/application/application.go | 105 +++------- core/handler/application/application_test.go | 43 ++-- core/handler/application/store.go | 164 ++++----------- core/handler/application/store_test.go | 103 +++++---- core/handler/convert_fields_test.go | 45 ++-- core/handler/convert_lorawan_test.go | 10 +- core/handler/device/device.go | 210 +++---------------- core/handler/device/device_test.go | 76 ++----- core/handler/device/store.go | 201 +++++------------- core/handler/device/store_test.go | 150 +++++++------ core/handler/downlink.go | 12 +- core/handler/downlink_test.go | 15 +- core/handler/dry_run_test.go | 12 +- core/handler/handler.go | 6 +- core/handler/manager_server.go | 58 +---- core/handler/mqtt_test.go | 5 +- core/handler/uplink.go | 3 +- core/handler/uplink_test.go | 46 ++-- 21 files changed, 441 insertions(+), 848 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index 8058c4bab..4197e0402 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -11,7 +11,7 @@ import ( "syscall" "google.golang.org/grpc" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler" @@ -41,7 +41,7 @@ var handlerCmd = &cobra.Command{ client := redis.NewClient(&redis.Options{ Addr: viper.GetString("handler.redis-address"), Password: "", // no password set - DB: int64(viper.GetInt("handler.redis-db")), + DB: viper.GetInt("handler.redis-db"), }) connectRedis(client) diff --git a/core/handler/activation.go b/core/handler/activation.go index 1b8ccdbfb..4f81473ac 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -208,12 +208,13 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv } // Update Device + dev.StartUpdate() dev.DevAddr = types.DevAddr(joinAccept.DevAddr) dev.AppSKey = appSKey dev.NwkSKey = nwkSKey dev.UsedAppNonces = append(dev.UsedAppNonces, appNonce) dev.UsedDevNonces = append(dev.UsedDevNonces, reqMAC.DevNonce) - err = h.devices.Set(dev, "dev_addr", "app_key", "app_s_key", "nwk_s_key", "used_app_nonces", "used_dev_nonces") // app_key is only needed when the default app_key is used to activate the device + err = h.devices.Set(dev) if err != nil { return nil, err } diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 79e776f9b..cddaf5ca8 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -4,8 +4,8 @@ package handler import ( - "sync" "testing" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -68,12 +68,12 @@ func TestHandleActivation(t *testing.T) { h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleActivation")}, - applications: application.NewApplicationStore(), - devices: device.NewDeviceStore(), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), } h.mqttActivation = make(chan *mqtt.Activation) h.mqttEvent = make(chan *mqttEvent, 10) - var wg sync.WaitGroup + var wg WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} appID := appEUI.String() @@ -86,6 +86,9 @@ func TestHandleActivation(t *testing.T) { h.applications.Set(&application.Application{ AppID: appID, }) + defer func() { + h.applications.Delete(appID) + }() h.devices.Set(&device.Device{ AppID: appID, @@ -94,6 +97,9 @@ func TestHandleActivation(t *testing.T) { DevEUI: devEUI, AppKey: appKey, }) + defer func() { + h.devices.Delete(appID, devID) + }() // Unknown res, err := doTestHandleActivation(h, @@ -131,7 +137,7 @@ func TestHandleActivation(t *testing.T) { a.So(err, ShouldBeNil) a.So(res, ShouldNotBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) // Same DevNonce used twice res, err = doTestHandleActivation(h, @@ -159,7 +165,7 @@ func TestHandleActivation(t *testing.T) { a.So(err, ShouldBeNil) a.So(res, ShouldNotBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) // TODO: Validate response diff --git a/core/handler/application/application.go b/core/handler/application/application.go index 2617ee508..08b6db7c6 100644 --- a/core/handler/application/application.go +++ b/core/handler/application/application.go @@ -4,104 +4,55 @@ package application import ( - "fmt" + "reflect" "time" + + "github.com/fatih/structs" ) // Application contains the state of an application type Application struct { - AppID string + old *Application + AppID string `redis:"app_id"` // Decoder is a JavaScript function that accepts the payload as byte array and // returns an object containing the decoded values - Decoder string + Decoder string `redis:"decoder"` // Converter is a JavaScript function that accepts the data as decoded by // Decoder and returns an object containing the converted values - Converter string + Converter string `redis:"converter"` // Validator is a JavaScript function that validates the data is converted by // Converter and returns a boolean value indicating the validity of the data - Validator string + Validator string `redis:"validator"` // Encoder is a JavaScript function that encode the data send on Downlink messages // Returns an object containing the converted values in []byte - Encoder string - - UpdatedAt time.Time -} + Encoder string `redis:"encoder"` -// ApplicationProperties contains all properties of a Application that can be stored in Redis. -var ApplicationProperties = []string{ - "app_id", - "decoder", - "converter", - "validator", - "encoder", - "updated_at", + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` } -// ToStringStringMap converts the given properties of an Application to a -// map[string]string for storage in Redis. -func (application *Application) ToStringStringMap(properties ...string) (map[string]string, error) { - output := make(map[string]string) - for _, p := range properties { - property, err := application.formatProperty(p) - if err != nil { - return output, err - } - output[p] = property - } - return output, nil +// StartUpdate stores the state of the device +func (a *Application) StartUpdate() { + old := *a + a.old = &old } -// FromStringStringMap imports known values from the input to an Application. -func (application *Application) FromStringStringMap(input map[string]string) error { - for k, v := range input { - application.parseProperty(k, v) +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (a Application) ChangedFields() (changed []string) { + new := structs.New(a) + fields := new.Names() + if a.old == nil { + return fields } - return nil -} + old := structs.New(*a.old) -func (application *Application) formatProperty(property string) (formatted string, err error) { - switch property { - case "app_id": - formatted = application.AppID - case "decoder": - formatted = application.Decoder - case "converter": - formatted = application.Converter - case "validator": - formatted = application.Validator - case "encoder": - formatted = application.Encoder - case "updated_at": - if !application.UpdatedAt.IsZero() { - formatted = application.UpdatedAt.UTC().Format(time.RFC3339Nano) + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue } - default: - err = fmt.Errorf("Property %s does not exist in Application", property) - } - return -} - -func (application *Application) parseProperty(property string, value string) error { - if value == "" { - return nil - } - switch property { - case "app_id": - application.AppID = value - case "decoder": - application.Decoder = value - case "converter": - application.Converter = value - case "validator": - application.Validator = value - case "encoder": - application.Encoder = value - case "updated_at": - val, err := time.Parse(time.RFC3339Nano, value) - if err != nil { - return err + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) } - application.UpdatedAt = val } - return nil + return } diff --git a/core/handler/application/application_test.go b/core/handler/application/application_test.go index acd356e36..149857b61 100644 --- a/core/handler/application/application_test.go +++ b/core/handler/application/application_test.go @@ -9,36 +9,23 @@ import ( . "github.com/smartystreets/assertions" ) -func getTestApplication() (application *Application, dmap map[string]string) { - return &Application{ - AppID: "AppID-1", - Decoder: `function (payload) { return { size: payload.length; } }`, - Converter: `function (data) { return data; }`, - Validator: `function (data) { return data.size % 2 == 0; }`, - Encoder: `function (payload){return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]; }`, - }, map[string]string{ - "app_id": "AppID-1", - "decoder": `function (payload) { return { size: payload.length; } }`, - "converter": `function (data) { return data; }`, - "validator": `function (data) { return data.size % 2 == 0; }`, - "encoder": `function (payload){return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0]; }`, - "updated_at": "", - } -} - -func TestToStringMap(t *testing.T) { +func TestApplicationUpdate(t *testing.T) { a := New(t) - application, expected := getTestApplication() - dmap, err := application.ToStringStringMap(ApplicationProperties...) - a.So(err, ShouldBeNil) - a.So(dmap, ShouldResemble, expected) + application := &Application{ + AppID: "App", + } + application.StartUpdate() + a.So(application.old.AppID, ShouldEqual, application.AppID) } -func TestFromStringMap(t *testing.T) { +func TestApplicationChangedFields(t *testing.T) { a := New(t) - application := &Application{} - expected, dmap := getTestApplication() - err := application.FromStringStringMap(dmap) - a.So(err, ShouldBeNil) - a.So(application, ShouldResemble, expected) + application := &Application{ + AppID: "Application", + } + application.StartUpdate() + application.AppID = "NewAppID" + + a.So(application.ChangedFields(), ShouldHaveLength, 1) + a.So(application.ChangedFields(), ShouldContain, "AppID") } diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 5ca4b747d..4a1a16817 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -4,161 +4,89 @@ package application import ( - "fmt" - "sort" "time" + "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) -// Store is used to store application configurations +// Store interface for Applications type Store interface { - // List all applications List() ([]*Application, error) - // Get the full information about a application Get(appID string) (*Application, error) - // Set the given fields of a application. If fields empty, it sets all fields. - Set(application *Application, fields ...string) error - // Delete a application - Delete(appid string) error + Set(new *Application, properties ...string) (err error) + Delete(appID string) error } -// NewApplicationStore creates a new in-memory Application store -func NewApplicationStore() Store { - return &applicationStore{ - applications: make(map[string]*Application), - } -} +const defaultRedisPrefix = "handler" +const redisApplicationPrefix = "application" -// applicationStore is an in-memory Application store. It should only be used for testing -// purposes. Use the redisApplicationStore for actual deployments. -type applicationStore struct { - applications map[string]*Application -} - -func (s *applicationStore) List() ([]*Application, error) { - apps := make([]*Application, 0, len(s.applications)) - for _, application := range s.applications { - apps = append(apps, application) +// NewRedisApplicationStore creates a new Redis-based Application store +// if an empty prefix is passed, a default prefix will be used. +func NewRedisApplicationStore(client *redis.Client, prefix string) Store { + if prefix == "" { + prefix = defaultRedisPrefix } - return apps, nil -} - -func (s *applicationStore) Get(appID string) (*Application, error) { - if app, ok := s.applications[appID]; ok { - return app, nil - } - return nil, errors.NewErrNotFound(fmt.Sprintf("%s", appID)) -} - -func (s *applicationStore) Set(new *Application, fields ...string) error { - // NOTE: We don't care about fields for testing - s.applications[new.AppID] = new - return nil -} - -func (s *applicationStore) Delete(appID string) error { - delete(s.applications, appID) - return nil -} - -// NewRedisApplicationStore creates a new Redis-based status store -func NewRedisApplicationStore(client *redis.Client) Store { - return &redisApplicationStore{ - client: client, + store := storage.NewRedisMapStore(client, prefix+":"+redisApplicationPrefix) + store.SetBase(Application{}, "") + return &RedisApplicationStore{ + store: store, } } -const redisApplicationPrefix = "handler:application" - -type redisApplicationStore struct { - client *redis.Client +// RedisApplicationStore stores Applications in Redis. +// - Applications are stored as a Hash +type RedisApplicationStore struct { + store *storage.RedisMapStore } -func (s *redisApplicationStore) List() ([]*Application, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisApplicationPrefix)).Result() - if err != nil { - return nil, err - } - - sort.Strings(keys) - - pipe := s.client.Pipeline() - defer pipe.Close() - - // Add all commands to pipeline - cmds := make(map[string]*redis.StringStringMapCmd) - for _, key := range keys { - cmds[key] = pipe.HGetAllMap(key) - } - - // Execute pipeline - _, err = pipe.Exec() +// List all Applications +func (s *RedisApplicationStore) List() ([]*Application, error) { + applicationsI, err := s.store.List("", nil) if err != nil { return nil, err } - - // Get all results from pipeline - applications := make([]*Application, 0, len(keys)) - for _, cmd := range cmds { - dmap, err := cmd.Result() - if err == nil { - application := &Application{} - err := application.FromStringStringMap(dmap) - if err == nil { - applications = append(applications, application) - } + applications := make([]*Application, 0, len(applicationsI)) + for _, applicationI := range applicationsI { + if application, ok := applicationI.(Application); ok { + applications = append(applications, &application) } } - return applications, nil } -func (s *redisApplicationStore) Get(appID string) (*Application, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s", redisApplicationPrefix, appID)).Result() +// Get a specific Application +func (s *RedisApplicationStore) Get(appID string) (*Application, error) { + applicationI, err := s.store.Get(appID) if err != nil { - if err == redis.Nil { - return nil, errors.NewErrNotFound(fmt.Sprintf("%s", appID)) - } return nil, err - } else if len(res) == 0 { - return nil, errors.NewErrNotFound(fmt.Sprintf("%s", appID)) } - application := &Application{} - err = application.FromStringStringMap(res) - if err != nil { - return nil, err + if application, ok := applicationI.(Application); ok { + return &application, nil } - return application, nil + return nil, errors.New("Database did not return a Application") } -func (s *redisApplicationStore) Set(new *Application, fields ...string) error { - if len(fields) == 0 { - fields = ApplicationProperties - } else { - fields = append(fields, "updated_at") - } +// Set a new Application or update an existing one +func (s *RedisApplicationStore) Set(new *Application, properties ...string) (err error) { + now := time.Now() + new.UpdatedAt = now - key := fmt.Sprintf("%s:%s", redisApplicationPrefix, new.AppID) - new.UpdatedAt = time.Now() - dmap, err := new.ToStringStringMap(fields...) - if err != nil { - return err + if new.old != nil { + err = s.store.Update(new.AppID, *new, properties...) + } else { + new.CreatedAt = now + err = s.store.Create(new.AppID, *new, properties...) } - err = s.client.HMSetMap(key, dmap).Err() if err != nil { - return err + return } return nil } -func (s *redisApplicationStore) Delete(appID string) error { - key := fmt.Sprintf("%s:%s", redisApplicationPrefix, appID) - err := s.client.Del(key).Err() - if err != nil { - return err - } - return nil +// Delete an Application +func (s *RedisApplicationStore) Delete(appID string) error { + return s.store.Delete(appID) } diff --git a/core/handler/application/store_test.go b/core/handler/application/store_test.go index 8180ee2f9..7b3b5170a 100644 --- a/core/handler/application/store_test.go +++ b/core/handler/application/store_test.go @@ -4,69 +4,68 @@ package application import ( - "fmt" - "os" "testing" - "gopkg.in/redis.v3" - + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB - }) -} - func TestApplicationStore(t *testing.T) { a := New(t) - stores := map[string]Store{ - "local": NewApplicationStore(), - "redis": NewRedisApplicationStore(getRedisClient()), - } - - for name, s := range stores { - - t.Logf("Testing %s store", name) - - appID := "AppID-1" + NewRedisApplicationStore(GetRedisClient(), "") - // Get non-existing - app, err := s.Get(appID) - a.So(err, ShouldNotBeNil) - a.So(app, ShouldBeNil) + s := NewRedisApplicationStore(GetRedisClient(), "handler-test-application-store") - // Create - err = s.Set(&Application{ - AppID: appID, - }) - a.So(err, ShouldBeNil) + appID := "AppID-1" - // Get existing - app, err = s.Get(appID) - a.So(err, ShouldBeNil) - a.So(app, ShouldNotBeNil) + // Get non-existing + app, err := s.Get(appID) + a.So(err, ShouldNotBeNil) + a.So(app, ShouldBeNil) - // List - apps, err := s.List() - a.So(err, ShouldBeNil) - a.So(apps, ShouldHaveLength, 1) - - // Delete - err = s.Delete(appID) - a.So(err, ShouldBeNil) - - // Get deleted - app, err = s.Get(appID) - a.So(err, ShouldNotBeNil) - a.So(app, ShouldBeNil) + // Create + app = &Application{ + AppID: appID, + Encoder: "encoder", } + err = s.Set(app) + defer func() { + s.Delete(appID) + }() + a.So(err, ShouldBeNil) + + // Get existing + app, err = s.Get(appID) + a.So(err, ShouldBeNil) + a.So(app, ShouldNotBeNil) + a.So(app.Encoder, ShouldEqual, "encoder") + + // Update + err = s.Set(&Application{ + old: app, + AppID: appID, + Encoder: "new encoder", + }) + a.So(err, ShouldBeNil) + + // Get existing + app, err = s.Get(appID) + a.So(err, ShouldBeNil) + a.So(app, ShouldNotBeNil) + a.So(app.Encoder, ShouldEqual, "new encoder") + + // List + apps, err := s.List() + a.So(err, ShouldBeNil) + a.So(apps, ShouldHaveLength, 1) + + // Delete + err = s.Delete(appID) + a.So(err, ShouldBeNil) + + // Get deleted + app, err = s.Get(appID) + a.So(err, ShouldNotBeNil) + a.So(app, ShouldBeNil) } diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index fc131e6f8..ae750b9dc 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -17,14 +17,14 @@ import ( . "github.com/smartystreets/assertions" ) -func buildConversionUplink() (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { +func buildConversionUplink(appID string) (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { ttnUp := &pb_broker.DeduplicatedUplinkMessage{ - AppId: "AppID-1", + AppId: appID, DevId: "DevID-1", } appUp := &mqtt.UplinkMessage{ FPort: 1, - AppID: "AppID-1", + AppID: appID, DevID: "DevID-1", PayloadRaw: []byte{0x08, 0x70}, } @@ -36,21 +36,25 @@ func TestConvertFieldsUp(t *testing.T) { appID := "AppID-1" h := &handler{ - applications: application.NewApplicationStore(), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-up"), } // No functions - ttnUp, appUp := buildConversionUplink() + ttnUp, appUp := buildConversionUplink(appID) err := h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.PayloadFields, ShouldBeEmpty) // Normal flow - h.applications.Set(&application.Application{ + app := &application.Application{ AppID: appID, Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, - }) - ttnUp, appUp = buildConversionUplink() + } + a.So(h.applications.Set(app), ShouldBeNil) + defer func() { + h.applications.Delete(appID) + }() + ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) @@ -59,23 +63,19 @@ func TestConvertFieldsUp(t *testing.T) { }) // Invalidate data - h.applications.Set(&application.Application{ - AppID: appID, - Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, - Validator: `function(data) { return false; }`, - }) - ttnUp, appUp = buildConversionUplink() + app.StartUpdate() + app.Validator = `function(data) { return false; }` + h.applications.Set(app) + ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldNotBeNil) a.So(appUp.PayloadFields, ShouldBeEmpty) // Function error - h.applications.Set(&application.Application{ - AppID: appID, - Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, - Converter: `function(data) { throw "expected"; }`, - }) - ttnUp, appUp = buildConversionUplink() + app.StartUpdate() + app.Validator = `function(data) { throw "expected"; }` + h.applications.Set(app) + ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) a.So(err, ShouldBeNil) a.So(appUp.PayloadFields, ShouldBeEmpty) @@ -324,7 +324,7 @@ func TestConvertFieldsDown(t *testing.T) { appID := "AppID-1" h := &handler{ - applications: application.NewApplicationStore(), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-down"), } // Case1: No Encoder @@ -341,6 +341,9 @@ func TestConvertFieldsDown(t *testing.T) { return [ 1, 2, 3, 4, 5, 6, 7 ] }`, }) + defer func() { + h.applications.Delete(appID) + }() ttnDown, appDown = buildConversionDownlink() err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index 0fc2a0dcc..c81df3e1c 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -34,7 +34,7 @@ func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, * func TestConvertFromLoRaWAN(t *testing.T) { a := New(t) h := &handler{ - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-from-lorawan"), Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, mqttEvent: make(chan *mqttEvent, 10), } @@ -42,6 +42,9 @@ func TestConvertFromLoRaWAN(t *testing.T) { DevID: "devid", AppID: "appid", }) + defer func() { + h.devices.Delete("appid", "devid") + }() ttnUp, appUp := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x20, 0x01, 0x00, 0x0A, 0x46, 0x55, 0x96, 0x42, 0x92, 0xF2}) err := h.ConvertFromLoRaWAN(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) @@ -71,13 +74,16 @@ func buildLorawanDownlink(payload []byte) (*mqtt.DownlinkMessage, *pb_broker.Dow func TestConvertToLoRaWAN(t *testing.T) { a := New(t) h := &handler{ - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-to-lorawan"), Component: &core.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, } h.devices.Set(&device.Device{ DevID: "devid", AppID: "appid", }) + defer func() { + h.devices.Delete("appid", "devid") + }() appDown, ttnDown := buildLorawanDownlink([]byte{0xaa, 0xbc}) err := h.ConvertToLoRaWAN(h.Ctx, appDown, ttnDown) a.So(err, ShouldBeNil) diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 760a31815..783147b45 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -4,13 +4,12 @@ package device import ( - "encoding/json" - "fmt" - "strings" + "reflect" "time" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/fatih/structs" ) type DevNonce [2]byte @@ -18,188 +17,45 @@ type AppNonce [3]byte // Device contains the state of a device type Device struct { - DevEUI types.DevEUI - AppEUI types.AppEUI - AppID string - DevID string - DevAddr types.DevAddr - AppKey types.AppKey - UsedDevNonces []DevNonce - UsedAppNonces []AppNonce - NwkSKey types.NwkSKey - AppSKey types.AppSKey - NextDownlink *mqtt.DownlinkMessage - UpdatedAt time.Time + old *Device + DevEUI types.DevEUI `redis:"dev_eui"` + AppEUI types.AppEUI `redis:"app_eui"` + AppID string `redis:"app_id"` + DevID string `redis:"dev_id"` + DevAddr types.DevAddr `redis:"dev_addr"` + AppKey types.AppKey `redis:"app_key"` + UsedDevNonces []DevNonce `redis:"used_dev_nonces"` + UsedAppNonces []AppNonce `redis:"used_app_nonces"` + NwkSKey types.NwkSKey `redis:"nwk_s_key"` + AppSKey types.AppSKey `redis:"app_s_key"` + NextDownlink *mqtt.DownlinkMessage `redis:"next_downlink"` + + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` } -// DeviceProperties contains all properties of a Device that can be stored in Redis. -var DeviceProperties = []string{ - "dev_eui", - "app_eui", - "app_id", - "dev_id", - "dev_addr", - "app_key", - "nwk_s_key", - "app_s_key", - "used_dev_nonces", - "used_app_nonces", - "next_downlink", - "updated_at", +// StartUpdate stores the state of the device +func (d *Device) StartUpdate() { + old := *d + d.old = &old } -// ToStringStringMap converts the given properties of Device to a -// map[string]string for storage in Redis. -func (device *Device) ToStringStringMap(properties ...string) (map[string]string, error) { - output := make(map[string]string) - for _, p := range properties { - property, err := device.formatProperty(p) - if err != nil { - return output, err - } - output[p] = property +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (d Device) ChangedFields() (changed []string) { + new := structs.New(d) + fields := new.Names() + if d.old == nil { + return fields } - return output, nil -} - -// FromStringStringMap imports known values from the input to a Device. -func (device *Device) FromStringStringMap(input map[string]string) error { - for k, v := range input { - device.parseProperty(k, v) - } - return nil -} + old := structs.New(*d.old) -func (device *Device) formatProperty(property string) (formatted string, err error) { - switch property { - case "dev_eui": - formatted = device.DevEUI.String() - case "app_eui": - formatted = device.AppEUI.String() - case "app_id": - formatted = device.AppID - case "dev_id": - formatted = device.DevID - case "dev_addr": - formatted = device.DevAddr.String() - case "app_key": - formatted = device.AppKey.String() - case "nwk_s_key": - formatted = device.NwkSKey.String() - case "app_s_key": - formatted = device.AppSKey.String() - case "used_dev_nonces": - nonces := make([]string, 0, len(device.UsedDevNonces)) - for _, nonce := range device.UsedDevNonces { - nonces = append(nonces, fmt.Sprintf("%X", nonce)) + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue } - formatted = strings.Join(nonces, ",") - case "used_app_nonces": - nonces := make([]string, 0, len(device.UsedAppNonces)) - for _, nonce := range device.UsedAppNonces { - nonces = append(nonces, fmt.Sprintf("%X", nonce)) + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) } - formatted = strings.Join(nonces, ",") - case "next_downlink": - var jsonBytes []byte - jsonBytes, err = json.Marshal(device.NextDownlink) - formatted = string(jsonBytes) - case "updated_at": - if !device.UpdatedAt.IsZero() { - formatted = device.UpdatedAt.UTC().Format(time.RFC3339Nano) - } - default: - err = fmt.Errorf("Property %s does not exist in Device", property) } return } - -func (device *Device) parseProperty(property string, value string) error { - if value == "" { - return nil - } - switch property { - case "dev_eui": - val, err := types.ParseDevEUI(value) - if err != nil { - return err - } - device.DevEUI = val - case "app_eui": - val, err := types.ParseAppEUI(value) - if err != nil { - return err - } - device.AppEUI = val - case "app_id": - device.AppID = value - case "dev_id": - device.DevID = value - case "dev_addr": - val, err := types.ParseDevAddr(value) - if err != nil { - return err - } - device.DevAddr = val - case "app_key": - val, err := types.ParseAppKey(value) - if err != nil { - return err - } - device.AppKey = val - case "nwk_s_key": - val, err := types.ParseNwkSKey(value) - if err != nil { - return err - } - device.NwkSKey = val - case "app_s_key": - val, err := types.ParseAppSKey(value) - if err != nil { - return err - } - device.AppSKey = val - case "used_dev_nonces": - nonceStrs := strings.Split(value, ",") - nonces := make([]DevNonce, 0, len(nonceStrs)) - for _, nonceStr := range nonceStrs { - var nonce DevNonce - nonceBytes, err := types.ParseHEX(nonceStr, 2) - if err != nil { - return err - } - copy(nonce[:], nonceBytes) - nonces = append(nonces, nonce) - } - device.UsedDevNonces = nonces - case "used_app_nonces": - nonceStrs := strings.Split(value, ",") - nonces := make([]AppNonce, 0, len(nonceStrs)) - for _, nonceStr := range nonceStrs { - var nonce AppNonce - nonceBytes, err := types.ParseHEX(nonceStr, 3) - if err != nil { - return err - } - copy(nonce[:], nonceBytes) - nonces = append(nonces, nonce) - } - device.UsedAppNonces = nonces - case "next_downlink": - if value != "null" { - nextDownlink := &mqtt.DownlinkMessage{} - err := json.Unmarshal([]byte(value), nextDownlink) - if err != nil { - return err - } - device.NextDownlink = nextDownlink - } - case "updated_at": - val, err := time.Parse(time.RFC3339Nano, value) - if err != nil { - return err - } - device.UpdatedAt = val - } - return nil -} diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index 067196d11..cdaae2951 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -6,76 +6,26 @@ package device import ( "testing" - "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/smartystreets/assertions" ) -func getTestDevice() (device *Device, dmap map[string]string) { - return &Device{ - DevEUI: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), - AppEUI: types.AppEUI([8]byte{8, 7, 6, 5, 4, 3, 2, 1}), - AppID: "AppID-1", - DevID: "DevID-1", - DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), - AppKey: types.AppKey([16]byte{0, 1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8}), - NwkSKey: types.NwkSKey([16]byte{1, 1, 1, 2, 1, 3, 1, 4, 1, 5, 1, 6, 1, 7, 1, 8}), - AppSKey: types.AppSKey([16]byte{2, 1, 2, 2, 2, 3, 2, 4, 2, 5, 2, 6, 2, 7, 2, 8}), - UsedDevNonces: []DevNonce{DevNonce{1, 2}, DevNonce{3, 4}}, - UsedAppNonces: []AppNonce{AppNonce{1, 2, 3}, AppNonce{4, 5, 6}}, - }, map[string]string{ - "dev_eui": "0102030405060708", - "app_eui": "0807060504030201", - "app_id": "AppID-1", - "dev_id": "DevID-1", - "dev_addr": "01020304", - "app_key": "00010002000300040005000600070008", - "nwk_s_key": "01010102010301040105010601070108", - "app_s_key": "02010202020302040205020602070208", - "used_dev_nonces": "0102,0304", - "used_app_nonces": "010203,040506", - "next_downlink": "null", - "updated_at": "", - } -} - -func TestToStringMap(t *testing.T) { - a := New(t) - device, expected := getTestDevice() - dmap, err := device.ToStringStringMap(DeviceProperties...) - a.So(err, ShouldBeNil) - a.So(dmap, ShouldResemble, expected) -} - -func TestFromStringMap(t *testing.T) { +func TestDeviceUpdate(t *testing.T) { a := New(t) - device := &Device{} - expected, dmap := getTestDevice() - err := device.FromStringStringMap(dmap) - a.So(err, ShouldBeNil) - a.So(device, ShouldResemble, expected) + device := &Device{ + DevID: "Device", + } + device.StartUpdate() + a.So(device.old.DevID, ShouldEqual, device.DevID) } -func TestNextDownlink(t *testing.T) { +func TestDeviceChangedFields(t *testing.T) { a := New(t) - dev := &Device{ - NextDownlink: &mqtt.DownlinkMessage{ - PayloadFields: map[string]interface{}{ - "string": "hello!", - "int": 42, - "bool": true, - }, - }, + device := &Device{ + DevID: "Device", } - formatted, err := dev.formatProperty("next_downlink") - a.So(err, ShouldBeNil) - a.So(formatted, ShouldContainSubstring, `"payload_fields":{"bool":true,"int":42,"string":"hello!"}`) + device.StartUpdate() + device.DevID = "NewDevID" - dev = &Device{} - err = dev.parseProperty("next_downlink", `{"payload_fields":{"bool":true,"int":42,"string":"hello!"}}`) - a.So(err, ShouldBeNil) - a.So(dev.NextDownlink.PayloadFields, ShouldNotBeNil) - a.So(dev.NextDownlink.PayloadFields["bool"], ShouldBeTrue) - a.So(dev.NextDownlink.PayloadFields["int"], ShouldEqual, 42) - a.So(dev.NextDownlink.PayloadFields["string"], ShouldEqual, "hello!") + a.So(device.ChangedFields(), ShouldHaveLength, 1) + a.So(device.ChangedFields(), ShouldContain, "DevID") } diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 7c68c4957..100ee7821 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -5,196 +5,107 @@ package device import ( "fmt" - "sort" "time" + "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) -// Store is used to store device configurations +// Store interface for Devices type Store interface { - // List all devices List() ([]*Device, error) - // ListForApp lists all devices for an app ListForApp(appID string) ([]*Device, error) - // Get the full information about a device Get(appID, devID string) (*Device, error) - // Set the given fields of a device. If fields empty, it sets all fields. - Set(device *Device, fields ...string) error - // Delete a device + Set(new *Device, properties ...string) (err error) Delete(appID, devID string) error } -// NewDeviceStore creates a new in-memory Device store -func NewDeviceStore() Store { - return &deviceStore{ - devices: make(map[string]map[string]*Device), - } -} - -// deviceStore is an in-memory Device store. It should only be used for testing -// purposes. Use the redisDeviceStore for actual deployments. -type deviceStore struct { - devices map[string]map[string]*Device -} - -func (s *deviceStore) List() ([]*Device, error) { - devices := make([]*Device, 0, len(s.devices)) - for _, app := range s.devices { - for _, device := range app { - devices = append(devices, device) - } - } - return devices, nil -} - -func (s *deviceStore) ListForApp(appID string) ([]*Device, error) { - devices := make([]*Device, 0, len(s.devices)) - if app, ok := s.devices[appID]; ok { - for _, device := range app { - devices = append(devices, device) - } - } - return devices, nil -} +const defaultRedisPrefix = "handler" +const redisDevicePrefix = "device" -func (s *deviceStore) Get(appID, devID string) (*Device, error) { - if app, ok := s.devices[appID]; ok { - if dev, ok := app[devID]; ok { - return dev, nil - } +// NewRedisDeviceStore creates a new Redis-based Device store +func NewRedisDeviceStore(client *redis.Client, prefix string) *RedisDeviceStore { + if prefix == "" { + prefix = defaultRedisPrefix } - return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) -} - -func (s *deviceStore) Set(new *Device, fields ...string) error { - // NOTE: We don't care about fields for testing - if app, ok := s.devices[new.AppID]; ok { - app[new.DevID] = new - } else { - s.devices[new.AppID] = map[string]*Device{new.DevID: new} + store := storage.NewRedisMapStore(client, prefix+":"+redisDevicePrefix) + store.SetBase(Device{}, "") + return &RedisDeviceStore{ + store: store, } - return nil } -func (s *deviceStore) Delete(appID, devID string) error { - if app, ok := s.devices[appID]; ok { - delete(app, devID) - } - return nil -} - -// NewRedisDeviceStore creates a new Redis-based status store -func NewRedisDeviceStore(client *redis.Client) Store { - return &redisDeviceStore{ - client: client, - } +// RedisDeviceStore stores Devices in Redis. +// - Devices are stored as a Hash +type RedisDeviceStore struct { + store *storage.RedisMapStore } -const redisDevicePrefix = "handler:device" - -type redisDeviceStore struct { - client *redis.Client -} - -func (s *redisDeviceStore) getForKeys(keys []string) ([]*Device, error) { - sort.Strings(keys) - - pipe := s.client.Pipeline() - defer pipe.Close() - - // Add all commands to pipeline - cmds := make(map[string]*redis.StringStringMapCmd) - for _, key := range keys { - cmds[key] = pipe.HGetAllMap(key) - } - - // Execute pipeline - _, err := pipe.Exec() +// List all Devices +func (s *RedisDeviceStore) List() ([]*Device, error) { + devicesI, err := s.store.List("", nil) if err != nil { return nil, err } - - // Get all results from pipeline - devices := make([]*Device, 0, len(keys)) - for _, cmd := range cmds { - dmap, err := cmd.Result() - if err == nil { - device := &Device{} - err := device.FromStringStringMap(dmap) - if err == nil { - devices = append(devices, device) - } + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) } } - return devices, nil } -func (s *redisDeviceStore) List() ([]*Device, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisDevicePrefix)).Result() +// ListForApp lists all devices for a specific Application +func (s *RedisDeviceStore) ListForApp(appID string) ([]*Device, error) { + devicesI, err := s.store.List(fmt.Sprintf("%s:*", appID), nil) if err != nil { return nil, err } - return s.getForKeys(keys) -} - -func (s *redisDeviceStore) ListForApp(appID string) ([]*Device, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:%s:*", redisDevicePrefix, appID)).Result() - if err != nil { - return nil, err + devices := make([]*Device, 0, len(devicesI)) + for _, deviceI := range devicesI { + if device, ok := deviceI.(Device); ok { + devices = append(devices, &device) + } } - return s.getForKeys(keys) + return devices, nil } -func (s *redisDeviceStore) Get(appID, devID string) (*Device, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appID, devID)).Result() +// Get a specific Device +func (s *RedisDeviceStore) Get(appID, devID string) (*Device, error) { + deviceI, err := s.store.Get(fmt.Sprintf("%s:%s", appID, devID)) if err != nil { - if err == redis.Nil { - return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) - } return nil, err - } else if len(res) == 0 { - return nil, errors.NewErrNotFound(fmt.Sprintf("%s/%s", appID, devID)) } - device := &Device{} - err = device.FromStringStringMap(res) - if err != nil { - return nil, err + if device, ok := deviceI.(Device); ok { + return &device, nil } - return device, nil + return nil, errors.New("Database did not return a Device") } -func (s *redisDeviceStore) Set(new *Device, fields ...string) error { - if len(fields) == 0 { - fields = DeviceProperties - } else { - fields = append(fields, "updated_at") - } +// Set a new Device or update an existing one +func (s *RedisDeviceStore) Set(new *Device, properties ...string) (err error) { - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, new.AppID, new.DevID) - new.UpdatedAt = time.Now() - dmap, err := new.ToStringStringMap(fields...) - if err != nil { - return err - } - if len(dmap) == 0 { - return nil + now := time.Now() + new.UpdatedAt = now + + key := fmt.Sprintf("%s:%s", new.AppID, new.DevID) + if new.old != nil { + err = s.store.Update(key, *new, properties...) + } else { + new.CreatedAt = now + err = s.store.Create(key, *new, properties...) } - err = s.client.HMSetMap(key, dmap).Err() if err != nil { - return err + return } return nil } -func (s *redisDeviceStore) Delete(appID, devID string) error { - key := fmt.Sprintf("%s:%s:%s", redisDevicePrefix, appID, devID) - err := s.client.Del(key).Err() - if err != nil { - return err - } - return nil +// Delete a Device +func (s *RedisDeviceStore) Delete(appID, devID string) error { + key := fmt.Sprintf("%s:%s", appID, devID) + return s.store.Delete(key) } diff --git a/core/handler/device/store_test.go b/core/handler/device/store_test.go index 7736b2bf4..5b2eede2f 100644 --- a/core/handler/device/store_test.go +++ b/core/handler/device/store_test.go @@ -4,86 +4,98 @@ package device import ( - "fmt" - "os" "testing" - "gopkg.in/redis.v3" - "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB - }) -} - func TestDeviceStore(t *testing.T) { a := New(t) - stores := map[string]Store{ - "local": NewDeviceStore(), - "redis": NewRedisDeviceStore(getRedisClient()), + NewRedisDeviceStore(GetRedisClient(), "") + + s := NewRedisDeviceStore(GetRedisClient(), "handler-test-device-store") + + // Get non-existing + dev, err := s.Get("AppID-1", "DevID-1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + devs, err := s.ListForApp("AppID-1") + a.So(err, ShouldBeNil) + a.So(devs, ShouldHaveLength, 0) + + // Create + err = s.Set(&Device{ + DevAddr: types.DevAddr([4]byte{0, 0, 0, 1}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppID: "AppID-1", + DevID: "DevID-1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("AppID-1", "DevID-1") + }() + + // Get existing + dev, err = s.Get("AppID-1", "DevID-1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + devs, err = s.ListForApp("AppID-1") + a.So(err, ShouldBeNil) + a.So(devs, ShouldHaveLength, 1) + + // Create extra and update + dev = &Device{ + DevAddr: types.DevAddr([4]byte{0, 0, 0, 2}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), + AppID: "AppID-1", + DevID: "DevID-2", } + err = s.Set(dev) + a.So(err, ShouldBeNil) + + err = s.Set(&Device{ + old: dev, + DevAddr: types.DevAddr([4]byte{0, 0, 0, 3}), + DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 3}), + AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), + AppID: "AppID-1", + DevID: "DevID-2", + }) + a.So(err, ShouldBeNil) - for name, s := range stores { - - t.Logf("Testing %s store", name) - - // Get non-existing - dev, err := s.Get("AppID-1", "DevID-1") - a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) - - // Create - err = s.Set(&Device{ - DevAddr: types.DevAddr([4]byte{0, 0, 0, 1}), - DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - AppID: "AppID-1", - DevID: "DevID-1", - }) - a.So(err, ShouldBeNil) - - // Get existing - dev, err = s.Get("AppID-1", "DevID-1") - a.So(err, ShouldBeNil) - a.So(dev, ShouldNotBeNil) - - // Create extra - err = s.Set(&Device{ - DevAddr: types.DevAddr([4]byte{0, 0, 0, 2}), - DevEUI: types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 2}), - AppEUI: types.AppEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 1}), - AppID: "AppID-1", - DevID: "DevID-2", - }) - a.So(err, ShouldBeNil) - - // List - devices, err := s.List() - a.So(err, ShouldBeNil) - a.So(devices, ShouldHaveLength, 2) - - // Delete - err = s.Delete("AppID-1", "DevID-1") - a.So(err, ShouldBeNil) - - // Get deleted - dev, err = s.Get("AppID-1", "DevID-1") - a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) - - // Cleanup + dev, err = s.Get("AppID-1", "DevID-2") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + a.So(dev.DevEUI, ShouldEqual, types.DevEUI([8]byte{0, 0, 0, 0, 0, 0, 0, 3})) + + defer func() { s.Delete("AppID-1", "DevID-2") - } + }() + + // List + devices, err := s.List() + a.So(err, ShouldBeNil) + a.So(devices, ShouldHaveLength, 2) + + // Delete + err = s.Delete("AppID-1", "DevID-1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("AppID-1", "DevID-1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + devs, err = s.ListForApp("AppID-1") + a.So(err, ShouldBeNil) + a.So(devs, ShouldHaveLength, 1) } diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 03fa8349c..4ca3d0609 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -7,19 +7,17 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) -func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { +func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) (err error) { appID, devID := appDownlink.AppID, appDownlink.DevID ctx := h.Ctx.WithFields(log.Fields{ "AppID": appID, "DevID": devID, }) - var err error start := time.Now() defer func() { if err != nil { @@ -29,16 +27,18 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error { } }() - var dev *device.Device - dev, err = h.devices.Get(appID, devID) + dev, err := h.devices.Get(appID, devID) if err != nil { return err } + // Clear redundant fields appDownlink.AppID = "" appDownlink.DevID = "" + + dev.StartUpdate() dev.NextDownlink = appDownlink - err = h.devices.Set(dev, "next_downlink") + err = h.devices.Set(dev) if err != nil { return err } diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index ac327fef3..9e9269c32 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -23,7 +23,7 @@ func TestEnqueueDownlink(t *testing.T) { devID := "dev1" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-enqueue-downlink"), mqttEvent: make(chan *mqttEvent, 10), } err := h.EnqueueDownlink(&mqtt.DownlinkMessage{ @@ -35,6 +35,9 @@ func TestEnqueueDownlink(t *testing.T) { AppID: appID, DevID: devID, }) + defer func() { + h.devices.Delete(appID, devID) + }() err = h.EnqueueDownlink(&mqtt.DownlinkMessage{ AppID: appID, DevID: devID, @@ -60,8 +63,8 @@ func TestHandleDownlink(t *testing.T) { devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, - devices: device.NewDeviceStore(), - applications: application.NewApplicationStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-downlink"), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-enqueue-downlink"), downlink: make(chan *pb_broker.DownlinkMessage), mqttEvent: make(chan *mqttEvent, 10), } @@ -79,6 +82,9 @@ func TestHandleDownlink(t *testing.T) { AppID: appID, DevID: devID, }) + defer func() { + h.devices.Delete(appID, devID) + }() err = h.HandleDownlink(&mqtt.DownlinkMessage{ AppID: appID, DevID: devID, @@ -115,6 +121,9 @@ func TestHandleDownlink(t *testing.T) { return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0] }`, }) + defer func() { + h.applications.Delete(appID) + }() jsonFields := map[string]interface{}{"temperature": 11} err = h.HandleDownlink(&mqtt.DownlinkMessage{ FPort: 1, diff --git a/core/handler/dry_run_test.go b/core/handler/dry_run_test.go index acc4fb1fb..3a221dc54 100644 --- a/core/handler/dry_run_test.go +++ b/core/handler/dry_run_test.go @@ -10,6 +10,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/handler/application" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -63,7 +64,7 @@ func (s *countingStore) Delete(appID string) error { func TestDryUplinkFields(t *testing.T) { a := New(t) - store := newCountingStore(application.NewApplicationStore()) + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink")) h := &handler{ applications: store, } @@ -72,6 +73,7 @@ func TestDryUplinkFields(t *testing.T) { dryUplinkMessage := &pb.DryUplinkMessage{ Payload: []byte{11, 22, 33}, App: &pb.Application{ + AppId: "DryUplinkFields", Decoder: `function (bytes) { return { length: bytes.length }}`, Converter: `function (obj) { return obj }`, Validator: `function (bytes) { return true; }`, @@ -95,7 +97,7 @@ func TestDryUplinkFields(t *testing.T) { func TestDryUplinkEmptyApp(t *testing.T) { a := New(t) - store := newCountingStore(application.NewApplicationStore()) + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-uplink")) h := &handler{ applications: store, } @@ -122,7 +124,7 @@ func TestDryUplinkEmptyApp(t *testing.T) { func TestDryDownlinkFields(t *testing.T) { a := New(t) - store := newCountingStore(application.NewApplicationStore()) + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } @@ -150,7 +152,7 @@ func TestDryDownlinkFields(t *testing.T) { func TestDryDownlinkPayload(t *testing.T) { a := New(t) - store := newCountingStore(application.NewApplicationStore()) + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } @@ -178,7 +180,7 @@ func TestDryDownlinkPayload(t *testing.T) { func TestDryDownlinkEmptyApp(t *testing.T) { a := New(t) - store := newCountingStore(application.NewApplicationStore()) + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) h := &handler{ applications: store, } diff --git a/core/handler/handler.go b/core/handler/handler.go index bbe5d00bd..a5eb2eb70 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -16,7 +16,7 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) // Handler component @@ -33,8 +33,8 @@ type Handler interface { // NewRedisHandler creates a new Redis-backed Handler func NewRedisHandler(client *redis.Client, ttnBrokerID string, mqttUsername string, mqttPassword string, mqttBrokers ...string) Handler { return &handler{ - devices: device.NewRedisDeviceStore(client), - applications: application.NewRedisApplicationStore(client), + devices: device.NewRedisDeviceStore(client, "handler"), + applications: application.NewRedisApplicationStore(client, "handler"), ttnBrokerID: ttnBrokerID, mqttUsername: mqttUsername, mqttPassword: mqttPassword, diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index f3bc96d5c..e9b40f8c1 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -109,6 +109,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) } } + dev.StartUpdate() } else { // When this is a create existingDevices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { @@ -121,49 +122,27 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } dev = new(device.Device) } - fields := []string{} - - if dev.AppID != in.AppId { - dev.AppID = in.AppId - fields = append(fields, "app_id") - } - - if dev.AppEUI != *lorawan.AppEui { - dev.AppEUI = *lorawan.AppEui - fields = append(fields, "app_eui") - } - - if dev.DevID != in.DevId { - dev.DevID = in.DevId - fields = append(fields, "dev_id") - } - - if dev.DevEUI != *lorawan.DevEui { - dev.DevEUI = *lorawan.DevEui - fields = append(fields, "dev_eui") - } + dev.AppID = in.AppId + dev.AppEUI = *lorawan.AppEui + dev.DevID = in.DevId + dev.DevEUI = *lorawan.DevEui if lorawan.DevAddr != nil { dev.DevAddr = *lorawan.DevAddr - fields = append(fields, "dev_addr") } if lorawan.NwkSKey != nil { dev.NwkSKey = *lorawan.NwkSKey - fields = append(fields, "nwk_s_key") } if lorawan.AppSKey != nil { dev.AppSKey = *lorawan.AppSKey - fields = append(fields, "app_s_key") } if lorawan.AppKey != nil { if dev.AppKey != *lorawan.AppKey { // When the AppKey of an existing device is changed dev.UsedAppNonces = []device.AppNonce{} dev.UsedDevNonces = []device.DevNonce{} - fields = append(fields, "used_dev_nonces", "used_app_nonces") } dev.AppKey = *lorawan.AppKey - fields = append(fields, "app_key") } nsUpdated := &pb_lorawan.Device{ @@ -190,7 +169,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not set device")) } - err = h.handler.devices.Set(dev, fields...) + err = h.handler.devices.Set(dev) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -330,27 +309,12 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) return nil, grpcErrf(codes.InvalidArgument, "Invalid Application") } - fields := []string{} + app.StartUpdate() - if app.Decoder != in.Decoder { - app.Decoder = in.Decoder - fields = append(fields, "decoder") - } - - if app.Converter != in.Converter { - app.Converter = in.Converter - fields = append(fields, "converter") - } - - if app.Validator != in.Validator { - app.Validator = in.Validator - fields = append(fields, "validator") - } - - if app.Encoder != in.Encoder { - app.Encoder = in.Encoder - fields = append(fields, "encoder") - } + app.Decoder = in.Decoder + app.Converter = in.Converter + app.Validator = in.Validator + app.Encoder = in.Encoder err = h.handler.applications.Set(app) if err != nil { diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index d80c94b2b..23318c04d 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -30,12 +30,15 @@ func TestHandleMQTT(t *testing.T) { devID := "handler-mqtt-dev1" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, - devices: device.NewDeviceStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-mqtt"), } h.devices.Set(&device.Device{ AppID: appID, DevID: devID, }) + defer func() { + h.devices.Delete(appID, devID) + }() err := h.HandleMQTT("", "", fmt.Sprintf("tcp://%s:1883", host)) a.So(err, ShouldBeNil) diff --git a/core/handler/uplink.go b/core/handler/uplink.go index dd0006189..9e39a6df2 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -93,8 +93,9 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err } // Clear Downlink + dev.StartUpdate() dev.NextDownlink = nil - err = h.devices.Set(dev, "next_downlink") + err = h.devices.Set(dev) if err != nil { return err } diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index aaed87e8d..40f57fb09 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -4,8 +4,8 @@ package handler import ( - "sync" "testing" + "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" @@ -22,25 +22,32 @@ import ( func TestHandleUplink(t *testing.T) { a := New(t) var err error - var wg sync.WaitGroup + var wg WaitGroup appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) appID := "appid" devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devID := "devid" h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestHandleUplink")}, - devices: device.NewDeviceStore(), - applications: application.NewApplicationStore(), + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-uplink"), + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-handle-uplink"), } - h.devices.Set(&device.Device{ + dev := &device.Device{ AppID: appID, DevID: devID, AppEUI: appEUI, DevEUI: devEUI, - }) + } + h.devices.Set(dev) + defer func() { + h.devices.Delete(appID, devID) + }() h.applications.Set(&application.Application{ AppID: appID, }) + defer func() { + h.applications.Delete(appID) + }() h.mqttUp = make(chan *mqtt.UplinkMessage) h.mqttEvent = make(chan *mqttEvent, 10) h.downlink = make(chan *pb_broker.DownlinkMessage) @@ -68,7 +75,7 @@ func TestHandleUplink(t *testing.T) { }() err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) uplink.ResponseTemplate = downlink @@ -81,7 +88,7 @@ func TestHandleUplink(t *testing.T) { downlink.Payload = downlinkEmpty err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) // Test Uplink, ACK downlink needed wg.Add(2) @@ -96,7 +103,7 @@ func TestHandleUplink(t *testing.T) { downlink.Payload = downlinkACK err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) // Test Uplink, MAC downlink needed wg.Add(2) @@ -111,18 +118,15 @@ func TestHandleUplink(t *testing.T) { downlink.Payload = downlinkMAC err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) + + dev.StartUpdate() + dev.NextDownlink = &mqtt.DownlinkMessage{ + PayloadRaw: []byte{0xaa, 0xbc}, + } // Test Uplink, Data downlink needed - h.devices.Set(&device.Device{ - AppID: appID, - DevID: devID, - AppEUI: appEUI, - DevEUI: devEUI, - NextDownlink: &mqtt.DownlinkMessage{ - PayloadRaw: []byte{0xaa, 0xbc}, - }, - }) + h.devices.Set(dev) wg.Add(2) go func() { <-h.mqttUp @@ -136,8 +140,8 @@ func TestHandleUplink(t *testing.T) { downlink.Payload = downlinkEmpty err = h.HandleUplink(uplink) a.So(err, ShouldBeNil) - wg.Wait() + wg.WaitFor(50 * time.Millisecond) - dev, _ := h.devices.Get(appID, devID) + dev, _ = h.devices.Get(appID, devID) a.So(dev.NextDownlink, ShouldBeNil) } From 9643a8f50550006331c9200955a24c06498d5a57 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 10:40:35 +0200 Subject: [PATCH 1985/2266] Use RedisMapStore, RedisSetStore and RedisKVStore in Discovery --- cmd/discovery.go | 4 +- core/discovery/announcement/announcement.go | 220 +++++++++++++ .../announcement/announcement_test.go | 86 +++++ core/discovery/announcement/store.go | 310 +++++++++++------- core/discovery/announcement/store_test.go | 203 ++++++++---- core/discovery/discovery.go | 101 ++---- core/discovery/discovery_test.go | 265 ++++++--------- core/discovery/kv/store.go | 141 -------- core/discovery/kv/store_test.go | 75 ----- 9 files changed, 764 insertions(+), 641 deletions(-) create mode 100644 core/discovery/announcement/announcement.go create mode 100644 core/discovery/announcement/announcement_test.go delete mode 100644 core/discovery/kv/store.go delete mode 100644 core/discovery/kv/store_test.go diff --git a/cmd/discovery.go b/cmd/discovery.go index 0c02e0896..29026b358 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -12,7 +12,7 @@ import ( "google.golang.org/grpc" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" @@ -39,7 +39,7 @@ var discoveryCmd = &cobra.Command{ client := redis.NewClient(&redis.Options{ Addr: viper.GetString("discovery.redis-address"), Password: "", // no password set - DB: int64(viper.GetInt("discovery.redis-db")), + DB: viper.GetInt("discovery.redis-db"), }) connectRedis(client) diff --git a/core/discovery/announcement/announcement.go b/core/discovery/announcement/announcement.go new file mode 100644 index 000000000..3a9708230 --- /dev/null +++ b/core/discovery/announcement/announcement.go @@ -0,0 +1,220 @@ +package announcement + +import ( + "encoding" + "fmt" + "reflect" + "strings" + "time" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/fatih/structs" +) + +// Metadata represents metadata that is stored with an Announcement +type Metadata interface { + encoding.TextMarshaler + ToProto() *pb.Metadata +} + +// AppEUIMetadata is used to store an AppEUI +type AppEUIMetadata struct { + AppEUI types.AppEUI +} + +// ToProto implements the Metadata interface +func (m AppEUIMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Key: pb.Metadata_APP_EUI, + Value: m.AppEUI.Bytes(), + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m AppEUIMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("AppEUI %s", m.AppEUI)), nil +} + +// AppIDMetadata is used to store an AppID +type AppIDMetadata struct { + AppID string +} + +// ToProto implements the Metadata interface +func (m AppIDMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte(m.AppID), + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m AppIDMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("AppID %s", m.AppID)), nil +} + +// PrefixMetadata is used to store a DevAddr prefix +type PrefixMetadata struct { + Prefix types.DevAddrPrefix +} + +// ToProto implements the Metadata interface +func (m PrefixMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Key: pb.Metadata_PREFIX, + Value: []byte{byte(m.Prefix.Length), m.Prefix.DevAddr[0], m.Prefix.DevAddr[1], m.Prefix.DevAddr[2], m.Prefix.DevAddr[3]}, + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m PrefixMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("Prefix %s", m.Prefix)), nil +} + +// OtherMetadata is used to store arbitrary information +type OtherMetadata struct { + Data string +} + +// ToProto implements the Metadata interface +func (m OtherMetadata) ToProto() *pb.Metadata { + return &pb.Metadata{ + Key: pb.Metadata_OTHER, + Value: []byte(m.Data), + } +} + +// MarshalText implements the encoding.TextMarshaler interface +func (m OtherMetadata) MarshalText() ([]byte, error) { + return []byte(fmt.Sprintf("Other %s", m.Data)), nil +} + +// MetadataFromProto converts a protocol buffer metadata to a Metadata +func MetadataFromProto(proto *pb.Metadata) Metadata { + switch proto.Key { + case pb.Metadata_APP_EUI: + appEUI := new(types.AppEUI) + appEUI.UnmarshalBinary(proto.Value) + return AppEUIMetadata{*appEUI} + case pb.Metadata_APP_ID: + return AppIDMetadata{string(proto.Value)} + case pb.Metadata_PREFIX: + prefix := types.DevAddrPrefix{ + Length: 32, + } + if len(proto.Value) == 5 { + prefix.Length = int(proto.Value[0]) + prefix.DevAddr[0] = proto.Value[1] + prefix.DevAddr[1] = proto.Value[2] + prefix.DevAddr[2] = proto.Value[3] + prefix.DevAddr[3] = proto.Value[4] + } + return PrefixMetadata{prefix} + case pb.Metadata_OTHER: + return OtherMetadata{string(proto.Value)} + } + return nil +} + +// MetadataFromString converts a string to a Metadata +func MetadataFromString(str string) Metadata { + meta := strings.SplitAfterN(str, " ", 2) + key := strings.TrimSpace(meta[0]) + value := meta[1] + switch key { + case "AppEUI": + var appEUI types.AppEUI + appEUI.UnmarshalText([]byte(value)) + return AppEUIMetadata{appEUI} + case "AppID": + return AppIDMetadata{value} + case "Prefix": + prefix := &types.DevAddrPrefix{ + Length: 32, + } + prefix.UnmarshalText([]byte(value)) + return PrefixMetadata{*prefix} + case "Other": + return OtherMetadata{value} + } + return nil +} + +// Announcement of a network component +type Announcement struct { + old *Announcement + ID string `redis:"id"` + ServiceName string `redis:"service_name"` + ServiceVersion string `redis:"service_version"` + Description string `redis:"description"` + NetAddress string `redis:"net_address"` + PublicKey string `redis:"public_key"` + Certificate string `redis:"certificate"` + Metadata []Metadata + + CreatedAt time.Time `redis:"created_at"` + UpdatedAt time.Time `redis:"updated_at"` +} + +// StartUpdate stores the state of the announcement +func (a *Announcement) StartUpdate() { + old := *a + a.old = &old +} + +// ChangedFields returns the names of the changed fields since the last call to StartUpdate +func (a Announcement) ChangedFields() (changed []string) { + new := structs.New(a) + fields := new.Names() + if a.old == nil { + return fields + } + old := structs.New(*a.old) + + for _, field := range new.Fields() { + if !field.IsExported() || field.Name() == "old" { + continue + } + if !reflect.DeepEqual(field.Value(), old.Field(field.Name()).Value()) { + changed = append(changed, field.Name()) + } + } + return +} + +// ToProto converts the Announcement to a protobuf Announcement +func (a Announcement) ToProto() *pb.Announcement { + metadata := make([]*pb.Metadata, 0, len(a.Metadata)) + for _, meta := range a.Metadata { + metadata = append(metadata, meta.ToProto()) + } + return &pb.Announcement{ + Id: a.ID, + ServiceName: a.ServiceName, + ServiceVersion: a.ServiceVersion, + Description: a.Description, + NetAddress: a.NetAddress, + PublicKey: a.PublicKey, + Certificate: a.Certificate, + Metadata: metadata, + } +} + +// FromProto converts an Announcement protobuf to an Announcement +func FromProto(a *pb.Announcement) Announcement { + metadata := make([]Metadata, 0, len(a.Metadata)) + for _, meta := range a.Metadata { + metadata = append(metadata, MetadataFromProto(meta)) + } + return Announcement{ + ID: a.Id, + ServiceName: a.ServiceName, + ServiceVersion: a.ServiceVersion, + Description: a.Description, + NetAddress: a.NetAddress, + PublicKey: a.PublicKey, + Certificate: a.Certificate, + Metadata: metadata, + } +} diff --git a/core/discovery/announcement/announcement_test.go b/core/discovery/announcement/announcement_test.go new file mode 100644 index 000000000..b45eb4a7e --- /dev/null +++ b/core/discovery/announcement/announcement_test.go @@ -0,0 +1,86 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +func TestMetadataTextMarshaling(t *testing.T) { + a := New(t) + subjects := map[string]Metadata{ + "AppEUI 0102030405060708": AppEUIMetadata{types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})}, + "AppID AppID": AppIDMetadata{"AppID"}, + "Prefix 00000000/0": PrefixMetadata{types.DevAddrPrefix{}}, + } + + for str, obj := range subjects { + marshaled, err := obj.MarshalText() + a.So(err, ShouldBeNil) + a.So(string(marshaled), ShouldEqual, str) + unmarshaled := MetadataFromString(str) + a.So(unmarshaled, ShouldResemble, obj) + } +} + +func TestAnnouncementUpdate(t *testing.T) { + a := New(t) + announcement := &Announcement{ + ID: "ID", + } + announcement.StartUpdate() + a.So(announcement.old.ID, ShouldEqual, announcement.ID) +} + +func TestAnnouncementChangedFields(t *testing.T) { + a := New(t) + announcement := &Announcement{ + ID: "ID", + } + announcement.StartUpdate() + announcement.ID = "ID2" + + a.So(announcement.ChangedFields(), ShouldHaveLength, 1) + a.So(announcement.ChangedFields(), ShouldContain, "ID") +} + +func TestAnnouncementToProto(t *testing.T) { + a := New(t) + announcement := &Announcement{ + ID: "ID", + Metadata: []Metadata{ + AppEUIMetadata{types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})}, + AppIDMetadata{"AppID"}, + PrefixMetadata{types.DevAddrPrefix{}}, + OtherMetadata{}, + }, + } + proto := announcement.ToProto() + a.So(proto.Id, ShouldEqual, announcement.ID) + a.So(proto.Metadata, ShouldHaveLength, 4) + a.So(proto.Metadata[0].Key, ShouldEqual, pb.Metadata_APP_EUI) + a.So(proto.Metadata[1].Key, ShouldEqual, pb.Metadata_APP_ID) + a.So(proto.Metadata[2].Key, ShouldEqual, pb.Metadata_PREFIX) + a.So(proto.Metadata[3].Key, ShouldEqual, pb.Metadata_OTHER) +} + +func TestAnnouncementFromProto(t *testing.T) { + a := New(t) + proto := &pb.Announcement{ + Id: "ID", + Metadata: []*pb.Metadata{ + &pb.Metadata{Key: pb.Metadata_APP_EUI, Value: []byte{1, 2, 3, 4, 5, 6, 7, 8}}, + &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID")}, + &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0, 0, 0, 0, 0}}, + &pb.Metadata{Key: pb.Metadata_OTHER, Value: []byte{}}, + }, + } + announcement := FromProto(proto) + a.So(announcement.ID, ShouldEqual, proto.Id) + a.So(announcement.Metadata, ShouldHaveLength, 4) +} diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go index 9f977f4d5..2f540af19 100644 --- a/core/discovery/announcement/store.go +++ b/core/discovery/announcement/store.go @@ -5,183 +5,259 @@ package announcement import ( "fmt" - "sort" + "strings" + "time" - pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/storage" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) -// Store is used to store announcement configurations +// Store interface for Announcements type Store interface { - // List all announcements - List() ([]*pb.Announcement, error) - // List all announcements for a service - ListService(serviceName string) ([]*pb.Announcement, error) - // Get the full announcement - Get(serviceName, serviceID string) (*pb.Announcement, error) - // Set the announcement. - Set(announcement *pb.Announcement) error - // Delete a announcement + List() ([]*Announcement, error) + ListService(serviceName string) ([]*Announcement, error) + Get(serviceName, serviceID string) (*Announcement, error) + GetMetadata(serviceName, serviceID string) ([]Metadata, error) + GetForAppID(appID string) (*Announcement, error) + GetForAppEUI(appEUI types.AppEUI) (*Announcement, error) + Set(new *Announcement) error + AddMetadata(serviceName, serviceID string, metadata ...Metadata) error + RemoveMetadata(serviceName, serviceID string, metadata ...Metadata) error Delete(serviceName, serviceID string) error } -// NewAnnouncementStore creates a new in-memory Announcement store -func NewAnnouncementStore() Store { - return &announcementStore{ - announcements: make(map[string]map[string]*pb.Announcement), +const defaultRedisPrefix = "discovery" + +const redisAnnouncementPrefix = "announcement" +const redisMetadataPrefix = "metadata" +const redisAppIDPrefix = "app_id" +const redisAppEUIPrefix = "app_eui" + +// NewRedisAnnouncementStore creates a new Redis-based Announcement store +func NewRedisAnnouncementStore(client *redis.Client, prefix string) Store { + if prefix == "" { + prefix = defaultRedisPrefix + } + store := storage.NewRedisMapStore(client, prefix+":"+redisAnnouncementPrefix) + store.SetBase(Announcement{}, "") + return &RedisAnnouncementStore{ + store: store, + metadata: storage.NewRedisSetStore(client, prefix+":"+redisMetadataPrefix), + byAppID: storage.NewRedisKVStore(client, prefix+":"+redisAppIDPrefix), + byAppEUI: storage.NewRedisKVStore(client, prefix+":"+redisAppEUIPrefix), } } -// announcementStore is an in-memory Announcement store. It should only be used for testing -// purposes. Use the redisAnnouncementStore for actual deployments. -type announcementStore struct { - announcements map[string]map[string]*pb.Announcement +// RedisAnnouncementStore stores Announcements in Redis. +// - Announcements are stored as a Hash +// - Metadata is stored in a Set +// - AppIDs and AppEUIs are indexed with key/value pairs +type RedisAnnouncementStore struct { + store *storage.RedisMapStore + metadata *storage.RedisSetStore + byAppID *storage.RedisKVStore + byAppEUI *storage.RedisKVStore } -func (s *announcementStore) List() ([]*pb.Announcement, error) { - announcements := make([]*pb.Announcement, 0, len(s.announcements)) - for _, service := range s.announcements { - for _, announcement := range service { - announcements = append(announcements, announcement) +// List all Announcements +// The resulting Announcements do *not* include metadata +func (s *RedisAnnouncementStore) List() ([]*Announcement, error) { + announcementsI, err := s.store.List("", nil) + if err != nil { + return nil, err + } + announcements := make([]*Announcement, 0, len(announcementsI)) + for _, announcementI := range announcementsI { + if announcement, ok := announcementI.(Announcement); ok { + announcements = append(announcements, &announcement) } } return announcements, nil } -func (s *announcementStore) ListService(serviceName string) ([]*pb.Announcement, error) { - if service, ok := s.announcements[serviceName]; ok { - announcements := make([]*pb.Announcement, 0, len(s.announcements)) - for _, announcement := range service { - announcements = append(announcements, announcement) - } - return announcements, nil +// ListService lists all Announcements for a given service (router/broker/handler) +// The resulting Announcements *do* include metadata +func (s *RedisAnnouncementStore) ListService(serviceName string) ([]*Announcement, error) { + announcementsI, err := s.store.List(serviceName+":*", nil) + if err != nil { + return nil, err } - return []*pb.Announcement{}, nil -} - -func (s *announcementStore) Get(serviceName, serviceID string) (*pb.Announcement, error) { - if service, ok := s.announcements[serviceName]; ok { - if announcement, ok := service[serviceID]; ok { - return announcement, nil + announcements := make([]*Announcement, 0, len(announcementsI)) + for _, announcementI := range announcementsI { + if announcement, ok := announcementI.(Announcement); ok { + announcements = append(announcements, &announcement) + announcement.Metadata, err = s.GetMetadata(announcement.ServiceName, announcement.ID) + if err != nil { + return nil, err + } } } - return nil, errors.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) + return announcements, nil } -func (s *announcementStore) Set(new *pb.Announcement) error { - if _, ok := s.announcements[new.ServiceName]; !ok { - s.announcements[new.ServiceName] = map[string]*pb.Announcement{} +// Get a specific service Announcement +// The result *does* include metadata +func (s *RedisAnnouncementStore) Get(serviceName, serviceID string) (*Announcement, error) { + announcementI, err := s.store.Get(fmt.Sprintf("%s:%s", serviceName, serviceID)) + if err != nil { + return nil, err } - s.announcements[new.ServiceName][new.Id] = new - return nil -} - -func (s *announcementStore) Delete(serviceName, serviceID string) error { - if service, ok := s.announcements[serviceName]; ok { - delete(service, serviceID) + announcement, ok := announcementI.(Announcement) + if !ok { + return nil, errors.New("Database did not return an Announcement") } - return nil -} - -// NewRedisAnnouncementStore creates a new Redis-based status store -func NewRedisAnnouncementStore(client *redis.Client) Store { - return &redisAnnouncementStore{ - client: client, + announcement.Metadata, err = s.GetMetadata(serviceName, serviceID) + if err != nil { + return nil, err } + return &announcement, nil } -const redisAnnouncementPrefix = "discovery:announcement" - -type redisAnnouncementStore struct { - client *redis.Client -} - -func (s *redisAnnouncementStore) getForKeys(keys []string) ([]*pb.Announcement, error) { - sort.Strings(keys) - - pipe := s.client.Pipeline() - defer pipe.Close() - - // Add all commands to pipeline - cmds := make(map[string]*redis.StringStringMapCmd) - for _, key := range keys { - cmds[key] = pipe.HGetAllMap(key) +// GetMetadata returns the metadata of the specified service +func (s *RedisAnnouncementStore) GetMetadata(serviceName, serviceID string) ([]Metadata, error) { + var out []Metadata + metadata, err := s.metadata.Get(fmt.Sprintf("%s:%s", serviceName, serviceID)) + if errors.GetErrType(err) == errors.NotFound { + return nil, nil } - - // Execute pipeline - _, err := pipe.Exec() if err != nil { return nil, err } - - // Get all results from pipeline - announcements := make([]*pb.Announcement, 0, len(keys)) - for _, cmd := range cmds { - dmap, err := cmd.Result() - if err == nil { - announcement := &pb.Announcement{} - err := announcement.FromStringStringMap(dmap) - if err == nil { - announcements = append(announcements, announcement) - } + for _, meta := range metadata { + if meta := MetadataFromString(meta); meta != nil { + out = append(out, meta) } } - - return announcements, nil + return out, nil } -func (s *redisAnnouncementStore) List() ([]*pb.Announcement, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:*", redisAnnouncementPrefix)).Result() +// GetForAppID returns the last Announcement that contains metadata for the given AppID +func (s *RedisAnnouncementStore) GetForAppID(appID string) (*Announcement, error) { + key, err := s.byAppID.Get(appID) if err != nil { return nil, err } - return s.getForKeys(keys) + service := strings.Split(key, ":") + return s.Get(service[0], service[1]) } -func (s *redisAnnouncementStore) ListService(serviceName string) ([]*pb.Announcement, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:%s:*", redisAnnouncementPrefix, serviceName)).Result() +// GetForAppEUI returns the last Announcement that contains metadata for the given AppEUI +func (s *RedisAnnouncementStore) GetForAppEUI(appEUI types.AppEUI) (*Announcement, error) { + key, err := s.byAppEUI.Get(appEUI.String()) if err != nil { return nil, err } - return s.getForKeys(keys) + service := strings.Split(key, ":") + return s.Get(service[0], service[1]) } -func (s *redisAnnouncementStore) Get(serviceName, serviceID string) (*pb.Announcement, error) { - res, err := s.client.HGetAllMap(fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, serviceID)).Result() - if err != nil { - if err == redis.Nil { - return nil, errors.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) - } - return nil, err - } else if len(res) == 0 { - return nil, errors.NewErrNotFound(fmt.Sprintf("Discovery: %s/%s", serviceName, serviceID)) +// Set a new Announcement or update an existing one +// The metadata of the announcement is ignored, as metadata should be managed with AddMetadata and RemoveMetadata +func (s *RedisAnnouncementStore) Set(new *Announcement) error { + key := fmt.Sprintf("%s:%s", new.ServiceName, new.ID) + now := time.Now() + new.UpdatedAt = now + err := s.store.Update(key, *new) + if errors.GetErrType(err) == errors.NotFound { + new.CreatedAt = now + err = s.store.Create(key, *new) } - announcement := &pb.Announcement{} - err = announcement.FromStringStringMap(res) if err != nil { - return nil, err + return err } - return announcement, nil + return nil } -func (s *redisAnnouncementStore) Set(new *pb.Announcement) error { - key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, new.ServiceName, new.Id) - dmap, err := new.ToStringStringMap(pb.AnnouncementProperties...) +// AddMetadata adds metadata to the announcement of the specified service +func (s *RedisAnnouncementStore) AddMetadata(serviceName, serviceID string, metadata ...Metadata) error { + key := fmt.Sprintf("%s:%s", serviceName, serviceID) + + metadataStrings := make([]string, 0, len(metadata)) + for _, meta := range metadata { + txt, err := meta.MarshalText() + if err != nil { + return err + } + metadataStrings = append(metadataStrings, string(txt)) + + switch meta := meta.(type) { + case AppIDMetadata: + existing, err := s.byAppID.Get(meta.AppID) + switch { + case errors.GetErrType(err) == errors.NotFound: + if err := s.byAppID.Create(meta.AppID, key); err != nil { + return err + } + case err != nil: + return err + case existing == key: + continue + default: + go s.metadata.Remove(existing, string(txt)) + if err := s.byAppID.Update(meta.AppID, key); err != nil { + return err + } + } + case AppEUIMetadata: + existing, err := s.byAppEUI.Get(meta.AppEUI.String()) + switch { + case errors.GetErrType(err) == errors.NotFound: + if err := s.byAppEUI.Create(meta.AppEUI.String(), key); err != nil { + return err + } + case err != nil: + return err + case existing == key: + continue + default: + go s.metadata.Remove(existing, string(txt)) + if err := s.byAppEUI.Update(meta.AppEUI.String(), key); err != nil { + return err + } + } + } + } + err := s.metadata.Add(key, metadataStrings...) if err != nil { return err } - err = s.client.HMSetMap(key, dmap).Err() + return nil +} + +// RemoveMetadata removes metadata from the announcement of the specified service +func (s *RedisAnnouncementStore) RemoveMetadata(serviceName, serviceID string, metadata ...Metadata) error { + metadataStrings := make([]string, 0, len(metadata)) + for _, meta := range metadata { + if txt, err := meta.MarshalText(); err == nil { + metadataStrings = append(metadataStrings, string(txt)) + } + switch meta := meta.(type) { + case AppIDMetadata: + s.byAppID.Delete(meta.AppID) + case AppEUIMetadata: + s.byAppEUI.Delete(meta.AppEUI.String()) + } + } + err := s.metadata.Remove(fmt.Sprintf("%s:%s", serviceName, serviceID), metadataStrings...) if err != nil { return err } - return nil } -func (s *redisAnnouncementStore) Delete(serviceName, serviceID string) error { - key := fmt.Sprintf("%s:%s:%s", redisAnnouncementPrefix, serviceName, serviceID) - err := s.client.Del(key).Err() +// Delete an Announcement and its metadata +func (s *RedisAnnouncementStore) Delete(serviceName, serviceID string) error { + metadata, err := s.GetMetadata(serviceName, serviceID) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return err + } + if len(metadata) > 0 { + s.RemoveMetadata(serviceName, serviceID, metadata...) + } + key := fmt.Sprintf("%s:%s", serviceName, serviceID) + err = s.store.Delete(key) if err != nil { return err } diff --git a/core/discovery/announcement/store_test.go b/core/discovery/announcement/store_test.go index 3e2dff724..065efed7e 100644 --- a/core/discovery/announcement/store_test.go +++ b/core/discovery/announcement/store_test.go @@ -4,84 +4,145 @@ package announcement import ( - "fmt" - "os" "testing" - pb "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" - "gopkg.in/redis.v3" ) -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB +func TestRedisAnnouncementStore(t *testing.T) { + a := New(t) + + s := NewRedisAnnouncementStore(GetRedisClient(), "discovery-test-announcement-store") + + // Get non-existing + dev, err := s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&Announcement{ + ServiceName: "router", + ID: "router1", }) -} + a.So(err, ShouldBeNil) -func TestAnnouncementStore(t *testing.T) { - a := New(t) + defer func() { + s.Delete("router", "router1") + }() + + // Get existing + dev, err = s.Get("router", "router1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) - stores := map[string]Store{ - "local": NewAnnouncementStore(), - "redis": NewRedisAnnouncementStore(getRedisClient()), - } - - for name, s := range stores { - - t.Logf("Testing %s store", name) - - // Get non-existing - dev, err := s.Get("router", "router1") - a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) - - // Create - err = s.Set(&pb.Announcement{ - ServiceName: "router", - Id: "router1", - }) - a.So(err, ShouldBeNil) - - // Get existing - dev, err = s.Get("router", "router1") - a.So(err, ShouldBeNil) - a.So(dev, ShouldNotBeNil) - - // Create extra - err = s.Set(&pb.Announcement{ - ServiceName: "broker", - Id: "broker1", - }) - a.So(err, ShouldBeNil) - - // List - announcements, err := s.List() - a.So(err, ShouldBeNil) - a.So(announcements, ShouldHaveLength, 2) - - // List - announcements, err = s.ListService("router") - a.So(err, ShouldBeNil) - a.So(announcements, ShouldHaveLength, 1) - - // Delete - err = s.Delete("router", "router1") - a.So(err, ShouldBeNil) - - // Get deleted - dev, err = s.Get("router", "router1") - a.So(err, ShouldNotBeNil) - a.So(dev, ShouldBeNil) - - // Cleanup - s.Delete("broker", "broker1") - } + // Create extra + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler1", + }) + a.So(err, ShouldBeNil) + defer func() { + s.Delete("handler", "handler1") + }() + + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler2", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler2") + }() + + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + + err = s.AddMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + handler, err := s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + AppIDMetadata{AppID: "OtherAppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + metadata, err := s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 4) + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + metadata, err = s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 4) + + handler, err = s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + err = s.RemoveMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + err = s.RemoveMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + // List + announcements, err := s.List() + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 3) + + // List + announcements, err = s.ListService("router") + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 1) + + // Delete + err = s.Delete("router", "router1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Delete with Metadata + err = s.Delete("handler", "handler2") + a.So(err, ShouldBeNil) } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index e9b0ed468..50f7fdd57 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -5,14 +5,11 @@ package discovery import ( - "bytes" - pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" - "github.com/TheThingsNetwork/ttn/core/discovery/kv" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) // Discovery specifies the interface for the TTN Service Discovery component @@ -29,7 +26,6 @@ type Discovery interface { type discovery struct { *core.Component services announcement.Store - appIDs kv.Store } func (d *discovery) Init(c *core.Component) error { @@ -43,15 +39,25 @@ func (d *discovery) Init(c *core.Component) error { } func (d *discovery) Announce(in *pb.Announcement) error { - existing, err := d.services.Get(in.ServiceName, in.Id) - if errors.GetErrType(err) == errors.NotFound { - // Not found; create new - existing = &pb.Announcement{} - } else if err != nil { + service, err := d.services.Get(in.ServiceName, in.Id) + if err != nil && errors.GetErrType(err) != errors.NotFound { return err } - in.Metadata = existing.Metadata - return d.services.Set(in) + if service == nil { + service = new(announcement.Announcement) + } + + service.StartUpdate() + + service.ID = in.Id + service.ServiceName = in.ServiceName + service.ServiceVersion = in.ServiceVersion + service.Description = in.Description + service.NetAddress = in.NetAddress + service.PublicKey = in.PublicKey + service.Certificate = in.Certificate + + return d.services.Set(service) } func (d *discovery) Get(serviceName string, id string) (*pb.Announcement, error) { @@ -59,8 +65,7 @@ func (d *discovery) Get(serviceName string, id string) (*pb.Announcement, error) if err != nil { return nil, err } - serviceCopy := *service - return &serviceCopy, nil + return service.ToProto(), nil } func (d *discovery) GetAll(serviceName string) ([]*pb.Announcement, error) { @@ -70,80 +75,24 @@ func (d *discovery) GetAll(serviceName string) ([]*pb.Announcement, error) { } serviceCopies := make([]*pb.Announcement, 0, len(services)) for _, service := range services { - serviceCopy := *service - serviceCopies = append(serviceCopies, &serviceCopy) + serviceCopies = append(serviceCopies, service.ToProto()) } return serviceCopies, nil } func (d *discovery) AddMetadata(serviceName string, id string, in *pb.Metadata) error { - existing, err := d.services.Get(serviceName, id) - if err != nil { - return err - } - // Skip if already existing - for _, md := range existing.Metadata { - if md.Key == in.Key && bytes.Equal(md.Value, in.Value) { - return nil - } - } - - // Pre-update - switch in.Key { - case pb.Metadata_APP_ID: - existingHandler, err := d.appIDs.Get(string(in.Value)) - if err == nil { - d.DeleteMetadata("handler", existingHandler, in) - } - } - - // Update - existing.Metadata = append(existing.Metadata, in) - err = d.services.Set(existing) - if err != nil { - return err - } - - // Post-update - switch in.Key { - case pb.Metadata_APP_ID: - err := d.appIDs.Set(string(in.Value), id) - if err != nil { - return err - } - } - - return nil + meta := announcement.MetadataFromProto(in) + return d.services.AddMetadata(serviceName, id, meta) } func (d *discovery) DeleteMetadata(serviceName string, id string, in *pb.Metadata) error { - existing, err := d.services.Get(serviceName, id) - if err != nil { - return err - } - newMeta := make([]*pb.Metadata, 0, len(existing.Metadata)) - for _, md := range existing.Metadata { - if md.Key == in.Key && bytes.Equal(md.Value, in.Value) { - continue - } - newMeta = append(newMeta, md) - } - existing.Metadata = newMeta - return d.services.Set(existing) -} - -// NewDiscovery creates a new memory-based discovery service -func NewDiscovery(client *redis.Client) Discovery { - return &discovery{ - services: announcement.NewAnnouncementStore(), - appIDs: kv.NewKVStore(), - } + meta := announcement.MetadataFromProto(in) + return d.services.RemoveMetadata(serviceName, id, meta) } // NewRedisDiscovery creates a new Redis-based discovery service func NewRedisDiscovery(client *redis.Client) Discovery { return &discovery{ - services: announcement.NewRedisAnnouncementStore(client), - appIDs: kv.NewRedisKVStore(client, "app-id"), + services: announcement.NewRedisAnnouncementStore(client, "discovery"), } } diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 8ef721c37..101bb13c0 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -8,15 +8,13 @@ import ( "os" "testing" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core/discovery/announcement" - "github.com/TheThingsNetwork/ttn/core/discovery/kv" . "github.com/smartystreets/assertions" ) -func getRedisClient(db int64) *redis.Client { +func getRedisClient(db int) *redis.Client { host := os.Getenv("REDIS_HOST") if host == "" { host = "localhost" @@ -31,74 +29,47 @@ func getRedisClient(db int64) *redis.Client { func TestDiscoveryAnnounce(t *testing.T) { a := New(t) - localDiscovery := &discovery{ - services: announcement.NewAnnouncementStore(), - appIDs: kv.NewKVStore(), - } - client := getRedisClient(1) - redisDiscovery := NewRedisDiscovery(client) + d := NewRedisDiscovery(client) defer func() { client.Del("discovery:announcement:broker:broker1.1") client.Del("discovery:announcement:broker:broker1.2") }() - discoveries := map[string]Discovery{ - "local": localDiscovery, - "redis": redisDiscovery, - } - - for name, d := range discoveries { - broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "current address"} - broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "updated address"} - broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker1.2", NetAddress: "other address"} - - t.Logf("Testing %s\n", name) + broker1a := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "current address"} + broker1b := &pb.Announcement{ServiceName: "broker", Id: "broker1.1", NetAddress: "updated address"} + broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker1.2", NetAddress: "other address"} - err := d.Announce(broker1a) - a.So(err, ShouldBeNil) + err := d.Announce(broker1a) + a.So(err, ShouldBeNil) - services, err := d.GetAll("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 1) - a.So(services[0].NetAddress, ShouldEqual, "current address") + services, err := d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "current address") - err = d.Announce(broker1b) - a.So(err, ShouldBeNil) + err = d.Announce(broker1b) + a.So(err, ShouldBeNil) - services, err = d.GetAll("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 1) - a.So(services[0].NetAddress, ShouldEqual, "updated address") + services, err = d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].NetAddress, ShouldEqual, "updated address") - err = d.Announce(broker2) - a.So(err, ShouldBeNil) + err = d.Announce(broker2) + a.So(err, ShouldBeNil) - services, err = d.GetAll("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 2) - } + services, err = d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) } func TestDiscoveryDiscover(t *testing.T) { a := New(t) - router := &pb.Announcement{ServiceName: "router", Id: "router2.0"} - broker1 := &pb.Announcement{ServiceName: "broker", Id: "broker2.1"} - broker2 := &pb.Announcement{ServiceName: "broker", Id: "broker2.2"} - - localDiscovery := &discovery{ - services: announcement.NewAnnouncementStore(), - appIDs: kv.NewKVStore(), - } - - localDiscovery.services.Set(router) - localDiscovery.services.Set(broker1) - localDiscovery.services.Set(broker2) - client := getRedisClient(2) - redisDiscovery := NewRedisDiscovery(client) + d := NewRedisDiscovery(client) defer func() { client.Del("discovery:announcement:router:router2.0") client.Del("discovery:announcement:broker:broker2.1") @@ -106,127 +77,103 @@ func TestDiscoveryDiscover(t *testing.T) { }() // This depends on the previous test to pass - redisDiscovery.Announce(router) - redisDiscovery.Announce(broker1) - redisDiscovery.Announce(broker2) - - discoveries := map[string]Discovery{ - "local": localDiscovery, - "redis": redisDiscovery, - } + d.Announce(&pb.Announcement{ServiceName: "router", Id: "router2.0"}) + d.Announce(&pb.Announcement{ServiceName: "broker", Id: "broker2.1"}) + d.Announce(&pb.Announcement{ServiceName: "broker", Id: "broker2.2"}) - for name, d := range discoveries { - t.Logf("Testing %s\n", name) + services, err := d.GetAll("random") + a.So(err, ShouldBeNil) + a.So(services, ShouldBeEmpty) - services, err := d.GetAll("random") - a.So(err, ShouldBeNil) - a.So(services, ShouldBeEmpty) + services, err = d.GetAll("router") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 1) + a.So(services[0].Id, ShouldEqual, "router2.0") - services, err = d.GetAll("router") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 1) - a.So(services[0].Id, ShouldEqual, router.Id) + services, err = d.GetAll("broker") + a.So(err, ShouldBeNil) + a.So(services, ShouldHaveLength, 2) - services, err = d.GetAll("broker") - a.So(err, ShouldBeNil) - a.So(services, ShouldHaveLength, 2) - - } } func TestDiscoveryMetadata(t *testing.T) { a := New(t) - localDiscovery := &discovery{ - services: announcement.NewAnnouncementStore(), - appIDs: kv.NewKVStore(), - } - client := getRedisClient(1) - redisDiscovery := NewRedisDiscovery(client) + d := NewRedisDiscovery(client) defer func() { client.Del("discovery:announcement:broker:broker3") client.Del("discovery:announcement:broker:broker4") client.Del("discovery:app-id:app-id-2") }() - discoveries := map[string]Discovery{ - "local": localDiscovery, - "redis": redisDiscovery, - } - - for name, d := range discoveries { - broker3 := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-1"), - }}} - broker4 := &pb.Announcement{ServiceName: "broker", Id: "broker4", Metadata: []*pb.Metadata{&pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), - }}} - - t.Logf("Testing %s\n", name) - - // Announce should not change metadata - err := d.Announce(broker3) - a.So(err, ShouldBeNil) - service, err := d.Get("broker", "broker3") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 0) - - d.Announce(broker4) - - // AddMetadata should add one - err = d.AddMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), - }) - a.So(err, ShouldBeNil) - service, err = d.Get("broker", "broker3") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 1) - - // And should remove it from the other broker - service, err = d.Get("broker", "broker4") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 0) - - // AddMetadata again should not add one - err = d.AddMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), - }) - service, err = d.Get("broker", "broker3") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 1) - - // DeleteMetadata for non-existing should not delete one - err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-3"), - }) - a.So(err, ShouldBeNil) - service, err = d.Get("broker", "broker3") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 1) - - // Announce should not change metadata - err = d.Announce(broker3) - a.So(err, ShouldBeNil) - service, err = d.Get("broker", "broker3") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 1) - - // DeleteMetadata should delete one - err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), - }) - a.So(err, ShouldBeNil) - service, err = d.Get("broker", "broker3") - a.So(err, ShouldBeNil) - a.So(service.Metadata, ShouldHaveLength, 0) - - } + broker3 := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-1"), + }}} + broker4 := &pb.Announcement{ServiceName: "broker", Id: "broker4", Metadata: []*pb.Metadata{&pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }}} + + // Announce should not change metadata + err := d.Announce(broker3) + a.So(err, ShouldBeNil) + service, err := d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + + d.Announce(broker4) + + // AddMetadata should add one + err = d.AddMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // And should remove it from the other broker + service, err = d.Get("broker", "broker4") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) + + // AddMetadata again should not add one + err = d.AddMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // DeleteMetadata for non-existing should not delete one + err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-3"), + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // Announce should not change metadata + err = d.Announce(broker3) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 1) + + // DeleteMetadata should delete one + err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ + Key: pb.Metadata_APP_ID, + Value: []byte("app-id-2"), + }) + a.So(err, ShouldBeNil) + service, err = d.Get("broker", "broker3") + a.So(err, ShouldBeNil) + a.So(service.Metadata, ShouldHaveLength, 0) } diff --git a/core/discovery/kv/store.go b/core/discovery/kv/store.go deleted file mode 100644 index fea6efad1..000000000 --- a/core/discovery/kv/store.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package kv - -import ( - "fmt" - "sort" - - "github.com/TheThingsNetwork/ttn/utils/errors" - - "gopkg.in/redis.v3" -) - -// Store is a simple String/String Key-Value store -type Store interface { - // List all items in the store - List() (map[string]string, error) - // Get the full HandlerID for the AppID - Get(key string) (value string, err error) - // Set the mapping. - Set(key, value string) error - // Delete a mapping - Delete(key string) error -} - -// NewKVStore creates a new in-memory Key-Value store -func NewKVStore() Store { - return &kvStore{ - data: make(map[string]string), - } -} - -// kvStore is an in-memory Key-Value store. It should only be used for testing -// purposes. Use the redisKVStore for actual deployments. -type kvStore struct { - data map[string]string -} - -func (s *kvStore) List() (map[string]string, error) { - return s.data, nil -} - -func (s *kvStore) Get(key string) (string, error) { - if value, ok := s.data[key]; ok { - return value, nil - } - return "", errors.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) -} - -func (s *kvStore) Set(key, value string) error { - s.data[key] = value - return nil -} - -func (s *kvStore) Delete(key string) error { - delete(s.data, key) - return nil -} - -const redisKVPrefix = "discovery:" - -// NewRedisKVStore creates a new Redis-based Key-Value store -func NewRedisKVStore(client *redis.Client, prefix string) Store { - return &redisKVStore{ - client: client, - prefix: redisKVPrefix + prefix, - } -} - -type redisKVStore struct { - prefix string - client *redis.Client -} - -func (s *redisKVStore) getForKeys(keys []string) (map[string]string, error) { - sort.Strings(keys) - - pipe := s.client.Pipeline() - defer pipe.Close() - - // Add all commands to pipeline - cmds := make(map[string]*redis.StringCmd) - for _, key := range keys { - cmds[key] = pipe.Get(key) - } - - // Execute pipeline - _, err := pipe.Exec() - if err != nil { - return nil, err - } - - // Get all results from pipeline - data := make(map[string]string) - for key, cmd := range cmds { - res, err := cmd.Result() - if err == nil { - data[key] = res - } - } - - return data, nil -} - -func (s *redisKVStore) List() (map[string]string, error) { - keys, err := s.client.Keys(fmt.Sprintf("%s:*", s.prefix)).Result() - if err != nil { - return nil, err - } - return s.getForKeys(keys) -} - -func (s *redisKVStore) Get(key string) (string, error) { - res, err := s.client.Get(fmt.Sprintf("%s:%s", s.prefix, key)).Result() - if err != nil { - if err == redis.Nil { - return "", errors.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) - } - return "", err - } else if res == "" { - return "", errors.NewErrNotFound(fmt.Sprintf("Discovery: %s", key)) - } - return res, nil -} - -func (s *redisKVStore) Set(key, value string) error { - err := s.client.Set(fmt.Sprintf("%s:%s", s.prefix, key), value, 0).Err() - if err != nil { - return err - } - return nil -} - -func (s *redisKVStore) Delete(key string) error { - err := s.client.Del(fmt.Sprintf("%s:%s", s.prefix, key)).Err() - if err != nil { - return err - } - return nil -} diff --git a/core/discovery/kv/store_test.go b/core/discovery/kv/store_test.go deleted file mode 100644 index 8d1a2a504..000000000 --- a/core/discovery/kv/store_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package kv - -import ( - "fmt" - "os" - "testing" - - . "github.com/smartystreets/assertions" - "gopkg.in/redis.v3" -) - -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB - }) -} - -func TestAnnouncementStore(t *testing.T) { - a := New(t) - - stores := map[string]Store{ - "local": NewKVStore(), - "redis": NewRedisKVStore(getRedisClient(), "test"), - } - - for name, s := range stores { - - t.Logf("Testing %s store", name) - - // Get non-existing - res, err := s.Get("some-key") - a.So(err, ShouldNotBeNil) - a.So(res, ShouldBeEmpty) - - // Create - err = s.Set("some-key", "some-value") - a.So(err, ShouldBeNil) - - // Get existing - res, err = s.Get("some-key") - a.So(err, ShouldBeNil) - a.So(res, ShouldEqual, "some-value") - - // Create extra - err = s.Set("other-key", "other-value") - a.So(err, ShouldBeNil) - - // List - resps, err := s.List() - a.So(err, ShouldBeNil) - a.So(resps, ShouldHaveLength, 2) - - // Delete - err = s.Delete("other-key") - a.So(err, ShouldBeNil) - - // Get deleted - res, err = s.Get("other-key") - a.So(err, ShouldNotBeNil) - a.So(res, ShouldBeEmpty) - - // Cleanup - s.Delete("some-key") - } - -} From acb7dc0c0a1005b7ef052d570575e4328efe62ee Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 13:59:51 +0200 Subject: [PATCH 1986/2266] Use redis.v4 in cmd/root.go --- cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/root.go b/cmd/root.go index d754d7035..fb712817f 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tj/go-elastic" - "gopkg.in/redis.v3" + "gopkg.in/redis.v4" ) var cfgFile string From 70b401d572ba398c33890755d9c2c85f793f4107 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 15:15:06 +0200 Subject: [PATCH 1987/2266] Remove RedisStatusStore from router/gateway In-memory storage is fine, because gateways have persistent connections. For real storage, we'll use the NOC. --- core/router/gateway/status.go | 36 --------------------- core/router/gateway/status_test.go | 52 +----------------------------- 2 files changed, 1 insertion(+), 87 deletions(-) diff --git a/core/router/gateway/status.go b/core/router/gateway/status.go index 9e59923b8..fa9e4796e 100644 --- a/core/router/gateway/status.go +++ b/core/router/gateway/status.go @@ -4,11 +4,9 @@ package gateway import ( - "fmt" "sync" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - "gopkg.in/redis.v3" ) // StatusStore is a database for setting and retrieving the latest gateway status @@ -44,37 +42,3 @@ func (s *statusStore) Get() (*pb_gateway.Status, error) { } return &pb_gateway.Status{}, nil } - -// NewRedisStatusStore creates a new Redis-based status store -func NewRedisStatusStore(client *redis.Client, id string) StatusStore { - return &redisStatusStore{ - client: client, - key: fmt.Sprintf("router:gateway:%s", id), - } -} - -type redisStatusStore struct { - client *redis.Client - key string -} - -func (s *redisStatusStore) Update(status *pb_gateway.Status) error { - m, err := status.ToStringStringMap(pb_gateway.StatusMessageProperties...) - if err != nil { - return err - } - return s.client.HMSetMap(s.key, m).Err() -} - -func (s *redisStatusStore) Get() (*pb_gateway.Status, error) { - status := &pb_gateway.Status{} - res, err := s.client.HGetAllMap(s.key).Result() - if err != nil { - return status, nil - } - err = status.FromStringStringMap(res) - if err != nil { - return nil, err - } - return status, nil -} diff --git a/core/router/gateway/status_test.go b/core/router/gateway/status_test.go index 336c26797..57a71faeb 100644 --- a/core/router/gateway/status_test.go +++ b/core/router/gateway/status_test.go @@ -4,35 +4,15 @@ package gateway import ( - "fmt" - "os" "testing" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" . "github.com/smartystreets/assertions" - - "gopkg.in/redis.v3" ) -func getRedisClient() *redis.Client { - host := os.Getenv("REDIS_HOST") - if host == "" { - host = "localhost" - } - return redis.NewClient(&redis.Options{ - Addr: fmt.Sprintf("%s:6379", host), - Password: "", // no password set - DB: 1, // use default DB - }) -} - func TestNewGatewayStatusStore(t *testing.T) { a := New(t) - client := getRedisClient() - id := "0000000000000000" - store := NewRedisStatusStore(client, id) - a.So(store, ShouldNotBeNil) - store = NewStatusStore() + store := NewStatusStore() a.So(store, ShouldNotBeNil) } @@ -57,33 +37,3 @@ func TestStatusGetUpsert(t *testing.T) { a.So(status, ShouldNotBeNil) a.So(*status, ShouldResemble, *statusMessage) } - -func TestRedisStatusGetUpsert(t *testing.T) { - a := New(t) - id := "0000000000000001" - client := getRedisClient() - store := NewRedisStatusStore(client, id) - - // Cleanup before and after - client.Del(store.(*redisStatusStore).key) - defer client.Del(store.(*redisStatusStore).key) - - // Get non-existing gateway status -> expect empty - status, err := store.Get() - a.So(err, ShouldBeNil) - a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, pb_gateway.Status{}) - - // Update -> expect no error - statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} - err = store.Update(statusMessage) - a.So(err, ShouldBeNil) - - // Get existing gateway status -> expect status - status, err = store.Get() - a.So(err, ShouldBeNil) - a.So(status, ShouldNotBeNil) - a.So(*status, ShouldResemble, *statusMessage) -} - -// TODO: Test error cases From da491e0555830d3a819d6775b6c9f62c45a1a4c8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 16:46:10 +0200 Subject: [PATCH 1988/2266] =?UTF-8?q?Implement=20unit=20tests=20for=20secu?= =?UTF-8?q?rity=20package=20=F0=9F=91=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/security/jwt.go | 11 +++----- utils/security/jwt_test.go | 48 +++++++++++++++++++++++++++++++++ utils/security/security_test.go | 47 ++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 7 deletions(-) create mode 100644 utils/security/jwt_test.go diff --git a/utils/security/jwt.go b/utils/security/jwt.go index f650cd579..69fd69353 100644 --- a/utils/security/jwt.go +++ b/utils/security/jwt.go @@ -2,7 +2,6 @@ package security import ( "crypto/ecdsa" - "errors" "fmt" "time" @@ -13,6 +12,7 @@ import ( func BuildJWT(subject string, ttl time.Duration, privateKey []byte) (token string, err error) { claims := jwt.StandardClaims{ Issuer: subject, + Subject: subject, IssuedAt: time.Now().Add(-20 * time.Second).Unix(), NotBefore: time.Now().Add(-20 * time.Second).Unix(), } @@ -35,9 +35,9 @@ func BuildJWT(subject string, ttl time.Duration, privateKey []byte) (token strin // ValidateJWT validates a JSON Web Token with the given public key func ValidateJWT(token string, publicKey []byte) (*jwt.StandardClaims, error) { claims := &jwt.StandardClaims{} - parsed, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { + _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok { - return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + return nil, fmt.Errorf("Unexpected JWT signing method: %v", token.Header["alg"]) } key, err := jwt.ParseECPublicKeyFromPEM(publicKey) if err != nil { @@ -46,10 +46,7 @@ func ValidateJWT(token string, publicKey []byte) (*jwt.StandardClaims, error) { return key, nil }) if err != nil { - return nil, fmt.Errorf("Unable to parse token: %s", err.Error()) - } - if !parsed.Valid { - return nil, errors.New("The token is not valid or is expired") + return nil, fmt.Errorf("Unable to verify JWT: %s", err.Error()) } return claims, nil } diff --git a/utils/security/jwt_test.go b/utils/security/jwt_test.go new file mode 100644 index 000000000..d93bf7393 --- /dev/null +++ b/utils/security/jwt_test.go @@ -0,0 +1,48 @@ +package security + +import ( + "testing" + "time" + + . "github.com/smartystreets/assertions" +) + +var privKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEII97KXBANi9c3EjrYmjAGOqTG40yIBnIsGRHjHJpZp2ToAoGCCqGSM49 +AwEHoUQDQgAEjpDmYI4+tNGyOncpxWKfPs8mirDYOft1TEC43DTCN5vCSfupyBS7 +ZKgUUjg4E0Aq5SIJENqeRP3tTko8O3VZYQ== +-----END EC PRIVATE KEY-----` + +var pubKey = `-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEjpDmYI4+tNGyOncpxWKfPs8mirDY +Oft1TEC43DTCN5vCSfupyBS7ZKgUUjg4E0Aq5SIJENqeRP3tTko8O3VZYQ== +-----END PUBLIC KEY-----` + +func TestJWT(t *testing.T) { + a := New(t) + + jwt, err := BuildJWT("the-subject", time.Second, []byte(privKey)) + a.So(err, ShouldBeNil) + + claims, err := ValidateJWT(jwt, []byte(pubKey)) + a.So(err, ShouldBeNil) + + a.So(claims.Subject, ShouldEqual, "the-subject") + a.So(claims.Issuer, ShouldEqual, "the-subject") + + // Wrong private key + _, err = ValidateJWT(jwt, []byte("this is no key")) + a.So(err, ShouldNotBeNil) + + // Wrong algorithm + _, err = ValidateJWT("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.Rq8IxqeX7eA6GgYxlcHdPFVRNFFZc5rEI3MQTZZbK3I", []byte(pubKey)) + a.So(err, ShouldNotBeNil) + + // Wrong signature + _, err = ValidateJWT("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzY4MDE0MDUsImlhdCI6MTQ3NjgwMTMyNSwiaXNzIjoidGhlLXN1YmplY3QiLCJuYmYiOjE0NzY4MDEzMjUsInN1YiI6InRoZS1zdWJqZWN0In0.4YudFUVQL4ODy8MMHWZrdB3CCgZedCD7FMUu2iPF4O1WIvptKaUyp9lBu-Eo2SfuNXcTIa1CiOiye36aeelCEw", []byte(pubKey)) + a.So(err, ShouldNotBeNil) + + // Expired + _, err = ValidateJWT("eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0NzY4MDE1NzEsImlhdCI6MTQ3NjgwMTU1MCwiaXNzIjoidGhlLXN1YmplY3QiLCJuYmYiOjE0NzY4MDE1NTAsInN1YiI6InRoZS1zdWJqZWN0In0.AsKUzs9kenfqmDEtLXvNk3Akf_dkfU-8Zy8brHRawsOr64LxA0Mfb2Ufxwzk0JQr5Rtigw2RAVGirFyZI5meBQ", []byte(pubKey)) + a.So(err, ShouldNotBeNil) +} diff --git a/utils/security/security_test.go b/utils/security/security_test.go index 3c8a4b4bc..1a99d38bb 100644 --- a/utils/security/security_test.go +++ b/utils/security/security_test.go @@ -1 +1,48 @@ package security + +import ( + "io/ioutil" + "os" + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestKeyPairFuncs(t *testing.T) { + a := New(t) + + location := os.TempDir() + + err := GenerateKeypair(location) + a.So(err, ShouldBeNil) + + _, err = LoadKeypair(location + "/derp") + a.So(err, ShouldNotBeNil) + + key, err := LoadKeypair(location) + a.So(err, ShouldBeNil) + a.So(key, ShouldNotBeNil) + + ioutil.WriteFile(location+"/server.key", []byte{}, 0644) + + _, err = LoadKeypair(location) + a.So(err, ShouldNotBeNil) +} + +func TestCertFuncs(t *testing.T) { + a := New(t) + + location := os.TempDir() + + GenerateKeypair(location) + + err := GenerateCert(location, "localhost") + a.So(err, ShouldBeNil) + + _, err = LoadCert(location + "/derp") + a.So(err, ShouldNotBeNil) + + cert, err := LoadCert(location) + a.So(err, ShouldBeNil) + a.So(cert, ShouldNotBeNil) +} From b95a0a33db1959854a525f3f8c16caf832bf840b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 16:51:15 +0200 Subject: [PATCH 1989/2266] Remove stats util package (dead code) --- utils/stats/registry.go | 183 ---------------------------------------- utils/stats/stats.go | 69 --------------- utils/stats/string.go | 110 ------------------------ 3 files changed, 362 deletions(-) delete mode 100644 utils/stats/registry.go delete mode 100644 utils/stats/stats.go delete mode 100644 utils/stats/string.go diff --git a/utils/stats/registry.go b/utils/stats/registry.go deleted file mode 100644 index 9eaecaaea..000000000 --- a/utils/stats/registry.go +++ /dev/null @@ -1,183 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package stats - -import ( - "fmt" - "reflect" - "sync" - - "github.com/rcrowley/go-metrics" -) - -// Ticker has a Tick() func -type Ticker interface { - Tick() - SetDefaultTTL(uint) - SetTTL(string, uint) - Renew(string) -} - -// DefaultTTL for the registry -var DefaultTTL uint = 0 - -// Registry is the default metrics registry -var Registry = NewRegistry() - -// TTNRegistry is a mutex-protected map of names to metrics. -// It is basically an extension of https://github.com/rcrowley/go-metrics/blob/master/registry.go -type TTNRegistry struct { - metrics map[string]interface{} - timeouts map[string]uint - defaultTTL uint - mutex sync.Mutex -} - -// NewRegistry reates a new registry and starts a goroutine for the TTL. -func NewRegistry() metrics.Registry { - return &TTNRegistry{ - metrics: make(map[string]interface{}), - timeouts: make(map[string]uint), - defaultTTL: DefaultTTL, - } -} - -// Each calls the given function for each registered metric. -func (r *TTNRegistry) Each(f func(string, interface{})) { - for name, i := range r.registered() { - f(name, i) - } -} - -// Get the metric by the given name or nil if none is registered. -func (r *TTNRegistry) Get(name string) interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() - return r.metrics[name] -} - -// GetOrRegister gets an existing metric or creates and registers a new one. Threadsafe -// alternative to calling Get and Register on failure. -// The interface can be the metric to register if not found in registry, -// or a function returning the metric for lazy instantiation. -func (r *TTNRegistry) GetOrRegister(name string, i interface{}) interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() - if metric, ok := r.metrics[name]; ok { - return metric - } - if v := reflect.ValueOf(i); v.Kind() == reflect.Func { - i = v.Call(nil)[0].Interface() - } - r.register(name, i) - return i -} - -// Register the given metric under the given name. Returns a DuplicateMetric -// if a metric by the given name is already registered. -func (r *TTNRegistry) Register(name string, i interface{}) error { - r.mutex.Lock() - defer r.mutex.Unlock() - return r.register(name, i) -} - -// RunHealthchecks runs all registered healthchecks. -func (r *TTNRegistry) RunHealthchecks() { - r.mutex.Lock() - defer r.mutex.Unlock() - for _, i := range r.metrics { - if h, ok := i.(metrics.Healthcheck); ok { - h.Check() - } - } -} - -// Unregister the metric with the given name. -func (r *TTNRegistry) Unregister(name string) { - r.mutex.Lock() - defer r.mutex.Unlock() - delete(r.metrics, name) -} - -// UnregisterAll unregisters all metrics. (Mostly for testing.) -func (r *TTNRegistry) UnregisterAll() { - r.mutex.Lock() - defer r.mutex.Unlock() - for name := range r.metrics { - delete(r.metrics, name) - } -} - -// Tick decreases the TTL of all metrics that have one -// If the TTL becomes zero, the metric is deleted -func (r *TTNRegistry) Tick() { - r.mutex.Lock() - defer r.mutex.Unlock() - for name, ttl := range r.timeouts { - r.timeouts[name] = ttl - 1 - if ttl == 0 { - delete(r.metrics, name) - delete(r.timeouts, name) - } - } -} - -// SetDefaultTTL sets a default TTL of a number of ticks for all new metrics -func (r *TTNRegistry) SetDefaultTTL(ticks uint) { - r.defaultTTL = ticks -} - -// SetTTL sets a TTL of a number of ticks for a given metric -func (r *TTNRegistry) SetTTL(name string, ticks uint) { - r.mutex.Lock() - defer r.mutex.Unlock() - if _, ok := r.metrics[name]; ok { - // Ignore if the existing ttl is higher than the new ttl - if ttl, ok := r.timeouts[name]; ok && ttl > ticks { - return - } - r.timeouts[name] = ticks - } -} - -// Renew sets the TTL of a metric to the default value -func (r *TTNRegistry) Renew(name string) { - if r.defaultTTL == 0 { - return - } - - r.mutex.Lock() - defer r.mutex.Unlock() - - _, ok := r.metrics[name] - ttl, _ := r.timeouts[name] - - if ok && r.defaultTTL > ttl { - r.timeouts[name] = r.defaultTTL - } -} - -func (r *TTNRegistry) register(name string, i interface{}) error { - if _, ok := r.metrics[name]; ok { - return fmt.Errorf("duplicate metric: %s", name) - } - switch i.(type) { - case metrics.Counter, metrics.Gauge, metrics.GaugeFloat64, metrics.Healthcheck, metrics.Histogram, metrics.Meter, metrics.Timer, String: - r.metrics[name] = i - if r.defaultTTL > 0 { - r.timeouts[name] = r.defaultTTL - } - } - return nil -} - -func (r *TTNRegistry) registered() map[string]interface{} { - r.mutex.Lock() - defer r.mutex.Unlock() - metrics := make(map[string]interface{}, len(r.metrics)) - for name, i := range r.metrics { - metrics[name] = i - } - return metrics -} diff --git a/utils/stats/stats.go b/utils/stats/stats.go deleted file mode 100644 index 0fea929d7..000000000 --- a/utils/stats/stats.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -// Package stats supports the collection of metrics from a running component. -package stats - -import ( - "time" - - "github.com/rcrowley/go-metrics" -) - -// Enabled activates stats collection -var Enabled = true - -// Initialize the stats -func Initialize() { - go func() { - c := time.Tick(1 * time.Minute) - for _ = range c { - Registry.(Ticker).Tick() - } - }() -} - -// MarkMeter registers an event -func MarkMeter(name string) { - if Enabled { - metrics.GetOrRegisterMeter(name, Registry).Mark(1) - Registry.(Ticker).Renew(name) - } -} - -// UpdateHistogram registers a new value for a histogram -func UpdateHistogram(name string, value int64) { - if Enabled { - metrics.GetOrRegisterHistogram(name, Registry, metrics.NewUniformSample(1000)).Update(value) - Registry.(Ticker).Renew(name) - } -} - -// IncCounter increments a counter by 1 -func IncCounter(name string) { - if Enabled { - metrics.GetOrRegisterCounter(name, Registry).Inc(1) - Registry.(Ticker).Renew(name) - } -} - -// DecCounter decrements a counter by 1 -func DecCounter(name string) { - if Enabled { - metrics.GetOrRegisterCounter(name, Registry).Dec(1) - Registry.(Ticker).Renew(name) - } -} - -// SetString ... -func SetString(name, tag, value string) { - if Enabled { - GetOrRegisterString(name, Registry).Set(tag, value) - Registry.(Ticker).Renew(name) - } -} - -// SetTimeout ... -func SetTimeout(name string, ttl uint) { - Registry.(Ticker).SetTTL(name, ttl) -} diff --git a/utils/stats/string.go b/utils/stats/string.go deleted file mode 100644 index adb4c612d..000000000 --- a/utils/stats/string.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package stats - -import ( - "sync" - - "github.com/rcrowley/go-metrics" -) - -// String stores a string -type String interface { - Get() map[string]string - Set(string, string) - Snapshot() String -} - -// GetOrRegisterString returns an existing String or constructs and registers a -// new StandardString. -func GetOrRegisterString(name string, r metrics.Registry) String { - if nil == r { - r = metrics.DefaultRegistry - } - return r.GetOrRegister(name, NewString).(String) -} - -// NewString constructs a new StandardString. -func NewString() String { - if metrics.UseNilMetrics { - return NilString{} - } - s := newStandardString() - return s -} - -// NewRegisteredString constructs and registers a new StandardString. -func NewRegisteredString(name string, r metrics.Registry) String { - c := NewString() - if nil == r { - r = metrics.DefaultRegistry - } - r.Register(name, c) - return c -} - -// StringSnapshot is a read-only copy of another String. -type StringSnapshot struct { - values map[string]string -} - -// Get returns the value of events at the time the snapshot was taken. -func (m *StringSnapshot) Get() map[string]string { return m.values } - -// Set panics. -func (*StringSnapshot) Set(t, v string) { - panic("Set called on a StringSnapshot") -} - -// Snapshot returns the snapshot. -func (m *StringSnapshot) Snapshot() String { return m } - -// NilString is a no-op String. -type NilString struct{} - -// Get is a no-op. -func (NilString) Get() map[string]string { return map[string]string{} } - -// Set is a no-op. -func (NilString) Set(t, s string) {} - -// Snapshot is a no-op. -func (NilString) Snapshot() String { return NilString{} } - -// StandardString is the standard implementation of a String. -type StandardString struct { - lock sync.RWMutex - snapshot *StringSnapshot -} - -func newStandardString() *StandardString { - return &StandardString{ - snapshot: &StringSnapshot{ - values: map[string]string{}, - }, - } -} - -// Get returns the number of events recorded. -func (m *StandardString) Get() map[string]string { - m.lock.RLock() - value := m.snapshot.values - m.lock.RUnlock() - return value -} - -// Set sets the String to the given value. -func (m *StandardString) Set(tag, str string) { - m.lock.Lock() - defer m.lock.Unlock() - m.snapshot.values[tag] = str -} - -// Snapshot returns a read-only copy of the string. -func (m *StandardString) Snapshot() String { - m.lock.RLock() - snapshot := *m.snapshot - m.lock.RUnlock() - return &snapshot -} From f5b6a887dcb8d80758e1b76c7ff460df5f5feffc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 16:54:54 +0200 Subject: [PATCH 1990/2266] types.AppKey is always 16 bytes so we don't need this check --- utils/otaa/session_keys.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/utils/otaa/session_keys.go b/utils/otaa/session_keys.go index b137b6a70..2838fcadb 100644 --- a/utils/otaa/session_keys.go +++ b/utils/otaa/session_keys.go @@ -7,7 +7,6 @@ import ( "crypto/aes" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/utils/errors" ) // CalculateSessionKeys calculates the AppSKey and NwkSKey @@ -19,12 +18,7 @@ func CalculateSessionKeys(appKey types.AppKey, appNonce [3]byte, netID [3]byte, copy(buf[4:7], reverse(netID[:])) copy(buf[7:9], reverse(devNonce[:])) - block, err := aes.NewCipher(appKey[:]) - - if err != nil || block.BlockSize() != 16 { - err = errors.NewErrInternal("Unable to create cipher to generate keys") - return - } + block, _ := aes.NewCipher(appKey[:]) buf[0] = 0x1 block.Encrypt(nwkSKey[:], buf) From 8b1a1c2d43a8f88ed8dbe3af5673b9648d8473f0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 16:56:20 +0200 Subject: [PATCH 1991/2266] Test for invalid input in toa package --- utils/toa/toa_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go index 7daed8621..0b277349f 100644 --- a/utils/toa/toa_test.go +++ b/utils/toa/toa_test.go @@ -16,6 +16,12 @@ func TestComputeLoRa(t *testing.T) { var toa time.Duration var err error + _, err = ComputeLoRa(10, "SFWUT", "4/5") + a.So(err, ShouldNotBeNil) + + _, err = ComputeLoRa(10, "SF10BW125", "1/9") + a.So(err, ShouldNotBeNil) + // Test different SFs sfTests := map[string]uint{ "SF7BW125": 41216, From e9778b206e1fc6d9d223283a1ad1e1e26812f369 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 17:58:08 +0200 Subject: [PATCH 1992/2266] Test coverage in core/types --- core/types/data_rate.go | 5 +++-- core/types/data_rate_test.go | 23 +++++++++++++++++++++ core/types/dev_addr.go | 3 ++- core/types/dev_addr_test.go | 16 ++++++++++++++- core/types/eui_test.go | 30 +++++++++++++++++++++++++++ core/types/keys_test.go | 40 ++++++++++++++++++++++++++++++++++++ core/types/parse_hex.go | 10 ++------- core/types/parse_hex_test.go | 24 ++++++++++++++++++++++ 8 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 core/types/parse_hex_test.go diff --git a/core/types/data_rate.go b/core/types/data_rate.go index 786e497f6..27510b682 100644 --- a/core/types/data_rate.go +++ b/core/types/data_rate.go @@ -5,10 +5,11 @@ package types import ( "fmt" - "github.com/TheThingsNetwork/ttn/utils/errors" "regexp" "strconv" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan/band" ) @@ -36,7 +37,7 @@ func ParseDataRate(input string) (datr *DataRate, err error) { func ConvertDataRate(input band.DataRate) (datr *DataRate, err error) { if input.Modulation != band.LoRaModulation { - err = errors.New("ttn/core: FSK not yet supported") + err = errors.New(fmt.Sprintf("ttn/core: %s can not be converted to a LoRa DataRate", input.Modulation)) } datr = &DataRate{ SpreadingFactor: uint(input.SpreadFactor), diff --git a/core/types/data_rate_test.go b/core/types/data_rate_test.go index c013f119f..13e6ca407 100644 --- a/core/types/data_rate_test.go +++ b/core/types/data_rate_test.go @@ -6,6 +6,7 @@ package types import ( "testing" + "github.com/brocaar/lorawan/band" . "github.com/smartystreets/assertions" ) @@ -38,17 +39,39 @@ func TestDataRate(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 8) + _, err = datr.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := datr.Size() + a.So(s, ShouldEqual, 8) + // Parse pOut, err := ParseDataRate(str) a.So(err, ShouldBeNil) a.So(*pOut, ShouldResemble, datr) + _, err = ParseDataRate("") + a.So(err, ShouldNotBeNil) + + // Convert + dr := band.DataRate{Modulation: band.LoRaModulation, SpreadFactor: 7, Bandwidth: 125} + cOut, err := ConvertDataRate(dr) + a.So(err, ShouldBeNil) + a.So(*cOut, ShouldResemble, datr) + // UnmarshalText utOut := &DataRate{} err = utOut.UnmarshalText([]byte(str)) a.So(err, ShouldBeNil) a.So(*utOut, ShouldResemble, datr) + err = utOut.UnmarshalText([]byte("")) + a.So(err, ShouldNotBeNil) + // UnmarshalBinary ubOut := &DataRate{} err = ubOut.UnmarshalBinary(bin) diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 899850bd8..7cd1d5085 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -6,10 +6,11 @@ package types import ( "encoding/hex" "fmt" - "github.com/TheThingsNetwork/ttn/utils/errors" "regexp" "strconv" "strings" + + "github.com/TheThingsNetwork/ttn/utils/errors" ) // DevAddr is a non-unique address for LoRaWAN devices. diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index 4dcdd315a..0dda1c4a4 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -38,11 +38,24 @@ func TestDevAddr(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 4) + _, err = addr.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := addr.Size() + a.So(s, ShouldEqual, 4) + // Parse pOut, err := ParseDevAddr(str) a.So(err, ShouldBeNil) a.So(pOut, ShouldEqual, addr) + _, err = ParseDevAddr("no-dev-addr") + a.So(err, ShouldNotBeNil) + // UnmarshalText utOut := &DevAddr{} err = utOut.UnmarshalText([]byte(str)) @@ -61,10 +74,11 @@ func TestDevAddr(t *testing.T) { a.So(err, ShouldBeNil) a.So(*uOut, ShouldEqual, addr) - // IsEmpty + // Empty var empty DevAddr a.So(empty.IsEmpty(), ShouldEqual, true) a.So(addr.IsEmpty(), ShouldEqual, false) + a.So(empty.String(), ShouldEqual, "") } func TestDevAddrMask(t *testing.T) { diff --git a/core/types/eui_test.go b/core/types/eui_test.go index 4890acc80..227567912 100644 --- a/core/types/eui_test.go +++ b/core/types/eui_test.go @@ -38,6 +38,16 @@ func TestEUI64(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 8) + _, err = eui.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := eui.Size() + a.So(s, ShouldEqual, 8) + // Parse pOut, err := ParseEUI64(str) a.So(err, ShouldBeNil) @@ -96,6 +106,16 @@ func TestAppEUI(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 8) + _, err = eui.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := eui.Size() + a.So(s, ShouldEqual, 8) + // Parse pOut, err := ParseAppEUI(str) a.So(err, ShouldBeNil) @@ -154,6 +174,16 @@ func TestDevEUI(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 8) + _, err = eui.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := eui.Size() + a.So(s, ShouldEqual, 8) + // Parse pOut, err := ParseDevEUI(str) a.So(err, ShouldBeNil) diff --git a/core/types/keys_test.go b/core/types/keys_test.go index 04b442510..5c637611e 100644 --- a/core/types/keys_test.go +++ b/core/types/keys_test.go @@ -38,6 +38,16 @@ func TestAES128Key(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + // Parse pOut, err := ParseAES128Key(str) a.So(err, ShouldBeNil) @@ -96,6 +106,16 @@ func TestAppKey(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + // Parse pOut, err := ParseAppKey(str) a.So(err, ShouldBeNil) @@ -154,6 +174,16 @@ func TestNwkSKey(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + // Parse pOut, err := ParseNwkSKey(str) a.So(err, ShouldBeNil) @@ -212,6 +242,16 @@ func TestAppSKey(t *testing.T) { a.So(err, ShouldBeNil) a.So(mOut, ShouldResemble, bin) + // MarshalTo + bOut := make([]byte, 16) + _, err = key.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := key.Size() + a.So(s, ShouldEqual, 16) + // Parse pOut, err := ParseAppSKey(str) a.So(err, ShouldBeNil) diff --git a/core/types/parse_hex.go b/core/types/parse_hex.go index 4820b53e2..8ad04f4db 100644 --- a/core/types/parse_hex.go +++ b/core/types/parse_hex.go @@ -15,20 +15,14 @@ func ParseHEX(input string, length int) ([]byte, error) { return make([]byte, length), nil } - pattern, err := regexp.Compile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) - if err != nil { - return nil, fmt.Errorf("Invalid pattern") - } + pattern := regexp.MustCompile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) valid := pattern.MatchString(input) if !valid { return nil, fmt.Errorf("Invalid input: %s", input) } - slice, err := hex.DecodeString(input) - if err != nil { - return nil, fmt.Errorf("Could not decode input: %s", input) - } + slice, _ := hex.DecodeString(input) return slice, nil } diff --git a/core/types/parse_hex_test.go b/core/types/parse_hex_test.go new file mode 100644 index 000000000..14c18ba82 --- /dev/null +++ b/core/types/parse_hex_test.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestParseHex(t *testing.T) { + a := New(t) + b, err := ParseHEX("AABC", 2) + a.So(err, ShouldBeNil) + a.So(b, ShouldResemble, []byte{0xaa, 0xbc}) + + b, err = ParseHEX("", 2) + a.So(err, ShouldBeNil) + a.So(b, ShouldResemble, []byte{0x00, 0x00}) + + _, err = ParseHEX("ab", 2) + a.So(err, ShouldNotBeNil) +} From 12bb8d5eee9aa8f9bb23513056f80fea5a8638fc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 18:04:36 +0200 Subject: [PATCH 1993/2266] Update vendors --- vendor/vendor.json | 404 +++++++++++++++++++++------------------------ 1 file changed, 187 insertions(+), 217 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index e9c5360e4..70785287f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,63 +9,63 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "LxBmvOQzHVoY2cdh8vflnzARtNU=", + "checksumSHA1": "nOFUap/+7NNA/tWGsTOzlXb1ZWU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { "checksumSHA1": "ANS1jzUqsv1Q1X0gZNzSe9wY0Fo=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { - "checksumSHA1": "BKotLY522RCIk0OHwfZHdOOicOs=", + "checksumSHA1": "kD6p9T1bgMU/33BG8czy2e9CJRw=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "b4d794c504fcfc3406b9c7edcf74b2992380c96e", - "revisionTime": "2016-10-14T09:56:50Z" + "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", + "revisionTime": "2016-10-18T12:27:50Z" }, { - "checksumSHA1": "klPzXuUi+zvLz4ADQcPzlJsepVc=", + "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", - "revision": "ca9398a9d975d5c912f87b4869c3bc82091034df", + "revision": "0c68f91c240f5a01058bfd605923cd35aa37ff92", "revisionTime": "2016-10-18T13:04:05Z" }, { @@ -99,10 +99,10 @@ "revisionTime": "2016-10-01T16:31:30Z" }, { - "checksumSHA1": "rVZEfQyYQxiivnzH6ZWzcqrbi6o=", + "checksumSHA1": "hqQV+0W1kp33/SBnZDlJkZ98Jcs=", "path": "github.com/bluele/gcache", - "revision": "69623c269a10cc02d8f770133ca36357c0877d81", - "revisionTime": "2016-07-16T13:40:25Z" + "revision": "b8e8e90c3718b76ff835a8b78ddcc859815a0222", + "revisionTime": "2016-10-07T16:21:42Z" }, { "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", @@ -125,14 +125,14 @@ { "checksumSHA1": "PPyNjcW5yfrfXvARE74fh9JBSpo=", "path": "github.com/eclipse/paho.mqtt.golang", - "revision": "5d3aa4073fcbdaf5fcacf2c3f033ca6448e0bd85", - "revisionTime": "2016-10-04T07:53:38Z" + "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", + "revisionTime": "2016-10-12T12:34:52Z" }, { "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", "path": "github.com/eclipse/paho.mqtt.golang/packets", - "revision": "5d3aa4073fcbdaf5fcacf2c3f033ca6448e0bd85", - "revisionTime": "2016-10-04T07:53:38Z" + "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", + "revisionTime": "2016-10-12T12:34:52Z" }, { "checksumSHA1": "KCWVxG+J8SxHGlGiUghe0KBGsa8=", @@ -141,10 +141,10 @@ "revisionTime": "2016-08-07T23:55:29Z" }, { - "checksumSHA1": "e2o/P8ZZ8Iz+um6/nyLUEg7S2H8=", + "checksumSHA1": "nmc2qcdbg17Ny8tHeEDQuphV6jA=", "path": "github.com/fsnotify/fsnotify", - "revision": "944cff21b3baf3ced9a880365682152ba577d348", - "revisionTime": "2016-10-05T04:06:20Z" + "revision": "bd2828f9f176e52d7222e565abb2d338d3f3c103", + "revisionTime": "2016-10-13T01:22:19Z" }, { "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", @@ -161,20 +161,20 @@ { "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", "path": "github.com/gogo/protobuf/gogoproto", - "revision": "fdc14ac22689d09f8639e603614593811bc1d81c", - "revisionTime": "2016-10-01T10:27:20Z" + "revision": "50d1bd39ce4e7a96b75e3e040be9caf79dbb4c61", + "revisionTime": "2016-10-14T17:32:44Z" }, { "checksumSHA1": "2eIRbxeMUEF1ZnOanJhXmFR7pFs=", "path": "github.com/gogo/protobuf/proto", - "revision": "fdc14ac22689d09f8639e603614593811bc1d81c", - "revisionTime": "2016-10-01T10:27:20Z" + "revision": "50d1bd39ce4e7a96b75e3e040be9caf79dbb4c61", + "revisionTime": "2016-10-14T17:32:44Z" }, { "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", - "revision": "fdc14ac22689d09f8639e603614593811bc1d81c", - "revisionTime": "2016-10-01T10:27:20Z" + "revision": "50d1bd39ce4e7a96b75e3e040be9caf79dbb4c61", + "revisionTime": "2016-10-14T17:32:44Z" }, { "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", @@ -183,16 +183,16 @@ "revisionTime": "2016-01-21T18:51:14Z" }, { - "checksumSHA1": "qjr2SKQanbmna221z0Ce2n0hnDE=", + "checksumSHA1": "SVXOQdpDBh0ihdZ5aIflgdA+Rpw=", "path": "github.com/golang/protobuf/proto", - "revision": "df1d3ca07d2d07bba352d5b73c4313b4e2a6203e", - "revisionTime": "2016-09-27T20:09:49Z" + "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", + "revisionTime": "2016-10-12T20:53:35Z" }, { "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", - "revision": "df1d3ca07d2d07bba352d5b73c4313b4e2a6203e", - "revisionTime": "2016-09-27T20:09:49Z" + "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", + "revisionTime": "2016-10-12T20:53:35Z" }, { "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", @@ -215,56 +215,56 @@ { "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", "path": "github.com/hashicorp/hcl", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { - "checksumSHA1": "67DfevLBglV52Y2eAuhFc/xQni0=", + "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { - "checksumSHA1": "l2oQxBsZRwn6eZjf+whXr8c9+8c=", + "checksumSHA1": "un4pN4yL5bl6LL3CgWacFbIeHVg=", "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { - "checksumSHA1": "jQ45CCc1ed/nlV7bbSnx6z72q1M=", + "checksumSHA1": "fpQQdjFUZOoslYuFNKZMSO0N0ik=", "path": "github.com/hashicorp/hcl/json/parser", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", - "revision": "ef8133da8cda503718a74741312bf50821e6de79", - "revisionTime": "2016-09-16T13:01:00Z" + "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", + "revisionTime": "2016-10-08T07:35:57Z" }, { "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", @@ -309,10 +309,10 @@ "revisionTime": "2016-09-08T09:36:58Z" }, { - "checksumSHA1": "DdH3xAkzAWJ4B/LGYJyCeRsly2I=", + "checksumSHA1": "yf185lmVPcvXjLZuPT1s4JzxI18=", "path": "github.com/mattn/go-runewidth", - "revision": "d6bea18f789704b5f83375793155289da36a3c7f", - "revisionTime": "2016-03-15T04:07:12Z" + "revision": "737072b4e32b7a5018b4a7125da8d12de90e8045", + "revisionTime": "2016-10-12T01:35:12Z" }, { "checksumSHA1": "AXacfEchaUqT5RGmPmMXsOWRhv8=", @@ -321,10 +321,10 @@ "revisionTime": "2016-06-21T17:42:43Z" }, { - "checksumSHA1": "LUrnGREfnifW4WDMaavmc9MlLI0=", + "checksumSHA1": "HzhmHrGdk67cMJ2xt5ToPxhwLWk=", "path": "github.com/mitchellh/mapstructure", - "revision": "ca63d7c062ee3c9f34db231e352b60012b4fd0c1", - "revisionTime": "2016-08-08T18:12:53Z" + "revision": "a6ef2f080c66d0a2e94e97cf74f80f772855da63", + "revisionTime": "2016-10-06T23:39:02Z" }, { "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", @@ -405,46 +405,46 @@ "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "ys3WakqgBbJBZBQ7FCbxW26eSAw=", + "checksumSHA1": "iYQIw8g0QguREYVFkLXU3r57P24=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", "path": "github.com/shirou/gopsutil/host", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", "path": "github.com/shirou/gopsutil/load", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", "path": "github.com/shirou/gopsutil/mem", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { - "checksumSHA1": "Fa7lUUadHW9402ifjheuSFDkufo=", + "checksumSHA1": "0f/OGYrQuJq0DCw2MdCpDXhg/LA=", "path": "github.com/shirou/gopsutil/net", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { - "checksumSHA1": "k1ug9JDvES8wNqxaQcO7qxgpOKc=", + "checksumSHA1": "Cmj97derBOe/m/D2Db++Z57uWBw=", "path": "github.com/shirou/gopsutil/process", - "revision": "14eb7acb2355d760c7cc114df03042b3cb630d67", - "revisionTime": "2016-09-30T12:56:23Z" + "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", + "revisionTime": "2016-10-15T12:41:39Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -501,10 +501,10 @@ "revisionTime": "2016-09-26T08:42:49Z" }, { - "checksumSHA1": "fEGgcE+iSzLkxdrbRj7j/vV7a7E=", + "checksumSHA1": "FZ0r4TzEy9UxXLkFVXFygApni4M=", "path": "github.com/spf13/cobra", - "revision": "9c28e4bbd74e5c3ed7aacbc552b2cab7cfdfe744", - "revisionTime": "2016-08-30T17:49:25Z" + "revision": "856b96dcb49d6427babe192998a35190a12c2230", + "revisionTime": "2016-10-14T22:20:36Z" }, { "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", @@ -513,16 +513,16 @@ "revisionTime": "2016-03-01T12:00:06Z" }, { - "checksumSHA1": "b1FgvXRMwcr3D1litcrDNxgowBw=", + "checksumSHA1": "1wuE9EGcNZMuQ1fWRZ4Z/TPXjzQ=", "path": "github.com/spf13/pflag", - "revision": "c7e63cf4530bcd3ba943729cee0efeff2ebea63f", - "revisionTime": "2016-09-15T15:31:01Z" + "revision": "bf8481a6aebc13a8aab52e699ffe2e79771f5a3f", + "revisionTime": "2016-10-11T12:08:26Z" }, { - "checksumSHA1": "c95O+eiZqqbHBDb8oy+RqzUBzRg=", + "checksumSHA1": "2EeKIC5kUssQK8g49DOa78FoMgs=", "path": "github.com/spf13/viper", - "revision": "382f87b929b84ce13e9c8a375a4b217f224e6c65", - "revisionTime": "2016-09-26T15:04:02Z" + "revision": "50515b700e02658272117a72bd641b6b7f1222e5", + "revisionTime": "2016-10-14T09:24:45Z" }, { "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", @@ -545,206 +545,206 @@ { "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", "path": "golang.org/x/crypto/curve25519", - "revision": "7682e7e3945130cf3cde089834664f68afdd1523", - "revisionTime": "2016-10-03T20:54:26Z" + "revision": "14f9af67c679edd414f72f13d67c917447113df2", + "revisionTime": "2016-10-17T19:14:20Z" }, { "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "path": "golang.org/x/crypto/ed25519", - "revision": "7682e7e3945130cf3cde089834664f68afdd1523", - "revisionTime": "2016-10-03T20:54:26Z" + "revision": "14f9af67c679edd414f72f13d67c917447113df2", + "revisionTime": "2016-10-17T19:14:20Z" }, { "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "7682e7e3945130cf3cde089834664f68afdd1523", - "revisionTime": "2016-10-03T20:54:26Z" + "revision": "14f9af67c679edd414f72f13d67c917447113df2", + "revisionTime": "2016-10-17T19:14:20Z" }, { - "checksumSHA1": "1LydpuiE3oBdkbYvSdKKwe9lsLs=", + "checksumSHA1": "LlElMHeTC34ng8eHzjvtUhAgrr8=", "path": "golang.org/x/crypto/ssh", - "revision": "7682e7e3945130cf3cde089834664f68afdd1523", - "revisionTime": "2016-10-03T20:54:26Z" + "revision": "14f9af67c679edd414f72f13d67c917447113df2", + "revisionTime": "2016-10-17T19:14:20Z" }, { "checksumSHA1": "9C4Av3ypK5pi173F76ogJT/d8x4=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "7682e7e3945130cf3cde089834664f68afdd1523", - "revisionTime": "2016-10-03T20:54:26Z" + "revision": "14f9af67c679edd414f72f13d67c917447113df2", + "revisionTime": "2016-10-17T19:14:20Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { "checksumSHA1": "TUt1YBoSgtklP3MKWuOJwXBrkRw=", "path": "golang.org/x/net/http2", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { - "checksumSHA1": "TSgie2Sfk7n2xAssjIGa4JwY7Lk=", + "checksumSHA1": "4MMbG0LI3ghvWooRn36RmDrFIB0=", "path": "golang.org/x/net/trace", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { - "checksumSHA1": "sM7kVFxASf7lzSkUI9sF6YClGaA=", + "checksumSHA1": "O2sws+miRriMPObINsbwa0jnXxE=", "path": "golang.org/x/net/websocket", - "revision": "ffe101cce3477a6c6d8f0754d103bb0a84ec1266", - "revisionTime": "2016-10-03T09:22:05Z" + "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", + "revisionTime": "2016-10-13T03:31:11Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", - "revision": "3c3a985cb79f52a3190fbc056984415ca6763d01", - "revisionTime": "2016-08-26T23:14:08Z" + "revision": "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5", + "revisionTime": "2016-10-06T21:47:20Z" }, { "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", "path": "golang.org/x/oauth2/internal", - "revision": "3c3a985cb79f52a3190fbc056984415ca6763d01", - "revisionTime": "2016-08-26T23:14:08Z" + "revision": "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5", + "revisionTime": "2016-10-06T21:47:20Z" }, { - "checksumSHA1": "vu7njn8O4vhFtmgGWntwjA2NAbk=", + "checksumSHA1": "e6tx+mrbPNlzXbr1VErnBPY+vmQ=", "path": "golang.org/x/sys/unix", - "revision": "8f0908ab3b2457e2e15403d3697c9ef5cb4b57a9", - "revisionTime": "2016-09-14T20:33:16Z" + "revision": "002cbb5f952456d0c50e0d2aff17ea5eca716979", + "revisionTime": "2016-10-16T18:36:00Z" }, { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "098f51fb687dbaba1f6efabeafbb6461203f9e21", - "revisionTime": "2016-09-28T15:11:56Z" + "revision": "a35b82c6add3543753645decf6532c40cac8b855", + "revisionTime": "2016-10-18T11:00:34Z" }, { "checksumSHA1": "Aj3JSVO324FCjEAGm4ZwmC79bbo=", "path": "golang.org/x/text/unicode/norm", - "revision": "098f51fb687dbaba1f6efabeafbb6461203f9e21", - "revisionTime": "2016-09-28T15:11:56Z" + "revision": "a35b82c6add3543753645decf6532c40cac8b855", + "revisionTime": "2016-10-18T11:00:34Z" }, { - "checksumSHA1": "Iq/i8Bsr/xrLXTT14mBcyLYOyrY=", + "checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=", "path": "google.golang.org/appengine/internal", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", "path": "google.golang.org/appengine/internal/base", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", "path": "google.golang.org/appengine/internal/datastore", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", "path": "google.golang.org/appengine/internal/log", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", "path": "google.golang.org/appengine/internal/remote_api", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", "path": "google.golang.org/appengine/internal/urlfetch", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", "path": "google.golang.org/appengine/urlfetch", - "revision": "926995697fa8241be2dc73eb318666e24f44ed51", - "revisionTime": "2016-09-22T22:46:31Z" + "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", + "revisionTime": "2016-10-14T20:17:33Z" }, { - "checksumSHA1": "N0GmUbip6LljIQkixSZnQ7a76Fs=", + "checksumSHA1": "D9M/719krq7NJYQu523UODpBKcI=", "path": "google.golang.org/grpc", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", "path": "google.golang.org/grpc/metadata", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { - "checksumSHA1": "dzbx9oVtSfVgNE3lTl+r5xOXxcw=", + "checksumSHA1": "HQJrtiTtr5eiRsXQLut2R1Q9kuY=", "path": "google.golang.org/grpc/transport", - "revision": "b1a2821ca5a4fd6b6e48ddfbb7d6d7584d839d21", - "revisionTime": "2016-10-03T17:23:44Z" + "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", + "revisionTime": "2016-10-17T23:02:05Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", @@ -753,76 +753,46 @@ "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "/EL/UuzIPObHgESjkBMaD4gaXOw=", - "path": "gopkg.in/redis.v3", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" - }, - { - "checksumSHA1": "b4Z72l8sq4xuWhR+nM5vbLQIv+o=", - "path": "gopkg.in/redis.v3/internal", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" - }, - { - "checksumSHA1": "tNI/mAuqqQqfYU4vuJghUroIts4=", - "path": "gopkg.in/redis.v3/internal/consistenthash", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" - }, - { - "checksumSHA1": "JvqkSear94dc+NgofRqBAUmLcN0=", - "path": "gopkg.in/redis.v3/internal/hashtag", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" - }, - { - "checksumSHA1": "ROpD4/McQLnnxpPenjA0mEZfTL8=", - "path": "gopkg.in/redis.v3/internal/pool", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" - }, - { - "checksumSHA1": "CKEuRia+Qsk0UcWie9f/TxW6zpU=", + "checksumSHA1": "hyNpm6i99xiOSkIAJn5zPRblWaU=", "path": "gopkg.in/redis.v4", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { - "checksumSHA1": "4HbfnrL1KOz/6nsdBAPeL0oUXzc=", + "checksumSHA1": "u24d3wK+Otp3+nu3wSQOMqhg+N8=", "path": "gopkg.in/redis.v4/internal", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { "checksumSHA1": "l+1jiOGUyjHP0u0mB+KiT/UHVcY=", "path": "gopkg.in/redis.v4/internal/consistenthash", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { "checksumSHA1": "XxoAGEMN5ZCJJ3FeDMSTVhvscHg=", "path": "gopkg.in/redis.v4/internal/errors", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { "checksumSHA1": "6XSNzBg5bzd2TjEfC/q41tPT/Zg=", "path": "gopkg.in/redis.v4/internal/hashtag", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { "checksumSHA1": "fYkMv2BMIeHKM1VirUzJ7A/jG7w=", "path": "gopkg.in/redis.v4/internal/pool", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { "checksumSHA1": "BGzuM8ZpMCmTKWnb/3CtrV4OwDU=", "path": "gopkg.in/redis.v4/internal/proto", - "revision": "5a272d03b960a72c61e5025a7d6e8da5d8815442", - "revisionTime": "2016-10-02T13:00:31Z" + "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", + "revisionTime": "2016-10-14T12:43:29Z" }, { "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", From 11508e0dab9e5dceda7fdad79a36e470c07ff4e5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 18 Oct 2016 18:10:40 +0200 Subject: [PATCH 1994/2266] Update ttnctl for account server lib update --- ttnctl/cmd/user_login.go | 4 +++- ttnctl/cmd/user_register.go | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index e1cdf0edd..ce3ecd111 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/go-account-lib/auth" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -34,7 +35,8 @@ $ ttnctl user login [paste the access code you requested above] ctx.WithError(err).Fatal("Login failed") } - acc := account.New(viper.GetString("ttn-account-server"), token.AccessToken) + acc := account.New(viper.GetString("ttn-account-server")) + acc.WithAuth(auth.AccessToken(token.AccessToken)) profile, err := acc.Profile() if err != nil { ctx.WithError(err).Fatal("Could not get user profile") diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go index 593f46863..0b5a1e22e 100644 --- a/ttnctl/cmd/user_register.go +++ b/ttnctl/cmd/user_register.go @@ -33,7 +33,8 @@ Password: if err != nil { ctx.Fatal(err.Error()) } - err = account.RegisterUser(viper.GetString("ttn-account-server"), username, email, string(password)) + acc := account.New(viper.GetString("ttn-account-server")) + err = acc.RegisterUser(username, email, string(password)) if err != nil { ctx.WithError(err).Fatal("Could not register user") } From 04becc8b3c8f43be76f5e5ecbb2ef524ac0e63c3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 19 Oct 2016 11:57:53 +0200 Subject: [PATCH 1995/2266] Improve docs for mqtt events --- mqtt/events.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mqtt/events.go b/mqtt/events.go index 5090a4c99..3b74c12cc 100644 --- a/mqtt/events.go +++ b/mqtt/events.go @@ -38,7 +38,8 @@ func (c *DefaultClient) PublishDeviceEvent(appID string, devID string, eventType return c.publish(topic.String(), msg) } -// SubscribeAppEvents subscribes to events of the given type for the given application +// SubscribeAppEvents subscribes to events of the given type for the given application. In order to subscribe to +// application events from all applications the user has access to, pass an empty string as appID. func (c *DefaultClient) SubscribeAppEvents(appID string, eventType string, handler AppEventHandler) Token { topic := ApplicationTopic{appID, AppEvents, eventType} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { @@ -51,7 +52,9 @@ func (c *DefaultClient) SubscribeAppEvents(appID string, eventType string, handl }) } -// SubscribeDeviceEvents subscribes to events of the given type for the given device +// SubscribeDeviceEvents subscribes to events of the given type for the given device. In order to subscribe to +// events from all devices within an application, pass an empty string as devID. In order to subscribe to all +// events from all devices in all applications the user has access to, pass an empty string as appID. func (c *DefaultClient) SubscribeDeviceEvents(appID string, devID string, eventType string, handler DeviceEventHandler) Token { topic := DeviceTopic{appID, devID, DeviceEvents, eventType} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { From 50b865bfdd6c3e93412bcbec058ab7eed33b9a1b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 19 Oct 2016 14:43:17 +0200 Subject: [PATCH 1996/2266] Add caching to Discovery server --- cmd/discovery.go | 7 + core/discovery/announcement/cache.go | 143 +++++++++++++++++++++ core/discovery/announcement/cache_test.go | 150 ++++++++++++++++++++++ core/discovery/discovery.go | 5 + 4 files changed, 305 insertions(+) create mode 100644 core/discovery/announcement/cache.go create mode 100644 core/discovery/announcement/cache_test.go diff --git a/cmd/discovery.go b/cmd/discovery.go index 29026b358..3143438cf 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -16,6 +16,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" + "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -52,6 +53,9 @@ var discoveryCmd = &cobra.Command{ // Discovery Server discovery := discovery.NewRedisDiscovery(client) + if viper.GetBool("discovery.cache") { + discovery.WithCache(announcement.DefaultCacheOptions) + } err = discovery.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize discovery") @@ -88,4 +92,7 @@ func init() { discoveryCmd.Flags().Int("server-port", 1900, "The port for communication") viper.BindPFlag("discovery.server-address", discoveryCmd.Flags().Lookup("server-address")) viper.BindPFlag("discovery.server-port", discoveryCmd.Flags().Lookup("server-port")) + + discoveryCmd.Flags().Bool("cache", false, "Add a cache in front of the database") + viper.BindPFlag("discovery.cache", discoveryCmd.Flags().Lookup("cache")) } diff --git a/core/discovery/announcement/cache.go b/core/discovery/announcement/cache.go new file mode 100644 index 000000000..902e2e956 --- /dev/null +++ b/core/discovery/announcement/cache.go @@ -0,0 +1,143 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/bluele/gcache" +) + +type cachedAnnouncementStore struct { + backingStore Store + serviceCache gcache.Cache + listCache gcache.Cache +} + +// CacheOptions used for the cache +type CacheOptions struct { + ServiceCacheSize int + ServiceCacheExpiration time.Duration + ListCacheSize int + ListCacheExpiration time.Duration +} + +// DefaultCacheOptions are the default CacheOptions +var DefaultCacheOptions = CacheOptions{ + ServiceCacheSize: 100, + ServiceCacheExpiration: 10 * time.Minute, + ListCacheSize: 100, + ListCacheExpiration: 10 * time.Second, +} + +type serviceCacheKey struct { + ServiceName string + ServiceID string +} + +// NewCachedAnnouncementStore returns a cache wrapper around the existing store +func NewCachedAnnouncementStore(store Store, options CacheOptions) Store { + serviceCache := gcache.New(options.ServiceCacheSize).Expiration(options.ServiceCacheExpiration).ARC(). + LoaderFunc(func(k interface{}) (interface{}, error) { + key := k.(*serviceCacheKey) + return store.Get(key.ServiceName, key.ServiceID) + }).Build() + + listCache := gcache.New(options.ListCacheSize).Expiration(options.ListCacheExpiration).ARC(). + LoaderFunc(func(k interface{}) (interface{}, error) { + key := k.(string) + announcements, err := store.ListService(key) + if err != nil { + return nil, err + } + go func(announcements []*Announcement) { + for _, announcement := range announcements { + serviceCache.Set(&serviceCacheKey{announcement.ServiceName, announcement.ID}, announcement) + } + }(announcements) + return announcements, nil + }).Build() + + return &cachedAnnouncementStore{ + backingStore: store, + serviceCache: serviceCache, + listCache: listCache, + } +} + +func (s *cachedAnnouncementStore) List() ([]*Announcement, error) { + // TODO: We're not using this function. Implement cache when we start using it. + return s.backingStore.List() +} + +func (s *cachedAnnouncementStore) ListService(serviceName string) ([]*Announcement, error) { + l, err := s.listCache.Get(serviceName) + if err != nil { + return nil, err + } + return l.([]*Announcement), nil +} + +func (s *cachedAnnouncementStore) Get(serviceName, serviceID string) (*Announcement, error) { + a, err := s.serviceCache.Get(&serviceCacheKey{serviceName, serviceID}) + if err != nil { + return nil, err + } + return a.(*Announcement), nil +} + +func (s *cachedAnnouncementStore) GetMetadata(serviceName, serviceID string) ([]Metadata, error) { + a, err := s.serviceCache.Get(&serviceCacheKey{serviceName, serviceID}) + if err != nil { + return nil, err + } + return a.(*Announcement).Metadata, nil +} + +func (s *cachedAnnouncementStore) GetForAppID(appID string) (*Announcement, error) { + // TODO: We're not using this function. Implement cache when we start using it. + return s.backingStore.GetForAppID(appID) +} + +func (s *cachedAnnouncementStore) GetForAppEUI(appEUI types.AppEUI) (*Announcement, error) { + // TODO: We're not using this function. Implement cache when we start using it. + return s.backingStore.GetForAppEUI(appEUI) +} + +func (s *cachedAnnouncementStore) Set(new *Announcement) error { + if err := s.backingStore.Set(new); err != nil { + return err + } + s.serviceCache.Remove(&serviceCacheKey{new.ServiceName, new.ID}) + s.listCache.Remove(&new.ServiceName) + return nil +} + +func (s *cachedAnnouncementStore) AddMetadata(serviceName, serviceID string, metadata ...Metadata) error { + if err := s.backingStore.AddMetadata(serviceName, serviceID, metadata...); err != nil { + return err + } + s.serviceCache.Remove(&serviceCacheKey{serviceName, serviceID}) + s.listCache.Remove(&serviceName) + return nil +} + +func (s *cachedAnnouncementStore) RemoveMetadata(serviceName, serviceID string, metadata ...Metadata) error { + if err := s.backingStore.RemoveMetadata(serviceName, serviceID, metadata...); err != nil { + return err + } + s.serviceCache.Remove(&serviceCacheKey{serviceName, serviceID}) + s.listCache.Remove(&serviceName) + return nil +} + +func (s *cachedAnnouncementStore) Delete(serviceName, serviceID string) error { + if err := s.backingStore.Delete(serviceName, serviceID); err != nil { + return err + } + s.serviceCache.Remove(&serviceCacheKey{serviceName, serviceID}) + s.listCache.Remove(&serviceName) + return nil +} diff --git a/core/discovery/announcement/cache_test.go b/core/discovery/announcement/cache_test.go new file mode 100644 index 000000000..9fdeb9add --- /dev/null +++ b/core/discovery/announcement/cache_test.go @@ -0,0 +1,150 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package announcement + +import ( + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestCachedAnnouncementStore(t *testing.T) { + a := New(t) + + s := NewRedisAnnouncementStore(GetRedisClient(), "discovery-test-announcement-store") + + s = NewCachedAnnouncementStore(s, DefaultCacheOptions) + + // Get non-existing + dev, err := s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Create + err = s.Set(&Announcement{ + ServiceName: "router", + ID: "router1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("router", "router1") + }() + + // Get existing + dev, err = s.Get("router", "router1") + a.So(err, ShouldBeNil) + a.So(dev, ShouldNotBeNil) + + // Create extra + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler1", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler1") + }() + + err = s.Set(&Announcement{ + ServiceName: "handler", + ID: "handler2", + }) + a.So(err, ShouldBeNil) + + defer func() { + s.Delete("handler", "handler2") + }() + + appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) + + err = s.AddMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + handler, err := s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler1") + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + AppIDMetadata{AppID: "OtherAppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + metadata, err := s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 4) + + err = s.AddMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + ) + a.So(err, ShouldBeNil) + + metadata, err = s.GetMetadata("handler", "handler2") + a.So(err, ShouldBeNil) + a.So(metadata, ShouldHaveLength, 4) + + handler, err = s.GetForAppEUI(appEUI) + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + handler, err = s.GetForAppID("AppID") + a.So(err, ShouldBeNil) + a.So(handler, ShouldNotBeNil) + a.So(handler.ID, ShouldEqual, "handler2") + + err = s.RemoveMetadata("handler", "handler1", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + err = s.RemoveMetadata("handler", "handler2", + AppEUIMetadata{AppEUI: appEUI}, + AppIDMetadata{AppID: "AppID"}, + OtherMetadata{}, + ) + a.So(err, ShouldBeNil) + + // List + announcements, err := s.List() + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 3) + + // List + announcements, err = s.ListService("router") + a.So(err, ShouldBeNil) + a.So(announcements, ShouldHaveLength, 1) + + // Delete + err = s.Delete("router", "router1") + a.So(err, ShouldBeNil) + + // Get deleted + dev, err = s.Get("router", "router1") + a.So(err, ShouldNotBeNil) + a.So(dev, ShouldBeNil) + + // Delete with Metadata + err = s.Delete("handler", "handler2") + a.So(err, ShouldBeNil) +} diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 50f7fdd57..f16665b39 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -15,6 +15,7 @@ import ( // Discovery specifies the interface for the TTN Service Discovery component type Discovery interface { core.ComponentInterface + WithCache(options announcement.CacheOptions) Announce(announcement *pb.Announcement) error GetAll(serviceName string) ([]*pb.Announcement, error) Get(serviceName string, id string) (*pb.Announcement, error) @@ -28,6 +29,10 @@ type discovery struct { services announcement.Store } +func (d *discovery) WithCache(options announcement.CacheOptions) { + d.services = announcement.NewCachedAnnouncementStore(d.services, options) +} + func (d *discovery) Init(c *core.Component) error { d.Component = c err := d.Component.UpdateTokenKey() From fd49431b348ec07e548db94e3879645d47212f2e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 19 Oct 2016 16:24:13 +0200 Subject: [PATCH 1997/2266] Fix monitor generated proto --- api/monitor/monitor.pb.go | 56 +++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/api/monitor/monitor.pb.go b/api/monitor/monitor.pb.go index e47adfb3e..da2fb73c2 100644 --- a/api/monitor/monitor.pb.go +++ b/api/monitor/monitor.pb.go @@ -3,7 +3,7 @@ // DO NOT EDIT! /* - Package noc is a generated protocol buffer package. + Package monitor is a generated protocol buffer package. It is generated from these files: github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto @@ -65,7 +65,7 @@ func NewMonitorClient(cc *grpc.ClientConn) MonitorClient { } func (c *monitorClient) GatewayStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/noc.Monitor/GatewayStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[0], c.cc, "/monitor.Monitor/GatewayStatus", opts...) if err != nil { return nil, err } @@ -99,7 +99,7 @@ func (x *monitorGatewayStatusClient) CloseAndRecv() (*google_protobuf1.Empty, er } func (c *monitorClient) GatewayUplink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayUplinkClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[1], c.cc, "/noc.Monitor/GatewayUplink", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[1], c.cc, "/monitor.Monitor/GatewayUplink", opts...) if err != nil { return nil, err } @@ -133,7 +133,7 @@ func (x *monitorGatewayUplinkClient) CloseAndRecv() (*google_protobuf1.Empty, er } func (c *monitorClient) GatewayDownlink(ctx context.Context, opts ...grpc.CallOption) (Monitor_GatewayDownlinkClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[2], c.cc, "/noc.Monitor/GatewayDownlink", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[2], c.cc, "/monitor.Monitor/GatewayDownlink", opts...) if err != nil { return nil, err } @@ -167,7 +167,7 @@ func (x *monitorGatewayDownlinkClient) CloseAndRecv() (*google_protobuf1.Empty, } func (c *monitorClient) RouterStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_RouterStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[3], c.cc, "/noc.Monitor/RouterStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[3], c.cc, "/monitor.Monitor/RouterStatus", opts...) if err != nil { return nil, err } @@ -201,7 +201,7 @@ func (x *monitorRouterStatusClient) CloseAndRecv() (*google_protobuf1.Empty, err } func (c *monitorClient) BrokerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_BrokerStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[4], c.cc, "/noc.Monitor/BrokerStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[4], c.cc, "/monitor.Monitor/BrokerStatus", opts...) if err != nil { return nil, err } @@ -235,7 +235,7 @@ func (x *monitorBrokerStatusClient) CloseAndRecv() (*google_protobuf1.Empty, err } func (c *monitorClient) HandlerStatus(ctx context.Context, opts ...grpc.CallOption) (Monitor_HandlerStatusClient, error) { - stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[5], c.cc, "/noc.Monitor/HandlerStatus", opts...) + stream, err := grpc.NewClientStream(ctx, &_Monitor_serviceDesc.Streams[5], c.cc, "/monitor.Monitor/HandlerStatus", opts...) if err != nil { return nil, err } @@ -440,7 +440,7 @@ func (x *monitorHandlerStatusServer) Recv() (*handler.Status, error) { } var _Monitor_serviceDesc = grpc.ServiceDesc{ - ServiceName: "noc.Monitor", + ServiceName: "monitor.Monitor", HandlerType: (*MonitorServer)(nil), Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{ @@ -483,24 +483,24 @@ func init() { } var fileDescriptorMonitor = []byte{ - // 299 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x91, 0xcd, 0x4a, 0xf4, 0x30, - 0x18, 0x85, 0xbf, 0x7e, 0x82, 0x42, 0x98, 0x71, 0xa4, 0xa0, 0x42, 0x85, 0xae, 0x5d, 0x25, 0xa0, - 0x0b, 0xc7, 0x59, 0xc9, 0x30, 0xa2, 0x9b, 0x71, 0xa1, 0xe3, 0x05, 0xa4, 0x63, 0x4c, 0x4b, 0xdb, - 0xbc, 0x25, 0x7d, 0x4b, 0xf1, 0x4e, 0xbc, 0x24, 0x97, 0x5e, 0x82, 0xd4, 0xeb, 0x10, 0xc4, 0xfc, - 0x74, 0x69, 0xeb, 0xea, 0x90, 0xd0, 0xe7, 0xa1, 0xe7, 0x84, 0x5c, 0xca, 0x0c, 0xd3, 0x26, 0xa1, - 0x5b, 0x28, 0xd9, 0x26, 0x15, 0x9b, 0x34, 0x53, 0xb2, 0xbe, 0x13, 0xd8, 0x82, 0xce, 0x19, 0xa2, - 0x62, 0xbc, 0xca, 0x58, 0x09, 0x2a, 0x43, 0xd0, 0x3e, 0x69, 0xa5, 0x01, 0x21, 0xdc, 0x51, 0xb0, - 0x8d, 0x46, 0xf1, 0x92, 0xa3, 0x68, 0xf9, 0x8b, 0x4f, 0xcb, 0x47, 0x17, 0x63, 0x50, 0x0d, 0x0d, - 0x0a, 0xed, 0xe2, 0x2f, 0x60, 0xa2, 0x21, 0x17, 0xda, 0x85, 0x03, 0x47, 0xfd, 0x6c, 0xca, 0xd5, - 0x53, 0x21, 0xb4, 0x4f, 0x87, 0x9e, 0x48, 0x00, 0x59, 0x08, 0x66, 0x4e, 0x49, 0xf3, 0xcc, 0x44, - 0x59, 0xa1, 0x6b, 0x72, 0xf6, 0xf5, 0x9f, 0xec, 0xad, 0xed, 0x36, 0xe1, 0x82, 0x4c, 0x6f, 0x6c, - 0xcd, 0x07, 0xe4, 0xd8, 0xd4, 0xe1, 0x8c, 0xfa, 0xda, 0xf6, 0x22, 0x3a, 0xa2, 0xd6, 0x45, 0xbd, - 0x8b, 0x5e, 0xff, 0xb8, 0x4e, 0x83, 0xf0, 0xaa, 0x67, 0x1f, 0xab, 0x22, 0x53, 0x79, 0x78, 0x48, - 0x5d, 0x71, 0x7b, 0x5e, 0x8b, 0xba, 0xe6, 0x52, 0xfc, 0x62, 0x58, 0x91, 0x99, 0x33, 0xac, 0xa0, - 0x55, 0xc6, 0x71, 0xec, 0x1d, 0xfe, 0x66, 0xd8, 0x32, 0x27, 0x93, 0x7b, 0xc3, 0xb8, 0x0a, 0xfb, - 0x5e, 0x31, 0xd8, 0x60, 0x4e, 0x26, 0x4b, 0xb3, 0x78, 0x4f, 0xba, 0x07, 0x18, 0x24, 0x17, 0x64, - 0x7a, 0x6b, 0x17, 0xef, 0x77, 0xf3, 0x2f, 0x30, 0xc4, 0x2e, 0x0f, 0xde, 0xba, 0x38, 0x78, 0xef, - 0xe2, 0xe0, 0xa3, 0x8b, 0x83, 0xd7, 0xcf, 0xf8, 0x5f, 0xb2, 0x6b, 0xbe, 0x39, 0xff, 0x0e, 0x00, - 0x00, 0xff, 0xff, 0xbd, 0x86, 0x73, 0x31, 0xdf, 0x02, 0x00, 0x00, + // 298 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x91, 0x3f, 0x4a, 0xc4, 0x40, + 0x14, 0xc6, 0x8d, 0x85, 0x0b, 0xc3, 0xae, 0x2b, 0x01, 0x15, 0x22, 0xa4, 0xb6, 0x9a, 0x01, 0x2d, + 0x5c, 0xb7, 0x92, 0x65, 0x45, 0x9b, 0xb5, 0xd0, 0xf5, 0x00, 0x13, 0x1d, 0x27, 0x21, 0xc9, 0xbc, + 0x30, 0x79, 0x21, 0x78, 0x13, 0x8f, 0x64, 0xe9, 0x11, 0x24, 0x9e, 0x43, 0x10, 0xe7, 0x4f, 0x4a, + 0x13, 0xab, 0x8f, 0x37, 0xe4, 0xf7, 0x23, 0xdf, 0x7b, 0xe4, 0x52, 0x66, 0x98, 0x36, 0x09, 0x7d, + 0x82, 0x92, 0x6d, 0x53, 0xb1, 0x4d, 0x33, 0x25, 0xeb, 0x3b, 0x81, 0x2d, 0xe8, 0x9c, 0x21, 0x2a, + 0xc6, 0xab, 0x8c, 0x95, 0xa0, 0x32, 0x04, 0xed, 0x93, 0x56, 0x1a, 0x10, 0xc2, 0x89, 0x1b, 0xa3, + 0x51, 0x0e, 0xc9, 0x51, 0xb4, 0xfc, 0xd5, 0xa7, 0x75, 0x44, 0x17, 0x63, 0x50, 0x0d, 0x0d, 0x0a, + 0xed, 0xe2, 0x3f, 0x60, 0xa2, 0x21, 0x17, 0xda, 0x85, 0x03, 0x47, 0xfd, 0x6c, 0xca, 0xd5, 0x73, + 0x21, 0xb4, 0x4f, 0x87, 0x9e, 0x48, 0x00, 0x59, 0x08, 0x66, 0xa6, 0xa4, 0x79, 0x61, 0xa2, 0xac, + 0xd0, 0x35, 0x39, 0xfb, 0xde, 0x25, 0x93, 0x8d, 0x5d, 0x48, 0xb8, 0x24, 0xb3, 0x1b, 0x5b, 0xf3, + 0x01, 0x39, 0x36, 0x75, 0x38, 0xa7, 0xbe, 0xb6, 0x7d, 0x88, 0x8e, 0xa8, 0x75, 0x51, 0xef, 0xa2, + 0xd7, 0xbf, 0xae, 0xd3, 0x20, 0xbc, 0xea, 0xd9, 0xc7, 0xaa, 0xc8, 0x54, 0x1e, 0x1e, 0x52, 0x57, + 0xdc, 0xce, 0x1b, 0x51, 0xd7, 0x5c, 0x8a, 0x3f, 0x0c, 0x6b, 0x32, 0x77, 0x86, 0x35, 0xb4, 0xca, + 0x38, 0x8e, 0xbd, 0xc3, 0xbf, 0x0c, 0x5b, 0x16, 0x64, 0x7a, 0x6f, 0x18, 0x57, 0x61, 0xdf, 0x2b, + 0x06, 0x1b, 0x2c, 0xc8, 0x74, 0x65, 0x36, 0xde, 0x93, 0xee, 0x00, 0x83, 0xe4, 0x92, 0xcc, 0x6e, + 0xed, 0xc6, 0xfb, 0xbd, 0xf9, 0x0b, 0x0c, 0xb1, 0xab, 0x83, 0xf7, 0x2e, 0x0e, 0x3e, 0xba, 0x38, + 0xf8, 0xec, 0xe2, 0xe0, 0xed, 0x2b, 0xde, 0x49, 0xf6, 0xcc, 0x37, 0xe7, 0x3f, 0x01, 0x00, 0x00, + 0xff, 0xff, 0x8e, 0x88, 0xea, 0xa5, 0xe3, 0x02, 0x00, 0x00, } From ab5ce69886f62c901024b43d1491c90df16815b6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 19 Oct 2016 18:32:54 +0200 Subject: [PATCH 1998/2266] Remove up/down from ttnctl devices list Up/Down counters are managed by the network server, which we don't want to bother with that much traffic. --- ttnctl/cmd/devices_list.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index 22532a4a8..604de71d3 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -22,8 +22,8 @@ var devicesListCmd = &cobra.Command{ INFO Discovering Handler... INFO Connecting with Handler... -DevID AppEUI DevEUI DevAddr Up/Down -test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 +DevID AppEUI DevEUI DevAddr +test 70B3D57EF0000024 0001D544B2936FCE 26001ADA INFO Listed 1 devices AppID=test `, @@ -41,14 +41,14 @@ test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 table := uitable.New() table.MaxColWidth = 70 - table.AddRow("DevID", "AppEUI", "DevEUI", "DevAddr", "Up/Down") + table.AddRow("DevID", "AppEUI", "DevEUI", "DevAddr") for _, dev := range devices { if lorawan := dev.GetLorawanDevice(); lorawan != nil { devAddr := lorawan.DevAddr if devAddr.IsEmpty() { devAddr = nil } - table.AddRow(dev.DevId, lorawan.AppEui, lorawan.DevEui, devAddr, fmt.Sprintf("%d/%d", lorawan.FCntUp, lorawan.FCntDown)) + table.AddRow(dev.DevId, lorawan.AppEui, lorawan.DevEui, devAddr) } else { table.AddRow(dev.DevId) } From f89afeb08d756cd6b2c8d927180a9e5f051fba73 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 20 Oct 2016 11:34:00 +0200 Subject: [PATCH 1999/2266] Fix regexp parsing --- core/types/parse_hex.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/types/parse_hex.go b/core/types/parse_hex.go index 8ad04f4db..87a6ababb 100644 --- a/core/types/parse_hex.go +++ b/core/types/parse_hex.go @@ -15,7 +15,7 @@ func ParseHEX(input string, length int) ([]byte, error) { return make([]byte, length), nil } - pattern := regexp.MustCompile(fmt.Sprintf("[[:xdigit:]]{%d}", length*2)) + pattern := regexp.MustCompile(fmt.Sprintf("^[[:xdigit:]]{%d}$", length*2)) valid := pattern.MatchString(input) if !valid { From 3cc238b79761ec9d2b06a3847e6fbbcc63c34700 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 20 Oct 2016 11:35:21 +0200 Subject: [PATCH 2000/2266] Pass []byte to RecvMsg --- api/router/client.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/router/client.go b/api/router/client.go index 2893d9564..1068c11ac 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -110,7 +110,7 @@ func (c *gatewayClient) setupGatewayStatus() error { c.gatewayStatus = gatewayStatusClient c.stopGatewayStatus = make(chan bool) go func() { - var msg interface{} + var msg []byte for { select { case <-c.stopGatewayStatus: @@ -168,7 +168,7 @@ func (c *gatewayClient) setupUplink() error { c.uplink = uplinkClient c.stopUplink = make(chan bool) go func() { - var msg interface{} + var msg []byte for { select { case <-c.stopUplink: From c689122898eb80814452954a2d4629025cda35af Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 19 Oct 2016 12:08:32 +0200 Subject: [PATCH 2001/2266] Basic AMQP uplink publisher --- amqp/client.go | 106 ++++++++++++++++ amqp/client_test.go | 79 ++++++++++++ amqp/logging.go | 29 +++++ amqp/routing_keys.go | 120 ++++++++++++++++++ amqp/routing_keys_test.go | 88 +++++++++++++ amqp/types.go | 48 +++++++ amqp/uplink.go | 20 +++ amqp/uplink_test.go | 24 ++++ core/handler/handler.go | 16 ++- core/types/json_time.go | 40 ++++++ .../types/json_time_test.go | 2 +- mqtt/types.go | 56 ++------ 12 files changed, 575 insertions(+), 53 deletions(-) create mode 100644 amqp/client.go create mode 100644 amqp/client_test.go create mode 100644 amqp/logging.go create mode 100644 amqp/routing_keys.go create mode 100644 amqp/routing_keys_test.go create mode 100644 amqp/types.go create mode 100644 amqp/uplink.go create mode 100644 amqp/uplink_test.go create mode 100644 core/types/json_time.go rename mqtt/types_test.go => core/types/json_time_test.go (98%) diff --git a/amqp/client.go b/amqp/client.go new file mode 100644 index 000000000..973040d7f --- /dev/null +++ b/amqp/client.go @@ -0,0 +1,106 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + "time" + + AMQP "github.com/streadway/amqp" +) + +// Publisher connects to the AMQP server and can publish on uplink and activations from devices +type Publisher interface { + Connect() error + Disconnect() + IsConnected() bool + + PublishUplink(payload UplinkMessage) error +} + +// DefaultPublisher is the default AMQP client for The Things Network +type DefaultPublisher struct { + url string + ctx Logger + conn *AMQP.Connection + channel *AMQP.Channel + exchange string +} + +var ( + // ConnectRetries says how many times the client should retry a failed connection + ConnectRetries = 10 + // ConnectRetryDelay says how long the client should wait between retries + ConnectRetryDelay = time.Second +) + +// NewPublisher creates a new DefaultPublisher +func NewPublisher(ctx Logger, url, exchange string) Publisher { + if ctx == nil { + ctx = &noopLogger{} + } + return &DefaultPublisher{ + ctx: ctx, + url: url, + exchange: exchange, + } +} + +// Connect to the MQTT broker. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +func (c *DefaultPublisher) Connect() error { + if c.IsConnected() { + return nil + } + var err error + var conn *AMQP.Connection + for retries := 0; retries < ConnectRetries; retries++ { + conn, err = AMQP.Dial(c.url) + if err == nil { + break + } + c.ctx.Warnf("Could not connect to AMQP Broker (%s). Retrying...", err.Error()) + <-time.After(ConnectRetryDelay) + } + if err != nil { + return fmt.Errorf("Could not connect to AMQP Broker (%s).", err) + } + channel, err := conn.Channel() + if err != nil { + conn.Close() + return fmt.Errorf("Could not get AMQP channel (%s).", err) + } + if err = channel.ExchangeDeclare(c.exchange, "topic", true, false, false, false, nil); err != nil { + channel.Close() + conn.Close() + return fmt.Errorf("Could not AMQP exchange (%s).", err) + } + c.conn = conn + c.channel = channel + return nil +} + +func (c *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) error { + return c.channel.Publish(c.exchange, key, false, false, AMQP.Publishing{ + ContentType: "application/json", + Timestamp: timestamp, + Body: msg, + }) +} + +// Disconnect from the AMQP broker +func (c *DefaultPublisher) Disconnect() { + if !c.IsConnected() { + return + } + c.ctx.Debug("Disconnecting from AMQP") + c.channel.Close() + c.channel = nil + c.conn.Close() + c.conn = nil +} + +// IsConnected returns true if there is a connection to the AMQP broker +func (c *DefaultPublisher) IsConnected() bool { + return c.conn != nil +} diff --git a/amqp/client_test.go b/amqp/client_test.go new file mode 100644 index 000000000..13faa4993 --- /dev/null +++ b/amqp/client_test.go @@ -0,0 +1,79 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + "os" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +var host string + +func init() { + host = os.Getenv("AMQP_ADDR") + if host == "" { + host = "localhost" + } +} + +func TestNewPublisher(t *testing.T) { + a := New(t) + c := NewPublisher(GetLogger(t, "TestNewPublisher"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + a.So(c, ShouldNotBeNil) +} + +func TestConnect(t *testing.T) { + a := New(t) + c := NewPublisher(GetLogger(t, "TestConnect"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) + + // Connecting while already connected should not change anything + err = c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) +} + +func TestConnectInvalidAddress(t *testing.T) { + a := New(t) + ConnectRetries = 2 + ConnectRetryDelay = 50 * time.Millisecond + c := NewPublisher(GetLogger(t, "TestConnectInvalidAddress"), fmt.Sprintf("amqp://guest:guest@%s:56720/", host), "test") + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldNotBeNil) +} + +func TestIsConnected(t *testing.T) { + a := New(t) + c := NewPublisher(GetLogger(t, "TestIsConnected"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + + a.So(c.IsConnected(), ShouldBeTrue) +} + +func TestDisconnect(t *testing.T) { + a := New(t) + c := NewPublisher(GetLogger(t, "TestDisconnect"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + + // Disconnecting when not connected should not change anything + c.Disconnect() + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + c.Disconnect() + + a.So(c.IsConnected(), ShouldBeFalse) +} diff --git a/amqp/logging.go b/amqp/logging.go new file mode 100644 index 000000000..921cb21cb --- /dev/null +++ b/amqp/logging.go @@ -0,0 +1,29 @@ +package amqp + +// Logger used in amqp package +type Logger interface { + Debug(msg string) + Info(msg string) + Warn(msg string) + Error(msg string) + Fatal(msg string) + Debugf(msg string, v ...interface{}) + Infof(msg string, v ...interface{}) + Warnf(msg string, v ...interface{}) + Errorf(msg string, v ...interface{}) + Fatalf(msg string, v ...interface{}) +} + +// noopLogger just does nothing +type noopLogger struct{} + +func (l noopLogger) Debug(msg string) {} +func (l noopLogger) Info(msg string) {} +func (l noopLogger) Warn(msg string) {} +func (l noopLogger) Error(msg string) {} +func (l noopLogger) Fatal(msg string) {} +func (l noopLogger) Debugf(msg string, v ...interface{}) {} +func (l noopLogger) Infof(msg string, v ...interface{}) {} +func (l noopLogger) Warnf(msg string, v ...interface{}) {} +func (l noopLogger) Errorf(msg string, v ...interface{}) {} +func (l noopLogger) Fatalf(msg string, v ...interface{}) {} diff --git a/amqp/routing_keys.go b/amqp/routing_keys.go new file mode 100644 index 000000000..edfd0dbc5 --- /dev/null +++ b/amqp/routing_keys.go @@ -0,0 +1,120 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + "regexp" + "strings" +) + +const wildcard = "*" + +// DeviceKeyType represents the type of a device topic +type DeviceKeyType string + +// Topic types for Devices +const ( + DeviceEvents DeviceKeyType = "events" + DeviceUplink DeviceKeyType = "up" + DeviceDownlink DeviceKeyType = "down" +) + +// DeviceKey represents an AMQP routing key for devices +type DeviceKey struct { + AppID string + DevID string + Type DeviceKeyType + Field string +} + +// ParseDeviceKey parses an AMQP device routing key string to a DeviceKey struct +func ParseDeviceKey(key string) (*DeviceKey, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(devices)\\.([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(events|up|down)([0-9a-z\\.]+)?$") + matches := pattern.FindStringSubmatch(key) + if len(matches) < 4 { + return nil, fmt.Errorf("Invalid key format") + } + var appID string + if matches[1] != wildcard { + appID = matches[1] + } + var devID string + if matches[3] != wildcard { + devID = matches[3] + } + keyType := DeviceKeyType(matches[4]) + deviceKey := &DeviceKey{appID, devID, keyType, ""} + if keyType == DeviceEvents && len(matches) > 4 { + deviceKey.Field = strings.Trim(matches[5], ".") + } + return deviceKey, nil +} + +// String implements the Stringer interface +func (t DeviceKey) String() string { + appID := wildcard + if t.AppID != "" { + appID = t.AppID + } + devID := wildcard + if t.DevID != "" { + devID = t.DevID + } + key := fmt.Sprintf("%s.%s.%s.%s", appID, "devices", devID, t.Type) + if t.Type == DeviceEvents && t.Field != "" { + key += "." + t.Field + } + return key +} + +// ApplicationKeyType represents an AMQP application routing key +type ApplicationKeyType string + +// Topic types for Applications +const ( + AppEvents ApplicationKeyType = "events" +) + +// ApplicationKey represents an MQTT topic for applications +type ApplicationKey struct { + AppID string + Type ApplicationKeyType + Field string +} + +// ParseApplicationKey parses an AMQP application routing key string to an ApplicationKey struct +func ParseApplicationKey(key string) (*ApplicationKey, error) { + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(events)([0-9a-z\\.]+)?$") + matches := pattern.FindStringSubmatch(key) + if len(matches) < 2 { + return nil, fmt.Errorf("Invalid key format") + } + var appID string + if matches[1] != wildcard { + appID = matches[1] + } + keyType := ApplicationKeyType(matches[2]) + appKey := &ApplicationKey{appID, keyType, ""} + if keyType == AppEvents && len(matches) > 2 { + appKey.Field = strings.Trim(matches[3], ".") + } + return appKey, nil +} + +// String implements the Stringer interface +func (t ApplicationKey) String() string { + appID := wildcard + if t.AppID != "" { + appID = t.AppID + } + if t.Type == AppEvents && t.Field == "" { + t.Field = wildcard + } + key := fmt.Sprintf("%s.%s", appID, t.Type) + if t.Type == AppEvents && t.Field != "" { + key += "." + t.Field + } + return key +} diff --git a/amqp/routing_keys_test.go b/amqp/routing_keys_test.go new file mode 100644 index 000000000..4ce1b2f6e --- /dev/null +++ b/amqp/routing_keys_test.go @@ -0,0 +1,88 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestParseDeviceKey(t *testing.T) { + a := New(t) + + key := "appid-1.devices.devid-1.up" + + expected := &DeviceKey{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceUplink, + } + + got, err := ParseDeviceKey(key) + + a.So(err, ShouldBeNil) + a.So(got, ShouldResemble, expected) +} + +func TestParseDeviceKeyInvalid(t *testing.T) { + a := New(t) + + _, err := ParseDeviceKey("appid:Invalid.devices.dev.up") + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceKey("appid-1.devices.devid:Invalid.up") // DevID contains hex chars + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceKey("appid-1.fridges.devid-1.up") // We don't support fridges (at least, not specifically fridges) + a.So(err, ShouldNotBeNil) + + _, err = ParseDeviceKey("appid-1.devices.devid-1.emotions") // Devices usually don't publish emotions + a.So(err, ShouldNotBeNil) +} + +func TestKeyString(t *testing.T) { + a := New(t) + + key := &DeviceKey{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceDownlink, + } + + expected := "appid-1.devices.devid-1.down" + + got := key.String() + + a.So(got, ShouldResemble, expected) +} + +func TestKeyParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + // Uppercase (not lowercase) + "0102030405060708.devices.abcdabcd12345678.up", + "0102030405060708.devices.abcdabcd12345678.down", + "0102030405060708.devices.abcdabcd12345678.events.activations", + // Numbers + "0102030405060708.devices.0000000012345678.up", + "0102030405060708.devices.0000000012345678.down", + "0102030405060708.devices.0000000012345678.events.activations", + // Wildcards + "*.devices.*.up", + "*.devices.*.down", + "*.devices.*.events.activations", + // Not Wildcard + "0102030405060708.devices.0100000000000000.up", + "0102030405060708.devices.0100000000000000.down", + "0102030405060708.devices.0100000000000000.events.activations", + } + + for _, expected := range expectedList { + key, err := ParseDeviceKey(expected) + a.So(err, ShouldBeNil) + a.So(key.String(), ShouldEqual, expected) + } +} diff --git a/amqp/types.go b/amqp/types.go new file mode 100644 index 000000000..dae06a2e2 --- /dev/null +++ b/amqp/types.go @@ -0,0 +1,48 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import "github.com/TheThingsNetwork/ttn/core/types" + +// LocationMetadata contains GPS coordinates +type LocationMetadata struct { + Altitude int32 `json:"altitude,omitempty"` + Longitude float32 `json:"longitude,omitempty"` + Latitude float32 `json:"latitude,omitempty"` +} + +// GatewayMetadata contains metadata for each gateway that received a message +type GatewayMetadata struct { + GtwID string `json:"gtw_id,omitempty"` + Timestamp uint32 `json:"timestamp,omitempty"` + Time types.JSONTime `json:"time,omitempty"` + Channel uint32 `json:"channel"` + RSSI float32 `json:"rssi,omitempty"` + SNR float32 `json:"snr,omitempty"` + RFChain uint32 `json:"rf_chain,omitempty"` + LocationMetadata +} + +// Metadata contains metadata of a message +type Metadata struct { + Time types.JSONTime `json:"time,omitempty,omitempty"` + Frequency float32 `json:"frequency,omitempty"` + Modulation string `json:"modulation,omitempty"` + DataRate string `json:"data_rate,omitempty"` + Bitrate uint32 `json:"bit_rate,omitempty"` + CodingRate string `json:"coding_rate,omitempty"` + Gateways []GatewayMetadata `json:"gateways,omitempty"` + LocationMetadata +} + +// UplinkMessage represents an application-layer uplink message +type UplinkMessage struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + FCnt uint32 `json:"counter"` + PayloadRaw []byte `json:"payload_raw"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` +} diff --git a/amqp/uplink.go b/amqp/uplink.go new file mode 100644 index 000000000..7b0c2381f --- /dev/null +++ b/amqp/uplink.go @@ -0,0 +1,20 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "encoding/json" + "fmt" + "time" +) + +// PublishUplink publishes an uplink message to the MQTT broker +func (c *DefaultPublisher) PublishUplink(dataUp UplinkMessage) error { + key := DeviceKey{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} + msg, err := json.Marshal(dataUp) + if err != nil { + return fmt.Errorf("Unable to marshal the message payload") + } + return c.publish(key.String(), msg, time.Time(dataUp.Metadata.Time)) +} diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go new file mode 100644 index 000000000..672da4af0 --- /dev/null +++ b/amqp/uplink_test.go @@ -0,0 +1,24 @@ +package amqp + +import ( + "fmt" + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestPublishUplink(t *testing.T) { + a := New(t) + c := NewPublisher(GetLogger(t, "TestPublishUplink"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) + + err = c.PublishUplink(UplinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) +} diff --git a/core/handler/handler.go b/core/handler/handler.go index a5eb2eb70..920df8cca 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -7,6 +7,7 @@ import ( "fmt" "time" + "github.com/TheThingsNetwork/ttn/amqp" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -55,14 +56,19 @@ type handler struct { downlink chan *pb_broker.DownlinkMessage - mqttClient mqtt.Client - mqttUsername string - mqttPassword string - mqttBrokers []string - + mqttClient mqtt.Client + mqttUsername string + mqttPassword string + mqttBrokers []string mqttUp chan *mqtt.UplinkMessage mqttActivation chan *mqtt.Activation mqttEvent chan *mqttEvent + + amqpClient amqp.Client + amqpUsername string + amqpPassword string + amqpAddress string + amqpUp chan *amqp.UplinkMessage } func (h *handler) Init(c *core.Component) error { diff --git a/core/types/json_time.go b/core/types/json_time.go new file mode 100644 index 000000000..459b92413 --- /dev/null +++ b/core/types/json_time.go @@ -0,0 +1,40 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import "time" + +// JSONTime is a time.Time that marshals to/from RFC3339Nano format +type JSONTime time.Time + +// MarshalText implements the encoding.TextMarshaler interface +func (t JSONTime) MarshalText() ([]byte, error) { + if time.Time(t).IsZero() || time.Time(t).Unix() == 0 { + return []byte{}, nil + } + stamp := time.Time(t).UTC().Format(time.RFC3339Nano) + return []byte(stamp), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (t *JSONTime) UnmarshalText(text []byte) error { + if len(text) == 0 { + *t = JSONTime{} + return nil + } + time, err := time.Parse(time.RFC3339Nano, string(text)) + if err != nil { + return err + } + *t = JSONTime(time) + return nil +} + +// BuildTime builds a new JSONTime +func BuildTime(unixNano int64) JSONTime { + if unixNano == 0 { + return JSONTime{} + } + return JSONTime(time.Unix(0, 0).Add(time.Duration(unixNano)).UTC()) +} diff --git a/mqtt/types_test.go b/core/types/json_time_test.go similarity index 98% rename from mqtt/types_test.go rename to core/types/json_time_test.go index 1897808e6..7361ea8f6 100644 --- a/mqtt/types_test.go +++ b/core/types/json_time_test.go @@ -1,7 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package mqtt +package types import ( "encoding/json" diff --git a/mqtt/types.go b/mqtt/types.go index 44598ed85..7151a8ef4 100644 --- a/mqtt/types.go +++ b/mqtt/types.go @@ -3,45 +3,7 @@ package mqtt -import ( - "time" - - "github.com/TheThingsNetwork/ttn/core/types" -) - -// JSONTime is a time.Time that marshals to/from RFC3339Nano format -type JSONTime time.Time - -// MarshalText implements the encoding.TextMarshaler interface -func (t JSONTime) MarshalText() ([]byte, error) { - if time.Time(t).IsZero() || time.Time(t).Unix() == 0 { - return []byte{}, nil - } - stamp := time.Time(t).UTC().Format(time.RFC3339Nano) - return []byte(stamp), nil -} - -// UnmarshalText implements the encoding.TextUnmarshaler interface -func (t *JSONTime) UnmarshalText(text []byte) error { - if len(text) == 0 { - *t = JSONTime{} - return nil - } - time, err := time.Parse(time.RFC3339Nano, string(text)) - if err != nil { - return err - } - *t = JSONTime(time) - return nil -} - -// BuildTime builds a new JSONTime -func BuildTime(unixNano int64) JSONTime { - if unixNano == 0 { - return JSONTime{} - } - return JSONTime(time.Unix(0, 0).Add(time.Duration(unixNano)).UTC()) -} +import "github.com/TheThingsNetwork/ttn/core/types" // LocationMetadata contains GPS coordinates type LocationMetadata struct { @@ -52,19 +14,19 @@ type LocationMetadata struct { // GatewayMetadata contains metadata for each gateway that received a message type GatewayMetadata struct { - GtwID string `json:"gtw_id,omitempty"` - Timestamp uint32 `json:"timestamp,omitempty"` - Time JSONTime `json:"time,omitempty"` - Channel uint32 `json:"channel"` - RSSI float32 `json:"rssi,omitempty"` - SNR float32 `json:"snr,omitempty"` - RFChain uint32 `json:"rf_chain,omitempty"` + GtwID string `json:"gtw_id,omitempty"` + Timestamp uint32 `json:"timestamp,omitempty"` + Time types.JSONTime `json:"time,omitempty"` + Channel uint32 `json:"channel"` + RSSI float32 `json:"rssi,omitempty"` + SNR float32 `json:"snr,omitempty"` + RFChain uint32 `json:"rf_chain,omitempty"` LocationMetadata } // Metadata contains metadata of a message type Metadata struct { - Time JSONTime `json:"time,omitempty,omitempty"` + Time types.JSONTime `json:"time,omitempty"` Frequency float32 `json:"frequency,omitempty"` Modulation string `json:"modulation,omitempty"` DataRate string `json:"data_rate,omitempty"` From 94c37200f3c3c6fb0be9957c5b1c21a14a427532 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Wed, 19 Oct 2016 16:45:47 +0200 Subject: [PATCH 2002/2266] Promoted mqtt/types to core/types and integrated basic AMQP uplink --- .env/handler/dev.yml | 5 ++ amqp/{client.go => publisher.go} | 34 +++++++---- amqp/{client_test.go => publisher_test.go} | 13 ++--- amqp/types.go | 48 ---------------- amqp/uplink.go | 4 +- amqp/uplink_test.go | 6 +- cmd/broker.go | 1 + cmd/handler.go | 27 ++++++++- cmd/networkserver.go | 1 + cmd/router.go | 1 + core/broker/broker.go | 2 + core/component.go | 1 + core/discovery/discovery.go | 2 + core/handler/activation.go | 9 ++- core/handler/activation_test.go | 3 +- core/handler/amqp.go | 45 +++++++++++++++ core/handler/convert_fields.go | 6 +- core/handler/convert_fields_test.go | 9 ++- core/handler/convert_lorawan.go | 6 +- core/handler/convert_lorawan_test.go | 10 ++-- core/handler/convert_metadata.go | 12 ++-- core/handler/convert_metadata_test.go | 4 +- core/handler/device/device.go | 23 ++++---- core/handler/downlink.go | 6 +- core/handler/downlink_test.go | 15 +++-- core/handler/handler.go | 44 +++++++++++--- core/handler/mqtt.go | 25 ++++---- core/handler/mqtt_test.go | 11 ++-- core/handler/types.go | 6 +- core/handler/uplink.go | 9 ++- core/handler/uplink_test.go | 5 +- core/networkserver/networkserver.go | 2 + core/router/router.go | 2 + core/types/activation.go | 14 +++++ core/types/downlink_message.go | 13 +++++ core/types/gateway_metadata.go | 16 ++++++ core/types/location_metadata.go | 11 ++++ core/types/metadata.go | 16 ++++++ core/types/uplink_message.go | 15 +++++ mqtt/README.md | 6 +- mqtt/activations.go | 12 ++-- mqtt/activations_test.go | 27 ++++----- mqtt/client.go | 9 +-- mqtt/client_test.go | 5 +- mqtt/downlink.go | 8 ++- mqtt/downlink_test.go | 19 +++--- mqtt/types.go | 67 ---------------------- mqtt/uplink.go | 7 ++- mqtt/uplink_test.go | 19 +++--- 49 files changed, 385 insertions(+), 276 deletions(-) rename amqp/{client.go => publisher.go} (72%) rename amqp/{client_test.go => publisher_test.go} (69%) delete mode 100644 amqp/types.go create mode 100644 core/handler/amqp.go create mode 100644 core/types/activation.go create mode 100644 core/types/downlink_message.go create mode 100644 core/types/gateway_metadata.go create mode 100644 core/types/location_metadata.go create mode 100644 core/types/metadata.go create mode 100644 core/types/uplink_message.go delete mode 100644 mqtt/types.go diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 06a2ac582..b85150764 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -7,5 +7,10 @@ auth-servers: ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/handler/" +handler: + amqp-host: localhost:5672 + amqp-username: guest + amqp-password: guest + amqp-exchange: topic auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg diff --git a/amqp/client.go b/amqp/publisher.go similarity index 72% rename from amqp/client.go rename to amqp/publisher.go index 973040d7f..699fa760d 100644 --- a/amqp/client.go +++ b/amqp/publisher.go @@ -7,6 +7,8 @@ import ( "fmt" "time" + "github.com/TheThingsNetwork/ttn/core/types" + AMQP "github.com/streadway/amqp" ) @@ -16,7 +18,7 @@ type Publisher interface { Disconnect() IsConnected() bool - PublishUplink(payload UplinkMessage) error + PublishUplink(payload types.UplinkMessage) error } // DefaultPublisher is the default AMQP client for The Things Network @@ -36,18 +38,26 @@ var ( ) // NewPublisher creates a new DefaultPublisher -func NewPublisher(ctx Logger, url, exchange string) Publisher { +func NewPublisher(ctx Logger, username, password, host, exchange string) Publisher { if ctx == nil { ctx = &noopLogger{} } + credentials := "guest" + if username != "" { + if password != "" { + credentials = fmt.Sprintf("%s:%s", username, password) + } else { + credentials = username + } + } return &DefaultPublisher{ ctx: ctx, - url: url, + url: fmt.Sprintf("amqp://%s@%s", credentials, host), exchange: exchange, } } -// Connect to the MQTT broker. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +// Connect to the AMQP server. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries func (c *DefaultPublisher) Connect() error { if c.IsConnected() { return nil @@ -59,11 +69,11 @@ func (c *DefaultPublisher) Connect() error { if err == nil { break } - c.ctx.Warnf("Could not connect to AMQP Broker (%s). Retrying...", err.Error()) + c.ctx.Warnf("Could not connect to AMQP server (%s). Retrying...", err.Error()) <-time.After(ConnectRetryDelay) } if err != nil { - return fmt.Errorf("Could not connect to AMQP Broker (%s).", err) + return fmt.Errorf("Could not connect to AMQP server (%s).", err) } channel, err := conn.Channel() if err != nil { @@ -88,19 +98,23 @@ func (c *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) }) } -// Disconnect from the AMQP broker +// Disconnect from the AMQP server func (c *DefaultPublisher) Disconnect() { if !c.IsConnected() { return } c.ctx.Debug("Disconnecting from AMQP") - c.channel.Close() + if err := c.channel.Close(); err != nil { + c.ctx.Warnf("Could not close AMQP channel (%s)", err) + } c.channel = nil - c.conn.Close() + if err := c.conn.Close(); err != nil { + c.ctx.Warnf("Could not close AMQP connection (%s)", err) + } c.conn = nil } -// IsConnected returns true if there is a connection to the AMQP broker +// IsConnected returns true if there is a connection to the AMQP server func (c *DefaultPublisher) IsConnected() bool { return c.conn != nil } diff --git a/amqp/client_test.go b/amqp/publisher_test.go similarity index 69% rename from amqp/client_test.go rename to amqp/publisher_test.go index 13faa4993..e85c35200 100644 --- a/amqp/client_test.go +++ b/amqp/publisher_test.go @@ -4,7 +4,6 @@ package amqp import ( - "fmt" "os" "testing" "time" @@ -18,19 +17,19 @@ var host string func init() { host = os.Getenv("AMQP_ADDR") if host == "" { - host = "localhost" + host = "localhost:5672" } } func TestNewPublisher(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestNewPublisher"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + c := NewPublisher(GetLogger(t, "TestNewPublisher"), "guest", "guest", host, "test") a.So(c, ShouldNotBeNil) } func TestConnect(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestConnect"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + c := NewPublisher(GetLogger(t, "TestConnect"), "guest", "guest", host, "test") err := c.Connect() defer c.Disconnect() a.So(err, ShouldBeNil) @@ -45,7 +44,7 @@ func TestConnectInvalidAddress(t *testing.T) { a := New(t) ConnectRetries = 2 ConnectRetryDelay = 50 * time.Millisecond - c := NewPublisher(GetLogger(t, "TestConnectInvalidAddress"), fmt.Sprintf("amqp://guest:guest@%s:56720/", host), "test") + c := NewPublisher(GetLogger(t, "TestConnectInvalidAddress"), "guest", "guest", "localhost:56720", "test") err := c.Connect() defer c.Disconnect() a.So(err, ShouldNotBeNil) @@ -53,7 +52,7 @@ func TestConnectInvalidAddress(t *testing.T) { func TestIsConnected(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestIsConnected"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + c := NewPublisher(GetLogger(t, "TestIsConnected"), "guest", "guest", host, "test") a.So(c.IsConnected(), ShouldBeFalse) @@ -65,7 +64,7 @@ func TestIsConnected(t *testing.T) { func TestDisconnect(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestDisconnect"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + c := NewPublisher(GetLogger(t, "TestDisconnect"), "guest", "guest", host, "test") // Disconnecting when not connected should not change anything c.Disconnect() diff --git a/amqp/types.go b/amqp/types.go deleted file mode 100644 index dae06a2e2..000000000 --- a/amqp/types.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package amqp - -import "github.com/TheThingsNetwork/ttn/core/types" - -// LocationMetadata contains GPS coordinates -type LocationMetadata struct { - Altitude int32 `json:"altitude,omitempty"` - Longitude float32 `json:"longitude,omitempty"` - Latitude float32 `json:"latitude,omitempty"` -} - -// GatewayMetadata contains metadata for each gateway that received a message -type GatewayMetadata struct { - GtwID string `json:"gtw_id,omitempty"` - Timestamp uint32 `json:"timestamp,omitempty"` - Time types.JSONTime `json:"time,omitempty"` - Channel uint32 `json:"channel"` - RSSI float32 `json:"rssi,omitempty"` - SNR float32 `json:"snr,omitempty"` - RFChain uint32 `json:"rf_chain,omitempty"` - LocationMetadata -} - -// Metadata contains metadata of a message -type Metadata struct { - Time types.JSONTime `json:"time,omitempty,omitempty"` - Frequency float32 `json:"frequency,omitempty"` - Modulation string `json:"modulation,omitempty"` - DataRate string `json:"data_rate,omitempty"` - Bitrate uint32 `json:"bit_rate,omitempty"` - CodingRate string `json:"coding_rate,omitempty"` - Gateways []GatewayMetadata `json:"gateways,omitempty"` - LocationMetadata -} - -// UplinkMessage represents an application-layer uplink message -type UplinkMessage struct { - AppID string `json:"app_id,omitempty"` - DevID string `json:"dev_id,omitempty"` - FPort uint8 `json:"port"` - FCnt uint32 `json:"counter"` - PayloadRaw []byte `json:"payload_raw"` - PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` -} diff --git a/amqp/uplink.go b/amqp/uplink.go index 7b0c2381f..e081ed7d4 100644 --- a/amqp/uplink.go +++ b/amqp/uplink.go @@ -7,10 +7,12 @@ import ( "encoding/json" "fmt" "time" + + "github.com/TheThingsNetwork/ttn/core/types" ) // PublishUplink publishes an uplink message to the MQTT broker -func (c *DefaultPublisher) PublishUplink(dataUp UplinkMessage) error { +func (c *DefaultPublisher) PublishUplink(dataUp types.UplinkMessage) error { key := DeviceKey{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} msg, err := json.Marshal(dataUp) if err != nil { diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index 672da4af0..c09ea89a5 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -1,21 +1,21 @@ package amqp import ( - "fmt" "testing" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestPublishUplink(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestPublishUplink"), fmt.Sprintf("amqp://guest:guest@%s:5672/", host), "test") + c := NewPublisher(GetLogger(t, "TestPublishUplink"), "guest", "guest", host, "test") err := c.Connect() defer c.Disconnect() a.So(err, ShouldBeNil) - err = c.PublishUplink(UplinkMessage{ + err = c.PublishUplink(types.UplinkMessage{ AppID: "app", DevID: "test", PayloadRaw: []byte{0x01, 0x08}, diff --git a/cmd/broker.go b/cmd/broker.go index 09a400e57..fe9ee95f3 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -79,6 +79,7 @@ var brokerCmd = &cobra.Command{ ctx.WithField("signal", <-sigChan).Info("signal received") grpc.Stop() + broker.Shutdown() }, } diff --git a/cmd/handler.go b/cmd/handler.go index 4197e0402..56e22a029 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -30,8 +30,9 @@ var handlerCmd = &cobra.Command{ "Server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), "Announce": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port")), "Database": fmt.Sprintf("%s/%d", viper.GetString("handler.redis-address"), viper.GetInt("handler.redis-db")), - "TTNbroker": viper.GetString("handler.ttn-broker"), - "MQTTbroker": viper.GetString("handler.mqtt-broker"), + "TTNBroker": viper.GetString("handler.ttn-broker"), + "MQTTBroker": viper.GetString("handler.mqtt-broker"), + "AMQPHost": viper.GetString("handler.amqp-host"), }).Info("Initializing Handler") }, Run: func(cmd *cobra.Command, args []string) { @@ -52,7 +53,7 @@ var handlerCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not initialize component") } - // Broker + // Handler handler := handler.NewRedisHandler( client, viper.GetString("handler.ttn-broker"), @@ -60,6 +61,13 @@ var handlerCmd = &cobra.Command{ viper.GetString("handler.mqtt-password"), viper.GetString("handler.mqtt-broker"), ) + if viper.GetString("handler.amqp-host") != "" { + handler = handler.WithAMQP( + viper.GetString("handler.amqp-username"), + viper.GetString("handler.amqp-password"), + viper.GetString("handler.amqp-host"), + viper.GetString("handler.amqp-exchange")) + } err = handler.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize handler") @@ -82,6 +90,7 @@ var handlerCmd = &cobra.Command{ ctx.WithField("signal", <-sigChan).Info("signal received") grpc.Stop() + handler.Shutdown() }, } @@ -106,6 +115,18 @@ func init() { handlerCmd.Flags().String("mqtt-password", "", "MQTT password") viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) + handlerCmd.Flags().String("amqp-host", "", "AMQP host and port. Leave empty to disable AMQP") + viper.BindPFlag("handler.amqp-host", handlerCmd.Flags().Lookup("amqp-host")) + + handlerCmd.Flags().String("amqp-username", "handler", "AMQP username") + viper.BindPFlag("handler.amqp-username", handlerCmd.Flags().Lookup("amqp-username")) + + handlerCmd.Flags().String("amqp-password", "", "AMQP password") + viper.BindPFlag("handler.amqp-password", handlerCmd.Flags().Lookup("amqp-password")) + + handlerCmd.Flags().String("amqp-exchange", "", "AMQP exchange") + viper.BindPFlag("handler.amqp-exchange", handlerCmd.Flags().Lookup("amqp-exchange")) + handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") handlerCmd.Flags().String("server-address-announce", "localhost", "The public IP address to announce") handlerCmd.Flags().Int("server-port", 1904, "The port for communication") diff --git a/cmd/networkserver.go b/cmd/networkserver.go index f5e69a796..cfc74515b 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -91,6 +91,7 @@ var networkserverCmd = &cobra.Command{ ctx.WithField("signal", <-sigChan).Info("signal received") grpc.Stop() + networkserver.Shutdown() }, } diff --git a/cmd/router.go b/cmd/router.go index 956083f9f..d7d6e504e 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -63,6 +63,7 @@ var routerCmd = &cobra.Command{ ctx.WithField("signal", <-sigChan).Info("signal received") grpc.Stop() + router.Shutdown() }, } diff --git a/core/broker/broker.go b/core/broker/broker.go index 0e6c321fb..33dd855fb 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -136,6 +136,8 @@ func (b *broker) Init(c *core.Component) error { return nil } +func (b *broker) Shutdown() {} + func (b *broker) ActivateRouter(id string) (<-chan *pb.DownlinkMessage, error) { b.routersLock.Lock() defer b.routersLock.Unlock() diff --git a/core/component.go b/core/component.go index 8bde2c663..e0678bae0 100644 --- a/core/component.go +++ b/core/component.go @@ -35,6 +35,7 @@ import ( type ComponentInterface interface { RegisterRPC(s *grpc.Server) Init(c *Component) error + Shutdown() ValidateNetworkContext(ctx context.Context) (*pb_discovery.Announcement, error) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index f16665b39..b698b4d2d 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -43,6 +43,8 @@ func (d *discovery) Init(c *core.Component) error { return nil } +func (d *discovery) Shutdown() {} + func (d *discovery) Announce(in *pb.Announcement) error { service, err := d.services.Get(in.ServiceName, in.Id) if err != nil && errors.GetErrType(err) != errors.NotFound { diff --git a/core/handler/activation.go b/core/handler/activation.go index 4f81473ac..f875d94a3 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -11,7 +11,6 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/otaa" "github.com/TheThingsNetwork/ttn/utils/random" @@ -19,16 +18,16 @@ import ( "github.com/brocaar/lorawan" ) -func (h *handler) getActivationMetadata(ctx log.Interface, activation *pb_broker.DeduplicatedDeviceActivationRequest) (mqtt.Metadata, error) { +func (h *handler) getActivationMetadata(ctx log.Interface, activation *pb_broker.DeduplicatedDeviceActivationRequest) (types.Metadata, error) { ttnUp := &pb_broker.DeduplicatedUplinkMessage{ ProtocolMetadata: activation.ProtocolMetadata, GatewayMetadata: activation.GatewayMetadata, ServerTime: activation.ServerTime, } - mqttUp := &mqtt.UplinkMessage{} + mqttUp := &types.UplinkMessage{} err := h.ConvertMetadata(ctx, ttnUp, mqttUp) if err != nil { - return mqtt.Metadata{}, err + return types.Metadata{}, err } return mqttUp.Metadata, nil } @@ -173,7 +172,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // Publish Activation mqttMetadata, _ := h.getActivationMetadata(ctx, activation) - h.mqttActivation <- &mqtt.Activation{ + h.mqttActivation <- &types.Activation{ AppEUI: *activation.AppEui, DevEUI: *activation.DevEui, AppID: appID, diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index cddaf5ca8..a7bb0bcbf 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -15,7 +15,6 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/brocaar/lorawan" . "github.com/smartystreets/assertions" @@ -71,7 +70,7 @@ func TestHandleActivation(t *testing.T) { applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), } - h.mqttActivation = make(chan *mqtt.Activation) + h.mqttActivation = make(chan *types.Activation) h.mqttEvent = make(chan *mqttEvent, 10) var wg WaitGroup diff --git a/core/handler/amqp.go b/core/handler/amqp.go new file mode 100644 index 000000000..352669640 --- /dev/null +++ b/core/handler/amqp.go @@ -0,0 +1,45 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/apex/log" + + "github.com/TheThingsNetwork/ttn/amqp" + "github.com/TheThingsNetwork/ttn/core/types" +) + +// AMQPBufferSize indicates the size for uplink channel buffers +var AMQPBufferSize = 10 + +func (h *handler) HandleAMQP(username, password, host, exchange string) error { + h.amqpPublisher = amqp.NewPublisher(h.Ctx, username, password, host, exchange) + + err := h.amqpPublisher.Connect() + if err != nil { + return err + } + + h.amqpUp = make(chan *types.UplinkMessage, AMQPBufferSize) + + ctx := h.Ctx.WithField("Protocol", "AMQP") + + go func() { + for up := range h.amqpUp { + ctx.WithFields(log.Fields{ + "DevID": up.DevID, + "AppID": up.AppID, + }).Debug("Publish Uplink") + msg := *up + go func() { + err := h.amqpPublisher.PublishUplink(msg) + if err != nil { + ctx.WithError(err).Warn("Could not publish Uplink") + } + }() + } + }() + + return nil +} diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index ff9711a27..d388c9ea6 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -9,14 +9,14 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/robertkrimen/otto" ) // ConvertFieldsUp converts the payload to fields using payload functions -func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { +func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { // Find Application app, err := h.applications.Get(ttnUp.AppId) if err != nil { @@ -277,7 +277,7 @@ func (f *DownlinkFunctions) Process(payload map[string]interface{}) ([]byte, boo } // ConvertFieldsDown converts the fields into a payload -func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { +func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { if appDown.PayloadFields == nil { return nil } diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index ae750b9dc..f1802cb30 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -9,7 +9,6 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/types" @@ -17,12 +16,12 @@ import ( . "github.com/smartystreets/assertions" ) -func buildConversionUplink(appID string) (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { +func buildConversionUplink(appID string) (*pb_broker.DeduplicatedUplinkMessage, *types.UplinkMessage) { ttnUp := &pb_broker.DeduplicatedUplinkMessage{ AppId: appID, DevId: "DevID-1", } - appUp := &mqtt.UplinkMessage{ + appUp := &types.UplinkMessage{ FPort: 1, AppID: appID, DevID: "DevID-1", @@ -302,14 +301,14 @@ func TestEncode(t *testing.T) { a.So(err, ShouldBeNil) } -func buildConversionDownlink() (*pb_broker.DownlinkMessage, *mqtt.DownlinkMessage) { +func buildConversionDownlink() (*pb_broker.DownlinkMessage, *types.DownlinkMessage) { appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) ttnDown := &pb_broker.DownlinkMessage{ AppEui: &appEUI, DevEui: &devEUI, } - appDown := &mqtt.DownlinkMessage{ + appDown := &types.DownlinkMessage{ FPort: 1, AppID: "AppID-1", DevID: "DevID-1", diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index 34f92b4c9..e3ee9dc14 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -5,14 +5,14 @@ package handler import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/pointer" "github.com/apex/log" "github.com/brocaar/lorawan" ) -func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { +func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { // Find Device dev, err := h.devices.Get(ttnUp.AppId, ttnUp.DevId) if err != nil { @@ -77,7 +77,7 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli return nil } -func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { +func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error { // Find Device dev, err := h.devices.Get(appDown.AppID, appDown.DevID) if err != nil { diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index c81df3e1c..af13e617a 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -11,12 +11,12 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/device" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) -func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, *mqtt.UplinkMessage) { +func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, *types.UplinkMessage) { ttnUp := &pb_broker.DeduplicatedUplinkMessage{ DevId: "devid", AppId: "appid", @@ -27,7 +27,7 @@ func buildLorawanUplink(payload []byte) (*pb_broker.DeduplicatedUplinkMessage, * }, }}, } - appUp := &mqtt.UplinkMessage{} + appUp := &types.UplinkMessage{} return ttnUp, appUp } @@ -52,8 +52,8 @@ func TestConvertFromLoRaWAN(t *testing.T) { a.So(appUp.FCnt, ShouldEqual, 1) } -func buildLorawanDownlink(payload []byte) (*mqtt.DownlinkMessage, *pb_broker.DownlinkMessage) { - appDown := &mqtt.DownlinkMessage{ +func buildLorawanDownlink(payload []byte) (*types.DownlinkMessage, *pb_broker.DownlinkMessage) { + appDown := &types.DownlinkMessage{ DevID: "devid", AppID: "appid", PayloadRaw: []byte{0xaa, 0xbc}, diff --git a/core/handler/convert_metadata.go b/core/handler/convert_metadata.go index 910b137d3..b9f3a114e 100644 --- a/core/handler/convert_metadata.go +++ b/core/handler/convert_metadata.go @@ -5,16 +5,16 @@ package handler import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) // ConvertMetadata converts the protobuf matadata to application metadata -func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error { +func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error { ctx = ctx.WithField("NumGateways", len(ttnUp.GatewayMetadata)) // Transform Metadata - appUp.Metadata.Time = mqtt.BuildTime(ttnUp.ServerTime) + appUp.Metadata.Time = types.BuildTime(ttnUp.ServerTime) if lorawan := ttnUp.ProtocolMetadata.GetLorawan(); lorawan != nil { appUp.Metadata.Modulation = lorawan.Modulation.String() appUp.Metadata.DataRate = lorawan.DataRate @@ -23,7 +23,7 @@ func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.Deduplicat } // Transform Gateway Metadata - appUp.Metadata.Gateways = make([]mqtt.GatewayMetadata, 0, len(ttnUp.GatewayMetadata)) + appUp.Metadata.Gateways = make([]types.GatewayMetadata, 0, len(ttnUp.GatewayMetadata)) for i, in := range ttnUp.GatewayMetadata { // Same for all gateways, take first one @@ -31,10 +31,10 @@ func (h *handler) ConvertMetadata(ctx log.Interface, ttnUp *pb_broker.Deduplicat appUp.Metadata.Frequency = float32(float64(in.Frequency) / 1000000) } - gatewayMetadata := mqtt.GatewayMetadata{ + gatewayMetadata := types.GatewayMetadata{ GtwID: in.GatewayId, Timestamp: in.Timestamp, - Time: mqtt.BuildTime(in.Time), + Time: types.BuildTime(in.Time), Channel: in.Channel, RFChain: in.RfChain, RSSI: in.Rssi, diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go index 8ed35faba..d8fb934cb 100644 --- a/core/handler/convert_metadata_test.go +++ b/core/handler/convert_metadata_test.go @@ -12,7 +12,7 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -24,7 +24,7 @@ func TestConvertMetadata(t *testing.T) { } ttnUp := &pb_broker.DeduplicatedUplinkMessage{} - appUp := &mqtt.UplinkMessage{} + appUp := &types.UplinkMessage{} err := h.ConvertMetadata(h.Ctx, ttnUp, appUp) a.So(err, ShouldBeNil) diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 783147b45..41416ef85 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -8,7 +8,6 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" "github.com/fatih/structs" ) @@ -18,17 +17,17 @@ type AppNonce [3]byte // Device contains the state of a device type Device struct { old *Device - DevEUI types.DevEUI `redis:"dev_eui"` - AppEUI types.AppEUI `redis:"app_eui"` - AppID string `redis:"app_id"` - DevID string `redis:"dev_id"` - DevAddr types.DevAddr `redis:"dev_addr"` - AppKey types.AppKey `redis:"app_key"` - UsedDevNonces []DevNonce `redis:"used_dev_nonces"` - UsedAppNonces []AppNonce `redis:"used_app_nonces"` - NwkSKey types.NwkSKey `redis:"nwk_s_key"` - AppSKey types.AppSKey `redis:"app_s_key"` - NextDownlink *mqtt.DownlinkMessage `redis:"next_downlink"` + DevEUI types.DevEUI `redis:"dev_eui"` + AppEUI types.AppEUI `redis:"app_eui"` + AppID string `redis:"app_id"` + DevID string `redis:"dev_id"` + DevAddr types.DevAddr `redis:"dev_addr"` + AppKey types.AppKey `redis:"app_key"` + UsedDevNonces []DevNonce `redis:"used_dev_nonces"` + UsedAppNonces []AppNonce `redis:"used_app_nonces"` + NwkSKey types.NwkSKey `redis:"nwk_s_key"` + AppSKey types.AppSKey `redis:"app_s_key"` + NextDownlink *types.DownlinkMessage `redis:"next_downlink"` CreatedAt time.Time `redis:"created_at"` UpdatedAt time.Time `redis:"updated_at"` diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 4ca3d0609..e08ebd810 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -7,11 +7,11 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) -func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) (err error) { +func (h *handler) EnqueueDownlink(appDownlink *types.DownlinkMessage) (err error) { appID, devID := appDownlink.AppID, appDownlink.DevID ctx := h.Ctx.WithFields(log.Fields{ @@ -53,7 +53,7 @@ func (h *handler) EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) (err error) return nil } -func (h *handler) HandleDownlink(appDownlink *mqtt.DownlinkMessage, downlink *pb_broker.DownlinkMessage) error { +func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *pb_broker.DownlinkMessage) error { appID, devID := appDownlink.AppID, appDownlink.DevID ctx := h.Ctx.WithFields(log.Fields{ diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 9e9269c32..a8456fceb 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -12,7 +12,6 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -26,7 +25,7 @@ func TestEnqueueDownlink(t *testing.T) { devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-enqueue-downlink"), mqttEvent: make(chan *mqttEvent, 10), } - err := h.EnqueueDownlink(&mqtt.DownlinkMessage{ + err := h.EnqueueDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, }) @@ -38,7 +37,7 @@ func TestEnqueueDownlink(t *testing.T) { defer func() { h.devices.Delete(appID, devID) }() - err = h.EnqueueDownlink(&mqtt.DownlinkMessage{ + err = h.EnqueueDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, PayloadFields: map[string]interface{}{ @@ -69,7 +68,7 @@ func TestHandleDownlink(t *testing.T) { mqttEvent: make(chan *mqttEvent, 10), } // Neither payload nor Fields provided : ERROR - err = h.HandleDownlink(&mqtt.DownlinkMessage{ + err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, }, &pb_broker.DownlinkMessage{ @@ -85,7 +84,7 @@ func TestHandleDownlink(t *testing.T) { defer func() { h.devices.Delete(appID, devID) }() - err = h.HandleDownlink(&mqtt.DownlinkMessage{ + err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, }, &pb_broker.DownlinkMessage{ @@ -102,7 +101,7 @@ func TestHandleDownlink(t *testing.T) { a.So(dl.Payload, ShouldNotBeEmpty) wg.Done() }() - err = h.HandleDownlink(&mqtt.DownlinkMessage{ + err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, DevID: devID, PayloadRaw: []byte{0xAA, 0xBC}, @@ -125,7 +124,7 @@ func TestHandleDownlink(t *testing.T) { h.applications.Delete(appID) }() jsonFields := map[string]interface{}{"temperature": 11} - err = h.HandleDownlink(&mqtt.DownlinkMessage{ + err = h.HandleDownlink(&types.DownlinkMessage{ FPort: 1, AppID: appID, DevID: devID, @@ -145,7 +144,7 @@ func TestHandleDownlink(t *testing.T) { a.So(dl.Payload, ShouldNotBeEmpty) wg.Done() }() - err = h.HandleDownlink(&mqtt.DownlinkMessage{ + err = h.HandleDownlink(&types.DownlinkMessage{ FPort: 1, AppID: appID, DevID: devID, diff --git a/core/handler/handler.go b/core/handler/handler.go index 920df8cca..61e24124d 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -14,6 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc" @@ -25,10 +26,12 @@ type Handler interface { core.ComponentInterface core.ManagementInterface + WithAMQP(username, password, host, exchange string) Handler + HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error HandleActivationChallenge(challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) HandleActivation(activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) - EnqueueDownlink(appDownlink *mqtt.DownlinkMessage) error + EnqueueDownlink(appDownlink *types.DownlinkMessage) error } // NewRedisHandler creates a new Redis-backed Handler @@ -60,15 +63,26 @@ type handler struct { mqttUsername string mqttPassword string mqttBrokers []string - mqttUp chan *mqtt.UplinkMessage - mqttActivation chan *mqtt.Activation + mqttUp chan *types.UplinkMessage + mqttActivation chan *types.Activation mqttEvent chan *mqttEvent - amqpClient amqp.Client - amqpUsername string - amqpPassword string - amqpAddress string - amqpUp chan *amqp.UplinkMessage + amqpPublisher amqp.Publisher + amqpUsername string + amqpPassword string + amqpHost string + amqpExchange string + amqpEnabled bool + amqpUp chan *types.UplinkMessage +} + +func (h *handler) WithAMQP(username, password, host, exchange string) Handler { + h.amqpUsername = username + h.amqpPassword = password + h.amqpHost = host + h.amqpExchange = exchange + h.amqpEnabled = true + return h } func (h *handler) Init(c *core.Component) error { @@ -92,6 +106,13 @@ func (h *handler) Init(c *core.Component) error { return err } + if h.amqpEnabled { + err = h.HandleAMQP(h.amqpUsername, h.amqpPassword, h.amqpHost, h.amqpExchange) + if err != nil { + return err + } + } + err = h.associateBroker() if err != nil { return err @@ -102,6 +123,13 @@ func (h *handler) Init(c *core.Component) error { return nil } +func (h *handler) Shutdown() { + h.mqttClient.Disconnect() + if h.amqpEnabled { + h.amqpPublisher.Disconnect() + } +} + func (h *handler) associateBroker() error { broker, err := h.Discover("broker", h.ttnBrokerID) if err != nil { diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 2e05db1ad..1cf55a1c5 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -6,6 +6,7 @@ package handler import ( "time" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) @@ -31,11 +32,11 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e return err } - h.mqttUp = make(chan *mqtt.UplinkMessage, MQTTBufferSize) - h.mqttActivation = make(chan *mqtt.Activation, MQTTBufferSize) + h.mqttUp = make(chan *types.UplinkMessage, MQTTBufferSize) + h.mqttActivation = make(chan *types.Activation, MQTTBufferSize) h.mqttEvent = make(chan *mqttEvent, MQTTBufferSize) - token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appID string, devID string, msg mqtt.DownlinkMessage) { + token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appID string, devID string, msg types.DownlinkMessage) { down := &msg down.DevID = devID down.AppID = appID @@ -46,9 +47,11 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e return err } + ctx := h.Ctx.WithField("Protocol", "MQTT") + go func() { for up := range h.mqttUp { - h.Ctx.WithFields(log.Fields{ + ctx.WithFields(log.Fields{ "DevID": up.DevID, "AppID": up.AppID, }).Debug("Publish Uplink") @@ -56,10 +59,10 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e go func() { if upToken.WaitTimeout(MQTTTimeout) { if upToken.Error() != nil { - h.Ctx.WithError(upToken.Error()).Warn("Could not publish Uplink") + ctx.WithError(upToken.Error()).Warn("Could not publish Uplink") } } else { - h.Ctx.Warn("Uplink publish timeout") + ctx.Warn("Uplink publish timeout") } }() if len(up.PayloadFields) > 0 { @@ -67,10 +70,10 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e go func() { if fieldsToken.WaitTimeout(MQTTTimeout) { if fieldsToken.Error() != nil { - h.Ctx.WithError(fieldsToken.Error()).Warn("Could not publish Uplink Fields") + ctx.WithError(fieldsToken.Error()).Warn("Could not publish Uplink Fields") } } else { - h.Ctx.Warn("Uplink Fields publish timeout") + ctx.Warn("Uplink Fields publish timeout") } }() } @@ -79,7 +82,7 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e go func() { for activation := range h.mqttActivation { - h.Ctx.WithFields(log.Fields{ + ctx.WithFields(log.Fields{ "DevID": activation.DevID, "AppID": activation.AppID, "DevEUI": activation.DevEUI, @@ -90,10 +93,10 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e go func() { if token.WaitTimeout(MQTTTimeout) { if token.Error() != nil { - h.Ctx.WithError(token.Error()).Warn("Could not publish Activation") + ctx.WithError(token.Error()).Warn("Could not publish Activation") } } else { - h.Ctx.Warn("Activation publish timeout") + ctx.Warn("Activation publish timeout") } }() } diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 23318c04d..e7ac02773 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -42,7 +43,7 @@ func TestHandleMQTT(t *testing.T) { err := h.HandleMQTT("", "", fmt.Sprintf("tcp://%s:1883", host)) a.So(err, ShouldBeNil) - c.PublishDownlink(mqtt.DownlinkMessage{ + c.PublishDownlink(types.DownlinkMessage{ AppID: appID, DevID: devID, PayloadRaw: []byte{0xAA, 0xBC}, @@ -52,14 +53,14 @@ func TestHandleMQTT(t *testing.T) { a.So(dev.NextDownlink, ShouldNotBeNil) wg.Add(1) - c.SubscribeDeviceUplink(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req mqtt.UplinkMessage) { + c.SubscribeDeviceUplink(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req types.UplinkMessage) { a.So(r_appID, ShouldEqual, appID) a.So(r_devID, ShouldEqual, devID) a.So(req.PayloadRaw, ShouldResemble, []byte{0xAA, 0xBC}) wg.Done() }).Wait() - h.mqttUp <- &mqtt.UplinkMessage{ + h.mqttUp <- &types.UplinkMessage{ DevID: devID, AppID: appID, PayloadRaw: []byte{0xAA, 0xBC}, @@ -69,13 +70,13 @@ func TestHandleMQTT(t *testing.T) { } wg.Add(1) - c.SubscribeDeviceActivations(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req mqtt.Activation) { + c.SubscribeDeviceActivations(appID, devID, func(client mqtt.Client, r_appID string, r_devID string, req types.Activation) { a.So(r_appID, ShouldEqual, appID) a.So(r_devID, ShouldEqual, devID) wg.Done() }).Wait() - h.mqttActivation <- &mqtt.Activation{ + h.mqttActivation <- &types.Activation{ DevID: devID, AppID: appID, } diff --git a/core/handler/types.go b/core/handler/types.go index 02ad2c60a..14de0016c 100644 --- a/core/handler/types.go +++ b/core/handler/types.go @@ -4,18 +4,18 @@ package handler import ( + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" ) // UplinkProcessor processes an uplink protobuf to an application-layer uplink message -type UplinkProcessor func(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *mqtt.UplinkMessage) error +type UplinkProcessor func(ctx log.Interface, ttnUp *pb_broker.DeduplicatedUplinkMessage, appUp *types.UplinkMessage) error // DownlinkProcessor processes an application-layer downlink message to a downlik protobuf -type DownlinkProcessor func(ctx log.Interface, appDown *mqtt.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error +type DownlinkProcessor func(ctx log.Interface, appDown *types.DownlinkMessage, ttnDown *pb_broker.DownlinkMessage) error // ErrNotNeeded indicates that the processing of a message should be aborted var ErrNotNeeded = errors.New("Further processing not needed") diff --git a/core/handler/uplink.go b/core/handler/uplink.go index 9e39a6df2..145dcd7c4 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -7,7 +7,7 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/mqtt" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" ) @@ -36,7 +36,7 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err }() // Build AppUplink - appUplink := &mqtt.UplinkMessage{ + appUplink := &types.UplinkMessage{ AppID: appID, DevID: devID, } @@ -63,11 +63,14 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err // Publish Uplink h.mqttUp <- appUplink + if h.amqpEnabled { + h.amqpUp <- appUplink + } <-time.After(ResponseDeadline) // Find Device and scheduled downlink - var appDownlink mqtt.DownlinkMessage + var appDownlink types.DownlinkMessage dev, err := h.devices.Get(uplink.AppId, uplink.DevId) if err != nil { return err diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index 40f57fb09..00ccabf15 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -14,7 +14,6 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -48,7 +47,7 @@ func TestHandleUplink(t *testing.T) { defer func() { h.applications.Delete(appID) }() - h.mqttUp = make(chan *mqtt.UplinkMessage) + h.mqttUp = make(chan *types.UplinkMessage) h.mqttEvent = make(chan *mqttEvent, 10) h.downlink = make(chan *pb_broker.DownlinkMessage) @@ -121,7 +120,7 @@ func TestHandleUplink(t *testing.T) { wg.WaitFor(50 * time.Millisecond) dev.StartUpdate() - dev.NextDownlink = &mqtt.DownlinkMessage{ + dev.NextDownlink = &types.DownlinkMessage{ PayloadRaw: []byte{0xaa, 0xbc}, } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 2410236ce..7c8c40cb0 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -84,3 +84,5 @@ func (n *networkServer) Init(c *core.Component) error { n.Component.SetStatus(core.StatusHealthy) return nil } + +func (n *networkServer) Shutdown() {} diff --git a/core/router/router.go b/core/router/router.go index 26897f81e..ac3d8b826 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -89,6 +89,8 @@ func (r *router) Init(c *core.Component) error { return nil } +func (r *router) Shutdown() {} + // getGateway gets or creates a Gateway func (r *router) getGateway(id string) *gateway.Gateway { // We're going to be optimistic and guess that the gateway is already active diff --git a/core/types/activation.go b/core/types/activation.go new file mode 100644 index 000000000..ee8aad09e --- /dev/null +++ b/core/types/activation.go @@ -0,0 +1,14 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// Activation messages are used to notify application of a device activation +type Activation struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + AppEUI AppEUI `json:"app_eui,omitempty"` + DevEUI DevEUI `json:"dev_eui,omitempty"` + DevAddr DevAddr `json:"dev_addr,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` +} diff --git a/core/types/downlink_message.go b/core/types/downlink_message.go new file mode 100644 index 000000000..986ae6489 --- /dev/null +++ b/core/types/downlink_message.go @@ -0,0 +1,13 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// DownlinkMessage represents an application-layer downlink message +type DownlinkMessage struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + PayloadRaw []byte `json:"payload_raw,omitempty"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` +} diff --git a/core/types/gateway_metadata.go b/core/types/gateway_metadata.go new file mode 100644 index 000000000..4c7cb9809 --- /dev/null +++ b/core/types/gateway_metadata.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// GatewayMetadata contains metadata for each gateway that received a message +type GatewayMetadata struct { + GtwID string `json:"gtw_id,omitempty"` + Timestamp uint32 `json:"timestamp,omitempty"` + Time JSONTime `json:"time,omitempty"` + Channel uint32 `json:"channel"` + RSSI float32 `json:"rssi,omitempty"` + SNR float32 `json:"snr,omitempty"` + RFChain uint32 `json:"rf_chain,omitempty"` + LocationMetadata +} diff --git a/core/types/location_metadata.go b/core/types/location_metadata.go new file mode 100644 index 000000000..7f3c6a691 --- /dev/null +++ b/core/types/location_metadata.go @@ -0,0 +1,11 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// LocationMetadata contains GPS coordinates +type LocationMetadata struct { + Altitude int32 `json:"altitude,omitempty"` + Longitude float32 `json:"longitude,omitempty"` + Latitude float32 `json:"latitude,omitempty"` +} diff --git a/core/types/metadata.go b/core/types/metadata.go new file mode 100644 index 000000000..c9935a93e --- /dev/null +++ b/core/types/metadata.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// Metadata contains metadata of a message +type Metadata struct { + Time JSONTime `json:"time,omitempty,omitempty"` + Frequency float32 `json:"frequency,omitempty"` + Modulation string `json:"modulation,omitempty"` + DataRate string `json:"data_rate,omitempty"` + Bitrate uint32 `json:"bit_rate,omitempty"` + CodingRate string `json:"coding_rate,omitempty"` + Gateways []GatewayMetadata `json:"gateways,omitempty"` + LocationMetadata +} diff --git a/core/types/uplink_message.go b/core/types/uplink_message.go new file mode 100644 index 000000000..f20129e8f --- /dev/null +++ b/core/types/uplink_message.go @@ -0,0 +1,15 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// UplinkMessage represents an application-layer uplink message +type UplinkMessage struct { + AppID string `json:"app_id,omitempty"` + DevID string `json:"dev_id,omitempty"` + FPort uint8 `json:"port"` + FCnt uint32 `json:"counter"` + PayloadRaw []byte `json:"payload_raw"` + PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` + Metadata Metadata `json:"metadata,omitempty"` +} diff --git a/mqtt/README.md b/mqtt/README.md index 00d6148c0..fc6a7f821 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -53,7 +53,7 @@ client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".theth if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } -token := client.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req UplinkMessage) { +token := client.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req types.UplinkMessage) { // Do something with the uplink message }) token.Wait() @@ -112,7 +112,7 @@ client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".theth if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } -token := client.PublishDownlink(DownlinkMessage{ +token := client.PublishDownlink(types.DownlinkMessage{ AppID: "my-app-id", DevID: "my-dev-id", FPort: 1, @@ -149,7 +149,7 @@ client := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", ".theth if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") } -token := client.PublishDownlink(DownlinkMessage{ +token := client.PublishDownlink(types.DownlinkMessage{ AppID: "my-app-id", DevID: "my-dev-id", FPort: 1, diff --git a/mqtt/activations.go b/mqtt/activations.go index cd1fdb18b..66031c4f9 100644 --- a/mqtt/activations.go +++ b/mqtt/activations.go @@ -3,16 +3,20 @@ package mqtt -import "encoding/json" +import ( + "encoding/json" + + "github.com/TheThingsNetwork/ttn/core/types" +) // ActivationHandler is called for activations -type ActivationHandler func(client Client, appID string, devID string, req Activation) +type ActivationHandler func(client Client, appID string, devID string, req types.Activation) // ActivationEvent for MQTT const ActivationEvent = "activations" // PublishActivation publishes an activation -func (c *DefaultClient) PublishActivation(activation Activation) Token { +func (c *DefaultClient) PublishActivation(activation types.Activation) Token { appID := activation.AppID devID := activation.DevID activation.AppID = "" @@ -23,7 +27,7 @@ func (c *DefaultClient) PublishActivation(activation Activation) Token { // SubscribeDeviceActivations subscribes to all activations for the given application and device func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { return c.SubscribeDeviceEvents(appID, devID, ActivationEvent, func(_ Client, appID string, devID string, _ string, payload []byte) { - activation := Activation{} + activation := types.Activation{} if err := json.Unmarshal(payload, &activation); err != nil { c.ctx.Warnf("Could not unmarshal activation (%s).", err.Error()) return diff --git a/mqtt/activations_test.go b/mqtt/activations_test.go index bd2186d52..0686ed216 100644 --- a/mqtt/activations_test.go +++ b/mqtt/activations_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -20,10 +21,10 @@ func TestPublishActivations(t *testing.T) { c.Connect() defer c.Disconnect() - dataActivations := Activation{ + dataActivations := types.Activation{ AppID: "someid", DevID: "someid", - Metadata: Metadata{DataRate: "SF7BW125"}, + Metadata: types.Metadata{DataRate: "SF7BW125"}, } token := c.PublishActivation(dataActivations) @@ -38,7 +39,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req Activation) { + token := c.SubscribeDeviceActivations("someid", "someid", func(client Client, appID string, devID string, req types.Activation) { }) waitForOK(token, a) @@ -55,7 +56,7 @@ func TestSubscribeAppActivations(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req Activation) { + token := c.SubscribeAppActivations("someid", func(client Client, appID string, devID string, req types.Activation) { }) waitForOK(token, a) @@ -72,7 +73,7 @@ func TestSubscribeActivations(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeActivations(func(client Client, appID string, devID string, req Activation) { + token := c.SubscribeActivations(func(client Client, appID string, devID string, req types.Activation) { }) waitForOK(token, a) @@ -93,7 +94,7 @@ func TestPubSubActivations(t *testing.T) { wg.Add(1) - subToken := c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req Activation) { + subToken := c.SubscribeDeviceActivations("app5", "dev1", func(client Client, appID string, devID string, req types.Activation) { a.So(appID, ShouldResemble, "app5") a.So(devID, ShouldResemble, "dev1") @@ -101,10 +102,10 @@ func TestPubSubActivations(t *testing.T) { }) waitForOK(subToken, a) - pubToken := c.PublishActivation(Activation{ + pubToken := c.PublishActivation(types.Activation{ AppID: "app5", DevID: "dev1", - Metadata: Metadata{DataRate: "SF7BW125"}, + Metadata: types.Metadata{DataRate: "SF7BW125"}, }) waitForOK(pubToken, a) @@ -124,23 +125,23 @@ func TestPubSubAppActivations(t *testing.T) { wg.Add(2) - subToken := c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req Activation) { + subToken := c.SubscribeAppActivations("app6", func(client Client, appID string, devID string, req types.Activation) { a.So(appID, ShouldResemble, "app6") a.So(req.Metadata.DataRate, ShouldEqual, "SF7BW125") wg.Done() }) waitForOK(subToken, a) - pubToken := c.PublishActivation(Activation{ + pubToken := c.PublishActivation(types.Activation{ AppID: "app6", DevID: "dev1", - Metadata: Metadata{DataRate: "SF7BW125"}, + Metadata: types.Metadata{DataRate: "SF7BW125"}, }) waitForOK(pubToken, a) - pubToken = c.PublishActivation(Activation{ + pubToken = c.PublishActivation(types.Activation{ AppID: "app6", DevID: "dev2", - Metadata: Metadata{DataRate: "SF7BW125"}, + Metadata: types.Metadata{DataRate: "SF7BW125"}, }) waitForOK(pubToken, a) diff --git a/mqtt/client.go b/mqtt/client.go index 10d791206..95b8b0ccf 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" MQTT "github.com/eclipse/paho.mqtt.golang" ) @@ -29,7 +30,7 @@ type Client interface { IsConnected() bool // Uplink pub/sub - PublishUplink(payload UplinkMessage) Token + PublishUplink(payload types.UplinkMessage) Token PublishUplinkFields(appID string, devID string, fields map[string]interface{}) Token SubscribeDeviceUplink(appID string, devID string, handler UplinkHandler) Token SubscribeAppUplink(appID string, handler UplinkHandler) Token @@ -39,7 +40,7 @@ type Client interface { UnsubscribeUplink() Token // Downlink pub/sub - PublishDownlink(payload DownlinkMessage) Token + PublishDownlink(payload types.DownlinkMessage) Token SubscribeDeviceDownlink(appID string, devID string, handler DownlinkHandler) Token SubscribeAppDownlink(appID string, handler DownlinkHandler) Token SubscribeDownlink(handler DownlinkHandler) Token @@ -56,7 +57,7 @@ type Client interface { UnsubscribeDeviceEvents(appID string, devID string, eventType string) Token // Activation pub/sub - PublishActivation(payload Activation) Token + PublishActivation(payload types.Activation) Token SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token SubscribeAppActivations(appID string, handler ActivationHandler) Token SubscribeActivations(handler ActivationHandler) Token @@ -229,7 +230,7 @@ func (c *DefaultClient) Connect() error { <-time.After(ConnectRetryDelay) } if err != nil { - return fmt.Errorf("Could not connect to MQTT Broker (%s).", err) + return fmt.Errorf("Could not connect to MQTT Broker (%s)", err) } return nil } diff --git a/mqtt/client_test.go b/mqtt/client_test.go index f08a5f1c9..977c092c8 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" . "github.com/smartystreets/assertions" @@ -157,7 +158,7 @@ func ExampleNewClient() { var exampleClient Client func ExampleDefaultClient_SubscribeDeviceUplink() { - token := exampleClient.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req UplinkMessage) { + token := exampleClient.SubscribeDeviceUplink("my-app-id", "my-dev-id", func(client Client, appID string, devID string, req types.UplinkMessage) { // Do something with the message }) token.Wait() @@ -167,7 +168,7 @@ func ExampleDefaultClient_SubscribeDeviceUplink() { } func ExampleDefaultClient_PublishDownlink() { - token := exampleClient.PublishDownlink(DownlinkMessage{ + token := exampleClient.PublishDownlink(types.DownlinkMessage{ AppID: "my-app-id", DevID: "my-dev-id", FPort: 1, diff --git a/mqtt/downlink.go b/mqtt/downlink.go index 9d1b7c8a0..d94000784 100644 --- a/mqtt/downlink.go +++ b/mqtt/downlink.go @@ -7,14 +7,16 @@ import ( "encoding/json" "fmt" + "github.com/TheThingsNetwork/ttn/core/types" + MQTT "github.com/eclipse/paho.mqtt.golang" ) // DownlinkHandler is called for downlink messages -type DownlinkHandler func(client Client, appID string, devID string, req DownlinkMessage) +type DownlinkHandler func(client Client, appID string, devID string, req types.DownlinkMessage) // PublishDownlink publishes a downlink message -func (c *DefaultClient) PublishDownlink(dataDown DownlinkMessage) Token { +func (c *DefaultClient) PublishDownlink(dataDown types.DownlinkMessage) Token { topic := DeviceTopic{dataDown.AppID, dataDown.DevID, DeviceDownlink, ""} dataDown.AppID = "" dataDown.DevID = "" @@ -37,7 +39,7 @@ func (c *DefaultClient) SubscribeDeviceDownlink(appID string, devID string, hand } // Unmarshal the payload - dataDown := &DownlinkMessage{} + dataDown := &types.DownlinkMessage{} err = json.Unmarshal(msg.Payload(), dataDown) if err != nil { c.ctx.Warnf("Could not unmarshal downlink (%s).", err.Error()) diff --git a/mqtt/downlink_test.go b/mqtt/downlink_test.go index de5ab3b4f..0b3d02194 100644 --- a/mqtt/downlink_test.go +++ b/mqtt/downlink_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -20,7 +21,7 @@ func TestPublishDownlink(t *testing.T) { c.Connect() defer c.Disconnect() - dataDown := DownlinkMessage{ + dataDown := types.DownlinkMessage{ AppID: "someid", DevID: "someid", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, @@ -38,7 +39,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req DownlinkMessage) { + token := c.SubscribeDeviceDownlink("someid", "someid", func(client Client, appID string, devID string, req types.DownlinkMessage) { }) waitForOK(token, a) @@ -55,7 +56,7 @@ func TestSubscribeAppDownlink(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req DownlinkMessage) { + token := c.SubscribeAppDownlink("someid", func(client Client, appID string, devID string, req types.DownlinkMessage) { }) waitForOK(token, a) @@ -72,7 +73,7 @@ func TestSubscribeDownlink(t *testing.T) { c.Connect() defer c.Disconnect() - token := c.SubscribeDownlink(func(client Client, appID string, devID string, req DownlinkMessage) { + token := c.SubscribeDownlink(func(client Client, appID string, devID string, req types.DownlinkMessage) { }) waitForOK(token, a) @@ -93,7 +94,7 @@ func TestPubSubDownlink(t *testing.T) { wg.Add(1) - subToken := c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req DownlinkMessage) { + subToken := c.SubscribeDeviceDownlink("app3", "dev3", func(client Client, appID string, devID string, req types.DownlinkMessage) { a.So(appID, ShouldResemble, "app3") a.So(devID, ShouldResemble, "dev3") @@ -101,7 +102,7 @@ func TestPubSubDownlink(t *testing.T) { }) waitForOK(subToken, a) - pubToken := c.PublishDownlink(DownlinkMessage{ + pubToken := c.PublishDownlink(types.DownlinkMessage{ AppID: "app3", DevID: "dev3", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, @@ -124,20 +125,20 @@ func TestPubSubAppDownlink(t *testing.T) { wg.Add(2) - subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req DownlinkMessage) { + subToken := c.SubscribeAppDownlink("app4", func(client Client, appID string, devID string, req types.DownlinkMessage) { a.So(appID, ShouldResemble, "app4") a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }) waitForOK(subToken, a) - pubToken := c.PublishDownlink(DownlinkMessage{ + pubToken := c.PublishDownlink(types.DownlinkMessage{ AppID: "app4", DevID: "dev1", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) - pubToken = c.PublishDownlink(DownlinkMessage{ + pubToken = c.PublishDownlink(types.DownlinkMessage{ AppID: "app4", DevID: "dev2", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, diff --git a/mqtt/types.go b/mqtt/types.go deleted file mode 100644 index 7151a8ef4..000000000 --- a/mqtt/types.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package mqtt - -import "github.com/TheThingsNetwork/ttn/core/types" - -// LocationMetadata contains GPS coordinates -type LocationMetadata struct { - Altitude int32 `json:"altitude,omitempty"` - Longitude float32 `json:"longitude,omitempty"` - Latitude float32 `json:"latitude,omitempty"` -} - -// GatewayMetadata contains metadata for each gateway that received a message -type GatewayMetadata struct { - GtwID string `json:"gtw_id,omitempty"` - Timestamp uint32 `json:"timestamp,omitempty"` - Time types.JSONTime `json:"time,omitempty"` - Channel uint32 `json:"channel"` - RSSI float32 `json:"rssi,omitempty"` - SNR float32 `json:"snr,omitempty"` - RFChain uint32 `json:"rf_chain,omitempty"` - LocationMetadata -} - -// Metadata contains metadata of a message -type Metadata struct { - Time types.JSONTime `json:"time,omitempty"` - Frequency float32 `json:"frequency,omitempty"` - Modulation string `json:"modulation,omitempty"` - DataRate string `json:"data_rate,omitempty"` - Bitrate uint32 `json:"bit_rate,omitempty"` - CodingRate string `json:"coding_rate,omitempty"` - Gateways []GatewayMetadata `json:"gateways,omitempty"` - LocationMetadata -} - -// UplinkMessage represents an application-layer uplink message -type UplinkMessage struct { - AppID string `json:"app_id,omitempty"` - DevID string `json:"dev_id,omitempty"` - FPort uint8 `json:"port"` - FCnt uint32 `json:"counter"` - PayloadRaw []byte `json:"payload_raw"` - PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` -} - -// DownlinkMessage represents an application-layer downlink message -type DownlinkMessage struct { - AppID string `json:"app_id,omitempty"` - DevID string `json:"dev_id,omitempty"` - FPort uint8 `json:"port"` - PayloadRaw []byte `json:"payload_raw,omitempty"` - PayloadFields map[string]interface{} `json:"payload_fields,omitempty"` -} - -// Activation messages are used to notify application of a device activation -type Activation struct { - AppID string `json:"app_id,omitempty"` - DevID string `json:"dev_id,omitempty"` - AppEUI types.AppEUI `json:"app_eui,omitempty"` - DevEUI types.DevEUI `json:"dev_eui,omitempty"` - DevAddr types.DevAddr `json:"dev_addr,omitempty"` - Metadata Metadata `json:"metadata,omitempty"` -} diff --git a/mqtt/uplink.go b/mqtt/uplink.go index f061c2054..9ca398134 100644 --- a/mqtt/uplink.go +++ b/mqtt/uplink.go @@ -7,14 +7,15 @@ import ( "encoding/json" "fmt" + "github.com/TheThingsNetwork/ttn/core/types" MQTT "github.com/eclipse/paho.mqtt.golang" ) // UplinkHandler is called for uplink messages -type UplinkHandler func(client Client, appID string, devID string, req UplinkMessage) +type UplinkHandler func(client Client, appID string, devID string, req types.UplinkMessage) // PublishUplink publishes an uplink message to the MQTT broker -func (c *DefaultClient) PublishUplink(dataUp UplinkMessage) Token { +func (c *DefaultClient) PublishUplink(dataUp types.UplinkMessage) Token { topic := DeviceTopic{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} dataUp.AppID = "" dataUp.DevID = "" @@ -75,7 +76,7 @@ func (c *DefaultClient) SubscribeDeviceUplink(appID string, devID string, handle } // Unmarshal the payload - dataUp := &UplinkMessage{} + dataUp := &types.UplinkMessage{} err = json.Unmarshal(msg.Payload(), dataUp) dataUp.AppID = topic.AppID dataUp.DevID = topic.DevID diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go index edc49a04d..fb5928b7a 100644 --- a/mqtt/uplink_test.go +++ b/mqtt/uplink_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" MQTT "github.com/eclipse/paho.mqtt.golang" . "github.com/smartystreets/assertions" @@ -22,7 +23,7 @@ func TestPublishUplink(t *testing.T) { c.Connect() defer c.Disconnect() - dataUp := UplinkMessage{ + dataUp := types.UplinkMessage{ AppID: "someid", DevID: "someid", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, @@ -110,7 +111,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { c.Connect() defer c.Disconnect() - subToken := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeDeviceUplink("someid", "someid", func(client Client, appID string, devID string, req types.UplinkMessage) { }) waitForOK(subToken, a) @@ -125,7 +126,7 @@ func TestSubscribeAppUplink(t *testing.T) { c.Connect() defer c.Disconnect() - subToken := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeAppUplink("someid", func(client Client, appID string, devID string, req types.UplinkMessage) { }) waitForOK(subToken, a) @@ -140,7 +141,7 @@ func TestSubscribeUplink(t *testing.T) { c.Connect() defer c.Disconnect() - subToken := c.SubscribeUplink(func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeUplink(func(client Client, appID string, devID string, req types.UplinkMessage) { }) waitForOK(subToken, a) @@ -157,7 +158,7 @@ func TestPubSubUplink(t *testing.T) { waitChan := make(chan bool, 1) - subToken := c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeDeviceUplink("app1", "dev1", func(client Client, appID string, devID string, req types.UplinkMessage) { a.So(appID, ShouldResemble, "app1") a.So(devID, ShouldResemble, "dev1") @@ -165,7 +166,7 @@ func TestPubSubUplink(t *testing.T) { }) waitForOK(subToken, a) - pubToken := c.PublishUplink(UplinkMessage{ + pubToken := c.PublishUplink(types.UplinkMessage{ PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, AppID: "app1", DevID: "dev1", @@ -192,20 +193,20 @@ func TestPubSubAppUplink(t *testing.T) { wg.Add(2) - subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req UplinkMessage) { + subToken := c.SubscribeAppUplink("app2", func(client Client, appID string, devID string, req types.UplinkMessage) { a.So(appID, ShouldResemble, "app2") a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x02, 0x03, 0x04}) wg.Done() }) waitForOK(subToken, a) - pubToken := c.PublishUplink(UplinkMessage{ + pubToken := c.PublishUplink(types.UplinkMessage{ AppID: "app2", DevID: "dev1", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, }) waitForOK(pubToken, a) - pubToken = c.PublishUplink(UplinkMessage{ + pubToken = c.PublishUplink(types.UplinkMessage{ AppID: "app2", DevID: "dev2", PayloadRaw: []byte{0x01, 0x02, 0x03, 0x04}, From 26cf8e668efcbf7bb01e3e9926426afb4a8725f3 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 11:50:41 +0200 Subject: [PATCH 2003/2266] Use of core/types instead of mqtt --- ttnctl/cmd/downlink.go | 3 +-- ttnctl/cmd/subscribe.go | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/downlink.go b/ttnctl/cmd/downlink.go index 3f71eb4cc..fb408c6ec 100644 --- a/ttnctl/cmd/downlink.go +++ b/ttnctl/cmd/downlink.go @@ -8,7 +8,6 @@ import ( "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/core/types" - "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" ) @@ -58,7 +57,7 @@ $ ttnctl downlink test --json '{"led":"on"}' ctx.WithError(err).Fatal("Failed to read fport flag") } - message := mqtt.DownlinkMessage{ + message := types.DownlinkMessage{ AppID: appID, DevID: devID, FPort: uint8(fPort), diff --git a/ttnctl/cmd/subscribe.go b/ttnctl/cmd/subscribe.go index 0e7f60f5e..005e84572 100644 --- a/ttnctl/cmd/subscribe.go +++ b/ttnctl/cmd/subscribe.go @@ -9,6 +9,7 @@ import ( "os/signal" "syscall" + "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/spf13/cobra" @@ -24,7 +25,7 @@ var subscribeCmd = &cobra.Command{ client := util.GetMQTT(ctx) defer client.Disconnect() - token := client.SubscribeActivations(func(client mqtt.Client, appID string, devID string, req mqtt.Activation) { + token := client.SubscribeActivations(func(client mqtt.Client, appID string, devID string, req types.Activation) { ctx.Info("Activation") printKV("AppID", appID) printKV("DevID", devID) @@ -39,7 +40,7 @@ var subscribeCmd = &cobra.Command{ } ctx.Info("Subscribed to activations") - token = client.SubscribeUplink(func(client mqtt.Client, appID string, devID string, req mqtt.UplinkMessage) { + token = client.SubscribeUplink(func(client mqtt.Client, appID string, devID string, req types.UplinkMessage) { ctx.Info("Uplink Message") printKV("AppID", appID) printKV("DevID", devID) From 73fec5ade417808dc7cc59dc894033eeb1f1ab98 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 11:50:56 +0200 Subject: [PATCH 2004/2266] Disable AMQP for development --- .env/handler/dev.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index b85150764..d4c67753a 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -7,10 +7,10 @@ auth-servers: ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/handler/" -handler: - amqp-host: localhost:5672 - amqp-username: guest - amqp-password: guest - amqp-exchange: topic +# handler: +# amqp-host: localhost:5672 +# amqp-username: guest +# amqp-password: guest +# amqp-exchange: topic auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg From ce966761044d79dd0c88a3eb7583deca51f687a2 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 11:51:35 +0200 Subject: [PATCH 2005/2266] AMQP publisher with own channel --- amqp/client.go | 98 ++++++++++++++++++++++++++++++++ amqp/client_test.go | 78 ++++++++++++++++++++++++++ amqp/publisher.go | 121 +++++++++++++--------------------------- amqp/publisher_test.go | 74 ++++++++---------------- amqp/uplink_test.go | 12 +++- core/handler/amqp.go | 28 +++++----- core/handler/handler.go | 16 +++--- 7 files changed, 272 insertions(+), 155 deletions(-) create mode 100644 amqp/client.go create mode 100644 amqp/client_test.go diff --git a/amqp/client.go b/amqp/client.go new file mode 100644 index 000000000..adfe56187 --- /dev/null +++ b/amqp/client.go @@ -0,0 +1,98 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "fmt" + "time" + + AMQP "github.com/streadway/amqp" +) + +// Client connects to an AMQP server +type Client interface { + Connect() error + Disconnect() + IsConnected() bool + NewPublisher(exchange string) Publisher +} + +// DefaultClient is the default AMQP client for The Things Network +type DefaultClient struct { + url string + ctx Logger + conn *AMQP.Connection +} + +var ( + // ConnectRetries says how many times the client should retry a failed connection + ConnectRetries = 10 + // ConnectRetryDelay says how long the client should wait between retries + ConnectRetryDelay = time.Second +) + +// NewClient creates a new DefaultClient +func NewClient(ctx Logger, username, password, host string) Client { + if ctx == nil { + ctx = &noopLogger{} + } + credentials := "guest" + if username != "" { + if password != "" { + credentials = fmt.Sprintf("%s:%s", username, password) + } else { + credentials = username + } + } + return &DefaultClient{ + ctx: ctx, + url: fmt.Sprintf("amqp://%s@%s", credentials, host), + } +} + +// Connect to the AMQP server. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +func (c *DefaultClient) Connect() error { + var err error + var conn *AMQP.Connection + for retries := 0; retries < ConnectRetries; retries++ { + conn, err = AMQP.Dial(c.url) + if err == nil { + break + } + c.ctx.Warnf("Could not connect to AMQP server (%s). Retrying...", err.Error()) + <-time.After(ConnectRetryDelay) + } + if err != nil { + return fmt.Errorf("Could not connect to AMQP server (%s).", err) + } + + c.conn = conn + return nil +} + +// Disconnect from the AMQP server +func (c *DefaultClient) Disconnect() { + if !c.IsConnected() { + return + } + c.ctx.Debug("Disconnecting from AMQP") + if err := c.conn.Close(); err != nil { + c.ctx.Warnf("Could not close AMQP connection (%s)", err) + } + c.conn = nil +} + +// IsConnected returns true if there is a connection to the AMQP server. +func (c *DefaultClient) IsConnected() bool { + return c.conn != nil +} + +// NewPublisher returns a new publisher +func (c *DefaultClient) NewPublisher(exchange string) Publisher { + return &DefaultPublisher{ + ctx: c.ctx, + conn: c.conn, + exchange: exchange, + } +} diff --git a/amqp/client_test.go b/amqp/client_test.go new file mode 100644 index 000000000..e86be0c26 --- /dev/null +++ b/amqp/client_test.go @@ -0,0 +1,78 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "os" + "testing" + "time" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +var host string + +func init() { + host = os.Getenv("AMQP_ADDR") + if host == "" { + host = "localhost:5672" + } +} + +func TestNewClient(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestNewClient"), "guest", "guest", host) + a.So(c, ShouldNotBeNil) +} + +func TestConnect(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestConnect"), "guest", "guest", host) + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) + + // Connecting while already connected should not change anything + err = c.Connect() + defer c.Disconnect() + a.So(err, ShouldBeNil) +} + +func TestConnectInvalidAddress(t *testing.T) { + a := New(t) + ConnectRetries = 2 + ConnectRetryDelay = 50 * time.Millisecond + c := NewClient(GetLogger(t, "TestConnectInvalidAddress"), "guest", "guest", "localhost:56720") + err := c.Connect() + defer c.Disconnect() + a.So(err, ShouldNotBeNil) +} + +func TestIsConnected(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestIsConnected"), "guest", "guest", host) + + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + + a.So(c.IsConnected(), ShouldBeTrue) +} + +func TestDisconnect(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestDisconnect"), "guest", "guest", host) + + // Disconnecting when not connected should not change anything + c.Disconnect() + a.So(c.IsConnected(), ShouldBeFalse) + + c.Connect() + defer c.Disconnect() + c.Disconnect() + + a.So(c.IsConnected(), ShouldBeFalse) +} diff --git a/amqp/publisher.go b/amqp/publisher.go index 699fa760d..a82d7a28a 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -5,6 +5,7 @@ package amqp import ( "fmt" + "io" "time" "github.com/TheThingsNetwork/ttn/core/types" @@ -12,109 +13,67 @@ import ( AMQP "github.com/streadway/amqp" ) -// Publisher connects to the AMQP server and can publish on uplink and activations from devices +// Publisher holds an AMQP channel and can publish on uplink from devices type Publisher interface { - Connect() error - Disconnect() - IsConnected() bool - + Open() error + io.Closer PublishUplink(payload types.UplinkMessage) error } -// DefaultPublisher is the default AMQP client for The Things Network +// DefaultPublisher is the default AMQP publisher type DefaultPublisher struct { - url string ctx Logger conn *AMQP.Connection channel *AMQP.Channel exchange string } -var ( - // ConnectRetries says how many times the client should retry a failed connection - ConnectRetries = 10 - // ConnectRetryDelay says how long the client should wait between retries - ConnectRetryDelay = time.Second -) - -// NewPublisher creates a new DefaultPublisher -func NewPublisher(ctx Logger, username, password, host, exchange string) Publisher { - if ctx == nil { - ctx = &noopLogger{} - } - credentials := "guest" - if username != "" { - if password != "" { - credentials = fmt.Sprintf("%s:%s", username, password) - } else { - credentials = username - } - } - return &DefaultPublisher{ - ctx: ctx, - url: fmt.Sprintf("amqp://%s@%s", credentials, host), - exchange: exchange, - } -} - -// Connect to the AMQP server. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries -func (c *DefaultPublisher) Connect() error { - if c.IsConnected() { - return nil - } - var err error - var conn *AMQP.Connection - for retries := 0; retries < ConnectRetries; retries++ { - conn, err = AMQP.Dial(c.url) - if err == nil { - break - } - c.ctx.Warnf("Could not connect to AMQP server (%s). Retrying...", err.Error()) - <-time.After(ConnectRetryDelay) - } - if err != nil { - return fmt.Errorf("Could not connect to AMQP server (%s).", err) - } - channel, err := conn.Channel() +// Open opens an AMQP channel for publishing +func (p *DefaultPublisher) Open() error { + channel, err := p.conn.Channel() if err != nil { - conn.Close() - return fmt.Errorf("Could not get AMQP channel (%s).", err) + return fmt.Errorf("Could not open AMQP channel (%s).", err) } - if err = channel.ExchangeDeclare(c.exchange, "topic", true, false, false, false, nil); err != nil { + + if err = channel.ExchangeDeclare(p.exchange, "topic", true, false, false, false, nil); err != nil { channel.Close() - conn.Close() - return fmt.Errorf("Could not AMQP exchange (%s).", err) + return fmt.Errorf("Could not declare AMQP exchange (%s).", err) } - c.conn = conn - c.channel = channel + + p.channel = channel return nil } -func (c *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) error { - return c.channel.Publish(c.exchange, key, false, false, AMQP.Publishing{ - ContentType: "application/json", - Timestamp: timestamp, - Body: msg, - }) +// Close closes the AMQP channel +func (p *DefaultPublisher) Close() error { + if p.channel == nil { + return nil + } + return p.channel.Close() } -// Disconnect from the AMQP server -func (c *DefaultPublisher) Disconnect() { - if !c.IsConnected() { - return - } - c.ctx.Debug("Disconnecting from AMQP") - if err := c.channel.Close(); err != nil { - c.ctx.Warnf("Could not close AMQP channel (%s)", err) +func (p *DefaultPublisher) do(action func() error) error { + err := action() + if err == nil { + return nil } - c.channel = nil - if err := c.conn.Close(); err != nil { - c.ctx.Warnf("Could not close AMQP connection (%s)", err) + if err == AMQP.ErrClosed { + err = p.Open() + if err != nil { + return err + } + err = action() } - c.conn = nil + return err } -// IsConnected returns true if there is a connection to the AMQP server -func (c *DefaultPublisher) IsConnected() bool { - return c.conn != nil +func (p *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) error { + return p.do(func() error { + return p.channel.Publish(p.exchange, key, false, false, AMQP.Publishing{ + ContentType: "application/json", + DeliveryMode: AMQP.Persistent, + Timestamp: timestamp, + Body: msg, + }) + }) } diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go index e85c35200..481fdf4c4 100644 --- a/amqp/publisher_test.go +++ b/amqp/publisher_test.go @@ -4,75 +4,47 @@ package amqp import ( - "os" "testing" - "time" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) -var host string - -func init() { - host = os.Getenv("AMQP_ADDR") - if host == "" { - host = "localhost:5672" - } -} - -func TestNewPublisher(t *testing.T) { - a := New(t) - c := NewPublisher(GetLogger(t, "TestNewPublisher"), "guest", "guest", host, "test") - a.So(c, ShouldNotBeNil) -} - -func TestConnect(t *testing.T) { +func TestOpenPublisher(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestConnect"), "guest", "guest", host, "test") + c := NewClient(GetLogger(t, "TestOpenPublisher"), "guest", "guest", host) err := c.Connect() - defer c.Disconnect() a.So(err, ShouldBeNil) - - // Connecting while already connected should not change anything - err = c.Connect() defer c.Disconnect() + + p := c.NewPublisher("test") + err = p.Open() a.So(err, ShouldBeNil) + defer p.Close() } -func TestConnectInvalidAddress(t *testing.T) { +func TestReopenPublisher(t *testing.T) { a := New(t) - ConnectRetries = 2 - ConnectRetryDelay = 50 * time.Millisecond - c := NewPublisher(GetLogger(t, "TestConnectInvalidAddress"), "guest", "guest", "localhost:56720", "test") + c := NewClient(GetLogger(t, "TestReopenPublisher"), "guest", "guest", host) err := c.Connect() + a.So(err, ShouldBeNil) defer c.Disconnect() - a.So(err, ShouldNotBeNil) -} - -func TestIsConnected(t *testing.T) { - a := New(t) - c := NewPublisher(GetLogger(t, "TestIsConnected"), "guest", "guest", host, "test") - - a.So(c.IsConnected(), ShouldBeFalse) - - c.Connect() - defer c.Disconnect() - - a.So(c.IsConnected(), ShouldBeTrue) -} -func TestDisconnect(t *testing.T) { - a := New(t) - c := NewPublisher(GetLogger(t, "TestDisconnect"), "guest", "guest", host, "test") + p := c.NewPublisher("test") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() - // Disconnecting when not connected should not change anything - c.Disconnect() - a.So(c.IsConnected(), ShouldBeFalse) + // First attempt should be OK + err = p.PublishUplink(types.UplinkMessage{}) + a.So(err, ShouldBeNil) - c.Connect() - defer c.Disconnect() - c.Disconnect() + // Closing the underlying channel + err = p.(*DefaultPublisher).channel.Close() + a.So(err, ShouldBeNil) - a.So(c.IsConnected(), ShouldBeFalse) + // Second attempt should reconnect and be OK as well + err = p.PublishUplink(types.UplinkMessage{}) + a.So(err, ShouldBeNil) } diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index c09ea89a5..63d29d2aa 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package amqp import ( @@ -10,12 +13,17 @@ import ( func TestPublishUplink(t *testing.T) { a := New(t) - c := NewPublisher(GetLogger(t, "TestPublishUplink"), "guest", "guest", host, "test") + c := NewClient(GetLogger(t, "TestPublishUplink"), "guest", "guest", host) err := c.Connect() + a.So(err, ShouldBeNil) defer c.Disconnect() + + p := c.NewPublisher("test") + err = p.Open() a.So(err, ShouldBeNil) + defer p.Close() - err = c.PublishUplink(types.UplinkMessage{ + err = p.PublishUplink(types.UplinkMessage{ AppID: "app", DevID: "test", PayloadRaw: []byte{0x01, 0x08}, diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 352669640..7bca1d7a0 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -10,34 +10,36 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -// AMQPBufferSize indicates the size for uplink channel buffers -var AMQPBufferSize = 10 - func (h *handler) HandleAMQP(username, password, host, exchange string) error { - h.amqpPublisher = amqp.NewPublisher(h.Ctx, username, password, host, exchange) + h.amqpClient = amqp.NewClient(h.Ctx, username, password, host) - err := h.amqpPublisher.Connect() + err := h.amqpClient.Connect() if err != nil { return err } - h.amqpUp = make(chan *types.UplinkMessage, AMQPBufferSize) + h.amqpUp = make(chan *types.UplinkMessage) ctx := h.Ctx.WithField("Protocol", "AMQP") go func() { + publisher := h.amqpClient.NewPublisher(exchange) + err := publisher.Open() + if err != nil { + ctx.WithError(err).Error("Could not open publisher channel") + return + } + defer publisher.Close() + for up := range h.amqpUp { ctx.WithFields(log.Fields{ "DevID": up.DevID, "AppID": up.AppID, }).Debug("Publish Uplink") - msg := *up - go func() { - err := h.amqpPublisher.PublishUplink(msg) - if err != nil { - ctx.WithError(err).Warn("Could not publish Uplink") - } - }() + err := publisher.PublishUplink(*up) + if err != nil { + ctx.WithError(err).Warn("Could not publish Uplink") + } } }() diff --git a/core/handler/handler.go b/core/handler/handler.go index 61e24124d..59369cd0a 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -67,13 +67,13 @@ type handler struct { mqttActivation chan *types.Activation mqttEvent chan *mqttEvent - amqpPublisher amqp.Publisher - amqpUsername string - amqpPassword string - amqpHost string - amqpExchange string - amqpEnabled bool - amqpUp chan *types.UplinkMessage + amqpClient amqp.Client + amqpUsername string + amqpPassword string + amqpHost string + amqpExchange string + amqpEnabled bool + amqpUp chan *types.UplinkMessage } func (h *handler) WithAMQP(username, password, host, exchange string) Handler { @@ -126,7 +126,7 @@ func (h *handler) Init(c *core.Component) error { func (h *handler) Shutdown() { h.mqttClient.Disconnect() if h.amqpEnabled { - h.amqpPublisher.Disconnect() + h.amqpClient.Disconnect() } } From 0f4e18690385ab2637b7295ce4f5899bf0898d55 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 11:58:21 +0200 Subject: [PATCH 2006/2266] Default AMQP guest password --- amqp/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/amqp/client.go b/amqp/client.go index adfe56187..407826b77 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -37,7 +37,7 @@ func NewClient(ctx Logger, username, password, host string) Client { if ctx == nil { ctx = &noopLogger{} } - credentials := "guest" + credentials := "guest:guest" if username != "" { if password != "" { credentials = fmt.Sprintf("%s:%s", username, password) From 101af9986a9330df0c77ac393a7e35d09071b19c Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 12:00:02 +0200 Subject: [PATCH 2007/2266] Added copyright --- amqp/logging.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/amqp/logging.go b/amqp/logging.go index 921cb21cb..3760bcd73 100644 --- a/amqp/logging.go +++ b/amqp/logging.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package amqp // Logger used in amqp package From 366d82bc87743911dad5251b070decd0b36f0347 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 12:00:09 +0200 Subject: [PATCH 2008/2266] Code cleanup --- amqp/publisher.go | 1 - 1 file changed, 1 deletion(-) diff --git a/amqp/publisher.go b/amqp/publisher.go index a82d7a28a..5ae5acb52 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -9,7 +9,6 @@ import ( "time" "github.com/TheThingsNetwork/ttn/core/types" - AMQP "github.com/streadway/amqp" ) From 99cb8c21748209e440f3e8291a132f581e12a604 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 20 Oct 2016 12:55:16 +0200 Subject: [PATCH 2009/2266] Add amqp to vendor file --- vendor/vendor.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/vendor/vendor.json b/vendor/vendor.json index 70785287f..9073194bf 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -524,6 +524,12 @@ "revision": "50515b700e02658272117a72bd641b6b7f1222e5", "revisionTime": "2016-10-14T09:24:45Z" }, + { + "checksumSHA1": "rKV8YLkXpeNG1Oix8hlYqVsEFb4=", + "path": "github.com/streadway/amqp", + "revision": "2e25825abdbd7752ff08b270d313b93519a0a232", + "revisionTime": "2016-03-11T21:55:03Z" + }, { "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", "path": "github.com/tj/go-elastic", From 76de07415f6b203c83337f44db862126558db00d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 20 Oct 2016 12:58:39 +0200 Subject: [PATCH 2010/2266] AMQP config for Travis and Gitlab CI --- .gitlab-ci.yml | 2 ++ .travis.yml | 1 + 2 files changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ff339549e..953f91447 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,10 +31,12 @@ tests: image: golang:latest services: - ansi/mosquitto + - rabbitmq - redis variables: REDIS_HOST: redis MQTT_HOST: ansi-mosquitto + AMQP_ADDR: rabbitmq:5672 script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps diff --git a/.travis.yml b/.travis.yml index 6e89633bb..ad601c5f0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,7 @@ install: before_script: - docker run -d -p 127.0.0.1:6379:6379 redis - docker run -d -p 127.0.0.1:1883:1883 ansi/mosquitto + - docker run -d -p 127.0.0.1:5672:5672 rabbitmq script: - make test From 2241780064cd53760710d490de9977cbb7632de4 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 13:24:27 +0200 Subject: [PATCH 2011/2266] Code cleanup --- amqp/routing_keys.go | 2 +- amqp/uplink.go | 2 +- mqtt/downlink.go | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/amqp/routing_keys.go b/amqp/routing_keys.go index edfd0dbc5..2712146c9 100644 --- a/amqp/routing_keys.go +++ b/amqp/routing_keys.go @@ -77,7 +77,7 @@ const ( AppEvents ApplicationKeyType = "events" ) -// ApplicationKey represents an MQTT topic for applications +// ApplicationKey represents an AMQP topic for applications type ApplicationKey struct { AppID string Type ApplicationKeyType diff --git a/amqp/uplink.go b/amqp/uplink.go index e081ed7d4..c5e183e01 100644 --- a/amqp/uplink.go +++ b/amqp/uplink.go @@ -11,7 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -// PublishUplink publishes an uplink message to the MQTT broker +// PublishUplink publishes an uplink message to the AMQP broker func (c *DefaultPublisher) PublishUplink(dataUp types.UplinkMessage) error { key := DeviceKey{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} msg, err := json.Marshal(dataUp) diff --git a/mqtt/downlink.go b/mqtt/downlink.go index d94000784..9c7587dac 100644 --- a/mqtt/downlink.go +++ b/mqtt/downlink.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/TheThingsNetwork/ttn/core/types" - MQTT "github.com/eclipse/paho.mqtt.golang" ) From 59fa60a5415b5bcf2f7717c64411d9a09bf5a88f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 20 Oct 2016 14:23:44 +0200 Subject: [PATCH 2012/2266] Test application MQTT topics --- mqtt/topics.go | 19 ++++++++-------- mqtt/topics_test.go | 53 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/mqtt/topics.go b/mqtt/topics.go index 963cd4c0d..32b2b2e48 100644 --- a/mqtt/topics.go +++ b/mqtt/topics.go @@ -9,7 +9,8 @@ import ( "strings" ) -const wildcard = "+" +const simpleWildcard = "+" +const wildcard = "#" // DeviceTopicType represents the type of a device topic type DeviceTopicType string @@ -37,11 +38,11 @@ func ParseDeviceTopic(topic string) (*DeviceTopic, error) { return nil, fmt.Errorf("Invalid topic format") } var appID string - if matches[1] != wildcard { + if matches[1] != simpleWildcard { appID = matches[1] } var devID string - if matches[3] != wildcard { + if matches[3] != simpleWildcard { devID = matches[3] } topicType := DeviceTopicType(matches[4]) @@ -54,16 +55,16 @@ func ParseDeviceTopic(topic string) (*DeviceTopic, error) { // String implements the Stringer interface func (t DeviceTopic) String() string { - appID := wildcard + appID := simpleWildcard if t.AppID != "" { appID = t.AppID } - devID := wildcard + devID := simpleWildcard if t.DevID != "" { devID = t.DevID } if t.Type == DeviceEvents && t.Field == "" { - t.Field = wildcard + t.Field = simpleWildcard } topic := fmt.Sprintf("%s/%s/%s/%s", appID, "devices", devID, t.Type) if (t.Type == DeviceUplink || t.Type == DeviceEvents) && t.Field != "" { @@ -89,13 +90,13 @@ type ApplicationTopic struct { // ParseApplicationTopic parses an MQTT device topic string to an ApplicationTopic struct func ParseApplicationTopic(topic string) (*ApplicationTopic, error) { - pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(events)([0-9a-z/]+)?$") + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\+)/(events)([0-9a-z/-]+|/#)?$") matches := pattern.FindStringSubmatch(topic) if len(matches) < 2 { return nil, fmt.Errorf("Invalid topic format") } var appID string - if matches[1] != wildcard { + if matches[1] != simpleWildcard { appID = matches[1] } topicType := ApplicationTopicType(matches[2]) @@ -108,7 +109,7 @@ func ParseApplicationTopic(topic string) (*ApplicationTopic, error) { // String implements the Stringer interface func (t ApplicationTopic) String() string { - appID := wildcard + appID := simpleWildcard if t.AppID != "" { appID = t.AppID } diff --git a/mqtt/topics_test.go b/mqtt/topics_test.go index 936a3dd04..a3c0384be 100644 --- a/mqtt/topics_test.go +++ b/mqtt/topics_test.go @@ -42,7 +42,7 @@ func TestParseDeviceTopicInvalid(t *testing.T) { a.So(err, ShouldNotBeNil) } -func TestTopicString(t *testing.T) { +func TestDeviceTopicString(t *testing.T) { a := New(t) topic := &DeviceTopic{ @@ -58,7 +58,7 @@ func TestTopicString(t *testing.T) { a.So(got, ShouldResemble, expected) } -func TestTopicParseAndString(t *testing.T) { +func TestDeviceTopicParseAndString(t *testing.T) { a := New(t) expectedList := []string{ @@ -90,3 +90,52 @@ func TestTopicParseAndString(t *testing.T) { } } + +func TestParseAppTopicInvalid(t *testing.T) { + a := New(t) + + _, err := ParseApplicationTopic("appid:Invalid/events") + a.So(err, ShouldNotBeNil) + + _, err = ParseApplicationTopic("appid/randomstuff") + a.So(err, ShouldNotBeNil) +} + +func TestAppTopicString(t *testing.T) { + a := New(t) + + topic := &ApplicationTopic{ + AppID: "appid-1", + Type: AppEvents, + } + + a.So(topic.String(), ShouldResemble, "appid-1/events/#") + + topic = &ApplicationTopic{ + AppID: "appid-1", + Type: AppEvents, + Field: "err", + } + + a.So(topic.String(), ShouldResemble, "appid-1/events/err") +} + +func TestAppTopicParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + "+/events/#", + "appid/events/#", + "+/events/some-event", + "appid/events/some-event", + "+/events/some/event", + "appid/events/some/event", + } + + for _, expected := range expectedList { + topic, err := ParseApplicationTopic(expected) + a.So(err, ShouldBeNil) + a.So(topic.String(), ShouldEqual, expected) + } + +} From 3a7221b59be80ca20ccae34291d4769cddf6f0e0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 20 Oct 2016 14:33:54 +0200 Subject: [PATCH 2013/2266] Test application AMQP routing keys --- amqp/routing_keys.go | 19 ++++++----- amqp/routing_keys_test.go | 69 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 77 insertions(+), 11 deletions(-) diff --git a/amqp/routing_keys.go b/amqp/routing_keys.go index 2712146c9..8888c5881 100644 --- a/amqp/routing_keys.go +++ b/amqp/routing_keys.go @@ -9,7 +9,8 @@ import ( "strings" ) -const wildcard = "*" +const simpleWildcard = "*" +const wildard = "#" // DeviceKeyType represents the type of a device topic type DeviceKeyType string @@ -37,11 +38,11 @@ func ParseDeviceKey(key string) (*DeviceKey, error) { return nil, fmt.Errorf("Invalid key format") } var appID string - if matches[1] != wildcard { + if matches[1] != simpleWildcard { appID = matches[1] } var devID string - if matches[3] != wildcard { + if matches[3] != simpleWildcard { devID = matches[3] } keyType := DeviceKeyType(matches[4]) @@ -54,11 +55,11 @@ func ParseDeviceKey(key string) (*DeviceKey, error) { // String implements the Stringer interface func (t DeviceKey) String() string { - appID := wildcard + appID := simpleWildcard if t.AppID != "" { appID = t.AppID } - devID := wildcard + devID := simpleWildcard if t.DevID != "" { devID = t.DevID } @@ -86,13 +87,13 @@ type ApplicationKey struct { // ParseApplicationKey parses an AMQP application routing key string to an ApplicationKey struct func ParseApplicationKey(key string) (*ApplicationKey, error) { - pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(events)([0-9a-z\\.]+)?$") + pattern := regexp.MustCompile("^([0-9a-z](?:[_-]?[0-9a-z]){1,35}|\\*)\\.(events)([0-9a-z\\.-]+|\\.#)?$") matches := pattern.FindStringSubmatch(key) if len(matches) < 2 { return nil, fmt.Errorf("Invalid key format") } var appID string - if matches[1] != wildcard { + if matches[1] != simpleWildcard { appID = matches[1] } keyType := ApplicationKeyType(matches[2]) @@ -105,12 +106,12 @@ func ParseApplicationKey(key string) (*ApplicationKey, error) { // String implements the Stringer interface func (t ApplicationKey) String() string { - appID := wildcard + appID := simpleWildcard if t.AppID != "" { appID = t.AppID } if t.Type == AppEvents && t.Field == "" { - t.Field = wildcard + t.Field = wildard } key := fmt.Sprintf("%s.%s", appID, t.Type) if t.Type == AppEvents && t.Field != "" { diff --git a/amqp/routing_keys_test.go b/amqp/routing_keys_test.go index 4ce1b2f6e..901245f60 100644 --- a/amqp/routing_keys_test.go +++ b/amqp/routing_keys_test.go @@ -42,7 +42,7 @@ func TestParseDeviceKeyInvalid(t *testing.T) { a.So(err, ShouldNotBeNil) } -func TestKeyString(t *testing.T) { +func TestDeviceKeyString(t *testing.T) { a := New(t) key := &DeviceKey{ @@ -58,7 +58,7 @@ func TestKeyString(t *testing.T) { a.So(got, ShouldResemble, expected) } -func TestKeyParseAndString(t *testing.T) { +func TestDeviceKeyParseAndString(t *testing.T) { a := New(t) expectedList := []string{ @@ -86,3 +86,68 @@ func TestKeyParseAndString(t *testing.T) { a.So(key.String(), ShouldEqual, expected) } } + +func TestParseAppKey(t *testing.T) { + a := New(t) + + key := "appid-1.devices.devid-1.up" + + expected := &DeviceKey{ + AppID: "appid-1", + DevID: "devid-1", + Type: DeviceUplink, + } + + got, err := ParseDeviceKey(key) + + a.So(err, ShouldBeNil) + a.So(got, ShouldResemble, expected) +} + +func TestParseAppKeyInvalid(t *testing.T) { + a := New(t) + + _, err := ParseApplicationKey("appid:Invalid.events") + a.So(err, ShouldNotBeNil) + + _, err = ParseApplicationKey("appid.randomstuff") + a.So(err, ShouldNotBeNil) +} + +func TestAppKeyString(t *testing.T) { + a := New(t) + + key := &ApplicationKey{ + AppID: "appid-1", + Type: AppEvents, + } + + a.So(key.String(), ShouldResemble, "appid-1.events.#") + + key = &ApplicationKey{ + AppID: "appid-1", + Type: AppEvents, + Field: "err", + } + + a.So(key.String(), ShouldResemble, "appid-1.events.err") +} + +func TestAppKeyParseAndString(t *testing.T) { + a := New(t) + + expectedList := []string{ + "*.events.#", + "appid.events.#", + "*.events.some-event", + "appid.events.some-event", + "*.events.some.event", + "appid.events.some.event", + } + + for _, expected := range expectedList { + key, err := ParseApplicationKey(expected) + a.So(err, ShouldBeNil) + a.So(key.String(), ShouldEqual, expected) + } +} From 3856b7973af37e0c3e96d52973176c4fd76432d9 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 20 Oct 2016 17:07:52 +0200 Subject: [PATCH 2014/2266] Add util function that prints config --- ttnctl/util/print_config.go | 44 +++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 ttnctl/util/print_config.go diff --git a/ttnctl/util/print_config.go b/ttnctl/util/print_config.go new file mode 100644 index 000000000..03ba62ab9 --- /dev/null +++ b/ttnctl/util/print_config.go @@ -0,0 +1,44 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package util + +import ( + "fmt" + + "github.com/apex/log" + "github.com/spf13/viper" +) + +func PrintConfig(ctx log.Interface, debug bool) { + prt := ctx.Infof + if debug { + prt = ctx.Debugf + } + + prt("Using config:") + fmt.Println() + printKV("config file", viper.ConfigFileUsed()) + printKV("data dir", viper.GetString("data")) + fmt.Println() + + for key, val := range viper.AllSettings() { + switch key { + case "builddate": + fallthrough + case "gitcommit": + fallthrough + case "gitbranch": + fallthrough + case "version": + continue + default: + printKV(key, val) + } + } + fmt.Println() +} + +func printKV(key, val interface{}) { + fmt.Printf("%20s: %s\n", key, val) +} From 770ade5e28bddd25fe6b487e35f1b35b442ad919 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 20 Oct 2016 17:08:06 +0200 Subject: [PATCH 2015/2266] Print config when debug is enabled --- ttnctl/cmd/root.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 51a979bd0..525887778 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -41,7 +41,9 @@ var RootCmd = &cobra.Command{ Handler: cliHandler.New(os.Stdout), } - ctx.WithField("config file", viper.ConfigFileUsed()).WithField("data dir", dataDir).Debug("Using config") + if debug { + util.PrintConfig(ctx, true) + } api.DialOptions = append(api.DialOptions, grpc.WithBlock()) api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) From 7285a83872da2f3dfc127745a8214ae3cddc61ba Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Thu, 20 Oct 2016 17:08:17 +0200 Subject: [PATCH 2016/2266] Add config command --- ttnctl/cmd/config.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 ttnctl/cmd/config.go diff --git a/ttnctl/cmd/config.go b/ttnctl/cmd/config.go new file mode 100644 index 000000000..1aece71d3 --- /dev/null +++ b/ttnctl/cmd/config.go @@ -0,0 +1,22 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/spf13/cobra" +) + +var configCmd = &cobra.Command{ + Use: "config", + Short: "Print the used configuration", + Long: `ttnctl config prints the configuration that is used`, + Run: func(cmd *cobra.Command, args []string) { + util.PrintConfig(ctx, false) + }, +} + +func init() { + RootCmd.AddCommand(configCmd) +} From 8bd2ffd2057213f9b621fa6abf231cb5af4b232a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 21 Oct 2016 09:21:15 +0200 Subject: [PATCH 2017/2266] Remove github.com/TheThingsNetwork/ prefix from proto imports --- Makefile | 7 +- api/api.pb.go | 69 ++++----- api/api.proto | 2 + api/broker/broker.pb.go | 146 +++++++++---------- api/broker/broker.proto | 8 +- api/discovery/discovery.pb.go | 74 +++++----- api/discovery/discovery.proto | 2 + api/gateway/gateway.pb.go | 76 +++++----- api/gateway/gateway.proto | 2 + api/handler/handler.pb.go | 122 ++++++++-------- api/handler/handler.proto | 10 +- api/monitor/monitor.pb.go | 41 +++--- api/monitor/monitor.proto | 10 +- api/networkserver/networkserver.pb.go | 78 +++++----- api/networkserver/networkserver.proto | 10 +- api/protocol/lorawan/device.pb.go | 110 ++++++++------ api/protocol/lorawan/device.proto | 2 + api/protocol/lorawan/device_address.pb.go | 56 ++++--- api/protocol/lorawan/device_address.proto | 2 + api/protocol/lorawan/lorawan.pb.go | 170 ++++++++++++++-------- api/protocol/lorawan/lorawan.proto | 2 + api/protocol/protocol.pb.go | 22 +-- api/protocol/protocol.proto | 4 +- api/router/router.pb.go | 105 ++++++------- api/router/router.proto | 8 +- 25 files changed, 618 insertions(+), 520 deletions(-) diff --git a/Makefile b/Makefile index c4215b059..4876b4ddd 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,10 @@ dev-deps: deps PROTO_FILES = $(shell find api -name "*.proto" -and -not -name ".git") COMPILED_PROTO_FILES = $(patsubst api%.proto, api%.pb.go, $(PROTO_FILES)) -PROTOC = protoc --gofast_out=plugins=grpc:$(GOPATH)/src/ --proto_path=$(GOPATH)/src/ $(GOPATH)/src/github.com/TheThingsNetwork/ttn +PROTOC = protoc -I/usr/local/include -I$(GOPATH)/src -I$(GOPATH)/src/github.com/TheThingsNetwork \ +-I$(GOPATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ +--gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GOPATH)/src \ +--grpc-gateway_out=:. $(GOPATH)/src/github.com/TheThingsNetwork/ttn/ protos-clean: rm -f $(COMPILED_PROTO_FILES) @@ -38,7 +41,7 @@ protos-clean: protos: $(COMPILED_PROTO_FILES) api/%.pb.go: api/%.proto - $(PROTOC)/"$<" + $(PROTOC)$< # Mocks diff --git a/api/api.pb.go b/api/api.pb.go index e8d7ed582..7f952e89a 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -1879,38 +1879,39 @@ var ( func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 526 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0xcf, 0x6e, 0xd3, 0x40, - 0x10, 0xc6, 0x71, 0xe2, 0x84, 0x66, 0xcc, 0x3f, 0xad, 0x50, 0x65, 0xa2, 0x2a, 0x54, 0x39, 0x20, - 0x24, 0x44, 0x12, 0x02, 0x56, 0xe4, 0x2b, 0x91, 0x38, 0xf0, 0xb7, 0x72, 0xdb, 0x73, 0xb5, 0x71, - 0x96, 0xc4, 0xc2, 0xf6, 0x5a, 0xde, 0x35, 0x55, 0xdf, 0x83, 0x03, 0x8f, 0xd4, 0x0b, 0x12, 0x8f, - 0x80, 0xc2, 0x8b, 0xa0, 0x1d, 0xaf, 0x8d, 0xbd, 0xe9, 0xb1, 0x87, 0x48, 0xf3, 0xcd, 0xfe, 0xf6, - 0xf3, 0xcc, 0x67, 0x39, 0xf0, 0x72, 0x13, 0xc9, 0x6d, 0xb1, 0x9a, 0x84, 0x3c, 0x99, 0x9e, 0x6d, - 0xd9, 0xd9, 0x36, 0x4a, 0x37, 0xe2, 0x33, 0x93, 0x97, 0x3c, 0xff, 0x36, 0x95, 0x32, 0x9d, 0xd2, - 0x2c, 0x52, 0xbf, 0x49, 0x96, 0x73, 0xc9, 0x49, 0x97, 0x66, 0xd1, 0xf8, 0x57, 0x07, 0x9c, 0x13, - 0x96, 0x87, 0x2c, 0x95, 0x51, 0xcc, 0x04, 0x39, 0x06, 0x27, 0xab, 0xe5, 0x2b, 0xd7, 0x3a, 0xb6, - 0x9e, 0x77, 0x82, 0x66, 0xab, 0x4d, 0x78, 0x6e, 0xc7, 0x24, 0x3c, 0x32, 0x86, 0x7b, 0x8d, 0x0b, - 0x33, 0xb7, 0x8b, 0x48, 0xab, 0xd7, 0x66, 0xe6, 0x9e, 0x6b, 0x9b, 0xcc, 0xdc, 0xf0, 0xf1, 0x66, - 0x6e, 0xcf, 0x64, 0x3c, 0xc3, 0x67, 0xe1, 0xb9, 0x7d, 0x93, 0x59, 0x18, 0x3e, 0xfe, 0xcc, 0xbd, - 0x6b, 0x32, 0xbe, 0xe1, 0xe3, 0x7b, 0xee, 0xc1, 0x1e, 0x63, 0xfa, 0xf8, 0xee, 0x60, 0x8f, 0xf1, - 0xc7, 0x1f, 0xa0, 0x17, 0x50, 0xc9, 0x04, 0x79, 0x0c, 0xbd, 0x9c, 0xca, 0x3a, 0xc2, 0x52, 0x54, - 0xdd, 0x2a, 0xb6, 0x52, 0x90, 0x43, 0xe8, 0xe3, 0xb1, 0xa7, 0xa3, 0xd2, 0x6a, 0xfc, 0xa3, 0x0b, - 0xce, 0xe9, 0x95, 0x90, 0x2c, 0x39, 0x95, 0x54, 0x0a, 0x32, 0x01, 0x3b, 0xe6, 0x74, 0x8d, 0x96, - 0xce, 0x7c, 0x38, 0x51, 0xef, 0xb2, 0x71, 0x3e, 0xf9, 0xc8, 0xe9, 0x5a, 0xa8, 0x2a, 0x40, 0x8e, - 0xbc, 0x80, 0x6e, 0x98, 0x15, 0xf8, 0x2c, 0x67, 0xfe, 0x64, 0x0f, 0x5f, 0x9e, 0x9c, 0x63, 0x11, - 0x28, 0x8a, 0xbc, 0x81, 0x7e, 0xc2, 0x12, 0x9e, 0x5f, 0xe1, 0x10, 0xce, 0xfc, 0x68, 0x8f, 0xff, - 0x84, 0xc7, 0xe5, 0x15, 0xcd, 0x0e, 0xbf, 0xc0, 0xa0, 0x7e, 0xaa, 0xda, 0x4e, 0x3d, 0xb7, 0xde, - 0x19, 0x45, 0xd5, 0xad, 0x77, 0x46, 0xa1, 0x76, 0xc6, 0xe3, 0x7a, 0xe7, 0x52, 0x0d, 0xdf, 0xc3, - 0x41, 0x35, 0x17, 0x21, 0x60, 0x17, 0x82, 0xe5, 0xda, 0x0e, 0x6b, 0x75, 0x4f, 0xe0, 0x4c, 0xda, - 0x4e, 0x2b, 0xc5, 0x46, 0xeb, 0x98, 0x69, 0x37, 0xac, 0x87, 0xe7, 0xe0, 0x34, 0x66, 0x56, 0x83, - 0x48, 0x2e, 0x69, 0x8c, 0x7e, 0x76, 0x50, 0x0a, 0x72, 0x04, 0x03, 0xfa, 0x9d, 0x46, 0x31, 0x5d, - 0xc5, 0x0c, 0x3d, 0xed, 0xe0, 0x7f, 0x43, 0x8f, 0xb0, 0x46, 0x5b, 0x1b, 0x47, 0x58, 0x8f, 0xaf, - 0x3b, 0xf0, 0x60, 0xc9, 0x93, 0x8c, 0xa7, 0x2c, 0x95, 0xd5, 0x9b, 0xc1, 0xa4, 0xad, 0x46, 0x72, - 0x6d, 0xc2, 0x08, 0x7b, 0x61, 0x84, 0xfd, 0xf4, 0xa6, 0x2b, 0x37, 0xe4, 0x4d, 0x46, 0x00, 0x1b, - 0x9e, 0xf3, 0x42, 0x46, 0x29, 0x13, 0xf8, 0xd5, 0xd8, 0x41, 0xa3, 0x43, 0x9e, 0xc1, 0xc3, 0x4d, - 0x78, 0x11, 0x66, 0xc5, 0xc5, 0xd7, 0x9c, 0x86, 0x32, 0xe2, 0xa9, 0xfe, 0x6c, 0xee, 0x6f, 0xc2, - 0x65, 0x56, 0xbc, 0xd3, 0xcd, 0x5b, 0x8d, 0xd9, 0x6f, 0xc7, 0x7c, 0x58, 0xef, 0x56, 0xe6, 0x5c, - 0x8d, 0x4e, 0xc0, 0x16, 0x97, 0x34, 0xd3, 0x19, 0x63, 0xfd, 0xf6, 0xd1, 0xf5, 0x6e, 0x64, 0xfd, - 0xde, 0x8d, 0xac, 0x3f, 0xbb, 0x91, 0xf5, 0xf3, 0xef, 0xe8, 0xce, 0xaa, 0x8f, 0x7f, 0x4e, 0xaf, - 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x03, 0x07, 0xe4, 0xab, 0xcd, 0x04, 0x00, 0x00, + // 536 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0x5d, 0x8b, 0xd3, 0x4e, + 0x14, 0xc6, 0xff, 0x69, 0xd3, 0xfe, 0xb7, 0x27, 0xbe, 0xc0, 0x20, 0x4b, 0x2c, 0x4b, 0x5d, 0x2a, + 0x88, 0x20, 0xb6, 0xb5, 0x1a, 0x4a, 0x6e, 0x2d, 0x78, 0xe1, 0xeb, 0x92, 0xdd, 0xbd, 0xf1, 0x66, + 0x99, 0xa6, 0x63, 0x1b, 0x4c, 0x32, 0x21, 0x33, 0x71, 0xd9, 0xef, 0xe1, 0x85, 0x1f, 0x69, 0x6f, + 0x04, 0x3f, 0x82, 0xd4, 0x2f, 0x22, 0x73, 0x32, 0x89, 0xc9, 0x74, 0x2f, 0xbc, 0xf0, 0x22, 0x30, + 0xcf, 0x99, 0xdf, 0x3c, 0x73, 0xce, 0x13, 0x12, 0x78, 0xba, 0x89, 0xe4, 0xb6, 0x58, 0x4d, 0x42, + 0x9e, 0x4c, 0xcf, 0xb6, 0xec, 0x6c, 0x1b, 0xa5, 0x1b, 0xf1, 0x9e, 0xc9, 0x4b, 0x9e, 0x7f, 0x9e, + 0x4a, 0x99, 0x4e, 0x69, 0x16, 0xa9, 0x67, 0x92, 0xe5, 0x5c, 0x72, 0xd2, 0xa5, 0x59, 0x34, 0xfe, + 0xde, 0x01, 0xe7, 0x84, 0xe5, 0x21, 0x4b, 0x65, 0x14, 0x33, 0x41, 0x8e, 0xc1, 0xc9, 0x6a, 0xf9, + 0xcc, 0xb5, 0x8e, 0xad, 0xc7, 0x9d, 0xa0, 0x59, 0x6a, 0x13, 0x9e, 0xdb, 0x31, 0x09, 0x8f, 0x8c, + 0xe1, 0x56, 0xe3, 0xc0, 0xcc, 0xed, 0x22, 0xd2, 0xaa, 0xb5, 0x99, 0xb9, 0xe7, 0xda, 0x26, 0x33, + 0x37, 0x7c, 0xbc, 0x99, 0xdb, 0x33, 0x19, 0xcf, 0xf0, 0x59, 0x78, 0x6e, 0xdf, 0x64, 0x16, 0x86, + 0x8f, 0x3f, 0x73, 0xff, 0x37, 0x19, 0xdf, 0xf0, 0xf1, 0x3d, 0xf7, 0x60, 0x8f, 0x31, 0x7d, 0x7c, + 0x77, 0xb0, 0xc7, 0xf8, 0xe3, 0x37, 0xd0, 0x0b, 0xa8, 0x64, 0x82, 0xdc, 0x83, 0x5e, 0x4e, 0x65, + 0x1d, 0x61, 0x29, 0xaa, 0x6a, 0x15, 0x5b, 0x29, 0xc8, 0x21, 0xf4, 0x71, 0xdb, 0xd3, 0x51, 0x69, + 0x35, 0xfe, 0xda, 0x05, 0xe7, 0xf4, 0x4a, 0x48, 0x96, 0x9c, 0x4a, 0x2a, 0x05, 0x99, 0x80, 0x1d, + 0x73, 0xba, 0x46, 0x4b, 0x67, 0x3e, 0x9c, 0xa8, 0x77, 0xd9, 0xd8, 0x9f, 0xbc, 0xe5, 0x74, 0x2d, + 0xd4, 0x2a, 0x40, 0x8e, 0x3c, 0x81, 0x6e, 0x98, 0x15, 0x78, 0x97, 0x33, 0xbf, 0xbf, 0x87, 0x2f, + 0x4f, 0xce, 0x71, 0x11, 0x28, 0x8a, 0xbc, 0x80, 0x7e, 0xc2, 0x12, 0x9e, 0x5f, 0x61, 0x13, 0xce, + 0xfc, 0x68, 0x8f, 0x7f, 0x87, 0xdb, 0xe5, 0x11, 0xcd, 0x0e, 0x3f, 0xc0, 0xa0, 0xbe, 0x55, 0x4d, + 0xa7, 0xee, 0xad, 0x67, 0x46, 0x51, 0x55, 0xeb, 0x99, 0x51, 0xa8, 0x99, 0x71, 0xbb, 0x9e, 0xb9, + 0x54, 0xc3, 0xd7, 0x70, 0x50, 0xf5, 0x45, 0x08, 0xd8, 0x85, 0x60, 0xb9, 0xb6, 0xc3, 0xb5, 0x3a, + 0x27, 0xb0, 0x27, 0x6d, 0xa7, 0x95, 0x62, 0xa3, 0x75, 0xcc, 0xb4, 0x1b, 0xae, 0x87, 0xe7, 0xe0, + 0x34, 0x7a, 0x56, 0x8d, 0x48, 0x2e, 0x69, 0x8c, 0x7e, 0x76, 0x50, 0x0a, 0x72, 0x04, 0x03, 0xfa, + 0x85, 0x46, 0x31, 0x5d, 0xc5, 0x0c, 0x3d, 0xed, 0xe0, 0x4f, 0x41, 0xb7, 0xb0, 0x46, 0x5b, 0x1b, + 0x5b, 0x58, 0x8f, 0xaf, 0x3b, 0x70, 0x67, 0xc9, 0x93, 0x8c, 0xa7, 0x2c, 0x95, 0xd5, 0x9b, 0xc1, + 0xa4, 0xad, 0x46, 0x72, 0x6d, 0xc2, 0x08, 0x7b, 0x61, 0x84, 0xfd, 0xe0, 0xa6, 0x23, 0x37, 0xe4, + 0x4d, 0x46, 0x00, 0x1b, 0x9e, 0xf3, 0x42, 0x46, 0x29, 0x13, 0xf8, 0xd5, 0xd8, 0x41, 0xa3, 0x42, + 0x1e, 0xc1, 0xdd, 0x4d, 0x78, 0x11, 0x66, 0xc5, 0xc5, 0xa7, 0x9c, 0x86, 0x32, 0xe2, 0xa9, 0xfe, + 0x6c, 0x6e, 0x6f, 0xc2, 0x65, 0x56, 0xbc, 0xd2, 0xc5, 0x7f, 0x1a, 0xb3, 0xdf, 0x8e, 0xf9, 0xb0, + 0x9e, 0xad, 0xcc, 0xb9, 0x6a, 0x9d, 0x80, 0x2d, 0x2e, 0x69, 0xa6, 0x33, 0xc6, 0xf5, 0x4b, 0xef, + 0x7a, 0x37, 0xb2, 0x7e, 0xec, 0x46, 0xd6, 0xcf, 0xdd, 0xc8, 0xfa, 0xf6, 0x6b, 0xf4, 0xdf, 0xc7, + 0x87, 0x7f, 0xf1, 0x13, 0x5b, 0xf5, 0xf1, 0x0f, 0xf6, 0xfc, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, + 0x9d, 0xbc, 0x69, 0xcf, 0xf2, 0x04, 0x00, 0x00, } diff --git a/api/api.proto b/api/api.proto index a3e821c59..2903fbffd 100644 --- a/api/api.proto +++ b/api/api.proto @@ -5,6 +5,8 @@ syntax = "proto3"; package api; +option go_package = "github.com/TheThingsNetwork/ttn/api"; + message Percentiles { float percentile1 = 1; float percentile5 = 2; diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index ffc7dcf4a..f0e299db6 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -4782,77 +4782,77 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1146 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0x67, 0xe3, 0xc6, 0x89, 0x9f, 0xe3, 0x3f, 0x99, 0x36, 0xc9, 0xd6, 0xa5, 0x8e, 0x31, 0x52, - 0x65, 0x09, 0x6a, 0xb7, 0x46, 0x50, 0x55, 0x20, 0x22, 0xe7, 0x8f, 0x20, 0x48, 0x2e, 0xd5, 0x26, - 0xe1, 0x84, 0x64, 0x8d, 0x77, 0x5f, 0xd6, 0xa3, 0xac, 0x77, 0xb7, 0x3b, 0xb3, 0x4e, 0x73, 0x47, - 0x7c, 0x06, 0xae, 0xf0, 0x31, 0x90, 0xb8, 0x73, 0x83, 0x33, 0x07, 0x40, 0xe1, 0xc2, 0x85, 0xef, - 0x80, 0x76, 0x76, 0x76, 0x6d, 0x27, 0x71, 0x13, 0x50, 0x0e, 0x94, 0xf6, 0xe4, 0x9d, 0xf7, 0x7e, - 0xf3, 0xdb, 0xb7, 0xbf, 0xf7, 0xc7, 0x33, 0xf0, 0xc8, 0x66, 0x62, 0x10, 0xf6, 0x9b, 0xa6, 0x37, - 0x6c, 0xed, 0x0f, 0x70, 0x7f, 0xc0, 0x5c, 0x9b, 0x3f, 0x41, 0x71, 0xec, 0x05, 0x47, 0x2d, 0x21, - 0xdc, 0x16, 0xf5, 0x59, 0xab, 0x1f, 0x78, 0x47, 0x18, 0xa8, 0x9f, 0xa6, 0x1f, 0x78, 0xc2, 0x23, - 0xd9, 0x78, 0x55, 0xb9, 0x63, 0x7b, 0x9e, 0xed, 0x60, 0x4b, 0x5a, 0xfb, 0xe1, 0x61, 0x0b, 0x87, - 0xbe, 0x38, 0x89, 0x41, 0x95, 0xfb, 0x13, 0xec, 0xb6, 0x67, 0x7b, 0x63, 0x54, 0xb4, 0x92, 0x0b, - 0xf9, 0x74, 0x01, 0x7c, 0x66, 0x30, 0xd4, 0x67, 0x0a, 0xfe, 0xe1, 0x55, 0xe0, 0x12, 0x6a, 0x7a, - 0x4e, 0xfa, 0xa0, 0x36, 0x3f, 0xbe, 0xca, 0x66, 0x9b, 0x0a, 0x3c, 0xa6, 0x27, 0xc9, 0x6f, 0xbc, - 0xb5, 0xfe, 0xf5, 0x1c, 0x14, 0xb7, 0xbd, 0x63, 0xd7, 0x61, 0xee, 0xd1, 0xe7, 0xbe, 0x60, 0x9e, - 0x4b, 0xaa, 0x00, 0xcc, 0x42, 0x57, 0xb0, 0x43, 0x86, 0x81, 0xae, 0xd5, 0xb4, 0x46, 0xce, 0x98, - 0xb0, 0x90, 0xbb, 0x00, 0x8a, 0xa3, 0xc7, 0x2c, 0x7d, 0x4e, 0xfa, 0x73, 0xca, 0xb2, 0x6b, 0x91, - 0x5b, 0x30, 0xcf, 0x4d, 0x2f, 0x40, 0x3d, 0x53, 0xd3, 0x1a, 0x05, 0x23, 0x5e, 0x90, 0x0a, 0x2c, - 0x5a, 0x48, 0x2d, 0x87, 0xb9, 0xa8, 0xdf, 0xa8, 0x69, 0x8d, 0x8c, 0x91, 0xae, 0xc9, 0x26, 0x94, - 0x92, 0x0f, 0xea, 0x99, 0x9e, 0x7b, 0xc8, 0x6c, 0x7d, 0xbe, 0xa6, 0x35, 0xf2, 0xed, 0xdb, 0xcd, - 0xf4, 0x43, 0xf7, 0x9f, 0x6f, 0x49, 0x4f, 0x18, 0xd0, 0x28, 0x48, 0xa3, 0x98, 0x78, 0x62, 0x33, - 0xd9, 0x80, 0x62, 0x12, 0x94, 0xa2, 0xc8, 0x4a, 0x0a, 0xbd, 0x99, 0x7c, 0xef, 0x59, 0x86, 0x82, - 0x72, 0xc4, 0xd6, 0xfa, 0x0f, 0x19, 0x28, 0x1c, 0xf8, 0x91, 0x0c, 0x5d, 0xe4, 0x9c, 0xda, 0x48, - 0x74, 0x58, 0xf0, 0xe9, 0x89, 0xe3, 0x51, 0x4b, 0x8a, 0xb0, 0x64, 0x24, 0x4b, 0xf2, 0x04, 0x16, - 0x2c, 0x1c, 0xf5, 0x30, 0x64, 0x7a, 0x3e, 0xf2, 0x6c, 0xbe, 0xff, 0xcb, 0xaf, 0xeb, 0x0f, 0x2f, - 0x4b, 0x42, 0xa4, 0x43, 0x4b, 0x9c, 0xf8, 0xc8, 0x9b, 0xdb, 0x38, 0xda, 0x39, 0xd8, 0x35, 0xb2, - 0x16, 0x8e, 0x76, 0x42, 0x16, 0xf1, 0x51, 0xdf, 0x97, 0x7c, 0x4b, 0xff, 0x8a, 0xaf, 0xe3, 0xfb, - 0x92, 0x8f, 0xfa, 0x7e, 0xc4, 0xb7, 0x02, 0xd1, 0x53, 0x94, 0x9d, 0x82, 0xcc, 0xce, 0x3c, 0xf5, - 0xfd, 0x5d, 0x2b, 0x32, 0x47, 0x61, 0x33, 0x4b, 0x2f, 0xc6, 0x66, 0x0b, 0x47, 0xbb, 0x16, 0xe9, - 0xc0, 0x72, 0x2a, 0xff, 0x10, 0x05, 0xb5, 0xa8, 0xa0, 0xfa, 0x8a, 0x54, 0xef, 0xd6, 0x38, 0x01, - 0xc6, 0xf3, 0xae, 0xf2, 0x19, 0xe5, 0xc4, 0x98, 0x58, 0xc8, 0xc7, 0x50, 0x4e, 0xd4, 0x4f, 0x19, - 0x56, 0x25, 0xc3, 0xcd, 0x54, 0xff, 0x09, 0x82, 0x92, 0xb2, 0xa5, 0xfb, 0x3b, 0x50, 0xb6, 0x54, - 0x11, 0xf6, 0x3c, 0x59, 0x85, 0x5c, 0x5f, 0xaf, 0x65, 0x1a, 0xf9, 0xf6, 0x6a, 0x53, 0x75, 0xea, - 0x74, 0x91, 0x1a, 0x25, 0x6b, 0x6a, 0xcd, 0xeb, 0xdf, 0xcf, 0x41, 0x29, 0xc1, 0xbc, 0x6a, 0x19, - 0xdc, 0x80, 0xd2, 0x19, 0xf9, 0x54, 0xfe, 0x66, 0xa9, 0x57, 0x9c, 0x56, 0xaf, 0x1e, 0x82, 0xbe, - 0x8d, 0x23, 0x66, 0x62, 0xc7, 0x14, 0x6c, 0x14, 0xf7, 0x07, 0x72, 0xdf, 0x73, 0xf9, 0x8b, 0x44, - 0xbc, 0xe0, 0xb5, 0xf9, 0x7f, 0xf4, 0xda, 0xbf, 0x32, 0x70, 0x7b, 0x1b, 0xad, 0xd0, 0x77, 0x98, - 0x49, 0x05, 0x5a, 0xaf, 0xfb, 0xef, 0x5a, 0xfb, 0x2f, 0x73, 0xe5, 0xfe, 0x5b, 0x87, 0x3c, 0xc7, - 0x60, 0x84, 0x41, 0x4f, 0xb0, 0x21, 0xea, 0x6b, 0x72, 0x40, 0x43, 0x6c, 0xda, 0x67, 0x43, 0x24, - 0xdb, 0xb0, 0x1c, 0xa8, 0x82, 0xe8, 0x09, 0x1c, 0xfa, 0x0e, 0x15, 0xa8, 0xaf, 0xcb, 0x18, 0xd7, - 0xce, 0x26, 0x5b, 0xe5, 0xcf, 0x28, 0x27, 0x3b, 0xf6, 0xd5, 0x86, 0xfa, 0x9f, 0x19, 0x58, 0x3b, - 0x5f, 0x67, 0xcf, 0x42, 0xe4, 0xe2, 0x25, 0xce, 0xf6, 0x7f, 0x60, 0x7e, 0x76, 0xe1, 0x26, 0x4d, - 0x15, 0x1d, 0x53, 0xac, 0x49, 0x8a, 0x37, 0xc7, 0x41, 0x8c, 0x65, 0x4f, 0xb9, 0x08, 0x3d, 0x67, - 0xbb, 0x8e, 0x71, 0xfc, 0xd3, 0x0d, 0x78, 0x7b, 0xb2, 0xb5, 0xff, 0x7f, 0x69, 0x7f, 0xe9, 0x9a, - 0xfc, 0x9a, 0x8b, 0xe4, 0xcc, 0xcc, 0xd0, 0xcf, 0xcd, 0x8c, 0xee, 0xec, 0x99, 0x51, 0x4b, 0xcb, - 0x68, 0xc6, 0xbf, 0xce, 0x05, 0xc3, 0xe3, 0xab, 0x39, 0xa8, 0x8c, 0x81, 0x5b, 0x03, 0xea, 0x38, - 0xe8, 0xda, 0xf8, 0x8a, 0x15, 0x52, 0xfd, 0x11, 0xdc, 0xb9, 0x50, 0x85, 0xcb, 0xfe, 0xad, 0xeb, - 0x04, 0xca, 0x7b, 0x61, 0x9f, 0x9b, 0x01, 0xeb, 0x27, 0xa2, 0xd5, 0x4b, 0x50, 0xd8, 0x13, 0x54, - 0x84, 0x3c, 0x31, 0xfc, 0x96, 0x81, 0x6c, 0x6c, 0x21, 0x0d, 0xc8, 0xf2, 0x13, 0x2e, 0x70, 0x28, - 0x89, 0xf2, 0xed, 0x72, 0x33, 0xba, 0xad, 0xec, 0x49, 0x53, 0x04, 0xe1, 0x86, 0xf2, 0x93, 0x87, - 0x90, 0x33, 0xbd, 0xa1, 0xef, 0xb9, 0xe8, 0x0a, 0x79, 0x1f, 0x88, 0x2a, 0x32, 0x02, 0x6f, 0x25, - 0xd6, 0x18, 0x3f, 0x46, 0x91, 0x3a, 0x64, 0x43, 0xf9, 0x67, 0xaf, 0x4e, 0x0c, 0x20, 0xf1, 0x06, - 0x15, 0xc8, 0x0d, 0xe5, 0x21, 0x2d, 0x28, 0xc4, 0x4f, 0xbd, 0xd0, 0x65, 0xcf, 0x42, 0x94, 0x6a, - 0x4f, 0x43, 0x97, 0x62, 0xc0, 0x81, 0xf4, 0x93, 0x7b, 0xb0, 0x98, 0x8c, 0x21, 0x29, 0xe5, 0x34, - 0x36, 0xf5, 0x91, 0x77, 0x21, 0x3f, 0xae, 0x67, 0x2e, 0xe5, 0x9d, 0x86, 0x4e, 0xba, 0xc9, 0x63, - 0x98, 0xa8, 0x7e, 0x9e, 0xc4, 0x52, 0x3a, 0xb7, 0x69, 0x79, 0x02, 0xa5, 0x02, 0xfa, 0x00, 0x0a, - 0x56, 0x3a, 0x03, 0xa3, 0xe3, 0x51, 0x79, 0x42, 0xc9, 0xa7, 0x18, 0x98, 0xd1, 0x9d, 0xca, 0x41, - 0x6e, 0x4c, 0xc3, 0xc8, 0x3b, 0xb0, 0x6c, 0x7a, 0xae, 0x8b, 0xa6, 0x40, 0xab, 0x17, 0x78, 0xa1, - 0xc0, 0x80, 0xcb, 0x61, 0x51, 0x30, 0xca, 0xa9, 0xc3, 0x88, 0xed, 0xe4, 0x3e, 0x90, 0x31, 0x78, - 0x40, 0x5d, 0xcb, 0x89, 0xd0, 0xab, 0x12, 0x3d, 0xa6, 0xf9, 0x54, 0x39, 0xea, 0x5f, 0x40, 0xb5, - 0xe3, 0xa7, 0xaf, 0x52, 0x66, 0x03, 0x6d, 0xc6, 0x45, 0x7c, 0x31, 0x9a, 0xa8, 0x47, 0x6d, 0xb2, - 0x1e, 0xef, 0x02, 0x28, 0xf6, 0x89, 0x6b, 0x9f, 0xb2, 0xec, 0x5a, 0xed, 0xef, 0xe6, 0x20, 0xbb, - 0x29, 0x9b, 0x9a, 0x6c, 0x40, 0xae, 0xc3, 0xb9, 0x67, 0x32, 0x2a, 0x90, 0xac, 0x24, 0xad, 0x3e, - 0x75, 0xb8, 0xab, 0xcc, 0x3a, 0x35, 0x34, 0xb4, 0x07, 0x1a, 0xf9, 0x0c, 0x72, 0x69, 0xa9, 0x12, - 0x3d, 0x41, 0x9e, 0xad, 0xde, 0xca, 0x5b, 0xe3, 0x29, 0x32, 0xe3, 0x0c, 0xf9, 0x40, 0x23, 0x1f, - 0xc1, 0xc2, 0xd3, 0xb0, 0xef, 0x30, 0x3e, 0x20, 0xb3, 0xde, 0x59, 0x59, 0x6d, 0xc6, 0x17, 0xff, - 0x66, 0x72, 0xa5, 0x6f, 0xee, 0x44, 0x17, 0xff, 0x86, 0x46, 0xba, 0xb0, 0xa8, 0xba, 0x0d, 0xc9, - 0xfa, 0xec, 0xa1, 0x15, 0xc7, 0x73, 0xe9, 0x54, 0x6b, 0x7f, 0xab, 0x41, 0x21, 0x16, 0xa9, 0x4b, - 0x5d, 0x6a, 0x63, 0x40, 0xbe, 0x84, 0x4a, 0x2c, 0x3e, 0x06, 0xe7, 0xd3, 0x42, 0xee, 0x25, 0x8c, - 0x2f, 0x4e, 0xd9, 0xac, 0x0f, 0x20, 0x6d, 0xc8, 0x7d, 0x82, 0x42, 0x35, 0x74, 0x9a, 0x89, 0xa9, - 0x96, 0xaf, 0x14, 0xa7, 0xcd, 0x9b, 0xe5, 0x1f, 0x4f, 0xab, 0xda, 0xcf, 0xa7, 0x55, 0xed, 0xf7, - 0xd3, 0xaa, 0xf6, 0xcd, 0x1f, 0xd5, 0x37, 0xfa, 0x59, 0xc9, 0xfa, 0xde, 0xdf, 0x01, 0x00, 0x00, - 0xff, 0xff, 0x92, 0x70, 0x31, 0xe1, 0x60, 0x11, 0x00, 0x00, + // 1151 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x58, 0x5f, 0x6f, 0xdb, 0x54, + 0x14, 0xc7, 0xcd, 0x96, 0x35, 0x27, 0xcd, 0x9f, 0xde, 0xad, 0xad, 0x97, 0xd1, 0x24, 0x04, 0x69, + 0x8a, 0x80, 0x25, 0x5b, 0x10, 0x4c, 0x48, 0x13, 0x55, 0xfa, 0x47, 0x50, 0xa4, 0x8c, 0xc9, 0x6d, + 0x79, 0x40, 0x48, 0xd1, 0x8d, 0x7d, 0xea, 0x5c, 0xd5, 0xb1, 0x3d, 0xdf, 0xeb, 0x74, 0x7d, 0x47, + 0x7c, 0x06, 0x5e, 0xe1, 0x63, 0x20, 0xf1, 0xce, 0x1b, 0x3c, 0xf3, 0x00, 0xa8, 0xbc, 0xf0, 0xc2, + 0x77, 0x40, 0xbe, 0xbe, 0x76, 0x92, 0xb6, 0x59, 0x07, 0xea, 0x03, 0x63, 0x3c, 0xc5, 0xf7, 0x77, + 0x7e, 0x3e, 0x3e, 0xfe, 0x9d, 0x3f, 0xbe, 0x37, 0xf0, 0xd0, 0x66, 0x62, 0x18, 0x0e, 0x5a, 0xa6, + 0x37, 0x6a, 0xef, 0x0f, 0x71, 0x7f, 0xc8, 0x5c, 0x9b, 0x3f, 0x46, 0x71, 0xec, 0x05, 0x47, 0x6d, + 0x21, 0xdc, 0x36, 0xf5, 0x59, 0x7b, 0x10, 0x78, 0x47, 0x18, 0xa8, 0x9f, 0x96, 0x1f, 0x78, 0xc2, + 0x23, 0xd9, 0x78, 0x55, 0xb9, 0x63, 0x7b, 0x9e, 0xed, 0x60, 0x5b, 0xa2, 0x83, 0xf0, 0xb0, 0x8d, + 0x23, 0x5f, 0x9c, 0xc4, 0xa4, 0xca, 0xbd, 0x29, 0xef, 0xb6, 0x67, 0x7b, 0x13, 0x56, 0xb4, 0x92, + 0x0b, 0x79, 0xa5, 0xe8, 0xcb, 0xc9, 0x03, 0xa9, 0xcf, 0x14, 0x54, 0x4b, 0x20, 0xb9, 0x34, 0x3d, + 0x27, 0xbd, 0x50, 0x84, 0xf5, 0x84, 0x60, 0x53, 0x81, 0xc7, 0xf4, 0x24, 0xf9, 0x8d, 0xcd, 0x8d, + 0xaf, 0x16, 0xa0, 0xb8, 0xed, 0x1d, 0xbb, 0x0e, 0x73, 0x8f, 0x3e, 0xf5, 0x05, 0xf3, 0x5c, 0x52, + 0x05, 0x60, 0x16, 0xba, 0x82, 0x1d, 0x32, 0x0c, 0x74, 0xad, 0xae, 0x35, 0x73, 0xc6, 0x14, 0x42, + 0xd6, 0x01, 0x94, 0x8f, 0x3e, 0xb3, 0xf4, 0x05, 0x69, 0xcf, 0x29, 0x64, 0xd7, 0x22, 0xb7, 0xe0, + 0x3a, 0x37, 0xbd, 0x00, 0xf5, 0x4c, 0x5d, 0x6b, 0x16, 0x8c, 0x78, 0x41, 0x2a, 0xb0, 0x68, 0x21, + 0xb5, 0x1c, 0xe6, 0xa2, 0x7e, 0xad, 0xae, 0x35, 0x33, 0x46, 0xba, 0x26, 0x9b, 0x50, 0x4a, 0x82, + 0xee, 0x9b, 0x9e, 0x7b, 0xc8, 0x6c, 0xfd, 0x7a, 0x5d, 0x6b, 0xe6, 0x3b, 0xb7, 0x5b, 0xe9, 0xcb, + 0xec, 0x3f, 0xdb, 0x92, 0x96, 0x30, 0xa0, 0x51, 0x90, 0x46, 0x31, 0xb1, 0xc4, 0x30, 0xd9, 0x80, + 0x62, 0x12, 0x94, 0x72, 0x91, 0x95, 0x2e, 0xf4, 0x56, 0xf2, 0xbe, 0x67, 0x3d, 0x14, 0x94, 0x21, + 0x46, 0x1b, 0xdf, 0x67, 0xa0, 0x70, 0xe0, 0x47, 0x32, 0xf4, 0x90, 0x73, 0x6a, 0x23, 0xd1, 0xe1, + 0x86, 0x4f, 0x4f, 0x1c, 0x8f, 0x5a, 0x52, 0x84, 0x25, 0x23, 0x59, 0x92, 0xc7, 0x70, 0xc3, 0xc2, + 0x71, 0x1f, 0x43, 0xa6, 0xe7, 0x23, 0xcb, 0xe6, 0x7b, 0x3f, 0xff, 0x52, 0x7b, 0x70, 0x59, 0xa5, + 0x44, 0x3a, 0xb4, 0xc5, 0x89, 0x8f, 0xbc, 0xb5, 0x8d, 0xe3, 0x9d, 0x83, 0x5d, 0x23, 0x6b, 0xe1, + 0x78, 0x27, 0x64, 0x91, 0x3f, 0xea, 0xfb, 0xd2, 0xdf, 0xd2, 0x3f, 0xf2, 0xd7, 0xf5, 0x7d, 0xe9, + 0x8f, 0xfa, 0x7e, 0xe4, 0x6f, 0x05, 0xa2, 0xab, 0x28, 0x3b, 0x05, 0x99, 0x9d, 0xeb, 0xd4, 0xf7, + 0x77, 0xad, 0x08, 0x8e, 0xc2, 0x66, 0x96, 0x5e, 0x8c, 0x61, 0x0b, 0xc7, 0xbb, 0x16, 0xe9, 0xc2, + 0x72, 0x2a, 0xff, 0x08, 0x05, 0xb5, 0xa8, 0xa0, 0xfa, 0x8a, 0x54, 0xef, 0xd6, 0x24, 0x01, 0xc6, + 0xb3, 0x9e, 0xb2, 0x19, 0xe5, 0x04, 0x4c, 0x10, 0xf2, 0x21, 0x94, 0x13, 0xf5, 0x53, 0x0f, 0xab, + 0xd2, 0xc3, 0xcd, 0x54, 0xff, 0x29, 0x07, 0x25, 0x85, 0xa5, 0xf7, 0x77, 0xa1, 0x6c, 0xa9, 0x22, + 0xec, 0x7b, 0xb2, 0x0a, 0xb9, 0x5e, 0xab, 0x67, 0x9a, 0xf9, 0xce, 0x6a, 0x4b, 0x75, 0xd5, 0x6c, + 0x91, 0x1a, 0x25, 0x6b, 0x66, 0xcd, 0x1b, 0xdf, 0x2d, 0x40, 0x29, 0xe1, 0xbc, 0x6a, 0x19, 0xdc, + 0x80, 0xd2, 0x19, 0xf9, 0x54, 0xfe, 0xe6, 0xa9, 0x57, 0x9c, 0x55, 0xaf, 0x11, 0x82, 0xbe, 0x8d, + 0x63, 0x66, 0x62, 0xd7, 0x14, 0x6c, 0x1c, 0xf7, 0x07, 0x72, 0xdf, 0x73, 0xf9, 0xf3, 0x44, 0xbc, + 0xe0, 0xb1, 0xf9, 0xbf, 0xf5, 0xd8, 0x3f, 0x33, 0x70, 0x7b, 0x1b, 0xad, 0xd0, 0x77, 0x98, 0x49, + 0x05, 0x5a, 0xff, 0xf7, 0xdf, 0x95, 0xf6, 0x5f, 0xe6, 0x85, 0xfb, 0xaf, 0x06, 0x79, 0x8e, 0xc1, + 0x18, 0x83, 0xbe, 0x60, 0x23, 0xd4, 0xd7, 0xe4, 0x80, 0x86, 0x18, 0xda, 0x67, 0x23, 0x24, 0xdb, + 0xb0, 0x1c, 0xa8, 0x82, 0xe8, 0x0b, 0x1c, 0xf9, 0x0e, 0x15, 0xa8, 0xd7, 0x64, 0x8c, 0x6b, 0x67, + 0x93, 0xad, 0xf2, 0x67, 0x94, 0x93, 0x3b, 0xf6, 0xd5, 0x0d, 0x8d, 0x3f, 0x32, 0xb0, 0x76, 0xbe, + 0xce, 0x9e, 0x86, 0xc8, 0xc5, 0x4b, 0x9c, 0xed, 0x7f, 0xc1, 0xfc, 0xec, 0xc1, 0x4d, 0x9a, 0x2a, + 0x3a, 0x71, 0xb1, 0x26, 0x5d, 0xbc, 0x3e, 0x09, 0x62, 0x22, 0x7b, 0xea, 0x8b, 0xd0, 0x73, 0xd8, + 0x55, 0x8c, 0xe3, 0x1f, 0xaf, 0xc1, 0x9b, 0xd3, 0xad, 0xfd, 0xdf, 0x4b, 0xfb, 0x4b, 0xd7, 0xe4, + 0x57, 0x5c, 0x24, 0x67, 0x66, 0x86, 0x7e, 0x6e, 0x66, 0xf4, 0xe6, 0xcf, 0x8c, 0x7a, 0x5a, 0x46, + 0x73, 0xbe, 0x3a, 0x17, 0x0c, 0x8f, 0x2f, 0x17, 0xa0, 0x32, 0x21, 0x6e, 0x0d, 0xa9, 0xe3, 0xa0, + 0x6b, 0xe3, 0x2b, 0x56, 0x48, 0x8d, 0x87, 0x70, 0xe7, 0x42, 0x15, 0x2e, 0xfb, 0x5a, 0x37, 0x08, + 0x94, 0xf7, 0xc2, 0x01, 0x37, 0x03, 0x36, 0x48, 0x44, 0x6b, 0x94, 0xa0, 0xb0, 0x27, 0xa8, 0x08, + 0x79, 0x02, 0xfc, 0x9a, 0x81, 0x6c, 0x8c, 0x90, 0x26, 0x64, 0xf9, 0x09, 0x17, 0x38, 0x92, 0x8e, + 0xf2, 0x9d, 0x72, 0x2b, 0x3a, 0x75, 0xec, 0x49, 0x28, 0xa2, 0x70, 0x43, 0xd9, 0xc9, 0x03, 0xc8, + 0x99, 0xde, 0xc8, 0xf7, 0x5c, 0x74, 0x85, 0x3c, 0x0f, 0x44, 0x15, 0x19, 0x91, 0xb7, 0x12, 0x34, + 0xe6, 0x4f, 0x58, 0xa4, 0x01, 0xd9, 0x50, 0x7e, 0xec, 0xd5, 0x8e, 0x01, 0x24, 0xdf, 0xa0, 0x02, + 0xb9, 0xa1, 0x2c, 0xa4, 0x0d, 0x85, 0xf8, 0xaa, 0x1f, 0xba, 0xec, 0x69, 0x88, 0x52, 0xed, 0x59, + 0xea, 0x52, 0x4c, 0x38, 0x90, 0x76, 0x72, 0x17, 0x16, 0x93, 0x31, 0x24, 0xa5, 0x9c, 0xe5, 0xa6, + 0x36, 0xf2, 0x0e, 0xe4, 0x27, 0xf5, 0xcc, 0xa5, 0xbc, 0xb3, 0xd4, 0x69, 0x33, 0xf9, 0x00, 0xa6, + 0xaa, 0x9f, 0x27, 0xb1, 0x94, 0xce, 0xdd, 0xb4, 0x3c, 0xc5, 0x52, 0x01, 0xbd, 0x0f, 0x05, 0x2b, + 0x9d, 0x81, 0xd1, 0xf6, 0xa8, 0x3c, 0xa5, 0xe4, 0x13, 0x0c, 0xcc, 0xe8, 0x4c, 0xe5, 0x20, 0x37, + 0x66, 0x69, 0xe4, 0x6d, 0x58, 0x36, 0x3d, 0xd7, 0x45, 0x53, 0xa0, 0xd5, 0x0f, 0xbc, 0x50, 0x60, + 0xc0, 0xe5, 0xb0, 0x28, 0x18, 0xe5, 0xd4, 0x60, 0xc4, 0x38, 0xb9, 0x07, 0x64, 0x42, 0x1e, 0x52, + 0xd7, 0x72, 0x22, 0xf6, 0xaa, 0x64, 0x4f, 0xdc, 0x7c, 0xac, 0x0c, 0x8d, 0xcf, 0xa0, 0xda, 0xf5, + 0xd3, 0x47, 0x29, 0xd8, 0x40, 0x9b, 0x71, 0x11, 0x1f, 0x8c, 0xa6, 0xea, 0x51, 0x9b, 0xae, 0xc7, + 0x75, 0x00, 0xe5, 0x7d, 0xea, 0xd8, 0xa7, 0x90, 0x5d, 0xab, 0xf3, 0xed, 0x02, 0x64, 0x37, 0x65, + 0x53, 0x93, 0x0d, 0xc8, 0x75, 0x39, 0xf7, 0x4c, 0x46, 0x05, 0x92, 0x95, 0xa4, 0xd5, 0x67, 0x36, + 0x77, 0x95, 0x79, 0xbb, 0x86, 0xa6, 0x76, 0x5f, 0x23, 0x9f, 0x40, 0x2e, 0x2d, 0x55, 0xa2, 0x27, + 0xcc, 0xb3, 0xd5, 0x5b, 0x79, 0x63, 0x32, 0x45, 0xe6, 0xec, 0x21, 0xef, 0x6b, 0xe4, 0x11, 0xdc, + 0x78, 0x12, 0x0e, 0x1c, 0xc6, 0x87, 0x64, 0xde, 0x33, 0x2b, 0xab, 0xad, 0xf8, 0x90, 0xde, 0x4a, + 0x8e, 0xdf, 0xad, 0x9d, 0xe8, 0x90, 0xde, 0xd4, 0x48, 0x0f, 0x16, 0x55, 0xb7, 0x21, 0xa9, 0xcd, + 0x1f, 0x5a, 0x71, 0x3c, 0x97, 0x4e, 0xb5, 0xce, 0x37, 0x1a, 0x14, 0x62, 0x91, 0x7a, 0xd4, 0xa5, + 0x36, 0x06, 0xe4, 0x0b, 0xa8, 0xc4, 0xe2, 0x63, 0x70, 0x3e, 0x2d, 0xe4, 0x6e, 0xe2, 0xf1, 0xf9, + 0x29, 0x9b, 0xf7, 0x02, 0xa4, 0x03, 0xb9, 0x8f, 0x50, 0xa8, 0x86, 0x4e, 0x33, 0x31, 0xd3, 0xf2, + 0x95, 0xe2, 0x2c, 0xbc, 0xf9, 0xe8, 0x87, 0xd3, 0xaa, 0xf6, 0xd3, 0x69, 0x55, 0xfb, 0xed, 0xb4, + 0xaa, 0x7d, 0xfd, 0x7b, 0xf5, 0xb5, 0xcf, 0xdf, 0x7a, 0xf1, 0xff, 0x40, 0x06, 0x59, 0x19, 0xc1, + 0xbb, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xe5, 0xfc, 0x23, 0x38, 0x11, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index e3f51d854..178537e3c 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -5,12 +5,14 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; -import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; -import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; +import "ttn/api/api.proto"; +import "ttn/api/protocol/protocol.proto"; +import "ttn/api/gateway/gateway.proto"; package broker; +option go_package = "github.com/TheThingsNetwork/ttn/api/broker"; + message DownlinkOption { string identifier = 1; string gateway_id = 2; diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index a33f59014..befaca3c9 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -1681,41 +1681,41 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 566 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x53, 0x4d, 0x6f, 0xd3, 0x40, - 0x10, 0xad, 0x13, 0x5a, 0xe2, 0x71, 0x9a, 0x5a, 0x0b, 0x05, 0x2b, 0x88, 0x60, 0x7c, 0xa1, 0x5c, - 0x6c, 0x29, 0x55, 0x4f, 0x08, 0x21, 0xa3, 0x84, 0x52, 0x95, 0x96, 0xca, 0x2a, 0x88, 0x5b, 0xe4, - 0xd8, 0xd3, 0x74, 0x55, 0x7b, 0x6d, 0xbc, 0xeb, 0xa0, 0x5c, 0xf9, 0x15, 0xdc, 0xf8, 0x3b, 0x9c, - 0x10, 0x3f, 0x01, 0x95, 0x3f, 0x82, 0xfc, 0x59, 0x47, 0x6d, 0x84, 0x80, 0x9b, 0xfd, 0xe6, 0xbd, - 0xb7, 0x33, 0x6f, 0x77, 0xe0, 0xf9, 0x8c, 0x8a, 0xf3, 0x74, 0x6a, 0x7a, 0x51, 0x68, 0x9d, 0x9e, - 0xe3, 0xe9, 0x39, 0x65, 0x33, 0x7e, 0x8c, 0xe2, 0x53, 0x94, 0x5c, 0x58, 0x42, 0x30, 0xcb, 0x8d, - 0xa9, 0xe5, 0x53, 0xee, 0x45, 0x73, 0x4c, 0x16, 0x57, 0x5f, 0x66, 0x9c, 0x44, 0x22, 0x22, 0x72, - 0x0d, 0xf4, 0x1f, 0xcc, 0xa2, 0x68, 0x16, 0xa0, 0x95, 0x17, 0xa6, 0xe9, 0x99, 0x85, 0x61, 0x2c, - 0x4a, 0x9e, 0xf1, 0x59, 0x82, 0xce, 0x11, 0x0a, 0xd7, 0x77, 0x85, 0x4b, 0x9e, 0x42, 0xfb, 0x02, - 0x17, 0x9a, 0xa4, 0x4b, 0x3b, 0xbd, 0xe1, 0x7d, 0xf3, 0xca, 0xb3, 0x62, 0x98, 0x87, 0xb8, 0x70, - 0x32, 0x0e, 0xb9, 0x0b, 0xeb, 0x73, 0x37, 0x48, 0x51, 0x6b, 0xe9, 0xd2, 0x4e, 0xd7, 0x29, 0x7e, - 0x8c, 0x3d, 0x68, 0x1f, 0xe2, 0x82, 0xc8, 0xb0, 0xfe, 0xf6, 0xf4, 0xf5, 0xd8, 0x51, 0xd7, 0x08, - 0xc0, 0xc6, 0x89, 0x33, 0x7e, 0x75, 0xf0, 0x41, 0x95, 0x88, 0x02, 0xb7, 0xed, 0x93, 0x93, 0xc9, - 0xf8, 0xdd, 0x81, 0xda, 0xca, 0x0a, 0xd9, 0xcf, 0xc1, 0x48, 0x6d, 0x1b, 0x5f, 0x5b, 0xd0, 0xb5, - 0x19, 0x8b, 0x52, 0xe6, 0x61, 0x88, 0x4c, 0x90, 0x1e, 0xb4, 0xa8, 0x9f, 0xf7, 0x21, 0x3b, 0x2d, - 0xea, 0x93, 0xc7, 0xd0, 0xe5, 0x98, 0xcc, 0xa9, 0x87, 0x13, 0xe6, 0x86, 0xc5, 0xa1, 0xb2, 0xa3, - 0x94, 0xd8, 0xb1, 0x1b, 0x22, 0x79, 0x02, 0x5b, 0x15, 0x65, 0x8e, 0x09, 0xa7, 0x11, 0xd3, 0xda, - 0x39, 0xab, 0x57, 0xc2, 0xef, 0x0b, 0x94, 0xe8, 0xa0, 0xf8, 0xc8, 0xbd, 0x84, 0xc6, 0x22, 0x23, - 0xdd, 0x2a, 0xac, 0x1a, 0x10, 0x79, 0x04, 0x0a, 0x43, 0x31, 0x71, 0x7d, 0x3f, 0x41, 0xce, 0x35, - 0x25, 0x67, 0x00, 0x43, 0x61, 0x17, 0x08, 0x79, 0x08, 0x10, 0xa7, 0xd3, 0x80, 0x7a, 0x93, 0x2c, - 0xae, 0x6e, 0x5e, 0x97, 0x0b, 0x24, 0x1b, 0x5f, 0x07, 0xc5, 0xc3, 0x44, 0xd0, 0x33, 0xea, 0xb9, - 0x02, 0xb5, 0xcd, 0xe2, 0x84, 0x06, 0x44, 0x2c, 0xe8, 0x84, 0x65, 0xa4, 0xda, 0xb6, 0xde, 0xde, - 0x51, 0x86, 0x77, 0x6e, 0x48, 0xdb, 0xa9, 0x49, 0xc6, 0x10, 0x36, 0xf7, 0x51, 0xd8, 0x41, 0xe0, - 0xe0, 0xc7, 0x14, 0xb9, 0xb8, 0x96, 0x88, 0x74, 0x2d, 0x11, 0xe3, 0x05, 0xc0, 0x3e, 0x8a, 0x4a, - 0xf0, 0xf7, 0x91, 0x1a, 0x29, 0x6c, 0xd5, 0xad, 0xfc, 0xb3, 0xcb, 0xd2, 0xac, 0x59, 0x94, 0x7f, - 0x9c, 0xf5, 0x0d, 0x6c, 0x37, 0x1f, 0x03, 0x77, 0x90, 0xc7, 0x11, 0xe3, 0x48, 0x76, 0xa1, 0x53, - 0x1a, 0x73, 0x4d, 0xca, 0x53, 0x6b, 0xbe, 0xd1, 0xa6, 0xc6, 0xa9, 0x89, 0xc3, 0xef, 0x2d, 0x90, - 0x47, 0x15, 0x89, 0x3c, 0x83, 0x4e, 0xc5, 0x23, 0xab, 0xc4, 0xfd, 0x7b, 0x66, 0xb1, 0x31, 0x66, - 0xb5, 0x31, 0xe6, 0x38, 0xdb, 0x18, 0x32, 0x82, 0x8d, 0xe2, 0x12, 0x88, 0xd6, 0x90, 0x2e, 0xdd, - 0x4b, 0x5f, 0x5f, 0x61, 0x7a, 0x35, 0xc5, 0x1e, 0xb4, 0xf7, 0x51, 0x90, 0xed, 0x65, 0x8b, 0x4a, - 0xbf, 0xaa, 0x29, 0x62, 0x83, 0x62, 0xfb, 0x7e, 0xbd, 0xaa, 0xfd, 0x9b, 0x32, 0x2c, 0x3d, 0x56, - 0xf7, 0xdf, 0x1b, 0x61, 0x80, 0x02, 0xff, 0xc7, 0x65, 0x48, 0x40, 0xad, 0xf3, 0x3c, 0x72, 0x99, - 0x3b, 0xc3, 0xe4, 0xa5, 0xfa, 0xed, 0x72, 0x20, 0xfd, 0xb8, 0x1c, 0x48, 0x3f, 0x2f, 0x07, 0xd2, - 0x97, 0x5f, 0x83, 0xb5, 0xe9, 0x46, 0xae, 0xda, 0xfd, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x11, 0x16, - 0x9b, 0xee, 0xc7, 0x04, 0x00, 0x00, + // 574 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xd3, 0x40, + 0x10, 0xae, 0x13, 0x5a, 0xe2, 0x71, 0x9a, 0x46, 0x0b, 0x05, 0x2b, 0x88, 0x60, 0x7c, 0xa1, 0x1c, + 0xb0, 0xa5, 0x54, 0x3d, 0x21, 0x54, 0x19, 0x25, 0x94, 0xaa, 0xb4, 0x54, 0x56, 0x41, 0x88, 0x4b, + 0xe4, 0xd8, 0xd3, 0x74, 0x55, 0x7b, 0x6d, 0xbc, 0xeb, 0xa0, 0x5c, 0x79, 0x0a, 0x6e, 0xbc, 0x0e, + 0x27, 0xc4, 0x23, 0xa0, 0xf2, 0x22, 0xc8, 0xbf, 0x75, 0xd4, 0x46, 0xa8, 0x70, 0xf3, 0x7e, 0xf3, + 0xcd, 0x37, 0x33, 0xdf, 0xee, 0x18, 0x5e, 0x4c, 0xa9, 0x38, 0x4b, 0x26, 0x86, 0x1b, 0x06, 0xe6, + 0xc9, 0x19, 0x9e, 0x9c, 0x51, 0x36, 0xe5, 0x47, 0x28, 0x3e, 0x87, 0xf1, 0xb9, 0x29, 0x04, 0x33, + 0x9d, 0x88, 0x9a, 0x1e, 0xe5, 0x6e, 0x38, 0xc3, 0x78, 0x7e, 0xf9, 0x65, 0x44, 0x71, 0x28, 0x42, + 0x22, 0x57, 0x40, 0xef, 0xc1, 0x34, 0x0c, 0xa7, 0x3e, 0x9a, 0x59, 0x60, 0x92, 0x9c, 0x9a, 0x18, + 0x44, 0xa2, 0xe0, 0xe9, 0x5f, 0x24, 0x68, 0x1d, 0xa2, 0x70, 0x3c, 0x47, 0x38, 0xe4, 0x29, 0x34, + 0xcf, 0x71, 0xae, 0x4a, 0x9a, 0xb4, 0xd5, 0x19, 0xdc, 0x37, 0x2e, 0x35, 0x4b, 0x86, 0x71, 0x80, + 0x73, 0x3b, 0xe5, 0x90, 0xbb, 0xb0, 0x3a, 0x73, 0xfc, 0x04, 0xd5, 0x86, 0x26, 0x6d, 0xb5, 0xed, + 0xfc, 0xa0, 0xef, 0x40, 0xf3, 0x00, 0xe7, 0x44, 0x86, 0xd5, 0xb7, 0x27, 0xaf, 0x47, 0x76, 0x77, + 0x85, 0x00, 0xac, 0x1d, 0xdb, 0xa3, 0x57, 0xfb, 0x1f, 0xba, 0x12, 0x51, 0xe0, 0xb6, 0x75, 0x7c, + 0x3c, 0x1e, 0xbd, 0xdb, 0xef, 0x36, 0xd2, 0x40, 0x7a, 0xd8, 0x1f, 0x76, 0x9b, 0xfa, 0xb7, 0x06, + 0xb4, 0x2d, 0xc6, 0xc2, 0x84, 0xb9, 0x18, 0x20, 0x13, 0xa4, 0x03, 0x0d, 0xea, 0x65, 0x7d, 0xc8, + 0x76, 0x83, 0x7a, 0xe4, 0x31, 0xb4, 0x39, 0xc6, 0x33, 0xea, 0xe2, 0x98, 0x39, 0x41, 0x5e, 0x54, + 0xb6, 0x95, 0x02, 0x3b, 0x72, 0x02, 0x24, 0x4f, 0x60, 0xa3, 0xa4, 0xcc, 0x30, 0xe6, 0x34, 0x64, + 0x6a, 0x33, 0x63, 0x75, 0x0a, 0xf8, 0x7d, 0x8e, 0x12, 0x0d, 0x14, 0x0f, 0xb9, 0x1b, 0xd3, 0x48, + 0xa4, 0xa4, 0x5b, 0xb9, 0x54, 0x0d, 0x22, 0x8f, 0x40, 0x61, 0x28, 0xc6, 0x8e, 0xe7, 0xc5, 0xc8, + 0xb9, 0xaa, 0x64, 0x0c, 0x60, 0x28, 0xac, 0x1c, 0x21, 0x0f, 0x01, 0xa2, 0x64, 0xe2, 0x53, 0x77, + 0x9c, 0xda, 0xd5, 0xce, 0xe2, 0x72, 0x8e, 0xa4, 0xe3, 0x6b, 0xa0, 0xb8, 0x18, 0x0b, 0x7a, 0x4a, + 0x5d, 0x47, 0xa0, 0xba, 0x9e, 0x57, 0xa8, 0x41, 0xc4, 0x84, 0x56, 0x50, 0x58, 0xaa, 0x6e, 0x6a, + 0xcd, 0x2d, 0x65, 0x70, 0xe7, 0x1a, 0xb7, 0xed, 0x8a, 0xa4, 0x0f, 0x60, 0x7d, 0x0f, 0x85, 0xe5, + 0xfb, 0x36, 0x7e, 0x4a, 0x90, 0x8b, 0x2b, 0x8e, 0x48, 0x57, 0x1c, 0xd1, 0x77, 0x01, 0xf6, 0x50, + 0x94, 0x09, 0x37, 0xb7, 0x54, 0x4f, 0x60, 0xa3, 0x6a, 0xe5, 0x9f, 0x55, 0x16, 0x66, 0x4d, 0xad, + 0xfc, 0xeb, 0xac, 0x6f, 0x60, 0xb3, 0xfe, 0x18, 0xb8, 0x8d, 0x3c, 0x0a, 0x19, 0x47, 0xb2, 0x0d, + 0xad, 0x42, 0x98, 0xab, 0x52, 0xe6, 0x5a, 0xfd, 0x8d, 0xd6, 0x73, 0xec, 0x8a, 0x38, 0xf8, 0xd1, + 0x00, 0x79, 0x58, 0x92, 0xc8, 0x73, 0x68, 0x95, 0x3c, 0xb2, 0x2c, 0xb9, 0x77, 0xcf, 0xc8, 0x37, + 0xc6, 0x28, 0x37, 0xc6, 0x18, 0xa5, 0x1b, 0x43, 0x86, 0xb0, 0x96, 0x5f, 0x02, 0x51, 0x6b, 0xa9, + 0x0b, 0xf7, 0xd2, 0xd3, 0x96, 0x88, 0x5e, 0x4e, 0xb1, 0x03, 0xcd, 0x3d, 0x14, 0x64, 0x73, 0x51, + 0xa2, 0xcc, 0x5f, 0xd6, 0x14, 0xb1, 0x40, 0xb1, 0x3c, 0xaf, 0x5a, 0xd5, 0xde, 0x75, 0x1e, 0x16, + 0x1a, 0xcb, 0xfb, 0xef, 0x0c, 0xd1, 0x47, 0x81, 0xff, 0xa3, 0x32, 0x20, 0xd0, 0xad, 0xfc, 0x3c, + 0x74, 0x98, 0x33, 0xc5, 0xf8, 0xe5, 0xee, 0xf7, 0x8b, 0xbe, 0xf4, 0xf3, 0xa2, 0x2f, 0xfd, 0xba, + 0xe8, 0x4b, 0x5f, 0x7f, 0xf7, 0x57, 0x3e, 0x3e, 0xbb, 0xd1, 0xef, 0x6b, 0xb2, 0x96, 0x15, 0xd9, + 0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x43, 0xf2, 0x4d, 0xa5, 0xf6, 0x04, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index d260e64cf..fce06e785 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -7,6 +7,8 @@ import "google/protobuf/empty.proto"; package discovery; +option go_package = "github.com/TheThingsNetwork/ttn/api/discovery"; + message Metadata { enum Key { OTHER = 0; diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index fb0ef7936..1c29fc441 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -1573,42 +1573,42 @@ func init() { } var fileDescriptorGateway = []byte{ - // 582 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x53, 0x5d, 0x6f, 0xd3, 0x3c, - 0x14, 0x7e, 0xdd, 0x76, 0x1f, 0x75, 0xd7, 0x6d, 0xf2, 0xb6, 0xbe, 0x66, 0x82, 0x12, 0x8a, 0x84, - 0xc2, 0xd7, 0x2a, 0x81, 0xb8, 0xe0, 0x96, 0x81, 0xd0, 0x2e, 0x60, 0xc8, 0xdb, 0x7d, 0xe4, 0x25, - 0x4e, 0x6a, 0x35, 0xb5, 0x83, 0xe3, 0xac, 0x1d, 0xbf, 0x84, 0x9f, 0xc4, 0x25, 0x12, 0x7f, 0x00, - 0x0d, 0x89, 0x9f, 0x81, 0x90, 0x4f, 0x93, 0x2c, 0x43, 0x82, 0x5d, 0xf5, 0x3c, 0xcf, 0x73, 0x4e, - 0xce, 0xc7, 0xe3, 0xe2, 0x97, 0x89, 0xb4, 0x93, 0xe2, 0xec, 0x20, 0xd4, 0xb3, 0xf1, 0xe9, 0x44, - 0x9c, 0x4e, 0xa4, 0x4a, 0xf2, 0xf7, 0xc2, 0xce, 0xb5, 0x99, 0x8e, 0xad, 0x55, 0x63, 0x9e, 0xc9, - 0x71, 0xc2, 0xad, 0x98, 0xf3, 0x8b, 0xea, 0xf7, 0x20, 0x33, 0xda, 0x6a, 0xb2, 0x56, 0xc2, 0xfd, - 0xa7, 0x8d, 0x6f, 0x24, 0x3a, 0xd1, 0x63, 0xd0, 0xcf, 0x8a, 0x18, 0x10, 0x00, 0x88, 0x96, 0x75, - 0xa3, 0x39, 0xee, 0xbd, 0xfd, 0x70, 0xf2, 0x4e, 0x58, 0x1e, 0x71, 0xcb, 0x09, 0xc1, 0x1d, 0x2b, - 0x67, 0x82, 0x22, 0x0f, 0xf9, 0x6d, 0x06, 0x31, 0xd9, 0xc7, 0xeb, 0x29, 0xb7, 0xd2, 0x16, 0x91, - 0xa0, 0x2d, 0x0f, 0xf9, 0x2d, 0x56, 0x63, 0x72, 0x1b, 0x77, 0x53, 0xad, 0x92, 0xa5, 0xd8, 0x06, - 0xf1, 0x8a, 0x70, 0x95, 0x3c, 0x2d, 0x2b, 0x3b, 0x1e, 0xf2, 0x57, 0x58, 0x8d, 0x47, 0xbf, 0x10, - 0xc6, 0x6c, 0x51, 0x37, 0xbe, 0x83, 0x71, 0xb9, 0x41, 0x20, 0x23, 0x68, 0xdf, 0x65, 0xdd, 0x92, - 0x39, 0x8a, 0x5c, 0x1f, 0x37, 0x4b, 0x6e, 0xf9, 0x2c, 0xa3, 0x3d, 0x0f, 0xf9, 0x7d, 0x76, 0x45, - 0xd4, 0x53, 0x6f, 0x34, 0xa6, 0xbe, 0x85, 0xd7, 0x4d, 0x1c, 0x84, 0x13, 0x2e, 0x15, 0xdd, 0x83, - 0x82, 0x35, 0x13, 0x1f, 0x3a, 0x48, 0x28, 0x5e, 0x0b, 0x27, 0x5c, 0x29, 0x91, 0xd2, 0xc1, 0x52, - 0x29, 0xa1, 0x6b, 0x13, 0x1b, 0xf1, 0xb1, 0x10, 0x2a, 0xbc, 0xa0, 0x77, 0x3d, 0xe4, 0x77, 0xd8, - 0x15, 0xe1, 0xda, 0x98, 0x3c, 0x97, 0xd4, 0x83, 0x3d, 0x21, 0x26, 0xdb, 0xb8, 0x9d, 0x2b, 0x43, - 0xef, 0x01, 0xe5, 0x42, 0xf2, 0x00, 0xb7, 0x93, 0x2c, 0xa7, 0x0f, 0x3d, 0xe4, 0xf7, 0x9e, 0xed, - 0x1e, 0x54, 0x36, 0x35, 0xae, 0xcc, 0x5c, 0xc2, 0xe8, 0x27, 0xc2, 0x5b, 0xa7, 0x8b, 0x43, 0xad, - 0x62, 0x99, 0x14, 0x86, 0x5b, 0xa9, 0xd5, 0x0d, 0x6b, 0xfe, 0x63, 0xa5, 0x6b, 0x83, 0x0f, 0xfe, - 0x1c, 0x7c, 0x17, 0xaf, 0x64, 0x7a, 0x2e, 0x0c, 0xfd, 0x1f, 0x4c, 0x58, 0x02, 0xf2, 0x02, 0x0f, - 0x32, 0x9d, 0x72, 0x23, 0x3f, 0x41, 0xf3, 0x40, 0xaa, 0x73, 0x61, 0x72, 0xa9, 0x15, 0x6c, 0xbe, - 0xce, 0xf6, 0x9a, 0xea, 0x51, 0x25, 0x92, 0x31, 0xde, 0xa9, 0xbf, 0x1c, 0x44, 0xe2, 0x5c, 0x82, - 0x0e, 0x47, 0xe9, 0x33, 0x52, 0x4b, 0xaf, 0x2b, 0x65, 0xf4, 0xad, 0x85, 0x57, 0x4f, 0x2c, 0xb7, - 0x45, 0x7e, 0x7d, 0x3f, 0xf4, 0x37, 0x1b, 0x5b, 0x0d, 0x1b, 0x37, 0x71, 0x4b, 0xba, 0x53, 0xb4, - 0xfd, 0x2e, 0x6b, 0xc9, 0xcc, 0x3d, 0xa9, 0x2c, 0xe5, 0x36, 0xd6, 0x66, 0x06, 0x76, 0x77, 0x59, - 0x8d, 0xc9, 0x7d, 0xdc, 0x0f, 0xb5, 0xb2, 0x3c, 0xb4, 0x81, 0x98, 0x71, 0x99, 0xd2, 0x3e, 0x24, - 0x6c, 0x94, 0xe4, 0x1b, 0xc7, 0x11, 0x0f, 0xf7, 0x22, 0x91, 0x87, 0x46, 0x66, 0x30, 0xf6, 0x26, - 0xa4, 0x34, 0x29, 0x32, 0xc0, 0xab, 0x46, 0x24, 0x4e, 0xdc, 0x02, 0xb1, 0x44, 0x95, 0xb1, 0x7b, - 0x37, 0x18, 0xeb, 0x9e, 0x84, 0xb1, 0x16, 0x8e, 0xd8, 0x67, 0x2e, 0x24, 0x3b, 0x78, 0xc5, 0x2c, - 0x02, 0xa9, 0xe0, 0x51, 0xf4, 0x59, 0xc7, 0x2c, 0x8e, 0x54, 0x49, 0xea, 0x29, 0x7d, 0x54, 0x91, - 0xc7, 0x53, 0x47, 0x5a, 0xc8, 0x7c, 0xbc, 0x24, 0x6d, 0x99, 0x69, 0x21, 0xf3, 0x49, 0x45, 0x1e, - 0x4f, 0x5f, 0x6d, 0x7f, 0xb9, 0x1c, 0xa2, 0xaf, 0x97, 0x43, 0xf4, 0xfd, 0x72, 0x88, 0x3e, 0xff, - 0x18, 0xfe, 0x77, 0xb6, 0x0a, 0xff, 0xe8, 0xe7, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0x31, 0xae, - 0xe0, 0x33, 0x46, 0x04, 0x00, 0x00, + // 590 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0x4d, 0x6f, 0xd3, 0x30, + 0x18, 0xc6, 0x6d, 0xf7, 0x51, 0x77, 0xdd, 0x90, 0xb7, 0x15, 0x33, 0x41, 0x09, 0x45, 0x42, 0x81, + 0xc1, 0x2a, 0x81, 0x38, 0x70, 0xe0, 0xc2, 0x40, 0x68, 0x07, 0x18, 0xf2, 0x76, 0xe2, 0x52, 0x79, + 0x89, 0x93, 0x5a, 0x4d, 0xed, 0xe0, 0x38, 0x6b, 0xc7, 0x2f, 0xe1, 0x27, 0x71, 0x44, 0xe2, 0x0f, + 0xa0, 0x21, 0xf1, 0x33, 0x10, 0xf2, 0xdb, 0x24, 0xcb, 0x90, 0x60, 0xe2, 0xd4, 0xf7, 0x79, 0x9e, + 0xd7, 0x7e, 0x3f, 0x1e, 0x37, 0xf8, 0x79, 0x2c, 0xed, 0x38, 0x3f, 0xd9, 0x0b, 0xf4, 0x74, 0x78, + 0x3c, 0x16, 0xc7, 0x63, 0xa9, 0xe2, 0xec, 0x9d, 0xb0, 0x33, 0x6d, 0x26, 0x43, 0x6b, 0xd5, 0x90, + 0xa7, 0x72, 0x18, 0x73, 0x2b, 0x66, 0xfc, 0xac, 0xfc, 0xdd, 0x4b, 0x8d, 0xb6, 0x9a, 0xac, 0x14, + 0x70, 0xe7, 0x71, 0xed, 0x8e, 0x58, 0xc7, 0x7a, 0x08, 0xfa, 0x49, 0x1e, 0x01, 0x02, 0x00, 0xd1, + 0xe2, 0xdc, 0x60, 0x86, 0x3b, 0x6f, 0xde, 0x1f, 0xbd, 0x15, 0x96, 0x87, 0xdc, 0x72, 0x42, 0x70, + 0xcb, 0xca, 0xa9, 0xa0, 0xc8, 0x43, 0x7e, 0x93, 0x41, 0x4c, 0x76, 0xf0, 0x6a, 0xc2, 0xad, 0xb4, + 0x79, 0x28, 0x68, 0xc3, 0x43, 0x7e, 0x83, 0x55, 0x98, 0xdc, 0xc2, 0xed, 0x44, 0xab, 0x78, 0x21, + 0x36, 0x41, 0xbc, 0x20, 0xdc, 0x49, 0x9e, 0x14, 0x27, 0x5b, 0x1e, 0xf2, 0x97, 0x58, 0x85, 0x07, + 0xbf, 0x10, 0xc6, 0x6c, 0x5e, 0x15, 0xbe, 0x8d, 0x71, 0x31, 0xc1, 0x48, 0x86, 0x50, 0xbe, 0xcd, + 0xda, 0x05, 0x73, 0x10, 0xba, 0x3a, 0xae, 0x97, 0xcc, 0xf2, 0x69, 0x4a, 0x3b, 0x1e, 0xf2, 0xbb, + 0xec, 0x82, 0xa8, 0xba, 0x5e, 0xab, 0x75, 0x7d, 0x13, 0xaf, 0x9a, 0x68, 0x14, 0x8c, 0xb9, 0x54, + 0x74, 0x1b, 0x0e, 0xac, 0x98, 0x68, 0xdf, 0x41, 0x42, 0xf1, 0x4a, 0x30, 0xe6, 0x4a, 0x89, 0x84, + 0xf6, 0x16, 0x4a, 0x01, 0x5d, 0x99, 0xc8, 0x88, 0x8f, 0xb9, 0x50, 0xc1, 0x19, 0xbd, 0xe3, 0x21, + 0xbf, 0xc5, 0x2e, 0x08, 0x57, 0xc6, 0x64, 0x99, 0xa4, 0x1e, 0xcc, 0x09, 0x31, 0xb9, 0x8e, 0x9b, + 0x99, 0x32, 0xf4, 0x2e, 0x50, 0x2e, 0x24, 0xf7, 0x71, 0x33, 0x4e, 0x33, 0xfa, 0xc0, 0x43, 0x7e, + 0xe7, 0xc9, 0xd6, 0x5e, 0x69, 0x53, 0x6d, 0xcb, 0xcc, 0x25, 0x0c, 0x7e, 0x22, 0xbc, 0x71, 0x3c, + 0xdf, 0xd7, 0x2a, 0x92, 0x71, 0x6e, 0xb8, 0x95, 0x5a, 0x5d, 0x31, 0xe6, 0x3f, 0x46, 0xba, 0xd4, + 0x78, 0xef, 0xcf, 0xc6, 0xb7, 0xf0, 0x52, 0xaa, 0x67, 0xc2, 0xd0, 0x1b, 0x60, 0xc2, 0x02, 0x90, + 0x67, 0xb8, 0x97, 0xea, 0x84, 0x1b, 0xf9, 0x09, 0x8a, 0x8f, 0xa4, 0x3a, 0x15, 0x26, 0x93, 0x5a, + 0xc1, 0xe4, 0xab, 0x6c, 0xbb, 0xae, 0x1e, 0x94, 0x22, 0x19, 0xe2, 0xcd, 0xea, 0xe6, 0x51, 0x28, + 0x4e, 0x25, 0xe8, 0xb0, 0x94, 0x2e, 0x23, 0x95, 0xf4, 0xaa, 0x54, 0x06, 0xdf, 0x1a, 0x78, 0xf9, + 0xc8, 0x72, 0x9b, 0x67, 0x97, 0xe7, 0x43, 0x7f, 0xb3, 0xb1, 0x51, 0xb3, 0x71, 0x1d, 0x37, 0xa4, + 0x5b, 0x45, 0xd3, 0x6f, 0xb3, 0x86, 0x4c, 0xdd, 0x93, 0x4a, 0x13, 0x6e, 0x23, 0x6d, 0xa6, 0x60, + 0x77, 0x9b, 0x55, 0x98, 0xdc, 0xc3, 0xdd, 0x40, 0x2b, 0xcb, 0x03, 0x3b, 0x12, 0x53, 0x2e, 0x13, + 0xda, 0x85, 0x84, 0xb5, 0x82, 0x7c, 0xed, 0x38, 0xe2, 0xe1, 0x4e, 0x28, 0xb2, 0xc0, 0xc8, 0x14, + 0xda, 0x5e, 0x87, 0x94, 0x3a, 0x45, 0x7a, 0x78, 0xd9, 0x88, 0xd8, 0x89, 0x1b, 0x20, 0x16, 0xa8, + 0x34, 0x76, 0xfb, 0x0a, 0x63, 0xdd, 0x93, 0x30, 0xd6, 0xc2, 0x12, 0xbb, 0xcc, 0x85, 0x64, 0x13, + 0x2f, 0x99, 0xf9, 0x48, 0x2a, 0x78, 0x14, 0x5d, 0xd6, 0x32, 0xf3, 0x03, 0x55, 0x90, 0x7a, 0x42, + 0x1f, 0x96, 0xe4, 0xe1, 0xc4, 0x91, 0x16, 0x32, 0x77, 0x17, 0xa4, 0x2d, 0x32, 0x2d, 0x64, 0x3e, + 0x2a, 0xc9, 0xc3, 0xc9, 0xcb, 0x17, 0x5f, 0xce, 0xfb, 0xe8, 0xeb, 0x79, 0x1f, 0x7d, 0x3f, 0xef, + 0xa3, 0xcf, 0x3f, 0xfa, 0xd7, 0x3e, 0xec, 0xfe, 0xc7, 0xd7, 0xe3, 0x64, 0x19, 0xfe, 0xfe, 0x4f, + 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x82, 0xbf, 0x95, 0x20, 0x73, 0x04, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 7d69921b0..32878a56c 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -7,6 +7,8 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; package gateway; +option go_package = "github.com/TheThingsNetwork/ttn/api/gateway"; + message GPSMetadata { int64 time = 1; float latitude = 2; diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 7d65d9766..9afbb0222 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -3199,63 +3199,67 @@ func init() { } var fileDescriptorHandler = []byte{ - // 921 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x55, 0x51, 0x6f, 0x1b, 0x45, - 0x10, 0xee, 0x35, 0xc4, 0x89, 0xc7, 0x89, 0x93, 0x6e, 0xda, 0x70, 0xb8, 0x95, 0x15, 0x0e, 0xa9, - 0x0a, 0x82, 0x9e, 0x85, 0xa9, 0x14, 0x10, 0x12, 0x34, 0xad, 0x49, 0x1b, 0x41, 0xa8, 0x74, 0x09, - 0x0f, 0xf0, 0x40, 0xb4, 0xf1, 0x4e, 0xec, 0x55, 0xce, 0xb7, 0xcb, 0xed, 0xda, 0x91, 0xf9, 0x1d, - 0x3c, 0xf0, 0x87, 0x2a, 0xf1, 0xc8, 0x3b, 0x0f, 0xa0, 0xf0, 0x47, 0xd0, 0xed, 0xee, 0x9d, 0x2f, - 0xb6, 0xd3, 0x24, 0x88, 0xa7, 0xbb, 0x99, 0xf9, 0xe6, 0xdb, 0xfd, 0x76, 0x76, 0x66, 0xe1, 0xf3, - 0x1e, 0xd7, 0xfd, 0xe1, 0x49, 0xd8, 0x15, 0x83, 0xd6, 0x51, 0x1f, 0x8f, 0xfa, 0x3c, 0xe9, 0xa9, - 0xef, 0x50, 0x9f, 0x8b, 0xf4, 0xac, 0xa5, 0x75, 0xd2, 0xa2, 0x92, 0xb7, 0xfa, 0x34, 0x61, 0x31, - 0xa6, 0xf9, 0x37, 0x94, 0xa9, 0xd0, 0x82, 0x2c, 0x39, 0xb3, 0xf1, 0xb0, 0x27, 0x44, 0x2f, 0xc6, - 0x96, 0x71, 0x9f, 0x0c, 0x4f, 0x5b, 0x38, 0x90, 0x7a, 0x6c, 0x51, 0x8d, 0x27, 0x37, 0x59, 0x80, - 0x4a, 0xee, 0xe0, 0x3b, 0x37, 0x81, 0x9f, 0xa4, 0xe2, 0x0c, 0x53, 0xf7, 0x71, 0x89, 0x5f, 0xdc, - 0x24, 0xd1, 0x40, 0xbb, 0x22, 0x2e, 0x7e, 0x5c, 0xf2, 0xee, 0xad, 0x92, 0x63, 0x91, 0xd2, 0x73, - 0x9a, 0xb4, 0x18, 0x8e, 0x78, 0x17, 0x2d, 0x45, 0xf0, 0xa7, 0x07, 0x7e, 0xc7, 0x38, 0x76, 0xbb, - 0x9a, 0x8f, 0xa8, 0xe6, 0x22, 0x89, 0x50, 0x49, 0x91, 0x28, 0x24, 0x3e, 0x2c, 0x49, 0x3a, 0x8e, - 0x05, 0x65, 0xbe, 0xb7, 0xe5, 0x6d, 0xaf, 0x44, 0xb9, 0x49, 0x1e, 0x40, 0x85, 0x4a, 0x79, 0xcc, - 0x99, 0x7f, 0x77, 0xcb, 0xdb, 0xae, 0x46, 0x8b, 0x54, 0xca, 0x7d, 0x46, 0xbe, 0x82, 0x35, 0x26, - 0xce, 0x93, 0x98, 0x27, 0x67, 0xc7, 0x42, 0x66, 0x5c, 0x7e, 0x6d, 0xcb, 0xdb, 0xae, 0xb5, 0x37, - 0x43, 0xa7, 0xba, 0xe3, 0xc2, 0xaf, 0x4d, 0x34, 0xaa, 0xb3, 0x4b, 0x36, 0x39, 0x80, 0x0d, 0x5a, - 0xec, 0xe3, 0x78, 0x80, 0x9a, 0x32, 0xaa, 0xa9, 0xff, 0xae, 0x21, 0x79, 0x14, 0x16, 0xfa, 0x27, - 0x9b, 0x3d, 0x70, 0x98, 0x88, 0xd0, 0x19, 0x5f, 0xb0, 0x06, 0xab, 0x87, 0x9a, 0xea, 0xa1, 0x8a, - 0xf0, 0xe7, 0x21, 0x2a, 0x1d, 0xfc, 0xe5, 0x41, 0xc5, 0x7a, 0xc8, 0x36, 0x54, 0xd4, 0x58, 0x69, - 0x1c, 0x18, 0x6d, 0xb5, 0xf6, 0x7a, 0x98, 0x95, 0xf3, 0xd0, 0xb8, 0x32, 0x88, 0x8a, 0x5c, 0x9c, - 0x7c, 0x02, 0xd5, 0xae, 0x18, 0x48, 0x91, 0x60, 0xa2, 0x8d, 0xde, 0x5a, 0x7b, 0xc3, 0x80, 0x5f, - 0xe4, 0x5e, 0x8b, 0x9f, 0xa0, 0x48, 0x00, 0x95, 0xa1, 0xcc, 0x74, 0x39, 0xfd, 0x60, 0xf0, 0x11, - 0xd5, 0xa8, 0x22, 0x17, 0x21, 0x8f, 0x61, 0x39, 0x57, 0xef, 0xaf, 0xcc, 0xa0, 0x8a, 0x18, 0xf9, - 0x18, 0x6a, 0x13, 0x69, 0xca, 0x5f, 0x9d, 0x81, 0x96, 0xc3, 0x41, 0x08, 0x0f, 0x76, 0xa5, 0x8c, - 0x79, 0xd7, 0xd8, 0xfb, 0x0c, 0x13, 0xcd, 0x4f, 0x39, 0xa6, 0xa5, 0x92, 0x79, 0xa5, 0x92, 0x05, - 0xbf, 0x7a, 0x50, 0x2b, 0x25, 0x5c, 0x01, 0xcb, 0xae, 0x02, 0xc3, 0xae, 0x60, 0x98, 0xba, 0x8a, - 0xe7, 0x26, 0x79, 0x94, 0x9d, 0x4e, 0x32, 0xc2, 0x54, 0x63, 0xea, 0x2f, 0x98, 0xd8, 0xc4, 0x91, - 0x45, 0x47, 0x34, 0xe6, 0x8c, 0x6a, 0x91, 0xfa, 0xef, 0xd8, 0x68, 0xe1, 0xc8, 0x58, 0x31, 0xb1, - 0xac, 0x8b, 0x96, 0xd5, 0x99, 0xc1, 0x33, 0x58, 0xb7, 0xd7, 0xf2, 0x5a, 0x05, 0x99, 0x9b, 0xe1, - 0xa8, 0x74, 0x17, 0x19, 0x8e, 0xf6, 0x59, 0xf0, 0x0b, 0x54, 0x2c, 0xc3, 0xed, 0xf2, 0xc8, 0x67, - 0x50, 0x77, 0x9d, 0x72, 0x6c, 0x3b, 0xc5, 0x88, 0xaa, 0xb5, 0xd7, 0x42, 0xe7, 0x0e, 0x2d, 0xed, - 0xab, 0x3b, 0xd1, 0xaa, 0xf3, 0x58, 0xc7, 0xf3, 0x65, 0x43, 0xc8, 0xbb, 0x18, 0xec, 0x00, 0x58, - 0xdf, 0xb7, 0x5c, 0x69, 0xf2, 0x61, 0x76, 0x76, 0x99, 0xa5, 0x7c, 0x6f, 0x6b, 0xc1, 0x50, 0xe5, - 0x23, 0xc9, 0xa2, 0xa2, 0x3c, 0x1e, 0x24, 0x40, 0x3a, 0xe9, 0x38, 0x6f, 0x92, 0x03, 0x54, 0x8a, - 0xf6, 0xde, 0xd6, 0x87, 0x9b, 0x50, 0x39, 0xe5, 0x18, 0x33, 0xe5, 0x34, 0x38, 0x8b, 0x3c, 0x86, - 0x05, 0x2a, 0xa5, 0xdb, 0xf9, 0xfd, 0x62, 0xb9, 0x52, 0xa1, 0xa3, 0x0c, 0x10, 0x1c, 0xc1, 0x7a, - 0x27, 0x1d, 0x7f, 0x2f, 0x6f, 0xb6, 0x9a, 0x63, 0xbd, 0x7b, 0x1d, 0xeb, 0x0f, 0xb0, 0x56, 0xb0, - 0x46, 0xa8, 0x86, 0xb1, 0xfe, 0x0f, 0x12, 0xee, 0xc3, 0xa2, 0xb9, 0x28, 0x46, 0xc4, 0x72, 0x64, - 0x8d, 0xe0, 0x09, 0xdc, 0x2b, 0x1d, 0xd0, 0x75, 0xe4, 0xed, 0x37, 0x1e, 0x2c, 0xbd, 0xb2, 0xdb, - 0x24, 0x3f, 0xc1, 0xc6, 0x64, 0x6c, 0xbc, 0xe8, 0xd3, 0x38, 0xc6, 0xa4, 0x87, 0x24, 0xc8, 0x47, - 0xd3, 0x9c, 0xa0, 0x1b, 0x1b, 0x8d, 0x0f, 0xde, 0x8a, 0x71, 0xd3, 0xf2, 0x47, 0x58, 0x76, 0x61, - 0x24, 0x1f, 0x15, 0xf3, 0x0e, 0xd9, 0xd0, 0x9e, 0x0e, 0xb2, 0xd9, 0x39, 0x6b, 0xd9, 0xdf, 0x9f, - 0xba, 0x0e, 0xb3, 0x93, 0xb8, 0xfd, 0x66, 0x11, 0x48, 0xe9, 0x98, 0x0f, 0x68, 0x42, 0x7b, 0x98, - 0x66, 0xe3, 0x32, 0xc2, 0x1e, 0x57, 0x1a, 0xd3, 0x72, 0x0f, 0x37, 0xe7, 0x95, 0x66, 0xd2, 0x48, - 0x8d, 0xcd, 0xd0, 0x3e, 0x7d, 0x61, 0xfe, 0xf4, 0x85, 0x5f, 0x67, 0x4f, 0x1f, 0xd9, 0x83, 0xfa, - 0x4b, 0xd4, 0xb7, 0x61, 0x9a, 0x7b, 0x09, 0xc8, 0x97, 0x50, 0x3f, 0xbc, 0xcc, 0x33, 0x17, 0x77, - 0xe5, 0x3e, 0xbe, 0x81, 0x7b, 0x1d, 0x8c, 0x51, 0xe3, 0xff, 0x21, 0x6a, 0x07, 0xaa, 0x2f, 0x51, - 0xbb, 0x51, 0xf0, 0xde, 0xd4, 0x51, 0x97, 0xf2, 0xa7, 0x9b, 0x92, 0x3c, 0x85, 0xea, 0x61, 0x91, - 0x38, 0x1d, 0xbd, 0x72, 0xb9, 0x5d, 0x58, 0xb1, 0x7b, 0xbf, 0x7e, 0xc5, 0xab, 0x28, 0x5e, 0x83, - 0x5f, 0xec, 0x58, 0xed, 0x89, 0x5b, 0x95, 0x76, 0x63, 0x6a, 0x39, 0x33, 0x80, 0xf6, 0xa0, 0x56, - 0x6a, 0x1a, 0xf2, 0x70, 0x82, 0x99, 0x99, 0x35, 0x8d, 0xc6, 0xbc, 0xa0, 0xeb, 0xb3, 0x67, 0x50, - 0x2d, 0xfa, 0xba, 0x2c, 0x6c, 0x6a, 0x82, 0x34, 0xfc, 0xd9, 0x90, 0x65, 0x68, 0xef, 0x41, 0xdd, - 0xb5, 0x63, 0x7e, 0x85, 0x9f, 0x9a, 0xf2, 0xb8, 0x37, 0x79, 0xb3, 0x48, 0xbc, 0xf4, 0x6c, 0x97, - 0x6a, 0x63, 0xfd, 0xcf, 0xd7, 0x7f, 0xbf, 0x68, 0x7a, 0x7f, 0x5c, 0x34, 0xbd, 0xbf, 0x2f, 0x9a, - 0xde, 0x6f, 0xff, 0x34, 0xef, 0x9c, 0x54, 0xcc, 0x21, 0x7e, 0xfa, 0x6f, 0x00, 0x00, 0x00, 0xff, - 0xff, 0xf6, 0xa1, 0x00, 0x96, 0x1a, 0x0a, 0x00, 0x00, + // 987 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5f, 0x6f, 0x1b, 0x45, + 0x10, 0xef, 0x25, 0xc4, 0x49, 0xc6, 0xf9, 0xbb, 0x6e, 0xc2, 0xd5, 0x8e, 0x4c, 0x7a, 0x40, 0x15, + 0x28, 0xdc, 0x09, 0x53, 0xa9, 0x80, 0x84, 0x68, 0x5a, 0x93, 0x36, 0x82, 0x50, 0xe9, 0x12, 0x1e, + 0xe8, 0x03, 0xd1, 0xc6, 0x3b, 0xb1, 0x8f, 0x9c, 0x6f, 0x8f, 0xdb, 0xb5, 0x23, 0x83, 0x78, 0xe1, + 0x2b, 0xc0, 0x03, 0xdf, 0x81, 0xcf, 0x81, 0xc4, 0x23, 0x12, 0x8f, 0x3c, 0x80, 0x02, 0x1f, 0x04, + 0xdd, 0xee, 0xde, 0xf9, 0xe2, 0x3f, 0x0d, 0x41, 0x7d, 0xb2, 0x67, 0x7e, 0xbf, 0xfd, 0xcd, 0xce, + 0xce, 0xce, 0xec, 0xc1, 0xfb, 0xed, 0x40, 0x76, 0x7a, 0x27, 0x6e, 0x8b, 0x77, 0xbd, 0xa3, 0x0e, + 0x1e, 0x75, 0x82, 0xa8, 0x2d, 0x3e, 0x43, 0x79, 0xce, 0x93, 0x33, 0x4f, 0xca, 0xc8, 0xa3, 0x71, + 0xe0, 0x75, 0x68, 0xc4, 0x42, 0x4c, 0xb2, 0x5f, 0x37, 0x4e, 0xb8, 0xe4, 0x64, 0xde, 0x98, 0xd5, + 0x5a, 0x9b, 0xf3, 0x76, 0x88, 0x9e, 0x72, 0x9f, 0xf4, 0x4e, 0x3d, 0xec, 0xc6, 0x72, 0xa0, 0x59, + 0xd5, 0x2d, 0x03, 0xa6, 0x3a, 0x34, 0x8a, 0xb8, 0xa4, 0x32, 0xe0, 0x91, 0x30, 0xe8, 0x7a, 0x16, + 0x82, 0xc6, 0x81, 0x71, 0xd5, 0x32, 0xd7, 0x49, 0xc2, 0xcf, 0x30, 0x31, 0x3f, 0x06, 0x7c, 0x25, + 0x03, 0x95, 0xd9, 0xe2, 0x61, 0xfe, 0xc7, 0x10, 0x5e, 0x1f, 0x23, 0x84, 0x3c, 0xa1, 0xe7, 0x34, + 0xf2, 0x18, 0xf6, 0x83, 0x16, 0x6a, 0x9a, 0xf3, 0x87, 0x05, 0x76, 0x53, 0x39, 0x76, 0x5b, 0x32, + 0xe8, 0xab, 0x3d, 0xf9, 0x28, 0x62, 0x1e, 0x09, 0x24, 0x36, 0xcc, 0xc7, 0x74, 0x10, 0x72, 0xca, + 0x6c, 0x6b, 0xdb, 0xda, 0x59, 0xf2, 0x33, 0x93, 0x6c, 0x40, 0x89, 0xc6, 0xf1, 0x71, 0xc0, 0xec, + 0x99, 0x6d, 0x6b, 0x67, 0xd1, 0x9f, 0xa3, 0x71, 0xbc, 0xcf, 0xc8, 0x47, 0xb0, 0xca, 0xf8, 0x79, + 0x14, 0x06, 0xd1, 0xd9, 0x31, 0x8f, 0x53, 0x2d, 0xbb, 0xbc, 0x6d, 0xed, 0x94, 0x1b, 0x9b, 0xae, + 0xd9, 0x7d, 0xd3, 0xc0, 0x4f, 0x15, 0xea, 0xaf, 0xb0, 0x4b, 0x36, 0x39, 0x80, 0x0a, 0xcd, 0xf7, + 0x71, 0xdc, 0x45, 0x49, 0x19, 0x95, 0xd4, 0x7e, 0x59, 0x89, 0x6c, 0xb9, 0x79, 0x8e, 0xc3, 0xcd, + 0x1e, 0x18, 0x8e, 0x4f, 0xe8, 0x98, 0xcf, 0x59, 0x85, 0xe5, 0x43, 0x49, 0x65, 0x4f, 0xf8, 0xf8, + 0x75, 0x0f, 0x85, 0x74, 0xfe, 0xb4, 0xa0, 0xa4, 0x3d, 0x64, 0x07, 0x4a, 0x62, 0x20, 0x24, 0x76, + 0x55, 0x6e, 0xe5, 0xc6, 0x9a, 0x9b, 0x1e, 0xfd, 0xa1, 0x72, 0xa5, 0x14, 0xe1, 0x1b, 0x9c, 0xbc, + 0x03, 0x8b, 0x2d, 0xde, 0x8d, 0x79, 0x84, 0x91, 0x54, 0xf9, 0x96, 0x1b, 0x15, 0x45, 0x7e, 0x94, + 0x79, 0x35, 0x7f, 0xc8, 0x22, 0x0e, 0x94, 0x7a, 0x71, 0x9a, 0x97, 0xc9, 0x1f, 0x14, 0xdf, 0xa7, + 0x12, 0x85, 0x6f, 0x10, 0x72, 0x07, 0x16, 0xb2, 0xec, 0xed, 0xa5, 0x31, 0x56, 0x8e, 0x91, 0xb7, + 0xa0, 0x3c, 0x4c, 0x4d, 0xd8, 0xcb, 0x63, 0xd4, 0x22, 0xec, 0xb8, 0xb0, 0xb1, 0x1b, 0xc7, 0x61, + 0xd0, 0x52, 0xf6, 0x3e, 0xc3, 0x48, 0x06, 0xa7, 0x01, 0x26, 0x85, 0x92, 0x59, 0x85, 0x92, 0x39, + 0x3f, 0x5a, 0x50, 0x2e, 0x2c, 0x98, 0x42, 0x4b, 0xaf, 0x02, 0xc3, 0x16, 0x67, 0x98, 0x98, 0x8a, + 0x67, 0x26, 0xd9, 0x4a, 0x4f, 0x27, 0xea, 0x63, 0x22, 0x31, 0xb1, 0x67, 0x15, 0x36, 0x74, 0xa4, + 0x68, 0x9f, 0x86, 0x01, 0xa3, 0x92, 0x27, 0xf6, 0x4b, 0x1a, 0xcd, 0x1d, 0xa9, 0x2a, 0x46, 0x5a, + 0x75, 0x4e, 0xab, 0x1a, 0xd3, 0x79, 0x00, 0x6b, 0xfa, 0x5a, 0x5e, 0x99, 0x41, 0xea, 0x66, 0xd8, + 0x2f, 0xdc, 0x45, 0x86, 0xfd, 0x7d, 0xe6, 0x7c, 0x03, 0x25, 0xad, 0x70, 0xbd, 0x75, 0xe4, 0x3d, + 0x58, 0x31, 0x9d, 0x72, 0xac, 0x3b, 0x45, 0x25, 0x55, 0x6e, 0xac, 0xba, 0xc6, 0xed, 0x6a, 0xd9, + 0x27, 0x37, 0xfc, 0x65, 0xe3, 0xd1, 0x8e, 0x87, 0x0b, 0x4a, 0x30, 0x68, 0xa1, 0x73, 0x1f, 0x40, + 0xfb, 0x3e, 0x0d, 0x84, 0x24, 0x6f, 0xa4, 0x67, 0x97, 0x5a, 0xc2, 0xb6, 0xb6, 0x67, 0x95, 0x54, + 0x36, 0x40, 0x34, 0xcb, 0xcf, 0x70, 0x27, 0x02, 0xd2, 0x4c, 0x06, 0x59, 0x93, 0x1c, 0xa0, 0x10, + 0xb4, 0xfd, 0xbc, 0x3e, 0xdc, 0x84, 0xd2, 0x69, 0x80, 0x21, 0x13, 0x26, 0x07, 0x63, 0x91, 0x3b, + 0x30, 0x4b, 0xe3, 0xd8, 0xec, 0xfc, 0x66, 0x1e, 0xae, 0x50, 0x68, 0x3f, 0x25, 0x38, 0x47, 0xb0, + 0xd6, 0x4c, 0x06, 0x9f, 0xc7, 0xff, 0x2d, 0x9a, 0x51, 0x9d, 0xb9, 0x4a, 0xf5, 0x0b, 0x58, 0xcd, + 0x55, 0x7d, 0x14, 0xbd, 0x50, 0xfe, 0x8f, 0x14, 0x6e, 0xc2, 0x9c, 0xba, 0x28, 0x2a, 0x89, 0x05, + 0x5f, 0x1b, 0xce, 0xdb, 0xb0, 0x5e, 0x38, 0xa0, 0xab, 0xc4, 0x1b, 0xbf, 0x58, 0x30, 0xff, 0x44, + 0x6f, 0x93, 0x7c, 0x09, 0x95, 0xe1, 0xd8, 0x78, 0xd4, 0xa1, 0x61, 0x88, 0x51, 0x1b, 0x89, 0x93, + 0x8d, 0xa6, 0x09, 0xa0, 0x19, 0x1b, 0xd5, 0x57, 0x9f, 0xcb, 0x31, 0xd3, 0xf2, 0x19, 0x2c, 0x18, + 0x18, 0xc9, 0xdd, 0x7c, 0xde, 0x21, 0xeb, 0xe9, 0xd3, 0x41, 0x36, 0x3e, 0x67, 0xb5, 0xfa, 0xed, + 0x91, 0xeb, 0x30, 0x3e, 0x89, 0x1b, 0x3f, 0x97, 0x80, 0x14, 0x8e, 0xf9, 0x80, 0x46, 0xb4, 0x8d, + 0x09, 0xf9, 0x0a, 0x2a, 0x3e, 0xb6, 0x03, 0x21, 0x31, 0x29, 0xf6, 0x70, 0x7d, 0x52, 0x69, 0x86, + 0x8d, 0x54, 0xdd, 0x74, 0xf5, 0x5b, 0xe4, 0x66, 0x0f, 0x95, 0xfb, 0x71, 0xfa, 0x50, 0x39, 0xb5, + 0xef, 0x7f, 0xff, 0xe7, 0x87, 0x99, 0x0d, 0x67, 0xcd, 0xeb, 0x37, 0x3c, 0x3a, 0x5c, 0x2a, 0x3e, + 0xb0, 0xde, 0x24, 0x01, 0xac, 0x3c, 0x46, 0x79, 0x9d, 0x30, 0x13, 0x6f, 0x88, 0x73, 0x5b, 0x05, + 0xa9, 0x91, 0x5b, 0xa3, 0x41, 0xbc, 0x6f, 0x75, 0x97, 0x7e, 0x47, 0x18, 0xac, 0x1c, 0x5e, 0x0e, + 0x35, 0x51, 0x6a, 0x6a, 0x1e, 0xaf, 0xa9, 0x10, 0x75, 0x67, 0x7a, 0x88, 0x34, 0xa1, 0x4f, 0x60, + 0xbd, 0x89, 0x21, 0x4a, 0x7c, 0x01, 0x47, 0x47, 0xee, 0xc3, 0xe2, 0x63, 0x94, 0x66, 0xe0, 0xdc, + 0x1a, 0x29, 0x68, 0x61, 0xfd, 0x68, 0xeb, 0x93, 0x7b, 0xb0, 0x78, 0x98, 0x2f, 0x1c, 0x45, 0xa7, + 0x86, 0xdb, 0x85, 0x25, 0xbd, 0xf7, 0xab, 0x23, 0x4e, 0x93, 0x78, 0x0a, 0x76, 0xbe, 0x63, 0xb1, + 0xc7, 0xaf, 0x75, 0x81, 0x2a, 0x23, 0xe1, 0xd4, 0x98, 0xdb, 0x83, 0x72, 0xa1, 0x35, 0x49, 0x6d, + 0xc8, 0x19, 0x9b, 0x68, 0xd5, 0xea, 0x24, 0xd0, 0x74, 0xf3, 0x03, 0x58, 0xcc, 0xa7, 0x47, 0x31, + 0xb1, 0x91, 0x39, 0x55, 0xb5, 0xc7, 0x21, 0xad, 0xd0, 0xd8, 0x83, 0x15, 0xd3, 0xf4, 0x59, 0xa3, + 0xdc, 0x53, 0xe5, 0x31, 0x2f, 0xff, 0x66, 0xbe, 0xf0, 0xd2, 0xc7, 0x41, 0xa1, 0x36, 0xda, 0xff, + 0xf0, 0xc3, 0x5f, 0x2f, 0xea, 0xd6, 0x6f, 0x17, 0x75, 0xeb, 0xaf, 0x8b, 0xba, 0xf5, 0xd3, 0xdf, + 0xf5, 0x1b, 0xcf, 0xee, 0x5e, 0xe3, 0x2b, 0xf1, 0xa4, 0xa4, 0x4e, 0xfc, 0xdd, 0x7f, 0x03, 0x00, + 0x00, 0xff, 0xff, 0xf6, 0x20, 0x83, 0x71, 0x5b, 0x0a, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 4b548482f..cb1d01017 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -4,13 +4,15 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; -import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; -import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; -import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto"; +import "ttn/api/api.proto"; +import "ttn/api/broker/broker.proto"; +import "ttn/api/protocol/protocol.proto"; +import "ttn/api/protocol/lorawan/device.proto"; package handler; +option go_package = "github.com/TheThingsNetwork/ttn/api/handler"; + message DeviceActivationResponse { bytes payload = 1; string app_id = 2; diff --git a/api/monitor/monitor.pb.go b/api/monitor/monitor.pb.go index da2fb73c2..e0a26a6cf 100644 --- a/api/monitor/monitor.pb.go +++ b/api/monitor/monitor.pb.go @@ -483,24 +483,25 @@ func init() { } var fileDescriptorMonitor = []byte{ - // 298 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x91, 0x3f, 0x4a, 0xc4, 0x40, - 0x14, 0xc6, 0x8d, 0x85, 0x0b, 0xc3, 0xae, 0x2b, 0x01, 0x15, 0x22, 0xa4, 0xb6, 0x9a, 0x01, 0x2d, - 0x5c, 0xb7, 0x92, 0x65, 0x45, 0x9b, 0xb5, 0xd0, 0xf5, 0x00, 0x13, 0x1d, 0x27, 0x21, 0xc9, 0xbc, - 0x30, 0x79, 0x21, 0x78, 0x13, 0x8f, 0x64, 0xe9, 0x11, 0x24, 0x9e, 0x43, 0x10, 0xe7, 0x4f, 0x4a, - 0x13, 0xab, 0x8f, 0x37, 0xe4, 0xf7, 0x23, 0xdf, 0x7b, 0xe4, 0x52, 0x66, 0x98, 0x36, 0x09, 0x7d, - 0x82, 0x92, 0x6d, 0x53, 0xb1, 0x4d, 0x33, 0x25, 0xeb, 0x3b, 0x81, 0x2d, 0xe8, 0x9c, 0x21, 0x2a, - 0xc6, 0xab, 0x8c, 0x95, 0xa0, 0x32, 0x04, 0xed, 0x93, 0x56, 0x1a, 0x10, 0xc2, 0x89, 0x1b, 0xa3, - 0x51, 0x0e, 0xc9, 0x51, 0xb4, 0xfc, 0xd5, 0xa7, 0x75, 0x44, 0x17, 0x63, 0x50, 0x0d, 0x0d, 0x0a, - 0xed, 0xe2, 0x3f, 0x60, 0xa2, 0x21, 0x17, 0xda, 0x85, 0x03, 0x47, 0xfd, 0x6c, 0xca, 0xd5, 0x73, - 0x21, 0xb4, 0x4f, 0x87, 0x9e, 0x48, 0x00, 0x59, 0x08, 0x66, 0xa6, 0xa4, 0x79, 0x61, 0xa2, 0xac, - 0xd0, 0x35, 0x39, 0xfb, 0xde, 0x25, 0x93, 0x8d, 0x5d, 0x48, 0xb8, 0x24, 0xb3, 0x1b, 0x5b, 0xf3, - 0x01, 0x39, 0x36, 0x75, 0x38, 0xa7, 0xbe, 0xb6, 0x7d, 0x88, 0x8e, 0xa8, 0x75, 0x51, 0xef, 0xa2, - 0xd7, 0xbf, 0xae, 0xd3, 0x20, 0xbc, 0xea, 0xd9, 0xc7, 0xaa, 0xc8, 0x54, 0x1e, 0x1e, 0x52, 0x57, - 0xdc, 0xce, 0x1b, 0x51, 0xd7, 0x5c, 0x8a, 0x3f, 0x0c, 0x6b, 0x32, 0x77, 0x86, 0x35, 0xb4, 0xca, - 0x38, 0x8e, 0xbd, 0xc3, 0xbf, 0x0c, 0x5b, 0x16, 0x64, 0x7a, 0x6f, 0x18, 0x57, 0x61, 0xdf, 0x2b, - 0x06, 0x1b, 0x2c, 0xc8, 0x74, 0x65, 0x36, 0xde, 0x93, 0xee, 0x00, 0x83, 0xe4, 0x92, 0xcc, 0x6e, - 0xed, 0xc6, 0xfb, 0xbd, 0xf9, 0x0b, 0x0c, 0xb1, 0xab, 0x83, 0xf7, 0x2e, 0x0e, 0x3e, 0xba, 0x38, - 0xf8, 0xec, 0xe2, 0xe0, 0xed, 0x2b, 0xde, 0x49, 0xf6, 0xcc, 0x37, 0xe7, 0x3f, 0x01, 0x00, 0x00, - 0xff, 0xff, 0x8e, 0x88, 0xea, 0xa5, 0xe3, 0x02, 0x00, 0x00, + // 306 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x92, 0xc1, 0x4a, 0xfc, 0x30, + 0x10, 0xc6, 0xff, 0xfd, 0x1f, 0x5c, 0x08, 0xbb, 0x2e, 0x14, 0x54, 0x58, 0xb1, 0x67, 0x41, 0x48, + 0x40, 0x2f, 0xeb, 0x82, 0x20, 0xcb, 0x8a, 0x5e, 0xd6, 0x83, 0xae, 0x17, 0x6f, 0xa9, 0xc6, 0xb4, + 0xb4, 0xcd, 0x94, 0x74, 0x4a, 0xf1, 0x4d, 0x7c, 0x24, 0x8f, 0x3e, 0x82, 0xd4, 0xe7, 0x10, 0xc4, + 0x26, 0xd3, 0x83, 0x07, 0x8b, 0xa7, 0x8f, 0xc9, 0xe4, 0xfb, 0x75, 0xe6, 0x6b, 0xd8, 0xa9, 0x4e, + 0x31, 0xa9, 0x63, 0xfe, 0x00, 0x85, 0xd8, 0x24, 0x6a, 0x93, 0xa4, 0x46, 0x57, 0xd7, 0x0a, 0x1b, + 0xb0, 0x99, 0x40, 0x34, 0x42, 0x96, 0xa9, 0x28, 0xc0, 0xa4, 0x08, 0x96, 0x94, 0x97, 0x16, 0x10, + 0xc2, 0x91, 0x2f, 0x67, 0x07, 0x74, 0x4f, 0x4b, 0x54, 0x8d, 0x7c, 0x26, 0x75, 0xf7, 0x66, 0xfb, + 0xd4, 0xb6, 0x50, 0xa3, 0xb2, 0x5e, 0x7e, 0x36, 0x63, 0x0b, 0x99, 0xb2, 0x5e, 0x7c, 0xb3, 0x07, + 0x27, 0xd2, 0x3c, 0xe6, 0xca, 0x92, 0x92, 0x57, 0x03, 0xe8, 0x5c, 0x89, 0xae, 0x8a, 0xeb, 0x27, + 0xa1, 0x8a, 0x12, 0xfd, 0x57, 0x8f, 0x3f, 0xff, 0xb3, 0xd1, 0xda, 0x0d, 0x18, 0x2e, 0xd8, 0xe4, + 0xd2, 0x8d, 0x74, 0x8b, 0x12, 0xeb, 0x2a, 0x9c, 0x72, 0x1a, 0xd1, 0x1d, 0xcc, 0x76, 0xb9, 0x63, + 0x71, 0x62, 0xf1, 0x8b, 0x6f, 0xd6, 0x61, 0x10, 0x9e, 0xf7, 0xde, 0xbb, 0x32, 0x4f, 0x4d, 0x16, + 0xee, 0x70, 0xbf, 0x80, 0xab, 0xd7, 0xaa, 0xaa, 0xa4, 0x56, 0xbf, 0x10, 0x56, 0x6c, 0xea, 0x09, + 0x2b, 0x68, 0x4c, 0xc7, 0xd8, 0x23, 0x06, 0x9d, 0x0c, 0x53, 0xe6, 0x6c, 0x7c, 0xd3, 0x79, 0xfc, + 0x0a, 0xdb, 0x84, 0x18, 0xdc, 0x60, 0xce, 0xc6, 0xcb, 0x2e, 0xd5, 0xde, 0xe9, 0x43, 0x1e, 0x74, + 0x2e, 0xd8, 0xe4, 0xca, 0x25, 0xde, 0xe7, 0x46, 0x7f, 0x60, 0xc8, 0xbb, 0x3c, 0x7b, 0x6d, 0xa3, + 0xe0, 0xad, 0x8d, 0x82, 0xf7, 0x36, 0x0a, 0x5e, 0x3e, 0xa2, 0x7f, 0xf7, 0x47, 0x7f, 0x78, 0x6a, + 0xf1, 0x56, 0x07, 0x3c, 0xf9, 0x0a, 0x00, 0x00, 0xff, 0xff, 0x06, 0x34, 0x73, 0x98, 0xa0, 0x02, + 0x00, 0x00, } diff --git a/api/monitor/monitor.proto b/api/monitor/monitor.proto index 1e292ad8c..810fd755a 100644 --- a/api/monitor/monitor.proto +++ b/api/monitor/monitor.proto @@ -3,15 +3,17 @@ syntax = "proto3"; -import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; -import "github.com/TheThingsNetwork/ttn/api/router/router.proto"; -import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; -import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; +import "ttn/api/gateway/gateway.proto"; +import "ttn/api/router/router.proto"; +import "ttn/api/broker/broker.proto"; +import "ttn/api/handler/handler.proto"; import "google/protobuf/empty.proto"; package monitor; +option go_package = "github.com/TheThingsNetwork/ttn/api/monitor"; + service Monitor { rpc GatewayStatus(stream gateway.Status) returns (google.protobuf.Empty); rpc GatewayUplink(stream router.UplinkMessage) returns (google.protobuf.Empty); diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 2699e88b2..1e6c7c135 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -1274,43 +1274,43 @@ func init() { } var fileDescriptorNetworkserver = []byte{ - // 605 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x94, 0x41, 0x4f, 0x13, 0x41, - 0x14, 0xc7, 0x5d, 0xd0, 0x52, 0x5e, 0xa9, 0xc0, 0x20, 0xb1, 0x69, 0xb4, 0x42, 0x0f, 0xa6, 0x46, - 0xdd, 0x8d, 0x35, 0xd1, 0x98, 0x70, 0xa0, 0x50, 0xc3, 0xc1, 0x40, 0xea, 0x82, 0x89, 0x37, 0x32, - 0xdd, 0x7d, 0x6c, 0x37, 0x6c, 0x67, 0xd6, 0x99, 0xd9, 0x22, 0xdf, 0xc4, 0xab, 0x67, 0xbf, 0x88, - 0x47, 0xcf, 0x1e, 0x8c, 0xc1, 0x2f, 0x62, 0x3a, 0x33, 0x5b, 0x58, 0x81, 0x50, 0x4f, 0xbb, 0xef, - 0xfd, 0x7f, 0xef, 0xcd, 0xeb, 0xfe, 0xdf, 0x14, 0xde, 0x46, 0xb1, 0x1a, 0x64, 0x7d, 0x37, 0xe0, - 0x43, 0xef, 0x60, 0x80, 0x07, 0x83, 0x98, 0x45, 0x72, 0x0f, 0xd5, 0x09, 0x17, 0xc7, 0x9e, 0x52, - 0xcc, 0xa3, 0x69, 0xec, 0x31, 0x13, 0x4b, 0x14, 0x23, 0x14, 0xc5, 0xc8, 0x4d, 0x05, 0x57, 0x9c, - 0x54, 0x0b, 0xc9, 0xfa, 0xf3, 0x0b, 0x5d, 0x23, 0x1e, 0x71, 0x4f, 0x53, 0xfd, 0xec, 0x48, 0x47, - 0x3a, 0xd0, 0x6f, 0xa6, 0xba, 0x80, 0x5f, 0x3b, 0x04, 0x4d, 0x63, 0x8b, 0x77, 0xa6, 0xc1, 0x35, - 0x1a, 0xf0, 0xc4, 0x4b, 0xb8, 0xa0, 0x27, 0x94, 0x79, 0x21, 0x8e, 0xe2, 0x00, 0x6d, 0x8b, 0xd7, - 0xd3, 0xb4, 0xe8, 0x0b, 0x7e, 0x8c, 0xc2, 0x3e, 0x6c, 0xe1, 0x9b, 0x69, 0x0a, 0x07, 0x94, 0x85, - 0x09, 0x8a, 0xfc, 0x69, 0x4a, 0x9b, 0x9f, 0xe1, 0x6e, 0x57, 0xcf, 0x20, 0x7d, 0xfc, 0x94, 0xa1, - 0x54, 0xe4, 0x3d, 0x94, 0x43, 0x1c, 0x1d, 0xd2, 0x30, 0x14, 0x35, 0x67, 0xcd, 0x69, 0x2d, 0x6c, - 0xbd, 0xfa, 0xf9, 0xeb, 0x51, 0xfb, 0xa6, 0x23, 0x02, 0x2e, 0xd0, 0x53, 0xa7, 0x29, 0x4a, 0xb7, - 0x8b, 0xa3, 0x4e, 0x18, 0x0a, 0x7f, 0x2e, 0x34, 0x2f, 0x64, 0x05, 0xee, 0x1c, 0x1d, 0x06, 0x4c, - 0xd5, 0x66, 0xd6, 0x9c, 0x56, 0xd5, 0xbf, 0x7d, 0xb4, 0xcd, 0x54, 0x73, 0x03, 0x16, 0x27, 0x27, - 0xcb, 0x94, 0x33, 0x89, 0xe4, 0x09, 0xcc, 0x09, 0x94, 0x59, 0xa2, 0x64, 0xcd, 0x59, 0x9b, 0x6d, - 0x55, 0xda, 0x8b, 0xae, 0xfd, 0x50, 0xae, 0x41, 0xfd, 0x5c, 0x6f, 0x2e, 0x42, 0x75, 0x5f, 0x51, - 0x95, 0xe5, 0x63, 0x37, 0xbf, 0xce, 0x40, 0xc9, 0x64, 0x48, 0x0b, 0x4a, 0xf2, 0x54, 0x2a, 0x1c, - 0xea, 0xf9, 0x2b, 0xed, 0x25, 0x77, 0x6c, 0xd3, 0xbe, 0x4e, 0x8d, 0x11, 0xe9, 0x5b, 0x9d, 0xbc, - 0x80, 0xf9, 0x80, 0x0f, 0x53, 0xce, 0xd0, 0x0e, 0x57, 0x69, 0xaf, 0x68, 0x78, 0x3b, 0xcf, 0x1a, - 0xfe, 0x9c, 0x22, 0x4d, 0x28, 0x65, 0x69, 0x12, 0xb3, 0xe3, 0x5a, 0x45, 0xf3, 0xa0, 0x79, 0x9f, - 0x2a, 0x94, 0xbe, 0x55, 0xc8, 0x63, 0x28, 0x87, 0xfc, 0x84, 0x69, 0x6a, 0xe1, 0x12, 0x35, 0xd1, - 0xc8, 0x33, 0xa8, 0xd0, 0x40, 0xc5, 0x23, 0xaa, 0x62, 0xce, 0x64, 0xad, 0x7a, 0x09, 0xbd, 0x28, - 0x93, 0x4d, 0x58, 0x31, 0xeb, 0x22, 0x0f, 0x53, 0x14, 0xda, 0x20, 0x94, 0xb2, 0xb6, 0x7a, 0xe1, - 0x37, 0xf6, 0x50, 0x04, 0xc8, 0x54, 0x9c, 0xa0, 0xf4, 0x97, 0x2d, 0xdc, 0x43, 0xd1, 0x31, 0x68, - 0xfb, 0xdb, 0x2c, 0x54, 0xad, 0x67, 0xfb, 0xfa, 0x4e, 0x90, 0x77, 0x00, 0x3b, 0xa8, 0xac, 0x0f, - 0xe4, 0xa1, 0x5b, 0xbc, 0x46, 0xc5, 0xcd, 0xa8, 0x37, 0xae, 0x93, 0xad, 0x7d, 0x43, 0x58, 0xee, - 0x09, 0x4c, 0xa9, 0xc0, 0xce, 0x64, 0x6c, 0xf2, 0xd4, 0xb5, 0xab, 0xda, 0xc5, 0x70, 0xfc, 0x79, - 0x02, 0xaa, 0x30, 0x34, 0x95, 0xe7, 0x54, 0x7e, 0xc2, 0xff, 0xc0, 0xa4, 0x07, 0x65, 0x9b, 0x44, - 0xb2, 0xee, 0xe6, 0x6b, 0x7d, 0x99, 0x36, 0xd3, 0xd5, 0x6f, 0x46, 0xc8, 0x1e, 0x94, 0x3e, 0x18, - 0x07, 0xd7, 0xaf, 0x1a, 0xc4, 0x68, 0xbb, 0x28, 0x25, 0x8d, 0xc6, 0xfd, 0x6e, 0x42, 0xc8, 0x06, - 0x94, 0xbb, 0xb9, 0xd7, 0xf7, 0x27, 0xb8, 0xcd, 0xe4, 0x7d, 0xae, 0x13, 0xda, 0x1f, 0xe1, 0x5e, - 0xc1, 0xac, 0x5d, 0xca, 0x68, 0x84, 0x82, 0x6c, 0xc2, 0xfc, 0x0e, 0x2a, 0xbb, 0xeb, 0x0f, 0xfe, - 0xf1, 0xa4, 0x70, 0x29, 0xea, 0xab, 0x57, 0xaa, 0x5b, 0x4b, 0xdf, 0xcf, 0x1a, 0xce, 0x8f, 0xb3, - 0x86, 0xf3, 0xfb, 0xac, 0xe1, 0x7c, 0xf9, 0xd3, 0xb8, 0xd5, 0x2f, 0xe9, 0x7f, 0x83, 0x97, 0x7f, - 0x03, 0x00, 0x00, 0xff, 0xff, 0x66, 0xb2, 0x91, 0x81, 0x7a, 0x05, 0x00, 0x00, + // 608 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x94, 0x4f, 0x4f, 0x13, 0x4f, + 0x18, 0xc7, 0x7f, 0x0b, 0x3f, 0x4b, 0x79, 0x4a, 0x45, 0x06, 0x89, 0x4d, 0x95, 0x0a, 0x4d, 0x34, + 0x35, 0xea, 0x6e, 0xa8, 0x89, 0x27, 0x0e, 0xfc, 0xa9, 0xe1, 0x60, 0x20, 0x75, 0xc1, 0xc4, 0x78, + 0x21, 0xd3, 0xdd, 0x87, 0x76, 0xc3, 0x76, 0x66, 0x9d, 0x99, 0x2d, 0xf2, 0x4e, 0xbc, 0x7a, 0xf6, + 0x8d, 0x78, 0xf4, 0xec, 0xc1, 0x18, 0x7c, 0x23, 0xa6, 0xf3, 0xa7, 0xb0, 0x02, 0x21, 0x9c, 0x76, + 0x9f, 0xef, 0xf7, 0xb3, 0x33, 0xcf, 0xce, 0xf7, 0xd9, 0x85, 0x37, 0xfd, 0x44, 0x0d, 0xf2, 0x9e, + 0x1f, 0xf1, 0x61, 0x70, 0x30, 0xc0, 0x83, 0x41, 0xc2, 0xfa, 0x72, 0x0f, 0xd5, 0x09, 0x17, 0xc7, + 0x81, 0x52, 0x2c, 0xa0, 0x59, 0x12, 0x30, 0x53, 0x4b, 0x14, 0x23, 0x14, 0xc5, 0xca, 0xcf, 0x04, + 0x57, 0x9c, 0x54, 0x0b, 0x62, 0xfd, 0xe5, 0x85, 0x55, 0xfb, 0xbc, 0xcf, 0x03, 0x4d, 0xf5, 0xf2, + 0x23, 0x5d, 0xe9, 0x42, 0xdf, 0x99, 0xa7, 0xeb, 0x0b, 0x6e, 0x23, 0x9a, 0x25, 0x56, 0x7a, 0xe2, + 0x24, 0x5d, 0x46, 0x3c, 0x0d, 0x52, 0x2e, 0xe8, 0x09, 0x65, 0x41, 0x8c, 0xa3, 0x24, 0x42, 0x8b, + 0x3d, 0x74, 0x58, 0x4f, 0xf0, 0x63, 0x14, 0xf6, 0x62, 0xcd, 0x65, 0x67, 0x0e, 0x28, 0x8b, 0x53, + 0x14, 0xee, 0x6a, 0xec, 0xe6, 0x67, 0xb8, 0xdb, 0xd1, 0x6b, 0xc9, 0x10, 0x3f, 0xe5, 0x28, 0x15, + 0x79, 0x07, 0xe5, 0x18, 0x47, 0x87, 0x34, 0x8e, 0x45, 0xcd, 0x5b, 0xf1, 0x5a, 0x73, 0x5b, 0xaf, + 0x7f, 0xfe, 0x7a, 0xdc, 0xbe, 0xe9, 0x88, 0x22, 0x2e, 0x30, 0x50, 0xa7, 0x19, 0x4a, 0xbf, 0x83, + 0xa3, 0xcd, 0x38, 0x16, 0xe1, 0x4c, 0x6c, 0x6e, 0xc8, 0x22, 0xdc, 0x39, 0x3a, 0x8c, 0x98, 0xaa, + 0x4d, 0xad, 0x78, 0xad, 0x6a, 0xf8, 0xff, 0xd1, 0x36, 0x53, 0xcd, 0x75, 0x98, 0x9f, 0xec, 0x2c, + 0x33, 0xce, 0x24, 0x92, 0x67, 0x30, 0x23, 0x50, 0xe6, 0xa9, 0x92, 0x35, 0x6f, 0x65, 0xba, 0x55, + 0x69, 0xcf, 0xfb, 0xf6, 0x85, 0x7d, 0x83, 0x86, 0xce, 0x6f, 0xce, 0x43, 0x75, 0x5f, 0x51, 0x95, + 0xbb, 0xb6, 0x9b, 0x5f, 0xa7, 0xa0, 0x64, 0x14, 0xd2, 0x82, 0x92, 0x3c, 0x95, 0x0a, 0x87, 0xba, + 0xff, 0x4a, 0xfb, 0x9e, 0x3f, 0x3e, 0xd2, 0x7d, 0x2d, 0x8d, 0x11, 0x19, 0x5a, 0x9f, 0xac, 0xc1, + 0x6c, 0xc4, 0x87, 0x19, 0x67, 0x68, 0x9b, 0xab, 0xb4, 0x17, 0x35, 0xbc, 0xed, 0x54, 0xc3, 0x9f, + 0x53, 0xa4, 0x09, 0xa5, 0x3c, 0x4b, 0x13, 0x76, 0x5c, 0xab, 0x68, 0x1e, 0x34, 0x1f, 0x52, 0x85, + 0x32, 0xb4, 0x0e, 0x79, 0x0a, 0xe5, 0x98, 0x9f, 0x30, 0x4d, 0xcd, 0x5d, 0xa2, 0x26, 0x1e, 0x79, + 0x01, 0x15, 0x1a, 0xa9, 0x64, 0x44, 0x55, 0xc2, 0x99, 0xac, 0x55, 0x2f, 0xa1, 0x17, 0x6d, 0xb2, + 0x01, 0x8b, 0x26, 0x76, 0x79, 0x98, 0xa1, 0xd0, 0x01, 0xa1, 0x94, 0xb5, 0xa5, 0x0b, 0xef, 0xd8, + 0x45, 0x11, 0x21, 0x53, 0x49, 0x8a, 0x32, 0x5c, 0xb0, 0x70, 0x17, 0xc5, 0xa6, 0x41, 0xdb, 0xdf, + 0xa6, 0xa1, 0x6a, 0x33, 0xdb, 0xd7, 0x33, 0x4a, 0xde, 0x02, 0xec, 0xa0, 0xb2, 0x39, 0x90, 0x65, + 0xbf, 0x38, 0xd6, 0xc5, 0xc9, 0xa8, 0x37, 0xae, 0xb3, 0x6d, 0x7c, 0x43, 0x58, 0xe8, 0x0a, 0xcc, + 0xa8, 0xc0, 0xcd, 0x49, 0xdb, 0xe4, 0xb9, 0x6f, 0xc7, 0xb1, 0x83, 0xf1, 0xf8, 0x78, 0x22, 0xaa, + 0x30, 0x36, 0x4f, 0x9e, 0x53, 0x6e, 0x87, 0xdb, 0xc0, 0xa4, 0x0b, 0x65, 0x2b, 0x22, 0x59, 0xf5, + 0xdd, 0x58, 0x5f, 0xa6, 0x4d, 0x77, 0xf5, 0x9b, 0x11, 0xb2, 0x07, 0xa5, 0xf7, 0x26, 0xc1, 0xd5, + 0xab, 0x1a, 0x31, 0xde, 0x2e, 0x4a, 0x49, 0xfb, 0xe3, 0xf5, 0x6e, 0x42, 0xc8, 0x3a, 0x94, 0x3b, + 0x2e, 0xeb, 0x07, 0x13, 0xdc, 0x2a, 0x6e, 0x9d, 0xeb, 0x8c, 0xf6, 0x07, 0xb8, 0x5f, 0x08, 0x6b, + 0x97, 0x32, 0xda, 0x47, 0x41, 0x36, 0x60, 0x76, 0x07, 0x95, 0x9d, 0xf5, 0x47, 0xff, 0x64, 0x52, + 0xf8, 0x28, 0xea, 0x4b, 0x57, 0xba, 0x5b, 0xdb, 0xdf, 0xcf, 0x1a, 0xde, 0x8f, 0xb3, 0x86, 0xf7, + 0xfb, 0xac, 0xe1, 0x7d, 0xf9, 0xd3, 0xf8, 0xef, 0xe3, 0xda, 0xad, 0xff, 0x80, 0xbd, 0x92, 0xfe, + 0x81, 0xbc, 0xfa, 0x1b, 0x00, 0x00, 0xff, 0xff, 0xa6, 0x40, 0x55, 0xeb, 0x3d, 0x05, 0x00, 0x00, } diff --git a/api/networkserver/networkserver.proto b/api/networkserver/networkserver.proto index 077c9b29b..7b69755c4 100644 --- a/api/networkserver/networkserver.proto +++ b/api/networkserver/networkserver.proto @@ -5,13 +5,15 @@ syntax = "proto3"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; -import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto"; -import "github.com/TheThingsNetwork/ttn/api/broker/broker.proto"; -import "github.com/TheThingsNetwork/ttn/api/handler/handler.proto"; +import "ttn/api/api.proto"; +import "ttn/api/protocol/lorawan/device.proto"; +import "ttn/api/broker/broker.proto"; +import "ttn/api/handler/handler.proto"; package networkserver; +option go_package = "github.com/TheThingsNetwork/ttn/api/networkserver"; + message DevicesRequest { bytes dev_addr = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; uint32 f_cnt = 2; diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index cd59762e2..db4ee02a9 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -2,12 +2,22 @@ // source: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto // DO NOT EDIT! +/* + Package lorawan is a generated protocol buffer package. + + It is generated from these files: + github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto + + It has these top-level messages: + DeviceIdentifier + Device +*/ package lorawan import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" -import google_protobuf1 "github.com/golang/protobuf/ptypes/empty" +import google_protobuf "github.com/golang/protobuf/ptypes/empty" import _ "github.com/gogo/protobuf/gogoproto" import github_com_TheThingsNetwork_ttn_core_types "github.com/TheThingsNetwork/ttn/core/types" @@ -24,6 +34,12 @@ var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package + type DeviceIdentifier struct { AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` @@ -75,8 +91,8 @@ const _ = grpc.SupportPackageIsVersion3 type DeviceManagerClient interface { GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) - SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) - DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) + SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) } type deviceManagerClient struct { @@ -96,8 +112,8 @@ func (c *deviceManagerClient) GetDevice(ctx context.Context, in *DeviceIdentifie return out, nil } -func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { - out := new(google_protobuf1.Empty) +func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/lorawan.DeviceManager/SetDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -105,8 +121,8 @@ func (c *deviceManagerClient) SetDevice(ctx context.Context, in *Device, opts .. return out, nil } -func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf1.Empty, error) { - out := new(google_protobuf1.Empty) +func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) { + out := new(google_protobuf.Empty) err := grpc.Invoke(ctx, "/lorawan.DeviceManager/DeleteDevice", in, out, c.cc, opts...) if err != nil { return nil, err @@ -118,8 +134,8 @@ func (c *deviceManagerClient) DeleteDevice(ctx context.Context, in *DeviceIdenti type DeviceManagerServer interface { GetDevice(context.Context, *DeviceIdentifier) (*Device, error) - SetDevice(context.Context, *Device) (*google_protobuf1.Empty, error) - DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf1.Empty, error) + SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) + DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) } func RegisterDeviceManagerServer(s *grpc.Server, srv DeviceManagerServer) { @@ -1133,42 +1149,42 @@ func init() { } var fileDescriptorDevice = []byte{ - // 578 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xc1, 0x6e, 0xd3, 0x30, - 0x18, 0xc7, 0x09, 0x63, 0x6d, 0x63, 0x56, 0x51, 0x19, 0x6d, 0x32, 0x1d, 0xea, 0xaa, 0x5d, 0xe8, - 0x65, 0x89, 0xd8, 0x18, 0x9c, 0xbb, 0xb6, 0xa0, 0x0a, 0x31, 0x89, 0x6c, 0x3b, 0x47, 0x6e, 0xfc, - 0x35, 0xb5, 0x9a, 0xd9, 0x56, 0xe2, 0x24, 0xea, 0x03, 0xf0, 0x0e, 0x3c, 0x07, 0x6f, 0xc0, 0x8d, - 0x23, 0xe7, 0x1d, 0x26, 0x34, 0x5e, 0x04, 0x39, 0xee, 0x18, 0xaa, 0x84, 0x26, 0x7a, 0xe2, 0xf6, - 0xe5, 0xff, 0xff, 0xfb, 0xf7, 0xd9, 0x75, 0xfd, 0xa1, 0x7e, 0xcc, 0xf5, 0x2c, 0x9f, 0x78, 0x91, - 0xbc, 0xf4, 0xcf, 0x67, 0x70, 0x3e, 0xe3, 0x22, 0xce, 0x4e, 0x41, 0x97, 0x32, 0x9d, 0xfb, 0x5a, - 0x0b, 0x9f, 0x2a, 0xee, 0xab, 0x54, 0x6a, 0x19, 0xc9, 0xc4, 0x4f, 0x64, 0x4a, 0x4b, 0x2a, 0x7c, - 0x06, 0x05, 0x8f, 0xc0, 0xab, 0x74, 0x5c, 0x5f, 0xaa, 0xed, 0xdd, 0x58, 0xca, 0x38, 0x01, 0x1b, - 0x9f, 0xe4, 0x53, 0x1f, 0x2e, 0x95, 0x5e, 0xd8, 0x54, 0xfb, 0xe0, 0x8f, 0x46, 0xb1, 0x8c, 0xe5, - 0x5d, 0xca, 0x7c, 0x55, 0x1f, 0x55, 0x65, 0xe3, 0xfb, 0x5f, 0x1c, 0xd4, 0x1a, 0x56, 0x5d, 0xc6, - 0x0c, 0x84, 0xe6, 0x53, 0x0e, 0x29, 0x3e, 0x45, 0x75, 0xaa, 0x54, 0x08, 0x39, 0x27, 0x4e, 0xd7, - 0xe9, 0x6d, 0x9d, 0x1c, 0x5f, 0x5d, 0xef, 0xbd, 0xbc, 0xef, 0x04, 0x91, 0x4c, 0xc1, 0xd7, 0x0b, - 0x05, 0x99, 0xd7, 0x57, 0x6a, 0x74, 0x31, 0x0e, 0x6a, 0x54, 0xa9, 0x51, 0xce, 0x0d, 0x8f, 0x41, - 0x51, 0xf1, 0x1e, 0xae, 0xc5, 0x1b, 0x42, 0x51, 0xf1, 0x18, 0x14, 0xa3, 0x9c, 0xef, 0x7f, 0xaa, - 0xa1, 0x9a, 0xdd, 0xf4, 0xff, 0xbe, 0x55, 0xbc, 0x8d, 0x0c, 0x39, 0xe4, 0x8c, 0x6c, 0x74, 0x9d, - 0x9e, 0x1b, 0x6c, 0x52, 0xa5, 0xc6, 0xcc, 0xc8, 0xa6, 0x0d, 0x67, 0xe4, 0x91, 0x95, 0x19, 0x14, - 0x63, 0x86, 0x3f, 0xa2, 0x86, 0x91, 0x29, 0x63, 0x29, 0xd9, 0xac, 0xda, 0xbf, 0xbe, 0xba, 0xde, - 0x3b, 0xfc, 0xb7, 0xf6, 0x7d, 0xc6, 0xd2, 0xc0, 0x9c, 0xc2, 0x14, 0x38, 0x40, 0xae, 0x28, 0xe7, - 0x61, 0x16, 0xce, 0x61, 0x41, 0x6a, 0x6b, 0x31, 0x4f, 0xcb, 0xf9, 0xd9, 0x7b, 0x58, 0x04, 0x75, - 0x61, 0x0b, 0xc3, 0x34, 0x87, 0xb2, 0xcc, 0xfa, 0x5a, 0xcc, 0xbe, 0x52, 0x96, 0x49, 0x6d, 0x71, - 0x7b, 0x91, 0x86, 0xd8, 0x58, 0xf7, 0x22, 0x0d, 0xd0, 0xfc, 0xdc, 0x86, 0x47, 0x50, 0x63, 0x1a, - 0x46, 0x42, 0x87, 0xb9, 0x22, 0x6e, 0xd7, 0xe9, 0x35, 0x83, 0xda, 0x74, 0x20, 0xf4, 0x85, 0xc2, - 0xcf, 0x11, 0xb2, 0x0e, 0x93, 0xa5, 0x20, 0xa8, 0xf2, 0x1a, 0xc6, 0x1b, 0xca, 0x52, 0xe0, 0x03, - 0xf4, 0x94, 0xf1, 0x8c, 0x4e, 0x12, 0x08, 0x6d, 0x2a, 0x9a, 0x41, 0x34, 0x27, 0x8f, 0xbb, 0x4e, - 0xaf, 0x11, 0xb4, 0x96, 0xd6, 0xdb, 0x81, 0xd0, 0x03, 0xa3, 0xe3, 0x17, 0xa8, 0x95, 0x67, 0x90, - 0x1d, 0x1d, 0x86, 0x13, 0xae, 0xed, 0x0a, 0xb2, 0x55, 0x65, 0x9b, 0x56, 0x3f, 0xe1, 0xda, 0xa4, - 0xf1, 0x31, 0xda, 0xa1, 0x91, 0xe6, 0x05, 0xd5, 0x5c, 0x8a, 0x30, 0x92, 0x22, 0xd3, 0x29, 0xe5, - 0x42, 0x67, 0xa4, 0x59, 0xfd, 0x03, 0xb6, 0xef, 0xdc, 0xc1, 0x9d, 0x89, 0x77, 0x91, 0x9b, 0xd0, - 0x4c, 0x87, 0x19, 0x80, 0x20, 0xdb, 0x5d, 0xa7, 0xb7, 0x11, 0x34, 0x8c, 0x70, 0x06, 0x20, 0x0e, - 0xbf, 0x3a, 0xa8, 0x69, 0xdf, 0xc1, 0x07, 0x2a, 0x68, 0x0c, 0x29, 0x7e, 0x83, 0xdc, 0x77, 0xa0, - 0x97, 0x6f, 0xe3, 0x99, 0xb7, 0x9c, 0x18, 0xde, 0xea, 0x0b, 0x6f, 0x3f, 0x59, 0xb1, 0xf0, 0x2b, - 0xe4, 0x9e, 0xfd, 0x5e, 0xb8, 0xea, 0xb6, 0x77, 0x3c, 0x3b, 0x72, 0xbc, 0xdb, 0x61, 0xe2, 0x8d, - 0xcc, 0xc8, 0xc1, 0x7d, 0xb4, 0x35, 0x84, 0x04, 0x34, 0xdc, 0xdf, 0xf1, 0x2f, 0x88, 0x93, 0xd6, - 0xb7, 0x9b, 0x8e, 0xf3, 0xfd, 0xa6, 0xe3, 0xfc, 0xb8, 0xe9, 0x38, 0x9f, 0x7f, 0x76, 0x1e, 0x4c, - 0x6a, 0x55, 0xe2, 0xe8, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0x4d, 0x74, 0x98, 0x46, 0x33, 0x05, - 0x00, 0x00, + // 586 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xcc, 0x94, 0xcf, 0x6e, 0x13, 0x3d, + 0x14, 0xc5, 0xbf, 0xf9, 0x4a, 0x93, 0x8c, 0x69, 0x44, 0x65, 0xd4, 0xca, 0xa4, 0x28, 0x8d, 0xba, + 0x21, 0x9b, 0xce, 0x88, 0xfe, 0x81, 0x75, 0x9a, 0xa4, 0x28, 0x42, 0x54, 0x62, 0xda, 0x6e, 0xd8, + 0x8c, 0x9c, 0xf1, 0xcd, 0xc4, 0xca, 0xd4, 0xb6, 0x66, 0x3c, 0x33, 0xca, 0x03, 0xf0, 0x0e, 0x3c, + 0x07, 0x6f, 0xc0, 0x8e, 0x25, 0xeb, 0x2e, 0x2a, 0x54, 0x5e, 0x04, 0x79, 0x9c, 0x52, 0x14, 0x09, + 0x55, 0x64, 0xc5, 0xee, 0xce, 0x39, 0xc7, 0xbf, 0x6b, 0xc7, 0xf1, 0x45, 0xbd, 0x98, 0xeb, 0x69, + 0x3e, 0xf6, 0x22, 0x79, 0xe5, 0x5f, 0x4c, 0xe1, 0x62, 0xca, 0x45, 0x9c, 0x9d, 0x81, 0x2e, 0x65, + 0x3a, 0xf3, 0xb5, 0x16, 0x3e, 0x55, 0xdc, 0x57, 0xa9, 0xd4, 0x32, 0x92, 0x89, 0x9f, 0xc8, 0x94, + 0x96, 0x54, 0xf8, 0x0c, 0x0a, 0x1e, 0x81, 0x57, 0xe9, 0xb8, 0xbe, 0x50, 0x5b, 0x3b, 0xb1, 0x94, + 0x71, 0x02, 0x36, 0x3e, 0xce, 0x27, 0x3e, 0x5c, 0x29, 0x3d, 0xb7, 0xa9, 0xd6, 0xfe, 0x6f, 0x8d, + 0x62, 0x19, 0xcb, 0xfb, 0x94, 0xf9, 0xaa, 0x3e, 0xaa, 0xca, 0xc6, 0xf7, 0x3e, 0x3b, 0x68, 0x73, + 0x50, 0x75, 0x19, 0x31, 0x10, 0x9a, 0x4f, 0x38, 0xa4, 0xf8, 0x0c, 0xd5, 0xa9, 0x52, 0x21, 0xe4, + 0x9c, 0x38, 0x1d, 0xa7, 0xbb, 0x71, 0x72, 0x7c, 0x7d, 0xb3, 0xfb, 0xf2, 0xa1, 0x13, 0x44, 0x32, + 0x05, 0x5f, 0xcf, 0x15, 0x64, 0x5e, 0x4f, 0xa9, 0xe1, 0xe5, 0x28, 0xa8, 0x51, 0xa5, 0x86, 0x39, + 0x37, 0x3c, 0x06, 0x45, 0xc5, 0xfb, 0x7f, 0x25, 0xde, 0x00, 0x8a, 0x8a, 0xc7, 0xa0, 0x18, 0xe6, + 0x7c, 0xef, 0x63, 0x0d, 0xd5, 0xec, 0xa6, 0xff, 0xf5, 0xad, 0xe2, 0x2d, 0x64, 0xc8, 0x21, 0x67, + 0x64, 0xad, 0xe3, 0x74, 0xdd, 0x60, 0x9d, 0x2a, 0x35, 0x62, 0x46, 0x36, 0x6d, 0x38, 0x23, 0x8f, + 0xac, 0xcc, 0xa0, 0x18, 0x31, 0xfc, 0x1e, 0x35, 0x8c, 0x4c, 0x19, 0x4b, 0xc9, 0x7a, 0xd5, 0xfe, + 0xd5, 0xf5, 0xcd, 0xee, 0xc1, 0xdf, 0xb5, 0xef, 0x31, 0x96, 0x06, 0xe6, 0x14, 0xa6, 0xc0, 0x01, + 0x72, 0x45, 0x39, 0x0b, 0xb3, 0x70, 0x06, 0x73, 0x52, 0x5b, 0x89, 0x79, 0x56, 0xce, 0xce, 0xdf, + 0xc2, 0x3c, 0xa8, 0x0b, 0x5b, 0x18, 0xa6, 0x39, 0x94, 0x65, 0xd6, 0x57, 0x62, 0xf6, 0x94, 0xb2, + 0x4c, 0x6a, 0x8b, 0xbb, 0x8b, 0x34, 0xc4, 0xc6, 0xaa, 0x17, 0x69, 0x80, 0xe6, 0xe7, 0x36, 0x3c, + 0x82, 0x1a, 0x93, 0x30, 0x12, 0x3a, 0xcc, 0x15, 0x71, 0x3b, 0x4e, 0xb7, 0x19, 0xd4, 0x26, 0x7d, + 0xa1, 0x2f, 0x15, 0x7e, 0x8e, 0x90, 0x75, 0x98, 0x2c, 0x05, 0x41, 0x95, 0xd7, 0x30, 0xde, 0x40, + 0x96, 0x02, 0xef, 0xa3, 0xa7, 0x8c, 0x67, 0x74, 0x9c, 0x40, 0x68, 0x53, 0xd1, 0x14, 0xa2, 0x19, + 0x79, 0xdc, 0x71, 0xba, 0x8d, 0x60, 0x73, 0x61, 0x9d, 0xf6, 0x85, 0xee, 0x1b, 0x1d, 0xbf, 0x40, + 0x9b, 0x79, 0x06, 0xd9, 0xe1, 0x41, 0x38, 0xe6, 0xda, 0xae, 0x20, 0x1b, 0x55, 0xb6, 0x69, 0xf5, + 0x13, 0xae, 0x4d, 0x1a, 0x1f, 0xa3, 0x6d, 0x1a, 0x69, 0x5e, 0x50, 0xcd, 0xa5, 0x08, 0x23, 0x29, + 0x32, 0x9d, 0x52, 0x2e, 0x74, 0x46, 0x9a, 0xd5, 0x3f, 0x60, 0xeb, 0xde, 0xed, 0xdf, 0x9b, 0x78, + 0x07, 0xb9, 0x09, 0xcd, 0x74, 0x98, 0x01, 0x08, 0xb2, 0xd5, 0x71, 0xba, 0x6b, 0x41, 0xc3, 0x08, + 0xe7, 0x00, 0xe2, 0xe0, 0x8b, 0x83, 0x9a, 0xf6, 0x1d, 0xbc, 0xa3, 0x82, 0xc6, 0x90, 0xe2, 0xd7, + 0xc8, 0x7d, 0x03, 0x7a, 0xf1, 0x36, 0x9e, 0x79, 0x8b, 0x89, 0xe1, 0x2d, 0xbf, 0xf0, 0xd6, 0x93, + 0x25, 0x0b, 0x1f, 0x21, 0xf7, 0xfc, 0xd7, 0xc2, 0x65, 0xb7, 0xb5, 0xed, 0xd9, 0x91, 0xe3, 0xdd, + 0x0d, 0x13, 0x6f, 0x68, 0x46, 0x0e, 0xee, 0xa1, 0x8d, 0x01, 0x24, 0xa0, 0xe1, 0xe1, 0x8e, 0x7f, + 0x40, 0x9c, 0x9c, 0x7e, 0xbd, 0x6d, 0x3b, 0xdf, 0x6e, 0xdb, 0xce, 0xf7, 0xdb, 0xb6, 0xf3, 0xe9, + 0x47, 0xfb, 0xbf, 0x0f, 0x47, 0xab, 0x8c, 0xca, 0x71, 0xad, 0x52, 0x0e, 0x7f, 0x06, 0x00, 0x00, + 0xff, 0xff, 0x5e, 0xdc, 0x9f, 0x7c, 0x69, 0x05, 0x00, 0x00, } diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 384925419..09d3012c6 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -8,6 +8,8 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; package lorawan; +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; + message DeviceIdentifier { bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; diff --git a/api/protocol/lorawan/device_address.pb.go b/api/protocol/lorawan/device_address.pb.go index f41623e08..edbd947d2 100644 --- a/api/protocol/lorawan/device_address.pb.go +++ b/api/protocol/lorawan/device_address.pb.go @@ -7,19 +7,12 @@ It is generated from these files: github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto - github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto - github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto It has these top-level messages: PrefixesRequest PrefixesResponse DevAddrRequest DevAddrResponse - DeviceIdentifier - Device - Metadata - TxConfiguration - ActivationMetadata */ package lorawan @@ -969,28 +962,29 @@ func init() { } var fileDescriptorDeviceAddress = []byte{ - // 361 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x84, 0x51, 0x4d, 0x4f, 0xe3, 0x30, - 0x14, 0x5c, 0x6f, 0xb5, 0xfd, 0x70, 0x77, 0xb7, 0x5d, 0x6b, 0xb5, 0x1b, 0x72, 0x08, 0x55, 0x0f, - 0xd0, 0x0b, 0x89, 0x54, 0x24, 0x6e, 0x08, 0x51, 0x90, 0x2a, 0x0e, 0x45, 0x10, 0xf5, 0x8e, 0xdc, - 0xf8, 0x35, 0x8d, 0x28, 0x71, 0xb0, 0x9d, 0x16, 0x7e, 0x08, 0x88, 0x9f, 0xc4, 0x91, 0x33, 0x07, - 0x84, 0xca, 0x1f, 0x41, 0x72, 0xdc, 0xb4, 0xe5, 0x43, 0xdc, 0x3c, 0x6f, 0xe6, 0xcd, 0x9b, 0x4c, - 0xf0, 0x51, 0x18, 0xa9, 0x51, 0x3a, 0x70, 0x03, 0x7e, 0xe1, 0xf5, 0x47, 0xd0, 0x1f, 0x45, 0x71, - 0x28, 0x8f, 0x41, 0x4d, 0xb9, 0x38, 0xf7, 0x94, 0x8a, 0x3d, 0x9a, 0x44, 0x5e, 0x22, 0xb8, 0xe2, - 0x01, 0x1f, 0x7b, 0x63, 0x2e, 0xe8, 0x94, 0xc6, 0x1e, 0x83, 0x49, 0x14, 0xc0, 0x19, 0x65, 0x4c, - 0x80, 0x94, 0xae, 0xe6, 0x49, 0xc9, 0xb0, 0xf6, 0xd6, 0x92, 0x67, 0xc8, 0x43, 0x9e, 0xed, 0x0f, - 0xd2, 0xa1, 0x46, 0x1a, 0xe8, 0x57, 0xb6, 0xd7, 0xfc, 0x83, 0x6b, 0x27, 0x02, 0x86, 0xd1, 0x15, - 0x48, 0x1f, 0x2e, 0x53, 0x90, 0xaa, 0x79, 0x8b, 0x70, 0x7d, 0x31, 0x93, 0x09, 0x8f, 0x25, 0x90, - 0x03, 0x5c, 0x4e, 0xcc, 0xcc, 0x42, 0x8d, 0x42, 0xab, 0xda, 0xde, 0x74, 0xcd, 0x49, 0xf7, 0xad, - 0xd8, 0x0c, 0x7a, 0x34, 0x49, 0xa2, 0x38, 0xf4, 0xf3, 0x45, 0x7b, 0x17, 0xff, 0x5a, 0xa1, 0xc8, - 0x3f, 0x5c, 0xcc, 0x48, 0x0b, 0x35, 0x50, 0xab, 0xe2, 0x1b, 0x44, 0xfe, 0xe2, 0x1f, 0xa9, 0xa4, - 0x21, 0x58, 0xdf, 0x1b, 0x85, 0x56, 0xc5, 0xcf, 0x40, 0x73, 0x03, 0xff, 0x3e, 0x84, 0xc9, 0x3e, - 0x63, 0xc2, 0x44, 0x5d, 0xe8, 0xd0, 0xb2, 0x8e, 0xe1, 0x5a, 0xae, 0x33, 0xf1, 0x4f, 0x71, 0x99, - 0xc1, 0x44, 0x77, 0xa6, 0x4f, 0xfd, 0xec, 0xec, 0x3c, 0x3e, 0xad, 0xb7, 0xbf, 0xea, 0x3f, 0xe0, - 0x02, 0x3c, 0x75, 0x9d, 0x80, 0x74, 0xe7, 0x8e, 0x25, 0x96, 0x3d, 0xda, 0x37, 0x28, 0x8f, 0xd3, - 0xa3, 0x31, 0x0d, 0x41, 0x90, 0x0e, 0xae, 0x76, 0x41, 0xcd, 0xeb, 0x20, 0xd6, 0x07, 0x0d, 0xe9, - 0xdc, 0xf6, 0xda, 0xa7, 0xdd, 0x91, 0x3d, 0x8c, 0xbb, 0xa0, 0x8c, 0x31, 0xf9, 0x9f, 0x0b, 0x57, - 0xbf, 0xdc, 0xb6, 0xde, 0x13, 0x99, 0x41, 0xa7, 0x7e, 0x3f, 0x73, 0xd0, 0xc3, 0xcc, 0x41, 0xcf, - 0x33, 0x07, 0xdd, 0xbd, 0x38, 0xdf, 0x06, 0x45, 0xfd, 0xab, 0xb7, 0x5f, 0x03, 0x00, 0x00, 0xff, - 0xff, 0x4c, 0x06, 0x93, 0x24, 0x6f, 0x02, 0x00, 0x00, + // 369 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x9c, 0x52, 0x4d, 0x4f, 0xdb, 0x40, + 0x14, 0xec, 0x36, 0x6a, 0x3e, 0x36, 0x6d, 0xd3, 0xae, 0xaa, 0xd6, 0xf5, 0xc1, 0x8d, 0x72, 0x68, + 0x73, 0xa9, 0x2d, 0x05, 0xc4, 0x0d, 0x21, 0x02, 0x22, 0xe2, 0x10, 0x04, 0x56, 0x4e, 0x5c, 0xd0, + 0xc6, 0xfb, 0xe2, 0x58, 0x04, 0xaf, 0xd9, 0x5d, 0x27, 0xf0, 0x43, 0x40, 0xfc, 0x24, 0x8e, 0x9c, + 0x39, 0x20, 0x14, 0xfe, 0x08, 0xd2, 0x7a, 0xe3, 0x24, 0x7c, 0x08, 0x89, 0xdb, 0xbe, 0x99, 0x79, + 0xf3, 0xc6, 0x23, 0xe3, 0xdd, 0x30, 0x52, 0xc3, 0xb4, 0xef, 0x06, 0xfc, 0xc4, 0xeb, 0x0d, 0xa1, + 0x37, 0x8c, 0xe2, 0x50, 0xee, 0x81, 0x9a, 0x70, 0x71, 0xec, 0x29, 0x15, 0x7b, 0x34, 0x89, 0xbc, + 0x44, 0x70, 0xc5, 0x03, 0x3e, 0xf2, 0x46, 0x5c, 0xd0, 0x09, 0x8d, 0x3d, 0x06, 0xe3, 0x28, 0x80, + 0x23, 0xca, 0x98, 0x00, 0x29, 0x5d, 0xcd, 0x93, 0x92, 0x61, 0xed, 0xff, 0x0b, 0x9e, 0x21, 0x0f, + 0x79, 0xb6, 0xdf, 0x4f, 0x07, 0x7a, 0xd2, 0x83, 0x7e, 0x65, 0x7b, 0x8d, 0xef, 0xb8, 0xb6, 0x2f, + 0x60, 0x10, 0x9d, 0x81, 0xf4, 0xe1, 0x34, 0x05, 0xa9, 0x1a, 0x97, 0x08, 0x7f, 0x9b, 0x63, 0x32, + 0xe1, 0xb1, 0x04, 0xb2, 0x85, 0xcb, 0x89, 0xc1, 0x2c, 0x54, 0x2f, 0x34, 0xab, 0xad, 0x7f, 0xae, + 0x39, 0xe9, 0x3e, 0x15, 0x1b, 0xa0, 0x4b, 0x93, 0x24, 0x8a, 0x43, 0x3f, 0x5f, 0xb4, 0xd7, 0xf1, + 0x97, 0x25, 0x8a, 0xfc, 0xc4, 0xc5, 0x8c, 0xb4, 0x50, 0x1d, 0x35, 0x2b, 0xbe, 0x99, 0xc8, 0x0f, + 0xfc, 0x29, 0x95, 0x34, 0x04, 0xeb, 0x63, 0xbd, 0xd0, 0xac, 0xf8, 0xd9, 0xd0, 0xf8, 0x8b, 0xbf, + 0x6e, 0xc3, 0x78, 0x93, 0x31, 0x61, 0xa2, 0xce, 0x75, 0x68, 0x51, 0xc7, 0x70, 0x2d, 0xd7, 0x99, + 0xf8, 0x07, 0xb8, 0xcc, 0x60, 0xac, 0x3b, 0xd3, 0xa7, 0x3e, 0xb7, 0xd7, 0x6e, 0xef, 0xfe, 0xb4, + 0xde, 0xea, 0x3f, 0xe0, 0x02, 0x3c, 0x75, 0x9e, 0x80, 0x74, 0x67, 0x8e, 0x25, 0x96, 0x3d, 0x5a, + 0x17, 0x28, 0x8f, 0xd3, 0xa5, 0x31, 0x0d, 0x41, 0x90, 0x36, 0xae, 0x76, 0x40, 0xcd, 0xea, 0x20, + 0xd6, 0x0b, 0x0d, 0xe9, 0xdc, 0xf6, 0xef, 0x57, 0xbb, 0x23, 0x1b, 0x18, 0x77, 0x40, 0x19, 0x63, + 0xf2, 0x2b, 0x17, 0x2e, 0x7f, 0xb9, 0x6d, 0x3d, 0x27, 0x32, 0x83, 0xf6, 0xce, 0xf5, 0xd4, 0x41, + 0x37, 0x53, 0x07, 0xdd, 0x4f, 0x1d, 0x74, 0xf5, 0xe0, 0x7c, 0x38, 0x5c, 0x7d, 0xcf, 0x6f, 0xd6, + 0x2f, 0x6a, 0x64, 0xe5, 0x31, 0x00, 0x00, 0xff, 0xff, 0x04, 0x47, 0x60, 0xc6, 0xa5, 0x02, 0x00, + 0x00, } diff --git a/api/protocol/lorawan/device_address.proto b/api/protocol/lorawan/device_address.proto index e2f320319..7de5a3961 100644 --- a/api/protocol/lorawan/device_address.proto +++ b/api/protocol/lorawan/device_address.proto @@ -7,6 +7,8 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; package lorawan; +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; + message PrefixesRequest {} message PrefixesResponse { diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 994d64594..a9d24ee39 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -129,7 +129,7 @@ type ActivationMetadata struct { Rx1DrOffset uint32 `protobuf:"varint,11,opt,name=rx1_dr_offset,json=rx1DrOffset,proto3" json:"rx1_dr_offset,omitempty"` Rx2Dr uint32 `protobuf:"varint,12,opt,name=rx2_dr,json=rx2Dr,proto3" json:"rx2_dr,omitempty"` RxDelay uint32 `protobuf:"varint,13,opt,name=rx_delay,json=rxDelay,proto3" json:"rx_delay,omitempty"` - CfList []uint64 `protobuf:"varint,14,rep,name=cf_list,json=cfList" json:"cf_list,omitempty"` + CfList []uint64 `protobuf:"varint,14,rep,packed,name=cf_list,json=cfList" json:"cf_list,omitempty"` } func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } @@ -305,11 +305,21 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { i = encodeVarintLorawan(data, i, uint64(m.RxDelay)) } if len(m.CfList) > 0 { + data6 := make([]byte, len(m.CfList)*10) + var j5 int for _, num := range m.CfList { - data[i] = 0x70 - i++ - i = encodeVarintLorawan(data, i, uint64(num)) + for num >= 1<<7 { + data6[j5] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j5++ + } + data6[j5] = uint8(num) + j5++ } + data[i] = 0x72 + i++ + i = encodeVarintLorawan(data, i, uint64(j5)) + i += copy(data[i:], data6[:j5]) } return i, nil } @@ -416,9 +426,11 @@ func (m *ActivationMetadata) Size() (n int) { n += 1 + sovLorawan(uint64(m.RxDelay)) } if len(m.CfList) > 0 { + l = 0 for _, e := range m.CfList { - n += 1 + sovLorawan(uint64(e)) + l += sovLorawan(uint64(e)) } + n += 1 + sovLorawan(uint64(l)) + l } return n } @@ -981,25 +993,67 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } } case 14: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) - } - var v uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowLorawan + if wireType == 2 { + var packedLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + packedLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } } - if iNdEx >= l { + if packedLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + packedLen + if postIndex > l { return io.ErrUnexpectedEOF } - b := data[iNdEx] - iNdEx++ - v |= (uint64(b) & 0x7F) << shift - if b < 0x80 { - break + for iNdEx < postIndex { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.CfList = append(m.CfList, v) } + } else if wireType == 0 { + var v uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.CfList = append(m.CfList, v) + } else { + return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) } - m.CfList = append(m.CfList, v) default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -1131,43 +1185,43 @@ func init() { } var fileDescriptorLorawan = []byte{ - // 601 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x93, 0xcf, 0x6e, 0xda, 0x4e, - 0x10, 0xc7, 0xe3, 0x40, 0x6c, 0x32, 0x09, 0xc4, 0x72, 0xf4, 0xd3, 0xcf, 0x6d, 0x25, 0x40, 0x39, - 0xa1, 0x48, 0xe5, 0x8f, 0x49, 0x02, 0x1c, 0x49, 0xa0, 0x52, 0x95, 0x84, 0xa8, 0x26, 0x9c, 0x57, - 0x8b, 0xbd, 0x76, 0x56, 0x10, 0xaf, 0xb5, 0x2c, 0x60, 0x6e, 0x7d, 0x8c, 0xbe, 0x44, 0x6f, 0x7d, - 0x88, 0xaa, 0xa7, 0x9e, 0x73, 0x88, 0xaa, 0xf4, 0x45, 0xaa, 0x5d, 0x87, 0x34, 0xb7, 0xaa, 0xb9, - 0xf5, 0xc4, 0x7c, 0xe7, 0x3b, 0xf3, 0xd9, 0x11, 0x33, 0x86, 0xd3, 0x90, 0x8a, 0x9b, 0xf9, 0xb8, - 0xea, 0xb1, 0xdb, 0xda, 0xf5, 0x0d, 0xb9, 0xbe, 0xa1, 0x51, 0x38, 0x1b, 0x10, 0xb1, 0x64, 0x7c, - 0x52, 0x13, 0x22, 0xaa, 0xe1, 0x98, 0xd6, 0x62, 0xce, 0x04, 0xf3, 0xd8, 0xb4, 0x36, 0x65, 0x1c, - 0x2f, 0x71, 0xb4, 0xfe, 0xad, 0x2a, 0xc3, 0x32, 0x1e, 0xe5, 0xeb, 0xb7, 0xcf, 0x60, 0x21, 0x0b, - 0x59, 0xda, 0x38, 0x9e, 0x07, 0x4a, 0x29, 0xa1, 0xa2, 0xb4, 0xef, 0xe0, 0xb3, 0x06, 0xb9, 0x4b, - 0x22, 0xb0, 0x8f, 0x05, 0xb6, 0x9a, 0x00, 0xb7, 0xcc, 0x9f, 0x4f, 0xb1, 0xa0, 0x2c, 0xb2, 0x77, - 0xca, 0x5a, 0xa5, 0xe0, 0xec, 0x57, 0xd7, 0x0f, 0x5d, 0x3e, 0x59, 0xee, 0xb3, 0x32, 0xeb, 0x0d, - 0x6c, 0xcb, 0x66, 0xc4, 0xb1, 0x20, 0xf6, 0x6e, 0x59, 0xab, 0x6c, 0xbb, 0x39, 0x99, 0x70, 0xb1, - 0x20, 0xd6, 0x2b, 0xc8, 0x8d, 0xa9, 0x48, 0xbd, 0x7c, 0x59, 0xab, 0xe4, 0x5d, 0x63, 0x4c, 0x85, - 0xb2, 0x4a, 0xb0, 0xe3, 0x31, 0x9f, 0x46, 0x61, 0xea, 0x16, 0x54, 0x27, 0xa4, 0x29, 0x55, 0xb0, - 0x0f, 0x5b, 0x01, 0xf2, 0x22, 0x61, 0xef, 0xa9, 0xc6, 0x6c, 0x70, 0x16, 0x89, 0x83, 0x2f, 0x1a, - 0xec, 0x5d, 0x27, 0x67, 0x2c, 0x0a, 0x68, 0x38, 0xe7, 0xe9, 0x04, 0xff, 0xc0, 0xd8, 0xdf, 0x32, - 0x60, 0x75, 0x3d, 0x41, 0x17, 0xea, 0xf1, 0xa7, 0x3f, 0x7c, 0x00, 0x06, 0x8e, 0x63, 0x44, 0xe6, - 0xd4, 0xd6, 0xca, 0x5a, 0x65, 0xf7, 0xf4, 0xf8, 0xee, 0xbe, 0xd4, 0xf8, 0xd3, 0x39, 0x78, 0x8c, - 0x93, 0x9a, 0x58, 0xc5, 0x64, 0x56, 0xed, 0xc6, 0x71, 0x7f, 0xf4, 0xde, 0xd5, 0x71, 0x1c, 0xf7, - 0xe7, 0x54, 0xf2, 0x7c, 0xb2, 0x50, 0xbc, 0xcd, 0x17, 0xf1, 0x7a, 0x64, 0xa1, 0x78, 0x3e, 0x59, - 0x48, 0xde, 0x07, 0xc8, 0x49, 0x1e, 0xf6, 0x7d, 0x6e, 0x67, 0x14, 0xf0, 0xe4, 0xee, 0xbe, 0xe4, - 0xfc, 0x1d, 0xb0, 0xeb, 0xfb, 0xdc, 0x95, 0x73, 0xc9, 0xc0, 0x72, 0x61, 0x3b, 0x5a, 0x4e, 0xd0, - 0x0c, 0x4d, 0xc8, 0xca, 0xce, 0xbe, 0x88, 0x39, 0x58, 0x4e, 0x86, 0xe7, 0x64, 0xe5, 0x1a, 0x51, - 0x1a, 0x58, 0x07, 0x90, 0xe7, 0x49, 0x03, 0xf9, 0x1c, 0xb1, 0x20, 0x98, 0x11, 0xa1, 0x6e, 0x20, - 0xef, 0xee, 0xf0, 0xa4, 0xd1, 0xe3, 0x57, 0x2a, 0x65, 0xfd, 0x07, 0x3a, 0x4f, 0x1c, 0xe4, 0x73, - 0xb5, 0xec, 0xbc, 0xbb, 0xc5, 0x13, 0xa7, 0xc7, 0xe5, 0xa6, 0x79, 0x82, 0x7c, 0x32, 0xc5, 0xab, - 0xf5, 0xa6, 0x79, 0xd2, 0x93, 0xd2, 0xfa, 0x1f, 0x0c, 0x2f, 0x40, 0x53, 0x3a, 0x13, 0x76, 0xa1, - 0x9c, 0xa9, 0x64, 0x5d, 0xdd, 0x0b, 0x2e, 0xe8, 0x4c, 0x1c, 0x96, 0x00, 0x7e, 0x1f, 0x95, 0x95, - 0x83, 0xec, 0xc5, 0x95, 0xdb, 0x35, 0x37, 0x2c, 0x03, 0x32, 0xef, 0x86, 0xe7, 0xa6, 0x76, 0xf8, - 0x51, 0x03, 0xdd, 0x25, 0xa1, 0x74, 0x0b, 0x00, 0xfd, 0x11, 0x6a, 0x9f, 0x34, 0x51, 0xbb, 0x55, - 0x37, 0x37, 0xa4, 0x1e, 0x0d, 0x51, 0xa7, 0xee, 0xa0, 0x8e, 0xd3, 0x36, 0x35, 0xa9, 0xcf, 0x06, - 0xa8, 0xd5, 0xea, 0xa0, 0x56, 0xbb, 0x65, 0x6e, 0x5a, 0x00, 0x7a, 0x7f, 0x84, 0x8e, 0x9a, 0x4d, - 0x33, 0x23, 0xbd, 0xee, 0x08, 0x75, 0x1a, 0xc7, 0xaa, 0x36, 0xfb, 0x58, 0x7b, 0xd4, 0xaa, 0xa3, - 0xe3, 0x46, 0xdd, 0xdc, 0x92, 0xb5, 0xdd, 0x21, 0xea, 0x38, 0x4d, 0x53, 0x97, 0xde, 0xf0, 0x1c, - 0x75, 0x9c, 0xba, 0xd2, 0xc6, 0xa9, 0xf9, 0xf5, 0xa1, 0xa8, 0x7d, 0x7f, 0x28, 0x6a, 0x3f, 0x1e, - 0x8a, 0xda, 0xa7, 0x9f, 0xc5, 0x8d, 0xb1, 0xae, 0x3e, 0xf8, 0xe6, 0xaf, 0x00, 0x00, 0x00, 0xff, - 0xff, 0x91, 0xac, 0x02, 0x91, 0x6e, 0x04, 0x00, 0x00, + // 607 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x94, 0xcf, 0x4e, 0xdb, 0x4e, + 0x10, 0xc7, 0x31, 0x09, 0x76, 0x18, 0x48, 0xb0, 0x8c, 0x7e, 0xfa, 0xb9, 0xad, 0x94, 0x44, 0x9c, + 0x22, 0xa4, 0xe6, 0x8f, 0x03, 0x24, 0x39, 0x06, 0x12, 0xa4, 0x0a, 0x08, 0xaa, 0x43, 0x2e, 0xbd, + 0xac, 0x36, 0xf6, 0xda, 0xac, 0x12, 0xbc, 0xd6, 0x66, 0x93, 0x38, 0xb7, 0x3e, 0x46, 0x5f, 0xa2, + 0xb7, 0x3e, 0x44, 0xd5, 0x53, 0xcf, 0x1c, 0x50, 0x45, 0x5f, 0xa4, 0xda, 0x35, 0x50, 0x6e, 0x55, + 0xb9, 0xf5, 0x94, 0xf9, 0xce, 0x77, 0xe6, 0xb3, 0xa3, 0xcc, 0xc8, 0x70, 0x1c, 0x52, 0x71, 0x3d, + 0x1f, 0x57, 0x3d, 0x76, 0x53, 0xbb, 0xba, 0x26, 0x57, 0xd7, 0x34, 0x0a, 0x67, 0x03, 0x22, 0x96, + 0x8c, 0x4f, 0x6a, 0x42, 0x44, 0x35, 0x1c, 0xd3, 0x5a, 0xcc, 0x99, 0x60, 0x1e, 0x9b, 0xd6, 0xa6, + 0x8c, 0xe3, 0x25, 0x8e, 0x1e, 0x7f, 0xab, 0xca, 0xb0, 0x8c, 0x07, 0xf9, 0xfa, 0xed, 0x33, 0x58, + 0xc8, 0x42, 0x96, 0x36, 0x8e, 0xe7, 0x81, 0x52, 0x4a, 0xa8, 0x28, 0xed, 0xdb, 0xfb, 0xac, 0x41, + 0xee, 0x82, 0x08, 0xec, 0x63, 0x81, 0xad, 0x26, 0xc0, 0x0d, 0xf3, 0xe7, 0x53, 0x2c, 0x28, 0x8b, + 0xec, 0xad, 0xb2, 0x56, 0x29, 0x38, 0xbb, 0xd5, 0xc7, 0x87, 0x2e, 0x9e, 0x2c, 0xf7, 0x59, 0x99, + 0xf5, 0x06, 0x36, 0x65, 0x33, 0xe2, 0x58, 0x10, 0x7b, 0xbb, 0xac, 0x55, 0x36, 0xdd, 0x9c, 0x4c, + 0xb8, 0x58, 0x10, 0xeb, 0x15, 0xe4, 0xc6, 0x54, 0xa4, 0x5e, 0xbe, 0xac, 0x55, 0xf2, 0xae, 0x31, + 0xa6, 0x42, 0x59, 0x25, 0xd8, 0xf2, 0x98, 0x4f, 0xa3, 0x30, 0x75, 0x0b, 0xaa, 0x13, 0xd2, 0x94, + 0x2a, 0xd8, 0x85, 0x8d, 0x00, 0x79, 0x91, 0xb0, 0x77, 0x54, 0x63, 0x36, 0x38, 0x89, 0xc4, 0xde, + 0x17, 0x0d, 0x76, 0xae, 0x92, 0x13, 0x16, 0x05, 0x34, 0x9c, 0xf3, 0x74, 0x82, 0x7f, 0x60, 0xec, + 0x6f, 0x19, 0xb0, 0xba, 0x9e, 0xa0, 0x0b, 0xf5, 0xf8, 0xd3, 0x1f, 0x3e, 0x00, 0x03, 0xc7, 0x31, + 0x22, 0x73, 0x6a, 0x6b, 0x65, 0xad, 0xb2, 0x7d, 0x7c, 0x78, 0x7b, 0x57, 0x6a, 0xfc, 0xe9, 0x1c, + 0x3c, 0xc6, 0x49, 0x4d, 0xac, 0x62, 0x32, 0xab, 0x76, 0xe3, 0xb8, 0x3f, 0x7a, 0xe7, 0xea, 0x38, + 0x8e, 0xfb, 0x73, 0x2a, 0x79, 0x3e, 0x59, 0x28, 0xde, 0xfa, 0x8b, 0x78, 0x3d, 0xb2, 0x50, 0x3c, + 0x9f, 0x2c, 0x24, 0xef, 0x3d, 0xe4, 0x24, 0x0f, 0xfb, 0x3e, 0xb7, 0x33, 0x0a, 0x78, 0x74, 0x7b, + 0x57, 0x72, 0xfe, 0x0e, 0xd8, 0xf5, 0x7d, 0xee, 0xca, 0xb9, 0x64, 0x60, 0xb9, 0xb0, 0x19, 0x2d, + 0x27, 0x68, 0x86, 0x26, 0x64, 0x65, 0x67, 0x5f, 0xc4, 0x1c, 0x2c, 0x27, 0xc3, 0x33, 0xb2, 0x72, + 0x8d, 0x28, 0x0d, 0xac, 0x3d, 0xc8, 0xf3, 0xa4, 0x81, 0x7c, 0x8e, 0x58, 0x10, 0xcc, 0x88, 0x50, + 0x37, 0x90, 0x77, 0xb7, 0x78, 0xd2, 0xe8, 0xf1, 0x4b, 0x95, 0xb2, 0xfe, 0x03, 0x9d, 0x27, 0x0e, + 0xf2, 0xb9, 0x5a, 0x76, 0xde, 0xdd, 0xe0, 0x89, 0xd3, 0xe3, 0x72, 0xd3, 0x3c, 0x41, 0x3e, 0x99, + 0xe2, 0xd5, 0xe3, 0xa6, 0x79, 0xd2, 0x93, 0xd2, 0xfa, 0x1f, 0x0c, 0x2f, 0x40, 0x53, 0x3a, 0x13, + 0x76, 0xa1, 0x9c, 0xa9, 0x64, 0x5d, 0xdd, 0x0b, 0xce, 0xe9, 0x4c, 0xec, 0x97, 0x00, 0x7e, 0x1f, + 0x95, 0x95, 0x83, 0xec, 0xf9, 0xa5, 0xdb, 0x35, 0xd7, 0x2c, 0x03, 0x32, 0xa7, 0xc3, 0x33, 0x53, + 0xdb, 0xff, 0xa8, 0x81, 0xee, 0x92, 0x50, 0xba, 0x05, 0x80, 0xfe, 0x08, 0xb5, 0x8f, 0x9a, 0xa8, + 0xdd, 0xaa, 0x9b, 0x6b, 0x52, 0x8f, 0x86, 0xa8, 0x53, 0x77, 0x50, 0xc7, 0x69, 0x9b, 0x9a, 0xd4, + 0x27, 0x03, 0xd4, 0x6a, 0x75, 0x50, 0xab, 0xdd, 0x32, 0xd7, 0x2d, 0x00, 0xbd, 0x3f, 0x42, 0x07, + 0xcd, 0xa6, 0x99, 0x91, 0x5e, 0x77, 0x84, 0x3a, 0x8d, 0x43, 0x55, 0x9b, 0x7d, 0xa8, 0x3d, 0x68, + 0xd5, 0xd1, 0x61, 0xa3, 0x6e, 0x6e, 0xc8, 0xda, 0xee, 0x10, 0x75, 0x9c, 0xa6, 0xa9, 0x4b, 0x6f, + 0x78, 0x86, 0x3a, 0x4e, 0x5d, 0x69, 0xe3, 0xf8, 0xf4, 0xeb, 0x7d, 0x51, 0xfb, 0x7e, 0x5f, 0xd4, + 0x7e, 0xdc, 0x17, 0xb5, 0x4f, 0x3f, 0x8b, 0x6b, 0x1f, 0x0e, 0x5e, 0xf2, 0x95, 0x19, 0xeb, 0x2a, + 0xd3, 0xfc, 0x15, 0x00, 0x00, 0xff, 0xff, 0x84, 0xe2, 0x37, 0xdc, 0xa4, 0x04, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index 4337a8079..ab71692bd 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -7,6 +7,8 @@ import "github.com/gogo/protobuf/gogoproto/gogo.proto"; package lorawan; +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; + enum Modulation { LORA = 0; FSK = 1; diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 077ad4fa8..165fcdcd8 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -882,19 +882,19 @@ func init() { } var fileDescriptorProtocol = []byte{ - // 215 bytes of a gzipped FileDescriptorProto + // 219 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0x81, 0x33, 0xf4, 0xc0, 0x0c, 0x21, 0x0e, 0x18, - 0x5f, 0xca, 0x89, 0x24, 0x63, 0x72, 0xf2, 0x8b, 0x12, 0xcb, 0x13, 0xf3, 0x60, 0x34, 0xc4, 0x34, - 0x25, 0x77, 0x2e, 0xae, 0xa0, 0x0a, 0xdf, 0xd4, 0x92, 0xc4, 0x94, 0xc4, 0x92, 0x44, 0x21, 0x5d, - 0x2e, 0x76, 0xa8, 0xb4, 0x04, 0xa3, 0x02, 0xa3, 0x06, 0xb7, 0x91, 0xa0, 0x1e, 0x4c, 0x39, 0x4c, - 0x8d, 0x07, 0x43, 0x10, 0x4c, 0x8d, 0x13, 0x17, 0x17, 0xdc, 0x31, 0x4a, 0xc1, 0x5c, 0xfc, 0x21, - 0x15, 0xce, 0xf9, 0x79, 0x69, 0x99, 0xe9, 0xa5, 0x45, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x42, 0x26, - 0xe8, 0xa6, 0x49, 0xc0, 0x4d, 0x43, 0x53, 0x8a, 0xcb, 0xd0, 0x48, 0x2e, 0x21, 0xc7, 0xe4, 0x92, - 0xcc, 0x32, 0xb0, 0x22, 0xb8, 0x2b, 0xcd, 0xd1, 0xcd, 0x95, 0x86, 0x9b, 0x8b, 0xa9, 0x1a, 0x87, - 0xd1, 0x4e, 0x02, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0xe3, - 0x8c, 0xc7, 0x72, 0x0c, 0x49, 0x6c, 0x60, 0x39, 0x63, 0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x86, - 0x56, 0x63, 0x9b, 0x9e, 0x01, 0x00, 0x00, + 0x5f, 0x4a, 0x0d, 0x43, 0x69, 0x4e, 0x7e, 0x51, 0x62, 0x79, 0x62, 0x1e, 0x8c, 0x86, 0xe8, 0x50, + 0x72, 0xe7, 0xe2, 0x0a, 0xaa, 0xf0, 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x14, 0xd2, 0xe5, + 0x62, 0x87, 0x4a, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0xea, 0xc1, 0x94, 0xc3, 0xd4, + 0x78, 0x30, 0x04, 0xc1, 0xd4, 0x38, 0x71, 0x71, 0xc1, 0x2d, 0x54, 0x0a, 0xe6, 0xe2, 0x0f, 0xa9, + 0x70, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0x2f, 0x2d, 0x4a, 0x2c, 0xc9, 0xcc, 0xcf, 0x13, 0x32, 0x41, + 0x37, 0x4d, 0x02, 0x6e, 0x1a, 0x9a, 0x52, 0x5c, 0x86, 0x46, 0x72, 0x09, 0x39, 0x26, 0x97, 0x64, + 0x96, 0x81, 0x15, 0xc1, 0x5d, 0x69, 0x8e, 0x6e, 0xae, 0x34, 0xdc, 0x5c, 0x4c, 0xd5, 0x38, 0x8c, + 0x76, 0xb2, 0x3b, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, + 0x3c, 0x96, 0x63, 0x88, 0xd2, 0x21, 0x25, 0xe4, 0x93, 0xd8, 0xc0, 0x2c, 0x63, 0x40, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x24, 0x8c, 0x89, 0xc2, 0xb0, 0x01, 0x00, 0x00, } diff --git a/api/protocol/protocol.proto b/api/protocol/protocol.proto index d10cacfe6..e8803ad8a 100644 --- a/api/protocol/protocol.proto +++ b/api/protocol/protocol.proto @@ -3,7 +3,9 @@ syntax = "proto3"; -import "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/lorawan.proto"; +import "ttn/api/protocol/lorawan/lorawan.proto"; + +option go_package = "github.com/TheThingsNetwork/ttn/api/protocol"; package protocol; diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 846712963..48049c896 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -2398,56 +2398,57 @@ func init() { } var fileDescriptorRouter = []byte{ - // 815 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0x1b, 0x45, - 0x14, 0x67, 0x83, 0xb4, 0x8d, 0x5f, 0xb2, 0xb1, 0x3b, 0x8d, 0xd3, 0xc5, 0xa5, 0x6e, 0xb4, 0x07, - 0xb0, 0x04, 0x5d, 0x13, 0xa3, 0x0a, 0x15, 0x24, 0x44, 0xd2, 0x44, 0x55, 0x25, 0x5c, 0xa1, 0x75, - 0x7a, 0x44, 0xd6, 0x78, 0xfd, 0xba, 0x59, 0xc5, 0xde, 0x59, 0x76, 0x66, 0x9d, 0xfa, 0x5b, 0xc0, - 0x8d, 0x0f, 0xc1, 0x07, 0xe1, 0xc0, 0x81, 0x13, 0x07, 0x0e, 0x08, 0x85, 0x3b, 0x9f, 0x01, 0xed, - 0xfc, 0x59, 0x7b, 0x1d, 0x27, 0x04, 0x7a, 0xb2, 0xe7, 0xf7, 0x7e, 0xef, 0xb7, 0xbf, 0x79, 0xf3, - 0xe6, 0x0d, 0x7c, 0x16, 0xc5, 0xe2, 0x2c, 0x1f, 0xf9, 0x21, 0x9b, 0x76, 0x4f, 0xcf, 0xf0, 0xf4, - 0x2c, 0x4e, 0x22, 0xfe, 0x12, 0xc5, 0x05, 0xcb, 0xce, 0xbb, 0x42, 0x24, 0x5d, 0x9a, 0xc6, 0xdd, - 0x8c, 0xe5, 0x02, 0x33, 0xfd, 0xe3, 0xa7, 0x19, 0x13, 0x8c, 0xd8, 0x6a, 0xd5, 0x7a, 0x10, 0x31, - 0x16, 0x4d, 0xb0, 0x2b, 0xd1, 0x51, 0xfe, 0xba, 0x8b, 0xd3, 0x54, 0xcc, 0x15, 0xa9, 0xf5, 0x78, - 0x49, 0x3d, 0x62, 0x11, 0x5b, 0xb0, 0x8a, 0x95, 0x5c, 0xc8, 0x7f, 0x6b, 0xe8, 0xd7, 0x9a, 0xa1, - 0x69, 0xac, 0xe9, 0x5f, 0xdc, 0x86, 0x2e, 0xa9, 0x21, 0x9b, 0x94, 0x7f, 0x74, 0xf2, 0xd3, 0xdb, - 0x24, 0x47, 0x54, 0xe0, 0x05, 0x9d, 0x9b, 0x5f, 0x95, 0xea, 0x11, 0x68, 0x0c, 0xf2, 0x11, 0x0f, - 0xb3, 0x78, 0x84, 0x01, 0x7e, 0x97, 0x23, 0x17, 0xde, 0x4f, 0x16, 0x38, 0xaf, 0xd2, 0x49, 0x9c, - 0x9c, 0xf7, 0x91, 0x73, 0x1a, 0x21, 0x71, 0xe1, 0x4e, 0x4a, 0xe7, 0x13, 0x46, 0xc7, 0xae, 0xb5, - 0x6f, 0x75, 0xb6, 0x03, 0xb3, 0x24, 0x87, 0x70, 0xd7, 0x98, 0x19, 0x4e, 0x51, 0xd0, 0x31, 0x15, - 0xd4, 0xdd, 0xda, 0xb7, 0x3a, 0x5b, 0xbd, 0x5d, 0xbf, 0xb4, 0x19, 0xbc, 0xe9, 0xeb, 0x58, 0xd0, - 0x30, 0xa0, 0x41, 0xc8, 0x97, 0xd0, 0xd0, 0x9e, 0x16, 0x0a, 0xdb, 0x52, 0xe1, 0x9e, 0x6f, 0xcc, - 0x2e, 0x09, 0xd4, 0x35, 0x66, 0x00, 0xef, 0x17, 0x0b, 0xea, 0xc7, 0xec, 0x22, 0xb9, 0x9d, 0xe1, - 0x6f, 0x60, 0xaf, 0x34, 0x1c, 0xb2, 0xe4, 0x75, 0x1c, 0xe5, 0x19, 0x15, 0x31, 0x4b, 0xb4, 0xeb, - 0xf7, 0x16, 0xae, 0x4f, 0xdf, 0x3c, 0x5b, 0x26, 0x04, 0x4d, 0x13, 0xa9, 0xc0, 0xa4, 0x0f, 0x4d, - 0xe3, 0xbf, 0x2a, 0xa8, 0x36, 0xe1, 0x96, 0x9b, 0x58, 0xd5, 0xdb, 0xd5, 0x81, 0x0a, 0xea, 0xfd, - 0xb6, 0x01, 0xf7, 0x8f, 0x71, 0x16, 0x87, 0x78, 0x18, 0x8a, 0x78, 0xa6, 0xa8, 0xea, 0x64, 0x6e, - 0xd8, 0xd6, 0x4b, 0xb8, 0x33, 0xc6, 0xd9, 0x10, 0xf3, 0x58, 0xee, 0x63, 0xfb, 0xe8, 0xc9, 0xef, - 0x7f, 0x3c, 0x3a, 0xf8, 0xb7, 0xbe, 0x08, 0x59, 0x86, 0x5d, 0x31, 0x4f, 0x91, 0xfb, 0xc7, 0x38, - 0x3b, 0x79, 0xf5, 0x22, 0xb0, 0xc7, 0x38, 0x3b, 0xc9, 0xe3, 0x42, 0x8f, 0xa6, 0xa9, 0xd4, 0xdb, - 0xfe, 0x5f, 0x7a, 0x87, 0x69, 0x2a, 0xf5, 0x68, 0x9a, 0x16, 0x7a, 0x6b, 0xfb, 0xa4, 0xf9, 0xd6, - 0x7d, 0xb2, 0xf7, 0x1f, 0xfa, 0xa4, 0x05, 0xee, 0xd5, 0xba, 0xf2, 0x94, 0x25, 0x1c, 0xbd, 0x27, - 0xb0, 0xfb, 0x5c, 0xd1, 0x07, 0x82, 0x8a, 0x9c, 0x9b, 0x82, 0x3f, 0x04, 0x30, 0xdf, 0x8c, 0x55, - 0xcd, 0x6b, 0x41, 0x4d, 0x23, 0x2f, 0xc6, 0xde, 0xb7, 0xd0, 0x5c, 0x49, 0x53, 0x7a, 0xe4, 0x01, - 0xd4, 0x26, 0x94, 0x8b, 0x21, 0x47, 0x4c, 0x64, 0xda, 0xbb, 0xc1, 0x66, 0x01, 0x0c, 0x10, 0x13, - 0xf2, 0x21, 0xd8, 0x5c, 0xd2, 0xdd, 0x0d, 0x69, 0xbf, 0x5e, 0xda, 0xd7, 0x2a, 0x3a, 0xec, 0xd5, - 0xc1, 0xa9, 0xd8, 0xf1, 0xfe, 0xde, 0x00, 0x5b, 0x21, 0xa4, 0x03, 0x36, 0x9f, 0x73, 0x81, 0x53, - 0x29, 0xbf, 0xd5, 0x6b, 0xf8, 0xc5, 0x30, 0x19, 0x48, 0xa8, 0xa0, 0x14, 0x2a, 0x72, 0x41, 0x0e, - 0xa0, 0x16, 0xb2, 0x69, 0xca, 0x12, 0x4c, 0x84, 0xfe, 0xe2, 0x3d, 0x49, 0x7e, 0x66, 0x50, 0xc5, - 0x5f, 0xb0, 0xc8, 0x01, 0xec, 0x98, 0x6d, 0x6b, 0xa7, 0xea, 0x72, 0x80, 0xcc, 0x0b, 0xa8, 0x40, - 0x1e, 0x38, 0xd1, 0xf2, 0xce, 0x89, 0x07, 0x76, 0x2e, 0x67, 0x86, 0x6e, 0xfb, 0x65, 0xaa, 0x8e, - 0x90, 0x0f, 0x60, 0x73, 0xac, 0x2f, 0xaa, 0xeb, 0x5c, 0x61, 0x95, 0x31, 0xf2, 0x31, 0x6c, 0xd1, - 0xf2, 0x8c, 0xb8, 0xbb, 0x73, 0x85, 0xba, 0x1c, 0x26, 0x8f, 0x81, 0x84, 0x2c, 0x49, 0x30, 0x14, - 0x38, 0x1e, 0x6a, 0x53, 0x5c, 0xf6, 0x96, 0x13, 0xdc, 0x2d, 0x23, 0xfa, 0x9c, 0x38, 0xf9, 0x08, - 0x16, 0xe0, 0x70, 0x94, 0xb1, 0x73, 0xcc, 0xb8, 0xec, 0x23, 0x27, 0x68, 0x94, 0x81, 0x23, 0x85, - 0xf7, 0xbe, 0xdf, 0x00, 0x3b, 0x90, 0x8f, 0x03, 0xf9, 0x1c, 0x9c, 0xca, 0x59, 0x93, 0xd5, 0x63, - 0x6b, 0xed, 0xf9, 0xea, 0xfd, 0xf0, 0xcd, 0xcb, 0xe0, 0x9f, 0x14, 0xef, 0x47, 0xc7, 0x22, 0x4f, - 0xc1, 0x56, 0x03, 0x95, 0x34, 0x7d, 0xfd, 0xf2, 0x54, 0x06, 0xec, 0x0d, 0xa9, 0x5f, 0x41, 0xad, - 0x1c, 0xd0, 0xc4, 0x35, 0xd9, 0xab, 0x33, 0xbb, 0x75, 0xdf, 0x44, 0x56, 0x26, 0xe1, 0x27, 0x16, - 0xe9, 0xc3, 0xa6, 0xee, 0x78, 0x24, 0x8f, 0x4a, 0xda, 0xfa, 0x09, 0xd3, 0xda, 0xbf, 0x9e, 0xa0, - 0x5a, 0xbb, 0xf7, 0x83, 0x05, 0x8e, 0x2a, 0x49, 0x9f, 0x26, 0x34, 0xc2, 0x8c, 0x7c, 0xbd, 0x5a, - 0x99, 0xf7, 0x8d, 0xc8, 0xba, 0x3b, 0xd5, 0x7a, 0x78, 0x4d, 0x54, 0x5f, 0x9d, 0x1e, 0xd4, 0x9e, - 0xa3, 0xd0, 0x4a, 0x65, 0xb9, 0xaa, 0x12, 0x3b, 0x55, 0xf8, 0xa8, 0xf1, 0xf3, 0x65, 0xdb, 0xfa, - 0xf5, 0xb2, 0x6d, 0xfd, 0x79, 0xd9, 0xb6, 0x7e, 0xfc, 0xab, 0xfd, 0xce, 0xc8, 0x96, 0x85, 0xfc, - 0xf4, 0x9f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x78, 0xda, 0x25, 0xba, 0x14, 0x08, 0x00, 0x00, + // 818 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0xe3, 0x44, + 0x14, 0xc7, 0x45, 0xf2, 0x36, 0xaf, 0x71, 0x93, 0xce, 0x36, 0x5d, 0x93, 0xa5, 0x69, 0xe5, 0x03, + 0x44, 0xc0, 0x3a, 0x34, 0x68, 0x85, 0x40, 0x08, 0xd1, 0x6e, 0xab, 0xd5, 0x4a, 0x64, 0x85, 0x9c, + 0xee, 0x05, 0x09, 0x45, 0x13, 0xe7, 0xad, 0x6b, 0x35, 0xf1, 0x18, 0xcf, 0x38, 0xdd, 0x7c, 0x0b, + 0xb8, 0xf1, 0x21, 0xf8, 0x20, 0x1c, 0x38, 0x70, 0xe2, 0xc0, 0x01, 0xa1, 0x72, 0xe7, 0x33, 0x20, + 0xcf, 0x1f, 0x27, 0x4e, 0xbb, 0xcb, 0x02, 0xa7, 0x64, 0x7e, 0xef, 0x37, 0xbf, 0xf9, 0xbd, 0x37, + 0x6f, 0x9e, 0xe1, 0xe3, 0x28, 0x16, 0x17, 0xf9, 0xd8, 0x0f, 0xd9, 0xac, 0x77, 0x7e, 0x81, 0xe7, + 0x17, 0x71, 0x12, 0xf1, 0xa7, 0x28, 0xae, 0x58, 0x76, 0xd9, 0x13, 0x22, 0xe9, 0xd1, 0x34, 0xee, + 0x65, 0x2c, 0x17, 0x98, 0xe9, 0x1f, 0x3f, 0xcd, 0x98, 0x60, 0xc4, 0x56, 0xab, 0xf6, 0xfd, 0x88, + 0xb1, 0x68, 0x8a, 0x3d, 0x89, 0x8e, 0xf3, 0xe7, 0x3d, 0x9c, 0xa5, 0x62, 0xa1, 0x48, 0xed, 0x07, + 0x2b, 0xea, 0x11, 0x8b, 0xd8, 0x92, 0x55, 0xac, 0xe4, 0x42, 0xfe, 0xd3, 0xf4, 0x1d, 0x73, 0x20, + 0x4d, 0x63, 0x0d, 0x1d, 0x18, 0x48, 0x2e, 0x43, 0x36, 0x2d, 0xff, 0x68, 0xc2, 0xbe, 0x21, 0x44, + 0x54, 0xe0, 0x15, 0x5d, 0x98, 0x5f, 0x15, 0xf6, 0x08, 0x34, 0x87, 0xf9, 0x98, 0x87, 0x59, 0x3c, + 0xc6, 0x00, 0xbf, 0xcd, 0x91, 0x0b, 0xef, 0x47, 0x0b, 0x9c, 0x67, 0xe9, 0x34, 0x4e, 0x2e, 0x07, + 0xc8, 0x39, 0x8d, 0x90, 0xb8, 0x70, 0x27, 0xa5, 0x8b, 0x29, 0xa3, 0x13, 0xd7, 0x3a, 0xb4, 0xba, + 0xf5, 0xc0, 0x2c, 0xc9, 0x31, 0xec, 0x98, 0x03, 0x47, 0x33, 0x14, 0x74, 0x42, 0x05, 0x75, 0xb7, + 0x0e, 0xad, 0xee, 0x56, 0x7f, 0xd7, 0x2f, 0xad, 0x04, 0x2f, 0x06, 0x3a, 0x16, 0x34, 0x0d, 0x68, + 0x10, 0xf2, 0x39, 0x34, 0xb5, 0xa7, 0xa5, 0x42, 0x5d, 0x2a, 0xdc, 0xf5, 0x8d, 0xd9, 0x15, 0x81, + 0x86, 0xc6, 0x0c, 0xe0, 0xfd, 0x6c, 0x41, 0xe3, 0x94, 0x5d, 0x25, 0xaf, 0x67, 0xf8, 0x2b, 0xd8, + 0x2b, 0x0d, 0x87, 0x2c, 0x79, 0x1e, 0x47, 0x79, 0x46, 0x45, 0xcc, 0x12, 0xed, 0xfa, 0xad, 0xa5, + 0xeb, 0xf3, 0x17, 0x8f, 0x56, 0x09, 0x41, 0xcb, 0x44, 0x2a, 0x30, 0x19, 0x40, 0xcb, 0xf8, 0xaf, + 0x0a, 0xaa, 0x24, 0xdc, 0x32, 0x89, 0x75, 0xbd, 0x5d, 0x1d, 0xa8, 0xa0, 0xde, 0xaf, 0x1b, 0x70, + 0xef, 0x14, 0xe7, 0x71, 0x88, 0xc7, 0xa1, 0x88, 0xe7, 0x8a, 0xaa, 0x6e, 0xe6, 0x15, 0x69, 0x3d, + 0x85, 0x3b, 0x13, 0x9c, 0x8f, 0x30, 0x8f, 0x65, 0x1e, 0xf5, 0x93, 0x87, 0xbf, 0xfd, 0x7e, 0x70, + 0xf4, 0x4f, 0xcd, 0x1b, 0xb2, 0x0c, 0x7b, 0x62, 0x91, 0x22, 0xf7, 0x4f, 0x71, 0x7e, 0xf6, 0xec, + 0x49, 0x60, 0x4f, 0x70, 0x7e, 0x96, 0xc7, 0x85, 0x1e, 0x4d, 0x53, 0xa9, 0x57, 0xff, 0x4f, 0x7a, + 0xc7, 0x69, 0x2a, 0xf5, 0x68, 0x9a, 0x16, 0x7a, 0xb7, 0xf6, 0x49, 0xeb, 0x7f, 0xf7, 0xc9, 0xde, + 0xbf, 0xe8, 0x93, 0x36, 0xb8, 0x37, 0xeb, 0xca, 0x53, 0x96, 0x70, 0xf4, 0x1e, 0xc2, 0xee, 0x63, + 0x45, 0x1f, 0x0a, 0x2a, 0x72, 0x6e, 0x0a, 0xbe, 0x0f, 0x60, 0xce, 0x8c, 0x55, 0xcd, 0x6b, 0x41, + 0x4d, 0x23, 0x4f, 0x26, 0xde, 0x37, 0xd0, 0x5a, 0xdb, 0xa6, 0xf4, 0xc8, 0x7d, 0xa8, 0x4d, 0x29, + 0x17, 0x23, 0x8e, 0x98, 0xc8, 0x6d, 0x6f, 0x06, 0x9b, 0x05, 0x30, 0x44, 0x4c, 0xc8, 0xbb, 0x60, + 0x73, 0x49, 0x77, 0x37, 0xa4, 0xfd, 0x46, 0x69, 0x5f, 0xab, 0xe8, 0xb0, 0xd7, 0x00, 0xa7, 0x62, + 0xc7, 0xfb, 0x6b, 0x03, 0x6c, 0x85, 0x90, 0x2e, 0xd8, 0x7c, 0xc1, 0x05, 0xce, 0xa4, 0xfc, 0x56, + 0xbf, 0xe9, 0x17, 0x43, 0x61, 0x28, 0xa1, 0x82, 0x52, 0xa8, 0xc8, 0x05, 0x39, 0x82, 0x5a, 0xc8, + 0x66, 0x29, 0x4b, 0x30, 0x11, 0xfa, 0xc4, 0xbb, 0x92, 0xfc, 0xc8, 0xa0, 0x8a, 0xbf, 0x64, 0x91, + 0x23, 0xd8, 0x36, 0x69, 0x6b, 0xa7, 0xea, 0x71, 0x80, 0xdc, 0x17, 0x50, 0x81, 0x3c, 0x70, 0xa2, + 0xd5, 0xcc, 0x89, 0x07, 0x76, 0x2e, 0x67, 0x86, 0x6e, 0xfb, 0x55, 0xaa, 0x8e, 0x90, 0x77, 0x60, + 0x73, 0xa2, 0x1f, 0xaa, 0xeb, 0xdc, 0x60, 0x95, 0x31, 0xf2, 0x01, 0x6c, 0xd1, 0xf2, 0x8e, 0xb8, + 0xbb, 0x7d, 0x83, 0xba, 0x1a, 0x26, 0x0f, 0x80, 0x84, 0x2c, 0x49, 0x30, 0x14, 0x38, 0x19, 0x69, + 0x53, 0x5c, 0xf6, 0x96, 0x13, 0xec, 0x94, 0x11, 0x7d, 0x4f, 0x9c, 0xbc, 0x0f, 0x4b, 0x70, 0x34, + 0xce, 0xd8, 0x25, 0x66, 0x5c, 0xf6, 0x91, 0x13, 0x34, 0xcb, 0xc0, 0x89, 0xc2, 0xfb, 0xdf, 0x6d, + 0x80, 0x1d, 0xc8, 0x41, 0x4e, 0x3e, 0x05, 0xa7, 0x72, 0xd7, 0x64, 0xfd, 0xda, 0xda, 0x7b, 0xbe, + 0x9a, 0xf5, 0xbe, 0x99, 0xe2, 0xfe, 0x59, 0x31, 0xeb, 0xbb, 0x16, 0xf9, 0x04, 0x6c, 0x35, 0x50, + 0x49, 0xcb, 0xd7, 0x5f, 0x89, 0xca, 0x80, 0x7d, 0xc5, 0xd6, 0x2f, 0xa0, 0x56, 0x0e, 0x68, 0xe2, + 0x9a, 0xdd, 0xeb, 0x33, 0xbb, 0x7d, 0xcf, 0x44, 0xd6, 0x26, 0xe1, 0x87, 0x16, 0x19, 0xc0, 0xa6, + 0xee, 0x78, 0x24, 0x07, 0x25, 0xed, 0xf6, 0x09, 0xd3, 0x3e, 0x7c, 0x39, 0x41, 0xb5, 0x76, 0xff, + 0x7b, 0x0b, 0x1c, 0x55, 0x92, 0x01, 0x4d, 0x68, 0x84, 0x19, 0xf9, 0x72, 0xbd, 0x32, 0x6f, 0x1b, + 0x91, 0xdb, 0xde, 0x54, 0x7b, 0xff, 0x25, 0x51, 0xfd, 0x74, 0xfa, 0x50, 0x7b, 0x8c, 0x42, 0x2b, + 0x95, 0xe5, 0xaa, 0x4a, 0x6c, 0x57, 0xe1, 0x93, 0xcf, 0x7e, 0xba, 0xee, 0x58, 0xbf, 0x5c, 0x77, + 0xac, 0x3f, 0xae, 0x3b, 0xd6, 0x0f, 0x7f, 0x76, 0xde, 0xf8, 0xfa, 0xbd, 0xd7, 0xff, 0x6e, 0x8f, + 0x6d, 0x59, 0xf4, 0x8f, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x7d, 0xce, 0x7e, 0x6e, 0xec, 0x07, + 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index 7d30a25ef..57e5467ab 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -5,12 +5,14 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; -import "github.com/TheThingsNetwork/ttn/api/api.proto"; -import "github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto"; -import "github.com/TheThingsNetwork/ttn/api/gateway/gateway.proto"; +import "ttn/api/api.proto"; +import "ttn/api/protocol/protocol.proto"; +import "ttn/api/gateway/gateway.proto"; package router; +option go_package = "github.com/TheThingsNetwork/ttn/api/router"; + message SubscribeRequest {} message UplinkMessage { From ffc575efd80dfa29a489eea6028ed859a33e09a9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 21 Oct 2016 13:50:11 +0200 Subject: [PATCH 2018/2266] Update vendors --- vendor/vendor.json | 192 ++++++++++++++++++++++----------------------- 1 file changed, 96 insertions(+), 96 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 9073194bf..5ac9a698a 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,64 +9,64 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "nOFUap/+7NNA/tWGsTOzlXb1ZWU=", + "checksumSHA1": "xqWW+yCYPj3/94MAob6OkyaHcLc=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "ANS1jzUqsv1Q1X0gZNzSe9wY0Fo=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "kD6p9T1bgMU/33BG8czy2e9CJRw=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "8c1567b9ceabf1975fedab27714dc458dcfb21b7", - "revisionTime": "2016-10-18T12:27:50Z" + "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", + "revisionTime": "2016-10-18T16:08:10Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", - "revision": "0c68f91c240f5a01058bfd605923cd35aa37ff92", - "revisionTime": "2016-10-18T13:04:05Z" + "revision": "be802c4b3aa5d49dec780d4d5e2ba1e9bd25dba0", + "revisionTime": "2016-10-21T11:29:37Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", @@ -215,56 +215,56 @@ { "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", "path": "github.com/hashicorp/hcl", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { - "checksumSHA1": "un4pN4yL5bl6LL3CgWacFbIeHVg=", + "checksumSHA1": "croNloscHsjX87X+4/cKOURf1EY=", "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "fpQQdjFUZOoslYuFNKZMSO0N0ik=", "path": "github.com/hashicorp/hcl/json/parser", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", - "revision": "6f5bfed9a0a22222fbe4e731ae3481730ba41e93", - "revisionTime": "2016-10-08T07:35:57Z" + "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", + "revisionTime": "2016-10-19T17:07:01Z" }, { "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", @@ -321,10 +321,10 @@ "revisionTime": "2016-06-21T17:42:43Z" }, { - "checksumSHA1": "HzhmHrGdk67cMJ2xt5ToPxhwLWk=", + "checksumSHA1": "UuXgD2dDojfS8AViUEe15gLIWZE=", "path": "github.com/mitchellh/mapstructure", - "revision": "a6ef2f080c66d0a2e94e97cf74f80f772855da63", - "revisionTime": "2016-10-06T23:39:02Z" + "revision": "f3009df150dadf309fdee4a54ed65c124afad715", + "revisionTime": "2016-10-20T16:18:36Z" }, { "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", @@ -513,10 +513,10 @@ "revisionTime": "2016-03-01T12:00:06Z" }, { - "checksumSHA1": "1wuE9EGcNZMuQ1fWRZ4Z/TPXjzQ=", + "checksumSHA1": "GxPD7A0NjMDom1xte0mghkpzr0E=", "path": "github.com/spf13/pflag", - "revision": "bf8481a6aebc13a8aab52e699ffe2e79771f5a3f", - "revisionTime": "2016-10-11T12:08:26Z" + "revision": "dabebe21bf790f782ea4c7bbd2efc430de182afd", + "revisionTime": "2016-10-18T23:08:44Z" }, { "checksumSHA1": "2EeKIC5kUssQK8g49DOa78FoMgs=", @@ -551,80 +551,80 @@ { "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", "path": "golang.org/x/crypto/curve25519", - "revision": "14f9af67c679edd414f72f13d67c917447113df2", - "revisionTime": "2016-10-17T19:14:20Z" + "revision": "3ded668c5379f6951fb0de06174442072e5447d3", + "revisionTime": "2016-10-19T17:38:27Z" }, { "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "path": "golang.org/x/crypto/ed25519", - "revision": "14f9af67c679edd414f72f13d67c917447113df2", - "revisionTime": "2016-10-17T19:14:20Z" + "revision": "3ded668c5379f6951fb0de06174442072e5447d3", + "revisionTime": "2016-10-19T17:38:27Z" }, { "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "14f9af67c679edd414f72f13d67c917447113df2", - "revisionTime": "2016-10-17T19:14:20Z" + "revision": "3ded668c5379f6951fb0de06174442072e5447d3", + "revisionTime": "2016-10-19T17:38:27Z" }, { "checksumSHA1": "LlElMHeTC34ng8eHzjvtUhAgrr8=", "path": "golang.org/x/crypto/ssh", - "revision": "14f9af67c679edd414f72f13d67c917447113df2", - "revisionTime": "2016-10-17T19:14:20Z" + "revision": "3ded668c5379f6951fb0de06174442072e5447d3", + "revisionTime": "2016-10-19T17:38:27Z" }, { "checksumSHA1": "9C4Av3ypK5pi173F76ogJT/d8x4=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "14f9af67c679edd414f72f13d67c917447113df2", - "revisionTime": "2016-10-17T19:14:20Z" + "revision": "3ded668c5379f6951fb0de06174442072e5447d3", + "revisionTime": "2016-10-19T17:38:27Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { - "checksumSHA1": "TUt1YBoSgtklP3MKWuOJwXBrkRw=", + "checksumSHA1": "hcnPVnz/CBo9WkAovPzRkeHaxIU=", "path": "golang.org/x/net/http2", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "4MMbG0LI3ghvWooRn36RmDrFIB0=", "path": "golang.org/x/net/trace", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "O2sws+miRriMPObINsbwa0jnXxE=", "path": "golang.org/x/net/websocket", - "revision": "8b4af36cd21a1f85a7484b49feb7c79363106d8e", - "revisionTime": "2016-10-13T03:31:11Z" + "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", + "revisionTime": "2016-10-20T03:26:43Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", @@ -647,14 +647,14 @@ { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "a35b82c6add3543753645decf6532c40cac8b855", - "revisionTime": "2016-10-18T11:00:34Z" + "revision": "5a42fa2464759cbb7ee0af9de00b54d69f09a29c", + "revisionTime": "2016-10-16T16:34:30Z" }, { "checksumSHA1": "Aj3JSVO324FCjEAGm4ZwmC79bbo=", "path": "golang.org/x/text/unicode/norm", - "revision": "a35b82c6add3543753645decf6532c40cac8b855", - "revisionTime": "2016-10-18T11:00:34Z" + "revision": "5a42fa2464759cbb7ee0af9de00b54d69f09a29c", + "revisionTime": "2016-10-16T16:34:30Z" }, { "checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=", @@ -699,58 +699,58 @@ "revisionTime": "2016-10-14T20:17:33Z" }, { - "checksumSHA1": "D9M/719krq7NJYQu523UODpBKcI=", + "checksumSHA1": "jR/Ha3HGGTGZx7WGIRG9Qhq+sS4=", "path": "google.golang.org/grpc", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", "path": "google.golang.org/grpc/metadata", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "HQJrtiTtr5eiRsXQLut2R1Q9kuY=", "path": "google.golang.org/grpc/transport", - "revision": "b7f1379d3cbbbeb2ca3405852012e237aa05459e", - "revisionTime": "2016-10-17T23:02:05Z" + "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", + "revisionTime": "2016-10-21T00:52:52Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", From 67c791c7bc64ded18ce1aca1a1ea6d1a3e539732 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 21 Oct 2016 14:18:44 +0200 Subject: [PATCH 2019/2266] Clean up version cmds --- cmd/version.go | 51 ++++++++++++++++++++++++------------------- ttnctl/cmd/version.go | 51 ++++++++++++++++++++++++------------------- 2 files changed, 56 insertions(+), 46 deletions(-) diff --git a/cmd/version.go b/cmd/version.go index 2f4531f69..da1f97bc6 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -7,6 +7,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -18,41 +19,45 @@ var versionCmd = &cobra.Command{ Short: "Get build and version information", Long: `ttn version gets the build and version information of ttn`, Run: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("Version", viper.GetString("version")) - gitBranch := viper.GetString("gitBranch") - ctx = ctx.WithField("Branch", gitBranch) - gitCommit := viper.GetString("gitCommit") - ctx = ctx.WithField("Commit", gitCommit) - buildDate := viper.GetString("buildDate") - ctx = ctx.WithField("BuildDate", buildDate) + + ctx.WithFields(log.Fields{ + "Version": viper.GetString("version"), + "Branch": gitBranch, + "Commit": gitCommit, + "BuildDate": buildDate, + }).Info("Got build information") if gitBranch == unknown || gitCommit == unknown || buildDate == unknown { ctx.Warn("This is not an official ttn build") + ctx.Warn("If you're building ttn from source, you should use the Makefile") + return + } + + latest, err := version.GetLatestInfo() + if err != nil { + ctx.WithError(err).Warn("Could not get latest version information") + return + } + + if latest.Commit == gitCommit { + ctx.Info("This is an up-to-date ttn build") + return } - if gitBranch != unknown { - if version, err := version.GetLatestInfo(); err == nil { - if version.Commit == gitCommit { - ctx.Info("This is an up-to-date ttn build") - } else { - if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { - if buildDate.Before(version.Date) { - ctx.Warn("This is not an up-to-date ttn build") - ctx.Warnf("The newest build is %s newer.", version.Date.Sub(buildDate)) - } else { - ctx.Warn("This is not an official ttn build") - } - } - } + if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { + ctx.Warn("This is not the latest official ttn build") + if buildDate.Before(latest.Date) { + ctx.Warnf("The newest ttn build is %s newer.", latest.Date.Sub(buildDate)) } else { - ctx.Warn("Could not get latest version information") + ctx.Warn("Your ttn build is newer than the latest official one, which is fine if you're a developer") } + } else { + ctx.Warn("This ttn contains invalid build information") } - ctx.Info("") }, } diff --git a/ttnctl/cmd/version.go b/ttnctl/cmd/version.go index 4509b626b..d97bbf2e0 100644 --- a/ttnctl/cmd/version.go +++ b/ttnctl/cmd/version.go @@ -7,6 +7,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/utils/version" + "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" ) @@ -18,41 +19,45 @@ var versionCmd = &cobra.Command{ Short: "Get build and version information", Long: `ttnctl version gets the build and version information of ttnctl`, Run: func(cmd *cobra.Command, args []string) { - ctx = ctx.WithField("Version", viper.GetString("version")) - gitBranch := viper.GetString("gitBranch") - ctx = ctx.WithField("Branch", gitBranch) - gitCommit := viper.GetString("gitCommit") - ctx = ctx.WithField("Commit", gitCommit) - buildDate := viper.GetString("buildDate") - ctx = ctx.WithField("BuildDate", buildDate) + + ctx.WithFields(log.Fields{ + "Version": viper.GetString("version"), + "Branch": gitBranch, + "Commit": gitCommit, + "BuildDate": buildDate, + }).Info("Got build information") if gitBranch == unknown || gitCommit == unknown || buildDate == unknown { ctx.Warn("This is not an official ttnctl build") + ctx.Warn("If you're building ttnctl from source, you should use the Makefile") + return + } + + latest, err := version.GetLatestInfo() + if err != nil { + ctx.WithError(err).Warn("Could not get latest version information") + return + } + + if latest.Commit == gitCommit { + ctx.Info("This is an up-to-date ttnctl build") + return } - if gitBranch != unknown { - if version, err := version.GetLatestInfo(); err == nil { - if version.Commit == gitCommit { - ctx.Info("This is an up-to-date ttnctl build") - } else { - if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { - if buildDate.Before(version.Date) { - ctx.Warn("This is not an up-to-date ttnctl build") - ctx.Warnf("The newest build is %s newer.", version.Date.Sub(buildDate)) - } else { - ctx.Warn("This is not an official ttnctl build") - } - } - } + if buildDate, err := time.Parse(time.RFC3339, buildDate); err == nil { + ctx.Warn("This is not the latest official ttnctl build") + if buildDate.Before(latest.Date) { + ctx.Warnf("The newest ttnctl build is %s newer.", latest.Date.Sub(buildDate)) } else { - ctx.Warn("Could not get latest version information") + ctx.Warn("Your ttnctl build is newer than the latest official one, which is fine if you're a developer") } + } else { + ctx.Warn("This ttnctl contains invalid build information") } - ctx.Info("") }, } From f8134c8e0fbf6ba1648b565516860e33d445c5d8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 21 Oct 2016 14:51:32 +0200 Subject: [PATCH 2020/2266] Interpret empty string from Redis as null/nil/empty --- core/storage/decoder.go | 6 +++++- core/storage/redis_map_store_test.go | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/storage/decoder.go b/core/storage/decoder.go index 7ebbd3f0b..504d0b09b 100644 --- a/core/storage/decoder.go +++ b/core/storage/decoder.go @@ -135,10 +135,14 @@ func buildDefaultStructDecoder(base interface{}, tagName string) StringStringMap if str, ok := input[tagName]; ok { baseField, _ := baseType.FieldByName(field.Name()) + if str == "" { + continue + } + var val interface{} switch field.Kind() { case reflect.Ptr: - if str == "" || str == "null" { + if str == "null" { continue } fallthrough diff --git a/core/storage/redis_map_store_test.go b/core/storage/redis_map_store_test.go index 4ac381f23..a701a6e10 100644 --- a/core/storage/redis_map_store_test.go +++ b/core/storage/redis_map_store_test.go @@ -16,6 +16,7 @@ type testRedisStruct struct { unexported string Skipped string `redis:"-"` Name string `redis:"name,omitempty"` + EmptyStr string `redis:"EmptyStr"` UpdatedAt Time `redis:"updated_at,omitempty"` Empty *map[string]string `redis:"empty"` NotEmpty *map[string]string `redis:"not_empty"` From a9dc4d75a9b7f8d2f7a6a726ce0f0fdbc1547d16 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 21 Oct 2016 14:57:08 +0200 Subject: [PATCH 2021/2266] Update ttn/ttnctl docs --- cmd/docs/README.md | 56 ++++++++++++++++++++++++++++++--------- ttnctl/cmd/docs/README.md | 23 +++++++++++++--- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/cmd/docs/README.md b/cmd/docs/README.md index 385f8a8ce..0761b888d 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -6,7 +6,7 @@ The Things Network's backend servers. ``` --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yaml") + --config string config file (default "$HOME/.ttn.yml") --description string The description of this component --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") --elasticsearch string Location of Elasticsearch server for logging @@ -37,11 +37,17 @@ The Things Network's backend servers. --server-port int The port for communication (default 1902) ``` -### ttn broker genkeys +### ttn broker gen-cert -ttn genkeys generates keys and a TLS certificate for this component +ttn gen-cert generates a TLS Certificate -**Usage:** `ttn broker genkeys` +**Usage:** `ttn broker gen-cert` + +### ttn broker gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn broker gen-keypair` ### ttn broker register-prefix @@ -58,6 +64,7 @@ ttn broker register prefix registers a prefix to this Broker **Options** ``` + --cache Add a cache in front of the database --redis-address string Redis server and port (default "localhost:6379") --redis-db int Redis database --server-address string The IP address to listen for communication (default "0.0.0.0") @@ -73,6 +80,10 @@ ttn broker register prefix registers a prefix to this Broker **Options** ``` + --amqp-exchange string AMQP exchange + --amqp-host string AMQP host and port. Leave empty to disable AMQP + --amqp-password string AMQP password + --amqp-username string AMQP username (default "handler") --mqtt-broker string MQTT broker host and port (default "localhost:1883") --mqtt-password string MQTT password --mqtt-username string MQTT username (default "handler") @@ -84,11 +95,17 @@ ttn broker register prefix registers a prefix to this Broker --ttn-broker string The ID of the TTN Broker as announced in the Discovery server (default "dev") ``` -### ttn handler genkeys +### ttn handler gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn handler gen-cert` + +### ttn handler gen-keypair -ttn genkeys generates keys and a TLS certificate for this component +ttn gen-keypair generates a public/private keypair -**Usage:** `ttn handler genkeys` +**Usage:** `ttn handler gen-keypair` ## ttn networkserver @@ -119,11 +136,17 @@ ttn networkserver authorize generates a token that Brokers should use to connect --valid int The number of days the token is valid ``` -### ttn networkserver genkeys +### ttn networkserver gen-cert -ttn genkeys generates keys and a TLS certificate for this component +ttn gen-cert generates a TLS Certificate -**Usage:** `ttn networkserver genkeys` +**Usage:** `ttn networkserver gen-cert` + +### ttn networkserver gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn networkserver gen-keypair` ## ttn router @@ -137,13 +160,20 @@ ttn genkeys generates keys and a TLS certificate for this component --server-address string The IP address to listen for communication (default "0.0.0.0") --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1901) + --skip-verify-gateway-token Skip verification of the gateway token ``` -### ttn router genkeys +### ttn router gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn router gen-cert` + +### ttn router gen-keypair -ttn genkeys generates keys and a TLS certificate for this component +ttn gen-keypair generates a public/private keypair -**Usage:** `ttn router genkeys` +**Usage:** `ttn router gen-keypair` ## ttn selfupdate diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index 2376842c7..ce4ccf15c 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -5,7 +5,7 @@ Control The Things Network from the command line. **Options** ``` - --config string config file (default is $HOME/.ttnctl.yaml) + --config string config file (default is $HOME/.ttnctl.yml) --data string directory where ttnctl stores data (default is $HOME/.ttnctl) -d, --debug Enable debug mode --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") @@ -196,6 +196,12 @@ Are you sure you want to unregister application test? INFO Unregistered application AppID=test ``` +## ttnctl config + +ttnctl config prints the configuration that is used + +**Usage:** `ttnctl config` + ## ttnctl devices ttnctl devices can be used to manage devices. @@ -277,8 +283,8 @@ $ ttnctl devices list INFO Discovering Handler... INFO Connecting with Handler... -DevID AppEUI DevEUI DevAddr Up/Down -test 70B3D57EF0000024 0001D544B2936FCE 26001ADA 0/0 +DevID AppEUI DevEUI DevAddr +test 70B3D57EF0000024 0001D544B2936FCE 26001ADA INFO Listed 1 devices AppID=test ``` @@ -329,16 +335,19 @@ ttnctl devices set can be used to set properties of a device. **Options** ``` - --32-bit-fcnt Use 32 bit FCnt + --16-bit-fcnt Use 16 bit FCnt + --32-bit-fcnt Use 32 bit FCnt (default) --app-eui string Set AppEUI --app-key string Set AppKey --app-s-key string Set AppSKey --dev-addr string Set DevAddr --dev-eui string Set DevEUI --disable-fcnt-check Disable FCnt check + --enable-fcnt-check Enable FCnt check (default) --fcnt-down int Set FCnt Down (default -1) --fcnt-up int Set FCnt Up (default -1) --nwk-s-key string Set NwkSKey + --override Override protection against breaking changes ``` **Example** @@ -415,6 +424,12 @@ $ ttnctl gateways edit test --location 52.37403,4.88968 --frequency-plan EU INFO Edited gateway Gateway ID=test ``` +### ttnctl gateways info + +ttnctl gateways info can be used to get information about a gateway + +**Usage:** `ttnctl gateways info [GatewayID]` + ### ttnctl gateways list ttnctl gateways list can be used to list the gateways you have access to From ea178d6c9a7b3fc724edaf336609b152f6166984 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 21 Oct 2016 15:04:59 +0200 Subject: [PATCH 2022/2266] Add ttnctl config to README --- README.md | 42 ++++++++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index c402bb0f0..963c313e4 100644 --- a/README.md +++ b/README.md @@ -32,31 +32,45 @@ When you get started with The Things Network, you'll probably have some question 3. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` 4. Install the dependencies for development: `make dev-deps` 5. Run the tests: `make test` +6. Run `make build` to build both `ttn` and `ttnctl` from source. +7. Run `make link` to link them to `$GOPATH/bin/` (In order to run the commands, you should have `export PATH="$GOPATH/bin:$PATH"` in your profile). +8. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. -**NOTE:** From now on you should run all commands from the `$GOPATH/src/github.com/TheThingsNetwork/ttn` directory. +You can check your `ttnctl` configuration by running `ttnctl config`. It should look like this: + +``` + INFO Using config: + + config file: /home/your-user/.ttnctl.yml + data dir: /home/your-user/.ttnctl -## Build, install and run The Things Network's backend locally + ttn-account-server: https://preview.account.thethingsnetwork.org + discovery-server: localhost:1900 + ttn-router: dev + ttn-handler: dev + mqtt-broker: localhost:1883 +``` -Additional to setting up the backend for development, you should do the following to build, install and run the backend locally. +**NOTE:** From now on you should run all commands from the `$GOPATH/src/github.com/TheThingsNetwork/ttn` directory. + +## Run The Things Network's backend locally -1. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. -2. Run `make build` to build both `ttn` and `ttnctl` from source. -2. Run `make link` to link them to `$GOPATH/bin/` (In order to run the commands, you should have `export PATH="$GOPATH/bin:$PATH"` in your profile). -3. Run `forego start` to start all backend services at the same time. Make sure that Redis and Mosquitto **are running** on your machine. -4. First time only (or when Redis is flushed): +- Set up the backend as described [above](#set-up-the-things-networks-backend-for-development). +- Run `forego start` to start all backend services at the same time. Make sure that Redis and Mosquitto **are running** on your machine. +- First time only (or when Redis is flushed): * Run `ttn broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` * Restart the backend services ## Build and run The Things Network's backend in Docker -1. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. -2. Add the following line to your `/etc/hosts` file: +- Set up the backend as described [above](#set-up-the-things-networks-backend-for-development). +- Add the following line to your `/etc/hosts` file: `127.0.0.1 router handler` -3. Run `make docker` to build the docker image -4. Run `docker-compose up` to start all backend services in Docker. Make sure that Redis and Mosquitto **are not running** on your local machine. -5. First time only (or when Redis is flushed): +- Run `make docker` to build the docker image +- Run `docker-compose up` to start all backend services in Docker. Make sure that Redis and Mosquitto **are not running** on your local machine. +- First time only (or when Redis is flushed): * Run `docker-compose run broker broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` - * Restart the backend + * Restart the backend services ## Contributing From a991d7ed2addd605d1196f456d7d7d7531c58875 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 15:17:12 +0200 Subject: [PATCH 2023/2266] Replaced Mosquitto by RabbitMQ --- .env/handler/dev.yml | 8 +++--- .gitlab-ci.yml | 3 +-- .travis.yml | 3 +-- Brewfile | 2 +- README.md | 2 +- cmd/handler.go | 8 +++--- ttnctl/cmd/root.go | 6 +++++ ttnctl/util/mqtt.go | 58 +++++++++++++++++++++++++++++--------------- 8 files changed, 55 insertions(+), 35 deletions(-) diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index d4c67753a..ec1211052 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -7,10 +7,8 @@ auth-servers: ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/handler/" -# handler: -# amqp-host: localhost:5672 -# amqp-username: guest -# amqp-password: guest -# amqp-exchange: topic +handler: + amqp-host: localhost:5672 + amqp-exchange: topic auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 953f91447..1c94d16e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,12 +30,11 @@ tests: stage: test image: golang:latest services: - - ansi/mosquitto - rabbitmq - redis variables: REDIS_HOST: redis - MQTT_HOST: ansi-mosquitto + MQTT_HOST: rabbitmq AMQP_ADDR: rabbitmq:5672 script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn diff --git a/.travis.yml b/.travis.yml index ad601c5f0..1ee1d17f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,8 +14,7 @@ install: before_script: - docker run -d -p 127.0.0.1:6379:6379 redis - - docker run -d -p 127.0.0.1:1883:1883 ansi/mosquitto - - docker run -d -p 127.0.0.1:5672:5672 rabbitmq + - docker run -d -p 127.0.0.1:5672:5672 127.0.0.1:1883:1883 rabbitmq script: - make test diff --git a/Brewfile b/Brewfile index 42c88f091..7cac5624b 100644 --- a/Brewfile +++ b/Brewfile @@ -1,6 +1,6 @@ tap 'homebrew/bundle' brew 'go' -brew 'mosquitto', restart_service: true +brew 'rabbitmq', restart_service: true brew 'protobuf' brew 'redis', restart_service: true diff --git a/README.md b/README.md index 963c313e4..a6eb478cd 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed. 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. Make sure you have [Mosquitto](http://mosquitto.org/download/) and [Redis](http://redis.io/download) **installed** and **running**. +4. Make sure you have [RabbitMQ](https://www.rabbitmq.com/download.html) and [Redis](http://redis.io/download) **installed** and **running**. If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. Windows users are currently on their own (feel free to contribute a guide for Windows). ## Set up The Things Network's backend for Development diff --git a/cmd/handler.go b/cmd/handler.go index 56e22a029..211caec24 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -109,19 +109,19 @@ func init() { handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "MQTT broker host and port") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) - handlerCmd.Flags().String("mqtt-username", "handler", "MQTT username") + handlerCmd.Flags().String("mqtt-username", "guest", "MQTT username") viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) - handlerCmd.Flags().String("mqtt-password", "", "MQTT password") + handlerCmd.Flags().String("mqtt-password", "guest", "MQTT password") viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) handlerCmd.Flags().String("amqp-host", "", "AMQP host and port. Leave empty to disable AMQP") viper.BindPFlag("handler.amqp-host", handlerCmd.Flags().Lookup("amqp-host")) - handlerCmd.Flags().String("amqp-username", "handler", "AMQP username") + handlerCmd.Flags().String("amqp-username", "guest", "AMQP username") viper.BindPFlag("handler.amqp-username", handlerCmd.Flags().Lookup("amqp-username")) - handlerCmd.Flags().String("amqp-password", "", "AMQP password") + handlerCmd.Flags().String("amqp-password", "guest", "AMQP password") viper.BindPFlag("handler.amqp-password", handlerCmd.Flags().Lookup("amqp-password")) handlerCmd.Flags().String("amqp-exchange", "", "AMQP exchange") diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 525887778..0a4e72268 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -82,6 +82,12 @@ func init() { RootCmd.PersistentFlags().String("mqtt-broker", "eu.thethings.network:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) + RootCmd.PersistentFlags().String("mqtt-username", "guest", "The username for the MQTT broker") + viper.BindPFlag("mqtt-username", RootCmd.PersistentFlags().Lookup("mqtt-username")) + + RootCmd.PersistentFlags().String("mqtt-password", "guest", "The password for the MQTT broker") + viper.BindPFlag("mqtt-password", RootCmd.PersistentFlags().Lookup("mqtt-password")) + RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) } diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 390344e58..78e9aac82 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -4,6 +4,7 @@ package util import ( + "errors" "fmt" "strings" @@ -15,18 +16,51 @@ import ( // GetMQTT connects a new MQTT clients with the specified credentials func GetMQTT(ctx log.Interface) mqtt.Client { + username, password, err := getMQTTCredentials(ctx) + if err != nil { + ctx.WithError(err).Fatal("Failed to get MQTT credentials") + } + + mqttProto := "tcp" + if strings.HasSuffix(viper.GetString("mqtt-broker"), ":8883") { + mqttProto = "ssl" + ctx.Fatal("TLS connections are not yet supported by ttnctl") + } + broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-broker")) + client := mqtt.NewClient(ctx, "ttnctl", username, password, broker) + + ctx.WithField("MQTT Broker", broker).Info("Connecting to MQTT...") + + if err := client.Connect(); err != nil { + ctx.WithError(err).Fatal("Could not connect") + } + + return client +} + +func getMQTTCredentials(ctx log.Interface) (username string, password string, err error) { + username = viper.GetString("mqtt-username") + password = viper.GetString("mqtt-password") + if username != "" { + return + } + + return getAppMQTTCredentials(ctx) +} + +func getAppMQTTCredentials(ctx log.Interface) (string, string, error) { appID := GetAppID(ctx) account := GetAccount(ctx) app, err := account.FindApplication(appID) if err != nil { - ctx.WithError(err).Fatal("Failed to get application") + return "", "", err } var keyIdx int switch len(app.AccessKeys) { case 0: - ctx.Fatal("Can not connect to MQTT. Your application does not have any access keys.") + return "", "", errors.New("Can not connect to MQTT. Your application does not have any access keys.") case 1: default: ctx.Infof("Found %d access keys for your application:", len(app.AccessKeys)) @@ -53,23 +87,7 @@ func GetMQTT(ctx log.Interface) mqtt.Client { } if keyIdx < 0 || keyIdx >= len(app.AccessKeys) { - ctx.Fatal("Invalid choice for access key") - } - key := app.AccessKeys[keyIdx] - - mqttProto := "tcp" - if strings.HasSuffix(viper.GetString("mqtt-broker"), ":8883") { - mqttProto = "ssl" - ctx.Fatal("TLS connections are not yet supported by ttnctl") + return "", "", errors.New("Invalid choice for access key") } - broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-broker")) - client := mqtt.NewClient(ctx, "ttnctl", appID, key.Key, broker) - - ctx.WithField("MQTT Broker", broker).Info("Connecting to MQTT...") - - if err := client.Connect(); err != nil { - ctx.WithError(err).Fatal("Could not connect") - } - - return client + return appID, app.AccessKeys[keyIdx].Key, nil } From 8af18ba6e39176ed35080b3a50e6820b5aac3642 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 15:29:31 +0200 Subject: [PATCH 2024/2266] Default AMQP exchange is ttn-handler --- .env/handler/dev.yml | 1 - cmd/handler.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index ec1211052..66a4cbbfa 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -9,6 +9,5 @@ tls: true key-dir: "./.env/handler/" handler: amqp-host: localhost:5672 - amqp-exchange: topic auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg diff --git a/cmd/handler.go b/cmd/handler.go index 211caec24..79096be1a 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -124,7 +124,7 @@ func init() { handlerCmd.Flags().String("amqp-password", "guest", "AMQP password") viper.BindPFlag("handler.amqp-password", handlerCmd.Flags().Lookup("amqp-password")) - handlerCmd.Flags().String("amqp-exchange", "", "AMQP exchange") + handlerCmd.Flags().String("amqp-exchange", "ttn-handler", "AMQP exchange") viper.BindPFlag("handler.amqp-exchange", handlerCmd.Flags().Lookup("amqp-exchange")) handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") From 1eefc3e5e081bda14fedc36b40e7b729dfdf01c8 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Thu, 20 Oct 2016 15:33:35 +0200 Subject: [PATCH 2025/2266] Fix port binding --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 1ee1d17f2..5834cff0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: before_script: - docker run -d -p 127.0.0.1:6379:6379 redis - - docker run -d -p 127.0.0.1:5672:5672 127.0.0.1:1883:1883 rabbitmq + - docker run -d -p 127.0.0.1:5672:5672 -p 127.0.0.1:1883:1883 rabbitmq script: - make test From f031b4a33998d1f716df3ce831fc1fc704c1c7fa Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 21 Oct 2016 16:02:28 +0100 Subject: [PATCH 2026/2266] Reconnect and reopen channels --- amqp/client.go | 97 ++++++++++++++++++++++++++++++++++-------- amqp/publisher.go | 76 ++++++++++++++------------------- amqp/publisher_test.go | 19 ++++++--- amqp/uplink_test.go | 2 +- core/handler/amqp.go | 2 +- 5 files changed, 128 insertions(+), 68 deletions(-) diff --git a/amqp/client.go b/amqp/client.go index 407826b77..b2335d9d0 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -5,24 +5,37 @@ package amqp import ( "fmt" + "io" + "sync" "time" AMQP "github.com/streadway/amqp" ) +// ChannelUser represents a user of an AMQP channel, for example a Publisher +type ChannelUser interface { + Open() error + io.Closer +} + // Client connects to an AMQP server type Client interface { Connect() error Disconnect() IsConnected() bool - NewPublisher(exchange string) Publisher + + NewTopicPublisher(exchange string) Publisher } // DefaultClient is the default AMQP client for The Things Network type DefaultClient struct { - url string - ctx Logger - conn *AMQP.Connection + url string + ctx Logger + conn *AMQP.Connection + mutex *sync.Mutex + closed chan *AMQP.Error + channels map[ChannelUser]*AMQP.Channel + reconnecting bool } var ( @@ -46,8 +59,10 @@ func NewClient(ctx Logger, username, password, host string) Client { } } return &DefaultClient{ - ctx: ctx, - url: fmt.Sprintf("amqp://%s@%s", credentials, host), + ctx: ctx, + url: fmt.Sprintf("amqp://%s@%s", credentials, host), + mutex: &sync.Mutex{}, + channels: make(map[ChannelUser]*AMQP.Channel), } } @@ -55,19 +70,41 @@ func NewClient(ctx Logger, username, password, host string) Client { func (c *DefaultClient) Connect() error { var err error var conn *AMQP.Connection - for retries := 0; retries < ConnectRetries; retries++ { + for retries := 0; c.reconnecting || retries < ConnectRetries; retries++ { conn, err = AMQP.Dial(c.url) if err == nil { break } - c.ctx.Warnf("Could not connect to AMQP server (%s). Retrying...", err.Error()) + c.ctx.Warnf("Could not connect to AMQP server (%s). Retrying attempt %d, reconnect is %v...", err.Error(), retries+1, c.reconnecting) <-time.After(ConnectRetryDelay) } if err != nil { - return fmt.Errorf("Could not connect to AMQP server (%s).", err) + return fmt.Errorf("Could not connect to AMQP server (%s)", err) } + c.closed = make(chan *AMQP.Error) + conn.NotifyClose(c.closed) + go func(errc chan *AMQP.Error) { + err := <-errc + if err != nil { + c.ctx.Warnf("Connection closed (%s). Reconnecting...", err) + c.reconnecting = true + c.Connect() + } else { + c.ctx.Info("Connection closed") + } + }(c.closed) + c.conn = conn + c.reconnecting = false + + c.mutex.Lock() + defer c.mutex.Unlock() + for user, channel := range c.channels { + channel.Close() + go user.Open() + } + return nil } @@ -76,10 +113,18 @@ func (c *DefaultClient) Disconnect() { if !c.IsConnected() { return } + c.ctx.Debug("Disconnecting from AMQP") - if err := c.conn.Close(); err != nil { - c.ctx.Warnf("Could not close AMQP connection (%s)", err) + + c.mutex.Lock() + defer c.mutex.Unlock() + for user, channel := range c.channels { + channel.Close() + delete(c.channels, user) } + + c.reconnecting = false + c.conn.Close() c.conn = nil } @@ -88,11 +133,29 @@ func (c *DefaultClient) IsConnected() bool { return c.conn != nil } -// NewPublisher returns a new publisher -func (c *DefaultClient) NewPublisher(exchange string) Publisher { - return &DefaultPublisher{ - ctx: c.ctx, - conn: c.conn, - exchange: exchange, +func (c *DefaultClient) openChannel(u ChannelUser) (*AMQP.Channel, error) { + c.mutex.Lock() + defer c.mutex.Unlock() + + channel, err := c.conn.Channel() + if err != nil { + return nil, err + } + c.channels[u] = channel + + return channel, nil +} + +func (c *DefaultClient) closeChannel(u ChannelUser) error { + channel, ok := c.channels[u] + if !ok { + return nil } + err := channel.Close() + + c.mutex.Lock() + defer c.mutex.Unlock() + delete(c.channels, u) + + return err } diff --git a/amqp/publisher.go b/amqp/publisher.go index 5ae5acb52..e21a94438 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -5,74 +5,64 @@ package amqp import ( "fmt" - "io" "time" "github.com/TheThingsNetwork/ttn/core/types" AMQP "github.com/streadway/amqp" ) -// Publisher holds an AMQP channel and can publish on uplink from devices +// Publisher represents a publisher for uplink messages type Publisher interface { - Open() error - io.Closer - PublishUplink(payload types.UplinkMessage) error + ChannelUser + PublishUplink(dataUp types.UplinkMessage) error } -// DefaultPublisher is the default AMQP publisher +// DefaultPublisher represents the default AMQP publisher type DefaultPublisher struct { - ctx Logger - conn *AMQP.Connection - channel *AMQP.Channel - exchange string + ctx Logger + client *DefaultClient + channel *AMQP.Channel + exchange string + exchangeType string } -// Open opens an AMQP channel for publishing +// NewTopicPublisher returns a new topic publisher on the specified exchange +func (c *DefaultClient) NewTopicPublisher(exchange string) Publisher { + return &DefaultPublisher{ + ctx: c.ctx, + client: c, + exchange: exchange, + exchangeType: "topic", + } +} + +// Open opens a new channel and declares the exchange func (p *DefaultPublisher) Open() error { - channel, err := p.conn.Channel() + channel, err := p.client.openChannel(p) if err != nil { - return fmt.Errorf("Could not open AMQP channel (%s).", err) + return fmt.Errorf("Could not open AMQP channel (%s)", err) } - if err = channel.ExchangeDeclare(p.exchange, "topic", true, false, false, false, nil); err != nil { - channel.Close() - return fmt.Errorf("Could not declare AMQP exchange (%s).", err) + if err := channel.ExchangeDeclare(p.exchange, p.exchangeType, true, false, false, false, nil); err != nil { + return fmt.Errorf("Could not declare AMQP exchange (%s)", err) } + p.ctx.Debugf("Opened channel for exchange %s", p.exchange) + p.channel = channel return nil } -// Close closes the AMQP channel +// Close closes the channel func (p *DefaultPublisher) Close() error { - if p.channel == nil { - return nil - } - return p.channel.Close() -} - -func (p *DefaultPublisher) do(action func() error) error { - err := action() - if err == nil { - return nil - } - if err == AMQP.ErrClosed { - err = p.Open() - if err != nil { - return err - } - err = action() - } - return err + return p.client.closeChannel(p) } func (p *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) error { - return p.do(func() error { - return p.channel.Publish(p.exchange, key, false, false, AMQP.Publishing{ - ContentType: "application/json", - DeliveryMode: AMQP.Persistent, - Timestamp: timestamp, - Body: msg, - }) + return p.channel.Publish(p.exchange, key, false, false, AMQP.Publishing{ + ContentType: "application/json", + DeliveryMode: AMQP.Persistent, + Timestamp: timestamp, + Body: msg, }) } diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go index 481fdf4c4..7cd83bd47 100644 --- a/amqp/publisher_test.go +++ b/amqp/publisher_test.go @@ -5,10 +5,12 @@ package amqp import ( "testing" + "time" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" + AMQP "github.com/streadway/amqp" ) func TestOpenPublisher(t *testing.T) { @@ -18,7 +20,7 @@ func TestOpenPublisher(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("test") + p := c.NewTopicPublisher("test") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() @@ -31,7 +33,7 @@ func TestReopenPublisher(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("test") + p := c.NewTopicPublisher("test") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() @@ -40,11 +42,16 @@ func TestReopenPublisher(t *testing.T) { err = p.PublishUplink(types.UplinkMessage{}) a.So(err, ShouldBeNil) - // Closing the underlying channel - err = p.(*DefaultPublisher).channel.Close() - a.So(err, ShouldBeNil) + // Make sure that the publisher's old channel is closed + p.(*DefaultPublisher).channel.Close() + + // Simulate a connection close so a new channel should be opened + c.(*DefaultClient).closed <- AMQP.ErrClosed + + // Give the reconnect some time + time.Sleep(100 * time.Millisecond) - // Second attempt should reconnect and be OK as well + // Second attempt should be OK as well and will only work on a new channel err = p.PublishUplink(types.UplinkMessage{}) a.So(err, ShouldBeNil) } diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index 63d29d2aa..5540c547b 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -18,7 +18,7 @@ func TestPublishUplink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("test") + p := c.NewTopicPublisher("test") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 7bca1d7a0..dcc56ca71 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -23,7 +23,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange string) error { ctx := h.Ctx.WithField("Protocol", "AMQP") go func() { - publisher := h.amqpClient.NewPublisher(exchange) + publisher := h.amqpClient.NewTopicPublisher(h.amqpExchange) err := publisher.Open() if err != nil { ctx.WithError(err).Error("Could not open publisher channel") From c09ed148a0ffee0508a42d683dd7e615af3df311 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 21 Oct 2016 16:07:17 +0100 Subject: [PATCH 2027/2266] Renamed default AMQP exchange to ttn.handler --- cmd/handler.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/handler.go b/cmd/handler.go index 79096be1a..d1cb574fd 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -124,7 +124,7 @@ func init() { handlerCmd.Flags().String("amqp-password", "guest", "AMQP password") viper.BindPFlag("handler.amqp-password", handlerCmd.Flags().Lookup("amqp-password")) - handlerCmd.Flags().String("amqp-exchange", "ttn-handler", "AMQP exchange") + handlerCmd.Flags().String("amqp-exchange", "ttn.handler", "AMQP exchange") viper.BindPFlag("handler.amqp-exchange", handlerCmd.Flags().Lookup("amqp-exchange")) handlerCmd.Flags().String("server-address", "0.0.0.0", "The IP address to listen for communication") From b69c3d7fbf2f72a9f5bdef769f442c6171b86ab5 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 21 Oct 2016 18:06:22 +0100 Subject: [PATCH 2028/2266] Promoted channel client and introduced subscriber --- amqp/client.go | 54 +++++++++++++++++++++++++++------- amqp/client_test.go | 41 ++++++++++++++++++++++++++ amqp/publisher.go | 46 +++++++---------------------- amqp/publisher_test.go | 35 +--------------------- amqp/subscriber.go | 65 +++++++++++++++++++++++++++++++++++++++++ amqp/subscriber_test.go | 24 +++++++++++++++ amqp/uplink.go | 38 ++++++++++++++++++++++++ amqp/uplink_test.go | 40 ++++++++++++++++++++++++- core/handler/amqp.go | 2 +- 9 files changed, 263 insertions(+), 82 deletions(-) create mode 100644 amqp/subscriber.go create mode 100644 amqp/subscriber_test.go diff --git a/amqp/client.go b/amqp/client.go index b2335d9d0..ed1756c6b 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -12,19 +12,14 @@ import ( AMQP "github.com/streadway/amqp" ) -// ChannelUser represents a user of an AMQP channel, for example a Publisher -type ChannelUser interface { - Open() error - io.Closer -} - // Client connects to an AMQP server type Client interface { Connect() error Disconnect() IsConnected() bool - NewTopicPublisher(exchange string) Publisher + NewPublisher(exchange, exchangeType string) Publisher + NewSubscriber(exchange, exchangeType, name string, durable, autoDelete bool) Subscriber } // DefaultClient is the default AMQP client for The Things Network @@ -34,10 +29,25 @@ type DefaultClient struct { conn *AMQP.Connection mutex *sync.Mutex closed chan *AMQP.Error - channels map[ChannelUser]*AMQP.Channel + channels map[*DefaultChannelClient]*AMQP.Channel reconnecting bool } +// ChannelClient represents a AMQP channel client +type ChannelClient interface { + Open() error + io.Closer +} + +// DefaultChannelClient represents the default client of an AMQP channel +type DefaultChannelClient struct { + ctx Logger + client *DefaultClient + channel *AMQP.Channel + exchange string + exchangeType string +} + var ( // ConnectRetries says how many times the client should retry a failed connection ConnectRetries = 10 @@ -62,7 +72,7 @@ func NewClient(ctx Logger, username, password, host string) Client { ctx: ctx, url: fmt.Sprintf("amqp://%s@%s", credentials, host), mutex: &sync.Mutex{}, - channels: make(map[ChannelUser]*AMQP.Channel), + channels: make(map[*DefaultChannelClient]*AMQP.Channel), } } @@ -133,7 +143,7 @@ func (c *DefaultClient) IsConnected() bool { return c.conn != nil } -func (c *DefaultClient) openChannel(u ChannelUser) (*AMQP.Channel, error) { +func (c *DefaultClient) openChannel(u *DefaultChannelClient) (*AMQP.Channel, error) { c.mutex.Lock() defer c.mutex.Unlock() @@ -146,7 +156,7 @@ func (c *DefaultClient) openChannel(u ChannelUser) (*AMQP.Channel, error) { return channel, nil } -func (c *DefaultClient) closeChannel(u ChannelUser) error { +func (c *DefaultClient) closeChannel(u *DefaultChannelClient) error { channel, ok := c.channels[u] if !ok { return nil @@ -159,3 +169,25 @@ func (c *DefaultClient) closeChannel(u ChannelUser) error { return err } + +// Open opens a new channel and declares the exchange +func (p *DefaultChannelClient) Open() error { + channel, err := p.client.openChannel(p) + if err != nil { + return fmt.Errorf("Could not open AMQP channel (%s)", err) + } + + if p.exchange != "" { + if err := channel.ExchangeDeclare(p.exchange, p.exchangeType, true, false, false, false, nil); err != nil { + return fmt.Errorf("Could not declare AMQP exchange (%s)", err) + } + } + + p.channel = channel + return nil +} + +// Close closes the channel +func (p *DefaultChannelClient) Close() error { + return p.client.closeChannel(p) +} diff --git a/amqp/client_test.go b/amqp/client_test.go index e86be0c26..c802c19be 100644 --- a/amqp/client_test.go +++ b/amqp/client_test.go @@ -10,6 +10,7 @@ import ( . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" + AMQP "github.com/streadway/amqp" ) var host string @@ -76,3 +77,43 @@ func TestDisconnect(t *testing.T) { a.So(c.IsConnected(), ShouldBeFalse) } + +func TestReopenChannelClient(t *testing.T) { + a := New(t) + ctx := GetLogger(t, "TestReopenChannelClient") + c := NewClient(ctx, "guest", "guest", host).(*DefaultClient) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := &DefaultChannelClient{ + ctx: ctx, + client: c, + } + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + test := func() error { + return p.channel.Publish("", "test", false, false, AMQP.Publishing{ + Body: []byte("test"), + }) + } + + // First attempt should be OK + err = test() + a.So(err, ShouldBeNil) + + // Make sure that the old channel is closed + p.channel.Close() + + // Simulate a connection close so a new channel should be opened + c.closed <- AMQP.ErrClosed + + // Give the reconnect some time + time.Sleep(100 * time.Millisecond) + + // Second attempt should be OK as well and will only work on a new channel + err = test() + a.So(err, ShouldBeNil) +} diff --git a/amqp/publisher.go b/amqp/publisher.go index e21a94438..df043fd7d 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -4,7 +4,6 @@ package amqp import ( - "fmt" "time" "github.com/TheThingsNetwork/ttn/core/types" @@ -13,49 +12,26 @@ import ( // Publisher represents a publisher for uplink messages type Publisher interface { - ChannelUser + ChannelClient + PublishUplink(dataUp types.UplinkMessage) error } // DefaultPublisher represents the default AMQP publisher type DefaultPublisher struct { - ctx Logger - client *DefaultClient - channel *AMQP.Channel - exchange string - exchangeType string + DefaultChannelClient } -// NewTopicPublisher returns a new topic publisher on the specified exchange -func (c *DefaultClient) NewTopicPublisher(exchange string) Publisher { +// NewPublisher returns a new topic publisher on the specified exchange +func (c *DefaultClient) NewPublisher(exchange, exchangeType string) Publisher { return &DefaultPublisher{ - ctx: c.ctx, - client: c, - exchange: exchange, - exchangeType: "topic", - } -} - -// Open opens a new channel and declares the exchange -func (p *DefaultPublisher) Open() error { - channel, err := p.client.openChannel(p) - if err != nil { - return fmt.Errorf("Could not open AMQP channel (%s)", err) - } - - if err := channel.ExchangeDeclare(p.exchange, p.exchangeType, true, false, false, false, nil); err != nil { - return fmt.Errorf("Could not declare AMQP exchange (%s)", err) + DefaultChannelClient: DefaultChannelClient{ + ctx: c.ctx, + client: c, + exchange: exchange, + exchangeType: exchangeType, + }, } - - p.ctx.Debugf("Opened channel for exchange %s", p.exchange) - - p.channel = channel - return nil -} - -// Close closes the channel -func (p *DefaultPublisher) Close() error { - return p.client.closeChannel(p) } func (p *DefaultPublisher) publish(key string, msg []byte, timestamp time.Time) error { diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go index 7cd83bd47..93f8f2a72 100644 --- a/amqp/publisher_test.go +++ b/amqp/publisher_test.go @@ -5,12 +5,9 @@ package amqp import ( "testing" - "time" - "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" - AMQP "github.com/streadway/amqp" ) func TestOpenPublisher(t *testing.T) { @@ -20,38 +17,8 @@ func TestOpenPublisher(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewTopicPublisher("test") + p := c.NewPublisher("test", "topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() } - -func TestReopenPublisher(t *testing.T) { - a := New(t) - c := NewClient(GetLogger(t, "TestReopenPublisher"), "guest", "guest", host) - err := c.Connect() - a.So(err, ShouldBeNil) - defer c.Disconnect() - - p := c.NewTopicPublisher("test") - err = p.Open() - a.So(err, ShouldBeNil) - defer p.Close() - - // First attempt should be OK - err = p.PublishUplink(types.UplinkMessage{}) - a.So(err, ShouldBeNil) - - // Make sure that the publisher's old channel is closed - p.(*DefaultPublisher).channel.Close() - - // Simulate a connection close so a new channel should be opened - c.(*DefaultClient).closed <- AMQP.ErrClosed - - // Give the reconnect some time - time.Sleep(100 * time.Millisecond) - - // Second attempt should be OK as well and will only work on a new channel - err = p.PublishUplink(types.UplinkMessage{}) - a.So(err, ShouldBeNil) -} diff --git a/amqp/subscriber.go b/amqp/subscriber.go new file mode 100644 index 000000000..6e404532d --- /dev/null +++ b/amqp/subscriber.go @@ -0,0 +1,65 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import AMQP "github.com/streadway/amqp" + +var ( + // PrefetchCount represents the number of messages to prefetch before the AMQP server requires acknowledgment + PrefetchCount = 3 + // PrefetchSize represents the number of bytes to prefetch before the AMQP server requires acknowledgment + PrefetchSize = 0 +) + +// Subscriber represents a subscriber for uplink messages +type Subscriber interface { + ChannelClient + + SubscribeDeviceUplink(appID, devID string, handler UplinkHandler) error + SubscribeAppUplink(appID string, handler UplinkHandler) error + SubscribeUplink(handler UplinkHandler) error +} + +// DefaultSubscriber represents the default AMQP subscriber +type DefaultSubscriber struct { + DefaultChannelClient + + name string + durable bool + autoDelete bool +} + +// NewSubscriber returns a new topic subscriber on the specified exchange +func (c *DefaultClient) NewSubscriber(exchange, exchangeType, name string, durable, autoDelete bool) Subscriber { + return &DefaultSubscriber{ + DefaultChannelClient: DefaultChannelClient{ + ctx: c.ctx, + client: c, + exchange: exchange, + exchangeType: exchangeType, + }, + name: name, + durable: durable, + autoDelete: autoDelete, + } +} + +func (s *DefaultSubscriber) subscribe(key string) (<-chan AMQP.Delivery, error) { + queue, err := s.channel.QueueDeclare(s.name, s.durable, s.autoDelete, false, false, nil) + if err != nil { + return nil, err + } + + err = s.channel.QueueBind(queue.Name, key, s.exchange, false, nil) + if err != nil { + return nil, err + } + + err = s.channel.Qos(PrefetchCount, PrefetchSize, false) + if err != nil { + return nil, err + } + + return s.channel.Consume(queue.Name, "", false, false, false, false, nil) +} diff --git a/amqp/subscriber_test.go b/amqp/subscriber_test.go new file mode 100644 index 000000000..369dbcf20 --- /dev/null +++ b/amqp/subscriber_test.go @@ -0,0 +1,24 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestOpenSubscriber(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestOpenSubscriber"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + s := c.NewSubscriber("test", "topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() +} diff --git a/amqp/uplink.go b/amqp/uplink.go index c5e183e01..cca8bc89f 100644 --- a/amqp/uplink.go +++ b/amqp/uplink.go @@ -11,6 +11,9 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) +// UplinkHandler is called for uplink messages +type UplinkHandler func(subscriber Subscriber, appID string, devID string, req types.UplinkMessage) + // PublishUplink publishes an uplink message to the AMQP broker func (c *DefaultPublisher) PublishUplink(dataUp types.UplinkMessage) error { key := DeviceKey{dataUp.AppID, dataUp.DevID, DeviceUplink, ""} @@ -20,3 +23,38 @@ func (c *DefaultPublisher) PublishUplink(dataUp types.UplinkMessage) error { } return c.publish(key.String(), msg, time.Time(dataUp.Metadata.Time)) } + +// SubscribeDeviceUplink subscribes to all uplink messages for the given application and device +func (s *DefaultSubscriber) SubscribeDeviceUplink(appID, devID string, handler UplinkHandler) error { + key := DeviceKey{appID, devID, DeviceUplink, ""} + messages, err := s.subscribe(key.String()) + if err != nil { + return err + } + + go func() { + for delivery := range messages { + dataUp := &types.UplinkMessage{} + err := json.Unmarshal(delivery.Body, dataUp) + if err != nil { + s.ctx.Warnf("Could not unmarshal uplink %v (%s)", delivery, err) + continue + } + handler(s, dataUp.AppID, dataUp.DevID, *dataUp) + delivery.Ack(false) + break + } + }() + + return nil +} + +// SubscribeAppUplink subscribes to all uplink messages for the given application +func (s *DefaultSubscriber) SubscribeAppUplink(appID string, handler UplinkHandler) error { + return s.SubscribeDeviceUplink(appID, "", handler) +} + +// SubscribeUplink subscribes to all uplink messages that the current user has access to +func (s *DefaultSubscriber) SubscribeUplink(handler UplinkHandler) error { + return s.SubscribeDeviceUplink("", "", handler) +} diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index 5540c547b..eb2f4db47 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -4,6 +4,7 @@ package amqp import ( + "sync" "testing" "github.com/TheThingsNetwork/ttn/core/types" @@ -18,7 +19,7 @@ func TestPublishUplink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewTopicPublisher("test") + p := c.NewPublisher("", "") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() @@ -30,3 +31,40 @@ func TestPublishUplink(t *testing.T) { }) a.So(err, ShouldBeNil) } + +func TestSubscribeUplink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestSubscribeUplink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("test", "topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + s := c.NewSubscriber("test", "topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() + + wg := &sync.WaitGroup{} + err = s.SubscribeUplink(func(_ Subscriber, appID, devID string, req types.UplinkMessage) { + a.So(appID, ShouldEqual, "app") + a.So(devID, ShouldEqual, "test") + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x08}) + wg.Done() + }) + a.So(err, ShouldBeNil) + + wg.Add(1) + err = p.PublishUplink(types.UplinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) + + wg.Wait() +} diff --git a/core/handler/amqp.go b/core/handler/amqp.go index dcc56ca71..87cd8eb0a 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -23,7 +23,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange string) error { ctx := h.Ctx.WithField("Protocol", "AMQP") go func() { - publisher := h.amqpClient.NewTopicPublisher(h.amqpExchange) + publisher := h.amqpClient.NewPublisher(h.amqpExchange, "topic") err := publisher.Open() if err != nil { ctx.WithError(err).Error("Could not open publisher channel") From 6334faba0cfbd331ecbcd8c577172f7f0156fece Mon Sep 17 00:00:00 2001 From: johanstokking Date: Fri, 21 Oct 2016 18:42:52 +0100 Subject: [PATCH 2029/2266] Use RabbitMQ with MQTT plugin enabled --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 5834cff0f..c180b6a3c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: before_script: - docker run -d -p 127.0.0.1:6379:6379 redis - - docker run -d -p 127.0.0.1:5672:5672 -p 127.0.0.1:1883:1883 rabbitmq + - docker run -d -p 127.0.0.1:5672:5672 -p 127.0.0.1:1883:1883 robertobarreda/rabbitmq:mqtt script: - make test From 1ca9cddec1d6e8e092e1b8f5fc774d77ee38b467 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 23 Oct 2016 16:48:56 +0100 Subject: [PATCH 2030/2266] Use builtin exchanges --- amqp/publisher_test.go | 2 +- amqp/subscriber_test.go | 2 +- amqp/uplink_test.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go index 93f8f2a72..d6a50bcbb 100644 --- a/amqp/publisher_test.go +++ b/amqp/publisher_test.go @@ -17,7 +17,7 @@ func TestOpenPublisher(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("test", "topic") + p := c.NewPublisher("amq.topic", "topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() diff --git a/amqp/subscriber_test.go b/amqp/subscriber_test.go index 369dbcf20..5c32c69f5 100644 --- a/amqp/subscriber_test.go +++ b/amqp/subscriber_test.go @@ -17,7 +17,7 @@ func TestOpenSubscriber(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - s := c.NewSubscriber("test", "topic", "", false, true) + s := c.NewSubscriber("amq.topic", "topic", "", false, true) err = s.Open() a.So(err, ShouldBeNil) defer s.Close() diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index eb2f4db47..eb67b0cc9 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -39,12 +39,12 @@ func TestSubscribeUplink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("test", "topic") + p := c.NewPublisher("amq.topic", "topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() - s := c.NewSubscriber("test", "topic", "", false, true) + s := c.NewSubscriber("amq.topic", "topic", "", false, true) err = s.Open() a.So(err, ShouldBeNil) defer s.Close() From 94f15c299c45bc6e15f37f4865d9d68a0a15ca72 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 23 Oct 2016 17:06:44 +0100 Subject: [PATCH 2031/2266] Reconnect without sharing state --- amqp/client.go | 59 ++++++++++++++++++++++++--------------------- amqp/client_test.go | 4 +-- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/amqp/client.go b/amqp/client.go index ed1756c6b..25b7426e0 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -24,13 +24,11 @@ type Client interface { // DefaultClient is the default AMQP client for The Things Network type DefaultClient struct { - url string - ctx Logger - conn *AMQP.Connection - mutex *sync.Mutex - closed chan *AMQP.Error - channels map[*DefaultChannelClient]*AMQP.Channel - reconnecting bool + url string + ctx Logger + conn *AMQP.Connection + mutex *sync.Mutex + channels map[*DefaultChannelClient]*AMQP.Channel } // ChannelClient represents a AMQP channel client @@ -76,46 +74,52 @@ func NewClient(ctx Logger, username, password, host string) Client { } } -// Connect to the AMQP server. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries -func (c *DefaultClient) Connect() error { +func (c *DefaultClient) connect(reconnect bool) (chan *AMQP.Error, error) { var err error var conn *AMQP.Connection - for retries := 0; c.reconnecting || retries < ConnectRetries; retries++ { + for retries := 0; reconnect || retries < ConnectRetries; retries++ { conn, err = AMQP.Dial(c.url) if err == nil { break } - c.ctx.Warnf("Could not connect to AMQP server (%s). Retrying attempt %d, reconnect is %v...", err.Error(), retries+1, c.reconnecting) + c.ctx.Warnf("Could not connect to AMQP server (%s). Retry attempt %d, reconnect is %v...", err.Error(), retries+1, reconnect) <-time.After(ConnectRetryDelay) } if err != nil { - return fmt.Errorf("Could not connect to AMQP server (%s)", err) + return nil, fmt.Errorf("Could not connect to AMQP server (%s)", err) } - c.closed = make(chan *AMQP.Error) - conn.NotifyClose(c.closed) - go func(errc chan *AMQP.Error) { - err := <-errc + closed := make(chan *AMQP.Error) + conn.NotifyClose(closed) + go func() { + err := <-closed if err != nil { c.ctx.Warnf("Connection closed (%s). Reconnecting...", err) - c.reconnecting = true - c.Connect() + c.connect(true) } else { c.ctx.Info("Connection closed") } - }(c.closed) + }() c.conn = conn - c.reconnecting = false - - c.mutex.Lock() - defer c.mutex.Unlock() - for user, channel := range c.channels { - channel.Close() - go user.Open() + c.ctx.Info("Connected to AMQP") + + if reconnect { + c.mutex.Lock() + defer c.mutex.Unlock() + for user, channel := range c.channels { + channel.Close() + go user.Open() + } } - return nil + return closed, nil +} + +// Connect to the AMQP server. It will retry for ConnectRetries times with a delay of ConnectRetryDelay between retries +func (c *DefaultClient) Connect() error { + _, err := c.connect(false) + return err } // Disconnect from the AMQP server @@ -133,7 +137,6 @@ func (c *DefaultClient) Disconnect() { delete(c.channels, user) } - c.reconnecting = false c.conn.Close() c.conn = nil } diff --git a/amqp/client_test.go b/amqp/client_test.go index c802c19be..d61c7992d 100644 --- a/amqp/client_test.go +++ b/amqp/client_test.go @@ -82,7 +82,7 @@ func TestReopenChannelClient(t *testing.T) { a := New(t) ctx := GetLogger(t, "TestReopenChannelClient") c := NewClient(ctx, "guest", "guest", host).(*DefaultClient) - err := c.Connect() + closed, err := c.connect(false) a.So(err, ShouldBeNil) defer c.Disconnect() @@ -108,7 +108,7 @@ func TestReopenChannelClient(t *testing.T) { p.channel.Close() // Simulate a connection close so a new channel should be opened - c.closed <- AMQP.ErrClosed + closed <- AMQP.ErrClosed // Give the reconnect some time time.Sleep(100 * time.Millisecond) From 000467f74f582fe221d640df6d11260e56374099 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 23 Oct 2016 18:11:17 +0100 Subject: [PATCH 2032/2266] Downlink publish and subscribe --- amqp/downlink.go | 60 +++++++++++++++++++++++++++++++++++++ amqp/downlink_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++ amqp/publisher.go | 1 + amqp/subscriber.go | 4 +++ amqp/uplink_test.go | 2 +- 5 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 amqp/downlink.go create mode 100644 amqp/downlink_test.go diff --git a/amqp/downlink.go b/amqp/downlink.go new file mode 100644 index 000000000..f22f8a626 --- /dev/null +++ b/amqp/downlink.go @@ -0,0 +1,60 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/core/types" +) + +// DownlinkHandler is called for downlink messages +type DownlinkHandler func(subscriber Subscriber, appID string, devID string, req types.DownlinkMessage) + +// PublishDownlink publishes a downlink message to the AMQP broker +func (c *DefaultPublisher) PublishDownlink(dataDown types.DownlinkMessage) error { + key := DeviceKey{dataDown.AppID, dataDown.DevID, DeviceDownlink, ""} + msg, err := json.Marshal(dataDown) + if err != nil { + return fmt.Errorf("Unable to marshal the message payload") + } + return c.publish(key.String(), msg, time.Now()) +} + +// SubscribeDeviceDownlink subscribes to all downlink messages for the given application and device +func (s *DefaultSubscriber) SubscribeDeviceDownlink(appID, devID string, handler DownlinkHandler) error { + key := DeviceKey{appID, devID, DeviceDownlink, ""} + messages, err := s.subscribe(key.String()) + if err != nil { + return err + } + + go func() { + for delivery := range messages { + dataDown := &types.DownlinkMessage{} + err := json.Unmarshal(delivery.Body, dataDown) + if err != nil { + s.ctx.Warnf("Could not unmarshal downlink %v (%s)", delivery, err) + continue + } + handler(s, dataDown.AppID, dataDown.DevID, *dataDown) + delivery.Ack(false) + break + } + }() + + return nil +} + +// SubscribeAppDownlink subscribes to all downlink messages for the given application +func (s *DefaultSubscriber) SubscribeAppDownlink(appID string, handler DownlinkHandler) error { + return s.SubscribeDeviceDownlink(appID, "", handler) +} + +// SubscribeDownlink subscribes to all downlink messages that the current user has access to +func (s *DefaultSubscriber) SubscribeDownlink(handler DownlinkHandler) error { + return s.SubscribeDeviceDownlink("", "", handler) +} diff --git a/amqp/downlink_test.go b/amqp/downlink_test.go new file mode 100644 index 000000000..4397f9904 --- /dev/null +++ b/amqp/downlink_test.go @@ -0,0 +1,70 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "sync" + "testing" + + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestPublishDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestPublishDownlink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("", "") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + err = p.PublishDownlink(types.DownlinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) +} + +func TestSubscribeDownlink(t *testing.T) { + a := New(t) + c := NewClient(GetLogger(t, "TestSubscribeDownlink"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + p := c.NewPublisher("amq.topic", "topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + + s := c.NewSubscriber("amq.topic", "topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() + + wg := &sync.WaitGroup{} + wg.Add(1) + err = s.SubscribeDownlink(func(_ Subscriber, appID, devID string, req types.DownlinkMessage) { + a.So(appID, ShouldEqual, "app") + a.So(devID, ShouldEqual, "test") + a.So(req.PayloadRaw, ShouldResemble, []byte{0x01, 0x08}) + wg.Done() + }) + a.So(err, ShouldBeNil) + + err = p.PublishDownlink(types.DownlinkMessage{ + AppID: "app", + DevID: "test", + PayloadRaw: []byte{0x01, 0x08}, + }) + a.So(err, ShouldBeNil) + + wg.Wait() +} diff --git a/amqp/publisher.go b/amqp/publisher.go index df043fd7d..2582d7e81 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -15,6 +15,7 @@ type Publisher interface { ChannelClient PublishUplink(dataUp types.UplinkMessage) error + PublishDownlink(dataDown types.DownlinkMessage) error } // DefaultPublisher represents the default AMQP publisher diff --git a/amqp/subscriber.go b/amqp/subscriber.go index 6e404532d..56f7cc992 100644 --- a/amqp/subscriber.go +++ b/amqp/subscriber.go @@ -19,6 +19,10 @@ type Subscriber interface { SubscribeDeviceUplink(appID, devID string, handler UplinkHandler) error SubscribeAppUplink(appID string, handler UplinkHandler) error SubscribeUplink(handler UplinkHandler) error + + SubscribeDeviceDownlink(appID, devID string, handler DownlinkHandler) error + SubscribeAppDownlink(appID string, handler DownlinkHandler) error + SubscribeDownlink(handler DownlinkHandler) error } // DefaultSubscriber represents the default AMQP subscriber diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index eb67b0cc9..c13cd0d4d 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -50,6 +50,7 @@ func TestSubscribeUplink(t *testing.T) { defer s.Close() wg := &sync.WaitGroup{} + wg.Add(1) err = s.SubscribeUplink(func(_ Subscriber, appID, devID string, req types.UplinkMessage) { a.So(appID, ShouldEqual, "app") a.So(devID, ShouldEqual, "test") @@ -58,7 +59,6 @@ func TestSubscribeUplink(t *testing.T) { }) a.So(err, ShouldBeNil) - wg.Add(1) err = p.PublishUplink(types.UplinkMessage{ AppID: "app", DevID: "test", From 4d1ea25328a710fca3487c7c0a8202c0ad8e4c74 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 23 Oct 2016 18:50:26 +0100 Subject: [PATCH 2033/2266] Testing for downlink using AMQP --- amqp/subscriber.go | 12 ++++-- core/handler/amqp.go | 22 ++++++++++ core/handler/amqp_test.go | 86 +++++++++++++++++++++++++++++++++++++++ core/handler/mqtt_test.go | 5 ++- 4 files changed, 119 insertions(+), 6 deletions(-) create mode 100644 core/handler/amqp_test.go diff --git a/amqp/subscriber.go b/amqp/subscriber.go index 56f7cc992..6528ecf49 100644 --- a/amqp/subscriber.go +++ b/amqp/subscriber.go @@ -3,7 +3,11 @@ package amqp -import AMQP "github.com/streadway/amqp" +import ( + "fmt" + + AMQP "github.com/streadway/amqp" +) var ( // PrefetchCount represents the number of messages to prefetch before the AMQP server requires acknowledgment @@ -52,17 +56,17 @@ func (c *DefaultClient) NewSubscriber(exchange, exchangeType, name string, durab func (s *DefaultSubscriber) subscribe(key string) (<-chan AMQP.Delivery, error) { queue, err := s.channel.QueueDeclare(s.name, s.durable, s.autoDelete, false, false, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to declare queue '%s' (%s)", s.name, err) } err = s.channel.QueueBind(queue.Name, key, s.exchange, false, nil) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to bind queue %s with key %s on exchange '%s' (%s)", queue.Name, key, s.exchange, err) } err = s.channel.Qos(PrefetchCount, PrefetchSize, false) if err != nil { - return nil, err + return nil, fmt.Errorf("Failed to set channel QoS (%s)", err) } return s.channel.Consume(queue.Name, "", false, false, false, false, nil) diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 87cd8eb0a..5007337c5 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -17,9 +17,31 @@ func (h *handler) HandleAMQP(username, password, host, exchange string) error { if err != nil { return err } + defer func() { + if err != nil { + h.amqpClient.Disconnect() + } + }() h.amqpUp = make(chan *types.UplinkMessage) + subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, "topic", "ttn-handler-downlink", true, false) + err = subscriber.Open() + if err != nil { + return err + } + defer func() { + if err != nil { + subscriber.Close() + } + }() + err = subscriber.SubscribeDownlink(func(_ amqp.Subscriber, appID, devID string, req types.DownlinkMessage) { + h.EnqueueDownlink(&req) + }) + if err != nil { + return err + } + ctx := h.Ctx.WithField("Protocol", "AMQP") go func() { diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go new file mode 100644 index 000000000..da8187d30 --- /dev/null +++ b/core/handler/amqp_test.go @@ -0,0 +1,86 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "os" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/amqp" + "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/handler/device" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestHandleAMQP(t *testing.T) { + host := os.Getenv("AMQP_HOST") + if host == "" { + host = "localhost:5672" + } + + a := New(t) + var wg WaitGroup + c := amqp.NewClient(GetLogger(t, "TestHandleAMQP"), "guest", "guest", host) + err := c.Connect() + a.So(err, ShouldBeNil) + defer c.Disconnect() + + appID := "handler-amqp-app1" + devID := "handler-amqp-dev1" + h := &handler{ + Component: &core.Component{Ctx: GetLogger(t, "TestHandleAMQP")}, + devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-amqp"), + } + h.WithAMQP("guest", "guest", host, "amq.topic") + h.devices.Set(&device.Device{ + AppID: appID, + DevID: devID, + }) + defer func() { + h.devices.Delete(appID, devID) + }() + err = h.HandleAMQP("guest", "guest", host, "amq.topic") + a.So(err, ShouldBeNil) + + p := c.NewPublisher("amq.topic", "topic") + err = p.Open() + a.So(err, ShouldBeNil) + defer p.Close() + err = p.PublishDownlink(types.DownlinkMessage{ + AppID: appID, + DevID: devID, + PayloadRaw: []byte{0xAA, 0xBC}, + }) + a.So(err, ShouldBeNil) + <-time.After(50 * time.Millisecond) + dev, _ := h.devices.Get(appID, devID) + a.So(dev.NextDownlink, ShouldNotBeNil) + + wg.Add(1) + s := c.NewSubscriber("amq.topic", "topic", "", false, true) + err = s.Open() + a.So(err, ShouldBeNil) + defer s.Close() + err = s.SubscribeDeviceUplink(appID, devID, func(_ amqp.Subscriber, r_appID string, r_devID string, req types.UplinkMessage) { + a.So(r_appID, ShouldEqual, appID) + a.So(r_devID, ShouldEqual, devID) + a.So(req.PayloadRaw, ShouldResemble, []byte{0xAA, 0xBC}) + wg.Done() + }) + a.So(err, ShouldBeNil) + + h.amqpUp <- &types.UplinkMessage{ + DevID: devID, + AppID: appID, + PayloadRaw: []byte{0xAA, 0xBC}, + PayloadFields: map[string]interface{}{ + "field": "value", + }, + } + + a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) +} diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index e7ac02773..821a93739 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -26,7 +26,8 @@ func TestHandleMQTT(t *testing.T) { a := New(t) var wg WaitGroup c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) - c.Connect() + err := c.Connect() + a.So(err, ShouldBeNil) appID := "handler-mqtt-app1" devID := "handler-mqtt-dev1" h := &handler{ @@ -40,7 +41,7 @@ func TestHandleMQTT(t *testing.T) { defer func() { h.devices.Delete(appID, devID) }() - err := h.HandleMQTT("", "", fmt.Sprintf("tcp://%s:1883", host)) + err = h.HandleMQTT("", "", fmt.Sprintf("tcp://%s:1883", host)) a.So(err, ShouldBeNil) c.PublishDownlink(types.DownlinkMessage{ From c5d6aa19cfae7698201fb7a6d2cef4ffb615b01e Mon Sep 17 00:00:00 2001 From: johanstokking Date: Sun, 23 Oct 2016 19:03:52 +0100 Subject: [PATCH 2034/2266] Allow for custom downlink queue name --- core/handler/amqp.go | 4 ++-- core/handler/amqp_test.go | 2 +- core/handler/handler.go | 2 +- core/handler/mqtt_test.go | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 5007337c5..58dde4d08 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -10,7 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -func (h *handler) HandleAMQP(username, password, host, exchange string) error { +func (h *handler) HandleAMQP(username, password, host, exchange, name string) error { h.amqpClient = amqp.NewClient(h.Ctx, username, password, host) err := h.amqpClient.Connect() @@ -25,7 +25,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange string) error { h.amqpUp = make(chan *types.UplinkMessage) - subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, "topic", "ttn-handler-downlink", true, false) + subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, "topic", name, name != "", name == "") err = subscriber.Open() if err != nil { return err diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go index da8187d30..9d61bc25d 100644 --- a/core/handler/amqp_test.go +++ b/core/handler/amqp_test.go @@ -43,7 +43,7 @@ func TestHandleAMQP(t *testing.T) { defer func() { h.devices.Delete(appID, devID) }() - err = h.HandleAMQP("guest", "guest", host, "amq.topic") + err = h.HandleAMQP("guest", "guest", host, "amq.topic", "") a.So(err, ShouldBeNil) p := c.NewPublisher("amq.topic", "topic") diff --git a/core/handler/handler.go b/core/handler/handler.go index 59369cd0a..be7e88386 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -107,7 +107,7 @@ func (h *handler) Init(c *core.Component) error { } if h.amqpEnabled { - err = h.HandleAMQP(h.amqpUsername, h.amqpPassword, h.amqpHost, h.amqpExchange) + err = h.HandleAMQP(h.amqpUsername, h.amqpPassword, h.amqpHost, h.amqpExchange, "ttn-handler-downlink") if err != nil { return err } diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 821a93739..e39d14e4b 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -27,7 +27,7 @@ func TestHandleMQTT(t *testing.T) { var wg WaitGroup c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) err := c.Connect() - a.So(err, ShouldBeNil) + a.So(err, ShouldBeNil) appID := "handler-mqtt-app1" devID := "handler-mqtt-dev1" h := &handler{ From 446e37efa943daefcc92848824e226d44ef9ba28 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 24 Oct 2016 09:36:59 +0100 Subject: [PATCH 2035/2266] Code cleanup --- core/handler/amqp.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 58dde4d08..8bb1b931e 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -10,7 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" ) -func (h *handler) HandleAMQP(username, password, host, exchange, name string) error { +func (h *handler) HandleAMQP(username, password, host, exchange, downlinkQueue string) error { h.amqpClient = amqp.NewClient(h.Ctx, username, password, host) err := h.amqpClient.Connect() @@ -25,7 +25,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange, name string) er h.amqpUp = make(chan *types.UplinkMessage) - subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, "topic", name, name != "", name == "") + subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, "topic", downlinkQueue, downlinkQueue != "", downlinkQueue == "") err = subscriber.Open() if err != nil { return err @@ -35,7 +35,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange, name string) er subscriber.Close() } }() - err = subscriber.SubscribeDownlink(func(_ amqp.Subscriber, appID, devID string, req types.DownlinkMessage) { + err = subscriber.SubscribeDownlink(func(_ amqp.Subscriber, _, _ string, req types.DownlinkMessage) { h.EnqueueDownlink(&req) }) if err != nil { From 80796b6e8bc02c82d2ecccad43eaf21ea7a04d97 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 11:31:31 +0200 Subject: [PATCH 2036/2266] Do not use authentication on local MQTT broker --- cmd/handler.go | 4 ++-- ttnctl/cmd/root.go | 4 ++-- ttnctl/util/mqtt.go | 10 +++++++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index d1cb574fd..2fb65192a 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -109,10 +109,10 @@ func init() { handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "MQTT broker host and port") viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) - handlerCmd.Flags().String("mqtt-username", "guest", "MQTT username") + handlerCmd.Flags().String("mqtt-username", "", "MQTT username") viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) - handlerCmd.Flags().String("mqtt-password", "guest", "MQTT password") + handlerCmd.Flags().String("mqtt-password", "", "MQTT password") viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) handlerCmd.Flags().String("amqp-host", "", "AMQP host and port. Leave empty to disable AMQP") diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 0a4e72268..5dee4073a 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -82,10 +82,10 @@ func init() { RootCmd.PersistentFlags().String("mqtt-broker", "eu.thethings.network:1883", "The address of the MQTT broker") viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) - RootCmd.PersistentFlags().String("mqtt-username", "guest", "The username for the MQTT broker") + RootCmd.PersistentFlags().String("mqtt-username", "", "The username for the MQTT broker") viper.BindPFlag("mqtt-username", RootCmd.PersistentFlags().Lookup("mqtt-username")) - RootCmd.PersistentFlags().String("mqtt-password", "guest", "The password for the MQTT broker") + RootCmd.PersistentFlags().String("mqtt-password", "", "The password for the MQTT broker") viper.BindPFlag("mqtt-password", RootCmd.PersistentFlags().Lookup("mqtt-password")) RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 78e9aac82..c915b3de1 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -29,7 +29,10 @@ func GetMQTT(ctx log.Interface) mqtt.Client { broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-broker")) client := mqtt.NewClient(ctx, "ttnctl", username, password, broker) - ctx.WithField("MQTT Broker", broker).Info("Connecting to MQTT...") + ctx.WithFields(log.Fields{ + "MQTT Broker": broker, + "Username": username, + }).Info("Connecting to MQTT...") if err := client.Connect(); err != nil { ctx.WithError(err).Fatal("Could not connect") @@ -45,6 +48,11 @@ func getMQTTCredentials(ctx log.Interface) (username string, password string, er return } + // Do not use authentication on local MQTT + if strings.HasPrefix(viper.GetString("mqtt-broker"), "localhost") { + return + } + return getAppMQTTCredentials(ctx) } From 7c3bc30890207c0a9e28aee6e2485e728598c84e Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Thu, 20 Oct 2016 17:56:26 +0200 Subject: [PATCH 2037/2266] monitor integration: #296 reimplementation - adds api/monitor.Client - wrapper for monitor.MonitorClient - adds api/monitor.GatewayClient - uses api/monitor.GatewayClient instead of core/router/gateway/monitor.go - improves logging functionality --- api/monitor/client.go | 412 ++++++++++++++++++++++++++++++++- core/component.go | 6 +- core/router/gateway/gateway.go | 21 +- core/router/gateway/monitor.go | 193 --------------- core/router/router.go | 6 +- 5 files changed, 427 insertions(+), 211 deletions(-) delete mode 100644 core/router/gateway/monitor.go diff --git a/api/monitor/client.go b/api/monitor/client.go index 2421b8fc8..d3826c21a 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -1,17 +1,421 @@ package monitor import ( + "context" + "io" + "sync" + "github.com/TheThingsNetwork/ttn/api" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" ) +// Client is a wrapper around MonitorClient +type Client struct { + Log log.Interface + + client MonitorClient + conn *grpc.ClientConn + addr string + + reopening chan struct{} + + gateways map[string]GatewayClient + sync.RWMutex +} + // NewClient is a wrapper for NewMonitorClient, initializes // connection to MonitorServer on monitorAddr with default gRPC options -func NewClient(monitorAddr string) (cl MonitorClient, err error) { - var conn *grpc.ClientConn - if conn, err = grpc.Dial(monitorAddr, append(api.DialOptions, grpc.WithInsecure())...); err != nil { +func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { + cl = &Client{ + Log: ctx, + addr: monitorAddr, + gateways: make(map[string]GatewayClient), + } + return cl, cl.Open() +} + +func (cl *Client) Open() (err error) { + cl.Lock() + defer cl.Unlock() + + return cl.open() +} +func (cl *Client) open() (err error) { + addr := cl.addr + + ctx := cl.Log.WithField("addr", addr) + ctx.Debug("Opening monitor connection...") + + cl.conn, err = grpc.Dial(addr, append(api.DialOptions, grpc.WithInsecure())...) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection") + return err + } + ctx.Debug("Connection established") + + cl.client = NewMonitorClient(cl.conn) + return nil +} + +// Close closes connection to the monitor +func (cl *Client) Close() (err error) { + cl.Lock() + defer cl.Unlock() + + return cl.close() +} +func (cl *Client) close() (err error) { + cl.Log.Debug("Closing monitor connection...") + for _, gtw := range cl.gateways { + err = gtw.Close() + if err != nil { + cl.Log.WithError(err).WithField("GatewayID", gtw.(*gatewayClient).id).Warn("Failed to close streams") + } + } + + err = cl.conn.Close() + if err != nil { + return err + } + + cl.conn = nil + return nil +} + +func (cl *Client) Reopen() (err error) { + cl.Lock() + defer cl.Unlock() + + return cl.reopen() +} +func (cl *Client) reopen() (err error) { + cl.Log.Debug("Reopening monitor connection...") + + cl.reopening = make(chan struct{}) + defer func() { + close(cl.reopening) + cl.reopening = nil + }() + + err = cl.close() + if err != nil { + return err + } + return cl.open() +} + +func (cl *Client) IsReopening() bool { + return cl.reopening != nil +} + +func (cl *Client) IsConnected() bool { + return cl.client != nil && cl.conn != nil +} + +func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { + cl.RLock() + gtwCl, ok := cl.gateways[id] + cl.RUnlock() + if !ok { + cl.Lock() + gtwCl = &gatewayClient{ + Log: cl.Log.WithField("GatewayID", id), + + client: cl, + + id: id, + token: token, + } + cl.gateways[id] = gtwCl + cl.Unlock() + } + return gtwCl +} + +type gatewayClient struct { + client *Client + + Log log.Interface + + id, token string + + status struct { + stream Monitor_GatewayStatusClient + sync.RWMutex + } + + uplink struct { + stream Monitor_GatewayUplinkClient + sync.RWMutex + } + + downlink struct { + stream Monitor_GatewayDownlinkClient + sync.RWMutex + } +} + +// GatewayClient is used as the main client for Gateways to communicate with the Router +type GatewayClient interface { + SendStatus(status *pb_gateway.Status) (err error) + SendUplink(msg *router.UplinkMessage) (err error) + SendDownlink(msg *router.DownlinkMessage) (err error) + Close() (err error) +} + +func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { + defer func() { + if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor") + + if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { + cl.client.Lock() + defer cl.client.Unlock() + + if !cl.client.IsReopening() { + err = cl.client.reopen() + } + } + } else { + cl.Log.Debug("Sent status to monitor") + } + }() + + cl.status.RLock() + stream := cl.status.stream + cl.status.RUnlock() + + if stream == nil { + cl.status.Lock() + if stream = cl.status.stream; stream == nil { + stream, err = cl.setupStatus() + if err != nil { + cl.status.Unlock() + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") + return err + } + + cl.Log.Debug("Opened new monitor status stream") + } + cl.status.Unlock() + } + + if err = stream.Send(status); err == io.EOF { + cl.Log.Warn("Monitor status stream closed") + cl.status.Lock() + if cl.status.stream == stream { + cl.status.stream = nil + } + cl.status.Unlock() + return nil + } + return err +} + +func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { + defer func() { + if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor") + + if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { + cl.Log.Debug("error is internal") + cl.Log.Debug("Locking...") + cl.client.Lock() + cl.Log.Debug("Locked...") + + cl.Log.Debug("Check if reopening...") + if !cl.client.IsReopening() { + cl.Log.Debug("Not reopening...") + err = cl.client.reopen() + } + cl.Log.Debug("Unlocking...") + cl.client.Unlock() + cl.Log.Debug("Unlocked...") + cl.Log.Debugf("return %s", err) + } + } else { + cl.Log.Debug("Sent uplink to monitor") + } + }() + + cl.uplink.RLock() + stream := cl.uplink.stream + cl.uplink.RUnlock() + + if stream == nil { + cl.uplink.Lock() + if stream = cl.uplink.stream; stream == nil { + stream, err = cl.setupUplink() + if err != nil { + cl.uplink.Unlock() + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") + return err + } + + cl.Log.Debug("Opened new monitor uplink stream") + } + cl.uplink.Unlock() + } + + if err = stream.Send(uplink); err == io.EOF { + cl.Log.Warn("Monitor uplink stream closed") + cl.uplink.Lock() + if cl.uplink.stream == stream { + cl.uplink.stream = nil + } + cl.uplink.Unlock() + return nil + } + return err +} + +func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { + defer func() { + if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor") + + if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { + cl.client.Lock() + defer cl.client.Unlock() + + if !cl.client.IsReopening() { + err = cl.client.reopen() + } + } + } else { + cl.Log.Debug("Sent downlink to monitor") + } + }() + + cl.downlink.RLock() + stream := cl.downlink.stream + cl.downlink.RUnlock() + + if stream == nil { + cl.downlink.Lock() + if stream = cl.downlink.stream; stream == nil { + stream, err = cl.setupDownlink() + if err != nil { + cl.downlink.Unlock() + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") + return err + } + + cl.Log.Debug("Opened new monitor downlink stream") + } + cl.downlink.Unlock() + } + + if err = stream.Send(downlink); err == io.EOF { + cl.Log.Warn("Monitor downlink stream closed") + cl.downlink.Lock() + if cl.downlink.stream == stream { + cl.downlink.stream = nil + } + cl.downlink.Unlock() + return nil + } + return err +} + +func (cl *gatewayClient) Close() (err error) { + wg := &sync.WaitGroup{} + + wg.Add(1) + go func() { + defer wg.Done() + + cl.Log.Debug("Status locking...") + cl.status.Lock() + cl.Log.Debug("Status locked") + + if cl.status.stream != nil { + if cerr := cl.status.stream.CloseSend(); cerr != nil { + cl.Log.WithError(cerr).Warn("Failed to close status stream") + err = cerr + } + cl.status.stream = nil + } + }() + defer cl.status.Unlock() + + wg.Add(1) + go func() { + defer wg.Done() + + cl.Log.Debug("Uplink locking...") + cl.uplink.Lock() + cl.Log.Debug("Uplink locked") + + if cl.uplink.stream != nil { + if cerr := cl.uplink.stream.CloseSend(); cerr != nil { + cl.Log.WithError(cerr).Warn("Failed to close uplink stream") + err = cerr + } + cl.uplink.stream = nil + } + }() + defer cl.uplink.Unlock() + + wg.Add(1) + go func() { + defer wg.Done() + + cl.downlink.Lock() + + if cl.downlink.stream != nil { + if cerr := cl.downlink.stream.CloseSend(); cerr != nil { + cl.Log.WithError(cerr).Warn("Failed to close downlink stream") + err = cerr + } + cl.downlink.stream = nil + } + }() + defer cl.downlink.Unlock() + + wg.Wait() + return err +} + +func (cl *gatewayClient) Context() (monitorContext context.Context) { + return metadata.NewContext(context.Background(), metadata.Pairs( + "id", cl.id, + "token", cl.token, + )) +} + +func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err error) { + stream, err = cl.client.client.GatewayStatus(cl.Context()) + if err != nil { + return nil, err + } + + cl.status.stream = stream + return stream, nil +} + +func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err error) { + stream, err = cl.client.client.GatewayUplink(cl.Context()) + if err != nil { + return nil, err + } + + cl.uplink.stream = stream + return stream, nil +} + +func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, err error) { + stream, err = cl.client.client.GatewayDownlink(cl.Context()) + if err != nil { return nil, err } - return NewMonitorClient(conn), nil + cl.downlink.stream = stream + return stream, nil } diff --git a/core/component.go b/core/component.go index 8bde2c663..b3636f521 100644 --- a/core/component.go +++ b/core/component.go @@ -133,10 +133,10 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string } if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { - component.Monitors = make(map[string]pb_monitor.MonitorClient) + component.Monitors = make(map[string]*pb_monitor.Client) for name, addr := range monitors { var err error - component.Monitors[name], err = pb_monitor.NewClient(addr) + component.Monitors[name], err = pb_monitor.NewClient(ctx.WithField("Monitor", name), addr) if err != nil { // Assuming grpc.WithBlock() is not set return nil, err @@ -161,7 +161,7 @@ const ( type Component struct { Identity *pb_discovery.Announcement Discovery pb_discovery.Client - Monitors map[string]pb_monitor.MonitorClient + Monitors map[string]*pb_monitor.Client Ctx log.Interface AccessToken string privateKey *ecdsa.PrivateKey diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 3a1d7c55a..8291232e1 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -8,6 +8,7 @@ import ( "time" pb "github.com/TheThingsNetwork/ttn/api/gateway" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" pb_router "github.com/TheThingsNetwork/ttn/api/router" "github.com/apex/log" ) @@ -35,7 +36,7 @@ type Gateway struct { Token string tokenLock sync.Mutex - monitor *monitorConn + Monitors map[string]pb_monitor.GatewayClient Ctx log.Interface } @@ -57,9 +58,9 @@ func (g *Gateway) HandleStatus(status *pb.Status) (err error) { } g.updateLastSeen() - if g.monitor != nil { - for name := range g.monitor.clients { - go g.pushStatusToMonitor(g.Ctx.WithField("Monitor", name), name, status) + if g.Monitors != nil { + for _, monitor := range g.Monitors { + go monitor.SendStatus(status) } } return nil @@ -72,9 +73,9 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) g.updateLastSeen() - if g.monitor != nil { - for name := range g.monitor.clients { - go g.pushUplinkToMonitor(g.Ctx.WithField("Monitor", name), name, uplink) + if g.Monitors != nil { + for _, monitor := range g.Monitors { + go monitor.SendUplink(uplink) } } return nil @@ -87,9 +88,9 @@ func (g *Gateway) HandleDownlink(identifier string, downlink *pb_router.Downlink return err } - if g.monitor != nil { - for name := range g.monitor.clients { - go g.pushDownlinkToMonitor(ctx.WithField("Monitor", name), name, downlink) + if g.Monitors != nil { + for _, monitor := range g.Monitors { + go monitor.SendDownlink(downlink) } } return nil diff --git a/core/router/gateway/monitor.go b/core/router/gateway/monitor.go deleted file mode 100644 index 5c6e343cb..000000000 --- a/core/router/gateway/monitor.go +++ /dev/null @@ -1,193 +0,0 @@ -package gateway - -import ( - "io" - "sync" - - pb "github.com/TheThingsNetwork/ttn/api/gateway" - pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" - pb_router "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/apex/log" - context "golang.org/x/net/context" - "google.golang.org/grpc/metadata" -) - -func (g *Gateway) monitorContext() (ctx context.Context) { - return metadata.NewContext(context.Background(), metadata.Pairs( - "id", g.ID, - "token", g.Token, - )) -} - -type monitorConn struct { - clients map[string]pb_monitor.MonitorClient - - status struct { - streams map[string]pb_monitor.Monitor_GatewayStatusClient - sync.RWMutex - } - - uplink struct { - streams map[string]pb_monitor.Monitor_GatewayUplinkClient - sync.RWMutex - } - - downlink struct { - streams map[string]pb_monitor.Monitor_GatewayDownlinkClient - sync.RWMutex - } -} - -func (g *Gateway) SetMonitors(clients map[string]pb_monitor.MonitorClient) { - g.monitor = NewMonitorConn(clients) -} - -func NewMonitorConn(clients map[string]pb_monitor.MonitorClient) (conn *monitorConn) { - conn = &monitorConn{clients: clients} - conn.uplink.streams = make(map[string]pb_monitor.Monitor_GatewayUplinkClient) - conn.downlink.streams = make(map[string]pb_monitor.Monitor_GatewayDownlinkClient) - conn.status.streams = make(map[string]pb_monitor.Monitor_GatewayStatusClient) - return conn -} - -func (g *Gateway) pushStatusToMonitor(ctx log.Interface, name string, status *pb.Status) (err error) { - defer func() { - if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor status push failed") - } else { - ctx.Debug("Pushed status to monitor") - } - }() - - g.monitor.status.RLock() - stream, ok := g.monitor.status.streams[name] - g.monitor.status.RUnlock() - - if !ok { - g.monitor.status.Lock() - if _, ok := g.monitor.status.streams[name]; !ok { - cl, ok := g.monitor.clients[name] - if !ok { - // Should not happen - g.monitor.status.Unlock() - return errors.New("Monitor not found") - } - - stream, err = cl.GatewayStatus(g.monitorContext()) - if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") - g.monitor.status.Unlock() - return err - } - ctx.Debug("Opened new monitor status stream") - - g.monitor.status.streams[name] = stream - } - g.monitor.status.Unlock() - } - - if err = stream.Send(status); err == io.EOF { - ctx.Warn("Monitor status stream closed") - g.monitor.status.Lock() - if g.monitor.status.streams[name] == stream { - delete(g.monitor.status.streams, name) - } - g.monitor.status.Unlock() - } - return err -} - -func (g *Gateway) pushUplinkToMonitor(ctx log.Interface, name string, uplink *pb_router.UplinkMessage) (err error) { - defer func() { - if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor uplink push failed") - } else { - ctx.Debug("Pushed uplink to monitor") - } - }() - - g.monitor.uplink.RLock() - stream, ok := g.monitor.uplink.streams[name] - g.monitor.uplink.RUnlock() - - if !ok { - g.monitor.uplink.Lock() - if _, ok := g.monitor.uplink.streams[name]; !ok { - cl, ok := g.monitor.clients[name] - if !ok { - // Should not happen - g.monitor.uplink.Unlock() - return errors.New("Monitor not found") - } - - stream, err = cl.GatewayUplink(g.monitorContext()) - if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") - g.monitor.uplink.Unlock() - return err - } - ctx.Debug("Opened new monitor uplink stream") - - g.monitor.uplink.streams[name] = stream - } - g.monitor.uplink.Unlock() - } - - if err = stream.Send(uplink); err == io.EOF { - ctx.Warn("Monitor uplink stream closed") - g.monitor.uplink.Lock() - if g.monitor.uplink.streams[name] == stream { - delete(g.monitor.uplink.streams, name) - } - g.monitor.uplink.Unlock() - } - return err -} - -func (g *Gateway) pushDownlinkToMonitor(ctx log.Interface, name string, downlink *pb_router.DownlinkMessage) (err error) { - defer func() { - if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Monitor downlink push failed") - } else { - ctx.Debug("Pushed downlink to monitor") - } - }() - - g.monitor.downlink.RLock() - stream, ok := g.monitor.downlink.streams[name] - g.monitor.downlink.RUnlock() - - if !ok { - g.monitor.downlink.Lock() - if _, ok := g.monitor.downlink.streams[name]; !ok { - cl, ok := g.monitor.clients[name] - if !ok { - // Should not happen - g.monitor.downlink.Unlock() - return errors.New("Monitor not found") - } - - stream, err = cl.GatewayDownlink(g.monitorContext()) - if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") - g.monitor.downlink.Unlock() - return err - } - ctx.Debug("Opened new monitor downlink stream") - - g.monitor.downlink.streams[name] = stream - } - g.monitor.downlink.Unlock() - } - - if err = stream.Send(downlink); err == io.EOF { - ctx.Warn("Monitor downlink stream closed") - g.monitor.downlink.Lock() - if g.monitor.downlink.streams[name] == stream { - delete(g.monitor.downlink.streams, name) - } - g.monitor.downlink.Unlock() - } - return err -} diff --git a/core/router/router.go b/core/router/router.go index 26897f81e..a90f00482 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -11,6 +11,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/router/gateway" @@ -107,7 +108,10 @@ func (r *router) getGateway(id string) *gateway.Gateway { gtw = gateway.NewGateway(r.Ctx, id) if r.Component.Monitors != nil { - gtw.SetMonitors(r.Component.Monitors) + gtw.Monitors = make(map[string]pb_monitor.GatewayClient) + for name, cl := range r.Component.Monitors { + gtw.Monitors[name] = cl.GatewayClient(gtw.ID, gtw.Token) + } } r.gateways[id] = gtw From 46b662ac76fcf43a0ab159c2c4089ef4988092b4 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 12:02:34 +0200 Subject: [PATCH 2038/2266] Move device events to core package --- core/handler/activation.go | 27 ++++++++------ core/handler/activation_test.go | 7 ++-- core/handler/convert_lorawan.go | 9 ++--- core/handler/convert_lorawan_test.go | 2 +- core/handler/downlink.go | 32 +++++++--------- core/handler/downlink_test.go | 4 +- core/handler/handler.go | 13 +++---- core/handler/mqtt.go | 37 ++---------------- core/handler/mqtt_test.go | 3 +- core/handler/uplink.go | 10 ++--- core/handler/uplink_test.go | 2 +- core/types/event.go | 56 ++++++++++++++++++++++++++++ mqtt/README.md | 6 +-- mqtt/activations.go | 13 +++---- mqtt/client.go | 12 +++--- mqtt/events.go | 33 ++++++++-------- mqtt/events_test.go | 5 ++- 17 files changed, 146 insertions(+), 125 deletions(-) create mode 100644 core/types/event.go diff --git a/core/handler/activation.go b/core/handler/activation.go index f875d94a3..9c1d16926 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -87,11 +87,11 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv start := time.Now() defer func() { if err != nil { - h.mqttEvent <- &mqttEvent{ - AppID: appID, - DevID: devID, - Type: "activations/errors", - Payload: map[string]string{"error": err.Error()}, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.ActivationErrorEvent, + Data: types.ErrorEventData{Error: err.Error()}, } ctx.WithError(err).Warn("Could not handle activation") } else { @@ -172,13 +172,16 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv // Publish Activation mqttMetadata, _ := h.getActivationMetadata(ctx, activation) - h.mqttActivation <- &types.Activation{ - AppEUI: *activation.AppEui, - DevEUI: *activation.DevEui, - AppID: appID, - DevID: devID, - DevAddr: types.DevAddr(joinAccept.DevAddr), - Metadata: mqttMetadata, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.ActivationEvent, + Data: types.ActivationEventData{ + AppEUI: *activation.AppEui, + DevEUI: *activation.DevEui, + DevAddr: types.DevAddr(joinAccept.DevAddr), + Metadata: mqttMetadata, + }, } // Generate random AppNonce diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index a7bb0bcbf..4db14da09 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -70,8 +70,7 @@ func TestHandleActivation(t *testing.T) { applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), } - h.mqttActivation = make(chan *types.Activation) - h.mqttEvent = make(chan *mqttEvent, 10) + h.mqttEvent = make(chan *types.DeviceEvent, 10) var wg WaitGroup appEUI := types.AppEUI{1, 2, 3, 4, 5, 6, 7, 8} @@ -122,7 +121,7 @@ func TestHandleActivation(t *testing.T) { wg.Add(1) go func() { - <-h.mqttActivation + <-h.mqttEvent wg.Done() }() @@ -150,7 +149,7 @@ func TestHandleActivation(t *testing.T) { wg.Add(1) go func() { - <-h.mqttActivation + <-h.mqttEvent wg.Done() }() diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index e3ee9dc14..645081639 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -66,11 +66,10 @@ func (h *handler) ConvertFromLoRaWAN(ctx log.Interface, ttnUp *pb_broker.Dedupli // LoRaWAN: Publish ACKs as events if macPayload.FHDR.FCtrl.ACK { - h.mqttEvent <- &mqttEvent{ - AppID: appUp.AppID, - DevID: appUp.DevID, - Type: "down/acks", - Payload: map[string]interface{}{}, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appUp.AppID, + DevID: appUp.DevID, + Event: types.DownlinkAckEvent, } } diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index af13e617a..fb5c9e3bc 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -36,7 +36,7 @@ func TestConvertFromLoRaWAN(t *testing.T) { h := &handler{ devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-from-lorawan"), Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, - mqttEvent: make(chan *mqttEvent, 10), + mqttEvent: make(chan *types.DeviceEvent, 10), } h.devices.Set(&device.Device{ DevID: "devid", diff --git a/core/handler/downlink.go b/core/handler/downlink.go index e08ebd810..815dc8c30 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -43,11 +43,10 @@ func (h *handler) EnqueueDownlink(appDownlink *types.DownlinkMessage) (err error return err } - h.mqttEvent <- &mqttEvent{ - AppID: appID, - DevID: devID, - Type: "down/scheduled", - Payload: appDownlink, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.DownlinkScheduledEvent, } return nil @@ -66,11 +65,11 @@ func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *p var err error defer func() { if err != nil { - h.mqttEvent <- &mqttEvent{ - AppID: appID, - DevID: devID, - Type: "down/errors", - Payload: map[string]string{"error": err.Error()}, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.DownlinkErrorEvent, + Data: types.ErrorEventData{Error: err.Error()}, } ctx.WithError(err).Warn("Could not handle downlink") } @@ -99,16 +98,11 @@ func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *p h.downlink <- downlink - appDownlinkCopy := *appDownlink - appDownlinkCopy.AppID = "" - appDownlinkCopy.DevID = "" - appDownlinkCopy.PayloadFields = make(map[string]interface{}) - h.mqttEvent <- &mqttEvent{ - AppID: appDownlink.AppID, - DevID: appDownlink.DevID, - Type: "down/sent", - Payload: appDownlinkCopy, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appDownlink.AppID, + DevID: appDownlink.DevID, + Event: types.DownlinkSentEvent, } return nil diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index a8456fceb..f7dfc28a3 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -23,7 +23,7 @@ func TestEnqueueDownlink(t *testing.T) { h := &handler{ Component: &core.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-enqueue-downlink"), - mqttEvent: make(chan *mqttEvent, 10), + mqttEvent: make(chan *types.DeviceEvent, 10), } err := h.EnqueueDownlink(&types.DownlinkMessage{ AppID: appID, @@ -65,7 +65,7 @@ func TestHandleDownlink(t *testing.T) { devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-downlink"), applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-enqueue-downlink"), downlink: make(chan *pb_broker.DownlinkMessage), - mqttEvent: make(chan *mqttEvent, 10), + mqttEvent: make(chan *types.DeviceEvent, 10), } // Neither payload nor Fields provided : ERROR err = h.HandleDownlink(&types.DownlinkMessage{ diff --git a/core/handler/handler.go b/core/handler/handler.go index 59369cd0a..af9b11414 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -59,13 +59,12 @@ type handler struct { downlink chan *pb_broker.DownlinkMessage - mqttClient mqtt.Client - mqttUsername string - mqttPassword string - mqttBrokers []string - mqttUp chan *types.UplinkMessage - mqttActivation chan *types.Activation - mqttEvent chan *mqttEvent + mqttClient mqtt.Client + mqttUsername string + mqttPassword string + mqttBrokers []string + mqttUp chan *types.UplinkMessage + mqttEvent chan *types.DeviceEvent amqpClient amqp.Client amqpUsername string diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 1cf55a1c5..94c885dfd 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -11,13 +11,6 @@ import ( "github.com/apex/log" ) -type mqttEvent struct { - AppID string - DevID string - Type string - Payload interface{} -} - // MQTTTimeout indicates how long we should wait for an MQTT publish var MQTTTimeout = 2 * time.Second @@ -33,8 +26,7 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e } h.mqttUp = make(chan *types.UplinkMessage, MQTTBufferSize) - h.mqttActivation = make(chan *types.Activation, MQTTBufferSize) - h.mqttEvent = make(chan *mqttEvent, MQTTBufferSize) + h.mqttEvent = make(chan *types.DeviceEvent, MQTTBufferSize) token := h.mqttClient.SubscribeDownlink(func(client mqtt.Client, appID string, devID string, msg types.DownlinkMessage) { down := &msg @@ -80,39 +72,18 @@ func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) e } }() - go func() { - for activation := range h.mqttActivation { - ctx.WithFields(log.Fields{ - "DevID": activation.DevID, - "AppID": activation.AppID, - "DevEUI": activation.DevEUI, - "AppEUI": activation.AppEUI, - "DevAddr": activation.DevAddr, - }).Debug("Publish Activation") - token := h.mqttClient.PublishActivation(*activation) - go func() { - if token.WaitTimeout(MQTTTimeout) { - if token.Error() != nil { - ctx.WithError(token.Error()).Warn("Could not publish Activation") - } - } else { - ctx.Warn("Activation publish timeout") - } - }() - } - }() - go func() { for event := range h.mqttEvent { h.Ctx.WithFields(log.Fields{ "DevID": event.DevID, "AppID": event.AppID, + "Event": event.Event, }).Debug("Publish Event") var token mqtt.Token if event.DevID == "" { - token = h.mqttClient.PublishAppEvent(event.AppID, event.Type, event.Payload) + token = h.mqttClient.PublishAppEvent(event.AppID, event.Event, event.Data) } else { - token = h.mqttClient.PublishDeviceEvent(event.AppID, event.DevID, event.Type, event.Payload) + token = h.mqttClient.PublishDeviceEvent(event.AppID, event.DevID, event.Event, event.Data) } go func() { if token.WaitTimeout(MQTTTimeout) { diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index e7ac02773..c4b903a61 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -76,9 +76,10 @@ func TestHandleMQTT(t *testing.T) { wg.Done() }).Wait() - h.mqttActivation <- &types.Activation{ + h.mqttEvent <- &types.DeviceEvent{ DevID: devID, AppID: appID, + Event: types.ActivationEvent, } a.So(wg.WaitFor(200*time.Millisecond), ShouldBeNil) diff --git a/core/handler/uplink.go b/core/handler/uplink.go index 145dcd7c4..ea427051e 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -23,11 +23,11 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err start := time.Now() defer func() { if err != nil { - h.mqttEvent <- &mqttEvent{ - AppID: appID, - DevID: devID, - Type: "up/errors", - Payload: map[string]string{"error": err.Error()}, + h.mqttEvent <- &types.DeviceEvent{ + AppID: appID, + DevID: devID, + Event: types.UplinkErrorEvent, + Data: types.ErrorEventData{Error: err.Error()}, } ctx.WithError(err).Warn("Could not handle uplink") } else { diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index 00ccabf15..5c8301144 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -48,7 +48,7 @@ func TestHandleUplink(t *testing.T) { h.applications.Delete(appID) }() h.mqttUp = make(chan *types.UplinkMessage) - h.mqttEvent = make(chan *mqttEvent, 10) + h.mqttEvent = make(chan *types.DeviceEvent, 10) h.downlink = make(chan *pb_broker.DownlinkMessage) uplink, _ := buildLorawanUplink([]byte{0x40, 0x04, 0x03, 0x02, 0x01, 0x00, 0x01, 0x00, 0x0A, 0x4D, 0xDA, 0x23, 0x99, 0x61, 0xD4}) diff --git a/core/types/event.go b/core/types/event.go new file mode 100644 index 000000000..696969809 --- /dev/null +++ b/core/types/event.go @@ -0,0 +1,56 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +// EventType represents the type of event +type EventType string + +// Event types +const ( + UplinkErrorEvent EventType = "up/errors" + DownlinkScheduledEvent EventType = "down/scheduled" + DownlinkSentEvent EventType = "down/sent" + DownlinkErrorEvent EventType = "down/errors" + DownlinkAckEvent EventType = "down/acks" + ActivationEvent EventType = "activations" + ActivationErrorEvent EventType = "activations/errors" +) + +// DeviceEvent represents an application-layer event message for a device event +type DeviceEvent struct { + AppID string + DevID string + Event EventType + Data interface{} +} + +// ErrorEventData is added to error events +type ErrorEventData struct { + Error string `json:"error"` +} + +// ActivationEventData is added to activation events +type ActivationEventData struct { + AppEUI AppEUI `json:"app_eui"` + DevEUI DevEUI `json:"dev_eui"` + DevAddr DevAddr `json:"dev_addr"` + Metadata Metadata `json:"metadata"` +} + +// DownlinkEventConfigInfo contains configuration information for a downlink message, all fields are optional +type DownlinkEventConfigInfo struct { + Modulation string `json:"modulation,omitempty"` + DataRate string `json:"data_rate,omitempty"` + BitRate uint `json:"bit_rate,omitempty"` + FCnt uint `json:"counter,omitempty"` + Frequency uint `json:"frequency,omitempty"` + Power int `json:"power,omitempty"` +} + +// DownlinkEventData is added to downlink events +type DownlinkEventData struct { + Payload []byte `json:"payload"` + GatewayID string `json:"gateway_id"` + Config DownlinkEventConfigInfo `json:"config"` +} diff --git a/mqtt/README.md b/mqtt/README.md index fc6a7f821..98ab1c468 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -203,9 +203,9 @@ if err := token.Error(); err != nil { ### Downlink Events -* Downlink Scheduled: `/devices//events/down/scheduled` (payload: the message - see **Downlink Messages**) -* Downlink Sent: `/devices//events/down/sent` (payload: the message - see **Downlink Messages**) -* Acknowledgements: `/devices//events/ack` (payload: `{}`) +* Downlink Scheduled: `/devices//events/down/scheduled` (payload: _null_) +* Downlink Sent: `/devices//events/down/sent` (payload: _null_) +* Acknowledgements: `/devices//events/ack` (payload: _null_) ### Error Events diff --git a/mqtt/activations.go b/mqtt/activations.go index 66031c4f9..92313b221 100644 --- a/mqtt/activations.go +++ b/mqtt/activations.go @@ -12,21 +12,18 @@ import ( // ActivationHandler is called for activations type ActivationHandler func(client Client, appID string, devID string, req types.Activation) -// ActivationEvent for MQTT -const ActivationEvent = "activations" - // PublishActivation publishes an activation func (c *DefaultClient) PublishActivation(activation types.Activation) Token { appID := activation.AppID devID := activation.DevID activation.AppID = "" activation.DevID = "" - return c.PublishDeviceEvent(appID, devID, ActivationEvent, activation) + return c.PublishDeviceEvent(appID, devID, types.ActivationEvent, activation) } // SubscribeDeviceActivations subscribes to all activations for the given application and device func (c *DefaultClient) SubscribeDeviceActivations(appID string, devID string, handler ActivationHandler) Token { - return c.SubscribeDeviceEvents(appID, devID, ActivationEvent, func(_ Client, appID string, devID string, _ string, payload []byte) { + return c.SubscribeDeviceEvents(appID, devID, types.ActivationEvent, func(_ Client, appID string, devID string, _ types.EventType, payload []byte) { activation := types.Activation{} if err := json.Unmarshal(payload, &activation); err != nil { c.ctx.Warnf("Could not unmarshal activation (%s).", err.Error()) @@ -51,15 +48,15 @@ func (c *DefaultClient) SubscribeActivations(handler ActivationHandler) Token { // UnsubscribeDeviceActivations unsubscribes from the activations for the given application and device func (c *DefaultClient) UnsubscribeDeviceActivations(appID string, devID string) Token { - return c.UnsubscribeDeviceEvents(appID, devID, ActivationEvent) + return c.UnsubscribeDeviceEvents(appID, devID, types.ActivationEvent) } // UnsubscribeAppActivations unsubscribes from the activations for the given application func (c *DefaultClient) UnsubscribeAppActivations(appID string) Token { - return c.UnsubscribeDeviceEvents(appID, "", ActivationEvent) + return c.UnsubscribeDeviceEvents(appID, "", types.ActivationEvent) } // UnsubscribeActivations unsubscribes from the activations that the current user has access to func (c *DefaultClient) UnsubscribeActivations() Token { - return c.UnsubscribeDeviceEvents("", "", ActivationEvent) + return c.UnsubscribeDeviceEvents("", "", types.ActivationEvent) } diff --git a/mqtt/client.go b/mqtt/client.go index 95b8b0ccf..9507a87bc 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -49,12 +49,12 @@ type Client interface { UnsubscribeDownlink() Token // Event pub/sub - PublishAppEvent(appID string, eventType string, payload interface{}) Token - PublishDeviceEvent(appID string, devID string, eventType string, payload interface{}) Token - SubscribeAppEvents(appID string, eventType string, handler AppEventHandler) Token - SubscribeDeviceEvents(appID string, devID string, eventType string, handler DeviceEventHandler) Token - UnsubscribeAppEvents(appID string, eventType string) Token - UnsubscribeDeviceEvents(appID string, devID string, eventType string) Token + PublishAppEvent(appID string, eventType types.EventType, payload interface{}) Token + PublishDeviceEvent(appID string, devID string, eventType types.EventType, payload interface{}) Token + SubscribeAppEvents(appID string, eventType types.EventType, handler AppEventHandler) Token + SubscribeDeviceEvents(appID string, devID string, eventType types.EventType, handler DeviceEventHandler) Token + UnsubscribeAppEvents(appID string, eventType types.EventType) Token + UnsubscribeDeviceEvents(appID string, devID string, eventType types.EventType) Token // Activation pub/sub PublishActivation(payload types.Activation) Token diff --git a/mqtt/events.go b/mqtt/events.go index 3b74c12cc..7303a89dd 100644 --- a/mqtt/events.go +++ b/mqtt/events.go @@ -7,19 +7,20 @@ import ( "encoding/json" "fmt" + "github.com/TheThingsNetwork/ttn/core/types" MQTT "github.com/eclipse/paho.mqtt.golang" ) // AppEventHandler is called for events -type AppEventHandler func(client Client, appID string, eventType string, payload []byte) +type AppEventHandler func(client Client, appID string, eventType types.EventType, payload []byte) // DeviceEventHandler is called for events -type DeviceEventHandler func(client Client, appID string, devID string, eventType string, payload []byte) +type DeviceEventHandler func(client Client, appID string, devID string, eventType types.EventType, payload []byte) // PublishAppEvent publishes an event to the topic for application events of the given type // it will marshal the payload to json -func (c *DefaultClient) PublishAppEvent(appID string, eventType string, payload interface{}) Token { - topic := ApplicationTopic{appID, AppEvents, eventType} +func (c *DefaultClient) PublishAppEvent(appID string, eventType types.EventType, payload interface{}) Token { + topic := ApplicationTopic{appID, AppEvents, string(eventType)} msg, err := json.Marshal(payload) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -29,8 +30,8 @@ func (c *DefaultClient) PublishAppEvent(appID string, eventType string, payload // PublishDeviceEvent publishes an event to the topic for device events of the given type // it will marshal the payload to json -func (c *DefaultClient) PublishDeviceEvent(appID string, devID string, eventType string, payload interface{}) Token { - topic := DeviceTopic{appID, devID, DeviceEvents, eventType} +func (c *DefaultClient) PublishDeviceEvent(appID string, devID string, eventType types.EventType, payload interface{}) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, string(eventType)} msg, err := json.Marshal(payload) if err != nil { return &simpleToken{fmt.Errorf("Unable to marshal the message payload")} @@ -40,41 +41,41 @@ func (c *DefaultClient) PublishDeviceEvent(appID string, devID string, eventType // SubscribeAppEvents subscribes to events of the given type for the given application. In order to subscribe to // application events from all applications the user has access to, pass an empty string as appID. -func (c *DefaultClient) SubscribeAppEvents(appID string, eventType string, handler AppEventHandler) Token { - topic := ApplicationTopic{appID, AppEvents, eventType} +func (c *DefaultClient) SubscribeAppEvents(appID string, eventType types.EventType, handler AppEventHandler) Token { + topic := ApplicationTopic{appID, AppEvents, string(eventType)} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { topic, err := ParseApplicationTopic(msg.Topic()) if err != nil { c.ctx.Warnf("Received message on invalid events topic: %s", msg.Topic()) return } - handler(c, topic.AppID, topic.Field, msg.Payload()) + handler(c, topic.AppID, types.EventType(topic.Field), msg.Payload()) }) } // SubscribeDeviceEvents subscribes to events of the given type for the given device. In order to subscribe to // events from all devices within an application, pass an empty string as devID. In order to subscribe to all // events from all devices in all applications the user has access to, pass an empty string as appID. -func (c *DefaultClient) SubscribeDeviceEvents(appID string, devID string, eventType string, handler DeviceEventHandler) Token { - topic := DeviceTopic{appID, devID, DeviceEvents, eventType} +func (c *DefaultClient) SubscribeDeviceEvents(appID string, devID string, eventType types.EventType, handler DeviceEventHandler) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, string(eventType)} return c.subscribe(topic.String(), func(mqtt MQTT.Client, msg MQTT.Message) { topic, err := ParseDeviceTopic(msg.Topic()) if err != nil { c.ctx.Warnf("Received message on invalid events topic: %s", msg.Topic()) return } - handler(c, topic.AppID, topic.DevID, topic.Field, msg.Payload()) + handler(c, topic.AppID, topic.DevID, types.EventType(topic.Field), msg.Payload()) }) } // UnsubscribeAppEvents unsubscribes from the events that were subscribed to by SubscribeAppEvents -func (c *DefaultClient) UnsubscribeAppEvents(appID string, eventType string) Token { - topic := ApplicationTopic{appID, AppEvents, eventType} +func (c *DefaultClient) UnsubscribeAppEvents(appID string, eventType types.EventType) Token { + topic := ApplicationTopic{appID, AppEvents, string(eventType)} return c.unsubscribe(topic.String()) } // UnsubscribeDeviceEvents unsubscribes from the events that were subscribed to by SubscribeDeviceEvents -func (c *DefaultClient) UnsubscribeDeviceEvents(appID string, devID string, eventType string) Token { - topic := DeviceTopic{appID, devID, DeviceEvents, eventType} +func (c *DefaultClient) UnsubscribeDeviceEvents(appID string, devID string, eventType types.EventType) Token { + topic := DeviceTopic{appID, devID, DeviceEvents, string(eventType)} return c.unsubscribe(topic.String()) } diff --git a/mqtt/events_test.go b/mqtt/events_test.go index 7e3509244..8aa301633 100644 --- a/mqtt/events_test.go +++ b/mqtt/events_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -19,7 +20,7 @@ func TestPublishSubscribeAppEvents(t *testing.T) { defer c.Disconnect() var wg WaitGroup wg.Add(1) - subToken := c.SubscribeAppEvents("app-id", "", func(_ Client, appID string, eventType string, payload []byte) { + subToken := c.SubscribeAppEvents("app-id", "", func(_ Client, appID string, eventType types.EventType, payload []byte) { a.So(appID, ShouldEqual, "app-id") a.So(eventType, ShouldEqual, "some-event") a.So(string(payload), ShouldEqual, `"payload"`) @@ -40,7 +41,7 @@ func TestPublishSubscribeDeviceEvents(t *testing.T) { defer c.Disconnect() var wg WaitGroup wg.Add(1) - subToken := c.SubscribeDeviceEvents("app-id", "dev-id", "", func(_ Client, appID string, devID string, eventType string, payload []byte) { + subToken := c.SubscribeDeviceEvents("app-id", "dev-id", "", func(_ Client, appID string, devID string, eventType types.EventType, payload []byte) { a.So(appID, ShouldEqual, "app-id") a.So(devID, ShouldEqual, "dev-id") a.So(eventType, ShouldEqual, "some-event") From fd494a43190c3e0cecf428db4b0b1e3be421edfb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 12:03:53 +0200 Subject: [PATCH 2039/2266] Send full payload and configuration with downlink sent event Resolves #318 --- core/handler/convert_lorawan.go | 2 +- core/handler/downlink.go | 19 +++++++++++++++++++ core/handler/downlink_test.go | 14 ++++++++------ mqtt/README.md | 30 ++++++++++++++++++++++++------ 4 files changed, 52 insertions(+), 13 deletions(-) diff --git a/core/handler/convert_lorawan.go b/core/handler/convert_lorawan.go index 645081639..100f31b62 100644 --- a/core/handler/convert_lorawan.go +++ b/core/handler/convert_lorawan.go @@ -93,7 +93,7 @@ func (h *handler) ConvertToLoRaWAN(ctx log.Interface, appDown *types.DownlinkMes if !ok { return errors.NewErrInvalidArgument("Downlink", "does not contain a MAC payload") } - if ttnDown.DownlinkOption != nil { + if ttnDown.DownlinkOption != nil && ttnDown.DownlinkOption.ProtocolConfig.GetLorawan() != nil { macPayload.FHDR.FCnt = ttnDown.DownlinkOption.ProtocolConfig.GetLorawan().FCnt } diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 815dc8c30..2d47b239a 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -98,11 +98,30 @@ func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *p h.downlink <- downlink + downlinkConfig := types.DownlinkEventConfigInfo{} + + if downlink.DownlinkOption.ProtocolConfig != nil { + if lorawan := downlink.DownlinkOption.ProtocolConfig.GetLorawan(); lorawan != nil { + downlinkConfig.Modulation = lorawan.Modulation.String() + downlinkConfig.DataRate = lorawan.DataRate + downlinkConfig.BitRate = uint(lorawan.BitRate) + downlinkConfig.FCnt = uint(lorawan.FCnt) + } + } + if gateway := downlink.DownlinkOption.GatewayConfig; gateway != nil { + downlinkConfig.Frequency = uint(downlink.DownlinkOption.GatewayConfig.Frequency) + downlinkConfig.Power = int(downlink.DownlinkOption.GatewayConfig.Power) + } h.mqttEvent <- &types.DeviceEvent{ AppID: appDownlink.AppID, DevID: appDownlink.DevID, Event: types.DownlinkSentEvent, + Data: types.DownlinkEventData{ + Payload: downlink.Payload, + GatewayID: downlink.DownlinkOption.GatewayId, + Config: downlinkConfig, + }, } return nil diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index f7dfc28a3..8a750410b 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -106,9 +106,10 @@ func TestHandleDownlink(t *testing.T) { DevID: devID, PayloadRaw: []byte{0xAA, 0xBC}, }, &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + DownlinkOption: &pb_broker.DownlinkOption{}, }) a.So(err, ShouldBeNil) wg.WaitFor(100 * time.Millisecond) @@ -150,9 +151,10 @@ func TestHandleDownlink(t *testing.T) { DevID: devID, PayloadFields: jsonFields, }, &pb_broker.DownlinkMessage{ - AppEui: &appEUI, - DevEui: &devEUI, - Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + AppEui: &appEUI, + DevEui: &devEUI, + Payload: []byte{96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0}, + DownlinkOption: &pb_broker.DownlinkOption{}, }) a.So(err, ShouldBeNil) wg.WaitFor(100 * time.Millisecond) diff --git a/mqtt/README.md b/mqtt/README.md index 98ab1c468..f96a7bd45 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -203,16 +203,34 @@ if err := token.Error(); err != nil { ### Downlink Events -* Downlink Scheduled: `/devices//events/down/scheduled` (payload: _null_) -* Downlink Sent: `/devices//events/down/sent` (payload: _null_) -* Acknowledgements: `/devices//events/ack` (payload: _null_) +**Downlink Scheduled:** `/devices//events/down/scheduled` +payload: _null_ + +**Downlink Sent:** `/devices//events/down/sent` + +```js +{ + "payload": "Base64 encoded LoRaWAN packet", + "gateway_id": "some-gateway", + "config": { + "modulation": "LORA", + "data_rate": "SF7BW125", + "counter": 123, + "frequency": 868300000, + "power": 14 + } +} +``` + +**Downlink Acknowledgements:** `/devices//events/down/acks` +payload: _null_ ### Error Events The payload of error events is a JSON object with the error's description. -* Uplink Errors: `/devices//events/up/errors` -* Downlink Errors: `/devices//events/down/errors` -* Activation Errors: `/devices//events/activations/errors` +**Uplink Errors:** `/devices//events/up/errors` +**Downlink Errors:** `/devices//events/down/errors` +**Activation Errors:** `/devices//events/activations/errors` Example: `{"error":"Activation DevNonce not valid: already used"}` From ccc169cb1032419f17eda64eb29ffcec3afd1583 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 24 Oct 2016 11:52:03 +0100 Subject: [PATCH 2040/2266] Use AMQP downlink queue name as variable --- core/handler/handler.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/core/handler/handler.go b/core/handler/handler.go index be7e88386..bd4801884 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -76,6 +76,11 @@ type handler struct { amqpUp chan *types.UplinkMessage } +var ( + // AMQPDownlinkQueue is the AMQP queue to use for downlink + AMQPDownlinkQueue = "ttn-handler-downlink" +) + func (h *handler) WithAMQP(username, password, host, exchange string) Handler { h.amqpUsername = username h.amqpPassword = password @@ -107,7 +112,7 @@ func (h *handler) Init(c *core.Component) error { } if h.amqpEnabled { - err = h.HandleAMQP(h.amqpUsername, h.amqpPassword, h.amqpHost, h.amqpExchange, "ttn-handler-downlink") + err = h.HandleAMQP(h.amqpUsername, h.amqpPassword, h.amqpHost, h.amqpExchange, AMQPDownlinkQueue) if err != nil { return err } From ea4f13fbad2e4d7240b7776a795bac7c132ace01 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 14:13:45 +0200 Subject: [PATCH 2041/2266] Allow makefile to be used with multiple paths in GOPATH --- Makefile | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 4876b4ddd..279023050 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,8 @@ SHELL = bash GIT_BRANCH = $(or $(CI_BUILD_REF_NAME) ,`git rev-parse --abbrev-ref HEAD 2>/dev/null`) GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) +GO_PATH = `echo $(GOPATH) | awk -F':' '{print $$1}'` +PARENT_DIRECTORY= `dirname $(PWD)` # All @@ -30,10 +32,13 @@ dev-deps: deps PROTO_FILES = $(shell find api -name "*.proto" -and -not -name ".git") COMPILED_PROTO_FILES = $(patsubst api%.proto, api%.pb.go, $(PROTO_FILES)) -PROTOC = protoc -I/usr/local/include -I$(GOPATH)/src -I$(GOPATH)/src/github.com/TheThingsNetwork \ --I$(GOPATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ ---gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GOPATH)/src \ ---grpc-gateway_out=:. $(GOPATH)/src/github.com/TheThingsNetwork/ttn/ +PROTOC = protoc \ +-I/usr/local/include \ +-I$(GO_PATH)/src \ +-I$(PARENT_DIRECTORY) \ +-I$(GO_PATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ +--gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GO_PATH)/src \ +--grpc-gateway_out=:. `pwd`/ protos-clean: rm -f $(COMPILED_PROTO_FILES) @@ -116,7 +121,7 @@ $(RELEASE_DIR)/ttnctl-%: $(GO_FILES) build: ttn ttnctl -GOBIN ?= $(GOPATH)/bin +GOBIN ?= $(GO_PATH)/bin link: build ln -sf $(PWD)/$(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) $(GOBIN)/ttn From a514201f232849a86b74863aee9fd2eb73137a8e Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 14:32:02 +0200 Subject: [PATCH 2042/2266] Fix current gopath for protc --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 279023050..8efa05e3a 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) GO_PATH = `echo $(GOPATH) | awk -F':' '{print $$1}'` PARENT_DIRECTORY= `dirname $(PWD)` +GO_SRC = `pwd | xargs dirname | xargs dirname | xargs dirname` # All @@ -37,7 +38,7 @@ PROTOC = protoc \ -I$(GO_PATH)/src \ -I$(PARENT_DIRECTORY) \ -I$(GO_PATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ ---gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GO_PATH)/src \ +--gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GO_SRC) \ --grpc-gateway_out=:. `pwd`/ protos-clean: From 9d72807af591608c1dc410312faada0e4e29a672 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 14:33:56 +0200 Subject: [PATCH 2043/2266] monitor client: refactor, use sync.Once --- api/monitor/client.go | 219 +++++++++++++++++++++++++----------------- 1 file changed, 133 insertions(+), 86 deletions(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index d3826c21a..32a689806 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -17,23 +17,23 @@ import ( // Client is a wrapper around MonitorClient type Client struct { - Log log.Interface + Ctx log.Interface client MonitorClient conn *grpc.ClientConn addr string - reopening chan struct{} + once sync.Once gateways map[string]GatewayClient - sync.RWMutex + mutex sync.RWMutex } // NewClient is a wrapper for NewMonitorClient, initializes // connection to MonitorServer on monitorAddr with default gRPC options func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { cl = &Client{ - Log: ctx, + Ctx: ctx, addr: monitorAddr, gateways: make(map[string]GatewayClient), } @@ -41,23 +41,30 @@ func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { } func (cl *Client) Open() (err error) { - cl.Lock() - defer cl.Unlock() + cl.mutex.Lock() + defer cl.mutex.Unlock() return cl.open() } func (cl *Client) open() (err error) { addr := cl.addr + ctx := cl.Ctx.WithField("addr", addr) + + defer func() { + if err != nil { + ctx.Warn("Failed to open monitor connection") + } else { + ctx.Info("Monitor connection opened") + } + }() - ctx := cl.Log.WithField("addr", addr) ctx.Debug("Opening monitor connection...") cl.conn, err = grpc.Dial(addr, append(api.DialOptions, grpc.WithInsecure())...) if err != nil { - ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection") + ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection to gRPC service") return err } - ctx.Debug("Connection established") cl.client = NewMonitorClient(cl.conn) return nil @@ -65,20 +72,31 @@ func (cl *Client) open() (err error) { // Close closes connection to the monitor func (cl *Client) Close() (err error) { - cl.Lock() - defer cl.Unlock() + cl.mutex.Lock() + defer cl.mutex.Unlock() return cl.close() } func (cl *Client) close() (err error) { - cl.Log.Debug("Closing monitor connection...") + defer func() { + if err != nil { + cl.Ctx.Warn("Failed to close monitor connection") + } else { + cl.Ctx.Info("Monitor connection closed") + } + }() + for _, gtw := range cl.gateways { + ctx := cl.Ctx.WithField("GatewayID", gtw.(*gatewayClient).id) + + ctx.Debug("Closing gateway streams...") err = gtw.Close() if err != nil { - cl.Log.WithError(err).WithField("GatewayID", gtw.(*gatewayClient).id).Warn("Failed to close streams") + ctx.Warn("Failed to close gateway streams") } } + cl.Ctx.Debug("Closing monitor connection...") err = cl.conn.Close() if err != nil { return err @@ -89,20 +107,22 @@ func (cl *Client) close() (err error) { } func (cl *Client) Reopen() (err error) { - cl.Lock() - defer cl.Unlock() + cl.mutex.Lock() + defer cl.mutex.Unlock() return cl.reopen() } func (cl *Client) reopen() (err error) { - cl.Log.Debug("Reopening monitor connection...") - - cl.reopening = make(chan struct{}) defer func() { - close(cl.reopening) - cl.reopening = nil + if err != nil { + cl.Ctx.Warn("Failed to reopen monitor connection") + } else { + cl.Ctx.Info("Monitor connection reopened") + } }() + cl.Ctx.Debug("Reopening monitor connection...") + err = cl.close() if err != nil { return err @@ -110,22 +130,18 @@ func (cl *Client) reopen() (err error) { return cl.open() } -func (cl *Client) IsReopening() bool { - return cl.reopening != nil -} - func (cl *Client) IsConnected() bool { return cl.client != nil && cl.conn != nil } func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { - cl.RLock() + cl.mutex.RLock() gtwCl, ok := cl.gateways[id] - cl.RUnlock() + cl.mutex.RUnlock() if !ok { - cl.Lock() + cl.mutex.Lock() gtwCl = &gatewayClient{ - Log: cl.Log.WithField("GatewayID", id), + Log: cl.Ctx.WithField("GatewayID", id), client: cl, @@ -133,7 +149,7 @@ func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { token: token, } cl.gateways[id] = gtwCl - cl.Unlock() + cl.mutex.Unlock() } return gtwCl } @@ -170,38 +186,44 @@ type GatewayClient interface { } func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { + cl.status.RLock() + cl.client.mutex.RLock() + + once := cl.client.once + stream := cl.status.stream + + cl.status.RUnlock() + cl.client.mutex.RUnlock() + defer func() { if err != nil { cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - cl.client.Lock() - defer cl.client.Unlock() + cl.client.mutex.Lock() + defer cl.client.mutex.Unlock() - if !cl.client.IsReopening() { - err = cl.client.reopen() - } + once.Do(func() { + err = cl.client.Reopen() + + cl.client.mutex.Lock() + cl.client.once = sync.Once{} + cl.client.mutex.Unlock() + }) } } else { cl.Log.Debug("Sent status to monitor") } }() - cl.status.RLock() - stream := cl.status.stream - cl.status.RUnlock() - if stream == nil { cl.status.Lock() if stream = cl.status.stream; stream == nil { stream, err = cl.setupStatus() if err != nil { cl.status.Unlock() - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") return err } - - cl.Log.Debug("Opened new monitor status stream") } cl.status.Unlock() } @@ -219,46 +241,42 @@ func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { } func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { + cl.uplink.RLock() + cl.client.mutex.RLock() + + once := cl.client.once + stream := cl.uplink.stream + + cl.uplink.RUnlock() + cl.client.mutex.RUnlock() + defer func() { if err != nil { cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - cl.Log.Debug("error is internal") - cl.Log.Debug("Locking...") - cl.client.Lock() - cl.Log.Debug("Locked...") - - cl.Log.Debug("Check if reopening...") - if !cl.client.IsReopening() { - cl.Log.Debug("Not reopening...") - err = cl.client.reopen() - } - cl.Log.Debug("Unlocking...") - cl.client.Unlock() - cl.Log.Debug("Unlocked...") - cl.Log.Debugf("return %s", err) + + once.Do(func() { + err = cl.client.Reopen() + + cl.client.mutex.Lock() + cl.client.once = sync.Once{} + cl.client.mutex.Unlock() + }) } } else { cl.Log.Debug("Sent uplink to monitor") } }() - cl.uplink.RLock() - stream := cl.uplink.stream - cl.uplink.RUnlock() - if stream == nil { cl.uplink.Lock() if stream = cl.uplink.stream; stream == nil { stream, err = cl.setupUplink() if err != nil { cl.uplink.Unlock() - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") return err } - - cl.Log.Debug("Opened new monitor uplink stream") } cl.uplink.Unlock() } @@ -276,38 +294,41 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { } func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { + cl.downlink.RLock() + cl.client.mutex.RLock() + + once := cl.client.once + stream := cl.downlink.stream + + cl.downlink.RUnlock() + cl.client.mutex.RUnlock() + defer func() { if err != nil { cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - cl.client.Lock() - defer cl.client.Unlock() + once.Do(func() { + err = cl.client.Reopen() - if !cl.client.IsReopening() { - err = cl.client.reopen() - } + cl.client.mutex.Lock() + cl.client.once = sync.Once{} + cl.client.mutex.Unlock() + }) } } else { cl.Log.Debug("Sent downlink to monitor") } }() - cl.downlink.RLock() - stream := cl.downlink.stream - cl.downlink.RUnlock() - if stream == nil { cl.downlink.Lock() if stream = cl.downlink.stream; stream == nil { stream, err = cl.setupDownlink() if err != nil { cl.downlink.Unlock() - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") return err } - - cl.Log.Debug("Opened new monitor downlink stream") } cl.downlink.Unlock() } @@ -331,13 +352,10 @@ func (cl *gatewayClient) Close() (err error) { go func() { defer wg.Done() - cl.Log.Debug("Status locking...") cl.status.Lock() - cl.Log.Debug("Status locked") if cl.status.stream != nil { - if cerr := cl.status.stream.CloseSend(); cerr != nil { - cl.Log.WithError(cerr).Warn("Failed to close status stream") + if cerr := cl.closeStatus(); cerr != nil { err = cerr } cl.status.stream = nil @@ -349,13 +367,10 @@ func (cl *gatewayClient) Close() (err error) { go func() { defer wg.Done() - cl.Log.Debug("Uplink locking...") cl.uplink.Lock() - cl.Log.Debug("Uplink locked") if cl.uplink.stream != nil { - if cerr := cl.uplink.stream.CloseSend(); cerr != nil { - cl.Log.WithError(cerr).Warn("Failed to close uplink stream") + if cerr := cl.closeUplink(); cerr != nil { err = cerr } cl.uplink.stream = nil @@ -370,8 +385,8 @@ func (cl *gatewayClient) Close() (err error) { cl.downlink.Lock() if cl.downlink.stream != nil { - if cerr := cl.downlink.stream.CloseSend(); cerr != nil { - cl.Log.WithError(cerr).Warn("Failed to close downlink stream") + cerr := cl.closeDownlink() + if cerr != nil { err = cerr } cl.downlink.stream = nil @@ -393,29 +408,61 @@ func (cl *gatewayClient) Context() (monitorContext context.Context) { func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err error) { stream, err = cl.client.client.GatewayStatus(cl.Context()) if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") return nil, err } + cl.Log.Debug("Opened new monitor status stream") cl.status.stream = stream return stream, nil } - func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err error) { stream, err = cl.client.client.GatewayUplink(cl.Context()) if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") return nil, err } + cl.Log.Debug("Opened new monitor uplink stream") cl.uplink.stream = stream return stream, nil } - func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, err error) { stream, err = cl.client.client.GatewayDownlink(cl.Context()) if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") return nil, err } + cl.Log.Debug("Opened new monitor downlink stream") cl.downlink.stream = stream return stream, nil } + +func (cl *gatewayClient) closeStatus() (err error) { + err = cl.status.stream.CloseSend() + if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to close status stream") + } + cl.Log.Debug("Closed status stream") + + return err +} +func (cl *gatewayClient) closeUplink() (err error) { + err = cl.uplink.stream.CloseSend() + if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to close uplink stream") + } + cl.Log.Debug("Closed uplink stream") + + return err +} +func (cl *gatewayClient) closeDownlink() (err error) { + err = cl.downlink.stream.CloseSend() + if err != nil { + cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to close downlink stream") + } + cl.Log.Debug("Closed downlink stream") + + return err +} From 70a6044e7fd09e679f90b482837d1402586df66d Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:12:42 +0200 Subject: [PATCH 2044/2266] monitor client: Log -> Ctx --- api/monitor/client.go | 46 +++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index 32a689806..f3f459c1b 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -141,7 +141,7 @@ func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { if !ok { cl.mutex.Lock() gtwCl = &gatewayClient{ - Log: cl.Ctx.WithField("GatewayID", id), + Ctx: cl.Ctx.WithField("GatewayID", id), client: cl, @@ -157,7 +157,7 @@ func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { type gatewayClient struct { client *Client - Log log.Interface + Ctx log.Interface id, token string @@ -197,7 +197,7 @@ func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { defer func() { if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { cl.client.mutex.Lock() @@ -212,7 +212,7 @@ func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { }) } } else { - cl.Log.Debug("Sent status to monitor") + cl.Ctx.Debug("Sent status to monitor") } }() @@ -229,7 +229,7 @@ func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { } if err = stream.Send(status); err == io.EOF { - cl.Log.Warn("Monitor status stream closed") + cl.Ctx.Warn("Monitor status stream closed") cl.status.Lock() if cl.status.stream == stream { cl.status.stream = nil @@ -252,7 +252,7 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { defer func() { if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { @@ -265,7 +265,7 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { }) } } else { - cl.Log.Debug("Sent uplink to monitor") + cl.Ctx.Debug("Sent uplink to monitor") } }() @@ -282,7 +282,7 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { } if err = stream.Send(uplink); err == io.EOF { - cl.Log.Warn("Monitor uplink stream closed") + cl.Ctx.Warn("Monitor uplink stream closed") cl.uplink.Lock() if cl.uplink.stream == stream { cl.uplink.stream = nil @@ -305,7 +305,7 @@ func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err err defer func() { if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { once.Do(func() { @@ -317,7 +317,7 @@ func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err err }) } } else { - cl.Log.Debug("Sent downlink to monitor") + cl.Ctx.Debug("Sent downlink to monitor") } }() @@ -334,7 +334,7 @@ func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err err } if err = stream.Send(downlink); err == io.EOF { - cl.Log.Warn("Monitor downlink stream closed") + cl.Ctx.Warn("Monitor downlink stream closed") cl.downlink.Lock() if cl.downlink.stream == stream { cl.downlink.stream = nil @@ -408,10 +408,10 @@ func (cl *gatewayClient) Context() (monitorContext context.Context) { func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err error) { stream, err = cl.client.client.GatewayStatus(cl.Context()) if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") return nil, err } - cl.Log.Debug("Opened new monitor status stream") + cl.Ctx.Debug("Opened new monitor status stream") cl.status.stream = stream return stream, nil @@ -419,10 +419,10 @@ func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err error) { stream, err = cl.client.client.GatewayUplink(cl.Context()) if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") return nil, err } - cl.Log.Debug("Opened new monitor uplink stream") + cl.Ctx.Debug("Opened new monitor uplink stream") cl.uplink.stream = stream return stream, nil @@ -430,10 +430,10 @@ func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, err error) { stream, err = cl.client.client.GatewayDownlink(cl.Context()) if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") return nil, err } - cl.Log.Debug("Opened new monitor downlink stream") + cl.Ctx.Debug("Opened new monitor downlink stream") cl.downlink.stream = stream return stream, nil @@ -442,27 +442,27 @@ func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, func (cl *gatewayClient) closeStatus() (err error) { err = cl.status.stream.CloseSend() if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to close status stream") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close status stream") } - cl.Log.Debug("Closed status stream") + cl.Ctx.Debug("Closed status stream") return err } func (cl *gatewayClient) closeUplink() (err error) { err = cl.uplink.stream.CloseSend() if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to close uplink stream") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close uplink stream") } - cl.Log.Debug("Closed uplink stream") + cl.Ctx.Debug("Closed uplink stream") return err } func (cl *gatewayClient) closeDownlink() (err error) { err = cl.downlink.stream.CloseSend() if err != nil { - cl.Log.WithError(errors.FromGRPCError(err)).Warn("Failed to close downlink stream") + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close downlink stream") } - cl.Log.Debug("Closed downlink stream") + cl.Ctx.Debug("Closed downlink stream") return err } From 99c3c421a904362f60fbc6f7062fab8ef64f223c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:19:47 +0200 Subject: [PATCH 2045/2266] monitor client: use pointer to sync.Once --- api/monitor/client.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index f3f459c1b..fc8129ff2 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -23,7 +23,7 @@ type Client struct { conn *grpc.ClientConn addr string - once sync.Once + once *sync.Once gateways map[string]GatewayClient mutex sync.RWMutex @@ -36,6 +36,8 @@ func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { Ctx: ctx, addr: monitorAddr, gateways: make(map[string]GatewayClient), + + once: &sync.Once{}, } return cl, cl.Open() } @@ -207,7 +209,7 @@ func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { err = cl.client.Reopen() cl.client.mutex.Lock() - cl.client.once = sync.Once{} + cl.client.once = &sync.Once{} cl.client.mutex.Unlock() }) } @@ -260,7 +262,7 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { err = cl.client.Reopen() cl.client.mutex.Lock() - cl.client.once = sync.Once{} + cl.client.once = &sync.Once{} cl.client.mutex.Unlock() }) } @@ -312,7 +314,7 @@ func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err err err = cl.client.Reopen() cl.client.mutex.Lock() - cl.client.once = sync.Once{} + cl.client.once = &sync.Once{} cl.client.mutex.Unlock() }) } From 7168418a904875b9a5f49348a50f6220ba5e651e Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:29:48 +0200 Subject: [PATCH 2046/2266] monitor client: pb_gateway -> gateway --- api/monitor/client.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index fc8129ff2..b0cf025ca 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -6,7 +6,7 @@ import ( "sync" "github.com/TheThingsNetwork/ttn/api" - pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" @@ -181,13 +181,13 @@ type gatewayClient struct { // GatewayClient is used as the main client for Gateways to communicate with the Router type GatewayClient interface { - SendStatus(status *pb_gateway.Status) (err error) + SendStatus(status *gateway.Status) (err error) SendUplink(msg *router.UplinkMessage) (err error) SendDownlink(msg *router.DownlinkMessage) (err error) Close() (err error) } -func (cl *gatewayClient) SendStatus(status *pb_gateway.Status) (err error) { +func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { cl.status.RLock() cl.client.mutex.RLock() From 4ef4062e09bcb53d2241501aed6f3d6031f42022 Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:37:03 +0200 Subject: [PATCH 2047/2266] monitor client: comments --- api/monitor/client.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index b0cf025ca..6b29007e1 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -42,6 +42,7 @@ func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { return cl, cl.Open() } +// Open opens connection to the monitor func (cl *Client) Open() (err error) { cl.mutex.Lock() defer cl.mutex.Unlock() @@ -108,6 +109,7 @@ func (cl *Client) close() (err error) { return nil } +// Reopen reopens connection to the monitor. It first attemts to close already opened connection and then opens a new one. If closing already opened connection fails, Reopen fails too. func (cl *Client) Reopen() (err error) { cl.mutex.Lock() defer cl.mutex.Unlock() @@ -132,10 +134,12 @@ func (cl *Client) reopen() (err error) { return cl.open() } +// IsConnected returns whether connection to the monitor had been established or not func (cl *Client) IsConnected() bool { return cl.client != nil && cl.conn != nil } +// GatewayClient returns monitor GatewayClient for id and token specified func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { cl.mutex.RLock() gtwCl, ok := cl.gateways[id] @@ -179,7 +183,7 @@ type gatewayClient struct { } } -// GatewayClient is used as the main client for Gateways to communicate with the Router +// GatewayClient is used as the main client for Gateways to communicate with the monitor type GatewayClient interface { SendStatus(status *gateway.Status) (err error) SendUplink(msg *router.UplinkMessage) (err error) @@ -187,6 +191,7 @@ type GatewayClient interface { Close() (err error) } +// SendStatus sends status to the monitor func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { cl.status.RLock() cl.client.mutex.RLock() @@ -242,6 +247,7 @@ func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { return err } +// SendUplink sends uplink to the monitor func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { cl.uplink.RLock() cl.client.mutex.RLock() @@ -295,6 +301,7 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { return err } +// SendUplink sends downlink to the monitor func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { cl.downlink.RLock() cl.client.mutex.RLock() @@ -347,6 +354,7 @@ func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err err return err } +// Close closes all opened monitor streams for the gateway func (cl *gatewayClient) Close() (err error) { wg := &sync.WaitGroup{} @@ -400,6 +408,7 @@ func (cl *gatewayClient) Close() (err error) { return err } +// Context returns monitor connection context for gateway func (cl *gatewayClient) Context() (monitorContext context.Context) { return metadata.NewContext(context.Background(), metadata.Pairs( "id", cl.id, From c526e5d1bd266d1eb0c70a1aaaab9a3935c558af Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:38:23 +0200 Subject: [PATCH 2048/2266] monitor client: fix deadlock --- api/monitor/client.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index 6b29007e1..175c6ebf3 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -207,9 +207,6 @@ func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - cl.client.mutex.Lock() - defer cl.client.mutex.Unlock() - once.Do(func() { err = cl.client.Reopen() From 14d48e6c05ea36294b4058483ae40c42623bab7a Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:39:55 +0200 Subject: [PATCH 2049/2266] monitor client: fix formatting --- api/monitor/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index 175c6ebf3..b2350c891 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -260,7 +260,6 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor") if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - once.Do(func() { err = cl.client.Reopen() From 42c6634485f295aece647486fea405cb4aa0a94c Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 15:42:15 +0200 Subject: [PATCH 2050/2266] monitor client: fix comment formatting --- api/monitor/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index b2350c891..170126265 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -109,7 +109,8 @@ func (cl *Client) close() (err error) { return nil } -// Reopen reopens connection to the monitor. It first attemts to close already opened connection and then opens a new one. If closing already opened connection fails, Reopen fails too. +// Reopen reopens connection to the monitor. It first attempts to close already opened connection +// and then opens a new one. If closing already opened connection fails, Reopen fails too. func (cl *Client) Reopen() (err error) { cl.mutex.Lock() defer cl.mutex.Unlock() From 84ca4acf0e46af0864a6eda01f431dc01245d6cf Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Mon, 24 Oct 2016 16:44:26 +0200 Subject: [PATCH 2051/2266] monitor client: context -> golang.org/x/net/context --- api/monitor/client.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index 170126265..797650e73 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -1,10 +1,11 @@ package monitor import ( - "context" "io" "sync" + "golang.org/x/net/context" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/router" From ceae997424dbf7a61998c0a505021d281544be6d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 16:39:10 +0200 Subject: [PATCH 2052/2266] Add ports to payload functions and dry payload functions --- api/handler/handler.pb.go | 179 ++++++++++++++++++---------- api/handler/handler.proto | 2 + core/handler/convert_fields.go | 36 +++--- core/handler/convert_fields_test.go | 77 ++++++------ core/handler/dry_run.go | 4 +- 5 files changed, 182 insertions(+), 116 deletions(-) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 9afbb0222..2216f560d 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -281,6 +281,7 @@ type DryDownlinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` App *Application `protobuf:"bytes,3,opt,name=app" json:"app,omitempty"` + Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` } func (m *DryDownlinkMessage) Reset() { *m = DryDownlinkMessage{} } @@ -298,6 +299,7 @@ func (m *DryDownlinkMessage) GetApp() *Application { type DryUplinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` App *Application `protobuf:"bytes,2,opt,name=app" json:"app,omitempty"` + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` } func (m *DryUplinkMessage) Reset() { *m = DryUplinkMessage{} } @@ -1235,6 +1237,11 @@ func (m *DryDownlinkMessage) MarshalTo(data []byte) (int, error) { } i += n10 } + if m.Port != 0 { + data[i] = 0x20 + i++ + i = encodeVarintHandler(data, i, uint64(m.Port)) + } return i, nil } @@ -1269,6 +1276,11 @@ func (m *DryUplinkMessage) MarshalTo(data []byte) (int, error) { } i += n11 } + if m.Port != 0 { + data[i] = 0x18 + i++ + i = encodeVarintHandler(data, i, uint64(m.Port)) + } return i, nil } @@ -1520,6 +1532,9 @@ func (m *DryDownlinkMessage) Size() (n int) { l = m.App.Size() n += 1 + l + sovHandler(uint64(l)) } + if m.Port != 0 { + n += 1 + sovHandler(uint64(m.Port)) + } return n } @@ -1534,6 +1549,9 @@ func (m *DryUplinkMessage) Size() (n int) { l = m.App.Size() n += 1 + l + sovHandler(uint64(l)) } + if m.Port != 0 { + n += 1 + sovHandler(uint64(m.Port)) + } return n } @@ -2743,6 +2761,25 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Port |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -2857,6 +2894,25 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Port", wireType) + } + m.Port = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Port |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -3199,67 +3255,64 @@ func init() { } var fileDescriptorHandler = []byte{ - // 987 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5f, 0x6f, 0x1b, 0x45, - 0x10, 0xef, 0x25, 0xc4, 0x49, 0xc6, 0xf9, 0xbb, 0x6e, 0xc2, 0xd5, 0x8e, 0x4c, 0x7a, 0x40, 0x15, - 0x28, 0xdc, 0x09, 0x53, 0xa9, 0x80, 0x84, 0x68, 0x5a, 0x93, 0x36, 0x82, 0x50, 0xe9, 0x12, 0x1e, - 0xe8, 0x03, 0xd1, 0xc6, 0x3b, 0xb1, 0x8f, 0x9c, 0x6f, 0x8f, 0xdb, 0xb5, 0x23, 0x83, 0x78, 0xe1, - 0x2b, 0xc0, 0x03, 0xdf, 0x81, 0xcf, 0x81, 0xc4, 0x23, 0x12, 0x8f, 0x3c, 0x80, 0x02, 0x1f, 0x04, - 0xdd, 0xee, 0xde, 0xf9, 0xe2, 0x3f, 0x0d, 0x41, 0x7d, 0xb2, 0x67, 0x7e, 0xbf, 0xfd, 0xcd, 0xce, - 0xce, 0xce, 0xec, 0xc1, 0xfb, 0xed, 0x40, 0x76, 0x7a, 0x27, 0x6e, 0x8b, 0x77, 0xbd, 0xa3, 0x0e, - 0x1e, 0x75, 0x82, 0xa8, 0x2d, 0x3e, 0x43, 0x79, 0xce, 0x93, 0x33, 0x4f, 0xca, 0xc8, 0xa3, 0x71, - 0xe0, 0x75, 0x68, 0xc4, 0x42, 0x4c, 0xb2, 0x5f, 0x37, 0x4e, 0xb8, 0xe4, 0x64, 0xde, 0x98, 0xd5, - 0x5a, 0x9b, 0xf3, 0x76, 0x88, 0x9e, 0x72, 0x9f, 0xf4, 0x4e, 0x3d, 0xec, 0xc6, 0x72, 0xa0, 0x59, - 0xd5, 0x2d, 0x03, 0xa6, 0x3a, 0x34, 0x8a, 0xb8, 0xa4, 0x32, 0xe0, 0x91, 0x30, 0xe8, 0x7a, 0x16, - 0x82, 0xc6, 0x81, 0x71, 0xd5, 0x32, 0xd7, 0x49, 0xc2, 0xcf, 0x30, 0x31, 0x3f, 0x06, 0x7c, 0x25, - 0x03, 0x95, 0xd9, 0xe2, 0x61, 0xfe, 0xc7, 0x10, 0x5e, 0x1f, 0x23, 0x84, 0x3c, 0xa1, 0xe7, 0x34, - 0xf2, 0x18, 0xf6, 0x83, 0x16, 0x6a, 0x9a, 0xf3, 0x87, 0x05, 0x76, 0x53, 0x39, 0x76, 0x5b, 0x32, - 0xe8, 0xab, 0x3d, 0xf9, 0x28, 0x62, 0x1e, 0x09, 0x24, 0x36, 0xcc, 0xc7, 0x74, 0x10, 0x72, 0xca, - 0x6c, 0x6b, 0xdb, 0xda, 0x59, 0xf2, 0x33, 0x93, 0x6c, 0x40, 0x89, 0xc6, 0xf1, 0x71, 0xc0, 0xec, - 0x99, 0x6d, 0x6b, 0x67, 0xd1, 0x9f, 0xa3, 0x71, 0xbc, 0xcf, 0xc8, 0x47, 0xb0, 0xca, 0xf8, 0x79, - 0x14, 0x06, 0xd1, 0xd9, 0x31, 0x8f, 0x53, 0x2d, 0xbb, 0xbc, 0x6d, 0xed, 0x94, 0x1b, 0x9b, 0xae, - 0xd9, 0x7d, 0xd3, 0xc0, 0x4f, 0x15, 0xea, 0xaf, 0xb0, 0x4b, 0x36, 0x39, 0x80, 0x0a, 0xcd, 0xf7, - 0x71, 0xdc, 0x45, 0x49, 0x19, 0x95, 0xd4, 0x7e, 0x59, 0x89, 0x6c, 0xb9, 0x79, 0x8e, 0xc3, 0xcd, - 0x1e, 0x18, 0x8e, 0x4f, 0xe8, 0x98, 0xcf, 0x59, 0x85, 0xe5, 0x43, 0x49, 0x65, 0x4f, 0xf8, 0xf8, - 0x75, 0x0f, 0x85, 0x74, 0xfe, 0xb4, 0xa0, 0xa4, 0x3d, 0x64, 0x07, 0x4a, 0x62, 0x20, 0x24, 0x76, - 0x55, 0x6e, 0xe5, 0xc6, 0x9a, 0x9b, 0x1e, 0xfd, 0xa1, 0x72, 0xa5, 0x14, 0xe1, 0x1b, 0x9c, 0xbc, - 0x03, 0x8b, 0x2d, 0xde, 0x8d, 0x79, 0x84, 0x91, 0x54, 0xf9, 0x96, 0x1b, 0x15, 0x45, 0x7e, 0x94, - 0x79, 0x35, 0x7f, 0xc8, 0x22, 0x0e, 0x94, 0x7a, 0x71, 0x9a, 0x97, 0xc9, 0x1f, 0x14, 0xdf, 0xa7, - 0x12, 0x85, 0x6f, 0x10, 0x72, 0x07, 0x16, 0xb2, 0xec, 0xed, 0xa5, 0x31, 0x56, 0x8e, 0x91, 0xb7, - 0xa0, 0x3c, 0x4c, 0x4d, 0xd8, 0xcb, 0x63, 0xd4, 0x22, 0xec, 0xb8, 0xb0, 0xb1, 0x1b, 0xc7, 0x61, - 0xd0, 0x52, 0xf6, 0x3e, 0xc3, 0x48, 0x06, 0xa7, 0x01, 0x26, 0x85, 0x92, 0x59, 0x85, 0x92, 0x39, - 0x3f, 0x5a, 0x50, 0x2e, 0x2c, 0x98, 0x42, 0x4b, 0xaf, 0x02, 0xc3, 0x16, 0x67, 0x98, 0x98, 0x8a, - 0x67, 0x26, 0xd9, 0x4a, 0x4f, 0x27, 0xea, 0x63, 0x22, 0x31, 0xb1, 0x67, 0x15, 0x36, 0x74, 0xa4, - 0x68, 0x9f, 0x86, 0x01, 0xa3, 0x92, 0x27, 0xf6, 0x4b, 0x1a, 0xcd, 0x1d, 0xa9, 0x2a, 0x46, 0x5a, - 0x75, 0x4e, 0xab, 0x1a, 0xd3, 0x79, 0x00, 0x6b, 0xfa, 0x5a, 0x5e, 0x99, 0x41, 0xea, 0x66, 0xd8, - 0x2f, 0xdc, 0x45, 0x86, 0xfd, 0x7d, 0xe6, 0x7c, 0x03, 0x25, 0xad, 0x70, 0xbd, 0x75, 0xe4, 0x3d, - 0x58, 0x31, 0x9d, 0x72, 0xac, 0x3b, 0x45, 0x25, 0x55, 0x6e, 0xac, 0xba, 0xc6, 0xed, 0x6a, 0xd9, - 0x27, 0x37, 0xfc, 0x65, 0xe3, 0xd1, 0x8e, 0x87, 0x0b, 0x4a, 0x30, 0x68, 0xa1, 0x73, 0x1f, 0x40, - 0xfb, 0x3e, 0x0d, 0x84, 0x24, 0x6f, 0xa4, 0x67, 0x97, 0x5a, 0xc2, 0xb6, 0xb6, 0x67, 0x95, 0x54, - 0x36, 0x40, 0x34, 0xcb, 0xcf, 0x70, 0x27, 0x02, 0xd2, 0x4c, 0x06, 0x59, 0x93, 0x1c, 0xa0, 0x10, - 0xb4, 0xfd, 0xbc, 0x3e, 0xdc, 0x84, 0xd2, 0x69, 0x80, 0x21, 0x13, 0x26, 0x07, 0x63, 0x91, 0x3b, - 0x30, 0x4b, 0xe3, 0xd8, 0xec, 0xfc, 0x66, 0x1e, 0xae, 0x50, 0x68, 0x3f, 0x25, 0x38, 0x47, 0xb0, - 0xd6, 0x4c, 0x06, 0x9f, 0xc7, 0xff, 0x2d, 0x9a, 0x51, 0x9d, 0xb9, 0x4a, 0xf5, 0x0b, 0x58, 0xcd, - 0x55, 0x7d, 0x14, 0xbd, 0x50, 0xfe, 0x8f, 0x14, 0x6e, 0xc2, 0x9c, 0xba, 0x28, 0x2a, 0x89, 0x05, - 0x5f, 0x1b, 0xce, 0xdb, 0xb0, 0x5e, 0x38, 0xa0, 0xab, 0xc4, 0x1b, 0xbf, 0x58, 0x30, 0xff, 0x44, - 0x6f, 0x93, 0x7c, 0x09, 0x95, 0xe1, 0xd8, 0x78, 0xd4, 0xa1, 0x61, 0x88, 0x51, 0x1b, 0x89, 0x93, - 0x8d, 0xa6, 0x09, 0xa0, 0x19, 0x1b, 0xd5, 0x57, 0x9f, 0xcb, 0x31, 0xd3, 0xf2, 0x19, 0x2c, 0x18, - 0x18, 0xc9, 0xdd, 0x7c, 0xde, 0x21, 0xeb, 0xe9, 0xd3, 0x41, 0x36, 0x3e, 0x67, 0xb5, 0xfa, 0xed, - 0x91, 0xeb, 0x30, 0x3e, 0x89, 0x1b, 0x3f, 0x97, 0x80, 0x14, 0x8e, 0xf9, 0x80, 0x46, 0xb4, 0x8d, - 0x09, 0xf9, 0x0a, 0x2a, 0x3e, 0xb6, 0x03, 0x21, 0x31, 0x29, 0xf6, 0x70, 0x7d, 0x52, 0x69, 0x86, - 0x8d, 0x54, 0xdd, 0x74, 0xf5, 0x5b, 0xe4, 0x66, 0x0f, 0x95, 0xfb, 0x71, 0xfa, 0x50, 0x39, 0xb5, - 0xef, 0x7f, 0xff, 0xe7, 0x87, 0x99, 0x0d, 0x67, 0xcd, 0xeb, 0x37, 0x3c, 0x3a, 0x5c, 0x2a, 0x3e, - 0xb0, 0xde, 0x24, 0x01, 0xac, 0x3c, 0x46, 0x79, 0x9d, 0x30, 0x13, 0x6f, 0x88, 0x73, 0x5b, 0x05, - 0xa9, 0x91, 0x5b, 0xa3, 0x41, 0xbc, 0x6f, 0x75, 0x97, 0x7e, 0x47, 0x18, 0xac, 0x1c, 0x5e, 0x0e, - 0x35, 0x51, 0x6a, 0x6a, 0x1e, 0xaf, 0xa9, 0x10, 0x75, 0x67, 0x7a, 0x88, 0x34, 0xa1, 0x4f, 0x60, - 0xbd, 0x89, 0x21, 0x4a, 0x7c, 0x01, 0x47, 0x47, 0xee, 0xc3, 0xe2, 0x63, 0x94, 0x66, 0xe0, 0xdc, - 0x1a, 0x29, 0x68, 0x61, 0xfd, 0x68, 0xeb, 0x93, 0x7b, 0xb0, 0x78, 0x98, 0x2f, 0x1c, 0x45, 0xa7, - 0x86, 0xdb, 0x85, 0x25, 0xbd, 0xf7, 0xab, 0x23, 0x4e, 0x93, 0x78, 0x0a, 0x76, 0xbe, 0x63, 0xb1, - 0xc7, 0xaf, 0x75, 0x81, 0x2a, 0x23, 0xe1, 0xd4, 0x98, 0xdb, 0x83, 0x72, 0xa1, 0x35, 0x49, 0x6d, - 0xc8, 0x19, 0x9b, 0x68, 0xd5, 0xea, 0x24, 0xd0, 0x74, 0xf3, 0x03, 0x58, 0xcc, 0xa7, 0x47, 0x31, - 0xb1, 0x91, 0x39, 0x55, 0xb5, 0xc7, 0x21, 0xad, 0xd0, 0xd8, 0x83, 0x15, 0xd3, 0xf4, 0x59, 0xa3, - 0xdc, 0x53, 0xe5, 0x31, 0x2f, 0xff, 0x66, 0xbe, 0xf0, 0xd2, 0xc7, 0x41, 0xa1, 0x36, 0xda, 0xff, - 0xf0, 0xc3, 0x5f, 0x2f, 0xea, 0xd6, 0x6f, 0x17, 0x75, 0xeb, 0xaf, 0x8b, 0xba, 0xf5, 0xd3, 0xdf, - 0xf5, 0x1b, 0xcf, 0xee, 0x5e, 0xe3, 0x2b, 0xf1, 0xa4, 0xa4, 0x4e, 0xfc, 0xdd, 0x7f, 0x03, 0x00, - 0x00, 0xff, 0xff, 0xf6, 0x20, 0x83, 0x71, 0x5b, 0x0a, 0x00, 0x00, + // 941 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x41, 0x6f, 0x1b, 0x45, + 0x14, 0xee, 0x36, 0x8d, 0x93, 0x3c, 0x27, 0x4e, 0x32, 0x69, 0xc3, 0xe2, 0x54, 0x26, 0x2c, 0xa2, + 0x0a, 0x2a, 0xac, 0x85, 0xa9, 0x54, 0x38, 0x00, 0x4d, 0x6b, 0xd2, 0x46, 0x10, 0x2a, 0x6d, 0xca, + 0x81, 0x1e, 0x88, 0x26, 0x9e, 0x17, 0x7b, 0x94, 0xf5, 0xce, 0xb0, 0x33, 0x76, 0x64, 0x8e, 0xfc, + 0x06, 0x0e, 0xfc, 0xa1, 0x4a, 0x1c, 0xb9, 0x73, 0x00, 0x85, 0x3f, 0x82, 0x76, 0x66, 0x76, 0xbd, + 0xb1, 0x9d, 0xa6, 0x41, 0x3d, 0x79, 0xdf, 0xfb, 0xbe, 0xf9, 0xe6, 0xbd, 0x79, 0xf3, 0xde, 0x18, + 0xbe, 0xe8, 0x72, 0xdd, 0x1b, 0x1c, 0x87, 0x1d, 0xd1, 0x6f, 0xbe, 0xe8, 0xe1, 0x8b, 0x1e, 0x4f, + 0xba, 0xea, 0x7b, 0xd4, 0x67, 0x22, 0x3d, 0x6d, 0x6a, 0x9d, 0x34, 0xa9, 0xe4, 0xcd, 0x1e, 0x4d, + 0x58, 0x8c, 0x69, 0xfe, 0x1b, 0xca, 0x54, 0x68, 0x41, 0x16, 0x9c, 0x59, 0xdf, 0xea, 0x0a, 0xd1, + 0x8d, 0xb1, 0x69, 0xdc, 0xc7, 0x83, 0x93, 0x26, 0xf6, 0xa5, 0x1e, 0x59, 0x56, 0x7d, 0x3d, 0x17, + 0xa1, 0x92, 0x3b, 0xd7, 0x56, 0xee, 0x3a, 0x4e, 0xc5, 0x29, 0xa6, 0xee, 0xc7, 0x81, 0xef, 0xe5, + 0xa0, 0x31, 0x3b, 0x22, 0x2e, 0x3e, 0x1c, 0xe1, 0xc3, 0x29, 0x42, 0x2c, 0x52, 0x7a, 0x46, 0x93, + 0x26, 0xc3, 0x21, 0xef, 0xa0, 0xa5, 0x05, 0x7f, 0x79, 0xe0, 0xb7, 0x8d, 0x63, 0xb7, 0xa3, 0xf9, + 0x90, 0x6a, 0x2e, 0x92, 0x08, 0x95, 0x14, 0x89, 0x42, 0xe2, 0xc3, 0x82, 0xa4, 0xa3, 0x58, 0x50, + 0xe6, 0x7b, 0xdb, 0xde, 0xce, 0x72, 0x94, 0x9b, 0xe4, 0x0e, 0x54, 0xa8, 0x94, 0x47, 0x9c, 0xf9, + 0x37, 0xb7, 0xbd, 0x9d, 0xa5, 0x68, 0x9e, 0x4a, 0xb9, 0xcf, 0xc8, 0xd7, 0xb0, 0xca, 0xc4, 0x59, + 0x12, 0xf3, 0xe4, 0xf4, 0x48, 0xc8, 0x4c, 0xcb, 0xaf, 0x6e, 0x7b, 0x3b, 0xd5, 0xd6, 0x66, 0xe8, + 0xa2, 0x6f, 0x3b, 0xf8, 0xb9, 0x41, 0xa3, 0x1a, 0xbb, 0x60, 0x93, 0x03, 0xd8, 0xa0, 0x45, 0x1c, + 0x47, 0x7d, 0xd4, 0x94, 0x51, 0x4d, 0xfd, 0x77, 0x8c, 0xc8, 0xdd, 0xb0, 0xc8, 0x71, 0x1c, 0xec, + 0x81, 0xe3, 0x44, 0x84, 0x4e, 0xf9, 0x82, 0x55, 0x58, 0x39, 0xd4, 0x54, 0x0f, 0x54, 0x84, 0x3f, + 0x0f, 0x50, 0xe9, 0xe0, 0x6f, 0x0f, 0x2a, 0xd6, 0x43, 0x76, 0xa0, 0xa2, 0x46, 0x4a, 0x63, 0xdf, + 0xe4, 0x56, 0x6d, 0xad, 0x85, 0xd9, 0xd1, 0x1f, 0x1a, 0x57, 0x46, 0x51, 0x91, 0xc3, 0xc9, 0xa7, + 0xb0, 0xd4, 0x11, 0x7d, 0x29, 0x12, 0x4c, 0xb4, 0xc9, 0xb7, 0xda, 0xda, 0x30, 0xe4, 0x27, 0xb9, + 0xd7, 0xf2, 0xc7, 0x2c, 0x12, 0x40, 0x65, 0x20, 0xb3, 0xbc, 0x5c, 0xfe, 0x60, 0xf8, 0x11, 0xd5, + 0xa8, 0x22, 0x87, 0x90, 0x7b, 0xb0, 0x98, 0x67, 0xef, 0x2f, 0x4f, 0xb1, 0x0a, 0x8c, 0x7c, 0x0c, + 0xd5, 0x71, 0x6a, 0xca, 0x5f, 0x99, 0xa2, 0x96, 0xe1, 0x20, 0x84, 0x3b, 0xbb, 0x52, 0xc6, 0xbc, + 0x63, 0xec, 0x7d, 0x86, 0x89, 0xe6, 0x27, 0x1c, 0xd3, 0x52, 0xc9, 0xbc, 0x52, 0xc9, 0x82, 0xdf, + 0x3c, 0xa8, 0x96, 0x16, 0x5c, 0x42, 0xcb, 0xae, 0x02, 0xc3, 0x8e, 0x60, 0x98, 0xba, 0x8a, 0xe7, + 0x26, 0xb9, 0x9b, 0x9d, 0x4e, 0x32, 0xc4, 0x54, 0x63, 0xea, 0xcf, 0x19, 0x6c, 0xec, 0xc8, 0xd0, + 0x21, 0x8d, 0x39, 0xa3, 0x5a, 0xa4, 0xfe, 0x2d, 0x8b, 0x16, 0x8e, 0x4c, 0x15, 0x13, 0xab, 0x3a, + 0x6f, 0x55, 0x9d, 0x19, 0x3c, 0x82, 0x35, 0x7b, 0x2d, 0xaf, 0xcc, 0x20, 0x73, 0x33, 0x1c, 0x96, + 0xee, 0x22, 0xc3, 0xe1, 0x3e, 0x0b, 0x7e, 0x81, 0x8a, 0x55, 0xb8, 0xde, 0x3a, 0xf2, 0x39, 0xd4, + 0x5c, 0xa7, 0x1c, 0xd9, 0x4e, 0x31, 0x49, 0x55, 0x5b, 0xab, 0xa1, 0x73, 0x87, 0x56, 0xf6, 0xd9, + 0x8d, 0x68, 0xc5, 0x79, 0xac, 0xe3, 0xf1, 0xa2, 0x11, 0xe4, 0x1d, 0x0c, 0x1e, 0x02, 0x58, 0xdf, + 0x77, 0x5c, 0x69, 0xf2, 0x51, 0x76, 0x76, 0x99, 0xa5, 0x7c, 0x6f, 0x7b, 0xce, 0x48, 0xe5, 0x23, + 0xc2, 0xb2, 0xa2, 0x1c, 0x0f, 0x7e, 0xf5, 0x80, 0xb4, 0xd3, 0x51, 0xde, 0x25, 0x07, 0xa8, 0x14, + 0xed, 0xbe, 0xae, 0x11, 0x37, 0xa1, 0x72, 0xc2, 0x31, 0x66, 0xca, 0x25, 0xe1, 0x2c, 0x72, 0x0f, + 0xe6, 0xa8, 0x94, 0x2e, 0xf4, 0xdb, 0xc5, 0x7e, 0xa5, 0x4a, 0x47, 0x19, 0x81, 0x10, 0xb8, 0x25, + 0x45, 0xaa, 0x4d, 0x69, 0x56, 0x22, 0xf3, 0x1d, 0xf4, 0x60, 0xad, 0x9d, 0x8e, 0x7e, 0x90, 0x6f, + 0x16, 0x81, 0xdb, 0xe9, 0xe6, 0x9b, 0xee, 0x34, 0x57, 0xda, 0xe9, 0x47, 0x58, 0x2d, 0x76, 0x8a, + 0x50, 0x0d, 0x62, 0xfd, 0x3f, 0x52, 0xbd, 0x0d, 0xf3, 0xe6, 0x46, 0x19, 0xe5, 0xc5, 0xc8, 0x1a, + 0xc1, 0x27, 0xb0, 0x5e, 0x3a, 0xc8, 0xab, 0xc4, 0x5b, 0xaf, 0x3c, 0x58, 0x78, 0x66, 0x43, 0x27, + 0x3f, 0xc1, 0xc6, 0x78, 0xbe, 0x3c, 0xe9, 0xd1, 0x38, 0xc6, 0xa4, 0x8b, 0x24, 0xc8, 0x67, 0xd8, + 0x0c, 0xd0, 0xcd, 0x97, 0xfa, 0x07, 0xaf, 0xe5, 0xb8, 0xb1, 0xfa, 0x12, 0x16, 0x1d, 0x8c, 0xe4, + 0x7e, 0x31, 0x18, 0x91, 0x0d, 0xec, 0x89, 0x21, 0x9b, 0x1e, 0xc8, 0x56, 0xfd, 0xfd, 0x89, 0x7b, + 0x33, 0x3d, 0xb2, 0x5b, 0xaf, 0xe6, 0x81, 0x94, 0x8e, 0xfe, 0x80, 0x26, 0xb4, 0x8b, 0x69, 0x36, + 0x57, 0x23, 0xec, 0x72, 0xa5, 0x31, 0x2d, 0x37, 0x7b, 0x63, 0x56, 0xb9, 0xc6, 0x1d, 0x57, 0xdf, + 0x0c, 0xed, 0x9b, 0x15, 0xe6, 0x6f, 0x56, 0xf8, 0x4d, 0xf6, 0x66, 0x91, 0x3d, 0xa8, 0x3d, 0x45, + 0x7d, 0x1d, 0xa5, 0x99, 0x17, 0x83, 0x7c, 0x05, 0xb5, 0xc3, 0x8b, 0x3a, 0x33, 0x79, 0x97, 0xc6, + 0xf1, 0x2d, 0xac, 0xb7, 0x31, 0x46, 0x8d, 0x6f, 0x23, 0xa9, 0x87, 0xb0, 0xf4, 0x14, 0xb5, 0x9b, + 0x19, 0xef, 0x4e, 0x1c, 0x75, 0x69, 0xfd, 0x64, 0xf7, 0x92, 0x07, 0xb0, 0x74, 0x58, 0x2c, 0x9c, + 0x44, 0x2f, 0xdd, 0x6e, 0x17, 0x96, 0x6d, 0xec, 0x57, 0xef, 0x78, 0x99, 0xc4, 0x73, 0xf0, 0x8b, + 0x88, 0xd5, 0x9e, 0xb8, 0x56, 0x69, 0x37, 0x26, 0xb6, 0x33, 0x93, 0x6a, 0x0f, 0xaa, 0xa5, 0xa6, + 0x21, 0x5b, 0x63, 0xce, 0xd4, 0x4c, 0xaa, 0xd7, 0x67, 0x81, 0xae, 0xcf, 0x1e, 0xc1, 0x52, 0xd1, + 0xd7, 0xe5, 0xc4, 0x26, 0xa6, 0x4a, 0xdd, 0x9f, 0x86, 0xac, 0x42, 0x6b, 0x0f, 0x6a, 0xae, 0x1d, + 0xf3, 0x2b, 0xfc, 0xc0, 0x94, 0xc7, 0x3d, 0xde, 0x9b, 0xc5, 0xc2, 0x0b, 0xef, 0x7b, 0xa9, 0x36, + 0xd6, 0xff, 0xf8, 0xcb, 0x3f, 0xce, 0x1b, 0xde, 0x9f, 0xe7, 0x0d, 0xef, 0x9f, 0xf3, 0x86, 0xf7, + 0xfb, 0xbf, 0x8d, 0x1b, 0x2f, 0xef, 0x5f, 0xe3, 0xaf, 0xdc, 0x71, 0xc5, 0x9c, 0xf8, 0x67, 0xff, + 0x05, 0x00, 0x00, 0xff, 0xff, 0xdc, 0xc4, 0xd3, 0xd4, 0x00, 0x0a, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index cb1d01017..8e557fef0 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -72,11 +72,13 @@ message DryDownlinkMessage { bytes payload = 1; string fields = 2; Application app = 3; + uint32 port = 4; } message DryUplinkMessage { bytes payload = 1; Application app = 2; + uint32 port = 3; } message DryUplinkResult { diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index d388c9ea6..25e6ddddc 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -29,7 +29,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat Validator: app.Validator, } - fields, valid, err := functions.Process(appUp.PayloadRaw) + fields, valid, err := functions.Process(appUp.PayloadRaw, appUp.FPort) if err != nil { return nil // Do not set fields if processing failed } @@ -60,14 +60,15 @@ type UplinkFunctions struct { var timeOut = 100 * time.Millisecond // Decode decodes the payload using the Decoder function into a map -func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) { +func (f *UplinkFunctions) Decode(payload []byte, port uint8) (map[string]interface{}, error) { if f.Decoder == "" { return nil, errors.NewErrInternal("Decoder function not set") } vm := otto.New() vm.Set("payload", payload) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload.slice(0))", f.Decoder), timeOut) + vm.Set("port", port) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload.slice(0), port)", f.Decoder), timeOut) if err != nil { return nil, err } @@ -87,14 +88,15 @@ func (f *UplinkFunctions) Decode(payload []byte) (map[string]interface{}, error) // Convert converts the values in the specified map to a another map using the // Converter function. If the Converter function is not set, this function // returns the data as-is -func (f *UplinkFunctions) Convert(data map[string]interface{}) (map[string]interface{}, error) { +func (f *UplinkFunctions) Convert(data map[string]interface{}, port uint8) (map[string]interface{}, error) { if f.Converter == "" { return data, nil } vm := otto.New() vm.Set("data", data) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Converter), timeOut) + vm.Set("port", port) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data, port)", f.Converter), timeOut) if err != nil { return nil, err } @@ -114,14 +116,15 @@ func (f *UplinkFunctions) Convert(data map[string]interface{}) (map[string]inter // Validate validates the values in the specified map using the Validator // function. If the Validator function is not set, this function returns true -func (f *UplinkFunctions) Validate(data map[string]interface{}) (bool, error) { +func (f *UplinkFunctions) Validate(data map[string]interface{}, port uint8) (bool, error) { if f.Validator == "" { return true, nil } vm := otto.New() vm.Set("data", data) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data)", f.Validator), timeOut) + vm.Set("port", port) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data, port)", f.Validator), timeOut) if err != nil { return false, err } @@ -134,18 +137,18 @@ func (f *UplinkFunctions) Validate(data map[string]interface{}) (bool, error) { } // Process decodes the specified payload, converts it and test the validity -func (f *UplinkFunctions) Process(payload []byte) (map[string]interface{}, bool, error) { - decoded, err := f.Decode(payload) +func (f *UplinkFunctions) Process(payload []byte, port uint8) (map[string]interface{}, bool, error) { + decoded, err := f.Decode(payload, port) if err != nil { return nil, false, err } - converted, err := f.Convert(decoded) + converted, err := f.Convert(decoded, port) if err != nil { return nil, false, err } - valid, err := f.Validate(converted) + valid, err := f.Validate(converted, port) return converted, valid, err } @@ -187,14 +190,15 @@ type DownlinkFunctions struct { // Encode encodes the map into a byte slice using the encoder payload function // If no encoder function is set, this function returns an array. -func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, error) { +func (f *DownlinkFunctions) Encode(payload map[string]interface{}, port uint8) ([]byte, error) { if f.Encoder == "" { return nil, errors.NewErrInternal("Encoder function not set") } vm := otto.New() vm.Set("payload", payload) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload)", f.Encoder), timeOut) + vm.Set("port", port) + value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload, port)", f.Encoder), timeOut) if err != nil { return nil, err } @@ -267,8 +271,8 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}) ([]byte, erro } // Process encode the specified field, converts it into a valid payload -func (f *DownlinkFunctions) Process(payload map[string]interface{}) ([]byte, bool, error) { - encoded, err := f.Encode(payload) +func (f *DownlinkFunctions) Process(payload map[string]interface{}, port uint8) ([]byte, bool, error) { + encoded, err := f.Encode(payload, port) if err != nil { return nil, false, err } @@ -295,7 +299,7 @@ func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *types.DownlinkMe Encoder: app.Encoder, } - message, _, err := functions.Process(appDown.PayloadFields) + message, _, err := functions.Process(appDown.PayloadFields, appDown.FPort) if err != nil { return err } diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index f1802cb30..29a5fd694 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -84,38 +84,45 @@ func TestDecode(t *testing.T) { a := New(t) functions := &UplinkFunctions{ - Decoder: `function(payload) { + Decoder: `function(payload, port) { return { - value: (payload[0] << 8) | payload[1] + value: (payload[0] << 8) | payload[1], + port: port, }; }`, } payload := []byte{0x48, 0x65} - m, err := functions.Decode(payload) + m, err := functions.Decode(payload, 12) a.So(err, ShouldBeNil) size, ok := m["value"] a.So(ok, ShouldBeTrue) a.So(size, ShouldEqual, 18533) + + port, ok := m["port"] + a.So(ok, ShouldBeTrue) + a.So(port, ShouldEqual, 12) } func TestConvert(t *testing.T) { a := New(t) withFunction := &UplinkFunctions{ - Converter: `function(data) { + Converter: `function(data, port) { return { celcius: data.temperature * 2 + port: port, }; }`, } - data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}) + data, err := withFunction.Convert(map[string]interface{}{"temperature": 11}, 33) a.So(err, ShouldBeNil) a.So(data["celcius"], ShouldEqual, 22) + a.So(data["port"], ShouldEqual, 33) withoutFunction := &UplinkFunctions{} - data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}) + data, err = withoutFunction.Convert(map[string]interface{}{"temperature": 11}, 33) a.So(err, ShouldBeNil) a.So(data["temperature"], ShouldEqual, 11) } @@ -128,15 +135,15 @@ func TestValidate(t *testing.T) { return data.temperature < 20; }`, } - valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}) + valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}, 0) a.So(err, ShouldBeNil) a.So(valid, ShouldBeTrue) - valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}) + valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}, 0) a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) withoutFunction := &UplinkFunctions{} - valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}) + valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}, 0) a.So(err, ShouldBeNil) a.So(valid, ShouldBeTrue) } @@ -160,7 +167,7 @@ func TestProcessUplink(t *testing.T) { }`, } - data, valid, err := functions.Process([]byte{40, 110}) + data, valid, err := functions.Process([]byte{40, 110}, 0) a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) a.So(data["temperature"], ShouldEqual, 20) @@ -174,21 +181,21 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { functions := &UplinkFunctions{ Decoder: ``, } - _, _, err := functions.Process([]byte{40, 110}) + _, _, err := functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Function functions = &UplinkFunctions{ Decoder: `this is not valid JavaScript`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid return functions = &UplinkFunctions{ Decoder: `function(payload) { return "Hello" }`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Function @@ -196,7 +203,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `this is not valid JavaScript`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Return @@ -204,7 +211,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `function(data) { return "Hello" }`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Function @@ -212,7 +219,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `this is not valid JavaScript`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Return @@ -220,7 +227,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `function(data) { return "Hello" }`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Object (Arrays are Objects too, but don't jive well with @@ -228,7 +235,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { functions = &UplinkFunctions{ Decoder: `function(payload) { return [1] }`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Object (Arrays are Objects too, but don't jive well with @@ -237,7 +244,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `function(payload) { return [1] }`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) // Invalid Object (Arrays are Objects too), this should work error because @@ -246,7 +253,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `function(payload) { return [1] }`, } - _, _, err = functions.Process([]byte{40, 110}) + _, _, err = functions.Process([]byte{40, 110}, 0) a.So(err, ShouldNotBeNil) } @@ -264,7 +271,7 @@ func TestTimeoutExceeded(t *testing.T) { interrupted := make(chan bool, 2) go func() { - _, _, err := functions.Process([]byte{0}) + _, _, err := functions.Process([]byte{0}, 0) a.So(time.Since(start), ShouldAlmostEqual, 100*time.Millisecond, 0.5e9) a.So(err, ShouldNotBeNil) interrupted <- true @@ -287,7 +294,7 @@ func TestEncode(t *testing.T) { // The payload is a JSON structure payload := map[string]interface{}{"temperature": 11} - m, err := functions.Encode(payload) + m, err := functions.Encode(payload, 0) a.So(err, ShouldBeNil) a.So(m, ShouldHaveLength, 7) @@ -295,9 +302,9 @@ func TestEncode(t *testing.T) { // Return int type functions = &DownlinkFunctions{ - Encoder: `function(payload) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, + Encoder: `function(payload, port) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldBeNil) } @@ -336,8 +343,8 @@ func TestConvertFieldsDown(t *testing.T) { h.applications.Set(&application.Application{ AppID: appID, // Encoder takes JSON fields as argument and return the payload as []byte - Encoder: `function test(payload){ - return [ 1, 2, 3, 4, 5, 6, 7 ] + Encoder: `function test(payload, port){ + return [ port, 1, 2, 3, 4, 5, 6, 7 ] }`, }) defer func() { @@ -347,7 +354,7 @@ func TestConvertFieldsDown(t *testing.T) { ttnDown, appDown = buildConversionDownlink() err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) a.So(err, ShouldBeNil) - a.So(appDown.PayloadRaw, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) + a.So(appDown.PayloadRaw, ShouldResemble, []byte{byte(appDown.FPort), 1, 2, 3, 4, 5, 6, 7}) } func TestProcessDownlinkInvalidFunction(t *testing.T) { @@ -357,35 +364,35 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { functions := &DownlinkFunctions{ Encoder: ``, } - _, _, err := functions.Process(map[string]interface{}{"key": 11}) + _, _, err := functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) // Invalid Function functions = &DownlinkFunctions{ Encoder: `this is not valid JavaScript`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ Encoder: `function(payload) { return "Hello" }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ Encoder: `function(payload) { return [ 100, 2256, 7 ] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ Encoder: `function(payload) { return [0, -1, "blablabla"] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) // Invalid return @@ -397,13 +404,13 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { } } }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) functions = &DownlinkFunctions{ Encoder: `function(payload) { return [ 1, 1.5 ] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldNotBeNil) } @@ -418,7 +425,7 @@ func TestEncodeCharCode(t *testing.T) { }); }`, } - val, _, err := functions.Process(map[string]interface{}{"key": 11}) + val, _, err := functions.Process(map[string]interface{}{"key": 11}, 0) a.So(err, ShouldBeNil) fmt.Println("VALUE", val) diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go index 7fb4c520c..2eb8d6efd 100644 --- a/core/handler/dry_run.go +++ b/core/handler/dry_run.go @@ -26,7 +26,7 @@ func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) Validator: app.Validator, } - fields, val, err := functions.Process(in.Payload) + fields, val, err := functions.Process(in.Payload, uint8(in.Port)) if err != nil { return nil, err } @@ -81,7 +81,7 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess return nil, errors.NewErrInvalidArgument("Fields", err.Error()) } - payload, _, err := functions.Process(parsed) + payload, _, err := functions.Process(parsed, uint8(in.Port)) if err != nil { return nil, err } From 99ab9d5f7115f5f53cbf61c55daa30ed1d7bc080 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 14:58:39 +0200 Subject: [PATCH 2053/2266] Use permission denied error if JWT token validation fails --- core/component.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/component.go b/core/component.go index 7fefd25dc..3ed23e24d 100644 --- a/core/component.go +++ b/core/component.go @@ -303,7 +303,7 @@ func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, claims, err := claims.FromToken(c.TokenKeyProvider, token[0]) if err != nil { - return nil, err + return nil, errors.NewErrPermissionDenied(err.Error()) } return claims, nil From a3b0c2cc2707e4333c9049aaf4fdd26a02cf0b5e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 15:41:41 +0200 Subject: [PATCH 2054/2266] Add gRPC proxy to Handler --- Makefile | 3 +- api/handler/handler.pb.go | 131 ++++---- api/handler/handler.pb.gw.go | 627 +++++++++++++++++++++++++++++++++++ api/handler/handler.proto | 56 +++- cmd/handler.go | 38 ++- core/handler/server.go | 4 +- core/proxy/proxy.go | 27 ++ core/proxy/proxy_test.go | 49 +++ vendor/vendor.json | 36 ++ 9 files changed, 891 insertions(+), 80 deletions(-) create mode 100644 api/handler/handler.pb.gw.go create mode 100644 core/proxy/proxy.go create mode 100644 core/proxy/proxy_test.go diff --git a/Makefile b/Makefile index 8efa05e3a..b3dc4009f 100644 --- a/Makefile +++ b/Makefile @@ -25,6 +25,7 @@ deps: build-deps dev-deps: deps @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast + @command -v protoc-gen-grpc-gateway > /dev/null || go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway @command -v mockgen > /dev/null || go get github.com/golang/mock/mockgen @command -v golint > /dev/null || go get github.com/golang/lint/golint @command -v forego > /dev/null || go get github.com/ddollar/forego @@ -39,7 +40,7 @@ PROTOC = protoc \ -I$(PARENT_DIRECTORY) \ -I$(GO_PATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GO_SRC) \ ---grpc-gateway_out=:. `pwd`/ +--grpc-gateway_out=:$(GO_SRC) `pwd`/ protos-clean: rm -f $(COMPILED_PROTO_FILES) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 9afbb0222..7bd6aaa02 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -28,6 +28,7 @@ import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" import api "github.com/TheThingsNetwork/ttn/api" import broker "github.com/TheThingsNetwork/ttn/api/broker" import protocol "github.com/TheThingsNetwork/ttn/api/protocol" @@ -3199,67 +3200,71 @@ func init() { } var fileDescriptorHandler = []byte{ - // 987 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5f, 0x6f, 0x1b, 0x45, - 0x10, 0xef, 0x25, 0xc4, 0x49, 0xc6, 0xf9, 0xbb, 0x6e, 0xc2, 0xd5, 0x8e, 0x4c, 0x7a, 0x40, 0x15, - 0x28, 0xdc, 0x09, 0x53, 0xa9, 0x80, 0x84, 0x68, 0x5a, 0x93, 0x36, 0x82, 0x50, 0xe9, 0x12, 0x1e, - 0xe8, 0x03, 0xd1, 0xc6, 0x3b, 0xb1, 0x8f, 0x9c, 0x6f, 0x8f, 0xdb, 0xb5, 0x23, 0x83, 0x78, 0xe1, - 0x2b, 0xc0, 0x03, 0xdf, 0x81, 0xcf, 0x81, 0xc4, 0x23, 0x12, 0x8f, 0x3c, 0x80, 0x02, 0x1f, 0x04, - 0xdd, 0xee, 0xde, 0xf9, 0xe2, 0x3f, 0x0d, 0x41, 0x7d, 0xb2, 0x67, 0x7e, 0xbf, 0xfd, 0xcd, 0xce, - 0xce, 0xce, 0xec, 0xc1, 0xfb, 0xed, 0x40, 0x76, 0x7a, 0x27, 0x6e, 0x8b, 0x77, 0xbd, 0xa3, 0x0e, - 0x1e, 0x75, 0x82, 0xa8, 0x2d, 0x3e, 0x43, 0x79, 0xce, 0x93, 0x33, 0x4f, 0xca, 0xc8, 0xa3, 0x71, - 0xe0, 0x75, 0x68, 0xc4, 0x42, 0x4c, 0xb2, 0x5f, 0x37, 0x4e, 0xb8, 0xe4, 0x64, 0xde, 0x98, 0xd5, - 0x5a, 0x9b, 0xf3, 0x76, 0x88, 0x9e, 0x72, 0x9f, 0xf4, 0x4e, 0x3d, 0xec, 0xc6, 0x72, 0xa0, 0x59, - 0xd5, 0x2d, 0x03, 0xa6, 0x3a, 0x34, 0x8a, 0xb8, 0xa4, 0x32, 0xe0, 0x91, 0x30, 0xe8, 0x7a, 0x16, - 0x82, 0xc6, 0x81, 0x71, 0xd5, 0x32, 0xd7, 0x49, 0xc2, 0xcf, 0x30, 0x31, 0x3f, 0x06, 0x7c, 0x25, - 0x03, 0x95, 0xd9, 0xe2, 0x61, 0xfe, 0xc7, 0x10, 0x5e, 0x1f, 0x23, 0x84, 0x3c, 0xa1, 0xe7, 0x34, - 0xf2, 0x18, 0xf6, 0x83, 0x16, 0x6a, 0x9a, 0xf3, 0x87, 0x05, 0x76, 0x53, 0x39, 0x76, 0x5b, 0x32, - 0xe8, 0xab, 0x3d, 0xf9, 0x28, 0x62, 0x1e, 0x09, 0x24, 0x36, 0xcc, 0xc7, 0x74, 0x10, 0x72, 0xca, - 0x6c, 0x6b, 0xdb, 0xda, 0x59, 0xf2, 0x33, 0x93, 0x6c, 0x40, 0x89, 0xc6, 0xf1, 0x71, 0xc0, 0xec, - 0x99, 0x6d, 0x6b, 0x67, 0xd1, 0x9f, 0xa3, 0x71, 0xbc, 0xcf, 0xc8, 0x47, 0xb0, 0xca, 0xf8, 0x79, - 0x14, 0x06, 0xd1, 0xd9, 0x31, 0x8f, 0x53, 0x2d, 0xbb, 0xbc, 0x6d, 0xed, 0x94, 0x1b, 0x9b, 0xae, - 0xd9, 0x7d, 0xd3, 0xc0, 0x4f, 0x15, 0xea, 0xaf, 0xb0, 0x4b, 0x36, 0x39, 0x80, 0x0a, 0xcd, 0xf7, - 0x71, 0xdc, 0x45, 0x49, 0x19, 0x95, 0xd4, 0x7e, 0x59, 0x89, 0x6c, 0xb9, 0x79, 0x8e, 0xc3, 0xcd, - 0x1e, 0x18, 0x8e, 0x4f, 0xe8, 0x98, 0xcf, 0x59, 0x85, 0xe5, 0x43, 0x49, 0x65, 0x4f, 0xf8, 0xf8, - 0x75, 0x0f, 0x85, 0x74, 0xfe, 0xb4, 0xa0, 0xa4, 0x3d, 0x64, 0x07, 0x4a, 0x62, 0x20, 0x24, 0x76, - 0x55, 0x6e, 0xe5, 0xc6, 0x9a, 0x9b, 0x1e, 0xfd, 0xa1, 0x72, 0xa5, 0x14, 0xe1, 0x1b, 0x9c, 0xbc, - 0x03, 0x8b, 0x2d, 0xde, 0x8d, 0x79, 0x84, 0x91, 0x54, 0xf9, 0x96, 0x1b, 0x15, 0x45, 0x7e, 0x94, - 0x79, 0x35, 0x7f, 0xc8, 0x22, 0x0e, 0x94, 0x7a, 0x71, 0x9a, 0x97, 0xc9, 0x1f, 0x14, 0xdf, 0xa7, - 0x12, 0x85, 0x6f, 0x10, 0x72, 0x07, 0x16, 0xb2, 0xec, 0xed, 0xa5, 0x31, 0x56, 0x8e, 0x91, 0xb7, - 0xa0, 0x3c, 0x4c, 0x4d, 0xd8, 0xcb, 0x63, 0xd4, 0x22, 0xec, 0xb8, 0xb0, 0xb1, 0x1b, 0xc7, 0x61, - 0xd0, 0x52, 0xf6, 0x3e, 0xc3, 0x48, 0x06, 0xa7, 0x01, 0x26, 0x85, 0x92, 0x59, 0x85, 0x92, 0x39, - 0x3f, 0x5a, 0x50, 0x2e, 0x2c, 0x98, 0x42, 0x4b, 0xaf, 0x02, 0xc3, 0x16, 0x67, 0x98, 0x98, 0x8a, - 0x67, 0x26, 0xd9, 0x4a, 0x4f, 0x27, 0xea, 0x63, 0x22, 0x31, 0xb1, 0x67, 0x15, 0x36, 0x74, 0xa4, - 0x68, 0x9f, 0x86, 0x01, 0xa3, 0x92, 0x27, 0xf6, 0x4b, 0x1a, 0xcd, 0x1d, 0xa9, 0x2a, 0x46, 0x5a, - 0x75, 0x4e, 0xab, 0x1a, 0xd3, 0x79, 0x00, 0x6b, 0xfa, 0x5a, 0x5e, 0x99, 0x41, 0xea, 0x66, 0xd8, - 0x2f, 0xdc, 0x45, 0x86, 0xfd, 0x7d, 0xe6, 0x7c, 0x03, 0x25, 0xad, 0x70, 0xbd, 0x75, 0xe4, 0x3d, - 0x58, 0x31, 0x9d, 0x72, 0xac, 0x3b, 0x45, 0x25, 0x55, 0x6e, 0xac, 0xba, 0xc6, 0xed, 0x6a, 0xd9, - 0x27, 0x37, 0xfc, 0x65, 0xe3, 0xd1, 0x8e, 0x87, 0x0b, 0x4a, 0x30, 0x68, 0xa1, 0x73, 0x1f, 0x40, - 0xfb, 0x3e, 0x0d, 0x84, 0x24, 0x6f, 0xa4, 0x67, 0x97, 0x5a, 0xc2, 0xb6, 0xb6, 0x67, 0x95, 0x54, - 0x36, 0x40, 0x34, 0xcb, 0xcf, 0x70, 0x27, 0x02, 0xd2, 0x4c, 0x06, 0x59, 0x93, 0x1c, 0xa0, 0x10, - 0xb4, 0xfd, 0xbc, 0x3e, 0xdc, 0x84, 0xd2, 0x69, 0x80, 0x21, 0x13, 0x26, 0x07, 0x63, 0x91, 0x3b, - 0x30, 0x4b, 0xe3, 0xd8, 0xec, 0xfc, 0x66, 0x1e, 0xae, 0x50, 0x68, 0x3f, 0x25, 0x38, 0x47, 0xb0, - 0xd6, 0x4c, 0x06, 0x9f, 0xc7, 0xff, 0x2d, 0x9a, 0x51, 0x9d, 0xb9, 0x4a, 0xf5, 0x0b, 0x58, 0xcd, - 0x55, 0x7d, 0x14, 0xbd, 0x50, 0xfe, 0x8f, 0x14, 0x6e, 0xc2, 0x9c, 0xba, 0x28, 0x2a, 0x89, 0x05, - 0x5f, 0x1b, 0xce, 0xdb, 0xb0, 0x5e, 0x38, 0xa0, 0xab, 0xc4, 0x1b, 0xbf, 0x58, 0x30, 0xff, 0x44, - 0x6f, 0x93, 0x7c, 0x09, 0x95, 0xe1, 0xd8, 0x78, 0xd4, 0xa1, 0x61, 0x88, 0x51, 0x1b, 0x89, 0x93, - 0x8d, 0xa6, 0x09, 0xa0, 0x19, 0x1b, 0xd5, 0x57, 0x9f, 0xcb, 0x31, 0xd3, 0xf2, 0x19, 0x2c, 0x18, - 0x18, 0xc9, 0xdd, 0x7c, 0xde, 0x21, 0xeb, 0xe9, 0xd3, 0x41, 0x36, 0x3e, 0x67, 0xb5, 0xfa, 0xed, - 0x91, 0xeb, 0x30, 0x3e, 0x89, 0x1b, 0x3f, 0x97, 0x80, 0x14, 0x8e, 0xf9, 0x80, 0x46, 0xb4, 0x8d, - 0x09, 0xf9, 0x0a, 0x2a, 0x3e, 0xb6, 0x03, 0x21, 0x31, 0x29, 0xf6, 0x70, 0x7d, 0x52, 0x69, 0x86, - 0x8d, 0x54, 0xdd, 0x74, 0xf5, 0x5b, 0xe4, 0x66, 0x0f, 0x95, 0xfb, 0x71, 0xfa, 0x50, 0x39, 0xb5, - 0xef, 0x7f, 0xff, 0xe7, 0x87, 0x99, 0x0d, 0x67, 0xcd, 0xeb, 0x37, 0x3c, 0x3a, 0x5c, 0x2a, 0x3e, - 0xb0, 0xde, 0x24, 0x01, 0xac, 0x3c, 0x46, 0x79, 0x9d, 0x30, 0x13, 0x6f, 0x88, 0x73, 0x5b, 0x05, - 0xa9, 0x91, 0x5b, 0xa3, 0x41, 0xbc, 0x6f, 0x75, 0x97, 0x7e, 0x47, 0x18, 0xac, 0x1c, 0x5e, 0x0e, - 0x35, 0x51, 0x6a, 0x6a, 0x1e, 0xaf, 0xa9, 0x10, 0x75, 0x67, 0x7a, 0x88, 0x34, 0xa1, 0x4f, 0x60, - 0xbd, 0x89, 0x21, 0x4a, 0x7c, 0x01, 0x47, 0x47, 0xee, 0xc3, 0xe2, 0x63, 0x94, 0x66, 0xe0, 0xdc, - 0x1a, 0x29, 0x68, 0x61, 0xfd, 0x68, 0xeb, 0x93, 0x7b, 0xb0, 0x78, 0x98, 0x2f, 0x1c, 0x45, 0xa7, - 0x86, 0xdb, 0x85, 0x25, 0xbd, 0xf7, 0xab, 0x23, 0x4e, 0x93, 0x78, 0x0a, 0x76, 0xbe, 0x63, 0xb1, - 0xc7, 0xaf, 0x75, 0x81, 0x2a, 0x23, 0xe1, 0xd4, 0x98, 0xdb, 0x83, 0x72, 0xa1, 0x35, 0x49, 0x6d, - 0xc8, 0x19, 0x9b, 0x68, 0xd5, 0xea, 0x24, 0xd0, 0x74, 0xf3, 0x03, 0x58, 0xcc, 0xa7, 0x47, 0x31, - 0xb1, 0x91, 0x39, 0x55, 0xb5, 0xc7, 0x21, 0xad, 0xd0, 0xd8, 0x83, 0x15, 0xd3, 0xf4, 0x59, 0xa3, - 0xdc, 0x53, 0xe5, 0x31, 0x2f, 0xff, 0x66, 0xbe, 0xf0, 0xd2, 0xc7, 0x41, 0xa1, 0x36, 0xda, 0xff, - 0xf0, 0xc3, 0x5f, 0x2f, 0xea, 0xd6, 0x6f, 0x17, 0x75, 0xeb, 0xaf, 0x8b, 0xba, 0xf5, 0xd3, 0xdf, - 0xf5, 0x1b, 0xcf, 0xee, 0x5e, 0xe3, 0x2b, 0xf1, 0xa4, 0xa4, 0x4e, 0xfc, 0xdd, 0x7f, 0x03, 0x00, - 0x00, 0xff, 0xff, 0xf6, 0x20, 0x83, 0x71, 0x5b, 0x0a, 0x00, 0x00, + // 1041 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5d, 0x6f, 0xe3, 0x44, + 0x17, 0x5e, 0xb7, 0x6f, 0xd3, 0xf6, 0xa4, 0x9f, 0xd3, 0xdd, 0xbe, 0x26, 0xad, 0x42, 0x77, 0x56, + 0x94, 0x6e, 0x17, 0x6c, 0x11, 0x90, 0x80, 0x95, 0x10, 0xfb, 0x51, 0xba, 0x5b, 0x89, 0x82, 0x70, + 0x8b, 0x10, 0xbd, 0xa0, 0x9a, 0xc6, 0xa7, 0x89, 0x55, 0xc7, 0x63, 0x3c, 0x93, 0x54, 0x61, 0xb5, + 0x37, 0xdc, 0x73, 0x05, 0x17, 0xfc, 0x02, 0xfe, 0x09, 0x12, 0x97, 0x48, 0x5c, 0x72, 0x01, 0x2a, + 0xfc, 0x10, 0xe4, 0x99, 0xb1, 0xe3, 0xe6, 0xa3, 0x69, 0x11, 0x57, 0xc9, 0x39, 0xcf, 0xf1, 0x73, + 0xe6, 0x39, 0x33, 0xf3, 0xd8, 0xf0, 0x7e, 0x23, 0x90, 0xcd, 0xf6, 0x89, 0x53, 0xe7, 0x2d, 0xf7, + 0xb0, 0x89, 0x87, 0xcd, 0x20, 0x6a, 0x88, 0x4f, 0x50, 0x9e, 0xf3, 0xe4, 0xcc, 0x95, 0x32, 0x72, + 0x59, 0x1c, 0xb8, 0x4d, 0x16, 0xf9, 0x21, 0x26, 0xd9, 0xaf, 0x13, 0x27, 0x5c, 0x72, 0x32, 0x6d, + 0xc2, 0xca, 0x5a, 0x83, 0xf3, 0x46, 0x88, 0xae, 0x4a, 0x9f, 0xb4, 0x4f, 0x5d, 0x6c, 0xc5, 0xb2, + 0xab, 0xab, 0x2a, 0xeb, 0x06, 0x4c, 0x79, 0x58, 0x14, 0x71, 0xc9, 0x64, 0xc0, 0x23, 0x61, 0xd0, + 0xe5, 0xac, 0x05, 0x8b, 0x03, 0x93, 0x5a, 0xcb, 0x52, 0x27, 0x09, 0x3f, 0xc3, 0xc4, 0xfc, 0x18, + 0xf0, 0xd5, 0x0c, 0x54, 0x61, 0x9d, 0x87, 0xf9, 0x1f, 0x53, 0xf0, 0xda, 0x40, 0x41, 0xc8, 0x13, + 0x76, 0xce, 0x22, 0xd7, 0xc7, 0x4e, 0x50, 0x47, 0x5d, 0x46, 0x7f, 0xb7, 0xc0, 0xde, 0x51, 0x89, + 0xc7, 0x75, 0x19, 0x74, 0xd4, 0x9a, 0x3c, 0x14, 0x31, 0x8f, 0x04, 0x12, 0x1b, 0xa6, 0x63, 0xd6, + 0x0d, 0x39, 0xf3, 0x6d, 0x6b, 0xc3, 0xda, 0x9a, 0xf3, 0xb2, 0x90, 0xdc, 0x81, 0x12, 0x8b, 0xe3, + 0xe3, 0xc0, 0xb7, 0x27, 0x36, 0xac, 0xad, 0x59, 0x6f, 0x8a, 0xc5, 0xf1, 0x9e, 0x4f, 0x3e, 0x84, + 0x45, 0x9f, 0x9f, 0x47, 0x61, 0x10, 0x9d, 0x1d, 0xf3, 0x38, 0xe5, 0xb2, 0xcb, 0x1b, 0xd6, 0x56, + 0xb9, 0xb6, 0xea, 0x98, 0xd5, 0xef, 0x18, 0xf8, 0x53, 0x85, 0x7a, 0x0b, 0xfe, 0xa5, 0x98, 0xec, + 0xc3, 0x0a, 0xcb, 0xd7, 0x71, 0xdc, 0x42, 0xc9, 0x7c, 0x26, 0x99, 0xfd, 0x7f, 0x45, 0xb2, 0xee, + 0xe4, 0x1a, 0x7b, 0x8b, 0xdd, 0x37, 0x35, 0x1e, 0x61, 0x03, 0x39, 0xba, 0x08, 0xf3, 0x07, 0x92, + 0xc9, 0xb6, 0xf0, 0xf0, 0xeb, 0x36, 0x0a, 0x49, 0xff, 0xb0, 0xa0, 0xa4, 0x33, 0x64, 0x0b, 0x4a, + 0xa2, 0x2b, 0x24, 0xb6, 0x94, 0xb6, 0x72, 0x6d, 0xc9, 0x49, 0x47, 0x7f, 0xa0, 0x52, 0x69, 0x89, + 0xf0, 0x0c, 0x4e, 0xde, 0x82, 0xd9, 0x3a, 0x6f, 0xc5, 0x3c, 0xc2, 0x48, 0x2a, 0xbd, 0xe5, 0xda, + 0x8a, 0x2a, 0x7e, 0x9a, 0x65, 0x75, 0x7d, 0xaf, 0x8a, 0x50, 0x28, 0xb5, 0xe3, 0x54, 0x97, 0xd1, + 0x0f, 0xaa, 0xde, 0x63, 0x12, 0x85, 0x67, 0x10, 0xb2, 0x09, 0x33, 0x99, 0x7a, 0x7b, 0x6e, 0xa0, + 0x2a, 0xc7, 0xc8, 0x1b, 0x50, 0xee, 0x49, 0x13, 0xf6, 0xfc, 0x40, 0x69, 0x11, 0xa6, 0x0e, 0xdc, + 0x79, 0x1c, 0xc7, 0x61, 0x50, 0x57, 0xf1, 0x9e, 0x8f, 0x91, 0x0c, 0x4e, 0x03, 0x4c, 0x0a, 0x5b, + 0x66, 0x15, 0xb6, 0x8c, 0xfe, 0x60, 0x41, 0xb9, 0xf0, 0xc0, 0x88, 0xb2, 0xf4, 0x28, 0xf8, 0x58, + 0xe7, 0x3e, 0x26, 0x66, 0xc7, 0xb3, 0x90, 0xac, 0xa7, 0xd3, 0x89, 0x3a, 0x98, 0x48, 0x4c, 0xec, + 0x49, 0x85, 0xf5, 0x12, 0x29, 0xda, 0x61, 0x61, 0xe0, 0x33, 0xc9, 0x13, 0xfb, 0x7f, 0x1a, 0xcd, + 0x13, 0x29, 0x2b, 0x46, 0x9a, 0x75, 0x4a, 0xb3, 0x9a, 0x90, 0x3e, 0x82, 0x25, 0x7d, 0x2c, 0xc7, + 0x2a, 0x48, 0xd3, 0x3e, 0x76, 0x0a, 0x67, 0xd1, 0xc7, 0xce, 0x9e, 0x4f, 0xbf, 0x81, 0x92, 0x66, + 0xb8, 0xd9, 0x73, 0xe4, 0x3d, 0x58, 0x30, 0x37, 0xe5, 0x58, 0xdf, 0x14, 0x25, 0xaa, 0x5c, 0x5b, + 0x74, 0x4c, 0xda, 0xd1, 0xb4, 0xcf, 0x6f, 0x79, 0xf3, 0x26, 0xa3, 0x13, 0x4f, 0x66, 0x14, 0x61, + 0x50, 0x47, 0xfa, 0x2e, 0x80, 0xce, 0x7d, 0x1c, 0x08, 0x49, 0xee, 0xa7, 0xb3, 0x4b, 0x23, 0x61, + 0x5b, 0x1b, 0x93, 0x8a, 0x2a, 0x33, 0x10, 0x5d, 0xe5, 0x65, 0x38, 0x8d, 0x80, 0xec, 0x24, 0xdd, + 0xec, 0x92, 0xec, 0xa3, 0x10, 0xac, 0x71, 0xd5, 0x3d, 0x5c, 0x85, 0xd2, 0x69, 0x80, 0xa1, 0x2f, + 0x8c, 0x06, 0x13, 0x91, 0x4d, 0x98, 0x64, 0x71, 0x6c, 0x56, 0x7e, 0x3b, 0x6f, 0x57, 0xd8, 0x68, + 0x2f, 0x2d, 0xa0, 0x87, 0xb0, 0xb4, 0x93, 0x74, 0x3f, 0x8f, 0xaf, 0xd7, 0xcd, 0xb0, 0x4e, 0x8c, + 0x63, 0xfd, 0x12, 0x16, 0x73, 0x56, 0x0f, 0x45, 0x3b, 0x94, 0xff, 0x42, 0xc2, 0x6d, 0x98, 0x52, + 0x07, 0x45, 0x89, 0x98, 0xf1, 0x74, 0x40, 0xdf, 0x84, 0xe5, 0xc2, 0x80, 0xc6, 0x91, 0xd7, 0x7e, + 0xb6, 0x60, 0xfa, 0xb9, 0x5e, 0x26, 0xf9, 0x0a, 0x56, 0x7a, 0xb6, 0xf1, 0xb4, 0xc9, 0xc2, 0x10, + 0xa3, 0x06, 0x12, 0x9a, 0x59, 0xd3, 0x10, 0xd0, 0xd8, 0x46, 0xe5, 0xde, 0x95, 0x35, 0xc6, 0x2d, + 0x8f, 0x60, 0xc6, 0xc0, 0x48, 0x1e, 0xe4, 0x7e, 0x87, 0x7e, 0x5b, 0x4f, 0x07, 0xfd, 0x41, 0x9f, + 0xd5, 0xec, 0x77, 0xfb, 0x8e, 0xc3, 0xa0, 0x13, 0xd7, 0x7e, 0x9a, 0x01, 0x52, 0x18, 0xf3, 0x3e, + 0x8b, 0x58, 0x03, 0x13, 0xd2, 0x80, 0x15, 0x0f, 0x1b, 0x81, 0x90, 0x98, 0x14, 0xef, 0x70, 0x75, + 0xd8, 0xd6, 0xf4, 0x2e, 0x52, 0x65, 0xd5, 0xd1, 0xef, 0x22, 0x27, 0x7b, 0x51, 0x39, 0x1f, 0xa5, + 0x2f, 0x2a, 0x6a, 0x7f, 0xfb, 0xdb, 0xdf, 0xdf, 0x4f, 0x10, 0x3a, 0xef, 0xb2, 0xde, 0x73, 0xe2, + 0xa1, 0xb5, 0x4d, 0x4e, 0x61, 0xe1, 0x19, 0xca, 0x9b, 0xf4, 0x18, 0x7a, 0x3c, 0x68, 0x55, 0x75, + 0xb0, 0xc9, 0xea, 0xa5, 0x0e, 0xee, 0x0b, 0x7d, 0x3f, 0x5f, 0x12, 0x06, 0x0b, 0x07, 0x97, 0xfb, + 0x0c, 0xe5, 0x19, 0xa9, 0xe0, 0xae, 0xe2, 0x5f, 0xa3, 0x23, 0xf8, 0x53, 0x29, 0x67, 0xb0, 0xbc, + 0x83, 0x21, 0x4a, 0xfc, 0x2f, 0x26, 0x66, 0xf4, 0x6c, 0x8f, 0xd2, 0xd3, 0x84, 0xd9, 0x67, 0x28, + 0x8d, 0x0f, 0xbd, 0xd2, 0xb7, 0xcf, 0x05, 0xfe, 0x7e, 0x47, 0xa0, 0xae, 0x22, 0xbe, 0x4f, 0x5e, + 0x1f, 0x4e, 0x6c, 0x5e, 0xe2, 0xc2, 0x7d, 0xa1, 0x2d, 0xec, 0x25, 0xf9, 0xce, 0x82, 0xd9, 0x83, + 0xbc, 0x55, 0x3f, 0xdf, 0x48, 0x01, 0x5f, 0xa8, 0x3e, 0x9f, 0xd1, 0xeb, 0xf6, 0x79, 0x68, 0x6d, + 0x1f, 0xdd, 0xa3, 0xd5, 0xab, 0xab, 0xd3, 0x31, 0x27, 0x30, 0xa7, 0xc7, 0x3c, 0x5e, 0xfc, 0xa8, + 0xb5, 0x99, 0x19, 0x6c, 0x5f, 0x7b, 0x06, 0xe7, 0x60, 0xe7, 0xd3, 0x16, 0xbb, 0xfc, 0x46, 0x77, + 0x62, 0xa5, 0x6f, 0x7d, 0xa9, 0x73, 0xd3, 0x4d, 0xb5, 0x82, 0x0d, 0x32, 0x46, 0x2f, 0xd9, 0x85, + 0x72, 0xc1, 0x95, 0xc8, 0x5a, 0x8f, 0x6b, 0xc0, 0xcc, 0x2b, 0x95, 0x61, 0xa0, 0x31, 0xb2, 0x47, + 0x30, 0x9b, 0x1b, 0x67, 0x71, 0x62, 0x7d, 0x16, 0x5d, 0xb1, 0x07, 0x21, 0xcd, 0x50, 0xdb, 0x85, + 0x05, 0xe3, 0x77, 0x99, 0x47, 0xbc, 0xa3, 0x8e, 0xa0, 0xf9, 0xe8, 0x59, 0xcd, 0x1f, 0xbc, 0xf4, + 0x5d, 0x54, 0x38, 0x7f, 0x3a, 0xff, 0xe4, 0x83, 0x5f, 0x2e, 0xaa, 0xd6, 0xaf, 0x17, 0x55, 0xeb, + 0xcf, 0x8b, 0xaa, 0xf5, 0xe3, 0x5f, 0xd5, 0x5b, 0x47, 0x0f, 0x6e, 0xf0, 0x81, 0x7c, 0x52, 0x52, + 0x5b, 0xf9, 0xf6, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x47, 0x68, 0x2a, 0xd3, 0x56, 0x0b, 0x00, + 0x00, } diff --git a/api/handler/handler.pb.gw.go b/api/handler/handler.pb.gw.go new file mode 100644 index 000000000..737df7257 --- /dev/null +++ b/api/handler/handler.pb.gw.go @@ -0,0 +1,627 @@ +// Code generated by protoc-gen-grpc-gateway +// source: github.com/TheThingsNetwork/ttn/api/handler/handler.proto +// DO NOT EDIT! + +/* +Package handler is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package handler + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" +) + +var _ codes.Code +var _ io.Reader +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_ApplicationManager_RegisterApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.RegisterApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_GetApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Application + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_DeleteApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.DeleteApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_GetDevice_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeviceIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_1(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_DeleteDevice_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq DeviceIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.DeleteDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_GetDevicesForApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq ApplicationIdentifier + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetDevicesForApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterApplicationManagerHandlerFromEndpoint is same as RegisterApplicationManagerHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterApplicationManagerHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterApplicationManagerHandler(ctx, mux, conn) +} + +// RegisterApplicationManagerHandler registers the http handlers for service ApplicationManager to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterApplicationManagerHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewApplicationManagerClient(conn) + + mux.Handle("POST", pattern_ApplicationManager_RegisterApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_RegisterApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_RegisterApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_ApplicationManager_GetApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_GetApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_GetApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_ApplicationManager_SetApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_ApplicationManager_DeleteApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_DeleteApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_DeleteApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_ApplicationManager_GetDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_GetDevice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_GetDevice_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_ApplicationManager_SetDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("POST", pattern_ApplicationManager_SetDevice_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_1(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_1(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("DELETE", pattern_ApplicationManager_DeleteDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_DeleteDevice_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_DeleteDevice_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_ApplicationManager_GetDevicesForApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_GetDevicesForApplication_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_GetDevicesForApplication_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_ApplicationManager_RegisterApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0}, []string{"applications"}, "")) + + pattern_ApplicationManager_GetApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_SetApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_DeleteApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + + pattern_ApplicationManager_GetDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_SetDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_SetDevice_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) + + pattern_ApplicationManager_DeleteDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_GetDevicesForApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) +) + +var ( + forward_ApplicationManager_RegisterApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_GetApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_DeleteApplication_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_GetDevice_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_1 = runtime.ForwardResponseMessage + + forward_ApplicationManager_DeleteDevice_0 = runtime.ForwardResponseMessage + + forward_ApplicationManager_GetDevicesForApplication_0 = runtime.ForwardResponseMessage +) diff --git a/api/handler/handler.proto b/api/handler/handler.proto index cb1d01017..16e66cbbc 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -4,6 +4,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; import "ttn/api/api.proto"; import "ttn/api/broker/broker.proto"; import "ttn/api/protocol/protocol.proto"; @@ -90,14 +91,53 @@ message DryDownlinkResult { } service ApplicationManager { - rpc RegisterApplication(ApplicationIdentifier) returns (google.protobuf.Empty); - rpc GetApplication(ApplicationIdentifier) returns (Application); - rpc SetApplication(Application) returns (google.protobuf.Empty); - rpc DeleteApplication(ApplicationIdentifier) returns (google.protobuf.Empty); - rpc GetDevice(DeviceIdentifier) returns (Device); - rpc SetDevice(Device) returns (google.protobuf.Empty); - rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty); - rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList); + rpc RegisterApplication(ApplicationIdentifier) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/applications" + body: "*" + }; + } + rpc GetApplication(ApplicationIdentifier) returns (Application) { + option (google.api.http) = { + get: "/applications/{app_id}" + }; + } + rpc SetApplication(Application) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/applications/{app_id}" + body: "*" + }; + } + rpc DeleteApplication(ApplicationIdentifier) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/applications/{app_id}" + }; + } + rpc GetDevice(DeviceIdentifier) returns (Device) { + option (google.api.http) = { + get: "/applications/{app_id}/devices/{dev_id}" + }; + } + rpc SetDevice(Device) returns (google.protobuf.Empty) { + option (google.api.http) = { + post: "/applications/{app_id}/devices/{dev_id}" + body: "*" + additional_bindings { + post: "/applications/{app_id}/devices" + body: "*" + } + }; + } + rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty) { + option (google.api.http) = { + delete: "/applications/{app_id}/devices/{dev_id}" + }; + } + rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList) { + option (google.api.http) = { + get: "/applications/{app_id}/devices" + }; + } rpc DryDownlink(DryDownlinkMessage) returns (DryDownlinkResult); rpc DryUplink(DryUplinkMessage) returns (DryUplinkResult); } diff --git a/cmd/handler.go b/cmd/handler.go index 2fb65192a..d9cf7f10e 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -6,18 +6,22 @@ package cmd import ( "fmt" "net" + "net/http" "os" "os/signal" "syscall" - "google.golang.org/grpc" - "gopkg.in/redis.v4" - + pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/handler" + "github.com/TheThingsNetwork/ttn/core/proxy" "github.com/apex/log" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" "github.com/spf13/viper" + "golang.org/x/net/context" + "google.golang.org/grpc" + "gopkg.in/redis.v4" ) // handlerCmd represents the handler command @@ -28,6 +32,7 @@ var handlerCmd = &cobra.Command{ PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ "Server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + "HTTP Proxy": fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), "Announce": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port")), "Database": fmt.Sprintf("%s/%d", viper.GetString("handler.redis-address"), viper.GetInt("handler.redis-db")), "TTNBroker": viper.GetString("handler.ttn-broker"), @@ -72,6 +77,7 @@ var handlerCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Could not initialize handler") } + defer handler.Shutdown() // gRPC Server lis, err := net.Listen("tcp", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port"))) @@ -84,14 +90,29 @@ var handlerCmd = &cobra.Command{ handler.RegisterRPC(grpc) handler.RegisterManager(grpc) go grpc.Serve(lis) + defer grpc.Stop() + + if viper.GetString("handler.http-address") != "" && viper.GetInt("handler.http-port") != 0 { + proxyConn, err := component.Identity.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not start client for gRPC proxy") + } + mux := runtime.NewServeMux() + netCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + pb.RegisterApplicationManagerHandler(netCtx, mux, proxyConn) + go func() { + err := http.ListenAndServe(fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), proxy.WithToken(mux)) + if err != nil { + ctx.WithError(err).Fatal("Error in gRPC proxy") + } + }() + } sigChan := make(chan os.Signal) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) ctx.WithField("signal", <-sigChan).Info("signal received") - grpc.Stop() - handler.Shutdown() - }, } @@ -133,4 +154,9 @@ func init() { viper.BindPFlag("handler.server-address", handlerCmd.Flags().Lookup("server-address")) viper.BindPFlag("handler.server-address-announce", handlerCmd.Flags().Lookup("server-address-announce")) viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) + + handlerCmd.Flags().String("http-address", "0.0.0.0", "The IP address where the gRPC proxy should listen") + handlerCmd.Flags().Int("http-port", 0, "The port where the gRPC proxy should listen") + viper.BindPFlag("handler.http-address", handlerCmd.Flags().Lookup("http-address")) + viper.BindPFlag("handler.http-port", handlerCmd.Flags().Lookup("http-port")) } diff --git a/core/handler/server.go b/core/handler/server.go index 21d37c455..08b952ad5 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -49,7 +49,7 @@ func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.Dedupli } // RegisterRPC registers this handler as a HandlerServer (github.com/TheThingsNetwork/ttn/api/handler) -func (r *handler) RegisterRPC(s *grpc.Server) { - server := &handlerRPC{r} +func (h *handler) RegisterRPC(s *grpc.Server) { + server := &handlerRPC{h} pb.RegisterHandlerServer(s, server) } diff --git a/core/proxy/proxy.go b/core/proxy/proxy.go new file mode 100644 index 000000000..01a23a17c --- /dev/null +++ b/core/proxy/proxy.go @@ -0,0 +1,27 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package proxy + +import ( + "net/http" + "strings" +) + +type tokenProxier struct { + handler http.Handler +} + +func (p *tokenProxier) ServeHTTP(res http.ResponseWriter, req *http.Request) { + if authorization := req.Header.Get("authorization"); authorization != "" { + if len(authorization) > 6 && strings.ToLower(authorization[0:7]) == "bearer " { + req.Header.Set("Grpc-Metadata-Token", authorization[7:]) + } + } + p.handler.ServeHTTP(res, req) +} + +// WithToken wraps the handler so that each request gets the Bearer token attached +func WithToken(handler http.Handler) http.Handler { + return &tokenProxier{handler} +} diff --git a/core/proxy/proxy_test.go b/core/proxy/proxy_test.go new file mode 100644 index 000000000..37998512f --- /dev/null +++ b/core/proxy/proxy_test.go @@ -0,0 +1,49 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package proxy + +import ( + "bytes" + "net/http" + "net/http/httptest" + "testing" + + . "github.com/smartystreets/assertions" +) + +type testHandler struct { + req *http.Request + res http.ResponseWriter +} + +func (h *testHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) { + h.res = res + h.req = req +} + +func TestTokenProxier(t *testing.T) { + a := New(t) + + hdl := &testHandler{} + p := WithToken(hdl) + + req := httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldBeEmpty) + + req = httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + req.Header.Add("Authorization", "Key blabla") + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldBeEmpty) + + req = httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + req.Header.Add("Authorization", "bearer token") + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldEqual, "token") + + req = httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + req.Header.Add("Authorization", "Bearer token") + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldEqual, "token") +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 5ac9a698a..8f3fab8a3 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -182,12 +182,24 @@ "revision": "bd3c8e81be01eef76d4b503f5e687d2d1354d2d9", "revisionTime": "2016-01-21T18:51:14Z" }, + { + "checksumSHA1": "+HPilCYNEcR4B/Q13LiW3OF3i64=", + "path": "github.com/golang/protobuf/jsonpb", + "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", + "revisionTime": "2016-10-12T20:53:35Z" + }, { "checksumSHA1": "SVXOQdpDBh0ihdZ5aIflgdA+Rpw=", "path": "github.com/golang/protobuf/proto", "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", "revisionTime": "2016-10-12T20:53:35Z" }, + { + "checksumSHA1": "juNiTc9bfhQYo4BkWc83sW4Z5gw=", + "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", + "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", + "revisionTime": "2016-10-12T20:53:35Z" + }, { "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", @@ -212,6 +224,30 @@ "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, + { + "checksumSHA1": "+1AwpnM6pIDftpw+iE1/hj/BOXo=", + "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", + "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", + "revisionTime": "2016-10-21T05:32:21Z" + }, + { + "checksumSHA1": "GN9lH15GbtcX7xx70TjRYXi23Uk=", + "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", + "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", + "revisionTime": "2016-10-21T05:32:21Z" + }, + { + "checksumSHA1": "D+k9iBM97p4RvkjpogAJevV8hmw=", + "path": "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api", + "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", + "revisionTime": "2016-10-21T05:32:21Z" + }, + { + "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", + "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", + "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", + "revisionTime": "2016-10-21T05:32:21Z" + }, { "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", "path": "github.com/hashicorp/hcl", From ab98ca710fbe67e35c53471df3e9f5876209aa72 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 16:29:08 +0200 Subject: [PATCH 2055/2266] Add logging to gRPC proxy --- cmd/handler.go | 9 ++++++++- core/proxy/proxy.go | 21 +++++++++++++++++++++ core/proxy/proxy_test.go | 13 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/cmd/handler.go b/cmd/handler.go index d9cf7f10e..d537d2cd5 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -101,8 +101,15 @@ var handlerCmd = &cobra.Command{ netCtx, cancel := context.WithCancel(context.Background()) defer cancel() pb.RegisterApplicationManagerHandler(netCtx, mux, proxyConn) + + prxy := proxy.WithToken(mux) + prxy = proxy.WithLogger(prxy, ctx) + go func() { - err := http.ListenAndServe(fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), proxy.WithToken(mux)) + err := http.ListenAndServe( + fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), + prxy, + ) if err != nil { ctx.WithError(err).Fatal("Error in gRPC proxy") } diff --git a/core/proxy/proxy.go b/core/proxy/proxy.go index 01a23a17c..dc037fdbc 100644 --- a/core/proxy/proxy.go +++ b/core/proxy/proxy.go @@ -6,6 +6,8 @@ package proxy import ( "net/http" "strings" + + "github.com/apex/log" ) type tokenProxier struct { @@ -25,3 +27,22 @@ func (p *tokenProxier) ServeHTTP(res http.ResponseWriter, req *http.Request) { func WithToken(handler http.Handler) http.Handler { return &tokenProxier{handler} } + +type logProxier struct { + ctx log.Interface + handler http.Handler +} + +func (p *logProxier) ServeHTTP(res http.ResponseWriter, req *http.Request) { + p.ctx.WithFields(log.Fields{ + "RemoteAddress": req.RemoteAddr, + "Method": req.Method, + "URI": req.RequestURI, + }).Info("Proxy HTTP request") + p.handler.ServeHTTP(res, req) +} + +// WithLogger wraps the handler so that each request gets logged +func WithLogger(handler http.Handler, ctx log.Interface) http.Handler { + return &logProxier{ctx, handler} +} diff --git a/core/proxy/proxy_test.go b/core/proxy/proxy_test.go index 37998512f..c74eb2517 100644 --- a/core/proxy/proxy_test.go +++ b/core/proxy/proxy_test.go @@ -9,6 +9,7 @@ import ( "net/http/httptest" "testing" + . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) @@ -47,3 +48,15 @@ func TestTokenProxier(t *testing.T) { p.ServeHTTP(httptest.NewRecorder(), req) a.So(hdl.req.Header.Get("Grpc-Metadata-Token"), ShouldEqual, "token") } + +func TestLogProxier(t *testing.T) { + a := New(t) + + hdl := &testHandler{} + p := WithLogger(hdl, GetLogger(t, "")) + + req := httptest.NewRequest("GET", "/uri", bytes.NewBuffer([]byte{})) + p.ServeHTTP(httptest.NewRecorder(), req) + a.So(hdl.req, ShouldNotBeNil) + a.So(hdl.res, ShouldNotBeNil) +} From b5e81fcd56ef380108fd24319af286063a63455b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 16:29:33 +0200 Subject: [PATCH 2056/2266] Make description Application Already exists more explicit --- core/handler/manager_server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index e9b40f8c1..72481b66e 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -270,7 +270,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica return nil, errors.BuildGRPCError(err) } if app != nil { - return nil, grpcErrf(codes.AlreadyExists, "Application") + return nil, grpcErrf(codes.AlreadyExists, "Application already exists") } err = h.handler.applications.Set(&application.Application{ From 8eb673e0957d0a9320bcb4e71d769cd5e64cb0c3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 17:22:09 +0200 Subject: [PATCH 2057/2266] Add Handler API docs --- api/handler/HTTP-API.md | 228 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 228 insertions(+) create mode 100644 api/handler/HTTP-API.md diff --git a/api/handler/HTTP-API.md b/api/handler/HTTP-API.md new file mode 100644 index 000000000..3ca3efcd6 --- /dev/null +++ b/api/handler/HTTP-API.md @@ -0,0 +1,228 @@ +# HTTP API Reference + +The Handler HTTP API is a wrapper around the Handler's gRPC interface. We recommend everyone to use the gRPC interface if possible. + +## Authorization + +Authorization to the Handler HTTP API is done with the `Authorization` header in your requests. +This header should contain a `Bearer` token with the JSON Web Token issued by the account server. + +Example: + +``` +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl +``` + +## Register Application + +Request: + +``` +POST /applications + +{ + "app_id": "the-app-id" +} +``` + +Response: + +``` +200 OK + +{} +``` + +## Get Application + +Request: + +``` +GET /applications/the-app-id +``` + +Response: + +``` +200 OK + +{ + "app_id": "the-app-id", + "decoder": "function Decoder(...) { ... }" + "converter": "function Converter(...) { ... }" + "validator": "function Validator(...) { ... }" + "encoder": "function Encoder(...) { ... }" +} +``` + +## Get Devices For Application + +Request: + +``` +GET /applications/the-app-id/devices +``` + +Response: + +``` +200 OK + +{ + "devices": [ + { + "app_id": "the-app-id", + "dev_id": "the-dev-id", + "lorawan_device": { + "app_eui": "70B3D57EF0000001", + "dev_eui": "70B3D57EF0000001", + "app_id": "the-app-id", + "dev_id": "the-dev-id", + "dev_addr": "", + "nwk_s_key": "", + "app_s_key": "", + "app_key": "01020304050607080102030405060708" + } + }, + ... + ] +} +``` + +## Set Application (after it has been registered) + +Request: + +``` +POST /applications/the-app-id +{ + "decoder": "function Decoder(...) { ... }" + "converter": "function Converter(...) { ... }" + "validator": "function Validator(...) { ... }" + "encoder": "function Encoder(...) { ... }" +} +``` + +Response: + +``` +200 OK + +{} +``` + +## Delete Application + +Request: + +``` +DELETE /applications/the-app-id +``` + +Response: + +``` +200 OK + +{} +``` + +## Get Device + +``` +GET /applications/the-app-id/devices/the-dev-id +``` + +Response: + +``` +200 OK + +{ + "app_id": "the-app-id", + "dev_id": "the-dev-id", + "lorawan_device": { + "app_eui": "70B3D57EF0000001", + "dev_eui": "70B3D57EF0000001", + "app_id": "the-app-id", + "dev_id": "the-dev-id", + "dev_addr": "", + "nwk_s_key": "", + "app_s_key": "", + "app_key": "01020304050607080102030405060708" + } +} +``` + +## Set Device + +``` +POST /applications/the-app-id/devices/the-dev-id + +{ + "lorawan_device": { + "app_eui": "70B3D57EF0000001", + "dev_eui": "70B3D57EF0000001", + "dev_addr": "26000001", + "nwk_s_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "app_key": "" + } +} +``` + +Response: + +``` +200 OK + +{} +``` + +## Delete Device + +Request: + +``` +DELETE /applications/the-app-id/devices/the-dev-id +``` + +Response: + +``` +200 OK + +{} +``` + +## Types + +### Application + +``` +app_id string +decoder string +converter string +validator string +encoder string +``` + +### Device + +``` +app_id string +dev_id string + +lorawan_device: + app_eui string + dev_eui string + dev_addr string + nwk_s_key string + app_s_key string + app_key string + f_cnt_up int + f_cnt_down int + disable_f_cnt_check bool + uses32_bit_f_cnt bool + last_seen int (unix-nanoseconds) +``` From 4b4218292e2d695ba5f48343b2dd8fc0556bc4cd Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 20:52:52 +0200 Subject: [PATCH 2058/2266] Add encode w/o port --- core/handler/convert_fields_test.go | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 29a5fd694..423ee0eae 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -357,6 +357,38 @@ func TestConvertFieldsDown(t *testing.T) { a.So(appDown.PayloadRaw, ShouldResemble, []byte{byte(appDown.FPort), 1, 2, 3, 4, 5, 6, 7}) } +func TestConvertFieldsDownNoPort(t *testing.T) { + a := New(t) + appID := "AppID-1" + + h := &handler{ + applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-convert-fields-down"), + } + + // Case1: No Encoder + ttnDown, appDown := buildConversionDownlink() + err := h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(appDown.PayloadRaw, ShouldBeEmpty) + + // Case2: Normal flow with Encoder + h.applications.Set(&application.Application{ + AppID: appID, + // Encoder takes JSON fields as argument and return the payload as []byte + Encoder: `function test(payload){ + return [ 1, 2, 3, 4, 5, 6, 7 ] + }`, + }) + defer func() { + h.applications.Delete(appID) + }() + + ttnDown, appDown = buildConversionDownlink() + err = h.ConvertFieldsDown(GetLogger(t, "TestConvertFieldsDown"), appDown, ttnDown) + a.So(err, ShouldBeNil) + a.So(appDown.PayloadRaw, ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7}) +} + func TestProcessDownlinkInvalidFunction(t *testing.T) { a := New(t) From 8f7a12288e8c5ad1764c5baa940d4c54169307af Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 24 Oct 2016 22:31:31 +0200 Subject: [PATCH 2059/2266] Fix AMQP addr for Handler test --- core/handler/amqp_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go index 9d61bc25d..e066745d9 100644 --- a/core/handler/amqp_test.go +++ b/core/handler/amqp_test.go @@ -17,7 +17,7 @@ import ( ) func TestHandleAMQP(t *testing.T) { - host := os.Getenv("AMQP_HOST") + host := os.Getenv("AMQP_ADDR") if host == "" { host = "localhost:5672" } From bc77653de440f63dfdc5b4a719c8ab2a873dd09b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 25 Oct 2016 08:30:54 +0200 Subject: [PATCH 2060/2266] Use same rabbitmq image in gitlab as in travis --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1c94d16e5..31520c037 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,12 +30,12 @@ tests: stage: test image: golang:latest services: - - rabbitmq + - robertobarreda/rabbitmq:mqtt - redis variables: REDIS_HOST: redis - MQTT_HOST: rabbitmq - AMQP_ADDR: rabbitmq:5672 + MQTT_HOST: robertobarreda__rabbitmq + AMQP_ADDR: robertobarreda__rabbitmq:5672 script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps From cf24e2502ec4b3d9c5bc36b10c31525f6ad8ccb8 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 09:19:08 +0200 Subject: [PATCH 2061/2266] Use port 1 instead of 0 in tests --- core/handler/convert_fields_test.go | 50 ++++++++++++++--------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 423ee0eae..59c9e63e4 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -135,15 +135,15 @@ func TestValidate(t *testing.T) { return data.temperature < 20; }`, } - valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}, 0) + valid, err := withFunction.Validate(map[string]interface{}{"temperature": 10}, 1) a.So(err, ShouldBeNil) a.So(valid, ShouldBeTrue) - valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}, 0) + valid, err = withFunction.Validate(map[string]interface{}{"temperature": 30}, 1) a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) withoutFunction := &UplinkFunctions{} - valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}, 0) + valid, err = withoutFunction.Validate(map[string]interface{}{"temperature": 10}, 1) a.So(err, ShouldBeNil) a.So(valid, ShouldBeTrue) } @@ -167,7 +167,7 @@ func TestProcessUplink(t *testing.T) { }`, } - data, valid, err := functions.Process([]byte{40, 110}, 0) + data, valid, err := functions.Process([]byte{40, 110}, 1) a.So(err, ShouldBeNil) a.So(valid, ShouldBeFalse) a.So(data["temperature"], ShouldEqual, 20) @@ -181,21 +181,21 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { functions := &UplinkFunctions{ Decoder: ``, } - _, _, err := functions.Process([]byte{40, 110}, 0) + _, _, err := functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Function functions = &UplinkFunctions{ Decoder: `this is not valid JavaScript`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &UplinkFunctions{ Decoder: `function(payload) { return "Hello" }`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Function @@ -203,7 +203,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `this is not valid JavaScript`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Return @@ -211,7 +211,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `function(data) { return "Hello" }`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Function @@ -219,7 +219,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `this is not valid JavaScript`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Return @@ -227,7 +227,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `function(data) { return "Hello" }`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Object (Arrays are Objects too, but don't jive well with @@ -235,7 +235,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { functions = &UplinkFunctions{ Decoder: `function(payload) { return [1] }`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Object (Arrays are Objects too, but don't jive well with @@ -244,7 +244,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Converter: `function(payload) { return [1] }`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Object (Arrays are Objects too), this should work error because @@ -253,7 +253,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { Decoder: `function(payload) { return { temperature: payload[0] } }`, Validator: `function(payload) { return [1] }`, } - _, _, err = functions.Process([]byte{40, 110}, 0) + _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) } @@ -271,7 +271,7 @@ func TestTimeoutExceeded(t *testing.T) { interrupted := make(chan bool, 2) go func() { - _, _, err := functions.Process([]byte{0}, 0) + _, _, err := functions.Process([]byte{0}, 1) a.So(time.Since(start), ShouldAlmostEqual, 100*time.Millisecond, 0.5e9) a.So(err, ShouldNotBeNil) interrupted <- true @@ -294,7 +294,7 @@ func TestEncode(t *testing.T) { // The payload is a JSON structure payload := map[string]interface{}{"temperature": 11} - m, err := functions.Encode(payload, 0) + m, err := functions.Encode(payload, 1) a.So(err, ShouldBeNil) a.So(m, ShouldHaveLength, 7) @@ -304,7 +304,7 @@ func TestEncode(t *testing.T) { functions = &DownlinkFunctions{ Encoder: `function(payload, port) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldBeNil) } @@ -396,35 +396,35 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { functions := &DownlinkFunctions{ Encoder: ``, } - _, _, err := functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err := functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid Function functions = &DownlinkFunctions{ Encoder: `this is not valid JavaScript`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ Encoder: `function(payload) { return "Hello" }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ Encoder: `function(payload) { return [ 100, 2256, 7 ] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ Encoder: `function(payload) { return [0, -1, "blablabla"] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return @@ -436,13 +436,13 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { } } }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) functions = &DownlinkFunctions{ Encoder: `function(payload) { return [ 1, 1.5 ] }`, } - _, _, err = functions.Process(map[string]interface{}{"key": 11}, 0) + _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) } @@ -457,7 +457,7 @@ func TestEncodeCharCode(t *testing.T) { }); }`, } - val, _, err := functions.Process(map[string]interface{}{"key": 11}, 0) + val, _, err := functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldBeNil) fmt.Println("VALUE", val) From e55130ad263ee49f494f53a0d947795d9e738a7b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 25 Oct 2016 10:26:49 +0200 Subject: [PATCH 2062/2266] Cache entire vendor dir for Gitlab CI --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 31520c037..e90d5dc54 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,6 +11,7 @@ cache: key: "$CI_PROJECT_PATH" paths: - .govendor + - vendor/ before_script: - mkdir -p $(pwd)/.govendor From 6a1acbfd82f376fd79b06fbf6bb6f1f4eed7886d Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 24 Oct 2016 15:54:57 +0100 Subject: [PATCH 2063/2266] Declare exchanges out of band --- amqp/client.go | 10 ++-------- amqp/downlink_test.go | 6 +++--- amqp/publisher.go | 9 ++++----- amqp/publisher_test.go | 2 +- amqp/subscriber.go | 9 ++++----- amqp/subscriber_test.go | 2 +- amqp/uplink_test.go | 6 +++--- core/handler/amqp.go | 4 ++-- core/handler/amqp_test.go | 4 ++-- 9 files changed, 22 insertions(+), 30 deletions(-) diff --git a/amqp/client.go b/amqp/client.go index 25b7426e0..caccb7b66 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -18,8 +18,8 @@ type Client interface { Disconnect() IsConnected() bool - NewPublisher(exchange, exchangeType string) Publisher - NewSubscriber(exchange, exchangeType, name string, durable, autoDelete bool) Subscriber + NewPublisher(exchange string) Publisher + NewSubscriber(exchange, name string, durable, autoDelete bool) Subscriber } // DefaultClient is the default AMQP client for The Things Network @@ -180,12 +180,6 @@ func (p *DefaultChannelClient) Open() error { return fmt.Errorf("Could not open AMQP channel (%s)", err) } - if p.exchange != "" { - if err := channel.ExchangeDeclare(p.exchange, p.exchangeType, true, false, false, false, nil); err != nil { - return fmt.Errorf("Could not declare AMQP exchange (%s)", err) - } - } - p.channel = channel return nil } diff --git a/amqp/downlink_test.go b/amqp/downlink_test.go index 4397f9904..b36572300 100644 --- a/amqp/downlink_test.go +++ b/amqp/downlink_test.go @@ -19,7 +19,7 @@ func TestPublishDownlink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("", "") + p := c.NewPublisher("") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() @@ -39,12 +39,12 @@ func TestSubscribeDownlink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("amq.topic", "topic") + p := c.NewPublisher("amq.topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() - s := c.NewSubscriber("amq.topic", "topic", "", false, true) + s := c.NewSubscriber("amq.topic", "", false, true) err = s.Open() a.So(err, ShouldBeNil) defer s.Close() diff --git a/amqp/publisher.go b/amqp/publisher.go index 2582d7e81..00ffb7dec 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -24,13 +24,12 @@ type DefaultPublisher struct { } // NewPublisher returns a new topic publisher on the specified exchange -func (c *DefaultClient) NewPublisher(exchange, exchangeType string) Publisher { +func (c *DefaultClient) NewPublisher(exchange string) Publisher { return &DefaultPublisher{ DefaultChannelClient: DefaultChannelClient{ - ctx: c.ctx, - client: c, - exchange: exchange, - exchangeType: exchangeType, + ctx: c.ctx, + client: c, + exchange: exchange, }, } } diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go index d6a50bcbb..d609a349a 100644 --- a/amqp/publisher_test.go +++ b/amqp/publisher_test.go @@ -17,7 +17,7 @@ func TestOpenPublisher(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("amq.topic", "topic") + p := c.NewPublisher("amq.topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() diff --git a/amqp/subscriber.go b/amqp/subscriber.go index 6528ecf49..721d78a05 100644 --- a/amqp/subscriber.go +++ b/amqp/subscriber.go @@ -39,13 +39,12 @@ type DefaultSubscriber struct { } // NewSubscriber returns a new topic subscriber on the specified exchange -func (c *DefaultClient) NewSubscriber(exchange, exchangeType, name string, durable, autoDelete bool) Subscriber { +func (c *DefaultClient) NewSubscriber(exchange, name string, durable, autoDelete bool) Subscriber { return &DefaultSubscriber{ DefaultChannelClient: DefaultChannelClient{ - ctx: c.ctx, - client: c, - exchange: exchange, - exchangeType: exchangeType, + ctx: c.ctx, + client: c, + exchange: exchange, }, name: name, durable: durable, diff --git a/amqp/subscriber_test.go b/amqp/subscriber_test.go index 5c32c69f5..c811788b2 100644 --- a/amqp/subscriber_test.go +++ b/amqp/subscriber_test.go @@ -17,7 +17,7 @@ func TestOpenSubscriber(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - s := c.NewSubscriber("amq.topic", "topic", "", false, true) + s := c.NewSubscriber("amq.topic", "", false, true) err = s.Open() a.So(err, ShouldBeNil) defer s.Close() diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index c13cd0d4d..21b34d509 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -19,7 +19,7 @@ func TestPublishUplink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("", "") + p := c.NewPublisher("") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() @@ -39,12 +39,12 @@ func TestSubscribeUplink(t *testing.T) { a.So(err, ShouldBeNil) defer c.Disconnect() - p := c.NewPublisher("amq.topic", "topic") + p := c.NewPublisher("amq.topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() - s := c.NewSubscriber("amq.topic", "topic", "", false, true) + s := c.NewSubscriber("amq.topic", "", false, true) err = s.Open() a.So(err, ShouldBeNil) defer s.Close() diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 8bb1b931e..05a8de102 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -25,7 +25,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange, downlinkQueue s h.amqpUp = make(chan *types.UplinkMessage) - subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, "topic", downlinkQueue, downlinkQueue != "", downlinkQueue == "") + subscriber := h.amqpClient.NewSubscriber(h.amqpExchange, downlinkQueue, downlinkQueue != "", downlinkQueue == "") err = subscriber.Open() if err != nil { return err @@ -45,7 +45,7 @@ func (h *handler) HandleAMQP(username, password, host, exchange, downlinkQueue s ctx := h.Ctx.WithField("Protocol", "AMQP") go func() { - publisher := h.amqpClient.NewPublisher(h.amqpExchange, "topic") + publisher := h.amqpClient.NewPublisher(h.amqpExchange) err := publisher.Open() if err != nil { ctx.WithError(err).Error("Could not open publisher channel") diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go index e066745d9..7f2f2d9c1 100644 --- a/core/handler/amqp_test.go +++ b/core/handler/amqp_test.go @@ -46,7 +46,7 @@ func TestHandleAMQP(t *testing.T) { err = h.HandleAMQP("guest", "guest", host, "amq.topic", "") a.So(err, ShouldBeNil) - p := c.NewPublisher("amq.topic", "topic") + p := c.NewPublisher("amq.topic") err = p.Open() a.So(err, ShouldBeNil) defer p.Close() @@ -61,7 +61,7 @@ func TestHandleAMQP(t *testing.T) { a.So(dev.NextDownlink, ShouldNotBeNil) wg.Add(1) - s := c.NewSubscriber("amq.topic", "topic", "", false, true) + s := c.NewSubscriber("amq.topic", "", false, true) err = s.Open() a.So(err, ShouldBeNil) defer s.Close() From bbb945bc2e61ff214c2a1f965d15fc2496b38d88 Mon Sep 17 00:00:00 2001 From: johanstokking Date: Mon, 24 Oct 2016 16:29:59 +0100 Subject: [PATCH 2064/2266] Fix for handling multiple uplink messages --- amqp/uplink.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/amqp/uplink.go b/amqp/uplink.go index cca8bc89f..a5f1c2991 100644 --- a/amqp/uplink.go +++ b/amqp/uplink.go @@ -35,14 +35,14 @@ func (s *DefaultSubscriber) SubscribeDeviceUplink(appID, devID string, handler U go func() { for delivery := range messages { dataUp := &types.UplinkMessage{} - err := json.Unmarshal(delivery.Body, dataUp) - if err != nil { - s.ctx.Warnf("Could not unmarshal uplink %v (%s)", delivery, err) + if err := json.Unmarshal(delivery.Body, dataUp); err != nil { + s.ctx.Warnf("Could not unmarshal uplink (%s)", err) continue } handler(s, dataUp.AppID, dataUp.DevID, *dataUp) - delivery.Ack(false) - break + if err := delivery.Ack(false); err != nil { + s.ctx.Warnf("Could not acknowledge message (%s)", err) + } } }() From 19ca954592f032eee122148e168e46f417cff16b Mon Sep 17 00:00:00 2001 From: johanstokking Date: Tue, 25 Oct 2016 10:26:45 +0200 Subject: [PATCH 2065/2266] Update doc on how to declare AMQP exchange --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a6eb478cd..50807f9d1 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ When you get started with The Things Network, you'll probably have some question 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) 4. Make sure you have [RabbitMQ](https://www.rabbitmq.com/download.html) and [Redis](http://redis.io/download) **installed** and **running**. If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. Windows users are currently on their own (feel free to contribute a guide for Windows). +5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` ## Set up The Things Network's backend for Development From 3801b6547c80151e6981ba527476e77111ecb862 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 25 Oct 2016 11:23:30 +0200 Subject: [PATCH 2066/2266] Add "url" and "public" to Announcement --- api/discovery/discovery.pb.go | 150 +++++++++++++++----- api/discovery/discovery.proto | 22 +-- cmd/root.go | 3 + core/component.go | 1 + core/discovery/announcement/announcement.go | 6 + core/discovery/discovery.go | 2 + 6 files changed, 137 insertions(+), 47 deletions(-) diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index befaca3c9..785e06a01 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -89,6 +89,8 @@ type Announcement struct { ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` ServiceVersion string `protobuf:"bytes,3,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` + Public bool `protobuf:"varint,6,opt,name=public,proto3" json:"public,omitempty"` NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` PublicKey string `protobuf:"bytes,12,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` Certificate string `protobuf:"bytes,13,opt,name=certificate,proto3" json:"certificate,omitempty"` @@ -472,6 +474,22 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) i += copy(data[i:], m.Description) } + if len(m.Url) > 0 { + data[i] = 0x2a + i++ + i = encodeVarintDiscovery(data, i, uint64(len(m.Url))) + i += copy(data[i:], m.Url) + } + if m.Public { + data[i] = 0x30 + i++ + if m.Public { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } if len(m.NetAddress) > 0 { data[i] = 0x5a i++ @@ -690,6 +708,13 @@ func (m *Announcement) Size() (n int) { if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } + l = len(m.Url) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } + if m.Public { + n += 2 + } l = len(m.NetAddress) if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) @@ -1023,6 +1048,55 @@ func (m *Announcement) Unmarshal(data []byte) error { } m.Description = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Url", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Url = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Public", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Public = bool(v != 0) case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field NetAddress", wireType) @@ -1681,41 +1755,43 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 574 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xae, 0x13, 0x5a, 0xe2, 0x71, 0x9a, 0x46, 0x0b, 0x05, 0x2b, 0x88, 0x60, 0x7c, 0xa1, 0x1c, - 0xb0, 0xa5, 0x54, 0x3d, 0x21, 0x54, 0x19, 0x25, 0x94, 0xaa, 0xb4, 0x54, 0x56, 0x41, 0x88, 0x4b, - 0xe4, 0xd8, 0xd3, 0x74, 0x55, 0x7b, 0x6d, 0xbc, 0xeb, 0xa0, 0x5c, 0x79, 0x0a, 0x6e, 0xbc, 0x0e, - 0x27, 0xc4, 0x23, 0xa0, 0xf2, 0x22, 0xc8, 0xbf, 0x75, 0xd4, 0x46, 0xa8, 0x70, 0xf3, 0x7e, 0xf3, - 0xcd, 0x37, 0x33, 0xdf, 0xee, 0x18, 0x5e, 0x4c, 0xa9, 0x38, 0x4b, 0x26, 0x86, 0x1b, 0x06, 0xe6, - 0xc9, 0x19, 0x9e, 0x9c, 0x51, 0x36, 0xe5, 0x47, 0x28, 0x3e, 0x87, 0xf1, 0xb9, 0x29, 0x04, 0x33, - 0x9d, 0x88, 0x9a, 0x1e, 0xe5, 0x6e, 0x38, 0xc3, 0x78, 0x7e, 0xf9, 0x65, 0x44, 0x71, 0x28, 0x42, - 0x22, 0x57, 0x40, 0xef, 0xc1, 0x34, 0x0c, 0xa7, 0x3e, 0x9a, 0x59, 0x60, 0x92, 0x9c, 0x9a, 0x18, - 0x44, 0xa2, 0xe0, 0xe9, 0x5f, 0x24, 0x68, 0x1d, 0xa2, 0x70, 0x3c, 0x47, 0x38, 0xe4, 0x29, 0x34, - 0xcf, 0x71, 0xae, 0x4a, 0x9a, 0xb4, 0xd5, 0x19, 0xdc, 0x37, 0x2e, 0x35, 0x4b, 0x86, 0x71, 0x80, - 0x73, 0x3b, 0xe5, 0x90, 0xbb, 0xb0, 0x3a, 0x73, 0xfc, 0x04, 0xd5, 0x86, 0x26, 0x6d, 0xb5, 0xed, - 0xfc, 0xa0, 0xef, 0x40, 0xf3, 0x00, 0xe7, 0x44, 0x86, 0xd5, 0xb7, 0x27, 0xaf, 0x47, 0x76, 0x77, - 0x85, 0x00, 0xac, 0x1d, 0xdb, 0xa3, 0x57, 0xfb, 0x1f, 0xba, 0x12, 0x51, 0xe0, 0xb6, 0x75, 0x7c, - 0x3c, 0x1e, 0xbd, 0xdb, 0xef, 0x36, 0xd2, 0x40, 0x7a, 0xd8, 0x1f, 0x76, 0x9b, 0xfa, 0xb7, 0x06, - 0xb4, 0x2d, 0xc6, 0xc2, 0x84, 0xb9, 0x18, 0x20, 0x13, 0xa4, 0x03, 0x0d, 0xea, 0x65, 0x7d, 0xc8, - 0x76, 0x83, 0x7a, 0xe4, 0x31, 0xb4, 0x39, 0xc6, 0x33, 0xea, 0xe2, 0x98, 0x39, 0x41, 0x5e, 0x54, - 0xb6, 0x95, 0x02, 0x3b, 0x72, 0x02, 0x24, 0x4f, 0x60, 0xa3, 0xa4, 0xcc, 0x30, 0xe6, 0x34, 0x64, - 0x6a, 0x33, 0x63, 0x75, 0x0a, 0xf8, 0x7d, 0x8e, 0x12, 0x0d, 0x14, 0x0f, 0xb9, 0x1b, 0xd3, 0x48, - 0xa4, 0xa4, 0x5b, 0xb9, 0x54, 0x0d, 0x22, 0x8f, 0x40, 0x61, 0x28, 0xc6, 0x8e, 0xe7, 0xc5, 0xc8, - 0xb9, 0xaa, 0x64, 0x0c, 0x60, 0x28, 0xac, 0x1c, 0x21, 0x0f, 0x01, 0xa2, 0x64, 0xe2, 0x53, 0x77, - 0x9c, 0xda, 0xd5, 0xce, 0xe2, 0x72, 0x8e, 0xa4, 0xe3, 0x6b, 0xa0, 0xb8, 0x18, 0x0b, 0x7a, 0x4a, - 0x5d, 0x47, 0xa0, 0xba, 0x9e, 0x57, 0xa8, 0x41, 0xc4, 0x84, 0x56, 0x50, 0x58, 0xaa, 0x6e, 0x6a, - 0xcd, 0x2d, 0x65, 0x70, 0xe7, 0x1a, 0xb7, 0xed, 0x8a, 0xa4, 0x0f, 0x60, 0x7d, 0x0f, 0x85, 0xe5, - 0xfb, 0x36, 0x7e, 0x4a, 0x90, 0x8b, 0x2b, 0x8e, 0x48, 0x57, 0x1c, 0xd1, 0x77, 0x01, 0xf6, 0x50, - 0x94, 0x09, 0x37, 0xb7, 0x54, 0x4f, 0x60, 0xa3, 0x6a, 0xe5, 0x9f, 0x55, 0x16, 0x66, 0x4d, 0xad, - 0xfc, 0xeb, 0xac, 0x6f, 0x60, 0xb3, 0xfe, 0x18, 0xb8, 0x8d, 0x3c, 0x0a, 0x19, 0x47, 0xb2, 0x0d, - 0xad, 0x42, 0x98, 0xab, 0x52, 0xe6, 0x5a, 0xfd, 0x8d, 0xd6, 0x73, 0xec, 0x8a, 0x38, 0xf8, 0xd1, - 0x00, 0x79, 0x58, 0x92, 0xc8, 0x73, 0x68, 0x95, 0x3c, 0xb2, 0x2c, 0xb9, 0x77, 0xcf, 0xc8, 0x37, - 0xc6, 0x28, 0x37, 0xc6, 0x18, 0xa5, 0x1b, 0x43, 0x86, 0xb0, 0x96, 0x5f, 0x02, 0x51, 0x6b, 0xa9, - 0x0b, 0xf7, 0xd2, 0xd3, 0x96, 0x88, 0x5e, 0x4e, 0xb1, 0x03, 0xcd, 0x3d, 0x14, 0x64, 0x73, 0x51, - 0xa2, 0xcc, 0x5f, 0xd6, 0x14, 0xb1, 0x40, 0xb1, 0x3c, 0xaf, 0x5a, 0xd5, 0xde, 0x75, 0x1e, 0x16, - 0x1a, 0xcb, 0xfb, 0xef, 0x0c, 0xd1, 0x47, 0x81, 0xff, 0xa3, 0x32, 0x20, 0xd0, 0xad, 0xfc, 0x3c, - 0x74, 0x98, 0x33, 0xc5, 0xf8, 0xe5, 0xee, 0xf7, 0x8b, 0xbe, 0xf4, 0xf3, 0xa2, 0x2f, 0xfd, 0xba, - 0xe8, 0x4b, 0x5f, 0x7f, 0xf7, 0x57, 0x3e, 0x3e, 0xbb, 0xd1, 0xef, 0x6b, 0xb2, 0x96, 0x15, 0xd9, - 0xfe, 0x13, 0x00, 0x00, 0xff, 0xff, 0x43, 0xf2, 0x4d, 0xa5, 0xf6, 0x04, 0x00, 0x00, + // 597 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0xad, 0xe3, 0xaf, 0xf9, 0x92, 0xeb, 0x34, 0x8d, 0x06, 0x5a, 0xac, 0x20, 0x82, 0xc9, 0x86, + 0xb0, 0xc0, 0x96, 0x52, 0x75, 0x85, 0x50, 0x65, 0x94, 0x50, 0xaa, 0xd2, 0x52, 0x59, 0x05, 0x21, + 0x36, 0x91, 0x63, 0xdf, 0xa6, 0xa3, 0xda, 0x63, 0xe3, 0x19, 0x07, 0x65, 0xcb, 0x53, 0xf0, 0x48, + 0xb0, 0x41, 0x3c, 0x02, 0x2a, 0x2f, 0x82, 0xfc, 0x5b, 0x47, 0x6d, 0x84, 0x0a, 0x3b, 0xcf, 0xb9, + 0xe7, 0x9c, 0xb9, 0x73, 0x66, 0xae, 0xe1, 0xf9, 0x8c, 0x8a, 0xf3, 0x78, 0xaa, 0x3b, 0x81, 0x6f, + 0x9c, 0x9e, 0xe3, 0xe9, 0x39, 0x65, 0x33, 0x7e, 0x8c, 0xe2, 0x53, 0x10, 0x5d, 0x18, 0x42, 0x30, + 0xc3, 0x0e, 0xa9, 0xe1, 0x52, 0xee, 0x04, 0x73, 0x8c, 0x16, 0x57, 0x5f, 0x7a, 0x18, 0x05, 0x22, + 0x20, 0xcd, 0x12, 0xe8, 0xde, 0x9f, 0x05, 0xc1, 0xcc, 0x43, 0x23, 0x2d, 0x4c, 0xe3, 0x33, 0x03, + 0xfd, 0x50, 0xe4, 0xbc, 0xfe, 0x67, 0x09, 0x1a, 0x47, 0x28, 0x6c, 0xd7, 0x16, 0x36, 0x79, 0x02, + 0xf2, 0x05, 0x2e, 0x54, 0x49, 0x93, 0x06, 0xed, 0xe1, 0x3d, 0xfd, 0xca, 0xb3, 0x60, 0xe8, 0x87, + 0xb8, 0xb0, 0x12, 0x0e, 0xb9, 0x0b, 0xeb, 0x73, 0xdb, 0x8b, 0x51, 0xad, 0x69, 0xd2, 0xa0, 0x65, + 0x65, 0x8b, 0xfe, 0x2e, 0xc8, 0x87, 0xb8, 0x20, 0x4d, 0x58, 0x7f, 0x73, 0xfa, 0x6a, 0x6c, 0x75, + 0xd6, 0x08, 0x40, 0xfd, 0xc4, 0x1a, 0xbf, 0x3c, 0x78, 0xdf, 0x91, 0x88, 0x02, 0xff, 0x9b, 0x27, + 0x27, 0x93, 0xf1, 0xdb, 0x83, 0x4e, 0x2d, 0x29, 0x24, 0x8b, 0x83, 0x51, 0x47, 0xee, 0x7f, 0xab, + 0x41, 0xcb, 0x64, 0x2c, 0x88, 0x99, 0x83, 0x3e, 0x32, 0x41, 0xda, 0x50, 0xa3, 0x6e, 0xda, 0x47, + 0xd3, 0xaa, 0x51, 0x97, 0x3c, 0x82, 0x16, 0xc7, 0x68, 0x4e, 0x1d, 0x9c, 0x30, 0xdb, 0xcf, 0x36, + 0x6d, 0x5a, 0x4a, 0x8e, 0x1d, 0xdb, 0x3e, 0x92, 0xc7, 0xb0, 0x59, 0x50, 0xe6, 0x18, 0x71, 0x1a, + 0x30, 0x55, 0x4e, 0x59, 0xed, 0x1c, 0x7e, 0x97, 0xa1, 0x44, 0x03, 0xc5, 0x45, 0xee, 0x44, 0x34, + 0x14, 0x09, 0xe9, 0xbf, 0xcc, 0xaa, 0x02, 0x91, 0x0e, 0xc8, 0x71, 0xe4, 0xa9, 0xeb, 0x69, 0x25, + 0xf9, 0x24, 0xdb, 0x50, 0x0f, 0xe3, 0xa9, 0x47, 0x1d, 0xb5, 0xae, 0x49, 0x83, 0x86, 0x95, 0xaf, + 0xc8, 0x43, 0x50, 0x18, 0x8a, 0x89, 0xed, 0xba, 0x11, 0x72, 0xae, 0x2a, 0xa9, 0x02, 0x18, 0x0a, + 0x33, 0x43, 0xc8, 0x03, 0x80, 0x8c, 0x3a, 0x49, 0x82, 0x6d, 0xa5, 0xf5, 0x66, 0x86, 0x24, 0x41, + 0x69, 0xa0, 0x38, 0x18, 0x09, 0x7a, 0x46, 0x1d, 0x5b, 0xa0, 0xba, 0x91, 0xf5, 0x52, 0x81, 0x88, + 0x01, 0x0d, 0x3f, 0x0f, 0x5f, 0xdd, 0xd2, 0xe4, 0x81, 0x32, 0xbc, 0x73, 0xc3, 0xbd, 0x58, 0x25, + 0xa9, 0x3f, 0x84, 0x8d, 0x7d, 0x14, 0xa6, 0xe7, 0x59, 0xf8, 0x31, 0x46, 0x2e, 0xae, 0x65, 0x27, + 0x5d, 0xcb, 0xae, 0xbf, 0x07, 0xb0, 0x8f, 0xa2, 0x10, 0xdc, 0x3e, 0xfc, 0x7e, 0x0c, 0x9b, 0x65, + 0x2b, 0x7f, 0xed, 0xb2, 0x74, 0xd6, 0x24, 0xca, 0x3f, 0x9e, 0xf5, 0x35, 0x6c, 0x55, 0x9f, 0x0d, + 0xb7, 0x90, 0x87, 0x01, 0xe3, 0x48, 0x76, 0xa0, 0x91, 0x1b, 0x73, 0x55, 0x4a, 0x53, 0xab, 0xbe, + 0xe6, 0xaa, 0xc6, 0x2a, 0x89, 0xc3, 0xef, 0x35, 0x68, 0x8e, 0x0a, 0x12, 0x79, 0x06, 0x8d, 0x82, + 0x47, 0x56, 0x89, 0xbb, 0xdb, 0x7a, 0x36, 0x5b, 0x7a, 0x31, 0x5b, 0xfa, 0x38, 0x99, 0x2d, 0x32, + 0x82, 0x7a, 0x76, 0x09, 0x44, 0xad, 0x48, 0x97, 0xee, 0xa5, 0xab, 0xad, 0x30, 0xbd, 0x3a, 0xc5, + 0x2e, 0xc8, 0xfb, 0x28, 0xc8, 0xd6, 0xb2, 0x45, 0xa1, 0x5f, 0xd5, 0x14, 0x31, 0x41, 0x31, 0x5d, + 0xb7, 0x1c, 0xea, 0xee, 0x4d, 0x19, 0xe6, 0x1e, 0xab, 0xfb, 0x6f, 0x8f, 0xd0, 0x43, 0x81, 0xff, + 0xe2, 0x32, 0x24, 0xd0, 0x29, 0xf3, 0x3c, 0xb2, 0x99, 0x3d, 0xc3, 0xe8, 0xc5, 0xde, 0xd7, 0xcb, + 0x9e, 0xf4, 0xe3, 0xb2, 0x27, 0xfd, 0xbc, 0xec, 0x49, 0x5f, 0x7e, 0xf5, 0xd6, 0x3e, 0x3c, 0xbd, + 0xd5, 0x8f, 0x6e, 0x5a, 0x4f, 0x37, 0xd9, 0xf9, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x91, 0x67, + 0x60, 0x20, 0x05, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index fce06e785..93d097698 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -15,28 +15,30 @@ message Metadata { // The value for PREFIX consists of 1 byte denoting the number of bits, // followed by the prefix and enough trailing bits to fill 4 octets - PREFIX = 1; + PREFIX = 1; // For Brokers // APP_EUI is used for announcing join handlers. // The value for APP_EUI is the byte slice of the AppEUI string - APP_EUI = 2; + APP_EUI = 2; // For Handlers // APP_ID is used for announcing regular handlers // The value for APP_ID is the byte slice of the AppID string - APP_ID = 3; + APP_ID = 3; // For Handlers } Key key = 1; bytes value = 2; } message Announcement { - string id = 1; - string service_name = 2; - string service_version = 3; - string description = 4; - string net_address = 11; - string public_key = 12; - string certificate = 13; + string id = 1; // ID + string service_name = 2; // Service name/type: router/broker/handler + string service_version = 3; // Service version in the form "[version]-[commit] ([build date])" + string description = 4; // Description of the component + string url = 5; // URL with documentation or more information about this component + bool public = 6; // Indicates whether this service is part of The Things Network (the public community network) + string net_address = 11; // Network address in the form "[hostname]:[port]" + string public_key = 12; // ECDSA public key of this component + string certificate = 13; // TLS Certificate (if enabled) repeated Metadata metadata = 21; } diff --git a/cmd/root.go b/cmd/root.go index fb712817f..abad592cc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -121,6 +121,9 @@ func init() { RootCmd.PersistentFlags().String("description", "", "The description of this component") viper.BindPFlag("description", RootCmd.PersistentFlags().Lookup("description")) + RootCmd.PersistentFlags().Bool("public", false, "Announce this component as part of The Things Network (public community network)") + viper.BindPFlag("public", RootCmd.PersistentFlags().Lookup("public")) + RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) diff --git a/core/component.go b/core/component.go index 3ed23e24d..855747591 100644 --- a/core/component.go +++ b/core/component.go @@ -71,6 +71,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string ServiceName: serviceName, ServiceVersion: fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), NetAddress: announcedAddress, + Public: viper.GetBool("public"), }, AccessToken: viper.GetString("auth-token"), TokenKeyProvider: tokenkey.HTTPProvider( diff --git a/core/discovery/announcement/announcement.go b/core/discovery/announcement/announcement.go index 3a9708230..69daff300 100644 --- a/core/discovery/announcement/announcement.go +++ b/core/discovery/announcement/announcement.go @@ -148,6 +148,8 @@ type Announcement struct { ServiceName string `redis:"service_name"` ServiceVersion string `redis:"service_version"` Description string `redis:"description"` + URL string `redis:"url"` + Public bool `redis:"public"` NetAddress string `redis:"net_address"` PublicKey string `redis:"public_key"` Certificate string `redis:"certificate"` @@ -194,6 +196,8 @@ func (a Announcement) ToProto() *pb.Announcement { ServiceName: a.ServiceName, ServiceVersion: a.ServiceVersion, Description: a.Description, + Url: a.URL, + Public: a.Public, NetAddress: a.NetAddress, PublicKey: a.PublicKey, Certificate: a.Certificate, @@ -212,6 +216,8 @@ func FromProto(a *pb.Announcement) Announcement { ServiceName: a.ServiceName, ServiceVersion: a.ServiceVersion, Description: a.Description, + URL: a.Url, + Public: a.Public, NetAddress: a.NetAddress, PublicKey: a.PublicKey, Certificate: a.Certificate, diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index b698b4d2d..db99643c7 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -60,6 +60,8 @@ func (d *discovery) Announce(in *pb.Announcement) error { service.ServiceName = in.ServiceName service.ServiceVersion = in.ServiceVersion service.Description = in.Description + service.URL = in.Url + service.Public = in.Public service.NetAddress = in.NetAddress service.PublicKey = in.PublicKey service.Certificate = in.Certificate From 69d052b44a521e80933dcf530488ce9b67698267 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 18:00:05 +0200 Subject: [PATCH 2067/2266] Add logs in protocol buffer --- api/handler/handler.pb.go | 247 +++++++++++++++++++++++++++----------- api/handler/handler.proto | 10 +- 2 files changed, 181 insertions(+), 76 deletions(-) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index e596adb00..405e15095 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -316,9 +316,10 @@ func (m *DryUplinkMessage) GetApp() *Application { } type DryUplinkResult struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` - Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` + Logs []string `protobuf:"bytes,4,rep,name=logs" json:"logs,omitempty"` } func (m *DryUplinkResult) Reset() { *m = DryUplinkResult{} } @@ -327,7 +328,8 @@ func (*DryUplinkResult) ProtoMessage() {} func (*DryUplinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{10} } type DryDownlinkResult struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Logs []string `protobuf:"bytes,2,rep,name=logs" json:"logs,omitempty"` } func (m *DryDownlinkResult) Reset() { *m = DryDownlinkResult{} } @@ -1322,6 +1324,21 @@ func (m *DryUplinkResult) MarshalTo(data []byte) (int, error) { } i++ } + if len(m.Logs) > 0 { + for _, s := range m.Logs { + data[i] = 0x22 + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } return i, nil } @@ -1346,6 +1363,21 @@ func (m *DryDownlinkResult) MarshalTo(data []byte) (int, error) { i = encodeVarintHandler(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if len(m.Logs) > 0 { + for _, s := range m.Logs { + data[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } return i, nil } @@ -1570,6 +1602,12 @@ func (m *DryUplinkResult) Size() (n int) { if m.Valid { n += 2 } + if len(m.Logs) > 0 { + for _, s := range m.Logs { + l = len(s) + n += 1 + l + sovHandler(uint64(l)) + } + } return n } @@ -1580,6 +1618,12 @@ func (m *DryDownlinkResult) Size() (n int) { if l > 0 { n += 1 + l + sovHandler(uint64(l)) } + if len(m.Logs) > 0 { + for _, s := range m.Logs { + l = len(s) + n += 1 + l + sovHandler(uint64(l)) + } + } return n } @@ -3044,6 +3088,35 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { } } m.Valid = bool(v != 0) + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Logs = append(m.Logs, string(data[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -3125,6 +3198,35 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Logs = append(m.Logs, string(data[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipHandler(data[iNdEx:]) @@ -3256,72 +3358,73 @@ func init() { } var fileDescriptorHandler = []byte{ - // 1057 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5d, 0x6f, 0xe3, 0x44, - 0x17, 0x5e, 0xb7, 0xdb, 0xb4, 0x3d, 0xe9, 0xe7, 0x74, 0xb7, 0xaf, 0xdf, 0xb4, 0x0a, 0xdd, 0x59, - 0x51, 0xba, 0x5d, 0xb0, 0x45, 0x40, 0x02, 0x56, 0x42, 0xec, 0x47, 0xe8, 0x6e, 0x25, 0x0a, 0xc2, - 0x5d, 0x84, 0xe8, 0x05, 0xd5, 0x34, 0x3e, 0x4d, 0xac, 0x3a, 0x1e, 0xe3, 0x99, 0xa4, 0x0a, 0xab, - 0xbd, 0xd9, 0x7b, 0xae, 0xe0, 0x82, 0x5f, 0xc0, 0x3f, 0x41, 0xe2, 0x12, 0x89, 0x4b, 0x2e, 0x40, - 0x85, 0x1f, 0x82, 0x3c, 0x33, 0x76, 0xdc, 0x7c, 0xf4, 0x03, 0x71, 0x95, 0xcc, 0x79, 0x8e, 0x9f, - 0x73, 0x9e, 0x33, 0x33, 0x8f, 0x0d, 0x1f, 0x34, 0x03, 0xd9, 0xea, 0x1c, 0x39, 0x0d, 0xde, 0x76, - 0x9f, 0xb7, 0xf0, 0x79, 0x2b, 0x88, 0x9a, 0xe2, 0x53, 0x94, 0xa7, 0x3c, 0x39, 0x71, 0xa5, 0x8c, - 0x5c, 0x16, 0x07, 0x6e, 0x8b, 0x45, 0x7e, 0x88, 0x49, 0xf6, 0xeb, 0xc4, 0x09, 0x97, 0x9c, 0x4c, - 0x9b, 0x65, 0x65, 0xad, 0xc9, 0x79, 0x33, 0x44, 0x57, 0x85, 0x8f, 0x3a, 0xc7, 0x2e, 0xb6, 0x63, - 0xd9, 0xd3, 0x59, 0x95, 0x75, 0x03, 0xa6, 0x3c, 0x2c, 0x8a, 0xb8, 0x64, 0x32, 0xe0, 0x91, 0x30, - 0xe8, 0x72, 0x56, 0x82, 0xc5, 0x81, 0x09, 0xad, 0x65, 0xa1, 0xa3, 0x84, 0x9f, 0x60, 0x62, 0x7e, - 0x0c, 0xf8, 0x5a, 0x06, 0xaa, 0x65, 0x83, 0x87, 0xf9, 0x1f, 0x93, 0xf0, 0xfa, 0x50, 0x42, 0xc8, - 0x13, 0x76, 0xca, 0x22, 0xd7, 0xc7, 0x6e, 0xd0, 0x40, 0x9d, 0x46, 0x7f, 0xb7, 0xc0, 0xae, 0xab, - 0xc0, 0xa3, 0x86, 0x0c, 0xba, 0xaa, 0x27, 0x0f, 0x45, 0xcc, 0x23, 0x81, 0xc4, 0x86, 0xe9, 0x98, - 0xf5, 0x42, 0xce, 0x7c, 0xdb, 0xda, 0xb0, 0xb6, 0xe6, 0xbc, 0x6c, 0x49, 0x6e, 0x43, 0x89, 0xc5, - 0xf1, 0x61, 0xe0, 0xdb, 0x13, 0x1b, 0xd6, 0xd6, 0xac, 0x37, 0xc5, 0xe2, 0x78, 0xd7, 0x27, 0x1f, - 0xc1, 0xa2, 0xcf, 0x4f, 0xa3, 0x30, 0x88, 0x4e, 0x0e, 0x79, 0x9c, 0x72, 0xd9, 0xe5, 0x0d, 0x6b, - 0xab, 0x5c, 0x5b, 0x75, 0x4c, 0xf7, 0x75, 0x03, 0x7f, 0xa6, 0x50, 0x6f, 0xc1, 0x3f, 0xb7, 0x26, - 0x7b, 0xb0, 0xc2, 0xf2, 0x3e, 0x0e, 0xdb, 0x28, 0x99, 0xcf, 0x24, 0xb3, 0xff, 0xa7, 0x48, 0xd6, - 0x9d, 0x5c, 0x63, 0xbf, 0xd9, 0x3d, 0x93, 0xe3, 0x11, 0x36, 0x14, 0xa3, 0x8b, 0x30, 0xbf, 0x2f, - 0x99, 0xec, 0x08, 0x0f, 0xbf, 0xe9, 0xa0, 0x90, 0xf4, 0x0f, 0x0b, 0x4a, 0x3a, 0x42, 0xb6, 0xa0, - 0x24, 0x7a, 0x42, 0x62, 0x5b, 0x69, 0x2b, 0xd7, 0x96, 0x9c, 0x74, 0xf4, 0xfb, 0x2a, 0x94, 0xa6, - 0x08, 0xcf, 0xe0, 0xe4, 0x6d, 0x98, 0x6d, 0xf0, 0x76, 0xcc, 0x23, 0x8c, 0xa4, 0xd2, 0x5b, 0xae, - 0xad, 0xa8, 0xe4, 0x27, 0x59, 0x54, 0xe7, 0xf7, 0xb3, 0x08, 0x85, 0x52, 0x27, 0x4e, 0x75, 0x19, - 0xfd, 0xa0, 0xf2, 0x3d, 0x26, 0x51, 0x78, 0x06, 0x21, 0x9b, 0x30, 0x93, 0xa9, 0xb7, 0xe7, 0x86, - 0xb2, 0x72, 0x8c, 0xbc, 0x09, 0xe5, 0xbe, 0x34, 0x61, 0xcf, 0x0f, 0xa5, 0x16, 0x61, 0xea, 0xc0, - 0xed, 0x47, 0x71, 0x1c, 0x06, 0x0d, 0xb5, 0xde, 0xf5, 0x31, 0x92, 0xc1, 0x71, 0x80, 0x49, 0x61, - 0xcb, 0xac, 0xc2, 0x96, 0xd1, 0x1f, 0x2c, 0x28, 0x17, 0x1e, 0x18, 0x93, 0x96, 0x1e, 0x05, 0x1f, - 0x1b, 0xdc, 0xc7, 0xc4, 0xec, 0x78, 0xb6, 0x24, 0xeb, 0xe9, 0x74, 0xa2, 0x2e, 0x26, 0x12, 0x13, - 0x7b, 0x52, 0x61, 0xfd, 0x40, 0x8a, 0x76, 0x59, 0x18, 0xf8, 0x4c, 0xf2, 0xc4, 0xbe, 0xa9, 0xd1, - 0x3c, 0x90, 0xb2, 0x62, 0xa4, 0x59, 0xa7, 0x34, 0xab, 0x59, 0xd2, 0x87, 0xb0, 0xa4, 0x8f, 0xe5, - 0xa5, 0x0a, 0xd2, 0xb0, 0x8f, 0xdd, 0xc2, 0x59, 0xf4, 0xb1, 0xbb, 0xeb, 0xd3, 0x6f, 0xa1, 0xa4, - 0x19, 0xae, 0xf7, 0x1c, 0x79, 0x1f, 0x16, 0xcc, 0x4d, 0x39, 0xd4, 0x37, 0x45, 0x89, 0x2a, 0xd7, - 0x16, 0x1d, 0x13, 0x76, 0x34, 0xed, 0xb3, 0x1b, 0xde, 0xbc, 0x89, 0xe8, 0xc0, 0xe3, 0x19, 0x45, - 0x18, 0x34, 0x90, 0xbe, 0x07, 0xa0, 0x63, 0x9f, 0x04, 0x42, 0x92, 0x7b, 0xe9, 0xec, 0xd2, 0x95, - 0xb0, 0xad, 0x8d, 0x49, 0x45, 0x95, 0x19, 0x88, 0xce, 0xf2, 0x32, 0x9c, 0xbe, 0xb2, 0x80, 0xd4, - 0x93, 0x5e, 0x76, 0x4b, 0xf6, 0x50, 0x08, 0xd6, 0xbc, 0xe8, 0x22, 0xae, 0x42, 0xe9, 0x38, 0xc0, - 0xd0, 0x17, 0x46, 0x84, 0x59, 0x91, 0x4d, 0x98, 0x64, 0x71, 0x6c, 0x5a, 0xbf, 0x95, 0xd7, 0x2b, - 0xec, 0xb4, 0x97, 0x26, 0x10, 0x02, 0x37, 0x63, 0x9e, 0x48, 0xb5, 0x35, 0xf3, 0x9e, 0xfa, 0x4f, - 0x5b, 0xb0, 0x54, 0x4f, 0x7a, 0x5f, 0xc4, 0x57, 0xeb, 0xc0, 0x54, 0x9a, 0xb8, 0x6a, 0xa5, 0xc9, - 0x42, 0xa5, 0xaf, 0x60, 0x31, 0xaf, 0xe4, 0xa1, 0xe8, 0x84, 0xf2, 0x5f, 0x48, 0xbd, 0x05, 0x53, - 0xea, 0x44, 0x29, 0xe6, 0x19, 0x4f, 0x2f, 0xe8, 0x5b, 0xb0, 0x5c, 0x18, 0xe4, 0x65, 0xe4, 0xb5, - 0x9f, 0x2d, 0x98, 0x7e, 0xa6, 0x5b, 0x27, 0x5f, 0xc3, 0x4a, 0xdf, 0x5f, 0x9e, 0xb4, 0x58, 0x18, - 0x62, 0xd4, 0x44, 0x42, 0x33, 0x0f, 0x1b, 0x01, 0x1a, 0x7f, 0xa9, 0xdc, 0xbd, 0x30, 0xc7, 0xd8, - 0xea, 0x01, 0xcc, 0x18, 0x18, 0xc9, 0xfd, 0xdc, 0x18, 0xd1, 0xef, 0xe8, 0x89, 0xa1, 0x3f, 0x6c, - 0xc8, 0x9a, 0xfd, 0xce, 0xc0, 0xb9, 0x19, 0xb6, 0xec, 0xda, 0x4f, 0x33, 0x40, 0x0a, 0xa3, 0xdf, - 0x63, 0x11, 0x6b, 0x62, 0x42, 0x9a, 0xb0, 0xe2, 0x61, 0x33, 0x10, 0x12, 0x93, 0xe2, 0x65, 0xaf, - 0x8e, 0xda, 0xae, 0xfe, 0x8d, 0xab, 0xac, 0x3a, 0xfa, 0xa5, 0xe5, 0x64, 0x6f, 0x34, 0xe7, 0xe3, - 0xf4, 0x8d, 0x46, 0xed, 0x57, 0xbf, 0xfd, 0xfd, 0xfd, 0x04, 0xa1, 0xf3, 0x2e, 0xeb, 0x3f, 0x27, - 0x1e, 0x58, 0xdb, 0xe4, 0x18, 0x16, 0x9e, 0xa2, 0xbc, 0x4e, 0x8d, 0x91, 0x47, 0x86, 0x56, 0x55, - 0x05, 0x9b, 0xac, 0x9e, 0xab, 0xe0, 0xbe, 0xd0, 0x17, 0xf9, 0x25, 0x61, 0xb0, 0xb0, 0x7f, 0xbe, - 0xce, 0x48, 0x9e, 0xb1, 0x0a, 0xee, 0x28, 0xfe, 0x35, 0x3a, 0x86, 0x3f, 0x95, 0x72, 0x02, 0xcb, - 0x75, 0x0c, 0x51, 0xe2, 0x7f, 0x31, 0x31, 0xa3, 0x67, 0x7b, 0x9c, 0x9e, 0x16, 0xcc, 0x3e, 0x45, - 0x69, 0x0c, 0xeb, 0xff, 0x03, 0xfb, 0x5c, 0xe0, 0x1f, 0xb4, 0x0e, 0xea, 0x2a, 0xe2, 0x7b, 0xe4, - 0x8d, 0xd1, 0xc4, 0xe6, 0x6d, 0x2f, 0xdc, 0x17, 0xda, 0xeb, 0x5e, 0x92, 0xef, 0x2c, 0x98, 0xdd, - 0xcf, 0x4b, 0x0d, 0xf2, 0x8d, 0x15, 0xf0, 0xa5, 0xaa, 0xf3, 0x39, 0xbd, 0x6a, 0x9d, 0x07, 0xd6, - 0xf6, 0xc1, 0x5d, 0x5a, 0xbd, 0x38, 0x3b, 0x1d, 0x73, 0x02, 0x73, 0x7a, 0xcc, 0x97, 0x8b, 0x1f, - 0xd7, 0x9b, 0x99, 0xc1, 0xf6, 0x95, 0x67, 0x70, 0x0a, 0x76, 0x3e, 0x6d, 0xb1, 0xc3, 0xaf, 0x75, - 0x27, 0x56, 0x06, 0xfa, 0x4b, 0x2d, 0x9e, 0x6e, 0xaa, 0x0e, 0x36, 0xc8, 0x25, 0x7a, 0xc9, 0x0e, - 0x94, 0x0b, 0xae, 0x44, 0xd6, 0xfa, 0x5c, 0x43, 0xa6, 0x5f, 0xa9, 0x8c, 0x02, 0x8d, 0x91, 0x3d, - 0x84, 0xd9, 0xdc, 0x38, 0x8b, 0x13, 0x1b, 0xb0, 0xed, 0x8a, 0x3d, 0x0c, 0x69, 0x86, 0xda, 0x0e, - 0x2c, 0x18, 0xbf, 0xcb, 0x3c, 0xe2, 0x5d, 0x75, 0x04, 0xcd, 0xd7, 0xd1, 0x6a, 0xfe, 0xe0, 0xb9, - 0x0f, 0xa8, 0xc2, 0xf9, 0xd3, 0xf1, 0xc7, 0x1f, 0xfe, 0x72, 0x56, 0xb5, 0x7e, 0x3d, 0xab, 0x5a, - 0x7f, 0x9e, 0x55, 0xad, 0x1f, 0xff, 0xaa, 0xde, 0x38, 0xb8, 0x7f, 0x8d, 0x2f, 0xe9, 0xa3, 0x92, - 0xda, 0xca, 0x77, 0xfe, 0x09, 0x00, 0x00, 0xff, 0xff, 0x7c, 0x92, 0xd0, 0x2f, 0x7f, 0x0b, 0x00, - 0x00, + // 1074 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5d, 0x6f, 0xdc, 0x44, + 0x17, 0xae, 0x93, 0x66, 0x93, 0x3d, 0x9b, 0xcf, 0x49, 0x9b, 0xd7, 0xef, 0x26, 0x5a, 0xb6, 0x53, + 0x11, 0xd2, 0x14, 0xd9, 0x62, 0x41, 0x02, 0x2a, 0x21, 0x9a, 0x36, 0xa4, 0x8d, 0x44, 0x40, 0x38, + 0x45, 0x48, 0xb9, 0x20, 0x9a, 0xac, 0x4f, 0x76, 0xad, 0x78, 0x3d, 0xc6, 0x33, 0xbb, 0x51, 0xa8, + 0x7a, 0xd3, 0x7b, 0xae, 0xe0, 0x82, 0x5f, 0xc0, 0x3f, 0x41, 0xe2, 0x12, 0x89, 0x4b, 0x2e, 0x40, + 0x81, 0x1f, 0x82, 0x3c, 0x33, 0xf6, 0x3a, 0xfb, 0x91, 0x0f, 0xc4, 0x95, 0x7d, 0x3e, 0xfc, 0x9c, + 0x79, 0xce, 0x99, 0x79, 0xc6, 0xf0, 0x61, 0x2b, 0x90, 0xed, 0xee, 0x91, 0xd3, 0xe4, 0x1d, 0xf7, + 0x45, 0x1b, 0x5f, 0xb4, 0x83, 0xa8, 0x25, 0x3e, 0x43, 0x79, 0xca, 0x93, 0x13, 0x57, 0xca, 0xc8, + 0x65, 0x71, 0xe0, 0xb6, 0x59, 0xe4, 0x87, 0x98, 0x64, 0x4f, 0x27, 0x4e, 0xb8, 0xe4, 0x64, 0xda, + 0x98, 0xd5, 0xd5, 0x16, 0xe7, 0xad, 0x10, 0x5d, 0xe5, 0x3e, 0xea, 0x1e, 0xbb, 0xd8, 0x89, 0xe5, + 0x99, 0xce, 0xaa, 0xae, 0x99, 0x60, 0x8a, 0xc3, 0xa2, 0x88, 0x4b, 0x26, 0x03, 0x1e, 0x09, 0x13, + 0x5d, 0xca, 0x4a, 0xb0, 0x38, 0x30, 0xae, 0xd5, 0xcc, 0x75, 0x94, 0xf0, 0x13, 0x4c, 0xcc, 0xc3, + 0x04, 0xdf, 0xc8, 0x82, 0xca, 0x6c, 0xf2, 0x30, 0x7f, 0x31, 0x09, 0x6f, 0x0e, 0x25, 0x84, 0x3c, + 0x61, 0xa7, 0x2c, 0x72, 0x7d, 0xec, 0x05, 0x4d, 0xd4, 0x69, 0xf4, 0x77, 0x0b, 0xec, 0x6d, 0xe5, + 0xd8, 0x6a, 0xca, 0xa0, 0xa7, 0xd6, 0xe4, 0xa1, 0x88, 0x79, 0x24, 0x90, 0xd8, 0x30, 0x1d, 0xb3, + 0xb3, 0x90, 0x33, 0xdf, 0xb6, 0xea, 0xd6, 0xc6, 0xac, 0x97, 0x99, 0xe4, 0x2e, 0x94, 0x58, 0x1c, + 0x1f, 0x06, 0xbe, 0x3d, 0x51, 0xb7, 0x36, 0xca, 0xde, 0x14, 0x8b, 0xe3, 0x5d, 0x9f, 0x7c, 0x0c, + 0x0b, 0x3e, 0x3f, 0x8d, 0xc2, 0x20, 0x3a, 0x39, 0xe4, 0x71, 0x8a, 0x65, 0x57, 0xea, 0xd6, 0x46, + 0xa5, 0xb1, 0xe2, 0x98, 0xd5, 0x6f, 0x9b, 0xf0, 0xe7, 0x2a, 0xea, 0xcd, 0xfb, 0x17, 0x6c, 0xb2, + 0x07, 0xcb, 0x2c, 0x5f, 0xc7, 0x61, 0x07, 0x25, 0xf3, 0x99, 0x64, 0xf6, 0xff, 0x14, 0xc8, 0x9a, + 0x93, 0x73, 0xec, 0x2f, 0x76, 0xcf, 0xe4, 0x78, 0x84, 0x0d, 0xf9, 0xe8, 0x02, 0xcc, 0xed, 0x4b, + 0x26, 0xbb, 0xc2, 0xc3, 0x6f, 0xba, 0x28, 0x24, 0xfd, 0xc3, 0x82, 0x92, 0xf6, 0x90, 0x0d, 0x28, + 0x89, 0x33, 0x21, 0xb1, 0xa3, 0xb8, 0x55, 0x1a, 0x8b, 0x4e, 0xda, 0xfa, 0x7d, 0xe5, 0x4a, 0x53, + 0x84, 0x67, 0xe2, 0xe4, 0x1d, 0x28, 0x37, 0x79, 0x27, 0xe6, 0x11, 0x46, 0x52, 0xf1, 0xad, 0x34, + 0x96, 0x55, 0xf2, 0xd3, 0xcc, 0xab, 0xf3, 0xfb, 0x59, 0x84, 0x42, 0xa9, 0x1b, 0xa7, 0xbc, 0x0c, + 0x7f, 0x50, 0xf9, 0x1e, 0x93, 0x28, 0x3c, 0x13, 0x21, 0xeb, 0x30, 0x93, 0xb1, 0xb7, 0x67, 0x87, + 0xb2, 0xf2, 0x18, 0x79, 0x1b, 0x2a, 0x7d, 0x6a, 0xc2, 0x9e, 0x1b, 0x4a, 0x2d, 0x86, 0xa9, 0x03, + 0x77, 0xb7, 0xe2, 0x38, 0x0c, 0x9a, 0xca, 0xde, 0xf5, 0x31, 0x92, 0xc1, 0x71, 0x80, 0x49, 0x61, + 0x64, 0x56, 0x61, 0x64, 0xf4, 0x07, 0x0b, 0x2a, 0x85, 0x0f, 0xc6, 0xa4, 0xa5, 0x5b, 0xc1, 0xc7, + 0x26, 0xf7, 0x31, 0x31, 0x13, 0xcf, 0x4c, 0xb2, 0x96, 0x76, 0x27, 0xea, 0x61, 0x22, 0x31, 0xb1, + 0x27, 0x55, 0xac, 0xef, 0x48, 0xa3, 0x3d, 0x16, 0x06, 0x3e, 0x93, 0x3c, 0xb1, 0x6f, 0xeb, 0x68, + 0xee, 0x48, 0x51, 0x31, 0xd2, 0xa8, 0x53, 0x1a, 0xd5, 0x98, 0xf4, 0x31, 0x2c, 0xea, 0x6d, 0x79, + 0x25, 0x83, 0xd4, 0xed, 0x63, 0xaf, 0xb0, 0x17, 0x7d, 0xec, 0xed, 0xfa, 0xf4, 0x5b, 0x28, 0x69, + 0x84, 0x9b, 0x7d, 0x47, 0x3e, 0x80, 0x79, 0x73, 0x52, 0x0e, 0xf5, 0x49, 0x51, 0xa4, 0x2a, 0x8d, + 0x05, 0xc7, 0xb8, 0x1d, 0x0d, 0xfb, 0xfc, 0x96, 0x37, 0x67, 0x3c, 0xda, 0xf1, 0x64, 0x46, 0x01, + 0x06, 0x4d, 0xa4, 0xef, 0x03, 0x68, 0xdf, 0xa7, 0x81, 0x90, 0xe4, 0x41, 0xda, 0xbb, 0xd4, 0x12, + 0xb6, 0x55, 0x9f, 0x54, 0x50, 0x99, 0x80, 0xe8, 0x2c, 0x2f, 0x8b, 0xd3, 0xd7, 0x16, 0x90, 0xed, + 0xe4, 0x2c, 0x3b, 0x25, 0x7b, 0x28, 0x04, 0x6b, 0x5d, 0x76, 0x10, 0x57, 0xa0, 0x74, 0x1c, 0x60, + 0xe8, 0x0b, 0x43, 0xc2, 0x58, 0x64, 0x1d, 0x26, 0x59, 0x1c, 0x9b, 0xa5, 0xdf, 0xc9, 0xeb, 0x15, + 0x26, 0xed, 0xa5, 0x09, 0x84, 0xc0, 0xed, 0x98, 0x27, 0x52, 0x8d, 0x66, 0xce, 0x53, 0xef, 0xb4, + 0x0d, 0x8b, 0xdb, 0xc9, 0xd9, 0x97, 0xf1, 0xf5, 0x56, 0x60, 0x2a, 0x4d, 0x5c, 0xb7, 0xd2, 0x64, + 0xa1, 0x52, 0x07, 0x16, 0xf2, 0x4a, 0x1e, 0x8a, 0x6e, 0x28, 0xff, 0x05, 0xd5, 0x3b, 0x30, 0xa5, + 0x76, 0x94, 0x42, 0x9e, 0xf1, 0xb4, 0x91, 0x96, 0x0b, 0x79, 0x4b, 0xd8, 0xb7, 0xeb, 0x93, 0x1b, + 0x65, 0x4f, 0xbd, 0xd3, 0x2d, 0x58, 0x2a, 0x34, 0xf7, 0xca, 0x82, 0x19, 0xc4, 0x44, 0x1f, 0xa2, + 0xf1, 0xb3, 0x05, 0xd3, 0xcf, 0x35, 0x45, 0xf2, 0x35, 0x2c, 0xf7, 0x75, 0xe8, 0x69, 0x9b, 0x85, + 0x21, 0x46, 0x2d, 0x24, 0x34, 0xd3, 0xba, 0x11, 0x41, 0xa3, 0x43, 0xd5, 0xfb, 0x97, 0xe6, 0x18, + 0xf9, 0x3d, 0x80, 0x19, 0x13, 0x46, 0xf2, 0x30, 0x17, 0x50, 0xf4, 0xbb, 0xba, 0xb3, 0xe8, 0x0f, + 0x0b, 0xb7, 0x46, 0xbf, 0x37, 0xb0, 0xbf, 0x86, 0xa5, 0xbd, 0xf1, 0xd3, 0x0c, 0x90, 0xc2, 0x88, + 0xf6, 0x58, 0xc4, 0x5a, 0x98, 0x90, 0x16, 0x2c, 0x7b, 0xd8, 0x0a, 0x84, 0xc4, 0xa4, 0x28, 0x0a, + 0xb5, 0x51, 0x63, 0xed, 0x9f, 0xcc, 0xea, 0x8a, 0xa3, 0x2f, 0x37, 0x27, 0xbb, 0xf9, 0x9c, 0x4f, + 0xd2, 0x9b, 0x8f, 0xda, 0xaf, 0x7f, 0xfb, 0xfb, 0xfb, 0x09, 0x42, 0xe7, 0x5c, 0xd6, 0xff, 0x4e, + 0x3c, 0xb2, 0x36, 0xc9, 0x31, 0xcc, 0x3f, 0x43, 0x79, 0x93, 0x1a, 0x23, 0xb7, 0x16, 0xad, 0xa9, + 0x0a, 0x36, 0x59, 0xb9, 0x50, 0xc1, 0x7d, 0xa9, 0x0f, 0xfc, 0x2b, 0xc2, 0x60, 0x7e, 0xff, 0x62, + 0x9d, 0x91, 0x38, 0x63, 0x19, 0xdc, 0x53, 0xf8, 0xab, 0x74, 0x0c, 0x7e, 0x4a, 0xe5, 0x04, 0x96, + 0xb6, 0x31, 0x44, 0x89, 0xff, 0x45, 0xc7, 0x0c, 0x9f, 0xcd, 0x71, 0x7c, 0xda, 0x50, 0x7e, 0x86, + 0xd2, 0x08, 0xdb, 0xff, 0x07, 0xe6, 0x5c, 0xc0, 0x1f, 0x94, 0x18, 0xea, 0x2a, 0xe0, 0x07, 0xe4, + 0xad, 0xd1, 0xc0, 0xe6, 0xaf, 0x40, 0xb8, 0x2f, 0xb5, 0x26, 0xbe, 0x22, 0xdf, 0x59, 0x50, 0xde, + 0xcf, 0x4b, 0x0d, 0xe2, 0x8d, 0x25, 0xf0, 0x95, 0xaa, 0xf3, 0x05, 0xbd, 0x6e, 0x9d, 0x47, 0xd6, + 0xe6, 0xc1, 0x7d, 0x5a, 0xbb, 0x3c, 0x3b, 0x6d, 0x73, 0x02, 0xb3, 0xba, 0xcd, 0x57, 0x93, 0x1f, + 0xb7, 0x36, 0xd3, 0x83, 0xcd, 0x6b, 0xf7, 0xe0, 0x14, 0xec, 0xbc, 0xdb, 0x62, 0x87, 0xdf, 0xe8, + 0x4c, 0x2c, 0x0f, 0xac, 0x2f, 0xbd, 0x0a, 0xe8, 0xba, 0x5a, 0x41, 0x9d, 0x5c, 0xc1, 0x97, 0xec, + 0x40, 0xa5, 0xa0, 0x54, 0x64, 0xb5, 0x8f, 0x35, 0x74, 0x39, 0x54, 0xab, 0xa3, 0x82, 0x46, 0xdc, + 0x1e, 0x43, 0x39, 0x17, 0xd8, 0x62, 0xc7, 0x06, 0xe4, 0xbd, 0x6a, 0x0f, 0x87, 0x34, 0x42, 0x63, + 0x07, 0xe6, 0x8d, 0xde, 0x65, 0x1a, 0xf1, 0x9e, 0xda, 0x82, 0xe6, 0x2f, 0x6a, 0x25, 0xff, 0xf0, + 0xc2, 0x8f, 0x56, 0x61, 0xff, 0x69, 0xff, 0x93, 0x8f, 0x7e, 0x39, 0xaf, 0x59, 0xbf, 0x9e, 0xd7, + 0xac, 0x3f, 0xcf, 0x6b, 0xd6, 0x8f, 0x7f, 0xd5, 0x6e, 0x1d, 0x3c, 0xbc, 0xc1, 0x1f, 0xf7, 0x51, + 0x49, 0x8d, 0xf2, 0xdd, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x6f, 0x1e, 0xf8, 0xa7, 0x0b, + 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 0eaec8861..e377e72fc 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -83,13 +83,15 @@ message DryUplinkMessage { } message DryUplinkResult { - bytes payload = 1; - string fields = 2; - bool valid = 3; + bytes payload = 1; + string fields = 2; + bool valid = 3; + repeated string logs = 4; } message DryDownlinkResult { - bytes payload = 1; + bytes payload = 1; + repeated string logs = 2; } service ApplicationManager { From 8f71529d7bc074e7f822cc72e52ede82133b7398 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 18:00:42 +0200 Subject: [PATCH 2068/2266] Allow logs to be saved in payload functions --- core/handler/run_code.go | 93 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 core/handler/run_code.go diff --git a/core/handler/run_code.go b/core/handler/run_code.go new file mode 100644 index 000000000..dc38bcf56 --- /dev/null +++ b/core/handler/run_code.go @@ -0,0 +1,93 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/robertkrimen/otto" +) + +type Logger interface { + Log(call otto.FunctionCall) otto.Value +} + +type console struct { + Logs [][]string +} + +func JSON(val otto.Value) string { + vm := otto.New() + vm.Set("value", val) + res, _ := vm.Run(`JSON.stringify(value)`) + return res.String() +} + +func (c *console) Log(call otto.FunctionCall) otto.Value { + line := []string{} + for _, arg := range call.ArgumentList { + line = append(line, JSON(arg)) + } + + c.Logs = append(c.Logs, line) + + return otto.UndefinedValue() +} + +type ignore struct{} + +func (c *ignore) Log(call otto.FunctionCall) otto.Value { + return otto.UndefinedValue() +} + +func runUnsafeCodeWithLogger(vm *otto.Otto, code string, timeOut time.Duration, logger Logger) (otto.Value, error) { + if logger == nil { + logger = &ignore{} + } + + vm.Set("__log", logger.Log) + vm.Run("console.log = __log") + start := time.Now() + + var value otto.Value + var err error + + defer func() { + duration := time.Since(start) + if caught := recover(); caught != nil { + if caught == errTimeOutExceeded { + value = otto.Value{} + err = errors.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) + return + } + // if this is not the our timeout interrupt, raise the panic again + // so someone else can handle it + panic(caught) + } + }() + + vm.Interrupt = make(chan func(), 1) + + go func() { + time.Sleep(timeOut) + vm.Interrupt <- func() { + panic(errTimeOutExceeded) + } + }() + val, err := vm.Run(code) + + return val, err +} + +func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (otto.Value, error) { + return runUnsafeCodeWithLogger(vm, code, timeOut, &ignore{}) +} + +func runUnsafeCodeWithLogs(vm *otto.Otto, code string, timeOut time.Duration) (otto.Value, [][]string, error) { + console := &console{} + val, err := runUnsafeCodeWithLogger(vm, code, timeOut, console) + return val, console.Logs, err +} From 8e31bb15d8b94521541d7a3c5ff6ac3c9cad9bce Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 18:01:07 +0200 Subject: [PATCH 2069/2266] Use logger when process payloads --- core/handler/convert_fields.go | 42 +++++++++------------------------- 1 file changed, 11 insertions(+), 31 deletions(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 25e6ddddc..45a83f0be 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -27,6 +27,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat Decoder: app.Decoder, Converter: app.Converter, Validator: app.Validator, + Logger: &ignore{}, } fields, valid, err := functions.Process(appUp.PayloadRaw, appUp.FPort) @@ -54,6 +55,9 @@ type UplinkFunctions struct { // Validator is a JavaScript function that validates the data is converted by // Converter and returns a boolean value indicating the validity of the data Validator string + + // Logger is the logger that will be used to store logs + Logger Logger } // timeOut is the maximum allowed time a payload function is allowed to run @@ -68,7 +72,7 @@ func (f *UplinkFunctions) Decode(payload []byte, port uint8) (map[string]interfa vm := otto.New() vm.Set("payload", payload) vm.Set("port", port) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload.slice(0), port)", f.Decoder), timeOut) + value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(payload.slice(0), port)", f.Decoder), timeOut, f.Logger) if err != nil { return nil, err } @@ -96,7 +100,7 @@ func (f *UplinkFunctions) Convert(data map[string]interface{}, port uint8) (map[ vm := otto.New() vm.Set("data", data) vm.Set("port", port) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data, port)", f.Converter), timeOut) + value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(data, port)", f.Converter), timeOut, f.Logger) if err != nil { return nil, err } @@ -124,7 +128,7 @@ func (f *UplinkFunctions) Validate(data map[string]interface{}, port uint8) (boo vm := otto.New() vm.Set("data", data) vm.Set("port", port) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(data, port)", f.Validator), timeOut) + value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(data, port)", f.Validator), timeOut, f.Logger) if err != nil { return false, err } @@ -154,38 +158,14 @@ func (f *UplinkFunctions) Process(payload []byte, port uint8) (map[string]interf var errTimeOutExceeded = errors.NewErrInternal("Code has been running to long") -func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (value otto.Value, err error) { - start := time.Now() - defer func() { - duration := time.Since(start) - if caught := recover(); caught != nil { - if caught == errTimeOutExceeded { - value = otto.Value{} - err = errors.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) - return - } - // if this is not the our timeout interrupt, raise the panic again - // so someone else can handle it - panic(caught) - } - }() - - vm.Interrupt = make(chan func(), 1) - - go func() { - time.Sleep(timeOut) - vm.Interrupt <- func() { - panic(errTimeOutExceeded) - } - }() - return vm.Run(code) -} - // DownlinkFunctions encodes payload using JavaScript functions type DownlinkFunctions struct { // Encoder is a JavaScript function that accepts the payload as JSON and // returns an array of bytes Encoder string + + // Logger is the logger that will be used to store logs + Logger Logger } // Encode encodes the map into a byte slice using the encoder payload function @@ -198,7 +178,7 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}, port uint8) ( vm := otto.New() vm.Set("payload", payload) vm.Set("port", port) - value, err := runUnsafeCode(vm, fmt.Sprintf("(%s)(payload, port)", f.Encoder), timeOut) + value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(payload, port)", f.Encoder), timeOut, f.Logger) if err != nil { return nil, err } From 2e372ea6cc92083b1740353a6bbfa20077fb5e4b Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 24 Oct 2016 18:01:37 +0200 Subject: [PATCH 2070/2266] Use logger when dry processing payload functions --- core/handler/dry_run.go | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go index 2eb8d6efd..59e4e55a0 100644 --- a/core/handler/dry_run.go +++ b/core/handler/dry_run.go @@ -17,6 +17,8 @@ import ( func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) (*pb.DryUplinkResult, error) { app := in.App + console := &console{} + flds := "" valid := true if app != nil && app.Decoder != "" { @@ -24,6 +26,7 @@ func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) Decoder: app.Decoder, Converter: app.Converter, Validator: app.Validator, + Logger: console, } fields, val, err := functions.Process(in.Payload, uint8(in.Port)) @@ -41,10 +44,20 @@ func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) flds = string(marshalled) } + logs := make([]string, len(console.Logs)) + for i, line := range console.Logs { + enc, err := json.Marshal(line) + if err != nil { + return nil, err + } + logs[i] = string(enc) + } + return &pb.DryUplinkResult{ Payload: in.Payload, Fields: flds, Valid: valid, + Logs: logs, }, nil } @@ -71,8 +84,11 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess return nil, errors.NewErrInvalidArgument("Encoder", "Not specified") } + console := &console{} + functions := &DownlinkFunctions{ Encoder: app.Encoder, + Logger: console, } var parsed map[string]interface{} @@ -86,7 +102,17 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess return nil, err } + logs := make([]string, len(console.Logs)) + for i, line := range console.Logs { + enc, err := json.Marshal(line) + if err != nil { + return nil, err + } + logs[i] = string(enc) + } + return &pb.DryDownlinkResult{ Payload: payload, + Logs: logs, }, nil } From 209969123ad25a1bb4b688ac182e8fb6ffb75048 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 11:26:42 +0200 Subject: [PATCH 2071/2266] Make log entries more structured --- api/handler/handler.pb.go | 411 ++++++++++++++++++++++++++++---------- api/handler/handler.proto | 17 +- 2 files changed, 311 insertions(+), 117 deletions(-) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 405e15095..7e367a302 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -19,6 +19,7 @@ DeviceList DryDownlinkMessage DryUplinkMessage + LogEntry DryUplinkResult DryDownlinkResult */ @@ -315,27 +316,51 @@ func (m *DryUplinkMessage) GetApp() *Application { return nil } +type LogEntry struct { + Function string `protobuf:"bytes,1,opt,name=function,proto3" json:"function,omitempty"` + Fields []string `protobuf:"bytes,2,rep,name=fields" json:"fields,omitempty"` +} + +func (m *LogEntry) Reset() { *m = LogEntry{} } +func (m *LogEntry) String() string { return proto.CompactTextString(m) } +func (*LogEntry) ProtoMessage() {} +func (*LogEntry) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{10} } + type DryUplinkResult struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` - Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` - Logs []string `protobuf:"bytes,4,rep,name=logs" json:"logs,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` + Logs []*LogEntry `protobuf:"bytes,4,rep,name=logs" json:"logs,omitempty"` } func (m *DryUplinkResult) Reset() { *m = DryUplinkResult{} } func (m *DryUplinkResult) String() string { return proto.CompactTextString(m) } func (*DryUplinkResult) ProtoMessage() {} -func (*DryUplinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{10} } +func (*DryUplinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{11} } + +func (m *DryUplinkResult) GetLogs() []*LogEntry { + if m != nil { + return m.Logs + } + return nil +} type DryDownlinkResult struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - Logs []string `protobuf:"bytes,2,rep,name=logs" json:"logs,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Logs []*LogEntry `protobuf:"bytes,2,rep,name=logs" json:"logs,omitempty"` } func (m *DryDownlinkResult) Reset() { *m = DryDownlinkResult{} } func (m *DryDownlinkResult) String() string { return proto.CompactTextString(m) } func (*DryDownlinkResult) ProtoMessage() {} -func (*DryDownlinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{11} } +func (*DryDownlinkResult) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{12} } + +func (m *DryDownlinkResult) GetLogs() []*LogEntry { + if m != nil { + return m.Logs + } + return nil +} func init() { proto.RegisterType((*DeviceActivationResponse)(nil), "handler.DeviceActivationResponse") @@ -348,6 +373,7 @@ func init() { proto.RegisterType((*DeviceList)(nil), "handler.DeviceList") proto.RegisterType((*DryDownlinkMessage)(nil), "handler.DryDownlinkMessage") proto.RegisterType((*DryUplinkMessage)(nil), "handler.DryUplinkMessage") + proto.RegisterType((*LogEntry)(nil), "handler.LogEntry") proto.RegisterType((*DryUplinkResult)(nil), "handler.DryUplinkResult") proto.RegisterType((*DryDownlinkResult)(nil), "handler.DryDownlinkResult") } @@ -1287,6 +1313,45 @@ func (m *DryUplinkMessage) MarshalTo(data []byte) (int, error) { return i, nil } +func (m *LogEntry) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *LogEntry) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Function) > 0 { + data[i] = 0xa + i++ + i = encodeVarintHandler(data, i, uint64(len(m.Function))) + i += copy(data[i:], m.Function) + } + if len(m.Fields) > 0 { + for _, s := range m.Fields { + data[i] = 0x12 + i++ + l = len(s) + for l >= 1<<7 { + data[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + data[i] = uint8(l) + i++ + i += copy(data[i:], s) + } + } + return i, nil +} + func (m *DryUplinkResult) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) @@ -1325,18 +1390,15 @@ func (m *DryUplinkResult) MarshalTo(data []byte) (int, error) { i++ } if len(m.Logs) > 0 { - for _, s := range m.Logs { + for _, msg := range m.Logs { data[i] = 0x22 i++ - l = len(s) - for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ + i = encodeVarintHandler(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err } - data[i] = uint8(l) - i++ - i += copy(data[i:], s) + i += n } } return i, nil @@ -1364,18 +1426,15 @@ func (m *DryDownlinkResult) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.Payload) } if len(m.Logs) > 0 { - for _, s := range m.Logs { + for _, msg := range m.Logs { data[i] = 0x12 i++ - l = len(s) - for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) - l >>= 7 - i++ + i = encodeVarintHandler(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err } - data[i] = uint8(l) - i++ - i += copy(data[i:], s) + i += n } } return i, nil @@ -1588,6 +1647,22 @@ func (m *DryUplinkMessage) Size() (n int) { return n } +func (m *LogEntry) Size() (n int) { + var l int + _ = l + l = len(m.Function) + if l > 0 { + n += 1 + l + sovHandler(uint64(l)) + } + if len(m.Fields) > 0 { + for _, s := range m.Fields { + l = len(s) + n += 1 + l + sovHandler(uint64(l)) + } + } + return n +} + func (m *DryUplinkResult) Size() (n int) { var l int _ = l @@ -1603,8 +1678,8 @@ func (m *DryUplinkResult) Size() (n int) { n += 2 } if len(m.Logs) > 0 { - for _, s := range m.Logs { - l = len(s) + for _, e := range m.Logs { + l = e.Size() n += 1 + l + sovHandler(uint64(l)) } } @@ -1619,8 +1694,8 @@ func (m *DryDownlinkResult) Size() (n int) { n += 1 + l + sovHandler(uint64(l)) } if len(m.Logs) > 0 { - for _, s := range m.Logs { - l = len(s) + for _, e := range m.Logs { + l = e.Size() n += 1 + l + sovHandler(uint64(l)) } } @@ -2979,6 +3054,114 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { } return nil } +func (m *LogEntry) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: LogEntry: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: LogEntry: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Function", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Function = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Fields", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowHandler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthHandler + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Fields = append(m.Fields, string(data[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipHandler(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthHandler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *DryUplinkResult) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -3092,7 +3275,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -3102,20 +3285,22 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthHandler } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.Logs = append(m.Logs, string(data[iNdEx:postIndex])) + m.Logs = append(m.Logs, &LogEntry{}) + if err := m.Logs[len(m.Logs)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -3202,7 +3387,7 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Logs", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -3212,20 +3397,22 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthHandler } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.Logs = append(m.Logs, string(data[iNdEx:postIndex])) + m.Logs = append(m.Logs, &LogEntry{}) + if err := m.Logs[len(m.Logs)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex default: iNdEx = preIndex @@ -3358,73 +3545,75 @@ func init() { } var fileDescriptorHandler = []byte{ - // 1074 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x5d, 0x6f, 0xdc, 0x44, - 0x17, 0xae, 0x93, 0x66, 0x93, 0x3d, 0x9b, 0xcf, 0x49, 0x9b, 0xd7, 0xef, 0x26, 0x5a, 0xb6, 0x53, - 0x11, 0xd2, 0x14, 0xd9, 0x62, 0x41, 0x02, 0x2a, 0x21, 0x9a, 0x36, 0xa4, 0x8d, 0x44, 0x40, 0x38, - 0x45, 0x48, 0xb9, 0x20, 0x9a, 0xac, 0x4f, 0x76, 0xad, 0x78, 0x3d, 0xc6, 0x33, 0xbb, 0x51, 0xa8, - 0x7a, 0xd3, 0x7b, 0xae, 0xe0, 0x82, 0x5f, 0xc0, 0x3f, 0x41, 0xe2, 0x12, 0x89, 0x4b, 0x2e, 0x40, - 0x81, 0x1f, 0x82, 0x3c, 0x33, 0xf6, 0x3a, 0xfb, 0x91, 0x0f, 0xc4, 0x95, 0x7d, 0x3e, 0xfc, 0x9c, - 0x79, 0xce, 0x99, 0x79, 0xc6, 0xf0, 0x61, 0x2b, 0x90, 0xed, 0xee, 0x91, 0xd3, 0xe4, 0x1d, 0xf7, - 0x45, 0x1b, 0x5f, 0xb4, 0x83, 0xa8, 0x25, 0x3e, 0x43, 0x79, 0xca, 0x93, 0x13, 0x57, 0xca, 0xc8, - 0x65, 0x71, 0xe0, 0xb6, 0x59, 0xe4, 0x87, 0x98, 0x64, 0x4f, 0x27, 0x4e, 0xb8, 0xe4, 0x64, 0xda, - 0x98, 0xd5, 0xd5, 0x16, 0xe7, 0xad, 0x10, 0x5d, 0xe5, 0x3e, 0xea, 0x1e, 0xbb, 0xd8, 0x89, 0xe5, - 0x99, 0xce, 0xaa, 0xae, 0x99, 0x60, 0x8a, 0xc3, 0xa2, 0x88, 0x4b, 0x26, 0x03, 0x1e, 0x09, 0x13, - 0x5d, 0xca, 0x4a, 0xb0, 0x38, 0x30, 0xae, 0xd5, 0xcc, 0x75, 0x94, 0xf0, 0x13, 0x4c, 0xcc, 0xc3, - 0x04, 0xdf, 0xc8, 0x82, 0xca, 0x6c, 0xf2, 0x30, 0x7f, 0x31, 0x09, 0x6f, 0x0e, 0x25, 0x84, 0x3c, - 0x61, 0xa7, 0x2c, 0x72, 0x7d, 0xec, 0x05, 0x4d, 0xd4, 0x69, 0xf4, 0x77, 0x0b, 0xec, 0x6d, 0xe5, - 0xd8, 0x6a, 0xca, 0xa0, 0xa7, 0xd6, 0xe4, 0xa1, 0x88, 0x79, 0x24, 0x90, 0xd8, 0x30, 0x1d, 0xb3, - 0xb3, 0x90, 0x33, 0xdf, 0xb6, 0xea, 0xd6, 0xc6, 0xac, 0x97, 0x99, 0xe4, 0x2e, 0x94, 0x58, 0x1c, - 0x1f, 0x06, 0xbe, 0x3d, 0x51, 0xb7, 0x36, 0xca, 0xde, 0x14, 0x8b, 0xe3, 0x5d, 0x9f, 0x7c, 0x0c, - 0x0b, 0x3e, 0x3f, 0x8d, 0xc2, 0x20, 0x3a, 0x39, 0xe4, 0x71, 0x8a, 0x65, 0x57, 0xea, 0xd6, 0x46, - 0xa5, 0xb1, 0xe2, 0x98, 0xd5, 0x6f, 0x9b, 0xf0, 0xe7, 0x2a, 0xea, 0xcd, 0xfb, 0x17, 0x6c, 0xb2, - 0x07, 0xcb, 0x2c, 0x5f, 0xc7, 0x61, 0x07, 0x25, 0xf3, 0x99, 0x64, 0xf6, 0xff, 0x14, 0xc8, 0x9a, - 0x93, 0x73, 0xec, 0x2f, 0x76, 0xcf, 0xe4, 0x78, 0x84, 0x0d, 0xf9, 0xe8, 0x02, 0xcc, 0xed, 0x4b, - 0x26, 0xbb, 0xc2, 0xc3, 0x6f, 0xba, 0x28, 0x24, 0xfd, 0xc3, 0x82, 0x92, 0xf6, 0x90, 0x0d, 0x28, - 0x89, 0x33, 0x21, 0xb1, 0xa3, 0xb8, 0x55, 0x1a, 0x8b, 0x4e, 0xda, 0xfa, 0x7d, 0xe5, 0x4a, 0x53, - 0x84, 0x67, 0xe2, 0xe4, 0x1d, 0x28, 0x37, 0x79, 0x27, 0xe6, 0x11, 0x46, 0x52, 0xf1, 0xad, 0x34, - 0x96, 0x55, 0xf2, 0xd3, 0xcc, 0xab, 0xf3, 0xfb, 0x59, 0x84, 0x42, 0xa9, 0x1b, 0xa7, 0xbc, 0x0c, - 0x7f, 0x50, 0xf9, 0x1e, 0x93, 0x28, 0x3c, 0x13, 0x21, 0xeb, 0x30, 0x93, 0xb1, 0xb7, 0x67, 0x87, - 0xb2, 0xf2, 0x18, 0x79, 0x1b, 0x2a, 0x7d, 0x6a, 0xc2, 0x9e, 0x1b, 0x4a, 0x2d, 0x86, 0xa9, 0x03, - 0x77, 0xb7, 0xe2, 0x38, 0x0c, 0x9a, 0xca, 0xde, 0xf5, 0x31, 0x92, 0xc1, 0x71, 0x80, 0x49, 0x61, - 0x64, 0x56, 0x61, 0x64, 0xf4, 0x07, 0x0b, 0x2a, 0x85, 0x0f, 0xc6, 0xa4, 0xa5, 0x5b, 0xc1, 0xc7, - 0x26, 0xf7, 0x31, 0x31, 0x13, 0xcf, 0x4c, 0xb2, 0x96, 0x76, 0x27, 0xea, 0x61, 0x22, 0x31, 0xb1, - 0x27, 0x55, 0xac, 0xef, 0x48, 0xa3, 0x3d, 0x16, 0x06, 0x3e, 0x93, 0x3c, 0xb1, 0x6f, 0xeb, 0x68, - 0xee, 0x48, 0x51, 0x31, 0xd2, 0xa8, 0x53, 0x1a, 0xd5, 0x98, 0xf4, 0x31, 0x2c, 0xea, 0x6d, 0x79, - 0x25, 0x83, 0xd4, 0xed, 0x63, 0xaf, 0xb0, 0x17, 0x7d, 0xec, 0xed, 0xfa, 0xf4, 0x5b, 0x28, 0x69, - 0x84, 0x9b, 0x7d, 0x47, 0x3e, 0x80, 0x79, 0x73, 0x52, 0x0e, 0xf5, 0x49, 0x51, 0xa4, 0x2a, 0x8d, - 0x05, 0xc7, 0xb8, 0x1d, 0x0d, 0xfb, 0xfc, 0x96, 0x37, 0x67, 0x3c, 0xda, 0xf1, 0x64, 0x46, 0x01, - 0x06, 0x4d, 0xa4, 0xef, 0x03, 0x68, 0xdf, 0xa7, 0x81, 0x90, 0xe4, 0x41, 0xda, 0xbb, 0xd4, 0x12, - 0xb6, 0x55, 0x9f, 0x54, 0x50, 0x99, 0x80, 0xe8, 0x2c, 0x2f, 0x8b, 0xd3, 0xd7, 0x16, 0x90, 0xed, - 0xe4, 0x2c, 0x3b, 0x25, 0x7b, 0x28, 0x04, 0x6b, 0x5d, 0x76, 0x10, 0x57, 0xa0, 0x74, 0x1c, 0x60, - 0xe8, 0x0b, 0x43, 0xc2, 0x58, 0x64, 0x1d, 0x26, 0x59, 0x1c, 0x9b, 0xa5, 0xdf, 0xc9, 0xeb, 0x15, - 0x26, 0xed, 0xa5, 0x09, 0x84, 0xc0, 0xed, 0x98, 0x27, 0x52, 0x8d, 0x66, 0xce, 0x53, 0xef, 0xb4, - 0x0d, 0x8b, 0xdb, 0xc9, 0xd9, 0x97, 0xf1, 0xf5, 0x56, 0x60, 0x2a, 0x4d, 0x5c, 0xb7, 0xd2, 0x64, - 0xa1, 0x52, 0x07, 0x16, 0xf2, 0x4a, 0x1e, 0x8a, 0x6e, 0x28, 0xff, 0x05, 0xd5, 0x3b, 0x30, 0xa5, - 0x76, 0x94, 0x42, 0x9e, 0xf1, 0xb4, 0x91, 0x96, 0x0b, 0x79, 0x4b, 0xd8, 0xb7, 0xeb, 0x93, 0x1b, - 0x65, 0x4f, 0xbd, 0xd3, 0x2d, 0x58, 0x2a, 0x34, 0xf7, 0xca, 0x82, 0x19, 0xc4, 0x44, 0x1f, 0xa2, - 0xf1, 0xb3, 0x05, 0xd3, 0xcf, 0x35, 0x45, 0xf2, 0x35, 0x2c, 0xf7, 0x75, 0xe8, 0x69, 0x9b, 0x85, - 0x21, 0x46, 0x2d, 0x24, 0x34, 0xd3, 0xba, 0x11, 0x41, 0xa3, 0x43, 0xd5, 0xfb, 0x97, 0xe6, 0x18, - 0xf9, 0x3d, 0x80, 0x19, 0x13, 0x46, 0xf2, 0x30, 0x17, 0x50, 0xf4, 0xbb, 0xba, 0xb3, 0xe8, 0x0f, - 0x0b, 0xb7, 0x46, 0xbf, 0x37, 0xb0, 0xbf, 0x86, 0xa5, 0xbd, 0xf1, 0xd3, 0x0c, 0x90, 0xc2, 0x88, - 0xf6, 0x58, 0xc4, 0x5a, 0x98, 0x90, 0x16, 0x2c, 0x7b, 0xd8, 0x0a, 0x84, 0xc4, 0xa4, 0x28, 0x0a, - 0xb5, 0x51, 0x63, 0xed, 0x9f, 0xcc, 0xea, 0x8a, 0xa3, 0x2f, 0x37, 0x27, 0xbb, 0xf9, 0x9c, 0x4f, - 0xd2, 0x9b, 0x8f, 0xda, 0xaf, 0x7f, 0xfb, 0xfb, 0xfb, 0x09, 0x42, 0xe7, 0x5c, 0xd6, 0xff, 0x4e, - 0x3c, 0xb2, 0x36, 0xc9, 0x31, 0xcc, 0x3f, 0x43, 0x79, 0x93, 0x1a, 0x23, 0xb7, 0x16, 0xad, 0xa9, - 0x0a, 0x36, 0x59, 0xb9, 0x50, 0xc1, 0x7d, 0xa9, 0x0f, 0xfc, 0x2b, 0xc2, 0x60, 0x7e, 0xff, 0x62, - 0x9d, 0x91, 0x38, 0x63, 0x19, 0xdc, 0x53, 0xf8, 0xab, 0x74, 0x0c, 0x7e, 0x4a, 0xe5, 0x04, 0x96, - 0xb6, 0x31, 0x44, 0x89, 0xff, 0x45, 0xc7, 0x0c, 0x9f, 0xcd, 0x71, 0x7c, 0xda, 0x50, 0x7e, 0x86, - 0xd2, 0x08, 0xdb, 0xff, 0x07, 0xe6, 0x5c, 0xc0, 0x1f, 0x94, 0x18, 0xea, 0x2a, 0xe0, 0x07, 0xe4, - 0xad, 0xd1, 0xc0, 0xe6, 0xaf, 0x40, 0xb8, 0x2f, 0xb5, 0x26, 0xbe, 0x22, 0xdf, 0x59, 0x50, 0xde, - 0xcf, 0x4b, 0x0d, 0xe2, 0x8d, 0x25, 0xf0, 0x95, 0xaa, 0xf3, 0x05, 0xbd, 0x6e, 0x9d, 0x47, 0xd6, - 0xe6, 0xc1, 0x7d, 0x5a, 0xbb, 0x3c, 0x3b, 0x6d, 0x73, 0x02, 0xb3, 0xba, 0xcd, 0x57, 0x93, 0x1f, - 0xb7, 0x36, 0xd3, 0x83, 0xcd, 0x6b, 0xf7, 0xe0, 0x14, 0xec, 0xbc, 0xdb, 0x62, 0x87, 0xdf, 0xe8, - 0x4c, 0x2c, 0x0f, 0xac, 0x2f, 0xbd, 0x0a, 0xe8, 0xba, 0x5a, 0x41, 0x9d, 0x5c, 0xc1, 0x97, 0xec, - 0x40, 0xa5, 0xa0, 0x54, 0x64, 0xb5, 0x8f, 0x35, 0x74, 0x39, 0x54, 0xab, 0xa3, 0x82, 0x46, 0xdc, - 0x1e, 0x43, 0x39, 0x17, 0xd8, 0x62, 0xc7, 0x06, 0xe4, 0xbd, 0x6a, 0x0f, 0x87, 0x34, 0x42, 0x63, - 0x07, 0xe6, 0x8d, 0xde, 0x65, 0x1a, 0xf1, 0x9e, 0xda, 0x82, 0xe6, 0x2f, 0x6a, 0x25, 0xff, 0xf0, - 0xc2, 0x8f, 0x56, 0x61, 0xff, 0x69, 0xff, 0x93, 0x8f, 0x7e, 0x39, 0xaf, 0x59, 0xbf, 0x9e, 0xd7, - 0xac, 0x3f, 0xcf, 0x6b, 0xd6, 0x8f, 0x7f, 0xd5, 0x6e, 0x1d, 0x3c, 0xbc, 0xc1, 0x1f, 0xf7, 0x51, - 0x49, 0x8d, 0xf2, 0xdd, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x6f, 0x1e, 0xf8, 0xa7, 0x0b, - 0x00, 0x00, + // 1109 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6f, 0xdc, 0x44, + 0x10, 0xaf, 0x93, 0xf4, 0x7a, 0x99, 0xcb, 0xe7, 0xa6, 0x0d, 0xe6, 0x12, 0x1d, 0xe9, 0x56, 0x0d, + 0x69, 0x8a, 0xce, 0xe2, 0x40, 0x02, 0x2a, 0x01, 0xfd, 0x48, 0xd3, 0x46, 0x6a, 0x40, 0x38, 0x41, + 0x48, 0x79, 0x20, 0xda, 0x9c, 0x27, 0x3e, 0x2b, 0x8e, 0xd7, 0x78, 0xf7, 0x2e, 0x3a, 0xaa, 0x4a, + 0xa8, 0xef, 0x3c, 0xc1, 0x03, 0x7f, 0x01, 0xff, 0x09, 0x12, 0x8f, 0x48, 0x3c, 0xf2, 0x00, 0x0a, + 0xfc, 0x21, 0xc8, 0xbb, 0x6b, 0x9f, 0x73, 0x1f, 0xf9, 0x40, 0x3c, 0xdd, 0xcd, 0xfc, 0xc6, 0xbf, + 0x99, 0xdf, 0xcc, 0x7a, 0xd6, 0xf0, 0x91, 0x1f, 0xc8, 0x56, 0xfb, 0xa0, 0xde, 0xe4, 0xc7, 0xce, + 0x6e, 0x0b, 0x77, 0x5b, 0x41, 0xe4, 0x8b, 0xcf, 0x50, 0x9e, 0xf0, 0xe4, 0xc8, 0x91, 0x32, 0x72, + 0x58, 0x1c, 0x38, 0x2d, 0x16, 0x79, 0x21, 0x26, 0xd9, 0x6f, 0x3d, 0x4e, 0xb8, 0xe4, 0xe4, 0x86, + 0x31, 0xab, 0x4b, 0x3e, 0xe7, 0x7e, 0x88, 0x8e, 0x72, 0x1f, 0xb4, 0x0f, 0x1d, 0x3c, 0x8e, 0x65, + 0x57, 0x47, 0x55, 0x97, 0x0d, 0x98, 0xf2, 0xb0, 0x28, 0xe2, 0x92, 0xc9, 0x80, 0x47, 0xc2, 0xa0, + 0xf3, 0x59, 0x0a, 0x16, 0x07, 0xc6, 0xb5, 0x94, 0xb9, 0x0e, 0x12, 0x7e, 0x84, 0x89, 0xf9, 0x31, + 0xe0, 0x5b, 0x19, 0xa8, 0xcc, 0x26, 0x0f, 0xf3, 0x3f, 0x26, 0xe0, 0xee, 0x40, 0x40, 0xc8, 0x13, + 0x76, 0xc2, 0x22, 0xc7, 0xc3, 0x4e, 0xd0, 0x44, 0x1d, 0x46, 0xff, 0xb0, 0xc0, 0xde, 0x50, 0x8e, + 0x47, 0x4d, 0x19, 0x74, 0x54, 0x4d, 0x2e, 0x8a, 0x98, 0x47, 0x02, 0x89, 0x0d, 0x37, 0x62, 0xd6, + 0x0d, 0x39, 0xf3, 0x6c, 0x6b, 0xc5, 0x5a, 0x9b, 0x72, 0x33, 0x93, 0xdc, 0x82, 0x12, 0x8b, 0xe3, + 0xfd, 0xc0, 0xb3, 0xc7, 0x56, 0xac, 0xb5, 0x49, 0xf7, 0x3a, 0x8b, 0xe3, 0x2d, 0x8f, 0x7c, 0x0a, + 0xb3, 0x1e, 0x3f, 0x89, 0xc2, 0x20, 0x3a, 0xda, 0xe7, 0x71, 0xca, 0x65, 0x57, 0x56, 0xac, 0xb5, + 0x4a, 0x63, 0xb1, 0x6e, 0xaa, 0xdf, 0x30, 0xf0, 0xe7, 0x0a, 0x75, 0x67, 0xbc, 0x33, 0x36, 0xd9, + 0x86, 0x05, 0x96, 0xd7, 0xb1, 0x7f, 0x8c, 0x92, 0x79, 0x4c, 0x32, 0xfb, 0x0d, 0x45, 0xb2, 0x5c, + 0xcf, 0x35, 0xf6, 0x8a, 0xdd, 0x36, 0x31, 0x2e, 0x61, 0x03, 0x3e, 0x3a, 0x0b, 0xd3, 0x3b, 0x92, + 0xc9, 0xb6, 0x70, 0xf1, 0x9b, 0x36, 0x0a, 0x49, 0xff, 0xb4, 0xa0, 0xa4, 0x3d, 0x64, 0x0d, 0x4a, + 0xa2, 0x2b, 0x24, 0x1e, 0x2b, 0x6d, 0x95, 0xc6, 0x5c, 0x3d, 0x6d, 0xfd, 0x8e, 0x72, 0xa5, 0x21, + 0xc2, 0x35, 0x38, 0x79, 0x17, 0x26, 0x9b, 0xfc, 0x38, 0xe6, 0x11, 0x46, 0x52, 0xe9, 0xad, 0x34, + 0x16, 0x54, 0xf0, 0x93, 0xcc, 0xab, 0xe3, 0x7b, 0x51, 0x84, 0x42, 0xa9, 0x1d, 0xa7, 0xba, 0x8c, + 0x7e, 0x50, 0xf1, 0x2e, 0x93, 0x28, 0x5c, 0x83, 0x90, 0x55, 0x28, 0x67, 0xea, 0xed, 0xa9, 0x81, + 0xa8, 0x1c, 0x23, 0xef, 0x40, 0xa5, 0x27, 0x4d, 0xd8, 0xd3, 0x03, 0xa1, 0x45, 0x98, 0xd6, 0xe1, + 0xd6, 0xa3, 0x38, 0x0e, 0x83, 0xa6, 0xb2, 0xb7, 0x3c, 0x8c, 0x64, 0x70, 0x18, 0x60, 0x52, 0x18, + 0x99, 0x55, 0x18, 0x19, 0xfd, 0xd1, 0x82, 0x4a, 0xe1, 0x81, 0x11, 0x61, 0xe9, 0x51, 0xf0, 0xb0, + 0xc9, 0x3d, 0x4c, 0xcc, 0xc4, 0x33, 0x93, 0x2c, 0xa7, 0xdd, 0x89, 0x3a, 0x98, 0x48, 0x4c, 0xec, + 0x71, 0x85, 0xf5, 0x1c, 0x29, 0xda, 0x61, 0x61, 0xe0, 0x31, 0xc9, 0x13, 0x7b, 0x42, 0xa3, 0xb9, + 0x23, 0x65, 0xc5, 0x48, 0xb3, 0x5e, 0xd7, 0xac, 0xc6, 0xa4, 0x0f, 0x61, 0x4e, 0x1f, 0xcb, 0x0b, + 0x15, 0xa4, 0x6e, 0x0f, 0x3b, 0x85, 0xb3, 0xe8, 0x61, 0x67, 0xcb, 0xa3, 0xdf, 0x42, 0x49, 0x33, + 0x5c, 0xed, 0x39, 0xf2, 0x21, 0xcc, 0x98, 0x37, 0x65, 0x5f, 0xbf, 0x29, 0x4a, 0x54, 0xa5, 0x31, + 0x5b, 0x37, 0xee, 0xba, 0xa6, 0x7d, 0x7e, 0xcd, 0x9d, 0x36, 0x1e, 0xed, 0x78, 0x5c, 0x56, 0x84, + 0x41, 0x13, 0xe9, 0x07, 0x00, 0xda, 0xf7, 0x22, 0x10, 0x92, 0xdc, 0x4b, 0x7b, 0x97, 0x5a, 0xc2, + 0xb6, 0x56, 0xc6, 0x15, 0x55, 0xb6, 0x40, 0x74, 0x94, 0x9b, 0xe1, 0xf4, 0xb5, 0x05, 0x64, 0x23, + 0xe9, 0x66, 0x6f, 0xc9, 0x36, 0x0a, 0xc1, 0xfc, 0xf3, 0x5e, 0xc4, 0x45, 0x28, 0x1d, 0x06, 0x18, + 0x7a, 0xc2, 0x88, 0x30, 0x16, 0x59, 0x85, 0x71, 0x16, 0xc7, 0xa6, 0xf4, 0x9b, 0x79, 0xbe, 0xc2, + 0xa4, 0xdd, 0x34, 0x80, 0x10, 0x98, 0x88, 0x79, 0x22, 0xd5, 0x68, 0xa6, 0x5d, 0xf5, 0x9f, 0xb6, + 0x60, 0x6e, 0x23, 0xe9, 0x7e, 0x19, 0x5f, 0xae, 0x02, 0x93, 0x69, 0xec, 0xb2, 0x99, 0xc6, 0x0b, + 0x99, 0x3e, 0x81, 0xf2, 0x0b, 0xee, 0x3f, 0x8d, 0x64, 0xd2, 0x25, 0x55, 0x28, 0x1f, 0xb6, 0xa3, + 0xa6, 0x5a, 0x1a, 0x7a, 0x4e, 0xb9, 0x7d, 0x46, 0xe5, 0x78, 0x4f, 0x25, 0xfd, 0xce, 0x82, 0xd9, + 0xbc, 0x54, 0x17, 0x45, 0x3b, 0x94, 0xff, 0xa1, 0x57, 0x37, 0xe1, 0xba, 0x3a, 0x92, 0xaa, 0xb4, + 0xb2, 0xab, 0x0d, 0x72, 0x17, 0x26, 0x42, 0xee, 0x0b, 0x7b, 0x42, 0x8d, 0x6c, 0x3e, 0x17, 0x96, + 0x15, 0xec, 0x2a, 0x98, 0xee, 0xc2, 0x7c, 0x61, 0x60, 0x17, 0xd6, 0x90, 0xb1, 0x8e, 0x9d, 0xcb, + 0xda, 0xf8, 0xc5, 0x82, 0x1b, 0xcf, 0x35, 0x44, 0xbe, 0x86, 0x85, 0xde, 0xba, 0x7b, 0xd2, 0x62, + 0x61, 0x88, 0x91, 0x8f, 0x84, 0x66, 0x2b, 0x75, 0x08, 0x68, 0xd6, 0x5d, 0xf5, 0xce, 0xb9, 0x31, + 0x66, 0xcb, 0xef, 0x41, 0xd9, 0xc0, 0x48, 0xee, 0xe7, 0x7b, 0x1a, 0xbd, 0xb6, 0x1e, 0x20, 0x7a, + 0x83, 0xf7, 0x83, 0x66, 0xbf, 0xdd, 0x77, 0x8c, 0x07, 0x6f, 0x90, 0xc6, 0xcf, 0x65, 0x20, 0x85, + 0x93, 0xb0, 0xcd, 0x22, 0xe6, 0x63, 0x42, 0x7c, 0x58, 0x70, 0xd1, 0x0f, 0x84, 0xc4, 0xa4, 0xb8, + 0x7b, 0x6a, 0xc3, 0x4e, 0x4f, 0x6f, 0x01, 0x54, 0x17, 0xeb, 0xfa, 0x0e, 0xad, 0x67, 0x17, 0x6c, + 0xfd, 0x69, 0x7a, 0xc1, 0x52, 0xfb, 0xf5, 0xef, 0xff, 0xfc, 0x30, 0x46, 0xe8, 0xb4, 0xc3, 0x7a, + 0xcf, 0x89, 0x07, 0xd6, 0x3a, 0x39, 0x84, 0x99, 0x67, 0x28, 0xaf, 0x92, 0x63, 0xe8, 0x09, 0xa6, + 0x35, 0x95, 0xc1, 0x26, 0x8b, 0x67, 0x32, 0x38, 0x2f, 0xf5, 0x5e, 0x79, 0x45, 0x18, 0xcc, 0xec, + 0x9c, 0xcd, 0x33, 0x94, 0x67, 0xa4, 0x82, 0xdb, 0x8a, 0x7f, 0x89, 0x8e, 0xe0, 0x4f, 0xa5, 0x1c, + 0xc1, 0xfc, 0x06, 0x86, 0x28, 0xf1, 0xff, 0xe8, 0x98, 0xd1, 0xb3, 0x3e, 0x4a, 0x4f, 0x0b, 0x26, + 0x9f, 0xa1, 0x34, 0xfb, 0xf3, 0xcd, 0xbe, 0x39, 0x17, 0xf8, 0xfb, 0x37, 0x19, 0x75, 0x14, 0xf1, + 0x3d, 0xf2, 0xf6, 0x70, 0x62, 0xf3, 0xf1, 0x21, 0x9c, 0x97, 0x7a, 0xf5, 0xbe, 0x22, 0xdf, 0x5b, + 0x30, 0xb9, 0x93, 0xa7, 0xea, 0xe7, 0x1b, 0x29, 0xe0, 0x2b, 0x95, 0xe7, 0x0b, 0x7a, 0xd9, 0x3c, + 0x0f, 0xac, 0xf5, 0xbd, 0x3b, 0xb4, 0x76, 0x7e, 0x74, 0xda, 0xe6, 0x04, 0xa6, 0x74, 0x9b, 0x2f, + 0x16, 0x3f, 0xaa, 0x36, 0xd3, 0x83, 0xf5, 0x4b, 0xf7, 0xe0, 0x04, 0xec, 0xbc, 0xdb, 0x62, 0x93, + 0x5f, 0xe9, 0x9d, 0x58, 0xe8, 0xab, 0x2f, 0xbd, 0x71, 0xe8, 0xaa, 0xaa, 0x60, 0x85, 0x5c, 0xa0, + 0x97, 0x6c, 0x42, 0xa5, 0xb0, 0xbc, 0xc8, 0x52, 0x8f, 0x6b, 0xe0, 0x0e, 0xaa, 0x56, 0x87, 0x81, + 0x66, 0xdf, 0x3d, 0x84, 0xc9, 0x7c, 0x0d, 0x17, 0x3b, 0xd6, 0x77, 0x8b, 0x54, 0xed, 0x41, 0x48, + 0x33, 0x34, 0x36, 0x61, 0xc6, 0xec, 0xbb, 0x6c, 0x47, 0xbc, 0xaf, 0x8e, 0xa0, 0xf9, 0x58, 0x5b, + 0xcc, 0x1f, 0x3c, 0xf3, 0x3d, 0x57, 0x38, 0x7f, 0xda, 0xff, 0xf8, 0xe3, 0x5f, 0x4f, 0x6b, 0xd6, + 0x6f, 0xa7, 0x35, 0xeb, 0xaf, 0xd3, 0x9a, 0xf5, 0xd3, 0xdf, 0xb5, 0x6b, 0x7b, 0xf7, 0xaf, 0xf0, + 0x61, 0x7f, 0x50, 0x52, 0xa3, 0x7c, 0xef, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0x1e, 0x8b, + 0xa0, 0x0e, 0x0c, 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index e377e72fc..118c7574b 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -82,16 +82,21 @@ message DryUplinkMessage { uint32 port = 3; } +message LogEntry { + string function = 1; + repeated string fields = 2; +} + message DryUplinkResult { - bytes payload = 1; - string fields = 2; - bool valid = 3; - repeated string logs = 4; + bytes payload = 1; + string fields = 2; + bool valid = 3; + repeated LogEntry logs = 4; } message DryDownlinkResult { - bytes payload = 1; - repeated string logs = 2; + bytes payload = 1; + repeated LogEntry logs = 2; } service ApplicationManager { From f1a1d0c08bf5b838696bd98d9c4beaf44bba184a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 11:27:17 +0200 Subject: [PATCH 2072/2266] Move run_code into it's own package, with it's own tests --- core/handler/convert_fields.go | 62 +++++++++------- core/handler/dry_run.go | 31 ++------ core/handler/dry_run_test.go | 69 +++++++++++++++++- core/handler/functions/functions.go | 65 +++++++++++++++++ core/handler/functions/functions_test.go | 54 ++++++++++++++ core/handler/functions/logger.go | 66 +++++++++++++++++ core/handler/run_code.go | 93 ------------------------ 7 files changed, 294 insertions(+), 146 deletions(-) create mode 100644 core/handler/functions/functions.go create mode 100644 core/handler/functions/functions_test.go create mode 100644 core/handler/functions/logger.go delete mode 100644 core/handler/run_code.go diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index 45a83f0be..dbeb09790 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -9,10 +9,10 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/core/handler/functions" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" - "github.com/robertkrimen/otto" ) // ConvertFieldsUp converts the payload to fields using payload functions @@ -27,7 +27,7 @@ func (h *handler) ConvertFieldsUp(ctx log.Interface, ttnUp *pb_broker.Deduplicat Decoder: app.Decoder, Converter: app.Converter, Validator: app.Validator, - Logger: &ignore{}, + Logger: functions.Ignore, } fields, valid, err := functions.Process(appUp.PayloadRaw, appUp.FPort) @@ -57,7 +57,7 @@ type UplinkFunctions struct { Validator string // Logger is the logger that will be used to store logs - Logger Logger + Logger functions.Logger } // timeOut is the maximum allowed time a payload function is allowed to run @@ -69,10 +69,13 @@ func (f *UplinkFunctions) Decode(payload []byte, port uint8) (map[string]interfa return nil, errors.NewErrInternal("Decoder function not set") } - vm := otto.New() - vm.Set("payload", payload) - vm.Set("port", port) - value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(payload.slice(0), port)", f.Decoder), timeOut, f.Logger) + env := map[string]interface{}{ + "payload": payload, + "port": port, + } + code := fmt.Sprintf("(%s)(payload.slice(0), port)", f.Decoder) + + value, err := functions.RunCode("decoder", code, env, timeOut, f.Logger) if err != nil { return nil, err } @@ -92,15 +95,19 @@ func (f *UplinkFunctions) Decode(payload []byte, port uint8) (map[string]interfa // Convert converts the values in the specified map to a another map using the // Converter function. If the Converter function is not set, this function // returns the data as-is -func (f *UplinkFunctions) Convert(data map[string]interface{}, port uint8) (map[string]interface{}, error) { +func (f *UplinkFunctions) Convert(fields map[string]interface{}, port uint8) (map[string]interface{}, error) { if f.Converter == "" { - return data, nil + return fields, nil } - vm := otto.New() - vm.Set("data", data) - vm.Set("port", port) - value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(data, port)", f.Converter), timeOut, f.Logger) + env := map[string]interface{}{ + "fields": fields, + "port": port, + } + + code := fmt.Sprintf("(%s)(fields, port)", f.Converter) + + value, err := functions.RunCode("converter", code, env, timeOut, f.Logger) if err != nil { return nil, err } @@ -120,15 +127,18 @@ func (f *UplinkFunctions) Convert(data map[string]interface{}, port uint8) (map[ // Validate validates the values in the specified map using the Validator // function. If the Validator function is not set, this function returns true -func (f *UplinkFunctions) Validate(data map[string]interface{}, port uint8) (bool, error) { +func (f *UplinkFunctions) Validate(fields map[string]interface{}, port uint8) (bool, error) { if f.Validator == "" { return true, nil } - vm := otto.New() - vm.Set("data", data) - vm.Set("port", port) - value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(data, port)", f.Validator), timeOut, f.Logger) + env := map[string]interface{}{ + "fields": fields, + "port": port, + } + code := fmt.Sprintf("(%s)(fields, port)", f.Validator) + + value, err := functions.RunCode("valdator", code, env, timeOut, f.Logger) if err != nil { return false, err } @@ -156,8 +166,6 @@ func (f *UplinkFunctions) Process(payload []byte, port uint8) (map[string]interf return converted, valid, err } -var errTimeOutExceeded = errors.NewErrInternal("Code has been running to long") - // DownlinkFunctions encodes payload using JavaScript functions type DownlinkFunctions struct { // Encoder is a JavaScript function that accepts the payload as JSON and @@ -165,7 +173,7 @@ type DownlinkFunctions struct { Encoder string // Logger is the logger that will be used to store logs - Logger Logger + Logger functions.Logger } // Encode encodes the map into a byte slice using the encoder payload function @@ -175,10 +183,13 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}, port uint8) ( return nil, errors.NewErrInternal("Encoder function not set") } - vm := otto.New() - vm.Set("payload", payload) - vm.Set("port", port) - value, err := runUnsafeCodeWithLogger(vm, fmt.Sprintf("(%s)(payload, port)", f.Encoder), timeOut, f.Logger) + env := map[string]interface{}{ + "payload": payload, + "port": port, + } + code := fmt.Sprintf("(%s)(payload, port)", f.Encoder) + + value, err := functions.RunCode("encoder", code, env, timeOut, f.Logger) if err != nil { return nil, err } @@ -277,6 +288,7 @@ func (h *handler) ConvertFieldsDown(ctx log.Interface, appDown *types.DownlinkMe functions := &DownlinkFunctions{ Encoder: app.Encoder, + Logger: functions.Ignore, } message, _, err := functions.Process(appDown.PayloadFields, appDown.FPort) diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go index 59e4e55a0..1ba4b3c4c 100644 --- a/core/handler/dry_run.go +++ b/core/handler/dry_run.go @@ -7,6 +7,7 @@ import ( "encoding/json" pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/core/handler/functions" "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" ) @@ -17,7 +18,7 @@ import ( func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) (*pb.DryUplinkResult, error) { app := in.App - console := &console{} + logger := functions.NewEntryLogger() flds := "" valid := true @@ -26,7 +27,7 @@ func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) Decoder: app.Decoder, Converter: app.Converter, Validator: app.Validator, - Logger: console, + Logger: logger, } fields, val, err := functions.Process(in.Payload, uint8(in.Port)) @@ -44,20 +45,11 @@ func (h *handlerManager) DryUplink(ctx context.Context, in *pb.DryUplinkMessage) flds = string(marshalled) } - logs := make([]string, len(console.Logs)) - for i, line := range console.Logs { - enc, err := json.Marshal(line) - if err != nil { - return nil, err - } - logs[i] = string(enc) - } - return &pb.DryUplinkResult{ Payload: in.Payload, Fields: flds, Valid: valid, - Logs: logs, + Logs: logger.Logs, }, nil } @@ -84,11 +76,11 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess return nil, errors.NewErrInvalidArgument("Encoder", "Not specified") } - console := &console{} + logger := functions.NewEntryLogger() functions := &DownlinkFunctions{ Encoder: app.Encoder, - Logger: console, + Logger: logger, } var parsed map[string]interface{} @@ -102,17 +94,8 @@ func (h *handlerManager) DryDownlink(ctx context.Context, in *pb.DryDownlinkMess return nil, err } - logs := make([]string, len(console.Logs)) - for i, line := range console.Logs { - enc, err := json.Marshal(line) - if err != nil { - return nil, err - } - logs[i] = string(enc) - } - return &pb.DryDownlinkResult{ Payload: payload, - Logs: logs, + Logs: logger.Logs, }, nil } diff --git a/core/handler/dry_run_test.go b/core/handler/dry_run_test.go index 3a221dc54..7436eec4b 100644 --- a/core/handler/dry_run_test.go +++ b/core/handler/dry_run_test.go @@ -73,9 +73,14 @@ func TestDryUplinkFields(t *testing.T) { dryUplinkMessage := &pb.DryUplinkMessage{ Payload: []byte{11, 22, 33}, App: &pb.Application{ - AppId: "DryUplinkFields", - Decoder: `function (bytes) { return { length: bytes.length }}`, - Converter: `function (obj) { return obj }`, + AppId: "DryUplinkFields", + Decoder: `function (bytes) { + console.log("hi", 11) + return { length: bytes.length }}`, + Converter: `function (obj) { + console.log("foo") + return obj + }`, Validator: `function (bytes) { return true; }`, }, } @@ -86,6 +91,16 @@ func TestDryUplinkFields(t *testing.T) { a.So(res.Payload, ShouldResemble, dryUplinkMessage.Payload) a.So(res.Fields, ShouldEqual, `{"length":3}`) a.So(res.Valid, ShouldBeTrue) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ + &pb.LogEntry{ + Function: "decoder", + Fields: []string{`"hi"`, "11"}, + }, + &pb.LogEntry{ + Function: "converter", + Fields: []string{`"foo"`}, + }, + }) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) @@ -133,7 +148,11 @@ func TestDryDownlinkFields(t *testing.T) { msg := &pb.DryDownlinkMessage{ Fields: `{ "foo": [ 1, 2, 3 ] }`, App: &pb.Application{ - Encoder: `function (fields) { return fields.foo }`, + Encoder: ` + function (fields) { + console.log("hello", { foo: 33 }) + return fields.foo + }`, }, } @@ -141,6 +160,12 @@ func TestDryDownlinkFields(t *testing.T) { a.So(err, ShouldBeNil) a.So(res.Payload, ShouldResemble, []byte{1, 2, 3}) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ + &pb.LogEntry{ + Function: "encoder", + Fields: []string{`"hello"`, `{"foo":33}`}, + }, + }) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) @@ -169,6 +194,7 @@ func TestDryDownlinkPayload(t *testing.T) { a.So(err, ShouldBeNil) a.So(res.Payload, ShouldResemble, []byte{0x1, 0x2, 0x3}) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry(nil)) // make sure no calls to app store were made a.So(store.Count("list"), ShouldEqual, 0) @@ -199,3 +225,38 @@ func TestDryDownlinkEmptyApp(t *testing.T) { a.So(store.Count("set"), ShouldEqual, 0) a.So(store.Count("delete"), ShouldEqual, 0) } + +func TestLogs(t *testing.T) { + a := New(t) + + store := newCountingStore(application.NewRedisApplicationStore(GetRedisClient(), "handler-test-dry-downlink")) + h := &handler{ + applications: store, + } + m := &handlerManager{handler: h} + + msg := &pb.DryDownlinkMessage{ + Fields: `{ "foo": [ 1, 2, 3 ] }`, + App: &pb.Application{ + Encoder: ` + function (fields) { + console.log("foo", 1, "bar", new Date(0)) + console.log(1, { baz: 10, baa: "foo", bal: { "bar": 10 }}) + return fields.foo + }`, + }, + } + + res, err := m.DryDownlink(context.TODO(), msg) + a.So(err, ShouldBeNil) + a.So(res.Logs, ShouldResemble, []*pb.LogEntry{ + &pb.LogEntry{ + Function: "encoder", + Fields: []string{`"foo"`, "1", `"bar"`, `"1970-01-01T00:00:00.000Z"`}, + }, + &pb.LogEntry{ + Function: "encoder", + Fields: []string{"1", `{"baa":"foo","bal":{"bar":10},"baz":10}`}, + }, + }) +} diff --git a/core/handler/functions/functions.go b/core/handler/functions/functions.go new file mode 100644 index 000000000..970217093 --- /dev/null +++ b/core/handler/functions/functions.go @@ -0,0 +1,65 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package functions + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/robertkrimen/otto" +) + +var errTimeOutExceeded = errors.NewErrInternal("Code has been running to long") + +func RunCode(name, code string, env map[string]interface{}, timeout time.Duration, logger Logger) (otto.Value, error) { + vm := otto.New() + + // load the environment + for key, val := range env { + vm.Set(key, val) + } + + if logger == nil { + logger = Ignore + } + logger.Enter(name) + + vm.Set("__log", func(call otto.FunctionCall) otto.Value { + logger.Log(call) + return otto.UndefinedValue() + }) + vm.Run("console.log = __log") + + var value otto.Value + var err error + + start := time.Now() + + defer func() { + duration := time.Since(start) + if caught := recover(); caught != nil { + if caught == errTimeOutExceeded { + value = otto.Value{} + err = errors.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) + return + } + // if this is not the our timeout interrupt, raise the panic again + // so someone else can handle it + panic(caught) + } + }() + + vm.Interrupt = make(chan func(), 1) + + go func() { + time.Sleep(timeout) + vm.Interrupt <- func() { + panic(errTimeOutExceeded) + } + }() + val, err := vm.Run(code) + + return val, err +} diff --git a/core/handler/functions/functions_test.go b/core/handler/functions/functions_test.go new file mode 100644 index 000000000..02f9d6902 --- /dev/null +++ b/core/handler/functions/functions_test.go @@ -0,0 +1,54 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package functions + +import ( + "testing" + "time" + + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/robertkrimen/otto" + + . "github.com/smartystreets/assertions" +) + +func TestRunCode(t *testing.T) { + a := New(t) + + logger := NewEntryLogger() + foo := 10 + env := map[string]interface{}{ + "foo": foo, + "bar": "baz", + } + + code := ` + (function (foo, bar) { + console.log("hello", foo, bar) + return foo + })(foo,bar) + ` + + val, err := RunCode("test", code, env, time.Second, logger) + a.So(err, ShouldBeNil) + e, _ := val.Export() + a.So(e, ShouldEqual, foo) + a.So(logger.Logs, ShouldResemble, []*pb_handler.LogEntry{ + &pb_handler.LogEntry{ + Function: "test", + Fields: []string{`"hello"`, "10", `"baz"`}, + }, + }) +} + +var result string + +func BenchmarkJSON(b *testing.B) { + v, _ := otto.ToValue("foo") + var r string + for n := 0; n < b.N; n++ { + r = JSON(v) + } + result = r +} diff --git a/core/handler/functions/logger.go b/core/handler/functions/logger.go new file mode 100644 index 000000000..6201d891c --- /dev/null +++ b/core/handler/functions/logger.go @@ -0,0 +1,66 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package functions + +import ( + pb_handler "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/robertkrimen/otto" +) + +// Logger is something that can be logged to, saving the logs for later use +type Logger interface { + // Log passes the console.log function call to the logger + Log(call otto.FunctionCall) + + // Enter tells the Logger what function it is currently in + Enter(function string) +} + +// console is a Logger that saves the logs as LogEntries +type EntryLogger struct { + Logs []*pb_handler.LogEntry + function string +} + +// Console returns a new Logger that save the logs to +func NewEntryLogger() *EntryLogger { + return &EntryLogger{ + Logs: make([]*pb_handler.LogEntry, 0), + } +} + +// vm is used for stringifying values +var vm = otto.New() + +// JSON stringifies a value inside of the otto vm, yielding better +// results than Export for Object-like class such as Date, but being much +// slower. +func JSON(val otto.Value) string { + vm.Set("value", val) + res, _ := vm.Run(`JSON.stringify(value)`) + return res.String() +} + +func (c *EntryLogger) Log(call otto.FunctionCall) { + fields := []string{} + for _, field := range call.ArgumentList { + fields = append(fields, JSON(field)) + } + + c.Logs = append(c.Logs, &pb_handler.LogEntry{ + Function: c.function, + Fields: fields, + }) +} + +func (c *EntryLogger) Enter(function string) { + c.function = function +} + +type IgnoreLogger struct{} + +var Ignore = &IgnoreLogger{} + +func (c *IgnoreLogger) Log(call otto.FunctionCall) {} +func (c *IgnoreLogger) Enter(function string) {} diff --git a/core/handler/run_code.go b/core/handler/run_code.go deleted file mode 100644 index dc38bcf56..000000000 --- a/core/handler/run_code.go +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package handler - -import ( - "fmt" - "time" - - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/robertkrimen/otto" -) - -type Logger interface { - Log(call otto.FunctionCall) otto.Value -} - -type console struct { - Logs [][]string -} - -func JSON(val otto.Value) string { - vm := otto.New() - vm.Set("value", val) - res, _ := vm.Run(`JSON.stringify(value)`) - return res.String() -} - -func (c *console) Log(call otto.FunctionCall) otto.Value { - line := []string{} - for _, arg := range call.ArgumentList { - line = append(line, JSON(arg)) - } - - c.Logs = append(c.Logs, line) - - return otto.UndefinedValue() -} - -type ignore struct{} - -func (c *ignore) Log(call otto.FunctionCall) otto.Value { - return otto.UndefinedValue() -} - -func runUnsafeCodeWithLogger(vm *otto.Otto, code string, timeOut time.Duration, logger Logger) (otto.Value, error) { - if logger == nil { - logger = &ignore{} - } - - vm.Set("__log", logger.Log) - vm.Run("console.log = __log") - start := time.Now() - - var value otto.Value - var err error - - defer func() { - duration := time.Since(start) - if caught := recover(); caught != nil { - if caught == errTimeOutExceeded { - value = otto.Value{} - err = errors.NewErrInternal(fmt.Sprintf("Interrupted javascript execution after %v", duration)) - return - } - // if this is not the our timeout interrupt, raise the panic again - // so someone else can handle it - panic(caught) - } - }() - - vm.Interrupt = make(chan func(), 1) - - go func() { - time.Sleep(timeOut) - vm.Interrupt <- func() { - panic(errTimeOutExceeded) - } - }() - val, err := vm.Run(code) - - return val, err -} - -func runUnsafeCode(vm *otto.Otto, code string, timeOut time.Duration) (otto.Value, error) { - return runUnsafeCodeWithLogger(vm, code, timeOut, &ignore{}) -} - -func runUnsafeCodeWithLogs(vm *otto.Otto, code string, timeOut time.Duration) (otto.Value, [][]string, error) { - console := &console{} - val, err := runUnsafeCodeWithLogger(vm, code, timeOut, console) - return val, console.Logs, err -} From 79b0354ad4a14295d80256ebd8e0d8d32823ce53 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 11:35:15 +0200 Subject: [PATCH 2073/2266] Explain that LogEntry fields are JSON encoded --- api/handler/handler.pb.go | 6 ++++-- api/handler/handler.proto | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 7e367a302..61421aab1 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -317,8 +317,10 @@ func (m *DryUplinkMessage) GetApp() *Application { } type LogEntry struct { - Function string `protobuf:"bytes,1,opt,name=function,proto3" json:"function,omitempty"` - Fields []string `protobuf:"bytes,2,rep,name=fields" json:"fields,omitempty"` + // The location where the log was created (what payload function) + Function string `protobuf:"bytes,1,opt,name=function,proto3" json:"function,omitempty"` + // A list of JSON-encoded fields that were logged + Fields []string `protobuf:"bytes,2,rep,name=fields" json:"fields,omitempty"` } func (m *LogEntry) Reset() { *m = LogEntry{} } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 118c7574b..c37d94040 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -83,7 +83,10 @@ message DryUplinkMessage { } message LogEntry { + // The location where the log was created (what payload function) string function = 1; + + // A list of JSON-encoded fields that were logged repeated string fields = 2; } From 90b9ff7017ec043317c47f51e6a8623249750104 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 11:37:22 +0200 Subject: [PATCH 2074/2266] Test invalid code --- core/handler/functions/functions_test.go | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/handler/functions/functions_test.go b/core/handler/functions/functions_test.go index 02f9d6902..bfb2c5e75 100644 --- a/core/handler/functions/functions_test.go +++ b/core/handler/functions/functions_test.go @@ -52,3 +52,23 @@ func BenchmarkJSON(b *testing.B) { } result = r } + +func TestRunInvalidCode(t *testing.T) { + a := New(t) + + logger := NewEntryLogger() + foo := 10 + env := map[string]interface{}{ + "foo": foo, + "bar": "baz", + } + + code := ` + (function (foo, bar) { + derp + })(foo,bar) + ` + + _, err := RunCode("test", code, env, time.Second, logger) + a.So(err, ShouldNotBeNil) +} From e842214851e3d5ff76e96ac032d3ff27c89b2ccc Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 11:40:05 +0200 Subject: [PATCH 2075/2266] Move vm into JSON, to avoid race conditions --- core/handler/functions/logger.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/handler/functions/logger.go b/core/handler/functions/logger.go index 6201d891c..329fb1ea0 100644 --- a/core/handler/functions/logger.go +++ b/core/handler/functions/logger.go @@ -30,13 +30,11 @@ func NewEntryLogger() *EntryLogger { } } -// vm is used for stringifying values -var vm = otto.New() - // JSON stringifies a value inside of the otto vm, yielding better // results than Export for Object-like class such as Date, but being much // slower. func JSON(val otto.Value) string { + vm := otto.New() vm.Set("value", val) res, _ := vm.Run(`JSON.stringify(value)`) return res.String() From a44faecd4eaf1c163fc9f78315f470d9974084a1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 25 Oct 2016 11:49:47 +0200 Subject: [PATCH 2076/2266] Exclude gRPC servers from coverage --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b3dc4009f..b51e34c1d 100644 --- a/Makefile +++ b/Makefile @@ -80,7 +80,7 @@ cover: $(GO_COVER_FILE) $(GO_COVER_FILE): cover-clean $(GO_COVER_FILES) echo "mode: set" > $(GO_COVER_FILE) - cat $(GO_COVER_FILES) | grep -v "mode: set" | sort >> $(GO_COVER_FILE) + cat $(GO_COVER_FILES) | grep -vE "mode: set|/server.go|/manager_server.go" | sort >> $(GO_COVER_FILE) $(GO_COVER_DIR)/%.out: % @mkdir -p "$(GO_COVER_DIR)/$<" From 9263f420f85b324b2cbee6d330793d9fb4c8997a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Tue, 25 Oct 2016 14:22:48 +0200 Subject: [PATCH 2077/2266] Allow for additional code by calling named payload function --- core/handler/convert_fields.go | 20 ++++++++-- core/handler/convert_fields_test.go | 60 ++++++++++++++--------------- core/handler/downlink_test.go | 2 +- core/handler/dry_run_test.go | 10 ++--- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/core/handler/convert_fields.go b/core/handler/convert_fields.go index dbeb09790..5afa9e78c 100644 --- a/core/handler/convert_fields.go +++ b/core/handler/convert_fields.go @@ -73,7 +73,10 @@ func (f *UplinkFunctions) Decode(payload []byte, port uint8) (map[string]interfa "payload": payload, "port": port, } - code := fmt.Sprintf("(%s)(payload.slice(0), port)", f.Decoder) + code := fmt.Sprintf(` + %s; + Decoder(payload.slice(0), port); + `, f.Decoder) value, err := functions.RunCode("decoder", code, env, timeOut, f.Logger) if err != nil { @@ -105,7 +108,10 @@ func (f *UplinkFunctions) Convert(fields map[string]interface{}, port uint8) (ma "port": port, } - code := fmt.Sprintf("(%s)(fields, port)", f.Converter) + code := fmt.Sprintf(` + %s; + Converter(fields, port) + `, f.Converter) value, err := functions.RunCode("converter", code, env, timeOut, f.Logger) if err != nil { @@ -136,7 +142,10 @@ func (f *UplinkFunctions) Validate(fields map[string]interface{}, port uint8) (b "fields": fields, "port": port, } - code := fmt.Sprintf("(%s)(fields, port)", f.Validator) + code := fmt.Sprintf(` + %s; + Validator(fields, port) + `, f.Validator) value, err := functions.RunCode("valdator", code, env, timeOut, f.Logger) if err != nil { @@ -187,7 +196,10 @@ func (f *DownlinkFunctions) Encode(payload map[string]interface{}, port uint8) ( "payload": payload, "port": port, } - code := fmt.Sprintf("(%s)(payload, port)", f.Encoder) + code := fmt.Sprintf(` + %s; + Encoder(payload, port) + `, f.Encoder) value, err := functions.RunCode("encoder", code, env, timeOut, f.Logger) if err != nil { diff --git a/core/handler/convert_fields_test.go b/core/handler/convert_fields_test.go index 59c9e63e4..26febac16 100644 --- a/core/handler/convert_fields_test.go +++ b/core/handler/convert_fields_test.go @@ -47,7 +47,7 @@ func TestConvertFieldsUp(t *testing.T) { // Normal flow app := &application.Application{ AppID: appID, - Decoder: `function(data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, + Decoder: `function Decoder (data) { return { temperature: ((data[0] << 8) | data[1]) / 100 }; }`, } a.So(h.applications.Set(app), ShouldBeNil) defer func() { @@ -63,7 +63,7 @@ func TestConvertFieldsUp(t *testing.T) { // Invalidate data app.StartUpdate() - app.Validator = `function(data) { return false; }` + app.Validator = `function Validator (data) { return false; }` h.applications.Set(app) ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) @@ -72,7 +72,7 @@ func TestConvertFieldsUp(t *testing.T) { // Function error app.StartUpdate() - app.Validator = `function(data) { throw "expected"; }` + app.Validator = `function Validator (data) { throw "expected"; }` h.applications.Set(app) ttnUp, appUp = buildConversionUplink(appID) err = h.ConvertFieldsUp(GetLogger(t, "TestConvertFieldsUp"), ttnUp, appUp) @@ -84,7 +84,7 @@ func TestDecode(t *testing.T) { a := New(t) functions := &UplinkFunctions{ - Decoder: `function(payload, port) { + Decoder: `function Decoder (payload, port) { return { value: (payload[0] << 8) | payload[1], port: port, @@ -109,7 +109,7 @@ func TestConvert(t *testing.T) { a := New(t) withFunction := &UplinkFunctions{ - Converter: `function(data, port) { + Converter: `function Converter (data, port) { return { celcius: data.temperature * 2 port: port, @@ -131,7 +131,7 @@ func TestValidate(t *testing.T) { a := New(t) withFunction := &UplinkFunctions{ - Validator: `function(data) { + Validator: `function Validator (data) { return data.temperature < 20; }`, } @@ -152,17 +152,17 @@ func TestProcessUplink(t *testing.T) { a := New(t) functions := &UplinkFunctions{ - Decoder: `function(payload) { + Decoder: `function Decoder (payload) { return { temperature: payload[0], humidity: payload[1] } }`, - Converter: `function(data) { + Converter: `function Converter (data) { data.temperature /= 2; return data; }`, - Validator: `function(data) { + Validator: `function Validator (data) { return data.humidity >= 0 && data.humidity <= 100; }`, } @@ -193,14 +193,14 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { // Invalid return functions = &UplinkFunctions{ - Decoder: `function(payload) { return "Hello" }`, + Decoder: `function Decoder (payload) { return "Hello" }`, } _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Function functions = &UplinkFunctions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, Converter: `this is not valid JavaScript`, } _, _, err = functions.Process([]byte{40, 110}, 1) @@ -208,15 +208,15 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { // Invalid Return functions = &UplinkFunctions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Converter: `function(data) { return "Hello" }`, + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Converter: `function Converter (data) { return "Hello" }`, } _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) // Invalid Function functions = &UplinkFunctions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, Validator: `this is not valid JavaScript`, } _, _, err = functions.Process([]byte{40, 110}, 1) @@ -224,8 +224,8 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { // Invalid Return functions = &UplinkFunctions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Validator: `function(data) { return "Hello" }`, + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Validator: `function Validator (data) { return "Hello" }`, } _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) @@ -233,7 +233,7 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { // Invalid Object (Arrays are Objects too, but don't jive well with // map[string]interface{}) functions = &UplinkFunctions{ - Decoder: `function(payload) { return [1] }`, + Decoder: `function Decoder (payload) { return [1] }`, } _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) @@ -241,8 +241,8 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { // Invalid Object (Arrays are Objects too, but don't jive well with // map[string]interface{}) functions = &UplinkFunctions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Converter: `function(payload) { return [1] }`, + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Converter: `function Converter (payload) { return [1] }`, } _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) @@ -250,8 +250,8 @@ func TestProcessInvalidUplinkFunction(t *testing.T) { // Invalid Object (Arrays are Objects too), this should work error because // we're expecting a Boolean functions = &UplinkFunctions{ - Decoder: `function(payload) { return { temperature: payload[0] } }`, - Validator: `function(payload) { return [1] }`, + Decoder: `function Decoder (payload) { return { temperature: payload[0] } }`, + Validator: `function Validator (payload) { return [1] }`, } _, _, err = functions.Process([]byte{40, 110}, 1) a.So(err, ShouldNotBeNil) @@ -286,7 +286,7 @@ func TestEncode(t *testing.T) { // This function return an array of bytes (random) functions := &DownlinkFunctions{ - Encoder: `function test(payload){ + Encoder: `function Encoder (payload){ return [ 1, 2, 3, 4, 5, 6, 7 ] }`, } @@ -302,7 +302,7 @@ func TestEncode(t *testing.T) { // Return int type functions = &DownlinkFunctions{ - Encoder: `function(payload, port) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, + Encoder: `function Encoder (payload, port) { var x = [1, 2, 3 ]; return [ x.length || 0 ] }`, } _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldBeNil) @@ -343,7 +343,7 @@ func TestConvertFieldsDown(t *testing.T) { h.applications.Set(&application.Application{ AppID: appID, // Encoder takes JSON fields as argument and return the payload as []byte - Encoder: `function test(payload, port){ + Encoder: `function Encoder (payload, port){ return [ port, 1, 2, 3, 4, 5, 6, 7 ] }`, }) @@ -375,7 +375,7 @@ func TestConvertFieldsDownNoPort(t *testing.T) { h.applications.Set(&application.Application{ AppID: appID, // Encoder takes JSON fields as argument and return the payload as []byte - Encoder: `function test(payload){ + Encoder: `function Encoder (payload){ return [ 1, 2, 3, 4, 5, 6, 7 ] }`, }) @@ -408,28 +408,28 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { // Invalid return functions = &DownlinkFunctions{ - Encoder: `function(payload) { return "Hello" }`, + Encoder: `function Encoder (payload) { return "Hello" }`, } _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ - Encoder: `function(payload) { return [ 100, 2256, 7 ] }`, + Encoder: `function Encoder (payload) { return [ 100, 2256, 7 ] }`, } _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ - Encoder: `function(payload) { return [0, -1, "blablabla"] }`, + Encoder: `function Encoder (payload) { return [0, -1, "blablabla"] }`, } _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) // Invalid return functions = &DownlinkFunctions{ - Encoder: `function(payload) { + Encoder: `function Encoder (payload) { return { temperature: payload[0], humidity: payload[1] @@ -440,7 +440,7 @@ func TestProcessDownlinkInvalidFunction(t *testing.T) { a.So(err, ShouldNotBeNil) functions = &DownlinkFunctions{ - Encoder: `function(payload) { return [ 1, 1.5 ] }`, + Encoder: `function Encoder (payload) { return [ 1, 1.5 ] }`, } _, _, err = functions.Process(map[string]interface{}{"key": 11}, 1) a.So(err, ShouldNotBeNil) diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 8a750410b..deb6f9289 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -117,7 +117,7 @@ func TestHandleDownlink(t *testing.T) { // Both Payload and Fields provided h.applications.Set(&application.Application{ AppID: appID, - Encoder: `function (payload){ + Encoder: `function Encoder (payload){ return [96, 4, 3, 2, 1, 0, 1, 0, 1, 0, 0, 0, 0] }`, }) diff --git a/core/handler/dry_run_test.go b/core/handler/dry_run_test.go index 7436eec4b..f521aad67 100644 --- a/core/handler/dry_run_test.go +++ b/core/handler/dry_run_test.go @@ -74,14 +74,14 @@ func TestDryUplinkFields(t *testing.T) { Payload: []byte{11, 22, 33}, App: &pb.Application{ AppId: "DryUplinkFields", - Decoder: `function (bytes) { + Decoder: `function Decoder (bytes) { console.log("hi", 11) return { length: bytes.length }}`, - Converter: `function (obj) { + Converter: `function Converter (obj) { console.log("foo") return obj }`, - Validator: `function (bytes) { return true; }`, + Validator: `function Validator (bytes) { return true; }`, }, } @@ -149,7 +149,7 @@ func TestDryDownlinkFields(t *testing.T) { Fields: `{ "foo": [ 1, 2, 3 ] }`, App: &pb.Application{ Encoder: ` - function (fields) { + function Encoder (fields) { console.log("hello", { foo: 33 }) return fields.foo }`, @@ -239,7 +239,7 @@ func TestLogs(t *testing.T) { Fields: `{ "foo": [ 1, 2, 3 ] }`, App: &pb.Application{ Encoder: ` - function (fields) { + function Encoder (fields) { console.log("foo", 1, "bar", new Date(0)) console.log(1, { baz: 10, baa: "foo", bal: { "bar": 10 }}) return fields.foo From b4957a6bec9c56117a786f2c0ceb2af9c9dc3436 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 26 Oct 2016 17:37:56 +0200 Subject: [PATCH 2078/2266] Add make dev task for quick dev builds --- Makefile | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b51e34c1d..dc8a2316f 100644 --- a/Makefile +++ b/Makefile @@ -104,12 +104,15 @@ RELEASE_DIR ?= release GOOS ?= $(shell go env GOOS) GOARCH ?= $(shell go env GOARCH) GOEXE = $(shell GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE) +CGO_ENABLED ?= 0 + +DIST_FLAGS ?= -a -installsuffix cgo splitfilename = $(subst ., ,$(subst -, ,$(subst $(RELEASE_DIR)/,,$1))) GOOSfromfilename = $(word 2, $(call splitfilename, $1)) GOARCHfromfilename = $(word 3, $(call splitfilename, $1)) LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" -GOBUILD = CGO_ENABLED=0 GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build -a -installsuffix cgo ${LDFLAGS} -tags "${TAGS}" -o "$@" +GOBUILD = CGO_ENABLED=$(CGO_ENABLED) GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build $(DIST_FLAGS) ${LDFLAGS} -tags "${TAGS}" -o "$@" ttn: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) @@ -123,6 +126,19 @@ $(RELEASE_DIR)/ttnctl-%: $(GO_FILES) build: ttn ttnctl +ttn-dev: DIST_FLAGS= +ttn-dev: CGO_ENABLED=1 +ttn-dev: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) + +ttnctl-dev: DIST_FLAGS= +ttnctl-dev: CGO_ENABLED=1 +ttnctl-dev: $(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) + +install: + go install -v + +dev: install ttn-dev ttnctl-dev + GOBIN ?= $(GO_PATH)/bin link: build From e6678cb772af3b27ad1645fe9fc5e9a47a496c40 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 28 Oct 2016 14:23:10 +0200 Subject: [PATCH 2079/2266] Stop monitor streams on err --- api/monitor/client.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/api/monitor/client.go b/api/monitor/client.go index 797650e73..f8b5ac18c 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -231,6 +231,18 @@ func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { return err } } + go func() { + var msg []byte + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor status stream, closing...") + cl.status.Lock() + cl.status.stream.CloseSend() + if cl.status.stream == stream { + cl.status.stream = nil + } + cl.status.Unlock() + } + }() cl.status.Unlock() } @@ -284,6 +296,18 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { return err } } + go func() { + var msg []byte + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor uplink stream, closing...") + cl.uplink.Lock() + cl.uplink.stream.CloseSend() + if cl.uplink.stream == stream { + cl.uplink.stream = nil + } + cl.uplink.Unlock() + } + }() cl.uplink.Unlock() } @@ -337,6 +361,18 @@ func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err err return err } } + go func() { + var msg []byte + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor downlink stream, closing...") + cl.downlink.Lock() + cl.downlink.stream.CloseSend() + if cl.downlink.stream == stream { + cl.downlink.stream = nil + } + cl.downlink.Unlock() + } + }() cl.downlink.Unlock() } From b17aedbfc614b2610dcb3810c23fd7ddb0c4c29b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 28 Oct 2016 14:23:59 +0200 Subject: [PATCH 2080/2266] Get gateway token from metadata even when not verifying --- core/router/server.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/router/server.go b/core/router/server.go index af8d0286d..1de8bafae 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -36,13 +36,9 @@ func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gatewa return nil, err } - var token string + token, _ := api.TokenFromMetadata(md) if !viper.GetBool("router.skip-verify-gateway-token") { - token, err = api.TokenFromMetadata(md) - if err != nil { - return nil, err - } if token == "" { return nil, errors.NewErrPermissionDenied("No gateway token supplied") } From 4f6d129b81e3eb1ac1556384ae51277b5f3c9c90 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 28 Oct 2016 14:25:15 +0200 Subject: [PATCH 2081/2266] Set monitor token for gateway after it is instantiated --- api/monitor/client.go | 38 ++++++++++++++++++++++++++++------ core/router/gateway/gateway.go | 15 +++++++------- core/router/router.go | 2 +- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index f8b5ac18c..2eb59dfe5 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -142,19 +142,16 @@ func (cl *Client) IsConnected() bool { } // GatewayClient returns monitor GatewayClient for id and token specified -func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { +func (cl *Client) GatewayClient(id string) (gtwCl GatewayClient) { cl.mutex.RLock() gtwCl, ok := cl.gateways[id] cl.mutex.RUnlock() if !ok { cl.mutex.Lock() gtwCl = &gatewayClient{ - Ctx: cl.Ctx.WithField("GatewayID", id), - + Ctx: cl.Ctx.WithField("GatewayID", id), client: cl, - - id: id, - token: token, + id: id, } cl.gateways[id] = gtwCl cl.mutex.Unlock() @@ -163,6 +160,8 @@ func (cl *Client) GatewayClient(id, token string) (gtwCl GatewayClient) { } type gatewayClient struct { + sync.RWMutex + client *Client Ctx log.Interface @@ -187,14 +186,31 @@ type gatewayClient struct { // GatewayClient is used as the main client for Gateways to communicate with the monitor type GatewayClient interface { + SetToken(token string) SendStatus(status *gateway.Status) (err error) SendUplink(msg *router.UplinkMessage) (err error) SendDownlink(msg *router.DownlinkMessage) (err error) Close() (err error) } +func (cl *gatewayClient) SetToken(token string) { + cl.Lock() + defer cl.Unlock() + cl.token = token +} + +func (cl *gatewayClient) IsConfigured() bool { + cl.RLock() + defer cl.RUnlock() + return cl.token != "" +} + // SendStatus sends status to the monitor func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { + if !cl.IsConfigured() { + return nil + } + cl.status.RLock() cl.client.mutex.RLock() @@ -260,6 +276,10 @@ func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { // SendUplink sends uplink to the monitor func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { + if !cl.IsConfigured() { + return nil + } + cl.uplink.RLock() cl.client.mutex.RLock() @@ -325,6 +345,10 @@ func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { // SendUplink sends downlink to the monitor func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { + if !cl.IsConfigured() { + return nil + } + cl.downlink.RLock() cl.client.mutex.RLock() @@ -444,6 +468,8 @@ func (cl *gatewayClient) Close() (err error) { // Context returns monitor connection context for gateway func (cl *gatewayClient) Context() (monitorContext context.Context) { + cl.RLock() + defer cl.RUnlock() return metadata.NewContext(context.Background(), metadata.Pairs( "id", cl.id, "token", cl.token, diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index 8291232e1..f36b6509c 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -4,7 +4,6 @@ package gateway import ( - "sync" "time" pb "github.com/TheThingsNetwork/ttn/api/gateway" @@ -33,8 +32,7 @@ type Gateway struct { Schedule Schedule LastSeen time.Time - Token string - tokenLock sync.Mutex + token string Monitors map[string]pb_monitor.GatewayClient @@ -42,10 +40,13 @@ type Gateway struct { } func (g *Gateway) SetToken(token string) { - g.tokenLock.Lock() - defer g.tokenLock.Unlock() - - g.Token = token + if token == g.token { + return + } + g.token = token + for _, monitor := range g.Monitors { + monitor.SetToken(token) + } } func (g *Gateway) updateLastSeen() { diff --git a/core/router/router.go b/core/router/router.go index 9b1922456..989deaf8f 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -112,7 +112,7 @@ func (r *router) getGateway(id string) *gateway.Gateway { if r.Component.Monitors != nil { gtw.Monitors = make(map[string]pb_monitor.GatewayClient) for name, cl := range r.Component.Monitors { - gtw.Monitors[name] = cl.GatewayClient(gtw.ID, gtw.Token) + gtw.Monitors[name] = cl.GatewayClient(gtw.ID) } } From a1fa904b1b1670dcae2a244bd3231a688dbf07b8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 11:11:25 +0100 Subject: [PATCH 2082/2266] Rename MQTT_HOST and AMQP_ADDR to MQTT_ADDRESS and AMQP_ADDRESS --- .gitlab-ci.yml | 4 ++-- amqp/client_test.go | 2 +- core/handler/amqp_test.go | 2 +- core/handler/mqtt_test.go | 8 ++++---- mqtt/client_test.go | 14 +++++++------- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e90d5dc54..279c782dd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -35,8 +35,8 @@ tests: - redis variables: REDIS_HOST: redis - MQTT_HOST: robertobarreda__rabbitmq - AMQP_ADDR: robertobarreda__rabbitmq:5672 + MQTT_ADDRESS: robertobarreda__rabbitmq:1883 + AMQP_ADDRESS: robertobarreda__rabbitmq:5672 script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps diff --git a/amqp/client_test.go b/amqp/client_test.go index d61c7992d..a250e19f6 100644 --- a/amqp/client_test.go +++ b/amqp/client_test.go @@ -16,7 +16,7 @@ import ( var host string func init() { - host = os.Getenv("AMQP_ADDR") + host = os.Getenv("AMQP_ADDRESS") if host == "" { host = "localhost:5672" } diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go index 7f2f2d9c1..2f30fe08b 100644 --- a/core/handler/amqp_test.go +++ b/core/handler/amqp_test.go @@ -17,7 +17,7 @@ import ( ) func TestHandleAMQP(t *testing.T) { - host := os.Getenv("AMQP_ADDR") + host := os.Getenv("AMQP_ADDRESS") if host == "" { host = "localhost:5672" } diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index fe24672dc..66fc8fe74 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -18,14 +18,14 @@ import ( ) func TestHandleMQTT(t *testing.T) { - host := os.Getenv("MQTT_HOST") + host := os.Getenv("MQTT_ADDRESS") if host == "" { - host = "localhost" + host = "localhost:1883" } a := New(t) var wg WaitGroup - c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", fmt.Sprintf("tcp://%s", host)) err := c.Connect() a.So(err, ShouldBeNil) appID := "handler-mqtt-app1" @@ -41,7 +41,7 @@ func TestHandleMQTT(t *testing.T) { defer func() { h.devices.Delete(appID, devID) }() - err = h.HandleMQTT("", "", fmt.Sprintf("tcp://%s:1883", host)) + err = h.HandleMQTT("", "", fmt.Sprintf("tcp://%s", host)) a.So(err, ShouldBeNil) c.PublishDownlink(types.DownlinkMessage{ diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 977c092c8..517295875 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -19,9 +19,9 @@ import ( var host string func init() { - host = os.Getenv("MQTT_HOST") + host = os.Getenv("MQTT_ADDRESS") if host == "" { - host = "localhost" + host = "localhost:1883" } } @@ -70,13 +70,13 @@ func TestSimpleToken(t *testing.T) { func TestNewClient(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) a.So(c.(*DefaultClient).mqtt, ShouldNotBeNil) } func TestConnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) err := c.Connect() defer c.Disconnect() a.So(err, ShouldBeNil) @@ -103,7 +103,7 @@ func TestConnectInvalidCredentials(t *testing.T) { func TestIsConnected(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) a.So(c.IsConnected(), ShouldBeFalse) @@ -115,7 +115,7 @@ func TestIsConnected(t *testing.T) { func TestDisconnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) // Disconnecting when not connected should not change anything c.Disconnect() @@ -132,7 +132,7 @@ func TestRandomTopicPublish(t *testing.T) { a := New(t) ctx := GetLogger(t, "TestRandomTopicPublish") - c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() From e54b5f8e4c90876ac90fc3ac76d35a312cf1582d Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 1 Nov 2016 11:19:09 +0100 Subject: [PATCH 2083/2266] Clarify region, add TLS port and link to PEM --- mqtt/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mqtt/README.md b/mqtt/README.md index f96a7bd45..5f882c361 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -1,8 +1,8 @@ # API Reference -* Host: `.thethings.network` -* Port: `1883` -* TLS: Not yet available +* Host: `.thethings.network`, where `` is last part of the handler you registered your application to, e.g. `eu`. +* Port: `1883` or `8883` for TLS +* PEM encoded CA certificate for TLS: [mqtt-ca.pem](http://preview.console.thethingsnetwork.org/mqtt-ca.pem) * Username: Application ID * Password: Application Access Key From 625002a2e616667ec4b9006c4ff7944a82b8a40c Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 1 Nov 2016 11:47:41 +0100 Subject: [PATCH 2084/2266] Get from https --- mqtt/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mqtt/README.md b/mqtt/README.md index 5f882c361..f6c42bb93 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -2,7 +2,7 @@ * Host: `.thethings.network`, where `` is last part of the handler you registered your application to, e.g. `eu`. * Port: `1883` or `8883` for TLS -* PEM encoded CA certificate for TLS: [mqtt-ca.pem](http://preview.console.thethingsnetwork.org/mqtt-ca.pem) +* PEM encoded CA certificate for TLS: [mqtt-ca.pem](https://preview.console.thethingsnetwork.org/mqtt-ca.pem) * Username: Application ID * Password: Application Access Key From ec7a970086b11c2bd3ccef1be7b428a5d0e7bb9f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 12:44:33 +0100 Subject: [PATCH 2085/2266] Rename cli args and env vars discovery-server -> discovery-address ttn-account-server -> auth-server ttn-router -> router-id ttn-broker -> broker-id ttn-handler -> handler-id mqtt-broker -> mqtt-address amqp-host -> amqp-address --- .env/broker/dev.yml | 2 +- .env/discovery/dev.yml | 2 +- .env/handler/dev.yml | 5 +++-- .env/networkserver/dev.yml | 2 +- .env/router/dev.yml | 2 +- .env/ttnctl.yml.dev-example | 10 ++++----- README.md | 16 +++++++------- cmd/broker_register_prefix.go | 2 +- cmd/docs/README.md | 39 +++++++++++++++++++---------------- cmd/handler.go | 34 +++++++++++++++--------------- cmd/root.go | 14 ++++++------- core/component.go | 2 +- docker-compose.yml | 22 +++++++++++++------- ttnctl/README.md | 10 ++++----- ttnctl/cmd/docs/README.md | 18 +++++++++------- ttnctl/cmd/root.go | 20 +++++++++--------- ttnctl/cmd/user_login.go | 2 +- ttnctl/cmd/user_register.go | 2 +- ttnctl/util/account.go | 12 +++++------ ttnctl/util/discovery.go | 2 +- ttnctl/util/handler.go | 4 ++-- ttnctl/util/mqtt.go | 6 +++--- ttnctl/util/router.go | 4 ++-- 23 files changed, 122 insertions(+), 110 deletions(-) diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index cf6779d9e..999a6cd0f 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -1,6 +1,6 @@ id: dev debug: true -discovery-server: "localhost:1900" +discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index 217a4abb8..e0de476c9 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -1,6 +1,6 @@ id: dev debug: true -discovery-server: "localhost:1900" +discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 66a4cbbfa..14b23be58 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -1,6 +1,6 @@ id: dev debug: true -discovery-server: "localhost:1900" +discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" @@ -8,6 +8,7 @@ auth-servers: tls: true key-dir: "./.env/handler/" handler: - amqp-host: localhost:5672 + amqp-address: localhost:5672 + mqtt-address: localhost:1883 auth-token: eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImhhbmRsZXIiLCJpYXQiOjE0NzY0Mzk0Mzh9.iIz5KgH41FRLoIIJKVb9dpaZyYjT0W_ujNoSyY3Eg16xkpSuAA2s0NlmEmKBcLEEQXsMNQJd2HnrsDi9RMljRtHQ-pJkqFsb9asHQ6eVmZb7Yx8uGLGG7wmVDlu_R5nNTyvUDvpNoug3pJf13QAfUpNWbN1pgiK2Or0IalDjiCToHCrq9OE08kM0PjXLacuFMhC-OUrsqadHs4WNm8OQYY5yYOpY0G2l4sL0I53QR5kvMjMyoJIrFHD5VNGCFZ-edyaKsGcmZtuR2b-6c9LuI9elsZT9QcdYpANGaepq6cM-inJdtWKEMnhNxDGykxVhBqbBCaYwBgyP4OgBPIrlVg diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index 3b67552b2..ee393bd41 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -1,5 +1,5 @@ debug: true -discovery-server: "localhost:1900" +discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 66c379c5e..0fa93434a 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -1,6 +1,6 @@ id: dev debug: true -discovery-server: "localhost:1900" +discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" diff --git a/.env/ttnctl.yml.dev-example b/.env/ttnctl.yml.dev-example index b702ddc82..3acc3ea2b 100644 --- a/.env/ttnctl.yml.dev-example +++ b/.env/ttnctl.yml.dev-example @@ -1,5 +1,5 @@ -discovery-server: localhost:1900 -mqtt-broker: localhost:1883 -ttn-handler: dev -ttn-router: dev -ttn-account-server: https://preview.account.thethingsnetwork.org +discovery-address: localhost:1900 +auth-server: https://preview.account.thethingsnetwork.org +router-id: dev +handler-id: dev +mqtt-address: localhost:1883 diff --git a/README.md b/README.md index 50807f9d1..525656402 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed. 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. Make sure you have [RabbitMQ](https://www.rabbitmq.com/download.html) and [Redis](http://redis.io/download) **installed** and **running**. +4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) with the MQTT plugin **installed** and **running**. If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. Windows users are currently on their own (feel free to contribute a guide for Windows). 5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` @@ -45,11 +45,11 @@ You can check your `ttnctl` configuration by running `ttnctl config`. It should config file: /home/your-user/.ttnctl.yml data dir: /home/your-user/.ttnctl - ttn-account-server: https://preview.account.thethingsnetwork.org - discovery-server: localhost:1900 - ttn-router: dev - ttn-handler: dev - mqtt-broker: localhost:1883 + auth-server: https://preview.account.thethingsnetwork.org + discovery-address: localhost:1900 + router-id: dev + handler-id: dev + mqtt-address: localhost:1883 ``` **NOTE:** From now on you should run all commands from the `$GOPATH/src/github.com/TheThingsNetwork/ttn` directory. @@ -57,7 +57,7 @@ You can check your `ttnctl` configuration by running `ttnctl config`. It should ## Run The Things Network's backend locally - Set up the backend as described [above](#set-up-the-things-networks-backend-for-development). -- Run `forego start` to start all backend services at the same time. Make sure that Redis and Mosquitto **are running** on your machine. +- Run `forego start` to start all backend services at the same time. Make sure that Redis and RabbitMQ **are running** on your machine. - First time only (or when Redis is flushed): * Run `ttn broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` * Restart the backend services @@ -68,7 +68,7 @@ You can check your `ttnctl` configuration by running `ttnctl config`. It should - Add the following line to your `/etc/hosts` file: `127.0.0.1 router handler` - Run `make docker` to build the docker image -- Run `docker-compose up` to start all backend services in Docker. Make sure that Redis and Mosquitto **are not running** on your local machine. +- Run `docker-compose up` to start all backend services in Docker. Make sure that Redis and RabbitMQ **are not running** on your local machine, because they will be started by `docker-compose`. - First time only (or when Redis is flushed): * Run `docker-compose run broker broker register-prefix 00000000/0 --config ./.env/broker/dev.yml` * Restart the backend services diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index e25df8853..a5d72099c 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -24,7 +24,7 @@ var brokerRegisterPrefixCmd = &cobra.Command{ cmd.UsageFunc()(cmd) } - conn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithInsecure())...) + conn, err := grpc.Dial(viper.GetString("discovery-address"), append(api.DialOptions, grpc.WithInsecure())...) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") } diff --git a/cmd/docs/README.md b/cmd/docs/README.md index 0761b888d..5d117298a 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -5,17 +5,18 @@ The Things Network's backend servers. **Options** ``` - --auth-token string The JWT token to be used for the discovery server - --config string config file (default "$HOME/.ttn.yml") - --description string The description of this component - --discovery-server 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 - --tls Use TLS + --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 ``` @@ -80,19 +81,21 @@ ttn broker register prefix registers a prefix to this Broker **Options** ``` - --amqp-exchange string AMQP exchange - --amqp-host string AMQP host and port. Leave empty to disable AMQP - --amqp-password string AMQP password - --amqp-username string AMQP username (default "handler") - --mqtt-broker string MQTT broker host and port (default "localhost:1883") + --amqp-address string AMQP host and port. Leave empty to disable AMQP + --amqp-exchange string AMQP exchange (default "ttn.handler") + --amqp-password string AMQP password (default "guest") + --amqp-username string AMQP username (default "guest") + --broker-id string The ID of the TTN Broker as announced in the Discovery server (default "dev") + --http-address string The IP address where the gRPC proxy should listen (default "0.0.0.0") + --http-port int The port where the gRPC proxy should listen + --mqtt-address string MQTT host and port --mqtt-password string MQTT password - --mqtt-username string MQTT username (default "handler") + --mqtt-username string MQTT username --redis-address string Redis host and port (default "localhost:6379") --redis-db int Redis database --server-address string The IP address to listen for communication (default "0.0.0.0") --server-address-announce string The public IP address to announce (default "localhost") --server-port int The port for communication (default 1904) - --ttn-broker string The ID of the TTN Broker as announced in the Discovery server (default "dev") ``` ### ttn handler gen-cert diff --git a/cmd/handler.go b/cmd/handler.go index d537d2cd5..362e49358 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -31,13 +31,13 @@ var handlerCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "Server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), - "HTTP Proxy": fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), - "Announce": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port")), - "Database": fmt.Sprintf("%s/%d", viper.GetString("handler.redis-address"), viper.GetInt("handler.redis-db")), - "TTNBroker": viper.GetString("handler.ttn-broker"), - "MQTTBroker": viper.GetString("handler.mqtt-broker"), - "AMQPHost": viper.GetString("handler.amqp-host"), + "Server": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address"), viper.GetInt("handler.server-port")), + "HTTP Proxy": fmt.Sprintf("%s:%d", viper.GetString("handler.http-address"), viper.GetInt("handler.http-port")), + "Announce": fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("handler.redis-address"), viper.GetInt("handler.redis-db")), + "TTN Broker ID": viper.GetString("handler.broker-id"), + "MQTT": viper.GetString("handler.mqtt-address"), + "AMQP": viper.GetString("handler.amqp-address"), }).Info("Initializing Handler") }, Run: func(cmd *cobra.Command, args []string) { @@ -61,16 +61,16 @@ var handlerCmd = &cobra.Command{ // Handler handler := handler.NewRedisHandler( client, - viper.GetString("handler.ttn-broker"), + viper.GetString("handler.broker-id"), viper.GetString("handler.mqtt-username"), viper.GetString("handler.mqtt-password"), - viper.GetString("handler.mqtt-broker"), + viper.GetString("handler.mqtt-address"), ) - if viper.GetString("handler.amqp-host") != "" { + if viper.GetString("handler.amqp-address") != "" { handler = handler.WithAMQP( viper.GetString("handler.amqp-username"), viper.GetString("handler.amqp-password"), - viper.GetString("handler.amqp-host"), + viper.GetString("handler.amqp-address"), viper.GetString("handler.amqp-exchange")) } err = handler.Init(component) @@ -131,11 +131,11 @@ func init() { handlerCmd.Flags().Int("redis-db", 0, "Redis database") viper.BindPFlag("handler.redis-db", handlerCmd.Flags().Lookup("redis-db")) - handlerCmd.Flags().String("ttn-broker", "dev", "The ID of the TTN Broker as announced in the Discovery server") - viper.BindPFlag("handler.ttn-broker", handlerCmd.Flags().Lookup("ttn-broker")) + handlerCmd.Flags().String("broker-id", "dev", "The ID of the TTN Broker as announced in the Discovery server") + viper.BindPFlag("handler.broker-id", handlerCmd.Flags().Lookup("broker-id")) - handlerCmd.Flags().String("mqtt-broker", "localhost:1883", "MQTT broker host and port") - viper.BindPFlag("handler.mqtt-broker", handlerCmd.Flags().Lookup("mqtt-broker")) + handlerCmd.Flags().String("mqtt-address", "", "MQTT host and port") + viper.BindPFlag("handler.mqtt-address", handlerCmd.Flags().Lookup("mqtt-address")) handlerCmd.Flags().String("mqtt-username", "", "MQTT username") viper.BindPFlag("handler.mqtt-username", handlerCmd.Flags().Lookup("mqtt-username")) @@ -143,8 +143,8 @@ func init() { handlerCmd.Flags().String("mqtt-password", "", "MQTT password") viper.BindPFlag("handler.mqtt-password", handlerCmd.Flags().Lookup("mqtt-password")) - handlerCmd.Flags().String("amqp-host", "", "AMQP host and port. Leave empty to disable AMQP") - viper.BindPFlag("handler.amqp-host", handlerCmd.Flags().Lookup("amqp-host")) + handlerCmd.Flags().String("amqp-address", "", "AMQP host and port. Leave empty to disable AMQP") + viper.BindPFlag("handler.amqp-address", handlerCmd.Flags().Lookup("amqp-address")) handlerCmd.Flags().String("amqp-username", "guest", "AMQP username") viper.BindPFlag("handler.amqp-username", handlerCmd.Flags().Lookup("amqp-username")) diff --git a/cmd/root.go b/cmd/root.go index abad592cc..fcd0ced59 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -79,11 +79,11 @@ var RootCmd = &cobra.Command{ Handler: multiHandler.New(logHandlers...), } ctx.WithFields(log.Fields{ - "ComponentID": viper.GetString("id"), - "Description": viper.GetString("description"), - "DiscoveryServer": viper.GetString("discovery-server"), - "AuthServers": viper.GetStringMapString("auth-servers"), - "Monitors": viper.GetStringMapString("monitor-servers"), + "ComponentID": viper.GetString("id"), + "Description": viper.GetString("description"), + "Discovery Server Address": viper.GetString("discovery-address"), + "Auth Servers": viper.GetStringMapString("auth-servers"), + "Monitors": viper.GetStringMapString("monitor-servers"), }).Info("Initializing The Things Network") }, PersistentPostRun: func(cmd *cobra.Command, args []string) { @@ -124,8 +124,8 @@ func init() { RootCmd.PersistentFlags().Bool("public", false, "Announce this component as part of The Things Network (public community network)") viper.BindPFlag("public", RootCmd.PersistentFlags().Lookup("public")) - RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") - viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) + RootCmd.PersistentFlags().String("discovery-address", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") + viper.BindPFlag("discovery-address", RootCmd.PersistentFlags().Lookup("discovery-address")) viper.SetDefault("auth-servers", map[string]string{ "ttn-account": "https://account.thethingsnetwork.org", diff --git a/core/component.go b/core/component.go index 855747591..902401053 100644 --- a/core/component.go +++ b/core/component.go @@ -83,7 +83,7 @@ func NewComponent(ctx log.Interface, serviceName string, announcedAddress string if serviceName != "discovery" { var err error component.Discovery, err = pb_discovery.NewClient( - viper.GetString("discovery-server"), + viper.GetString("discovery-address"), component.Identity, func() string { token, _ := component.BuildJWT() diff --git a/docker-compose.yml b/docker-compose.yml index 71e207206..bcfe7c633 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,10 +7,15 @@ services: - "6379:6379" volumes: - ./.env/redis:/data - mosquitto: - image: ansi/mosquitto + rabbitmq: + image: thethingsnetwork/rabbitmq + hostname: rabbitserver ports: - "1883:1883" + - "5672:5672" + - "15672:15672" + volumes: + - /var/lib/rabbitmq discovery: image: thethingsnetwork/ttn hostname: discovery @@ -34,7 +39,7 @@ services: - discovery environment: - TERM - - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_DISCOVERY_ADDRESS=discovery:1900 - TTN_ROUTER_SERVER_ADDRESS_ANNOUNCE=router ports: - "1901:1901" @@ -50,7 +55,7 @@ services: - networkserver environment: - TERM - - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_DISCOVERY_ADDRESS=discovery:1900 - TTN_BROKER_SERVER_ADDRESS_ANNOUNCE=broker - TTN_BROKER_NETWORKSERVER_ADDRESS=networkserver:1903 ports: @@ -66,7 +71,7 @@ services: - redis environment: - TERM - - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_DISCOVERY_ADDRESS=discovery:1900 - TTN_NETWORKSERVER_REDIS_ADDRESS=redis:6379 ports: - "1903:1903" @@ -80,13 +85,14 @@ services: depends_on: - discovery - redis - - mosquitto + - rabbitmq environment: - TERM - - TTN_DISCOVERY_SERVER=discovery:1900 + - TTN_DISCOVERY_ADDRESS=discovery:1900 - TTN_HANDLER_SERVER_ADDRESS_ANNOUNCE=handler - TTN_HANDLER_REDIS_ADDRESS=redis:6379 - - TTN_HANDLER_MQTT_BROKER=mosquitto:1883 + - TTN_HANDLER_MQTT_ADDRESS=rabbitmq:1883 + - TTN_HANDLER_AMQP_ADDRESS=rabbitmq:5672 ports: - "1904:1904" volumes: diff --git a/ttnctl/README.md b/ttnctl/README.md index f6ce92e25..bfe6aa753 100644 --- a/ttnctl/README.md +++ b/ttnctl/README.md @@ -19,11 +19,11 @@ The following configuration options can be set: | `app-id` | `TTNCTL_APP_ID` | The application ID that should be used | | `app-eui` | `TTNCTL_APP_EUI` | The LoRaWAN AppEUI that should be used | | `debug` | `TTNCTL_DEBUG` | Print debug logs | -| `discovery-server` | `TTNCTL_DISCOVERY_SERVER` | The address and port of the discovery server | -| `ttn-router` | `TTNCTL_TTN_ROUTER` | The id of the router | -| `ttn-handler` | `TTNCTL_TTN_HANDLER` | The id of the handler | -| `mqtt-broker` | `TTNCTL_MQTT_BROKER` | The address and port of the MQTT broker | -| `ttn-account-server` | `TTNCTL_TTN_ACCOUNT_SERVER` | The protocol, address (and port) of the account server | +| `discovery-address` | `TTNCTL_DISCOVERY_ADDRESS` | The address and port of the discovery server | +| `router-id` | `TTNCTL_TTN_ROUTER` | The id of the router | +| `handler-id` | `TTNCTL_TTN_HANDLER` | The id of the handler | +| `mqtt-address` | `TTNCTL_MQTT_ADDRESS` | The address and port of the MQTT broker | +| `auth-server` | `TTNCTL_AUTH_SERVER` | The protocol (http/https), address and port of the auth server | ## Development diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index ce4ccf15c..693f1fa48 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -5,14 +5,16 @@ Control The Things Network from the command line. **Options** ``` - --config string config file (default is $HOME/.ttnctl.yml) - --data string directory where ttnctl stores data (default is $HOME/.ttnctl) - -d, --debug Enable debug mode - --discovery-server string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") - --mqtt-broker string The address of the MQTT broker (default "eu.thethings.network:1883") - --ttn-account-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") - --ttn-handler string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") - --ttn-router string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") + --auth-server string The address of the OAuth 2.0 server (default "https://account.thethingsnetwork.org") + --config string config file (default is $HOME/.ttnctl.yml) + --data string directory where ttnctl stores data (default is $HOME/.ttnctl) + -d, --debug Enable debug mode + --discovery-address string The address of the Discovery server (default "discover.thethingsnetwork.org:1900") + --handler-id string The ID of the TTN Handler as announced in the Discovery server (default "ttn-handler-eu") + --mqtt-address string The address of the MQTT broker (default "eu.thethings.network:1883") + --mqtt-password string The password for the MQTT broker + --mqtt-username string The username for the MQTT broker + --router-id string The ID of the TTN Router as announced in the Discovery server (default "ttn-router-eu") ``` diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 5dee4073a..0aa7493bf 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -70,17 +70,17 @@ func init() { RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") viper.BindPFlag("data", RootCmd.PersistentFlags().Lookup("data")) - RootCmd.PersistentFlags().String("discovery-server", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") - viper.BindPFlag("discovery-server", RootCmd.PersistentFlags().Lookup("discovery-server")) + RootCmd.PersistentFlags().String("discovery-address", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") + viper.BindPFlag("discovery-address", RootCmd.PersistentFlags().Lookup("discovery-address")) - RootCmd.PersistentFlags().String("ttn-router", "ttn-router-eu", "The ID of the TTN Router as announced in the Discovery server") - viper.BindPFlag("ttn-router", RootCmd.PersistentFlags().Lookup("ttn-router")) + RootCmd.PersistentFlags().String("router-id", "ttn-router-eu", "The ID of the TTN Router as announced in the Discovery server") + viper.BindPFlag("router-id", RootCmd.PersistentFlags().Lookup("router-id")) - RootCmd.PersistentFlags().String("ttn-handler", "ttn-handler-eu", "The ID of the TTN Handler as announced in the Discovery server") - viper.BindPFlag("ttn-handler", RootCmd.PersistentFlags().Lookup("ttn-handler")) + RootCmd.PersistentFlags().String("handler-id", "ttn-handler-eu", "The ID of the TTN Handler as announced in the Discovery server") + viper.BindPFlag("handler-id", RootCmd.PersistentFlags().Lookup("handler-id")) - RootCmd.PersistentFlags().String("mqtt-broker", "eu.thethings.network:1883", "The address of the MQTT broker") - viper.BindPFlag("mqtt-broker", RootCmd.PersistentFlags().Lookup("mqtt-broker")) + RootCmd.PersistentFlags().String("mqtt-address", "eu.thethings.network:1883", "The address of the MQTT broker") + viper.BindPFlag("mqtt-address", RootCmd.PersistentFlags().Lookup("mqtt-address")) RootCmd.PersistentFlags().String("mqtt-username", "", "The username for the MQTT broker") viper.BindPFlag("mqtt-username", RootCmd.PersistentFlags().Lookup("mqtt-username")) @@ -88,8 +88,8 @@ func init() { RootCmd.PersistentFlags().String("mqtt-password", "", "The password for the MQTT broker") viper.BindPFlag("mqtt-password", RootCmd.PersistentFlags().Lookup("mqtt-password")) - RootCmd.PersistentFlags().String("ttn-account-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") - viper.BindPFlag("ttn-account-server", RootCmd.PersistentFlags().Lookup("ttn-account-server")) + RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") + viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) } func printKV(key, t interface{}) { diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index ce3ecd111..7c2ab9877 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -35,7 +35,7 @@ $ ttnctl user login [paste the access code you requested above] ctx.WithError(err).Fatal("Login failed") } - acc := account.New(viper.GetString("ttn-account-server")) + acc := account.New(viper.GetString("auth-server")) acc.WithAuth(auth.AccessToken(token.AccessToken)) profile, err := acc.Profile() if err != nil { diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go index 0b5a1e22e..c63bea4de 100644 --- a/ttnctl/cmd/user_register.go +++ b/ttnctl/cmd/user_register.go @@ -33,7 +33,7 @@ Password: if err != nil { ctx.Fatal(err.Error()) } - acc := account.New(viper.GetString("ttn-account-server")) + acc := account.New(viper.GetString("auth-server")) err = acc.RegisterUser(username, email, string(password)) if err != nil { ctx.WithError(err).Fatal("Could not register user") diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index b3964e7dd..b44738846 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -30,12 +30,12 @@ func derivedTokenFile() string { } func tokenName() string { - return viper.GetString("ttn-account-server") + return viper.GetString("auth-server") } func getServerKey() string { replacer := strings.NewReplacer("https:", "", "http:", "", "/", "", ".", "") - return replacer.Replace(viper.GetString("ttn-account-server")) + return replacer.Replace(viper.GetString("auth-server")) } func tokenFilename(name string) string { @@ -48,7 +48,7 @@ func GetTokenCache() cache.Cache { } func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { - config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") + config := accountUtil.MakeConfig(viper.GetString("auth-server"), "ttnctl", "", "") return config.TokenSource(context.Background(), token) } @@ -119,7 +119,7 @@ func GetTokenSource(ctx log.Interface) oauth2.TokenSource { } func GetTokenManager(accessToken string) tokens.Manager { - server := viper.GetString("ttn-account-server") + server := viper.GetString("auth-server") return tokens.HTTPManager(server, accessToken, tokens.FileStore(path.Join(GetDataDir(), derivedTokenFile()))) } @@ -130,7 +130,7 @@ func GetAccount(ctx log.Interface) *account.Account { ctx.WithError(err).Fatal("Could not get access token") } - server := viper.GetString("ttn-account-server") + server := viper.GetString("auth-server") manager := GetTokenManager(token.AccessToken) return account.NewWithManager(server, token.AccessToken, manager) @@ -138,7 +138,7 @@ func GetAccount(ctx log.Interface) *account.Account { // Login does a login to the Account server with the given username and password func Login(ctx log.Interface, code string) (*oauth2.Token, error) { - config := accountUtil.MakeConfig(viper.GetString("ttn-account-server"), "ttnctl", "", "") + config := accountUtil.MakeConfig(viper.GetString("auth-server"), "ttnctl", "", "") token, err := config.Exchange(context.Background(), code) if err != nil { return nil, err diff --git a/ttnctl/util/discovery.go b/ttnctl/util/discovery.go index 415819a37..912ea512c 100644 --- a/ttnctl/util/discovery.go +++ b/ttnctl/util/discovery.go @@ -13,7 +13,7 @@ import ( // GetDiscovery gets the Discovery client for ttnctl func GetDiscovery(ctx log.Interface) (*grpc.ClientConn, discovery.DiscoveryClient) { - conn, err := grpc.Dial(viper.GetString("discovery-server"), append(api.DialOptions, grpc.WithInsecure())...) + conn, err := grpc.Dial(viper.GetString("discovery-address"), append(api.DialOptions, grpc.WithInsecure())...) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") } diff --git a/ttnctl/util/handler.go b/ttnctl/util/handler.go index 8dcafb41c..eaf3725a1 100644 --- a/ttnctl/util/handler.go +++ b/ttnctl/util/handler.go @@ -15,12 +15,12 @@ import ( // GetHandlerManager gets a new HandlerManager for ttnctl func GetHandlerManager(ctx log.Interface, appID string) (*grpc.ClientConn, *handler.ManagerClient) { - ctx.WithField("Handler", viper.GetString("ttn-handler")).Info("Discovering Handler...") + ctx.WithField("Handler", viper.GetString("handler-id")).Info("Discovering Handler...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() handlerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ ServiceName: "handler", - Id: viper.GetString("ttn-handler"), + Id: viper.GetString("handler-id"), }) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not find Handler") diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index c915b3de1..340dfac11 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -22,11 +22,11 @@ func GetMQTT(ctx log.Interface) mqtt.Client { } mqttProto := "tcp" - if strings.HasSuffix(viper.GetString("mqtt-broker"), ":8883") { + if strings.HasSuffix(viper.GetString("mqtt-address"), ":8883") { mqttProto = "ssl" ctx.Fatal("TLS connections are not yet supported by ttnctl") } - broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-broker")) + broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-address")) client := mqtt.NewClient(ctx, "ttnctl", username, password, broker) ctx.WithFields(log.Fields{ @@ -49,7 +49,7 @@ func getMQTTCredentials(ctx log.Interface) (username string, password string, er } // Do not use authentication on local MQTT - if strings.HasPrefix(viper.GetString("mqtt-broker"), "localhost") { + if strings.HasPrefix(viper.GetString("mqtt-address"), "localhost") { return } diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index ca07753e7..e1b9f7ae7 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -19,7 +19,7 @@ func GetRouter(ctx log.Interface) *router.Client { defer dscConn.Close() routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ ServiceName: "router", - Id: viper.GetString("ttn-router"), + Id: viper.GetString("router-id"), }) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") @@ -40,7 +40,7 @@ func GetRouterManager(ctx log.Interface) (*grpc.ClientConn, router.RouterManager defer dscConn.Close() routerAnnouncement, err := client.Get(GetContext(ctx), &discovery.GetRequest{ ServiceName: "router", - Id: viper.GetString("ttn-router"), + Id: viper.GetString("router-id"), }) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") From 344069aa3236785ce1888219a9e268698b3285d6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 12:52:50 +0100 Subject: [PATCH 2086/2266] Fix MQTT tests --- mqtt/activations_test.go | 12 ++++++------ mqtt/downlink_test.go | 12 ++++++------ mqtt/events_test.go | 4 ++-- mqtt/uplink_test.go | 14 +++++++------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/mqtt/activations_test.go b/mqtt/activations_test.go index 0686ed216..1108533d8 100644 --- a/mqtt/activations_test.go +++ b/mqtt/activations_test.go @@ -17,7 +17,7 @@ import ( func TestPublishActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -35,7 +35,7 @@ func TestPublishActivations(t *testing.T) { func TestSubscribeDeviceActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -52,7 +52,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { func TestSubscribeAppActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -69,7 +69,7 @@ func TestSubscribeAppActivations(t *testing.T) { func TestSubscribeActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -86,7 +86,7 @@ func TestSubscribeActivations(t *testing.T) { func TestPubSubActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -117,7 +117,7 @@ func TestPubSubActivations(t *testing.T) { func TestPubSubAppActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() diff --git a/mqtt/downlink_test.go b/mqtt/downlink_test.go index 0b3d02194..f873eaefa 100644 --- a/mqtt/downlink_test.go +++ b/mqtt/downlink_test.go @@ -17,7 +17,7 @@ import ( func TestPublishDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -35,7 +35,7 @@ func TestPublishDownlink(t *testing.T) { func TestSubscribeDeviceDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -52,7 +52,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { func TestSubscribeAppDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -69,7 +69,7 @@ func TestSubscribeAppDownlink(t *testing.T) { func TestSubscribeDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -86,7 +86,7 @@ func TestSubscribeDownlink(t *testing.T) { func TestPubSubDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -117,7 +117,7 @@ func TestPubSubDownlink(t *testing.T) { func TestPubSubAppDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() diff --git a/mqtt/events_test.go b/mqtt/events_test.go index 8aa301633..e230ec043 100644 --- a/mqtt/events_test.go +++ b/mqtt/events_test.go @@ -15,7 +15,7 @@ import ( func TestPublishSubscribeAppEvents(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() var wg WaitGroup @@ -36,7 +36,7 @@ func TestPublishSubscribeAppEvents(t *testing.T) { func TestPublishSubscribeDeviceEvents(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() var wg WaitGroup diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go index fb5928b7a..252bd1a44 100644 --- a/mqtt/uplink_test.go +++ b/mqtt/uplink_test.go @@ -19,7 +19,7 @@ import ( func TestPublishUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -36,7 +36,7 @@ func TestPublishUplink(t *testing.T) { func TestPublishUplinkFields(t *testing.T) { a := New(t) ctx := GetLogger(t, "Test") - c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -107,7 +107,7 @@ func TestPublishUplinkFields(t *testing.T) { func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -122,7 +122,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { func TestSubscribeAppUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -137,7 +137,7 @@ func TestSubscribeAppUplink(t *testing.T) { func TestSubscribeUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -152,7 +152,7 @@ func TestSubscribeUplink(t *testing.T) { func TestPubSubUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -185,7 +185,7 @@ func TestPubSubUplink(t *testing.T) { func TestPubSubAppUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s:1883", host)) + c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() From 5414227b0cbd5a7c145683bed51100fdc8fea0c8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 13:18:37 +0100 Subject: [PATCH 2087/2266] Also check for "token" in IsConfigured --- api/monitor/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/monitor/client.go b/api/monitor/client.go index 2eb59dfe5..f05f7a967 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -202,7 +202,7 @@ func (cl *gatewayClient) SetToken(token string) { func (cl *gatewayClient) IsConfigured() bool { cl.RLock() defer cl.RUnlock() - return cl.token != "" + return cl.token != "" && cl.token != "token" } // SendStatus sends status to the monitor From 03ee71b96e076f216c5f4e60b25f8ea620d24627 Mon Sep 17 00:00:00 2001 From: Johan Stokking Date: Tue, 1 Nov 2016 11:23:58 +0100 Subject: [PATCH 2088/2266] Explicit reopen of channel after reconnect --- amqp/client.go | 12 ++++++++++-- amqp/client_test.go | 1 + amqp/publisher.go | 1 + amqp/subscriber.go | 1 + 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/amqp/client.go b/amqp/client.go index caccb7b66..86182d636 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -31,7 +31,7 @@ type DefaultClient struct { channels map[*DefaultChannelClient]*AMQP.Channel } -// ChannelClient represents a AMQP channel client +// ChannelClient represents an AMQP channel client type ChannelClient interface { Open() error io.Closer @@ -42,6 +42,7 @@ type DefaultChannelClient struct { ctx Logger client *DefaultClient channel *AMQP.Channel + name string exchange string exchangeType string } @@ -109,7 +110,14 @@ func (c *DefaultClient) connect(reconnect bool) (chan *AMQP.Error, error) { defer c.mutex.Unlock() for user, channel := range c.channels { channel.Close() - go user.Open() + channel, err = c.conn.Channel() + if err != nil { + c.ctx.Warnf("Failed to reopen channel %s for %s (%s)", user.name, user.exchange, err) + continue + } + c.ctx.Infof("Reopened channel %s for %s", user.name, user.exchange) + user.channel = channel + c.channels[user] = channel } } diff --git a/amqp/client_test.go b/amqp/client_test.go index a250e19f6..2dc425158 100644 --- a/amqp/client_test.go +++ b/amqp/client_test.go @@ -95,6 +95,7 @@ func TestReopenChannelClient(t *testing.T) { defer p.Close() test := func() error { + ctx.Debug("Testing publish") return p.channel.Publish("", "test", false, false, AMQP.Publishing{ Body: []byte("test"), }) diff --git a/amqp/publisher.go b/amqp/publisher.go index 00ffb7dec..478f1dde3 100644 --- a/amqp/publisher.go +++ b/amqp/publisher.go @@ -30,6 +30,7 @@ func (c *DefaultClient) NewPublisher(exchange string) Publisher { ctx: c.ctx, client: c, exchange: exchange, + name: "Publisher", }, } } diff --git a/amqp/subscriber.go b/amqp/subscriber.go index 721d78a05..8b84eef0f 100644 --- a/amqp/subscriber.go +++ b/amqp/subscriber.go @@ -45,6 +45,7 @@ func (c *DefaultClient) NewSubscriber(exchange, name string, durable, autoDelete ctx: c.ctx, client: c, exchange: exchange, + name: "Subscriber", }, name: name, durable: durable, From a1911df8535003eb6a46bfb7dd0231b272d3408e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 13:47:08 +0100 Subject: [PATCH 2089/2266] Make MQTT optional Resolves #326 --- cmd/handler.go | 12 ++++++++---- core/handler/handler.go | 35 +++++++++++++++++++++++------------ 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/cmd/handler.go b/cmd/handler.go index 362e49358..aa7292aee 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -62,10 +62,14 @@ var handlerCmd = &cobra.Command{ handler := handler.NewRedisHandler( client, viper.GetString("handler.broker-id"), - viper.GetString("handler.mqtt-username"), - viper.GetString("handler.mqtt-password"), - viper.GetString("handler.mqtt-address"), ) + if viper.GetString("handler.mqtt-address") != "" { + handler = handler.WithMQTT( + viper.GetString("handler.mqtt-username"), + viper.GetString("handler.mqtt-password"), + viper.GetString("handler.mqtt-address"), + ) + } if viper.GetString("handler.amqp-address") != "" { handler = handler.WithAMQP( viper.GetString("handler.amqp-username"), @@ -134,7 +138,7 @@ func init() { handlerCmd.Flags().String("broker-id", "dev", "The ID of the TTN Broker as announced in the Discovery server") viper.BindPFlag("handler.broker-id", handlerCmd.Flags().Lookup("broker-id")) - handlerCmd.Flags().String("mqtt-address", "", "MQTT host and port") + handlerCmd.Flags().String("mqtt-address", "", "MQTT host and port. Leave empty to disable MQTT") viper.BindPFlag("handler.mqtt-address", handlerCmd.Flags().Lookup("mqtt-address")) handlerCmd.Flags().String("mqtt-username", "", "MQTT username") diff --git a/core/handler/handler.go b/core/handler/handler.go index 457e8f4f0..ff434e63b 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -26,6 +26,7 @@ type Handler interface { core.ComponentInterface core.ManagementInterface + WithMQTT(username, password string, brokers ...string) Handler WithAMQP(username, password, host, exchange string) Handler HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) error @@ -35,14 +36,11 @@ type Handler interface { } // NewRedisHandler creates a new Redis-backed Handler -func NewRedisHandler(client *redis.Client, ttnBrokerID string, mqttUsername string, mqttPassword string, mqttBrokers ...string) Handler { +func NewRedisHandler(client *redis.Client, ttnBrokerID string) Handler { return &handler{ devices: device.NewRedisDeviceStore(client, "handler"), applications: application.NewRedisApplicationStore(client, "handler"), ttnBrokerID: ttnBrokerID, - mqttUsername: mqttUsername, - mqttPassword: mqttPassword, - mqttBrokers: mqttBrokers, } } @@ -63,6 +61,7 @@ type handler struct { mqttUsername string mqttPassword string mqttBrokers []string + mqttEnabled bool mqttUp chan *types.UplinkMessage mqttEvent chan *types.DeviceEvent @@ -80,6 +79,14 @@ var ( AMQPDownlinkQueue = "ttn-handler-downlink" ) +func (h *handler) WithMQTT(username, password string, brokers ...string) Handler { + h.mqttUsername = username + h.mqttPassword = password + h.mqttBrokers = brokers + h.mqttEnabled = true + return h +} + func (h *handler) WithAMQP(username, password, host, exchange string) Handler { h.amqpUsername = username h.amqpPassword = password @@ -101,13 +108,15 @@ func (h *handler) Init(c *core.Component) error { return err } - var brokers []string - for _, broker := range h.mqttBrokers { - brokers = append(brokers, fmt.Sprintf("tcp://%s", broker)) - } - err = h.HandleMQTT(h.mqttUsername, h.mqttPassword, brokers...) - if err != nil { - return err + if h.mqttEnabled { + var brokers []string + for _, broker := range h.mqttBrokers { + brokers = append(brokers, fmt.Sprintf("tcp://%s", broker)) + } + err = h.HandleMQTT(h.mqttUsername, h.mqttPassword, brokers...) + if err != nil { + return err + } } if h.amqpEnabled { @@ -128,7 +137,9 @@ func (h *handler) Init(c *core.Component) error { } func (h *handler) Shutdown() { - h.mqttClient.Disconnect() + if h.mqttEnabled { + h.mqttClient.Disconnect() + } if h.amqpEnabled { h.amqpClient.Disconnect() } From 37f71207c346da172e01a26cf45360f2769a69a6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 13:53:40 +0100 Subject: [PATCH 2090/2266] Warn when MQTT/AMQP are not enabled --- cmd/handler.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/handler.go b/cmd/handler.go index aa7292aee..65c4659ab 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -69,6 +69,8 @@ var handlerCmd = &cobra.Command{ viper.GetString("handler.mqtt-password"), viper.GetString("handler.mqtt-address"), ) + } else { + ctx.Warn("MQTT is not enabled in your configuration") } if viper.GetString("handler.amqp-address") != "" { handler = handler.WithAMQP( @@ -76,6 +78,8 @@ var handlerCmd = &cobra.Command{ viper.GetString("handler.amqp-password"), viper.GetString("handler.amqp-address"), viper.GetString("handler.amqp-exchange")) + } else { + ctx.Warn("AMQP is not enabled in your configuration") } err = handler.Init(component) if err != nil { From 1e4eb4d53d2249c6f3467c291d97f9796d8035fd Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 1 Nov 2016 13:55:34 +0100 Subject: [PATCH 2091/2266] Update PF templates with port --- ttnctl/cmd/applications_pf.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index d3b235462..4eb3e1b89 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -20,9 +20,11 @@ converting and validating binary payload.`, INFO Connecting with Handler... INFO Found Application INFO Decoder function -function Decoder(bytes) { +function Decoder(bytes, port) { var decoded = {}; - decoded.led = bytes[0]; + if (port === 1) { + decoded.led = bytes[0]; + } return decoded; } INFO No converter function From 7dd5b1067cd54196f38cc0803cfaf359c40150ca Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 1 Nov 2016 13:58:10 +0100 Subject: [PATCH 2092/2266] Update PF placeholders with port --- ttnctl/cmd/applications_pf_set.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 782926d6f..2be2bd1a7 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -23,20 +23,20 @@ The functions are read from the supplied file or from STDIN.`, Example: `$ ttnctl applications pf set decoder INFO Discovering Handler... INFO Connecting with Handler... -function Decoder(bytes) { +function Decoder(bytes, port) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. var decoded = {}; - // decoded.led = bytes[0]; + // if (port === 1) decoded.led = bytes[0]; return decoded; } ########## Write your Decoder here and end with Ctrl+D (EOF): -function Decoder(bytes) { +function Decoder(bytes, port) { var decoded = {}; - decoded.led = bytes[0]; + if (port === 1) decoded.led = bytes[0]; return decoded; } @@ -83,12 +83,12 @@ function Decoder(bytes) { } else { switch function { case "decoder": - fmt.Println(`function Decoder(bytes) { + fmt.Println(`function Decoder(bytes, port) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. var decoded = {}; - // decoded.led = bytes[0]; + // if (port === 1) decoded.led = bytes[0]; return decoded; } From 54376bb66d3f0372fbd95b783ef93072439b0ae0 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 1 Nov 2016 13:58:26 +0100 Subject: [PATCH 2093/2266] Update applications_pf.go --- ttnctl/cmd/applications_pf.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index 4eb3e1b89..ea5a09065 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -22,9 +22,7 @@ converting and validating binary payload.`, INFO Decoder function function Decoder(bytes, port) { var decoded = {}; - if (port === 1) { - decoded.led = bytes[0]; - } + if (port === 1) decoded.led = bytes[0]; return decoded; } INFO No converter function From ba5ba179febc248ee758c7a63145389821de0ee8 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Tue, 1 Nov 2016 14:01:10 +0100 Subject: [PATCH 2094/2266] Update encoder, validator and converter as well --- ttnctl/cmd/applications_pf_set.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index 2be2bd1a7..c825b7f44 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -95,12 +95,12 @@ function Decoder(bytes, port) { ########## Write your Decoder here and end with Ctrl+D (EOF):`) app.Decoder = readFunction() case "converter": - fmt.Println(`function Converter(decoded) { + fmt.Println(`function Converter(decoded, port) { // Merge, split or otherwise // mutate decoded fields. var converted = decoded; - // if (converted.led === 0 || converted.led === 1) { + // if (port === 1 && (converted.led === 0 || converted.led === 1)) { // converted.led = Boolean(converted.led); // } @@ -109,11 +109,11 @@ function Decoder(bytes, port) { ########## Write your Converter here and end with Ctrl+D (EOF):`) app.Converter = readFunction() case "validator": - fmt.Println(`function Validator(converted) { + fmt.Println(`function Validator(converted, port) { // Return false if the decoded, converted // message is invalid and should be dropped. - // if (typeof converted.led !== 'boolean') { + // if (port === 1 && typeof converted.led !== 'boolean') { // return false; // } @@ -122,12 +122,12 @@ function Decoder(bytes, port) { ########## Write your Validator here and end with Ctrl+D (EOF):`) app.Validator = readFunction() case "encoder": - fmt.Println(`function Encoder(object) { + fmt.Println(`function Encoder(object, port) { // Encode downlink messages sent as // object to an array or buffer of bytes. var bytes = []; - // bytes[0] = object.led ? 1 : 0; + // if (port === 1) bytes[0] = object.led ? 1 : 0; return bytes; } From d0260cfcb6ff94116be3eac437e6da7a983914a9 Mon Sep 17 00:00:00 2001 From: Antoine Rondelet Date: Tue, 1 Nov 2016 15:20:50 +0100 Subject: [PATCH 2095/2266] Use %v instead of %s in print_config --- ttnctl/util/print_config.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/util/print_config.go b/ttnctl/util/print_config.go index 03ba62ab9..26e1be341 100644 --- a/ttnctl/util/print_config.go +++ b/ttnctl/util/print_config.go @@ -40,5 +40,5 @@ func PrintConfig(ctx log.Interface, debug bool) { } func printKV(key, val interface{}) { - fmt.Printf("%20s: %s\n", key, val) + fmt.Printf("%20s: %v\n", key, val) } From 3645ef210a1da403043b1e894bddc235ac9a83fd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 1 Nov 2016 16:44:26 +0100 Subject: [PATCH 2096/2266] Update Protos Add Message proto Add Validations Migrate CFList --- api/broker/broker.pb.go | 724 +++- api/broker/broker.proto | 26 +- api/broker/validation.go | 31 +- api/handler/handler.pb.go | 228 +- api/handler/handler.proto | 2 +- api/handler/validation.go | 11 +- api/protocol/lorawan/lorawan.pb.go | 2905 +++++++++++++++-- api/protocol/lorawan/lorawan.proto | 81 +- api/protocol/lorawan/message_conversion.go | 192 ++ .../lorawan/message_conversion_test.go | 58 + api/protocol/lorawan/validation.go | 37 + api/protocol/protocol.pb.go | 278 +- api/protocol/protocol.proto | 6 + api/protocol/validation.go | 28 + api/router/router.pb.go | 400 ++- api/router/router.proto | 15 +- api/router/validation.go | 6 - core/handler/activation.go | 1 - core/networkserver/activation.go | 6 +- core/networkserver/activation_test.go | 6 +- core/router/activation.go | 2 +- core/types/activation.go | 219 ++ core/types/activation_test.go | 184 ++ 23 files changed, 4779 insertions(+), 667 deletions(-) create mode 100644 api/protocol/lorawan/message_conversion.go create mode 100644 api/protocol/lorawan/message_conversion_test.go create mode 100644 core/types/activation_test.go diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index f0e299db6..7ce07dfdb 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -84,7 +84,8 @@ func (m *DownlinkOption) GetGatewayConfig() *gateway.TxConfiguration { // received from the Router type UplinkMessage struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` // NOTE: For LoRaWAN, the Router doesn't know the DevEUI/ID and AppEUI/ID DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` @@ -100,6 +101,13 @@ func (m *UplinkMessage) String() string { return proto.CompactTextStr func (*UplinkMessage) ProtoMessage() {} func (*UplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{1} } +func (m *UplinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *UplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { if m != nil { return m.ProtocolMetadata @@ -124,6 +132,7 @@ func (m *UplinkMessage) GetDownlinkOptions() []*DownlinkOption { // received from the Handler, sent to the Router, used as Template type DownlinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` @@ -136,6 +145,13 @@ func (m *DownlinkMessage) String() string { return proto.CompactTextS func (*DownlinkMessage) ProtoMessage() {} func (*DownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{2} } +func (m *DownlinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DownlinkMessage) GetDownlinkOption() *DownlinkOption { if m != nil { return m.DownlinkOption @@ -145,8 +161,9 @@ func (m *DownlinkMessage) GetDownlinkOption() *DownlinkOption { // sent to the Router, used as Template type DeviceActivationResponse struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DownlinkOption *DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DownlinkOption *DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` } func (m *DeviceActivationResponse) Reset() { *m = DeviceActivationResponse{} } @@ -154,6 +171,13 @@ func (m *DeviceActivationResponse) String() string { return proto.Com func (*DeviceActivationResponse) ProtoMessage() {} func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{3} } +func (m *DeviceActivationResponse) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DeviceActivationResponse) GetDownlinkOption() *DownlinkOption { if m != nil { return m.DownlinkOption @@ -164,6 +188,7 @@ func (m *DeviceActivationResponse) GetDownlinkOption() *DownlinkOption { // sent to the Handler type DeduplicatedUplinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` @@ -179,6 +204,13 @@ func (m *DeduplicatedUplinkMessage) String() string { return proto.Co func (*DeduplicatedUplinkMessage) ProtoMessage() {} func (*DeduplicatedUplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{4} } +func (m *DeduplicatedUplinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DeduplicatedUplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { if m != nil { return m.ProtocolMetadata @@ -203,6 +235,7 @@ func (m *DeduplicatedUplinkMessage) GetResponseTemplate() *DownlinkMessage { // received from the Router type DeviceActivationRequest struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` @@ -216,6 +249,13 @@ func (m *DeviceActivationRequest) String() string { return proto.Comp func (*DeviceActivationRequest) ProtoMessage() {} func (*DeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{5} } +func (m *DeviceActivationRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { if m != nil { return m.ProtocolMetadata @@ -247,6 +287,7 @@ func (m *DeviceActivationRequest) GetDownlinkOptions() []*DownlinkOption { // sent to the Handler type DeduplicatedDeviceActivationRequest struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` @@ -265,6 +306,13 @@ func (*DeduplicatedDeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{6} } +func (m *DeduplicatedDeviceActivationRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DeduplicatedDeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { if m != nil { return m.ProtocolMetadata @@ -295,6 +343,7 @@ func (m *DeduplicatedDeviceActivationRequest) GetResponseTemplate() *DeviceActiv type ActivationChallengeRequest struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` AppId string `protobuf:"bytes,13,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` @@ -306,8 +355,16 @@ func (m *ActivationChallengeRequest) String() string { return proto.C func (*ActivationChallengeRequest) ProtoMessage() {} func (*ActivationChallengeRequest) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{7} } +func (m *ActivationChallengeRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + type ActivationChallengeResponse struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` } func (m *ActivationChallengeResponse) Reset() { *m = ActivationChallengeResponse{} } @@ -317,6 +374,13 @@ func (*ActivationChallengeResponse) Descriptor() ([]byte, []int) { return fileDescriptorBroker, []int{8} } +func (m *ActivationChallengeResponse) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + // message SubscribeRequest is used by a Handler to subscribe to uplink messages type SubscribeRequest struct { } @@ -893,25 +957,35 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n3, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n3 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n3, err := m.DevEui.MarshalTo(data[i:]) + n4, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n4 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n4, err := m.AppEui.MarshalTo(data[i:]) + n5, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n5 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -931,11 +1005,11 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n5, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n6, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n6 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -943,11 +1017,11 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n6, err := m.GatewayMetadata.MarshalTo(data[i:]) + n7, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n7 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -987,25 +1061,35 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n8, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n7, err := m.DevEui.MarshalTo(data[i:]) + n9, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n9 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n8, err := m.AppEui.MarshalTo(data[i:]) + n10, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n10 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1025,11 +1109,11 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n9, err := m.DownlinkOption.MarshalTo(data[i:]) + n11, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n11 } return i, nil } @@ -1055,15 +1139,25 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n12, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n12 + } if m.DownlinkOption != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n10, err := m.DownlinkOption.MarshalTo(data[i:]) + n13, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n13 } return i, nil } @@ -1089,25 +1183,35 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n14, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n14 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n11, err := m.DevEui.MarshalTo(data[i:]) + n15, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n15 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n12, err := m.AppEui.MarshalTo(data[i:]) + n16, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n16 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1127,11 +1231,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n13, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n17, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n17 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1160,11 +1264,11 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n14, err := m.ResponseTemplate.MarshalTo(data[i:]) + n18, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n18 } return i, nil } @@ -1190,25 +1294,35 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n19, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n19 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n15, err := m.DevEui.MarshalTo(data[i:]) + n20, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n20 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n16, err := m.AppEui.MarshalTo(data[i:]) + n21, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n16 + i += n21 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -1216,11 +1330,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n17, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n22, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n17 + i += n22 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -1228,11 +1342,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n18, err := m.GatewayMetadata.MarshalTo(data[i:]) + n23, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n18 + i += n23 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -1240,11 +1354,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n19, err := m.ActivationMetadata.MarshalTo(data[i:]) + n24, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n19 + i += n24 } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { @@ -1284,25 +1398,35 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n25, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n25 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n20, err := m.DevEui.MarshalTo(data[i:]) + n26, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n20 + i += n26 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n21, err := m.AppEui.MarshalTo(data[i:]) + n27, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n21 + i += n27 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1322,11 +1446,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n22, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n28, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n22 + i += n28 } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { @@ -1348,11 +1472,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n23, err := m.ActivationMetadata.MarshalTo(data[i:]) + n29, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n23 + i += n29 } if m.ServerTime != 0 { data[i] = 0xc0 @@ -1367,11 +1491,11 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n24, err := m.ResponseTemplate.MarshalTo(data[i:]) + n30, err := m.ResponseTemplate.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n24 + i += n30 } return i, nil } @@ -1397,25 +1521,35 @@ func (m *ActivationChallengeRequest) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n31, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n31 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n25, err := m.DevEui.MarshalTo(data[i:]) + n32, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n25 + i += n32 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n26, err := m.AppEui.MarshalTo(data[i:]) + n33, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n26 + i += n33 } if len(m.AppId) > 0 { data[i] = 0x6a @@ -1453,6 +1587,16 @@ func (m *ActivationChallengeResponse) MarshalTo(data []byte) (int, error) { i = encodeVarintBroker(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintBroker(data, i, uint64(m.Message.Size())) + n34, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n34 + } return i, nil } @@ -1511,71 +1655,71 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintBroker(data, i, uint64(m.System.Size())) - n27, err := m.System.MarshalTo(data[i:]) + n35, err := m.System.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n27 + i += n35 } if m.Component != nil { data[i] = 0x12 i++ i = encodeVarintBroker(data, i, uint64(m.Component.Size())) - n28, err := m.Component.MarshalTo(data[i:]) + n36, err := m.Component.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n28 + i += n36 } if m.Uplink != nil { data[i] = 0x5a i++ i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n29, err := m.Uplink.MarshalTo(data[i:]) + n37, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n29 + i += n37 } if m.UplinkUnique != nil { data[i] = 0x62 i++ i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n30, err := m.UplinkUnique.MarshalTo(data[i:]) + n38, err := m.UplinkUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n30 + i += n38 } if m.Downlink != nil { data[i] = 0x6a i++ i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n31, err := m.Downlink.MarshalTo(data[i:]) + n39, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n31 + i += n39 } if m.Activations != nil { data[i] = 0x72 i++ i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n32, err := m.Activations.MarshalTo(data[i:]) + n40, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n32 + i += n40 } if m.ActivationsUnique != nil { data[i] = 0x7a i++ i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n33, err := m.ActivationsUnique.MarshalTo(data[i:]) + n41, err := m.ActivationsUnique.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n33 + i += n41 } if m.Deduplication != nil { data[i] = 0x82 @@ -1583,11 +1727,11 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n34, err := m.Deduplication.MarshalTo(data[i:]) + n42, err := m.Deduplication.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n34 + i += n42 } if m.ConnectedRouters != 0 { data[i] = 0xa8 @@ -1698,6 +1842,10 @@ func (m *UplinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1738,6 +1886,10 @@ func (m *DownlinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1768,6 +1920,10 @@ func (m *DeviceActivationResponse) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DownlinkOption != nil { l = m.DownlinkOption.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1782,6 +1938,10 @@ func (m *DeduplicatedUplinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1825,6 +1985,10 @@ func (m *DeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1861,6 +2025,10 @@ func (m *DeduplicatedDeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1908,6 +2076,10 @@ func (m *ActivationChallengeRequest) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovBroker(uint64(l)) @@ -1934,6 +2106,10 @@ func (m *ActivationChallengeResponse) Size() (n int) { if l > 0 { n += 1 + l + sovBroker(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovBroker(uint64(l)) + } return n } @@ -2292,6 +2468,39 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -2592,6 +2801,39 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -2828,6 +3070,39 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DownlinkOption", wireType) @@ -2942,6 +3217,39 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -3261,6 +3569,39 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -3536,6 +3877,39 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -3888,6 +4262,39 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -4091,6 +4498,39 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowBroker + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthBroker + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipBroker(data[iNdEx:]) @@ -4782,77 +5222,79 @@ func init() { } var fileDescriptorBroker = []byte{ - // 1151 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x58, 0x5f, 0x6f, 0xdb, 0x54, - 0x14, 0xc7, 0xcd, 0x96, 0x35, 0x27, 0xcd, 0x9f, 0xde, 0xad, 0xad, 0x97, 0xd1, 0x24, 0x04, 0x69, - 0x8a, 0x80, 0x25, 0x5b, 0x10, 0x4c, 0x48, 0x13, 0x55, 0xfa, 0x47, 0x50, 0xa4, 0x8c, 0xc9, 0x6d, - 0x79, 0x40, 0x48, 0xd1, 0x8d, 0x7d, 0xea, 0x5c, 0xd5, 0xb1, 0x3d, 0xdf, 0xeb, 0x74, 0x7d, 0x47, - 0x7c, 0x06, 0x5e, 0xe1, 0x63, 0x20, 0xf1, 0xce, 0x1b, 0x3c, 0xf3, 0x00, 0xa8, 0xbc, 0xf0, 0xc2, - 0x77, 0x40, 0xbe, 0xbe, 0x76, 0x92, 0xb6, 0x59, 0x07, 0xea, 0x03, 0x63, 0x3c, 0xc5, 0xf7, 0x77, - 0x7e, 0x3e, 0x3e, 0xfe, 0x9d, 0x3f, 0xbe, 0x37, 0xf0, 0xd0, 0x66, 0x62, 0x18, 0x0e, 0x5a, 0xa6, - 0x37, 0x6a, 0xef, 0x0f, 0x71, 0x7f, 0xc8, 0x5c, 0x9b, 0x3f, 0x46, 0x71, 0xec, 0x05, 0x47, 0x6d, - 0x21, 0xdc, 0x36, 0xf5, 0x59, 0x7b, 0x10, 0x78, 0x47, 0x18, 0xa8, 0x9f, 0x96, 0x1f, 0x78, 0xc2, - 0x23, 0xd9, 0x78, 0x55, 0xb9, 0x63, 0x7b, 0x9e, 0xed, 0x60, 0x5b, 0xa2, 0x83, 0xf0, 0xb0, 0x8d, - 0x23, 0x5f, 0x9c, 0xc4, 0xa4, 0xca, 0xbd, 0x29, 0xef, 0xb6, 0x67, 0x7b, 0x13, 0x56, 0xb4, 0x92, - 0x0b, 0x79, 0xa5, 0xe8, 0xcb, 0xc9, 0x03, 0xa9, 0xcf, 0x14, 0x54, 0x4b, 0x20, 0xb9, 0x34, 0x3d, - 0x27, 0xbd, 0x50, 0x84, 0xf5, 0x84, 0x60, 0x53, 0x81, 0xc7, 0xf4, 0x24, 0xf9, 0x8d, 0xcd, 0x8d, - 0xaf, 0x16, 0xa0, 0xb8, 0xed, 0x1d, 0xbb, 0x0e, 0x73, 0x8f, 0x3e, 0xf5, 0x05, 0xf3, 0x5c, 0x52, - 0x05, 0x60, 0x16, 0xba, 0x82, 0x1d, 0x32, 0x0c, 0x74, 0xad, 0xae, 0x35, 0x73, 0xc6, 0x14, 0x42, - 0xd6, 0x01, 0x94, 0x8f, 0x3e, 0xb3, 0xf4, 0x05, 0x69, 0xcf, 0x29, 0x64, 0xd7, 0x22, 0xb7, 0xe0, - 0x3a, 0x37, 0xbd, 0x00, 0xf5, 0x4c, 0x5d, 0x6b, 0x16, 0x8c, 0x78, 0x41, 0x2a, 0xb0, 0x68, 0x21, - 0xb5, 0x1c, 0xe6, 0xa2, 0x7e, 0xad, 0xae, 0x35, 0x33, 0x46, 0xba, 0x26, 0x9b, 0x50, 0x4a, 0x82, - 0xee, 0x9b, 0x9e, 0x7b, 0xc8, 0x6c, 0xfd, 0x7a, 0x5d, 0x6b, 0xe6, 0x3b, 0xb7, 0x5b, 0xe9, 0xcb, - 0xec, 0x3f, 0xdb, 0x92, 0x96, 0x30, 0xa0, 0x51, 0x90, 0x46, 0x31, 0xb1, 0xc4, 0x30, 0xd9, 0x80, - 0x62, 0x12, 0x94, 0x72, 0x91, 0x95, 0x2e, 0xf4, 0x56, 0xf2, 0xbe, 0x67, 0x3d, 0x14, 0x94, 0x21, - 0x46, 0x1b, 0xdf, 0x67, 0xa0, 0x70, 0xe0, 0x47, 0x32, 0xf4, 0x90, 0x73, 0x6a, 0x23, 0xd1, 0xe1, - 0x86, 0x4f, 0x4f, 0x1c, 0x8f, 0x5a, 0x52, 0x84, 0x25, 0x23, 0x59, 0x92, 0xc7, 0x70, 0xc3, 0xc2, - 0x71, 0x1f, 0x43, 0xa6, 0xe7, 0x23, 0xcb, 0xe6, 0x7b, 0x3f, 0xff, 0x52, 0x7b, 0x70, 0x59, 0xa5, - 0x44, 0x3a, 0xb4, 0xc5, 0x89, 0x8f, 0xbc, 0xb5, 0x8d, 0xe3, 0x9d, 0x83, 0x5d, 0x23, 0x6b, 0xe1, - 0x78, 0x27, 0x64, 0x91, 0x3f, 0xea, 0xfb, 0xd2, 0xdf, 0xd2, 0x3f, 0xf2, 0xd7, 0xf5, 0x7d, 0xe9, - 0x8f, 0xfa, 0x7e, 0xe4, 0x6f, 0x05, 0xa2, 0xab, 0x28, 0x3b, 0x05, 0x99, 0x9d, 0xeb, 0xd4, 0xf7, - 0x77, 0xad, 0x08, 0x8e, 0xc2, 0x66, 0x96, 0x5e, 0x8c, 0x61, 0x0b, 0xc7, 0xbb, 0x16, 0xe9, 0xc2, - 0x72, 0x2a, 0xff, 0x08, 0x05, 0xb5, 0xa8, 0xa0, 0xfa, 0x8a, 0x54, 0xef, 0xd6, 0x24, 0x01, 0xc6, - 0xb3, 0x9e, 0xb2, 0x19, 0xe5, 0x04, 0x4c, 0x10, 0xf2, 0x21, 0x94, 0x13, 0xf5, 0x53, 0x0f, 0xab, - 0xd2, 0xc3, 0xcd, 0x54, 0xff, 0x29, 0x07, 0x25, 0x85, 0xa5, 0xf7, 0x77, 0xa1, 0x6c, 0xa9, 0x22, - 0xec, 0x7b, 0xb2, 0x0a, 0xb9, 0x5e, 0xab, 0x67, 0x9a, 0xf9, 0xce, 0x6a, 0x4b, 0x75, 0xd5, 0x6c, - 0x91, 0x1a, 0x25, 0x6b, 0x66, 0xcd, 0x1b, 0xdf, 0x2d, 0x40, 0x29, 0xe1, 0xbc, 0x6a, 0x19, 0xdc, - 0x80, 0xd2, 0x19, 0xf9, 0x54, 0xfe, 0xe6, 0xa9, 0x57, 0x9c, 0x55, 0xaf, 0x11, 0x82, 0xbe, 0x8d, - 0x63, 0x66, 0x62, 0xd7, 0x14, 0x6c, 0x1c, 0xf7, 0x07, 0x72, 0xdf, 0x73, 0xf9, 0xf3, 0x44, 0xbc, - 0xe0, 0xb1, 0xf9, 0xbf, 0xf5, 0xd8, 0x3f, 0x33, 0x70, 0x7b, 0x1b, 0xad, 0xd0, 0x77, 0x98, 0x49, - 0x05, 0x5a, 0xff, 0xf7, 0xdf, 0x95, 0xf6, 0x5f, 0xe6, 0x85, 0xfb, 0xaf, 0x06, 0x79, 0x8e, 0xc1, - 0x18, 0x83, 0xbe, 0x60, 0x23, 0xd4, 0xd7, 0xe4, 0x80, 0x86, 0x18, 0xda, 0x67, 0x23, 0x24, 0xdb, - 0xb0, 0x1c, 0xa8, 0x82, 0xe8, 0x0b, 0x1c, 0xf9, 0x0e, 0x15, 0xa8, 0xd7, 0x64, 0x8c, 0x6b, 0x67, - 0x93, 0xad, 0xf2, 0x67, 0x94, 0x93, 0x3b, 0xf6, 0xd5, 0x0d, 0x8d, 0x3f, 0x32, 0xb0, 0x76, 0xbe, - 0xce, 0x9e, 0x86, 0xc8, 0xc5, 0x4b, 0x9c, 0xed, 0x7f, 0xc1, 0xfc, 0xec, 0xc1, 0x4d, 0x9a, 0x2a, - 0x3a, 0x71, 0xb1, 0x26, 0x5d, 0xbc, 0x3e, 0x09, 0x62, 0x22, 0x7b, 0xea, 0x8b, 0xd0, 0x73, 0xd8, - 0x55, 0x8c, 0xe3, 0x1f, 0xaf, 0xc1, 0x9b, 0xd3, 0xad, 0xfd, 0xdf, 0x4b, 0xfb, 0x4b, 0xd7, 0xe4, - 0x57, 0x5c, 0x24, 0x67, 0x66, 0x86, 0x7e, 0x6e, 0x66, 0xf4, 0xe6, 0xcf, 0x8c, 0x7a, 0x5a, 0x46, - 0x73, 0xbe, 0x3a, 0x17, 0x0c, 0x8f, 0x2f, 0x17, 0xa0, 0x32, 0x21, 0x6e, 0x0d, 0xa9, 0xe3, 0xa0, - 0x6b, 0xe3, 0x2b, 0x56, 0x48, 0x8d, 0x87, 0x70, 0xe7, 0x42, 0x15, 0x2e, 0xfb, 0x5a, 0x37, 0x08, - 0x94, 0xf7, 0xc2, 0x01, 0x37, 0x03, 0x36, 0x48, 0x44, 0x6b, 0x94, 0xa0, 0xb0, 0x27, 0xa8, 0x08, - 0x79, 0x02, 0xfc, 0x9a, 0x81, 0x6c, 0x8c, 0x90, 0x26, 0x64, 0xf9, 0x09, 0x17, 0x38, 0x92, 0x8e, - 0xf2, 0x9d, 0x72, 0x2b, 0x3a, 0x75, 0xec, 0x49, 0x28, 0xa2, 0x70, 0x43, 0xd9, 0xc9, 0x03, 0xc8, - 0x99, 0xde, 0xc8, 0xf7, 0x5c, 0x74, 0x85, 0x3c, 0x0f, 0x44, 0x15, 0x19, 0x91, 0xb7, 0x12, 0x34, - 0xe6, 0x4f, 0x58, 0xa4, 0x01, 0xd9, 0x50, 0x7e, 0xec, 0xd5, 0x8e, 0x01, 0x24, 0xdf, 0xa0, 0x02, - 0xb9, 0xa1, 0x2c, 0xa4, 0x0d, 0x85, 0xf8, 0xaa, 0x1f, 0xba, 0xec, 0x69, 0x88, 0x52, 0xed, 0x59, - 0xea, 0x52, 0x4c, 0x38, 0x90, 0x76, 0x72, 0x17, 0x16, 0x93, 0x31, 0x24, 0xa5, 0x9c, 0xe5, 0xa6, - 0x36, 0xf2, 0x0e, 0xe4, 0x27, 0xf5, 0xcc, 0xa5, 0xbc, 0xb3, 0xd4, 0x69, 0x33, 0xf9, 0x00, 0xa6, - 0xaa, 0x9f, 0x27, 0xb1, 0x94, 0xce, 0xdd, 0xb4, 0x3c, 0xc5, 0x52, 0x01, 0xbd, 0x0f, 0x05, 0x2b, - 0x9d, 0x81, 0xd1, 0xf6, 0xa8, 0x3c, 0xa5, 0xe4, 0x13, 0x0c, 0xcc, 0xe8, 0x4c, 0xe5, 0x20, 0x37, - 0x66, 0x69, 0xe4, 0x6d, 0x58, 0x36, 0x3d, 0xd7, 0x45, 0x53, 0xa0, 0xd5, 0x0f, 0xbc, 0x50, 0x60, - 0xc0, 0xe5, 0xb0, 0x28, 0x18, 0xe5, 0xd4, 0x60, 0xc4, 0x38, 0xb9, 0x07, 0x64, 0x42, 0x1e, 0x52, - 0xd7, 0x72, 0x22, 0xf6, 0xaa, 0x64, 0x4f, 0xdc, 0x7c, 0xac, 0x0c, 0x8d, 0xcf, 0xa0, 0xda, 0xf5, - 0xd3, 0x47, 0x29, 0xd8, 0x40, 0x9b, 0x71, 0x11, 0x1f, 0x8c, 0xa6, 0xea, 0x51, 0x9b, 0xae, 0xc7, - 0x75, 0x00, 0xe5, 0x7d, 0xea, 0xd8, 0xa7, 0x90, 0x5d, 0xab, 0xf3, 0xed, 0x02, 0x64, 0x37, 0x65, - 0x53, 0x93, 0x0d, 0xc8, 0x75, 0x39, 0xf7, 0x4c, 0x46, 0x05, 0x92, 0x95, 0xa4, 0xd5, 0x67, 0x36, - 0x77, 0x95, 0x79, 0xbb, 0x86, 0xa6, 0x76, 0x5f, 0x23, 0x9f, 0x40, 0x2e, 0x2d, 0x55, 0xa2, 0x27, - 0xcc, 0xb3, 0xd5, 0x5b, 0x79, 0x63, 0x32, 0x45, 0xe6, 0xec, 0x21, 0xef, 0x6b, 0xe4, 0x11, 0xdc, - 0x78, 0x12, 0x0e, 0x1c, 0xc6, 0x87, 0x64, 0xde, 0x33, 0x2b, 0xab, 0xad, 0xf8, 0x90, 0xde, 0x4a, - 0x8e, 0xdf, 0xad, 0x9d, 0xe8, 0x90, 0xde, 0xd4, 0x48, 0x0f, 0x16, 0x55, 0xb7, 0x21, 0xa9, 0xcd, - 0x1f, 0x5a, 0x71, 0x3c, 0x97, 0x4e, 0xb5, 0xce, 0x37, 0x1a, 0x14, 0x62, 0x91, 0x7a, 0xd4, 0xa5, - 0x36, 0x06, 0xe4, 0x0b, 0xa8, 0xc4, 0xe2, 0x63, 0x70, 0x3e, 0x2d, 0xe4, 0x6e, 0xe2, 0xf1, 0xf9, - 0x29, 0x9b, 0xf7, 0x02, 0xa4, 0x03, 0xb9, 0x8f, 0x50, 0xa8, 0x86, 0x4e, 0x33, 0x31, 0xd3, 0xf2, - 0x95, 0xe2, 0x2c, 0xbc, 0xf9, 0xe8, 0x87, 0xd3, 0xaa, 0xf6, 0xd3, 0x69, 0x55, 0xfb, 0xed, 0xb4, - 0xaa, 0x7d, 0xfd, 0x7b, 0xf5, 0xb5, 0xcf, 0xdf, 0x7a, 0xf1, 0xff, 0x40, 0x06, 0x59, 0x19, 0xc1, - 0xbb, 0x7f, 0x05, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xe5, 0xfc, 0x23, 0x38, 0x11, 0x00, 0x00, + // 1178 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xec, 0x58, 0xdf, 0x8e, 0xdb, 0xc4, + 0x17, 0xfe, 0x79, 0x77, 0x9b, 0xed, 0x9e, 0x34, 0xff, 0xa6, 0xed, 0xae, 0x9b, 0xfe, 0x9a, 0x84, + 0x20, 0x55, 0x11, 0xa5, 0x49, 0x1b, 0x04, 0x08, 0xa9, 0xa2, 0xca, 0x76, 0x2b, 0x58, 0xa4, 0x94, + 0xca, 0xdd, 0x72, 0x81, 0x90, 0xa2, 0x89, 0x7d, 0xea, 0x8c, 0xea, 0xd8, 0xae, 0x67, 0x9c, 0x36, + 0x2f, 0xc0, 0x1b, 0x20, 0x21, 0xee, 0xe8, 0x1b, 0xf0, 0x16, 0x5c, 0x72, 0xcd, 0x05, 0xa0, 0x72, + 0x81, 0xc4, 0x33, 0x70, 0x81, 0x3c, 0x9e, 0xb1, 0x93, 0xdd, 0xa6, 0x2d, 0x68, 0x25, 0x40, 0xdd, + 0x2b, 0x7b, 0xbe, 0xf3, 0xcd, 0xe7, 0xe3, 0x73, 0xce, 0x1c, 0xcf, 0x18, 0xde, 0x77, 0x99, 0x98, + 0xc4, 0xe3, 0xae, 0x1d, 0x4c, 0x7b, 0x07, 0x13, 0x3c, 0x98, 0x30, 0xdf, 0xe5, 0x77, 0x50, 0x3c, + 0x0e, 0xa2, 0x87, 0x3d, 0x21, 0xfc, 0x1e, 0x0d, 0x59, 0x6f, 0x1c, 0x05, 0x0f, 0x31, 0x52, 0x97, + 0x6e, 0x18, 0x05, 0x22, 0x20, 0x85, 0x74, 0x54, 0xbf, 0xe8, 0x06, 0x81, 0xeb, 0x61, 0x4f, 0xa2, + 0xe3, 0xf8, 0x41, 0x0f, 0xa7, 0xa1, 0x98, 0xa7, 0xa4, 0xfa, 0xd5, 0x05, 0x75, 0x37, 0x70, 0x83, + 0x9c, 0x95, 0x8c, 0xe4, 0x40, 0xde, 0x29, 0x7a, 0x4d, 0x3f, 0x90, 0x86, 0x4c, 0x41, 0x4d, 0x0d, + 0xc9, 0xa1, 0x1d, 0x78, 0xd9, 0x8d, 0x22, 0x5c, 0xd2, 0x04, 0x97, 0x0a, 0x7c, 0x4c, 0xe7, 0xfa, + 0x9a, 0x9a, 0xdb, 0x5f, 0xae, 0x41, 0x79, 0x2f, 0x78, 0xec, 0x7b, 0xcc, 0x7f, 0xf8, 0x69, 0x28, + 0x58, 0xe0, 0x93, 0x06, 0x00, 0x73, 0xd0, 0x17, 0xec, 0x01, 0xc3, 0xc8, 0x34, 0x5a, 0x46, 0x67, + 0xcb, 0x5a, 0x40, 0xc8, 0x25, 0x00, 0xa5, 0x31, 0x62, 0x8e, 0xb9, 0x26, 0xed, 0x5b, 0x0a, 0xd9, + 0x77, 0xc8, 0x39, 0x38, 0xc5, 0xed, 0x20, 0x42, 0x73, 0xbd, 0x65, 0x74, 0x4a, 0x56, 0x3a, 0x20, + 0x75, 0x38, 0xed, 0x20, 0x75, 0x3c, 0xe6, 0xa3, 0xb9, 0xd1, 0x32, 0x3a, 0xeb, 0x56, 0x36, 0x26, + 0xbb, 0x50, 0xd1, 0x4e, 0x8f, 0xec, 0xc0, 0x7f, 0xc0, 0x5c, 0xf3, 0x54, 0xcb, 0xe8, 0x14, 0xfb, + 0x17, 0xba, 0xd9, 0xcb, 0x1c, 0x3c, 0xb9, 0x25, 0x2d, 0x71, 0x44, 0x13, 0x27, 0xad, 0xb2, 0xb6, + 0xa4, 0x30, 0xb9, 0x09, 0x65, 0xed, 0x94, 0x92, 0x28, 0x48, 0x09, 0xb3, 0xab, 0xdf, 0xf7, 0xb0, + 0x42, 0x49, 0x19, 0x52, 0xb4, 0xfd, 0xfb, 0x3a, 0x94, 0xee, 0x87, 0x49, 0x18, 0x86, 0xc8, 0x39, + 0x75, 0x91, 0x98, 0xb0, 0x19, 0xd2, 0xb9, 0x17, 0x50, 0x47, 0x06, 0xe1, 0x8c, 0xa5, 0x87, 0xe4, + 0x0a, 0x6c, 0x4e, 0x53, 0x92, 0x7c, 0xfd, 0x62, 0xbf, 0x96, 0x3b, 0xaa, 0x66, 0x5b, 0x9a, 0x41, + 0xee, 0xc0, 0xa6, 0x83, 0xb3, 0x11, 0xc6, 0xcc, 0x2c, 0x26, 0x32, 0xbb, 0xef, 0xfe, 0xf8, 0x53, + 0xf3, 0xfa, 0xcb, 0xca, 0x2a, 0x09, 0x5a, 0x4f, 0xcc, 0x43, 0xe4, 0xdd, 0x3d, 0x9c, 0xdd, 0xbe, + 0xbf, 0x6f, 0x15, 0x1c, 0x9c, 0xdd, 0x8e, 0x59, 0xa2, 0x47, 0xc3, 0x50, 0xea, 0x9d, 0xf9, 0x5b, + 0x7a, 0x83, 0x30, 0x94, 0x7a, 0x34, 0x0c, 0x13, 0xbd, 0xf3, 0x90, 0xdc, 0x25, 0xa9, 0x2c, 0xc9, + 0x54, 0x9e, 0xa2, 0x61, 0xb8, 0xef, 0x24, 0x70, 0xe2, 0x36, 0x73, 0xcc, 0x72, 0x0a, 0x3b, 0x38, + 0xdb, 0x77, 0xc8, 0x00, 0x6a, 0x59, 0xae, 0xa6, 0x28, 0xa8, 0x43, 0x05, 0x35, 0xcf, 0xcb, 0x20, + 0x9c, 0xcb, 0x83, 0x60, 0x3d, 0x19, 0x2a, 0x9b, 0x55, 0xd5, 0xa0, 0x46, 0xc8, 0x87, 0x50, 0xd5, + 0xa9, 0xca, 0x14, 0xb6, 0xa5, 0xc2, 0xd9, 0x2c, 0x59, 0x0b, 0x02, 0x15, 0x85, 0x65, 0xf3, 0x07, + 0x50, 0x75, 0x54, 0xc5, 0x8e, 0x02, 0x59, 0xb2, 0xdc, 0x6c, 0xb6, 0xd6, 0x3b, 0xc5, 0xfe, 0x76, + 0x57, 0x2d, 0xc1, 0xe5, 0x8a, 0xb6, 0x2a, 0xce, 0xd2, 0x98, 0xb7, 0x7f, 0x5b, 0x83, 0x8a, 0xe6, + 0x9c, 0xa4, 0xfb, 0x05, 0xe9, 0xbe, 0x09, 0x95, 0x43, 0xb1, 0x56, 0xc9, 0x5e, 0x15, 0xea, 0xf2, + 0x72, 0xa8, 0xdb, 0x4f, 0x0d, 0x30, 0xf7, 0x70, 0xc6, 0x6c, 0x1c, 0xd8, 0x82, 0xcd, 0xd2, 0xa5, + 0x87, 0x3c, 0x0c, 0x7c, 0x7e, 0x6c, 0x21, 0x7f, 0x8e, 0x93, 0xc5, 0xbf, 0xe4, 0xe4, 0x37, 0x1b, + 0x70, 0x61, 0x0f, 0x9d, 0x38, 0xf4, 0x98, 0x4d, 0x05, 0x3a, 0x27, 0x7d, 0xe0, 0x9f, 0xeb, 0x03, + 0xeb, 0xaf, 0xdc, 0x07, 0x9a, 0x50, 0xe4, 0x18, 0xcd, 0x30, 0x1a, 0x09, 0x36, 0x45, 0x73, 0x47, + 0x7e, 0x55, 0x20, 0x85, 0x0e, 0xd8, 0x14, 0xc9, 0x1e, 0xd4, 0x22, 0x55, 0x6a, 0x23, 0x81, 0xd3, + 0xd0, 0xa3, 0x02, 0xcd, 0xa6, 0xf4, 0x71, 0xe7, 0x70, 0x65, 0xe8, 0x74, 0x55, 0xf5, 0x8c, 0x03, + 0x35, 0xa1, 0xfd, 0xd5, 0x06, 0xec, 0x1c, 0xad, 0xe0, 0x47, 0x31, 0x72, 0xf1, 0xba, 0x94, 0xc6, + 0xbf, 0xa0, 0xe9, 0x0f, 0xe1, 0x2c, 0xcd, 0xc2, 0x9f, 0x4b, 0xec, 0x48, 0x89, 0xff, 0xe7, 0x4e, + 0xe4, 0x39, 0xca, 0xb4, 0x08, 0x3d, 0x82, 0x1d, 0xc7, 0x37, 0xe4, 0x8f, 0x0d, 0x78, 0x73, 0xb1, + 0x69, 0xbc, 0xe6, 0x35, 0xf2, 0x9f, 0x6b, 0x1f, 0xc7, 0x5c, 0x51, 0x87, 0xba, 0x91, 0x79, 0xa4, + 0x1b, 0x0d, 0x57, 0x77, 0xa3, 0x56, 0x56, 0x73, 0x2b, 0xbe, 0x94, 0xcf, 0x69, 0x4b, 0xdf, 0xad, + 0x41, 0x3d, 0x27, 0xde, 0x9a, 0x50, 0xcf, 0x43, 0xdf, 0xc5, 0x93, 0xaa, 0x5b, 0x5d, 0x75, 0x6d, + 0x07, 0x2e, 0x3e, 0x37, 0x64, 0xc7, 0xba, 0x1d, 0x69, 0x13, 0xa8, 0xde, 0x8b, 0xc7, 0xdc, 0x8e, + 0xd8, 0x58, 0xa7, 0xa3, 0x5d, 0x81, 0xd2, 0x3d, 0x41, 0x45, 0xcc, 0x35, 0xf0, 0xf3, 0x3a, 0x14, + 0x52, 0x84, 0x74, 0xa0, 0xc0, 0xe7, 0x5c, 0xe0, 0x54, 0x3e, 0xb5, 0xd8, 0xaf, 0x76, 0x93, 0xe3, + 0xdd, 0x3d, 0x09, 0x25, 0x14, 0x6e, 0x29, 0x3b, 0xb9, 0x0e, 0x5b, 0x76, 0x30, 0x0d, 0x03, 0x1f, + 0x7d, 0xa1, 0x1c, 0x39, 0x2b, 0xc9, 0xb7, 0x34, 0x9a, 0xf2, 0x73, 0x16, 0x69, 0x43, 0x21, 0x96, + 0xbb, 0x19, 0xb5, 0x25, 0x02, 0xc9, 0xb7, 0xa8, 0x40, 0x6e, 0x29, 0x0b, 0xe9, 0x41, 0x29, 0xbd, + 0x1b, 0xc5, 0x3e, 0x7b, 0x14, 0xa3, 0x4c, 0xcd, 0x32, 0xf5, 0x4c, 0x4a, 0xb8, 0x2f, 0xed, 0xe4, + 0x32, 0x9c, 0xd6, 0xdd, 0x50, 0xc6, 0x7d, 0x99, 0x9b, 0xd9, 0xc8, 0xdb, 0x50, 0xcc, 0x57, 0x0a, + 0x97, 0xb9, 0x58, 0xa6, 0x2e, 0x9a, 0xc9, 0x07, 0xb0, 0xb0, 0xae, 0xb8, 0xf6, 0xa5, 0x72, 0x64, + 0x52, 0x6d, 0x81, 0xa5, 0x1c, 0x7a, 0x0f, 0x4a, 0x4e, 0xd6, 0x8a, 0x93, 0xfd, 0x5f, 0x75, 0x21, + 0x92, 0x77, 0x31, 0xb2, 0x93, 0xc3, 0xab, 0x87, 0xdc, 0x5a, 0xa6, 0x91, 0x2b, 0x50, 0xb3, 0x03, + 0xdf, 0x47, 0x5b, 0xa0, 0x33, 0x8a, 0x82, 0x58, 0x60, 0xc4, 0x65, 0x1b, 0x2a, 0x59, 0xd5, 0xcc, + 0x60, 0xa5, 0x38, 0xb9, 0x0a, 0x24, 0x27, 0x4f, 0xa8, 0xef, 0x78, 0x09, 0x7b, 0x5b, 0xb2, 0x73, + 0x99, 0x8f, 0x95, 0xa1, 0xfd, 0x19, 0x34, 0x06, 0x61, 0xf6, 0x28, 0x05, 0x5b, 0xe8, 0x32, 0x2e, + 0xd2, 0x13, 0xe8, 0x42, 0xf1, 0x1a, 0x8b, 0xc5, 0x7b, 0x09, 0x40, 0xa9, 0x2f, 0x9c, 0xaf, 0x15, + 0xb2, 0xef, 0xf4, 0x9f, 0xae, 0x41, 0x61, 0x57, 0xb6, 0x0b, 0x72, 0x13, 0xb6, 0x06, 0x9c, 0x07, + 0x36, 0xa3, 0x02, 0xc9, 0x79, 0xdd, 0x44, 0x96, 0x76, 0xaf, 0xf5, 0x55, 0x3b, 0x9d, 0x8e, 0x71, + 0xcd, 0x20, 0x9f, 0xc0, 0x56, 0x56, 0xaa, 0xc4, 0xd4, 0xcc, 0xc3, 0xd5, 0x5b, 0x7f, 0x23, 0xef, + 0x4f, 0x2b, 0x36, 0xc9, 0xd7, 0x0c, 0x72, 0x03, 0x36, 0xef, 0xc6, 0x63, 0x8f, 0xf1, 0x09, 0x59, + 0xf5, 0xcc, 0xfa, 0x76, 0x37, 0xfd, 0x1b, 0xd2, 0xd5, 0xff, 0x39, 0xba, 0xb7, 0xa7, 0xa1, 0x98, + 0x77, 0x0c, 0x32, 0x84, 0xd3, 0x6a, 0x69, 0x22, 0x69, 0xae, 0x6e, 0x87, 0xa9, 0x3f, 0x2f, 0xed, + 0x97, 0xfd, 0x6f, 0x0d, 0x28, 0xa5, 0x41, 0x1a, 0x52, 0x9f, 0xba, 0x18, 0x91, 0x2f, 0xa0, 0x9e, + 0x06, 0x1f, 0xa3, 0xa3, 0x69, 0x21, 0x97, 0xb5, 0xe2, 0x8b, 0x53, 0xb6, 0xea, 0x05, 0x48, 0x1f, + 0xb6, 0x3e, 0x42, 0xa1, 0x16, 0x74, 0x96, 0x89, 0xa5, 0x25, 0x5f, 0x2f, 0x2f, 0xc3, 0xbb, 0x37, + 0xbe, 0x7f, 0xd6, 0x30, 0x7e, 0x78, 0xd6, 0x30, 0x7e, 0x79, 0xd6, 0x30, 0xbe, 0xfe, 0xb5, 0xf1, + 0xbf, 0xcf, 0xdf, 0x7a, 0xf5, 0x9f, 0x4d, 0xe3, 0x82, 0xf4, 0xe0, 0x9d, 0x3f, 0x03, 0x00, 0x00, + 0xff, 0xff, 0x1f, 0x69, 0x15, 0xd0, 0xa1, 0x12, 0x00, 0x00, } diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 178537e3c..5b9cf2e09 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -25,6 +25,7 @@ message DownlinkOption { // received from the Router message UplinkMessage { bytes payload = 1; + protocol.Message message = 2; // NOTE: For LoRaWAN, the Router doesn't know the DevEUI/ID and AppEUI/ID bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; @@ -37,23 +38,26 @@ message UplinkMessage { // received from the Handler, sent to the Router, used as Template message DownlinkMessage { - bytes payload = 1; - bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; - string app_id = 13; - string dev_id = 14; - DownlinkOption downlink_option = 21; + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + string app_id = 13; + string dev_id = 14; + DownlinkOption downlink_option = 21; } //sent to the Router, used as Template message DeviceActivationResponse { - bytes payload = 1; - DownlinkOption downlink_option = 11; + bytes payload = 1; + protocol.Message message = 2; + DownlinkOption downlink_option = 11; } // sent to the Handler message DeduplicatedUplinkMessage { bytes payload = 1; + protocol.Message message = 2; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; string app_id = 13; @@ -67,6 +71,7 @@ message DeduplicatedUplinkMessage { // received from the Router message DeviceActivationRequest { bytes payload = 1; + protocol.Message message = 2; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; protocol.RxMetadata protocol_metadata = 21; @@ -78,6 +83,7 @@ message DeviceActivationRequest { // sent to the Handler message DeduplicatedDeviceActivationRequest { bytes payload = 1; + protocol.Message message = 2; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; string app_id = 13; @@ -91,6 +97,7 @@ message DeduplicatedDeviceActivationRequest { message ActivationChallengeRequest { bytes payload = 1; + protocol.Message message = 2; bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; string app_id = 13; @@ -98,7 +105,8 @@ message ActivationChallengeRequest { } message ActivationChallengeResponse { - bytes payload = 1; + bytes payload = 1; + protocol.Message message = 2; } // message SubscribeRequest is used by a Handler to subscribe to uplink messages diff --git a/api/broker/validation.go b/api/broker/validation.go index b6b4fbaf0..3efad3083 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -32,10 +32,10 @@ func (m *UplinkMessage) Validate() bool { // Validate implements the api.Validator interface func (m *DownlinkMessage) Validate() bool { - if m.DevEui == nil || m.DevEui.IsEmpty() { + if m.DevId == "" || !api.ValidID(m.DevId) { return false } - if m.AppEui == nil || m.AppEui.IsEmpty() { + if m.AppId == "" || !api.ValidID(m.AppId) { return false } if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { @@ -46,12 +46,6 @@ func (m *DownlinkMessage) Validate() bool { // Validate implements the api.Validator interface func (m *DeduplicatedUplinkMessage) Validate() bool { - if m.DevEui == nil || m.DevEui.IsEmpty() { - return false - } - if m.AppEui == nil || m.AppEui.IsEmpty() { - return false - } if m.DevId == "" || !api.ValidID(m.DevId) { return false } @@ -61,17 +55,14 @@ func (m *DeduplicatedUplinkMessage) Validate() bool { if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { return false } + if m.ResponseTemplate != nil && !m.ResponseTemplate.Validate() { + return false + } return true } // Validate implements the api.Validator interface func (m *DeviceActivationRequest) Validate() bool { - if m.DevEui == nil || m.DevEui.IsEmpty() { - return false - } - if m.AppEui == nil || m.AppEui.IsEmpty() { - return false - } if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { return false } @@ -86,12 +77,6 @@ func (m *DeviceActivationRequest) Validate() bool { // Validate implements the api.Validator interface func (m *DeduplicatedDeviceActivationRequest) Validate() bool { - if m.DevEui == nil || m.DevEui.IsEmpty() { - return false - } - if m.AppEui == nil || m.AppEui.IsEmpty() { - return false - } if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { return false } @@ -100,12 +85,6 @@ func (m *DeduplicatedDeviceActivationRequest) Validate() bool { // Validate implements the api.Validator interface func (m *ActivationChallengeRequest) Validate() bool { - if m.DevEui == nil || m.DevEui.IsEmpty() { - return false - } - if m.AppEui == nil || m.AppEui.IsEmpty() { - return false - } return true } diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 61421aab1..73796ed3f 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -55,7 +55,7 @@ const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DeviceActivationResponse struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - AppId string `protobuf:"bytes,2,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` DownlinkOption *broker.DownlinkOption `protobuf:"bytes,11,opt,name=downlink_option,json=downlinkOption" json:"downlink_option,omitempty"` ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` } @@ -65,6 +65,13 @@ func (m *DeviceActivationResponse) String() string { return proto.Com func (*DeviceActivationResponse) ProtoMessage() {} func (*DeviceActivationResponse) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{0} } +func (m *DeviceActivationResponse) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DeviceActivationResponse) GetDownlinkOption() *broker.DownlinkOption { if m != nil { return m.DownlinkOption @@ -931,21 +938,25 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { i = encodeVarintHandler(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } - if len(m.AppId) > 0 { + if m.Message != nil { data[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintHandler(data, i, uint64(m.Message.Size())) + n1, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 } if m.DownlinkOption != nil { data[i] = 0x5a i++ i = encodeVarintHandler(data, i, uint64(m.DownlinkOption.Size())) - n1, err := m.DownlinkOption.MarshalTo(data[i:]) + n2, err := m.DownlinkOption.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n1 + i += n2 } if m.ActivationMetadata != nil { data[i] = 0xba @@ -953,11 +964,11 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintHandler(data, i, uint64(m.ActivationMetadata.Size())) - n2, err := m.ActivationMetadata.MarshalTo(data[i:]) + n3, err := m.ActivationMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n3 } return i, nil } @@ -999,51 +1010,51 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintHandler(data, i, uint64(m.System.Size())) - n3, err := m.System.MarshalTo(data[i:]) + n4, err := m.System.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n4 } if m.Component != nil { data[i] = 0x12 i++ i = encodeVarintHandler(data, i, uint64(m.Component.Size())) - n4, err := m.Component.MarshalTo(data[i:]) + n5, err := m.Component.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n5 } if m.Uplink != nil { data[i] = 0x5a i++ i = encodeVarintHandler(data, i, uint64(m.Uplink.Size())) - n5, err := m.Uplink.MarshalTo(data[i:]) + n6, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n6 } if m.Downlink != nil { data[i] = 0x62 i++ i = encodeVarintHandler(data, i, uint64(m.Downlink.Size())) - n6, err := m.Downlink.MarshalTo(data[i:]) + n7, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n7 } if m.Activations != nil { data[i] = 0x6a i++ i = encodeVarintHandler(data, i, uint64(m.Activations.Size())) - n7, err := m.Activations.MarshalTo(data[i:]) + n8, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n8 } return i, nil } @@ -1178,11 +1189,11 @@ func (m *Device) MarshalTo(data []byte) (int, error) { i += copy(data[i:], m.DevId) } if m.Device != nil { - nn8, err := m.Device.MarshalTo(data[i:]) + nn9, err := m.Device.MarshalTo(data[i:]) if err != nil { return 0, err } - i += nn8 + i += nn9 } return i, nil } @@ -1193,11 +1204,11 @@ func (m *Device_LorawanDevice) MarshalTo(data []byte) (int, error) { data[i] = 0x1a i++ i = encodeVarintHandler(data, i, uint64(m.LorawanDevice.Size())) - n9, err := m.LorawanDevice.MarshalTo(data[i:]) + n10, err := m.LorawanDevice.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n10 } return i, nil } @@ -1262,11 +1273,11 @@ func (m *DryDownlinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x1a i++ i = encodeVarintHandler(data, i, uint64(m.App.Size())) - n10, err := m.App.MarshalTo(data[i:]) + n11, err := m.App.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n11 } if m.Port != 0 { data[i] = 0x20 @@ -1301,11 +1312,11 @@ func (m *DryUplinkMessage) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintHandler(data, i, uint64(m.App.Size())) - n11, err := m.App.MarshalTo(data[i:]) + n12, err := m.App.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n12 } if m.Port != 0 { data[i] = 0x18 @@ -1476,8 +1487,8 @@ func (m *DeviceActivationResponse) Size() (n int) { if l > 0 { n += 1 + l + sovHandler(uint64(l)) } - l = len(m.AppId) - if l > 0 { + if m.Message != nil { + l = m.Message.Size() n += 1 + l + sovHandler(uint64(l)) } if m.DownlinkOption != nil { @@ -1779,9 +1790,9 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowHandler @@ -1791,20 +1802,24 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthHandler } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex case 11: if wireType != 2 { @@ -3547,75 +3562,76 @@ func init() { } var fileDescriptorHandler = []byte{ - // 1109 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6f, 0xdc, 0x44, - 0x10, 0xaf, 0x93, 0xf4, 0x7a, 0x99, 0xcb, 0xe7, 0xa6, 0x0d, 0xe6, 0x12, 0x1d, 0xe9, 0x56, 0x0d, - 0x69, 0x8a, 0xce, 0xe2, 0x40, 0x02, 0x2a, 0x01, 0xfd, 0x48, 0xd3, 0x46, 0x6a, 0x40, 0x38, 0x41, - 0x48, 0x79, 0x20, 0xda, 0x9c, 0x27, 0x3e, 0x2b, 0x8e, 0xd7, 0x78, 0xf7, 0x2e, 0x3a, 0xaa, 0x4a, - 0xa8, 0xef, 0x3c, 0xc1, 0x03, 0x7f, 0x01, 0xff, 0x09, 0x12, 0x8f, 0x48, 0x3c, 0xf2, 0x00, 0x0a, - 0xfc, 0x21, 0xc8, 0xbb, 0x6b, 0x9f, 0x73, 0x1f, 0xf9, 0x40, 0x3c, 0xdd, 0xcd, 0xfc, 0xc6, 0xbf, - 0x99, 0xdf, 0xcc, 0x7a, 0xd6, 0xf0, 0x91, 0x1f, 0xc8, 0x56, 0xfb, 0xa0, 0xde, 0xe4, 0xc7, 0xce, - 0x6e, 0x0b, 0x77, 0x5b, 0x41, 0xe4, 0x8b, 0xcf, 0x50, 0x9e, 0xf0, 0xe4, 0xc8, 0x91, 0x32, 0x72, - 0x58, 0x1c, 0x38, 0x2d, 0x16, 0x79, 0x21, 0x26, 0xd9, 0x6f, 0x3d, 0x4e, 0xb8, 0xe4, 0xe4, 0x86, - 0x31, 0xab, 0x4b, 0x3e, 0xe7, 0x7e, 0x88, 0x8e, 0x72, 0x1f, 0xb4, 0x0f, 0x1d, 0x3c, 0x8e, 0x65, - 0x57, 0x47, 0x55, 0x97, 0x0d, 0x98, 0xf2, 0xb0, 0x28, 0xe2, 0x92, 0xc9, 0x80, 0x47, 0xc2, 0xa0, - 0xf3, 0x59, 0x0a, 0x16, 0x07, 0xc6, 0xb5, 0x94, 0xb9, 0x0e, 0x12, 0x7e, 0x84, 0x89, 0xf9, 0x31, - 0xe0, 0x5b, 0x19, 0xa8, 0xcc, 0x26, 0x0f, 0xf3, 0x3f, 0x26, 0xe0, 0xee, 0x40, 0x40, 0xc8, 0x13, - 0x76, 0xc2, 0x22, 0xc7, 0xc3, 0x4e, 0xd0, 0x44, 0x1d, 0x46, 0xff, 0xb0, 0xc0, 0xde, 0x50, 0x8e, - 0x47, 0x4d, 0x19, 0x74, 0x54, 0x4d, 0x2e, 0x8a, 0x98, 0x47, 0x02, 0x89, 0x0d, 0x37, 0x62, 0xd6, - 0x0d, 0x39, 0xf3, 0x6c, 0x6b, 0xc5, 0x5a, 0x9b, 0x72, 0x33, 0x93, 0xdc, 0x82, 0x12, 0x8b, 0xe3, - 0xfd, 0xc0, 0xb3, 0xc7, 0x56, 0xac, 0xb5, 0x49, 0xf7, 0x3a, 0x8b, 0xe3, 0x2d, 0x8f, 0x7c, 0x0a, - 0xb3, 0x1e, 0x3f, 0x89, 0xc2, 0x20, 0x3a, 0xda, 0xe7, 0x71, 0xca, 0x65, 0x57, 0x56, 0xac, 0xb5, - 0x4a, 0x63, 0xb1, 0x6e, 0xaa, 0xdf, 0x30, 0xf0, 0xe7, 0x0a, 0x75, 0x67, 0xbc, 0x33, 0x36, 0xd9, - 0x86, 0x05, 0x96, 0xd7, 0xb1, 0x7f, 0x8c, 0x92, 0x79, 0x4c, 0x32, 0xfb, 0x0d, 0x45, 0xb2, 0x5c, - 0xcf, 0x35, 0xf6, 0x8a, 0xdd, 0x36, 0x31, 0x2e, 0x61, 0x03, 0x3e, 0x3a, 0x0b, 0xd3, 0x3b, 0x92, - 0xc9, 0xb6, 0x70, 0xf1, 0x9b, 0x36, 0x0a, 0x49, 0xff, 0xb4, 0xa0, 0xa4, 0x3d, 0x64, 0x0d, 0x4a, - 0xa2, 0x2b, 0x24, 0x1e, 0x2b, 0x6d, 0x95, 0xc6, 0x5c, 0x3d, 0x6d, 0xfd, 0x8e, 0x72, 0xa5, 0x21, - 0xc2, 0x35, 0x38, 0x79, 0x17, 0x26, 0x9b, 0xfc, 0x38, 0xe6, 0x11, 0x46, 0x52, 0xe9, 0xad, 0x34, - 0x16, 0x54, 0xf0, 0x93, 0xcc, 0xab, 0xe3, 0x7b, 0x51, 0x84, 0x42, 0xa9, 0x1d, 0xa7, 0xba, 0x8c, - 0x7e, 0x50, 0xf1, 0x2e, 0x93, 0x28, 0x5c, 0x83, 0x90, 0x55, 0x28, 0x67, 0xea, 0xed, 0xa9, 0x81, - 0xa8, 0x1c, 0x23, 0xef, 0x40, 0xa5, 0x27, 0x4d, 0xd8, 0xd3, 0x03, 0xa1, 0x45, 0x98, 0xd6, 0xe1, - 0xd6, 0xa3, 0x38, 0x0e, 0x83, 0xa6, 0xb2, 0xb7, 0x3c, 0x8c, 0x64, 0x70, 0x18, 0x60, 0x52, 0x18, - 0x99, 0x55, 0x18, 0x19, 0xfd, 0xd1, 0x82, 0x4a, 0xe1, 0x81, 0x11, 0x61, 0xe9, 0x51, 0xf0, 0xb0, - 0xc9, 0x3d, 0x4c, 0xcc, 0xc4, 0x33, 0x93, 0x2c, 0xa7, 0xdd, 0x89, 0x3a, 0x98, 0x48, 0x4c, 0xec, - 0x71, 0x85, 0xf5, 0x1c, 0x29, 0xda, 0x61, 0x61, 0xe0, 0x31, 0xc9, 0x13, 0x7b, 0x42, 0xa3, 0xb9, - 0x23, 0x65, 0xc5, 0x48, 0xb3, 0x5e, 0xd7, 0xac, 0xc6, 0xa4, 0x0f, 0x61, 0x4e, 0x1f, 0xcb, 0x0b, - 0x15, 0xa4, 0x6e, 0x0f, 0x3b, 0x85, 0xb3, 0xe8, 0x61, 0x67, 0xcb, 0xa3, 0xdf, 0x42, 0x49, 0x33, - 0x5c, 0xed, 0x39, 0xf2, 0x21, 0xcc, 0x98, 0x37, 0x65, 0x5f, 0xbf, 0x29, 0x4a, 0x54, 0xa5, 0x31, - 0x5b, 0x37, 0xee, 0xba, 0xa6, 0x7d, 0x7e, 0xcd, 0x9d, 0x36, 0x1e, 0xed, 0x78, 0x5c, 0x56, 0x84, - 0x41, 0x13, 0xe9, 0x07, 0x00, 0xda, 0xf7, 0x22, 0x10, 0x92, 0xdc, 0x4b, 0x7b, 0x97, 0x5a, 0xc2, - 0xb6, 0x56, 0xc6, 0x15, 0x55, 0xb6, 0x40, 0x74, 0x94, 0x9b, 0xe1, 0xf4, 0xb5, 0x05, 0x64, 0x23, - 0xe9, 0x66, 0x6f, 0xc9, 0x36, 0x0a, 0xc1, 0xfc, 0xf3, 0x5e, 0xc4, 0x45, 0x28, 0x1d, 0x06, 0x18, - 0x7a, 0xc2, 0x88, 0x30, 0x16, 0x59, 0x85, 0x71, 0x16, 0xc7, 0xa6, 0xf4, 0x9b, 0x79, 0xbe, 0xc2, - 0xa4, 0xdd, 0x34, 0x80, 0x10, 0x98, 0x88, 0x79, 0x22, 0xd5, 0x68, 0xa6, 0x5d, 0xf5, 0x9f, 0xb6, - 0x60, 0x6e, 0x23, 0xe9, 0x7e, 0x19, 0x5f, 0xae, 0x02, 0x93, 0x69, 0xec, 0xb2, 0x99, 0xc6, 0x0b, - 0x99, 0x3e, 0x81, 0xf2, 0x0b, 0xee, 0x3f, 0x8d, 0x64, 0xd2, 0x25, 0x55, 0x28, 0x1f, 0xb6, 0xa3, - 0xa6, 0x5a, 0x1a, 0x7a, 0x4e, 0xb9, 0x7d, 0x46, 0xe5, 0x78, 0x4f, 0x25, 0xfd, 0xce, 0x82, 0xd9, - 0xbc, 0x54, 0x17, 0x45, 0x3b, 0x94, 0xff, 0xa1, 0x57, 0x37, 0xe1, 0xba, 0x3a, 0x92, 0xaa, 0xb4, - 0xb2, 0xab, 0x0d, 0x72, 0x17, 0x26, 0x42, 0xee, 0x0b, 0x7b, 0x42, 0x8d, 0x6c, 0x3e, 0x17, 0x96, - 0x15, 0xec, 0x2a, 0x98, 0xee, 0xc2, 0x7c, 0x61, 0x60, 0x17, 0xd6, 0x90, 0xb1, 0x8e, 0x9d, 0xcb, - 0xda, 0xf8, 0xc5, 0x82, 0x1b, 0xcf, 0x35, 0x44, 0xbe, 0x86, 0x85, 0xde, 0xba, 0x7b, 0xd2, 0x62, - 0x61, 0x88, 0x91, 0x8f, 0x84, 0x66, 0x2b, 0x75, 0x08, 0x68, 0xd6, 0x5d, 0xf5, 0xce, 0xb9, 0x31, - 0x66, 0xcb, 0xef, 0x41, 0xd9, 0xc0, 0x48, 0xee, 0xe7, 0x7b, 0x1a, 0xbd, 0xb6, 0x1e, 0x20, 0x7a, - 0x83, 0xf7, 0x83, 0x66, 0xbf, 0xdd, 0x77, 0x8c, 0x07, 0x6f, 0x90, 0xc6, 0xcf, 0x65, 0x20, 0x85, - 0x93, 0xb0, 0xcd, 0x22, 0xe6, 0x63, 0x42, 0x7c, 0x58, 0x70, 0xd1, 0x0f, 0x84, 0xc4, 0xa4, 0xb8, - 0x7b, 0x6a, 0xc3, 0x4e, 0x4f, 0x6f, 0x01, 0x54, 0x17, 0xeb, 0xfa, 0x0e, 0xad, 0x67, 0x17, 0x6c, - 0xfd, 0x69, 0x7a, 0xc1, 0x52, 0xfb, 0xf5, 0xef, 0xff, 0xfc, 0x30, 0x46, 0xe8, 0xb4, 0xc3, 0x7a, - 0xcf, 0x89, 0x07, 0xd6, 0x3a, 0x39, 0x84, 0x99, 0x67, 0x28, 0xaf, 0x92, 0x63, 0xe8, 0x09, 0xa6, - 0x35, 0x95, 0xc1, 0x26, 0x8b, 0x67, 0x32, 0x38, 0x2f, 0xf5, 0x5e, 0x79, 0x45, 0x18, 0xcc, 0xec, - 0x9c, 0xcd, 0x33, 0x94, 0x67, 0xa4, 0x82, 0xdb, 0x8a, 0x7f, 0x89, 0x8e, 0xe0, 0x4f, 0xa5, 0x1c, - 0xc1, 0xfc, 0x06, 0x86, 0x28, 0xf1, 0xff, 0xe8, 0x98, 0xd1, 0xb3, 0x3e, 0x4a, 0x4f, 0x0b, 0x26, - 0x9f, 0xa1, 0x34, 0xfb, 0xf3, 0xcd, 0xbe, 0x39, 0x17, 0xf8, 0xfb, 0x37, 0x19, 0x75, 0x14, 0xf1, - 0x3d, 0xf2, 0xf6, 0x70, 0x62, 0xf3, 0xf1, 0x21, 0x9c, 0x97, 0x7a, 0xf5, 0xbe, 0x22, 0xdf, 0x5b, - 0x30, 0xb9, 0x93, 0xa7, 0xea, 0xe7, 0x1b, 0x29, 0xe0, 0x2b, 0x95, 0xe7, 0x0b, 0x7a, 0xd9, 0x3c, - 0x0f, 0xac, 0xf5, 0xbd, 0x3b, 0xb4, 0x76, 0x7e, 0x74, 0xda, 0xe6, 0x04, 0xa6, 0x74, 0x9b, 0x2f, - 0x16, 0x3f, 0xaa, 0x36, 0xd3, 0x83, 0xf5, 0x4b, 0xf7, 0xe0, 0x04, 0xec, 0xbc, 0xdb, 0x62, 0x93, - 0x5f, 0xe9, 0x9d, 0x58, 0xe8, 0xab, 0x2f, 0xbd, 0x71, 0xe8, 0xaa, 0xaa, 0x60, 0x85, 0x5c, 0xa0, - 0x97, 0x6c, 0x42, 0xa5, 0xb0, 0xbc, 0xc8, 0x52, 0x8f, 0x6b, 0xe0, 0x0e, 0xaa, 0x56, 0x87, 0x81, - 0x66, 0xdf, 0x3d, 0x84, 0xc9, 0x7c, 0x0d, 0x17, 0x3b, 0xd6, 0x77, 0x8b, 0x54, 0xed, 0x41, 0x48, - 0x33, 0x34, 0x36, 0x61, 0xc6, 0xec, 0xbb, 0x6c, 0x47, 0xbc, 0xaf, 0x8e, 0xa0, 0xf9, 0x58, 0x5b, - 0xcc, 0x1f, 0x3c, 0xf3, 0x3d, 0x57, 0x38, 0x7f, 0xda, 0xff, 0xf8, 0xe3, 0x5f, 0x4f, 0x6b, 0xd6, - 0x6f, 0xa7, 0x35, 0xeb, 0xaf, 0xd3, 0x9a, 0xf5, 0xd3, 0xdf, 0xb5, 0x6b, 0x7b, 0xf7, 0xaf, 0xf0, - 0x61, 0x7f, 0x50, 0x52, 0xa3, 0x7c, 0xef, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0x1e, 0x8b, - 0xa0, 0x0e, 0x0c, 0x00, 0x00, + // 1122 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0x1b, 0x45, + 0x14, 0xee, 0xe6, 0xc7, 0xb1, 0x8f, 0x9b, 0xa4, 0x99, 0xb4, 0x61, 0x71, 0x2a, 0x93, 0x4e, 0xd5, + 0x90, 0x26, 0xc8, 0x16, 0x06, 0x09, 0xa8, 0x04, 0xf4, 0x27, 0x4d, 0x1b, 0xa9, 0x01, 0xb1, 0x09, + 0x42, 0xca, 0x05, 0xd1, 0xc4, 0x7b, 0xb2, 0x5e, 0x65, 0xbd, 0xb3, 0xec, 0x8c, 0x1d, 0x99, 0xaa, + 0x12, 0xea, 0x3d, 0x57, 0x70, 0xc1, 0x13, 0xf0, 0x26, 0x48, 0x5c, 0x22, 0xf1, 0x00, 0xa0, 0xc0, + 0x0b, 0xf0, 0x06, 0x68, 0x67, 0x66, 0xd7, 0x1b, 0xff, 0xe4, 0x07, 0x71, 0x65, 0x9f, 0xf3, 0x9d, + 0xf9, 0xce, 0xf9, 0xce, 0xcc, 0x9c, 0x59, 0xf8, 0xc8, 0xf3, 0x65, 0xab, 0x73, 0x58, 0x6b, 0xf2, + 0x76, 0x7d, 0xaf, 0x85, 0x7b, 0x2d, 0x3f, 0xf4, 0xc4, 0x67, 0x28, 0x4f, 0x78, 0x7c, 0x5c, 0x97, + 0x32, 0xac, 0xb3, 0xc8, 0xaf, 0xb7, 0x58, 0xe8, 0x06, 0x18, 0xa7, 0xbf, 0xb5, 0x28, 0xe6, 0x92, + 0x93, 0x19, 0x63, 0x56, 0x96, 0x3d, 0xce, 0xbd, 0x00, 0xeb, 0xca, 0x7d, 0xd8, 0x39, 0xaa, 0x63, + 0x3b, 0x92, 0x3d, 0x1d, 0x55, 0xb9, 0x6d, 0xc0, 0x84, 0x87, 0x85, 0x21, 0x97, 0x4c, 0xfa, 0x3c, + 0x14, 0x06, 0x5d, 0x48, 0x53, 0xb0, 0xc8, 0x37, 0xae, 0xe5, 0xd4, 0x75, 0x18, 0xf3, 0x63, 0x8c, + 0xcd, 0x8f, 0x01, 0xdf, 0x4a, 0x41, 0x65, 0x36, 0x79, 0x90, 0xfd, 0x31, 0x01, 0xf7, 0x86, 0x02, + 0x02, 0x1e, 0xb3, 0x13, 0x16, 0xd6, 0x5d, 0xec, 0xfa, 0x4d, 0xd4, 0x61, 0xf4, 0x1f, 0x0b, 0xec, + 0x4d, 0xe5, 0x78, 0xd4, 0x94, 0x7e, 0x57, 0xd5, 0xe4, 0xa0, 0x88, 0x78, 0x28, 0x90, 0xd8, 0x30, + 0x13, 0xb1, 0x5e, 0xc0, 0x99, 0x6b, 0x5b, 0x2b, 0xd6, 0xda, 0x75, 0x27, 0x35, 0xc9, 0x06, 0xcc, + 0xb4, 0x51, 0x08, 0xe6, 0xa1, 0x3d, 0xb1, 0x62, 0xad, 0x95, 0x1b, 0x0b, 0xb5, 0x2c, 0xff, 0x8e, + 0x06, 0x9c, 0x34, 0x82, 0x7c, 0x0a, 0xf3, 0x2e, 0x3f, 0x09, 0x03, 0x3f, 0x3c, 0x3e, 0xe0, 0x51, + 0x92, 0xc1, 0x2e, 0xab, 0x45, 0x4b, 0x35, 0xa3, 0x69, 0xd3, 0xc0, 0x9f, 0x2b, 0xd4, 0x99, 0x73, + 0xcf, 0xd8, 0x64, 0x07, 0x16, 0x59, 0x56, 0xdd, 0x41, 0x1b, 0x25, 0x73, 0x99, 0x64, 0xf6, 0x1b, + 0x8a, 0xe4, 0x76, 0x3f, 0x73, 0x5f, 0xc2, 0x8e, 0x89, 0x71, 0x08, 0x1b, 0xf2, 0xd1, 0x79, 0x98, + 0xdd, 0x95, 0x4c, 0x76, 0x84, 0x83, 0xdf, 0x74, 0x50, 0x48, 0xfa, 0x87, 0x05, 0x05, 0xed, 0x21, + 0x6b, 0x50, 0x10, 0x3d, 0x21, 0xb1, 0xad, 0x14, 0x97, 0x1b, 0x37, 0x6a, 0xc9, 0x86, 0xec, 0x2a, + 0x57, 0x12, 0x22, 0x1c, 0x83, 0x93, 0x77, 0xa1, 0xd4, 0xe4, 0xed, 0x88, 0x87, 0x18, 0x4a, 0xd3, + 0x84, 0x45, 0x15, 0xfc, 0x24, 0xf5, 0xea, 0xf8, 0x7e, 0x14, 0xa1, 0x50, 0xe8, 0x44, 0x89, 0x2e, + 0xa3, 0x1f, 0x54, 0xbc, 0xc3, 0x24, 0x0a, 0xc7, 0x20, 0x64, 0x15, 0x8a, 0xa9, 0x7a, 0xfb, 0xfa, + 0x50, 0x54, 0x86, 0x91, 0x77, 0xa0, 0xdc, 0x97, 0x26, 0xec, 0xd9, 0xa1, 0xd0, 0x3c, 0x4c, 0x6b, + 0x70, 0xeb, 0x51, 0x14, 0x05, 0x7e, 0x53, 0xd9, 0xdb, 0x2e, 0x86, 0xd2, 0x3f, 0xf2, 0x31, 0x26, + 0xb7, 0xa0, 0xc0, 0xa2, 0xe8, 0xc0, 0xd7, 0x3b, 0x5c, 0x72, 0xa6, 0x59, 0x14, 0x6d, 0xbb, 0xf4, + 0x47, 0x0b, 0xca, 0xb9, 0x05, 0x63, 0xc2, 0x92, 0x03, 0xe2, 0x62, 0x93, 0xbb, 0x18, 0xab, 0x0e, + 0x94, 0x9c, 0xd4, 0x24, 0xb7, 0x93, 0xee, 0x84, 0x5d, 0x8c, 0x25, 0xc6, 0xf6, 0xa4, 0xc2, 0xfa, + 0x8e, 0x04, 0xed, 0xb2, 0xc0, 0x77, 0x99, 0xe4, 0xb1, 0x3d, 0xa5, 0xd1, 0xcc, 0x91, 0xb0, 0x62, + 0xa8, 0x59, 0xa7, 0x35, 0xab, 0x31, 0xe9, 0x43, 0xb8, 0xa1, 0x0f, 0xeb, 0x85, 0x0a, 0x12, 0xb7, + 0x8b, 0xdd, 0xc4, 0xad, 0x2b, 0x9b, 0x76, 0xb1, 0xbb, 0xed, 0xd2, 0x6f, 0xa1, 0xa0, 0x19, 0xae, + 0xb6, 0x8e, 0x7c, 0x08, 0x73, 0xe6, 0xfe, 0x1c, 0xe8, 0xfb, 0xa3, 0x44, 0x95, 0x1b, 0xf3, 0x35, + 0xe3, 0xae, 0x69, 0xda, 0xe7, 0xd7, 0x9c, 0x59, 0xe3, 0xd1, 0x8e, 0xc7, 0x45, 0x45, 0xe8, 0x37, + 0x91, 0x7e, 0x00, 0xa0, 0x7d, 0x2f, 0x7c, 0x21, 0xc9, 0xfd, 0xa4, 0x77, 0x89, 0x25, 0x6c, 0x6b, + 0x65, 0x52, 0x51, 0xa5, 0x63, 0x45, 0x47, 0x39, 0x29, 0x4e, 0x5f, 0x5b, 0x40, 0x36, 0xe3, 0x5e, + 0x7a, 0x4b, 0xcc, 0x05, 0x3b, 0xe7, 0x7a, 0x2e, 0x41, 0xe1, 0xc8, 0xc7, 0xc0, 0x15, 0x46, 0x84, + 0xb1, 0xc8, 0x2a, 0x4c, 0xb2, 0x28, 0x32, 0xa5, 0xdf, 0xcc, 0xf2, 0xe5, 0x76, 0xda, 0x49, 0x02, + 0x08, 0x81, 0xa9, 0x88, 0xc7, 0x52, 0x6d, 0xcd, 0xac, 0xa3, 0xfe, 0xd3, 0x16, 0xdc, 0xd8, 0x8c, + 0x7b, 0x5f, 0x46, 0x97, 0xab, 0xc0, 0x64, 0x9a, 0xb8, 0x6c, 0xa6, 0xc9, 0x5c, 0xa6, 0x4f, 0xa0, + 0xf8, 0x82, 0x7b, 0x4f, 0x43, 0x19, 0xf7, 0x48, 0x05, 0x8a, 0x47, 0x9d, 0xb0, 0xa9, 0x86, 0x86, + 0xde, 0xa7, 0xcc, 0x3e, 0xa3, 0x72, 0xb2, 0xaf, 0x92, 0x7e, 0x67, 0xc1, 0x7c, 0x56, 0xaa, 0x83, + 0xa2, 0x13, 0xc8, 0xff, 0xd0, 0xab, 0x9b, 0x30, 0xad, 0x8e, 0xa4, 0x2a, 0xad, 0xe8, 0x68, 0x83, + 0xdc, 0x83, 0xa9, 0x80, 0x7b, 0xc2, 0x9e, 0x52, 0x5b, 0xb6, 0x90, 0x09, 0x4b, 0x0b, 0x76, 0x14, + 0x4c, 0xf7, 0x60, 0x21, 0xb7, 0x61, 0x17, 0xd6, 0x90, 0xb2, 0x4e, 0x9c, 0xcb, 0xda, 0xf8, 0xc5, + 0x82, 0x99, 0xe7, 0x1a, 0x22, 0x5f, 0xc3, 0x62, 0x7f, 0xdc, 0x3d, 0x69, 0xb1, 0x20, 0xc0, 0xd0, + 0x43, 0x42, 0xd3, 0x91, 0x3a, 0x02, 0x34, 0xe3, 0xae, 0x72, 0xf7, 0xdc, 0x18, 0x33, 0xfb, 0xf7, + 0xa1, 0x68, 0x60, 0x24, 0x1b, 0xd9, 0x9c, 0x46, 0xb7, 0xa3, 0x37, 0x10, 0xdd, 0xe1, 0x57, 0x43, + 0xb3, 0xdf, 0x19, 0x38, 0xc6, 0xc3, 0xef, 0x4a, 0xe3, 0xe7, 0x22, 0x90, 0xdc, 0x49, 0xd8, 0x61, + 0x21, 0xf3, 0x30, 0x26, 0x1e, 0x2c, 0x3a, 0xe8, 0xf9, 0x42, 0x62, 0x9c, 0x9f, 0x3d, 0xd5, 0x51, + 0xa7, 0xa7, 0x3f, 0x00, 0x2a, 0x4b, 0x35, 0xfd, 0xb2, 0xd6, 0xd2, 0x67, 0xb7, 0xf6, 0x34, 0x79, + 0x76, 0xa9, 0xfd, 0xfa, 0xf7, 0xbf, 0x7f, 0x98, 0x20, 0x74, 0xb6, 0xce, 0xfa, 0xeb, 0xc4, 0x03, + 0x6b, 0x9d, 0x1c, 0xc1, 0xdc, 0x33, 0x94, 0x57, 0xc9, 0x31, 0xf2, 0x04, 0xd3, 0xaa, 0xca, 0x60, + 0x93, 0xa5, 0x33, 0x19, 0xea, 0x2f, 0xf5, 0x5c, 0x79, 0x45, 0x18, 0xcc, 0xed, 0x9e, 0xcd, 0x33, + 0x92, 0x67, 0xac, 0x82, 0x3b, 0x8a, 0x7f, 0x99, 0x8e, 0xe1, 0x4f, 0xa4, 0x1c, 0xc3, 0xc2, 0x26, + 0x06, 0x28, 0xf1, 0xff, 0xe8, 0x98, 0xd1, 0xb3, 0x3e, 0x4e, 0x4f, 0x0b, 0x4a, 0xcf, 0x50, 0x9a, + 0xf9, 0xf9, 0xe6, 0xc0, 0x3e, 0xe7, 0xf8, 0x07, 0x27, 0x19, 0xad, 0x2b, 0xe2, 0xfb, 0xe4, 0xed, + 0xd1, 0xc4, 0xe6, 0x93, 0x44, 0xd4, 0x5f, 0xea, 0xd1, 0xfb, 0x8a, 0x7c, 0x6f, 0x41, 0x69, 0x37, + 0x4b, 0x35, 0xc8, 0x37, 0x56, 0xc0, 0x57, 0x2a, 0xcf, 0x17, 0xf4, 0xb2, 0x79, 0x1e, 0x58, 0xeb, + 0xfb, 0x77, 0x69, 0xf5, 0xfc, 0xe8, 0xa4, 0xcd, 0x31, 0x5c, 0xd7, 0x6d, 0xbe, 0x58, 0xfc, 0xb8, + 0xda, 0x4c, 0x0f, 0xd6, 0x2f, 0xdd, 0x83, 0x13, 0xb0, 0xb3, 0x6e, 0x8b, 0x2d, 0x7e, 0xa5, 0x3b, + 0xb1, 0x38, 0x50, 0x5f, 0xf2, 0xe2, 0xd0, 0x55, 0x55, 0xc1, 0x0a, 0xb9, 0x40, 0x2f, 0xd9, 0x82, + 0x72, 0x6e, 0x78, 0x91, 0xe5, 0x3e, 0xd7, 0xd0, 0x1b, 0x54, 0xa9, 0x8c, 0x02, 0xcd, 0xbc, 0x7b, + 0x08, 0xa5, 0x6c, 0x0c, 0xe7, 0x3b, 0x36, 0xf0, 0x8a, 0x54, 0xec, 0x61, 0x48, 0x33, 0x34, 0xb6, + 0x60, 0xce, 0xcc, 0xbb, 0x74, 0x46, 0xbc, 0xaf, 0x8e, 0xa0, 0xf9, 0x58, 0x5b, 0xca, 0x16, 0x9e, + 0xf9, 0x9e, 0xcb, 0x9d, 0x3f, 0xed, 0x7f, 0xfc, 0xf1, 0xaf, 0xa7, 0x55, 0xeb, 0xb7, 0xd3, 0xaa, + 0xf5, 0xe7, 0x69, 0xd5, 0xfa, 0xe9, 0xaf, 0xea, 0xb5, 0xfd, 0x8d, 0x2b, 0x7c, 0xee, 0x1f, 0x16, + 0xd4, 0x56, 0xbe, 0xf7, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x41, 0x10, 0xfc, 0x24, 0x0c, + 0x00, 0x00, } diff --git a/api/handler/handler.proto b/api/handler/handler.proto index c37d94040..3d0128203 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -16,7 +16,7 @@ option go_package = "github.com/TheThingsNetwork/ttn/api/handler"; message DeviceActivationResponse { bytes payload = 1; - string app_id = 2; + protocol.Message message = 2; broker.DownlinkOption downlink_option = 11; protocol.ActivationMetadata activation_metadata = 23; } diff --git a/api/handler/validation.go b/api/handler/validation.go index 1619c17e5..2c47bacc6 100644 --- a/api/handler/validation.go +++ b/api/handler/validation.go @@ -4,15 +4,15 @@ import "github.com/TheThingsNetwork/ttn/api" // Validate implements the api.Validator interface func (m *DeviceActivationResponse) Validate() bool { - if m.AppId == "" || !api.ValidID(m.AppId) { - return false - } if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { return false } if m.ActivationMetadata == nil || !m.ActivationMetadata.Validate() { return false } + if m.Message != nil && !m.Message.Validate() { + return false + } return true } @@ -56,3 +56,8 @@ func (m *Device) Validate() bool { } return true } + +// Validate implements the api.Validator interface +func (m *Device_LorawanDevice) Validate() bool { + return m.LorawanDevice.Validate() +} diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index a9d24ee39..3f8bd1312 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -12,6 +12,16 @@ Metadata TxConfiguration ActivationMetadata + Message + MHDR + MACPayload + FHDR + FCtrl + MACCommand + JoinRequestPayload + JoinAcceptPayload + DLSettings + CFList */ package lorawan @@ -95,6 +105,57 @@ func (x Region) String() string { } func (Region) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{1} } +type Major int32 + +const ( + Major_LORAWAN_R1 Major = 0 +) + +var Major_name = map[int32]string{ + 0: "LORAWAN_R1", +} +var Major_value = map[string]int32{ + "LORAWAN_R1": 0, +} + +func (x Major) String() string { + return proto.EnumName(Major_name, int32(x)) +} +func (Major) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } + +type MType int32 + +const ( + MType_JOIN_REQUEST MType = 0 + MType_JOIN_ACCEPT MType = 1 + MType_UNCONFIRMED_UP MType = 2 + MType_UNCONFIRMED_DOWN MType = 3 + MType_CONFIRMED_UP MType = 4 + MType_CONFIRMED_DOWN MType = 5 +) + +var MType_name = map[int32]string{ + 0: "JOIN_REQUEST", + 1: "JOIN_ACCEPT", + 2: "UNCONFIRMED_UP", + 3: "UNCONFIRMED_DOWN", + 4: "CONFIRMED_UP", + 5: "CONFIRMED_DOWN", +} +var MType_value = map[string]int32{ + "JOIN_REQUEST": 0, + "JOIN_ACCEPT": 1, + "UNCONFIRMED_UP": 2, + "UNCONFIRMED_DOWN": 3, + "CONFIRMED_UP": 4, + "CONFIRMED_DOWN": 5, +} + +func (x MType) String() string { + return proto.EnumName(MType_name, int32(x)) +} +func (MType) EnumDescriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } + type Metadata struct { Modulation Modulation `protobuf:"varint,11,opt,name=modulation,proto3,enum=lorawan.Modulation" json:"modulation,omitempty"` DataRate string `protobuf:"bytes,12,opt,name=data_rate,json=dataRate,proto3" json:"data_rate,omitempty"` @@ -129,7 +190,7 @@ type ActivationMetadata struct { Rx1DrOffset uint32 `protobuf:"varint,11,opt,name=rx1_dr_offset,json=rx1DrOffset,proto3" json:"rx1_dr_offset,omitempty"` Rx2Dr uint32 `protobuf:"varint,12,opt,name=rx2_dr,json=rx2Dr,proto3" json:"rx2_dr,omitempty"` RxDelay uint32 `protobuf:"varint,13,opt,name=rx_delay,json=rxDelay,proto3" json:"rx_delay,omitempty"` - CfList []uint64 `protobuf:"varint,14,rep,packed,name=cf_list,json=cfList" json:"cf_list,omitempty"` + CfList *CFList `protobuf:"bytes,14,opt,name=cf_list,json=cfList" json:"cf_list,omitempty"` } func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } @@ -137,12 +198,301 @@ func (m *ActivationMetadata) String() string { return proto.CompactTe func (*ActivationMetadata) ProtoMessage() {} func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{2} } +func (m *ActivationMetadata) GetCfList() *CFList { + if m != nil { + return m.CfList + } + return nil +} + +type Message struct { + MHDR `protobuf:"bytes,1,opt,name=m_hdr,json=mHdr,embedded=m_hdr" json:"m_hdr"` + Mic []byte `protobuf:"bytes,2,opt,name=mic,proto3" json:"mic,omitempty"` + // Types that are valid to be assigned to Payload: + // *Message_MacPayload + // *Message_JoinRequestPayload + // *Message_JoinAcceptPayload + Payload isMessage_Payload `protobuf_oneof:"Payload"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{3} } + +type isMessage_Payload interface { + isMessage_Payload() + MarshalTo([]byte) (int, error) + Size() int +} + +type Message_MacPayload struct { + MacPayload *MACPayload `protobuf:"bytes,3,opt,name=mac_payload,json=macPayload,oneof"` +} +type Message_JoinRequestPayload struct { + JoinRequestPayload *JoinRequestPayload `protobuf:"bytes,4,opt,name=join_request_payload,json=joinRequestPayload,oneof"` +} +type Message_JoinAcceptPayload struct { + JoinAcceptPayload *JoinAcceptPayload `protobuf:"bytes,5,opt,name=join_accept_payload,json=joinAcceptPayload,oneof"` +} + +func (*Message_MacPayload) isMessage_Payload() {} +func (*Message_JoinRequestPayload) isMessage_Payload() {} +func (*Message_JoinAcceptPayload) isMessage_Payload() {} + +func (m *Message) GetPayload() isMessage_Payload { + if m != nil { + return m.Payload + } + return nil +} + +func (m *Message) GetMacPayload() *MACPayload { + if x, ok := m.GetPayload().(*Message_MacPayload); ok { + return x.MacPayload + } + return nil +} + +func (m *Message) GetJoinRequestPayload() *JoinRequestPayload { + if x, ok := m.GetPayload().(*Message_JoinRequestPayload); ok { + return x.JoinRequestPayload + } + return nil +} + +func (m *Message) GetJoinAcceptPayload() *JoinAcceptPayload { + if x, ok := m.GetPayload().(*Message_JoinAcceptPayload); ok { + return x.JoinAcceptPayload + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Message) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Message_OneofMarshaler, _Message_OneofUnmarshaler, _Message_OneofSizer, []interface{}{ + (*Message_MacPayload)(nil), + (*Message_JoinRequestPayload)(nil), + (*Message_JoinAcceptPayload)(nil), + } +} + +func _Message_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Message) + // Payload + switch x := m.Payload.(type) { + case *Message_MacPayload: + _ = b.EncodeVarint(3<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.MacPayload); err != nil { + return err + } + case *Message_JoinRequestPayload: + _ = b.EncodeVarint(4<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.JoinRequestPayload); err != nil { + return err + } + case *Message_JoinAcceptPayload: + _ = b.EncodeVarint(5<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.JoinAcceptPayload); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Message.Payload has unexpected type %T", x) + } + return nil +} + +func _Message_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Message) + switch tag { + case 3: // Payload.mac_payload + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(MACPayload) + err := b.DecodeMessage(msg) + m.Payload = &Message_MacPayload{msg} + return true, err + case 4: // Payload.join_request_payload + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(JoinRequestPayload) + err := b.DecodeMessage(msg) + m.Payload = &Message_JoinRequestPayload{msg} + return true, err + case 5: // Payload.join_accept_payload + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(JoinAcceptPayload) + err := b.DecodeMessage(msg) + m.Payload = &Message_JoinAcceptPayload{msg} + return true, err + default: + return false, nil + } +} + +func _Message_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Message) + // Payload + switch x := m.Payload.(type) { + case *Message_MacPayload: + s := proto.Size(x.MacPayload) + n += proto.SizeVarint(3<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Message_JoinRequestPayload: + s := proto.Size(x.JoinRequestPayload) + n += proto.SizeVarint(4<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case *Message_JoinAcceptPayload: + s := proto.Size(x.JoinAcceptPayload) + n += proto.SizeVarint(5<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + +type MHDR struct { + MType MType `protobuf:"varint,1,opt,name=m_type,json=mType,proto3,enum=lorawan.MType" json:"m_type,omitempty"` + Major Major `protobuf:"varint,2,opt,name=major,proto3,enum=lorawan.Major" json:"major,omitempty"` +} + +func (m *MHDR) Reset() { *m = MHDR{} } +func (m *MHDR) String() string { return proto.CompactTextString(m) } +func (*MHDR) ProtoMessage() {} +func (*MHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{4} } + +type MACPayload struct { + FHDR `protobuf:"bytes,1,opt,name=f_hdr,json=fHdr,embedded=f_hdr" json:"f_hdr"` + FPort int32 `protobuf:"varint,2,opt,name=f_port,json=fPort,proto3" json:"f_port,omitempty"` + FrmPayload []byte `protobuf:"bytes,3,opt,name=frm_payload,json=frmPayload,proto3" json:"frm_payload,omitempty"` +} + +func (m *MACPayload) Reset() { *m = MACPayload{} } +func (m *MACPayload) String() string { return proto.CompactTextString(m) } +func (*MACPayload) ProtoMessage() {} +func (*MACPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{5} } + +type FHDR struct { + DevAddr github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,1,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr"` + FCtrl `protobuf:"bytes,2,opt,name=f_ctrl,json=fCtrl,embedded=f_ctrl" json:"f_ctrl"` + FCnt uint32 `protobuf:"varint,3,opt,name=f_cnt,json=fCnt,proto3" json:"f_cnt,omitempty"` + FOpts []MACCommand `protobuf:"bytes,4,rep,name=f_opts,json=fOpts" json:"f_opts"` +} + +func (m *FHDR) Reset() { *m = FHDR{} } +func (m *FHDR) String() string { return proto.CompactTextString(m) } +func (*FHDR) ProtoMessage() {} +func (*FHDR) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{6} } + +func (m *FHDR) GetFOpts() []MACCommand { + if m != nil { + return m.FOpts + } + return nil +} + +type FCtrl struct { + Adr bool `protobuf:"varint,1,opt,name=adr,proto3" json:"adr,omitempty"` + AdrAckReq bool `protobuf:"varint,2,opt,name=adr_ack_req,json=adrAckReq,proto3" json:"adr_ack_req,omitempty"` + Ack bool `protobuf:"varint,3,opt,name=ack,proto3" json:"ack,omitempty"` + FPending bool `protobuf:"varint,4,opt,name=f_pending,json=fPending,proto3" json:"f_pending,omitempty"` +} + +func (m *FCtrl) Reset() { *m = FCtrl{} } +func (m *FCtrl) String() string { return proto.CompactTextString(m) } +func (*FCtrl) ProtoMessage() {} +func (*FCtrl) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{7} } + +type MACCommand struct { + Cid uint32 `protobuf:"varint,1,opt,name=cid,proto3" json:"cid,omitempty"` + Payload []byte `protobuf:"bytes,2,opt,name=payload,proto3" json:"payload,omitempty"` +} + +func (m *MACCommand) Reset() { *m = MACCommand{} } +func (m *MACCommand) String() string { return proto.CompactTextString(m) } +func (*MACCommand) ProtoMessage() {} +func (*MACCommand) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{8} } + +type JoinRequestPayload struct { + AppEui github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui"` + DevEui github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui"` + DevNonce github_com_TheThingsNetwork_ttn_core_types.DevNonce `protobuf:"bytes,3,opt,name=dev_nonce,json=devNonce,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevNonce" json:"dev_nonce"` +} + +func (m *JoinRequestPayload) Reset() { *m = JoinRequestPayload{} } +func (m *JoinRequestPayload) String() string { return proto.CompactTextString(m) } +func (*JoinRequestPayload) ProtoMessage() {} +func (*JoinRequestPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{9} } + +type JoinAcceptPayload struct { + Encrypted []byte `protobuf:"bytes,1,opt,name=encrypted,proto3" json:"encrypted,omitempty"` + AppNonce github_com_TheThingsNetwork_ttn_core_types.AppNonce `protobuf:"bytes,2,opt,name=app_nonce,json=appNonce,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppNonce" json:"app_nonce"` + NetId github_com_TheThingsNetwork_ttn_core_types.NetID `protobuf:"bytes,3,opt,name=net_id,json=netId,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NetID" json:"net_id"` + DevAddr github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,4,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr"` + DLSettings `protobuf:"bytes,5,opt,name=dl_settings,json=dlSettings,embedded=dl_settings" json:"dl_settings"` + RxDelay uint32 `protobuf:"varint,6,opt,name=rx_delay,json=rxDelay,proto3" json:"rx_delay,omitempty"` + CfList *CFList `protobuf:"bytes,7,opt,name=cf_list,json=cfList" json:"cf_list,omitempty"` +} + +func (m *JoinAcceptPayload) Reset() { *m = JoinAcceptPayload{} } +func (m *JoinAcceptPayload) String() string { return proto.CompactTextString(m) } +func (*JoinAcceptPayload) ProtoMessage() {} +func (*JoinAcceptPayload) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{10} } + +func (m *JoinAcceptPayload) GetCfList() *CFList { + if m != nil { + return m.CfList + } + return nil +} + +type DLSettings struct { + Rx1DrOffset uint32 `protobuf:"varint,1,opt,name=rx1_dr_offset,json=rx1DrOffset,proto3" json:"rx1_dr_offset,omitempty"` + Rx2Dr uint32 `protobuf:"varint,2,opt,name=rx2_dr,json=rx2Dr,proto3" json:"rx2_dr,omitempty"` +} + +func (m *DLSettings) Reset() { *m = DLSettings{} } +func (m *DLSettings) String() string { return proto.CompactTextString(m) } +func (*DLSettings) ProtoMessage() {} +func (*DLSettings) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{11} } + +type CFList struct { + Freq []uint32 `protobuf:"varint,1,rep,packed,name=freq" json:"freq,omitempty"` +} + +func (m *CFList) Reset() { *m = CFList{} } +func (m *CFList) String() string { return proto.CompactTextString(m) } +func (*CFList) ProtoMessage() {} +func (*CFList) Descriptor() ([]byte, []int) { return fileDescriptorLorawan, []int{12} } + func init() { proto.RegisterType((*Metadata)(nil), "lorawan.Metadata") proto.RegisterType((*TxConfiguration)(nil), "lorawan.TxConfiguration") proto.RegisterType((*ActivationMetadata)(nil), "lorawan.ActivationMetadata") + proto.RegisterType((*Message)(nil), "lorawan.Message") + proto.RegisterType((*MHDR)(nil), "lorawan.MHDR") + proto.RegisterType((*MACPayload)(nil), "lorawan.MACPayload") + proto.RegisterType((*FHDR)(nil), "lorawan.FHDR") + proto.RegisterType((*FCtrl)(nil), "lorawan.FCtrl") + proto.RegisterType((*MACCommand)(nil), "lorawan.MACCommand") + proto.RegisterType((*JoinRequestPayload)(nil), "lorawan.JoinRequestPayload") + proto.RegisterType((*JoinAcceptPayload)(nil), "lorawan.JoinAcceptPayload") + proto.RegisterType((*DLSettings)(nil), "lorawan.DLSettings") + proto.RegisterType((*CFList)(nil), "lorawan.CFList") proto.RegisterEnum("lorawan.Modulation", Modulation_name, Modulation_value) proto.RegisterEnum("lorawan.Region", Region_name, Region_value) + proto.RegisterEnum("lorawan.Major", Major_name, Major_value) + proto.RegisterEnum("lorawan.MType", MType_name, MType_value) } func (m *Metadata) Marshal() (data []byte, err error) { size := m.Size() @@ -304,151 +654,1805 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintLorawan(data, i, uint64(m.RxDelay)) } - if len(m.CfList) > 0 { - data6 := make([]byte, len(m.CfList)*10) - var j5 int - for _, num := range m.CfList { - for num >= 1<<7 { - data6[j5] = uint8(uint64(num)&0x7f | 0x80) - num >>= 7 - j5++ - } - data6[j5] = uint8(num) - j5++ - } + if m.CfList != nil { data[i] = 0x72 i++ - i = encodeVarintLorawan(data, i, uint64(j5)) - i += copy(data[i:], data6[:j5]) + i = encodeVarintLorawan(data, i, uint64(m.CfList.Size())) + n5, err := m.CfList.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n5 } return i, nil } -func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) - return offset + 8 -} -func encodeFixed32Lorawan(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - return offset + 4 -} -func encodeVarintLorawan(data []byte, offset int, v uint64) int { - for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) - v >>= 7 - offset++ +func (m *Message) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err } - data[offset] = uint8(v) - return offset + 1 + return data[:n], nil } -func (m *Metadata) Size() (n int) { + +func (m *Message) MarshalTo(data []byte) (int, error) { + var i int + _ = i var l int _ = l - if m.Modulation != 0 { - n += 1 + sovLorawan(uint64(m.Modulation)) + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.MHDR.Size())) + n6, err := m.MHDR.MarshalTo(data[i:]) + if err != nil { + return 0, err } - l = len(m.DataRate) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) + i += n6 + if len(m.Mic) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.Mic))) + i += copy(data[i:], m.Mic) } - if m.BitRate != 0 { - n += 1 + sovLorawan(uint64(m.BitRate)) + if m.Payload != nil { + nn7, err := m.Payload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn7 } - l = len(m.CodingRate) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) + return i, nil +} + +func (m *Message_MacPayload) MarshalTo(data []byte) (int, error) { + i := 0 + if m.MacPayload != nil { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(m.MacPayload.Size())) + n8, err := m.MacPayload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 } - if m.FCnt != 0 { - n += 1 + sovLorawan(uint64(m.FCnt)) + return i, nil +} +func (m *Message_JoinRequestPayload) MarshalTo(data []byte) (int, error) { + i := 0 + if m.JoinRequestPayload != nil { + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(m.JoinRequestPayload.Size())) + n9, err := m.JoinRequestPayload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n9 } - return n + return i, nil +} +func (m *Message_JoinAcceptPayload) MarshalTo(data []byte) (int, error) { + i := 0 + if m.JoinAcceptPayload != nil { + data[i] = 0x2a + i++ + i = encodeVarintLorawan(data, i, uint64(m.JoinAcceptPayload.Size())) + n10, err := m.JoinAcceptPayload.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n10 + } + return i, nil +} +func (m *MHDR) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil } -func (m *TxConfiguration) Size() (n int) { +func (m *MHDR) MarshalTo(data []byte) (int, error) { + var i int + _ = i var l int _ = l - if m.Modulation != 0 { - n += 1 + sovLorawan(uint64(m.Modulation)) - } - l = len(m.DataRate) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) - } - if m.BitRate != 0 { - n += 1 + sovLorawan(uint64(m.BitRate)) + if m.MType != 0 { + data[i] = 0x8 + i++ + i = encodeVarintLorawan(data, i, uint64(m.MType)) } - l = len(m.CodingRate) - if l > 0 { - n += 1 + l + sovLorawan(uint64(l)) + if m.Major != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Major)) } - if m.FCnt != 0 { - n += 1 + sovLorawan(uint64(m.FCnt)) + return i, nil +} + +func (m *MACPayload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err } - return n + return data[:n], nil } -func (m *ActivationMetadata) Size() (n int) { +func (m *MACPayload) MarshalTo(data []byte) (int, error) { + var i int + _ = i var l int _ = l - if m.AppEui != nil { - l = m.AppEui.Size() - n += 1 + l + sovLorawan(uint64(l)) - } - if m.DevEui != nil { - l = m.DevEui.Size() - n += 1 + l + sovLorawan(uint64(l)) + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.FHDR.Size())) + n11, err := m.FHDR.MarshalTo(data[i:]) + if err != nil { + return 0, err } - if m.DevAddr != nil { - l = m.DevAddr.Size() - n += 1 + l + sovLorawan(uint64(l)) + i += n11 + if m.FPort != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FPort)) } - if m.NwkSKey != nil { - l = m.NwkSKey.Size() - n += 1 + l + sovLorawan(uint64(l)) + if len(m.FrmPayload) > 0 { + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.FrmPayload))) + i += copy(data[i:], m.FrmPayload) } - if m.Rx1DrOffset != 0 { - n += 1 + sovLorawan(uint64(m.Rx1DrOffset)) + return i, nil +} + +func (m *FHDR) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err } - if m.Rx2Dr != 0 { - n += 1 + sovLorawan(uint64(m.Rx2Dr)) + return data[:n], nil +} + +func (m *FHDR) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) + n12, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n12 + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) + n13, err := m.FCtrl.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n13 + if m.FCnt != 0 { + data[i] = 0x18 + i++ + i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, msg := range m.FOpts { + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(msg.Size())) + n, err := msg.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *FCtrl) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *FCtrl) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Adr { + data[i] = 0x8 + i++ + if m.Adr { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.AdrAckReq { + data[i] = 0x10 + i++ + if m.AdrAckReq { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.Ack { + data[i] = 0x18 + i++ + if m.Ack { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + if m.FPending { + data[i] = 0x20 + i++ + if m.FPending { + data[i] = 1 + } else { + data[i] = 0 + } + i++ + } + return i, nil +} + +func (m *MACCommand) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *MACCommand) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Cid != 0 { + data[i] = 0x8 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Cid)) + } + if len(m.Payload) > 0 { + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.Payload))) + i += copy(data[i:], m.Payload) + } + return i, nil +} + +func (m *JoinRequestPayload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinRequestPayload) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(m.AppEui.Size())) + n14, err := m.AppEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n14 + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.DevEui.Size())) + n15, err := m.DevEui.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n15 + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(m.DevNonce.Size())) + n16, err := m.DevNonce.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n16 + return i, nil +} + +func (m *JoinAcceptPayload) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *JoinAcceptPayload) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Encrypted) > 0 { + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(len(m.Encrypted))) + i += copy(data[i:], m.Encrypted) } + data[i] = 0x12 + i++ + i = encodeVarintLorawan(data, i, uint64(m.AppNonce.Size())) + n17, err := m.AppNonce.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n17 + data[i] = 0x1a + i++ + i = encodeVarintLorawan(data, i, uint64(m.NetId.Size())) + n18, err := m.NetId.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n18 + data[i] = 0x22 + i++ + i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) + n19, err := m.DevAddr.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n19 + data[i] = 0x2a + i++ + i = encodeVarintLorawan(data, i, uint64(m.DLSettings.Size())) + n20, err := m.DLSettings.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n20 if m.RxDelay != 0 { - n += 1 + sovLorawan(uint64(m.RxDelay)) + data[i] = 0x30 + i++ + i = encodeVarintLorawan(data, i, uint64(m.RxDelay)) } - if len(m.CfList) > 0 { - l = 0 - for _, e := range m.CfList { - l += sovLorawan(uint64(e)) + if m.CfList != nil { + data[i] = 0x3a + i++ + i = encodeVarintLorawan(data, i, uint64(m.CfList.Size())) + n21, err := m.CfList.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n21 + } + return i, nil +} + +func (m *DLSettings) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *DLSettings) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Rx1DrOffset != 0 { + data[i] = 0x8 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + data[i] = 0x10 + i++ + i = encodeVarintLorawan(data, i, uint64(m.Rx2Dr)) + } + return i, nil +} + +func (m *CFList) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *CFList) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Freq) > 0 { + data23 := make([]byte, len(m.Freq)*10) + var j22 int + for _, num := range m.Freq { + for num >= 1<<7 { + data23[j22] = uint8(uint64(num)&0x7f | 0x80) + num >>= 7 + j22++ + } + data23[j22] = uint8(num) + j22++ + } + data[i] = 0xa + i++ + i = encodeVarintLorawan(data, i, uint64(j22)) + i += copy(data[i:], data23[:j22]) + } + return i, nil +} + +func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + data[offset+4] = uint8(v >> 32) + data[offset+5] = uint8(v >> 40) + data[offset+6] = uint8(v >> 48) + data[offset+7] = uint8(v >> 56) + return offset + 8 +} +func encodeFixed32Lorawan(data []byte, offset int, v uint32) int { + data[offset] = uint8(v) + data[offset+1] = uint8(v >> 8) + data[offset+2] = uint8(v >> 16) + data[offset+3] = uint8(v >> 24) + return offset + 4 +} +func encodeVarintLorawan(data []byte, offset int, v uint64) int { + for v >= 1<<7 { + data[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + data[offset] = uint8(v) + return offset + 1 +} +func (m *Metadata) Size() (n int) { + var l int + _ = l + if m.Modulation != 0 { + n += 1 + sovLorawan(uint64(m.Modulation)) + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.BitRate != 0 { + n += 1 + sovLorawan(uint64(m.BitRate)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + return n +} + +func (m *TxConfiguration) Size() (n int) { + var l int + _ = l + if m.Modulation != 0 { + n += 1 + sovLorawan(uint64(m.Modulation)) + } + l = len(m.DataRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.BitRate != 0 { + n += 1 + sovLorawan(uint64(m.BitRate)) + } + l = len(m.CodingRate) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + return n +} + +func (m *ActivationMetadata) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = m.AppEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.DevEui != nil { + l = m.DevEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.DevAddr != nil { + l = m.DevAddr.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.NwkSKey != nil { + l = m.NwkSKey.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + if m.Rx1DrOffset != 0 { + n += 1 + sovLorawan(uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + n += 1 + sovLorawan(uint64(m.Rx2Dr)) + } + if m.RxDelay != 0 { + n += 1 + sovLorawan(uint64(m.RxDelay)) + } + if m.CfList != nil { + l = m.CfList.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *Message) Size() (n int) { + var l int + _ = l + l = m.MHDR.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = len(m.Mic) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + if m.Payload != nil { + n += m.Payload.Size() + } + return n +} + +func (m *Message_MacPayload) Size() (n int) { + var l int + _ = l + if m.MacPayload != nil { + l = m.MacPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} +func (m *Message_JoinRequestPayload) Size() (n int) { + var l int + _ = l + if m.JoinRequestPayload != nil { + l = m.JoinRequestPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} +func (m *Message_JoinAcceptPayload) Size() (n int) { + var l int + _ = l + if m.JoinAcceptPayload != nil { + l = m.JoinAcceptPayload.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} +func (m *MHDR) Size() (n int) { + var l int + _ = l + if m.MType != 0 { + n += 1 + sovLorawan(uint64(m.MType)) + } + if m.Major != 0 { + n += 1 + sovLorawan(uint64(m.Major)) + } + return n +} + +func (m *MACPayload) Size() (n int) { + var l int + _ = l + l = m.FHDR.Size() + n += 1 + l + sovLorawan(uint64(l)) + if m.FPort != 0 { + n += 1 + sovLorawan(uint64(m.FPort)) + } + l = len(m.FrmPayload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *FHDR) Size() (n int) { + var l int + _ = l + l = m.DevAddr.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.FCtrl.Size() + n += 1 + l + sovLorawan(uint64(l)) + if m.FCnt != 0 { + n += 1 + sovLorawan(uint64(m.FCnt)) + } + if len(m.FOpts) > 0 { + for _, e := range m.FOpts { + l = e.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + } + return n +} + +func (m *FCtrl) Size() (n int) { + var l int + _ = l + if m.Adr { + n += 2 + } + if m.AdrAckReq { + n += 2 + } + if m.Ack { + n += 2 + } + if m.FPending { + n += 2 + } + return n +} + +func (m *MACCommand) Size() (n int) { + var l int + _ = l + if m.Cid != 0 { + n += 1 + sovLorawan(uint64(m.Cid)) + } + l = len(m.Payload) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *JoinRequestPayload) Size() (n int) { + var l int + _ = l + l = m.AppEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DevEui.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DevNonce.Size() + n += 1 + l + sovLorawan(uint64(l)) + return n +} + +func (m *JoinAcceptPayload) Size() (n int) { + var l int + _ = l + l = len(m.Encrypted) + if l > 0 { + n += 1 + l + sovLorawan(uint64(l)) + } + l = m.AppNonce.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.NetId.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DevAddr.Size() + n += 1 + l + sovLorawan(uint64(l)) + l = m.DLSettings.Size() + n += 1 + l + sovLorawan(uint64(l)) + if m.RxDelay != 0 { + n += 1 + sovLorawan(uint64(m.RxDelay)) + } + if m.CfList != nil { + l = m.CfList.Size() + n += 1 + l + sovLorawan(uint64(l)) + } + return n +} + +func (m *DLSettings) Size() (n int) { + var l int + _ = l + if m.Rx1DrOffset != 0 { + n += 1 + sovLorawan(uint64(m.Rx1DrOffset)) + } + if m.Rx2Dr != 0 { + n += 1 + sovLorawan(uint64(m.Rx2Dr)) + } + return n +} + +func (m *CFList) Size() (n int) { + var l int + _ = l + if len(m.Freq) > 0 { + l = 0 + for _, e := range m.Freq { + l += sovLorawan(uint64(e)) + } + n += 1 + sovLorawan(uint64(l)) + l + } + return n +} + +func sovLorawan(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozLorawan(x uint64) (n int) { + return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *Metadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + m.Modulation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Modulation |= (Modulation(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + } + m.BitRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.BitRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *TxConfiguration) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + } + m.Modulation = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Modulation |= (Modulation(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.DataRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + } + m.BitRate = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.BitRate |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.CodingRate = string(data[iNdEx:postIndex]) + iNdEx = postIndex + case 15: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + } + m.FCnt = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FCnt |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ActivationMetadata) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ActivationMetadata: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ActivationMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.AppEUI + m.AppEui = &v + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevEUI + m.DevEui = &v + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.DevAddr + m.DevAddr = &v + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey + m.NwkSKey = &v + if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 11: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx1DrOffset", wireType) + } + m.Rx1DrOffset = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 12: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Rx2Dr", wireType) + } + m.Rx2Dr = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Rx2Dr |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 13: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field RxDelay", wireType) + } + m.RxDelay = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.RxDelay |= (uint32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CfList == nil { + m.CfList = &CFList{} + } + if err := m.CfList.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Message) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Message: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Message: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MHDR", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Mic", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Mic = append(m.Mic[:0], data[iNdEx:postIndex]...) + if m.Mic == nil { + m.Mic = []byte{} + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MacPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &MACPayload{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Payload = &Message_MacPayload{v} + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JoinRequestPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &JoinRequestPayload{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Payload = &Message_JoinRequestPayload{v} + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field JoinAcceptPayload", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &JoinAcceptPayload{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Payload = &Message_JoinAcceptPayload{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MHDR) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MHDR: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MHDR: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field MType", wireType) + } + m.MType = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.MType |= (MType(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Major", wireType) + } + m.Major = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.Major |= (Major(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MACPayload) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MACPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MACPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FHDR", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.FHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPort", wireType) + } + m.FPort = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + m.FPort |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FrmPayload", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + byteLen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.FrmPayload = append(m.FrmPayload[:0], data[iNdEx:postIndex]...) + if m.FrmPayload == nil { + m.FrmPayload = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy } - n += 1 + sovLorawan(uint64(l)) + l } - return n -} -func sovLorawan(x uint64) (n int) { - for { - n++ - x >>= 7 - if x == 0 { - break - } + if iNdEx > l { + return io.ErrUnexpectedEOF } - return n -} -func sozLorawan(x uint64) (n int) { - return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) + return nil } -func (m *Metadata) Unmarshal(data []byte) error { +func (m *FHDR) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -471,17 +2475,17 @@ func (m *Metadata) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: Metadata: wiretype end group for non-group") + return fmt.Errorf("proto: FHDR: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: FHDR: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 11: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } - m.Modulation = 0 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -491,16 +2495,27 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.Modulation |= (Modulation(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - case 12: + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FCtrl", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -510,26 +2525,27 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthLorawan } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.DataRate = string(data[iNdEx:postIndex]) + if err := m.FCtrl.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 13: + case 3: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) } - m.BitRate = 0 + m.FCnt = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -539,16 +2555,16 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.BitRate |= (uint32(b) & 0x7F) << shift + m.FCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - case 14: + case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field FOpts", wireType) } - var stringLen uint64 + var msglen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -558,26 +2574,78 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + msglen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if msglen < 0 { return ErrInvalidLengthLorawan } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + msglen if postIndex > l { return io.ErrUnexpectedEOF } - m.CodingRate = string(data[iNdEx:postIndex]) + m.FOpts = append(m.FOpts, MACCommand{}) + if err := m.FOpts[len(m.FOpts)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 15: + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FCtrl) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: FCtrl: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FCtrl: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Adr", wireType) } - m.FCnt = 0 + var v int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -587,11 +2655,72 @@ func (m *Metadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Adr = bool(v != 0) + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field AdrAckReq", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.AdrAckReq = bool(v != 0) + case 3: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Ack", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.Ack = bool(v != 0) + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field FPending", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + v |= (int(b) & 0x7F) << shift if b < 0x80 { break } } + m.FPending = bool(v != 0) default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -613,7 +2742,7 @@ func (m *Metadata) Unmarshal(data []byte) error { } return nil } -func (m *TxConfiguration) Unmarshal(data []byte) error { +func (m *MACCommand) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -636,17 +2765,17 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: TxConfiguration: wiretype end group for non-group") + return fmt.Errorf("proto: MACCommand: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: TxConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: MACCommand: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 11: + case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Modulation", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Cid", wireType) } - m.Modulation = 0 + m.Cid = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -656,16 +2785,16 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.Modulation |= (Modulation(b) & 0x7F) << shift + m.Cid |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - case 12: + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DataRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Payload", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -675,26 +2804,78 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthLorawan } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.DataRate = string(data[iNdEx:postIndex]) + m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + if m.Payload == nil { + m.Payload = []byte{} + } iNdEx = postIndex - case 13: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field BitRate", wireType) + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *JoinRequestPayload) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break } - m.BitRate = 0 + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: JoinRequestPayload: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: JoinRequestPayload: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + } + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -704,16 +2885,27 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.BitRate |= (uint32(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - case 14: + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field CodingRate", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) } - var stringLen uint64 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -723,26 +2915,27 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - stringLen |= (uint64(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - intStringLen := int(stringLen) - if intStringLen < 0 { + if byteLen < 0 { return ErrInvalidLengthLorawan } - postIndex := iNdEx + intStringLen + postIndex := iNdEx + byteLen if postIndex > l { return io.ErrUnexpectedEOF } - m.CodingRate = string(data[iNdEx:postIndex]) + if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } iNdEx = postIndex - case 15: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field FCnt", wireType) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevNonce", wireType) } - m.FCnt = 0 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -752,11 +2945,22 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.FCnt |= (uint32(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } + if byteLen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DevNonce.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipLorawan(data[iNdEx:]) @@ -778,7 +2982,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } return nil } -func (m *ActivationMetadata) Unmarshal(data []byte) error { +func (m *JoinAcceptPayload) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 for iNdEx < l { @@ -801,15 +3005,15 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ActivationMetadata: wiretype end group for non-group") + return fmt.Errorf("proto: JoinAcceptPayload: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ActivationMetadata: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: JoinAcceptPayload: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Encrypted", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -833,15 +3037,14 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.AppEUI - m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { - return err + m.Encrypted = append(m.Encrypted[:0], data[iNdEx:postIndex]...) + if m.Encrypted == nil { + m.Encrypted = []byte{} } iNdEx = postIndex case 2: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppNonce", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -865,15 +3068,13 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.DevEUI - m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppNonce.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field NetId", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -897,15 +3098,13 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.DevAddr - m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.NetId.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex case 4: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field NwkSKey", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field DevAddr", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -929,17 +3128,45 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey - m.NwkSKey = &v - if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex - case 11: + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DLSettings", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.DLSettings.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Rx1DrOffset", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field RxDelay", wireType) } - m.Rx1DrOffset = 0 + m.RxDelay = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -949,16 +3176,99 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift + m.RxDelay |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - case 12: + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthLorawan + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.CfList == nil { + m.CfList = &CFList{} + } + if err := m.CfList.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *DLSettings) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: DLSettings: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: DLSettings: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Rx2Dr", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Rx1DrOffset", wireType) } - m.Rx2Dr = 0 + m.Rx1DrOffset = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -968,16 +3278,16 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.Rx2Dr |= (uint32(b) & 0x7F) << shift + m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - case 13: + case 2: if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field RxDelay", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Rx2Dr", wireType) } - m.RxDelay = 0 + m.Rx2Dr = 0 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -987,12 +3297,62 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - m.RxDelay |= (uint32(b) & 0x7F) << shift + m.Rx2Dr |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - case 14: + default: + iNdEx = preIndex + skippy, err := skipLorawan(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthLorawan + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *CFList) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLorawan + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: CFList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: CFList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: if wireType == 2 { var packedLen int for shift := uint(0); ; shift += 7 { @@ -1017,7 +3377,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } for iNdEx < postIndex { - var v uint64 + var v uint32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -1027,15 +3387,15 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - v |= (uint64(b) & 0x7F) << shift + v |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - m.CfList = append(m.CfList, v) + m.Freq = append(m.Freq, v) } } else if wireType == 0 { - var v uint64 + var v uint32 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLorawan @@ -1045,14 +3405,14 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } b := data[iNdEx] iNdEx++ - v |= (uint64(b) & 0x7F) << shift + v |= (uint32(b) & 0x7F) << shift if b < 0x80 { break } } - m.CfList = append(m.CfList, v) + m.Freq = append(m.Freq, v) } else { - return fmt.Errorf("proto: wrong wireType = %d for field CfList", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field Freq", wireType) } default: iNdEx = preIndex @@ -1185,43 +3545,86 @@ func init() { } var fileDescriptorLorawan = []byte{ - // 607 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x94, 0xcf, 0x4e, 0xdb, 0x4e, - 0x10, 0xc7, 0x31, 0x09, 0x76, 0x18, 0x48, 0xb0, 0x8c, 0x7e, 0xfa, 0xb9, 0xad, 0x94, 0x44, 0x9c, - 0x22, 0xa4, 0xe6, 0x8f, 0x03, 0x24, 0x39, 0x06, 0x12, 0xa4, 0x0a, 0x08, 0xaa, 0x43, 0x2e, 0xbd, - 0xac, 0x36, 0xf6, 0xda, 0xac, 0x12, 0xbc, 0xd6, 0x66, 0x93, 0x38, 0xb7, 0x3e, 0x46, 0x5f, 0xa2, - 0xb7, 0x3e, 0x44, 0xd5, 0x53, 0xcf, 0x1c, 0x50, 0x45, 0x5f, 0xa4, 0xda, 0x35, 0x50, 0x6e, 0x55, - 0xb9, 0xf5, 0x94, 0xf9, 0xce, 0x77, 0xe6, 0xb3, 0xa3, 0xcc, 0xc8, 0x70, 0x1c, 0x52, 0x71, 0x3d, - 0x1f, 0x57, 0x3d, 0x76, 0x53, 0xbb, 0xba, 0x26, 0x57, 0xd7, 0x34, 0x0a, 0x67, 0x03, 0x22, 0x96, - 0x8c, 0x4f, 0x6a, 0x42, 0x44, 0x35, 0x1c, 0xd3, 0x5a, 0xcc, 0x99, 0x60, 0x1e, 0x9b, 0xd6, 0xa6, - 0x8c, 0xe3, 0x25, 0x8e, 0x1e, 0x7f, 0xab, 0xca, 0xb0, 0x8c, 0x07, 0xf9, 0xfa, 0xed, 0x33, 0x58, - 0xc8, 0x42, 0x96, 0x36, 0x8e, 0xe7, 0x81, 0x52, 0x4a, 0xa8, 0x28, 0xed, 0xdb, 0xfb, 0xac, 0x41, - 0xee, 0x82, 0x08, 0xec, 0x63, 0x81, 0xad, 0x26, 0xc0, 0x0d, 0xf3, 0xe7, 0x53, 0x2c, 0x28, 0x8b, - 0xec, 0xad, 0xb2, 0x56, 0x29, 0x38, 0xbb, 0xd5, 0xc7, 0x87, 0x2e, 0x9e, 0x2c, 0xf7, 0x59, 0x99, - 0xf5, 0x06, 0x36, 0x65, 0x33, 0xe2, 0x58, 0x10, 0x7b, 0xbb, 0xac, 0x55, 0x36, 0xdd, 0x9c, 0x4c, - 0xb8, 0x58, 0x10, 0xeb, 0x15, 0xe4, 0xc6, 0x54, 0xa4, 0x5e, 0xbe, 0xac, 0x55, 0xf2, 0xae, 0x31, - 0xa6, 0x42, 0x59, 0x25, 0xd8, 0xf2, 0x98, 0x4f, 0xa3, 0x30, 0x75, 0x0b, 0xaa, 0x13, 0xd2, 0x94, - 0x2a, 0xd8, 0x85, 0x8d, 0x00, 0x79, 0x91, 0xb0, 0x77, 0x54, 0x63, 0x36, 0x38, 0x89, 0xc4, 0xde, - 0x17, 0x0d, 0x76, 0xae, 0x92, 0x13, 0x16, 0x05, 0x34, 0x9c, 0xf3, 0x74, 0x82, 0x7f, 0x60, 0xec, - 0x6f, 0x19, 0xb0, 0xba, 0x9e, 0xa0, 0x0b, 0xf5, 0xf8, 0xd3, 0x1f, 0x3e, 0x00, 0x03, 0xc7, 0x31, - 0x22, 0x73, 0x6a, 0x6b, 0x65, 0xad, 0xb2, 0x7d, 0x7c, 0x78, 0x7b, 0x57, 0x6a, 0xfc, 0xe9, 0x1c, - 0x3c, 0xc6, 0x49, 0x4d, 0xac, 0x62, 0x32, 0xab, 0x76, 0xe3, 0xb8, 0x3f, 0x7a, 0xe7, 0xea, 0x38, - 0x8e, 0xfb, 0x73, 0x2a, 0x79, 0x3e, 0x59, 0x28, 0xde, 0xfa, 0x8b, 0x78, 0x3d, 0xb2, 0x50, 0x3c, - 0x9f, 0x2c, 0x24, 0xef, 0x3d, 0xe4, 0x24, 0x0f, 0xfb, 0x3e, 0xb7, 0x33, 0x0a, 0x78, 0x74, 0x7b, - 0x57, 0x72, 0xfe, 0x0e, 0xd8, 0xf5, 0x7d, 0xee, 0xca, 0xb9, 0x64, 0x60, 0xb9, 0xb0, 0x19, 0x2d, - 0x27, 0x68, 0x86, 0x26, 0x64, 0x65, 0x67, 0x5f, 0xc4, 0x1c, 0x2c, 0x27, 0xc3, 0x33, 0xb2, 0x72, - 0x8d, 0x28, 0x0d, 0xac, 0x3d, 0xc8, 0xf3, 0xa4, 0x81, 0x7c, 0x8e, 0x58, 0x10, 0xcc, 0x88, 0x50, - 0x37, 0x90, 0x77, 0xb7, 0x78, 0xd2, 0xe8, 0xf1, 0x4b, 0x95, 0xb2, 0xfe, 0x03, 0x9d, 0x27, 0x0e, - 0xf2, 0xb9, 0x5a, 0x76, 0xde, 0xdd, 0xe0, 0x89, 0xd3, 0xe3, 0x72, 0xd3, 0x3c, 0x41, 0x3e, 0x99, - 0xe2, 0xd5, 0xe3, 0xa6, 0x79, 0xd2, 0x93, 0xd2, 0xfa, 0x1f, 0x0c, 0x2f, 0x40, 0x53, 0x3a, 0x13, - 0x76, 0xa1, 0x9c, 0xa9, 0x64, 0x5d, 0xdd, 0x0b, 0xce, 0xe9, 0x4c, 0xec, 0x97, 0x00, 0x7e, 0x1f, - 0x95, 0x95, 0x83, 0xec, 0xf9, 0xa5, 0xdb, 0x35, 0xd7, 0x2c, 0x03, 0x32, 0xa7, 0xc3, 0x33, 0x53, - 0xdb, 0xff, 0xa8, 0x81, 0xee, 0x92, 0x50, 0xba, 0x05, 0x80, 0xfe, 0x08, 0xb5, 0x8f, 0x9a, 0xa8, - 0xdd, 0xaa, 0x9b, 0x6b, 0x52, 0x8f, 0x86, 0xa8, 0x53, 0x77, 0x50, 0xc7, 0x69, 0x9b, 0x9a, 0xd4, - 0x27, 0x03, 0xd4, 0x6a, 0x75, 0x50, 0xab, 0xdd, 0x32, 0xd7, 0x2d, 0x00, 0xbd, 0x3f, 0x42, 0x07, - 0xcd, 0xa6, 0x99, 0x91, 0x5e, 0x77, 0x84, 0x3a, 0x8d, 0x43, 0x55, 0x9b, 0x7d, 0xa8, 0x3d, 0x68, - 0xd5, 0xd1, 0x61, 0xa3, 0x6e, 0x6e, 0xc8, 0xda, 0xee, 0x10, 0x75, 0x9c, 0xa6, 0xa9, 0x4b, 0x6f, - 0x78, 0x86, 0x3a, 0x4e, 0x5d, 0x69, 0xe3, 0xf8, 0xf4, 0xeb, 0x7d, 0x51, 0xfb, 0x7e, 0x5f, 0xd4, - 0x7e, 0xdc, 0x17, 0xb5, 0x4f, 0x3f, 0x8b, 0x6b, 0x1f, 0x0e, 0x5e, 0xf2, 0x95, 0x19, 0xeb, 0x2a, - 0xd3, 0xfc, 0x15, 0x00, 0x00, 0xff, 0xff, 0x84, 0xe2, 0x37, 0xdc, 0xa4, 0x04, 0x00, 0x00, + // 1281 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xd4, 0x56, 0x4b, 0x6f, 0x1b, 0x45, + 0x1c, 0xcf, 0xda, 0x5e, 0x3f, 0xfe, 0x8e, 0x93, 0xed, 0xb4, 0x15, 0xa6, 0xad, 0x92, 0xc8, 0x02, + 0x29, 0x8a, 0x20, 0x76, 0xec, 0xb6, 0x89, 0x41, 0x42, 0xf2, 0x2b, 0x34, 0x6d, 0x62, 0xa7, 0xe3, + 0x58, 0x45, 0x5c, 0x46, 0x9b, 0xdd, 0x59, 0x67, 0x63, 0xef, 0xa3, 0xe3, 0x71, 0x62, 0x73, 0xe2, + 0x53, 0x20, 0xbe, 0x03, 0xe2, 0xc6, 0x81, 0x8f, 0xd0, 0x63, 0x2f, 0x5c, 0x8a, 0x14, 0xa1, 0xf2, + 0x45, 0xd0, 0xcc, 0xae, 0x63, 0xc7, 0x81, 0xa2, 0xa6, 0x5c, 0x38, 0xed, 0xff, 0xf9, 0x9b, 0xff, + 0xfc, 0x5f, 0xb3, 0x50, 0xed, 0xda, 0xfc, 0x64, 0x78, 0xbc, 0x69, 0x78, 0x4e, 0xfe, 0xe8, 0x84, + 0x1e, 0x9d, 0xd8, 0x6e, 0x77, 0xd0, 0xa4, 0xfc, 0xdc, 0x63, 0xbd, 0x3c, 0xe7, 0x6e, 0x5e, 0xf7, + 0xed, 0xbc, 0xcf, 0x3c, 0xee, 0x19, 0x5e, 0x3f, 0xdf, 0xf7, 0x98, 0x7e, 0xae, 0xbb, 0x93, 0xef, + 0xa6, 0x54, 0xa0, 0x44, 0xc8, 0xde, 0xfb, 0x7c, 0x06, 0xac, 0xeb, 0x75, 0xbd, 0xc0, 0xf1, 0x78, + 0x68, 0x49, 0x4e, 0x32, 0x92, 0x0a, 0xfc, 0x72, 0x3f, 0x2b, 0x90, 0x3c, 0xa0, 0x5c, 0x37, 0x75, + 0xae, 0xa3, 0x12, 0x80, 0xe3, 0x99, 0xc3, 0xbe, 0xce, 0x6d, 0xcf, 0xcd, 0xa6, 0xd7, 0x94, 0xf5, + 0xa5, 0xe2, 0xed, 0xcd, 0xc9, 0x41, 0x07, 0x97, 0x2a, 0x3c, 0x63, 0x86, 0xee, 0x43, 0x4a, 0x38, + 0x13, 0xa6, 0x73, 0x9a, 0x5d, 0x5c, 0x53, 0xd6, 0x53, 0x38, 0x29, 0x04, 0x58, 0xe7, 0x14, 0x7d, + 0x0c, 0xc9, 0x63, 0x9b, 0x07, 0xba, 0xcc, 0x9a, 0xb2, 0x9e, 0xc1, 0x89, 0x63, 0x9b, 0x4b, 0xd5, + 0x2a, 0xa4, 0x0d, 0xcf, 0xb4, 0xdd, 0x6e, 0xa0, 0x5d, 0x92, 0x9e, 0x10, 0x88, 0xa4, 0xc1, 0x6d, + 0x50, 0x2d, 0x62, 0xb8, 0x3c, 0xbb, 0x2c, 0x1d, 0x63, 0x56, 0xcd, 0xe5, 0xb9, 0x5f, 0x14, 0x58, + 0x3e, 0x1a, 0xd5, 0x3c, 0xd7, 0xb2, 0xbb, 0x43, 0x16, 0x44, 0xf0, 0x3f, 0x08, 0xfb, 0xf7, 0x28, + 0xa0, 0x8a, 0xc1, 0xed, 0x33, 0x79, 0xf8, 0x65, 0xc2, 0x9b, 0x90, 0xd0, 0x7d, 0x9f, 0xd0, 0xa1, + 0x9d, 0x55, 0xd6, 0x94, 0xf5, 0xc5, 0xea, 0xa3, 0x37, 0x17, 0xab, 0x5b, 0xff, 0xd6, 0x0e, 0x86, + 0xc7, 0x68, 0x9e, 0x8f, 0x7d, 0x3a, 0xd8, 0xac, 0xf8, 0x7e, 0xa3, 0xb3, 0x87, 0xe3, 0xba, 0xef, + 0x37, 0x86, 0xb6, 0xc0, 0x33, 0xe9, 0x99, 0xc4, 0x8b, 0xdc, 0x08, 0xaf, 0x4e, 0xcf, 0x24, 0x9e, + 0x49, 0xcf, 0x04, 0xde, 0x73, 0x48, 0x0a, 0x3c, 0xdd, 0x34, 0x59, 0x36, 0x2a, 0x01, 0x1f, 0xbf, + 0xb9, 0x58, 0x2d, 0xbe, 0x1f, 0x60, 0xc5, 0x34, 0x19, 0x16, 0x71, 0x09, 0x02, 0x61, 0x48, 0xb9, + 0xe7, 0x3d, 0x32, 0x20, 0x3d, 0x3a, 0xce, 0xc6, 0x6e, 0x84, 0xd9, 0x3c, 0xef, 0xb5, 0x9f, 0xd1, + 0x31, 0x4e, 0xb8, 0x01, 0x81, 0x72, 0x90, 0x61, 0xa3, 0x2d, 0x62, 0x32, 0xe2, 0x59, 0xd6, 0x80, + 0x72, 0xd9, 0x03, 0x19, 0x9c, 0x66, 0xa3, 0xad, 0x3a, 0x6b, 0x49, 0x11, 0xba, 0x0b, 0x71, 0x36, + 0x2a, 0x12, 0x93, 0xc9, 0x62, 0x67, 0xb0, 0xca, 0x46, 0xc5, 0x3a, 0x13, 0x95, 0x66, 0x23, 0x62, + 0xd2, 0xbe, 0x3e, 0x9e, 0x54, 0x9a, 0x8d, 0xea, 0x82, 0x45, 0xeb, 0x90, 0x30, 0x2c, 0xd2, 0xb7, + 0x07, 0x5c, 0x56, 0x39, 0x5d, 0x5c, 0xbe, 0xec, 0xa9, 0xda, 0xee, 0xbe, 0x3d, 0xe0, 0x38, 0x6e, + 0x58, 0xe2, 0x9b, 0xfb, 0x29, 0x02, 0x89, 0x03, 0x3a, 0x18, 0xe8, 0x5d, 0x8a, 0x3e, 0x03, 0xd5, + 0x21, 0x27, 0x26, 0x93, 0x05, 0x4d, 0x17, 0x33, 0xd3, 0x3e, 0x7c, 0x52, 0xc7, 0xd5, 0xe4, 0xab, + 0x8b, 0xd5, 0x85, 0xd7, 0x17, 0xab, 0x0a, 0x8e, 0x39, 0x4f, 0x4c, 0x86, 0x34, 0x88, 0x3a, 0xb6, + 0x11, 0x14, 0x0b, 0x0b, 0x12, 0x3d, 0x86, 0xb4, 0xa3, 0x1b, 0xc4, 0xd7, 0xc7, 0x7d, 0x4f, 0x37, + 0x65, 0xd6, 0xd3, 0xb3, 0xdd, 0x5c, 0xa9, 0x1d, 0x06, 0xaa, 0x27, 0x0b, 0x18, 0x1c, 0xdd, 0x08, + 0x39, 0xd4, 0x82, 0x3b, 0xa7, 0x9e, 0xed, 0x12, 0x46, 0x5f, 0x0e, 0xe9, 0x80, 0x5f, 0x02, 0xc4, + 0x24, 0xc0, 0xfd, 0x4b, 0x80, 0xa7, 0x9e, 0xed, 0xe2, 0xc0, 0x66, 0x0a, 0x84, 0x4e, 0xaf, 0x49, + 0xd1, 0x3e, 0xdc, 0x96, 0x80, 0xba, 0x61, 0x50, 0x7f, 0x8a, 0xa7, 0x4a, 0xbc, 0x7b, 0x57, 0xf0, + 0x2a, 0xd2, 0x64, 0x0a, 0x77, 0xeb, 0x74, 0x5e, 0x58, 0x4d, 0x41, 0x22, 0x24, 0x73, 0x6d, 0x88, + 0x89, 0x5c, 0xa0, 0x4f, 0x21, 0xee, 0x10, 0x51, 0x51, 0x99, 0xaa, 0xa5, 0xe2, 0xd2, 0xf4, 0x92, + 0x47, 0x63, 0x9f, 0x62, 0xd5, 0x11, 0x1f, 0xf4, 0x09, 0xa8, 0x8e, 0x7e, 0xea, 0x31, 0x99, 0xa4, + 0x2b, 0x56, 0x42, 0x8a, 0x03, 0x65, 0x8e, 0x01, 0x4c, 0x53, 0x23, 0x8a, 0x60, 0xfd, 0x6d, 0x11, + 0x76, 0xe7, 0x8a, 0x60, 0x89, 0x22, 0xdc, 0x85, 0xb8, 0x45, 0x7c, 0x8f, 0x71, 0x79, 0x84, 0x8a, + 0x55, 0xeb, 0xd0, 0x63, 0x5c, 0x4c, 0xba, 0xc5, 0x9c, 0x2b, 0x95, 0x58, 0xc4, 0x60, 0x31, 0x67, + 0x72, 0x91, 0xdf, 0x14, 0x88, 0x09, 0x40, 0xd4, 0x99, 0x19, 0x93, 0x60, 0x8e, 0xbf, 0x10, 0x47, + 0x7c, 0xe8, 0xa8, 0xe4, 0x45, 0x5c, 0x06, 0x67, 0x7d, 0x19, 0x57, 0x7a, 0xe6, 0xea, 0xbb, 0x35, + 0xce, 0xfa, 0x33, 0xf7, 0x50, 0x2d, 0x21, 0x98, 0xae, 0x9e, 0xe8, 0x74, 0xf5, 0xa0, 0x82, 0x40, + 0xf1, 0x7c, 0x3e, 0xc8, 0xc6, 0xd6, 0xa2, 0xf3, 0xbd, 0x54, 0xf3, 0x1c, 0x47, 0x77, 0xcd, 0x6a, + 0x4c, 0x40, 0x61, 0xd5, 0x6a, 0xf9, 0x7c, 0x90, 0x3b, 0x01, 0x55, 0x1e, 0x20, 0xba, 0x53, 0x0f, + 0xaf, 0x94, 0xc4, 0x82, 0x44, 0x2b, 0x90, 0xd6, 0x4d, 0x46, 0x74, 0xa3, 0x27, 0x1a, 0x4d, 0xc6, + 0x95, 0xc4, 0x29, 0xdd, 0x64, 0x15, 0xa3, 0x87, 0xe9, 0x4b, 0xe9, 0x61, 0xf4, 0xe4, 0xf9, 0xc2, + 0xc3, 0xe8, 0x89, 0x3d, 0x6b, 0x11, 0x9f, 0xba, 0x62, 0x3f, 0xca, 0x66, 0x4c, 0xe2, 0xa4, 0x75, + 0x18, 0xf0, 0xb9, 0x1d, 0x59, 0xb5, 0x30, 0x08, 0xe1, 0x6c, 0xd8, 0xa6, 0x3c, 0x2e, 0x83, 0x05, + 0x89, 0xb2, 0x90, 0x98, 0xa4, 0x3f, 0x18, 0x91, 0x09, 0x9b, 0xfb, 0x21, 0x02, 0xe8, 0x7a, 0x2b, + 0x23, 0x3c, 0xbf, 0x50, 0xcb, 0x61, 0x21, 0x3e, 0x60, 0xa9, 0xe2, 0xf9, 0xa5, 0x7a, 0x13, 0xcc, + 0xb9, 0xc5, 0xfa, 0x0d, 0xa4, 0x04, 0xa6, 0xeb, 0xb9, 0x06, 0x0d, 0x37, 0xeb, 0x97, 0x21, 0x6a, + 0xe9, 0xfd, 0x50, 0x9b, 0x02, 0x02, 0x8b, 0xfe, 0x93, 0x54, 0xee, 0xd7, 0x28, 0xdc, 0xba, 0x36, + 0x93, 0xe8, 0x01, 0xa4, 0xa8, 0x6b, 0xb0, 0xb1, 0xcf, 0x69, 0x90, 0xe0, 0x45, 0x3c, 0x15, 0x88, + 0x68, 0x44, 0xd6, 0x82, 0x68, 0x22, 0x37, 0x8e, 0xa6, 0xe2, 0xfb, 0x61, 0x34, 0x7a, 0x48, 0xa1, + 0x16, 0xc4, 0x5d, 0xca, 0x89, 0x1d, 0x8e, 0x4f, 0x75, 0x27, 0x84, 0x2d, 0xbc, 0xcf, 0xba, 0xa7, + 0x7c, 0xaf, 0x8e, 0x55, 0x97, 0xf2, 0x3d, 0xf3, 0xca, 0xa8, 0xc5, 0xfe, 0xbb, 0x51, 0xfb, 0x0a, + 0xd2, 0x66, 0x9f, 0x0c, 0x28, 0xe7, 0xc2, 0x2b, 0x5c, 0x72, 0xd3, 0x49, 0xa9, 0xef, 0xb7, 0x43, + 0xd5, 0xcc, 0xd0, 0x81, 0xd9, 0x9f, 0x48, 0xaf, 0x3c, 0x23, 0xf1, 0x7f, 0x7c, 0x46, 0x12, 0xef, + 0x7e, 0x46, 0xbe, 0x06, 0x98, 0x1e, 0x74, 0xfd, 0x51, 0x53, 0xde, 0xf5, 0xa8, 0x45, 0x66, 0x1e, + 0xb5, 0xdc, 0x03, 0x88, 0x07, 0xd0, 0x08, 0x41, 0xcc, 0x12, 0x83, 0xaa, 0xac, 0x45, 0xe5, 0x42, + 0x60, 0xf4, 0xe5, 0xc6, 0x2a, 0xc0, 0xf4, 0x9f, 0x08, 0x25, 0x21, 0xb6, 0xdf, 0xc2, 0x15, 0x6d, + 0x01, 0x25, 0x20, 0xba, 0xdb, 0x7e, 0xa6, 0x29, 0x1b, 0xdf, 0x2b, 0x10, 0xc7, 0xb4, 0x2b, 0xb4, + 0x4b, 0x00, 0x8d, 0x0e, 0xd9, 0x79, 0x5c, 0x22, 0x3b, 0xdb, 0x05, 0x6d, 0x41, 0xf0, 0x9d, 0x36, + 0x29, 0x17, 0x8a, 0xa4, 0x5c, 0xdc, 0xd1, 0x14, 0xc1, 0xd7, 0x9a, 0x64, 0x7b, 0xbb, 0x4c, 0xb6, + 0x77, 0xb6, 0xb5, 0x08, 0x02, 0x88, 0x37, 0x3a, 0xe4, 0x61, 0xa9, 0xa4, 0x45, 0x85, 0xae, 0xd2, + 0x21, 0xe5, 0xad, 0x47, 0xd2, 0x36, 0x16, 0xda, 0x3e, 0xdc, 0x2e, 0x90, 0x47, 0x5b, 0x05, 0x4d, + 0x15, 0xb6, 0x95, 0x36, 0x29, 0x17, 0x4b, 0x5a, 0x5c, 0xe8, 0xda, 0xcf, 0x48, 0xb9, 0x58, 0x90, + 0x7c, 0x62, 0xe3, 0x23, 0x50, 0xe5, 0x7a, 0x17, 0x0a, 0x11, 0xde, 0x8b, 0x4a, 0x93, 0xe0, 0x2d, + 0x6d, 0x61, 0xe3, 0x3b, 0x50, 0xe5, 0xeb, 0x80, 0x34, 0x58, 0x7c, 0xda, 0xda, 0x6b, 0x12, 0xdc, + 0x78, 0xde, 0x69, 0xb4, 0x8f, 0xb4, 0x05, 0xb4, 0x0c, 0x69, 0x29, 0xa9, 0xd4, 0x6a, 0x8d, 0xc3, + 0x23, 0x4d, 0x41, 0x08, 0x96, 0x3a, 0xcd, 0x5a, 0xab, 0xb9, 0xbb, 0x87, 0x0f, 0x1a, 0x75, 0xd2, + 0x39, 0xd4, 0x22, 0xe8, 0x0e, 0x68, 0xb3, 0xb2, 0x7a, 0xeb, 0x45, 0x53, 0x8b, 0x0a, 0xb0, 0x2b, + 0x76, 0x31, 0xe1, 0x3b, 0x67, 0xa5, 0x56, 0x77, 0x5f, 0xbd, 0x5d, 0x51, 0x5e, 0xbf, 0x5d, 0x51, + 0xfe, 0x78, 0xbb, 0xa2, 0xfc, 0xf8, 0xe7, 0xca, 0xc2, 0xb7, 0x0f, 0x6f, 0xf2, 0xe7, 0x7e, 0x1c, + 0x97, 0x92, 0xd2, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x43, 0xc5, 0xba, 0x6a, 0xf8, 0x0b, 0x00, + 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index ab71692bd..dc9d853a1 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -41,7 +41,7 @@ message ActivationMetadata { uint32 rx1_dr_offset = 11; uint32 rx2_dr = 12; uint32 rx_delay = 13; - repeated uint64 cf_list = 14; + CFList cf_list = 14; } enum Region { @@ -54,3 +54,82 @@ enum Region { AS_923 = 6; SK_920_923 = 7; } + +message Message { + MHDR m_hdr = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + bytes mic = 2; + + oneof Payload { + MACPayload mac_payload = 3; + JoinRequestPayload join_request_payload = 4; + JoinAcceptPayload join_accept_payload = 5; + } +} + +enum Major { + LORAWAN_R1 = 0; +} + +enum MType { + JOIN_REQUEST = 0; + JOIN_ACCEPT = 1; + UNCONFIRMED_UP = 2; + UNCONFIRMED_DOWN = 3; + CONFIRMED_UP = 4; + CONFIRMED_DOWN = 5; +} + +message MHDR { + MType m_type = 1; + Major major = 2; +} + +message MACPayload { + FHDR f_hdr = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + int32 f_port = 2; + bytes frm_payload = 3; +} + +message FHDR { + bytes dev_addr = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + FCtrl f_ctrl = 2 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + uint32 f_cnt = 3; + repeated MACCommand f_opts = 4 [(gogoproto.nullable) = false]; +} + +message FCtrl { + bool adr = 1; + bool adr_ack_req = 2; + bool ack = 3; + bool f_pending = 4; +} + +message MACCommand { + uint32 cid = 1; + bytes payload = 2; +} + +message JoinRequestPayload { + bytes app_eui = 1 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + bytes dev_eui = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes dev_nonce = 3 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevNonce"]; +} + +message JoinAcceptPayload { + bytes encrypted = 1; + bytes app_nonce = 2 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppNonce"]; + bytes net_id = 3 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NetID"]; + bytes dev_addr = 4 [(gogoproto.nullable) = false, (gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + DLSettings dl_settings = 5 [(gogoproto.embed) = true, (gogoproto.nullable) = false]; + uint32 rx_delay = 6; + CFList cf_list = 7; +} + +message DLSettings { + uint32 rx1_dr_offset = 1; + uint32 rx2_dr = 2; +} + +message CFList { + repeated uint32 freq = 1; +} diff --git a/api/protocol/lorawan/message_conversion.go b/api/protocol/lorawan/message_conversion.go new file mode 100644 index 000000000..253609a7b --- /dev/null +++ b/api/protocol/lorawan/message_conversion.go @@ -0,0 +1,192 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package lorawan + +import ( + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/brocaar/lorawan" +) + +type payloader interface { + Payload() lorawan.Payload +} + +type macCommandPayload []byte + +func (m macCommandPayload) MarshalBinary() (data []byte, err error) { + return []byte(m), nil +} + +func (m *macCommandPayload) UnmarshalBinary(data []byte) error { + *m = data + return nil +} + +// MACCommand converts the MACCommand to a lorawan.MACCommand +func (m *MACCommand) MACCommand() (cmd lorawan.MACCommand) { + cmd.CID = lorawan.CID(m.Cid) + payload := macCommandPayload(m.Payload) + cmd.Payload = &payload + return +} + +// MACCommandFromMACCommand creates a new MACCommand from a lorawan.MACCommand +func MACCommandFromMACCommand(cmd lorawan.MACCommand) (m MACCommand) { + m.Cid = uint32(cmd.CID) + if cmd.Payload != nil { + m.Payload, _ = cmd.Payload.MarshalBinary() + } + return +} + +// Payload converts the MACPayload to a lorawan.Payload +func (msg *Message_MacPayload) Payload() lorawan.Payload { + m := *msg.MacPayload + var mac lorawan.MACPayload + mac.FHDR.DevAddr = lorawan.DevAddr(m.DevAddr) + mac.FHDR.FCtrl.ADR = m.Adr + mac.FHDR.FCtrl.ADRACKReq = m.AdrAckReq + mac.FHDR.FCtrl.ACK = m.Ack + mac.FHDR.FCtrl.FPending = m.FPending + mac.FHDR.FCnt = m.FCnt + for _, cmd := range m.FOpts { + mac.FHDR.FOpts = append(mac.FHDR.FOpts, cmd.MACCommand()) + } + if m.FPort >= 0 { + fPort := uint8(m.FPort) + mac.FPort = &fPort + } + mac.FRMPayload = []lorawan.Payload{ + &lorawan.DataPayload{Bytes: m.FrmPayload}, + } + return &mac +} + +// MACPayloadFromPayload creates a new MACPayload from a lorawan.Payload +func MACPayloadFromPayload(payload lorawan.Payload) (mac MACPayload) { + if payload, ok := payload.(*lorawan.MACPayload); ok { + mac.DevAddr = types.DevAddr(payload.FHDR.DevAddr) + mac.Adr = payload.FHDR.FCtrl.ADR + mac.AdrAckReq = payload.FHDR.FCtrl.ADRACKReq + mac.Ack = payload.FHDR.FCtrl.ACK + mac.FPending = payload.FHDR.FCtrl.FPending + mac.FCnt = payload.FHDR.FCnt + for _, cmd := range payload.FHDR.FOpts { + mac.FOpts = append(mac.FOpts, MACCommandFromMACCommand(cmd)) + } + if payload.FPort != nil { + mac.FPort = int32(*payload.FPort) + } + if len(payload.FRMPayload) == 1 { + if payload, ok := payload.FRMPayload[0].(*lorawan.DataPayload); ok { + mac.FrmPayload = payload.Bytes + } + } + } + return +} + +// Payload converts the JoinRequestPayload to a lorawan.Payload +func (msg *Message_JoinRequestPayload) Payload() lorawan.Payload { + m := *msg.JoinRequestPayload + var mac lorawan.JoinRequestPayload + mac.AppEUI = lorawan.EUI64(m.AppEui) + mac.DevEUI = lorawan.EUI64(m.DevEui) + mac.DevNonce = m.DevNonce + return &mac +} + +// JoinRequestPayloadFromPayload creates a new JoinRequestPayload from a lorawan.Payload +func JoinRequestPayloadFromPayload(payload lorawan.Payload) (request JoinRequestPayload) { + if payload, ok := payload.(*lorawan.JoinRequestPayload); ok { + request.AppEui = types.AppEUI(payload.AppEUI) + request.DevEui = types.DevEUI(payload.DevEUI) + request.DevNonce = types.DevNonce(payload.DevNonce) + } + return +} + +// Payload converts the JoinAcceptPayload to a lorawan.Payload +func (msg *Message_JoinAcceptPayload) Payload() lorawan.Payload { + m := *msg.JoinAcceptPayload + if len(m.Encrypted) != 0 { + return &lorawan.DataPayload{Bytes: m.Encrypted} + } + var mac lorawan.JoinAcceptPayload + mac.AppNonce = m.AppNonce + mac.NetID = m.NetId + mac.DevAddr = lorawan.DevAddr(m.DevAddr) + mac.DLSettings.RX1DROffset = uint8(m.Rx1DrOffset) + mac.DLSettings.RX2DataRate = uint8(m.Rx2Dr) + mac.RXDelay = uint8(m.RxDelay) + if m.CfList != nil && len(m.CfList.Freq) == 5 { + mac.CFList = &lorawan.CFList{ + m.CfList.Freq[0], + m.CfList.Freq[1], + m.CfList.Freq[2], + m.CfList.Freq[3], + m.CfList.Freq[4], + } + } + return &mac +} + +// JoinAcceptPayloadFromPayload creates a new JoinAcceptPayload from a lorawan.Payload +func JoinAcceptPayloadFromPayload(payload lorawan.Payload) (accept JoinAcceptPayload) { + if dataPayload, ok := payload.(*lorawan.DataPayload); ok { + joinAccept := &lorawan.JoinAcceptPayload{} + joinAccept.UnmarshalBinary(false, dataPayload.Bytes) + payload = joinAccept + } + + if payload, ok := payload.(*lorawan.JoinAcceptPayload); ok { + accept.AppNonce = types.AppNonce(payload.AppNonce) + accept.NetId = types.NetID(payload.NetID) + accept.DevAddr = types.DevAddr(payload.DevAddr) + accept.DLSettings.Rx1DrOffset = uint32(payload.DLSettings.RX1DROffset) + accept.DLSettings.Rx2Dr = uint32(payload.DLSettings.RX2DataRate) + accept.RxDelay = uint32(payload.RXDelay) + if payload.CFList != nil { + accept.CfList = &CFList{ + Freq: payload.CFList[:], + } + } + } + if encrypted, ok := payload.(*lorawan.DataPayload); ok { + accept.Encrypted = encrypted.Bytes + } + return +} + +// PHYPayload converts the Message to a lorawan.PHYPayload +func (m *Message) PHYPayload() (phy lorawan.PHYPayload) { + phy.MHDR.Major = lorawan.Major(m.Major) + phy.MHDR.MType = lorawan.MType(m.MType) + phy.MACPayload = m.Payload.(payloader).Payload() + copy(phy.MIC[:], m.Mic) + return +} + +// MessageFromPHYPayload converts a lorawan.PHYPayload to a Message +func MessageFromPHYPayload(phy lorawan.PHYPayload) Message { + var m Message + m.Major = Major(phy.MHDR.Major) + m.MType = MType(phy.MHDR.MType) + m.Mic = phy.MIC[:] + switch m.MType { + case MType_JOIN_REQUEST: + payload := JoinRequestPayloadFromPayload(phy.MACPayload) + m.Payload = &Message_JoinRequestPayload{JoinRequestPayload: &payload} + case MType_JOIN_ACCEPT: + payload := JoinAcceptPayloadFromPayload(phy.MACPayload) + m.Payload = &Message_JoinAcceptPayload{JoinAcceptPayload: &payload} + case MType_UNCONFIRMED_UP, + MType_UNCONFIRMED_DOWN, + MType_CONFIRMED_UP, + MType_CONFIRMED_DOWN: + payload := MACPayloadFromPayload(phy.MACPayload) + m.Payload = &Message_MacPayload{MacPayload: &payload} + } + return m +} diff --git a/api/protocol/lorawan/message_conversion_test.go b/api/protocol/lorawan/message_conversion_test.go new file mode 100644 index 000000000..bd79338e1 --- /dev/null +++ b/api/protocol/lorawan/message_conversion_test.go @@ -0,0 +1,58 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package lorawan + +import ( + "testing" + + "github.com/brocaar/lorawan" + . "github.com/smartystreets/assertions" +) + +func TestConvertPHYPayload(t *testing.T) { + a := New(t) + + { + m1 := Message{Mic: []byte{0, 0, 0, 0}} + m1.MType = MType_UNCONFIRMED_UP + macPayload := MACPayload{} + macPayload.FOpts = []MACCommand{ + MACCommand{Cid: 0x02}, + } + m1.Payload = &Message_MacPayload{MacPayload: &macPayload} + phy := m1.PHYPayload() + m2 := MessageFromPHYPayload(phy) + a.So(m2, ShouldResemble, m1) + } + + { + m1 := Message{Mic: []byte{0, 0, 0, 0}} + m1.MType = MType_JOIN_REQUEST + joinRequestPayload := JoinRequestPayload{} + m1.Payload = &Message_JoinRequestPayload{JoinRequestPayload: &joinRequestPayload} + phy := m1.PHYPayload() + m2 := MessageFromPHYPayload(phy) + a.So(m2, ShouldResemble, m1) + } + + { + m1 := Message{Mic: []byte{0, 0, 0, 0}} + m1.MType = MType_JOIN_ACCEPT + joinAcceptPayload := JoinAcceptPayload{} + joinAcceptPayload.CfList = &CFList{ + Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}, + } + m1.Payload = &Message_JoinAcceptPayload{JoinAcceptPayload: &joinAcceptPayload} + phy := m1.PHYPayload() + m2 := MessageFromPHYPayload(phy) + a.So(m2, ShouldResemble, m1) + + phy.MACPayload = &lorawan.DataPayload{Bytes: []byte{0x01, 0x02, 0x03, 0x04}} + + m3 := MessageFromPHYPayload(phy) + + phy = m3.PHYPayload() + } + +} diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 4f298ea07..f8ca971de 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -82,3 +82,40 @@ func (m *ActivationMetadata) Validate() bool { } return true } + +// Validate implements the api.Validator interface +func (m *Message) Validate() bool { + if m.Major != Major_LORAWAN_R1 { + return false + } + switch m.MType { + case MType_JOIN_REQUEST: + return m.GetJoinRequestPayload() != nil && m.GetJoinRequestPayload().Validate() + case MType_JOIN_ACCEPT: + return m.GetJoinAcceptPayload() != nil && m.GetJoinAcceptPayload().Validate() + case MType_UNCONFIRMED_UP, MType_UNCONFIRMED_DOWN, MType_CONFIRMED_UP, MType_CONFIRMED_DOWN: + return m.GetMacPayload() != nil && m.GetMacPayload().Validate() + } + return false +} + +// Validate implements the api.Validator interface +func (m *JoinRequestPayload) Validate() bool { + return len(m.AppEui) == 8 && len(m.DevEui) == 8 && len(m.DevNonce) == 2 +} + +// Validate implements the api.Validator interface +func (m *JoinAcceptPayload) Validate() bool { + if len(m.Encrypted) != 0 { + return true + } + if m.CfList != nil && len(m.CfList.Freq) != 5 { + return false + } + return len(m.DevAddr) == 4 && len(m.AppNonce) == 3 && len(m.NetId) == 3 +} + +// Validate implements the api.Validator interface +func (m *MACPayload) Validate() bool { + return len(m.DevAddr) == 4 +} diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 165fcdcd8..4e6ac8519 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -9,6 +9,7 @@ github.com/TheThingsNetwork/ttn/api/protocol/protocol.proto It has these top-level messages: + Message RxMetadata TxConfiguration ActivationMetadata @@ -33,6 +34,98 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package +type Message struct { + // Types that are valid to be assigned to Protocol: + // *Message_Lorawan + Protocol isMessage_Protocol `protobuf_oneof:"protocol"` +} + +func (m *Message) Reset() { *m = Message{} } +func (m *Message) String() string { return proto.CompactTextString(m) } +func (*Message) ProtoMessage() {} +func (*Message) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{0} } + +type isMessage_Protocol interface { + isMessage_Protocol() + MarshalTo([]byte) (int, error) + Size() int +} + +type Message_Lorawan struct { + Lorawan *lorawan.Message `protobuf:"bytes,1,opt,name=lorawan,oneof"` +} + +func (*Message_Lorawan) isMessage_Protocol() {} + +func (m *Message) GetProtocol() isMessage_Protocol { + if m != nil { + return m.Protocol + } + return nil +} + +func (m *Message) GetLorawan() *lorawan.Message { + if x, ok := m.GetProtocol().(*Message_Lorawan); ok { + return x.Lorawan + } + return nil +} + +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Message) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Message_OneofMarshaler, _Message_OneofUnmarshaler, _Message_OneofSizer, []interface{}{ + (*Message_Lorawan)(nil), + } +} + +func _Message_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Message) + // protocol + switch x := m.Protocol.(type) { + case *Message_Lorawan: + _ = b.EncodeVarint(1<<3 | proto.WireBytes) + if err := b.EncodeMessage(x.Lorawan); err != nil { + return err + } + case nil: + default: + return fmt.Errorf("Message.Protocol has unexpected type %T", x) + } + return nil +} + +func _Message_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Message) + switch tag { + case 1: // protocol.lorawan + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + msg := new(lorawan.Message) + err := b.DecodeMessage(msg) + m.Protocol = &Message_Lorawan{msg} + return true, err + default: + return false, nil + } +} + +func _Message_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Message) + // protocol + switch x := m.Protocol.(type) { + case *Message_Lorawan: + s := proto.Size(x.Lorawan) + n += proto.SizeVarint(1<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(s)) + n += s + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} + type RxMetadata struct { // Types that are valid to be assigned to Protocol: // *RxMetadata_Lorawan @@ -42,7 +135,7 @@ type RxMetadata struct { func (m *RxMetadata) Reset() { *m = RxMetadata{} } func (m *RxMetadata) String() string { return proto.CompactTextString(m) } func (*RxMetadata) ProtoMessage() {} -func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{0} } +func (*RxMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{1} } type isRxMetadata_Protocol interface { isRxMetadata_Protocol() @@ -134,7 +227,7 @@ type TxConfiguration struct { func (m *TxConfiguration) Reset() { *m = TxConfiguration{} } func (m *TxConfiguration) String() string { return proto.CompactTextString(m) } func (*TxConfiguration) ProtoMessage() {} -func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{1} } +func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{2} } type isTxConfiguration_Protocol interface { isTxConfiguration_Protocol() @@ -226,7 +319,7 @@ type ActivationMetadata struct { func (m *ActivationMetadata) Reset() { *m = ActivationMetadata{} } func (m *ActivationMetadata) String() string { return proto.CompactTextString(m) } func (*ActivationMetadata) ProtoMessage() {} -func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{2} } +func (*ActivationMetadata) Descriptor() ([]byte, []int) { return fileDescriptorProtocol, []int{3} } type isActivationMetadata_Protocol interface { isActivationMetadata_Protocol() @@ -310,11 +403,12 @@ func _ActivationMetadata_OneofSizer(msg proto.Message) (n int) { } func init() { + proto.RegisterType((*Message)(nil), "protocol.Message") proto.RegisterType((*RxMetadata)(nil), "protocol.RxMetadata") proto.RegisterType((*TxConfiguration)(nil), "protocol.TxConfiguration") proto.RegisterType((*ActivationMetadata)(nil), "protocol.ActivationMetadata") } -func (m *RxMetadata) Marshal() (data []byte, err error) { +func (m *Message) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -324,7 +418,7 @@ func (m *RxMetadata) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *RxMetadata) MarshalTo(data []byte) (int, error) { +func (m *Message) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -339,7 +433,7 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *RxMetadata_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *Message_Lorawan) MarshalTo(data []byte) (int, error) { i := 0 if m.Lorawan != nil { data[i] = 0xa @@ -353,7 +447,7 @@ func (m *RxMetadata_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *TxConfiguration) Marshal() (data []byte, err error) { +func (m *RxMetadata) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -363,7 +457,7 @@ func (m *TxConfiguration) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { +func (m *RxMetadata) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -378,7 +472,7 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *TxConfiguration_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *RxMetadata_Lorawan) MarshalTo(data []byte) (int, error) { i := 0 if m.Lorawan != nil { data[i] = 0xa @@ -392,7 +486,7 @@ func (m *TxConfiguration_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *ActivationMetadata) Marshal() (data []byte, err error) { +func (m *TxConfiguration) Marshal() (data []byte, err error) { size := m.Size() data = make([]byte, size) n, err := m.MarshalTo(data) @@ -402,7 +496,7 @@ func (m *ActivationMetadata) Marshal() (data []byte, err error) { return data[:n], nil } -func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { +func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { var i int _ = i var l int @@ -417,7 +511,7 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *TxConfiguration_Lorawan) MarshalTo(data []byte) (int, error) { i := 0 if m.Lorawan != nil { data[i] = 0xa @@ -431,6 +525,45 @@ func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } +func (m *ActivationMetadata) Marshal() (data []byte, err error) { + size := m.Size() + data = make([]byte, size) + n, err := m.MarshalTo(data) + if err != nil { + return nil, err + } + return data[:n], nil +} + +func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Protocol != nil { + nn7, err := m.Protocol.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += nn7 + } + return i, nil +} + +func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { + i := 0 + if m.Lorawan != nil { + data[i] = 0xa + i++ + i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) + n8, err := m.Lorawan.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n8 + } + return i, nil +} func encodeFixed64Protocol(data []byte, offset int, v uint64) int { data[offset] = uint8(v) data[offset+1] = uint8(v >> 8) @@ -458,6 +591,24 @@ func encodeVarintProtocol(data []byte, offset int, v uint64) int { data[offset] = uint8(v) return offset + 1 } +func (m *Message) Size() (n int) { + var l int + _ = l + if m.Protocol != nil { + n += m.Protocol.Size() + } + return n +} + +func (m *Message_Lorawan) Size() (n int) { + var l int + _ = l + if m.Lorawan != nil { + l = m.Lorawan.Size() + n += 1 + l + sovProtocol(uint64(l)) + } + return n +} func (m *RxMetadata) Size() (n int) { var l int _ = l @@ -526,6 +677,88 @@ func sovProtocol(x uint64) (n int) { func sozProtocol(x uint64) (n int) { return sovProtocol(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (m *Message) Unmarshal(data []byte) error { + l := len(data) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: Message: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Message: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Lorawan", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowProtocol + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthProtocol + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &lorawan.Message{} + if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + m.Protocol = &Message_Lorawan{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipProtocol(data[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthProtocol + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *RxMetadata) Unmarshal(data []byte) error { l := len(data) iNdEx := 0 @@ -882,19 +1115,20 @@ func init() { } var fileDescriptorProtocol = []byte{ - // 219 bytes of a gzipped FileDescriptorProto + // 240 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x0f, 0xc9, 0x48, 0x0d, 0xc9, 0xc8, 0xcc, 0x4b, 0x2f, 0xf6, 0x4b, 0x2d, 0x29, 0xcf, 0x2f, 0xca, 0xd6, 0x2f, 0x29, 0xc9, 0xd3, 0x4f, 0x2c, 0xc8, 0xd4, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0x81, 0x33, 0xf4, 0xc0, 0x0c, 0x21, 0x0e, 0x18, 0x5f, 0x4a, 0x0d, 0x43, 0x69, 0x4e, 0x7e, 0x51, 0x62, 0x79, 0x62, 0x1e, 0x8c, 0x86, 0xe8, 0x50, - 0x72, 0xe7, 0xe2, 0x0a, 0xaa, 0xf0, 0x4d, 0x2d, 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x14, 0xd2, 0xe5, - 0x62, 0x87, 0x4a, 0x4b, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0xea, 0xc1, 0x94, 0xc3, 0xd4, - 0x78, 0x30, 0x04, 0xc1, 0xd4, 0x38, 0x71, 0x71, 0xc1, 0x2d, 0x54, 0x0a, 0xe6, 0xe2, 0x0f, 0xa9, - 0x70, 0xce, 0xcf, 0x4b, 0xcb, 0x4c, 0x2f, 0x2d, 0x4a, 0x2c, 0xc9, 0xcc, 0xcf, 0x13, 0x32, 0x41, - 0x37, 0x4d, 0x02, 0x6e, 0x1a, 0x9a, 0x52, 0x5c, 0x86, 0x46, 0x72, 0x09, 0x39, 0x26, 0x97, 0x64, - 0x96, 0x81, 0x15, 0xc1, 0x5d, 0x69, 0x8e, 0x6e, 0xae, 0x34, 0xdc, 0x5c, 0x4c, 0xd5, 0x38, 0x8c, - 0x76, 0xb2, 0x3b, 0xf1, 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x67, - 0x3c, 0x96, 0x63, 0x88, 0xd2, 0x21, 0x25, 0xe4, 0x93, 0xd8, 0xc0, 0x2c, 0x63, 0x40, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x24, 0x8c, 0x89, 0xc2, 0xb0, 0x01, 0x00, 0x00, + 0x72, 0xe6, 0x62, 0xf7, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x15, 0xd2, 0xe1, 0x62, 0x87, 0xca, + 0x49, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x1b, 0x09, 0xe8, 0xc1, 0xd4, 0x42, 0x95, 0x78, 0x30, 0x04, + 0xc1, 0x94, 0x38, 0x71, 0x71, 0xc1, 0x2d, 0x53, 0x72, 0xe7, 0xe2, 0x0a, 0xaa, 0xf0, 0x4d, 0x2d, + 0x49, 0x4c, 0x49, 0x2c, 0x49, 0x14, 0xd2, 0x45, 0x37, 0x47, 0x10, 0xc9, 0x1c, 0x88, 0x1a, 0x5c, + 0x06, 0x05, 0x73, 0xf1, 0x87, 0x54, 0x38, 0xe7, 0xe7, 0xa5, 0x65, 0xa6, 0x97, 0x16, 0x25, 0x96, + 0x64, 0xe6, 0xe7, 0x09, 0x99, 0xa0, 0x9b, 0x26, 0x01, 0x37, 0x0d, 0x4d, 0x29, 0x2e, 0x43, 0x23, + 0xb9, 0x84, 0x1c, 0x93, 0x4b, 0x32, 0xcb, 0xc0, 0x8a, 0xe0, 0xae, 0x34, 0x47, 0x37, 0x57, 0x1a, + 0x6e, 0x2e, 0xa6, 0x6a, 0x1c, 0x46, 0x3b, 0xd9, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, + 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x33, 0x1e, 0xcb, 0x31, 0x44, 0xe9, 0x90, 0x12, 0x7d, 0x49, 0x6c, + 0x60, 0x96, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff, 0x48, 0xdc, 0x20, 0x24, 0xf5, 0x01, 0x00, 0x00, } diff --git a/api/protocol/protocol.proto b/api/protocol/protocol.proto index e8803ad8a..9a01f2121 100644 --- a/api/protocol/protocol.proto +++ b/api/protocol/protocol.proto @@ -9,6 +9,12 @@ option go_package = "github.com/TheThingsNetwork/ttn/api/protocol"; package protocol; +message Message { + oneof protocol { + lorawan.Message lorawan = 1; + } +} + message RxMetadata { oneof protocol { lorawan.Metadata lorawan = 1; diff --git a/api/protocol/validation.go b/api/protocol/validation.go index 4455a9cc2..c3a8d1f86 100644 --- a/api/protocol/validation.go +++ b/api/protocol/validation.go @@ -10,6 +10,11 @@ func (m *RxMetadata) Validate() bool { return true } +// Validate implements the api.Validator interface +func (m *RxMetadata_Lorawan) Validate() bool { + return m.Lorawan.Validate() +} + // Validate implements the api.Validator interface func (m *TxConfiguration) Validate() bool { if m.Protocol == nil || !api.Validate(m.Protocol) { @@ -18,6 +23,11 @@ func (m *TxConfiguration) Validate() bool { return true } +// Validate implements the api.Validator interface +func (m *TxConfiguration_Lorawan) Validate() bool { + return m.Lorawan.Validate() +} + // Validate implements the api.Validator interface func (m *ActivationMetadata) Validate() bool { if m.Protocol == nil || !api.Validate(m.Protocol) { @@ -25,3 +35,21 @@ func (m *ActivationMetadata) Validate() bool { } return true } + +// Validate implements the api.Validator interface +func (m *ActivationMetadata_Lorawan) Validate() bool { + return m.Lorawan.Validate() +} + +// Validate implements the api.Validator interface +func (m *Message) Validate() bool { + if m.Protocol == nil || !api.Validate(m.Protocol) { + return false + } + return true +} + +// Validate implements the api.Validator interface +func (m *Message_Lorawan) Validate() bool { + return m.Lorawan.Validate() +} diff --git a/api/router/router.pb.go b/api/router/router.pb.go index 48049c896..b462433fc 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -60,6 +60,7 @@ func (*SubscribeRequest) Descriptor() ([]byte, []int) { return fileDescriptorRou type UplinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,11,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,12,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` } @@ -69,6 +70,13 @@ func (m *UplinkMessage) String() string { return proto.CompactTextStr func (*UplinkMessage) ProtoMessage() {} func (*UplinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{1} } +func (m *UplinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *UplinkMessage) GetProtocolMetadata() *protocol.RxMetadata { if m != nil { return m.ProtocolMetadata @@ -85,6 +93,7 @@ func (m *UplinkMessage) GetGatewayMetadata() *gateway.RxMetadata { type DownlinkMessage struct { Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` ProtocolConfiguration *protocol.TxConfiguration `protobuf:"bytes,11,opt,name=protocol_configuration,json=protocolConfiguration" json:"protocol_configuration,omitempty"` GatewayConfiguration *gateway.TxConfiguration `protobuf:"bytes,12,opt,name=gateway_configuration,json=gatewayConfiguration" json:"gateway_configuration,omitempty"` } @@ -94,6 +103,13 @@ func (m *DownlinkMessage) String() string { return proto.CompactTextS func (*DownlinkMessage) ProtoMessage() {} func (*DownlinkMessage) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{2} } +func (m *DownlinkMessage) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DownlinkMessage) GetProtocolConfiguration() *protocol.TxConfiguration { if m != nil { return m.ProtocolConfiguration @@ -109,11 +125,13 @@ func (m *DownlinkMessage) GetGatewayConfiguration() *gateway.TxConfiguration { } type DeviceActivationRequest struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` - GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + Message *protocol.Message `protobuf:"bytes,2,opt,name=message" json:"message,omitempty"` + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,11,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,12,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + ProtocolMetadata *protocol.RxMetadata `protobuf:"bytes,21,opt,name=protocol_metadata,json=protocolMetadata" json:"protocol_metadata,omitempty"` + GatewayMetadata *gateway.RxMetadata `protobuf:"bytes,22,opt,name=gateway_metadata,json=gatewayMetadata" json:"gateway_metadata,omitempty"` + ActivationMetadata *protocol.ActivationMetadata `protobuf:"bytes,23,opt,name=activation_metadata,json=activationMetadata" json:"activation_metadata,omitempty"` } func (m *DeviceActivationRequest) Reset() { *m = DeviceActivationRequest{} } @@ -121,6 +139,13 @@ func (m *DeviceActivationRequest) String() string { return proto.Comp func (*DeviceActivationRequest) ProtoMessage() {} func (*DeviceActivationRequest) Descriptor() ([]byte, []int) { return fileDescriptorRouter, []int{3} } +func (m *DeviceActivationRequest) GetMessage() *protocol.Message { + if m != nil { + return m.Message + } + return nil +} + func (m *DeviceActivationRequest) GetProtocolMetadata() *protocol.RxMetadata { if m != nil { return m.ProtocolMetadata @@ -135,6 +160,13 @@ func (m *DeviceActivationRequest) GetGatewayMetadata() *gateway.RxMetadata { return nil } +func (m *DeviceActivationRequest) GetActivationMetadata() *protocol.ActivationMetadata { + if m != nil { + return m.ActivationMetadata + } + return nil +} + type DeviceActivationResponse struct { } @@ -667,25 +699,35 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintRouter(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Message.Size())) + n1, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n1 + } if m.ProtocolMetadata != nil { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) - n1, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n2, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n1 + i += n2 } if m.GatewayMetadata != nil { data[i] = 0x62 i++ i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) - n2, err := m.GatewayMetadata.MarshalTo(data[i:]) + n3, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n2 + i += n3 } return i, nil } @@ -711,25 +753,35 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { i = encodeVarintRouter(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Message.Size())) + n4, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n4 + } if m.ProtocolConfiguration != nil { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.ProtocolConfiguration.Size())) - n3, err := m.ProtocolConfiguration.MarshalTo(data[i:]) + n5, err := m.ProtocolConfiguration.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n3 + i += n5 } if m.GatewayConfiguration != nil { data[i] = 0x62 i++ i = encodeVarintRouter(data, i, uint64(m.GatewayConfiguration.Size())) - n4, err := m.GatewayConfiguration.MarshalTo(data[i:]) + n6, err := m.GatewayConfiguration.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n4 + i += n6 } return i, nil } @@ -755,25 +807,35 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { i = encodeVarintRouter(data, i, uint64(len(m.Payload))) i += copy(data[i:], m.Payload) } + if m.Message != nil { + data[i] = 0x12 + i++ + i = encodeVarintRouter(data, i, uint64(m.Message.Size())) + n7, err := m.Message.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n7 + } if m.DevEui != nil { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.DevEui.Size())) - n5, err := m.DevEui.MarshalTo(data[i:]) + n8, err := m.DevEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n5 + i += n8 } if m.AppEui != nil { data[i] = 0x62 i++ i = encodeVarintRouter(data, i, uint64(m.AppEui.Size())) - n6, err := m.AppEui.MarshalTo(data[i:]) + n9, err := m.AppEui.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n6 + i += n9 } if m.ProtocolMetadata != nil { data[i] = 0xaa @@ -781,11 +843,11 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) - n7, err := m.ProtocolMetadata.MarshalTo(data[i:]) + n10, err := m.ProtocolMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n7 + i += n10 } if m.GatewayMetadata != nil { data[i] = 0xb2 @@ -793,11 +855,23 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { data[i] = 0x1 i++ i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) - n8, err := m.GatewayMetadata.MarshalTo(data[i:]) + n11, err := m.GatewayMetadata.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n8 + i += n11 + } + if m.ActivationMetadata != nil { + data[i] = 0xba + i++ + data[i] = 0x1 + i++ + i = encodeVarintRouter(data, i, uint64(m.ActivationMetadata.Size())) + n12, err := m.ActivationMetadata.MarshalTo(data[i:]) + if err != nil { + return 0, err + } + i += n12 } return i, nil } @@ -868,11 +942,11 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { data[i] = 0x12 i++ i = encodeVarintRouter(data, i, uint64(m.Status.Size())) - n9, err := m.Status.MarshalTo(data[i:]) + n13, err := m.Status.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n9 + i += n13 } return i, nil } @@ -914,61 +988,61 @@ func (m *Status) MarshalTo(data []byte) (int, error) { data[i] = 0xa i++ i = encodeVarintRouter(data, i, uint64(m.System.Size())) - n10, err := m.System.MarshalTo(data[i:]) + n14, err := m.System.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n10 + i += n14 } if m.Component != nil { data[i] = 0x12 i++ i = encodeVarintRouter(data, i, uint64(m.Component.Size())) - n11, err := m.Component.MarshalTo(data[i:]) + n15, err := m.Component.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n11 + i += n15 } if m.GatewayStatus != nil { data[i] = 0x5a i++ i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n12, err := m.GatewayStatus.MarshalTo(data[i:]) + n16, err := m.GatewayStatus.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n12 + i += n16 } if m.Uplink != nil { data[i] = 0x62 i++ i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n13, err := m.Uplink.MarshalTo(data[i:]) + n17, err := m.Uplink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n13 + i += n17 } if m.Downlink != nil { data[i] = 0x6a i++ i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n14, err := m.Downlink.MarshalTo(data[i:]) + n18, err := m.Downlink.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n14 + i += n18 } if m.Activations != nil { data[i] = 0x72 i++ i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n15, err := m.Activations.MarshalTo(data[i:]) + n19, err := m.Activations.MarshalTo(data[i:]) if err != nil { return 0, err } - i += n15 + i += n19 } if m.ConnectedGateways != 0 { data[i] = 0xa8 @@ -1027,6 +1101,10 @@ func (m *UplinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovRouter(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovRouter(uint64(l)) + } if m.ProtocolMetadata != nil { l = m.ProtocolMetadata.Size() n += 1 + l + sovRouter(uint64(l)) @@ -1045,6 +1123,10 @@ func (m *DownlinkMessage) Size() (n int) { if l > 0 { n += 1 + l + sovRouter(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovRouter(uint64(l)) + } if m.ProtocolConfiguration != nil { l = m.ProtocolConfiguration.Size() n += 1 + l + sovRouter(uint64(l)) @@ -1063,6 +1145,10 @@ func (m *DeviceActivationRequest) Size() (n int) { if l > 0 { n += 1 + l + sovRouter(uint64(l)) } + if m.Message != nil { + l = m.Message.Size() + n += 1 + l + sovRouter(uint64(l)) + } if m.DevEui != nil { l = m.DevEui.Size() n += 1 + l + sovRouter(uint64(l)) @@ -1079,6 +1165,10 @@ func (m *DeviceActivationRequest) Size() (n int) { l = m.GatewayMetadata.Size() n += 2 + l + sovRouter(uint64(l)) } + if m.ActivationMetadata != nil { + l = m.ActivationMetadata.Size() + n += 2 + l + sovRouter(uint64(l)) + } return n } @@ -1276,6 +1366,39 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolMetadata", wireType) @@ -1423,6 +1546,39 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field ProtocolConfiguration", wireType) @@ -1570,6 +1726,39 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { m.Payload = []byte{} } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Message == nil { + m.Message = &protocol.Message{} + } + if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex case 11: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field DevEui", wireType) @@ -1700,6 +1889,39 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { return err } iNdEx = postIndex + case 23: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActivationMetadata", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRouter + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRouter + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.ActivationMetadata == nil { + m.ActivationMetadata = &protocol.ActivationMetadata{} + } + if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRouter(data[iNdEx:]) @@ -2398,57 +2620,59 @@ func init() { } var fileDescriptorRouter = []byte{ - // 818 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x55, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0xc7, 0x45, 0xf2, 0x36, 0xaf, 0x71, 0x93, 0xce, 0x36, 0x5d, 0x93, 0xa5, 0x69, 0xe5, 0x03, - 0x44, 0xc0, 0x3a, 0x34, 0x68, 0x85, 0x40, 0x08, 0xd1, 0x6e, 0xab, 0xd5, 0x4a, 0x64, 0x85, 0x9c, - 0xee, 0x05, 0x09, 0x45, 0x13, 0xe7, 0xad, 0x6b, 0x35, 0xf1, 0x18, 0xcf, 0x38, 0xdd, 0x7c, 0x0b, - 0xb8, 0xf1, 0x21, 0xf8, 0x20, 0x1c, 0x38, 0x70, 0xe2, 0xc0, 0x01, 0xa1, 0x72, 0xe7, 0x33, 0x20, - 0xcf, 0x1f, 0x27, 0x4e, 0xbb, 0xcb, 0x02, 0xa7, 0x64, 0x7e, 0xef, 0x37, 0xbf, 0xf9, 0xbd, 0x37, - 0x6f, 0x9e, 0xe1, 0xe3, 0x28, 0x16, 0x17, 0xf9, 0xd8, 0x0f, 0xd9, 0xac, 0x77, 0x7e, 0x81, 0xe7, - 0x17, 0x71, 0x12, 0xf1, 0xa7, 0x28, 0xae, 0x58, 0x76, 0xd9, 0x13, 0x22, 0xe9, 0xd1, 0x34, 0xee, - 0x65, 0x2c, 0x17, 0x98, 0xe9, 0x1f, 0x3f, 0xcd, 0x98, 0x60, 0xc4, 0x56, 0xab, 0xf6, 0xfd, 0x88, - 0xb1, 0x68, 0x8a, 0x3d, 0x89, 0x8e, 0xf3, 0xe7, 0x3d, 0x9c, 0xa5, 0x62, 0xa1, 0x48, 0xed, 0x07, - 0x2b, 0xea, 0x11, 0x8b, 0xd8, 0x92, 0x55, 0xac, 0xe4, 0x42, 0xfe, 0xd3, 0xf4, 0x1d, 0x73, 0x20, - 0x4d, 0x63, 0x0d, 0x1d, 0x18, 0x48, 0x2e, 0x43, 0x36, 0x2d, 0xff, 0x68, 0xc2, 0xbe, 0x21, 0x44, - 0x54, 0xe0, 0x15, 0x5d, 0x98, 0x5f, 0x15, 0xf6, 0x08, 0x34, 0x87, 0xf9, 0x98, 0x87, 0x59, 0x3c, - 0xc6, 0x00, 0xbf, 0xcd, 0x91, 0x0b, 0xef, 0x47, 0x0b, 0x9c, 0x67, 0xe9, 0x34, 0x4e, 0x2e, 0x07, - 0xc8, 0x39, 0x8d, 0x90, 0xb8, 0x70, 0x27, 0xa5, 0x8b, 0x29, 0xa3, 0x13, 0xd7, 0x3a, 0xb4, 0xba, - 0xf5, 0xc0, 0x2c, 0xc9, 0x31, 0xec, 0x98, 0x03, 0x47, 0x33, 0x14, 0x74, 0x42, 0x05, 0x75, 0xb7, - 0x0e, 0xad, 0xee, 0x56, 0x7f, 0xd7, 0x2f, 0xad, 0x04, 0x2f, 0x06, 0x3a, 0x16, 0x34, 0x0d, 0x68, - 0x10, 0xf2, 0x39, 0x34, 0xb5, 0xa7, 0xa5, 0x42, 0x5d, 0x2a, 0xdc, 0xf5, 0x8d, 0xd9, 0x15, 0x81, - 0x86, 0xc6, 0x0c, 0xe0, 0xfd, 0x6c, 0x41, 0xe3, 0x94, 0x5d, 0x25, 0xaf, 0x67, 0xf8, 0x2b, 0xd8, - 0x2b, 0x0d, 0x87, 0x2c, 0x79, 0x1e, 0x47, 0x79, 0x46, 0x45, 0xcc, 0x12, 0xed, 0xfa, 0xad, 0xa5, - 0xeb, 0xf3, 0x17, 0x8f, 0x56, 0x09, 0x41, 0xcb, 0x44, 0x2a, 0x30, 0x19, 0x40, 0xcb, 0xf8, 0xaf, - 0x0a, 0xaa, 0x24, 0xdc, 0x32, 0x89, 0x75, 0xbd, 0x5d, 0x1d, 0xa8, 0xa0, 0xde, 0xaf, 0x1b, 0x70, - 0xef, 0x14, 0xe7, 0x71, 0x88, 0xc7, 0xa1, 0x88, 0xe7, 0x8a, 0xaa, 0x6e, 0xe6, 0x15, 0x69, 0x3d, - 0x85, 0x3b, 0x13, 0x9c, 0x8f, 0x30, 0x8f, 0x65, 0x1e, 0xf5, 0x93, 0x87, 0xbf, 0xfd, 0x7e, 0x70, - 0xf4, 0x4f, 0xcd, 0x1b, 0xb2, 0x0c, 0x7b, 0x62, 0x91, 0x22, 0xf7, 0x4f, 0x71, 0x7e, 0xf6, 0xec, - 0x49, 0x60, 0x4f, 0x70, 0x7e, 0x96, 0xc7, 0x85, 0x1e, 0x4d, 0x53, 0xa9, 0x57, 0xff, 0x4f, 0x7a, - 0xc7, 0x69, 0x2a, 0xf5, 0x68, 0x9a, 0x16, 0x7a, 0xb7, 0xf6, 0x49, 0xeb, 0x7f, 0xf7, 0xc9, 0xde, - 0xbf, 0xe8, 0x93, 0x36, 0xb8, 0x37, 0xeb, 0xca, 0x53, 0x96, 0x70, 0xf4, 0x1e, 0xc2, 0xee, 0x63, - 0x45, 0x1f, 0x0a, 0x2a, 0x72, 0x6e, 0x0a, 0xbe, 0x0f, 0x60, 0xce, 0x8c, 0x55, 0xcd, 0x6b, 0x41, - 0x4d, 0x23, 0x4f, 0x26, 0xde, 0x37, 0xd0, 0x5a, 0xdb, 0xa6, 0xf4, 0xc8, 0x7d, 0xa8, 0x4d, 0x29, - 0x17, 0x23, 0x8e, 0x98, 0xc8, 0x6d, 0x6f, 0x06, 0x9b, 0x05, 0x30, 0x44, 0x4c, 0xc8, 0xbb, 0x60, - 0x73, 0x49, 0x77, 0x37, 0xa4, 0xfd, 0x46, 0x69, 0x5f, 0xab, 0xe8, 0xb0, 0xd7, 0x00, 0xa7, 0x62, - 0xc7, 0xfb, 0x6b, 0x03, 0x6c, 0x85, 0x90, 0x2e, 0xd8, 0x7c, 0xc1, 0x05, 0xce, 0xa4, 0xfc, 0x56, - 0xbf, 0xe9, 0x17, 0x43, 0x61, 0x28, 0xa1, 0x82, 0x52, 0xa8, 0xc8, 0x05, 0x39, 0x82, 0x5a, 0xc8, - 0x66, 0x29, 0x4b, 0x30, 0x11, 0xfa, 0xc4, 0xbb, 0x92, 0xfc, 0xc8, 0xa0, 0x8a, 0xbf, 0x64, 0x91, - 0x23, 0xd8, 0x36, 0x69, 0x6b, 0xa7, 0xea, 0x71, 0x80, 0xdc, 0x17, 0x50, 0x81, 0x3c, 0x70, 0xa2, - 0xd5, 0xcc, 0x89, 0x07, 0x76, 0x2e, 0x67, 0x86, 0x6e, 0xfb, 0x55, 0xaa, 0x8e, 0x90, 0x77, 0x60, - 0x73, 0xa2, 0x1f, 0xaa, 0xeb, 0xdc, 0x60, 0x95, 0x31, 0xf2, 0x01, 0x6c, 0xd1, 0xf2, 0x8e, 0xb8, - 0xbb, 0x7d, 0x83, 0xba, 0x1a, 0x26, 0x0f, 0x80, 0x84, 0x2c, 0x49, 0x30, 0x14, 0x38, 0x19, 0x69, - 0x53, 0x5c, 0xf6, 0x96, 0x13, 0xec, 0x94, 0x11, 0x7d, 0x4f, 0x9c, 0xbc, 0x0f, 0x4b, 0x70, 0x34, - 0xce, 0xd8, 0x25, 0x66, 0x5c, 0xf6, 0x91, 0x13, 0x34, 0xcb, 0xc0, 0x89, 0xc2, 0xfb, 0xdf, 0x6d, - 0x80, 0x1d, 0xc8, 0x41, 0x4e, 0x3e, 0x05, 0xa7, 0x72, 0xd7, 0x64, 0xfd, 0xda, 0xda, 0x7b, 0xbe, - 0x9a, 0xf5, 0xbe, 0x99, 0xe2, 0xfe, 0x59, 0x31, 0xeb, 0xbb, 0x16, 0xf9, 0x04, 0x6c, 0x35, 0x50, - 0x49, 0xcb, 0xd7, 0x5f, 0x89, 0xca, 0x80, 0x7d, 0xc5, 0xd6, 0x2f, 0xa0, 0x56, 0x0e, 0x68, 0xe2, - 0x9a, 0xdd, 0xeb, 0x33, 0xbb, 0x7d, 0xcf, 0x44, 0xd6, 0x26, 0xe1, 0x87, 0x16, 0x19, 0xc0, 0xa6, - 0xee, 0x78, 0x24, 0x07, 0x25, 0xed, 0xf6, 0x09, 0xd3, 0x3e, 0x7c, 0x39, 0x41, 0xb5, 0x76, 0xff, - 0x7b, 0x0b, 0x1c, 0x55, 0x92, 0x01, 0x4d, 0x68, 0x84, 0x19, 0xf9, 0x72, 0xbd, 0x32, 0x6f, 0x1b, - 0x91, 0xdb, 0xde, 0x54, 0x7b, 0xff, 0x25, 0x51, 0xfd, 0x74, 0xfa, 0x50, 0x7b, 0x8c, 0x42, 0x2b, - 0x95, 0xe5, 0xaa, 0x4a, 0x6c, 0x57, 0xe1, 0x93, 0xcf, 0x7e, 0xba, 0xee, 0x58, 0xbf, 0x5c, 0x77, - 0xac, 0x3f, 0xae, 0x3b, 0xd6, 0x0f, 0x7f, 0x76, 0xde, 0xf8, 0xfa, 0xbd, 0xd7, 0xff, 0x6e, 0x8f, - 0x6d, 0x59, 0xf4, 0x8f, 0xfe, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x7d, 0xce, 0x7e, 0x6e, 0xec, 0x07, - 0x00, 0x00, + // 853 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x56, 0xcd, 0x6f, 0xe3, 0x44, + 0x14, 0xc7, 0x5d, 0xc9, 0x6d, 0x5e, 0xeb, 0x36, 0x9d, 0x36, 0xad, 0xc9, 0x6e, 0x3f, 0xe4, 0x03, + 0x54, 0x2c, 0xeb, 0xd0, 0xa0, 0x15, 0x02, 0x21, 0x44, 0xbb, 0xad, 0x56, 0x2b, 0x91, 0x15, 0x72, + 0xbb, 0x17, 0x24, 0x14, 0x4d, 0x9c, 0xb7, 0xae, 0xd5, 0xc4, 0x63, 0x3c, 0xe3, 0x74, 0xf3, 0x5f, + 0xc0, 0x8d, 0x3f, 0x89, 0x23, 0xe2, 0x06, 0x07, 0x84, 0xca, 0x9d, 0x3b, 0x37, 0xe4, 0xf9, 0xb0, + 0xe3, 0xb4, 0x0b, 0x15, 0xb0, 0xa7, 0x78, 0x7e, 0xef, 0xf7, 0x7e, 0x33, 0xef, 0x63, 0xe6, 0x05, + 0x3e, 0x8a, 0x62, 0x71, 0x91, 0x0f, 0xfc, 0x90, 0x8d, 0x3b, 0xe7, 0x17, 0x78, 0x7e, 0x11, 0x27, + 0x11, 0x7f, 0x8e, 0xe2, 0x8a, 0x65, 0x97, 0x1d, 0x21, 0x92, 0x0e, 0x4d, 0xe3, 0x4e, 0xc6, 0x72, + 0x81, 0x99, 0xfe, 0xf1, 0xd3, 0x8c, 0x09, 0x46, 0x6c, 0xb5, 0x6a, 0xdf, 0x8f, 0x18, 0x8b, 0x46, + 0xd8, 0x91, 0xe8, 0x20, 0x7f, 0xd9, 0xc1, 0x71, 0x2a, 0xa6, 0x8a, 0xd4, 0x7e, 0x34, 0xa3, 0x1e, + 0xb1, 0x88, 0x55, 0xac, 0x62, 0x25, 0x17, 0xf2, 0x4b, 0xd3, 0xd7, 0xcd, 0x86, 0x34, 0x8d, 0x35, + 0xb4, 0x67, 0x20, 0xb9, 0x0c, 0xd9, 0xa8, 0xfc, 0xd0, 0x84, 0x1d, 0x43, 0x88, 0xa8, 0xc0, 0x2b, + 0x3a, 0x35, 0xbf, 0xca, 0xec, 0x11, 0x68, 0x9e, 0xe5, 0x03, 0x1e, 0x66, 0xf1, 0x00, 0x03, 0xfc, + 0x26, 0x47, 0x2e, 0xbc, 0x9f, 0x2d, 0x70, 0x5e, 0xa4, 0xa3, 0x38, 0xb9, 0xec, 0x21, 0xe7, 0x34, + 0x42, 0xe2, 0xc2, 0x62, 0x4a, 0xa7, 0x23, 0x46, 0x87, 0xae, 0xb5, 0x6f, 0x1d, 0xac, 0x04, 0x66, + 0x49, 0x1e, 0xc2, 0xe2, 0x58, 0x91, 0xdc, 0x85, 0x7d, 0xeb, 0x60, 0xb9, 0xbb, 0xee, 0x97, 0x07, + 0xd0, 0xde, 0x81, 0x61, 0x90, 0x23, 0x58, 0x37, 0xc6, 0xfe, 0x18, 0x05, 0x1d, 0x52, 0x41, 0xdd, + 0x65, 0xe9, 0xb6, 0x59, 0xb9, 0x05, 0xaf, 0x7a, 0xda, 0x16, 0x34, 0x0d, 0x68, 0x10, 0xf2, 0x19, + 0x34, 0x75, 0x00, 0x95, 0xc2, 0x8a, 0x54, 0xd8, 0xf0, 0x4d, 0x64, 0x33, 0x02, 0x6b, 0x1a, 0x33, + 0x80, 0xf7, 0xa7, 0x05, 0x6b, 0x27, 0xec, 0x2a, 0x79, 0x03, 0xd1, 0x7d, 0x09, 0x5b, 0x65, 0x74, + 0x21, 0x4b, 0x5e, 0xc6, 0x51, 0x9e, 0x51, 0x11, 0xb3, 0x44, 0x87, 0xf8, 0x76, 0xe5, 0x7b, 0xfe, + 0xea, 0xc9, 0x2c, 0x21, 0x68, 0x19, 0x4b, 0x0d, 0x26, 0x3d, 0x68, 0x99, 0x60, 0xeb, 0x82, 0x2a, + 0x62, 0xb7, 0x8c, 0x78, 0x5e, 0x6f, 0x53, 0x1b, 0x6a, 0xa8, 0xf7, 0xd3, 0x3d, 0xd8, 0x3e, 0xc1, + 0x49, 0x1c, 0xe2, 0x51, 0x28, 0xe2, 0x89, 0xa2, 0xaa, 0x9a, 0xff, 0x5f, 0x39, 0x78, 0x0e, 0x8b, + 0x43, 0x9c, 0xf4, 0x31, 0x8f, 0x65, 0xd0, 0x2b, 0xc7, 0x8f, 0x7f, 0xf9, 0x75, 0xef, 0xf0, 0x9f, + 0xee, 0x50, 0xc8, 0x32, 0xec, 0x88, 0x69, 0x8a, 0xdc, 0x3f, 0xc1, 0xc9, 0xe9, 0x8b, 0x67, 0x81, + 0x3d, 0xc4, 0xc9, 0x69, 0x1e, 0x17, 0x7a, 0x34, 0x4d, 0xa5, 0xde, 0xca, 0xbf, 0xd2, 0x3b, 0x4a, + 0x53, 0xa9, 0x47, 0xd3, 0xb4, 0xd0, 0xbb, 0xb5, 0x03, 0x5b, 0xff, 0xb9, 0x03, 0xb7, 0xee, 0xde, + 0x81, 0xa4, 0x07, 0x1b, 0xb4, 0x4c, 0x7f, 0x25, 0xb1, 0x2d, 0x25, 0x1e, 0x54, 0x87, 0xa8, 0x6a, + 0x54, 0x6a, 0x11, 0x7a, 0x03, 0xf3, 0xda, 0xe0, 0xde, 0xac, 0x29, 0x4f, 0x59, 0xc2, 0xd1, 0x7b, + 0x0c, 0x9b, 0x4f, 0xd5, 0xee, 0x67, 0x82, 0x8a, 0x9c, 0x9b, 0x62, 0xef, 0x00, 0x98, 0x10, 0x62, + 0x55, 0xef, 0x46, 0xd0, 0xd0, 0xc8, 0xb3, 0xa1, 0xf7, 0x35, 0xb4, 0xe6, 0xdc, 0x94, 0x1e, 0xb9, + 0x0f, 0x8d, 0x11, 0xe5, 0xa2, 0xcf, 0x11, 0x13, 0xe9, 0x76, 0x2f, 0x58, 0x2a, 0x80, 0x33, 0xc4, + 0x84, 0xbc, 0x0b, 0x36, 0x97, 0x74, 0xdd, 0x26, 0x6b, 0x65, 0x36, 0xb4, 0x8a, 0x36, 0x7b, 0x6b, + 0xe0, 0xd4, 0x8e, 0xe3, 0xfd, 0xb1, 0x00, 0xb6, 0x42, 0xc8, 0x01, 0xd8, 0x7c, 0xca, 0x05, 0x8e, + 0xa5, 0xfc, 0x72, 0xb7, 0xe9, 0x17, 0x4f, 0xdd, 0x99, 0x84, 0x0a, 0x4a, 0xa1, 0x22, 0x17, 0xe4, + 0x10, 0x1a, 0x21, 0x1b, 0xa7, 0x2c, 0xc1, 0x44, 0xe8, 0x1d, 0x37, 0x24, 0xf9, 0x89, 0x41, 0x15, + 0xbf, 0x62, 0x91, 0x43, 0x58, 0x35, 0x61, 0xeb, 0x93, 0xaa, 0x8b, 0x09, 0xd2, 0x2f, 0xa0, 0x02, + 0x79, 0xe0, 0x44, 0xb3, 0x91, 0x13, 0x0f, 0xec, 0x5c, 0xbe, 0x84, 0xfa, 0xca, 0xcd, 0x52, 0xb5, + 0x85, 0xbc, 0x03, 0x4b, 0x43, 0xfd, 0xa2, 0xb8, 0xce, 0x0d, 0x56, 0x69, 0x23, 0xef, 0xc3, 0x72, + 0x55, 0x3f, 0xee, 0xae, 0xde, 0xa0, 0xce, 0x9a, 0xc9, 0x23, 0x20, 0x21, 0x4b, 0x12, 0x0c, 0x05, + 0x0e, 0xfb, 0xfa, 0x50, 0x5c, 0xb6, 0xaa, 0x13, 0xac, 0x97, 0x16, 0x5d, 0x27, 0x4e, 0x1e, 0x42, + 0x05, 0xf6, 0x07, 0x19, 0xbb, 0xc4, 0x8c, 0xcb, 0xb6, 0x74, 0x82, 0x66, 0x69, 0x38, 0x56, 0x78, + 0xf7, 0xdb, 0x05, 0xb0, 0x03, 0x39, 0x9e, 0xc8, 0x27, 0xe0, 0xd4, 0x6a, 0x4d, 0xe6, 0xcb, 0xd6, + 0xde, 0xf2, 0xd5, 0x04, 0xf3, 0xcd, 0x6c, 0xf2, 0x4f, 0x8b, 0x09, 0x76, 0x60, 0x91, 0x8f, 0xc1, + 0x56, 0x63, 0x82, 0xb4, 0x7c, 0x3d, 0xfb, 0x6a, 0x63, 0xe3, 0x6f, 0x5c, 0x3f, 0x87, 0x46, 0x39, + 0x76, 0x88, 0x6b, 0xbc, 0xe7, 0x27, 0x51, 0x7b, 0xdb, 0x58, 0xe6, 0x9e, 0xec, 0x0f, 0x2c, 0xd2, + 0x83, 0x25, 0xdd, 0xf1, 0x48, 0xf6, 0x4a, 0xda, 0xed, 0xaf, 0x5b, 0x7b, 0xff, 0xf5, 0x04, 0xd5, + 0xda, 0xdd, 0xef, 0x2c, 0x70, 0x54, 0x4a, 0x7a, 0x34, 0xa1, 0x11, 0x66, 0xe4, 0x8b, 0xf9, 0xcc, + 0x3c, 0x30, 0x22, 0xb7, 0xdd, 0xa9, 0xf6, 0xce, 0x6b, 0xac, 0xfa, 0xea, 0x74, 0xa1, 0xf1, 0x14, + 0x85, 0x56, 0x2a, 0xd3, 0x55, 0x97, 0x58, 0xad, 0xc3, 0xc7, 0x9f, 0xfe, 0x70, 0xbd, 0x6b, 0xfd, + 0x78, 0xbd, 0x6b, 0xfd, 0x76, 0xbd, 0x6b, 0x7d, 0xff, 0xfb, 0xee, 0x5b, 0x5f, 0xbd, 0x77, 0xf7, + 0x7f, 0x23, 0x03, 0x5b, 0x26, 0xfd, 0xc3, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x43, 0x88, 0x6d, + 0x23, 0xc2, 0x08, 0x00, 0x00, } diff --git a/api/router/router.proto b/api/router/router.proto index 57e5467ab..b017c6e61 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -17,22 +17,26 @@ message SubscribeRequest {} message UplinkMessage { bytes payload = 1; + protocol.Message message = 2; protocol.RxMetadata protocol_metadata = 11; gateway.RxMetadata gateway_metadata = 12; } message DownlinkMessage { bytes payload = 1; + protocol.Message message = 2; protocol.TxConfiguration protocol_configuration = 11; gateway.TxConfiguration gateway_configuration = 12; } message DeviceActivationRequest { - bytes payload = 1; - bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; - bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; - protocol.RxMetadata protocol_metadata = 21; - gateway.RxMetadata gateway_metadata = 22; + bytes payload = 1; + protocol.Message message = 2; + bytes dev_eui = 11 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + bytes app_eui = 12 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + protocol.RxMetadata protocol_metadata = 21; + gateway.RxMetadata gateway_metadata = 22; + protocol.ActivationMetadata activation_metadata = 23; } message DeviceActivationResponse { @@ -40,6 +44,7 @@ message DeviceActivationResponse { // this message is just an Ack. // // bytes payload = 1; + // protocol.Message message = 2; // protocol.TxConfiguration protocol_configuration = 11; // gateway.TxConfiguration gateway_configuration = 12; } diff --git a/api/router/validation.go b/api/router/validation.go index 3b4e29993..e3affefc7 100644 --- a/api/router/validation.go +++ b/api/router/validation.go @@ -24,12 +24,6 @@ func (m *DownlinkMessage) Validate() bool { // Validate implements the api.Validator interface func (m *DeviceActivationRequest) Validate() bool { - if m.AppEui == nil || m.AppEui.IsEmpty() { - return false - } - if m.DevEui == nil || m.DevEui.IsEmpty() { - return false - } if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { return false } diff --git a/core/handler/activation.go b/core/handler/activation.go index 9c1d16926..103d20621 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -241,7 +241,6 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv Payload: resBytes, DownlinkOption: activation.ResponseTemplate.DownlinkOption, ActivationMetadata: metadata, - AppId: dev.AppID, } return res, nil diff --git a/core/networkserver/activation.go b/core/networkserver/activation.go index 33a828636..fafbcf785 100644 --- a/core/networkserver/activation.go +++ b/core/networkserver/activation.go @@ -91,10 +91,10 @@ func (n *networkServer) HandlePrepareActivation(activation *pb_broker.Deduplicat DevAddr: lorawan.DevAddr(devAddr), }, } - if len(lorawanMeta.CfList) == 5 { + if lorawanMeta.CfList != nil { var cfList lorawan.CFList - for i, cfListItem := range lorawanMeta.CfList { - cfList[i] = uint32(cfListItem) + for i, cfListItem := range lorawanMeta.CfList.Freq { + cfList[i] = cfListItem } phy.MACPayload.(*lorawan.JoinAcceptPayload).CFList = &cfList } diff --git a/core/networkserver/activation_test.go b/core/networkserver/activation_test.go index 5ccd6d716..8ea46a20c 100644 --- a/core/networkserver/activation_test.go +++ b/core/networkserver/activation_test.go @@ -37,7 +37,7 @@ func TestHandlePrepareActivation(t *testing.T) { resp, err := ns.HandlePrepareActivation(&pb_broker.DeduplicatedDeviceActivationRequest{ ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ - CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, @@ -59,7 +59,7 @@ func TestHandlePrepareActivation(t *testing.T) { AppEui: &appEUI, ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ - CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, @@ -76,7 +76,7 @@ func TestHandlePrepareActivation(t *testing.T) { AppEui: &appEUI, ActivationMetadata: &pb_protocol.ActivationMetadata{Protocol: &pb_protocol.ActivationMetadata_Lorawan{ Lorawan: &pb_lorawan.ActivationMetadata{ - CfList: []uint64{867100000, 867300000, 867500000, 867700000, 867900000}, + CfList: &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}}, }, }}, ResponseTemplate: &pb_broker.DeviceActivationResponse{}, diff --git a/core/router/activation.go b/core/router/activation.go index ec199207c..803fb672a 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -93,7 +93,7 @@ func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivat lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds()) switch region { case "EU_863_870": - lorawan.CfList = []uint64{867100000, 867300000, 867500000, 867700000, 867900000} + lorawan.CfList = &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}} } ctx = ctx.WithField("NumBrokers", len(brokers)) diff --git a/core/types/activation.go b/core/types/activation.go index ee8aad09e..eb5a6ca46 100644 --- a/core/types/activation.go +++ b/core/types/activation.go @@ -3,6 +3,12 @@ package types +import ( + "encoding/hex" + "errors" + "strings" +) + // Activation messages are used to notify application of a device activation type Activation struct { AppID string `json:"app_id,omitempty"` @@ -12,3 +18,216 @@ type Activation struct { DevAddr DevAddr `json:"dev_addr,omitempty"` Metadata Metadata `json:"metadata,omitempty"` } + +// DevNonce for LoRaWAN +type DevNonce [2]byte + +// Bytes returns the DevNonce as a byte slice +func (n DevNonce) Bytes() []byte { + return n[:] +} + +func (n DevNonce) String() string { + if n == [2]byte{0, 0} { + return "" + } + return strings.ToUpper(hex.EncodeToString(n.Bytes())) +} + +// GoString implements the GoStringer interface. +func (n DevNonce) GoString() string { + return n.String() +} + +// MarshalText implements the TextMarshaler interface. +func (n DevNonce) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (n *DevNonce) UnmarshalText(data []byte) error { + parsed, err := ParseHEX(string(data), 2) + if err != nil { + return err + } + copy(n[:], parsed[:]) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (n DevNonce) MarshalBinary() ([]byte, error) { + return n.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (n *DevNonce) UnmarshalBinary(data []byte) error { + if len(data) != 2 { + return errors.New("ttn/core: Invalid length for DevNonce") + } + copy(n[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (n *DevNonce) MarshalTo(b []byte) (int, error) { + copy(b, n.Bytes()) + return 2, nil +} + +// Size is used by Protobuf +func (n *DevNonce) Size() int { + return 2 +} + +// Marshal implements the Marshaler interface. +func (n DevNonce) Marshal() ([]byte, error) { + return n.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (n *DevNonce) Unmarshal(data []byte) error { + *n = [2]byte{} // Reset the receiver + return n.UnmarshalBinary(data) +} + +// AppNonce for LoRaWAN +type AppNonce [3]byte + +// Bytes returns the AppNonce as a byte slice +func (n AppNonce) Bytes() []byte { + return n[:] +} + +func (n AppNonce) String() string { + if n == [3]byte{0, 0, 0} { + return "" + } + return strings.ToUpper(hex.EncodeToString(n.Bytes())) +} + +// GoString implements the GoStringer interface. +func (n AppNonce) GoString() string { + return n.String() +} + +// MarshalText implements the TextMarshaler interface. +func (n AppNonce) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (n *AppNonce) UnmarshalText(data []byte) error { + parsed, err := ParseHEX(string(data), 3) + if err != nil { + return err + } + copy(n[:], parsed[:]) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (n AppNonce) MarshalBinary() ([]byte, error) { + return n.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (n *AppNonce) UnmarshalBinary(data []byte) error { + if len(data) != 3 { + return errors.New("ttn/core: Invalid length for AppNonce") + } + copy(n[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (n *AppNonce) MarshalTo(b []byte) (int, error) { + copy(b, n.Bytes()) + return 3, nil +} + +// Size is used by Protobuf +func (n *AppNonce) Size() int { + return 3 +} + +// Marshal implements the Marshaler interface. +func (n AppNonce) Marshal() ([]byte, error) { + return n.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (n *AppNonce) Unmarshal(data []byte) error { + *n = [3]byte{} // Reset the receiver + return n.UnmarshalBinary(data) +} + +// NetID for LoRaWAN +type NetID [3]byte + +// Bytes returns the NetID as a byte slice +func (n NetID) Bytes() []byte { + return n[:] +} + +func (n NetID) String() string { + if n == [3]byte{0, 0, 0} { + return "" + } + return strings.ToUpper(hex.EncodeToString(n.Bytes())) +} + +// GoString implements the GoStringer interface. +func (n NetID) GoString() string { + return n.String() +} + +// MarshalText implements the TextMarshaler interface. +func (n NetID) MarshalText() ([]byte, error) { + return []byte(n.String()), nil +} + +// UnmarshalText implements the TextUnmarshaler interface. +func (n *NetID) UnmarshalText(data []byte) error { + parsed, err := ParseHEX(string(data), 3) + if err != nil { + return err + } + copy(n[:], parsed[:]) + return nil +} + +// MarshalBinary implements the BinaryMarshaler interface. +func (n NetID) MarshalBinary() ([]byte, error) { + return n.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (n *NetID) UnmarshalBinary(data []byte) error { + if len(data) != 3 { + return errors.New("ttn/core: Invalid length for NetID") + } + copy(n[:], data) + return nil +} + +// MarshalTo is used by Protobuf +func (n *NetID) MarshalTo(b []byte) (int, error) { + copy(b, n.Bytes()) + return 3, nil +} + +// Size is used by Protobuf +func (n *NetID) Size() int { + return 3 +} + +// Marshal implements the Marshaler interface. +func (n NetID) Marshal() ([]byte, error) { + return n.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (n *NetID) Unmarshal(data []byte) error { + *n = [3]byte{} // Reset the receiver + return n.UnmarshalBinary(data) +} diff --git a/core/types/activation_test.go b/core/types/activation_test.go new file mode 100644 index 000000000..055fd5863 --- /dev/null +++ b/core/types/activation_test.go @@ -0,0 +1,184 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package types + +import ( + "testing" + + . "github.com/smartystreets/assertions" +) + +func TestDevNonce(t *testing.T) { + a := New(t) + + // Setup + nonce := DevNonce{1, 2} + str := "0102" + bin := []byte{0x01, 0x02} + + // Bytes + a.So(nonce.Bytes(), ShouldResemble, bin) + + // String + a.So(nonce.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := nonce.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := nonce.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := nonce.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 2) + _, err = nonce.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := nonce.Size() + a.So(s, ShouldEqual, 2) + + // UnmarshalText + utOut := &DevNonce{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &nonce) + + // UnmarshalBinary + ubOut := &DevNonce{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &nonce) + + // Unmarshal + uOut := &DevNonce{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &nonce) +} + +func TestAppNonce(t *testing.T) { + a := New(t) + + // Setup + nonce := AppNonce{1, 2, 3} + str := "010203" + bin := []byte{0x01, 0x02, 0x03} + + // Bytes + a.So(nonce.Bytes(), ShouldResemble, bin) + + // String + a.So(nonce.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := nonce.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := nonce.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := nonce.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 3) + _, err = nonce.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := nonce.Size() + a.So(s, ShouldEqual, 3) + + // UnmarshalText + utOut := &AppNonce{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &nonce) + + // UnmarshalBinary + ubOut := &AppNonce{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &nonce) + + // Unmarshal + uOut := &AppNonce{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &nonce) +} + +func TestNetID(t *testing.T) { + a := New(t) + + // Setup + nid := NetID{1, 2, 3} + str := "010203" + bin := []byte{0x01, 0x02, 0x03} + + // Bytes + a.So(nid.Bytes(), ShouldResemble, bin) + + // String + a.So(nid.String(), ShouldEqual, str) + + // MarshalText + mtOut, err := nid.MarshalText() + a.So(err, ShouldBeNil) + a.So(mtOut, ShouldResemble, []byte(str)) + + // MarshalBinary + mbOut, err := nid.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(mbOut, ShouldResemble, bin) + + // Marshal + mOut, err := nid.Marshal() + a.So(err, ShouldBeNil) + a.So(mOut, ShouldResemble, bin) + + // MarshalTo + bOut := make([]byte, 3) + _, err = nid.MarshalTo(bOut) + a.So(err, ShouldBeNil) + a.So(bOut, ShouldResemble, bin) + + // Size + s := nid.Size() + a.So(s, ShouldEqual, 3) + + // UnmarshalText + utOut := &NetID{} + err = utOut.UnmarshalText([]byte(str)) + a.So(err, ShouldBeNil) + a.So(utOut, ShouldResemble, &nid) + + // UnmarshalBinary + ubOut := &NetID{} + err = ubOut.UnmarshalBinary(bin) + a.So(err, ShouldBeNil) + a.So(ubOut, ShouldResemble, &nid) + + // Unmarshal + uOut := &NetID{} + err = uOut.Unmarshal(bin) + a.So(err, ShouldBeNil) + a.So(uOut, ShouldResemble, &nid) +} From 456fc229e44aa214efc7a71afd5acbb303f1f0aa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Nov 2016 09:26:08 +0100 Subject: [PATCH 2097/2266] Add discover cmd to ttnctl --- ttnctl/cmd/discover.go | 118 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 ttnctl/cmd/discover.go diff --git a/ttnctl/cmd/discover.go b/ttnctl/cmd/discover.go new file mode 100644 index 000000000..632c54563 --- /dev/null +++ b/ttnctl/cmd/discover.go @@ -0,0 +1,118 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "strings" + + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/spf13/cobra" +) + +const serviceFmt = "%-36s %-36s %-20s %-6s\n" + +func crop(in string, length int) string { + if len(in) > length { + return in[:length] + } + return in +} + +func listDevAddrPrefixes(in []*discovery.Metadata) (prefixes []types.DevAddrPrefix) { + for _, meta := range in { + if meta.Key != discovery.Metadata_PREFIX || len(meta.Value) != 5 { + continue + } + prefix := types.DevAddrPrefix{ + Length: int(meta.Value[0]), + } + prefix.DevAddr[0] = meta.Value[1] + prefix.DevAddr[1] = meta.Value[2] + prefix.DevAddr[2] = meta.Value[3] + prefix.DevAddr[3] = meta.Value[4] + prefixes = append(prefixes, prefix) + } + return +} + +func listAppIDs(in []*discovery.Metadata) (appIDs []string) { + for _, meta := range in { + if meta.Key != discovery.Metadata_APP_ID { + continue + } + appIDs = append(appIDs, string(meta.Value)) + } + return +} + +var discoverCmd = &cobra.Command{ + Use: "discover [ServiceType]", + Short: "Discover routing services", + Long: `ttnctl discover is used to discover routing services`, + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + if len(args) == 0 { + cmd.UsageFunc()(cmd) + return + } + + serviceType := strings.TrimRight(args[0], "s") // Allow both singular and plural + + switch serviceType { + case "router": + ctx.Info("Discovering routers...") + case "broker": + ctx.Info("Discovering brokers and their prefixes...") + case "handler": + ctx.Info("Discovering handlers and their apps...") + default: + ctx.Fatalf("Service type %s unknown", serviceType) + } + + conn, client := util.GetDiscovery(ctx) + defer conn.Close() + + res, err := client.GetAll(util.GetContext(ctx), &discovery.GetAllRequest{ + ServiceName: serviceType, + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not get %ss", serviceType) + } + + ctx.Infof("Discovered %d %ss", len(res.Services), serviceType) + + fmt.Printf(serviceFmt, "ID", "ADDRESS", "VERSION", "PUBLIC") + fmt.Printf(serviceFmt, "==", "=======", "=======", "======") + fmt.Println() + for _, service := range res.Services { + fmt.Printf(serviceFmt, service.Id, crop(service.NetAddress, 36), crop(service.ServiceVersion, 20), fmt.Sprintf("%v", service.Public)) + if showMetadata, _ := cmd.Flags().GetBool("metadata"); showMetadata { + switch serviceType { + case "broker": + fmt.Println(" DevAddr Prefixes:") + for _, prefix := range listDevAddrPrefixes(service.Metadata) { + min := types.DevAddr{0x00, 0x00, 0x00, 0x00}.WithPrefix(prefix) + max := types.DevAddr{0xff, 0xff, 0xff, 0xff}.WithPrefix(prefix) + fmt.Printf(" %s (%s-%s)\n", prefix, min, max) + } + case "handler": + fmt.Println(" AppIDs:") + for _, appID := range listAppIDs(service.Metadata) { + fmt.Println(" ", appID) + } + } + fmt.Println() + } + } + }, +} + +func init() { + RootCmd.AddCommand(discoverCmd) + discoverCmd.Flags().Bool("metadata", false, "Show additional metadata") +} From cd6704e226d371aff0f5deb48959061194cdaa78 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Nov 2016 09:26:36 +0100 Subject: [PATCH 2098/2266] Activate health port for development --- Procfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Procfile b/Procfile index 54e453c42..8dd4221ea 100644 --- a/Procfile +++ b/Procfile @@ -1,5 +1,5 @@ -rtr: ttn router --config ./.env/router/dev.yml -dsc: ttn discovery --config ./.env/discovery/dev.yml -ns: ttn networkserver --config ./.env/networkserver/dev.yml -brk: ttn broker --config ./.env/broker/dev.yml -hdl: ttn handler --config ./.env/handler/dev.yml +rtr: ttn router --config ./.env/router/dev.yml --health-port 10901 +dsc: ttn discovery --config ./.env/discovery/dev.yml --health-port 10900 +ns: ttn networkserver --config ./.env/networkserver/dev.yml --health-port 10903 +brk: ttn broker --config ./.env/broker/dev.yml --health-port 10902 +hdl: ttn handler --config ./.env/handler/dev.yml --health-port 10904 From 7036e9a38980c47a6c72d597bef4208bc16b1971 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Nov 2016 11:35:30 +0100 Subject: [PATCH 2099/2266] Add marshaling and unmarshaling from Payload to Message in protos --- api/broker/message_marshaling.go | 227 +++++++++++++++++++++++++ api/broker/message_marshaling_test.go | 165 ++++++++++++++++++ api/handler/message_marshaling.go | 41 +++++ api/handler/message_marshaling_test.go | 81 +++++++++ api/router/message_marshaling.go | 105 ++++++++++++ api/router/message_marshaling_test.go | 110 ++++++++++++ 6 files changed, 729 insertions(+) create mode 100644 api/broker/message_marshaling.go create mode 100644 api/broker/message_marshaling_test.go create mode 100644 api/handler/message_marshaling.go create mode 100644 api/handler/message_marshaling_test.go create mode 100644 api/router/message_marshaling.go create mode 100644 api/router/message_marshaling_test.go diff --git a/api/broker/message_marshaling.go b/api/broker/message_marshaling.go new file mode 100644 index 000000000..ef55c209e --- /dev/null +++ b/api/broker/message_marshaling.go @@ -0,0 +1,227 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "errors" + "fmt" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/brocaar/lorawan" +) + +func msgFromPayload(payload []byte) (*pb_protocol.Message, error) { + var phy lorawan.PHYPayload + if err := phy.UnmarshalBinary(payload); err != nil { + return nil, err + } + msg := pb_lorawan.MessageFromPHYPayload(phy) + return &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}}, nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *UplinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DownlinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationResponse) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeduplicatedUplinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationRequest) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeduplicatedDeviceActivationRequest) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *ActivationChallengeRequest) UnmarshalPayload() error { + if m.GetMessage() == nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *ActivationChallengeResponse) UnmarshalPayload() error { + if m.GetMessage() == nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +func payloadFromMsg(msg *pb_protocol.Message) ([]byte, error) { + if msg.GetLorawan() == nil { + return nil, errors.New("No LoRaWAN message to marshal") + } + phy := msg.GetLorawan().PHYPayload() + bin, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + fmt.Println(bin) + return bin, nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *UplinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DownlinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationResponse) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeduplicatedUplinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeduplicatedDeviceActivationRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *ActivationChallengeRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *ActivationChallengeResponse) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} diff --git a/api/broker/message_marshaling_test.go b/api/broker/message_marshaling_test.go new file mode 100644 index 000000000..e5a5eb910 --- /dev/null +++ b/api/broker/message_marshaling_test.go @@ -0,0 +1,165 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type payloadMarshalerUnmarshaler interface { + UnmarshalPayload() error + MarshalPayload() error +} + +func TestMarshalUnmarshalPayload(t *testing.T) { + a := New(t) + + var subjects []payloadMarshalerUnmarshaler + + // Do nothing when message and payload are nil + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{}, + &DownlinkMessage{}, + &DeviceActivationResponse{}, + &DeduplicatedUplinkMessage{}, + &DeviceActivationRequest{}, + &DeduplicatedDeviceActivationRequest{}, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + + rxMeta := &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}} + txConf := &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}} + + macMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_UNCONFIRMED_UP, + }, + Payload: &pb_lorawan.Message_MacPayload{MacPayload: &pb_lorawan.MACPayload{ + FHDR: pb_lorawan.FHDR{ + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + macBin := []byte{65, 4, 3, 2, 1, 0, 1, 0, 0, 1, 2, 3, 4} + joinReqMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_REQUEST, + }, + Payload: &pb_lorawan.Message_JoinRequestPayload{JoinRequestPayload: &pb_lorawan.JoinRequestPayload{ + AppEui: types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEui: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevNonce: types.DevNonce([2]byte{1, 2}), + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinReqBin := []byte{1, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 2, 1, 1, 2, 3, 4} + joinAccMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_ACCEPT, + }, + Payload: &pb_lorawan.Message_JoinAcceptPayload{JoinAcceptPayload: &pb_lorawan.JoinAcceptPayload{ + AppNonce: types.AppNonce([3]byte{1, 2, 3}), + NetId: types.NetID([3]byte{1, 2, 3}), + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + DLSettings: pb_lorawan.DLSettings{ + Rx2Dr: 3, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinAccBin := []byte{33, 3, 2, 1, 3, 2, 1, 4, 3, 2, 1, 3, 0, 1, 2, 3, 4} + + // Only Marshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Message: macMsg, + }, + &DownlinkMessage{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Message: macMsg, + }, + &DeviceActivationResponse{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Message: joinAccMsg, + }, + &DeduplicatedUplinkMessage{ + ProtocolMetadata: rxMeta, + Message: macMsg, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Message: joinReqMsg, + }, + &DeduplicatedDeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Message: joinReqMsg, + }, + &ActivationChallengeRequest{ + Message: joinReqMsg, + }, + &ActivationChallengeResponse{ + Message: joinReqMsg, + }, + } + + for _, sub := range subjects { + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + a.So(sub.MarshalPayload(), ShouldEqual, nil) + } + + // Only Unmarshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Payload: macBin, + }, + &DownlinkMessage{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Payload: macBin, + }, + &DeviceActivationResponse{ + DownlinkOption: &DownlinkOption{ProtocolConfig: txConf}, + Payload: joinAccBin, + }, + &DeduplicatedUplinkMessage{ + ProtocolMetadata: rxMeta, + Payload: macBin, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Payload: joinReqBin, + }, + &DeduplicatedDeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Payload: joinReqBin, + }, + &ActivationChallengeRequest{ + Payload: joinReqBin, + }, + &ActivationChallengeResponse{ + Payload: joinReqBin, + }, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + +} diff --git a/api/handler/message_marshaling.go b/api/handler/message_marshaling.go new file mode 100644 index 000000000..98280ad8e --- /dev/null +++ b/api/handler/message_marshaling.go @@ -0,0 +1,41 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "errors" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/brocaar/lorawan" +) + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationResponse) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetDownlinkOption() != nil && m.DownlinkOption.GetProtocolConfig() != nil && m.DownlinkOption.ProtocolConfig.GetLorawan() != nil { + var phy lorawan.PHYPayload + if err := phy.UnmarshalBinary(m.Payload); err != nil { + return err + } + msg := pb_lorawan.MessageFromPHYPayload(phy) + m.Message = &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}} + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationResponse) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + if m.Message.GetLorawan() == nil { + return errors.New("No LoRaWAN message to marshal") + } + phy := m.Message.GetLorawan().PHYPayload() + bin, err := phy.MarshalBinary() + if err != nil { + return err + } + m.Payload = bin + } + return nil +} diff --git a/api/handler/message_marshaling_test.go b/api/handler/message_marshaling_test.go new file mode 100644 index 000000000..624257215 --- /dev/null +++ b/api/handler/message_marshaling_test.go @@ -0,0 +1,81 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type payloadMarshalerUnmarshaler interface { + UnmarshalPayload() error + MarshalPayload() error +} + +func TestMarshalUnmarshalPayload(t *testing.T) { + a := New(t) + + var subjects []payloadMarshalerUnmarshaler + + // Do nothing when message and payload are nil + subjects = []payloadMarshalerUnmarshaler{ + &DeviceActivationResponse{}, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + + txConf := &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}} + + joinAccMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_ACCEPT, + }, + Payload: &pb_lorawan.Message_JoinAcceptPayload{JoinAcceptPayload: &pb_lorawan.JoinAcceptPayload{ + AppNonce: types.AppNonce([3]byte{1, 2, 3}), + NetId: types.NetID([3]byte{1, 2, 3}), + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + DLSettings: pb_lorawan.DLSettings{ + Rx2Dr: 3, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinAccBin := []byte{33, 3, 2, 1, 3, 2, 1, 4, 3, 2, 1, 3, 0, 1, 2, 3, 4} + + // Only Marshal + subjects = []payloadMarshalerUnmarshaler{ + &DeviceActivationResponse{ + DownlinkOption: &pb_broker.DownlinkOption{ProtocolConfig: txConf}, + Message: joinAccMsg, + }, + } + + for _, sub := range subjects { + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + a.So(sub.MarshalPayload(), ShouldEqual, nil) + } + + // Only Unmarshal + subjects = []payloadMarshalerUnmarshaler{ + &DeviceActivationResponse{ + DownlinkOption: &pb_broker.DownlinkOption{ProtocolConfig: txConf}, + Payload: joinAccBin, + }, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + +} diff --git a/api/router/message_marshaling.go b/api/router/message_marshaling.go new file mode 100644 index 000000000..3e24ca909 --- /dev/null +++ b/api/router/message_marshaling.go @@ -0,0 +1,105 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "errors" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/brocaar/lorawan" +) + +func msgFromPayload(payload []byte) (*pb_protocol.Message, error) { + var phy lorawan.PHYPayload + if err := phy.UnmarshalBinary(payload); err != nil { + return nil, err + } + msg := pb_lorawan.MessageFromPHYPayload(phy) + return &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &msg}}, nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *UplinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DownlinkMessage) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolConfiguration() != nil && m.ProtocolConfiguration.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +// UnmarshalPayload unmarshals the Payload into Message if Message is nil +func (m *DeviceActivationRequest) UnmarshalPayload() error { + if m.GetMessage() == nil && m.GetProtocolMetadata() != nil && m.ProtocolMetadata.GetLorawan() != nil { + msg, err := msgFromPayload(m.Payload) + if err != nil { + return err + } + m.Message = msg + } + return nil +} + +func payloadFromMsg(msg *pb_protocol.Message) ([]byte, error) { + if msg.GetLorawan() == nil { + return nil, errors.New("No LoRaWAN message to marshal") + } + phy := msg.GetLorawan().PHYPayload() + bin, err := phy.MarshalBinary() + if err != nil { + return nil, err + } + return bin, nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *UplinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DownlinkMessage) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} + +// MarshalPayload marshals the Message into Payload if Payload is nil +func (m *DeviceActivationRequest) MarshalPayload() error { + if m.Payload == nil && m.GetMessage() != nil { + bin, err := payloadFromMsg(m.Message) + if err != nil { + return err + } + m.Payload = bin + } + return nil +} diff --git a/api/router/message_marshaling_test.go b/api/router/message_marshaling_test.go new file mode 100644 index 000000000..c84e96383 --- /dev/null +++ b/api/router/message_marshaling_test.go @@ -0,0 +1,110 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/core/types" + . "github.com/smartystreets/assertions" +) + +type payloadMarshalerUnmarshaler interface { + UnmarshalPayload() error + MarshalPayload() error +} + +func TestMarshalUnmarshalPayload(t *testing.T) { + a := New(t) + + var subjects []payloadMarshalerUnmarshaler + + // Do nothing when message and payload are nil + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{}, + &DownlinkMessage{}, + &DeviceActivationRequest{}, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + + rxMeta := &pb_protocol.RxMetadata{Protocol: &pb_protocol.RxMetadata_Lorawan{Lorawan: &pb_lorawan.Metadata{}}} + txConf := &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{}}} + + macMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_UNCONFIRMED_UP, + }, + Payload: &pb_lorawan.Message_MacPayload{MacPayload: &pb_lorawan.MACPayload{ + FHDR: pb_lorawan.FHDR{ + DevAddr: types.DevAddr([4]byte{1, 2, 3, 4}), + FCnt: 1, + }, + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + macBin := []byte{65, 4, 3, 2, 1, 0, 1, 0, 0, 1, 2, 3, 4} + joinReqMsg := &pb_protocol.Message{Protocol: &pb_protocol.Message_Lorawan{Lorawan: &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{ + Major: 1, + MType: pb_lorawan.MType_JOIN_REQUEST, + }, + Payload: &pb_lorawan.Message_JoinRequestPayload{JoinRequestPayload: &pb_lorawan.JoinRequestPayload{ + AppEui: types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevEui: types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}), + DevNonce: types.DevNonce([2]byte{1, 2}), + }}, + Mic: []byte{1, 2, 3, 4}, + }}} + joinReqBin := []byte{1, 8, 7, 6, 5, 4, 3, 2, 1, 8, 7, 6, 5, 4, 3, 2, 1, 2, 1, 1, 2, 3, 4} + + // Only Marshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Message: macMsg, + }, + &DownlinkMessage{ + ProtocolConfiguration: txConf, + Message: macMsg, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Message: joinReqMsg, + }, + } + + for _, sub := range subjects { + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + a.So(sub.MarshalPayload(), ShouldEqual, nil) + } + + // Only Unmarshal + subjects = []payloadMarshalerUnmarshaler{ + &UplinkMessage{ + ProtocolMetadata: rxMeta, + Payload: macBin, + }, + &DownlinkMessage{ + ProtocolConfiguration: txConf, + Payload: macBin, + }, + &DeviceActivationRequest{ + ProtocolMetadata: rxMeta, + Payload: joinReqBin, + }, + } + + for _, sub := range subjects { + a.So(sub.MarshalPayload(), ShouldEqual, nil) + a.So(sub.UnmarshalPayload(), ShouldEqual, nil) + } + +} From d3d9cea8f18367b1722caf2f2fa6da3883aa0d5d Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Wed, 2 Nov 2016 12:07:47 +0100 Subject: [PATCH 2100/2266] Add port argumetns to dry up- and downlink methods --- api/handler/manager_client.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index 20d05c075..02b9a049e 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -141,10 +141,11 @@ func (h *ManagerClient) GetDevAddr(constraints ...string) (types.DevAddr, error) // DryUplink transforms the uplink payload with the payload functions provided // in the app.. -func (h *ManagerClient) DryUplink(payload []byte, app *Application) (*DryUplinkResult, error) { +func (h *ManagerClient) DryUplink(payload []byte, app *Application, port uint32) (*DryUplinkResult, error) { res, err := h.applicationManagerClient.DryUplink(h.getContext(), &DryUplinkMessage{ App: app, Payload: payload, + Port: port, }) if err != nil { return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run uplink on Handler") @@ -154,10 +155,11 @@ func (h *ManagerClient) DryUplink(payload []byte, app *Application) (*DryUplinkR // DryDownlinkWithPayload transforms the downlink payload with the payload functions // provided in app. -func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application) (*DryDownlinkResult, error) { +func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application, port uint32) (*DryDownlinkResult, error) { res, err := h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ App: app, Payload: payload, + Port: port, }) if err != nil { return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run downlink with payload on Handler") @@ -167,7 +169,7 @@ func (h *ManagerClient) DryDownlinkWithPayload(payload []byte, app *Application) // DryDownlinkWithFields transforms the downlink fields with the payload functions // provided in app. -func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app *Application) (*DryDownlinkResult, error) { +func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app *Application, port uint32) (*DryDownlinkResult, error) { marshalled, err := json.Marshal(fields) if err != nil { return nil, err @@ -176,6 +178,7 @@ func (h *ManagerClient) DryDownlinkWithFields(fields map[string]interface{}, app res, err := h.applicationManagerClient.DryDownlink(h.getContext(), &DryDownlinkMessage{ App: app, Fields: string(marshalled), + Port: port, }) if err != nil { return nil, errors.Wrap(errors.FromGRPCError(err), "Could not dry-run downlink with fields on Handler") From 4600f2fa23fa60414efe8237da4467efa6d2c6b9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Nov 2016 14:08:55 +0100 Subject: [PATCH 2101/2266] Add checksums to info file --- .gitlab-ci.yml | 3 +++ utils/version/version.go | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 279c782dd..d9df0871e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -60,6 +60,9 @@ binaries: - GOOS=windows GOARCH=386 make build - GOOS=windows GOARCH=amd64 make build - popd + - pushd release + - shasum -a 256 $(ls ttn*) | awk '{print "sha256 " $2 " " $1}' >> info + - popd artifacts: paths: - release/ diff --git a/utils/version/version.go b/utils/version/version.go index b9369670a..ddc66b781 100644 --- a/utils/version/version.go +++ b/utils/version/version.go @@ -65,8 +65,6 @@ func GetLatestInfo() (*Info, error) { if date, err := time.Parse(time.RFC3339, infoLine[1]); err == nil { info.Date = date } - default: - fmt.Printf("Can't handle %s", infoLine[0]) } } From ddb63de73ed9b81c14b2bfa16046ea462d61a4e6 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 2 Nov 2016 15:13:41 +0100 Subject: [PATCH 2102/2266] Use braces --- ttnctl/cmd/applications_pf.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ttnctl/cmd/applications_pf.go b/ttnctl/cmd/applications_pf.go index ea5a09065..4eb3e1b89 100644 --- a/ttnctl/cmd/applications_pf.go +++ b/ttnctl/cmd/applications_pf.go @@ -22,7 +22,9 @@ converting and validating binary payload.`, INFO Decoder function function Decoder(bytes, port) { var decoded = {}; - if (port === 1) decoded.led = bytes[0]; + if (port === 1) { + decoded.led = bytes[0]; + } return decoded; } INFO No converter function From dc0ff2e0e66a3f0bdc60299437cc30fd5feba724 Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Wed, 2 Nov 2016 15:14:53 +0100 Subject: [PATCH 2103/2266] Use braces --- ttnctl/cmd/applications_pf_set.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/ttnctl/cmd/applications_pf_set.go b/ttnctl/cmd/applications_pf_set.go index c825b7f44..cd3a6303c 100644 --- a/ttnctl/cmd/applications_pf_set.go +++ b/ttnctl/cmd/applications_pf_set.go @@ -28,7 +28,9 @@ function Decoder(bytes, port) { // (array) of bytes to an object of fields. var decoded = {}; - // if (port === 1) decoded.led = bytes[0]; + // if (port === 1) { + // decoded.led = bytes[0]; + // } return decoded; } @@ -36,7 +38,9 @@ function Decoder(bytes, port) { function Decoder(bytes, port) { var decoded = {}; - if (port === 1) decoded.led = bytes[0]; + // if (port === 1) { + // decoded.led = bytes[0]; + // } return decoded; } @@ -88,7 +92,9 @@ function Decoder(bytes, port) { // (array) of bytes to an object of fields. var decoded = {}; - // if (port === 1) decoded.led = bytes[0]; + // if (port === 1) { + // decoded.led = bytes[0]; + // } return decoded; } @@ -127,7 +133,9 @@ function Decoder(bytes, port) { // object to an array or buffer of bytes. var bytes = []; - // if (port === 1) bytes[0] = object.led ? 1 : 0; + // if (port === 1) { + // bytes[0] = object.led ? 1 : 0; + // } return bytes; } From e4526fe52311855138130b6cbd36befb4d0824a5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 2 Nov 2016 14:47:04 +0100 Subject: [PATCH 2104/2266] Add sign stage to gitlab CI --- .gitlab-ci.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d9df0871e..d3b8bba27 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ stages: - deps - test - build + - sign - package variables: @@ -60,12 +61,26 @@ binaries: - GOOS=windows GOARCH=386 make build - GOOS=windows GOARCH=amd64 make build - popd + artifacts: + paths: + - release/ + +sign: + only: + - v1-staging@thethingsnetwork/ttn + - v2-preview@thethingsnetwork/ttn + stage: sign + image: golang:latest + script: - pushd release - - shasum -a 256 $(ls ttn*) | awk '{print "sha256 " $2 " " $1}' >> info + - shasum -a 256 $(ls) > checksums + - gpg --no-tty --batch --import /gpg/signing.ci.gpg-key + - gpg --no-tty --batch --no-use-agent --passphrase $GPG_PASSPHRASE --detach-sign checksums - popd artifacts: paths: - - release/ + - release/checksums + - release/checksums.sig gitlab-image: stage: package From e403ac5bb59b0d43920ca1abf6f16e13f8fbc94e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Nov 2016 17:04:08 +0100 Subject: [PATCH 2105/2266] Pass valid DownlinkMessage from Broker to NS --- core/broker/uplink.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 4712a9ce8..62150abf6 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -158,6 +158,10 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { // Select best DownlinkOption if len(downlinkOptions) > 0 { downlinkMessage = &pb.DownlinkMessage{ + DevEui: device.DevEui, + AppEui: device.AppEui, + AppId: device.AppId, + DevId: device.DevId, DownlinkOption: selectBestDownlink(downlinkOptions), } } From b4f729909c0a11fb5ceb1fdc91d694ad5ca4cc05 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Nov 2016 21:34:49 +0100 Subject: [PATCH 2106/2266] Re-implement monitor client Also add example server and tests --- api/monitor/client.go | 348 ++-------------------------------- api/monitor/client_test.go | 148 +++++++++++++++ api/monitor/downlink.go | 100 ++++++++++ api/monitor/example_server.go | 114 +++++++++++ api/monitor/gateway_status.go | 100 ++++++++++ api/monitor/uplink.go | 100 ++++++++++ utils/backoff/backoff.go | 60 ++++++ 7 files changed, 640 insertions(+), 330 deletions(-) create mode 100644 api/monitor/client_test.go create mode 100644 api/monitor/downlink.go create mode 100644 api/monitor/example_server.go create mode 100644 api/monitor/gateway_status.go create mode 100644 api/monitor/uplink.go create mode 100644 utils/backoff/backoff.go diff --git a/api/monitor/client.go b/api/monitor/client.go index f05f7a967..7040e9b40 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -1,21 +1,21 @@ package monitor import ( - "io" "sync" - "golang.org/x/net/context" - "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" + "golang.org/x/net/context" "google.golang.org/grpc" - "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) +// BufferSize gives the size for the monitor buffers +const BufferSize = 10 + // Client is a wrapper around MonitorClient type Client struct { Ctx log.Interface @@ -24,8 +24,6 @@ type Client struct { conn *grpc.ClientConn addr string - once *sync.Once - gateways map[string]GatewayClient mutex sync.RWMutex } @@ -37,8 +35,6 @@ func NewClient(ctx log.Interface, monitorAddr string) (cl *Client, err error) { Ctx: ctx, addr: monitorAddr, gateways: make(map[string]GatewayClient), - - once: &sync.Once{}, } return cl, cl.Open() } @@ -169,17 +165,23 @@ type gatewayClient struct { id, token string status struct { - stream Monitor_GatewayStatusClient + init sync.Once + ch chan *gateway.Status + cancel func() sync.RWMutex } uplink struct { - stream Monitor_GatewayUplinkClient - sync.RWMutex + init sync.Once + ch chan *router.UplinkMessage + cancel func() + sync.Mutex } downlink struct { - stream Monitor_GatewayDownlinkClient + init sync.Once + ch chan *router.DownlinkMessage + cancel func() sync.RWMutex } } @@ -187,6 +189,7 @@ type gatewayClient struct { // GatewayClient is used as the main client for Gateways to communicate with the monitor type GatewayClient interface { SetToken(token string) + IsConfigured() bool SendStatus(status *gateway.Status) (err error) SendUplink(msg *router.UplinkMessage) (err error) SendDownlink(msg *router.DownlinkMessage) (err error) @@ -205,264 +208,11 @@ func (cl *gatewayClient) IsConfigured() bool { return cl.token != "" && cl.token != "token" } -// SendStatus sends status to the monitor -func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { - if !cl.IsConfigured() { - return nil - } - - cl.status.RLock() - cl.client.mutex.RLock() - - once := cl.client.once - stream := cl.status.stream - - cl.status.RUnlock() - cl.client.mutex.RUnlock() - - defer func() { - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send status to monitor") - - if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - once.Do(func() { - err = cl.client.Reopen() - - cl.client.mutex.Lock() - cl.client.once = &sync.Once{} - cl.client.mutex.Unlock() - }) - } - } else { - cl.Ctx.Debug("Sent status to monitor") - } - }() - - if stream == nil { - cl.status.Lock() - if stream = cl.status.stream; stream == nil { - stream, err = cl.setupStatus() - if err != nil { - cl.status.Unlock() - return err - } - } - go func() { - var msg []byte - if err := stream.RecvMsg(&msg); err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor status stream, closing...") - cl.status.Lock() - cl.status.stream.CloseSend() - if cl.status.stream == stream { - cl.status.stream = nil - } - cl.status.Unlock() - } - }() - cl.status.Unlock() - } - - if err = stream.Send(status); err == io.EOF { - cl.Ctx.Warn("Monitor status stream closed") - cl.status.Lock() - if cl.status.stream == stream { - cl.status.stream = nil - } - cl.status.Unlock() - return nil - } - return err -} - -// SendUplink sends uplink to the monitor -func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { - if !cl.IsConfigured() { - return nil - } - - cl.uplink.RLock() - cl.client.mutex.RLock() - - once := cl.client.once - stream := cl.uplink.stream - - cl.uplink.RUnlock() - cl.client.mutex.RUnlock() - - defer func() { - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send uplink to monitor") - - if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - once.Do(func() { - err = cl.client.Reopen() - - cl.client.mutex.Lock() - cl.client.once = &sync.Once{} - cl.client.mutex.Unlock() - }) - } - } else { - cl.Ctx.Debug("Sent uplink to monitor") - } - }() - - if stream == nil { - cl.uplink.Lock() - if stream = cl.uplink.stream; stream == nil { - stream, err = cl.setupUplink() - if err != nil { - cl.uplink.Unlock() - return err - } - } - go func() { - var msg []byte - if err := stream.RecvMsg(&msg); err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor uplink stream, closing...") - cl.uplink.Lock() - cl.uplink.stream.CloseSend() - if cl.uplink.stream == stream { - cl.uplink.stream = nil - } - cl.uplink.Unlock() - } - }() - cl.uplink.Unlock() - } - - if err = stream.Send(uplink); err == io.EOF { - cl.Ctx.Warn("Monitor uplink stream closed") - cl.uplink.Lock() - if cl.uplink.stream == stream { - cl.uplink.stream = nil - } - cl.uplink.Unlock() - return nil - } - return err -} - -// SendUplink sends downlink to the monitor -func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { - if !cl.IsConfigured() { - return nil - } - - cl.downlink.RLock() - cl.client.mutex.RLock() - - once := cl.client.once - stream := cl.downlink.stream - - cl.downlink.RUnlock() - cl.client.mutex.RUnlock() - - defer func() { - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to send downlink to monitor") - - if code := grpc.Code(err); code == codes.Unavailable || code == codes.Internal { - once.Do(func() { - err = cl.client.Reopen() - - cl.client.mutex.Lock() - cl.client.once = &sync.Once{} - cl.client.mutex.Unlock() - }) - } - } else { - cl.Ctx.Debug("Sent downlink to monitor") - } - }() - - if stream == nil { - cl.downlink.Lock() - if stream = cl.downlink.stream; stream == nil { - stream, err = cl.setupDownlink() - if err != nil { - cl.downlink.Unlock() - return err - } - } - go func() { - var msg []byte - if err := stream.RecvMsg(&msg); err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor downlink stream, closing...") - cl.downlink.Lock() - cl.downlink.stream.CloseSend() - if cl.downlink.stream == stream { - cl.downlink.stream = nil - } - cl.downlink.Unlock() - } - }() - cl.downlink.Unlock() - } - - if err = stream.Send(downlink); err == io.EOF { - cl.Ctx.Warn("Monitor downlink stream closed") - cl.downlink.Lock() - if cl.downlink.stream == stream { - cl.downlink.stream = nil - } - cl.downlink.Unlock() - return nil - } - return err -} - // Close closes all opened monitor streams for the gateway func (cl *gatewayClient) Close() (err error) { - wg := &sync.WaitGroup{} - - wg.Add(1) - go func() { - defer wg.Done() - - cl.status.Lock() - - if cl.status.stream != nil { - if cerr := cl.closeStatus(); cerr != nil { - err = cerr - } - cl.status.stream = nil - } - }() - defer cl.status.Unlock() - - wg.Add(1) - go func() { - defer wg.Done() - - cl.uplink.Lock() - - if cl.uplink.stream != nil { - if cerr := cl.closeUplink(); cerr != nil { - err = cerr - } - cl.uplink.stream = nil - } - }() - defer cl.uplink.Unlock() - - wg.Add(1) - go func() { - defer wg.Done() - - cl.downlink.Lock() - - if cl.downlink.stream != nil { - cerr := cl.closeDownlink() - if cerr != nil { - err = cerr - } - cl.downlink.stream = nil - } - }() - defer cl.downlink.Unlock() - - wg.Wait() + cl.closeStatus() + cl.closeUplink() + cl.closeDownlink() return err } @@ -475,65 +225,3 @@ func (cl *gatewayClient) Context() (monitorContext context.Context) { "token", cl.token, )) } - -func (cl *gatewayClient) setupStatus() (stream Monitor_GatewayStatusClient, err error) { - stream, err = cl.client.client.GatewayStatus(cl.Context()) - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") - return nil, err - } - cl.Ctx.Debug("Opened new monitor status stream") - - cl.status.stream = stream - return stream, nil -} -func (cl *gatewayClient) setupUplink() (stream Monitor_GatewayUplinkClient, err error) { - stream, err = cl.client.client.GatewayUplink(cl.Context()) - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") - return nil, err - } - cl.Ctx.Debug("Opened new monitor uplink stream") - - cl.uplink.stream = stream - return stream, nil -} -func (cl *gatewayClient) setupDownlink() (stream Monitor_GatewayDownlinkClient, err error) { - stream, err = cl.client.client.GatewayDownlink(cl.Context()) - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") - return nil, err - } - cl.Ctx.Debug("Opened new monitor downlink stream") - - cl.downlink.stream = stream - return stream, nil -} - -func (cl *gatewayClient) closeStatus() (err error) { - err = cl.status.stream.CloseSend() - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close status stream") - } - cl.Ctx.Debug("Closed status stream") - - return err -} -func (cl *gatewayClient) closeUplink() (err error) { - err = cl.uplink.stream.CloseSend() - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close uplink stream") - } - cl.Ctx.Debug("Closed uplink stream") - - return err -} -func (cl *gatewayClient) closeDownlink() (err error) { - err = cl.downlink.stream.CloseSend() - if err != nil { - cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to close downlink stream") - } - cl.Ctx.Debug("Closed downlink stream") - - return err -} diff --git a/api/monitor/client_test.go b/api/monitor/client_test.go new file mode 100644 index 000000000..f09a888a4 --- /dev/null +++ b/api/monitor/client_test.go @@ -0,0 +1,148 @@ +package monitor + +import ( + "fmt" + "math/rand" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" +) + +func TestClient(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "Monitor Client") + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go startExampleServer(2, port) + + { + client, err := NewClient(ctx, fmt.Sprintf(":%d", port)) + a.So(err, ShouldBeNil) + a.So(client.IsConnected(), ShouldBeTrue) + a.So(client.Reopen(), ShouldBeNil) + a.So(client.IsConnected(), ShouldBeTrue) + a.So(client.Close(), ShouldBeNil) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + gtw := client.GatewayClient("dev") + a.So(gtw.IsConfigured(), ShouldBeFalse) + gtw.SetToken("SOME.AWESOME.JWT") + a.So(gtw.IsConfigured(), ShouldBeTrue) + + ctx := gtw.(*gatewayClient).Context() + id, _ := api.IDFromContext(ctx) + a.So(id, ShouldEqual, "dev") + token, _ := api.TokenFromContext(ctx) + a.So(token, ShouldEqual, "SOME.AWESOME.JWT") + + a.So(client.Close(), ShouldBeNil) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + defer client.Close() + gtw := client.GatewayClient("dev") + + err := gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldBeNil) + + gtw.SetToken("SOME.AWESOME.JWT") + + // The first two statuses are OK + for i := 0; i < 2; i++ { + err = gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldBeNil) + } + + // The next one will cause an error on the test server + err = gtw.SendStatus(&gateway.Status{}) + time.Sleep(10 * time.Millisecond) + + // Then, we are going to buffer 10 statuses locally + for i := 0; i < 10; i++ { + err = gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldBeNil) + } + + // After which statuses will get dropped + err = gtw.SendStatus(&gateway.Status{}) + a.So(err, ShouldNotBeNil) + + time.Sleep(100 * time.Millisecond) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + defer client.Close() + gtw := client.GatewayClient("dev") + + err := gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldBeNil) + + gtw.SetToken("SOME.AWESOME.JWT") + + // The first two messages are OK + for i := 0; i < 2; i++ { + err = gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldBeNil) + } + + // The next one will cause an error on the test server + err = gtw.SendUplink(&router.UplinkMessage{}) + time.Sleep(10 * time.Millisecond) + + // Then, we are going to buffer 10 messages locally + for i := 0; i < 10; i++ { + err = gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldBeNil) + } + + // After which messages will get dropped + err = gtw.SendUplink(&router.UplinkMessage{}) + a.So(err, ShouldNotBeNil) + + time.Sleep(100 * time.Millisecond) + } + + { + client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + defer client.Close() + gtw := client.GatewayClient("dev") + + err := gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldBeNil) + + gtw.SetToken("SOME.AWESOME.JWT") + + // The first two messages are OK + for i := 0; i < 2; i++ { + err = gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldBeNil) + } + + // The next one will cause an error on the test server + err = gtw.SendDownlink(&router.DownlinkMessage{}) + time.Sleep(10 * time.Millisecond) + + // Then, we are going to buffer 10 messages locally + for i := 0; i < 10; i++ { + err = gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldBeNil) + } + + // After which messages will get dropped + err = gtw.SendDownlink(&router.DownlinkMessage{}) + a.So(err, ShouldNotBeNil) + + time.Sleep(100 * time.Millisecond) + } + +} diff --git a/api/monitor/downlink.go b/api/monitor/downlink.go new file mode 100644 index 000000000..f459c27ac --- /dev/null +++ b/api/monitor/downlink.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" +) + +func (cl *gatewayClient) initDownlink() { + cl.downlink.ch = make(chan *router.DownlinkMessage, BufferSize) + go cl.monitorDownlink() +} + +func (cl *gatewayClient) monitorDownlink() { + var retries int +newStream: + for { + ctx, cancel := context.WithCancel(cl.Context()) + cl.downlink.Lock() + cl.downlink.cancel = cancel + cl.downlink.Unlock() + + stream, err := cl.client.client.GatewayDownlink(ctx) + if err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor downlink stream") + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue + } + retries = 0 + cl.Ctx.Debug("Opened new monitor downlink stream") + + // The actual stream + go func() { + for { + select { + case <-ctx.Done(): + return + case downlink, ok := <-cl.downlink.ch: + if ok { + stream.Send(downlink) + cl.Ctx.Debug("Sent downlink to monitor") + } + } + } + }() + + var msg []byte + for { + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor downlink stream, closing...") + stream.CloseSend() + cl.Ctx.Debug("Closed monitor downlink stream") + + cl.downlink.Lock() + cl.downlink.cancel() + cl.downlink.cancel = nil + cl.downlink.Unlock() + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue newStream + } + } + } +} + +func (cl *gatewayClient) closeDownlink() { + cl.downlink.Lock() + defer cl.downlink.Unlock() + if cl.downlink.cancel != nil { + cl.downlink.cancel() + } +} + +// SendDownlink sends downlink to the monitor +func (cl *gatewayClient) SendDownlink(downlink *router.DownlinkMessage) (err error) { + if !cl.IsConfigured() { + return nil + } + + cl.downlink.init.Do(cl.initDownlink) + + select { + case cl.downlink.ch <- downlink: + default: + cl.Ctx.Warn("Not sending downlink to monitor, buffer full") + return errors.New("Not sending downlink to monitor, buffer full") + } + return +} diff --git a/api/monitor/example_server.go b/api/monitor/example_server.go new file mode 100644 index 000000000..4d20adf2a --- /dev/null +++ b/api/monitor/example_server.go @@ -0,0 +1,114 @@ +package monitor + +import ( + "fmt" + "io" + "net" + + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +var errNotImplemented = grpc.Errorf(codes.Unimplemented, "That's not implemented yet") +var errBufferFull = grpc.Errorf(codes.ResourceExhausted, "Take it easy, dude! My buffers are full") + +func newExampleServer(channelSize int) *exampleServer { + return &exampleServer{ + gatewayStatuses: make(chan *gateway.Status, channelSize), + uplinkMessages: make(chan *router.UplinkMessage, channelSize), + downlinkMessages: make(chan *router.DownlinkMessage, channelSize), + } +} + +type exampleServer struct { + gatewayStatuses chan *gateway.Status + uplinkMessages chan *router.UplinkMessage + downlinkMessages chan *router.DownlinkMessage +} + +func (s *exampleServer) GatewayStatus(stream Monitor_GatewayStatusServer) error { + for { + status, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + select { + case s.gatewayStatuses <- status: + fmt.Println("Saving gateway status to database and doing something cool") + default: + fmt.Println("Warning: Dropping gateway status [full buffer]") + return errBufferFull + } + } +} + +func (s *exampleServer) GatewayUplink(stream Monitor_GatewayUplinkServer) error { + for { + uplink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + select { + case s.uplinkMessages <- uplink: + fmt.Println("Saving uplink to database and doing something cool") + default: + fmt.Println("Warning: Dropping uplink [full buffer]") + return errBufferFull + } + } +} + +func (s *exampleServer) GatewayDownlink(stream Monitor_GatewayDownlinkServer) error { + for { + downlink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + select { + case s.downlinkMessages <- downlink: + fmt.Println("Saving downlink to database and doing something cool") + default: + fmt.Println("Warning: Dropping downlink [full buffer]") + return errBufferFull + } + } +} + +func (s *exampleServer) RouterStatus(stream Monitor_RouterStatusServer) error { + return errNotImplemented +} + +func (s *exampleServer) BrokerStatus(stream Monitor_BrokerStatusServer) error { + return errNotImplemented +} + +func (s *exampleServer) HandlerStatus(stream Monitor_HandlerStatusServer) error { + return errNotImplemented +} + +func (s *exampleServer) Serve(port int) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + srv := grpc.NewServer() + RegisterMonitorServer(srv, s) + srv.Serve(lis) +} + +func startExampleServer(channelSize, port int) { + s := newExampleServer(channelSize) + s.Serve(port) +} diff --git a/api/monitor/gateway_status.go b/api/monitor/gateway_status.go new file mode 100644 index 000000000..f22a5b260 --- /dev/null +++ b/api/monitor/gateway_status.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" +) + +func (cl *gatewayClient) initStatus() { + cl.status.ch = make(chan *gateway.Status, BufferSize) + go cl.monitorStatus() +} + +func (cl *gatewayClient) monitorStatus() { + var retries int +newStream: + for { + ctx, cancel := context.WithCancel(cl.Context()) + cl.status.Lock() + cl.status.cancel = cancel + cl.status.Unlock() + + stream, err := cl.client.client.GatewayStatus(ctx) + if err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor status stream") + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue + } + retries = 0 + cl.Ctx.Debug("Opened new monitor status stream") + + // The actual stream + go func() { + for { + select { + case <-ctx.Done(): + return + case status, ok := <-cl.status.ch: + if ok { + stream.Send(status) + cl.Ctx.Debug("Sent status to monitor") + } + } + } + }() + + var msg []byte + for { + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor status stream, closing...") + stream.CloseSend() + cl.Ctx.Debug("Closed monitor status stream") + + cl.status.Lock() + cl.status.cancel() + cl.status.cancel = nil + cl.status.Unlock() + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue newStream + } + } + } +} + +func (cl *gatewayClient) closeStatus() { + cl.status.Lock() + defer cl.status.Unlock() + if cl.status.cancel != nil { + cl.status.cancel() + } +} + +// SendStatus sends status to the monitor +func (cl *gatewayClient) SendStatus(status *gateway.Status) (err error) { + if !cl.IsConfigured() { + return nil + } + + cl.status.init.Do(cl.initStatus) + + select { + case cl.status.ch <- status: + default: + cl.Ctx.Warn("Not sending status to monitor, buffer full") + return errors.New("Not sending status to monitor, buffer full") + } + return +} diff --git a/api/monitor/uplink.go b/api/monitor/uplink.go new file mode 100644 index 000000000..afbb9bdf2 --- /dev/null +++ b/api/monitor/uplink.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package monitor + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" +) + +func (cl *gatewayClient) initUplink() { + cl.uplink.ch = make(chan *router.UplinkMessage, BufferSize) + go cl.monitorUplink() +} + +func (cl *gatewayClient) monitorUplink() { + var retries int +newStream: + for { + ctx, cancel := context.WithCancel(cl.Context()) + cl.uplink.Lock() + cl.uplink.cancel = cancel + cl.uplink.Unlock() + + stream, err := cl.client.client.GatewayUplink(ctx) + if err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to open new monitor uplink stream") + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue + } + retries = 0 + cl.Ctx.Debug("Opened new monitor uplink stream") + + // The actual stream + go func() { + for { + select { + case <-ctx.Done(): + return + case uplink, ok := <-cl.uplink.ch: + if ok { + stream.Send(uplink) + cl.Ctx.Debug("Sent uplink to monitor") + } + } + } + }() + + var msg []byte + for { + if err := stream.RecvMsg(&msg); err != nil { + cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor uplink stream, closing...") + stream.CloseSend() + cl.Ctx.Debug("Closed monitor uplink stream") + + cl.uplink.Lock() + cl.uplink.cancel() + cl.uplink.cancel = nil + cl.uplink.Unlock() + + retries++ + time.Sleep(backoff.Backoff(retries)) + + continue newStream + } + } + } +} + +func (cl *gatewayClient) closeUplink() { + cl.uplink.Lock() + defer cl.uplink.Unlock() + if cl.uplink.cancel != nil { + cl.uplink.cancel() + } +} + +// SendUplink sends uplink to the monitor +func (cl *gatewayClient) SendUplink(uplink *router.UplinkMessage) (err error) { + if !cl.IsConfigured() { + return nil + } + + cl.uplink.init.Do(cl.initUplink) + + select { + case cl.uplink.ch <- uplink: + default: + cl.Ctx.Warn("Not sending uplink to monitor, buffer full") + return errors.New("Not sending uplink to monitor, buffer full") + } + return +} diff --git a/utils/backoff/backoff.go b/utils/backoff/backoff.go new file mode 100644 index 000000000..5fe896743 --- /dev/null +++ b/utils/backoff/backoff.go @@ -0,0 +1,60 @@ +// Package backoff is a slightly changed version of the backoff algorithm that comes with gRPC +// See: vendor/google.golang.org/grpc/LICENSE for the license +package backoff + +import ( + "math/rand" + "time" +) + +// DefaultConfig is the default backoff configuration +var ( + DefaultConfig = Config{ + MaxDelay: 120 * time.Second, + BaseDelay: 1.0 * time.Second, + Factor: 1.6, + Jitter: 0.2, + } +) + +// Config defines the parameters for backoff +type Config struct { + // MaxDelay is the upper bound of backoff delay. + MaxDelay time.Duration + + // BaseDelay is the amount of time to wait before retrying after the first failure. + BaseDelay time.Duration + + // factor is applied to the backoff after each retry. + Factor float64 + + // jitter provides a range to randomize backoff delays. + Jitter float64 +} + +// Backoff returns the delay for the current amount of retries +func (bc Config) Backoff(retries int) time.Duration { + if retries == 0 { + return bc.BaseDelay + } + backoff, max := float64(bc.BaseDelay), float64(bc.MaxDelay) + for backoff < max && retries > 0 { + backoff *= bc.Factor + retries-- + } + if backoff > max { + backoff = max + } + // Randomize backoff delays so that if a cluster of requests start at + // the same time, they won't operate in lockstep. + backoff *= 1 + bc.Jitter*(rand.Float64()*2-1) + if backoff < 0 { + return 0 + } + return time.Duration(backoff) +} + +// Backoff returns the delay for the current amount of retries +func Backoff(retries int) time.Duration { + return DefaultConfig.Backoff(retries) +} From 5daf3fa3a30c59e8f2a1d848f1858399eaff70f5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 3 Nov 2016 22:01:00 +0100 Subject: [PATCH 2107/2266] Add license header to monitor client files --- api/monitor/client.go | 3 +++ api/monitor/client_test.go | 3 +++ api/monitor/example_server.go | 3 +++ 3 files changed, 9 insertions(+) diff --git a/api/monitor/client.go b/api/monitor/client.go index 7040e9b40..05eba7f6d 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package monitor import ( diff --git a/api/monitor/client_test.go b/api/monitor/client_test.go index f09a888a4..23df43234 100644 --- a/api/monitor/client_test.go +++ b/api/monitor/client_test.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package monitor import ( diff --git a/api/monitor/example_server.go b/api/monitor/example_server.go index 4d20adf2a..353ce0540 100644 --- a/api/monitor/example_server.go +++ b/api/monitor/example_server.go @@ -1,3 +1,6 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + package monitor import ( From e24ac009bdc1f94e32a35b7b3bbee821877a5f8d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Nov 2016 12:52:15 +0100 Subject: [PATCH 2108/2266] Fix ActivationMetadata validation Uplink ActivationMetadata does not have DevAddr and NwkSKey --- api/protocol/lorawan/validation.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index f8ca971de..6b2a9c55f 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -74,10 +74,10 @@ func (m *ActivationMetadata) Validate() bool { if m.DevEui == nil || m.DevEui.IsEmpty() { return false } - if m.DevAddr == nil || m.DevAddr.IsEmpty() { + if m.DevAddr != nil && m.DevAddr.IsEmpty() { return false } - if m.NwkSKey == nil || m.NwkSKey.IsEmpty() { + if m.NwkSKey != nil && m.NwkSKey.IsEmpty() { return false } return true From 9791d6447db822d43c290fb8cb6ac37af50f6166 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Nov 2016 15:50:50 +0100 Subject: [PATCH 2109/2266] Set JoinAccept dataPayload bytes to Encrypted --- api/protocol/lorawan/message_conversion.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/protocol/lorawan/message_conversion.go b/api/protocol/lorawan/message_conversion.go index 253609a7b..3c52706e6 100644 --- a/api/protocol/lorawan/message_conversion.go +++ b/api/protocol/lorawan/message_conversion.go @@ -135,6 +135,7 @@ func (msg *Message_JoinAcceptPayload) Payload() lorawan.Payload { // JoinAcceptPayloadFromPayload creates a new JoinAcceptPayload from a lorawan.Payload func JoinAcceptPayloadFromPayload(payload lorawan.Payload) (accept JoinAcceptPayload) { if dataPayload, ok := payload.(*lorawan.DataPayload); ok { + accept.Encrypted = dataPayload.Bytes joinAccept := &lorawan.JoinAcceptPayload{} joinAccept.UnmarshalBinary(false, dataPayload.Bytes) payload = joinAccept @@ -153,9 +154,6 @@ func JoinAcceptPayloadFromPayload(payload lorawan.Payload) (accept JoinAcceptPay } } } - if encrypted, ok := payload.(*lorawan.DataPayload); ok { - accept.Encrypted = encrypted.Bytes - } return } From 3b8c2e438c62ee4cbf761819bb82776a450ad428 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Nov 2016 16:43:54 +0100 Subject: [PATCH 2110/2266] Inject Gateway GPS into uplink metadata --- core/router/activation.go | 6 +++--- core/router/gateway/gateway.go | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/core/router/activation.go b/core/router/activation.go index 803fb672a..8af828e80 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -40,9 +40,9 @@ func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivat GatewayMetadata: activation.GatewayMetadata, } - // Only for LoRaWAN - gateway.Schedule.Sync(uplink.GatewayMetadata.Timestamp) - gateway.Utilization.AddRx(uplink) + if err = gateway.HandleUplink(uplink); err != nil { + return nil, err + } if !gateway.Schedule.IsActive() { return nil, errors.NewErrInternal(fmt.Sprintf("Gateway %s not available for downlink", gatewayID)) diff --git a/core/router/gateway/gateway.go b/core/router/gateway/gateway.go index f36b6509c..90257177d 100644 --- a/core/router/gateway/gateway.go +++ b/core/router/gateway/gateway.go @@ -74,6 +74,13 @@ func (g *Gateway) HandleUplink(uplink *pb_router.UplinkMessage) (err error) { g.Schedule.Sync(uplink.GatewayMetadata.Timestamp) g.updateLastSeen() + // Inject Gateway location + if uplink.GatewayMetadata.Gps == nil { + if status, err := g.Status.Get(); err == nil { + uplink.GatewayMetadata.Gps = status.GetGps() + } + } + if g.Monitors != nil { for _, monitor := range g.Monitors { go monitor.SendUplink(uplink) From 20854c806841096a9798be3096973a7cb42e6f2a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Nov 2016 16:46:29 +0100 Subject: [PATCH 2111/2266] Add link to MQTT plugin in README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 525656402..7a0cb8c14 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed. 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. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) with the MQTT plugin **installed** and **running**. +4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) with the [MQTT plugin](https://www.rabbitmq.com/mqtt.html) **installed** and **running**. If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. Windows users are currently on their own (feel free to contribute a guide for Windows). 5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` From 2035725ca9a86b8894d27cb0d1a268be4859182e Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Fri, 4 Nov 2016 16:54:18 +0100 Subject: [PATCH 2112/2266] Add windows specific info for RabbitMQ --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a0cb8c14..011a9a1fa 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,9 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed. 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. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) with the [MQTT plugin](https://www.rabbitmq.com/mqtt.html) **installed** and **running**. - If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. Windows users are currently on their own (feel free to contribute a guide for Windows). +4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. + On a fresh installation you might need to install the [MQTT plugin](https://www.rabbitmq.com/mqtt.html). + If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. 5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` ## Set up The Things Network's backend for Development From 348063da18fd9396bb0de1595fb4278c9c5a0a24 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 4 Nov 2016 17:26:00 +0100 Subject: [PATCH 2113/2266] Change order of Location metadata --- core/types/location_metadata.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/types/location_metadata.go b/core/types/location_metadata.go index 7f3c6a691..f522a72c8 100644 --- a/core/types/location_metadata.go +++ b/core/types/location_metadata.go @@ -5,7 +5,7 @@ package types // LocationMetadata contains GPS coordinates type LocationMetadata struct { - Altitude int32 `json:"altitude,omitempty"` - Longitude float32 `json:"longitude,omitempty"` Latitude float32 `json:"latitude,omitempty"` + Longitude float32 `json:"longitude,omitempty"` + Altitude int32 `json:"altitude,omitempty"` } From 5761810e5c5a88958d48f02cd15b071c52246893 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Mon, 7 Nov 2016 08:24:46 +0100 Subject: [PATCH 2114/2266] Make everything run on windows (#345) * Add 'make dev' to readme, while letting 'make link' optional for linux / mac * Check-in networkserver.cert to replace the symlink for windows users --- .env/broker/networkserver.cert | 12 +++++++++++- README.md | 3 ++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.env/broker/networkserver.cert b/.env/broker/networkserver.cert index b425d24e6..b04453e85 120000 --- a/.env/broker/networkserver.cert +++ b/.env/broker/networkserver.cert @@ -1 +1,11 @@ -../networkserver/server.cert \ No newline at end of file +-----BEGIN CERTIFICATE----- +MIIBljCCATygAwIBAgIRAOs3B7qZNgejMf+laNHTRuYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIxNVoXDTE3 +MDcyOTEyMTIxNVowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx +3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMqNdMFswDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +IwYDVR0RBBwwGoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVyMAoGCCqGSM49BAMC +A0gAMEUCIBrRl5a2PX+fn68Uefq15Cn1C1XE6NGVmI+HvmP1sA1JAiEA0L4WgKdo +HcUc8PnKlUUgN9nLVx98W9Sb2TvOaldspVE= +-----END CERTIFICATE----- diff --git a/README.md b/README.md index 011a9a1fa..410a79cc3 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,8 @@ When you get started with The Things Network, you'll probably have some question 4. Install the dependencies for development: `make dev-deps` 5. Run the tests: `make test` 6. Run `make build` to build both `ttn` and `ttnctl` from source. -7. Run `make link` to link them to `$GOPATH/bin/` (In order to run the commands, you should have `export PATH="$GOPATH/bin:$PATH"` in your profile). +7. Run `make dev` to install the go binaries into `$GOPATH/bin/` + * Optionally on Linux or Mac you can use `make link` to link them to `$GOPATH/bin/` (In order to run the commands, you should have `export PATH="$GOPATH/bin:$PATH"` in your profile). 8. Configure your `ttnctl` with the settings in `.env/ttnctl.yml.dev-example` by copying that file to `~/.ttnctl.yml`. You can check your `ttnctl` configuration by running `ttnctl config`. It should look like this: From 5b3de44009c11eb6d675a377c7ac9f1bca6da9da Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 08:25:42 +0100 Subject: [PATCH 2115/2266] Fix typo introduced in PR#344 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 410a79cc3..9dbb89748 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ When you get started with The Things Network, you'll probably have some question 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) 4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. On a fresh installation you might need to install the [MQTT plugin](https://www.rabbitmq.com/mqtt.html). - If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. + If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabbitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. 5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` ## Set up The Things Network's backend for Development From 49dbe3d45619cf01bd5f793973ac8cd89c7ca5be Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 09:25:46 +0100 Subject: [PATCH 2116/2266] Correct type for networkserver.cert --- .env/broker/networkserver.cert | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 120000 => 100644 .env/broker/networkserver.cert diff --git a/.env/broker/networkserver.cert b/.env/broker/networkserver.cert deleted file mode 120000 index b04453e85..000000000 --- a/.env/broker/networkserver.cert +++ /dev/null @@ -1,11 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBljCCATygAwIBAgIRAOs3B7qZNgejMf+laNHTRuYwCgYIKoZIzj0EAwIwHTEb -MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIxNVoXDTE3 -MDcyOTEyMTIxNVowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH -KoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx -3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMqNdMFswDgYDVR0P -AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w -IwYDVR0RBBwwGoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVyMAoGCCqGSM49BAMC -A0gAMEUCIBrRl5a2PX+fn68Uefq15Cn1C1XE6NGVmI+HvmP1sA1JAiEA0L4WgKdo -HcUc8PnKlUUgN9nLVx98W9Sb2TvOaldspVE= ------END CERTIFICATE----- diff --git a/.env/broker/networkserver.cert b/.env/broker/networkserver.cert new file mode 100644 index 000000000..b04453e85 --- /dev/null +++ b/.env/broker/networkserver.cert @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBljCCATygAwIBAgIRAOs3B7qZNgejMf+laNHTRuYwCgYIKoZIzj0EAwIwHTEb +MBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMB4XDTE2MDcyOTEyMTIxNVoXDTE3 +MDcyOTEyMTIxNVowHTEbMBkGA1UEChMSVGhlIFRoaW5ncyBOZXR3b3JrMFkwEwYH +KoZIzj0CAQYIKoZIzj0DAQcDQgAEsfIDb4Va9ocbwGuc375Bxw5ICTCXZ60mbgdx +3JSyWm19DW5dihzPFrB0Ezu+lak91rTEaon9WNcVibhFNG5wMqNdMFswDgYDVR0P +AQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQFMAMBAf8w +IwYDVR0RBBwwGoIJbG9jYWxob3N0gg1uZXR3b3Jrc2VydmVyMAoGCCqGSM49BAMC +A0gAMEUCIBrRl5a2PX+fn68Uefq15Cn1C1XE6NGVmI+HvmP1sA1JAiEA0L4WgKdo +HcUc8PnKlUUgN9nLVx98W9Sb2TvOaldspVE= +-----END CERTIFICATE----- From c874fe9cee7b6a345c62f8fa146c04b445ec8865 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 10:19:29 +0100 Subject: [PATCH 2117/2266] Update vendors And re-generate protos left out github.com/TheThingsNetwork/go-account-lib. @romeovs will update that. --- api/api.pb.go | 560 +++++------ api/broker/broker.pb.go | 1044 ++++++++++----------- api/discovery/discovery.pb.go | 348 +++---- api/gateway/gateway.pb.go | 406 ++++---- api/handler/handler.pb.go | 648 ++++++------- api/monitor/monitor.pb.go | 4 +- api/networkserver/networkserver.pb.go | 214 ++--- api/protocol/lorawan/device.pb.go | 234 ++--- api/protocol/lorawan/device_address.pb.go | 196 ++-- api/protocol/lorawan/lorawan.pb.go | 732 +++++++-------- api/protocol/protocol.pb.go | 176 ++-- api/router/router.pb.go | 474 +++++----- vendor/vendor.json | 340 ++++--- 13 files changed, 2703 insertions(+), 2673 deletions(-) diff --git a/api/api.pb.go b/api/api.pb.go index 7f952e89a..1c96c60c9 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -184,142 +184,142 @@ func init() { proto.RegisterType((*ComponentStats_CPUStats)(nil), "api.ComponentStats.CPUStats") proto.RegisterType((*ComponentStats_MemoryStats)(nil), "api.ComponentStats.MemoryStats") } -func (m *Percentiles) Marshal() (data []byte, err error) { +func (m *Percentiles) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Percentiles) MarshalTo(data []byte) (int, error) { +func (m *Percentiles) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Percentile1 != 0 { - data[i] = 0xd + dAtA[i] = 0xd i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile1)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile1)))) } if m.Percentile5 != 0 { - data[i] = 0x15 + dAtA[i] = 0x15 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile5)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile5)))) } if m.Percentile10 != 0 { - data[i] = 0x1d + dAtA[i] = 0x1d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile10)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile10)))) } if m.Percentile25 != 0 { - data[i] = 0x25 + dAtA[i] = 0x25 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile25)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile25)))) } if m.Percentile50 != 0 { - data[i] = 0x2d + dAtA[i] = 0x2d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile50)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile50)))) } if m.Percentile75 != 0 { - data[i] = 0x35 + dAtA[i] = 0x35 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile75)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile75)))) } if m.Percentile90 != 0 { - data[i] = 0x3d + dAtA[i] = 0x3d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile90)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile90)))) } if m.Percentile95 != 0 { - data[i] = 0x45 + dAtA[i] = 0x45 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile95)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile95)))) } if m.Percentile99 != 0 { - data[i] = 0x4d + dAtA[i] = 0x4d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Percentile99)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Percentile99)))) } return i, nil } -func (m *Rates) Marshal() (data []byte, err error) { +func (m *Rates) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Rates) MarshalTo(data []byte) (int, error) { +func (m *Rates) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Rate1 != 0 { - data[i] = 0xd + dAtA[i] = 0xd i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Rate1)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Rate1)))) } if m.Rate5 != 0 { - data[i] = 0x15 + dAtA[i] = 0x15 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Rate5)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Rate5)))) } if m.Rate15 != 0 { - data[i] = 0x1d + dAtA[i] = 0x1d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Rate15)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Rate15)))) } return i, nil } -func (m *SystemStats) Marshal() (data []byte, err error) { +func (m *SystemStats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *SystemStats) MarshalTo(data []byte) (int, error) { +func (m *SystemStats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Load != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintApi(data, i, uint64(m.Load.Size())) - n1, err := m.Load.MarshalTo(data[i:]) + i = encodeVarintApi(dAtA, i, uint64(m.Load.Size())) + n1, err := m.Load.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.Cpu != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintApi(data, i, uint64(m.Cpu.Size())) - n2, err := m.Cpu.MarshalTo(data[i:]) + i = encodeVarintApi(dAtA, i, uint64(m.Cpu.Size())) + n2, err := m.Cpu.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n2 } if m.Memory != nil { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintApi(data, i, uint64(m.Memory.Size())) - n3, err := m.Memory.MarshalTo(data[i:]) + i = encodeVarintApi(dAtA, i, uint64(m.Memory.Size())) + n3, err := m.Memory.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -328,239 +328,239 @@ func (m *SystemStats) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SystemStats_Loadstats) Marshal() (data []byte, err error) { +func (m *SystemStats_Loadstats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *SystemStats_Loadstats) MarshalTo(data []byte) (int, error) { +func (m *SystemStats_Loadstats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Load1 != 0 { - data[i] = 0xd + dAtA[i] = 0xd i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Load1)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Load1)))) } if m.Load5 != 0 { - data[i] = 0x15 + dAtA[i] = 0x15 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Load5)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Load5)))) } if m.Load15 != 0 { - data[i] = 0x1d + dAtA[i] = 0x1d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Load15)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Load15)))) } return i, nil } -func (m *SystemStats_CPUStats) Marshal() (data []byte, err error) { +func (m *SystemStats_CPUStats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *SystemStats_CPUStats) MarshalTo(data []byte) (int, error) { +func (m *SystemStats_CPUStats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.User != 0 { - data[i] = 0xd + dAtA[i] = 0xd i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.User)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.User)))) } if m.System != 0 { - data[i] = 0x15 + dAtA[i] = 0x15 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.System)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.System)))) } if m.Idle != 0 { - data[i] = 0x1d + dAtA[i] = 0x1d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Idle)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Idle)))) } return i, nil } -func (m *SystemStats_MemoryStats) Marshal() (data []byte, err error) { +func (m *SystemStats_MemoryStats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *SystemStats_MemoryStats) MarshalTo(data []byte) (int, error) { +func (m *SystemStats_MemoryStats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Total != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintApi(data, i, uint64(m.Total)) + i = encodeVarintApi(dAtA, i, uint64(m.Total)) } if m.Available != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintApi(data, i, uint64(m.Available)) + i = encodeVarintApi(dAtA, i, uint64(m.Available)) } if m.Used != 0 { - data[i] = 0x18 + dAtA[i] = 0x18 i++ - i = encodeVarintApi(data, i, uint64(m.Used)) + i = encodeVarintApi(dAtA, i, uint64(m.Used)) } return i, nil } -func (m *ComponentStats) Marshal() (data []byte, err error) { +func (m *ComponentStats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ComponentStats) MarshalTo(data []byte) (int, error) { +func (m *ComponentStats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Cpu != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintApi(data, i, uint64(m.Cpu.Size())) - n4, err := m.Cpu.MarshalTo(data[i:]) + i = encodeVarintApi(dAtA, i, uint64(m.Cpu.Size())) + n4, err := m.Cpu.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if m.Memory != nil { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintApi(data, i, uint64(m.Memory.Size())) - n5, err := m.Memory.MarshalTo(data[i:]) + i = encodeVarintApi(dAtA, i, uint64(m.Memory.Size())) + n5, err := m.Memory.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n5 } if m.Goroutines != 0 { - data[i] = 0x20 + dAtA[i] = 0x20 i++ - i = encodeVarintApi(data, i, uint64(m.Goroutines)) + i = encodeVarintApi(dAtA, i, uint64(m.Goroutines)) } if m.GcCpuFraction != 0 { - data[i] = 0x2d + dAtA[i] = 0x2d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.GcCpuFraction)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.GcCpuFraction)))) } return i, nil } -func (m *ComponentStats_CPUStats) Marshal() (data []byte, err error) { +func (m *ComponentStats_CPUStats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ComponentStats_CPUStats) MarshalTo(data []byte) (int, error) { +func (m *ComponentStats_CPUStats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.User != 0 { - data[i] = 0xd + dAtA[i] = 0xd i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.User)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.User)))) } if m.System != 0 { - data[i] = 0x15 + dAtA[i] = 0x15 i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.System)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.System)))) } if m.Idle != 0 { - data[i] = 0x1d + dAtA[i] = 0x1d i++ - i = encodeFixed32Api(data, i, uint32(math.Float32bits(float32(m.Idle)))) + i = encodeFixed32Api(dAtA, i, uint32(math.Float32bits(float32(m.Idle)))) } return i, nil } -func (m *ComponentStats_MemoryStats) Marshal() (data []byte, err error) { +func (m *ComponentStats_MemoryStats) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ComponentStats_MemoryStats) MarshalTo(data []byte) (int, error) { +func (m *ComponentStats_MemoryStats) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Memory != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintApi(data, i, uint64(m.Memory)) + i = encodeVarintApi(dAtA, i, uint64(m.Memory)) } if m.Swap != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintApi(data, i, uint64(m.Swap)) + i = encodeVarintApi(dAtA, i, uint64(m.Swap)) } return i, nil } -func encodeFixed64Api(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Api(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Api(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Api(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintApi(data []byte, offset int, v uint64) int { +func encodeVarintApi(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *Percentiles) Size() (n int) { @@ -734,8 +734,8 @@ func sovApi(x uint64) (n int) { func sozApi(x uint64) (n int) { return sovApi(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Percentiles) Unmarshal(data []byte) error { - l := len(data) +func (m *Percentiles) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -747,7 +747,7 @@ func (m *Percentiles) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -772,10 +772,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile1 = float32(math.Float32frombits(v)) case 2: if wireType != 5 { @@ -786,10 +786,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile5 = float32(math.Float32frombits(v)) case 3: if wireType != 5 { @@ -800,10 +800,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile10 = float32(math.Float32frombits(v)) case 4: if wireType != 5 { @@ -814,10 +814,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile25 = float32(math.Float32frombits(v)) case 5: if wireType != 5 { @@ -828,10 +828,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile50 = float32(math.Float32frombits(v)) case 6: if wireType != 5 { @@ -842,10 +842,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile75 = float32(math.Float32frombits(v)) case 7: if wireType != 5 { @@ -856,10 +856,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile90 = float32(math.Float32frombits(v)) case 8: if wireType != 5 { @@ -870,10 +870,10 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile95 = float32(math.Float32frombits(v)) case 9: if wireType != 5 { @@ -884,14 +884,14 @@ func (m *Percentiles) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Percentile99 = float32(math.Float32frombits(v)) default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -910,8 +910,8 @@ func (m *Percentiles) Unmarshal(data []byte) error { } return nil } -func (m *Rates) Unmarshal(data []byte) error { - l := len(data) +func (m *Rates) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -923,7 +923,7 @@ func (m *Rates) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -948,10 +948,10 @@ func (m *Rates) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Rate1 = float32(math.Float32frombits(v)) case 2: if wireType != 5 { @@ -962,10 +962,10 @@ func (m *Rates) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Rate5 = float32(math.Float32frombits(v)) case 3: if wireType != 5 { @@ -976,14 +976,14 @@ func (m *Rates) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Rate15 = float32(math.Float32frombits(v)) default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1002,8 +1002,8 @@ func (m *Rates) Unmarshal(data []byte) error { } return nil } -func (m *SystemStats) Unmarshal(data []byte) error { - l := len(data) +func (m *SystemStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1015,7 +1015,7 @@ func (m *SystemStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1043,7 +1043,7 @@ func (m *SystemStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1060,7 +1060,7 @@ func (m *SystemStats) Unmarshal(data []byte) error { if m.Load == nil { m.Load = &SystemStats_Loadstats{} } - if err := m.Load.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Load.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1076,7 +1076,7 @@ func (m *SystemStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1093,7 +1093,7 @@ func (m *SystemStats) Unmarshal(data []byte) error { if m.Cpu == nil { m.Cpu = &SystemStats_CPUStats{} } - if err := m.Cpu.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Cpu.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1109,7 +1109,7 @@ func (m *SystemStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1126,13 +1126,13 @@ func (m *SystemStats) Unmarshal(data []byte) error { if m.Memory == nil { m.Memory = &SystemStats_MemoryStats{} } - if err := m.Memory.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Memory.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1151,8 +1151,8 @@ func (m *SystemStats) Unmarshal(data []byte) error { } return nil } -func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { - l := len(data) +func (m *SystemStats_Loadstats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1164,7 +1164,7 @@ func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1189,10 +1189,10 @@ func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Load1 = float32(math.Float32frombits(v)) case 2: if wireType != 5 { @@ -1203,10 +1203,10 @@ func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Load5 = float32(math.Float32frombits(v)) case 3: if wireType != 5 { @@ -1217,14 +1217,14 @@ func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Load15 = float32(math.Float32frombits(v)) default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1243,8 +1243,8 @@ func (m *SystemStats_Loadstats) Unmarshal(data []byte) error { } return nil } -func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { - l := len(data) +func (m *SystemStats_CPUStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1256,7 +1256,7 @@ func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1281,10 +1281,10 @@ func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.User = float32(math.Float32frombits(v)) case 2: if wireType != 5 { @@ -1295,10 +1295,10 @@ func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.System = float32(math.Float32frombits(v)) case 3: if wireType != 5 { @@ -1309,14 +1309,14 @@ func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Idle = float32(math.Float32frombits(v)) default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1335,8 +1335,8 @@ func (m *SystemStats_CPUStats) Unmarshal(data []byte) error { } return nil } -func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { - l := len(data) +func (m *SystemStats_MemoryStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1348,7 +1348,7 @@ func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1376,7 +1376,7 @@ func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Total |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1395,7 +1395,7 @@ func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Available |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1414,7 +1414,7 @@ func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Used |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1423,7 +1423,7 @@ func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1442,8 +1442,8 @@ func (m *SystemStats_MemoryStats) Unmarshal(data []byte) error { } return nil } -func (m *ComponentStats) Unmarshal(data []byte) error { - l := len(data) +func (m *ComponentStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1455,7 +1455,7 @@ func (m *ComponentStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1483,7 +1483,7 @@ func (m *ComponentStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1500,7 +1500,7 @@ func (m *ComponentStats) Unmarshal(data []byte) error { if m.Cpu == nil { m.Cpu = &ComponentStats_CPUStats{} } - if err := m.Cpu.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Cpu.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1516,7 +1516,7 @@ func (m *ComponentStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1533,7 +1533,7 @@ func (m *ComponentStats) Unmarshal(data []byte) error { if m.Memory == nil { m.Memory = &ComponentStats_MemoryStats{} } - if err := m.Memory.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Memory.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1549,7 +1549,7 @@ func (m *ComponentStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Goroutines |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1565,14 +1565,14 @@ func (m *ComponentStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.GcCpuFraction = float32(math.Float32frombits(v)) default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1591,8 +1591,8 @@ func (m *ComponentStats) Unmarshal(data []byte) error { } return nil } -func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { - l := len(data) +func (m *ComponentStats_CPUStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1604,7 +1604,7 @@ func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1629,10 +1629,10 @@ func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.User = float32(math.Float32frombits(v)) case 2: if wireType != 5 { @@ -1643,10 +1643,10 @@ func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.System = float32(math.Float32frombits(v)) case 3: if wireType != 5 { @@ -1657,14 +1657,14 @@ func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Idle = float32(math.Float32frombits(v)) default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1683,8 +1683,8 @@ func (m *ComponentStats_CPUStats) Unmarshal(data []byte) error { } return nil } -func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { - l := len(data) +func (m *ComponentStats_MemoryStats) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1696,7 +1696,7 @@ func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1724,7 +1724,7 @@ func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Memory |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1743,7 +1743,7 @@ func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Swap |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1752,7 +1752,7 @@ func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipApi(data[iNdEx:]) + skippy, err := skipApi(dAtA[iNdEx:]) if err != nil { return err } @@ -1771,8 +1771,8 @@ func (m *ComponentStats_MemoryStats) Unmarshal(data []byte) error { } return nil } -func skipApi(data []byte) (n int, err error) { - l := len(data) +func skipApi(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -1783,7 +1783,7 @@ func skipApi(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1801,7 +1801,7 @@ func skipApi(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -1818,7 +1818,7 @@ func skipApi(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1841,7 +1841,7 @@ func skipApi(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1852,7 +1852,7 @@ func skipApi(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipApi(data[start:]) + next, err := skipApi(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 7ce07dfdb..4bd92d42f 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -508,7 +508,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for Broker service @@ -772,7 +772,7 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, - Metadata: fileDescriptorBroker, + Metadata: "github.com/TheThingsNetwork/ttn/api/broker/broker.proto", } // Client API for BrokerManager service @@ -873,61 +873,61 @@ var _BrokerManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorBroker, + Metadata: "github.com/TheThingsNetwork/ttn/api/broker/broker.proto", } -func (m *DownlinkOption) Marshal() (data []byte, err error) { +func (m *DownlinkOption) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { +func (m *DownlinkOption) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Identifier) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Identifier))) - i += copy(data[i:], m.Identifier) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Identifier))) + i += copy(dAtA[i:], m.Identifier) } if len(m.GatewayId) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.GatewayId))) + i += copy(dAtA[i:], m.GatewayId) } if m.Score != 0 { - data[i] = 0x18 + dAtA[i] = 0x18 i++ - i = encodeVarintBroker(data, i, uint64(m.Score)) + i = encodeVarintBroker(dAtA, i, uint64(m.Score)) } if m.Deadline != 0 { - data[i] = 0x20 + dAtA[i] = 0x20 i++ - i = encodeVarintBroker(data, i, uint64(m.Deadline)) + i = encodeVarintBroker(dAtA, i, uint64(m.Deadline)) } if m.ProtocolConfig != nil { - data[i] = 0x2a + dAtA[i] = 0x2a i++ - i = encodeVarintBroker(data, i, uint64(m.ProtocolConfig.Size())) - n1, err := m.ProtocolConfig.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolConfig.Size())) + n1, err := m.ProtocolConfig.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.GatewayConfig != nil { - data[i] = 0x32 + dAtA[i] = 0x32 i++ - i = encodeVarintBroker(data, i, uint64(m.GatewayConfig.Size())) - n2, err := m.GatewayConfig.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.GatewayConfig.Size())) + n2, err := m.GatewayConfig.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -936,88 +936,88 @@ func (m *DownlinkOption) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *UplinkMessage) Marshal() (data []byte, err error) { +func (m *UplinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { +func (m *UplinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n3, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n3, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n3 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n4, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n4, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n5, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n5, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n5 } if len(m.AppId) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } if m.ProtocolMetadata != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n6, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n6, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n6 } if m.GatewayMetadata != nil { - data[i] = 0xb2 + dAtA[i] = 0xb2 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n7, err := m.GatewayMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.GatewayMetadata.Size())) + n7, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1025,12 +1025,12 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { - data[i] = 0xfa + dAtA[i] = 0xfa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1040,76 +1040,76 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DownlinkMessage) Marshal() (data []byte, err error) { +func (m *DownlinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { +func (m *DownlinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n8, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n8, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n8 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n9, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n9, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n9 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n10, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n10, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n10 } if len(m.AppId) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } if m.DownlinkOption != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n11, err := m.DownlinkOption.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DownlinkOption.Size())) + n11, err := m.DownlinkOption.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1118,42 +1118,42 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { +func (m *DeviceActivationResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { +func (m *DeviceActivationResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n12, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n12, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n12 } if m.DownlinkOption != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DownlinkOption.Size())) - n13, err := m.DownlinkOption.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DownlinkOption.Size())) + n13, err := m.DownlinkOption.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1162,76 +1162,76 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DeduplicatedUplinkMessage) Marshal() (data []byte, err error) { +func (m *DeduplicatedUplinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { +func (m *DeduplicatedUplinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n14, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n14, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n14 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n15, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n15, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n15 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n16, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n16, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n16 } if len(m.AppId) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } if m.ProtocolMetadata != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n17, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n17, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1239,12 +1239,12 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { - data[i] = 0xb2 + dAtA[i] = 0xb2 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1252,19 +1252,19 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { } } if m.ServerTime != 0 { - data[i] = 0xb8 + dAtA[i] = 0xb8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ServerTime)) + i = encodeVarintBroker(dAtA, i, uint64(m.ServerTime)) } if m.ResponseTemplate != nil { - data[i] = 0xfa + dAtA[i] = 0xfa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n18, err := m.ResponseTemplate.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ResponseTemplate.Size())) + n18, err := m.ResponseTemplate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1273,88 +1273,88 @@ func (m *DeduplicatedUplinkMessage) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DeviceActivationRequest) Marshal() (data []byte, err error) { +func (m *DeviceActivationRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { +func (m *DeviceActivationRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n19, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n19, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n19 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n20, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n20, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n20 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n21, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n21, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n21 } if m.ProtocolMetadata != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n22, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n22, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n22 } if m.GatewayMetadata != nil { - data[i] = 0xb2 + dAtA[i] = 0xb2 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.GatewayMetadata.Size())) - n23, err := m.GatewayMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.GatewayMetadata.Size())) + n23, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n23 } if m.ActivationMetadata != nil { - data[i] = 0xba + dAtA[i] = 0xba i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n24, err := m.ActivationMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ActivationMetadata.Size())) + n24, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1362,12 +1362,12 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { } if len(m.DownlinkOptions) > 0 { for _, msg := range m.DownlinkOptions { - data[i] = 0xfa + dAtA[i] = 0xfa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1377,76 +1377,76 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DeduplicatedDeviceActivationRequest) Marshal() (data []byte, err error) { +func (m *DeduplicatedDeviceActivationRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error) { +func (m *DeduplicatedDeviceActivationRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n25, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n25, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n25 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n26, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n26, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n26 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n27, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n27, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n27 } if len(m.AppId) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } if m.ProtocolMetadata != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ProtocolMetadata.Size())) - n28, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n28, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1454,12 +1454,12 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error } if len(m.GatewayMetadata) > 0 { for _, msg := range m.GatewayMetadata { - data[i] = 0xb2 + dAtA[i] = 0xb2 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1467,31 +1467,31 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error } } if m.ActivationMetadata != nil { - data[i] = 0xba + dAtA[i] = 0xba i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ActivationMetadata.Size())) - n29, err := m.ActivationMetadata.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ActivationMetadata.Size())) + n29, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n29 } if m.ServerTime != 0 { - data[i] = 0xc0 + dAtA[i] = 0xc0 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ServerTime)) + i = encodeVarintBroker(dAtA, i, uint64(m.ServerTime)) } if m.ResponseTemplate != nil { - data[i] = 0xfa + dAtA[i] = 0xfa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ResponseTemplate.Size())) - n30, err := m.ResponseTemplate.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ResponseTemplate.Size())) + n30, err := m.ResponseTemplate.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1500,98 +1500,98 @@ func (m *DeduplicatedDeviceActivationRequest) MarshalTo(data []byte) (int, error return i, nil } -func (m *ActivationChallengeRequest) Marshal() (data []byte, err error) { +func (m *ActivationChallengeRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ActivationChallengeRequest) MarshalTo(data []byte) (int, error) { +func (m *ActivationChallengeRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n31, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n31, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n31 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.DevEui.Size())) - n32, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.DevEui.Size())) + n32, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n32 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.AppEui.Size())) - n33, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.AppEui.Size())) + n33, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n33 } if len(m.AppId) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } return i, nil } -func (m *ActivationChallengeResponse) Marshal() (data []byte, err error) { +func (m *ActivationChallengeResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ActivationChallengeResponse) MarshalTo(data []byte) (int, error) { +func (m *ActivationChallengeResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintBroker(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Message.Size())) - n34, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Message.Size())) + n34, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1600,17 +1600,17 @@ func (m *ActivationChallengeResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *SubscribeRequest) Marshal() (data []byte, err error) { +func (m *SubscribeRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { +func (m *SubscribeRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -1618,17 +1618,17 @@ func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusRequest) Marshal() (data []byte, err error) { +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *StatusRequest) MarshalTo(data []byte) (int, error) { +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -1636,175 +1636,175 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Status) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Status) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.System != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(m.System.Size())) - n35, err := m.System.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.System.Size())) + n35, err := m.System.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n35 } if m.Component != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(m.Component.Size())) - n36, err := m.Component.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Component.Size())) + n36, err := m.Component.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n36 } if m.Uplink != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintBroker(data, i, uint64(m.Uplink.Size())) - n37, err := m.Uplink.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Uplink.Size())) + n37, err := m.Uplink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n37 } if m.UplinkUnique != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintBroker(data, i, uint64(m.UplinkUnique.Size())) - n38, err := m.UplinkUnique.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.UplinkUnique.Size())) + n38, err := m.UplinkUnique.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n38 } if m.Downlink != nil { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintBroker(data, i, uint64(m.Downlink.Size())) - n39, err := m.Downlink.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Downlink.Size())) + n39, err := m.Downlink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n39 } if m.Activations != nil { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintBroker(data, i, uint64(m.Activations.Size())) - n40, err := m.Activations.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Activations.Size())) + n40, err := m.Activations.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n40 } if m.ActivationsUnique != nil { - data[i] = 0x7a + dAtA[i] = 0x7a i++ - i = encodeVarintBroker(data, i, uint64(m.ActivationsUnique.Size())) - n41, err := m.ActivationsUnique.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.ActivationsUnique.Size())) + n41, err := m.ActivationsUnique.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n41 } if m.Deduplication != nil { - data[i] = 0x82 + dAtA[i] = 0x82 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.Deduplication.Size())) - n42, err := m.Deduplication.MarshalTo(data[i:]) + i = encodeVarintBroker(dAtA, i, uint64(m.Deduplication.Size())) + n42, err := m.Deduplication.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n42 } if m.ConnectedRouters != 0 { - data[i] = 0xa8 + dAtA[i] = 0xa8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ConnectedRouters)) + i = encodeVarintBroker(dAtA, i, uint64(m.ConnectedRouters)) } if m.ConnectedHandlers != 0 { - data[i] = 0xb0 + dAtA[i] = 0xb0 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintBroker(data, i, uint64(m.ConnectedHandlers)) + i = encodeVarintBroker(dAtA, i, uint64(m.ConnectedHandlers)) } return i, nil } -func (m *ApplicationHandlerRegistration) Marshal() (data []byte, err error) { +func (m *ApplicationHandlerRegistration) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ApplicationHandlerRegistration) MarshalTo(data []byte) (int, error) { +func (m *ApplicationHandlerRegistration) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.AppId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintBroker(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.HandlerId) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintBroker(data, i, uint64(len(m.HandlerId))) - i += copy(data[i:], m.HandlerId) + i = encodeVarintBroker(dAtA, i, uint64(len(m.HandlerId))) + i += copy(dAtA[i:], m.HandlerId) } return i, nil } -func encodeFixed64Broker(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Broker(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Broker(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Broker(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintBroker(data []byte, offset int, v uint64) int { +func encodeVarintBroker(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *DownlinkOption) Size() (n int) { @@ -2196,8 +2196,8 @@ func sovBroker(x uint64) (n int) { func sozBroker(x uint64) (n int) { return sovBroker(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *DownlinkOption) Unmarshal(data []byte) error { - l := len(data) +func (m *DownlinkOption) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2209,7 +2209,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2237,7 +2237,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2252,7 +2252,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Identifier = string(data[iNdEx:postIndex]) + m.Identifier = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -2266,7 +2266,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2281,7 +2281,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayId = string(data[iNdEx:postIndex]) + m.GatewayId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 0 { @@ -2295,7 +2295,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Score |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -2314,7 +2314,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Deadline |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -2333,7 +2333,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2350,7 +2350,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if m.ProtocolConfig == nil { m.ProtocolConfig = &protocol.TxConfiguration{} } - if err := m.ProtocolConfig.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2366,7 +2366,7 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2383,13 +2383,13 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { if m.GatewayConfig == nil { m.GatewayConfig = &gateway.TxConfiguration{} } - if err := m.GatewayConfig.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -2408,8 +2408,8 @@ func (m *DownlinkOption) Unmarshal(data []byte) error { } return nil } -func (m *UplinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *UplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2421,7 +2421,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2449,7 +2449,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2463,7 +2463,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -2480,7 +2480,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2497,7 +2497,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2513,7 +2513,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2529,7 +2529,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2545,7 +2545,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2561,7 +2561,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2577,7 +2577,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2592,7 +2592,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 14: if wireType != 2 { @@ -2606,7 +2606,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2621,7 +2621,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -2635,7 +2635,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2652,7 +2652,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if m.ProtocolMetadata == nil { m.ProtocolMetadata = &protocol.RxMetadata{} } - if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2668,7 +2668,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2685,7 +2685,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if m.GatewayMetadata == nil { m.GatewayMetadata = &gateway.RxMetadata{} } - if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2701,7 +2701,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2716,13 +2716,13 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.DownlinkOptions = append(m.DownlinkOptions, &DownlinkOption{}) - if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -2741,8 +2741,8 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *DownlinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *DownlinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2754,7 +2754,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2782,7 +2782,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2796,7 +2796,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -2813,7 +2813,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2830,7 +2830,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2846,7 +2846,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2862,7 +2862,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2878,7 +2878,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2894,7 +2894,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2910,7 +2910,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2925,7 +2925,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 14: if wireType != 2 { @@ -2939,7 +2939,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2954,7 +2954,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -2968,7 +2968,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2985,13 +2985,13 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if m.DownlinkOption == nil { m.DownlinkOption = &DownlinkOption{} } - if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DownlinkOption.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -3010,8 +3010,8 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *DeviceActivationResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceActivationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3023,7 +3023,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3051,7 +3051,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3065,7 +3065,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3082,7 +3082,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3099,7 +3099,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3115,7 +3115,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3132,13 +3132,13 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if m.DownlinkOption == nil { m.DownlinkOption = &DownlinkOption{} } - if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DownlinkOption.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -3157,8 +3157,8 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { } return nil } -func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *DeduplicatedUplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3170,7 +3170,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3198,7 +3198,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3212,7 +3212,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3229,7 +3229,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3246,7 +3246,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3262,7 +3262,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3278,7 +3278,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3294,7 +3294,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3310,7 +3310,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3326,7 +3326,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3341,7 +3341,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 14: if wireType != 2 { @@ -3355,7 +3355,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3370,7 +3370,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -3384,7 +3384,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3401,7 +3401,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if m.ProtocolMetadata == nil { m.ProtocolMetadata = &protocol.RxMetadata{} } - if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3417,7 +3417,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3432,7 +3432,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.GatewayMetadata = append(m.GatewayMetadata, &gateway.RxMetadata{}) - if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3448,7 +3448,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.ServerTime |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -3467,7 +3467,7 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3484,13 +3484,13 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { if m.ResponseTemplate == nil { m.ResponseTemplate = &DownlinkMessage{} } - if err := m.ResponseTemplate.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ResponseTemplate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -3509,8 +3509,8 @@ func (m *DeduplicatedUplinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *DeviceActivationRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceActivationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3522,7 +3522,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3550,7 +3550,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3564,7 +3564,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3581,7 +3581,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3598,7 +3598,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3614,7 +3614,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3630,7 +3630,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3646,7 +3646,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3662,7 +3662,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3678,7 +3678,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3695,7 +3695,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.ProtocolMetadata == nil { m.ProtocolMetadata = &protocol.RxMetadata{} } - if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3711,7 +3711,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3728,7 +3728,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.GatewayMetadata == nil { m.GatewayMetadata = &gateway.RxMetadata{} } - if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3744,7 +3744,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3761,7 +3761,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.ActivationMetadata == nil { m.ActivationMetadata = &protocol.ActivationMetadata{} } - if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3777,7 +3777,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3792,13 +3792,13 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.DownlinkOptions = append(m.DownlinkOptions, &DownlinkOption{}) - if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DownlinkOptions[len(m.DownlinkOptions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -3817,8 +3817,8 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { } return nil } -func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *DeduplicatedDeviceActivationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3830,7 +3830,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3858,7 +3858,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3872,7 +3872,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3889,7 +3889,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3906,7 +3906,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3922,7 +3922,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3938,7 +3938,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3954,7 +3954,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3970,7 +3970,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3986,7 +3986,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4001,7 +4001,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 14: if wireType != 2 { @@ -4015,7 +4015,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4030,7 +4030,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -4044,7 +4044,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4061,7 +4061,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if m.ProtocolMetadata == nil { m.ProtocolMetadata = &protocol.RxMetadata{} } - if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4077,7 +4077,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4092,7 +4092,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.GatewayMetadata = append(m.GatewayMetadata, &gateway.RxMetadata{}) - if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayMetadata[len(m.GatewayMetadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4108,7 +4108,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4125,7 +4125,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if m.ActivationMetadata == nil { m.ActivationMetadata = &protocol.ActivationMetadata{} } - if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4141,7 +4141,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.ServerTime |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -4160,7 +4160,7 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4177,13 +4177,13 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { if m.ResponseTemplate == nil { m.ResponseTemplate = &DeviceActivationResponse{} } - if err := m.ResponseTemplate.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ResponseTemplate.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -4202,8 +4202,8 @@ func (m *DeduplicatedDeviceActivationRequest) Unmarshal(data []byte) error { } return nil } -func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *ActivationChallengeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -4215,7 +4215,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4243,7 +4243,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4257,7 +4257,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -4274,7 +4274,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4291,7 +4291,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4307,7 +4307,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4323,7 +4323,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4339,7 +4339,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4355,7 +4355,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4371,7 +4371,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4386,7 +4386,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 14: if wireType != 2 { @@ -4400,7 +4400,7 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4415,11 +4415,11 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -4438,8 +4438,8 @@ func (m *ActivationChallengeRequest) Unmarshal(data []byte) error { } return nil } -func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *ActivationChallengeResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -4451,7 +4451,7 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4479,7 +4479,7 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4493,7 +4493,7 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -4510,7 +4510,7 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4527,13 +4527,13 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -4552,8 +4552,8 @@ func (m *ActivationChallengeResponse) Unmarshal(data []byte) error { } return nil } -func (m *SubscribeRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *SubscribeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -4565,7 +4565,7 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4583,7 +4583,7 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -4602,8 +4602,8 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { } return nil } -func (m *StatusRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -4615,7 +4615,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4633,7 +4633,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -4652,8 +4652,8 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *Status) Unmarshal(data []byte) error { - l := len(data) +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -4665,7 +4665,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -4693,7 +4693,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4710,7 +4710,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.System == nil { m.System = &api.SystemStats{} } - if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4726,7 +4726,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4743,7 +4743,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Component == nil { m.Component = &api.ComponentStats{} } - if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4759,7 +4759,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4776,7 +4776,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Uplink == nil { m.Uplink = &api.Rates{} } - if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4792,7 +4792,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4809,7 +4809,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.UplinkUnique == nil { m.UplinkUnique = &api.Rates{} } - if err := m.UplinkUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.UplinkUnique.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4825,7 +4825,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4842,7 +4842,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Downlink == nil { m.Downlink = &api.Rates{} } - if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4858,7 +4858,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4875,7 +4875,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Activations == nil { m.Activations = &api.Rates{} } - if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4891,7 +4891,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4908,7 +4908,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.ActivationsUnique == nil { m.ActivationsUnique = &api.Rates{} } - if err := m.ActivationsUnique.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ActivationsUnique.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4924,7 +4924,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -4941,7 +4941,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Deduplication == nil { m.Deduplication = &api.Percentiles{} } - if err := m.Deduplication.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Deduplication.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -4957,7 +4957,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.ConnectedRouters |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -4976,7 +4976,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.ConnectedHandlers |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -4985,7 +4985,7 @@ func (m *Status) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -5004,8 +5004,8 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } -func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { - l := len(data) +func (m *ApplicationHandlerRegistration) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -5017,7 +5017,7 @@ func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -5045,7 +5045,7 @@ func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -5060,7 +5060,7 @@ func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -5074,7 +5074,7 @@ func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -5089,11 +5089,11 @@ func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.HandlerId = string(data[iNdEx:postIndex]) + m.HandlerId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipBroker(data[iNdEx:]) + skippy, err := skipBroker(dAtA[iNdEx:]) if err != nil { return err } @@ -5112,8 +5112,8 @@ func (m *ApplicationHandlerRegistration) Unmarshal(data []byte) error { } return nil } -func skipBroker(data []byte) (n int, err error) { - l := len(data) +func skipBroker(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -5124,7 +5124,7 @@ func skipBroker(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -5142,7 +5142,7 @@ func skipBroker(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -5159,7 +5159,7 @@ func skipBroker(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -5182,7 +5182,7 @@ func skipBroker(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -5193,7 +5193,7 @@ func skipBroker(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipBroker(data[start:]) + next, err := skipBroker(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 785e06a01..c4a0e7d22 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -178,7 +178,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for Discovery service @@ -373,7 +373,7 @@ var _Discovery_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorDiscovery, + Metadata: "github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto", } // Client API for DiscoveryManager service @@ -403,119 +403,119 @@ var _DiscoveryManager_serviceDesc = grpc.ServiceDesc{ HandlerType: (*DiscoveryManagerServer)(nil), Methods: []grpc.MethodDesc{}, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorDiscovery, + Metadata: "github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto", } -func (m *Metadata) Marshal() (data []byte, err error) { +func (m *Metadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Metadata) MarshalTo(data []byte) (int, error) { +func (m *Metadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Key != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintDiscovery(data, i, uint64(m.Key)) + i = encodeVarintDiscovery(dAtA, i, uint64(m.Key)) } if len(m.Value) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Value))) - i += copy(data[i:], m.Value) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Value))) + i += copy(dAtA[i:], m.Value) } return i, nil } -func (m *Announcement) Marshal() (data []byte, err error) { +func (m *Announcement) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Announcement) MarshalTo(data []byte) (int, error) { +func (m *Announcement) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Id) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) - i += copy(data[i:], m.Id) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) } if len(m.ServiceName) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) - i += copy(data[i:], m.ServiceName) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) } if len(m.ServiceVersion) > 0 { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceVersion))) - i += copy(data[i:], m.ServiceVersion) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceVersion))) + i += copy(dAtA[i:], m.ServiceVersion) } if len(m.Description) > 0 { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Description))) - i += copy(data[i:], m.Description) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Description))) + i += copy(dAtA[i:], m.Description) } if len(m.Url) > 0 { - data[i] = 0x2a + dAtA[i] = 0x2a i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Url))) - i += copy(data[i:], m.Url) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Url))) + i += copy(dAtA[i:], m.Url) } if m.Public { - data[i] = 0x30 + dAtA[i] = 0x30 i++ if m.Public { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if len(m.NetAddress) > 0 { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.NetAddress))) - i += copy(data[i:], m.NetAddress) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.NetAddress))) + i += copy(dAtA[i:], m.NetAddress) } if len(m.PublicKey) > 0 { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.PublicKey))) - i += copy(data[i:], m.PublicKey) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.PublicKey))) + i += copy(dAtA[i:], m.PublicKey) } if len(m.Certificate) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Certificate))) - i += copy(data[i:], m.Certificate) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Certificate))) + i += copy(dAtA[i:], m.Certificate) } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintDiscovery(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintDiscovery(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -525,92 +525,92 @@ func (m *Announcement) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *GetAllRequest) Marshal() (data []byte, err error) { +func (m *GetAllRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *GetAllRequest) MarshalTo(data []byte) (int, error) { +func (m *GetAllRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.ServiceName) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) - i += copy(data[i:], m.ServiceName) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) } return i, nil } -func (m *GetRequest) Marshal() (data []byte, err error) { +func (m *GetRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *GetRequest) MarshalTo(data []byte) (int, error) { +func (m *GetRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Id) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) - i += copy(data[i:], m.Id) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) } if len(m.ServiceName) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) - i += copy(data[i:], m.ServiceName) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) } return i, nil } -func (m *MetadataRequest) Marshal() (data []byte, err error) { +func (m *MetadataRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *MetadataRequest) MarshalTo(data []byte) (int, error) { +func (m *MetadataRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Id) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.Id))) - i += copy(data[i:], m.Id) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Id))) + i += copy(dAtA[i:], m.Id) } if len(m.ServiceName) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintDiscovery(data, i, uint64(len(m.ServiceName))) - i += copy(data[i:], m.ServiceName) + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ServiceName))) + i += copy(dAtA[i:], m.ServiceName) } if m.Metadata != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintDiscovery(data, i, uint64(m.Metadata.Size())) - n1, err := m.Metadata.MarshalTo(data[i:]) + i = encodeVarintDiscovery(dAtA, i, uint64(m.Metadata.Size())) + n1, err := m.Metadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -619,27 +619,27 @@ func (m *MetadataRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *AnnouncementsResponse) Marshal() (data []byte, err error) { +func (m *AnnouncementsResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *AnnouncementsResponse) MarshalTo(data []byte) (int, error) { +func (m *AnnouncementsResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Services) > 0 { for _, msg := range m.Services { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDiscovery(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintDiscovery(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -649,31 +649,31 @@ func (m *AnnouncementsResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func encodeFixed64Discovery(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Discovery(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Discovery(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Discovery(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintDiscovery(data []byte, offset int, v uint64) int { +func encodeVarintDiscovery(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *Metadata) Size() (n int) { @@ -803,8 +803,8 @@ func sovDiscovery(x uint64) (n int) { func sozDiscovery(x uint64) (n int) { return sovDiscovery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Metadata) Unmarshal(data []byte) error { - l := len(data) +func (m *Metadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -816,7 +816,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -844,7 +844,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Key |= (Metadata_Key(b) & 0x7F) << shift if b < 0x80 { @@ -863,7 +863,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -877,14 +877,14 @@ func (m *Metadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Value = append(m.Value[:0], data[iNdEx:postIndex]...) + m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) if m.Value == nil { m.Value = []byte{} } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDiscovery(data[iNdEx:]) + skippy, err := skipDiscovery(dAtA[iNdEx:]) if err != nil { return err } @@ -903,8 +903,8 @@ func (m *Metadata) Unmarshal(data []byte) error { } return nil } -func (m *Announcement) Unmarshal(data []byte) error { - l := len(data) +func (m *Announcement) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -916,7 +916,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -944,7 +944,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -959,7 +959,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(data[iNdEx:postIndex]) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -973,7 +973,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -988,7 +988,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.ServiceName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { @@ -1002,7 +1002,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1017,7 +1017,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceVersion = string(data[iNdEx:postIndex]) + m.ServiceVersion = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { @@ -1031,7 +1031,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1046,7 +1046,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Description = string(data[iNdEx:postIndex]) + m.Description = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { @@ -1060,7 +1060,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1075,7 +1075,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Url = string(data[iNdEx:postIndex]) + m.Url = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 6: if wireType != 0 { @@ -1089,7 +1089,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1109,7 +1109,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1124,7 +1124,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.NetAddress = string(data[iNdEx:postIndex]) + m.NetAddress = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 12: if wireType != 2 { @@ -1138,7 +1138,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1153,7 +1153,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.PublicKey = string(data[iNdEx:postIndex]) + m.PublicKey = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 13: if wireType != 2 { @@ -1167,7 +1167,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1182,7 +1182,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Certificate = string(data[iNdEx:postIndex]) + m.Certificate = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -1196,7 +1196,7 @@ func (m *Announcement) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1211,13 +1211,13 @@ func (m *Announcement) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Metadata = append(m.Metadata, &Metadata{}) - if err := m.Metadata[len(m.Metadata)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Metadata[len(m.Metadata)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDiscovery(data[iNdEx:]) + skippy, err := skipDiscovery(dAtA[iNdEx:]) if err != nil { return err } @@ -1236,8 +1236,8 @@ func (m *Announcement) Unmarshal(data []byte) error { } return nil } -func (m *GetAllRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *GetAllRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1249,7 +1249,7 @@ func (m *GetAllRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1277,7 +1277,7 @@ func (m *GetAllRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1292,11 +1292,11 @@ func (m *GetAllRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.ServiceName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDiscovery(data[iNdEx:]) + skippy, err := skipDiscovery(dAtA[iNdEx:]) if err != nil { return err } @@ -1315,8 +1315,8 @@ func (m *GetAllRequest) Unmarshal(data []byte) error { } return nil } -func (m *GetRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *GetRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1328,7 +1328,7 @@ func (m *GetRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1356,7 +1356,7 @@ func (m *GetRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1371,7 +1371,7 @@ func (m *GetRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(data[iNdEx:postIndex]) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -1385,7 +1385,7 @@ func (m *GetRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1400,11 +1400,11 @@ func (m *GetRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.ServiceName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDiscovery(data[iNdEx:]) + skippy, err := skipDiscovery(dAtA[iNdEx:]) if err != nil { return err } @@ -1423,8 +1423,8 @@ func (m *GetRequest) Unmarshal(data []byte) error { } return nil } -func (m *MetadataRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *MetadataRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1436,7 +1436,7 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1464,7 +1464,7 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1479,7 +1479,7 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Id = string(data[iNdEx:postIndex]) + m.Id = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -1493,7 +1493,7 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1508,7 +1508,7 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ServiceName = string(data[iNdEx:postIndex]) + m.ServiceName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 11: if wireType != 2 { @@ -1522,7 +1522,7 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1539,13 +1539,13 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { if m.Metadata == nil { m.Metadata = &Metadata{} } - if err := m.Metadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Metadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDiscovery(data[iNdEx:]) + skippy, err := skipDiscovery(dAtA[iNdEx:]) if err != nil { return err } @@ -1564,8 +1564,8 @@ func (m *MetadataRequest) Unmarshal(data []byte) error { } return nil } -func (m *AnnouncementsResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *AnnouncementsResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1577,7 +1577,7 @@ func (m *AnnouncementsResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1605,7 +1605,7 @@ func (m *AnnouncementsResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1620,13 +1620,13 @@ func (m *AnnouncementsResponse) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Services = append(m.Services, &Announcement{}) - if err := m.Services[len(m.Services)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Services[len(m.Services)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDiscovery(data[iNdEx:]) + skippy, err := skipDiscovery(dAtA[iNdEx:]) if err != nil { return err } @@ -1645,8 +1645,8 @@ func (m *AnnouncementsResponse) Unmarshal(data []byte) error { } return nil } -func skipDiscovery(data []byte) (n int, err error) { - l := len(data) +func skipDiscovery(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -1657,7 +1657,7 @@ func skipDiscovery(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1675,7 +1675,7 @@ func skipDiscovery(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -1692,7 +1692,7 @@ func skipDiscovery(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1715,7 +1715,7 @@ func skipDiscovery(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1726,7 +1726,7 @@ func skipDiscovery(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipDiscovery(data[start:]) + next, err := skipDiscovery(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index 1c29fc441..e49b380fa 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -120,117 +120,117 @@ func init() { proto.RegisterType((*TxConfiguration)(nil), "gateway.TxConfiguration") proto.RegisterType((*Status)(nil), "gateway.Status") } -func (m *GPSMetadata) Marshal() (data []byte, err error) { +func (m *GPSMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *GPSMetadata) MarshalTo(data []byte) (int, error) { +func (m *GPSMetadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Time != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintGateway(data, i, uint64(m.Time)) + i = encodeVarintGateway(dAtA, i, uint64(m.Time)) } if m.Latitude != 0 { - data[i] = 0x15 + dAtA[i] = 0x15 i++ - i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Latitude)))) + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Latitude)))) } if m.Longitude != 0 { - data[i] = 0x1d + dAtA[i] = 0x1d i++ - i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Longitude)))) + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Longitude)))) } if m.Altitude != 0 { - data[i] = 0x20 + dAtA[i] = 0x20 i++ - i = encodeVarintGateway(data, i, uint64(m.Altitude)) + i = encodeVarintGateway(dAtA, i, uint64(m.Altitude)) } return i, nil } -func (m *RxMetadata) Marshal() (data []byte, err error) { +func (m *RxMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *RxMetadata) MarshalTo(data []byte) (int, error) { +func (m *RxMetadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.GatewayId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintGateway(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) + i = encodeVarintGateway(dAtA, i, uint64(len(m.GatewayId))) + i += copy(dAtA[i:], m.GatewayId) } if m.Timestamp != 0 { - data[i] = 0x58 + dAtA[i] = 0x58 i++ - i = encodeVarintGateway(data, i, uint64(m.Timestamp)) + i = encodeVarintGateway(dAtA, i, uint64(m.Timestamp)) } if m.Time != 0 { - data[i] = 0x60 + dAtA[i] = 0x60 i++ - i = encodeVarintGateway(data, i, uint64(m.Time)) + i = encodeVarintGateway(dAtA, i, uint64(m.Time)) } if m.RfChain != 0 { - data[i] = 0xa8 + dAtA[i] = 0xa8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.RfChain)) + i = encodeVarintGateway(dAtA, i, uint64(m.RfChain)) } if m.Channel != 0 { - data[i] = 0xb0 + dAtA[i] = 0xb0 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.Channel)) + i = encodeVarintGateway(dAtA, i, uint64(m.Channel)) } if m.Frequency != 0 { - data[i] = 0xf8 + dAtA[i] = 0xf8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.Frequency)) + i = encodeVarintGateway(dAtA, i, uint64(m.Frequency)) } if m.Rssi != 0 { - data[i] = 0x85 + dAtA[i] = 0x85 i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Rssi)))) + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Rssi)))) } if m.Snr != 0 { - data[i] = 0x8d + dAtA[i] = 0x8d i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeFixed32Gateway(data, i, uint32(math.Float32bits(float32(m.Snr)))) + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Snr)))) } if m.Gps != nil { - data[i] = 0xca + dAtA[i] = 0xca i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n1, err := m.Gps.MarshalTo(data[i:]) + i = encodeVarintGateway(dAtA, i, uint64(m.Gps.Size())) + n1, err := m.Gps.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -239,208 +239,208 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *TxConfiguration) Marshal() (data []byte, err error) { +func (m *TxConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { +func (m *TxConfiguration) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Timestamp != 0 { - data[i] = 0x58 + dAtA[i] = 0x58 i++ - i = encodeVarintGateway(data, i, uint64(m.Timestamp)) + i = encodeVarintGateway(dAtA, i, uint64(m.Timestamp)) } if m.RfChain != 0 { - data[i] = 0xa8 + dAtA[i] = 0xa8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.RfChain)) + i = encodeVarintGateway(dAtA, i, uint64(m.RfChain)) } if m.Frequency != 0 { - data[i] = 0xb0 + dAtA[i] = 0xb0 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.Frequency)) + i = encodeVarintGateway(dAtA, i, uint64(m.Frequency)) } if m.Power != 0 { - data[i] = 0xb8 + dAtA[i] = 0xb8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.Power)) + i = encodeVarintGateway(dAtA, i, uint64(m.Power)) } if m.PolarizationInversion { - data[i] = 0xf8 + dAtA[i] = 0xf8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ if m.PolarizationInversion { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if m.FrequencyDeviation != 0 { - data[i] = 0x80 + dAtA[i] = 0x80 i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.FrequencyDeviation)) + i = encodeVarintGateway(dAtA, i, uint64(m.FrequencyDeviation)) } return i, nil } -func (m *Status) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Status) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Timestamp != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintGateway(data, i, uint64(m.Timestamp)) + i = encodeVarintGateway(dAtA, i, uint64(m.Timestamp)) } if m.Time != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintGateway(data, i, uint64(m.Time)) + i = encodeVarintGateway(dAtA, i, uint64(m.Time)) } if len(m.Ip) > 0 { for _, s := range m.Ip { - data[i] = 0x5a + dAtA[i] = 0x5a i++ l = len(s) for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) l >>= 7 i++ } - data[i] = uint8(l) + dAtA[i] = uint8(l) i++ - i += copy(data[i:], s) + i += copy(dAtA[i:], s) } } if len(m.Platform) > 0 { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintGateway(data, i, uint64(len(m.Platform))) - i += copy(data[i:], m.Platform) + i = encodeVarintGateway(dAtA, i, uint64(len(m.Platform))) + i += copy(dAtA[i:], m.Platform) } if len(m.ContactEmail) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintGateway(data, i, uint64(len(m.ContactEmail))) - i += copy(data[i:], m.ContactEmail) + i = encodeVarintGateway(dAtA, i, uint64(len(m.ContactEmail))) + i += copy(dAtA[i:], m.ContactEmail) } if len(m.Description) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintGateway(data, i, uint64(len(m.Description))) - i += copy(data[i:], m.Description) + i = encodeVarintGateway(dAtA, i, uint64(len(m.Description))) + i += copy(dAtA[i:], m.Description) } if len(m.Region) > 0 { - data[i] = 0x7a + dAtA[i] = 0x7a i++ - i = encodeVarintGateway(data, i, uint64(len(m.Region))) - i += copy(data[i:], m.Region) + i = encodeVarintGateway(dAtA, i, uint64(len(m.Region))) + i += copy(dAtA[i:], m.Region) } if m.Gps != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.Gps.Size())) - n2, err := m.Gps.MarshalTo(data[i:]) + i = encodeVarintGateway(dAtA, i, uint64(m.Gps.Size())) + n2, err := m.Gps.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n2 } if m.Rtt != 0 { - data[i] = 0xf8 + dAtA[i] = 0xf8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintGateway(data, i, uint64(m.Rtt)) + i = encodeVarintGateway(dAtA, i, uint64(m.Rtt)) } if m.RxIn != 0 { - data[i] = 0xc8 + dAtA[i] = 0xc8 i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.RxIn)) + i = encodeVarintGateway(dAtA, i, uint64(m.RxIn)) } if m.RxOk != 0 { - data[i] = 0xd0 + dAtA[i] = 0xd0 i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.RxOk)) + i = encodeVarintGateway(dAtA, i, uint64(m.RxOk)) } if m.TxIn != 0 { - data[i] = 0xd8 + dAtA[i] = 0xd8 i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.TxIn)) + i = encodeVarintGateway(dAtA, i, uint64(m.TxIn)) } if m.TxOk != 0 { - data[i] = 0xe0 + dAtA[i] = 0xe0 i++ - data[i] = 0x2 + dAtA[i] = 0x2 i++ - i = encodeVarintGateway(data, i, uint64(m.TxOk)) + i = encodeVarintGateway(dAtA, i, uint64(m.TxOk)) } return i, nil } -func encodeFixed64Gateway(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Gateway(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Gateway(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Gateway(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintGateway(data []byte, offset int, v uint64) int { +func encodeVarintGateway(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *GPSMetadata) Size() (n int) { @@ -586,8 +586,8 @@ func sovGateway(x uint64) (n int) { func sozGateway(x uint64) (n int) { return sovGateway(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *GPSMetadata) Unmarshal(data []byte) error { - l := len(data) +func (m *GPSMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -599,7 +599,7 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -627,7 +627,7 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Time |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -643,10 +643,10 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Latitude = float32(math.Float32frombits(v)) case 3: if wireType != 5 { @@ -657,10 +657,10 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Longitude = float32(math.Float32frombits(v)) case 4: if wireType != 0 { @@ -674,7 +674,7 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Altitude |= (int32(b) & 0x7F) << shift if b < 0x80 { @@ -683,7 +683,7 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipGateway(data[iNdEx:]) + skippy, err := skipGateway(dAtA[iNdEx:]) if err != nil { return err } @@ -702,8 +702,8 @@ func (m *GPSMetadata) Unmarshal(data []byte) error { } return nil } -func (m *RxMetadata) Unmarshal(data []byte) error { - l := len(data) +func (m *RxMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -715,7 +715,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -743,7 +743,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -758,7 +758,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayId = string(data[iNdEx:postIndex]) + m.GatewayId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 11: if wireType != 0 { @@ -772,7 +772,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Timestamp |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -791,7 +791,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Time |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -810,7 +810,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.RfChain |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -829,7 +829,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Channel |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -848,7 +848,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Frequency |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -864,10 +864,10 @@ func (m *RxMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Rssi = float32(math.Float32frombits(v)) case 33: if wireType != 5 { @@ -878,10 +878,10 @@ func (m *RxMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } iNdEx += 4 - v = uint32(data[iNdEx-4]) - v |= uint32(data[iNdEx-3]) << 8 - v |= uint32(data[iNdEx-2]) << 16 - v |= uint32(data[iNdEx-1]) << 24 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 m.Snr = float32(math.Float32frombits(v)) case 41: if wireType != 2 { @@ -895,7 +895,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -912,13 +912,13 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if m.Gps == nil { m.Gps = &GPSMetadata{} } - if err := m.Gps.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Gps.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipGateway(data[iNdEx:]) + skippy, err := skipGateway(dAtA[iNdEx:]) if err != nil { return err } @@ -937,8 +937,8 @@ func (m *RxMetadata) Unmarshal(data []byte) error { } return nil } -func (m *TxConfiguration) Unmarshal(data []byte) error { - l := len(data) +func (m *TxConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -950,7 +950,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -978,7 +978,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Timestamp |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -997,7 +997,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.RfChain |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1016,7 +1016,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Frequency |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1035,7 +1035,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Power |= (int32(b) & 0x7F) << shift if b < 0x80 { @@ -1054,7 +1054,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1074,7 +1074,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FrequencyDeviation |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1083,7 +1083,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipGateway(data[iNdEx:]) + skippy, err := skipGateway(dAtA[iNdEx:]) if err != nil { return err } @@ -1102,8 +1102,8 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } return nil } -func (m *Status) Unmarshal(data []byte) error { - l := len(data) +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1115,7 +1115,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1143,7 +1143,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Timestamp |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1162,7 +1162,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Time |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -1181,7 +1181,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1196,7 +1196,7 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Ip = append(m.Ip, string(data[iNdEx:postIndex])) + m.Ip = append(m.Ip, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex case 12: if wireType != 2 { @@ -1210,7 +1210,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1225,7 +1225,7 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Platform = string(data[iNdEx:postIndex]) + m.Platform = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 13: if wireType != 2 { @@ -1239,7 +1239,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1254,7 +1254,7 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ContactEmail = string(data[iNdEx:postIndex]) + m.ContactEmail = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 14: if wireType != 2 { @@ -1268,7 +1268,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1283,7 +1283,7 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Description = string(data[iNdEx:postIndex]) + m.Description = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 15: if wireType != 2 { @@ -1297,7 +1297,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1312,7 +1312,7 @@ func (m *Status) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Region = string(data[iNdEx:postIndex]) + m.Region = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 2 { @@ -1326,7 +1326,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1343,7 +1343,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Gps == nil { m.Gps = &GPSMetadata{} } - if err := m.Gps.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Gps.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1359,7 +1359,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Rtt |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1378,7 +1378,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.RxIn |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1397,7 +1397,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.RxOk |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1416,7 +1416,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.TxIn |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1435,7 +1435,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.TxOk |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1444,7 +1444,7 @@ func (m *Status) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipGateway(data[iNdEx:]) + skippy, err := skipGateway(dAtA[iNdEx:]) if err != nil { return err } @@ -1463,8 +1463,8 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } -func skipGateway(data []byte) (n int, err error) { - l := len(data) +func skipGateway(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -1475,7 +1475,7 @@ func skipGateway(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1493,7 +1493,7 @@ func skipGateway(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -1510,7 +1510,7 @@ func skipGateway(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1533,7 +1533,7 @@ func skipGateway(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1544,7 +1544,7 @@ func skipGateway(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipGateway(data[start:]) + next, err := skipGateway(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 73796ed3f..5deb22873 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -393,7 +393,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for Handler service @@ -489,7 +489,7 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorHandler, + Metadata: "github.com/TheThingsNetwork/ttn/api/handler/handler.proto", } // Client API for ApplicationManager service @@ -850,7 +850,7 @@ var _ApplicationManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorHandler, + Metadata: "github.com/TheThingsNetwork/ttn/api/handler/handler.proto", } // Client API for HandlerManager service @@ -914,57 +914,57 @@ var _HandlerManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorHandler, + Metadata: "github.com/TheThingsNetwork/ttn/api/handler/handler.proto", } -func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { +func (m *DeviceActivationResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { +func (m *DeviceActivationResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(m.Message.Size())) - n1, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.Message.Size())) + n1, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.DownlinkOption != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintHandler(data, i, uint64(m.DownlinkOption.Size())) - n2, err := m.DownlinkOption.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.DownlinkOption.Size())) + n2, err := m.DownlinkOption.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n2 } if m.ActivationMetadata != nil { - data[i] = 0xba + dAtA[i] = 0xba i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintHandler(data, i, uint64(m.ActivationMetadata.Size())) - n3, err := m.ActivationMetadata.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.ActivationMetadata.Size())) + n3, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -973,17 +973,17 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusRequest) Marshal() (data []byte, err error) { +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *StatusRequest) MarshalTo(data []byte) (int, error) { +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -991,66 +991,66 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Status) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Status) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.System != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(m.System.Size())) - n4, err := m.System.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.System.Size())) + n4, err := m.System.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if m.Component != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(m.Component.Size())) - n5, err := m.Component.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.Component.Size())) + n5, err := m.Component.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n5 } if m.Uplink != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintHandler(data, i, uint64(m.Uplink.Size())) - n6, err := m.Uplink.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.Uplink.Size())) + n6, err := m.Uplink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n6 } if m.Downlink != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintHandler(data, i, uint64(m.Downlink.Size())) - n7, err := m.Downlink.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.Downlink.Size())) + n7, err := m.Downlink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n7 } if m.Activations != nil { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintHandler(data, i, uint64(m.Activations.Size())) - n8, err := m.Activations.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.Activations.Size())) + n8, err := m.Activations.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1059,137 +1059,137 @@ func (m *Status) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ApplicationIdentifier) Marshal() (data []byte, err error) { +func (m *ApplicationIdentifier) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ApplicationIdentifier) MarshalTo(data []byte) (int, error) { +func (m *ApplicationIdentifier) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.AppId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } return i, nil } -func (m *Application) Marshal() (data []byte, err error) { +func (m *Application) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Application) MarshalTo(data []byte) (int, error) { +func (m *Application) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.AppId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.Decoder) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(len(m.Decoder))) - i += copy(data[i:], m.Decoder) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Decoder))) + i += copy(dAtA[i:], m.Decoder) } if len(m.Converter) > 0 { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintHandler(data, i, uint64(len(m.Converter))) - i += copy(data[i:], m.Converter) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Converter))) + i += copy(dAtA[i:], m.Converter) } if len(m.Validator) > 0 { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintHandler(data, i, uint64(len(m.Validator))) - i += copy(data[i:], m.Validator) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Validator))) + i += copy(dAtA[i:], m.Validator) } if len(m.Encoder) > 0 { - data[i] = 0x2a + dAtA[i] = 0x2a i++ - i = encodeVarintHandler(data, i, uint64(len(m.Encoder))) - i += copy(data[i:], m.Encoder) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Encoder))) + i += copy(dAtA[i:], m.Encoder) } return i, nil } -func (m *DeviceIdentifier) Marshal() (data []byte, err error) { +func (m *DeviceIdentifier) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceIdentifier) MarshalTo(data []byte) (int, error) { +func (m *DeviceIdentifier) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.AppId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintHandler(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } return i, nil } -func (m *Device) Marshal() (data []byte, err error) { +func (m *Device) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Device) MarshalTo(data []byte) (int, error) { +func (m *Device) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.AppId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintHandler(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintHandler(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } if m.Device != nil { - nn9, err := m.Device.MarshalTo(data[i:]) + nn9, err := m.Device.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1198,13 +1198,13 @@ func (m *Device) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Device_LorawanDevice) MarshalTo(data []byte) (int, error) { +func (m *Device_LorawanDevice) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.LorawanDevice != nil { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintHandler(data, i, uint64(m.LorawanDevice.Size())) - n10, err := m.LorawanDevice.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.LorawanDevice.Size())) + n10, err := m.LorawanDevice.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1212,27 +1212,27 @@ func (m *Device_LorawanDevice) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *DeviceList) Marshal() (data []byte, err error) { +func (m *DeviceList) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceList) MarshalTo(data []byte) (int, error) { +func (m *DeviceList) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Devices) > 0 { for _, msg := range m.Devices { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1242,172 +1242,172 @@ func (m *DeviceList) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DryDownlinkMessage) Marshal() (data []byte, err error) { +func (m *DryDownlinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DryDownlinkMessage) MarshalTo(data []byte) (int, error) { +func (m *DryDownlinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if len(m.Fields) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(len(m.Fields))) - i += copy(data[i:], m.Fields) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Fields))) + i += copy(dAtA[i:], m.Fields) } if m.App != nil { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintHandler(data, i, uint64(m.App.Size())) - n11, err := m.App.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.App.Size())) + n11, err := m.App.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n11 } if m.Port != 0 { - data[i] = 0x20 + dAtA[i] = 0x20 i++ - i = encodeVarintHandler(data, i, uint64(m.Port)) + i = encodeVarintHandler(dAtA, i, uint64(m.Port)) } return i, nil } -func (m *DryUplinkMessage) Marshal() (data []byte, err error) { +func (m *DryUplinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DryUplinkMessage) MarshalTo(data []byte) (int, error) { +func (m *DryUplinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.App != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(m.App.Size())) - n12, err := m.App.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(m.App.Size())) + n12, err := m.App.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n12 } if m.Port != 0 { - data[i] = 0x18 + dAtA[i] = 0x18 i++ - i = encodeVarintHandler(data, i, uint64(m.Port)) + i = encodeVarintHandler(dAtA, i, uint64(m.Port)) } return i, nil } -func (m *LogEntry) Marshal() (data []byte, err error) { +func (m *LogEntry) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *LogEntry) MarshalTo(data []byte) (int, error) { +func (m *LogEntry) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Function) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.Function))) - i += copy(data[i:], m.Function) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Function))) + i += copy(dAtA[i:], m.Function) } if len(m.Fields) > 0 { for _, s := range m.Fields { - data[i] = 0x12 + dAtA[i] = 0x12 i++ l = len(s) for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) l >>= 7 i++ } - data[i] = uint8(l) + dAtA[i] = uint8(l) i++ - i += copy(data[i:], s) + i += copy(dAtA[i:], s) } } return i, nil } -func (m *DryUplinkResult) Marshal() (data []byte, err error) { +func (m *DryUplinkResult) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DryUplinkResult) MarshalTo(data []byte) (int, error) { +func (m *DryUplinkResult) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if len(m.Fields) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(len(m.Fields))) - i += copy(data[i:], m.Fields) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Fields))) + i += copy(dAtA[i:], m.Fields) } if m.Valid { - data[i] = 0x18 + dAtA[i] = 0x18 i++ if m.Valid { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if len(m.Logs) > 0 { for _, msg := range m.Logs { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintHandler(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1417,33 +1417,33 @@ func (m *DryUplinkResult) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DryDownlinkResult) Marshal() (data []byte, err error) { +func (m *DryDownlinkResult) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DryDownlinkResult) MarshalTo(data []byte) (int, error) { +func (m *DryDownlinkResult) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintHandler(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintHandler(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if len(m.Logs) > 0 { for _, msg := range m.Logs { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintHandler(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintHandler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1453,31 +1453,31 @@ func (m *DryDownlinkResult) MarshalTo(data []byte) (int, error) { return i, nil } -func encodeFixed64Handler(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Handler(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Handler(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Handler(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintHandler(data []byte, offset int, v uint64) int { +func encodeVarintHandler(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *DeviceActivationResponse) Size() (n int) { @@ -1728,8 +1728,8 @@ func sovHandler(x uint64) (n int) { func sozHandler(x uint64) (n int) { return sovHandler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *DeviceActivationResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceActivationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1741,7 +1741,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1769,7 +1769,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1783,7 +1783,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -1800,7 +1800,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1817,7 +1817,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1833,7 +1833,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1850,7 +1850,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if m.DownlinkOption == nil { m.DownlinkOption = &broker.DownlinkOption{} } - if err := m.DownlinkOption.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DownlinkOption.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1866,7 +1866,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1883,13 +1883,13 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if m.ActivationMetadata == nil { m.ActivationMetadata = &protocol.ActivationMetadata{} } - if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -1908,8 +1908,8 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { } return nil } -func (m *StatusRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1921,7 +1921,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1939,7 +1939,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -1958,8 +1958,8 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *Status) Unmarshal(data []byte) error { - l := len(data) +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1971,7 +1971,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1999,7 +1999,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2016,7 +2016,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.System == nil { m.System = &api.SystemStats{} } - if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2032,7 +2032,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2049,7 +2049,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Component == nil { m.Component = &api.ComponentStats{} } - if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2065,7 +2065,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2082,7 +2082,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Uplink == nil { m.Uplink = &api.Rates{} } - if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2098,7 +2098,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2115,7 +2115,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Downlink == nil { m.Downlink = &api.Rates{} } - if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2131,7 +2131,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2148,13 +2148,13 @@ func (m *Status) Unmarshal(data []byte) error { if m.Activations == nil { m.Activations = &api.Rates{} } - if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2173,8 +2173,8 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } -func (m *ApplicationIdentifier) Unmarshal(data []byte) error { - l := len(data) +func (m *ApplicationIdentifier) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2186,7 +2186,7 @@ func (m *ApplicationIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2214,7 +2214,7 @@ func (m *ApplicationIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2229,11 +2229,11 @@ func (m *ApplicationIdentifier) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2252,8 +2252,8 @@ func (m *ApplicationIdentifier) Unmarshal(data []byte) error { } return nil } -func (m *Application) Unmarshal(data []byte) error { - l := len(data) +func (m *Application) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2265,7 +2265,7 @@ func (m *Application) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2293,7 +2293,7 @@ func (m *Application) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2308,7 +2308,7 @@ func (m *Application) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -2322,7 +2322,7 @@ func (m *Application) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2337,7 +2337,7 @@ func (m *Application) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Decoder = string(data[iNdEx:postIndex]) + m.Decoder = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { @@ -2351,7 +2351,7 @@ func (m *Application) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2366,7 +2366,7 @@ func (m *Application) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Converter = string(data[iNdEx:postIndex]) + m.Converter = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { @@ -2380,7 +2380,7 @@ func (m *Application) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2395,7 +2395,7 @@ func (m *Application) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Validator = string(data[iNdEx:postIndex]) + m.Validator = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { @@ -2409,7 +2409,7 @@ func (m *Application) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2424,11 +2424,11 @@ func (m *Application) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Encoder = string(data[iNdEx:postIndex]) + m.Encoder = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2447,8 +2447,8 @@ func (m *Application) Unmarshal(data []byte) error { } return nil } -func (m *DeviceIdentifier) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceIdentifier) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2460,7 +2460,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2488,7 +2488,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2503,7 +2503,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -2517,7 +2517,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2532,11 +2532,11 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2555,8 +2555,8 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { } return nil } -func (m *Device) Unmarshal(data []byte) error { - l := len(data) +func (m *Device) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2568,7 +2568,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2596,7 +2596,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2611,7 +2611,7 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -2625,7 +2625,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2640,7 +2640,7 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { @@ -2654,7 +2654,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2669,14 +2669,14 @@ func (m *Device) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &lorawan1.Device{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Device = &Device_LorawanDevice{v} iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2695,8 +2695,8 @@ func (m *Device) Unmarshal(data []byte) error { } return nil } -func (m *DeviceList) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceList) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2708,7 +2708,7 @@ func (m *DeviceList) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2736,7 +2736,7 @@ func (m *DeviceList) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2751,13 +2751,13 @@ func (m *DeviceList) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Devices = append(m.Devices, &Device{}) - if err := m.Devices[len(m.Devices)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Devices[len(m.Devices)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2776,8 +2776,8 @@ func (m *DeviceList) Unmarshal(data []byte) error { } return nil } -func (m *DryDownlinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *DryDownlinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2789,7 +2789,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2817,7 +2817,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2831,7 +2831,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -2848,7 +2848,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2863,7 +2863,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Fields = string(data[iNdEx:postIndex]) + m.Fields = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 2 { @@ -2877,7 +2877,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2894,7 +2894,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if m.App == nil { m.App = &Application{} } - if err := m.App.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.App.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2910,7 +2910,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Port |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -2919,7 +2919,7 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -2938,8 +2938,8 @@ func (m *DryDownlinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *DryUplinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *DryUplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2951,7 +2951,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2979,7 +2979,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2993,7 +2993,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3010,7 +3010,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3027,7 +3027,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { if m.App == nil { m.App = &Application{} } - if err := m.App.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.App.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3043,7 +3043,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Port |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -3052,7 +3052,7 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -3071,8 +3071,8 @@ func (m *DryUplinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *LogEntry) Unmarshal(data []byte) error { - l := len(data) +func (m *LogEntry) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3084,7 +3084,7 @@ func (m *LogEntry) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3112,7 +3112,7 @@ func (m *LogEntry) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3127,7 +3127,7 @@ func (m *LogEntry) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Function = string(data[iNdEx:postIndex]) + m.Function = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -3141,7 +3141,7 @@ func (m *LogEntry) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3156,11 +3156,11 @@ func (m *LogEntry) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Fields = append(m.Fields, string(data[iNdEx:postIndex])) + m.Fields = append(m.Fields, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -3179,8 +3179,8 @@ func (m *LogEntry) Unmarshal(data []byte) error { } return nil } -func (m *DryUplinkResult) Unmarshal(data []byte) error { - l := len(data) +func (m *DryUplinkResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3192,7 +3192,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3220,7 +3220,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3234,7 +3234,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3251,7 +3251,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3266,7 +3266,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Fields = string(data[iNdEx:postIndex]) + m.Fields = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 3: if wireType != 0 { @@ -3280,7 +3280,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3300,7 +3300,7 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3315,13 +3315,13 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Logs = append(m.Logs, &LogEntry{}) - if err := m.Logs[len(m.Logs)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Logs[len(m.Logs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -3340,8 +3340,8 @@ func (m *DryUplinkResult) Unmarshal(data []byte) error { } return nil } -func (m *DryDownlinkResult) Unmarshal(data []byte) error { - l := len(data) +func (m *DryDownlinkResult) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3353,7 +3353,7 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3381,7 +3381,7 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3395,7 +3395,7 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -3412,7 +3412,7 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3427,13 +3427,13 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Logs = append(m.Logs, &LogEntry{}) - if err := m.Logs[len(m.Logs)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Logs[len(m.Logs)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipHandler(data[iNdEx:]) + skippy, err := skipHandler(dAtA[iNdEx:]) if err != nil { return err } @@ -3452,8 +3452,8 @@ func (m *DryDownlinkResult) Unmarshal(data []byte) error { } return nil } -func skipHandler(data []byte) (n int, err error) { - l := len(data) +func skipHandler(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -3464,7 +3464,7 @@ func skipHandler(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3482,7 +3482,7 @@ func skipHandler(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -3499,7 +3499,7 @@ func skipHandler(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3522,7 +3522,7 @@ func skipHandler(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3533,7 +3533,7 @@ func skipHandler(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipHandler(data[start:]) + next, err := skipHandler(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/monitor/monitor.pb.go b/api/monitor/monitor.pb.go index e0a26a6cf..d3380d644 100644 --- a/api/monitor/monitor.pb.go +++ b/api/monitor/monitor.pb.go @@ -43,7 +43,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for Monitor service @@ -475,7 +475,7 @@ var _Monitor_serviceDesc = grpc.ServiceDesc{ ClientStreams: true, }, }, - Metadata: fileDescriptorMonitor, + Metadata: "github.com/TheThingsNetwork/ttn/api/monitor/monitor.proto", } func init() { diff --git a/api/networkserver/networkserver.pb.go b/api/networkserver/networkserver.pb.go index 1e6c7c135..9dc6945af 100644 --- a/api/networkserver/networkserver.pb.go +++ b/api/networkserver/networkserver.pb.go @@ -150,7 +150,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for NetworkServer service @@ -355,7 +355,7 @@ var _NetworkServer_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorNetworkserver, + Metadata: "github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto", } // Client API for NetworkServerManager service @@ -419,63 +419,63 @@ var _NetworkServerManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorNetworkserver, + Metadata: "github.com/TheThingsNetwork/ttn/api/networkserver/networkserver.proto", } -func (m *DevicesRequest) Marshal() (data []byte, err error) { +func (m *DevicesRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DevicesRequest) MarshalTo(data []byte) (int, error) { +func (m *DevicesRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.DevAddr != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintNetworkserver(data, i, uint64(m.DevAddr.Size())) - n1, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.FCnt != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintNetworkserver(data, i, uint64(m.FCnt)) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.FCnt)) } return i, nil } -func (m *DevicesResponse) Marshal() (data []byte, err error) { +func (m *DevicesResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DevicesResponse) MarshalTo(data []byte) (int, error) { +func (m *DevicesResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Results) > 0 { for _, msg := range m.Results { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintNetworkserver(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -485,17 +485,17 @@ func (m *DevicesResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusRequest) Marshal() (data []byte, err error) { +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *StatusRequest) MarshalTo(data []byte) (int, error) { +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -503,78 +503,78 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Status) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Status) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.System != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintNetworkserver(data, i, uint64(m.System.Size())) - n2, err := m.System.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.System.Size())) + n2, err := m.System.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n2 } if m.Component != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintNetworkserver(data, i, uint64(m.Component.Size())) - n3, err := m.Component.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Component.Size())) + n3, err := m.Component.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n3 } if m.Uplink != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintNetworkserver(data, i, uint64(m.Uplink.Size())) - n4, err := m.Uplink.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Uplink.Size())) + n4, err := m.Uplink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if m.Downlink != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintNetworkserver(data, i, uint64(m.Downlink.Size())) - n5, err := m.Downlink.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Downlink.Size())) + n5, err := m.Downlink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n5 } if m.Activations != nil { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintNetworkserver(data, i, uint64(m.Activations.Size())) - n6, err := m.Activations.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.Activations.Size())) + n6, err := m.Activations.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n6 } if m.DevicesPerAddress != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintNetworkserver(data, i, uint64(m.DevicesPerAddress.Size())) - n7, err := m.DevicesPerAddress.MarshalTo(data[i:]) + i = encodeVarintNetworkserver(dAtA, i, uint64(m.DevicesPerAddress.Size())) + n7, err := m.DevicesPerAddress.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -583,31 +583,31 @@ func (m *Status) MarshalTo(data []byte) (int, error) { return i, nil } -func encodeFixed64Networkserver(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Networkserver(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Networkserver(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Networkserver(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintNetworkserver(data []byte, offset int, v uint64) int { +func encodeVarintNetworkserver(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *DevicesRequest) Size() (n int) { @@ -684,8 +684,8 @@ func sovNetworkserver(x uint64) (n int) { func sozNetworkserver(x uint64) (n int) { return sovNetworkserver(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *DevicesRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *DevicesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -697,7 +697,7 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -725,7 +725,7 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -741,7 +741,7 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevAddr m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -757,7 +757,7 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -766,7 +766,7 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) + skippy, err := skipNetworkserver(dAtA[iNdEx:]) if err != nil { return err } @@ -785,8 +785,8 @@ func (m *DevicesRequest) Unmarshal(data []byte) error { } return nil } -func (m *DevicesResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *DevicesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -798,7 +798,7 @@ func (m *DevicesResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -826,7 +826,7 @@ func (m *DevicesResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -841,13 +841,13 @@ func (m *DevicesResponse) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Results = append(m.Results, &lorawan.Device{}) - if err := m.Results[len(m.Results)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Results[len(m.Results)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) + skippy, err := skipNetworkserver(dAtA[iNdEx:]) if err != nil { return err } @@ -866,8 +866,8 @@ func (m *DevicesResponse) Unmarshal(data []byte) error { } return nil } -func (m *StatusRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -879,7 +879,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -897,7 +897,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) + skippy, err := skipNetworkserver(dAtA[iNdEx:]) if err != nil { return err } @@ -916,8 +916,8 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *Status) Unmarshal(data []byte) error { - l := len(data) +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -929,7 +929,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -957,7 +957,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -974,7 +974,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.System == nil { m.System = &api.SystemStats{} } - if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -990,7 +990,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1007,7 +1007,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Component == nil { m.Component = &api.ComponentStats{} } - if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1023,7 +1023,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1040,7 +1040,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Uplink == nil { m.Uplink = &api.Rates{} } - if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1056,7 +1056,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1073,7 +1073,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Downlink == nil { m.Downlink = &api.Rates{} } - if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1089,7 +1089,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1106,7 +1106,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Activations == nil { m.Activations = &api.Rates{} } - if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1122,7 +1122,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1139,13 +1139,13 @@ func (m *Status) Unmarshal(data []byte) error { if m.DevicesPerAddress == nil { m.DevicesPerAddress = &api.Percentiles{} } - if err := m.DevicesPerAddress.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevicesPerAddress.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipNetworkserver(data[iNdEx:]) + skippy, err := skipNetworkserver(dAtA[iNdEx:]) if err != nil { return err } @@ -1164,8 +1164,8 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } -func skipNetworkserver(data []byte) (n int, err error) { - l := len(data) +func skipNetworkserver(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -1176,7 +1176,7 @@ func skipNetworkserver(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1194,7 +1194,7 @@ func skipNetworkserver(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -1211,7 +1211,7 @@ func skipNetworkserver(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1234,7 +1234,7 @@ func skipNetworkserver(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1245,7 +1245,7 @@ func skipNetworkserver(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipNetworkserver(data[start:]) + next, err := skipNetworkserver(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index db4ee02a9..f3efba8da 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -85,7 +85,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for DeviceManager service @@ -214,39 +214,39 @@ var _DeviceManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorDevice, + Metadata: "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device.proto", } -func (m *DeviceIdentifier) Marshal() (data []byte, err error) { +func (m *DeviceIdentifier) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceIdentifier) MarshalTo(data []byte) (int, error) { +func (m *DeviceIdentifier) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.AppEui != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDevice(data, i, uint64(m.AppEui.Size())) - n1, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.AppEui.Size())) + n1, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.DevEui != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintDevice(data, i, uint64(m.DevEui.Size())) - n2, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.DevEui.Size())) + n2, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -255,164 +255,164 @@ func (m *DeviceIdentifier) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Device) Marshal() (data []byte, err error) { +func (m *Device) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Device) MarshalTo(data []byte) (int, error) { +func (m *Device) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.AppEui != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDevice(data, i, uint64(m.AppEui.Size())) - n3, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.AppEui.Size())) + n3, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n3 } if m.DevEui != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintDevice(data, i, uint64(m.DevEui.Size())) - n4, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.DevEui.Size())) + n4, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if len(m.AppId) > 0 { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintDevice(data, i, uint64(len(m.AppId))) - i += copy(data[i:], m.AppId) + i = encodeVarintDevice(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) } if len(m.DevId) > 0 { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintDevice(data, i, uint64(len(m.DevId))) - i += copy(data[i:], m.DevId) + i = encodeVarintDevice(dAtA, i, uint64(len(m.DevId))) + i += copy(dAtA[i:], m.DevId) } if m.DevAddr != nil { - data[i] = 0x2a + dAtA[i] = 0x2a i++ - i = encodeVarintDevice(data, i, uint64(m.DevAddr.Size())) - n5, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.DevAddr.Size())) + n5, err := m.DevAddr.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n5 } if m.NwkSKey != nil { - data[i] = 0x32 + dAtA[i] = 0x32 i++ - i = encodeVarintDevice(data, i, uint64(m.NwkSKey.Size())) - n6, err := m.NwkSKey.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.NwkSKey.Size())) + n6, err := m.NwkSKey.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n6 } if m.AppSKey != nil { - data[i] = 0x3a + dAtA[i] = 0x3a i++ - i = encodeVarintDevice(data, i, uint64(m.AppSKey.Size())) - n7, err := m.AppSKey.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.AppSKey.Size())) + n7, err := m.AppSKey.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n7 } if m.AppKey != nil { - data[i] = 0x42 + dAtA[i] = 0x42 i++ - i = encodeVarintDevice(data, i, uint64(m.AppKey.Size())) - n8, err := m.AppKey.MarshalTo(data[i:]) + i = encodeVarintDevice(dAtA, i, uint64(m.AppKey.Size())) + n8, err := m.AppKey.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n8 } if m.FCntUp != 0 { - data[i] = 0x48 + dAtA[i] = 0x48 i++ - i = encodeVarintDevice(data, i, uint64(m.FCntUp)) + i = encodeVarintDevice(dAtA, i, uint64(m.FCntUp)) } if m.FCntDown != 0 { - data[i] = 0x50 + dAtA[i] = 0x50 i++ - i = encodeVarintDevice(data, i, uint64(m.FCntDown)) + i = encodeVarintDevice(dAtA, i, uint64(m.FCntDown)) } if m.DisableFCntCheck { - data[i] = 0x58 + dAtA[i] = 0x58 i++ if m.DisableFCntCheck { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if m.Uses32BitFCnt { - data[i] = 0x60 + dAtA[i] = 0x60 i++ if m.Uses32BitFCnt { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if len(m.ActivationConstraints) > 0 { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintDevice(data, i, uint64(len(m.ActivationConstraints))) - i += copy(data[i:], m.ActivationConstraints) + i = encodeVarintDevice(dAtA, i, uint64(len(m.ActivationConstraints))) + i += copy(dAtA[i:], m.ActivationConstraints) } if m.LastSeen != 0 { - data[i] = 0xa8 + dAtA[i] = 0xa8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintDevice(data, i, uint64(m.LastSeen)) + i = encodeVarintDevice(dAtA, i, uint64(m.LastSeen)) } return i, nil } -func encodeFixed64Device(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Device(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Device(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Device(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintDevice(data []byte, offset int, v uint64) int { +func encodeVarintDevice(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *DeviceIdentifier) Size() (n int) { @@ -499,8 +499,8 @@ func sovDevice(x uint64) (n int) { func sozDevice(x uint64) (n int) { return sovDevice(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *DeviceIdentifier) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceIdentifier) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -512,7 +512,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -540,7 +540,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -556,7 +556,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -572,7 +572,7 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -588,13 +588,13 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDevice(data[iNdEx:]) + skippy, err := skipDevice(dAtA[iNdEx:]) if err != nil { return err } @@ -613,8 +613,8 @@ func (m *DeviceIdentifier) Unmarshal(data []byte) error { } return nil } -func (m *Device) Unmarshal(data []byte) error { - l := len(data) +func (m *Device) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -626,7 +626,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -654,7 +654,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -670,7 +670,7 @@ func (m *Device) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -686,7 +686,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -702,7 +702,7 @@ func (m *Device) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -718,7 +718,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -733,7 +733,7 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.AppId = string(data[iNdEx:postIndex]) + m.AppId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 4: if wireType != 2 { @@ -747,7 +747,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -762,7 +762,7 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DevId = string(data[iNdEx:postIndex]) + m.DevId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 5: if wireType != 2 { @@ -776,7 +776,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -792,7 +792,7 @@ func (m *Device) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevAddr m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -808,7 +808,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -824,7 +824,7 @@ func (m *Device) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey m.NwkSKey = &v - if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.NwkSKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -840,7 +840,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -856,7 +856,7 @@ func (m *Device) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppSKey m.AppSKey = &v - if err := m.AppSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppSKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -872,7 +872,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -888,7 +888,7 @@ func (m *Device) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppKey m.AppKey = &v - if err := m.AppKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -904,7 +904,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FCntUp |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -923,7 +923,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FCntDown |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -942,7 +942,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -962,7 +962,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -982,7 +982,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -997,7 +997,7 @@ func (m *Device) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.ActivationConstraints = string(data[iNdEx:postIndex]) + m.ActivationConstraints = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 21: if wireType != 0 { @@ -1011,7 +1011,7 @@ func (m *Device) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.LastSeen |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -1020,7 +1020,7 @@ func (m *Device) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipDevice(data[iNdEx:]) + skippy, err := skipDevice(dAtA[iNdEx:]) if err != nil { return err } @@ -1039,8 +1039,8 @@ func (m *Device) Unmarshal(data []byte) error { } return nil } -func skipDevice(data []byte) (n int, err error) { - l := len(data) +func skipDevice(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -1051,7 +1051,7 @@ func skipDevice(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1069,7 +1069,7 @@ func skipDevice(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -1086,7 +1086,7 @@ func skipDevice(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1109,7 +1109,7 @@ func skipDevice(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1120,7 +1120,7 @@ func skipDevice(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipDevice(data[start:]) + next, err := skipDevice(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/protocol/lorawan/device_address.pb.go b/api/protocol/lorawan/device_address.pb.go index edbd947d2..738aed402 100644 --- a/api/protocol/lorawan/device_address.pb.go +++ b/api/protocol/lorawan/device_address.pb.go @@ -109,7 +109,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for DevAddrManager service @@ -205,20 +205,20 @@ var _DevAddrManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorDeviceAddress, + Metadata: "github.com/TheThingsNetwork/ttn/api/protocol/lorawan/device_address.proto", } -func (m *PrefixesRequest) Marshal() (data []byte, err error) { +func (m *PrefixesRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *PrefixesRequest) MarshalTo(data []byte) (int, error) { +func (m *PrefixesRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -226,27 +226,27 @@ func (m *PrefixesRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *PrefixesResponse) Marshal() (data []byte, err error) { +func (m *PrefixesResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *PrefixesResponse) MarshalTo(data []byte) (int, error) { +func (m *PrefixesResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Prefixes) > 0 { for _, msg := range m.Prefixes { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDeviceAddress(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintDeviceAddress(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -256,98 +256,98 @@ func (m *PrefixesResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *PrefixesResponse_PrefixMapping) Marshal() (data []byte, err error) { +func (m *PrefixesResponse_PrefixMapping) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *PrefixesResponse_PrefixMapping) MarshalTo(data []byte) (int, error) { +func (m *PrefixesResponse_PrefixMapping) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Prefix) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDeviceAddress(data, i, uint64(len(m.Prefix))) - i += copy(data[i:], m.Prefix) + i = encodeVarintDeviceAddress(dAtA, i, uint64(len(m.Prefix))) + i += copy(dAtA[i:], m.Prefix) } if len(m.Usage) > 0 { for _, s := range m.Usage { - data[i] = 0x12 + dAtA[i] = 0x12 i++ l = len(s) for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) l >>= 7 i++ } - data[i] = uint8(l) + dAtA[i] = uint8(l) i++ - i += copy(data[i:], s) + i += copy(dAtA[i:], s) } } return i, nil } -func (m *DevAddrRequest) Marshal() (data []byte, err error) { +func (m *DevAddrRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DevAddrRequest) MarshalTo(data []byte) (int, error) { +func (m *DevAddrRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Usage) > 0 { for _, s := range m.Usage { - data[i] = 0xa + dAtA[i] = 0xa i++ l = len(s) for l >= 1<<7 { - data[i] = uint8(uint64(l)&0x7f | 0x80) + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) l >>= 7 i++ } - data[i] = uint8(l) + dAtA[i] = uint8(l) i++ - i += copy(data[i:], s) + i += copy(dAtA[i:], s) } } return i, nil } -func (m *DevAddrResponse) Marshal() (data []byte, err error) { +func (m *DevAddrResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DevAddrResponse) MarshalTo(data []byte) (int, error) { +func (m *DevAddrResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.DevAddr != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintDeviceAddress(data, i, uint64(m.DevAddr.Size())) - n1, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintDeviceAddress(dAtA, i, uint64(m.DevAddr.Size())) + n1, err := m.DevAddr.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -356,31 +356,31 @@ func (m *DevAddrResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func encodeFixed64DeviceAddress(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64DeviceAddress(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32DeviceAddress(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32DeviceAddress(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintDeviceAddress(data []byte, offset int, v uint64) int { +func encodeVarintDeviceAddress(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *PrefixesRequest) Size() (n int) { @@ -452,8 +452,8 @@ func sovDeviceAddress(x uint64) (n int) { func sozDeviceAddress(x uint64) (n int) { return sovDeviceAddress(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *PrefixesRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *PrefixesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -465,7 +465,7 @@ func (m *PrefixesRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -483,7 +483,7 @@ func (m *PrefixesRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipDeviceAddress(data[iNdEx:]) + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) if err != nil { return err } @@ -502,8 +502,8 @@ func (m *PrefixesRequest) Unmarshal(data []byte) error { } return nil } -func (m *PrefixesResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *PrefixesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -515,7 +515,7 @@ func (m *PrefixesResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -543,7 +543,7 @@ func (m *PrefixesResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -558,13 +558,13 @@ func (m *PrefixesResponse) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.Prefixes = append(m.Prefixes, &PrefixesResponse_PrefixMapping{}) - if err := m.Prefixes[len(m.Prefixes)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Prefixes[len(m.Prefixes)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDeviceAddress(data[iNdEx:]) + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) if err != nil { return err } @@ -583,8 +583,8 @@ func (m *PrefixesResponse) Unmarshal(data []byte) error { } return nil } -func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { - l := len(data) +func (m *PrefixesResponse_PrefixMapping) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -596,7 +596,7 @@ func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -624,7 +624,7 @@ func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -639,7 +639,7 @@ func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Prefix = string(data[iNdEx:postIndex]) + m.Prefix = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 2: if wireType != 2 { @@ -653,7 +653,7 @@ func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -668,11 +668,11 @@ func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Usage = append(m.Usage, string(data[iNdEx:postIndex])) + m.Usage = append(m.Usage, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDeviceAddress(data[iNdEx:]) + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) if err != nil { return err } @@ -691,8 +691,8 @@ func (m *PrefixesResponse_PrefixMapping) Unmarshal(data []byte) error { } return nil } -func (m *DevAddrRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *DevAddrRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -704,7 +704,7 @@ func (m *DevAddrRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -732,7 +732,7 @@ func (m *DevAddrRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -747,11 +747,11 @@ func (m *DevAddrRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Usage = append(m.Usage, string(data[iNdEx:postIndex])) + m.Usage = append(m.Usage, string(dAtA[iNdEx:postIndex])) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDeviceAddress(data[iNdEx:]) + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) if err != nil { return err } @@ -770,8 +770,8 @@ func (m *DevAddrRequest) Unmarshal(data []byte) error { } return nil } -func (m *DevAddrResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *DevAddrResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -783,7 +783,7 @@ func (m *DevAddrResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -811,7 +811,7 @@ func (m *DevAddrResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -827,13 +827,13 @@ func (m *DevAddrResponse) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevAddr m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipDeviceAddress(data[iNdEx:]) + skippy, err := skipDeviceAddress(dAtA[iNdEx:]) if err != nil { return err } @@ -852,8 +852,8 @@ func (m *DevAddrResponse) Unmarshal(data []byte) error { } return nil } -func skipDeviceAddress(data []byte) (n int, err error) { - l := len(data) +func skipDeviceAddress(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -864,7 +864,7 @@ func skipDeviceAddress(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -882,7 +882,7 @@ func skipDeviceAddress(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -899,7 +899,7 @@ func skipDeviceAddress(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -922,7 +922,7 @@ func skipDeviceAddress(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -933,7 +933,7 @@ func skipDeviceAddress(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipDeviceAddress(data[start:]) + next, err := skipDeviceAddress(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index 3f8bd1312..c7e93c9af 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -494,171 +494,171 @@ func init() { proto.RegisterEnum("lorawan.Major", Major_name, Major_value) proto.RegisterEnum("lorawan.MType", MType_name, MType_value) } -func (m *Metadata) Marshal() (data []byte, err error) { +func (m *Metadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Metadata) MarshalTo(data []byte) (int, error) { +func (m *Metadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Modulation != 0 { - data[i] = 0x58 + dAtA[i] = 0x58 i++ - i = encodeVarintLorawan(data, i, uint64(m.Modulation)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Modulation)) } if len(m.DataRate) > 0 { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DataRate))) - i += copy(data[i:], m.DataRate) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.DataRate))) + i += copy(dAtA[i:], m.DataRate) } if m.BitRate != 0 { - data[i] = 0x68 + dAtA[i] = 0x68 i++ - i = encodeVarintLorawan(data, i, uint64(m.BitRate)) + i = encodeVarintLorawan(dAtA, i, uint64(m.BitRate)) } if len(m.CodingRate) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.CodingRate))) - i += copy(data[i:], m.CodingRate) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.CodingRate))) + i += copy(dAtA[i:], m.CodingRate) } if m.FCnt != 0 { - data[i] = 0x78 + dAtA[i] = 0x78 i++ - i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + i = encodeVarintLorawan(dAtA, i, uint64(m.FCnt)) } return i, nil } -func (m *TxConfiguration) Marshal() (data []byte, err error) { +func (m *TxConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { +func (m *TxConfiguration) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Modulation != 0 { - data[i] = 0x58 + dAtA[i] = 0x58 i++ - i = encodeVarintLorawan(data, i, uint64(m.Modulation)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Modulation)) } if len(m.DataRate) > 0 { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.DataRate))) - i += copy(data[i:], m.DataRate) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.DataRate))) + i += copy(dAtA[i:], m.DataRate) } if m.BitRate != 0 { - data[i] = 0x68 + dAtA[i] = 0x68 i++ - i = encodeVarintLorawan(data, i, uint64(m.BitRate)) + i = encodeVarintLorawan(dAtA, i, uint64(m.BitRate)) } if len(m.CodingRate) > 0 { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.CodingRate))) - i += copy(data[i:], m.CodingRate) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.CodingRate))) + i += copy(dAtA[i:], m.CodingRate) } if m.FCnt != 0 { - data[i] = 0x78 + dAtA[i] = 0x78 i++ - i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + i = encodeVarintLorawan(dAtA, i, uint64(m.FCnt)) } return i, nil } -func (m *ActivationMetadata) Marshal() (data []byte, err error) { +func (m *ActivationMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { +func (m *ActivationMetadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.AppEui != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(m.AppEui.Size())) - n1, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.AppEui.Size())) + n1, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.DevEui != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(m.DevEui.Size())) - n2, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DevEui.Size())) + n2, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n2 } if m.DevAddr != nil { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n3, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DevAddr.Size())) + n3, err := m.DevAddr.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n3 } if m.NwkSKey != nil { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintLorawan(data, i, uint64(m.NwkSKey.Size())) - n4, err := m.NwkSKey.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.NwkSKey.Size())) + n4, err := m.NwkSKey.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if m.Rx1DrOffset != 0 { - data[i] = 0x58 + dAtA[i] = 0x58 i++ - i = encodeVarintLorawan(data, i, uint64(m.Rx1DrOffset)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx1DrOffset)) } if m.Rx2Dr != 0 { - data[i] = 0x60 + dAtA[i] = 0x60 i++ - i = encodeVarintLorawan(data, i, uint64(m.Rx2Dr)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx2Dr)) } if m.RxDelay != 0 { - data[i] = 0x68 + dAtA[i] = 0x68 i++ - i = encodeVarintLorawan(data, i, uint64(m.RxDelay)) + i = encodeVarintLorawan(dAtA, i, uint64(m.RxDelay)) } if m.CfList != nil { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintLorawan(data, i, uint64(m.CfList.Size())) - n5, err := m.CfList.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.CfList.Size())) + n5, err := m.CfList.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -667,37 +667,37 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Message) Marshal() (data []byte, err error) { +func (m *Message) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Message) MarshalTo(data []byte) (int, error) { +func (m *Message) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(m.MHDR.Size())) - n6, err := m.MHDR.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.MHDR.Size())) + n6, err := m.MHDR.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n6 if len(m.Mic) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.Mic))) - i += copy(data[i:], m.Mic) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.Mic))) + i += copy(dAtA[i:], m.Mic) } if m.Payload != nil { - nn7, err := m.Payload.MarshalTo(data[i:]) + nn7, err := m.Payload.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -706,13 +706,13 @@ func (m *Message) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Message_MacPayload) MarshalTo(data []byte) (int, error) { +func (m *Message_MacPayload) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.MacPayload != nil { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintLorawan(data, i, uint64(m.MacPayload.Size())) - n8, err := m.MacPayload.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.MacPayload.Size())) + n8, err := m.MacPayload.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -720,13 +720,13 @@ func (m *Message_MacPayload) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *Message_JoinRequestPayload) MarshalTo(data []byte) (int, error) { +func (m *Message_JoinRequestPayload) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.JoinRequestPayload != nil { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintLorawan(data, i, uint64(m.JoinRequestPayload.Size())) - n9, err := m.JoinRequestPayload.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.JoinRequestPayload.Size())) + n9, err := m.JoinRequestPayload.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -734,13 +734,13 @@ func (m *Message_JoinRequestPayload) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *Message_JoinAcceptPayload) MarshalTo(data []byte) (int, error) { +func (m *Message_JoinAcceptPayload) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.JoinAcceptPayload != nil { - data[i] = 0x2a + dAtA[i] = 0x2a i++ - i = encodeVarintLorawan(data, i, uint64(m.JoinAcceptPayload.Size())) - n10, err := m.JoinAcceptPayload.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.JoinAcceptPayload.Size())) + n10, err := m.JoinAcceptPayload.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -748,113 +748,113 @@ func (m *Message_JoinAcceptPayload) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *MHDR) Marshal() (data []byte, err error) { +func (m *MHDR) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *MHDR) MarshalTo(data []byte) (int, error) { +func (m *MHDR) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.MType != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintLorawan(data, i, uint64(m.MType)) + i = encodeVarintLorawan(dAtA, i, uint64(m.MType)) } if m.Major != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintLorawan(data, i, uint64(m.Major)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Major)) } return i, nil } -func (m *MACPayload) Marshal() (data []byte, err error) { +func (m *MACPayload) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *MACPayload) MarshalTo(data []byte) (int, error) { +func (m *MACPayload) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(m.FHDR.Size())) - n11, err := m.FHDR.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.FHDR.Size())) + n11, err := m.FHDR.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n11 if m.FPort != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintLorawan(data, i, uint64(m.FPort)) + i = encodeVarintLorawan(dAtA, i, uint64(m.FPort)) } if len(m.FrmPayload) > 0 { - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintLorawan(data, i, uint64(len(m.FrmPayload))) - i += copy(data[i:], m.FrmPayload) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.FrmPayload))) + i += copy(dAtA[i:], m.FrmPayload) } return i, nil } -func (m *FHDR) Marshal() (data []byte, err error) { +func (m *FHDR) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *FHDR) MarshalTo(data []byte) (int, error) { +func (m *FHDR) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n12, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DevAddr.Size())) + n12, err := m.DevAddr.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n12 - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(m.FCtrl.Size())) - n13, err := m.FCtrl.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.FCtrl.Size())) + n13, err := m.FCtrl.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n13 if m.FCnt != 0 { - data[i] = 0x18 + dAtA[i] = 0x18 i++ - i = encodeVarintLorawan(data, i, uint64(m.FCnt)) + i = encodeVarintLorawan(dAtA, i, uint64(m.FCnt)) } if len(m.FOpts) > 0 { for _, msg := range m.FOpts { - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintLorawan(data, i, uint64(msg.Size())) - n, err := msg.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -864,128 +864,128 @@ func (m *FHDR) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *FCtrl) Marshal() (data []byte, err error) { +func (m *FCtrl) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *FCtrl) MarshalTo(data []byte) (int, error) { +func (m *FCtrl) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Adr { - data[i] = 0x8 + dAtA[i] = 0x8 i++ if m.Adr { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if m.AdrAckReq { - data[i] = 0x10 + dAtA[i] = 0x10 i++ if m.AdrAckReq { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if m.Ack { - data[i] = 0x18 + dAtA[i] = 0x18 i++ if m.Ack { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } if m.FPending { - data[i] = 0x20 + dAtA[i] = 0x20 i++ if m.FPending { - data[i] = 1 + dAtA[i] = 1 } else { - data[i] = 0 + dAtA[i] = 0 } i++ } return i, nil } -func (m *MACCommand) Marshal() (data []byte, err error) { +func (m *MACCommand) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *MACCommand) MarshalTo(data []byte) (int, error) { +func (m *MACCommand) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Cid != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintLorawan(data, i, uint64(m.Cid)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Cid)) } if len(m.Payload) > 0 { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } return i, nil } -func (m *JoinRequestPayload) Marshal() (data []byte, err error) { +func (m *JoinRequestPayload) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *JoinRequestPayload) MarshalTo(data []byte) (int, error) { +func (m *JoinRequestPayload) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(m.AppEui.Size())) - n14, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.AppEui.Size())) + n14, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n14 - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(m.DevEui.Size())) - n15, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DevEui.Size())) + n15, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n15 - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintLorawan(data, i, uint64(m.DevNonce.Size())) - n16, err := m.DevNonce.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DevNonce.Size())) + n16, err := m.DevNonce.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -993,69 +993,69 @@ func (m *JoinRequestPayload) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *JoinAcceptPayload) Marshal() (data []byte, err error) { +func (m *JoinAcceptPayload) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *JoinAcceptPayload) MarshalTo(data []byte) (int, error) { +func (m *JoinAcceptPayload) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Encrypted) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(len(m.Encrypted))) - i += copy(data[i:], m.Encrypted) + i = encodeVarintLorawan(dAtA, i, uint64(len(m.Encrypted))) + i += copy(dAtA[i:], m.Encrypted) } - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintLorawan(data, i, uint64(m.AppNonce.Size())) - n17, err := m.AppNonce.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.AppNonce.Size())) + n17, err := m.AppNonce.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n17 - data[i] = 0x1a + dAtA[i] = 0x1a i++ - i = encodeVarintLorawan(data, i, uint64(m.NetId.Size())) - n18, err := m.NetId.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.NetId.Size())) + n18, err := m.NetId.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n18 - data[i] = 0x22 + dAtA[i] = 0x22 i++ - i = encodeVarintLorawan(data, i, uint64(m.DevAddr.Size())) - n19, err := m.DevAddr.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DevAddr.Size())) + n19, err := m.DevAddr.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n19 - data[i] = 0x2a + dAtA[i] = 0x2a i++ - i = encodeVarintLorawan(data, i, uint64(m.DLSettings.Size())) - n20, err := m.DLSettings.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.DLSettings.Size())) + n20, err := m.DLSettings.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n20 if m.RxDelay != 0 { - data[i] = 0x30 + dAtA[i] = 0x30 i++ - i = encodeVarintLorawan(data, i, uint64(m.RxDelay)) + i = encodeVarintLorawan(dAtA, i, uint64(m.RxDelay)) } if m.CfList != nil { - data[i] = 0x3a + dAtA[i] = 0x3a i++ - i = encodeVarintLorawan(data, i, uint64(m.CfList.Size())) - n21, err := m.CfList.MarshalTo(data[i:]) + i = encodeVarintLorawan(dAtA, i, uint64(m.CfList.Size())) + n21, err := m.CfList.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -1064,94 +1064,94 @@ func (m *JoinAcceptPayload) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DLSettings) Marshal() (data []byte, err error) { +func (m *DLSettings) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DLSettings) MarshalTo(data []byte) (int, error) { +func (m *DLSettings) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Rx1DrOffset != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintLorawan(data, i, uint64(m.Rx1DrOffset)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx1DrOffset)) } if m.Rx2Dr != 0 { - data[i] = 0x10 + dAtA[i] = 0x10 i++ - i = encodeVarintLorawan(data, i, uint64(m.Rx2Dr)) + i = encodeVarintLorawan(dAtA, i, uint64(m.Rx2Dr)) } return i, nil } -func (m *CFList) Marshal() (data []byte, err error) { +func (m *CFList) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *CFList) MarshalTo(data []byte) (int, error) { +func (m *CFList) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Freq) > 0 { - data23 := make([]byte, len(m.Freq)*10) + dAtA23 := make([]byte, len(m.Freq)*10) var j22 int for _, num := range m.Freq { for num >= 1<<7 { - data23[j22] = uint8(uint64(num)&0x7f | 0x80) + dAtA23[j22] = uint8(uint64(num)&0x7f | 0x80) num >>= 7 j22++ } - data23[j22] = uint8(num) + dAtA23[j22] = uint8(num) j22++ } - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintLorawan(data, i, uint64(j22)) - i += copy(data[i:], data23[:j22]) + i = encodeVarintLorawan(dAtA, i, uint64(j22)) + i += copy(dAtA[i:], dAtA23[:j22]) } return i, nil } -func encodeFixed64Lorawan(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Lorawan(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Lorawan(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Lorawan(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintLorawan(data []byte, offset int, v uint64) int { +func encodeVarintLorawan(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *Metadata) Size() (n int) { @@ -1429,8 +1429,8 @@ func sovLorawan(x uint64) (n int) { func sozLorawan(x uint64) (n int) { return sovLorawan(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Metadata) Unmarshal(data []byte) error { - l := len(data) +func (m *Metadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1442,7 +1442,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1470,7 +1470,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Modulation |= (Modulation(b) & 0x7F) << shift if b < 0x80 { @@ -1489,7 +1489,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1504,7 +1504,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DataRate = string(data[iNdEx:postIndex]) + m.DataRate = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 13: if wireType != 0 { @@ -1518,7 +1518,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.BitRate |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1537,7 +1537,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1552,7 +1552,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.CodingRate = string(data[iNdEx:postIndex]) + m.CodingRate = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 15: if wireType != 0 { @@ -1566,7 +1566,7 @@ func (m *Metadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1575,7 +1575,7 @@ func (m *Metadata) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -1594,8 +1594,8 @@ func (m *Metadata) Unmarshal(data []byte) error { } return nil } -func (m *TxConfiguration) Unmarshal(data []byte) error { - l := len(data) +func (m *TxConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1607,7 +1607,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1635,7 +1635,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Modulation |= (Modulation(b) & 0x7F) << shift if b < 0x80 { @@ -1654,7 +1654,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1669,7 +1669,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.DataRate = string(data[iNdEx:postIndex]) + m.DataRate = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 13: if wireType != 0 { @@ -1683,7 +1683,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.BitRate |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1702,7 +1702,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1717,7 +1717,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.CodingRate = string(data[iNdEx:postIndex]) + m.CodingRate = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex case 15: if wireType != 0 { @@ -1731,7 +1731,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1740,7 +1740,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -1759,8 +1759,8 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } return nil } -func (m *ActivationMetadata) Unmarshal(data []byte) error { - l := len(data) +func (m *ActivationMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1772,7 +1772,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1800,7 +1800,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1816,7 +1816,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1832,7 +1832,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1848,7 +1848,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1864,7 +1864,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1880,7 +1880,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevAddr m.DevAddr = &v - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1896,7 +1896,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1912,7 +1912,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.NwkSKey m.NwkSKey = &v - if err := m.NwkSKey.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.NwkSKey.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1928,7 +1928,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1947,7 +1947,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Rx2Dr |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1966,7 +1966,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.RxDelay |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -1985,7 +1985,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2002,13 +2002,13 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if m.CfList == nil { m.CfList = &CFList{} } - if err := m.CfList.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.CfList.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2027,8 +2027,8 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } return nil } -func (m *Message) Unmarshal(data []byte) error { - l := len(data) +func (m *Message) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2040,7 +2040,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2068,7 +2068,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2082,7 +2082,7 @@ func (m *Message) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.MHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.MHDR.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2098,7 +2098,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2112,7 +2112,7 @@ func (m *Message) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Mic = append(m.Mic[:0], data[iNdEx:postIndex]...) + m.Mic = append(m.Mic[:0], dAtA[iNdEx:postIndex]...) if m.Mic == nil { m.Mic = []byte{} } @@ -2129,7 +2129,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2144,7 +2144,7 @@ func (m *Message) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &MACPayload{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Payload = &Message_MacPayload{v} @@ -2161,7 +2161,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2176,7 +2176,7 @@ func (m *Message) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &JoinRequestPayload{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Payload = &Message_JoinRequestPayload{v} @@ -2193,7 +2193,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2208,14 +2208,14 @@ func (m *Message) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &JoinAcceptPayload{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Payload = &Message_JoinAcceptPayload{v} iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2234,8 +2234,8 @@ func (m *Message) Unmarshal(data []byte) error { } return nil } -func (m *MHDR) Unmarshal(data []byte) error { - l := len(data) +func (m *MHDR) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2247,7 +2247,7 @@ func (m *MHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2275,7 +2275,7 @@ func (m *MHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.MType |= (MType(b) & 0x7F) << shift if b < 0x80 { @@ -2294,7 +2294,7 @@ func (m *MHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Major |= (Major(b) & 0x7F) << shift if b < 0x80 { @@ -2303,7 +2303,7 @@ func (m *MHDR) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2322,8 +2322,8 @@ func (m *MHDR) Unmarshal(data []byte) error { } return nil } -func (m *MACPayload) Unmarshal(data []byte) error { - l := len(data) +func (m *MACPayload) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2335,7 +2335,7 @@ func (m *MACPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2363,7 +2363,7 @@ func (m *MACPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2377,7 +2377,7 @@ func (m *MACPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.FHDR.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.FHDR.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2393,7 +2393,7 @@ func (m *MACPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FPort |= (int32(b) & 0x7F) << shift if b < 0x80 { @@ -2412,7 +2412,7 @@ func (m *MACPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2426,14 +2426,14 @@ func (m *MACPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.FrmPayload = append(m.FrmPayload[:0], data[iNdEx:postIndex]...) + m.FrmPayload = append(m.FrmPayload[:0], dAtA[iNdEx:postIndex]...) if m.FrmPayload == nil { m.FrmPayload = []byte{} } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2452,8 +2452,8 @@ func (m *MACPayload) Unmarshal(data []byte) error { } return nil } -func (m *FHDR) Unmarshal(data []byte) error { - l := len(data) +func (m *FHDR) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2465,7 +2465,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2493,7 +2493,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2507,7 +2507,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2523,7 +2523,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2537,7 +2537,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.FCtrl.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.FCtrl.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2553,7 +2553,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.FCnt |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -2572,7 +2572,7 @@ func (m *FHDR) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2587,13 +2587,13 @@ func (m *FHDR) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } m.FOpts = append(m.FOpts, MACCommand{}) - if err := m.FOpts[len(m.FOpts)-1].Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.FOpts[len(m.FOpts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2612,8 +2612,8 @@ func (m *FHDR) Unmarshal(data []byte) error { } return nil } -func (m *FCtrl) Unmarshal(data []byte) error { - l := len(data) +func (m *FCtrl) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2625,7 +2625,7 @@ func (m *FCtrl) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2653,7 +2653,7 @@ func (m *FCtrl) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2673,7 +2673,7 @@ func (m *FCtrl) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2693,7 +2693,7 @@ func (m *FCtrl) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2713,7 +2713,7 @@ func (m *FCtrl) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2723,7 +2723,7 @@ func (m *FCtrl) Unmarshal(data []byte) error { m.FPending = bool(v != 0) default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2742,8 +2742,8 @@ func (m *FCtrl) Unmarshal(data []byte) error { } return nil } -func (m *MACCommand) Unmarshal(data []byte) error { - l := len(data) +func (m *MACCommand) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2755,7 +2755,7 @@ func (m *MACCommand) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2783,7 +2783,7 @@ func (m *MACCommand) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Cid |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -2802,7 +2802,7 @@ func (m *MACCommand) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2816,14 +2816,14 @@ func (m *MACCommand) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2842,8 +2842,8 @@ func (m *MACCommand) Unmarshal(data []byte) error { } return nil } -func (m *JoinRequestPayload) Unmarshal(data []byte) error { - l := len(data) +func (m *JoinRequestPayload) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2855,7 +2855,7 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2883,7 +2883,7 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2897,7 +2897,7 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2913,7 +2913,7 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2927,7 +2927,7 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2943,7 +2943,7 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2957,13 +2957,13 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DevNonce.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevNonce.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -2982,8 +2982,8 @@ func (m *JoinRequestPayload) Unmarshal(data []byte) error { } return nil } -func (m *JoinAcceptPayload) Unmarshal(data []byte) error { - l := len(data) +func (m *JoinAcceptPayload) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2995,7 +2995,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3023,7 +3023,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3037,7 +3037,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Encrypted = append(m.Encrypted[:0], data[iNdEx:postIndex]...) + m.Encrypted = append(m.Encrypted[:0], dAtA[iNdEx:postIndex]...) if m.Encrypted == nil { m.Encrypted = []byte{} } @@ -3054,7 +3054,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3068,7 +3068,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.AppNonce.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppNonce.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3084,7 +3084,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3098,7 +3098,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.NetId.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.NetId.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3114,7 +3114,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3128,7 +3128,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DevAddr.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevAddr.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3144,7 +3144,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3158,7 +3158,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.DLSettings.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DLSettings.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -3174,7 +3174,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.RxDelay |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -3193,7 +3193,7 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3210,13 +3210,13 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { if m.CfList == nil { m.CfList = &CFList{} } - if err := m.CfList.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.CfList.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -3235,8 +3235,8 @@ func (m *JoinAcceptPayload) Unmarshal(data []byte) error { } return nil } -func (m *DLSettings) Unmarshal(data []byte) error { - l := len(data) +func (m *DLSettings) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3248,7 +3248,7 @@ func (m *DLSettings) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3276,7 +3276,7 @@ func (m *DLSettings) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Rx1DrOffset |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -3295,7 +3295,7 @@ func (m *DLSettings) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.Rx2Dr |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -3304,7 +3304,7 @@ func (m *DLSettings) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -3323,8 +3323,8 @@ func (m *DLSettings) Unmarshal(data []byte) error { } return nil } -func (m *CFList) Unmarshal(data []byte) error { - l := len(data) +func (m *CFList) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -3336,7 +3336,7 @@ func (m *CFList) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3362,7 +3362,7 @@ func (m *CFList) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ packedLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3385,7 +3385,7 @@ func (m *CFList) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -3403,7 +3403,7 @@ func (m *CFList) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ v |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -3416,7 +3416,7 @@ func (m *CFList) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipLorawan(data[iNdEx:]) + skippy, err := skipLorawan(dAtA[iNdEx:]) if err != nil { return err } @@ -3435,8 +3435,8 @@ func (m *CFList) Unmarshal(data []byte) error { } return nil } -func skipLorawan(data []byte) (n int, err error) { - l := len(data) +func skipLorawan(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -3447,7 +3447,7 @@ func skipLorawan(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3465,7 +3465,7 @@ func skipLorawan(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -3482,7 +3482,7 @@ func skipLorawan(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -3505,7 +3505,7 @@ func skipLorawan(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -3516,7 +3516,7 @@ func skipLorawan(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipLorawan(data[start:]) + next, err := skipLorawan(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/protocol/protocol.pb.go b/api/protocol/protocol.pb.go index 4e6ac8519..f4c9d2e6f 100644 --- a/api/protocol/protocol.pb.go +++ b/api/protocol/protocol.pb.go @@ -408,23 +408,23 @@ func init() { proto.RegisterType((*TxConfiguration)(nil), "protocol.TxConfiguration") proto.RegisterType((*ActivationMetadata)(nil), "protocol.ActivationMetadata") } -func (m *Message) Marshal() (data []byte, err error) { +func (m *Message) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Message) MarshalTo(data []byte) (int, error) { +func (m *Message) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Protocol != nil { - nn1, err := m.Protocol.MarshalTo(data[i:]) + nn1, err := m.Protocol.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -433,13 +433,13 @@ func (m *Message) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Message_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *Message_Lorawan) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.Lorawan != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) - n2, err := m.Lorawan.MarshalTo(data[i:]) + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n2, err := m.Lorawan.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -447,23 +447,23 @@ func (m *Message_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *RxMetadata) Marshal() (data []byte, err error) { +func (m *RxMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *RxMetadata) MarshalTo(data []byte) (int, error) { +func (m *RxMetadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Protocol != nil { - nn3, err := m.Protocol.MarshalTo(data[i:]) + nn3, err := m.Protocol.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -472,13 +472,13 @@ func (m *RxMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *RxMetadata_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *RxMetadata_Lorawan) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.Lorawan != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) - n4, err := m.Lorawan.MarshalTo(data[i:]) + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n4, err := m.Lorawan.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -486,23 +486,23 @@ func (m *RxMetadata_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *TxConfiguration) Marshal() (data []byte, err error) { +func (m *TxConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { +func (m *TxConfiguration) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Protocol != nil { - nn5, err := m.Protocol.MarshalTo(data[i:]) + nn5, err := m.Protocol.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -511,13 +511,13 @@ func (m *TxConfiguration) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *TxConfiguration_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *TxConfiguration_Lorawan) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.Lorawan != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) - n6, err := m.Lorawan.MarshalTo(data[i:]) + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n6, err := m.Lorawan.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -525,23 +525,23 @@ func (m *TxConfiguration_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func (m *ActivationMetadata) Marshal() (data []byte, err error) { +func (m *ActivationMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { +func (m *ActivationMetadata) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.Protocol != nil { - nn7, err := m.Protocol.MarshalTo(data[i:]) + nn7, err := m.Protocol.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -550,13 +550,13 @@ func (m *ActivationMetadata) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { +func (m *ActivationMetadata_Lorawan) MarshalTo(dAtA []byte) (int, error) { i := 0 if m.Lorawan != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintProtocol(data, i, uint64(m.Lorawan.Size())) - n8, err := m.Lorawan.MarshalTo(data[i:]) + i = encodeVarintProtocol(dAtA, i, uint64(m.Lorawan.Size())) + n8, err := m.Lorawan.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -564,31 +564,31 @@ func (m *ActivationMetadata_Lorawan) MarshalTo(data []byte) (int, error) { } return i, nil } -func encodeFixed64Protocol(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Protocol(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Protocol(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Protocol(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintProtocol(data []byte, offset int, v uint64) int { +func encodeVarintProtocol(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *Message) Size() (n int) { @@ -677,8 +677,8 @@ func sovProtocol(x uint64) (n int) { func sozProtocol(x uint64) (n int) { return sovProtocol(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *Message) Unmarshal(data []byte) error { - l := len(data) +func (m *Message) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -690,7 +690,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -718,7 +718,7 @@ func (m *Message) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -733,14 +733,14 @@ func (m *Message) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &lorawan.Message{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Protocol = &Message_Lorawan{v} iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipProtocol(data[iNdEx:]) + skippy, err := skipProtocol(dAtA[iNdEx:]) if err != nil { return err } @@ -759,8 +759,8 @@ func (m *Message) Unmarshal(data []byte) error { } return nil } -func (m *RxMetadata) Unmarshal(data []byte) error { - l := len(data) +func (m *RxMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -772,7 +772,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -800,7 +800,7 @@ func (m *RxMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -815,14 +815,14 @@ func (m *RxMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &lorawan.Metadata{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Protocol = &RxMetadata_Lorawan{v} iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipProtocol(data[iNdEx:]) + skippy, err := skipProtocol(dAtA[iNdEx:]) if err != nil { return err } @@ -841,8 +841,8 @@ func (m *RxMetadata) Unmarshal(data []byte) error { } return nil } -func (m *TxConfiguration) Unmarshal(data []byte) error { - l := len(data) +func (m *TxConfiguration) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -854,7 +854,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -882,7 +882,7 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -897,14 +897,14 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &lorawan.TxConfiguration{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Protocol = &TxConfiguration_Lorawan{v} iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipProtocol(data[iNdEx:]) + skippy, err := skipProtocol(dAtA[iNdEx:]) if err != nil { return err } @@ -923,8 +923,8 @@ func (m *TxConfiguration) Unmarshal(data []byte) error { } return nil } -func (m *ActivationMetadata) Unmarshal(data []byte) error { - l := len(data) +func (m *ActivationMetadata) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -936,7 +936,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -964,7 +964,7 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -979,14 +979,14 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { return io.ErrUnexpectedEOF } v := &lorawan.ActivationMetadata{} - if err := v.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } m.Protocol = &ActivationMetadata_Lorawan{v} iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipProtocol(data[iNdEx:]) + skippy, err := skipProtocol(dAtA[iNdEx:]) if err != nil { return err } @@ -1005,8 +1005,8 @@ func (m *ActivationMetadata) Unmarshal(data []byte) error { } return nil } -func skipProtocol(data []byte) (n int, err error) { - l := len(data) +func skipProtocol(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -1017,7 +1017,7 @@ func skipProtocol(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1035,7 +1035,7 @@ func skipProtocol(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -1052,7 +1052,7 @@ func skipProtocol(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1075,7 +1075,7 @@ func skipProtocol(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1086,7 +1086,7 @@ func skipProtocol(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipProtocol(data[start:]) + next, err := skipProtocol(dAtA[start:]) if err != nil { return 0, err } diff --git a/api/router/router.pb.go b/api/router/router.pb.go index b462433fc..e5c39fb0a 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -290,7 +290,7 @@ var _ grpc.ClientConn // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -const _ = grpc.SupportPackageIsVersion3 +const _ = grpc.SupportPackageIsVersion4 // Client API for Router service @@ -556,7 +556,7 @@ var _Router_serviceDesc = grpc.ServiceDesc{ ServerStreams: true, }, }, - Metadata: fileDescriptorRouter, + Metadata: "github.com/TheThingsNetwork/ttn/api/router/router.proto", } // Client API for RouterManager service @@ -657,20 +657,20 @@ var _RouterManager_serviceDesc = grpc.ServiceDesc{ }, }, Streams: []grpc.StreamDesc{}, - Metadata: fileDescriptorRouter, + Metadata: "github.com/TheThingsNetwork/ttn/api/router/router.proto", } -func (m *SubscribeRequest) Marshal() (data []byte, err error) { +func (m *SubscribeRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { +func (m *SubscribeRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -678,52 +678,52 @@ func (m *SubscribeRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *UplinkMessage) Marshal() (data []byte, err error) { +func (m *UplinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { +func (m *UplinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintRouter(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.Message.Size())) - n1, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Message.Size())) + n1, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n1 } if m.ProtocolMetadata != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) - n2, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n2, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n2 } if m.GatewayMetadata != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) - n3, err := m.GatewayMetadata.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayMetadata.Size())) + n3, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -732,52 +732,52 @@ func (m *UplinkMessage) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DownlinkMessage) Marshal() (data []byte, err error) { +func (m *DownlinkMessage) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { +func (m *DownlinkMessage) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintRouter(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.Message.Size())) - n4, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Message.Size())) + n4, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n4 } if m.ProtocolConfiguration != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintRouter(data, i, uint64(m.ProtocolConfiguration.Size())) - n5, err := m.ProtocolConfiguration.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.ProtocolConfiguration.Size())) + n5, err := m.ProtocolConfiguration.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n5 } if m.GatewayConfiguration != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayConfiguration.Size())) - n6, err := m.GatewayConfiguration.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayConfiguration.Size())) + n6, err := m.GatewayConfiguration.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -786,88 +786,88 @@ func (m *DownlinkMessage) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DeviceActivationRequest) Marshal() (data []byte, err error) { +func (m *DeviceActivationRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { +func (m *DeviceActivationRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.Payload) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.Payload))) - i += copy(data[i:], m.Payload) + i = encodeVarintRouter(dAtA, i, uint64(len(m.Payload))) + i += copy(dAtA[i:], m.Payload) } if m.Message != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.Message.Size())) - n7, err := m.Message.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Message.Size())) + n7, err := m.Message.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n7 } if m.DevEui != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintRouter(data, i, uint64(m.DevEui.Size())) - n8, err := m.DevEui.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.DevEui.Size())) + n8, err := m.DevEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n8 } if m.AppEui != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintRouter(data, i, uint64(m.AppEui.Size())) - n9, err := m.AppEui.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.AppEui.Size())) + n9, err := m.AppEui.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n9 } if m.ProtocolMetadata != nil { - data[i] = 0xaa + dAtA[i] = 0xaa i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintRouter(data, i, uint64(m.ProtocolMetadata.Size())) - n10, err := m.ProtocolMetadata.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.ProtocolMetadata.Size())) + n10, err := m.ProtocolMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n10 } if m.GatewayMetadata != nil { - data[i] = 0xb2 + dAtA[i] = 0xb2 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayMetadata.Size())) - n11, err := m.GatewayMetadata.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayMetadata.Size())) + n11, err := m.GatewayMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n11 } if m.ActivationMetadata != nil { - data[i] = 0xba + dAtA[i] = 0xba i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintRouter(data, i, uint64(m.ActivationMetadata.Size())) - n12, err := m.ActivationMetadata.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.ActivationMetadata.Size())) + n12, err := m.ActivationMetadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -876,17 +876,17 @@ func (m *DeviceActivationRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *DeviceActivationResponse) Marshal() (data []byte, err error) { +func (m *DeviceActivationResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { +func (m *DeviceActivationResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -894,55 +894,55 @@ func (m *DeviceActivationResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *GatewayStatusRequest) Marshal() (data []byte, err error) { +func (m *GatewayStatusRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *GatewayStatusRequest) MarshalTo(data []byte) (int, error) { +func (m *GatewayStatusRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if len(m.GatewayId) > 0 { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(len(m.GatewayId))) - i += copy(data[i:], m.GatewayId) + i = encodeVarintRouter(dAtA, i, uint64(len(m.GatewayId))) + i += copy(dAtA[i:], m.GatewayId) } return i, nil } -func (m *GatewayStatusResponse) Marshal() (data []byte, err error) { +func (m *GatewayStatusResponse) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { +func (m *GatewayStatusResponse) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.LastSeen != 0 { - data[i] = 0x8 + dAtA[i] = 0x8 i++ - i = encodeVarintRouter(data, i, uint64(m.LastSeen)) + i = encodeVarintRouter(dAtA, i, uint64(m.LastSeen)) } if m.Status != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.Status.Size())) - n13, err := m.Status.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Status.Size())) + n13, err := m.Status.MarshalTo(dAtA[i:]) if err != nil { return 0, err } @@ -951,17 +951,17 @@ func (m *GatewayStatusResponse) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *StatusRequest) Marshal() (data []byte, err error) { +func (m *StatusRequest) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *StatusRequest) MarshalTo(data []byte) (int, error) { +func (m *StatusRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -969,123 +969,123 @@ func (m *StatusRequest) MarshalTo(data []byte) (int, error) { return i, nil } -func (m *Status) Marshal() (data []byte, err error) { +func (m *Status) Marshal() (dAtA []byte, err error) { size := m.Size() - data = make([]byte, size) - n, err := m.MarshalTo(data) + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) if err != nil { return nil, err } - return data[:n], nil + return dAtA[:n], nil } -func (m *Status) MarshalTo(data []byte) (int, error) { +func (m *Status) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int _ = l if m.System != nil { - data[i] = 0xa + dAtA[i] = 0xa i++ - i = encodeVarintRouter(data, i, uint64(m.System.Size())) - n14, err := m.System.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.System.Size())) + n14, err := m.System.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n14 } if m.Component != nil { - data[i] = 0x12 + dAtA[i] = 0x12 i++ - i = encodeVarintRouter(data, i, uint64(m.Component.Size())) - n15, err := m.Component.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Component.Size())) + n15, err := m.Component.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n15 } if m.GatewayStatus != nil { - data[i] = 0x5a + dAtA[i] = 0x5a i++ - i = encodeVarintRouter(data, i, uint64(m.GatewayStatus.Size())) - n16, err := m.GatewayStatus.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.GatewayStatus.Size())) + n16, err := m.GatewayStatus.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n16 } if m.Uplink != nil { - data[i] = 0x62 + dAtA[i] = 0x62 i++ - i = encodeVarintRouter(data, i, uint64(m.Uplink.Size())) - n17, err := m.Uplink.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Uplink.Size())) + n17, err := m.Uplink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n17 } if m.Downlink != nil { - data[i] = 0x6a + dAtA[i] = 0x6a i++ - i = encodeVarintRouter(data, i, uint64(m.Downlink.Size())) - n18, err := m.Downlink.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Downlink.Size())) + n18, err := m.Downlink.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n18 } if m.Activations != nil { - data[i] = 0x72 + dAtA[i] = 0x72 i++ - i = encodeVarintRouter(data, i, uint64(m.Activations.Size())) - n19, err := m.Activations.MarshalTo(data[i:]) + i = encodeVarintRouter(dAtA, i, uint64(m.Activations.Size())) + n19, err := m.Activations.MarshalTo(dAtA[i:]) if err != nil { return 0, err } i += n19 } if m.ConnectedGateways != 0 { - data[i] = 0xa8 + dAtA[i] = 0xa8 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintRouter(data, i, uint64(m.ConnectedGateways)) + i = encodeVarintRouter(dAtA, i, uint64(m.ConnectedGateways)) } if m.ConnectedBrokers != 0 { - data[i] = 0xb0 + dAtA[i] = 0xb0 i++ - data[i] = 0x1 + dAtA[i] = 0x1 i++ - i = encodeVarintRouter(data, i, uint64(m.ConnectedBrokers)) + i = encodeVarintRouter(dAtA, i, uint64(m.ConnectedBrokers)) } return i, nil } -func encodeFixed64Router(data []byte, offset int, v uint64) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) - data[offset+4] = uint8(v >> 32) - data[offset+5] = uint8(v >> 40) - data[offset+6] = uint8(v >> 48) - data[offset+7] = uint8(v >> 56) +func encodeFixed64Router(dAtA []byte, offset int, v uint64) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) + dAtA[offset+4] = uint8(v >> 32) + dAtA[offset+5] = uint8(v >> 40) + dAtA[offset+6] = uint8(v >> 48) + dAtA[offset+7] = uint8(v >> 56) return offset + 8 } -func encodeFixed32Router(data []byte, offset int, v uint32) int { - data[offset] = uint8(v) - data[offset+1] = uint8(v >> 8) - data[offset+2] = uint8(v >> 16) - data[offset+3] = uint8(v >> 24) +func encodeFixed32Router(dAtA []byte, offset int, v uint32) int { + dAtA[offset] = uint8(v) + dAtA[offset+1] = uint8(v >> 8) + dAtA[offset+2] = uint8(v >> 16) + dAtA[offset+3] = uint8(v >> 24) return offset + 4 } -func encodeVarintRouter(data []byte, offset int, v uint64) int { +func encodeVarintRouter(dAtA []byte, offset int, v uint64) int { for v >= 1<<7 { - data[offset] = uint8(v&0x7f | 0x80) + dAtA[offset] = uint8(v&0x7f | 0x80) v >>= 7 offset++ } - data[offset] = uint8(v) + dAtA[offset] = uint8(v) return offset + 1 } func (m *SubscribeRequest) Size() (n int) { @@ -1256,8 +1256,8 @@ func sovRouter(x uint64) (n int) { func sozRouter(x uint64) (n int) { return sovRouter(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } -func (m *SubscribeRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *SubscribeRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1269,7 +1269,7 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1287,7 +1287,7 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -1306,8 +1306,8 @@ func (m *SubscribeRequest) Unmarshal(data []byte) error { } return nil } -func (m *UplinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *UplinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1319,7 +1319,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1347,7 +1347,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1361,7 +1361,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -1378,7 +1378,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1395,7 +1395,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1411,7 +1411,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1428,7 +1428,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if m.ProtocolMetadata == nil { m.ProtocolMetadata = &protocol.RxMetadata{} } - if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1444,7 +1444,7 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1461,13 +1461,13 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { if m.GatewayMetadata == nil { m.GatewayMetadata = &gateway.RxMetadata{} } - if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -1486,8 +1486,8 @@ func (m *UplinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *DownlinkMessage) Unmarshal(data []byte) error { - l := len(data) +func (m *DownlinkMessage) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1499,7 +1499,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1527,7 +1527,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1541,7 +1541,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -1558,7 +1558,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1575,7 +1575,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1591,7 +1591,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1608,7 +1608,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if m.ProtocolConfiguration == nil { m.ProtocolConfiguration = &protocol.TxConfiguration{} } - if err := m.ProtocolConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1624,7 +1624,7 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1641,13 +1641,13 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { if m.GatewayConfiguration == nil { m.GatewayConfiguration = &gateway.TxConfiguration{} } - if err := m.GatewayConfiguration.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayConfiguration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -1666,8 +1666,8 @@ func (m *DownlinkMessage) Unmarshal(data []byte) error { } return nil } -func (m *DeviceActivationRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceActivationRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1679,7 +1679,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1707,7 +1707,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1721,7 +1721,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Payload = append(m.Payload[:0], data[iNdEx:postIndex]...) + m.Payload = append(m.Payload[:0], dAtA[iNdEx:postIndex]...) if m.Payload == nil { m.Payload = []byte{} } @@ -1738,7 +1738,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1755,7 +1755,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.Message == nil { m.Message = &protocol.Message{} } - if err := m.Message.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Message.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1771,7 +1771,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1787,7 +1787,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.DevEUI m.DevEui = &v - if err := m.DevEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.DevEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1803,7 +1803,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1819,7 +1819,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { } var v github_com_TheThingsNetwork_ttn_core_types.AppEUI m.AppEui = &v - if err := m.AppEui.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.AppEui.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1835,7 +1835,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1852,7 +1852,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.ProtocolMetadata == nil { m.ProtocolMetadata = &protocol.RxMetadata{} } - if err := m.ProtocolMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ProtocolMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1868,7 +1868,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1885,7 +1885,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.GatewayMetadata == nil { m.GatewayMetadata = &gateway.RxMetadata{} } - if err := m.GatewayMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1901,7 +1901,7 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -1918,13 +1918,13 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { if m.ActivationMetadata == nil { m.ActivationMetadata = &protocol.ActivationMetadata{} } - if err := m.ActivationMetadata.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.ActivationMetadata.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -1943,8 +1943,8 @@ func (m *DeviceActivationRequest) Unmarshal(data []byte) error { } return nil } -func (m *DeviceActivationResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *DeviceActivationResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -1956,7 +1956,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -1974,7 +1974,7 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -1993,8 +1993,8 @@ func (m *DeviceActivationResponse) Unmarshal(data []byte) error { } return nil } -func (m *GatewayStatusRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *GatewayStatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2006,7 +2006,7 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2034,7 +2034,7 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ stringLen |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2049,11 +2049,11 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.GatewayId = string(data[iNdEx:postIndex]) + m.GatewayId = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -2072,8 +2072,8 @@ func (m *GatewayStatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *GatewayStatusResponse) Unmarshal(data []byte) error { - l := len(data) +func (m *GatewayStatusResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2085,7 +2085,7 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2113,7 +2113,7 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.LastSeen |= (int64(b) & 0x7F) << shift if b < 0x80 { @@ -2132,7 +2132,7 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2149,13 +2149,13 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { if m.Status == nil { m.Status = &gateway.Status{} } - if err := m.Status.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Status.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -2174,8 +2174,8 @@ func (m *GatewayStatusResponse) Unmarshal(data []byte) error { } return nil } -func (m *StatusRequest) Unmarshal(data []byte) error { - l := len(data) +func (m *StatusRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2187,7 +2187,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2205,7 +2205,7 @@ func (m *StatusRequest) Unmarshal(data []byte) error { switch fieldNum { default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -2224,8 +2224,8 @@ func (m *StatusRequest) Unmarshal(data []byte) error { } return nil } -func (m *Status) Unmarshal(data []byte) error { - l := len(data) +func (m *Status) Unmarshal(dAtA []byte) error { + l := len(dAtA) iNdEx := 0 for iNdEx < l { preIndex := iNdEx @@ -2237,7 +2237,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2265,7 +2265,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2282,7 +2282,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.System == nil { m.System = &api.SystemStats{} } - if err := m.System.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.System.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2298,7 +2298,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2315,7 +2315,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Component == nil { m.Component = &api.ComponentStats{} } - if err := m.Component.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Component.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2331,7 +2331,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2348,7 +2348,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.GatewayStatus == nil { m.GatewayStatus = &api.Rates{} } - if err := m.GatewayStatus.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.GatewayStatus.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2364,7 +2364,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2381,7 +2381,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Uplink == nil { m.Uplink = &api.Rates{} } - if err := m.Uplink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Uplink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2397,7 +2397,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2414,7 +2414,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Downlink == nil { m.Downlink = &api.Rates{} } - if err := m.Downlink.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Downlink.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2430,7 +2430,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ msglen |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2447,7 +2447,7 @@ func (m *Status) Unmarshal(data []byte) error { if m.Activations == nil { m.Activations = &api.Rates{} } - if err := m.Activations.Unmarshal(data[iNdEx:postIndex]); err != nil { + if err := m.Activations.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -2463,7 +2463,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.ConnectedGateways |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -2482,7 +2482,7 @@ func (m *Status) Unmarshal(data []byte) error { if iNdEx >= l { return io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ m.ConnectedBrokers |= (uint32(b) & 0x7F) << shift if b < 0x80 { @@ -2491,7 +2491,7 @@ func (m *Status) Unmarshal(data []byte) error { } default: iNdEx = preIndex - skippy, err := skipRouter(data[iNdEx:]) + skippy, err := skipRouter(dAtA[iNdEx:]) if err != nil { return err } @@ -2510,8 +2510,8 @@ func (m *Status) Unmarshal(data []byte) error { } return nil } -func skipRouter(data []byte) (n int, err error) { - l := len(data) +func skipRouter(dAtA []byte) (n int, err error) { + l := len(dAtA) iNdEx := 0 for iNdEx < l { var wire uint64 @@ -2522,7 +2522,7 @@ func skipRouter(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ wire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2540,7 +2540,7 @@ func skipRouter(data []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } iNdEx++ - if data[iNdEx-1] < 0x80 { + if dAtA[iNdEx-1] < 0x80 { break } } @@ -2557,7 +2557,7 @@ func skipRouter(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ length |= (int(b) & 0x7F) << shift if b < 0x80 { @@ -2580,7 +2580,7 @@ func skipRouter(data []byte) (n int, err error) { if iNdEx >= l { return 0, io.ErrUnexpectedEOF } - b := data[iNdEx] + b := dAtA[iNdEx] iNdEx++ innerWire |= (uint64(b) & 0x7F) << shift if b < 0x80 { @@ -2591,7 +2591,7 @@ func skipRouter(data []byte) (n int, err error) { if innerWireType == 4 { break } - next, err := skipRouter(data[start:]) + next, err := skipRouter(dAtA[start:]) if err != nil { return 0, err } diff --git a/vendor/vendor.json b/vendor/vendor.json index 8f3fab8a3..02ccd71b8 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -65,8 +65,8 @@ { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", - "revision": "be802c4b3aa5d49dec780d4d5e2ba1e9bd25dba0", - "revisionTime": "2016-10-21T11:29:37Z" + "revision": "364a5d5b165140adfe751763e651fca78ea5e7ed", + "revisionTime": "2016-11-01T08:25:08Z" }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", @@ -99,10 +99,10 @@ "revisionTime": "2016-10-01T16:31:30Z" }, { - "checksumSHA1": "hqQV+0W1kp33/SBnZDlJkZ98Jcs=", + "checksumSHA1": "eKlcnwFZSIut8pNEvkLrm3entgc=", "path": "github.com/bluele/gcache", - "revision": "b8e8e90c3718b76ff835a8b78ddcc859815a0222", - "revisionTime": "2016-10-07T16:21:42Z" + "revision": "740e70e1c445f08ae353214243a4fb0fbe970510", + "revisionTime": "2016-10-27T05:39:47Z" }, { "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", @@ -117,10 +117,10 @@ "revisionTime": "2016-09-30T18:12:03Z" }, { - "checksumSHA1": "EsJksAKyY2+r3mY+rdNocSCe1wg=", + "checksumSHA1": "2Fy1Y6Z3lRRX1891WF/+HT4XS2I=", "path": "github.com/dgrijalva/jwt-go", - "revision": "24c63f56522a87ec5339cc3567883f1039378fdb", - "revisionTime": "2016-08-31T18:35:34Z" + "revision": "9ed569b5d1ac936e6494082958d63a6aa4fff99a", + "revisionTime": "2016-11-01T19:39:35Z" }, { "checksumSHA1": "PPyNjcW5yfrfXvARE74fh9JBSpo=", @@ -141,10 +141,10 @@ "revisionTime": "2016-08-07T23:55:29Z" }, { - "checksumSHA1": "nmc2qcdbg17Ny8tHeEDQuphV6jA=", + "checksumSHA1": "hveFTNQ9YEyYRs6SWuXM+XU9qRI=", "path": "github.com/fsnotify/fsnotify", - "revision": "bd2828f9f176e52d7222e565abb2d338d3f3c103", - "revisionTime": "2016-10-13T01:22:19Z" + "revision": "fd9ec7deca8bf46ecd2a795baaacf2b3a9be1197", + "revisionTime": "2016-10-26T20:31:22Z" }, { "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", @@ -161,20 +161,20 @@ { "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", "path": "github.com/gogo/protobuf/gogoproto", - "revision": "50d1bd39ce4e7a96b75e3e040be9caf79dbb4c61", - "revisionTime": "2016-10-14T17:32:44Z" + "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", + "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "2eIRbxeMUEF1ZnOanJhXmFR7pFs=", + "checksumSHA1": "oDNpaVF/BHUyme8A7RoBAH2q6is=", "path": "github.com/gogo/protobuf/proto", - "revision": "50d1bd39ce4e7a96b75e3e040be9caf79dbb4c61", - "revisionTime": "2016-10-14T17:32:44Z" + "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", + "revisionTime": "2016-11-05T09:51:13Z" }, { "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", - "revision": "50d1bd39ce4e7a96b75e3e040be9caf79dbb4c61", - "revisionTime": "2016-10-14T17:32:44Z" + "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", + "revisionTime": "2016-11-05T09:51:13Z" }, { "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", @@ -185,26 +185,26 @@ { "checksumSHA1": "+HPilCYNEcR4B/Q13LiW3OF3i64=", "path": "github.com/golang/protobuf/jsonpb", - "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", - "revisionTime": "2016-10-12T20:53:35Z" + "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", + "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "SVXOQdpDBh0ihdZ5aIflgdA+Rpw=", + "checksumSHA1": "oJGkod8y/3GTbKfazzQM+nQOPR0=", "path": "github.com/golang/protobuf/proto", - "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", - "revisionTime": "2016-10-12T20:53:35Z" + "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", + "revisionTime": "2016-11-03T22:44:32Z" }, { "checksumSHA1": "juNiTc9bfhQYo4BkWc83sW4Z5gw=", "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", - "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", - "revisionTime": "2016-10-12T20:53:35Z" + "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", + "revisionTime": "2016-11-03T22:44:32Z" }, { "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", - "revision": "98fa357170587e470c5f27d3c3ea0947b71eb455", - "revisionTime": "2016-10-12T20:53:35Z" + "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", + "revisionTime": "2016-11-03T22:44:32Z" }, { "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", @@ -225,82 +225,82 @@ "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "+1AwpnM6pIDftpw+iE1/hj/BOXo=", + "checksumSHA1": "mJuOZXdE9GBhUTHeQ/Wm8U1rR4I=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", - "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", - "revisionTime": "2016-10-21T05:32:21Z" + "revision": "84398b94e188ee336f307779b57b3aa91af7063c", + "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "GN9lH15GbtcX7xx70TjRYXi23Uk=", + "checksumSHA1": "7K3QiKeoZ0tgA8ymV9vicmfA97Q=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", - "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", - "revisionTime": "2016-10-21T05:32:21Z" + "revision": "84398b94e188ee336f307779b57b3aa91af7063c", + "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "D+k9iBM97p4RvkjpogAJevV8hmw=", + "checksumSHA1": "mYOr8zd2ww05AQ59oq/xdKL+mJo=", "path": "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api", - "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", - "revisionTime": "2016-10-21T05:32:21Z" + "revision": "84398b94e188ee336f307779b57b3aa91af7063c", + "revisionTime": "2016-11-05T22:35:13Z" }, { "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", - "revision": "1185a73676418404924d2bcd707a2a40ee7176eb", - "revisionTime": "2016-10-21T05:32:21Z" + "revision": "84398b94e188ee336f307779b57b3aa91af7063c", + "revisionTime": "2016-11-05T22:35:13Z" }, { "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", "path": "github.com/hashicorp/hcl", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "croNloscHsjX87X+4/cKOURf1EY=", "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "fpQQdjFUZOoslYuFNKZMSO0N0ik=", + "checksumSHA1": "138aCV5n8n7tkGYMsMVQQnnLq+0=", "path": "github.com/hashicorp/hcl/json/parser", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", - "revision": "99ce73d4fe576449f7a689d4fc2b2ad09a86bdaa", - "revisionTime": "2016-10-19T17:07:01Z" + "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", + "revisionTime": "2016-11-04T01:42:59Z" }, { "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", @@ -383,8 +383,8 @@ { "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", "path": "github.com/pkg/errors", - "revision": "839d9e913e063e28dfd0e6c7b7512793e0a48be9", - "revisionTime": "2016-10-02T05:25:12Z" + "revision": "248dadf4e9068a0b3e79f02ed0a610d935de5302", + "revisionTime": "2016-10-29T09:36:37Z" }, { "checksumSHA1": "k9SlQdp/DTB72G/u4aNecX/fFIg=", @@ -441,46 +441,46 @@ "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "iYQIw8g0QguREYVFkLXU3r57P24=", + "checksumSHA1": "pkzbHrJg79tEMajgi+7ED9rPVuQ=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", "path": "github.com/shirou/gopsutil/host", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", "path": "github.com/shirou/gopsutil/load", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", "path": "github.com/shirou/gopsutil/mem", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "0f/OGYrQuJq0DCw2MdCpDXhg/LA=", + "checksumSHA1": "Sxy9qDOEqAa+F2P6rSLKQGZL+l8=", "path": "github.com/shirou/gopsutil/net", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { "checksumSHA1": "Cmj97derBOe/m/D2Db++Z57uWBw=", "path": "github.com/shirou/gopsutil/process", - "revision": "c2dd8ca3d4d77d2516214e7d99b703d5ba82c1da", - "revisionTime": "2016-10-15T12:41:39Z" + "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", + "revisionTime": "2016-10-27T13:29:33Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -539,8 +539,8 @@ { "checksumSHA1": "FZ0r4TzEy9UxXLkFVXFygApni4M=", "path": "github.com/spf13/cobra", - "revision": "856b96dcb49d6427babe192998a35190a12c2230", - "revisionTime": "2016-10-14T22:20:36Z" + "revision": "6e91dded25d73176bf7f60b40dd7aa1f0bf9be8d", + "revisionTime": "2016-10-26T01:28:26Z" }, { "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", @@ -551,14 +551,14 @@ { "checksumSHA1": "GxPD7A0NjMDom1xte0mghkpzr0E=", "path": "github.com/spf13/pflag", - "revision": "dabebe21bf790f782ea4c7bbd2efc430de182afd", - "revisionTime": "2016-10-18T23:08:44Z" + "revision": "5ccb023bc27df288a957c5e994cd44fd19619465", + "revisionTime": "2016-10-24T13:13:51Z" }, { - "checksumSHA1": "2EeKIC5kUssQK8g49DOa78FoMgs=", + "checksumSHA1": "802GjFNHMmnFXEIkQ137ucUUacI=", "path": "github.com/spf13/viper", - "revision": "50515b700e02658272117a72bd641b6b7f1222e5", - "revisionTime": "2016-10-14T09:24:45Z" + "revision": "651d9d916abc3c3d6a91a12549495caba5edffd2", + "revisionTime": "2016-10-29T21:33:52Z" }, { "checksumSHA1": "rKV8YLkXpeNG1Oix8hlYqVsEFb4=", @@ -585,208 +585,208 @@ "revisionTime": "2016-06-07T20:24:39Z" }, { - "checksumSHA1": "h+pFYiRHBogczS8/F1NoN3Ata44=", + "checksumSHA1": "dwOedwBJ1EIK9+S3t108Bx054Y8=", "path": "golang.org/x/crypto/curve25519", - "revision": "3ded668c5379f6951fb0de06174442072e5447d3", - "revisionTime": "2016-10-19T17:38:27Z" + "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revisionTime": "2016-10-31T15:37:30Z" }, { "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", "path": "golang.org/x/crypto/ed25519", - "revision": "3ded668c5379f6951fb0de06174442072e5447d3", - "revisionTime": "2016-10-19T17:38:27Z" + "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revisionTime": "2016-10-31T15:37:30Z" }, { "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "3ded668c5379f6951fb0de06174442072e5447d3", - "revisionTime": "2016-10-19T17:38:27Z" + "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revisionTime": "2016-10-31T15:37:30Z" }, { "checksumSHA1": "LlElMHeTC34ng8eHzjvtUhAgrr8=", "path": "golang.org/x/crypto/ssh", - "revision": "3ded668c5379f6951fb0de06174442072e5447d3", - "revisionTime": "2016-10-19T17:38:27Z" + "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revisionTime": "2016-10-31T15:37:30Z" }, { "checksumSHA1": "9C4Av3ypK5pi173F76ogJT/d8x4=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "3ded668c5379f6951fb0de06174442072e5447d3", - "revisionTime": "2016-10-19T17:38:27Z" + "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", + "revisionTime": "2016-10-31T15:37:30Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { - "checksumSHA1": "hcnPVnz/CBo9WkAovPzRkeHaxIU=", + "checksumSHA1": "a7Jc+Pyru34/ZKm3fw8bYh7TKa4=", "path": "golang.org/x/net/http2", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "4MMbG0LI3ghvWooRn36RmDrFIB0=", "path": "golang.org/x/net/trace", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "O2sws+miRriMPObINsbwa0jnXxE=", "path": "golang.org/x/net/websocket", - "revision": "daba796358cd2742b75aae05761f1b898c9f6a5c", - "revisionTime": "2016-10-20T03:26:43Z" + "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", + "revisionTime": "2016-11-04T22:18:50Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", - "revision": "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5", - "revisionTime": "2016-10-06T21:47:20Z" + "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", + "revisionTime": "2016-06-06T21:15:38Z" }, { "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", "path": "golang.org/x/oauth2/internal", - "revision": "1e695b1c8febf17aad3bfa7bf0a819ef94b98ad5", - "revisionTime": "2016-10-06T21:47:20Z" + "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", + "revisionTime": "2016-06-06T21:15:38Z" }, { - "checksumSHA1": "e6tx+mrbPNlzXbr1VErnBPY+vmQ=", + "checksumSHA1": "aVgPDgwY3/t4J/JOw9H3FVMHqh0=", "path": "golang.org/x/sys/unix", - "revision": "002cbb5f952456d0c50e0d2aff17ea5eca716979", - "revisionTime": "2016-10-16T18:36:00Z" + "revision": "c200b10b5d5e122be351b67af224adc6128af5bf", + "revisionTime": "2016-10-22T18:22:21Z" }, { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "5a42fa2464759cbb7ee0af9de00b54d69f09a29c", - "revisionTime": "2016-10-16T16:34:30Z" + "revision": "a8b38433e35b65ba247bb267317037dee1b70cea", + "revisionTime": "2016-10-19T13:35:53Z" }, { - "checksumSHA1": "Aj3JSVO324FCjEAGm4ZwmC79bbo=", + "checksumSHA1": "Vircurgvsnt4k26havmxPM67PUA=", "path": "golang.org/x/text/unicode/norm", - "revision": "5a42fa2464759cbb7ee0af9de00b54d69f09a29c", - "revisionTime": "2016-10-16T16:34:30Z" + "revision": "a8b38433e35b65ba247bb267317037dee1b70cea", + "revisionTime": "2016-10-19T13:35:53Z" }, { "checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=", "path": "google.golang.org/appengine/internal", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", "path": "google.golang.org/appengine/internal/base", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", "path": "google.golang.org/appengine/internal/datastore", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", "path": "google.golang.org/appengine/internal/log", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", "path": "google.golang.org/appengine/internal/remote_api", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", "path": "google.golang.org/appengine/internal/urlfetch", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", "path": "google.golang.org/appengine/urlfetch", - "revision": "5b8c3b819891014a2d12354528f7d046dd53c89e", - "revisionTime": "2016-10-14T20:17:33Z" + "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", + "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "jR/Ha3HGGTGZx7WGIRG9Qhq+sS4=", + "checksumSHA1": "KzbvI1E+0cJe41jNUEoBksFpAZ8=", "path": "google.golang.org/grpc", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", "path": "google.golang.org/grpc/metadata", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "HQJrtiTtr5eiRsXQLut2R1Q9kuY=", "path": "google.golang.org/grpc/transport", - "revision": "2b7e876a2eb64e93cb8140f497d3ea9d8882279c", - "revisionTime": "2016-10-21T00:52:52Z" + "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", + "revisionTime": "2016-11-04T23:58:17Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", @@ -794,6 +794,36 @@ "revision": "db14e161995a5177acef654cb0dd785e8ee8bc22", "revisionTime": "2016-02-20T15:49:07Z" }, + { + "checksumSHA1": "/EL/UuzIPObHgESjkBMaD4gaXOw=", + "path": "gopkg.in/redis.v3", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "b4Z72l8sq4xuWhR+nM5vbLQIv+o=", + "path": "gopkg.in/redis.v3/internal", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "tNI/mAuqqQqfYU4vuJghUroIts4=", + "path": "gopkg.in/redis.v3/internal/consistenthash", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "JvqkSear94dc+NgofRqBAUmLcN0=", + "path": "gopkg.in/redis.v3/internal/hashtag", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, + { + "checksumSHA1": "ROpD4/McQLnnxpPenjA0mEZfTL8=", + "path": "gopkg.in/redis.v3/internal/pool", + "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", + "revisionTime": "2016-06-27T09:56:34Z" + }, { "checksumSHA1": "hyNpm6i99xiOSkIAJn5zPRblWaU=", "path": "gopkg.in/redis.v4", From d65c273c8977df5a541a58b0c433b0fbb38da60f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 10:33:52 +0100 Subject: [PATCH 2118/2266] Upgrade to redis.v5 --- cmd/discovery.go | 2 +- cmd/handler.go | 2 +- cmd/networkserver.go | 2 +- cmd/root.go | 2 +- core/discovery/announcement/store.go | 2 +- core/discovery/discovery.go | 2 +- core/discovery/discovery_test.go | 2 +- core/handler/application/store.go | 2 +- core/handler/device/store.go | 2 +- core/handler/handler.go | 2 +- core/networkserver/device/store.go | 2 +- core/networkserver/networkserver.go | 2 +- core/networkserver/networkserver_test.go | 2 +- core/storage/redis_kv_store.go | 14 ++-- core/storage/redis_map_store.go | 14 ++-- core/storage/redis_set_store.go | 2 +- core/storage/util_test.go | 2 +- utils/testing/redis.go | 2 +- vendor/vendor.json | 84 +++++++----------------- 19 files changed, 54 insertions(+), 90 deletions(-) diff --git a/cmd/discovery.go b/cmd/discovery.go index 3143438cf..0762d5dd3 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -12,7 +12,7 @@ import ( "google.golang.org/grpc" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery" diff --git a/cmd/handler.go b/cmd/handler.go index 65c4659ab..f2edd0235 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -21,7 +21,7 @@ import ( "github.com/spf13/viper" "golang.org/x/net/context" "google.golang.org/grpc" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // handlerCmd represents the handler command diff --git a/cmd/networkserver.go b/cmd/networkserver.go index cfc74515b..7420a264f 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -18,7 +18,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "google.golang.org/grpc" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // networkserverCmd represents the networkserver command diff --git a/cmd/root.go b/cmd/root.go index fcd0ced59..b4d12abad 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -23,7 +23,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tj/go-elastic" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) var cfgFile string diff --git a/core/discovery/announcement/store.go b/core/discovery/announcement/store.go index 2f540af19..995bd7f77 100644 --- a/core/discovery/announcement/store.go +++ b/core/discovery/announcement/store.go @@ -11,7 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // Store interface for Announcements diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index db99643c7..b0cd76970 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // Discovery specifies the interface for the TTN Service Discovery component diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 101bb13c0..33895dad1 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -8,7 +8,7 @@ import ( "os" "testing" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" pb "github.com/TheThingsNetwork/ttn/api/discovery" . "github.com/smartystreets/assertions" diff --git a/core/handler/application/store.go b/core/handler/application/store.go index 4a1a16817..7aadf3e41 100644 --- a/core/handler/application/store.go +++ b/core/handler/application/store.go @@ -8,7 +8,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // Store interface for Applications diff --git a/core/handler/device/store.go b/core/handler/device/store.go index 100ee7821..451cecafd 100644 --- a/core/handler/device/store.go +++ b/core/handler/device/store.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // Store interface for Devices diff --git a/core/handler/handler.go b/core/handler/handler.go index ff434e63b..866febc4c 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -18,7 +18,7 @@ import ( "github.com/TheThingsNetwork/ttn/mqtt" "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // Handler component diff --git a/core/networkserver/device/store.go b/core/networkserver/device/store.go index ea458c19f..3af6e443b 100644 --- a/core/networkserver/device/store.go +++ b/core/networkserver/device/store.go @@ -10,7 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/storage" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // Store interface for Devices diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 7c8c40cb0..193287b2b 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -11,7 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // NetworkServer implements LoRaWAN-specific functionality for TTN diff --git a/core/networkserver/networkserver_test.go b/core/networkserver/networkserver_test.go index 7ab378e60..3897f11ea 100644 --- a/core/networkserver/networkserver_test.go +++ b/core/networkserver/networkserver_test.go @@ -8,7 +8,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" . "github.com/smartystreets/assertions" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) func getDevAddr(bytes ...byte) (addr types.DevAddr) { diff --git a/core/storage/redis_kv_store.go b/core/storage/redis_kv_store.go index aec51a802..4c21ea4fb 100644 --- a/core/storage/redis_kv_store.go +++ b/core/storage/redis_kv_store.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // RedisKVStore stores arbitrary data in Redis @@ -112,8 +112,8 @@ func (s *RedisKVStore) Create(key string, value string) error { if exists { return errors.NewErrAlreadyExists(key) } - _, err = tx.MultiExec(func() error { - tx.Set(key, value, 0) + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Set(key, value, 0) return nil }) if err != nil { @@ -142,8 +142,8 @@ func (s *RedisKVStore) Update(key string, value string) error { if !exists { return errors.NewErrNotFound(key) } - _, err = tx.MultiExec(func() error { - tx.Set(key, value, 0) + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Set(key, value, 0) return nil }) if err != nil { @@ -172,8 +172,8 @@ func (s *RedisKVStore) Delete(key string) error { if !exists { return errors.NewErrNotFound(key) } - _, err = tx.MultiExec(func() error { - tx.Del(key) + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Del(key) return nil }) if err != nil { diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index 64f86f146..eabe7c96e 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" - redis "gopkg.in/redis.v4" + redis "gopkg.in/redis.v5" ) // RedisMapStore stores structs as HMaps in Redis @@ -179,8 +179,8 @@ func (s *RedisMapStore) Create(key string, value interface{}, properties ...stri if exists { return errors.NewErrAlreadyExists(key) } - _, err = tx.MultiExec(func() error { - tx.HMSet(key, vmap) + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.HMSet(key, vmap) return nil }) if err != nil { @@ -223,8 +223,8 @@ func (s *RedisMapStore) Update(key string, value interface{}, properties ...stri if !exists { return errors.NewErrNotFound(key) } - _, err = tx.MultiExec(func() error { - tx.HMSet(key, vmap) + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.HMSet(key, vmap) return nil }) if err != nil { @@ -253,8 +253,8 @@ func (s *RedisMapStore) Delete(key string) error { if !exists { return errors.NewErrNotFound(key) } - _, err = tx.MultiExec(func() error { - tx.Del(key) + _, err = tx.Pipelined(func(pipe *redis.Pipeline) error { + pipe.Del(key) return nil }) if err != nil { diff --git a/core/storage/redis_set_store.go b/core/storage/redis_set_store.go index 53e9661de..a06688f36 100644 --- a/core/storage/redis_set_store.go +++ b/core/storage/redis_set_store.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" - "gopkg.in/redis.v4" + "gopkg.in/redis.v5" ) // RedisSetStore stores sets in Redis diff --git a/core/storage/util_test.go b/core/storage/util_test.go index 4dca9f813..93c89468d 100644 --- a/core/storage/util_test.go +++ b/core/storage/util_test.go @@ -7,7 +7,7 @@ import ( "fmt" "os" - redis "gopkg.in/redis.v4" + redis "gopkg.in/redis.v5" ) func getRedisClient() *redis.Client { diff --git a/utils/testing/redis.go b/utils/testing/redis.go index 2fb3fa8d5..caa729a16 100644 --- a/utils/testing/redis.go +++ b/utils/testing/redis.go @@ -7,7 +7,7 @@ import ( "fmt" "os" - redis "gopkg.in/redis.v4" + redis "gopkg.in/redis.v5" ) // GetRedisClient returns a redis client that can be used for testing diff --git a/vendor/vendor.json b/vendor/vendor.json index 02ccd71b8..c6dbcd0aa 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -795,76 +795,40 @@ "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "/EL/UuzIPObHgESjkBMaD4gaXOw=", - "path": "gopkg.in/redis.v3", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" + "checksumSHA1": "rr5P5knxMTqvnRElnvlrM4dyuQM=", + "path": "gopkg.in/redis.v5", + "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", + "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "b4Z72l8sq4xuWhR+nM5vbLQIv+o=", - "path": "gopkg.in/redis.v3/internal", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" + "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", + "path": "gopkg.in/redis.v5/internal", + "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", + "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "tNI/mAuqqQqfYU4vuJghUroIts4=", - "path": "gopkg.in/redis.v3/internal/consistenthash", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" + "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", + "path": "gopkg.in/redis.v5/internal/consistenthash", + "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", + "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "JvqkSear94dc+NgofRqBAUmLcN0=", - "path": "gopkg.in/redis.v3/internal/hashtag", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" + "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", + "path": "gopkg.in/redis.v5/internal/hashtag", + "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", + "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "ROpD4/McQLnnxpPenjA0mEZfTL8=", - "path": "gopkg.in/redis.v3/internal/pool", - "revision": "b5e368500d0a508ef8f16e9c2d4025a8a46bcc29", - "revisionTime": "2016-06-27T09:56:34Z" + "checksumSHA1": "MuVVHw/uzk6gwBsQ8deMYacmgTM=", + "path": "gopkg.in/redis.v5/internal/pool", + "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", + "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "hyNpm6i99xiOSkIAJn5zPRblWaU=", - "path": "gopkg.in/redis.v4", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" - }, - { - "checksumSHA1": "u24d3wK+Otp3+nu3wSQOMqhg+N8=", - "path": "gopkg.in/redis.v4/internal", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" - }, - { - "checksumSHA1": "l+1jiOGUyjHP0u0mB+KiT/UHVcY=", - "path": "gopkg.in/redis.v4/internal/consistenthash", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" - }, - { - "checksumSHA1": "XxoAGEMN5ZCJJ3FeDMSTVhvscHg=", - "path": "gopkg.in/redis.v4/internal/errors", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" - }, - { - "checksumSHA1": "6XSNzBg5bzd2TjEfC/q41tPT/Zg=", - "path": "gopkg.in/redis.v4/internal/hashtag", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" - }, - { - "checksumSHA1": "fYkMv2BMIeHKM1VirUzJ7A/jG7w=", - "path": "gopkg.in/redis.v4/internal/pool", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" - }, - { - "checksumSHA1": "BGzuM8ZpMCmTKWnb/3CtrV4OwDU=", - "path": "gopkg.in/redis.v4/internal/proto", - "revision": "8a8d997ad58dc600d2ff0f64914102192f3b51ac", - "revisionTime": "2016-10-14T12:43:29Z" + "checksumSHA1": "Pn4Vc2X2hUhm0SV7j82YlMW8j5o=", + "path": "gopkg.in/redis.v5/internal/proto", + "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", + "revisionTime": "2016-10-24T09:52:32Z" }, { "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", From 38a0555d053d4e427ca98e35b50f3092097a9ea6 Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 7 Nov 2016 10:46:45 +0100 Subject: [PATCH 2119/2266] Update go-account-lib --- ttnctl/util/account.go | 17 +++++++++------ vendor/vendor.json | 48 ++++++++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index b44738846..46a6b7003 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -12,11 +12,10 @@ import ( "github.com/TheThingsNetwork/go-account-lib/account" "github.com/TheThingsNetwork/go-account-lib/cache" + "github.com/TheThingsNetwork/go-account-lib/oauth" "github.com/TheThingsNetwork/go-account-lib/tokens" - accountUtil "github.com/TheThingsNetwork/go-account-lib/util" "github.com/apex/log" "github.com/spf13/viper" - "golang.org/x/net/context" "golang.org/x/oauth2" ) @@ -47,9 +46,15 @@ func GetTokenCache() cache.Cache { return cache.FileCacheWithNameFn(GetDataDir(), tokenFilename) } +// getOAuth gets the OAuth client +func getOAuth() *oauth.Config { + return oauth.OAuth(viper.GetString("auth-server"), &oauth.Client{ + ID: "ttnctl", + }) +} + func getAccountServerTokenSource(token *oauth2.Token) oauth2.TokenSource { - config := accountUtil.MakeConfig(viper.GetString("auth-server"), "ttnctl", "", "") - return config.TokenSource(context.Background(), token) + return getOAuth().TokenSource(token) } func getStoredToken(ctx log.Interface) *oauth2.Token { @@ -138,8 +143,8 @@ func GetAccount(ctx log.Interface) *account.Account { // Login does a login to the Account server with the given username and password func Login(ctx log.Interface, code string) (*oauth2.Token, error) { - config := accountUtil.MakeConfig(viper.GetString("auth-server"), "ttnctl", "", "") - token, err := config.Exchange(context.Background(), code) + config := getOAuth() + token, err := config.Exchange(code) if err != nil { return nil, err } diff --git a/vendor/vendor.json b/vendor/vendor.json index c6dbcd0aa..5b5a1a8eb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -9,58 +9,64 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "xqWW+yCYPj3/94MAob6OkyaHcLc=", + "checksumSHA1": "joWAlgyzANmhca8jS0qTN6J16ZU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" + }, + { + "checksumSHA1": "k/wSvhZn0njRTiXi6DkqJUGe+YM=", + "path": "github.com/TheThingsNetwork/go-account-lib/oauth", + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { - "checksumSHA1": "ANS1jzUqsv1Q1X0gZNzSe9wY0Fo=", + "checksumSHA1": "Y5gCuXyeW17OnCuSOhQYWFfN+q8=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { - "checksumSHA1": "kD6p9T1bgMU/33BG8czy2e9CJRw=", + "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "446fea12fa05bd10027ad7b6c1daac4330280e23", - "revisionTime": "2016-10-18T16:08:10Z" + "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", + "revisionTime": "2016-11-07T09:42:54Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", From bff265575e7671fd15233da578b57adbe323d1b0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 10:47:55 +0100 Subject: [PATCH 2120/2266] Add comments about why we're still using golang.org/x/net/context --- api/context.go | 2 +- api/discovery/client.go | 4 +--- api/handler/manager_client.go | 2 +- api/metadata.go | 3 +-- api/monitor/client.go | 2 +- api/monitor/downlink.go | 2 +- api/monitor/gateway_status.go | 2 +- api/monitor/uplink.go | 2 +- api/router/client.go | 4 +--- cmd/broker_register_prefix.go | 2 +- cmd/handler.go | 2 +- core/broker/broker_test.go | 5 +++-- core/broker/manager_server.go | 2 +- core/broker/server.go | 2 +- core/component.go | 2 +- core/discovery/server.go | 2 +- core/discovery/server_test.go | 2 +- core/handler/dry_run.go | 2 +- core/handler/dry_run_test.go | 3 +-- core/handler/manager_server.go | 2 +- core/handler/server.go | 2 +- core/networkserver/manager_server.go | 2 +- core/networkserver/server.go | 2 +- core/router/manager_server.go | 2 +- core/router/server.go | 2 +- ttnctl/util/account.go | 2 +- ttnctl/util/context.go | 3 +-- 27 files changed, 29 insertions(+), 35 deletions(-) diff --git a/api/context.go b/api/context.go index 04efecec7..33de0278a 100644 --- a/api/context.go +++ b/api/context.go @@ -1,6 +1,6 @@ package api -import context "golang.org/x/net/context" +import "context" func TokenFromContext(ctx context.Context) (token string, err error) { md, err := MetadataFromContext(ctx) diff --git a/api/discovery/client.go b/api/discovery/client.go index 1689e0c8c..76e5f3de2 100644 --- a/api/discovery/client.go +++ b/api/discovery/client.go @@ -11,9 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/core/types" "github.com/bluele/gcache" - - "golang.org/x/net/context" - + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711 "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) diff --git a/api/handler/manager_client.go b/api/handler/manager_client.go index 02b9a049e..8ee483db7 100644 --- a/api/handler/manager_client.go +++ b/api/handler/manager_client.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) diff --git a/api/metadata.go b/api/metadata.go index 84251aa94..52a0fccb9 100644 --- a/api/metadata.go +++ b/api/metadata.go @@ -1,9 +1,8 @@ package api import ( - context "golang.org/x/net/context" //TODO change to "context", when protoc supports it - "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc/metadata" ) diff --git a/api/monitor/client.go b/api/monitor/client.go index 05eba7f6d..ffdd4f65a 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -11,7 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) diff --git a/api/monitor/downlink.go b/api/monitor/downlink.go index f459c27ac..964b51e8b 100644 --- a/api/monitor/downlink.go +++ b/api/monitor/downlink.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) func (cl *gatewayClient) initDownlink() { diff --git a/api/monitor/gateway_status.go b/api/monitor/gateway_status.go index f22a5b260..4bda5cac2 100644 --- a/api/monitor/gateway_status.go +++ b/api/monitor/gateway_status.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) func (cl *gatewayClient) initStatus() { diff --git a/api/monitor/uplink.go b/api/monitor/uplink.go index afbb9bdf2..982d21c37 100644 --- a/api/monitor/uplink.go +++ b/api/monitor/uplink.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) func (cl *gatewayClient) initUplink() { diff --git a/api/router/client.go b/api/router/client.go index 1068c11ac..99b635013 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -11,9 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" - - "golang.org/x/net/context" - + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index a5d72099c..38f4507f5 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -9,7 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/types" "github.com/spf13/cobra" "github.com/spf13/viper" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) diff --git a/cmd/handler.go b/cmd/handler.go index f2edd0235..68d6712b9 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -19,7 +19,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" "github.com/spf13/viper" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "gopkg.in/redis.v5" ) diff --git a/core/broker/broker_test.go b/core/broker/broker_test.go index 749af2c14..e48aa566a 100644 --- a/core/broker/broker_test.go +++ b/core/broker/broker_test.go @@ -4,17 +4,18 @@ package broker import ( - "github.com/TheThingsNetwork/ttn/utils/errors" "sync" "testing" + "github.com/TheThingsNetwork/ttn/utils/errors" + pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" . "github.com/smartystreets/assertions" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" ) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index cf7ba8731..def87bea8 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -11,7 +11,7 @@ import ( pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/core/broker/server.go b/core/broker/server.go index 376611af1..931408eb5 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -10,7 +10,7 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/core/component.go b/core/component.go index 902401053..37b46bd36 100644 --- a/core/component.go +++ b/core/component.go @@ -24,7 +24,7 @@ import ( "github.com/dgrijalva/jwt-go" "github.com/mwitkow/go-grpc-middleware" "github.com/spf13/viper" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/grpclog" diff --git a/core/discovery/server.go b/core/discovery/server.go index d29994f10..6c595b5ff 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -8,7 +8,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index 2eb5b1ce1..0b3f424e7 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -11,7 +11,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" ) diff --git a/core/handler/dry_run.go b/core/handler/dry_run.go index 1ba4b3c4c..a1416d3d2 100644 --- a/core/handler/dry_run.go +++ b/core/handler/dry_run.go @@ -9,7 +9,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/handler/functions" "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) // DryUplink converts the uplink message payload by running the payload diff --git a/core/handler/dry_run_test.go b/core/handler/dry_run_test.go index f521aad67..ce3e8abf8 100644 --- a/core/handler/dry_run_test.go +++ b/core/handler/dry_run_test.go @@ -6,12 +6,11 @@ package handler import ( "testing" - "golang.org/x/net/context" - pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/handler/application" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) type countingStore struct { diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 72481b66e..db63fe574 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -15,7 +15,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" diff --git a/core/handler/server.go b/core/handler/server.go index 08b952ad5..3c7f83d1e 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -7,7 +7,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/utils/errors" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index f12a3dc6b..cd1190195 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -13,7 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 31401d03a..44e5196d6 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -10,7 +10,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/security" "github.com/dgrijalva/jwt-go" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" diff --git a/core/router/manager_server.go b/core/router/manager_server.go index fd8c894fa..625fa22d3 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -5,7 +5,7 @@ package router import ( pb "github.com/TheThingsNetwork/ttn/api/router" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/core/router/server.go b/core/router/server.go index 1de8bafae..2caf09547 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -14,7 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "github.com/spf13/viper" - context "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index b44738846..6410d1384 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -16,7 +16,7 @@ import ( accountUtil "github.com/TheThingsNetwork/go-account-lib/util" "github.com/apex/log" "github.com/spf13/viper" - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "golang.org/x/oauth2" ) diff --git a/ttnctl/util/context.go b/ttnctl/util/context.go index 195be4645..65dd8cbf9 100644 --- a/ttnctl/util/context.go +++ b/ttnctl/util/context.go @@ -8,8 +8,7 @@ import ( "os/user" "github.com/apex/log" - - "golang.org/x/net/context" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc/metadata" ) From dcb49f661c71782f44a00afb9e5d12cf6c4d9cdd Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 10:48:12 +0100 Subject: [PATCH 2121/2266] Add current Go version to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9dbb89748..d5dfb50a6 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ When you get started with The Things Network, you'll probably have some question ## Prepare your Development Environment -1. Make sure you have [Go](https://golang.org) installed. +1. Make sure you have [Go](https://golang.org) installed (version 1.7 or later). 2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) 4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. From 5e8772e0237fdea9e5200c01048096a3ab58a15a Mon Sep 17 00:00:00 2001 From: Romeo Van Snick Date: Mon, 7 Nov 2016 11:30:46 +0100 Subject: [PATCH 2122/2266] Update go-account-lib to have clean errors --- vendor/vendor.json | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 5b5a1a8eb..b4ba12f3d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -11,62 +11,62 @@ { "checksumSHA1": "joWAlgyzANmhca8jS0qTN6J16ZU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { - "checksumSHA1": "k/wSvhZn0njRTiXi6DkqJUGe+YM=", + "checksumSHA1": "n7yqlvNiCbkkbUewQLOydLhtGgQ=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { - "checksumSHA1": "Y5gCuXyeW17OnCuSOhQYWFfN+q8=", + "checksumSHA1": "iw9nN70RU1HK4VbosXcZp290WMg=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "55e2eb88df306d0fa3260ce95557e5992da5f8f6", - "revisionTime": "2016-11-07T09:42:54Z" + "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", + "revisionTime": "2016-11-07T10:26:11Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", From 6622d70f220f875c93321269ddf1a2169887ae9d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 11:36:11 +0100 Subject: [PATCH 2123/2266] Move Component from core to own package --- cmd/broker.go | 4 +- cmd/discovery.go | 10 +- cmd/handler.go | 4 +- cmd/networkserver.go | 4 +- cmd/router.go | 7 +- core/broker/broker.go | 12 +- core/broker/downlink_test.go | 4 +- core/broker/util_test.go | 4 +- core/component.go | 410 -------------------------- core/component/auth.go | 133 +++++++++ core/component/component.go | 175 +++++++++++ core/component/discovery.go | 29 ++ core/component/grpc.go | 80 +++++ core/{ => component}/pprof.go | 2 +- core/component/status.go | 23 ++ core/discovery/discovery.go | 10 +- core/handler/activation_test.go | 4 +- core/handler/amqp_test.go | 4 +- core/handler/convert_lorawan_test.go | 6 +- core/handler/convert_metadata_test.go | 4 +- core/handler/downlink_test.go | 6 +- core/handler/handler.go | 12 +- core/handler/mqtt_test.go | 4 +- core/handler/uplink_test.go | 4 +- core/networkserver/networkserver.go | 12 +- core/router/activation_test.go | 4 +- core/router/downlink_test.go | 6 +- core/router/gateway_status_test.go | 4 +- core/router/router.go | 12 +- core/router/util_test.go | 4 +- 30 files changed, 512 insertions(+), 485 deletions(-) delete mode 100644 core/component.go create mode 100644 core/component/auth.go create mode 100644 core/component/component.go create mode 100644 core/component/discovery.go create mode 100644 core/component/grpc.go rename core/{ => component}/pprof.go (91%) create mode 100644 core/component/status.go diff --git a/cmd/broker.go b/cmd/broker.go index fe9ee95f3..2a9ed77a5 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -14,8 +14,8 @@ import ( "google.golang.org/grpc" - "github.com/TheThingsNetwork/ttn/core" "github.com/TheThingsNetwork/ttn/core/broker" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -38,7 +38,7 @@ var brokerCmd = &cobra.Command{ ctx.Info("Starting") // Component - component, err := core.NewComponent(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) + component, err := component.New(ctx, "broker", fmt.Sprintf("%s:%d", viper.GetString("broker.server-address-announce"), viper.GetInt("broker.server-port"))) if err != nil { ctx.WithError(err).Fatal("Could not initialize component") } diff --git a/cmd/discovery.go b/cmd/discovery.go index 0762d5dd3..558cc3e93 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -10,16 +10,14 @@ import ( "os/signal" "syscall" - "google.golang.org/grpc" - - "gopkg.in/redis.v5" - - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/discovery" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc" + "gopkg.in/redis.v5" ) // discoveryCmd represents the discovery command @@ -46,7 +44,7 @@ var discoveryCmd = &cobra.Command{ connectRedis(client) // Component - component, err := core.NewComponent(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) + component, err := component.New(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) if err != nil { ctx.WithError(err).Fatal("Could not initialize component") } diff --git a/cmd/handler.go b/cmd/handler.go index 68d6712b9..9c3ea9cc9 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -12,7 +12,7 @@ import ( "syscall" pb "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler" "github.com/TheThingsNetwork/ttn/core/proxy" "github.com/apex/log" @@ -53,7 +53,7 @@ var handlerCmd = &cobra.Command{ connectRedis(client) // Component - component, err := core.NewComponent(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) + component, err := component.New(ctx, "handler", fmt.Sprintf("%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.server-port"))) if err != nil { ctx.WithError(err).Fatal("Could not initialize component") } diff --git a/cmd/networkserver.go b/cmd/networkserver.go index 7420a264f..c240559f4 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -11,7 +11,7 @@ import ( "strings" "syscall" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/networkserver" "github.com/TheThingsNetwork/ttn/core/types" "github.com/apex/log" @@ -46,7 +46,7 @@ var networkserverCmd = &cobra.Command{ connectRedis(client) // Component - component, err := core.NewComponent(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) + component, err := component.New(ctx, "networkserver", fmt.Sprintf("%s:%d", viper.GetString("networkserver.server-address-announce"), viper.GetInt("networkserver.server-port"))) if err != nil { ctx.WithError(err).Fatal("Could not initialize component") } diff --git a/cmd/router.go b/cmd/router.go index d7d6e504e..120252d3c 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -10,13 +10,12 @@ import ( "os/signal" "syscall" - "google.golang.org/grpc" - - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc" ) // routerCmd represents the router command @@ -34,7 +33,7 @@ var routerCmd = &cobra.Command{ ctx.Info("Starting") // Component - component, err := core.NewComponent(ctx, "router", fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port"))) + component, err := component.New(ctx, "router", fmt.Sprintf("%s:%d", viper.GetString("router.server-address-announce"), viper.GetInt("router.server-port"))) if err != nil { ctx.WithError(err).Fatal("Could not initialize component") } diff --git a/core/broker/broker.go b/core/broker/broker.go index 33dd855fb..08c56d0b8 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -14,15 +14,15 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "google.golang.org/grpc" ) type Broker interface { - core.ComponentInterface - core.ManagementInterface + component.Interface + component.ManagementInterface SetNetworkServer(addr, cert, token string) @@ -52,7 +52,7 @@ func (b *broker) SetNetworkServer(addr, cert, token string) { } type broker struct { - *core.Component + *component.Component routers map[string]chan *pb.DownlinkMessage routersLock sync.RWMutex handlers map[string]chan *pb.DeduplicatedUplinkMessage @@ -114,7 +114,7 @@ nextPrefix: return nil } -func (b *broker) Init(c *core.Component) error { +func (b *broker) Init(c *component.Component) error { b.Component = c err := b.Component.UpdateTokenKey() if err != nil { @@ -132,7 +132,7 @@ func (b *broker) Init(c *core.Component) error { b.nsConn = conn b.ns = networkserver.NewNetworkServerClient(conn) b.checkPrefixAnnouncements() - b.Component.SetStatus(core.StatusHealthy) + b.Component.SetStatus(component.StatusHealthy) return nil } diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go index d09cb71f7..0d28e1991 100644 --- a/core/broker/downlink_test.go +++ b/core/broker/downlink_test.go @@ -7,7 +7,7 @@ import ( "testing" pb "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -22,7 +22,7 @@ func TestDownlink(t *testing.T) { dlch := make(chan *pb.DownlinkMessage, 2) b := &broker{ - Component: &core.Component{ + Component: &component.Component{ Ctx: GetLogger(t, "TestDownlink"), }, ns: &mockNetworkServer{}, diff --git a/core/broker/util_test.go b/core/broker/util_test.go index 831b75ee1..59cf5b366 100644 --- a/core/broker/util_test.go +++ b/core/broker/util_test.go @@ -10,7 +10,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_networkserver "github.com/TheThingsNetwork/ttn/api/networkserver" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/golang/mock/gomock" ) @@ -28,7 +28,7 @@ func getTestBroker(t *testing.T) *testBroker { ns := pb_networkserver.NewMockNetworkServerClient(ctrl) return &testBroker{ broker: &broker{ - Component: &core.Component{ + Component: &component.Component{ Discovery: discovery, Ctx: GetLogger(t, "TestBroker"), }, diff --git a/core/component.go b/core/component.go deleted file mode 100644 index 37b46bd36..000000000 --- a/core/component.go +++ /dev/null @@ -1,410 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package core - -import ( - "crypto/ecdsa" - "crypto/tls" - "fmt" - "net/http" - "runtime" - "sync/atomic" - "time" - - "github.com/TheThingsNetwork/go-account-lib/cache" - "github.com/TheThingsNetwork/go-account-lib/claims" - "github.com/TheThingsNetwork/go-account-lib/tokenkey" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" - pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/TheThingsNetwork/ttn/utils/logging" - "github.com/TheThingsNetwork/ttn/utils/security" - "github.com/apex/log" - "github.com/dgrijalva/jwt-go" - "github.com/mwitkow/go-grpc-middleware" - "github.com/spf13/viper" - "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" - "google.golang.org/grpc/grpclog" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/peer" -) - -type ComponentInterface interface { - RegisterRPC(s *grpc.Server) - Init(c *Component) error - Shutdown() - ValidateNetworkContext(ctx context.Context) (*pb_discovery.Announcement, error) - ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) -} - -type ManagementInterface interface { - RegisterManager(s *grpc.Server) -} - -// NewComponent creates a new Component -func NewComponent(ctx log.Interface, serviceName string, announcedAddress string) (*Component, error) { - go func() { - memstats := new(runtime.MemStats) - for range time.Tick(time.Minute) { - runtime.ReadMemStats(memstats) - ctx.WithFields(log.Fields{ - "Goroutines": runtime.NumGoroutine(), - "Memory": float64(memstats.Alloc) / 1000000, - }).Debugf("Stats") - } - }() - - // Disable gRPC tracing - // SEE: https://github.com/grpc/grpc-go/issues/695 - grpc.EnableTracing = false - - grpclog.SetLogger(logging.NewGRPCLogger(ctx)) - - component := &Component{ - Ctx: ctx, - Identity: &pb_discovery.Announcement{ - Id: viper.GetString("id"), - Description: viper.GetString("description"), - ServiceName: serviceName, - ServiceVersion: fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), - NetAddress: announcedAddress, - Public: viper.GetBool("public"), - }, - AccessToken: viper.GetString("auth-token"), - TokenKeyProvider: tokenkey.HTTPProvider( - viper.GetStringMapString("auth-servers"), - cache.WriteTroughCacheWithFormat(viper.GetString("key-dir"), "auth-%s.pub"), - ), - } - - if serviceName != "discovery" { - var err error - component.Discovery, err = pb_discovery.NewClient( - viper.GetString("discovery-address"), - component.Identity, - func() string { - token, _ := component.BuildJWT() - return token - }, - ) - if err != nil { - return nil, err - } - } - - if priv, err := security.LoadKeypair(viper.GetString("key-dir")); err == nil { - component.privateKey = priv - - pubPEM, _ := security.PublicPEM(priv) - component.Identity.PublicKey = string(pubPEM) - - privPEM, _ := security.PrivatePEM(priv) - - if viper.GetBool("tls") { - cert, err := security.LoadCert(viper.GetString("key-dir")) - if err != nil { - return nil, err - } - component.Identity.Certificate = string(cert) - - cer, err := tls.X509KeyPair(cert, privPEM) - if err != nil { - return nil, err - } - component.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} - } - } - - if healthPort := viper.GetInt("health-port"); healthPort > 0 { - http.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { - switch component.GetStatus() { - case StatusHealthy: - w.WriteHeader(200) - w.Write([]byte("Status is HEALTHY")) - return - case StatusUnhealthy: - w.WriteHeader(503) - w.Write([]byte("Status is UNHEALTHY")) - return - } - }) - go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) - } - - if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { - component.Monitors = make(map[string]*pb_monitor.Client) - for name, addr := range monitors { - var err error - component.Monitors[name], err = pb_monitor.NewClient(ctx.WithField("Monitor", name), addr) - if err != nil { - // Assuming grpc.WithBlock() is not set - return nil, err - } - } - } - - return component, nil -} - -// Status indicates the health status of this component -type Status int - -const ( - // StatusHealthy indicates a healthy component - StatusHealthy Status = iota - // StatusUnhealthy indicates an unhealthy component - StatusUnhealthy -) - -// Component contains the common attributes for all TTN components -type Component struct { - Identity *pb_discovery.Announcement - Discovery pb_discovery.Client - Monitors map[string]*pb_monitor.Client - Ctx log.Interface - AccessToken string - privateKey *ecdsa.PrivateKey - tlsConfig *tls.Config - TokenKeyProvider tokenkey.Provider - status int64 -} - -// GetStatus gets the health status of the component -func (c *Component) GetStatus() Status { - return Status(atomic.LoadInt64(&c.status)) -} - -// SetStatus sets the health status of the component -func (c *Component) SetStatus(status Status) { - atomic.StoreInt64(&c.status, int64(status)) -} - -// Discover is used to discover another component -func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { - res, err := c.Discovery.Get(serviceName, id) - if err != nil { - return nil, errors.Wrapf(errors.FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) - } - return res, nil -} - -// Announce the component to TTN discovery -func (c *Component) Announce() error { - if c.Identity.Id == "" { - return errors.NewErrInvalidArgument("Component ID", "can not be empty") - } - err := c.Discovery.Announce(c.AccessToken) - if err != nil { - return errors.Wrapf(errors.FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) - } - c.Ctx.Info("ttn: Announced to TTN discovery") - - return nil -} - -// UpdateTokenKey updates the OAuth Bearer token key -func (c *Component) UpdateTokenKey() error { - if c.TokenKeyProvider == nil { - return errors.NewErrInternal("No public key provider configured for token validation") - } - - // Set up Auth Server Token Validation - err := c.TokenKeyProvider.Update() - if err != nil { - c.Ctx.Warnf("ttn: Failed to refresh public keys for token validation: %s", err.Error()) - } else { - c.Ctx.Info("ttn: Got public keys for token validation") - } - - return nil - -} - -// ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) -func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_discovery.Announcement, err error) { - defer func() { - if err != nil { - time.Sleep(time.Second) - } - }() - - md, ok := metadata.FromContext(ctx) - if !ok { - err = errors.NewErrInternal("Could not get metadata from context") - return - } - var id, serviceName, token string - if ids, ok := md["id"]; ok && len(ids) == 1 { - id = ids[0] - } - if id == "" { - err = errors.NewErrInvalidArgument("Metadata", "id missing") - return - } - if serviceNames, ok := md["service-name"]; ok && len(serviceNames) == 1 { - serviceName = serviceNames[0] - } - if serviceName == "" { - err = errors.NewErrInvalidArgument("Metadata", "service-name missing") - return - } - if tokens, ok := md["token"]; ok && len(tokens) == 1 { - token = tokens[0] - } - - var announcement *pb_discovery.Announcement - announcement, err = c.Discover(serviceName, id) - if err != nil { - return - } - - if announcement.PublicKey == "" { - return announcement, nil - } - - if token == "" { - err = errors.NewErrInvalidArgument("Metadata", "token missing") - return - } - - var claims *jwt.StandardClaims - claims, err = security.ValidateJWT(token, []byte(announcement.PublicKey)) - if err != nil { - return - } - if claims.Issuer != id { - err = errors.NewErrInvalidArgument("Metadata", "token was issued by different component id") - return - } - - return announcement, nil -} - -// ValidateTTNAuthContext gets a token from the context and validates it -func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) { - md, ok := metadata.FromContext(ctx) - if !ok { - return nil, errors.NewErrInternal("Could not get metadata from context") - } - token, ok := md["token"] - if !ok || len(token) < 1 { - return nil, errors.NewErrInvalidArgument("Metadata", "token missing") - } - - if c.TokenKeyProvider == nil { - return nil, errors.NewErrInternal("No token provider configured") - } - - if token[0] == "" { - return nil, errors.NewErrInvalidArgument("Metadata", "token is empty") - } - - claims, err := claims.FromToken(c.TokenKeyProvider, token[0]) - if err != nil { - return nil, errors.NewErrPermissionDenied(err.Error()) - } - - return claims, nil -} - -func (c *Component) ServerOptions() []grpc.ServerOption { - unary := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - var peerAddr string - peer, ok := peer.FromContext(ctx) - if ok { - peerAddr = peer.Addr.String() - } - var peerID string - meta, ok := metadata.FromContext(ctx) - if ok { - id, ok := meta["id"] - if ok && len(id) > 0 { - peerID = id[0] - } - } - logCtx := c.Ctx.WithFields(log.Fields{ - "CallerID": peerID, - "CallerIP": peerAddr, - "Method": info.FullMethod, - }) - t := time.Now() - iface, err := handler(ctx, req) - logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) - if err != nil { - err := errors.FromGRPCError(err) - logCtx.WithField("error", err.Error()).Warn("Could not handle Request") - } else { - logCtx.Info("Handled request") - } - return iface, err - } - - stream := func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { - var peerAddr string - peer, ok := peer.FromContext(stream.Context()) - if ok { - peerAddr = peer.Addr.String() - } - var peerID string - meta, ok := metadata.FromContext(stream.Context()) - if ok { - id, ok := meta["id"] - if ok && len(id) > 0 { - peerID = id[0] - } - } - c.Ctx.WithFields(log.Fields{ - "CallerID": peerID, - "CallerIP": peerAddr, - "Method": info.FullMethod, - }).Info("Start stream") - return handler(srv, stream) - } - - opts := []grpc.ServerOption{ - grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary)), - grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream)), - } - - if c.tlsConfig != nil { - opts = append(opts, grpc.Creds(credentials.NewTLS(c.tlsConfig))) - } - - return opts -} - -// BuildJWT builds a short-lived JSON Web Token for this component -func (c *Component) BuildJWT() (string, error) { - if c.privateKey != nil { - privPEM, err := security.PrivatePEM(c.privateKey) - if err != nil { - return "", err - } - return security.BuildJWT(c.Identity.Id, 20*time.Second, privPEM) - } - return "", nil -} - -// GetContext returns a context for outgoing RPC request. If token is "", this function will generate a short lived token from the component -func (c *Component) GetContext(token string) context.Context { - var serviceName, id, netAddress string - if c.Identity != nil { - serviceName = c.Identity.ServiceName - id = c.Identity.Id - if token == "" { - token, _ = c.BuildJWT() - } - netAddress = c.Identity.NetAddress - } - md := metadata.Pairs( - "service-name", serviceName, - "id", id, - "token", token, - "net-address", netAddress, - ) - ctx := metadata.NewContext(context.Background(), md) - return ctx -} diff --git a/core/component/auth.go b/core/component/auth.go new file mode 100644 index 000000000..635dbdf1f --- /dev/null +++ b/core/component/auth.go @@ -0,0 +1,133 @@ +package component + +import ( + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/security" + jwt "github.com/dgrijalva/jwt-go" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +// BuildJWT builds a short-lived JSON Web Token for this component +func (c *Component) BuildJWT() (string, error) { + if c.privateKey != nil { + privPEM, err := security.PrivatePEM(c.privateKey) + if err != nil { + return "", err + } + return security.BuildJWT(c.Identity.Id, 20*time.Second, privPEM) + } + return "", nil +} + +// GetContext returns a context for outgoing RPC request. If token is "", this function will generate a short lived token from the component +func (c *Component) GetContext(token string) context.Context { + var serviceName, id, netAddress string + if c.Identity != nil { + serviceName = c.Identity.ServiceName + id = c.Identity.Id + if token == "" { + token, _ = c.BuildJWT() + } + netAddress = c.Identity.NetAddress + } + md := metadata.Pairs( + "service-name", serviceName, + "id", id, + "token", token, + "net-address", netAddress, + ) + ctx := metadata.NewContext(context.Background(), md) + return ctx +} + +// ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) +func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_discovery.Announcement, err error) { + defer func() { + if err != nil { + time.Sleep(time.Second) + } + }() + + md, ok := metadata.FromContext(ctx) + if !ok { + err = errors.NewErrInternal("Could not get metadata from context") + return + } + var id, serviceName, token string + if ids, ok := md["id"]; ok && len(ids) == 1 { + id = ids[0] + } + if id == "" { + err = errors.NewErrInvalidArgument("Metadata", "id missing") + return + } + if serviceNames, ok := md["service-name"]; ok && len(serviceNames) == 1 { + serviceName = serviceNames[0] + } + if serviceName == "" { + err = errors.NewErrInvalidArgument("Metadata", "service-name missing") + return + } + if tokens, ok := md["token"]; ok && len(tokens) == 1 { + token = tokens[0] + } + + var announcement *pb_discovery.Announcement + announcement, err = c.Discover(serviceName, id) + if err != nil { + return + } + + if announcement.PublicKey == "" { + return announcement, nil + } + + if token == "" { + err = errors.NewErrInvalidArgument("Metadata", "token missing") + return + } + + var claims *jwt.StandardClaims + claims, err = security.ValidateJWT(token, []byte(announcement.PublicKey)) + if err != nil { + return + } + if claims.Issuer != id { + err = errors.NewErrInvalidArgument("Metadata", "token was issued by different component id") + return + } + + return announcement, nil +} + +// ValidateTTNAuthContext gets a token from the context and validates it +func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) { + md, ok := metadata.FromContext(ctx) + if !ok { + return nil, errors.NewErrInternal("Could not get metadata from context") + } + token, ok := md["token"] + if !ok || len(token) < 1 { + return nil, errors.NewErrInvalidArgument("Metadata", "token missing") + } + + if c.TokenKeyProvider == nil { + return nil, errors.NewErrInternal("No token provider configured") + } + + if token[0] == "" { + return nil, errors.NewErrInvalidArgument("Metadata", "token is empty") + } + + claims, err := claims.FromToken(c.TokenKeyProvider, token[0]) + if err != nil { + return nil, errors.NewErrPermissionDenied(err.Error()) + } + + return claims, nil +} diff --git a/core/component/component.go b/core/component/component.go new file mode 100644 index 000000000..613fc547a --- /dev/null +++ b/core/component/component.go @@ -0,0 +1,175 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package component + +import ( + "crypto/ecdsa" + "crypto/tls" + "fmt" + "net/http" + "runtime" + "time" + + "github.com/TheThingsNetwork/go-account-lib/cache" + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/tokenkey" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/logging" + "github.com/TheThingsNetwork/ttn/utils/security" + "github.com/apex/log" + "github.com/spf13/viper" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/grpclog" +) + +type Interface interface { + RegisterRPC(s *grpc.Server) + Init(c *Component) error + Shutdown() + ValidateNetworkContext(ctx context.Context) (*pb_discovery.Announcement, error) + ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) +} + +type ManagementInterface interface { + RegisterManager(s *grpc.Server) +} + +// New creates a new Component +func New(ctx log.Interface, serviceName string, announcedAddress string) (*Component, error) { + go func() { + memstats := new(runtime.MemStats) + for range time.Tick(time.Minute) { + runtime.ReadMemStats(memstats) + ctx.WithFields(log.Fields{ + "Goroutines": runtime.NumGoroutine(), + "Memory": float64(memstats.Alloc) / 1000000, + }).Debugf("Stats") + } + }() + + // Disable gRPC tracing + // SEE: https://github.com/grpc/grpc-go/issues/695 + grpc.EnableTracing = false + + grpclog.SetLogger(logging.NewGRPCLogger(ctx)) + + component := &Component{ + Ctx: ctx, + Identity: &pb_discovery.Announcement{ + Id: viper.GetString("id"), + Description: viper.GetString("description"), + ServiceName: serviceName, + ServiceVersion: fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), + NetAddress: announcedAddress, + Public: viper.GetBool("public"), + }, + AccessToken: viper.GetString("auth-token"), + TokenKeyProvider: tokenkey.HTTPProvider( + viper.GetStringMapString("auth-servers"), + cache.WriteTroughCacheWithFormat(viper.GetString("key-dir"), "auth-%s.pub"), + ), + } + + if serviceName != "discovery" { + var err error + component.Discovery, err = pb_discovery.NewClient( + viper.GetString("discovery-address"), + component.Identity, + func() string { + token, _ := component.BuildJWT() + return token + }, + ) + if err != nil { + return nil, err + } + } + + if priv, err := security.LoadKeypair(viper.GetString("key-dir")); err == nil { + component.privateKey = priv + + pubPEM, _ := security.PublicPEM(priv) + component.Identity.PublicKey = string(pubPEM) + + privPEM, _ := security.PrivatePEM(priv) + + if viper.GetBool("tls") { + cert, err := security.LoadCert(viper.GetString("key-dir")) + if err != nil { + return nil, err + } + component.Identity.Certificate = string(cert) + + cer, err := tls.X509KeyPair(cert, privPEM) + if err != nil { + return nil, err + } + component.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} + } + } + + if healthPort := viper.GetInt("health-port"); healthPort > 0 { + http.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { + switch component.GetStatus() { + case StatusHealthy: + w.WriteHeader(200) + w.Write([]byte("Status is HEALTHY")) + return + case StatusUnhealthy: + w.WriteHeader(503) + w.Write([]byte("Status is UNHEALTHY")) + return + } + }) + go http.ListenAndServe(fmt.Sprintf(":%d", healthPort), nil) + } + + if monitors := viper.GetStringMapString("monitor-servers"); len(monitors) != 0 { + component.Monitors = make(map[string]*pb_monitor.Client) + for name, addr := range monitors { + var err error + component.Monitors[name], err = pb_monitor.NewClient(ctx.WithField("Monitor", name), addr) + if err != nil { + // Assuming grpc.WithBlock() is not set + return nil, err + } + } + } + + return component, nil +} + +// Component contains the common attributes for all TTN components +type Component struct { + Identity *pb_discovery.Announcement + Discovery pb_discovery.Client + Monitors map[string]*pb_monitor.Client + Ctx log.Interface + AccessToken string + privateKey *ecdsa.PrivateKey + tlsConfig *tls.Config + TokenKeyProvider tokenkey.Provider + status int64 +} + +// UpdateTokenKey updates the OAuth Bearer token key +func (c *Component) UpdateTokenKey() error { + if c.TokenKeyProvider == nil { + return errors.NewErrInternal("No public key provider configured for token validation") + } + + // Set up Auth Server Token Validation + err := c.TokenKeyProvider.Update() + if err != nil { + c.Ctx.Warnf("ttn: Failed to refresh public keys for token validation: %s", err.Error()) + } else { + c.Ctx.Info("ttn: Got public keys for token validation") + } + + return nil + +} diff --git a/core/component/discovery.go b/core/component/discovery.go new file mode 100644 index 000000000..6a3ab66d8 --- /dev/null +++ b/core/component/discovery.go @@ -0,0 +1,29 @@ +package component + +import ( + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/errors" +) + +// Discover is used to discover another component +func (c *Component) Discover(serviceName, id string) (*pb_discovery.Announcement, error) { + res, err := c.Discovery.Get(serviceName, id) + if err != nil { + return nil, errors.Wrapf(errors.FromGRPCError(err), "Failed to discover %s/%s", serviceName, id) + } + return res, nil +} + +// Announce the component to TTN discovery +func (c *Component) Announce() error { + if c.Identity.Id == "" { + return errors.NewErrInvalidArgument("Component ID", "can not be empty") + } + err := c.Discovery.Announce(c.AccessToken) + if err != nil { + return errors.Wrapf(errors.FromGRPCError(err), "Failed to announce this component to TTN discovery: %s", err.Error()) + } + c.Ctx.Info("ttn: Announced to TTN discovery") + + return nil +} diff --git a/core/component/grpc.go b/core/component/grpc.go new file mode 100644 index 000000000..a005b22a7 --- /dev/null +++ b/core/component/grpc.go @@ -0,0 +1,80 @@ +package component + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" + "github.com/mwitkow/go-grpc-middleware" + "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/metadata" + "google.golang.org/grpc/peer" +) + +func (c *Component) ServerOptions() []grpc.ServerOption { + unary := func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + var peerAddr string + peer, ok := peer.FromContext(ctx) + if ok { + peerAddr = peer.Addr.String() + } + var peerID string + meta, ok := metadata.FromContext(ctx) + if ok { + id, ok := meta["id"] + if ok && len(id) > 0 { + peerID = id[0] + } + } + logCtx := c.Ctx.WithFields(log.Fields{ + "CallerID": peerID, + "CallerIP": peerAddr, + "Method": info.FullMethod, + }) + t := time.Now() + iface, err := handler(ctx, req) + logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) + if err != nil { + err := errors.FromGRPCError(err) + logCtx.WithField("error", err.Error()).Warn("Could not handle Request") + } else { + logCtx.Info("Handled request") + } + return iface, err + } + + stream := func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + var peerAddr string + peer, ok := peer.FromContext(stream.Context()) + if ok { + peerAddr = peer.Addr.String() + } + var peerID string + meta, ok := metadata.FromContext(stream.Context()) + if ok { + id, ok := meta["id"] + if ok && len(id) > 0 { + peerID = id[0] + } + } + c.Ctx.WithFields(log.Fields{ + "CallerID": peerID, + "CallerIP": peerAddr, + "Method": info.FullMethod, + }).Info("Start stream") + return handler(srv, stream) + } + + opts := []grpc.ServerOption{ + grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(unary)), + grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(stream)), + } + + if c.tlsConfig != nil { + opts = append(opts, grpc.Creds(credentials.NewTLS(c.tlsConfig))) + } + + return opts +} diff --git a/core/pprof.go b/core/component/pprof.go similarity index 91% rename from core/pprof.go rename to core/component/pprof.go index 0bd862acb..0512776ac 100644 --- a/core/pprof.go +++ b/core/component/pprof.go @@ -3,6 +3,6 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package core +package component import _ "net/http/pprof" diff --git a/core/component/status.go b/core/component/status.go new file mode 100644 index 000000000..4c15a8650 --- /dev/null +++ b/core/component/status.go @@ -0,0 +1,23 @@ +package component + +import "sync/atomic" + +// Status indicates the health status of this component +type Status int + +const ( + // StatusHealthy indicates a healthy component + StatusHealthy Status = iota + // StatusUnhealthy indicates an unhealthy component + StatusUnhealthy +) + +// GetStatus gets the health status of the component +func (c *Component) GetStatus() Status { + return Status(atomic.LoadInt64(&c.status)) +} + +// SetStatus sets the health status of the component +func (c *Component) SetStatus(status Status) { + atomic.StoreInt64(&c.status, int64(status)) +} diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index b0cd76970..cb1ac0527 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -6,7 +6,7 @@ package discovery import ( pb "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" "github.com/TheThingsNetwork/ttn/utils/errors" "gopkg.in/redis.v5" @@ -14,7 +14,7 @@ import ( // Discovery specifies the interface for the TTN Service Discovery component type Discovery interface { - core.ComponentInterface + component.Interface WithCache(options announcement.CacheOptions) Announce(announcement *pb.Announcement) error GetAll(serviceName string) ([]*pb.Announcement, error) @@ -25,7 +25,7 @@ type Discovery interface { // discovery is a reference implementation for a TTN Service Discovery component. type discovery struct { - *core.Component + *component.Component services announcement.Store } @@ -33,13 +33,13 @@ func (d *discovery) WithCache(options announcement.CacheOptions) { d.services = announcement.NewCachedAnnouncementStore(d.services, options) } -func (d *discovery) Init(c *core.Component) error { +func (d *discovery) Init(c *component.Component) error { d.Component = c err := d.Component.UpdateTokenKey() if err != nil { return err } - d.Component.SetStatus(core.StatusHealthy) + d.Component.SetStatus(component.StatusHealthy) return nil } diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 4db14da09..416efd961 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -11,7 +11,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/handler" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -66,7 +66,7 @@ func TestHandleActivation(t *testing.T) { a := New(t) h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestHandleActivation")}, + Component: &component.Component{Ctx: GetLogger(t, "TestHandleActivation")}, applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), } diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go index 2f30fe08b..d54d872a7 100644 --- a/core/handler/amqp_test.go +++ b/core/handler/amqp_test.go @@ -9,7 +9,7 @@ import ( "time" "github.com/TheThingsNetwork/ttn/amqp" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -32,7 +32,7 @@ func TestHandleAMQP(t *testing.T) { appID := "handler-amqp-app1" devID := "handler-amqp-dev1" h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestHandleAMQP")}, + Component: &component.Component{Ctx: GetLogger(t, "TestHandleAMQP")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-amqp"), } h.WithAMQP("guest", "guest", host, "amq.topic") diff --git a/core/handler/convert_lorawan_test.go b/core/handler/convert_lorawan_test.go index fb5c9e3bc..6b22a79ef 100644 --- a/core/handler/convert_lorawan_test.go +++ b/core/handler/convert_lorawan_test.go @@ -9,7 +9,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -35,7 +35,7 @@ func TestConvertFromLoRaWAN(t *testing.T) { a := New(t) h := &handler{ devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-from-lorawan"), - Component: &core.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, + Component: &component.Component{Ctx: GetLogger(t, "TestConvertFromLoRaWAN")}, mqttEvent: make(chan *types.DeviceEvent, 10), } h.devices.Set(&device.Device{ @@ -75,7 +75,7 @@ func TestConvertToLoRaWAN(t *testing.T) { a := New(t) h := &handler{ devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-convert-to-lorawan"), - Component: &core.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, + Component: &component.Component{Ctx: GetLogger(t, "TestConvertToLoRaWAN")}, } h.devices.Set(&device.Device{ DevID: "devid", diff --git a/core/handler/convert_metadata_test.go b/core/handler/convert_metadata_test.go index d8fb934cb..c066d70e4 100644 --- a/core/handler/convert_metadata_test.go +++ b/core/handler/convert_metadata_test.go @@ -11,7 +11,7 @@ import ( pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -20,7 +20,7 @@ import ( func TestConvertMetadata(t *testing.T) { a := New(t) h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestConvertMetadata")}, + Component: &component.Component{Ctx: GetLogger(t, "TestConvertMetadata")}, } ttnUp := &pb_broker.DeduplicatedUplinkMessage{} diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index deb6f9289..6937b4aa9 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -8,7 +8,7 @@ import ( "time" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -21,7 +21,7 @@ func TestEnqueueDownlink(t *testing.T) { appID := "app1" devID := "dev1" h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, + Component: &component.Component{Ctx: GetLogger(t, "TestEnqueueDownlink")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-enqueue-downlink"), mqttEvent: make(chan *types.DeviceEvent, 10), } @@ -61,7 +61,7 @@ func TestHandleDownlink(t *testing.T) { appEUI := types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, + Component: &component.Component{Ctx: GetLogger(t, "TestHandleDownlink")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-downlink"), applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-enqueue-downlink"), downlink: make(chan *pb_broker.DownlinkMessage), diff --git a/core/handler/handler.go b/core/handler/handler.go index 866febc4c..25afc156f 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -11,7 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -23,8 +23,8 @@ import ( // Handler component type Handler interface { - core.ComponentInterface - core.ManagementInterface + component.Interface + component.ManagementInterface WithMQTT(username, password string, brokers ...string) Handler WithAMQP(username, password, host, exchange string) Handler @@ -45,7 +45,7 @@ func NewRedisHandler(client *redis.Client, ttnBrokerID string) Handler { } type handler struct { - *core.Component + *component.Component devices device.Store applications application.Store @@ -96,7 +96,7 @@ func (h *handler) WithAMQP(username, password, host, exchange string) Handler { return h } -func (h *handler) Init(c *core.Component) error { +func (h *handler) Init(c *component.Component) error { h.Component = c err := h.Component.UpdateTokenKey() if err != nil { @@ -131,7 +131,7 @@ func (h *handler) Init(c *core.Component) error { return err } - h.Component.SetStatus(core.StatusHealthy) + h.Component.SetStatus(component.StatusHealthy) return nil } diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index 66fc8fe74..e9f7a7710 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" @@ -31,7 +31,7 @@ func TestHandleMQTT(t *testing.T) { appID := "handler-mqtt-app1" devID := "handler-mqtt-dev1" h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, + Component: &component.Component{Ctx: GetLogger(t, "TestHandleMQTT")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-mqtt"), } h.devices.Set(&device.Device{ diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index 5c8301144..71b4a9c73 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -10,7 +10,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -27,7 +27,7 @@ func TestHandleUplink(t *testing.T) { devEUI := types.DevEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8}) devID := "devid" h := &handler{ - Component: &core.Component{Ctx: GetLogger(t, "TestHandleUplink")}, + Component: &component.Component{Ctx: GetLogger(t, "TestHandleUplink")}, devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-uplink"), applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-handle-uplink"), } diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 193287b2b..36c7fdcc6 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -7,7 +7,7 @@ import ( pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_handler "github.com/TheThingsNetwork/ttn/api/handler" pb "github.com/TheThingsNetwork/ttn/api/networkserver" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -16,8 +16,8 @@ import ( // NetworkServer implements LoRaWAN-specific functionality for TTN type NetworkServer interface { - core.ComponentInterface - core.ManagementInterface + component.Interface + component.ManagementInterface UsePrefix(prefix types.DevAddrPrefix, usage []string) error GetPrefixesFor(requiredUsages ...string) []types.DevAddrPrefix @@ -40,7 +40,7 @@ func NewRedisNetworkServer(client *redis.Client, netID int) NetworkServer { } type networkServer struct { - *core.Component + *component.Component devices device.Store netID [3]byte prefixes map[types.DevAddrPrefix][]string @@ -75,13 +75,13 @@ func (n *networkServer) GetPrefixesFor(requiredUsages ...string) []types.DevAddr return suitablePrefixes } -func (n *networkServer) Init(c *core.Component) error { +func (n *networkServer) Init(c *component.Component) error { n.Component = c err := n.Component.UpdateTokenKey() if err != nil { return err } - n.Component.SetStatus(core.StatusHealthy) + n.Component.SetStatus(component.StatusHealthy) return nil } diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 168d805cc..2d6644d93 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -7,7 +7,7 @@ import ( "testing" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" . "github.com/TheThingsNetwork/ttn/utils/testing" @@ -20,7 +20,7 @@ func TestHandleActivation(t *testing.T) { gtwID := "eui-0102030405060708" r := &router{ - Component: &core.Component{ + Component: &component.Component{ Ctx: GetLogger(t, "TestHandleActivation"), }, gateways: map[string]*gateway.Gateway{ diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index a0c75868b..f07733ea0 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -14,7 +14,7 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -41,7 +41,7 @@ func TestHandleDownlink(t *testing.T) { a := New(t) r := &router{ - Component: &core.Component{ + Component: &component.Component{ Ctx: GetLogger(t, "TestHandleDownlink"), }, gateways: map[string]*gateway.Gateway{}, @@ -66,7 +66,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { a := New(t) r := &router{ - Component: &core.Component{ + Component: &component.Component{ Ctx: GetLogger(t, "TestSubscribeUnsubscribeDownlink"), }, gateways: map[string]*gateway.Gateway{}, diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index 2356aa825..fcb2c2005 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -7,7 +7,7 @@ import ( "testing" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -18,7 +18,7 @@ func TestHandleGatewayStatus(t *testing.T) { gtwID := "eui-0102030405060708" router := &router{ - Component: &core.Component{ + Component: &component.Component{ Ctx: GetLogger(t, "TestHandleGatewayStatus"), }, gateways: map[string]*gateway.Gateway{}, diff --git a/core/router/router.go b/core/router/router.go index 989deaf8f..4d2eb00dc 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -13,15 +13,15 @@ import ( pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" pb "github.com/TheThingsNetwork/ttn/api/router" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" ) // Router component type Router interface { - core.ComponentInterface - core.ManagementInterface + component.Interface + component.ManagementInterface // Handle a status message from a gateway HandleGatewayStatus(gatewayID string, status *pb_gateway.Status) error @@ -54,7 +54,7 @@ func NewRouter() Router { } type router struct { - *core.Component + *component.Component gateways map[string]*gateway.Gateway gatewaysLock sync.RWMutex brokers map[string]*broker @@ -69,7 +69,7 @@ func (r *router) tickGateways() { } } -func (r *router) Init(c *core.Component) error { +func (r *router) Init(c *component.Component) error { r.Component = c err := r.Component.UpdateTokenKey() if err != nil { @@ -86,7 +86,7 @@ func (r *router) Init(c *core.Component) error { r.tickGateways() } }() - r.Component.SetStatus(core.StatusHealthy) + r.Component.SetStatus(component.StatusHealthy) return nil } diff --git a/core/router/util_test.go b/core/router/util_test.go index 2a134f572..18933f571 100644 --- a/core/router/util_test.go +++ b/core/router/util_test.go @@ -7,7 +7,7 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/core" + "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/golang/mock/gomock" @@ -24,7 +24,7 @@ func getTestRouter(t *testing.T) *testRouter { discovery := discovery.NewMockClient(ctrl) return &testRouter{ router: &router{ - Component: &core.Component{ + Component: &component.Component{ Discovery: discovery, Ctx: GetLogger(t, "TestRouter"), }, From 1aea8ef5f223f22479a4876121f83bb786f32db7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 14:39:55 +0100 Subject: [PATCH 2124/2266] Add keypair for discovery server --- .env/discovery/server.key | 5 +++++ .env/discovery/server.pub | 4 ++++ cmd/genkeys.go | 2 ++ core/proxy/proxy.go | 5 ++++- 4 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 .env/discovery/server.key create mode 100644 .env/discovery/server.pub diff --git a/.env/discovery/server.key b/.env/discovery/server.key new file mode 100644 index 000000000..71d888584 --- /dev/null +++ b/.env/discovery/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIGnSFCNG/2H/5usWkVu8jDBfolSeZqC/hm7FYlyHjSCMoAoGCCqGSM49 +AwEHoUQDQgAEGdwVP3pf5UGc+ISdvzQG/qDjzbQiZuXVflmyEkPkUSFxyqPRupgz +BIbsKxQOC8opGdcn7TcSqCUeyyCd8JHUfg== +-----END EC PRIVATE KEY----- diff --git a/.env/discovery/server.pub b/.env/discovery/server.pub new file mode 100644 index 000000000..ee8222998 --- /dev/null +++ b/.env/discovery/server.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGdwVP3pf5UGc+ISdvzQG/qDjzbQi +ZuXVflmyEkPkUSFxyqPRupgzBIbsKxQOC8opGdcn7TcSqCUeyyCd8JHUfg== +-----END PUBLIC KEY----- diff --git a/cmd/genkeys.go b/cmd/genkeys.go index 51828c818..a14d43c27 100644 --- a/cmd/genkeys.go +++ b/cmd/genkeys.go @@ -44,10 +44,12 @@ func init() { routerCmd.AddCommand(genKeypairCmd("router")) brokerCmd.AddCommand(genKeypairCmd("broker")) handlerCmd.AddCommand(genKeypairCmd("handler")) + discoveryCmd.AddCommand(genKeypairCmd("discovery")) networkserverCmd.AddCommand(genKeypairCmd("networkserver")) routerCmd.AddCommand(genCertCmd("router")) brokerCmd.AddCommand(genCertCmd("broker")) handlerCmd.AddCommand(genCertCmd("handler")) + discoveryCmd.AddCommand(genCertCmd("discovery")) networkserverCmd.AddCommand(genCertCmd("networkserver")) } diff --git a/core/proxy/proxy.go b/core/proxy/proxy.go index dc037fdbc..867bf2032 100644 --- a/core/proxy/proxy.go +++ b/core/proxy/proxy.go @@ -16,9 +16,12 @@ type tokenProxier struct { func (p *tokenProxier) ServeHTTP(res http.ResponseWriter, req *http.Request) { if authorization := req.Header.Get("authorization"); authorization != "" { - if len(authorization) > 6 && strings.ToLower(authorization[0:7]) == "bearer " { + if len(authorization) >= 7 && strings.ToLower(authorization[0:7]) == "bearer " { req.Header.Set("Grpc-Metadata-Token", authorization[7:]) } + if len(authorization) >= 4 && strings.ToLower(authorization[0:4]) == "key " { + req.Header.Set("Grpc-Metadata-Key", authorization[4:]) + } } p.handler.ServeHTTP(res, req) } From 24435f0cfb80a8b5d42eb2046c7c049a1a396a2f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 7 Nov 2016 14:45:04 +0100 Subject: [PATCH 2125/2266] Move component auth init to auth.go --- core/component/auth.go | 114 +++++++++++++++++-- core/component/auth_test.go | 212 ++++++++++++++++++++++++++++++++++++ core/component/component.go | 83 ++++---------- core/component/config.go | 19 ++++ 4 files changed, 354 insertions(+), 74 deletions(-) create mode 100644 core/component/auth_test.go create mode 100644 core/component/config.go diff --git a/core/component/auth.go b/core/component/auth.go index 635dbdf1f..dede4c414 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -1,9 +1,15 @@ package component import ( + "crypto/tls" "time" + "regexp" + + "github.com/TheThingsNetwork/go-account-lib/cache" "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/tokenkey" + "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/security" @@ -12,6 +18,98 @@ import ( "google.golang.org/grpc/metadata" ) +// InitAuth initializes Auth functionality +func (c *Component) InitAuth() error { + inits := []func() error{ + c.initAuthServers, + c.initKeyPair, + } + if c.Config.UseTLS { + inits = append(inits, c.initTLS) + } + + for _, init := range inits { + if err := init(); err != nil { + return err + } + } + + return nil +} + +// AuthServerRegex gives the format of auth server configuration. +// Format: [username[:password]@]domain +// - usernames can contain lowercase letters, numbers, underscores and dashes +// - passwords can contain uppercase and lowercase letters, numbers, and special characters +// - domains can be http/https and can contain lowercase letters, numbers, dashes and dots +var AuthServerRegex = regexp.MustCompile(`^(http[s]?://)(?:([0-9a-z_-]+)(?::([0-9A-Za-z-!"#$%&'()*+,.:;<=>?@[\]^_{|}~]+))?@)?([0-9a-z.-]+)/?$`) + +// ErrNoAuthServerRegexMatch is returned when an auth server +var ErrNoAuthServerRegexMatch = errors.New("Account server did not match AuthServerRegex") + +func (c *Component) initAuthServers() error { + urlMap := make(map[string]string) + for id, url := range c.Config.AuthServers { + matches := AuthServerRegex.FindStringSubmatch(url) + if len(matches) == 0 { + return ErrNoAuthServerRegexMatch + } + urlMap[id] = matches[1] + matches[4] + } + c.TokenKeyProvider = tokenkey.HTTPProvider( + urlMap, + cache.WriteTroughCacheWithFormat(c.Config.KeyDir, "auth-%s.pub"), + ) + return nil +} + +// UpdateTokenKey updates the OAuth Bearer token key +func (c *Component) UpdateTokenKey() error { + if c.TokenKeyProvider == nil { + return errors.NewErrInternal("No public key provider configured for token validation") + } + + // Set up Auth Server Token Validation + err := c.TokenKeyProvider.Update() + if err != nil { + c.Ctx.Warnf("ttn: Failed to refresh public keys for token validation: %s", err.Error()) + } else { + c.Ctx.Info("ttn: Got public keys for token validation") + } + + return nil +} + +func (c *Component) initKeyPair() error { + priv, err := security.LoadKeypair(c.Config.KeyDir) + if err != nil { + return err + } + c.privateKey = priv + + pubPEM, _ := security.PublicPEM(priv) + c.Identity.PublicKey = string(pubPEM) + + return nil +} + +func (c *Component) initTLS() error { + cert, err := security.LoadCert(c.Config.KeyDir) + if err != nil { + return err + } + c.Identity.Certificate = string(cert) + + privPEM, _ := security.PrivatePEM(c.privateKey) + cer, err := tls.X509KeyPair(cert, privPEM) + if err != nil { + return err + } + + c.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} + return nil +} + // BuildJWT builds a short-lived JSON Web Token for this component func (c *Component) BuildJWT() (string, error) { if c.privateKey != nil { @@ -107,24 +205,16 @@ func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_d // ValidateTTNAuthContext gets a token from the context and validates it func (c *Component) ValidateTTNAuthContext(ctx context.Context) (*claims.Claims, error) { - md, ok := metadata.FromContext(ctx) - if !ok { - return nil, errors.NewErrInternal("Could not get metadata from context") - } - token, ok := md["token"] - if !ok || len(token) < 1 { - return nil, errors.NewErrInvalidArgument("Metadata", "token missing") + token, err := api.TokenFromContext(ctx) + if err != nil { + return nil, err } if c.TokenKeyProvider == nil { return nil, errors.NewErrInternal("No token provider configured") } - if token[0] == "" { - return nil, errors.NewErrInvalidArgument("Metadata", "token is empty") - } - - claims, err := claims.FromToken(c.TokenKeyProvider, token[0]) + claims, err := claims.FromToken(c.TokenKeyProvider, token) if err != nil { return nil, errors.NewErrPermissionDenied(err.Error()) } diff --git a/core/component/auth_test.go b/core/component/auth_test.go new file mode 100644 index 000000000..8c4bf40b3 --- /dev/null +++ b/core/component/auth_test.go @@ -0,0 +1,212 @@ +package component + +import ( + "context" + "fmt" + "math/rand" + "os" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/utils/security" + . "github.com/TheThingsNetwork/ttn/utils/testing" + "github.com/golang/mock/gomock" + "github.com/smartystreets/assertions" + "google.golang.org/grpc/metadata" +) + +func TestAuthServerRegex(t *testing.T) { + a := assertions.New(t) + var matches []string + matches = AuthServerRegex.FindStringSubmatch("https://user:pass@account.thethingsnetwork.org/") + a.So(matches, assertions.ShouldResemble, []string{"https://user:pass@account.thethingsnetwork.org/", "https://", "user", "pass", "account.thethingsnetwork.org"}) + matches = AuthServerRegex.FindStringSubmatch("https://user@account.thethingsnetwork.org/") + a.So(matches, assertions.ShouldResemble, []string{"https://user@account.thethingsnetwork.org/", "https://", "user", "", "account.thethingsnetwork.org"}) + matches = AuthServerRegex.FindStringSubmatch("https://account.thethingsnetwork.org/") + a.So(matches, assertions.ShouldResemble, []string{"https://account.thethingsnetwork.org/", "https://", "", "", "account.thethingsnetwork.org"}) +} + +func TestInitAuthServers(t *testing.T) { + if testing.Short() { + t.Skip("skipping test in short mode") + } + + a := assertions.New(t) + c := new(Component) + c.Config.KeyDir = os.TempDir() + c.Ctx = GetLogger(t, "TestInitAuthServers") + c.Config.AuthServers = map[string]string{ + "ttn": "https://account.thethingsnetwork.org/", + "ttn-user": "https://user@account.thethingsnetwork.org/", + "ttn-user-pass": "https://user:pass@account.thethingsnetwork.org/", + } + err := c.initAuthServers() + a.So(err, assertions.ShouldBeNil) + + { + k, err := c.TokenKeyProvider.Get("ttn", true) + a.So(err, assertions.ShouldBeNil) + a.So(k.Algorithm, assertions.ShouldEqual, "RS256") + } + + { + k, err := c.TokenKeyProvider.Get("ttn-user", true) + a.So(err, assertions.ShouldBeNil) + a.So(k.Algorithm, assertions.ShouldEqual, "RS256") + } + + { + k, err := c.TokenKeyProvider.Get("ttn-user-pass", true) + a.So(err, assertions.ShouldBeNil) + a.So(k.Algorithm, assertions.ShouldEqual, "RS256") + } + + a.So(c.UpdateTokenKey(), assertions.ShouldBeNil) +} + +func TestValidateTTNAuthContext(t *testing.T) { + a := assertions.New(t) + c := new(Component) + c.Config.KeyDir = os.TempDir() + c.Config.AuthServers = map[string]string{ + "ttn-account-preview": "https://preview.account.thethingsnetwork.org/", + } + err := c.initAuthServers() + a.So(err, assertions.ShouldBeNil) + + { + ctx := context.Background() + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + { + md := metadata.Pairs() + ctx := metadata.NewContext(context.Background(), md) + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + { + md := metadata.Pairs( + "id", "dev", + ) + ctx := metadata.NewContext(context.Background(), md) + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + { + md := metadata.Pairs( + "id", "dev", + "token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6InJvdXRlciIsImlhdCI6MTQ3NjQzOTQzOH0.Duz-E5aMYEPY_Nf5Pky7Qmjbs1dMp9PN9nMqbSzoU079b8TPL4DH2SKcRHrrMqieB3yhJb3YaQBfY6dKWfgVz8BmTeKlGXfFrqEj91y30J7r9_VsHRzgDMJedlqXryvf0S_yD27TsJ7TMbGYyE00T4tAX3Uf6wQZDhdyHNGtdf4jtoAjzOxVAodNtXZp26LR7fFk56UstBxOxztBMzyzmAdiTG4lSyEqq7zsuJcFjmHB9MfEoD4ZT-iTRL1ohFjGuj2HN49oPyYlZAVPP7QajLyNsLnv-nDqXE_QecOjAcEq4PLNJ3DpXtX-lo8I_F1eV9yQnDdQQi4EUvxmxZWeBA", + ) + ctx := metadata.NewContext(context.Background(), md) + _, err = c.ValidateTTNAuthContext(ctx) + a.So(err, assertions.ShouldBeNil) + } +} + +func TestInitKeyPair(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + c.Identity = new(discovery.Announcement) + c.Config.KeyDir = tmpDir + + a.So(c.initKeyPair(), assertions.ShouldNotBeNil) + + security.GenerateKeypair(tmpDir) + + a.So(c.initKeyPair(), assertions.ShouldBeNil) + + a.So(c.Identity.PublicKey, assertions.ShouldNotBeEmpty) + a.So(c.privateKey, assertions.ShouldNotBeNil) +} + +func TestInitTLS(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + c.Identity = new(discovery.Announcement) + c.Config.KeyDir = tmpDir + + security.GenerateKeypair(tmpDir) + c.initKeyPair() + + a.So(c.initTLS(), assertions.ShouldNotBeNil) + + security.GenerateCert(tmpDir) + + a.So(c.initTLS(), assertions.ShouldBeNil) + + a.So(c.Identity.Certificate, assertions.ShouldNotBeEmpty) + a.So(c.tlsConfig, assertions.ShouldNotBeNil) +} + +func TestInit(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + c.Identity = new(discovery.Announcement) + c.Config.KeyDir = tmpDir + + security.GenerateKeypair(tmpDir) + + a.So(c.InitAuth(), assertions.ShouldBeNil) +} + +func TestGetAndVerifyContext(t *testing.T) { + r := rand.New(rand.NewSource(time.Now().UnixNano())) + tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) + os.Mkdir(tmpDir, 755) + defer os.Remove(tmpDir) + + a := assertions.New(t) + c := new(Component) + + c.Identity = new(discovery.Announcement) + + c.Config.KeyDir = tmpDir + security.GenerateKeypair(tmpDir) + c.initKeyPair() + + { + ctx := c.GetContext("") + _, err := c.ValidateNetworkContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + c.Identity.Id = "test-context" + { + ctx := c.GetContext("") + _, err := c.ValidateNetworkContext(ctx) + a.So(err, assertions.ShouldNotBeNil) + } + + c.Identity.ServiceName = "test-service" + + ctrl := gomock.NewController(t) + discoveryClient := discovery.NewMockClient(ctrl) + c.Discovery = discoveryClient + + discoveryClient.EXPECT().Get("test-service", "test-context").Return(c.Identity, nil) + + ctx := c.GetContext("") + _, err := c.ValidateNetworkContext(ctx) + a.So(err, assertions.ShouldBeNil) + +} diff --git a/core/component/component.go b/core/component/component.go index 613fc547a..092b9137c 100644 --- a/core/component/component.go +++ b/core/component/component.go @@ -1,6 +1,7 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. +// Package component contains code that is shared by all components (discovery, router, broker, networkserver, handler) package component import ( @@ -11,14 +12,11 @@ import ( "runtime" "time" - "github.com/TheThingsNetwork/go-account-lib/cache" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/tokenkey" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/logging" - "github.com/TheThingsNetwork/ttn/utils/security" "github.com/apex/log" "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" @@ -26,6 +24,20 @@ import ( "google.golang.org/grpc/grpclog" ) +// Component contains the common attributes for all TTN components +type Component struct { + Config Config + Identity *pb_discovery.Announcement + Discovery pb_discovery.Client + Monitors map[string]*pb_monitor.Client + Ctx log.Interface + AccessToken string + privateKey *ecdsa.PrivateKey + tlsConfig *tls.Config + TokenKeyProvider tokenkey.Provider + status int64 +} + type Interface interface { RegisterRPC(s *grpc.Server) Init(c *Component) error @@ -58,7 +70,8 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo grpclog.SetLogger(logging.NewGRPCLogger(ctx)) component := &Component{ - Ctx: ctx, + Config: ConfigFromViper(), + Ctx: ctx, Identity: &pb_discovery.Announcement{ Id: viper.GetString("id"), Description: viper.GetString("description"), @@ -68,10 +81,10 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo Public: viper.GetBool("public"), }, AccessToken: viper.GetString("auth-token"), - TokenKeyProvider: tokenkey.HTTPProvider( - viper.GetStringMapString("auth-servers"), - cache.WriteTroughCacheWithFormat(viper.GetString("key-dir"), "auth-%s.pub"), - ), + } + + if err := component.InitAuth(); err != nil { + return nil, err } if serviceName != "discovery" { @@ -89,29 +102,6 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo } } - if priv, err := security.LoadKeypair(viper.GetString("key-dir")); err == nil { - component.privateKey = priv - - pubPEM, _ := security.PublicPEM(priv) - component.Identity.PublicKey = string(pubPEM) - - privPEM, _ := security.PrivatePEM(priv) - - if viper.GetBool("tls") { - cert, err := security.LoadCert(viper.GetString("key-dir")) - if err != nil { - return nil, err - } - component.Identity.Certificate = string(cert) - - cer, err := tls.X509KeyPair(cert, privPEM) - if err != nil { - return nil, err - } - component.tlsConfig = &tls.Config{Certificates: []tls.Certificate{cer}} - } - } - if healthPort := viper.GetInt("health-port"); healthPort > 0 { http.HandleFunc("/healthz", func(w http.ResponseWriter, req *http.Request) { switch component.GetStatus() { @@ -142,34 +132,3 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo return component, nil } - -// Component contains the common attributes for all TTN components -type Component struct { - Identity *pb_discovery.Announcement - Discovery pb_discovery.Client - Monitors map[string]*pb_monitor.Client - Ctx log.Interface - AccessToken string - privateKey *ecdsa.PrivateKey - tlsConfig *tls.Config - TokenKeyProvider tokenkey.Provider - status int64 -} - -// UpdateTokenKey updates the OAuth Bearer token key -func (c *Component) UpdateTokenKey() error { - if c.TokenKeyProvider == nil { - return errors.NewErrInternal("No public key provider configured for token validation") - } - - // Set up Auth Server Token Validation - err := c.TokenKeyProvider.Update() - if err != nil { - c.Ctx.Warnf("ttn: Failed to refresh public keys for token validation: %s", err.Error()) - } else { - c.Ctx.Info("ttn: Got public keys for token validation") - } - - return nil - -} diff --git a/core/component/config.go b/core/component/config.go new file mode 100644 index 000000000..a20402355 --- /dev/null +++ b/core/component/config.go @@ -0,0 +1,19 @@ +package component + +import "github.com/spf13/viper" + +// Config is the configuration for this component +type Config struct { + AuthServers map[string]string + KeyDir string + UseTLS bool +} + +// ConfigFromViper imports configuration from Viper +func ConfigFromViper() Config { + return Config{ + AuthServers: viper.GetStringMapString("auth-servers"), + KeyDir: viper.GetString("key-dir"), + UseTLS: viper.GetBool("tls"), + } +} From 74eaeed6bba325021ec58b91b371e155109083c8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 8 Nov 2016 11:23:55 +0100 Subject: [PATCH 2126/2266] Add methods to extract key from context --- api/context.go | 8 ++++++++ api/metadata.go | 19 +++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/api/context.go b/api/context.go index 33de0278a..99ff509b5 100644 --- a/api/context.go +++ b/api/context.go @@ -10,6 +10,14 @@ func TokenFromContext(ctx context.Context) (token string, err error) { return TokenFromMetadata(md) } +func KeyFromContext(ctx context.Context) (key string, err error) { + md, err := MetadataFromContext(ctx) + if err != nil { + return "", err + } + return KeyFromMetadata(md) +} + func IDFromContext(ctx context.Context) (token string, err error) { md, err := MetadataFromContext(ctx) if err != nil { diff --git a/api/metadata.go b/api/metadata.go index 52a0fccb9..c674729c7 100644 --- a/api/metadata.go +++ b/api/metadata.go @@ -6,10 +6,13 @@ import ( "google.golang.org/grpc/metadata" ) -var ErrContext = errors.NewErrInternal("Could not get metadata from context") - -var ErrNoToken = errors.NewErrInvalidArgument("Metadata", "token missing") -var ErrNoID = errors.NewErrInvalidArgument("Metadata", "id missing") +// Errors that are returned when an item could not be retrieved +var ( + ErrContext = errors.NewErrInternal("Could not get metadata from context") + ErrNoToken = errors.NewErrInvalidArgument("Metadata", "token missing") + ErrNoKey = errors.NewErrInvalidArgument("Metadata", "key missing") + ErrNoID = errors.NewErrInvalidArgument("Metadata", "id missing") +) func MetadataFromContext(ctx context.Context) (metadata.MD, error) { md, ok := metadata.FromContext(ctx) @@ -34,3 +37,11 @@ func TokenFromMetadata(md metadata.MD) (string, error) { } return token[0], nil } + +func KeyFromMetadata(md metadata.MD) (string, error) { + key, ok := md["key"] + if !ok || len(key) == 0 { + return "", ErrNoKey + } + return key[0], nil +} From 509fc190bf58e061fc02a93a684cc651206d316a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 8 Nov 2016 11:30:34 +0100 Subject: [PATCH 2127/2266] Add functions to exchange key for token --- core/component/auth.go | 63 +++++++++++++++++++++--- core/component/auth_test.go | 97 +++++++++++++++++++++++++++++++------ vendor/vendor.json | 50 ++++++++++--------- 3 files changed, 168 insertions(+), 42 deletions(-) diff --git a/core/component/auth.go b/core/component/auth.go index dede4c414..f5886e3f7 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -2,12 +2,14 @@ package component import ( "crypto/tls" - "time" - + "fmt" "regexp" + "time" "github.com/TheThingsNetwork/go-account-lib/cache" "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-account-lib/keys" + "github.com/TheThingsNetwork/go-account-lib/oauth" "github.com/TheThingsNetwork/go-account-lib/tokenkey" "github.com/TheThingsNetwork/ttn/api" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" @@ -37,6 +39,24 @@ func (c *Component) InitAuth() error { return nil } +type authServer struct { + url string + username string + password string +} + +func parseAuthServer(str string) (srv authServer, err error) { + matches := AuthServerRegex.FindStringSubmatch(str) + if len(matches) != 5 || matches[4] == "" { + return srv, ErrNoAuthServerRegexMatch + } + return authServer{ + url: matches[1] + matches[4], + username: matches[2], + password: matches[3], + }, nil +} + // AuthServerRegex gives the format of auth server configuration. // Format: [username[:password]@]domain // - usernames can contain lowercase letters, numbers, underscores and dashes @@ -50,11 +70,11 @@ var ErrNoAuthServerRegexMatch = errors.New("Account server did not match AuthSer func (c *Component) initAuthServers() error { urlMap := make(map[string]string) for id, url := range c.Config.AuthServers { - matches := AuthServerRegex.FindStringSubmatch(url) - if len(matches) == 0 { - return ErrNoAuthServerRegexMatch + srv, err := parseAuthServer(url) + if err != nil { + return err } - urlMap[id] = matches[1] + matches[4] + urlMap[id] = srv.url } c.TokenKeyProvider = tokenkey.HTTPProvider( urlMap, @@ -143,6 +163,37 @@ func (c *Component) GetContext(token string) context.Context { return ctx } +// ExchangeAppKeyForToken enables authentication with the App Access Key +func (c *Component) ExchangeAppKeyForToken(appID, key string) (string, error) { + issuerID := keys.KeyIssuer(key) + if issuerID == "" { + // Take the first configured auth server + for k := range c.Config.AuthServers { + issuerID = k + break + } + key = fmt.Sprintf("%s.%s", issuerID, key) + } + issuer, ok := c.Config.AuthServers[issuerID] + if !ok { + return "", fmt.Errorf("Auth server %s not registered", issuer) + } + + srv, _ := parseAuthServer(issuer) + + oauth := oauth.OAuth(srv.url, &oauth.Client{ + ID: srv.username, + Secret: srv.password, + }) + + token, err := oauth.ExchangeAppKeyForToken(appID, key) + if err != nil { + return "", err + } + + return token.AccessToken, nil +} + // ValidateNetworkContext validates the context of a network request (router-broker, broker-handler, etc) func (c *Component) ValidateNetworkContext(ctx context.Context) (component *pb_discovery.Announcement, err error) { defer func() { diff --git a/core/component/auth_test.go b/core/component/auth_test.go index 8c4bf40b3..58204408f 100644 --- a/core/component/auth_test.go +++ b/core/component/auth_test.go @@ -5,6 +5,7 @@ import ( "fmt" "math/rand" "os" + "strings" "testing" "time" @@ -16,20 +17,33 @@ import ( "google.golang.org/grpc/metadata" ) -func TestAuthServerRegex(t *testing.T) { +func TestParseAuthServer(t *testing.T) { a := assertions.New(t) - var matches []string - matches = AuthServerRegex.FindStringSubmatch("https://user:pass@account.thethingsnetwork.org/") - a.So(matches, assertions.ShouldResemble, []string{"https://user:pass@account.thethingsnetwork.org/", "https://", "user", "pass", "account.thethingsnetwork.org"}) - matches = AuthServerRegex.FindStringSubmatch("https://user@account.thethingsnetwork.org/") - a.So(matches, assertions.ShouldResemble, []string{"https://user@account.thethingsnetwork.org/", "https://", "user", "", "account.thethingsnetwork.org"}) - matches = AuthServerRegex.FindStringSubmatch("https://account.thethingsnetwork.org/") - a.So(matches, assertions.ShouldResemble, []string{"https://account.thethingsnetwork.org/", "https://", "", "", "account.thethingsnetwork.org"}) + { + srv, err := parseAuthServer("https://user:pass@account.thethingsnetwork.org/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "https://account.thethingsnetwork.org") + a.So(srv.username, assertions.ShouldEqual, "user") + a.So(srv.password, assertions.ShouldEqual, "pass") + } + { + srv, err := parseAuthServer("https://user@account.thethingsnetwork.org/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "https://account.thethingsnetwork.org") + a.So(srv.username, assertions.ShouldEqual, "user") + } + { + srv, err := parseAuthServer("http://account.thethingsnetwork.org/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "http://account.thethingsnetwork.org") + } } func TestInitAuthServers(t *testing.T) { - if testing.Short() { - t.Skip("skipping test in short mode") + for _, env := range strings.Split("ACCOUNT_SERVER_PROTO ACCOUNT_SERVER_USERNAME ACCOUNT_SERVER_PASSWORD ACCOUNT_SERVER_URL", " ") { + if os.Getenv(env) == "" { + t.Skipf("Skipping auth server test: %s configured", env) + } } a := assertions.New(t) @@ -37,9 +51,21 @@ func TestInitAuthServers(t *testing.T) { c.Config.KeyDir = os.TempDir() c.Ctx = GetLogger(t, "TestInitAuthServers") c.Config.AuthServers = map[string]string{ - "ttn": "https://account.thethingsnetwork.org/", - "ttn-user": "https://user@account.thethingsnetwork.org/", - "ttn-user-pass": "https://user:pass@account.thethingsnetwork.org/", + "ttn": fmt.Sprintf("%s://%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + "ttn-user": fmt.Sprintf("%s://%s@%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_USERNAME"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + "ttn-user-pass": fmt.Sprintf("%s://%s:%s@%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_USERNAME"), + os.Getenv("ACCOUNT_SERVER_PASSWORD"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), } err := c.initAuthServers() a.So(err, assertions.ShouldBeNil) @@ -66,11 +92,21 @@ func TestInitAuthServers(t *testing.T) { } func TestValidateTTNAuthContext(t *testing.T) { + for _, env := range strings.Split("ACCOUNT_SERVER_PROTO ACCOUNT_SERVER_URL", " ") { + if os.Getenv(env) == "" { + t.Skipf("Skipping auth server test: %s configured", env) + } + } + accountServer := fmt.Sprintf("%s://%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_URL"), + ) + a := assertions.New(t) c := new(Component) c.Config.KeyDir = os.TempDir() c.Config.AuthServers = map[string]string{ - "ttn-account-preview": "https://preview.account.thethingsnetwork.org/", + "ttn-account-preview": accountServer, } err := c.initAuthServers() a.So(err, assertions.ShouldBeNil) @@ -108,6 +144,39 @@ func TestValidateTTNAuthContext(t *testing.T) { } } +func TestExchangeAppKeyForToken(t *testing.T) { + for _, env := range strings.Split("ACCOUNT_SERVER_PROTO ACCOUNT_SERVER_USERNAME ACCOUNT_SERVER_PASSWORD ACCOUNT_SERVER_URL APP_ID APP_TOKEN", " ") { + if os.Getenv(env) == "" { + t.Skipf("Skipping auth server test: %s configured", env) + } + } + + a := assertions.New(t) + c := new(Component) + c.Config.KeyDir = os.TempDir() + c.Config.AuthServers = map[string]string{ + "ttn-account-preview": fmt.Sprintf("%s://%s:%s@%s", + os.Getenv("ACCOUNT_SERVER_PROTO"), + os.Getenv("ACCOUNT_SERVER_USERNAME"), + os.Getenv("ACCOUNT_SERVER_PASSWORD"), + os.Getenv("ACCOUNT_SERVER_URL"), + ), + } + c.initAuthServers() + + { + token, err := c.ExchangeAppKeyForToken(os.Getenv("APP_ID"), "ttn-account-preview."+os.Getenv("APP_TOKEN")) + a.So(err, assertions.ShouldBeNil) + a.So(token, assertions.ShouldNotBeEmpty) + } + + { + token, err := c.ExchangeAppKeyForToken(os.Getenv("APP_ID"), os.Getenv("APP_TOKEN")) + a.So(err, assertions.ShouldBeNil) + a.So(token, assertions.ShouldNotBeEmpty) + } +} + func TestInitKeyPair(t *testing.T) { r := rand.New(rand.NewSource(time.Now().UnixNano())) tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), r.Int63()) diff --git a/vendor/vendor.json b/vendor/vendor.json index b4ba12f3d..7f0ce9cbb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -11,62 +11,68 @@ { "checksumSHA1": "joWAlgyzANmhca8jS0qTN6J16ZU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "n7yqlvNiCbkkbUewQLOydLhtGgQ=", + "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", + "path": "github.com/TheThingsNetwork/go-account-lib/keys", + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" + }, + { + "checksumSHA1": "7bEBiMFJqABXsCSR/iCRLteZIa8=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "Tb98SzRS+adY/QZQVO7BdvqMftw=", + "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "iw9nN70RU1HK4VbosXcZp290WMg=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "830fbb344811b694e3481e34e2192369a82bf3ef", - "revisionTime": "2016-11-07T10:26:11Z" + "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", + "revisionTime": "2016-11-07T18:43:25Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", From 647ac5b6d46c939d9760d7624485755f86b6f4a8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 8 Nov 2016 11:30:44 +0100 Subject: [PATCH 2128/2266] Exchange key for token in Handler manager --- core/handler/manager_server.go | 141 +++++++++++++++++++++++---------- 1 file changed, 97 insertions(+), 44 deletions(-) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index db63fe574..9acf988cc 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -6,7 +6,9 @@ package handler import ( "fmt" + "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/rights" + "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" @@ -27,29 +29,46 @@ type handlerManager struct { devAddrManager pb_lorawan.DevAddrManagerClient } -func (h *handlerManager) getDevice(ctx context.Context, in *pb.DeviceIdentifier) (*device.Device, error) { - if !in.Validate() { - return nil, errors.NewErrInvalidArgument("Device Identifier", "validation failed") +func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID string) (context.Context, *claims.Claims, error) { + md, err := api.MetadataFromContext(ctx) + if err != nil { + return ctx, nil, err + } + // If token is empty, try to get the access key and convert it into a token + token, err := api.TokenFromMetadata(md) + if err != nil || token == "" { + key, err := api.KeyFromMetadata(md) + if err != nil { + return ctx, nil, errors.NewErrInvalidArgument("Metadata", "neither token nor key present") + } + token, err := h.handler.Component.ExchangeAppKeyForToken(appID, key) + if err != nil { + return ctx, nil, err + } + md = metadata.Join(md, metadata.Pairs("token", token)) + ctx = metadata.NewContext(ctx, md) } claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) if err != nil { - return nil, err + return ctx, nil, err } - if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + return ctx, claims, nil +} + +func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { + if !in.Validate() { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", "validation failed")) } - dev, err := h.handler.devices.Get(in.AppId, in.DevId) + + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, err + return nil, errors.BuildGRPCError(err) } - if !claims.AppRight(dev.AppID, rights.AppSettings) { - return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) } - return dev, nil -} -func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { - dev, err := h.getDevice(ctx, in) + dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -84,13 +103,21 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) } func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { - dev, err := h.getDevice(ctx, &pb.DeviceIdentifier{AppId: in.AppId, DevId: in.DevId}) - if err != nil && errors.GetErrType(err) != errors.NotFound { + if !in.Validate() { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device", "validation failed")) + } + + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { return nil, errors.BuildGRPCError(err) } + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) + } - if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Device") + dev, err := h.handler.devices.Get(in.AppId, in.DevId) + if err != nil && errors.GetErrType(err) != errors.NotFound { + return nil, errors.BuildGRPCError(err) } lorawan := in.GetLorawanDevice() @@ -178,7 +205,17 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { - dev, err := h.getDevice(ctx, in) + if !in.Validate() { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", "validation failed")) + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, errors.BuildGRPCError(err) + } + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) + } + dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -197,12 +234,12 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap if !in.Validate() { return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") } - claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { return nil, errors.BuildGRPCError(err) } - if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, grpcErrf(codes.PermissionDenied, "No access to this application") + if !claims.AppRight(in.AppId, rights.Devices) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) } devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { @@ -228,29 +265,18 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap return res, nil } -func (h *handlerManager) getApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*application.Application, error) { +func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { if !in.Validate() { - return nil, errors.NewErrInvalidArgument("Application Identifier", "validation failed") + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", "validation failed")) } - claims, err := h.handler.Component.ValidateTTNAuthContext(ctx) + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, err + return nil, errors.BuildGRPCError(err) } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) } app, err := h.handler.applications.Get(in.AppId) - if err != nil { - return nil, err - } - if !claims.AppRight(app.AppID, rights.AppSettings) { - return nil, errors.NewErrPermissionDenied(fmt.Sprintf("No access to Application %s", in.AppId)) - } - return app, nil -} - -func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { - app, err := h.getApplication(ctx, in) if err != nil { return nil, errors.BuildGRPCError(err) } @@ -265,7 +291,17 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI } func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { - app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) + if !in.Validate() { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", "validation failed")) + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, errors.BuildGRPCError(err) + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + } + app, err := h.handler.applications.Get(in.AppId) if err != nil && errors.GetErrType(err) != errors.NotFound { return nil, errors.BuildGRPCError(err) } @@ -300,13 +336,19 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica } func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { - app, err := h.getApplication(ctx, &pb.ApplicationIdentifier{AppId: in.AppId}) + if !in.Validate() { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application", "validation failed")) + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { return nil, errors.BuildGRPCError(err) } - - if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Application") + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + } + app, err := h.handler.applications.Get(in.AppId) + if err != nil { + return nil, errors.BuildGRPCError(err) } app.StartUpdate() @@ -325,12 +367,23 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) } func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { - _, err := h.getApplication(ctx, in) + if !in.Validate() { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", "validation failed")) + } + ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) + if err != nil { + return nil, errors.BuildGRPCError(err) + } + if !claims.AppRight(in.AppId, rights.AppSettings) { + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + } + _, err = h.handler.applications.Get(in.AppId) if err != nil { return nil, errors.BuildGRPCError(err) } // Get and delete all devices for this application + // TODO: add "app:devices:r" and "app:devices:w" check devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { return nil, errors.BuildGRPCError(err) From f40f2c61f2a4178da13aeac1302925dde9d83ee7 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Tue, 8 Nov 2016 12:33:43 +0100 Subject: [PATCH 2129/2266] Add link to make for windows --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index d5dfb50a6..ad55c8ff2 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ When you get started with The Things Network, you'll probably have some question On a fresh installation you might need to install the [MQTT plugin](https://www.rabbitmq.com/mqtt.html). If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabbitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. 5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` +6. You need make. For windows you can get it from [https://gnuarmeclipse.github.io/windows-build-tools/](https://gnuarmeclipse.github.io/windows-build-tools/) ## Set up The Things Network's backend for Development From f91fdc35bc220335ed620731235178aff350be34 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 8 Nov 2016 14:33:20 +0100 Subject: [PATCH 2130/2266] Makefile: Also install ttnctl in go install [Skip CI] --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dc8a2316f..eec3e4490 100644 --- a/Makefile +++ b/Makefile @@ -135,7 +135,7 @@ ttnctl-dev: CGO_ENABLED=1 ttnctl-dev: $(RELEASE_DIR)/ttnctl-$(GOOS)-$(GOARCH)$(GOEXE) install: - go install -v + go install -v . ./ttnctl dev: install ttn-dev ttnctl-dev From d8d64b027837a33311d1ef3d4795937f04fd43fd Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Tue, 8 Nov 2016 14:56:14 +0100 Subject: [PATCH 2131/2266] Improve install make instructions in readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ad55c8ff2..1c580c1a9 100644 --- a/README.md +++ b/README.md @@ -22,11 +22,11 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed (version 1.7 or later). 2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) -4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. +4. Install `make`. On Linux install `build-essential`. On MacOS, make comes with XCode or the developer tools. On Windows you can get make from [https://gnuarmeclipse.github.io/windows-build-tools/](https://gnuarmeclipse.github.io/windows-build-tools/) +5. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. On a fresh installation you might need to install the [MQTT plugin](https://www.rabbitmq.com/mqtt.html). If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabbitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. -5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` -6. You need make. For windows you can get it from [https://gnuarmeclipse.github.io/windows-build-tools/](https://gnuarmeclipse.github.io/windows-build-tools/) +6. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` ## Set up The Things Network's backend for Development From 9d3d4b275aaee77ff61cd979c20a39b15cb7d124 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Tue, 8 Nov 2016 15:05:21 +0100 Subject: [PATCH 2132/2266] Fix typo in ttnctl devicesListCmd --- ttnctl/cmd/devices_list.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/devices_list.go b/ttnctl/cmd/devices_list.go index 604de71d3..7fa1cf7d9 100644 --- a/ttnctl/cmd/devices_list.go +++ b/ttnctl/cmd/devices_list.go @@ -15,7 +15,7 @@ import ( var devicesListCmd = &cobra.Command{ Use: "list", Aliases: []string{"ls"}, - Short: "List al devices for the current application", + Short: "List all devices for the current application", Long: `ttnctl devices list can be used to list all devices for the current application.`, Example: `$ ttnctl devices list INFO Using Application AppID=test From d6ad4881466d48c98a4f415ecce3843b123ec0a1 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Tue, 8 Nov 2016 15:57:29 +0100 Subject: [PATCH 2133/2266] Improve readme based on review --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c580c1a9..a92ac3a8f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed (version 1.7 or later). 2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) -4. Install `make`. On Linux install `build-essential`. On MacOS, make comes with XCode or the developer tools. On Windows you can get make from [https://gnuarmeclipse.github.io/windows-build-tools/](https://gnuarmeclipse.github.io/windows-build-tools/) +4. Install `make`. On Linux install `build-essential`. On macOS, `make` comes with XCode or the developer tools. On Windows you can get `make` from [https://gnuarmeclipse.github.io/windows-build-tools/](https://gnuarmeclipse.github.io/windows-build-tools/) 5. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. On a fresh installation you might need to install the [MQTT plugin](https://www.rabbitmq.com/mqtt.html). If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabbitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. From d8271e5ac67bf0695e4a1ea53cb54197cccce91c Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Wed, 9 Nov 2016 18:35:28 +0100 Subject: [PATCH 2134/2266] Make validation return informative error instead of true/false Closes #348 --- api/api.go | 8 +- api/broker/validation.go | 114 ++++++++++++----------- api/gateway/validation.go | 23 +++-- api/handler/validation.go | 67 +++++++------- api/protocol/lorawan/validation.go | 132 ++++++++++++++++++--------- api/protocol/validation.go | 61 ++++++++----- api/router/validation.go | 38 ++++---- api/validation.go | 23 ++++- core/broker/manager_server.go | 4 +- core/broker/server.go | 13 ++- core/handler/manager_server.go | 32 +++---- core/handler/server.go | 9 +- core/networkserver/manager_server.go | 8 +- core/networkserver/server.go | 16 ++-- core/router/server.go | 14 ++- 15 files changed, 331 insertions(+), 231 deletions(-) diff --git a/api/api.go b/api/api.go index 931acb784..0b170fb97 100644 --- a/api/api.go +++ b/api/api.go @@ -13,17 +13,17 @@ import ( "google.golang.org/grpc/credentials" ) -// Validator interface is used to validate protos +// Validator interface is used to validate protos, nil if validation is successful type Validator interface { - Validate() bool + Validate() error } // Validate the given object if it implements the Validator interface -func Validate(in interface{}) bool { +func Validate(in interface{}) error { if v, ok := in.(Validator); ok { return v.Validate() } - return true + return nil } // Backoff indicates how long a client should wait between failed requests diff --git a/api/broker/validation.go b/api/broker/validation.go index 3efad3083..1232c4cd7 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -1,100 +1,110 @@ package broker -import "github.com/TheThingsNetwork/ttn/api" +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) // Validate implements the api.Validator interface -func (m *DownlinkOption) Validate() bool { +func (m *DownlinkOption) Validate() error { if m.Identifier == "" { - return false + return errors.NewErrInvalidArgument("Identifier", "can not be empty") } if m.GatewayId == "" { - return false + return errors.NewErrInvalidArgument("GatewayId", "can not be empty") } - if m.ProtocolConfig == nil || !m.ProtocolConfig.Validate() { - return false + if err := api.NotNilAndValid(m.ProtocolConfig, "ProtocolConfig"); err != nil { + return err } - if m.GatewayConfig == nil || !m.GatewayConfig.Validate() { - return false + if err := api.NotNilAndValid(m.GatewayConfig, "GatewayConfig"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *UplinkMessage) Validate() bool { - if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { - return false +func (m *UplinkMessage) Validate() error { + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err } - if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *DownlinkMessage) Validate() bool { - if m.DevId == "" || !api.ValidID(m.DevId) { - return false +func (m *DownlinkMessage) Validate() error { + if m.DevId == "" { + return errors.NewErrInvalidArgument("DevId", "can not be empty") } - if m.AppId == "" || !api.ValidID(m.AppId) { - return false + if api.ValidID(m.DevId) { + return errors.NewErrInvalidArgument("DevId", "has wrong format " + m.DevId) } - if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { - return false + if m.AppId == "" { + return errors.NewErrInvalidArgument("AppId", "can not be empty") } - return true + if api.ValidID(m.AppId) { + return errors.NewErrInvalidArgument("AppId", "has wrong format " + m.AppId) + } + if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { + return err + } + return nil } // Validate implements the api.Validator interface -func (m *DeduplicatedUplinkMessage) Validate() bool { - if m.DevId == "" || !api.ValidID(m.DevId) { - return false +func (m *DeduplicatedUplinkMessage) Validate() error { + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } - if m.AppId == "" || !api.ValidID(m.AppId) { - return false + if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + return err } - if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err } - if m.ResponseTemplate != nil && !m.ResponseTemplate.Validate() { - return false + if err := api.NotNilAndValid(m.ResponseTemplate, "ResponseTemplate"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *DeviceActivationRequest) Validate() bool { - if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { - return false +func (m *DeviceActivationRequest) Validate() error { + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err } - if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err } - if m.ActivationMetadata == nil || !m.ActivationMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.ActivationMetadata, "ActivationMetadata"); err != nil { + return err } - return true + + return nil } // Validate implements the api.Validator interface -func (m *DeduplicatedDeviceActivationRequest) Validate() bool { - if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { - return false +func (m *DeduplicatedDeviceActivationRequest) Validate() error { + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *ActivationChallengeRequest) Validate() bool { - return true +func (m *ActivationChallengeRequest) Validate() error { + return nil } // Validate implements the api.Validator interface -func (m *ApplicationHandlerRegistration) Validate() bool { - if m.AppId == "" || !api.ValidID(m.AppId) { - return false +func (m *ApplicationHandlerRegistration) Validate() error { + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } if m.HandlerId == "" { - return false + return errors.NewErrInvalidArgument("HandlerId", "can not be empty") } - return true + return nil } diff --git a/api/gateway/validation.go b/api/gateway/validation.go index f31e0107e..7cfbff8c7 100644 --- a/api/gateway/validation.go +++ b/api/gateway/validation.go @@ -1,26 +1,31 @@ package gateway +import "github.com/TheThingsNetwork/ttn/utils/errors" + // Validate implements the api.Validator interface -func (m *RxMetadata) Validate() bool { +func (m *RxMetadata) Validate() error { if m.GatewayId == "" { - return false + return errors.NewErrInvalidArgument("GatewayId", "can not be empty") } - return true + return nil } // Validate implements the api.Validator interface -func (m *TxConfiguration) Validate() bool { - return true +func (m *TxConfiguration) Validate() error { + return nil } // Validate implements the api.Validator interface -func (m *Status) Validate() bool { - return true +func (m *Status) Validate() error { + return nil } // Validate implements the api.Validator interface -func (m *GPSMetadata) Validate() bool { - return m != nil && !m.IsZero() +func (m *GPSMetadata) Validate() error { + if m == nil || m.IsZero() { + return errors.NewErrInvalidArgument("GPSMetadata", "can not be empty") + } + return nil } func (m GPSMetadata) IsZero() bool { diff --git a/api/handler/validation.go b/api/handler/validation.go index 2c47bacc6..24fbee36b 100644 --- a/api/handler/validation.go +++ b/api/handler/validation.go @@ -3,61 +3,64 @@ package handler import "github.com/TheThingsNetwork/ttn/api" // Validate implements the api.Validator interface -func (m *DeviceActivationResponse) Validate() bool { - if m.DownlinkOption == nil || !m.DownlinkOption.Validate() { - return false +func (m *DeviceActivationResponse) Validate() error { + if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { + return err } - if m.ActivationMetadata == nil || !m.ActivationMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.ActivationMetadata, "ActivationMetadata"); err != nil { + return err } - if m.Message != nil && !m.Message.Validate() { - return false + if err := api.NotNilAndValid(m.Message, "Message"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *ApplicationIdentifier) Validate() bool { - if m.AppId == "" || !api.ValidID(m.AppId) { - return false +func (m *ApplicationIdentifier) Validate() error { + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *Application) Validate() bool { - if m.AppId == "" || !api.ValidID(m.AppId) { - return false +func (m *Application) Validate() error { + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *DeviceIdentifier) Validate() bool { - if m.AppId == "" || !api.ValidID(m.AppId) { - return false +func (m *DeviceIdentifier) Validate() error { + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } - if m.DevId == "" || !api.ValidID(m.DevId) { - return false + if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *Device) Validate() bool { - if m.AppId == "" || !api.ValidID(m.AppId) { - return false +func (m *Device) Validate() error { + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } - if m.DevId == "" || !api.ValidID(m.DevId) { - return false + if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + return err } - if m.Device == nil || !api.Validate(m.Device) { - return false + if err := api.NotNilAndValid(m.Device, "Device"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *Device_LorawanDevice) Validate() bool { - return m.LorawanDevice.Validate() +func (m *Device_LorawanDevice) Validate() error { + if err := api.NotNilAndValid(m.LorawanDevice, "LorawanDevice"); err != nil { + return err + } + return nil } diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 6b2a9c55f..9d7c6b78e 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -1,121 +1,167 @@ package lorawan -import "github.com/TheThingsNetwork/ttn/api" +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) // Validate implements the api.Validator interface -func (m *DeviceIdentifier) Validate() bool { +func (m *DeviceIdentifier) Validate() error { if m.AppEui == nil || m.AppEui.IsEmpty() { - return false + return errors.NewErrInvalidArgument("AppEui", "can not be empty") } if m.DevEui == nil || m.DevEui.IsEmpty() { - return false + return errors.NewErrInvalidArgument("DevEui", "can not be empty") } - return true + return nil } // Validate implements the api.Validator interface -func (m *Device) Validate() bool { +func (m *Device) Validate() error { if m.AppEui == nil || m.AppEui.IsEmpty() { - return false + return errors.NewErrInvalidArgument("AppEui", "can not be empty") } if m.DevEui == nil || m.DevEui.IsEmpty() { - return false + return errors.NewErrInvalidArgument("DevEui", "can not be empty") } - if m.AppId == "" || !api.ValidID(m.AppId) { - return false + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } - if m.DevId == "" || !api.ValidID(m.DevId) { - return false + if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *Metadata) Validate() bool { +func (m *Metadata) Validate() error { switch m.Modulation { case Modulation_LORA: if m.DataRate == "" { - return false + return errors.NewErrInvalidArgument("DataRate", "can not be empty") } case Modulation_FSK: if m.BitRate == 0 { - return false + return errors.NewErrInvalidArgument("BitRate", "can not be empty") } } if m.CodingRate == "" { - return false + return errors.NewErrInvalidArgument("CodingRate", "can not be empty") } - return true + return nil } // Validate implements the api.Validator interface -func (m *TxConfiguration) Validate() bool { +func (m *TxConfiguration) Validate() error { switch m.Modulation { case Modulation_LORA: if m.DataRate == "" { - return false + return errors.NewErrInvalidArgument("DataRate", "can not be empty") } case Modulation_FSK: if m.BitRate == 0 { - return false + return errors.NewErrInvalidArgument("BitRate", "can not be empty") } } if m.CodingRate == "" { - return false + return errors.NewErrInvalidArgument("CodingRate", "can not be empty") } - return true + return nil } // Validate implements the api.Validator interface -func (m *ActivationMetadata) Validate() bool { +func (m *ActivationMetadata) Validate() error { if m.AppEui == nil || m.AppEui.IsEmpty() { - return false + return errors.NewErrInvalidArgument("AppEui", "can not be empty") } if m.DevEui == nil || m.DevEui.IsEmpty() { - return false + return errors.NewErrInvalidArgument("DevEui", "can not be empty") } if m.DevAddr != nil && m.DevAddr.IsEmpty() { - return false + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") } if m.NwkSKey != nil && m.NwkSKey.IsEmpty() { - return false + return errors.NewErrInvalidArgument("NwkSKey", "can not be empty") } - return true + return nil } // Validate implements the api.Validator interface -func (m *Message) Validate() bool { +func (m *Message) Validate() error { if m.Major != Major_LORAWAN_R1 { - return false + return errors.NewErrInvalidArgument("Major", "invalid value " + Major_LORAWAN_R1.String()) } switch m.MType { case MType_JOIN_REQUEST: - return m.GetJoinRequestPayload() != nil && m.GetJoinRequestPayload().Validate() + if m.GetJoinRequestPayload() == nil { + return errors.NewErrInvalidArgument("JoinRequestPayload", "can not be empty") + } + if err := m.GetJoinRequestPayload().Validate(); err != nil { + return errors.NewErrInvalidArgument("JoinRequestPayload", err.Error()) + } case MType_JOIN_ACCEPT: - return m.GetJoinAcceptPayload() != nil && m.GetJoinAcceptPayload().Validate() + if m.GetJoinAcceptPayload() == nil { + return errors.NewErrInvalidArgument("JoinAcceptPayload", "can not be empty") + } + if err := m.GetJoinAcceptPayload().Validate(); err != nil { + return errors.NewErrInvalidArgument("JoinAcceptPayload", err.Error()) + } case MType_UNCONFIRMED_UP, MType_UNCONFIRMED_DOWN, MType_CONFIRMED_UP, MType_CONFIRMED_DOWN: - return m.GetMacPayload() != nil && m.GetMacPayload().Validate() + if m.GetMacPayload() == nil { + return errors.NewErrInvalidArgument("MacPayload", "can not be empty") + } + if err := m.GetMacPayload().Validate(); err != nil { + return errors.NewErrInvalidArgument("MacPayload", err.Error()) + } + default: + return errors.NewErrInvalidArgument("MType", "unknown type " + m.MType.String()) } - return false + + return nil } // Validate implements the api.Validator interface -func (m *JoinRequestPayload) Validate() bool { - return len(m.AppEui) == 8 && len(m.DevEui) == 8 && len(m.DevNonce) == 2 +func (m *JoinRequestPayload) Validate() error { + if len(m.AppEui) != 8 { + return errors.NewErrInvalidArgument("AppEui", "length must be 8") + } + if len(m.DevEui) != 8 { + return errors.NewErrInvalidArgument("DevEui", "length must be 8") + } + if len(m.DevNonce) != 2 { + return errors.NewErrInvalidArgument("DevNonce", "length must be 2") + } + + return nil } // Validate implements the api.Validator interface -func (m *JoinAcceptPayload) Validate() bool { +func (m *JoinAcceptPayload) Validate() error { if len(m.Encrypted) != 0 { - return true + return nil } + if m.CfList != nil && len(m.CfList.Freq) != 5 { - return false + return errors.NewErrInvalidArgument("CfList.Freq", "length must be 5"); + } + + if len(m.DevAddr) != 4 { + return errors.NewErrInvalidArgument("DevAddr", "length must be 4") } - return len(m.DevAddr) == 4 && len(m.AppNonce) == 3 && len(m.NetId) == 3 + if len(m.AppNonce) != 3 { + return errors.NewErrInvalidArgument("AppNonce", "length must be 3") + } + if len(m.NetId) != 3 { + return errors.NewErrInvalidArgument("NetId", "length must be 3") + } + + return nil } // Validate implements the api.Validator interface -func (m *MACPayload) Validate() bool { - return len(m.DevAddr) == 4 +func (m *MACPayload) Validate() error { + if len(m.DevAddr) != 4 { + return errors.NewErrInvalidArgument("DevAddr", "length must be 4") + } + return nil } diff --git a/api/protocol/validation.go b/api/protocol/validation.go index c3a8d1f86..d439eea95 100644 --- a/api/protocol/validation.go +++ b/api/protocol/validation.go @@ -1,55 +1,72 @@ package protocol -import "github.com/TheThingsNetwork/ttn/api" +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) // Validate implements the api.Validator interface -func (m *RxMetadata) Validate() bool { - if m.Protocol == nil || !api.Validate(m.Protocol) { - return false +func (m *RxMetadata) Validate() error { + if m.Protocol == nil { + return errors.NewErrInvalidArgument("Protocol", "can not be empty") } - return true + if err := api.Validate(m.Protocol); err != nil { + return errors.NewErrInvalidArgument("Protocol", err.Error()) + } + return nil } // Validate implements the api.Validator interface -func (m *RxMetadata_Lorawan) Validate() bool { - return m.Lorawan.Validate() +func (m *RxMetadata_Lorawan) Validate() error { + if err := m.Lorawan.Validate(); err != nil { + return errors.NewErrInvalidArgument("Lorawan", err.Error()) + } + return nil } // Validate implements the api.Validator interface -func (m *TxConfiguration) Validate() bool { - if m.Protocol == nil || !api.Validate(m.Protocol) { - return false +func (m *TxConfiguration) Validate() error { + if m.Protocol == nil { + return errors.New("RxMetadata.Protocol is nil") } - return true + return api.Validate(m.Protocol) } // Validate implements the api.Validator interface -func (m *TxConfiguration_Lorawan) Validate() bool { +func (m *TxConfiguration_Lorawan) Validate() error { return m.Lorawan.Validate() } // Validate implements the api.Validator interface -func (m *ActivationMetadata) Validate() bool { - if m.Protocol == nil || !api.Validate(m.Protocol) { - return false +func (m *ActivationMetadata) Validate() error { + if m.Protocol == nil { + return errors.NewErrInvalidArgument("Protocol", "can not be empty") + } + if err := api.Validate(m.Protocol); err != nil { + return errors.NewErrInvalidArgument("Protocol", err.Error()) } - return true + + return nil } // Validate implements the api.Validator interface -func (m *ActivationMetadata_Lorawan) Validate() bool { +func (m *ActivationMetadata_Lorawan) Validate() error { return m.Lorawan.Validate() } // Validate implements the api.Validator interface -func (m *Message) Validate() bool { - if m.Protocol == nil || !api.Validate(m.Protocol) { - return false +func (m *Message) Validate() error { + if m.Protocol == nil { + return errors.NewErrInvalidArgument("Protocol", "can not be empty") + } + if err := api.Validate(m.Protocol); err != nil { + return errors.NewErrInvalidArgument("Protocol", err.Error()) } - return true + + return nil } // Validate implements the api.Validator interface -func (m *Message_Lorawan) Validate() bool { +func (m *Message_Lorawan) Validate() error { return m.Lorawan.Validate() } diff --git a/api/router/validation.go b/api/router/validation.go index e3affefc7..7a5d7b335 100644 --- a/api/router/validation.go +++ b/api/router/validation.go @@ -1,34 +1,36 @@ package router +import "github.com/TheThingsNetwork/ttn/api" + // Validate implements the api.Validator interface -func (m *UplinkMessage) Validate() bool { - if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { - return false +func (m *UplinkMessage) Validate() error { + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err } - if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *DownlinkMessage) Validate() bool { - if m.ProtocolConfiguration == nil || !m.ProtocolConfiguration.Validate() { - return false +func (m *DownlinkMessage) Validate() error { + if err := api.NotNilAndValid(m.ProtocolConfiguration, "ProtocolConfiguration"); err != nil { + return err } - if m.GatewayConfiguration == nil || !m.GatewayConfiguration.Validate() { - return false + if err := api.NotNilAndValid(m.GatewayConfiguration, "GatewayConfiguration"); err != nil { + return err } - return true + return nil } // Validate implements the api.Validator interface -func (m *DeviceActivationRequest) Validate() bool { - if m.GatewayMetadata == nil || !m.GatewayMetadata.Validate() { - return false +func (m *DeviceActivationRequest) Validate() error { + if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { + return err } - if m.ProtocolMetadata == nil || !m.ProtocolMetadata.Validate() { - return false + if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { + return err } - return true + return nil } diff --git a/api/validation.go b/api/validation.go index 74bca11e9..c1fa3e498 100644 --- a/api/validation.go +++ b/api/validation.go @@ -1,6 +1,9 @@ package api -import "regexp" +import ( + "regexp" + "github.com/TheThingsNetwork/ttn/utils/errors" +) var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") @@ -8,3 +11,21 @@ var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") func ValidID(id string) bool { return idRegexp.Match([]byte(id)) } + +func NotEmptyAndValidId(id string, argument string) error { + if id == "" { + return errors.NewErrInvalidArgument(argument, "can not be empty") + } + if !ValidID(id) { + errors.NewErrInvalidArgument(argument, "has wrong format " + id) + } + return nil +} + + +func NotNilAndValid(in interface{}, argument string) error { + if in == nil { + return errors.NewErrInvalidArgument(argument, "can not be empty") + } + return Validate(in) +} diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index def87bea8..e857456b8 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -51,8 +51,8 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if err != nil { return nil, errors.BuildGRPCError(errors.FromGRPCError(err)) } - if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Handler Registration") + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Handler Registration")) } if !claims.AppRight(in.AppId, rights.AppSettings) { return nil, grpcErrf(codes.PermissionDenied, "No access to this application") diff --git a/core/broker/server.go b/core/broker/server.go index 931408eb5..2771ba95c 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -12,7 +12,6 @@ import ( "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/codes" ) type brokerRPC struct { @@ -57,8 +56,8 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { if err != nil { return err } - if !uplink.Validate() { - return grpcErrf(codes.InvalidArgument, "Invalid Uplink") + if err := uplink.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) } go b.broker.HandleUplink(uplink) } @@ -104,8 +103,8 @@ func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { if err != nil { return err } - if !downlink.Validate() { - return grpcErrf(codes.InvalidArgument, "Invalid Downlink") + if err := downlink.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Downlink")) } go func(downlink *pb.DownlinkMessage) { // Get latest Handler metadata @@ -133,8 +132,8 @@ func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques if err != nil { return nil, errors.BuildGRPCError(err) } - if !req.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + if err := req.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } res, err = b.broker.HandleActivation(req) if err != nil { diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 9acf988cc..eca042643 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -56,8 +56,8 @@ func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID st } func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", err.Error())) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) @@ -103,8 +103,8 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) } func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device", err.Error())) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) @@ -205,8 +205,8 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", err.Error())) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -231,8 +231,8 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifi } func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.DeviceList, error) { - if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Application Identifier") + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Identifier")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -266,8 +266,8 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap } func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.Application, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.NewErrInvalidArgument("Application Identifier", err.Error()) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -291,8 +291,8 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI } func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", err.Error())) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -336,8 +336,8 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica } func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application", err.Error())) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -367,8 +367,8 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) } func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { - if !in.Validate() { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", "validation failed")) + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", err.Error())) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { diff --git a/core/handler/server.go b/core/handler/server.go index 3c7f83d1e..4bc9783da 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -9,7 +9,6 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/codes" ) type handlerRPC struct { @@ -23,8 +22,8 @@ func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_brok if err != nil { return nil, errors.BuildGRPCError(err) } - if !challenge.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + if err := challenge.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Challenge Request")) } res, err := h.handler.HandleActivationChallenge(challenge) if err != nil { @@ -38,8 +37,8 @@ func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.Dedupli if err != nil { return nil, errors.BuildGRPCError(err) } - if !activation.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + if err := activation.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } res, err := h.handler.HandleActivation(activation) if err != nil { diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index cd1190195..2e4edc11a 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -23,8 +23,8 @@ type networkServerManager struct { } func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { - if !in.Validate() { - return nil, errors.NewErrInvalidArgument("Device Identifier", "validation failed") + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", err.Error())) } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) if err != nil { @@ -72,8 +72,8 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev return nil, errors.BuildGRPCError(err) } - if !in.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Device") + if err := in.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device")) } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 44e5196d6..21386c038 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -69,8 +69,8 @@ func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *br if err := s.ValidateContext(ctx); err != nil { return nil, errors.BuildGRPCError(err) } - if !activation.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + if err := activation.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } res, err := s.networkServer.HandlePrepareActivation(activation) if err != nil { @@ -83,8 +83,8 @@ func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.Dev if err := s.ValidateContext(ctx); err != nil { return nil, errors.BuildGRPCError(err) } - if !activation.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + if err := activation.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } res, err := s.networkServer.HandleActivate(activation) if err != nil { @@ -97,8 +97,8 @@ func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.Deduplica if err := s.ValidateContext(ctx); err != nil { return nil, errors.BuildGRPCError(err) } - if !message.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Uplink") + if err := message.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) } res, err := s.networkServer.HandleUplink(message) if err != nil { @@ -111,8 +111,8 @@ func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.Downlin if err := s.ValidateContext(ctx); err != nil { return nil, errors.BuildGRPCError(err) } - if !message.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Downlink") + if err := message.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Downlink")) } res, err := s.networkServer.HandleDownlink(message) if err != nil { diff --git a/core/router/server.go b/core/router/server.go index 2caf09547..7bd2de659 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -16,7 +16,6 @@ import ( "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/codes" ) type routerRPC struct { @@ -75,8 +74,8 @@ func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { if err != nil { return err } - if !status.Validate() { - return grpcErrf(codes.InvalidArgument, "Invalid Gateway Status") + if err := status.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Gateway Status")) } go r.router.HandleGatewayStatus(gateway.ID, status) } @@ -97,8 +96,8 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { if err != nil { return err } - if !uplink.Validate() { - return grpcErrf(codes.InvalidArgument, "Invalid Uplink") + if err := uplink.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) } go r.router.HandleUplink(gateway.ID, uplink) } @@ -138,9 +137,8 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques if err != nil { return nil, errors.BuildGRPCError(err) } - - if !req.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Activation Request") + if err := req.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } return r.router.HandleActivation(gateway.ID, req) } From ca8ca95771df2b7247fa70f50617c262f12bb3fd Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Wed, 9 Nov 2016 19:36:36 +0100 Subject: [PATCH 2135/2266] Make validation return informative error instead of true/false Closes #348 --- api/api.go | 1 + api/broker/validation.go | 4 +- api/protocol/lorawan/validation.go | 6 +- api/protocol/validation.go | 2 +- api/validation.go | 10 +- api/validation_test.go | 43 +++++ vendor/vendor.json | 254 ++++++++++++++--------------- 7 files changed, 182 insertions(+), 138 deletions(-) create mode 100644 api/validation_test.go diff --git a/api/api.go b/api/api.go index 0b170fb97..f0ccd1c76 100644 --- a/api/api.go +++ b/api/api.go @@ -19,6 +19,7 @@ type Validator interface { } // Validate the given object if it implements the Validator interface +// Must not be called with nil values! func Validate(in interface{}) error { if v, ok := in.(Validator); ok { return v.Validate() diff --git a/api/broker/validation.go b/api/broker/validation.go index 1232c4cd7..39508a34c 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -39,13 +39,13 @@ func (m *DownlinkMessage) Validate() error { return errors.NewErrInvalidArgument("DevId", "can not be empty") } if api.ValidID(m.DevId) { - return errors.NewErrInvalidArgument("DevId", "has wrong format " + m.DevId) + return errors.NewErrInvalidArgument("DevId", "has wrong format "+m.DevId) } if m.AppId == "" { return errors.NewErrInvalidArgument("AppId", "can not be empty") } if api.ValidID(m.AppId) { - return errors.NewErrInvalidArgument("AppId", "has wrong format " + m.AppId) + return errors.NewErrInvalidArgument("AppId", "has wrong format "+m.AppId) } if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { return err diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 9d7c6b78e..5695cb1af 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -89,7 +89,7 @@ func (m *ActivationMetadata) Validate() error { // Validate implements the api.Validator interface func (m *Message) Validate() error { if m.Major != Major_LORAWAN_R1 { - return errors.NewErrInvalidArgument("Major", "invalid value " + Major_LORAWAN_R1.String()) + return errors.NewErrInvalidArgument("Major", "invalid value "+Major_LORAWAN_R1.String()) } switch m.MType { case MType_JOIN_REQUEST: @@ -114,7 +114,7 @@ func (m *Message) Validate() error { return errors.NewErrInvalidArgument("MacPayload", err.Error()) } default: - return errors.NewErrInvalidArgument("MType", "unknown type " + m.MType.String()) + return errors.NewErrInvalidArgument("MType", "unknown type "+m.MType.String()) } return nil @@ -142,7 +142,7 @@ func (m *JoinAcceptPayload) Validate() error { } if m.CfList != nil && len(m.CfList.Freq) != 5 { - return errors.NewErrInvalidArgument("CfList.Freq", "length must be 5"); + return errors.NewErrInvalidArgument("CfList.Freq", "length must be 5") } if len(m.DevAddr) != 4 { diff --git a/api/protocol/validation.go b/api/protocol/validation.go index d439eea95..20c2928a5 100644 --- a/api/protocol/validation.go +++ b/api/protocol/validation.go @@ -7,7 +7,7 @@ import ( // Validate implements the api.Validator interface func (m *RxMetadata) Validate() error { - if m.Protocol == nil { + if m.Protocol == nil { return errors.NewErrInvalidArgument("Protocol", "can not be empty") } if err := api.Validate(m.Protocol); err != nil { diff --git a/api/validation.go b/api/validation.go index c1fa3e498..f211943c9 100644 --- a/api/validation.go +++ b/api/validation.go @@ -1,15 +1,16 @@ package api import ( - "regexp" "github.com/TheThingsNetwork/ttn/utils/errors" + "reflect" + "regexp" ) var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") // ValidID returns true if the given ID is a valid application or device ID func ValidID(id string) bool { - return idRegexp.Match([]byte(id)) + return idRegexp.MatchString(id) } func NotEmptyAndValidId(id string, argument string) error { @@ -17,14 +18,13 @@ func NotEmptyAndValidId(id string, argument string) error { return errors.NewErrInvalidArgument(argument, "can not be empty") } if !ValidID(id) { - errors.NewErrInvalidArgument(argument, "has wrong format " + id) + return errors.NewErrInvalidArgument(argument, "has wrong format "+id) } return nil } - func NotNilAndValid(in interface{}, argument string) error { - if in == nil { + if in == nil || reflect.ValueOf(in).IsNil() { return errors.NewErrInvalidArgument(argument, "can not be empty") } return Validate(in) diff --git a/api/validation_test.go b/api/validation_test.go new file mode 100644 index 000000000..114725048 --- /dev/null +++ b/api/validation_test.go @@ -0,0 +1,43 @@ +package api + +import ( + "testing" +) + +func TestValidate_nil(t *testing.T) { + Validate(nil) +} + +type invalid struct { + nestedPtr *interface{} +} + +func (i *invalid) Validate() error { + return NotNilAndValid(i.nestedPtr, "nestedPtr") +} + +func TestNotNilAndValid(t *testing.T) { + subject := invalid{} + err := NotNilAndValid(subject.nestedPtr, "subject") + if err == nil || err.Error() != "subject not valid: can not be empty" { + t.Error("Expected validation error: 'subject not valid: can not be empty' but found", err) + } +} + +func TestValidID(t *testing.T) { + if ValidID("a") { + t.Error("'a' is not a valid id") + } +} + +func TestNotEmptyAndValidId(t *testing.T) { + err := NotEmptyAndValidId("", "subject") + if err == nil || err.Error() != "subject not valid: can not be empty" { + t.Error("Expected validation error: 'subject not valid: can not be empty' but found", err) + } + + err = NotEmptyAndValidId("a", "subject") + if err == nil || err.Error() != "subject not valid: has wrong format a" { + t.Error("Expected validation error: 'subject not valid: has wrong format a' but found", err) + } +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 7f0ce9cbb..7ea4dd9ea 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,595 +3,595 @@ "ignore": "test", "package": [ { - "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", + "checksumSHA1": "TCzqKDFui8wGc2coQ70ZZ9co6CY=", "path": "github.com/StackExchange/wmi", "revision": "e54cbda6595d7293a7a468ccf9525f6bc8887f99", "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "joWAlgyzANmhca8jS0qTN6J16ZU=", + "checksumSHA1": "7bQvmTNCLpNSRlYlEYsRFOlmnb0=", "path": "github.com/TheThingsNetwork/go-account-lib/account", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", + "checksumSHA1": "9BUFkr1vS+9FDdMUT/YGxDN8F2M=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", + "checksumSHA1": "q6Me8tLgsDG4a8v9udRmfjVr0AY=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", + "checksumSHA1": "ejnGSPEus73obF6V6M1qP20r8gM=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", + "checksumSHA1": "SZdGFefKeqMHQl9KnEWLo9RGWTA=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "7bEBiMFJqABXsCSR/iCRLteZIa8=", + "checksumSHA1": "j+OxAByGiQ5eyNytVa7+hEA9iT0=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", + "checksumSHA1": "CrQxBYkLWrZ1H9RAfLpYYwntFjk=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", + "checksumSHA1": "KjGWTLBOpS/L4o4N2RnVboeMKl8=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", + "checksumSHA1": "djrnI836WstNSbVQ5qU8lSF1mgs=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "iw9nN70RU1HK4VbosXcZp290WMg=", + "checksumSHA1": "cd+lXfybfgRCSNFNGrDAuFkL3qM=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", + "checksumSHA1": "KPgYwQ5u2saZX6nXx6Q+QYTSUXA=", "path": "github.com/TheThingsNetwork/go-account-lib/util", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", + "checksumSHA1": "Z/H/SIYu/zsJhup0Uw0ECpNXshA=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", "revision": "364a5d5b165140adfe751763e651fca78ea5e7ed", "revisionTime": "2016-11-01T08:25:08Z" }, { - "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", + "checksumSHA1": "vqER+A692XagGmfLHfE6wfi2cCc=", "path": "github.com/apex/log", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "LjQdQscNb25c2HxbREjmFFOoyx4=", + "checksumSHA1": "Ql1hONEyZw+myzLqrVib7lJ4kes=", "path": "github.com/apex/log/handlers/json", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "AHCiF3VnEqmXyZDeH+z/IGsAtnI=", + "checksumSHA1": "M2eXW/i+VotmKxD3P4LcrGV237w=", "path": "github.com/apex/log/handlers/level", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "rxQUkWqruIZKpRzdwqrkxfcZvyw=", + "checksumSHA1": "9ZpSB5BUUtoSaW3vb2ITg+zHkUk=", "path": "github.com/apex/log/handlers/multi", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", + "checksumSHA1": "pI3gO8Krabth9Ehx31zinQqomWk=", "path": "github.com/asaskevich/govalidator", "revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", "revisionTime": "2016-10-01T16:31:30Z" }, { - "checksumSHA1": "eKlcnwFZSIut8pNEvkLrm3entgc=", + "checksumSHA1": "JgVmYVWUsM1R3yL+Ha44cLAHxZ4=", "path": "github.com/bluele/gcache", "revision": "740e70e1c445f08ae353214243a4fb0fbe970510", "revisionTime": "2016-10-27T05:39:47Z" }, { - "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", + "checksumSHA1": "HabHz9Fh89GYXP2mPZzHnNY3gfM=", "path": "github.com/brocaar/lorawan", "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", "revisionTime": "2016-09-30T18:12:03Z" }, { - "checksumSHA1": "MCRyJiR3hs/bt++6w5SCcyIYaZE=", + "checksumSHA1": "IYDPjFweUGLG1qxJ90kDrGEM+RE=", "path": "github.com/brocaar/lorawan/band", "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", "revisionTime": "2016-09-30T18:12:03Z" }, { - "checksumSHA1": "2Fy1Y6Z3lRRX1891WF/+HT4XS2I=", + "checksumSHA1": "PcdItMMwb39o55X5JulfTDLGOKE=", "path": "github.com/dgrijalva/jwt-go", "revision": "9ed569b5d1ac936e6494082958d63a6aa4fff99a", "revisionTime": "2016-11-01T19:39:35Z" }, { - "checksumSHA1": "PPyNjcW5yfrfXvARE74fh9JBSpo=", + "checksumSHA1": "rgSZOHu5s274YA70EsK6g7E3VQc=", "path": "github.com/eclipse/paho.mqtt.golang", "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", "revisionTime": "2016-10-12T12:34:52Z" }, { - "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", + "checksumSHA1": "CrfbTTSutkj1lpKT/9GXwhcx3xI=", "path": "github.com/eclipse/paho.mqtt.golang/packets", "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", "revisionTime": "2016-10-12T12:34:52Z" }, { - "checksumSHA1": "KCWVxG+J8SxHGlGiUghe0KBGsa8=", + "checksumSHA1": "9K7s0i+FyqEFNlu6ovGX9iOMSMQ=", "path": "github.com/fatih/structs", "revision": "dc3312cb1a4513a366c4c9e622ad55c32df12ed3", "revisionTime": "2016-08-07T23:55:29Z" }, { - "checksumSHA1": "hveFTNQ9YEyYRs6SWuXM+XU9qRI=", + "checksumSHA1": "NcUSqemYhygiMcCShH4W1LMm9Eo=", "path": "github.com/fsnotify/fsnotify", "revision": "fd9ec7deca8bf46ecd2a795baaacf2b3a9be1197", "revisionTime": "2016-10-26T20:31:22Z" }, { - "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", + "checksumSHA1": "IuKHgdsIy5+YbYCtD8YCVM9y7FI=", "path": "github.com/go-ole/go-ole", "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", "revisionTime": "2016-07-29T03:38:29Z" }, { - "checksumSHA1": "qLYVTQDhgrVIeZ2KI9eZV51mmug=", + "checksumSHA1": "HgOBsItWIoZjtrDzuhZfRrov3Tg=", "path": "github.com/go-ole/go-ole/oleutil", "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", "revisionTime": "2016-07-29T03:38:29Z" }, { - "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", + "checksumSHA1": "JmYN40G4IXpHgD46fdzSH+354/s=", "path": "github.com/gogo/protobuf/gogoproto", "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "oDNpaVF/BHUyme8A7RoBAH2q6is=", + "checksumSHA1": "Fhkmd1XWRuiWxPE1iKXjqbvVjFI=", "path": "github.com/gogo/protobuf/proto", "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", + "checksumSHA1": "zQrUmFXRFK09Ub6+2AOHCikmXF0=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", + "checksumSHA1": "x95Kau9SsidqdzIPo3iAmz+mqdk=", "path": "github.com/golang/mock/gomock", "revision": "bd3c8e81be01eef76d4b503f5e687d2d1354d2d9", "revisionTime": "2016-01-21T18:51:14Z" }, { - "checksumSHA1": "+HPilCYNEcR4B/Q13LiW3OF3i64=", + "checksumSHA1": "enOMCfj2ih2BfM8Nn1DNqJUpTEU=", "path": "github.com/golang/protobuf/jsonpb", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "oJGkod8y/3GTbKfazzQM+nQOPR0=", + "checksumSHA1": "V+dsLhBAmvoCO7yIk8VXEh/8r+M=", "path": "github.com/golang/protobuf/proto", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "juNiTc9bfhQYo4BkWc83sW4Z5gw=", + "checksumSHA1": "BCNnZKNwGrJIBy+7F2iW557fI/Q=", "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", + "checksumSHA1": "CbddspWWKZsS/Vw16UPCvvrVG3I=", "path": "github.com/golang/protobuf/ptypes/empty", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", + "checksumSHA1": "X7kjOSGiHkg/dfJY8PhGWc4F4ls=", "path": "github.com/gosuri/uitable", "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "hfL7iFULaUity86NGidQt/AiYyo=", + "checksumSHA1": "ubZKTAH5dGTapCUCUvxLLN2xzW0=", "path": "github.com/gosuri/uitable/util/strutil", "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "xUvDrGeY4HijGKl+W8WSvWl1Evs=", + "checksumSHA1": "y8lWvQbCnejmDN0+XvkUzhP9NqM=", "path": "github.com/gosuri/uitable/util/wordwrap", "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "mJuOZXdE9GBhUTHeQ/Wm8U1rR4I=", + "checksumSHA1": "RAddxvUxP232tqSfc7ApAZFm+pU=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "7K3QiKeoZ0tgA8ymV9vicmfA97Q=", + "checksumSHA1": "2dObfehqh2fWi4mXG2vB1ZZtiR4=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "mYOr8zd2ww05AQ59oq/xdKL+mJo=", + "checksumSHA1": "U+OWOFSEeSKXru4OypJwLWZRIAs=", "path": "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", + "checksumSHA1": "KA57yh+m9mEFa0VueMK1qvMz3b8=", "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", + "checksumSHA1": "/lypnmuhzHEzMTarlV6dbnl01GU=", "path": "github.com/hashicorp/hcl", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", + "checksumSHA1": "7QpzuLg3aKDJWPD2kUz1Jw+JJfU=", "path": "github.com/hashicorp/hcl/hcl/ast", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "croNloscHsjX87X+4/cKOURf1EY=", + "checksumSHA1": "6EecoH3nidR8QJX7CTg8fA3JoEc=", "path": "github.com/hashicorp/hcl/hcl/parser", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", + "checksumSHA1": "2FxUL4byD9QLI/oVB1sQmIV3FZs=", "path": "github.com/hashicorp/hcl/hcl/scanner", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", + "checksumSHA1": "6n+9agh1diNM4kkVNkbG0/tcrXE=", "path": "github.com/hashicorp/hcl/hcl/strconv", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", + "checksumSHA1": "dljEjBgEewtuvGQ64XBvHB3iUkg=", "path": "github.com/hashicorp/hcl/hcl/token", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "138aCV5n8n7tkGYMsMVQQnnLq+0=", + "checksumSHA1": "fIT1ehfpm6RPCsotJ5qEZY1qWR0=", "path": "github.com/hashicorp/hcl/json/parser", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", + "checksumSHA1": "LntdNdMAHiPKqfBEVvQYwr9mSYw=", "path": "github.com/hashicorp/hcl/json/scanner", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", + "checksumSHA1": "knLekryOag5SGOV4jX8nwudBCDo=", "path": "github.com/hashicorp/hcl/json/token", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", + "checksumSHA1": "VzTtv1sy2diKOYEUc4mIe+8cPgE=", "path": "github.com/howeyc/gopass", "revision": "f5387c492211eb133053880d23dfae62aa14123d", "revisionTime": "2016-10-03T13:09:00Z" }, { - "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", + "checksumSHA1": "UYDU8nJJ/RXSTrR+dgQodj78uQI=", "path": "github.com/inconshreveable/mousetrap", "revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75", "revisionTime": "2014-10-17T20:07:13Z" }, { - "checksumSHA1": "YEZ/qI/rjyPhyyjuGI0NysuKIfA=", + "checksumSHA1": "nLZf/iMYZv0BsLGR0UOn/y3AJug=", "path": "github.com/jacobsa/crypto/cmac", "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", "revisionTime": "2016-04-10T22:58:39Z" }, { - "checksumSHA1": "NBvtX91AEKxFLmj8mwwhXEKl6d0=", + "checksumSHA1": "fLhDe/RAyAOWY2i2zCcu6V7o9EE=", "path": "github.com/jacobsa/crypto/common", "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", "revisionTime": "2016-04-10T22:58:39Z" }, { - "checksumSHA1": "g+afVQQVopBLiLB5pFZp/8s6aBs=", + "checksumSHA1": "kuwREeq736QUok7wyPzN9QsesTo=", "path": "github.com/kardianos/osext", "revision": "c2c54e542fb797ad986b31721e1baedf214ca413", "revisionTime": "2016-08-11T00:15:26Z" }, { - "checksumSHA1": "KQhA4EQp4Ldwj9nJZnEURlE6aQw=", + "checksumSHA1": "nvGuYn0dr8PhD6jFH9N/VGJQ2gg=", "path": "github.com/kr/fs", "revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b", "revisionTime": "2013-11-06T22:25:44Z" }, { - "checksumSHA1": "S6PDDQMYaKwLDIP/NsRYb4FRAqQ=", + "checksumSHA1": "xiTZt1TMF1nwhb7Qu1cexuZyuPk=", "path": "github.com/magiconair/properties", "revision": "0723e352fa358f9322c938cc2dadda874e9151a9", "revisionTime": "2016-09-08T09:36:58Z" }, { - "checksumSHA1": "yf185lmVPcvXjLZuPT1s4JzxI18=", + "checksumSHA1": "0EbIiM2NAjPjW6FT9lf3UevJzGM=", "path": "github.com/mattn/go-runewidth", "revision": "737072b4e32b7a5018b4a7125da8d12de90e8045", "revisionTime": "2016-10-12T01:35:12Z" }, { - "checksumSHA1": "AXacfEchaUqT5RGmPmMXsOWRhv8=", + "checksumSHA1": "BKqC6vWBJ+wzzBLcGBEG3YQkTY8=", "path": "github.com/mitchellh/go-homedir", "revision": "756f7b183b7ab78acdbbee5c7f392838ed459dda", "revisionTime": "2016-06-21T17:42:43Z" }, { - "checksumSHA1": "UuXgD2dDojfS8AViUEe15gLIWZE=", + "checksumSHA1": "2UFUWcJ5PR5sQgBitTz7gIxpcXg=", "path": "github.com/mitchellh/mapstructure", "revision": "f3009df150dadf309fdee4a54ed65c124afad715", "revisionTime": "2016-10-20T16:18:36Z" }, { - "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", + "checksumSHA1": "CF9y7b52fTlBeW9dKrvYXUeeOyg=", "path": "github.com/mwitkow/go-grpc-middleware", "revision": "0664ec3f4e6e08d02f2bf6a62be6900c0b82ea69", "revisionTime": "2016-09-11T13:13:45Z" }, { - "checksumSHA1": "8Y05Pz7onrQPcVWW6JStSsYRh6E=", + "checksumSHA1": "p85wl+d5C+h6wD454NGR06jZ6pA=", "path": "github.com/pelletier/go-buffruneio", "revision": "df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d", "revisionTime": "2016-01-24T19:35:03Z" }, { - "checksumSHA1": "CtI2rBQw2rxHNIU+a0rcAf29Sco=", + "checksumSHA1": "b0hNJsJNnt8RrXsFEhidgFilF+w=", "path": "github.com/pelletier/go-toml", "revision": "45932ad32dfdd20826f5671da37a5f3ce9f26a8d", "revisionTime": "2016-09-20T07:07:15Z" }, { - "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", + "checksumSHA1": "iwLqOY6Nr1BitBUex4K0Wep3JL8=", "path": "github.com/pkg/errors", "revision": "248dadf4e9068a0b3e79f02ed0a610d935de5302", "revisionTime": "2016-10-29T09:36:37Z" }, { - "checksumSHA1": "k9SlQdp/DTB72G/u4aNecX/fFIg=", + "checksumSHA1": "IcLlvmJUmPHbEBMDa6WxDzh0z9w=", "path": "github.com/pkg/sftp", "revision": "4d0e916071f68db74f8a73926335f809396d6b42", "revisionTime": "2016-09-30T22:07:58Z" }, { - "checksumSHA1": "GiX6yRUzizn1C+ckgj1xLFLoz8g=", + "checksumSHA1": "+yZxFRm3leIBDLHJeM8BK6fsUhg=", "path": "github.com/rcrowley/go-metrics", "revision": "ab2277b1c5d15c3cba104e9cbddbdfc622df5ad8", "revisionTime": "2016-09-21T19:52:07Z" }, { - "checksumSHA1": "5qwv3yDROEz5ZV8HztOBmQxen8c=", + "checksumSHA1": "GqQXu+amOr1hSLHmU3wGSyCuGfk=", "path": "github.com/robertkrimen/otto", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=", + "checksumSHA1": "yPk7CP14otQHh23dmGBYFWhVzNc=", "path": "github.com/robertkrimen/otto/ast", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "L0KsB2EzTlPgv0iae3q3SukNW7U=", + "checksumSHA1": "Ih8IyrcvYIRe1H1BbExbHs+s8kM=", "path": "github.com/robertkrimen/otto/dbg", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "euDLJKhw4doeTSxjEoezjxYXLzs=", + "checksumSHA1": "FnQzQouypvp1tUyPE3mYbWNH6es=", "path": "github.com/robertkrimen/otto/file", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "LLuLITFO8chqSG0+APJIy5NtOHU=", + "checksumSHA1": "61W7S2mWb4DujuqG2msycPC+Q2E=", "path": "github.com/robertkrimen/otto/parser", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "7J/7NaYRqKhBvZ+dTIutsEoEgFw=", + "checksumSHA1": "i9dRdlVYwqPt0PTRnJS6GxKoCRw=", "path": "github.com/robertkrimen/otto/registry", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "/jMXYuXycBpTqWhRyJ2xsqvHvQI=", + "checksumSHA1": "KJp6P0GEODVBpNGqY8Scn+Q/0lg=", "path": "github.com/robertkrimen/otto/token", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "pkzbHrJg79tEMajgi+7ED9rPVuQ=", + "checksumSHA1": "wVbykdHaj8eufSPcydD8cI3gbA0=", "path": "github.com/shirou/gopsutil/cpu", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", + "checksumSHA1": "OJ2K143hFQ5ePll2wLTiorCnogY=", "path": "github.com/shirou/gopsutil/host", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", + "checksumSHA1": "9rHfc6RQjWyCl89gejFuX9p+TmI=", "path": "github.com/shirou/gopsutil/internal/common", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", + "checksumSHA1": "u+w5wFphtvxpuPp5GN74xBg/zjA=", "path": "github.com/shirou/gopsutil/load", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", + "checksumSHA1": "P4noCjIYU1jbzVHxyVEDkjj1IAk=", "path": "github.com/shirou/gopsutil/mem", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "Sxy9qDOEqAa+F2P6rSLKQGZL+l8=", + "checksumSHA1": "h9btZjjcHSoQRhBNeLVUxhz0fF0=", "path": "github.com/shirou/gopsutil/net", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "Cmj97derBOe/m/D2Db++Z57uWBw=", + "checksumSHA1": "RWhx870jIT8+pBYxYTeCf4GN/qc=", "path": "github.com/shirou/gopsutil/process", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", + "checksumSHA1": "K+Z5DS27MEqKqgUXEaiCYA5WajM=", "path": "github.com/shirou/w32", "revision": "bb4de0191aa41b5507caa14b0650cdbddcd9280b", "revisionTime": "2016-09-30T03:27:40Z" }, { - "checksumSHA1": "6AYg4fjEvFuAVN3wHakGApjhZAM=", + "checksumSHA1": "H2ZvmkbJ9YyhyaVc3YSYVTJlNGU=", "path": "github.com/smartystreets/assertions", "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", "revisionTime": "2016-07-07T19:03:55Z" }, { - "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", + "checksumSHA1": "D/geobFtKMvBIAONfT+4sie/PGk=", "path": "github.com/smartystreets/assertions/internal/go-render/render", "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", "revisionTime": "2016-07-07T19:03:55Z" }, { - "checksumSHA1": "SLC6TfV4icQA9l8YJQu8acJYbuo=", + "checksumSHA1": "NtJsLMlKqou92xDHcAvu4KzQtek=", "path": "github.com/smartystreets/assertions/internal/oglematchers", "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", "revisionTime": "2016-07-07T19:03:55Z" }, { - "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", + "checksumSHA1": "oNcjSQf1zGY27b9xipsqb2L4/RY=", "path": "github.com/smartystreets/go-aws-auth", "revision": "2043e6d0bb7e4c18464a7bba562acbe482e3cabd", "revisionTime": "2016-07-22T04:48:03Z" }, { - "checksumSHA1": "59wTbS4fE2282Q88NrBYImbFGbo=", + "checksumSHA1": "78ho4yEFjGrhCf8wnqjhV1S4+ZQ=", "path": "github.com/spf13/afero", "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", "revisionTime": "2016-09-19T21:01:14Z" }, { - "checksumSHA1": "u6B0SEgZ/TUEfIvF6w/HnFVQbII=", + "checksumSHA1": "gne0kouue3bbQVpN31WK7QKRk7I=", "path": "github.com/spf13/afero/mem", "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", "revisionTime": "2016-09-19T21:01:14Z" }, { - "checksumSHA1": "sLyAUiIT7V0DNVp6yBhW4Ms5BEs=", + "checksumSHA1": "3kb6SeZJKlCGSMeE1zADtBC96ic=", "path": "github.com/spf13/afero/sftp", "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", "revisionTime": "2016-09-19T21:01:14Z" }, { - "checksumSHA1": "M4qI7Ul0vf2uj3fF69DvRnwqfd0=", + "checksumSHA1": "TCBpgfcH969AYYryAFARBeqQnSQ=", "path": "github.com/spf13/cast", "revision": "2580bc98dc0e62908119e4737030cc2fdfc45e4c", "revisionTime": "2016-09-26T08:42:49Z" }, { - "checksumSHA1": "FZ0r4TzEy9UxXLkFVXFygApni4M=", + "checksumSHA1": "Y0fPCXjwXkF7NpBM8L8Od4z4vp4=", "path": "github.com/spf13/cobra", "revision": "6e91dded25d73176bf7f60b40dd7aa1f0bf9be8d", "revisionTime": "2016-10-26T01:28:26Z" }, { - "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", + "checksumSHA1": "M6v7rwFodsBRcOT6HH3ppNdo0d4=", "path": "github.com/spf13/jwalterweatherman", "revision": "33c24e77fb80341fe7130ee7c594256ff08ccc46", "revisionTime": "2016-03-01T12:00:06Z" }, { - "checksumSHA1": "GxPD7A0NjMDom1xte0mghkpzr0E=", + "checksumSHA1": "7pwNi6OV1KCJLv76PrzW//SrGS8=", "path": "github.com/spf13/pflag", "revision": "5ccb023bc27df288a957c5e994cd44fd19619465", "revisionTime": "2016-10-24T13:13:51Z" }, { - "checksumSHA1": "802GjFNHMmnFXEIkQ137ucUUacI=", + "checksumSHA1": "Fk2TsHHHKycaJhyLheArJwi3mgY=", "path": "github.com/spf13/viper", "revision": "651d9d916abc3c3d6a91a12549495caba5edffd2", "revisionTime": "2016-10-29T21:33:52Z" }, { - "checksumSHA1": "rKV8YLkXpeNG1Oix8hlYqVsEFb4=", + "checksumSHA1": "uboKbybSheSH99DWeX328g+tfbw=", "path": "github.com/streadway/amqp", "revision": "2e25825abdbd7752ff08b270d313b93519a0a232", "revisionTime": "2016-03-11T21:55:03Z" }, { - "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", + "checksumSHA1": "5VPm/vvjh+5aQxUymjxNkU/vHGg=", "path": "github.com/tj/go-elastic", "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" }, { - "checksumSHA1": "nL4enNHknemOmxcaPTIJCrJc0/I=", + "checksumSHA1": "GUIZQPUYHp+OYcAa7xgspP0sI2Y=", "path": "github.com/tj/go-elastic/aliases", "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" }, { - "checksumSHA1": "SgbyhOvKGvet/Nw70Rxa8d3gLZ0=", + "checksumSHA1": "8WVfx2stDSNVDmR3d13xVI6KfvA=", "path": "github.com/tj/go-elastic/batch", "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" @@ -675,13 +675,13 @@ "revisionTime": "2016-11-04T22:18:50Z" }, { - "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", + "checksumSHA1": "xbp8j2KLXQ34tAjnM+zrzMZZKcE=", "path": "golang.org/x/oauth2", "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", "revisionTime": "2016-06-06T21:15:38Z" }, { - "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", + "checksumSHA1": "lakUMUcLmmJ910/aft5iXQr/Fas=", "path": "golang.org/x/oauth2/internal", "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", "revisionTime": "2016-06-06T21:15:38Z" @@ -705,157 +705,157 @@ "revisionTime": "2016-10-19T13:35:53Z" }, { - "checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=", + "checksumSHA1": "8xPvH/BFj/tgNJD1U8nUptEW254=", "path": "google.golang.org/appengine/internal", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", + "checksumSHA1": "RD2tuuDmEYtQdNm5844fOVPBVkA=", "path": "google.golang.org/appengine/internal/base", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", + "checksumSHA1": "6JDEiCxSEIK1Bfo9IWxFly+RXIw=", "path": "google.golang.org/appengine/internal/datastore", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", + "checksumSHA1": "VSjY7WrvJPnBWkC2L7xsO2dWyfc=", "path": "google.golang.org/appengine/internal/log", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", + "checksumSHA1": "WE74DpnexU2S0ZTtnwFEPKzlJfI=", "path": "google.golang.org/appengine/internal/remote_api", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", + "checksumSHA1": "UNZNiUKpkvGKzBrYsdjUx7cUM5g=", "path": "google.golang.org/appengine/internal/urlfetch", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", + "checksumSHA1": "k2vHVgCk1AqIZravw4ZP3NbiRJM=", "path": "google.golang.org/appengine/urlfetch", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "KzbvI1E+0cJe41jNUEoBksFpAZ8=", + "checksumSHA1": "wez47b9vPwT1MoMgT5wbjVeWFt8=", "path": "google.golang.org/grpc", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", + "checksumSHA1": "h4kdKXtXyT0z/ufmJFN8+p4lILI=", "path": "google.golang.org/grpc/codes", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", + "checksumSHA1": "H5ZYYPZw15XMgyXgu+edvbXa8qY=", "path": "google.golang.org/grpc/credentials", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", + "checksumSHA1": "ebZ/HnoSyN49a5qC/ciXvOS8pNs=", "path": "google.golang.org/grpc/grpclog", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", + "checksumSHA1": "spX/LCShDQ1Fxm5/7rcVe+GYQn0=", "path": "google.golang.org/grpc/internal", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", + "checksumSHA1": "FfS+uNOaXRHZEhj+vGz+GkyHqQs=", "path": "google.golang.org/grpc/metadata", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", + "checksumSHA1": "V6pPMv24b/wpQSUrZT1EJ45XSAY=", "path": "google.golang.org/grpc/naming", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", + "checksumSHA1": "MpM8NbSbvKr0UdN+pF3qIQzZT3A=", "path": "google.golang.org/grpc/peer", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "HQJrtiTtr5eiRsXQLut2R1Q9kuY=", + "checksumSHA1": "qKXWDkD01BvFmLIakamkY0C/SJo=", "path": "google.golang.org/grpc/transport", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", + "checksumSHA1": "lzxr2rdWylya6Z5Ft4oA0Fvr4TU=", "path": "gopkg.in/bsm/ratelimit.v1", "revision": "db14e161995a5177acef654cb0dd785e8ee8bc22", "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "rr5P5knxMTqvnRElnvlrM4dyuQM=", + "checksumSHA1": "Wyj9ievtsiIyEICcydukdiKmV2I=", "path": "gopkg.in/redis.v5", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", + "checksumSHA1": "KgBz3V2H/OF/PTUlzHNZLETdqMs=", "path": "gopkg.in/redis.v5/internal", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", + "checksumSHA1": "9DDcWtzDkAGPKplEPcf0iyIn0aA=", "path": "gopkg.in/redis.v5/internal/consistenthash", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", + "checksumSHA1": "zXEc7u1ExA2urrGa1lFiDaElsuE=", "path": "gopkg.in/redis.v5/internal/hashtag", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "MuVVHw/uzk6gwBsQ8deMYacmgTM=", + "checksumSHA1": "UguaV//oFWmZMQkAmXVLJ+qVPBY=", "path": "gopkg.in/redis.v5/internal/pool", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "Pn4Vc2X2hUhm0SV7j82YlMW8j5o=", + "checksumSHA1": "sukBHMNbLaempvn9Oz60j4q66Hk=", "path": "gopkg.in/redis.v5/internal/proto", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", + "checksumSHA1": "rRJ0dXt61DcHYS043MAw+o1ReYs=", "path": "gopkg.in/sourcemap.v1", "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", "revisionTime": "2016-06-02T08:55:44Z" }, { - "checksumSHA1": "DPyTTxwhl5mrDF15nLNtshc0cWs=", + "checksumSHA1": "jd9JtETw+VJRYUfjyFPJEyYLpvA=", "path": "gopkg.in/sourcemap.v1/base64vlq", "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", "revisionTime": "2016-06-02T08:55:44Z" }, { - "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", + "checksumSHA1": "21kDrz4DnDppRHgK7vVmOFwgcSQ=", "path": "gopkg.in/yaml.v2", "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", "revisionTime": "2016-09-28T15:37:09Z" From 70996b4d6dac2a0067ca05800f2fce1ff815abcc Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Wed, 9 Nov 2016 22:24:12 +0100 Subject: [PATCH 2136/2266] Make validation return informative error instead of true/false Fixes from Review Closes #348 --- AUTHORS | 1 + api/api.go | 3 ++- api/broker/validation.go | 15 +++++---------- api/discovery/validation.go | 15 +++++++++------ api/protocol/lorawan/validation.go | 26 ++++++++++---------------- core/broker/server.go | 4 +--- core/handler/server.go | 2 -- core/networkserver/server.go | 2 +- core/router/server.go | 4 +--- core/types/activation.go | 6 ++++++ 10 files changed, 36 insertions(+), 42 deletions(-) diff --git a/AUTHORS b/AUTHORS index b85c1a113..68e1e9bc2 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,3 +15,4 @@ Johan Stokking Matthias Benkort Roman Volosatovs Romeo Van Snick +Tobias Kaupat diff --git a/api/api.go b/api/api.go index f0ccd1c76..2c1167275 100644 --- a/api/api.go +++ b/api/api.go @@ -13,8 +13,9 @@ import ( "google.golang.org/grpc/credentials" ) -// Validator interface is used to validate protos, nil if validation is successful +// Validator interface is used to validate protos type Validator interface { + // Returns the validation error or nil if valid Validate() error } diff --git a/api/broker/validation.go b/api/broker/validation.go index 39508a34c..a0e7368da 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -35,18 +35,13 @@ func (m *UplinkMessage) Validate() error { // Validate implements the api.Validator interface func (m *DownlinkMessage) Validate() error { - if m.DevId == "" { - return errors.NewErrInvalidArgument("DevId", "can not be empty") - } - if api.ValidID(m.DevId) { - return errors.NewErrInvalidArgument("DevId", "has wrong format "+m.DevId) - } - if m.AppId == "" { - return errors.NewErrInvalidArgument("AppId", "can not be empty") + if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + return err } - if api.ValidID(m.AppId) { - return errors.NewErrInvalidArgument("AppId", "has wrong format "+m.AppId) + if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + return err } + if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { return err } diff --git a/api/discovery/validation.go b/api/discovery/validation.go index 43d096f64..ccf971b6b 100644 --- a/api/discovery/validation.go +++ b/api/discovery/validation.go @@ -1,16 +1,19 @@ package discovery -import "github.com/TheThingsNetwork/ttn/api" +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) // Validate implements the api.Validator interface -func (m *Announcement) Validate() bool { - if m.Id == "" || !api.ValidID(m.Id) { - return false +func (m *Announcement) Validate() error { + if err := api.NotEmptyAndValidId(m.Id, "Id"); err != nil { + return err } switch m.ServiceName { case "router", "broker", "handler": default: - return false + return errors.InvalidArgument("ServiceName", "expected one of router, broker, handler but was "+m.ServiceName) } - return true + return nil } diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 5695cb1af..046dc25cb 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -122,14 +122,11 @@ func (m *Message) Validate() error { // Validate implements the api.Validator interface func (m *JoinRequestPayload) Validate() error { - if len(m.AppEui) != 8 { - return errors.NewErrInvalidArgument("AppEui", "length must be 8") - } - if len(m.DevEui) != 8 { - return errors.NewErrInvalidArgument("DevEui", "length must be 8") + if m.AppEui.IsEmpty() { + return errors.NewErrInvalidArgument("AppEui", "can not be empty") } - if len(m.DevNonce) != 2 { - return errors.NewErrInvalidArgument("DevNonce", "length must be 2") + if m.DevEui.IsEmpty() { + return errors.NewErrInvalidArgument("DevEui", "can not be empty") } return nil @@ -145,14 +142,11 @@ func (m *JoinAcceptPayload) Validate() error { return errors.NewErrInvalidArgument("CfList.Freq", "length must be 5") } - if len(m.DevAddr) != 4 { - return errors.NewErrInvalidArgument("DevAddr", "length must be 4") - } - if len(m.AppNonce) != 3 { - return errors.NewErrInvalidArgument("AppNonce", "length must be 3") + if m.DevAddr.IsEmpty() { + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") } - if len(m.NetId) != 3 { - return errors.NewErrInvalidArgument("NetId", "length must be 3") + if m.NetId.IsEmpty() { + return errors.NewErrInvalidArgument("NetId", "can not be empty") } return nil @@ -160,8 +154,8 @@ func (m *JoinAcceptPayload) Validate() error { // Validate implements the api.Validator interface func (m *MACPayload) Validate() error { - if len(m.DevAddr) != 4 { - return errors.NewErrInvalidArgument("DevAddr", "length must be 4") + if m.DevAddr.IsEmpty() { + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") } return nil } diff --git a/core/broker/server.go b/core/broker/server.go index 2771ba95c..e4f221276 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -18,8 +18,6 @@ type brokerRPC struct { broker *broker } -var grpcErrf = grpc.Errorf // To make go vet stop complaining - func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { router, err := b.broker.ValidateNetworkContext(stream.Context()) if err != nil { @@ -57,7 +55,7 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { return err } if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + return errors.BuildGRPCError(errors.NewErrInvalidArgument("Uplink", err.Error())) } go b.broker.HandleUplink(uplink) } diff --git a/core/handler/server.go b/core/handler/server.go index 4bc9783da..75d31b3da 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -15,8 +15,6 @@ type handlerRPC struct { handler Handler } -var grpcErrf = grpc.Errorf // To make go vet stop complaining - func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 21386c038..a5638c59d 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -98,7 +98,7 @@ func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.Deduplica return nil, errors.BuildGRPCError(err) } if err := message.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Uplink", err.Error())) } res, err := s.networkServer.HandleUplink(message) if err != nil { diff --git a/core/router/server.go b/core/router/server.go index 7bd2de659..467d7fef5 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -22,8 +22,6 @@ type routerRPC struct { router *router } -var grpcErrf = grpc.Errorf // To make go vet stop complaining - func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gateway, err error) { md, err := api.MetadataFromContext(ctx) if err != nil { @@ -97,7 +95,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { return err } if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + return errors.BuildGRPCError(errors.NewErrInvalidArgument("Uplink", err.Error())) } go r.router.HandleUplink(gateway.ID, uplink) } diff --git a/core/types/activation.go b/core/types/activation.go index eb5a6ca46..858953a0e 100644 --- a/core/types/activation.go +++ b/core/types/activation.go @@ -164,6 +164,8 @@ func (n *AppNonce) Unmarshal(data []byte) error { // NetID for LoRaWAN type NetID [3]byte +var emptyNetID NetID + // Bytes returns the NetID as a byte slice func (n NetID) Bytes() []byte { return n[:] @@ -176,6 +178,10 @@ func (n NetID) String() string { return strings.ToUpper(hex.EncodeToString(n.Bytes())) } +func (n NetID) IsEmpty() bool { + return n == emptyNetID +} + // GoString implements the GoStringer interface. func (n NetID) GoString() string { return n.String() From 5e0b412635d0d652832154f5ff9f21f1154aab39 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Wed, 9 Nov 2016 22:35:51 +0100 Subject: [PATCH 2137/2266] Make validation return informative error instead of true/false Fix compile errors introduced in last commit Closes #348 --- api/discovery/validation.go | 2 +- core/broker/manager_server.go | 2 ++ core/handler/manager_server.go | 2 ++ core/router/manager_server.go | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) diff --git a/api/discovery/validation.go b/api/discovery/validation.go index ccf971b6b..ed03428a2 100644 --- a/api/discovery/validation.go +++ b/api/discovery/validation.go @@ -13,7 +13,7 @@ func (m *Announcement) Validate() error { switch m.ServiceName { case "router", "broker", "handler": default: - return errors.InvalidArgument("ServiceName", "expected one of router, broker, handler but was "+m.ServiceName) + return errors.NewErrInvalidArgument("ServiceName", "expected one of router, broker, handler but was " + m.ServiceName) } return nil } diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index e857456b8..b238240ef 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -16,6 +16,8 @@ import ( "google.golang.org/grpc/codes" ) +var grpcErrf = grpc.Errorf // To make go vet stop complaining + type brokerManager struct { broker *broker deviceManager pb_lorawan.DeviceManagerClient diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index eca042643..546a5675f 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -23,6 +23,8 @@ import ( "google.golang.org/grpc/metadata" ) +var grpcErrf = grpc.Errorf // To make go vet stop complaining + type handlerManager struct { handler *handler deviceManager pb_lorawan.DeviceManagerClient diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 625fa22d3..4f1e3fbee 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -10,6 +10,8 @@ import ( "google.golang.org/grpc/codes" ) +var grpcErrf = grpc.Errorf // To make go vet stop complaining + type routerManager struct { router *router } From 0b2b5960be62589f29350a07b58b6404d92f0e5a Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Wed, 9 Nov 2016 22:45:38 +0100 Subject: [PATCH 2138/2266] Make validation return informative error instead of true/false Fix go fmt errors. Enabled on commit now. Closes #348 --- api/discovery/validation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/discovery/validation.go b/api/discovery/validation.go index ed03428a2..6134a7458 100644 --- a/api/discovery/validation.go +++ b/api/discovery/validation.go @@ -13,7 +13,7 @@ func (m *Announcement) Validate() error { switch m.ServiceName { case "router", "broker", "handler": default: - return errors.NewErrInvalidArgument("ServiceName", "expected one of router, broker, handler but was " + m.ServiceName) + return errors.NewErrInvalidArgument("ServiceName", "expected one of router, broker, handler but was "+m.ServiceName) } return nil } From 7c7427e411e5b7bf215d5e378cd5ddd570563b30 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 10 Nov 2016 09:18:41 +0100 Subject: [PATCH 2139/2266] Document Key auth in Handler HTTP API --- api/handler/HTTP-API.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/api/handler/HTTP-API.md b/api/handler/HTTP-API.md index 3ca3efcd6..a04339c47 100644 --- a/api/handler/HTTP-API.md +++ b/api/handler/HTTP-API.md @@ -5,14 +5,20 @@ The Handler HTTP API is a wrapper around the Handler's gRPC interface. We recomm ## Authorization Authorization to the Handler HTTP API is done with the `Authorization` header in your requests. -This header should contain a `Bearer` token with the JSON Web Token issued by the account server. +This header should either contain a `Bearer` token with the JSON Web Token issued by the account server or a `Key` that is issued by the account server and can be exchanged to a JSON Web Token. -Example: +Example (`Bearer`): ``` Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl ``` +Example (`Key`): + +``` +Authorization: Key ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ +``` + ## Register Application Request: From bcf49659cf21a71a2fc9897c24d28e013ed6f211 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 10 Nov 2016 09:19:15 +0100 Subject: [PATCH 2140/2266] Print Access Key in `ttnctl gateways info` if present --- ttnctl/cmd/gateways_info.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ttnctl/cmd/gateways_info.go b/ttnctl/cmd/gateways_info.go index eee74ee1e..43a40773f 100644 --- a/ttnctl/cmd/gateways_info.go +++ b/ttnctl/cmd/gateways_info.go @@ -51,6 +51,9 @@ var gatewaysInfoCmd = &cobra.Command{ } else { fmt.Print("Status Info: private\n") } + if gateway.Key != "" { + fmt.Printf("Access Key : %s\n", gateway.Key) + } fmt.Println() }, From 88b5934e58c7461bef1fbc73b2559330e1e6d68d Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Thu, 10 Nov 2016 11:15:46 +0100 Subject: [PATCH 2141/2266] Revert vendor.json Closes #348 --- vendor/vendor.json | 254 ++++++++++++++++++++++----------------------- 1 file changed, 127 insertions(+), 127 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 7ea4dd9ea..7f0ce9cbb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,595 +3,595 @@ "ignore": "test", "package": [ { - "checksumSHA1": "TCzqKDFui8wGc2coQ70ZZ9co6CY=", + "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", "path": "github.com/StackExchange/wmi", "revision": "e54cbda6595d7293a7a468ccf9525f6bc8887f99", "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "7bQvmTNCLpNSRlYlEYsRFOlmnb0=", + "checksumSHA1": "joWAlgyzANmhca8jS0qTN6J16ZU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "9BUFkr1vS+9FDdMUT/YGxDN8F2M=", + "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "q6Me8tLgsDG4a8v9udRmfjVr0AY=", + "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "ejnGSPEus73obF6V6M1qP20r8gM=", + "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "SZdGFefKeqMHQl9KnEWLo9RGWTA=", + "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "j+OxAByGiQ5eyNytVa7+hEA9iT0=", + "checksumSHA1": "7bEBiMFJqABXsCSR/iCRLteZIa8=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "CrQxBYkLWrZ1H9RAfLpYYwntFjk=", + "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "KjGWTLBOpS/L4o4N2RnVboeMKl8=", + "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "djrnI836WstNSbVQ5qU8lSF1mgs=", + "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "cd+lXfybfgRCSNFNGrDAuFkL3qM=", + "checksumSHA1": "iw9nN70RU1HK4VbosXcZp290WMg=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "KPgYwQ5u2saZX6nXx6Q+QYTSUXA=", + "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", "path": "github.com/TheThingsNetwork/go-account-lib/util", "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", "revisionTime": "2016-11-07T18:43:25Z" }, { - "checksumSHA1": "Z/H/SIYu/zsJhup0Uw0ECpNXshA=", + "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", "revision": "364a5d5b165140adfe751763e651fca78ea5e7ed", "revisionTime": "2016-11-01T08:25:08Z" }, { - "checksumSHA1": "vqER+A692XagGmfLHfE6wfi2cCc=", + "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", "path": "github.com/apex/log", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "Ql1hONEyZw+myzLqrVib7lJ4kes=", + "checksumSHA1": "LjQdQscNb25c2HxbREjmFFOoyx4=", "path": "github.com/apex/log/handlers/json", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "M2eXW/i+VotmKxD3P4LcrGV237w=", + "checksumSHA1": "AHCiF3VnEqmXyZDeH+z/IGsAtnI=", "path": "github.com/apex/log/handlers/level", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "9ZpSB5BUUtoSaW3vb2ITg+zHkUk=", + "checksumSHA1": "rxQUkWqruIZKpRzdwqrkxfcZvyw=", "path": "github.com/apex/log/handlers/multi", "revision": "4ea85e918cc8389903d5f12d7ccac5c23ab7d89b", "revisionTime": "2016-09-05T15:13:04Z" }, { - "checksumSHA1": "pI3gO8Krabth9Ehx31zinQqomWk=", + "checksumSHA1": "BdLdZP/C2uOO3lqk9X3NCKFpXa4=", "path": "github.com/asaskevich/govalidator", "revision": "7b3beb6df3c42abd3509abfc3bcacc0fbfb7c877", "revisionTime": "2016-10-01T16:31:30Z" }, { - "checksumSHA1": "JgVmYVWUsM1R3yL+Ha44cLAHxZ4=", + "checksumSHA1": "eKlcnwFZSIut8pNEvkLrm3entgc=", "path": "github.com/bluele/gcache", "revision": "740e70e1c445f08ae353214243a4fb0fbe970510", "revisionTime": "2016-10-27T05:39:47Z" }, { - "checksumSHA1": "HabHz9Fh89GYXP2mPZzHnNY3gfM=", + "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", "path": "github.com/brocaar/lorawan", "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", "revisionTime": "2016-09-30T18:12:03Z" }, { - "checksumSHA1": "IYDPjFweUGLG1qxJ90kDrGEM+RE=", + "checksumSHA1": "MCRyJiR3hs/bt++6w5SCcyIYaZE=", "path": "github.com/brocaar/lorawan/band", "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", "revisionTime": "2016-09-30T18:12:03Z" }, { - "checksumSHA1": "PcdItMMwb39o55X5JulfTDLGOKE=", + "checksumSHA1": "2Fy1Y6Z3lRRX1891WF/+HT4XS2I=", "path": "github.com/dgrijalva/jwt-go", "revision": "9ed569b5d1ac936e6494082958d63a6aa4fff99a", "revisionTime": "2016-11-01T19:39:35Z" }, { - "checksumSHA1": "rgSZOHu5s274YA70EsK6g7E3VQc=", + "checksumSHA1": "PPyNjcW5yfrfXvARE74fh9JBSpo=", "path": "github.com/eclipse/paho.mqtt.golang", "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", "revisionTime": "2016-10-12T12:34:52Z" }, { - "checksumSHA1": "CrfbTTSutkj1lpKT/9GXwhcx3xI=", + "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", "path": "github.com/eclipse/paho.mqtt.golang/packets", "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", "revisionTime": "2016-10-12T12:34:52Z" }, { - "checksumSHA1": "9K7s0i+FyqEFNlu6ovGX9iOMSMQ=", + "checksumSHA1": "KCWVxG+J8SxHGlGiUghe0KBGsa8=", "path": "github.com/fatih/structs", "revision": "dc3312cb1a4513a366c4c9e622ad55c32df12ed3", "revisionTime": "2016-08-07T23:55:29Z" }, { - "checksumSHA1": "NcUSqemYhygiMcCShH4W1LMm9Eo=", + "checksumSHA1": "hveFTNQ9YEyYRs6SWuXM+XU9qRI=", "path": "github.com/fsnotify/fsnotify", "revision": "fd9ec7deca8bf46ecd2a795baaacf2b3a9be1197", "revisionTime": "2016-10-26T20:31:22Z" }, { - "checksumSHA1": "IuKHgdsIy5+YbYCtD8YCVM9y7FI=", + "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", "path": "github.com/go-ole/go-ole", "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", "revisionTime": "2016-07-29T03:38:29Z" }, { - "checksumSHA1": "HgOBsItWIoZjtrDzuhZfRrov3Tg=", + "checksumSHA1": "qLYVTQDhgrVIeZ2KI9eZV51mmug=", "path": "github.com/go-ole/go-ole/oleutil", "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", "revisionTime": "2016-07-29T03:38:29Z" }, { - "checksumSHA1": "JmYN40G4IXpHgD46fdzSH+354/s=", + "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", "path": "github.com/gogo/protobuf/gogoproto", "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "Fhkmd1XWRuiWxPE1iKXjqbvVjFI=", + "checksumSHA1": "oDNpaVF/BHUyme8A7RoBAH2q6is=", "path": "github.com/gogo/protobuf/proto", "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "zQrUmFXRFK09Ub6+2AOHCikmXF0=", + "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", "revisionTime": "2016-11-05T09:51:13Z" }, { - "checksumSHA1": "x95Kau9SsidqdzIPo3iAmz+mqdk=", + "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", "path": "github.com/golang/mock/gomock", "revision": "bd3c8e81be01eef76d4b503f5e687d2d1354d2d9", "revisionTime": "2016-01-21T18:51:14Z" }, { - "checksumSHA1": "enOMCfj2ih2BfM8Nn1DNqJUpTEU=", + "checksumSHA1": "+HPilCYNEcR4B/Q13LiW3OF3i64=", "path": "github.com/golang/protobuf/jsonpb", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "V+dsLhBAmvoCO7yIk8VXEh/8r+M=", + "checksumSHA1": "oJGkod8y/3GTbKfazzQM+nQOPR0=", "path": "github.com/golang/protobuf/proto", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "BCNnZKNwGrJIBy+7F2iW557fI/Q=", + "checksumSHA1": "juNiTc9bfhQYo4BkWc83sW4Z5gw=", "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "CbddspWWKZsS/Vw16UPCvvrVG3I=", + "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, { - "checksumSHA1": "X7kjOSGiHkg/dfJY8PhGWc4F4ls=", + "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", "path": "github.com/gosuri/uitable", "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "ubZKTAH5dGTapCUCUvxLLN2xzW0=", + "checksumSHA1": "hfL7iFULaUity86NGidQt/AiYyo=", "path": "github.com/gosuri/uitable/util/strutil", "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "y8lWvQbCnejmDN0+XvkUzhP9NqM=", + "checksumSHA1": "xUvDrGeY4HijGKl+W8WSvWl1Evs=", "path": "github.com/gosuri/uitable/util/wordwrap", "revision": "36ee7e946282a3fb1cfecd476ddc9b35d8847e42", "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "RAddxvUxP232tqSfc7ApAZFm+pU=", + "checksumSHA1": "mJuOZXdE9GBhUTHeQ/Wm8U1rR4I=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "2dObfehqh2fWi4mXG2vB1ZZtiR4=", + "checksumSHA1": "7K3QiKeoZ0tgA8ymV9vicmfA97Q=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "U+OWOFSEeSKXru4OypJwLWZRIAs=", + "checksumSHA1": "mYOr8zd2ww05AQ59oq/xdKL+mJo=", "path": "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "KA57yh+m9mEFa0VueMK1qvMz3b8=", + "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", "revision": "84398b94e188ee336f307779b57b3aa91af7063c", "revisionTime": "2016-11-05T22:35:13Z" }, { - "checksumSHA1": "/lypnmuhzHEzMTarlV6dbnl01GU=", + "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", "path": "github.com/hashicorp/hcl", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "7QpzuLg3aKDJWPD2kUz1Jw+JJfU=", + "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "6EecoH3nidR8QJX7CTg8fA3JoEc=", + "checksumSHA1": "croNloscHsjX87X+4/cKOURf1EY=", "path": "github.com/hashicorp/hcl/hcl/parser", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "2FxUL4byD9QLI/oVB1sQmIV3FZs=", + "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", "path": "github.com/hashicorp/hcl/hcl/scanner", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "6n+9agh1diNM4kkVNkbG0/tcrXE=", + "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", "path": "github.com/hashicorp/hcl/hcl/strconv", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "dljEjBgEewtuvGQ64XBvHB3iUkg=", + "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "fIT1ehfpm6RPCsotJ5qEZY1qWR0=", + "checksumSHA1": "138aCV5n8n7tkGYMsMVQQnnLq+0=", "path": "github.com/hashicorp/hcl/json/parser", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "LntdNdMAHiPKqfBEVvQYwr9mSYw=", + "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", "path": "github.com/hashicorp/hcl/json/scanner", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "knLekryOag5SGOV4jX8nwudBCDo=", + "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", "revisionTime": "2016-11-04T01:42:59Z" }, { - "checksumSHA1": "VzTtv1sy2diKOYEUc4mIe+8cPgE=", + "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", "path": "github.com/howeyc/gopass", "revision": "f5387c492211eb133053880d23dfae62aa14123d", "revisionTime": "2016-10-03T13:09:00Z" }, { - "checksumSHA1": "UYDU8nJJ/RXSTrR+dgQodj78uQI=", + "checksumSHA1": "40vJyUB4ezQSn/NSadsKEOrudMc=", "path": "github.com/inconshreveable/mousetrap", "revision": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75", "revisionTime": "2014-10-17T20:07:13Z" }, { - "checksumSHA1": "nLZf/iMYZv0BsLGR0UOn/y3AJug=", + "checksumSHA1": "YEZ/qI/rjyPhyyjuGI0NysuKIfA=", "path": "github.com/jacobsa/crypto/cmac", "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", "revisionTime": "2016-04-10T22:58:39Z" }, { - "checksumSHA1": "fLhDe/RAyAOWY2i2zCcu6V7o9EE=", + "checksumSHA1": "NBvtX91AEKxFLmj8mwwhXEKl6d0=", "path": "github.com/jacobsa/crypto/common", "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", "revisionTime": "2016-04-10T22:58:39Z" }, { - "checksumSHA1": "kuwREeq736QUok7wyPzN9QsesTo=", + "checksumSHA1": "g+afVQQVopBLiLB5pFZp/8s6aBs=", "path": "github.com/kardianos/osext", "revision": "c2c54e542fb797ad986b31721e1baedf214ca413", "revisionTime": "2016-08-11T00:15:26Z" }, { - "checksumSHA1": "nvGuYn0dr8PhD6jFH9N/VGJQ2gg=", + "checksumSHA1": "KQhA4EQp4Ldwj9nJZnEURlE6aQw=", "path": "github.com/kr/fs", "revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b", "revisionTime": "2013-11-06T22:25:44Z" }, { - "checksumSHA1": "xiTZt1TMF1nwhb7Qu1cexuZyuPk=", + "checksumSHA1": "S6PDDQMYaKwLDIP/NsRYb4FRAqQ=", "path": "github.com/magiconair/properties", "revision": "0723e352fa358f9322c938cc2dadda874e9151a9", "revisionTime": "2016-09-08T09:36:58Z" }, { - "checksumSHA1": "0EbIiM2NAjPjW6FT9lf3UevJzGM=", + "checksumSHA1": "yf185lmVPcvXjLZuPT1s4JzxI18=", "path": "github.com/mattn/go-runewidth", "revision": "737072b4e32b7a5018b4a7125da8d12de90e8045", "revisionTime": "2016-10-12T01:35:12Z" }, { - "checksumSHA1": "BKqC6vWBJ+wzzBLcGBEG3YQkTY8=", + "checksumSHA1": "AXacfEchaUqT5RGmPmMXsOWRhv8=", "path": "github.com/mitchellh/go-homedir", "revision": "756f7b183b7ab78acdbbee5c7f392838ed459dda", "revisionTime": "2016-06-21T17:42:43Z" }, { - "checksumSHA1": "2UFUWcJ5PR5sQgBitTz7gIxpcXg=", + "checksumSHA1": "UuXgD2dDojfS8AViUEe15gLIWZE=", "path": "github.com/mitchellh/mapstructure", "revision": "f3009df150dadf309fdee4a54ed65c124afad715", "revisionTime": "2016-10-20T16:18:36Z" }, { - "checksumSHA1": "CF9y7b52fTlBeW9dKrvYXUeeOyg=", + "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", "path": "github.com/mwitkow/go-grpc-middleware", "revision": "0664ec3f4e6e08d02f2bf6a62be6900c0b82ea69", "revisionTime": "2016-09-11T13:13:45Z" }, { - "checksumSHA1": "p85wl+d5C+h6wD454NGR06jZ6pA=", + "checksumSHA1": "8Y05Pz7onrQPcVWW6JStSsYRh6E=", "path": "github.com/pelletier/go-buffruneio", "revision": "df1e16fde7fc330a0ca68167c23bf7ed6ac31d6d", "revisionTime": "2016-01-24T19:35:03Z" }, { - "checksumSHA1": "b0hNJsJNnt8RrXsFEhidgFilF+w=", + "checksumSHA1": "CtI2rBQw2rxHNIU+a0rcAf29Sco=", "path": "github.com/pelletier/go-toml", "revision": "45932ad32dfdd20826f5671da37a5f3ce9f26a8d", "revisionTime": "2016-09-20T07:07:15Z" }, { - "checksumSHA1": "iwLqOY6Nr1BitBUex4K0Wep3JL8=", + "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", "path": "github.com/pkg/errors", "revision": "248dadf4e9068a0b3e79f02ed0a610d935de5302", "revisionTime": "2016-10-29T09:36:37Z" }, { - "checksumSHA1": "IcLlvmJUmPHbEBMDa6WxDzh0z9w=", + "checksumSHA1": "k9SlQdp/DTB72G/u4aNecX/fFIg=", "path": "github.com/pkg/sftp", "revision": "4d0e916071f68db74f8a73926335f809396d6b42", "revisionTime": "2016-09-30T22:07:58Z" }, { - "checksumSHA1": "+yZxFRm3leIBDLHJeM8BK6fsUhg=", + "checksumSHA1": "GiX6yRUzizn1C+ckgj1xLFLoz8g=", "path": "github.com/rcrowley/go-metrics", "revision": "ab2277b1c5d15c3cba104e9cbddbdfc622df5ad8", "revisionTime": "2016-09-21T19:52:07Z" }, { - "checksumSHA1": "GqQXu+amOr1hSLHmU3wGSyCuGfk=", + "checksumSHA1": "5qwv3yDROEz5ZV8HztOBmQxen8c=", "path": "github.com/robertkrimen/otto", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "yPk7CP14otQHh23dmGBYFWhVzNc=", + "checksumSHA1": "qgziiO3/QDVJMKw2nGrUbC8QldY=", "path": "github.com/robertkrimen/otto/ast", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "Ih8IyrcvYIRe1H1BbExbHs+s8kM=", + "checksumSHA1": "L0KsB2EzTlPgv0iae3q3SukNW7U=", "path": "github.com/robertkrimen/otto/dbg", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "FnQzQouypvp1tUyPE3mYbWNH6es=", + "checksumSHA1": "euDLJKhw4doeTSxjEoezjxYXLzs=", "path": "github.com/robertkrimen/otto/file", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "61W7S2mWb4DujuqG2msycPC+Q2E=", + "checksumSHA1": "LLuLITFO8chqSG0+APJIy5NtOHU=", "path": "github.com/robertkrimen/otto/parser", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "i9dRdlVYwqPt0PTRnJS6GxKoCRw=", + "checksumSHA1": "7J/7NaYRqKhBvZ+dTIutsEoEgFw=", "path": "github.com/robertkrimen/otto/registry", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "KJp6P0GEODVBpNGqY8Scn+Q/0lg=", + "checksumSHA1": "/jMXYuXycBpTqWhRyJ2xsqvHvQI=", "path": "github.com/robertkrimen/otto/token", "revision": "bf1c3795ba078da6905fe80bfbc3ed3d8c36e9aa", "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "wVbykdHaj8eufSPcydD8cI3gbA0=", + "checksumSHA1": "pkzbHrJg79tEMajgi+7ED9rPVuQ=", "path": "github.com/shirou/gopsutil/cpu", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "OJ2K143hFQ5ePll2wLTiorCnogY=", + "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", "path": "github.com/shirou/gopsutil/host", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "9rHfc6RQjWyCl89gejFuX9p+TmI=", + "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", "path": "github.com/shirou/gopsutil/internal/common", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "u+w5wFphtvxpuPp5GN74xBg/zjA=", + "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", "path": "github.com/shirou/gopsutil/load", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "P4noCjIYU1jbzVHxyVEDkjj1IAk=", + "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", "path": "github.com/shirou/gopsutil/mem", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "h9btZjjcHSoQRhBNeLVUxhz0fF0=", + "checksumSHA1": "Sxy9qDOEqAa+F2P6rSLKQGZL+l8=", "path": "github.com/shirou/gopsutil/net", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "RWhx870jIT8+pBYxYTeCf4GN/qc=", + "checksumSHA1": "Cmj97derBOe/m/D2Db++Z57uWBw=", "path": "github.com/shirou/gopsutil/process", "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", "revisionTime": "2016-10-27T13:29:33Z" }, { - "checksumSHA1": "K+Z5DS27MEqKqgUXEaiCYA5WajM=", + "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", "path": "github.com/shirou/w32", "revision": "bb4de0191aa41b5507caa14b0650cdbddcd9280b", "revisionTime": "2016-09-30T03:27:40Z" }, { - "checksumSHA1": "H2ZvmkbJ9YyhyaVc3YSYVTJlNGU=", + "checksumSHA1": "6AYg4fjEvFuAVN3wHakGApjhZAM=", "path": "github.com/smartystreets/assertions", "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", "revisionTime": "2016-07-07T19:03:55Z" }, { - "checksumSHA1": "D/geobFtKMvBIAONfT+4sie/PGk=", + "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", "path": "github.com/smartystreets/assertions/internal/go-render/render", "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", "revisionTime": "2016-07-07T19:03:55Z" }, { - "checksumSHA1": "NtJsLMlKqou92xDHcAvu4KzQtek=", + "checksumSHA1": "SLC6TfV4icQA9l8YJQu8acJYbuo=", "path": "github.com/smartystreets/assertions/internal/oglematchers", "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", "revisionTime": "2016-07-07T19:03:55Z" }, { - "checksumSHA1": "oNcjSQf1zGY27b9xipsqb2L4/RY=", + "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", "path": "github.com/smartystreets/go-aws-auth", "revision": "2043e6d0bb7e4c18464a7bba562acbe482e3cabd", "revisionTime": "2016-07-22T04:48:03Z" }, { - "checksumSHA1": "78ho4yEFjGrhCf8wnqjhV1S4+ZQ=", + "checksumSHA1": "59wTbS4fE2282Q88NrBYImbFGbo=", "path": "github.com/spf13/afero", "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", "revisionTime": "2016-09-19T21:01:14Z" }, { - "checksumSHA1": "gne0kouue3bbQVpN31WK7QKRk7I=", + "checksumSHA1": "u6B0SEgZ/TUEfIvF6w/HnFVQbII=", "path": "github.com/spf13/afero/mem", "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", "revisionTime": "2016-09-19T21:01:14Z" }, { - "checksumSHA1": "3kb6SeZJKlCGSMeE1zADtBC96ic=", + "checksumSHA1": "sLyAUiIT7V0DNVp6yBhW4Ms5BEs=", "path": "github.com/spf13/afero/sftp", "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", "revisionTime": "2016-09-19T21:01:14Z" }, { - "checksumSHA1": "TCBpgfcH969AYYryAFARBeqQnSQ=", + "checksumSHA1": "M4qI7Ul0vf2uj3fF69DvRnwqfd0=", "path": "github.com/spf13/cast", "revision": "2580bc98dc0e62908119e4737030cc2fdfc45e4c", "revisionTime": "2016-09-26T08:42:49Z" }, { - "checksumSHA1": "Y0fPCXjwXkF7NpBM8L8Od4z4vp4=", + "checksumSHA1": "FZ0r4TzEy9UxXLkFVXFygApni4M=", "path": "github.com/spf13/cobra", "revision": "6e91dded25d73176bf7f60b40dd7aa1f0bf9be8d", "revisionTime": "2016-10-26T01:28:26Z" }, { - "checksumSHA1": "M6v7rwFodsBRcOT6HH3ppNdo0d4=", + "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", "path": "github.com/spf13/jwalterweatherman", "revision": "33c24e77fb80341fe7130ee7c594256ff08ccc46", "revisionTime": "2016-03-01T12:00:06Z" }, { - "checksumSHA1": "7pwNi6OV1KCJLv76PrzW//SrGS8=", + "checksumSHA1": "GxPD7A0NjMDom1xte0mghkpzr0E=", "path": "github.com/spf13/pflag", "revision": "5ccb023bc27df288a957c5e994cd44fd19619465", "revisionTime": "2016-10-24T13:13:51Z" }, { - "checksumSHA1": "Fk2TsHHHKycaJhyLheArJwi3mgY=", + "checksumSHA1": "802GjFNHMmnFXEIkQ137ucUUacI=", "path": "github.com/spf13/viper", "revision": "651d9d916abc3c3d6a91a12549495caba5edffd2", "revisionTime": "2016-10-29T21:33:52Z" }, { - "checksumSHA1": "uboKbybSheSH99DWeX328g+tfbw=", + "checksumSHA1": "rKV8YLkXpeNG1Oix8hlYqVsEFb4=", "path": "github.com/streadway/amqp", "revision": "2e25825abdbd7752ff08b270d313b93519a0a232", "revisionTime": "2016-03-11T21:55:03Z" }, { - "checksumSHA1": "5VPm/vvjh+5aQxUymjxNkU/vHGg=", + "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", "path": "github.com/tj/go-elastic", "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" }, { - "checksumSHA1": "GUIZQPUYHp+OYcAa7xgspP0sI2Y=", + "checksumSHA1": "nL4enNHknemOmxcaPTIJCrJc0/I=", "path": "github.com/tj/go-elastic/aliases", "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" }, { - "checksumSHA1": "8WVfx2stDSNVDmR3d13xVI6KfvA=", + "checksumSHA1": "SgbyhOvKGvet/Nw70Rxa8d3gLZ0=", "path": "github.com/tj/go-elastic/batch", "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" @@ -675,13 +675,13 @@ "revisionTime": "2016-11-04T22:18:50Z" }, { - "checksumSHA1": "xbp8j2KLXQ34tAjnM+zrzMZZKcE=", + "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", "revisionTime": "2016-06-06T21:15:38Z" }, { - "checksumSHA1": "lakUMUcLmmJ910/aft5iXQr/Fas=", + "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", "path": "golang.org/x/oauth2/internal", "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", "revisionTime": "2016-06-06T21:15:38Z" @@ -705,157 +705,157 @@ "revisionTime": "2016-10-19T13:35:53Z" }, { - "checksumSHA1": "8xPvH/BFj/tgNJD1U8nUptEW254=", + "checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=", "path": "google.golang.org/appengine/internal", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "RD2tuuDmEYtQdNm5844fOVPBVkA=", + "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", "path": "google.golang.org/appengine/internal/base", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "6JDEiCxSEIK1Bfo9IWxFly+RXIw=", + "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", "path": "google.golang.org/appengine/internal/datastore", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "VSjY7WrvJPnBWkC2L7xsO2dWyfc=", + "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", "path": "google.golang.org/appengine/internal/log", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "WE74DpnexU2S0ZTtnwFEPKzlJfI=", + "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", "path": "google.golang.org/appengine/internal/remote_api", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "UNZNiUKpkvGKzBrYsdjUx7cUM5g=", + "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", "path": "google.golang.org/appengine/internal/urlfetch", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "k2vHVgCk1AqIZravw4ZP3NbiRJM=", + "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", "path": "google.golang.org/appengine/urlfetch", "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "wez47b9vPwT1MoMgT5wbjVeWFt8=", + "checksumSHA1": "KzbvI1E+0cJe41jNUEoBksFpAZ8=", "path": "google.golang.org/grpc", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "h4kdKXtXyT0z/ufmJFN8+p4lILI=", + "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "H5ZYYPZw15XMgyXgu+edvbXa8qY=", + "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "ebZ/HnoSyN49a5qC/ciXvOS8pNs=", + "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "spX/LCShDQ1Fxm5/7rcVe+GYQn0=", + "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "FfS+uNOaXRHZEhj+vGz+GkyHqQs=", + "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", "path": "google.golang.org/grpc/metadata", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "V6pPMv24b/wpQSUrZT1EJ45XSAY=", + "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "MpM8NbSbvKr0UdN+pF3qIQzZT3A=", + "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "qKXWDkD01BvFmLIakamkY0C/SJo=", + "checksumSHA1": "HQJrtiTtr5eiRsXQLut2R1Q9kuY=", "path": "google.golang.org/grpc/transport", "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, { - "checksumSHA1": "lzxr2rdWylya6Z5Ft4oA0Fvr4TU=", + "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", "path": "gopkg.in/bsm/ratelimit.v1", "revision": "db14e161995a5177acef654cb0dd785e8ee8bc22", "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "Wyj9ievtsiIyEICcydukdiKmV2I=", + "checksumSHA1": "rr5P5knxMTqvnRElnvlrM4dyuQM=", "path": "gopkg.in/redis.v5", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "KgBz3V2H/OF/PTUlzHNZLETdqMs=", + "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", "path": "gopkg.in/redis.v5/internal", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "9DDcWtzDkAGPKplEPcf0iyIn0aA=", + "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", "path": "gopkg.in/redis.v5/internal/consistenthash", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "zXEc7u1ExA2urrGa1lFiDaElsuE=", + "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", "path": "gopkg.in/redis.v5/internal/hashtag", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "UguaV//oFWmZMQkAmXVLJ+qVPBY=", + "checksumSHA1": "MuVVHw/uzk6gwBsQ8deMYacmgTM=", "path": "gopkg.in/redis.v5/internal/pool", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "sukBHMNbLaempvn9Oz60j4q66Hk=", + "checksumSHA1": "Pn4Vc2X2hUhm0SV7j82YlMW8j5o=", "path": "gopkg.in/redis.v5/internal/proto", "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", "revisionTime": "2016-10-24T09:52:32Z" }, { - "checksumSHA1": "rRJ0dXt61DcHYS043MAw+o1ReYs=", + "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", "path": "gopkg.in/sourcemap.v1", "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", "revisionTime": "2016-06-02T08:55:44Z" }, { - "checksumSHA1": "jd9JtETw+VJRYUfjyFPJEyYLpvA=", + "checksumSHA1": "DPyTTxwhl5mrDF15nLNtshc0cWs=", "path": "gopkg.in/sourcemap.v1/base64vlq", "revision": "eef8f47ab679652a7d3a4ee34c34314d255d2536", "revisionTime": "2016-06-02T08:55:44Z" }, { - "checksumSHA1": "21kDrz4DnDppRHgK7vVmOFwgcSQ=", + "checksumSHA1": "12GqsW8PiRPnezDDy0v4brZrndM=", "path": "gopkg.in/yaml.v2", "revision": "a5b47d31c556af34a302ce5d659e6fea44d90de0", "revisionTime": "2016-09-28T15:37:09Z" From a0415232f0957231619947aa123415f78bac551a Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Thu, 10 Nov 2016 12:23:27 +0100 Subject: [PATCH 2142/2266] Make validation return informative error instead of true/false Fix review issues - Consistency - nil pointer mess Closes #348 --- api/validation.go | 7 ++++ api/validation_test.go | 55 +++++++++++++++++++++++++--- core/broker/server.go | 2 +- core/handler/manager_server.go | 12 +++--- core/networkserver/manager_server.go | 2 +- core/networkserver/server.go | 2 +- core/router/server.go | 2 +- 7 files changed, 67 insertions(+), 15 deletions(-) diff --git a/api/validation.go b/api/validation.go index f211943c9..61137ad71 100644 --- a/api/validation.go +++ b/api/validation.go @@ -24,6 +24,13 @@ func NotEmptyAndValidId(id string, argument string) error { } func NotNilAndValid(in interface{}, argument string) error { + // Structs can not be nil and reflect.ValueOf(in).IsNil() would panic + if reflect.ValueOf(in).Kind() == reflect.Struct { + return Validate(in) + } + + // We need to check for the interface to be nil and the value of the interface + // See: https://stackoverflow.com/questions/13476349/check-for-nil-and-nil-interface-in-go if in == nil || reflect.ValueOf(in).IsNil() { return errors.NewErrInvalidArgument(argument, "can not be empty") } diff --git a/api/validation_test.go b/api/validation_test.go index 114725048..76861cf96 100644 --- a/api/validation_test.go +++ b/api/validation_test.go @@ -12,15 +12,60 @@ type invalid struct { nestedPtr *interface{} } -func (i *invalid) Validate() error { +func (i invalid) Validate() error { return NotNilAndValid(i.nestedPtr, "nestedPtr") } -func TestNotNilAndValid(t *testing.T) { +func TestNotNilAndValidStruct(t *testing.T) { subject := invalid{} - err := NotNilAndValid(subject.nestedPtr, "subject") - if err == nil || err.Error() != "subject not valid: can not be empty" { - t.Error("Expected validation error: 'subject not valid: can not be empty' but found", err) + + err := NotNilAndValid(subject, "subject") + if err == nil || err.Error() != "nestedPtr not valid: can not be empty" { + t.Error("Expected validation error: 'nestedPtr not valid: can not be empty' but found", err) + } +} + +func TestNotNilAndValidPtr(t *testing.T) { + subject := &invalid{} + err := NotNilAndValid(subject, "subject") + if err == nil || err.Error() != "nestedPtr not valid: can not be empty" { + t.Error("Expected validation error: 'nestedPtr not valid: can not be empty' but found", err) + } +} + +type testInterface interface { + Nothing() +} + +func TestNotNilAndValidDifferentTypes(t *testing.T) { + err := NotNilAndValid(struct{}{}, "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(&struct{}{}, "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(func() {}, "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(make(chan byte), "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(make([]byte, 0), "subject") + if err != nil { + t.Error("Expected nil but got", err) + } + + err = NotNilAndValid(make(map[byte]byte, 0), "subject") + if err != nil { + t.Error("Expected nil but got", err) } } diff --git a/core/broker/server.go b/core/broker/server.go index e4f221276..0942b4269 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -55,7 +55,7 @@ func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { return err } if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.NewErrInvalidArgument("Uplink", err.Error())) + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) } go b.broker.HandleUplink(uplink) } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 546a5675f..08ff32fb7 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -59,7 +59,7 @@ func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID st func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device Identifier")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) @@ -106,7 +106,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) @@ -208,7 +208,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device Identifier")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -294,7 +294,7 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Identifier")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -339,7 +339,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { @@ -370,7 +370,7 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Application Identifier", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Identifier")) } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 2e4edc11a..2682e6b35 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -24,7 +24,7 @@ type networkServerManager struct { func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device Identifier", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device Identifier")) } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) if err != nil { diff --git a/core/networkserver/server.go b/core/networkserver/server.go index a5638c59d..21386c038 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -98,7 +98,7 @@ func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.Deduplica return nil, errors.BuildGRPCError(err) } if err := message.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Uplink", err.Error())) + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) } res, err := s.networkServer.HandleUplink(message) if err != nil { diff --git a/core/router/server.go b/core/router/server.go index 467d7fef5..179ac3d9c 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -95,7 +95,7 @@ func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { return err } if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.NewErrInvalidArgument("Uplink", err.Error())) + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) } go r.router.HandleUplink(gateway.ID, uplink) } From 14e2e7ff20b7541c3728d13210f59975f4fa6be8 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Thu, 10 Nov 2016 12:50:13 +0100 Subject: [PATCH 2143/2266] Make validation return informative error instead of true/false ResponseTemplate can be nil Closes #348 --- api/broker/validation.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/api/broker/validation.go b/api/broker/validation.go index a0e7368da..f634f3c94 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -59,9 +59,12 @@ func (m *DeduplicatedUplinkMessage) Validate() error { if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { return err } - if err := api.NotNilAndValid(m.ResponseTemplate, "ResponseTemplate"); err != nil { - return err + if m.ResponseTemplate != nil { + if err := m.ResponseTemplate.Validate(); err != nil { + return errors.NewErrInvalidArgument("ResponseTemplate", err.Error()) + } } + return nil } From e48c0ebb533263309dc4e317da240363780e7500 Mon Sep 17 00:00:00 2001 From: Tobias Kaupat Date: Tue, 8 Nov 2016 11:50:25 +0100 Subject: [PATCH 2144/2266] Linking the NuGet download for Redis which works on Windows --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d5dfb50a6..b294eb2ff 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ When you get started with The Things Network, you'll probably have some question 1. Make sure you have [Go](https://golang.org) installed (version 1.7 or later). 2. Set up your [Go environment](https://golang.org/doc/code.html#GOPATH) 3. Install the [protobuf compiler (`protoc`)](https://github.com/google/protobuf/releases) -4. Make sure you have [Redis](http://redis.io/download) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. +4. Make sure you have [Redis](http://redis.io/download) or [Redis from NuGet for Windows](https://www.nuget.org/packages/Redis-64/) and [RabbitMQ](https://www.rabbitmq.com/download.html) **installed** and **running**. On a fresh installation you might need to install the [MQTT plugin](https://www.rabbitmq.com/mqtt.html). If you're on Linux, you probably know how to do that. On a Mac, just run `brew bundle`. The Windows installer will setup and start RabbitMQ as a service. Use the `RabbitMQ Command Prompt (sbin dir)` to run commands, i.e. to enable plugins. 5. Declare a RabbitMQ exchange `ttn.handler` of type `topic`. Using [the management plugin](http://www.rabbitmq.com/management.html), declare the exchange in the web interface `http://server-name:15672` or using the management cli, run `rabbitmqadmin declare exchange name=ttn.handler type=topic auto_delete=false durable=true` From aedb60cb10d090fd559fbeea963ece248d372fb3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 09:12:47 +0100 Subject: [PATCH 2145/2266] Correctly validate in NetworkServer --- api/networkserver/validation.go | 8 +++++--- core/networkserver/server.go | 7 ++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/api/networkserver/validation.go b/api/networkserver/validation.go index 0dad88356..9470b86d0 100644 --- a/api/networkserver/validation.go +++ b/api/networkserver/validation.go @@ -1,9 +1,11 @@ package networkserver +import "github.com/TheThingsNetwork/ttn/utils/errors" + // Validate implements the api.Validator interface -func (m *DevicesRequest) Validate() bool { +func (m *DevicesRequest) Validate() error { if m.DevAddr == nil || m.DevAddr.IsEmpty() { - return false + return errors.NewErrInvalidArgument("DevAddr", "can not be empty") } - return true + return nil } diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 21386c038..01a1f4f68 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -12,7 +12,6 @@ import ( "github.com/dgrijalva/jwt-go" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -20,8 +19,6 @@ type networkServerRPC struct { networkServer NetworkServer } -var grpcErrf = grpc.Errorf // To make go vet stop complaining - func (s *networkServerRPC) ValidateContext(ctx context.Context) error { md, ok := metadata.FromContext(ctx) if !ok { @@ -55,8 +52,8 @@ func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesReques if err := s.ValidateContext(ctx); err != nil { return nil, errors.BuildGRPCError(err) } - if !req.Validate() { - return nil, grpcErrf(codes.InvalidArgument, "Invalid Devices Request") + if err := req.Validate(); err != nil { + return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Devices Request")) } res, err := s.networkServer.HandleGetDevices(req) if err != nil { From 9b54811c2124fb47c90b82e729c3f75e34e1273b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 09:13:11 +0100 Subject: [PATCH 2146/2266] Correctly validate optional *Message --- api/broker/validation.go | 27 +++++++++++++++++++++++++-- api/handler/validation.go | 11 ++++++++--- api/router/validation.go | 20 +++++++++++++++++++- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/api/broker/validation.go b/api/broker/validation.go index f634f3c94..bb8ca7607 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -30,6 +30,11 @@ func (m *UplinkMessage) Validate() error { if err := api.NotNilAndValid(m.GatewayMetadata, "GatewayMetadata"); err != nil { return err } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } @@ -45,6 +50,11 @@ func (m *DownlinkMessage) Validate() error { if err := api.NotNilAndValid(m.DownlinkOption, "DownlinkOption"); err != nil { return err } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } @@ -64,7 +74,11 @@ func (m *DeduplicatedUplinkMessage) Validate() error { return errors.NewErrInvalidArgument("ResponseTemplate", err.Error()) } } - + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } @@ -79,7 +93,11 @@ func (m *DeviceActivationRequest) Validate() error { if err := api.NotNilAndValid(m.ActivationMetadata, "ActivationMetadata"); err != nil { return err } - + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } @@ -88,6 +106,11 @@ func (m *DeduplicatedDeviceActivationRequest) Validate() error { if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { return err } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } diff --git a/api/handler/validation.go b/api/handler/validation.go index 24fbee36b..5b5f3f9d7 100644 --- a/api/handler/validation.go +++ b/api/handler/validation.go @@ -1,6 +1,9 @@ package handler -import "github.com/TheThingsNetwork/ttn/api" +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) // Validate implements the api.Validator interface func (m *DeviceActivationResponse) Validate() error { @@ -10,8 +13,10 @@ func (m *DeviceActivationResponse) Validate() error { if err := api.NotNilAndValid(m.ActivationMetadata, "ActivationMetadata"); err != nil { return err } - if err := api.NotNilAndValid(m.Message, "Message"); err != nil { - return err + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } } return nil } diff --git a/api/router/validation.go b/api/router/validation.go index 7a5d7b335..adc55fcfb 100644 --- a/api/router/validation.go +++ b/api/router/validation.go @@ -1,6 +1,9 @@ package router -import "github.com/TheThingsNetwork/ttn/api" +import ( + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" +) // Validate implements the api.Validator interface func (m *UplinkMessage) Validate() error { @@ -10,6 +13,11 @@ func (m *UplinkMessage) Validate() error { if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { return err } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } @@ -21,6 +29,11 @@ func (m *DownlinkMessage) Validate() error { if err := api.NotNilAndValid(m.GatewayConfiguration, "GatewayConfiguration"); err != nil { return err } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } @@ -32,5 +45,10 @@ func (m *DeviceActivationRequest) Validate() error { if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { return err } + if m.Message != nil { + if err := m.Message.Validate(); err != nil { + return errors.NewErrInvalidArgument("Message", err.Error()) + } + } return nil } From c7d18f2f20382e710de69d6d4d41e83a335e2b76 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 09:19:25 +0100 Subject: [PATCH 2147/2266] Use utils/errors instead of gRPC errors --- core/broker/manager_server.go | 8 +++----- core/discovery/server.go | 7 +++---- core/handler/manager_server.go | 8 +++----- core/networkserver/manager_server.go | 2 +- core/router/manager_server.go | 15 +++++++-------- utils/errors/errors.go | 4 +--- 6 files changed, 18 insertions(+), 26 deletions(-) diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index b238240ef..d8fac9c51 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -16,8 +16,6 @@ import ( "google.golang.org/grpc/codes" ) -var grpcErrf = grpc.Errorf // To make go vet stop complaining - type brokerManager struct { broker *broker deviceManager pb_lorawan.DeviceManagerClient @@ -57,12 +55,12 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Handler Registration")) } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, grpcErrf(codes.PermissionDenied, "No access to this application") + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied("No access to this application")) } // Add Handler in local cache handler, err := b.broker.Discovery.Get("handler", in.HandlerId) if err != nil { - return nil, grpcErrf(codes.Internal, "Could not get Handler Announcement") + return nil, errors.BuildGRPCError(errors.NewErrInternal("Could not get Handler Announcement")) } handler.AddMetadata(discovery.Metadata_APP_ID, []byte(in.AppId)) return &empty.Empty{}, nil @@ -85,7 +83,7 @@ func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrReque } func (b *brokerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpcErrf(codes.Unimplemented, "Not Implemented") + return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") } func (b *broker) RegisterManager(s *grpc.Server) { diff --git a/core/discovery/server.go b/core/discovery/server.go index 6c595b5ff..4b2659210 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -4,23 +4,22 @@ package discovery import ( + "fmt" + "github.com/TheThingsNetwork/go-account-lib/rights" pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/codes" ) type discoveryServer struct { discovery *discovery } -var grpcErrf = grpc.Errorf // To make go vet stop complaining - func errPermissionDeniedf(format string, args ...string) error { - return grpcErrf(codes.PermissionDenied, "Discovery:"+format, args) + return errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args))) } func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 08ff32fb7..e3c39f981 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -23,8 +23,6 @@ import ( "google.golang.org/grpc/metadata" ) -var grpcErrf = grpc.Errorf // To make go vet stop complaining - type handlerManager struct { handler *handler deviceManager pb_lorawan.DeviceManagerClient @@ -124,7 +122,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E lorawan := in.GetLorawanDevice() if lorawan == nil { - return nil, grpcErrf(codes.InvalidArgument, "No LoRaWAN Device") + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device", "No LoRaWAN Device")) } if dev != nil { // When this is an update @@ -308,7 +306,7 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica return nil, errors.BuildGRPCError(err) } if app != nil { - return nil, grpcErrf(codes.AlreadyExists, "Application already exists") + return nil, errors.BuildGRPCError(errors.NewErrAlreadyExists("Application")) } err = h.handler.applications.Set(&application.Application{ @@ -434,7 +432,7 @@ func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrR } func (h *handlerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpcErrf(codes.Unimplemented, "Not Implemented") + return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") } func (h *handler) RegisterManager(s *grpc.Server) { diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 2682e6b35..ded54586e 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -152,7 +152,7 @@ func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.De } func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpcErrf(codes.Unimplemented, "Not Implemented") + return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") } // RegisterManager registers this networkserver as a NetworkServerManagerServer (github.com/TheThingsNetwork/ttn/api/networkserver) diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 4f1e3fbee..c6c69338e 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -4,33 +4,32 @@ package router import ( + "fmt" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) -var grpcErrf = grpc.Errorf // To make go vet stop complaining - type routerManager struct { router *router } -var errf = grpc.Errorf - func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusRequest) (*pb.GatewayStatusResponse, error) { if in.GatewayId == "" { - return nil, errf(codes.InvalidArgument, "GatewayID is required") + return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Gateway Status Request", "ID is required")) } _, err := r.router.ValidateTTNAuthContext(ctx) if err != nil { - return nil, errf(codes.PermissionDenied, "No access") + return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied("No access")) } r.router.gatewaysLock.RLock() gtw, ok := r.router.gateways[in.GatewayId] r.router.gatewaysLock.RUnlock() if !ok { - return nil, grpcErrf(codes.NotFound, "Gateway %s not found", in.GatewayId) + return nil, errors.BuildGRPCError(errors.NewErrNotFound(fmt.Sprintf("Gateway %s", in.GatewayId))) } status, err := gtw.Status.Get() if err != nil { @@ -43,7 +42,7 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR } func (r *routerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpcErrf(codes.Unimplemented, "Not Implemented") + return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") } // RegisterManager registers this router as a RouterManagerServer (github.com/TheThingsNetwork/ttn/api/router) diff --git a/utils/errors/errors.go b/utils/errors/errors.go index e0c0e7ac6..e75087b74 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -40,8 +40,6 @@ func GetErrType(err error) ErrType { return Unknown } -var grpcErrf = grpc.Errorf - // BuildGRPCError returns the error with a GRPC code func BuildGRPCError(err error) error { if err == nil { @@ -60,7 +58,7 @@ func BuildGRPCError(err error) error { case *ErrPermissionDenied: code = codes.PermissionDenied } - return grpcErrf(code, err.Error()) + return grpc.Errorf(code, err.Error()) } // FromGRPCError creates a regular error with the same type as the gRPC error From 5bed74c9a7c927723f8b3e526a9db0c3853cbb3e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 09:52:43 +0100 Subject: [PATCH 2148/2266] [Gitlab-CI] Don't cache vendor dir --- .gitlab-ci.yml | 1 - Makefile | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d3b8bba27..858ce5542 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,7 +12,6 @@ cache: key: "$CI_PROJECT_PATH" paths: - .govendor - - vendor/ before_script: - mkdir -p $(pwd)/.govendor diff --git a/Makefile b/Makefile index eec3e4490..33818baa7 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ build-deps: @command -v govendor > /dev/null || go get "github.com/kardianos/govendor" deps: build-deps - govendor sync + govendor sync -v dev-deps: deps @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast From c8e482937ddfd2e5febd2886c33feac7f405d14d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 10:09:05 +0100 Subject: [PATCH 2149/2266] Use thethingsnetwork/rabbitmq --- .gitlab-ci.yml | 6 +++--- .travis.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 858ce5542..ab0291b40 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,12 +31,12 @@ tests: stage: test image: golang:latest services: - - robertobarreda/rabbitmq:mqtt + - thethingsnetwork/rabbitmq - redis variables: REDIS_HOST: redis - MQTT_ADDRESS: robertobarreda__rabbitmq:1883 - AMQP_ADDRESS: robertobarreda__rabbitmq:5672 + MQTT_ADDRESS: thethingsnetwork__rabbitmq:1883 + AMQP_ADDRESS: thethingsnetwork__rabbitmq:5672 script: - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - make deps diff --git a/.travis.yml b/.travis.yml index c180b6a3c..8ea7fadac 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,7 +14,7 @@ install: before_script: - docker run -d -p 127.0.0.1:6379:6379 redis - - docker run -d -p 127.0.0.1:5672:5672 -p 127.0.0.1:1883:1883 robertobarreda/rabbitmq:mqtt + - docker run -d -p 127.0.0.1:5672:5672 -p 127.0.0.1:1883:1883 thethingsnetwork/rabbitmq script: - make test From 358ac574f4524ef2ed8f6e189efc3f56422dc664 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 10:29:40 +0100 Subject: [PATCH 2150/2266] Receive Empty on proto client streams --- api/monitor/downlink.go | 3 ++- api/monitor/gateway_status.go | 3 ++- api/monitor/uplink.go | 3 ++- api/router/client.go | 5 +++-- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/api/monitor/downlink.go b/api/monitor/downlink.go index 964b51e8b..fddfeff0d 100644 --- a/api/monitor/downlink.go +++ b/api/monitor/downlink.go @@ -9,6 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) @@ -53,7 +54,7 @@ newStream: } }() - var msg []byte + msg := new(empty.Empty) for { if err := stream.RecvMsg(&msg); err != nil { cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor downlink stream, closing...") diff --git a/api/monitor/gateway_status.go b/api/monitor/gateway_status.go index 4bda5cac2..41cdf0c96 100644 --- a/api/monitor/gateway_status.go +++ b/api/monitor/gateway_status.go @@ -9,6 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) @@ -53,7 +54,7 @@ newStream: } }() - var msg []byte + msg := new(empty.Empty) for { if err := stream.RecvMsg(&msg); err != nil { cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor status stream, closing...") diff --git a/api/monitor/uplink.go b/api/monitor/uplink.go index 982d21c37..26771defd 100644 --- a/api/monitor/uplink.go +++ b/api/monitor/uplink.go @@ -9,6 +9,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" ) @@ -53,7 +54,7 @@ newStream: } }() - var msg []byte + msg := new(empty.Empty) for { if err := stream.RecvMsg(&msg); err != nil { cl.Ctx.WithError(errors.FromGRPCError(err)).Warn("Received error on monitor uplink stream, closing...") diff --git a/api/router/client.go b/api/router/client.go index 99b635013..5f00a7dd4 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -11,6 +11,7 @@ import ( "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -108,7 +109,7 @@ func (c *gatewayClient) setupGatewayStatus() error { c.gatewayStatus = gatewayStatusClient c.stopGatewayStatus = make(chan bool) go func() { - var msg []byte + msg := new(empty.Empty) for { select { case <-c.stopGatewayStatus: @@ -166,7 +167,7 @@ func (c *gatewayClient) setupUplink() error { c.uplink = uplinkClient c.stopUplink = make(chan bool) go func() { - var msg []byte + msg := new(empty.Empty) for { select { case <-c.stopUplink: From 801dabff7f60627ca032232639c8e9e39002300f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 11:58:57 +0100 Subject: [PATCH 2151/2266] Wrap apex/log in api/logging.go --- api/logging.go | 65 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 10 deletions(-) diff --git a/api/logging.go b/api/logging.go index c1c4e45de..59c4bd044 100644 --- a/api/logging.go +++ b/api/logging.go @@ -1,5 +1,7 @@ package api +import "github.com/apex/log" + // Logger used in mqtt package type Logger interface { Debug(msg string) @@ -12,6 +14,46 @@ type Logger interface { Warnf(msg string, v ...interface{}) Errorf(msg string, v ...interface{}) Fatalf(msg string, v ...interface{}) + WithField(string, interface{}) Logger + WithFields(log.Fielder) Logger + WithError(error) Logger +} + +// Apex wraps apex/log +func Apex(ctx log.Interface) Logger { + return &apexInterfaceWrapper{ctx} +} + +type apexInterfaceWrapper struct { + log.Interface +} + +func (w *apexInterfaceWrapper) WithField(k string, v interface{}) Logger { + return &apexEntryWrapper{w.Interface.WithField(k, v)} +} + +func (w *apexInterfaceWrapper) WithFields(fields log.Fielder) Logger { + return &apexEntryWrapper{w.Interface.WithFields(fields)} +} + +func (w *apexInterfaceWrapper) WithError(err error) Logger { + return &apexEntryWrapper{w.Interface.WithError(err)} +} + +type apexEntryWrapper struct { + *log.Entry +} + +func (w *apexEntryWrapper) WithField(k string, v interface{}) Logger { + return &apexEntryWrapper{w.Entry.WithField(k, v)} +} + +func (w *apexEntryWrapper) WithFields(fields log.Fielder) Logger { + return &apexEntryWrapper{w.Entry.WithFields(fields)} +} + +func (w *apexEntryWrapper) WithError(err error) Logger { + return &apexEntryWrapper{w.Entry.WithError(err)} } var logger Logger = noopLogger{} @@ -29,13 +71,16 @@ func SetLogger(log Logger) { // noopLogger just does nothing type noopLogger struct{} -func (l noopLogger) Debug(msg string) {} -func (l noopLogger) Info(msg string) {} -func (l noopLogger) Warn(msg string) {} -func (l noopLogger) Error(msg string) {} -func (l noopLogger) Fatal(msg string) {} -func (l noopLogger) Debugf(msg string, v ...interface{}) {} -func (l noopLogger) Infof(msg string, v ...interface{}) {} -func (l noopLogger) Warnf(msg string, v ...interface{}) {} -func (l noopLogger) Errorf(msg string, v ...interface{}) {} -func (l noopLogger) Fatalf(msg string, v ...interface{}) {} +func (l noopLogger) Debug(msg string) {} +func (l noopLogger) Info(msg string) {} +func (l noopLogger) Warn(msg string) {} +func (l noopLogger) Error(msg string) {} +func (l noopLogger) Fatal(msg string) {} +func (l noopLogger) Debugf(msg string, v ...interface{}) {} +func (l noopLogger) Infof(msg string, v ...interface{}) {} +func (l noopLogger) Warnf(msg string, v ...interface{}) {} +func (l noopLogger) Errorf(msg string, v ...interface{}) {} +func (l noopLogger) Fatalf(msg string, v ...interface{}) {} +func (l noopLogger) WithField(string, interface{}) Logger { return l } +func (l noopLogger) WithFields(log.Fielder) Logger { return l } +func (l noopLogger) WithError(error) Logger { return l } From 05d69aca3bb8df4d736a5173d1c92e404b4a8f03 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 12:35:40 +0100 Subject: [PATCH 2152/2266] Wrap Sirupsen/logrus in api/logging.go --- api/logging.go | 67 ++++++++++++++++++++++++++++++++++++++++------ vendor/vendor.json | 6 +++++ 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/api/logging.go b/api/logging.go index 59c4bd044..5a6f826d0 100644 --- a/api/logging.go +++ b/api/logging.go @@ -1,6 +1,9 @@ package api -import "github.com/apex/log" +import ( + logrus "github.com/Sirupsen/logrus" + apex "github.com/apex/log" +) // Logger used in mqtt package type Logger interface { @@ -15,24 +18,72 @@ type Logger interface { Errorf(msg string, v ...interface{}) Fatalf(msg string, v ...interface{}) WithField(string, interface{}) Logger - WithFields(log.Fielder) Logger + WithFields(apex.Fielder) Logger WithError(error) Logger } +// StandardLogrus wraps the standard Logrus Logger into a Logger +func StandardLogrus() Logger { + return Logrus(logrus.StandardLogger()) +} + +// Logrus wraps logrus into a Logger +func Logrus(logger *logrus.Logger) Logger { + return &logrusEntryWrapper{logrus.NewEntry(logger)} +} + +type logrusEntryWrapper struct { + *logrus.Entry +} + +func (w *logrusEntryWrapper) Debug(msg string) { + w.Entry.Debug(msg) +} + +func (w *logrusEntryWrapper) Info(msg string) { + w.Entry.Info(msg) +} + +func (w *logrusEntryWrapper) Warn(msg string) { + w.Entry.Warn(msg) +} + +func (w *logrusEntryWrapper) Error(msg string) { + w.Entry.Error(msg) +} + +func (w *logrusEntryWrapper) Fatal(msg string) { + w.Entry.Fatal(msg) +} + +func (w *logrusEntryWrapper) WithError(err error) Logger { + return &logrusEntryWrapper{w.Entry.WithError(err)} +} + +func (w *logrusEntryWrapper) WithField(k string, v interface{}) Logger { + return &logrusEntryWrapper{w.Entry.WithField(k, v)} +} + +func (w *logrusEntryWrapper) WithFields(fields apex.Fielder) Logger { + return &logrusEntryWrapper{w.Entry.WithFields( + map[string]interface{}(fields.Fields()), + )} +} + // Apex wraps apex/log -func Apex(ctx log.Interface) Logger { +func Apex(ctx apex.Interface) Logger { return &apexInterfaceWrapper{ctx} } type apexInterfaceWrapper struct { - log.Interface + apex.Interface } func (w *apexInterfaceWrapper) WithField(k string, v interface{}) Logger { return &apexEntryWrapper{w.Interface.WithField(k, v)} } -func (w *apexInterfaceWrapper) WithFields(fields log.Fielder) Logger { +func (w *apexInterfaceWrapper) WithFields(fields apex.Fielder) Logger { return &apexEntryWrapper{w.Interface.WithFields(fields)} } @@ -41,14 +92,14 @@ func (w *apexInterfaceWrapper) WithError(err error) Logger { } type apexEntryWrapper struct { - *log.Entry + *apex.Entry } func (w *apexEntryWrapper) WithField(k string, v interface{}) Logger { return &apexEntryWrapper{w.Entry.WithField(k, v)} } -func (w *apexEntryWrapper) WithFields(fields log.Fielder) Logger { +func (w *apexEntryWrapper) WithFields(fields apex.Fielder) Logger { return &apexEntryWrapper{w.Entry.WithFields(fields)} } @@ -82,5 +133,5 @@ func (l noopLogger) Warnf(msg string, v ...interface{}) {} func (l noopLogger) Errorf(msg string, v ...interface{}) {} func (l noopLogger) Fatalf(msg string, v ...interface{}) {} func (l noopLogger) WithField(string, interface{}) Logger { return l } -func (l noopLogger) WithFields(log.Fielder) Logger { return l } +func (l noopLogger) WithFields(apex.Fielder) Logger { return l } func (l noopLogger) WithError(error) Logger { return l } diff --git a/vendor/vendor.json b/vendor/vendor.json index 7f0ce9cbb..22a8f1b3e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -2,6 +2,12 @@ "comment": "", "ignore": "test", "package": [ + { + "checksumSHA1": "yRNw/uu/4N7XHVe1PHtKPl6xtDg=", + "path": "github.com/Sirupsen/logrus", + "revision": "1445b7a38228c041834afc69231b7966b9943397", + "revisionTime": "2016-11-08T19:08:11Z" + }, { "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", "path": "github.com/StackExchange/wmi", From 365925a6d86cd5baacc52470b90db35defbb2c3e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 13:37:40 +0100 Subject: [PATCH 2153/2266] Add gRPC health server to components --- cmd/broker.go | 1 + cmd/discovery.go | 1 + cmd/handler.go | 1 + cmd/networkserver.go | 1 + cmd/router.go | 1 + core/component/component.go | 2 ++ core/component/status.go | 22 +++++++++++++++++++++- vendor/vendor.json | 12 ++++++++++++ 8 files changed, 40 insertions(+), 1 deletion(-) diff --git a/cmd/broker.go b/cmd/broker.go index 2a9ed77a5..6806156ba 100644 --- a/cmd/broker.go +++ b/cmd/broker.go @@ -70,6 +70,7 @@ var brokerCmd = &cobra.Command{ grpc := grpc.NewServer(component.ServerOptions()...) // Register and Listen + component.RegisterHealthServer(grpc) broker.RegisterRPC(grpc) broker.RegisterManager(grpc) go grpc.Serve(lis) diff --git a/cmd/discovery.go b/cmd/discovery.go index 558cc3e93..fb430c54b 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -67,6 +67,7 @@ var discoveryCmd = &cobra.Command{ grpc := grpc.NewServer(component.ServerOptions()...) // Register and Listen + component.RegisterHealthServer(grpc) discovery.RegisterRPC(grpc) go grpc.Serve(lis) diff --git a/cmd/handler.go b/cmd/handler.go index 9c3ea9cc9..15bab997e 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -95,6 +95,7 @@ var handlerCmd = &cobra.Command{ grpc := grpc.NewServer(component.ServerOptions()...) // Register and Listen + component.RegisterHealthServer(grpc) handler.RegisterRPC(grpc) handler.RegisterManager(grpc) go grpc.Serve(lis) diff --git a/cmd/networkserver.go b/cmd/networkserver.go index c240559f4..b639974e6 100644 --- a/cmd/networkserver.go +++ b/cmd/networkserver.go @@ -82,6 +82,7 @@ var networkserverCmd = &cobra.Command{ grpc := grpc.NewServer(component.ServerOptions()...) // Register and Listen + component.RegisterHealthServer(grpc) networkserver.RegisterRPC(grpc) networkserver.RegisterManager(grpc) go grpc.Serve(lis) diff --git a/cmd/router.go b/cmd/router.go index 120252d3c..c45b754b5 100644 --- a/cmd/router.go +++ b/cmd/router.go @@ -53,6 +53,7 @@ var routerCmd = &cobra.Command{ grpc := grpc.NewServer(component.ServerOptions()...) // Register and Listen + component.RegisterHealthServer(grpc) router.RegisterRPC(grpc) router.RegisterManager(grpc) go grpc.Serve(lis) diff --git a/core/component/component.go b/core/component/component.go index 092b9137c..d8eb6e28d 100644 --- a/core/component/component.go +++ b/core/component/component.go @@ -22,6 +22,7 @@ import ( "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/health" ) // Component contains the common attributes for all TTN components @@ -36,6 +37,7 @@ type Component struct { tlsConfig *tls.Config TokenKeyProvider tokenkey.Provider status int64 + healthServer *health.Server } type Interface interface { diff --git a/core/component/status.go b/core/component/status.go index 4c15a8650..bd0516c53 100644 --- a/core/component/status.go +++ b/core/component/status.go @@ -1,6 +1,12 @@ package component -import "sync/atomic" +import ( + "sync/atomic" + + "google.golang.org/grpc" + "google.golang.org/grpc/health" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) // Status indicates the health status of this component type Status int @@ -20,4 +26,18 @@ func (c *Component) GetStatus() Status { // SetStatus sets the health status of the component func (c *Component) SetStatus(status Status) { atomic.StoreInt64(&c.status, int64(status)) + if c.healthServer != nil { + switch status { + case StatusHealthy: + c.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) + case StatusUnhealthy: + c.healthServer.SetServingStatus("", healthpb.HealthCheckResponse_NOT_SERVING) + } + } +} + +// RegisterHealthServer registers the component's health status to the gRPC server +func (c *Component) RegisterHealthServer(srv *grpc.Server) { + c.healthServer = health.NewServer() + healthpb.RegisterHealthServer(srv, c.healthServer) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 22a8f1b3e..2bc5e8154 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -776,6 +776,18 @@ "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", "revisionTime": "2016-11-04T23:58:17Z" }, + { + "checksumSHA1": "d0iunsiWfA0qXxLMNkTC4tGJnOo=", + "path": "google.golang.org/grpc/health", + "revision": "e59af7a0a8bf571556b40c3f871dbc4298f77693", + "revisionTime": "2016-11-11T01:56:15Z" + }, + { + "checksumSHA1": "pSFXzfvPlaDBK2RsMcTiIeks4ok=", + "path": "google.golang.org/grpc/health/grpc_health_v1", + "revision": "e59af7a0a8bf571556b40c3f871dbc4298f77693", + "revisionTime": "2016-11-11T01:56:15Z" + }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", From 54c8ef880e0787ae6ae69773606d37680f52aa7e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 11 Nov 2016 13:38:26 +0100 Subject: [PATCH 2154/2266] Use correct logging in ttnctl --- ttnctl/cmd/root.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 0aa7493bf..a313e851f 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -48,7 +48,7 @@ var RootCmd = &cobra.Command{ api.DialOptions = append(api.DialOptions, grpc.WithBlock()) api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) grpclog.SetLogger(logging.NewGRPCLogger(ctx)) - api.SetLogger(ctx) + api.SetLogger(api.Apex(ctx)) }, } From a9734b1ef339989817009d967a83ade4594b1b24 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 14 Nov 2016 11:41:31 +0100 Subject: [PATCH 2155/2266] Correctly wrap errors in api.NotNilAndValid --- api/validation.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/api/validation.go b/api/validation.go index 61137ad71..760b77df8 100644 --- a/api/validation.go +++ b/api/validation.go @@ -1,9 +1,10 @@ package api import ( - "github.com/TheThingsNetwork/ttn/utils/errors" "reflect" "regexp" + + "github.com/TheThingsNetwork/ttn/utils/errors" ) var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") @@ -26,7 +27,7 @@ func NotEmptyAndValidId(id string, argument string) error { func NotNilAndValid(in interface{}, argument string) error { // Structs can not be nil and reflect.ValueOf(in).IsNil() would panic if reflect.ValueOf(in).Kind() == reflect.Struct { - return Validate(in) + return errors.Wrap(Validate(in), "Invalid "+argument) } // We need to check for the interface to be nil and the value of the interface @@ -34,5 +35,10 @@ func NotNilAndValid(in interface{}, argument string) error { if in == nil || reflect.ValueOf(in).IsNil() { return errors.NewErrInvalidArgument(argument, "can not be empty") } - return Validate(in) + + if err := Validate(in); err != nil { + return errors.Wrap(Validate(in), "Invalid "+argument) + } + + return nil } From a9981c6301acb68e1b920bcfa9fb94920f60106f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 14 Nov 2016 11:48:59 +0100 Subject: [PATCH 2156/2266] Commit tests for a9734b1 --- api/validation_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/validation_test.go b/api/validation_test.go index 76861cf96..5d88cb306 100644 --- a/api/validation_test.go +++ b/api/validation_test.go @@ -20,16 +20,16 @@ func TestNotNilAndValidStruct(t *testing.T) { subject := invalid{} err := NotNilAndValid(subject, "subject") - if err == nil || err.Error() != "nestedPtr not valid: can not be empty" { - t.Error("Expected validation error: 'nestedPtr not valid: can not be empty' but found", err) + if err == nil || err.Error() != "Invalid subject: nestedPtr not valid: can not be empty" { + t.Error("Expected validation error: 'Invalid subject: nestedPtr not valid: can not be empty", err) } } func TestNotNilAndValidPtr(t *testing.T) { subject := &invalid{} err := NotNilAndValid(subject, "subject") - if err == nil || err.Error() != "nestedPtr not valid: can not be empty" { - t.Error("Expected validation error: 'nestedPtr not valid: can not be empty' but found", err) + if err == nil || err.Error() != "Invalid subject: nestedPtr not valid: can not be empty" { + t.Error("Expected validation error: 'Invalid subject: nestedPtr not valid: can not be empty", err) } } From 35597e1e47f73d7286876d8dde85acf2fdc6bfc9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 14 Nov 2016 13:31:27 +0100 Subject: [PATCH 2157/2266] Move gRPC logger to api package --- api/logging.go | 31 +++++++++++++++- cmd/root.go | 5 +++ core/component/component.go | 4 -- ttnctl/cmd/root.go | 5 +-- utils/logging/grpc.go | 73 ------------------------------------- 5 files changed, 37 insertions(+), 81 deletions(-) delete mode 100644 utils/logging/grpc.go diff --git a/api/logging.go b/api/logging.go index 5a6f826d0..cc67dcc33 100644 --- a/api/logging.go +++ b/api/logging.go @@ -1,8 +1,11 @@ package api import ( + "fmt" + logrus "github.com/Sirupsen/logrus" apex "github.com/apex/log" + "google.golang.org/grpc/grpclog" ) // Logger used in mqtt package @@ -22,6 +25,31 @@ type Logger interface { WithError(error) Logger } +// GRPC logger +func GRPC() grpclog.Logger { + return &grpcWrapper{GetLogger()} +} + +type grpcWrapper struct { + Logger +} + +func (w *grpcWrapper) Fatal(args ...interface{}) { + w.Logger.Fatal(fmt.Sprint(args...)) +} +func (w *grpcWrapper) Fatalln(args ...interface{}) { + w.Fatal(args...) +} +func (w *grpcWrapper) Print(args ...interface{}) { + w.Logger.Debug(fmt.Sprint(args...)) +} +func (w *grpcWrapper) Printf(format string, args ...interface{}) { + w.Logger.Debugf(format, args...) +} +func (w *grpcWrapper) Println(args ...interface{}) { + w.Print(args...) +} + // StandardLogrus wraps the standard Logrus Logger into a Logger func StandardLogrus() Logger { return Logrus(logrus.StandardLogger()) @@ -114,9 +142,10 @@ func GetLogger() Logger { return logger } -// SetLogger returns the API Logger +// SetLogger sets the API and gRPC Logger func SetLogger(log Logger) { logger = log + grpclog.SetLogger(GRPC()) } // noopLogger just does nothing diff --git a/cmd/root.go b/cmd/root.go index b4d12abad..f1c1d3d27 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,6 +14,7 @@ import ( "time" cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" + "github.com/TheThingsNetwork/ttn/api" esHandler "github.com/TheThingsNetwork/ttn/utils/elasticsearch/handler" "github.com/apex/log" jsonHandler "github.com/apex/log/handlers/json" @@ -78,6 +79,10 @@ var RootCmd = &cobra.Command{ ctx = &log.Logger{ Handler: multiHandler.New(logHandlers...), } + + // Set the API/gRPC logger + api.SetLogger(api.Apex(ctx)) + ctx.WithFields(log.Fields{ "ComponentID": viper.GetString("id"), "Description": viper.GetString("description"), diff --git a/core/component/component.go b/core/component/component.go index d8eb6e28d..383d22cd3 100644 --- a/core/component/component.go +++ b/core/component/component.go @@ -16,12 +16,10 @@ import ( "github.com/TheThingsNetwork/go-account-lib/tokenkey" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_monitor "github.com/TheThingsNetwork/ttn/api/monitor" - "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/apex/log" "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/grpclog" "google.golang.org/grpc/health" ) @@ -69,8 +67,6 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo // SEE: https://github.com/grpc/grpc-go/issues/695 grpc.EnableTracing = false - grpclog.SetLogger(logging.NewGRPCLogger(ctx)) - component := &Component{ Config: ConfigFromViper(), Ctx: ctx, diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index a313e851f..be4a6b0ae 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -12,12 +12,10 @@ import ( cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/ttnctl/util" - "github.com/TheThingsNetwork/ttn/utils/logging" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" "google.golang.org/grpc" - "google.golang.org/grpc/grpclog" ) var cfgFile string @@ -47,8 +45,9 @@ var RootCmd = &cobra.Command{ api.DialOptions = append(api.DialOptions, grpc.WithBlock()) api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) - grpclog.SetLogger(logging.NewGRPCLogger(ctx)) + api.SetLogger(api.Apex(ctx)) + }, } diff --git a/utils/logging/grpc.go b/utils/logging/grpc.go deleted file mode 100644 index 9f6c0db58..000000000 --- a/utils/logging/grpc.go +++ /dev/null @@ -1,73 +0,0 @@ -package logging - -import ( - "fmt" - "strings" - - "google.golang.org/grpc/grpclog" - - "github.com/apex/log" -) - -// NewGRPCLogger wraps the ctx and returns a grpclog.Logger -func NewGRPCLogger(ctx log.Interface) grpclog.Logger { - return &gRPCLogger{ctx} -} - -// gRPCLogger implements the grpc/grpclog.Logger interface -type gRPCLogger struct { - ctx log.Interface -} - -var filteredLogs = []string{ - "failed to complete security handshake", -} - -func (l *gRPCLogger) shouldLog(log string) bool { - for _, filter := range filteredLogs { - if strings.Contains(log, filter) { - return false - } - } - return true -} - -// Fatal implements the grpc/grpclog.Logger interface -func (l *gRPCLogger) Fatal(args ...interface{}) { - l.fatalln(fmt.Sprint(args...)) -} - -// Fatalf implements the grpc/grpclog.Logger interface -func (l *gRPCLogger) Fatalf(format string, args ...interface{}) { - l.fatalln(fmt.Sprintf(format, args...)) -} - -// Fatalln implements the grpc/grpclog.Logger interface -func (l *gRPCLogger) Fatalln(args ...interface{}) { - l.fatalln(fmt.Sprint(args...)) -} - -func (l *gRPCLogger) fatalln(in string) { - l.ctx.Error(in) -} - -// Print implements the grpc/grpclog.Logger interface -func (l *gRPCLogger) Print(args ...interface{}) { - l.println(fmt.Sprint(args...)) -} - -// Printf implements the grpc/grpclog.Logger interface -func (l *gRPCLogger) Printf(format string, args ...interface{}) { - l.println(fmt.Sprintf(format, args...)) -} - -// Println implements the grpc/grpclog.Logger interface -func (l *gRPCLogger) Println(args ...interface{}) { - l.println(fmt.Sprint(args...)) -} - -func (l *gRPCLogger) println(in string) { - if l.shouldLog(in) { - l.ctx.Debug(in) - } -} From b3726473333c6c1dd3e4ae92f50ad7a02e89ac1f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 14 Nov 2016 16:39:18 +0100 Subject: [PATCH 2158/2266] Fix help for ttnctl gateway token --- ttnctl/cmd/gateways_token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ttnctl/cmd/gateways_token.go b/ttnctl/cmd/gateways_token.go index 31f775d46..77231f499 100644 --- a/ttnctl/cmd/gateways_token.go +++ b/ttnctl/cmd/gateways_token.go @@ -12,7 +12,7 @@ import ( ) var gatewaysTokenCmd = &cobra.Command{ - Use: "token [Type] [gatewayID]", + Use: "token [GatewayID]", Hidden: true, Short: "Get the token for a gateway.", Long: `gateways token gets a signed token for the gateway.`, From f66279584c0a101b0455aa52ebae797ff4f0eca0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 15 Nov 2016 12:56:38 +0100 Subject: [PATCH 2159/2266] Master auth server should not be hardcoded --- cmd/discovery.go | 4 ++++ core/discovery/discovery.go | 18 ++++++++++++++++-- core/discovery/server.go | 19 +++++++++---------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/cmd/discovery.go b/cmd/discovery.go index fb430c54b..9f3a6984f 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -54,6 +54,7 @@ var discoveryCmd = &cobra.Command{ if viper.GetBool("discovery.cache") { discovery.WithCache(announcement.DefaultCacheOptions) } + discovery.WithMasterAuthServers(viper.GetStringSlice("discovery.master-auth-servers")...) err = discovery.Init(component) if err != nil { ctx.WithError(err).Fatal("Could not initialize discovery") @@ -94,4 +95,7 @@ func init() { discoveryCmd.Flags().Bool("cache", false, "Add a cache in front of the database") viper.BindPFlag("discovery.cache", discoveryCmd.Flags().Lookup("cache")) + + discoveryCmd.Flags().StringSlice("master-auth-servers", []string{"ttn-account"}, "Auth servers that are allowed to manage this network") + viper.BindPFlag("discovery.master-auth-servers", discoveryCmd.Flags().Lookup("master-auth-servers")) } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index cb1ac0527..7347c9d6e 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -16,6 +16,7 @@ import ( type Discovery interface { component.Interface WithCache(options announcement.CacheOptions) + WithMasterAuthServers(serverID ...string) Announce(announcement *pb.Announcement) error GetAll(serviceName string) ([]*pb.Announcement, error) Get(serviceName string, id string) (*pb.Announcement, error) @@ -26,13 +27,25 @@ type Discovery interface { // discovery is a reference implementation for a TTN Service Discovery component. type discovery struct { *component.Component - services announcement.Store + services announcement.Store + masterAuthServers map[string]struct{} } func (d *discovery) WithCache(options announcement.CacheOptions) { d.services = announcement.NewCachedAnnouncementStore(d.services, options) } +func (d *discovery) WithMasterAuthServers(serverID ...string) { + for _, serverID := range serverID { + d.masterAuthServers[serverID] = struct{}{} + } +} + +func (d *discovery) IsMasterAuthServer(serverID string) bool { + _, ok := d.masterAuthServers[serverID] + return ok +} + func (d *discovery) Init(c *component.Component) error { d.Component = c err := d.Component.UpdateTokenKey() @@ -102,6 +115,7 @@ func (d *discovery) DeleteMetadata(serviceName string, id string, in *pb.Metadat // NewRedisDiscovery creates a new Redis-based discovery service func NewRedisDiscovery(client *redis.Client) Discovery { return &discovery{ - services: announcement.NewRedisAnnouncementStore(client, "discovery"), + services: announcement.NewRedisAnnouncementStore(client, "discovery"), + masterAuthServers: make(map[string]struct{}), } } diff --git a/core/discovery/server.go b/core/discovery/server.go index 4b2659210..6b9326012 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -32,9 +32,9 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me if in.ServiceName != "broker" { return errPermissionDeniedf("Announcement service type should be \"broker\"") } - // Only allow prefix announcements if token is issued by the official ttn account server (or if in dev mode) - if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return errPermissionDeniedf("Token issuer \"%s\" should be \"ttn-account\"", claims.Issuer) + // Only allow prefix announcements if token is issued by a master auth server (or if in dev mode) + if d.discovery.Component.Identity.Id != "dev" && !d.discovery.IsMasterAuthServer(claims.Issuer) { + return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer) } if claims.Type != in.ServiceName { return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) @@ -47,9 +47,9 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me if in.ServiceName != "handler" { return errPermissionDeniedf("Announcement service type should be \"handler\"") } - // Only allow eui announcements if token is issued by the official ttn account server (or if in dev mode) - if claims.Issuer != "ttn-account" && d.discovery.Component.Identity.Id != "dev" { - return errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) + // Only allow eui announcements if token is issued by a master auth server (or if in dev mode) + if d.discovery.Component.Identity.Id != "dev" && !d.discovery.IsMasterAuthServer(claims.Issuer) { + return errPermissionDeniedf("Token issuer %s is not allowed to make changes to the network settings", claims.Issuer) } if claims.Type != in.ServiceName { return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) @@ -80,14 +80,13 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc // If not in development mode if d.discovery.Component.Identity.Id != "dev" { - // Tokens must be issued by official ttn account server - if claims.Issuer != "ttn-account" { - return nil, errPermissionDeniedf("Token issuer %s should be ttn-account", claims.Issuer) + if !d.discovery.IsMasterAuthServer(claims.Issuer) { + return nil, errPermissionDeniedf("Token issuer %s is not allowed to make changes to the network settings", claims.Issuer) } // Can't announce development components if claims.Subject == "dev" { - return nil, errPermissionDeniedf("Can't announce development components") + return nil, errPermissionDeniedf("Can't announce development components to production networks") } } From ce561b60108dc8ae76f12535d83c63e4d67e302c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 15 Nov 2016 15:33:33 +0100 Subject: [PATCH 2160/2266] Add x509.KeyUsageCertSign usage --- utils/security/generate_keys.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/security/generate_keys.go b/utils/security/generate_keys.go index c0ae16c34..3f0942fbc 100644 --- a/utils/security/generate_keys.go +++ b/utils/security/generate_keys.go @@ -70,7 +70,7 @@ func GenerateCert(location string, hostnames ...string) error { IsCA: true, NotBefore: notBefore, NotAfter: notAfter, - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, BasicConstraintsValid: true, } From 36b4ef63f18dbc9edcc640da56feb3fbaba84a04 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 15 Nov 2016 17:41:29 +0100 Subject: [PATCH 2161/2266] Remove TODOs that have been resolved --- core/handler/manager_server.go | 1 - core/networkserver/downlink.go | 2 -- utils/toa/toa.go | 2 -- utils/toa/toa_test.go | 1 - 4 files changed, 6 deletions(-) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index e3c39f981..b503c313d 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -383,7 +383,6 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati } // Get and delete all devices for this application - // TODO: add "app:devices:r" and "app:devices:w" check devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { return nil, errors.BuildGRPCError(err) diff --git a/core/networkserver/downlink.go b/core/networkserver/downlink.go index 50a32e63c..1ef39f652 100644 --- a/core/networkserver/downlink.go +++ b/core/networkserver/downlink.go @@ -45,8 +45,6 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ return nil, err } - // TODO: Maybe we need to add MAC commands on downlink - // Sign MIC phyPayload.SetMIC(lorawan.AES128Key(dev.NwkSKey)) diff --git a/utils/toa/toa.go b/utils/toa/toa.go index 6553ef6f5..c42dc99f7 100644 --- a/utils/toa/toa.go +++ b/utils/toa/toa.go @@ -57,8 +57,6 @@ func ComputeLoRa(payloadSize uint, datr string, codr string) (time.Duration, err // ComputeFSK computes the time-on-air given a PHY payload size in bytes and a // bitrate, Note that this function operates on the PHY payload size and does // not add the LoRaWAN header. -// -// TODO: (@tftelkamp): Verify this func ComputeFSK(payloadSize uint, bitrate int) (time.Duration, error) { tPkt := int(time.Second) * (int(payloadSize) + 5 + 3 + 1 + 2) * 8 / bitrate return time.Duration(tPkt), nil diff --git a/utils/toa/toa_test.go b/utils/toa/toa_test.go index 0b277349f..e86671d54 100644 --- a/utils/toa/toa_test.go +++ b/utils/toa/toa_test.go @@ -80,7 +80,6 @@ func TestComputeLoRa(t *testing.T) { } -// TODO: (@tftelkamp): Verify this func TestComputeFSK(t *testing.T) { a := New(t) toa, err := ComputeFSK(200, 50000) From dab01cd74b77a43f077c062f813a5a1387943954 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 15 Nov 2016 17:42:05 +0100 Subject: [PATCH 2162/2266] Expose Handler HTTP API on 8080 [dev] --- Procfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Procfile b/Procfile index 8dd4221ea..4e4e325c7 100644 --- a/Procfile +++ b/Procfile @@ -2,4 +2,4 @@ rtr: ttn router --config ./.env/router/dev.yml --health-port 10901 dsc: ttn discovery --config ./.env/discovery/dev.yml --health-port 10900 ns: ttn networkserver --config ./.env/networkserver/dev.yml --health-port 10903 brk: ttn broker --config ./.env/broker/dev.yml --health-port 10902 -hdl: ttn handler --config ./.env/handler/dev.yml --health-port 10904 +hdl: ttn handler --config ./.env/handler/dev.yml --health-port 10904 --http-port 8080 From 193793c363821e084a5417187de94723052d019e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 15 Nov 2016 17:55:34 +0100 Subject: [PATCH 2163/2266] Add component health check to ttnctl --- api/health/client.go | 19 +++++++++ ttnctl/cmd/components_check.go | 74 ++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 api/health/client.go create mode 100644 ttnctl/cmd/components_check.go diff --git a/api/health/client.go b/api/health/client.go new file mode 100644 index 000000000..b8d2e043b --- /dev/null +++ b/api/health/client.go @@ -0,0 +1,19 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package health + +import ( + "golang.org/x/net/context" + "google.golang.org/grpc" + healthpb "google.golang.org/grpc/health/grpc_health_v1" +) + +// Check the health of a connection +func Check(conn *grpc.ClientConn) (bool, error) { + res, err := healthpb.NewHealthClient(conn).Check(context.Background(), &healthpb.HealthCheckRequest{}) + if err != nil { + return false, err + } + return res.Status == healthpb.HealthCheckResponse_SERVING, nil +} diff --git a/ttnctl/cmd/components_check.go b/ttnctl/cmd/components_check.go new file mode 100644 index 000000000..5f6c7b8fe --- /dev/null +++ b/ttnctl/cmd/components_check.go @@ -0,0 +1,74 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/health" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/spf13/cobra" +) + +var checkCmd = &cobra.Command{ + Use: "check [ServiceType] [ServiceID]", + Short: "Check routing services", + Long: `ttnctl components check is used to check the status of routing services`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + return + } + + serviceType := args[0] + switch serviceType { + case "router", "broker", "handler": + default: + ctx.Fatalf("Service type %s unknown", serviceType) + } + + serviceID := args[1] + if !api.ValidID(serviceID) { + ctx.Fatalf("Service ID %s invalid", serviceID) + } + + dscConn, client := util.GetDiscovery(ctx) + defer dscConn.Close() + + res, err := client.Get(util.GetContext(ctx), &discovery.GetRequest{ + ServiceName: serviceType, + Id: serviceID, + }) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not get %s %s", serviceType, serviceID) + } + + conn, err := res.Dial() + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not dial %s %s", serviceType, serviceID) + } + defer conn.Close() + + start := time.Now() + ok, err := health.Check(conn) + if err != nil { + ctx.WithError(errors.FromGRPCError(err)).Fatalf("Could not check %s %s", serviceType, serviceID) + } + ctx = ctx.WithField("Duration", time.Now().Sub(start)) + + if ok { + ctx.Infof("%s %s is up and running", serviceType, serviceID) + } else { + ctx.Warnf("%s %s is not feeling well", serviceType, serviceID) + } + + }, +} + +func init() { + componentsCmd.AddCommand(checkCmd) +} From cf8f05a3a5eb61296b7f05ae8656c70198a727c8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 15 Nov 2016 18:01:34 +0100 Subject: [PATCH 2164/2266] Add more details to gRPC stream logs --- core/component/grpc.go | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/core/component/grpc.go b/core/component/grpc.go index a005b22a7..0cfeb57e7 100644 --- a/core/component/grpc.go +++ b/core/component/grpc.go @@ -3,7 +3,6 @@ package component import ( "time" - "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/mwitkow/go-grpc-middleware" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" @@ -37,10 +36,9 @@ func (c *Component) ServerOptions() []grpc.ServerOption { iface, err := handler(ctx, req) logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) if err != nil { - err := errors.FromGRPCError(err) - logCtx.WithField("error", err.Error()).Warn("Could not handle Request") + logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Warn("Could not handle Request") } else { - logCtx.Info("Handled request") + logCtx.Debug("Handled request") } return iface, err } @@ -59,12 +57,22 @@ func (c *Component) ServerOptions() []grpc.ServerOption { peerID = id[0] } } - c.Ctx.WithFields(log.Fields{ + logCtx := c.Ctx.WithFields(log.Fields{ "CallerID": peerID, "CallerIP": peerAddr, "Method": info.FullMethod, - }).Info("Start stream") - return handler(srv, stream) + }) + t := time.Now() + logCtx.Debug("Start stream") + err := handler(srv, stream) + logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) + switch err { + case nil, context.Canceled: + logCtx.Debug("End stream") + default: + logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Warn("End stream") + } + return err } opts := []grpc.ServerOption{ From 82611df3a6cc18dc16422db5fca1e4c4ee4e853c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 16 Nov 2016 09:49:47 +0100 Subject: [PATCH 2165/2266] Include service version in gRPC contexts --- core/component/auth.go | 4 +++- ttnctl/util/context.go | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/component/auth.go b/core/component/auth.go index f5886e3f7..f383f88c7 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -144,17 +144,19 @@ func (c *Component) BuildJWT() (string, error) { // GetContext returns a context for outgoing RPC request. If token is "", this function will generate a short lived token from the component func (c *Component) GetContext(token string) context.Context { - var serviceName, id, netAddress string + var serviceName, serviceVersion, id, netAddress string if c.Identity != nil { serviceName = c.Identity.ServiceName id = c.Identity.Id if token == "" { token, _ = c.BuildJWT() } + serviceVersion = c.Identity.ServiceVersion netAddress = c.Identity.NetAddress } md := metadata.Pairs( "service-name", serviceName, + "service-version", serviceVersion, "id", id, "token", token, "net-address", netAddress, diff --git a/ttnctl/util/context.go b/ttnctl/util/context.go index 65dd8cbf9..99bdfcbda 100644 --- a/ttnctl/util/context.go +++ b/ttnctl/util/context.go @@ -4,10 +4,12 @@ package util import ( + "fmt" "os" "os/user" "github.com/apex/log" + "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc/metadata" ) @@ -33,6 +35,7 @@ func GetContext(ctx log.Interface, extraPairs ...string) context.Context { md := metadata.Pairs( "id", GetID(), "service-name", "ttnctl", + "service-version", fmt.Sprintf("%s-%s (%s)", viper.GetString("version"), viper.GetString("gitCommit"), viper.GetString("buildDate")), "token", token.AccessToken, ) return metadata.NewContext(context.Background(), md) From bc39e0c555f5c0c0d33ad0534b33497059e6d384 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 16 Nov 2016 11:38:44 +0100 Subject: [PATCH 2166/2266] Wait for gateway streams to close before closing router connection --- api/router/client.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/api/router/client.go b/api/router/client.go index 5f00a7dd4..44d54fdc1 100644 --- a/api/router/client.go +++ b/api/router/client.go @@ -7,6 +7,8 @@ import ( "io" "sync" + "time" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/gateway" @@ -58,13 +60,14 @@ func (c *Client) ForGateway(gatewayID string, tokenFunc func() string) GatewayCl return gatewayClient } -// Close purges the cache and closes the connection with the Router +// Close closes all gateway connections and then closes the connection with the Router func (c *Client) Close() error { defer c.mutex.Unlock() c.mutex.Lock() for _, gateway := range c.gateways { gateway.Close() } + time.Sleep(200 * time.Millisecond) return c.conn.Close() } From 859b3fda30709cc6d4a6f255e69fb42a6fa785af Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 16 Nov 2016 11:39:29 +0100 Subject: [PATCH 2167/2266] Add ttnctl join command --- ttnctl/cmd/join.go | 135 +++++++++++++++++++++++++++++++++++++++++++ ttnctl/cmd/root.go | 3 + ttnctl/cmd/uplink.go | 14 ++--- 3 files changed, 145 insertions(+), 7 deletions(-) create mode 100644 ttnctl/cmd/join.go diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go new file mode 100644 index 000000000..d2af980d3 --- /dev/null +++ b/ttnctl/cmd/join.go @@ -0,0 +1,135 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "time" + + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/types" + "github.com/TheThingsNetwork/ttn/ttnctl/util" + "github.com/TheThingsNetwork/ttn/utils/otaa" + "github.com/apex/log" + "github.com/brocaar/lorawan" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var joinCmd = &cobra.Command{ + Hidden: true, + Use: "join [AppEUI] [DevEUI] [AppKey] [DevNonce]", + Short: "Simulate an join message to the network", + Long: `ttnctl join simulates an join message to the network`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 4 { + cmd.UsageFunc()(cmd) + return + } + + appEUI, err := types.ParseAppEUI(args[0]) + if err != nil { + ctx.WithError(err).Fatal("Could not parse AppEUI") + } + + devEUI, err := types.ParseDevEUI(args[1]) + if err != nil { + ctx.WithError(err).Fatal("Could not parse DevEUI") + } + + appKey, err := types.ParseAppKey(args[2]) + if err != nil { + ctx.WithError(err).Fatal("Could not parse AppKey") + } + + devNonceSlice, err := types.ParseHEX(args[3], 2) + if err != nil { + ctx.WithError(err).Fatal("Could not parse DevNonce") + } + devNonce := [2]byte{devNonceSlice[0], devNonceSlice[1]} + + rtrClient := util.GetRouter(ctx) + defer rtrClient.Close() + + gatewayID := viper.GetString("gateway-id") + gatewayToken := viper.GetString("gateway-token") + + if gatewayID != "dev" { + account := util.GetAccount(ctx) + token, err := account.GetGatewayToken(gatewayID) + if err != nil { + ctx.WithError(err).Warn("Could not get gateway token") + ctx.Warn("Trying without token. Your message may not be processed by the router") + gatewayToken = "" + } else if token != nil && token.Token != "" { + gatewayToken = token.Token + } + } + + gtwClient := rtrClient.ForGateway(gatewayID, func() string { return gatewayToken }) + defer gtwClient.Close() + + downlink, errChan, err := gtwClient.Subscribe() + if err != nil { + ctx.WithError(err).Fatal("Could not start downlink stream") + } + + joinReq := &pb_lorawan.Message{ + MHDR: pb_lorawan.MHDR{MType: pb_lorawan.MType_JOIN_REQUEST, Major: pb_lorawan.Major_LORAWAN_R1}, + Payload: &pb_lorawan.Message_JoinRequestPayload{JoinRequestPayload: &pb_lorawan.JoinRequestPayload{ + AppEui: appEUI, + DevEui: devEUI, + DevNonce: types.DevNonce(devNonce), + }}} + joinPhy := joinReq.PHYPayload() + joinPhy.SetMIC(lorawan.AES128Key(appKey)) + bytes, _ := joinPhy.MarshalBinary() + + uplink := &router.UplinkMessage{ + Payload: bytes, + GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), + ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), + } + uplink.UnmarshalPayload() + + err = gtwClient.SendUplink(uplink) + if err != nil { + ctx.WithError(err).Fatal("Could not send uplink to Router") + } + + time.Sleep(100 * time.Millisecond) + + ctx.Info("Sent uplink to Router") + + select { + case err := <-errChan: + ctx.WithError(err).Fatal("Error in downlink") + case downlinkMessage := <-downlink: + + downlinkMessage.UnmarshalPayload() + resPhy := downlinkMessage.Message.GetLorawan().PHYPayload() + resPhy.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) + res := pb_lorawan.MessageFromPHYPayload(resPhy) + accept := res.GetJoinAcceptPayload() + + appSKey, nwkSKey, _ := otaa.CalculateSessionKeys(appKey, accept.AppNonce, accept.NetId, devNonce) + + ctx.WithFields(log.Fields{ + "DevAddr": accept.DevAddr, + "NwkSKey": nwkSKey, + "AppSKey": appSKey, + }).Info("Received JoinAccept") + case <-time.After(6 * time.Second): + ctx.Info("Did not receive downlink") + } + + }, +} + +func init() { + RootCmd.AddCommand(joinCmd) + + joinCmd.Flags().String("gateway-id", "", "The ID of the gateway that you are faking (you can only fake gateways that you own)") + viper.BindPFlag("gateway-id", joinCmd.Flags().Lookup("gateway-id")) +} diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index be4a6b0ae..7a2bf23c5 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -89,6 +89,9 @@ func init() { RootCmd.PersistentFlags().String("auth-server", "https://account.thethingsnetwork.org", "The address of the OAuth 2.0 server") viper.BindPFlag("auth-server", RootCmd.PersistentFlags().Lookup("auth-server")) + + viper.SetDefault("gateway-id", "dev") + viper.SetDefault("gateway-token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImdhdGV3YXkiLCJpYXQiOjE0NzY0Mzk0Mzh9.kEOiLe9j4qRElZOt_bAXmZlva1nV6duIL0MDVa3bx2SEWC3qredaBWXWq4FmV4PKeI_zndovQtOoValH0B_6MW6vXuWL1wYzV6foTH5gQdxmn-iuQ1AmAIYbZeyHl9a-NPqDgkXLwKmo2iB1hUi9wV6HXfIOalguDtGJbmMfJ2tommsgmuNCXd-2zqhStSy8ArpROFXPm7voGDTcgm4hfchr7zhn-Er76R-eJa3RZ1Seo9BsiWrQ0N3VDSuh7ycCakZtkaLD4OTutAemcbzbrNJSOCvvZr8Asn-RmMkjKUdTN4Bgn3qlacIQ9iZikPLT8XyjFkj-8xjs3KAobWg40A") } func printKV(key, t interface{}) { diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index a6f7c1d74..38942681c 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -12,6 +12,7 @@ import ( "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" + "github.com/spf13/viper" ) var uplinkCmd = &cobra.Command{ @@ -60,14 +61,10 @@ var uplinkCmd = &cobra.Command{ rtrClient := util.GetRouter(ctx) defer rtrClient.Close() - gatewayID, _ := cmd.Flags().GetString("gateway-id") - var gatewayToken string + gatewayID := viper.GetString("gateway-id") + gatewayToken := viper.GetString("gateway-token") - if gatewayID == "" { - gatewayID = "dev" - // This token is valid for the "dev" gateway: - gatewayToken = `eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJ0dG4tYWNjb3VudC1wcmV2aWV3Iiwic3ViIjoiZGV2IiwidHlwZSI6ImdhdGV3YXkiLCJpYXQiOjE0NzY0Mzk0Mzh9.kEOiLe9j4qRElZOt_bAXmZlva1nV6duIL0MDVa3bx2SEWC3qredaBWXWq4FmV4PKeI_zndovQtOoValH0B_6MW6vXuWL1wYzV6foTH5gQdxmn-iuQ1AmAIYbZeyHl9a-NPqDgkXLwKmo2iB1hUi9wV6HXfIOalguDtGJbmMfJ2tommsgmuNCXd-2zqhStSy8ArpROFXPm7voGDTcgm4hfchr7zhn-Er76R-eJa3RZ1Seo9BsiWrQ0N3VDSuh7ycCakZtkaLD4OTutAemcbzbrNJSOCvvZr8Asn-RmMkjKUdTN4Bgn3qlacIQ9iZikPLT8XyjFkj-8xjs3KAobWg40A` - } else { + if gatewayID != "dev" { account := util.GetAccount(ctx) token, err := account.GetGatewayToken(gatewayID) if err != nil { @@ -80,6 +77,7 @@ var uplinkCmd = &cobra.Command{ } gtwClient := rtrClient.ForGateway(gatewayID, func() string { return gatewayToken }) + defer gtwClient.Close() var downlink <-chan *router.DownlinkMessage var errChan <-chan error @@ -132,5 +130,7 @@ func init() { RootCmd.AddCommand(uplinkCmd) uplinkCmd.Flags().Bool("downlink", false, "Also start downlink (unstable)") uplinkCmd.Flags().Bool("confirmed", false, "Use confirmed uplink (this also sets --downlink)") + uplinkCmd.Flags().String("gateway-id", "", "The ID of the gateway that you are faking (you can only fake gateways that you own)") + viper.BindPFlag("gateway-id", uplinkCmd.Flags().Lookup("gateway-id")) } From 7ad15e93d3bba56bdae4c273c937972dbe18efc5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 16 Nov 2016 15:32:31 +0100 Subject: [PATCH 2168/2266] Add proto documentation for ApplicationManager --- api/handler/handler.proto | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 3d0128203..4685e61cd 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -44,11 +44,24 @@ message ApplicationIdentifier { string app_id = 1; } +// The Application settings message Application { string app_id = 1; + + // The decoder is a JavaScript function that decodes a byte array to an object. string decoder = 2; + + // The converter is a JavaScript function that can be used to convert values + // in the object returned from the decoder. This can for example be useful to + // convert a voltage to a temperature. string converter = 3; + + // The validator is a JavaScript function that checks the validity of the + // object returned by the decoder or converter. If validation fails, the + // message is dropped. string validator = 4; + + // The encoder is a JavaScript function that encodes an object to a byte array. string encoder = 5; } @@ -57,9 +70,12 @@ message DeviceIdentifier { string dev_id = 2; } +// The Device settings message Device { string app_id = 1; string dev_id = 2; + + // The device can be of different kinds oneof device { lorawan.Device lorawan_device = 3; } @@ -69,16 +85,25 @@ message DeviceList { repeated Device devices = 1; } +// DryDownlinkMessage is a simulated message to test downlink processing message DryDownlinkMessage { + // The binary payload to use bytes payload = 1; + // JSON-encoded object with fields to encode string fields = 2; + // The Application containing the payload functions that should be executed Application app = 3; + // The port number that should be passed to the payload function uint32 port = 4; } +// DryUplinkMessage is a simulated message to test uplink processing message DryUplinkMessage { + // The binary payload to use bytes payload = 1; + // The Application containing the payload functions that should be executed Application app = 2; + // The port number that should be passed to the payload function uint32 port = 3; } @@ -90,46 +115,67 @@ message LogEntry { repeated string fields = 2; } +// DryUplinkResult is the result from an uplink simulation message DryUplinkResult { + // The binary payload bytes payload = 1; + // The decoded fields string fields = 2; + // Was validation of the message successful bool valid = 3; + // Logs that have been generated while processing repeated LogEntry logs = 4; } +// DryDownlinkResult is the result from a downlink simulation message DryDownlinkResult { + // The payload that was encoded bytes payload = 1; + // Logs that have been generated while processing repeated LogEntry logs = 2; } +// ApplicationManager manages application and device registrations on the Handler service ApplicationManager { + + // Applications should first be registered to the Handler with the `RegisterApplication` method rpc RegisterApplication(ApplicationIdentifier) returns (google.protobuf.Empty) { option (google.api.http) = { post: "/applications" body: "*" }; } + + // GetApplication returns the application with the given identifier (app_id) rpc GetApplication(ApplicationIdentifier) returns (Application) { option (google.api.http) = { get: "/applications/{app_id}" }; } + + // SetApplication updates the settings for the application. All fields must be supplied. rpc SetApplication(Application) returns (google.protobuf.Empty) { option (google.api.http) = { post: "/applications/{app_id}" body: "*" }; } + + // DeleteApplication deletes the application with the given identifier (app_id) rpc DeleteApplication(ApplicationIdentifier) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/applications/{app_id}" }; } + + // GetDevice returns the device with the given identifier (app_id and dev_id) rpc GetDevice(DeviceIdentifier) returns (Device) { option (google.api.http) = { get: "/applications/{app_id}/devices/{dev_id}" }; } + + // SetDevice creates or updates a device. All fields must be supplied. rpc SetDevice(Device) returns (google.protobuf.Empty) { option (google.api.http) = { post: "/applications/{app_id}/devices/{dev_id}" @@ -140,17 +186,25 @@ service ApplicationManager { } }; } + + // DeleteDevice deletes the device with the given identifier (app_id and dev_id) rpc DeleteDevice(DeviceIdentifier) returns (google.protobuf.Empty) { option (google.api.http) = { delete: "/applications/{app_id}/devices/{dev_id}" }; } + + // GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) rpc GetDevicesForApplication(ApplicationIdentifier) returns (DeviceList) { option (google.api.http) = { get: "/applications/{app_id}/devices" }; } + + // DryUplink simulates processing an uplink message and returns the result rpc DryDownlink(DryDownlinkMessage) returns (DryDownlinkResult); + + // DryUplink simulates processing a downlink message and returns the result rpc DryUplink(DryUplinkMessage) returns (DryUplinkResult); } From f15ba1f73cf85d6898598292d1b6357c6e81c339 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 08:32:28 +0100 Subject: [PATCH 2169/2266] Use net/url instead of AuthServerRegex --- core/component/auth.go | 29 ++++++++++------------------- core/component/auth_test.go | 5 +++++ 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/core/component/auth.go b/core/component/auth.go index f383f88c7..aad3c59a9 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -3,7 +3,7 @@ package component import ( "crypto/tls" "fmt" - "regexp" + "net/url" "time" "github.com/TheThingsNetwork/go-account-lib/cache" @@ -46,27 +46,18 @@ type authServer struct { } func parseAuthServer(str string) (srv authServer, err error) { - matches := AuthServerRegex.FindStringSubmatch(str) - if len(matches) != 5 || matches[4] == "" { - return srv, ErrNoAuthServerRegexMatch + url, err := url.Parse(str) + if err != nil { + return srv, err + } + srv.url = fmt.Sprintf("%s://%s", url.Scheme, url.Host) + if url.User != nil { + srv.username = url.User.Username() + srv.password, _ = url.User.Password() } - return authServer{ - url: matches[1] + matches[4], - username: matches[2], - password: matches[3], - }, nil + return srv, nil } -// AuthServerRegex gives the format of auth server configuration. -// Format: [username[:password]@]domain -// - usernames can contain lowercase letters, numbers, underscores and dashes -// - passwords can contain uppercase and lowercase letters, numbers, and special characters -// - domains can be http/https and can contain lowercase letters, numbers, dashes and dots -var AuthServerRegex = regexp.MustCompile(`^(http[s]?://)(?:([0-9a-z_-]+)(?::([0-9A-Za-z-!"#$%&'()*+,.:;<=>?@[\]^_{|}~]+))?@)?([0-9a-z.-]+)/?$`) - -// ErrNoAuthServerRegexMatch is returned when an auth server -var ErrNoAuthServerRegexMatch = errors.New("Account server did not match AuthServerRegex") - func (c *Component) initAuthServers() error { urlMap := make(map[string]string) for id, url := range c.Config.AuthServers { diff --git a/core/component/auth_test.go b/core/component/auth_test.go index 58204408f..d9bd483b3 100644 --- a/core/component/auth_test.go +++ b/core/component/auth_test.go @@ -37,6 +37,11 @@ func TestParseAuthServer(t *testing.T) { a.So(err, assertions.ShouldBeNil) a.So(srv.url, assertions.ShouldEqual, "http://account.thethingsnetwork.org") } + { + srv, err := parseAuthServer("http://localhost:9090/") + a.So(err, assertions.ShouldBeNil) + a.So(srv.url, assertions.ShouldEqual, "http://localhost:9090") + } } func TestInitAuthServers(t *testing.T) { From 42dde38546eb4d45ab4d7fed93af475795a039d1 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 11:49:20 +0100 Subject: [PATCH 2170/2266] Add proto documentation for LoRaWAN devices --- api/protocol/lorawan/device.proto | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/api/protocol/lorawan/device.proto b/api/protocol/lorawan/device.proto index 09d3012c6..18d686525 100644 --- a/api/protocol/lorawan/device.proto +++ b/api/protocol/lorawan/device.proto @@ -11,28 +11,45 @@ package lorawan; option go_package = "github.com/TheThingsNetwork/ttn/api/protocol/lorawan"; message DeviceIdentifier { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + // The DevEUI is a unique, 8 byte identifier for the device. bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; } message Device { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. bytes app_eui = 1 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppEUI"]; + // The DevEUI is a unique, 8 byte identifier for the device. bytes dev_eui = 2 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevEUI"]; + // The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. string app_id = 3; + // The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. string dev_id = 4; + // The DevAddr is a dynamic, 4 byte session address for the device. bytes dev_addr = 5 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.DevAddr"]; + // The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. bytes nwk_s_key = 6 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.NwkSKey"]; + // The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. bytes app_s_key = 7 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppSKey"]; + // The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). bytes app_key = 8 [(gogoproto.customtype) = "github.com/TheThingsNetwork/ttn/core/types.AppKey"]; + // FCntUp is the uplink frame counter for a device session. uint32 f_cnt_up = 9; + // FCntDown is the downlink frame counter for a device session. uint32 f_cnt_down = 10; - // Options + // The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. bool disable_f_cnt_check = 11; + // The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. bool uses32_bit_f_cnt = 12; + // The ActivationContstraints are used to allocate a device address for a device. + // There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. string activation_constraints = 13; - // Other + // When the device was last seen (Unix nanoseconds) int64 last_seen = 21; } From eb8cf88c17d4bc6cf9be9c970c9a0ebf344183bc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 12:09:50 +0100 Subject: [PATCH 2171/2266] Determine paths only once --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 33818baa7..ec5f8d260 100644 --- a/Makefile +++ b/Makefile @@ -5,9 +5,9 @@ SHELL = bash GIT_BRANCH = $(or $(CI_BUILD_REF_NAME) ,`git rev-parse --abbrev-ref HEAD 2>/dev/null`) GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) -GO_PATH = `echo $(GOPATH) | awk -F':' '{print $$1}'` -PARENT_DIRECTORY= `dirname $(PWD)` -GO_SRC = `pwd | xargs dirname | xargs dirname | xargs dirname` +GO_PATH = $(shell echo $(GOPATH) | awk -F':' '{print $$1}') +PARENT_DIRECTORY= $(shell dirname $(PWD)) +GO_SRC = $(shell pwd | xargs dirname | xargs dirname | xargs dirname) # All From 6398799239900d04c33c30fc2d0843a45da18aaf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 12:11:47 +0100 Subject: [PATCH 2172/2266] Initial implementation of proto doc generator --- utils/protoc-gen-ttndoc/README.md | 15 ++ utils/protoc-gen-ttndoc/build_tree.go | 184 +++++++++++++++ utils/protoc-gen-ttndoc/main.go | 323 ++++++++++++++++++++++++++ utils/protoc-gen-ttndoc/types.go | 158 +++++++++++++ vendor/vendor.json | 12 + 5 files changed, 692 insertions(+) create mode 100644 utils/protoc-gen-ttndoc/README.md create mode 100644 utils/protoc-gen-ttndoc/build_tree.go create mode 100644 utils/protoc-gen-ttndoc/main.go create mode 100644 utils/protoc-gen-ttndoc/types.go diff --git a/utils/protoc-gen-ttndoc/README.md b/utils/protoc-gen-ttndoc/README.md new file mode 100644 index 000000000..408faa757 --- /dev/null +++ b/utils/protoc-gen-ttndoc/README.md @@ -0,0 +1,15 @@ +# protoc-gen-ttndoc + +Generate docs for TTN API + +## Installation + +``` +go install +``` + +## Usage + +``` +protoc -I/usr/local/include -I$GOPATH/src -I$GOPATH/src/github.com/TheThingsNetwork -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --ttndoc_out=logtostderr=true,.handler.ApplicationManager=all:. $GOPATH/src/github.com/TheThingsNetwork/ttn/api/handler/handler.proto +``` diff --git a/utils/protoc-gen-ttndoc/build_tree.go b/utils/protoc-gen-ttndoc/build_tree.go new file mode 100644 index 000000000..a03711a3d --- /dev/null +++ b/utils/protoc-gen-ttndoc/build_tree.go @@ -0,0 +1,184 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strconv" + "strings" + + protobuf "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/descriptor" + gateway "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" +) + +func buildTree(files []*descriptor.FileDescriptorProto) *tree { + tree := &tree{ + services: make(map[string]*service), + methods: make(map[string]*method), + messages: make(map[string]*message), + fields: make(map[string]*field), + enums: make(map[string]*enum), + } + for _, file := range files { + fillTreeWithFile(tree, file) + } + return tree +} + +func fillTreeWithFile(tree *tree, file *descriptor.FileDescriptorProto) { + key := fmt.Sprintf(".%s", file.GetPackage()) + locs := make(map[string]*descriptor.SourceCodeInfo_Location) + for _, loc := range file.GetSourceCodeInfo().GetLocation() { + if loc.LeadingComments == nil { + continue + } + var p []string + for _, n := range loc.Path { + p = append(p, strconv.Itoa(int(n))) + } + locs[strings.Join(p, ",")] = loc + } + + // Messages + for idx, proto := range file.GetMessageType() { + fillTreeWithMessage(tree, key, proto, fmt.Sprintf("4,%d", idx), locs) + } + + // Enums + for idx, proto := range file.GetEnumType() { + fillTreeWithEnum(tree, key, proto, fmt.Sprintf("5,%d", idx), locs) + } + + // Services + for idx, proto := range file.GetService() { + fillTreeWithService(tree, key, proto, fmt.Sprintf("6,%d", idx), locs) + } +} + +func fillTreeWithService(tree *tree, key string, proto *descriptor.ServiceDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *service { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + tree.services[key] = &service{key: key, comment: getComment(loc, locs), ServiceDescriptorProto: proto} + + // Methods + for idx, proto := range proto.GetMethod() { + method := fillTreeWithMethod(tree, key, proto, fmt.Sprintf("%s,2,%d", loc, idx), locs) + tree.services[key].methods = append(tree.services[key].methods, method) + } + + return tree.services[key] +} + +func fillTreeWithMethod(tree *tree, key string, proto *descriptor.MethodDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *method { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + tree.methods[key] = &method{key: key, comment: getComment(loc, locs), MethodDescriptorProto: proto} + if input, ok := tree.messages[proto.GetInputType()]; ok { + tree.methods[key].input = input + } + if proto.GetClientStreaming() { + tree.methods[key].inputStream = true + } + if output, ok := tree.messages[proto.GetOutputType()]; ok { + tree.methods[key].output = output + } + if proto.GetServerStreaming() { + tree.methods[key].outputStream = true + } + if proto.Options != nil && protobuf.HasExtension(proto.Options, gateway.E_Http) { + ext, err := protobuf.GetExtension(proto.Options, gateway.E_Http) + if err == nil { + if opts, ok := ext.(*gateway.HttpRule); ok { + if endpoint := newEndpoint(opts); endpoint != nil { + tree.methods[key].endpoints = append(tree.methods[key].endpoints, endpoint) + } + for _, opts := range opts.AdditionalBindings { + if endpoint := newEndpoint(opts); endpoint != nil { + tree.methods[key].endpoints = append(tree.methods[key].endpoints, endpoint) + } + } + } + } + } + return tree.methods[key] +} + +func fillTreeWithMessage(tree *tree, key string, proto *descriptor.DescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *message { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + tree.messages[key] = &message{key: key, comment: getComment(loc, locs), DescriptorProto: proto} + + // Oneofs + for idx, proto := range proto.GetOneofDecl() { + tree.messages[key].oneofs = append(tree.messages[key].oneofs, &oneof{ + index: int32(idx), + OneofDescriptorProto: proto, + }) + } + + // Fields + for idx, proto := range proto.GetField() { + field := fillTreeWithField(tree, key, proto, fmt.Sprintf("%s,2,%d", loc, idx), locs) + tree.messages[key].fields = append(tree.messages[key].fields, field) + } + + // Nested + for idx, proto := range proto.GetNestedType() { + message := fillTreeWithMessage(tree, key, proto, fmt.Sprintf("%s,3,%d", loc, idx), locs) + tree.messages[key].nested = append(tree.messages[key].nested, message) + } + + // Enums + for idx, proto := range proto.GetEnumType() { + fillTreeWithEnum(tree, key, proto, fmt.Sprintf("%s,4,%d", loc, idx), locs) + } + + return tree.messages[key] +} + +func fillTreeWithField(tree *tree, parent string, proto *descriptor.FieldDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *field { + key := fmt.Sprintf("%s.%s", parent, proto.GetName()) + tree.fields[key] = &field{key: key, comment: getComment(loc, locs), FieldDescriptorProto: proto} + if proto.GetLabel() == descriptor.FieldDescriptorProto_LABEL_REPEATED { + tree.fields[key].repeated = true + } + if proto.OneofIndex != nil { + if parent, ok := tree.messages[parent]; ok { + for _, oneof := range parent.oneofs { + if oneof.index == proto.GetOneofIndex() { + oneof.fields = append(oneof.fields, tree.fields[key]) + tree.fields[key].isOneOf = true + } + } + } + } + return tree.fields[key] +} + +func fillTreeWithEnum(tree *tree, key string, proto *descriptor.EnumDescriptorProto, loc string, locs map[string]*descriptor.SourceCodeInfo_Location) *enum { + key = fmt.Sprintf("%s.%s", key, proto.GetName()) + + tree.enums[key] = &enum{key: key, comment: getComment(loc, locs), EnumDescriptorProto: proto} + + // Values + for idx, proto := range proto.GetValue() { + tree.enums[key].values = append(tree.enums[key].values, &enumValue{ + getComment(fmt.Sprintf("%s,2,%d", loc, idx), locs), + proto, + }) + } + + return tree.enums[key] +} + +func getComment(loc string, locs map[string]*descriptor.SourceCodeInfo_Location) (comment string) { + if loc, ok := locs[loc]; ok { + var lines []string + for _, line := range strings.Split(strings.TrimSuffix(loc.GetLeadingComments(), "\n"), "\n") { + line = strings.TrimPrefix(line, " ") + line = strings.Replace(line, "```", "", -1) + lines = append(lines, line) + } + return strings.Join(lines, "\n") + } + return "" +} diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go new file mode 100644 index 000000000..4e70662ac --- /dev/null +++ b/utils/protoc-gen-ttndoc/main.go @@ -0,0 +1,323 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "os" + "path" + "sort" + "strings" + + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/protoc-gen-go/generator" + plugin "github.com/golang/protobuf/protoc-gen-go/plugin" +) + +func debug(msgs ...string) { + if logtostderr { + s := strings.Join(msgs, " ") + fmt.Fprintln(os.Stderr, "protoc-gen-ttndoc: ", s) + } +} + +func failWithError(err error, msgs ...string) { + s := strings.Join(msgs, " ") + ":" + err.Error() + log.Print("protoc-gen-ttndoc: error:", s) + os.Exit(1) +} + +func fail(msgs ...string) { + s := strings.Join(msgs, " ") + log.Print("protoc-gen-ttndoc: fail:", s) + os.Exit(1) +} + +var logtostderr bool + +// Read from standard input +func main() { + g := generator.New() + + data, err := ioutil.ReadAll(os.Stdin) + if err != nil { + failWithError(err, "reading input") + } + + if err := proto.Unmarshal(data, g.Request); err != nil { + failWithError(err, "parsing input proto") + } + + if len(g.Request.FileToGenerate) == 0 { + fail("no files to generate") + } + + g.CommandLineParameters(g.Request.GetParameter()) + + if val, ok := g.Param["logtostderr"]; ok && val == "true" { + logtostderr = true + } + + tree := buildTree(g.Request.GetProtoFile()) + + packageLocations := make(map[string]string) + for _, file := range g.Request.GetProtoFile() { + loc := file.GetName()[:strings.LastIndex(file.GetName(), "/")] + packageLocations[file.GetPackage()] = loc + } + + selectedServices := make(map[string]string) + for k, v := range g.Param { + switch { + case k == "logtostderr": + if v == "true" { + logtostderr = true + } + case strings.HasPrefix(k, "."): + selectedServices[k] = v + } + } + + for serviceKey := range selectedServices { + service, ok := tree.services[serviceKey] + if !ok { + fail("Service", serviceKey, "unknown") + } + + usedMessages := make(map[string]*message) + usedEnums := make(map[string]*enum) + + content := new(bytes.Buffer) + + fmt.Fprintf(content, "## %s\n\n", service.GetName()) + if service.comment != "" { + fmt.Fprintf(content, "%s\n\n", service.comment) + } + + for _, method := range service.methods { + fmt.Fprintf(content, "### %s\n\n", method.GetName()) + if method.comment != "" { + fmt.Fprintf(content, "%s\n\n", method.comment) + } + if method.inputStream { + fmt.Fprintf(content, "- Client stream of [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) + } else { + fmt.Fprintf(content, "- Request: [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) + } + useMessage(tree, method.input, usedMessages, usedEnums) + if method.outputStream { + fmt.Fprintf(content, "- Server stream of [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) + } else { + fmt.Fprintf(content, "- Response: [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) + } + useMessage(tree, method.output, usedMessages, usedEnums) + + fmt.Fprintln(content) + + if len(method.endpoints) != 0 { + if len(method.endpoints) == 1 { + fmt.Fprint(content, "#### HTTP Endpoint\n\n") + } else { + fmt.Fprint(content, "#### HTTP Endpoints\n\n") + } + for _, endpoint := range method.endpoints { + fmt.Fprintf(content, "- `%s` `%s`\n", endpoint.method, endpoint.url) + } + fmt.Fprintln(content) + } + } + + fmt.Fprint(content, "## Used Messages\n\n") + + var messageKeys []string + for key := range usedMessages { + messageKeys = append(messageKeys, key) + } + sort.Strings(messageKeys) + + for _, messageKey := range messageKeys { + message := usedMessages[messageKey] + fmt.Fprintf(content, "### `%s`\n\n", message.key) + if strings.HasPrefix(messageKey, ".google") { + fmt.Fprintf(content, "%s\n\n", strings.SplitAfter(message.comment, ".")[0]) + } else if message.comment != "" { + fmt.Fprintf(content, "%s\n\n", message.comment) + } + if len(message.fields) > 0 { + fmt.Fprintln(content, "| Field Name | Type | Description |") + fmt.Fprintln(content, "| ---------- | ---- | ----------- |") + for idx, field := range message.fields { + if field.isOneOf { + if idx == 0 || !message.fields[idx-1].isOneOf || message.fields[idx-1].GetOneofIndex() != field.GetOneofIndex() { + oneof := message.GetOneof(field.GetOneofIndex()) + if len(oneof.fields) > 1 { + fmt.Fprintf(content, "| **%s** | **oneof %d** | one of the following %d |\n", oneof.GetName(), len(oneof.fields), len(oneof.fields)) + } + } + } else { + if idx > 0 && message.fields[idx-1].isOneOf { + oneof := message.GetOneof(message.fields[idx-1].GetOneofIndex()) + if len(oneof.fields) > 1 { + fmt.Fprintf(content, "| **%s** | **end oneof %d** | |\n", oneof.GetName(), len(oneof.fields)) + } + } + } + + var fieldType string + if field.repeated { + fieldType += "_repeated_ " + } + typ := strings.ToLower(strings.TrimPrefix(field.GetType().String(), "TYPE_")) + switch typ { + case "message": + friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] + fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) + case "enum": + // TODO(htdvisser): test this + if enum, ok := tree.enums[field.GetTypeName()]; ok { + usedEnums[field.GetTypeName()] = enum + } + friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] + fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) + default: + fieldType += fmt.Sprintf("`%s`", typ) + } + fmt.Fprintf(content, "| %s | %s | %s |\n", field.GetName(), fieldType, strings.Replace(field.comment, "\n", " ", -1)) + } + fmt.Fprintln(content) + } + } + + if len(usedEnums) > 0 { + fmt.Fprint(content, "## Used Enums\n\n") + + var enumKeys []string + for key := range usedEnums { + enumKeys = append(enumKeys, key) + } + sort.Strings(enumKeys) + + for _, enumKey := range enumKeys { + enum := usedEnums[enumKey] + + fmt.Fprintf(content, "### `%s`\n\n", enum.key) + if enum.comment != "" { + fmt.Fprintf(content, "%s\n\n", enum.comment) + } + + if len(enum.values) > 0 { + fmt.Fprintln(content, "| Value | Description |") + fmt.Fprintln(content, "| ----- | ----------- |") + for _, value := range enum.values { + fmt.Fprintf(content, "| %s | %s |\n", value.GetName(), value.comment) + } + fmt.Fprintln(content) + } + } + } + + packageService := strings.TrimPrefix(service.key, ".") + packageName := packageService[:strings.Index(packageService, ".")] + location, ok := packageLocations[packageName] + if !ok { + fail("Could not find location of package", packageName) + } + fileName := path.Join(location, service.GetName()+".md") + contentString := content.String() + g.Response.File = append(g.Response.File, &plugin.CodeGeneratorResponse_File{ + Name: &fileName, + Content: &contentString, + }) + } + + // for k, v := range tree.services { + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Service", k, v.GoString()) + // for _, v := range v.methods { + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Method", v.GoString()) + // for _, v := range v.endpoints { + // debug("Endpoint", v.GoString()) + // } + // } + // } + + // for k, v := range tree.messages { + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Message", k, v.GoString()) + // for _, v := range v.oneofs { + // debug("Oneof", v.GoString()) + // for _, v := range v.fields { + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Field", v.GoString()) + // } + // } + // for _, v := range v.fields { + // if v.isOneOf { + // continue + // } + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Field", v.GoString()) + // } + // } + + // for k, v := range tree.enums { + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Enum", k, v.GoString()) + // for _, v := range v.values { + // if v.comment != "" { + // debug("//", v.comment) + // } + // debug("Value", v.GoString()) + // } + // } + + // Send back the results. + data, err = proto.Marshal(g.Response) + if err != nil { + failWithError(err, "failed to marshal output proto") + } + _, err = os.Stdout.Write(data) + if err != nil { + failWithError(err, "failed to write output proto") + } +} + +func stringPtr(str string) *string { + return &str +} + +func heading(str string) string { + return strings.ToLower(strings.NewReplacer(".", "").Replace(str)) +} + +func useMessage(tree *tree, msg *message, messages map[string]*message, enums map[string]*enum) { + messages[msg.key] = msg + for _, msg := range msg.nested { + useMessage(tree, msg, messages, enums) + } + for _, field := range msg.fields { + if msg, ok := tree.messages[field.GetTypeName()]; ok { + useMessage(tree, msg, messages, enums) + } + if enum, ok := tree.enums[field.GetTypeName()]; ok { + enums[enum.key] = enum + } + } +} diff --git a/utils/protoc-gen-ttndoc/types.go b/utils/protoc-gen-ttndoc/types.go new file mode 100644 index 000000000..514efa3e4 --- /dev/null +++ b/utils/protoc-gen-ttndoc/types.go @@ -0,0 +1,158 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "fmt" + "strings" + + "github.com/golang/protobuf/protoc-gen-go/descriptor" + gateway "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" +) + +type service struct { + key string + comment string + *descriptor.ServiceDescriptorProto + methods []*method +} + +func (s *service) GoString() string { + return s.key +} + +type method struct { + key string + comment string + *descriptor.MethodDescriptorProto + input *message + inputStream bool + output *message + outputStream bool + endpoints []*endpoint +} + +func (m *method) GoString() string { + var inputType, outputType string + if m.inputStream { + inputType += "stream " + } + inputType += m.input.GoString() + + if m.outputStream { + outputType += "stream " + } + outputType += m.output.GoString() + return fmt.Sprintf("%s (%s) -> (%s)", m.key, inputType, outputType) +} + +type endpoint struct { + method string + url string +} + +func newEndpoint(opts *gateway.HttpRule) *endpoint { + if opts == nil { + return nil + } + switch opt := opts.GetPattern().(type) { + case *gateway.HttpRule_Get: + return &endpoint{"GET", opt.Get} + case *gateway.HttpRule_Put: + return &endpoint{"PUT", opt.Put} + case *gateway.HttpRule_Post: + return &endpoint{"POST", opt.Post} + case *gateway.HttpRule_Delete: + return &endpoint{"DELETE", opt.Delete} + case *gateway.HttpRule_Patch: + return &endpoint{"PATCH", opt.Patch} + } + return nil +} + +func (e *endpoint) GoString() string { + return fmt.Sprintf("%s %s", e.method, e.url) +} + +type message struct { + key string + comment string + *descriptor.DescriptorProto + fields []*field + nested []*message + oneofs []*oneof +} + +func (m *message) GoString() string { + return m.key +} + +func (m *message) GetOneof(idx int32) *oneof { + for _, oneof := range m.oneofs { + if oneof.index == idx { + return oneof + } + } + return nil +} + +type oneof struct { + index int32 + *descriptor.OneofDescriptorProto + fields []*field +} + +func (o *oneof) GoString() string { + return o.GetName() +} + +type field struct { + key string + comment string + repeated bool + isOneOf bool + *descriptor.FieldDescriptorProto +} + +func (f *field) GoString() string { + var fieldInfo string + if f.repeated { + fieldInfo += "repeated " + } + typ := strings.ToLower(strings.TrimPrefix(f.GetType().String(), "TYPE_")) + if typ == "message" { + fieldInfo += f.GetTypeName() + } else { + fieldInfo += typ + } + return fmt.Sprintf("%s (%s)", f.key, fieldInfo) +} + +type enumValue struct { + comment string + *descriptor.EnumValueDescriptorProto +} + +func (e *enumValue) GoString() string { + return e.GetName() +} + +type enum struct { + key string + comment string + *descriptor.EnumDescriptorProto + values []*enumValue +} + +func (e *enum) GoString() string { + return e.key +} + +type tree struct { + services map[string]*service + methods map[string]*method + messages map[string]*message + fields map[string]*field + enums map[string]*enum +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 2bc5e8154..5c50e2931 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -218,6 +218,18 @@ "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", "revisionTime": "2016-11-03T22:44:32Z" }, + { + "checksumSHA1": "lPJ5a2uV2CPHch++4zKkJ1au0sw=", + "path": "github.com/golang/protobuf/protoc-gen-go/generator", + "revision": "4bd1920723d7b7c925de087aa32e2187708897f7", + "revisionTime": "2016-11-09T07:27:36Z" + }, + { + "checksumSHA1": "zps2+aJoFhpFf2F8TsU9zCGXL2c=", + "path": "github.com/golang/protobuf/protoc-gen-go/plugin", + "revision": "4bd1920723d7b7c925de087aa32e2187708897f7", + "revisionTime": "2016-11-09T07:27:36Z" + }, { "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", From 1aafc684db51edd523e48df07619e371ac21477c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 12:12:25 +0100 Subject: [PATCH 2173/2266] Generate protodocs for handler.ApplicationManager --- Makefile | 14 +- api/handler/ApplicationManager.md | 222 ++++++++++++++++++++++++++++++ 2 files changed, 230 insertions(+), 6 deletions(-) create mode 100644 api/handler/ApplicationManager.md diff --git a/Makefile b/Makefile index ec5f8d260..ea33fb009 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ GO_SRC = $(shell pwd | xargs dirname | xargs dirname | xargs dirname) # All -.PHONY: all build-deps deps dev-deps protos-clean protos mocks test cover-clean cover-deps cover coveralls fmt vet ttn ttnctl build link docs clean docker +.PHONY: all build-deps deps dev-deps protos-clean protos protodoc mocks test cover-clean cover-deps cover coveralls fmt vet ttn ttnctl build link docs clean docker all: deps build @@ -26,6 +26,7 @@ deps: build-deps dev-deps: deps @command -v protoc-gen-gofast > /dev/null || go get github.com/gogo/protobuf/protoc-gen-gofast @command -v protoc-gen-grpc-gateway > /dev/null || go get github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway + @command -v protoc-gen-ttndoc > /dev/null || go install github.com/TheThingsNetwork/ttn/utils/protoc-gen-ttndoc @command -v mockgen > /dev/null || go get github.com/golang/mock/mockgen @command -v golint > /dev/null || go get github.com/golang/lint/golint @command -v forego > /dev/null || go get github.com/ddollar/forego @@ -34,11 +35,9 @@ dev-deps: deps PROTO_FILES = $(shell find api -name "*.proto" -and -not -name ".git") COMPILED_PROTO_FILES = $(patsubst api%.proto, api%.pb.go, $(PROTO_FILES)) -PROTOC = protoc \ --I/usr/local/include \ --I$(GO_PATH)/src \ --I$(PARENT_DIRECTORY) \ --I$(GO_PATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ +PROTOC_IMPORTS= -I/usr/local/include -I$(GO_PATH)/src -I$(PARENT_DIRECTORY) \ +-I$(GO_PATH)/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis +PROTOC = protoc $(PROTOC_IMPORTS) \ --gofast_out=Mgoogle/api/annotations.proto=github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api,plugins=grpc:$(GO_SRC) \ --grpc-gateway_out=:$(GO_SRC) `pwd`/ @@ -50,6 +49,9 @@ protos: $(COMPILED_PROTO_FILES) api/%.pb.go: api/%.proto $(PROTOC)$< +protodoc: $(PROTO_FILES) + protoc $(PROTOC_IMPORTS) --ttndoc_out=logtostderr=true,.handler.ApplicationManager=all:$(GO_SRC) `pwd`/api/handler/handler.proto + # Mocks mocks: diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md new file mode 100644 index 000000000..cc7702a80 --- /dev/null +++ b/api/handler/ApplicationManager.md @@ -0,0 +1,222 @@ +## ApplicationManager + +ApplicationManager manages application and device registrations on the Handler + +### RegisterApplication + +Applications should first be registered to the Handler with the `RegisterApplication` method + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`Empty`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `POST` `/applications` + +### GetApplication + +GetApplication returns the application with the given identifier (app_id) + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`Application`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `GET` `/applications/{app_id}` + +### SetApplication + +SetApplication updates the settings for the application. All fields must be supplied. + +- Request: [`Application`](#handlerapplication) +- Response: [`Empty`](#handlerapplication) + +#### HTTP Endpoint + +- `POST` `/applications/{app_id}` + +### DeleteApplication + +DeleteApplication deletes the application with the given identifier (app_id) + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`Empty`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `DELETE` `/applications/{app_id}` + +### GetDevice + +GetDevice returns the device with the given identifier (app_id and dev_id) + +- Request: [`DeviceIdentifier`](#handlerdeviceidentifier) +- Response: [`Device`](#handlerdeviceidentifier) + +#### HTTP Endpoint + +- `GET` `/applications/{app_id}/devices/{dev_id}` + +### SetDevice + +SetDevice creates or updates a device. All fields must be supplied. + +- Request: [`Device`](#handlerdevice) +- Response: [`Empty`](#handlerdevice) + +#### HTTP Endpoints + +- `POST` `/applications/{app_id}/devices/{dev_id}` +- `POST` `/applications/{app_id}/devices` + +### DeleteDevice + +DeleteDevice deletes the device with the given identifier (app_id and dev_id) + +- Request: [`DeviceIdentifier`](#handlerdeviceidentifier) +- Response: [`Empty`](#handlerdeviceidentifier) + +#### HTTP Endpoint + +- `DELETE` `/applications/{app_id}/devices/{dev_id}` + +### GetDevicesForApplication + +GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) + +- Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) +- Response: [`DeviceList`](#handlerapplicationidentifier) + +#### HTTP Endpoint + +- `GET` `/applications/{app_id}/devices` + +### DryDownlink + +DryUplink simulates processing an uplink message and returns the result + +- Request: [`DryDownlinkMessage`](#handlerdrydownlinkmessage) +- Response: [`DryDownlinkResult`](#handlerdrydownlinkmessage) + +### DryUplink + +DryUplink simulates processing a downlink message and returns the result + +- Request: [`DryUplinkMessage`](#handlerdryuplinkmessage) +- Response: [`DryUplinkResult`](#handlerdryuplinkmessage) + +## Used Messages + +### `.google.protobuf.Empty` + +A generic empty message that you can re-use to avoid defining duplicated +empty messages in your APIs. + +### `.handler.Application` + +The Application settings + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| app_id | `string` | | +| decoder | `string` | The decoder is a JavaScript function that decodes a byte array to an object. | +| converter | `string` | The converter is a JavaScript function that can be used to convert values in the object returned from the decoder. This can for example be useful to convert a voltage to a temperature. | +| validator | `string` | The validator is a JavaScript function that checks the validity of the object returned by the decoder or converter. If validation fails, the message is dropped. | +| encoder | `string` | The encoder is a JavaScript function that encodes an object to a byte array. | + +### `.handler.ApplicationIdentifier` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| app_id | `string` | | + +### `.handler.Device` + +The Device settings + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| app_id | `string` | | +| dev_id | `string` | | +| lorawan_device | [`Device`](#lorawandevice) | | + +### `.handler.DeviceIdentifier` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| app_id | `string` | | +| dev_id | `string` | | + +### `.handler.DeviceList` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| devices | _repeated_ [`Device`](#handlerdevice) | | + +### `.handler.DryDownlinkMessage` + +DryDownlinkMessage is a simulated message to test downlink processing + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| payload | `bytes` | The binary payload to use | +| fields | `string` | JSON-encoded object with fields to encode | +| app | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | +| port | `uint32` | The port number that should be passed to the payload function | + +### `.handler.DryDownlinkResult` + +DryDownlinkResult is the result from a downlink simulation + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| payload | `bytes` | The payload that was encoded | +| logs | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | + +### `.handler.DryUplinkMessage` + +DryUplinkMessage is a simulated message to test uplink processing + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| payload | `bytes` | The binary payload to use | +| app | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | +| port | `uint32` | The port number that should be passed to the payload function | + +### `.handler.DryUplinkResult` + +DryUplinkResult is the result from an uplink simulation + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| payload | `bytes` | The binary payload | +| fields | `string` | The decoded fields | +| valid | `bool` | Was validation of the message successful | +| logs | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | + +### `.handler.LogEntry` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| function | `string` | The location where the log was created (what payload function) | +| fields | _repeated_ `string` | A list of JSON-encoded fields that were logged | + +### `.lorawan.Device` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| app_eui | `bytes` | The AppEUI is a unique, 8 byte identifier for the application a device belongs to. | +| dev_eui | `bytes` | The DevEUI is a unique, 8 byte identifier for the device. | +| app_id | `string` | The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. | +| dev_id | `string` | The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. | +| dev_addr | `bytes` | The DevAddr is a dynamic, 4 byte session address for the device. | +| nwk_s_key | `bytes` | The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | +| app_s_key | `bytes` | The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | +| app_key | `bytes` | The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). | +| f_cnt_up | `uint32` | FCntUp is the uplink frame counter for a device session. | +| f_cnt_down | `uint32` | FCntDown is the downlink frame counter for a device session. | +| disable_f_cnt_check | `bool` | The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. | +| uses32_bit_f_cnt | `bool` | The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. | +| activation_constraints | `string` | The ActivationContstraints are used to allocate a device address for a device. There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. | +| last_seen | `int64` | When the device was last seen (Unix nanoseconds) | + From 33bc6939d405980c35c18722df5796069bf09899 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 12:12:34 +0100 Subject: [PATCH 2174/2266] Re-generate protos --- api/handler/handler.pb.go | 81 +++++++++++++++++++++++++------ api/protocol/lorawan/device.pb.go | 45 +++++++++++------ 2 files changed, 96 insertions(+), 30 deletions(-) diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index 5deb22873..e6365fc43 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -153,12 +153,21 @@ func (m *ApplicationIdentifier) String() string { return proto.Compac func (*ApplicationIdentifier) ProtoMessage() {} func (*ApplicationIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{3} } +// The Application settings type Application struct { - AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` - Decoder string `protobuf:"bytes,2,opt,name=decoder,proto3" json:"decoder,omitempty"` + AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + // The decoder is a JavaScript function that decodes a byte array to an object. + Decoder string `protobuf:"bytes,2,opt,name=decoder,proto3" json:"decoder,omitempty"` + // The converter is a JavaScript function that can be used to convert values + // in the object returned from the decoder. This can for example be useful to + // convert a voltage to a temperature. Converter string `protobuf:"bytes,3,opt,name=converter,proto3" json:"converter,omitempty"` + // The validator is a JavaScript function that checks the validity of the + // object returned by the decoder or converter. If validation fails, the + // message is dropped. Validator string `protobuf:"bytes,4,opt,name=validator,proto3" json:"validator,omitempty"` - Encoder string `protobuf:"bytes,5,opt,name=encoder,proto3" json:"encoder,omitempty"` + // The encoder is a JavaScript function that encodes an object to a byte array. + Encoder string `protobuf:"bytes,5,opt,name=encoder,proto3" json:"encoder,omitempty"` } func (m *Application) Reset() { *m = Application{} } @@ -176,9 +185,12 @@ func (m *DeviceIdentifier) String() string { return proto.CompactText func (*DeviceIdentifier) ProtoMessage() {} func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{5} } +// The Device settings type Device struct { AppId string `protobuf:"bytes,1,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` DevId string `protobuf:"bytes,2,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + // The device can be of different kinds + // // Types that are valid to be assigned to Device: // *Device_LorawanDevice Device isDevice_Device `protobuf_oneof:"device"` @@ -286,11 +298,16 @@ func (m *DeviceList) GetDevices() []*Device { return nil } +// DryDownlinkMessage is a simulated message to test downlink processing type DryDownlinkMessage struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` - App *Application `protobuf:"bytes,3,opt,name=app" json:"app,omitempty"` - Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` + // The binary payload to use + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // JSON-encoded object with fields to encode + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + // The Application containing the payload functions that should be executed + App *Application `protobuf:"bytes,3,opt,name=app" json:"app,omitempty"` + // The port number that should be passed to the payload function + Port uint32 `protobuf:"varint,4,opt,name=port,proto3" json:"port,omitempty"` } func (m *DryDownlinkMessage) Reset() { *m = DryDownlinkMessage{} } @@ -305,10 +322,14 @@ func (m *DryDownlinkMessage) GetApp() *Application { return nil } +// DryUplinkMessage is a simulated message to test uplink processing type DryUplinkMessage struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - App *Application `protobuf:"bytes,2,opt,name=app" json:"app,omitempty"` - Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` + // The binary payload to use + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // The Application containing the payload functions that should be executed + App *Application `protobuf:"bytes,2,opt,name=app" json:"app,omitempty"` + // The port number that should be passed to the payload function + Port uint32 `protobuf:"varint,3,opt,name=port,proto3" json:"port,omitempty"` } func (m *DryUplinkMessage) Reset() { *m = DryUplinkMessage{} } @@ -335,11 +356,16 @@ func (m *LogEntry) String() string { return proto.CompactTextString(m func (*LogEntry) ProtoMessage() {} func (*LogEntry) Descriptor() ([]byte, []int) { return fileDescriptorHandler, []int{10} } +// DryUplinkResult is the result from an uplink simulation type DryUplinkResult struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` - Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` - Logs []*LogEntry `protobuf:"bytes,4,rep,name=logs" json:"logs,omitempty"` + // The binary payload + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // The decoded fields + Fields string `protobuf:"bytes,2,opt,name=fields,proto3" json:"fields,omitempty"` + // Was validation of the message successful + Valid bool `protobuf:"varint,3,opt,name=valid,proto3" json:"valid,omitempty"` + // Logs that have been generated while processing + Logs []*LogEntry `protobuf:"bytes,4,rep,name=logs" json:"logs,omitempty"` } func (m *DryUplinkResult) Reset() { *m = DryUplinkResult{} } @@ -354,9 +380,12 @@ func (m *DryUplinkResult) GetLogs() []*LogEntry { return nil } +// DryDownlinkResult is the result from a downlink simulation type DryDownlinkResult struct { - Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` - Logs []*LogEntry `protobuf:"bytes,2,rep,name=logs" json:"logs,omitempty"` + // The payload that was encoded + Payload []byte `protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"` + // Logs that have been generated while processing + Logs []*LogEntry `protobuf:"bytes,2,rep,name=logs" json:"logs,omitempty"` } func (m *DryDownlinkResult) Reset() { *m = DryDownlinkResult{} } @@ -495,15 +524,25 @@ var _Handler_serviceDesc = grpc.ServiceDesc{ // Client API for ApplicationManager service type ApplicationManagerClient interface { + // Applications should first be registered to the Handler with the `RegisterApplication` method RegisterApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // GetApplication returns the application with the given identifier (app_id) GetApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*Application, error) + // SetApplication updates the settings for the application. All fields must be supplied. SetApplication(ctx context.Context, in *Application, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // DeleteApplication deletes the application with the given identifier (app_id) DeleteApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // GetDevice returns the device with the given identifier (app_id and dev_id) GetDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*Device, error) + // SetDevice creates or updates a device. All fields must be supplied. SetDevice(ctx context.Context, in *Device, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // DeleteDevice deletes the device with the given identifier (app_id and dev_id) DeleteDevice(ctx context.Context, in *DeviceIdentifier, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) GetDevicesForApplication(ctx context.Context, in *ApplicationIdentifier, opts ...grpc.CallOption) (*DeviceList, error) + // DryUplink simulates processing an uplink message and returns the result DryDownlink(ctx context.Context, in *DryDownlinkMessage, opts ...grpc.CallOption) (*DryDownlinkResult, error) + // DryUplink simulates processing a downlink message and returns the result DryUplink(ctx context.Context, in *DryUplinkMessage, opts ...grpc.CallOption) (*DryUplinkResult, error) } @@ -608,15 +647,25 @@ func (c *applicationManagerClient) DryUplink(ctx context.Context, in *DryUplinkM // Server API for ApplicationManager service type ApplicationManagerServer interface { + // Applications should first be registered to the Handler with the `RegisterApplication` method RegisterApplication(context.Context, *ApplicationIdentifier) (*google_protobuf.Empty, error) + // GetApplication returns the application with the given identifier (app_id) GetApplication(context.Context, *ApplicationIdentifier) (*Application, error) + // SetApplication updates the settings for the application. All fields must be supplied. SetApplication(context.Context, *Application) (*google_protobuf.Empty, error) + // DeleteApplication deletes the application with the given identifier (app_id) DeleteApplication(context.Context, *ApplicationIdentifier) (*google_protobuf.Empty, error) + // GetDevice returns the device with the given identifier (app_id and dev_id) GetDevice(context.Context, *DeviceIdentifier) (*Device, error) + // SetDevice creates or updates a device. All fields must be supplied. SetDevice(context.Context, *Device) (*google_protobuf.Empty, error) + // DeleteDevice deletes the device with the given identifier (app_id and dev_id) DeleteDevice(context.Context, *DeviceIdentifier) (*google_protobuf.Empty, error) + // GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) GetDevicesForApplication(context.Context, *ApplicationIdentifier) (*DeviceList, error) + // DryUplink simulates processing an uplink message and returns the result DryDownlink(context.Context, *DryDownlinkMessage) (*DryDownlinkResult, error) + // DryUplink simulates processing a downlink message and returns the result DryUplink(context.Context, *DryUplinkMessage) (*DryUplinkResult, error) } diff --git a/api/protocol/lorawan/device.pb.go b/api/protocol/lorawan/device.pb.go index f3efba8da..1c426ad04 100644 --- a/api/protocol/lorawan/device.pb.go +++ b/api/protocol/lorawan/device.pb.go @@ -41,7 +41,9 @@ var _ = math.Inf const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package type DeviceIdentifier struct { + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + // The DevEUI is a unique, 8 byte identifier for the device. DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` } @@ -51,21 +53,36 @@ func (*DeviceIdentifier) ProtoMessage() {} func (*DeviceIdentifier) Descriptor() ([]byte, []int) { return fileDescriptorDevice, []int{0} } type Device struct { - AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` - DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` - AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` - DevId string `protobuf:"bytes,4,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` - DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,5,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` - NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,6,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` - AppSKey *github_com_TheThingsNetwork_ttn_core_types.AppSKey `protobuf:"bytes,7,opt,name=app_s_key,json=appSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppSKey" json:"app_s_key,omitempty"` - AppKey *github_com_TheThingsNetwork_ttn_core_types.AppKey `protobuf:"bytes,8,opt,name=app_key,json=appKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppKey" json:"app_key,omitempty"` - FCntUp uint32 `protobuf:"varint,9,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` - FCntDown uint32 `protobuf:"varint,10,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` - // Options - DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` - Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + // The AppEUI is a unique, 8 byte identifier for the application a device belongs to. + AppEui *github_com_TheThingsNetwork_ttn_core_types.AppEUI `protobuf:"bytes,1,opt,name=app_eui,json=appEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppEUI" json:"app_eui,omitempty"` + // The DevEUI is a unique, 8 byte identifier for the device. + DevEui *github_com_TheThingsNetwork_ttn_core_types.DevEUI `protobuf:"bytes,2,opt,name=dev_eui,json=devEui,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevEUI" json:"dev_eui,omitempty"` + // The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. + AppId string `protobuf:"bytes,3,opt,name=app_id,json=appId,proto3" json:"app_id,omitempty"` + // The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. + DevId string `protobuf:"bytes,4,opt,name=dev_id,json=devId,proto3" json:"dev_id,omitempty"` + // The DevAddr is a dynamic, 4 byte session address for the device. + DevAddr *github_com_TheThingsNetwork_ttn_core_types.DevAddr `protobuf:"bytes,5,opt,name=dev_addr,json=devAddr,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.DevAddr" json:"dev_addr,omitempty"` + // The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. + NwkSKey *github_com_TheThingsNetwork_ttn_core_types.NwkSKey `protobuf:"bytes,6,opt,name=nwk_s_key,json=nwkSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.NwkSKey" json:"nwk_s_key,omitempty"` + // The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. + // This key is negotiated during the OTAA join procedure, or statically configured using ABP. + AppSKey *github_com_TheThingsNetwork_ttn_core_types.AppSKey `protobuf:"bytes,7,opt,name=app_s_key,json=appSKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppSKey" json:"app_s_key,omitempty"` + // The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). + AppKey *github_com_TheThingsNetwork_ttn_core_types.AppKey `protobuf:"bytes,8,opt,name=app_key,json=appKey,proto3,customtype=github.com/TheThingsNetwork/ttn/core/types.AppKey" json:"app_key,omitempty"` + // FCntUp is the uplink frame counter for a device session. + FCntUp uint32 `protobuf:"varint,9,opt,name=f_cnt_up,json=fCntUp,proto3" json:"f_cnt_up,omitempty"` + // FCntDown is the downlink frame counter for a device session. + FCntDown uint32 `protobuf:"varint,10,opt,name=f_cnt_down,json=fCntDown,proto3" json:"f_cnt_down,omitempty"` + // The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. + DisableFCntCheck bool `protobuf:"varint,11,opt,name=disable_f_cnt_check,json=disableFCntCheck,proto3" json:"disable_f_cnt_check,omitempty"` + // The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. + Uses32BitFCnt bool `protobuf:"varint,12,opt,name=uses32_bit_f_cnt,json=uses32BitFCnt,proto3" json:"uses32_bit_f_cnt,omitempty"` + // The ActivationContstraints are used to allocate a device address for a device. + // There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. ActivationConstraints string `protobuf:"bytes,13,opt,name=activation_constraints,json=activationConstraints,proto3" json:"activation_constraints,omitempty"` - // Other + // When the device was last seen (Unix nanoseconds) LastSeen int64 `protobuf:"varint,21,opt,name=last_seen,json=lastSeen,proto3" json:"last_seen,omitempty"` } From c0645794798447ca1e6e682d81b4275055aa834a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 12:32:14 +0100 Subject: [PATCH 2175/2266] Fix newlines in enum description [protodoc] --- utils/protoc-gen-ttndoc/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go index 4e70662ac..9fd63d634 100644 --- a/utils/protoc-gen-ttndoc/main.go +++ b/utils/protoc-gen-ttndoc/main.go @@ -213,7 +213,7 @@ func main() { fmt.Fprintln(content, "| Value | Description |") fmt.Fprintln(content, "| ----- | ----------- |") for _, value := range enum.values { - fmt.Fprintf(content, "| %s | %s |\n", value.GetName(), value.comment) + fmt.Fprintf(content, "| %s | %s |\n", value.GetName(), strings.Replace(value.comment, "\n", " ", -1)) } fmt.Fprintln(content) } From 31d680ed745abc49e680d252dd02bb723b575c18 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 12:33:49 +0100 Subject: [PATCH 2176/2266] Add protodoc for Discovery server --- Makefile | 1 + api/discovery/Discovery.md | 118 ++++++++++++++++++++++++++++++++++ api/discovery/discovery.proto | 78 ++++++++++++++++------ 3 files changed, 178 insertions(+), 19 deletions(-) create mode 100644 api/discovery/Discovery.md diff --git a/Makefile b/Makefile index ea33fb009..b74320f86 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,7 @@ api/%.pb.go: api/%.proto protodoc: $(PROTO_FILES) protoc $(PROTOC_IMPORTS) --ttndoc_out=logtostderr=true,.handler.ApplicationManager=all:$(GO_SRC) `pwd`/api/handler/handler.proto + protoc $(PROTOC_IMPORTS) --ttndoc_out=logtostderr=true,.discovery.Discovery=all:$(GO_SRC) `pwd`/api/discovery/discovery.proto # Mocks diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md new file mode 100644 index 000000000..98bcd653e --- /dev/null +++ b/api/discovery/Discovery.md @@ -0,0 +1,118 @@ +## Discovery + +The Discovery service is used to discover services within The Things Network. + +### Announce + +Announce your component to the Discovery server + +- Request: [`Announcement`](#discoveryannouncement) +- Response: [`Empty`](#discoveryannouncement) + +### GetAll + +Get all announcements for a specific service + +- Request: [`GetAllRequest`](#discoverygetallrequest) +- Response: [`AnnouncementsResponse`](#discoverygetallrequest) + +### Get + +Get a specific announcement + +- Request: [`GetRequest`](#discoverygetrequest) +- Response: [`Announcement`](#discoverygetrequest) + +### AddMetadata + +Add metadata to an announement + +- Request: [`MetadataRequest`](#discoverymetadatarequest) +- Response: [`Empty`](#discoverymetadatarequest) + +### DeleteMetadata + +Delete metadata from an announcement + +- Request: [`MetadataRequest`](#discoverymetadatarequest) +- Response: [`Empty`](#discoverymetadatarequest) + +## Used Messages + +### `.discovery.Announcement` + +The Announcement of a service (also called component) + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| id | `string` | The ID of the component | +| service_name | `string` | The name of the component (router/broker/handler) | +| service_version | `string` | Service version in the form "[version]-[commit] ([build date])" | +| description | `string` | Description of the component | +| url | `string` | URL with documentation or more information about this component | +| public | `bool` | Indicates whether this service is part of The Things Network (the public community network) | +| net_address | `string` | Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) | +| public_key | `string` | ECDSA public key of this component | +| certificate | `string` | TLS Certificate (if TLS is enabled) | +| metadata | _repeated_ [`Metadata`](#discoverymetadata) | Metadata for this component | + +### `.discovery.AnnouncementsResponse` + +A list of announcements + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| services | _repeated_ [`Announcement`](#discoveryannouncement) | | + +### `.discovery.GetAllRequest` + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| service_name | `string` | The name of the service (router/broker/handler) | + +### `.discovery.GetRequest` + +The identifier of the service that should be returned + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| id | `string` | The ID of the service | +| service_name | `string` | The name of the service (router/broker/handler) | + +### `.discovery.Metadata` + +Announcements have a list of Metadata + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| key | [`Key`](#discoverymetadatakey) | The key indicates the metadata type | +| value | `bytes` | The value depends on the key type | + +### `.discovery.MetadataRequest` + +The metadata to add or remove from an announement + +| Field Name | Type | Description | +| ---------- | ---- | ----------- | +| id | `string` | The ID of the service that should be modified | +| service_name | `string` | The name of the service (router/broker/handler) that should be modified | +| metadata | [`Metadata`](#discoverymetadata) | | + +### `.google.protobuf.Empty` + +A generic empty message that you can re-use to avoid defining duplicated +empty messages in your APIs. + +## Used Enums + +### `.discovery.Metadata.Key` + +The Key indicates the metadata type + +| Value | Description | +| ----- | ----------- | +| OTHER | OTHER indicates arbitrary metadata. We currently don't allow this. | +| PREFIX | The value for PREFIX consists of 1 byte denoting the number of bits, followed by the prefix and enough trailing bits to fill 4 octets. Only authorized brokers can announce PREFIX metadata. | +| APP_EUI | APP_EUI is used for announcing join handlers. The value for APP_EUI is the byte slice of the AppEUI. Only authorized join handlers can announce APP_EUI metadata (and we don't have any of those yet). | +| APP_ID | APP_ID is used for announcing that this handler is responsible for a certain AppID. The value for APP_ID is the byte slice of the AppID string. This metadata can only be added if the requesting client is authorized to manage this AppID. | + diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 93d097698..8d283da1a 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -9,63 +9,103 @@ package discovery; option go_package = "github.com/TheThingsNetwork/ttn/api/discovery"; +// Announcements have a list of Metadata message Metadata { + + // In the future we will have to change this message to use a oneof. + + // The Key indicates the metadata type enum Key { + // OTHER indicates arbitrary metadata. We currently don't allow this. OTHER = 0; // The value for PREFIX consists of 1 byte denoting the number of bits, - // followed by the prefix and enough trailing bits to fill 4 octets - PREFIX = 1; // For Brokers - - // APP_EUI is used for announcing join handlers. - // The value for APP_EUI is the byte slice of the AppEUI string - APP_EUI = 2; // For Handlers - - // APP_ID is used for announcing regular handlers - // The value for APP_ID is the byte slice of the AppID string - APP_ID = 3; // For Handlers + // followed by the prefix and enough trailing bits to fill 4 octets. + // Only authorized brokers can announce PREFIX metadata. + PREFIX = 1; + + // APP_EUI is used for announcing join handlers. The value for APP_EUI + // is the byte slice of the AppEUI. + // Only authorized join handlers can announce APP_EUI metadata (and we + // don't have any of those yet). + APP_EUI = 2; + + // APP_ID is used for announcing that this handler is responsible for + // a certain AppID. The value for APP_ID is the byte slice of the AppID + // string. This metadata can only be added if the requesting client is + // authorized to manage this AppID. + APP_ID = 3; } + + // The key indicates the metadata type Key key = 1; + + // The value depends on the key type bytes value = 2; } +// The Announcement of a service (also called component) message Announcement { - string id = 1; // ID - string service_name = 2; // Service name/type: router/broker/handler - string service_version = 3; // Service version in the form "[version]-[commit] ([build date])" - string description = 4; // Description of the component - string url = 5; // URL with documentation or more information about this component - bool public = 6; // Indicates whether this service is part of The Things Network (the public community network) - string net_address = 11; // Network address in the form "[hostname]:[port]" - string public_key = 12; // ECDSA public key of this component - string certificate = 13; // TLS Certificate (if enabled) + // The ID of the component + string id = 1; + // The name of the component (router/broker/handler) + string service_name = 2; + // Service version in the form "[version]-[commit] ([build date])" + string service_version = 3; + // Description of the component + string description = 4; + // URL with documentation or more information about this component + string url = 5; + // Indicates whether this service is part of The Things Network (the public community network) + bool public = 6; + // Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) + string net_address = 11; + // ECDSA public key of this component + string public_key = 12; + // TLS Certificate (if TLS is enabled) + string certificate = 13; + // Metadata for this component repeated Metadata metadata = 21; } message GetAllRequest { + // The name of the service (router/broker/handler) string service_name = 1; } +// The identifier of the service that should be returned message GetRequest { + // The ID of the service string id = 1; + // The name of the service (router/broker/handler) string service_name = 2; } +// The metadata to add or remove from an announement message MetadataRequest { + // The ID of the service that should be modified string id = 1; + // The name of the service (router/broker/handler) that should be modified string service_name = 2; Metadata metadata = 11; } +// A list of announcements message AnnouncementsResponse { repeated Announcement services = 1; } +// The Discovery service is used to discover services within The Things Network. service Discovery { + // Announce your component to the Discovery server rpc Announce(Announcement) returns (google.protobuf.Empty); + // Get all announcements for a specific service rpc GetAll(GetAllRequest) returns (AnnouncementsResponse); + // Get a specific announcement rpc Get(GetRequest) returns (Announcement); + // Add metadata to an announement rpc AddMetadata(MetadataRequest) returns (google.protobuf.Empty); + // Delete metadata from an announcement rpc DeleteMetadata(MetadataRequest) returns (google.protobuf.Empty); } From eaf64818145a4fe9e3d61a76547b1cdc45a5979b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 13:34:16 +0100 Subject: [PATCH 2177/2266] Start proto docs with h1 --- api/discovery/Discovery.md | 32 ++++++++-------- api/handler/ApplicationManager.md | 64 +++++++++++++++---------------- utils/protoc-gen-ttndoc/main.go | 16 ++++---- 3 files changed, 56 insertions(+), 56 deletions(-) diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index 98bcd653e..e5784a4a6 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -1,45 +1,45 @@ -## Discovery +# Discovery The Discovery service is used to discover services within The Things Network. -### Announce +## Announce Announce your component to the Discovery server - Request: [`Announcement`](#discoveryannouncement) - Response: [`Empty`](#discoveryannouncement) -### GetAll +## GetAll Get all announcements for a specific service - Request: [`GetAllRequest`](#discoverygetallrequest) - Response: [`AnnouncementsResponse`](#discoverygetallrequest) -### Get +## Get Get a specific announcement - Request: [`GetRequest`](#discoverygetrequest) - Response: [`Announcement`](#discoverygetrequest) -### AddMetadata +## AddMetadata Add metadata to an announement - Request: [`MetadataRequest`](#discoverymetadatarequest) - Response: [`Empty`](#discoverymetadatarequest) -### DeleteMetadata +## DeleteMetadata Delete metadata from an announcement - Request: [`MetadataRequest`](#discoverymetadatarequest) - Response: [`Empty`](#discoverymetadatarequest) -## Used Messages +# Used Messages -### `.discovery.Announcement` +## `.discovery.Announcement` The Announcement of a service (also called component) @@ -56,7 +56,7 @@ The Announcement of a service (also called component) | certificate | `string` | TLS Certificate (if TLS is enabled) | | metadata | _repeated_ [`Metadata`](#discoverymetadata) | Metadata for this component | -### `.discovery.AnnouncementsResponse` +## `.discovery.AnnouncementsResponse` A list of announcements @@ -64,13 +64,13 @@ A list of announcements | ---------- | ---- | ----------- | | services | _repeated_ [`Announcement`](#discoveryannouncement) | | -### `.discovery.GetAllRequest` +## `.discovery.GetAllRequest` | Field Name | Type | Description | | ---------- | ---- | ----------- | | service_name | `string` | The name of the service (router/broker/handler) | -### `.discovery.GetRequest` +## `.discovery.GetRequest` The identifier of the service that should be returned @@ -79,7 +79,7 @@ The identifier of the service that should be returned | id | `string` | The ID of the service | | service_name | `string` | The name of the service (router/broker/handler) | -### `.discovery.Metadata` +## `.discovery.Metadata` Announcements have a list of Metadata @@ -88,7 +88,7 @@ Announcements have a list of Metadata | key | [`Key`](#discoverymetadatakey) | The key indicates the metadata type | | value | `bytes` | The value depends on the key type | -### `.discovery.MetadataRequest` +## `.discovery.MetadataRequest` The metadata to add or remove from an announement @@ -98,14 +98,14 @@ The metadata to add or remove from an announement | service_name | `string` | The name of the service (router/broker/handler) that should be modified | | metadata | [`Metadata`](#discoverymetadata) | | -### `.google.protobuf.Empty` +## `.google.protobuf.Empty` A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. -## Used Enums +# Used Enums -### `.discovery.Metadata.Key` +## `.discovery.Metadata.Key` The Key indicates the metadata type diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md index cc7702a80..a980775e6 100644 --- a/api/handler/ApplicationManager.md +++ b/api/handler/ApplicationManager.md @@ -1,118 +1,118 @@ -## ApplicationManager +# ApplicationManager ApplicationManager manages application and device registrations on the Handler -### RegisterApplication +## RegisterApplication Applications should first be registered to the Handler with the `RegisterApplication` method - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`Empty`](#handlerapplicationidentifier) -#### HTTP Endpoint +### HTTP Endpoint - `POST` `/applications` -### GetApplication +## GetApplication GetApplication returns the application with the given identifier (app_id) - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`Application`](#handlerapplicationidentifier) -#### HTTP Endpoint +### HTTP Endpoint - `GET` `/applications/{app_id}` -### SetApplication +## SetApplication SetApplication updates the settings for the application. All fields must be supplied. - Request: [`Application`](#handlerapplication) - Response: [`Empty`](#handlerapplication) -#### HTTP Endpoint +### HTTP Endpoint - `POST` `/applications/{app_id}` -### DeleteApplication +## DeleteApplication DeleteApplication deletes the application with the given identifier (app_id) - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`Empty`](#handlerapplicationidentifier) -#### HTTP Endpoint +### HTTP Endpoint - `DELETE` `/applications/{app_id}` -### GetDevice +## GetDevice GetDevice returns the device with the given identifier (app_id and dev_id) - Request: [`DeviceIdentifier`](#handlerdeviceidentifier) - Response: [`Device`](#handlerdeviceidentifier) -#### HTTP Endpoint +### HTTP Endpoint - `GET` `/applications/{app_id}/devices/{dev_id}` -### SetDevice +## SetDevice SetDevice creates or updates a device. All fields must be supplied. - Request: [`Device`](#handlerdevice) - Response: [`Empty`](#handlerdevice) -#### HTTP Endpoints +### HTTP Endpoints - `POST` `/applications/{app_id}/devices/{dev_id}` - `POST` `/applications/{app_id}/devices` -### DeleteDevice +## DeleteDevice DeleteDevice deletes the device with the given identifier (app_id and dev_id) - Request: [`DeviceIdentifier`](#handlerdeviceidentifier) - Response: [`Empty`](#handlerdeviceidentifier) -#### HTTP Endpoint +### HTTP Endpoint - `DELETE` `/applications/{app_id}/devices/{dev_id}` -### GetDevicesForApplication +## GetDevicesForApplication GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`DeviceList`](#handlerapplicationidentifier) -#### HTTP Endpoint +### HTTP Endpoint - `GET` `/applications/{app_id}/devices` -### DryDownlink +## DryDownlink DryUplink simulates processing an uplink message and returns the result - Request: [`DryDownlinkMessage`](#handlerdrydownlinkmessage) - Response: [`DryDownlinkResult`](#handlerdrydownlinkmessage) -### DryUplink +## DryUplink DryUplink simulates processing a downlink message and returns the result - Request: [`DryUplinkMessage`](#handlerdryuplinkmessage) - Response: [`DryUplinkResult`](#handlerdryuplinkmessage) -## Used Messages +# Used Messages -### `.google.protobuf.Empty` +## `.google.protobuf.Empty` A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. -### `.handler.Application` +## `.handler.Application` The Application settings @@ -124,13 +124,13 @@ The Application settings | validator | `string` | The validator is a JavaScript function that checks the validity of the object returned by the decoder or converter. If validation fails, the message is dropped. | | encoder | `string` | The encoder is a JavaScript function that encodes an object to a byte array. | -### `.handler.ApplicationIdentifier` +## `.handler.ApplicationIdentifier` | Field Name | Type | Description | | ---------- | ---- | ----------- | | app_id | `string` | | -### `.handler.Device` +## `.handler.Device` The Device settings @@ -140,20 +140,20 @@ The Device settings | dev_id | `string` | | | lorawan_device | [`Device`](#lorawandevice) | | -### `.handler.DeviceIdentifier` +## `.handler.DeviceIdentifier` | Field Name | Type | Description | | ---------- | ---- | ----------- | | app_id | `string` | | | dev_id | `string` | | -### `.handler.DeviceList` +## `.handler.DeviceList` | Field Name | Type | Description | | ---------- | ---- | ----------- | | devices | _repeated_ [`Device`](#handlerdevice) | | -### `.handler.DryDownlinkMessage` +## `.handler.DryDownlinkMessage` DryDownlinkMessage is a simulated message to test downlink processing @@ -164,7 +164,7 @@ DryDownlinkMessage is a simulated message to test downlink processing | app | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | | port | `uint32` | The port number that should be passed to the payload function | -### `.handler.DryDownlinkResult` +## `.handler.DryDownlinkResult` DryDownlinkResult is the result from a downlink simulation @@ -173,7 +173,7 @@ DryDownlinkResult is the result from a downlink simulation | payload | `bytes` | The payload that was encoded | | logs | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | -### `.handler.DryUplinkMessage` +## `.handler.DryUplinkMessage` DryUplinkMessage is a simulated message to test uplink processing @@ -183,7 +183,7 @@ DryUplinkMessage is a simulated message to test uplink processing | app | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | | port | `uint32` | The port number that should be passed to the payload function | -### `.handler.DryUplinkResult` +## `.handler.DryUplinkResult` DryUplinkResult is the result from an uplink simulation @@ -194,14 +194,14 @@ DryUplinkResult is the result from an uplink simulation | valid | `bool` | Was validation of the message successful | | logs | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | -### `.handler.LogEntry` +## `.handler.LogEntry` | Field Name | Type | Description | | ---------- | ---- | ----------- | | function | `string` | The location where the log was created (what payload function) | | fields | _repeated_ `string` | A list of JSON-encoded fields that were logged | -### `.lorawan.Device` +## `.lorawan.Device` | Field Name | Type | Description | | ---------- | ---- | ----------- | diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go index 9fd63d634..e2f5f0486 100644 --- a/utils/protoc-gen-ttndoc/main.go +++ b/utils/protoc-gen-ttndoc/main.go @@ -93,13 +93,13 @@ func main() { content := new(bytes.Buffer) - fmt.Fprintf(content, "## %s\n\n", service.GetName()) + fmt.Fprintf(content, "# %s\n\n", service.GetName()) if service.comment != "" { fmt.Fprintf(content, "%s\n\n", service.comment) } for _, method := range service.methods { - fmt.Fprintf(content, "### %s\n\n", method.GetName()) + fmt.Fprintf(content, "## %s\n\n", method.GetName()) if method.comment != "" { fmt.Fprintf(content, "%s\n\n", method.comment) } @@ -120,9 +120,9 @@ func main() { if len(method.endpoints) != 0 { if len(method.endpoints) == 1 { - fmt.Fprint(content, "#### HTTP Endpoint\n\n") + fmt.Fprint(content, "### HTTP Endpoint\n\n") } else { - fmt.Fprint(content, "#### HTTP Endpoints\n\n") + fmt.Fprint(content, "### HTTP Endpoints\n\n") } for _, endpoint := range method.endpoints { fmt.Fprintf(content, "- `%s` `%s`\n", endpoint.method, endpoint.url) @@ -131,7 +131,7 @@ func main() { } } - fmt.Fprint(content, "## Used Messages\n\n") + fmt.Fprint(content, "# Used Messages\n\n") var messageKeys []string for key := range usedMessages { @@ -141,7 +141,7 @@ func main() { for _, messageKey := range messageKeys { message := usedMessages[messageKey] - fmt.Fprintf(content, "### `%s`\n\n", message.key) + fmt.Fprintf(content, "## `%s`\n\n", message.key) if strings.HasPrefix(messageKey, ".google") { fmt.Fprintf(content, "%s\n\n", strings.SplitAfter(message.comment, ".")[0]) } else if message.comment != "" { @@ -193,7 +193,7 @@ func main() { } if len(usedEnums) > 0 { - fmt.Fprint(content, "## Used Enums\n\n") + fmt.Fprint(content, "# Used Enums\n\n") var enumKeys []string for key := range usedEnums { @@ -204,7 +204,7 @@ func main() { for _, enumKey := range enumKeys { enum := usedEnums[enumKey] - fmt.Fprintf(content, "### `%s`\n\n", enum.key) + fmt.Fprintf(content, "## `%s`\n\n", enum.key) if enum.comment != "" { fmt.Fprintf(content, "%s\n\n", enum.comment) } From 101cb4ba655088a8b80ebd48178b4a7c3975df0c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 13:47:42 +0100 Subject: [PATCH 2178/2266] Update protodoc formatting --- api/discovery/Discovery.md | 82 ++++++------ api/handler/ApplicationManager.md | 132 +++++++++--------- utils/protoc-gen-ttndoc/main.go | 216 ++++++++++++------------------ 3 files changed, 193 insertions(+), 237 deletions(-) diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index e5784a4a6..86758a213 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -1,118 +1,120 @@ -# Discovery +# Discovery API Reference The Discovery service is used to discover services within The Things Network. -## Announce +## Methods + +### `Announce` Announce your component to the Discovery server - Request: [`Announcement`](#discoveryannouncement) - Response: [`Empty`](#discoveryannouncement) -## GetAll +### `GetAll` Get all announcements for a specific service - Request: [`GetAllRequest`](#discoverygetallrequest) - Response: [`AnnouncementsResponse`](#discoverygetallrequest) -## Get +### `Get` Get a specific announcement - Request: [`GetRequest`](#discoverygetrequest) - Response: [`Announcement`](#discoverygetrequest) -## AddMetadata +### `AddMetadata` Add metadata to an announement - Request: [`MetadataRequest`](#discoverymetadatarequest) - Response: [`Empty`](#discoverymetadatarequest) -## DeleteMetadata +### `DeleteMetadata` Delete metadata from an announcement - Request: [`MetadataRequest`](#discoverymetadatarequest) - Response: [`Empty`](#discoverymetadatarequest) -# Used Messages +## Messages -## `.discovery.Announcement` +### `.discovery.Announcement` The Announcement of a service (also called component) | Field Name | Type | Description | | ---------- | ---- | ----------- | -| id | `string` | The ID of the component | -| service_name | `string` | The name of the component (router/broker/handler) | -| service_version | `string` | Service version in the form "[version]-[commit] ([build date])" | -| description | `string` | Description of the component | -| url | `string` | URL with documentation or more information about this component | -| public | `bool` | Indicates whether this service is part of The Things Network (the public community network) | -| net_address | `string` | Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) | -| public_key | `string` | ECDSA public key of this component | -| certificate | `string` | TLS Certificate (if TLS is enabled) | -| metadata | _repeated_ [`Metadata`](#discoverymetadata) | Metadata for this component | - -## `.discovery.AnnouncementsResponse` +| `id` | `string` | The ID of the component | +| `service_name` | `string` | The name of the component (router/broker/handler) | +| `service_version` | `string` | Service version in the form "[version]-[commit] ([build date])" | +| `description` | `string` | Description of the component | +| `url` | `string` | URL with documentation or more information about this component | +| `public` | `bool` | Indicates whether this service is part of The Things Network (the public community network) | +| `net_address` | `string` | Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) | +| `public_key` | `string` | ECDSA public key of this component | +| `certificate` | `string` | TLS Certificate (if TLS is enabled) | +| `metadata` | _repeated_ [`Metadata`](#discoverymetadata) | Metadata for this component | + +### `.discovery.AnnouncementsResponse` A list of announcements | Field Name | Type | Description | | ---------- | ---- | ----------- | -| services | _repeated_ [`Announcement`](#discoveryannouncement) | | +| `services` | _repeated_ [`Announcement`](#discoveryannouncement) | | -## `.discovery.GetAllRequest` +### `.discovery.GetAllRequest` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| service_name | `string` | The name of the service (router/broker/handler) | +| `service_name` | `string` | The name of the service (router/broker/handler) | -## `.discovery.GetRequest` +### `.discovery.GetRequest` The identifier of the service that should be returned | Field Name | Type | Description | | ---------- | ---- | ----------- | -| id | `string` | The ID of the service | -| service_name | `string` | The name of the service (router/broker/handler) | +| `id` | `string` | The ID of the service | +| `service_name` | `string` | The name of the service (router/broker/handler) | -## `.discovery.Metadata` +### `.discovery.Metadata` Announcements have a list of Metadata | Field Name | Type | Description | | ---------- | ---- | ----------- | -| key | [`Key`](#discoverymetadatakey) | The key indicates the metadata type | -| value | `bytes` | The value depends on the key type | +| `key` | [`Key`](#discoverymetadatakey) | The key indicates the metadata type | +| `value` | `bytes` | The value depends on the key type | -## `.discovery.MetadataRequest` +### `.discovery.MetadataRequest` The metadata to add or remove from an announement | Field Name | Type | Description | | ---------- | ---- | ----------- | -| id | `string` | The ID of the service that should be modified | -| service_name | `string` | The name of the service (router/broker/handler) that should be modified | -| metadata | [`Metadata`](#discoverymetadata) | | +| `id` | `string` | The ID of the service that should be modified | +| `service_name` | `string` | The name of the service (router/broker/handler) that should be modified | +| `metadata` | [`Metadata`](#discoverymetadata) | | -## `.google.protobuf.Empty` +### `.google.protobuf.Empty` A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. -# Used Enums +## Used Enums -## `.discovery.Metadata.Key` +### `.discovery.Metadata.Key` The Key indicates the metadata type | Value | Description | | ----- | ----------- | -| OTHER | OTHER indicates arbitrary metadata. We currently don't allow this. | -| PREFIX | The value for PREFIX consists of 1 byte denoting the number of bits, followed by the prefix and enough trailing bits to fill 4 octets. Only authorized brokers can announce PREFIX metadata. | -| APP_EUI | APP_EUI is used for announcing join handlers. The value for APP_EUI is the byte slice of the AppEUI. Only authorized join handlers can announce APP_EUI metadata (and we don't have any of those yet). | -| APP_ID | APP_ID is used for announcing that this handler is responsible for a certain AppID. The value for APP_ID is the byte slice of the AppID string. This metadata can only be added if the requesting client is authorized to manage this AppID. | +| `OTHER` | OTHER indicates arbitrary metadata. We currently don't allow this. | +| `PREFIX` | The value for PREFIX consists of 1 byte denoting the number of bits, followed by the prefix and enough trailing bits to fill 4 octets. Only authorized brokers can announce PREFIX metadata. | +| `APP_EUI` | APP_EUI is used for announcing join handlers. The value for APP_EUI is the byte slice of the AppEUI. Only authorized join handlers can announce APP_EUI metadata (and we don't have any of those yet). | +| `APP_ID` | APP_ID is used for announcing that this handler is responsible for a certain AppID. The value for APP_ID is the byte slice of the AppID string. This metadata can only be added if the requesting client is authorized to manage this AppID. | diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md index a980775e6..f829a7027 100644 --- a/api/handler/ApplicationManager.md +++ b/api/handler/ApplicationManager.md @@ -1,8 +1,10 @@ -# ApplicationManager +# ApplicationManager API Reference ApplicationManager manages application and device registrations on the Handler -## RegisterApplication +## Methods + +### `RegisterApplication` Applications should first be registered to the Handler with the `RegisterApplication` method @@ -13,7 +15,7 @@ Applications should first be registered to the Handler with the `RegisterApplica - `POST` `/applications` -## GetApplication +### `GetApplication` GetApplication returns the application with the given identifier (app_id) @@ -24,7 +26,7 @@ GetApplication returns the application with the given identifier (app_id) - `GET` `/applications/{app_id}` -## SetApplication +### `SetApplication` SetApplication updates the settings for the application. All fields must be supplied. @@ -35,7 +37,7 @@ SetApplication updates the settings for the application. All fields must be supp - `POST` `/applications/{app_id}` -## DeleteApplication +### `DeleteApplication` DeleteApplication deletes the application with the given identifier (app_id) @@ -46,7 +48,7 @@ DeleteApplication deletes the application with the given identifier (app_id) - `DELETE` `/applications/{app_id}` -## GetDevice +### `GetDevice` GetDevice returns the device with the given identifier (app_id and dev_id) @@ -57,7 +59,7 @@ GetDevice returns the device with the given identifier (app_id and dev_id) - `GET` `/applications/{app_id}/devices/{dev_id}` -## SetDevice +### `SetDevice` SetDevice creates or updates a device. All fields must be supplied. @@ -69,7 +71,7 @@ SetDevice creates or updates a device. All fields must be supplied. - `POST` `/applications/{app_id}/devices/{dev_id}` - `POST` `/applications/{app_id}/devices` -## DeleteDevice +### `DeleteDevice` DeleteDevice deletes the device with the given identifier (app_id and dev_id) @@ -80,7 +82,7 @@ DeleteDevice deletes the device with the given identifier (app_id and dev_id) - `DELETE` `/applications/{app_id}/devices/{dev_id}` -## GetDevicesForApplication +### `GetDevicesForApplication` GetDevicesForApplication returns all devices that belong to the application with the given identifier (app_id) @@ -91,132 +93,132 @@ GetDevicesForApplication returns all devices that belong to the application with - `GET` `/applications/{app_id}/devices` -## DryDownlink +### `DryDownlink` DryUplink simulates processing an uplink message and returns the result - Request: [`DryDownlinkMessage`](#handlerdrydownlinkmessage) - Response: [`DryDownlinkResult`](#handlerdrydownlinkmessage) -## DryUplink +### `DryUplink` DryUplink simulates processing a downlink message and returns the result - Request: [`DryUplinkMessage`](#handlerdryuplinkmessage) - Response: [`DryUplinkResult`](#handlerdryuplinkmessage) -# Used Messages +## Messages -## `.google.protobuf.Empty` +### `.google.protobuf.Empty` A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. -## `.handler.Application` +### `.handler.Application` The Application settings | Field Name | Type | Description | | ---------- | ---- | ----------- | -| app_id | `string` | | -| decoder | `string` | The decoder is a JavaScript function that decodes a byte array to an object. | -| converter | `string` | The converter is a JavaScript function that can be used to convert values in the object returned from the decoder. This can for example be useful to convert a voltage to a temperature. | -| validator | `string` | The validator is a JavaScript function that checks the validity of the object returned by the decoder or converter. If validation fails, the message is dropped. | -| encoder | `string` | The encoder is a JavaScript function that encodes an object to a byte array. | +| `app_id` | `string` | | +| `decoder` | `string` | The decoder is a JavaScript function that decodes a byte array to an object. | +| `converter` | `string` | The converter is a JavaScript function that can be used to convert values in the object returned from the decoder. This can for example be useful to convert a voltage to a temperature. | +| `validator` | `string` | The validator is a JavaScript function that checks the validity of the object returned by the decoder or converter. If validation fails, the message is dropped. | +| `encoder` | `string` | The encoder is a JavaScript function that encodes an object to a byte array. | -## `.handler.ApplicationIdentifier` +### `.handler.ApplicationIdentifier` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| app_id | `string` | | +| `app_id` | `string` | | -## `.handler.Device` +### `.handler.Device` The Device settings | Field Name | Type | Description | | ---------- | ---- | ----------- | -| app_id | `string` | | -| dev_id | `string` | | -| lorawan_device | [`Device`](#lorawandevice) | | +| `app_id` | `string` | | +| `dev_id` | `string` | | +| `lorawan_device` | [`Device`](#lorawandevice) | | -## `.handler.DeviceIdentifier` +### `.handler.DeviceIdentifier` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| app_id | `string` | | -| dev_id | `string` | | +| `app_id` | `string` | | +| `dev_id` | `string` | | -## `.handler.DeviceList` +### `.handler.DeviceList` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| devices | _repeated_ [`Device`](#handlerdevice) | | +| `devices` | _repeated_ [`Device`](#handlerdevice) | | -## `.handler.DryDownlinkMessage` +### `.handler.DryDownlinkMessage` DryDownlinkMessage is a simulated message to test downlink processing | Field Name | Type | Description | | ---------- | ---- | ----------- | -| payload | `bytes` | The binary payload to use | -| fields | `string` | JSON-encoded object with fields to encode | -| app | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | -| port | `uint32` | The port number that should be passed to the payload function | +| `payload` | `bytes` | The binary payload to use | +| `fields` | `string` | JSON-encoded object with fields to encode | +| `app` | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | +| `port` | `uint32` | The port number that should be passed to the payload function | -## `.handler.DryDownlinkResult` +### `.handler.DryDownlinkResult` DryDownlinkResult is the result from a downlink simulation | Field Name | Type | Description | | ---------- | ---- | ----------- | -| payload | `bytes` | The payload that was encoded | -| logs | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | +| `payload` | `bytes` | The payload that was encoded | +| `logs` | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | -## `.handler.DryUplinkMessage` +### `.handler.DryUplinkMessage` DryUplinkMessage is a simulated message to test uplink processing | Field Name | Type | Description | | ---------- | ---- | ----------- | -| payload | `bytes` | The binary payload to use | -| app | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | -| port | `uint32` | The port number that should be passed to the payload function | +| `payload` | `bytes` | The binary payload to use | +| `app` | [`Application`](#handlerapplication) | The Application containing the payload functions that should be executed | +| `port` | `uint32` | The port number that should be passed to the payload function | -## `.handler.DryUplinkResult` +### `.handler.DryUplinkResult` DryUplinkResult is the result from an uplink simulation | Field Name | Type | Description | | ---------- | ---- | ----------- | -| payload | `bytes` | The binary payload | -| fields | `string` | The decoded fields | -| valid | `bool` | Was validation of the message successful | -| logs | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | +| `payload` | `bytes` | The binary payload | +| `fields` | `string` | The decoded fields | +| `valid` | `bool` | Was validation of the message successful | +| `logs` | _repeated_ [`LogEntry`](#handlerlogentry) | Logs that have been generated while processing | -## `.handler.LogEntry` +### `.handler.LogEntry` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| function | `string` | The location where the log was created (what payload function) | -| fields | _repeated_ `string` | A list of JSON-encoded fields that were logged | +| `function` | `string` | The location where the log was created (what payload function) | +| `fields` | _repeated_ `string` | A list of JSON-encoded fields that were logged | -## `.lorawan.Device` +### `.lorawan.Device` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| app_eui | `bytes` | The AppEUI is a unique, 8 byte identifier for the application a device belongs to. | -| dev_eui | `bytes` | The DevEUI is a unique, 8 byte identifier for the device. | -| app_id | `string` | The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. | -| dev_id | `string` | The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. | -| dev_addr | `bytes` | The DevAddr is a dynamic, 4 byte session address for the device. | -| nwk_s_key | `bytes` | The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | -| app_s_key | `bytes` | The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | -| app_key | `bytes` | The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). | -| f_cnt_up | `uint32` | FCntUp is the uplink frame counter for a device session. | -| f_cnt_down | `uint32` | FCntDown is the downlink frame counter for a device session. | -| disable_f_cnt_check | `bool` | The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. | -| uses32_bit_f_cnt | `bool` | The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. | -| activation_constraints | `string` | The ActivationContstraints are used to allocate a device address for a device. There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. | -| last_seen | `int64` | When the device was last seen (Unix nanoseconds) | +| `app_eui` | `bytes` | The AppEUI is a unique, 8 byte identifier for the application a device belongs to. | +| `dev_eui` | `bytes` | The DevEUI is a unique, 8 byte identifier for the device. | +| `app_id` | `string` | The AppID is a unique identifier for the application a device belongs to. It can contain lowercase letters, numbers, - and _. | +| `dev_id` | `string` | The DevID is a unique identifier for the device. It can contain lowercase letters, numbers, - and _. | +| `dev_addr` | `bytes` | The DevAddr is a dynamic, 4 byte session address for the device. | +| `nwk_s_key` | `bytes` | The NwkSKey is a 16 byte session key that is known by the device and the network. It is used for routing and MAC related functionality. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | +| `app_s_key` | `bytes` | The AppSKey is a 16 byte session key that is known by the device and the application. It is used for payload encryption. This key is negotiated during the OTAA join procedure, or statically configured using ABP. | +| `app_key` | `bytes` | The AppKey is a 16 byte static key that is known by the device and the application. It is used for negotiating session keys (OTAA). | +| `f_cnt_up` | `uint32` | FCntUp is the uplink frame counter for a device session. | +| `f_cnt_down` | `uint32` | FCntDown is the downlink frame counter for a device session. | +| `disable_f_cnt_check` | `bool` | The DisableFCntCheck option disables the frame counter check. Disabling this makes the device vulnerable to replay attacks, but makes ABP slightly easier. | +| `uses32_bit_f_cnt` | `bool` | The Uses32BitFCnt option indicates that the device keeps track of full 32 bit frame counters. As only the 16 lsb are actually transmitted, the 16 msb will have to be inferred. | +| `activation_constraints` | `string` | The ActivationContstraints are used to allocate a device address for a device. There are different prefixes for `otaa`, `abp`, `world`, `local`, `private`, `testing`. | +| `last_seen` | `int64` | When the device was last seen (Unix nanoseconds) | diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go index e2f5f0486..a620064d1 100644 --- a/utils/protoc-gen-ttndoc/main.go +++ b/utils/protoc-gen-ttndoc/main.go @@ -93,107 +93,113 @@ func main() { content := new(bytes.Buffer) - fmt.Fprintf(content, "# %s\n\n", service.GetName()) + fmt.Fprintf(content, "# %s API Reference\n\n", service.GetName()) if service.comment != "" { fmt.Fprintf(content, "%s\n\n", service.comment) } - for _, method := range service.methods { - fmt.Fprintf(content, "## %s\n\n", method.GetName()) - if method.comment != "" { - fmt.Fprintf(content, "%s\n\n", method.comment) - } - if method.inputStream { - fmt.Fprintf(content, "- Client stream of [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) - } else { - fmt.Fprintf(content, "- Request: [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) - } - useMessage(tree, method.input, usedMessages, usedEnums) - if method.outputStream { - fmt.Fprintf(content, "- Server stream of [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) - } else { - fmt.Fprintf(content, "- Response: [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) - } - useMessage(tree, method.output, usedMessages, usedEnums) + if len(service.methods) > 0 { + fmt.Fprint(content, "## Methods\n\n") - fmt.Fprintln(content) - - if len(method.endpoints) != 0 { - if len(method.endpoints) == 1 { - fmt.Fprint(content, "### HTTP Endpoint\n\n") + for _, method := range service.methods { + fmt.Fprintf(content, "### `%s`\n\n", method.GetName()) + if method.comment != "" { + fmt.Fprintf(content, "%s\n\n", method.comment) + } + if method.inputStream { + fmt.Fprintf(content, "- Client stream of [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) } else { - fmt.Fprint(content, "### HTTP Endpoints\n\n") + fmt.Fprintf(content, "- Request: [`%s`](#%s)\n", method.input.GetName(), heading(method.input.key)) } - for _, endpoint := range method.endpoints { - fmt.Fprintf(content, "- `%s` `%s`\n", endpoint.method, endpoint.url) + useMessage(tree, method.input, usedMessages, usedEnums) + if method.outputStream { + fmt.Fprintf(content, "- Server stream of [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) + } else { + fmt.Fprintf(content, "- Response: [`%s`](#%s)\n", method.output.GetName(), heading(method.input.key)) } + useMessage(tree, method.output, usedMessages, usedEnums) + fmt.Fprintln(content) + + if len(method.endpoints) != 0 { + if len(method.endpoints) == 1 { + fmt.Fprint(content, "### HTTP Endpoint\n\n") + } else { + fmt.Fprint(content, "### HTTP Endpoints\n\n") + } + for _, endpoint := range method.endpoints { + fmt.Fprintf(content, "- `%s` `%s`\n", endpoint.method, endpoint.url) + } + fmt.Fprintln(content) + } } } - fmt.Fprint(content, "# Used Messages\n\n") + if len(usedMessages) > 0 { + fmt.Fprint(content, "## Messages\n\n") - var messageKeys []string - for key := range usedMessages { - messageKeys = append(messageKeys, key) - } - sort.Strings(messageKeys) - - for _, messageKey := range messageKeys { - message := usedMessages[messageKey] - fmt.Fprintf(content, "## `%s`\n\n", message.key) - if strings.HasPrefix(messageKey, ".google") { - fmt.Fprintf(content, "%s\n\n", strings.SplitAfter(message.comment, ".")[0]) - } else if message.comment != "" { - fmt.Fprintf(content, "%s\n\n", message.comment) + var messageKeys []string + for key := range usedMessages { + messageKeys = append(messageKeys, key) } - if len(message.fields) > 0 { - fmt.Fprintln(content, "| Field Name | Type | Description |") - fmt.Fprintln(content, "| ---------- | ---- | ----------- |") - for idx, field := range message.fields { - if field.isOneOf { - if idx == 0 || !message.fields[idx-1].isOneOf || message.fields[idx-1].GetOneofIndex() != field.GetOneofIndex() { - oneof := message.GetOneof(field.GetOneofIndex()) - if len(oneof.fields) > 1 { - fmt.Fprintf(content, "| **%s** | **oneof %d** | one of the following %d |\n", oneof.GetName(), len(oneof.fields), len(oneof.fields)) + sort.Strings(messageKeys) + + for _, messageKey := range messageKeys { + message := usedMessages[messageKey] + fmt.Fprintf(content, "### `%s`\n\n", message.key) + if strings.HasPrefix(messageKey, ".google") { + fmt.Fprintf(content, "%s\n\n", strings.SplitAfter(message.comment, ".")[0]) + } else if message.comment != "" { + fmt.Fprintf(content, "%s\n\n", message.comment) + } + if len(message.fields) > 0 { + fmt.Fprintln(content, "| Field Name | Type | Description |") + fmt.Fprintln(content, "| ---------- | ---- | ----------- |") + for idx, field := range message.fields { + if field.isOneOf { + if idx == 0 || !message.fields[idx-1].isOneOf || message.fields[idx-1].GetOneofIndex() != field.GetOneofIndex() { + oneof := message.GetOneof(field.GetOneofIndex()) + if len(oneof.fields) > 1 { + fmt.Fprintf(content, "| **%s** | **oneof %d** | one of the following %d |\n", oneof.GetName(), len(oneof.fields), len(oneof.fields)) + } } - } - } else { - if idx > 0 && message.fields[idx-1].isOneOf { - oneof := message.GetOneof(message.fields[idx-1].GetOneofIndex()) - if len(oneof.fields) > 1 { - fmt.Fprintf(content, "| **%s** | **end oneof %d** | |\n", oneof.GetName(), len(oneof.fields)) + } else { + if idx > 0 && message.fields[idx-1].isOneOf { + oneof := message.GetOneof(message.fields[idx-1].GetOneofIndex()) + if len(oneof.fields) > 1 { + fmt.Fprintf(content, "| **%s** | **end oneof %d** | |\n", oneof.GetName(), len(oneof.fields)) + } } } - } - var fieldType string - if field.repeated { - fieldType += "_repeated_ " - } - typ := strings.ToLower(strings.TrimPrefix(field.GetType().String(), "TYPE_")) - switch typ { - case "message": - friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] - fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) - case "enum": - // TODO(htdvisser): test this - if enum, ok := tree.enums[field.GetTypeName()]; ok { - usedEnums[field.GetTypeName()] = enum + var fieldType string + if field.repeated { + fieldType += "_repeated_ " + } + typ := strings.ToLower(strings.TrimPrefix(field.GetType().String(), "TYPE_")) + switch typ { + case "message": + friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] + fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) + case "enum": + // TODO(htdvisser): test this + if enum, ok := tree.enums[field.GetTypeName()]; ok { + usedEnums[field.GetTypeName()] = enum + } + friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] + fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) + default: + fieldType += fmt.Sprintf("`%s`", typ) } - friendlyType := field.GetTypeName()[strings.LastIndex(field.GetTypeName(), ".")+1:] - fieldType += fmt.Sprintf("[`%s`](#%s)", friendlyType, heading(field.GetTypeName())) - default: - fieldType += fmt.Sprintf("`%s`", typ) + fmt.Fprintf(content, "| `%s` | %s | %s |\n", field.GetName(), fieldType, strings.Replace(field.comment, "\n", " ", -1)) } - fmt.Fprintf(content, "| %s | %s | %s |\n", field.GetName(), fieldType, strings.Replace(field.comment, "\n", " ", -1)) + fmt.Fprintln(content) } - fmt.Fprintln(content) } } if len(usedEnums) > 0 { - fmt.Fprint(content, "# Used Enums\n\n") + fmt.Fprint(content, "## Used Enums\n\n") var enumKeys []string for key := range usedEnums { @@ -204,7 +210,7 @@ func main() { for _, enumKey := range enumKeys { enum := usedEnums[enumKey] - fmt.Fprintf(content, "## `%s`\n\n", enum.key) + fmt.Fprintf(content, "### `%s`\n\n", enum.key) if enum.comment != "" { fmt.Fprintf(content, "%s\n\n", enum.comment) } @@ -213,7 +219,7 @@ func main() { fmt.Fprintln(content, "| Value | Description |") fmt.Fprintln(content, "| ----- | ----------- |") for _, value := range enum.values { - fmt.Fprintf(content, "| %s | %s |\n", value.GetName(), strings.Replace(value.comment, "\n", " ", -1)) + fmt.Fprintf(content, "| `%s` | %s |\n", value.GetName(), strings.Replace(value.comment, "\n", " ", -1)) } fmt.Fprintln(content) } @@ -234,60 +240,6 @@ func main() { }) } - // for k, v := range tree.services { - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Service", k, v.GoString()) - // for _, v := range v.methods { - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Method", v.GoString()) - // for _, v := range v.endpoints { - // debug("Endpoint", v.GoString()) - // } - // } - // } - - // for k, v := range tree.messages { - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Message", k, v.GoString()) - // for _, v := range v.oneofs { - // debug("Oneof", v.GoString()) - // for _, v := range v.fields { - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Field", v.GoString()) - // } - // } - // for _, v := range v.fields { - // if v.isOneOf { - // continue - // } - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Field", v.GoString()) - // } - // } - - // for k, v := range tree.enums { - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Enum", k, v.GoString()) - // for _, v := range v.values { - // if v.comment != "" { - // debug("//", v.comment) - // } - // debug("Value", v.GoString()) - // } - // } - // Send back the results. data, err = proto.Marshal(g.Response) if err != nil { From 2757c272892f81a7c18b9520e69139e833a313bf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 15:03:38 +0100 Subject: [PATCH 2179/2266] Add JSON Req/Res formats for proto HTTP Endpoints --- api/handler/ApplicationManager.md | 221 +++++++++++++++++++++++++++--- utils/protoc-gen-ttndoc/json.go | 99 +++++++++++++ utils/protoc-gen-ttndoc/main.go | 24 +++- 3 files changed, 322 insertions(+), 22 deletions(-) create mode 100644 utils/protoc-gen-ttndoc/json.go diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md index f829a7027..78feba336 100644 --- a/api/handler/ApplicationManager.md +++ b/api/handler/ApplicationManager.md @@ -11,10 +11,24 @@ Applications should first be registered to the Handler with the `RegisterApplica - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`Empty`](#handlerapplicationidentifier) -### HTTP Endpoint +#### HTTP Endpoint - `POST` `/applications` +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{} +``` + ### `GetApplication` GetApplication returns the application with the given identifier (app_id) @@ -22,9 +36,29 @@ GetApplication returns the application with the given identifier (app_id) - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`Application`](#handlerapplicationidentifier) -### HTTP Endpoint +#### HTTP Endpoint + +- `GET` `/applications/{app_id}`(`app_id` can be left out of the request) + +#### JSON Request Format -- `GET` `/applications/{app_id}` +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{ + "app_id": "some-app-id", + "converter": "function Converter(decoded, port) {...", + "decoder": "function Decoder(bytes, port) {...", + "encoder": "Encoder(object, port) {...", + "validator": "Validator(converted, port) {..." +} +``` ### `SetApplication` @@ -33,9 +67,27 @@ SetApplication updates the settings for the application. All fields must be supp - Request: [`Application`](#handlerapplication) - Response: [`Empty`](#handlerapplication) -### HTTP Endpoint +#### HTTP Endpoint + +- `POST` `/applications/{app_id}`(`app_id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "converter": "function Converter(decoded, port) {...", + "decoder": "function Decoder(bytes, port) {...", + "encoder": "Encoder(object, port) {...", + "validator": "Validator(converted, port) {..." +} +``` + +#### JSON Response Format -- `POST` `/applications/{app_id}` +```json +{} +``` ### `DeleteApplication` @@ -44,9 +96,23 @@ DeleteApplication deletes the application with the given identifier (app_id) - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`Empty`](#handlerapplicationidentifier) -### HTTP Endpoint +#### HTTP Endpoint -- `DELETE` `/applications/{app_id}` +- `DELETE` `/applications/{app_id}`(`app_id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{} +``` ### `GetDevice` @@ -55,9 +121,43 @@ GetDevice returns the device with the given identifier (app_id and dev_id) - Request: [`DeviceIdentifier`](#handlerdeviceidentifier) - Response: [`Device`](#handlerdeviceidentifier) -### HTTP Endpoint - -- `GET` `/applications/{app_id}/devices/{dev_id}` +#### HTTP Endpoint + +- `GET` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id" +} +``` + +#### JSON Response Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id", + "lorawan_device": { + "activation_constraints": "local", + "app_eui": "0102030405060708", + "app_id": "some-app-id", + "app_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "dev_addr": "01020304", + "dev_eui": "0102030405060708", + "dev_id": "some-dev-id", + "disable_f_cnt_check": false, + "f_cnt_down": 0, + "f_cnt_up": 0, + "last_seen": 0, + "nwk_s_key": "01020304050607080102030405060708", + "uses32_bit_f_cnt": true + } +} +``` ### `SetDevice` @@ -66,10 +166,41 @@ SetDevice creates or updates a device. All fields must be supplied. - Request: [`Device`](#handlerdevice) - Response: [`Empty`](#handlerdevice) -### HTTP Endpoints - -- `POST` `/applications/{app_id}/devices/{dev_id}` -- `POST` `/applications/{app_id}/devices` +#### HTTP Endpoints + +- `POST` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) +- `POST` `/applications/{app_id}/devices`(`app_id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id", + "lorawan_device": { + "activation_constraints": "local", + "app_eui": "0102030405060708", + "app_id": "some-app-id", + "app_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "dev_addr": "01020304", + "dev_eui": "0102030405060708", + "dev_id": "some-dev-id", + "disable_f_cnt_check": false, + "f_cnt_down": 0, + "f_cnt_up": 0, + "last_seen": 0, + "nwk_s_key": "01020304050607080102030405060708", + "uses32_bit_f_cnt": true + } +} +``` + +#### JSON Response Format + +```json +{} +``` ### `DeleteDevice` @@ -78,9 +209,24 @@ DeleteDevice deletes the device with the given identifier (app_id and dev_id) - Request: [`DeviceIdentifier`](#handlerdeviceidentifier) - Response: [`Empty`](#handlerdeviceidentifier) -### HTTP Endpoint +#### HTTP Endpoint -- `DELETE` `/applications/{app_id}/devices/{dev_id}` +- `DELETE` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id", + "dev_id": "some-dev-id" +} +``` + +#### JSON Response Format + +```json +{} +``` ### `GetDevicesForApplication` @@ -89,9 +235,46 @@ GetDevicesForApplication returns all devices that belong to the application with - Request: [`ApplicationIdentifier`](#handlerapplicationidentifier) - Response: [`DeviceList`](#handlerapplicationidentifier) -### HTTP Endpoint - -- `GET` `/applications/{app_id}/devices` +#### HTTP Endpoint + +- `GET` `/applications/{app_id}/devices`(`app_id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "app_id": "some-app-id" +} +``` + +#### JSON Response Format + +```json +{ + "devices": [ + { + "app_id": "some-app-id", + "dev_id": "some-dev-id", + "lorawan_device": { + "activation_constraints": "local", + "app_eui": "0102030405060708", + "app_id": "some-app-id", + "app_key": "01020304050607080102030405060708", + "app_s_key": "01020304050607080102030405060708", + "dev_addr": "01020304", + "dev_eui": "0102030405060708", + "dev_id": "some-dev-id", + "disable_f_cnt_check": false, + "f_cnt_down": 0, + "f_cnt_up": 0, + "last_seen": 0, + "nwk_s_key": "01020304050607080102030405060708", + "uses32_bit_f_cnt": true + } + } + ] +} +``` ### `DryDownlink` diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go new file mode 100644 index 000000000..fa92b3931 --- /dev/null +++ b/utils/protoc-gen-ttndoc/json.go @@ -0,0 +1,99 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package main + +import ( + "encoding/json" + "os" + "strings" +) + +var exampleValues = map[string]interface{}{ + ".discovery.*.id": "ttn-handler-eu", + ".discovery.*.service_name": "handler", + ".discovery.Announcement.certificate": "-----BEGIN CERTIFICATE-----\n...", + ".discovery.Announcement.net_address": "eu.thethings.network:1904", + ".discovery.Announcement.public_key": "-----BEGIN PUBLIC KEY-----\n...", + ".discovery.Announcement.public": true, + ".discovery.Announcement.service_version": "2.0.0-dev-abcdef...", + ".discovery.Metadata.key": "APP_ID", + ".discovery.Metadata.value": "some-app-id", + ".handler.*.app_id": "some-app-id", + ".handler.*.dev_id": "some-dev-id", + ".handler.Application.converter": "function Converter(decoded, port) {...", + ".handler.Application.decoder": "function Decoder(bytes, port) {...", + ".handler.Application.encoder": "Encoder(object, port) {...", + ".handler.Application.validator": "Validator(converted, port) {...", + ".lorawan.Device.activation_constraints": "local", + ".lorawan.Device.app_eui": "0102030405060708", + ".lorawan.Device.app_id": "some-app-id", + ".lorawan.Device.app_key": "01020304050607080102030405060708", + ".lorawan.Device.app_s_key": "01020304050607080102030405060708", + ".lorawan.Device.dev_addr": "01020304", + ".lorawan.Device.dev_eui": "0102030405060708", + ".lorawan.Device.dev_id": "some-dev-id", + ".lorawan.Device.nwk_s_key": "01020304050607080102030405060708", + ".lorawan.Device.uses32_bit_f_cnt": true, + ".handler.*.port": 1, + ".handler.*.fields": `{"light":100}`, + ".handler.LogEntry.function": "decoder", + ".handler.LogEntry.fields": `["TTN",123]`, + ".handler.*.payload": "ZA==", + ".handler.DryDownlinkMessage.payload": "", +} + +func (m *message) MapExample(tree *tree) map[string]interface{} { + example := make(map[string]interface{}) + for _, field := range m.fields { + typ := strings.ToLower(strings.TrimPrefix(field.GetType().String(), "TYPE_")) + var val interface{} + + if exampleValue, ok := exampleValues[field.key]; ok { + val = exampleValue + } else if exampleValue, ok := exampleValues[field.key[:strings.Index(field.key[1:], ".")+1]+".*."+field.GetName()]; ok { + val = exampleValue + } else { + os.Stderr.WriteString(field.key + "\n") + switch typ { + case "message": + if message, ok := tree.messages[field.GetTypeName()]; ok { + val = message.MapExample(tree) + } + case "enum": + if enums, ok := tree.enums[field.GetTypeName()]; ok { + val = enums.MapExample(tree) + } + case "string": + val = "" + case "bool": + val = false + case "bytes": + val = []byte{} + case "int64", "uint32": + val = 0 + default: + os.Stderr.WriteString(typ + "\n") + } + } + if field.repeated { + example[field.GetName()] = []interface{}{val} + } else { + example[field.GetName()] = val + } + } + return example +} + +func (m *enum) MapExample(tree *tree) string { + if len(m.values) == 0 { + return "" + } + return m.values[len(m.values)-1].GetName() +} + +func (m *message) JSONExample(tree *tree) string { + example := m.MapExample(tree) + exampleBytes, _ := json.MarshalIndent(example, "", " ") + return string(exampleBytes) +} diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go index a620064d1..e493a479c 100644 --- a/utils/protoc-gen-ttndoc/main.go +++ b/utils/protoc-gen-ttndoc/main.go @@ -13,6 +13,8 @@ import ( "sort" "strings" + "regexp" + "github.com/golang/protobuf/proto" "github.com/golang/protobuf/protoc-gen-go/generator" plugin "github.com/golang/protobuf/protoc-gen-go/plugin" @@ -123,14 +125,30 @@ func main() { if len(method.endpoints) != 0 { if len(method.endpoints) == 1 { - fmt.Fprint(content, "### HTTP Endpoint\n\n") + fmt.Fprint(content, "#### HTTP Endpoint\n\n") } else { - fmt.Fprint(content, "### HTTP Endpoints\n\n") + fmt.Fprint(content, "#### HTTP Endpoints\n\n") } for _, endpoint := range method.endpoints { - fmt.Fprintf(content, "- `%s` `%s`\n", endpoint.method, endpoint.url) + fmt.Fprintf(content, "- `%s` `%s`", endpoint.method, endpoint.url) + var params []string + for _, match := range regexp.MustCompile(`\{([a-z_]+)\}`).FindAllStringSubmatch(endpoint.url, -1) { + if match != nil && len(match) == 2 && match[1] != "" { + params = append(params, fmt.Sprintf("`%s`", match[1])) + } + } + if len(params) > 0 { + fmt.Fprintf(content, "(%s can be left out of the request)", strings.Join(params, ", ")) + } + fmt.Fprintln(content) } fmt.Fprintln(content) + + fmt.Fprint(content, "#### JSON Request Format\n\n") + fmt.Fprintf(content, "```json\n%s\n```\n\n", method.input.JSONExample(tree)) + + fmt.Fprint(content, "#### JSON Response Format\n\n") + fmt.Fprintf(content, "```json\n%s\n```\n\n", method.output.JSONExample(tree)) } } } From b7c32160e08f1fe757926cdc6d864973f7f84033 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 15:24:22 +0100 Subject: [PATCH 2180/2266] Add API authentication doc --- api/API_AUTHENTICATION.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 api/API_AUTHENTICATION.md diff --git a/api/API_AUTHENTICATION.md b/api/API_AUTHENTICATION.md new file mode 100644 index 000000000..dd30ec738 --- /dev/null +++ b/api/API_AUTHENTICATION.md @@ -0,0 +1,26 @@ +# Authentication + +Currently, there are two methods of authenticating to the gRPC APIs: + +- Bearer token: OAuth 2.0 Bearer JSON Web Tokens (preferred) +- Access keys: Application access keys (only for `ApplicationManager` API) + +## Bearer Token + +This authentication method is the preferred method of authenticating. + +_TODO: Add reference to Account Server reference_ + +You can authenticate to the gRPC endpoint by supplying a `token` field in the Metadata. The value of this field should be the JSON Web Token. + +For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Bearer `. + +## Access Key + +With this authentication method, the server will exchange an Access Key for a Bearer Token internally. + +_TODO: Add reference to Account Server reference_ + +You can authenticate to the gRPC endpoint by supplying a `key` field in the Metadata. The value of this field should be the Application access key. + +For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Key `. From 40973191c0230091375193c8cf5a4241e8adc8d3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 15:29:57 +0100 Subject: [PATCH 2181/2266] Move HTTP Auth examples to API_AUTHENTICATION.md --- api/API_AUTHENTICATION.md | 38 ++++++- api/handler/HTTP-API.md | 234 -------------------------------------- 2 files changed, 36 insertions(+), 236 deletions(-) delete mode 100644 api/handler/HTTP-API.md diff --git a/api/API_AUTHENTICATION.md b/api/API_AUTHENTICATION.md index dd30ec738..30846e55c 100644 --- a/api/API_AUTHENTICATION.md +++ b/api/API_AUTHENTICATION.md @@ -9,18 +9,52 @@ Currently, there are two methods of authenticating to the gRPC APIs: This authentication method is the preferred method of authenticating. -_TODO: Add reference to Account Server reference_ +### gRPC You can authenticate to the gRPC endpoint by supplying a `token` field in the Metadata. The value of this field should be the JSON Web Token. +**Example (Go):** + +```go +md := metadata.Pairs( + "token", "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl", +) +ctx := metadata.NewContext(context.Background(), md) +``` + +### HTTP + For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Bearer `. +**Example:** + +``` +Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl +``` + ## Access Key With this authentication method, the server will exchange an Access Key for a Bearer Token internally. -_TODO: Add reference to Account Server reference_ +### gRPC You can authenticate to the gRPC endpoint by supplying a `key` field in the Metadata. The value of this field should be the Application access key. +**Example (Go):** + +```go +md := metadata.Pairs( + "key", "ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ", +) +ctx := metadata.NewContext(context.Background(), md) +``` + +### HTTP + For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Key `. + +**Exampele:** + +``` +Authorization: Key ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ +``` diff --git a/api/handler/HTTP-API.md b/api/handler/HTTP-API.md deleted file mode 100644 index a04339c47..000000000 --- a/api/handler/HTTP-API.md +++ /dev/null @@ -1,234 +0,0 @@ -# HTTP API Reference - -The Handler HTTP API is a wrapper around the Handler's gRPC interface. We recommend everyone to use the gRPC interface if possible. - -## Authorization - -Authorization to the Handler HTTP API is done with the `Authorization` header in your requests. -This header should either contain a `Bearer` token with the JSON Web Token issued by the account server or a `Key` that is issued by the account server and can be exchanged to a JSON Web Token. - -Example (`Bearer`): - -``` -Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhcHBzIjp7InRlc3QiOlsic2V0dGluZ3MiXX19.VGhpcyBpcyB0aGUgc2lnbmF0dXJl -``` - -Example (`Key`): - -``` -Authorization: Key ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ -``` - -## Register Application - -Request: - -``` -POST /applications - -{ - "app_id": "the-app-id" -} -``` - -Response: - -``` -200 OK - -{} -``` - -## Get Application - -Request: - -``` -GET /applications/the-app-id -``` - -Response: - -``` -200 OK - -{ - "app_id": "the-app-id", - "decoder": "function Decoder(...) { ... }" - "converter": "function Converter(...) { ... }" - "validator": "function Validator(...) { ... }" - "encoder": "function Encoder(...) { ... }" -} -``` - -## Get Devices For Application - -Request: - -``` -GET /applications/the-app-id/devices -``` - -Response: - -``` -200 OK - -{ - "devices": [ - { - "app_id": "the-app-id", - "dev_id": "the-dev-id", - "lorawan_device": { - "app_eui": "70B3D57EF0000001", - "dev_eui": "70B3D57EF0000001", - "app_id": "the-app-id", - "dev_id": "the-dev-id", - "dev_addr": "", - "nwk_s_key": "", - "app_s_key": "", - "app_key": "01020304050607080102030405060708" - } - }, - ... - ] -} -``` - -## Set Application (after it has been registered) - -Request: - -``` -POST /applications/the-app-id -{ - "decoder": "function Decoder(...) { ... }" - "converter": "function Converter(...) { ... }" - "validator": "function Validator(...) { ... }" - "encoder": "function Encoder(...) { ... }" -} -``` - -Response: - -``` -200 OK - -{} -``` - -## Delete Application - -Request: - -``` -DELETE /applications/the-app-id -``` - -Response: - -``` -200 OK - -{} -``` - -## Get Device - -``` -GET /applications/the-app-id/devices/the-dev-id -``` - -Response: - -``` -200 OK - -{ - "app_id": "the-app-id", - "dev_id": "the-dev-id", - "lorawan_device": { - "app_eui": "70B3D57EF0000001", - "dev_eui": "70B3D57EF0000001", - "app_id": "the-app-id", - "dev_id": "the-dev-id", - "dev_addr": "", - "nwk_s_key": "", - "app_s_key": "", - "app_key": "01020304050607080102030405060708" - } -} -``` - -## Set Device - -``` -POST /applications/the-app-id/devices/the-dev-id - -{ - "lorawan_device": { - "app_eui": "70B3D57EF0000001", - "dev_eui": "70B3D57EF0000001", - "dev_addr": "26000001", - "nwk_s_key": "01020304050607080102030405060708", - "app_s_key": "01020304050607080102030405060708", - "app_key": "" - } -} -``` - -Response: - -``` -200 OK - -{} -``` - -## Delete Device - -Request: - -``` -DELETE /applications/the-app-id/devices/the-dev-id -``` - -Response: - -``` -200 OK - -{} -``` - -## Types - -### Application - -``` -app_id string -decoder string -converter string -validator string -encoder string -``` - -### Device - -``` -app_id string -dev_id string - -lorawan_device: - app_eui string - dev_eui string - dev_addr string - nwk_s_key string - app_s_key string - app_key string - f_cnt_up int - f_cnt_down int - disable_f_cnt_check bool - uses32_bit_f_cnt bool - last_seen int (unix-nanoseconds) -``` From 8cd116da0552416025db621c9cc8a2572a301f69 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 15:39:18 +0100 Subject: [PATCH 2182/2266] Add all scalar protobuf types and remove logging --- utils/protoc-gen-ttndoc/json.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go index fa92b3931..5397f8a58 100644 --- a/utils/protoc-gen-ttndoc/json.go +++ b/utils/protoc-gen-ttndoc/json.go @@ -5,7 +5,6 @@ package main import ( "encoding/json" - "os" "strings" ) @@ -54,7 +53,6 @@ func (m *message) MapExample(tree *tree) map[string]interface{} { } else if exampleValue, ok := exampleValues[field.key[:strings.Index(field.key[1:], ".")+1]+".*."+field.GetName()]; ok { val = exampleValue } else { - os.Stderr.WriteString(field.key + "\n") switch typ { case "message": if message, ok := tree.messages[field.GetTypeName()]; ok { @@ -69,11 +67,12 @@ func (m *message) MapExample(tree *tree) map[string]interface{} { case "bool": val = false case "bytes": - val = []byte{} - case "int64", "uint32": + val = "" + case "int64", "int32", "uint64", "uint32", "sint64", "sint32", "fixed64", "fixed32", "sfixed32", "sfixed64": val = 0 + case "double", "float": + val = 0.0 default: - os.Stderr.WriteString(typ + "\n") } } if field.repeated { From a07b549a01c3960c80d6e7238279ae881ed89eda Mon Sep 17 00:00:00 2001 From: Fokke Zandbergen Date: Thu, 17 Nov 2016 16:07:51 +0100 Subject: [PATCH 2183/2266] Fix typo and refer to both gRPC and HTTP (#364) --- api/API_AUTHENTICATION.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/API_AUTHENTICATION.md b/api/API_AUTHENTICATION.md index 30846e55c..f91bcc32b 100644 --- a/api/API_AUTHENTICATION.md +++ b/api/API_AUTHENTICATION.md @@ -1,6 +1,6 @@ # Authentication -Currently, there are two methods of authenticating to the gRPC APIs: +Currently, there are two methods of authenticating to the gRPC and HTTP APIs: - Bearer token: OAuth 2.0 Bearer JSON Web Tokens (preferred) - Access keys: Application access keys (only for `ApplicationManager` API) @@ -53,7 +53,7 @@ ctx := metadata.NewContext(context.Background(), md) For HTTP Endpoints, you should supply the `Authorization` header: `Authorization: Key `. -**Exampele:** +**Example:** ``` Authorization: Key ttn-account-preview.n4BAoKOGuK2hj7MXg_OVtpLO0BTJI8lLzt66UsvTlUvZPsi6FADOptnmSH3e3PuQzbLLEUhXxYhkxr34xyUqBQ From af034426cd507b2c42400db091e220008755ecde Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 16:24:45 +0100 Subject: [PATCH 2184/2266] Add comments to discovery cache options --- core/discovery/announcement/cache.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/discovery/announcement/cache.go b/core/discovery/announcement/cache.go index 902e2e956..4d926a57f 100644 --- a/core/discovery/announcement/cache.go +++ b/core/discovery/announcement/cache.go @@ -26,10 +26,10 @@ type CacheOptions struct { // DefaultCacheOptions are the default CacheOptions var DefaultCacheOptions = CacheOptions{ - ServiceCacheSize: 100, - ServiceCacheExpiration: 10 * time.Minute, - ListCacheSize: 100, - ListCacheExpiration: 10 * time.Second, + ServiceCacheSize: 1000, // Total number of announcements to cache, thousand should be enough for now + ServiceCacheExpiration: 10 * time.Minute, // Items be updated by ListCache fetch anyway + ListCacheSize: 10, // We actually only need 3: router/broker/handler + ListCacheExpiration: 10 * time.Second, // We can afford to fetch every 10 seconds } type serviceCacheKey struct { From aff423a782806e8d560cf09dade531a449de8e19 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 16:44:06 +0100 Subject: [PATCH 2185/2266] Fix cache keys for discovery ServiceCache --- core/discovery/announcement/cache.go | 29 ++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/core/discovery/announcement/cache.go b/core/discovery/announcement/cache.go index 4d926a57f..711013b29 100644 --- a/core/discovery/announcement/cache.go +++ b/core/discovery/announcement/cache.go @@ -4,6 +4,8 @@ package announcement import ( + "fmt" + "strings" "time" "github.com/TheThingsNetwork/ttn/core/types" @@ -32,20 +34,19 @@ var DefaultCacheOptions = CacheOptions{ ListCacheExpiration: 10 * time.Second, // We can afford to fetch every 10 seconds } -type serviceCacheKey struct { - ServiceName string - ServiceID string +func serviceCacheKey(serviceName, serviceID string) string { + return fmt.Sprintf("%s:%s", serviceName, serviceID) } // NewCachedAnnouncementStore returns a cache wrapper around the existing store func NewCachedAnnouncementStore(store Store, options CacheOptions) Store { - serviceCache := gcache.New(options.ServiceCacheSize).Expiration(options.ServiceCacheExpiration).ARC(). + serviceCache := gcache.New(options.ServiceCacheSize).Expiration(options.ServiceCacheExpiration).LFU(). LoaderFunc(func(k interface{}) (interface{}, error) { - key := k.(*serviceCacheKey) - return store.Get(key.ServiceName, key.ServiceID) + key := strings.Split(k.(string), ":") + return store.Get(key[0], key[1]) }).Build() - listCache := gcache.New(options.ListCacheSize).Expiration(options.ListCacheExpiration).ARC(). + listCache := gcache.New(options.ListCacheSize).Expiration(options.ListCacheExpiration).LFU(). LoaderFunc(func(k interface{}) (interface{}, error) { key := k.(string) announcements, err := store.ListService(key) @@ -54,7 +55,7 @@ func NewCachedAnnouncementStore(store Store, options CacheOptions) Store { } go func(announcements []*Announcement) { for _, announcement := range announcements { - serviceCache.Set(&serviceCacheKey{announcement.ServiceName, announcement.ID}, announcement) + serviceCache.Set(serviceCacheKey(announcement.ServiceName, announcement.ID), announcement) } }(announcements) return announcements, nil @@ -81,7 +82,7 @@ func (s *cachedAnnouncementStore) ListService(serviceName string) ([]*Announceme } func (s *cachedAnnouncementStore) Get(serviceName, serviceID string) (*Announcement, error) { - a, err := s.serviceCache.Get(&serviceCacheKey{serviceName, serviceID}) + a, err := s.serviceCache.Get(serviceCacheKey(serviceName, serviceID)) if err != nil { return nil, err } @@ -89,7 +90,7 @@ func (s *cachedAnnouncementStore) Get(serviceName, serviceID string) (*Announcem } func (s *cachedAnnouncementStore) GetMetadata(serviceName, serviceID string) ([]Metadata, error) { - a, err := s.serviceCache.Get(&serviceCacheKey{serviceName, serviceID}) + a, err := s.serviceCache.Get(serviceCacheKey(serviceName, serviceID)) if err != nil { return nil, err } @@ -110,7 +111,7 @@ func (s *cachedAnnouncementStore) Set(new *Announcement) error { if err := s.backingStore.Set(new); err != nil { return err } - s.serviceCache.Remove(&serviceCacheKey{new.ServiceName, new.ID}) + s.serviceCache.Remove(serviceCacheKey(new.ServiceName, new.ID)) s.listCache.Remove(&new.ServiceName) return nil } @@ -119,7 +120,7 @@ func (s *cachedAnnouncementStore) AddMetadata(serviceName, serviceID string, met if err := s.backingStore.AddMetadata(serviceName, serviceID, metadata...); err != nil { return err } - s.serviceCache.Remove(&serviceCacheKey{serviceName, serviceID}) + s.serviceCache.Remove(serviceCacheKey(serviceName, serviceID)) s.listCache.Remove(&serviceName) return nil } @@ -128,7 +129,7 @@ func (s *cachedAnnouncementStore) RemoveMetadata(serviceName, serviceID string, if err := s.backingStore.RemoveMetadata(serviceName, serviceID, metadata...); err != nil { return err } - s.serviceCache.Remove(&serviceCacheKey{serviceName, serviceID}) + s.serviceCache.Remove(serviceCacheKey(serviceName, serviceID)) s.listCache.Remove(&serviceName) return nil } @@ -137,7 +138,7 @@ func (s *cachedAnnouncementStore) Delete(serviceName, serviceID string) error { if err := s.backingStore.Delete(serviceName, serviceID); err != nil { return err } - s.serviceCache.Remove(&serviceCacheKey{serviceName, serviceID}) + s.serviceCache.Remove(serviceCacheKey(serviceName, serviceID)) s.listCache.Remove(&serviceName) return nil } From 0fb090c9786c42aab1ad08319acc77ce43799c17 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 16:44:59 +0100 Subject: [PATCH 2186/2266] Add HTTP Proxy to Discovery Server --- api/discovery/Discovery.md | 73 ++++++++++++ api/discovery/discovery.pb.go | 159 ++++++++++++++++--------- api/discovery/discovery.pb.gw.go | 194 +++++++++++++++++++++++++++++++ api/discovery/discovery.proto | 13 ++- cmd/discovery.go | 38 +++++- 5 files changed, 415 insertions(+), 62 deletions(-) create mode 100644 api/discovery/discovery.pb.gw.go diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index 86758a213..d4602455d 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -18,6 +18,44 @@ Get all announcements for a specific service - Request: [`GetAllRequest`](#discoverygetallrequest) - Response: [`AnnouncementsResponse`](#discoverygetallrequest) +#### HTTP Endpoint + +- `GET` `/announcements/{service_name}`(`service_name` can be left out of the request) + +#### JSON Request Format + +```json +{ + "service_name": "handler" +} +``` + +#### JSON Response Format + +```json +{ + "services": [ + { + "certificate": "-----BEGIN CERTIFICATE-----\n...", + "description": "", + "id": "ttn-handler-eu", + "metadata": [ + { + "key": "APP_ID", + "value": "some-app-id" + } + ], + "net_address": "eu.thethings.network:1904", + "public": true, + "public_key": "-----BEGIN PUBLIC KEY-----\n...", + "service_name": "handler", + "service_version": "2.0.0-dev-abcdef...", + "url": "" + } + ] +} +``` + ### `Get` Get a specific announcement @@ -25,6 +63,41 @@ Get a specific announcement - Request: [`GetRequest`](#discoverygetrequest) - Response: [`Announcement`](#discoverygetrequest) +#### HTTP Endpoint + +- `GET` `/announcements/{service_name}/{id}`(`service_name`, `id` can be left out of the request) + +#### JSON Request Format + +```json +{ + "id": "ttn-handler-eu", + "service_name": "handler" +} +``` + +#### JSON Response Format + +```json +{ + "certificate": "-----BEGIN CERTIFICATE-----\n...", + "description": "", + "id": "ttn-handler-eu", + "metadata": [ + { + "key": "APP_ID", + "value": "some-app-id" + } + ], + "net_address": "eu.thethings.network:1904", + "public": true, + "public_key": "-----BEGIN PUBLIC KEY-----\n...", + "service_name": "handler", + "service_version": "2.0.0-dev-abcdef...", + "url": "" +} +``` + ### `AddMetadata` Add metadata to an announement diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index c4a0e7d22..f093cd6ae 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -22,6 +22,7 @@ import proto "github.com/golang/protobuf/proto" import fmt "fmt" import math "math" import google_protobuf "github.com/golang/protobuf/ptypes/empty" +import _ "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api" import ( context "golang.org/x/net/context" @@ -41,18 +42,25 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package +// The Key indicates the metadata type type Metadata_Key int32 const ( + // OTHER indicates arbitrary metadata. We currently don't allow this. Metadata_OTHER Metadata_Key = 0 // The value for PREFIX consists of 1 byte denoting the number of bits, - // followed by the prefix and enough trailing bits to fill 4 octets + // followed by the prefix and enough trailing bits to fill 4 octets. + // Only authorized brokers can announce PREFIX metadata. Metadata_PREFIX Metadata_Key = 1 - // APP_EUI is used for announcing join handlers. - // The value for APP_EUI is the byte slice of the AppEUI string + // APP_EUI is used for announcing join handlers. The value for APP_EUI + // is the byte slice of the AppEUI. + // Only authorized join handlers can announce APP_EUI metadata (and we + // don't have any of those yet). Metadata_APP_EUI Metadata_Key = 2 - // APP_ID is used for announcing regular handlers - // The value for APP_ID is the byte slice of the AppID string + // APP_ID is used for announcing that this handler is responsible for + // a certain AppID. The value for APP_ID is the byte slice of the AppID + // string. This metadata can only be added if the requesting client is + // authorized to manage this AppID. Metadata_APP_ID Metadata_Key = 3 ) @@ -74,9 +82,12 @@ func (x Metadata_Key) String() string { } func (Metadata_Key) EnumDescriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0, 0} } +// Announcements have a list of Metadata type Metadata struct { - Key Metadata_Key `protobuf:"varint,1,opt,name=key,proto3,enum=discovery.Metadata_Key" json:"key,omitempty"` - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` + // The key indicates the metadata type + Key Metadata_Key `protobuf:"varint,1,opt,name=key,proto3,enum=discovery.Metadata_Key" json:"key,omitempty"` + // The value depends on the key type + Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` } func (m *Metadata) Reset() { *m = Metadata{} } @@ -84,17 +95,28 @@ func (m *Metadata) String() string { return proto.CompactTextString(m func (*Metadata) ProtoMessage() {} func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } +// The Announcement of a service (also called component) type Announcement struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - ServiceVersion string `protobuf:"bytes,3,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` - Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` - Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` - Public bool `protobuf:"varint,6,opt,name=public,proto3" json:"public,omitempty"` - NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` - PublicKey string `protobuf:"bytes,12,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` - Certificate string `protobuf:"bytes,13,opt,name=certificate,proto3" json:"certificate,omitempty"` - Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` + // The ID of the component + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the component (router/broker/handler) + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Service version in the form "[version]-[commit] ([build date])" + ServiceVersion string `protobuf:"bytes,3,opt,name=service_version,json=serviceVersion,proto3" json:"service_version,omitempty"` + // Description of the component + Description string `protobuf:"bytes,4,opt,name=description,proto3" json:"description,omitempty"` + // URL with documentation or more information about this component + Url string `protobuf:"bytes,5,opt,name=url,proto3" json:"url,omitempty"` + // Indicates whether this service is part of The Things Network (the public community network) + Public bool `protobuf:"varint,6,opt,name=public,proto3" json:"public,omitempty"` + // Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) + NetAddress string `protobuf:"bytes,11,opt,name=net_address,json=netAddress,proto3" json:"net_address,omitempty"` + // ECDSA public key of this component + PublicKey string `protobuf:"bytes,12,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` + // TLS Certificate (if TLS is enabled) + Certificate string `protobuf:"bytes,13,opt,name=certificate,proto3" json:"certificate,omitempty"` + // Metadata for this component + Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } func (m *Announcement) Reset() { *m = Announcement{} } @@ -110,6 +132,7 @@ func (m *Announcement) GetMetadata() []*Metadata { } type GetAllRequest struct { + // The name of the service (router/broker/handler) ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` } @@ -118,8 +141,11 @@ func (m *GetAllRequest) String() string { return proto.CompactTextStr func (*GetAllRequest) ProtoMessage() {} func (*GetAllRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } +// The identifier of the service that should be returned type GetRequest struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The ID of the service + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the service (router/broker/handler) ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` } @@ -128,8 +154,11 @@ func (m *GetRequest) String() string { return proto.CompactTextString func (*GetRequest) ProtoMessage() {} func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{3} } +// The metadata to add or remove from an announement type MetadataRequest struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The ID of the service that should be modified + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + // The name of the service (router/broker/handler) that should be modified ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` Metadata *Metadata `protobuf:"bytes,11,opt,name=metadata" json:"metadata,omitempty"` } @@ -146,6 +175,7 @@ func (m *MetadataRequest) GetMetadata() *Metadata { return nil } +// A list of announcements type AnnouncementsResponse struct { Services []*Announcement `protobuf:"bytes,1,rep,name=services" json:"services,omitempty"` } @@ -183,10 +213,15 @@ const _ = grpc.SupportPackageIsVersion4 // Client API for Discovery service type DiscoveryClient interface { + // Announce your component to the Discovery server Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // Get all announcements for a specific service GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) + // Get a specific announcement Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) + // Add metadata to an announement AddMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) + // Delete metadata from an announcement DeleteMetadata(ctx context.Context, in *MetadataRequest, opts ...grpc.CallOption) (*google_protobuf.Empty, error) } @@ -246,10 +281,15 @@ func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataReques // Server API for Discovery service type DiscoveryServer interface { + // Announce your component to the Discovery server Announce(context.Context, *Announcement) (*google_protobuf.Empty, error) + // Get all announcements for a specific service GetAll(context.Context, *GetAllRequest) (*AnnouncementsResponse, error) + // Get a specific announcement Get(context.Context, *GetRequest) (*Announcement, error) + // Add metadata to an announement AddMetadata(context.Context, *MetadataRequest) (*google_protobuf.Empty, error) + // Delete metadata from an announcement DeleteMetadata(context.Context, *MetadataRequest) (*google_protobuf.Empty, error) } @@ -1755,43 +1795,46 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 597 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0xad, 0xe3, 0xaf, 0xf9, 0x92, 0xeb, 0x34, 0x8d, 0x06, 0x5a, 0xac, 0x20, 0x82, 0xc9, 0x86, - 0xb0, 0xc0, 0x96, 0x52, 0x75, 0x85, 0x50, 0x65, 0x94, 0x50, 0xaa, 0xd2, 0x52, 0x59, 0x05, 0x21, - 0x36, 0x91, 0x63, 0xdf, 0xa6, 0xa3, 0xda, 0x63, 0xe3, 0x19, 0x07, 0x65, 0xcb, 0x53, 0xf0, 0x48, - 0xb0, 0x41, 0x3c, 0x02, 0x2a, 0x2f, 0x82, 0xfc, 0x5b, 0x47, 0x6d, 0x84, 0x0a, 0x3b, 0xcf, 0xb9, - 0xe7, 0x9c, 0xb9, 0x73, 0x66, 0xae, 0xe1, 0xf9, 0x8c, 0x8a, 0xf3, 0x78, 0xaa, 0x3b, 0x81, 0x6f, - 0x9c, 0x9e, 0xe3, 0xe9, 0x39, 0x65, 0x33, 0x7e, 0x8c, 0xe2, 0x53, 0x10, 0x5d, 0x18, 0x42, 0x30, - 0xc3, 0x0e, 0xa9, 0xe1, 0x52, 0xee, 0x04, 0x73, 0x8c, 0x16, 0x57, 0x5f, 0x7a, 0x18, 0x05, 0x22, - 0x20, 0xcd, 0x12, 0xe8, 0xde, 0x9f, 0x05, 0xc1, 0xcc, 0x43, 0x23, 0x2d, 0x4c, 0xe3, 0x33, 0x03, - 0xfd, 0x50, 0xe4, 0xbc, 0xfe, 0x67, 0x09, 0x1a, 0x47, 0x28, 0x6c, 0xd7, 0x16, 0x36, 0x79, 0x02, - 0xf2, 0x05, 0x2e, 0x54, 0x49, 0x93, 0x06, 0xed, 0xe1, 0x3d, 0xfd, 0xca, 0xb3, 0x60, 0xe8, 0x87, - 0xb8, 0xb0, 0x12, 0x0e, 0xb9, 0x0b, 0xeb, 0x73, 0xdb, 0x8b, 0x51, 0xad, 0x69, 0xd2, 0xa0, 0x65, - 0x65, 0x8b, 0xfe, 0x2e, 0xc8, 0x87, 0xb8, 0x20, 0x4d, 0x58, 0x7f, 0x73, 0xfa, 0x6a, 0x6c, 0x75, - 0xd6, 0x08, 0x40, 0xfd, 0xc4, 0x1a, 0xbf, 0x3c, 0x78, 0xdf, 0x91, 0x88, 0x02, 0xff, 0x9b, 0x27, - 0x27, 0x93, 0xf1, 0xdb, 0x83, 0x4e, 0x2d, 0x29, 0x24, 0x8b, 0x83, 0x51, 0x47, 0xee, 0x7f, 0xab, - 0x41, 0xcb, 0x64, 0x2c, 0x88, 0x99, 0x83, 0x3e, 0x32, 0x41, 0xda, 0x50, 0xa3, 0x6e, 0xda, 0x47, - 0xd3, 0xaa, 0x51, 0x97, 0x3c, 0x82, 0x16, 0xc7, 0x68, 0x4e, 0x1d, 0x9c, 0x30, 0xdb, 0xcf, 0x36, - 0x6d, 0x5a, 0x4a, 0x8e, 0x1d, 0xdb, 0x3e, 0x92, 0xc7, 0xb0, 0x59, 0x50, 0xe6, 0x18, 0x71, 0x1a, - 0x30, 0x55, 0x4e, 0x59, 0xed, 0x1c, 0x7e, 0x97, 0xa1, 0x44, 0x03, 0xc5, 0x45, 0xee, 0x44, 0x34, - 0x14, 0x09, 0xe9, 0xbf, 0xcc, 0xaa, 0x02, 0x91, 0x0e, 0xc8, 0x71, 0xe4, 0xa9, 0xeb, 0x69, 0x25, - 0xf9, 0x24, 0xdb, 0x50, 0x0f, 0xe3, 0xa9, 0x47, 0x1d, 0xb5, 0xae, 0x49, 0x83, 0x86, 0x95, 0xaf, - 0xc8, 0x43, 0x50, 0x18, 0x8a, 0x89, 0xed, 0xba, 0x11, 0x72, 0xae, 0x2a, 0xa9, 0x02, 0x18, 0x0a, - 0x33, 0x43, 0xc8, 0x03, 0x80, 0x8c, 0x3a, 0x49, 0x82, 0x6d, 0xa5, 0xf5, 0x66, 0x86, 0x24, 0x41, - 0x69, 0xa0, 0x38, 0x18, 0x09, 0x7a, 0x46, 0x1d, 0x5b, 0xa0, 0xba, 0x91, 0xf5, 0x52, 0x81, 0x88, - 0x01, 0x0d, 0x3f, 0x0f, 0x5f, 0xdd, 0xd2, 0xe4, 0x81, 0x32, 0xbc, 0x73, 0xc3, 0xbd, 0x58, 0x25, - 0xa9, 0x3f, 0x84, 0x8d, 0x7d, 0x14, 0xa6, 0xe7, 0x59, 0xf8, 0x31, 0x46, 0x2e, 0xae, 0x65, 0x27, - 0x5d, 0xcb, 0xae, 0xbf, 0x07, 0xb0, 0x8f, 0xa2, 0x10, 0xdc, 0x3e, 0xfc, 0x7e, 0x0c, 0x9b, 0x65, - 0x2b, 0x7f, 0xed, 0xb2, 0x74, 0xd6, 0x24, 0xca, 0x3f, 0x9e, 0xf5, 0x35, 0x6c, 0x55, 0x9f, 0x0d, - 0xb7, 0x90, 0x87, 0x01, 0xe3, 0x48, 0x76, 0xa0, 0x91, 0x1b, 0x73, 0x55, 0x4a, 0x53, 0xab, 0xbe, - 0xe6, 0xaa, 0xc6, 0x2a, 0x89, 0xc3, 0xef, 0x35, 0x68, 0x8e, 0x0a, 0x12, 0x79, 0x06, 0x8d, 0x82, - 0x47, 0x56, 0x89, 0xbb, 0xdb, 0x7a, 0x36, 0x5b, 0x7a, 0x31, 0x5b, 0xfa, 0x38, 0x99, 0x2d, 0x32, - 0x82, 0x7a, 0x76, 0x09, 0x44, 0xad, 0x48, 0x97, 0xee, 0xa5, 0xab, 0xad, 0x30, 0xbd, 0x3a, 0xc5, - 0x2e, 0xc8, 0xfb, 0x28, 0xc8, 0xd6, 0xb2, 0x45, 0xa1, 0x5f, 0xd5, 0x14, 0x31, 0x41, 0x31, 0x5d, - 0xb7, 0x1c, 0xea, 0xee, 0x4d, 0x19, 0xe6, 0x1e, 0xab, 0xfb, 0x6f, 0x8f, 0xd0, 0x43, 0x81, 0xff, - 0xe2, 0x32, 0x24, 0xd0, 0x29, 0xf3, 0x3c, 0xb2, 0x99, 0x3d, 0xc3, 0xe8, 0xc5, 0xde, 0xd7, 0xcb, - 0x9e, 0xf4, 0xe3, 0xb2, 0x27, 0xfd, 0xbc, 0xec, 0x49, 0x5f, 0x7e, 0xf5, 0xd6, 0x3e, 0x3c, 0xbd, - 0xd5, 0x8f, 0x6e, 0x5a, 0x4f, 0x37, 0xd9, 0xf9, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xeb, 0x91, 0x67, - 0x60, 0x20, 0x05, 0x00, 0x00, + // 653 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcf, 0x6e, 0xd3, 0x4e, + 0x10, 0xae, 0xe3, 0x5f, 0xf3, 0x4b, 0xc6, 0x69, 0x1a, 0x2d, 0xb4, 0x58, 0x81, 0xa6, 0xc1, 0x02, + 0x11, 0x90, 0xb0, 0xa5, 0x54, 0x9c, 0x10, 0xaa, 0x82, 0x12, 0x4a, 0x55, 0x5a, 0x2a, 0xab, 0x20, + 0xc4, 0x25, 0xda, 0xd8, 0xd3, 0x74, 0x55, 0x67, 0x1d, 0xec, 0x75, 0x50, 0x54, 0xf5, 0xd2, 0x57, + 0xe0, 0xc2, 0x95, 0xb7, 0x81, 0x1b, 0x12, 0x2f, 0x80, 0x0a, 0x0f, 0x82, 0xfc, 0xb7, 0xae, 0xda, + 0x80, 0x0a, 0x37, 0xef, 0x37, 0xdf, 0x7c, 0x33, 0xfb, 0x8d, 0x67, 0xe1, 0xc9, 0x90, 0x89, 0x83, + 0x60, 0xa0, 0x5b, 0xee, 0xc8, 0xd8, 0x3b, 0xc0, 0xbd, 0x03, 0xc6, 0x87, 0xfe, 0x0e, 0x8a, 0xf7, + 0xae, 0x77, 0x68, 0x08, 0xc1, 0x0d, 0x3a, 0x66, 0x86, 0xcd, 0x7c, 0xcb, 0x9d, 0xa0, 0x37, 0x3d, + 0xfb, 0xd2, 0xc7, 0x9e, 0x2b, 0x5c, 0x52, 0xce, 0x80, 0xfa, 0xcd, 0xa1, 0xeb, 0x0e, 0x1d, 0x34, + 0xa2, 0xc0, 0x20, 0xd8, 0x37, 0x70, 0x34, 0x16, 0x09, 0xaf, 0x7e, 0x2b, 0x09, 0x86, 0x6a, 0x94, + 0x73, 0x57, 0x50, 0xc1, 0x5c, 0xee, 0xc7, 0x51, 0xed, 0x44, 0x82, 0xd2, 0x36, 0x0a, 0x6a, 0x53, + 0x41, 0xc9, 0x7d, 0x90, 0x0f, 0x71, 0xaa, 0x4a, 0x4d, 0xa9, 0x55, 0x6d, 0xdf, 0xd0, 0xcf, 0x2a, + 0xa6, 0x0c, 0x7d, 0x0b, 0xa7, 0x66, 0xc8, 0x21, 0xd7, 0x61, 0x7e, 0x42, 0x9d, 0x00, 0xd5, 0x42, + 0x53, 0x6a, 0x55, 0xcc, 0xf8, 0xa0, 0x3d, 0x02, 0x79, 0x0b, 0xa7, 0xa4, 0x0c, 0xf3, 0x2f, 0xf7, + 0x9e, 0xf7, 0xcc, 0xda, 0x1c, 0x01, 0x28, 0xee, 0x9a, 0xbd, 0x67, 0x9b, 0x6f, 0x6a, 0x12, 0x51, + 0xe0, 0xff, 0xce, 0xee, 0x6e, 0xbf, 0xf7, 0x6a, 0xb3, 0x56, 0x08, 0x03, 0xe1, 0x61, 0xb3, 0x5b, + 0x93, 0xb5, 0x2f, 0x05, 0xa8, 0x74, 0x38, 0x77, 0x03, 0x6e, 0xe1, 0x08, 0xb9, 0x20, 0x55, 0x28, + 0x30, 0x3b, 0xea, 0xa3, 0x6c, 0x16, 0x98, 0x4d, 0x6e, 0x43, 0xc5, 0x47, 0x6f, 0xc2, 0x2c, 0xec, + 0x73, 0x3a, 0x8a, 0x8b, 0x96, 0x4d, 0x25, 0xc1, 0x76, 0xe8, 0x08, 0xc9, 0x3d, 0x58, 0x4c, 0x29, + 0x13, 0xf4, 0x7c, 0xe6, 0x72, 0x55, 0x8e, 0x58, 0xd5, 0x04, 0x7e, 0x1d, 0xa3, 0xa4, 0x09, 0x8a, + 0x8d, 0xbe, 0xe5, 0xb1, 0x71, 0xe8, 0x83, 0xfa, 0x5f, 0x2c, 0x95, 0x83, 0x48, 0x0d, 0xe4, 0xc0, + 0x73, 0xd4, 0xf9, 0x28, 0x12, 0x7e, 0x92, 0x65, 0x28, 0x8e, 0x83, 0x81, 0xc3, 0x2c, 0xb5, 0xd8, + 0x94, 0x5a, 0x25, 0x33, 0x39, 0x91, 0x55, 0x50, 0x38, 0x8a, 0x3e, 0xb5, 0x6d, 0x0f, 0x7d, 0x5f, + 0x55, 0xa2, 0x0c, 0xe0, 0x28, 0x3a, 0x31, 0x42, 0x56, 0x00, 0x62, 0x6a, 0x3f, 0x34, 0xb6, 0x12, + 0xc5, 0xcb, 0x31, 0x12, 0x1a, 0xd5, 0x04, 0xc5, 0x42, 0x4f, 0xb0, 0x7d, 0x66, 0x51, 0x81, 0xea, + 0x42, 0xdc, 0x4b, 0x0e, 0x22, 0x06, 0x94, 0x46, 0x89, 0xf9, 0xea, 0x52, 0x53, 0x6e, 0x29, 0xed, + 0x6b, 0x97, 0xcc, 0xc5, 0xcc, 0x48, 0x5a, 0x1b, 0x16, 0x36, 0x50, 0x74, 0x1c, 0xc7, 0xc4, 0x77, + 0x01, 0xfa, 0xe2, 0x82, 0x77, 0xd2, 0x05, 0xef, 0xb4, 0x75, 0x80, 0x0d, 0x14, 0x69, 0xc2, 0xd5, + 0xcd, 0xd7, 0x02, 0x58, 0xcc, 0x5a, 0xf9, 0x6b, 0x95, 0x73, 0x77, 0x0d, 0xad, 0xfc, 0xe3, 0x5d, + 0x5f, 0xc0, 0x52, 0xfe, 0xb7, 0xf1, 0x4d, 0xf4, 0xc7, 0x2e, 0xf7, 0x91, 0xac, 0x41, 0x29, 0x11, + 0xf6, 0x55, 0x29, 0x72, 0x2d, 0xff, 0x37, 0xe7, 0x73, 0xcc, 0x8c, 0xd8, 0xfe, 0x24, 0x43, 0xb9, + 0x9b, 0x92, 0xc8, 0x63, 0x28, 0xa5, 0x3c, 0x32, 0x2b, 0xb9, 0xbe, 0xac, 0xc7, 0xcb, 0xa5, 0xa7, + 0x9b, 0xa7, 0xf7, 0xc2, 0xcd, 0x23, 0x87, 0x50, 0x8c, 0x87, 0x40, 0xd4, 0x5c, 0xea, 0xb9, 0xb9, + 0xd4, 0x9b, 0x33, 0x44, 0xb3, 0x5b, 0x68, 0x77, 0x4f, 0xbe, 0xfd, 0xfc, 0x50, 0x58, 0x25, 0x2b, + 0xd1, 0xde, 0x66, 0x71, 0xe3, 0x28, 0x6f, 0xe4, 0x31, 0xa1, 0x20, 0x6f, 0xa0, 0x20, 0x4b, 0xe7, + 0x2b, 0xa5, 0x65, 0x66, 0xf5, 0xae, 0x3d, 0x88, 0xd4, 0xef, 0x10, 0xed, 0xb7, 0xea, 0xc6, 0x11, + 0xb3, 0x8f, 0x49, 0x07, 0x94, 0x8e, 0x6d, 0x67, 0xef, 0x44, 0xfd, 0xb2, 0xb1, 0x24, 0xf5, 0x66, + 0x59, 0xd2, 0x85, 0x6a, 0x17, 0x1d, 0x14, 0xf8, 0x2f, 0x2a, 0x6d, 0x02, 0xb5, 0x6c, 0x44, 0xdb, + 0x94, 0xd3, 0x21, 0x7a, 0x4f, 0xd7, 0x3f, 0x9f, 0x36, 0xa4, 0xaf, 0xa7, 0x0d, 0xe9, 0xfb, 0x69, + 0x43, 0xfa, 0xf8, 0xa3, 0x31, 0xf7, 0xf6, 0xe1, 0x95, 0x5e, 0xd6, 0x41, 0x31, 0x2a, 0xb2, 0xf6, + 0x2b, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x1a, 0x6d, 0xb4, 0x91, 0x05, 0x00, 0x00, } diff --git a/api/discovery/discovery.pb.gw.go b/api/discovery/discovery.pb.gw.go new file mode 100644 index 000000000..cf68860c0 --- /dev/null +++ b/api/discovery/discovery.pb.gw.go @@ -0,0 +1,194 @@ +// Code generated by protoc-gen-grpc-gateway +// source: github.com/TheThingsNetwork/ttn/api/discovery/discovery.proto +// DO NOT EDIT! + +/* +Package discovery is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package discovery + +import ( + "io" + "net/http" + + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" +) + +var _ codes.Code +var _ io.Reader +var _ = runtime.String +var _ = utilities.NewDoubleArray + +func request_Discovery_GetAll_0(ctx context.Context, marshaler runtime.Marshaler, client DiscoveryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetAllRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["service_name"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "service_name") + } + + protoReq.ServiceName, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.GetAll(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_Discovery_Get_0(ctx context.Context, marshaler runtime.Marshaler, client DiscoveryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq GetRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["service_name"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "service_name") + } + + protoReq.ServiceName, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + val, ok = pathParams["id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "id") + } + + protoReq.Id, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.Get(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +// RegisterDiscoveryHandlerFromEndpoint is same as RegisterDiscoveryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterDiscoveryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Printf("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterDiscoveryHandler(ctx, mux, conn) +} + +// RegisterDiscoveryHandler registers the http handlers for service Discovery to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterDiscoveryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + client := NewDiscoveryClient(conn) + + mux.Handle("GET", pattern_Discovery_GetAll_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_Discovery_GetAll_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_Discovery_GetAll_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Discovery_Get_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_Discovery_Get_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_Discovery_Get_0(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Discovery_GetAll_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"announcements", "service_name"}, "")) + + pattern_Discovery_Get_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 1, 0, 4, 1, 5, 2}, []string{"announcements", "service_name", "id"}, "")) +) + +var ( + forward_Discovery_GetAll_0 = runtime.ForwardResponseMessage + + forward_Discovery_Get_0 = runtime.ForwardResponseMessage +) diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 8d283da1a..979488aaa 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -4,6 +4,7 @@ syntax = "proto3"; import "google/protobuf/empty.proto"; +import "google/api/annotations.proto"; package discovery; @@ -100,9 +101,17 @@ service Discovery { // Announce your component to the Discovery server rpc Announce(Announcement) returns (google.protobuf.Empty); // Get all announcements for a specific service - rpc GetAll(GetAllRequest) returns (AnnouncementsResponse); + rpc GetAll(GetAllRequest) returns (AnnouncementsResponse) { + option (google.api.http) = { + get: "/announcements/{service_name}" + }; + } // Get a specific announcement - rpc Get(GetRequest) returns (Announcement); + rpc Get(GetRequest) returns (Announcement) { + option (google.api.http) = { + get: "/announcements/{service_name}/{id}" + }; + } // Add metadata to an announement rpc AddMetadata(MetadataRequest) returns (google.protobuf.Empty); // Delete metadata from an announcement diff --git a/cmd/discovery.go b/cmd/discovery.go index 9f3a6984f..2e42b7b4a 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -4,16 +4,21 @@ package cmd import ( + "context" "fmt" "net" + "net/http" "os" "os/signal" "syscall" + pb "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/discovery" "github.com/TheThingsNetwork/ttn/core/discovery/announcement" + "github.com/TheThingsNetwork/ttn/core/proxy" "github.com/apex/log" + "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" "github.com/spf13/viper" "google.golang.org/grpc" @@ -27,8 +32,9 @@ var discoveryCmd = &cobra.Command{ Long: ``, PreRun: func(cmd *cobra.Command, args []string) { ctx.WithFields(log.Fields{ - "Server": fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port")), - "Database": fmt.Sprintf("%s/%d", viper.GetString("discovery.redis-address"), viper.GetInt("discovery.redis-db")), + "Server": fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address"), viper.GetInt("discovery.server-port")), + "HTTP Proxy": fmt.Sprintf("%s:%d", viper.GetString("discovery.http-address"), viper.GetInt("discovery.http-port")), + "Database": fmt.Sprintf("%s/%d", viper.GetString("discovery.redis-address"), viper.GetInt("discovery.redis-db")), }).Info("Initializing Discovery") }, Run: func(cmd *cobra.Command, args []string) { @@ -72,6 +78,29 @@ var discoveryCmd = &cobra.Command{ discovery.RegisterRPC(grpc) go grpc.Serve(lis) + if viper.GetString("discovery.http-address") != "" && viper.GetInt("discovery.http-port") != 0 { + proxyConn, err := component.Identity.Dial() + if err != nil { + ctx.WithError(err).Fatal("Could not start client for gRPC proxy") + } + mux := runtime.NewServeMux() + netCtx, cancel := context.WithCancel(context.Background()) + defer cancel() + pb.RegisterDiscoveryHandler(netCtx, mux, proxyConn) + + prxy := proxy.WithLogger(mux, ctx) + + go func() { + err := http.ListenAndServe( + fmt.Sprintf("%s:%d", viper.GetString("discovery.http-address"), viper.GetInt("discovery.http-port")), + prxy, + ) + if err != nil { + ctx.WithError(err).Fatal("Error in gRPC proxy") + } + }() + } + sigChan := make(chan os.Signal) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) ctx.WithField("signal", <-sigChan).Info("signal received") @@ -98,4 +127,9 @@ func init() { discoveryCmd.Flags().StringSlice("master-auth-servers", []string{"ttn-account"}, "Auth servers that are allowed to manage this network") viper.BindPFlag("discovery.master-auth-servers", discoveryCmd.Flags().Lookup("master-auth-servers")) + + discoveryCmd.Flags().String("http-address", "0.0.0.0", "The IP address where the gRPC proxy should listen") + discoveryCmd.Flags().Int("http-port", 0, "The port where the gRPC proxy should listen") + viper.BindPFlag("discovery.http-address", discoveryCmd.Flags().Lookup("http-address")) + viper.BindPFlag("discovery.http-port", discoveryCmd.Flags().Lookup("http-port")) } From 6afcdfa83916576e884c4d9abbdaec73c78c7911 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 17:19:49 +0100 Subject: [PATCH 2187/2266] Allow PUT for Handler HTTP endpoints Refs #355 --- api/handler/ApplicationManager.md | 5 +- api/handler/handler.pb.go | 144 ++++++++++----------- api/handler/handler.pb.gw.go | 204 +++++++++++++++++++++++++++++- api/handler/handler.proto | 12 ++ 4 files changed, 290 insertions(+), 75 deletions(-) diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md index 78feba336..9eef53965 100644 --- a/api/handler/ApplicationManager.md +++ b/api/handler/ApplicationManager.md @@ -67,9 +67,10 @@ SetApplication updates the settings for the application. All fields must be supp - Request: [`Application`](#handlerapplication) - Response: [`Empty`](#handlerapplication) -#### HTTP Endpoint +#### HTTP Endpoints - `POST` `/applications/{app_id}`(`app_id` can be left out of the request) +- `PUT` `/applications/{app_id}`(`app_id` can be left out of the request) #### JSON Request Format @@ -169,7 +170,9 @@ SetDevice creates or updates a device. All fields must be supplied. #### HTTP Endpoints - `POST` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) +- `PUT` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) - `POST` `/applications/{app_id}/devices`(`app_id` can be left out of the request) +- `PUT` `/applications/{app_id}/devices`(`app_id` can be left out of the request) #### JSON Request Format diff --git a/api/handler/handler.pb.go b/api/handler/handler.pb.go index e6365fc43..3fc10fc50 100644 --- a/api/handler/handler.pb.go +++ b/api/handler/handler.pb.go @@ -3611,76 +3611,76 @@ func init() { } var fileDescriptorHandler = []byte{ - // 1122 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0xdd, 0x6e, 0x1b, 0x45, - 0x14, 0xee, 0xe6, 0xc7, 0xb1, 0x8f, 0x9b, 0xa4, 0x99, 0xb4, 0x61, 0x71, 0x2a, 0x93, 0x4e, 0xd5, - 0x90, 0x26, 0xc8, 0x16, 0x06, 0x09, 0xa8, 0x04, 0xf4, 0x27, 0x4d, 0x1b, 0xa9, 0x01, 0xb1, 0x09, - 0x42, 0xca, 0x05, 0xd1, 0xc4, 0x7b, 0xb2, 0x5e, 0x65, 0xbd, 0xb3, 0xec, 0x8c, 0x1d, 0x99, 0xaa, - 0x12, 0xea, 0x3d, 0x57, 0x70, 0xc1, 0x13, 0xf0, 0x26, 0x48, 0x5c, 0x22, 0xf1, 0x00, 0xa0, 0xc0, - 0x0b, 0xf0, 0x06, 0x68, 0x67, 0x66, 0xd7, 0x1b, 0xff, 0xe4, 0x07, 0x71, 0x65, 0x9f, 0xf3, 0x9d, - 0xf9, 0xce, 0xf9, 0xce, 0xcc, 0x9c, 0x59, 0xf8, 0xc8, 0xf3, 0x65, 0xab, 0x73, 0x58, 0x6b, 0xf2, - 0x76, 0x7d, 0xaf, 0x85, 0x7b, 0x2d, 0x3f, 0xf4, 0xc4, 0x67, 0x28, 0x4f, 0x78, 0x7c, 0x5c, 0x97, - 0x32, 0xac, 0xb3, 0xc8, 0xaf, 0xb7, 0x58, 0xe8, 0x06, 0x18, 0xa7, 0xbf, 0xb5, 0x28, 0xe6, 0x92, - 0x93, 0x19, 0x63, 0x56, 0x96, 0x3d, 0xce, 0xbd, 0x00, 0xeb, 0xca, 0x7d, 0xd8, 0x39, 0xaa, 0x63, - 0x3b, 0x92, 0x3d, 0x1d, 0x55, 0xb9, 0x6d, 0xc0, 0x84, 0x87, 0x85, 0x21, 0x97, 0x4c, 0xfa, 0x3c, - 0x14, 0x06, 0x5d, 0x48, 0x53, 0xb0, 0xc8, 0x37, 0xae, 0xe5, 0xd4, 0x75, 0x18, 0xf3, 0x63, 0x8c, - 0xcd, 0x8f, 0x01, 0xdf, 0x4a, 0x41, 0x65, 0x36, 0x79, 0x90, 0xfd, 0x31, 0x01, 0xf7, 0x86, 0x02, - 0x02, 0x1e, 0xb3, 0x13, 0x16, 0xd6, 0x5d, 0xec, 0xfa, 0x4d, 0xd4, 0x61, 0xf4, 0x1f, 0x0b, 0xec, - 0x4d, 0xe5, 0x78, 0xd4, 0x94, 0x7e, 0x57, 0xd5, 0xe4, 0xa0, 0x88, 0x78, 0x28, 0x90, 0xd8, 0x30, - 0x13, 0xb1, 0x5e, 0xc0, 0x99, 0x6b, 0x5b, 0x2b, 0xd6, 0xda, 0x75, 0x27, 0x35, 0xc9, 0x06, 0xcc, - 0xb4, 0x51, 0x08, 0xe6, 0xa1, 0x3d, 0xb1, 0x62, 0xad, 0x95, 0x1b, 0x0b, 0xb5, 0x2c, 0xff, 0x8e, - 0x06, 0x9c, 0x34, 0x82, 0x7c, 0x0a, 0xf3, 0x2e, 0x3f, 0x09, 0x03, 0x3f, 0x3c, 0x3e, 0xe0, 0x51, - 0x92, 0xc1, 0x2e, 0xab, 0x45, 0x4b, 0x35, 0xa3, 0x69, 0xd3, 0xc0, 0x9f, 0x2b, 0xd4, 0x99, 0x73, - 0xcf, 0xd8, 0x64, 0x07, 0x16, 0x59, 0x56, 0xdd, 0x41, 0x1b, 0x25, 0x73, 0x99, 0x64, 0xf6, 0x1b, - 0x8a, 0xe4, 0x76, 0x3f, 0x73, 0x5f, 0xc2, 0x8e, 0x89, 0x71, 0x08, 0x1b, 0xf2, 0xd1, 0x79, 0x98, - 0xdd, 0x95, 0x4c, 0x76, 0x84, 0x83, 0xdf, 0x74, 0x50, 0x48, 0xfa, 0x87, 0x05, 0x05, 0xed, 0x21, - 0x6b, 0x50, 0x10, 0x3d, 0x21, 0xb1, 0xad, 0x14, 0x97, 0x1b, 0x37, 0x6a, 0xc9, 0x86, 0xec, 0x2a, - 0x57, 0x12, 0x22, 0x1c, 0x83, 0x93, 0x77, 0xa1, 0xd4, 0xe4, 0xed, 0x88, 0x87, 0x18, 0x4a, 0xd3, - 0x84, 0x45, 0x15, 0xfc, 0x24, 0xf5, 0xea, 0xf8, 0x7e, 0x14, 0xa1, 0x50, 0xe8, 0x44, 0x89, 0x2e, - 0xa3, 0x1f, 0x54, 0xbc, 0xc3, 0x24, 0x0a, 0xc7, 0x20, 0x64, 0x15, 0x8a, 0xa9, 0x7a, 0xfb, 0xfa, - 0x50, 0x54, 0x86, 0x91, 0x77, 0xa0, 0xdc, 0x97, 0x26, 0xec, 0xd9, 0xa1, 0xd0, 0x3c, 0x4c, 0x6b, - 0x70, 0xeb, 0x51, 0x14, 0x05, 0x7e, 0x53, 0xd9, 0xdb, 0x2e, 0x86, 0xd2, 0x3f, 0xf2, 0x31, 0x26, - 0xb7, 0xa0, 0xc0, 0xa2, 0xe8, 0xc0, 0xd7, 0x3b, 0x5c, 0x72, 0xa6, 0x59, 0x14, 0x6d, 0xbb, 0xf4, - 0x47, 0x0b, 0xca, 0xb9, 0x05, 0x63, 0xc2, 0x92, 0x03, 0xe2, 0x62, 0x93, 0xbb, 0x18, 0xab, 0x0e, - 0x94, 0x9c, 0xd4, 0x24, 0xb7, 0x93, 0xee, 0x84, 0x5d, 0x8c, 0x25, 0xc6, 0xf6, 0xa4, 0xc2, 0xfa, - 0x8e, 0x04, 0xed, 0xb2, 0xc0, 0x77, 0x99, 0xe4, 0xb1, 0x3d, 0xa5, 0xd1, 0xcc, 0x91, 0xb0, 0x62, - 0xa8, 0x59, 0xa7, 0x35, 0xab, 0x31, 0xe9, 0x43, 0xb8, 0xa1, 0x0f, 0xeb, 0x85, 0x0a, 0x12, 0xb7, - 0x8b, 0xdd, 0xc4, 0xad, 0x2b, 0x9b, 0x76, 0xb1, 0xbb, 0xed, 0xd2, 0x6f, 0xa1, 0xa0, 0x19, 0xae, - 0xb6, 0x8e, 0x7c, 0x08, 0x73, 0xe6, 0xfe, 0x1c, 0xe8, 0xfb, 0xa3, 0x44, 0x95, 0x1b, 0xf3, 0x35, - 0xe3, 0xae, 0x69, 0xda, 0xe7, 0xd7, 0x9c, 0x59, 0xe3, 0xd1, 0x8e, 0xc7, 0x45, 0x45, 0xe8, 0x37, - 0x91, 0x7e, 0x00, 0xa0, 0x7d, 0x2f, 0x7c, 0x21, 0xc9, 0xfd, 0xa4, 0x77, 0x89, 0x25, 0x6c, 0x6b, - 0x65, 0x52, 0x51, 0xa5, 0x63, 0x45, 0x47, 0x39, 0x29, 0x4e, 0x5f, 0x5b, 0x40, 0x36, 0xe3, 0x5e, - 0x7a, 0x4b, 0xcc, 0x05, 0x3b, 0xe7, 0x7a, 0x2e, 0x41, 0xe1, 0xc8, 0xc7, 0xc0, 0x15, 0x46, 0x84, - 0xb1, 0xc8, 0x2a, 0x4c, 0xb2, 0x28, 0x32, 0xa5, 0xdf, 0xcc, 0xf2, 0xe5, 0x76, 0xda, 0x49, 0x02, - 0x08, 0x81, 0xa9, 0x88, 0xc7, 0x52, 0x6d, 0xcd, 0xac, 0xa3, 0xfe, 0xd3, 0x16, 0xdc, 0xd8, 0x8c, - 0x7b, 0x5f, 0x46, 0x97, 0xab, 0xc0, 0x64, 0x9a, 0xb8, 0x6c, 0xa6, 0xc9, 0x5c, 0xa6, 0x4f, 0xa0, - 0xf8, 0x82, 0x7b, 0x4f, 0x43, 0x19, 0xf7, 0x48, 0x05, 0x8a, 0x47, 0x9d, 0xb0, 0xa9, 0x86, 0x86, - 0xde, 0xa7, 0xcc, 0x3e, 0xa3, 0x72, 0xb2, 0xaf, 0x92, 0x7e, 0x67, 0xc1, 0x7c, 0x56, 0xaa, 0x83, - 0xa2, 0x13, 0xc8, 0xff, 0xd0, 0xab, 0x9b, 0x30, 0xad, 0x8e, 0xa4, 0x2a, 0xad, 0xe8, 0x68, 0x83, - 0xdc, 0x83, 0xa9, 0x80, 0x7b, 0xc2, 0x9e, 0x52, 0x5b, 0xb6, 0x90, 0x09, 0x4b, 0x0b, 0x76, 0x14, - 0x4c, 0xf7, 0x60, 0x21, 0xb7, 0x61, 0x17, 0xd6, 0x90, 0xb2, 0x4e, 0x9c, 0xcb, 0xda, 0xf8, 0xc5, - 0x82, 0x99, 0xe7, 0x1a, 0x22, 0x5f, 0xc3, 0x62, 0x7f, 0xdc, 0x3d, 0x69, 0xb1, 0x20, 0xc0, 0xd0, - 0x43, 0x42, 0xd3, 0x91, 0x3a, 0x02, 0x34, 0xe3, 0xae, 0x72, 0xf7, 0xdc, 0x18, 0x33, 0xfb, 0xf7, - 0xa1, 0x68, 0x60, 0x24, 0x1b, 0xd9, 0x9c, 0x46, 0xb7, 0xa3, 0x37, 0x10, 0xdd, 0xe1, 0x57, 0x43, - 0xb3, 0xdf, 0x19, 0x38, 0xc6, 0xc3, 0xef, 0x4a, 0xe3, 0xe7, 0x22, 0x90, 0xdc, 0x49, 0xd8, 0x61, - 0x21, 0xf3, 0x30, 0x26, 0x1e, 0x2c, 0x3a, 0xe8, 0xf9, 0x42, 0x62, 0x9c, 0x9f, 0x3d, 0xd5, 0x51, - 0xa7, 0xa7, 0x3f, 0x00, 0x2a, 0x4b, 0x35, 0xfd, 0xb2, 0xd6, 0xd2, 0x67, 0xb7, 0xf6, 0x34, 0x79, - 0x76, 0xa9, 0xfd, 0xfa, 0xf7, 0xbf, 0x7f, 0x98, 0x20, 0x74, 0xb6, 0xce, 0xfa, 0xeb, 0xc4, 0x03, - 0x6b, 0x9d, 0x1c, 0xc1, 0xdc, 0x33, 0x94, 0x57, 0xc9, 0x31, 0xf2, 0x04, 0xd3, 0xaa, 0xca, 0x60, - 0x93, 0xa5, 0x33, 0x19, 0xea, 0x2f, 0xf5, 0x5c, 0x79, 0x45, 0x18, 0xcc, 0xed, 0x9e, 0xcd, 0x33, - 0x92, 0x67, 0xac, 0x82, 0x3b, 0x8a, 0x7f, 0x99, 0x8e, 0xe1, 0x4f, 0xa4, 0x1c, 0xc3, 0xc2, 0x26, - 0x06, 0x28, 0xf1, 0xff, 0xe8, 0x98, 0xd1, 0xb3, 0x3e, 0x4e, 0x4f, 0x0b, 0x4a, 0xcf, 0x50, 0x9a, - 0xf9, 0xf9, 0xe6, 0xc0, 0x3e, 0xe7, 0xf8, 0x07, 0x27, 0x19, 0xad, 0x2b, 0xe2, 0xfb, 0xe4, 0xed, - 0xd1, 0xc4, 0xe6, 0x93, 0x44, 0xd4, 0x5f, 0xea, 0xd1, 0xfb, 0x8a, 0x7c, 0x6f, 0x41, 0x69, 0x37, - 0x4b, 0x35, 0xc8, 0x37, 0x56, 0xc0, 0x57, 0x2a, 0xcf, 0x17, 0xf4, 0xb2, 0x79, 0x1e, 0x58, 0xeb, - 0xfb, 0x77, 0x69, 0xf5, 0xfc, 0xe8, 0xa4, 0xcd, 0x31, 0x5c, 0xd7, 0x6d, 0xbe, 0x58, 0xfc, 0xb8, - 0xda, 0x4c, 0x0f, 0xd6, 0x2f, 0xdd, 0x83, 0x13, 0xb0, 0xb3, 0x6e, 0x8b, 0x2d, 0x7e, 0xa5, 0x3b, - 0xb1, 0x38, 0x50, 0x5f, 0xf2, 0xe2, 0xd0, 0x55, 0x55, 0xc1, 0x0a, 0xb9, 0x40, 0x2f, 0xd9, 0x82, - 0x72, 0x6e, 0x78, 0x91, 0xe5, 0x3e, 0xd7, 0xd0, 0x1b, 0x54, 0xa9, 0x8c, 0x02, 0xcd, 0xbc, 0x7b, - 0x08, 0xa5, 0x6c, 0x0c, 0xe7, 0x3b, 0x36, 0xf0, 0x8a, 0x54, 0xec, 0x61, 0x48, 0x33, 0x34, 0xb6, - 0x60, 0xce, 0xcc, 0xbb, 0x74, 0x46, 0xbc, 0xaf, 0x8e, 0xa0, 0xf9, 0x58, 0x5b, 0xca, 0x16, 0x9e, - 0xf9, 0x9e, 0xcb, 0x9d, 0x3f, 0xed, 0x7f, 0xfc, 0xf1, 0xaf, 0xa7, 0x55, 0xeb, 0xb7, 0xd3, 0xaa, - 0xf5, 0xe7, 0x69, 0xd5, 0xfa, 0xe9, 0xaf, 0xea, 0xb5, 0xfd, 0x8d, 0x2b, 0x7c, 0xee, 0x1f, 0x16, - 0xd4, 0x56, 0xbe, 0xf7, 0x6f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x41, 0x10, 0xfc, 0x24, 0x0c, - 0x00, 0x00, + // 1134 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0xdb, 0x6e, 0xdc, 0x44, + 0x18, 0xae, 0x73, 0xd8, 0xec, 0xfe, 0x9b, 0xe3, 0xa4, 0x0d, 0xc6, 0x89, 0x96, 0x30, 0x55, 0x43, + 0x9a, 0x54, 0xb6, 0x58, 0x90, 0x28, 0x95, 0x28, 0x3d, 0xa4, 0x69, 0x23, 0x35, 0x20, 0x39, 0xe1, + 0x26, 0x17, 0x44, 0x93, 0xf5, 0xc4, 0x6b, 0xc5, 0xeb, 0x31, 0x9e, 0xd9, 0x8d, 0x96, 0xaa, 0x08, + 0xf5, 0x15, 0xe0, 0xa2, 0x0f, 0xc0, 0x1d, 0xcf, 0x81, 0xc4, 0x25, 0x12, 0x0f, 0x00, 0x0a, 0xbc, + 0x00, 0x6f, 0x80, 0x3c, 0x33, 0xf6, 0x3a, 0x7b, 0xc8, 0x01, 0x71, 0x95, 0xfd, 0xff, 0xef, 0xf3, + 0xf7, 0x9f, 0xc6, 0xff, 0x38, 0xf0, 0xa9, 0x1f, 0x88, 0x66, 0xfb, 0xc8, 0x6e, 0xb0, 0x96, 0xb3, + 0xdf, 0xa4, 0xfb, 0xcd, 0x20, 0xf2, 0xf9, 0x17, 0x54, 0x9c, 0xb2, 0xe4, 0xc4, 0x11, 0x22, 0x72, + 0x48, 0x1c, 0x38, 0x4d, 0x12, 0x79, 0x21, 0x4d, 0xb2, 0xbf, 0x76, 0x9c, 0x30, 0xc1, 0xd0, 0x94, + 0x36, 0xad, 0x65, 0x9f, 0x31, 0x3f, 0xa4, 0x8e, 0x74, 0x1f, 0xb5, 0x8f, 0x1d, 0xda, 0x8a, 0x45, + 0x57, 0xb1, 0xac, 0x15, 0x0d, 0xa6, 0x3a, 0x24, 0x8a, 0x98, 0x20, 0x22, 0x60, 0x11, 0xd7, 0xe8, + 0x42, 0x16, 0x82, 0xc4, 0x81, 0x76, 0x2d, 0x67, 0xae, 0xa3, 0x84, 0x9d, 0xd0, 0x44, 0xff, 0xd1, + 0xe0, 0x7b, 0x19, 0x28, 0xcd, 0x06, 0x0b, 0xf3, 0x1f, 0x9a, 0x70, 0x67, 0x80, 0x10, 0xb2, 0x84, + 0x9c, 0x92, 0xc8, 0xf1, 0x68, 0x27, 0x68, 0x50, 0x45, 0xc3, 0xff, 0x18, 0x60, 0x6e, 0x49, 0xc7, + 0xe3, 0x86, 0x08, 0x3a, 0x32, 0x27, 0x97, 0xf2, 0x98, 0x45, 0x9c, 0x22, 0x13, 0xa6, 0x62, 0xd2, + 0x0d, 0x19, 0xf1, 0x4c, 0x63, 0xd5, 0x58, 0x9f, 0x76, 0x33, 0x13, 0x6d, 0xc2, 0x54, 0x8b, 0x72, + 0x4e, 0x7c, 0x6a, 0x8e, 0xad, 0x1a, 0xeb, 0xd5, 0xfa, 0x82, 0x9d, 0xc7, 0xdf, 0x55, 0x80, 0x9b, + 0x31, 0xd0, 0xe7, 0x30, 0xe7, 0xb1, 0xd3, 0x28, 0x0c, 0xa2, 0x93, 0x43, 0x16, 0xa7, 0x11, 0xcc, + 0xaa, 0x7c, 0x68, 0xc9, 0xd6, 0x35, 0x6d, 0x69, 0xf8, 0x4b, 0x89, 0xba, 0xb3, 0xde, 0x39, 0x1b, + 0xed, 0xc2, 0x22, 0xc9, 0xb3, 0x3b, 0x6c, 0x51, 0x41, 0x3c, 0x22, 0x88, 0xf9, 0x8e, 0x14, 0x59, + 0xe9, 0x45, 0xee, 0x95, 0xb0, 0xab, 0x39, 0x2e, 0x22, 0x03, 0x3e, 0x3c, 0x07, 0x33, 0x7b, 0x82, + 0x88, 0x36, 0x77, 0xe9, 0x37, 0x6d, 0xca, 0x05, 0xfe, 0xc3, 0x80, 0x92, 0xf2, 0xa0, 0x75, 0x28, + 0xf1, 0x2e, 0x17, 0xb4, 0x25, 0x2b, 0xae, 0xd6, 0xe7, 0xed, 0x74, 0x20, 0x7b, 0xd2, 0x95, 0x52, + 0xb8, 0xab, 0x71, 0xf4, 0x21, 0x54, 0x1a, 0xac, 0x15, 0xb3, 0x88, 0x46, 0x42, 0x37, 0x61, 0x51, + 0x92, 0x9f, 0x66, 0x5e, 0xc5, 0xef, 0xb1, 0x10, 0x86, 0x52, 0x3b, 0x4e, 0xeb, 0xd2, 0xf5, 0x83, + 0xe4, 0xbb, 0x44, 0x50, 0xee, 0x6a, 0x04, 0xad, 0x41, 0x39, 0xab, 0xde, 0x9c, 0x1e, 0x60, 0xe5, + 0x18, 0xba, 0x07, 0xd5, 0x5e, 0x69, 0xdc, 0x9c, 0x19, 0xa0, 0x16, 0x61, 0x6c, 0xc3, 0xad, 0xc7, + 0x71, 0x1c, 0x06, 0x0d, 0x69, 0xef, 0x78, 0x34, 0x12, 0xc1, 0x71, 0x40, 0x13, 0x74, 0x0b, 0x4a, + 0x24, 0x8e, 0x0f, 0x03, 0x35, 0xe1, 0x8a, 0x3b, 0x49, 0xe2, 0x78, 0xc7, 0xc3, 0x3f, 0x1a, 0x50, + 0x2d, 0x3c, 0x30, 0x82, 0x96, 0x1e, 0x10, 0x8f, 0x36, 0x98, 0x47, 0x13, 0xd9, 0x81, 0x8a, 0x9b, + 0x99, 0x68, 0x25, 0xed, 0x4e, 0xd4, 0xa1, 0x89, 0xa0, 0x89, 0x39, 0x2e, 0xb1, 0x9e, 0x23, 0x45, + 0x3b, 0x24, 0x0c, 0x3c, 0x22, 0x58, 0x62, 0x4e, 0x28, 0x34, 0x77, 0xa4, 0xaa, 0x34, 0x52, 0xaa, + 0x93, 0x4a, 0x55, 0x9b, 0xf8, 0x11, 0xcc, 0xab, 0xc3, 0x7a, 0x69, 0x05, 0xa9, 0xdb, 0xa3, 0x9d, + 0xd4, 0xad, 0x32, 0x9b, 0xf4, 0x68, 0x67, 0xc7, 0xc3, 0xdf, 0x42, 0x49, 0x29, 0x5c, 0xef, 0x39, + 0x74, 0x1f, 0x66, 0xf5, 0xfb, 0x73, 0xa8, 0xde, 0x1f, 0x59, 0x54, 0xb5, 0x3e, 0x67, 0x6b, 0xb7, + 0xad, 0x64, 0x5f, 0xdc, 0x70, 0x67, 0xb4, 0x47, 0x39, 0x9e, 0x94, 0xa5, 0x60, 0xd0, 0xa0, 0xf8, + 0x13, 0x00, 0xe5, 0x7b, 0x19, 0x70, 0x81, 0xee, 0xa6, 0xbd, 0x4b, 0x2d, 0x6e, 0x1a, 0xab, 0xe3, + 0x52, 0x2a, 0x5b, 0x2b, 0x8a, 0xe5, 0x66, 0x38, 0x7e, 0x63, 0x00, 0xda, 0x4a, 0xba, 0xd9, 0x5b, + 0xa2, 0x5f, 0xb0, 0x0b, 0x5e, 0xcf, 0x25, 0x28, 0x1d, 0x07, 0x34, 0xf4, 0xb8, 0x2e, 0x42, 0x5b, + 0x68, 0x0d, 0xc6, 0x49, 0x1c, 0xeb, 0xd4, 0x6f, 0xe6, 0xf1, 0x0a, 0x93, 0x76, 0x53, 0x02, 0x42, + 0x30, 0x11, 0xb3, 0x44, 0xc8, 0xd1, 0xcc, 0xb8, 0xf2, 0x37, 0x6e, 0xc2, 0xfc, 0x56, 0xd2, 0xfd, + 0x2a, 0xbe, 0x5a, 0x06, 0x3a, 0xd2, 0xd8, 0x55, 0x23, 0x8d, 0x17, 0x22, 0x3d, 0x84, 0xf2, 0x4b, + 0xe6, 0x3f, 0x8b, 0x44, 0xd2, 0x45, 0x16, 0x94, 0x8f, 0xdb, 0x51, 0x43, 0x2e, 0x0d, 0x35, 0xa7, + 0xdc, 0x3e, 0x57, 0xe5, 0x78, 0xaf, 0x4a, 0xfc, 0xbd, 0x01, 0x73, 0x79, 0xaa, 0x2e, 0xe5, 0xed, + 0x50, 0xfc, 0x87, 0x5e, 0xdd, 0x84, 0x49, 0x79, 0x24, 0x65, 0x6a, 0x65, 0x57, 0x19, 0xe8, 0x0e, + 0x4c, 0x84, 0xcc, 0xe7, 0xe6, 0x84, 0x1c, 0xd9, 0x42, 0x5e, 0x58, 0x96, 0xb0, 0x2b, 0x61, 0xbc, + 0x0f, 0x0b, 0x85, 0x81, 0x5d, 0x9a, 0x43, 0xa6, 0x3a, 0x76, 0xa1, 0x6a, 0xfd, 0x17, 0x03, 0xa6, + 0x5e, 0x28, 0x08, 0x7d, 0x0d, 0x8b, 0xbd, 0x75, 0xf7, 0xb4, 0x49, 0xc2, 0x90, 0x46, 0x3e, 0x45, + 0x38, 0x5b, 0xa9, 0x43, 0x40, 0xbd, 0xee, 0xac, 0xdb, 0x17, 0x72, 0xf4, 0xee, 0x3f, 0x80, 0xb2, + 0x86, 0x29, 0xda, 0xcc, 0xf7, 0x34, 0xf5, 0xda, 0x6a, 0x80, 0xd4, 0x1b, 0xbc, 0x35, 0x94, 0xfa, + 0xfb, 0x7d, 0xc7, 0x78, 0xf0, 0x5e, 0xa9, 0xbf, 0xad, 0x00, 0x2a, 0x9c, 0x84, 0x5d, 0x12, 0x11, + 0x9f, 0x26, 0xc8, 0x87, 0x45, 0x97, 0xfa, 0x01, 0x17, 0x34, 0x29, 0xee, 0x9e, 0xda, 0xb0, 0xd3, + 0xd3, 0x5b, 0x00, 0xd6, 0x92, 0xad, 0x6e, 0x56, 0x3b, 0xbb, 0x76, 0xed, 0x67, 0xe9, 0xb5, 0x8b, + 0xcd, 0x37, 0xbf, 0xff, 0xfd, 0xc3, 0x18, 0xc2, 0x33, 0x0e, 0xe9, 0x3d, 0xc7, 0x1f, 0x18, 0x1b, + 0xe8, 0x18, 0x66, 0x9f, 0x53, 0x71, 0x9d, 0x18, 0x43, 0x4f, 0x30, 0xae, 0xc9, 0x08, 0x26, 0x5a, + 0x3a, 0x17, 0xc1, 0x79, 0xa5, 0xf6, 0xca, 0x6b, 0xf4, 0x1d, 0xcc, 0xee, 0x9d, 0x8f, 0x33, 0x54, + 0x67, 0x64, 0x05, 0x0f, 0xa5, 0xfe, 0x7d, 0x3c, 0x42, 0xff, 0x81, 0xb1, 0x71, 0xb0, 0x6c, 0x8d, + 0x06, 0xd1, 0x09, 0x2c, 0x6c, 0xd1, 0x90, 0x0a, 0xfa, 0x7f, 0xb4, 0x53, 0x17, 0xbb, 0x31, 0xaa, + 0xd8, 0x26, 0x54, 0x9e, 0x53, 0xa1, 0x97, 0xeb, 0xbb, 0x7d, 0x87, 0xa0, 0xa0, 0xdf, 0xbf, 0xe6, + 0xb0, 0x23, 0x85, 0xef, 0xa2, 0x0f, 0x86, 0x0b, 0xeb, 0xef, 0x15, 0xee, 0xbc, 0x52, 0x7b, 0xf9, + 0x35, 0x3a, 0x33, 0xa0, 0xb2, 0x97, 0x87, 0xea, 0xd7, 0x1b, 0x59, 0xc0, 0xcf, 0x86, 0x0c, 0xf4, + 0x93, 0x81, 0xaf, 0x1a, 0x29, 0x6d, 0xf0, 0x3d, 0xeb, 0x3a, 0xec, 0xdb, 0xb8, 0x76, 0x31, 0x5b, + 0x92, 0xac, 0xcb, 0x49, 0x28, 0x81, 0x69, 0x35, 0xbb, 0xcb, 0x3b, 0x3a, 0xaa, 0x60, 0xdd, 0xd8, + 0x8d, 0x2b, 0x37, 0xf6, 0x14, 0xcc, 0x7c, 0x84, 0x7c, 0x9b, 0x5d, 0xeb, 0x2d, 0x5c, 0xec, 0xcb, + 0x2f, 0xbd, 0xe3, 0xf0, 0x9a, 0xcc, 0x60, 0x15, 0x5d, 0x52, 0x2f, 0xda, 0x86, 0x6a, 0x61, 0x5d, + 0xa2, 0xe5, 0x9e, 0xd6, 0xc0, 0xad, 0x67, 0x59, 0xc3, 0x40, 0xbd, 0x61, 0x1f, 0x41, 0x25, 0x5f, + 0xfc, 0xc5, 0x8e, 0xf5, 0xdd, 0x5b, 0x96, 0x39, 0x08, 0x29, 0x85, 0xfa, 0x36, 0xcc, 0xea, 0x0d, + 0x9b, 0x6d, 0xa5, 0x8f, 0xe5, 0xb9, 0xd6, 0x9f, 0x87, 0x4b, 0xf9, 0x83, 0xe7, 0xbe, 0x20, 0x0b, + 0x87, 0x5a, 0xf9, 0x9f, 0x7c, 0xf6, 0xeb, 0x59, 0xcd, 0xf8, 0xed, 0xac, 0x66, 0xfc, 0x79, 0x56, + 0x33, 0xde, 0xfe, 0x55, 0xbb, 0x71, 0xb0, 0x79, 0x8d, 0x7f, 0x30, 0x8e, 0x4a, 0x72, 0x94, 0x1f, + 0xfd, 0x1b, 0x00, 0x00, 0xff, 0xff, 0x0a, 0x10, 0x8d, 0xba, 0x96, 0x0c, 0x00, 0x00, } diff --git a/api/handler/handler.pb.gw.go b/api/handler/handler.pb.gw.go index 737df7257..369a9818d 100644 --- a/api/handler/handler.pb.gw.go +++ b/api/handler/handler.pb.gw.go @@ -98,6 +98,37 @@ func request_ApplicationManager_SetApplication_0(ctx context.Context, marshaler } +func request_ApplicationManager_SetApplication_1(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Application + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetApplication(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + func request_ApplicationManager_DeleteApplication_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { var protoReq ApplicationIdentifier var metadata runtime.ServerMetadata @@ -231,6 +262,79 @@ func request_ApplicationManager_SetDevice_1(ctx context.Context, marshaler runti return nil, metadata, err } + val, ok = pathParams["dev_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "dev_id") + } + + protoReq.DevId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_2(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func request_ApplicationManager_SetDevice_3(ctx context.Context, marshaler runtime.Marshaler, client ApplicationManagerClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq Device + var metadata runtime.ServerMetadata + + if err := marshaler.NewDecoder(req.Body).Decode(&protoReq); err != nil { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "%v", err) + } + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["app_id"] + if !ok { + return nil, metadata, grpc.Errorf(codes.InvalidArgument, "missing parameter %s", "app_id") + } + + protoReq.AppId, err = runtime.String(val) + + if err != nil { + return nil, metadata, err + } + msg, err := client.SetDevice(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) return msg, metadata, err @@ -415,6 +519,34 @@ func RegisterApplicationManagerHandler(ctx context.Context, mux *runtime.ServeMu }) + mux.Handle("PUT", pattern_ApplicationManager_SetApplication_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetApplication_1(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetApplication_1(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("DELETE", pattern_ApplicationManager_DeleteApplication_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -499,7 +631,7 @@ func RegisterApplicationManagerHandler(ctx context.Context, mux *runtime.ServeMu }) - mux.Handle("POST", pattern_ApplicationManager_SetDevice_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + mux.Handle("PUT", pattern_ApplicationManager_SetDevice_1, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(ctx) defer cancel() if cn, ok := w.(http.CloseNotifier); ok { @@ -527,6 +659,62 @@ func RegisterApplicationManagerHandler(ctx context.Context, mux *runtime.ServeMu }) + mux.Handle("POST", pattern_ApplicationManager_SetDevice_2, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_2(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_2(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("PUT", pattern_ApplicationManager_SetDevice_3, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(ctx) + defer cancel() + if cn, ok := w.(http.CloseNotifier); ok { + go func(done <-chan struct{}, closed <-chan bool) { + select { + case <-done: + case <-closed: + cancel() + } + }(ctx.Done(), cn.CloseNotify()) + } + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, req) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + } + resp, md, err := request_ApplicationManager_SetDevice_3(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, outboundMarshaler, w, req, err) + return + } + + forward_ApplicationManager_SetDevice_3(ctx, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + mux.Handle("DELETE", pattern_ApplicationManager_DeleteDevice_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { ctx, cancel := context.WithCancel(ctx) defer cancel() @@ -593,13 +781,19 @@ var ( pattern_ApplicationManager_SetApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + pattern_ApplicationManager_SetApplication_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) + pattern_ApplicationManager_DeleteApplication_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1}, []string{"applications", "app_id"}, "")) pattern_ApplicationManager_GetDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) pattern_ApplicationManager_SetDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) - pattern_ApplicationManager_SetDevice_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) + pattern_ApplicationManager_SetDevice_1 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) + + pattern_ApplicationManager_SetDevice_2 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) + + pattern_ApplicationManager_SetDevice_3 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2}, []string{"applications", "app_id", "devices"}, "")) pattern_ApplicationManager_DeleteDevice_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 1, 0, 4, 1, 5, 1, 2, 2, 1, 0, 4, 1, 5, 3}, []string{"applications", "app_id", "devices", "dev_id"}, "")) @@ -613,6 +807,8 @@ var ( forward_ApplicationManager_SetApplication_0 = runtime.ForwardResponseMessage + forward_ApplicationManager_SetApplication_1 = runtime.ForwardResponseMessage + forward_ApplicationManager_DeleteApplication_0 = runtime.ForwardResponseMessage forward_ApplicationManager_GetDevice_0 = runtime.ForwardResponseMessage @@ -621,6 +817,10 @@ var ( forward_ApplicationManager_SetDevice_1 = runtime.ForwardResponseMessage + forward_ApplicationManager_SetDevice_2 = runtime.ForwardResponseMessage + + forward_ApplicationManager_SetDevice_3 = runtime.ForwardResponseMessage + forward_ApplicationManager_DeleteDevice_0 = runtime.ForwardResponseMessage forward_ApplicationManager_GetDevicesForApplication_0 = runtime.ForwardResponseMessage diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 4685e61cd..3b2011c86 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -158,6 +158,10 @@ service ApplicationManager { option (google.api.http) = { post: "/applications/{app_id}" body: "*" + additional_bindings { + put: "/applications/{app_id}" + body: "*" + } }; } @@ -180,10 +184,18 @@ service ApplicationManager { option (google.api.http) = { post: "/applications/{app_id}/devices/{dev_id}" body: "*" + additional_bindings { + put: "/applications/{app_id}/devices/{dev_id}" + body: "*" + } additional_bindings { post: "/applications/{app_id}/devices" body: "*" } + additional_bindings { + put: "/applications/{app_id}/devices" + body: "*" + } }; } From 121933d4c900aa6bd5c4a57ec8d9fdff9452c70d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 17:20:27 +0100 Subject: [PATCH 2188/2266] Clarify that uri vars can be left out of request *body* --- api/discovery/Discovery.md | 4 ++-- api/handler/ApplicationManager.md | 22 +++++++++++----------- utils/protoc-gen-ttndoc/main.go | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index d4602455d..c9c8833e1 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -20,7 +20,7 @@ Get all announcements for a specific service #### HTTP Endpoint -- `GET` `/announcements/{service_name}`(`service_name` can be left out of the request) +- `GET` `/announcements/{service_name}`(`service_name` can be left out of the request body) #### JSON Request Format @@ -65,7 +65,7 @@ Get a specific announcement #### HTTP Endpoint -- `GET` `/announcements/{service_name}/{id}`(`service_name`, `id` can be left out of the request) +- `GET` `/announcements/{service_name}/{id}`(`service_name`, `id` can be left out of the request body) #### JSON Request Format diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md index 9eef53965..79c37453f 100644 --- a/api/handler/ApplicationManager.md +++ b/api/handler/ApplicationManager.md @@ -38,7 +38,7 @@ GetApplication returns the application with the given identifier (app_id) #### HTTP Endpoint -- `GET` `/applications/{app_id}`(`app_id` can be left out of the request) +- `GET` `/applications/{app_id}`(`app_id` can be left out of the request body) #### JSON Request Format @@ -69,8 +69,8 @@ SetApplication updates the settings for the application. All fields must be supp #### HTTP Endpoints -- `POST` `/applications/{app_id}`(`app_id` can be left out of the request) -- `PUT` `/applications/{app_id}`(`app_id` can be left out of the request) +- `POST` `/applications/{app_id}`(`app_id` can be left out of the request body) +- `PUT` `/applications/{app_id}`(`app_id` can be left out of the request body) #### JSON Request Format @@ -99,7 +99,7 @@ DeleteApplication deletes the application with the given identifier (app_id) #### HTTP Endpoint -- `DELETE` `/applications/{app_id}`(`app_id` can be left out of the request) +- `DELETE` `/applications/{app_id}`(`app_id` can be left out of the request body) #### JSON Request Format @@ -124,7 +124,7 @@ GetDevice returns the device with the given identifier (app_id and dev_id) #### HTTP Endpoint -- `GET` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) +- `GET` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) #### JSON Request Format @@ -169,10 +169,10 @@ SetDevice creates or updates a device. All fields must be supplied. #### HTTP Endpoints -- `POST` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) -- `PUT` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) -- `POST` `/applications/{app_id}/devices`(`app_id` can be left out of the request) -- `PUT` `/applications/{app_id}/devices`(`app_id` can be left out of the request) +- `POST` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) +- `PUT` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) +- `POST` `/applications/{app_id}/devices`(`app_id` can be left out of the request body) +- `PUT` `/applications/{app_id}/devices`(`app_id` can be left out of the request body) #### JSON Request Format @@ -214,7 +214,7 @@ DeleteDevice deletes the device with the given identifier (app_id and dev_id) #### HTTP Endpoint -- `DELETE` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request) +- `DELETE` `/applications/{app_id}/devices/{dev_id}`(`app_id`, `dev_id` can be left out of the request body) #### JSON Request Format @@ -240,7 +240,7 @@ GetDevicesForApplication returns all devices that belong to the application with #### HTTP Endpoint -- `GET` `/applications/{app_id}/devices`(`app_id` can be left out of the request) +- `GET` `/applications/{app_id}/devices`(`app_id` can be left out of the request body) #### JSON Request Format diff --git a/utils/protoc-gen-ttndoc/main.go b/utils/protoc-gen-ttndoc/main.go index e493a479c..066b53df6 100644 --- a/utils/protoc-gen-ttndoc/main.go +++ b/utils/protoc-gen-ttndoc/main.go @@ -138,7 +138,7 @@ func main() { } } if len(params) > 0 { - fmt.Fprintf(content, "(%s can be left out of the request)", strings.Join(params, ", ")) + fmt.Fprintf(content, "(%s can be left out of the request body)", strings.Join(params, ", ")) } fmt.Fprintln(content) } From ec1c2cb214b8e5c507dde9f3ee0163e42f208366 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 19:54:19 +0100 Subject: [PATCH 2189/2266] Default HTTP ports (8080+cid) - Discovery 190(0) -> 8080 - Handler 190(4) -> 8084 --- Procfile | 2 +- cmd/discovery.go | 2 +- cmd/handler.go | 2 +- docker-compose.yml | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Procfile b/Procfile index 4e4e325c7..8dd4221ea 100644 --- a/Procfile +++ b/Procfile @@ -2,4 +2,4 @@ rtr: ttn router --config ./.env/router/dev.yml --health-port 10901 dsc: ttn discovery --config ./.env/discovery/dev.yml --health-port 10900 ns: ttn networkserver --config ./.env/networkserver/dev.yml --health-port 10903 brk: ttn broker --config ./.env/broker/dev.yml --health-port 10902 -hdl: ttn handler --config ./.env/handler/dev.yml --health-port 10904 --http-port 8080 +hdl: ttn handler --config ./.env/handler/dev.yml --health-port 10904 diff --git a/cmd/discovery.go b/cmd/discovery.go index 2e42b7b4a..b7f3a4ae1 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -129,7 +129,7 @@ func init() { viper.BindPFlag("discovery.master-auth-servers", discoveryCmd.Flags().Lookup("master-auth-servers")) discoveryCmd.Flags().String("http-address", "0.0.0.0", "The IP address where the gRPC proxy should listen") - discoveryCmd.Flags().Int("http-port", 0, "The port where the gRPC proxy should listen") + discoveryCmd.Flags().Int("http-port", 8080, "The port where the gRPC proxy should listen") viper.BindPFlag("discovery.http-address", discoveryCmd.Flags().Lookup("http-address")) viper.BindPFlag("discovery.http-port", discoveryCmd.Flags().Lookup("http-port")) } diff --git a/cmd/handler.go b/cmd/handler.go index 15bab997e..c9c282cb6 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -172,7 +172,7 @@ func init() { viper.BindPFlag("handler.server-port", handlerCmd.Flags().Lookup("server-port")) handlerCmd.Flags().String("http-address", "0.0.0.0", "The IP address where the gRPC proxy should listen") - handlerCmd.Flags().Int("http-port", 0, "The port where the gRPC proxy should listen") + handlerCmd.Flags().Int("http-port", 8084, "The port where the gRPC proxy should listen") viper.BindPFlag("handler.http-address", handlerCmd.Flags().Lookup("http-address")) viper.BindPFlag("handler.http-port", handlerCmd.Flags().Lookup("http-port")) } diff --git a/docker-compose.yml b/docker-compose.yml index bcfe7c633..687a902f8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: - TTN_DISCOVERY_REDIS_ADDRESS=redis:6379 ports: - "1900:1900" + - "8080:8080" volumes: - "./.env/:/root/.env/" router: @@ -95,6 +96,7 @@ services: - TTN_HANDLER_AMQP_ADDRESS=rabbitmq:5672 ports: - "1904:1904" + - "8084:8084" volumes: - "./.env/:/root/.env/" bridge: From fc9761f8ab755907f4388dbc8227b56cab4f85a6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 20:18:22 +0100 Subject: [PATCH 2190/2266] Announce Handler API Address to Discovery server --- api/discovery/Discovery.md | 3 + api/discovery/discovery.pb.go | 126 +++++++++++++------- api/discovery/discovery.proto | 2 + cmd/handler.go | 7 +- core/discovery/announcement/announcement.go | 3 + core/discovery/discovery.go | 1 + utils/protoc-gen-ttndoc/json.go | 13 +- 7 files changed, 106 insertions(+), 49 deletions(-) diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index c9c8833e1..ecd552f56 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -36,6 +36,7 @@ Get all announcements for a specific service { "services": [ { + "api_address": "http://eu.thethings.network:8084", "certificate": "-----BEGIN CERTIFICATE-----\n...", "description": "", "id": "ttn-handler-eu", @@ -80,6 +81,7 @@ Get a specific announcement ```json { + "api_address": "http://eu.thethings.network:8084", "certificate": "-----BEGIN CERTIFICATE-----\n...", "description": "", "id": "ttn-handler-eu", @@ -129,6 +131,7 @@ The Announcement of a service (also called component) | `net_address` | `string` | Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) | | `public_key` | `string` | ECDSA public key of this component | | `certificate` | `string` | TLS Certificate (if TLS is enabled) | +| `api_address` | `string` | Contains the address where the HTTP API is exposed (if there is one) | | `metadata` | _repeated_ [`Metadata`](#discoverymetadata) | Metadata for this component | ### `.discovery.AnnouncementsResponse` diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index f093cd6ae..81bb90006 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -115,6 +115,8 @@ type Announcement struct { PublicKey string `protobuf:"bytes,12,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` // TLS Certificate (if TLS is enabled) Certificate string `protobuf:"bytes,13,opt,name=certificate,proto3" json:"certificate,omitempty"` + // Contains the address where the HTTP API is exposed (if there is one) + ApiAddress string `protobuf:"bytes,14,opt,name=api_address,json=apiAddress,proto3" json:"api_address,omitempty"` // Metadata for this component Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` } @@ -548,6 +550,12 @@ func (m *Announcement) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Certificate))) i += copy(dAtA[i:], m.Certificate) } + if len(m.ApiAddress) > 0 { + dAtA[i] = 0x72 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.ApiAddress))) + i += copy(dAtA[i:], m.ApiAddress) + } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { dAtA[i] = 0xaa @@ -767,6 +775,10 @@ func (m *Announcement) Size() (n int) { if l > 0 { n += 1 + l + sovDiscovery(uint64(l)) } + l = len(m.ApiAddress) + if l > 0 { + n += 1 + l + sovDiscovery(uint64(l)) + } if len(m.Metadata) > 0 { for _, e := range m.Metadata { l = e.Size() @@ -1224,6 +1236,35 @@ func (m *Announcement) Unmarshal(dAtA []byte) error { } m.Certificate = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 14: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ApiAddress", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ApiAddress = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) @@ -1795,46 +1836,47 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 653 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcf, 0x6e, 0xd3, 0x4e, - 0x10, 0xae, 0xe3, 0x5f, 0xf3, 0x4b, 0xc6, 0x69, 0x1a, 0x2d, 0xb4, 0x58, 0x81, 0xa6, 0xc1, 0x02, - 0x11, 0x90, 0xb0, 0xa5, 0x54, 0x9c, 0x10, 0xaa, 0x82, 0x12, 0x4a, 0x55, 0x5a, 0x2a, 0xab, 0x20, - 0xc4, 0x25, 0xda, 0xd8, 0xd3, 0x74, 0x55, 0x67, 0x1d, 0xec, 0x75, 0x50, 0x54, 0xf5, 0xd2, 0x57, - 0xe0, 0xc2, 0x95, 0xb7, 0x81, 0x1b, 0x12, 0x2f, 0x80, 0x0a, 0x0f, 0x82, 0xfc, 0xb7, 0xae, 0xda, - 0x80, 0x0a, 0x37, 0xef, 0x37, 0xdf, 0x7c, 0x33, 0xfb, 0x8d, 0x67, 0xe1, 0xc9, 0x90, 0x89, 0x83, - 0x60, 0xa0, 0x5b, 0xee, 0xc8, 0xd8, 0x3b, 0xc0, 0xbd, 0x03, 0xc6, 0x87, 0xfe, 0x0e, 0x8a, 0xf7, - 0xae, 0x77, 0x68, 0x08, 0xc1, 0x0d, 0x3a, 0x66, 0x86, 0xcd, 0x7c, 0xcb, 0x9d, 0xa0, 0x37, 0x3d, - 0xfb, 0xd2, 0xc7, 0x9e, 0x2b, 0x5c, 0x52, 0xce, 0x80, 0xfa, 0xcd, 0xa1, 0xeb, 0x0e, 0x1d, 0x34, - 0xa2, 0xc0, 0x20, 0xd8, 0x37, 0x70, 0x34, 0x16, 0x09, 0xaf, 0x7e, 0x2b, 0x09, 0x86, 0x6a, 0x94, - 0x73, 0x57, 0x50, 0xc1, 0x5c, 0xee, 0xc7, 0x51, 0xed, 0x44, 0x82, 0xd2, 0x36, 0x0a, 0x6a, 0x53, - 0x41, 0xc9, 0x7d, 0x90, 0x0f, 0x71, 0xaa, 0x4a, 0x4d, 0xa9, 0x55, 0x6d, 0xdf, 0xd0, 0xcf, 0x2a, - 0xa6, 0x0c, 0x7d, 0x0b, 0xa7, 0x66, 0xc8, 0x21, 0xd7, 0x61, 0x7e, 0x42, 0x9d, 0x00, 0xd5, 0x42, - 0x53, 0x6a, 0x55, 0xcc, 0xf8, 0xa0, 0x3d, 0x02, 0x79, 0x0b, 0xa7, 0xa4, 0x0c, 0xf3, 0x2f, 0xf7, - 0x9e, 0xf7, 0xcc, 0xda, 0x1c, 0x01, 0x28, 0xee, 0x9a, 0xbd, 0x67, 0x9b, 0x6f, 0x6a, 0x12, 0x51, - 0xe0, 0xff, 0xce, 0xee, 0x6e, 0xbf, 0xf7, 0x6a, 0xb3, 0x56, 0x08, 0x03, 0xe1, 0x61, 0xb3, 0x5b, - 0x93, 0xb5, 0x2f, 0x05, 0xa8, 0x74, 0x38, 0x77, 0x03, 0x6e, 0xe1, 0x08, 0xb9, 0x20, 0x55, 0x28, - 0x30, 0x3b, 0xea, 0xa3, 0x6c, 0x16, 0x98, 0x4d, 0x6e, 0x43, 0xc5, 0x47, 0x6f, 0xc2, 0x2c, 0xec, - 0x73, 0x3a, 0x8a, 0x8b, 0x96, 0x4d, 0x25, 0xc1, 0x76, 0xe8, 0x08, 0xc9, 0x3d, 0x58, 0x4c, 0x29, - 0x13, 0xf4, 0x7c, 0xe6, 0x72, 0x55, 0x8e, 0x58, 0xd5, 0x04, 0x7e, 0x1d, 0xa3, 0xa4, 0x09, 0x8a, - 0x8d, 0xbe, 0xe5, 0xb1, 0x71, 0xe8, 0x83, 0xfa, 0x5f, 0x2c, 0x95, 0x83, 0x48, 0x0d, 0xe4, 0xc0, - 0x73, 0xd4, 0xf9, 0x28, 0x12, 0x7e, 0x92, 0x65, 0x28, 0x8e, 0x83, 0x81, 0xc3, 0x2c, 0xb5, 0xd8, - 0x94, 0x5a, 0x25, 0x33, 0x39, 0x91, 0x55, 0x50, 0x38, 0x8a, 0x3e, 0xb5, 0x6d, 0x0f, 0x7d, 0x5f, - 0x55, 0xa2, 0x0c, 0xe0, 0x28, 0x3a, 0x31, 0x42, 0x56, 0x00, 0x62, 0x6a, 0x3f, 0x34, 0xb6, 0x12, - 0xc5, 0xcb, 0x31, 0x12, 0x1a, 0xd5, 0x04, 0xc5, 0x42, 0x4f, 0xb0, 0x7d, 0x66, 0x51, 0x81, 0xea, - 0x42, 0xdc, 0x4b, 0x0e, 0x22, 0x06, 0x94, 0x46, 0x89, 0xf9, 0xea, 0x52, 0x53, 0x6e, 0x29, 0xed, - 0x6b, 0x97, 0xcc, 0xc5, 0xcc, 0x48, 0x5a, 0x1b, 0x16, 0x36, 0x50, 0x74, 0x1c, 0xc7, 0xc4, 0x77, - 0x01, 0xfa, 0xe2, 0x82, 0x77, 0xd2, 0x05, 0xef, 0xb4, 0x75, 0x80, 0x0d, 0x14, 0x69, 0xc2, 0xd5, - 0xcd, 0xd7, 0x02, 0x58, 0xcc, 0x5a, 0xf9, 0x6b, 0x95, 0x73, 0x77, 0x0d, 0xad, 0xfc, 0xe3, 0x5d, - 0x5f, 0xc0, 0x52, 0xfe, 0xb7, 0xf1, 0x4d, 0xf4, 0xc7, 0x2e, 0xf7, 0x91, 0xac, 0x41, 0x29, 0x11, - 0xf6, 0x55, 0x29, 0x72, 0x2d, 0xff, 0x37, 0xe7, 0x73, 0xcc, 0x8c, 0xd8, 0xfe, 0x24, 0x43, 0xb9, - 0x9b, 0x92, 0xc8, 0x63, 0x28, 0xa5, 0x3c, 0x32, 0x2b, 0xb9, 0xbe, 0xac, 0xc7, 0xcb, 0xa5, 0xa7, - 0x9b, 0xa7, 0xf7, 0xc2, 0xcd, 0x23, 0x87, 0x50, 0x8c, 0x87, 0x40, 0xd4, 0x5c, 0xea, 0xb9, 0xb9, - 0xd4, 0x9b, 0x33, 0x44, 0xb3, 0x5b, 0x68, 0x77, 0x4f, 0xbe, 0xfd, 0xfc, 0x50, 0x58, 0x25, 0x2b, - 0xd1, 0xde, 0x66, 0x71, 0xe3, 0x28, 0x6f, 0xe4, 0x31, 0xa1, 0x20, 0x6f, 0xa0, 0x20, 0x4b, 0xe7, - 0x2b, 0xa5, 0x65, 0x66, 0xf5, 0xae, 0x3d, 0x88, 0xd4, 0xef, 0x10, 0xed, 0xb7, 0xea, 0xc6, 0x11, - 0xb3, 0x8f, 0x49, 0x07, 0x94, 0x8e, 0x6d, 0x67, 0xef, 0x44, 0xfd, 0xb2, 0xb1, 0x24, 0xf5, 0x66, - 0x59, 0xd2, 0x85, 0x6a, 0x17, 0x1d, 0x14, 0xf8, 0x2f, 0x2a, 0x6d, 0x02, 0xb5, 0x6c, 0x44, 0xdb, - 0x94, 0xd3, 0x21, 0x7a, 0x4f, 0xd7, 0x3f, 0x9f, 0x36, 0xa4, 0xaf, 0xa7, 0x0d, 0xe9, 0xfb, 0x69, - 0x43, 0xfa, 0xf8, 0xa3, 0x31, 0xf7, 0xf6, 0xe1, 0x95, 0x5e, 0xd6, 0x41, 0x31, 0x2a, 0xb2, 0xf6, - 0x2b, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x1a, 0x6d, 0xb4, 0x91, 0x05, 0x00, 0x00, + // 666 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, + 0x14, 0xad, 0xe3, 0xaf, 0xf9, 0x92, 0xeb, 0x34, 0x8d, 0x06, 0x5a, 0xac, 0x40, 0xd3, 0x60, 0x81, + 0x08, 0x48, 0xc4, 0x52, 0x2a, 0x56, 0x08, 0x55, 0x41, 0x09, 0xa5, 0x2a, 0x2d, 0x95, 0x55, 0x10, + 0x62, 0x13, 0x4d, 0xec, 0xdb, 0x74, 0x54, 0x67, 0x6c, 0xec, 0x71, 0x50, 0x54, 0x75, 0xd3, 0x57, + 0x60, 0xc3, 0x96, 0xb7, 0x61, 0x89, 0xc4, 0x0b, 0xa0, 0xc2, 0x8a, 0xa7, 0x40, 0xfe, 0xad, 0xab, + 0x36, 0xa0, 0xc2, 0xce, 0x73, 0xee, 0xb9, 0xe7, 0xcc, 0x9c, 0xeb, 0x19, 0x78, 0x32, 0x62, 0xe2, + 0x20, 0x18, 0xb6, 0x4d, 0x67, 0xac, 0xef, 0x1d, 0xe0, 0xde, 0x01, 0xe3, 0x23, 0x7f, 0x07, 0xc5, + 0x7b, 0xc7, 0x3b, 0xd4, 0x85, 0xe0, 0x3a, 0x75, 0x99, 0x6e, 0x31, 0xdf, 0x74, 0x26, 0xe8, 0x4d, + 0xcf, 0xbe, 0xda, 0xae, 0xe7, 0x08, 0x87, 0x94, 0x33, 0xa0, 0x7e, 0x73, 0xe4, 0x38, 0x23, 0x1b, + 0xf5, 0xa8, 0x30, 0x0c, 0xf6, 0x75, 0x1c, 0xbb, 0x22, 0xe1, 0xd5, 0x6f, 0x25, 0xc5, 0x50, 0x8d, + 0x72, 0xee, 0x08, 0x2a, 0x98, 0xc3, 0xfd, 0xb8, 0xaa, 0x9d, 0x48, 0x50, 0xda, 0x46, 0x41, 0x2d, + 0x2a, 0x28, 0xb9, 0x0f, 0xf2, 0x21, 0x4e, 0x55, 0xa9, 0x29, 0xb5, 0xaa, 0x9d, 0x1b, 0xed, 0x33, + 0xc7, 0x94, 0xd1, 0xde, 0xc2, 0xa9, 0x11, 0x72, 0xc8, 0x75, 0x98, 0x9f, 0x50, 0x3b, 0x40, 0xb5, + 0xd0, 0x94, 0x5a, 0x15, 0x23, 0x5e, 0x68, 0x8f, 0x40, 0xde, 0xc2, 0x29, 0x29, 0xc3, 0xfc, 0xcb, + 0xbd, 0xe7, 0x7d, 0xa3, 0x36, 0x47, 0x00, 0x8a, 0xbb, 0x46, 0xff, 0xd9, 0xe6, 0x9b, 0x9a, 0x44, + 0x14, 0xf8, 0xbf, 0xbb, 0xbb, 0x3b, 0xe8, 0xbf, 0xda, 0xac, 0x15, 0xc2, 0x42, 0xb8, 0xd8, 0xec, + 0xd5, 0x64, 0xed, 0x67, 0x01, 0x2a, 0x5d, 0xce, 0x9d, 0x80, 0x9b, 0x38, 0x46, 0x2e, 0x48, 0x15, + 0x0a, 0xcc, 0x8a, 0xf6, 0x51, 0x36, 0x0a, 0xcc, 0x22, 0xb7, 0xa1, 0xe2, 0xa3, 0x37, 0x61, 0x26, + 0x0e, 0x38, 0x1d, 0xc7, 0xa6, 0x65, 0x43, 0x49, 0xb0, 0x1d, 0x3a, 0x46, 0x72, 0x0f, 0x16, 0x53, + 0xca, 0x04, 0x3d, 0x9f, 0x39, 0x5c, 0x95, 0x23, 0x56, 0x35, 0x81, 0x5f, 0xc7, 0x28, 0x69, 0x82, + 0x62, 0xa1, 0x6f, 0x7a, 0xcc, 0x0d, 0x73, 0x50, 0xff, 0x8b, 0xa5, 0x72, 0x10, 0xa9, 0x81, 0x1c, + 0x78, 0xb6, 0x3a, 0x1f, 0x55, 0xc2, 0x4f, 0xb2, 0x0c, 0x45, 0x37, 0x18, 0xda, 0xcc, 0x54, 0x8b, + 0x4d, 0xa9, 0x55, 0x32, 0x92, 0x15, 0x59, 0x05, 0x85, 0xa3, 0x18, 0x50, 0xcb, 0xf2, 0xd0, 0xf7, + 0x55, 0x25, 0xea, 0x00, 0x8e, 0xa2, 0x1b, 0x23, 0x64, 0x05, 0x20, 0xa6, 0x0e, 0xc2, 0x60, 0x2b, + 0x51, 0xbd, 0x1c, 0x23, 0x61, 0x50, 0x4d, 0x50, 0x4c, 0xf4, 0x04, 0xdb, 0x67, 0x26, 0x15, 0xa8, + 0x2e, 0xc4, 0x7b, 0xc9, 0x41, 0xa1, 0x03, 0x75, 0x59, 0xe6, 0x50, 0x8d, 0x1d, 0xa8, 0xcb, 0x52, + 0x07, 0x1d, 0x4a, 0xe3, 0x64, 0x3a, 0xea, 0x52, 0x53, 0x6e, 0x29, 0x9d, 0x6b, 0x97, 0x0c, 0xce, + 0xc8, 0x48, 0x5a, 0x07, 0x16, 0x36, 0x50, 0x74, 0x6d, 0xdb, 0xc0, 0x77, 0x01, 0xfa, 0xe2, 0x42, + 0xb8, 0xd2, 0x85, 0x70, 0xb5, 0x75, 0x80, 0x0d, 0x14, 0x69, 0xc3, 0xd5, 0xa7, 0xa3, 0x05, 0xb0, + 0x98, 0x6d, 0xe5, 0xaf, 0x55, 0xce, 0x9d, 0x35, 0xcc, 0xfa, 0x8f, 0x67, 0x7d, 0x01, 0x4b, 0xf9, + 0xff, 0xca, 0x37, 0xd0, 0x77, 0x1d, 0xee, 0x23, 0x59, 0x83, 0x52, 0x22, 0xec, 0xab, 0x52, 0x94, + 0x5a, 0xfe, 0x77, 0xcf, 0xf7, 0x18, 0x19, 0xb1, 0xf3, 0x49, 0x86, 0x72, 0x2f, 0x25, 0x91, 0xc7, + 0x50, 0x4a, 0x79, 0x64, 0x56, 0x73, 0x7d, 0xb9, 0x1d, 0xdf, 0xbe, 0x76, 0x7a, 0x35, 0xdb, 0xfd, + 0xf0, 0x6a, 0x92, 0x43, 0x28, 0xc6, 0x43, 0x20, 0x6a, 0xae, 0xf5, 0xdc, 0x5c, 0xea, 0xcd, 0x19, + 0xa2, 0xd9, 0x29, 0xb4, 0xbb, 0x27, 0x5f, 0x7f, 0x7c, 0x28, 0xac, 0x92, 0x95, 0xe8, 0x62, 0x67, + 0x75, 0xfd, 0x28, 0x1f, 0xe4, 0x31, 0xa1, 0x20, 0x6f, 0xa0, 0x20, 0x4b, 0xe7, 0x9d, 0x52, 0x9b, + 0x59, 0x7b, 0xd7, 0x1e, 0x44, 0xea, 0x77, 0x88, 0xf6, 0x5b, 0x75, 0xfd, 0x88, 0x59, 0xc7, 0xa4, + 0x0b, 0x4a, 0xd7, 0xb2, 0xb2, 0x87, 0xa4, 0x7e, 0xd9, 0x58, 0x12, 0xbf, 0x59, 0x91, 0xf4, 0xa0, + 0xda, 0x43, 0x1b, 0x05, 0xfe, 0x8b, 0x4a, 0x87, 0x40, 0x2d, 0x1b, 0xd1, 0x36, 0xe5, 0x74, 0x84, + 0xde, 0xd3, 0xf5, 0xcf, 0xa7, 0x0d, 0xe9, 0xcb, 0x69, 0x43, 0xfa, 0x76, 0xda, 0x90, 0x3e, 0x7e, + 0x6f, 0xcc, 0xbd, 0x7d, 0x78, 0xa5, 0xa7, 0x77, 0x58, 0x8c, 0x4c, 0xd6, 0x7e, 0x05, 0x00, 0x00, + 0xff, 0xff, 0x7a, 0x2d, 0x85, 0xda, 0xb2, 0x05, 0x00, 0x00, } diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 979488aaa..ce057f40e 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -65,6 +65,8 @@ message Announcement { string public_key = 12; // TLS Certificate (if TLS is enabled) string certificate = 13; + // Contains the address where the HTTP API is exposed (if there is one) + string api_address = 14; // Metadata for this component repeated Metadata metadata = 21; } diff --git a/cmd/handler.go b/cmd/handler.go index c9c282cb6..070aa6bfe 100644 --- a/cmd/handler.go +++ b/cmd/handler.go @@ -58,6 +58,11 @@ var handlerCmd = &cobra.Command{ ctx.WithError(err).Fatal("Could not initialize component") } + httpActive := viper.GetString("handler.http-address") != "" && viper.GetInt("handler.http-port") != 0 + if httpActive && component.Identity.ApiAddress == "" { + component.Identity.ApiAddress = fmt.Sprintf("http://%s:%d", viper.GetString("handler.server-address-announce"), viper.GetInt("handler.http-port")) + } + // Handler handler := handler.NewRedisHandler( client, @@ -101,7 +106,7 @@ var handlerCmd = &cobra.Command{ go grpc.Serve(lis) defer grpc.Stop() - if viper.GetString("handler.http-address") != "" && viper.GetInt("handler.http-port") != 0 { + if httpActive { proxyConn, err := component.Identity.Dial() if err != nil { ctx.WithError(err).Fatal("Could not start client for gRPC proxy") diff --git a/core/discovery/announcement/announcement.go b/core/discovery/announcement/announcement.go index 69daff300..571d99f6d 100644 --- a/core/discovery/announcement/announcement.go +++ b/core/discovery/announcement/announcement.go @@ -153,6 +153,7 @@ type Announcement struct { NetAddress string `redis:"net_address"` PublicKey string `redis:"public_key"` Certificate string `redis:"certificate"` + APIAddress string `redis:"api_address"` Metadata []Metadata CreatedAt time.Time `redis:"created_at"` @@ -201,6 +202,7 @@ func (a Announcement) ToProto() *pb.Announcement { NetAddress: a.NetAddress, PublicKey: a.PublicKey, Certificate: a.Certificate, + ApiAddress: a.APIAddress, Metadata: metadata, } } @@ -221,6 +223,7 @@ func FromProto(a *pb.Announcement) Announcement { NetAddress: a.NetAddress, PublicKey: a.PublicKey, Certificate: a.Certificate, + APIAddress: a.ApiAddress, Metadata: metadata, } } diff --git a/core/discovery/discovery.go b/core/discovery/discovery.go index 7347c9d6e..a280f62fa 100644 --- a/core/discovery/discovery.go +++ b/core/discovery/discovery.go @@ -78,6 +78,7 @@ func (d *discovery) Announce(in *pb.Announcement) error { service.NetAddress = in.NetAddress service.PublicKey = in.PublicKey service.Certificate = in.Certificate + service.APIAddress = in.ApiAddress return d.services.Set(service) } diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go index 5397f8a58..327665a75 100644 --- a/utils/protoc-gen-ttndoc/json.go +++ b/utils/protoc-gen-ttndoc/json.go @@ -11,6 +11,7 @@ import ( var exampleValues = map[string]interface{}{ ".discovery.*.id": "ttn-handler-eu", ".discovery.*.service_name": "handler", + ".discovery.Announcement.api_address": "http://eu.thethings.network:8084", ".discovery.Announcement.certificate": "-----BEGIN CERTIFICATE-----\n...", ".discovery.Announcement.net_address": "eu.thethings.network:1904", ".discovery.Announcement.public_key": "-----BEGIN PUBLIC KEY-----\n...", @@ -20,10 +21,16 @@ var exampleValues = map[string]interface{}{ ".discovery.Metadata.value": "some-app-id", ".handler.*.app_id": "some-app-id", ".handler.*.dev_id": "some-dev-id", + ".handler.*.fields": `{"light":100}`, + ".handler.*.payload": "ZA==", + ".handler.*.port": 1, ".handler.Application.converter": "function Converter(decoded, port) {...", ".handler.Application.decoder": "function Decoder(bytes, port) {...", ".handler.Application.encoder": "Encoder(object, port) {...", ".handler.Application.validator": "Validator(converted, port) {...", + ".handler.DryDownlinkMessage.payload": "", + ".handler.LogEntry.fields": `["TTN",123]`, + ".handler.LogEntry.function": "decoder", ".lorawan.Device.activation_constraints": "local", ".lorawan.Device.app_eui": "0102030405060708", ".lorawan.Device.app_id": "some-app-id", @@ -34,12 +41,6 @@ var exampleValues = map[string]interface{}{ ".lorawan.Device.dev_id": "some-dev-id", ".lorawan.Device.nwk_s_key": "01020304050607080102030405060708", ".lorawan.Device.uses32_bit_f_cnt": true, - ".handler.*.port": 1, - ".handler.*.fields": `{"light":100}`, - ".handler.LogEntry.function": "decoder", - ".handler.LogEntry.fields": `["TTN",123]`, - ".handler.*.payload": "ZA==", - ".handler.DryDownlinkMessage.payload": "", } func (m *message) MapExample(tree *tree) map[string]interface{} { From ff3a5bc2bb3238f41cda44976b87f58fa3de4ef6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 17 Nov 2016 20:18:45 +0100 Subject: [PATCH 2191/2266] Update CLI docs --- cmd/docs/README.md | 29 ++++++++++++++++++++++------- ttnctl/cmd/docs/README.md | 18 ++++++++++++------ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/cmd/docs/README.md b/cmd/docs/README.md index 5d117298a..57c98ca5f 100644 --- a/cmd/docs/README.md +++ b/cmd/docs/README.md @@ -65,13 +65,28 @@ ttn broker register prefix registers a prefix to this Broker **Options** ``` - --cache Add a cache in front of the database - --redis-address string Redis server and port (default "localhost:6379") - --redis-db int Redis database - --server-address string The IP address to listen for communication (default "0.0.0.0") - --server-port int The port for communication (default 1900) + --cache Add a cache in front of the database + --http-address string The IP address where the gRPC proxy should listen (default "0.0.0.0") + --http-port int The port where the gRPC proxy should listen (default 8080) + --master-auth-servers stringSlice Auth servers that are allowed to manage this network (default [ttn-account]) + --redis-address string Redis server and port (default "localhost:6379") + --redis-db int Redis database + --server-address string The IP address to listen for communication (default "0.0.0.0") + --server-port int The port for communication (default 1900) ``` +### ttn discovery gen-cert + +ttn gen-cert generates a TLS Certificate + +**Usage:** `ttn discovery gen-cert` + +### ttn discovery gen-keypair + +ttn gen-keypair generates a public/private keypair + +**Usage:** `ttn discovery gen-keypair` + ## ttn handler @@ -87,8 +102,8 @@ ttn broker register prefix registers a prefix to this Broker --amqp-username string AMQP username (default "guest") --broker-id string The ID of the TTN Broker as announced in the Discovery server (default "dev") --http-address string The IP address where the gRPC proxy should listen (default "0.0.0.0") - --http-port int The port where the gRPC proxy should listen - --mqtt-address string MQTT host and port + --http-port int The port where the gRPC proxy should listen (default 8084) + --mqtt-address string MQTT host and port. Leave empty to disable MQTT --mqtt-password string MQTT password --mqtt-username string MQTT username --redis-address string Redis host and port (default "localhost:6379") diff --git a/ttnctl/cmd/docs/README.md b/ttnctl/cmd/docs/README.md index 693f1fa48..005e6aa5c 100644 --- a/ttnctl/cmd/docs/README.md +++ b/ttnctl/cmd/docs/README.md @@ -108,9 +108,11 @@ $ ttnctl applications pf INFO Connecting with Handler... INFO Found Application INFO Decoder function -function Decoder(bytes) { +function Decoder(bytes, port) { var decoded = {}; - decoded.led = bytes[0]; + if (port === 1) { + decoded.led = bytes[0]; + } return decoded; } INFO No converter function @@ -131,20 +133,24 @@ The functions are read from the supplied file or from STDIN. $ ttnctl applications pf set decoder INFO Discovering Handler... INFO Connecting with Handler... -function Decoder(bytes) { +function Decoder(bytes, port) { // Decode an uplink message from a buffer // (array) of bytes to an object of fields. var decoded = {}; - // decoded.led = bytes[0]; + // if (port === 1) { + // decoded.led = bytes[0]; + // } return decoded; } ########## Write your Decoder here and end with Ctrl+D (EOF): -function Decoder(bytes) { +function Decoder(bytes, port) { var decoded = {}; - decoded.led = bytes[0]; + // if (port === 1) { + // decoded.led = bytes[0]; + // } return decoded; } From e21eaebb37d8673722d783f75b4b9ecf1b3ed4eb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 21 Nov 2016 11:00:52 +0100 Subject: [PATCH 2192/2266] Discovery Metadata Value is byte slice [docs] [Skip CI] --- utils/protoc-gen-ttndoc/json.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go index 327665a75..31e4c88fc 100644 --- a/utils/protoc-gen-ttndoc/json.go +++ b/utils/protoc-gen-ttndoc/json.go @@ -18,7 +18,7 @@ var exampleValues = map[string]interface{}{ ".discovery.Announcement.public": true, ".discovery.Announcement.service_version": "2.0.0-dev-abcdef...", ".discovery.Metadata.key": "APP_ID", - ".discovery.Metadata.value": "some-app-id", + ".discovery.Metadata.value": "c29tZS1hcHAtaWQ=", ".handler.*.app_id": "some-app-id", ".handler.*.dev_id": "some-dev-id", ".handler.*.fields": `{"light":100}`, From ff869e97d8ad36ba8d5d32c11e8bf2abd585b186 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 21 Nov 2016 15:28:59 +0100 Subject: [PATCH 2193/2266] Make ttnctl debug consistent with ttn debug --- ttnctl/cmd/root.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 7a2bf23c5..9bfa61e57 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -31,7 +31,7 @@ var RootCmd = &cobra.Command{ Long: `ttnctl controls The Things Network from the command line.`, PersistentPreRun: func(cmd *cobra.Command, args []string) { var logLevel = log.InfoLevel - if debug { + if viper.GetBool("debug") { logLevel = log.DebugLevel } ctx = &log.Logger{ @@ -39,7 +39,7 @@ var RootCmd = &cobra.Command{ Handler: cliHandler.New(os.Stdout), } - if debug { + if viper.GetBool("debug") { util.PrintConfig(ctx, true) } @@ -64,7 +64,6 @@ func init() { cobra.OnInitialize(initConfig) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ttnctl.yml)") - RootCmd.PersistentFlags().BoolVarP(&debug, "debug", "d", false, "Enable debug mode") RootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "directory where ttnctl stores data (default is $HOME/.ttnctl)") viper.BindPFlag("data", RootCmd.PersistentFlags().Lookup("data")) @@ -141,6 +140,8 @@ func initConfig() { viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_", "-", "_")) viper.AutomaticEnv() + viper.BindEnv("debug") + // If a config file is found, read it in. if _, err := os.Stat(cfgFile); err == nil { err := viper.ReadInConfig() From 20beebb8c018183526aa95de95942abba6870c54 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 21 Nov 2016 17:44:23 +0100 Subject: [PATCH 2194/2266] Implement streaming for Router Refs #362 --- api/router/client.go | 303 ---------------------------- api/router/client_streams.go | 334 +++++++++++++++++++++++++++++++ api/router/communication_test.go | 187 +++++++++++++++++ api/router/gateway_client.go | 100 +++++++++ api/router/server_streams.go | 114 +++++++++++ core/router/server.go | 106 ++++------ ttnctl/cmd/join.go | 22 +- ttnctl/cmd/uplink.go | 26 ++- ttnctl/util/router.go | 10 +- vendor/vendor.json | 62 +++--- 10 files changed, 841 insertions(+), 423 deletions(-) delete mode 100644 api/router/client.go create mode 100644 api/router/client_streams.go create mode 100644 api/router/communication_test.go create mode 100644 api/router/gateway_client.go create mode 100644 api/router/server_streams.go diff --git a/api/router/client.go b/api/router/client.go deleted file mode 100644 index 44d54fdc1..000000000 --- a/api/router/client.go +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package router - -import ( - "io" - "sync" - - "time" - - "github.com/TheThingsNetwork/ttn/api" - "github.com/TheThingsNetwork/ttn/api/discovery" - "github.com/TheThingsNetwork/ttn/api/gateway" - "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/golang/protobuf/ptypes/empty" - "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" -) - -// GatewayClient is used as the main client for Gateways to communicate with the Router -type GatewayClient interface { - SendGatewayStatus(*gateway.Status) error - SendUplink(*UplinkMessage) error - Subscribe() (<-chan *DownlinkMessage, <-chan error, error) - Unsubscribe() error - Activate(*DeviceActivationRequest) (*DeviceActivationResponse, error) - Close() error -} - -// NewClient returns a new Client -func NewClient(routerAnnouncement *discovery.Announcement) (*Client, error) { - conn, err := routerAnnouncement.Dial() - if err != nil { - return nil, err - } - client := &Client{ - conn: conn, - client: NewRouterClient(conn), - } - return client, nil -} - -// Client is a wrapper around RouterClient -type Client struct { - mutex sync.Mutex - conn *grpc.ClientConn - client RouterClient - gateways []GatewayClient -} - -// ForGateway returns a GatewayClient that is configured for the provided gateway -func (c *Client) ForGateway(gatewayID string, tokenFunc func() string) GatewayClient { - defer c.mutex.Unlock() - c.mutex.Lock() - gatewayClient := NewGatewayClient(c.client, gatewayID, tokenFunc) - c.gateways = append(c.gateways, gatewayClient) - return gatewayClient -} - -// Close closes all gateway connections and then closes the connection with the Router -func (c *Client) Close() error { - defer c.mutex.Unlock() - c.mutex.Lock() - for _, gateway := range c.gateways { - gateway.Close() - } - time.Sleep(200 * time.Millisecond) - return c.conn.Close() -} - -// NewGatewayClient returns a new GatewayClient -func NewGatewayClient(client RouterClient, gatewayID string, tokenFunc func() string) GatewayClient { - gatewayClient := &gatewayClient{ - client: client, - id: gatewayID, - tokenFunc: tokenFunc, - } - return gatewayClient -} - -type gatewayClient struct { - mutex sync.Mutex - id string - tokenFunc func() string - client RouterClient - gatewayStatus Router_GatewayStatusClient - stopGatewayStatus chan bool - uplink Router_UplinkClient - stopUplink chan bool - downlink Router_SubscribeClient - stopDownlink chan bool -} - -func (c *gatewayClient) getContext() context.Context { - md := metadata.Pairs( - "id", c.id, - "token", c.tokenFunc(), - ) - gatewayContext := metadata.NewContext(context.Background(), md) - return gatewayContext -} - -func (c *gatewayClient) setupGatewayStatus() error { - api.GetLogger().Debugf("Setting up gateway status stream for %s...", c.id) - gatewayStatusClient, err := c.client.GatewayStatus(c.getContext()) - if err != nil { - return err - } - c.gatewayStatus = gatewayStatusClient - c.stopGatewayStatus = make(chan bool) - go func() { - msg := new(empty.Empty) - for { - select { - case <-c.stopGatewayStatus: - return - default: - if err := gatewayStatusClient.RecvMsg(msg); err != nil { - api.GetLogger().Warnf("Error in gateway status stream for %s: %s", c.id, err.Error()) - c.teardownGatewayStatus() - return - } - api.GetLogger().Debugf("Received: %v", msg) - } - } - }() - return nil -} - -func (c *gatewayClient) teardownGatewayStatus() { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.gatewayStatus != nil { - api.GetLogger().Debugf("Closing gateway status stream for %s...", c.id) - close(c.stopGatewayStatus) - c.gatewayStatus.CloseSend() - c.gatewayStatus = nil - } -} - -func (c *gatewayClient) SendGatewayStatus(status *gateway.Status) error { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.gatewayStatus == nil { - if err := c.setupGatewayStatus(); err != nil { - return errors.FromGRPCError(err) - } - } - if err := c.gatewayStatus.Send(status); err != nil { - if err == io.EOF { - api.GetLogger().Warnf("Could not send gateway status for %s on closed stream", c.id) - go c.teardownGatewayStatus() - return errors.FromGRPCError(err) - } - api.GetLogger().Warnf("Error sending gateway status for %s: %s", c.id, err.Error()) - return errors.FromGRPCError(err) - } - return nil -} - -func (c *gatewayClient) setupUplink() error { - api.GetLogger().Debugf("Setting up uplink stream for %s...", c.id) - uplinkClient, err := c.client.Uplink(c.getContext()) - if err != nil { - return err - } - c.uplink = uplinkClient - c.stopUplink = make(chan bool) - go func() { - msg := new(empty.Empty) - for { - select { - case <-c.stopUplink: - return - default: - if err := uplinkClient.RecvMsg(msg); err != nil { - api.GetLogger().Warnf("Error in uplink stream for %s: %s", c.id, err.Error()) - c.teardownUplink() - return - } - api.GetLogger().Debugf("Received: %v", msg) - - } - } - }() - return nil -} - -func (c *gatewayClient) teardownUplink() { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.uplink != nil { - api.GetLogger().Debugf("Closing uplink stream for %s...", c.id) - close(c.stopUplink) - c.uplink.CloseSend() - c.uplink = nil - } -} - -func (c *gatewayClient) SendUplink(uplink *UplinkMessage) error { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.uplink == nil { - if err := c.setupUplink(); err != nil { - return errors.FromGRPCError(err) - } - } - if err := c.uplink.Send(uplink); err != nil { - if err == io.EOF { - api.GetLogger().Warnf("Could not send uplink for %s on closed stream", c.id) - go c.teardownUplink() - return errors.FromGRPCError(err) - } - api.GetLogger().Warnf("Error sending uplink for %s: %s", c.id, err.Error()) - return errors.FromGRPCError(err) - } - return nil -} - -func (c *gatewayClient) setupDownlink() error { - api.GetLogger().Debugf("Setting up downlink stream for %s...", c.id) - ctx, cancel := context.WithCancel(c.getContext()) - downlinkClient, err := c.client.Subscribe(ctx, &SubscribeRequest{}) - if err != nil { - return err - } - c.stopDownlink = make(chan bool) - go func() { - <-c.stopDownlink - cancel() - }() - c.downlink = downlinkClient - return nil -} - -func (c *gatewayClient) teardownDownlink() { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.downlink != nil { - api.GetLogger().Debugf("Closing downlink stream for %s...", c.id) - close(c.stopDownlink) - c.downlink.CloseSend() - c.downlink = nil - } -} - -func (c *gatewayClient) Subscribe() (<-chan *DownlinkMessage, <-chan error, error) { - defer c.mutex.Unlock() - c.mutex.Lock() - if c.downlink == nil { - if err := c.setupDownlink(); err != nil { - return nil, nil, errors.FromGRPCError(err) - } - } - downChan := make(chan *DownlinkMessage) - errChan := make(chan error) - go func() { - defer func() { - close(downChan) - close(errChan) - }() - for { - select { - case <-c.stopDownlink: - return - default: - downlink, err := c.downlink.Recv() - if err != nil { - if grpc.Code(err) == codes.Canceled { - api.GetLogger().Debugf("Downlink stream for %s was canceled", c.id) - errChan <- nil - } else { - api.GetLogger().Warnf("Error receiving gateway downlink for %s: %s", c.id, err.Error()) - errChan <- errors.FromGRPCError(err) - } - c.teardownDownlink() - return - } - downChan <- downlink - } - } - }() - return downChan, errChan, nil -} - -func (c *gatewayClient) Unsubscribe() error { - c.teardownDownlink() - return nil -} - -func (c *gatewayClient) Activate(req *DeviceActivationRequest) (*DeviceActivationResponse, error) { - return c.client.Activate(c.getContext(), req) -} - -func (c *gatewayClient) Close() error { - c.teardownGatewayStatus() - c.teardownUplink() - c.teardownDownlink() - return nil -} diff --git a/api/router/client_streams.go b/api/router/client_streams.go new file mode 100644 index 000000000..cdde9c563 --- /dev/null +++ b/api/router/client_streams.go @@ -0,0 +1,334 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "io" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +// GatewayStream interface +type GatewayStream interface { + Close() +} + +type gatewayStream struct { + closing bool + ctx api.Logger + client RouterClientForGateway +} + +// DefaultBufferSize indicates the default send and receive buffer sizes +var DefaultBufferSize = 10 + +// GatewayStatusStream for sending gateway statuses +type GatewayStatusStream interface { + GatewayStream + Send(*gateway.Status) error +} + +// NewMonitoredGatewayStatusStream starts and monitors a GatewayStatusStream +func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatusStream { + s := &gatewayStatusStream{ + ch: make(chan *gateway.Status, DefaultBufferSize), + err: make(chan error), + } + s.client = client + s.ctx = client.GetLogger() + + go func() { + var retries int + + for { + // Session channels + ch := make(chan *gateway.Status) + errCh := make(chan error) + + // Session client + client, err := s.client.GatewayStatus() + if err != nil { + s.ctx.WithError(err).Warn("Could not start GatewayStatus stream, retrying...") + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Debug("Started GatewayStatus stream") + + // Receive errors + go func() { + empty := new(empty.Empty) + if err := client.RecvMsg(empty); err != nil { + errCh <- err + } + close(errCh) + }() + + // Send + go func() { + for status := range ch { + s.ctx.Debug("Sending GatewayStatus message") + if err := client.Send(status); err != nil { + s.ctx.WithError(err).Warn("Error sending GatewayStatus message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case mErr = <-errCh: + break monitor + case msg, ok := <-s.ch: + if !ok { + break monitor // channel closed + } + ch <- msg + } + } + + close(ch) + _, mErr = client.CloseAndRecv() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped GatewayStatus stream") + if s.closing { + break + } + } else { + s.ctx.WithError(mErr).Warn("Error in GatewayStatus stream") + } + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type gatewayStatusStream struct { + gatewayStream + ch chan *gateway.Status + err chan error +} + +func (s *gatewayStatusStream) Send(status *gateway.Status) error { + select { + case s.ch <- status: + default: + s.ctx.Warn("Dropping GatewayStatus message, buffer full") + } + return nil +} + +func (s *gatewayStatusStream) Close() { + s.closing = true + close(s.ch) +} + +// UplinkStream for sending uplink messages +type UplinkStream interface { + GatewayStream + Send(*UplinkMessage) error +} + +// NewMonitoredUplinkStream starts and monitors a UplinkStream +func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { + s := &uplinkStream{ + ch: make(chan *UplinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.client = client + s.ctx = client.GetLogger() + + go func() { + var retries int + + for { + // Session channels + ch := make(chan *UplinkMessage) + errCh := make(chan error) + + // Session client + client, err := s.client.Uplink() + if err != nil { + s.ctx.WithError(err).Warn("Could not start Uplink stream, retrying...") + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Debug("Started Uplink stream") + + // Receive errors + go func() { + empty := new(empty.Empty) + if err := client.RecvMsg(empty); err != nil { + errCh <- err + } + close(errCh) + }() + + // Send + go func() { + for message := range ch { + s.ctx.Debug("Sending Uplink message") + if err := client.Send(message); err != nil { + s.ctx.WithError(err).Warn("Error sending Uplink message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case mErr = <-errCh: + break monitor + case msg, ok := <-s.ch: + if !ok { + break monitor // channel closed + } + ch <- msg + } + } + + close(ch) + _, mErr = client.CloseAndRecv() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + if s.closing { + break + } + } else { + s.ctx.WithError(mErr).Warn("Error in Uplink stream") + } + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type uplinkStream struct { + gatewayStream + ch chan *UplinkMessage + err chan error +} + +func (s *uplinkStream) Send(message *UplinkMessage) error { + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Uplink message, buffer full") + } + return nil +} + +func (s *uplinkStream) Close() { + s.closing = true + close(s.ch) +} + +// DownlinkStream for sending downlink messages +type DownlinkStream interface { + GatewayStream + Channel() <-chan *DownlinkMessage +} + +// NewMonitoredDownlinkStream starts and monitors a DownlinkStream +func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { + s := &downlinkStream{ + ch: make(chan *DownlinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.client = client + s.ctx = client.GetLogger() + + go func() { + var client Router_SubscribeClient + var err error + var retries int + var message *DownlinkMessage + + for { + client, s.cancel, err = s.client.Subscribe() + if err != nil { + s.ctx.WithError(err).Warn("Could not start Downlink stream, retrying...") + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Debug("Started Downlink stream") + + for { + message, err = client.Recv() + if message != nil { + s.ctx.Debug("Receiving Downlink message") + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Downlink message, buffer full") + } + } + if err != nil { + break + } + } + + if err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + if s.closing { + break + } + } else { + s.ctx.WithError(err).Warn("Error in Downlink stream") + } + time.Sleep(backoff.Backoff(retries)) + retries++ + } + + close(s.ch) + }() + return s +} + +type downlinkStream struct { + gatewayStream + cancel context.CancelFunc + ch chan *DownlinkMessage + err chan error +} + +func (s *downlinkStream) Close() { + s.closing = true + if s.cancel != nil { + s.cancel() + } +} + +func (s *downlinkStream) Channel() <-chan *DownlinkMessage { + return s.ch +} diff --git a/api/router/communication_test.go b/api/router/communication_test.go new file mode 100644 index 000000000..2bee6175c --- /dev/null +++ b/api/router/communication_test.go @@ -0,0 +1,187 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "fmt" + "math/rand" + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/protocol" + "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +func newTestRouter() *testRouter { + return &testRouter{ + RouterStreamServer: NewRouterStreamServer(), + } +} + +type testRouter struct { + *RouterStreamServer +} + +func (s *testRouter) Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) { + return nil, grpc.Errorf(codes.Unimplemented, "Not implemented") +} + +func (s *testRouter) Serve(port int) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + srv := grpc.NewServer() + RegisterRouterServer(srv, s) + srv.Serve(lis) +} + +func TestRouterCommunication(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestRouterCommunication") + api.SetLogger(api.Apex(ctx)) + + rtr := newTestRouter() + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go rtr.Serve(port) + + conn, _ := grpc.Dial(fmt.Sprintf("localhost:%d", port), grpc.WithBlock(), grpc.WithInsecure()) + + { + rtr.UplinkChanFunc = func(md metadata.MD) (chan *UplinkMessage, error) { + ch := make(chan *UplinkMessage, 1) + go func() { + ctx.Info("[SERVER] Channel opened") + for message := range ch { + ctx.WithField("Message", message).Info("[SERVER] Received Uplink") + } + ctx.Info("[SERVER] Channel closed") + }() + return ch, nil + } + + rtrClient := NewRouterClient(conn) + gtwClient := NewRouterClientForGateway(rtrClient, "dev", "token") + uplink := NewMonitoredUplinkStream(gtwClient) + + err := uplink.Send(&UplinkMessage{ + Payload: []byte{1, 2, 3, 4}, + ProtocolMetadata: &protocol.RxMetadata{Protocol: &protocol.RxMetadata_Lorawan{Lorawan: &lorawan.Metadata{ + Modulation: lorawan.Modulation_LORA, + DataRate: "SF7BW125", + CodingRate: "4/7", + }}}, + GatewayMetadata: &gateway.RxMetadata{ + GatewayId: "dev", + }, + }) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + uplink.Close() + + time.Sleep(10 * time.Millisecond) + + gtwClient.Close() + + time.Sleep(10 * time.Millisecond) + } + + { + rtr.GatewayStatusChanFunc = func(md metadata.MD) (chan *gateway.Status, error) { + ch := make(chan *gateway.Status, 1) + go func() { + ctx.Info("[SERVER] Channel opened") + for message := range ch { + ctx.WithField("Message", message).Info("[SERVER] Received GatewayStatus") + } + ctx.Info("[SERVER] Channel closed") + }() + return ch, nil + } + + rtrClient := NewRouterClient(conn) + gtwClient := NewRouterClientForGateway(rtrClient, "dev", "token") + status := NewMonitoredGatewayStatusStream(gtwClient) + + err := status.Send(&gateway.Status{Time: time.Now().UnixNano()}) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + status.Close() + + time.Sleep(10 * time.Millisecond) + + gtwClient.Close() + + time.Sleep(10 * time.Millisecond) + } + + { + rtr.DownlinkChanFunc = func(md metadata.MD) (<-chan *DownlinkMessage, func(), error) { + ch := make(chan *DownlinkMessage, 1) + stop := make(chan struct{}) + cancel := func() { + ctx.Info("[SERVER] Canceling downlink") + close(stop) + } + go func() { + loop: + for { + select { + case <-stop: + break loop + case <-time.After(5 * time.Millisecond): + ctx.Info("[SERVER] Sending Downlink") + ch <- &DownlinkMessage{ + Payload: []byte{1, 2, 3, 4}, + } + } + } + close(ch) + ctx.Info("[SERVER] Closed Downlink") + }() + return ch, cancel, nil + } + + rtrClient := NewRouterClient(conn) + gtwClient := NewRouterClientForGateway(rtrClient, "dev", "token") + downlink := NewMonitoredDownlinkStream(gtwClient) + + ch := downlink.Channel() + + go func() { + for downlink := range ch { + ctx.WithField("Downlink", downlink).Info("[CLIENT] Received Downlink") + } + ctx.Info("[CLIENT] Closed Downlink") + }() + + time.Sleep(10 * time.Millisecond) + + downlink.Close() + + time.Sleep(10 * time.Millisecond) + + gtwClient.Close() + + time.Sleep(10 * time.Millisecond) + } + +} diff --git a/api/router/gateway_client.go b/api/router/gateway_client.go new file mode 100644 index 000000000..727111911 --- /dev/null +++ b/api/router/gateway_client.go @@ -0,0 +1,100 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "sync" + + "github.com/TheThingsNetwork/ttn/api" + "golang.org/x/net/context" + "google.golang.org/grpc/metadata" +) + +// RouterClientForGateway is a RouterClient for a specific gateway +type RouterClientForGateway interface { + Close() + + GetLogger() api.Logger + SetLogger(api.Logger) + + SetToken(token string) + + GatewayStatus() (Router_GatewayStatusClient, error) + Uplink() (Router_UplinkClient, error) + Subscribe() (Router_SubscribeClient, context.CancelFunc, error) + Activate(in *DeviceActivationRequest) (*DeviceActivationResponse, error) +} + +// NewRouterClientForGateway returns a new RouterClient for the given gateway ID and access token +func NewRouterClientForGateway(client RouterClient, gatewayID, token string) RouterClientForGateway { + ctx, cancel := context.WithCancel(context.Background()) + return &routerClientForGateway{ + ctx: api.GetLogger().WithField("GatewayID", gatewayID), + client: client, + gatewayID: gatewayID, + token: token, + bgCtx: ctx, + cancel: cancel, + } +} + +type routerClientForGateway struct { + ctx api.Logger + client RouterClient + gatewayID string + token string + bgCtx context.Context + cancel context.CancelFunc + mu sync.RWMutex +} + +func (c *routerClientForGateway) Close() { + c.cancel() +} + +func (c *routerClientForGateway) GetLogger() api.Logger { + return c.ctx +} + +func (c *routerClientForGateway) SetLogger(logger api.Logger) { + c.ctx = logger +} + +func (c *routerClientForGateway) getContext() context.Context { + c.mu.RLock() + defer c.mu.RUnlock() + md := metadata.Pairs( + "id", c.gatewayID, + "token", c.token, + ) + return metadata.NewContext(c.bgCtx, md) +} + +func (c *routerClientForGateway) SetToken(token string) { + c.mu.Lock() + defer c.mu.Unlock() + c.token = token +} + +func (c *routerClientForGateway) GatewayStatus() (Router_GatewayStatusClient, error) { + c.ctx.Debug("Starting GatewayStatus stream") + return c.client.GatewayStatus(c.getContext()) +} + +func (c *routerClientForGateway) Uplink() (Router_UplinkClient, error) { + c.ctx.Debug("Starting Uplink stream") + return c.client.Uplink(c.getContext()) +} + +func (c *routerClientForGateway) Subscribe() (Router_SubscribeClient, context.CancelFunc, error) { + c.ctx.Debug("Starting Subscribe stream") + ctx, cancel := context.WithCancel(c.getContext()) + client, err := c.client.Subscribe(ctx, &SubscribeRequest{}) + return client, cancel, err +} + +func (c *routerClientForGateway) Activate(in *DeviceActivationRequest) (*DeviceActivationResponse, error) { + c.ctx.Debug("Calling Activate") + return c.client.Activate(c.getContext(), in) +} diff --git a/api/router/server_streams.go b/api/router/server_streams.go new file mode 100644 index 000000000..e6c12a44a --- /dev/null +++ b/api/router/server_streams.go @@ -0,0 +1,114 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "io" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc/metadata" +) + +// RouterStreamServer handles gRPC streams as channels +type RouterStreamServer struct { + ctx api.Logger + UplinkChanFunc func(md metadata.MD) (ch chan *UplinkMessage, err error) + GatewayStatusChanFunc func(md metadata.MD) (ch chan *gateway.Status, err error) + DownlinkChanFunc func(md metadata.MD) (ch <-chan *DownlinkMessage, cancel func(), err error) +} + +// NewRouterStreamServer returns a new RouterStreamServer +func NewRouterStreamServer() *RouterStreamServer { + return &RouterStreamServer{ + ctx: api.GetLogger(), + } +} + +// SetLogger sets the logger +func (s *RouterStreamServer) SetLogger(logger api.Logger) { + s.ctx = logger +} + +// Uplink handles uplink streams +func (s *RouterStreamServer) Uplink(stream Router_UplinkServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, err := s.UplinkChanFunc(md) + if err != nil { + return err + } + defer func() { + ctx := s.ctx + if err != nil { + ctx = ctx.WithError(err) + } + close(ch) + ctx.Debug("Closed Uplink stream") + }() + for { + uplink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + if err := uplink.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + } + ch <- uplink + } +} + +// Subscribe handles downlink streams +func (s *RouterStreamServer) Subscribe(req *SubscribeRequest, stream Router_SubscribeServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, cancel, err := s.DownlinkChanFunc(md) + if err != nil { + return err + } + defer cancel() + for downlink := range ch { + if err := stream.Send(downlink); err != nil { + return err + } + } + return nil +} + +// GatewayStatus handles gateway status streams +func (s *RouterStreamServer) GatewayStatus(stream Router_GatewayStatusServer) error { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, err := s.GatewayStatusChanFunc(md) + if err != nil { + return err + } + defer func() { + close(ch) + }() + for { + status, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + if err := status.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Gateway Status")) + } + ch <- status + } +} diff --git a/core/router/server.go b/core/router/server.go index 179ac3d9c..0174fa32e 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -5,29 +5,25 @@ package router import ( "fmt" - "io" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/ttn/api" + pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/golang/protobuf/ptypes/empty" "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" ) type routerRPC struct { router *router + pb.RouterStreamServer } -func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gateway, err error) { - md, err := api.MetadataFromContext(ctx) - if err != nil { - return nil, err - } - +func (r *routerRPC) gatewayFromMetadata(md metadata.MD) (gtw *gateway.Gateway, err error) { gatewayID, err := api.IDFromMetadata(md) if err != nil { return nil, err @@ -57,76 +53,56 @@ func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gatewa return gtw, nil } -// GatewayStatus implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) -func (r *routerRPC) GatewayStatus(stream pb.Router_GatewayStatusServer) error { - gateway, err := r.gatewayFromContext(stream.Context()) +func (r *routerRPC) gatewayFromContext(ctx context.Context) (gtw *gateway.Gateway, err error) { + md, err := api.MetadataFromContext(ctx) if err != nil { - return errors.BuildGRPCError(err) + return nil, err } + return r.gatewayFromMetadata(md) +} - for { - status, err := stream.Recv() - if err == io.EOF { - return stream.SendAndClose(&empty.Empty{}) - } - if err != nil { - return err - } - if err := status.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Gateway Status")) - } - go r.router.HandleGatewayStatus(gateway.ID, status) +func (r *routerRPC) getUplink(md metadata.MD) (ch chan *pb.UplinkMessage, err error) { + gateway, err := r.gatewayFromMetadata(md) + if err != nil { + return nil, err } + ch = make(chan *pb.UplinkMessage) + go func() { + for uplink := range ch { + r.router.HandleUplink(gateway.ID, uplink) + } + }() + return } -// Uplink implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) -func (r *routerRPC) Uplink(stream pb.Router_UplinkServer) error { - gateway, err := r.gatewayFromContext(stream.Context()) +func (r *routerRPC) getGatewayStatus(md metadata.MD) (ch chan *pb_gateway.Status, err error) { + gateway, err := r.gatewayFromMetadata(md) if err != nil { - return errors.BuildGRPCError(err) + return nil, err } - - for { - uplink, err := stream.Recv() - if err == io.EOF { - return stream.SendAndClose(&empty.Empty{}) - } - if err != nil { - return err - } - if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + ch = make(chan *pb_gateway.Status) + go func() { + for status := range ch { + r.router.HandleGatewayStatus(gateway.ID, status) } - go r.router.HandleUplink(gateway.ID, uplink) - } + }() + return } -// Subscribe implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) -func (r *routerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Router_SubscribeServer) error { - gateway, err := r.gatewayFromContext(stream.Context()) +func (r *routerRPC) getDownlink(md metadata.MD) (ch <-chan *pb.DownlinkMessage, cancel func(), err error) { + gateway, err := r.gatewayFromMetadata(md) if err != nil { - return errors.BuildGRPCError(err) + return nil, nil, err + } + ch = make(chan *pb.DownlinkMessage) + cancel = func() { + r.router.UnsubscribeDownlink(gateway.ID) } - downlinkChannel, err := r.router.SubscribeDownlink(gateway.ID) if err != nil { - return errors.BuildGRPCError(err) - } - defer r.router.UnsubscribeDownlink(gateway.ID) - - for { - if downlinkChannel == nil { - return nil - } - select { - case <-stream.Context().Done(): - return stream.Context().Err() - case downlink := <-downlinkChannel: - if err := stream.Send(downlink); err != nil { - return err - } - } + return nil, nil, err } + return downlinkChannel, cancel, nil } // Activate implements RouterServer interface (github.com/TheThingsNetwork/ttn/api/router) @@ -143,6 +119,10 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques // RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) func (r *router) RegisterRPC(s *grpc.Server) { - server := &routerRPC{r} + server := &routerRPC{router: r} + server.SetLogger(api.Apex(r.Ctx)) + server.UplinkChanFunc = server.getUplink + server.DownlinkChanFunc = server.getDownlink + server.GatewayStatusChanFunc = server.getGatewayStatus pb.RegisterRouterServer(s, server) } diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index d2af980d3..1fe1092a0 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -49,8 +49,8 @@ var joinCmd = &cobra.Command{ } devNonce := [2]byte{devNonceSlice[0], devNonceSlice[1]} - rtrClient := util.GetRouter(ctx) - defer rtrClient.Close() + rtrConn, rtrClient := util.GetRouter(ctx) + defer rtrConn.Close() gatewayID := viper.GetString("gateway-id") gatewayToken := viper.GetString("gateway-token") @@ -67,13 +67,11 @@ var joinCmd = &cobra.Command{ } } - gtwClient := rtrClient.ForGateway(gatewayID, func() string { return gatewayToken }) + gtwClient := router.NewRouterClientForGateway(rtrClient, gatewayID, gatewayToken) defer gtwClient.Close() - downlink, errChan, err := gtwClient.Subscribe() - if err != nil { - ctx.WithError(err).Fatal("Could not start downlink stream") - } + downlinkStream := router.NewMonitoredDownlinkStream(gtwClient) + defer downlinkStream.Close() joinReq := &pb_lorawan.Message{ MHDR: pb_lorawan.MHDR{MType: pb_lorawan.MType_JOIN_REQUEST, Major: pb_lorawan.Major_LORAWAN_R1}, @@ -93,7 +91,10 @@ var joinCmd = &cobra.Command{ } uplink.UnmarshalPayload() - err = gtwClient.SendUplink(uplink) + uplinkStream := router.NewMonitoredUplinkStream(gtwClient) + defer uplinkStream.Close() + + err = uplinkStream.Send(uplink) if err != nil { ctx.WithError(err).Fatal("Could not send uplink to Router") } @@ -103,10 +104,7 @@ var joinCmd = &cobra.Command{ ctx.Info("Sent uplink to Router") select { - case err := <-errChan: - ctx.WithError(err).Fatal("Error in downlink") - case downlinkMessage := <-downlink: - + case downlinkMessage := <-downlinkStream.Channel(): downlinkMessage.UnmarshalPayload() resPhy := downlinkMessage.Message.GetLorawan().PHYPayload() resPhy.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 38942681c..9bc65243c 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -58,8 +58,8 @@ var uplinkCmd = &cobra.Command{ withDownlink = true } - rtrClient := util.GetRouter(ctx) - defer rtrClient.Close() + rtrConn, rtrClient := util.GetRouter(ctx) + defer rtrConn.Close() gatewayID := viper.GetString("gateway-id") gatewayToken := viper.GetString("gateway-token") @@ -76,16 +76,13 @@ var uplinkCmd = &cobra.Command{ } } - gtwClient := rtrClient.ForGateway(gatewayID, func() string { return gatewayToken }) + gtwClient := router.NewRouterClientForGateway(rtrClient, gatewayID, gatewayToken) defer gtwClient.Close() - var downlink <-chan *router.DownlinkMessage - var errChan <-chan error + var downlinkStream router.DownlinkStream if withDownlink { - downlink, errChan, err = gtwClient.Subscribe() - if err != nil { - ctx.WithError(err).Fatal("Could not start downlink stream") - } + downlinkStream = router.NewMonitoredDownlinkStream(gtwClient) + defer downlinkStream.Close() } m := &util.Message{} @@ -93,7 +90,10 @@ var uplinkCmd = &cobra.Command{ m.SetMessage(confirmed, fCnt, payload) bytes := m.Bytes() - err = gtwClient.SendUplink(&router.UplinkMessage{ + uplinkStream := router.NewMonitoredUplinkStream(gtwClient) + defer uplinkStream.Close() + + err = uplinkStream.Send(&router.UplinkMessage{ Payload: bytes, GatewayMetadata: util.GetGatewayMetadata("ttnctl", 868100000), ProtocolMetadata: util.GetProtocolMetadata("SF7BW125"), @@ -106,11 +106,9 @@ var uplinkCmd = &cobra.Command{ ctx.Info("Sent uplink to Router") - if downlink != nil { + if downlinkStream != nil { select { - case err := <-errChan: - ctx.WithError(err).Fatal("Error in downlink") - case downlinkMessage := <-downlink: + case downlinkMessage := <-downlinkStream.Channel(): if err := m.Unmarshal(downlinkMessage.Payload); err != nil { ctx.WithError(err).Fatal("Could not unmarshal downlink") } diff --git a/ttnctl/util/router.go b/ttnctl/util/router.go index e1b9f7ae7..ca76a0766 100644 --- a/ttnctl/util/router.go +++ b/ttnctl/util/router.go @@ -13,7 +13,7 @@ import ( ) // GetRouter starts a connection with the router -func GetRouter(ctx log.Interface) *router.Client { +func GetRouter(ctx log.Interface) (*grpc.ClientConn, router.RouterClient) { ctx.Info("Discovering Router...") dscConn, client := GetDiscovery(ctx) defer dscConn.Close() @@ -25,12 +25,10 @@ func GetRouter(ctx log.Interface) *router.Client { ctx.WithError(errors.FromGRPCError(err)).Fatal("Could not get Router from Discovery") } ctx.Info("Connecting with Router...") - router, err := router.NewClient(routerAnnouncement) - if err != nil { - ctx.WithError(err).Fatal("Could not connect to Router") - } + rtrConn, err := routerAnnouncement.Dial() ctx.Info("Connected to Router") - return router + rtrClient := router.NewRouterClient(rtrConn) + return rtrConn, rtrClient } // GetRouterManager starts a management connection with the router diff --git a/vendor/vendor.json b/vendor/vendor.json index 5c50e2931..12ae67f3f 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -765,70 +765,82 @@ "revisionTime": "2016-10-25T16:43:32Z" }, { - "checksumSHA1": "KzbvI1E+0cJe41jNUEoBksFpAZ8=", + "checksumSHA1": "xyB2Py2ViSKX8Td+oe2hxG6f0Ak=", "path": "google.golang.org/grpc", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "d0iunsiWfA0qXxLMNkTC4tGJnOo=", "path": "google.golang.org/grpc/health", - "revision": "e59af7a0a8bf571556b40c3f871dbc4298f77693", - "revisionTime": "2016-11-11T01:56:15Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "pSFXzfvPlaDBK2RsMcTiIeks4ok=", "path": "google.golang.org/grpc/health/grpc_health_v1", - "revision": "e59af7a0a8bf571556b40c3f871dbc4298f77693", - "revisionTime": "2016-11-11T01:56:15Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { - "checksumSHA1": "P64GkSdsTZ8Nxop5HYqZJ6e+iHs=", + "checksumSHA1": "XXpD8+S3gLrfmCLOf+RbxblOQkU=", "path": "google.golang.org/grpc/metadata", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { - "checksumSHA1": "HQJrtiTtr5eiRsXQLut2R1Q9kuY=", + "checksumSHA1": "FCgy+WB249Vt1XEG5pe4Z7plTLs=", + "path": "google.golang.org/grpc/stats", + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" + }, + { + "checksumSHA1": "N0TftT6/CyWqp6VRi2DqDx60+Fo=", + "path": "google.golang.org/grpc/tap", + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" + }, + { + "checksumSHA1": "uVGwQAu6ncFK7qd3BpzbMEJDUP4=", "path": "google.golang.org/grpc/transport", - "revision": "ceb1db3272127bb48be3933ba391bf5d74951274", - "revisionTime": "2016-11-04T23:58:17Z" + "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", + "revisionTime": "2016-11-18T23:23:07Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", From c128a3746b0d523f787f2e8064aedc4f5be72ac6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Nov 2016 08:30:40 +0100 Subject: [PATCH 2195/2266] Discovery Metadata Value is byte slice [docs] [Skip CI] --- api/discovery/Discovery.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index ecd552f56..627190406 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -43,7 +43,7 @@ Get all announcements for a specific service "metadata": [ { "key": "APP_ID", - "value": "some-app-id" + "value": "c29tZS1hcHAtaWQ=" } ], "net_address": "eu.thethings.network:1904", @@ -88,7 +88,7 @@ Get a specific announcement "metadata": [ { "key": "APP_ID", - "value": "some-app-id" + "value": "c29tZS1hcHAtaWQ=" } ], "net_address": "eu.thethings.network:1904", From 85f406ad2978bf859ebfb805130af184bec8fb69 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Nov 2016 08:31:23 +0100 Subject: [PATCH 2196/2266] Rename SK_920_923 to KR_920_923 To be consistent with LoRaWAN Spec --- api/protocol/lorawan/lorawan.pb.go | 24 ++++++++++++------------ api/protocol/lorawan/lorawan.proto | 2 +- core/router/downlink.go | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api/protocol/lorawan/lorawan.pb.go b/api/protocol/lorawan/lorawan.pb.go index c7e93c9af..0c0cb9606 100644 --- a/api/protocol/lorawan/lorawan.pb.go +++ b/api/protocol/lorawan/lorawan.pb.go @@ -76,7 +76,7 @@ const ( Region_AU_915_928 Region = 4 Region_CN_470_510 Region = 5 Region_AS_923 Region = 6 - Region_SK_920_923 Region = 7 + Region_KR_920_923 Region = 7 ) var Region_name = map[int32]string{ @@ -87,7 +87,7 @@ var Region_name = map[int32]string{ 4: "AU_915_928", 5: "CN_470_510", 6: "AS_923", - 7: "SK_920_923", + 7: "KR_920_923", } var Region_value = map[string]int32{ "EU_863_870": 0, @@ -97,7 +97,7 @@ var Region_value = map[string]int32{ "AU_915_928": 4, "CN_470_510": 5, "AS_923": 6, - "SK_920_923": 7, + "KR_920_923": 7, } func (x Region) String() string { @@ -3617,14 +3617,14 @@ var fileDescriptorLorawan = []byte{ 0x29, 0x17, 0x8a, 0xa4, 0x5c, 0xdc, 0xd1, 0x14, 0xc1, 0xd7, 0x9a, 0x64, 0x7b, 0xbb, 0x4c, 0xb6, 0x77, 0xb6, 0xb5, 0x08, 0x02, 0x88, 0x37, 0x3a, 0xe4, 0x61, 0xa9, 0xa4, 0x45, 0x85, 0xae, 0xd2, 0x21, 0xe5, 0xad, 0x47, 0xd2, 0x36, 0x16, 0xda, 0x3e, 0xdc, 0x2e, 0x90, 0x47, 0x5b, 0x05, 0x4d, - 0x15, 0xb6, 0x95, 0x36, 0x29, 0x17, 0x4b, 0x5a, 0x5c, 0xe8, 0xda, 0xcf, 0x48, 0xb9, 0x58, 0x90, - 0x7c, 0x62, 0xe3, 0x23, 0x50, 0xe5, 0x7a, 0x17, 0x0a, 0x11, 0xde, 0x8b, 0x4a, 0x93, 0xe0, 0x2d, - 0x6d, 0x61, 0xe3, 0x3b, 0x50, 0xe5, 0xeb, 0x80, 0x34, 0x58, 0x7c, 0xda, 0xda, 0x6b, 0x12, 0xdc, - 0x78, 0xde, 0x69, 0xb4, 0x8f, 0xb4, 0x05, 0xb4, 0x0c, 0x69, 0x29, 0xa9, 0xd4, 0x6a, 0x8d, 0xc3, - 0x23, 0x4d, 0x41, 0x08, 0x96, 0x3a, 0xcd, 0x5a, 0xab, 0xb9, 0xbb, 0x87, 0x0f, 0x1a, 0x75, 0xd2, - 0x39, 0xd4, 0x22, 0xe8, 0x0e, 0x68, 0xb3, 0xb2, 0x7a, 0xeb, 0x45, 0x53, 0x8b, 0x0a, 0xb0, 0x2b, - 0x76, 0x31, 0xe1, 0x3b, 0x67, 0xa5, 0x56, 0x77, 0x5f, 0xbd, 0x5d, 0x51, 0x5e, 0xbf, 0x5d, 0x51, - 0xfe, 0x78, 0xbb, 0xa2, 0xfc, 0xf8, 0xe7, 0xca, 0xc2, 0xb7, 0x0f, 0x6f, 0xf2, 0xe7, 0x7e, 0x1c, - 0x97, 0x92, 0xd2, 0x5f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x43, 0xc5, 0xba, 0x6a, 0xf8, 0x0b, 0x00, + 0x15, 0xb6, 0x95, 0x36, 0x29, 0x17, 0x4b, 0x5a, 0x5c, 0xe8, 0x9e, 0x61, 0x52, 0x2e, 0x16, 0x24, + 0x9f, 0xd8, 0xf8, 0x08, 0x54, 0xb9, 0xde, 0x85, 0x42, 0x84, 0xf7, 0xa2, 0xd2, 0x24, 0x78, 0x4b, + 0x5b, 0xd8, 0xf8, 0x0e, 0x54, 0xf9, 0x3a, 0x20, 0x0d, 0x16, 0x9f, 0xb6, 0xf6, 0x9a, 0x04, 0x37, + 0x9e, 0x77, 0x1a, 0xed, 0x23, 0x6d, 0x01, 0x2d, 0x43, 0x5a, 0x4a, 0x2a, 0xb5, 0x5a, 0xe3, 0xf0, + 0x48, 0x53, 0x10, 0x82, 0xa5, 0x4e, 0xb3, 0xd6, 0x6a, 0xee, 0xee, 0xe1, 0x83, 0x46, 0x9d, 0x74, + 0x0e, 0xb5, 0x08, 0xba, 0x03, 0xda, 0xac, 0xac, 0xde, 0x7a, 0xd1, 0xd4, 0xa2, 0x02, 0xec, 0x8a, + 0x5d, 0x4c, 0xf8, 0xce, 0x59, 0xa9, 0xd5, 0xdd, 0x57, 0x6f, 0x57, 0x94, 0xd7, 0x6f, 0x57, 0x94, + 0x3f, 0xde, 0xae, 0x28, 0x3f, 0xfe, 0xb9, 0xb2, 0xf0, 0xed, 0xc3, 0x9b, 0xfc, 0xb9, 0x1f, 0xc7, + 0xa5, 0xa4, 0xf4, 0x57, 0x00, 0x00, 0x00, 0xff, 0xff, 0xc8, 0x4e, 0xb3, 0xab, 0xf8, 0x0b, 0x00, 0x00, } diff --git a/api/protocol/lorawan/lorawan.proto b/api/protocol/lorawan/lorawan.proto index dc9d853a1..a35476455 100644 --- a/api/protocol/lorawan/lorawan.proto +++ b/api/protocol/lorawan/lorawan.proto @@ -52,7 +52,7 @@ enum Region { AU_915_928 = 4; CN_470_510 = 5; AS_923 = 6; - SK_920_923 = 7; + KR_920_923 = 7; } message Message { diff --git a/core/router/downlink.go b/core/router/downlink.go index 4beb01162..07e27fd5c 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -80,7 +80,7 @@ func guessRegion(frequency uint64) string { case frequency == 923200000 || frequency == 923400000: return pb_lorawan.Region_AS_923.String() case frequency >= 920900000 || frequency == 923300000: - return pb_lorawan.Region_SK_920_923.String() + return pb_lorawan.Region_KR_920_923.String() case frequency >= 915200000 && frequency <= 927800000: return pb_lorawan.Region_AU_915_928.String() case frequency >= 470300000 && frequency <= 489300000: @@ -107,7 +107,7 @@ func getBand(region string) (band *lora.Band, err error) { err = errors.NewErrInternal("China 470-510 MHz band not supported") case pb_lorawan.Region_AS_923.String(): err = errors.NewErrInternal("Asia 923 MHz band not supported") - case pb_lorawan.Region_SK_920_923.String(): + case pb_lorawan.Region_KR_920_923.String(): err = errors.NewErrInternal("South Korea 920-923 MHz band not supported") default: err = errors.NewErrInvalidArgument("Frequency Band", "unknown") From f9bb8ee670af647dee3a49ba9b0f21ad52fc7fce Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Nov 2016 11:49:46 +0100 Subject: [PATCH 2197/2266] Also close streams if s.closing AND err --- api/router/client_streams.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/api/router/client_streams.go b/api/router/client_streams.go index cdde9c563..8f49b5d7a 100644 --- a/api/router/client_streams.go +++ b/api/router/client_streams.go @@ -102,16 +102,18 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu } close(ch) - _, mErr = client.CloseAndRecv() + client.CloseAndRecv() if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { s.ctx.Debug("Stopped GatewayStatus stream") - if s.closing { - break - } } else { s.ctx.WithError(mErr).Warn("Error in GatewayStatus stream") } + + if s.closing { + break + } + time.Sleep(backoff.Backoff(retries)) retries++ } @@ -212,16 +214,18 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { } close(ch) - _, mErr = client.CloseAndRecv() + client.CloseAndRecv() if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { s.ctx.Debug("Stopped Uplink stream") - if s.closing { - break - } } else { s.ctx.WithError(mErr).Warn("Error in Uplink stream") } + + if s.closing { + break + } + time.Sleep(backoff.Backoff(retries)) retries++ } @@ -300,12 +304,14 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { if err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled { s.ctx.Debug("Stopped Downlink stream") - if s.closing { - break - } } else { s.ctx.WithError(err).Warn("Error in Downlink stream") } + + if s.closing { + break + } + time.Sleep(backoff.Backoff(retries)) retries++ } From 42eafaeb7552aea47542584d2f28aa987cf1b2e0 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Nov 2016 12:02:42 +0100 Subject: [PATCH 2198/2266] Don't reconnect on canceled streams --- api/router/client_streams.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/api/router/client_streams.go b/api/router/client_streams.go index 8f49b5d7a..6b8a62c0a 100644 --- a/api/router/client_streams.go +++ b/api/router/client_streams.go @@ -56,6 +56,10 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu // Session client client, err := s.client.GatewayStatus() if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped GatewayStatus stream") + break + } s.ctx.WithError(err).Warn("Could not start GatewayStatus stream, retrying...") time.Sleep(backoff.Backoff(retries)) retries++ @@ -168,6 +172,10 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { // Session client client, err := s.client.Uplink() if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + break + } s.ctx.WithError(err).Warn("Could not start Uplink stream, retrying...") time.Sleep(backoff.Backoff(retries)) retries++ @@ -278,6 +286,10 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { for { client, s.cancel, err = s.client.Subscribe() if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + break + } s.ctx.WithError(err).Warn("Could not start Downlink stream, retrying...") time.Sleep(backoff.Backoff(retries)) retries++ From 2a8e6ef5c57df13303a1ba783efd7fcde701dd25 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 22 Nov 2016 12:32:59 +0100 Subject: [PATCH 2199/2266] Wait for stream setup to finish before closing --- api/router/client_streams.go | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/api/router/client_streams.go b/api/router/client_streams.go index 6b8a62c0a..5b53b34c8 100644 --- a/api/router/client_streams.go +++ b/api/router/client_streams.go @@ -5,6 +5,7 @@ package router import ( "io" + "sync" "time" "github.com/TheThingsNetwork/ttn/api" @@ -23,6 +24,7 @@ type GatewayStream interface { type gatewayStream struct { closing bool + setup sync.WaitGroup ctx api.Logger client RouterClientForGateway } @@ -42,6 +44,7 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu ch: make(chan *gateway.Status, DefaultBufferSize), err: make(chan error), } + s.setup.Add(1) s.client = client s.ctx = client.GetLogger() @@ -55,19 +58,21 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu // Session client client, err := s.client.GatewayStatus() + s.setup.Done() if err != nil { if grpc.Code(err) == codes.Canceled { s.ctx.Debug("Stopped GatewayStatus stream") break } s.ctx.WithError(err).Warn("Could not start GatewayStatus stream, retrying...") + s.setup.Add(1) time.Sleep(backoff.Backoff(retries)) retries++ continue } retries = 0 - s.ctx.Debug("Started GatewayStatus stream") + s.ctx.Info("Started GatewayStatus stream") // Receive errors go func() { @@ -118,6 +123,7 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu break } + s.setup.Add(1) time.Sleep(backoff.Backoff(retries)) retries++ } @@ -142,6 +148,8 @@ func (s *gatewayStatusStream) Send(status *gateway.Status) error { } func (s *gatewayStatusStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing GatewayStatus stream") s.closing = true close(s.ch) } @@ -158,6 +166,7 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { ch: make(chan *UplinkMessage, DefaultBufferSize), err: make(chan error), } + s.setup.Add(1) s.client = client s.ctx = client.GetLogger() @@ -171,19 +180,21 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { // Session client client, err := s.client.Uplink() + s.setup.Done() if err != nil { if grpc.Code(err) == codes.Canceled { s.ctx.Debug("Stopped Uplink stream") break } s.ctx.WithError(err).Warn("Could not start Uplink stream, retrying...") + s.setup.Add(1) time.Sleep(backoff.Backoff(retries)) retries++ continue } retries = 0 - s.ctx.Debug("Started Uplink stream") + s.ctx.Info("Started Uplink stream") // Receive errors go func() { @@ -234,6 +245,7 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { break } + s.setup.Add(1) time.Sleep(backoff.Backoff(retries)) retries++ } @@ -258,6 +270,8 @@ func (s *uplinkStream) Send(message *UplinkMessage) error { } func (s *uplinkStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Uplink stream") s.closing = true close(s.ch) } @@ -274,6 +288,7 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { ch: make(chan *DownlinkMessage, DefaultBufferSize), err: make(chan error), } + s.setup.Add(1) s.client = client s.ctx = client.GetLogger() @@ -285,19 +300,21 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { for { client, s.cancel, err = s.client.Subscribe() + s.setup.Done() if err != nil { if grpc.Code(err) == codes.Canceled { s.ctx.Debug("Stopped Downlink stream") break } s.ctx.WithError(err).Warn("Could not start Downlink stream, retrying...") + s.setup.Add(1) time.Sleep(backoff.Backoff(retries)) retries++ continue } retries = 0 - s.ctx.Debug("Started Downlink stream") + s.ctx.Info("Started Downlink stream") for { message, err = client.Recv() @@ -324,6 +341,7 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { break } + s.setup.Add(1) time.Sleep(backoff.Backoff(retries)) retries++ } @@ -341,6 +359,8 @@ type downlinkStream struct { } func (s *downlinkStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Downlink stream") s.closing = true if s.cancel != nil { s.cancel() From b3b889d008e190146ee795f1a7a35201ed0a0bbd Mon Sep 17 00:00:00 2001 From: Roman Volosatovs Date: Tue, 22 Nov 2016 15:13:00 +0100 Subject: [PATCH 2200/2266] utils/errors.FromGRPCError: io.EOF, fix unknown --- utils/errors/errors.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/utils/errors/errors.go b/utils/errors/errors.go index e75087b74..82265bfee 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -2,6 +2,7 @@ package errors import ( "fmt" + "io" "strings" "google.golang.org/grpc" @@ -83,7 +84,10 @@ func FromGRPCError(err error) error { case codes.PermissionDenied: return NewErrPermissionDenied(strings.TrimPrefix(desc, "permission denied: ")) case codes.Unknown: // This also includes all non-gRPC errors - return errs.New(err.Error()) + if desc == "EOF" { + return io.EOF + } + return errs.New(desc) } return NewErrInternal(fmt.Sprintf("[%s] %s", code, desc)) } From 20c251761395c4169cf9672f826ea00fdb475f3e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 14:23:53 +0100 Subject: [PATCH 2201/2266] Remove Println from broker message marshaling --- api/broker/message_marshaling.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/api/broker/message_marshaling.go b/api/broker/message_marshaling.go index ef55c209e..1b52b86c9 100644 --- a/api/broker/message_marshaling.go +++ b/api/broker/message_marshaling.go @@ -5,7 +5,6 @@ package broker import ( "errors" - "fmt" pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" @@ -126,7 +125,6 @@ func payloadFromMsg(msg *pb_protocol.Message) ([]byte, error) { if err != nil { return nil, err } - fmt.Println(bin) return bin, nil } From 55877d5ab7f4b9e5a1728a5338394b46b66124dc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 14:25:21 +0100 Subject: [PATCH 2202/2266] Correct comment for rtr downlink stream --- api/router/client_streams.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/router/client_streams.go b/api/router/client_streams.go index 5b53b34c8..4553ced28 100644 --- a/api/router/client_streams.go +++ b/api/router/client_streams.go @@ -276,7 +276,7 @@ func (s *uplinkStream) Close() { close(s.ch) } -// DownlinkStream for sending downlink messages +// DownlinkStream for receiving downlink messages type DownlinkStream interface { GatewayStream Channel() <-chan *DownlinkMessage From a9cd8fc6d474cf6069d162f00a54669da40aa1aa Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 14:58:18 +0100 Subject: [PATCH 2203/2266] Clarify that RegisterApplicationHandler is temporary --- api/broker/broker.proto | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/api/broker/broker.proto b/api/broker/broker.proto index 5b9cf2e09..5916a6dcc 100644 --- a/api/broker/broker.proto +++ b/api/broker/broker.proto @@ -153,7 +153,8 @@ message ApplicationHandlerRegistration { // The BrokerManager service provides configuration and monitoring functionality service BrokerManager { - // Handler announces new application to Broker + // Handler announces a new application to Broker. This is a temporary method that will be removed + // when we can push updates from the Discovery service to the routing services. rpc RegisterApplicationHandler(ApplicationHandlerRegistration) returns (google.protobuf.Empty); // Network operator requests Broker status rpc GetStatus(StatusRequest) returns (Status); From d75183b1154f9d1001ec382a5993082c24463955 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 16:23:02 +0100 Subject: [PATCH 2204/2266] Fix closing of Router Subscribe stream --- api/router/server_streams.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/router/server_streams.go b/api/router/server_streams.go index e6c12a44a..4bdc8e541 100644 --- a/api/router/server_streams.go +++ b/api/router/server_streams.go @@ -76,13 +76,17 @@ func (s *RouterStreamServer) Subscribe(req *SubscribeRequest, stream Router_Subs if err != nil { return err } - defer cancel() + go func() { + <-stream.Context().Done() + err = stream.Context().Err() + cancel() + }() for downlink := range ch { if err := stream.Send(downlink); err != nil { return err } } - return nil + return } // GatewayStatus handles gateway status streams From 3e232bc6282a31eca58c8cf2e3fbe48a3eb5ad92 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 17:49:19 +0100 Subject: [PATCH 2205/2266] Wrap context.Canceled error --- api/router/server_streams.go | 2 +- core/component/grpc.go | 6 ++++-- utils/errors/errors.go | 4 ++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/api/router/server_streams.go b/api/router/server_streams.go index 4bdc8e541..259151856 100644 --- a/api/router/server_streams.go +++ b/api/router/server_streams.go @@ -78,7 +78,7 @@ func (s *RouterStreamServer) Subscribe(req *SubscribeRequest, stream Router_Subs } go func() { <-stream.Context().Done() - err = stream.Context().Err() + err = errors.BuildGRPCError(stream.Context().Err()) cancel() }() for downlink := range ch { diff --git a/core/component/grpc.go b/core/component/grpc.go index 0cfeb57e7..7cc3a444a 100644 --- a/core/component/grpc.go +++ b/core/component/grpc.go @@ -1,12 +1,14 @@ package component import ( + "io" "time" "github.com/apex/log" "github.com/mwitkow/go-grpc-middleware" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" @@ -66,8 +68,8 @@ func (c *Component) ServerOptions() []grpc.ServerOption { logCtx.Debug("Start stream") err := handler(srv, stream) logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) - switch err { - case nil, context.Canceled: + switch { + case err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled: logCtx.Debug("End stream") default: logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Warn("End stream") diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 82265bfee..13b9c974d 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -9,6 +9,7 @@ import ( "google.golang.org/grpc/codes" errs "github.com/pkg/errors" + "golang.org/x/net/context" ) type ErrType string @@ -59,6 +60,9 @@ func BuildGRPCError(err error) error { case *ErrPermissionDenied: code = codes.PermissionDenied } + if err == context.Canceled { + code = codes.Canceled + } return grpc.Errorf(code, err.Error()) } From 81f795edd1419f11823c1037109a58d0aeb08b4c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 17:51:53 +0100 Subject: [PATCH 2206/2266] Implement gRPC stream wrappers for Broker --- api/broker/client_streams.go | 397 +++++++++++++++++++++++++++++++ api/broker/communication_test.go | 221 +++++++++++++++++ api/broker/server_streams.go | 145 +++++++++++ core/broker/server.go | 153 +++++------- core/handler/handler.go | 42 +--- core/router/router.go | 82 +++---- 6 files changed, 868 insertions(+), 172 deletions(-) create mode 100644 api/broker/client_streams.go create mode 100644 api/broker/communication_test.go create mode 100644 api/broker/server_streams.go diff --git a/api/broker/client_streams.go b/api/broker/client_streams.go new file mode 100644 index 000000000..82e70b441 --- /dev/null +++ b/api/broker/client_streams.go @@ -0,0 +1,397 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "io" + "sync" + "time" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/backoff" + "github.com/golang/protobuf/ptypes/empty" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" +) + +// Stream interface +type Stream interface { + SetLogger(api.Logger) + Close() +} + +type stream struct { + closing bool + setup sync.WaitGroup + ctx api.Logger + client BrokerClient +} + +func (s *stream) SetLogger(logger api.Logger) { + s.ctx = logger +} + +// DefaultBufferSize indicates the default send and receive buffer sizes +var DefaultBufferSize = 10 + +// RouterStream for sending gateway statuses +type RouterStream interface { + Stream + Send(*UplinkMessage) error + Channel() <-chan *DownlinkMessage +} + +// NewMonitoredRouterStream starts and monitors a RouterStream +func NewMonitoredRouterStream(client BrokerClient, getContextFunc func() context.Context) RouterStream { + s := &routerStream{ + up: make(chan *UplinkMessage, DefaultBufferSize), + down: make(chan *DownlinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = api.GetLogger() + + go func() { + var retries int + + for { + // Session channels + up := make(chan *UplinkMessage) + errCh := make(chan error) + + // Session client + var ctx context.Context + ctx, s.cancel = context.WithCancel(getContextFunc()) + client, err := s.client.Associate(ctx) + s.setup.Done() + if err != nil { + s.ctx.WithError(err).Warn("Could not start Associate stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Debug("Started Associate stream") + + // Receive downlink errors + go func() { + for { + message, err := client.Recv() + if message != nil { + s.ctx.Debug("Receiving Downlink message") + select { + case s.down <- message: + default: + s.ctx.Warn("Dropping Downlink message, buffer full") + } + } + if err != nil { + errCh <- err + break + } + } + close(errCh) + }() + + // Send uplink + go func() { + for message := range up { + s.ctx.Debug("Sending Uplink message") + if err := client.Send(message); err != nil { + s.ctx.WithError(err).Warn("Error sending Uplink message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case mErr = <-errCh: + break monitor + case msg, ok := <-s.up: + if !ok { + break monitor // channel closed + } + up <- msg + } + } + + close(up) + client.CloseSend() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped Associate stream") + } else { + s.ctx.WithError(mErr).Warn("Error in Associate stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type routerStream struct { + stream + cancel context.CancelFunc + up chan *UplinkMessage + down chan *DownlinkMessage + err chan error +} + +func (s *routerStream) Send(uplink *UplinkMessage) error { + select { + case s.up <- uplink: + default: + s.ctx.Warn("Dropping Uplink message, buffer full") + } + return nil +} + +func (s *routerStream) Channel() <-chan *DownlinkMessage { + return s.down +} + +func (s *routerStream) Close() { + s.closing = true + close(s.up) + if s.cancel != nil { + s.cancel() + } +} + +// HandlerPublishStream for sending downlink messages to the broker +type HandlerPublishStream interface { + Stream + Send(*DownlinkMessage) error +} + +// NewMonitoredHandlerPublishStream starts and monitors a HandlerPublishStream +func NewMonitoredHandlerPublishStream(client BrokerClient, getContextFunc func() context.Context) HandlerPublishStream { + s := &handlerPublishStream{ + ch: make(chan *DownlinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = api.GetLogger() + + go func() { + var retries int + + for { + // Session channels + ch := make(chan *DownlinkMessage) + errCh := make(chan error) + + // Session client + client, err := s.client.Publish(getContextFunc()) + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + break + } + s.ctx.WithError(err).Warn("Could not start Downlink stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Info("Started Downlink stream") + + // Receive errors + go func() { + empty := new(empty.Empty) + if err := client.RecvMsg(empty); err != nil { + errCh <- err + } + close(errCh) + }() + + // Send + go func() { + for message := range ch { + s.ctx.Debug("Sending Downlink message") + if err := client.Send(message); err != nil { + s.ctx.WithError(err).Warn("Error sending Downlink message") + break + } + } + }() + + // Monitoring + var mErr error + + monitor: + for { + select { + case mErr = <-errCh: + break monitor + case msg, ok := <-s.ch: + if !ok { + break monitor // channel closed + } + ch <- msg + } + } + + close(ch) + client.CloseAndRecv() + + if mErr == nil || mErr == io.EOF || grpc.Code(mErr) == codes.Canceled { + s.ctx.Debug("Stopped Downlink stream") + } else { + s.ctx.WithError(mErr).Warn("Error in Downlink stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + }() + + return s +} + +type handlerPublishStream struct { + stream + ch chan *DownlinkMessage + err chan error +} + +func (s *handlerPublishStream) Send(message *DownlinkMessage) error { + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Downlink message, buffer full") + } + return nil +} + +func (s *handlerPublishStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Downlink stream") + s.closing = true + close(s.ch) +} + +// HandlerSubscribeStream for receiving uplink messages +type HandlerSubscribeStream interface { + Stream + Channel() <-chan *DeduplicatedUplinkMessage +} + +// NewMonitoredHandlerSubscribeStream starts and monitors a HandlerSubscribeStream +func NewMonitoredHandlerSubscribeStream(client BrokerClient, getContextFunc func() context.Context) HandlerSubscribeStream { + s := &handlerSubscribeStream{ + ch: make(chan *DeduplicatedUplinkMessage, DefaultBufferSize), + err: make(chan error), + } + s.setup.Add(1) + s.client = client + s.ctx = api.GetLogger() + + go func() { + var client Broker_SubscribeClient + var err error + var retries int + var message *DeduplicatedUplinkMessage + + for { + // Session client + var ctx context.Context + ctx, s.cancel = context.WithCancel(getContextFunc()) + client, err = s.client.Subscribe(ctx, &SubscribeRequest{}) + s.setup.Done() + if err != nil { + if grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + break + } + s.ctx.WithError(err).Warn("Could not start Uplink stream, retrying...") + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + retries = 0 + + s.ctx.Info("Started Uplink stream") + + for { + message, err = client.Recv() + if message != nil { + s.ctx.Debug("Receiving Uplink message") + select { + case s.ch <- message: + default: + s.ctx.Warn("Dropping Uplink message, buffer full") + } + } + if err != nil { + break + } + } + + if err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled { + s.ctx.Debug("Stopped Uplink stream") + } else { + s.ctx.WithError(err).Warn("Error in Uplink stream") + } + + if s.closing { + break + } + + s.setup.Add(1) + time.Sleep(backoff.Backoff(retries)) + retries++ + } + + close(s.ch) + }() + return s +} + +type handlerSubscribeStream struct { + stream + cancel context.CancelFunc + ch chan *DeduplicatedUplinkMessage + err chan error +} + +func (s *handlerSubscribeStream) Close() { + s.setup.Wait() + s.ctx.Debug("Closing Uplink stream") + s.closing = true + if s.cancel != nil { + s.cancel() + } +} + +func (s *handlerSubscribeStream) Channel() <-chan *DeduplicatedUplinkMessage { + return s.ch +} diff --git a/api/broker/communication_test.go b/api/broker/communication_test.go new file mode 100644 index 000000000..a73f37937 --- /dev/null +++ b/api/broker/communication_test.go @@ -0,0 +1,221 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "fmt" + "math/rand" + "net" + "testing" + "time" + + "github.com/TheThingsNetwork/ttn/api" + . "github.com/TheThingsNetwork/ttn/utils/testing" + . "github.com/smartystreets/assertions" + "golang.org/x/net/context" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/metadata" +) + +func newTestBroker() *testBroker { + return &testBroker{ + BrokerStreamServer: NewBrokerStreamServer(), + } +} + +type testBroker struct { + *BrokerStreamServer +} + +var _ BrokerServer = &testBroker{} + +func (s *testBroker) Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) { + return nil, grpc.Errorf(codes.Unimplemented, "Not implemented") +} + +func (s *testBroker) Serve(port int) { + lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port)) + if err != nil { + panic(err) + } + srv := grpc.NewServer() + RegisterBrokerServer(srv, s) + srv.Serve(lis) +} + +func TestHandlerBrokerCommunication(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestHandlerBrokerCommunication") + api.SetLogger(api.Apex(ctx)) + + brk := newTestBroker() + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go brk.Serve(port) + + conn, _ := grpc.Dial(fmt.Sprintf("localhost:%d", port), grpc.WithBlock(), grpc.WithInsecure()) + + { + brk.HandlerPublishChanFunc = func(md metadata.MD) (chan *DownlinkMessage, error) { + ch := make(chan *DownlinkMessage, 1) + go func() { + ctx.Info("[SERVER] Channel opened") + for message := range ch { + ctx.WithField("Message", message).Info("[SERVER] Received Downlink") + } + ctx.Info("[SERVER] Channel closed") + }() + return ch, nil + } + + brkClient := NewBrokerClient(conn) + downlink := NewMonitoredHandlerPublishStream(brkClient, func() context.Context { + return context.Background() + }) + + err := downlink.Send(&DownlinkMessage{ + Payload: []byte{1, 2, 3, 4}, + }) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + downlink.Close() + + time.Sleep(10 * time.Millisecond) + } + + { + brk.HandlerSubscribeChanFunc = func(md metadata.MD) (<-chan *DeduplicatedUplinkMessage, func(), error) { + ch := make(chan *DeduplicatedUplinkMessage, 1) + stop := make(chan struct{}) + cancel := func() { + ctx.Info("[SERVER] Canceling uplink") + close(stop) + } + go func() { + loop: + for { + select { + case <-stop: + break loop + case <-time.After(5 * time.Millisecond): + ctx.Info("[SERVER] Sending Uplink") + ch <- &DeduplicatedUplinkMessage{ + Payload: []byte{1, 2, 3, 4}, + } + } + } + close(ch) + ctx.Info("[SERVER] Closed Uplink") + }() + return ch, cancel, nil + } + + brkClient := NewBrokerClient(conn) + uplink := NewMonitoredHandlerSubscribeStream(brkClient, func() context.Context { + return context.Background() + }) + + ch := uplink.Channel() + + go func() { + for uplink := range ch { + ctx.WithField("Uplink", uplink).Info("[CLIENT] Received Uplink") + } + ctx.Info("[CLIENT] Closed Uplink") + }() + + time.Sleep(10 * time.Millisecond) + + uplink.Close() + + time.Sleep(10 * time.Millisecond) + } + +} + +func TestRouterBrokerCommunication(t *testing.T) { + a := New(t) + + ctx := GetLogger(t, "TestRouterBrokerCommunication") + api.SetLogger(api.Apex(ctx)) + + brk := newTestBroker() + rand.Seed(time.Now().UnixNano()) + port := rand.Intn(1000) + 10000 + go brk.Serve(port) + + conn, _ := grpc.Dial(fmt.Sprintf("localhost:%d", port), grpc.WithBlock(), grpc.WithInsecure()) + + { + brk.RouterAssociateChanFunc = func(md metadata.MD) (chan *UplinkMessage, <-chan *DownlinkMessage, func(), error) { + up := make(chan *UplinkMessage, 1) + down := make(chan *DownlinkMessage, 1) + + stop := make(chan struct{}) + cancel := func() { + ctx.Info("[SERVER] Canceling downlink") + close(stop) + } + + go func() { + ctx.Info("[SERVER] Uplink channel opened") + for message := range up { + ctx.WithField("Message", message).Info("[SERVER] Received Uplink") + } + ctx.Info("[SERVER] Uplink channel closed") + }() + + go func() { + loop: + for { + select { + case <-stop: + break loop + case <-time.After(5 * time.Millisecond): + ctx.Info("[SERVER] Sending Downlink") + down <- &DownlinkMessage{ + Payload: []byte{1, 2, 3, 4}, + } + } + } + close(down) + ctx.Info("[SERVER] Closed Downlink") + }() + + return up, down, cancel, nil + } + + brkClient := NewBrokerClient(conn) + stream := NewMonitoredRouterStream(brkClient, func() context.Context { + return context.Background() + }) + + ch := stream.Channel() + + go func() { + for downlink := range ch { + ctx.WithField("Downlink", downlink).Info("[CLIENT] Received Downlink") + } + ctx.Info("[CLIENT] Closed Downlink") + }() + + err := stream.Send(&UplinkMessage{ + Payload: []byte{1, 2, 3, 4}, + }) + + a.So(err, ShouldBeNil) + + time.Sleep(10 * time.Millisecond) + + stream.Close() + + time.Sleep(10 * time.Millisecond) + } + +} diff --git a/api/broker/server_streams.go b/api/broker/server_streams.go new file mode 100644 index 000000000..ed30b9861 --- /dev/null +++ b/api/broker/server_streams.go @@ -0,0 +1,145 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "io" + + "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/golang/protobuf/ptypes/empty" + "google.golang.org/grpc/metadata" +) + +// BrokerStreamServer handles gRPC streams as channels +type BrokerStreamServer struct { + ctx api.Logger + RouterAssociateChanFunc func(md metadata.MD) (up chan *UplinkMessage, down <-chan *DownlinkMessage, cancel func(), err error) + HandlerSubscribeChanFunc func(md metadata.MD) (ch <-chan *DeduplicatedUplinkMessage, cancel func(), err error) + HandlerPublishChanFunc func(md metadata.MD) (ch chan *DownlinkMessage, err error) +} + +// NewBrokerStreamServer returns a new BrokerStreamServer +func NewBrokerStreamServer() *BrokerStreamServer { + return &BrokerStreamServer{ + ctx: api.GetLogger(), + } +} + +// SetLogger sets the logger +func (s *BrokerStreamServer) SetLogger(logger api.Logger) { + s.ctx = logger +} + +// Associate handles uplink streams from and downlink streams to the router +func (s *BrokerStreamServer) Associate(stream Broker_AssociateServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + upChan, downChan, downCancel, err := s.RouterAssociateChanFunc(md) + if err != nil { + return err + } + defer func() { + ctx := s.ctx + if err != nil { + ctx = ctx.WithError(err) + } + downCancel() + close(upChan) + ctx.Debug("Closed Associate stream") + }() + + upErr := make(chan error) + go func() (err error) { + defer func() { + if err != nil { + upErr <- err + } + close(upErr) + }() + for { + uplink, err := stream.Recv() + if err == io.EOF { + return nil + } + if err != nil { + return err + } + if err := uplink.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + } + upChan <- uplink + } + }() + + for { + select { + case err, errPresent := <-upErr: + if !errPresent { + return nil // stream closed + } + return err + case downlink, downlinkPresent := <-downChan: + if !downlinkPresent { + return nil // stream closed + } + if err := stream.Send(downlink); err != nil { + return err + } + } + } +} + +// Subscribe handles uplink streams towards the handler +func (s *BrokerStreamServer) Subscribe(req *SubscribeRequest, stream Broker_SubscribeServer) (err error) { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, cancel, err := s.HandlerSubscribeChanFunc(md) + if err != nil { + return err + } + go func() { + <-stream.Context().Done() + err = errors.BuildGRPCError(stream.Context().Err()) + cancel() + }() + for uplink := range ch { + if err := stream.Send(uplink); err != nil { + return err + } + } + return +} + +// Publish handles downlink streams from the handler +func (s *BrokerStreamServer) Publish(stream Broker_PublishServer) error { + md, err := api.MetadataFromContext(stream.Context()) + if err != nil { + return err + } + ch, err := s.HandlerPublishChanFunc(md) + if err != nil { + return err + } + defer func() { + close(ch) + }() + for { + downlink, err := stream.Recv() + if err == io.EOF { + return stream.SendAndClose(&empty.Empty{}) + } + if err != nil { + return err + } + if err := downlink.Validate(); err != nil { + return errors.BuildGRPCError(errors.Wrap(err, "Invalid Downlink")) + } + ch <- downlink + } +} diff --git a/core/broker/server.go b/core/broker/server.go index 0942b4269..b702fb03f 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -4,125 +4,96 @@ package broker import ( - "io" - + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/utils/errors" - "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" + "google.golang.org/grpc/metadata" ) type brokerRPC struct { broker *broker + pb.BrokerStreamServer } -func (b *brokerRPC) Associate(stream pb.Broker_AssociateServer) error { - router, err := b.broker.ValidateNetworkContext(stream.Context()) +func (b *brokerRPC) associateRouter(md metadata.MD) (chan *pb.UplinkMessage, <-chan *pb.DownlinkMessage, func(), error) { + ctx := metadata.NewContext(context.Background(), md) + router, err := b.broker.ValidateNetworkContext(ctx) if err != nil { - return errors.BuildGRPCError(err) + return nil, nil, nil, errors.BuildGRPCError(err) } - downlinkChannel, err := b.broker.ActivateRouter(router.Id) + down, err := b.broker.ActivateRouter(router.Id) if err != nil { - return errors.BuildGRPCError(err) + return nil, nil, nil, errors.BuildGRPCError(err) + } + + up := make(chan *pb.UplinkMessage, 1) + + cancel := func() { + b.broker.DeactivateRouter(router.Id) } - defer b.broker.DeactivateRouter(router.Id) + go func() { - for { - if downlinkChannel == nil { - return - } - select { - case <-stream.Context().Done(): - return - case downlink := <-downlinkChannel: - if downlink != nil { - if err := stream.Send(downlink); err != nil { - // TODO: Check if the stream should be closed here - return - } - } - } + for message := range up { + go b.broker.HandleUplink(message) } }() - for { - uplink, err := stream.Recv() - if err == io.EOF { - return nil - } - if err != nil { - return err - } - if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) - } - go b.broker.HandleUplink(uplink) - } + + return up, down, cancel, nil } -func (b *brokerRPC) Subscribe(req *pb.SubscribeRequest, stream pb.Broker_SubscribeServer) error { - handler, err := b.broker.ValidateNetworkContext(stream.Context()) +func (b *brokerRPC) getHandlerSubscribe(md metadata.MD) (<-chan *pb.DeduplicatedUplinkMessage, func(), error) { + ctx := metadata.NewContext(context.Background(), md) + handler, err := b.broker.ValidateNetworkContext(ctx) if err != nil { - return errors.BuildGRPCError(err) + return nil, nil, errors.BuildGRPCError(err) } - uplinkChannel, err := b.broker.ActivateHandler(handler.Id) + + ch, err := b.broker.ActivateHandler(handler.Id) if err != nil { - return errors.BuildGRPCError(err) + return nil, nil, errors.BuildGRPCError(err) } - defer b.broker.DeactivateHandler(handler.Id) - for { - if uplinkChannel == nil { - return nil - } - select { - case <-stream.Context().Done(): - return stream.Context().Err() - case uplink := <-uplinkChannel: - if uplink != nil { - if err := stream.Send(uplink); err != nil { - return err - } - } - } + + cancel := func() { + b.broker.DeactivateHandler(handler.Id) } + + return ch, cancel, nil } -func (b *brokerRPC) Publish(stream pb.Broker_PublishServer) error { - handler, err := b.broker.ValidateNetworkContext(stream.Context()) +func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, error) { + ctx := metadata.NewContext(context.Background(), md) + handler, err := b.broker.ValidateNetworkContext(ctx) if err != nil { - return errors.BuildGRPCError(err) + return nil, errors.BuildGRPCError(err) } - for { - downlink, err := stream.Recv() - if err == io.EOF { - return stream.SendAndClose(&empty.Empty{}) - } - if err != nil { - return err - } - if err := downlink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Downlink")) - } - go func(downlink *pb.DownlinkMessage) { - // Get latest Handler metadata - handler, err := b.broker.Component.Discover("handler", handler.Id) - if err != nil { - return - } - // Check if this Handler can publish for this AppId - for _, meta := range handler.Metadata { - switch meta.Key { - case pb_discovery.Metadata_APP_ID: - announcedID := string(meta.Value) - if announcedID == downlink.AppId { - b.broker.HandleDownlink(downlink) - return + + ch := make(chan *pb.DownlinkMessage, 1) + go func() { + for message := range ch { + go func(downlink *pb.DownlinkMessage) { + // Get latest Handler metadata + handler, err := b.broker.Component.Discover("handler", handler.Id) + if err != nil { + return + } + // Check if this Handler can publish for this AppId + for _, meta := range handler.Metadata { + switch meta.Key { + case pb_discovery.Metadata_APP_ID: + announcedID := string(meta.Value) + if announcedID == downlink.AppId { + b.broker.HandleDownlink(downlink) + return + } } } - } - }(downlink) - } + }(message) + } + }() + return ch, nil } func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { @@ -141,6 +112,10 @@ func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques } func (b *broker) RegisterRPC(s *grpc.Server) { - server := &brokerRPC{b} + server := &brokerRPC{broker: b} + server.SetLogger(api.Apex(b.Ctx)) + server.RouterAssociateChanFunc = server.associateRouter + server.HandlerPublishChanFunc = server.getHandlerPublish + server.HandlerSubscribeChanFunc = server.getHandlerSubscribe pb.RegisterBrokerServer(s, server) } diff --git a/core/handler/handler.go b/core/handler/handler.go index 25afc156f..d28df2656 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -5,10 +5,8 @@ package handler import ( "fmt" - "time" "github.com/TheThingsNetwork/ttn/amqp" - "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb "github.com/TheThingsNetwork/ttn/api/handler" "github.com/TheThingsNetwork/ttn/core/component" @@ -16,7 +14,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" - "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" "google.golang.org/grpc" "gopkg.in/redis.v5" ) @@ -160,39 +158,21 @@ func (h *handler) associateBroker() error { h.downlink = make(chan *pb_broker.DownlinkMessage) + contextFunc := func() context.Context { return h.GetContext("") } + + upStream := pb_broker.NewMonitoredHandlerSubscribeStream(h.ttnBroker, contextFunc) + downStream := pb_broker.NewMonitoredHandlerPublishStream(h.ttnBroker, contextFunc) + go func() { - for { - upStream, err := h.ttnBroker.Subscribe(h.GetContext(""), &pb_broker.SubscribeRequest{}) - if err != nil { - h.Ctx.WithError(errors.FromGRPCError(err)).Error("Could not start Broker subscribe stream") - <-time.After(api.Backoff) - continue - } - for { - in, err := upStream.Recv() - if err != nil { - h.Ctx.WithError(errors.FromGRPCError(err)).Error("Error in Broker subscribe stream") - break - } - go h.HandleUplink(in) - } + for message := range upStream.Channel() { + go h.HandleUplink(message) } }() go func() { - for { - downStream, err := h.ttnBroker.Publish(h.GetContext("")) - if err != nil { - h.Ctx.WithError(errors.FromGRPCError(err)).Error("Could not start Broker publish stream") - <-time.After(api.Backoff) - continue - } - for downlink := range h.downlink { - err := downStream.Send(downlink) - if err != nil { - h.Ctx.WithError(errors.FromGRPCError(err)).Error("Error in Broker publish stream") - break - } + for message := range h.downlink { + if err := downStream.Send(message); err != nil { + h.Ctx.WithError(err).Warn("Could not send downlink to Broker") } } }() diff --git a/core/router/router.go b/core/router/router.go index 4d2eb00dc..94d77d81b 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -7,7 +7,8 @@ import ( "sync" "time" - "github.com/TheThingsNetwork/ttn/api" + "google.golang.org/grpc" + pb_broker "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" @@ -15,7 +16,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" - "github.com/TheThingsNetwork/ttn/utils/errors" + "golang.org/x/net/context" ) // Router component @@ -40,9 +41,11 @@ type Router interface { } type broker struct { - client pb_broker.BrokerClient - uplink chan *pb_broker.UplinkMessage - downlink chan *pb_broker.DownlinkMessage + conn *grpc.ClientConn + association pb_broker.RouterStream + client pb_broker.BrokerClient + uplink chan *pb_broker.UplinkMessage + downlink chan *pb_broker.DownlinkMessage } // NewRouter creates a new Router @@ -90,7 +93,14 @@ func (r *router) Init(c *component.Component) error { return nil } -func (r *router) Shutdown() {} +func (r *router) Shutdown() { + r.brokersLock.Lock() + defer r.brokersLock.Unlock() + for _, broker := range r.brokers { + broker.association.Close() + broker.conn.Close() + } +} // getGateway gets or creates a Gateway func (r *router) getGateway(id string) *gateway.Gateway { @@ -132,6 +142,7 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok if ok { return brk, nil } + // If it doesn't we still have to lock r.brokersLock.Lock() defer r.brokersLock.Unlock() @@ -144,59 +155,26 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok } client := pb_broker.NewBrokerClient(conn) + association := pb_broker.NewMonitoredRouterStream(client, func() context.Context { + return r.GetContext("") + }) + downlink := association.Channel() + brk := &broker{ - client: client, - uplink: make(chan *pb_broker.UplinkMessage), - downlink: make(chan *pb_broker.DownlinkMessage), + conn: conn, + association: association, + uplink: make(chan *pb_broker.UplinkMessage), } go func() { - numErrs := 0 for { - association, err := client.Associate(r.Component.GetContext("")) - if err != nil { - numErrs++ - <-time.After(api.Backoff) - if numErrs > 10 { - break - } - continue + select { + case message := <-brk.uplink: + association.Send(message) + case message := <-downlink: + go r.HandleDownlink(message) } - - errChan := make(chan error) - - go func() { - for { - downlink, err := association.Recv() - if err != nil { - errChan <- err - return - } - brk.downlink <- downlink - } - }() - - associationLoop: - for { - select { - case err := <-errChan: - r.Ctx.WithError(errors.FromGRPCError(err)).Error("Error in Broker associate") - break associationLoop - case uplink := <-brk.uplink: - err := association.Send(uplink) - if err != nil { - errChan <- err - } - case downlink := <-brk.downlink: - go r.HandleDownlink(downlink) - } - } - } - conn.Close() - r.brokersLock.Lock() - defer r.brokersLock.Unlock() - delete(r.brokers, brokerAnnouncement.Id) }() r.brokers[brokerAnnouncement.Id] = brk From f0064fb3cc00b52883e45ba8050e3a9f22481e3a Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 18:03:50 +0100 Subject: [PATCH 2207/2266] Remove "deps" stage from gitlab-ci Every build step can now do this --- .gitlab-ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ab0291b40..11d818481 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,4 @@ stages: - - deps - test - build - sign @@ -19,14 +18,6 @@ before_script: - mkdir -p $GOPATH/.cache && ln -s $(pwd)/.govendor $GOPATH/.cache/govendor - mkdir -p $GOPATH/src/github.com/TheThingsNetwork && ln -s $(pwd) $GOPATH/src/github.com/TheThingsNetwork/ttn -deps: - stage: deps - image: golang:latest - script: - - pushd $GOPATH/src/github.com/TheThingsNetwork/ttn - - make deps - - popd - tests: stage: test image: golang:latest From c781ba573e85c10f099d20086e25024b939daac7 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 23 Nov 2016 18:56:53 +0100 Subject: [PATCH 2208/2266] Fix client not being set in getBroker Caused panics in activation --- core/router/router.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/router/router.go b/core/router/router.go index 94d77d81b..2a5de1cac 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -163,6 +163,7 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok brk := &broker{ conn: conn, association: association, + client: client, uplink: make(chan *pb_broker.UplinkMessage), } From 64d7efbf580461afa026aa650d9ecd6d13c6248c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Nov 2016 10:21:03 +0100 Subject: [PATCH 2209/2266] Add comments to Discovery proto Refs #370 --- api/broker/broker.pb.go | 6 ++++-- api/discovery/Discovery.md | 4 +++- api/discovery/discovery.pb.go | 8 ++++++-- api/discovery/discovery.proto | 21 ++++++++++++++++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/api/broker/broker.pb.go b/api/broker/broker.pb.go index 4bd92d42f..cfeec2c60 100644 --- a/api/broker/broker.pb.go +++ b/api/broker/broker.pb.go @@ -778,7 +778,8 @@ var _Broker_serviceDesc = grpc.ServiceDesc{ // Client API for BrokerManager service type BrokerManagerClient interface { - // Handler announces new application to Broker + // Handler announces a new application to Broker. This is a temporary method that will be removed + // when we can push updates from the Discovery service to the routing services. RegisterApplicationHandler(ctx context.Context, in *ApplicationHandlerRegistration, opts ...grpc.CallOption) (*google_protobuf.Empty, error) // Network operator requests Broker status GetStatus(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*Status, error) @@ -813,7 +814,8 @@ func (c *brokerManagerClient) GetStatus(ctx context.Context, in *StatusRequest, // Server API for BrokerManager service type BrokerManagerServer interface { - // Handler announces new application to Broker + // Handler announces a new application to Broker. This is a temporary method that will be removed + // when we can push updates from the Discovery service to the routing services. RegisterApplicationHandler(context.Context, *ApplicationHandlerRegistration) (*google_protobuf.Empty, error) // Network operator requests Broker status GetStatus(context.Context, *StatusRequest) (*Status, error) diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index 627190406..0396ad349 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -6,7 +6,9 @@ The Discovery service is used to discover services within The Things Network. ### `Announce` -Announce your component to the Discovery server +Announce a component to the Discovery server. +A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. +Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. - Request: [`Announcement`](#discoveryannouncement) - Response: [`Empty`](#discoveryannouncement) diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index 81bb90006..e7a439847 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -215,7 +215,9 @@ const _ = grpc.SupportPackageIsVersion4 // Client API for Discovery service type DiscoveryClient interface { - // Announce your component to the Discovery server + // Announce a component to the Discovery server. + // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. + // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) // Get all announcements for a specific service GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) @@ -283,7 +285,9 @@ func (c *discoveryClient) DeleteMetadata(ctx context.Context, in *MetadataReques // Server API for Discovery service type DiscoveryServer interface { - // Announce your component to the Discovery server + // Announce a component to the Discovery server. + // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. + // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. Announce(context.Context, *Announcement) (*google_protobuf.Empty, error) // Get all announcements for a specific service GetAll(context.Context, *GetAllRequest) (*AnnouncementsResponse, error) diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index ce057f40e..496c87360 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -49,24 +49,34 @@ message Metadata { message Announcement { // The ID of the component string id = 1; + // The name of the component (router/broker/handler) string service_name = 2; + // Service version in the form "[version]-[commit] ([build date])" string service_version = 3; + // Description of the component string description = 4; + // URL with documentation or more information about this component string url = 5; + // Indicates whether this service is part of The Things Network (the public community network) bool public = 6; + // Comma-separated network addresses in the form "[hostname]:[port]" (currently we only use the first) string net_address = 11; + // ECDSA public key of this component string public_key = 12; + // TLS Certificate (if TLS is enabled) string certificate = 13; + // Contains the address where the HTTP API is exposed (if there is one) string api_address = 14; + // Metadata for this component repeated Metadata metadata = 21; } @@ -80,6 +90,7 @@ message GetAllRequest { message GetRequest { // The ID of the service string id = 1; + // The name of the service (router/broker/handler) string service_name = 2; } @@ -88,8 +99,10 @@ message GetRequest { message MetadataRequest { // The ID of the service that should be modified string id = 1; + // The name of the service (router/broker/handler) that should be modified string service_name = 2; + Metadata metadata = 11; } @@ -100,22 +113,28 @@ message AnnouncementsResponse { // The Discovery service is used to discover services within The Things Network. service Discovery { - // Announce your component to the Discovery server + // Announce a component to the Discovery server. + // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. + // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. rpc Announce(Announcement) returns (google.protobuf.Empty); + // Get all announcements for a specific service rpc GetAll(GetAllRequest) returns (AnnouncementsResponse) { option (google.api.http) = { get: "/announcements/{service_name}" }; } + // Get a specific announcement rpc Get(GetRequest) returns (Announcement) { option (google.api.http) = { get: "/announcements/{service_name}/{id}" }; } + // Add metadata to an announement rpc AddMetadata(MetadataRequest) returns (google.protobuf.Empty); + // Delete metadata from an announcement rpc DeleteMetadata(MetadataRequest) returns (google.protobuf.Empty); } From 1dd4d66da7f57c3b119f11cc5768952b14f8d036 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Nov 2016 10:30:27 +0100 Subject: [PATCH 2210/2266] Inject Bridge and Router into gateway status --- api/gateway/gateway.pb.go | 162 ++++++++++++++++++++++++++-------- api/gateway/gateway.proto | 2 + core/router/gateway_status.go | 2 + 3 files changed, 128 insertions(+), 38 deletions(-) diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index e49b380fa..dd35d6112 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -94,6 +94,8 @@ type Status struct { ContactEmail string `protobuf:"bytes,13,opt,name=contact_email,json=contactEmail,proto3" json:"contact_email,omitempty"` Description string `protobuf:"bytes,14,opt,name=description,proto3" json:"description,omitempty"` Region string `protobuf:"bytes,15,opt,name=region,proto3" json:"region,omitempty"` + Bridge string `protobuf:"bytes,16,opt,name=bridge,proto3" json:"bridge,omitempty"` + Router string `protobuf:"bytes,17,opt,name=router,proto3" json:"router,omitempty"` Gps *GPSMetadata `protobuf:"bytes,21,opt,name=gps" json:"gps,omitempty"` Rtt uint32 `protobuf:"varint,31,opt,name=rtt,proto3" json:"rtt,omitempty"` RxIn uint32 `protobuf:"varint,41,opt,name=rx_in,json=rxIn,proto3" json:"rx_in,omitempty"` @@ -366,6 +368,22 @@ func (m *Status) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintGateway(dAtA, i, uint64(len(m.Region))) i += copy(dAtA[i:], m.Region) } + if len(m.Bridge) > 0 { + dAtA[i] = 0x82 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Bridge))) + i += copy(dAtA[i:], m.Bridge) + } + if len(m.Router) > 0 { + dAtA[i] = 0x8a + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintGateway(dAtA, i, uint64(len(m.Router))) + i += copy(dAtA[i:], m.Router) + } if m.Gps != nil { dAtA[i] = 0xaa i++ @@ -551,6 +569,14 @@ func (m *Status) Size() (n int) { if l > 0 { n += 1 + l + sovGateway(uint64(l)) } + l = len(m.Bridge) + if l > 0 { + n += 2 + l + sovGateway(uint64(l)) + } + l = len(m.Router) + if l > 0 { + n += 2 + l + sovGateway(uint64(l)) + } if m.Gps != nil { l = m.Gps.Size() n += 2 + l + sovGateway(uint64(l)) @@ -1314,6 +1340,64 @@ func (m *Status) Unmarshal(dAtA []byte) error { } m.Region = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Bridge", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Bridge = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Router", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Router = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex case 21: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Gps", wireType) @@ -1573,42 +1657,44 @@ func init() { } var fileDescriptorGateway = []byte{ - // 590 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0x4d, 0x6f, 0xd3, 0x30, - 0x18, 0xc6, 0x6d, 0xf7, 0x51, 0x77, 0xdd, 0x90, 0xb7, 0x15, 0x33, 0x41, 0x09, 0x45, 0x42, 0x81, - 0xc1, 0x2a, 0x81, 0x38, 0x70, 0xe0, 0xc2, 0x40, 0x68, 0x07, 0x18, 0xf2, 0x76, 0xe2, 0x52, 0x79, - 0x89, 0x93, 0x5a, 0x4d, 0xed, 0xe0, 0x38, 0x6b, 0xc7, 0x2f, 0xe1, 0x27, 0x71, 0x44, 0xe2, 0x0f, - 0xa0, 0x21, 0xf1, 0x33, 0x10, 0xf2, 0xdb, 0x24, 0xcb, 0x90, 0x60, 0xe2, 0xd4, 0xf7, 0x79, 0x9e, - 0xd7, 0x7e, 0x3f, 0x1e, 0x37, 0xf8, 0x79, 0x2c, 0xed, 0x38, 0x3f, 0xd9, 0x0b, 0xf4, 0x74, 0x78, - 0x3c, 0x16, 0xc7, 0x63, 0xa9, 0xe2, 0xec, 0x9d, 0xb0, 0x33, 0x6d, 0x26, 0x43, 0x6b, 0xd5, 0x90, - 0xa7, 0x72, 0x18, 0x73, 0x2b, 0x66, 0xfc, 0xac, 0xfc, 0xdd, 0x4b, 0x8d, 0xb6, 0x9a, 0xac, 0x14, - 0x70, 0xe7, 0x71, 0xed, 0x8e, 0x58, 0xc7, 0x7a, 0x08, 0xfa, 0x49, 0x1e, 0x01, 0x02, 0x00, 0xd1, - 0xe2, 0xdc, 0x60, 0x86, 0x3b, 0x6f, 0xde, 0x1f, 0xbd, 0x15, 0x96, 0x87, 0xdc, 0x72, 0x42, 0x70, - 0xcb, 0xca, 0xa9, 0xa0, 0xc8, 0x43, 0x7e, 0x93, 0x41, 0x4c, 0x76, 0xf0, 0x6a, 0xc2, 0xad, 0xb4, - 0x79, 0x28, 0x68, 0xc3, 0x43, 0x7e, 0x83, 0x55, 0x98, 0xdc, 0xc2, 0xed, 0x44, 0xab, 0x78, 0x21, - 0x36, 0x41, 0xbc, 0x20, 0xdc, 0x49, 0x9e, 0x14, 0x27, 0x5b, 0x1e, 0xf2, 0x97, 0x58, 0x85, 0x07, - 0xbf, 0x10, 0xc6, 0x6c, 0x5e, 0x15, 0xbe, 0x8d, 0x71, 0x31, 0xc1, 0x48, 0x86, 0x50, 0xbe, 0xcd, - 0xda, 0x05, 0x73, 0x10, 0xba, 0x3a, 0xae, 0x97, 0xcc, 0xf2, 0x69, 0x4a, 0x3b, 0x1e, 0xf2, 0xbb, - 0xec, 0x82, 0xa8, 0xba, 0x5e, 0xab, 0x75, 0x7d, 0x13, 0xaf, 0x9a, 0x68, 0x14, 0x8c, 0xb9, 0x54, - 0x74, 0x1b, 0x0e, 0xac, 0x98, 0x68, 0xdf, 0x41, 0x42, 0xf1, 0x4a, 0x30, 0xe6, 0x4a, 0x89, 0x84, - 0xf6, 0x16, 0x4a, 0x01, 0x5d, 0x99, 0xc8, 0x88, 0x8f, 0xb9, 0x50, 0xc1, 0x19, 0xbd, 0xe3, 0x21, - 0xbf, 0xc5, 0x2e, 0x08, 0x57, 0xc6, 0x64, 0x99, 0xa4, 0x1e, 0xcc, 0x09, 0x31, 0xb9, 0x8e, 0x9b, - 0x99, 0x32, 0xf4, 0x2e, 0x50, 0x2e, 0x24, 0xf7, 0x71, 0x33, 0x4e, 0x33, 0xfa, 0xc0, 0x43, 0x7e, - 0xe7, 0xc9, 0xd6, 0x5e, 0x69, 0x53, 0x6d, 0xcb, 0xcc, 0x25, 0x0c, 0x7e, 0x22, 0xbc, 0x71, 0x3c, - 0xdf, 0xd7, 0x2a, 0x92, 0x71, 0x6e, 0xb8, 0x95, 0x5a, 0x5d, 0x31, 0xe6, 0x3f, 0x46, 0xba, 0xd4, - 0x78, 0xef, 0xcf, 0xc6, 0xb7, 0xf0, 0x52, 0xaa, 0x67, 0xc2, 0xd0, 0x1b, 0x60, 0xc2, 0x02, 0x90, - 0x67, 0xb8, 0x97, 0xea, 0x84, 0x1b, 0xf9, 0x09, 0x8a, 0x8f, 0xa4, 0x3a, 0x15, 0x26, 0x93, 0x5a, - 0xc1, 0xe4, 0xab, 0x6c, 0xbb, 0xae, 0x1e, 0x94, 0x22, 0x19, 0xe2, 0xcd, 0xea, 0xe6, 0x51, 0x28, - 0x4e, 0x25, 0xe8, 0xb0, 0x94, 0x2e, 0x23, 0x95, 0xf4, 0xaa, 0x54, 0x06, 0xdf, 0x1a, 0x78, 0xf9, - 0xc8, 0x72, 0x9b, 0x67, 0x97, 0xe7, 0x43, 0x7f, 0xb3, 0xb1, 0x51, 0xb3, 0x71, 0x1d, 0x37, 0xa4, - 0x5b, 0x45, 0xd3, 0x6f, 0xb3, 0x86, 0x4c, 0xdd, 0x93, 0x4a, 0x13, 0x6e, 0x23, 0x6d, 0xa6, 0x60, - 0x77, 0x9b, 0x55, 0x98, 0xdc, 0xc3, 0xdd, 0x40, 0x2b, 0xcb, 0x03, 0x3b, 0x12, 0x53, 0x2e, 0x13, - 0xda, 0x85, 0x84, 0xb5, 0x82, 0x7c, 0xed, 0x38, 0xe2, 0xe1, 0x4e, 0x28, 0xb2, 0xc0, 0xc8, 0x14, - 0xda, 0x5e, 0x87, 0x94, 0x3a, 0x45, 0x7a, 0x78, 0xd9, 0x88, 0xd8, 0x89, 0x1b, 0x20, 0x16, 0xa8, - 0x34, 0x76, 0xfb, 0x0a, 0x63, 0xdd, 0x93, 0x30, 0xd6, 0xc2, 0x12, 0xbb, 0xcc, 0x85, 0x64, 0x13, - 0x2f, 0x99, 0xf9, 0x48, 0x2a, 0x78, 0x14, 0x5d, 0xd6, 0x32, 0xf3, 0x03, 0x55, 0x90, 0x7a, 0x42, - 0x1f, 0x96, 0xe4, 0xe1, 0xc4, 0x91, 0x16, 0x32, 0x77, 0x17, 0xa4, 0x2d, 0x32, 0x2d, 0x64, 0x3e, - 0x2a, 0xc9, 0xc3, 0xc9, 0xcb, 0x17, 0x5f, 0xce, 0xfb, 0xe8, 0xeb, 0x79, 0x1f, 0x7d, 0x3f, 0xef, - 0xa3, 0xcf, 0x3f, 0xfa, 0xd7, 0x3e, 0xec, 0xfe, 0xc7, 0xd7, 0xe3, 0x64, 0x19, 0xfe, 0xfe, 0x4f, - 0x7f, 0x07, 0x00, 0x00, 0xff, 0xff, 0x82, 0xbf, 0x95, 0x20, 0x73, 0x04, 0x00, 0x00, + // 609 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0x13, 0x31, + 0x10, 0xc6, 0x49, 0xfa, 0x13, 0xa7, 0x69, 0x8b, 0xdb, 0x06, 0x53, 0x41, 0x58, 0x82, 0x84, 0x16, + 0x0a, 0x8d, 0x04, 0xe2, 0xc0, 0x81, 0x0b, 0x05, 0xa1, 0x1e, 0xa0, 0xc8, 0xed, 0x89, 0x4b, 0xe4, + 0xec, 0x3a, 0x1b, 0x2b, 0x1b, 0x7b, 0xf1, 0x7a, 0x9b, 0x94, 0x2b, 0x2f, 0xc1, 0x23, 0x71, 0xe4, + 0x11, 0x50, 0x91, 0x78, 0x0c, 0x84, 0x3c, 0xfb, 0xd3, 0x14, 0x09, 0x2a, 0x4e, 0x3b, 0xdf, 0x8f, + 0x77, 0x66, 0x3c, 0x23, 0xe3, 0xe7, 0x91, 0xb4, 0xe3, 0x6c, 0xb8, 0x1f, 0xe8, 0x69, 0xff, 0x64, + 0x2c, 0x4e, 0xc6, 0x52, 0x45, 0xe9, 0x3b, 0x61, 0x67, 0xda, 0x4c, 0xfa, 0xd6, 0xaa, 0x3e, 0x4f, + 0x64, 0x3f, 0xe2, 0x56, 0xcc, 0xf8, 0x59, 0xf9, 0xdd, 0x4f, 0x8c, 0xb6, 0x9a, 0xac, 0x14, 0x70, + 0xf7, 0xf1, 0xc2, 0x3f, 0x22, 0x1d, 0xe9, 0x3e, 0xe8, 0xc3, 0x6c, 0x04, 0x08, 0x00, 0x44, 0xf9, + 0xb9, 0xde, 0x0c, 0xb7, 0xde, 0xbc, 0x3f, 0x7e, 0x2b, 0x2c, 0x0f, 0xb9, 0xe5, 0x84, 0xe0, 0x86, + 0x95, 0x53, 0x41, 0x91, 0x87, 0xfc, 0x3a, 0x83, 0x98, 0xec, 0xe2, 0xd5, 0x98, 0x5b, 0x69, 0xb3, + 0x50, 0xd0, 0x9a, 0x87, 0xfc, 0x1a, 0xab, 0x30, 0xb9, 0x85, 0x9b, 0xb1, 0x56, 0x51, 0x2e, 0xd6, + 0x41, 0xbc, 0x20, 0xdc, 0x49, 0x1e, 0x17, 0x27, 0x1b, 0x1e, 0xf2, 0x97, 0x58, 0x85, 0x7b, 0xbf, + 0x10, 0xc6, 0x6c, 0x5e, 0x25, 0xbe, 0x8d, 0x71, 0xd1, 0xc1, 0x40, 0x86, 0x90, 0xbe, 0xc9, 0x9a, + 0x05, 0x73, 0x18, 0xba, 0x3c, 0xae, 0x96, 0xd4, 0xf2, 0x69, 0x42, 0x5b, 0x1e, 0xf2, 0xdb, 0xec, + 0x82, 0xa8, 0xaa, 0x5e, 0x5b, 0xa8, 0xfa, 0x26, 0x5e, 0x35, 0xa3, 0x41, 0x30, 0xe6, 0x52, 0xd1, + 0x1d, 0x38, 0xb0, 0x62, 0x46, 0x07, 0x0e, 0x12, 0x8a, 0x57, 0x82, 0x31, 0x57, 0x4a, 0xc4, 0xb4, + 0x93, 0x2b, 0x05, 0x74, 0x69, 0x46, 0x46, 0x7c, 0xcc, 0x84, 0x0a, 0xce, 0xe8, 0x1d, 0x0f, 0xf9, + 0x0d, 0x76, 0x41, 0xb8, 0x34, 0x26, 0x4d, 0x25, 0xf5, 0xa0, 0x4f, 0x88, 0xc9, 0x26, 0xae, 0xa7, + 0xca, 0xd0, 0xbb, 0x40, 0xb9, 0x90, 0xdc, 0xc7, 0xf5, 0x28, 0x49, 0xe9, 0x03, 0x0f, 0xf9, 0xad, + 0x27, 0xdb, 0xfb, 0xe5, 0x98, 0x16, 0x6e, 0x99, 0x39, 0x43, 0xef, 0x27, 0xc2, 0x1b, 0x27, 0xf3, + 0x03, 0xad, 0x46, 0x32, 0xca, 0x0c, 0xb7, 0x52, 0xab, 0x2b, 0xda, 0xfc, 0x47, 0x4b, 0x97, 0x0a, + 0xef, 0xfc, 0x59, 0xf8, 0x36, 0x5e, 0x4a, 0xf4, 0x4c, 0x18, 0x7a, 0x03, 0x86, 0x90, 0x03, 0xf2, + 0x0c, 0x77, 0x12, 0x1d, 0x73, 0x23, 0x3f, 0x41, 0xf2, 0x81, 0x54, 0xa7, 0xc2, 0xa4, 0x52, 0x2b, + 0xe8, 0x7c, 0x95, 0xed, 0x2c, 0xaa, 0x87, 0xa5, 0x48, 0xfa, 0x78, 0xab, 0xfa, 0xf3, 0x20, 0x14, + 0xa7, 0x12, 0x74, 0xb8, 0x94, 0x36, 0x23, 0x95, 0xf4, 0xaa, 0x54, 0x7a, 0x9f, 0xeb, 0x78, 0xf9, + 0xd8, 0x72, 0x9b, 0xa5, 0x97, 0xfb, 0x43, 0x7f, 0x1b, 0x63, 0x6d, 0x61, 0x8c, 0xeb, 0xb8, 0x26, + 0xdd, 0x55, 0xd4, 0xfd, 0x26, 0xab, 0xc9, 0xc4, 0xad, 0x54, 0x12, 0x73, 0x3b, 0xd2, 0x66, 0x0a, + 0xe3, 0x6e, 0xb2, 0x0a, 0x93, 0x7b, 0xb8, 0x1d, 0x68, 0x65, 0x79, 0x60, 0x07, 0x62, 0xca, 0x65, + 0x4c, 0xdb, 0x60, 0x58, 0x2b, 0xc8, 0xd7, 0x8e, 0x23, 0x1e, 0x6e, 0x85, 0x22, 0x0d, 0x8c, 0x4c, + 0xa0, 0xec, 0x75, 0xb0, 0x2c, 0x52, 0xa4, 0x83, 0x97, 0x8d, 0x88, 0x9c, 0xb8, 0x01, 0x62, 0x81, + 0x1c, 0x3f, 0x34, 0x32, 0x8c, 0x04, 0xdd, 0xcc, 0xf9, 0x1c, 0x81, 0x5f, 0x67, 0x56, 0x18, 0x7a, + 0xbd, 0xf0, 0x03, 0x2a, 0x17, 0x61, 0xe7, 0x8a, 0x45, 0x70, 0x2b, 0x64, 0xac, 0x85, 0x4b, 0x6f, + 0x33, 0x17, 0x92, 0x2d, 0xbc, 0x64, 0xe6, 0x03, 0xa9, 0x60, 0x89, 0xda, 0xac, 0x61, 0xe6, 0x87, + 0xaa, 0x20, 0xf5, 0x84, 0x3e, 0x2c, 0xc9, 0xa3, 0x89, 0x23, 0x2d, 0x38, 0xf7, 0x72, 0xd2, 0x16, + 0x4e, 0x0b, 0xce, 0x47, 0x25, 0x79, 0x34, 0x79, 0xf9, 0xe2, 0xeb, 0x79, 0x17, 0x7d, 0x3b, 0xef, + 0xa2, 0xef, 0xe7, 0x5d, 0xf4, 0xe5, 0x47, 0xf7, 0xda, 0x87, 0xbd, 0xff, 0x78, 0x6d, 0x86, 0xcb, + 0xf0, 0x5c, 0x3c, 0xfd, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xda, 0x61, 0xf5, 0x33, 0xa3, 0x04, 0x00, + 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 32878a56c..580382f7d 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -54,6 +54,8 @@ message Status { string contact_email = 13; string description = 14; string region = 15; + string bridge = 16; + string router = 17; GPSMetadata gps = 21; diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index ad3076c44..5c8591b31 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -20,5 +20,7 @@ func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status } }() + status.Router = r.Identity.Id + return r.getGateway(gatewayID).HandleStatus(status) } From 5c116ab948c7228a9f1c0894932954369512fcd6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Nov 2016 10:37:05 +0100 Subject: [PATCH 2211/2266] And update the test for 1dd4d66 --- core/router/gateway_status_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index fcb2c2005..31a972754 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -6,6 +6,7 @@ package router import ( "testing" + pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/router/gateway" @@ -19,7 +20,8 @@ func TestHandleGatewayStatus(t *testing.T) { router := &router{ Component: &component.Component{ - Ctx: GetLogger(t, "TestHandleGatewayStatus"), + Ctx: GetLogger(t, "TestHandleGatewayStatus"), + Identity: &pb_discovery.Announcement{}, }, gateways: map[string]*gateway.Gateway{}, } From 86ad20aafdcdd89be4b31d6231da580e35b7600c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 24 Nov 2016 13:52:47 +0100 Subject: [PATCH 2212/2266] Clean up old redis-conversions - discovery.Announcement - gateway.Status --- api/discovery/announcement.go | 101 +-------------- api/discovery/announcement_test.go | 57 +++----- api/gateway/status_message.go | 202 ----------------------------- api/gateway/status_message_test.go | 70 ---------- 4 files changed, 16 insertions(+), 414 deletions(-) delete mode 100644 api/gateway/status_message.go delete mode 100644 api/gateway/status_message_test.go diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go index c43bd38dc..22affe675 100644 --- a/api/discovery/announcement.go +++ b/api/discovery/announcement.go @@ -3,106 +3,7 @@ package discovery -import ( - "bytes" - "encoding/json" - "fmt" -) - -// AnnouncementProperties contains all properties of an Announcement that can -// be stored in Redis. -var AnnouncementProperties = []string{ - "id", - "description", - "service_name", - "service_version", - "net_address", - "public_key", - "certificate", - "metadata", -} - -// ToStringStringMap converts the given properties of Announcement to a -// map[string]string for storage in Redis. -func (announcement *Announcement) ToStringStringMap(properties ...string) (map[string]string, error) { - output := make(map[string]string) - for _, p := range properties { - property, err := announcement.formatProperty(p) - if err != nil { - return output, err - } - output[p] = property - } - return output, nil -} - -// FromStringStringMap imports known values from the input to a Status. -func (announcement *Announcement) FromStringStringMap(input map[string]string) error { - for k, v := range input { - announcement.parseProperty(k, v) - } - return nil -} - -func (announcement *Announcement) formatProperty(property string) (formatted string, err error) { - switch property { - case "id": - formatted = announcement.Id - case "description": - formatted = announcement.Description - case "service_name": - formatted = announcement.ServiceName - case "service_version": - formatted = announcement.ServiceVersion - case "net_address": - formatted = announcement.NetAddress - case "public_key": - formatted = announcement.PublicKey - case "certificate": - formatted = announcement.Certificate - case "metadata": - json, err := json.Marshal(announcement.Metadata) - if err != nil { - return "", err - } - formatted = string(json) - default: - err = fmt.Errorf("Property %s does not exist in Announcement", property) - } - return -} - -func (announcement *Announcement) parseProperty(property string, value string) error { - if value == "" { - return nil - } - switch property { - case "id": - announcement.Id = value - case "description": - announcement.Description = value - case "service_name": - announcement.ServiceName = value - case "service_version": - announcement.ServiceVersion = value - case "net_address": - announcement.NetAddress = value - case "public_key": - announcement.PublicKey = value - case "certificate": - announcement.Certificate = value - case "metadata": - metadata := []*Metadata{} - err := json.Unmarshal([]byte(value), &metadata) - if err != nil { - return err - } - announcement.Metadata = metadata - default: - return fmt.Errorf("Property %s does not exist in Announcement", property) - } - return nil -} +import "bytes" // AddMetadata adds metadata to the announcement if it doesn't exist func (announcement *Announcement) AddMetadata(key Metadata_Key, value []byte) { diff --git a/api/discovery/announcement_test.go b/api/discovery/announcement_test.go index 9c2aadc31..3af298253 100644 --- a/api/discovery/announcement_test.go +++ b/api/discovery/announcement_test.go @@ -9,48 +9,21 @@ import ( . "github.com/smartystreets/assertions" ) -func getTestAnnouncement() (announcement *Announcement, dmap map[string]string) { - return &Announcement{ - Id: "abcdef", - Description: "Test Description", - ServiceName: "router", - ServiceVersion: "1.0-preview build abcdef", - NetAddress: "localhost:1234", - Metadata: []*Metadata{ - &Metadata{ - Key: Metadata_PREFIX, - Value: []byte("38"), - }, - &Metadata{ - Key: Metadata_PREFIX, - Value: []byte("39"), - }, - }, - }, map[string]string{ - "id": "abcdef", - "description": "Test Description", - "service_name": "router", - "service_version": "1.0-preview build abcdef", - "net_address": "localhost:1234", - "public_key": "", - "certificate": "", - "metadata": `[{"key":1,"value":"Mzg="},{"key":1,"value":"Mzk="}]`, - } -} - -func TestToStringMap(t *testing.T) { +func TestAnnouncementAddDeleteMetadata(t *testing.T) { a := New(t) - announcement, expected := getTestAnnouncement() - dmap, err := announcement.ToStringStringMap(AnnouncementProperties...) - a.So(err, ShouldBeNil) - a.So(dmap, ShouldResemble, expected) -} + announcement := new(Announcement) + + announcement.AddMetadata(Metadata_APP_ID, []byte("app-id")) + a.So(announcement.Metadata, ShouldHaveLength, 1) + a.So(announcement.Metadata[0], ShouldResemble, &Metadata{Key: Metadata_APP_ID, Value: []byte("app-id")}) + + announcement.AddMetadata(Metadata_APP_ID, []byte("app-id")) + a.So(announcement.Metadata, ShouldHaveLength, 1) + + announcement.AddMetadata(Metadata_APP_ID, []byte("other-app-id")) + a.So(announcement.Metadata, ShouldHaveLength, 2) + + announcement.DeleteMetadata(Metadata_APP_ID, []byte("app-id")) + a.So(announcement.Metadata, ShouldHaveLength, 1) -func TestFromStringMap(t *testing.T) { - a := New(t) - announcement := &Announcement{} - expected, dmap := getTestAnnouncement() - err := announcement.FromStringStringMap(dmap) - a.So(err, ShouldBeNil) - a.So(announcement, ShouldResemble, expected) } diff --git a/api/gateway/status_message.go b/api/gateway/status_message.go deleted file mode 100644 index 51c01bbd4..000000000 --- a/api/gateway/status_message.go +++ /dev/null @@ -1,202 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "fmt" - "strings" - - "github.com/TheThingsNetwork/ttn/core/storage" -) - -// StatusMessageProperties contains all properties of a StatusMessage that can -// be stored in Redis. -var StatusMessageProperties = []string{ - "timestamp", - "time", - "ip", - "platform", - "contact_email", - "description", - "region", - "gps.time", - "gps.latitude", - "gps.longitude", - "gps.altitude", - "rtt", - "rx_in", - "rx_ok", - "tx_in", - "tx_ok", -} - -// ToStringStringMap converts the given properties of Status to a -// map[string]string for storage in Redis. -func (status *Status) ToStringStringMap(properties ...string) (map[string]string, error) { - output := make(map[string]string) - for _, p := range properties { - property, err := status.formatProperty(p) - if err != nil { - return output, err - } - if property != "" { - output[p] = property - } - } - return output, nil -} - -// FromStringStringMap imports known values from the input to a Status. -func (status *Status) FromStringStringMap(input map[string]string) error { - for k, v := range input { - status.parseProperty(k, v) - } - return nil -} - -func (status *Status) formatProperty(property string) (formatted string, err error) { - switch property { - case "timestamp": - formatted = storage.FormatUint32(status.Timestamp) - case "time": - formatted = storage.FormatInt64(status.Time) - case "ip": - formatted = strings.Join(status.Ip, ",") - case "platform": - formatted = status.Platform - case "contact_email": - formatted = status.ContactEmail - case "description": - formatted = status.Description - case "region": - formatted = status.Region - case "gps.time": - if status.Gps != nil { - formatted = storage.FormatInt64(status.Gps.Time) - } - case "gps.latitude": - if status.Gps != nil { - formatted = storage.FormatFloat32(status.Gps.Latitude) - } - case "gps.longitude": - if status.Gps != nil { - formatted = storage.FormatFloat32(status.Gps.Longitude) - } - case "gps.altitude": - if status.Gps != nil { - formatted = storage.FormatInt32(status.Gps.Altitude) - } - case "rtt": - formatted = storage.FormatUint32(status.Rtt) - case "rx_in": - formatted = storage.FormatUint32(status.RxIn) - case "rx_ok": - formatted = storage.FormatUint32(status.RxOk) - case "tx_in": - formatted = storage.FormatUint32(status.TxIn) - case "tx_ok": - formatted = storage.FormatUint32(status.TxOk) - default: - err = fmt.Errorf("Property %s does not exist in Status", property) - } - return -} - -func (status *Status) parseProperty(property string, value string) error { - if value == "" { - return nil - } - switch property { - case "timestamp": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - status.Timestamp = val - case "time": - val, err := storage.ParseInt64(value) - if err != nil { - return err - } - status.Time = val - case "ip": - val := strings.Split(value, ",") - status.Ip = val - case "platform": - status.Platform = value - case "contact_email": - status.ContactEmail = value - case "description": - status.Description = value - case "region": - status.Region = value - case "gps.time": - if status.Gps == nil { - status.Gps = &GPSMetadata{} - } - val, err := storage.ParseInt64(value) - if err != nil { - return err - } - status.Gps.Time = val - case "gps.latitude": - if status.Gps == nil { - status.Gps = &GPSMetadata{} - } - val, err := storage.ParseFloat32(value) - if err != nil { - return err - } - status.Gps.Latitude = val - case "gps.longitude": - if status.Gps == nil { - status.Gps = &GPSMetadata{} - } - val, err := storage.ParseFloat32(value) - if err != nil { - return err - } - status.Gps.Longitude = val - case "gps.altitude": - if status.Gps == nil { - status.Gps = &GPSMetadata{} - } - val, err := storage.ParseInt32(value) - if err != nil { - return err - } - status.Gps.Altitude = val - case "rtt": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - status.Rtt = val - case "rx_in": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - status.RxIn = val - case "rx_ok": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - status.RxOk = val - case "tx_in": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - status.TxIn = val - case "tx_ok": - val, err := storage.ParseUint32(value) - if err != nil { - return err - } - status.TxOk = val - } - return nil -} diff --git a/api/gateway/status_message_test.go b/api/gateway/status_message_test.go deleted file mode 100644 index 5a66b3f84..000000000 --- a/api/gateway/status_message_test.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package gateway - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func getStatusMessage() (status *Status, smap map[string]string) { - t := int64(1462201853428843766) - return &Status{ - Timestamp: 12345, - Time: t, - Ip: []string{"169.50.131.24", "2a03:8180:1401:14f::2"}, - Platform: "The Things Gateway", - ContactEmail: "contact@email.net", - Description: "Description", - Region: "EU_863_870", - Gps: &GPSMetadata{ - Time: t, - Latitude: 52.3737171, - Longitude: 4.884567, - Altitude: 9, - }, - Rtt: 12, - RxIn: 42, - RxOk: 41, - TxIn: 52, - TxOk: 51, - }, map[string]string{ - "timestamp": "12345", - "time": "1462201853428843766", - "ip": "169.50.131.24,2a03:8180:1401:14f::2", - "platform": "The Things Gateway", - "contact_email": "contact@email.net", - "description": "Description", - "region": "EU_863_870", - "gps.time": "1462201853428843766", - "gps.latitude": "52.37372", - "gps.longitude": "4.884567", - "gps.altitude": "9", - "rtt": "12", - "rx_in": "42", - "rx_ok": "41", - "tx_in": "52", - "tx_ok": "51", - } -} - -func TestToStringMap(t *testing.T) { - a := New(t) - status, expected := getStatusMessage() - smap, err := status.ToStringStringMap(StatusMessageProperties...) - a.So(err, ShouldBeNil) - a.So(smap, ShouldResemble, expected) -} - -func TestFromStringMap(t *testing.T) { - a := New(t) - status := &Status{} - expected, smap := getStatusMessage() - err := status.FromStringStringMap(smap) - a.So(err, ShouldBeNil) - a.So(status, ShouldResemble, expected) -} - -// TODO: Test error cases From b19cdad5c6585bf40c6d920e232388751a32b54d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 28 Nov 2016 13:29:46 +0100 Subject: [PATCH 2213/2266] Move Frequency Plans to band package and add CN/AS/KR Resolves #359 --- api/protocol/lorawan/message_conversion.go | 36 +++ .../lorawan/message_conversion_test.go | 42 ++++ core/band/band.go | 81 ++++++ core/router/activation.go | 13 +- core/router/downlink.go | 230 +++++------------- core/router/downlink_test.go | 73 +++++- vendor/vendor.json | 12 +- 7 files changed, 310 insertions(+), 177 deletions(-) create mode 100644 core/band/band.go diff --git a/api/protocol/lorawan/message_conversion.go b/api/protocol/lorawan/message_conversion.go index 3c52706e6..da09ad121 100644 --- a/api/protocol/lorawan/message_conversion.go +++ b/api/protocol/lorawan/message_conversion.go @@ -6,6 +6,7 @@ package lorawan import ( "github.com/TheThingsNetwork/ttn/core/types" "github.com/brocaar/lorawan" + "github.com/brocaar/lorawan/band" ) type payloader interface { @@ -188,3 +189,38 @@ func MessageFromPHYPayload(phy lorawan.PHYPayload) Message { } return m } + +// GetDataRate returns the band.Datarate for the current Metadata +func (m *Metadata) GetDataRate() (dataRate band.DataRate, err error) { + switch m.Modulation { + case Modulation_LORA: + dataRate.Modulation = band.LoRaModulation + dr, err := types.ParseDataRate(m.DataRate) + if err != nil { + return dataRate, err + } + dataRate.Bandwidth = int(dr.Bandwidth) + dataRate.SpreadFactor = int(dr.SpreadingFactor) + case Modulation_FSK: + dataRate.Modulation = band.FSKModulation + dataRate.BitRate = int(m.BitRate) + } + return +} + +// SetDataRate sets the dataRate for the current Metadata based from a band.Datarate +func (c *TxConfiguration) SetDataRate(dataRate band.DataRate) error { + switch dataRate.Modulation { + case band.LoRaModulation: + c.Modulation = Modulation_LORA + datr, err := types.ConvertDataRate(dataRate) + if err != nil { + return err + } + c.DataRate = datr.String() + case band.FSKModulation: + c.Modulation = Modulation_FSK + c.BitRate = uint32(dataRate.BitRate) + } + return nil +} diff --git a/api/protocol/lorawan/message_conversion_test.go b/api/protocol/lorawan/message_conversion_test.go index bd79338e1..a82a59025 100644 --- a/api/protocol/lorawan/message_conversion_test.go +++ b/api/protocol/lorawan/message_conversion_test.go @@ -7,6 +7,7 @@ import ( "testing" "github.com/brocaar/lorawan" + "github.com/brocaar/lorawan/band" . "github.com/smartystreets/assertions" ) @@ -56,3 +57,44 @@ func TestConvertPHYPayload(t *testing.T) { } } + +func TestConvertDataRate(t *testing.T) { + a := New(t) + + { + md := &Metadata{ + Modulation: Modulation_LORA, + DataRate: "SF7BW125", + } + dr, err := md.GetDataRate() + a.So(err, ShouldBeNil) + a.So(dr, ShouldResemble, band.DataRate{Modulation: band.LoRaModulation, SpreadFactor: 7, Bandwidth: 125}) + } + + { + md := &Metadata{ + Modulation: Modulation_FSK, + BitRate: 50000, + } + dr, err := md.GetDataRate() + a.So(err, ShouldBeNil) + a.So(dr, ShouldResemble, band.DataRate{Modulation: band.FSKModulation, BitRate: 50000}) + } + + { + tx := new(TxConfiguration) + err := tx.SetDataRate(band.DataRate{Modulation: band.LoRaModulation, SpreadFactor: 7, Bandwidth: 125}) + a.So(err, ShouldBeNil) + a.So(tx.Modulation, ShouldEqual, Modulation_LORA) + a.So(tx.DataRate, ShouldEqual, "SF7BW125") + } + + { + tx := new(TxConfiguration) + err := tx.SetDataRate(band.DataRate{Modulation: band.FSKModulation, BitRate: 50000}) + a.So(err, ShouldBeNil) + a.So(tx.Modulation, ShouldEqual, Modulation_FSK) + a.So(tx.BitRate, ShouldEqual, 50000) + } + +} diff --git a/core/band/band.go b/core/band/band.go new file mode 100644 index 000000000..6b9c4e696 --- /dev/null +++ b/core/band/band.go @@ -0,0 +1,81 @@ +package band + +import ( + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/brocaar/lorawan" + lora "github.com/brocaar/lorawan/band" +) + +// FrequencyPlan includes band configuration and CFList +type FrequencyPlan struct { + lora.Band + CFList *lorawan.CFList +} + +// Guess the region based on frequency +func Guess(frequency uint64) string { + switch { + case frequency >= 863000000 && frequency <= 870000000: + return pb_lorawan.Region_EU_863_870.String() + case frequency >= 902300000 && frequency <= 914900000: + return pb_lorawan.Region_US_902_928.String() + case frequency >= 779500000 && frequency <= 786500000: + return pb_lorawan.Region_CN_779_787.String() + case frequency >= 433175000 && frequency <= 434665000: + return pb_lorawan.Region_EU_433.String() + case frequency == 923200000 || frequency == 923400000: + return pb_lorawan.Region_AS_923.String() + case frequency >= 920900000 || frequency == 923300000: + return pb_lorawan.Region_KR_920_923.String() + case frequency >= 915200000 && frequency <= 927800000: + return pb_lorawan.Region_AU_915_928.String() + case frequency >= 470300000 && frequency <= 489300000: + return pb_lorawan.Region_CN_470_510.String() + } + return "" +} + +// Get the frequency plan for the given region +func Get(region string) (frequencyPlan FrequencyPlan, err error) { + switch region { + case pb_lorawan.Region_EU_863_870.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.EU_863_870, false, lorawan.DwellTimeNoLimit) + // TTN uses SF9BW125 in RX2 + frequencyPlan.RX2DataRate = 3 + // TTN frequency plan includes extra channels next to the default channels: + frequencyPlan.UplinkChannels = []lora.Channel{ + lora.Channel{Frequency: 868100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 868300000, DataRates: []int{0, 1, 2, 3, 4, 5, 6}}, // Also SF7BW250 + lora.Channel{Frequency: 868500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 868800000, DataRates: []int{7}}, // FSK 50kbps + lora.Channel{Frequency: 867100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + lora.Channel{Frequency: 867900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, + } + frequencyPlan.DownlinkChannels = frequencyPlan.UplinkChannels + frequencyPlan.CFList = &lorawan.CFList{867100000, 867300000, 867500000, 867700000, 867900000} + case pb_lorawan.Region_US_902_928.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.US_902_928, false, lorawan.DwellTime400ms) + case pb_lorawan.Region_CN_779_787.String(): + err = errors.NewErrInternal("China 779-787 MHz band not supported") + case pb_lorawan.Region_EU_433.String(): + err = errors.NewErrInternal("Europe 433 MHz band not supported") + case pb_lorawan.Region_AU_915_928.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.AU_915_928, false, lorawan.DwellTime400ms) + case pb_lorawan.Region_CN_470_510.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.CN_470_510, false, lorawan.DwellTimeNoLimit) + case pb_lorawan.Region_AS_923.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.AS_923, false, lorawan.DwellTime400ms) + case pb_lorawan.Region_KR_920_923.String(): + frequencyPlan.Band, err = lora.GetConfig(lora.KR_920_923, false, lorawan.DwellTimeNoLimit) + default: + err = errors.NewErrInvalidArgument("Frequency Band", "unknown") + } + if err != nil { + return + } + return +} diff --git a/core/router/activation.go b/core/router/activation.go index 8af828e80..9eaab6fc7 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -12,6 +12,7 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/band" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" ) @@ -81,9 +82,9 @@ func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivat } region := status.Region if region == "" { - region = guessRegion(uplink.GatewayMetadata.Frequency) + region = band.Guess(uplink.GatewayMetadata.Frequency) } - band, err := getBand(region) + band, err := band.Get(region) if err != nil { return nil, err } @@ -91,9 +92,11 @@ func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivat lorawan.Rx1DrOffset = 0 lorawan.Rx2Dr = uint32(band.RX2DataRate) lorawan.RxDelay = uint32(band.ReceiveDelay1.Seconds()) - switch region { - case "EU_863_870": - lorawan.CfList = &pb_lorawan.CFList{Freq: []uint32{867100000, 867300000, 867500000, 867700000, 867900000}} + if band.CFList != nil { + lorawan.CfList = new(pb_lorawan.CFList) + for _, freq := range band.CFList { + lorawan.CfList.Freq = append(lorawan.CfList.Freq, freq) + } } ctx = ctx.WithField("NumBrokers", len(brokers)) diff --git a/core/router/downlink.go b/core/router/downlink.go index 07e27fd5c..ba836583a 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -14,12 +14,12 @@ import ( pb_protocol "github.com/TheThingsNetwork/ttn/api/protocol" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/core/band" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/TheThingsNetwork/ttn/utils/toa" "github.com/apex/log" - lora "github.com/brocaar/lorawan/band" ) func (r *router) SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage, error) { @@ -67,76 +67,23 @@ func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { return r.getGateway(downlink.DownlinkOption.GatewayId).HandleDownlink(identifier, downlinkMessage) } -func guessRegion(frequency uint64) string { - switch { - case frequency >= 863000000 && frequency <= 870000000: - return pb_lorawan.Region_EU_863_870.String() - case frequency >= 902300000 && frequency <= 914900000: - return pb_lorawan.Region_US_902_928.String() - case frequency >= 779500000 && frequency <= 786500000: - return pb_lorawan.Region_CN_779_787.String() - case frequency >= 433175000 && frequency <= 434665000: - return pb_lorawan.Region_EU_433.String() - case frequency == 923200000 || frequency == 923400000: - return pb_lorawan.Region_AS_923.String() - case frequency >= 920900000 || frequency == 923300000: - return pb_lorawan.Region_KR_920_923.String() - case frequency >= 915200000 && frequency <= 927800000: - return pb_lorawan.Region_AU_915_928.String() - case frequency >= 470300000 && frequency <= 489300000: - return pb_lorawan.Region_CN_470_510.String() +// buildDownlinkOption builds a DownlinkOption with default values +func (r *router) buildDownlinkOption(gatewayID string, band band.FrequencyPlan) *pb_broker.DownlinkOption { + dataRate, _ := types.ConvertDataRate(band.DataRates[band.RX2DataRate]) + return &pb_broker.DownlinkOption{ + GatewayId: gatewayID, + ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ + Modulation: pb_lorawan.Modulation_LORA, + DataRate: dataRate.String(), + CodingRate: "4/5", + }}}, + GatewayConfig: &pb_gateway.TxConfiguration{ + RfChain: 0, + PolarizationInversion: true, + Frequency: uint64(band.RX2Frequency), + Power: int32(band.DefaultTXPower), + }, } - return "" -} - -func getBand(region string) (band *lora.Band, err error) { - var b lora.Band - - switch region { - case pb_lorawan.Region_EU_863_870.String(): - b, err = lora.GetConfig(lora.EU_863_870) - case pb_lorawan.Region_US_902_928.String(): - b, err = lora.GetConfig(lora.US_902_928) - case pb_lorawan.Region_CN_779_787.String(): - err = errors.NewErrInternal("China 779-787 MHz band not supported") - case pb_lorawan.Region_EU_433.String(): - err = errors.NewErrInternal("Europe 433 MHz band not supported") - case pb_lorawan.Region_AU_915_928.String(): - b, err = lora.GetConfig(lora.AU_915_928) - case pb_lorawan.Region_CN_470_510.String(): - err = errors.NewErrInternal("China 470-510 MHz band not supported") - case pb_lorawan.Region_AS_923.String(): - err = errors.NewErrInternal("Asia 923 MHz band not supported") - case pb_lorawan.Region_KR_920_923.String(): - err = errors.NewErrInternal("South Korea 920-923 MHz band not supported") - default: - err = errors.NewErrInvalidArgument("Frequency Band", "unknown") - } - if err != nil { - return - } - band = &b - - // TTN-specific configuration - if region == "EU_863_870" { - // TTN uses SF9BW125 in RX2 - band.RX2DataRate = 3 - // TTN frequency plan includes extra channels next to the default channels: - band.UplinkChannels = []lora.Channel{ - lora.Channel{Frequency: 868100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - lora.Channel{Frequency: 868300000, DataRates: []int{0, 1, 2, 3, 4, 5, 6}}, // Also SF7BW250 - lora.Channel{Frequency: 868500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - lora.Channel{Frequency: 868800000, DataRates: []int{7}}, // FSK 50kbps - lora.Channel{Frequency: 867100000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - lora.Channel{Frequency: 867300000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - lora.Channel{Frequency: 867500000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - lora.Channel{Frequency: 867700000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - lora.Channel{Frequency: 867900000, DataRates: []int{0, 1, 2, 3, 4, 5}}, - } - band.DownlinkChannels = band.UplinkChannels - } - - return } func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation bool, gateway *gateway.Gateway) (downlinkOptions []*pb_broker.DownlinkOption) { @@ -151,118 +98,75 @@ func (r *router) buildDownlinkOptions(uplink *pb.UplinkMessage, isActivation boo region := gatewayStatus.Region if region == "" { - region = guessRegion(uplink.GatewayMetadata.Frequency) + region = band.Guess(uplink.GatewayMetadata.Frequency) } - band, err := getBand(region) + band, err := band.Get(region) if err != nil { return // We can't handle this region } - - var dataRate lora.DataRate - - // LORA Modulation - if lorawanMetadata.Modulation == pb_lorawan.Modulation_LORA { - dataRate.Modulation = lora.LoRaModulation - dr, err := types.ParseDataRate(lorawanMetadata.DataRate) - if err != nil { - return // Invalid packet, probably won't happen if the gateway is just doing its job - } - dataRate.Bandwidth = int(dr.Bandwidth) - dataRate.SpreadFactor = int(dr.SpreadingFactor) + if region == "EU_863_870" && isActivation { + band.RX2DataRate = 0 } - if lorawanMetadata.Modulation == pb_lorawan.Modulation_FSK { - dataRate.Modulation = lora.FSKModulation - dataRate.BitRate = int(lorawanMetadata.BitRate) - } - - uplinkDRIndex, err := band.GetDataRate(dataRate) + dataRate, err := lorawanMetadata.GetDataRate() if err != nil { - return // Invalid packet, probably won't happen if the gateway is just doing its job + return } // Configuration for RX2 - { - power := int32(band.DefaultTXPower) + buildRX2 := func() (*pb_broker.DownlinkOption, error) { + option := r.buildDownlinkOption(gateway.ID, band) if region == "EU_863_870" { - power = 27 // The EU Downlink frequency allows up to 27dBm - if isActivation { - // TTN uses SF9BW125 in RX2, we have to reset this for joins - band.RX2DataRate = 0 - } + option.GatewayConfig.Power = 27 // The EU RX2 frequency allows up to 27dBm } - dataRate, _ := types.ConvertDataRate(band.DataRates[band.RX2DataRate]) - delay := band.ReceiveDelay2 if isActivation { - delay = band.JoinAcceptDelay2 - } - rx2 := &pb_broker.DownlinkOption{ - GatewayId: gateway.ID, - ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ - Modulation: pb_lorawan.Modulation_LORA, // RX2 is always LoRa - DataRate: dataRate.String(), // This is default - CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx - }}}, - GatewayConfig: &pb_gateway.TxConfiguration{ - Timestamp: uplink.GatewayMetadata.Timestamp + uint32(delay/1000), - RfChain: 0, - PolarizationInversion: true, - Frequency: uint64(band.RX2Frequency), - Power: power, - }, + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.JoinAcceptDelay2/1000) + } else { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.ReceiveDelay2/1000) } - options = append(options, rx2) + option.ProtocolConfig.GetLorawan().CodingRate = lorawanMetadata.CodingRate + return option, nil + } + + if option, err := buildRX2(); err == nil { + options = append(options, option) } // Configuration for RX1 - { - uplinkChannel, err := band.GetChannel(int(uplink.GatewayMetadata.Frequency), nil) - if err == nil { - downlinkChannel := band.DownlinkChannels[band.GetRX1Channel(uplinkChannel)] - downlinkDRIndex, err := band.GetRX1DataRateForOffset(uplinkDRIndex, 0) - if err == nil { - var modulation pb_lorawan.Modulation - var dataRateString string - var bitRate int - var frequencyDeviation int - - dr := band.DataRates[downlinkDRIndex] - if dr.Modulation == lora.LoRaModulation { - modulation = pb_lorawan.Modulation_LORA - dataRate, _ := types.ConvertDataRate(dr) - dataRateString = dataRate.String() - } + buildRX1 := func() (*pb_broker.DownlinkOption, error) { + option := r.buildDownlinkOption(gateway.ID, band) + if isActivation { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.JoinAcceptDelay1/1000) + } else { + option.GatewayConfig.Timestamp = uplink.GatewayMetadata.Timestamp + uint32(band.ReceiveDelay1/1000) + } + option.ProtocolConfig.GetLorawan().CodingRate = lorawanMetadata.CodingRate - if dr.Modulation == lora.FSKModulation { - modulation = pb_lorawan.Modulation_FSK - bitRate = dr.BitRate - frequencyDeviation = bitRate / 2 - } + freq, err := band.GetRX1Frequency(int(uplink.GatewayMetadata.Frequency)) + if err != nil { + return nil, err + } + option.GatewayConfig.Frequency = uint64(freq) - delay := band.ReceiveDelay1 - if isActivation { - delay = band.JoinAcceptDelay1 - } - rx1 := &pb_broker.DownlinkOption{ - GatewayId: gateway.ID, - ProtocolConfig: &pb_protocol.TxConfiguration{Protocol: &pb_protocol.TxConfiguration_Lorawan{Lorawan: &pb_lorawan.TxConfiguration{ - Modulation: modulation, - DataRate: dataRateString, - BitRate: uint32(bitRate), - CodingRate: lorawanMetadata.CodingRate, // Let's just take this from the Rx - }}}, - GatewayConfig: &pb_gateway.TxConfiguration{ - Timestamp: uplink.GatewayMetadata.Timestamp + uint32(delay/1000), - RfChain: 0, - PolarizationInversion: true, - Frequency: uint64(downlinkChannel.Frequency), - Power: int32(band.DefaultTXPower), - FrequencyDeviation: uint32(frequencyDeviation), - }, - } - options = append(options, rx1) - } + upDR, err := band.GetDataRate(dataRate) + if err != nil { + return nil, err } + downDR, err := band.GetRX1DataRate(upDR, 0) + if err != nil { + return nil, err + } + + if err := option.ProtocolConfig.GetLorawan().SetDataRate(band.DataRates[downDR]); err != nil { + return nil, err + } + option.GatewayConfig.FrequencyDeviation = uint32(option.ProtocolConfig.GetLorawan().BitRate / 2) + + return option, nil + } + + if option, err := buildRX1(); err == nil { + options = append(options, option) } computeDownlinkScores(gateway, uplink, options) @@ -291,7 +195,7 @@ func computeDownlinkScores(gateway *gateway.Gateway, uplink *pb.UplinkMessage, o region := gatewayStatus.Region if region == "" { - region = guessRegion(uplink.GatewayMetadata.Frequency) + region = band.Guess(uplink.GatewayMetadata.Frequency) } gatewayRx, _ := gateway.Utilization.Get() diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index f07733ea0..e3b8ca144 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -4,7 +4,6 @@ package router import ( - "fmt" "sync" "testing" "time" @@ -313,6 +312,76 @@ func TestUplinkBuildDownlinkOptionsDataRate(t *testing.T) { a.So(options, ShouldHaveLength, 2) a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) } + + gtw = newReferenceGateway(t, "CN_470_510") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnCNDataRates := []string{ + "SF7BW125", + "SF8BW125", + "SF9BW125", + "SF10BW125", + "SF11BW125", + "SF12BW125", + } + for _, dr := range ttnCNDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 470300000 + up.ProtocolMetadata.GetLorawan().DataRate = dr + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 500300000) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 505300000) + } + + gtw = newReferenceGateway(t, "AS_923") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnASDataRates := map[string]string{ + "SF7BW125": "SF7BW125", + "SF8BW125": "SF8BW125", + "SF9BW125": "SF9BW125", + "SF10BW125": "SF10BW125", + "SF11BW125": "SF10BW125", // MinDR = 2 + "SF12BW125": "SF10BW125", // MinDR = 2 + } + for drUp, drDown := range ttnASDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 923200000 + up.ProtocolMetadata.GetLorawan().DataRate = drUp + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, drDown) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 923200000) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF10BW125") + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 923200000) + } + + gtw = newReferenceGateway(t, "KR_920_923") + + // Supported datarates use RX1 (on the same datarate) for downlink + ttnKRDataRates := []string{ + "SF7BW125", + "SF8BW125", + "SF9BW125", + "SF10BW125", + "SF11BW125", + "SF12BW125", + } + for _, dr := range ttnKRDataRates { + up := newReferenceUplink() + up.GatewayMetadata.Frequency = 922100000 + up.ProtocolMetadata.GetLorawan().DataRate = dr + options := r.buildDownlinkOptions(up, false, gtw) + a.So(options, ShouldHaveLength, 2) + a.So(options[1].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, dr) + a.So(options[1].GatewayConfig.Frequency, ShouldEqual, 922100000) + a.So(options[0].ProtocolConfig.GetLorawan().DataRate, ShouldEqual, "SF12BW125") + a.So(options[0].GatewayConfig.Frequency, ShouldEqual, 921900000) + } + } // Note: This test uses r.buildDownlinkOptions which in turn calls computeDownlinkScores @@ -379,8 +448,6 @@ func TestComputeDownlinkScores(t *testing.T) { a.So(options, ShouldHaveLength, 1) // RX1 Removed a.So(options[0].GatewayConfig.Frequency, ShouldNotEqual, 868100000) - fmt.Println() - // European Duty-cycle Preferences - Prefer RX1 for low SF testSubject = newReferenceUplink() testSubject.ProtocolMetadata.GetLorawan().DataRate = "SF7BW125" diff --git a/vendor/vendor.json b/vendor/vendor.json index 12ae67f3f..340f7b705 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -123,16 +123,16 @@ "revisionTime": "2016-10-27T05:39:47Z" }, { - "checksumSHA1": "FHlkhEYvZoz3ynia1f+0zeyEWXU=", + "checksumSHA1": "7K5mkZjohicK/GIgyVwLCkX7vO4=", "path": "github.com/brocaar/lorawan", - "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", - "revisionTime": "2016-09-30T18:12:03Z" + "revision": "4e299458950055dd794109951c3b3e3e6f8785ec", + "revisionTime": "2016-11-23T19:29:51Z" }, { - "checksumSHA1": "MCRyJiR3hs/bt++6w5SCcyIYaZE=", + "checksumSHA1": "q51jt7CAzQoh6JWSV7gRpuKKKHU=", "path": "github.com/brocaar/lorawan/band", - "revision": "f4277a4ddb8f1adc1fead0f96e2e8a7e7bae0456", - "revisionTime": "2016-09-30T18:12:03Z" + "revision": "4e299458950055dd794109951c3b3e3e6f8785ec", + "revisionTime": "2016-11-23T19:29:51Z" }, { "checksumSHA1": "2Fy1Y6Z3lRRX1891WF/+HT4XS2I=", From f4ae03303eec829d2a8a3500e957430eacb9c6dc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 28 Nov 2016 16:05:49 +0100 Subject: [PATCH 2214/2266] Implement Rate Limiting in gRPC servers Initial values: - Uplink per gateway: 1500/min (expected max is 600/min) - Status per gateway: 10/min (expected max is 2/min) - Uplink per router: 1000/sec - Downlink per handler: 125/sec - Management: 5000/hour (per user and per application) The numbers might change depending on server capacity and measurements Resolves #263 --- api/handler/ApplicationManager.md | 4 +++ api/handler/handler.proto | 4 +++ api/ratelimit/ratelimit.go | 50 ++++++++++++++++++++++++++++ core/broker/manager_server.go | 28 ++++++++++++++++ core/broker/server.go | 19 +++++++++++ core/handler/manager_server.go | 20 +++++++++-- core/networkserver/manager_server.go | 10 +++++- core/router/server.go | 29 ++++++++++++++++ utils/errors/errors.go | 5 ++- 9 files changed, 164 insertions(+), 5 deletions(-) create mode 100644 api/ratelimit/ratelimit.go diff --git a/api/handler/ApplicationManager.md b/api/handler/ApplicationManager.md index 79c37453f..ff9f5be60 100644 --- a/api/handler/ApplicationManager.md +++ b/api/handler/ApplicationManager.md @@ -2,6 +2,10 @@ ApplicationManager manages application and device registrations on the Handler +To protect our quality of service, you can make up to 5000 calls to the +ApplicationManager API per hour. Once you go over the rate limit, you will +receive an error response. + ## Methods ### `RegisterApplication` diff --git a/api/handler/handler.proto b/api/handler/handler.proto index 3b2011c86..563608646 100644 --- a/api/handler/handler.proto +++ b/api/handler/handler.proto @@ -136,6 +136,10 @@ message DryDownlinkResult { } // ApplicationManager manages application and device registrations on the Handler +// +// To protect our quality of service, you can make up to 5000 calls to the +// ApplicationManager API per hour. Once you go over the rate limit, you will +// receive an error response. service ApplicationManager { // Applications should first be registered to the Handler with the `RegisterApplication` method diff --git a/api/ratelimit/ratelimit.go b/api/ratelimit/ratelimit.go new file mode 100644 index 000000000..483a4cd9b --- /dev/null +++ b/api/ratelimit/ratelimit.go @@ -0,0 +1,50 @@ +package ratelimit + +import ratelimit "gopkg.in/bsm/ratelimit.v1" +import "sync" +import "time" + +// Registry for rate limiting +type Registry struct { + rate int + per time.Duration + mu sync.RWMutex + entities map[string]*ratelimit.RateLimiter +} + +// NewRegistry returns a new Registry for rate limiting +func NewRegistry(rate int, per time.Duration) *Registry { + return &Registry{ + entities: make(map[string]*ratelimit.RateLimiter), + } +} + +func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.RateLimiter) *ratelimit.RateLimiter { + r.mu.RLock() + limiter, ok := r.entities[id] + r.mu.RUnlock() + if ok { + return limiter + } + limiter = createFunc() + r.mu.Lock() + r.entities[id] = limiter + r.mu.Unlock() + return limiter +} + +// Limit returns true if the ratelimit for the given entity has been reached +func (r *Registry) Limit(id string) bool { + return r.getOrCreate(id, func() *ratelimit.RateLimiter { + return ratelimit.New(r.rate, r.per) + }).Limit() +} + +// LimitWithRate returns true if the ratelimit for the given entity has been reached +// The first time this function is called for this ID, a RateLimiter is created with +// the given settings +func (r *Registry) LimitWithRate(id string, rate int, per time.Duration) bool { + return r.getOrCreate(id, func() *ratelimit.RateLimiter { + return ratelimit.New(rate, per) + }).Limit() +} diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index d8fac9c51..503b1cc70 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -4,11 +4,15 @@ package broker import ( + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/rights" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/ratelimit" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" @@ -20,9 +24,24 @@ type brokerManager struct { broker *broker deviceManager pb_lorawan.DeviceManagerClient devAddrManager pb_lorawan.DevAddrManagerClient + clientRate *ratelimit.Registry +} + +func (b *brokerManager) validateClient(ctx context.Context) (*claims.Claims, error) { + claims, err := b.broker.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, err + } + if b.clientRate.Limit(claims.Subject) { + return claims, grpc.Errorf(codes.ResourceExhausted, "Rate limit for client reached") + } + return claims, nil } func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*lorawan.Device, error) { + if _, err := b.validateClient(ctx); err != nil { + return nil, err + } res, err := b.deviceManager.GetDevice(ctx, in) if err != nil { return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return device")) @@ -31,6 +50,9 @@ func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentif } func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*empty.Empty, error) { + if _, err := b.validateClient(ctx); err != nil { + return nil, err + } res, err := b.deviceManager.SetDevice(ctx, in) if err != nil { return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not set device")) @@ -39,6 +61,9 @@ func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*emp } func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIdentifier) (*empty.Empty, error) { + if _, err := b.validateClient(ctx); err != nil { + return nil, err + } res, err := b.deviceManager.DeleteDevice(ctx, in) if err != nil { return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not delete device")) @@ -92,6 +117,9 @@ func (b *broker) RegisterManager(s *grpc.Server) { deviceManager: pb_lorawan.NewDeviceManagerClient(b.nsConn), devAddrManager: pb_lorawan.NewDevAddrManagerClient(b.nsConn), } + + server.clientRate = ratelimit.NewRegistry(5000, time.Hour) + pb.RegisterBrokerManagerServer(s, server) lorawan.RegisterDeviceManagerServer(s, server) lorawan.RegisterDevAddrManagerServer(s, server) diff --git a/core/broker/server.go b/core/broker/server.go index b702fb03f..d11629832 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -4,9 +4,12 @@ package broker import ( + "time" + "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" + "github.com/TheThingsNetwork/ttn/api/ratelimit" "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" @@ -16,6 +19,9 @@ import ( type brokerRPC struct { broker *broker pb.BrokerStreamServer + + routerUpRate *ratelimit.Registry + handlerDownRate *ratelimit.Registry } func (b *brokerRPC) associateRouter(md metadata.MD) (chan *pb.UplinkMessage, <-chan *pb.DownlinkMessage, func(), error) { @@ -37,6 +43,10 @@ func (b *brokerRPC) associateRouter(md metadata.MD) (chan *pb.UplinkMessage, <-c go func() { for message := range up { + if b.routerUpRate.Limit(router.Id) { + b.broker.Ctx.WithField("RouterID", router.Id).Warn("Router reached uplink rate limit, 1s penalty") + time.Sleep(time.Second) + } go b.broker.HandleUplink(message) } }() @@ -85,6 +95,10 @@ func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, case pb_discovery.Metadata_APP_ID: announcedID := string(meta.Value) if announcedID == downlink.AppId { + if b.handlerDownRate.Limit(handler.Id) { + b.broker.Ctx.WithField("HandlerID", handler.Id).Warn("Handler reached downlink rate limit, 1s penalty") + time.Sleep(time.Second) + } b.broker.HandleDownlink(downlink) return } @@ -117,5 +131,10 @@ func (b *broker) RegisterRPC(s *grpc.Server) { server.RouterAssociateChanFunc = server.associateRouter server.HandlerPublishChanFunc = server.getHandlerPublish server.HandlerSubscribeChanFunc = server.getHandlerSubscribe + + // TODO: Monitor actual rates and configure sensible limits + server.routerUpRate = ratelimit.NewRegistry(1000, time.Second) + server.handlerDownRate = ratelimit.NewRegistry(125, time.Second) // one eight of uplink + pb.RegisterBrokerServer(s, server) } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index b503c313d..e0e4249a0 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -5,6 +5,7 @@ package handler import ( "fmt" + "time" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/go-account-lib/rights" @@ -13,6 +14,7 @@ import ( pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/ratelimit" "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -24,9 +26,11 @@ import ( ) type handlerManager struct { - handler *handler - deviceManager pb_lorawan.DeviceManagerClient - devAddrManager pb_lorawan.DevAddrManagerClient + handler *handler + deviceManager pb_lorawan.DeviceManagerClient + devAddrManager pb_lorawan.DevAddrManagerClient + applicationRate *ratelimit.Registry + clientRate *ratelimit.Registry } func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID string) (context.Context, *claims.Claims, error) { @@ -52,6 +56,12 @@ func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID st if err != nil { return ctx, nil, err } + if h.clientRate.Limit(claims.Subject) { + return ctx, claims, grpc.Errorf(codes.ResourceExhausted, "Rate limit for client reached") + } + if h.applicationRate.Limit(appID) { + return ctx, claims, grpc.Errorf(codes.ResourceExhausted, "Rate limit for application reached") + } return ctx, claims, nil } @@ -440,6 +450,10 @@ func (h *handler) RegisterManager(s *grpc.Server) { deviceManager: pb_lorawan.NewDeviceManagerClient(h.ttnBrokerConn), devAddrManager: pb_lorawan.NewDevAddrManagerClient(h.ttnBrokerConn), } + + server.applicationRate = ratelimit.NewRegistry(5000, time.Hour) + server.clientRate = ratelimit.NewRegistry(5000, time.Hour) + pb.RegisterHandlerManagerServer(s, server) pb.RegisterApplicationManagerServer(s, server) pb_lorawan.RegisterDevAddrManagerServer(s, server) diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index ded54586e..448c369aa 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -10,6 +10,7 @@ import ( "github.com/TheThingsNetwork/go-account-lib/rights" pb "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" + "github.com/TheThingsNetwork/ttn/api/ratelimit" "github.com/TheThingsNetwork/ttn/core/networkserver/device" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" @@ -20,6 +21,7 @@ import ( type networkServerManager struct { networkServer *networkServer + clientRate *ratelimit.Registry } func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { @@ -30,6 +32,9 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev if err != nil { return nil, err } + if n.clientRate.Limit(claims.Subject) { + return nil, grpc.Errorf(codes.ResourceExhausted, "Rate limit for client reached") + } dev, err := n.networkServer.devices.Get(*in.AppEui, *in.DevEui) if err != nil { return nil, err @@ -157,7 +162,10 @@ func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusReque // RegisterManager registers this networkserver as a NetworkServerManagerServer (github.com/TheThingsNetwork/ttn/api/networkserver) func (n *networkServer) RegisterManager(s *grpc.Server) { - server := &networkServerManager{n} + server := &networkServerManager{networkServer: n} + + server.clientRate = ratelimit.NewRegistry(5000, time.Hour) + pb.RegisterNetworkServerManagerServer(s, server) pb_lorawan.RegisterDeviceManagerServer(s, server) pb_lorawan.RegisterDevAddrManagerServer(s, server) diff --git a/core/router/server.go b/core/router/server.go index 0174fa32e..f78c64b7c 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -5,22 +5,28 @@ package router import ( "fmt" + "time" "github.com/TheThingsNetwork/go-account-lib/claims" "github.com/TheThingsNetwork/ttn/api" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" + "github.com/TheThingsNetwork/ttn/api/ratelimit" pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) type routerRPC struct { router *router pb.RouterStreamServer + + uplinkRate *ratelimit.Registry + statusRate *ratelimit.Registry } func (r *routerRPC) gatewayFromMetadata(md metadata.MD) (gtw *gateway.Gateway, err error) { @@ -69,7 +75,12 @@ func (r *routerRPC) getUplink(md metadata.MD) (ch chan *pb.UplinkMessage, err er ch = make(chan *pb.UplinkMessage) go func() { for uplink := range ch { + if r.uplinkRate.Limit(gateway.ID) { + r.router.Ctx.WithField("GatewayID", gateway.ID).Warn("Gateway reached uplink rate limit, 1s penalty") + time.Sleep(time.Second) + } r.router.HandleUplink(gateway.ID, uplink) + } }() return @@ -83,6 +94,10 @@ func (r *routerRPC) getGatewayStatus(md metadata.MD) (ch chan *pb_gateway.Status ch = make(chan *pb_gateway.Status) go func() { for status := range ch { + if r.statusRate.Limit(gateway.ID) { + r.router.Ctx.WithField("GatewayID", gateway.ID).Warn("Gateway reached status rate limit, 1s penalty") + time.Sleep(time.Second) + } r.router.HandleGatewayStatus(gateway.ID, status) } }() @@ -114,6 +129,9 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques if err := req.Validate(); err != nil { return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } + if r.uplinkRate.Limit(gateway.ID) { + return nil, grpc.Errorf(codes.ResourceExhausted, "Gateway reached uplink rate limit, 1s penalty") + } return r.router.HandleActivation(gateway.ID, req) } @@ -124,5 +142,16 @@ func (r *router) RegisterRPC(s *grpc.Server) { server.UplinkChanFunc = server.getUplink server.DownlinkChanFunc = server.getDownlink server.GatewayStatusChanFunc = server.getGatewayStatus + + // TODO: Monitor actual rates and configure sensible limits + // + // The current values are based on the following: + // - 20 byte messages on all 6 orthogonal SFs at the same time -> ~1500 msgs/minute + // - 8 channels at 5% utilization: 600 msgs/minute + // - let's double that and round it to 1500/minute + + server.uplinkRate = ratelimit.NewRegistry(1500, time.Minute) // includes activations + server.statusRate = ratelimit.NewRegistry(10, time.Minute) // 10 per minute (pkt fwd default is 2 per minute) + pb.RegisterRouterServer(s, server) } diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 13b9c974d..944838a9d 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -47,7 +47,10 @@ func BuildGRPCError(err error) error { if err == nil { return nil } - code := codes.Unknown + code := grpc.Code(err) + if code != codes.Unknown { + return err // it already is a gRPC error + } switch errs.Cause(err).(type) { case *ErrAlreadyExists: code = codes.AlreadyExists From 2b83f51edfa3aaab0306b11bac526abab1ccc6fb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 28 Nov 2016 19:52:43 +0100 Subject: [PATCH 2215/2266] Fix ratelimit instantiate --- api/ratelimit/ratelimit.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/ratelimit/ratelimit.go b/api/ratelimit/ratelimit.go index 483a4cd9b..fd088add7 100644 --- a/api/ratelimit/ratelimit.go +++ b/api/ratelimit/ratelimit.go @@ -16,6 +16,8 @@ type Registry struct { func NewRegistry(rate int, per time.Duration) *Registry { return &Registry{ entities: make(map[string]*ratelimit.RateLimiter), + rate: rate, + per: per, } } From fc38ae087994c2b4d6729414fad019691760968e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 28 Nov 2016 20:23:29 +0100 Subject: [PATCH 2216/2266] Use juju/ratelimit for rate limiting --- api/ratelimit/ratelimit.go | 35 +++++++++++++++++++---------------- core/broker/server.go | 12 ++++++------ core/router/server.go | 14 +++++++------- vendor/vendor.json | 6 ++++++ 4 files changed, 38 insertions(+), 29 deletions(-) diff --git a/api/ratelimit/ratelimit.go b/api/ratelimit/ratelimit.go index fd088add7..bddb87f25 100644 --- a/api/ratelimit/ratelimit.go +++ b/api/ratelimit/ratelimit.go @@ -1,27 +1,31 @@ package ratelimit -import ratelimit "gopkg.in/bsm/ratelimit.v1" -import "sync" -import "time" +import ( + "fmt" + "sync" + "time" + + "github.com/juju/ratelimit" +) // Registry for rate limiting type Registry struct { rate int per time.Duration mu sync.RWMutex - entities map[string]*ratelimit.RateLimiter + entities map[string]*ratelimit.Bucket } // NewRegistry returns a new Registry for rate limiting func NewRegistry(rate int, per time.Duration) *Registry { return &Registry{ - entities: make(map[string]*ratelimit.RateLimiter), rate: rate, per: per, + entities: make(map[string]*ratelimit.Bucket), } } -func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.RateLimiter) *ratelimit.RateLimiter { +func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.Bucket) *ratelimit.Bucket { r.mu.RLock() limiter, ok := r.entities[id] r.mu.RUnlock() @@ -35,18 +39,17 @@ func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.RateLimit return limiter } +func (r *Registry) newFunc() *ratelimit.Bucket { + fmt.Println(r.per, r.rate) + return ratelimit.NewBucket(r.per, int64(r.rate)) +} + // Limit returns true if the ratelimit for the given entity has been reached func (r *Registry) Limit(id string) bool { - return r.getOrCreate(id, func() *ratelimit.RateLimiter { - return ratelimit.New(r.rate, r.per) - }).Limit() + return r.Wait(id) != 0 } -// LimitWithRate returns true if the ratelimit for the given entity has been reached -// The first time this function is called for this ID, a RateLimiter is created with -// the given settings -func (r *Registry) LimitWithRate(id string, rate int, per time.Duration) bool { - return r.getOrCreate(id, func() *ratelimit.RateLimiter { - return ratelimit.New(rate, per) - }).Limit() +// Wait returns the time to wait until available +func (r *Registry) Wait(id string) time.Duration { + return r.getOrCreate(id, r.newFunc).Take(1) } diff --git a/core/broker/server.go b/core/broker/server.go index d11629832..dc30e7ab9 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -43,9 +43,9 @@ func (b *brokerRPC) associateRouter(md metadata.MD) (chan *pb.UplinkMessage, <-c go func() { for message := range up { - if b.routerUpRate.Limit(router.Id) { - b.broker.Ctx.WithField("RouterID", router.Id).Warn("Router reached uplink rate limit, 1s penalty") - time.Sleep(time.Second) + if waitTime := b.routerUpRate.Wait(router.Id); waitTime != 0 { + b.broker.Ctx.WithField("RouterID", router.Id).WithField("Wait", waitTime).Warn("Router reached uplink rate limit") + time.Sleep(waitTime) } go b.broker.HandleUplink(message) } @@ -95,9 +95,9 @@ func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, case pb_discovery.Metadata_APP_ID: announcedID := string(meta.Value) if announcedID == downlink.AppId { - if b.handlerDownRate.Limit(handler.Id) { - b.broker.Ctx.WithField("HandlerID", handler.Id).Warn("Handler reached downlink rate limit, 1s penalty") - time.Sleep(time.Second) + if waitTime := b.handlerDownRate.Wait(handler.Id); waitTime != 0 { + b.broker.Ctx.WithField("HandlerID", handler.Id).WithField("Wait", waitTime).Warn("Handler reached downlink rate limit") + time.Sleep(waitTime) } b.broker.HandleDownlink(downlink) return diff --git a/core/router/server.go b/core/router/server.go index f78c64b7c..789a05523 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -75,9 +75,9 @@ func (r *routerRPC) getUplink(md metadata.MD) (ch chan *pb.UplinkMessage, err er ch = make(chan *pb.UplinkMessage) go func() { for uplink := range ch { - if r.uplinkRate.Limit(gateway.ID) { - r.router.Ctx.WithField("GatewayID", gateway.ID).Warn("Gateway reached uplink rate limit, 1s penalty") - time.Sleep(time.Second) + if waitTime := r.uplinkRate.Wait(gateway.ID); waitTime != 0 { + r.router.Ctx.WithField("GatewayID", gateway.ID).WithField("Wait", waitTime).Warn("Gateway reached uplink rate limit") + time.Sleep(waitTime) } r.router.HandleUplink(gateway.ID, uplink) @@ -94,9 +94,9 @@ func (r *routerRPC) getGatewayStatus(md metadata.MD) (ch chan *pb_gateway.Status ch = make(chan *pb_gateway.Status) go func() { for status := range ch { - if r.statusRate.Limit(gateway.ID) { - r.router.Ctx.WithField("GatewayID", gateway.ID).Warn("Gateway reached status rate limit, 1s penalty") - time.Sleep(time.Second) + if waitTime := r.statusRate.Wait(gateway.ID); waitTime != 0 { + r.router.Ctx.WithField("GatewayID", gateway.ID).WithField("Wait", waitTime).Warn("Gateway reached status rate limit") + time.Sleep(waitTime) } r.router.HandleGatewayStatus(gateway.ID, status) } @@ -130,7 +130,7 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) } if r.uplinkRate.Limit(gateway.ID) { - return nil, grpc.Errorf(codes.ResourceExhausted, "Gateway reached uplink rate limit, 1s penalty") + return nil, grpc.Errorf(codes.ResourceExhausted, "Gateway reached uplink rate limit") } return r.router.HandleActivation(gateway.ID, req) } diff --git a/vendor/vendor.json b/vendor/vendor.json index 340f7b705..95e73a4f5 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -356,6 +356,12 @@ "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", "revisionTime": "2016-04-10T22:58:39Z" }, + { + "checksumSHA1": "sKheT5xw89Tbu2Q071FQO27CVmE=", + "path": "github.com/juju/ratelimit", + "revision": "77ed1c8a01217656d2080ad51981f6e99adaa177", + "revisionTime": "2015-11-25T20:19:25Z" + }, { "checksumSHA1": "g+afVQQVopBLiLB5pFZp/8s6aBs=", "path": "github.com/kardianos/osext", From 4e6c9abd5c8a50a9ba6d3dcca56767b492180dae Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 10:21:16 +0100 Subject: [PATCH 2217/2266] Move gRPC error wrapping to interceptor --- api/broker/server_streams.go | 6 +- api/router/server_streams.go | 6 +- core/broker/activation.go | 32 ++++------ core/broker/manager_server.go | 18 +++--- core/broker/server.go | 20 +++--- core/broker/uplink.go | 4 +- core/component/grpc.go | 17 ++--- core/discovery/server.go | 12 ++-- core/handler/manager_server.go | 96 ++++++++++++++-------------- core/handler/server.go | 12 ++-- core/networkserver/manager_server.go | 16 ++--- core/networkserver/server.go | 30 ++++----- core/router/activation.go | 1 + core/router/manager_server.go | 6 +- core/router/server.go | 4 +- core/router/uplink.go | 4 +- utils/errors/errors.go | 5 +- 17 files changed, 146 insertions(+), 143 deletions(-) diff --git a/api/broker/server_streams.go b/api/broker/server_streams.go index ed30b9861..bbc9eacda 100644 --- a/api/broker/server_streams.go +++ b/api/broker/server_streams.go @@ -69,7 +69,7 @@ func (s *BrokerStreamServer) Associate(stream Broker_AssociateServer) (err error return err } if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + return errors.Wrap(err, "Invalid Uplink") } upChan <- uplink } @@ -105,7 +105,7 @@ func (s *BrokerStreamServer) Subscribe(req *SubscribeRequest, stream Broker_Subs } go func() { <-stream.Context().Done() - err = errors.BuildGRPCError(stream.Context().Err()) + err = stream.Context().Err() cancel() }() for uplink := range ch { @@ -138,7 +138,7 @@ func (s *BrokerStreamServer) Publish(stream Broker_PublishServer) error { return err } if err := downlink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Downlink")) + return errors.Wrap(err, "Invalid Downlink") } ch <- downlink } diff --git a/api/router/server_streams.go b/api/router/server_streams.go index 259151856..f4571301c 100644 --- a/api/router/server_streams.go +++ b/api/router/server_streams.go @@ -60,7 +60,7 @@ func (s *RouterStreamServer) Uplink(stream Router_UplinkServer) (err error) { return err } if err := uplink.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + return errors.Wrap(err, "Invalid Uplink") } ch <- uplink } @@ -78,7 +78,7 @@ func (s *RouterStreamServer) Subscribe(req *SubscribeRequest, stream Router_Subs } go func() { <-stream.Context().Done() - err = errors.BuildGRPCError(stream.Context().Err()) + err = stream.Context().Err() cancel() }() for downlink := range ch { @@ -111,7 +111,7 @@ func (s *RouterStreamServer) GatewayStatus(stream Router_GatewayStatusServer) er return err } if err := status.Validate(); err != nil { - return errors.BuildGRPCError(errors.Wrap(err, "Invalid Gateway Status")) + return errors.Wrap(err, "Invalid Gateway Status") } ch <- status } diff --git a/core/broker/activation.go b/core/broker/activation.go index 3bd0d6f52..626617ce7 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -25,13 +25,14 @@ type challengeResponseWithHandler struct { response *pb.ActivationChallengeResponse } -func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { +var errDuplicateActivation = errors.New("Not handling duplicate activation on this gateway") + +func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { ctx := b.Ctx.WithFields(log.Fields{ "GatewayID": activation.GatewayMetadata.GatewayId, "AppEUI": *activation.AppEui, "DevEUI": *activation.DevEui, }) - var err error start := time.Now() defer func() { if err != nil { @@ -46,8 +47,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) if len(duplicates) == 0 { - err = errors.NewErrInternal("No duplicates") - return nil, err + return nil, errDuplicateActivation } base := duplicates[0] @@ -83,8 +83,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // Send Activate to NS deduplicatedActivationRequest, err = b.ns.PrepareActivation(b.Component.GetContext(b.nsToken), deduplicatedActivationRequest) if err != nil { - err = errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused to prepare activation") - return nil, err + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused to prepare activation") } ctx = ctx.WithFields(log.Fields{ @@ -99,8 +98,7 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D return nil, err } if len(announcements) == 0 { - err = errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) - return nil, err + return nil, errors.NewErrNotFound(fmt.Sprintf("Handler for AppID %s", deduplicatedActivationRequest.AppId)) } ctx = ctx.WithField("NumHandlers", len(announcements)) @@ -184,31 +182,27 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (*pb.D // Activation not accepted by any broker if !gotFirst { ctx.Debug("Activation not accepted by any Handler") - err = errors.New("Activation not accepted by any Handler") - return nil, err + return nil, errors.New("Activation not accepted by any Handler") } ctx.WithField("HandlerID", joinHandler.Id).Debug("Forward Activation") - var handlerResponse *pb_handler.DeviceActivationResponse - handlerResponse, err = joinHandlerClient.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) + handlerResponse, err := joinHandlerClient.Activate(b.Component.GetContext(""), deduplicatedActivationRequest) if err != nil { - err = errors.Wrap(errors.FromGRPCError(err), "Handler refused activation") - return nil, err + return nil, errors.Wrap(errors.FromGRPCError(err), "Handler refused activation") } - handlerResponse, err = b.ns.Activate(b.Component.GetContext(b.nsToken), handlerResponse) if err != nil { - err = errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused activation") - return nil, err + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer refused activation") } - deviceActivationResponse = &pb.DeviceActivationResponse{ + res = &pb.DeviceActivationResponse{ Payload: handlerResponse.Payload, + Message: handlerResponse.Message, DownlinkOption: handlerResponse.DownlinkOption, } - return deviceActivationResponse, nil + return res, nil } func (b *broker) deduplicateActivation(duplicate *pb.DeviceActivationRequest) (activations []*pb.DeviceActivationRequest) { diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 503b1cc70..b8476fea8 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -44,7 +44,7 @@ func (b *brokerManager) GetDevice(ctx context.Context, in *lorawan.DeviceIdentif } res, err := b.deviceManager.GetDevice(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return device") } return res, nil } @@ -55,7 +55,7 @@ func (b *brokerManager) SetDevice(ctx context.Context, in *lorawan.Device) (*emp } res, err := b.deviceManager.SetDevice(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not set device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not set device") } return res, nil } @@ -66,7 +66,7 @@ func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIden } res, err := b.deviceManager.DeleteDevice(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not delete device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not delete device") } return res, nil } @@ -74,18 +74,18 @@ func (b *brokerManager) DeleteDevice(ctx context.Context, in *lorawan.DeviceIden func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.ApplicationHandlerRegistration) (*empty.Empty, error) { claims, err := b.broker.Component.ValidateTTNAuthContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(errors.FromGRPCError(err)) + return nil, err } if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Handler Registration")) + return nil, errors.Wrap(err, "Invalid Application Handler Registration") } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied("No access to this application")) + return nil, errors.NewErrPermissionDenied("No access to this application") } // Add Handler in local cache handler, err := b.broker.Discovery.Get("handler", in.HandlerId) if err != nil { - return nil, errors.BuildGRPCError(errors.NewErrInternal("Could not get Handler Announcement")) + return nil, errors.NewErrInternal("Could not get Handler Announcement") } handler.AddMetadata(discovery.Metadata_APP_ID, []byte(in.AppId)) return &empty.Empty{}, nil @@ -94,7 +94,7 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesRequest) (*lorawan.PrefixesResponse, error) { res, err := b.devAddrManager.GetPrefixes(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return prefixes")) + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return prefixes") } return res, nil } @@ -102,7 +102,7 @@ func (b *brokerManager) GetPrefixes(ctx context.Context, in *lorawan.PrefixesReq func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrRequest) (*lorawan.DevAddrResponse, error) { res, err := b.devAddrManager.GetDevAddr(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return DevAddr")) + return nil, errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return DevAddr") } return res, nil } diff --git a/core/broker/server.go b/core/broker/server.go index dc30e7ab9..f7d66dffb 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -13,6 +13,7 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) @@ -28,11 +29,11 @@ func (b *brokerRPC) associateRouter(md metadata.MD) (chan *pb.UplinkMessage, <-c ctx := metadata.NewContext(context.Background(), md) router, err := b.broker.ValidateNetworkContext(ctx) if err != nil { - return nil, nil, nil, errors.BuildGRPCError(err) + return nil, nil, nil, err } down, err := b.broker.ActivateRouter(router.Id) if err != nil { - return nil, nil, nil, errors.BuildGRPCError(err) + return nil, nil, nil, err } up := make(chan *pb.UplinkMessage, 1) @@ -58,12 +59,12 @@ func (b *brokerRPC) getHandlerSubscribe(md metadata.MD) (<-chan *pb.Deduplicated ctx := metadata.NewContext(context.Background(), md) handler, err := b.broker.ValidateNetworkContext(ctx) if err != nil { - return nil, nil, errors.BuildGRPCError(err) + return nil, nil, err } ch, err := b.broker.ActivateHandler(handler.Id) if err != nil { - return nil, nil, errors.BuildGRPCError(err) + return nil, nil, err } cancel := func() { @@ -77,7 +78,7 @@ func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, ctx := metadata.NewContext(context.Background(), md) handler, err := b.broker.ValidateNetworkContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } ch := make(chan *pb.DownlinkMessage, 1) @@ -113,14 +114,17 @@ func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (res *pb.DeviceActivationResponse, err error) { _, err = b.broker.ValidateNetworkContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := req.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) + return nil, errors.Wrap(err, "Invalid Activation Request") } res, err = b.broker.HandleActivation(req) + if err == errDuplicateActivation { + return nil, grpc.Errorf(codes.OutOfRange, err.Error()) + } if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return } diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 62150abf6..94a585b8d 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -74,7 +74,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { FCnt: macPayload.FHDR.FCnt, }) if err != nil { - return errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices")) + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices") } if len(getDevicesResp.Results) == 0 { return errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) @@ -182,7 +182,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { // Pass Uplink through NS deduplicatedUplink, err = b.ns.Uplink(b.Component.GetContext(b.nsToken), deduplicatedUplink) if err != nil { - return errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle uplink")) + return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle uplink") } var announcements []*pb_discovery.Announcement diff --git a/core/component/grpc.go b/core/component/grpc.go index 7cc3a444a..8ea5e2140 100644 --- a/core/component/grpc.go +++ b/core/component/grpc.go @@ -1,9 +1,9 @@ package component import ( - "io" "time" + "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/apex/log" "github.com/mwitkow/go-grpc-middleware" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" @@ -36,11 +36,12 @@ func (c *Component) ServerOptions() []grpc.ServerOption { }) t := time.Now() iface, err := handler(ctx, req) + err = errors.BuildGRPCError(err) logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) - if err != nil { - logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Warn("Could not handle Request") - } else { + if grpc.Code(err) == codes.OK || grpc.Code(err) == codes.Canceled { logCtx.Debug("Handled request") + } else { + logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Debug("Handled request with error") } return iface, err } @@ -67,12 +68,12 @@ func (c *Component) ServerOptions() []grpc.ServerOption { t := time.Now() logCtx.Debug("Start stream") err := handler(srv, stream) + err = errors.BuildGRPCError(err) logCtx = logCtx.WithField("Duration", time.Now().Sub(t)) - switch { - case err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled: + if grpc.Code(err) == codes.OK || grpc.Code(err) == codes.Canceled { logCtx.Debug("End stream") - default: - logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Warn("End stream") + } else { + logCtx.WithField("ErrCode", grpc.Code(err)).WithError(err).Debug("End stream with error") } return err } diff --git a/core/discovery/server.go b/core/discovery/server.go index 6b9326012..e6fac3a0d 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -19,7 +19,7 @@ type discoveryServer struct { } func errPermissionDeniedf(format string, args ...string) error { - return errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args))) + return errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args)) } func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { @@ -100,7 +100,7 @@ func (d *discoveryServer) Announce(ctx context.Context, announcement *pb.Announc announcement.Metadata = []*pb.Metadata{} // This will be taken from existing announcement err = d.discovery.Announce(&announcementCopy) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil } @@ -112,7 +112,7 @@ func (d *discoveryServer) AddMetadata(ctx context.Context, in *pb.MetadataReques } err = d.discovery.AddMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil } @@ -124,7 +124,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq } err = d.discovery.DeleteMetadata(in.ServiceName, in.Id, in.Metadata) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil } @@ -132,7 +132,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { services, err := d.discovery.GetAll(req.ServiceName) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &pb.AnnouncementsResponse{ Services: services, @@ -142,7 +142,7 @@ func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*p func (d *discoveryServer) Get(ctx context.Context, req *pb.GetRequest) (*pb.Announcement, error) { service, err := d.discovery.Get(req.ServiceName, req.Id) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return service, nil } diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index e0e4249a0..3bf5589f1 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -67,20 +67,20 @@ func (h *handlerManager) validateTTNAuthAppContext(ctx context.Context, appID st func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) (*pb.Device, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device Identifier")) + return nil, errors.Wrap(err, "Invalid Device Identifier") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.Devices) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } nsDev, err := h.deviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ @@ -88,7 +88,7 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) DevEui: &dev.DevEUI, }) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not return device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not return device") } return &pb.Device{ @@ -114,25 +114,25 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device")) + return nil, errors.Wrap(err, "Invalid Device") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.Devices) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil && errors.GetErrType(err) != errors.NotFound { - return nil, errors.BuildGRPCError(err) + return nil, err } lorawan := in.GetLorawanDevice() if lorawan == nil { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Device", "No LoRaWAN Device")) + return nil, errors.NewErrInvalidArgument("Device", "No LoRaWAN Device") } if dev != nil { // When this is an update @@ -143,7 +143,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E DevEui: &dev.DevEUI, }) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") } } dev.StartUpdate() @@ -154,7 +154,7 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E } for _, existingDevice := range existingDevices { if existingDevice.AppEUI == *lorawan.AppEui && existingDevice.DevEUI == *lorawan.DevEui { - return nil, errors.BuildGRPCError(errors.NewErrAlreadyExists("Device with AppEUI and DevEUI")) + return nil, errors.NewErrAlreadyExists("Device with AppEUI and DevEUI") } } dev = new(device.Device) @@ -203,12 +203,12 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E _, err = h.deviceManager.SetDevice(ctx, nsUpdated) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not set device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not set device") } err = h.handler.devices.Set(dev) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil @@ -216,44 +216,44 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifier) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device Identifier")) + return nil, errors.Wrap(err, "Invalid Device Identifier") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.Devices) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") } err = h.handler.devices.Delete(in.AppId, in.DevId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil } func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*pb.DeviceList, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Identifier")) + return nil, errors.Wrap(err, "Invalid Application Identifier") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.Devices) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId))) + return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } res := &pb.DeviceList{Devices: []*pb.Device{}} for _, dev := range devices { @@ -281,14 +281,14 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) } app, err := h.handler.applications.Get(in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &pb.Application{ @@ -302,28 +302,28 @@ func (h *handlerManager) GetApplication(ctx context.Context, in *pb.ApplicationI func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Identifier")) + return nil, errors.Wrap(err, "Invalid Application Identifier") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) } app, err := h.handler.applications.Get(in.AppId) if err != nil && errors.GetErrType(err) != errors.NotFound { - return nil, errors.BuildGRPCError(err) + return nil, err } if app != nil { - return nil, errors.BuildGRPCError(errors.NewErrAlreadyExists("Application")) + return nil, errors.NewErrAlreadyExists("Application") } err = h.handler.applications.Set(&application.Application{ AppID: in.AppId, }) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } md, _ := metadata.FromContext(ctx) @@ -347,18 +347,18 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application")) + return nil, errors.Wrap(err, "Invalid Application") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) } app, err := h.handler.applications.Get(in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } app.StartUpdate() @@ -370,7 +370,7 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) err = h.handler.applications.Set(app) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil @@ -378,40 +378,40 @@ func (h *handlerManager) SetApplication(ctx context.Context, in *pb.Application) func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.ApplicationIdentifier) (*empty.Empty, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Application Identifier")) + return nil, errors.Wrap(err, "Invalid Application Identifier") } ctx, claims, err := h.validateTTNAuthAppContext(ctx, in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if !claims.AppRight(in.AppId, rights.AppSettings) { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied(`No "settings" rights to application`)) + return nil, errors.NewErrPermissionDenied(`No "settings" rights to application`) } _, err = h.handler.applications.Get(in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } // Get and delete all devices for this application devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } for _, dev := range devices { _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") } err = h.handler.devices.Delete(dev.AppID, dev.DevID) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } } // Delete the Application err = h.handler.applications.Delete(in.AppId) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } md, _ := metadata.FromContext(ctx) @@ -427,7 +427,7 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.PrefixesRequest) (*pb_lorawan.PrefixesResponse, error) { res, err := h.devAddrManager.GetPrefixes(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not return prefixes")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not return prefixes") } return res, nil } @@ -435,7 +435,7 @@ func (h *handlerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.Prefixe func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { res, err := h.devAddrManager.GetDevAddr(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(errors.FromGRPCError(err), "Broker did not return DevAddr")) + return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not return DevAddr") } return res, nil } diff --git a/core/handler/server.go b/core/handler/server.go index 75d31b3da..0b962d0aa 100644 --- a/core/handler/server.go +++ b/core/handler/server.go @@ -18,14 +18,14 @@ type handlerRPC struct { func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_broker.ActivationChallengeRequest) (*pb_broker.ActivationChallengeResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := challenge.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Challenge Request")) + return nil, errors.Wrap(err, "Invalid Activation Challenge Request") } res, err := h.handler.HandleActivationChallenge(challenge) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } @@ -33,14 +33,14 @@ func (h *handlerRPC) ActivationChallenge(ctx context.Context, challenge *pb_brok func (h *handlerRPC) Activate(ctx context.Context, activation *pb_broker.DeduplicatedDeviceActivationRequest) (*pb.DeviceActivationResponse, error) { _, err := h.handler.ValidateNetworkContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := activation.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) + return nil, errors.Wrap(err, "Invalid Activation Request") } res, err := h.handler.HandleActivation(activation) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index 448c369aa..fc8d93a14 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -26,7 +26,7 @@ type networkServerManager struct { func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*device.Device, error) { if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device Identifier")) + return nil, errors.Wrap(err, "Invalid Device Identifier") } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) if err != nil { @@ -48,7 +48,7 @@ func (n *networkServerManager) getDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*pb_lorawan.Device, error) { dev, err := n.getDevice(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } lastSeen := time.Unix(0, 0) @@ -74,11 +74,11 @@ func (n *networkServerManager) GetDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Device) (*empty.Empty, error) { dev, err := n.getDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: in.AppEui, DevEui: in.DevEui}) if err != nil && errors.GetErrType(err) != errors.NotFound { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := in.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Device")) + return nil, errors.Wrap(err, "Invalid Device") } claims, err := n.networkServer.Component.ValidateTTNAuthContext(ctx) @@ -115,7 +115,7 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev err = n.networkServer.devices.Set(dev) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil @@ -124,11 +124,11 @@ func (n *networkServerManager) SetDevice(ctx context.Context, in *pb_lorawan.Dev func (n *networkServerManager) DeleteDevice(ctx context.Context, in *pb_lorawan.DeviceIdentifier) (*empty.Empty, error) { _, err := n.getDevice(ctx, in) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } err = n.networkServer.devices.Delete(*in.AppEui, *in.DevEui) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &empty.Empty{}, nil } @@ -149,7 +149,7 @@ func (n *networkServerManager) GetPrefixes(ctx context.Context, in *pb_lorawan.P func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrRequest) (*pb_lorawan.DevAddrResponse, error) { devAddr, err := n.networkServer.getDevAddr(in.Usage...) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return &pb_lorawan.DevAddrResponse{ DevAddr: &devAddr, diff --git a/core/networkserver/server.go b/core/networkserver/server.go index 01a1f4f68..d4d90b0de 100644 --- a/core/networkserver/server.go +++ b/core/networkserver/server.go @@ -50,70 +50,70 @@ func (s *networkServerRPC) ValidateContext(ctx context.Context) error { func (s *networkServerRPC) GetDevices(ctx context.Context, req *pb.DevicesRequest) (*pb.DevicesResponse, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := req.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Devices Request")) + return nil, errors.Wrap(err, "Invalid Devices Request") } res, err := s.networkServer.HandleGetDevices(req) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } func (s *networkServerRPC) PrepareActivation(ctx context.Context, activation *broker.DeduplicatedDeviceActivationRequest) (*broker.DeduplicatedDeviceActivationRequest, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := activation.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) + return nil, errors.Wrap(err, "Invalid Activation Request") } res, err := s.networkServer.HandlePrepareActivation(activation) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } func (s *networkServerRPC) Activate(ctx context.Context, activation *handler.DeviceActivationResponse) (*handler.DeviceActivationResponse, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := activation.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) + return nil, errors.Wrap(err, "Invalid Activation Request") } res, err := s.networkServer.HandleActivate(activation) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } func (s *networkServerRPC) Uplink(ctx context.Context, message *broker.DeduplicatedUplinkMessage) (*broker.DeduplicatedUplinkMessage, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := message.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Uplink")) + return nil, errors.Wrap(err, "Invalid Uplink") } res, err := s.networkServer.HandleUplink(message) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } func (s *networkServerRPC) Downlink(ctx context.Context, message *broker.DownlinkMessage) (*broker.DownlinkMessage, error) { if err := s.ValidateContext(ctx); err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := message.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Downlink")) + return nil, errors.Wrap(err, "Invalid Downlink") } res, err := s.networkServer.HandleDownlink(message) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } return res, nil } diff --git a/core/router/activation.go b/core/router/activation.go index 9eaab6fc7..f3d842f50 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -135,6 +135,7 @@ func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivat gotFirst = true downlink := &pb_broker.DownlinkMessage{ Payload: res.Payload, + Message: res.Message, DownlinkOption: res.DownlinkOption, } err := r.HandleDownlink(downlink) diff --git a/core/router/manager_server.go b/core/router/manager_server.go index c6c69338e..9d384559e 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -19,17 +19,17 @@ type routerManager struct { func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusRequest) (*pb.GatewayStatusResponse, error) { if in.GatewayId == "" { - return nil, errors.BuildGRPCError(errors.NewErrInvalidArgument("Gateway Status Request", "ID is required")) + return nil, errors.NewErrInvalidArgument("Gateway Status Request", "ID is required") } _, err := r.router.ValidateTTNAuthContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(errors.NewErrPermissionDenied("No access")) + return nil, errors.NewErrPermissionDenied("No access") } r.router.gatewaysLock.RLock() gtw, ok := r.router.gateways[in.GatewayId] r.router.gatewaysLock.RUnlock() if !ok { - return nil, errors.BuildGRPCError(errors.NewErrNotFound(fmt.Sprintf("Gateway %s", in.GatewayId))) + return nil, errors.NewErrNotFound(fmt.Sprintf("Gateway %s", in.GatewayId)) } status, err := gtw.Status.Get() if err != nil { diff --git a/core/router/server.go b/core/router/server.go index 789a05523..80b0812aa 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -124,10 +124,10 @@ func (r *routerRPC) getDownlink(md metadata.MD) (ch <-chan *pb.DownlinkMessage, func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) { gateway, err := r.gatewayFromContext(ctx) if err != nil { - return nil, errors.BuildGRPCError(err) + return nil, err } if err := req.Validate(); err != nil { - return nil, errors.BuildGRPCError(errors.Wrap(err, "Invalid Activation Request")) + return nil, errors.Wrap(err, "Invalid Activation Request") } if r.uplinkRate.Limit(gateway.ID) { return nil, grpc.Errorf(codes.ResourceExhausted, "Gateway reached uplink rate limit") diff --git a/core/router/uplink.go b/core/router/uplink.go index 0dd079fbd..b58f59d74 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -42,14 +42,14 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err e "DevEUI": devEUI, "AppEUI": appEUI, }).Debug("Handle Uplink as Activation") - _, err := r.HandleActivation(gatewayID, &pb.DeviceActivationRequest{ + r.HandleActivation(gatewayID, &pb.DeviceActivationRequest{ Payload: uplink.Payload, DevEui: &devEUI, AppEui: &appEUI, ProtocolMetadata: uplink.ProtocolMetadata, GatewayMetadata: uplink.GatewayMetadata, }) - return err + return nil } if lorawan := uplink.ProtocolMetadata.GetLorawan(); lorawan != nil { diff --git a/utils/errors/errors.go b/utils/errors/errors.go index 944838a9d..7e02ed2cb 100644 --- a/utils/errors/errors.go +++ b/utils/errors/errors.go @@ -63,8 +63,11 @@ func BuildGRPCError(err error) error { case *ErrPermissionDenied: code = codes.PermissionDenied } - if err == context.Canceled { + switch err { + case context.Canceled: code = codes.Canceled + case io.EOF: + code = codes.OutOfRange } return grpc.Errorf(code, err.Error()) } From d22bebc22039dce761ed0472f03c59643384362e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 10:53:57 +0100 Subject: [PATCH 2218/2266] Improve ttnctl uplink and join cmds - Wait after opening downlink stream - Check for closed channel in downlink receive --- ttnctl/cmd/join.go | 7 ++++++- ttnctl/cmd/uplink.go | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index 1fe1092a0..da4fbbccd 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -72,6 +72,7 @@ var joinCmd = &cobra.Command{ downlinkStream := router.NewMonitoredDownlinkStream(gtwClient) defer downlinkStream.Close() + time.Sleep(100 * time.Millisecond) joinReq := &pb_lorawan.Message{ MHDR: pb_lorawan.MHDR{MType: pb_lorawan.MType_JOIN_REQUEST, Major: pb_lorawan.Major_LORAWAN_R1}, @@ -104,7 +105,11 @@ var joinCmd = &cobra.Command{ ctx.Info("Sent uplink to Router") select { - case downlinkMessage := <-downlinkStream.Channel(): + case downlinkMessage, ok := <-downlinkStream.Channel(): + if !ok { + ctx.Info("Did not receive downlink") + break + } downlinkMessage.UnmarshalPayload() resPhy := downlinkMessage.Message.GetLorawan().PHYPayload() resPhy.DecryptJoinAcceptPayload(lorawan.AES128Key(appKey)) diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 9bc65243c..5d89ca049 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -83,6 +83,7 @@ var uplinkCmd = &cobra.Command{ if withDownlink { downlinkStream = router.NewMonitoredDownlinkStream(gtwClient) defer downlinkStream.Close() + time.Sleep(100 * time.Millisecond) } m := &util.Message{} @@ -108,7 +109,11 @@ var uplinkCmd = &cobra.Command{ if downlinkStream != nil { select { - case downlinkMessage := <-downlinkStream.Channel(): + case downlinkMessage, ok := <-downlinkStream.Channel(): + if !ok { + ctx.Info("Did not receive downlink") + break + } if err := m.Unmarshal(downlinkMessage.Payload); err != nil { ctx.WithError(err).Fatal("Could not unmarshal downlink") } From 8047aab2855d953445acb936b33123da1352400f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 10:54:38 +0100 Subject: [PATCH 2219/2266] Remove fmt.Print leftovers --- api/ratelimit/ratelimit.go | 2 -- mqtt/uplink.go | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/api/ratelimit/ratelimit.go b/api/ratelimit/ratelimit.go index bddb87f25..efb476f02 100644 --- a/api/ratelimit/ratelimit.go +++ b/api/ratelimit/ratelimit.go @@ -1,7 +1,6 @@ package ratelimit import ( - "fmt" "sync" "time" @@ -40,7 +39,6 @@ func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.Bucket) * } func (r *Registry) newFunc() *ratelimit.Bucket { - fmt.Println(r.per, r.rate) return ratelimit.NewBucket(r.per, int64(r.rate)) } diff --git a/mqtt/uplink.go b/mqtt/uplink.go index 9ca398134..4e4b5e4a4 100644 --- a/mqtt/uplink.go +++ b/mqtt/uplink.go @@ -42,7 +42,7 @@ func (c *DefaultClient) PublishUplinkFields(appID string, devID string, fields m for _, token := range tokens { token.Wait() if token.Error() != nil { - fmt.Println(token.Error()) + c.ctx.Warnf("Error publishing uplink fields: %s", token.Error().Error()) t.err = token.Error() } } From a388a318fdf6b7f144d737d47673e394417a74c3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 11:16:55 +0100 Subject: [PATCH 2220/2266] Update vendors --- vendor/vendor.json | 490 +++++++++++++++++++++------------------------ 1 file changed, 224 insertions(+), 266 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 95e73a4f5..d2c9ffe76 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,10 +3,10 @@ "ignore": "test", "package": [ { - "checksumSHA1": "yRNw/uu/4N7XHVe1PHtKPl6xtDg=", + "checksumSHA1": "jRtYpPa7CRuA+LP4ELF9c9CjJao=", "path": "github.com/Sirupsen/logrus", - "revision": "1445b7a38228c041834afc69231b7966b9943397", - "revisionTime": "2016-11-08T19:08:11Z" + "revision": "e400ff7861bce9661cf37c162ce3b7b303baf333", + "revisionTime": "2016-11-28T22:57:24Z" }, { "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", @@ -15,70 +15,70 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "joWAlgyzANmhca8jS0qTN6J16ZU=", + "checksumSHA1": "ICp8dlJ+2nmD5G2IW5VktDKNNbE=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { - "checksumSHA1": "8jECnauAWMYPUg48QaAFsLgB6n4=", + "checksumSHA1": "isNnBVNlGIIAMzgvJtrg59ky+uI=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { - "checksumSHA1": "7bEBiMFJqABXsCSR/iCRLteZIa8=", + "checksumSHA1": "oZso6YaE7ogZLUZzUCA3yYDym0E=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "iw9nN70RU1HK4VbosXcZp290WMg=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "c810617a21a7f3ff6241005747da7c8922caeaff", - "revisionTime": "2016-11-07T18:43:25Z" + "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", + "revisionTime": "2016-11-29T09:14:58Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", @@ -167,14 +167,14 @@ { "checksumSHA1": "wDZdTaY9JiqqqnF4c3pHP71nWmk=", "path": "github.com/go-ole/go-ole", - "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", - "revisionTime": "2016-07-29T03:38:29Z" + "revision": "5e9c030faf78847db7aa77e3661b9cc449de29b7", + "revisionTime": "2016-11-16T06:46:58Z" }, { - "checksumSHA1": "qLYVTQDhgrVIeZ2KI9eZV51mmug=", + "checksumSHA1": "IvdiJE1NIogRmGi3WmteEKZQJB8=", "path": "github.com/go-ole/go-ole/oleutil", - "revision": "7dfdcf409020452e29b4babcbb22f984d2aa308a", - "revisionTime": "2016-07-29T03:38:29Z" + "revision": "5e9c030faf78847db7aa77e3661b9cc449de29b7", + "revisionTime": "2016-11-16T06:46:58Z" }, { "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", @@ -201,40 +201,40 @@ "revisionTime": "2016-01-21T18:51:14Z" }, { - "checksumSHA1": "+HPilCYNEcR4B/Q13LiW3OF3i64=", + "checksumSHA1": "APDDi2ohrU7OkChQCekD9tSVUhs=", "path": "github.com/golang/protobuf/jsonpb", - "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", - "revisionTime": "2016-11-03T22:44:32Z" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { - "checksumSHA1": "oJGkod8y/3GTbKfazzQM+nQOPR0=", + "checksumSHA1": "kBeNcaKk56FguvPSUCEaH6AxpRc=", "path": "github.com/golang/protobuf/proto", - "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", - "revisionTime": "2016-11-03T22:44:32Z" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { - "checksumSHA1": "juNiTc9bfhQYo4BkWc83sW4Z5gw=", + "checksumSHA1": "AjyXQ5eohrCPS/jSWZFPn5E8wnQ=", "path": "github.com/golang/protobuf/protoc-gen-go/descriptor", - "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", - "revisionTime": "2016-11-03T22:44:32Z" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { - "checksumSHA1": "lPJ5a2uV2CPHch++4zKkJ1au0sw=", + "checksumSHA1": "T/EqMkqzvjQUL1c+yN32kketgfE=", "path": "github.com/golang/protobuf/protoc-gen-go/generator", - "revision": "4bd1920723d7b7c925de087aa32e2187708897f7", - "revisionTime": "2016-11-09T07:27:36Z" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { "checksumSHA1": "zps2+aJoFhpFf2F8TsU9zCGXL2c=", "path": "github.com/golang/protobuf/protoc-gen-go/plugin", - "revision": "4bd1920723d7b7c925de087aa32e2187708897f7", - "revisionTime": "2016-11-09T07:27:36Z" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { "checksumSHA1": "9wOTz0iWfOSTSTmUkoq0WYkiMdY=", "path": "github.com/golang/protobuf/ptypes/empty", - "revision": "2bc9827a78f95c6665b5fe0abd1fd66b496ae2d8", - "revisionTime": "2016-11-03T22:44:32Z" + "revision": "8ee79997227bf9b34611aee7946ae64735e6fd93", + "revisionTime": "2016-11-17T03:31:26Z" }, { "checksumSHA1": "cACEkFM7kIL+NVF6jSJPY2tW4d8=", @@ -255,82 +255,82 @@ "revisionTime": "2016-04-04T20:39:58Z" }, { - "checksumSHA1": "mJuOZXdE9GBhUTHeQ/Wm8U1rR4I=", + "checksumSHA1": "LoEQ+t5UoMm4InaYVPVn0XqHPwA=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime", - "revision": "84398b94e188ee336f307779b57b3aa91af7063c", - "revisionTime": "2016-11-05T22:35:13Z" + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" }, { - "checksumSHA1": "7K3QiKeoZ0tgA8ymV9vicmfA97Q=", + "checksumSHA1": "x396LPNfci/5x8aVJbliQHH11HQ=", "path": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal", - "revision": "84398b94e188ee336f307779b57b3aa91af7063c", - "revisionTime": "2016-11-05T22:35:13Z" + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" }, { - "checksumSHA1": "mYOr8zd2ww05AQ59oq/xdKL+mJo=", + "checksumSHA1": "NCyVGekDqPMTHHK4ZbEDPZeiN2s=", "path": "github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis/google/api", - "revision": "84398b94e188ee336f307779b57b3aa91af7063c", - "revisionTime": "2016-11-05T22:35:13Z" + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" }, { "checksumSHA1": "vqiK5r5dntV7JNZ+ZsGlD0Samos=", "path": "github.com/grpc-ecosystem/grpc-gateway/utilities", - "revision": "84398b94e188ee336f307779b57b3aa91af7063c", - "revisionTime": "2016-11-05T22:35:13Z" + "revision": "199c40a060d1e55508b3b85182ce6f3895ae6302", + "revisionTime": "2016-11-28T00:20:07Z" }, { "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", "path": "github.com/hashicorp/hcl", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "croNloscHsjX87X+4/cKOURf1EY=", "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { - "checksumSHA1": "lgR7PSAZ0RtvAc9OCtCnNsF/x8g=", + "checksumSHA1": "Zz4271B4Kc+rwwK7cbaRv7STfO8=", "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { - "checksumSHA1": "JlZmnzqdmFFyb1+2afLyR3BOE/8=", + "checksumSHA1": "/e0ULfQnGeUKiM1+iMnQhImo62k=", "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "138aCV5n8n7tkGYMsMVQQnnLq+0=", "path": "github.com/hashicorp/hcl/json/parser", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", - "revision": "f74cf8281543a0797d7b4ab7d88e76e7ba125308", - "revisionTime": "2016-11-04T01:42:59Z" + "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", + "revisionTime": "2016-11-22T02:11:24Z" }, { "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", @@ -345,16 +345,16 @@ "revisionTime": "2014-10-17T20:07:13Z" }, { - "checksumSHA1": "YEZ/qI/rjyPhyyjuGI0NysuKIfA=", + "checksumSHA1": "7PLlrIaGI1TKWB96RkizgkTtOtQ=", "path": "github.com/jacobsa/crypto/cmac", - "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", - "revisionTime": "2016-04-10T22:58:39Z" + "revision": "293ce0c192fb4f59cd879b46544922b9ed09a13a", + "revisionTime": "2016-11-11T03:08:13Z" }, { "checksumSHA1": "NBvtX91AEKxFLmj8mwwhXEKl6d0=", "path": "github.com/jacobsa/crypto/common", - "revision": "42daa9d04c68a12aca47231abd9fb7f8aac27ef7", - "revisionTime": "2016-04-10T22:58:39Z" + "revision": "293ce0c192fb4f59cd879b46544922b9ed09a13a", + "revisionTime": "2016-11-11T03:08:13Z" }, { "checksumSHA1": "sKheT5xw89Tbu2Q071FQO27CVmE=", @@ -369,16 +369,10 @@ "revisionTime": "2016-08-11T00:15:26Z" }, { - "checksumSHA1": "KQhA4EQp4Ldwj9nJZnEURlE6aQw=", - "path": "github.com/kr/fs", - "revision": "2788f0dbd16903de03cb8186e5c7d97b69ad387b", - "revisionTime": "2013-11-06T22:25:44Z" - }, - { - "checksumSHA1": "S6PDDQMYaKwLDIP/NsRYb4FRAqQ=", + "checksumSHA1": "506eXGmFfB7mgzbMcsdT/UAXJgI=", "path": "github.com/magiconair/properties", - "revision": "0723e352fa358f9322c938cc2dadda874e9151a9", - "revisionTime": "2016-09-08T09:36:58Z" + "revision": "9c47895dc1ce54302908ab8a43385d1f5df2c11c", + "revisionTime": "2016-11-28T00:34:34Z" }, { "checksumSHA1": "yf185lmVPcvXjLZuPT1s4JzxI18=", @@ -401,8 +395,8 @@ { "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", "path": "github.com/mwitkow/go-grpc-middleware", - "revision": "0664ec3f4e6e08d02f2bf6a62be6900c0b82ea69", - "revisionTime": "2016-09-11T13:13:45Z" + "revision": "591df76e9998aebfb14fc2c4d96ca3216180596c", + "revisionTime": "2016-11-20T07:58:21Z" }, { "checksumSHA1": "8Y05Pz7onrQPcVWW6JStSsYRh6E=", @@ -411,10 +405,10 @@ "revisionTime": "2016-01-24T19:35:03Z" }, { - "checksumSHA1": "CtI2rBQw2rxHNIU+a0rcAf29Sco=", + "checksumSHA1": "jIC6IQtpM2X84AUYISO/V7C9hQs=", "path": "github.com/pelletier/go-toml", - "revision": "45932ad32dfdd20826f5671da37a5f3ce9f26a8d", - "revisionTime": "2016-09-20T07:07:15Z" + "revision": "7cb988051d5045890cb91402a0b5fddc76c627bc", + "revisionTime": "2016-11-23T15:24:52Z" }, { "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", @@ -423,16 +417,10 @@ "revisionTime": "2016-10-29T09:36:37Z" }, { - "checksumSHA1": "k9SlQdp/DTB72G/u4aNecX/fFIg=", - "path": "github.com/pkg/sftp", - "revision": "4d0e916071f68db74f8a73926335f809396d6b42", - "revisionTime": "2016-09-30T22:07:58Z" - }, - { - "checksumSHA1": "GiX6yRUzizn1C+ckgj1xLFLoz8g=", + "checksumSHA1": "KAzbLjI9MzW2tjfcAsK75lVRp6I=", "path": "github.com/rcrowley/go-metrics", - "revision": "ab2277b1c5d15c3cba104e9cbddbdfc622df5ad8", - "revisionTime": "2016-09-21T19:52:07Z" + "revision": "1f30fe9094a513ce4c700b9a54458bbb0c96996c", + "revisionTime": "2016-11-28T21:05:44Z" }, { "checksumSHA1": "5qwv3yDROEz5ZV8HztOBmQxen8c=", @@ -477,46 +465,46 @@ "revisionTime": "2016-10-04T12:49:59Z" }, { - "checksumSHA1": "pkzbHrJg79tEMajgi+7ED9rPVuQ=", + "checksumSHA1": "ujFjoR6H3TDeiuY9kvvwSwBMcJk=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { - "checksumSHA1": "NuQpBEPzjTLUqDW2zxChEBpDAkk=", + "checksumSHA1": "A5OQcD4rTEhWAfWvp6lzFBA9lfs=", "path": "github.com/shirou/gopsutil/host", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { - "checksumSHA1": "UnT7JW8ZDcUVJUYOPBfW6SToB0k=", + "checksumSHA1": "sNAWEDfrq5thpuxsiTvRNJ1fii0=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { - "checksumSHA1": "4I3yYYZEctqf3foI2+CI92lu5pM=", + "checksumSHA1": "jB8En6qWQ7G2yPJey4uY1FvOjWM=", "path": "github.com/shirou/gopsutil/load", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { - "checksumSHA1": "5HYtuEgPkRvE6I8VFQMAN0rbYRU=", + "checksumSHA1": "sOBIj+eocRSO0xtX8vkJDZKTDl8=", "path": "github.com/shirou/gopsutil/mem", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { - "checksumSHA1": "Sxy9qDOEqAa+F2P6rSLKQGZL+l8=", + "checksumSHA1": "DWrRDbT4hDhy1oiYM0My7UJv+Zc=", "path": "github.com/shirou/gopsutil/net", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { - "checksumSHA1": "Cmj97derBOe/m/D2Db++Z57uWBw=", + "checksumSHA1": "+4bS/41wTuGe0oczEwrBCWp6YO8=", "path": "github.com/shirou/gopsutil/process", - "revision": "a63900a44bc14bcd39693da21afe001f57d567d3", - "revisionTime": "2016-10-27T13:29:33Z" + "revision": "061b699a971e0f74834f69ef8b78643f48980d10", + "revisionTime": "2016-11-25T00:04:14Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -525,22 +513,22 @@ "revisionTime": "2016-09-30T03:27:40Z" }, { - "checksumSHA1": "6AYg4fjEvFuAVN3wHakGApjhZAM=", + "checksumSHA1": "8sTogvgjVmUeLd4vWfcARLDBksg=", "path": "github.com/smartystreets/assertions", - "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", - "revisionTime": "2016-07-07T19:03:55Z" + "revision": "e60cfa771e3f4d18723a4119f1833898c9c62066", + "revisionTime": "2016-11-10T22:55:57Z" }, { "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", "path": "github.com/smartystreets/assertions/internal/go-render/render", - "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", - "revisionTime": "2016-07-07T19:03:55Z" + "revision": "e60cfa771e3f4d18723a4119f1833898c9c62066", + "revisionTime": "2016-11-10T22:55:57Z" }, { - "checksumSHA1": "SLC6TfV4icQA9l8YJQu8acJYbuo=", + "checksumSHA1": "dSZQzhiGN0tEILHxUZcrFFNW2Xw=", "path": "github.com/smartystreets/assertions/internal/oglematchers", - "revision": "2063fd1cc7c975db70502811a34b06ad034ccdf2", - "revisionTime": "2016-07-07T19:03:55Z" + "revision": "e60cfa771e3f4d18723a4119f1833898c9c62066", + "revisionTime": "2016-11-10T22:55:57Z" }, { "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", @@ -549,34 +537,28 @@ "revisionTime": "2016-07-22T04:48:03Z" }, { - "checksumSHA1": "59wTbS4fE2282Q88NrBYImbFGbo=", + "checksumSHA1": "f2mjcLDkc28ImTfmedA5kfcOzUw=", "path": "github.com/spf13/afero", - "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", - "revisionTime": "2016-09-19T21:01:14Z" + "revision": "06b7e5f50606ecd49148a01a6008942d9b669217", + "revisionTime": "2016-11-09T00:09:53Z" }, { "checksumSHA1": "u6B0SEgZ/TUEfIvF6w/HnFVQbII=", "path": "github.com/spf13/afero/mem", - "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", - "revisionTime": "2016-09-19T21:01:14Z" + "revision": "06b7e5f50606ecd49148a01a6008942d9b669217", + "revisionTime": "2016-11-09T00:09:53Z" }, { - "checksumSHA1": "sLyAUiIT7V0DNVp6yBhW4Ms5BEs=", - "path": "github.com/spf13/afero/sftp", - "revision": "52e4a6cfac46163658bd4f123c49b6ee7dc75f78", - "revisionTime": "2016-09-19T21:01:14Z" - }, - { - "checksumSHA1": "M4qI7Ul0vf2uj3fF69DvRnwqfd0=", + "checksumSHA1": "+mfjYfgvbP8vg0ubsMOw/iTloo8=", "path": "github.com/spf13/cast", - "revision": "2580bc98dc0e62908119e4737030cc2fdfc45e4c", - "revisionTime": "2016-09-26T08:42:49Z" + "revision": "24b6558033ffe202bf42f0f3b870dcc798dd2ba8", + "revisionTime": "2016-11-16T01:33:54Z" }, { "checksumSHA1": "FZ0r4TzEy9UxXLkFVXFygApni4M=", "path": "github.com/spf13/cobra", - "revision": "6e91dded25d73176bf7f60b40dd7aa1f0bf9be8d", - "revisionTime": "2016-10-26T01:28:26Z" + "revision": "9495bc009a56819bdb0ddbc1a373e29c140bc674", + "revisionTime": "2016-11-16T13:20:53Z" }, { "checksumSHA1": "dkruahfhuLXXuyeCuRpsWlcRK+8=", @@ -597,10 +579,10 @@ "revisionTime": "2016-10-29T21:33:52Z" }, { - "checksumSHA1": "rKV8YLkXpeNG1Oix8hlYqVsEFb4=", + "checksumSHA1": "KLtNzjyzIFsTTMvM2WdHGzhdbXQ=", "path": "github.com/streadway/amqp", - "revision": "2e25825abdbd7752ff08b270d313b93519a0a232", - "revisionTime": "2016-03-11T21:55:03Z" + "revision": "1b8853833a94be8ba57fd121a8651f7221e5b051", + "revisionTime": "2016-11-28T15:24:32Z" }, { "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", @@ -620,233 +602,209 @@ "revision": "9a9a2a21e071e6e38f236740c3b650e7316ae67e", "revisionTime": "2016-06-07T20:24:39Z" }, - { - "checksumSHA1": "dwOedwBJ1EIK9+S3t108Bx054Y8=", - "path": "golang.org/x/crypto/curve25519", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", - "revisionTime": "2016-10-31T15:37:30Z" - }, - { - "checksumSHA1": "wGb//LjBPNxYHqk+dcLo7BjPXK8=", - "path": "golang.org/x/crypto/ed25519", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", - "revisionTime": "2016-10-31T15:37:30Z" - }, - { - "checksumSHA1": "LXFcVx8I587SnWmKycSDEq9yvK8=", - "path": "golang.org/x/crypto/ed25519/internal/edwards25519", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", - "revisionTime": "2016-10-31T15:37:30Z" - }, - { - "checksumSHA1": "LlElMHeTC34ng8eHzjvtUhAgrr8=", - "path": "golang.org/x/crypto/ssh", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", - "revisionTime": "2016-10-31T15:37:30Z" - }, { "checksumSHA1": "9C4Av3ypK5pi173F76ogJT/d8x4=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "9477e0b78b9ac3d0b03822fd95422e2fe07627cd", - "revisionTime": "2016-10-31T15:37:30Z" + "revision": "ede567c8e044a5913dad1d1af3696d9da953104c", + "revisionTime": "2016-11-04T19:41:44Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { - "checksumSHA1": "a7Jc+Pyru34/ZKm3fw8bYh7TKa4=", + "checksumSHA1": "pLsZUQhI8jm3W9R/4JO9D/L1cUA=", "path": "golang.org/x/net/http2", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { - "checksumSHA1": "4MMbG0LI3ghvWooRn36RmDrFIB0=", + "checksumSHA1": "P9qTIn8a6L6Q9wd1IJBCuhno1Q8=", "path": "golang.org/x/net/trace", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { "checksumSHA1": "O2sws+miRriMPObINsbwa0jnXxE=", "path": "golang.org/x/net/websocket", - "revision": "55a3084c9119aeb9ba2437d595b0a7e9cb635da9", - "revisionTime": "2016-11-04T22:18:50Z" + "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", + "revisionTime": "2016-11-15T21:05:04Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", - "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", - "revisionTime": "2016-06-06T21:15:38Z" + "revision": "d5040cddfc0da40b408c9a1da4728662435176a9", + "revisionTime": "2016-11-03T22:50:36Z" }, { "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", "path": "golang.org/x/oauth2/internal", - "revision": "36bc61733fc9aafc59f010d5e6bfefb63b007fd7", - "revisionTime": "2016-06-06T21:15:38Z" + "revision": "d5040cddfc0da40b408c9a1da4728662435176a9", + "revisionTime": "2016-11-03T22:50:36Z" }, { - "checksumSHA1": "aVgPDgwY3/t4J/JOw9H3FVMHqh0=", + "checksumSHA1": "MlTI84eWAFvqeRgXxBtjRYHk1yQ=", "path": "golang.org/x/sys/unix", - "revision": "c200b10b5d5e122be351b67af224adc6128af5bf", - "revisionTime": "2016-10-22T18:22:21Z" + "revision": "30237cf4eefd639b184d1f2cb77a581ea0be8947", + "revisionTime": "2016-11-19T15:29:01Z" }, { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "a8b38433e35b65ba247bb267317037dee1b70cea", - "revisionTime": "2016-10-19T13:35:53Z" + "revision": "b01949dc0793a9af5e4cb3fce4d42999e76e8ca1", + "revisionTime": "2016-11-03T07:49:12Z" }, { "checksumSHA1": "Vircurgvsnt4k26havmxPM67PUA=", "path": "golang.org/x/text/unicode/norm", - "revision": "a8b38433e35b65ba247bb267317037dee1b70cea", - "revisionTime": "2016-10-19T13:35:53Z" + "revision": "b01949dc0793a9af5e4cb3fce4d42999e76e8ca1", + "revisionTime": "2016-11-03T07:49:12Z" }, { - "checksumSHA1": "vIZ71Qe81RHec1vNHpKG+CSx/es=", + "checksumSHA1": "gYHoPrPncGO926bN0jr1rzDxBQU=", "path": "google.golang.org/appengine/internal", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "TsNO8P0xUlLNyh3Ic/tzSp/fDWM=", "path": "google.golang.org/appengine/internal/base", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "5QsV5oLGSfKZqTCVXP6NRz5T4Tw=", "path": "google.golang.org/appengine/internal/datastore", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "Gep2T9zmVYV8qZfK2gu3zrmG6QE=", "path": "google.golang.org/appengine/internal/log", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "a1XY7rz3BieOVqVI2Et6rKiwQCk=", "path": "google.golang.org/appengine/internal/remote_api", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "QtAbHtHmDzcf6vOV9eqlCpKgjiw=", "path": "google.golang.org/appengine/internal/urlfetch", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "akOV9pYnCbcPA8wJUutSQVibdyg=", "path": "google.golang.org/appengine/urlfetch", - "revision": "46239ca616842c00f41b8cbc6bbf2bd6ffbfcdad", - "revisionTime": "2016-10-25T16:43:32Z" + "revision": "ca59ef35f409df61fa4a5f8290ff289b37eccfb8", + "revisionTime": "2016-11-15T22:01:06Z" }, { "checksumSHA1": "xyB2Py2ViSKX8Td+oe2hxG6f0Ak=", "path": "google.golang.org/grpc", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "d0iunsiWfA0qXxLMNkTC4tGJnOo=", "path": "google.golang.org/grpc/health", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "pSFXzfvPlaDBK2RsMcTiIeks4ok=", "path": "google.golang.org/grpc/health/grpc_health_v1", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "XXpD8+S3gLrfmCLOf+RbxblOQkU=", "path": "google.golang.org/grpc/metadata", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "FCgy+WB249Vt1XEG5pe4Z7plTLs=", "path": "google.golang.org/grpc/stats", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "N0TftT6/CyWqp6VRi2DqDx60+Fo=", "path": "google.golang.org/grpc/tap", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "uVGwQAu6ncFK7qd3BpzbMEJDUP4=", "path": "google.golang.org/grpc/transport", - "revision": "63bd55dfbf781b183216d2dd4433a659c947648a", - "revisionTime": "2016-11-18T23:23:07Z" + "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", + "revisionTime": "2016-11-28T22:26:00Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", @@ -855,40 +813,40 @@ "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "rr5P5knxMTqvnRElnvlrM4dyuQM=", + "checksumSHA1": "ios0gBgesygjqNgXiQq7+sfKW1A=", "path": "gopkg.in/redis.v5", - "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", - "revisionTime": "2016-10-24T09:52:32Z" + "revision": "c9856861674f102a5f51104c36401a3cf691739c", + "revisionTime": "2016-11-19T12:14:48Z" }, { "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", "path": "gopkg.in/redis.v5/internal", - "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", - "revisionTime": "2016-10-24T09:52:32Z" + "revision": "c9856861674f102a5f51104c36401a3cf691739c", + "revisionTime": "2016-11-19T12:14:48Z" }, { "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", "path": "gopkg.in/redis.v5/internal/consistenthash", - "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", - "revisionTime": "2016-10-24T09:52:32Z" + "revision": "c9856861674f102a5f51104c36401a3cf691739c", + "revisionTime": "2016-11-19T12:14:48Z" }, { "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", "path": "gopkg.in/redis.v5/internal/hashtag", - "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", - "revisionTime": "2016-10-24T09:52:32Z" + "revision": "c9856861674f102a5f51104c36401a3cf691739c", + "revisionTime": "2016-11-19T12:14:48Z" }, { "checksumSHA1": "MuVVHw/uzk6gwBsQ8deMYacmgTM=", "path": "gopkg.in/redis.v5/internal/pool", - "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", - "revisionTime": "2016-10-24T09:52:32Z" + "revision": "c9856861674f102a5f51104c36401a3cf691739c", + "revisionTime": "2016-11-19T12:14:48Z" }, { - "checksumSHA1": "Pn4Vc2X2hUhm0SV7j82YlMW8j5o=", + "checksumSHA1": "YyFo2hNsHxZGcTMe/vKsjXHOiOQ=", "path": "gopkg.in/redis.v5/internal/proto", - "revision": "80cf5d1652d5590c35edc6c2dc1aa354790e3010", - "revisionTime": "2016-10-24T09:52:32Z" + "revision": "c9856861674f102a5f51104c36401a3cf691739c", + "revisionTime": "2016-11-19T12:14:48Z" }, { "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", From 54da98f8a101b0152f52b8bd95da4d1813716c63 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 12:40:00 +0100 Subject: [PATCH 2221/2266] Store device options in Handler and re-register to NS if gone Resolves #342 Related to #292 --- core/handler/device/device.go | 48 +++++++++++---- core/handler/device/device_test.go | 7 +++ core/handler/manager_server.go | 94 +++++++++++++++++------------- 3 files changed, 98 insertions(+), 51 deletions(-) diff --git a/core/handler/device/device.go b/core/handler/device/device.go index 41416ef85..349cf56e8 100644 --- a/core/handler/device/device.go +++ b/core/handler/device/device.go @@ -7,6 +7,7 @@ import ( "reflect" "time" + pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/types" "github.com/fatih/structs" ) @@ -14,20 +15,29 @@ import ( type DevNonce [2]byte type AppNonce [3]byte +// Options for the device +type Options struct { + ActivationConstraints string `json:"activation_constraints,omitempty"` // Activation Constraints (public/local/private) + DisableFCntCheck bool `json:"disable_fcnt_check,omitemtpy"` // Disable Frame counter check (insecure) + Uses32BitFCnt bool `json:"uses_32_bit_fcnt,omitemtpy"` // Use 32-bit Frame counters +} + // Device contains the state of a device type Device struct { old *Device - DevEUI types.DevEUI `redis:"dev_eui"` - AppEUI types.AppEUI `redis:"app_eui"` - AppID string `redis:"app_id"` - DevID string `redis:"dev_id"` - DevAddr types.DevAddr `redis:"dev_addr"` - AppKey types.AppKey `redis:"app_key"` - UsedDevNonces []DevNonce `redis:"used_dev_nonces"` - UsedAppNonces []AppNonce `redis:"used_app_nonces"` - NwkSKey types.NwkSKey `redis:"nwk_s_key"` - AppSKey types.AppSKey `redis:"app_s_key"` - NextDownlink *types.DownlinkMessage `redis:"next_downlink"` + DevEUI types.DevEUI `redis:"dev_eui"` + AppEUI types.AppEUI `redis:"app_eui"` + AppID string `redis:"app_id"` + DevID string `redis:"dev_id"` + DevAddr types.DevAddr `redis:"dev_addr"` + AppKey types.AppKey `redis:"app_key"` + UsedDevNonces []DevNonce `redis:"used_dev_nonces"` + UsedAppNonces []AppNonce `redis:"used_app_nonces"` + NwkSKey types.NwkSKey `redis:"nwk_s_key"` + AppSKey types.AppSKey `redis:"app_s_key"` + Options Options `redis:"options"` + + NextDownlink *types.DownlinkMessage `redis:"next_downlink"` CreatedAt time.Time `redis:"created_at"` UpdatedAt time.Time `redis:"updated_at"` @@ -58,3 +68,19 @@ func (d Device) ChangedFields() (changed []string) { } return } + +// GetLoRaWAN returns a LoRaWAN Device proto +func (d Device) GetLoRaWAN() *pb_lorawan.Device { + dev := &pb_lorawan.Device{ + AppId: d.AppID, + DevId: d.DevID, + AppEui: &d.AppEUI, + DevEui: &d.DevEUI, + DevAddr: &d.DevAddr, + NwkSKey: &d.NwkSKey, + DisableFCntCheck: d.Options.DisableFCntCheck, + Uses32BitFCnt: d.Options.Uses32BitFCnt, + ActivationConstraints: d.Options.ActivationConstraints, + } + return dev +} diff --git a/core/handler/device/device_test.go b/core/handler/device/device_test.go index cdaae2951..a8a2c6516 100644 --- a/core/handler/device/device_test.go +++ b/core/handler/device/device_test.go @@ -29,3 +29,10 @@ func TestDeviceChangedFields(t *testing.T) { a.So(device.ChangedFields(), ShouldHaveLength, 1) a.So(device.ChangedFields(), ShouldContain, "DevID") } + +func TestDeviceGetLoRaWAN(t *testing.T) { + device := &Device{ + DevID: "Device", + } + device.GetLoRaWAN() +} diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index 3bf5589f1..a91e2f954 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -18,6 +18,7 @@ import ( "github.com/TheThingsNetwork/ttn/core/handler/application" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/apex/log" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" @@ -83,33 +84,50 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) return nil, err } + pbDev := &pb.Device{ + AppId: dev.AppID, + DevId: dev.DevID, + Device: &pb.Device_LorawanDevice{LorawanDevice: &pb_lorawan.Device{ + AppId: dev.AppID, + AppEui: &dev.AppEUI, + DevId: dev.DevID, + DevEui: &dev.DevEUI, + DevAddr: &dev.DevAddr, + NwkSKey: &dev.NwkSKey, + AppSKey: &dev.AppSKey, + AppKey: &dev.AppKey, + DisableFCntCheck: dev.Options.DisableFCntCheck, + Uses32BitFCnt: dev.Options.Uses32BitFCnt, + ActivationConstraints: dev.Options.ActivationConstraints, + }}, + } + nsDev, err := h.deviceManager.GetDevice(ctx, &pb_lorawan.DeviceIdentifier{ AppEui: &dev.AppEUI, DevEui: &dev.DevEUI, }) - if err != nil { - return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not return device") + if errors.GetErrType(errors.FromGRPCError(err)) == errors.NotFound { + // Re-register the device in the Broker (NetworkServer) + h.handler.Ctx.WithFields(log.Fields{ + "AppID": dev.AppID, + "DevID": dev.DevID, + "AppEUI": dev.AppEUI, + "DevEUI": dev.DevEUI, + }).Warn("Re-registering missing device to Broker") + nsDev = dev.GetLoRaWAN() + _, err = h.deviceManager.SetDevice(ctx, nsDev) + if err != nil { + return nil, errors.Wrap(errors.FromGRPCError(err), "Could not re-register missing device to Broker") + } + } else if err != nil { + return pbDev, errors.Wrap(errors.FromGRPCError(err), "Broker did not return device") } - return &pb.Device{ - AppId: dev.AppID, - DevId: dev.DevID, - Device: &pb.Device_LorawanDevice{LorawanDevice: &pb_lorawan.Device{ - AppId: dev.AppID, - AppEui: nsDev.AppEui, - DevId: dev.DevID, - DevEui: nsDev.DevEui, - DevAddr: nsDev.DevAddr, - NwkSKey: nsDev.NwkSKey, - AppSKey: &dev.AppSKey, - AppKey: &dev.AppKey, - FCntUp: nsDev.FCntUp, - FCntDown: nsDev.FCntDown, - DisableFCntCheck: nsDev.DisableFCntCheck, - Uses32BitFCnt: nsDev.Uses32BitFCnt, - LastSeen: nsDev.LastSeen, - }}, - }, nil + pbDev.GetLorawanDevice().FCntUp = nsDev.FCntUp + pbDev.GetLorawanDevice().FCntDown = nsDev.FCntDown + pbDev.GetLorawanDevice().LastSeen = nsDev.LastSeen + + return pbDev, nil } func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.Empty, error) { @@ -164,6 +182,16 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E dev.AppEUI = *lorawan.AppEui dev.DevID = in.DevId dev.DevEUI = *lorawan.DevEui + + dev.Options = device.Options{ + DisableFCntCheck: lorawan.DisableFCntCheck, + Uses32BitFCnt: lorawan.Uses32BitFCnt, + ActivationConstraints: lorawan.ActivationConstraints, + } + if dev.Options.ActivationConstraints == "" { + dev.Options.ActivationConstraints = "local" + } + if lorawan.DevAddr != nil { dev.DevAddr = *lorawan.DevAddr } @@ -182,24 +210,10 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E dev.AppKey = *lorawan.AppKey } - nsUpdated := &pb_lorawan.Device{ - AppId: in.AppId, - DevId: in.DevId, - AppEui: lorawan.AppEui, - DevEui: lorawan.DevEui, - DevAddr: lorawan.DevAddr, - NwkSKey: lorawan.NwkSKey, - FCntUp: lorawan.FCntUp, - FCntDown: lorawan.FCntDown, - DisableFCntCheck: lorawan.DisableFCntCheck, - Uses32BitFCnt: lorawan.Uses32BitFCnt, - ActivationConstraints: lorawan.ActivationConstraints, - } - - // Devices are activated locally by default - if nsUpdated.ActivationConstraints == "" { - nsUpdated.ActivationConstraints = "local" - } + // Update the device in the Broker (NetworkServer) + nsUpdated := dev.GetLoRaWAN() + nsUpdated.FCntUp = lorawan.FCntUp + nsUpdated.FCntDown = lorawan.FCntDown _, err = h.deviceManager.SetDevice(ctx, nsUpdated) if err != nil { @@ -230,7 +244,7 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifi return nil, err } _, err = h.deviceManager.DeleteDevice(ctx, &pb_lorawan.DeviceIdentifier{AppEui: &dev.AppEUI, DevEui: &dev.DevEUI}) - if err != nil { + if err != nil && errors.GetErrType(errors.FromGRPCError(err)) != errors.NotFound { return nil, errors.Wrap(errors.FromGRPCError(err), "Broker did not delete device") } err = h.handler.devices.Delete(in.AppId, in.DevId) From 9d6003d38bb2d753211139506b21adac34140a16 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 15:54:37 +0100 Subject: [PATCH 2222/2266] Refactor Discovery Metadata to use oneof Note: This is a breaking change in the Discovery API Resolves #367 --- api/discovery/Discovery.md | 49 +- api/discovery/announcement.go | 30 -- api/discovery/announcement_test.go | 29 -- api/discovery/client.go | 108 ++-- api/discovery/client_mock.go | 39 +- api/discovery/discovery.pb.go | 463 ++++++++++++------ api/discovery/discovery.pb.gw.go | 2 +- api/discovery/discovery.proto | 63 ++- api/discovery/metadata.go | 41 ++ cmd/broker_register_prefix.go | 11 +- core/broker/broker.go | 11 +- core/broker/manager_server.go | 4 +- core/broker/server.go | 20 +- core/discovery/announcement/announcement.go | 68 +-- .../announcement/announcement_test.go | 19 +- core/discovery/announcement/cache_test.go | 8 +- core/discovery/announcement/store_test.go | 8 +- core/discovery/discovery_test.go | 18 +- core/discovery/server.go | 80 +-- core/discovery/server_test.go | 2 +- core/handler/manager_server.go | 11 +- core/types/dev_addr.go | 46 ++ core/types/dev_addr_test.go | 28 +- ttnctl/cmd/discover.go | 33 +- utils/protoc-gen-ttndoc/json.go | 4 +- 25 files changed, 687 insertions(+), 508 deletions(-) delete mode 100644 api/discovery/announcement.go delete mode 100644 api/discovery/announcement_test.go create mode 100644 api/discovery/metadata.go diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index 0396ad349..cc90df2fd 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -15,10 +15,10 @@ Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMet ### `GetAll` -Get all announcements for a specific service +Get all announcements for a specific service type -- Request: [`GetAllRequest`](#discoverygetallrequest) -- Response: [`AnnouncementsResponse`](#discoverygetallrequest) +- Request: [`GetServiceRequest`](#discoverygetservicerequest) +- Response: [`AnnouncementsResponse`](#discoverygetservicerequest) #### HTTP Endpoint @@ -44,8 +44,9 @@ Get all announcements for a specific service "id": "ttn-handler-eu", "metadata": [ { - "key": "APP_ID", - "value": "c29tZS1hcHAtaWQ=" + "app_eui": "", + "app_id": "some-app-id", + "dev_addr_prefix": "AAAAAAA=" } ], "net_address": "eu.thethings.network:1904", @@ -89,8 +90,9 @@ Get a specific announcement "id": "ttn-handler-eu", "metadata": [ { - "key": "APP_ID", - "value": "c29tZS1hcHAtaWQ=" + "app_eui": "", + "app_id": "some-app-id", + "dev_addr_prefix": "AAAAAAA=" } ], "net_address": "eu.thethings.network:1904", @@ -144,29 +146,29 @@ A list of announcements | ---------- | ---- | ----------- | | `services` | _repeated_ [`Announcement`](#discoveryannouncement) | | -### `.discovery.GetAllRequest` +### `.discovery.GetRequest` + +The identifier of the service that should be returned | Field Name | Type | Description | | ---------- | ---- | ----------- | +| `id` | `string` | The ID of the service | | `service_name` | `string` | The name of the service (router/broker/handler) | -### `.discovery.GetRequest` - -The identifier of the service that should be returned +### `.discovery.GetServiceRequest` | Field Name | Type | Description | | ---------- | ---- | ----------- | -| `id` | `string` | The ID of the service | | `service_name` | `string` | The name of the service (router/broker/handler) | ### `.discovery.Metadata` -Announcements have a list of Metadata - | Field Name | Type | Description | | ---------- | ---- | ----------- | -| `key` | [`Key`](#discoverymetadatakey) | The key indicates the metadata type | -| `value` | `bytes` | The value depends on the key type | +| **metadata** | **oneof 3** | one of the following 3 | +| `dev_addr_prefix` | `bytes` | DevAddr prefix that is routed by this Broker 5 bytes; the first byte is the prefix length, the following 4 bytes are the address. Only authorized Brokers can announce PREFIX metadata. | +| `app_id` | `string` | AppID that is registered to this Handler This metadata can only be added if the requesting client is authorized to manage this AppID. | +| `app_eui` | `bytes` | AppEUI that is registered to this Join Handler Only authorized Join Handlers can announce APP_EUI metadata (and we don't have any of those yet). | ### `.discovery.MetadataRequest` @@ -176,23 +178,10 @@ The metadata to add or remove from an announement | ---------- | ---- | ----------- | | `id` | `string` | The ID of the service that should be modified | | `service_name` | `string` | The name of the service (router/broker/handler) that should be modified | -| `metadata` | [`Metadata`](#discoverymetadata) | | +| `metadata` | [`Metadata`](#discoverymetadata) | Metadata to add or remove | ### `.google.protobuf.Empty` A generic empty message that you can re-use to avoid defining duplicated empty messages in your APIs. -## Used Enums - -### `.discovery.Metadata.Key` - -The Key indicates the metadata type - -| Value | Description | -| ----- | ----------- | -| `OTHER` | OTHER indicates arbitrary metadata. We currently don't allow this. | -| `PREFIX` | The value for PREFIX consists of 1 byte denoting the number of bits, followed by the prefix and enough trailing bits to fill 4 octets. Only authorized brokers can announce PREFIX metadata. | -| `APP_EUI` | APP_EUI is used for announcing join handlers. The value for APP_EUI is the byte slice of the AppEUI. Only authorized join handlers can announce APP_EUI metadata (and we don't have any of those yet). | -| `APP_ID` | APP_ID is used for announcing that this handler is responsible for a certain AppID. The value for APP_ID is the byte slice of the AppID string. This metadata can only be added if the requesting client is authorized to manage this AppID. | - diff --git a/api/discovery/announcement.go b/api/discovery/announcement.go deleted file mode 100644 index 22affe675..000000000 --- a/api/discovery/announcement.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import "bytes" - -// AddMetadata adds metadata to the announcement if it doesn't exist -func (announcement *Announcement) AddMetadata(key Metadata_Key, value []byte) { - for _, meta := range announcement.Metadata { - if meta.Key == key && bytes.Equal(value, meta.Value) { - return - } - } - announcement.Metadata = append(announcement.Metadata, &Metadata{ - Key: key, - Value: value, - }) -} - -// DeleteMetadata deletes metadata from the announcement if it exists -func (announcement *Announcement) DeleteMetadata(key Metadata_Key, value []byte) { - newMeta := make([]*Metadata, 0, len(announcement.Metadata)) - for _, meta := range announcement.Metadata { - if !(meta.Key == key && bytes.Equal(value, meta.Value)) { - newMeta = append(newMeta, meta) - } - } - announcement.Metadata = newMeta -} diff --git a/api/discovery/announcement_test.go b/api/discovery/announcement_test.go deleted file mode 100644 index 3af298253..000000000 --- a/api/discovery/announcement_test.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package discovery - -import ( - "testing" - - . "github.com/smartystreets/assertions" -) - -func TestAnnouncementAddDeleteMetadata(t *testing.T) { - a := New(t) - announcement := new(Announcement) - - announcement.AddMetadata(Metadata_APP_ID, []byte("app-id")) - a.So(announcement.Metadata, ShouldHaveLength, 1) - a.So(announcement.Metadata[0], ShouldResemble, &Metadata{Key: Metadata_APP_ID, Value: []byte("app-id")}) - - announcement.AddMetadata(Metadata_APP_ID, []byte("app-id")) - a.So(announcement.Metadata, ShouldHaveLength, 1) - - announcement.AddMetadata(Metadata_APP_ID, []byte("other-app-id")) - a.So(announcement.Metadata, ShouldHaveLength, 2) - - announcement.DeleteMetadata(Metadata_APP_ID, []byte("app-id")) - a.So(announcement.Metadata, ShouldHaveLength, 1) - -} diff --git a/api/discovery/client.go b/api/discovery/client.go index 76e5f3de2..29cca2741 100644 --- a/api/discovery/client.go +++ b/api/discovery/client.go @@ -27,9 +27,10 @@ type Client interface { Announce(token string) error GetAll(serviceName string) ([]*Announcement, error) Get(serviceName, id string) (*Announcement, error) - AddMetadata(key Metadata_Key, value []byte, token string) error - DeleteMetadata(key Metadata_Key, value []byte, token string) error - GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func(value []byte) bool) ([]*Announcement, error) + AddDevAddrPrefix(prefix types.DevAddrPrefix) error + AddAppID(appID string, token string) error + RemoveDevAddrPrefix(prefix types.DevAddrPrefix) error + RemoveAppID(appID string, token string) error GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) GetAllHandlersForAppID(appID string) ([]*Announcement, error) Close() error @@ -107,7 +108,7 @@ func (c *DefaultClient) get(serviceName, id string) (*Announcement, error) { } func (c *DefaultClient) getAll(serviceName string) ([]*Announcement, error) { - res, err := c.client.GetAll(c.getContext(""), &GetAllRequest{ServiceName: serviceName}) + res, err := c.client.GetAll(c.getContext(""), &GetServiceRequest{ServiceName: serviceName}) if err != nil { return nil, err } @@ -156,69 +157,88 @@ func (c *DefaultClient) Get(serviceName, id string) (*Announcement, error) { return res.(*Announcement), nil } -// AddMetadata publishes metadata for the current component to the Discovery server -func (c *DefaultClient) AddMetadata(key Metadata_Key, value []byte, token string) error { +// AddDevAddrPrefix adds a DevAddrPrefix to the current component +func (c *DefaultClient) AddDevAddrPrefix(prefix types.DevAddrPrefix) error { + _, err := c.client.AddMetadata(c.getContext(""), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{Metadata: &Metadata_DevAddrPrefix{ + DevAddrPrefix: prefix.Bytes(), + }}, + }) + return err +} + +// AddAppID adds an AppID to the current component +func (c *DefaultClient) AddAppID(appID string, token string) error { _, err := c.client.AddMetadata(c.getContext(token), &MetadataRequest{ ServiceName: c.self.ServiceName, Id: c.self.Id, - Metadata: &Metadata{ - Key: key, - Value: value, - }, + Metadata: &Metadata{Metadata: &Metadata_AppId{ + AppId: appID, + }}, }) return err } -// DeleteMetadata deletes metadata for the current component from the Discovery server -func (c *DefaultClient) DeleteMetadata(key Metadata_Key, value []byte, token string) error { +// RemoveDevAddrPrefix removes a DevAddrPrefix from the current component +func (c *DefaultClient) RemoveDevAddrPrefix(prefix types.DevAddrPrefix) error { + _, err := c.client.DeleteMetadata(c.getContext(""), &MetadataRequest{ + ServiceName: c.self.ServiceName, + Id: c.self.Id, + Metadata: &Metadata{Metadata: &Metadata_DevAddrPrefix{ + DevAddrPrefix: prefix.Bytes(), + }}, + }) + return err +} + +// RemoveAppID removes an AppID from the current component +func (c *DefaultClient) RemoveAppID(appID string, token string) error { _, err := c.client.DeleteMetadata(c.getContext(token), &MetadataRequest{ ServiceName: c.self.ServiceName, Id: c.self.Id, - Metadata: &Metadata{ - Key: key, - Value: value, - }, + Metadata: &Metadata{Metadata: &Metadata_AppId{ + AppId: appID, + }}, }) return err } -// GetAllForMetadata returns all annoucements of given type that contain given metadata and match the given function -func (c *DefaultClient) GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func(value []byte) bool) ([]*Announcement, error) { - announcements, err := c.GetAll(serviceName) +// GetAllBrokersForDevAddr returns all brokers that can handle the given DevAddr +func (c *DefaultClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) (announcements []*Announcement, err error) { + brokers, err := c.GetAll("broker") if err != nil { return nil, err } - res := make([]*Announcement, 0, len(announcements)) -nextAnnouncement: - for _, announcement := range announcements { - for _, meta := range announcement.Metadata { - if meta.Key == key && matchFunc(meta.Value) { - res = append(res, announcement) - continue nextAnnouncement +next: + for _, broker := range brokers { + for _, prefix := range broker.DevAddrPrefixes() { + if devAddr.HasPrefix(prefix) { + announcements = append(announcements, broker) + continue next } } } - return res, nil -} - -// GetAllBrokersForDevAddr returns all brokers that can handle the given DevAddr -func (c *DefaultClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) { - return c.GetAllForMetadata("broker", Metadata_PREFIX, func(value []byte) bool { - if len(value) != 5 { - return false - } - var prefix types.DevAddrPrefix - copy(prefix.DevAddr[:], value[1:]) - prefix.Length = int(value[0]) - return devAddr.HasPrefix(prefix) - }) + return } // GetAllHandlersForAppID returns all handlers that can handle the given AppID -func (c *DefaultClient) GetAllHandlersForAppID(appID string) ([]*Announcement, error) { - return c.GetAllForMetadata("handler", Metadata_APP_ID, func(value []byte) bool { - return string(value) == appID - }) +func (c *DefaultClient) GetAllHandlersForAppID(appID string) (announcements []*Announcement, err error) { + handlers, err := c.GetAll("handler") + if err != nil { + return nil, err + } +next: + for _, handler := range handlers { + for _, handlerAppID := range handler.AppIDs() { + if handlerAppID == appID { + announcements = append(announcements, handler) + continue next + } + } + } + return } // Close purges the cache and closes the connection with the Discovery server diff --git a/api/discovery/client_mock.go b/api/discovery/client_mock.go index a11b4f090..8a7ec081e 100644 --- a/api/discovery/client_mock.go +++ b/api/discovery/client_mock.go @@ -61,35 +61,44 @@ func (_mr *_MockClientRecorder) Get(arg0, arg1 interface{}) *gomock.Call { return _mr.mock.ctrl.RecordCall(_mr.mock, "Get", arg0, arg1) } -func (_m *MockClient) AddMetadata(key Metadata_Key, value []byte, token string) error { - ret := _m.ctrl.Call(_m, "AddMetadata", key, value, token) +func (_m *MockClient) AddDevAddrPrefix(prefix types.DevAddrPrefix) error { + ret := _m.ctrl.Call(_m, "AddDevAddrPrefix", prefix) ret0, _ := ret[0].(error) return ret0 } -func (_mr *_MockClientRecorder) AddMetadata(arg0, arg1, arg2 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "AddMetadata", arg0, arg1, arg2) +func (_mr *_MockClientRecorder) AddDevAddrPrefix(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "AddDevAddrPrefix", arg0) } -func (_m *MockClient) DeleteMetadata(key Metadata_Key, value []byte, token string) error { - ret := _m.ctrl.Call(_m, "DeleteMetadata", key, value, token) +func (_m *MockClient) AddAppID(appID string, token string) error { + ret := _m.ctrl.Call(_m, "AddAppID", appID, token) ret0, _ := ret[0].(error) return ret0 } -func (_mr *_MockClientRecorder) DeleteMetadata(arg0, arg1, arg2 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "DeleteMetadata", arg0, arg1, arg2) +func (_mr *_MockClientRecorder) AddAppID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "AddAppID", arg0, arg1) } -func (_m *MockClient) GetAllForMetadata(serviceName string, key Metadata_Key, matchFunc func([]byte) bool) ([]*Announcement, error) { - ret := _m.ctrl.Call(_m, "GetAllForMetadata", serviceName, key, matchFunc) - ret0, _ := ret[0].([]*Announcement) - ret1, _ := ret[1].(error) - return ret0, ret1 +func (_m *MockClient) RemoveDevAddrPrefix(prefix types.DevAddrPrefix) error { + ret := _m.ctrl.Call(_m, "RemoveDevAddrPrefix", prefix) + ret0, _ := ret[0].(error) + return ret0 +} + +func (_mr *_MockClientRecorder) RemoveDevAddrPrefix(arg0 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "RemoveDevAddrPrefix", arg0) +} + +func (_m *MockClient) RemoveAppID(appID string, token string) error { + ret := _m.ctrl.Call(_m, "RemoveAppID", appID, token) + ret0, _ := ret[0].(error) + return ret0 } -func (_mr *_MockClientRecorder) GetAllForMetadata(arg0, arg1, arg2 interface{}) *gomock.Call { - return _mr.mock.ctrl.RecordCall(_mr.mock, "GetAllForMetadata", arg0, arg1, arg2) +func (_mr *_MockClientRecorder) RemoveAppID(arg0, arg1 interface{}) *gomock.Call { + return _mr.mock.ctrl.RecordCall(_mr.mock, "RemoveAppID", arg0, arg1) } func (_m *MockClient) GetAllBrokersForDevAddr(devAddr types.DevAddr) ([]*Announcement, error) { diff --git a/api/discovery/discovery.pb.go b/api/discovery/discovery.pb.go index e7a439847..35106377a 100644 --- a/api/discovery/discovery.pb.go +++ b/api/discovery/discovery.pb.go @@ -11,7 +11,7 @@ It has these top-level messages: Metadata Announcement - GetAllRequest + GetServiceRequest GetRequest MetadataRequest AnnouncementsResponse @@ -42,58 +42,147 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package -// The Key indicates the metadata type -type Metadata_Key int32 - -const ( - // OTHER indicates arbitrary metadata. We currently don't allow this. - Metadata_OTHER Metadata_Key = 0 - // The value for PREFIX consists of 1 byte denoting the number of bits, - // followed by the prefix and enough trailing bits to fill 4 octets. - // Only authorized brokers can announce PREFIX metadata. - Metadata_PREFIX Metadata_Key = 1 - // APP_EUI is used for announcing join handlers. The value for APP_EUI - // is the byte slice of the AppEUI. - // Only authorized join handlers can announce APP_EUI metadata (and we - // don't have any of those yet). - Metadata_APP_EUI Metadata_Key = 2 - // APP_ID is used for announcing that this handler is responsible for - // a certain AppID. The value for APP_ID is the byte slice of the AppID - // string. This metadata can only be added if the requesting client is - // authorized to manage this AppID. - Metadata_APP_ID Metadata_Key = 3 -) +type Metadata struct { + // Types that are valid to be assigned to Metadata: + // *Metadata_DevAddrPrefix + // *Metadata_AppId + // *Metadata_AppEui + Metadata isMetadata_Metadata `protobuf_oneof:"metadata"` +} + +func (m *Metadata) Reset() { *m = Metadata{} } +func (m *Metadata) String() string { return proto.CompactTextString(m) } +func (*Metadata) ProtoMessage() {} +func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } + +type isMetadata_Metadata interface { + isMetadata_Metadata() + MarshalTo([]byte) (int, error) + Size() int +} + +type Metadata_DevAddrPrefix struct { + DevAddrPrefix []byte `protobuf:"bytes,20,opt,name=dev_addr_prefix,json=devAddrPrefix,proto3,oneof"` +} +type Metadata_AppId struct { + AppId string `protobuf:"bytes,30,opt,name=app_id,json=appId,proto3,oneof"` +} +type Metadata_AppEui struct { + AppEui []byte `protobuf:"bytes,31,opt,name=app_eui,json=appEui,proto3,oneof"` +} + +func (*Metadata_DevAddrPrefix) isMetadata_Metadata() {} +func (*Metadata_AppId) isMetadata_Metadata() {} +func (*Metadata_AppEui) isMetadata_Metadata() {} -var Metadata_Key_name = map[int32]string{ - 0: "OTHER", - 1: "PREFIX", - 2: "APP_EUI", - 3: "APP_ID", +func (m *Metadata) GetMetadata() isMetadata_Metadata { + if m != nil { + return m.Metadata + } + return nil } -var Metadata_Key_value = map[string]int32{ - "OTHER": 0, - "PREFIX": 1, - "APP_EUI": 2, - "APP_ID": 3, + +func (m *Metadata) GetDevAddrPrefix() []byte { + if x, ok := m.GetMetadata().(*Metadata_DevAddrPrefix); ok { + return x.DevAddrPrefix + } + return nil } -func (x Metadata_Key) String() string { - return proto.EnumName(Metadata_Key_name, int32(x)) +func (m *Metadata) GetAppId() string { + if x, ok := m.GetMetadata().(*Metadata_AppId); ok { + return x.AppId + } + return "" } -func (Metadata_Key) EnumDescriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0, 0} } -// Announcements have a list of Metadata -type Metadata struct { - // The key indicates the metadata type - Key Metadata_Key `protobuf:"varint,1,opt,name=key,proto3,enum=discovery.Metadata_Key" json:"key,omitempty"` - // The value depends on the key type - Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +func (m *Metadata) GetAppEui() []byte { + if x, ok := m.GetMetadata().(*Metadata_AppEui); ok { + return x.AppEui + } + return nil } -func (m *Metadata) Reset() { *m = Metadata{} } -func (m *Metadata) String() string { return proto.CompactTextString(m) } -func (*Metadata) ProtoMessage() {} -func (*Metadata) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{0} } +// XXX_OneofFuncs is for the internal use of the proto package. +func (*Metadata) XXX_OneofFuncs() (func(msg proto.Message, b *proto.Buffer) error, func(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error), func(msg proto.Message) (n int), []interface{}) { + return _Metadata_OneofMarshaler, _Metadata_OneofUnmarshaler, _Metadata_OneofSizer, []interface{}{ + (*Metadata_DevAddrPrefix)(nil), + (*Metadata_AppId)(nil), + (*Metadata_AppEui)(nil), + } +} + +func _Metadata_OneofMarshaler(msg proto.Message, b *proto.Buffer) error { + m := msg.(*Metadata) + // metadata + switch x := m.Metadata.(type) { + case *Metadata_DevAddrPrefix: + _ = b.EncodeVarint(20<<3 | proto.WireBytes) + _ = b.EncodeRawBytes(x.DevAddrPrefix) + case *Metadata_AppId: + _ = b.EncodeVarint(30<<3 | proto.WireBytes) + _ = b.EncodeStringBytes(x.AppId) + case *Metadata_AppEui: + _ = b.EncodeVarint(31<<3 | proto.WireBytes) + _ = b.EncodeRawBytes(x.AppEui) + case nil: + default: + return fmt.Errorf("Metadata.Metadata has unexpected type %T", x) + } + return nil +} + +func _Metadata_OneofUnmarshaler(msg proto.Message, tag, wire int, b *proto.Buffer) (bool, error) { + m := msg.(*Metadata) + switch tag { + case 20: // metadata.dev_addr_prefix + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeRawBytes(true) + m.Metadata = &Metadata_DevAddrPrefix{x} + return true, err + case 30: // metadata.app_id + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeStringBytes() + m.Metadata = &Metadata_AppId{x} + return true, err + case 31: // metadata.app_eui + if wire != proto.WireBytes { + return true, proto.ErrInternalBadWireType + } + x, err := b.DecodeRawBytes(true) + m.Metadata = &Metadata_AppEui{x} + return true, err + default: + return false, nil + } +} + +func _Metadata_OneofSizer(msg proto.Message) (n int) { + m := msg.(*Metadata) + // metadata + switch x := m.Metadata.(type) { + case *Metadata_DevAddrPrefix: + n += proto.SizeVarint(20<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.DevAddrPrefix))) + n += len(x.DevAddrPrefix) + case *Metadata_AppId: + n += proto.SizeVarint(30<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.AppId))) + n += len(x.AppId) + case *Metadata_AppEui: + n += proto.SizeVarint(31<<3 | proto.WireBytes) + n += proto.SizeVarint(uint64(len(x.AppEui))) + n += len(x.AppEui) + case nil: + default: + panic(fmt.Sprintf("proto: unexpected type %T in oneof", x)) + } + return n +} // The Announcement of a service (also called component) type Announcement struct { @@ -118,7 +207,7 @@ type Announcement struct { // Contains the address where the HTTP API is exposed (if there is one) ApiAddress string `protobuf:"bytes,14,opt,name=api_address,json=apiAddress,proto3" json:"api_address,omitempty"` // Metadata for this component - Metadata []*Metadata `protobuf:"bytes,21,rep,name=metadata" json:"metadata,omitempty"` + Metadata []*Metadata `protobuf:"bytes,22,rep,name=metadata" json:"metadata,omitempty"` } func (m *Announcement) Reset() { *m = Announcement{} } @@ -133,15 +222,15 @@ func (m *Announcement) GetMetadata() []*Metadata { return nil } -type GetAllRequest struct { +type GetServiceRequest struct { // The name of the service (router/broker/handler) ServiceName string `protobuf:"bytes,1,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` } -func (m *GetAllRequest) Reset() { *m = GetAllRequest{} } -func (m *GetAllRequest) String() string { return proto.CompactTextString(m) } -func (*GetAllRequest) ProtoMessage() {} -func (*GetAllRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } +func (m *GetServiceRequest) Reset() { *m = GetServiceRequest{} } +func (m *GetServiceRequest) String() string { return proto.CompactTextString(m) } +func (*GetServiceRequest) ProtoMessage() {} +func (*GetServiceRequest) Descriptor() ([]byte, []int) { return fileDescriptorDiscovery, []int{2} } // The identifier of the service that should be returned type GetRequest struct { @@ -161,8 +250,9 @@ type MetadataRequest struct { // The ID of the service that should be modified Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // The name of the service (router/broker/handler) that should be modified - ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` - Metadata *Metadata `protobuf:"bytes,11,opt,name=metadata" json:"metadata,omitempty"` + ServiceName string `protobuf:"bytes,2,opt,name=service_name,json=serviceName,proto3" json:"service_name,omitempty"` + // Metadata to add or remove + Metadata *Metadata `protobuf:"bytes,12,opt,name=metadata" json:"metadata,omitempty"` } func (m *MetadataRequest) Reset() { *m = MetadataRequest{} } @@ -197,11 +287,10 @@ func (m *AnnouncementsResponse) GetServices() []*Announcement { func init() { proto.RegisterType((*Metadata)(nil), "discovery.Metadata") proto.RegisterType((*Announcement)(nil), "discovery.Announcement") - proto.RegisterType((*GetAllRequest)(nil), "discovery.GetAllRequest") + proto.RegisterType((*GetServiceRequest)(nil), "discovery.GetServiceRequest") proto.RegisterType((*GetRequest)(nil), "discovery.GetRequest") proto.RegisterType((*MetadataRequest)(nil), "discovery.MetadataRequest") proto.RegisterType((*AnnouncementsResponse)(nil), "discovery.AnnouncementsResponse") - proto.RegisterEnum("discovery.Metadata_Key", Metadata_Key_name, Metadata_Key_value) } // Reference imports to suppress errors if they are not otherwise used. @@ -219,8 +308,8 @@ type DiscoveryClient interface { // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. Announce(ctx context.Context, in *Announcement, opts ...grpc.CallOption) (*google_protobuf.Empty, error) - // Get all announcements for a specific service - GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) + // Get all announcements for a specific service type + GetAll(ctx context.Context, in *GetServiceRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) // Get a specific announcement Get(ctx context.Context, in *GetRequest, opts ...grpc.CallOption) (*Announcement, error) // Add metadata to an announement @@ -246,7 +335,7 @@ func (c *discoveryClient) Announce(ctx context.Context, in *Announcement, opts . return out, nil } -func (c *discoveryClient) GetAll(ctx context.Context, in *GetAllRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) { +func (c *discoveryClient) GetAll(ctx context.Context, in *GetServiceRequest, opts ...grpc.CallOption) (*AnnouncementsResponse, error) { out := new(AnnouncementsResponse) err := grpc.Invoke(ctx, "/discovery.Discovery/GetAll", in, out, c.cc, opts...) if err != nil { @@ -289,8 +378,8 @@ type DiscoveryServer interface { // A call to `Announce` does not processes the `metadata` field, so you can safely leave this field empty. // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. Announce(context.Context, *Announcement) (*google_protobuf.Empty, error) - // Get all announcements for a specific service - GetAll(context.Context, *GetAllRequest) (*AnnouncementsResponse, error) + // Get all announcements for a specific service type + GetAll(context.Context, *GetServiceRequest) (*AnnouncementsResponse, error) // Get a specific announcement Get(context.Context, *GetRequest) (*Announcement, error) // Add metadata to an announement @@ -322,7 +411,7 @@ func _Discovery_Announce_Handler(srv interface{}, ctx context.Context, dec func( } func _Discovery_GetAll_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(GetAllRequest) + in := new(GetServiceRequest) if err := dec(in); err != nil { return nil, err } @@ -334,7 +423,7 @@ func _Discovery_GetAll_Handler(srv interface{}, ctx context.Context, dec func(in FullMethod: "/discovery.Discovery/GetAll", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(DiscoveryServer).GetAll(ctx, req.(*GetAllRequest)) + return srv.(DiscoveryServer).GetAll(ctx, req.(*GetServiceRequest)) } return interceptor(ctx, in, info, handler) } @@ -467,20 +556,50 @@ func (m *Metadata) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.Key != 0 { - dAtA[i] = 0x8 + if m.Metadata != nil { + nn1, err := m.Metadata.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += nn1 + } + return i, nil +} + +func (m *Metadata_DevAddrPrefix) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.DevAddrPrefix != nil { + dAtA[i] = 0xa2 i++ - i = encodeVarintDiscovery(dAtA, i, uint64(m.Key)) + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.DevAddrPrefix))) + i += copy(dAtA[i:], m.DevAddrPrefix) } - if len(m.Value) > 0 { - dAtA[i] = 0x12 + return i, nil +} +func (m *Metadata_AppId) MarshalTo(dAtA []byte) (int, error) { + i := 0 + dAtA[i] = 0xf2 + i++ + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.AppId))) + i += copy(dAtA[i:], m.AppId) + return i, nil +} +func (m *Metadata_AppEui) MarshalTo(dAtA []byte) (int, error) { + i := 0 + if m.AppEui != nil { + dAtA[i] = 0xfa i++ - i = encodeVarintDiscovery(dAtA, i, uint64(len(m.Value))) - i += copy(dAtA[i:], m.Value) + dAtA[i] = 0x1 + i++ + i = encodeVarintDiscovery(dAtA, i, uint64(len(m.AppEui))) + i += copy(dAtA[i:], m.AppEui) } return i, nil } - func (m *Announcement) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -562,7 +681,7 @@ func (m *Announcement) MarshalTo(dAtA []byte) (int, error) { } if len(m.Metadata) > 0 { for _, msg := range m.Metadata { - dAtA[i] = 0xaa + dAtA[i] = 0xb2 i++ dAtA[i] = 0x1 i++ @@ -577,7 +696,7 @@ func (m *Announcement) MarshalTo(dAtA []byte) (int, error) { return i, nil } -func (m *GetAllRequest) Marshal() (dAtA []byte, err error) { +func (m *GetServiceRequest) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) @@ -587,7 +706,7 @@ func (m *GetAllRequest) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *GetAllRequest) MarshalTo(dAtA []byte) (int, error) { +func (m *GetServiceRequest) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -659,14 +778,14 @@ func (m *MetadataRequest) MarshalTo(dAtA []byte) (int, error) { i += copy(dAtA[i:], m.ServiceName) } if m.Metadata != nil { - dAtA[i] = 0x5a + dAtA[i] = 0x62 i++ i = encodeVarintDiscovery(dAtA, i, uint64(m.Metadata.Size())) - n1, err := m.Metadata.MarshalTo(dAtA[i:]) + n2, err := m.Metadata.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n1 + i += n2 } return i, nil } @@ -731,16 +850,37 @@ func encodeVarintDiscovery(dAtA []byte, offset int, v uint64) int { func (m *Metadata) Size() (n int) { var l int _ = l - if m.Key != 0 { - n += 1 + sovDiscovery(uint64(m.Key)) - } - l = len(m.Value) - if l > 0 { - n += 1 + l + sovDiscovery(uint64(l)) + if m.Metadata != nil { + n += m.Metadata.Size() } return n } +func (m *Metadata_DevAddrPrefix) Size() (n int) { + var l int + _ = l + if m.DevAddrPrefix != nil { + l = len(m.DevAddrPrefix) + n += 2 + l + sovDiscovery(uint64(l)) + } + return n +} +func (m *Metadata_AppId) Size() (n int) { + var l int + _ = l + l = len(m.AppId) + n += 2 + l + sovDiscovery(uint64(l)) + return n +} +func (m *Metadata_AppEui) Size() (n int) { + var l int + _ = l + if m.AppEui != nil { + l = len(m.AppEui) + n += 2 + l + sovDiscovery(uint64(l)) + } + return n +} func (m *Announcement) Size() (n int) { var l int _ = l @@ -792,7 +932,7 @@ func (m *Announcement) Size() (n int) { return n } -func (m *GetAllRequest) Size() (n int) { +func (m *GetServiceRequest) Size() (n int) { var l int _ = l l = len(m.ServiceName) @@ -888,11 +1028,11 @@ func (m *Metadata) Unmarshal(dAtA []byte) error { return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 0 { - return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) + case 20: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field DevAddrPrefix", wireType) } - m.Key = 0 + var byteLen int for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowDiscovery @@ -902,14 +1042,54 @@ func (m *Metadata) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.Key |= (Metadata_Key(b) & 0x7F) << shift + byteLen |= (int(b) & 0x7F) << shift if b < 0x80 { break } } - case 2: + if byteLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + byteLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Metadata = &Metadata_DevAddrPrefix{v} + iNdEx = postIndex + case 30: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AppId", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowDiscovery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthDiscovery + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Metadata = &Metadata_AppId{string(dAtA[iNdEx:postIndex])} + iNdEx = postIndex + case 31: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field AppEui", wireType) } var byteLen int for shift := uint(0); ; shift += 7 { @@ -933,10 +1113,9 @@ func (m *Metadata) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) - if m.Value == nil { - m.Value = []byte{} - } + v := make([]byte, postIndex-iNdEx) + copy(v, dAtA[iNdEx:postIndex]) + m.Metadata = &Metadata_AppEui{v} iNdEx = postIndex default: iNdEx = preIndex @@ -1269,7 +1448,7 @@ func (m *Announcement) Unmarshal(dAtA []byte) error { } m.ApiAddress = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 21: + case 22: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1321,7 +1500,7 @@ func (m *Announcement) Unmarshal(dAtA []byte) error { } return nil } -func (m *GetAllRequest) Unmarshal(dAtA []byte) error { +func (m *GetServiceRequest) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1344,10 +1523,10 @@ func (m *GetAllRequest) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: GetAllRequest: wiretype end group for non-group") + return fmt.Errorf("proto: GetServiceRequest: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: GetAllRequest: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: GetServiceRequest: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -1595,7 +1774,7 @@ func (m *MetadataRequest) Unmarshal(dAtA []byte) error { } m.ServiceName = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 11: + case 12: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Metadata", wireType) } @@ -1840,47 +2019,47 @@ func init() { } var fileDescriptorDiscovery = []byte{ - // 666 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0xad, 0xe3, 0xaf, 0xf9, 0x92, 0xeb, 0x34, 0x8d, 0x06, 0x5a, 0xac, 0x40, 0xd3, 0x60, 0x81, - 0x08, 0x48, 0xc4, 0x52, 0x2a, 0x56, 0x08, 0x55, 0x41, 0x09, 0xa5, 0x2a, 0x2d, 0x95, 0x55, 0x10, - 0x62, 0x13, 0x4d, 0xec, 0xdb, 0x74, 0x54, 0x67, 0x6c, 0xec, 0x71, 0x50, 0x54, 0x75, 0xd3, 0x57, - 0x60, 0xc3, 0x96, 0xb7, 0x61, 0x89, 0xc4, 0x0b, 0xa0, 0xc2, 0x8a, 0xa7, 0x40, 0xfe, 0xad, 0xab, - 0x36, 0xa0, 0xc2, 0xce, 0x73, 0xee, 0xb9, 0xe7, 0xcc, 0x9c, 0xeb, 0x19, 0x78, 0x32, 0x62, 0xe2, - 0x20, 0x18, 0xb6, 0x4d, 0x67, 0xac, 0xef, 0x1d, 0xe0, 0xde, 0x01, 0xe3, 0x23, 0x7f, 0x07, 0xc5, - 0x7b, 0xc7, 0x3b, 0xd4, 0x85, 0xe0, 0x3a, 0x75, 0x99, 0x6e, 0x31, 0xdf, 0x74, 0x26, 0xe8, 0x4d, - 0xcf, 0xbe, 0xda, 0xae, 0xe7, 0x08, 0x87, 0x94, 0x33, 0xa0, 0x7e, 0x73, 0xe4, 0x38, 0x23, 0x1b, - 0xf5, 0xa8, 0x30, 0x0c, 0xf6, 0x75, 0x1c, 0xbb, 0x22, 0xe1, 0xd5, 0x6f, 0x25, 0xc5, 0x50, 0x8d, - 0x72, 0xee, 0x08, 0x2a, 0x98, 0xc3, 0xfd, 0xb8, 0xaa, 0x9d, 0x48, 0x50, 0xda, 0x46, 0x41, 0x2d, - 0x2a, 0x28, 0xb9, 0x0f, 0xf2, 0x21, 0x4e, 0x55, 0xa9, 0x29, 0xb5, 0xaa, 0x9d, 0x1b, 0xed, 0x33, - 0xc7, 0x94, 0xd1, 0xde, 0xc2, 0xa9, 0x11, 0x72, 0xc8, 0x75, 0x98, 0x9f, 0x50, 0x3b, 0x40, 0xb5, - 0xd0, 0x94, 0x5a, 0x15, 0x23, 0x5e, 0x68, 0x8f, 0x40, 0xde, 0xc2, 0x29, 0x29, 0xc3, 0xfc, 0xcb, - 0xbd, 0xe7, 0x7d, 0xa3, 0x36, 0x47, 0x00, 0x8a, 0xbb, 0x46, 0xff, 0xd9, 0xe6, 0x9b, 0x9a, 0x44, - 0x14, 0xf8, 0xbf, 0xbb, 0xbb, 0x3b, 0xe8, 0xbf, 0xda, 0xac, 0x15, 0xc2, 0x42, 0xb8, 0xd8, 0xec, - 0xd5, 0x64, 0xed, 0x67, 0x01, 0x2a, 0x5d, 0xce, 0x9d, 0x80, 0x9b, 0x38, 0x46, 0x2e, 0x48, 0x15, - 0x0a, 0xcc, 0x8a, 0xf6, 0x51, 0x36, 0x0a, 0xcc, 0x22, 0xb7, 0xa1, 0xe2, 0xa3, 0x37, 0x61, 0x26, - 0x0e, 0x38, 0x1d, 0xc7, 0xa6, 0x65, 0x43, 0x49, 0xb0, 0x1d, 0x3a, 0x46, 0x72, 0x0f, 0x16, 0x53, - 0xca, 0x04, 0x3d, 0x9f, 0x39, 0x5c, 0x95, 0x23, 0x56, 0x35, 0x81, 0x5f, 0xc7, 0x28, 0x69, 0x82, - 0x62, 0xa1, 0x6f, 0x7a, 0xcc, 0x0d, 0x73, 0x50, 0xff, 0x8b, 0xa5, 0x72, 0x10, 0xa9, 0x81, 0x1c, - 0x78, 0xb6, 0x3a, 0x1f, 0x55, 0xc2, 0x4f, 0xb2, 0x0c, 0x45, 0x37, 0x18, 0xda, 0xcc, 0x54, 0x8b, - 0x4d, 0xa9, 0x55, 0x32, 0x92, 0x15, 0x59, 0x05, 0x85, 0xa3, 0x18, 0x50, 0xcb, 0xf2, 0xd0, 0xf7, - 0x55, 0x25, 0xea, 0x00, 0x8e, 0xa2, 0x1b, 0x23, 0x64, 0x05, 0x20, 0xa6, 0x0e, 0xc2, 0x60, 0x2b, - 0x51, 0xbd, 0x1c, 0x23, 0x61, 0x50, 0x4d, 0x50, 0x4c, 0xf4, 0x04, 0xdb, 0x67, 0x26, 0x15, 0xa8, - 0x2e, 0xc4, 0x7b, 0xc9, 0x41, 0xa1, 0x03, 0x75, 0x59, 0xe6, 0x50, 0x8d, 0x1d, 0xa8, 0xcb, 0x52, - 0x07, 0x1d, 0x4a, 0xe3, 0x64, 0x3a, 0xea, 0x52, 0x53, 0x6e, 0x29, 0x9d, 0x6b, 0x97, 0x0c, 0xce, - 0xc8, 0x48, 0x5a, 0x07, 0x16, 0x36, 0x50, 0x74, 0x6d, 0xdb, 0xc0, 0x77, 0x01, 0xfa, 0xe2, 0x42, - 0xb8, 0xd2, 0x85, 0x70, 0xb5, 0x75, 0x80, 0x0d, 0x14, 0x69, 0xc3, 0xd5, 0xa7, 0xa3, 0x05, 0xb0, - 0x98, 0x6d, 0xe5, 0xaf, 0x55, 0xce, 0x9d, 0x35, 0xcc, 0xfa, 0x8f, 0x67, 0x7d, 0x01, 0x4b, 0xf9, - 0xff, 0xca, 0x37, 0xd0, 0x77, 0x1d, 0xee, 0x23, 0x59, 0x83, 0x52, 0x22, 0xec, 0xab, 0x52, 0x94, - 0x5a, 0xfe, 0x77, 0xcf, 0xf7, 0x18, 0x19, 0xb1, 0xf3, 0x49, 0x86, 0x72, 0x2f, 0x25, 0x91, 0xc7, - 0x50, 0x4a, 0x79, 0x64, 0x56, 0x73, 0x7d, 0xb9, 0x1d, 0xdf, 0xbe, 0x76, 0x7a, 0x35, 0xdb, 0xfd, - 0xf0, 0x6a, 0x92, 0x43, 0x28, 0xc6, 0x43, 0x20, 0x6a, 0xae, 0xf5, 0xdc, 0x5c, 0xea, 0xcd, 0x19, - 0xa2, 0xd9, 0x29, 0xb4, 0xbb, 0x27, 0x5f, 0x7f, 0x7c, 0x28, 0xac, 0x92, 0x95, 0xe8, 0x62, 0x67, - 0x75, 0xfd, 0x28, 0x1f, 0xe4, 0x31, 0xa1, 0x20, 0x6f, 0xa0, 0x20, 0x4b, 0xe7, 0x9d, 0x52, 0x9b, - 0x59, 0x7b, 0xd7, 0x1e, 0x44, 0xea, 0x77, 0x88, 0xf6, 0x5b, 0x75, 0xfd, 0x88, 0x59, 0xc7, 0xa4, - 0x0b, 0x4a, 0xd7, 0xb2, 0xb2, 0x87, 0xa4, 0x7e, 0xd9, 0x58, 0x12, 0xbf, 0x59, 0x91, 0xf4, 0xa0, - 0xda, 0x43, 0x1b, 0x05, 0xfe, 0x8b, 0x4a, 0x87, 0x40, 0x2d, 0x1b, 0xd1, 0x36, 0xe5, 0x74, 0x84, - 0xde, 0xd3, 0xf5, 0xcf, 0xa7, 0x0d, 0xe9, 0xcb, 0x69, 0x43, 0xfa, 0x76, 0xda, 0x90, 0x3e, 0x7e, - 0x6f, 0xcc, 0xbd, 0x7d, 0x78, 0xa5, 0xa7, 0x77, 0x58, 0x8c, 0x4c, 0xd6, 0x7e, 0x05, 0x00, 0x00, - 0xff, 0xff, 0x7a, 0x2d, 0x85, 0xda, 0xb2, 0x05, 0x00, 0x00, + // 660 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xa4, 0x54, 0xdf, 0x4e, 0x13, 0x4f, + 0x14, 0x66, 0xdb, 0x1f, 0xfd, 0xb5, 0xa7, 0xa5, 0xe0, 0x28, 0x30, 0x56, 0x28, 0xb5, 0xd1, 0xd8, + 0x98, 0xd8, 0x4d, 0x20, 0xf1, 0xc6, 0x18, 0x52, 0x02, 0x29, 0x46, 0x21, 0x66, 0x25, 0x5e, 0x78, + 0xd3, 0x4c, 0x77, 0x0e, 0x65, 0x42, 0x3b, 0xbb, 0xee, 0xcc, 0x56, 0x09, 0xe1, 0xc6, 0x57, 0xf0, + 0xc6, 0x17, 0xf0, 0x5d, 0xbc, 0x34, 0xf1, 0x05, 0x0c, 0x7a, 0xe5, 0x53, 0x98, 0xfd, 0xdb, 0x25, + 0x58, 0x0d, 0x7a, 0xb7, 0xfb, 0x9d, 0xef, 0x7c, 0xdf, 0xcc, 0x77, 0xf6, 0x2c, 0x3c, 0x1e, 0x08, + 0x7d, 0xe4, 0xf7, 0xdb, 0xb6, 0x33, 0x32, 0x0f, 0x8e, 0xf0, 0xe0, 0x48, 0xc8, 0x81, 0xda, 0x47, + 0xfd, 0xc6, 0xf1, 0x8e, 0x4d, 0xad, 0xa5, 0xc9, 0x5c, 0x61, 0x72, 0xa1, 0x6c, 0x67, 0x8c, 0xde, + 0xc9, 0xe4, 0xa9, 0xed, 0x7a, 0x8e, 0x76, 0x48, 0x29, 0x05, 0x6a, 0xb7, 0x06, 0x8e, 0x33, 0x18, + 0xa2, 0x19, 0x16, 0xfa, 0xfe, 0xa1, 0x89, 0x23, 0x57, 0xc7, 0xbc, 0xda, 0x4a, 0x5c, 0x0c, 0xd4, + 0x98, 0x94, 0x8e, 0x66, 0x5a, 0x38, 0x52, 0x45, 0xd5, 0xa6, 0x86, 0xe2, 0x1e, 0x6a, 0xc6, 0x99, + 0x66, 0xa4, 0x05, 0xf3, 0x1c, 0xc7, 0x3d, 0xc6, 0xb9, 0xd7, 0x73, 0x3d, 0x3c, 0x14, 0x6f, 0xe9, + 0x8d, 0x86, 0xd1, 0xaa, 0xec, 0xce, 0x58, 0x73, 0x1c, 0xc7, 0x1d, 0xce, 0xbd, 0xe7, 0x21, 0x4c, + 0x96, 0xa1, 0xc0, 0x5c, 0xb7, 0x27, 0x38, 0xad, 0x37, 0x8c, 0x56, 0x69, 0x77, 0xc6, 0x9a, 0x65, + 0xae, 0xfb, 0x84, 0x93, 0x9b, 0xf0, 0x7f, 0x50, 0x40, 0x5f, 0xd0, 0xb5, 0xb8, 0x35, 0x60, 0xee, + 0xf8, 0x62, 0x0b, 0xa0, 0x38, 0x8a, 0x9d, 0x9a, 0x3f, 0x72, 0x50, 0xe9, 0x48, 0xe9, 0xf8, 0xd2, + 0xc6, 0x11, 0x4a, 0x4d, 0xaa, 0x90, 0x13, 0x9c, 0x1a, 0x81, 0x98, 0x95, 0x13, 0x9c, 0xdc, 0x86, + 0x8a, 0x42, 0x6f, 0x2c, 0x6c, 0xec, 0x49, 0x36, 0x42, 0x9a, 0x0b, 0x2b, 0xe5, 0x18, 0xdb, 0x67, + 0x23, 0x24, 0xf7, 0x60, 0x3e, 0xa1, 0x8c, 0xd1, 0x53, 0xc2, 0x91, 0x34, 0x1f, 0xb2, 0xaa, 0x31, + 0xfc, 0x32, 0x42, 0x49, 0x03, 0xca, 0x1c, 0x95, 0xed, 0x09, 0x37, 0xb8, 0x38, 0xfd, 0x2f, 0x92, + 0xca, 0x40, 0x64, 0x01, 0xf2, 0xbe, 0x37, 0xa4, 0xb3, 0x61, 0x25, 0x78, 0x24, 0x4b, 0x50, 0x70, + 0xfd, 0xfe, 0x50, 0xd8, 0xb4, 0xd0, 0x30, 0x5a, 0x45, 0x2b, 0x7e, 0x23, 0x6b, 0x50, 0x96, 0xa8, + 0xc3, 0x88, 0x50, 0x29, 0x5a, 0x0e, 0x3b, 0x40, 0xa2, 0xee, 0x44, 0x08, 0x59, 0x05, 0x88, 0xa8, + 0xbd, 0x63, 0x3c, 0xa1, 0x95, 0xb0, 0x5e, 0x8a, 0x90, 0xa7, 0x78, 0x12, 0x9c, 0xc5, 0x46, 0x4f, + 0x8b, 0x43, 0x61, 0x33, 0x8d, 0x74, 0x2e, 0x3a, 0x4b, 0x06, 0x0a, 0x1c, 0x98, 0x2b, 0x52, 0x87, + 0x6a, 0xe4, 0xc0, 0x5c, 0x91, 0x38, 0x98, 0x93, 0x1c, 0xe9, 0x52, 0x23, 0xdf, 0x2a, 0xaf, 0x5f, + 0x6f, 0x4f, 0xbe, 0x8d, 0x64, 0x98, 0xd6, 0x24, 0xec, 0x87, 0x70, 0xad, 0x8b, 0xfa, 0x45, 0x14, + 0x8a, 0x85, 0xaf, 0x7d, 0x54, 0xfa, 0x52, 0xc0, 0xc6, 0xa5, 0x80, 0x9b, 0x9b, 0x00, 0x5d, 0xd4, + 0x49, 0xc3, 0xd5, 0x27, 0xd4, 0xf4, 0x61, 0x3e, 0x3d, 0xce, 0x5f, 0xab, 0x5c, 0xb8, 0x6f, 0x90, + 0xe7, 0x1f, 0xef, 0xfb, 0x0c, 0x16, 0xb3, 0xdf, 0x96, 0xb2, 0x50, 0xb9, 0x8e, 0x54, 0x48, 0x36, + 0xa0, 0x18, 0x0b, 0x2b, 0x6a, 0x84, 0xc9, 0x2d, 0x67, 0x94, 0xb2, 0x3d, 0x56, 0x4a, 0x5c, 0xff, + 0x98, 0x87, 0xd2, 0x76, 0x42, 0x22, 0x8f, 0xa0, 0x98, 0xf0, 0xc8, 0xb4, 0xe6, 0xda, 0x52, 0x3b, + 0x5a, 0xb9, 0x76, 0xb2, 0x8f, 0xed, 0x9d, 0x60, 0x1f, 0x89, 0x03, 0x85, 0x2e, 0xea, 0xce, 0x70, + 0x48, 0x56, 0x32, 0xad, 0x97, 0x66, 0x53, 0x6b, 0x4c, 0x11, 0x4e, 0x6f, 0xd2, 0xbc, 0xfb, 0xee, + 0xcb, 0xf7, 0xf7, 0xb9, 0x35, 0xb2, 0x1a, 0x6e, 0x74, 0x5a, 0x37, 0x4f, 0xb3, 0x61, 0x9e, 0x11, + 0x06, 0xf9, 0x2e, 0x6a, 0xb2, 0x78, 0xd1, 0x2d, 0xb1, 0x99, 0x76, 0xfe, 0xe6, 0xfd, 0x50, 0xfd, + 0x0e, 0x69, 0xfe, 0x56, 0xdd, 0x3c, 0x15, 0xfc, 0x8c, 0x74, 0xa0, 0xdc, 0xe1, 0x3c, 0xfd, 0x85, + 0xd4, 0x7e, 0x35, 0x9a, 0xd8, 0x6f, 0x5a, 0x2c, 0xdb, 0x50, 0xdd, 0xc6, 0x21, 0x6a, 0xfc, 0x17, + 0x95, 0x75, 0x02, 0x0b, 0xe9, 0x98, 0xf6, 0x98, 0x64, 0x03, 0xf4, 0xb6, 0x36, 0x3f, 0x9d, 0xd7, + 0x8d, 0xcf, 0xe7, 0x75, 0xe3, 0xeb, 0x79, 0xdd, 0xf8, 0xf0, 0xad, 0x3e, 0xf3, 0xea, 0xc1, 0x95, + 0xfe, 0xb9, 0xfd, 0x42, 0x68, 0xb2, 0xf1, 0x33, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xc1, 0x3f, 0xec, + 0xab, 0x05, 0x00, 0x00, } diff --git a/api/discovery/discovery.pb.gw.go b/api/discovery/discovery.pb.gw.go index cf68860c0..08925d7af 100644 --- a/api/discovery/discovery.pb.gw.go +++ b/api/discovery/discovery.pb.gw.go @@ -28,7 +28,7 @@ var _ = runtime.String var _ = utilities.NewDoubleArray func request_Discovery_GetAll_0(ctx context.Context, marshaler runtime.Marshaler, client DiscoveryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { - var protoReq GetAllRequest + var protoReq GetServiceRequest var metadata runtime.ServerMetadata var ( diff --git a/api/discovery/discovery.proto b/api/discovery/discovery.proto index 496c87360..22948d04c 100644 --- a/api/discovery/discovery.proto +++ b/api/discovery/discovery.proto @@ -10,39 +10,33 @@ package discovery; option go_package = "github.com/TheThingsNetwork/ttn/api/discovery"; -// Announcements have a list of Metadata message Metadata { + oneof metadata { + // General metadata (0-9) - // In the future we will have to change this message to use a oneof. - - // The Key indicates the metadata type - enum Key { - // OTHER indicates arbitrary metadata. We currently don't allow this. - OTHER = 0; - - // The value for PREFIX consists of 1 byte denoting the number of bits, - // followed by the prefix and enough trailing bits to fill 4 octets. - // Only authorized brokers can announce PREFIX metadata. - PREFIX = 1; - - // APP_EUI is used for announcing join handlers. The value for APP_EUI - // is the byte slice of the AppEUI. - // Only authorized join handlers can announce APP_EUI metadata (and we - // don't have any of those yet). - APP_EUI = 2; - - // APP_ID is used for announcing that this handler is responsible for - // a certain AppID. The value for APP_ID is the byte slice of the AppID - // string. This metadata can only be added if the requesting client is - // authorized to manage this AppID. - APP_ID = 3; - } + // + + // Metadata for Router component (10-19) + + // + + // Metadata for Broker component (20-29) - // The key indicates the metadata type - Key key = 1; + // DevAddr prefix that is routed by this Broker + // 5 bytes; the first byte is the prefix length, the following 4 bytes are the address. + // Only authorized Brokers can announce PREFIX metadata. + bytes dev_addr_prefix = 20; // for some reason gogoproto customtype doesn't work in a oneof, so we do this manually - // The value depends on the key type - bytes value = 2; + // Metadata for Handler component (30-39) + + // AppID that is registered to this Handler + // This metadata can only be added if the requesting client is authorized to manage this AppID. + string app_id = 30; + + // AppEUI that is registered to this Join Handler + // Only authorized Join Handlers can announce APP_EUI metadata (and we don't have any of those yet). + bytes app_eui = 31; // for some reason gogoproto customtype doesn't work in a oneof, so we do this manually + } } // The Announcement of a service (also called component) @@ -78,10 +72,10 @@ message Announcement { string api_address = 14; // Metadata for this component - repeated Metadata metadata = 21; + repeated Metadata metadata = 22; } -message GetAllRequest { +message GetServiceRequest { // The name of the service (router/broker/handler) string service_name = 1; } @@ -103,7 +97,8 @@ message MetadataRequest { // The name of the service (router/broker/handler) that should be modified string service_name = 2; - Metadata metadata = 11; + // Metadata to add or remove + Metadata metadata = 12; } // A list of announcements @@ -118,8 +113,8 @@ service Discovery { // Adding or removing Metadata should be done with the `AddMetadata` and `DeleteMetadata` methods. rpc Announce(Announcement) returns (google.protobuf.Empty); - // Get all announcements for a specific service - rpc GetAll(GetAllRequest) returns (AnnouncementsResponse) { + // Get all announcements for a specific service type + rpc GetAll(GetServiceRequest) returns (AnnouncementsResponse) { option (google.api.http) = { get: "/announcements/{service_name}" }; diff --git a/api/discovery/metadata.go b/api/discovery/metadata.go new file mode 100644 index 000000000..eebe65189 --- /dev/null +++ b/api/discovery/metadata.go @@ -0,0 +1,41 @@ +package discovery + +import "github.com/TheThingsNetwork/ttn/core/types" + +// AppIDs that are handled by this component +func (a *Announcement) AppIDs() (appIDs []string) { + for _, meta := range a.Metadata { + if appID := meta.GetAppId(); appID != "" { + appIDs = append(appIDs, appID) + } + } + return +} + +// DevAddrPrefixes that are handled by this component +func (a *Announcement) DevAddrPrefixes() (prefixes []types.DevAddrPrefix) { + for _, meta := range a.Metadata { + if prefixBytes := meta.GetDevAddrPrefix(); prefixBytes != nil { + prefix := new(types.DevAddrPrefix) + if err := prefix.Unmarshal(prefixBytes); err != nil { + continue + } + prefixes = append(prefixes, *prefix) + } + } + return +} + +// AppEUIs that are handled by this component +func (a *Announcement) AppEUIs() (euis []types.AppEUI) { + for _, meta := range a.Metadata { + if euiBytes := meta.GetAppEui(); euiBytes != nil { + eui := new(types.AppEUI) + if err := eui.Unmarshal(euiBytes); err != nil { + continue + } + euis = append(euis, *eui) + } + } + return +} diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index 38f4507f5..20e23f277 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -24,12 +24,14 @@ var brokerRegisterPrefixCmd = &cobra.Command{ cmd.UsageFunc()(cmd) } - conn, err := grpc.Dial(viper.GetString("discovery-address"), append(api.DialOptions, grpc.WithInsecure())...) + conn, err := grpc.Dial(viper.GetString("discovery-address"), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") } client := discovery.NewDiscoveryClient(conn) + client.GetAll(context.Background(), &discovery.GetServiceRequest{}) + md := metadata.Pairs( "service-name", "broker", "id", viper.GetString("id"), @@ -47,10 +49,9 @@ var brokerRegisterPrefixCmd = &cobra.Command{ _, err = client.AddMetadata(dscContext, &discovery.MetadataRequest{ ServiceName: "broker", Id: viper.GetString("id"), - Metadata: &discovery.Metadata{ - Key: discovery.Metadata_PREFIX, - Value: []byte{byte(prefix.Length), prefix.DevAddr[0], prefix.DevAddr[1], prefix.DevAddr[2], prefix.DevAddr[3]}, - }, + Metadata: &discovery.Metadata{Metadata: &discovery.Metadata_DevAddrPrefix{ + DevAddrPrefix: prefix.Bytes(), + }}, }) if err != nil { ctx.WithError(err).Error("Could not register prefix") diff --git a/core/broker/broker.go b/core/broker/broker.go index 08c56d0b8..941e5063f 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -11,7 +11,6 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/networkserver" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/core/component" @@ -83,19 +82,11 @@ func (b *broker) checkPrefixAnnouncements() error { } // Get self from Discovery - var announcedPrefixes []types.DevAddrPrefix self, err := b.Component.Discover("broker", b.Component.Identity.Id) if err != nil { return err } - for _, meta := range self.Metadata { - if meta.Key == pb_discovery.Metadata_PREFIX && len(meta.Value) == 5 { - var prefix types.DevAddrPrefix - copy(prefix.DevAddr[:], meta.Value[1:]) - prefix.Length = int(meta.Value[0]) - announcedPrefixes = append(announcedPrefixes, prefix) - } - } + announcedPrefixes := self.DevAddrPrefixes() nextPrefix: for nsPrefix, usage := range nsPrefixes { diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index b8476fea8..4233c45d6 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -87,7 +87,9 @@ func (b *brokerManager) RegisterApplicationHandler(ctx context.Context, in *pb.A if err != nil { return nil, errors.NewErrInternal("Could not get Handler Announcement") } - handler.AddMetadata(discovery.Metadata_APP_ID, []byte(in.AppId)) + handler.Metadata = append(handler.Metadata, &discovery.Metadata{Metadata: &discovery.Metadata_AppId{ + AppId: in.AppId, + }}) return &empty.Empty{}, nil } diff --git a/core/broker/server.go b/core/broker/server.go index f7d66dffb..8c0d35859 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -8,7 +8,6 @@ import ( "github.com/TheThingsNetwork/ttn/api" pb "github.com/TheThingsNetwork/ttn/api/broker" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/api/ratelimit" "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" @@ -90,19 +89,14 @@ func (b *brokerRPC) getHandlerPublish(md metadata.MD) (chan *pb.DownlinkMessage, if err != nil { return } - // Check if this Handler can publish for this AppId - for _, meta := range handler.Metadata { - switch meta.Key { - case pb_discovery.Metadata_APP_ID: - announcedID := string(meta.Value) - if announcedID == downlink.AppId { - if waitTime := b.handlerDownRate.Wait(handler.Id); waitTime != 0 { - b.broker.Ctx.WithField("HandlerID", handler.Id).WithField("Wait", waitTime).Warn("Handler reached downlink rate limit") - time.Sleep(waitTime) - } - b.broker.HandleDownlink(downlink) - return + for _, announcedID := range handler.AppIDs() { + if announcedID == downlink.AppId { + if waitTime := b.handlerDownRate.Wait(handler.Id); waitTime != 0 { + b.broker.Ctx.WithField("HandlerID", handler.Id).WithField("Wait", waitTime).Warn("Handler reached downlink rate limit") + time.Sleep(waitTime) } + b.broker.HandleDownlink(downlink) + return } } }(message) diff --git a/core/discovery/announcement/announcement.go b/core/discovery/announcement/announcement.go index 571d99f6d..75f5955a0 100644 --- a/core/discovery/announcement/announcement.go +++ b/core/discovery/announcement/announcement.go @@ -26,8 +26,9 @@ type AppEUIMetadata struct { // ToProto implements the Metadata interface func (m AppEUIMetadata) ToProto() *pb.Metadata { return &pb.Metadata{ - Key: pb.Metadata_APP_EUI, - Value: m.AppEUI.Bytes(), + Metadata: &pb.Metadata_AppEui{ + AppEui: m.AppEUI.Bytes(), + }, } } @@ -44,8 +45,9 @@ type AppIDMetadata struct { // ToProto implements the Metadata interface func (m AppIDMetadata) ToProto() *pb.Metadata { return &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte(m.AppID), + Metadata: &pb.Metadata_AppId{ + AppId: m.AppID, + }, } } @@ -62,8 +64,9 @@ type PrefixMetadata struct { // ToProto implements the Metadata interface func (m PrefixMetadata) ToProto() *pb.Metadata { return &pb.Metadata{ - Key: pb.Metadata_PREFIX, - Value: []byte{byte(m.Prefix.Length), m.Prefix.DevAddr[0], m.Prefix.DevAddr[1], m.Prefix.DevAddr[2], m.Prefix.DevAddr[3]}, + Metadata: &pb.Metadata_DevAddrPrefix{ + DevAddrPrefix: m.Prefix.Bytes(), + }, } } @@ -72,47 +75,24 @@ func (m PrefixMetadata) MarshalText() ([]byte, error) { return []byte(fmt.Sprintf("Prefix %s", m.Prefix)), nil } -// OtherMetadata is used to store arbitrary information -type OtherMetadata struct { - Data string -} - -// ToProto implements the Metadata interface -func (m OtherMetadata) ToProto() *pb.Metadata { - return &pb.Metadata{ - Key: pb.Metadata_OTHER, - Value: []byte(m.Data), - } -} - -// MarshalText implements the encoding.TextMarshaler interface -func (m OtherMetadata) MarshalText() ([]byte, error) { - return []byte(fmt.Sprintf("Other %s", m.Data)), nil -} - // MetadataFromProto converts a protocol buffer metadata to a Metadata func MetadataFromProto(proto *pb.Metadata) Metadata { - switch proto.Key { - case pb.Metadata_APP_EUI: - appEUI := new(types.AppEUI) - appEUI.UnmarshalBinary(proto.Value) - return AppEUIMetadata{*appEUI} - case pb.Metadata_APP_ID: - return AppIDMetadata{string(proto.Value)} - case pb.Metadata_PREFIX: - prefix := types.DevAddrPrefix{ - Length: 32, + if euiBytes := proto.GetAppEui(); euiBytes != nil { + eui := new(types.AppEUI) + if err := eui.Unmarshal(euiBytes); err != nil { + return nil } - if len(proto.Value) == 5 { - prefix.Length = int(proto.Value[0]) - prefix.DevAddr[0] = proto.Value[1] - prefix.DevAddr[1] = proto.Value[2] - prefix.DevAddr[2] = proto.Value[3] - prefix.DevAddr[3] = proto.Value[4] + return AppEUIMetadata{*eui} + } + if id := proto.GetAppId(); id != "" { + return AppIDMetadata{id} + } + if prefixBytes := proto.GetDevAddrPrefix(); prefixBytes != nil { + prefix := new(types.DevAddrPrefix) + if err := prefix.Unmarshal(prefixBytes); err != nil { + return nil } - return PrefixMetadata{prefix} - case pb.Metadata_OTHER: - return OtherMetadata{string(proto.Value)} + return PrefixMetadata{*prefix} } return nil } @@ -135,8 +115,6 @@ func MetadataFromString(str string) Metadata { } prefix.UnmarshalText([]byte(value)) return PrefixMetadata{*prefix} - case "Other": - return OtherMetadata{value} } return nil } diff --git a/core/discovery/announcement/announcement_test.go b/core/discovery/announcement/announcement_test.go index b45eb4a7e..9e9b60dad 100644 --- a/core/discovery/announcement/announcement_test.go +++ b/core/discovery/announcement/announcement_test.go @@ -57,16 +57,14 @@ func TestAnnouncementToProto(t *testing.T) { AppEUIMetadata{types.AppEUI([8]byte{1, 2, 3, 4, 5, 6, 7, 8})}, AppIDMetadata{"AppID"}, PrefixMetadata{types.DevAddrPrefix{}}, - OtherMetadata{}, }, } proto := announcement.ToProto() a.So(proto.Id, ShouldEqual, announcement.ID) - a.So(proto.Metadata, ShouldHaveLength, 4) - a.So(proto.Metadata[0].Key, ShouldEqual, pb.Metadata_APP_EUI) - a.So(proto.Metadata[1].Key, ShouldEqual, pb.Metadata_APP_ID) - a.So(proto.Metadata[2].Key, ShouldEqual, pb.Metadata_PREFIX) - a.So(proto.Metadata[3].Key, ShouldEqual, pb.Metadata_OTHER) + a.So(proto.Metadata, ShouldHaveLength, 3) + a.So(proto.Metadata[0].GetAppEui(), ShouldResemble, []byte{1, 2, 3, 4, 5, 6, 7, 8}) + a.So(proto.Metadata[1].GetAppId(), ShouldEqual, "AppID") + a.So(proto.Metadata[2].GetDevAddrPrefix(), ShouldResemble, []byte{0, 0, 0, 0, 0}) } func TestAnnouncementFromProto(t *testing.T) { @@ -74,13 +72,12 @@ func TestAnnouncementFromProto(t *testing.T) { proto := &pb.Announcement{ Id: "ID", Metadata: []*pb.Metadata{ - &pb.Metadata{Key: pb.Metadata_APP_EUI, Value: []byte{1, 2, 3, 4, 5, 6, 7, 8}}, - &pb.Metadata{Key: pb.Metadata_APP_ID, Value: []byte("AppID")}, - &pb.Metadata{Key: pb.Metadata_PREFIX, Value: []byte{0, 0, 0, 0, 0}}, - &pb.Metadata{Key: pb.Metadata_OTHER, Value: []byte{}}, + &pb.Metadata{Metadata: &pb.Metadata_AppEui{AppEui: []byte{1, 2, 3, 4, 5, 6, 7, 8}}}, + &pb.Metadata{Metadata: &pb.Metadata_AppId{AppId: "AppID"}}, + &pb.Metadata{Metadata: &pb.Metadata_DevAddrPrefix{DevAddrPrefix: []byte{0, 0, 0, 0, 0}}}, }, } announcement := FromProto(proto) a.So(announcement.ID, ShouldEqual, proto.Id) - a.So(announcement.Metadata, ShouldHaveLength, 4) + a.So(announcement.Metadata, ShouldHaveLength, 3) } diff --git a/core/discovery/announcement/cache_test.go b/core/discovery/announcement/cache_test.go index 9fdeb9add..0a4558e12 100644 --- a/core/discovery/announcement/cache_test.go +++ b/core/discovery/announcement/cache_test.go @@ -65,7 +65,6 @@ func TestCachedAnnouncementStore(t *testing.T) { err = s.AddMetadata("handler", "handler1", AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) @@ -83,13 +82,12 @@ func TestCachedAnnouncementStore(t *testing.T) { AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, AppIDMetadata{AppID: "OtherAppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) metadata, err := s.GetMetadata("handler", "handler2") a.So(err, ShouldBeNil) - a.So(metadata, ShouldHaveLength, 4) + a.So(metadata, ShouldHaveLength, 3) err = s.AddMetadata("handler", "handler2", AppEUIMetadata{AppEUI: appEUI}, @@ -99,7 +97,7 @@ func TestCachedAnnouncementStore(t *testing.T) { metadata, err = s.GetMetadata("handler", "handler2") a.So(err, ShouldBeNil) - a.So(metadata, ShouldHaveLength, 4) + a.So(metadata, ShouldHaveLength, 3) handler, err = s.GetForAppEUI(appEUI) a.So(err, ShouldBeNil) @@ -114,14 +112,12 @@ func TestCachedAnnouncementStore(t *testing.T) { err = s.RemoveMetadata("handler", "handler1", AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) err = s.RemoveMetadata("handler", "handler2", AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) diff --git a/core/discovery/announcement/store_test.go b/core/discovery/announcement/store_test.go index 065efed7e..2301e450d 100644 --- a/core/discovery/announcement/store_test.go +++ b/core/discovery/announcement/store_test.go @@ -63,7 +63,6 @@ func TestRedisAnnouncementStore(t *testing.T) { err = s.AddMetadata("handler", "handler1", AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) @@ -81,13 +80,12 @@ func TestRedisAnnouncementStore(t *testing.T) { AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, AppIDMetadata{AppID: "OtherAppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) metadata, err := s.GetMetadata("handler", "handler2") a.So(err, ShouldBeNil) - a.So(metadata, ShouldHaveLength, 4) + a.So(metadata, ShouldHaveLength, 3) err = s.AddMetadata("handler", "handler2", AppEUIMetadata{AppEUI: appEUI}, @@ -97,7 +95,7 @@ func TestRedisAnnouncementStore(t *testing.T) { metadata, err = s.GetMetadata("handler", "handler2") a.So(err, ShouldBeNil) - a.So(metadata, ShouldHaveLength, 4) + a.So(metadata, ShouldHaveLength, 3) handler, err = s.GetForAppEUI(appEUI) a.So(err, ShouldBeNil) @@ -112,14 +110,12 @@ func TestRedisAnnouncementStore(t *testing.T) { err = s.RemoveMetadata("handler", "handler1", AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) err = s.RemoveMetadata("handler", "handler2", AppEUIMetadata{AppEUI: appEUI}, AppIDMetadata{AppID: "AppID"}, - OtherMetadata{}, ) a.So(err, ShouldBeNil) diff --git a/core/discovery/discovery_test.go b/core/discovery/discovery_test.go index 33895dad1..2f1feb742 100644 --- a/core/discovery/discovery_test.go +++ b/core/discovery/discovery_test.go @@ -108,12 +108,10 @@ func TestDiscoveryMetadata(t *testing.T) { }() broker3 := &pb.Announcement{ServiceName: "broker", Id: "broker3", Metadata: []*pb.Metadata{&pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-1"), + Metadata: &pb.Metadata_AppId{AppId: "app-id-1"}, }}} broker4 := &pb.Announcement{ServiceName: "broker", Id: "broker4", Metadata: []*pb.Metadata{&pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, }}} // Announce should not change metadata @@ -127,8 +125,7 @@ func TestDiscoveryMetadata(t *testing.T) { // AddMetadata should add one err = d.AddMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, }) a.So(err, ShouldBeNil) service, err = d.Get("broker", "broker3") @@ -142,8 +139,7 @@ func TestDiscoveryMetadata(t *testing.T) { // AddMetadata again should not add one err = d.AddMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, }) service, err = d.Get("broker", "broker3") a.So(err, ShouldBeNil) @@ -151,8 +147,7 @@ func TestDiscoveryMetadata(t *testing.T) { // DeleteMetadata for non-existing should not delete one err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-3"), + Metadata: &pb.Metadata_AppId{AppId: "app-id-3"}, }) a.So(err, ShouldBeNil) service, err = d.Get("broker", "broker3") @@ -168,8 +163,7 @@ func TestDiscoveryMetadata(t *testing.T) { // DeleteMetadata should delete one err = d.DeleteMetadata("broker", "broker3", &pb.Metadata{ - Key: pb.Metadata_APP_ID, - Value: []byte("app-id-2"), + Metadata: &pb.Metadata_AppId{AppId: "app-id-2"}, }) a.So(err, ShouldBeNil) service, err = d.Get("broker", "broker3") diff --git a/core/discovery/server.go b/core/discovery/server.go index e6fac3a0d..3fa554d31 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -27,45 +27,59 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me if err != nil { return err } - switch in.Metadata.Key { - case pb.Metadata_PREFIX: - if in.ServiceName != "broker" { - return errPermissionDeniedf("Announcement service type should be \"broker\"") - } - // Only allow prefix announcements if token is issued by a master auth server (or if in dev mode) - if d.discovery.Component.Identity.Id != "dev" && !d.discovery.IsMasterAuthServer(claims.Issuer) { - return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer) - } - if claims.Type != in.ServiceName { - return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) - } - if claims.Subject != in.Id { - return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.Id) - } - // TODO: Check if this PREFIX can be announced - case pb.Metadata_APP_EUI: - if in.ServiceName != "handler" { - return errPermissionDeniedf("Announcement service type should be \"handler\"") - } - // Only allow eui announcements if token is issued by a master auth server (or if in dev mode) - if d.discovery.Component.Identity.Id != "dev" && !d.discovery.IsMasterAuthServer(claims.Issuer) { - return errPermissionDeniedf("Token issuer %s is not allowed to make changes to the network settings", claims.Issuer) + + appEUI := in.Metadata.GetAppEui() + appID := in.Metadata.GetAppId() + prefix := in.Metadata.GetDevAddrPrefix() + + if appEUI == nil && appID == "" && prefix == nil { + return errPermissionDeniedf("Unknown Metadata type") + } + + // AppEUI and AppID can only be added to Handlers + if (appEUI != nil || appID != "") && in.ServiceName != "handler" { + return errPermissionDeniedf("Announcement service type should be \"handler\"") + } + + // DevAddrPrefix can only be added to Brokers + if prefix != nil && in.ServiceName != "broker" { + return errPermissionDeniedf("Announcement service type should be \"broker\"") + } + + // DevAddrPrefix and AppEUI are network level changes + if prefix != nil || appEUI != nil { + + // If not in develop mode + if d.discovery.Component.Identity.Id != "dev" { + + // We require a signature from a master auth server + if !d.discovery.IsMasterAuthServer(claims.Issuer) { + return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer) + } + + // DevAddrPrefixes can not be announced yet + if prefix != nil { + return errPermissionDeniedf("Can not announce DevAddrPrefixes at this time") + } + + // AppEUI can not be announced yet + if appEUI != nil { + return errPermissionDeniedf("Can not announce AppEUIs at this time") + } } + + // Can only be announced to "self" if claims.Type != in.ServiceName { return errPermissionDeniedf("Token type %s does not correspond with announcement service type %s", claims.Type, in.ServiceName) } if claims.Subject != in.Id { return errPermissionDeniedf("Token subject %s does not correspond with announcement id %s", claims.Subject, in.Id) } - // TODO: Check if this APP_EUI can be announced - return errPermissionDeniedf("Can not announce AppEUIs at this time") - case pb.Metadata_APP_ID: - if in.ServiceName != "handler" { - return errPermissionDeniedf("Announcement service type should be \"handler\"") - } - // Allow APP_ID announcements from all trusted auth servers - // When announcing APP_ID, token is user token that contains apps - if !claims.AppRight(string(in.Metadata.Value), rights.AppSettings) { + } + + // Check claims for AppID + if appID != "" { + if !claims.AppRight(appID, rights.AppSettings) { return errPermissionDeniedf("No access to this application") } } @@ -129,7 +143,7 @@ func (d *discoveryServer) DeleteMetadata(ctx context.Context, in *pb.MetadataReq return &empty.Empty{}, nil } -func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { +func (d *discoveryServer) GetAll(ctx context.Context, req *pb.GetServiceRequest) (*pb.AnnouncementsResponse, error) { services, err := d.discovery.GetAll(req.ServiceName) if err != nil { return nil, err diff --git a/core/discovery/server_test.go b/core/discovery/server_test.go index 0b3f424e7..757e44051 100644 --- a/core/discovery/server_test.go +++ b/core/discovery/server_test.go @@ -57,7 +57,7 @@ func (d *mockDiscoveryServer) Announce(ctx context.Context, announcement *pb.Ann <-time.After(5 * time.Millisecond) return &empty.Empty{}, nil } -func (d *mockDiscoveryServer) GetAll(ctx context.Context, req *pb.GetAllRequest) (*pb.AnnouncementsResponse, error) { +func (d *mockDiscoveryServer) GetAll(ctx context.Context, req *pb.GetServiceRequest) (*pb.AnnouncementsResponse, error) { d.discover++ <-time.After(5 * time.Millisecond) return &pb.AnnouncementsResponse{ diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index a91e2f954..a0bb02719 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -11,7 +11,6 @@ import ( "github.com/TheThingsNetwork/go-account-lib/rights" "github.com/TheThingsNetwork/ttn/api" pb_broker "github.com/TheThingsNetwork/ttn/api/broker" - pb_discovery "github.com/TheThingsNetwork/ttn/api/discovery" pb "github.com/TheThingsNetwork/ttn/api/handler" pb_lorawan "github.com/TheThingsNetwork/ttn/api/protocol/lorawan" "github.com/TheThingsNetwork/ttn/api/ratelimit" @@ -340,9 +339,8 @@ func (h *handlerManager) RegisterApplication(ctx context.Context, in *pb.Applica return nil, err } - md, _ := metadata.FromContext(ctx) - token, _ := md["token"] - err = h.handler.Discovery.AddMetadata(pb_discovery.Metadata_APP_ID, []byte(in.AppId), token[0]) + token, _ := api.TokenFromContext(ctx) + err = h.handler.Discovery.AddAppID(in.AppId, token) if err != nil { h.handler.Ctx.WithField("AppID", in.AppId).WithError(err).Warn("Could not register Application with Discovery") } @@ -428,9 +426,8 @@ func (h *handlerManager) DeleteApplication(ctx context.Context, in *pb.Applicati return nil, err } - md, _ := metadata.FromContext(ctx) - token, _ := md["token"] - err = h.handler.Discovery.DeleteMetadata(pb_discovery.Metadata_APP_ID, []byte(in.AppId), token[0]) + token, _ := api.TokenFromContext(ctx) + err = h.handler.Discovery.RemoveAppID(in.AppId, token) if err != nil { h.handler.Ctx.WithField("AppID", in.AppId).WithError(errors.FromGRPCError(err)).Warn("Could not unregister Application from Discovery") } diff --git a/core/types/dev_addr.go b/core/types/dev_addr.go index 7cd1d5085..01acbfc18 100644 --- a/core/types/dev_addr.go +++ b/core/types/dev_addr.go @@ -121,6 +121,13 @@ func ParseDevAddrPrefix(prefixString string) (prefix DevAddrPrefix, err error) { return } +// Bytes returns the DevAddrPrefix as a byte slice +func (prefix DevAddrPrefix) Bytes() (bytes []byte) { + bytes = append(bytes, byte(prefix.Length)) + bytes = append(bytes, prefix.DevAddr.Bytes()...) + return bytes +} + // String implements the fmt.Stringer interface func (prefix DevAddrPrefix) String() string { var addr string @@ -147,6 +154,45 @@ func (prefix *DevAddrPrefix) UnmarshalText(data []byte) error { return nil } +// MarshalBinary implements the BinaryMarshaler interface. +func (prefix DevAddrPrefix) MarshalBinary() ([]byte, error) { + return prefix.Bytes(), nil +} + +// UnmarshalBinary implements the BinaryUnmarshaler interface. +func (prefix *DevAddrPrefix) UnmarshalBinary(data []byte) error { + if len(data) != 5 { + return errors.New("ttn/core: Invalid length for DevAddrPrefix") + } + copy(prefix.DevAddr[:], data[1:]) + prefix.Length = int(data[0]) + prefix.DevAddr = prefix.DevAddr.Mask(prefix.Length) + return nil +} + +// MarshalTo is used by Protobuf +func (prefix DevAddrPrefix) MarshalTo(b []byte) (int, error) { + copy(b, prefix.Bytes()) + return 4, nil +} + +// Size is used by Protobuf +func (prefix DevAddrPrefix) Size() int { + return 5 +} + +// Marshal implements the Marshaler interface. +func (prefix DevAddrPrefix) Marshal() ([]byte, error) { + return prefix.MarshalBinary() +} + +// Unmarshal implements the Unmarshaler interface. +func (prefix *DevAddrPrefix) Unmarshal(data []byte) error { + prefix.DevAddr = [4]byte{} // Reset the receiver + prefix.Length = 0 + return prefix.UnmarshalBinary(data) +} + // Mask returns a copy of the DevAddr with only the first "bits" bits func (addr DevAddr) Mask(bits int) (masked DevAddr) { return empty.WithPrefix(DevAddrPrefix{addr, bits}) diff --git a/core/types/dev_addr_test.go b/core/types/dev_addr_test.go index 0dda1c4a4..3a3fd0321 100644 --- a/core/types/dev_addr_test.go +++ b/core/types/dev_addr_test.go @@ -132,7 +132,7 @@ func TestParseDevAddrPrefix(t *testing.T) { a.So(prefix.Length, ShouldEqual, 1) } -func TestMarshalUnmarshalTextDevAddrPrefix(t *testing.T) { +func TestMarshalUnmarshalDevAddrPrefix(t *testing.T) { a := New(t) var prefix DevAddrPrefix @@ -148,4 +148,30 @@ func TestMarshalUnmarshalTextDevAddrPrefix(t *testing.T) { txt, err = prefix.MarshalText() a.So(err, ShouldBeNil) a.So(string(txt), ShouldEqual, "80000000/1") + + prefix = DevAddrPrefix{} + + bin, err := prefix.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(bin, ShouldResemble, []byte{0x00, 0x00, 0x00, 0x00, 0x00}) + + err = prefix.UnmarshalBinary([]byte{0x01, 0xff, 0x55, 0x66, 0x77}) + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) + + bin, err = prefix.MarshalBinary() + a.So(err, ShouldBeNil) + a.So(bin, ShouldResemble, []byte{0x01, 0x80, 0x00, 0x00, 0x00}) + + prefix = DevAddrPrefix{} + + bin, err = prefix.Marshal() + a.So(err, ShouldBeNil) + a.So(bin, ShouldResemble, []byte{0x00, 0x00, 0x00, 0x00, 0x00}) + + err = prefix.Unmarshal([]byte{0x01, 0x80, 0x00, 0x00, 0x00}) + a.So(err, ShouldBeNil) + a.So(prefix.DevAddr, ShouldEqual, DevAddr{128, 0, 0, 0}) + a.So(prefix.Length, ShouldEqual, 1) } diff --git a/ttnctl/cmd/discover.go b/ttnctl/cmd/discover.go index 632c54563..a4cba21f9 100644 --- a/ttnctl/cmd/discover.go +++ b/ttnctl/cmd/discover.go @@ -23,33 +23,6 @@ func crop(in string, length int) string { return in } -func listDevAddrPrefixes(in []*discovery.Metadata) (prefixes []types.DevAddrPrefix) { - for _, meta := range in { - if meta.Key != discovery.Metadata_PREFIX || len(meta.Value) != 5 { - continue - } - prefix := types.DevAddrPrefix{ - Length: int(meta.Value[0]), - } - prefix.DevAddr[0] = meta.Value[1] - prefix.DevAddr[1] = meta.Value[2] - prefix.DevAddr[2] = meta.Value[3] - prefix.DevAddr[3] = meta.Value[4] - prefixes = append(prefixes, prefix) - } - return -} - -func listAppIDs(in []*discovery.Metadata) (appIDs []string) { - for _, meta := range in { - if meta.Key != discovery.Metadata_APP_ID { - continue - } - appIDs = append(appIDs, string(meta.Value)) - } - return -} - var discoverCmd = &cobra.Command{ Use: "discover [ServiceType]", Short: "Discover routing services", @@ -77,7 +50,7 @@ var discoverCmd = &cobra.Command{ conn, client := util.GetDiscovery(ctx) defer conn.Close() - res, err := client.GetAll(util.GetContext(ctx), &discovery.GetAllRequest{ + res, err := client.GetAll(util.GetContext(ctx), &discovery.GetServiceRequest{ ServiceName: serviceType, }) if err != nil { @@ -95,14 +68,14 @@ var discoverCmd = &cobra.Command{ switch serviceType { case "broker": fmt.Println(" DevAddr Prefixes:") - for _, prefix := range listDevAddrPrefixes(service.Metadata) { + for _, prefix := range service.DevAddrPrefixes() { min := types.DevAddr{0x00, 0x00, 0x00, 0x00}.WithPrefix(prefix) max := types.DevAddr{0xff, 0xff, 0xff, 0xff}.WithPrefix(prefix) fmt.Printf(" %s (%s-%s)\n", prefix, min, max) } case "handler": fmt.Println(" AppIDs:") - for _, appID := range listAppIDs(service.Metadata) { + for _, appID := range service.AppIDs() { fmt.Println(" ", appID) } } diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go index 31e4c88fc..3a565e3d0 100644 --- a/utils/protoc-gen-ttndoc/json.go +++ b/utils/protoc-gen-ttndoc/json.go @@ -17,8 +17,8 @@ var exampleValues = map[string]interface{}{ ".discovery.Announcement.public_key": "-----BEGIN PUBLIC KEY-----\n...", ".discovery.Announcement.public": true, ".discovery.Announcement.service_version": "2.0.0-dev-abcdef...", - ".discovery.Metadata.key": "APP_ID", - ".discovery.Metadata.value": "c29tZS1hcHAtaWQ=", + ".discovery.Metadata.dev_addr_prefix": "AAAAAAA=", + ".discovery.Metadata.app_id": "some-app-id", ".handler.*.app_id": "some-app-id", ".handler.*.dev_id": "some-dev-id", ".handler.*.fields": `{"light":100}`, From 2f690dfab266061c732b575ffce0cdeb2ed68cdf Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 18:07:39 +0100 Subject: [PATCH 2223/2266] Allow multiple downlink subscriptions per gateway It is not recommended to open multiple subscriptions. If you do this anyway, you are responsible for de-duplication of downlink messages. --- api/router/router.pb.go | 4 ++ api/router/router.proto | 2 + core/router/downlink.go | 8 +-- core/router/downlink_test.go | 4 +- core/router/gateway/schedule.go | 92 +++++++++++++++++++++------- core/router/gateway/schedule_test.go | 4 +- core/router/router.go | 4 +- core/router/server.go | 6 +- 8 files changed, 89 insertions(+), 35 deletions(-) diff --git a/api/router/router.pb.go b/api/router/router.pb.go index e5c39fb0a..d098268db 100644 --- a/api/router/router.pb.go +++ b/api/router/router.pb.go @@ -300,6 +300,8 @@ type RouterClient interface { // Gateway streams uplink messages to Router Uplink(ctx context.Context, opts ...grpc.CallOption) (Router_UplinkClient, error) // Gateway subscribes to downlink messages from Router + // It is possible to open multiple subscriptions (but not recommended). + // If you do this, you are responsible for de-duplication of downlink messages. Subscribe(ctx context.Context, in *SubscribeRequest, opts ...grpc.CallOption) (Router_SubscribeClient, error) // Gateway requests device activation Activate(ctx context.Context, in *DeviceActivationRequest, opts ...grpc.CallOption) (*DeviceActivationResponse, error) @@ -430,6 +432,8 @@ type RouterServer interface { // Gateway streams uplink messages to Router Uplink(Router_UplinkServer) error // Gateway subscribes to downlink messages from Router + // It is possible to open multiple subscriptions (but not recommended). + // If you do this, you are responsible for de-duplication of downlink messages. Subscribe(*SubscribeRequest, Router_SubscribeServer) error // Gateway requests device activation Activate(context.Context, *DeviceActivationRequest) (*DeviceActivationResponse, error) diff --git a/api/router/router.proto b/api/router/router.proto index b017c6e61..da1ed4330 100644 --- a/api/router/router.proto +++ b/api/router/router.proto @@ -58,6 +58,8 @@ service Router { rpc Uplink(stream UplinkMessage) returns (google.protobuf.Empty); // Gateway subscribes to downlink messages from Router + // It is possible to open multiple subscriptions (but not recommended). + // If you do this, you are responsible for de-duplication of downlink messages. rpc Subscribe(SubscribeRequest) returns (stream DownlinkMessage); // Gateway requests device activation diff --git a/core/router/downlink.go b/core/router/downlink.go index ba836583a..c21bfa4f9 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -22,13 +22,13 @@ import ( "github.com/apex/log" ) -func (r *router) SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage, error) { +func (r *router) SubscribeDownlink(gatewayID string, subscriptionID string) (<-chan *pb.DownlinkMessage, error) { ctx := r.Ctx.WithFields(log.Fields{ "GatewayID": gatewayID, }) gateway := r.getGateway(gatewayID) - if fromSchedule := gateway.Schedule.Subscribe(); fromSchedule != nil { + if fromSchedule := gateway.Schedule.Subscribe(subscriptionID); fromSchedule != nil { toGateway := make(chan *pb.DownlinkMessage) go func() { ctx.Debug("Activate downlink") @@ -45,8 +45,8 @@ func (r *router) SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage return nil, errors.NewErrInternal(fmt.Sprintf("Already subscribed to downlink for %s", gatewayID)) } -func (r *router) UnsubscribeDownlink(gatewayID string) error { - r.getGateway(gatewayID).Schedule.Stop() +func (r *router) UnsubscribeDownlink(gatewayID string, subscriptionID string) error { + r.getGateway(gatewayID).Schedule.Stop(subscriptionID) return nil } diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index e3b8ca144..252ddf170 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -77,7 +77,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { gtw.Schedule.Sync(0) id, _ := gtw.Schedule.GetOption(5000, 10*1000) - ch, err := r.SubscribeDownlink(gtwID) + ch, err := r.SubscribeDownlink(gtwID, "") a.So(err, ShouldBeNil) var wg sync.WaitGroup @@ -105,7 +105,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { // Wait for the downlink to arrive <-time.After(10 * time.Millisecond) - err = r.UnsubscribeDownlink(gtwID) + err = r.UnsubscribeDownlink(gtwID, "") a.So(err, ShouldBeNil) wg.Wait() diff --git a/core/router/gateway/schedule.go b/core/router/gateway/schedule.go index 93487b15a..faaaa2597 100644 --- a/core/router/gateway/schedule.go +++ b/core/router/gateway/schedule.go @@ -27,11 +27,11 @@ type Schedule interface { // Schedule a transmission on a slot Schedule(id string, downlink *router_pb.DownlinkMessage) error // Subscribe to downlink messages - Subscribe() <-chan *router_pb.DownlinkMessage + Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage // Whether the gateway has active downlink IsActive() bool // Stop the subscription - Stop() + Stop(subscriptionID string) } // NewSchedule creates a new Schedule @@ -39,6 +39,7 @@ func NewSchedule(ctx log.Interface) Schedule { s := &schedule{ ctx: ctx, items: make(map[string]*scheduledItem), + downlinkSubscriptions: make(map[string]chan *router_pb.DownlinkMessage), } go func() { for { @@ -72,10 +73,12 @@ type scheduledItem struct { type schedule struct { sync.RWMutex - ctx log.Interface - offset int64 - items map[string]*scheduledItem - downlink chan *router_pb.DownlinkMessage + ctx log.Interface + offset int64 + items map[string]*scheduledItem + downlink chan *router_pb.DownlinkMessage + downlinkSubscriptionsLock sync.RWMutex + downlinkSubscriptions map[string]chan *router_pb.DownlinkMessage } func (s *schedule) GoString() (str string) { @@ -190,21 +193,29 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro waitTime := item.deadlineAt.Sub(time.Now()) ctx.WithField("Remaining", waitTime).Info("Scheduled downlink") <-time.After(waitTime) + s.RLock() + defer s.RUnlock() if s.downlink != nil { s.downlink <- item.payload } }() - } else if s.downlink != nil { - overdue := time.Now().Sub(item.deadlineAt) - if overdue < Deadline { - // Immediately send it - ctx.WithField("Overdue", overdue).Warn("Send Late Downlink") - s.downlink <- item.payload - } else { - ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink") - } } else { - ctx.Warn("Unable to send Downlink") + go func() { + s.RLock() + defer s.RUnlock() + if s.downlink != nil { + overdue := time.Now().Sub(item.deadlineAt) + if overdue < Deadline { + // Immediately send it + ctx.WithField("Overdue", overdue).Warn("Send Late Downlink") + s.downlink <- item.payload + } else { + ctx.WithField("Overdue", overdue).Warn("Discard Late Downlink") + } + } else { + ctx.Warn("Unable to send Downlink") + } + }() } return nil @@ -212,19 +223,54 @@ func (s *schedule) Schedule(id string, downlink *router_pb.DownlinkMessage) erro return errors.NewErrNotFound(id) } -func (s *schedule) Stop() { - close(s.downlink) - s.downlink = nil +func (s *schedule) Stop(subscriptionID string) { + s.downlinkSubscriptionsLock.Lock() + defer s.downlinkSubscriptionsLock.Unlock() + if sub, ok := s.downlinkSubscriptions[subscriptionID]; ok { + close(sub) + delete(s.downlinkSubscriptions, subscriptionID) + } + if len(s.downlinkSubscriptions) == 0 { + s.Lock() + defer s.Unlock() + close(s.downlink) + s.downlink = nil + } } -func (s *schedule) Subscribe() <-chan *router_pb.DownlinkMessage { - if s.downlink != nil { +func (s *schedule) Subscribe(subscriptionID string) <-chan *router_pb.DownlinkMessage { + s.Lock() + if s.downlink == nil { + s.downlink = make(chan *router_pb.DownlinkMessage) + go func() { + for downlink := range s.downlink { + s.downlinkSubscriptionsLock.RLock() + for _, ch := range s.downlinkSubscriptions { + select { + case ch <- downlink: + default: + s.ctx.WithField("SubscriptionID", subscriptionID).Warn("Could not send downlink message") + } + } + s.downlinkSubscriptionsLock.RUnlock() + } + }() + } + s.Unlock() + + s.downlinkSubscriptionsLock.Lock() + if _, ok := s.downlinkSubscriptions[subscriptionID]; ok { return nil } - s.downlink = make(chan *router_pb.DownlinkMessage) - return s.downlink + sub := make(chan *router_pb.DownlinkMessage) + s.downlinkSubscriptions[subscriptionID] = sub + s.downlinkSubscriptionsLock.Unlock() + + return sub } func (s *schedule) IsActive() bool { + s.RLock() + defer s.RUnlock() return s.downlink != nil } diff --git a/core/router/gateway/schedule_test.go b/core/router/gateway/schedule_test.go index a5c27ecbc..908182d1a 100644 --- a/core/router/gateway/schedule_test.go +++ b/core/router/gateway/schedule_test.go @@ -130,7 +130,7 @@ func TestScheduleSubscribe(t *testing.T) { go func() { var i int - for out := range s.Subscribe() { + for out := range s.Subscribe("") { switch i { case 0: a.So(out, ShouldEqual, downlink2) @@ -152,7 +152,7 @@ func TestScheduleSubscribe(t *testing.T) { go func() { <-time.After(400 * time.Millisecond) - s.Stop() + s.Stop("") }() <-time.After(500 * time.Millisecond) diff --git a/core/router/router.go b/core/router/router.go index 2a5de1cac..6deefedb0 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -31,9 +31,9 @@ type Router interface { // Handle a downlink message HandleDownlink(message *pb_broker.DownlinkMessage) error // Subscribe to downlink messages - SubscribeDownlink(gatewayID string) (<-chan *pb.DownlinkMessage, error) + SubscribeDownlink(gatewayID string, subscriptionID string) (<-chan *pb.DownlinkMessage, error) // Unsubscribe from downlink messages - UnsubscribeDownlink(gatewayID string) error + UnsubscribeDownlink(gatewayID string, subscriptionID string) error // Handle a device activation HandleActivation(gatewayID string, activation *pb.DeviceActivationRequest) (*pb.DeviceActivationResponse, error) diff --git a/core/router/server.go b/core/router/server.go index 80b0812aa..479c83438 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -14,6 +14,7 @@ import ( pb "github.com/TheThingsNetwork/ttn/api/router" "github.com/TheThingsNetwork/ttn/core/router/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" + "github.com/TheThingsNetwork/ttn/utils/random" "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" @@ -109,11 +110,12 @@ func (r *routerRPC) getDownlink(md metadata.MD) (ch <-chan *pb.DownlinkMessage, if err != nil { return nil, nil, err } + subscriptionID := random.String(10) ch = make(chan *pb.DownlinkMessage) cancel = func() { - r.router.UnsubscribeDownlink(gateway.ID) + r.router.UnsubscribeDownlink(gateway.ID, subscriptionID) } - downlinkChannel, err := r.router.SubscribeDownlink(gateway.ID) + downlinkChannel, err := r.router.SubscribeDownlink(gateway.ID, subscriptionID) if err != nil { return nil, nil, err } From 07db01f00ff700b3e56db8fcf3f3c82b8f91a96f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 18:37:23 +0100 Subject: [PATCH 2224/2266] Fix ratelimit bucket quantum the buckets should generate `rate` instead of `1` tokens per time interval. --- api/ratelimit/ratelimit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/ratelimit/ratelimit.go b/api/ratelimit/ratelimit.go index efb476f02..298a7b1f1 100644 --- a/api/ratelimit/ratelimit.go +++ b/api/ratelimit/ratelimit.go @@ -39,7 +39,7 @@ func (r *Registry) getOrCreate(id string, createFunc func() *ratelimit.Bucket) * } func (r *Registry) newFunc() *ratelimit.Bucket { - return ratelimit.NewBucket(r.per, int64(r.rate)) + return ratelimit.NewBucketWithQuantum(r.per, int64(r.rate), int64(r.rate)) } // Limit returns true if the ratelimit for the given entity has been reached From 8d95492eab3bc338770fc8a1bd7b24130b56057b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 29 Nov 2016 18:45:22 +0100 Subject: [PATCH 2225/2266] Reset stream retries *after* we know the stream is ok --- api/router/client_streams.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/router/client_streams.go b/api/router/client_streams.go index 4553ced28..e7aaec12b 100644 --- a/api/router/client_streams.go +++ b/api/router/client_streams.go @@ -70,7 +70,6 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu retries++ continue } - retries = 0 s.ctx.Info("Started GatewayStatus stream") @@ -100,6 +99,8 @@ func NewMonitoredGatewayStatusStream(client RouterClientForGateway) GatewayStatu monitor: for { select { + case <-time.After(10 * time.Second): + retries = 0 case mErr = <-errCh: break monitor case msg, ok := <-s.ch: @@ -192,7 +193,6 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { retries++ continue } - retries = 0 s.ctx.Info("Started Uplink stream") @@ -222,6 +222,8 @@ func NewMonitoredUplinkStream(client RouterClientForGateway) UplinkStream { monitor: for { select { + case <-time.After(10 * time.Second): + retries = 0 case mErr = <-errCh: break monitor case msg, ok := <-s.ch: @@ -312,7 +314,6 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { retries++ continue } - retries = 0 s.ctx.Info("Started Downlink stream") @@ -329,6 +330,7 @@ func NewMonitoredDownlinkStream(client RouterClientForGateway) DownlinkStream { if err != nil { break } + retries = 0 } if err == nil || err == io.EOF || grpc.Code(err) == codes.Canceled { From 6c8d8843c3755676a830d73bce840c254c594cae Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Nov 2016 14:25:37 +0100 Subject: [PATCH 2226/2266] Check if application is registered before creating devices on Handler Closes #365 --- core/handler/manager_server.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index a0bb02719..e549eb85a 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -78,6 +78,10 @@ func (h *handlerManager) GetDevice(ctx context.Context, in *pb.DeviceIdentifier) return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err @@ -142,6 +146,10 @@ func (h *handlerManager) SetDevice(ctx context.Context, in *pb.Device) (*empty.E return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil && errors.GetErrType(err) != errors.NotFound { return nil, err @@ -238,6 +246,11 @@ func (h *handlerManager) DeleteDevice(ctx context.Context, in *pb.DeviceIdentifi if !claims.AppRight(in.AppId, rights.Devices) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } + + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + dev, err := h.handler.devices.Get(in.AppId, in.DevId) if err != nil { return nil, err @@ -264,6 +277,11 @@ func (h *handlerManager) GetDevicesForApplication(ctx context.Context, in *pb.Ap if !claims.AppRight(in.AppId, rights.Devices) { return nil, errors.NewErrPermissionDenied(fmt.Sprintf(`No "devices" rights to application "%s"`, in.AppId)) } + + if _, err := h.handler.applications.Get(in.AppId); err != nil { + return nil, errors.Wrap(err, "Application not registered to this Handler") + } + devices, err := h.handler.devices.ListForApp(in.AppId) if err != nil { return nil, err From e6d97ebd37ff12ae2b72167a74f891a2734a6fac Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Nov 2016 14:29:01 +0100 Subject: [PATCH 2227/2266] Use oauth secret for ttnctl Closes #363 --- ttnctl/util/account.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index 46a6b7003..ddfe2bc57 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -49,7 +49,8 @@ func GetTokenCache() cache.Cache { // getOAuth gets the OAuth client func getOAuth() *oauth.Config { return oauth.OAuth(viper.GetString("auth-server"), &oauth.Client{ - ID: "ttnctl", + ID: "ttnctl", + Secret: "ttnctl", }) } From 249b772a350e65a8bc095a7437044fc001430d91 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 30 Nov 2016 14:42:44 +0100 Subject: [PATCH 2228/2266] Add host system metrics to gateway status proto Closes #244 --- api/gateway/gateway.pb.go | 401 ++++++++++++++++++++++++++++++++------ api/gateway/gateway.proto | 22 ++- 2 files changed, 366 insertions(+), 57 deletions(-) diff --git a/api/gateway/gateway.pb.go b/api/gateway/gateway.pb.go index dd35d6112..c9823f314 100644 --- a/api/gateway/gateway.pb.go +++ b/api/gateway/gateway.pb.go @@ -85,23 +85,23 @@ func (*TxConfiguration) ProtoMessage() {} func (*TxConfiguration) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{2} } // message Status represents a status update from a Gateway. -// See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat type Status struct { - Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` - Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` - Ip []string `protobuf:"bytes,11,rep,name=ip" json:"ip,omitempty"` - Platform string `protobuf:"bytes,12,opt,name=platform,proto3" json:"platform,omitempty"` - ContactEmail string `protobuf:"bytes,13,opt,name=contact_email,json=contactEmail,proto3" json:"contact_email,omitempty"` - Description string `protobuf:"bytes,14,opt,name=description,proto3" json:"description,omitempty"` - Region string `protobuf:"bytes,15,opt,name=region,proto3" json:"region,omitempty"` - Bridge string `protobuf:"bytes,16,opt,name=bridge,proto3" json:"bridge,omitempty"` - Router string `protobuf:"bytes,17,opt,name=router,proto3" json:"router,omitempty"` - Gps *GPSMetadata `protobuf:"bytes,21,opt,name=gps" json:"gps,omitempty"` - Rtt uint32 `protobuf:"varint,31,opt,name=rtt,proto3" json:"rtt,omitempty"` - RxIn uint32 `protobuf:"varint,41,opt,name=rx_in,json=rxIn,proto3" json:"rx_in,omitempty"` - RxOk uint32 `protobuf:"varint,42,opt,name=rx_ok,json=rxOk,proto3" json:"rx_ok,omitempty"` - TxIn uint32 `protobuf:"varint,43,opt,name=tx_in,json=txIn,proto3" json:"tx_in,omitempty"` - TxOk uint32 `protobuf:"varint,44,opt,name=tx_ok,json=txOk,proto3" json:"tx_ok,omitempty"` + Timestamp uint32 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` + Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Ip []string `protobuf:"bytes,11,rep,name=ip" json:"ip,omitempty"` + Platform string `protobuf:"bytes,12,opt,name=platform,proto3" json:"platform,omitempty"` + ContactEmail string `protobuf:"bytes,13,opt,name=contact_email,json=contactEmail,proto3" json:"contact_email,omitempty"` + Description string `protobuf:"bytes,14,opt,name=description,proto3" json:"description,omitempty"` + Region string `protobuf:"bytes,15,opt,name=region,proto3" json:"region,omitempty"` + Bridge string `protobuf:"bytes,16,opt,name=bridge,proto3" json:"bridge,omitempty"` + Router string `protobuf:"bytes,17,opt,name=router,proto3" json:"router,omitempty"` + Gps *GPSMetadata `protobuf:"bytes,21,opt,name=gps" json:"gps,omitempty"` + Rtt uint32 `protobuf:"varint,31,opt,name=rtt,proto3" json:"rtt,omitempty"` + RxIn uint32 `protobuf:"varint,41,opt,name=rx_in,json=rxIn,proto3" json:"rx_in,omitempty"` + RxOk uint32 `protobuf:"varint,42,opt,name=rx_ok,json=rxOk,proto3" json:"rx_ok,omitempty"` + TxIn uint32 `protobuf:"varint,43,opt,name=tx_in,json=txIn,proto3" json:"tx_in,omitempty"` + TxOk uint32 `protobuf:"varint,44,opt,name=tx_ok,json=txOk,proto3" json:"tx_ok,omitempty"` + Os *Status_OSMetrics `protobuf:"bytes,51,opt,name=os" json:"os,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -116,11 +116,34 @@ func (m *Status) GetGps() *GPSMetadata { return nil } +func (m *Status) GetOs() *Status_OSMetrics { + if m != nil { + return m.Os + } + return nil +} + +// Additional metrics from the operating system +type Status_OSMetrics struct { + Load_1 float32 `protobuf:"fixed32,1,opt,name=load_1,json=load1,proto3" json:"load_1,omitempty"` + Load_5 float32 `protobuf:"fixed32,2,opt,name=load_5,json=load5,proto3" json:"load_5,omitempty"` + Load_15 float32 `protobuf:"fixed32,3,opt,name=load_15,json=load15,proto3" json:"load_15,omitempty"` + CpuPercentage float32 `protobuf:"fixed32,11,opt,name=cpu_percentage,json=cpuPercentage,proto3" json:"cpu_percentage,omitempty"` + MemoryPercentage float32 `protobuf:"fixed32,21,opt,name=memory_percentage,json=memoryPercentage,proto3" json:"memory_percentage,omitempty"` + Temperature float32 `protobuf:"fixed32,31,opt,name=temperature,proto3" json:"temperature,omitempty"` +} + +func (m *Status_OSMetrics) Reset() { *m = Status_OSMetrics{} } +func (m *Status_OSMetrics) String() string { return proto.CompactTextString(m) } +func (*Status_OSMetrics) ProtoMessage() {} +func (*Status_OSMetrics) Descriptor() ([]byte, []int) { return fileDescriptorGateway, []int{3, 0} } + func init() { proto.RegisterType((*GPSMetadata)(nil), "gateway.GPSMetadata") proto.RegisterType((*RxMetadata)(nil), "gateway.RxMetadata") proto.RegisterType((*TxConfiguration)(nil), "gateway.TxConfiguration") proto.RegisterType((*Status)(nil), "gateway.Status") + proto.RegisterType((*Status_OSMetrics)(nil), "gateway.Status.OSMetrics") } func (m *GPSMetadata) Marshal() (dAtA []byte, err error) { size := m.Size() @@ -431,6 +454,70 @@ func (m *Status) MarshalTo(dAtA []byte) (int, error) { i++ i = encodeVarintGateway(dAtA, i, uint64(m.TxOk)) } + if m.Os != nil { + dAtA[i] = 0x9a + i++ + dAtA[i] = 0x3 + i++ + i = encodeVarintGateway(dAtA, i, uint64(m.Os.Size())) + n3, err := m.Os.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + +func (m *Status_OSMetrics) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Status_OSMetrics) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if m.Load_1 != 0 { + dAtA[i] = 0xd + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Load_1)))) + } + if m.Load_5 != 0 { + dAtA[i] = 0x15 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Load_5)))) + } + if m.Load_15 != 0 { + dAtA[i] = 0x1d + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Load_15)))) + } + if m.CpuPercentage != 0 { + dAtA[i] = 0x5d + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.CpuPercentage)))) + } + if m.MemoryPercentage != 0 { + dAtA[i] = 0xad + i++ + dAtA[i] = 0x1 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.MemoryPercentage)))) + } + if m.Temperature != 0 { + dAtA[i] = 0xfd + i++ + dAtA[i] = 0x1 + i++ + i = encodeFixed32Gateway(dAtA, i, uint32(math.Float32bits(float32(m.Temperature)))) + } return i, nil } @@ -596,6 +683,34 @@ func (m *Status) Size() (n int) { if m.TxOk != 0 { n += 2 + sovGateway(uint64(m.TxOk)) } + if m.Os != nil { + l = m.Os.Size() + n += 2 + l + sovGateway(uint64(l)) + } + return n +} + +func (m *Status_OSMetrics) Size() (n int) { + var l int + _ = l + if m.Load_1 != 0 { + n += 5 + } + if m.Load_5 != 0 { + n += 5 + } + if m.Load_15 != 0 { + n += 5 + } + if m.CpuPercentage != 0 { + n += 5 + } + if m.MemoryPercentage != 0 { + n += 6 + } + if m.Temperature != 0 { + n += 6 + } return n } @@ -1526,6 +1641,173 @@ func (m *Status) Unmarshal(dAtA []byte) error { break } } + case 51: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Os", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGateway + } + postIndex := iNdEx + msglen + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Os == nil { + m.Os = &Status_OSMetrics{} + } + if err := m.Os.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGateway(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGateway + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *Status_OSMetrics) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGateway + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: OSMetrics: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: OSMetrics: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load_1", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load_1 = float32(math.Float32frombits(v)) + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load_5", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load_5 = float32(math.Float32frombits(v)) + case 3: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Load_15", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Load_15 = float32(math.Float32frombits(v)) + case 11: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field CpuPercentage", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.CpuPercentage = float32(math.Float32frombits(v)) + case 21: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field MemoryPercentage", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.MemoryPercentage = float32(math.Float32frombits(v)) + case 31: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field Temperature", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + iNdEx += 4 + v = uint32(dAtA[iNdEx-4]) + v |= uint32(dAtA[iNdEx-3]) << 8 + v |= uint32(dAtA[iNdEx-2]) << 16 + v |= uint32(dAtA[iNdEx-1]) << 24 + m.Temperature = float32(math.Float32frombits(v)) default: iNdEx = preIndex skippy, err := skipGateway(dAtA[iNdEx:]) @@ -1657,44 +1939,51 @@ func init() { } var fileDescriptorGateway = []byte{ - // 609 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0x13, 0x31, - 0x10, 0xc6, 0x49, 0xfa, 0x13, 0xa7, 0x69, 0x8b, 0xdb, 0x06, 0x53, 0x41, 0x58, 0x82, 0x84, 0x16, - 0x0a, 0x8d, 0x04, 0xe2, 0xc0, 0x81, 0x0b, 0x05, 0xa1, 0x1e, 0xa0, 0xc8, 0xed, 0x89, 0x4b, 0xe4, - 0xec, 0x3a, 0x1b, 0x2b, 0x1b, 0x7b, 0xf1, 0x7a, 0x9b, 0x94, 0x2b, 0x2f, 0xc1, 0x23, 0x71, 0xe4, - 0x11, 0x50, 0x91, 0x78, 0x0c, 0x84, 0x3c, 0xfb, 0xd3, 0x14, 0x09, 0x2a, 0x4e, 0x3b, 0xdf, 0x8f, - 0x77, 0x66, 0x3c, 0x23, 0xe3, 0xe7, 0x91, 0xb4, 0xe3, 0x6c, 0xb8, 0x1f, 0xe8, 0x69, 0xff, 0x64, - 0x2c, 0x4e, 0xc6, 0x52, 0x45, 0xe9, 0x3b, 0x61, 0x67, 0xda, 0x4c, 0xfa, 0xd6, 0xaa, 0x3e, 0x4f, - 0x64, 0x3f, 0xe2, 0x56, 0xcc, 0xf8, 0x59, 0xf9, 0xdd, 0x4f, 0x8c, 0xb6, 0x9a, 0xac, 0x14, 0x70, - 0xf7, 0xf1, 0xc2, 0x3f, 0x22, 0x1d, 0xe9, 0x3e, 0xe8, 0xc3, 0x6c, 0x04, 0x08, 0x00, 0x44, 0xf9, - 0xb9, 0xde, 0x0c, 0xb7, 0xde, 0xbc, 0x3f, 0x7e, 0x2b, 0x2c, 0x0f, 0xb9, 0xe5, 0x84, 0xe0, 0x86, - 0x95, 0x53, 0x41, 0x91, 0x87, 0xfc, 0x3a, 0x83, 0x98, 0xec, 0xe2, 0xd5, 0x98, 0x5b, 0x69, 0xb3, - 0x50, 0xd0, 0x9a, 0x87, 0xfc, 0x1a, 0xab, 0x30, 0xb9, 0x85, 0x9b, 0xb1, 0x56, 0x51, 0x2e, 0xd6, - 0x41, 0xbc, 0x20, 0xdc, 0x49, 0x1e, 0x17, 0x27, 0x1b, 0x1e, 0xf2, 0x97, 0x58, 0x85, 0x7b, 0xbf, - 0x10, 0xc6, 0x6c, 0x5e, 0x25, 0xbe, 0x8d, 0x71, 0xd1, 0xc1, 0x40, 0x86, 0x90, 0xbe, 0xc9, 0x9a, - 0x05, 0x73, 0x18, 0xba, 0x3c, 0xae, 0x96, 0xd4, 0xf2, 0x69, 0x42, 0x5b, 0x1e, 0xf2, 0xdb, 0xec, - 0x82, 0xa8, 0xaa, 0x5e, 0x5b, 0xa8, 0xfa, 0x26, 0x5e, 0x35, 0xa3, 0x41, 0x30, 0xe6, 0x52, 0xd1, - 0x1d, 0x38, 0xb0, 0x62, 0x46, 0x07, 0x0e, 0x12, 0x8a, 0x57, 0x82, 0x31, 0x57, 0x4a, 0xc4, 0xb4, - 0x93, 0x2b, 0x05, 0x74, 0x69, 0x46, 0x46, 0x7c, 0xcc, 0x84, 0x0a, 0xce, 0xe8, 0x1d, 0x0f, 0xf9, - 0x0d, 0x76, 0x41, 0xb8, 0x34, 0x26, 0x4d, 0x25, 0xf5, 0xa0, 0x4f, 0x88, 0xc9, 0x26, 0xae, 0xa7, - 0xca, 0xd0, 0xbb, 0x40, 0xb9, 0x90, 0xdc, 0xc7, 0xf5, 0x28, 0x49, 0xe9, 0x03, 0x0f, 0xf9, 0xad, - 0x27, 0xdb, 0xfb, 0xe5, 0x98, 0x16, 0x6e, 0x99, 0x39, 0x43, 0xef, 0x27, 0xc2, 0x1b, 0x27, 0xf3, - 0x03, 0xad, 0x46, 0x32, 0xca, 0x0c, 0xb7, 0x52, 0xab, 0x2b, 0xda, 0xfc, 0x47, 0x4b, 0x97, 0x0a, - 0xef, 0xfc, 0x59, 0xf8, 0x36, 0x5e, 0x4a, 0xf4, 0x4c, 0x18, 0x7a, 0x03, 0x86, 0x90, 0x03, 0xf2, - 0x0c, 0x77, 0x12, 0x1d, 0x73, 0x23, 0x3f, 0x41, 0xf2, 0x81, 0x54, 0xa7, 0xc2, 0xa4, 0x52, 0x2b, - 0xe8, 0x7c, 0x95, 0xed, 0x2c, 0xaa, 0x87, 0xa5, 0x48, 0xfa, 0x78, 0xab, 0xfa, 0xf3, 0x20, 0x14, - 0xa7, 0x12, 0x74, 0xb8, 0x94, 0x36, 0x23, 0x95, 0xf4, 0xaa, 0x54, 0x7a, 0x9f, 0xeb, 0x78, 0xf9, - 0xd8, 0x72, 0x9b, 0xa5, 0x97, 0xfb, 0x43, 0x7f, 0x1b, 0x63, 0x6d, 0x61, 0x8c, 0xeb, 0xb8, 0x26, - 0xdd, 0x55, 0xd4, 0xfd, 0x26, 0xab, 0xc9, 0xc4, 0xad, 0x54, 0x12, 0x73, 0x3b, 0xd2, 0x66, 0x0a, - 0xe3, 0x6e, 0xb2, 0x0a, 0x93, 0x7b, 0xb8, 0x1d, 0x68, 0x65, 0x79, 0x60, 0x07, 0x62, 0xca, 0x65, - 0x4c, 0xdb, 0x60, 0x58, 0x2b, 0xc8, 0xd7, 0x8e, 0x23, 0x1e, 0x6e, 0x85, 0x22, 0x0d, 0x8c, 0x4c, - 0xa0, 0xec, 0x75, 0xb0, 0x2c, 0x52, 0xa4, 0x83, 0x97, 0x8d, 0x88, 0x9c, 0xb8, 0x01, 0x62, 0x81, - 0x1c, 0x3f, 0x34, 0x32, 0x8c, 0x04, 0xdd, 0xcc, 0xf9, 0x1c, 0x81, 0x5f, 0x67, 0x56, 0x18, 0x7a, - 0xbd, 0xf0, 0x03, 0x2a, 0x17, 0x61, 0xe7, 0x8a, 0x45, 0x70, 0x2b, 0x64, 0xac, 0x85, 0x4b, 0x6f, - 0x33, 0x17, 0x92, 0x2d, 0xbc, 0x64, 0xe6, 0x03, 0xa9, 0x60, 0x89, 0xda, 0xac, 0x61, 0xe6, 0x87, - 0xaa, 0x20, 0xf5, 0x84, 0x3e, 0x2c, 0xc9, 0xa3, 0x89, 0x23, 0x2d, 0x38, 0xf7, 0x72, 0xd2, 0x16, - 0x4e, 0x0b, 0xce, 0x47, 0x25, 0x79, 0x34, 0x79, 0xf9, 0xe2, 0xeb, 0x79, 0x17, 0x7d, 0x3b, 0xef, - 0xa2, 0xef, 0xe7, 0x5d, 0xf4, 0xe5, 0x47, 0xf7, 0xda, 0x87, 0xbd, 0xff, 0x78, 0x6d, 0x86, 0xcb, - 0xf0, 0x5c, 0x3c, 0xfd, 0x1d, 0x00, 0x00, 0xff, 0xff, 0xda, 0x61, 0xf5, 0x33, 0xa3, 0x04, 0x00, - 0x00, + // 727 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x6e, 0xe3, 0x44, + 0x1c, 0xc7, 0xce, 0x57, 0x33, 0xd9, 0x74, 0xbb, 0xb3, 0x9b, 0xec, 0xb4, 0x82, 0x60, 0x82, 0x40, + 0x29, 0x85, 0x44, 0xa5, 0xca, 0x81, 0x03, 0x17, 0x0a, 0x42, 0x3d, 0x40, 0xab, 0x69, 0x4f, 0x5c, + 0xac, 0x89, 0x3d, 0x71, 0x46, 0xb1, 0x67, 0xcc, 0x78, 0xdc, 0xa4, 0x3c, 0x09, 0x8f, 0xd4, 0x23, + 0x8f, 0x80, 0x8a, 0xc4, 0x3b, 0x70, 0x41, 0x68, 0xfe, 0x76, 0x5c, 0x17, 0x09, 0xaa, 0x3d, 0x79, + 0x7e, 0x1f, 0xe3, 0xff, 0xa7, 0x06, 0x7d, 0x15, 0x09, 0xb3, 0xca, 0x17, 0xd3, 0x40, 0x25, 0xb3, + 0x9b, 0x15, 0xbf, 0x59, 0x09, 0x19, 0x65, 0x3f, 0x72, 0xb3, 0x51, 0x7a, 0x3d, 0x33, 0x46, 0xce, + 0x58, 0x2a, 0x66, 0x11, 0x33, 0x7c, 0xc3, 0xee, 0x76, 0xdf, 0x69, 0xaa, 0x95, 0x51, 0xb8, 0x53, + 0xc2, 0xa3, 0x2f, 0x6a, 0xff, 0x88, 0x54, 0xa4, 0x66, 0xa0, 0x2f, 0xf2, 0x25, 0x20, 0x00, 0x70, + 0x2a, 0xee, 0x8d, 0x37, 0xa8, 0xf7, 0xfd, 0xd5, 0xf5, 0x0f, 0xdc, 0xb0, 0x90, 0x19, 0x86, 0x31, + 0x6a, 0x1a, 0x91, 0x70, 0xe2, 0x78, 0xce, 0xa4, 0x41, 0xe1, 0x8c, 0x8f, 0xd0, 0x5e, 0xcc, 0x8c, + 0x30, 0x79, 0xc8, 0x89, 0xeb, 0x39, 0x13, 0x97, 0x56, 0x18, 0xbf, 0x8f, 0xba, 0xb1, 0x92, 0x51, + 0x21, 0x36, 0x40, 0x7c, 0x24, 0xec, 0x4d, 0x16, 0x97, 0x37, 0x9b, 0x9e, 0x33, 0x69, 0xd1, 0x0a, + 0x8f, 0xff, 0x76, 0x10, 0xa2, 0xdb, 0x2a, 0xf0, 0x07, 0x08, 0x95, 0x15, 0xf8, 0x22, 0x84, 0xf0, + 0x5d, 0xda, 0x2d, 0x99, 0x8b, 0xd0, 0xc6, 0xb1, 0xb9, 0x64, 0x86, 0x25, 0x29, 0xe9, 0x79, 0xce, + 0xa4, 0x4f, 0x1f, 0x89, 0x2a, 0xeb, 0x17, 0xb5, 0xac, 0x0f, 0xd1, 0x9e, 0x5e, 0xfa, 0xc1, 0x8a, + 0x09, 0x49, 0x06, 0x70, 0xa1, 0xa3, 0x97, 0xe7, 0x16, 0x62, 0x82, 0x3a, 0xc1, 0x8a, 0x49, 0xc9, + 0x63, 0x32, 0x2c, 0x94, 0x12, 0xda, 0x30, 0x4b, 0xcd, 0x7f, 0xce, 0xb9, 0x0c, 0xee, 0xc8, 0x87, + 0x9e, 0x33, 0x69, 0xd2, 0x47, 0xc2, 0x86, 0xd1, 0x59, 0x26, 0x88, 0x07, 0x75, 0xc2, 0x19, 0x1f, + 0xa0, 0x46, 0x26, 0x35, 0xf9, 0x08, 0x28, 0x7b, 0xc4, 0x9f, 0xa2, 0x46, 0x94, 0x66, 0xe4, 0xd8, + 0x73, 0x26, 0xbd, 0x2f, 0xdf, 0x4c, 0x77, 0x63, 0xaa, 0x75, 0x99, 0x5a, 0xc3, 0xf8, 0x4f, 0x07, + 0xbd, 0xbc, 0xd9, 0x9e, 0x2b, 0xb9, 0x14, 0x51, 0xae, 0x99, 0x11, 0x4a, 0x3e, 0x53, 0xe6, 0xff, + 0x94, 0xf4, 0x24, 0xf1, 0xe1, 0xbf, 0x13, 0x7f, 0x83, 0x5a, 0xa9, 0xda, 0x70, 0x4d, 0xde, 0xc2, + 0x10, 0x0a, 0x80, 0xe7, 0x68, 0x98, 0xaa, 0x98, 0x69, 0xf1, 0x0b, 0x04, 0xf7, 0x85, 0xbc, 0xe5, + 0x3a, 0x13, 0x4a, 0x42, 0xe5, 0x7b, 0x74, 0x50, 0x57, 0x2f, 0x76, 0x22, 0x9e, 0xa1, 0xd7, 0xd5, + 0x9f, 0xfd, 0x90, 0xdf, 0x0a, 0xd0, 0xa1, 0x29, 0x7d, 0x8a, 0x2b, 0xe9, 0xdb, 0x9d, 0x32, 0xfe, + 0xab, 0x89, 0xda, 0xd7, 0x86, 0x99, 0x3c, 0x7b, 0x5a, 0x9f, 0xf3, 0x5f, 0x63, 0x74, 0x6b, 0x63, + 0xdc, 0x47, 0xae, 0xb0, 0xad, 0x68, 0x4c, 0xba, 0xd4, 0x15, 0xa9, 0x5d, 0xa9, 0x34, 0x66, 0x66, + 0xa9, 0x74, 0x02, 0xe3, 0xee, 0xd2, 0x0a, 0xe3, 0x8f, 0x51, 0x3f, 0x50, 0xd2, 0xb0, 0xc0, 0xf8, + 0x3c, 0x61, 0x22, 0x26, 0x7d, 0x30, 0xbc, 0x28, 0xc9, 0xef, 0x2c, 0x87, 0x3d, 0xd4, 0x0b, 0x79, + 0x16, 0x68, 0x91, 0x42, 0xda, 0xfb, 0x60, 0xa9, 0x53, 0x78, 0x88, 0xda, 0x9a, 0x47, 0x56, 0x7c, + 0x09, 0x62, 0x89, 0x2c, 0xbf, 0xd0, 0x22, 0x8c, 0x38, 0x39, 0x28, 0xf8, 0x02, 0x81, 0x5f, 0xe5, + 0x86, 0x6b, 0xf2, 0xaa, 0xf4, 0x03, 0xda, 0x2d, 0xc2, 0xe0, 0x99, 0x45, 0xb0, 0x2b, 0xa4, 0x8d, + 0x81, 0xa6, 0xf7, 0xa9, 0x3d, 0xe2, 0xd7, 0xa8, 0xa5, 0xb7, 0xbe, 0x90, 0xb0, 0x44, 0x7d, 0xda, + 0xd4, 0xdb, 0x0b, 0x59, 0x92, 0x6a, 0x4d, 0x3e, 0xdb, 0x91, 0x97, 0x6b, 0x4b, 0x1a, 0x70, 0x9e, + 0x14, 0xa4, 0x29, 0x9d, 0x06, 0x9c, 0x9f, 0xef, 0xc8, 0xcb, 0x35, 0x3e, 0x46, 0xae, 0xca, 0xc8, + 0x19, 0x24, 0x73, 0x58, 0x25, 0x53, 0xcc, 0x65, 0x7a, 0x69, 0x53, 0xd2, 0x22, 0xc8, 0xa8, 0xab, + 0xb2, 0xa3, 0x7b, 0x07, 0x75, 0x2b, 0x06, 0x0f, 0x50, 0x3b, 0x56, 0x2c, 0xf4, 0x4f, 0x61, 0x60, + 0x2e, 0x6d, 0x59, 0x74, 0x5a, 0xd1, 0xf3, 0xf2, 0x4d, 0x00, 0x7a, 0x8e, 0xdf, 0xa2, 0x4e, 0xe1, + 0x9e, 0x97, 0xcf, 0x01, 0xb8, 0x4e, 0xe7, 0xf8, 0x13, 0xb4, 0x1f, 0xa4, 0xb9, 0x9f, 0x72, 0x1d, + 0x70, 0x69, 0x58, 0xc4, 0x61, 0xbf, 0x5d, 0xda, 0x0f, 0xd2, 0xfc, 0xaa, 0x22, 0xf1, 0x09, 0x7a, + 0x95, 0xf0, 0x44, 0xe9, 0xbb, 0xba, 0x73, 0x00, 0xce, 0x83, 0x42, 0xa8, 0x99, 0x3d, 0xd4, 0x33, + 0x3c, 0x49, 0xb9, 0x66, 0x26, 0xd7, 0x1c, 0x3a, 0xe8, 0xd2, 0x3a, 0xf5, 0xcd, 0xd7, 0xf7, 0x0f, + 0x23, 0xe7, 0xb7, 0x87, 0x91, 0xf3, 0xfb, 0xc3, 0xc8, 0xf9, 0xf5, 0x8f, 0xd1, 0x7b, 0x3f, 0x9d, + 0xbc, 0xc3, 0x1b, 0xbb, 0x68, 0xc3, 0x23, 0x79, 0xf6, 0x4f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x79, + 0xa3, 0xfb, 0x4d, 0x99, 0x05, 0x00, 0x00, } diff --git a/api/gateway/gateway.proto b/api/gateway/gateway.proto index 580382f7d..bcb8a7a18 100644 --- a/api/gateway/gateway.proto +++ b/api/gateway/gateway.proto @@ -44,11 +44,12 @@ message TxConfiguration { } // message Status represents a status update from a Gateway. -// See https://gist.github.com/htdvisser/b2b1078005ed770233278a366430f992#stat message Status { uint32 timestamp = 1; int64 time = 2; + // Configuration and relatively static stuff + repeated string ip = 11; string platform = 12; string contact_email = 13; @@ -59,10 +60,29 @@ message Status { GPSMetadata gps = 21; + // Network (internet) stuff + uint32 rtt = 31; + // Rx and Tx stuff + uint32 rx_in = 41; uint32 rx_ok = 42; uint32 tx_in = 43; uint32 tx_ok = 44; + + // Additional metrics from the operating system + message OSMetrics { + float load_1 = 1; + float load_5 = 2; + float load_15 = 3; + + float cpu_percentage = 11; + + float memory_percentage = 21; + + float temperature = 31; + } + + OSMetrics os = 51; } From ae695393a66fee1eeff26f54d61edc32186c63e6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 3 Dec 2016 09:19:42 +0100 Subject: [PATCH 2229/2266] Move api stats to own package --- api/api.pb.go | 102 +++++++++++++++++++++++++-------------- api/api.proto | 3 +- api/{ => stats}/stats.go | 29 ++++++----- 3 files changed, 84 insertions(+), 50 deletions(-) rename api/{ => stats}/stats.go (67%) diff --git a/api/api.pb.go b/api/api.pb.go index 1c96c60c9..05ab56ca2 100644 --- a/api/api.pb.go +++ b/api/api.pb.go @@ -127,7 +127,8 @@ func (*SystemStats_MemoryStats) ProtoMessage() {} func (*SystemStats_MemoryStats) Descriptor() ([]byte, []int) { return fileDescriptorApi, []int{2, 2} } type ComponentStats struct { - Cpu *ComponentStats_CPUStats `protobuf:"bytes,1,opt,name=cpu" json:"cpu,omitempty"` + Uptime uint64 `protobuf:"varint,1,opt,name=uptime,proto3" json:"uptime,omitempty"` + Cpu *ComponentStats_CPUStats `protobuf:"bytes,2,opt,name=cpu" json:"cpu,omitempty"` Memory *ComponentStats_MemoryStats `protobuf:"bytes,3,opt,name=memory" json:"memory,omitempty"` Goroutines uint64 `protobuf:"varint,4,opt,name=goroutines,proto3" json:"goroutines,omitempty"` GcCpuFraction float32 `protobuf:"fixed32,5,opt,name=gc_cpu_fraction,json=gcCpuFraction,proto3" json:"gc_cpu_fraction,omitempty"` @@ -442,8 +443,13 @@ func (m *ComponentStats) MarshalTo(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.Uptime != 0 { + dAtA[i] = 0x8 + i++ + i = encodeVarintApi(dAtA, i, uint64(m.Uptime)) + } if m.Cpu != nil { - dAtA[i] = 0xa + dAtA[i] = 0x12 i++ i = encodeVarintApi(dAtA, i, uint64(m.Cpu.Size())) n4, err := m.Cpu.MarshalTo(dAtA[i:]) @@ -677,6 +683,9 @@ func (m *SystemStats_MemoryStats) Size() (n int) { func (m *ComponentStats) Size() (n int) { var l int _ = l + if m.Uptime != 0 { + n += 1 + sovApi(uint64(m.Uptime)) + } if m.Cpu != nil { l = m.Cpu.Size() n += 1 + l + sovApi(uint64(l)) @@ -1472,6 +1481,25 @@ func (m *ComponentStats) Unmarshal(dAtA []byte) error { } switch fieldNum { case 1: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Uptime", wireType) + } + m.Uptime = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowApi + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Uptime |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + case 2: if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field Cpu", wireType) } @@ -1879,39 +1907,39 @@ var ( func init() { proto.RegisterFile("github.com/TheThingsNetwork/ttn/api/api.proto", fileDescriptorApi) } var fileDescriptorApi = []byte{ - // 536 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x94, 0x5d, 0x8b, 0xd3, 0x4e, - 0x14, 0xc6, 0xff, 0x69, 0xd3, 0xfe, 0xb7, 0x27, 0xbe, 0xc0, 0x20, 0x4b, 0x2c, 0x4b, 0x5d, 0x2a, - 0x88, 0x20, 0xb6, 0xb5, 0x1a, 0x4a, 0x6e, 0x2d, 0x78, 0xe1, 0xeb, 0x92, 0xdd, 0xbd, 0xf1, 0x66, - 0x99, 0xa6, 0x63, 0x1b, 0x4c, 0x32, 0x21, 0x33, 0x71, 0xd9, 0xef, 0xe1, 0x85, 0x1f, 0x69, 0x6f, - 0x04, 0x3f, 0x82, 0xd4, 0x2f, 0x22, 0x73, 0x32, 0x89, 0xc9, 0x74, 0x2f, 0xbc, 0xf0, 0x22, 0x30, - 0xcf, 0x99, 0xdf, 0x3c, 0x73, 0xce, 0x13, 0x12, 0x78, 0xba, 0x89, 0xe4, 0xb6, 0x58, 0x4d, 0x42, - 0x9e, 0x4c, 0xcf, 0xb6, 0xec, 0x6c, 0x1b, 0xa5, 0x1b, 0xf1, 0x9e, 0xc9, 0x4b, 0x9e, 0x7f, 0x9e, - 0x4a, 0x99, 0x4e, 0x69, 0x16, 0xa9, 0x67, 0x92, 0xe5, 0x5c, 0x72, 0xd2, 0xa5, 0x59, 0x34, 0xfe, - 0xde, 0x01, 0xe7, 0x84, 0xe5, 0x21, 0x4b, 0x65, 0x14, 0x33, 0x41, 0x8e, 0xc1, 0xc9, 0x6a, 0xf9, - 0xcc, 0xb5, 0x8e, 0xad, 0xc7, 0x9d, 0xa0, 0x59, 0x6a, 0x13, 0x9e, 0xdb, 0x31, 0x09, 0x8f, 0x8c, - 0xe1, 0x56, 0xe3, 0xc0, 0xcc, 0xed, 0x22, 0xd2, 0xaa, 0xb5, 0x99, 0xb9, 0xe7, 0xda, 0x26, 0x33, - 0x37, 0x7c, 0xbc, 0x99, 0xdb, 0x33, 0x19, 0xcf, 0xf0, 0x59, 0x78, 0x6e, 0xdf, 0x64, 0x16, 0x86, - 0x8f, 0x3f, 0x73, 0xff, 0x37, 0x19, 0xdf, 0xf0, 0xf1, 0x3d, 0xf7, 0x60, 0x8f, 0x31, 0x7d, 0x7c, - 0x77, 0xb0, 0xc7, 0xf8, 0xe3, 0x37, 0xd0, 0x0b, 0xa8, 0x64, 0x82, 0xdc, 0x83, 0x5e, 0x4e, 0x65, - 0x1d, 0x61, 0x29, 0xaa, 0x6a, 0x15, 0x5b, 0x29, 0xc8, 0x21, 0xf4, 0x71, 0xdb, 0xd3, 0x51, 0x69, - 0x35, 0xfe, 0xda, 0x05, 0xe7, 0xf4, 0x4a, 0x48, 0x96, 0x9c, 0x4a, 0x2a, 0x05, 0x99, 0x80, 0x1d, - 0x73, 0xba, 0x46, 0x4b, 0x67, 0x3e, 0x9c, 0xa8, 0x77, 0xd9, 0xd8, 0x9f, 0xbc, 0xe5, 0x74, 0x2d, - 0xd4, 0x2a, 0x40, 0x8e, 0x3c, 0x81, 0x6e, 0x98, 0x15, 0x78, 0x97, 0x33, 0xbf, 0xbf, 0x87, 0x2f, - 0x4f, 0xce, 0x71, 0x11, 0x28, 0x8a, 0xbc, 0x80, 0x7e, 0xc2, 0x12, 0x9e, 0x5f, 0x61, 0x13, 0xce, - 0xfc, 0x68, 0x8f, 0x7f, 0x87, 0xdb, 0xe5, 0x11, 0xcd, 0x0e, 0x3f, 0xc0, 0xa0, 0xbe, 0x55, 0x4d, - 0xa7, 0xee, 0xad, 0x67, 0x46, 0x51, 0x55, 0xeb, 0x99, 0x51, 0xa8, 0x99, 0x71, 0xbb, 0x9e, 0xb9, - 0x54, 0xc3, 0xd7, 0x70, 0x50, 0xf5, 0x45, 0x08, 0xd8, 0x85, 0x60, 0xb9, 0xb6, 0xc3, 0xb5, 0x3a, - 0x27, 0xb0, 0x27, 0x6d, 0xa7, 0x95, 0x62, 0xa3, 0x75, 0xcc, 0xb4, 0x1b, 0xae, 0x87, 0xe7, 0xe0, - 0x34, 0x7a, 0x56, 0x8d, 0x48, 0x2e, 0x69, 0x8c, 0x7e, 0x76, 0x50, 0x0a, 0x72, 0x04, 0x03, 0xfa, - 0x85, 0x46, 0x31, 0x5d, 0xc5, 0x0c, 0x3d, 0xed, 0xe0, 0x4f, 0x41, 0xb7, 0xb0, 0x46, 0x5b, 0x1b, - 0x5b, 0x58, 0x8f, 0xaf, 0x3b, 0x70, 0x67, 0xc9, 0x93, 0x8c, 0xa7, 0x2c, 0x95, 0xd5, 0x9b, 0xc1, - 0xa4, 0xad, 0x46, 0x72, 0x6d, 0xc2, 0x08, 0x7b, 0x61, 0x84, 0xfd, 0xe0, 0xa6, 0x23, 0x37, 0xe4, - 0x4d, 0x46, 0x00, 0x1b, 0x9e, 0xf3, 0x42, 0x46, 0x29, 0x13, 0xf8, 0xd5, 0xd8, 0x41, 0xa3, 0x42, - 0x1e, 0xc1, 0xdd, 0x4d, 0x78, 0x11, 0x66, 0xc5, 0xc5, 0xa7, 0x9c, 0x86, 0x32, 0xe2, 0xa9, 0xfe, - 0x6c, 0x6e, 0x6f, 0xc2, 0x65, 0x56, 0xbc, 0xd2, 0xc5, 0x7f, 0x1a, 0xb3, 0xdf, 0x8e, 0xf9, 0xb0, - 0x9e, 0xad, 0xcc, 0xb9, 0x6a, 0x9d, 0x80, 0x2d, 0x2e, 0x69, 0xa6, 0x33, 0xc6, 0xf5, 0x4b, 0xef, - 0x7a, 0x37, 0xb2, 0x7e, 0xec, 0x46, 0xd6, 0xcf, 0xdd, 0xc8, 0xfa, 0xf6, 0x6b, 0xf4, 0xdf, 0xc7, - 0x87, 0x7f, 0xf1, 0x13, 0x5b, 0xf5, 0xf1, 0x0f, 0xf6, 0xfc, 0x77, 0x00, 0x00, 0x00, 0xff, 0xff, - 0x9d, 0xbc, 0x69, 0xcf, 0xf2, 0x04, 0x00, 0x00, + // 544 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x54, 0xdd, 0x8a, 0xd3, 0x40, + 0x14, 0xb6, 0x6d, 0x5a, 0xb7, 0x27, 0xfe, 0xc0, 0x20, 0x25, 0x96, 0xa5, 0x2e, 0x15, 0x44, 0x10, + 0xd3, 0x5a, 0x0d, 0x25, 0xb7, 0x16, 0xbc, 0xf0, 0x77, 0xc9, 0xee, 0xde, 0x78, 0xb3, 0x4c, 0xd3, + 0xb1, 0x0d, 0x26, 0x99, 0x90, 0x99, 0xb8, 0xec, 0x7b, 0x78, 0xe1, 0x23, 0x79, 0x23, 0xf8, 0x08, + 0x4b, 0x7d, 0x11, 0x99, 0x93, 0x69, 0x4c, 0xa6, 0x7b, 0xe1, 0xc5, 0x5e, 0x04, 0xce, 0x77, 0xe6, + 0x3b, 0xdf, 0x9c, 0xf3, 0x1d, 0x32, 0xf0, 0x7c, 0x1d, 0xc9, 0x4d, 0xb1, 0x74, 0x43, 0x9e, 0x4c, + 0x4e, 0x37, 0xec, 0x74, 0x13, 0xa5, 0x6b, 0xf1, 0x91, 0xc9, 0x0b, 0x9e, 0x7f, 0x9d, 0x48, 0x99, + 0x4e, 0x68, 0x16, 0xa9, 0xcf, 0xcd, 0x72, 0x2e, 0x39, 0xe9, 0xd0, 0x2c, 0x1a, 0xff, 0x6a, 0x83, + 0x7d, 0xcc, 0xf2, 0x90, 0xa5, 0x32, 0x8a, 0x99, 0x20, 0x47, 0x60, 0x67, 0x15, 0x7c, 0xe1, 0xb4, + 0x8e, 0x5a, 0x4f, 0xdb, 0x41, 0x3d, 0xd5, 0x64, 0x78, 0x4e, 0xdb, 0x64, 0x78, 0x64, 0x0c, 0x77, + 0x6a, 0x05, 0x53, 0xa7, 0x83, 0x94, 0x46, 0xae, 0xc9, 0x99, 0x79, 0x8e, 0x65, 0x72, 0x66, 0x86, + 0x8e, 0x37, 0x75, 0xba, 0x26, 0xc7, 0x33, 0x74, 0xe6, 0x9e, 0xd3, 0x33, 0x39, 0x73, 0x43, 0xc7, + 0x9f, 0x3a, 0xb7, 0x4d, 0x8e, 0x6f, 0xe8, 0xf8, 0x9e, 0x73, 0xb0, 0xc7, 0x31, 0x75, 0x7c, 0xa7, + 0xbf, 0xc7, 0xf1, 0xc7, 0xef, 0xa0, 0x1b, 0x50, 0xc9, 0x04, 0x79, 0x00, 0xdd, 0x9c, 0xca, 0xca, + 0xc2, 0x12, 0xec, 0xb2, 0x3b, 0xdb, 0x4a, 0x40, 0x06, 0xd0, 0xc3, 0x63, 0x4f, 0x5b, 0xa5, 0xd1, + 0xf8, 0x7b, 0x07, 0xec, 0x93, 0x4b, 0x21, 0x59, 0x72, 0x22, 0xa9, 0x14, 0xc4, 0x05, 0x2b, 0xe6, + 0x74, 0x85, 0x92, 0xf6, 0x6c, 0xe8, 0xaa, 0x5d, 0xd6, 0xce, 0xdd, 0xf7, 0x9c, 0xae, 0x84, 0x8a, + 0x02, 0xe4, 0x91, 0x67, 0xd0, 0x09, 0xb3, 0x02, 0xef, 0xb2, 0x67, 0x0f, 0xf7, 0xe8, 0x8b, 0xe3, + 0x33, 0x0c, 0x02, 0xc5, 0x22, 0xaf, 0xa0, 0x97, 0xb0, 0x84, 0xe7, 0x97, 0xd8, 0x84, 0x3d, 0x3b, + 0xdc, 0xe3, 0x7f, 0xc0, 0xe3, 0xb2, 0x44, 0x73, 0x87, 0x9f, 0xa0, 0x5f, 0xdd, 0xaa, 0xa6, 0x53, + 0xf7, 0x56, 0x33, 0x23, 0xd8, 0x65, 0xab, 0x99, 0x11, 0xa8, 0x99, 0xf1, 0xb8, 0x9a, 0xb9, 0x44, + 0xc3, 0xb7, 0x70, 0xb0, 0xeb, 0x8b, 0x10, 0xb0, 0x0a, 0xc1, 0x72, 0x2d, 0x87, 0xb1, 0xaa, 0x13, + 0xd8, 0x93, 0x96, 0xd3, 0x48, 0x71, 0xa3, 0x55, 0xcc, 0xb4, 0x1a, 0xc6, 0xc3, 0x33, 0xb0, 0x6b, + 0x3d, 0xab, 0x46, 0x24, 0x97, 0x34, 0x46, 0x3d, 0x2b, 0x28, 0x01, 0x39, 0x84, 0x3e, 0xfd, 0x46, + 0xa3, 0x98, 0x2e, 0x63, 0x86, 0x9a, 0x56, 0xf0, 0x2f, 0xa1, 0x5b, 0x58, 0xa1, 0xac, 0x85, 0x2d, + 0xac, 0xc6, 0x57, 0x6d, 0xb8, 0xb7, 0xe0, 0x49, 0xc6, 0x53, 0x96, 0xca, 0x52, 0x7a, 0x00, 0xbd, + 0x22, 0x93, 0x51, 0xc2, 0xb4, 0xb6, 0x46, 0xc4, 0xad, 0x6f, 0xa0, 0x74, 0xb4, 0x59, 0x69, 0x2c, + 0x61, 0x6e, 0x2c, 0xe1, 0xd1, 0x75, 0x25, 0xd7, 0xec, 0x81, 0x8c, 0x00, 0xd6, 0x3c, 0xe7, 0x85, + 0x8c, 0x52, 0x26, 0xf0, 0x6f, 0xb2, 0x82, 0x5a, 0x86, 0x3c, 0x81, 0xfb, 0xeb, 0xf0, 0x3c, 0xcc, + 0x8a, 0xf3, 0x2f, 0x39, 0x0d, 0x65, 0xc4, 0x53, 0xfd, 0x3b, 0xdd, 0x5d, 0x87, 0x8b, 0xac, 0x78, + 0xa3, 0x93, 0x37, 0x6a, 0xbf, 0xdf, 0xb4, 0x7f, 0x50, 0xcd, 0xa6, 0x3d, 0xd2, 0xad, 0x13, 0xb0, + 0xc4, 0x05, 0xcd, 0xb4, 0xf7, 0x18, 0xbf, 0xf6, 0x7e, 0x6e, 0x47, 0xad, 0xdf, 0xdb, 0x51, 0xeb, + 0x6a, 0x3b, 0x6a, 0xfd, 0xf8, 0x33, 0xba, 0xf5, 0xf9, 0xf1, 0x7f, 0x3c, 0x6e, 0xcb, 0x1e, 0xbe, + 0x6c, 0x2f, 0xff, 0x06, 0x00, 0x00, 0xff, 0xff, 0x67, 0xf3, 0x67, 0x35, 0x0a, 0x05, 0x00, 0x00, } diff --git a/api/api.proto b/api/api.proto index 2903fbffd..f9c878e04 100644 --- a/api/api.proto +++ b/api/api.proto @@ -47,12 +47,13 @@ message SystemStats { } message ComponentStats { + uint64 uptime = 1; message CPUStats { float user = 1; float system = 2; float idle = 3; } - CPUStats cpu = 1; + CPUStats cpu = 2; message MemoryStats { uint64 memory = 1; uint64 swap = 2; diff --git a/api/stats.go b/api/stats/stats.go similarity index 67% rename from api/stats.go rename to api/stats/stats.go index bfe8db8a1..fc6395c61 100644 --- a/api/stats.go +++ b/api/stats/stats.go @@ -1,37 +1,41 @@ // Copyright © 2016 The Things Network // Use of this source code is governed by the MIT license that can be found in the LICENSE file. -package api +package stats import ( "os" "runtime" + "time" + "github.com/TheThingsNetwork/ttn/api" "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" "github.com/shirou/gopsutil/process" ) -// GetSystemStats gets statistics about the system -func GetSystemStats() *SystemStats { - status := new(SystemStats) +var startTime = time.Now() + +// GetSystem gets statistics about the system +func GetSystem() *api.SystemStats { + status := new(api.SystemStats) if load, err := load.Avg(); err == nil { - status.Load = &SystemStats_Loadstats{ + status.Load = &api.SystemStats_Loadstats{ Load1: float32(load.Load1), Load5: float32(load.Load5), Load15: float32(load.Load15), } } if cpu, err := cpu.Times(false); err == nil && len(cpu) == 1 { - status.Cpu = &SystemStats_CPUStats{ + status.Cpu = &api.SystemStats_CPUStats{ User: float32(cpu[0].User), System: float32(cpu[0].System), Idle: float32(cpu[0].Idle), } } if mem, err := mem.VirtualMemory(); err == nil { - status.Memory = &SystemStats_MemoryStats{ + status.Memory = &api.SystemStats_MemoryStats{ Total: mem.Total, Available: mem.Available, Used: mem.Used, @@ -40,19 +44,20 @@ func GetSystemStats() *SystemStats { return status } -// GetComponentStats gets statistics about this component -func GetComponentStats() *ComponentStats { - status := new(ComponentStats) +// GetComponent gets statistics about this component +func GetComponent() *api.ComponentStats { + status := new(api.ComponentStats) + status.Uptime = uint64(time.Now().Sub(startTime).Seconds()) process, err := process.NewProcess(int32(os.Getpid())) if err == nil { if memory, err := process.MemoryInfo(); err == nil { - status.Memory = &ComponentStats_MemoryStats{ + status.Memory = &api.ComponentStats_MemoryStats{ Memory: memory.RSS, Swap: memory.Swap, } } if cpu, err := process.Times(); err == nil { - status.Cpu = &ComponentStats_CPUStats{ + status.Cpu = &api.ComponentStats_CPUStats{ User: float32(cpu.User), System: float32(cpu.System), Idle: float32(cpu.Idle), From 4ed5b3f0a8c9eca88e1499426c5f0b162a2550d5 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 3 Dec 2016 16:07:53 +0100 Subject: [PATCH 2230/2266] Add metrics to Broker --- core/broker/activation.go | 4 ++ core/broker/broker.go | 2 + core/broker/downlink.go | 2 + core/broker/downlink_test.go | 1 + core/broker/manager_server.go | 12 ++++- core/broker/status.go | 97 +++++++++++++++++++++++++++++++++++ core/broker/status_test.go | 21 ++++++++ core/broker/uplink.go | 5 ++ core/broker/util_test.go | 4 +- 9 files changed, 146 insertions(+), 2 deletions(-) create mode 100644 core/broker/status.go create mode 100644 core/broker/status_test.go diff --git a/core/broker/activation.go b/core/broker/activation.go index 626617ce7..c789ece8b 100644 --- a/core/broker/activation.go +++ b/core/broker/activation.go @@ -44,12 +44,16 @@ func (b *broker) HandleActivation(activation *pb.DeviceActivationRequest) (res * time := time.Now() + b.status.activations.Mark(1) + // De-duplicate uplink messages duplicates := b.deduplicateActivation(activation) if len(duplicates) == 0 { return nil, errDuplicateActivation } + b.status.activationsUnique.Mark(1) + base := duplicates[0] // Collect GatewayMetadata and DownlinkOptions diff --git a/core/broker/broker.go b/core/broker/broker.go index 941e5063f..b9ff4d91a 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -63,6 +63,7 @@ type broker struct { ns networkserver.NetworkServerClient uplinkDeduplicator Deduplicator activationDeduplicator Deduplicator + status *status } func (b *broker) checkPrefixAnnouncements() error { @@ -107,6 +108,7 @@ nextPrefix: func (b *broker) Init(c *component.Component) error { b.Component = c + b.InitStatus() err := b.Component.UpdateTokenKey() if err != nil { return err diff --git a/core/broker/downlink.go b/core/broker/downlink.go index 324e4526b..777273b98 100644 --- a/core/broker/downlink.go +++ b/core/broker/downlink.go @@ -34,6 +34,8 @@ func (b *broker) HandleDownlink(downlink *pb.DownlinkMessage) error { } }() + b.status.downlink.Mark(1) + downlink, err = b.ns.Downlink(b.Component.GetContext(b.nsToken), downlink) if err != nil { return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not handle downlink") diff --git a/core/broker/downlink_test.go b/core/broker/downlink_test.go index 0d28e1991..4eb7bf8a6 100644 --- a/core/broker/downlink_test.go +++ b/core/broker/downlink_test.go @@ -30,6 +30,7 @@ func TestDownlink(t *testing.T) { "routerID": dlch, }, } + b.InitStatus() err := b.HandleDownlink(&pb.DownlinkMessage{ DevEui: &devEUI, diff --git a/core/broker/manager_server.go b/core/broker/manager_server.go index 4233c45d6..1ac1938dc 100644 --- a/core/broker/manager_server.go +++ b/core/broker/manager_server.go @@ -110,7 +110,17 @@ func (b *brokerManager) GetDevAddr(ctx context.Context, in *lorawan.DevAddrReque } func (b *brokerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") + if b.broker.Identity.Id != "dev" { + claims, err := b.broker.ValidateTTNAuthContext(ctx) + if err != nil || !claims.ComponentAccess(b.broker.Identity.Id) { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := b.broker.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil } func (b *broker) RegisterManager(s *grpc.Server) { diff --git a/core/broker/status.go b/core/broker/status.go new file mode 100644 index 000000000..01893865f --- /dev/null +++ b/core/broker/status.go @@ -0,0 +1,97 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/broker" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + uplinkUnique metrics.Meter + downlink metrics.Meter + activations metrics.Meter + activationsUnique metrics.Meter + deduplication metrics.Histogram + connectedRouters metrics.Gauge + connectedHandlers metrics.Gauge +} + +func (b *broker) InitStatus() { + b.status = &status{ + uplink: metrics.NewMeter(), + uplinkUnique: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + activationsUnique: metrics.NewMeter(), + deduplication: metrics.NewHistogram(metrics.NewUniformSample(512)), + connectedRouters: metrics.NewFunctionalGauge(func() int64 { + b.routersLock.RLock() + defer b.routersLock.RUnlock() + return int64(len(b.routers)) + }), + connectedHandlers: metrics.NewFunctionalGauge(func() int64 { + b.handlersLock.RLock() + defer b.handlersLock.RUnlock() + return int64(len(b.handlers)) + }), + } +} + +func (b *broker) GetStatus() *pb.Status { + status := new(pb.Status) + if b.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := b.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + uplinkUnique := b.status.uplinkUnique.Snapshot() + status.UplinkUnique = &api.Rates{ + Rate1: float32(uplinkUnique.Rate1()), + Rate5: float32(uplinkUnique.Rate5()), + Rate15: float32(uplinkUnique.Rate15()), + } + downlink := b.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := b.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + activationsUnique := b.status.activationsUnique.Snapshot() + status.UplinkUnique = &api.Rates{ + Rate1: float32(activationsUnique.Rate1()), + Rate5: float32(activationsUnique.Rate5()), + Rate15: float32(activationsUnique.Rate15()), + } + deduplication := b.status.deduplication.Snapshot().Percentiles([]float64{0.01, 0.05, 0.10, 0.25, 0.50, 0.75, 0.90, 0.95, 0.99}) + status.Deduplication = &api.Percentiles{ + Percentile1: float32(deduplication[0]), + Percentile5: float32(deduplication[1]), + Percentile10: float32(deduplication[2]), + Percentile25: float32(deduplication[3]), + Percentile50: float32(deduplication[4]), + Percentile75: float32(deduplication[5]), + Percentile90: float32(deduplication[6]), + Percentile95: float32(deduplication[7]), + Percentile99: float32(deduplication[8]), + } + status.ConnectedRouters = uint32(b.status.connectedRouters.Snapshot().Value()) + status.ConnectedHandlers = uint32(b.status.connectedHandlers.Snapshot().Value()) + return status +} diff --git a/core/broker/status_test.go b/core/broker/status_test.go new file mode 100644 index 000000000..21945649f --- /dev/null +++ b/core/broker/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package broker + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/broker" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + b := new(broker) + a.So(b.GetStatus(), ShouldResemble, new(pb.Status)) + b.InitStatus() + a.So(b.status, ShouldNotBeNil) + status := b.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/broker/uplink.go b/core/broker/uplink.go index 94a585b8d..a9690bdf3 100644 --- a/core/broker/uplink.go +++ b/core/broker/uplink.go @@ -37,12 +37,16 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { time := time.Now() + b.status.uplink.Mark(1) + // De-duplicate uplink messages duplicates := b.deduplicateUplink(uplink) if len(duplicates) == 0 { return nil } + b.status.uplinkUnique.Mark(1) + ctx = ctx.WithField("Duplicates", len(duplicates)) base := duplicates[0] @@ -76,6 +80,7 @@ func (b *broker) HandleUplink(uplink *pb.UplinkMessage) (err error) { if err != nil { return errors.Wrap(errors.FromGRPCError(err), "NetworkServer did not return devices") } + b.status.deduplication.Update(int64(len(getDevicesResp.Results))) if len(getDevicesResp.Results) == 0 { return errors.NewErrNotFound(fmt.Sprintf("Device with DevAddr %s and FCnt <= %d", devAddr, macPayload.FHDR.FCnt)) } diff --git a/core/broker/util_test.go b/core/broker/util_test.go index 59cf5b366..2c0332cf4 100644 --- a/core/broker/util_test.go +++ b/core/broker/util_test.go @@ -26,7 +26,7 @@ func getTestBroker(t *testing.T) *testBroker { ctrl := gomock.NewController(t) discovery := pb_discovery.NewMockClient(ctrl) ns := pb_networkserver.NewMockNetworkServerClient(ctrl) - return &testBroker{ + b := &testBroker{ broker: &broker{ Component: &component.Component{ Discovery: discovery, @@ -41,4 +41,6 @@ func getTestBroker(t *testing.T) *testBroker { ctrl: ctrl, discovery: discovery, } + b.InitStatus() + return b } From 072956cd74f012d24ddcaef20fc3275c53b4dbd6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 3 Dec 2016 16:08:02 +0100 Subject: [PATCH 2231/2266] Add metrics to Handler --- core/handler/activation.go | 1 + core/handler/activation_test.go | 1 + core/handler/downlink.go | 2 ++ core/handler/downlink_test.go | 1 + core/handler/handler.go | 3 ++ core/handler/manager_server.go | 12 +++++++- core/handler/status.go | 53 +++++++++++++++++++++++++++++++++ core/handler/status_test.go | 21 +++++++++++++ core/handler/uplink.go | 1 + core/handler/uplink_test.go | 1 + 10 files changed, 95 insertions(+), 1 deletion(-) create mode 100644 core/handler/status.go create mode 100644 core/handler/status_test.go diff --git a/core/handler/activation.go b/core/handler/activation.go index 103d20621..9076f0265 100644 --- a/core/handler/activation.go +++ b/core/handler/activation.go @@ -98,6 +98,7 @@ func (h *handler) HandleActivation(activation *pb_broker.DeduplicatedDeviceActiv ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() + h.status.activations.Mark(1) if activation.ResponseTemplate == nil { err = errors.NewErrInternal("No downlink available") diff --git a/core/handler/activation_test.go b/core/handler/activation_test.go index 416efd961..53e465aee 100644 --- a/core/handler/activation_test.go +++ b/core/handler/activation_test.go @@ -70,6 +70,7 @@ func TestHandleActivation(t *testing.T) { applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-activation"), devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-activation"), } + h.InitStatus() h.mqttEvent = make(chan *types.DeviceEvent, 10) var wg WaitGroup diff --git a/core/handler/downlink.go b/core/handler/downlink.go index 2d47b239a..9fb310f4c 100644 --- a/core/handler/downlink.go +++ b/core/handler/downlink.go @@ -94,6 +94,8 @@ func (h *handler) HandleDownlink(appDownlink *types.DownlinkMessage, downlink *p } } + h.status.downlink.Mark(1) + ctx.Debug("Send Downlink") h.downlink <- downlink diff --git a/core/handler/downlink_test.go b/core/handler/downlink_test.go index 6937b4aa9..624accd00 100644 --- a/core/handler/downlink_test.go +++ b/core/handler/downlink_test.go @@ -67,6 +67,7 @@ func TestHandleDownlink(t *testing.T) { downlink: make(chan *pb_broker.DownlinkMessage), mqttEvent: make(chan *types.DeviceEvent, 10), } + h.InitStatus() // Neither payload nor Fields provided : ERROR err = h.HandleDownlink(&types.DownlinkMessage{ AppID: appID, diff --git a/core/handler/handler.go b/core/handler/handler.go index d28df2656..9948ace7c 100644 --- a/core/handler/handler.go +++ b/core/handler/handler.go @@ -70,6 +70,8 @@ type handler struct { amqpExchange string amqpEnabled bool amqpUp chan *types.UplinkMessage + + status *status } var ( @@ -96,6 +98,7 @@ func (h *handler) WithAMQP(username, password, host, exchange string) Handler { func (h *handler) Init(c *component.Component) error { h.Component = c + h.InitStatus() err := h.Component.UpdateTokenKey() if err != nil { return err diff --git a/core/handler/manager_server.go b/core/handler/manager_server.go index e549eb85a..b2287f4d3 100644 --- a/core/handler/manager_server.go +++ b/core/handler/manager_server.go @@ -470,7 +470,17 @@ func (h *handlerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.DevAddrR } func (h *handlerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") + if h.handler.Identity.Id != "dev" { + claims, err := h.handler.ValidateTTNAuthContext(ctx) + if err != nil || !claims.ComponentAccess(h.handler.Identity.Id) { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := h.handler.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil } func (h *handler) RegisterManager(s *grpc.Server) { diff --git a/core/handler/status.go b/core/handler/status.go new file mode 100644 index 000000000..2eb5f9f05 --- /dev/null +++ b/core/handler/status.go @@ -0,0 +1,53 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/handler" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter +} + +func (h *handler) InitStatus() { + h.status = &status{ + uplink: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + } +} + +func (h *handler) GetStatus() *pb.Status { + status := new(pb.Status) + if h.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := h.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + downlink := h.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := h.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + return status +} diff --git a/core/handler/status_test.go b/core/handler/status_test.go new file mode 100644 index 000000000..4187dddbb --- /dev/null +++ b/core/handler/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package handler + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/handler" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + h := new(handler) + a.So(h.GetStatus(), ShouldResemble, new(pb.Status)) + h.InitStatus() + a.So(h.status, ShouldNotBeNil) + status := h.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/handler/uplink.go b/core/handler/uplink.go index ea427051e..1baccaa21 100644 --- a/core/handler/uplink.go +++ b/core/handler/uplink.go @@ -34,6 +34,7 @@ func (h *handler) HandleUplink(uplink *pb_broker.DeduplicatedUplinkMessage) (err ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled uplink") } }() + h.status.uplink.Mark(1) // Build AppUplink appUplink := &types.UplinkMessage{ diff --git a/core/handler/uplink_test.go b/core/handler/uplink_test.go index 71b4a9c73..93ed91cd2 100644 --- a/core/handler/uplink_test.go +++ b/core/handler/uplink_test.go @@ -31,6 +31,7 @@ func TestHandleUplink(t *testing.T) { devices: device.NewRedisDeviceStore(GetRedisClient(), "handler-test-handle-uplink"), applications: application.NewRedisApplicationStore(GetRedisClient(), "handler-test-handle-uplink"), } + h.InitStatus() dev := &device.Device{ AppID: appID, DevID: devID, From 3eb07ed8de8622a5ba3c7fa57e98f6b3764a57ea Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 3 Dec 2016 16:08:18 +0100 Subject: [PATCH 2232/2266] Add metrics to Router --- core/router/activation.go | 1 + core/router/activation_test.go | 1 + core/router/downlink.go | 1 + core/router/downlink_test.go | 2 + core/router/gateway_status.go | 3 +- core/router/gateway_status_test.go | 1 + core/router/manager_server.go | 13 ++++++- core/router/router.go | 2 + core/router/status.go | 61 ++++++++++++++++++++++++++++++ core/router/status_test.go | 21 ++++++++++ core/router/uplink.go | 1 + core/router/util_test.go | 4 +- 12 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 core/router/status.go create mode 100644 core/router/status_test.go diff --git a/core/router/activation.go b/core/router/activation.go index f3d842f50..dcbb91573 100644 --- a/core/router/activation.go +++ b/core/router/activation.go @@ -31,6 +31,7 @@ func (r *router) HandleActivation(gatewayID string, activation *pb.DeviceActivat ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled activation") } }() + r.status.activations.Mark(1) gateway := r.getGateway(gatewayID) gateway.LastSeen = time.Now() diff --git a/core/router/activation_test.go b/core/router/activation_test.go index 2d6644d93..daa755c43 100644 --- a/core/router/activation_test.go +++ b/core/router/activation_test.go @@ -27,6 +27,7 @@ func TestHandleActivation(t *testing.T) { gtwID: newReferenceGateway(t, "EU_863_870"), }, } + r.InitStatus() appEUI := types.AppEUI{0, 1, 2, 3, 4, 5, 6, 7} devEUI := types.DevEUI{0, 1, 2, 3, 4, 5, 6, 7} diff --git a/core/router/downlink.go b/core/router/downlink.go index c21bfa4f9..aec1e4f81 100644 --- a/core/router/downlink.go +++ b/core/router/downlink.go @@ -51,6 +51,7 @@ func (r *router) UnsubscribeDownlink(gatewayID string, subscriptionID string) er } func (r *router) HandleDownlink(downlink *pb_broker.DownlinkMessage) error { + r.status.downlink.Mark(1) option := downlink.DownlinkOption downlinkMessage := &pb.DownlinkMessage{ diff --git a/core/router/downlink_test.go b/core/router/downlink_test.go index 252ddf170..ea55605de 100644 --- a/core/router/downlink_test.go +++ b/core/router/downlink_test.go @@ -45,6 +45,7 @@ func TestHandleDownlink(t *testing.T) { }, gateways: map[string]*gateway.Gateway{}, } + r.InitStatus() gtwID := "eui-0102030405060708" id, _ := r.getGateway(gtwID).Schedule.GetOption(0, 10*1000) @@ -70,6 +71,7 @@ func TestSubscribeUnsubscribeDownlink(t *testing.T) { }, gateways: map[string]*gateway.Gateway{}, } + r.InitStatus() gtwID := "eui-0102030405060708" gateway.Deadline = 1 * time.Millisecond diff --git a/core/router/gateway_status.go b/core/router/gateway_status.go index 5c8591b31..fbe38506e 100644 --- a/core/router/gateway_status.go +++ b/core/router/gateway_status.go @@ -19,8 +19,7 @@ func (r *router) HandleGatewayStatus(gatewayID string, status *pb_gateway.Status ctx.WithField("Duration", time.Now().Sub(start)).Info("Handled gateway status") } }() - + r.status.gatewayStatus.Mark(1) status.Router = r.Identity.Id - return r.getGateway(gatewayID).HandleStatus(status) } diff --git a/core/router/gateway_status_test.go b/core/router/gateway_status_test.go index 31a972754..0e3128a40 100644 --- a/core/router/gateway_status_test.go +++ b/core/router/gateway_status_test.go @@ -25,6 +25,7 @@ func TestHandleGatewayStatus(t *testing.T) { }, gateways: map[string]*gateway.Gateway{}, } + router.InitStatus() // Handle statusMessage := &pb_gateway.Status{Description: "Fake Gateway"} diff --git a/core/router/manager_server.go b/core/router/manager_server.go index 9d384559e..8b5e8bf4b 100644 --- a/core/router/manager_server.go +++ b/core/router/manager_server.go @@ -10,7 +10,6 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" "google.golang.org/grpc" - "google.golang.org/grpc/codes" ) type routerManager struct { @@ -42,7 +41,17 @@ func (r *routerManager) GatewayStatus(ctx context.Context, in *pb.GatewayStatusR } func (r *routerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") + if r.router.Identity.Id != "dev" { + claims, err := r.router.ValidateTTNAuthContext(ctx) + if err != nil || !claims.ComponentAccess(r.router.Identity.Id) { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := r.router.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil } // RegisterManager registers this router as a RouterManagerServer (github.com/TheThingsNetwork/ttn/api/router) diff --git a/core/router/router.go b/core/router/router.go index 6deefedb0..b3eb92acc 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -62,6 +62,7 @@ type router struct { gatewaysLock sync.RWMutex brokers map[string]*broker brokersLock sync.RWMutex + status *status } func (r *router) tickGateways() { @@ -74,6 +75,7 @@ func (r *router) tickGateways() { func (r *router) Init(c *component.Component) error { r.Component = c + r.InitStatus() err := r.Component.UpdateTokenKey() if err != nil { return err diff --git a/core/router/status.go b/core/router/status.go new file mode 100644 index 000000000..4b56ab8b8 --- /dev/null +++ b/core/router/status.go @@ -0,0 +1,61 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/router" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter + gatewayStatus metrics.Meter +} + +func (r *router) InitStatus() { + r.status = &status{ + uplink: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + gatewayStatus: metrics.NewMeter(), + } +} + +func (r *router) GetStatus() *pb.Status { + status := new(pb.Status) + if r.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := r.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + downlink := r.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := r.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + gatewayStatus := r.status.gatewayStatus.Snapshot() + status.GatewayStatus = &api.Rates{ + Rate1: float32(gatewayStatus.Rate1()), + Rate5: float32(gatewayStatus.Rate5()), + Rate15: float32(gatewayStatus.Rate15()), + } + return status +} diff --git a/core/router/status_test.go b/core/router/status_test.go new file mode 100644 index 000000000..59a7c094e --- /dev/null +++ b/core/router/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package router + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/router" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + r := new(router) + a.So(r.GetStatus(), ShouldResemble, new(pb.Status)) + r.InitStatus() + a.So(r.status, ShouldNotBeNil) + status := r.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/router/uplink.go b/core/router/uplink.go index b58f59d74..2223e7f74 100644 --- a/core/router/uplink.go +++ b/core/router/uplink.go @@ -23,6 +23,7 @@ func (r *router) HandleUplink(gatewayID string, uplink *pb.UplinkMessage) (err e ctx.WithError(err).Warn("Could not handle uplink") } }() + r.status.uplink.Mark(1) // LoRaWAN: Unmarshal var phyPayload lorawan.PHYPayload diff --git a/core/router/util_test.go b/core/router/util_test.go index 18933f571..efe061e45 100644 --- a/core/router/util_test.go +++ b/core/router/util_test.go @@ -22,7 +22,7 @@ type testRouter struct { func getTestRouter(t *testing.T) *testRouter { ctrl := gomock.NewController(t) discovery := discovery.NewMockClient(ctrl) - return &testRouter{ + r := &testRouter{ router: &router{ Component: &component.Component{ Discovery: discovery, @@ -33,4 +33,6 @@ func getTestRouter(t *testing.T) *testRouter { ctrl: ctrl, discovery: discovery, } + r.InitStatus() + return r } From cf9b9bc51ba206b07d264610b22177c3657668ed Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sat, 3 Dec 2016 16:19:29 +0100 Subject: [PATCH 2233/2266] Add metrics to NetworkServer --- core/networkserver/activation.go | 1 + core/networkserver/activation_test.go | 1 + core/networkserver/downlink.go | 2 + core/networkserver/downlink_test.go | 1 + core/networkserver/manager_server.go | 12 +++++- core/networkserver/networkserver.go | 2 + core/networkserver/status.go | 53 +++++++++++++++++++++++++++ core/networkserver/status_test.go | 21 +++++++++++ core/networkserver/uplink.go | 3 ++ core/networkserver/uplink_test.go | 1 + 10 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 core/networkserver/status.go create mode 100644 core/networkserver/status_test.go diff --git a/core/networkserver/activation.go b/core/networkserver/activation.go index fafbcf785..56b3fe74d 100644 --- a/core/networkserver/activation.go +++ b/core/networkserver/activation.go @@ -118,6 +118,7 @@ func (n *networkServer) HandleActivate(activation *pb_handler.DeviceActivationRe if lorawan == nil { return nil, errors.NewErrInvalidArgument("Activation", "missing LoRaWAN ActivationMetadata") } + n.status.activations.Mark(1) err := n.devices.Activate(*lorawan.AppEui, *lorawan.DevEui, *lorawan.DevAddr, *lorawan.NwkSKey) if err != nil { return nil, err diff --git a/core/networkserver/activation_test.go b/core/networkserver/activation_test.go index 8ea46a20c..c730b8032 100644 --- a/core/networkserver/activation_test.go +++ b/core/networkserver/activation_test.go @@ -101,6 +101,7 @@ func TestHandleActivate(t *testing.T) { ns := &networkServer{ devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-activate"), } + ns.InitStatus() dev := &device.Device{ AppEUI: types.AppEUI(getEUI(0, 0, 0, 0, 0, 0, 3, 1)), diff --git a/core/networkserver/downlink.go b/core/networkserver/downlink.go index 1ef39f652..3ed17c6e1 100644 --- a/core/networkserver/downlink.go +++ b/core/networkserver/downlink.go @@ -16,6 +16,8 @@ func (n *networkServer) HandleDownlink(message *pb_broker.DownlinkMessage) (*pb_ return nil, err } + n.status.downlink.Mark(1) + dev.StartUpdate() if dev.AppID != message.AppId || dev.DevID != message.DevId { diff --git a/core/networkserver/downlink_test.go b/core/networkserver/downlink_test.go index 044b627de..5df03a6d5 100644 --- a/core/networkserver/downlink_test.go +++ b/core/networkserver/downlink_test.go @@ -19,6 +19,7 @@ func TestHandleDownlink(t *testing.T) { ns := &networkServer{ devices: device.NewRedisDeviceStore(GetRedisClient(), "test-handle-downlink"), } + ns.InitStatus() appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) diff --git a/core/networkserver/manager_server.go b/core/networkserver/manager_server.go index fc8d93a14..7621bcecf 100644 --- a/core/networkserver/manager_server.go +++ b/core/networkserver/manager_server.go @@ -157,7 +157,17 @@ func (n *networkServerManager) GetDevAddr(ctx context.Context, in *pb_lorawan.De } func (n *networkServerManager) GetStatus(ctx context.Context, in *pb.StatusRequest) (*pb.Status, error) { - return nil, grpc.Errorf(codes.Unimplemented, "Not Implemented") + if n.networkServer.Identity.Id != "dev" { + _, err := n.networkServer.ValidateTTNAuthContext(ctx) + if err != nil { + return nil, errors.NewErrPermissionDenied("No access") + } + } + status := n.networkServer.GetStatus() + if status == nil { + return new(pb.Status), nil + } + return status, nil } // RegisterManager registers this networkserver as a NetworkServerManagerServer (github.com/TheThingsNetwork/ttn/api/networkserver) diff --git a/core/networkserver/networkserver.go b/core/networkserver/networkserver.go index 36c7fdcc6..dc6ef2a72 100644 --- a/core/networkserver/networkserver.go +++ b/core/networkserver/networkserver.go @@ -44,6 +44,7 @@ type networkServer struct { devices device.Store netID [3]byte prefixes map[types.DevAddrPrefix][]string + status *status } func (n *networkServer) UsePrefix(prefix types.DevAddrPrefix, usage []string) error { @@ -77,6 +78,7 @@ func (n *networkServer) GetPrefixesFor(requiredUsages ...string) []types.DevAddr func (n *networkServer) Init(c *component.Component) error { n.Component = c + n.InitStatus() err := n.Component.UpdateTokenKey() if err != nil { return err diff --git a/core/networkserver/status.go b/core/networkserver/status.go new file mode 100644 index 000000000..6c8311804 --- /dev/null +++ b/core/networkserver/status.go @@ -0,0 +1,53 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "github.com/TheThingsNetwork/ttn/api" + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + "github.com/TheThingsNetwork/ttn/api/stats" + "github.com/rcrowley/go-metrics" +) + +type status struct { + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter +} + +func (n *networkServer) InitStatus() { + n.status = &status{ + uplink: metrics.NewMeter(), + downlink: metrics.NewMeter(), + activations: metrics.NewMeter(), + } +} + +func (n *networkServer) GetStatus() *pb.Status { + status := new(pb.Status) + if n.status == nil { + return status + } + status.System = stats.GetSystem() + status.Component = stats.GetComponent() + uplink := n.status.uplink.Snapshot() + status.Uplink = &api.Rates{ + Rate1: float32(uplink.Rate1()), + Rate5: float32(uplink.Rate5()), + Rate15: float32(uplink.Rate15()), + } + downlink := n.status.downlink.Snapshot() + status.Downlink = &api.Rates{ + Rate1: float32(downlink.Rate1()), + Rate5: float32(downlink.Rate5()), + Rate15: float32(downlink.Rate15()), + } + activations := n.status.activations.Snapshot() + status.Activations = &api.Rates{ + Rate1: float32(activations.Rate1()), + Rate5: float32(activations.Rate5()), + Rate15: float32(activations.Rate15()), + } + return status +} diff --git a/core/networkserver/status_test.go b/core/networkserver/status_test.go new file mode 100644 index 000000000..6db9b5722 --- /dev/null +++ b/core/networkserver/status_test.go @@ -0,0 +1,21 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package networkserver + +import ( + "testing" + + pb "github.com/TheThingsNetwork/ttn/api/networkserver" + . "github.com/smartystreets/assertions" +) + +func TestStatus(t *testing.T) { + a := New(t) + ns := new(networkServer) + a.So(ns.GetStatus(), ShouldResemble, new(pb.Status)) + ns.InitStatus() + a.So(ns.status, ShouldNotBeNil) + status := ns.GetStatus() + a.So(status.Uplink.Rate1, ShouldEqual, 0) +} diff --git a/core/networkserver/uplink.go b/core/networkserver/uplink.go index 838949818..594742a77 100644 --- a/core/networkserver/uplink.go +++ b/core/networkserver/uplink.go @@ -17,6 +17,9 @@ func (n *networkServer) HandleUplink(message *pb_broker.DeduplicatedUplinkMessag if err != nil { return nil, err } + + n.status.uplink.Mark(1) + dev.StartUpdate() // Unmarshal LoRaWAN Payload diff --git a/core/networkserver/uplink_test.go b/core/networkserver/uplink_test.go index 7c99af309..57d105869 100644 --- a/core/networkserver/uplink_test.go +++ b/core/networkserver/uplink_test.go @@ -23,6 +23,7 @@ func TestHandleUplink(t *testing.T) { ns := &networkServer{ devices: device.NewRedisDeviceStore(GetRedisClient(), "ns-test-handle-uplink"), } + ns.InitStatus() appEUI := types.AppEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) devEUI := types.DevEUI(getEUI(1, 2, 3, 4, 5, 6, 7, 8)) From 108df925353fdfe298166b635fbced2cb5779788 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Sun, 4 Dec 2016 14:35:45 +0100 Subject: [PATCH 2234/2266] Add connected Gateways and Brokers to Router status --- core/router/status.go | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/core/router/status.go b/core/router/status.go index 4b56ab8b8..116461d65 100644 --- a/core/router/status.go +++ b/core/router/status.go @@ -11,10 +11,12 @@ import ( ) type status struct { - uplink metrics.Meter - downlink metrics.Meter - activations metrics.Meter - gatewayStatus metrics.Meter + uplink metrics.Meter + downlink metrics.Meter + activations metrics.Meter + gatewayStatus metrics.Meter + connectedGateways metrics.Gauge + connectedBrokers metrics.Gauge } func (r *router) InitStatus() { @@ -23,6 +25,16 @@ func (r *router) InitStatus() { downlink: metrics.NewMeter(), activations: metrics.NewMeter(), gatewayStatus: metrics.NewMeter(), + connectedGateways: metrics.NewFunctionalGauge(func() int64 { + r.gatewaysLock.RLock() + defer r.gatewaysLock.RUnlock() + return int64(len(r.gateways)) + }), + connectedBrokers: metrics.NewFunctionalGauge(func() int64 { + r.brokersLock.RLock() + defer r.brokersLock.RUnlock() + return int64(len(r.brokers)) + }), } } @@ -57,5 +69,7 @@ func (r *router) GetStatus() *pb.Status { Rate5: float32(gatewayStatus.Rate5()), Rate15: float32(gatewayStatus.Rate15()), } + status.ConnectedGateways = uint32(r.status.connectedGateways.Snapshot().Value()) + status.ConnectedBrokers = uint32(r.status.connectedBrokers.Snapshot().Value()) return status } From 314e220f4c60ac94d1cb6fffa136be610026c38d Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Dec 2016 12:02:34 +0100 Subject: [PATCH 2235/2266] Move API validation from api.go to validation.go --- api/api.go | 15 --------------- api/broker/validation.go | 10 +++++----- api/discovery/validation.go | 2 +- api/handler/validation.go | 12 ++++++------ api/protocol/lorawan/validation.go | 4 ++-- api/validation.go | 19 ++++++++++++++++++- api/validation_test.go | 6 +++--- 7 files changed, 35 insertions(+), 33 deletions(-) diff --git a/api/api.go b/api/api.go index 2c1167275..fd0dc611a 100644 --- a/api/api.go +++ b/api/api.go @@ -13,21 +13,6 @@ import ( "google.golang.org/grpc/credentials" ) -// Validator interface is used to validate protos -type Validator interface { - // Returns the validation error or nil if valid - Validate() error -} - -// Validate the given object if it implements the Validator interface -// Must not be called with nil values! -func Validate(in interface{}) error { - if v, ok := in.(Validator); ok { - return v.Validate() - } - return nil -} - // Backoff indicates how long a client should wait between failed requests var Backoff = 1 * time.Second diff --git a/api/broker/validation.go b/api/broker/validation.go index bb8ca7607..f4db3cc83 100644 --- a/api/broker/validation.go +++ b/api/broker/validation.go @@ -40,10 +40,10 @@ func (m *UplinkMessage) Validate() error { // Validate implements the api.Validator interface func (m *DownlinkMessage) Validate() error { - if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { return err } - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } @@ -60,10 +60,10 @@ func (m *DownlinkMessage) Validate() error { // Validate implements the api.Validator interface func (m *DeduplicatedUplinkMessage) Validate() error { - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } - if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { return err } if err := api.NotNilAndValid(m.ProtocolMetadata, "ProtocolMetadata"); err != nil { @@ -121,7 +121,7 @@ func (m *ActivationChallengeRequest) Validate() error { // Validate implements the api.Validator interface func (m *ApplicationHandlerRegistration) Validate() error { - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } if m.HandlerId == "" { diff --git a/api/discovery/validation.go b/api/discovery/validation.go index 6134a7458..ac309da12 100644 --- a/api/discovery/validation.go +++ b/api/discovery/validation.go @@ -7,7 +7,7 @@ import ( // Validate implements the api.Validator interface func (m *Announcement) Validate() error { - if err := api.NotEmptyAndValidId(m.Id, "Id"); err != nil { + if err := api.NotEmptyAndValidID(m.Id, "Id"); err != nil { return err } switch m.ServiceName { diff --git a/api/handler/validation.go b/api/handler/validation.go index 5b5f3f9d7..19fe82b33 100644 --- a/api/handler/validation.go +++ b/api/handler/validation.go @@ -23,7 +23,7 @@ func (m *DeviceActivationResponse) Validate() error { // Validate implements the api.Validator interface func (m *ApplicationIdentifier) Validate() error { - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } return nil @@ -31,7 +31,7 @@ func (m *ApplicationIdentifier) Validate() error { // Validate implements the api.Validator interface func (m *Application) Validate() error { - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } return nil @@ -39,10 +39,10 @@ func (m *Application) Validate() error { // Validate implements the api.Validator interface func (m *DeviceIdentifier) Validate() error { - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } - if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { return err } return nil @@ -50,10 +50,10 @@ func (m *DeviceIdentifier) Validate() error { // Validate implements the api.Validator interface func (m *Device) Validate() error { - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } - if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { return err } if err := api.NotNilAndValid(m.Device, "Device"); err != nil { diff --git a/api/protocol/lorawan/validation.go b/api/protocol/lorawan/validation.go index 046dc25cb..639a0827c 100644 --- a/api/protocol/lorawan/validation.go +++ b/api/protocol/lorawan/validation.go @@ -24,10 +24,10 @@ func (m *Device) Validate() error { if m.DevEui == nil || m.DevEui.IsEmpty() { return errors.NewErrInvalidArgument("DevEui", "can not be empty") } - if err := api.NotEmptyAndValidId(m.AppId, "AppId"); err != nil { + if err := api.NotEmptyAndValidID(m.AppId, "AppId"); err != nil { return err } - if err := api.NotEmptyAndValidId(m.DevId, "DevId"); err != nil { + if err := api.NotEmptyAndValidID(m.DevId, "DevId"); err != nil { return err } return nil diff --git a/api/validation.go b/api/validation.go index 760b77df8..389d8dd65 100644 --- a/api/validation.go +++ b/api/validation.go @@ -7,6 +7,21 @@ import ( "github.com/TheThingsNetwork/ttn/utils/errors" ) +// Validator interface is used to validate protos +type Validator interface { + // Returns the validation error or nil if valid + Validate() error +} + +// Validate the given object if it implements the Validator interface +// Must not be called with nil values! +func Validate(in interface{}) error { + if v, ok := in.(Validator); ok { + return v.Validate() + } + return nil +} + var idRegexp = regexp.MustCompile("^[0-9a-z](?:[_-]?[0-9a-z]){1,35}$") // ValidID returns true if the given ID is a valid application or device ID @@ -14,7 +29,8 @@ func ValidID(id string) bool { return idRegexp.MatchString(id) } -func NotEmptyAndValidId(id string, argument string) error { +// NotEmptyAndValidID checks if the ID is not empty AND has a valid format +func NotEmptyAndValidID(id string, argument string) error { if id == "" { return errors.NewErrInvalidArgument(argument, "can not be empty") } @@ -24,6 +40,7 @@ func NotEmptyAndValidId(id string, argument string) error { return nil } +// NotNilAndValid checks if the given interface is not nil AND validates it func NotNilAndValid(in interface{}, argument string) error { // Structs can not be nil and reflect.ValueOf(in).IsNil() would panic if reflect.ValueOf(in).Kind() == reflect.Struct { diff --git a/api/validation_test.go b/api/validation_test.go index 5d88cb306..31c53a0d7 100644 --- a/api/validation_test.go +++ b/api/validation_test.go @@ -75,13 +75,13 @@ func TestValidID(t *testing.T) { } } -func TestNotEmptyAndValidId(t *testing.T) { - err := NotEmptyAndValidId("", "subject") +func TestNotEmptyAndValidID(t *testing.T) { + err := NotEmptyAndValidID("", "subject") if err == nil || err.Error() != "subject not valid: can not be empty" { t.Error("Expected validation error: 'subject not valid: can not be empty' but found", err) } - err = NotEmptyAndValidId("a", "subject") + err = NotEmptyAndValidID("a", "subject") if err == nil || err.Error() != "subject not valid: has wrong format a" { t.Error("Expected validation error: 'subject not valid: has wrong format a' but found", err) } From 3851b090f91944dad230d389a8c16362f454eef2 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Dec 2016 12:53:05 +0100 Subject: [PATCH 2236/2266] Check for announced address when generating cert --- cmd/genkeys.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/genkeys.go b/cmd/genkeys.go index a14d43c27..eea0e4ba9 100644 --- a/cmd/genkeys.go +++ b/cmd/genkeys.go @@ -30,7 +30,9 @@ func genCertCmd(component string) *cobra.Command { Long: `ttn gen-cert generates a TLS Certificate`, Run: func(cmd *cobra.Command, args []string) { var names []string - names = append(names, viper.GetString(component+".server-address-announce")) + if announcedName := viper.GetString(component + ".server-address-announce"); announcedName != "" { + names = append(names, announcedName) + } names = append(names, args...) if err := security.GenerateCert(viper.GetString("key-dir"), names...); err != nil { ctx.WithError(err).Fatal("Could not generate certificate") From 8240c3fed9c50b1281a1e1cd0213859bc98e0314 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Dec 2016 13:14:18 +0100 Subject: [PATCH 2237/2266] use TLS-first api.Dial --- api/api.go | 59 --------------- api/broker/communication_test.go | 4 +- api/dial.go | 121 +++++++++++++++++++++++++++++++ api/discovery/client.go | 2 +- api/discovery/dial.go | 5 +- api/monitor/client.go | 2 +- api/monitor/client_test.go | 10 +-- api/router/communication_test.go | 2 +- cmd/broker_register_prefix.go | 3 +- cmd/discovery.go | 2 +- core/broker/broker.go | 7 +- core/component/auth.go | 15 ++++ core/component/component.go | 3 +- core/router/router.go | 1 + ttnctl/cmd/root.go | 5 -- ttnctl/util/discovery.go | 2 +- 16 files changed, 161 insertions(+), 82 deletions(-) delete mode 100644 api/api.go create mode 100644 api/dial.go diff --git a/api/api.go b/api/api.go deleted file mode 100644 index fd0dc611a..000000000 --- a/api/api.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package api - -import ( - "crypto/tls" - "crypto/x509" - "net" - "time" - - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" -) - -// Backoff indicates how long a client should wait between failed requests -var Backoff = 1 * time.Second - -// KeepAlive indicates the keep-alive time for the Dialer -var KeepAlive = 10 * time.Second - -// DialOptions to use in TTN gRPC -var DialOptions = []grpc.DialOption{ - WithKeepAliveDialer(), -} - -// DialWithCert dials the address using the given TLS root cert -func DialWithCert(address string, cert string) (*grpc.ClientConn, error) { - var tlsConfig *tls.Config - - if cert != "" { - roots := x509.NewCertPool() - ok := roots.AppendCertsFromPEM([]byte(cert)) - if !ok { - panic("failed to parse root certificate") - } - tlsConfig = &tls.Config{RootCAs: roots} - } - - opts := DialOptions - if tlsConfig != nil { - opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) - } else { - opts = append(opts, grpc.WithInsecure()) - } - - return grpc.Dial( - address, - opts..., - ) -} - -// WithKeepAliveDialer creates a dialer with the configured KeepAlive time -func WithKeepAliveDialer() grpc.DialOption { - return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { - d := net.Dialer{Timeout: timeout, KeepAlive: KeepAlive} - return d.Dial("tcp", addr) - }) -} diff --git a/api/broker/communication_test.go b/api/broker/communication_test.go index a73f37937..a2ea741d9 100644 --- a/api/broker/communication_test.go +++ b/api/broker/communication_test.go @@ -56,7 +56,7 @@ func TestHandlerBrokerCommunication(t *testing.T) { port := rand.Intn(1000) + 10000 go brk.Serve(port) - conn, _ := grpc.Dial(fmt.Sprintf("localhost:%d", port), grpc.WithBlock(), grpc.WithInsecure()) + conn, _ := api.Dial(fmt.Sprintf("localhost:%d", port)) { brk.HandlerPublishChanFunc = func(md metadata.MD) (chan *DownlinkMessage, error) { @@ -150,7 +150,7 @@ func TestRouterBrokerCommunication(t *testing.T) { port := rand.Intn(1000) + 10000 go brk.Serve(port) - conn, _ := grpc.Dial(fmt.Sprintf("localhost:%d", port), grpc.WithBlock(), grpc.WithInsecure()) + conn, _ := api.Dial(fmt.Sprintf("localhost:%d", port)) { brk.RouterAssociateChanFunc = func(md metadata.MD) (chan *UplinkMessage, <-chan *DownlinkMessage, func(), error) { diff --git a/api/dial.go b/api/dial.go new file mode 100644 index 000000000..02e4b0ebf --- /dev/null +++ b/api/dial.go @@ -0,0 +1,121 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package api + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "strings" + "time" + + "github.com/TheThingsNetwork/ttn/utils/backoff" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" +) + +// KeepAlive indicates the keep-alive time for the Dialer +var KeepAlive = 10 * time.Second + +// MaxRetries indicates how often clients should retry dialing a component +var MaxRetries = 100 + +// Timeout for connections +var Timeout = 2 * time.Second + +// DialOptions to use in TTN gRPC +var DialOptions = []grpc.DialOption{ + WithKeepAliveDialer(), + grpc.WithBlock(), + grpc.FailOnNonTempDialError(true), + grpc.WithTimeout(Timeout), +} + +func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.ClientConn, err error) { + ctx := GetLogger().WithField("Address", address) + retries := 0 + retriesLeft := MaxRetries + opts := DialOptions + if tlsConfig != nil { + tlsConfig.ServerName = strings.SplitN(address, ":", 2)[0] // trim the port + opts = append(opts, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig))) + } else { + opts = append(opts, grpc.WithInsecure()) + } + for retriesLeft > 0 { + conn, err = grpc.Dial( + address, + opts..., + ) + if err == nil { + ctx.Debug("Connected") + return + } + + switch err := err.(type) { + case *net.OpError: + // Dial problem + if err.Op == "dial" { + ctx.WithError(err).Debug("Could not connect, reconnecting...") + } + case x509.CertificateInvalidError, + x509.ConstraintViolationError, + x509.HostnameError, + x509.InsecureAlgorithmError, + x509.SystemRootsError, + x509.UnhandledCriticalExtension, + x509.UnknownAuthorityError: + // Non-temporary error while connecting to a TLS-enabled server + return nil, err + case tls.RecordHeaderError: + if fallback { + ctx.WithError(err).Warn("Could not connect with TLS, reconnecting without it...") + return dial(address, nil, fallback) + } + return nil, err + default: + GetLogger().WithField("ErrType", fmt.Sprintf("%T", err)).WithError(err).Error("Unhandled dial error [please create issue on Github]") + return nil, err + } + + // Backoff + time.Sleep(backoff.Backoff(retries)) + retries++ + retriesLeft-- + } + return +} + +// RootCAs to use in API connections +var RootCAs *x509.CertPool + +func init() { + RootCAs, _ = x509.SystemCertPool() +} + +// Dial an address +func Dial(address string) (*grpc.ClientConn, error) { + tlsConfig := &tls.Config{RootCAs: RootCAs} + return dial(address, tlsConfig, true) +} + +// DialWithCert dials the address using the given TLS cert +func DialWithCert(address string, cert string) (*grpc.ClientConn, error) { + rootCAs := x509.NewCertPool() + ok := rootCAs.AppendCertsFromPEM([]byte(cert)) + if !ok { + panic("failed to parse root certificate") + } + tlsConfig := &tls.Config{RootCAs: rootCAs} + return dial(address, tlsConfig, false) +} + +// WithKeepAliveDialer creates a dialer with the configured KeepAlive time +func WithKeepAliveDialer() grpc.DialOption { + return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + d := net.Dialer{Timeout: timeout, KeepAlive: KeepAlive} + return d.Dial("tcp", addr) + }) +} diff --git a/api/discovery/client.go b/api/discovery/client.go index 29cca2741..5d7358f33 100644 --- a/api/discovery/client.go +++ b/api/discovery/client.go @@ -38,7 +38,7 @@ type Client interface { // NewClient returns a new Client func NewClient(server string, announcement *Announcement, tokenFunc func() string) (Client, error) { - conn, err := grpc.Dial(server, append(api.DialOptions, grpc.WithBlock(), grpc.WithInsecure())...) + conn, err := api.Dial(server) if err != nil { return nil, err } diff --git a/api/discovery/dial.go b/api/discovery/dial.go index 2f66e4888..bad0f934d 100644 --- a/api/discovery/dial.go +++ b/api/discovery/dial.go @@ -1,7 +1,7 @@ package discovery import ( - "github.com/TheThingsNetwork/ttn/utils/errors" + "errors" "strings" "github.com/TheThingsNetwork/ttn/api" @@ -13,5 +13,8 @@ func (a *Announcement) Dial() (*grpc.ClientConn, error) { if a.NetAddress == "" { return nil, errors.New("Can not dial this component") } + if a.Certificate == "" { + return api.Dial(strings.Split(a.NetAddress, ",")[0]) + } return api.DialWithCert(strings.Split(a.NetAddress, ",")[0], a.Certificate) } diff --git a/api/monitor/client.go b/api/monitor/client.go index ffdd4f65a..93c7b2259 100644 --- a/api/monitor/client.go +++ b/api/monitor/client.go @@ -63,7 +63,7 @@ func (cl *Client) open() (err error) { ctx.Debug("Opening monitor connection...") - cl.conn, err = grpc.Dial(addr, append(api.DialOptions, grpc.WithInsecure())...) + cl.conn, err = api.Dial(addr) if err != nil { ctx.WithError(errors.FromGRPCError(err)).Warn("Failed to establish connection to gRPC service") return err diff --git a/api/monitor/client_test.go b/api/monitor/client_test.go index 23df43234..f3c37de2f 100644 --- a/api/monitor/client_test.go +++ b/api/monitor/client_test.go @@ -25,7 +25,7 @@ func TestClient(t *testing.T) { go startExampleServer(2, port) { - client, err := NewClient(ctx, fmt.Sprintf(":%d", port)) + client, err := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) a.So(err, ShouldBeNil) a.So(client.IsConnected(), ShouldBeTrue) a.So(client.Reopen(), ShouldBeNil) @@ -34,7 +34,7 @@ func TestClient(t *testing.T) { } { - client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) gtw := client.GatewayClient("dev") a.So(gtw.IsConfigured(), ShouldBeFalse) gtw.SetToken("SOME.AWESOME.JWT") @@ -50,7 +50,7 @@ func TestClient(t *testing.T) { } { - client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) defer client.Close() gtw := client.GatewayClient("dev") @@ -83,7 +83,7 @@ func TestClient(t *testing.T) { } { - client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) defer client.Close() gtw := client.GatewayClient("dev") @@ -116,7 +116,7 @@ func TestClient(t *testing.T) { } { - client, _ := NewClient(ctx, fmt.Sprintf(":%d", port)) + client, _ := NewClient(ctx, fmt.Sprintf("localhost:%d", port)) defer client.Close() gtw := client.GatewayClient("dev") diff --git a/api/router/communication_test.go b/api/router/communication_test.go index 2bee6175c..373aabecd 100644 --- a/api/router/communication_test.go +++ b/api/router/communication_test.go @@ -57,7 +57,7 @@ func TestRouterCommunication(t *testing.T) { port := rand.Intn(1000) + 10000 go rtr.Serve(port) - conn, _ := grpc.Dial(fmt.Sprintf("localhost:%d", port), grpc.WithBlock(), grpc.WithInsecure()) + conn, _ := api.Dial(fmt.Sprintf("localhost:%d", port)) { rtr.UplinkChanFunc = func(md metadata.MD) (chan *UplinkMessage, error) { diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index 20e23f277..183a740db 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -10,7 +10,6 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "golang.org/x/net/context" // See https://github.com/grpc/grpc-go/issues/711" - "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) @@ -24,7 +23,7 @@ var brokerRegisterPrefixCmd = &cobra.Command{ cmd.UsageFunc()(cmd) } - conn, err := grpc.Dial(viper.GetString("discovery-address"), append(api.DialOptions, grpc.WithInsecure(), grpc.WithBlock())...) + conn, err := api.Dial(viper.GetString("discovery-address")) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") } diff --git a/cmd/discovery.go b/cmd/discovery.go index b7f3a4ae1..383f6ab0f 100644 --- a/cmd/discovery.go +++ b/cmd/discovery.go @@ -50,7 +50,7 @@ var discoveryCmd = &cobra.Command{ connectRedis(client) // Component - component, err := component.New(ctx, "discovery", fmt.Sprintf("%s:%d", viper.GetString("discovery.server-address-announce"), viper.GetInt("discovery.server-port"))) + component, err := component.New(ctx, "discovery", fmt.Sprintf("%s:%d", "localhost", viper.GetInt("discovery.server-port"))) if err != nil { ctx.WithError(err).Fatal("Could not initialize component") } diff --git a/core/broker/broker.go b/core/broker/broker.go index b9ff4d91a..f6758a048 100644 --- a/core/broker/broker.go +++ b/core/broker/broker.go @@ -118,7 +118,12 @@ func (b *broker) Init(c *component.Component) error { return err } b.Discovery.GetAll("handler") // Update cache - conn, err := api.DialWithCert(b.nsAddr, b.nsCert) + var conn *grpc.ClientConn + if b.nsCert == "" { + conn, err = api.Dial(b.nsAddr) + } else { + conn, err = api.DialWithCert(b.nsAddr, b.nsCert) + } if err != nil { return err } diff --git a/core/component/auth.go b/core/component/auth.go index aad3c59a9..421f6a0d3 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -3,7 +3,9 @@ package component import ( "crypto/tls" "fmt" + "io/ioutil" "net/url" + "path/filepath" "time" "github.com/TheThingsNetwork/go-account-lib/cache" @@ -25,6 +27,7 @@ func (c *Component) InitAuth() error { inits := []func() error{ c.initAuthServers, c.initKeyPair, + c.initRoots, } if c.Config.UseTLS { inits = append(inits, c.initTLS) @@ -121,6 +124,18 @@ func (c *Component) initTLS() error { return nil } +func (c *Component) initRoots() error { + path := filepath.Clean(c.Config.KeyDir + "/ca.cert") + cert, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + if !api.RootCAs.AppendCertsFromPEM(cert) { + return fmt.Errorf("Could not add root certificates from %s", path) + } + return nil +} + // BuildJWT builds a short-lived JSON Web Token for this component func (c *Component) BuildJWT() (string, error) { if c.privateKey != nil { diff --git a/core/component/component.go b/core/component/component.go index 383d22cd3..3ecfb318c 100644 --- a/core/component/component.go +++ b/core/component/component.go @@ -85,7 +85,7 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo return nil, err } - if serviceName != "discovery" { + if serviceName != "discovery" && serviceName != "networkserver" { var err error component.Discovery, err = pb_discovery.NewClient( viper.GetString("discovery-address"), @@ -122,7 +122,6 @@ func New(ctx log.Interface, serviceName string, announcedAddress string) (*Compo var err error component.Monitors[name], err = pb_monitor.NewClient(ctx.WithField("Monitor", name), addr) if err != nil { - // Assuming grpc.WithBlock() is not set return nil, err } } diff --git a/core/router/router.go b/core/router/router.go index b3eb92acc..ffed69a87 100644 --- a/core/router/router.go +++ b/core/router/router.go @@ -151,6 +151,7 @@ func (r *router) getBroker(brokerAnnouncement *pb_discovery.Announcement) (*brok if _, ok := r.brokers[brokerAnnouncement.Id]; !ok { // Connect to the server + // TODO(htdvisser): This is blocking conn, err := brokerAnnouncement.Dial() if err != nil { return nil, err diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index 9bfa61e57..b75f1960f 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -7,7 +7,6 @@ import ( "fmt" "os" "strings" - "time" cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" "github.com/TheThingsNetwork/ttn/api" @@ -15,7 +14,6 @@ import ( "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" - "google.golang.org/grpc" ) var cfgFile string @@ -43,9 +41,6 @@ var RootCmd = &cobra.Command{ util.PrintConfig(ctx, true) } - api.DialOptions = append(api.DialOptions, grpc.WithBlock()) - api.DialOptions = append(api.DialOptions, grpc.WithTimeout(2*time.Second)) - api.SetLogger(api.Apex(ctx)) }, diff --git a/ttnctl/util/discovery.go b/ttnctl/util/discovery.go index 912ea512c..02945b945 100644 --- a/ttnctl/util/discovery.go +++ b/ttnctl/util/discovery.go @@ -13,7 +13,7 @@ import ( // GetDiscovery gets the Discovery client for ttnctl func GetDiscovery(ctx log.Interface) (*grpc.ClientConn, discovery.DiscoveryClient) { - conn, err := grpc.Dial(viper.GetString("discovery-address"), append(api.DialOptions, grpc.WithInsecure())...) + conn, err := api.Dial(viper.GetString("discovery-address")) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") } From e08ae42c824ca6caf826121fe691b26e77ff983c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 6 Dec 2016 13:14:36 +0100 Subject: [PATCH 2238/2266] Restructure RootCmd PersistentFlags --- cmd/root.go | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index f1c1d3d27..7dcd680da 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -120,28 +120,23 @@ func init() { RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default \"$HOME/.ttn.yml\")") - RootCmd.PersistentFlags().String("id", "", "The id of this component") - viper.BindPFlag("id", RootCmd.PersistentFlags().Lookup("id")) + RootCmd.PersistentFlags().Bool("no-cli-logs", false, "Disable CLI logs") + RootCmd.PersistentFlags().String("log-file", "", "Location of the log file") + RootCmd.PersistentFlags().String("elasticsearch", "", "Location of Elasticsearch server for logging") + RootCmd.PersistentFlags().String("id", "", "The id of this component") RootCmd.PersistentFlags().String("description", "", "The description of this component") - viper.BindPFlag("description", RootCmd.PersistentFlags().Lookup("description")) - RootCmd.PersistentFlags().Bool("public", false, "Announce this component as part of The Things Network (public community network)") - viper.BindPFlag("public", RootCmd.PersistentFlags().Lookup("public")) RootCmd.PersistentFlags().String("discovery-address", "discover.thethingsnetwork.org:1900", "The address of the Discovery server") - viper.BindPFlag("discovery-address", RootCmd.PersistentFlags().Lookup("discovery-address")) + RootCmd.PersistentFlags().String("auth-token", "", "The JWT token to be used for the discovery server") + + RootCmd.PersistentFlags().Int("health-port", 0, "The port number where the health server should be started") viper.SetDefault("auth-servers", map[string]string{ "ttn-account": "https://account.thethingsnetwork.org", }) - RootCmd.PersistentFlags().String("auth-token", "", "The JWT token to be used for the discovery server") - viper.BindPFlag("auth-token", RootCmd.PersistentFlags().Lookup("auth-token")) - - RootCmd.PersistentFlags().Int("health-port", 0, "The port number where the health server should be started") - viper.BindPFlag("health-port", RootCmd.PersistentFlags().Lookup("health-port")) - dir, err := homedir.Dir() if err == nil { dir, _ = homedir.Expand(dir) @@ -154,19 +149,9 @@ func init() { } RootCmd.PersistentFlags().Bool("tls", false, "Use TLS") - viper.BindPFlag("tls", RootCmd.PersistentFlags().Lookup("tls")) - RootCmd.PersistentFlags().String("key-dir", path.Clean(dir+"/.ttn/"), "The directory where public/private keys are stored") - viper.BindPFlag("key-dir", RootCmd.PersistentFlags().Lookup("key-dir")) - RootCmd.PersistentFlags().Bool("no-cli-logs", false, "Disable CLI logs") - viper.BindPFlag("no-cli-logs", RootCmd.PersistentFlags().Lookup("no-cli-logs")) - - RootCmd.PersistentFlags().String("log-file", "", "Location of the log file") - viper.BindPFlag("log-file", RootCmd.PersistentFlags().Lookup("log-file")) - - RootCmd.PersistentFlags().String("elasticsearch", "", "Location of Elasticsearch server for logging") - viper.BindPFlag("elasticsearch", RootCmd.PersistentFlags().Lookup("elasticsearch")) + viper.BindPFlags(RootCmd.PersistentFlags()) } // initConfig reads in config file and ENV variables if set. From 232ac0c68e46ce96522163227cefef4b6c248fa6 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 11:57:05 +0100 Subject: [PATCH 2239/2266] Allow local "auth server" pubKeys to be configured --- core/component/auth.go | 21 ++++++++++++- vendor/vendor.json | 68 +++++++++++++++++++++++++----------------- 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/core/component/auth.go b/core/component/auth.go index 421f6a0d3..49ea378a4 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -5,7 +5,9 @@ import ( "fmt" "io/ioutil" "net/url" + "path" "path/filepath" + "strings" "time" "github.com/TheThingsNetwork/go-account-lib/cache" @@ -63,17 +65,34 @@ func parseAuthServer(str string) (srv authServer, err error) { func (c *Component) initAuthServers() error { urlMap := make(map[string]string) + funcMap := make(map[string]tokenkey.TokenFunc) + var httpProvider tokenkey.Provider for id, url := range c.Config.AuthServers { + if strings.HasPrefix(url, "file://") { + file := strings.TrimPrefix(url, "file://") + contents, err := ioutil.ReadFile(path.Clean(file)) + if err != nil { + return err + } + funcMap[id] = func(renew bool) (*tokenkey.TokenKey, error) { + return &tokenkey.TokenKey{Algorithm: "ES256", Key: string(contents)}, nil + } + continue + } srv, err := parseAuthServer(url) if err != nil { return err } urlMap[id] = srv.url + funcMap[id] = func(renew bool) (*tokenkey.TokenKey, error) { + return httpProvider.Get(id, renew) + } } - c.TokenKeyProvider = tokenkey.HTTPProvider( + httpProvider = tokenkey.HTTPProvider( urlMap, cache.WriteTroughCacheWithFormat(c.Config.KeyDir, "auth-%s.pub"), ) + c.TokenKeyProvider = tokenkey.FuncProvider(funcMap) return nil } diff --git a/vendor/vendor.json b/vendor/vendor.json index d2c9ffe76..b462e05e8 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -15,70 +15,70 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "ICp8dlJ+2nmD5G2IW5VktDKNNbE=", + "checksumSHA1": "nXjI+Ldbj4poEcdG2qG6mJJSwHw=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { - "checksumSHA1": "q8JDdyjDeAQLP5ieycuGgUKgUuY=", + "checksumSHA1": "EDBQe1RJEmQmVvdMp6MHXW5Px9c=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { - "checksumSHA1": "isNnBVNlGIIAMzgvJtrg59ky+uI=", + "checksumSHA1": "iXtQRpCIHmyEn70SWf5ZdNqtkhk=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { "checksumSHA1": "oZso6YaE7ogZLUZzUCA3yYDym0E=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { - "checksumSHA1": "hb9TvLjXpj97hKBWHMBDyHGrpXY=", + "checksumSHA1": "RpKXQd5sp9/jsWM991S7OhE9/ME=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { - "checksumSHA1": "iw9nN70RU1HK4VbosXcZp290WMg=", + "checksumSHA1": "R0TfhR++1b5YqHsMso4QKiR3IAI=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { - "checksumSHA1": "bEAGmOA1XitIrfbvyE4K12aB5mc=", + "checksumSHA1": "SiXlhEd19Cs/PZqeBOYpvTsHRzM=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "f66a58147358c64b7fa0bfdd98064882e13391d5", - "revisionTime": "2016-11-29T09:14:58Z" + "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", + "revisionTime": "2016-12-08T10:46:26Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", @@ -86,6 +86,18 @@ "revision": "364a5d5b165140adfe751763e651fca78ea5e7ed", "revisionTime": "2016-11-01T08:25:08Z" }, + { + "checksumSHA1": "aXt7ZSqIfsHWBbJPgHFjqtyxyQ0=", + "path": "github.com/TheThingsNetwork/go-utils/log", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, + { + "checksumSHA1": "fLBAMyMsGRd2c9t2FifyyZVtvlk=", + "path": "github.com/TheThingsNetwork/go-utils/log/apex", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", "path": "github.com/apex/log", From 5577db28feb6d0a6bc64ff7e87b9b63b8bb7c5bb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 13:26:28 +0100 Subject: [PATCH 2240/2266] Update Vendors --- core/storage/redis_kv_store.go | 4 + core/storage/redis_map_store.go | 4 + core/storage/redis_set_store.go | 4 + vendor/vendor.json | 308 +++++++++++++++++--------------- 4 files changed, 178 insertions(+), 142 deletions(-) diff --git a/core/storage/redis_kv_store.go b/core/storage/redis_kv_store.go index 4c21ea4fb..e4848fbce 100644 --- a/core/storage/redis_kv_store.go +++ b/core/storage/redis_kv_store.go @@ -31,6 +31,10 @@ func NewRedisKVStore(client *redis.Client, prefix string) *RedisKVStore { // GetAll returns all results for the given keys, prepending the prefix to the keys if necessary func (s *RedisKVStore) GetAll(keys []string, options *ListOptions) (map[string]string, error) { + if len(keys) == 0 { + return map[string]string{}, nil + } + for i, key := range keys { if !strings.HasPrefix(key, s.prefix) { keys[i] = s.prefix + key diff --git a/core/storage/redis_map_store.go b/core/storage/redis_map_store.go index eabe7c96e..57b82e68c 100644 --- a/core/storage/redis_map_store.go +++ b/core/storage/redis_map_store.go @@ -49,6 +49,10 @@ func (s *RedisMapStore) SetDecoder(decoder StringStringMapDecoder) { // GetAll returns all results for the given keys, prepending the prefix to the keys if necessary func (s *RedisMapStore) GetAll(keys []string, options *ListOptions) ([]interface{}, error) { + if len(keys) == 0 { + return []interface{}{}, nil + } + for i, key := range keys { if !strings.HasPrefix(key, s.prefix) { keys[i] = s.prefix + key diff --git a/core/storage/redis_set_store.go b/core/storage/redis_set_store.go index a06688f36..22a8a7971 100644 --- a/core/storage/redis_set_store.go +++ b/core/storage/redis_set_store.go @@ -31,6 +31,10 @@ func NewRedisSetStore(client *redis.Client, prefix string) *RedisSetStore { // GetAll returns all results for the given keys, prepending the prefix to the keys if necessary func (s *RedisSetStore) GetAll(keys []string, options *ListOptions) (map[string][]string, error) { + if len(keys) == 0 { + return map[string][]string{}, nil + } + for i, key := range keys { if !strings.HasPrefix(key, s.prefix) { keys[i] = s.prefix + key diff --git a/vendor/vendor.json b/vendor/vendor.json index b462e05e8..5cc2783a9 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3,10 +3,10 @@ "ignore": "test", "package": [ { - "checksumSHA1": "jRtYpPa7CRuA+LP4ELF9c9CjJao=", + "checksumSHA1": "h5Q1o+kA7VMAR5QpBzVJZbBEgC0=", "path": "github.com/Sirupsen/logrus", - "revision": "e400ff7861bce9661cf37c162ce3b7b303baf333", - "revisionTime": "2016-11-28T22:57:24Z" + "revision": "881bee4e20a5d11a6a88a5667c6f292072ac1963", + "revisionTime": "2016-12-02T02:35:07Z" }, { "checksumSHA1": "9NR0rrcAT5J76C5xMS4AVksS9o0=", @@ -83,8 +83,8 @@ { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", "path": "github.com/TheThingsNetwork/go-utils/handlers/cli", - "revision": "364a5d5b165140adfe751763e651fca78ea5e7ed", - "revisionTime": "2016-11-01T08:25:08Z" + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" }, { "checksumSHA1": "aXt7ZSqIfsHWBbJPgHFjqtyxyQ0=", @@ -153,16 +153,16 @@ "revisionTime": "2016-11-01T19:39:35Z" }, { - "checksumSHA1": "PPyNjcW5yfrfXvARE74fh9JBSpo=", + "checksumSHA1": "7x4aKJIFmr5uno+npHbfMVoCFU4=", "path": "github.com/eclipse/paho.mqtt.golang", - "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", - "revisionTime": "2016-10-12T12:34:52Z" + "revision": "cf795e7e56f38805f30998d74674b207fa47ee3c", + "revisionTime": "2016-12-07T10:34:25Z" }, { "checksumSHA1": "wPreCwXsA/oU2R+lkOGpR6skdA0=", "path": "github.com/eclipse/paho.mqtt.golang/packets", - "revision": "13afcbe8e41508479762a90e9242577210c2ca8d", - "revisionTime": "2016-10-12T12:34:52Z" + "revision": "cf795e7e56f38805f30998d74674b207fa47ee3c", + "revisionTime": "2016-12-07T10:34:25Z" }, { "checksumSHA1": "KCWVxG+J8SxHGlGiUghe0KBGsa8=", @@ -191,20 +191,20 @@ { "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", "path": "github.com/gogo/protobuf/gogoproto", - "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", - "revisionTime": "2016-11-05T09:51:13Z" + "revision": "1a78ea63e7f62d64c7630e5980a0dfd623545449", + "revisionTime": "2016-12-07T19:43:35Z" }, { - "checksumSHA1": "oDNpaVF/BHUyme8A7RoBAH2q6is=", + "checksumSHA1": "PZNhiizCvRgRff2lcG9kRYJMXqI=", "path": "github.com/gogo/protobuf/proto", - "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", - "revisionTime": "2016-11-05T09:51:13Z" + "revision": "1a78ea63e7f62d64c7630e5980a0dfd623545449", + "revisionTime": "2016-12-07T19:43:35Z" }, { "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", - "revision": "8d70fb3182befc465c4a1eac8ad4d38ff49778e2", - "revisionTime": "2016-11-05T09:51:13Z" + "revision": "1a78ea63e7f62d64c7630e5980a0dfd623545449", + "revisionTime": "2016-12-07T19:43:35Z" }, { "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", @@ -291,58 +291,58 @@ "revisionTime": "2016-11-28T00:20:07Z" }, { - "checksumSHA1": "8OPDk+bKyRGJoKcS4QNw9F7dpE8=", + "checksumSHA1": "Ok3Csn6Voou7pQT6Dv2mkwpqFtw=", "path": "github.com/hashicorp/hcl", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { "checksumSHA1": "XQmjDva9JCGGkIecOgwtBEMCJhU=", "path": "github.com/hashicorp/hcl/hcl/ast", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { - "checksumSHA1": "croNloscHsjX87X+4/cKOURf1EY=", + "checksumSHA1": "vF6LLywGDoAaccTcAGrcY7mYvZc=", "path": "github.com/hashicorp/hcl/hcl/parser", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { - "checksumSHA1": "Zz4271B4Kc+rwwK7cbaRv7STfO8=", + "checksumSHA1": "z6wdP4mRw4GVjShkNHDaOWkbxS0=", "path": "github.com/hashicorp/hcl/hcl/scanner", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { - "checksumSHA1": "/e0ULfQnGeUKiM1+iMnQhImo62k=", + "checksumSHA1": "oS3SCN9Wd6D8/LG0Yx1fu84a7gI=", "path": "github.com/hashicorp/hcl/hcl/strconv", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { "checksumSHA1": "c6yprzj06ASwCo18TtbbNNBHljA=", "path": "github.com/hashicorp/hcl/hcl/token", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { "checksumSHA1": "138aCV5n8n7tkGYMsMVQQnnLq+0=", "path": "github.com/hashicorp/hcl/json/parser", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { "checksumSHA1": "YdvFsNOMSWMLnY6fcliWQa0O5Fw=", "path": "github.com/hashicorp/hcl/json/scanner", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { "checksumSHA1": "fNlXQCQEnb+B3k5UDL/r15xtSJY=", "path": "github.com/hashicorp/hcl/json/token", - "revision": "7cb7455c285ca3bf3362aa4ba6a06a6d6f5c3ba0", - "revisionTime": "2016-11-22T02:11:24Z" + "revision": "37ab263305aaeb501a60eb16863e808d426e37f2", + "revisionTime": "2016-12-01T14:17:04Z" }, { "checksumSHA1": "lJDwzzEBuS9sjxVOSsq7+rvw+cA=", @@ -393,16 +393,16 @@ "revisionTime": "2016-10-12T01:35:12Z" }, { - "checksumSHA1": "AXacfEchaUqT5RGmPmMXsOWRhv8=", + "checksumSHA1": "V/quM7+em2ByJbWBLOsEwnY3j/Q=", "path": "github.com/mitchellh/go-homedir", - "revision": "756f7b183b7ab78acdbbee5c7f392838ed459dda", - "revisionTime": "2016-06-21T17:42:43Z" + "revision": "b8bc1bf767474819792c23f32d8286a45736f1c6", + "revisionTime": "2016-12-03T19:45:07Z" }, { - "checksumSHA1": "UuXgD2dDojfS8AViUEe15gLIWZE=", + "checksumSHA1": "oouYEOJ4wFLlCWEgNy7/MWIzAfg=", "path": "github.com/mitchellh/mapstructure", - "revision": "f3009df150dadf309fdee4a54ed65c124afad715", - "revisionTime": "2016-10-20T16:18:36Z" + "revision": "5a0325d7fafaac12dda6e7fb8bd222ec1b69875e", + "revisionTime": "2016-12-04T05:35:18Z" }, { "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", @@ -417,10 +417,10 @@ "revisionTime": "2016-01-24T19:35:03Z" }, { - "checksumSHA1": "jIC6IQtpM2X84AUYISO/V7C9hQs=", + "checksumSHA1": "n+1TqBSxUOC3ajo+8ZgQHgpQTsw=", "path": "github.com/pelletier/go-toml", - "revision": "7cb988051d5045890cb91402a0b5fddc76c627bc", - "revisionTime": "2016-11-23T15:24:52Z" + "revision": "ce7be745f09fe4ff89af8e3ea744e1deabf20ee3", + "revisionTime": "2016-12-03T11:32:16Z" }, { "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", @@ -479,44 +479,44 @@ { "checksumSHA1": "ujFjoR6H3TDeiuY9kvvwSwBMcJk=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { "checksumSHA1": "A5OQcD4rTEhWAfWvp6lzFBA9lfs=", "path": "github.com/shirou/gopsutil/host", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { "checksumSHA1": "sNAWEDfrq5thpuxsiTvRNJ1fii0=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { "checksumSHA1": "jB8En6qWQ7G2yPJey4uY1FvOjWM=", "path": "github.com/shirou/gopsutil/load", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { "checksumSHA1": "sOBIj+eocRSO0xtX8vkJDZKTDl8=", "path": "github.com/shirou/gopsutil/mem", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { - "checksumSHA1": "DWrRDbT4hDhy1oiYM0My7UJv+Zc=", + "checksumSHA1": "TkJnrLSoB56wbzGI6hzsmMlSBj8=", "path": "github.com/shirou/gopsutil/net", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { "checksumSHA1": "+4bS/41wTuGe0oczEwrBCWp6YO8=", "path": "github.com/shirou/gopsutil/process", - "revision": "061b699a971e0f74834f69ef8b78643f48980d10", - "revisionTime": "2016-11-25T00:04:14Z" + "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", + "revisionTime": "2016-12-02T13:28:38Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -591,10 +591,10 @@ "revisionTime": "2016-10-29T21:33:52Z" }, { - "checksumSHA1": "KLtNzjyzIFsTTMvM2WdHGzhdbXQ=", + "checksumSHA1": "u/90tmP9R7gFX3vA0U2c3DLhjMc=", "path": "github.com/streadway/amqp", - "revision": "1b8853833a94be8ba57fd121a8651f7221e5b051", - "revisionTime": "2016-11-28T15:24:32Z" + "revision": "4da4cfa085c174b489ec35cf1e29ffb147d45298", + "revisionTime": "2016-12-06T10:42:06Z" }, { "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", @@ -615,88 +615,112 @@ "revisionTime": "2016-06-07T20:24:39Z" }, { - "checksumSHA1": "9C4Av3ypK5pi173F76ogJT/d8x4=", + "checksumSHA1": "VE+WBfxeMNC5a98uLXK2Iu80hOU=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "ede567c8e044a5913dad1d1af3696d9da953104c", - "revisionTime": "2016-11-04T19:41:44Z" + "revision": "95cb608f365d51e0e69abc646ec90c0e26fb427f", + "revisionTime": "2016-12-05T17:23:45Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { - "checksumSHA1": "pLsZUQhI8jm3W9R/4JO9D/L1cUA=", + "checksumSHA1": "ydijX5KGTum3FoyPXzKJFeubMbE=", "path": "golang.org/x/net/http2", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { "checksumSHA1": "P9qTIn8a6L6Q9wd1IJBCuhno1Q8=", "path": "golang.org/x/net/trace", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { - "checksumSHA1": "O2sws+miRriMPObINsbwa0jnXxE=", + "checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=", "path": "golang.org/x/net/websocket", - "revision": "4971afdc2f162e82d185353533d3cf16188a9f4e", - "revisionTime": "2016-11-15T21:05:04Z" + "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", + "revisionTime": "2016-12-07T20:53:17Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", - "revision": "d5040cddfc0da40b408c9a1da4728662435176a9", - "revisionTime": "2016-11-03T22:50:36Z" + "revision": "f6093e37b6cb4092101a298aba5d794eb570757f", + "revisionTime": "2016-11-07T21:06:47Z" }, { "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", "path": "golang.org/x/oauth2/internal", - "revision": "d5040cddfc0da40b408c9a1da4728662435176a9", - "revisionTime": "2016-11-03T22:50:36Z" + "revision": "f6093e37b6cb4092101a298aba5d794eb570757f", + "revisionTime": "2016-11-07T21:06:47Z" }, { - "checksumSHA1": "MlTI84eWAFvqeRgXxBtjRYHk1yQ=", + "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", "path": "golang.org/x/sys/unix", - "revision": "30237cf4eefd639b184d1f2cb77a581ea0be8947", - "revisionTime": "2016-11-19T15:29:01Z" + "revision": "478fcf54317e52ab69f40bb4c7a1520288d7f7ea", + "revisionTime": "2016-12-05T15:46:50Z" + }, + { + "checksumSHA1": "kv3jbPJGCczHVQ7g51am1MxlD1c=", + "path": "golang.org/x/text/internal/gen", + "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", + "revisionTime": "2016-11-30T07:41:09Z" + }, + { + "checksumSHA1": "47nwiUyVBY2RKoEGXmCSvusY4Js=", + "path": "golang.org/x/text/internal/triegen", + "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", + "revisionTime": "2016-11-30T07:41:09Z" + }, + { + "checksumSHA1": "Yd5wMObzagIfCiKLpZbtBIrOUA4=", + "path": "golang.org/x/text/internal/ucd", + "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", + "revisionTime": "2016-11-30T07:41:09Z" }, { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "b01949dc0793a9af5e4cb3fce4d42999e76e8ca1", - "revisionTime": "2016-11-03T07:49:12Z" + "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", + "revisionTime": "2016-11-30T07:41:09Z" + }, + { + "checksumSHA1": "i14IZXKECObKRUNvTr7xivSL1IU=", + "path": "golang.org/x/text/unicode/cldr", + "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", + "revisionTime": "2016-11-30T07:41:09Z" }, { "checksumSHA1": "Vircurgvsnt4k26havmxPM67PUA=", "path": "golang.org/x/text/unicode/norm", - "revision": "b01949dc0793a9af5e4cb3fce4d42999e76e8ca1", - "revisionTime": "2016-11-03T07:49:12Z" + "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", + "revisionTime": "2016-11-30T07:41:09Z" }, { "checksumSHA1": "gYHoPrPncGO926bN0jr1rzDxBQU=", @@ -741,82 +765,82 @@ "revisionTime": "2016-11-15T22:01:06Z" }, { - "checksumSHA1": "xyB2Py2ViSKX8Td+oe2hxG6f0Ak=", + "checksumSHA1": "+qB6gzApLVQj5Am4zO/a5VP+dCc=", "path": "google.golang.org/grpc", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "d0iunsiWfA0qXxLMNkTC4tGJnOo=", "path": "google.golang.org/grpc/health", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "pSFXzfvPlaDBK2RsMcTiIeks4ok=", "path": "google.golang.org/grpc/health/grpc_health_v1", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "XXpD8+S3gLrfmCLOf+RbxblOQkU=", "path": "google.golang.org/grpc/metadata", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { - "checksumSHA1": "FCgy+WB249Vt1XEG5pe4Z7plTLs=", + "checksumSHA1": "4zzPK1BUgnOcugiN2vnkhUal4ls=", "path": "google.golang.org/grpc/stats", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "N0TftT6/CyWqp6VRi2DqDx60+Fo=", "path": "google.golang.org/grpc/tap", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { - "checksumSHA1": "uVGwQAu6ncFK7qd3BpzbMEJDUP4=", + "checksumSHA1": "x+eyD2YGMYn973r3dQwGOMdi4mA=", "path": "google.golang.org/grpc/transport", - "revision": "5e3de3f21767d4e0e8dd0ee8383bbbfe172d35de", - "revisionTime": "2016-11-28T22:26:00Z" + "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", + "revisionTime": "2016-12-08T01:33:01Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", @@ -825,40 +849,40 @@ "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "ios0gBgesygjqNgXiQq7+sfKW1A=", + "checksumSHA1": "JtXTQXRlxRB///NYmPDuMpEpvNI=", "path": "gopkg.in/redis.v5", - "revision": "c9856861674f102a5f51104c36401a3cf691739c", - "revisionTime": "2016-11-19T12:14:48Z" + "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", + "revisionTime": "2016-12-03T15:45:52Z" }, { "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", "path": "gopkg.in/redis.v5/internal", - "revision": "c9856861674f102a5f51104c36401a3cf691739c", - "revisionTime": "2016-11-19T12:14:48Z" + "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", + "revisionTime": "2016-12-03T15:45:52Z" }, { "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", "path": "gopkg.in/redis.v5/internal/consistenthash", - "revision": "c9856861674f102a5f51104c36401a3cf691739c", - "revisionTime": "2016-11-19T12:14:48Z" + "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", + "revisionTime": "2016-12-03T15:45:52Z" }, { "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", "path": "gopkg.in/redis.v5/internal/hashtag", - "revision": "c9856861674f102a5f51104c36401a3cf691739c", - "revisionTime": "2016-11-19T12:14:48Z" + "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", + "revisionTime": "2016-12-03T15:45:52Z" }, { - "checksumSHA1": "MuVVHw/uzk6gwBsQ8deMYacmgTM=", + "checksumSHA1": "VnsHRPAMRMuhz7/n/85MZwMrchQ=", "path": "gopkg.in/redis.v5/internal/pool", - "revision": "c9856861674f102a5f51104c36401a3cf691739c", - "revisionTime": "2016-11-19T12:14:48Z" + "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", + "revisionTime": "2016-12-03T15:45:52Z" }, { - "checksumSHA1": "YyFo2hNsHxZGcTMe/vKsjXHOiOQ=", + "checksumSHA1": "604uyPTNWLBNAnAyNRMiwYHXknA=", "path": "gopkg.in/redis.v5/internal/proto", - "revision": "c9856861674f102a5f51104c36401a3cf691739c", - "revisionTime": "2016-11-19T12:14:48Z" + "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", + "revisionTime": "2016-12-03T15:45:52Z" }, { "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", From 9069c2d2e2adcee509ae9c52366a41b14d4f313e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 13:27:09 +0100 Subject: [PATCH 2241/2266] Move gRPC reconnect logic to Dialer --- api/dial.go | 89 ++++++++++++++++++++++++++--------------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/api/dial.go b/api/dial.go index 02e4b0ebf..d68b8cc98 100644 --- a/api/dial.go +++ b/api/dial.go @@ -27,7 +27,7 @@ var Timeout = 2 * time.Second // DialOptions to use in TTN gRPC var DialOptions = []grpc.DialOption{ - WithKeepAliveDialer(), + WithTTNDialer(), grpc.WithBlock(), grpc.FailOnNonTempDialError(true), grpc.WithTimeout(Timeout), @@ -35,8 +35,6 @@ var DialOptions = []grpc.DialOption{ func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.ClientConn, err error) { ctx := GetLogger().WithField("Address", address) - retries := 0 - retriesLeft := MaxRetries opts := DialOptions if tlsConfig != nil { tlsConfig.ServerName = strings.SplitN(address, ":", 2)[0] // trim the port @@ -44,48 +42,34 @@ func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.Clie } else { opts = append(opts, grpc.WithInsecure()) } - for retriesLeft > 0 { - conn, err = grpc.Dial( - address, - opts..., - ) - if err == nil { - ctx.Debug("Connected") - return - } + conn, err = grpc.Dial( + address, + opts..., + ) + if err == nil { + return + } - switch err := err.(type) { - case *net.OpError: - // Dial problem - if err.Op == "dial" { - ctx.WithError(err).Debug("Could not connect, reconnecting...") - } - case x509.CertificateInvalidError, - x509.ConstraintViolationError, - x509.HostnameError, - x509.InsecureAlgorithmError, - x509.SystemRootsError, - x509.UnhandledCriticalExtension, - x509.UnknownAuthorityError: - // Non-temporary error while connecting to a TLS-enabled server - return nil, err - case tls.RecordHeaderError: - if fallback { - ctx.WithError(err).Warn("Could not connect with TLS, reconnecting without it...") - return dial(address, nil, fallback) - } - return nil, err - default: - GetLogger().WithField("ErrType", fmt.Sprintf("%T", err)).WithError(err).Error("Unhandled dial error [please create issue on Github]") - return nil, err + switch err := err.(type) { + case x509.CertificateInvalidError, + x509.ConstraintViolationError, + x509.HostnameError, + x509.InsecureAlgorithmError, + x509.SystemRootsError, + x509.UnhandledCriticalExtension, + x509.UnknownAuthorityError: + // Non-temporary error while connecting to a TLS-enabled server + return nil, err + case tls.RecordHeaderError: + if fallback { + ctx.WithError(err).Warn("Could not connect to gRPC server with TLS, reconnecting without it...") + return dial(address, nil, fallback) } - - // Backoff - time.Sleep(backoff.Backoff(retries)) - retries++ - retriesLeft-- + return nil, err } - return + + GetLogger().WithField("ErrType", fmt.Sprintf("%T", err)).WithError(err).Error("Unhandled dial error [please create issue on Github]") + return nil, err } // RootCAs to use in API connections @@ -112,10 +96,25 @@ func DialWithCert(address string, cert string) (*grpc.ClientConn, error) { return dial(address, tlsConfig, false) } -// WithKeepAliveDialer creates a dialer with the configured KeepAlive time -func WithKeepAliveDialer() grpc.DialOption { +// WithTTNDialer creates a dialer for TTN +func WithTTNDialer() grpc.DialOption { return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { + ctx := GetLogger().WithField("Address", addr) d := net.Dialer{Timeout: timeout, KeepAlive: KeepAlive} - return d.Dial("tcp", addr) + var retries int + for { + conn, err := d.Dial("tcp", addr) + if err == nil { + ctx.Debug("Connected to gRPC server") + return conn, nil + } + if err, ok := err.(*net.OpError); ok && err.Op == "dial" && retries <= MaxRetries { + ctx.WithError(err).Debug("Could not connect to gRPC server, reconnecting...") + time.Sleep(backoff.Backoff(retries)) + retries++ + continue + } + return nil, err + } }) } From 4ce1002bc617aa10f7ca5970abf43619191eca8b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 14:11:17 +0100 Subject: [PATCH 2242/2266] Fix shadowing of Auth Server ID and URL --- core/component/auth.go | 1 + 1 file changed, 1 insertion(+) diff --git a/core/component/auth.go b/core/component/auth.go index 49ea378a4..ee36eb21c 100644 --- a/core/component/auth.go +++ b/core/component/auth.go @@ -68,6 +68,7 @@ func (c *Component) initAuthServers() error { funcMap := make(map[string]tokenkey.TokenFunc) var httpProvider tokenkey.Provider for id, url := range c.Config.AuthServers { + id, url := id, url // deliberately shadow these if strings.HasPrefix(url, "file://") { file := strings.TrimPrefix(url, "file://") contents, err := ioutil.ReadFile(path.Clean(file)) From 77cf02a07a98fe72d5aba245d7ab04fbeea6944f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 14:12:43 +0100 Subject: [PATCH 2243/2266] Add ttn discovery authorize cmd --- cmd/discovery_authorize.go | 68 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 cmd/discovery_authorize.go diff --git a/cmd/discovery_authorize.go b/cmd/discovery_authorize.go new file mode 100644 index 000000000..60ef88983 --- /dev/null +++ b/cmd/discovery_authorize.go @@ -0,0 +1,68 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package cmd + +import ( + "fmt" + "time" + + "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/ttn/utils/security" + jwt "github.com/dgrijalva/jwt-go" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +var discoveryAuthorizeCmd = &cobra.Command{ + Hidden: true, + Use: "authorize [router/broker/handler] [id]", + Short: "Generate a token that components should use to announce themselves", + Long: `ttn discovery authorize generates a token that components should use to announce themselves`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) != 2 { + cmd.UsageFunc()(cmd) + } + + privKey, err := security.LoadKeypair(viper.GetString("key-dir")) + if err != nil { + ctx.WithError(err).Fatal("Could not load security keys") + } + + ttl, err := cmd.Flags().GetInt("valid") + if err != nil { + ctx.WithError(err).Fatal("Could not read TTL") + } + + issuer, err := cmd.Flags().GetString("issuer") + if err != nil { + ctx.WithError(err).Fatal("Could not read issuer ID") + } + + var claims claims.ComponentClaims + claims.Subject = args[1] + claims.Type = args[0] + claims.Issuer = issuer + claims.IssuedAt = time.Now().Unix() + claims.NotBefore = time.Now().Unix() + if ttl > 0 { + claims.ExpiresAt = time.Now().Add(time.Duration(ttl) * time.Hour * 24).Unix() + } + tokenBuilder := jwt.NewWithClaims(jwt.SigningMethodES256, claims) + token, err := tokenBuilder.SignedString(privKey) + if err != nil { + ctx.WithError(err).Fatal("Could not sign JWT") + } + + ctx.WithField("ID", args[0]).Info("Generated token") + fmt.Println() + fmt.Println(token) + fmt.Println() + }, +} + +func init() { + discoveryCmd.AddCommand(discoveryAuthorizeCmd) + discoveryAuthorizeCmd.Flags().Int("valid", 0, "The number of days the token is valid") + discoveryAuthorizeCmd.Flags().String("issuer", "local", "The issuer ID to use") +} From 50438b6ccec33c1fd3c0d7a77685deab7d51af31 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 14:16:31 +0100 Subject: [PATCH 2244/2266] Clean up issue template --- .github/ISSUE_TEMPLATE.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 9d307762a..380c39283 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,10 +1,3 @@ - - This is a **{bug report/feature request/question/...}** for **{the Backend/ttnctl/the Console/the NOC/an integration}** (**{v1-staging/v2-preview}**). - Explain what you want to do From de18313f2f8a3941796700869a88f414c720a210 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 15:37:28 +0100 Subject: [PATCH 2245/2266] Remove gRPC timeout from api.Dial --- api/dial.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/api/dial.go b/api/dial.go index d68b8cc98..a2756a4e0 100644 --- a/api/dial.go +++ b/api/dial.go @@ -22,15 +22,11 @@ var KeepAlive = 10 * time.Second // MaxRetries indicates how often clients should retry dialing a component var MaxRetries = 100 -// Timeout for connections -var Timeout = 2 * time.Second - // DialOptions to use in TTN gRPC var DialOptions = []grpc.DialOption{ WithTTNDialer(), grpc.WithBlock(), grpc.FailOnNonTempDialError(true), - grpc.WithTimeout(Timeout), } func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.ClientConn, err error) { From c78cf522459e125987b32563e5c143ff32c0a0a8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 18:49:27 +0100 Subject: [PATCH 2246/2266] Load ca.cert in ttnctl and ttn broker register-prefix --- cmd/broker_register_prefix.go | 9 +++++++++ ttnctl/util/discovery.go | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index 183a740db..3d9f2a003 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -4,6 +4,9 @@ package cmd import ( + "io/ioutil" + "path/filepath" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/TheThingsNetwork/ttn/core/types" @@ -23,6 +26,12 @@ var brokerRegisterPrefixCmd = &cobra.Command{ cmd.UsageFunc()(cmd) } + path := filepath.Clean(viper.GetString("key-dir") + "/ca.cert") + cert, err := ioutil.ReadFile(path) + if err == nil && !api.RootCAs.AppendCertsFromPEM(cert) { + ctx.Warnf("Could not add root certificates from %s", path) + } + conn, err := api.Dial(viper.GetString("discovery-address")) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") diff --git a/ttnctl/util/discovery.go b/ttnctl/util/discovery.go index 02945b945..9caf83f3f 100644 --- a/ttnctl/util/discovery.go +++ b/ttnctl/util/discovery.go @@ -4,6 +4,9 @@ package util import ( + "io/ioutil" + "path" + "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/discovery" "github.com/apex/log" @@ -13,6 +16,12 @@ import ( // GetDiscovery gets the Discovery client for ttnctl func GetDiscovery(ctx log.Interface) (*grpc.ClientConn, discovery.DiscoveryClient) { + path := path.Join(GetDataDir(), "/ca.cert") + cert, err := ioutil.ReadFile(path) + if err == nil && !api.RootCAs.AppendCertsFromPEM(cert) { + ctx.Warnf("Could not add root certificates from %s", path) + } + conn, err := api.Dial(viper.GetString("discovery-address")) if err != nil { ctx.WithError(err).Fatal("Could not connect to Discovery server") From 2fefd76e543bc06a45750cc890210120a7c75069 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Thu, 8 Dec 2016 18:54:58 +0100 Subject: [PATCH 2247/2266] Allow announcing DevAddr prefixes --- core/discovery/server.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/discovery/server.go b/core/discovery/server.go index 3fa554d31..afaa20371 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -57,10 +57,7 @@ func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.Me return errPermissionDeniedf("Token issuer \"%s\" is not allowed to make changes to the network settings", claims.Issuer) } - // DevAddrPrefixes can not be announced yet - if prefix != nil { - return errPermissionDeniedf("Can not announce DevAddrPrefixes at this time") - } + // TODO: Check if claims allow DevAddrPrefix to be announced // AppEUI can not be announced yet if appEUI != nil { From f9bb9f634406e5b4381db25cae26702f1291f103 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 9 Dec 2016 13:58:58 +0100 Subject: [PATCH 2248/2266] Exit ttn on config errors --- cmd/root.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/root.go b/cmd/root.go index 7dcd680da..975bac7d1 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -173,6 +173,7 @@ func initConfig() { err := viper.ReadInConfig() if err != nil { fmt.Println("Error when reading config file:", err) + os.Exit(1) } else if err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) } From 1df9d67eba144e4f4a0632aeec2a839c6c6220bb Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 9 Dec 2016 14:33:10 +0100 Subject: [PATCH 2249/2266] Fix errPermissionDeniedf formatting --- core/discovery/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/discovery/server.go b/core/discovery/server.go index afaa20371..228cefa4d 100644 --- a/core/discovery/server.go +++ b/core/discovery/server.go @@ -18,8 +18,8 @@ type discoveryServer struct { discovery *discovery } -func errPermissionDeniedf(format string, args ...string) error { - return errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args)) +func errPermissionDeniedf(format string, args ...interface{}) error { + return errors.NewErrPermissionDenied(fmt.Sprintf("Discovery:"+format, args...)) } func (d *discoveryServer) checkMetadataEditRights(ctx context.Context, in *pb.MetadataRequest) error { From d16811d8cdf15104f25d6d21226c0f005c42618b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 9 Dec 2016 14:39:07 +0100 Subject: [PATCH 2250/2266] Return ttn cmd on invalid args --- cmd/broker_register_prefix.go | 1 + cmd/discovery_authorize.go | 1 + cmd/networkserver_authorize.go | 1 + 3 files changed, 3 insertions(+) diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index 3d9f2a003..bae7e7a08 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -24,6 +24,7 @@ var brokerRegisterPrefixCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { if len(args) == 0 { cmd.UsageFunc()(cmd) + return } path := filepath.Clean(viper.GetString("key-dir") + "/ca.cert") diff --git a/cmd/discovery_authorize.go b/cmd/discovery_authorize.go index 60ef88983..c46a42e18 100644 --- a/cmd/discovery_authorize.go +++ b/cmd/discovery_authorize.go @@ -22,6 +22,7 @@ var discoveryAuthorizeCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { if len(args) != 2 { cmd.UsageFunc()(cmd) + return } privKey, err := security.LoadKeypair(viper.GetString("key-dir")) diff --git a/cmd/networkserver_authorize.go b/cmd/networkserver_authorize.go index 31ad4bd35..e6c58bd76 100644 --- a/cmd/networkserver_authorize.go +++ b/cmd/networkserver_authorize.go @@ -21,6 +21,7 @@ var networkserverAuthorizeCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { if len(args) != 1 { cmd.UsageFunc()(cmd) + return } privKey, err := security.LoadKeypair(viper.GetString("key-dir")) From 0f92dd662ae2ff4bfcd0966af25108209f0041c8 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Fri, 9 Dec 2016 14:45:23 +0100 Subject: [PATCH 2251/2266] Don't say OK when it's not OK --- cmd/broker_register_prefix.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/cmd/broker_register_prefix.go b/cmd/broker_register_prefix.go index bae7e7a08..4f186ba96 100644 --- a/cmd/broker_register_prefix.go +++ b/cmd/broker_register_prefix.go @@ -5,6 +5,7 @@ package cmd import ( "io/ioutil" + "os" "path/filepath" "github.com/TheThingsNetwork/ttn/api" @@ -48,11 +49,13 @@ var brokerRegisterPrefixCmd = &cobra.Command{ ) dscContext := metadata.NewContext(context.Background(), md) + success := true for _, prefixString := range args { ctx := ctx.WithField("Prefix", prefixString) prefix, err := types.ParseDevAddrPrefix(prefixString) if err != nil { ctx.WithError(err).Error("Could not register prefix") + success = false continue } _, err = client.AddMetadata(dscContext, &discovery.MetadataRequest{ @@ -64,9 +67,15 @@ var brokerRegisterPrefixCmd = &cobra.Command{ }) if err != nil { ctx.WithError(err).Error("Could not register prefix") + success = false + continue } ctx.Info("Registered prefix") } + + if !success { + os.Exit(1) + } }, } From 59f23b53a14abf998403e6b4898832e96eb05f1b Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 12 Dec 2016 13:17:51 +0100 Subject: [PATCH 2252/2266] Migrate api logging to go-utils/log --- api/broker/client_streams.go | 14 +-- api/broker/communication_test.go | 6 +- api/broker/server_streams.go | 7 +- api/dial.go | 7 +- api/logging.go | 166 ------------------------------- api/router/client_streams.go | 4 +- api/router/communication_test.go | 4 +- api/router/gateway_client.go | 14 +-- api/router/server_streams.go | 7 +- cmd/root.go | 8 +- core/broker/server.go | 4 +- core/router/server.go | 3 +- ttnctl/cmd/root.go | 8 +- vendor/vendor.json | 6 ++ 14 files changed, 57 insertions(+), 201 deletions(-) delete mode 100644 api/logging.go diff --git a/api/broker/client_streams.go b/api/broker/client_streams.go index 82e70b441..d90f20e69 100644 --- a/api/broker/client_streams.go +++ b/api/broker/client_streams.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/golang/protobuf/ptypes/empty" "golang.org/x/net/context" @@ -18,18 +18,18 @@ import ( // Stream interface type Stream interface { - SetLogger(api.Logger) + SetLogger(log.Interface) Close() } type stream struct { closing bool setup sync.WaitGroup - ctx api.Logger + ctx log.Interface client BrokerClient } -func (s *stream) SetLogger(logger api.Logger) { +func (s *stream) SetLogger(logger log.Interface) { s.ctx = logger } @@ -52,7 +52,7 @@ func NewMonitoredRouterStream(client BrokerClient, getContextFunc func() context } s.setup.Add(1) s.client = client - s.ctx = api.GetLogger() + s.ctx = log.Get() go func() { var retries int @@ -190,7 +190,7 @@ func NewMonitoredHandlerPublishStream(client BrokerClient, getContextFunc func() } s.setup.Add(1) s.client = client - s.ctx = api.GetLogger() + s.ctx = log.Get() go func() { var retries int @@ -312,7 +312,7 @@ func NewMonitoredHandlerSubscribeStream(client BrokerClient, getContextFunc func } s.setup.Add(1) s.client = client - s.ctx = api.GetLogger() + s.ctx = log.Get() go func() { var client Broker_SubscribeClient diff --git a/api/broker/communication_test.go b/api/broker/communication_test.go index a2ea741d9..47f2155d7 100644 --- a/api/broker/communication_test.go +++ b/api/broker/communication_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/api" . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" @@ -49,7 +51,7 @@ func TestHandlerBrokerCommunication(t *testing.T) { a := New(t) ctx := GetLogger(t, "TestHandlerBrokerCommunication") - api.SetLogger(api.Apex(ctx)) + log.Set(apex.Wrap(ctx)) brk := newTestBroker() rand.Seed(time.Now().UnixNano()) @@ -143,7 +145,7 @@ func TestRouterBrokerCommunication(t *testing.T) { a := New(t) ctx := GetLogger(t, "TestRouterBrokerCommunication") - api.SetLogger(api.Apex(ctx)) + log.Set(apex.Wrap(ctx)) brk := newTestBroker() rand.Seed(time.Now().UnixNano()) diff --git a/api/broker/server_streams.go b/api/broker/server_streams.go index bbc9eacda..b89056680 100644 --- a/api/broker/server_streams.go +++ b/api/broker/server_streams.go @@ -6,6 +6,7 @@ package broker import ( "io" + "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/utils/errors" "github.com/golang/protobuf/ptypes/empty" @@ -14,7 +15,7 @@ import ( // BrokerStreamServer handles gRPC streams as channels type BrokerStreamServer struct { - ctx api.Logger + ctx log.Interface RouterAssociateChanFunc func(md metadata.MD) (up chan *UplinkMessage, down <-chan *DownlinkMessage, cancel func(), err error) HandlerSubscribeChanFunc func(md metadata.MD) (ch <-chan *DeduplicatedUplinkMessage, cancel func(), err error) HandlerPublishChanFunc func(md metadata.MD) (ch chan *DownlinkMessage, err error) @@ -23,12 +24,12 @@ type BrokerStreamServer struct { // NewBrokerStreamServer returns a new BrokerStreamServer func NewBrokerStreamServer() *BrokerStreamServer { return &BrokerStreamServer{ - ctx: api.GetLogger(), + ctx: log.Get(), } } // SetLogger sets the logger -func (s *BrokerStreamServer) SetLogger(logger api.Logger) { +func (s *BrokerStreamServer) SetLogger(logger log.Interface) { s.ctx = logger } diff --git a/api/dial.go b/api/dial.go index a2756a4e0..5a17133d8 100644 --- a/api/dial.go +++ b/api/dial.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/utils/backoff" "google.golang.org/grpc" "google.golang.org/grpc/credentials" @@ -30,7 +31,7 @@ var DialOptions = []grpc.DialOption{ } func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.ClientConn, err error) { - ctx := GetLogger().WithField("Address", address) + ctx := log.Get().WithField("Address", address) opts := DialOptions if tlsConfig != nil { tlsConfig.ServerName = strings.SplitN(address, ":", 2)[0] // trim the port @@ -64,7 +65,7 @@ func dial(address string, tlsConfig *tls.Config, fallback bool) (conn *grpc.Clie return nil, err } - GetLogger().WithField("ErrType", fmt.Sprintf("%T", err)).WithError(err).Error("Unhandled dial error [please create issue on Github]") + log.Get().WithField("ErrType", fmt.Sprintf("%T", err)).WithError(err).Error("Unhandled dial error [please create issue on Github]") return nil, err } @@ -95,7 +96,7 @@ func DialWithCert(address string, cert string) (*grpc.ClientConn, error) { // WithTTNDialer creates a dialer for TTN func WithTTNDialer() grpc.DialOption { return grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) { - ctx := GetLogger().WithField("Address", addr) + ctx := log.Get().WithField("Address", addr) d := net.Dialer{Timeout: timeout, KeepAlive: KeepAlive} var retries int for { diff --git a/api/logging.go b/api/logging.go deleted file mode 100644 index cc67dcc33..000000000 --- a/api/logging.go +++ /dev/null @@ -1,166 +0,0 @@ -package api - -import ( - "fmt" - - logrus "github.com/Sirupsen/logrus" - apex "github.com/apex/log" - "google.golang.org/grpc/grpclog" -) - -// Logger used in mqtt package -type Logger interface { - Debug(msg string) - Info(msg string) - Warn(msg string) - Error(msg string) - Fatal(msg string) - Debugf(msg string, v ...interface{}) - Infof(msg string, v ...interface{}) - Warnf(msg string, v ...interface{}) - Errorf(msg string, v ...interface{}) - Fatalf(msg string, v ...interface{}) - WithField(string, interface{}) Logger - WithFields(apex.Fielder) Logger - WithError(error) Logger -} - -// GRPC logger -func GRPC() grpclog.Logger { - return &grpcWrapper{GetLogger()} -} - -type grpcWrapper struct { - Logger -} - -func (w *grpcWrapper) Fatal(args ...interface{}) { - w.Logger.Fatal(fmt.Sprint(args...)) -} -func (w *grpcWrapper) Fatalln(args ...interface{}) { - w.Fatal(args...) -} -func (w *grpcWrapper) Print(args ...interface{}) { - w.Logger.Debug(fmt.Sprint(args...)) -} -func (w *grpcWrapper) Printf(format string, args ...interface{}) { - w.Logger.Debugf(format, args...) -} -func (w *grpcWrapper) Println(args ...interface{}) { - w.Print(args...) -} - -// StandardLogrus wraps the standard Logrus Logger into a Logger -func StandardLogrus() Logger { - return Logrus(logrus.StandardLogger()) -} - -// Logrus wraps logrus into a Logger -func Logrus(logger *logrus.Logger) Logger { - return &logrusEntryWrapper{logrus.NewEntry(logger)} -} - -type logrusEntryWrapper struct { - *logrus.Entry -} - -func (w *logrusEntryWrapper) Debug(msg string) { - w.Entry.Debug(msg) -} - -func (w *logrusEntryWrapper) Info(msg string) { - w.Entry.Info(msg) -} - -func (w *logrusEntryWrapper) Warn(msg string) { - w.Entry.Warn(msg) -} - -func (w *logrusEntryWrapper) Error(msg string) { - w.Entry.Error(msg) -} - -func (w *logrusEntryWrapper) Fatal(msg string) { - w.Entry.Fatal(msg) -} - -func (w *logrusEntryWrapper) WithError(err error) Logger { - return &logrusEntryWrapper{w.Entry.WithError(err)} -} - -func (w *logrusEntryWrapper) WithField(k string, v interface{}) Logger { - return &logrusEntryWrapper{w.Entry.WithField(k, v)} -} - -func (w *logrusEntryWrapper) WithFields(fields apex.Fielder) Logger { - return &logrusEntryWrapper{w.Entry.WithFields( - map[string]interface{}(fields.Fields()), - )} -} - -// Apex wraps apex/log -func Apex(ctx apex.Interface) Logger { - return &apexInterfaceWrapper{ctx} -} - -type apexInterfaceWrapper struct { - apex.Interface -} - -func (w *apexInterfaceWrapper) WithField(k string, v interface{}) Logger { - return &apexEntryWrapper{w.Interface.WithField(k, v)} -} - -func (w *apexInterfaceWrapper) WithFields(fields apex.Fielder) Logger { - return &apexEntryWrapper{w.Interface.WithFields(fields)} -} - -func (w *apexInterfaceWrapper) WithError(err error) Logger { - return &apexEntryWrapper{w.Interface.WithError(err)} -} - -type apexEntryWrapper struct { - *apex.Entry -} - -func (w *apexEntryWrapper) WithField(k string, v interface{}) Logger { - return &apexEntryWrapper{w.Entry.WithField(k, v)} -} - -func (w *apexEntryWrapper) WithFields(fields apex.Fielder) Logger { - return &apexEntryWrapper{w.Entry.WithFields(fields)} -} - -func (w *apexEntryWrapper) WithError(err error) Logger { - return &apexEntryWrapper{w.Entry.WithError(err)} -} - -var logger Logger = noopLogger{} - -// GetLogger returns the API Logger -func GetLogger() Logger { - return logger -} - -// SetLogger sets the API and gRPC Logger -func SetLogger(log Logger) { - logger = log - grpclog.SetLogger(GRPC()) -} - -// noopLogger just does nothing -type noopLogger struct{} - -func (l noopLogger) Debug(msg string) {} -func (l noopLogger) Info(msg string) {} -func (l noopLogger) Warn(msg string) {} -func (l noopLogger) Error(msg string) {} -func (l noopLogger) Fatal(msg string) {} -func (l noopLogger) Debugf(msg string, v ...interface{}) {} -func (l noopLogger) Infof(msg string, v ...interface{}) {} -func (l noopLogger) Warnf(msg string, v ...interface{}) {} -func (l noopLogger) Errorf(msg string, v ...interface{}) {} -func (l noopLogger) Fatalf(msg string, v ...interface{}) {} -func (l noopLogger) WithField(string, interface{}) Logger { return l } -func (l noopLogger) WithFields(apex.Fielder) Logger { return l } -func (l noopLogger) WithError(error) Logger { return l } diff --git a/api/router/client_streams.go b/api/router/client_streams.go index e7aaec12b..2929eefc1 100644 --- a/api/router/client_streams.go +++ b/api/router/client_streams.go @@ -8,7 +8,7 @@ import ( "sync" "time" - "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/utils/backoff" "github.com/golang/protobuf/ptypes/empty" @@ -25,7 +25,7 @@ type GatewayStream interface { type gatewayStream struct { closing bool setup sync.WaitGroup - ctx api.Logger + ctx log.Interface client RouterClientForGateway } diff --git a/api/router/communication_test.go b/api/router/communication_test.go index 373aabecd..48471cb62 100644 --- a/api/router/communication_test.go +++ b/api/router/communication_test.go @@ -10,6 +10,8 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/protocol" @@ -50,7 +52,7 @@ func TestRouterCommunication(t *testing.T) { a := New(t) ctx := GetLogger(t, "TestRouterCommunication") - api.SetLogger(api.Apex(ctx)) + log.Set(apex.Wrap(ctx)) rtr := newTestRouter() rand.Seed(time.Now().UnixNano()) diff --git a/api/router/gateway_client.go b/api/router/gateway_client.go index 727111911..f2088b7a4 100644 --- a/api/router/gateway_client.go +++ b/api/router/gateway_client.go @@ -6,7 +6,7 @@ package router import ( "sync" - "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/go-utils/log" "golang.org/x/net/context" "google.golang.org/grpc/metadata" ) @@ -15,8 +15,8 @@ import ( type RouterClientForGateway interface { Close() - GetLogger() api.Logger - SetLogger(api.Logger) + GetLogger() log.Interface + SetLogger(log.Interface) SetToken(token string) @@ -30,7 +30,7 @@ type RouterClientForGateway interface { func NewRouterClientForGateway(client RouterClient, gatewayID, token string) RouterClientForGateway { ctx, cancel := context.WithCancel(context.Background()) return &routerClientForGateway{ - ctx: api.GetLogger().WithField("GatewayID", gatewayID), + ctx: log.Get().WithField("GatewayID", gatewayID), client: client, gatewayID: gatewayID, token: token, @@ -40,7 +40,7 @@ func NewRouterClientForGateway(client RouterClient, gatewayID, token string) Rou } type routerClientForGateway struct { - ctx api.Logger + ctx log.Interface client RouterClient gatewayID string token string @@ -53,11 +53,11 @@ func (c *routerClientForGateway) Close() { c.cancel() } -func (c *routerClientForGateway) GetLogger() api.Logger { +func (c *routerClientForGateway) GetLogger() log.Interface { return c.ctx } -func (c *routerClientForGateway) SetLogger(logger api.Logger) { +func (c *routerClientForGateway) SetLogger(logger log.Interface) { c.ctx = logger } diff --git a/api/router/server_streams.go b/api/router/server_streams.go index f4571301c..e48ff0e1f 100644 --- a/api/router/server_streams.go +++ b/api/router/server_streams.go @@ -6,6 +6,7 @@ package router import ( "io" + "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/api" "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -15,7 +16,7 @@ import ( // RouterStreamServer handles gRPC streams as channels type RouterStreamServer struct { - ctx api.Logger + ctx log.Interface UplinkChanFunc func(md metadata.MD) (ch chan *UplinkMessage, err error) GatewayStatusChanFunc func(md metadata.MD) (ch chan *gateway.Status, err error) DownlinkChanFunc func(md metadata.MD) (ch <-chan *DownlinkMessage, cancel func(), err error) @@ -24,12 +25,12 @@ type RouterStreamServer struct { // NewRouterStreamServer returns a new RouterStreamServer func NewRouterStreamServer() *RouterStreamServer { return &RouterStreamServer{ - ctx: api.GetLogger(), + ctx: log.Get(), } } // SetLogger sets the logger -func (s *RouterStreamServer) SetLogger(logger api.Logger) { +func (s *RouterStreamServer) SetLogger(logger log.Interface) { s.ctx = logger } diff --git a/cmd/root.go b/cmd/root.go index 975bac7d1..7dbeba0b4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,7 +14,9 @@ import ( "time" cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" - "github.com/TheThingsNetwork/ttn/api" + ttnlog "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/go-utils/log/grpc" esHandler "github.com/TheThingsNetwork/ttn/utils/elasticsearch/handler" "github.com/apex/log" jsonHandler "github.com/apex/log/handlers/json" @@ -24,6 +26,7 @@ import ( "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/tj/go-elastic" + "google.golang.org/grpc/grpclog" "gopkg.in/redis.v5" ) @@ -81,7 +84,8 @@ var RootCmd = &cobra.Command{ } // Set the API/gRPC logger - api.SetLogger(api.Apex(ctx)) + ttnlog.Set(apex.Wrap(ctx)) + grpclog.SetLogger(grpc.Wrap(ttnlog.Get())) ctx.WithFields(log.Fields{ "ComponentID": viper.GetString("id"), diff --git a/core/broker/server.go b/core/broker/server.go index 8c0d35859..b83d7d1ec 100644 --- a/core/broker/server.go +++ b/core/broker/server.go @@ -6,7 +6,7 @@ package broker import ( "time" - "github.com/TheThingsNetwork/ttn/api" + "github.com/TheThingsNetwork/go-utils/log/apex" pb "github.com/TheThingsNetwork/ttn/api/broker" "github.com/TheThingsNetwork/ttn/api/ratelimit" "github.com/TheThingsNetwork/ttn/utils/errors" @@ -125,7 +125,7 @@ func (b *brokerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques func (b *broker) RegisterRPC(s *grpc.Server) { server := &brokerRPC{broker: b} - server.SetLogger(api.Apex(b.Ctx)) + server.SetLogger(apex.Wrap(b.Ctx)) server.RouterAssociateChanFunc = server.associateRouter server.HandlerPublishChanFunc = server.getHandlerPublish server.HandlerSubscribeChanFunc = server.getHandlerSubscribe diff --git a/core/router/server.go b/core/router/server.go index 479c83438..8a3ff3f0d 100644 --- a/core/router/server.go +++ b/core/router/server.go @@ -8,6 +8,7 @@ import ( "time" "github.com/TheThingsNetwork/go-account-lib/claims" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/api" pb_gateway "github.com/TheThingsNetwork/ttn/api/gateway" "github.com/TheThingsNetwork/ttn/api/ratelimit" @@ -140,7 +141,7 @@ func (r *routerRPC) Activate(ctx context.Context, req *pb.DeviceActivationReques // RegisterRPC registers this router as a RouterServer (github.com/TheThingsNetwork/ttn/api/router) func (r *router) RegisterRPC(s *grpc.Server) { server := &routerRPC{router: r} - server.SetLogger(api.Apex(r.Ctx)) + server.SetLogger(apex.Wrap(r.Ctx)) server.UplinkChanFunc = server.getUplink server.DownlinkChanFunc = server.getDownlink server.GatewayStatusChanFunc = server.getGatewayStatus diff --git a/ttnctl/cmd/root.go b/ttnctl/cmd/root.go index b75f1960f..3eb70219e 100644 --- a/ttnctl/cmd/root.go +++ b/ttnctl/cmd/root.go @@ -9,11 +9,14 @@ import ( "strings" cliHandler "github.com/TheThingsNetwork/go-utils/handlers/cli" - "github.com/TheThingsNetwork/ttn/api" + ttnlog "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + "github.com/TheThingsNetwork/go-utils/log/grpc" "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/apex/log" "github.com/spf13/cobra" "github.com/spf13/viper" + "google.golang.org/grpc/grpclog" ) var cfgFile string @@ -41,7 +44,8 @@ var RootCmd = &cobra.Command{ util.PrintConfig(ctx, true) } - api.SetLogger(api.Apex(ctx)) + ttnlog.Set(apex.Wrap(ctx)) + grpclog.SetLogger(grpc.Wrap(ttnlog.Get())) }, } diff --git a/vendor/vendor.json b/vendor/vendor.json index 5cc2783a9..fcaa41dd2 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -98,6 +98,12 @@ "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", "revisionTime": "2016-12-02T10:08:08Z" }, + { + "checksumSHA1": "sQ0vy3MCGY1WgK9xldn1V6pMeZk=", + "path": "github.com/TheThingsNetwork/go-utils/log/grpc", + "revision": "5149dd739482dd5efd18c0b5ce99baecba96668e", + "revisionTime": "2016-12-02T10:08:08Z" + }, { "checksumSHA1": "Ur88QI//9Ue82g83qvBSakGlzVg=", "path": "github.com/apex/log", From 59fd9fa6f00974ee71050f08c7518e0ab478222f Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 12 Dec 2016 13:56:58 +0100 Subject: [PATCH 2253/2266] Migrate mqtt logging to go-utils/log --- core/handler/mqtt.go | 3 ++- core/handler/mqtt_test.go | 3 ++- mqtt/activations_test.go | 12 ++++++------ mqtt/client.go | 7 ++++--- mqtt/client_test.go | 16 ++++++++-------- mqtt/downlink_test.go | 12 ++++++------ mqtt/events_test.go | 4 ++-- mqtt/logging.go | 29 ----------------------------- mqtt/uplink_test.go | 14 +++++++------- mqtt/utils_test.go | 16 ++++++++++++++++ ttnctl/util/mqtt.go | 3 ++- 11 files changed, 55 insertions(+), 64 deletions(-) delete mode 100644 mqtt/logging.go create mode 100644 mqtt/utils_test.go diff --git a/core/handler/mqtt.go b/core/handler/mqtt.go index 94c885dfd..d5281f1a3 100644 --- a/core/handler/mqtt.go +++ b/core/handler/mqtt.go @@ -6,6 +6,7 @@ package handler import ( "time" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" @@ -18,7 +19,7 @@ var MQTTTimeout = 2 * time.Second var MQTTBufferSize = 10 func (h *handler) HandleMQTT(username, password string, mqttBrokers ...string) error { - h.mqttClient = mqtt.NewClient(h.Ctx, "ttnhdl", username, password, mqttBrokers...) + h.mqttClient = mqtt.NewClient(apex.Wrap(h.Ctx), "ttnhdl", username, password, mqttBrokers...) err := h.mqttClient.Connect() if err != nil { diff --git a/core/handler/mqtt_test.go b/core/handler/mqtt_test.go index e9f7a7710..d8f31ea54 100644 --- a/core/handler/mqtt_test.go +++ b/core/handler/mqtt_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/device" "github.com/TheThingsNetwork/ttn/core/types" @@ -25,7 +26,7 @@ func TestHandleMQTT(t *testing.T) { a := New(t) var wg WaitGroup - c := mqtt.NewClient(GetLogger(t, "TestHandleMQTT"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := mqtt.NewClient(apex.Wrap(GetLogger(t, "TestHandleMQTT")), "test", "", "", fmt.Sprintf("tcp://%s", host)) err := c.Connect() a.So(err, ShouldBeNil) appID := "handler-mqtt-app1" diff --git a/mqtt/activations_test.go b/mqtt/activations_test.go index 1108533d8..cf9edc2a0 100644 --- a/mqtt/activations_test.go +++ b/mqtt/activations_test.go @@ -17,7 +17,7 @@ import ( func TestPublishActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -35,7 +35,7 @@ func TestPublishActivations(t *testing.T) { func TestSubscribeDeviceActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -52,7 +52,7 @@ func TestSubscribeDeviceActivations(t *testing.T) { func TestSubscribeAppActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -69,7 +69,7 @@ func TestSubscribeAppActivations(t *testing.T) { func TestSubscribeActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -86,7 +86,7 @@ func TestSubscribeActivations(t *testing.T) { func TestPubSubActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -117,7 +117,7 @@ func TestPubSubActivations(t *testing.T) { func TestPubSubAppActivations(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() diff --git a/mqtt/client.go b/mqtt/client.go index 9507a87bc..ef4b60c46 100644 --- a/mqtt/client.go +++ b/mqtt/client.go @@ -8,6 +8,7 @@ import ( "sync" "time" + "github.com/TheThingsNetwork/go-utils/log" "github.com/TheThingsNetwork/ttn/core/types" "github.com/TheThingsNetwork/ttn/utils/random" MQTT "github.com/eclipse/paho.mqtt.golang" @@ -144,14 +145,14 @@ func (t *token) Error() error { // DefaultClient is the default MQTT client for The Things Network type DefaultClient struct { mqtt MQTT.Client - ctx Logger + ctx log.Interface subscriptions map[string]MQTT.MessageHandler } // NewClient creates a new DefaultClient -func NewClient(ctx Logger, id, username, password string, brokers ...string) Client { +func NewClient(ctx log.Interface, id, username, password string, brokers ...string) Client { if ctx == nil { - ctx = &noopLogger{} + ctx = log.Get() } mqttOpts := MQTT.NewClientOptions() diff --git a/mqtt/client_test.go b/mqtt/client_test.go index 517295875..9e9861a32 100644 --- a/mqtt/client_test.go +++ b/mqtt/client_test.go @@ -10,8 +10,8 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" "github.com/apex/log" . "github.com/smartystreets/assertions" ) @@ -70,13 +70,13 @@ func TestSimpleToken(t *testing.T) { func TestNewClient(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) a.So(c.(*DefaultClient).mqtt, ShouldNotBeNil) } func TestConnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) err := c.Connect() defer c.Disconnect() a.So(err, ShouldBeNil) @@ -91,7 +91,7 @@ func TestConnectInvalidAddress(t *testing.T) { a := New(t) ConnectRetries = 2 ConnectRetryDelay = 50 * time.Millisecond - c := NewClient(GetLogger(t, "Test"), "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 + c := NewClient(getLogger(t, "Test"), "test", "", "", "tcp://localhost:18830") // No MQTT on 18830 err := c.Connect() defer c.Disconnect() a.So(err, ShouldNotBeNil) @@ -103,7 +103,7 @@ func TestConnectInvalidCredentials(t *testing.T) { func TestIsConnected(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) a.So(c.IsConnected(), ShouldBeFalse) @@ -115,7 +115,7 @@ func TestIsConnected(t *testing.T) { func TestDisconnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) // Disconnecting when not connected should not change anything c.Disconnect() @@ -130,7 +130,7 @@ func TestDisconnect(t *testing.T) { func TestRandomTopicPublish(t *testing.T) { a := New(t) - ctx := GetLogger(t, "TestRandomTopicPublish") + ctx := getLogger(t, "TestRandomTopicPublish") c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() @@ -147,7 +147,7 @@ func TestRandomTopicPublish(t *testing.T) { } func ExampleNewClient() { - ctx := log.WithField("Example", "NewClient") + ctx := apex.Wrap(log.WithField("Example", "NewClient")) exampleClient := NewClient(ctx, "ttnctl", "my-app-id", "my-access-key", "eu.thethings.network:1883") err := exampleClient.Connect() if err != nil { diff --git a/mqtt/downlink_test.go b/mqtt/downlink_test.go index f873eaefa..e51cf5f28 100644 --- a/mqtt/downlink_test.go +++ b/mqtt/downlink_test.go @@ -17,7 +17,7 @@ import ( func TestPublishDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -35,7 +35,7 @@ func TestPublishDownlink(t *testing.T) { func TestSubscribeDeviceDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -52,7 +52,7 @@ func TestSubscribeDeviceDownlink(t *testing.T) { func TestSubscribeAppDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -69,7 +69,7 @@ func TestSubscribeAppDownlink(t *testing.T) { func TestSubscribeDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -86,7 +86,7 @@ func TestSubscribeDownlink(t *testing.T) { func TestPubSubDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -117,7 +117,7 @@ func TestPubSubDownlink(t *testing.T) { func TestPubSubAppDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() diff --git a/mqtt/events_test.go b/mqtt/events_test.go index e230ec043..9bfd46c11 100644 --- a/mqtt/events_test.go +++ b/mqtt/events_test.go @@ -15,7 +15,7 @@ import ( func TestPublishSubscribeAppEvents(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() var wg WaitGroup @@ -36,7 +36,7 @@ func TestPublishSubscribeAppEvents(t *testing.T) { func TestPublishSubscribeDeviceEvents(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() var wg WaitGroup diff --git a/mqtt/logging.go b/mqtt/logging.go deleted file mode 100644 index f53056a12..000000000 --- a/mqtt/logging.go +++ /dev/null @@ -1,29 +0,0 @@ -package mqtt - -// Logger used in mqtt package -type Logger interface { - Debug(msg string) - Info(msg string) - Warn(msg string) - Error(msg string) - Fatal(msg string) - Debugf(msg string, v ...interface{}) - Infof(msg string, v ...interface{}) - Warnf(msg string, v ...interface{}) - Errorf(msg string, v ...interface{}) - Fatalf(msg string, v ...interface{}) -} - -// noopLogger just does nothing -type noopLogger struct{} - -func (l noopLogger) Debug(msg string) {} -func (l noopLogger) Info(msg string) {} -func (l noopLogger) Warn(msg string) {} -func (l noopLogger) Error(msg string) {} -func (l noopLogger) Fatal(msg string) {} -func (l noopLogger) Debugf(msg string, v ...interface{}) {} -func (l noopLogger) Infof(msg string, v ...interface{}) {} -func (l noopLogger) Warnf(msg string, v ...interface{}) {} -func (l noopLogger) Errorf(msg string, v ...interface{}) {} -func (l noopLogger) Fatalf(msg string, v ...interface{}) {} diff --git a/mqtt/uplink_test.go b/mqtt/uplink_test.go index 252bd1a44..285e8c042 100644 --- a/mqtt/uplink_test.go +++ b/mqtt/uplink_test.go @@ -19,7 +19,7 @@ import ( func TestPublishUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -35,7 +35,7 @@ func TestPublishUplink(t *testing.T) { func TestPublishUplinkFields(t *testing.T) { a := New(t) - ctx := GetLogger(t, "Test") + ctx := getLogger(t, "Test") c := NewClient(ctx, "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() @@ -107,7 +107,7 @@ func TestPublishUplinkFields(t *testing.T) { func TestSubscribeDeviceUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -122,7 +122,7 @@ func TestSubscribeDeviceUplink(t *testing.T) { func TestSubscribeAppUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -137,7 +137,7 @@ func TestSubscribeAppUplink(t *testing.T) { func TestSubscribeUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -152,7 +152,7 @@ func TestSubscribeUplink(t *testing.T) { func TestPubSubUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() @@ -185,7 +185,7 @@ func TestPubSubUplink(t *testing.T) { func TestPubSubAppUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) + c := NewClient(getLogger(t, "Test"), "test", "", "", fmt.Sprintf("tcp://%s", host)) c.Connect() defer c.Disconnect() diff --git a/mqtt/utils_test.go b/mqtt/utils_test.go new file mode 100644 index 000000000..de65f0f70 --- /dev/null +++ b/mqtt/utils_test.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package mqtt + +import ( + "testing" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + tt "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func getLogger(t *testing.T, tag string) log.Interface { + return apex.Wrap(tt.GetLogger(t, tag)) +} diff --git a/ttnctl/util/mqtt.go b/ttnctl/util/mqtt.go index 340dfac11..17555ce3e 100644 --- a/ttnctl/util/mqtt.go +++ b/ttnctl/util/mqtt.go @@ -8,6 +8,7 @@ import ( "fmt" "strings" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/mqtt" "github.com/apex/log" "github.com/gosuri/uitable" @@ -27,7 +28,7 @@ func GetMQTT(ctx log.Interface) mqtt.Client { ctx.Fatal("TLS connections are not yet supported by ttnctl") } broker := fmt.Sprintf("%s://%s", mqttProto, viper.GetString("mqtt-address")) - client := mqtt.NewClient(ctx, "ttnctl", username, password, broker) + client := mqtt.NewClient(apex.Wrap(ctx), "ttnctl", username, password, broker) ctx.WithFields(log.Fields{ "MQTT Broker": broker, From f274852e03344b89e643115e72d864f7779591af Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 12 Dec 2016 14:00:33 +0100 Subject: [PATCH 2254/2266] Migrate amqp logging to go-utils/log --- amqp/client.go | 9 +++++---- amqp/client_test.go | 13 ++++++------- amqp/downlink_test.go | 5 ++--- amqp/logging.go | 32 -------------------------------- amqp/publisher_test.go | 3 +-- amqp/subscriber_test.go | 3 +-- amqp/uplink_test.go | 5 ++--- amqp/utils_test.go | 16 ++++++++++++++++ core/handler/amqp.go | 3 ++- core/handler/amqp_test.go | 3 ++- 10 files changed, 37 insertions(+), 55 deletions(-) delete mode 100644 amqp/logging.go create mode 100644 amqp/utils_test.go diff --git a/amqp/client.go b/amqp/client.go index 86182d636..e61e0df1e 100644 --- a/amqp/client.go +++ b/amqp/client.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/TheThingsNetwork/go-utils/log" AMQP "github.com/streadway/amqp" ) @@ -25,7 +26,7 @@ type Client interface { // DefaultClient is the default AMQP client for The Things Network type DefaultClient struct { url string - ctx Logger + ctx log.Interface conn *AMQP.Connection mutex *sync.Mutex channels map[*DefaultChannelClient]*AMQP.Channel @@ -39,7 +40,7 @@ type ChannelClient interface { // DefaultChannelClient represents the default client of an AMQP channel type DefaultChannelClient struct { - ctx Logger + ctx log.Interface client *DefaultClient channel *AMQP.Channel name string @@ -55,9 +56,9 @@ var ( ) // NewClient creates a new DefaultClient -func NewClient(ctx Logger, username, password, host string) Client { +func NewClient(ctx log.Interface, username, password, host string) Client { if ctx == nil { - ctx = &noopLogger{} + ctx = log.Get() } credentials := "guest:guest" if username != "" { diff --git a/amqp/client_test.go b/amqp/client_test.go index 2dc425158..ea96cb141 100644 --- a/amqp/client_test.go +++ b/amqp/client_test.go @@ -8,7 +8,6 @@ import ( "testing" "time" - . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" AMQP "github.com/streadway/amqp" ) @@ -24,13 +23,13 @@ func init() { func TestNewClient(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestNewClient"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestNewClient"), "guest", "guest", host) a.So(c, ShouldNotBeNil) } func TestConnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestConnect"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestConnect"), "guest", "guest", host) err := c.Connect() defer c.Disconnect() a.So(err, ShouldBeNil) @@ -45,7 +44,7 @@ func TestConnectInvalidAddress(t *testing.T) { a := New(t) ConnectRetries = 2 ConnectRetryDelay = 50 * time.Millisecond - c := NewClient(GetLogger(t, "TestConnectInvalidAddress"), "guest", "guest", "localhost:56720") + c := NewClient(getLogger(t, "TestConnectInvalidAddress"), "guest", "guest", "localhost:56720") err := c.Connect() defer c.Disconnect() a.So(err, ShouldNotBeNil) @@ -53,7 +52,7 @@ func TestConnectInvalidAddress(t *testing.T) { func TestIsConnected(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestIsConnected"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestIsConnected"), "guest", "guest", host) a.So(c.IsConnected(), ShouldBeFalse) @@ -65,7 +64,7 @@ func TestIsConnected(t *testing.T) { func TestDisconnect(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestDisconnect"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestDisconnect"), "guest", "guest", host) // Disconnecting when not connected should not change anything c.Disconnect() @@ -80,7 +79,7 @@ func TestDisconnect(t *testing.T) { func TestReopenChannelClient(t *testing.T) { a := New(t) - ctx := GetLogger(t, "TestReopenChannelClient") + ctx := getLogger(t, "TestReopenChannelClient") c := NewClient(ctx, "guest", "guest", host).(*DefaultClient) closed, err := c.connect(false) a.So(err, ShouldBeNil) diff --git a/amqp/downlink_test.go b/amqp/downlink_test.go index b36572300..b1932cde9 100644 --- a/amqp/downlink_test.go +++ b/amqp/downlink_test.go @@ -8,13 +8,12 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestPublishDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestPublishDownlink"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestPublishDownlink"), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() @@ -34,7 +33,7 @@ func TestPublishDownlink(t *testing.T) { func TestSubscribeDownlink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestSubscribeDownlink"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestSubscribeDownlink"), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() diff --git a/amqp/logging.go b/amqp/logging.go deleted file mode 100644 index 3760bcd73..000000000 --- a/amqp/logging.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright © 2016 The Things Network -// Use of this source code is governed by the MIT license that can be found in the LICENSE file. - -package amqp - -// Logger used in amqp package -type Logger interface { - Debug(msg string) - Info(msg string) - Warn(msg string) - Error(msg string) - Fatal(msg string) - Debugf(msg string, v ...interface{}) - Infof(msg string, v ...interface{}) - Warnf(msg string, v ...interface{}) - Errorf(msg string, v ...interface{}) - Fatalf(msg string, v ...interface{}) -} - -// noopLogger just does nothing -type noopLogger struct{} - -func (l noopLogger) Debug(msg string) {} -func (l noopLogger) Info(msg string) {} -func (l noopLogger) Warn(msg string) {} -func (l noopLogger) Error(msg string) {} -func (l noopLogger) Fatal(msg string) {} -func (l noopLogger) Debugf(msg string, v ...interface{}) {} -func (l noopLogger) Infof(msg string, v ...interface{}) {} -func (l noopLogger) Warnf(msg string, v ...interface{}) {} -func (l noopLogger) Errorf(msg string, v ...interface{}) {} -func (l noopLogger) Fatalf(msg string, v ...interface{}) {} diff --git a/amqp/publisher_test.go b/amqp/publisher_test.go index d609a349a..5732a2cbb 100644 --- a/amqp/publisher_test.go +++ b/amqp/publisher_test.go @@ -6,13 +6,12 @@ package amqp import ( "testing" - . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestOpenPublisher(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestOpenPublisher"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestOpenPublisher"), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() diff --git a/amqp/subscriber_test.go b/amqp/subscriber_test.go index c811788b2..18c394e49 100644 --- a/amqp/subscriber_test.go +++ b/amqp/subscriber_test.go @@ -6,13 +6,12 @@ package amqp import ( "testing" - . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestOpenSubscriber(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestOpenSubscriber"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestOpenSubscriber"), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() diff --git a/amqp/uplink_test.go b/amqp/uplink_test.go index 21b34d509..fd8f8a422 100644 --- a/amqp/uplink_test.go +++ b/amqp/uplink_test.go @@ -8,13 +8,12 @@ import ( "testing" "github.com/TheThingsNetwork/ttn/core/types" - . "github.com/TheThingsNetwork/ttn/utils/testing" . "github.com/smartystreets/assertions" ) func TestPublishUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestPublishUplink"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestPublishUplink"), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() @@ -34,7 +33,7 @@ func TestPublishUplink(t *testing.T) { func TestSubscribeUplink(t *testing.T) { a := New(t) - c := NewClient(GetLogger(t, "TestSubscribeUplink"), "guest", "guest", host) + c := NewClient(getLogger(t, "TestSubscribeUplink"), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() diff --git a/amqp/utils_test.go b/amqp/utils_test.go new file mode 100644 index 000000000..021a33c7a --- /dev/null +++ b/amqp/utils_test.go @@ -0,0 +1,16 @@ +// Copyright © 2016 The Things Network +// Use of this source code is governed by the MIT license that can be found in the LICENSE file. + +package amqp + +import ( + "testing" + + "github.com/TheThingsNetwork/go-utils/log" + "github.com/TheThingsNetwork/go-utils/log/apex" + tt "github.com/TheThingsNetwork/ttn/utils/testing" +) + +func getLogger(t *testing.T, tag string) log.Interface { + return apex.Wrap(tt.GetLogger(t, tag)) +} diff --git a/core/handler/amqp.go b/core/handler/amqp.go index 05a8de102..5b5a10308 100644 --- a/core/handler/amqp.go +++ b/core/handler/amqp.go @@ -6,12 +6,13 @@ package handler import ( "github.com/apex/log" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/amqp" "github.com/TheThingsNetwork/ttn/core/types" ) func (h *handler) HandleAMQP(username, password, host, exchange, downlinkQueue string) error { - h.amqpClient = amqp.NewClient(h.Ctx, username, password, host) + h.amqpClient = amqp.NewClient(apex.Wrap(h.Ctx), username, password, host) err := h.amqpClient.Connect() if err != nil { diff --git a/core/handler/amqp_test.go b/core/handler/amqp_test.go index d54d872a7..8ca425ba5 100644 --- a/core/handler/amqp_test.go +++ b/core/handler/amqp_test.go @@ -8,6 +8,7 @@ import ( "testing" "time" + "github.com/TheThingsNetwork/go-utils/log/apex" "github.com/TheThingsNetwork/ttn/amqp" "github.com/TheThingsNetwork/ttn/core/component" "github.com/TheThingsNetwork/ttn/core/handler/device" @@ -24,7 +25,7 @@ func TestHandleAMQP(t *testing.T) { a := New(t) var wg WaitGroup - c := amqp.NewClient(GetLogger(t, "TestHandleAMQP"), "guest", "guest", host) + c := amqp.NewClient(apex.Wrap(GetLogger(t, "TestHandleAMQP")), "guest", "guest", host) err := c.Connect() a.So(err, ShouldBeNil) defer c.Disconnect() From ece3bc52b8c090568f4128d874823c995b70a51c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Mon, 12 Dec 2016 14:57:58 +0100 Subject: [PATCH 2255/2266] Update Vendors --- vendor/vendor.json | 166 ++++++++++++++++++++++----------------------- 1 file changed, 83 insertions(+), 83 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index fcaa41dd2..a342dc50d 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -195,22 +195,22 @@ "revisionTime": "2016-11-16T06:46:58Z" }, { - "checksumSHA1": "aXC93Lo0MY4HRMSF7ANJ97VtulM=", + "checksumSHA1": "3yco0089CSJ4qbyUccpbDC2+dPg=", "path": "github.com/gogo/protobuf/gogoproto", - "revision": "1a78ea63e7f62d64c7630e5980a0dfd623545449", - "revisionTime": "2016-12-07T19:43:35Z" + "revision": "06ec6c31ff1bac6ed4e205a547a3d72934813ef3", + "revisionTime": "2016-12-10T18:20:26Z" }, { - "checksumSHA1": "PZNhiizCvRgRff2lcG9kRYJMXqI=", + "checksumSHA1": "6ZxSmrIx3Jd15aou16oG0HPylP4=", "path": "github.com/gogo/protobuf/proto", - "revision": "1a78ea63e7f62d64c7630e5980a0dfd623545449", - "revisionTime": "2016-12-07T19:43:35Z" + "revision": "06ec6c31ff1bac6ed4e205a547a3d72934813ef3", + "revisionTime": "2016-12-10T18:20:26Z" }, { - "checksumSHA1": "zVCBnBzjkmL1Gkll/7IbpKKprmw=", + "checksumSHA1": "EaY86bsi1nucvO0/UKvp/A72aC8=", "path": "github.com/gogo/protobuf/protoc-gen-gogo/descriptor", - "revision": "1a78ea63e7f62d64c7630e5980a0dfd623545449", - "revisionTime": "2016-12-07T19:43:35Z" + "revision": "06ec6c31ff1bac6ed4e205a547a3d72934813ef3", + "revisionTime": "2016-12-10T18:20:26Z" }, { "checksumSHA1": "JSHl8b3nI8EWvzm+uyrIqj2Hiu4=", @@ -405,10 +405,10 @@ "revisionTime": "2016-12-03T19:45:07Z" }, { - "checksumSHA1": "oouYEOJ4wFLlCWEgNy7/MWIzAfg=", + "checksumSHA1": "LAR/G/IY1GviHYkGAoi6kVXq1Jg=", "path": "github.com/mitchellh/mapstructure", - "revision": "5a0325d7fafaac12dda6e7fb8bd222ec1b69875e", - "revisionTime": "2016-12-04T05:35:18Z" + "revision": "bfdb1a85537d60bc7e954e600c250219ea497417", + "revisionTime": "2016-12-11T22:23:15Z" }, { "checksumSHA1": "QTLHIDIubrhweCegl4ZBJMramZ4=", @@ -555,16 +555,16 @@ "revisionTime": "2016-07-22T04:48:03Z" }, { - "checksumSHA1": "f2mjcLDkc28ImTfmedA5kfcOzUw=", + "checksumSHA1": "KEzQv4I7c+tcoTizQM+tavqKsuM=", "path": "github.com/spf13/afero", - "revision": "06b7e5f50606ecd49148a01a6008942d9b669217", - "revisionTime": "2016-11-09T00:09:53Z" + "revision": "2f30b2a92c0e5700bcfe4715891adb1f2a7a406d", + "revisionTime": "2016-12-08T18:21:42Z" }, { "checksumSHA1": "u6B0SEgZ/TUEfIvF6w/HnFVQbII=", "path": "github.com/spf13/afero/mem", - "revision": "06b7e5f50606ecd49148a01a6008942d9b669217", - "revisionTime": "2016-11-09T00:09:53Z" + "revision": "2f30b2a92c0e5700bcfe4715891adb1f2a7a406d", + "revisionTime": "2016-12-08T18:21:42Z" }, { "checksumSHA1": "+mfjYfgvbP8vg0ubsMOw/iTloo8=", @@ -597,10 +597,10 @@ "revisionTime": "2016-10-29T21:33:52Z" }, { - "checksumSHA1": "u/90tmP9R7gFX3vA0U2c3DLhjMc=", + "checksumSHA1": "BKXPIqMlpj20qhEM+7luvOl2PpM=", "path": "github.com/streadway/amqp", - "revision": "4da4cfa085c174b489ec35cf1e29ffb147d45298", - "revisionTime": "2016-12-06T10:42:06Z" + "revision": "cb4fb930736ebd61a54da180a6aa4e92b206ff13", + "revisionTime": "2016-12-10T20:40:49Z" }, { "checksumSHA1": "F9X1T07FTXRxBrskitXNtlxZJ6w=", @@ -623,68 +623,68 @@ { "checksumSHA1": "VE+WBfxeMNC5a98uLXK2Iu80hOU=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "95cb608f365d51e0e69abc646ec90c0e26fb427f", - "revisionTime": "2016-12-05T17:23:45Z" + "revision": "b07d8c96772f426812d3fc5530710ec1f3b205e7", + "revisionTime": "2016-10-26T23:47:36Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { - "checksumSHA1": "ydijX5KGTum3FoyPXzKJFeubMbE=", + "checksumSHA1": "BgKnt50LaV97izvgLqONWKIbeMw=", "path": "golang.org/x/net/http2", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "P9qTIn8a6L6Q9wd1IJBCuhno1Q8=", "path": "golang.org/x/net/trace", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=", "path": "golang.org/x/net/websocket", - "revision": "944c58e9d57bcecf171fd4bea155269aa8a96805", - "revisionTime": "2016-12-07T20:53:17Z" + "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", + "revisionTime": "2016-12-10T00:34:51Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", - "revision": "f6093e37b6cb4092101a298aba5d794eb570757f", - "revisionTime": "2016-11-07T21:06:47Z" + "revision": "da3ce8d62a7f77aadfda06cb82bd604d6469c645", + "revisionTime": "2016-12-09T11:58:39Z" }, { - "checksumSHA1": "D3v/aqfB9swlaZcSksCoF+lbOqo=", + "checksumSHA1": "Wqm34oALxi3GkTSHIBa/EcfE37Y=", "path": "golang.org/x/oauth2/internal", - "revision": "f6093e37b6cb4092101a298aba5d794eb570757f", - "revisionTime": "2016-11-07T21:06:47Z" + "revision": "da3ce8d62a7f77aadfda06cb82bd604d6469c645", + "revisionTime": "2016-12-09T11:58:39Z" }, { "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", @@ -695,38 +695,38 @@ { "checksumSHA1": "kv3jbPJGCczHVQ7g51am1MxlD1c=", "path": "golang.org/x/text/internal/gen", - "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", - "revisionTime": "2016-11-30T07:41:09Z" + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" }, { "checksumSHA1": "47nwiUyVBY2RKoEGXmCSvusY4Js=", "path": "golang.org/x/text/internal/triegen", - "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", - "revisionTime": "2016-11-30T07:41:09Z" + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" }, { "checksumSHA1": "Yd5wMObzagIfCiKLpZbtBIrOUA4=", "path": "golang.org/x/text/internal/ucd", - "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", - "revisionTime": "2016-11-30T07:41:09Z" + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" }, { "checksumSHA1": "ziMb9+ANGRJSSIuxYdRbA+cDRBQ=", "path": "golang.org/x/text/transform", - "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", - "revisionTime": "2016-11-30T07:41:09Z" + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" }, { "checksumSHA1": "i14IZXKECObKRUNvTr7xivSL1IU=", "path": "golang.org/x/text/unicode/cldr", - "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", - "revisionTime": "2016-11-30T07:41:09Z" + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" }, { "checksumSHA1": "Vircurgvsnt4k26havmxPM67PUA=", "path": "golang.org/x/text/unicode/norm", - "revision": "5c6cf4f9a2357d38515014cea8c488ed22bdab90", - "revisionTime": "2016-11-30T07:41:09Z" + "revision": "47a200a05c8b3fd1b698571caecbb68beb2611ec", + "revisionTime": "2016-11-30T21:25:21Z" }, { "checksumSHA1": "gYHoPrPncGO926bN0jr1rzDxBQU=", @@ -771,82 +771,82 @@ "revisionTime": "2016-11-15T22:01:06Z" }, { - "checksumSHA1": "+qB6gzApLVQj5Am4zO/a5VP+dCc=", + "checksumSHA1": "F/69y+hieV9WDDBwLOTdsLJg1q8=", "path": "google.golang.org/grpc", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "08icuA15HRkdYCt6H+Cs90RPQsY=", "path": "google.golang.org/grpc/codes", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "Vd1MU+Ojs7GeS6jE52vlxtXvIrI=", "path": "google.golang.org/grpc/credentials", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "3Lt5hNAG8qJAYSsNghR5uA1zQns=", "path": "google.golang.org/grpc/grpclog", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "d0iunsiWfA0qXxLMNkTC4tGJnOo=", "path": "google.golang.org/grpc/health", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "pSFXzfvPlaDBK2RsMcTiIeks4ok=", "path": "google.golang.org/grpc/health/grpc_health_v1", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "T3Q0p8kzvXFnRkMaK/G8mCv6mc0=", "path": "google.golang.org/grpc/internal", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "XXpD8+S3gLrfmCLOf+RbxblOQkU=", "path": "google.golang.org/grpc/metadata", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "4GSUFhOQ0kdFlBH4D5OTeKy78z0=", "path": "google.golang.org/grpc/naming", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "3RRoLeH6X2//7tVClOVzxW2bY+E=", "path": "google.golang.org/grpc/peer", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "4zzPK1BUgnOcugiN2vnkhUal4ls=", "path": "google.golang.org/grpc/stats", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "N0TftT6/CyWqp6VRi2DqDx60+Fo=", "path": "google.golang.org/grpc/tap", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "x+eyD2YGMYn973r3dQwGOMdi4mA=", "path": "google.golang.org/grpc/transport", - "revision": "7484960149bc6abec1aa7512626c4831a9fe9af2", - "revisionTime": "2016-12-08T01:33:01Z" + "revision": "8712952b7d646dbbbc6fb73a782174f3115060f3", + "revisionTime": "2016-12-09T21:45:00Z" }, { "checksumSHA1": "+4r0PnLwwyhO5/jvU5R/TEJb4kA=", From 7cf5aa96fe8a554ca0eeedad79aa235bd6884cea Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Dec 2016 14:27:30 +0100 Subject: [PATCH 2256/2266] Update go-account-lib vendor --- ttnctl/cmd/gateways_token.go | 8 +++--- ttnctl/cmd/join.go | 4 +-- ttnctl/cmd/uplink.go | 4 +-- vendor/vendor.json | 50 ++++++++++++++++++------------------ 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/ttnctl/cmd/gateways_token.go b/ttnctl/cmd/gateways_token.go index 77231f499..4736fcd79 100644 --- a/ttnctl/cmd/gateways_token.go +++ b/ttnctl/cmd/gateways_token.go @@ -34,17 +34,17 @@ var gatewaysTokenCmd = &cobra.Command{ if err != nil { ctx.WithError(err).Fatal("Could not get gateway token") } - if token.Token == "" { + if token.AccessToken == "" { ctx.Fatal("Gateway token was empty") } ctx.Info("Got gateway token") fmt.Println() - fmt.Println(token.Token) + fmt.Println(token.AccessToken) fmt.Println() - if !token.Expires.IsZero() { - fmt.Printf("Expires %s\n", token.Expires) + if !token.Expiry.IsZero() { + fmt.Printf("Expires %s\n", token.Expiry) fmt.Println() } }, diff --git a/ttnctl/cmd/join.go b/ttnctl/cmd/join.go index da4fbbccd..c7bb75ad8 100644 --- a/ttnctl/cmd/join.go +++ b/ttnctl/cmd/join.go @@ -62,8 +62,8 @@ var joinCmd = &cobra.Command{ ctx.WithError(err).Warn("Could not get gateway token") ctx.Warn("Trying without token. Your message may not be processed by the router") gatewayToken = "" - } else if token != nil && token.Token != "" { - gatewayToken = token.Token + } else if token != nil && token.AccessToken != "" { + gatewayToken = token.AccessToken } } diff --git a/ttnctl/cmd/uplink.go b/ttnctl/cmd/uplink.go index 5d89ca049..bcbdc008c 100644 --- a/ttnctl/cmd/uplink.go +++ b/ttnctl/cmd/uplink.go @@ -71,8 +71,8 @@ var uplinkCmd = &cobra.Command{ ctx.WithError(err).Warn("Could not get gateway token") ctx.Warn("Trying without token. Your message may not be processed by the router") gatewayToken = "" - } else if token != nil && token.Token != "" { - gatewayToken = token.Token + } else if token != nil && token.AccessToken != "" { + gatewayToken = token.AccessToken } } diff --git a/vendor/vendor.json b/vendor/vendor.json index a342dc50d..b981e4885 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -15,70 +15,70 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "nXjI+Ldbj4poEcdG2qG6mJJSwHw=", + "checksumSHA1": "8hEuimIg/6UgM988+dLV0//ZfSg=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "EDBQe1RJEmQmVvdMp6MHXW5Px9c=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "iXtQRpCIHmyEn70SWf5ZdNqtkhk=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { - "checksumSHA1": "oZso6YaE7ogZLUZzUCA3yYDym0E=", + "checksumSHA1": "8b9wBzff/53hS7dHk3Ml9Hb2CoY=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "RpKXQd5sp9/jsWM991S7OhE9/ME=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "R0TfhR++1b5YqHsMso4QKiR3IAI=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { - "checksumSHA1": "SiXlhEd19Cs/PZqeBOYpvTsHRzM=", + "checksumSHA1": "Fw0V3OKAMqqtrkVlKYFAmDkq3TQ=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "4bd767d46725982809cde03b7ed3ff72b6b87735", - "revisionTime": "2016-12-08T10:46:26Z" + "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", + "revisionTime": "2016-12-13T13:00:32Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", From 727a56dc640c2a1dc4560562b60bc426e8004c6e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Dec 2016 18:12:53 +0100 Subject: [PATCH 2257/2266] Pass "ttnctl" and version in AS req headers --- ttnctl/cmd/user_login.go | 2 +- ttnctl/cmd/user_register.go | 3 ++- ttnctl/util/account.go | 18 ++++++++++++- vendor/vendor.json | 50 ++++++++++++++++++------------------- 4 files changed, 45 insertions(+), 28 deletions(-) diff --git a/ttnctl/cmd/user_login.go b/ttnctl/cmd/user_login.go index 7c2ab9877..5f9d169ef 100644 --- a/ttnctl/cmd/user_login.go +++ b/ttnctl/cmd/user_login.go @@ -35,7 +35,7 @@ $ ttnctl user login [paste the access code you requested above] ctx.WithError(err).Fatal("Login failed") } - acc := account.New(viper.GetString("auth-server")) + acc := account.New(viper.GetString("auth-server")).WithHeader("User-Agent", util.GetUserAgent()) acc.WithAuth(auth.AccessToken(token.AccessToken)) profile, err := acc.Profile() if err != nil { diff --git a/ttnctl/cmd/user_register.go b/ttnctl/cmd/user_register.go index c63bea4de..834731486 100644 --- a/ttnctl/cmd/user_register.go +++ b/ttnctl/cmd/user_register.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/TheThingsNetwork/go-account-lib/account" + "github.com/TheThingsNetwork/ttn/ttnctl/util" "github.com/howeyc/gopass" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -33,7 +34,7 @@ Password: if err != nil { ctx.Fatal(err.Error()) } - acc := account.New(viper.GetString("auth-server")) + acc := account.New(viper.GetString("auth-server")).WithHeader("User-Agent", util.GetUserAgent()) err = acc.RegisterUser(username, email, string(password)) if err != nil { ctx.WithError(err).Fatal("Could not register user") diff --git a/ttnctl/util/account.go b/ttnctl/util/account.go index ddfe2bc57..56155e730 100644 --- a/ttnctl/util/account.go +++ b/ttnctl/util/account.go @@ -5,8 +5,10 @@ package util import ( "encoding/json" + "fmt" "os" "path" + "runtime" "strings" "time" @@ -46,11 +48,25 @@ func GetTokenCache() cache.Cache { return cache.FileCacheWithNameFn(GetDataDir(), tokenFilename) } +func GetUserAgent() string { + return fmt.Sprintf( + "ttnctl/%s-%s (%s-%s) (%s)", + viper.GetString("version"), + viper.GetString("gitCommit"), + runtime.GOOS, + runtime.GOARCH, + GetID(), + ) +} + // getOAuth gets the OAuth client func getOAuth() *oauth.Config { return oauth.OAuth(viper.GetString("auth-server"), &oauth.Client{ ID: "ttnctl", Secret: "ttnctl", + ExtraHeaders: map[string]string{ + "User-Agent": GetUserAgent(), + }, }) } @@ -139,7 +155,7 @@ func GetAccount(ctx log.Interface) *account.Account { server := viper.GetString("auth-server") manager := GetTokenManager(token.AccessToken) - return account.NewWithManager(server, token.AccessToken, manager) + return account.NewWithManager(server, token.AccessToken, manager).WithHeader("User-Agent", GetUserAgent()) } // Login does a login to the Account server with the given username and password diff --git a/vendor/vendor.json b/vendor/vendor.json index b981e4885..42c1d0f9e 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -15,70 +15,70 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "8hEuimIg/6UgM988+dLV0//ZfSg=", + "checksumSHA1": "qLBKAnXk9gnqqEYxTr58f/7TdX4=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "EDBQe1RJEmQmVvdMp6MHXW5Px9c=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "iXtQRpCIHmyEn70SWf5ZdNqtkhk=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { - "checksumSHA1": "8b9wBzff/53hS7dHk3Ml9Hb2CoY=", + "checksumSHA1": "waV2cDv9k1cvbWiom6kgnVDWrQo=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "RpKXQd5sp9/jsWM991S7OhE9/ME=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "R0TfhR++1b5YqHsMso4QKiR3IAI=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { - "checksumSHA1": "Fw0V3OKAMqqtrkVlKYFAmDkq3TQ=", + "checksumSHA1": "42dAT68+66q7c7Dwm5AVxYi8rIE=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "063bc7ccf3a0e6cf1899c3d3ef4081e2bff077a6", - "revisionTime": "2016-12-13T13:00:32Z" + "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", + "revisionTime": "2016-12-13T16:33:40Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", From 5b371f1242c6335996c8fc8e1f38d2294ea970fc Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Tue, 13 Dec 2016 18:38:05 +0100 Subject: [PATCH 2258/2266] Remove ttn-account-staging issuer --- .env/broker/dev.yml | 1 - .env/discovery/dev.yml | 1 - .env/handler/dev.yml | 1 - .env/networkserver/dev.yml | 1 - .env/router/dev.yml | 1 - 5 files changed, 5 deletions(-) diff --git a/.env/broker/dev.yml b/.env/broker/dev.yml index 999a6cd0f..284171401 100644 --- a/.env/broker/dev.yml +++ b/.env/broker/dev.yml @@ -4,7 +4,6 @@ discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" - ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/broker/" diff --git a/.env/discovery/dev.yml b/.env/discovery/dev.yml index e0de476c9..175f3c49d 100644 --- a/.env/discovery/dev.yml +++ b/.env/discovery/dev.yml @@ -4,5 +4,4 @@ discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" - ttn-account-staging: "https://preview.account.thethingsnetwork.org" key-dir: "./.env/discovery/" diff --git a/.env/handler/dev.yml b/.env/handler/dev.yml index 14b23be58..a292d14b6 100644 --- a/.env/handler/dev.yml +++ b/.env/handler/dev.yml @@ -4,7 +4,6 @@ discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" - ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/handler/" handler: diff --git a/.env/networkserver/dev.yml b/.env/networkserver/dev.yml index ee393bd41..ddca01c26 100644 --- a/.env/networkserver/dev.yml +++ b/.env/networkserver/dev.yml @@ -3,6 +3,5 @@ discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" - ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/networkserver/" diff --git a/.env/router/dev.yml b/.env/router/dev.yml index 0fa93434a..5adc0df17 100644 --- a/.env/router/dev.yml +++ b/.env/router/dev.yml @@ -4,7 +4,6 @@ discovery-address: "localhost:1900" auth-servers: ttn-account: "https://account.thethingsnetwork.org" ttn-account-preview: "https://preview.account.thethingsnetwork.org" - ttn-account-staging: "https://preview.account.thethingsnetwork.org" tls: true key-dir: "./.env/router/" router: From 91c82cd6185f7d8c37dd4ad057b677a50ba5d1b3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 08:35:32 +0100 Subject: [PATCH 2259/2266] Add master branch to Gitlab Build --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 11d818481..604cd738c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,6 +59,7 @@ sign: only: - v1-staging@thethingsnetwork/ttn - v2-preview@thethingsnetwork/ttn + - master@thethingsnetwork/ttn stage: sign image: golang:latest script: @@ -87,6 +88,7 @@ dockerhub-image: only: - v1-staging@thethingsnetwork/ttn - v2-preview@thethingsnetwork/ttn + - master@thethingsnetwork/ttn stage: package image: docker:git services: @@ -101,6 +103,7 @@ azure-binaries: only: - v1-staging@thethingsnetwork/ttn - v2-preview@thethingsnetwork/ttn + - master@thethingsnetwork/ttn stage: package image: registry.gitlab.com/thethingsindustries/upload script: From 1adcd243bd8a6ab844583f67c189b3c6b937550e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 10:22:58 +0100 Subject: [PATCH 2260/2266] Upload binaries and images also on git tags --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 604cd738c..2e3ba5e78 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -57,6 +57,7 @@ binaries: sign: only: + - tags - v1-staging@thethingsnetwork/ttn - v2-preview@thethingsnetwork/ttn - master@thethingsnetwork/ttn @@ -86,6 +87,7 @@ gitlab-image: dockerhub-image: only: + - tags - v1-staging@thethingsnetwork/ttn - v2-preview@thethingsnetwork/ttn - master@thethingsnetwork/ttn @@ -101,6 +103,7 @@ dockerhub-image: azure-binaries: only: + - tags - v1-staging@thethingsnetwork/ttn - v2-preview@thethingsnetwork/ttn - master@thethingsnetwork/ttn From a1c18d6b48d93d9b2a390a149f8213cad5293d10 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 10:23:19 +0100 Subject: [PATCH 2261/2266] Build "latest" Docker image on master commits --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2e3ba5e78..0ee657d48 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -100,6 +100,7 @@ dockerhub-image: - docker login -u "$DOCKERHUB_USER" -p "$DOCKERHUB_PASSWORD" - docker tag ttn $CONTAINER_NAME:$CI_BUILD_REF_NAME - docker push $CONTAINER_NAME:$CI_BUILD_REF_NAME + - if [[ "$CI_BUILD_REF_NAME" == "master" ]]; then docker tag ttn $CONTAINER_NAME:latest && docker push $CONTAINER_NAME:latest; fi azure-binaries: only: From 35deb2c6b5c3d4c59ba3c729633121691374737e Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 10:50:19 +0100 Subject: [PATCH 2262/2266] Update vendors --- vendor/vendor.json | 184 ++++++++++++++++++++++----------------------- 1 file changed, 92 insertions(+), 92 deletions(-) diff --git a/vendor/vendor.json b/vendor/vendor.json index 42c1d0f9e..2cc6ab566 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -15,70 +15,70 @@ "revisionTime": "2016-08-11T21:45:55Z" }, { - "checksumSHA1": "qLBKAnXk9gnqqEYxTr58f/7TdX4=", + "checksumSHA1": "nIPYBc7nATMXCsoZLRtygo0EuTU=", "path": "github.com/TheThingsNetwork/go-account-lib/account", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "EDBQe1RJEmQmVvdMp6MHXW5Px9c=", "path": "github.com/TheThingsNetwork/go-account-lib/auth", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "crVnJMO1PZvU90k+O+Dj+yAkLu4=", "path": "github.com/TheThingsNetwork/go-account-lib/cache", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "iXtQRpCIHmyEn70SWf5ZdNqtkhk=", "path": "github.com/TheThingsNetwork/go-account-lib/claims", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "3IiXhWt/UvtK73ANnQVxm0g9uGU=", "path": "github.com/TheThingsNetwork/go-account-lib/keys", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "waV2cDv9k1cvbWiom6kgnVDWrQo=", "path": "github.com/TheThingsNetwork/go-account-lib/oauth", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "TVfGzHJqrzQhJMGR8D47HjJw28k=", "path": "github.com/TheThingsNetwork/go-account-lib/rights", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "SvUkgVuVVVssqpXbE8OfeWCm0KU=", "path": "github.com/TheThingsNetwork/go-account-lib/scope", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "RpKXQd5sp9/jsWM991S7OhE9/ME=", "path": "github.com/TheThingsNetwork/go-account-lib/tokenkey", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "R0TfhR++1b5YqHsMso4QKiR3IAI=", "path": "github.com/TheThingsNetwork/go-account-lib/tokens", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "42dAT68+66q7c7Dwm5AVxYi8rIE=", "path": "github.com/TheThingsNetwork/go-account-lib/util", - "revision": "dec29a829b07fdbe5232ef3a0ec2183f34e2ff56", - "revisionTime": "2016-12-13T16:33:40Z" + "revision": "6d65fa856c8916960532858c5fadf19914e7df7b", + "revisionTime": "2016-12-13T21:17:34Z" }, { "checksumSHA1": "T7iFQUlCUAv4cJNDZC0//46Nbio=", @@ -135,10 +135,10 @@ "revisionTime": "2016-10-01T16:31:30Z" }, { - "checksumSHA1": "eKlcnwFZSIut8pNEvkLrm3entgc=", + "checksumSHA1": "h/y88wOmQRm6qE29txL9LndV1yY=", "path": "github.com/bluele/gcache", - "revision": "740e70e1c445f08ae353214243a4fb0fbe970510", - "revisionTime": "2016-10-27T05:39:47Z" + "revision": "d920a928be099e4b9a6272f41699f4693cdcee5b", + "revisionTime": "2016-12-12T14:19:04Z" }, { "checksumSHA1": "7K5mkZjohicK/GIgyVwLCkX7vO4=", @@ -423,10 +423,10 @@ "revisionTime": "2016-01-24T19:35:03Z" }, { - "checksumSHA1": "n+1TqBSxUOC3ajo+8ZgQHgpQTsw=", + "checksumSHA1": "8HO0U5Iblu0bhhmmp6kSGwHBBak=", "path": "github.com/pelletier/go-toml", - "revision": "ce7be745f09fe4ff89af8e3ea744e1deabf20ee3", - "revisionTime": "2016-12-03T11:32:16Z" + "revision": "017119f7a78a0b5fc0ea39ef6be09f03acf3345d", + "revisionTime": "2016-12-13T14:20:06Z" }, { "checksumSHA1": "ynJSWoF6v+3zMnh9R0QmmG6iGV8=", @@ -485,44 +485,44 @@ { "checksumSHA1": "ujFjoR6H3TDeiuY9kvvwSwBMcJk=", "path": "github.com/shirou/gopsutil/cpu", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { "checksumSHA1": "A5OQcD4rTEhWAfWvp6lzFBA9lfs=", "path": "github.com/shirou/gopsutil/host", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { "checksumSHA1": "sNAWEDfrq5thpuxsiTvRNJ1fii0=", "path": "github.com/shirou/gopsutil/internal/common", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { "checksumSHA1": "jB8En6qWQ7G2yPJey4uY1FvOjWM=", "path": "github.com/shirou/gopsutil/load", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { "checksumSHA1": "sOBIj+eocRSO0xtX8vkJDZKTDl8=", "path": "github.com/shirou/gopsutil/mem", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { - "checksumSHA1": "TkJnrLSoB56wbzGI6hzsmMlSBj8=", + "checksumSHA1": "OHn1U/wFzUGaaBqpsPt5d90nucY=", "path": "github.com/shirou/gopsutil/net", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { "checksumSHA1": "+4bS/41wTuGe0oczEwrBCWp6YO8=", "path": "github.com/shirou/gopsutil/process", - "revision": "449c8250b0e837d670b2f9e7337e90ad00514b0c", - "revisionTime": "2016-12-02T13:28:38Z" + "revision": "c42889c31346d051d87b6b1bf8ba4745b4e485da", + "revisionTime": "2016-12-12T13:47:19Z" }, { "checksumSHA1": "Nve7SpDmjsv6+rhkXAkfg/UQx94=", @@ -531,22 +531,22 @@ "revisionTime": "2016-09-30T03:27:40Z" }, { - "checksumSHA1": "8sTogvgjVmUeLd4vWfcARLDBksg=", + "checksumSHA1": "RPVh5/wbvFG0q0CWHXifXpkYTDg=", "path": "github.com/smartystreets/assertions", - "revision": "e60cfa771e3f4d18723a4119f1833898c9c62066", - "revisionTime": "2016-11-10T22:55:57Z" + "revision": "26acb9229f421449ac63d014995b282d59261a8b", + "revisionTime": "2016-12-13T22:48:10Z" }, { "checksumSHA1": "Vzb+dEH/LTYbvr8RXHmt6xJHz04=", "path": "github.com/smartystreets/assertions/internal/go-render/render", - "revision": "e60cfa771e3f4d18723a4119f1833898c9c62066", - "revisionTime": "2016-11-10T22:55:57Z" + "revision": "26acb9229f421449ac63d014995b282d59261a8b", + "revisionTime": "2016-12-13T22:48:10Z" }, { "checksumSHA1": "dSZQzhiGN0tEILHxUZcrFFNW2Xw=", "path": "github.com/smartystreets/assertions/internal/oglematchers", - "revision": "e60cfa771e3f4d18723a4119f1833898c9c62066", - "revisionTime": "2016-11-10T22:55:57Z" + "revision": "26acb9229f421449ac63d014995b282d59261a8b", + "revisionTime": "2016-12-13T22:48:10Z" }, { "checksumSHA1": "iy7TNc01LWFOGwRwD6v0iDRqtLU=", @@ -585,16 +585,16 @@ "revisionTime": "2016-03-01T12:00:06Z" }, { - "checksumSHA1": "GxPD7A0NjMDom1xte0mghkpzr0E=", + "checksumSHA1": "AxfxmmBpbjQoaQKXbERcEw9pv+U=", "path": "github.com/spf13/pflag", - "revision": "5ccb023bc27df288a957c5e994cd44fd19619465", - "revisionTime": "2016-10-24T13:13:51Z" + "revision": "25f8b5b07aece3207895bf19f7ab517eb3b22a40", + "revisionTime": "2016-12-14T04:49:49Z" }, { - "checksumSHA1": "802GjFNHMmnFXEIkQ137ucUUacI=", + "checksumSHA1": "Ru5RGygreAp5OW9B6kO5Zawjt08=", "path": "github.com/spf13/viper", - "revision": "651d9d916abc3c3d6a91a12549495caba5edffd2", - "revisionTime": "2016-10-29T21:33:52Z" + "revision": "5ed0fc31f7f453625df314d8e66b9791e8d13003", + "revisionTime": "2016-12-13T09:38:49Z" }, { "checksumSHA1": "BKXPIqMlpj20qhEM+7luvOl2PpM=", @@ -623,68 +623,68 @@ { "checksumSHA1": "VE+WBfxeMNC5a98uLXK2Iu80hOU=", "path": "golang.org/x/crypto/ssh/terminal", - "revision": "b07d8c96772f426812d3fc5530710ec1f3b205e7", - "revisionTime": "2016-10-26T23:47:36Z" + "revision": "9a6f0a01987842989747adff311d80750ba25530", + "revisionTime": "2016-12-10T14:54:14Z" }, { "checksumSHA1": "9jjO5GjLa0XF/nfWihF02RoH4qc=", "path": "golang.org/x/net/context", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "BgKnt50LaV97izvgLqONWKIbeMw=", "path": "golang.org/x/net/http2", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "HzuGD7AwgC0p1az1WAQnEFnEk98=", "path": "golang.org/x/net/http2/hpack", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "GIGmSrYACByf5JDIP9ByBZksY80=", "path": "golang.org/x/net/idna", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "/k7k6eJDkxXx6K9Zpo/OwNm58XM=", "path": "golang.org/x/net/internal/timeseries", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "3xyuaSNmClqG4YWC7g0isQIbUTc=", "path": "golang.org/x/net/lex/httplex", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "P9qTIn8a6L6Q9wd1IJBCuhno1Q8=", "path": "golang.org/x/net/trace", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "7EZyXN0EmZLgGxZxK01IJua4c8o=", "path": "golang.org/x/net/websocket", - "revision": "b1a2d6e8c8b5fc8f601ead62536f02a8e1b6217d", - "revisionTime": "2016-12-10T00:34:51Z" + "revision": "cfae461cedfdcab6e261a26eb77db53695623303", + "revisionTime": "2016-12-13T01:09:39Z" }, { "checksumSHA1": "XH7CgbL5Z8COUc+MKrYqS3FFosY=", "path": "golang.org/x/oauth2", - "revision": "da3ce8d62a7f77aadfda06cb82bd604d6469c645", - "revisionTime": "2016-12-09T11:58:39Z" + "revision": "96382aa079b72d8c014eb0c50f6c223d1e6a2de0", + "revisionTime": "2016-12-13T06:27:07Z" }, { "checksumSHA1": "Wqm34oALxi3GkTSHIBa/EcfE37Y=", "path": "golang.org/x/oauth2/internal", - "revision": "da3ce8d62a7f77aadfda06cb82bd604d6469c645", - "revisionTime": "2016-12-09T11:58:39Z" + "revision": "96382aa079b72d8c014eb0c50f6c223d1e6a2de0", + "revisionTime": "2016-12-13T06:27:07Z" }, { "checksumSHA1": "8SH0adTcQlA+W5dzqiQ3Hft2VXg=", @@ -855,40 +855,40 @@ "revisionTime": "2016-02-20T15:49:07Z" }, { - "checksumSHA1": "JtXTQXRlxRB///NYmPDuMpEpvNI=", + "checksumSHA1": "39B/Bm5H2lA8P/j1wEY2sAryXzA=", "path": "gopkg.in/redis.v5", - "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", - "revisionTime": "2016-12-03T15:45:52Z" + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" }, { "checksumSHA1": "vQSE4FOH4EvyzYA72w60XOetmVY=", "path": "gopkg.in/redis.v5/internal", - "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", - "revisionTime": "2016-12-03T15:45:52Z" + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" }, { "checksumSHA1": "2Ek4SixeRSKOX3mUiBMs3Aw+Guc=", "path": "gopkg.in/redis.v5/internal/consistenthash", - "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", - "revisionTime": "2016-12-03T15:45:52Z" + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" }, { "checksumSHA1": "rJYVKcBrwYUGl7nuuusmZGrt8mY=", "path": "gopkg.in/redis.v5/internal/hashtag", - "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", - "revisionTime": "2016-12-03T15:45:52Z" + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" }, { "checksumSHA1": "VnsHRPAMRMuhz7/n/85MZwMrchQ=", "path": "gopkg.in/redis.v5/internal/pool", - "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", - "revisionTime": "2016-12-03T15:45:52Z" + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" }, { "checksumSHA1": "604uyPTNWLBNAnAyNRMiwYHXknA=", "path": "gopkg.in/redis.v5/internal/proto", - "revision": "854c88a72c8bb9c09936145aef886b7697d6b995", - "revisionTime": "2016-12-03T15:45:52Z" + "revision": "c6acf2ed159b22defbd9f077686cff03eba1e9b3", + "revisionTime": "2016-12-12T15:42:18Z" }, { "checksumSHA1": "omNnn0E5H5b9BuDZN5fUXBnETl8=", From 637554a7befbcb5dfe3a1a8bc18994f43ed196b9 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 10:50:50 +0100 Subject: [PATCH 2263/2266] Prepare merge to master --- .gitlab-ci.yml | 3 --- README.md | 4 ++-- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0ee657d48..e038455e3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -59,7 +59,6 @@ sign: only: - tags - v1-staging@thethingsnetwork/ttn - - v2-preview@thethingsnetwork/ttn - master@thethingsnetwork/ttn stage: sign image: golang:latest @@ -89,7 +88,6 @@ dockerhub-image: only: - tags - v1-staging@thethingsnetwork/ttn - - v2-preview@thethingsnetwork/ttn - master@thethingsnetwork/ttn stage: package image: docker:git @@ -106,7 +104,6 @@ azure-binaries: only: - tags - v1-staging@thethingsnetwork/ttn - - v2-preview@thethingsnetwork/ttn - master@thethingsnetwork/ttn stage: package image: registry.gitlab.com/thethingsindustries/upload diff --git a/README.md b/README.md index 671503ebf..2324188a8 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ The Things Network ================== -[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=develop)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) [![Coverage Status](https://coveralls.io/repos/github/TheThingsNetwork/ttn/badge.svg?branch=develop)](https://coveralls.io/github/TheThingsNetwork/ttn?branch=develop) +[![Build Status](https://travis-ci.org/TheThingsNetwork/ttn.svg?branch=master)](https://travis-ci.org/TheThingsNetwork/ttn) [![Slack Status](https://slack.thethingsnetwork.org/badge.svg)](https://slack.thethingsnetwork.org/) [![Coverage Status](https://coveralls.io/repos/github/TheThingsNetwork/ttn/badge.svg?branch=master)](https://coveralls.io/github/TheThingsNetwork/ttn?branch=master) ![The Things Network](http://thethingsnetwork.org/static/ttn/media/The%20Things%20Uitlijning.svg) @@ -31,7 +31,7 @@ When you get started with The Things Network, you'll probably have some question ## Set up The Things Network's backend for Development 1. Fork this repository -2. Clone your fork: `git clone --branch v2-preview --recursive https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` +2. Clone your fork: `git clone https://github.com/YOURUSERNAME/ttn.git $GOPATH/src/github.com/TheThingsNetwork/ttn` 3. `cd $GOPATH/src/github.com/TheThingsNetwork/ttn` 4. Install the dependencies for development: `make dev-deps` 5. Run the tests: `make test` From 6f678749488a6b285fcd22c572a02fb1e75c0a66 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 11:54:42 +0100 Subject: [PATCH 2264/2266] Prepare docs for v2.0.0 --- .github/ISSUE_TEMPLATE.md | 2 +- api/discovery/Discovery.md | 4 ++-- utils/protoc-gen-ttndoc/json.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 380c39283..c44466f35 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,4 +1,4 @@ -This is a **{bug report/feature request/question/...}** for **{the Backend/ttnctl/the Console/the NOC/an integration}** (**{v1-staging/v2-preview}**). +This is a **{bug report/feature request/question/...}** for **{the Backend/ttnctl/the Console/the NOC/an integration}**. - Explain what you want to do - Explain what steps you took diff --git a/api/discovery/Discovery.md b/api/discovery/Discovery.md index cc90df2fd..d23a5c41d 100644 --- a/api/discovery/Discovery.md +++ b/api/discovery/Discovery.md @@ -53,7 +53,7 @@ Get all announcements for a specific service type "public": true, "public_key": "-----BEGIN PUBLIC KEY-----\n...", "service_name": "handler", - "service_version": "2.0.0-dev-abcdef...", + "service_version": "2.0.0-abcdef...", "url": "" } ] @@ -99,7 +99,7 @@ Get a specific announcement "public": true, "public_key": "-----BEGIN PUBLIC KEY-----\n...", "service_name": "handler", - "service_version": "2.0.0-dev-abcdef...", + "service_version": "2.0.0-abcdef...", "url": "" } ``` diff --git a/utils/protoc-gen-ttndoc/json.go b/utils/protoc-gen-ttndoc/json.go index 3a565e3d0..be5ca236a 100644 --- a/utils/protoc-gen-ttndoc/json.go +++ b/utils/protoc-gen-ttndoc/json.go @@ -16,7 +16,7 @@ var exampleValues = map[string]interface{}{ ".discovery.Announcement.net_address": "eu.thethings.network:1904", ".discovery.Announcement.public_key": "-----BEGIN PUBLIC KEY-----\n...", ".discovery.Announcement.public": true, - ".discovery.Announcement.service_version": "2.0.0-dev-abcdef...", + ".discovery.Announcement.service_version": "2.0.0-abcdef...", ".discovery.Metadata.dev_addr_prefix": "AAAAAAA=", ".discovery.Metadata.app_id": "some-app-id", ".handler.*.app_id": "some-app-id", From fa0271c5047691dd493dc79aebc90cfa0b0d2b6c Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 12:07:35 +0100 Subject: [PATCH 2265/2266] Insert ttn/ttnctl version with make --- Makefile | 9 ++++++++- main.go | 2 +- ttnctl/main.go | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index b74320f86..fb5060531 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ SHELL = bash GIT_BRANCH = $(or $(CI_BUILD_REF_NAME) ,`git rev-parse --abbrev-ref HEAD 2>/dev/null`) GIT_COMMIT = $(or $(CI_BUILD_REF), `git rev-parse HEAD 2>/dev/null`) +GIT_TAG = $(shell git describe --abbrev=0 --tags) BUILD_DATE = $(or $(CI_BUILD_DATE), `date -u +%Y-%m-%dT%H:%M:%SZ`) GO_PATH = $(shell echo $(GOPATH) | awk -F':' '{print $$1}') PARENT_DIRECTORY= $(shell dirname $(PWD)) @@ -109,12 +110,18 @@ GOARCH ?= $(shell go env GOARCH) GOEXE = $(shell GOOS=$(GOOS) GOARCH=$(GOARCH) go env GOEXE) CGO_ENABLED ?= 0 +ifeq ($(GIT_BRANCH), $(GIT_TAG)) + TTN_VERSION = $(GIT_TAG) +else + TTN_VERSION = $(GIT_TAG)-dev +endif + DIST_FLAGS ?= -a -installsuffix cgo splitfilename = $(subst ., ,$(subst -, ,$(subst $(RELEASE_DIR)/,,$1))) GOOSfromfilename = $(word 2, $(call splitfilename, $1)) GOARCHfromfilename = $(word 3, $(call splitfilename, $1)) -LDFLAGS = -ldflags "-w -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" +LDFLAGS = -ldflags "-w -X main.version=${TTN_VERSION} -X main.gitBranch=${GIT_BRANCH} -X main.gitCommit=${GIT_COMMIT} -X main.buildDate=${BUILD_DATE}" GOBUILD = CGO_ENABLED=$(CGO_ENABLED) GOOS=$(call GOOSfromfilename, $@) GOARCH=$(call GOARCHfromfilename, $@) go build $(DIST_FLAGS) ${LDFLAGS} -tags "${TAGS}" -o "$@" ttn: $(RELEASE_DIR)/ttn-$(GOOS)-$(GOARCH)$(GOEXE) diff --git a/main.go b/main.go index e0a051fcb..03e5f21ee 100644 --- a/main.go +++ b/main.go @@ -9,7 +9,7 @@ import ( ) var ( - version = "2.0.0-dev" + version = "2.x.x" gitBranch = "unknown" gitCommit = "unknown" buildDate = "unknown" diff --git a/ttnctl/main.go b/ttnctl/main.go index 06f78c670..4e359d272 100644 --- a/ttnctl/main.go +++ b/ttnctl/main.go @@ -9,7 +9,7 @@ import ( ) var ( - version = "2.0.0-dev" + version = "2.x.x" gitBranch = "unknown" gitCommit = "unknown" buildDate = "unknown" From f8488947a6d3e812f366c4202d2903c4c67744a3 Mon Sep 17 00:00:00 2001 From: Hylke Visser Date: Wed, 14 Dec 2016 12:11:12 +0100 Subject: [PATCH 2266/2266] Add HISTORY.md --- HISTORY.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 HISTORY.md diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 000000000..63a72b1af --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,21 @@ +# History + +## 2.0.0 (2016-12-14) + +With the 2.0.0 release we now declare the v2 systems "out of preview". + +## v2-preview (2016-08-21) + +The v2-preview is a rewrite of almost all backend code, following a clear separation of concerns between components. Instead of using hard to read EUIs for applications and devices, you can now work with IDs that you can choose yourself. We added many new features and are sure that you'll love them. + +## v1-staging (2016-04-18) + +With the "staging" release we introduced device management, downlink messages, over the air activation, message encryption and MQTT feeds for receiving messages. + +![the command-line interface for managing devices](https://ttn.blob.core.windows.net/upload/ttnctl-staging.png) + +## v0-croft (2016-08-20) + +The day before the official launch of The Things Network, we sent our first text with an application built on The Things Network. + +![iPhone showing the first message sent over The Things Network](https://ttn.blob.core.windows.net/upload/slack_for_ios_upload_1024.jpg)